diff --git a/.coveragerc b/.coveragerc index 94ecfe88ffc..dffe22a6f46 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # -# Copyright 2024 Google LLC +# Copyright 2023 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -34,5 +34,6 @@ exclude_lines = omit = */gapic/*.py */proto/*.py + */core/*.py */site-packages/*.py google/cloud/__init__.py diff --git a/.flake8 b/.flake8 index 32986c79287..87f6e408c47 100644 --- a/.flake8 +++ b/.flake8 @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # -# Copyright 2024 Google LLC +# Copyright 2023 Google LLC # # 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/.gemini/common/constraints.md b/.gemini/common/constraints.md deleted file mode 100644 index 1b563eab3b8..00000000000 --- a/.gemini/common/constraints.md +++ /dev/null @@ -1,8 +0,0 @@ -## Constraints - -- Only add git commits. Do not change git history. -- Follow the spec file for development. - - Check off items in the "Acceptance - criteria" and "Detailed steps" sections with `[x]`. - - Please do this as they are completed. - - Refer back to the spec after each step. diff --git a/.gemini/common/docs.md b/.gemini/common/docs.md deleted file mode 100644 index 1a718005ad7..00000000000 --- a/.gemini/common/docs.md +++ /dev/null @@ -1,9 +0,0 @@ -## Documentation - -If a method or property is implementing the same interface as a third-party -package such as pandas or scikit-learn, place the relevant docstring in the -corresponding `third_party/bigframes_vendored/package_name` directory, not in -the `bigframes` directory. Implementations may be placed in the `bigframes` -directory, though. - -@../tools/test_docs.md diff --git a/.gemini/tasks/scalar_op.md b/.gemini/tasks/scalar_op.md deleted file mode 100644 index a9318d54824..00000000000 --- a/.gemini/tasks/scalar_op.md +++ /dev/null @@ -1,67 +0,0 @@ -## Adding a scalar operator - -For an example, see commit -[c5b7fdae74a22e581f7705bc0cf5390e928f4425](https://github.com/googleapis/python-bigquery-dataframes/commit/c5b7fdae74a22e581f7705bc0cf5390e928f4425). - -To add a new scalar operator, follow these steps: - -1. **Define the operation dataclass:** - - In `bigframes/operations/`, find the relevant file (e.g., `geo_ops.py` for geography functions) or create a new one. - - Create a new dataclass inheriting from `base_ops.UnaryOp` for unary - operators, `base_ops.BinaryOp` for binary operators, `base_ops.TernaryOp` - for ternary operators, or `base_ops.NaryOp for operators with many - arguments. Note that these operators are counting the number column-like - arguments. A function that takes only a single column but several literal - values would still be a `UnaryOp`. - - Define the `name` of the operation and any parameters it requires. - - Implement the `output_type` method to specify the data type of the result. - -2. **Export the new operation:** - - In `bigframes/operations/__init__.py`, import your new operation dataclass and add it to the `__all__` list. - -3. **Implement the user-facing function (pandas-like):** - - - Identify the canonical function from pandas / geopandas / awkward array / - other popular Python package that this operator implements. - - Find the corresponding class in BigFrames. For example, the implementation - for most geopandas.GeoSeries methods is in - `bigframes/geopandas/geoseries.py`. Pandas Series methods are implemented - in `bigframes/series.py` or one of the accessors, such as `StringMethods` - in `bigframes/operations/strings.py`. - - Create the user-facing function that will be called by users (e.g., `length`). - - If the SQL method differs from pandas or geopandas in a way that can't be - made the same, raise a `NotImplementedError` with an appropriate message and - link to the feedback form. - - Add the docstring to the corresponding file in - `third_party/bigframes_vendored`, modeled after pandas / geopandas. - -4. **Implement the user-facing function (SQL-like):** - - - In `bigframes/bigquery/_operations/`, find the relevant file (e.g., `geo.py`) or create a new one. - - Create the user-facing function that will be called by users (e.g., `st_length`). - - This function should take a `Series` for any column-like inputs, plus any other parameters. - - Inside the function, call `series._apply_unary_op`, - `series._apply_binary_op`, or similar passing the operation dataclass you - created. - - Add a comprehensive docstring with examples. - - In `bigframes/bigquery/__init__.py`, import your new user-facing function and add it to the `__all__` list. - -5. **Implement the compilation logic:** - - In `bigframes/core/compile/scalar_op_compiler.py`: - - If the BigQuery function has a direct equivalent in Ibis, you can often reuse an existing Ibis method. - - If not, define a new Ibis UDF using `@ibis_udf.scalar.builtin` to map to the specific BigQuery function signature. - - Create a new compiler implementation function (e.g., `geo_length_op_impl`). - - Register this function to your operation dataclass using `@scalar_op_compiler.register_unary_op` or `@scalar_op_compiler.register_binary_op`. - - This implementation will translate the BigQuery DataFrames operation into the appropriate Ibis expression. - -6. **Add Tests:** - - Add system tests in the `tests/system/` directory to verify the end-to-end - functionality of the new operator. Test various inputs, including edge cases - and `NULL` values. - - Where possible, run the same test code against pandas or GeoPandas and - compare that the outputs are the same (except for dtypes if BigFrames - differs from pandas). - - If you are overriding a pandas or GeoPandas property, add a unit test to - ensure the correct behavior (e.g., raising `NotImplementedError` if the - functionality is not supported). diff --git a/.gemini/tools/style_nox.md b/.gemini/tools/style_nox.md deleted file mode 100644 index 894fd102363..00000000000 --- a/.gemini/tools/style_nox.md +++ /dev/null @@ -1,18 +0,0 @@ -## Code Style with nox - -- We use the automatic code formatter `black`. You can run it using - the nox session `format`. This will eliminate many lint errors. Run via: - - ```bash - nox -r -s format - ``` - -- PEP8 compliance is required, with exceptions defined in the linter configuration. - If you have ``nox`` installed, you can test that you have not introduced - any non-compliant code via: - - ``` - nox -r -s lint - ``` - -- When writing tests, use the idiomatic "pytest" style. diff --git a/.gemini/tools/test_docs.md b/.gemini/tools/test_docs.md deleted file mode 100644 index 5cb988186c7..00000000000 --- a/.gemini/tools/test_docs.md +++ /dev/null @@ -1,10 +0,0 @@ -## Testing code samples - -Code samples are very important for accurate documentation. We use the "doctest" -framework to ensure the samples are functioning as expected. After adding a code -sample, please ensure it is correct by running doctest. To run the samples -doctests for just a single method, refer to the following example: - -```bash -pytest --doctest-modules bigframes/pandas/__init__.py::bigframes.pandas.cut -``` diff --git a/.gemini/tools/test_nox.md b/.gemini/tools/test_nox.md deleted file mode 100644 index 023ada1b61f..00000000000 --- a/.gemini/tools/test_nox.md +++ /dev/null @@ -1,28 +0,0 @@ -## Testing with nox - -Use `nox` to instrument our tests. - -- To test your changes, run unit tests with `nox`: - - ```bash - nox -r -s unit - ``` - -- To run a single unit test: - - ```bash - nox -r -s unit-3.14 -- -k - ``` - -- Ignore this step if you lack access to Google Cloud resources. To run system - tests, you can execute:: - - # Run all system tests - $ nox -r -s system - - # Run a single system test - $ nox -r -s system-3.14 -- -k - -- The codebase must have better coverage than it had previously after each - change. You can test coverage via `nox -s unit system cover` (takes a long - time). Omit `system` if you lack access to cloud resources. diff --git a/.gemini/tools/test_pytest.md b/.gemini/tools/test_pytest.md deleted file mode 100644 index 5228ae06ba8..00000000000 --- a/.gemini/tools/test_pytest.md +++ /dev/null @@ -1,9 +0,0 @@ -## Testing with pytest - -Use `pytest` to instrument our tests. - -- To test your changes, run `pytest`: - - ```bash - pytest :: - ``` diff --git a/.github/.OwlBot.lock.yaml b/.github/.OwlBot.lock.yaml new file mode 100644 index 00000000000..ec696b558c3 --- /dev/null +++ b/.github/.OwlBot.lock.yaml @@ -0,0 +1,17 @@ +# Copyright 2023 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. +docker: + image: gcr.io/cloud-devrel-public-resources/owlbot-python:latest + digest: sha256:30470597773378105e239b59fce8eb27cc97375580d592699206d17d117143d0 +# created: 2023-11-03T00:57:07.335914631Z diff --git a/.github/.OwlBot.yaml b/.github/.OwlBot.yaml new file mode 100644 index 00000000000..c379bd3092d --- /dev/null +++ b/.github/.OwlBot.yaml @@ -0,0 +1,18 @@ +# Copyright 2021 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. + +docker: + image: gcr.io/cloud-devrel-public-resources/owlbot-python:latest + +begin-after-commit-hash: 92006bb3cdc84677aa93c7f5235424ec2b157146 diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 00000000000..7686a50da62 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,12 @@ +# Code owners file. +# This file controls who is tagged for review for any given pull request. +# +# For syntax help see: +# https://help.github.com/en/github/creating-cloning-and-archiving-repositories/about-code-owners#codeowners-syntax +# Note: This file is autogenerated. To make changes to the codeowner team, please update .repo-metadata.json. + +# @googleapis/yoshi-python @googleapis/api-bigquery-dataframe are the default owners for changes in this repo +* @googleapis/yoshi-python @googleapis/api-bigquery-dataframe + +# @googleapis/python-samples-reviewers @googleapis/api-bigquery-dataframe are the default owners for samples changes +/samples/ @googleapis/python-samples-reviewers @googleapis/api-bigquery-dataframe diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md new file mode 100644 index 00000000000..939e5341e74 --- /dev/null +++ b/.github/CONTRIBUTING.md @@ -0,0 +1,28 @@ +# How to Contribute + +We'd love to accept your patches and contributions to this project. There are +just a few small guidelines you need to follow. + +## Contributor License Agreement + +Contributions to this project must be accompanied by a Contributor License +Agreement. You (or your employer) retain the copyright to your contribution; +this simply gives us permission to use and redistribute your contributions as +part of the project. Head over to to see +your current agreements on file or to sign a new one. + +You generally only need to submit a CLA once, so if you've already submitted one +(even if it was for a different project), you probably don't need to do it +again. + +## Code reviews + +All submissions, including submissions by project members, require review. We +use GitHub pull requests for this purpose. Consult +[GitHub Help](https://help.github.com/articles/about-pull-requests/) for more +information on using pull requests. + +## Community Guidelines + +This project follows [Google's Open Source Community +Guidelines](https://opensource.google.com/conduct/). diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 00000000000..7b0900728e4 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,43 @@ +--- +name: Bug report +about: Create a report to help us improve + +--- + +Thanks for stopping by to let us know something could be better! + +**PLEASE READ**: If you have a support contract with Google, please create an issue in the [support console](https://cloud.google.com/support/) instead of filing on GitHub. This will ensure a timely response. + +Please run down the following list and make sure you've tried the usual "quick fixes": + + - Search the issues already opened: https://github.com/googleapis/python-bigquery-dataframes/issues + - Search StackOverflow: https://stackoverflow.com/questions/tagged/google-cloud-platform+python + +If you are still having issues, please be sure to include as much information as possible: + +#### Environment details + + - OS type and version: + - Python version: `python --version` + - pip version: `pip --version` + - `bigframes` version: `pip show bigframes` + +#### Steps to reproduce + + 1. ? + 2. ? + +#### Code example + +```python +# example +``` + +#### Stack trace +``` +# example +``` + +Making sure to follow these steps will guarantee the quickest resolution possible. + +Thanks! diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 00000000000..6365857f33c --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,18 @@ +--- +name: Feature request +about: Suggest an idea for this library + +--- + +Thanks for stopping by to let us know something could be better! + +**PLEASE READ**: If you have a support contract with Google, please create an issue in the [support console](https://cloud.google.com/support/) instead of filing on GitHub. This will ensure a timely response. + + **Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + **Describe the solution you'd like** +A clear and concise description of what you want to happen. + **Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + **Additional context** +Add any other context or screenshots about the feature request here. diff --git a/.github/ISSUE_TEMPLATE/support_request.md b/.github/ISSUE_TEMPLATE/support_request.md new file mode 100644 index 00000000000..99586903212 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/support_request.md @@ -0,0 +1,7 @@ +--- +name: Support request +about: If you have a support contract with Google, please create an issue in the Google Cloud Support console. + +--- + +**PLEASE READ**: If you have a support contract with Google, please create an issue in the [support console](https://cloud.google.com/support/) instead of filing on GitHub. This will ensure a timely response. diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 00000000000..3e59d9a70d1 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,7 @@ +Thank you for opening a Pull Request! Before submitting your PR, there are a few things you can do to make sure it goes smoothly: +- [ ] Make sure to open an issue as a [bug/issue](https://github.com/googleapis/python-bigquery-dataframes/issues/new/choose) before writing your code! That way we can discuss the change, evaluate designs, and agree on the general idea +- [ ] Ensure the tests and linter pass +- [ ] Code coverage does not decrease (if any source code was changed) +- [ ] Appropriate docs were updated (if necessary) + +Fixes # 🦕 diff --git a/.github/auto-approve.yml b/.github/auto-approve.yml new file mode 100644 index 00000000000..311ebbb853a --- /dev/null +++ b/.github/auto-approve.yml @@ -0,0 +1,3 @@ +# https://github.com/googleapis/repo-automation-bots/tree/main/packages/auto-approve +processes: + - "OwlBotTemplateChanges" diff --git a/.github/auto-label.yaml b/.github/auto-label.yaml new file mode 100644 index 00000000000..b2016d119b4 --- /dev/null +++ b/.github/auto-label.yaml @@ -0,0 +1,15 @@ +# Copyright 2023 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. +requestsize: + enabled: true diff --git a/.github/header-checker-lint.yml b/.github/header-checker-lint.yml new file mode 100644 index 00000000000..6fe78aa7987 --- /dev/null +++ b/.github/header-checker-lint.yml @@ -0,0 +1,15 @@ +{"allowedCopyrightHolders": ["Google LLC"], + "allowedLicenses": ["Apache-2.0", "MIT", "BSD-3"], + "ignoreFiles": ["**/requirements.txt", "**/requirements-test.txt", "**/__init__.py", "samples/**/constraints.txt", "samples/**/constraints-test.txt"], + "sourceFileExtensions": [ + "ts", + "js", + "java", + "sh", + "Dockerfile", + "yaml", + "py", + "html", + "txt" + ] +} \ No newline at end of file diff --git a/.github/release-please.yml b/.github/release-please.yml new file mode 100644 index 00000000000..466597e5b19 --- /dev/null +++ b/.github/release-please.yml @@ -0,0 +1,2 @@ +releaseType: python +handleGHRelease: true diff --git a/.github/release-trigger.yml b/.github/release-trigger.yml new file mode 100644 index 00000000000..4fbd4aa427b --- /dev/null +++ b/.github/release-trigger.yml @@ -0,0 +1,2 @@ +enabled: true +multiScmName: python-bigquery-dataframes diff --git a/bigframes/py.typed b/.github/snippet-bot.yml similarity index 100% rename from bigframes/py.typed rename to .github/snippet-bot.yml diff --git a/.github/sync-repo-settings.yaml b/.github/sync-repo-settings.yaml new file mode 100644 index 00000000000..cfa62f787c9 --- /dev/null +++ b/.github/sync-repo-settings.yaml @@ -0,0 +1,32 @@ +# https://github.com/googleapis/repo-automation-bots/tree/main/packages/sync-repo-settings +# Rules for main branch protection +branchProtectionRules: +# Identifies the protection rule pattern. Name of the branch to be protected. +# Defaults to `main` +- pattern: main + requiresCodeOwnerReviews: true + requiresStrictStatusChecks: true + requiredStatusCheckContexts: + - 'conventionalcommits.org' + - 'cla/google' + - 'OwlBot Post Processor' + - 'docs' + - 'lint' + - 'unit (3.9)' + - 'unit (3.10)' + - 'unit (3.11)' + - 'cover' + - 'Kokoro presubmit' +permissionRules: + - team: actools-python + permission: admin + - team: actools + permission: admin + - team: api-bigquery-dataframe + permission: push + - team: yoshi-python + permission: push + - team: python-samples-owners + permission: push + - team: python-samples-reviewers + permission: push diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 00000000000..221806cedf5 --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,38 @@ +on: + pull_request: + branches: + - main +name: docs +jobs: + docs: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + - name: Setup Python + uses: actions/setup-python@v4 + with: + python-version: "3.9" + - name: Install nox + run: | + python -m pip install --upgrade setuptools pip wheel + python -m pip install nox + - name: Run docs + run: | + nox -s docs + docfx: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + - name: Setup Python + uses: actions/setup-python@v4 + with: + python-version: "3.10" + - name: Install nox + run: | + python -m pip install --upgrade setuptools pip wheel + python -m pip install nox + - name: Run docfx + run: | + nox -s docfx diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 00000000000..16d5a9e90f6 --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,25 @@ +on: + pull_request: + branches: + - main +name: lint +jobs: + lint: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + - name: Setup Python + uses: actions/setup-python@v4 + with: + python-version: "3.8" + - name: Install nox + run: | + python -m pip install --upgrade setuptools pip wheel + python -m pip install nox + - name: Run lint + run: | + nox -s lint + - name: Run lint_setup_py + run: | + nox -s lint_setup_py diff --git a/.github/workflows/unittest.yml b/.github/workflows/unittest.yml new file mode 100644 index 00000000000..465199fc9a9 --- /dev/null +++ b/.github/workflows/unittest.yml @@ -0,0 +1,57 @@ +on: + pull_request: + branches: + - main +name: unittest +jobs: + unit: + runs-on: ubuntu-latest + strategy: + matrix: + python: ['3.9', '3.10', '3.11'] + steps: + - name: Checkout + uses: actions/checkout@v3 + - name: Setup Python + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python }} + - name: Install nox + run: | + python -m pip install --upgrade setuptools pip wheel + python -m pip install nox + - name: Run unit tests + env: + COVERAGE_FILE: .coverage-${{ matrix.python }} + run: | + nox -s unit-${{ matrix.python }} + - name: Upload coverage results + uses: actions/upload-artifact@v3 + with: + name: coverage-artifacts + path: .coverage-${{ matrix.python }} + + cover: + runs-on: ubuntu-latest + needs: + - unit + steps: + - name: Checkout + uses: actions/checkout@v3 + - name: Setup Python + uses: actions/setup-python@v4 + with: + python-version: "3.8" + - name: Install coverage + run: | + python -m pip install --upgrade setuptools pip wheel + python -m pip install coverage + - name: Download coverage results + uses: actions/download-artifact@v3 + with: + name: coverage-artifacts + path: .coverage-results/ + - name: Report coverage results + run: | + coverage combine .coverage-results/.coverage* + coverage report --show-missing --fail-under=35 diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000000..d083ea1ddc3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,64 @@ +*.py[cod] +*.sw[op] + +# C extensions +*.so + +# Packages +*.egg +*.egg-info +dist +build +eggs +.eggs +parts +bin +var +sdist +develop-eggs +.installed.cfg +lib +lib64 +__pycache__ + +# Installer logs +pip-log.txt + +# Unit test / coverage reports +.coverage +.nox +.cache +.pytest_cache + + +# Mac +.DS_Store + +# JetBrains +.idea + +# VS Code +.vscode + +# emacs +*~ + +# Built documentation +docs/_build +bigquery/docs/generated +docs.metadata + +# Virtual environment +env/ +venv/ + +# Test logs +coverage.xml +*sponge_log.xml + +# System test environment variables. +system_tests/local_test_setup + +# Make sure a generated file isn't accidentally committed. +pylintrc +pylintrc.test diff --git a/.kokoro/build.sh b/.kokoro/build.sh index 01b0af912ee..58eaa7fedf9 100755 --- a/.kokoro/build.sh +++ b/.kokoro/build.sh @@ -1,11 +1,11 @@ #!/bin/bash -# Copyright 2022 Google LLC +# Copyright 2023 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 +# https://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, @@ -13,24 +13,40 @@ # See the License for the specific language governing permissions and # limitations under the License. -# `-e` enables the script to automatically fail when a command fails -# `-o pipefail` sets the exit code to non-zero if any command fails, -# or zero if all commands in the pipeline exit successfully. set -eo pipefail -cd "${KOKORO_ARTIFACTS_DIR}/git/bigframes" -pwd +PROJECT_SCM="github/python-bigquery-dataframes" -# If NOX_SESSION is set, it only runs the specified session, -# otherwise run all the sessions. -NOX_SESSION_ARG="" +if [[ -z "${PROJECT_ROOT:-}" ]]; then + PROJECT_ROOT="${KOKORO_ARTIFACTS_DIR}/${PROJECT_SCM}" +fi + +cd "${PROJECT_ROOT}" + +# Disable buffering, so that the logs stream through. +export PYTHONUNBUFFERED=1 -# IF NOX_FILE is set, it runs the specific nox file, -# otherwise it runs noxfile.py in the package directory. -NOX_FILE_ARG="" +# Workaround https://github.com/pytest-dev/pytest/issues/9567 +export PY_IGNORE_IMPORTMISMATCH=1 -[[ -z "${NOX_SESSION}" ]] || NOX_SESSION_ARG="-s ${NOX_SESSION}" +# Debug: show build environment +env | grep KOKORO -[[ -z "${NOX_FILE}" ]] || NOX_FILE_ARG="-f ${NOX_FILE}" +# Install pip +python3 -m pip install --upgrade --quiet pip +python3 -m pip --version -python3 -m nox ${NOX_SESSION_ARG} $NOX_FILE_ARG +# Remove old nox +python3 -m pip uninstall --yes --quiet nox-automation + +# Install nox +python3 -m pip install --upgrade --quiet nox +python3 -m nox --version + +# If NOX_SESSION is set, it only runs the specified session, +# otherwise run all the sessions. +if [[ -n "${NOX_SESSION:-}" ]]; then + python3 -m nox --stop-on-first-error -s ${NOX_SESSION:-} +else + python3 -m nox --stop-on-first-error +fi diff --git a/.kokoro/buildwheel.cfg b/.kokoro/buildwheel.cfg deleted file mode 100644 index af89c99e419..00000000000 --- a/.kokoro/buildwheel.cfg +++ /dev/null @@ -1,22 +0,0 @@ -# -*- protobuffer -*- -# proto-file: google3/devtools/kokoro/config/proto/build.proto -# proto-message: BuildConfig - -build_file: "bigframes-internal/bigframes/.kokoro/buildwheel.sh" -container_properties { - docker_image: "us-docker.pkg.dev/artifact-foundry-prod/docker-3p-trusted/python@sha256:0b3498e251759df85a00474be7d3b791d6abe1600ce3531a649e42964749655f" -} - -fileset_artifacts { - name: "artifacts" - artifact_globs: "artifacts/*" - error_if_missing: true - destinations { - store_attestation: true - gcs { - gcs_root_path: "oss-exit-gate-prod-projects-bucket/bigframes/pypi/attestations" - } - } - generate_sbom_from_fileset: true - generate_attestation: true -} diff --git a/.kokoro/buildwheel.sh b/.kokoro/buildwheel.sh deleted file mode 100755 index b64b6412b02..00000000000 --- a/.kokoro/buildwheel.sh +++ /dev/null @@ -1,48 +0,0 @@ -#!/bin/bash -set -euo pipefail - -cd "${KOKORO_ARTIFACTS_DIR}/git/bigframes-internal/bigframes" - -### Set up Airlock for Bookworm -rm -f /etc/apt/sources.list.d/* /etc/apt/sources.list -echo 'deb https://us-apt.pkg.dev/remote/artifact-foundry-prod/debian-3p-remote-bookworm bookworm main' | \ - tee -a /etc/apt/sources.list.d/artifact-registry.list - -# Set up Airlock for Python -cat > "$HOME/.pypirc" < "$HOME/.pip/pip.conf" <&2 ;} +function println { printf '%s\n' "$(now) $*" ;} + + +# Populates requested secrets set in SECRET_MANAGER_KEYS from service account: +# kokoro-trampoline@cloud-devrel-kokoro-resources.iam.gserviceaccount.com +SECRET_LOCATION="${KOKORO_GFILE_DIR}/secret_manager" +msg "Creating folder on disk for secrets: ${SECRET_LOCATION}" +mkdir -p ${SECRET_LOCATION} +for key in $(echo ${SECRET_MANAGER_KEYS} | sed "s/,/ /g") +do + msg "Retrieving secret ${key}" + docker run --entrypoint=gcloud \ + --volume=${KOKORO_GFILE_DIR}:${KOKORO_GFILE_DIR} \ + gcr.io/google.com/cloudsdktool/cloud-sdk \ + secrets versions access latest \ + --project cloud-devrel-kokoro-resources \ + --secret ${key} > \ + "${SECRET_LOCATION}/${key}" + if [[ $? == 0 ]]; then + msg "Secret written to ${SECRET_LOCATION}/${key}" + else + msg "Error retrieving secret ${key}" + fi +done diff --git a/.kokoro/presubmit/common.cfg b/.kokoro/presubmit/common.cfg index 5d40578ac79..97e0651aa92 100644 --- a/.kokoro/presubmit/common.cfg +++ b/.kokoro/presubmit/common.cfg @@ -7,4 +7,4 @@ action { } } -build_file: "bigframes/.kokoro/build.sh" +build_file: "python-bigquery-dataframes/.kokoro/build.sh" diff --git a/.kokoro/presubmit/e2e-gerrit.cfg b/.kokoro/presubmit/e2e-gerrit.cfg new file mode 100644 index 00000000000..d875f360603 --- /dev/null +++ b/.kokoro/presubmit/e2e-gerrit.cfg @@ -0,0 +1,7 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +# Only run this nox session. +env_vars: { + key: "NOX_SESSION" + value: "system_noextras e2e notebook samples" +} diff --git a/.kokoro/presubmit/e2e.cfg b/.kokoro/presubmit/e2e.cfg new file mode 100644 index 00000000000..d875f360603 --- /dev/null +++ b/.kokoro/presubmit/e2e.cfg @@ -0,0 +1,7 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +# Only run this nox session. +env_vars: { + key: "NOX_SESSION" + value: "system_noextras e2e notebook samples" +} diff --git a/.kokoro/presubmit/prerelease-deps.cfg b/.kokoro/presubmit/prerelease-deps.cfg new file mode 100644 index 00000000000..3595fb43f5c --- /dev/null +++ b/.kokoro/presubmit/prerelease-deps.cfg @@ -0,0 +1,7 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +# Only run this nox session. +env_vars: { + key: "NOX_SESSION" + value: "prerelease_deps" +} diff --git a/.kokoro/presubmit/presubmit-docs-linux.cfg b/.kokoro/presubmit/presubmit-docs-linux.cfg deleted file mode 100644 index 93832f9e5aa..00000000000 --- a/.kokoro/presubmit/presubmit-docs-linux.cfg +++ /dev/null @@ -1,6 +0,0 @@ -# Format: //devtools/kokoro/config/proto/build.proto - -env_vars: { - key: "NOX_SESSION" - value: "docs docfx" -} diff --git a/.kokoro/presubmit/presubmit-doctest-linux.cfg b/.kokoro/presubmit/presubmit-doctest-linux.cfg deleted file mode 100644 index af74ca0fbcd..00000000000 --- a/.kokoro/presubmit/presubmit-doctest-linux.cfg +++ /dev/null @@ -1,6 +0,0 @@ -# Format: //devtools/kokoro/config/proto/build.proto - -env_vars: { - key: "NOX_SESSION" - value: "doctest" -} diff --git a/.kokoro/presubmit/presubmit-e2e-linux.cfg b/.kokoro/presubmit/presubmit-e2e-linux.cfg deleted file mode 100644 index ed73a083ee0..00000000000 --- a/.kokoro/presubmit/presubmit-e2e-linux.cfg +++ /dev/null @@ -1,6 +0,0 @@ -# Format: //devtools/kokoro/config/proto/build.proto - -env_vars: { - key: "NOX_SESSION" - value: "e2e" -} diff --git a/.kokoro/presubmit/presubmit-gerrit.cfg b/.kokoro/presubmit/presubmit-gerrit.cfg new file mode 100644 index 00000000000..18a4c35325b --- /dev/null +++ b/.kokoro/presubmit/presubmit-gerrit.cfg @@ -0,0 +1 @@ +# Format: //devtools/kokoro/config/proto/build.proto diff --git a/.kokoro/presubmit/presubmit-lint-linux.cfg b/.kokoro/presubmit/presubmit-lint-linux.cfg deleted file mode 100644 index 490cafab878..00000000000 --- a/.kokoro/presubmit/presubmit-lint-linux.cfg +++ /dev/null @@ -1,6 +0,0 @@ -# Format: //devtools/kokoro/config/proto/build.proto - -env_vars: { - key: "NOX_SESSION" - value: "lint mypy" -} diff --git a/.kokoro/presubmit/presubmit-prerelease-linux.cfg b/.kokoro/presubmit/presubmit-prerelease-linux.cfg deleted file mode 100644 index bfc635fbf30..00000000000 --- a/.kokoro/presubmit/presubmit-prerelease-linux.cfg +++ /dev/null @@ -1,6 +0,0 @@ -# Format: //devtools/kokoro/config/proto/build.proto - -env_vars: { - key: "NOX_SESSION" - value: "unit_prerelease system_prerelease" -} diff --git a/.kokoro/presubmit/presubmit-system-linux.cfg b/.kokoro/presubmit/presubmit-system-linux.cfg deleted file mode 100644 index 9fa4e1b73b2..00000000000 --- a/.kokoro/presubmit/presubmit-system-linux.cfg +++ /dev/null @@ -1,6 +0,0 @@ -# Format: //devtools/kokoro/config/proto/build.proto - -env_vars: { - key: "NOX_SESSION" - value: "system" -} diff --git a/.kokoro/presubmit/presubmit-unit-linux.cfg b/.kokoro/presubmit/presubmit-unit-linux.cfg deleted file mode 100644 index 09f4f4b8311..00000000000 --- a/.kokoro/presubmit/presubmit-unit-linux.cfg +++ /dev/null @@ -1,6 +0,0 @@ -# Format: //devtools/kokoro/config/proto/build.proto - -env_vars: { - key: "NOX_SESSION" - value: "unit" -} diff --git a/.kokoro/presubmit/presubmit.cfg b/.kokoro/presubmit/presubmit.cfg new file mode 100644 index 00000000000..8f43917d92f --- /dev/null +++ b/.kokoro/presubmit/presubmit.cfg @@ -0,0 +1 @@ +# Format: //devtools/kokoro/config/proto/build.proto \ No newline at end of file diff --git a/.kokoro/publish-docs.sh b/.kokoro/publish-docs.sh new file mode 100755 index 00000000000..7700c90ee92 --- /dev/null +++ b/.kokoro/publish-docs.sh @@ -0,0 +1,65 @@ +#!/bin/bash +# Copyright 2023 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 +# +# https://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. + +set -eo pipefail + +# Disable buffering, so that the logs stream through. +export PYTHONUNBUFFERED=1 + +export PATH="${HOME}/.local/bin:${PATH}" + +# Install nox +python3 -m pip install --require-hashes -r .kokoro/requirements.txt +python3 -m nox --version + +# build docs +nox -s docs + +# create metadata +python3 -m docuploader create-metadata \ + --name=$(jq --raw-output '.name // empty' .repo-metadata.json) \ + --version=$(python3 setup.py --version) \ + --language=$(jq --raw-output '.language // empty' .repo-metadata.json) \ + --distribution-name=$(python3 setup.py --name) \ + --product-page=$(jq --raw-output '.product_documentation // empty' .repo-metadata.json) \ + --github-repository=$(jq --raw-output '.repo // empty' .repo-metadata.json) \ + --issue-tracker=$(jq --raw-output '.issue_tracker // empty' .repo-metadata.json) + +cat docs.metadata + +# upload docs +python3 -m docuploader upload docs/_build/html --metadata-file docs.metadata --staging-bucket "${STAGING_BUCKET}" + + +# docfx yaml files +nox -s docfx + +# create metadata. +python3 -m docuploader create-metadata \ + --name=$(jq --raw-output '.name // empty' .repo-metadata.json) \ + --version=$(python3 setup.py --version) \ + --language=$(jq --raw-output '.language // empty' .repo-metadata.json) \ + --distribution-name=$(python3 setup.py --name) \ + --product-page=$(jq --raw-output '.product_documentation // empty' .repo-metadata.json) \ + --github-repository=$(jq --raw-output '.repo // empty' .repo-metadata.json) \ + --issue-tracker=$(jq --raw-output '.issue_tracker // empty' .repo-metadata.json) + +cat docs.metadata + +# Replace toc.yml template file +mv docs/templates/toc.yml docs/_build/html/docfx_yaml/toc.yml + +# upload docs +python3 -m docuploader upload docs/_build/html/docfx_yaml --metadata-file docs.metadata --destination-prefix docfx --staging-bucket "${V2_STAGING_BUCKET}" diff --git a/.kokoro/release-nightly.sh b/.kokoro/release-nightly.sh new file mode 100755 index 00000000000..0751cf2502c --- /dev/null +++ b/.kokoro/release-nightly.sh @@ -0,0 +1,121 @@ +#!/bin/bash +# Copyright 2020 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 +# +# https://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. + +# Based loosely on +# https://github.com/googleapis/python-bigquery/blob/main/.kokoro/release.sh + +set -eo pipefail +set -x + +# Parse command line arguments +DRY_RUN= +while [ $# -gt 0 ] ; do + case "$1" in + -d | --dry-run ) + DRY_RUN=true + ;; + -h | --help ) + echo -e "USAGE: `basename $0` [ -d | --dry-run ]" + exit + ;; + esac + shift 1; +done + +if [[ -z "${KOKORO_GOB_COMMIT}" ]]; then + PROJECT_SCM="github/python-bigquery-dataframes" +else + PROJECT_SCM="git/bigframes" +fi + +if [ -z "${PROJECT_ROOT:-}" ]; then + PROJECT_ROOT="${KOKORO_ARTIFACTS_DIR}/${PROJECT_SCM}" +fi + +# Move into the package, build the distribution and upload to shared bucket. +# See internal bug 274624240 for details. + +cd "${PROJECT_ROOT}" +rm -rf build dist + +# Workaround the fact that the repository that has been fetched before the +# build script. See: go/kokoro-native-docker-migration#known-issues and +# internal issue b/261050975. +git config --global --add safe.directory "${PROJECT_ROOT}" + +python3.10 -m pip install --require-hashes -r .kokoro/requirements.txt + +# Disable buffering, so that the logs stream through. +export PYTHONUNBUFFERED=1 + +# Install dependencies, as the following steps depend on it +python3.10 -m pip install -e .[all] + +# Update version string to include git hash and date +CURRENT_DATE=$(date '+%Y%m%d') +GIT_HASH=$(git rev-parse --short HEAD) +BIGFRAMES_VERSION=$(python3.10 -c "import bigframes; print(bigframes.__version__)") +RELEASE_VERSION=${BIGFRAMES_VERSION}dev${CURRENT_DATE}+${GIT_HASH} +sed -i -e "s/$BIGFRAMES_VERSION/$RELEASE_VERSION/g" bigframes/version.py + +# Generate the package wheel +python3.10 setup.py sdist bdist_wheel + +# Make sure that the wheel file is generated +VERSION_WHEEL=`ls dist/bigframes-*.whl` +num_wheel_files=`echo $VERSION_WHEEL | wc -w` +if [ $num_wheel_files -ne 1 ] ; then + echo "Exactly one wheel file should have been generated, found $num_wheel_files: $VERSION_WHEEL" + exit -1 +fi + +# Create a copy of the wheel with a well known, version agnostic name +LATEST_WHEEL=dist/bigframes-latest-py2.py3-none-any.whl +cp $VERSION_WHEEL $LATEST_WHEEL +cp dist/bigframes-*.tar.gz dist/bigframes-latest.tar.gz + +if ! [ ${DRY_RUN} ]; then +for gcs_path in gs://vertex_sdk_private_releases/bigframe/ \ + gs://dl-platform-colab/bigframes/ \ + gs://bigframes-wheels/; + do + gsutil cp -v dist/* ${gcs_path} + gsutil cp -v LICENSE ${gcs_path} + gsutil -m cp -r -v "notebooks/" ${gcs_path}notebooks/ + + done + + # publish API coverage information to BigQuery + # Note: only the kokoro service account has permission to write to this + # table, if you want to test this step, point it to a table you have + # write access to + COVERAGE_TABLE=bigframes-metrics.coverage_report.bigframes_coverage_nightly + python3.10 scripts/publish_api_coverage.py \ + --bigframes_version=$BIGFRAMES_VERSION \ + --release_version=$RELEASE_VERSION \ + --bigquery_table=$COVERAGE_TABLE +fi + +# Undo the file changes, in case this script is running on a +# non-temporary instance of the bigframes repo +# TODO: This doesn't work with (set -eo pipefail) if the failure happened after +# the changes were made but before this cleanup, because the script would +# terminate with the failure itself. See if we can ensure the cleanup. +sed -i -e "s/$RELEASE_VERSION/$BIGFRAMES_VERSION/g" bigframes/version.py + +if ! [ ${DRY_RUN} ]; then + # Copy docs and wheels to Google Drive + python3.10 scripts/upload_to_google_drive.py +fi diff --git a/.kokoro/release.cfg b/.kokoro/release.cfg deleted file mode 100644 index 18c85a69d63..00000000000 --- a/.kokoro/release.cfg +++ /dev/null @@ -1,23 +0,0 @@ -# -*- protobuffer -*- -# proto-file: google3/devtools/kokoro/config/proto/build.proto -# proto-message: BuildConfig - -build_file: "bigframes-internal/bigframes/.kokoro/release.sh" -container_properties { - docker_image: "us-docker.pkg.dev/artifact-foundry-prod/docker-3p-trusted/ubuntu:22.04" -} - -fileset_artifacts { - name: "manifest" - artifact_globs: "manifest.json" - error_if_missing: true - destinations { - store_attestation: false - gcs { - gcs_root_path: "oss-exit-gate-prod-projects-bucket/bigframes/pypi/manifests" - populate_content_type: true - } - } - generate_sbom_from_fileset: false - generate_attestation: false -} diff --git a/.kokoro/release.sh b/.kokoro/release.sh index 58b865a6f54..320ac51271e 100755 --- a/.kokoro/release.sh +++ b/.kokoro/release.sh @@ -1,10 +1,29 @@ #!/bin/bash -set -euo pipefail +# Copyright 2023 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 +# +# https://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. -cd "${KOKORO_ARTIFACTS_DIR}" +set -eo pipefail -cat > manifest.json <<'EOF' -{ - "publish_all": true -} -EOF +# Start the releasetool reporter +python3 -m pip install --require-hashes -r github/python-bigquery-dataframes/.kokoro/requirements.txt +python3 -m releasetool publish-reporter-script > /tmp/publisher-script; source /tmp/publisher-script + +# Disable buffering, so that the logs stream through. +export PYTHONUNBUFFERED=1 + +# Move into the package, build the distribution and upload. +TWINE_PASSWORD=$(cat "${KOKORO_KEYSTORE_DIR}/73713_google-cloud-pypi-token-keystore-1") +cd github/python-bigquery-dataframes +python3 setup.py sdist bdist_wheel +twine upload --username __token__ --password "${TWINE_PASSWORD}" dist/* diff --git a/.kokoro/release/common.cfg b/.kokoro/release/common.cfg new file mode 100644 index 00000000000..a0c39946cf1 --- /dev/null +++ b/.kokoro/release/common.cfg @@ -0,0 +1,49 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +# Build logs will be here +action { + define_artifacts { + regex: "**/*sponge_log.xml" + } +} + +# Download trampoline resources. +gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/trampoline" + +# Use the trampoline script to run in docker. +build_file: "python-bigquery-dataframes/.kokoro/trampoline.sh" + +# Configure the docker image for kokoro-trampoline. +env_vars: { + key: "TRAMPOLINE_IMAGE" + value: "gcr.io/cloud-devrel-kokoro-resources/python-multi" +} +env_vars: { + key: "TRAMPOLINE_BUILD_FILE" + value: "github/python-bigquery-dataframes/.kokoro/release.sh" +} + +# Fetch PyPI password +before_action { + fetch_keystore { + keystore_resource { + keystore_config_id: 73713 + keyname: "google-cloud-pypi-token-keystore-1" + } + } +} + +# Tokens needed to report release status back to GitHub +env_vars: { + key: "SECRET_MANAGER_KEYS" + value: "releasetool-publish-reporter-app,releasetool-publish-reporter-googleapis-installation,releasetool-publish-reporter-pem" +} + +# Store the packages we uploaded to PyPI. That way, we have a record of exactly +# what we published, which we can use to generate SBOMs and attestations. +action { + define_artifacts { + regex: "github/python-bigquery-dataframes/**/*.tar.gz" + strip_prefix: "github/python-bigquery-dataframes" + } +} diff --git a/.kokoro/release/release.cfg b/.kokoro/release/release.cfg new file mode 100644 index 00000000000..8f43917d92f --- /dev/null +++ b/.kokoro/release/release.cfg @@ -0,0 +1 @@ +# Format: //devtools/kokoro/config/proto/build.proto \ No newline at end of file diff --git a/.kokoro/requirements.in b/.kokoro/requirements.in new file mode 100644 index 00000000000..ec867d9fd65 --- /dev/null +++ b/.kokoro/requirements.in @@ -0,0 +1,10 @@ +gcp-docuploader +gcp-releasetool>=1.10.5 # required for compatibility with cryptography>=39.x +importlib-metadata +typing-extensions +twine +wheel +setuptools +nox>=2022.11.21 # required to remove dependency on py +charset-normalizer<3 +click<8.1.0 diff --git a/.kokoro/requirements.txt b/.kokoro/requirements.txt new file mode 100644 index 00000000000..16170d0ca7b --- /dev/null +++ b/.kokoro/requirements.txt @@ -0,0 +1,497 @@ +# +# This file is autogenerated by pip-compile with Python 3.9 +# by the following command: +# +# pip-compile --allow-unsafe --generate-hashes requirements.in +# +argcomplete==2.0.0 \ + --hash=sha256:6372ad78c89d662035101418ae253668445b391755cfe94ea52f1b9d22425b20 \ + --hash=sha256:cffa11ea77999bb0dd27bb25ff6dc142a6796142f68d45b1a26b11f58724561e + # via nox +attrs==22.1.0 \ + --hash=sha256:29adc2665447e5191d0e7c568fde78b21f9672d344281d0c6e1ab085429b22b6 \ + --hash=sha256:86efa402f67bf2df34f51a335487cf46b1ec130d02b8d39fd248abfd30da551c + # via gcp-releasetool +bleach==5.0.1 \ + --hash=sha256:085f7f33c15bd408dd9b17a4ad77c577db66d76203e5984b1bd59baeee948b2a \ + --hash=sha256:0d03255c47eb9bd2f26aa9bb7f2107732e7e8fe195ca2f64709fcf3b0a4a085c + # via readme-renderer +cachetools==5.2.0 \ + --hash=sha256:6a94c6402995a99c3970cc7e4884bb60b4a8639938157eeed436098bf9831757 \ + --hash=sha256:f9f17d2aec496a9aa6b76f53e3b614c965223c061982d434d160f930c698a9db + # via google-auth +certifi==2023.7.22 \ + --hash=sha256:539cc1d13202e33ca466e88b2807e29f4c13049d6d87031a3c110744495cb082 \ + --hash=sha256:92d6037539857d8206b8f6ae472e8b77db8058fec5937a1ef3f54304089edbb9 + # via requests +cffi==1.15.1 \ + --hash=sha256:00a9ed42e88df81ffae7a8ab6d9356b371399b91dbdf0c3cb1e84c03a13aceb5 \ + --hash=sha256:03425bdae262c76aad70202debd780501fabeaca237cdfddc008987c0e0f59ef \ + --hash=sha256:04ed324bda3cda42b9b695d51bb7d54b680b9719cfab04227cdd1e04e5de3104 \ + --hash=sha256:0e2642fe3142e4cc4af0799748233ad6da94c62a8bec3a6648bf8ee68b1c7426 \ + --hash=sha256:173379135477dc8cac4bc58f45db08ab45d228b3363adb7af79436135d028405 \ + --hash=sha256:198caafb44239b60e252492445da556afafc7d1e3ab7a1fb3f0584ef6d742375 \ + --hash=sha256:1e74c6b51a9ed6589199c787bf5f9875612ca4a8a0785fb2d4a84429badaf22a \ + --hash=sha256:2012c72d854c2d03e45d06ae57f40d78e5770d252f195b93f581acf3ba44496e \ + --hash=sha256:21157295583fe8943475029ed5abdcf71eb3911894724e360acff1d61c1d54bc \ + --hash=sha256:2470043b93ff09bf8fb1d46d1cb756ce6132c54826661a32d4e4d132e1977adf \ + --hash=sha256:285d29981935eb726a4399badae8f0ffdff4f5050eaa6d0cfc3f64b857b77185 \ + --hash=sha256:30d78fbc8ebf9c92c9b7823ee18eb92f2e6ef79b45ac84db507f52fbe3ec4497 \ + --hash=sha256:320dab6e7cb2eacdf0e658569d2575c4dad258c0fcc794f46215e1e39f90f2c3 \ + --hash=sha256:33ab79603146aace82c2427da5ca6e58f2b3f2fb5da893ceac0c42218a40be35 \ + --hash=sha256:3548db281cd7d2561c9ad9984681c95f7b0e38881201e157833a2342c30d5e8c \ + --hash=sha256:3799aecf2e17cf585d977b780ce79ff0dc9b78d799fc694221ce814c2c19db83 \ + --hash=sha256:39d39875251ca8f612b6f33e6b1195af86d1b3e60086068be9cc053aa4376e21 \ + --hash=sha256:3b926aa83d1edb5aa5b427b4053dc420ec295a08e40911296b9eb1b6170f6cca \ + --hash=sha256:3bcde07039e586f91b45c88f8583ea7cf7a0770df3a1649627bf598332cb6984 \ + --hash=sha256:3d08afd128ddaa624a48cf2b859afef385b720bb4b43df214f85616922e6a5ac \ + --hash=sha256:3eb6971dcff08619f8d91607cfc726518b6fa2a9eba42856be181c6d0d9515fd \ + --hash=sha256:40f4774f5a9d4f5e344f31a32b5096977b5d48560c5592e2f3d2c4374bd543ee \ + --hash=sha256:4289fc34b2f5316fbb762d75362931e351941fa95fa18789191b33fc4cf9504a \ + --hash=sha256:470c103ae716238bbe698d67ad020e1db9d9dba34fa5a899b5e21577e6d52ed2 \ + --hash=sha256:4f2c9f67e9821cad2e5f480bc8d83b8742896f1242dba247911072d4fa94c192 \ + --hash=sha256:50a74364d85fd319352182ef59c5c790484a336f6db772c1a9231f1c3ed0cbd7 \ + --hash=sha256:54a2db7b78338edd780e7ef7f9f6c442500fb0d41a5a4ea24fff1c929d5af585 \ + --hash=sha256:5635bd9cb9731e6d4a1132a498dd34f764034a8ce60cef4f5319c0541159392f \ + --hash=sha256:59c0b02d0a6c384d453fece7566d1c7e6b7bae4fc5874ef2ef46d56776d61c9e \ + --hash=sha256:5d598b938678ebf3c67377cdd45e09d431369c3b1a5b331058c338e201f12b27 \ + --hash=sha256:5df2768244d19ab7f60546d0c7c63ce1581f7af8b5de3eb3004b9b6fc8a9f84b \ + --hash=sha256:5ef34d190326c3b1f822a5b7a45f6c4535e2f47ed06fec77d3d799c450b2651e \ + --hash=sha256:6975a3fac6bc83c4a65c9f9fcab9e47019a11d3d2cf7f3c0d03431bf145a941e \ + --hash=sha256:6c9a799e985904922a4d207a94eae35c78ebae90e128f0c4e521ce339396be9d \ + --hash=sha256:70df4e3b545a17496c9b3f41f5115e69a4f2e77e94e1d2a8e1070bc0c38c8a3c \ + --hash=sha256:7473e861101c9e72452f9bf8acb984947aa1661a7704553a9f6e4baa5ba64415 \ + --hash=sha256:8102eaf27e1e448db915d08afa8b41d6c7ca7a04b7d73af6514df10a3e74bd82 \ + --hash=sha256:87c450779d0914f2861b8526e035c5e6da0a3199d8f1add1a665e1cbc6fc6d02 \ + --hash=sha256:8b7ee99e510d7b66cdb6c593f21c043c248537a32e0bedf02e01e9553a172314 \ + --hash=sha256:91fc98adde3d7881af9b59ed0294046f3806221863722ba7d8d120c575314325 \ + --hash=sha256:94411f22c3985acaec6f83c6df553f2dbe17b698cc7f8ae751ff2237d96b9e3c \ + --hash=sha256:98d85c6a2bef81588d9227dde12db8a7f47f639f4a17c9ae08e773aa9c697bf3 \ + --hash=sha256:9ad5db27f9cabae298d151c85cf2bad1d359a1b9c686a275df03385758e2f914 \ + --hash=sha256:a0b71b1b8fbf2b96e41c4d990244165e2c9be83d54962a9a1d118fd8657d2045 \ + --hash=sha256:a0f100c8912c114ff53e1202d0078b425bee3649ae34d7b070e9697f93c5d52d \ + --hash=sha256:a591fe9e525846e4d154205572a029f653ada1a78b93697f3b5a8f1f2bc055b9 \ + --hash=sha256:a5c84c68147988265e60416b57fc83425a78058853509c1b0629c180094904a5 \ + --hash=sha256:a66d3508133af6e8548451b25058d5812812ec3798c886bf38ed24a98216fab2 \ + --hash=sha256:a8c4917bd7ad33e8eb21e9a5bbba979b49d9a97acb3a803092cbc1133e20343c \ + --hash=sha256:b3bbeb01c2b273cca1e1e0c5df57f12dce9a4dd331b4fa1635b8bec26350bde3 \ + --hash=sha256:cba9d6b9a7d64d4bd46167096fc9d2f835e25d7e4c121fb2ddfc6528fb0413b2 \ + --hash=sha256:cc4d65aeeaa04136a12677d3dd0b1c0c94dc43abac5860ab33cceb42b801c1e8 \ + --hash=sha256:ce4bcc037df4fc5e3d184794f27bdaab018943698f4ca31630bc7f84a7b69c6d \ + --hash=sha256:cec7d9412a9102bdc577382c3929b337320c4c4c4849f2c5cdd14d7368c5562d \ + --hash=sha256:d400bfb9a37b1351253cb402671cea7e89bdecc294e8016a707f6d1d8ac934f9 \ + --hash=sha256:d61f4695e6c866a23a21acab0509af1cdfd2c013cf256bbf5b6b5e2695827162 \ + --hash=sha256:db0fbb9c62743ce59a9ff687eb5f4afbe77e5e8403d6697f7446e5f609976f76 \ + --hash=sha256:dd86c085fae2efd48ac91dd7ccffcfc0571387fe1193d33b6394db7ef31fe2a4 \ + --hash=sha256:e00b098126fd45523dd056d2efba6c5a63b71ffe9f2bbe1a4fe1716e1d0c331e \ + --hash=sha256:e229a521186c75c8ad9490854fd8bbdd9a0c9aa3a524326b55be83b54d4e0ad9 \ + --hash=sha256:e263d77ee3dd201c3a142934a086a4450861778baaeeb45db4591ef65550b0a6 \ + --hash=sha256:ed9cb427ba5504c1dc15ede7d516b84757c3e3d7868ccc85121d9310d27eed0b \ + --hash=sha256:fa6693661a4c91757f4412306191b6dc88c1703f780c8234035eac011922bc01 \ + --hash=sha256:fcd131dd944808b5bdb38e6f5b53013c5aa4f334c5cad0c72742f6eba4b73db0 + # via cryptography +charset-normalizer==2.1.1 \ + --hash=sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845 \ + --hash=sha256:83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f + # via + # -r requirements.in + # requests +click==8.0.4 \ + --hash=sha256:6a7a62563bbfabfda3a38f3023a1db4a35978c0abd76f6c9605ecd6554d6d9b1 \ + --hash=sha256:8458d7b1287c5fb128c90e23381cf99dcde74beaf6c7ff6384ce84d6fe090adb + # via + # -r requirements.in + # gcp-docuploader + # gcp-releasetool +colorlog==6.7.0 \ + --hash=sha256:0d33ca236784a1ba3ff9c532d4964126d8a2c44f1f0cb1d2b0728196f512f662 \ + --hash=sha256:bd94bd21c1e13fac7bd3153f4bc3a7dc0eb0974b8bc2fdf1a989e474f6e582e5 + # via + # gcp-docuploader + # nox +commonmark==0.9.1 \ + --hash=sha256:452f9dc859be7f06631ddcb328b6919c67984aca654e5fefb3914d54691aed60 \ + --hash=sha256:da2f38c92590f83de410ba1a3cbceafbc74fee9def35f9251ba9a971d6d66fd9 + # via rich +cryptography==41.0.4 \ + --hash=sha256:004b6ccc95943f6a9ad3142cfabcc769d7ee38a3f60fb0dddbfb431f818c3a67 \ + --hash=sha256:047c4603aeb4bbd8db2756e38f5b8bd7e94318c047cfe4efeb5d715e08b49311 \ + --hash=sha256:0d9409894f495d465fe6fda92cb70e8323e9648af912d5b9141d616df40a87b8 \ + --hash=sha256:23a25c09dfd0d9f28da2352503b23e086f8e78096b9fd585d1d14eca01613e13 \ + --hash=sha256:2ed09183922d66c4ec5fdaa59b4d14e105c084dd0febd27452de8f6f74704143 \ + --hash=sha256:35c00f637cd0b9d5b6c6bd11b6c3359194a8eba9c46d4e875a3660e3b400005f \ + --hash=sha256:37480760ae08065437e6573d14be973112c9e6dcaf5f11d00147ee74f37a3829 \ + --hash=sha256:3b224890962a2d7b57cf5eeb16ccaafba6083f7b811829f00476309bce2fe0fd \ + --hash=sha256:5a0f09cefded00e648a127048119f77bc2b2ec61e736660b5789e638f43cc397 \ + --hash=sha256:5b72205a360f3b6176485a333256b9bcd48700fc755fef51c8e7e67c4b63e3ac \ + --hash=sha256:7e53db173370dea832190870e975a1e09c86a879b613948f09eb49324218c14d \ + --hash=sha256:7febc3094125fc126a7f6fb1f420d0da639f3f32cb15c8ff0dc3997c4549f51a \ + --hash=sha256:80907d3faa55dc5434a16579952ac6da800935cd98d14dbd62f6f042c7f5e839 \ + --hash=sha256:86defa8d248c3fa029da68ce61fe735432b047e32179883bdb1e79ed9bb8195e \ + --hash=sha256:8ac4f9ead4bbd0bc8ab2d318f97d85147167a488be0e08814a37eb2f439d5cf6 \ + --hash=sha256:93530900d14c37a46ce3d6c9e6fd35dbe5f5601bf6b3a5c325c7bffc030344d9 \ + --hash=sha256:9eeb77214afae972a00dee47382d2591abe77bdae166bda672fb1e24702a3860 \ + --hash=sha256:b5f4dfe950ff0479f1f00eda09c18798d4f49b98f4e2006d644b3301682ebdca \ + --hash=sha256:c3391bd8e6de35f6f1140e50aaeb3e2b3d6a9012536ca23ab0d9c35ec18c8a91 \ + --hash=sha256:c880eba5175f4307129784eca96f4e70b88e57aa3f680aeba3bab0e980b0f37d \ + --hash=sha256:cecfefa17042941f94ab54f769c8ce0fe14beff2694e9ac684176a2535bf9714 \ + --hash=sha256:e40211b4923ba5a6dc9769eab704bdb3fbb58d56c5b336d30996c24fcf12aadb \ + --hash=sha256:efc8ad4e6fc4f1752ebfb58aefece8b4e3c4cae940b0994d43649bdfce8d0d4f + # via + # gcp-releasetool + # secretstorage +distlib==0.3.6 \ + --hash=sha256:14bad2d9b04d3a36127ac97f30b12a19268f211063d8f8ee4f47108896e11b46 \ + --hash=sha256:f35c4b692542ca110de7ef0bea44d73981caeb34ca0b9b6b2e6d7790dda8f80e + # via virtualenv +docutils==0.19 \ + --hash=sha256:33995a6753c30b7f577febfc2c50411fec6aac7f7ffeb7c4cfe5991072dcf9e6 \ + --hash=sha256:5e1de4d849fee02c63b040a4a3fd567f4ab104defd8a5511fbbc24a8a017efbc + # via readme-renderer +filelock==3.8.0 \ + --hash=sha256:55447caa666f2198c5b6b13a26d2084d26fa5b115c00d065664b2124680c4edc \ + --hash=sha256:617eb4e5eedc82fc5f47b6d61e4d11cb837c56cb4544e39081099fa17ad109d4 + # via virtualenv +gcp-docuploader==0.6.4 \ + --hash=sha256:01486419e24633af78fd0167db74a2763974765ee8078ca6eb6964d0ebd388af \ + --hash=sha256:70861190c123d907b3b067da896265ead2eeb9263969d6955c9e0bb091b5ccbf + # via -r requirements.in +gcp-releasetool==1.10.5 \ + --hash=sha256:174b7b102d704b254f2a26a3eda2c684fd3543320ec239baf771542a2e58e109 \ + --hash=sha256:e29d29927fe2ca493105a82958c6873bb2b90d503acac56be2c229e74de0eec9 + # via -r requirements.in +google-api-core==2.10.2 \ + --hash=sha256:10c06f7739fe57781f87523375e8e1a3a4674bf6392cd6131a3222182b971320 \ + --hash=sha256:34f24bd1d5f72a8c4519773d99ca6bf080a6c4e041b4e9f024fe230191dda62e + # via + # google-cloud-core + # google-cloud-storage +google-auth==2.14.1 \ + --hash=sha256:ccaa901f31ad5cbb562615eb8b664b3dd0bf5404a67618e642307f00613eda4d \ + --hash=sha256:f5d8701633bebc12e0deea4df8abd8aff31c28b355360597f7f2ee60f2e4d016 + # via + # gcp-releasetool + # google-api-core + # google-cloud-core + # google-cloud-storage +google-cloud-core==2.3.2 \ + --hash=sha256:8417acf6466be2fa85123441696c4badda48db314c607cf1e5d543fa8bdc22fe \ + --hash=sha256:b9529ee7047fd8d4bf4a2182de619154240df17fbe60ead399078c1ae152af9a + # via google-cloud-storage +google-cloud-storage==2.6.0 \ + --hash=sha256:104ca28ae61243b637f2f01455cc8a05e8f15a2a18ced96cb587241cdd3820f5 \ + --hash=sha256:4ad0415ff61abdd8bb2ae81c1f8f7ec7d91a1011613f2db87c614c550f97bfe9 + # via gcp-docuploader +google-crc32c==1.5.0 \ + --hash=sha256:024894d9d3cfbc5943f8f230e23950cd4906b2fe004c72e29b209420a1e6b05a \ + --hash=sha256:02c65b9817512edc6a4ae7c7e987fea799d2e0ee40c53ec573a692bee24de876 \ + --hash=sha256:02ebb8bf46c13e36998aeaad1de9b48f4caf545e91d14041270d9dca767b780c \ + --hash=sha256:07eb3c611ce363c51a933bf6bd7f8e3878a51d124acfc89452a75120bc436289 \ + --hash=sha256:1034d91442ead5a95b5aaef90dbfaca8633b0247d1e41621d1e9f9db88c36298 \ + --hash=sha256:116a7c3c616dd14a3de8c64a965828b197e5f2d121fedd2f8c5585c547e87b02 \ + --hash=sha256:19e0a019d2c4dcc5e598cd4a4bc7b008546b0358bd322537c74ad47a5386884f \ + --hash=sha256:1c7abdac90433b09bad6c43a43af253e688c9cfc1c86d332aed13f9a7c7f65e2 \ + --hash=sha256:1e986b206dae4476f41bcec1faa057851f3889503a70e1bdb2378d406223994a \ + --hash=sha256:272d3892a1e1a2dbc39cc5cde96834c236d5327e2122d3aaa19f6614531bb6eb \ + --hash=sha256:278d2ed7c16cfc075c91378c4f47924c0625f5fc84b2d50d921b18b7975bd210 \ + --hash=sha256:2ad40e31093a4af319dadf503b2467ccdc8f67c72e4bcba97f8c10cb078207b5 \ + --hash=sha256:2e920d506ec85eb4ba50cd4228c2bec05642894d4c73c59b3a2fe20346bd00ee \ + --hash=sha256:3359fc442a743e870f4588fcf5dcbc1bf929df1fad8fb9905cd94e5edb02e84c \ + --hash=sha256:37933ec6e693e51a5b07505bd05de57eee12f3e8c32b07da7e73669398e6630a \ + --hash=sha256:398af5e3ba9cf768787eef45c803ff9614cc3e22a5b2f7d7ae116df8b11e3314 \ + --hash=sha256:3b747a674c20a67343cb61d43fdd9207ce5da6a99f629c6e2541aa0e89215bcd \ + --hash=sha256:461665ff58895f508e2866824a47bdee72497b091c730071f2b7575d5762ab65 \ + --hash=sha256:4c6fdd4fccbec90cc8a01fc00773fcd5fa28db683c116ee3cb35cd5da9ef6c37 \ + --hash=sha256:5829b792bf5822fd0a6f6eb34c5f81dd074f01d570ed7f36aa101d6fc7a0a6e4 \ + --hash=sha256:596d1f98fc70232fcb6590c439f43b350cb762fb5d61ce7b0e9db4539654cc13 \ + --hash=sha256:5ae44e10a8e3407dbe138984f21e536583f2bba1be9491239f942c2464ac0894 \ + --hash=sha256:635f5d4dd18758a1fbd1049a8e8d2fee4ffed124462d837d1a02a0e009c3ab31 \ + --hash=sha256:64e52e2b3970bd891309c113b54cf0e4384762c934d5ae56e283f9a0afcd953e \ + --hash=sha256:66741ef4ee08ea0b2cc3c86916ab66b6aef03768525627fd6a1b34968b4e3709 \ + --hash=sha256:67b741654b851abafb7bc625b6d1cdd520a379074e64b6a128e3b688c3c04740 \ + --hash=sha256:6ac08d24c1f16bd2bf5eca8eaf8304812f44af5cfe5062006ec676e7e1d50afc \ + --hash=sha256:6f998db4e71b645350b9ac28a2167e6632c239963ca9da411523bb439c5c514d \ + --hash=sha256:72218785ce41b9cfd2fc1d6a017dc1ff7acfc4c17d01053265c41a2c0cc39b8c \ + --hash=sha256:74dea7751d98034887dbd821b7aae3e1d36eda111d6ca36c206c44478035709c \ + --hash=sha256:759ce4851a4bb15ecabae28f4d2e18983c244eddd767f560165563bf9aefbc8d \ + --hash=sha256:77e2fd3057c9d78e225fa0a2160f96b64a824de17840351b26825b0848022906 \ + --hash=sha256:7c074fece789b5034b9b1404a1f8208fc2d4c6ce9decdd16e8220c5a793e6f61 \ + --hash=sha256:7c42c70cd1d362284289c6273adda4c6af8039a8ae12dc451dcd61cdabb8ab57 \ + --hash=sha256:7f57f14606cd1dd0f0de396e1e53824c371e9544a822648cd76c034d209b559c \ + --hash=sha256:83c681c526a3439b5cf94f7420471705bbf96262f49a6fe546a6db5f687a3d4a \ + --hash=sha256:8485b340a6a9e76c62a7dce3c98e5f102c9219f4cfbf896a00cf48caf078d438 \ + --hash=sha256:84e6e8cd997930fc66d5bb4fde61e2b62ba19d62b7abd7a69920406f9ecca946 \ + --hash=sha256:89284716bc6a5a415d4eaa11b1726d2d60a0cd12aadf5439828353662ede9dd7 \ + --hash=sha256:8b87e1a59c38f275c0e3676fc2ab6d59eccecfd460be267ac360cc31f7bcde96 \ + --hash=sha256:8f24ed114432de109aa9fd317278518a5af2d31ac2ea6b952b2f7782b43da091 \ + --hash=sha256:98cb4d057f285bd80d8778ebc4fde6b4d509ac3f331758fb1528b733215443ae \ + --hash=sha256:998679bf62b7fb599d2878aa3ed06b9ce688b8974893e7223c60db155f26bd8d \ + --hash=sha256:9ba053c5f50430a3fcfd36f75aff9caeba0440b2d076afdb79a318d6ca245f88 \ + --hash=sha256:9c99616c853bb585301df6de07ca2cadad344fd1ada6d62bb30aec05219c45d2 \ + --hash=sha256:a1fd716e7a01f8e717490fbe2e431d2905ab8aa598b9b12f8d10abebb36b04dd \ + --hash=sha256:a2355cba1f4ad8b6988a4ca3feed5bff33f6af2d7f134852cf279c2aebfde541 \ + --hash=sha256:b1f8133c9a275df5613a451e73f36c2aea4fe13c5c8997e22cf355ebd7bd0728 \ + --hash=sha256:b8667b48e7a7ef66afba2c81e1094ef526388d35b873966d8a9a447974ed9178 \ + --hash=sha256:ba1eb1843304b1e5537e1fca632fa894d6f6deca8d6389636ee5b4797affb968 \ + --hash=sha256:be82c3c8cfb15b30f36768797a640e800513793d6ae1724aaaafe5bf86f8f346 \ + --hash=sha256:c02ec1c5856179f171e032a31d6f8bf84e5a75c45c33b2e20a3de353b266ebd8 \ + --hash=sha256:c672d99a345849301784604bfeaeba4db0c7aae50b95be04dd651fd2a7310b93 \ + --hash=sha256:c6c777a480337ac14f38564ac88ae82d4cd238bf293f0a22295b66eb89ffced7 \ + --hash=sha256:cae0274952c079886567f3f4f685bcaf5708f0a23a5f5216fdab71f81a6c0273 \ + --hash=sha256:cd67cf24a553339d5062eff51013780a00d6f97a39ca062781d06b3a73b15462 \ + --hash=sha256:d3515f198eaa2f0ed49f8819d5732d70698c3fa37384146079b3799b97667a94 \ + --hash=sha256:d5280312b9af0976231f9e317c20e4a61cd2f9629b7bfea6a693d1878a264ebd \ + --hash=sha256:de06adc872bcd8c2a4e0dc51250e9e65ef2ca91be023b9d13ebd67c2ba552e1e \ + --hash=sha256:e1674e4307fa3024fc897ca774e9c7562c957af85df55efe2988ed9056dc4e57 \ + --hash=sha256:e2096eddb4e7c7bdae4bd69ad364e55e07b8316653234a56552d9c988bd2d61b \ + --hash=sha256:e560628513ed34759456a416bf86b54b2476c59144a9138165c9a1575801d0d9 \ + --hash=sha256:edfedb64740750e1a3b16152620220f51d58ff1b4abceb339ca92e934775c27a \ + --hash=sha256:f13cae8cc389a440def0c8c52057f37359014ccbc9dc1f0827936bcd367c6100 \ + --hash=sha256:f314013e7dcd5cf45ab1945d92e713eec788166262ae8deb2cfacd53def27325 \ + --hash=sha256:f583edb943cf2e09c60441b910d6a20b4d9d626c75a36c8fcac01a6c96c01183 \ + --hash=sha256:fd8536e902db7e365f49e7d9029283403974ccf29b13fc7028b97e2295b33556 \ + --hash=sha256:fe70e325aa68fa4b5edf7d1a4b6f691eb04bbccac0ace68e34820d283b5f80d4 + # via google-resumable-media +google-resumable-media==2.4.0 \ + --hash=sha256:2aa004c16d295c8f6c33b2b4788ba59d366677c0a25ae7382436cb30f776deaa \ + --hash=sha256:8d5518502f92b9ecc84ac46779bd4f09694ecb3ba38a3e7ca737a86d15cbca1f + # via google-cloud-storage +googleapis-common-protos==1.57.0 \ + --hash=sha256:27a849d6205838fb6cc3c1c21cb9800707a661bb21c6ce7fb13e99eb1f8a0c46 \ + --hash=sha256:a9f4a1d7f6d9809657b7f1316a1aa527f6664891531bcfcc13b6696e685f443c + # via google-api-core +idna==3.4 \ + --hash=sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4 \ + --hash=sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2 + # via requests +importlib-metadata==5.0.0 \ + --hash=sha256:da31db32b304314d044d3c12c79bd59e307889b287ad12ff387b3500835fc2ab \ + --hash=sha256:ddb0e35065e8938f867ed4928d0ae5bf2a53b7773871bfe6bcc7e4fcdc7dea43 + # via + # -r requirements.in + # keyring + # twine +jaraco-classes==3.2.3 \ + --hash=sha256:2353de3288bc6b82120752201c6b1c1a14b058267fa424ed5ce5984e3b922158 \ + --hash=sha256:89559fa5c1d3c34eff6f631ad80bb21f378dbcbb35dd161fd2c6b93f5be2f98a + # via keyring +jeepney==0.8.0 \ + --hash=sha256:5efe48d255973902f6badc3ce55e2aa6c5c3b3bc642059ef3a91247bcfcc5806 \ + --hash=sha256:c0a454ad016ca575060802ee4d590dd912e35c122fa04e70306de3d076cce755 + # via + # keyring + # secretstorage +jinja2==3.1.2 \ + --hash=sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852 \ + --hash=sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61 + # via gcp-releasetool +keyring==23.11.0 \ + --hash=sha256:3dd30011d555f1345dec2c262f0153f2f0ca6bca041fb1dc4588349bb4c0ac1e \ + --hash=sha256:ad192263e2cdd5f12875dedc2da13534359a7e760e77f8d04b50968a821c2361 + # via + # gcp-releasetool + # twine +markupsafe==2.1.1 \ + --hash=sha256:0212a68688482dc52b2d45013df70d169f542b7394fc744c02a57374a4207003 \ + --hash=sha256:089cf3dbf0cd6c100f02945abeb18484bd1ee57a079aefd52cffd17fba910b88 \ + --hash=sha256:10c1bfff05d95783da83491be968e8fe789263689c02724e0c691933c52994f5 \ + --hash=sha256:33b74d289bd2f5e527beadcaa3f401e0df0a89927c1559c8566c066fa4248ab7 \ + --hash=sha256:3799351e2336dc91ea70b034983ee71cf2f9533cdff7c14c90ea126bfd95d65a \ + --hash=sha256:3ce11ee3f23f79dbd06fb3d63e2f6af7b12db1d46932fe7bd8afa259a5996603 \ + --hash=sha256:421be9fbf0ffe9ffd7a378aafebbf6f4602d564d34be190fc19a193232fd12b1 \ + --hash=sha256:43093fb83d8343aac0b1baa75516da6092f58f41200907ef92448ecab8825135 \ + --hash=sha256:46d00d6cfecdde84d40e572d63735ef81423ad31184100411e6e3388d405e247 \ + --hash=sha256:4a33dea2b688b3190ee12bd7cfa29d39c9ed176bda40bfa11099a3ce5d3a7ac6 \ + --hash=sha256:4b9fe39a2ccc108a4accc2676e77da025ce383c108593d65cc909add5c3bd601 \ + --hash=sha256:56442863ed2b06d19c37f94d999035e15ee982988920e12a5b4ba29b62ad1f77 \ + --hash=sha256:671cd1187ed5e62818414afe79ed29da836dde67166a9fac6d435873c44fdd02 \ + --hash=sha256:694deca8d702d5db21ec83983ce0bb4b26a578e71fbdbd4fdcd387daa90e4d5e \ + --hash=sha256:6a074d34ee7a5ce3effbc526b7083ec9731bb3cbf921bbe1d3005d4d2bdb3a63 \ + --hash=sha256:6d0072fea50feec76a4c418096652f2c3238eaa014b2f94aeb1d56a66b41403f \ + --hash=sha256:6fbf47b5d3728c6aea2abb0589b5d30459e369baa772e0f37a0320185e87c980 \ + --hash=sha256:7f91197cc9e48f989d12e4e6fbc46495c446636dfc81b9ccf50bb0ec74b91d4b \ + --hash=sha256:86b1f75c4e7c2ac2ccdaec2b9022845dbb81880ca318bb7a0a01fbf7813e3812 \ + --hash=sha256:8dc1c72a69aa7e082593c4a203dcf94ddb74bb5c8a731e4e1eb68d031e8498ff \ + --hash=sha256:8e3dcf21f367459434c18e71b2a9532d96547aef8a871872a5bd69a715c15f96 \ + --hash=sha256:8e576a51ad59e4bfaac456023a78f6b5e6e7651dcd383bcc3e18d06f9b55d6d1 \ + --hash=sha256:96e37a3dc86e80bf81758c152fe66dbf60ed5eca3d26305edf01892257049925 \ + --hash=sha256:97a68e6ada378df82bc9f16b800ab77cbf4b2fada0081794318520138c088e4a \ + --hash=sha256:99a2a507ed3ac881b975a2976d59f38c19386d128e7a9a18b7df6fff1fd4c1d6 \ + --hash=sha256:a49907dd8420c5685cfa064a1335b6754b74541bbb3706c259c02ed65b644b3e \ + --hash=sha256:b09bf97215625a311f669476f44b8b318b075847b49316d3e28c08e41a7a573f \ + --hash=sha256:b7bd98b796e2b6553da7225aeb61f447f80a1ca64f41d83612e6139ca5213aa4 \ + --hash=sha256:b87db4360013327109564f0e591bd2a3b318547bcef31b468a92ee504d07ae4f \ + --hash=sha256:bcb3ed405ed3222f9904899563d6fc492ff75cce56cba05e32eff40e6acbeaa3 \ + --hash=sha256:d4306c36ca495956b6d568d276ac11fdd9c30a36f1b6eb928070dc5360b22e1c \ + --hash=sha256:d5ee4f386140395a2c818d149221149c54849dfcfcb9f1debfe07a8b8bd63f9a \ + --hash=sha256:dda30ba7e87fbbb7eab1ec9f58678558fd9a6b8b853530e176eabd064da81417 \ + --hash=sha256:e04e26803c9c3851c931eac40c695602c6295b8d432cbe78609649ad9bd2da8a \ + --hash=sha256:e1c0b87e09fa55a220f058d1d49d3fb8df88fbfab58558f1198e08c1e1de842a \ + --hash=sha256:e72591e9ecd94d7feb70c1cbd7be7b3ebea3f548870aa91e2732960fa4d57a37 \ + --hash=sha256:e8c843bbcda3a2f1e3c2ab25913c80a3c5376cd00c6e8c4a86a89a28c8dc5452 \ + --hash=sha256:efc1913fd2ca4f334418481c7e595c00aad186563bbc1ec76067848c7ca0a933 \ + --hash=sha256:f121a1420d4e173a5d96e47e9a0c0dcff965afdf1626d28de1460815f7c4ee7a \ + --hash=sha256:fc7b548b17d238737688817ab67deebb30e8073c95749d55538ed473130ec0c7 + # via jinja2 +more-itertools==9.0.0 \ + --hash=sha256:250e83d7e81d0c87ca6bd942e6aeab8cc9daa6096d12c5308f3f92fa5e5c1f41 \ + --hash=sha256:5a6257e40878ef0520b1803990e3e22303a41b5714006c32a3fd8304b26ea1ab + # via jaraco-classes +nox==2022.11.21 \ + --hash=sha256:0e41a990e290e274cb205a976c4c97ee3c5234441a8132c8c3fd9ea3c22149eb \ + --hash=sha256:e21c31de0711d1274ca585a2c5fde36b1aa962005ba8e9322bf5eeed16dcd684 + # via -r requirements.in +packaging==21.3 \ + --hash=sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb \ + --hash=sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522 + # via + # gcp-releasetool + # nox +pkginfo==1.8.3 \ + --hash=sha256:848865108ec99d4901b2f7e84058b6e7660aae8ae10164e015a6dcf5b242a594 \ + --hash=sha256:a84da4318dd86f870a9447a8c98340aa06216bfc6f2b7bdc4b8766984ae1867c + # via twine +platformdirs==2.5.4 \ + --hash=sha256:1006647646d80f16130f052404c6b901e80ee4ed6bef6792e1f238a8969106f7 \ + --hash=sha256:af0276409f9a02373d540bf8480021a048711d572745aef4b7842dad245eba10 + # via virtualenv +protobuf==3.20.3 \ + --hash=sha256:03038ac1cfbc41aa21f6afcbcd357281d7521b4157926f30ebecc8d4ea59dcb7 \ + --hash=sha256:28545383d61f55b57cf4df63eebd9827754fd2dc25f80c5253f9184235db242c \ + --hash=sha256:2e3427429c9cffebf259491be0af70189607f365c2f41c7c3764af6f337105f2 \ + --hash=sha256:398a9e0c3eaceb34ec1aee71894ca3299605fa8e761544934378bbc6c97de23b \ + --hash=sha256:44246bab5dd4b7fbd3c0c80b6f16686808fab0e4aca819ade6e8d294a29c7050 \ + --hash=sha256:447d43819997825d4e71bf5769d869b968ce96848b6479397e29fc24c4a5dfe9 \ + --hash=sha256:67a3598f0a2dcbc58d02dd1928544e7d88f764b47d4a286202913f0b2801c2e7 \ + --hash=sha256:74480f79a023f90dc6e18febbf7b8bac7508420f2006fabd512013c0c238f454 \ + --hash=sha256:819559cafa1a373b7096a482b504ae8a857c89593cf3a25af743ac9ecbd23480 \ + --hash=sha256:899dc660cd599d7352d6f10d83c95df430a38b410c1b66b407a6b29265d66469 \ + --hash=sha256:8c0c984a1b8fef4086329ff8dd19ac77576b384079247c770f29cc8ce3afa06c \ + --hash=sha256:9aae4406ea63d825636cc11ffb34ad3379335803216ee3a856787bcf5ccc751e \ + --hash=sha256:a7ca6d488aa8ff7f329d4c545b2dbad8ac31464f1d8b1c87ad1346717731e4db \ + --hash=sha256:b6cc7ba72a8850621bfec987cb72623e703b7fe2b9127a161ce61e61558ad905 \ + --hash=sha256:bf01b5720be110540be4286e791db73f84a2b721072a3711efff6c324cdf074b \ + --hash=sha256:c02ce36ec760252242a33967d51c289fd0e1c0e6e5cc9397e2279177716add86 \ + --hash=sha256:d9e4432ff660d67d775c66ac42a67cf2453c27cb4d738fc22cb53b5d84c135d4 \ + --hash=sha256:daa564862dd0d39c00f8086f88700fdbe8bc717e993a21e90711acfed02f2402 \ + --hash=sha256:de78575669dddf6099a8a0f46a27e82a1783c557ccc38ee620ed8cc96d3be7d7 \ + --hash=sha256:e64857f395505ebf3d2569935506ae0dfc4a15cb80dc25261176c784662cdcc4 \ + --hash=sha256:f4bd856d702e5b0d96a00ec6b307b0f51c1982c2bf9c0052cf9019e9a544ba99 \ + --hash=sha256:f4c42102bc82a51108e449cbb32b19b180022941c727bac0cfd50170341f16ee + # via + # gcp-docuploader + # gcp-releasetool + # google-api-core + # googleapis-common-protos +pyasn1==0.4.8 \ + --hash=sha256:39c7e2ec30515947ff4e87fb6f456dfc6e84857d34be479c9d4a4ba4bf46aa5d \ + --hash=sha256:aef77c9fb94a3ac588e87841208bdec464471d9871bd5050a287cc9a475cd0ba + # via + # pyasn1-modules + # rsa +pyasn1-modules==0.2.8 \ + --hash=sha256:905f84c712230b2c592c19470d3ca8d552de726050d1d1716282a1f6146be65e \ + --hash=sha256:a50b808ffeb97cb3601dd25981f6b016cbb3d31fbf57a8b8a87428e6158d0c74 + # via google-auth +pycparser==2.21 \ + --hash=sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9 \ + --hash=sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206 + # via cffi +pygments==2.15.0 \ + --hash=sha256:77a3299119af881904cd5ecd1ac6a66214b6e9bed1f2db16993b54adede64094 \ + --hash=sha256:f7e36cffc4c517fbc252861b9a6e4644ca0e5abadf9a113c72d1358ad09b9500 + # via + # readme-renderer + # rich +pyjwt==2.6.0 \ + --hash=sha256:69285c7e31fc44f68a1feb309e948e0df53259d579295e6cfe2b1792329f05fd \ + --hash=sha256:d83c3d892a77bbb74d3e1a2cfa90afaadb60945205d1095d9221f04466f64c14 + # via gcp-releasetool +pyparsing==3.0.9 \ + --hash=sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb \ + --hash=sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc + # via packaging +pyperclip==1.8.2 \ + --hash=sha256:105254a8b04934f0bc84e9c24eb360a591aaf6535c9def5f29d92af107a9bf57 + # via gcp-releasetool +python-dateutil==2.8.2 \ + --hash=sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86 \ + --hash=sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9 + # via gcp-releasetool +readme-renderer==37.3 \ + --hash=sha256:cd653186dfc73055656f090f227f5cb22a046d7f71a841dfa305f55c9a513273 \ + --hash=sha256:f67a16caedfa71eef48a31b39708637a6f4664c4394801a7b0d6432d13907343 + # via twine +requests==2.31.0 \ + --hash=sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f \ + --hash=sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1 + # via + # gcp-releasetool + # google-api-core + # google-cloud-storage + # requests-toolbelt + # twine +requests-toolbelt==0.10.1 \ + --hash=sha256:18565aa58116d9951ac39baa288d3adb5b3ff975c4f25eee78555d89e8f247f7 \ + --hash=sha256:62e09f7ff5ccbda92772a29f394a49c3ad6cb181d568b1337626b2abb628a63d + # via twine +rfc3986==2.0.0 \ + --hash=sha256:50b1502b60e289cb37883f3dfd34532b8873c7de9f49bb546641ce9cbd256ebd \ + --hash=sha256:97aacf9dbd4bfd829baad6e6309fa6573aaf1be3f6fa735c8ab05e46cecb261c + # via twine +rich==12.6.0 \ + --hash=sha256:a4eb26484f2c82589bd9a17c73d32a010b1e29d89f1604cd9bf3a2097b81bb5e \ + --hash=sha256:ba3a3775974105c221d31141f2c116f4fd65c5ceb0698657a11e9f295ec93fd0 + # via twine +rsa==4.9 \ + --hash=sha256:90260d9058e514786967344d0ef75fa8727eed8a7d2e43ce9f4bcf1b536174f7 \ + --hash=sha256:e38464a49c6c85d7f1351b0126661487a7e0a14a50f1675ec50eb34d4f20ef21 + # via google-auth +secretstorage==3.3.3 \ + --hash=sha256:2403533ef369eca6d2ba81718576c5e0f564d5cca1b58f73a8b23e7d4eeebd77 \ + --hash=sha256:f356e6628222568e3af06f2eba8df495efa13b3b63081dafd4f7d9a7b7bc9f99 + # via keyring +six==1.16.0 \ + --hash=sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926 \ + --hash=sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254 + # via + # bleach + # gcp-docuploader + # google-auth + # python-dateutil +twine==4.0.1 \ + --hash=sha256:42026c18e394eac3e06693ee52010baa5313e4811d5a11050e7d48436cf41b9e \ + --hash=sha256:96b1cf12f7ae611a4a40b6ae8e9570215daff0611828f5fe1f37a16255ab24a0 + # via -r requirements.in +typing-extensions==4.4.0 \ + --hash=sha256:1511434bb92bf8dd198c12b1cc812e800d4181cfcb867674e0f8279cc93087aa \ + --hash=sha256:16fa4864408f655d35ec496218b85f79b3437c829e93320c7c9215ccfd92489e + # via -r requirements.in +urllib3==1.26.18 \ + --hash=sha256:34b97092d7e0a3a8cf7cd10e386f401b3737364026c45e622aa02903dffe0f07 \ + --hash=sha256:f8ecc1bba5667413457c529ab955bf8c67b45db799d159066261719e328580a0 + # via + # requests + # twine +virtualenv==20.16.7 \ + --hash=sha256:8691e3ff9387f743e00f6bb20f70121f5e4f596cae754531f2b3b3a1b1ac696e \ + --hash=sha256:efd66b00386fdb7dbe4822d172303f40cd05e50e01740b19ea42425cbe653e29 + # via nox +webencodings==0.5.1 \ + --hash=sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78 \ + --hash=sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923 + # via bleach +wheel==0.38.4 \ + --hash=sha256:965f5259b566725405b05e7cf774052044b1ed30119b5d586b2703aafe8719ac \ + --hash=sha256:b60533f3f5d530e971d6737ca6d58681ee434818fab630c83a734bb10c083ce8 + # via -r requirements.in +zipp==3.10.0 \ + --hash=sha256:4fcb6f278987a6605757302a6e40e896257570d11c51628968ccb2a47e80c6c1 \ + --hash=sha256:7a7262fd930bd3e36c50b9a64897aec3fafff3dfdeec9623ae22b40e93f99bb8 + # via importlib-metadata + +# The following packages are considered to be unsafe in a requirements file: +setuptools==65.5.1 \ + --hash=sha256:d0b9a8433464d5800cbe05094acf5c6d52a91bfac9b52bcfc4d41382be5d5d31 \ + --hash=sha256:e197a19aa8ec9722928f2206f8de752def0e4c9fc6953527360d1c36d94ddb2f + # via -r requirements.in diff --git a/.kokoro/requirements/build.in b/.kokoro/requirements/build.in deleted file mode 100644 index a9455578618..00000000000 --- a/.kokoro/requirements/build.in +++ /dev/null @@ -1,6 +0,0 @@ ---only-binary :all: -twine>=6.2.0 -build>=1.3.0 -wheel>=0.45.1 -keyring>=25.7.0 -keyrings.google-artifactregistry-auth>=1.1.2 diff --git a/.kokoro/requirements/build.txt b/.kokoro/requirements/build.txt deleted file mode 100644 index 375d78c0f81..00000000000 --- a/.kokoro/requirements/build.txt +++ /dev/null @@ -1,408 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.13 -# by the following command: -# -# pip-compile --generate-hashes --output-file=.kokoro/requirements/build.txt .kokoro/requirements/build.in -# ---only-binary :all: - -build==1.3.0 \ - --hash=sha256:7145f0b5061ba90a1500d60bd1b13ca0a8a4cebdd0cc16ed8adf1c0e739f43b4 - # via -r .kokoro/requirements/build.in -cachetools==6.2.2 \ - --hash=sha256:6c09c98183bf58560c97b2abfcedcbaf6a896a490f534b031b661d3723b45ace - # via google-auth -certifi==2025.11.12 \ - --hash=sha256:97de8790030bbd5c2d96b7ec782fc2f7820ef8dba6db909ccf95449f2d062d4b - # via requests -cffi==2.0.0 \ - --hash=sha256:00bdf7acc5f795150faa6957054fbbca2439db2f775ce831222b66f192f03beb \ - --hash=sha256:07b271772c100085dd28b74fa0cd81c8fb1a3ba18b21e03d7c27f3436a10606b \ - --hash=sha256:087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f \ - --hash=sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9 \ - --hash=sha256:0cf2d91ecc3fcc0625c2c530fe004f82c110405f101548512cce44322fa8ac44 \ - --hash=sha256:0f6084a0ea23d05d20c3edcda20c3d006f9b6f3fefeac38f59262e10cef47ee2 \ - --hash=sha256:12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c \ - --hash=sha256:19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75 \ - --hash=sha256:1cd13c99ce269b3ed80b417dcd591415d3372bcac067009b6e0f59c7d4015e65 \ - --hash=sha256:1e3a615586f05fc4065a8b22b8152f0c1b00cdbc60596d187c2a74f9e3036e4e \ - --hash=sha256:1f72fb8906754ac8a2cc3f9f5aaa298070652a0ffae577e0ea9bd480dc3c931a \ - --hash=sha256:1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e \ - --hash=sha256:203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25 \ - --hash=sha256:2081580ebb843f759b9f617314a24ed5738c51d2aee65d31e02f6f7a2b97707a \ - --hash=sha256:21d1152871b019407d8ac3985f6775c079416c282e431a4da6afe7aefd2bccbe \ - --hash=sha256:24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b \ - --hash=sha256:256f80b80ca3853f90c21b23ee78cd008713787b1b1e93eae9f3d6a7134abd91 \ - --hash=sha256:28a3a209b96630bca57cce802da70c266eb08c6e97e5afd61a75611ee6c64592 \ - --hash=sha256:2c8f814d84194c9ea681642fd164267891702542f028a15fc97d4674b6206187 \ - --hash=sha256:2de9a304e27f7596cd03d16f1b7c72219bd944e99cc52b84d0145aefb07cbd3c \ - --hash=sha256:38100abb9d1b1435bc4cc340bb4489635dc2f0da7456590877030c9b3d40b0c1 \ - --hash=sha256:3925dd22fa2b7699ed2617149842d2e6adde22b262fcbfada50e3d195e4b3a94 \ - --hash=sha256:3e17ed538242334bf70832644a32a7aae3d83b57567f9fd60a26257e992b79ba \ - --hash=sha256:3e837e369566884707ddaf85fc1744b47575005c0a229de3327f8f9a20f4efeb \ - --hash=sha256:3f4d46d8b35698056ec29bca21546e1551a205058ae1a181d871e278b0b28165 \ - --hash=sha256:45d5e886156860dc35862657e1494b9bae8dfa63bf56796f2fb56e1679fc0bca \ - --hash=sha256:4647afc2f90d1ddd33441e5b0e85b16b12ddec4fca55f0d9671fef036ecca27c \ - --hash=sha256:4671d9dd5ec934cb9a73e7ee9676f9362aba54f7f34910956b84d727b0d73fb6 \ - --hash=sha256:53f77cbe57044e88bbd5ed26ac1d0514d2acf0591dd6bb02a3ae37f76811b80c \ - --hash=sha256:5eda85d6d1879e692d546a078b44251cdd08dd1cfb98dfb77b670c97cee49ea0 \ - --hash=sha256:5fed36fccc0612a53f1d4d9a816b50a36702c28a2aa880cb8a122b3466638743 \ - --hash=sha256:61d028e90346df14fedc3d1e5441df818d095f3b87d286825dfcbd6459b7ef63 \ - --hash=sha256:66f011380d0e49ed280c789fbd08ff0d40968ee7b665575489afa95c98196ab5 \ - --hash=sha256:6824f87845e3396029f3820c206e459ccc91760e8fa24422f8b0c3d1731cbec5 \ - --hash=sha256:6c6c373cfc5c83a975506110d17457138c8c63016b563cc9ed6e056a82f13ce4 \ - --hash=sha256:6d02d6655b0e54f54c4ef0b94eb6be0607b70853c45ce98bd278dc7de718be5d \ - --hash=sha256:6d50360be4546678fc1b79ffe7a66265e28667840010348dd69a314145807a1b \ - --hash=sha256:730cacb21e1bdff3ce90babf007d0a0917cc3e6492f336c2f0134101e0944f93 \ - --hash=sha256:737fe7d37e1a1bffe70bd5754ea763a62a066dc5913ca57e957824b72a85e205 \ - --hash=sha256:74a03b9698e198d47562765773b4a8309919089150a0bb17d829ad7b44b60d27 \ - --hash=sha256:7553fb2090d71822f02c629afe6042c299edf91ba1bf94951165613553984512 \ - --hash=sha256:7a66c7204d8869299919db4d5069a82f1561581af12b11b3c9f48c584eb8743d \ - --hash=sha256:7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c \ - --hash=sha256:81afed14892743bbe14dacb9e36d9e0e504cd204e0b165062c488942b9718037 \ - --hash=sha256:8941aaadaf67246224cee8c3803777eed332a19d909b47e29c9842ef1e79ac26 \ - --hash=sha256:89472c9762729b5ae1ad974b777416bfda4ac5642423fa93bd57a09204712322 \ - --hash=sha256:8ea985900c5c95ce9db1745f7933eeef5d314f0565b27625d9a10ec9881e1bfb \ - --hash=sha256:8eca2a813c1cb7ad4fb74d368c2ffbbb4789d377ee5bb8df98373c2cc0dee76c \ - --hash=sha256:92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8 \ - --hash=sha256:9332088d75dc3241c702d852d4671613136d90fa6881da7d770a483fd05248b4 \ - --hash=sha256:94698a9c5f91f9d138526b48fe26a199609544591f859c870d477351dc7b2414 \ - --hash=sha256:9a67fc9e8eb39039280526379fb3a70023d77caec1852002b4da7e8b270c4dd9 \ - --hash=sha256:9de40a7b0323d889cf8d23d1ef214f565ab154443c42737dfe52ff82cf857664 \ - --hash=sha256:a05d0c237b3349096d3981b727493e22147f934b20f6f125a3eba8f994bec4a9 \ - --hash=sha256:afb8db5439b81cf9c9d0c80404b60c3cc9c3add93e114dcae767f1477cb53775 \ - --hash=sha256:b18a3ed7d5b3bd8d9ef7a8cb226502c6bf8308df1525e1cc676c3680e7176739 \ - --hash=sha256:b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc \ - --hash=sha256:b21e08af67b8a103c71a250401c78d5e0893beff75e28c53c98f4de42f774062 \ - --hash=sha256:b4c854ef3adc177950a8dfc81a86f5115d2abd545751a304c5bcf2c2c7283cfe \ - --hash=sha256:b882b3df248017dba09d6b16defe9b5c407fe32fc7c65a9c69798e6175601be9 \ - --hash=sha256:baf5215e0ab74c16e2dd324e8ec067ef59e41125d3eade2b863d294fd5035c92 \ - --hash=sha256:c649e3a33450ec82378822b3dad03cc228b8f5963c0c12fc3b1e0ab940f768a5 \ - --hash=sha256:c654de545946e0db659b3400168c9ad31b5d29593291482c43e3564effbcee13 \ - --hash=sha256:c6638687455baf640e37344fe26d37c404db8b80d037c3d29f58fe8d1c3b194d \ - --hash=sha256:c8d3b5532fc71b7a77c09192b4a5a200ea992702734a2e9279a37f2478236f26 \ - --hash=sha256:cb527a79772e5ef98fb1d700678fe031e353e765d1ca2d409c92263c6d43e09f \ - --hash=sha256:cf364028c016c03078a23b503f02058f1814320a56ad535686f90565636a9495 \ - --hash=sha256:d48a880098c96020b02d5a1f7d9251308510ce8858940e6fa99ece33f610838b \ - --hash=sha256:d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6 \ - --hash=sha256:d9b29c1f0ae438d5ee9acb31cadee00a58c46cc9c0b2f9038c6b0b3470877a8c \ - --hash=sha256:d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef \ - --hash=sha256:da68248800ad6320861f129cd9c1bf96ca849a2771a59e0344e88681905916f5 \ - --hash=sha256:da902562c3e9c550df360bfa53c035b2f241fed6d9aef119048073680ace4a18 \ - --hash=sha256:dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad \ - --hash=sha256:dd4f05f54a52fb558f1ba9f528228066954fee3ebe629fc1660d874d040ae5a3 \ - --hash=sha256:de8dad4425a6ca6e4e5e297b27b5c824ecc7581910bf9aee86cb6835e6812aa7 \ - --hash=sha256:e11e82b744887154b182fd3e7e8512418446501191994dbf9c9fc1f32cc8efd5 \ - --hash=sha256:e6e73b9e02893c764e7e8d5bb5ce277f1a009cd5243f8228f75f842bf937c534 \ - --hash=sha256:f73b96c41e3b2adedc34a7356e64c8eb96e03a3782b535e043a986276ce12a49 \ - --hash=sha256:f93fd8e5c8c0a4aa1f424d6173f14a892044054871c771f8566e4008eaa359d2 \ - --hash=sha256:fc33c5141b55ed366cfaad382df24fe7dcbc686de5be719b207bb248e3053dc5 \ - --hash=sha256:fc7de24befaeae77ba923797c7c87834c73648a05a4bde34b3b7e5588973a453 \ - --hash=sha256:fe562eb1a64e67dd297ccc4f5addea2501664954f2692b69a76449ec7913ecbf - # via cryptography -charset-normalizer==3.4.4 \ - --hash=sha256:027f6de494925c0ab2a55eab46ae5129951638a49a34d87f4c3eda90f696b4ad \ - --hash=sha256:077fbb858e903c73f6c9db43374fd213b0b6a778106bc7032446a8e8b5b38b93 \ - --hash=sha256:0a98e6759f854bd25a58a73fa88833fba3b7c491169f86ce1180c948ab3fd394 \ - --hash=sha256:0d3d8f15c07f86e9ff82319b3d9ef6f4bf907608f53fe9d92b28ea9ae3d1fd89 \ - --hash=sha256:0f04b14ffe5fdc8c4933862d8306109a2c51e0704acfa35d51598eb45a1e89fc \ - --hash=sha256:11d694519d7f29d6cd09f6ac70028dba10f92f6cdd059096db198c283794ac86 \ - --hash=sha256:194f08cbb32dc406d6e1aea671a68be0823673db2832b38405deba2fb0d88f63 \ - --hash=sha256:1bee1e43c28aa63cb16e5c14e582580546b08e535299b8b6158a7c9c768a1f3d \ - --hash=sha256:21d142cc6c0ec30d2efee5068ca36c128a30b0f2c53c1c07bd78cb6bc1d3be5f \ - --hash=sha256:2437418e20515acec67d86e12bf70056a33abdacb5cb1655042f6538d6b085a8 \ - --hash=sha256:244bfb999c71b35de57821b8ea746b24e863398194a4014e4c76adc2bbdfeff0 \ - --hash=sha256:2677acec1a2f8ef614c6888b5b4ae4060cc184174a938ed4e8ef690e15d3e505 \ - --hash=sha256:277e970e750505ed74c832b4bf75dac7476262ee2a013f5574dd49075879e161 \ - --hash=sha256:2aaba3b0819274cc41757a1da876f810a3e4d7b6eb25699253a4effef9e8e4af \ - --hash=sha256:2b7d8f6c26245217bd2ad053761201e9f9680f8ce52f0fcd8d0755aeae5b2152 \ - --hash=sha256:2c9d3c380143a1fedbff95a312aa798578371eb29da42106a29019368a475318 \ - --hash=sha256:3162d5d8ce1bb98dd51af660f2121c55d0fa541b46dff7bb9b9f86ea1d87de72 \ - --hash=sha256:31fd66405eaf47bb62e8cd575dc621c56c668f27d46a61d975a249930dd5e2a4 \ - --hash=sha256:362d61fd13843997c1c446760ef36f240cf81d3ebf74ac62652aebaf7838561e \ - --hash=sha256:376bec83a63b8021bb5c8ea75e21c4ccb86e7e45ca4eb81146091b56599b80c3 \ - --hash=sha256:44c2a8734b333e0578090c4cd6b16f275e07aa6614ca8715e6c038e865e70576 \ - --hash=sha256:47cc91b2f4dd2833fddaedd2893006b0106129d4b94fdb6af1f4ce5a9965577c \ - --hash=sha256:4902828217069c3c5c71094537a8e623f5d097858ac6ca8252f7b4d10b7560f1 \ - --hash=sha256:4bd5d4137d500351a30687c2d3971758aac9a19208fc110ccb9d7188fbe709e8 \ - --hash=sha256:4fe7859a4e3e8457458e2ff592f15ccb02f3da787fcd31e0183879c3ad4692a1 \ - --hash=sha256:542d2cee80be6f80247095cc36c418f7bddd14f4a6de45af91dfad36d817bba2 \ - --hash=sha256:554af85e960429cf30784dd47447d5125aaa3b99a6f0683589dbd27e2f45da44 \ - --hash=sha256:5833d2c39d8896e4e19b689ffc198f08ea58116bee26dea51e362ecc7cd3ed26 \ - --hash=sha256:5947809c8a2417be3267efc979c47d76a079758166f7d43ef5ae8e9f92751f88 \ - --hash=sha256:5ae497466c7901d54b639cf42d5b8c1b6a4fead55215500d2f486d34db48d016 \ - --hash=sha256:5bd2293095d766545ec1a8f612559f6b40abc0eb18bb2f5d1171872d34036ede \ - --hash=sha256:5bfbb1b9acf3334612667b61bd3002196fe2a1eb4dd74d247e0f2a4d50ec9bbf \ - --hash=sha256:5cb4d72eea50c8868f5288b7f7f33ed276118325c1dfd3957089f6b519e1382a \ - --hash=sha256:5dbe56a36425d26d6cfb40ce79c314a2e4dd6211d51d6d2191c00bed34f354cc \ - --hash=sha256:5f819d5fe9234f9f82d75bdfa9aef3a3d72c4d24a6e57aeaebba32a704553aa0 \ - --hash=sha256:64b55f9dce520635f018f907ff1b0df1fdc31f2795a922fb49dd14fbcdf48c84 \ - --hash=sha256:6515f3182dbe4ea06ced2d9e8666d97b46ef4c75e326b79bb624110f122551db \ - --hash=sha256:65e2befcd84bc6f37095f5961e68a6f077bf44946771354a28ad434c2cce0ae1 \ - --hash=sha256:6aee717dcfead04c6eb1ce3bd29ac1e22663cdea57f943c87d1eab9a025438d7 \ - --hash=sha256:6b39f987ae8ccdf0d2642338faf2abb1862340facc796048b604ef14919e55ed \ - --hash=sha256:6e1fcf0720908f200cd21aa4e6750a48ff6ce4afe7ff5a79a90d5ed8a08296f8 \ - --hash=sha256:74018750915ee7ad843a774364e13a3db91682f26142baddf775342c3f5b1133 \ - --hash=sha256:74664978bb272435107de04e36db5a9735e78232b85b77d45cfb38f758efd33e \ - --hash=sha256:74bb723680f9f7a6234dcf67aea57e708ec1fbdf5699fb91dfd6f511b0a320ef \ - --hash=sha256:752944c7ffbfdd10c074dc58ec2d5a8a4cd9493b314d367c14d24c17684ddd14 \ - --hash=sha256:778d2e08eda00f4256d7f672ca9fef386071c9202f5e4607920b86d7803387f2 \ - --hash=sha256:780236ac706e66881f3b7f2f32dfe90507a09e67d1d454c762cf642e6e1586e0 \ - --hash=sha256:798d75d81754988d2565bff1b97ba5a44411867c0cf32b77a7e8f8d84796b10d \ - --hash=sha256:799a7a5e4fb2d5898c60b640fd4981d6a25f1c11790935a44ce38c54e985f828 \ - --hash=sha256:7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f \ - --hash=sha256:7c308f7e26e4363d79df40ca5b2be1c6ba9f02bdbccfed5abddb7859a6ce72cf \ - --hash=sha256:7fa17817dc5625de8a027cb8b26d9fefa3ea28c8253929b8d6649e705d2835b6 \ - --hash=sha256:81d5eb2a312700f4ecaa977a8235b634ce853200e828fbadf3a9c50bab278328 \ - --hash=sha256:82004af6c302b5d3ab2cfc4cc5f29db16123b1a8417f2e25f9066f91d4411090 \ - --hash=sha256:837c2ce8c5a65a2035be9b3569c684358dfbf109fd3b6969630a87535495ceaa \ - --hash=sha256:840c25fb618a231545cbab0564a799f101b63b9901f2569faecd6b222ac72381 \ - --hash=sha256:8a6562c3700cce886c5be75ade4a5db4214fda19fede41d9792d100288d8f94c \ - --hash=sha256:8af65f14dc14a79b924524b1e7fffe304517b2bff5a58bf64f30b98bbc5079eb \ - --hash=sha256:8ef3c867360f88ac904fd3f5e1f902f13307af9052646963ee08ff4f131adafc \ - --hash=sha256:99ae2cffebb06e6c22bdc25801d7b30f503cc87dbd283479e7b606f70aff57ec \ - --hash=sha256:9a26f18905b8dd5d685d6d07b0cdf98a79f3c7a918906af7cc143ea2e164c8bc \ - --hash=sha256:9b35f4c90079ff2e2edc5b26c0c77925e5d2d255c42c74fdb70fb49b172726ac \ - --hash=sha256:9cd98cdc06614a2f768d2b7286d66805f94c48cde050acdbbb7db2600ab3197e \ - --hash=sha256:9d1bb833febdff5c8927f922386db610b49db6e0d4f4ee29601d71e7c2694313 \ - --hash=sha256:9f7fcd74d410a36883701fafa2482a6af2ff5ba96b9a620e9e0721e28ead5569 \ - --hash=sha256:a59cb51917aa591b1c4e6a43c132f0cdc3c76dbad6155df4e28ee626cc77a0a3 \ - --hash=sha256:a61900df84c667873b292c3de315a786dd8dac506704dea57bc957bd31e22c7d \ - --hash=sha256:a79cfe37875f822425b89a82333404539ae63dbdddf97f84dcbc3d339aae9525 \ - --hash=sha256:a8a8b89589086a25749f471e6a900d3f662d1d3b6e2e59dcecf787b1cc3a1894 \ - --hash=sha256:a8bf8d0f749c5757af2142fe7903a9df1d2e8aa3841559b2bad34b08d0e2bcf3 \ - --hash=sha256:a9768c477b9d7bd54bc0c86dbaebdec6f03306675526c9927c0e8a04e8f94af9 \ - --hash=sha256:ac1c4a689edcc530fc9d9aa11f5774b9e2f33f9a0c6a57864e90908f5208d30a \ - --hash=sha256:af2d8c67d8e573d6de5bc30cdb27e9b95e49115cd9baad5ddbd1a6207aaa82a9 \ - --hash=sha256:b435cba5f4f750aa6c0a0d92c541fb79f69a387c91e61f1795227e4ed9cece14 \ - --hash=sha256:b5b290ccc2a263e8d185130284f8501e3e36c5e02750fc6b6bdeb2e9e96f1e25 \ - --hash=sha256:b5d84d37db046c5ca74ee7bb47dd6cbc13f80665fdde3e8040bdd3fb015ecb50 \ - --hash=sha256:b7cf1017d601aa35e6bb650b6ad28652c9cd78ee6caff19f3c28d03e1c80acbf \ - --hash=sha256:bc7637e2f80d8530ee4a78e878bce464f70087ce73cf7c1caf142416923b98f1 \ - --hash=sha256:c0463276121fdee9c49b98908b3a89c39be45d86d1dbaa22957e38f6321d4ce3 \ - --hash=sha256:c4ef880e27901b6cc782f1b95f82da9313c0eb95c3af699103088fa0ac3ce9ac \ - --hash=sha256:c8ae8a0f02f57a6e61203a31428fa1d677cbe50c93622b4149d5c0f319c1d19e \ - --hash=sha256:ca5862d5b3928c4940729dacc329aa9102900382fea192fc5e52eb69d6093815 \ - --hash=sha256:cb01158d8b88ee68f15949894ccc6712278243d95f344770fa7593fa2d94410c \ - --hash=sha256:cb6254dc36b47a990e59e1068afacdcd02958bdcce30bb50cc1700a8b9d624a6 \ - --hash=sha256:cc00f04ed596e9dc0da42ed17ac5e596c6ccba999ba6bd92b0e0aef2f170f2d6 \ - --hash=sha256:cd09d08005f958f370f539f186d10aec3377d55b9eeb0d796025d4886119d76e \ - --hash=sha256:cd4b7ca9984e5e7985c12bc60a6f173f3c958eae74f3ef6624bb6b26e2abbae4 \ - --hash=sha256:ce8a0633f41a967713a59c4139d29110c07e826d131a316b50ce11b1d79b4f84 \ - --hash=sha256:cead0978fc57397645f12578bfd2d5ea9138ea0fac82b2f63f7f7c6877986a69 \ - --hash=sha256:d055ec1e26e441f6187acf818b73564e6e6282709e9bcb5b63f5b23068356a15 \ - --hash=sha256:d1f13550535ad8cff21b8d757a3257963e951d96e20ec82ab44bc64aeb62a191 \ - --hash=sha256:d9c7f57c3d666a53421049053eaacdd14bbd0a528e2186fcb2e672effd053bb0 \ - --hash=sha256:d9e45d7faa48ee908174d8fe84854479ef838fc6a705c9315372eacbc2f02897 \ - --hash=sha256:da3326d9e65ef63a817ecbcc0df6e94463713b754fe293eaa03da99befb9a5bd \ - --hash=sha256:de00632ca48df9daf77a2c65a484531649261ec9f25489917f09e455cb09ddb2 \ - --hash=sha256:e1f185f86a6f3403aa2420e815904c67b2f9ebc443f045edd0de921108345794 \ - --hash=sha256:e824f1492727fa856dd6eda4f7cee25f8518a12f3c4a56a74e8095695089cf6d \ - --hash=sha256:e912091979546adf63357d7e2ccff9b44f026c075aeaf25a52d0e95ad2281074 \ - --hash=sha256:eaabd426fe94daf8fd157c32e571c85cb12e66692f15516a83a03264b08d06c3 \ - --hash=sha256:ebf3e58c7ec8a8bed6d66a75d7fb37b55e5015b03ceae72a8e7c74495551e224 \ - --hash=sha256:ecaae4149d99b1c9e7b88bb03e3221956f68fd6d50be2ef061b2381b61d20838 \ - --hash=sha256:eecbc200c7fd5ddb9a7f16c7decb07b566c29fa2161a16cf67b8d068bd21690a \ - --hash=sha256:f155a433c2ec037d4e8df17d18922c3a0d9b3232a396690f17175d2946f0218d \ - --hash=sha256:f1e34719c6ed0b92f418c7c780480b26b5d9c50349e9a9af7d76bf757530350d \ - --hash=sha256:f34be2938726fc13801220747472850852fe6b1ea75869a048d6f896838c896f \ - --hash=sha256:f820802628d2694cb7e56db99213f930856014862f3fd943d290ea8438d07ca8 \ - --hash=sha256:f8bf04158c6b607d747e93949aa60618b61312fe647a6369f88ce2ff16043490 \ - --hash=sha256:f8e160feb2aed042cd657a72acc0b481212ed28b1b9a95c0cee1621b524e1966 \ - --hash=sha256:f9d332f8c2a2fcbffe1378594431458ddbef721c1769d78e2cbc06280d8155f9 \ - --hash=sha256:fa09f53c465e532f4d3db095e0c55b615f010ad81803d383195b6b5ca6cbf5f3 \ - --hash=sha256:faa3a41b2b66b6e50f84ae4a68c64fcd0c44355741c6374813a800cd6695db9e \ - --hash=sha256:fd44c878ea55ba351104cb93cc85e74916eb8fa440ca7903e57575e97394f608 - # via requests -cryptography==46.0.3 \ - --hash=sha256:00a5e7e87938e5ff9ff5447ab086a5706a957137e6e433841e9d24f38a065217 \ - --hash=sha256:01ca9ff2885f3acc98c29f1860552e37f6d7c7d013d7334ff2a9de43a449315d \ - --hash=sha256:09859af8466b69bc3c27bdf4f5d84a665e0f7ab5088412e9e2ec49758eca5cbc \ - --hash=sha256:0abf1ffd6e57c67e92af68330d05760b7b7efb243aab8377e583284dbab72c71 \ - --hash=sha256:1000713389b75c449a6e979ffc7dcc8ac90b437048766cef052d4d30b8220971 \ - --hash=sha256:109d4ddfadf17e8e7779c39f9b18111a09efb969a301a31e987416a0191ed93a \ - --hash=sha256:10b01676fc208c3e6feeb25a8b83d81767e8059e1fe86e1dc62d10a3018fa926 \ - --hash=sha256:10ca84c4668d066a9878890047f03546f3ae0a6b8b39b697457b7757aaf18dbc \ - --hash=sha256:15ab9b093e8f09daab0f2159bb7e47532596075139dd74365da52ecc9cb46c5d \ - --hash=sha256:191bb60a7be5e6f54e30ba16fdfae78ad3a342a0599eb4193ba88e3f3d6e185b \ - --hash=sha256:22d7e97932f511d6b0b04f2bfd818d73dcd5928db509460aaf48384778eb6d20 \ - --hash=sha256:23b1a8f26e43f47ceb6d6a43115f33a5a37d57df4ea0ca295b780ae8546e8044 \ - --hash=sha256:36e627112085bb3b81b19fed209c05ce2a52ee8b15d161b7c643a7d5a88491f3 \ - --hash=sha256:39b6755623145ad5eff1dab323f4eae2a32a77a7abef2c5089a04a3d04366715 \ - --hash=sha256:3b51b8ca4f1c6453d8829e1eb7299499ca7f313900dd4d89a24b8b87c0a780d4 \ - --hash=sha256:402b58fc32614f00980b66d6e56a5b4118e6cb362ae8f3fda141ba4689bd4506 \ - --hash=sha256:416260257577718c05135c55958b674000baef9a1c7d9e8f306ec60d71db850f \ - --hash=sha256:46acf53b40ea38f9c6c229599a4a13f0d46a6c3fa9ef19fc1a124d62e338dfa0 \ - --hash=sha256:4b7387121ac7d15e550f5cb4a43aef2559ed759c35df7336c402bb8275ac9683 \ - --hash=sha256:50fc3343ac490c6b08c0cf0d704e881d0d660be923fd3076db3e932007e726e3 \ - --hash=sha256:516ea134e703e9fe26bcd1277a4b59ad30586ea90c365a87781d7887a646fe21 \ - --hash=sha256:549e234ff32571b1f4076ac269fcce7a808d3bf98b76c8dd560e42dbc66d7d91 \ - --hash=sha256:5d7f93296ee28f68447397bf5198428c9aeeab45705a55d53a6343455dcb2c3c \ - --hash=sha256:5ecfccd2329e37e9b7112a888e76d9feca2347f12f37918facbb893d7bb88ee8 \ - --hash=sha256:6276eb85ef938dc035d59b87c8a7dc559a232f954962520137529d77b18ff1df \ - --hash=sha256:6b5063083824e5509fdba180721d55909ffacccc8adbec85268b48439423d78c \ - --hash=sha256:6eae65d4c3d33da080cff9c4ab1f711b15c1d9760809dad6ea763f3812d254cb \ - --hash=sha256:6f61efb26e76c45c4a227835ddeae96d83624fb0d29eb5df5b96e14ed1a0afb7 \ - --hash=sha256:71e842ec9bc7abf543b47cf86b9a743baa95f4677d22baa4c7d5c69e49e9bc04 \ - --hash=sha256:760f83faa07f8b64e9c33fc963d790a2edb24efb479e3520c14a45741cd9b2db \ - --hash=sha256:78a97cf6a8839a48c49271cdcbd5cf37ca2c1d6b7fdd86cc864f302b5e9bf459 \ - --hash=sha256:7ce938a99998ed3c8aa7e7272dca1a610401ede816d36d0693907d863b10d9ea \ - --hash=sha256:8a6e050cb6164d3f830453754094c086ff2d0b2f3a897a1d9820f6139a1f0914 \ - --hash=sha256:9394673a9f4de09e28b5356e7fff97d778f8abad85c9d5ac4a4b7e25a0de7717 \ - --hash=sha256:94cd0549accc38d1494e1f8de71eca837d0509d0d44bf11d158524b0e12cebf9 \ - --hash=sha256:a04bee9ab6a4da801eb9b51f1b708a1b5b5c9eb48c03f74198464c66f0d344ac \ - --hash=sha256:a23582810fedb8c0bc47524558fb6c56aac3fc252cb306072fd2815da2a47c32 \ - --hash=sha256:a2c0cd47381a3229c403062f764160d57d4d175e022c1df84e168c6251a22eec \ - --hash=sha256:a9a3008438615669153eb86b26b61e09993921ebdd75385ddd748702c5adfddb \ - --hash=sha256:b02cf04496f6576afffef5ddd04a0cb7d49cf6be16a9059d793a30b035f6b6ac \ - --hash=sha256:b419ae593c86b87014b9be7396b385491ad7f320bde96826d0dd174459e54665 \ - --hash=sha256:c0a7bb1a68a5d3471880e264621346c48665b3bf1c3759d682fc0864c540bd9e \ - --hash=sha256:c70cc23f12726be8f8bc72e41d5065d77e4515efae3690326764ea1b07845cfb \ - --hash=sha256:c8daeb2d2174beb4575b77482320303f3d39b8e81153da4f0fb08eb5fe86a6c5 \ - --hash=sha256:cb3d760a6117f621261d662bccc8ef5bc32ca673e037c83fbe565324f5c46936 \ - --hash=sha256:d55f3dffadd674514ad19451161118fd010988540cee43d8bc20675e775925de \ - --hash=sha256:d89c3468de4cdc4f08a57e214384d0471911a3830fcdaf7a8cc587e42a866372 \ - --hash=sha256:db391fa7c66df6762ee3f00c95a89e6d428f4d60e7abc8328f4fe155b5ac6e54 \ - --hash=sha256:dfb781ff7eaa91a6f7fd41776ec37c5853c795d3b358d4896fdbb5df168af422 \ - --hash=sha256:e5bf0ed4490068a2e72ac03d786693adeb909981cc596425d09032d372bcc849 \ - --hash=sha256:e7aec276d68421f9574040c26e2a7c3771060bc0cff408bae1dcb19d3ab1e63c \ - --hash=sha256:ef639cb3372f69ec44915fafcd6698b6cc78fbe0c2ea41be867f6ed612811963 \ - --hash=sha256:f260d0d41e9b4da1ed1e0f1ce571f97fe370b152ab18778e9e8f67d6af432018 - # via secretstorage -docutils==0.22.3 \ - --hash=sha256:bd772e4aca73aff037958d44f2be5229ded4c09927fcf8690c577b66234d6ceb - # via readme-renderer -google-auth==2.43.0 \ - --hash=sha256:af628ba6fa493f75c7e9dbe9373d148ca9f4399b5ea29976519e0a3848eddd16 - # via keyrings-google-artifactregistry-auth -id==1.5.0 \ - --hash=sha256:f1434e1cef91f2cbb8a4ec64663d5a23b9ed43ef44c4c957d02583d61714c658 - # via twine -idna==3.11 \ - --hash=sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea - # via requests -jaraco-classes==3.4.0 \ - --hash=sha256:f662826b6bed8cace05e7ff873ce0f9283b5c924470fe664fff1c2f00f581790 - # via keyring -jaraco-context==6.0.1 \ - --hash=sha256:f797fc481b490edb305122c9181830a3a5b76d84ef6d1aef2fb9b47ab956f9e4 - # via keyring -jaraco-functools==4.3.0 \ - --hash=sha256:227ff8ed6f7b8f62c56deff101545fa7543cf2c8e7b82a7c2116e672f29c26e8 - # via keyring -jeepney==0.9.0 \ - --hash=sha256:97e5714520c16fc0a45695e5365a2e11b81ea79bba796e26f9f1d178cb182683 - # via - # keyring - # secretstorage -keyring==25.7.0 \ - --hash=sha256:be4a0b195f149690c166e850609a477c532ddbfbaed96a404d4e43f8d5e2689f - # via - # -r .kokoro/requirements/build.in - # keyrings-google-artifactregistry-auth - # twine -keyrings-google-artifactregistry-auth==1.1.2 \ - --hash=sha256:e3f18b50fa945c786593014dc225810d191671d4f5f8e12d9259e39bad3605a3 - # via -r .kokoro/requirements/build.in -markdown-it-py==4.0.0 \ - --hash=sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147 - # via rich -mdurl==0.1.2 \ - --hash=sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8 - # via markdown-it-py -more-itertools==10.8.0 \ - --hash=sha256:52d4362373dcf7c52546bc4af9a86ee7c4579df9a8dc268be0a2f949d376cc9b - # via - # jaraco-classes - # jaraco-functools -nh3==0.3.2 \ - --hash=sha256:019ecbd007536b67fdf76fab411b648fb64e2257ca3262ec80c3425c24028c80 \ - --hash=sha256:03d617e5c8aa7331bd2659c654e021caf9bba704b109e7b2b28b039a00949fe5 \ - --hash=sha256:0dca4365db62b2d71ff1620ee4f800c4729849906c5dd504ee1a7b2389558e31 \ - --hash=sha256:0fe7ee035dd7b2290715baf29cb27167dddd2ff70ea7d052c958dbd80d323c99 \ - --hash=sha256:13398e676a14d6233f372c75f52d5ae74f98210172991f7a3142a736bd92b131 \ - --hash=sha256:169db03df90da63286e0560ea0efa9b6f3b59844a9735514a1d47e6bb2c8c61b \ - --hash=sha256:1710f3901cd6440ca92494ba2eb6dc260f829fa8d9196b659fa10de825610ce0 \ - --hash=sha256:1f9ba555a797dbdcd844b89523f29cdc90973d8bd2e836ea6b962cf567cadd93 \ - --hash=sha256:2ab70e8c6c7d2ce953d2a58102eefa90c2d0a5ed7aa40c7e29a487bc5e613131 \ - --hash=sha256:2c9850041b77a9147d6bbd6dbbf13eeec7009eb60b44e83f07fcb2910075bf9b \ - --hash=sha256:403c11563e50b915d0efdb622866d1d9e4506bce590ef7da57789bf71dd148b5 \ - --hash=sha256:45c953e57028c31d473d6b648552d9cab1efe20a42ad139d78e11d8f42a36130 \ - --hash=sha256:562da3dca7a17f9077593214a9781a94b8d76de4f158f8c895e62f09573945fe \ - --hash=sha256:6d66f41672eb4060cf87c037f760bdbc6847852ca9ef8e9c5a5da18f090abf87 \ - --hash=sha256:7064ccf5ace75825bd7bf57859daaaf16ed28660c1c6b306b649a9eda4b54b1e \ - --hash=sha256:72d67c25a84579f4a432c065e8b4274e53b7cf1df8f792cf846abfe2c3090866 \ - --hash=sha256:7bb18403f02b655a1bbe4e3a4696c2ae1d6ae8f5991f7cacb684b1ae27e6c9f7 \ - --hash=sha256:91e9b001101fb4500a2aafe3e7c92928d85242d38bf5ac0aba0b7480da0a4cd6 \ - --hash=sha256:a40202fd58e49129764f025bbaae77028e420f1d5b3c8e6f6fd3a6490d513868 \ - --hash=sha256:c8745454cdd28bbbc90861b80a0111a195b0e3961b9fa2e672be89eb199fa5d8 \ - --hash=sha256:cf5964d54edd405e68583114a7cba929468bcd7db5e676ae38ee954de1cfc104 \ - --hash=sha256:d18957a90806d943d141cc5e4a0fefa1d77cf0d7a156878bf9a66eed52c9cc7d \ - --hash=sha256:dce4248edc427c9b79261f3e6e2b3ecbdd9b88c267012168b4a7b3fc6fd41d13 \ - --hash=sha256:f2f55c4d2d5a207e74eefe4d828067bbb01300e06e2a7436142f915c5928de07 \ - --hash=sha256:f97f8b25cb2681d25e2338148159447e4d689aafdccfcf19e61ff7db3905768a - # via readme-renderer -packaging==25.0 \ - --hash=sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484 - # via - # build - # twine - # wheel -pluggy==1.6.0 \ - --hash=sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746 - # via keyrings-google-artifactregistry-auth -pyasn1==0.6.1 \ - --hash=sha256:0d632f46f2ba09143da3a8afe9e33fb6f92fa2320ab7e886e2d0f7672af84629 - # via - # pyasn1-modules - # rsa -pyasn1-modules==0.4.2 \ - --hash=sha256:29253a9207ce32b64c3ac6600edc75368f98473906e8fd1043bd6b5b1de2c14a - # via google-auth -pycparser==2.23 \ - --hash=sha256:e5c6e8d3fbad53479cab09ac03729e0a9faf2bee3db8208a550daf5af81a5934 - # via cffi -pygments==2.19.2 \ - --hash=sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b - # via - # readme-renderer - # rich -pyproject-hooks==1.2.0 \ - --hash=sha256:9e5c6bfa8dcc30091c74b0cf803c81fdd29d94f01992a7707bc97babb1141913 - # via build -readme-renderer==44.0 \ - --hash=sha256:2fbca89b81a08526aadf1357a8c2ae889ec05fb03f5da67f9769c9a592166151 - # via twine -requests==2.32.5 \ - --hash=sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6 - # via - # id - # keyrings-google-artifactregistry-auth - # requests-toolbelt - # twine -requests-toolbelt==1.0.0 \ - --hash=sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06 - # via twine -rfc3986==2.0.0 \ - --hash=sha256:50b1502b60e289cb37883f3dfd34532b8873c7de9f49bb546641ce9cbd256ebd - # via twine -rich==14.2.0 \ - --hash=sha256:76bc51fe2e57d2b1be1f96c524b890b816e334ab4c1e45888799bfaab0021edd - # via twine -rsa==4.9.1 \ - --hash=sha256:68635866661c6836b8d39430f97a996acbd61bfa49406748ea243539fe239762 - # via google-auth -secretstorage==3.5.0 \ - --hash=sha256:0ce65888c0725fcb2c5bc0fdb8e5438eece02c523557ea40ce0703c266248137 - # via keyring -twine==6.2.0 \ - --hash=sha256:418ebf08ccda9a8caaebe414433b0ba5e25eb5e4a927667122fbe8f829f985d8 - # via -r .kokoro/requirements/build.in -urllib3==2.6.3 \ - --hash=sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4 - # via - # requests - # twine -wheel==0.46.1 \ - --hash=sha256:f796f65d72750ccde090663e466d0ca37cd72b62870f7520b96d34cdc07d86d8 - # via -r .kokoro/requirements/build.in diff --git a/.kokoro/samples/lint/common.cfg b/.kokoro/samples/lint/common.cfg new file mode 100644 index 00000000000..b4d26c1f982 --- /dev/null +++ b/.kokoro/samples/lint/common.cfg @@ -0,0 +1,34 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +# Build logs will be here +action { + define_artifacts { + regex: "**/*sponge_log.xml" + } +} + +# Specify which tests to run +env_vars: { + key: "RUN_TESTS_SESSION" + value: "lint" +} + +env_vars: { + key: "TRAMPOLINE_BUILD_FILE" + value: "github/python-bigquery-dataframes/.kokoro/test-samples.sh" +} + +# Configure the docker image for kokoro-trampoline. +env_vars: { + key: "TRAMPOLINE_IMAGE" + value: "gcr.io/cloud-devrel-kokoro-resources/python-samples-testing-docker" +} + +# Download secrets for samples +gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/python-docs-samples" + +# Download trampoline resources. +gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/trampoline" + +# Use the trampoline script to run in docker. +build_file: "python-bigquery-dataframes/.kokoro/trampoline_v2.sh" \ No newline at end of file diff --git a/.kokoro/samples/lint/continuous.cfg b/.kokoro/samples/lint/continuous.cfg new file mode 100644 index 00000000000..a1c8d9759c8 --- /dev/null +++ b/.kokoro/samples/lint/continuous.cfg @@ -0,0 +1,6 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +env_vars: { + key: "INSTALL_LIBRARY_FROM_SOURCE" + value: "True" +} \ No newline at end of file diff --git a/.kokoro/samples/lint/periodic.cfg b/.kokoro/samples/lint/periodic.cfg new file mode 100644 index 00000000000..50fec964973 --- /dev/null +++ b/.kokoro/samples/lint/periodic.cfg @@ -0,0 +1,6 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +env_vars: { + key: "INSTALL_LIBRARY_FROM_SOURCE" + value: "False" +} \ No newline at end of file diff --git a/.kokoro/samples/lint/presubmit.cfg b/.kokoro/samples/lint/presubmit.cfg new file mode 100644 index 00000000000..a1c8d9759c8 --- /dev/null +++ b/.kokoro/samples/lint/presubmit.cfg @@ -0,0 +1,6 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +env_vars: { + key: "INSTALL_LIBRARY_FROM_SOURCE" + value: "True" +} \ No newline at end of file diff --git a/.kokoro/samples/python3.10/common.cfg b/.kokoro/samples/python3.10/common.cfg new file mode 100644 index 00000000000..8f9c66c571b --- /dev/null +++ b/.kokoro/samples/python3.10/common.cfg @@ -0,0 +1,40 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +# Build logs will be here +action { + define_artifacts { + regex: "**/*sponge_log.xml" + } +} + +# Specify which tests to run +env_vars: { + key: "RUN_TESTS_SESSION" + value: "py-3.10" +} + +# Declare build specific Cloud project. +env_vars: { + key: "BUILD_SPECIFIC_GCLOUD_PROJECT" + value: "python-docs-samples-tests-310" +} + +env_vars: { + key: "TRAMPOLINE_BUILD_FILE" + value: "github/python-bigquery-dataframes/.kokoro/test-samples.sh" +} + +# Configure the docker image for kokoro-trampoline. +env_vars: { + key: "TRAMPOLINE_IMAGE" + value: "gcr.io/cloud-devrel-kokoro-resources/python-samples-testing-docker" +} + +# Download secrets for samples +gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/python-docs-samples" + +# Download trampoline resources. +gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/trampoline" + +# Use the trampoline script to run in docker. +build_file: "python-bigquery-dataframes/.kokoro/trampoline_v2.sh" \ No newline at end of file diff --git a/.kokoro/samples/python3.10/continuous.cfg b/.kokoro/samples/python3.10/continuous.cfg new file mode 100644 index 00000000000..a1c8d9759c8 --- /dev/null +++ b/.kokoro/samples/python3.10/continuous.cfg @@ -0,0 +1,6 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +env_vars: { + key: "INSTALL_LIBRARY_FROM_SOURCE" + value: "True" +} \ No newline at end of file diff --git a/.kokoro/samples/python3.10/periodic-head.cfg b/.kokoro/samples/python3.10/periodic-head.cfg new file mode 100644 index 00000000000..123a35fbd3d --- /dev/null +++ b/.kokoro/samples/python3.10/periodic-head.cfg @@ -0,0 +1,11 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +env_vars: { + key: "INSTALL_LIBRARY_FROM_SOURCE" + value: "True" +} + +env_vars: { + key: "TRAMPOLINE_BUILD_FILE" + value: "github/python-bigquery-dataframes/.kokoro/test-samples-against-head.sh" +} diff --git a/.kokoro/samples/python3.10/periodic.cfg b/.kokoro/samples/python3.10/periodic.cfg new file mode 100644 index 00000000000..71cd1e597e3 --- /dev/null +++ b/.kokoro/samples/python3.10/periodic.cfg @@ -0,0 +1,6 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +env_vars: { + key: "INSTALL_LIBRARY_FROM_SOURCE" + value: "False" +} diff --git a/.kokoro/samples/python3.10/presubmit.cfg b/.kokoro/samples/python3.10/presubmit.cfg new file mode 100644 index 00000000000..a1c8d9759c8 --- /dev/null +++ b/.kokoro/samples/python3.10/presubmit.cfg @@ -0,0 +1,6 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +env_vars: { + key: "INSTALL_LIBRARY_FROM_SOURCE" + value: "True" +} \ No newline at end of file diff --git a/.kokoro/samples/python3.11/common.cfg b/.kokoro/samples/python3.11/common.cfg new file mode 100644 index 00000000000..1bba39114aa --- /dev/null +++ b/.kokoro/samples/python3.11/common.cfg @@ -0,0 +1,40 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +# Build logs will be here +action { + define_artifacts { + regex: "**/*sponge_log.xml" + } +} + +# Specify which tests to run +env_vars: { + key: "RUN_TESTS_SESSION" + value: "py-3.11" +} + +# Declare build specific Cloud project. +env_vars: { + key: "BUILD_SPECIFIC_GCLOUD_PROJECT" + value: "python-docs-samples-tests-311" +} + +env_vars: { + key: "TRAMPOLINE_BUILD_FILE" + value: "github/python-bigquery-dataframes/.kokoro/test-samples.sh" +} + +# Configure the docker image for kokoro-trampoline. +env_vars: { + key: "TRAMPOLINE_IMAGE" + value: "gcr.io/cloud-devrel-kokoro-resources/python-samples-testing-docker" +} + +# Download secrets for samples +gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/python-docs-samples" + +# Download trampoline resources. +gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/trampoline" + +# Use the trampoline script to run in docker. +build_file: "python-bigquery-dataframes/.kokoro/trampoline_v2.sh" \ No newline at end of file diff --git a/.kokoro/samples/python3.11/continuous.cfg b/.kokoro/samples/python3.11/continuous.cfg new file mode 100644 index 00000000000..a1c8d9759c8 --- /dev/null +++ b/.kokoro/samples/python3.11/continuous.cfg @@ -0,0 +1,6 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +env_vars: { + key: "INSTALL_LIBRARY_FROM_SOURCE" + value: "True" +} \ No newline at end of file diff --git a/.kokoro/samples/python3.11/periodic-head.cfg b/.kokoro/samples/python3.11/periodic-head.cfg new file mode 100644 index 00000000000..123a35fbd3d --- /dev/null +++ b/.kokoro/samples/python3.11/periodic-head.cfg @@ -0,0 +1,11 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +env_vars: { + key: "INSTALL_LIBRARY_FROM_SOURCE" + value: "True" +} + +env_vars: { + key: "TRAMPOLINE_BUILD_FILE" + value: "github/python-bigquery-dataframes/.kokoro/test-samples-against-head.sh" +} diff --git a/.kokoro/samples/python3.11/periodic.cfg b/.kokoro/samples/python3.11/periodic.cfg new file mode 100644 index 00000000000..71cd1e597e3 --- /dev/null +++ b/.kokoro/samples/python3.11/periodic.cfg @@ -0,0 +1,6 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +env_vars: { + key: "INSTALL_LIBRARY_FROM_SOURCE" + value: "False" +} diff --git a/.kokoro/samples/python3.11/presubmit.cfg b/.kokoro/samples/python3.11/presubmit.cfg new file mode 100644 index 00000000000..a1c8d9759c8 --- /dev/null +++ b/.kokoro/samples/python3.11/presubmit.cfg @@ -0,0 +1,6 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +env_vars: { + key: "INSTALL_LIBRARY_FROM_SOURCE" + value: "True" +} \ No newline at end of file diff --git a/.kokoro/samples/python3.7/common.cfg b/.kokoro/samples/python3.7/common.cfg new file mode 100644 index 00000000000..09d7af02ba9 --- /dev/null +++ b/.kokoro/samples/python3.7/common.cfg @@ -0,0 +1,40 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +# Build logs will be here +action { + define_artifacts { + regex: "**/*sponge_log.xml" + } +} + +# Specify which tests to run +env_vars: { + key: "RUN_TESTS_SESSION" + value: "py-3.7" +} + +# Declare build specific Cloud project. +env_vars: { + key: "BUILD_SPECIFIC_GCLOUD_PROJECT" + value: "python-docs-samples-tests-py37" +} + +env_vars: { + key: "TRAMPOLINE_BUILD_FILE" + value: "github/python-bigquery-dataframes/.kokoro/test-samples.sh" +} + +# Configure the docker image for kokoro-trampoline. +env_vars: { + key: "TRAMPOLINE_IMAGE" + value: "gcr.io/cloud-devrel-kokoro-resources/python-samples-testing-docker" +} + +# Download secrets for samples +gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/python-docs-samples" + +# Download trampoline resources. +gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/trampoline" + +# Use the trampoline script to run in docker. +build_file: "python-bigquery-dataframes/.kokoro/trampoline_v2.sh" \ No newline at end of file diff --git a/.kokoro/samples/python3.7/continuous.cfg b/.kokoro/samples/python3.7/continuous.cfg new file mode 100644 index 00000000000..a1c8d9759c8 --- /dev/null +++ b/.kokoro/samples/python3.7/continuous.cfg @@ -0,0 +1,6 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +env_vars: { + key: "INSTALL_LIBRARY_FROM_SOURCE" + value: "True" +} \ No newline at end of file diff --git a/.kokoro/samples/python3.7/periodic-head.cfg b/.kokoro/samples/python3.7/periodic-head.cfg new file mode 100644 index 00000000000..123a35fbd3d --- /dev/null +++ b/.kokoro/samples/python3.7/periodic-head.cfg @@ -0,0 +1,11 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +env_vars: { + key: "INSTALL_LIBRARY_FROM_SOURCE" + value: "True" +} + +env_vars: { + key: "TRAMPOLINE_BUILD_FILE" + value: "github/python-bigquery-dataframes/.kokoro/test-samples-against-head.sh" +} diff --git a/.kokoro/samples/python3.7/periodic.cfg b/.kokoro/samples/python3.7/periodic.cfg new file mode 100644 index 00000000000..71cd1e597e3 --- /dev/null +++ b/.kokoro/samples/python3.7/periodic.cfg @@ -0,0 +1,6 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +env_vars: { + key: "INSTALL_LIBRARY_FROM_SOURCE" + value: "False" +} diff --git a/.kokoro/samples/python3.7/presubmit.cfg b/.kokoro/samples/python3.7/presubmit.cfg new file mode 100644 index 00000000000..a1c8d9759c8 --- /dev/null +++ b/.kokoro/samples/python3.7/presubmit.cfg @@ -0,0 +1,6 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +env_vars: { + key: "INSTALL_LIBRARY_FROM_SOURCE" + value: "True" +} \ No newline at end of file diff --git a/.kokoro/samples/python3.8/common.cfg b/.kokoro/samples/python3.8/common.cfg new file mode 100644 index 00000000000..976d9ce8c5c --- /dev/null +++ b/.kokoro/samples/python3.8/common.cfg @@ -0,0 +1,40 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +# Build logs will be here +action { + define_artifacts { + regex: "**/*sponge_log.xml" + } +} + +# Specify which tests to run +env_vars: { + key: "RUN_TESTS_SESSION" + value: "py-3.8" +} + +# Declare build specific Cloud project. +env_vars: { + key: "BUILD_SPECIFIC_GCLOUD_PROJECT" + value: "python-docs-samples-tests-py38" +} + +env_vars: { + key: "TRAMPOLINE_BUILD_FILE" + value: "github/python-bigquery-dataframes/.kokoro/test-samples.sh" +} + +# Configure the docker image for kokoro-trampoline. +env_vars: { + key: "TRAMPOLINE_IMAGE" + value: "gcr.io/cloud-devrel-kokoro-resources/python-samples-testing-docker" +} + +# Download secrets for samples +gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/python-docs-samples" + +# Download trampoline resources. +gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/trampoline" + +# Use the trampoline script to run in docker. +build_file: "python-bigquery-dataframes/.kokoro/trampoline_v2.sh" \ No newline at end of file diff --git a/.kokoro/samples/python3.8/continuous.cfg b/.kokoro/samples/python3.8/continuous.cfg new file mode 100644 index 00000000000..a1c8d9759c8 --- /dev/null +++ b/.kokoro/samples/python3.8/continuous.cfg @@ -0,0 +1,6 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +env_vars: { + key: "INSTALL_LIBRARY_FROM_SOURCE" + value: "True" +} \ No newline at end of file diff --git a/.kokoro/samples/python3.8/periodic-head.cfg b/.kokoro/samples/python3.8/periodic-head.cfg new file mode 100644 index 00000000000..123a35fbd3d --- /dev/null +++ b/.kokoro/samples/python3.8/periodic-head.cfg @@ -0,0 +1,11 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +env_vars: { + key: "INSTALL_LIBRARY_FROM_SOURCE" + value: "True" +} + +env_vars: { + key: "TRAMPOLINE_BUILD_FILE" + value: "github/python-bigquery-dataframes/.kokoro/test-samples-against-head.sh" +} diff --git a/.kokoro/samples/python3.8/periodic.cfg b/.kokoro/samples/python3.8/periodic.cfg new file mode 100644 index 00000000000..71cd1e597e3 --- /dev/null +++ b/.kokoro/samples/python3.8/periodic.cfg @@ -0,0 +1,6 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +env_vars: { + key: "INSTALL_LIBRARY_FROM_SOURCE" + value: "False" +} diff --git a/.kokoro/samples/python3.8/presubmit.cfg b/.kokoro/samples/python3.8/presubmit.cfg new file mode 100644 index 00000000000..a1c8d9759c8 --- /dev/null +++ b/.kokoro/samples/python3.8/presubmit.cfg @@ -0,0 +1,6 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +env_vars: { + key: "INSTALL_LIBRARY_FROM_SOURCE" + value: "True" +} \ No newline at end of file diff --git a/.kokoro/samples/python3.9/common.cfg b/.kokoro/samples/python3.9/common.cfg new file mode 100644 index 00000000000..603cfffa280 --- /dev/null +++ b/.kokoro/samples/python3.9/common.cfg @@ -0,0 +1,40 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +# Build logs will be here +action { + define_artifacts { + regex: "**/*sponge_log.xml" + } +} + +# Specify which tests to run +env_vars: { + key: "RUN_TESTS_SESSION" + value: "py-3.9" +} + +# Declare build specific Cloud project. +env_vars: { + key: "BUILD_SPECIFIC_GCLOUD_PROJECT" + value: "python-docs-samples-tests-py39" +} + +env_vars: { + key: "TRAMPOLINE_BUILD_FILE" + value: "github/python-bigquery-dataframes/.kokoro/test-samples.sh" +} + +# Configure the docker image for kokoro-trampoline. +env_vars: { + key: "TRAMPOLINE_IMAGE" + value: "gcr.io/cloud-devrel-kokoro-resources/python-samples-testing-docker" +} + +# Download secrets for samples +gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/python-docs-samples" + +# Download trampoline resources. +gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/trampoline" + +# Use the trampoline script to run in docker. +build_file: "python-bigquery-dataframes/.kokoro/trampoline_v2.sh" \ No newline at end of file diff --git a/.kokoro/samples/python3.9/continuous.cfg b/.kokoro/samples/python3.9/continuous.cfg new file mode 100644 index 00000000000..a1c8d9759c8 --- /dev/null +++ b/.kokoro/samples/python3.9/continuous.cfg @@ -0,0 +1,6 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +env_vars: { + key: "INSTALL_LIBRARY_FROM_SOURCE" + value: "True" +} \ No newline at end of file diff --git a/.kokoro/samples/python3.9/periodic-head.cfg b/.kokoro/samples/python3.9/periodic-head.cfg new file mode 100644 index 00000000000..123a35fbd3d --- /dev/null +++ b/.kokoro/samples/python3.9/periodic-head.cfg @@ -0,0 +1,11 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +env_vars: { + key: "INSTALL_LIBRARY_FROM_SOURCE" + value: "True" +} + +env_vars: { + key: "TRAMPOLINE_BUILD_FILE" + value: "github/python-bigquery-dataframes/.kokoro/test-samples-against-head.sh" +} diff --git a/.kokoro/samples/python3.9/periodic.cfg b/.kokoro/samples/python3.9/periodic.cfg new file mode 100644 index 00000000000..71cd1e597e3 --- /dev/null +++ b/.kokoro/samples/python3.9/periodic.cfg @@ -0,0 +1,6 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +env_vars: { + key: "INSTALL_LIBRARY_FROM_SOURCE" + value: "False" +} diff --git a/.kokoro/samples/python3.9/presubmit.cfg b/.kokoro/samples/python3.9/presubmit.cfg new file mode 100644 index 00000000000..a1c8d9759c8 --- /dev/null +++ b/.kokoro/samples/python3.9/presubmit.cfg @@ -0,0 +1,6 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +env_vars: { + key: "INSTALL_LIBRARY_FROM_SOURCE" + value: "True" +} \ No newline at end of file diff --git a/.kokoro/test-samples-against-head.sh b/.kokoro/test-samples-against-head.sh new file mode 100755 index 00000000000..63ac41dfae1 --- /dev/null +++ b/.kokoro/test-samples-against-head.sh @@ -0,0 +1,26 @@ +#!/bin/bash +# Copyright 2023 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 +# +# https://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. + +# A customized test runner for samples. +# +# For periodic builds, you can specify this file for testing against head. + +# `-e` enables the script to automatically fail when a command fails +# `-o pipefail` sets the exit code to the rightmost comment to exit with a non-zero +set -eo pipefail +# Enables `**` to include files nested inside sub-folders +shopt -s globstar + +exec .kokoro/test-samples-impl.sh diff --git a/.kokoro/test-samples-impl.sh b/.kokoro/test-samples-impl.sh new file mode 100755 index 00000000000..5a0f5fab6a8 --- /dev/null +++ b/.kokoro/test-samples-impl.sh @@ -0,0 +1,102 @@ +#!/bin/bash +# Copyright 2023 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 +# +# https://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. + + +# `-e` enables the script to automatically fail when a command fails +# `-o pipefail` sets the exit code to the rightmost comment to exit with a non-zero +set -eo pipefail +# Enables `**` to include files nested inside sub-folders +shopt -s globstar + +# Exit early if samples don't exist +if ! find samples -name 'requirements.txt' | grep -q .; then + echo "No tests run. './samples/**/requirements.txt' not found" + exit 0 +fi + +# Disable buffering, so that the logs stream through. +export PYTHONUNBUFFERED=1 + +# Debug: show build environment +env | grep KOKORO + +# Install nox +python3.9 -m pip install --upgrade --quiet nox + +# Use secrets acessor service account to get secrets +if [[ -f "${KOKORO_GFILE_DIR}/secrets_viewer_service_account.json" ]]; then + gcloud auth activate-service-account \ + --key-file="${KOKORO_GFILE_DIR}/secrets_viewer_service_account.json" \ + --project="cloud-devrel-kokoro-resources" +fi + +# This script will create 3 files: +# - testing/test-env.sh +# - testing/service-account.json +# - testing/client-secrets.json +./scripts/decrypt-secrets.sh + +source ./testing/test-env.sh +export GOOGLE_APPLICATION_CREDENTIALS=$(pwd)/testing/service-account.json + +# For cloud-run session, we activate the service account for gcloud sdk. +gcloud auth activate-service-account \ + --key-file "${GOOGLE_APPLICATION_CREDENTIALS}" + +export GOOGLE_CLIENT_SECRETS=$(pwd)/testing/client-secrets.json + +echo -e "\n******************** TESTING PROJECTS ********************" + +# Switch to 'fail at end' to allow all tests to complete before exiting. +set +e +# Use RTN to return a non-zero value if the test fails. +RTN=0 +ROOT=$(pwd) +# Find all requirements.txt in the samples directory (may break on whitespace). +for file in samples/**/requirements.txt; do + cd "$ROOT" + # Navigate to the project folder. + file=$(dirname "$file") + cd "$file" + + echo "------------------------------------------------------------" + echo "- testing $file" + echo "------------------------------------------------------------" + + # Use nox to execute the tests for the project. + python3.9 -m nox -s "$RUN_TESTS_SESSION" + EXIT=$? + + # If this is a periodic build, send the test log to the FlakyBot. + # See https://github.com/googleapis/repo-automation-bots/tree/main/packages/flakybot. + if [[ $KOKORO_BUILD_ARTIFACTS_SUBDIR = *"periodic"* ]]; then + chmod +x $KOKORO_GFILE_DIR/linux_amd64/flakybot + $KOKORO_GFILE_DIR/linux_amd64/flakybot + fi + + if [[ $EXIT -ne 0 ]]; then + RTN=1 + echo -e "\n Testing failed: Nox returned a non-zero exit code. \n" + else + echo -e "\n Testing completed.\n" + fi + +done +cd "$ROOT" + +# Workaround for Kokoro permissions issue: delete secrets +rm testing/{test-env.sh,client-secrets.json,service-account.json} + +exit "$RTN" diff --git a/.kokoro/test-samples.sh b/.kokoro/test-samples.sh new file mode 100755 index 00000000000..50b35a48c19 --- /dev/null +++ b/.kokoro/test-samples.sh @@ -0,0 +1,44 @@ +#!/bin/bash +# Copyright 2023 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 +# +# https://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. + +# The default test runner for samples. +# +# For periodic builds, we rewinds the repo to the latest release, and +# run test-samples-impl.sh. + +# `-e` enables the script to automatically fail when a command fails +# `-o pipefail` sets the exit code to the rightmost comment to exit with a non-zero +set -eo pipefail +# Enables `**` to include files nested inside sub-folders +shopt -s globstar + +# Run periodic samples tests at latest release +if [[ $KOKORO_BUILD_ARTIFACTS_SUBDIR = *"periodic"* ]]; then + # preserving the test runner implementation. + cp .kokoro/test-samples-impl.sh "${TMPDIR}/test-samples-impl.sh" + echo "--- IMPORTANT IMPORTANT IMPORTANT ---" + echo "Now we rewind the repo back to the latest release..." + LATEST_RELEASE=$(git describe --abbrev=0 --tags) + git checkout $LATEST_RELEASE + echo "The current head is: " + echo $(git rev-parse --verify HEAD) + echo "--- IMPORTANT IMPORTANT IMPORTANT ---" + # move back the test runner implementation if there's no file. + if [ ! -f .kokoro/test-samples-impl.sh ]; then + cp "${TMPDIR}/test-samples-impl.sh" .kokoro/test-samples-impl.sh + fi +fi + +exec .kokoro/test-samples-impl.sh diff --git a/.kokoro/trampoline.sh b/.kokoro/trampoline.sh new file mode 100755 index 00000000000..d85b1f26769 --- /dev/null +++ b/.kokoro/trampoline.sh @@ -0,0 +1,28 @@ +#!/bin/bash +# Copyright 2023 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. + +set -eo pipefail + +# Always run the cleanup script, regardless of the success of bouncing into +# the container. +function cleanup() { + chmod +x ${KOKORO_GFILE_DIR}/trampoline_cleanup.sh + ${KOKORO_GFILE_DIR}/trampoline_cleanup.sh + echo "cleanup"; +} +trap cleanup EXIT + +$(dirname $0)/populate-secrets.sh # Secret Manager secrets. +python3 "${KOKORO_GFILE_DIR}/trampoline_v1.py" \ No newline at end of file diff --git a/.kokoro/trampoline_v2.sh b/.kokoro/trampoline_v2.sh new file mode 100755 index 00000000000..59a7cf3a937 --- /dev/null +++ b/.kokoro/trampoline_v2.sh @@ -0,0 +1,487 @@ +#!/usr/bin/env bash +# Copyright 2023 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. + +# trampoline_v2.sh +# +# This script does 3 things. +# +# 1. Prepare the Docker image for the test +# 2. Run the Docker with appropriate flags to run the test +# 3. Upload the newly built Docker image +# +# in a way that is somewhat compatible with trampoline_v1. +# +# To run this script, first download few files from gcs to /dev/shm. +# (/dev/shm is passed into the container as KOKORO_GFILE_DIR). +# +# gsutil cp gs://cloud-devrel-kokoro-resources/python-docs-samples/secrets_viewer_service_account.json /dev/shm +# gsutil cp gs://cloud-devrel-kokoro-resources/python-docs-samples/automl_secrets.txt /dev/shm +# +# Then run the script. +# .kokoro/trampoline_v2.sh +# +# These environment variables are required: +# TRAMPOLINE_IMAGE: The docker image to use. +# TRAMPOLINE_DOCKERFILE: The location of the Dockerfile. +# +# You can optionally change these environment variables: +# TRAMPOLINE_IMAGE_UPLOAD: +# (true|false): Whether to upload the Docker image after the +# successful builds. +# TRAMPOLINE_BUILD_FILE: The script to run in the docker container. +# TRAMPOLINE_WORKSPACE: The workspace path in the docker container. +# Defaults to /workspace. +# Potentially there are some repo specific envvars in .trampolinerc in +# the project root. + + +set -euo pipefail + +TRAMPOLINE_VERSION="2.0.5" + +if command -v tput >/dev/null && [[ -n "${TERM:-}" ]]; then + readonly IO_COLOR_RED="$(tput setaf 1)" + readonly IO_COLOR_GREEN="$(tput setaf 2)" + readonly IO_COLOR_YELLOW="$(tput setaf 3)" + readonly IO_COLOR_RESET="$(tput sgr0)" +else + readonly IO_COLOR_RED="" + readonly IO_COLOR_GREEN="" + readonly IO_COLOR_YELLOW="" + readonly IO_COLOR_RESET="" +fi + +function function_exists { + [ $(LC_ALL=C type -t $1)"" == "function" ] +} + +# Logs a message using the given color. The first argument must be one +# of the IO_COLOR_* variables defined above, such as +# "${IO_COLOR_YELLOW}". The remaining arguments will be logged in the +# given color. The log message will also have an RFC-3339 timestamp +# prepended (in UTC). You can disable the color output by setting +# TERM=vt100. +function log_impl() { + local color="$1" + shift + local timestamp="$(date -u "+%Y-%m-%dT%H:%M:%SZ")" + echo "================================================================" + echo "${color}${timestamp}:" "$@" "${IO_COLOR_RESET}" + echo "================================================================" +} + +# Logs the given message with normal coloring and a timestamp. +function log() { + log_impl "${IO_COLOR_RESET}" "$@" +} + +# Logs the given message in green with a timestamp. +function log_green() { + log_impl "${IO_COLOR_GREEN}" "$@" +} + +# Logs the given message in yellow with a timestamp. +function log_yellow() { + log_impl "${IO_COLOR_YELLOW}" "$@" +} + +# Logs the given message in red with a timestamp. +function log_red() { + log_impl "${IO_COLOR_RED}" "$@" +} + +readonly tmpdir=$(mktemp -d -t ci-XXXXXXXX) +readonly tmphome="${tmpdir}/h" +mkdir -p "${tmphome}" + +function cleanup() { + rm -rf "${tmpdir}" +} +trap cleanup EXIT + +RUNNING_IN_CI="${RUNNING_IN_CI:-false}" + +# The workspace in the container, defaults to /workspace. +TRAMPOLINE_WORKSPACE="${TRAMPOLINE_WORKSPACE:-/workspace}" + +pass_down_envvars=( + # TRAMPOLINE_V2 variables. + # Tells scripts whether they are running as part of CI or not. + "RUNNING_IN_CI" + # Indicates which CI system we're in. + "TRAMPOLINE_CI" + # Indicates the version of the script. + "TRAMPOLINE_VERSION" +) + +log_yellow "Building with Trampoline ${TRAMPOLINE_VERSION}" + +# Detect which CI systems we're in. If we're in any of the CI systems +# we support, `RUNNING_IN_CI` will be true and `TRAMPOLINE_CI` will be +# the name of the CI system. Both envvars will be passing down to the +# container for telling which CI system we're in. +if [[ -n "${KOKORO_BUILD_ID:-}" ]]; then + # descriptive env var for indicating it's on CI. + RUNNING_IN_CI="true" + TRAMPOLINE_CI="kokoro" + if [[ "${TRAMPOLINE_USE_LEGACY_SERVICE_ACCOUNT:-}" == "true" ]]; then + if [[ ! -f "${KOKORO_GFILE_DIR}/kokoro-trampoline.service-account.json" ]]; then + log_red "${KOKORO_GFILE_DIR}/kokoro-trampoline.service-account.json does not exist. Did you forget to mount cloud-devrel-kokoro-resources/trampoline? Aborting." + exit 1 + fi + # This service account will be activated later. + TRAMPOLINE_SERVICE_ACCOUNT="${KOKORO_GFILE_DIR}/kokoro-trampoline.service-account.json" + else + if [[ "${TRAMPOLINE_VERBOSE:-}" == "true" ]]; then + gcloud auth list + fi + log_yellow "Configuring Container Registry access" + gcloud auth configure-docker --quiet + fi + pass_down_envvars+=( + # KOKORO dynamic variables. + "KOKORO_BUILD_NUMBER" + "KOKORO_BUILD_ID" + "KOKORO_JOB_NAME" + "KOKORO_GIT_COMMIT" + "KOKORO_GITHUB_COMMIT" + "KOKORO_GITHUB_PULL_REQUEST_NUMBER" + "KOKORO_GITHUB_PULL_REQUEST_COMMIT" + # For FlakyBot + "KOKORO_GITHUB_COMMIT_URL" + "KOKORO_GITHUB_PULL_REQUEST_URL" + ) +elif [[ "${TRAVIS:-}" == "true" ]]; then + RUNNING_IN_CI="true" + TRAMPOLINE_CI="travis" + pass_down_envvars+=( + "TRAVIS_BRANCH" + "TRAVIS_BUILD_ID" + "TRAVIS_BUILD_NUMBER" + "TRAVIS_BUILD_WEB_URL" + "TRAVIS_COMMIT" + "TRAVIS_COMMIT_MESSAGE" + "TRAVIS_COMMIT_RANGE" + "TRAVIS_JOB_NAME" + "TRAVIS_JOB_NUMBER" + "TRAVIS_JOB_WEB_URL" + "TRAVIS_PULL_REQUEST" + "TRAVIS_PULL_REQUEST_BRANCH" + "TRAVIS_PULL_REQUEST_SHA" + "TRAVIS_PULL_REQUEST_SLUG" + "TRAVIS_REPO_SLUG" + "TRAVIS_SECURE_ENV_VARS" + "TRAVIS_TAG" + ) +elif [[ -n "${GITHUB_RUN_ID:-}" ]]; then + RUNNING_IN_CI="true" + TRAMPOLINE_CI="github-workflow" + pass_down_envvars+=( + "GITHUB_WORKFLOW" + "GITHUB_RUN_ID" + "GITHUB_RUN_NUMBER" + "GITHUB_ACTION" + "GITHUB_ACTIONS" + "GITHUB_ACTOR" + "GITHUB_REPOSITORY" + "GITHUB_EVENT_NAME" + "GITHUB_EVENT_PATH" + "GITHUB_SHA" + "GITHUB_REF" + "GITHUB_HEAD_REF" + "GITHUB_BASE_REF" + ) +elif [[ "${CIRCLECI:-}" == "true" ]]; then + RUNNING_IN_CI="true" + TRAMPOLINE_CI="circleci" + pass_down_envvars+=( + "CIRCLE_BRANCH" + "CIRCLE_BUILD_NUM" + "CIRCLE_BUILD_URL" + "CIRCLE_COMPARE_URL" + "CIRCLE_JOB" + "CIRCLE_NODE_INDEX" + "CIRCLE_NODE_TOTAL" + "CIRCLE_PREVIOUS_BUILD_NUM" + "CIRCLE_PROJECT_REPONAME" + "CIRCLE_PROJECT_USERNAME" + "CIRCLE_REPOSITORY_URL" + "CIRCLE_SHA1" + "CIRCLE_STAGE" + "CIRCLE_USERNAME" + "CIRCLE_WORKFLOW_ID" + "CIRCLE_WORKFLOW_JOB_ID" + "CIRCLE_WORKFLOW_UPSTREAM_JOB_IDS" + "CIRCLE_WORKFLOW_WORKSPACE_ID" + ) +fi + +# Configure the service account for pulling the docker image. +function repo_root() { + local dir="$1" + while [[ ! -d "${dir}/.git" ]]; do + dir="$(dirname "$dir")" + done + echo "${dir}" +} + +# Detect the project root. In CI builds, we assume the script is in +# the git tree and traverse from there, otherwise, traverse from `pwd` +# to find `.git` directory. +if [[ "${RUNNING_IN_CI:-}" == "true" ]]; then + PROGRAM_PATH="$(realpath "$0")" + PROGRAM_DIR="$(dirname "${PROGRAM_PATH}")" + PROJECT_ROOT="$(repo_root "${PROGRAM_DIR}")" +else + PROJECT_ROOT="$(repo_root $(pwd))" +fi + +log_yellow "Changing to the project root: ${PROJECT_ROOT}." +cd "${PROJECT_ROOT}" + +# To support relative path for `TRAMPOLINE_SERVICE_ACCOUNT`, we need +# to use this environment variable in `PROJECT_ROOT`. +if [[ -n "${TRAMPOLINE_SERVICE_ACCOUNT:-}" ]]; then + + mkdir -p "${tmpdir}/gcloud" + gcloud_config_dir="${tmpdir}/gcloud" + + log_yellow "Using isolated gcloud config: ${gcloud_config_dir}." + export CLOUDSDK_CONFIG="${gcloud_config_dir}" + + log_yellow "Using ${TRAMPOLINE_SERVICE_ACCOUNT} for authentication." + gcloud auth activate-service-account \ + --key-file "${TRAMPOLINE_SERVICE_ACCOUNT}" + log_yellow "Configuring Container Registry access" + gcloud auth configure-docker --quiet +fi + +required_envvars=( + # The basic trampoline configurations. + "TRAMPOLINE_IMAGE" + "TRAMPOLINE_BUILD_FILE" +) + +if [[ -f "${PROJECT_ROOT}/.trampolinerc" ]]; then + source "${PROJECT_ROOT}/.trampolinerc" +fi + +log_yellow "Checking environment variables." +for e in "${required_envvars[@]}" +do + if [[ -z "${!e:-}" ]]; then + log "Missing ${e} env var. Aborting." + exit 1 + fi +done + +# We want to support legacy style TRAMPOLINE_BUILD_FILE used with V1 +# script: e.g. "github/repo-name/.kokoro/run_tests.sh" +TRAMPOLINE_BUILD_FILE="${TRAMPOLINE_BUILD_FILE#github/*/}" +log_yellow "Using TRAMPOLINE_BUILD_FILE: ${TRAMPOLINE_BUILD_FILE}" + +# ignore error on docker operations and test execution +set +e + +log_yellow "Preparing Docker image." +# We only download the docker image in CI builds. +if [[ "${RUNNING_IN_CI:-}" == "true" ]]; then + # Download the docker image specified by `TRAMPOLINE_IMAGE` + + # We may want to add --max-concurrent-downloads flag. + + log_yellow "Start pulling the Docker image: ${TRAMPOLINE_IMAGE}." + if docker pull "${TRAMPOLINE_IMAGE}"; then + log_green "Finished pulling the Docker image: ${TRAMPOLINE_IMAGE}." + has_image="true" + else + log_red "Failed pulling the Docker image: ${TRAMPOLINE_IMAGE}." + has_image="false" + fi +else + # For local run, check if we have the image. + if docker images "${TRAMPOLINE_IMAGE}:latest" | grep "${TRAMPOLINE_IMAGE}"; then + has_image="true" + else + has_image="false" + fi +fi + + +# The default user for a Docker container has uid 0 (root). To avoid +# creating root-owned files in the build directory we tell docker to +# use the current user ID. +user_uid="$(id -u)" +user_gid="$(id -g)" +user_name="$(id -un)" + +# To allow docker in docker, we add the user to the docker group in +# the host os. +docker_gid=$(cut -d: -f3 < <(getent group docker)) + +update_cache="false" +if [[ "${TRAMPOLINE_DOCKERFILE:-none}" != "none" ]]; then + # Build the Docker image from the source. + context_dir=$(dirname "${TRAMPOLINE_DOCKERFILE}") + docker_build_flags=( + "-f" "${TRAMPOLINE_DOCKERFILE}" + "-t" "${TRAMPOLINE_IMAGE}" + "--build-arg" "UID=${user_uid}" + "--build-arg" "USERNAME=${user_name}" + ) + if [[ "${has_image}" == "true" ]]; then + docker_build_flags+=("--cache-from" "${TRAMPOLINE_IMAGE}") + fi + + log_yellow "Start building the docker image." + if [[ "${TRAMPOLINE_VERBOSE:-false}" == "true" ]]; then + echo "docker build" "${docker_build_flags[@]}" "${context_dir}" + fi + + # ON CI systems, we want to suppress docker build logs, only + # output the logs when it fails. + if [[ "${RUNNING_IN_CI:-}" == "true" ]]; then + if docker build "${docker_build_flags[@]}" "${context_dir}" \ + > "${tmpdir}/docker_build.log" 2>&1; then + if [[ "${TRAMPOLINE_VERBOSE:-}" == "true" ]]; then + cat "${tmpdir}/docker_build.log" + fi + + log_green "Finished building the docker image." + update_cache="true" + else + log_red "Failed to build the Docker image, aborting." + log_yellow "Dumping the build logs:" + cat "${tmpdir}/docker_build.log" + exit 1 + fi + else + if docker build "${docker_build_flags[@]}" "${context_dir}"; then + log_green "Finished building the docker image." + update_cache="true" + else + log_red "Failed to build the Docker image, aborting." + exit 1 + fi + fi +else + if [[ "${has_image}" != "true" ]]; then + log_red "We do not have ${TRAMPOLINE_IMAGE} locally, aborting." + exit 1 + fi +fi + +# We use an array for the flags so they are easier to document. +docker_flags=( + # Remove the container after it exists. + "--rm" + + # Use the host network. + "--network=host" + + # Run in priviledged mode. We are not using docker for sandboxing or + # isolation, just for packaging our dev tools. + "--privileged" + + # Run the docker script with the user id. Because the docker image gets to + # write in ${PWD} you typically want this to be your user id. + # To allow docker in docker, we need to use docker gid on the host. + "--user" "${user_uid}:${docker_gid}" + + # Pass down the USER. + "--env" "USER=${user_name}" + + # Mount the project directory inside the Docker container. + "--volume" "${PROJECT_ROOT}:${TRAMPOLINE_WORKSPACE}" + "--workdir" "${TRAMPOLINE_WORKSPACE}" + "--env" "PROJECT_ROOT=${TRAMPOLINE_WORKSPACE}" + + # Mount the temporary home directory. + "--volume" "${tmphome}:/h" + "--env" "HOME=/h" + + # Allow docker in docker. + "--volume" "/var/run/docker.sock:/var/run/docker.sock" + + # Mount the /tmp so that docker in docker can mount the files + # there correctly. + "--volume" "/tmp:/tmp" + # Pass down the KOKORO_GFILE_DIR and KOKORO_KEYSTORE_DIR + # TODO(tmatsuo): This part is not portable. + "--env" "TRAMPOLINE_SECRET_DIR=/secrets" + "--volume" "${KOKORO_GFILE_DIR:-/dev/shm}:/secrets/gfile" + "--env" "KOKORO_GFILE_DIR=/secrets/gfile" + "--volume" "${KOKORO_KEYSTORE_DIR:-/dev/shm}:/secrets/keystore" + "--env" "KOKORO_KEYSTORE_DIR=/secrets/keystore" +) + +# Add an option for nicer output if the build gets a tty. +if [[ -t 0 ]]; then + docker_flags+=("-it") +fi + +# Passing down env vars +for e in "${pass_down_envvars[@]}" +do + if [[ -n "${!e:-}" ]]; then + docker_flags+=("--env" "${e}=${!e}") + fi +done + +# If arguments are given, all arguments will become the commands run +# in the container, otherwise run TRAMPOLINE_BUILD_FILE. +if [[ $# -ge 1 ]]; then + log_yellow "Running the given commands '" "${@:1}" "' in the container." + readonly commands=("${@:1}") + if [[ "${TRAMPOLINE_VERBOSE:-}" == "true" ]]; then + echo docker run "${docker_flags[@]}" "${TRAMPOLINE_IMAGE}" "${commands[@]}" + fi + docker run "${docker_flags[@]}" "${TRAMPOLINE_IMAGE}" "${commands[@]}" +else + log_yellow "Running the tests in a Docker container." + docker_flags+=("--entrypoint=${TRAMPOLINE_BUILD_FILE}") + if [[ "${TRAMPOLINE_VERBOSE:-}" == "true" ]]; then + echo docker run "${docker_flags[@]}" "${TRAMPOLINE_IMAGE}" + fi + docker run "${docker_flags[@]}" "${TRAMPOLINE_IMAGE}" +fi + + +test_retval=$? + +if [[ ${test_retval} -eq 0 ]]; then + log_green "Build finished with ${test_retval}" +else + log_red "Build finished with ${test_retval}" +fi + +# Only upload it when the test passes. +if [[ "${update_cache}" == "true" ]] && \ + [[ $test_retval == 0 ]] && \ + [[ "${TRAMPOLINE_IMAGE_UPLOAD:-false}" == "true" ]]; then + log_yellow "Uploading the Docker image." + if docker push "${TRAMPOLINE_IMAGE}"; then + log_green "Finished uploading the Docker image." + else + log_red "Failed uploading the Docker image." + fi + # Call trampoline_after_upload_hook if it's defined. + if function_exists trampoline_after_upload_hook; then + trampoline_after_upload_hook + fi + +fi + +exit "${test_retval}" diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 00000000000..6e0fd8b98fb --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,41 @@ +# Copyright 2023 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. +# +# See https://pre-commit.com for more information +# See https://pre-commit.com/hooks.html for more hooks +repos: +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.0.1 + hooks: + - id: trailing-whitespace + - id: end-of-file-fixer + - id: check-yaml +- repo: https://github.com/pycqa/isort + rev: 5.12.0 + hooks: + - id: isort + name: isort (python) +- repo: https://github.com/psf/black + rev: 22.3.0 + hooks: + - id: black +- repo: https://github.com/pycqa/flake8 + rev: 6.1.0 + hooks: + - id: flake8 +- repo: https://github.com/pre-commit/mirrors-mypy + rev: v1.1.1 + hooks: + - id: mypy + additional_dependencies: [types-requests] diff --git a/.repo-metadata.json b/.repo-metadata.json index b988476c181..0efaa967d2c 100644 --- a/.repo-metadata.json +++ b/.repo-metadata.json @@ -1,9 +1,16 @@ { - "client_documentation": "https://googleapis.dev/python/bigframes/latest", - "distribution_name": "bigframes", + "name": "bigframes", + "name_pretty": "A unified Python API in BigQuery", + "product_documentation": "https://cloud.google.com/bigquery", + "client_documentation": "https://cloud.google.com/python/docs/reference/bigframes/latest", + "issue_tracker": "https://github.com/googleapis/python-bigquery-dataframes/issues", + "release_level": "preview", "language": "python", "library_type": "INTEGRATION", - "name": "bigframes", - "release_level": "stable", - "repo": "googleapis/google-cloud-python" -} \ No newline at end of file + "repo": "googleapis/python-bigquery-dataframes", + "distribution_name": "bigframes", + "api_id": "bigquery.googleapis.com", + "default_version": "", + "codeowner_team": "@googleapis/api-bigquery-dataframe", + "api_shortname": "bigquery" +} diff --git a/.trampolinerc b/.trampolinerc new file mode 100644 index 00000000000..a7dfeb42c6d --- /dev/null +++ b/.trampolinerc @@ -0,0 +1,61 @@ +# Copyright 2023 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. + +# Add required env vars here. +required_envvars+=( +) + +# Add env vars which are passed down into the container here. +pass_down_envvars+=( + "NOX_SESSION" + ############### + # Docs builds + ############### + "STAGING_BUCKET" + "V2_STAGING_BUCKET" + ################## + # Samples builds + ################## + "INSTALL_LIBRARY_FROM_SOURCE" + "RUN_TESTS_SESSION" + "BUILD_SPECIFIC_GCLOUD_PROJECT" + # Target directories. + "RUN_TESTS_DIRS" + # The nox session to run. + "RUN_TESTS_SESSION" +) + +# Prevent unintentional override on the default image. +if [[ "${TRAMPOLINE_IMAGE_UPLOAD:-false}" == "true" ]] && \ + [[ -z "${TRAMPOLINE_IMAGE:-}" ]]; then + echo "Please set TRAMPOLINE_IMAGE if you want to upload the Docker image." + exit 1 +fi + +# Define the default value if it makes sense. +if [[ -z "${TRAMPOLINE_IMAGE_UPLOAD:-}" ]]; then + TRAMPOLINE_IMAGE_UPLOAD="" +fi + +if [[ -z "${TRAMPOLINE_IMAGE:-}" ]]; then + TRAMPOLINE_IMAGE="" +fi + +if [[ -z "${TRAMPOLINE_DOCKERFILE:-}" ]]; then + TRAMPOLINE_DOCKERFILE="" +fi + +if [[ -z "${TRAMPOLINE_BUILD_FILE:-}" ]]; then + TRAMPOLINE_BUILD_FILE="" +fi diff --git a/CHANGELOG.md b/CHANGELOG.md index 86f91791fb3..fc327b2e966 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,2903 +4,6 @@ [1]: https://pypi.org/project/bigframes/#history -## [2.47.0](https://github.com/googleapis/google-cloud-python/compare/bigframes-v2.46.0...bigframes-v2.47.0) (2026-08-03) - - -### Features - -* **bigframes:** add ai.embed and ai.similarity to bigquery accessor ([#17927](https://github.com/googleapis/google-cloud-python/issues/17927)) ([2d1372b](https://github.com/googleapis/google-cloud-python/commit/2d1372b25b871aaebba0296f516dbbdd9ef43a37)) - - -### Bug Fixes - -* **bigframes:** fix mypy errors in _magics.py related to get_ipython ([#17979](https://github.com/googleapis/google-cloud-python/issues/17979)) ([24d955e](https://github.com/googleapis/google-cloud-python/commit/24d955e7573acf5e2941f9d10b342b171d9def24)) -* bump brace-expansion from 5.0.6 to 5.0.7 in /packages/bigframes/bigframes/display/table_widget_angular ([#17794](https://github.com/googleapis/google-cloud-python/issues/17794)) ([2df1bb5](https://github.com/googleapis/google-cloud-python/commit/2df1bb5cb53906bfc41de92c10fdb5a8920ce36f)) -* bump fast-uri from 3.1.1 to 3.1.4 in /packages/bigframes/bigframes/display/table_widget_angular ([#17828](https://github.com/googleapis/google-cloud-python/issues/17828)) ([8ce495a](https://github.com/googleapis/google-cloud-python/commit/8ce495ab51d35cc56332f27589ce33b035215abf)) -* bump hono from 4.12.16 to 4.12.31 in /packages/bigframes/bigframes/display/table_widget_angular ([#17829](https://github.com/googleapis/google-cloud-python/issues/17829)) ([6d6fa39](https://github.com/googleapis/google-cloud-python/commit/6d6fa399b1578dd6c0ed7b2284b35f6bb0922da8)) -* bump immutable from 5.1.7 to 5.1.9 in /packages/bigframes/bigframes/display/table_widget_angular ([#17830](https://github.com/googleapis/google-cloud-python/issues/17830)) ([6abb1da](https://github.com/googleapis/google-cloud-python/commit/6abb1da519e7d4acdbdd531982530047fdafe854)) -* bump postcss from 8.5.14 to 8.5.23 in /packages/bigframes/bigframes/display/table_widget_angular ([#17908](https://github.com/googleapis/google-cloud-python/issues/17908)) ([e68eb8f](https://github.com/googleapis/google-cloud-python/commit/e68eb8ff03948b4a694aca465b69543ae0be6c27)) -* bump tar from 7.5.16 to 7.5.20 in /packages/bigframes/bigframes/display/table_widget_angular ([#17793](https://github.com/googleapis/google-cloud-python/issues/17793)) ([3502d41](https://github.com/googleapis/google-cloud-python/commit/3502d4183d9864e4310b3daf063ec90be9969e25)) -* require Protobuf 6.33.5+ ([#17743](https://github.com/googleapis/google-cloud-python/issues/17743)) ([d267342](https://github.com/googleapis/google-cloud-python/commit/d26734293c23f06ccce048f7d9b0fa365e813410)) - -## [2.46.0](https://github.com/googleapis/google-cloud-python/compare/bigframes-v2.45.0...bigframes-v2.46.0) (2026-07-16) - - -### Features - -* **bigframes:** Support groupby.agg/transform with udf transpiler ([#17613](https://github.com/googleapis/google-cloud-python/issues/17613)) ([cae94f9](https://github.com/googleapis/google-cloud-python/commit/cae94f99121d7708671a35acf82616cfe378cccb)) -* **bigframes:** support offset-based column access via iloc ([#17367](https://github.com/googleapis/google-cloud-python/issues/17367)) ([4253fab](https://github.com/googleapis/google-cloud-python/commit/4253fab07ccdb2b94e247f8dade793828754b88b)) - - -### Bug Fixes - -* **bigframes:** Fix sqlglot backend regressions ([#17655](https://github.com/googleapis/google-cloud-python/issues/17655)) ([91f93bc](https://github.com/googleapis/google-cloud-python/commit/91f93bcd7b71b6cea62f506ed684500cec1eb6bb)) -* bump gradio from 6.15.0 to 6.15.1 in /packages/bigframes ([#17712](https://github.com/googleapis/google-cloud-python/issues/17712)) ([a85d59f](https://github.com/googleapis/google-cloud-python/commit/a85d59f39998d94cbac8d5e98547f18b3cc5e5be)) -* bump mistune from 3.2.1 to 3.3.0 in /packages/bigframes ([#17694](https://github.com/googleapis/google-cloud-python/issues/17694)) ([e5f7fef](https://github.com/googleapis/google-cloud-python/commit/e5f7fef31c2bbe5f559f4c79fdaf4ebcf6e1bd3f)) -* bump soupsieve from 2.7 to 2.8.4 in /packages/bigframes ([#17695](https://github.com/googleapis/google-cloud-python/issues/17695)) ([635da34](https://github.com/googleapis/google-cloud-python/commit/635da3453b2ba78b8abea43c554a055257f33aa1)) -* bump transformers from 5.3.0 to 5.5.0 in /packages/bigframes ([#17700](https://github.com/googleapis/google-cloud-python/issues/17700)) ([4b049c4](https://github.com/googleapis/google-cloud-python/commit/4b049c4eb8dc1ec91320b55fe515c339cd448af3)) -* emit bracketed inline array syntax for scalar subquery expressions ([#17716](https://github.com/googleapis/google-cloud-python/issues/17716)) ([ce5fd50](https://github.com/googleapis/google-cloud-python/commit/ce5fd500b68c16f56ea8066d8a6fa4b0b8d92081)) - - -### Documentation - -* make landing page quickstart runnable ([fc423c8](https://github.com/googleapis/google-cloud-python/commit/fc423c809cc80168f45fee795d5db5dc7a571fb1)) -* make landing page quickstart runnable ([#17687](https://github.com/googleapis/google-cloud-python/issues/17687)) ([fc423c8](https://github.com/googleapis/google-cloud-python/commit/fc423c809cc80168f45fee795d5db5dc7a571fb1)) - -## [2.45.0](https://github.com/googleapis/google-cloud-python/compare/bigframes-v2.44.0...bigframes-v2.45.0) (2026-07-08) - - -### Features - -* **bigframes:** add ai.classify, ai.score, ai.if_ to the df bq accessor ([#17569](https://github.com/googleapis/google-cloud-python/issues/17569)) ([4f94be8](https://github.com/googleapis/google-cloud-python/commit/4f94be8f01971380f0fb5b433ab33d7b4cb7176d)) -* **bigframes:** Enable local udf execution ([#17588](https://github.com/googleapis/google-cloud-python/issues/17588)) ([b8ed34c](https://github.com/googleapis/google-cloud-python/commit/b8ed34cc05101c58ef285822d86298cd0f56613c)) -* **bigframes:** UDF transpiler handles some control flow ([#17558](https://github.com/googleapis/google-cloud-python/issues/17558)) ([a8cbde3](https://github.com/googleapis/google-cloud-python/commit/a8cbde39199f838a43ebc8b938ad722595655abd)) -* support gemini-3.x models ([#17615](https://github.com/googleapis/google-cloud-python/issues/17615)) ([5d0efa3](https://github.com/googleapis/google-cloud-python/commit/5d0efa3cb86568a33a5b3097f30733d39fcbef66)) - - -### Bug Fixes - -* bump gdal from 3.13.0 to 3.13.1 in /packages/bigframes ([#17609](https://github.com/googleapis/google-cloud-python/issues/17609)) ([0f4bfed](https://github.com/googleapis/google-cloud-python/commit/0f4bfed4685a362f6487cd4cb02ead3c0dde85c9)) -* bump gradio from 5.39.0 to 6.15.0 in /packages/bigframes ([#17619](https://github.com/googleapis/google-cloud-python/issues/17619)) ([bddda6a](https://github.com/googleapis/google-cloud-python/commit/bddda6a11a9c9bcce2d9e8b665b63d47f49f894f)) -* bump transformers from 4.54.1 to 5.3.0 in /packages/bigframes ([#17610](https://github.com/googleapis/google-cloud-python/issues/17610)) ([10eca3f](https://github.com/googleapis/google-cloud-python/commit/10eca3f4b6578c9451b06cdb2889561563fa8d0d)) - -## [2.44.0](https://github.com/googleapis/google-cloud-python/compare/bigframes-v2.43.0...bigframes-v2.44.0) (2026-06-25) - - -### Features - -* add date functions to `bigframes.bigquery` module ([#17514](https://github.com/googleapis/google-cloud-python/issues/17514)) ([e5d2e35](https://github.com/googleapis/google-cloud-python/commit/e5d2e35db94373ca395976fd755c2bc7e0a060bd)) -* **bigframes:** add AI TVFs to the pandas bq accessor ([#17402](https://github.com/googleapis/google-cloud-python/issues/17402)) ([ee74e31](https://github.com/googleapis/google-cloud-python/commit/ee74e3140a2e11936c36714a27393c3072bed6c7)) -* Experimental transpilation of unannotated python callables ([#17419](https://github.com/googleapis/google-cloud-python/issues/17419)) ([ea9aad9](https://github.com/googleapis/google-cloud-python/commit/ea9aad9a43c306ab109054183b257e6c41a1b2e6)) -* support gemini-3.x models in loader and update default model to gemini-3.5-flash ([#17557](https://github.com/googleapis/google-cloud-python/issues/17557)) ([3619b29](https://github.com/googleapis/google-cloud-python/commit/3619b29e10ae04623d101808cb98be5edbb483b4)) -* support interactive execution of deferred DataFrames in TableWidget ([#17486](https://github.com/googleapis/google-cloud-python/issues/17486)) ([421eebd](https://github.com/googleapis/google-cloud-python/commit/421eebdb31d526a6d5ba27c433cf2803d7619be3)) - - -### Bug Fixes - -* avoid invalid CAST(NULL AS NULL) in SQLGlot compiler ([#17487](https://github.com/googleapis/google-cloud-python/issues/17487)) ([3b79caa](https://github.com/googleapis/google-cloud-python/commit/3b79caa8f40f61ccd7c655542e9f242f34e068e2)) -* **bigframes:** world-readable temp zip in create_cloud_function ([#17522](https://github.com/googleapis/google-cloud-python/issues/17522)) ([e726878](https://github.com/googleapis/google-cloud-python/commit/e7268785c6736c10c1337160b4d8606975062637)) -* bump @angular/common, @angular/forms, @angular/platform-browser and @angular/router in /packages/bigframes/bigframes/display/table_widget_angular ([#17525](https://github.com/googleapis/google-cloud-python/issues/17525)) ([2f893b1](https://github.com/googleapis/google-cloud-python/commit/2f893b1b53e7394655fd204d1f8a138212ad8227)) -* bump langsmith from 0.8.0 to 0.8.18 in /packages/bigframes ([#17518](https://github.com/googleapis/google-cloud-python/issues/17518)) ([f23063f](https://github.com/googleapis/google-cloud-python/commit/f23063f9182cdec868c16afb80304892850fbe88)) -* bump msgpack from 1.1.1 to 1.2.1 in /packages/bigframes ([#17520](https://github.com/googleapis/google-cloud-python/issues/17520)) ([36b5b7e](https://github.com/googleapis/google-cloud-python/commit/36b5b7ebb01030a2d0f10d49fe4827ddc79dde9a)) -* bump undici and @angular/build in /packages/bigframes/bigframes/display/table_widget_angular ([#17519](https://github.com/googleapis/google-cloud-python/issues/17519)) ([6fc45e3](https://github.com/googleapis/google-cloud-python/commit/6fc45e3790c5a248dcec4b74799834c7b9219ef0)) -* handle empty endpoints during cloud function reuse ([#17501](https://github.com/googleapis/google-cloud-python/issues/17501)) ([4f5593a](https://github.com/googleapis/google-cloud-python/commit/4f5593a520b5afdeb02cc28f19a9596dbc35a90f)) - - -### Documentation - -* ensure that PlotAccessor is included in the API reference ([#17513](https://github.com/googleapis/google-cloud-python/issues/17513)) ([6febabf](https://github.com/googleapis/google-cloud-python/commit/6febabf795106a0c336dc905fc23da88d8cc94a0)) - -## [2.43.0](https://github.com/googleapis/google-cloud-python/compare/bigframes-v2.42.0...bigframes-v2.43.0) (2026-06-12) - - -### Documentation - -* add a notebook explaining bqsql magics cell chaining (#17216) ([1a0de4a7701b7fdf4c2593b1960f1194ebc49793](https://github.com/googleapis/google-cloud-python/commit/1a0de4a7701b7fdf4c2593b1960f1194ebc49793)) - - -### Features - -* add `bigframes.bigquery.bit_count` and conversion scalar function (#17433) ([7f29823fadb3cff42dbe666f8c7aa33bab3c7021](https://github.com/googleapis/google-cloud-python/commit/7f29823fadb3cff42dbe666f8c7aa33bab3c7021)) - - -### Bug Fixes - -* preserve aliases on cast columns and fix star selection in sqlglot (#17394) (#17455) ([145034a345eb3e14ea3f23dfcafa3d2409a09067](https://github.com/googleapis/google-cloud-python/commit/145034a345eb3e14ea3f23dfcafa3d2409a09067)) -* bump pyarrow from 15.0.2 to 23.0.1 in /packages/bigframes (#17386) ([f59c2b2aa61316cf04b650933036ef50f6a1f08c](https://github.com/googleapis/google-cloud-python/commit/f59c2b2aa61316cf04b650933036ef50f6a1f08c)) -* improve error message when unescaped `{` are found in SQL cells (#17346) ([3a90cc8e867c8a2d2f8060858fde9eda94f80a54](https://github.com/googleapis/google-cloud-python/commit/3a90cc8e867c8a2d2f8060858fde9eda94f80a54)) - -## [2.42.0](https://github.com/googleapis/google-cloud-python/compare/bigframes-v2.41.0...bigframes-v2.42.0) (2026-06-08) - - -### Features - -* create `Series.bigquery.function_name` accessors for array and AEAD functions (#17279) ([d01a4ba30040cfcb6498d0e9ef3ed3a54d56239d](https://github.com/googleapis/google-cloud-python/commit/d01a4ba30040cfcb6498d0e9ef3ed3a54d56239d)) -* support automatic per-cell execution history filtering and isolated callbacks (#17144) ([7d440111d836b94f0ce22f6b08c7ce0e7bf4a38a](https://github.com/googleapis/google-cloud-python/commit/7d440111d836b94f0ce22f6b08c7ce0e7bf4a38a)) -* Add ai_generate functions to the dataframe bq accessor (#17302) ([6b62cb6fb3de94326b8944ae08a400c12529cad2](https://github.com/googleapis/google-cloud-python/commit/6b62cb6fb3de94326b8944ae08a400c12529cad2)) - - -### Bug Fixes - -* nameless column to_frame bug for pandas 3.0 (#17371) ([b23bfa4ceb819bca8201a7fe8b64a9bed56733f0](https://github.com/googleapis/google-cloud-python/commit/b23bfa4ceb819bca8201a7fe8b64a9bed56733f0)) -* include pyopenssl as a dependency (#17362) ([1f6205ee5a370249ece2c2cc7131a47830ef00ea](https://github.com/googleapis/google-cloud-python/commit/1f6205ee5a370249ece2c2cc7131a47830ef00ea)) -* Fix IsInOp literal bug with sqlglot (#17356) ([a3d93afe74dd2b5ec8a2ae92f91c95962764debe](https://github.com/googleapis/google-cloud-python/commit/a3d93afe74dd2b5ec8a2ae92f91c95962764debe)) - -## [2.41.0](https://github.com/googleapis/google-cloud-python/compare/bigframes-v2.40.0...bigframes-v2.41.0) (2026-05-28) - - -### Documentation - -* modernize multimodal tutorials and migrate legacy blob APIs (#16918) ([05d80c3cccc237480dc5f589b7768b57a147cb0e](https://github.com/googleapis/google-cloud-python/commit/05d80c3cccc237480dc5f589b7768b57a147cb0e)) - - -### Features - -* Defer unnamed @udf deployment until needed (#17217) ([ad3b8fa9693b7d23859c417f2f5954ea946f2bb8](https://github.com/googleapis/google-cloud-python/commit/ad3b8fa9693b7d23859c417f2f5954ea946f2bb8)) -* set up Angular infrastructure for TableWidget (#16934) ([4d20bab8ce15c31e5832789e6a5d306983b8584a](https://github.com/googleapis/google-cloud-python/commit/4d20bab8ce15c31e5832789e6a5d306983b8584a)) -* support pandas inputs in more bigframes.bigquery functions (#17224) ([d4d885547f99c08caabad5e715aecd8c6f1fb4d6](https://github.com/googleapis/google-cloud-python/commit/d4d885547f99c08caabad5e715aecd8c6f1fb4d6)) -* add more scalar array functions to `bigframes.bigquery` (#17213) ([4f8a6c81797204f4334c7251c244bc0b6bd568e2](https://github.com/googleapis/google-cloud-python/commit/4f8a6c81797204f4334c7251c244bc0b6bd568e2)) -* add `bigframes.bigquery.deterministic_decrypt*` and `bigframes.bigquery.deterministic_encrypt` functions (#17212) ([85f36725802f3e7ad16156ca9e957e61d57d3112](https://github.com/googleapis/google-cloud-python/commit/85f36725802f3e7ad16156ca9e957e61d57d3112)) -* add `bigframes.bigquery.aead.*` scalar functions (#17168) ([a7e4d048e254cdb723df0a47ccbd8d09aed00c7a](https://github.com/googleapis/google-cloud-python/commit/a7e4d048e254cdb723df0a47ccbd8d09aed00c7a)) -* complete deprecation and cleanup of multimodal blob APIs (#16618) ([3624f3bb102e7d599097975db5cdaee508c9549a](https://github.com/googleapis/google-cloud-python/commit/3624f3bb102e7d599097975db5cdaee508c9549a)) -* support output_mode for ai.classify (#17097) ([098c35c5a8383d1585848e10806f9914b2ef4f97](https://github.com/googleapis/google-cloud-python/commit/098c35c5a8383d1585848e10806f9914b2ef4f97)) - - -### Bug Fixes - -* cast JSON and nested struct columns to string for anywidget rendering (#17189) ([994a22d64856b436d196743d16c3fd1967b20784](https://github.com/googleapis/google-cloud-python/commit/994a22d64856b436d196743d16c3fd1967b20784)) -* Respect display.progress_bar=None in background threads (#16715) ([07dd3315447d2feb6de8d53e0915798da9c04151](https://github.com/googleapis/google-cloud-python/commit/07dd3315447d2feb6de8d53e0915798da9c04151)) - - -### Dependencies - -* bump mistune from 3.1.3 to 3.2.1 in /packages/bigframes (#17202) ([52f21788f76575036624c9163b63620a1bb92a83](https://github.com/googleapis/google-cloud-python/commit/52f21788f76575036624c9163b63620a1bb92a83)) -* bump langsmith from 0.4.10 to 0.8.0 in /packages/bigframes (#17210) ([9dd0c02c585f7fda34d6e2199ab2bc7c0b5a246a](https://github.com/googleapis/google-cloud-python/commit/9dd0c02c585f7fda34d6e2199ab2bc7c0b5a246a)) -* bump gdal from 3.8.4 to 3.13.0 in /packages/bigframes (#17204) ([900007bab07feb7580cb7e8a36a5d4ee4cce14ab](https://github.com/googleapis/google-cloud-python/commit/900007bab07feb7580cb7e8a36a5d4ee4cce14ab)) - -## [2.40.0](https://github.com/googleapis/google-cloud-python/compare/bigframes-v2.39.0...bigframes-v2.40.0) (2026-05-13) - - -### Documentation - -* Add docs to the to_csv methods of dataframe and series (#16570) ([a8fccefd868e3474d3a2cfbabc03364891e05824](https://github.com/googleapis/google-cloud-python/commit/a8fccefd868e3474d3a2cfbabc03364891e05824)) - - -### Features - -* add more params to ai.classify (#16990) ([e9c52b12c02f8b15e43b62e6f3fb7617ac3bdfd9](https://github.com/googleapis/google-cloud-python/commit/e9c52b12c02f8b15e43b62e6f3fb7617ac3bdfd9)) -* add support for `hparam_range` and `hparam_candidates` to `bigframes.bigquery.create_model` (#16640) ([ca47835ce0e381c0833545ca1cf7734c3c34ceb5](https://github.com/googleapis/google-cloud-python/commit/ca47835ce0e381c0833545ca1cf7734c3c34ceb5)) -* update ai.score to match its SQL version (#16919) ([9f42fe1436df61ca0abad77bb4b51ed983a85a48](https://github.com/googleapis/google-cloud-python/commit/9f42fe1436df61ca0abad77bb4b51ed983a85a48)) -* update ai.if_() params to match the SQL version (#16857) ([f3cb4ad04a15a58a931d4feb43b172805209cf58](https://github.com/googleapis/google-cloud-python/commit/f3cb4ad04a15a58a931d4feb43b172805209cf58)) -* Support unstable sort_values, sort_index (#16665) ([bbdeb70fff766dc51bcac32b5312c13ce16764d4](https://github.com/googleapis/google-cloud-python/commit/bbdeb70fff766dc51bcac32b5312c13ce16764d4)) -* Support Expression objects in create_model options (#16606) ([cf12ffd858bdba0a95dba8fd591ed9adcf8c0e8a](https://github.com/googleapis/google-cloud-python/commit/cf12ffd858bdba0a95dba8fd591ed9adcf8c0e8a)) -* implement ai.similarity (#16771) ([d4afa2c835d53983ecd22e2f9835107791cde65f](https://github.com/googleapis/google-cloud-python/commit/d4afa2c835d53983ecd22e2f9835107791cde65f)) -* implement ai.embed (#16759) ([fcb4579b9e273c3ad43ed150f4ef0fbb7daeef2c](https://github.com/googleapis/google-cloud-python/commit/fcb4579b9e273c3ad43ed150f4ef0fbb7daeef2c)) -* Add bigframes.execution_history API to track BigQuery jobs (#16588) ([fa20a740b15accf2b1ae18a9ac20b75f006dbcad](https://github.com/googleapis/google-cloud-python/commit/fa20a740b15accf2b1ae18a9ac20b75f006dbcad)) -* Support loading avro, orc data (#16555) ([6d46cba3777c1b2adf6f1f86f6d3db3ea30c55d2](https://github.com/googleapis/google-cloud-python/commit/6d46cba3777c1b2adf6f1f86f6d3db3ea30c55d2)) -* Add numpy ufunc support to col expressions (#16554) ([2f792abd5d48ec680305e1e4ec9136360e16c9a5](https://github.com/googleapis/google-cloud-python/commit/2f792abd5d48ec680305e1e4ec9136360e16c9a5)) - - -### Bug Fixes - -* avoid `copy` argument warning in `to_pandas` (#16917) ([fe5245b8f20dd94231e72e2572609e029ee137c7](https://github.com/googleapis/google-cloud-python/commit/fe5245b8f20dd94231e72e2572609e029ee137c7)) -* BigFrames respects bq default region (#16933) ([ef9945a5d6296e6bbf00b6ef980462f5a0b91b20](https://github.com/googleapis/google-cloud-python/commit/ef9945a5d6296e6bbf00b6ef980462f5a0b91b20)) -* Fix bugs compiling ambiguous ids and in subqueries (#16617) ([479e44ddb8ba7515797f062064c4ebf2db5d09f2](https://github.com/googleapis/google-cloud-python/commit/479e44ddb8ba7515797f062064c4ebf2db5d09f2)) -* avoid views when querying BigLake tables from SQL cells (#16562) ([fdd3e0de66377d75ec235e4fc071e4ecc33a35c7](https://github.com/googleapis/google-cloud-python/commit/fdd3e0de66377d75ec235e4fc071e4ecc33a35c7)) - -## [2.39.0](https://github.com/googleapis/python-bigquery-dataframes/compare/v2.38.0...v2.39.0) (2026-03-31) - - -### Documentation - -* Rename Blob column references to ObjectRef column (#2535) ([44e0ffd947e9db66ab612f92de6e31f1085e7968](https://github.com/googleapis/python-bigquery-dataframes/commit/44e0ffd947e9db66ab612f92de6e31f1085e7968)) -* gemini retouch of the index page for seo (#2514) ([2e5311e2242b039da4c8e37b7b48942fa8ed34c2](https://github.com/googleapis/python-bigquery-dataframes/commit/2e5311e2242b039da4c8e37b7b48942fa8ed34c2)) - - -### Features - -* expose DataFrame.bigquery in both pandas and bigframes DataFrames (#2533) ([69fe317612a69aa92f06f0c418c67aa1f9488bd2](https://github.com/googleapis/python-bigquery-dataframes/commit/69fe317612a69aa92f06f0c418c67aa1f9488bd2)) -* support full round-trip persistence for multimodal reference cols (#2511) ([494a0a113b1ba6dcdc9f9b85a4f750d093f5652f](https://github.com/googleapis/python-bigquery-dataframes/commit/494a0a113b1ba6dcdc9f9b85a4f750d093f5652f)) -* add `df.bigquery.ai.forecast` method to pandas dataframe accessor (#2518) ([1126cec9cdfcc1ec1062c60e5affbe1b60223767](https://github.com/googleapis/python-bigquery-dataframes/commit/1126cec9cdfcc1ec1062c60e5affbe1b60223767)) - - -### Bug Fixes - -* handle aggregate operations on empty selections (#2510) ([34fb5daa93726d0d3ff364912a3c1de0fc535fb2](https://github.com/googleapis/python-bigquery-dataframes/commit/34fb5daa93726d0d3ff364912a3c1de0fc535fb2)) -* Localize BigQuery log suppression for gbq.py (#2541) ([af49ca29399aa2c63753d9045fd382e30334d134](https://github.com/googleapis/python-bigquery-dataframes/commit/af49ca29399aa2c63753d9045fd382e30334d134)) -* to_gbq may swap data columns when replace table (#2532) ([17ecc65e1c0397ef349fca4afcf5a77af72aa798](https://github.com/googleapis/python-bigquery-dataframes/commit/17ecc65e1c0397ef349fca4afcf5a77af72aa798)) -* Respect remote function config changes even if logic unchanged (#2512) ([b9524284ad3b457b15598f546bac04c76b3e27b8](https://github.com/googleapis/python-bigquery-dataframes/commit/b9524284ad3b457b15598f546bac04c76b3e27b8)) -* support melting empty DataFrames without crashing (#2509) ([e8c46032154e186042314d97aa813301413d8a13](https://github.com/googleapis/python-bigquery-dataframes/commit/e8c46032154e186042314d97aa813301413d8a13)) - -## [2.38.0](https://github.com/googleapis/python-bigquery-dataframes/compare/v2.37.0...v2.38.0) (2026-03-16) - - -### Documentation - -* add notebooks to user guide page (#2505) ([5cf37888bc0b4b1b0993dadd1e0fe5ee08341ef4](https://github.com/googleapis/python-bigquery-dataframes/commit/5cf37888bc0b4b1b0993dadd1e0fe5ee08341ef4)) -* Fix typo in ExperimentOptions class docstring (#2498) ([077cb2ebe515fc5e07bcbb5dc663edd28d3eaf00](https://github.com/googleapis/python-bigquery-dataframes/commit/077cb2ebe515fc5e07bcbb5dc663edd28d3eaf00)) - - -### Features - -* add `df.bigquery` pandas accessor (#2513) ([91b6c245521218bb78b543885e1b9424278ce2ab](https://github.com/googleapis/python-bigquery-dataframes/commit/91b6c245521218bb78b543885e1b9424278ce2ab)) -* use EUC for AI IF, CLASSIFY, and SCORE when connection is not provided (#2507) ([fe94910abff28e244dd79e1540a6c2184a12eb44](https://github.com/googleapis/python-bigquery-dataframes/commit/fe94910abff28e244dd79e1540a6c2184a12eb44)) -* Add `bigframes.bigquery.rand()` function (#2501) ([5c43efb745118f506ecc30196da68e9d6f4346dc](https://github.com/googleapis/python-bigquery-dataframes/commit/5c43efb745118f506ecc30196da68e9d6f4346dc)) -* add bigquery.ml.get_insights function (#2493) ([d29a60953ac989bb2c95e6eec3010620ac776a3c](https://github.com/googleapis/python-bigquery-dataframes/commit/d29a60953ac989bb2c95e6eec3010620ac776a3c)) -* Add str, dt accessors to pd.col Expression objects (#2488) ([ce5de57019449ca77d308946df72f04289343b51](https://github.com/googleapis/python-bigquery-dataframes/commit/ce5de57019449ca77d308946df72f04289343b51)) - - -### Bug Fixes - -* handle unsupported types and empty results in describe (#2506) ([2326ad6aec15c20a66756eff093b50be484b3ba8](https://github.com/googleapis/python-bigquery-dataframes/commit/2326ad6aec15c20a66756eff093b50be484b3ba8)) -* no longer automatically use anywidget in the `%%bqsql` magics (#2504) ([43353e2bc9ffbc38b7383c24ecaac80d3b8bab32](https://github.com/googleapis/python-bigquery-dataframes/commit/43353e2bc9ffbc38b7383c24ecaac80d3b8bab32)) - -## [2.37.0](https://github.com/googleapis/python-bigquery-dataframes/compare/v2.36.0...v2.37.0) (2026-03-03) - - -### Documentation - -* Fix recall_score doc example (#2477) ([a6f499c1e225a962b53621158f9d4a19ca220ccd](https://github.com/googleapis/python-bigquery-dataframes/commit/a6f499c1e225a962b53621158f9d4a19ca220ccd)) -* add code sample and docstring for bpd.options.experiments.sql_compiler (#2474) ([867951bcabcff12e2fce88143b45d929d3237088](https://github.com/googleapis/python-bigquery-dataframes/commit/867951bcabcff12e2fce88143b45d929d3237088)) -* use direct API for image (#2465) ([8a1a82f7a0fd224f2b075c68ab116d1f580d1d82](https://github.com/googleapis/python-bigquery-dataframes/commit/8a1a82f7a0fd224f2b075c68ab116d1f580d1d82)) -* add bigframes default connection warning (#2471) ([f1bbba23667f01d3b8e7c51b18fe64641a4b135f](https://github.com/googleapis/python-bigquery-dataframes/commit/f1bbba23667f01d3b8e7c51b18fe64641a4b135f)) -* Move readme content to new User Guide section (#2464) ([61a948451baeb1caa323e721ad88b31c7cd0b3cb](https://github.com/googleapis/python-bigquery-dataframes/commit/61a948451baeb1caa323e721ad88b31c7cd0b3cb)) -* Skip inherited methods, use autosummary only for big classes (#2470) ([a9512498ef39b9d5260cad2ca0513c701a6d3592](https://github.com/googleapis/python-bigquery-dataframes/commit/a9512498ef39b9d5260cad2ca0513c701a6d3592)) -* Add code examples to configuration docstrings (#2352) ([3c21993e6fca474c32f3c2371c41ef2be146267e](https://github.com/googleapis/python-bigquery-dataframes/commit/3c21993e6fca474c32f3c2371c41ef2be146267e)) - - -### Features - -* Add cloud_function_cpus option to remote_function (#2475) ([4caf74ccaeb9608d91da864bb80eddf1148a1502](https://github.com/googleapis/python-bigquery-dataframes/commit/4caf74ccaeb9608d91da864bb80eddf1148a1502)) -* Support pd.col simple aggregates (#2480) ([cb00daabce49f067be8e16627166dda00d5d8134](https://github.com/googleapis/python-bigquery-dataframes/commit/cb00daabce49f067be8e16627166dda00d5d8134)) -* add display.render_mode to control DataFrame/Series visualization (#2413) ([7813eaa6fa2ae42943b90583e600c95beaf5d75e](https://github.com/googleapis/python-bigquery-dataframes/commit/7813eaa6fa2ae42943b90583e600c95beaf5d75e)) -* add support for Python 3.14 (#2232) ([c25a6d0151380dde74368a35e13deb7a930b494f](https://github.com/googleapis/python-bigquery-dataframes/commit/c25a6d0151380dde74368a35e13deb7a930b494f)) -* Support pd.col expressions with .loc and getitem (#2473) ([ae5c8b322765aef51eed016bfacaff5a7a917a7b](https://github.com/googleapis/python-bigquery-dataframes/commit/ae5c8b322765aef51eed016bfacaff5a7a917a7b)) -* add dt.tz_localize() (#2469) ([f70f93a1227add1627d522d7e55a37f42fc3549e](https://github.com/googleapis/python-bigquery-dataframes/commit/f70f93a1227add1627d522d7e55a37f42fc3549e)) -* Update bigquery.ai.generate_table output_schema to allow Mapping type (#2463) ([f7fd1895e64a133fe63eddeb90f57a42a35c29b2](https://github.com/googleapis/python-bigquery-dataframes/commit/f7fd1895e64a133fe63eddeb90f57a42a35c29b2)) - - -### Bug Fixes - -* upload local data through write API if nested JSONs detected (#2478) ([01dc5a34e09171351575d5cbdc9f301e505e1567](https://github.com/googleapis/python-bigquery-dataframes/commit/01dc5a34e09171351575d5cbdc9f301e505e1567)) -* allow IsInOp with same dtypes regardless nullable (#2466) ([1d81b414acbc964502ca624eae72cdb8c14e1576](https://github.com/googleapis/python-bigquery-dataframes/commit/1d81b414acbc964502ca624eae72cdb8c14e1576)) - -## [2.36.0](https://github.com/googleapis/python-bigquery-dataframes/compare/v2.35.0...v2.36.0) (2026-02-17) - - -### Documentation - -* update multimodal dataframe notebook to use public APIs (#2456) ([342fa723c4631d371364a87ae0ddd6fa03360a4b](https://github.com/googleapis/python-bigquery-dataframes/commit/342fa723c4631d371364a87ae0ddd6fa03360a4b)) -* use direct API for pdf chunk and pdf extract (#2452) ([543ce52c18269eab2a89886f226d1478dbabf9ba](https://github.com/googleapis/python-bigquery-dataframes/commit/543ce52c18269eab2a89886f226d1478dbabf9ba)) -* fix generate_text and generate_table input docs (#2455) ([078bd32ebd28af0d2cfba6bb874ba79e904183e2](https://github.com/googleapis/python-bigquery-dataframes/commit/078bd32ebd28af0d2cfba6bb874ba79e904183e2)) -* Update multimodal notebook to use public runtime helpers (#2451) ([e36dd8b492fd7ab433fa4cac732b31774c1e428b](https://github.com/googleapis/python-bigquery-dataframes/commit/e36dd8b492fd7ab433fa4cac732b31774c1e428b)) -* use direct API for audio transcription (#2447) ([59cbc5db66fd178ecce03bf4b8b4a504d7ef3e9f](https://github.com/googleapis/python-bigquery-dataframes/commit/59cbc5db66fd178ecce03bf4b8b4a504d7ef3e9f)) -* Add EXIF metadata extraction example to multimodal notebook (#2429) ([84c6f883aef8048e7013a8b3c03a1bde47e94eea](https://github.com/googleapis/python-bigquery-dataframes/commit/84c6f883aef8048e7013a8b3c03a1bde47e94eea)) - - -### Features - -* Initial support for biglake iceberg tables (#2409) ([ae35a9890a2f9903b12e431488362c091118bbdd](https://github.com/googleapis/python-bigquery-dataframes/commit/ae35a9890a2f9903b12e431488362c091118bbdd)) -* add bigquery.ai.generate_table function (#2453) ([b925aa243dad0e42ad126c9397f42be0aad7152d](https://github.com/googleapis/python-bigquery-dataframes/commit/b925aa243dad0e42ad126c9397f42be0aad7152d)) - -## [2.35.0](https://github.com/googleapis/python-bigquery-dataframes/compare/v2.34.0...v2.35.0) (2026-02-07) - - -### Documentation - -* fix cast method shown on public docs (#2436) ([ad0f33c65ee01409826c381ae0f70aad65bb6a27](https://github.com/googleapis/python-bigquery-dataframes/commit/ad0f33c65ee01409826c381ae0f70aad65bb6a27)) - - -### Features - -* remove redundant "started." messages from progress output (#2440) ([2017cc2f27f0a432af46f60b3286b231caa4a98b](https://github.com/googleapis/python-bigquery-dataframes/commit/2017cc2f27f0a432af46f60b3286b231caa4a98b)) -* Add bigframes.pandas.col with basic operators (#2405) ([12741677c0391efb5d05281fc756445ccbb1387e](https://github.com/googleapis/python-bigquery-dataframes/commit/12741677c0391efb5d05281fc756445ccbb1387e)) -* Disable progress bars in Anywidget mode (#2444) ([4e2689a1c975c4cabaf36b7d0817dcbedc926853](https://github.com/googleapis/python-bigquery-dataframes/commit/4e2689a1c975c4cabaf36b7d0817dcbedc926853)) -* Disable progress bars in Anywidget mode to reduce notebook clutter (#2437) ([853240daf45301ad534c635c8955cb6ce91d23c2](https://github.com/googleapis/python-bigquery-dataframes/commit/853240daf45301ad534c635c8955cb6ce91d23c2)) -* add bigquery.ai.generate_text function (#2433) ([5bd0029a99e7653843de4ac7d57370c9dffeed4d](https://github.com/googleapis/python-bigquery-dataframes/commit/5bd0029a99e7653843de4ac7d57370c9dffeed4d)) -* Add a bigframes cell magic for ipython (#2395) ([e6de52ded6c5091275a936dec36f01a6cf701233](https://github.com/googleapis/python-bigquery-dataframes/commit/e6de52ded6c5091275a936dec36f01a6cf701233)) -* add `bigframes.bigquery.ai.generate_embedding` (#2343) ([e91536c8a5b2d8d896767510ced80c6fd2a68a97](https://github.com/googleapis/python-bigquery-dataframes/commit/e91536c8a5b2d8d896767510ced80c6fd2a68a97)) -* add bigframe.bigquery.load_data function (#2426) ([4b0f13b2fe10fa5b07d3ca3b7cb1ae1cb95030c7](https://github.com/googleapis/python-bigquery-dataframes/commit/4b0f13b2fe10fa5b07d3ca3b7cb1ae1cb95030c7)) - - -### Bug Fixes - -* suppress JSONDtypeWarning in Anywidget mode and clean up progress output (#2441) ([e0d185ad2c0245b17eac315f71152a46c6da41bb](https://github.com/googleapis/python-bigquery-dataframes/commit/e0d185ad2c0245b17eac315f71152a46c6da41bb)) -* exlcude gcsfs 2026.2.0 (#2445) ([311de31e79227408515f087dafbab7edc54ddf1b](https://github.com/googleapis/python-bigquery-dataframes/commit/311de31e79227408515f087dafbab7edc54ddf1b)) -* always display the results in the `%%bqsql` cell magics output (#2439) ([2d973b54550f30429dbd10894f78db7bb0c57345](https://github.com/googleapis/python-bigquery-dataframes/commit/2d973b54550f30429dbd10894f78db7bb0c57345)) - -## [2.34.0](https://github.com/googleapis/python-bigquery-dataframes/compare/v2.33.0...v2.34.0) (2026-02-02) - - -### Features - -* add `bigframes.pandas.options.experiments.sql_compiler` for switching the backend compiler (#2417) ([7eba6ee03f07938315d99e2aeaf72368c02074cf](https://github.com/googleapis/python-bigquery-dataframes/commit/7eba6ee03f07938315d99e2aeaf72368c02074cf)) -* add bigquery.ml.generate_embedding function (#2422) ([35f3f5e6f8c64b47e6e7214034f96f047785e647](https://github.com/googleapis/python-bigquery-dataframes/commit/35f3f5e6f8c64b47e6e7214034f96f047785e647)) -* add bigquery.create_external_table method (#2415) ([76db2956e505aec4f1055118ac7ca523facc10ff](https://github.com/googleapis/python-bigquery-dataframes/commit/76db2956e505aec4f1055118ac7ca523facc10ff)) -* add deprecation warnings for .blob accessor and read_gbq_object_table (#2408) ([7261a4ea5cdab6b30f5bc333501648c60e70be59](https://github.com/googleapis/python-bigquery-dataframes/commit/7261a4ea5cdab6b30f5bc333501648c60e70be59)) -* add bigquery.ml.generate_text function (#2403) ([5ac681028624de15e31f0c2ae360b47b2dcf1e8d](https://github.com/googleapis/python-bigquery-dataframes/commit/5ac681028624de15e31f0c2ae360b47b2dcf1e8d)) - - -### Bug Fixes - -* broken job url (#2411) ([fcb5bc1761c656e1aec61dbcf96a36d436833b7a](https://github.com/googleapis/python-bigquery-dataframes/commit/fcb5bc1761c656e1aec61dbcf96a36d436833b7a)) - -## [2.33.0](https://github.com/googleapis/python-bigquery-dataframes/compare/v2.32.0...v2.33.0) (2026-01-22) - - -### Features - -* add bigquery.ml.transform function (#2394) ([1f9ee373c1f1d0cd08b80169c3063b862ea46465](https://github.com/googleapis/python-bigquery-dataframes/commit/1f9ee373c1f1d0cd08b80169c3063b862ea46465)) -* Add BigQuery ObjectRef functions to `bigframes.bigquery.obj` (#2380) ([9c3bbc36983dffb265454f27b37450df8c5fbc71](https://github.com/googleapis/python-bigquery-dataframes/commit/9c3bbc36983dffb265454f27b37450df8c5fbc71)) -* Stabilize interactive table height to prevent notebook layout shifts (#2378) ([a634e976c0f44087ca2a65f68cf2775ae6f04024](https://github.com/googleapis/python-bigquery-dataframes/commit/a634e976c0f44087ca2a65f68cf2775ae6f04024)) -* Add max_columns control for anywidget mode (#2374) ([34b5975f6911c5aa5ffc64a2fe6967a9f3d86f78](https://github.com/googleapis/python-bigquery-dataframes/commit/34b5975f6911c5aa5ffc64a2fe6967a9f3d86f78)) -* Add dark mode to anywidget mode (#2365) ([2763b41d4b86939e389f76789f5b2acd44f18169](https://github.com/googleapis/python-bigquery-dataframes/commit/2763b41d4b86939e389f76789f5b2acd44f18169)) -* Configure Biome for Consistent Code Style (#2364) ([81e27b3d81da9b1684eae0b7f0b9abfd7badcc4f](https://github.com/googleapis/python-bigquery-dataframes/commit/81e27b3d81da9b1684eae0b7f0b9abfd7badcc4f)) - - -### Bug Fixes - -* Throw if write api commit op has stream_errors (#2385) ([7abfef0598d476ef233364a01f72d73291983c30](https://github.com/googleapis/python-bigquery-dataframes/commit/7abfef0598d476ef233364a01f72d73291983c30)) -* implement retry logic for cloud function endpoint fetching (#2369) ([0f593c27bfee89fe1bdfc880504f9ab0ac28a24e](https://github.com/googleapis/python-bigquery-dataframes/commit/0f593c27bfee89fe1bdfc880504f9ab0ac28a24e)) - -## [2.32.0](https://github.com/googleapis/google-cloud-python/compare/bigframes-v2.31.0...bigframes-v2.32.0) (2026-01-05) - - -### Documentation - -* generate sitemap.xml for better search indexing (#2351) ([7d2990f1c48c6d74e2af6bee3af87f90189a3d9b](https://github.com/googleapis/google-cloud-python/commit/7d2990f1c48c6d74e2af6bee3af87f90189a3d9b)) -* update supported pandas APIs documentation links (#2330) ([ea71936ce240b2becf21b552d4e41e8ef4418e2d](https://github.com/googleapis/google-cloud-python/commit/ea71936ce240b2becf21b552d4e41e8ef4418e2d)) -* Add time series analysis notebook (#2328) ([369f1c0aff29d197b577ec79e401b107985fe969](https://github.com/googleapis/google-cloud-python/commit/369f1c0aff29d197b577ec79e401b107985fe969)) - - -### Features - -* Enable multi-column sorting in anywidget mode (#2360) ([1feb956e4762e30276e5b380c0633e6ed7881357](https://github.com/googleapis/google-cloud-python/commit/1feb956e4762e30276e5b380c0633e6ed7881357)) -* display series in anywidget mode (#2346) ([7395d418550058c516ad878e13567256f4300a37](https://github.com/googleapis/google-cloud-python/commit/7395d418550058c516ad878e13567256f4300a37)) -* Refactor TableWidget and to_pandas_batches (#2250) ([b8f09015a7c8e6987dc124e6df925d4f6951b1da](https://github.com/googleapis/google-cloud-python/commit/b8f09015a7c8e6987dc124e6df925d4f6951b1da)) -* Auto-plan complex reduction expressions (#2298) ([4d5de14ccdd05b1ac8f50c3fe71c35ab9e5150c1](https://github.com/googleapis/google-cloud-python/commit/4d5de14ccdd05b1ac8f50c3fe71c35ab9e5150c1)) -* Display custom single index column in anywidget mode (#2311) ([f27196260743883ed8131d5fd33a335e311177e4](https://github.com/googleapis/google-cloud-python/commit/f27196260743883ed8131d5fd33a335e311177e4)) -* add fit_predict method to ml unsupervised models (#2320) ([59df7f70a12ef702224ad61e597bd775208dac45](https://github.com/googleapis/google-cloud-python/commit/59df7f70a12ef702224ad61e597bd775208dac45)) - - -### Bug Fixes - -* vendor sqlglot bigquery dialect and remove package dependency (#2354) ([b321d72d5eb005b6e9295541a002540f05f72209](https://github.com/googleapis/google-cloud-python/commit/b321d72d5eb005b6e9295541a002540f05f72209)) -* bigframes.ml fit with eval data in partial mode avoids join on null index (#2355) ([7171d21b8c8d5a2d61081f41fa1109b5c9c4bc5f](https://github.com/googleapis/google-cloud-python/commit/7171d21b8c8d5a2d61081f41fa1109b5c9c4bc5f)) -* Improve strictness of nan vs None usage (#2326) ([481d938fb0b840e17047bc4b57e61af15b976e54](https://github.com/googleapis/google-cloud-python/commit/481d938fb0b840e17047bc4b57e61af15b976e54)) -* Correct DataFrame widget rendering in Colab (#2319) ([7f1d3df3839ec58f52e48df088057fc0df967da9](https://github.com/googleapis/google-cloud-python/commit/7f1d3df3839ec58f52e48df088057fc0df967da9)) -* Fix pd.timedelta handling in polars comipler with polars 1.36 (#2325) ([252644826289d9db7a8548884de880b3a4fccafd](https://github.com/googleapis/google-cloud-python/commit/252644826289d9db7a8548884de880b3a4fccafd)) - -## [2.31.0](https://github.com/googleapis/google-cloud-python/compare/bigframes-v2.30.0...bigframes-v2.31.0) (2025-12-10) - - -### Features - -* add `bigframes.bigquery.ml` methods (#2300) ([719b278c844ca80c1bec741873b30a9ee4fd6c56](https://github.com/googleapis/google-cloud-python/commit/719b278c844ca80c1bec741873b30a9ee4fd6c56)) -* add 'weekday' property to DatatimeMethod (#2304) ([fafd7c732d434eca3f8b5d849a87149f106e3d5d](https://github.com/googleapis/google-cloud-python/commit/fafd7c732d434eca3f8b5d849a87149f106e3d5d)) - - -### Bug Fixes - -* cache DataFrames to temp tables in bigframes.bigquery.ml methods to avoid time travel (#2318) ([d99383195ac3f1683842cfe472cca5a914b04d8e](https://github.com/googleapis/google-cloud-python/commit/d99383195ac3f1683842cfe472cca5a914b04d8e)) - -## [2.30.0](https://github.com/googleapis/google-cloud-python/compare/bigframes-v2.29.0...bigframes-v2.30.0) (2025-12-03) - - -### Documentation - -* Add Google Analytics configuration to conf.py (#2301) ([0b266da10f4d3d0ef9b4dd71ddadebfc7d5064ca](https://github.com/googleapis/google-cloud-python/commit/0b266da10f4d3d0ef9b4dd71ddadebfc7d5064ca)) -* fix LogisticRegression docs rendering (#2295) ([32e531343c764156b45c6fb9de49793d26c19f02](https://github.com/googleapis/google-cloud-python/commit/32e531343c764156b45c6fb9de49793d26c19f02)) -* update API reference to new `dataframes.bigquery.dev` location (#2293) ([da064397acd2358c16fdd9659edf23afde5c882a](https://github.com/googleapis/google-cloud-python/commit/da064397acd2358c16fdd9659edf23afde5c882a)) -* use autosummary to split documentation pages (#2251) ([f7fd2d20896fe3e0e210c3833b6a4c3913270ebc](https://github.com/googleapis/google-cloud-python/commit/f7fd2d20896fe3e0e210c3833b6a4c3913270ebc)) -* update docs and tests for Gemini 2.5 models (#2279) ([08c0c0c8fe8f806f6224dc403a3f1d4db708573a](https://github.com/googleapis/google-cloud-python/commit/08c0c0c8fe8f806f6224dc403a3f1d4db708573a)) - - -### Features - -* Allow drop_duplicates over unordered dataframe (#2303) ([52665fa57ef13c58254bfc8736afcc521f7f0f11](https://github.com/googleapis/google-cloud-python/commit/52665fa57ef13c58254bfc8736afcc521f7f0f11)) -* Add agg/aggregate methods to windows (#2288) ([c4cb39dcbd388356f5f1c48ff28b19b79b996485](https://github.com/googleapis/google-cloud-python/commit/c4cb39dcbd388356f5f1c48ff28b19b79b996485)) -* Implement single-column sorting for interactive table widget (#2255) ([d1ecc61bf448651a0cca0fc760673da54f5c2183](https://github.com/googleapis/google-cloud-python/commit/d1ecc61bf448651a0cca0fc760673da54f5c2183)) -* add bigquery.json_keys (#2286) ([b487cf1f6ecacb1ee3b35ffdd934221516bbd558](https://github.com/googleapis/google-cloud-python/commit/b487cf1f6ecacb1ee3b35ffdd934221516bbd558)) -* use end user credentials for `bigframes.bigquery.ai` functions when `connection_id` is not present (#2272) ([7c062a68c6a3c9737865985b4f1fd80117490c73](https://github.com/googleapis/google-cloud-python/commit/7c062a68c6a3c9737865985b4f1fd80117490c73)) -* pivot_table supports fill_value arg (#2257) ([8f490e68a9a2584236486060ad3b55923781d975](https://github.com/googleapis/google-cloud-python/commit/8f490e68a9a2584236486060ad3b55923781d975)) -* Support mixed scalar-analytic expressions (#2239) ([20ab469d29767a2f04fe02aa66797893ecd1c539](https://github.com/googleapis/google-cloud-python/commit/20ab469d29767a2f04fe02aa66797893ecd1c539)) -* Support builtins funcs for df.agg (#2256) ([956a5b00dff55b73e3cbebb4e6e81672680f1f63](https://github.com/googleapis/google-cloud-python/commit/956a5b00dff55b73e3cbebb4e6e81672680f1f63)) -* Preserve source names better for more readable sql (#2243) ([64995d659837a8576b2ee9335921904e577c7014](https://github.com/googleapis/google-cloud-python/commit/64995d659837a8576b2ee9335921904e577c7014)) -* Add bigframes.pandas.crosstab (#2231) ([c62e5535ed4c19b6d65f9a46cb1531e8099621b2](https://github.com/googleapis/google-cloud-python/commit/c62e5535ed4c19b6d65f9a46cb1531e8099621b2)) - - -### Bug Fixes - -* Update max_instances default to reflect actual value (#2302) ([4489687eafc9a1ea1b985600010296a4245cef94](https://github.com/googleapis/google-cloud-python/commit/4489687eafc9a1ea1b985600010296a4245cef94)) -* Fix issue with stream upload batch size upload limit (#2290) ([6cdf64b0674d0e673f86362032d549316850837b](https://github.com/googleapis/google-cloud-python/commit/6cdf64b0674d0e673f86362032d549316850837b)) -* Pass credentials properly for read api instantiation (#2280) ([3e3fe259567d249d91f90786a577b05577e2b9fd](https://github.com/googleapis/google-cloud-python/commit/3e3fe259567d249d91f90786a577b05577e2b9fd)) -* Improve Anywidget pagination and display for unknown row counts (#2258) ([508deae5869e06cdad7bb94537c9c58d8f083d86](https://github.com/googleapis/google-cloud-python/commit/508deae5869e06cdad7bb94537c9c58d8f083d86)) -* calling info() on empty dataframes no longer leads to errors (#2267) ([95a83f7774766cd19cb583dfaa3417882b5c9b1e](https://github.com/googleapis/google-cloud-python/commit/95a83f7774766cd19cb583dfaa3417882b5c9b1e)) -* do not warn with DefaultIndexWarning in partial ordering mode (#2230) ([cc2dbae684103a21fe8838468f7eb8267188780d](https://github.com/googleapis/google-cloud-python/commit/cc2dbae684103a21fe8838468f7eb8267188780d)) - -## [2.29.0](https://github.com/googleapis/python-bigquery-dataframes/compare/v2.28.0...v2.29.0) (2025-11-10) - - -### Features - -* Add bigframes.bigquery.st_regionstats to join raster data from Earth Engine ([#2228](https://github.com/googleapis/python-bigquery-dataframes/issues/2228)) ([10ec52f](https://github.com/googleapis/python-bigquery-dataframes/commit/10ec52f30a0a9c61b9eda9cf4f9bd6aa0cd95db5)) -* Add DataFrame.resample and Series.resample ([#2213](https://github.com/googleapis/python-bigquery-dataframes/issues/2213)) ([c9ca02c](https://github.com/googleapis/python-bigquery-dataframes/commit/c9ca02c5194c8b8e9b940eddd2224efd2ff0d5d9)) -* SQL Cell no longer escapes formatted string values ([#2245](https://github.com/googleapis/python-bigquery-dataframes/issues/2245)) ([d2d38f9](https://github.com/googleapis/python-bigquery-dataframes/commit/d2d38f94ed8333eae6f9cff3833177756eefe85a)) -* Support left_index and right_index for merge ([#2220](https://github.com/googleapis/python-bigquery-dataframes/issues/2220)) ([da9ba26](https://github.com/googleapis/python-bigquery-dataframes/commit/da9ba267812c01ffa6fa0b09943d7a4c63b8f187)) - - -### Bug Fixes - -* Correctly iterate over null struct values in ManagedArrowTable ([#2209](https://github.com/googleapis/python-bigquery-dataframes/issues/2209)) ([12e04d5](https://github.com/googleapis/python-bigquery-dataframes/commit/12e04d55f0d6aef1297b7ca773935aecf3313ee7)) -* Simplify UnsupportedTypeError message ([#2212](https://github.com/googleapis/python-bigquery-dataframes/issues/2212)) ([6c9a18d](https://github.com/googleapis/python-bigquery-dataframes/commit/6c9a18d7e67841c6fe6c1c6f34f80b950815141f)) -* Support results with STRUCT and ARRAY columns containing JSON subfields in `to_pandas_batches()` ([#2216](https://github.com/googleapis/python-bigquery-dataframes/issues/2216)) ([3d8b17f](https://github.com/googleapis/python-bigquery-dataframes/commit/3d8b17fa5eb9bbfc9e151031141a419f2dc3acb4)) - - -### Documentation - -* Switch API reference docs to pydata theme ([#2237](https://github.com/googleapis/python-bigquery-dataframes/issues/2237)) ([9b86dcf](https://github.com/googleapis/python-bigquery-dataframes/commit/9b86dcf87929648bf5ab565dfd46a23b639f01ac)) -* Update notebook for JSON subfields support in to_pandas_batches() ([#2138](https://github.com/googleapis/python-bigquery-dataframes/issues/2138)) ([5663d2a](https://github.com/googleapis/python-bigquery-dataframes/commit/5663d2a18064589596558af109e915f87d426eb0)) - -## [2.28.0](https://github.com/googleapis/python-bigquery-dataframes/compare/v2.27.0...v2.28.0) (2025-11-03) - - -### Features - -* Add bigframes.bigquery.st_simplify ([#2210](https://github.com/googleapis/python-bigquery-dataframes/issues/2210)) ([ecee2bc](https://github.com/googleapis/python-bigquery-dataframes/commit/ecee2bc6ada0bc968fc56ed7194dc8c043547e93)) -* Add Series.dt.day_name ([#2218](https://github.com/googleapis/python-bigquery-dataframes/issues/2218)) ([5e006e4](https://github.com/googleapis/python-bigquery-dataframes/commit/5e006e404b65c32e5b1d342ebfcfce59ee592c8c)) -* Polars engine supports std, var ([#2215](https://github.com/googleapis/python-bigquery-dataframes/issues/2215)) ([ef5e83a](https://github.com/googleapis/python-bigquery-dataframes/commit/ef5e83acedf005cbe1e6ad174bec523ac50517d7)) -* Support INFORMATION_SCHEMA views in `read_gbq` ([#1895](https://github.com/googleapis/python-bigquery-dataframes/issues/1895)) ([d97cafc](https://github.com/googleapis/python-bigquery-dataframes/commit/d97cafcb5921fca2351b18011b0e54e2631cc53d)) -* Support some python standard lib callables in apply/combine ([#2187](https://github.com/googleapis/python-bigquery-dataframes/issues/2187)) ([86a2756](https://github.com/googleapis/python-bigquery-dataframes/commit/86a27564b48b854a32b3d11cd2105aa0fa496279)) - - -### Bug Fixes - -* Correct connection normalization in blob system tests ([#2222](https://github.com/googleapis/python-bigquery-dataframes/issues/2222)) ([a0e1e50](https://github.com/googleapis/python-bigquery-dataframes/commit/a0e1e50e47c758bdceb54d04180ed36b35cf2e35)) -* Improve error handling in blob operations ([#2194](https://github.com/googleapis/python-bigquery-dataframes/issues/2194)) ([d410046](https://github.com/googleapis/python-bigquery-dataframes/commit/d4100466612df0523d01ed01ca1e115dabd6ef45)) -* Resolve AttributeError in TableWidget and improve initialization ([#1937](https://github.com/googleapis/python-bigquery-dataframes/issues/1937)) ([4c4c9b1](https://github.com/googleapis/python-bigquery-dataframes/commit/4c4c9b14657b7cda1940ef39e7d4db20a9ff5308)) - - -### Documentation - -* Update bq_dataframes_llm_output_schema.ipynb ([#2004](https://github.com/googleapis/python-bigquery-dataframes/issues/2004)) ([316ba9f](https://github.com/googleapis/python-bigquery-dataframes/commit/316ba9f557d792117d5a7845d7567498f78dd513)) - -## [2.27.0](https://github.com/googleapis/python-bigquery-dataframes/compare/v2.26.0...v2.27.0) (2025-10-24) - - -### Features - -* Add __abs__ to dataframe ([#2186](https://github.com/googleapis/python-bigquery-dataframes/issues/2186)) ([c331dfe](https://github.com/googleapis/python-bigquery-dataframes/commit/c331dfed59174962fbdc8ace175dd00fcc3d5d50)) -* Add df.groupby().corr()/cov() support ([#2190](https://github.com/googleapis/python-bigquery-dataframes/issues/2190)) ([ccd7c07](https://github.com/googleapis/python-bigquery-dataframes/commit/ccd7c0774a65d09e6cf31d2b62d0bc64bd7c4248)) -* Add str accessor to index ([#2179](https://github.com/googleapis/python-bigquery-dataframes/issues/2179)) ([cd87ce0](https://github.com/googleapis/python-bigquery-dataframes/commit/cd87ce0d504747f44d1b5a55f869a2e0fca6df17)) -* Add support for `np.isnan` and `np.isfinite` ufuncs ([#2188](https://github.com/googleapis/python-bigquery-dataframes/issues/2188)) ([68723bc](https://github.com/googleapis/python-bigquery-dataframes/commit/68723bc1f08013e43a8b11752f908bf8fd6d51f5)) -* Include local data bytes in the dry run report when available ([#2185](https://github.com/googleapis/python-bigquery-dataframes/issues/2185)) ([ee2c40c](https://github.com/googleapis/python-bigquery-dataframes/commit/ee2c40c6789535e259fb6a9774831d6913d16212)) -* Support len() on Groupby objects ([#2183](https://github.com/googleapis/python-bigquery-dataframes/issues/2183)) ([4191821](https://github.com/googleapis/python-bigquery-dataframes/commit/4191821b0976281a96c8965336ef51f061b0c481)) -* Support pa.json_(pa.string()) in struct/list if available ([#2180](https://github.com/googleapis/python-bigquery-dataframes/issues/2180)) ([5ec3cc0](https://github.com/googleapis/python-bigquery-dataframes/commit/5ec3cc0298c7a6195d5bd12a08d996e7df57fc5f)) - - -### Documentation - -* Update AI operators deprecation notice ([#2182](https://github.com/googleapis/python-bigquery-dataframes/issues/2182)) ([2c50310](https://github.com/googleapis/python-bigquery-dataframes/commit/2c503107e17c59232b14b0d7bc40c350bb087d6f)) - -## [2.26.0](https://github.com/googleapis/python-bigquery-dataframes/compare/v2.25.0...v2.26.0) (2025-10-17) - - -### ⚠ BREAKING CHANGES - -* turn Series.struct.dtypes into a property to match pandas (https://github.com/googleapis/python-bigquery-dataframes/pull/2169) - -### Features - -* Add df.sort_index(axis=1) ([#2173](https://github.com/googleapis/python-bigquery-dataframes/issues/2173)) ([ebf95e3](https://github.com/googleapis/python-bigquery-dataframes/commit/ebf95e3ef77822650f2e190df7b868011174d412)) -* Enhanced multimodal error handling with verbose mode for blob image functions ([#2024](https://github.com/googleapis/python-bigquery-dataframes/issues/2024)) ([f9e28fe](https://github.com/googleapis/python-bigquery-dataframes/commit/f9e28fe3f883cc4d486178fe241bc8b76473700f)) -* Implement cos, sin, and log operations for polars compiler ([#2170](https://github.com/googleapis/python-bigquery-dataframes/issues/2170)) ([5613e44](https://github.com/googleapis/python-bigquery-dataframes/commit/5613e4454f198691209ec28e58ce652104ac2de4)) -* Make `all` and `any` compatible with integer columns on Polars session ([#2154](https://github.com/googleapis/python-bigquery-dataframes/issues/2154)) ([6353d6e](https://github.com/googleapis/python-bigquery-dataframes/commit/6353d6ecad5139551ef68376c08f8749dd440014)) - - -### Bug Fixes - -* `blob.display()` shows <NA> for null rows ([#2158](https://github.com/googleapis/python-bigquery-dataframes/issues/2158)) ([ddb4df0](https://github.com/googleapis/python-bigquery-dataframes/commit/ddb4df0dd991bef051e2a365c5cacf502803014d)) -* Turn Series.struct.dtypes into a property to match pandas (https://github.com/googleapis/python-bigquery-dataframes/pull/2169) ([62f7e9f](https://github.com/googleapis/python-bigquery-dataframes/commit/62f7e9f38f26b6eb549219a4cbf2c9b9023c9c35)) - - -### Documentation - -* Clarify that only NULL values are handled by fillna/isna, not NaN ([#2176](https://github.com/googleapis/python-bigquery-dataframes/issues/2176)) ([8f27e73](https://github.com/googleapis/python-bigquery-dataframes/commit/8f27e737fc78a182238090025d09479fac90b326)) -* Remove import bigframes.pandas as bpd boilerplate from many samples ([#2147](https://github.com/googleapis/python-bigquery-dataframes/issues/2147)) ([1a01ab9](https://github.com/googleapis/python-bigquery-dataframes/commit/1a01ab97f103361f489f37b0af8c4b4d7806707c)) - -## [2.25.0](https://github.com/googleapis/python-bigquery-dataframes/compare/v2.24.0...v2.25.0) (2025-10-13) - - -### Features - -* Add barh, pie plot types ([#2146](https://github.com/googleapis/python-bigquery-dataframes/issues/2146)) ([5cc3c5b](https://github.com/googleapis/python-bigquery-dataframes/commit/5cc3c5b1391a7dfa062b1d77f001726b013f6337)) -* Add Index.__eq__ for consts, aligned objects ([#2141](https://github.com/googleapis/python-bigquery-dataframes/issues/2141)) ([8514200](https://github.com/googleapis/python-bigquery-dataframes/commit/85142008ec895fa078d192bbab942d0257f70df3)) -* Add output_schema parameter to ai.generate() ([#2139](https://github.com/googleapis/python-bigquery-dataframes/issues/2139)) ([ef0b0b7](https://github.com/googleapis/python-bigquery-dataframes/commit/ef0b0b73843da2a93baf08e4cd5457fbb590b89c)) -* Create session-scoped `cut`, `DataFrame`, `MultiIndex`, `Index`, `Series`, `to_datetime`, and `to_timedelta` methods ([#2157](https://github.com/googleapis/python-bigquery-dataframes/issues/2157)) ([5e1e809](https://github.com/googleapis/python-bigquery-dataframes/commit/5e1e8098ecf212c91d73fa80d722d1cb3e46668b)) -* Replace ML.GENERATE_TEXT with AI.GENERATE for audio transcription ([#2151](https://github.com/googleapis/python-bigquery-dataframes/issues/2151)) ([a410d0a](https://github.com/googleapis/python-bigquery-dataframes/commit/a410d0ae43ef3b053b650804156eda0b1f569da9)) -* Support string literal inputs for AI functions ([#2152](https://github.com/googleapis/python-bigquery-dataframes/issues/2152)) ([7600001](https://github.com/googleapis/python-bigquery-dataframes/commit/760000122dc190ac8a3303234cf4cbee1bbb9493)) - - -### Bug Fixes - -* Address typo in error message ([#2142](https://github.com/googleapis/python-bigquery-dataframes/issues/2142)) ([cdf2dd5](https://github.com/googleapis/python-bigquery-dataframes/commit/cdf2dd55a0c03da50ab92de09788cafac0abf6f6)) -* Avoid possible circular imports in global session ([#2115](https://github.com/googleapis/python-bigquery-dataframes/issues/2115)) ([095c0b8](https://github.com/googleapis/python-bigquery-dataframes/commit/095c0b85a25a2e51087880909597cc62a0341c93)) -* Fix too many cluster columns requested by caching ([#2155](https://github.com/googleapis/python-bigquery-dataframes/issues/2155)) ([35c1c33](https://github.com/googleapis/python-bigquery-dataframes/commit/35c1c33b85d1b92e402aab73677df3ffe43a51b4)) -* Show progress even in job optional queries ([#2119](https://github.com/googleapis/python-bigquery-dataframes/issues/2119)) ([1f48d3a](https://github.com/googleapis/python-bigquery-dataframes/commit/1f48d3a62e7e6dac4acb39e911daf766b8e2fe62)) -* Yield row count from read session if otherwise unknown ([#2148](https://github.com/googleapis/python-bigquery-dataframes/issues/2148)) ([8997d4d](https://github.com/googleapis/python-bigquery-dataframes/commit/8997d4d7d9965e473195f98c550c80657035b7e1)) - - -### Documentation - -* Add a brief intro notebook for bbq AI functions ([#2150](https://github.com/googleapis/python-bigquery-dataframes/issues/2150)) ([1f434fb](https://github.com/googleapis/python-bigquery-dataframes/commit/1f434fb5c7c00601654b3ab19c6ad7fceb258bd6)) -* Fix ai function related docs ([#2149](https://github.com/googleapis/python-bigquery-dataframes/issues/2149)) ([93a0749](https://github.com/googleapis/python-bigquery-dataframes/commit/93a0749392b84f27162654fe5ea5baa329a23f99)) -* Remove progress bar from getting started template ([#2143](https://github.com/googleapis/python-bigquery-dataframes/issues/2143)) ([d13abad](https://github.com/googleapis/python-bigquery-dataframes/commit/d13abadbcd68d03997e8dc11bb7a2b14bbd57fcc)) - -## [2.24.0](https://github.com/googleapis/python-bigquery-dataframes/compare/v2.23.0...v2.24.0) (2025-10-07) - - -### Features - -* Add ai.classify() to bigframes.bigquery package ([#2137](https://github.com/googleapis/python-bigquery-dataframes/issues/2137)) ([56e5033](https://github.com/googleapis/python-bigquery-dataframes/commit/56e50331d198b7f517f85695c208f893ab9389d2)) -* Add ai.generate() to bigframes.bigquery module ([#2128](https://github.com/googleapis/python-bigquery-dataframes/issues/2128)) ([3810452](https://github.com/googleapis/python-bigquery-dataframes/commit/3810452f16d8d6c9d3eb9075f1537177d98b4725)) -* Add ai.if_() and ai.score() to bigframes.bigquery package ([#2132](https://github.com/googleapis/python-bigquery-dataframes/issues/2132)) ([32502f4](https://github.com/googleapis/python-bigquery-dataframes/commit/32502f4195306d262788f39d1ab4206fc84ae50e)) - - -### Bug Fixes - -* Fix internal type errors with temporal accessors ([#2125](https://github.com/googleapis/python-bigquery-dataframes/issues/2125)) ([c390da1](https://github.com/googleapis/python-bigquery-dataframes/commit/c390da11b7c2aa710bc2fbc692efb9f06059e4c4)) -* Fix row count local execution bug ([#2133](https://github.com/googleapis/python-bigquery-dataframes/issues/2133)) ([ece0762](https://github.com/googleapis/python-bigquery-dataframes/commit/ece07623e354a1dde2bd37020349e13f682e863f)) -* Join on, how args are now positional ([#2140](https://github.com/googleapis/python-bigquery-dataframes/issues/2140)) ([b711815](https://github.com/googleapis/python-bigquery-dataframes/commit/b7118152bfecc6ecf67aa4df23ec3f0a2b08aa30)) -* Only show JSON dtype warning when accessing dtypes directly ([#2136](https://github.com/googleapis/python-bigquery-dataframes/issues/2136)) ([eca22ee](https://github.com/googleapis/python-bigquery-dataframes/commit/eca22ee3104104cea96189391e527cad09bd7509)) -* Remove noisy AmbiguousWindowWarning from partial ordering mode ([#2129](https://github.com/googleapis/python-bigquery-dataframes/issues/2129)) ([4607f86](https://github.com/googleapis/python-bigquery-dataframes/commit/4607f86ebd77b916aafc37f69725b676e203b332)) - - -### Performance Improvements - -* Scale read stream workers to cpu count ([#2135](https://github.com/googleapis/python-bigquery-dataframes/issues/2135)) ([67e46cd](https://github.com/googleapis/python-bigquery-dataframes/commit/67e46cd47933b84b55808003ed344b559e47c498)) - -## [2.23.0](https://github.com/googleapis/python-bigquery-dataframes/compare/v2.22.0...v2.23.0) (2025-09-29) - - -### Features - -* Add ai.generate_double to bigframes.bigquery package ([#2111](https://github.com/googleapis/python-bigquery-dataframes/issues/2111)) ([6b8154c](https://github.com/googleapis/python-bigquery-dataframes/commit/6b8154c578bb1a276e9cf8fe494d91f8cd6260f2)) - - -### Bug Fixes - -* Prevent invalid syntax for no-op .replace ops ([#2112](https://github.com/googleapis/python-bigquery-dataframes/issues/2112)) ([c311876](https://github.com/googleapis/python-bigquery-dataframes/commit/c311876b2adbc0b66ae5e463c6e56466c6a6a495)) - - -### Documentation - -* Add timedelta notebook sample ([#2124](https://github.com/googleapis/python-bigquery-dataframes/issues/2124)) ([d1a9888](https://github.com/googleapis/python-bigquery-dataframes/commit/d1a9888a2b47de6aca5dddc94d0c8f280344b58a)) - -## [2.22.0](https://github.com/googleapis/python-bigquery-dataframes/compare/v2.21.0...v2.22.0) (2025-09-25) - - -### Features - -* Add `GroupBy.__iter__` ([#1394](https://github.com/googleapis/python-bigquery-dataframes/issues/1394)) ([c56a78c](https://github.com/googleapis/python-bigquery-dataframes/commit/c56a78cd509a535d4998d5b9a99ec3ecd334b883)) -* Add ai.generate_int to bigframes.bigquery package ([#2109](https://github.com/googleapis/python-bigquery-dataframes/issues/2109)) ([af6b862](https://github.com/googleapis/python-bigquery-dataframes/commit/af6b862de5c3921684210ec169338815f45b19dd)) -* Add Groupby.describe() ([#2088](https://github.com/googleapis/python-bigquery-dataframes/issues/2088)) ([328a765](https://github.com/googleapis/python-bigquery-dataframes/commit/328a765e746138806a021bea22475e8c03512aeb)) -* Implement `Index.to_list()` ([#2106](https://github.com/googleapis/python-bigquery-dataframes/issues/2106)) ([60056ca](https://github.com/googleapis/python-bigquery-dataframes/commit/60056ca06511f99092647fe55fc02eeab486b4ca)) -* Implement inplace parameter for `DataFrame.drop` ([#2105](https://github.com/googleapis/python-bigquery-dataframes/issues/2105)) ([3487f13](https://github.com/googleapis/python-bigquery-dataframes/commit/3487f13d12e34999b385c2e11551b5e27bfbf4ff)) -* Support callable for series map method ([#2100](https://github.com/googleapis/python-bigquery-dataframes/issues/2100)) ([ac25618](https://github.com/googleapis/python-bigquery-dataframes/commit/ac25618feed2da11fe4fb85058d498d262c085c0)) -* Support df.info() with null index ([#2094](https://github.com/googleapis/python-bigquery-dataframes/issues/2094)) ([fb81eea](https://github.com/googleapis/python-bigquery-dataframes/commit/fb81eeaf13af059f32cb38e7f117fb3504243d51)) - - -### Bug Fixes - -* Avoid ibis fillna warning in compiler ([#2113](https://github.com/googleapis/python-bigquery-dataframes/issues/2113)) ([7ef667b](https://github.com/googleapis/python-bigquery-dataframes/commit/7ef667b0f46f13bcc8ad4f2ed8f81278132b5aec)) -* Negative start and stop parameter values in Series.str.slice() ([#2104](https://github.com/googleapis/python-bigquery-dataframes/issues/2104)) ([f57a348](https://github.com/googleapis/python-bigquery-dataframes/commit/f57a348f1935a4e2bb14c501bb4c47cd552d102a)) -* Throw type error for incomparable join keys ([#2098](https://github.com/googleapis/python-bigquery-dataframes/issues/2098)) ([9dc9695](https://github.com/googleapis/python-bigquery-dataframes/commit/9dc96959a84b751d18b290129c2926df6e50b3f5)) -* Transformers with non-standard column names throw errors ([#2089](https://github.com/googleapis/python-bigquery-dataframes/issues/2089)) ([a2daa3f](https://github.com/googleapis/python-bigquery-dataframes/commit/a2daa3fffe6743327edb9f4c74db93198bd12f8e)) - -## [2.21.0](https://github.com/googleapis/python-bigquery-dataframes/compare/v2.20.0...v2.21.0) (2025-09-17) - - -### Features - -* Add bigframes.bigquery.to_json ([#2078](https://github.com/googleapis/python-bigquery-dataframes/issues/2078)) ([0fc795a](https://github.com/googleapis/python-bigquery-dataframes/commit/0fc795a9fb56f469b62603462c3f0f56f52bfe04)) -* Support average='binary' in precision_score() ([#2080](https://github.com/googleapis/python-bigquery-dataframes/issues/2080)) ([920f381](https://github.com/googleapis/python-bigquery-dataframes/commit/920f381aec7e0a0b986886cdbc333e86335c6d7d)) -* Support pandas series in ai.generate_bool ([#2086](https://github.com/googleapis/python-bigquery-dataframes/issues/2086)) ([a3de53f](https://github.com/googleapis/python-bigquery-dataframes/commit/a3de53f68b2a24f4ed85a474dfaff9b59570a2f1)) - - -### Bug Fixes - -* Allow bigframes.options.bigquery.credentials to be `None` ([#2092](https://github.com/googleapis/python-bigquery-dataframes/issues/2092)) ([78f4001](https://github.com/googleapis/python-bigquery-dataframes/commit/78f4001e8fcfc77fc82f3893d58e0d04c0f6d3db)) - -## [2.20.0](https://github.com/googleapis/python-bigquery-dataframes/compare/v2.19.0...v2.20.0) (2025-09-16) - - -### Features - -* Add `__dataframe__` interchange support ([#2063](https://github.com/googleapis/python-bigquery-dataframes/issues/2063)) ([3b46a0d](https://github.com/googleapis/python-bigquery-dataframes/commit/3b46a0d91eb379c61ced45ae0b25339281326c3d)) -* Add ai_generate_bool to the bigframes.bigquery package ([#2060](https://github.com/googleapis/python-bigquery-dataframes/issues/2060)) ([70d6562](https://github.com/googleapis/python-bigquery-dataframes/commit/70d6562df64b2aef4ff0024df6f57702d52dcaf8)) -* Add bigframes.bigquery.to_json_string ([#2076](https://github.com/googleapis/python-bigquery-dataframes/issues/2076)) ([41e8f33](https://github.com/googleapis/python-bigquery-dataframes/commit/41e8f33ceb46a7c2a75d1c59a4a3f2f9413d281d)) -* Add rank(pct=True) support ([#2084](https://github.com/googleapis/python-bigquery-dataframes/issues/2084)) ([c1e871d](https://github.com/googleapis/python-bigquery-dataframes/commit/c1e871d9327bf6c920d17e1476fed3088d506f5f)) -* Add StreamingDataFrame.to_bigtable and .to_pubsub start_timestamp parameter ([#2066](https://github.com/googleapis/python-bigquery-dataframes/issues/2066)) ([a63cbae](https://github.com/googleapis/python-bigquery-dataframes/commit/a63cbae24ff2dc191f0a53dced885bc95f38ec96)) -* Can call agg with some callables ([#2055](https://github.com/googleapis/python-bigquery-dataframes/issues/2055)) ([17a1ed9](https://github.com/googleapis/python-bigquery-dataframes/commit/17a1ed99ec8c6d3215d3431848814d5d458d4ff1)) -* Support astype to json ([#2073](https://github.com/googleapis/python-bigquery-dataframes/issues/2073)) ([6bd6738](https://github.com/googleapis/python-bigquery-dataframes/commit/6bd67386341de7a92ada948381702430c399406e)) -* Support pandas.Index as key for DataFrame.__setitem__() ([#2062](https://github.com/googleapis/python-bigquery-dataframes/issues/2062)) ([b3cf824](https://github.com/googleapis/python-bigquery-dataframes/commit/b3cf8248e3b8ea76637ded64fb12028d439448d1)) -* Support pd.cut() for array-like type ([#2064](https://github.com/googleapis/python-bigquery-dataframes/issues/2064)) ([21eb213](https://github.com/googleapis/python-bigquery-dataframes/commit/21eb213c5f0e0f696f2d1ca1f1263678d791cf7c)) -* Support to cast struct to json ([#2067](https://github.com/googleapis/python-bigquery-dataframes/issues/2067)) ([b0ff718](https://github.com/googleapis/python-bigquery-dataframes/commit/b0ff718a04fadda33cfa3613b1d02822cde34bc2)) - - -### Bug Fixes - -* Deflake ai_gen_bool multimodel test ([#2085](https://github.com/googleapis/python-bigquery-dataframes/issues/2085)) ([566a37a](https://github.com/googleapis/python-bigquery-dataframes/commit/566a37a30ad5677aef0c5f79bdd46bca2139cc1e)) -* Do not scroll page selector in anywidget `repr_mode` ([#2082](https://github.com/googleapis/python-bigquery-dataframes/issues/2082)) ([5ce5d63](https://github.com/googleapis/python-bigquery-dataframes/commit/5ce5d63fcb51bfb3df2769108b7486287896ccb9)) -* Fix the potential invalid VPC egress configuration ([#2068](https://github.com/googleapis/python-bigquery-dataframes/issues/2068)) ([cce4966](https://github.com/googleapis/python-bigquery-dataframes/commit/cce496605385f2ac7ab0becc0773800ed5901aa5)) -* Return a DataFrame containing query stats for all non-SELECT statements ([#2071](https://github.com/googleapis/python-bigquery-dataframes/issues/2071)) ([a52b913](https://github.com/googleapis/python-bigquery-dataframes/commit/a52b913d9d8794b4b959ea54744a38d9f2f174e7)) -* Use the remote and managed functions for bigframes results ([#2079](https://github.com/googleapis/python-bigquery-dataframes/issues/2079)) ([49b91e8](https://github.com/googleapis/python-bigquery-dataframes/commit/49b91e878de651de23649756259ee35709e3f5a8)) - - -### Performance Improvements - -* Avoid re-authenticating if credentials have already been fetched ([#2058](https://github.com/googleapis/python-bigquery-dataframes/issues/2058)) ([913de1b](https://github.com/googleapis/python-bigquery-dataframes/commit/913de1b31f3bb0b306846fddae5dcaff6be3cec4)) -* Improve apply axis=1 performance ([#2077](https://github.com/googleapis/python-bigquery-dataframes/issues/2077)) ([12e4380](https://github.com/googleapis/python-bigquery-dataframes/commit/12e438051134577e911c1a6ce9d5a5885a0b45ad)) - -## [2.19.0](https://github.com/googleapis/python-bigquery-dataframes/compare/v2.18.0...v2.19.0) (2025-09-09) - - -### Features - -* Add str.join method ([#2054](https://github.com/googleapis/python-bigquery-dataframes/issues/2054)) ([8804ada](https://github.com/googleapis/python-bigquery-dataframes/commit/8804adaf8ba23fdcad6e42a7bf034bd0a11c890f)) -* Support display.max_colwidth option ([#2053](https://github.com/googleapis/python-bigquery-dataframes/issues/2053)) ([5229e07](https://github.com/googleapis/python-bigquery-dataframes/commit/5229e07b4535c01b0cdbd731455ff225a373b5c8)) -* Support VPC egress setting in remote function ([#2059](https://github.com/googleapis/python-bigquery-dataframes/issues/2059)) ([5df779d](https://github.com/googleapis/python-bigquery-dataframes/commit/5df779d4f421d3ba777cfd928d99ca2e8a3f79ad)) - - -### Bug Fixes - -* Fix issue mishandling chunked array while loading data ([#2051](https://github.com/googleapis/python-bigquery-dataframes/issues/2051)) ([873d0ee](https://github.com/googleapis/python-bigquery-dataframes/commit/873d0eee474ed34f1d5164c37383f2737dbec4db)) -* Remove warning for slot_millis_sum ([#2047](https://github.com/googleapis/python-bigquery-dataframes/issues/2047)) ([425a691](https://github.com/googleapis/python-bigquery-dataframes/commit/425a6917d5442eeb4df486c6eed1fd136bbcedfb)) - -## [2.18.0](https://github.com/googleapis/python-bigquery-dataframes/compare/v2.17.0...v2.18.0) (2025-09-03) - - -### ⚠ BREAKING CHANGES - -* add `allow_large_results` option to `read_gbq_query`, aligning with `bpd.options.compute.allow_large_results` option ([#1935](https://github.com/googleapis/python-bigquery-dataframes/issues/1935)) - -### Features - -* Add `allow_large_results` option to `read_gbq_query`, aligning with `bpd.options.compute.allow_large_results` option ([#1935](https://github.com/googleapis/python-bigquery-dataframes/issues/1935)) ([a7963fe](https://github.com/googleapis/python-bigquery-dataframes/commit/a7963fe57a0e141debf726f0bc7b0e953ebe9634)) -* Add parameter shuffle for ml.model_selection.train_test_split ([#2030](https://github.com/googleapis/python-bigquery-dataframes/issues/2030)) ([2c72c56](https://github.com/googleapis/python-bigquery-dataframes/commit/2c72c56fb5893eb01d5aec6273d11945c9c532c5)) -* Can pivot unordered, unindexed dataframe ([#2040](https://github.com/googleapis/python-bigquery-dataframes/issues/2040)) ([1a0f710](https://github.com/googleapis/python-bigquery-dataframes/commit/1a0f710ac11418fd71ab3373f3f6002fa581b180)) -* Local date accessor execution support ([#2034](https://github.com/googleapis/python-bigquery-dataframes/issues/2034)) ([7ac6fe1](https://github.com/googleapis/python-bigquery-dataframes/commit/7ac6fe16f7f2c09d2efac6ab813ec841c21baef8)) -* Support args in dataframe apply method ([#2026](https://github.com/googleapis/python-bigquery-dataframes/issues/2026)) ([164c481](https://github.com/googleapis/python-bigquery-dataframes/commit/164c4818bc4ff2990dca16b9f22a798f47e0a60b)) -* Support args in series apply method ([#2013](https://github.com/googleapis/python-bigquery-dataframes/issues/2013)) ([d9d725c](https://github.com/googleapis/python-bigquery-dataframes/commit/d9d725cfbc3dca9e66b460cae4084e25162f2acf)) -* Support callable for dataframe mask method ([#2020](https://github.com/googleapis/python-bigquery-dataframes/issues/2020)) ([9d4504b](https://github.com/googleapis/python-bigquery-dataframes/commit/9d4504be310d38b63515d67c0f60d2e48e68c7b5)) -* Support multi-column assignment for DataFrame ([#2028](https://github.com/googleapis/python-bigquery-dataframes/issues/2028)) ([ba0d23b](https://github.com/googleapis/python-bigquery-dataframes/commit/ba0d23b59c44ba5a46ace8182ad0e0cfc703b3ab)) -* Support string matching in local executor ([#2032](https://github.com/googleapis/python-bigquery-dataframes/issues/2032)) ([c0b54f0](https://github.com/googleapis/python-bigquery-dataframes/commit/c0b54f03849ee3115413670e690e68f3ef10f2ec)) - - -### Bug Fixes - -* Fix scalar op lowering tree walk ([#2029](https://github.com/googleapis/python-bigquery-dataframes/issues/2029)) ([935af10](https://github.com/googleapis/python-bigquery-dataframes/commit/935af107ef98837fb2b81d72185d0b6a9e09fbcf)) -* Read_csv fails when check file size for wildcard gcs files ([#2019](https://github.com/googleapis/python-bigquery-dataframes/issues/2019)) ([b0d620b](https://github.com/googleapis/python-bigquery-dataframes/commit/b0d620bbe8227189bbdc2ba5a913b03c70575296)) -* Resolve the validation issue for other arg in dataframe where method ([#2042](https://github.com/googleapis/python-bigquery-dataframes/issues/2042)) ([8689199](https://github.com/googleapis/python-bigquery-dataframes/commit/8689199aa82212ed300fff592097093812e0290e)) - - -### Performance Improvements - -* Improve axis=1 aggregation performance ([#2036](https://github.com/googleapis/python-bigquery-dataframes/issues/2036)) ([fbb2094](https://github.com/googleapis/python-bigquery-dataframes/commit/fbb209468297a8057d9d49c40e425c3bfdeb92bd)) -* Improve iter_nodes_topo performance using Kahn's algorithm ([#2038](https://github.com/googleapis/python-bigquery-dataframes/issues/2038)) ([3961637](https://github.com/googleapis/python-bigquery-dataframes/commit/39616374bba424996ebeb9a12096bfaf22660b44)) - -## [2.17.0](https://github.com/googleapis/python-bigquery-dataframes/compare/v2.16.0...v2.17.0) (2025-08-22) - - -### Features - -* Add isin local execution impl ([#1993](https://github.com/googleapis/python-bigquery-dataframes/issues/1993)) ([26df6e6](https://github.com/googleapis/python-bigquery-dataframes/commit/26df6e691bb27ed09322a81214faedbf3639b32e)) -* Add reset_index names, col_level, col_fill, allow_duplicates args ([#2017](https://github.com/googleapis/python-bigquery-dataframes/issues/2017)) ([c02a1b6](https://github.com/googleapis/python-bigquery-dataframes/commit/c02a1b67d27758815430bb8006ac3a72cea55a89)) -* Support callable for series mask method ([#2014](https://github.com/googleapis/python-bigquery-dataframes/issues/2014)) ([5ac32eb](https://github.com/googleapis/python-bigquery-dataframes/commit/5ac32ebe17cfda447870859f5dd344b082b4d3d0)) - -## [2.16.0](https://github.com/googleapis/python-bigquery-dataframes/compare/v2.15.0...v2.16.0) (2025-08-20) - - -### Features - -* Add `bigframes.pandas.options.display.precision` option ([#1979](https://github.com/googleapis/python-bigquery-dataframes/issues/1979)) ([15e6175](https://github.com/googleapis/python-bigquery-dataframes/commit/15e6175ec0aeb1b7b02d0bba9e8e1e018bd11c31)) -* Add level, inplace params to reset_index ([#1988](https://github.com/googleapis/python-bigquery-dataframes/issues/1988)) ([3446950](https://github.com/googleapis/python-bigquery-dataframes/commit/34469504b79a082d3380f9f25c597483aef2068a)) -* Add ML code samples from dbt blog post ([#1978](https://github.com/googleapis/python-bigquery-dataframes/issues/1978)) ([ebaa244](https://github.com/googleapis/python-bigquery-dataframes/commit/ebaa244a9eb7b87f7f9fd9c3bebe5c7db24cd013)) -* Add where, coalesce, fillna, casewhen, invert local impl ([#1976](https://github.com/googleapis/python-bigquery-dataframes/issues/1976)) ([f7f686c](https://github.com/googleapis/python-bigquery-dataframes/commit/f7f686cf85ab7e265d9c07ebc7f0cd59babc5357)) -* Adjust anywidget CSS to prevent overflow ([#1981](https://github.com/googleapis/python-bigquery-dataframes/issues/1981)) ([204f083](https://github.com/googleapis/python-bigquery-dataframes/commit/204f083a2f00fcc9fd1500dcd7a738eda3904d2f)) -* Format page number in table widget ([#1992](https://github.com/googleapis/python-bigquery-dataframes/issues/1992)) ([e83836e](https://github.com/googleapis/python-bigquery-dataframes/commit/e83836e8e1357f009f3f95666f1661bdbe0d3751)) -* Or, And, Xor can execute locally ([#1994](https://github.com/googleapis/python-bigquery-dataframes/issues/1994)) ([59c52a5](https://github.com/googleapis/python-bigquery-dataframes/commit/59c52a55ebea697855eb4c70529e226cc077141f)) -* Support callable bigframes function for dataframe where ([#1990](https://github.com/googleapis/python-bigquery-dataframes/issues/1990)) ([44c1ec4](https://github.com/googleapis/python-bigquery-dataframes/commit/44c1ec48cc4db1c4c9c15ec1fab43d4ef0758e56)) -* Support callable for series where method ([#2005](https://github.com/googleapis/python-bigquery-dataframes/issues/2005)) ([768b82a](https://github.com/googleapis/python-bigquery-dataframes/commit/768b82af96a5dd0c434edcb171036eb42cfb9b41)) -* When using `repr_mode = "anywidget"`, numeric values align right ([15e6175](https://github.com/googleapis/python-bigquery-dataframes/commit/15e6175ec0aeb1b7b02d0bba9e8e1e018bd11c31)) - - -### Bug Fixes - -* Address the packages issue for bigframes function ([#1991](https://github.com/googleapis/python-bigquery-dataframes/issues/1991)) ([68f1d22](https://github.com/googleapis/python-bigquery-dataframes/commit/68f1d22d5ed8457a5cabc7751ed1d178063dd63e)) -* Correct pypdf dependency specifier for remote PDF functions ([#1980](https://github.com/googleapis/python-bigquery-dataframes/issues/1980)) ([0bd5e1b](https://github.com/googleapis/python-bigquery-dataframes/commit/0bd5e1b3c004124d2100c3fbec2fbe1e965d1e96)) -* Enable default retries in calls to BQ Storage Read API ([#1985](https://github.com/googleapis/python-bigquery-dataframes/issues/1985)) ([f25d7bd](https://github.com/googleapis/python-bigquery-dataframes/commit/f25d7bd30800dffa65b6c31b0b7ac711a13d790f)) -* Fix the copyright year in dbt sample files ([#1996](https://github.com/googleapis/python-bigquery-dataframes/issues/1996)) ([fad5722](https://github.com/googleapis/python-bigquery-dataframes/commit/fad57223d129f0c95d0c6a066179bb66880edd06)) - - -### Performance Improvements - -* Faster session startup by defering anon dataset fetch ([#1982](https://github.com/googleapis/python-bigquery-dataframes/issues/1982)) ([2720c4c](https://github.com/googleapis/python-bigquery-dataframes/commit/2720c4cf070bf57a0930d7623bfc41d89cc053ee)) - - -### Documentation - -* Add examples of running bigframes in kaggle ([#2002](https://github.com/googleapis/python-bigquery-dataframes/issues/2002)) ([7d89d76](https://github.com/googleapis/python-bigquery-dataframes/commit/7d89d76976595b75cb0105fbe7b4f7ca2fdf49f2)) -* Remove preview warning from partial ordering mode sample notebook ([#1986](https://github.com/googleapis/python-bigquery-dataframes/issues/1986)) ([132e0ed](https://github.com/googleapis/python-bigquery-dataframes/commit/132e0edfe9f96c15753649d77fcb6edd0b0708a3)) - -## [2.15.0](https://github.com/googleapis/python-bigquery-dataframes/compare/v2.14.0...v2.15.0) (2025-08-11) - - -### Features - -* Add `st_buffer`, `st_centroid`, and `st_convexhull` and their corresponding GeoSeries methods ([#1963](https://github.com/googleapis/python-bigquery-dataframes/issues/1963)) ([c4c7fa5](https://github.com/googleapis/python-bigquery-dataframes/commit/c4c7fa578e135e7f0e31ad3063db379514957acc)) -* Add first, last support to GroupBy ([#1969](https://github.com/googleapis/python-bigquery-dataframes/issues/1969)) ([41dda88](https://github.com/googleapis/python-bigquery-dataframes/commit/41dda889860c0ed8ca2eab81b34a9d71372c69f7)) -* Add value_counts to GroupBy classes ([#1974](https://github.com/googleapis/python-bigquery-dataframes/issues/1974)) ([82175a4](https://github.com/googleapis/python-bigquery-dataframes/commit/82175a4d0fa41d8aee11efdf8778a21bb70b1c0f)) -* Allow callable as a conditional or replacement input in DataFrame.where ([#1971](https://github.com/googleapis/python-bigquery-dataframes/issues/1971)) ([a8d57d2](https://github.com/googleapis/python-bigquery-dataframes/commit/a8d57d2f7075158eff69ec65a14c232756ab72a6)) -* Can cast locally in hybrid engine ([#1944](https://github.com/googleapis/python-bigquery-dataframes/issues/1944)) ([d9bc4a5](https://github.com/googleapis/python-bigquery-dataframes/commit/d9bc4a5940e9930d5e3c3bfffdadd2f91f96b53b)) -* Df.join lsuffix and rsuffix support ([#1857](https://github.com/googleapis/python-bigquery-dataframes/issues/1857)) ([26515c3](https://github.com/googleapis/python-bigquery-dataframes/commit/26515c34c4f0a5e4602d2f59bf229d41e0fc9196)) - - -### Bug Fixes - -* Add warnings for duplicated or conflicting type hints in bigfram… ([#1956](https://github.com/googleapis/python-bigquery-dataframes/issues/1956)) ([d38e42c](https://github.com/googleapis/python-bigquery-dataframes/commit/d38e42ce689e65f57223e9a8b14c4262cba08966)) -* Make `remote_function` more robust when there are `create_function` retries ([#1973](https://github.com/googleapis/python-bigquery-dataframes/issues/1973)) ([cd954ac](https://github.com/googleapis/python-bigquery-dataframes/commit/cd954ac07ad5e5820a20b941d3c6cab7cfcc1f29)) -* Make ExecutionMetrics stats tracking more robust to missing stats ([#1977](https://github.com/googleapis/python-bigquery-dataframes/issues/1977)) ([feb3ff4](https://github.com/googleapis/python-bigquery-dataframes/commit/feb3ff4b543eb8acbf6adf335b67a266a1cf4297)) - - -### Performance Improvements - -* Remove an unnecessary extra `dry_run` query from `read_gbq_table` ([#1972](https://github.com/googleapis/python-bigquery-dataframes/issues/1972)) ([d17b711](https://github.com/googleapis/python-bigquery-dataframes/commit/d17b711750d281ef3efd42c160f3784cd60021ae)) - - -### Documentation - -* Divide BQ DataFrames quickstart code cell ([#1975](https://github.com/googleapis/python-bigquery-dataframes/issues/1975)) ([fedb8f2](https://github.com/googleapis/python-bigquery-dataframes/commit/fedb8f23120aa315c7e9dd6f1bf1255ccf1ebc48)) - -## [2.14.0](https://github.com/googleapis/python-bigquery-dataframes/compare/v2.13.0...v2.14.0) (2025-08-05) - - -### Features - -* Dynamic table width for better display across devices (https://github.com/googleapis/python-bigquery-dataframes/issues/1948) ([a6d30ae](https://github.com/googleapis/python-bigquery-dataframes/commit/a6d30ae3f4358925c999c53b558c1ecd3ee03e6c)) ([a6d30ae](https://github.com/googleapis/python-bigquery-dataframes/commit/a6d30ae3f4358925c999c53b558c1ecd3ee03e6c)) -* Retry AI/ML jobs that fail more often ([#1965](https://github.com/googleapis/python-bigquery-dataframes/issues/1965)) ([25bde9f](https://github.com/googleapis/python-bigquery-dataframes/commit/25bde9f9b89112db0efcc119bf29b6d1f3896c33)) -* Support series input in managed function ([#1920](https://github.com/googleapis/python-bigquery-dataframes/issues/1920)) ([62a189f](https://github.com/googleapis/python-bigquery-dataframes/commit/62a189f4d69f6c05fe348a1acd1fbac364fa60b9)) - - -### Bug Fixes - -* Enhance type error messages for bigframes functions ([#1958](https://github.com/googleapis/python-bigquery-dataframes/issues/1958)) ([770918e](https://github.com/googleapis/python-bigquery-dataframes/commit/770918e998bf1fde7a656e8f8a0ff0a8c68509f2)) - - -### Performance Improvements - -* Use promote_offsets for consistent row number generation for index.get_loc ([#1957](https://github.com/googleapis/python-bigquery-dataframes/issues/1957)) ([c67a25a](https://github.com/googleapis/python-bigquery-dataframes/commit/c67a25a879ab2a35ca9053a81c9c85b5660206ae)) - - -### Documentation - -* Add code snippet for storing dataframes to a CSV file ([#1943](https://github.com/googleapis/python-bigquery-dataframes/issues/1943)) ([a511e09](https://github.com/googleapis/python-bigquery-dataframes/commit/a511e09e6924d2e8302af2eb4a602c6b9e5d2d72)) -* Add code snippet for storing dataframes to a CSV file ([#1953](https://github.com/googleapis/python-bigquery-dataframes/issues/1953)) ([a298a02](https://github.com/googleapis/python-bigquery-dataframes/commit/a298a02b451f03ca200fe0756b9a7b57e3d1bf0e)) - -## [2.13.0](https://github.com/googleapis/python-bigquery-dataframes/compare/v2.12.0...v2.13.0) (2025-07-25) - - -### Features - -* _read_gbq_colab creates hybrid session ([#1901](https://github.com/googleapis/python-bigquery-dataframes/issues/1901)) ([31b17b0](https://github.com/googleapis/python-bigquery-dataframes/commit/31b17b01706ccfcee9a2d838c43a9609ec4dc218)) -* Add CSS styling for TableWidget pagination interface ([#1934](https://github.com/googleapis/python-bigquery-dataframes/issues/1934)) ([5b232d7](https://github.com/googleapis/python-bigquery-dataframes/commit/5b232d7e33563196316f5dbb50b28c6be388d440)) -* Add row numbering local pushdown in hybrid execution ([#1932](https://github.com/googleapis/python-bigquery-dataframes/issues/1932)) ([92a2377](https://github.com/googleapis/python-bigquery-dataframes/commit/92a237712aa4ce516b1a44748127b34d7780fff6)) -* Implement Index.get_loc ([#1921](https://github.com/googleapis/python-bigquery-dataframes/issues/1921)) ([bbbcaf3](https://github.com/googleapis/python-bigquery-dataframes/commit/bbbcaf35df113617fd6bb8ae36468cf3f7ab493b)) - - -### Bug Fixes - -* Add license header and correct issues in dbt sample ([#1931](https://github.com/googleapis/python-bigquery-dataframes/issues/1931)) ([ab01b0a](https://github.com/googleapis/python-bigquery-dataframes/commit/ab01b0a236ffc7b667f258e0497105ea5c3d3aab)) - - -### Dependencies - -* Replace `google-cloud-iam` with `grpc-google-iam-v1` ([#1864](https://github.com/googleapis/python-bigquery-dataframes/issues/1864)) ([e5ff8f7](https://github.com/googleapis/python-bigquery-dataframes/commit/e5ff8f7d9fdac3ea47dabcc80a2598d601f39e64)) - -## [2.12.0](https://github.com/googleapis/python-bigquery-dataframes/compare/v2.11.0...v2.12.0) (2025-07-23) - - -### Features - -* Add code samples for dbt bigframes integration ([#1898](https://github.com/googleapis/python-bigquery-dataframes/issues/1898)) ([7e03252](https://github.com/googleapis/python-bigquery-dataframes/commit/7e03252d31e505731db113eb38af77842bf29b9b)) -* Add isin local execution to hybrid engine ([#1915](https://github.com/googleapis/python-bigquery-dataframes/issues/1915)) ([c0cefd3](https://github.com/googleapis/python-bigquery-dataframes/commit/c0cefd36cfd55962b86178d2a612d625ed17f79c)) -* Add ml.metrics.mean_absolute_error method ([#1910](https://github.com/googleapis/python-bigquery-dataframes/issues/1910)) ([15b8449](https://github.com/googleapis/python-bigquery-dataframes/commit/15b8449dc5ad0c8190a5cbf47894436de18c8e88)) -* Allow local arithmetic execution in hybrid engine ([#1906](https://github.com/googleapis/python-bigquery-dataframes/issues/1906)) ([ebdcd02](https://github.com/googleapis/python-bigquery-dataframes/commit/ebdcd0240f0d8edaef3094b3a4e664b4a84d4a25)) -* Provide day_of_year and day_of_week for dt accessor ([#1911](https://github.com/googleapis/python-bigquery-dataframes/issues/1911)) ([40e7638](https://github.com/googleapis/python-bigquery-dataframes/commit/40e76383948a79bde48108f6180fd6ae2b3d0875)) -* Support params `max_batching_rows`, `container_cpu`, and `container_memory` for `udf` ([#1897](https://github.com/googleapis/python-bigquery-dataframes/issues/1897)) ([8baa912](https://github.com/googleapis/python-bigquery-dataframes/commit/8baa9126e595ae682469a6bb462244240699f57f)) -* Support typed pyarrow.Scalar in assignment ([#1930](https://github.com/googleapis/python-bigquery-dataframes/issues/1930)) ([cd28e12](https://github.com/googleapis/python-bigquery-dataframes/commit/cd28e12b3f70a6934a68963a7f25dbd5e3c67335)) - - -### Bug Fixes - -* Correct min field from max() to min() in remote function tests ([#1917](https://github.com/googleapis/python-bigquery-dataframes/issues/1917)) ([d5c54fc](https://github.com/googleapis/python-bigquery-dataframes/commit/d5c54fca32ed75c1aef52c99781db7f8ac7426e1)) -* Resolve location reset issue in bigquery options ([#1914](https://github.com/googleapis/python-bigquery-dataframes/issues/1914)) ([c15cb8a](https://github.com/googleapis/python-bigquery-dataframes/commit/c15cb8a1a9c834c2c1c2984930415b246f3f948b)) -* Series.str.isdigit in unicode superscripts and fractions ([#1924](https://github.com/googleapis/python-bigquery-dataframes/issues/1924)) ([8d46c36](https://github.com/googleapis/python-bigquery-dataframes/commit/8d46c36da7881a99861166c03a0831beff8ee0dd)) - - -### Documentation - -* Add code snippets for session and IO public docs ([#1919](https://github.com/googleapis/python-bigquery-dataframes/issues/1919)) ([6e01cbe](https://github.com/googleapis/python-bigquery-dataframes/commit/6e01cbec0dcf40e528b4a96e944681df18773c11)) -* Add snippets for performance optimization doc ([#1923](https://github.com/googleapis/python-bigquery-dataframes/issues/1923)) ([4da309e](https://github.com/googleapis/python-bigquery-dataframes/commit/4da309e27bd58a685e8aca953717da75d4ba5305)) - -## [2.11.0](https://github.com/googleapis/python-bigquery-dataframes/compare/v2.10.0...v2.11.0) (2025-07-15) - - -### Features - -* Add `__contains__` to Index, Series, DataFrame ([#1899](https://github.com/googleapis/python-bigquery-dataframes/issues/1899)) ([07222bf](https://github.com/googleapis/python-bigquery-dataframes/commit/07222bfe2f6ae60859d33eb366598d7dee5c0572)) -* Add `thresh` param for Dataframe.dropna ([#1885](https://github.com/googleapis/python-bigquery-dataframes/issues/1885)) ([1395a50](https://github.com/googleapis/python-bigquery-dataframes/commit/1395a502ffa0faf4b7462045dcb0657485c7ce26)) -* Add concat pushdown for hybrid engine ([#1891](https://github.com/googleapis/python-bigquery-dataframes/issues/1891)) ([813624d](https://github.com/googleapis/python-bigquery-dataframes/commit/813624dddfd4f2396c8b1c9768c0c831bb0681ac)) -* Add pagination buttons (prev/next) to anywidget mode for DataFrames ([#1841](https://github.com/googleapis/python-bigquery-dataframes/issues/1841)) ([8eca767](https://github.com/googleapis/python-bigquery-dataframes/commit/8eca767425c7910c8f907747a8a8b335df0caa1a)) -* Add total_rows property to pandas batches iterator ([#1888](https://github.com/googleapis/python-bigquery-dataframes/issues/1888)) ([e3f5e65](https://github.com/googleapis/python-bigquery-dataframes/commit/e3f5e6539d220f8da57f08f67863ade29df4ad16)) -* Hybrid engine local join support ([#1900](https://github.com/googleapis/python-bigquery-dataframes/issues/1900)) ([1aa7950](https://github.com/googleapis/python-bigquery-dataframes/commit/1aa7950334bdc826a9a0a1894dad67ca6f755425)) -* Support `date` data type for to_datetime() ([#1902](https://github.com/googleapis/python-bigquery-dataframes/issues/1902)) ([24050cb](https://github.com/googleapis/python-bigquery-dataframes/commit/24050cb00247f68eb4ece827fd31ee1dd8b25380)) -* Support bpd.Series(json_data, dtype="json") ([#1882](https://github.com/googleapis/python-bigquery-dataframes/issues/1882)) ([05cb7d0](https://github.com/googleapis/python-bigquery-dataframes/commit/05cb7d0bc3599054acf8ecb8b15eb2045b9bf463)) - - -### Bug Fixes - -* Bpd.merge on common columns ([#1905](https://github.com/googleapis/python-bigquery-dataframes/issues/1905)) ([a1fa112](https://github.com/googleapis/python-bigquery-dataframes/commit/a1fa11291305a1da0d6a4121436c09ed04b224b5)) -* DataFrame string addition respects order ([#1894](https://github.com/googleapis/python-bigquery-dataframes/issues/1894)) ([52c8233](https://github.com/googleapis/python-bigquery-dataframes/commit/52c82337bcc9f2b6cfc1c6ac14deb83b693d114d)) -* Show slot_millis_sum warning only when `allow_large_results=False` ([#1892](https://github.com/googleapis/python-bigquery-dataframes/issues/1892)) ([25efabc](https://github.com/googleapis/python-bigquery-dataframes/commit/25efabc4897e0692725618ce43134127a7f2c2ee)) -* Used query row count metadata instead of table metadata ([#1893](https://github.com/googleapis/python-bigquery-dataframes/issues/1893)) ([e1ebc53](https://github.com/googleapis/python-bigquery-dataframes/commit/e1ebc5369a416280cec0ab1513e763b7a2fe3c20)) - -## [2.10.0](https://github.com/googleapis/python-bigquery-dataframes/compare/v2.9.0...v2.10.0) (2025-07-08) - - -### Features - -* `df.to_pandas_batches()` returns one empty DataFrame if `df` is empty ([#1878](https://github.com/googleapis/python-bigquery-dataframes/issues/1878)) ([e43d15d](https://github.com/googleapis/python-bigquery-dataframes/commit/e43d15d535d6d5fd73c33967271f3591c41dffb3)) -* Add filter pushdown to hybrid engine ([#1871](https://github.com/googleapis/python-bigquery-dataframes/issues/1871)) ([6454aff](https://github.com/googleapis/python-bigquery-dataframes/commit/6454aff726dee791acbac98f893075ee5ee6d9a1)) -* Add simple stats support to hybrid local pushdown ([#1873](https://github.com/googleapis/python-bigquery-dataframes/issues/1873)) ([8715105](https://github.com/googleapis/python-bigquery-dataframes/commit/8715105239216bffe899ddcbb15805f2e3063af4)) - - -### Bug Fixes - -* Fix issues where duration type returned as int ([#1875](https://github.com/googleapis/python-bigquery-dataframes/issues/1875)) ([f30f750](https://github.com/googleapis/python-bigquery-dataframes/commit/f30f75053a6966abd1a6a644c23efb86b2ac568d)) - - -### Documentation - -* Update gsutil commands to gcloud commands ([#1876](https://github.com/googleapis/python-bigquery-dataframes/issues/1876)) ([c289f70](https://github.com/googleapis/python-bigquery-dataframes/commit/c289f7061320ec6d9de099cab2416cc9f289baac)) - -## [2.9.0](https://github.com/googleapis/python-bigquery-dataframes/compare/v2.8.0...v2.9.0) (2025-06-30) - - -### Features - -* Add `bpd.read_arrow` to convert an Arrow object into a bigframes DataFrame ([#1855](https://github.com/googleapis/python-bigquery-dataframes/issues/1855)) ([633bf98](https://github.com/googleapis/python-bigquery-dataframes/commit/633bf98fde33264be4fc9d7454e541c560589152)) -* Add experimental polars execution ([#1747](https://github.com/googleapis/python-bigquery-dataframes/issues/1747)) ([daf0c3b](https://github.com/googleapis/python-bigquery-dataframes/commit/daf0c3b349fb1e85e7070c54a2d3f5460f5e40c9)) -* Add size op support in local engine ([#1865](https://github.com/googleapis/python-bigquery-dataframes/issues/1865)) ([942e66c](https://github.com/googleapis/python-bigquery-dataframes/commit/942e66c483c9afbb680a7af56c9e9a76172a33e1)) -* Create `deploy_remote_function` and `deploy_udf` functions to immediately deploy functions to BigQuery ([#1832](https://github.com/googleapis/python-bigquery-dataframes/issues/1832)) ([c706759](https://github.com/googleapis/python-bigquery-dataframes/commit/c706759b85359b6d23ce3449f6ab138ad2d22f9d)) -* Support index item assign in Series ([#1868](https://github.com/googleapis/python-bigquery-dataframes/issues/1868)) ([c5d251a](https://github.com/googleapis/python-bigquery-dataframes/commit/c5d251a1d454bb4ef55ea9905faeadd646a23b14)) -* Support item assignment in series ([#1859](https://github.com/googleapis/python-bigquery-dataframes/issues/1859)) ([25684ff](https://github.com/googleapis/python-bigquery-dataframes/commit/25684ff60367f49dd318d4677a7438abdc98bff9)) -* Support local execution of comparison ops ([#1849](https://github.com/googleapis/python-bigquery-dataframes/issues/1849)) ([1c45ccb](https://github.com/googleapis/python-bigquery-dataframes/commit/1c45ccb133091aa85bc34450704fc8cab3d9296b)) - - -### Bug Fixes - -* Fix bug selecting column repeatedly ([#1858](https://github.com/googleapis/python-bigquery-dataframes/issues/1858)) ([cc339e9](https://github.com/googleapis/python-bigquery-dataframes/commit/cc339e9938129cac896460e3a794b3ec8479fa4a)) -* Fix bug with DataFrame.agg for string values ([#1870](https://github.com/googleapis/python-bigquery-dataframes/issues/1870)) ([81e4d64](https://github.com/googleapis/python-bigquery-dataframes/commit/81e4d64c5a3bd8d30edaf909d0bef2d1d1a51c01)) -* Generate GoogleSQL instead of legacy SQL data types for `dry_run=True` from `bpd._read_gbq_colab` with local pandas DataFrame ([#1867](https://github.com/googleapis/python-bigquery-dataframes/issues/1867)) ([fab3c38](https://github.com/googleapis/python-bigquery-dataframes/commit/fab3c387b2ad66043244fa813a366e613b41c60f)) -* Revert dict back to protobuf in the iam binding update ([#1838](https://github.com/googleapis/python-bigquery-dataframes/issues/1838)) ([9fb3cb4](https://github.com/googleapis/python-bigquery-dataframes/commit/9fb3cb444607df6736d383a2807059bca470c453)) - - -### Documentation - -* Add data visualization samples for public doc ([#1847](https://github.com/googleapis/python-bigquery-dataframes/issues/1847)) ([15e1277](https://github.com/googleapis/python-bigquery-dataframes/commit/15e1277b1413de18a5e36f72959a99701d6df08b)) -* Changed broken logo ([#1866](https://github.com/googleapis/python-bigquery-dataframes/issues/1866)) ([e3c06b4](https://github.com/googleapis/python-bigquery-dataframes/commit/e3c06b4a07d0669a42460d081f1582b681ae3dd5)) -* Update ai.forecast notebook ([#1844](https://github.com/googleapis/python-bigquery-dataframes/issues/1844)) ([1863538](https://github.com/googleapis/python-bigquery-dataframes/commit/186353888db537b561ee994256f998df361b4071)) - -## [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) - - -### Features - -* Add bbq.json_query_array and warn bbq.json_extract_array deprecated ([#1811](https://github.com/googleapis/python-bigquery-dataframes/issues/1811)) ([dc9eb27](https://github.com/googleapis/python-bigquery-dataframes/commit/dc9eb27fa75e90c2c95a0619551bf67aea6ef63b)) -* Add bbq.json_value_array and deprecate bbq.json_extract_string_array ([#1818](https://github.com/googleapis/python-bigquery-dataframes/issues/1818)) ([019051e](https://github.com/googleapis/python-bigquery-dataframes/commit/019051e453d81769891aa398475ebd04d1826e81)) -* Add groupby cumcount ([#1798](https://github.com/googleapis/python-bigquery-dataframes/issues/1798)) ([18f43e8](https://github.com/googleapis/python-bigquery-dataframes/commit/18f43e8b58e03a27b021bce07566a3d006ac3679)) -* Support custom build service account in `remote_function` ([#1796](https://github.com/googleapis/python-bigquery-dataframes/issues/1796)) ([e586151](https://github.com/googleapis/python-bigquery-dataframes/commit/e586151df81917b49f702ae496aaacbd02931636)) - - -### Bug Fixes - -* Correct read_csv behaviours with use_cols, names, index_col ([#1804](https://github.com/googleapis/python-bigquery-dataframes/issues/1804)) ([855031a](https://github.com/googleapis/python-bigquery-dataframes/commit/855031a316a6957731a5d1c5e59dedb9757d9f7a)) -* Fix single row broadcast with null index ([#1803](https://github.com/googleapis/python-bigquery-dataframes/issues/1803)) ([080eb7b](https://github.com/googleapis/python-bigquery-dataframes/commit/080eb7be3cde591e08cad0d5c52c68cc0b25ade8)) - - -### Documentation - -* Document how to use ai.map() for information extraction ([#1808](https://github.com/googleapis/python-bigquery-dataframes/issues/1808)) ([b586746](https://github.com/googleapis/python-bigquery-dataframes/commit/b5867464a5bf30300dcfc069eda546b11f03146c)) -* Rearrange README.rst to include a short code sample ([#1812](https://github.com/googleapis/python-bigquery-dataframes/issues/1812)) ([f6265db](https://github.com/googleapis/python-bigquery-dataframes/commit/f6265dbb8e22de81bb59c7def175cd325e85c041)) -* Use pandas API instead of pandas-like or pandas-compatible ([#1825](https://github.com/googleapis/python-bigquery-dataframes/issues/1825)) ([aa32369](https://github.com/googleapis/python-bigquery-dataframes/commit/aa323694e161f558bc5e60490c2f21008961e2ca)) - -## [2.6.0](https://github.com/googleapis/python-bigquery-dataframes/compare/v2.5.0...v2.6.0) (2025-06-09) - - -### Features - -* Add blob.transcribe function ([#1773](https://github.com/googleapis/python-bigquery-dataframes/issues/1773)) ([86159a7](https://github.com/googleapis/python-bigquery-dataframes/commit/86159a7d24102574c26764a056478757844e2eca)) -* Implement ai.classify() ([#1781](https://github.com/googleapis/python-bigquery-dataframes/issues/1781)) ([8af26d0](https://github.com/googleapis/python-bigquery-dataframes/commit/8af26d07cf3e8b22e0c69dd0172352fadc1857d8)) -* Implement item() for Series and Index ([#1792](https://github.com/googleapis/python-bigquery-dataframes/issues/1792)) ([d2154c8](https://github.com/googleapis/python-bigquery-dataframes/commit/d2154c82fa0fed6e89c47db747d3c9cd57f9c618)) -* Implement ST_ISCLOSED geography function ([#1789](https://github.com/googleapis/python-bigquery-dataframes/issues/1789)) ([36bc179](https://github.com/googleapis/python-bigquery-dataframes/commit/36bc179ee7ef9b0b6799f98f8fac3f64d91412af)) -* Implement ST_LENGTH geography function ([#1791](https://github.com/googleapis/python-bigquery-dataframes/issues/1791)) ([c5b7fda](https://github.com/googleapis/python-bigquery-dataframes/commit/c5b7fdae74a22e581f7705bc0cf5390e928f4425)) -* Support isin with bigframes.pandas.Index arg ([#1779](https://github.com/googleapis/python-bigquery-dataframes/issues/1779)) ([e480d29](https://github.com/googleapis/python-bigquery-dataframes/commit/e480d29f03636fa9824404ef90c510701e510195)) - - -### Bug Fixes - -* Address `read_csv` with both `index_col` and `use_cols` behavior inconsistency with pandas ([#1785](https://github.com/googleapis/python-bigquery-dataframes/issues/1785)) ([ba7c313](https://github.com/googleapis/python-bigquery-dataframes/commit/ba7c313c8d308e3ff3f736b60978cb7a51715209)) -* Allow KMeans model init parameter as k-means++ alias ([#1790](https://github.com/googleapis/python-bigquery-dataframes/issues/1790)) ([0b59cf1](https://github.com/googleapis/python-bigquery-dataframes/commit/0b59cf1008613770fa1433c6da395e755c86fe22)) -* Replace function now can handle pd.NA value. ([#1786](https://github.com/googleapis/python-bigquery-dataframes/issues/1786)) ([7269512](https://github.com/googleapis/python-bigquery-dataframes/commit/7269512a28eb42029447d5380c764353278a74e1)) - - -### Documentation - -* Adjust strip method examples to match latest pandas ([#1797](https://github.com/googleapis/python-bigquery-dataframes/issues/1797)) ([817b0c0](https://github.com/googleapis/python-bigquery-dataframes/commit/817b0c0c5dc481598fbfdbe40fd925fb38f3a066)) -* Fix docstrings to improve html rendering of code examples ([#1788](https://github.com/googleapis/python-bigquery-dataframes/issues/1788)) ([38d9b73](https://github.com/googleapis/python-bigquery-dataframes/commit/38d9b7376697f8e19124e5d1f5fccda82d920b92)) - -## [2.5.0](https://github.com/googleapis/python-bigquery-dataframes/compare/v2.4.0...v2.5.0) (2025-05-30) - - -### ⚠ BREAKING CHANGES - -* the updated `ai.map()` parameter list is not backward-compatible - -### Features - -* Add `bpd.options.bigquery.requests_transport_adapters` option ([#1755](https://github.com/googleapis/python-bigquery-dataframes/issues/1755)) ([bb45db8](https://github.com/googleapis/python-bigquery-dataframes/commit/bb45db8afdffa1417f11c050d40d4ec6d15b8654)) -* Add bbq.json_query and warn bbq.json_extract deprecated ([#1756](https://github.com/googleapis/python-bigquery-dataframes/issues/1756)) ([ec81dd2](https://github.com/googleapis/python-bigquery-dataframes/commit/ec81dd2228697d5bf193d86396cf7f3212e0289d)) -* Add bpd.options.reset() method ([#1743](https://github.com/googleapis/python-bigquery-dataframes/issues/1743)) ([36c359d](https://github.com/googleapis/python-bigquery-dataframes/commit/36c359d2521089e186a412d353daf9de6cfbc8f4)) -* Add DataFrame.round method ([#1742](https://github.com/googleapis/python-bigquery-dataframes/issues/1742)) ([3ea6043](https://github.com/googleapis/python-bigquery-dataframes/commit/3ea6043be7025fa7a11cca27b02f5505bbc9b129)) -* Add deferred data uploading ([#1720](https://github.com/googleapis/python-bigquery-dataframes/issues/1720)) ([1f6442e](https://github.com/googleapis/python-bigquery-dataframes/commit/1f6442e576c35ec784ccf9cab3d081d46e45a5ce)) -* Add deprecation warning to Gemini-1.5-X, text-embedding-004, and remove remove legacy models in notebooks and docs ([#1723](https://github.com/googleapis/python-bigquery-dataframes/issues/1723)) ([80aad9a](https://github.com/googleapis/python-bigquery-dataframes/commit/80aad9af794c2e06d1608c879f459a836fd4448b)) -* Add structured output for ai map, ai filter and ai join ([#1746](https://github.com/googleapis/python-bigquery-dataframes/issues/1746)) ([133ac6b](https://github.com/googleapis/python-bigquery-dataframes/commit/133ac6b0e1f1e7a12844a4b6fd5b26df59f7ef37)) -* Add support for df.loc[list, column(s)] ([#1761](https://github.com/googleapis/python-bigquery-dataframes/issues/1761)) ([768a757](https://github.com/googleapis/python-bigquery-dataframes/commit/768a7570845c4eb88f495d7f3c0f3158accdc231)) -* Include bq schema and query string in dry run results ([#1752](https://github.com/googleapis/python-bigquery-dataframes/issues/1752)) ([bb51147](https://github.com/googleapis/python-bigquery-dataframes/commit/bb511475b74cc253230725846098a9045be2e324)) -* Support `inplace=True` in `rename` and `rename_axis` ([#1744](https://github.com/googleapis/python-bigquery-dataframes/issues/1744)) ([734cc65](https://github.com/googleapis/python-bigquery-dataframes/commit/734cc652e435dc5d97a23411735aa51b7824e381)) -* Support `unique()` for Index ([#1750](https://github.com/googleapis/python-bigquery-dataframes/issues/1750)) ([27fac78](https://github.com/googleapis/python-bigquery-dataframes/commit/27fac78cb5654e5655aec861062837a7d4f3f679)) -* Support astype conversions to and from JSON dtypes ([#1716](https://github.com/googleapis/python-bigquery-dataframes/issues/1716)) ([8ef4de1](https://github.com/googleapis/python-bigquery-dataframes/commit/8ef4de10151717f88364a909b29fa7600e959ada)) -* Support dict param for dataframe.agg() ([#1772](https://github.com/googleapis/python-bigquery-dataframes/issues/1772)) ([f9c29c8](https://github.com/googleapis/python-bigquery-dataframes/commit/f9c29c85053d8111a74ce382490daed36f8bb35b)) -* Support dtype parameter in read_csv for bigquery engine ([#1749](https://github.com/googleapis/python-bigquery-dataframes/issues/1749)) ([50dca4c](https://github.com/googleapis/python-bigquery-dataframes/commit/50dca4c706d78673b03f90eccf776118247ba30b)) -* Use read api for some peek ops ([#1731](https://github.com/googleapis/python-bigquery-dataframes/issues/1731)) ([108f4d2](https://github.com/googleapis/python-bigquery-dataframes/commit/108f4d259e1bcfbe6c7aa3c3c3f8f605cf7615ee)) - - -### Bug Fixes - -* Fix clip int series with float bounds ([#1739](https://github.com/googleapis/python-bigquery-dataframes/issues/1739)) ([d451aef](https://github.com/googleapis/python-bigquery-dataframes/commit/d451aefd2181aef250c3b48cceac09063081cab2)) -* Fix error with self-merge operations ([#1774](https://github.com/googleapis/python-bigquery-dataframes/issues/1774)) ([e5fe143](https://github.com/googleapis/python-bigquery-dataframes/commit/e5fe14339b4a40ab4a25657ee0453e4108cf8bba)) -* Fix the default value for na_value for numpy conversions ([#1766](https://github.com/googleapis/python-bigquery-dataframes/issues/1766)) ([0629cac](https://github.com/googleapis/python-bigquery-dataframes/commit/0629cac7f9a9370a72c1ae25e014eb478a4c8c08)) -* Include location in Session-based temporary storage manager DDL queries ([#1780](https://github.com/googleapis/python-bigquery-dataframes/issues/1780)) ([acba032](https://github.com/googleapis/python-bigquery-dataframes/commit/acba0321cafeb49f3e560a364ebbf3d15fb8af88)) -* Prevent creating unnecessary client objects in multithreaded environments ([#1757](https://github.com/googleapis/python-bigquery-dataframes/issues/1757)) ([1cf9f5e](https://github.com/googleapis/python-bigquery-dataframes/commit/1cf9f5e8dba733ee26d15fc5edc44c81e094e9a0)) -* Reduce bigquery table modification via DML for to_gbq ([#1737](https://github.com/googleapis/python-bigquery-dataframes/issues/1737)) ([545cdca](https://github.com/googleapis/python-bigquery-dataframes/commit/545cdcac1361607678c2574f0f31eb43950073e5)) -* Stop ignoring arguments to `MatrixFactorization.score(X, y)` ([#1726](https://github.com/googleapis/python-bigquery-dataframes/issues/1726)) ([55c07e9](https://github.com/googleapis/python-bigquery-dataframes/commit/55c07e9d4315949c37ffa3e03c8fedc6daf17faf)) -* Support JSON and STRUCT for bbq.sql_scalar ([#1754](https://github.com/googleapis/python-bigquery-dataframes/issues/1754)) ([190390b](https://github.com/googleapis/python-bigquery-dataframes/commit/190390b804c2131c2eaa624d7f025febb7784b01)) -* Support str.replace re.compile with flags ([#1736](https://github.com/googleapis/python-bigquery-dataframes/issues/1736)) ([f8d2cd2](https://github.com/googleapis/python-bigquery-dataframes/commit/f8d2cd24281415f4a8f9193b676f5483128cd173)) - - -### Performance Improvements - -* Faster local data comparison using idenitity ([#1738](https://github.com/googleapis/python-bigquery-dataframes/issues/1738)) ([2858b1e](https://github.com/googleapis/python-bigquery-dataframes/commit/2858b1efb4fe74097dcb17c086ee1dc18e53053c)) -* Optimize repr for unordered gbq table ([#1778](https://github.com/googleapis/python-bigquery-dataframes/issues/1778)) ([2bc4fbc](https://github.com/googleapis/python-bigquery-dataframes/commit/2bc4fbc78eba4bb2ee335e0475700a7ca5bc84d7)) -* Use JOB_CREATION_OPTIONAL when `allow_large_results=False` ([#1763](https://github.com/googleapis/python-bigquery-dataframes/issues/1763)) ([15f3f2a](https://github.com/googleapis/python-bigquery-dataframes/commit/15f3f2aa42cfe4a2233f62c5f8906e7f7658f9fa)) - - -### Dependencies - -* Avoid `gcsfs==2025.5.0` ([#1762](https://github.com/googleapis/python-bigquery-dataframes/issues/1762)) ([68d5e2c](https://github.com/googleapis/python-bigquery-dataframes/commit/68d5e2cbef3510cadc7e9dd199117c1e3b02d19f)) - - -### Documentation - -* Add llm output_schema notebook ([#1732](https://github.com/googleapis/python-bigquery-dataframes/issues/1732)) ([b2261cc](https://github.com/googleapis/python-bigquery-dataframes/commit/b2261cc07cd58b51d212f9bf495c5022e587f816)) -* Add MatrixFactorization to the table of contents ([#1725](https://github.com/googleapis/python-bigquery-dataframes/issues/1725)) ([611e43b](https://github.com/googleapis/python-bigquery-dataframes/commit/611e43b156483848a5470f889fb7b2b473ecff4d)) -* Fix typo for "population" in the `GeminiTextGenerator.predict(..., output_schema={...})` sample notebook ([#1748](https://github.com/googleapis/python-bigquery-dataframes/issues/1748)) ([bd07e05](https://github.com/googleapis/python-bigquery-dataframes/commit/bd07e05d26820313c052eaf41c267a1ab20b4fc6)) -* Integrations notebook extracts token from `bqclient._http.credentials` instead of `bqclient._credentials` ([#1784](https://github.com/googleapis/python-bigquery-dataframes/issues/1784)) ([6e63eca](https://github.com/googleapis/python-bigquery-dataframes/commit/6e63eca29f20d83435878273604816ce7595c396)) -* Updated multimodal notebook instructions ([#1745](https://github.com/googleapis/python-bigquery-dataframes/issues/1745)) ([1df8ca6](https://github.com/googleapis/python-bigquery-dataframes/commit/1df8ca6312ee428d55c2091a00c73b13d9a6b193)) -* Use partial ordering mode in the quickstart sample ([#1734](https://github.com/googleapis/python-bigquery-dataframes/issues/1734)) ([476b7dd](https://github.com/googleapis/python-bigquery-dataframes/commit/476b7dd7c2639cb6804272d06aa5c1db666819da)) - -## [2.4.0](https://github.com/googleapis/python-bigquery-dataframes/compare/v2.3.0...v2.4.0) (2025-05-12) - - -### Features - -* Add "dayofyear" property for `dt` accessors ([#1692](https://github.com/googleapis/python-bigquery-dataframes/issues/1692)) ([9d4a59d](https://github.com/googleapis/python-bigquery-dataframes/commit/9d4a59ddf22793d4e0587ea2f8648fae937875f3)) -* Add `.dt.days`, `.dt.seconds`, `dt.microseconds`, and `dt.total_seconds()` for timedelta series. ([#1713](https://github.com/googleapis/python-bigquery-dataframes/issues/1713)) ([2b3a45f](https://github.com/googleapis/python-bigquery-dataframes/commit/2b3a45f8c1fd299ee97cf1c343df7c80175b4287)) -* Add `DatetimeIndex` class ([#1719](https://github.com/googleapis/python-bigquery-dataframes/issues/1719)) ([c3c830c](https://github.com/googleapis/python-bigquery-dataframes/commit/c3c830cf20397830d531a89edf5302aede5d48a0)) -* Add `isocalendar()` for dt accessor" ([#1717](https://github.com/googleapis/python-bigquery-dataframes/issues/1717)) ([0479763](https://github.com/googleapis/python-bigquery-dataframes/commit/047976315dcbaed86e50d47f545b76c3a513dafb)) -* Add bigframes.bigquery.json_value ([#1697](https://github.com/googleapis/python-bigquery-dataframes/issues/1697)) ([46a9c53](https://github.com/googleapis/python-bigquery-dataframes/commit/46a9c53256be2a293f96122ba6b330564383bcd5)) -* Add blob.exif function support ([#1703](https://github.com/googleapis/python-bigquery-dataframes/issues/1703)) ([3f79528](https://github.com/googleapis/python-bigquery-dataframes/commit/3f79528781abe9bfc122f6f6e26bfa08b029265a)) -* Add inplace arg support to sort methods ([#1710](https://github.com/googleapis/python-bigquery-dataframes/issues/1710)) ([d1ccb52](https://github.com/googleapis/python-bigquery-dataframes/commit/d1ccb524ea26deac1cf9e481e9d55f9ae166247b)) -* Improve error message in `Series.apply` for direct udfs ([#1673](https://github.com/googleapis/python-bigquery-dataframes/issues/1673)) ([1a658b2](https://github.com/googleapis/python-bigquery-dataframes/commit/1a658b2aa43c4a7a7f2007a509b0e1401f925dab)) -* Publish bigframes blob(Multimodal) to preview ([#1693](https://github.com/googleapis/python-bigquery-dataframes/issues/1693)) ([e4c85ba](https://github.com/googleapis/python-bigquery-dataframes/commit/e4c85ba4813469d39edd7352201aefc26642d14c)) -* Support () operator between timedeltas ([#1702](https://github.com/googleapis/python-bigquery-dataframes/issues/1702)) ([edaac89](https://github.com/googleapis/python-bigquery-dataframes/commit/edaac89c03db1ffc93b56275c765d8a964f7d02d)) -* Support forecast_limit_lower_bound and forecast_limit_upper_bound in ARIMA_PLUS (and ARIMA_PLUS_XREG) models ([#1305](https://github.com/googleapis/python-bigquery-dataframes/issues/1305)) ([b16740e](https://github.com/googleapis/python-bigquery-dataframes/commit/b16740ef4ad7b1fbf731595238cf087c93c93066)) -* Support to_strip parameter for str.strip, str.lstrip and str.rstrip ([#1705](https://github.com/googleapis/python-bigquery-dataframes/issues/1705)) ([a84ee75](https://github.com/googleapis/python-bigquery-dataframes/commit/a84ee75ddd4d9dae1463e505549d74eb4f819338)) - - -### Bug Fixes - -* Fix dayofyear doc test ([#1701](https://github.com/googleapis/python-bigquery-dataframes/issues/1701)) ([9b777a0](https://github.com/googleapis/python-bigquery-dataframes/commit/9b777a019aa31a115a22289f21c7cd9df07aa8b9)) -* Fix issues with chunked arrow data ([#1700](https://github.com/googleapis/python-bigquery-dataframes/issues/1700)) ([e3289b7](https://github.com/googleapis/python-bigquery-dataframes/commit/e3289b7a64ee1400c6cb78e75cff4759d8da8b7a)) -* Rename columns with protected names such as `_TABLE_SUFFIX` in `to_gbq()` ([#1691](https://github.com/googleapis/python-bigquery-dataframes/issues/1691)) ([8ec6079](https://github.com/googleapis/python-bigquery-dataframes/commit/8ec607986fd38f357746fbaeabef2ce7ab3e501f)) - - -### Performance Improvements - -* Defer query in `read_gbq` with wildcard tables ([#1661](https://github.com/googleapis/python-bigquery-dataframes/issues/1661)) ([5c125c9](https://github.com/googleapis/python-bigquery-dataframes/commit/5c125c99d4632c617425c2ef5c399d17878c0043)) -* Rechunk result pages client side ([#1680](https://github.com/googleapis/python-bigquery-dataframes/issues/1680)) ([67d8760](https://github.com/googleapis/python-bigquery-dataframes/commit/67d876076027b6123e49d1d8ddee4e45eaa28f5d)) - - -### Dependencies - -* Move bigtable and pubsub to extras ([#1696](https://github.com/googleapis/python-bigquery-dataframes/issues/1696)) ([597d817](https://github.com/googleapis/python-bigquery-dataframes/commit/597d8178048b203cea4777f29b1ce95de7b0670e)) - - -### Documentation - -* Add snippets for Matrix Factorization tutorials ([#1630](https://github.com/googleapis/python-bigquery-dataframes/issues/1630)) ([24b37ae](https://github.com/googleapis/python-bigquery-dataframes/commit/24b37aece60460aabecce306397eb1bf6686f8a7)) -* Deprecate `bpd.options.bigquery.allow_large_results` in favor of `bpd.options.compute.allow_large_results` ([#1597](https://github.com/googleapis/python-bigquery-dataframes/issues/1597)) ([18780b4](https://github.com/googleapis/python-bigquery-dataframes/commit/18780b48a17dba2b3b3542500f027ae9527f6bee)) -* Include import statement in the bigframes code snippet ([#1699](https://github.com/googleapis/python-bigquery-dataframes/issues/1699)) ([08d70b6](https://github.com/googleapis/python-bigquery-dataframes/commit/08d70b6ad3ab3ac7b9a57d93da00168a8de7df9a)) -* Include the clean-up step in the udf code snippet ([#1698](https://github.com/googleapis/python-bigquery-dataframes/issues/1698)) ([48992e2](https://github.com/googleapis/python-bigquery-dataframes/commit/48992e26d460832704401bd2a3eedb800c5061cc)) -* Move multimodal notebook out of experimental folder ([#1712](https://github.com/googleapis/python-bigquery-dataframes/issues/1712)) ([68b6532](https://github.com/googleapis/python-bigquery-dataframes/commit/68b6532a780d6349a4b65994b696c8026457eb94)) -* Update blob_display option in snippets ([#1714](https://github.com/googleapis/python-bigquery-dataframes/issues/1714)) ([8b30143](https://github.com/googleapis/python-bigquery-dataframes/commit/8b30143e3320a730df168b5a72e6d18e631135ee)) - -## [2.3.0](https://github.com/googleapis/python-bigquery-dataframes/compare/v2.2.0...v2.3.0) (2025-05-06) - - -### Features - -* Add dry_run parameter to `read_gbq()`, `read_gbq_table()` and `read_gbq_query()` ([#1674](https://github.com/googleapis/python-bigquery-dataframes/issues/1674)) ([4c5dee5](https://github.com/googleapis/python-bigquery-dataframes/commit/4c5dee5e6f4b30deb01e258670aa21dbf3ac9aa5)) - - -### Bug Fixes - -* Guarantee guid thread safety across threads ([#1684](https://github.com/googleapis/python-bigquery-dataframes/issues/1684)) ([cb0267d](https://github.com/googleapis/python-bigquery-dataframes/commit/cb0267deea227ea85f20d6dbef8c29cf03526d7a)) -* Support large lists of lists in bpd.Series() constructor ([#1662](https://github.com/googleapis/python-bigquery-dataframes/issues/1662)) ([0f4024c](https://github.com/googleapis/python-bigquery-dataframes/commit/0f4024c84508c17657a9104ef1f8718094827ada)) -* Use value equality to check types for unix epoch functions and timestamp diff ([#1690](https://github.com/googleapis/python-bigquery-dataframes/issues/1690)) ([81e8fb8](https://github.com/googleapis/python-bigquery-dataframes/commit/81e8fb8627f1d35423dbbdcc99d02ab0ad362d11)) - - -### Performance Improvements - -* `to_datetime()` now avoids caching inputs unless data is inspected to infer format ([#1667](https://github.com/googleapis/python-bigquery-dataframes/issues/1667)) ([dd08857](https://github.com/googleapis/python-bigquery-dataframes/commit/dd08857f65140cbe5c524050d2d538949897c3cc)) - - -### Documentation - -* Add a visualization notebook to BigFrame samples ([#1675](https://github.com/googleapis/python-bigquery-dataframes/issues/1675)) ([ee062bf](https://github.com/googleapis/python-bigquery-dataframes/commit/ee062bfc29c27949205ca21d6c1dcd6125300e5e)) -* Fix spacing of k-means code snippet ([#1687](https://github.com/googleapis/python-bigquery-dataframes/issues/1687)) ([99f45dd](https://github.com/googleapis/python-bigquery-dataframes/commit/99f45dd14bd9632d209389a5fef009f18c57adbf)) -* Update snippet for `Create a k-means` model tutorial ([#1664](https://github.com/googleapis/python-bigquery-dataframes/issues/1664)) ([761c364](https://github.com/googleapis/python-bigquery-dataframes/commit/761c364f4df045b9e9d8d3d5fee91d9a87b772db)) - -## [2.2.0](https://github.com/googleapis/python-bigquery-dataframes/compare/v2.1.0...v2.2.0) (2025-04-30) - - -### Features - -* Add gemini-2.0-flash-001 and gemini-2.0-flash-lite-001 to fine tune score endponts and multimodal endpoints ([#1650](https://github.com/googleapis/python-bigquery-dataframes/issues/1650)) ([4fb54df](https://github.com/googleapis/python-bigquery-dataframes/commit/4fb54dfe448604a90fc1818cf18b1e77e1e7227b)) -* Add GeminiTextGenerator.predict structured output ([#1653](https://github.com/googleapis/python-bigquery-dataframes/issues/1653)) ([6199023](https://github.com/googleapis/python-bigquery-dataframes/commit/6199023a6a71e72e926f5879e74a15215bc6e4a0)) -* DataFrames.__getitem__ support for slice input ([#1668](https://github.com/googleapis/python-bigquery-dataframes/issues/1668)) ([563f0cb](https://github.com/googleapis/python-bigquery-dataframes/commit/563f0cbdf4a18c3cd1bd2a4b52de823165638911)) -* Print right origin of `PreviewWarning` for the `bpd.udf` ([#1629](https://github.com/googleapis/python-bigquery-dataframes/issues/1629)) ([48d10d1](https://github.com/googleapis/python-bigquery-dataframes/commit/48d10d1f0150a29dd3b91f505f8d3874e0b88c42)) -* Session.bytes_processed_sum will be updated when allow_large_re… ([#1669](https://github.com/googleapis/python-bigquery-dataframes/issues/1669)) ([ae312db](https://github.com/googleapis/python-bigquery-dataframes/commit/ae312dbed25da6da5e2817d5c9838654c2a1ad1c)) -* Short circuit query for local scan ([#1618](https://github.com/googleapis/python-bigquery-dataframes/issues/1618)) ([e84f232](https://github.com/googleapis/python-bigquery-dataframes/commit/e84f232b0fc5e2167a7cddb355cf0c8837ae5422)) -* Support names parameter in read_csv for bigquery engine ([#1659](https://github.com/googleapis/python-bigquery-dataframes/issues/1659)) ([3388191](https://github.com/googleapis/python-bigquery-dataframes/commit/33881914ab5b8d0e701eabd9c731aed1deab3d49)) -* Support passing list of values to bigframes.core.sql.simple_literal ([#1641](https://github.com/googleapis/python-bigquery-dataframes/issues/1641)) ([102d363](https://github.com/googleapis/python-bigquery-dataframes/commit/102d363aa7e3245ff262c817bc756ea0eaee57e7)) -* Support write api as loading option ([#1617](https://github.com/googleapis/python-bigquery-dataframes/issues/1617)) ([c46ad06](https://github.com/googleapis/python-bigquery-dataframes/commit/c46ad0647785a9207359eba0fb5b6f7a16610f2a)) - - -### Bug Fixes - -* DataFrame accessors is not pupulated ([#1639](https://github.com/googleapis/python-bigquery-dataframes/issues/1639)) ([28afa2c](https://github.com/googleapis/python-bigquery-dataframes/commit/28afa2c73c0517f9365fab05193706631b656551)) -* Prefer remote schema instead of throwing on materialize conflicts ([#1644](https://github.com/googleapis/python-bigquery-dataframes/issues/1644)) ([53fc25b](https://github.com/googleapis/python-bigquery-dataframes/commit/53fc25bfc86e166b91e5001506051b1cac34c996)) -* Remove itertools.pairwise usage ([#1638](https://github.com/googleapis/python-bigquery-dataframes/issues/1638)) ([9662745](https://github.com/googleapis/python-bigquery-dataframes/commit/9662745265c8c6e42f372629bd2c7806542cee1a)) -* Resolve issue where pre-release versions of google-auth are installed ([#1491](https://github.com/googleapis/python-bigquery-dataframes/issues/1491)) ([ebb7a5e](https://github.com/googleapis/python-bigquery-dataframes/commit/ebb7a5e2b24fa57d6fe6a76d9b857ad44c67d194)) -* Resolve some of the typo errors ([#1655](https://github.com/googleapis/python-bigquery-dataframes/issues/1655)) ([cd7fbde](https://github.com/googleapis/python-bigquery-dataframes/commit/cd7fbde026522f53a23a4bb6585ad8629769fad1)) - - -### Performance Improvements - -* Fold row count ops when known ([#1656](https://github.com/googleapis/python-bigquery-dataframes/issues/1656)) ([c958dbe](https://github.com/googleapis/python-bigquery-dataframes/commit/c958dbea32b77cec9fddfc09e3b40d1da220a42c)) -* Use flyweight for node fields ([#1654](https://github.com/googleapis/python-bigquery-dataframes/issues/1654)) ([8482bfc](https://github.com/googleapis/python-bigquery-dataframes/commit/8482bfc1d4caa91a35c4fbf0be420301d05ad544)) - - -### Dependencies - -* Support shapely 1.8.5+ again ([#1651](https://github.com/googleapis/python-bigquery-dataframes/issues/1651)) ([ae83e61](https://github.com/googleapis/python-bigquery-dataframes/commit/ae83e61c49ade64d6f727e9f364bd2f1aeec6e19)) - - -### Documentation - -* Add JSON data types notebook ([#1647](https://github.com/googleapis/python-bigquery-dataframes/issues/1647)) ([9128c4a](https://github.com/googleapis/python-bigquery-dataframes/commit/9128c4a31dab487bc23f67c43380abd0beda5b1c)) -* Add sample code snippets for `udf` ([#1649](https://github.com/googleapis/python-bigquery-dataframes/issues/1649)) ([53caa8d](https://github.com/googleapis/python-bigquery-dataframes/commit/53caa8d689e64436f5313095ee27479a06d8e8a8)) -* Fix `bq_dataframes_template` notebook to work if partial ordering mode is enabled ([#1665](https://github.com/googleapis/python-bigquery-dataframes/issues/1665)) ([f442e7a](https://github.com/googleapis/python-bigquery-dataframes/commit/f442e7a07ff273ba3af74eeabafb62110b78f692)) -* Note that `udf` is in preview and must be python 3.11 compatible ([#1629](https://github.com/googleapis/python-bigquery-dataframes/issues/1629)) ([48d10d1](https://github.com/googleapis/python-bigquery-dataframes/commit/48d10d1f0150a29dd3b91f505f8d3874e0b88c42)) - -## [2.1.0](https://github.com/googleapis/python-bigquery-dataframes/compare/v2.0.0...v2.1.0) (2025-04-22) - - -### Features - -* Add `bigframes.bigquery.st_distance` function ([#1637](https://github.com/googleapis/python-bigquery-dataframes/issues/1637)) ([bf1ae70](https://github.com/googleapis/python-bigquery-dataframes/commit/bf1ae7091a02ad28d222fa63d311ed5ef3800807)) -* Enable local json string validations ([#1614](https://github.com/googleapis/python-bigquery-dataframes/issues/1614)) ([233347a](https://github.com/googleapis/python-bigquery-dataframes/commit/233347aca0ac55b2407e0f49430bf13536986e25)) -* Enhance `read_csv` `index_col` parameter support ([#1631](https://github.com/googleapis/python-bigquery-dataframes/issues/1631)) ([f4e5b26](https://github.com/googleapis/python-bigquery-dataframes/commit/f4e5b26b7b7b00ef807987c4b9c5fded56ad883f)) - - -### Bug Fixes - -* Add retry for test_clean_up_via_context_manager ([#1627](https://github.com/googleapis/python-bigquery-dataframes/issues/1627)) ([58e7cb0](https://github.com/googleapis/python-bigquery-dataframes/commit/58e7cb025a86959164643cebb725c853dc2ebc34)) -* Improve robustness of managed udf code extraction ([#1634](https://github.com/googleapis/python-bigquery-dataframes/issues/1634)) ([8cc56d5](https://github.com/googleapis/python-bigquery-dataframes/commit/8cc56d5118017beb2931519ddd1eb8e151852849)) - - -### Documentation - -* Add code samples in the `udf` API docstring ([#1632](https://github.com/googleapis/python-bigquery-dataframes/issues/1632)) ([f68b80c](https://github.com/googleapis/python-bigquery-dataframes/commit/f68b80cce2451a8c8d931a54e0cb69e02f34ce10)) - -## [2.0.0](https://github.com/googleapis/python-bigquery-dataframes/compare/v1.42.0...v2.0.0) (2025-04-17) - - -### ⚠ BREAKING CHANGES - -* make `dataset` and `name` params mandatory in `udf` ([#1619](https://github.com/googleapis/python-bigquery-dataframes/issues/1619)) -* Locational endpoints support is not available in BigFrames 2.0. -* change default LLM model to gemini-2.0-flash-001, drop PaLM2TextGenerator and PaLM2TextEmbeddingGenerator ([#1558](https://github.com/googleapis/python-bigquery-dataframes/issues/1558)) -* change default ingress setting for `remote_function` to internal-only ([#1544](https://github.com/googleapis/python-bigquery-dataframes/issues/1544)) -* make `remote_function` params keyword only ([#1537](https://github.com/googleapis/python-bigquery-dataframes/issues/1537)) -* make `remote_function` default service account explicit ([#1537](https://github.com/googleapis/python-bigquery-dataframes/issues/1537)) -* set `allow_large_results=False` by default ([#1541](https://github.com/googleapis/python-bigquery-dataframes/issues/1541)) - -### Features - -* Add `on` parameter in `dataframe.rolling()` and `dataframe.groupby.rolling()` ([#1556](https://github.com/googleapis/python-bigquery-dataframes/issues/1556)) ([45c9d9f](https://github.com/googleapis/python-bigquery-dataframes/commit/45c9d9fd1c5c13a8692435aa22861820fc11e347)) -* Add component to manage temporary tables ([#1559](https://github.com/googleapis/python-bigquery-dataframes/issues/1559)) ([0a4e245](https://github.com/googleapis/python-bigquery-dataframes/commit/0a4e245670e678f4ead0aec8f8b534e7fe97d112)) -* Add Series.to_pandas_batches() method ([#1592](https://github.com/googleapis/python-bigquery-dataframes/issues/1592)) ([09ce979](https://github.com/googleapis/python-bigquery-dataframes/commit/09ce97999cfc1ded72906b1c7307da5950978ae6)) -* Add support for creating a Matrix Factorization model ([#1330](https://github.com/googleapis/python-bigquery-dataframes/issues/1330)) ([b5297f9](https://github.com/googleapis/python-bigquery-dataframes/commit/b5297f909b08928b97d887764d6e5142c763a5a3)) -* Allow `input_types`, `output_type`, and `dataset` to be used positionally in `remote_function` ([#1560](https://github.com/googleapis/python-bigquery-dataframes/issues/1560)) ([bcac8c6](https://github.com/googleapis/python-bigquery-dataframes/commit/bcac8c6ed0b40902d0ccaef3f907e6acbe6a52ed)) -* Allow pandas.cut 'labels' parameter to accept a list of string ([#1549](https://github.com/googleapis/python-bigquery-dataframes/issues/1549)) ([af842b1](https://github.com/googleapis/python-bigquery-dataframes/commit/af842b174de7eef4908b397d6a745caf8eda7b3d)) -* Change default ingress setting for `remote_function` to internal-only ([#1544](https://github.com/googleapis/python-bigquery-dataframes/issues/1544)) ([c848a80](https://github.com/googleapis/python-bigquery-dataframes/commit/c848a80766ff68ea92c05a5dc5c26508e6755381)) -* Detect duplicate column/index names in read_gbq before send query. ([#1615](https://github.com/googleapis/python-bigquery-dataframes/issues/1615)) ([40d6960](https://github.com/googleapis/python-bigquery-dataframes/commit/40d696088114fb08e68df74be261144350b785c8)) -* Drop support for locational endpoints ([#1542](https://github.com/googleapis/python-bigquery-dataframes/issues/1542)) ([4bf2e43](https://github.com/googleapis/python-bigquery-dataframes/commit/4bf2e43ef4498b11f32086231fc4cc749fde966a)) -* Enable time range rolling for DataFrame, DataFrameGroupBy and SeriesGroupBy ([#1605](https://github.com/googleapis/python-bigquery-dataframes/issues/1605)) ([b4b7073](https://github.com/googleapis/python-bigquery-dataframes/commit/b4b7073da8348b6597bd3d90d1a758cd29586533)) -* Improve local data validation ([#1598](https://github.com/googleapis/python-bigquery-dataframes/issues/1598)) ([815e471](https://github.com/googleapis/python-bigquery-dataframes/commit/815e471b904d4bd708afc4bfbf1db945e76f75c9)) -* Make `remote_function` default service account explicit ([#1537](https://github.com/googleapis/python-bigquery-dataframes/issues/1537)) ([9eb9089](https://github.com/googleapis/python-bigquery-dataframes/commit/9eb9089ce3f1dad39761ba8ebc2d6f76261bd243)) -* Set `allow_large_results=False` by default ([#1541](https://github.com/googleapis/python-bigquery-dataframes/issues/1541)) ([e9fb712](https://github.com/googleapis/python-bigquery-dataframes/commit/e9fb7129a05e8ac7c938ffe30e86902950316f20)) -* Support bigquery connection in managed function ([#1554](https://github.com/googleapis/python-bigquery-dataframes/issues/1554)) ([f6f697a](https://github.com/googleapis/python-bigquery-dataframes/commit/f6f697afc167e0fa7ea923c0aed85a9ef257d61f)) -* Support bq connection path format ([#1550](https://github.com/googleapis/python-bigquery-dataframes/issues/1550)) ([e7eb918](https://github.com/googleapis/python-bigquery-dataframes/commit/e7eb918dd9df3569febe695f57c1a5909844fd3c)) -* Support gemini-2.0-X models ([#1558](https://github.com/googleapis/python-bigquery-dataframes/issues/1558)) ([3104fab](https://github.com/googleapis/python-bigquery-dataframes/commit/3104fab019d20b0cbc06cd81d43b3f34fd1dd987)) -* Support inlining small list, struct, json data ([#1589](https://github.com/googleapis/python-bigquery-dataframes/issues/1589)) ([2ce891f](https://github.com/googleapis/python-bigquery-dataframes/commit/2ce891fcd5bfd9f093fbcbb1ea35158d2bf9d8b9)) -* Support time range rolling on Series. ([#1590](https://github.com/googleapis/python-bigquery-dataframes/issues/1590)) ([6e98a2c](https://github.com/googleapis/python-bigquery-dataframes/commit/6e98a2cf53dd130963a9c5ba07e21ce6c32b7c6d)) -* Use session temp tables for all ephemeral storage ([#1569](https://github.com/googleapis/python-bigquery-dataframes/issues/1569)) ([9711b83](https://github.com/googleapis/python-bigquery-dataframes/commit/9711b830a7bdc6740f4ebeaaab6f37082ae5dfd9)) -* Use validated local storage for data uploads ([#1612](https://github.com/googleapis/python-bigquery-dataframes/issues/1612)) ([aee4159](https://github.com/googleapis/python-bigquery-dataframes/commit/aee4159807401d7432bb8c0c41859ada3291599b)) -* Warn the deprecated `max_download_size`, `random_state` and `sampling_method` parameters in `(DataFrame|Series).to_pandas()` ([#1573](https://github.com/googleapis/python-bigquery-dataframes/issues/1573)) ([b9623da](https://github.com/googleapis/python-bigquery-dataframes/commit/b9623daa847805abf420f0f11e173674fb147193)) - - -### Bug Fixes - -* `to_pandas_batches()` respects `page_size` and `max_results` again ([#1572](https://github.com/googleapis/python-bigquery-dataframes/issues/1572)) ([27c5905](https://github.com/googleapis/python-bigquery-dataframes/commit/27c59051549b83fdac954eaa3d257803c6f9133d)) -* Ensure `page_size` works correctly in `to_pandas_batches` when `max_results` is not set ([#1588](https://github.com/googleapis/python-bigquery-dataframes/issues/1588)) ([570cff3](https://github.com/googleapis/python-bigquery-dataframes/commit/570cff3c2efe3a47535bb3c931a345856d256a19)) -* Include role and service account in IAM exception ([#1564](https://github.com/googleapis/python-bigquery-dataframes/issues/1564)) ([8c50755](https://github.com/googleapis/python-bigquery-dataframes/commit/8c507556c5f61fab95c6389a8ad04d731df1df7b)) -* Make `dataset` and `name` params mandatory in `udf` ([#1619](https://github.com/googleapis/python-bigquery-dataframes/issues/1619)) ([637e860](https://github.com/googleapis/python-bigquery-dataframes/commit/637e860d3cea0a36b1e58a45ec9b9ab0059fb3b1)) -* Pandas.cut returns labels index for numeric breaks when labels=False ([#1548](https://github.com/googleapis/python-bigquery-dataframes/issues/1548)) ([b2375de](https://github.com/googleapis/python-bigquery-dataframes/commit/b2375decedbf1a793eedbbc9dc2efc2296f8cc6e)) -* Prevent `KeyError` in `bpd.concat` with empty DF and struct/array types DF ([#1568](https://github.com/googleapis/python-bigquery-dataframes/issues/1568)) ([b4da1cf](https://github.com/googleapis/python-bigquery-dataframes/commit/b4da1cf3c0fb94a2bb21e6039896accab85742d4)) -* Read_csv supports for tilde local paths and includes index for bigquery_stream write engine ([#1580](https://github.com/googleapis/python-bigquery-dataframes/issues/1580)) ([352e8e4](https://github.com/googleapis/python-bigquery-dataframes/commit/352e8e4b05cf19e970b47b017f958a1c6fc89bea)) -* Use dictionaries to avoid problematic google.iam namespace ([#1611](https://github.com/googleapis/python-bigquery-dataframes/issues/1611)) ([b03e44f](https://github.com/googleapis/python-bigquery-dataframes/commit/b03e44f7fca429a6de41c42ec28504b688cd84f0)) - - -### Performance Improvements - -* Directly read gbq table for simple plans ([#1607](https://github.com/googleapis/python-bigquery-dataframes/issues/1607)) ([6ad38e8](https://github.com/googleapis/python-bigquery-dataframes/commit/6ad38e8287354f62b0c5cad1f3d5b897256860ca)) - - -### Dependencies - -* Remove jellyfish dependency ([#1604](https://github.com/googleapis/python-bigquery-dataframes/issues/1604)) ([1ac0e1e](https://github.com/googleapis/python-bigquery-dataframes/commit/1ac0e1e82c097717338a6816f27c01b67736f51c)) -* Remove parsy dependency ([#1610](https://github.com/googleapis/python-bigquery-dataframes/issues/1610)) ([293f676](https://github.com/googleapis/python-bigquery-dataframes/commit/293f676e98446c417c12c345d5db875dd4c438df)) -* Remove test dependency on pytest-mock package ([#1622](https://github.com/googleapis/python-bigquery-dataframes/issues/1622)) ([1ba72ea](https://github.com/googleapis/python-bigquery-dataframes/commit/1ba72ead256178afee6f1d3303b0556bec1c4a9b)) -* Support a shapely versions 1.8.5+ ([#1621](https://github.com/googleapis/python-bigquery-dataframes/issues/1621)) ([e39ee3b](https://github.com/googleapis/python-bigquery-dataframes/commit/e39ee3bcf37f2a4f5e6ce981d248c24c6f5d770b)) - - -### Documentation - -* Add details for `bigquery_connection` in `[@bpd](https://github.com/bpd).udf` docstring ([#1609](https://github.com/googleapis/python-bigquery-dataframes/issues/1609)) ([ef63772](https://github.com/googleapis/python-bigquery-dataframes/commit/ef6377277bc9c354385c83ceba9e00094c0a6cc6)) -* Add explain forecast snippet to multiple time series tutorial ([#1586](https://github.com/googleapis/python-bigquery-dataframes/issues/1586)) ([40c55a0](https://github.com/googleapis/python-bigquery-dataframes/commit/40c55a06a529ca49d203227ccf36c12427d0cd5b)) -* Add message to remove default model for version 3.0 ([#1563](https://github.com/googleapis/python-bigquery-dataframes/issues/1563)) ([910be2b](https://github.com/googleapis/python-bigquery-dataframes/commit/910be2b5b2bfaf0e21cdc4fd775c1605a864c1aa)) -* Add samples for ArimaPlus `time_series_id_col` feature ([#1577](https://github.com/googleapis/python-bigquery-dataframes/issues/1577)) ([1e4cd9c](https://github.com/googleapis/python-bigquery-dataframes/commit/1e4cd9cf69f98d4af6b2a70bd8189c619b19baaa)) -* Add warning for bigframes 2.0 ([#1557](https://github.com/googleapis/python-bigquery-dataframes/issues/1557)) ([3f0eaa1](https://github.com/googleapis/python-bigquery-dataframes/commit/3f0eaa1c6b02d086270421f91dbb6aa2f117317d)) -* Deprecate default model in `TextEmbedddingGenerator`, `GeminiTextGenerator`, and other `bigframes.ml.llm` classes ([#1570](https://github.com/googleapis/python-bigquery-dataframes/issues/1570)) ([89ab33e](https://github.com/googleapis/python-bigquery-dataframes/commit/89ab33e1179aef142415fd5c9073671903bf1d45)) -* Include all licenses for vendored packages in the root LICENSE file ([#1626](https://github.com/googleapis/python-bigquery-dataframes/issues/1626)) ([8116ed0](https://github.com/googleapis/python-bigquery-dataframes/commit/8116ed0938634d301a153613f8a9cd8053ddf026)) -* Remove gemini-1.5 deprecation warning for `GeminiTextGenerator` ([#1562](https://github.com/googleapis/python-bigquery-dataframes/issues/1562)) ([0cc6784](https://github.com/googleapis/python-bigquery-dataframes/commit/0cc678448fdec1eaa3acfbb563a018325a8c85bc)) -* Use restructured text to allow publishing to PyPI ([#1565](https://github.com/googleapis/python-bigquery-dataframes/issues/1565)) ([d1e9ec2](https://github.com/googleapis/python-bigquery-dataframes/commit/d1e9ec2936d270ec4035014ea3ddd335a5747ade)) - - -### Miscellaneous Chores - -* Make `remote_function` params keyword only ([#1537](https://github.com/googleapis/python-bigquery-dataframes/issues/1537)) ([9eb9089](https://github.com/googleapis/python-bigquery-dataframes/commit/9eb9089ce3f1dad39761ba8ebc2d6f76261bd243)) - -## [1.42.0](https://github.com/googleapis/python-bigquery-dataframes/compare/v1.41.0...v1.42.0) (2025-03-27) - - -### Features - -* Add `closed` parameter in rolling() ([#1539](https://github.com/googleapis/python-bigquery-dataframes/issues/1539)) ([8bcc89b](https://github.com/googleapis/python-bigquery-dataframes/commit/8bcc89b30022f5ccf9ced80676a279c261c2f697)) -* Add `GeoSeries.difference()` and `bigframes.bigquery.st_difference()` ([#1471](https://github.com/googleapis/python-bigquery-dataframes/issues/1471)) ([e9fe815](https://github.com/googleapis/python-bigquery-dataframes/commit/e9fe8154d83e2674a05d7b670e949368b175ec8b)) -* Add `GeoSeries.intersection()` and `bigframes.bigquery.st_intersection()` ([#1529](https://github.com/googleapis/python-bigquery-dataframes/issues/1529)) ([8542bd4](https://github.com/googleapis/python-bigquery-dataframes/commit/8542bd469ff8775a9073f5a040b4117facfd8513)) -* Add df.take and series.take ([#1509](https://github.com/googleapis/python-bigquery-dataframes/issues/1509)) ([7d00be6](https://github.com/googleapis/python-bigquery-dataframes/commit/7d00be67cf50fdf713c40912f207d14f0f65538f)) -* Add Linear_Regression.global_explain() ([#1446](https://github.com/googleapis/python-bigquery-dataframes/issues/1446)) ([7e5b6a8](https://github.com/googleapis/python-bigquery-dataframes/commit/7e5b6a873d00162ffca3d254d3af276c5f06d866)) -* Allow iloc to support lists of negative indices ([#1497](https://github.com/googleapis/python-bigquery-dataframes/issues/1497)) ([a9cf215](https://github.com/googleapis/python-bigquery-dataframes/commit/a9cf215fb1403fda4ab2b58252f5fedc33aba3e1)) -* Support dry_run in `to_pandas()` ([#1436](https://github.com/googleapis/python-bigquery-dataframes/issues/1436)) ([75fc7e0](https://github.com/googleapis/python-bigquery-dataframes/commit/75fc7e0268dc5b10bdbc33dcf28db97dce62e41c)) -* Support window partition by geo column ([#1512](https://github.com/googleapis/python-bigquery-dataframes/issues/1512)) ([bdcb1e7](https://github.com/googleapis/python-bigquery-dataframes/commit/bdcb1e7929dc2f24c642ddb052629da394f45876)) -* Upgrade BQ managed `udf` to preview ([#1536](https://github.com/googleapis/python-bigquery-dataframes/issues/1536)) ([4a7fe4d](https://github.com/googleapis/python-bigquery-dataframes/commit/4a7fe4d75724e734634d41f18b4957e0877becc3)) - - -### Bug Fixes - -* Add deprecation warning to TextEmbeddingGenerator model, espeically gemini-1.0-X and gemini-1.5-X ([#1534](https://github.com/googleapis/python-bigquery-dataframes/issues/1534)) ([c93e720](https://github.com/googleapis/python-bigquery-dataframes/commit/c93e7204758435b0306699d3a1332aaf522f576b)) -* Change the default value for pdf extract/chunk ([#1517](https://github.com/googleapis/python-bigquery-dataframes/issues/1517)) ([a70a607](https://github.com/googleapis/python-bigquery-dataframes/commit/a70a607512797463f70ed529f078fcb2d40c85a1)) -* Local data always has sequential index ([#1514](https://github.com/googleapis/python-bigquery-dataframes/issues/1514)) ([014bd33](https://github.com/googleapis/python-bigquery-dataframes/commit/014bd33317966e15d05617c978e847de8c953453)) -* Read_pandas inline returns None when exceeds limit ([#1525](https://github.com/googleapis/python-bigquery-dataframes/issues/1525)) ([578081e](https://github.com/googleapis/python-bigquery-dataframes/commit/578081e978f2cca21ddae8b3ee371972ba723777)) -* Temporary fix for StreamingDataFrame not working backend bug ([#1533](https://github.com/googleapis/python-bigquery-dataframes/issues/1533)) ([6ab4ffd](https://github.com/googleapis/python-bigquery-dataframes/commit/6ab4ffd33d4900da833020ffa7ffc03a93a2b4b2)) -* Tolerate BQ connection service account propagation delay ([#1505](https://github.com/googleapis/python-bigquery-dataframes/issues/1505)) ([6681f1f](https://github.com/googleapis/python-bigquery-dataframes/commit/6681f1f9e30ed2325b85668de8a0b1d3d0e2858b)) - - -### Performance Improvements - -* Update shape to use quer_and_wait ([#1519](https://github.com/googleapis/python-bigquery-dataframes/issues/1519)) ([34ab9b8](https://github.com/googleapis/python-bigquery-dataframes/commit/34ab9b8abd2c632c806afe69f00d9e7dddb6a8b5)) - - -### Documentation - -* Update `GeoSeries.difference()` and `bigframes.bigquery.st_difference()` docs ([#1526](https://github.com/googleapis/python-bigquery-dataframes/issues/1526)) ([d553fa2](https://github.com/googleapis/python-bigquery-dataframes/commit/d553fa25fe85b3590269ed2ce08d5dff3bd22dfc)) - -## [1.41.0](https://github.com/googleapis/python-bigquery-dataframes/compare/v1.40.0...v1.41.0) (2025-03-19) - - -### Features - -* Add support for the 'right' parameter in 'pandas.cut' ([#1496](https://github.com/googleapis/python-bigquery-dataframes/issues/1496)) ([8aff128](https://github.com/googleapis/python-bigquery-dataframes/commit/8aff1285b26754118cc8ee906c4ac3076456a791)) -* Support BQ managed functions through `read_gbq_function` ([#1476](https://github.com/googleapis/python-bigquery-dataframes/issues/1476)) ([802183d](https://github.com/googleapis/python-bigquery-dataframes/commit/802183dc000ad2ce5559d14181dd3f7d036b3fed)) -* Warn when the BigFrames version is more than a year old ([#1455](https://github.com/googleapis/python-bigquery-dataframes/issues/1455)) ([00e0750](https://github.com/googleapis/python-bigquery-dataframes/commit/00e07508cfb0d8798e079b86a14834b3b593aa54)) - - -### Bug Fixes - -* Fix pandas.cut errors with empty bins ([#1499](https://github.com/googleapis/python-bigquery-dataframes/issues/1499)) ([434fb5d](https://github.com/googleapis/python-bigquery-dataframes/commit/434fb5dd60d11f09b808ea656394790aba43fdde)) -* Fix read_gbq with ORDER BY query and index_col set ([#963](https://github.com/googleapis/python-bigquery-dataframes/issues/963)) ([de46d2f](https://github.com/googleapis/python-bigquery-dataframes/commit/de46d2fdf7a1a30b2be07dbaa1cb127f10f5fe30)) - - -### Performance Improvements - -* Eliminate count queries in llm retry ([#1489](https://github.com/googleapis/python-bigquery-dataframes/issues/1489)) ([1c934c2](https://github.com/googleapis/python-bigquery-dataframes/commit/1c934c2fe2374c9abaaa79696f5e5f349248f3b7)) - - -### Documentation - -* Add a sample notebook for vector search ([#1500](https://github.com/googleapis/python-bigquery-dataframes/issues/1500)) ([f3bf139](https://github.com/googleapis/python-bigquery-dataframes/commit/f3bf139d33ed00ca3081e4e0315f409fdb2ad84d)) - -## [1.40.0](https://github.com/googleapis/python-bigquery-dataframes/compare/v1.39.0...v1.40.0) (2025-03-11) - - -### ⚠ BREAKING CHANGES - -* reading JSON data as a custom arrow extension type ([#1458](https://github.com/googleapis/python-bigquery-dataframes/issues/1458)) - -### Features - -* Reading JSON data as a custom arrow extension type ([#1458](https://github.com/googleapis/python-bigquery-dataframes/issues/1458)) ([e720f41](https://github.com/googleapis/python-bigquery-dataframes/commit/e720f41ef643ac14ae94fa98de5ef4a3fd6dde93)) -* Support list output for managed function ([#1457](https://github.com/googleapis/python-bigquery-dataframes/issues/1457)) ([461e9e0](https://github.com/googleapis/python-bigquery-dataframes/commit/461e9e017d513376fc623a5ee47f8b9dd002b452)) - - -### Bug Fixes - -* Fix list-like indexers in partial ordering mode ([#1456](https://github.com/googleapis/python-bigquery-dataframes/issues/1456)) ([fe72ada](https://github.com/googleapis/python-bigquery-dataframes/commit/fe72ada9cebb32947560c97567d7937c8b618f0d)) -* Fix the merge issue between 1424 and 1373 ([#1461](https://github.com/googleapis/python-bigquery-dataframes/issues/1461)) ([7b6e361](https://github.com/googleapis/python-bigquery-dataframes/commit/7b6e3615f8d4531beb4b59ca1223927112e713da)) -* Use `==` instead of `is` for timedelta type equality checks ([#1480](https://github.com/googleapis/python-bigquery-dataframes/issues/1480)) ([0db248b](https://github.com/googleapis/python-bigquery-dataframes/commit/0db248b5597a3966ac3dee1cca849509e48f4648)) - - -### Performance Improvements - -* Compilation no longer bounded by recursion ([#1464](https://github.com/googleapis/python-bigquery-dataframes/issues/1464)) ([27ab028](https://github.com/googleapis/python-bigquery-dataframes/commit/27ab028cdc45296923b12446c77b344af4208a3a)) - -## [1.39.0](https://github.com/googleapis/python-bigquery-dataframes/compare/v1.38.0...v1.39.0) (2025-03-05) - - -### Features - -* (Preview) Support `diff()` for date series ([#1423](https://github.com/googleapis/python-bigquery-dataframes/issues/1423)) ([521e987](https://github.com/googleapis/python-bigquery-dataframes/commit/521e9874f1c7dcd80e10bfd86f1b467b0f6d6d6e)) -* (Preview) Support aggregations over timedeltas ([#1418](https://github.com/googleapis/python-bigquery-dataframes/issues/1418)) ([1251ded](https://github.com/googleapis/python-bigquery-dataframes/commit/1251dedac8faf383c931185a057a8bb26afb4b8f)) -* (Preview) Support arithmetics between dates and timedeltas ([#1413](https://github.com/googleapis/python-bigquery-dataframes/issues/1413)) ([962b152](https://github.com/googleapis/python-bigquery-dataframes/commit/962b152ce5a368132d1ac14f6d8348b7ba285694)) -* (Preview) Support automatic load of timedelta from BQ tables. ([#1429](https://github.com/googleapis/python-bigquery-dataframes/issues/1429)) ([b2917bb](https://github.com/googleapis/python-bigquery-dataframes/commit/b2917bb57212ac399c20356755c878d179454bfe)) -* Add `allow_large_results` option to many I/O methods. Set to `False` to reduce latency ([#1428](https://github.com/googleapis/python-bigquery-dataframes/issues/1428)) ([dd2f488](https://github.com/googleapis/python-bigquery-dataframes/commit/dd2f48893eced458afecc93dc17b7e22735c39b9)) -* Add `GeoSeries.boundary()` ([#1435](https://github.com/googleapis/python-bigquery-dataframes/issues/1435)) ([32cddfe](https://github.com/googleapis/python-bigquery-dataframes/commit/32cddfecd25ff4208473574df09a8010f8be0de9)) -* Add allow_large_results to peek ([#1448](https://github.com/googleapis/python-bigquery-dataframes/issues/1448)) ([67487b9](https://github.com/googleapis/python-bigquery-dataframes/commit/67487b9a3bbe07f1b76e0332fab693b4c4022529)) -* Add groupby.rank() ([#1433](https://github.com/googleapis/python-bigquery-dataframes/issues/1433)) ([3a633d5](https://github.com/googleapis/python-bigquery-dataframes/commit/3a633d5cc9c3e6a2bd8311c8834b406db5cb8699)) -* Iloc multiple columns selection. ([#1437](https://github.com/googleapis/python-bigquery-dataframes/issues/1437)) ([ddfd02a](https://github.com/googleapis/python-bigquery-dataframes/commit/ddfd02a83040847f6d4642420d3bd32a4a855001)) -* Support interface for BigQuery managed functions ([#1373](https://github.com/googleapis/python-bigquery-dataframes/issues/1373)) ([2bbf53f](https://github.com/googleapis/python-bigquery-dataframes/commit/2bbf53f0d92dc669e1d775fafc54199f582d9059)) -* Warn if default ingress_settings is used in remote_functions ([#1419](https://github.com/googleapis/python-bigquery-dataframes/issues/1419)) ([dfd891a](https://github.com/googleapis/python-bigquery-dataframes/commit/dfd891a0102314e7542d0b0057442dcde3d9a4a1)) - - -### Bug Fixes - -* Do not compare schema description during schema validation ([#1452](https://github.com/googleapis/python-bigquery-dataframes/issues/1452)) ([03a3a56](https://github.com/googleapis/python-bigquery-dataframes/commit/03a3a5632ab187e1208cdc7133acfe0214243832)) -* Remove warnings for null index and partial ordering mode in prep for GA ([#1431](https://github.com/googleapis/python-bigquery-dataframes/issues/1431)) ([6785aee](https://github.com/googleapis/python-bigquery-dataframes/commit/6785aee97f4ee0c122d83e78409f9d6cc361b6d8)) -* Warn if default `cloud_function_service_account` is used in `remote_function` ([#1424](https://github.com/googleapis/python-bigquery-dataframes/issues/1424)) ([fe7463a](https://github.com/googleapis/python-bigquery-dataframes/commit/fe7463a69e616776df3f1b3bce4abdeaf7579f9b)) -* Window operations over JSON columns ([#1451](https://github.com/googleapis/python-bigquery-dataframes/issues/1451)) ([0070e77](https://github.com/googleapis/python-bigquery-dataframes/commit/0070e77579d0d0535d9f9a6c12641128e8a6dfbc)) -* Write chunked text instead of dummy text for pdf chunk ([#1444](https://github.com/googleapis/python-bigquery-dataframes/issues/1444)) ([96b0e8a](https://github.com/googleapis/python-bigquery-dataframes/commit/96b0e8a7a9d405c895ffd8ece56f4e3d04e0fbe5)) - - -### Performance Improvements - -* Speed up DataFrame corr, cov ([#1309](https://github.com/googleapis/python-bigquery-dataframes/issues/1309)) ([c598c0a](https://github.com/googleapis/python-bigquery-dataframes/commit/c598c0a1694ebc5a49bd92c837e4aaf1c311a899)) - - -### Documentation - -* Add snippet for explaining the linear regression model prediction ([#1427](https://github.com/googleapis/python-bigquery-dataframes/issues/1427)) ([7c37c7d](https://github.com/googleapis/python-bigquery-dataframes/commit/7c37c7d81c0cdc4647667daeebf13d47dabf3972)) - -## [1.38.0](https://github.com/googleapis/python-bigquery-dataframes/compare/v1.37.0...v1.38.0) (2025-02-24) - - -### Features - -* (Preview) Support diff aggregation for timestamp series. ([#1405](https://github.com/googleapis/python-bigquery-dataframes/issues/1405)) ([abe48d6](https://github.com/googleapis/python-bigquery-dataframes/commit/abe48d6f13a954534460fa14c9337e1085d9fbb3)) -* Add `GeoSeries.from_wkt() `and `GeoSeries.to_wkt()` ([#1401](https://github.com/googleapis/python-bigquery-dataframes/issues/1401)) ([2993b28](https://github.com/googleapis/python-bigquery-dataframes/commit/2993b283966960430ad8482f40f177e276db2d64)) -* Support DF.__array__(copy=True) ([#1403](https://github.com/googleapis/python-bigquery-dataframes/issues/1403)) ([693ed8c](https://github.com/googleapis/python-bigquery-dataframes/commit/693ed8cfb1ecc3af161801225d3e9cda489c29dd)) -* Support routines with ARRAY return type in `read_gbq_function` ([#1412](https://github.com/googleapis/python-bigquery-dataframes/issues/1412)) ([4b60049](https://github.com/googleapis/python-bigquery-dataframes/commit/4b60049e8362bfb07c136d8b2eb02b984d71f084)) - - -### Bug Fixes - -* Calling to_timdelta() over timedeltas no longer changes their values ([#1411](https://github.com/googleapis/python-bigquery-dataframes/issues/1411)) ([650a190](https://github.com/googleapis/python-bigquery-dataframes/commit/650a1907fdf84897eb7aa288863ee27d938e0879)) -* Replace empty dict with None to avoid mutable default arguments ([#1416](https://github.com/googleapis/python-bigquery-dataframes/issues/1416)) ([fa4e3ad](https://github.com/googleapis/python-bigquery-dataframes/commit/fa4e3ad8bcd5db56fa26b26609cc7e58b1edf498)) - - -### Performance Improvements - -* Avoid redundant SQL casts ([#1399](https://github.com/googleapis/python-bigquery-dataframes/issues/1399)) ([6ee48d5](https://github.com/googleapis/python-bigquery-dataframes/commit/6ee48d5c16870f1caa99c3f658c2c1a0e14be749)) - - -### Dependencies - -* Remove scikit-learn and sqlalchemy as required dependencies ([#1296](https://github.com/googleapis/python-bigquery-dataframes/issues/1296)) ([fd8bc89](https://github.com/googleapis/python-bigquery-dataframes/commit/fd8bc894bdbdf551ebbec1fb93832588371ae6af)) - - -### Documentation - -* Add samples using SQL methods via the `bigframes.bigquery` module ([#1358](https://github.com/googleapis/python-bigquery-dataframes/issues/1358)) ([f54e768](https://github.com/googleapis/python-bigquery-dataframes/commit/f54e7688fda6372c6decc9b61796b0272d803c79)) -* Add snippets for visualizing a time series and creating a time series model for the Limit forecasted values in time series model tutorial ([#1310](https://github.com/googleapis/python-bigquery-dataframes/issues/1310)) ([c6c9120](https://github.com/googleapis/python-bigquery-dataframes/commit/c6c9120e839647e5b3cb97f04a8d90cc8690b8a3)) - -## [1.37.0](https://github.com/googleapis/python-bigquery-dataframes/compare/v1.36.0...v1.37.0) (2025-02-19) - - -### Features - -* (Preview) Support add, sub, mult, div, and more between timedeltas ([#1396](https://github.com/googleapis/python-bigquery-dataframes/issues/1396)) ([ffa63d4](https://github.com/googleapis/python-bigquery-dataframes/commit/ffa63d47ca1dd1a18617f44d9b3bc33419656a20)) -* (Preview) Support comparison, ordering, and filtering for timedeltas ([#1387](https://github.com/googleapis/python-bigquery-dataframes/issues/1387)) ([34d01b2](https://github.com/googleapis/python-bigquery-dataframes/commit/34d01b27f867abf10bddffdf4f88fa7052cd237c)) -* (Preview) Support subtraction in DATETIME/TIMESTAMP columns with timedelta columns ([#1390](https://github.com/googleapis/python-bigquery-dataframes/issues/1390)) ([50ad3a5](https://github.com/googleapis/python-bigquery-dataframes/commit/50ad3a56e9bd77bb77d60d7d5ec497e3335a7177)) -* JSON dtype support for read_pandas and Series constructor ([#1391](https://github.com/googleapis/python-bigquery-dataframes/issues/1391)) ([44f4137](https://github.com/googleapis/python-bigquery-dataframes/commit/44f4137adb02790e07c696f0641bc58390857210)) - - -### Bug Fixes - -* Ensure binops with pandas objects returns bigquery dataframes ([#1404](https://github.com/googleapis/python-bigquery-dataframes/issues/1404)) ([3cee24b](https://github.com/googleapis/python-bigquery-dataframes/commit/3cee24bae1d352015a5b6a8c18d5c394293d08fd)) - - -### Performance Improvements - -* Prune projections more aggressively ([#1398](https://github.com/googleapis/python-bigquery-dataframes/issues/1398)) ([7990262](https://github.com/googleapis/python-bigquery-dataframes/commit/7990262cf09e97c0739be922ede151d616655726)) -* Simplify sum aggregate SQL text ([#1395](https://github.com/googleapis/python-bigquery-dataframes/issues/1395)) ([0145656](https://github.com/googleapis/python-bigquery-dataframes/commit/0145656e5e378442f2f38f9f04e87e33ddf345f5)) -* Use simple null constraints to simplify queries ([#1381](https://github.com/googleapis/python-bigquery-dataframes/issues/1381)) ([00611d4](https://github.com/googleapis/python-bigquery-dataframes/commit/00611d4d697a8b74451375f5a7700b92a4410295)) - - -### Documentation - -* Add DataFrame.struct docs ([#1348](https://github.com/googleapis/python-bigquery-dataframes/issues/1348)) ([7e9e93a](https://github.com/googleapis/python-bigquery-dataframes/commit/7e9e93aafd26cbfec9a1710caaf97937bcb6ee05)) - -## [1.36.0](https://github.com/googleapis/python-bigquery-dataframes/compare/v1.35.0...v1.36.0) (2025-02-11) - - -### Features - -* (Preview) Support addition between a timestamp and a timedelta ([#1369](https://github.com/googleapis/python-bigquery-dataframes/issues/1369)) ([b598aa8](https://github.com/googleapis/python-bigquery-dataframes/commit/b598aa8ef4f6dd0cbca7629d290c5e511cdc86fc)) -* (Preview) Support casting floats and list-likes to timedelta series ([#1362](https://github.com/googleapis/python-bigquery-dataframes/issues/1362)) ([65933b6](https://github.com/googleapis/python-bigquery-dataframes/commit/65933b6b7608ec52717e818d8ec1732fb756b67b)) -* (Preview) Support timestamp subtractions ([#1346](https://github.com/googleapis/python-bigquery-dataframes/issues/1346)) ([86b7e72](https://github.com/googleapis/python-bigquery-dataframes/commit/86b7e72097ce67d88b72cfe031080d5af22f65cd)) -* Add `bigframes.bigquery.st_area` and suggest it from `GeoSeries.area` ([#1318](https://github.com/googleapis/python-bigquery-dataframes/issues/1318)) ([8b5ffa8](https://github.com/googleapis/python-bigquery-dataframes/commit/8b5ffa8893b51016c51794865c40def74ea6716b)) -* Add `GeoSeries.from_xy()` ([#1364](https://github.com/googleapis/python-bigquery-dataframes/issues/1364)) ([3c3e14c](https://github.com/googleapis/python-bigquery-dataframes/commit/3c3e14c715f476ca44f254c0d53d639ea5988a8d)) - - -### Bug Fixes - -* Dtype parameter ineffective in Series/DataFrame construction ([#1354](https://github.com/googleapis/python-bigquery-dataframes/issues/1354)) ([b9bdca8](https://github.com/googleapis/python-bigquery-dataframes/commit/b9bdca8285ee54fecf3795fbf3cbea6f878ee8ca)) -* Translate labels to col ids when copying dataframes ([#1372](https://github.com/googleapis/python-bigquery-dataframes/issues/1372)) ([0c55b07](https://github.com/googleapis/python-bigquery-dataframes/commit/0c55b07dc001b568875f06d578ca7d59409f2a11)) - - -### Performance Improvements - -* Prune unused operations from sql ([#1365](https://github.com/googleapis/python-bigquery-dataframes/issues/1365)) ([923da03](https://github.com/googleapis/python-bigquery-dataframes/commit/923da037ef6e4e7f8b54924ea5644c2c5ceb2234)) -* Simplify merge join key coalescing ([#1361](https://github.com/googleapis/python-bigquery-dataframes/issues/1361)) ([7ae565d](https://github.com/googleapis/python-bigquery-dataframes/commit/7ae565d9e0e59fdf75c7659c0263562688ccc1e8)) - -## [1.35.0](https://github.com/googleapis/python-bigquery-dataframes/compare/v1.34.0...v1.35.0) (2025-02-04) - - -### Features - -* (Preview) Support timedeltas for read_pandas() ([#1349](https://github.com/googleapis/python-bigquery-dataframes/issues/1349)) ([866ba9e](https://github.com/googleapis/python-bigquery-dataframes/commit/866ba9efb54f11c1fc2ced0d7995fff86277b049)) -* Add Series.keys() ([#1342](https://github.com/googleapis/python-bigquery-dataframes/issues/1342)) ([deb015d](https://github.com/googleapis/python-bigquery-dataframes/commit/deb015dc1276549519d51363501355272f8976d8)) -* Allow `case_when` to change dtypes if case list contains the condition `(True, some_default_value)` ([#1311](https://github.com/googleapis/python-bigquery-dataframes/issues/1311)) ([5c2a2c6](https://github.com/googleapis/python-bigquery-dataframes/commit/5c2a2c6086be20cba7da08ecd37899699aab518f)) -* Support python type as astype arg ([#1316](https://github.com/googleapis/python-bigquery-dataframes/issues/1316)) ([b26e135](https://github.com/googleapis/python-bigquery-dataframes/commit/b26e13570f198ec4d252590a8c07253624db667a)) -* Support time_series_id_col in ARIMAPlus ([#1282](https://github.com/googleapis/python-bigquery-dataframes/issues/1282)) ([97532c9](https://github.com/googleapis/python-bigquery-dataframes/commit/97532c9ba02cd709d69666dd0afca5c1df8b9faf)) - - -### Bug Fixes - -* Exclude `DataFrame` and `Series` `__call__` from unimplemented API metrics ([#1351](https://github.com/googleapis/python-bigquery-dataframes/issues/1351)) ([f2d5264](https://github.com/googleapis/python-bigquery-dataframes/commit/f2d526445da7dae29c49c8d6dacdfee7d2fa9d79)) -* Make `DataFrame` `__getattr__` and `__setattr__` more robust to subclassing ([#1352](https://github.com/googleapis/python-bigquery-dataframes/issues/1352)) ([417de3a](https://github.com/googleapis/python-bigquery-dataframes/commit/417de3a449e5d0748831b502f4f5b9fb9ba38714)) - - -### Performance Improvements - -* Fall back to ordering by bq pk when possible ([#1350](https://github.com/googleapis/python-bigquery-dataframes/issues/1350)) ([3c4abf2](https://github.com/googleapis/python-bigquery-dataframes/commit/3c4abf24ea186e98f629b6f83c0f3e36dc0571c6)) -* Improve isin performance ([#1203](https://github.com/googleapis/python-bigquery-dataframes/issues/1203)) ([db087b0](https://github.com/googleapis/python-bigquery-dataframes/commit/db087b0bfe4b3ba965682d620079c923e098e362)) -* Prevent inlining of remote ops ([#1347](https://github.com/googleapis/python-bigquery-dataframes/issues/1347)) ([012081a](https://github.com/googleapis/python-bigquery-dataframes/commit/012081af9ef825ced96ec1e772b9646cbe09d9a1)) - - -### Dependencies - -* Add support for Python 3.13 for everything but remote functions ([#1307](https://github.com/googleapis/python-bigquery-dataframes/issues/1307)) ([533db96](https://github.com/googleapis/python-bigquery-dataframes/commit/533db9685d159de2bc76307b0e0add676bd679a0)) - - -### Documentation - -* Add `GeoSeries` docs ([#1327](https://github.com/googleapis/python-bigquery-dataframes/issues/1327)) ([05f83d1](https://github.com/googleapis/python-bigquery-dataframes/commit/05f83d18d276091a1549dbba1f2baf8c91c8c37e)) -* Add link to DataFrames intro to improve SEO ([#1176](https://github.com/googleapis/python-bigquery-dataframes/issues/1176)) ([aafb5be](https://github.com/googleapis/python-bigquery-dataframes/commit/aafb5be3e9c50f477fca2a1ebb5338194672913f)) -* Add snippet to explain the univariate model's forecast result in the Forecast a single time series with a univariate model tutorial ([#1272](https://github.com/googleapis/python-bigquery-dataframes/issues/1272)) ([c22126b](https://github.com/googleapis/python-bigquery-dataframes/commit/c22126b846db428d21c0f5cbd2d439ecc56365b2)) - -## [1.34.0](https://github.com/googleapis/python-bigquery-dataframes/compare/v1.33.0...v1.34.0) (2025-01-27) - - -### ⚠ BREAKING CHANGES - -* Enable reading JSON data with `dbjson` extension dtype ([#1139](https://github.com/googleapis/python-bigquery-dataframes/issues/1139)) - -### Features - -* (df|s).hist(), (df|s).line(), (df|s).area(), (df|s).bar(), df.scatter() ([#1320](https://github.com/googleapis/python-bigquery-dataframes/issues/1320)) ([bd3f584](https://github.com/googleapis/python-bigquery-dataframes/commit/bd3f584a7eab5d01dedebb7ca2485942ef5b5ebe)) -* (Preview) Define timedelta type and to_timedelta function ([#1317](https://github.com/googleapis/python-bigquery-dataframes/issues/1317)) ([3901951](https://github.com/googleapis/python-bigquery-dataframes/commit/39019510d0c2758096589ecd0d83175f313a8cf5)) -* Add DataFrame.corrwith method ([#1315](https://github.com/googleapis/python-bigquery-dataframes/issues/1315)) ([b503355](https://github.com/googleapis/python-bigquery-dataframes/commit/b5033559a77a9bc5ffb7dc1e44e02aaaaf1e051e)) -* Add DataFrame.mask method ([#1302](https://github.com/googleapis/python-bigquery-dataframes/issues/1302)) ([8b8155f](https://github.com/googleapis/python-bigquery-dataframes/commit/8b8155fef9c5cd36cfabf728ccebf6a14a1cbbda)) -* Enable reading JSON data with `dbjson` extension dtype ([#1139](https://github.com/googleapis/python-bigquery-dataframes/issues/1139)) ([f672262](https://github.com/googleapis/python-bigquery-dataframes/commit/f6722629fb47eed5befb0ecae2e6b5ec9042d669)) - -## [1.33.0](https://github.com/googleapis/python-bigquery-dataframes/compare/v1.32.0...v1.33.0) (2025-01-22) - - -### Features - -* Add `bigframes.bigquery.sql_scalar()` to apply SQL syntax on Series objects ([#1293](https://github.com/googleapis/python-bigquery-dataframes/issues/1293)) ([aa2f73a](https://github.com/googleapis/python-bigquery-dataframes/commit/aa2f73ad86e42c37d85ac867a3702eb6f2724b11)) -* Add unix_seconds, unix_millis and unix_micros for timestamp series. ([#1297](https://github.com/googleapis/python-bigquery-dataframes/issues/1297)) ([e4b0c8d](https://github.com/googleapis/python-bigquery-dataframes/commit/e4b0c8dd9edda48e07c433b99f44db82e1ea2054)) -* DataFrame.join supports Series other ([#1303](https://github.com/googleapis/python-bigquery-dataframes/issues/1303)) ([ee37a0a](https://github.com/googleapis/python-bigquery-dataframes/commit/ee37a0ab84e9415046e0e15955c14a1965b3a904)) -* Support array output in `remote_function` ([#1057](https://github.com/googleapis/python-bigquery-dataframes/issues/1057)) ([bdee173](https://github.com/googleapis/python-bigquery-dataframes/commit/bdee1734809589e5a7a3c23ee9cd2f967adf346f)) - - -### Bug Fixes - -* Dataframe sort_values Series input keyerror. ([#1285](https://github.com/googleapis/python-bigquery-dataframes/issues/1285)) ([5a2731b](https://github.com/googleapis/python-bigquery-dataframes/commit/5a2731bda8b2e9ea54bf582f823acdb6153dbb8f)) -* Fix read_gbq_function issue in dataframe apply method ([#1174](https://github.com/googleapis/python-bigquery-dataframes/issues/1174)) ([0318764](https://github.com/googleapis/python-bigquery-dataframes/commit/0318764030f6753a4e925c62612aabbb8e192fdf)) -* Series sort_index and sort_values now raises when axis!=0 ([#1294](https://github.com/googleapis/python-bigquery-dataframes/issues/1294)) ([94bc2f2](https://github.com/googleapis/python-bigquery-dataframes/commit/94bc2f2dc3514fffeac625592ec4b28c32957723)) - - -### Documentation - -* Add snippet to forecast future time series in the Forecast a single time series with a univariate model tutorial ([#1271](https://github.com/googleapis/python-bigquery-dataframes/issues/1271)) ([a687050](https://github.com/googleapis/python-bigquery-dataframes/commit/a687050b2a92bed1af9cb86a812b62f9a69cf959)) -* Update `bigframes.pandas.Series` docs ([#1273](https://github.com/googleapis/python-bigquery-dataframes/issues/1273)) ([0cac64f](https://github.com/googleapis/python-bigquery-dataframes/commit/0cac64f5ba3f3c9e8495fc5acb09d81c39d36de0)) - -## [1.32.0](https://github.com/googleapis/python-bigquery-dataframes/compare/v1.31.0...v1.32.0) (2025-01-13) - - -### Features - -* Add max_retries to TextEmbeddingGenerator and Claude3TextGenerator ([#1259](https://github.com/googleapis/python-bigquery-dataframes/issues/1259)) ([8077ff4](https://github.com/googleapis/python-bigquery-dataframes/commit/8077ff49426b103dc5a52eeb86a2c6a869c99825)) -* Bigframes.bigquery.parse_json ([#1265](https://github.com/googleapis/python-bigquery-dataframes/issues/1265)) ([27bbd80](https://github.com/googleapis/python-bigquery-dataframes/commit/27bbd8085ccac175f113afbd6c94b52c034a3d97)) -* Support DataFrame.astype(dict) ([#1262](https://github.com/googleapis/python-bigquery-dataframes/issues/1262)) ([5934f8e](https://github.com/googleapis/python-bigquery-dataframes/commit/5934f8ee0a1c950a820d1911d73a46f6891a40bb)) - - -### Bug Fixes - -* Avoid global mutation in `BigQueryOptions.client_endpoints_override` ([#1280](https://github.com/googleapis/python-bigquery-dataframes/issues/1280)) ([788f6e9](https://github.com/googleapis/python-bigquery-dataframes/commit/788f6e94a1e80f0ba8741a53a05a467e7b18e902)) -* Fix erroneous window bounds removal during compilation ([#1163](https://github.com/googleapis/python-bigquery-dataframes/issues/1163)) ([f91756a](https://github.com/googleapis/python-bigquery-dataframes/commit/f91756a4413b10f1072c0ae96301fe854bb1ba4e)) - - -### Dependencies - -* Relax sqlglot upper bound ([#1278](https://github.com/googleapis/python-bigquery-dataframes/issues/1278)) ([c71ec09](https://github.com/googleapis/python-bigquery-dataframes/commit/c71ec093314409cd4c7a52a713dbd6164fbbd792)) - - -### Documentation - -* Add bq studio links that allows users to generate Jupiter notebooks in bq studio with github contents ([#1266](https://github.com/googleapis/python-bigquery-dataframes/issues/1266)) ([58f13cb](https://github.com/googleapis/python-bigquery-dataframes/commit/58f13cb9ef8bac3222e5013d8ae77dd20f886e30)) -* Add snippet to evaluate ARIMA plus model in the Forecast a single time series with a univariate model tutorial ([#1267](https://github.com/googleapis/python-bigquery-dataframes/issues/1267)) ([3dcae2d](https://github.com/googleapis/python-bigquery-dataframes/commit/3dcae2dca45efdd4493cf3f367bf025ea291f4df)) -* Add snippet to see the ARIMA coefficients in the Forecast a single time series with a univariate model tutorial ([#1268](https://github.com/googleapis/python-bigquery-dataframes/issues/1268)) ([059a564](https://github.com/googleapis/python-bigquery-dataframes/commit/059a564095dfea0518982f13c8118d3807861ccf)) -* Update `bigframes.pandas.pandas` docstrings ([#1247](https://github.com/googleapis/python-bigquery-dataframes/issues/1247)) ([c4bffc3](https://github.com/googleapis/python-bigquery-dataframes/commit/c4bffc3e8ec630a362c94f9d269a66073a14ad04)) -* Use 002 model for better scalability in text generation ([#1270](https://github.com/googleapis/python-bigquery-dataframes/issues/1270)) ([bb7a850](https://github.com/googleapis/python-bigquery-dataframes/commit/bb7a85005ebebfbcb0d2a4d5c4c27b354f38d3d1)) - -## [1.31.0](https://github.com/googleapis/python-bigquery-dataframes/compare/v1.30.0...v1.31.0) (2025-01-05) - - -### Features - -* Implement confirmation threshold for semantic operators ([#1251](https://github.com/googleapis/python-bigquery-dataframes/issues/1251)) ([5ba4511](https://github.com/googleapis/python-bigquery-dataframes/commit/5ba4511ad85cf02f0e5ad4e33ea3826b19527293)) - - -### Bug Fixes - -* Raise if trying to change `ordering_mode` after session has started ([#1252](https://github.com/googleapis/python-bigquery-dataframes/issues/1252)) ([8cfaae8](https://github.com/googleapis/python-bigquery-dataframes/commit/8cfaae8718f3c4c6739b7155a02ef13dbed73425)) -* Reduce the number of labels added to query jobs ([#1245](https://github.com/googleapis/python-bigquery-dataframes/issues/1245)) ([fdcdc18](https://github.com/googleapis/python-bigquery-dataframes/commit/fdcdc189e5fcae9de68bf8fb3872136f55be36cb)) - - -### Documentation - -* Remove bq studio link ([#1258](https://github.com/googleapis/python-bigquery-dataframes/issues/1258)) ([dd4fd2e](https://github.com/googleapis/python-bigquery-dataframes/commit/dd4fd2e8bafa73b4b5d99f095943bd9a757cd5b5)) -* Update bigframes.pandas.DatetimeMethods docstrings ([#1246](https://github.com/googleapis/python-bigquery-dataframes/issues/1246)) ([10f08da](https://github.com/googleapis/python-bigquery-dataframes/commit/10f08daec6034aafe48096be56683c953accc79a)) -* Update semantic_operators.ipynb ([#1260](https://github.com/googleapis/python-bigquery-dataframes/issues/1260)) ([a2ed989](https://github.com/googleapis/python-bigquery-dataframes/commit/a2ed989fac789b0debacc0ec8a044b473cc6112c)) - -## [1.30.0](https://github.com/googleapis/python-bigquery-dataframes/compare/v1.29.0...v1.30.0) (2024-12-30) - - -### Features - -* Add `GeoSeries.x` and `GeoSeries.y` ([#1126](https://github.com/googleapis/python-bigquery-dataframes/issues/1126)) ([4c3548f](https://github.com/googleapis/python-bigquery-dataframes/commit/4c3548f060ba7ce649aa368fa9367dfc769ae0c3)) -* Add `LinearRegression.predict_explain()` to generate `ML.EXPLAIN_PREDICT` columns ([#1190](https://github.com/googleapis/python-bigquery-dataframes/issues/1190)) ([e13eca2](https://github.com/googleapis/python-bigquery-dataframes/commit/e13eca2128b2bf8a914a5ce781b82dffb95563a8)) -* Add `LogisticRegression.predict_explain()` to generate `ML.EXPLAIN_PREDICT` columns ([#1222](https://github.com/googleapis/python-bigquery-dataframes/issues/1222)) ([bcbc732](https://github.com/googleapis/python-bigquery-dataframes/commit/bcbc732f321ab31f8fb6b995aeb908ac87750587)) -* Add `write_engine` parameter to `read_FORMATNAME` methods to control how data is written to BigQuery ([#371](https://github.com/googleapis/python-bigquery-dataframes/issues/371)) ([ed47ef1](https://github.com/googleapis/python-bigquery-dataframes/commit/ed47ef16ba6f4ae67a128712fd67113aefe08467)) -* Add client side retry to GeminiTextGenerator ([#1242](https://github.com/googleapis/python-bigquery-dataframes/issues/1242)) ([8193abe](https://github.com/googleapis/python-bigquery-dataframes/commit/8193abe395c5648db8169818eca29aee76c46478)) -* Add Gemini-pro-1.5 to GeminiTextGenerator Tuning and Support score() method in Gemini-pro-1.5 ([#1208](https://github.com/googleapis/python-bigquery-dataframes/issues/1208)) ([298fc73](https://github.com/googleapis/python-bigquery-dataframes/commit/298fc73985daf565033347dcf40afd0d5560c717)) -* Add support for `LinearRegression.predict_explain` and `LogisticRegression.predict_explain` parameter, `top_k_features` ([#1228](https://github.com/googleapis/python-bigquery-dataframes/issues/1228)) ([3068e19](https://github.com/googleapis/python-bigquery-dataframes/commit/3068e19495f99d2d7c39c67672350d0b411f79b7)) -* Support dataframe where method ([#1166](https://github.com/googleapis/python-bigquery-dataframes/issues/1166)) ([71b4053](https://github.com/googleapis/python-bigquery-dataframes/commit/71b4053f855239cc3b2f659a6bfa776e38a1d4d3)) - - -### Bug Fixes - -* Arima model series input. ([#1237](https://github.com/googleapis/python-bigquery-dataframes/issues/1237)) ([f7d52d9](https://github.com/googleapis/python-bigquery-dataframes/commit/f7d52d916e8fb6362abc56b3a27cdd994e994214)) -* Json in struct destination type ([#1187](https://github.com/googleapis/python-bigquery-dataframes/issues/1187)) ([200c9bb](https://github.com/googleapis/python-bigquery-dataframes/commit/200c9bbcf020913710de86822e2e2917484932fa)) -* Throw an error message when setting is_row_processor=True to read a multi param function ([#1160](https://github.com/googleapis/python-bigquery-dataframes/issues/1160)) ([b2816a5](https://github.com/googleapis/python-bigquery-dataframes/commit/b2816a5df2d03b97757b46a004ac54d86d1e26a1)) - - -### Documentation - -* Add an "open in BQ Studio" link to all BigFrames sample notebooks ([#1223](https://github.com/googleapis/python-bigquery-dataframes/issues/1223)) ([e0a8288](https://github.com/googleapis/python-bigquery-dataframes/commit/e0a82888cd34fa2404ac68229dc38496cb22c67b)) -* Add bq studio link for a new ipynb file called "bq_dataframes_template.ipynb" ([#1239](https://github.com/googleapis/python-bigquery-dataframes/issues/1239)) ([840aaff](https://github.com/googleapis/python-bigquery-dataframes/commit/840aaff6d5895ef0594a4f02bde03143c36e7d82)) -* Add example for logistic regression ([#1240](https://github.com/googleapis/python-bigquery-dataframes/issues/1240)) ([4d854fd](https://github.com/googleapis/python-bigquery-dataframes/commit/4d854fd6c7b6b7c2322032d720befc773cc56412)) -* Add examples for ml PCA and SimpleImputer ([#1236](https://github.com/googleapis/python-bigquery-dataframes/issues/1236)) ([0d84459](https://github.com/googleapis/python-bigquery-dataframes/commit/0d84459a083bbad2cb694da0256c4ff4a2438d4e)) -* Add KMeans example ([#1234](https://github.com/googleapis/python-bigquery-dataframes/issues/1234)) ([d87ab97](https://github.com/googleapis/python-bigquery-dataframes/commit/d87ab97011d09784ab528ec1ab1df7f3591502a6)) -* Add linear model example ([#1235](https://github.com/googleapis/python-bigquery-dataframes/issues/1235)) ([2c3e1fd](https://github.com/googleapis/python-bigquery-dataframes/commit/2c3e1fde7614057ac3deb637993134e7a9661c3d)) -* Add ml.model_selection examples ([#1238](https://github.com/googleapis/python-bigquery-dataframes/issues/1238)) ([50648e4](https://github.com/googleapis/python-bigquery-dataframes/commit/50648e4d5d7c0b8b41d9a9605a9923ead73a7831)) -* Add python snippet for "Create the time series model" section of the Forecast a single time series with a univariate model tutorial ([#1227](https://github.com/googleapis/python-bigquery-dataframes/issues/1227)) ([20f3190](https://github.com/googleapis/python-bigquery-dataframes/commit/20f3190d2fc26846f55328a7481de70e9fe3f84b)) - -## [1.29.0](https://github.com/googleapis/python-bigquery-dataframes/compare/v1.28.0...v1.29.0) (2024-12-12) - - -### Features - -* Add Gemini 2.0 preview text model support ([#1209](https://github.com/googleapis/python-bigquery-dataframes/issues/1209)) ([1021d57](https://github.com/googleapis/python-bigquery-dataframes/commit/1021d5761a291f2327fc10216e938826e53dbcc4)) - - -### Documentation - -* Add Gemini 2.0 text gen sample notebook ([#1211](https://github.com/googleapis/python-bigquery-dataframes/issues/1211)) ([9596b66](https://github.com/googleapis/python-bigquery-dataframes/commit/9596b66a8a41f5e5db6fa5f87b01c5363ffa89c4)) -* Update bigframes.pandas.index docs return types ([#1191](https://github.com/googleapis/python-bigquery-dataframes/issues/1191)) ([c63e7da](https://github.com/googleapis/python-bigquery-dataframes/commit/c63e7dad6fe67f5769ddcdd1730666580a7e7a05)) - -## [1.28.0](https://github.com/googleapis/python-bigquery-dataframes/compare/v1.27.0...v1.28.0) (2024-12-11) - - -### Features - -* (Series | DataFrame).plot.bar ([#1152](https://github.com/googleapis/python-bigquery-dataframes/issues/1152)) ([0fae2e0](https://github.com/googleapis/python-bigquery-dataframes/commit/0fae2e0291ec8d22341b5b543e8f1b384f83cd3c)) -* `bigframes.bigquery.vector_search` supports `use_brute_force` and `fraction_lists_to_search` parameters ([#1158](https://github.com/googleapis/python-bigquery-dataframes/issues/1158)) ([131edc3](https://github.com/googleapis/python-bigquery-dataframes/commit/131edc3d79f46d35a25422f0db7f150e63e8f561)) -* Add `ARIMAPlus.predict_explain()` to generate forecasts with explanation columns ([#1177](https://github.com/googleapis/python-bigquery-dataframes/issues/1177)) ([05f8b4d](https://github.com/googleapis/python-bigquery-dataframes/commit/05f8b4d2b2b5f624097228e65a3c42364fc40d36)) -* Add client_endpoints_override to bq options ([#1167](https://github.com/googleapis/python-bigquery-dataframes/issues/1167)) ([be74b99](https://github.com/googleapis/python-bigquery-dataframes/commit/be74b99977cfbd513def5b7e439de6b7706c0712)) -* Add support for temporal types in dataframe's describe() method ([#1189](https://github.com/googleapis/python-bigquery-dataframes/issues/1189)) ([2d564a6](https://github.com/googleapis/python-bigquery-dataframes/commit/2d564a6a9925b69c7e9a15b532fb66ad68c3e264)) -* Allow join-free alignment of analytic expressions ([#1168](https://github.com/googleapis/python-bigquery-dataframes/issues/1168)) ([daef4f0](https://github.com/googleapis/python-bigquery-dataframes/commit/daef4f0c7c5ff2d0a4e9a6ffefeb81f43780ac8b)) -* Series.isin supports bigframes.Series arg ([#1195](https://github.com/googleapis/python-bigquery-dataframes/issues/1195)) ([0d8a16b](https://github.com/googleapis/python-bigquery-dataframes/commit/0d8a16ba77a66dce544d0a7cf411fca0adc2a694)) -* Update llm.TextEmbeddingGenerator to 005 ([#1186](https://github.com/googleapis/python-bigquery-dataframes/issues/1186)) ([3072d38](https://github.com/googleapis/python-bigquery-dataframes/commit/3072d382c6ff57bdb37d7e080c794c67dbf6e701)) - - -### Bug Fixes - -* Fix error loading local dataframes into bigquery ([#1165](https://github.com/googleapis/python-bigquery-dataframes/issues/1165)) ([5b355ef](https://github.com/googleapis/python-bigquery-dataframes/commit/5b355efde122ed76b1cff39900ab8f94f5a13a30)) -* Fix null index join with 'on' arg ([#1153](https://github.com/googleapis/python-bigquery-dataframes/issues/1153)) ([9015c33](https://github.com/googleapis/python-bigquery-dataframes/commit/9015c33e73675ebb2299487dce3295732ea0527e)) -* Fix series.isin using local path always ([#1202](https://github.com/googleapis/python-bigquery-dataframes/issues/1202)) ([a44eafd](https://github.com/googleapis/python-bigquery-dataframes/commit/a44eafdd95eb1b994dc82411640b61fd0a78a492)) - - -### Performance Improvements - -* Update df.corr, df.cov to be used with more than 30 columns case. ([#1161](https://github.com/googleapis/python-bigquery-dataframes/issues/1161)) ([9dcf1aa](https://github.com/googleapis/python-bigquery-dataframes/commit/9dcf1aa918919704dcf4d12b05935b22fb502fc6)) - - -### Dependencies - -* Remove `ibis-framework` by vendoring a fork of the package to `bigframes_vendored`. ([#1170](https://github.com/googleapis/python-bigquery-dataframes/pull/1170)) ([421d24d](https://github.com/googleapis/python-bigquery-dataframes/commit/421d24d6e61d557aa696fc701c08c84389f72ed2)) - - -### Documentation - -* Add a code sample using `bpd.options.bigquery.ordering_mode = "partial"` ([#909](https://github.com/googleapis/python-bigquery-dataframes/issues/909)) ([f80d705](https://github.com/googleapis/python-bigquery-dataframes/commit/f80d70503b80559a0b1fe64434383aa3e028bf9b)) -* Add snippet for creating boosted tree model ([#1142](https://github.com/googleapis/python-bigquery-dataframes/issues/1142)) ([a972668](https://github.com/googleapis/python-bigquery-dataframes/commit/a972668833a454fb18e6cb148697165edd46e8cc)) -* Add snippet for evaluating a boosted tree model ([#1154](https://github.com/googleapis/python-bigquery-dataframes/issues/1154)) ([9d8970a](https://github.com/googleapis/python-bigquery-dataframes/commit/9d8970ac1f18b2520a061ac743e767ca8593cc8c)) -* Add snippet for predicting classifications using a boosted tree model ([#1156](https://github.com/googleapis/python-bigquery-dataframes/issues/1156)) ([e7b83f1](https://github.com/googleapis/python-bigquery-dataframes/commit/e7b83f166ef56e631120050103c2f43f454fce44)) -* Add third party `pandas.Index methods` and docstrings ([#1171](https://github.com/googleapis/python-bigquery-dataframes/issues/1171)) ([a970294](https://github.com/googleapis/python-bigquery-dataframes/commit/a9702945286fbe500ade4d0f0c14cc60a8aa00eb)) -* Fix Bigframes.Pandas.General_Function missing docs ([#1164](https://github.com/googleapis/python-bigquery-dataframes/issues/1164)) ([de923d0](https://github.com/googleapis/python-bigquery-dataframes/commit/de923d01b904b96cc51dfd526b6a412f28ff10c4)) -* Update `bigframes.pandas.Index` docstrings ([#1144](https://github.com/googleapis/python-bigquery-dataframes/issues/1144)) ([557ab8d](https://github.com/googleapis/python-bigquery-dataframes/commit/557ab8df526fcf743af0a609ec7ec636b00d0c0b)) - -## [1.27.0](https://github.com/googleapis/python-bigquery-dataframes/compare/v1.26.0...v1.27.0) (2024-11-16) - - -### Features - -* Add astype(type, errors='null') to cast safely ([#1122](https://github.com/googleapis/python-bigquery-dataframes/issues/1122)) ([b4d17ff](https://github.com/googleapis/python-bigquery-dataframes/commit/b4d17ffdd891da266ad9765a087d3512c0e056fc)) - - -### Bug Fixes - -* Dataframe fillna with scalar. ([#1132](https://github.com/googleapis/python-bigquery-dataframes/issues/1132)) ([37f8c32](https://github.com/googleapis/python-bigquery-dataframes/commit/37f8c32a541565208602f3f6ed37dded13e16b9b)) -* Exclude index columns from model fitting processes. ([#1138](https://github.com/googleapis/python-bigquery-dataframes/issues/1138)) ([8d4da15](https://github.com/googleapis/python-bigquery-dataframes/commit/8d4da1582a5965e6a1f9732ec0ce592ea47ce5fa)) -* Unordered mode too many labels issue. ([#1148](https://github.com/googleapis/python-bigquery-dataframes/issues/1148)) ([7216b21](https://github.com/googleapis/python-bigquery-dataframes/commit/7216b21abd01bc61878bb5686f83ee13ef297912)) - - -### Documentation - -* Document groupby.head and groupby.size methods ([#1111](https://github.com/googleapis/python-bigquery-dataframes/issues/1111)) ([a61eb4d](https://github.com/googleapis/python-bigquery-dataframes/commit/a61eb4d6e323e5001715d402e0e67054df6e62af)) - -## [1.26.0](https://github.com/googleapis/python-bigquery-dataframes/compare/v1.25.0...v1.26.0) (2024-11-12) - - -### Features - -* Add basic geopandas functionality ([#962](https://github.com/googleapis/python-bigquery-dataframes/issues/962)) ([3759c63](https://github.com/googleapis/python-bigquery-dataframes/commit/3759c6397eaa3c46c4142aa51ca22be3dc8e4971)) -* Support `json_extract_string_array` in the `bigquery` module ([#1131](https://github.com/googleapis/python-bigquery-dataframes/issues/1131)) ([4ef8bac](https://github.com/googleapis/python-bigquery-dataframes/commit/4ef8bacdcc5447ba53c0f354526346f4dec7c5a1)) - - -### Bug Fixes - -* Fix Series.to_frame generating string label instead of int where name is None ([#1118](https://github.com/googleapis/python-bigquery-dataframes/issues/1118)) ([14e32b5](https://github.com/googleapis/python-bigquery-dataframes/commit/14e32b51c11c1718128f49ef94e754afc0ac0618)) -* Update the API documentation with newly added rep ([#1120](https://github.com/googleapis/python-bigquery-dataframes/issues/1120)) ([72c228b](https://github.com/googleapis/python-bigquery-dataframes/commit/72c228b15627e6047d60ae42740563a6dfea73da)) - - -### Performance Improvements - -* Reduce CURRENT_TIMESTAMP queries ([#1114](https://github.com/googleapis/python-bigquery-dataframes/issues/1114)) ([32274b1](https://github.com/googleapis/python-bigquery-dataframes/commit/32274b130849b37d7e587643cf7b6d109455ff38)) -* Reduce dry runs from read_gbq with table ([#1129](https://github.com/googleapis/python-bigquery-dataframes/issues/1129)) ([f7e4354](https://github.com/googleapis/python-bigquery-dataframes/commit/f7e435488d630cf4cf493c89ecdde94a95a7a0d7)) - - -### Documentation - -* Add file for Classification with a Boosted Treed Model and snippet for preparing sample data ([#1135](https://github.com/googleapis/python-bigquery-dataframes/issues/1135)) ([7ac6639](https://github.com/googleapis/python-bigquery-dataframes/commit/7ac6639fb0e8baf5fb3adf5785dffd8cf9b06702)) -* Add snippet for Linear Regression tutorial Predict Outcomes section ([#1101](https://github.com/googleapis/python-bigquery-dataframes/issues/1101)) ([108f4a9](https://github.com/googleapis/python-bigquery-dataframes/commit/108f4a98463596d8df6d381b3580eb72eab41b6e)) -* Update `DataFrame` docstrings to include the errors section ([#1127](https://github.com/googleapis/python-bigquery-dataframes/issues/1127)) ([a38d4c4](https://github.com/googleapis/python-bigquery-dataframes/commit/a38d4c422b6b312f6a54d7b1dd105a474ec2e91a)) -* Update GroupBy docstrings ([#1103](https://github.com/googleapis/python-bigquery-dataframes/issues/1103)) ([9867a78](https://github.com/googleapis/python-bigquery-dataframes/commit/9867a788e7c46bf0850cacbe7cd41a11fea32d6b)) -* Update Session doctrings to include exceptions ([#1130](https://github.com/googleapis/python-bigquery-dataframes/issues/1130)) ([a870421](https://github.com/googleapis/python-bigquery-dataframes/commit/a87042158b181dceee31124fe208926a3bb1071f)) - -## [1.25.0](https://github.com/googleapis/python-bigquery-dataframes/compare/v1.24.0...v1.25.0) (2024-10-29) - - -### Features - -* Add the `ground_with_google_search` option for GeminiTextGenerator predict ([#1119](https://github.com/googleapis/python-bigquery-dataframes/issues/1119)) ([ca02cd4](https://github.com/googleapis/python-bigquery-dataframes/commit/ca02cd4b87d354c1e01c670cd9d4e36fa74896f5)) -* Add warning when user tries to access struct series fields with `__getitem__` ([#1082](https://github.com/googleapis/python-bigquery-dataframes/issues/1082)) ([20e5c58](https://github.com/googleapis/python-bigquery-dataframes/commit/20e5c58868af8b18595d5635cb7722da4f622eb5)) -* Allow `fit` to take additional eval data in linear and ensemble models ([#1096](https://github.com/googleapis/python-bigquery-dataframes/issues/1096)) ([254875c](https://github.com/googleapis/python-bigquery-dataframes/commit/254875c25f39df4bc477e1ed7339ecb30b395ab6)) -* Support context manager for bigframes session ([#1107](https://github.com/googleapis/python-bigquery-dataframes/issues/1107)) ([5f7b8b1](https://github.com/googleapis/python-bigquery-dataframes/commit/5f7b8b189c093629d176ffc99364767dc766397a)) - - -### Performance Improvements - -* Improve series.unique performance and replace drop_duplicates i… ([#1108](https://github.com/googleapis/python-bigquery-dataframes/issues/1108)) ([499f24a](https://github.com/googleapis/python-bigquery-dataframes/commit/499f24a5f22ce484db96eb09cd3a0ce972398d81)) - -## [1.24.0](https://github.com/googleapis/python-bigquery-dataframes/compare/v1.23.0...v1.24.0) (2024-10-24) - - -### Features - -* Support series items method ([#1089](https://github.com/googleapis/python-bigquery-dataframes/issues/1089)) ([245a89c](https://github.com/googleapis/python-bigquery-dataframes/commit/245a89c36544faf2bcecb5735abbc00c0b4dd687)) - - -### Documentation - -* Update docstrings of DataFrame and related files ([#1092](https://github.com/googleapis/python-bigquery-dataframes/issues/1092)) ([15e9fd5](https://github.com/googleapis/python-bigquery-dataframes/commit/15e9fd547a01572cbda3d21de04d5548c7a4a82c)) - -## [1.23.0](https://github.com/googleapis/python-bigquery-dataframes/compare/v1.22.0...v1.23.0) (2024-10-23) - - -### Features - -* Add `bigframes.bigquery.create_vector_index` to assist in creating vector index on `ARRAY` columns ([#1024](https://github.com/googleapis/python-bigquery-dataframes/issues/1024)) ([863d694](https://github.com/googleapis/python-bigquery-dataframes/commit/863d6942eaf0cc435c3b76dc5d579c68fd478aa4)) -* Add gemini-1.5-pro-002 and gemini-1.5-flash-002 to known Gemini model list. ([#1105](https://github.com/googleapis/python-bigquery-dataframes/issues/1105)) ([7094c85](https://github.com/googleapis/python-bigquery-dataframes/commit/7094c85945efeb57067640404f7b98969401191b)) -* Add support for pandas series & data frames as inputs for ml models. ([#1088](https://github.com/googleapis/python-bigquery-dataframes/issues/1088)) ([30c8883](https://github.com/googleapis/python-bigquery-dataframes/commit/30c8883ff19db2c223d84c099c7b822467e9eb9a)) -* Cleanup temp resources with session deletion ([#1068](https://github.com/googleapis/python-bigquery-dataframes/issues/1068)) ([1d5373d](https://github.com/googleapis/python-bigquery-dataframes/commit/1d5373dd531c95b4a6a4132ef9b0ead0ecab14b4)) -* Show possible correct key(s) in `.__getitem__` KeyError message ([#1097](https://github.com/googleapis/python-bigquery-dataframes/issues/1097)) ([32fab96](https://github.com/googleapis/python-bigquery-dataframes/commit/32fab9626b9278e20c70c2ada8702e28e167a539)) -* Support uploading local geo data ([#1036](https://github.com/googleapis/python-bigquery-dataframes/issues/1036)) ([51cdd33](https://github.com/googleapis/python-bigquery-dataframes/commit/51cdd33e9f8377b3b992e0392eeb212aed499e3b)) - - -### Bug Fixes - -* Escape ids more consistently in ml module ([#1074](https://github.com/googleapis/python-bigquery-dataframes/issues/1074)) ([103e998](https://github.com/googleapis/python-bigquery-dataframes/commit/103e99823d442a36b2aaa5113950b988f6d3ba1e)) -* Model.fit metric not collected issue. ([#1085](https://github.com/googleapis/python-bigquery-dataframes/issues/1085)) ([06cec00](https://github.com/googleapis/python-bigquery-dataframes/commit/06cec00c51ba4b8df591e0988379db75b20c450b)) -* Remove index requirement from some dataframe APIs ([#1073](https://github.com/googleapis/python-bigquery-dataframes/issues/1073)) ([2d16f6d](https://github.com/googleapis/python-bigquery-dataframes/commit/2d16f6d1e9519e228533a67084000568a61c086e)) -* Update session metrics in `read_gbq_query` ([#1084](https://github.com/googleapis/python-bigquery-dataframes/issues/1084)) ([dced460](https://github.com/googleapis/python-bigquery-dataframes/commit/dced46070ee4212b5585a1eb53ae341dc0bf63ba)) - - -### Performance Improvements - -* Speed up tree transforms during sql compile ([#1071](https://github.com/googleapis/python-bigquery-dataframes/issues/1071)) ([d73fe9d](https://github.com/googleapis/python-bigquery-dataframes/commit/d73fe9d5fd2907aeaaa892a329221c10bb390da0)) -* Utilize ORDER BY LIMIT over ROW_NUMBER where possible ([#1077](https://github.com/googleapis/python-bigquery-dataframes/issues/1077)) ([7003d1a](https://github.com/googleapis/python-bigquery-dataframes/commit/7003d1ae6fddd535f6c206081e85f82bb6006f17)) - - -### Documentation - -* Add ml tutorial for Evaluate the model ([#1038](https://github.com/googleapis/python-bigquery-dataframes/issues/1038)) ([a120bae](https://github.com/googleapis/python-bigquery-dataframes/commit/a120bae2a8039d6115369b1f4a9047d4f0586120)) -* Show best practice of closing the session to cleanup resources in sample notebooks ([#1095](https://github.com/googleapis/python-bigquery-dataframes/issues/1095)) ([62a88e8](https://github.com/googleapis/python-bigquery-dataframes/commit/62a88e87f55f9cc109aa38f4b7ac10dd45ca41fd)) -* Update docstrings of Session and related files ([#1087](https://github.com/googleapis/python-bigquery-dataframes/issues/1087)) ([bf93e80](https://github.com/googleapis/python-bigquery-dataframes/commit/bf93e808daad2454e5c1aa933e0d2164d63084e7)) - -## [1.22.0](https://github.com/googleapis/python-bigquery-dataframes/compare/v1.21.0...v1.22.0) (2024-10-09) - - -### Features - -* Support regional endpoints for more bigquery locations ([#1061](https://github.com/googleapis/python-bigquery-dataframes/issues/1061)) ([45b672a](https://github.com/googleapis/python-bigquery-dataframes/commit/45b672a9a6359ec8c4755d94e63e5ae77a39754b)) -* Update LLM generators to warn user about model name instead of raising error. ([#1048](https://github.com/googleapis/python-bigquery-dataframes/issues/1048)) ([650d80d](https://github.com/googleapis/python-bigquery-dataframes/commit/650d80d1ad90927068cdb71efbfc548b416641a6)) - - -### Bug Fixes - -* Access MATERIALIZED_VIEW with read_gbq ([#1070](https://github.com/googleapis/python-bigquery-dataframes/issues/1070)) ([601e984](https://github.com/googleapis/python-bigquery-dataframes/commit/601e984aeb3ebf1dcf9cb3f1c34b7f0e4ec7cd16)) -* Correct zero row count in DataFrame from table view ([#1062](https://github.com/googleapis/python-bigquery-dataframes/issues/1062)) ([b536070](https://github.com/googleapis/python-bigquery-dataframes/commit/b53607015abb79be0aa5666681f1c53b5b1bc2b5)) -* Fix generic error message when entering an incorrect column name ([#1031](https://github.com/googleapis/python-bigquery-dataframes/issues/1031)) ([5ac217d](https://github.com/googleapis/python-bigquery-dataframes/commit/5ac217d650bc4f5576ba2b6595a3c0b1d88813ad)) -* Make `explode` respect the index labels ([#1064](https://github.com/googleapis/python-bigquery-dataframes/issues/1064)) ([99ca0df](https://github.com/googleapis/python-bigquery-dataframes/commit/99ca0df90acbbd81197c9b6718b7de7e4dfb86cc)) -* Make invalid location warning case-insensitive ([#1044](https://github.com/googleapis/python-bigquery-dataframes/issues/1044)) ([b6cd55a](https://github.com/googleapis/python-bigquery-dataframes/commit/b6cd55afc49b522904a13a7fd34d40201d176588)) -* Remove palm2 test case from llm load test ([#1063](https://github.com/googleapis/python-bigquery-dataframes/issues/1063)) ([575a10a](https://github.com/googleapis/python-bigquery-dataframes/commit/575a10a7ba0fbac76867f02da1dd65355f00d7aa)) -* Show warning for unknown location set through .ctor ([#1052](https://github.com/googleapis/python-bigquery-dataframes/issues/1052)) ([02c2da7](https://github.com/googleapis/python-bigquery-dataframes/commit/02c2da733b834b99d8044f3c5cac3ac9a85802a6)) - - -### Performance Improvements - -* Reduce schema tracking overhead ([#1056](https://github.com/googleapis/python-bigquery-dataframes/issues/1056)) ([1c3879d](https://github.com/googleapis/python-bigquery-dataframes/commit/1c3879df2d6925e17e2cdca827db8ec919471f72)) -* Repr generates fewer queries ([#1046](https://github.com/googleapis/python-bigquery-dataframes/issues/1046)) ([d204603](https://github.com/googleapis/python-bigquery-dataframes/commit/d204603fdc024823421397dbe514f1f7ced1bc2c)) -* Speedup internal tree comparisons ([#1060](https://github.com/googleapis/python-bigquery-dataframes/issues/1060)) ([4379438](https://github.com/googleapis/python-bigquery-dataframes/commit/4379438fc4f44ea847fd2c00a82af544265a30d2)) - - -### Documentation - -* Add docstring return type section to BigQueryOptions class ([#964](https://github.com/googleapis/python-bigquery-dataframes/issues/964)) ([307385f](https://github.com/googleapis/python-bigquery-dataframes/commit/307385f5295ae6918e7d42dcca2c0e0c32e82446)) - -## [1.21.0](https://github.com/googleapis/python-bigquery-dataframes/compare/v1.20.0...v1.21.0) (2024-10-02) - - -### Features - -* Add deprecation warning to PaLM2TextGenerator model ([#1035](https://github.com/googleapis/python-bigquery-dataframes/issues/1035)) ([1183b0f](https://github.com/googleapis/python-bigquery-dataframes/commit/1183b0fb2be7af7386e4bd0d0d1312433db60454)) -* Add DeprecationWarning for PaLM2TextEmbeddingGenerator ([#1018](https://github.com/googleapis/python-bigquery-dataframes/issues/1018)) ([4af5bbb](https://github.com/googleapis/python-bigquery-dataframes/commit/4af5bbb9e42fdb0add17308475c7881d7035fbfd)) -* Add ml.model_selection.cross_validate support ([#1020](https://github.com/googleapis/python-bigquery-dataframes/issues/1020)) ([1a38063](https://github.com/googleapis/python-bigquery-dataframes/commit/1a380631f793f82637cd384601956ee4457dc58a)) -* Allow access of struct fields with dot operators on `Series` ([#1019](https://github.com/googleapis/python-bigquery-dataframes/issues/1019)) ([ef76f13](https://github.com/googleapis/python-bigquery-dataframes/commit/ef76f137fbbf9e8f8c5a63023554d22059ab4fbd)) - - -### Bug Fixes - -* Ensure no double execution for to_pandas ([#1032](https://github.com/googleapis/python-bigquery-dataframes/issues/1032)) ([4992cc2](https://github.com/googleapis/python-bigquery-dataframes/commit/4992cc27e46bc2b0a908c7d521785989735186f4)) -* Remove pre-caching of remote function results ([#1028](https://github.com/googleapis/python-bigquery-dataframes/issues/1028)) ([0359bc8](https://github.com/googleapis/python-bigquery-dataframes/commit/0359bc85839c37b5cd10c0c418b275ac0dc29c4a)) - - -### Documentation - -* Add ml cross-validation notebook ([#1037](https://github.com/googleapis/python-bigquery-dataframes/issues/1037)) ([057f3f0](https://github.com/googleapis/python-bigquery-dataframes/commit/057f3f0d694ddffe8745443a85b4fb43081893bb)) - -## [1.20.0](https://github.com/googleapis/python-bigquery-dataframes/compare/v1.19.0...v1.20.0) (2024-09-25) - - -### Features - -* Add bigframes.bigquery.approx_top_count ([#1010](https://github.com/googleapis/python-bigquery-dataframes/issues/1010)) ([3263bd7](https://github.com/googleapis/python-bigquery-dataframes/commit/3263bd70cff01bc18f1ae4ac3d5aa7f9d70fd4b7)) -* Add bigframes.ml.compose.SQLScalarColumnTransformer to create custom SQL-based transformations ([#955](https://github.com/googleapis/python-bigquery-dataframes/issues/955)) ([1930b4e](https://github.com/googleapis/python-bigquery-dataframes/commit/1930b4efe60295751ceef89c2a824923a35b19af)) -* Allow multiple columns input for llm models ([#998](https://github.com/googleapis/python-bigquery-dataframes/issues/998)) ([2fe5e48](https://github.com/googleapis/python-bigquery-dataframes/commit/2fe5e48c56bbc359d3769824c83745d65a001dd7)) - - -### Bug Fixes - -* Fix __repr__ caching with partial ordering ([#1016](https://github.com/googleapis/python-bigquery-dataframes/issues/1016)) ([208a984](https://github.com/googleapis/python-bigquery-dataframes/commit/208a98475389f59d4e32e0cfbcc46824cac278a6)) - - -### Documentation - -* Limit pypi notebook to 7 days and add more info about differences with partial ordering mode ([#1013](https://github.com/googleapis/python-bigquery-dataframes/issues/1013)) ([3c54399](https://github.com/googleapis/python-bigquery-dataframes/commit/3c543990297ec3be0e30425ee841546217e26d2a)) -* Move and edit existing linear-regression tutorial snippet ([#991](https://github.com/googleapis/python-bigquery-dataframes/issues/991)) ([4cb62fd](https://github.com/googleapis/python-bigquery-dataframes/commit/4cb62fd74fc1ac3bb21da23b8639464a9ae3525d)) - -## [1.19.0](https://github.com/googleapis/python-bigquery-dataframes/compare/v1.18.0...v1.19.0) (2024-09-24) - - -### Features - -* Add ml.model_selection.KFold class ([#1001](https://github.com/googleapis/python-bigquery-dataframes/issues/1001)) ([952cab9](https://github.com/googleapis/python-bigquery-dataframes/commit/952cab92e548b70d077b20bf10f5307751d2ae76)) -* Support bool and bytes types in `describe(include='all')` ([#994](https://github.com/googleapis/python-bigquery-dataframes/issues/994)) ([cc48f58](https://github.com/googleapis/python-bigquery-dataframes/commit/cc48f58cbd94f8110ee863eb57d3fe8dc5a17778)) -* Support ingress settings in `remote_function` ([#1011](https://github.com/googleapis/python-bigquery-dataframes/issues/1011)) ([8e9919b](https://github.com/googleapis/python-bigquery-dataframes/commit/8e9919b53899b6951a10d02643d1d0e53e15665f)) - - -### Bug Fixes - -* Fix miscasting issues with case_when ([#1003](https://github.com/googleapis/python-bigquery-dataframes/issues/1003)) ([038139d](https://github.com/googleapis/python-bigquery-dataframes/commit/038139dfa4fa89167c52c1cb559c2eb5fe2f0411)) - - -### Performance Improvements - -* Join op discards child ordering in unordered mode ([#923](https://github.com/googleapis/python-bigquery-dataframes/issues/923)) ([1b5b0ee](https://github.com/googleapis/python-bigquery-dataframes/commit/1b5b0eea92631b7dd1b688cf1da617fc7ce862dc)) - - -### Dependencies - -* Update ibis version in prerelease tests ([#1012](https://github.com/googleapis/python-bigquery-dataframes/issues/1012)) ([f89785f](https://github.com/googleapis/python-bigquery-dataframes/commit/f89785fcfc51c541253ca8c1e8baf80fbfaea3b6)) - -## [1.18.0](https://github.com/googleapis/python-bigquery-dataframes/compare/v1.17.0...v1.18.0) (2024-09-18) - - -### Features - -* Add "include" param to describe for string types ([#973](https://github.com/googleapis/python-bigquery-dataframes/issues/973)) ([deac6d2](https://github.com/googleapis/python-bigquery-dataframes/commit/deac6d2d6e459b26c05f6e5ff328ea03a3cff45f)) -* Add `subset` parameter to `DataFrame.dropna` to select which columns to consider ([#981](https://github.com/googleapis/python-bigquery-dataframes/issues/981)) ([f7c03dc](https://github.com/googleapis/python-bigquery-dataframes/commit/f7c03dcaf7ee4d62497f6653851e390795fc60a2)) - - -### Bug Fixes - -* DataFrameGroupby.agg now works with unnamed tuples ([#985](https://github.com/googleapis/python-bigquery-dataframes/issues/985)) ([0f047b4](https://github.com/googleapis/python-bigquery-dataframes/commit/0f047b4fae2a10b2a465c506bea561f8bb8d4262)) -* Fix a bug that raises exception when re-indexing columns with their original order ([#988](https://github.com/googleapis/python-bigquery-dataframes/issues/988)) ([596b03b](https://github.com/googleapis/python-bigquery-dataframes/commit/596b03bb3ea27cead9b90200b9ef3cdcd99ca184)) -* Make the `Series.apply` outcome `assign`able to the original dataframe in partial ordering mode ([#874](https://github.com/googleapis/python-bigquery-dataframes/issues/874)) ([c94ead9](https://github.com/googleapis/python-bigquery-dataframes/commit/c94ead996e3bfa98edd51ff678a3d43a10ee980f)) - - -### Dependencies - -* Limit ibis-framework version to 9.2.0 ([#989](https://github.com/googleapis/python-bigquery-dataframes/issues/989)) ([06c1b33](https://github.com/googleapis/python-bigquery-dataframes/commit/06c1b3396d77d1de4f927328bae70cd7b3eb0b0b)) -* Update to ibis-framework 9.x and newer sqlglot ([#827](https://github.com/googleapis/python-bigquery-dataframes/issues/827)) ([89ea44f](https://github.com/googleapis/python-bigquery-dataframes/commit/89ea44fb66314b134fc0a10d816c1659978d4182)) - -## [1.17.0](https://github.com/googleapis/python-bigquery-dataframes/compare/v1.16.0...v1.17.0) (2024-09-11) - - -### Features - -* Add `__version__` alias to bigframes.pandas ([#967](https://github.com/googleapis/python-bigquery-dataframes/issues/967)) ([9ce10b4](https://github.com/googleapis/python-bigquery-dataframes/commit/9ce10b4248f106ac9e09fc0fe686cece86827337)) -* Add Gemini 1.5 stable models support ([#945](https://github.com/googleapis/python-bigquery-dataframes/issues/945)) ([c1cde19](https://github.com/googleapis/python-bigquery-dataframes/commit/c1cde19769c169b962b58b25f0be61c8c41edb95)) -* Allow setting table labels in `to_gbq` ([#941](https://github.com/googleapis/python-bigquery-dataframes/issues/941)) ([cccc6ca](https://github.com/googleapis/python-bigquery-dataframes/commit/cccc6ca8c1271097bbe15e3d9ccdcfd7c633227a)) -* Define list accessor for bigframes Series ([#946](https://github.com/googleapis/python-bigquery-dataframes/issues/946)) ([8e8279d](https://github.com/googleapis/python-bigquery-dataframes/commit/8e8279d4da90feb5766f266b49cb417f8cbec6c9)) -* Enable read_csv() to process other files ([#940](https://github.com/googleapis/python-bigquery-dataframes/issues/940)) ([3b35860](https://github.com/googleapis/python-bigquery-dataframes/commit/3b35860776033fc8e71e471422c6d2b9366a7c9f)) -* Include the bigframes package version alongside the feedback link in error messages ([#936](https://github.com/googleapis/python-bigquery-dataframes/issues/936)) ([7b59b6d](https://github.com/googleapis/python-bigquery-dataframes/commit/7b59b6dc6f0cedfee713b5b273d46fa84b70bfa4)) - - -### Bug Fixes - -* Astype Decimal to Int64 conversion. ([#957](https://github.com/googleapis/python-bigquery-dataframes/issues/957)) ([27764a6](https://github.com/googleapis/python-bigquery-dataframes/commit/27764a64f90092374458fafbe393bc6c30c85681)) -* Make `read_gbq_function` work for multi-param functions ([#947](https://github.com/googleapis/python-bigquery-dataframes/issues/947)) ([c750be6](https://github.com/googleapis/python-bigquery-dataframes/commit/c750be6093941677572a10c36a92984e954de32c)) -* Support `read_gbq_function` for axis=1 application ([#950](https://github.com/googleapis/python-bigquery-dataframes/issues/950)) ([86e54b1](https://github.com/googleapis/python-bigquery-dataframes/commit/86e54b13d2b91517b1df2d9c1f852a8e1925309a)) - - -### Documentation - -* Add docstring returns section to Options ([#937](https://github.com/googleapis/python-bigquery-dataframes/issues/937)) ([a2640a2](https://github.com/googleapis/python-bigquery-dataframes/commit/a2640a2d731c8d0aba1307311092f5e85b8ba077)) -* Update title of pypi notebook example to reflect use of the PyPI public dataset ([#952](https://github.com/googleapis/python-bigquery-dataframes/issues/952)) ([cd62e60](https://github.com/googleapis/python-bigquery-dataframes/commit/cd62e604967adac0c2f8600408bd9ce7886f2f98)) - -## [1.16.0](https://github.com/googleapis/python-bigquery-dataframes/compare/v1.15.0...v1.16.0) (2024-09-04) - - -### Features - -* Add `DataFrame.struct.explode` to add struct subfields to a DataFrame ([#916](https://github.com/googleapis/python-bigquery-dataframes/issues/916)) ([ad2f75e](https://github.com/googleapis/python-bigquery-dataframes/commit/ad2f75ecbc3660459814716eec7d1f88d1188942)) -* Implement `bigframes.bigquery.json_extract_array` ([#910](https://github.com/googleapis/python-bigquery-dataframes/issues/910)) ([575a29e](https://github.com/googleapis/python-bigquery-dataframes/commit/575a29e77d50d60d7e9a84ebb87abcdb993adef1)) -* Recover struct column from exploded Series ([#904](https://github.com/googleapis/python-bigquery-dataframes/issues/904)) ([7dd304c](https://github.com/googleapis/python-bigquery-dataframes/commit/7dd304cc7168fac222fa1330f868677818d10903)) - - -### Bug Fixes - -* Fix issue with iterating on >10gb dataframes ([#949](https://github.com/googleapis/python-bigquery-dataframes/issues/949)) ([2b0f0fa](https://github.com/googleapis/python-bigquery-dataframes/commit/2b0f0faf840a1ec43d007827bbbf908df62ce9d3)) -* Improve `Series.replace` for dict input ([#907](https://github.com/googleapis/python-bigquery-dataframes/issues/907)) ([4208044](https://github.com/googleapis/python-bigquery-dataframes/commit/4208044222c6a8494004ec6f511a3b85f4eb4180)) -* NullIndex in ML model.predict error ([#917](https://github.com/googleapis/python-bigquery-dataframes/issues/917)) ([612271d](https://github.com/googleapis/python-bigquery-dataframes/commit/612271d35675353effa465a797d6e3a1285d4d37)) -* Struct field non-nullable type issue. ([#914](https://github.com/googleapis/python-bigquery-dataframes/issues/914)) ([149d5ff](https://github.com/googleapis/python-bigquery-dataframes/commit/149d5ff822da3d7fda18dbed4814e0406708cf07)) -* Unordered mode errors in ml train_test_split ([#925](https://github.com/googleapis/python-bigquery-dataframes/issues/925)) ([85d7c21](https://github.com/googleapis/python-bigquery-dataframes/commit/85d7c21b4bd5dc669098342fc60d66d89ef06b2b)) - - -### Performance Improvements - -* Improve repr performance ([#918](https://github.com/googleapis/python-bigquery-dataframes/issues/918)) ([46f2dd7](https://github.com/googleapis/python-bigquery-dataframes/commit/46f2dd79f59131bbb98fe4ae3780b98cb4d50646)) - - -### Dependencies - -* Re-introduce support for numpy 1.24.x ([#931](https://github.com/googleapis/python-bigquery-dataframes/issues/931)) ([3d71913](https://github.com/googleapis/python-bigquery-dataframes/commit/3d71913b3cf357fc9e94304ca0c94070e0a16f92)) -* Update minimum support to Pandas 1.5.3 and Pyarrow 10.0.1 ([#903](https://github.com/googleapis/python-bigquery-dataframes/issues/903)) ([7ed3962](https://github.com/googleapis/python-bigquery-dataframes/commit/7ed39629c638874d8e9cc3c7a9b3ec92ad480eca)) - - -### Documentation - -* Add Claude3 ML and RemoteFunc notebooks ([#930](https://github.com/googleapis/python-bigquery-dataframes/issues/930)) ([cfd16c1](https://github.com/googleapis/python-bigquery-dataframes/commit/cfd16c1278023bd2c3dce9c0cb378615aa00e58d)) -* Create sample notebook to manipulate struct and array data ([#883](https://github.com/googleapis/python-bigquery-dataframes/issues/883)) ([3031903](https://github.com/googleapis/python-bigquery-dataframes/commit/303190331d3194562c5ed44fefc2c9fd1d73bedd)) -* Update struct examples. ([#953](https://github.com/googleapis/python-bigquery-dataframes/issues/953)) ([d632cd0](https://github.com/googleapis/python-bigquery-dataframes/commit/d632cd03e3e3ea6dfa7c56dd459c422e95be906e)) -* Use unstack() from BigQuery DataFrames instead of pandas in the PyPI sample notebook ([#890](https://github.com/googleapis/python-bigquery-dataframes/issues/890)) ([d1883cc](https://github.com/googleapis/python-bigquery-dataframes/commit/d1883cc04ce5b2944d87a00c79b99a406001ba8f)) - -## [1.15.0](https://github.com/googleapis/python-bigquery-dataframes/compare/v1.14.0...v1.15.0) (2024-08-20) - - -### Features - -* Add llm.TextEmbeddingGenerator to support new embedding models ([#905](https://github.com/googleapis/python-bigquery-dataframes/issues/905)) ([6bc6a41](https://github.com/googleapis/python-bigquery-dataframes/commit/6bc6a41426fbbb60e77cd77f80860f88a1751a4b)) -* Add ml.llm.Claude3TextGenerator model ([#901](https://github.com/googleapis/python-bigquery-dataframes/issues/901)) ([7050038](https://github.com/googleapis/python-bigquery-dataframes/commit/7050038eeee258452860941aa6b01d6a8ae10c6f)) - - -### Documentation - -* Add columns for "requires ordering/index" to supported APIs summary ([#892](https://github.com/googleapis/python-bigquery-dataframes/issues/892)) ([d2fc51a](https://github.com/googleapis/python-bigquery-dataframes/commit/d2fc51a30c4fff6fe0b98df61eec70ddb28b37ec)) -* Remove duplicate description for `kms_key_name` ([#898](https://github.com/googleapis/python-bigquery-dataframes/issues/898)) ([1053d56](https://github.com/googleapis/python-bigquery-dataframes/commit/1053d56260eef1cff6e7c419f6c86be8f7e74373)) -* Update embedding model notebooks ([#906](https://github.com/googleapis/python-bigquery-dataframes/issues/906)) ([d9b8ef5](https://github.com/googleapis/python-bigquery-dataframes/commit/d9b8ef56deb0c776edeeb0112bd9d35d5ed1b70e)) - -## [1.14.0](https://github.com/googleapis/python-bigquery-dataframes/compare/v1.13.0...v1.14.0) (2024-08-14) - - -### Features - -* Implement `bigframes.bigquery.json_extract` ([#868](https://github.com/googleapis/python-bigquery-dataframes/issues/868)) ([3dbf84b](https://github.com/googleapis/python-bigquery-dataframes/commit/3dbf84bd1531c1f8d41ba57c2c38b3ba6abfb812)) -* Implement `Series.str.__getitem__` ([#897](https://github.com/googleapis/python-bigquery-dataframes/issues/897)) ([e027b7e](https://github.com/googleapis/python-bigquery-dataframes/commit/e027b7e9d29f628d058611106014a1790459958c)) - - -### Bug Fixes - -* Fix caching from generating row numbers in partial ordering mode ([#872](https://github.com/googleapis/python-bigquery-dataframes/issues/872)) ([52b7786](https://github.com/googleapis/python-bigquery-dataframes/commit/52b7786c3a28da6c29e3ddf12629802215194ad9)) - - -### Performance Improvements - -* Generate SQL with fewer CTEs ([#877](https://github.com/googleapis/python-bigquery-dataframes/issues/877)) ([eb60804](https://github.com/googleapis/python-bigquery-dataframes/commit/eb6080460344aff2fabb7864536ea4fe24c5fbef)) -* Speed up compilation by reducing redundant type normalization ([#896](https://github.com/googleapis/python-bigquery-dataframes/issues/896)) ([e0b11bc](https://github.com/googleapis/python-bigquery-dataframes/commit/e0b11bc8c038db7b950b1653ed4cd44a6246c713)) - - -### Documentation - -* Add streaming html docs ([#884](https://github.com/googleapis/python-bigquery-dataframes/issues/884)) ([171da6c](https://github.com/googleapis/python-bigquery-dataframes/commit/171da6cb33165b49d46ea6528038342abd89e9fa)) -* Fix the `DisplayOptions` doc rendering ([#893](https://github.com/googleapis/python-bigquery-dataframes/issues/893)) ([3eb6a17](https://github.com/googleapis/python-bigquery-dataframes/commit/3eb6a17a5823faf5ecba92cb9a554df74477871d)) -* Update streaming notebook ([#887](https://github.com/googleapis/python-bigquery-dataframes/issues/887)) ([6e6f9df](https://github.com/googleapis/python-bigquery-dataframes/commit/6e6f9df55d435afe0b3ade728ca06826e92a6ee6)) - -## [1.13.0](https://github.com/googleapis/python-bigquery-dataframes/compare/v1.12.0...v1.13.0) (2024-08-05) - - -### Features - -* `df.apply(axis=1)` to support remote function with mutiple params ([#851](https://github.com/googleapis/python-bigquery-dataframes/issues/851)) ([2158818](https://github.com/googleapis/python-bigquery-dataframes/commit/2158818e53e09e55c87ffd574e3ebc2e201285fb)) -* Allow windowing in 'partial' ordering mode ([#861](https://github.com/googleapis/python-bigquery-dataframes/issues/861)) ([ca26fe5](https://github.com/googleapis/python-bigquery-dataframes/commit/ca26fe5f9edec519788c276a09eaff33ecd87434)) -* Create a separate OrderingModePartialPreviewWarning for more fine-grained warning filters ([#879](https://github.com/googleapis/python-bigquery-dataframes/issues/879)) ([8753bdd](https://github.com/googleapis/python-bigquery-dataframes/commit/8753bdd1e44701e56eae914ebc0e91d9b1a6adf1)) - - -### Bug Fixes - -* Fix issue with invalid sql generated by ml distance functions ([#865](https://github.com/googleapis/python-bigquery-dataframes/issues/865)) ([9959fc8](https://github.com/googleapis/python-bigquery-dataframes/commit/9959fc8fcba93441fdd3d9c17e8fdbe6e6a7b504)) - - -### Documentation - -* Create sample notebook using `ordering_mode="partial"` ([#880](https://github.com/googleapis/python-bigquery-dataframes/issues/880)) ([c415eb9](https://github.com/googleapis/python-bigquery-dataframes/commit/c415eb91eb71dea53d245ba2bce416062e3f02f8)) -* Update streaming notebook ([#875](https://github.com/googleapis/python-bigquery-dataframes/issues/875)) ([e9b0557](https://github.com/googleapis/python-bigquery-dataframes/commit/e9b05571123cf13079772856317ca3cd3d564c5a)) - -## [1.12.0](https://github.com/googleapis/python-bigquery-dataframes/compare/v1.11.1...v1.12.0) (2024-07-31) - - -### Features - -* Add bigframes-mode label to query jobs ([#832](https://github.com/googleapis/python-bigquery-dataframes/issues/832)) ([c9eaff0](https://github.com/googleapis/python-bigquery-dataframes/commit/c9eaff0a1a0731b28f4c67bca5606db12a47c8c0)) -* Add config option to set partial ordering mode ([#855](https://github.com/googleapis/python-bigquery-dataframes/issues/855)) ([823c0ce](https://github.com/googleapis/python-bigquery-dataframes/commit/823c0ce57611c0918a9e9999638d7393337fe9af)) -* Add stratify param support to ml.model_selection.train_test_split method ([#815](https://github.com/googleapis/python-bigquery-dataframes/issues/815)) ([27f8631](https://github.com/googleapis/python-bigquery-dataframes/commit/27f8631be81a3e136cfeb8904558bb4f3f5caa05)) -* Add streaming.StreamingDataFrame class ([#864](https://github.com/googleapis/python-bigquery-dataframes/issues/864)) ([a7d7197](https://github.com/googleapis/python-bigquery-dataframes/commit/a7d7197a32c55b989ae4ea8f6cf6e1c0f7184cd4)) -* Allow DataFrame.join for self-join on Null index ([#860](https://github.com/googleapis/python-bigquery-dataframes/issues/860)) ([e950533](https://github.com/googleapis/python-bigquery-dataframes/commit/e95053372c36ea5a91a2d7295c1a3a3671181670)) -* Support remote function cleanup with `session.close` ([#818](https://github.com/googleapis/python-bigquery-dataframes/issues/818)) ([ed06436](https://github.com/googleapis/python-bigquery-dataframes/commit/ed06436612c0d46f190f79721416d473bde7e2f4)) -* Support to_csv/parquet/json to local files/objects ([#858](https://github.com/googleapis/python-bigquery-dataframes/issues/858)) ([d0ab9cc](https://github.com/googleapis/python-bigquery-dataframes/commit/d0ab9cc47298bdde638299baecac9dffd7841ede)) - - -### Bug Fixes - -* Fewer relation joins from df self-operations ([#823](https://github.com/googleapis/python-bigquery-dataframes/issues/823)) ([0d24f73](https://github.com/googleapis/python-bigquery-dataframes/commit/0d24f737041c7dd70253ebb4baa8d8ef67bd4f1d)) -* Fix 'sql' property for null index ([#844](https://github.com/googleapis/python-bigquery-dataframes/issues/844)) ([1b6a556](https://github.com/googleapis/python-bigquery-dataframes/commit/1b6a556206a7a66283339d827ab12db2753521e2)) -* Fix unordered mode using ordered path to print frame ([#839](https://github.com/googleapis/python-bigquery-dataframes/issues/839)) ([93785cb](https://github.com/googleapis/python-bigquery-dataframes/commit/93785cb48be4a2eb8770129148bd0b897fed4ee7)) -* Reduce redundant `remote_function` deployments ([#856](https://github.com/googleapis/python-bigquery-dataframes/issues/856)) ([cbf2d42](https://github.com/googleapis/python-bigquery-dataframes/commit/cbf2d42e4d961a7537381a9c3b28a8b463ad8f74)) - - -### Documentation - -* Add partner attribution steps to integrations sample notebook ([#835](https://github.com/googleapis/python-bigquery-dataframes/issues/835)) ([d7b333f](https://github.com/googleapis/python-bigquery-dataframes/commit/d7b333fa26acddaeb5ccca4f81b1d624dff03ba2)) -* Make `get_global_session`/`close_session`/`reset_session` appears in the docs ([#847](https://github.com/googleapis/python-bigquery-dataframes/issues/847)) ([01d6bbb](https://github.com/googleapis/python-bigquery-dataframes/commit/01d6bbb7479da706dc62bb5e7d51dc28a4042812)) - -## [1.11.1](https://github.com/googleapis/python-bigquery-dataframes/compare/v1.11.0...v1.11.1) (2024-07-08) - - -### Documentation - -* Remove session and connection in llm notebook ([#821](https://github.com/googleapis/python-bigquery-dataframes/issues/821)) ([74170da](https://github.com/googleapis/python-bigquery-dataframes/commit/74170dabd323f1b08ad76241e37ff9f2a5b67ab5)) -* Remove the experimental flask icon from the public docs ([#820](https://github.com/googleapis/python-bigquery-dataframes/issues/820)) ([067ff17](https://github.com/googleapis/python-bigquery-dataframes/commit/067ff173f0abfcf5bf06d3fbdb6d12e0fa5283c3)) - -## [1.11.0](https://github.com/googleapis/python-bigquery-dataframes/compare/v1.10.0...v1.11.0) (2024-07-01) - - -### Features - -* Add .agg support for size ([#792](https://github.com/googleapis/python-bigquery-dataframes/issues/792)) ([87e6018](https://github.com/googleapis/python-bigquery-dataframes/commit/87e60182c964c369079165e87ce73dd0c0481a5a)) -* Add `bigframes.bigquery.json_set` ([#782](https://github.com/googleapis/python-bigquery-dataframes/issues/782)) ([1b613e0](https://github.com/googleapis/python-bigquery-dataframes/commit/1b613e00eddf18fa40ed1d08ff19c4ebeeac2197)) -* Add `bigframes.streaming.to_pubsub` method to create continuous query that writes to Pub/Sub ([#801](https://github.com/googleapis/python-bigquery-dataframes/issues/801)) ([b47f32d](https://github.com/googleapis/python-bigquery-dataframes/commit/b47f32d74a0c9eb908be690b2dd56b0f5579b133)) -* Add `DataFrame.to_arrow` to create Arrow Table from DataFrame ([#807](https://github.com/googleapis/python-bigquery-dataframes/issues/807)) ([1e3feda](https://github.com/googleapis/python-bigquery-dataframes/commit/1e3feda9e8fe9d08a0e3838066f6414f8015197d)) -* Add `PolynomialFeatures` support to `to_gbq` and pipelines ([#805](https://github.com/googleapis/python-bigquery-dataframes/issues/805)) ([57d98b9](https://github.com/googleapis/python-bigquery-dataframes/commit/57d98b9e3298583ec40c04665ab84e6ad2b948fb)) -* Add Series.peek to preview data efficiently ([#727](https://github.com/googleapis/python-bigquery-dataframes/issues/727)) ([580e1b9](https://github.com/googleapis/python-bigquery-dataframes/commit/580e1b9e965d883a67f91a6db8311c2416ca8fe5)) -* Expose gcf memory param in `remote_function` ([#803](https://github.com/googleapis/python-bigquery-dataframes/issues/803)) ([014765c](https://github.com/googleapis/python-bigquery-dataframes/commit/014765c22410a0b4559896d163c440f46f7ce98f)) -* More informative error when query plan too complex ([#811](https://github.com/googleapis/python-bigquery-dataframes/issues/811)) ([136dc24](https://github.com/googleapis/python-bigquery-dataframes/commit/136dc24e160339d27f6335e7b28f08cd95d2c67d)) - - -### Bug Fixes - -* Include internally required packages in `remote_function` hash ([#799](https://github.com/googleapis/python-bigquery-dataframes/issues/799)) ([4b8fc15](https://github.com/googleapis/python-bigquery-dataframes/commit/4b8fc15ec2c126566269f84d75289198fee2c655)) - - -### Documentation - -* Document dtype limitation on row processing `remote_function` ([#800](https://github.com/googleapis/python-bigquery-dataframes/issues/800)) ([487dff6](https://github.com/googleapis/python-bigquery-dataframes/commit/487dff6ac147683aef529e1ff8c197dce3fb437c)) - -## [1.10.0](https://github.com/googleapis/python-bigquery-dataframes/compare/v1.9.0...v1.10.0) (2024-06-21) - - -### Features - -* Add dataframe.insert ([#770](https://github.com/googleapis/python-bigquery-dataframes/issues/770)) ([e8bab68](https://github.com/googleapis/python-bigquery-dataframes/commit/e8bab681a2d07636e5809e804f4fd81b0d582685)) -* Add groupby head API ([#791](https://github.com/googleapis/python-bigquery-dataframes/issues/791)) ([44202bc](https://github.com/googleapis/python-bigquery-dataframes/commit/44202bc3541df03154ea0b2cca8eac18094a91a9)) -* Add ml.preprocessing.PolynomialFeatures class ([#793](https://github.com/googleapis/python-bigquery-dataframes/issues/793)) ([b4fbb51](https://github.com/googleapis/python-bigquery-dataframes/commit/b4fbb518711922c09ac6f55f3b8f6ab57c89114b)) -* Bigframes.streaming module for continuous queries ([#703](https://github.com/googleapis/python-bigquery-dataframes/issues/703)) ([0433a1c](https://github.com/googleapis/python-bigquery-dataframes/commit/0433a1cff57fddda26b2c57adc0ea71f3fdd3201)) -* Include index columns in DataFrame.sql if they are named ([#788](https://github.com/googleapis/python-bigquery-dataframes/issues/788)) ([c8d16c0](https://github.com/googleapis/python-bigquery-dataframes/commit/c8d16c0f72a25bce854b80be517114e1603c947e)) - - -### Bug Fixes - -* Allow `__repr__` to work with uninitialed DataFrame/Series/Index ([#778](https://github.com/googleapis/python-bigquery-dataframes/issues/778)) ([e14c7a9](https://github.com/googleapis/python-bigquery-dataframes/commit/e14c7a9e7a9cb8847e0382b135fc06c7b82b872a)) -* Df.loc with the 2nd input as bigframes boolean Series ([#789](https://github.com/googleapis/python-bigquery-dataframes/issues/789)) ([a4ac82e](https://github.com/googleapis/python-bigquery-dataframes/commit/a4ac82e06221581ddfcfc1246a3e3cd65a8bb00e)) -* Ensure numpy version matches in `remote_function` deployment ([#798](https://github.com/googleapis/python-bigquery-dataframes/issues/798)) ([324d93c](https://github.com/googleapis/python-bigquery-dataframes/commit/324d93cb31191520b790bbbc501468b8d1d8467d)) -* Fix temp table creation retries by now throwing if table already exists. ([#787](https://github.com/googleapis/python-bigquery-dataframes/issues/787)) ([0e57d1f](https://github.com/googleapis/python-bigquery-dataframes/commit/0e57d1f1f8a150ba6faac5f667bb5b4c78f4c0a3)) -* Self-join optimization doesn't needlessly invalidate caching ([#797](https://github.com/googleapis/python-bigquery-dataframes/issues/797)) ([1b96b80](https://github.com/googleapis/python-bigquery-dataframes/commit/1b96b8027a550e1601a5360f2af35d24a8806da9)) - -## [1.9.0](https://github.com/googleapis/python-bigquery-dataframes/compare/v1.8.0...v1.9.0) (2024-06-10) - - -### Features - -* Allow functions returned from `bpd.read_gbq_function` to execute outside of `apply` ([#706](https://github.com/googleapis/python-bigquery-dataframes/issues/706)) ([ad7d8ac](https://github.com/googleapis/python-bigquery-dataframes/commit/ad7d8ac1247ec3b9532dd5375265c36907f50da2)) -* Support `bigquery.vector_search()` ([#736](https://github.com/googleapis/python-bigquery-dataframes/issues/736)) ([dad66fd](https://github.com/googleapis/python-bigquery-dataframes/commit/dad66fdd22bb2d507e7f366c970d971554598cf3)) -* Support `score()` in GeminiTextGenerator ([#740](https://github.com/googleapis/python-bigquery-dataframes/issues/740)) ([b2c7d8b](https://github.com/googleapis/python-bigquery-dataframes/commit/b2c7d8b28e235c839370818137fba71796c9f02a)) -* Support bytes type in `remote_function` ([#761](https://github.com/googleapis/python-bigquery-dataframes/issues/761)) ([4915424](https://github.com/googleapis/python-bigquery-dataframes/commit/4915424a68f36542e901a0ac27946f1ecb2d05ab)) -* Support fit() in GeminiTextGenerator ([#758](https://github.com/googleapis/python-bigquery-dataframes/issues/758)) ([d751f5c](https://github.com/googleapis/python-bigquery-dataframes/commit/d751f5cd1cf578618eabbb992cfb6b0a3c36608c)) - - -### Bug Fixes - -* ARIMAPlus loads auto_arima_min_order param ([#752](https://github.com/googleapis/python-bigquery-dataframes/issues/752)) ([39d7013](https://github.com/googleapis/python-bigquery-dataframes/commit/39d7013a8a8d2908f20bfe54a7dc8de166323b90)) -* Improve to_pandas_batches for large results ([#746](https://github.com/googleapis/python-bigquery-dataframes/issues/746)) ([61f18cb](https://github.com/googleapis/python-bigquery-dataframes/commit/61f18cb63f2785c03dc612a34c030079fc8f4172)) -* Resolve issue with unset thread-local options ([#741](https://github.com/googleapis/python-bigquery-dataframes/issues/741)) ([d93dbaf](https://github.com/googleapis/python-bigquery-dataframes/commit/d93dbafe2bb405c60f7141d9ae4135db4ffdb702)) - - -### Documentation - -* Fix ML.EVALUATE spelling ([#749](https://github.com/googleapis/python-bigquery-dataframes/issues/749)) ([7899749](https://github.com/googleapis/python-bigquery-dataframes/commit/7899749505a75ed89c68e9df64124a153644de96)) -* Remove LogisticRegression normal_equation strategy ([#753](https://github.com/googleapis/python-bigquery-dataframes/issues/753)) ([ea5d367](https://github.com/googleapis/python-bigquery-dataframes/commit/ea5d367d5ecc6826d30082e75c957af8362c9e61)) - -## [1.8.0](https://github.com/googleapis/python-bigquery-dataframes/compare/v1.7.0...v1.8.0) (2024-05-31) - - -### Features - -* `merge` only generates a default index if both inputs already have an index ([#733](https://github.com/googleapis/python-bigquery-dataframes/issues/733)) ([25d049c](https://github.com/googleapis/python-bigquery-dataframes/commit/25d049c078693466905a19cc0954fafcac6c414c)) -* Add `+`, `-` as unary ops, `^` binary op ([#724](https://github.com/googleapis/python-bigquery-dataframes/issues/724)) ([968d825](https://github.com/googleapis/python-bigquery-dataframes/commit/968d8257edbfcb6d437c6203c7c0078ba782cfed)) -* Add `GroupBy.size()` to get number of rows in each group ([#479](https://github.com/googleapis/python-bigquery-dataframes/issues/479)) ([1fca588](https://github.com/googleapis/python-bigquery-dataframes/commit/1fca588e4398baa0dae61bdea0d3bff17e3971b5)) -* Add DataFrame `~` operator ([#721](https://github.com/googleapis/python-bigquery-dataframes/issues/721)) ([354abc1](https://github.com/googleapis/python-bigquery-dataframes/commit/354abc17b5bd55d70d47f893cfccd7cd0ac9794a)) -* Add GeminiText 1.5 Preview models ([#737](https://github.com/googleapis/python-bigquery-dataframes/issues/737)) ([56cbd3b](https://github.com/googleapis/python-bigquery-dataframes/commit/56cbd3b6f17c5ac22572e872b270ac7e3636675a)) -* Add slot_millis and add stats to session object ([#725](https://github.com/googleapis/python-bigquery-dataframes/issues/725)) ([72e9583](https://github.com/googleapis/python-bigquery-dataframes/commit/72e95834f8755760f3529d38f340703f3b971f0a)) -* Adds bigframes.bigquery.array_to_string to convert array elements to delimited strings ([#731](https://github.com/googleapis/python-bigquery-dataframes/issues/731)) ([f12c906](https://github.com/googleapis/python-bigquery-dataframes/commit/f12c90611adb4741069ec32840ebbf2aea83a9f3)) -* Allow functions decorated with `bpd.remote_function()` to execute locally ([#704](https://github.com/googleapis/python-bigquery-dataframes/issues/704)) ([d850da6](https://github.com/googleapis/python-bigquery-dataframes/commit/d850da6364b98c4e01120725e1e609ad8f6c1263)) -* Ensure `"bigframes-api"` label is always set on jobs, even if the API is unknown ([#722](https://github.com/googleapis/python-bigquery-dataframes/issues/722)) ([1832778](https://github.com/googleapis/python-bigquery-dataframes/commit/1832778cfc4f29fdab1b22380f03b192eb8aebb9)) -* Support `ml.SimpleImputer` in bigframes ([#708](https://github.com/googleapis/python-bigquery-dataframes/issues/708)) ([4c4415f](https://github.com/googleapis/python-bigquery-dataframes/commit/4c4415fb137e3baedc4b2d77ec146827b003557e)) -* Support type annotations to supply input and output types to `bpd.remote_function()` decorator ([#717](https://github.com/googleapis/python-bigquery-dataframes/issues/717)) ([4a12e3c](https://github.com/googleapis/python-bigquery-dataframes/commit/4a12e3c6d49d78fc2b51d783cc8de5d09e7c9995)) -* Support type annotations with `bpd.remote_function()` and `axis=1` (a preview feature) ([#730](https://github.com/googleapis/python-bigquery-dataframes/issues/730)) ([e5a2992](https://github.com/googleapis/python-bigquery-dataframes/commit/e5a299271e3bcf94c66fb6ef70393071c1b7dc69)) - - -### Bug Fixes - -* Correct index labels in multiple aggregations for DataFrameGroupBy ([#723](https://github.com/googleapis/python-bigquery-dataframes/issues/723)) ([6a78c89](https://github.com/googleapis/python-bigquery-dataframes/commit/6a78c89a3a766b747b03c8a739760db1c79f533f)) -* Fix Null index assign series to column ([#711](https://github.com/googleapis/python-bigquery-dataframes/issues/711)) ([ffb4b57](https://github.com/googleapis/python-bigquery-dataframes/commit/ffb4b5712a1a07c703ea88f66ba3f43dd2f98197)) -* Set `bpd.remote_function()`s `input_types` and `output_types` default to `None` to allow omitting them when type annotations are present ([#729](https://github.com/googleapis/python-bigquery-dataframes/issues/729)) ([0e25a3b](https://github.com/googleapis/python-bigquery-dataframes/commit/0e25a3b3ae704bf75b752c57f613e778af58bac3)) -* Warn and disable time travel for linked datasets ([#712](https://github.com/googleapis/python-bigquery-dataframes/issues/712)) ([085fa9d](https://github.com/googleapis/python-bigquery-dataframes/commit/085fa9d8fe1ea4cd02a3d25d443beaa697e10784)) - - -### Performance Improvements - -* Optimize dataframe-series alignment on axis=1 ([#732](https://github.com/googleapis/python-bigquery-dataframes/issues/732)) ([3d39221](https://github.com/googleapis/python-bigquery-dataframes/commit/3d39221526df82617a8560fd2ab7ea13bc3c03d9)) - - -### Documentation - -* Add examples to DataFrameGroupBy and SeriesGroupBy ([#701](https://github.com/googleapis/python-bigquery-dataframes/issues/701)) ([e7da0f0](https://github.com/googleapis/python-bigquery-dataframes/commit/e7da0f085eb9b9cec06e5de972f07d9c1d545ac7)) - -## [1.7.0](https://github.com/googleapis/python-bigquery-dataframes/compare/v1.6.0...v1.7.0) (2024-05-20) - - -### Features - -* `read_gbq_query` supports `filters` ([9386373](https://github.com/googleapis/python-bigquery-dataframes/commit/9386373538c1e7827e2210c4fd9946312821b54d)) -* `read_gbq` suggests a correct column name when one is not found ([9386373](https://github.com/googleapis/python-bigquery-dataframes/commit/9386373538c1e7827e2210c4fd9946312821b54d)) -* Add `DefaultIndexKind.NULL` to use as `index_col` in `read_gbq*`, creating an indexless DataFrame/Series ([#662](https://github.com/googleapis/python-bigquery-dataframes/issues/662)) ([29e4886](https://github.com/googleapis/python-bigquery-dataframes/commit/29e4886d41e3d615bc493cf3a104ef1b0698ece8)) -* Bigframes.bigquery.array_agg(SeriesGroupBy|DataFrameGroupby) ([#663](https://github.com/googleapis/python-bigquery-dataframes/issues/663)) ([412f28b](https://github.com/googleapis/python-bigquery-dataframes/commit/412f28bf7551430473690160a2a1c4c2f133539e)) -* To_datetime supports utc=False for string inputs ([#579](https://github.com/googleapis/python-bigquery-dataframes/issues/579)) ([adf9889](https://github.com/googleapis/python-bigquery-dataframes/commit/adf98892e499f4a9c85162c38f56ca5634a1ba6d)) - - -### Bug Fixes - -* `read_gbq_table` respects primary keys even when `filters` are set ([#689](https://github.com/googleapis/python-bigquery-dataframes/issues/689)) ([9386373](https://github.com/googleapis/python-bigquery-dataframes/commit/9386373538c1e7827e2210c4fd9946312821b54d)) -* Fix type error in test_cluster ([#698](https://github.com/googleapis/python-bigquery-dataframes/issues/698)) ([14d81c1](https://github.com/googleapis/python-bigquery-dataframes/commit/14d81c17505f9a09439a874ff855aec6f95fc0d1)) -* Improve escaping of literals and identifiers ([#682](https://github.com/googleapis/python-bigquery-dataframes/issues/682)) ([da9b136](https://github.com/googleapis/python-bigquery-dataframes/commit/da9b136df08b243c8515946f7c0d7b591b8fcbdc)) -* Properly identify non-unique index in tables without primary keys ([#699](https://github.com/googleapis/python-bigquery-dataframes/issues/699)) ([6e0f4d8](https://github.com/googleapis/python-bigquery-dataframes/commit/6e0f4d8c76f78dc26f4aa1880dd67ebdb638bb5e)) -* Remove a usage of the `resource` package when not available, such as on Windows ([#681](https://github.com/googleapis/python-bigquery-dataframes/issues/681)) ([96243f2](https://github.com/googleapis/python-bigquery-dataframes/commit/96243f23a1571001509d0d01c16c1e72e47e0d23)) -* The imported samples error and use peek() ([#688](https://github.com/googleapis/python-bigquery-dataframes/issues/688)) ([1a0b744](https://github.com/googleapis/python-bigquery-dataframes/commit/1a0b744c5aacdd8ba4eececf7b0a374808e8672c)) - - -### Performance Improvements - -* Don't run query immediately from `read_gbq_table` if `filters` is set ([9386373](https://github.com/googleapis/python-bigquery-dataframes/commit/9386373538c1e7827e2210c4fd9946312821b54d)) -* Use a `LIMIT` clause when `max_results` is set ([9386373](https://github.com/googleapis/python-bigquery-dataframes/commit/9386373538c1e7827e2210c4fd9946312821b54d)) - - -### Documentation - -* Add code snippets for imported onnx tutorials ([#684](https://github.com/googleapis/python-bigquery-dataframes/issues/684)) ([cb36e46](https://github.com/googleapis/python-bigquery-dataframes/commit/cb36e468d1c2a34c2231638124f3c8d9052f032b)) -* Add code snippets for imported tensorflow model ([#679](https://github.com/googleapis/python-bigquery-dataframes/issues/679)) ([b02c401](https://github.com/googleapis/python-bigquery-dataframes/commit/b02c401614eeab9cbf2e9a7c648b3d0a4e741b97)) -* Use `class_weight="balanced"` in the logistic regression prediction tutorial ([#678](https://github.com/googleapis/python-bigquery-dataframes/issues/678)) ([b951549](https://github.com/googleapis/python-bigquery-dataframes/commit/b95154908fd7838e499a2af0fc3760c5ab33358f)) - -## [1.6.0](https://github.com/googleapis/python-bigquery-dataframes/compare/v1.5.0...v1.6.0) (2024-05-13) - - -### Features - -* Add `DataFrame.__delitem__` ([#673](https://github.com/googleapis/python-bigquery-dataframes/issues/673)) ([2218c21](https://github.com/googleapis/python-bigquery-dataframes/commit/2218c21b5bb0f9e54a365ba1ada0203cbc4c9efc)) -* Add `Series.case_when()` ([#673](https://github.com/googleapis/python-bigquery-dataframes/issues/673)) ([2218c21](https://github.com/googleapis/python-bigquery-dataframes/commit/2218c21b5bb0f9e54a365ba1ada0203cbc4c9efc)) -* Add `strategy="quantile"` in KBinsDiscretizer ([#654](https://github.com/googleapis/python-bigquery-dataframes/issues/654)) ([c6c487f](https://github.com/googleapis/python-bigquery-dataframes/commit/c6c487fb3e39a980a05ff2dab5fb2b528d44016a)) -* Add Series.combine ([#680](https://github.com/googleapis/python-bigquery-dataframes/issues/680)) ([2fd1b81](https://github.com/googleapis/python-bigquery-dataframes/commit/2fd1b8117bda0dee5d8fc0924c80ce257fa9e3f1)) -* Series.str.split ([#675](https://github.com/googleapis/python-bigquery-dataframes/issues/675)) ([6eb19a7](https://github.com/googleapis/python-bigquery-dataframes/commit/6eb19a7288155b093aa7cc9bcbc710b31e7dc87a)) -* Suggest correct options in bpd.options.bigquery.location ([#666](https://github.com/googleapis/python-bigquery-dataframes/issues/666)) ([57ccabc](https://github.com/googleapis/python-bigquery-dataframes/commit/57ccabcd1402b7938e2c7068e5b4880ef018f39c)) -* Support `axis=1` in `df.apply` for scalar outputs ([#629](https://github.com/googleapis/python-bigquery-dataframes/issues/629)) ([f6bdc4a](https://github.com/googleapis/python-bigquery-dataframes/commit/f6bdc4aeb3f81a1e0b955521c04ac0dd22981c76)) -* Support gcf vpc connector in `remote_function` ([#677](https://github.com/googleapis/python-bigquery-dataframes/issues/677)) ([9ca92d0](https://github.com/googleapis/python-bigquery-dataframes/commit/9ca92d09e9c56db408350b35ec698152c13954ed)) -* Warn with a more specific `DefaultLocationWarning` category when no location can be detected ([#648](https://github.com/googleapis/python-bigquery-dataframes/issues/648)) ([e084e54](https://github.com/googleapis/python-bigquery-dataframes/commit/e084e54557addff78522bbd710637ecb4b46d23e)) - - -### Bug Fixes - -* Include `index_col` when selecting `columns` and `filters` in `read_gbq_table` ([#648](https://github.com/googleapis/python-bigquery-dataframes/issues/648)) ([e084e54](https://github.com/googleapis/python-bigquery-dataframes/commit/e084e54557addff78522bbd710637ecb4b46d23e)) - - -### Dependencies - -* Add jellyfish as a dependency for spelling correction ([57ccabc](https://github.com/googleapis/python-bigquery-dataframes/commit/57ccabcd1402b7938e2c7068e5b4880ef018f39c)) - - -### Documentation - -* Add code snippets for llm text generatiion ([#669](https://github.com/googleapis/python-bigquery-dataframes/issues/669)) ([93416ed](https://github.com/googleapis/python-bigquery-dataframes/commit/93416ed2f8353c12eb162e21e9bf155312b0ed8c)) -* Add logistic regression samples ([#673](https://github.com/googleapis/python-bigquery-dataframes/issues/673)) ([2218c21](https://github.com/googleapis/python-bigquery-dataframes/commit/2218c21b5bb0f9e54a365ba1ada0203cbc4c9efc)) -* Address lint errors in code samples ([#665](https://github.com/googleapis/python-bigquery-dataframes/issues/665)) ([4fc8964](https://github.com/googleapis/python-bigquery-dataframes/commit/4fc89644e47a6da9367b54826b25c6abbe97327b)) -* Document inlining of small data in `read_*` APIs ([#670](https://github.com/googleapis/python-bigquery-dataframes/issues/670)) ([306953a](https://github.com/googleapis/python-bigquery-dataframes/commit/306953aaae69e57c7c2f5eefb88d55a35bdcca9d)) - -## [1.5.0](https://github.com/googleapis/python-bigquery-dataframes/compare/v1.4.0...v1.5.0) (2024-05-07) - - -### Features - -* `bigframes.options` and `bigframes.option_context` now uses thread-local variables to prevent context managers in separate threads from affecting each other ([#652](https://github.com/googleapis/python-bigquery-dataframes/issues/652)) ([651fd7d](https://github.com/googleapis/python-bigquery-dataframes/commit/651fd7daf14273f172c6c55e5d6c374eb590a22d)) -* Add `ARIMAPlus.coef_` property exposing `ML.ARIMA_COEFFICIENTS` functionality ([#585](https://github.com/googleapis/python-bigquery-dataframes/issues/585)) ([81d1262](https://github.com/googleapis/python-bigquery-dataframes/commit/81d1262a40c133017c6debe89506d66aab7bb0c5)) -* Add a unique session_id to Session and allow cleaning up sessions ([#553](https://github.com/googleapis/python-bigquery-dataframes/issues/553)) ([c8d4e23](https://github.com/googleapis/python-bigquery-dataframes/commit/c8d4e231fe8263f5b10fae9b879ff82df58da534)) -* Add the `bigframes.bigquery` sub-package with a `bigframes.bigquery.array_length` function ([#630](https://github.com/googleapis/python-bigquery-dataframes/issues/630)) ([9963f85](https://github.com/googleapis/python-bigquery-dataframes/commit/9963f85b84c3b3c681447ab79e22ac93ac48349c)) -* Always do a query dry run when `option.repr_mode == "deferred"` ([#652](https://github.com/googleapis/python-bigquery-dataframes/issues/652)) ([651fd7d](https://github.com/googleapis/python-bigquery-dataframes/commit/651fd7daf14273f172c6c55e5d6c374eb590a22d)) -* Custom query labels for compute options ([#638](https://github.com/googleapis/python-bigquery-dataframes/issues/638)) ([f561799](https://github.com/googleapis/python-bigquery-dataframes/commit/f5617994bc136de5caa72719b8c3c297c512cb36)) -* Warn with `DefaultIndexWarning` from `read_gbq` on clustered/partitioned tables with no `index_col` or `filters` set ([#631](https://github.com/googleapis/python-bigquery-dataframes/issues/631), [#658](https://github.com/googleapis/python-bigquery-dataframes/issues/658)) ([2715d2b](https://github.com/googleapis/python-bigquery-dataframes/commit/2715d2b4a353710175a66a4f6149356f583f2c45), [73064dd](https://github.com/googleapis/python-bigquery-dataframes/commit/73064dd2aa1ece5de8f5849a0fd337d0ba677404)) -* Support `index_col=False` in `read_csv` and `engine="bigquery"` ([73064dd](https://github.com/googleapis/python-bigquery-dataframes/commit/73064dd2aa1ece5de8f5849a0fd337d0ba677404)) -* Support gcf max instance count in `remote_function` ([#657](https://github.com/googleapis/python-bigquery-dataframes/issues/657)) ([36578ab](https://github.com/googleapis/python-bigquery-dataframes/commit/36578ab431119f71dda746de415d0c6417bb4de2)) - - -### Bug Fixes - -* Don't raise UnknownLocationWarning for US or EU multi-regions ([#653](https://github.com/googleapis/python-bigquery-dataframes/issues/653)) ([8e4616b](https://github.com/googleapis/python-bigquery-dataframes/commit/8e4616b896f4e0d13d8bb0424c89335d3a1fe697)) -* Fix bug with na in the column labels in stack ([#659](https://github.com/googleapis/python-bigquery-dataframes/issues/659)) ([4a34293](https://github.com/googleapis/python-bigquery-dataframes/commit/4a342933559fba417fe42e2bd386838defdb2778)) -* Use explicit session in `PaLM2TextGenerator` ([#651](https://github.com/googleapis/python-bigquery-dataframes/issues/651)) ([e4f13c3](https://github.com/googleapis/python-bigquery-dataframes/commit/e4f13c3633b90e32d3171976d8b27ed10049882f)) - - -### Documentation - -* Add python code sample for multiple forecasting time series ([#531](https://github.com/googleapis/python-bigquery-dataframes/issues/531)) ([16866d2](https://github.com/googleapis/python-bigquery-dataframes/commit/16866d2bbd4901b1bf57f7e8cfbdb444d63fee6c)) -* Fix the Palm2TextGenerator output token size ([#649](https://github.com/googleapis/python-bigquery-dataframes/issues/649)) ([c67e501](https://github.com/googleapis/python-bigquery-dataframes/commit/c67e501a4958ac097216cc1c0a9d5c1530c87ae5)) - -## [1.4.0](https://github.com/googleapis/python-bigquery-dataframes/compare/v1.3.0...v1.4.0) (2024-04-29) - - -### Features - -* Add .cache() method to persist intermediate dataframe ([#626](https://github.com/googleapis/python-bigquery-dataframes/issues/626)) ([a5c94ec](https://github.com/googleapis/python-bigquery-dataframes/commit/a5c94ec90dcf2c541d7d4b9558a629f935649dd2)) -* Add transpose support for small homogeneously typed DataFrames. ([#621](https://github.com/googleapis/python-bigquery-dataframes/issues/621)) ([054075d](https://github.com/googleapis/python-bigquery-dataframes/commit/054075d448f7de1b3bc1a4631b4e2340643de4ef)) -* Allow single input type in `remote_function` ([#641](https://github.com/googleapis/python-bigquery-dataframes/issues/641)) ([3aa643f](https://github.com/googleapis/python-bigquery-dataframes/commit/3aa643f7ab6dd0ff826ca2aafbeef29035d7c912)) -* Expose gcf max timeout in `remote_function` ([#639](https://github.com/googleapis/python-bigquery-dataframes/issues/639)) ([dfeaad0](https://github.com/googleapis/python-bigquery-dataframes/commit/dfeaad0ae3b3557a9e8ccb21ddbdc55cfd611e0f)) -* Series binary ops compatible with more types ([#618](https://github.com/googleapis/python-bigquery-dataframes/issues/618)) ([518d315](https://github.com/googleapis/python-bigquery-dataframes/commit/518d315487f351c227070c0127382d11381c5e88)) -* Support the `score` method for `PaLM2TextGenerator` ([#634](https://github.com/googleapis/python-bigquery-dataframes/issues/634)) ([3ffc1d2](https://github.com/googleapis/python-bigquery-dataframes/commit/3ffc1d275ae110bffea2f08e63ef75b053764a0c)) - - -### Bug Fixes - -* Allow to_pandas to download more than 10GB ([#637](https://github.com/googleapis/python-bigquery-dataframes/issues/637)) ([ce56495](https://github.com/googleapis/python-bigquery-dataframes/commit/ce5649513b66c5191a56fc1fd29240b5dbe02394)) -* Extend row hash to 128 bits to guarantee unique row id ([#632](https://github.com/googleapis/python-bigquery-dataframes/issues/632)) ([9005c6e](https://github.com/googleapis/python-bigquery-dataframes/commit/9005c6e79297d7130e93a0e632eb3936aa145efe)) -* Llm fine tuning tests ([#627](https://github.com/googleapis/python-bigquery-dataframes/issues/627)) ([4724a1a](https://github.com/googleapis/python-bigquery-dataframes/commit/4724a1a456076d003613d2e964a8dd2d80a09ad9)) -* Llm palm score tests ([#643](https://github.com/googleapis/python-bigquery-dataframes/issues/643)) ([cf4ec3a](https://github.com/googleapis/python-bigquery-dataframes/commit/cf4ec3af96c28d42e76868c6230a38511052c44e)) - - -### Performance Improvements - -* Automatically condense internal expression representation ([#516](https://github.com/googleapis/python-bigquery-dataframes/issues/516)) ([03c1b0d](https://github.com/googleapis/python-bigquery-dataframes/commit/03c1b0d8122afe9e56b480100d6207d1228ca576)) -* Cache transpose to allow performant retranspose ([#635](https://github.com/googleapis/python-bigquery-dataframes/issues/635)) ([44b738d](https://github.com/googleapis/python-bigquery-dataframes/commit/44b738df07d0ee9d9ae2ced339a123f31139f887)) - - -### Documentation - -* Add supported pandas apis on the main page ([#628](https://github.com/googleapis/python-bigquery-dataframes/issues/628)) ([8d2a51c](https://github.com/googleapis/python-bigquery-dataframes/commit/8d2a51c4079844daba20f414b6c0c0ca030ba1f9)) -* Add the first sample for the Single time-series forecasting from Google Analytics data tutorial ([#623](https://github.com/googleapis/python-bigquery-dataframes/issues/623)) ([2b84c4f](https://github.com/googleapis/python-bigquery-dataframes/commit/2b84c4f173e956ba2c7fcc0ad92785ae95161d8e)) -* Address more technical writers' feedback ([#640](https://github.com/googleapis/python-bigquery-dataframes/issues/640)) ([1e7793c](https://github.com/googleapis/python-bigquery-dataframes/commit/1e7793cdcb56b8c0bcccc1c1ab356bac44454592)) - -## [1.3.0](https://github.com/googleapis/python-bigquery-dataframes/compare/v1.2.0...v1.3.0) (2024-04-22) - - -### Features - -* Add `Series.struct.dtypes` property ([#599](https://github.com/googleapis/python-bigquery-dataframes/issues/599)) ([d924ec2](https://github.com/googleapis/python-bigquery-dataframes/commit/d924ec2937c158644b5d1bbae4f82476de2c1655)) -* Add fine tuning `fit()` for Palm2TextGenerator ([#616](https://github.com/googleapis/python-bigquery-dataframes/issues/616)) ([9c106bd](https://github.com/googleapis/python-bigquery-dataframes/commit/9c106bd24482620ef5ff3c85f94be9da76c49716)) -* Add quantile statistic ([#613](https://github.com/googleapis/python-bigquery-dataframes/issues/613)) ([bc82804](https://github.com/googleapis/python-bigquery-dataframes/commit/bc82804da43c03c2311cd56f47a2316d3aae93d2)) -* Expose `max_batching_rows` in `remote_function` ([#622](https://github.com/googleapis/python-bigquery-dataframes/issues/622)) ([240a1ac](https://github.com/googleapis/python-bigquery-dataframes/commit/240a1ac6fa914550bb6216cd5d179a36009f2657)) -* Support primary key(s) in `read_gbq` by using as the `index_col` by default ([#625](https://github.com/googleapis/python-bigquery-dataframes/issues/625)) ([75bb240](https://github.com/googleapis/python-bigquery-dataframes/commit/75bb2409532e80de742030d05ffcbacacf5ffba2)) -* Warn if location is set to unknown location ([#609](https://github.com/googleapis/python-bigquery-dataframes/issues/609)) ([3706b4f](https://github.com/googleapis/python-bigquery-dataframes/commit/3706b4f9dde65788b5e6343a6428fb1866499461)) - - -### Bug Fixes - -* Address technical writers fb ([#611](https://github.com/googleapis/python-bigquery-dataframes/issues/611)) ([9f8f181](https://github.com/googleapis/python-bigquery-dataframes/commit/9f8f181279133abdb7da3aa045df6fa278587013)) -* Infer narrowest numeric type when combining numeric columns ([#602](https://github.com/googleapis/python-bigquery-dataframes/issues/602)) ([8f9ece6](https://github.com/googleapis/python-bigquery-dataframes/commit/8f9ece6d13f57f02d677bf0e3fea97dea94ae240)) -* Use exact median implementation by default ([#619](https://github.com/googleapis/python-bigquery-dataframes/issues/619)) ([9d205ae](https://github.com/googleapis/python-bigquery-dataframes/commit/9d205aecb77f35baeec82a8f6e1b72c2d852ca46)) - - -### Documentation - -* Fix rendering of examples for multiple apis ([#620](https://github.com/googleapis/python-bigquery-dataframes/issues/620)) ([9665e39](https://github.com/googleapis/python-bigquery-dataframes/commit/9665e39ef288841f03a9d823bd2210ef58394ad3)) -* Set `index_cols` in `read_gbq` as a best practice ([#624](https://github.com/googleapis/python-bigquery-dataframes/issues/624)) ([70015b7](https://github.com/googleapis/python-bigquery-dataframes/commit/70015b79e8cff16ff1b36c5e3f019fe099750a9d)) - -## [1.2.0](https://github.com/googleapis/python-bigquery-dataframes/compare/v1.1.0...v1.2.0) (2024-04-15) - - -### Features - -* Add hasnans, combine_first, update to Series ([#600](https://github.com/googleapis/python-bigquery-dataframes/issues/600)) ([86e0f38](https://github.com/googleapis/python-bigquery-dataframes/commit/86e0f38adc71d76e09dd832e5e33cb7c1aab02ac)) -* Add MultiIndex subclass. ([#596](https://github.com/googleapis/python-bigquery-dataframes/issues/596)) ([5d0f149](https://github.com/googleapis/python-bigquery-dataframes/commit/5d0f149dce5425098fcd154d96a302c1661ce5d3)) -* Add pivot_table for DataFrame. ([#473](https://github.com/googleapis/python-bigquery-dataframes/issues/473)) ([5f1d670](https://github.com/googleapis/python-bigquery-dataframes/commit/5f1d670e6b839a30acdb495a05011c2ce4e0c7a4)) -* Add Series.autocorr ([#605](https://github.com/googleapis/python-bigquery-dataframes/issues/605)) ([4ec8034](https://github.com/googleapis/python-bigquery-dataframes/commit/4ec80340459e675b82b437f6c48b2872d362bafe)) -* Support list of numerics in pandas.cut ([#580](https://github.com/googleapis/python-bigquery-dataframes/issues/580)) ([290f95d](https://github.com/googleapis/python-bigquery-dataframes/commit/290f95dc5198f9ab7cd9d726d40af704250c0449)) - - -### Bug Fixes - -* Address more technical writers feedback ([#581](https://github.com/googleapis/python-bigquery-dataframes/issues/581)) ([4b08d92](https://github.com/googleapis/python-bigquery-dataframes/commit/4b08d9243272229f71688152dbeb69d0ab7c68b4)) -* Error for object dtype on read_pandas ([#570](https://github.com/googleapis/python-bigquery-dataframes/issues/570)) ([8702dcf](https://github.com/googleapis/python-bigquery-dataframes/commit/8702dcf54c0f2073e21df42eaef51927481da421)) -* Inverting int now does bitwise inversion rather than sign flip ([#574](https://github.com/googleapis/python-bigquery-dataframes/issues/574)) ([5f1db8b](https://github.com/googleapis/python-bigquery-dataframes/commit/5f1db8b270b32ab366be3690761da137d9fe65f5)) -* Loc setitem dtype issue. ([#603](https://github.com/googleapis/python-bigquery-dataframes/issues/603)) ([b94bae9](https://github.com/googleapis/python-bigquery-dataframes/commit/b94bae9892e0fa79dc4bde0f4f1427d00accda6d)) -* Toc menu missing plotting name ([#591](https://github.com/googleapis/python-bigquery-dataframes/issues/591)) ([eed12c1](https://github.com/googleapis/python-bigquery-dataframes/commit/eed12c181ff8724333b1c426a0eb442c627528b8)) - - -### Documentation - -* (Series|Dataframe).dtypes ([#598](https://github.com/googleapis/python-bigquery-dataframes/issues/598)) ([edef48f](https://github.com/googleapis/python-bigquery-dataframes/commit/edef48f7a93e19bc1f6d37fb041dfd6314d881d5)) -* Add code samples for `str` accessor methdos ([#594](https://github.com/googleapis/python-bigquery-dataframes/issues/594)) ([a557ea2](https://github.com/googleapis/python-bigquery-dataframes/commit/a557ea2b64633932f730b56688f76806da6195fb)) -* Add docs for `DataFrame` and `Series` dunder methods ([#562](https://github.com/googleapis/python-bigquery-dataframes/issues/562)) ([8fc26c4](https://github.com/googleapis/python-bigquery-dataframes/commit/8fc26c424b29a8b78542372e402fcc4e8fface7b)) -* Add examples for at/iat ([#582](https://github.com/googleapis/python-bigquery-dataframes/issues/582)) ([3be4a2e](https://github.com/googleapis/python-bigquery-dataframes/commit/3be4a2e784e046ca9a1fac8d386d072537b6c4de)) - -## [1.1.0](https://github.com/googleapis/python-bigquery-dataframes/compare/v1.0.0...v1.1.0) (2024-04-04) - - -### Features - -* (Series|DataFrame).explode ([#556](https://github.com/googleapis/python-bigquery-dataframes/issues/556)) ([9e32f57](https://github.com/googleapis/python-bigquery-dataframes/commit/9e32f570b42c8ddae0c9b281b25beff91f0c922c)) -* Add `DataFrame.eval` and `DataFrame.query` ([#361](https://github.com/googleapis/python-bigquery-dataframes/issues/361)) ([5e28ebd](https://github.com/googleapis/python-bigquery-dataframes/commit/5e28ebd1ba3a5559e093c2ea676c0714c1434ba9)) -* Add ColumnTransformer save/load ([#541](https://github.com/googleapis/python-bigquery-dataframes/issues/541)) ([9d8cf67](https://github.com/googleapis/python-bigquery-dataframes/commit/9d8cf6792a8dbe03e03b102c454d15fcde7986af)) -* Add ml.metrics.mean_squared_error ([#559](https://github.com/googleapis/python-bigquery-dataframes/issues/559)) ([853c25e](https://github.com/googleapis/python-bigquery-dataframes/commit/853c25e8023bf877f28cda4dade0694d0299a83e)) -* Add support for numpy expm1, log1p, floor, ceil, arctan2 ops ([#505](https://github.com/googleapis/python-bigquery-dataframes/issues/505)) ([e8e66cf](https://github.com/googleapis/python-bigquery-dataframes/commit/e8e66cf25887f64d2a7cb26081c2ef3cea10827d)) -* Add transformers save/load ([#552](https://github.com/googleapis/python-bigquery-dataframes/issues/552)) ([d805241](https://github.com/googleapis/python-bigquery-dataframes/commit/d805241b7ec99fcb7579dce778d4b04778a72002)) -* Allow DataFrame binary ops to align on either axis and with loc… ([#544](https://github.com/googleapis/python-bigquery-dataframes/issues/544)) ([6d8f3af](https://github.com/googleapis/python-bigquery-dataframes/commit/6d8f3afe28d39eb15b969f50d37c58a2c3ff1967)) -* Expose `DataFrame.bqclient` to assist in integrations ([#519](https://github.com/googleapis/python-bigquery-dataframes/issues/519)) ([0be8911](https://github.com/googleapis/python-bigquery-dataframes/commit/0be891191ed89be77494e4dcda30fb37836842ac)) -* Read_pandas accepts pandas Series and Index objects ([#573](https://github.com/googleapis/python-bigquery-dataframes/issues/573)) ([f8821fe](https://github.com/googleapis/python-bigquery-dataframes/commit/f8821fe7ecf8a80532a6aab98044fad601ff939c)) -* Support `ML.GENERATE_EMBEDDING` in `PaLM2TextEmbeddingGenerator` ([#539](https://github.com/googleapis/python-bigquery-dataframes/issues/539)) ([1156c1e](https://github.com/googleapis/python-bigquery-dataframes/commit/1156c1e3ce8c1e62898dbe68ccd6c5ab3cd4068f)) -* Support max_columns in repr and make repr more efficient ([#515](https://github.com/googleapis/python-bigquery-dataframes/issues/515)) ([54e49cf](https://github.com/googleapis/python-bigquery-dataframes/commit/54e49cff89bd329852a823cd5cf5c5b41b7f9e32)) - - -### Bug Fixes - -* Assign NaN scalar to column error. ([#513](https://github.com/googleapis/python-bigquery-dataframes/issues/513)) ([0a4153c](https://github.com/googleapis/python-bigquery-dataframes/commit/0a4153cc71a44c09b8d691897f1e5afa58c69f25)) -* Don't download 100gb onto local python machine in load test ([#537](https://github.com/googleapis/python-bigquery-dataframes/issues/537)) ([082c58b](https://github.com/googleapis/python-bigquery-dataframes/commit/082c58bbe76821b90337dc5af0ab5fa7515682c2)) -* Exclude list-like s parameter in plot.scatter ([#568](https://github.com/googleapis/python-bigquery-dataframes/issues/568)) ([1caac27](https://github.com/googleapis/python-bigquery-dataframes/commit/1caac27fe95ef3eb36bad2ac351090891922858c)) -* Fix case where df.peek would fail to execute even with force=True ([#511](https://github.com/googleapis/python-bigquery-dataframes/issues/511)) ([8eca99a](https://github.com/googleapis/python-bigquery-dataframes/commit/8eca99a03bc4bdaccf15a979b5382f3659f2aac5)) -* Fix error in `Series.drop(0)` ([#575](https://github.com/googleapis/python-bigquery-dataframes/issues/575)) ([75dd786](https://github.com/googleapis/python-bigquery-dataframes/commit/75dd7862e60502c97f7defe5dfefb044ea74bae8)) -* Include all names in MultiIndex repr ([#564](https://github.com/googleapis/python-bigquery-dataframes/issues/564)) ([b188146](https://github.com/googleapis/python-bigquery-dataframes/commit/b188146466780e6f7a041f51f5be51a7d60719c9)) -* Plot.scatter s parameter cannot accept float-like column ([#563](https://github.com/googleapis/python-bigquery-dataframes/issues/563)) ([8d39187](https://github.com/googleapis/python-bigquery-dataframes/commit/8d3918761a17649180aa806d7b01aa103f69b4fe)) -* Product operation produces float result for all input types ([#501](https://github.com/googleapis/python-bigquery-dataframes/issues/501)) ([6873b30](https://github.com/googleapis/python-bigquery-dataframes/commit/6873b30b691a11a368308825a72013d8ec1408ed)) -* Reloaded transformer .transform error ([#569](https://github.com/googleapis/python-bigquery-dataframes/issues/569)) ([39fe474](https://github.com/googleapis/python-bigquery-dataframes/commit/39fe47451d24a8cf55d7dbb15c6d3b176d25ab18)) -* Rename PaLM2TextEmbeddingGenerator.predict output columns to be backward compatible ([#561](https://github.com/googleapis/python-bigquery-dataframes/issues/561)) ([4995c00](https://github.com/googleapis/python-bigquery-dataframes/commit/4995c0046265463bc5c502cbeb34c7632d5a255e)) -* Respect hard stack size limit and swallow limit change exception. ([#558](https://github.com/googleapis/python-bigquery-dataframes/issues/558)) ([4833908](https://github.com/googleapis/python-bigquery-dataframes/commit/483390830ae0ee2fe0fb47dc7d2aea143b2dc7d8)) -* Restore string to date/time type coercion ([#565](https://github.com/googleapis/python-bigquery-dataframes/issues/565)) ([4ae0262](https://github.com/googleapis/python-bigquery-dataframes/commit/4ae0262a2b1dfc35c1e4c3392b9e21456d6e964e)) -* Sync the notebook with embedding changes ([#550](https://github.com/googleapis/python-bigquery-dataframes/issues/550)) ([347f2dd](https://github.com/googleapis/python-bigquery-dataframes/commit/347f2dda2298e17cd44a298f04a723f2d20c080a)) -* Use bytes limit on frame inlining rather than element count ([#576](https://github.com/googleapis/python-bigquery-dataframes/issues/576)) ([659a161](https://github.com/googleapis/python-bigquery-dataframes/commit/659a161a53e93f66334cd04d1c3dc1f1f47ecc16)) - - -### Performance Improvements - -* Add multi-query execution capability for complex dataframes ([#427](https://github.com/googleapis/python-bigquery-dataframes/issues/427)) ([d2d7e33](https://github.com/googleapis/python-bigquery-dataframes/commit/d2d7e33b1f8b4e184ef3e76eedbd673a8fcee60e)) - - -### Dependencies - -* Include `pyarrow` as a dependency ([#529](https://github.com/googleapis/python-bigquery-dataframes/issues/529)) ([9b1525a](https://github.com/googleapis/python-bigquery-dataframes/commit/9b1525a0c359455160bfbc0dc1366e37982ad01f)) - - -### Documentation - -* `bigframes.options.bigquery.project` and `location` are optional in some circumstances ([#548](https://github.com/googleapis/python-bigquery-dataframes/issues/548)) ([90bcec5](https://github.com/googleapis/python-bigquery-dataframes/commit/90bcec5c73f7eefeff14bbd8bdcad3a4c9d91d8f)) -* Add "Supported pandas APIs" reference to the documentation ([#542](https://github.com/googleapis/python-bigquery-dataframes/issues/542)) ([74c3915](https://github.com/googleapis/python-bigquery-dataframes/commit/74c391586280b55c35d66c697167122d72c13386)) -* Add General Availability banner to README ([#507](https://github.com/googleapis/python-bigquery-dataframes/issues/507)) ([262ff59](https://github.com/googleapis/python-bigquery-dataframes/commit/262ff5922643039e037bd9b6c0a91b5bd20a4e08)) -* Add opeartions in API docs ([#557](https://github.com/googleapis/python-bigquery-dataframes/issues/557)) ([ea95761](https://github.com/googleapis/python-bigquery-dataframes/commit/ea9576125d46f3912372f75ebe51196ba83e96db)) -* Add progress_bar code sample ([#508](https://github.com/googleapis/python-bigquery-dataframes/issues/508)) ([92a1af3](https://github.com/googleapis/python-bigquery-dataframes/commit/92a1af35b8de4afb6cdb5b5e89facdceb5c151d2)) -* Add the code samples for metrics{auc, roc_auc_score, roc_curve} ([#520](https://github.com/googleapis/python-bigquery-dataframes/issues/520)) ([5f37b09](https://github.com/googleapis/python-bigquery-dataframes/commit/5f37b0902fae2c099207acf3ce2e251c09ac889d)) -* Address more comments from technical writers to meet legal purposes ([#571](https://github.com/googleapis/python-bigquery-dataframes/issues/571)) ([9084df3](https://github.com/googleapis/python-bigquery-dataframes/commit/9084df369bc6819edf5f57ceba85667a14371ac5)) -* Fix docs of ARIMAPlus.predict ([#512](https://github.com/googleapis/python-bigquery-dataframes/issues/512)) ([3b80f95](https://github.com/googleapis/python-bigquery-dataframes/commit/3b80f956755c9d7043138aab6e5687cba50be8cb)) -* Include Index in table-of-contents ([#564](https://github.com/googleapis/python-bigquery-dataframes/issues/564)) ([b188146](https://github.com/googleapis/python-bigquery-dataframes/commit/b188146466780e6f7a041f51f5be51a7d60719c9)) -* Mark Gemini model as Pre-GA ([#543](https://github.com/googleapis/python-bigquery-dataframes/issues/543)) ([769868b](https://github.com/googleapis/python-bigquery-dataframes/commit/769868b9fc7dfff2e7b1ed5cec52a5dd3dfd6ff2)) -* Migrate the overview page to Bigframes official landing page ([#536](https://github.com/googleapis/python-bigquery-dataframes/issues/536)) ([a0fb8bb](https://github.com/googleapis/python-bigquery-dataframes/commit/a0fb8bbfddd07f1e0ef03eeb4be653d1e9f06772)) - -## [1.0.0](https://github.com/googleapis/python-bigquery-dataframes/compare/v0.26.0...v1.0.0) (2024-03-25) - - -### ⚠ BREAKING CHANGES - -* rename model parameter `min_rel_progress` to `tol` -* `early_stop` setting no longer supported, always uses `True` -* rename model parameter `n_parallell_trees` to `n_estimators` -* rename `class_weights` to `class_weight` -* rename `learn_rate` to `learning_rate` -* PCA `n_components` supports float value and `None`, default to `None` -* rename various ml model parameters for consistency with sklearn (https://github.com/googleapis/python-bigquery-dataframes/pull/491) - -### Features - -* Add configuration option to read_gbq ([#401](https://github.com/googleapis/python-bigquery-dataframes/issues/401)) ([85cede2](https://github.com/googleapis/python-bigquery-dataframes/commit/85cede22587a9fe1dae888721492f9390dc46d70)) -* Add ml ARIMAPlus model params ([#488](https://github.com/googleapis/python-bigquery-dataframes/issues/488)) ([352cb85](https://github.com/googleapis/python-bigquery-dataframes/commit/352cb850d23e41a2278edf0df584b89ee9619aab)) -* Add ml KMeans model params ([#477](https://github.com/googleapis/python-bigquery-dataframes/issues/477)) ([23a8d9a](https://github.com/googleapis/python-bigquery-dataframes/commit/23a8d9a32e1619aff92c8dfabb7bcdd54c314bd5)) -* Add ml LogisticRegression model params ([#481](https://github.com/googleapis/python-bigquery-dataframes/issues/481)) ([f959b65](https://github.com/googleapis/python-bigquery-dataframes/commit/f959b653a0e82b5bfd21f9e994031cf6d25c281a)) -* Add ml PCA model params ([#474](https://github.com/googleapis/python-bigquery-dataframes/issues/474)) ([fb5d83b](https://github.com/googleapis/python-bigquery-dataframes/commit/fb5d83b1e35c465cff486e6cf7862e5b32e3c65a)) -* Add params for LinearRegression model ([#464](https://github.com/googleapis/python-bigquery-dataframes/issues/464)) ([21b2188](https://github.com/googleapis/python-bigquery-dataframes/commit/21b2188cd0ca85485b5171ee9e46da4c924e2ff8)) -* Add support for Python 3.12 ([#231](https://github.com/googleapis/python-bigquery-dataframes/issues/231)) ([df2976f](https://github.com/googleapis/python-bigquery-dataframes/commit/df2976fa9fd0319b824128d0ccf2ebb20f381caa)) -* Allow assigning directly to Series.name property ([#495](https://github.com/googleapis/python-bigquery-dataframes/issues/495)) ([ad0e99e](https://github.com/googleapis/python-bigquery-dataframes/commit/ad0e99eddb1dddd3d439cea7db1e4f222b45c6b9)) -* Ensure `Series.str.len()` can get length of array columns ([#497](https://github.com/googleapis/python-bigquery-dataframes/issues/497)) ([10c0446](https://github.com/googleapis/python-bigquery-dataframes/commit/10c044686228e5c6f3868c1eb10454f6a086ac8b)) -* Option to use bq connection without check ([#460](https://github.com/googleapis/python-bigquery-dataframes/issues/460)) ([0b3f8e5](https://github.com/googleapis/python-bigquery-dataframes/commit/0b3f8e5ce63f75ba99ee8cf29226a0fd38bef99f)) -* PCA `n_components` supports float value and `None`, default to `None` ([65c6f47](https://github.com/googleapis/python-bigquery-dataframes/commit/65c6f4736d1a5552835e4cec8b777b2c0f3dd8da)) -* Rename `class_weights` to `class_weight` ([65c6f47](https://github.com/googleapis/python-bigquery-dataframes/commit/65c6f4736d1a5552835e4cec8b777b2c0f3dd8da)) -* Rename `learn_rate` to `learning_rate` ([65c6f47](https://github.com/googleapis/python-bigquery-dataframes/commit/65c6f4736d1a5552835e4cec8b777b2c0f3dd8da)) -* Rename model parameter `min_rel_progress` to `tol` ([65c6f47](https://github.com/googleapis/python-bigquery-dataframes/commit/65c6f4736d1a5552835e4cec8b777b2c0f3dd8da)) -* Rename model parameter `n_parallell_trees` to `n_estimators` ([65c6f47](https://github.com/googleapis/python-bigquery-dataframes/commit/65c6f4736d1a5552835e4cec8b777b2c0f3dd8da)) -* Rename various ml model parameters for consistency with sklearn (https://github.com/googleapis/python-bigquery-dataframes/pull/491) ([65c6f47](https://github.com/googleapis/python-bigquery-dataframes/commit/65c6f4736d1a5552835e4cec8b777b2c0f3dd8da)) -* Support BQ regional endpoints for europe-west9, europe-west3, us-east4, and us-west1 ([#504](https://github.com/googleapis/python-bigquery-dataframes/issues/504)) ([fbada4a](https://github.com/googleapis/python-bigquery-dataframes/commit/fbada4a70688c5d13fa35d1843b0c4252c5ced72)) -* Support dataframe.cov ([#498](https://github.com/googleapis/python-bigquery-dataframes/issues/498)) ([c4beafd](https://github.com/googleapis/python-bigquery-dataframes/commit/c4beafdf0c1ba88b306ca96fa3ca46b86debaa4c)) -* Support Series.dt.floor ([#493](https://github.com/googleapis/python-bigquery-dataframes/issues/493)) ([2dd01c2](https://github.com/googleapis/python-bigquery-dataframes/commit/2dd01c25e9f01c03979c61e71d3c5cd9f0bd4c96)) -* Support Series.dt.normalize ([#483](https://github.com/googleapis/python-bigquery-dataframes/issues/483)) ([0bf1e91](https://github.com/googleapis/python-bigquery-dataframes/commit/0bf1e916c2b636ec02ac010190e89d38e88fce4b)) -* Update plot sample to 1000 rows ([#458](https://github.com/googleapis/python-bigquery-dataframes/issues/458)) ([60d4a7b](https://github.com/googleapis/python-bigquery-dataframes/commit/60d4a7bbac867256f8bbfd3053c7dd2645c1b062)) - - -### Bug Fixes - -* `early_stop` setting no longer supported, always uses `True` ([65c6f47](https://github.com/googleapis/python-bigquery-dataframes/commit/65c6f4736d1a5552835e4cec8b777b2c0f3dd8da)) -* Fix -1 offset lookups failing ([#463](https://github.com/googleapis/python-bigquery-dataframes/issues/463)) ([2dfb9c2](https://github.com/googleapis/python-bigquery-dataframes/commit/2dfb9c24d07841d785e41b33573c5f3a218efeea)) -* Plot.scatter `c` argument functionalities ([#494](https://github.com/googleapis/python-bigquery-dataframes/issues/494)) ([d6ee994](https://github.com/googleapis/python-bigquery-dataframes/commit/d6ee994c17e0b1dd6768b09ee81d2c902f601b76)) -* Properly support format param for numerical input. ([#486](https://github.com/googleapis/python-bigquery-dataframes/issues/486)) ([ae20c35](https://github.com/googleapis/python-bigquery-dataframes/commit/ae20c3583d5526777548b5d594ecca6034bb49ec)) -* Renable to_csv and to_json related tests ([#468](https://github.com/googleapis/python-bigquery-dataframes/issues/468)) ([2b9a01d](https://github.com/googleapis/python-bigquery-dataframes/commit/2b9a01de0adb8d41fbe73ce94b1acc8d22f507b5)) -* Sampling plot cannot preserve ordering if index is not ordered ([#475](https://github.com/googleapis/python-bigquery-dataframes/issues/475)) ([a5345fe](https://github.com/googleapis/python-bigquery-dataframes/commit/a5345fe8943667a89fcba48ce31aa8ecfc283f92)) -* Use actual BigQuery types rather than ibis types in to_pandas ([#500](https://github.com/googleapis/python-bigquery-dataframes/issues/500)) ([82b4f91](https://github.com/googleapis/python-bigquery-dataframes/commit/82b4f91db365fe06d8bd0bf938f880a48091104e)) - - -### Dependencies - -* Support pandas 2.2 ([#492](https://github.com/googleapis/python-bigquery-dataframes/issues/492)) ([e2cf50e](https://github.com/googleapis/python-bigquery-dataframes/commit/e2cf50e053f7163d1654c4b5621cc93e922d5148)) - - -### Documentation - -* Add code samples for metrics.{accuracy_score, confusion_matrix} ([#478](https://github.com/googleapis/python-bigquery-dataframes/issues/478)) ([3e3329a](https://github.com/googleapis/python-bigquery-dataframes/commit/3e3329a37c1020bd3e6d4d5e980103c63ab0c337)) -* Add code samples for metrics.{recall_score, precision_score, f11_score} ([#502](https://github.com/googleapis/python-bigquery-dataframes/issues/502)) ([370fe90](https://github.com/googleapis/python-bigquery-dataframes/commit/370fe9087848862d02f0e5a333fcb4cd37cf5ca0)) -* Improve API documentation ([#489](https://github.com/googleapis/python-bigquery-dataframes/issues/489)) ([751266e](https://github.com/googleapis/python-bigquery-dataframes/commit/751266e056ac566ef5b6e40fbbca84ed95e7a7a9)) -* Update bigquery connection documentation ([#499](https://github.com/googleapis/python-bigquery-dataframes/issues/499)) ([4bfe094](https://github.com/googleapis/python-bigquery-dataframes/commit/4bfe094fdf2f7e1af72cc939558713a499760129)) -* Update LLM + K-means notebook to handle partial failures ([#496](https://github.com/googleapis/python-bigquery-dataframes/issues/496)) ([97afad9](https://github.com/googleapis/python-bigquery-dataframes/commit/97afad96f80c1815db8ad34f0ff62095631036c2)) - -## [0.26.0](https://github.com/googleapis/python-bigquery-dataframes/compare/v0.25.0...v0.26.0) (2024-03-20) - - -### ⚠ BREAKING CHANGES - -* exclude remote models for .register() ([#465](https://github.com/googleapis/python-bigquery-dataframes/issues/465)) - -### Features - -* (Series|DataFrame).plot ([#438](https://github.com/googleapis/python-bigquery-dataframes/issues/438)) ([1c3e668](https://github.com/googleapis/python-bigquery-dataframes/commit/1c3e668ceb26fd0f1377acbf6b95e8f4bcef40d6)) -* `read_gbq_table` supports `LIKE` as a operator in `filters` ([#454](https://github.com/googleapis/python-bigquery-dataframes/issues/454)) ([d2d425a](https://github.com/googleapis/python-bigquery-dataframes/commit/d2d425a93aa9e96f3b71c3ca3b185f4b5eaf32ef)) -* Add DataFrame.pipe() method ([#421](https://github.com/googleapis/python-bigquery-dataframes/issues/421)) ([95f5a6e](https://github.com/googleapis/python-bigquery-dataframes/commit/95f5a6e749468743af65062e559bc35ac56f3c24)) -* Set `force=True` by default in `DataFrame.peek()` ([#469](https://github.com/googleapis/python-bigquery-dataframes/issues/469)) ([4e8e97d](https://github.com/googleapis/python-bigquery-dataframes/commit/4e8e97d661078ed38d77be93b0bc1ad0fd52949c)) -* Support datetime related casting in (Series|DataFrame|Index).astype ([#442](https://github.com/googleapis/python-bigquery-dataframes/issues/442)) ([fde339b](https://github.com/googleapis/python-bigquery-dataframes/commit/fde339b71c754e617c61052940215b77890b59e4)) -* Support Series.dt.strftime ([#453](https://github.com/googleapis/python-bigquery-dataframes/issues/453)) ([8f6e955](https://github.com/googleapis/python-bigquery-dataframes/commit/8f6e955fc946db97c95ea012659432355b0cd12c)) - - -### Bug Fixes - -* Any() on empty set now correctly returns False ([#471](https://github.com/googleapis/python-bigquery-dataframes/issues/471)) ([f55680c](https://github.com/googleapis/python-bigquery-dataframes/commit/f55680cd0eed46ee06cd9baf658de792f4a27f31)) -* Df.drop_na preserves columns dtype ([#457](https://github.com/googleapis/python-bigquery-dataframes/issues/457)) ([3bab1a9](https://github.com/googleapis/python-bigquery-dataframes/commit/3bab1a917a5833bd58b20071a229ee95cf86a251)) -* Disable to_json and to_csv related tests ([#462](https://github.com/googleapis/python-bigquery-dataframes/issues/462)) ([874026d](https://github.com/googleapis/python-bigquery-dataframes/commit/874026da612bf08fbaf6d7dbfaa3325dc8a61500)) -* Exclude remote models for .register() ([#465](https://github.com/googleapis/python-bigquery-dataframes/issues/465)) ([73fe0f8](https://github.com/googleapis/python-bigquery-dataframes/commit/73fe0f89a96557afc4225521654978b96a2291b3)) -* Fix broken link in covid notebook ([#450](https://github.com/googleapis/python-bigquery-dataframes/issues/450)) ([adadb06](https://github.com/googleapis/python-bigquery-dataframes/commit/adadb0658c35142fed228abbd9baa42f9372f44b)) -* Fix broken multiindex loc cases ([#467](https://github.com/googleapis/python-bigquery-dataframes/issues/467)) ([b519197](https://github.com/googleapis/python-bigquery-dataframes/commit/b519197d51cc098ac4981a9a57a9d6988ba07d03)) -* Fix grouping series on multiple other series ([#455](https://github.com/googleapis/python-bigquery-dataframes/issues/455)) ([3971bd2](https://github.com/googleapis/python-bigquery-dataframes/commit/3971bd27c96b68b859399564dbb6abdb93de5f14)) -* Groupby aggregates no longer check if grouping keys are numeric ([#472](https://github.com/googleapis/python-bigquery-dataframes/issues/472)) ([4fbf938](https://github.com/googleapis/python-bigquery-dataframes/commit/4fbf938c200a3e0e6b592aa4a4e18b59f2f34082)) -* Raise `ValueError` when `read_pandas()` receives a bigframes `DataFrame` ([#447](https://github.com/googleapis/python-bigquery-dataframes/issues/447)) ([b28f9fd](https://github.com/googleapis/python-bigquery-dataframes/commit/b28f9fdd9681b3c9783a6e52322b70093e0283ec)) -* Series.(to_csv|to_json) leverages bq export ([#452](https://github.com/googleapis/python-bigquery-dataframes/issues/452)) ([718a00c](https://github.com/googleapis/python-bigquery-dataframes/commit/718a00c1fa8ac44b0d3a79a2217e5b12690785fb)) -* Warn when `read_gbq` / `read_gbq_table` uses the snapshot time cache ([#441](https://github.com/googleapis/python-bigquery-dataframes/issues/441)) ([e16a8c0](https://github.com/googleapis/python-bigquery-dataframes/commit/e16a8c0a6fb46cf1a7be12eec9471ae95d6f2c44)) - - -### Documentation - -* Add code samples for `ml.metrics.r2_score` ([#459](https://github.com/googleapis/python-bigquery-dataframes/issues/459)) ([85fefa2](https://github.com/googleapis/python-bigquery-dataframes/commit/85fefa2f1d4dbe3e0c9d4ab8124cea88eb5df38f)) -* Add the docs for loc and iloc indexers ([#446](https://github.com/googleapis/python-bigquery-dataframes/issues/446)) ([14ab8d8](https://github.com/googleapis/python-bigquery-dataframes/commit/14ab8d834d793ac7644f066145912e6d50966881)) -* Add the pages for at and iat indexers ([#456](https://github.com/googleapis/python-bigquery-dataframes/issues/456)) ([340f0b5](https://github.com/googleapis/python-bigquery-dataframes/commit/340f0b5b41fc5150d73890c7f27ae68dc308e160)) -* Add version information to bug template ([#437](https://github.com/googleapis/python-bigquery-dataframes/issues/437)) ([91bd39e](https://github.com/googleapis/python-bigquery-dataframes/commit/91bd39e8b194ddad09d53fca96201eee58063bb9)) -* Indicate that project and location are optional in example notebooks ([#451](https://github.com/googleapis/python-bigquery-dataframes/issues/451)) ([1df0140](https://github.com/googleapis/python-bigquery-dataframes/commit/1df014010652e7827a2720a906d0afe482a30ca9)) - -## [0.25.0](https://github.com/googleapis/python-bigquery-dataframes/compare/v0.24.0...v0.25.0) (2024-03-14) - - -### Features - -* (Series|DataFrame).plot.(line|area|scatter) ([#431](https://github.com/googleapis/python-bigquery-dataframes/issues/431)) ([0772510](https://github.com/googleapis/python-bigquery-dataframes/commit/077251084e3121019c56e5d6c16aebab16be8dc7)) -* Support CMEK for `remote_function` cloud functions ([#430](https://github.com/googleapis/python-bigquery-dataframes/issues/430)) ([2fd69f4](https://github.com/googleapis/python-bigquery-dataframes/commit/2fd69f4bed143fc8c040dac1c55288c1cb660f6e)) - -## [0.24.0](https://github.com/googleapis/python-bigquery-dataframes/compare/v0.23.0...v0.24.0) (2024-03-12) - - -### ⚠ BREAKING CHANGES - -* `read_parquet` uses a "pandas" engine to parse files by default. Use `engine="bigquery"` for the previous behavior - -### Features - -* (Series|Dataframe).plot.hist() ([#420](https://github.com/googleapis/python-bigquery-dataframes/issues/420)) ([4aadff4](https://github.com/googleapis/python-bigquery-dataframes/commit/4aadff4db59243b4510a874fef2bdb17402d1674)) -* Add detect_anomalies to ml ARIMAPlus and KMeans models ([#426](https://github.com/googleapis/python-bigquery-dataframes/issues/426)) ([6df28ed](https://github.com/googleapis/python-bigquery-dataframes/commit/6df28ed704552ebec7869e1f2034614cb6407098)) -* Add engine parameter to `read_parquet` ([#413](https://github.com/googleapis/python-bigquery-dataframes/issues/413)) ([31325a1](https://github.com/googleapis/python-bigquery-dataframes/commit/31325a190320bf01ced53d9f4cdb94462daaa06b)) -* Add ml PCA.detect_anomalies method ([#422](https://github.com/googleapis/python-bigquery-dataframes/issues/422)) ([8d82945](https://github.com/googleapis/python-bigquery-dataframes/commit/8d8294544ac7fedaca753c5473e3ca2a27868420)) -* Support BYOSA in `remote_function` ([#407](https://github.com/googleapis/python-bigquery-dataframes/issues/407)) ([d92ced2](https://github.com/googleapis/python-bigquery-dataframes/commit/d92ced2adaa30a0405ace9ca6cd70a8e217f13d0)) -* Support CMEK for BQ tables ([#403](https://github.com/googleapis/python-bigquery-dataframes/issues/403)) ([9a678e3](https://github.com/googleapis/python-bigquery-dataframes/commit/9a678e35201d935e1d93875429005033cfe7cff6)) - - -### Bug Fixes - -* Move `third_party.bigframes_vendored` to `bigframes_vendored` ([#424](https://github.com/googleapis/python-bigquery-dataframes/issues/424)) ([763edeb](https://github.com/googleapis/python-bigquery-dataframes/commit/763edeb4f4e8bc4b8bb05a992dae80c49c245e25)) -* Only do row identity based joins when joining by index ([#356](https://github.com/googleapis/python-bigquery-dataframes/issues/356)) ([76b252f](https://github.com/googleapis/python-bigquery-dataframes/commit/76b252f907055d72556e3e95f6cb5ee41de5b1c2)) -* Read_pandas inline respects location ([#412](https://github.com/googleapis/python-bigquery-dataframes/issues/412)) ([ae0e3ea](https://github.com/googleapis/python-bigquery-dataframes/commit/ae0e3eaca49171fd449de4d43ddc3e3ce9fdc2ce)) - - -### Documentation - -* Add predict sample to samples/snippets/bqml_getting_started_test.py ([#388](https://github.com/googleapis/python-bigquery-dataframes/issues/388)) ([6a3b0cc](https://github.com/googleapis/python-bigquery-dataframes/commit/6a3b0cc7f84120fc5978ce11b6b7c55e89654304)) -* Document minimum IAM requirement ([#416](https://github.com/googleapis/python-bigquery-dataframes/issues/416)) ([36173b0](https://github.com/googleapis/python-bigquery-dataframes/commit/36173b0c14747fb52909bbedd93249024bae9ac1)) -* Fix the note rendering for DataFrames methods: nlargest, nsmallest ([#417](https://github.com/googleapis/python-bigquery-dataframes/issues/417)) ([38bd2ba](https://github.com/googleapis/python-bigquery-dataframes/commit/38bd2ba21bc1a3222635de22eecd97930bf5b1de)) - -## [0.23.0](https://github.com/googleapis/python-bigquery-dataframes/compare/v0.22.0...v0.23.0) (2024-03-05) - - -### Features - -* Add ml.metrics.pairwise.euclidean_distance ([#397](https://github.com/googleapis/python-bigquery-dataframes/issues/397)) ([1726588](https://github.com/googleapis/python-bigquery-dataframes/commit/1726588beb8894bc08c272d718ca8e3a9451d0c2)) -* Add TextEmbedding model version support ([#394](https://github.com/googleapis/python-bigquery-dataframes/issues/394)) ([e0f1ab0](https://github.com/googleapis/python-bigquery-dataframes/commit/e0f1ab07cbc81034e24767baff54560561950e67)) - - -### Bug Fixes - -* Code exception in `remote_function` now prevents retry and surfaces in the client ([#387](https://github.com/googleapis/python-bigquery-dataframes/issues/387)) ([dd3643d](https://github.com/googleapis/python-bigquery-dataframes/commit/dd3643d3733ca1c2a18352bafac7d32fbdfa2a25)) -* Docs link for metrics.pairwise ([#400](https://github.com/googleapis/python-bigquery-dataframes/issues/400)) ([a60aba7](https://github.com/googleapis/python-bigquery-dataframes/commit/a60aba712576e2e4e14cfcfffe9349d6972716a5)) - - -### Dependencies - -* Update ibis to version 8.0.0 and refactor `remote_function` to use ibis UDF method ([#277](https://github.com/googleapis/python-bigquery-dataframes/issues/277)) ([350499b](https://github.com/googleapis/python-bigquery-dataframes/commit/350499bccb62e22169ab2f2e1400175b2179ef85)) - - -### Documentation - -* Update README to point to new summary pages ([#402](https://github.com/googleapis/python-bigquery-dataframes/issues/402)) ([bfe2b23](https://github.com/googleapis/python-bigquery-dataframes/commit/bfe2b23e2dea0cdf1e1b6ff5b17f6759d73c3e24)) - -## [0.22.0](https://github.com/googleapis/python-bigquery-dataframes/compare/v0.21.0...v0.22.0) (2024-02-27) - - -### ⚠ BREAKING CHANGES - -* rename cosine_similarity to paired_cosine_distances ([#393](https://github.com/googleapis/python-bigquery-dataframes/issues/393)) -* move model optional args to kwargs ([#381](https://github.com/googleapis/python-bigquery-dataframes/issues/381)) - -### Features - -* Add `DataFrames.corr()` method ([#379](https://github.com/googleapis/python-bigquery-dataframes/issues/379)) ([67fd434](https://github.com/googleapis/python-bigquery-dataframes/commit/67fd434bbb1c73f9013f65252d1ecc8da79542f6)) -* Add ml.metrics.pairwise.manhattan_distance ([#392](https://github.com/googleapis/python-bigquery-dataframes/issues/392)) ([9d31865](https://github.com/googleapis/python-bigquery-dataframes/commit/9d318653c001287bcc8ae9d8e09d0187413cbed6)) -* Enable regional endpoints for me-central2 ([#386](https://github.com/googleapis/python-bigquery-dataframes/issues/386)) ([469674d](https://github.com/googleapis/python-bigquery-dataframes/commit/469674d64f6ad5dac0f24ad450a7b8b6998fdf68)) - - -### Bug Fixes - -* Avoid ibis warning for "database" table() method argument ([#390](https://github.com/googleapis/python-bigquery-dataframes/issues/390)) ([a0490a4](https://github.com/googleapis/python-bigquery-dataframes/commit/a0490a492a43db24a314b3f42bfac61da7683151)) -* Correct the numeric literal dtype ([#365](https://github.com/googleapis/python-bigquery-dataframes/issues/365)) ([93b02cd](https://github.com/googleapis/python-bigquery-dataframes/commit/93b02cd8bc620823563f8214b43bc5f2f35c155b)) -* Rename cosine_similarity to paired_cosine_distances ([#393](https://github.com/googleapis/python-bigquery-dataframes/issues/393)) ([81ece46](https://github.com/googleapis/python-bigquery-dataframes/commit/81ece463b69765b0f93585d6b866fb642ddc65dc)) - - -### Performance Improvements - -* Inline read_pandas for small data ([#383](https://github.com/googleapis/python-bigquery-dataframes/issues/383)) ([59b446b](https://github.com/googleapis/python-bigquery-dataframes/commit/59b446bad8d2c5fca791c384616cfa7e54d54c09)) - - -### Dependencies - -* Add minimum version constraint for sqlglot to 19.9.0 ([#389](https://github.com/googleapis/python-bigquery-dataframes/issues/389)) ([8b62d77](https://github.com/googleapis/python-bigquery-dataframes/commit/8b62d77d8274cff2842c98b032bf98d69c483482)) - - -### Documentation - -* Add a code sample for creating a kmeans model ([#267](https://github.com/googleapis/python-bigquery-dataframes/issues/267)) ([4291d65](https://github.com/googleapis/python-bigquery-dataframes/commit/4291d656f30dc50b8ffcdd10ccbfa7f327711100)) -* Fix `bigframes.pandas.concat` documentation ([#382](https://github.com/googleapis/python-bigquery-dataframes/issues/382)) ([234b61c](https://github.com/googleapis/python-bigquery-dataframes/commit/234b61cdfe75b402adf1b56f53b5f06934777f95)) - - -### Miscellaneous Chores - -* Release 0.22.0 ([#396](https://github.com/googleapis/python-bigquery-dataframes/issues/396)) ([8f73d9e](https://github.com/googleapis/python-bigquery-dataframes/commit/8f73d9e37827ecdc90683313000364922ae61dab)) - - -### Code Refactoring - -* Move model optional args to kwargs ([#381](https://github.com/googleapis/python-bigquery-dataframes/issues/381)) ([4037992](https://github.com/googleapis/python-bigquery-dataframes/commit/4037992b61ff352320d5dfb87dcf5f274791ace1)) - -## [0.21.0](https://github.com/googleapis/python-bigquery-dataframes/compare/v0.20.1...v0.21.0) (2024-02-13) - - -### Features - -* Add `Series.cov` method ([#368](https://github.com/googleapis/python-bigquery-dataframes/issues/368)) ([443db22](https://github.com/googleapis/python-bigquery-dataframes/commit/443db228375da9b232376140c9d5b0db14895eae)) -* Add ml.llm.GeminiTextGenerator model ([#370](https://github.com/googleapis/python-bigquery-dataframes/issues/370)) ([de1e0a4](https://github.com/googleapis/python-bigquery-dataframes/commit/de1e0a451785e679f37b083be6d58c267319f56a)) -* Add ml.metrics.pairwise.cosine_similarity function ([#374](https://github.com/googleapis/python-bigquery-dataframes/issues/374)) ([126f566](https://github.com/googleapis/python-bigquery-dataframes/commit/126f5660bd61bd8998e5f17ca0cbd39959590367)) -* Add XGBoostModel ([#363](https://github.com/googleapis/python-bigquery-dataframes/issues/363)) ([d5518b2](https://github.com/googleapis/python-bigquery-dataframes/commit/d5518b28509be0ce070b22d9134a6a662412010a)) -* Limited support of lambdas in `Series.apply` ([#345](https://github.com/googleapis/python-bigquery-dataframes/issues/345)) ([208e081](https://github.com/googleapis/python-bigquery-dataframes/commit/208e081fa99e17b8085e83c111c07eb6fc5c4730)) -* Support bigframes.pandas.to_datetime for scalars, iterables and series. ([#372](https://github.com/googleapis/python-bigquery-dataframes/issues/372)) ([ffb0d15](https://github.com/googleapis/python-bigquery-dataframes/commit/ffb0d15602fe4d86e7a1aad72bba0a7049193a14)) -* Support read_gbq wildcard table path ([#377](https://github.com/googleapis/python-bigquery-dataframes/issues/377)) ([90caf86](https://github.com/googleapis/python-bigquery-dataframes/commit/90caf865efc940f94e16643bda7ba261c2f2e473)) - - -### Bug Fixes - -* Error message fix. ([#375](https://github.com/googleapis/python-bigquery-dataframes/issues/375)) ([930cf6b](https://github.com/googleapis/python-bigquery-dataframes/commit/930cf6b9ae8a48f422586dbd21b52e15c9ef9492)) - - -### Documentation - -* Clarify ADC pre-auth in a non-interactive environment ([#348](https://github.com/googleapis/python-bigquery-dataframes/issues/348)) ([99a9e6e](https://github.com/googleapis/python-bigquery-dataframes/commit/99a9e6e15c6eef4297035ce89bb619f8e4ca54ff)) - -## [0.20.1](https://github.com/googleapis/python-bigquery-dataframes/compare/v0.20.0...v0.20.1) (2024-02-06) - - -### Performance Improvements - -* Make repr cache the block where appropriate ([#350](https://github.com/googleapis/python-bigquery-dataframes/issues/350)) ([068879f](https://github.com/googleapis/python-bigquery-dataframes/commit/068879f97fb1626aca081106150803f832a0cf81)) - - -### Documentation - -* Add a sample to demonstrate the evaluation results ([#364](https://github.com/googleapis/python-bigquery-dataframes/issues/364)) ([cff0919](https://github.com/googleapis/python-bigquery-dataframes/commit/cff09194b2c3a96a1f50e86a38ee59783c2a343b)) -* Fix the `DataFrame.apply` code sample ([#366](https://github.com/googleapis/python-bigquery-dataframes/issues/366)) ([1866a26](https://github.com/googleapis/python-bigquery-dataframes/commit/1866a266f0fa40882b589579654c1ad428b036d8)) - -## [0.20.0](https://github.com/googleapis/python-bigquery-dataframes/compare/v0.19.2...v0.20.0) (2024-01-30) - - -### Features - -* Add `DataFrame.peek()` as an efficient alternative to `head()` results preview ([#318](https://github.com/googleapis/python-bigquery-dataframes/issues/318)) ([9c34d83](https://github.com/googleapis/python-bigquery-dataframes/commit/9c34d834e83ca5514bee723ebb9a7ad1ad50e88d)) -* Add ARIMA_EVAULATE options in forecasting models ([#336](https://github.com/googleapis/python-bigquery-dataframes/issues/336)) ([73e997b](https://github.com/googleapis/python-bigquery-dataframes/commit/73e997b3e80f844a8120b52ed2ece8b046cf4ca9)) -* Add Index constructor, repr, copy, get_level_values, to_series ([#334](https://github.com/googleapis/python-bigquery-dataframes/issues/334)) ([e5d054e](https://github.com/googleapis/python-bigquery-dataframes/commit/e5d054e93a05f5c504e8db57b954c07d33e5f5b9)) -* Improve error message for drive based BQ table reads ([#344](https://github.com/googleapis/python-bigquery-dataframes/issues/344)) ([0794788](https://github.com/googleapis/python-bigquery-dataframes/commit/0794788a2d232d795d803cd0c5b3f7d51c562cf1)) -* Update cut to work without labels = False and show intervals as dict ([#335](https://github.com/googleapis/python-bigquery-dataframes/issues/335)) ([4ff53db](https://github.com/googleapis/python-bigquery-dataframes/commit/4ff53db48133b817bec5f123b634690244a610d3)) - - -### Bug Fixes - -* Chance default connection name in getting_started.ipnyb ([#347](https://github.com/googleapis/python-bigquery-dataframes/issues/347)) ([677f014](https://github.com/googleapis/python-bigquery-dataframes/commit/677f0146acf19def88fddbeb0527a078458948ae)) -* Series iteration correctly returns values instead of index ([#339](https://github.com/googleapis/python-bigquery-dataframes/issues/339)) ([2c6af9b](https://github.com/googleapis/python-bigquery-dataframes/commit/2c6af9ba8b362dae39a6e082cdc816c955c73517)) - - -### Documentation - -* Add code samples for `Series.{between, cumprod}` ([#353](https://github.com/googleapis/python-bigquery-dataframes/issues/353)) ([09a52fd](https://github.com/googleapis/python-bigquery-dataframes/commit/09a52fda19cde8efa6b20731d5b8e21f50b18a9a)) - -## [0.19.2](https://github.com/googleapis/python-bigquery-dataframes/compare/v0.19.1...v0.19.2) (2024-01-22) - - -### Bug Fixes - -* Read_gbq large response issue ([#332](https://github.com/googleapis/python-bigquery-dataframes/issues/332)) ([b8178b9](https://github.com/googleapis/python-bigquery-dataframes/commit/b8178b9a47958d9176d99dfd8833556a64d9724d)) -* Use object dtype for ARRAY columns in `to_pandas()` with pandas 1.x ([#329](https://github.com/googleapis/python-bigquery-dataframes/issues/329)) ([374ddb5](https://github.com/googleapis/python-bigquery-dataframes/commit/374ddb534777895d93a1e2ae2f9c6dbe5f10bf8c)) - - -### Documentation - -* Add `DataFrame.applymap` documentation ([#326](https://github.com/googleapis/python-bigquery-dataframes/issues/326)) ([bd531a1](https://github.com/googleapis/python-bigquery-dataframes/commit/bd531a1557c08bcee6a0d275747f0939cdd33e81)) -* Add code samples for series methods ([#323](https://github.com/googleapis/python-bigquery-dataframes/issues/323)) ([32cc6fa](https://github.com/googleapis/python-bigquery-dataframes/commit/32cc6fa73dea80e31985d380d550d8042e5f5566)) -* Add remote model requirements ([#333](https://github.com/googleapis/python-bigquery-dataframes/issues/333)) ([c91f70c](https://github.com/googleapis/python-bigquery-dataframes/commit/c91f70ca7b9793cc62578d7845c3aa31cf8a4507)) - -## [0.19.1](https://github.com/googleapis/python-bigquery-dataframes/compare/v0.19.0...v0.19.1) (2024-01-17) - - -### Bug Fixes - -* Handle multi-level columns for df aggregates properly ([#305](https://github.com/googleapis/python-bigquery-dataframes/issues/305)) ([5bb45ba](https://github.com/googleapis/python-bigquery-dataframes/commit/5bb45ba5560f178438d490a62520ccd36fd2f284)) -* Update max_output_token limitation. ([#308](https://github.com/googleapis/python-bigquery-dataframes/issues/308)) ([5cccd36](https://github.com/googleapis/python-bigquery-dataframes/commit/5cccd36fd2081becd741541c4ac8d5cf53c076f2)) - - -### Documentation - -* Add code samples for Series.corr ([#316](https://github.com/googleapis/python-bigquery-dataframes/issues/316)) ([9150c16](https://github.com/googleapis/python-bigquery-dataframes/commit/9150c16e951fb757547721e0003910c7c49e3d27)) - -## [0.19.0](https://github.com/googleapis/python-bigquery-dataframes/compare/v0.18.0...v0.19.0) (2024-01-09) - - -### Features - -* Add 'columns' as an alias for 'col_order' ([#298](https://github.com/googleapis/python-bigquery-dataframes/issues/298)) ([a01b271](https://github.com/googleapis/python-bigquery-dataframes/commit/a01b271e76d05459f531cd83c6e93a2d13bfa061)) -* Add Series dt.tz and dt.unit properties ([#303](https://github.com/googleapis/python-bigquery-dataframes/issues/303)) ([2e1a403](https://github.com/googleapis/python-bigquery-dataframes/commit/2e1a4036e58fb6b35aa68ac6d121cb0d04f4f369)) -* Add to_gbq() method for LLM models ([#299](https://github.com/googleapis/python-bigquery-dataframes/issues/299)) ([dafbc1b](https://github.com/googleapis/python-bigquery-dataframes/commit/dafbc1bdb225c7132cdf7191792fde785947c7a1)) -* Allow manually set clustering_columns in dataframe.to_gbq ([#302](https://github.com/googleapis/python-bigquery-dataframes/issues/302)) ([9c21323](https://github.com/googleapis/python-bigquery-dataframes/commit/9c213239a73b5cd0ca7b647a86238263d3947431)) -* Support assigning to columns like a property ([#304](https://github.com/googleapis/python-bigquery-dataframes/issues/304)) ([f645c56](https://github.com/googleapis/python-bigquery-dataframes/commit/f645c56e5436adb100018afbf9ef18003a1a6ed9)) -* Support upcasting numeric columns in concat ([#294](https://github.com/googleapis/python-bigquery-dataframes/issues/294)) ([e3a056a](https://github.com/googleapis/python-bigquery-dataframes/commit/e3a056a301e99c4c3d2a2ecdcbcaf8804be8089f)) - - -### Bug Fixes - -* DF.drop tuple input as multi-index ([#301](https://github.com/googleapis/python-bigquery-dataframes/issues/301)) ([21391a9](https://github.com/googleapis/python-bigquery-dataframes/commit/21391a9d07bb0dc6b6f900f1b069350d6232bd92)) -* Fix bug converting non-string labels to sql ids ([#296](https://github.com/googleapis/python-bigquery-dataframes/issues/296)) ([a61c5fe](https://github.com/googleapis/python-bigquery-dataframes/commit/a61c5fef1e3b88f38269ee5bfd50886b8d2908ae)) - - -### Documentation - -* Add code samples for `Series.ffill` and `DataFrame.ffill` ([#307](https://github.com/googleapis/python-bigquery-dataframes/issues/307)) ([1c63b45](https://github.com/googleapis/python-bigquery-dataframes/commit/1c63b451bb057e5b6470d63d4b44c090d7172aa5)) - -## [0.18.0](https://github.com/googleapis/python-bigquery-dataframes/compare/v0.17.0...v0.18.0) (2024-01-02) - - -### Features - -* Add dataframe.to_html ([#259](https://github.com/googleapis/python-bigquery-dataframes/issues/259)) ([2cd6489](https://github.com/googleapis/python-bigquery-dataframes/commit/2cd64891170dcd4f2a709024a2993e36db210976)) -* Add IntervalIndex support to bigframes.pandas.cut ([#254](https://github.com/googleapis/python-bigquery-dataframes/issues/254)) ([6c1969a](https://github.com/googleapis/python-bigquery-dataframes/commit/6c1969a35fe720cf3a804006bcc9046ba554fcc3)) -* Add replace method to DataFrame ([#261](https://github.com/googleapis/python-bigquery-dataframes/issues/261)) ([5092215](https://github.com/googleapis/python-bigquery-dataframes/commit/5092215767d77c90b132e9cd6b3e3749827ebe09)) -* Specific pyarrow mappings for decimal, bytes types ([#283](https://github.com/googleapis/python-bigquery-dataframes/issues/283)) ([a1c0631](https://github.com/googleapis/python-bigquery-dataframes/commit/a1c06319ab0e3697c3175112490488002bb344c0)) - - -### Bug Fixes - -* Dataframes to_gbq now creates dataset if it doesn't exist ([#222](https://github.com/googleapis/python-bigquery-dataframes/issues/222)) ([bac62f7](https://github.com/googleapis/python-bigquery-dataframes/commit/bac62f76af1af6ca8834c3690c7c79aeb12dd331)) -* Exclude pandas 2.2.0rc0 to unblock prerelease tests ([#292](https://github.com/googleapis/python-bigquery-dataframes/issues/292)) ([ac1a745](https://github.com/googleapis/python-bigquery-dataframes/commit/ac1a745ddce9865f4585777b43c2234b9bf2841d)) -* Fix DataFrameGroupby.agg() issue with as_index=False ([#273](https://github.com/googleapis/python-bigquery-dataframes/issues/273)) ([ab49350](https://github.com/googleapis/python-bigquery-dataframes/commit/ab493506e71ed8970a11fe2f88b2145150e09291)) -* Make `Series.str.replace` work for simple strings ([#285](https://github.com/googleapis/python-bigquery-dataframes/issues/285)) ([ad67465](https://github.com/googleapis/python-bigquery-dataframes/commit/ad6746569b3af11be9d40805a1449ee1e89288dc)) -* Update dataframe.to_gbq to dedup column names. ([#286](https://github.com/googleapis/python-bigquery-dataframes/issues/286)) ([746115d](https://github.com/googleapis/python-bigquery-dataframes/commit/746115d5564c95bc3c4a5309c99e7a29e535e6fe)) -* Use setuptools.find_namespace_packages ([#246](https://github.com/googleapis/python-bigquery-dataframes/issues/246)) ([9ec352a](https://github.com/googleapis/python-bigquery-dataframes/commit/9ec352a338f11d82aee9cd665ffb0e6e97cb391b)) - - -### Dependencies - -* Migrate to `ibis-framework >= "7.1.0"` ([#53](https://github.com/googleapis/python-bigquery-dataframes/issues/53)) ([9798a2b](https://github.com/googleapis/python-bigquery-dataframes/commit/9798a2b14dffb20432f732343cac92341e42fe09)) - - -### Documentation - -* Add code snippets for explore query result page ([#278](https://github.com/googleapis/python-bigquery-dataframes/issues/278)) ([7cbbb7d](https://github.com/googleapis/python-bigquery-dataframes/commit/7cbbb7d4608d8b7d1a360b2fe2d39d89a52f9546)) -* Code samples for `astype` common to DataFrame and Series ([#280](https://github.com/googleapis/python-bigquery-dataframes/issues/280)) ([95b673a](https://github.com/googleapis/python-bigquery-dataframes/commit/95b673aeb1545744e4b1a353cf1f4d0202d8a1b2)) -* Code samples for `DataFrame.copy` and `Series.copy` ([#290](https://github.com/googleapis/python-bigquery-dataframes/issues/290)) ([7cbc2b0](https://github.com/googleapis/python-bigquery-dataframes/commit/7cbc2b0ba572d11778ba7caf7c95b7fb8f3a31a7)) -* Code samples for `drop` and `fillna` ([#284](https://github.com/googleapis/python-bigquery-dataframes/issues/284)) ([9c5012e](https://github.com/googleapis/python-bigquery-dataframes/commit/9c5012ec68275db83d1f6f7e743f5edaaaacd8cb)) -* Code samples for `isna`, `isnull`, `dropna`, `isin` ([#289](https://github.com/googleapis/python-bigquery-dataframes/issues/289)) ([ad51035](https://github.com/googleapis/python-bigquery-dataframes/commit/ad51035bcf80d6a49f134df26624b578010b5b12)) -* Code samples for `rename` , `size` ([#293](https://github.com/googleapis/python-bigquery-dataframes/issues/293)) ([eb69f60](https://github.com/googleapis/python-bigquery-dataframes/commit/eb69f60db52544882fb06c2d5fa0e41226dfe93f)) -* Code samples for `reset_index` and `sort_values` ([#282](https://github.com/googleapis/python-bigquery-dataframes/issues/282)) ([acc0eb7](https://github.com/googleapis/python-bigquery-dataframes/commit/acc0eb7010951c8cfb91aecc45268b041217dd09)) -* Code samples for `sample`, `get`, `Series.round` ([#295](https://github.com/googleapis/python-bigquery-dataframes/issues/295)) ([c2b1892](https://github.com/googleapis/python-bigquery-dataframes/commit/c2b1892825545a34ce4ed5b0ef99e99348466108)) -* Code samples for `Series.{add, replace, unique, T, transpose}` ([#287](https://github.com/googleapis/python-bigquery-dataframes/issues/287)) ([0e1bbfc](https://github.com/googleapis/python-bigquery-dataframes/commit/0e1bbfc1055aff9757b5138907c11caab2f3965a)) -* Code samples for `Series.{map, to_list, count}` ([#290](https://github.com/googleapis/python-bigquery-dataframes/issues/290)) ([7cbc2b0](https://github.com/googleapis/python-bigquery-dataframes/commit/7cbc2b0ba572d11778ba7caf7c95b7fb8f3a31a7)) -* Code samples for `Series.{name, std, agg}` ([#293](https://github.com/googleapis/python-bigquery-dataframes/issues/293)) ([eb69f60](https://github.com/googleapis/python-bigquery-dataframes/commit/eb69f60db52544882fb06c2d5fa0e41226dfe93f)) -* Code samples for `Series.groupby` and `Series.{sum,mean,min,max}` ([#280](https://github.com/googleapis/python-bigquery-dataframes/issues/280)) ([95b673a](https://github.com/googleapis/python-bigquery-dataframes/commit/95b673aeb1545744e4b1a353cf1f4d0202d8a1b2)) -* Code samples for DataFrame `set_index`, `items` ([#295](https://github.com/googleapis/python-bigquery-dataframes/issues/295)) ([c2b1892](https://github.com/googleapis/python-bigquery-dataframes/commit/c2b1892825545a34ce4ed5b0ef99e99348466108)) -* Fix the rendering for `get_dummies` ([#291](https://github.com/googleapis/python-bigquery-dataframes/issues/291)) ([252f3a2](https://github.com/googleapis/python-bigquery-dataframes/commit/252f3a2a0e1296c7d786acdc0bdebe9e4a9ae1be)) - -## [0.17.0](https://github.com/googleapis/python-bigquery-dataframes/compare/v0.16.0...v0.17.0) (2023-12-14) - - -### Features - -* Add `filters` argument to `read_gbq` for enhanced data querying ([#198](https://github.com/googleapis/python-bigquery-dataframes/issues/198)) ([034f71f](https://github.com/googleapis/python-bigquery-dataframes/commit/034f71f113235f2218223e43f129507c1ec3f6ff)) -* Add module/class level api tracking ([#272](https://github.com/googleapis/python-bigquery-dataframes/issues/272)) ([4f3db3d](https://github.com/googleapis/python-bigquery-dataframes/commit/4f3db3d50fb782dbe03051ed024d03e19944d775)) -* Deprecate `use_regional_endpoints` ([#199](https://github.com/googleapis/python-bigquery-dataframes/issues/199)) ([319a1f2](https://github.com/googleapis/python-bigquery-dataframes/commit/319a1f27be5bd96ebbe29f11a00a5a62d2b4237f)) - - -### Bug Fixes - -* Increase recursion limit, cache compilation tree hashes ([#184](https://github.com/googleapis/python-bigquery-dataframes/issues/184)) ([b54791c](https://github.com/googleapis/python-bigquery-dataframes/commit/b54791c820f56c578a0bd9883489de9b9c7eb3a2)) -* Replaced raise `NotImplementedError` with return `NotImplemented` ([#258](https://github.com/googleapis/python-bigquery-dataframes/issues/258)) ([a133822](https://github.com/googleapis/python-bigquery-dataframes/commit/a133822974229f70529a414a682b6d98770d1846)) - - -### Documentation - -* Add code samples for `values` and `value_counts` ([#249](https://github.com/googleapis/python-bigquery-dataframes/issues/249)) ([f247d95](https://github.com/googleapis/python-bigquery-dataframes/commit/f247d957a12a119ce8a263df215e8a9ef7310ef6)) -* Add sample for getting started with BQML ([#141](https://github.com/googleapis/python-bigquery-dataframes/issues/141)) ([fb14f54](https://github.com/googleapis/python-bigquery-dataframes/commit/fb14f54548e988c6c226753fcca162cf15b5c8d7)) - -## [0.16.0](https://github.com/googleapis/python-bigquery-dataframes/compare/v0.15.0...v0.16.0) (2023-12-12) - - -### Features - -* Add ARIMAPlus.predict parameters ([#264](https://github.com/googleapis/python-bigquery-dataframes/issues/264)) ([99598c7](https://github.com/googleapis/python-bigquery-dataframes/commit/99598c7d359f1d1e0671dcf27a5c77094f3c7f67)) -* Add DataFrame from_dict and from_records methods ([#244](https://github.com/googleapis/python-bigquery-dataframes/issues/244)) ([8d81e24](https://github.com/googleapis/python-bigquery-dataframes/commit/8d81e24677613dcf4d275c27a327384b8c17bc85)) -* Add DataFrame.select_dtypes method ([#242](https://github.com/googleapis/python-bigquery-dataframes/issues/242)) ([1737acc](https://github.com/googleapis/python-bigquery-dataframes/commit/1737acc51b4fdd9b385bbf91a758efd2e7ead11a)) -* Add nunique method to Series/DataFrameGroupby ([#256](https://github.com/googleapis/python-bigquery-dataframes/issues/256)) ([c8ec245](https://github.com/googleapis/python-bigquery-dataframes/commit/c8ec245070402aa0770bc9b2375693de674ca925)) -* Support dataframe.loc with conditional columns selection ([#233](https://github.com/googleapis/python-bigquery-dataframes/issues/233)) ([3febea9](https://github.com/googleapis/python-bigquery-dataframes/commit/3febea99358d10f823d43c3af83ea30458e579a2)) - - -### Bug Fixes - -* Enfore pandas version requirement <2.1.4 ([#265](https://github.com/googleapis/python-bigquery-dataframes/issues/265)) ([9dd63f6](https://github.com/googleapis/python-bigquery-dataframes/commit/9dd63f6dcb6234e1f3aebd63c59e1e5c717099dc)) -* Exclude pandas 2.1.4 from prerelease tests to unblock e2e tests ([b02fc2c](https://github.com/googleapis/python-bigquery-dataframes/commit/b02fc2c1843e18d3a8d6894c64763f53e6af1b73)) -* Fix value_counts column label for normalize=True ([#245](https://github.com/googleapis/python-bigquery-dataframes/issues/245)) ([d3fa6f2](https://github.com/googleapis/python-bigquery-dataframes/commit/d3fa6f26931d5d0f0ae3fa49baccfc148f870417)) -* Migrate e2e tests to bigframes-load-testing project ([8766ac6](https://github.com/googleapis/python-bigquery-dataframes/commit/8766ac63f501929577f71e6bd2b523e92c43ba66)) -* Ml.sql logic ([#262](https://github.com/googleapis/python-bigquery-dataframes/issues/262)) ([68c6fdf](https://github.com/googleapis/python-bigquery-dataframes/commit/68c6fdf78af8b87fa4ef4f832631f24d7433a4d8)) -* Update the llm_kmeans notebook ([#247](https://github.com/googleapis/python-bigquery-dataframes/issues/247)) ([66d1839](https://github.com/googleapis/python-bigquery-dataframes/commit/66d1839c3e9a3011c7feb13a59d966b64cf8313f)) - - -### Documentation - -* Add code samples for `shape` and `head` ([#257](https://github.com/googleapis/python-bigquery-dataframes/issues/257)) ([5bdcc65](https://github.com/googleapis/python-bigquery-dataframes/commit/5bdcc6594ef2e99e96636341d286ea70420858fe)) -* Add example for dataframe.melt, dataframe.pivot, dataframe.stac… ([#252](https://github.com/googleapis/python-bigquery-dataframes/issues/252)) ([8c63697](https://github.com/googleapis/python-bigquery-dataframes/commit/8c636978f4a21eda2856862100b7a8272797fe42)) -* Add example to dataframe.nlargest, dataframe.nsmallest, datafra… ([#234](https://github.com/googleapis/python-bigquery-dataframes/issues/234)) ([e735412](https://github.com/googleapis/python-bigquery-dataframes/commit/e735412fdc52d034df92dd5462d6956bdc0167be)) -* Add examples for dataframe.cummin, dataframe.cummax, dataframe.cumsum, dataframe.cumprod ([#243](https://github.com/googleapis/python-bigquery-dataframes/issues/243)) ([0523a31](https://github.com/googleapis/python-bigquery-dataframes/commit/0523a31fa0b589f88afe0ad5b447634409ddeb86)) -* Add examples for dataframe.nunique, dataframe.diff, dataframe.a… ([#251](https://github.com/googleapis/python-bigquery-dataframes/issues/251)) ([77074ec](https://github.com/googleapis/python-bigquery-dataframes/commit/77074ecbe7f52d1d7d1d1dc537fbe4062b407672)) -* Correct the docs for `option_context` ([#263](https://github.com/googleapis/python-bigquery-dataframes/issues/263)) ([d21c6dd](https://github.com/googleapis/python-bigquery-dataframes/commit/d21c6dd26eadd64c526b0fd35b977a74b8334562)) -* Correct the params rendering for `ml.remote` and `ml.ensemble` modules ([#248](https://github.com/googleapis/python-bigquery-dataframes/issues/248)) ([c2829e3](https://github.com/googleapis/python-bigquery-dataframes/commit/c2829e3d976a43c53251c9288266e3a8ec5304c5)) -* Fix return annotation in API docstrings ([#253](https://github.com/googleapis/python-bigquery-dataframes/issues/253)) ([89a1c67](https://github.com/googleapis/python-bigquery-dataframes/commit/89a1c67fa5cbb76c1cc6ae24d5f919e22514705c)) - -## [0.15.0](https://github.com/googleapis/python-bigquery-dataframes/compare/v0.14.1...v0.15.0) (2023-11-29) - - -### ⚠ BREAKING CHANGES - -* model.predict returns all the columns ([#204](https://github.com/googleapis/python-bigquery-dataframes/issues/204)) - -### Features - -* Add info and memory_usage methods to dataframe ([#219](https://github.com/googleapis/python-bigquery-dataframes/issues/219)) ([9d6613d](https://github.com/googleapis/python-bigquery-dataframes/commit/9d6613d318b558722b7bab12773efdea4bbe9931)) -* Add remote vertex model support ([#237](https://github.com/googleapis/python-bigquery-dataframes/issues/237)) ([0bfc4fb](https://github.com/googleapis/python-bigquery-dataframes/commit/0bfc4fb117686c734d4a2503d5a6de0e64e9f9b9)) -* Add the recent api method for ML component ([#225](https://github.com/googleapis/python-bigquery-dataframes/issues/225)) ([ed8876d](https://github.com/googleapis/python-bigquery-dataframes/commit/ed8876d3439a3b45b65e8789737c3c2e3a7f1adb)) -* Model.predict returns all the columns ([#204](https://github.com/googleapis/python-bigquery-dataframes/issues/204)) ([416171a](https://github.com/googleapis/python-bigquery-dataframes/commit/416171a70d91d4a6b71622ba72685147ab7d6186)) -* Send warnings on LLM prediction partial failures ([#216](https://github.com/googleapis/python-bigquery-dataframes/issues/216)) ([81125f9](https://github.com/googleapis/python-bigquery-dataframes/commit/81125f9505ad98e89939769a8e1fcf30518705f0)) - - -### Bug Fixes - -* Add df snapshots lookup for `read_gbq` ([#229](https://github.com/googleapis/python-bigquery-dataframes/issues/229)) ([d0d9b84](https://github.com/googleapis/python-bigquery-dataframes/commit/d0d9b84b101eb03c499d85e74dcfc900dedd4137)) -* Avoid unnecessary row_number() on sort key for io ([#211](https://github.com/googleapis/python-bigquery-dataframes/issues/211)) ([a18d40e](https://github.com/googleapis/python-bigquery-dataframes/commit/a18d40e808ee0822d21715cc3e8f794c418aeebc)) -* Dedup special character ([#209](https://github.com/googleapis/python-bigquery-dataframes/issues/209)) ([dd78acb](https://github.com/googleapis/python-bigquery-dataframes/commit/dd78acb174545ba292776a642afcec46f8ee4a2a)) -* Invalid JSON type of the notebook ([#215](https://github.com/googleapis/python-bigquery-dataframes/issues/215)) ([a729831](https://github.com/googleapis/python-bigquery-dataframes/commit/a7298317ea2604faa6ae31817f1f729d7e0b9818)) -* Make to_pandas override enable_downsampling when sampling_method is manually set. ([#200](https://github.com/googleapis/python-bigquery-dataframes/issues/200)) ([ae03756](https://github.com/googleapis/python-bigquery-dataframes/commit/ae03756f5ee45e0e74e0c0bdd4777e018eba2273)) -* Polish the llm+kmeans notebook ([#208](https://github.com/googleapis/python-bigquery-dataframes/issues/208)) ([e8532b1](https://github.com/googleapis/python-bigquery-dataframes/commit/e8532b1d999d26ea1ebdd30efb8f2c0a93a6a28d)) -* Update the llm+kmeans notebook with recent change ([#236](https://github.com/googleapis/python-bigquery-dataframes/issues/236)) ([f8917ab](https://github.com/googleapis/python-bigquery-dataframes/commit/f8917abc094e222e0435891d4d184b77bfe67722)) -* Use anonymous dataset to create `remote_function` ([#205](https://github.com/googleapis/python-bigquery-dataframes/issues/205)) ([69b016e](https://github.com/googleapis/python-bigquery-dataframes/commit/69b016eae7ea97d84ceeb22ba09f5472841db072)) - - -### Documentation - -* Add code samples for `index` and `column` properties ([#212](https://github.com/googleapis/python-bigquery-dataframes/issues/212)) ([c88d38e](https://github.com/googleapis/python-bigquery-dataframes/commit/c88d38e69682f4c620174086b8f16f4780c04811)) -* Add code samples for df reshaping, function, merge, and join methods ([#203](https://github.com/googleapis/python-bigquery-dataframes/issues/203)) ([010486c](https://github.com/googleapis/python-bigquery-dataframes/commit/010486c3494e05d714da6cc7d51514518d9ae1ea)) -* Add examples for dataframe.kurt, dataframe.std, dataframe.count ([#232](https://github.com/googleapis/python-bigquery-dataframes/issues/232)) ([f9c6e72](https://github.com/googleapis/python-bigquery-dataframes/commit/f9c6e727e2b901310bb5301da449d616ea85e135)) -* Add examples for dataframe.mean, dataframe.median, dataframe.va… ([#228](https://github.com/googleapis/python-bigquery-dataframes/issues/228)) ([edd0522](https://github.com/googleapis/python-bigquery-dataframes/commit/edd0522747eadb74780124fb18ed7face251441d)) -* Add examples for dataframe.min, dataframe.max and dataframe.sum ([#227](https://github.com/googleapis/python-bigquery-dataframes/issues/227)) ([3a375e8](https://github.com/googleapis/python-bigquery-dataframes/commit/3a375e87b64b8fb51370bfec8f2cfdbcd8fe960a)) -* Code samples for `Series.dot` and `DataFrame.dot` ([#226](https://github.com/googleapis/python-bigquery-dataframes/issues/226)) ([b62a07a](https://github.com/googleapis/python-bigquery-dataframes/commit/b62a07a95cd60f995a48825c9874822d0eb02483)) -* Code samples for `Series.where` and `Series.mask` ([#217](https://github.com/googleapis/python-bigquery-dataframes/issues/217)) ([52dfad2](https://github.com/googleapis/python-bigquery-dataframes/commit/52dfad281def82548751a276ce42b087dbb09f9a)) -* Code samples for dataframe.any, dataframe.all and dataframe.prod ([#223](https://github.com/googleapis/python-bigquery-dataframes/issues/223)) ([d7957fa](https://github.com/googleapis/python-bigquery-dataframes/commit/d7957fad071d223ef8f6fb8f3de395c865ff60aa)) -* Make the code samples reflect default bq connection usage ([#206](https://github.com/googleapis/python-bigquery-dataframes/issues/206)) ([71844b0](https://github.com/googleapis/python-bigquery-dataframes/commit/71844b03cdbfe684320c186a0488c8c7fb4fcd6e)) - - -### Miscellaneous Chores - -* Release 0.15.0 ([#241](https://github.com/googleapis/python-bigquery-dataframes/issues/241)) ([6c899be](https://github.com/googleapis/python-bigquery-dataframes/commit/6c899be2989e24f697d72fe1bb92ebbf7dec84cb)) - -## [0.14.1](https://github.com/googleapis/python-bigquery-dataframes/compare/v0.14.0...v0.14.1) (2023-11-16) - - -### Bug Fixes - -* Correctly handle null values when initializing fingerprint ordering ([#210](https://github.com/googleapis/python-bigquery-dataframes/issues/210)) ([8324f13](https://github.com/googleapis/python-bigquery-dataframes/commit/8324f133547ec35da5eefc0a8b02fe0f3887d81d)) - - -### Documentation - -* Add an example notebook about line graphs ([#197](https://github.com/googleapis/python-bigquery-dataframes/issues/197)) ([f957b27](https://github.com/googleapis/python-bigquery-dataframes/commit/f957b278b39e0a472a3153e9e1906c2d5f2ac2e5)) - -## [0.14.0](https://github.com/googleapis/python-bigquery-dataframes/compare/v0.13.0...v0.14.0) (2023-11-14) - - -### Features - -* Add 'cross' join support ([#176](https://github.com/googleapis/python-bigquery-dataframes/issues/176)) ([765446a](https://github.com/googleapis/python-bigquery-dataframes/commit/765446a929abe1ac076c3037afa7892f64105356)) -* Add 'index', 'pad', 'nearest' interpolate methods ([#162](https://github.com/googleapis/python-bigquery-dataframes/issues/162)) ([6a28403](https://github.com/googleapis/python-bigquery-dataframes/commit/6a2840349a23035bdfdabacd1e231b41bbb5ed7a)) -* Add series.sample (identical to existing dataframe.sample) ([#187](https://github.com/googleapis/python-bigquery-dataframes/issues/187)) ([37914a4](https://github.com/googleapis/python-bigquery-dataframes/commit/37914a4077c681881491f5c36d1a9c9f4255e18f)) -* Add unordered sql compilation ([#156](https://github.com/googleapis/python-bigquery-dataframes/issues/156)) ([58f420c](https://github.com/googleapis/python-bigquery-dataframes/commit/58f420c91d94ca085e9810f36513ffe772bfddcf)) -* Log most recent API calls as `recent-bigframes-api-xx` labels on BigQuery jobs ([#145](https://github.com/googleapis/python-bigquery-dataframes/issues/145)) ([4ea33b7](https://github.com/googleapis/python-bigquery-dataframes/commit/4ea33b7433532ae3a386a6ffa9eb57360ea39526)) -* Read_gbq creates order deterministically without table copy ([#191](https://github.com/googleapis/python-bigquery-dataframes/issues/191)) ([8ab81de](https://github.com/googleapis/python-bigquery-dataframes/commit/8ab81dee4d0eee499094f2dd576550f0c59d7551)) -* Support `date_series.astype("string[pyarrow]")` to cast DATE to STRING ([#186](https://github.com/googleapis/python-bigquery-dataframes/issues/186)) ([aee0e8e](https://github.com/googleapis/python-bigquery-dataframes/commit/aee0e8e2518c59bd1e0b07940c3309871fde8899)) -* Support `series.at[row_label] = scalar` ([#173](https://github.com/googleapis/python-bigquery-dataframes/issues/173)) ([0c8bd33](https://github.com/googleapis/python-bigquery-dataframes/commit/0c8bd33806bb99206b8b12dbdf7d7485c6ffb759)) -* Temporary resources no longer use BigQuery Sessions ([#194](https://github.com/googleapis/python-bigquery-dataframes/issues/194)) ([4a02cac](https://github.com/googleapis/python-bigquery-dataframes/commit/4a02cac88c7d7b46bed1fa813a862fc2ef9ef084)) - - -### Bug Fixes - -* All sort operation are now stable ([#195](https://github.com/googleapis/python-bigquery-dataframes/issues/195)) ([3a2761f](https://github.com/googleapis/python-bigquery-dataframes/commit/3a2761f3c38d0de8b8eda47fffa15b8412aa84b0)) -* Default to 7 days expiration for `read_csv`, `read_json`, `read_parquet` ([#193](https://github.com/googleapis/python-bigquery-dataframes/issues/193)) ([03606cd](https://github.com/googleapis/python-bigquery-dataframes/commit/03606cda30eb7645bfd4534460112dcca56b0ab0)) -* Deprecate the `remote_service_type` in llm model ([#180](https://github.com/googleapis/python-bigquery-dataframes/issues/180)) ([a8a409a](https://github.com/googleapis/python-bigquery-dataframes/commit/a8a409ab0bd1f99dfb442df0703bf8786e0fe58e)) -* For reset_index on unnamed multiindex, always use level_[n] label ([#182](https://github.com/googleapis/python-bigquery-dataframes/issues/182)) ([f95000d](https://github.com/googleapis/python-bigquery-dataframes/commit/f95000d3f88662be4d88c8b0152f1b838e99ec55)) -* Match pandas behavior when assigning listlike to empty dfs ([#172](https://github.com/googleapis/python-bigquery-dataframes/issues/172)) ([c1d1f42](https://github.com/googleapis/python-bigquery-dataframes/commit/c1d1f42a21cc089877f79ebb46a39ddef6958e04)) -* Use anonymous dataset instead of session dataset for temp tables ([#181](https://github.com/googleapis/python-bigquery-dataframes/issues/181)) ([800d44e](https://github.com/googleapis/python-bigquery-dataframes/commit/800d44eb5eb77da5d87b2e005f5a2ed53842e7b5)) -* Use random table for `read_pandas` ([#192](https://github.com/googleapis/python-bigquery-dataframes/issues/192)) ([741c75e](https://github.com/googleapis/python-bigquery-dataframes/commit/741c75e5797e26a1487ff3da76a07953d9537f3f)) -* Use random table when loading data for `read_csv`, `read_json`, `read_parquet` ([#175](https://github.com/googleapis/python-bigquery-dataframes/issues/175)) ([9d2e6dc](https://github.com/googleapis/python-bigquery-dataframes/commit/9d2e6dc1ae4e11e80da4aabe0daa3a6044137cc6)) - - -### Documentation - -* Add code samples for `read_gbq_function` using community UDFs ([#188](https://github.com/googleapis/python-bigquery-dataframes/issues/188)) ([7506eab](https://github.com/googleapis/python-bigquery-dataframes/commit/7506eabf2e58159507809e36abfe90c417dfe92f)) -* Add docstring code samples for `Series.apply` and `DataFrame.map` ([#185](https://github.com/googleapis/python-bigquery-dataframes/issues/185)) ([c816d84](https://github.com/googleapis/python-bigquery-dataframes/commit/c816d843e6f3c5a944cd4395ed0e1e91cec49812)) -* Add llm kmeans notebook as an included example ([#177](https://github.com/googleapis/python-bigquery-dataframes/issues/177)) ([d49ae42](https://github.com/googleapis/python-bigquery-dataframes/commit/d49ae42a379fafd601cc94227e7f8f14b3d5f8c3)) -* Use `head()` to get top `n` results, not to preview results ([#190](https://github.com/googleapis/python-bigquery-dataframes/issues/190)) ([87f84c9](https://github.com/googleapis/python-bigquery-dataframes/commit/87f84c9e58e7d0ea521ac386c9f02791cdddd19f)) - ## [0.13.0](https://github.com/googleapis/python-bigquery-dataframes/compare/v0.12.0...v0.13.0) (2023-11-07) diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 00000000000..039f4368120 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,95 @@ + +# Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to making participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, gender identity and expression, level of +experience, education, socio-economic status, nationality, personal appearance, +race, religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment +include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or + advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic + address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, or to ban temporarily or permanently any +contributor for other behaviors that they deem inappropriate, threatening, +offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. Examples of +representing a project or community include using an official project e-mail +address, posting via an official social media account, or acting as an appointed +representative at an online or offline event. Representation of a project may be +further defined and clarified by project maintainers. + +This Code of Conduct also applies outside the project spaces when the Project +Steward has a reasonable belief that an individual's behavior may have a +negative impact on the project or its community. + +## Conflict Resolution + +We do not believe that all conflict is bad; healthy debate and disagreement +often yield positive results. However, it is never okay to be disrespectful or +to engage in behavior that violates the project’s code of conduct. + +If you see someone violating the code of conduct, you are encouraged to address +the behavior directly with those involved. Many issues can be resolved quickly +and easily, and this gives people more control over the outcome of their +dispute. If you are unable to resolve the matter for any reason, or if the +behavior is threatening or harassing, report it. We are dedicated to providing +an environment where participants feel welcome and safe. + + +Reports should be directed to *googleapis-stewards@google.com*, the +Project Steward(s) for *Google Cloud Client Libraries*. It is the Project Steward’s duty to +receive and address reported violations of the code of conduct. They will then +work with a committee consisting of representatives from the Open Source +Programs Office and the Google Open Source Strategy team. If for any reason you +are uncomfortable reaching out to the Project Steward, please email +opensource@google.com. + +We will investigate every complaint, but you may not receive a direct response. +We will use our discretion in determining when and how to follow up on reported +incidents, which may range from not taking action to permanent expulsion from +the project and project-sponsored spaces. We will notify the accused of the +report and provide them an opportunity to discuss it before any action is taken. +The identity of the reporter will be omitted from the details of the report +supplied to the accused. In potentially harmful situations, such as ongoing +harassment or threats to anyone's safety, we may take action without notice. + +## Attribution + +This Code of Conduct is adapted from the Contributor Covenant, version 1.4, +available at +https://www.contributor-covenant.org/version/1/4/code-of-conduct.html \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000000..b16bd944285 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,33 @@ +# How to contribute + +We'd love to accept your patches and contributions to this project. + +## Before you begin + +### Sign our Contributor License Agreement + +Contributions to this project must be accompanied by a +[Contributor License Agreement](https://cla.developers.google.com/about) (CLA). +You (or your employer) retain the copyright to your contribution; this simply +gives us permission to use and redistribute your contributions as part of the +project. + +If you or your current employer have already signed the Google CLA (even if it +was for a different project), you probably don't need to do it again. + +Visit to see your current agreements or to +sign a new one. + +### Review our community guidelines + +This project follows +[Google's Open Source Community Guidelines](https://opensource.google/conduct/). + +## Contribution process + +### Code reviews + +All submissions, including submissions by project members, require review. We +use GitHub pull requests for this purpose. Consult +[GitHub Help](https://help.github.com/articles/about-pull-requests/) for more +information on using pull requests. diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index ba8400eb866..3933152cf78 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -22,7 +22,7 @@ In order to add a feature: documentation. - The feature must work fully on the following CPython versions: - 3.10, 3.11, 3.12, 3.13 and 3.14 on both UNIX and Windows. + 3.9, 3.10 and 3.11 on both UNIX and Windows. - The feature must not add unnecessary dependencies (where "unnecessary" is of course subjective, but new dependencies should @@ -42,14 +42,14 @@ You'll have to create a development environment using a Git checkout: - Clone your fork of ``python-bigquery-dataframes`` from your GitHub account to your local computer, substituting your account username and specifying the destination - as ``hack-on-google-cloud-python``. E.g.:: + as ``hack-on-python-bigquery-dataframes``. E.g.:: $ cd ${HOME} - $ git clone git@github.com:USERNAME/google-cloud-python.git hack-on-google-cloud-python - $ cd hack-on-google-cloud-python - # Configure remotes such that you can pull changes from the googleapis/google-cloud-python + $ git clone git@github.com:USERNAME/python-bigquery-dataframes.git hack-on-python-bigquery-dataframes + $ cd hack-on-python-bigquery-dataframes + # Configure remotes such that you can pull changes from the googleapis/python-bigquery-dataframes # repository into your local repository. - $ git remote add upstream git@github.com:googleapis/google-cloud-python.git + $ git remote add upstream git@github.com:googleapis/python-bigquery-dataframes.git # fetch and merge changes from upstream into main $ git fetch upstream $ git merge upstream/main @@ -60,7 +60,7 @@ repo, from which you can submit a pull request. To work on the codebase and run the tests, we recommend using ``nox``, but you can also use a ``virtualenv`` of your own creation. -.. _repo: https://github.com/googleapis/google-cloud-python/tree/main/packages/bigframes +.. _repo: https://github.com/googleapis/python-bigquery-dataframes Using ``nox`` ============= @@ -72,7 +72,7 @@ We use `nox `__ to instrument our tests. - To run a single unit test:: - $ nox -s unit-3.14 -- -k + $ nox -s unit-3.11 -- -k .. note:: @@ -96,9 +96,9 @@ On Debian/Ubuntu:: Coding Style ************ - We use the automatic code formatter ``black``. You can run it using - the nox session ``format``. This will eliminate many lint errors. Run via:: + the nox session ``blacken``. This will eliminate many lint errors. Run via:: - $ nox -s format + $ nox -s blacken - PEP8 compliance is required, with exceptions defined in the linter configuration. If you have ``nox`` installed, you can test that you have not introduced @@ -143,56 +143,19 @@ Running System Tests $ nox -s system # Run a single system test - $ nox -s system-3.14 -- -k + $ nox -s system-3.11 -- -k .. note:: - System tests are only configured to run under Python 3.10, 3.12 and 3.14. + System tests are only configured to run under Python 3.9 and 3.11. For expediency, we do not run them in older versions of Python 3. This alone will not run the tests. You'll need to change some local auth settings and change some configuration in your project to run all the tests. -- System tests will be run against an actual project. A project can be set in - the environment variable ``$GOOGLE_CLOUD_PROJECT``. If not, the project property - set in the `Google Cloud CLI `__ - will be effective, which can be peeked into via ``gcloud config get project``, - or set via ``gcloud config set project ``. The following roles - carry the permissions to run the system tests in the project: - - - `BigQuery User `__ - to be able to create test datasets and run BigQuery jobs in the project. - - - `BigQuery Connection Admin `__ - to be able to use BigQuery connections in the project. - - - `BigQuery Data Editor `__ - to be able to create BigQuery remote functions in the project. - - - `Browser `__ - to be able to get current IAM policy for the service accounts of the BigQuery connections in the project. - - - `Cloud Functions Developer `__ - to be able to create cloud functions to support BigQuery DataFrames remote functions. - - - `Service Account User `__ - to be able to use the project's service accounts. - - - `Vertex AI User `__ - to be able to use the BigQuery DataFrames' ML integration with Vertex AI. - -- You can run the script ``scripts/setup-project-for-testing.sh []`` - to set up a project for running system tests and optionally set up necessary - IAM roles for a principal (user/group/service-account). You need to have the following - IAM permission to be able to run the set up script successfully: - - - ``serviceusage.services.enable`` - - ``bigquery.connections.create`` - - ``resourcemanager.projects.setIamPolicy`` - -- You should use local credentials from gcloud when possible. See `Best practices for application authentication `__. Some tests require a service account. For those tests see `Authenticating as a service account `__. +- System tests will be run against an actual project. You should use local credentials from gcloud when possible. See `Best practices for application authentication `__. Some tests require a service account. For those tests see `Authenticating as a service account `__. ************* Test Coverage @@ -232,11 +195,11 @@ configure them just like the System Tests. # Run all tests in a folder $ cd samples/snippets - $ nox -s py-3.10 + $ nox -s py-3.8 # Run a single sample test $ cd samples/snippets - $ nox -s py-3.10 -- -k + $ nox -s py-3.8 -- -k ******************************************** Note About ``README`` as it pertains to PyPI @@ -246,7 +209,7 @@ The `description on PyPI`_ for the project comes directly from the ``README``. Due to the reStructuredText (``rst``) parser used by PyPI, relative links which will work on GitHub (e.g. ``CONTRIBUTING.rst`` instead of -``https://github.com/googleapis/google-cloud-python/blob/main/packages/bigframes/CONTRIBUTING.rst``) +``https://github.com/googleapis/python-bigquery-dataframes/blob/main/CONTRIBUTING.rst``) may cause problems creating links or rendering the description. .. _description on PyPI: https://pypi.org/project/bigframes @@ -258,25 +221,31 @@ Supported Python Versions We support: +- `Python 3.9`_ - `Python 3.10`_ - `Python 3.11`_ -- `Python 3.12`_ -- `Python 3.13`_ -- `Python 3.14`_ +.. _Python 3.9: https://docs.python.org/3.9/ .. _Python 3.10: https://docs.python.org/3.10/ .. _Python 3.11: https://docs.python.org/3.11/ -.. _Python 3.12: https://docs.python.org/3.12/ -.. _Python 3.13: https://docs.python.org/3.13/ -.. _Python 3.14: https://docs.python.org/3.14/ Supported versions can be found in our ``noxfile.py`` `config`_. -.. _config: https://github.com/googleapis/google-cloud-python/blob/main/packages/bigframes/noxfile.py +.. _config: https://github.com/googleapis/python-bigquery-dataframes/blob/main/noxfile.py + +We also explicitly decided to support Python 3 beginning with version 3.9. +Reasons for this include: +- Encouraging use of newest versions of Python 3 +- Taking the lead of `prominent`_ open-source `projects`_ +- `Unicode literal support`_ which allows for a cleaner codebase that + works in both Python 2 and Python 3 +.. _prominent: https://docs.djangoproject.com/en/1.9/faq/install/#what-python-version-can-i-use-with-django +.. _projects: http://flask.pocoo.org/docs/0.10/python3/ +.. _Unicode literal support: https://www.python.org/dev/peps/pep-0414/ ********** Versioning diff --git a/LICENSE b/LICENSE index 4f29daf576c..d6456956733 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,3 @@ -Files: All files not covered by another license. Notably: the bigframes module, -tests/*, bigframes_vendored.google_cloud_bigquery module, -bigframes_vendored.ibis module, and bigframes_vendored.xgboost module. Apache License Version 2.0, January 2004 @@ -203,144 +200,3 @@ bigframes_vendored.ibis module, and bigframes_vendored.xgboost module. 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. - ---- - -Files: For the bigframes_vendored.cpython module. - -PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2 - -1. This LICENSE AGREEMENT is between the Python Software Foundation ("PSF"), and the Individual or Organization ("Licensee") accessing and otherwise using this software ("Python") in source or binary form and its associated documentation. -2. Subject to the terms and conditions of this License Agreement, PSF hereby grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce, analyze, test, perform and/or display publicly, prepare derivative works, distribute, and otherwise use Python alone or in any derivative version, provided, however, that PSF's License Agreement and PSF's notice of copyright , i.e., "Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006 Python Software Foundation All Rights Reserved" are retained in Python alone or in any derivative version prepared by Licensee. -3. In the event Licensee prepares a derivative work that is based on or incorporates Python or any part thereof, and wants to make the derivative work available to others as provided herein, then Licensee hereby agrees to include in any such work a brief summary of the changes made to Python. -4. PSF is making Python available to Licensee on an "AS IS" basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT INFRINGE ANY THIRD PARTY RIGHTS. -5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON, OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. -6. This License Agreement will automatically terminate upon a material breach of its terms and conditions. -7. Nothing in this License Agreement shall be deemed to create any relationship of agency, partnership, or joint venture between PSF and Licensee. This License Agreement does not grant permission to use PSF trademarks or trade name in a trademark sense to endorse or promote products or services of Licensee, or any third party. -8. By copying, installing or otherwise using Python, Licensee agrees to be bound by the terms and conditions of this License Agreement. - ---- - -Files: for the bigframes_vendored.geopandas module. - -Copyright (c) 2013-2022, GeoPandas developers. -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - * Neither the name of GeoPandas nor the names of its contributors may - be used to endorse or promote products derived from this software without - specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR -ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON -ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - ---- - -Files: The bigframes_vendored.pandas module. - -BSD 3-Clause License - -Copyright (c) 2008-2011, AQR Capital Management, LLC, Lambda Foundry, Inc. and PyData Development Team -All rights reserved. - -Copyright (c) 2011-2023, Open source contributors. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -* Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -* Neither the name of the copyright holder nor the names of its - contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - ---- - -Files: The bigframes_vendored.sklearn module. - -BSD 3-Clause License - -Copyright (c) 2007-2023 The scikit-learn developers. -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -* Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -* Neither the name of the copyright holder nor the names of its - contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - ---- - -Files: The bigframes_vendored.sqlglot module. - -MIT License - -Copyright (c) 2025 Toby Mao - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/MANIFEST.in b/MANIFEST.in index c8555a39bf8..b422266a96a 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # -# Copyright 2024 Google LLC +# Copyright 2023 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -16,8 +16,8 @@ # Generated by synthtool. DO NOT EDIT! include README.rst LICENSE -recursive-include third_party/bigframes_vendored * -recursive-include bigframes *.json *.proto *.js *.css py.typed +recursive-include third_party * +recursive-include bigframes *.json *.proto py.typed recursive-include tests * global-exclude *.py[co] global-exclude __pycache__ diff --git a/OWNERS b/OWNERS index 562ee0f19b9..f86ad551efa 100644 --- a/OWNERS +++ b/OWNERS @@ -1,8 +1,12 @@ +ashleyxu@google.com +bmil@google.com chelsealin@google.com garrettwu@google.com +henryjsolberg@google.com +hormati@google.com huanc@google.com jiaxun@google.com -mlaurencechen@google.com +kemppeterson@google.com shobs@google.com swast@google.com -tbergeron@google.com \ No newline at end of file +tbergeron@google.com diff --git a/README.rst b/README.rst index a3aef5380bb..5ddb4a7639a 100644 --- a/README.rst +++ b/README.rst @@ -1,78 +1,368 @@ -BigQuery DataFrames (BigFrames) -=============================== +BigQuery DataFrames +=================== +BigQuery DataFrames provides a Pythonic DataFrame and machine learning (ML) API +powered by the BigQuery engine. -|GA| |pypi| |versions| +* ``bigframes.pandas`` provides a pandas-compatible API for analytics. +* ``bigframes.ml`` provides a scikit-learn-like API for ML. -BigQuery DataFrames (also known as BigFrames) provides a Pythonic DataFrame -and machine learning (ML) API powered by the BigQuery engine. It provides modules -for many use cases, including: +BigQuery DataFrames is an open-source package. You can run +``pip install --upgrade bigframes`` to install the latest version. -* `bigframes.pandas `_ - is a pandas API for analytics. Many workloads can be - migrated from pandas to bigframes by just changing a few imports. -* `bigframes.ml `_ - is a scikit-learn-like API for ML. -* `bigframes.bigquery.ai `_ - are a collection of powerful AI methods, powered by Gemini. +Documentation +------------- -BigQuery DataFrames is an `open-source package `_. +* `BigQuery DataFrames source code (GitHub) `_ +* `BigQuery DataFrames sample notebooks `_ +* `BigQuery DataFrames API reference `_ +* `BigQuery documentation `_ -.. |GA| image:: https://img.shields.io/badge/support-GA-gold.svg - :target: https://github.com/googleapis/google-cloud-python/blob/main/README.rst#general-availability -.. |pypi| image:: https://img.shields.io/pypi/v/bigframes.svg - :target: https://pypi.org/project/bigframes/ -.. |versions| image:: https://img.shields.io/pypi/pyversions/bigframes.svg - :target: https://pypi.org/project/bigframes/ -Getting started with BigQuery DataFrames ----------------------------------------- +Quickstart +---------- -The easiest way to get started is to try the -`BigFrames quickstart `_ -in a `notebook in BigQuery Studio `_. +Prerequisites +^^^^^^^^^^^^^ -To use BigFrames in your local development environment, +* Install the ``bigframes`` package. +* Create a Google Cloud project and billing account. +* When running locally, authenticate with application default credentials. See + the `gcloud auth application-default login + `_ + reference. -1. Run ``pip install --upgrade bigframes`` to install the latest version. +Code sample +^^^^^^^^^^^ -2. Setup `Application default credentials `_ - for your local development environment enviroment. +Import ``bigframes.pandas`` for a pandas-like interface. The ``read_gbq`` +method accepts either a fully-qualified table ID or a SQL query. -3. Create a `GCP project with the BigQuery API enabled `_. +.. code-block:: python -4. Use the ``bigframes`` package to query data. + import bigframes.pandas as bpd -.. code-block:: python + bpd.options.bigquery.project = your_gcp_project_id + df1 = bpd.read_gbq("project.dataset.table") + df2 = bpd.read_gbq("SELECT a, b, c, FROM `project.dataset.table`") - import bigframes.pandas as bpd +* `More code samples `_ - bpd.options.bigquery.project = your_gcp_project_id # Optional in BQ Studio. - bpd.options.bigquery.ordering_mode = "partial" # Recommended for performance. - df = bpd.read_gbq("bigquery-public-data.usa_names.usa_1910_2013") - print( - df.groupby("name") - .agg({"number": "sum"}) - .sort_values("number", ascending=False) - .head(10) - .to_pandas() - ) -Documentation -------------- +Locations +--------- +BigQuery DataFrames uses a +`BigQuery session `_ +internally to manage metadata on the service side. This session is tied to a +`location `_ . +BigQuery DataFrames uses the US multi-region as the default location, but you +can use ``session_options.location`` to set a different location. Every query +in a session is executed in the location where the session was created. +BigQuery DataFrames +auto-populates ``bf.options.bigquery.location`` if the user starts with +``read_gbq/read_gbq_table/read_gbq_query()`` and specifies a table, either +directly or in a SQL statement. + +If you want to reset the location of the created DataFrame or Series objects, +you can close the session by executing ``bigframes.pandas.close_session()``. +After that, you can reuse ``bigframes.pandas.options.bigquery.location`` to +specify another location. + + +``read_gbq()`` requires you to specify a location if the dataset you are +querying is not in the US multi-region. If you try to read a table from another +location, you get a NotFound exception. + +Project +------- +If ``bf.options.bigquery.project`` is not set, the ``$GOOGLE_CLOUD_PROJECT`` +environment variable is used, which is set in the notebook runtime serving the +BigQuery Studio/Vertex Notebooks. + +ML Capabilities +--------------- + +The ML capabilities in BigQuery DataFrames let you preprocess data, and +then train models on that data. You can also chain these actions together to +create data pipelines. + +Preprocess data +^^^^^^^^^^^^^^^^^^^^^^^^ + +Create transformers to prepare data for use in estimators (models) by +using the +`bigframes.ml.preprocessing module `_ +and the `bigframes.ml.compose module `_. +BigQuery DataFrames offers the following transformations: + +* Use the `KBinsDiscretizer class `_ + in the ``bigframes.ml.preprocessing`` module to bin continuous data into intervals. +* Use the `LabelEncoder class `_ + in the ``bigframes.ml.preprocessing`` module to normalize the target labels as integer values. +* Use the `MaxAbsScaler class `_ + in the ``bigframes.ml.preprocessing`` module to scale each feature to the range ``[-1, 1]`` by its maximum absolute value. +* Use the `MinMaxScaler class `_ + in the ``bigframes.ml.preprocessing`` module to standardize features by scaling each feature to the range ``[0, 1]``. +* Use the `StandardScaler class `_ + in the ``bigframes.ml.preprocessing`` module to standardize features by removing the mean and scaling to unit variance. +* Use the `OneHotEncoder class `_ + in the ``bigframes.ml.preprocessing`` module to transform categorical values into numeric format. +* Use the `ColumnTransformer class `_ + in the ``bigframes.ml.compose`` module to apply transformers to DataFrames columns. + + +Train models +^^^^^^^^^^^^ + +Create estimators to train models in BigQuery DataFrames. + +**Clustering models** + +Create estimators for clustering models by using the +`bigframes.ml.cluster module `_. + +* Use the `KMeans class `_ + to create K-means clustering models. Use these models for + data segmentation. For example, identifying customer segments. K-means is an + unsupervised learning technique, so model training doesn't require labels or split + data for training or evaluation. + +**Decomposition models** + +Create estimators for decomposition models by using the `bigframes.ml.decomposition module `_. + +* Use the `PCA class `_ + to create principal component analysis (PCA) models. Use these + models for computing principal components and using them to perform a change of + basis on the data. This provides dimensionality reduction by projecting each data + point onto only the first few principal components to obtain lower-dimensional + data while preserving as much of the data's variation as possible. + + +**Ensemble models** + +Create estimators for ensemble models by using the `bigframes.ml.ensemble module `_. + +* Use the `RandomForestClassifier class `_ + to create random forest classifier models. Use these models for constructing multiple + learning method decision trees for classification. +* Use the `RandomForestRegressor class `_ + to create random forest regression models. Use + these models for constructing multiple learning method decision trees for regression. +* Use the `XGBClassifier class `_ + to create gradient boosted tree classifier models. Use these models for additively + constructing multiple learning method decision trees for classification. +* Use the `XGBRegressor class `_ + to create gradient boosted tree regression models. Use these models for additively + constructing multiple learning method decision trees for regression. + + +**Forecasting models** + +Create estimators for forecasting models by using the `bigframes.ml.forecasting module `_. + +* Use the `ARIMAPlus class `_ + to create time series forecasting models. + +**Imported models** + +Create estimators for imported models by using the `bigframes.ml.imported module `_. + +* Use the `ONNXModel class `_ + to import Open Neural Network Exchange (ONNX) models. +* Use the `TensorFlowModel class `_ + to import TensorFlow models. + +**Linear models** + +Create estimators for linear models by using the `bigframes.ml.linear_model module `_. + +* Use the `LinearRegression class `_ + to create linear regression models. Use these models for forecasting. For example, + forecasting the sales of an item on a given day. +* Use the `LogisticRegression class `_ + to create logistic regression models. Use these models for the classification of two + or more possible values such as whether an input is ``low-value``, ``medium-value``, + or ``high-value``. + +**Large language models** + +Create estimators for LLMs by using the `bigframes.ml.llm module `_. + +* Use the `PaLM2TextGenerator class `_ to create PaLM2 text generator models. Use these models + for text generation tasks. +* Use the `PaLM2TextEmbeddingGenerator class `_ to create PaLM2 text embedding generator models. + Use these models for text embedding generation tasks. + + +Create pipelines +^^^^^^^^^^^^^^^^ + +Create ML pipelines by using +`bigframes.ml.pipeline module `_. +Pipelines let you assemble several ML steps to be cross-validated together while setting +different parameters. This simplifies your code, and allows you to deploy data preprocessing +steps and an estimator together. + +* Use the `Pipeline class `_ + to create a pipeline of transforms with a final estimator. + + +ML locations +------------ + +``bigframes.ml`` supports the same locations as BigQuery ML. BigQuery ML model +prediction and other ML functions are supported in all BigQuery regions. Support +for model training varies by region. For more information, see +`BigQuery ML locations `_. + + +Data types +---------- + +BigQuery DataFrames supports the following numpy and pandas dtypes: + +* ``numpy.dtype("O")`` +* ``pandas.BooleanDtype()`` +* ``pandas.Float64Dtype()`` +* ``pandas.Int64Dtype()`` +* ``pandas.StringDtype(storage="pyarrow")`` +* ``pandas.ArrowDtype(pa.date32())`` +* ``pandas.ArrowDtype(pa.time64("us"))`` +* ``pandas.ArrowDtype(pa.timestamp("us"))`` +* ``pandas.ArrowDtype(pa.timestamp("us", tz="UTC"))`` + +BigQuery DataFrames doesn’t support the following BigQuery data types: + +* ``ARRAY`` +* ``NUMERIC`` +* ``BIGNUMERIC`` +* ``INTERVAL`` +* ``STRUCT`` +* ``JSON`` + +All other BigQuery data types display as the object type. + + +Remote functions +---------------- + +BigQuery DataFrames gives you the ability to turn your custom scalar functions +into `BigQuery remote functions +`_ . Creating a remote +function in BigQuery DataFrames (See `code samples +`_) +creates a BigQuery remote function, a `BigQuery +connection +`_ , +and a `Cloud Functions (2nd gen) function +`_ . + +BigQuery connections are created in the same location as the BigQuery +DataFrames session, using the name you provide in the custom function +definition. To view and manage connections, do the following: + +1. Go to `BigQuery in the Google Cloud Console `__. +2. Select the project in which you created the remote function. +3. In the Explorer pane, expand that project and then expand External connections. + +BigQuery remote functions are created in the dataset you specify, or +in a dataset with the name ``bigframes_temp_location``, where location is +the location used by the BigQuery DataFrames session. For example, +``bigframes_temp_us_central1``. To view and manage remote functions, do +the following: + +1. Go to `BigQuery in the Google Cloud Console `__. +2. Select the project in which you created the remote function. +3. In the Explorer pane, expand that project, expand the dataset in which you + created the remote function, and then expand Routines. + +To view and manage Cloud Functions functions, use the +`Functions `_ +page and use the project picker to select the project in which you +created the function. For easy identification, the names of the functions +created by BigQuery DataFrames are prefixed by ``bigframes``. + +**Requirements** + +BigQuery DataFrames uses the ``gcloud`` command-line interface internally, +so you must run ``gcloud auth login`` before using remote functions. + +To use BigQuery DataFrames remote functions, you must enable the following APIs: + +* The BigQuery API (bigquery.googleapis.com) +* The BigQuery Connection API (bigqueryconnection.googleapis.com) +* The Cloud Functions API (cloudfunctions.googleapis.com) +* The Cloud Run API (run.googleapis.com) +* The Artifact Registry API (artifactregistry.googleapis.com) +* The Cloud Build API (cloudbuild.googleapis.com ) +* The Cloud Resource Manager API (cloudresourcemanager.googleapis.com) + +To use BigQuery DataFrames remote functions, you must be granted the +following IAM roles: + +* BigQuery Data Editor (roles/bigquery.dataEditor) +* BigQuery Connection Admin (roles/bigquery.connectionAdmin) +* Cloud Functions Developer (roles/cloudfunctions.developer) +* Service Account User (roles/iam.serviceAccountUser) on the + `service account ` + ``PROJECT_NUMBER-compute@developer.gserviceaccount.com`` +* Storage Object Viewer (roles/storage.objectViewer) +* Project IAM Admin (roles/resourcemanager.projectIamAdmin) + +**Limitations** + +* Remote functions take about 90 seconds to become available when you first create them. +* Trivial changes in the notebook, such as inserting a new cell or renaming a variable, + might cause the remote function to be re-created, even if these changes are unrelated + to the remote function code. +* BigQuery DataFrames does not differentiate any personal data you include in the remote + function code. The remote function code is serialized as an opaque box to deploy it as a + Cloud Functions function. +* The Cloud Functions (2nd gen) functions, BigQuery connections, and BigQuery remote + functions created by BigQuery DataFrames persist in Google Cloud. If you don’t want to + keep these resources, you must delete them separately using an appropriate Cloud Functions + or BigQuery interface. +* A project can have up to 1000 Cloud Functions (2nd gen) functions at a time. See Cloud + Functions quotas for all the limits. + + +Quotas and limits +------------------ + +`BigQuery quotas `_ +including hardware, software, and network components. + + +Session termination +------------------- + +Each BigQuery DataFrames DataFrame or Series object is tied to a BigQuery +DataFrames session, which is in turn based on a BigQuery session. BigQuery +sessions +`auto-terminate `_ +; when this happens, you can’t use previously +created DataFrame or Series objects and must re-create them using a new +BigQuery DataFrames session. You can do this by running +``bigframes.pandas.close_session()`` and then re-running the BigQuery +DataFrames expressions. + + +Data processing location +------------------------ -To learn more about BigQuery DataFrames, visit these pages +BigQuery DataFrames is designed for scale, which it achieves by keeping data +and processing on the BigQuery service. However, you can bring data into the +memory of your client machine by calling ``.to_pandas()`` on a DataFrame or Series +object. If you choose to do this, the memory limitation of your client machine +applies. -* `Introduction to BigQuery DataFrames (BigFrames) `_ -* `Sample notebooks `_ -* `API reference `_ -* `Source code (GitHub) `_ License ------- BigQuery DataFrames is distributed with the `Apache-2.0 license -`_. +`_. It also contains code derived from the following third-party packages: @@ -81,10 +371,9 @@ It also contains code derived from the following third-party packages: * `Python `_ * `scikit-learn `_ * `XGBoost `_ -* `SQLGlot `_ For details, see the `third_party -`_ +`_ directory. diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 00000000000..8b58ae9c01a --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,7 @@ +# Security Policy + +To report a security issue, please use [g.co/vulnz](https://g.co/vulnz). + +The Google Security Team will respond within 5 working days of your report on g.co/vulnz. + +We use g.co/vulnz for our intake, and do coordination and disclosure here using GitHub Security Advisory to privately discuss and fix the issue. diff --git a/bigframes/__init__.py b/bigframes/__init__.py index 533726343a5..bd1476957bf 100644 --- a/bigframes/__init__.py +++ b/bigframes/__init__.py @@ -14,66 +14,17 @@ """BigQuery DataFrames provides a DataFrame API scaled by the BigQuery engine.""" -import warnings - -# Suppress Python version support warnings from google-cloud libraries. -# These are particularly noisy in Colab which still uses Python 3.10. -warnings.filterwarnings( - "ignore", - category=FutureWarning, - message=".*Google will stop supporting.*Python.*", -) - -# import configuration and types. -# This ensures that when the deeper 'core' modules ask for 'dtypes','options', et. al., -# they are already defined and available. -import bigframes.dtypes # noqa: E402 # isort: skip -import bigframes._config # noqa: E402 # isort: skip -from bigframes._config import option_context, options # noqa: E402 # isort: skip - -import bigframes.enums as enums # noqa: E402 -import bigframes.exceptions as exceptions # noqa: E402 - -# We import operations early to resolve a circular dependency between -# bigframes.core.expression and bigframes.operations. -# This ensures the 'Expression' base class is defined before 'Aggregation' -# subclasses attempt to inherit from it. -import bigframes.operations # noqa: E402 # isort: skip - -# Register pandas extensions -import bigframes.extensions.pandas.dataframe_accessor # noqa: F401, E402 -import bigframes.extensions.pandas.series_accessor # noqa: F401, E402 -from bigframes._config.bigquery_options import BigQueryOptions # noqa: E402 -from bigframes.core.global_session import ( # noqa: E402 - close_session, - execution_history, - get_global_session, -) -from bigframes.session import Session, connect # noqa: E402 -from bigframes.version import __version__ # noqa: E402 - -_MAGIC_NAMES = ["bqsql"] - - -def load_ipython_extension(ipython): - """Called by IPython when this module is loaded as an IPython extension.""" - # Requires IPython to be installed for import to succeed - from bigframes._magics import _cell_magic - - for magic_name in _MAGIC_NAMES: - ipython.register_magic_function( - _cell_magic, magic_kind="cell", magic_name=magic_name - ) - +from bigframes._config import option_context, options +from bigframes._config.bigquery_options import BigQueryOptions +from bigframes.core.global_session import close_session, get_global_session +from bigframes.session import connect, Session +from bigframes.version import __version__ __all__ = [ "options", "BigQueryOptions", "get_global_session", "close_session", - "execution_history", - "enums", - "exceptions", "connect", "Session", "__version__", diff --git a/bigframes/_config/__init__.py b/bigframes/_config/__init__.py index cbe369ad58c..8dcebfce6a2 100644 --- a/bigframes/_config/__init__.py +++ b/bigframes/_config/__init__.py @@ -17,24 +17,55 @@ DataFrames from this package. """ -import bigframes._config.global_options as global_options -from bigframes._config.bigquery_options import BigQueryOptions -from bigframes._config.compute_options import ComputeOptions -from bigframes._config.display_options import DisplayOptions -from bigframes._config.experiment_options import ExperimentOptions -from bigframes._config.global_options import Options, option_context -from bigframes._config.sampling_options import SamplingOptions +import bigframes._config.bigquery_options as bigquery_options +import bigframes._config.compute_options as compute_options +import bigframes._config.display_options as display_options +import bigframes._config.sampling_options as sampling_options +import third_party.bigframes_vendored.pandas._config.config as pandas_config + + +class Options: + """Global options affecting BigQuery DataFrames behavior.""" + + def __init__(self): + self._bigquery_options = bigquery_options.BigQueryOptions() + self._display_options = display_options.DisplayOptions() + self._sampling_options = sampling_options.SamplingOptions() + self._compute_options = compute_options.ComputeOptions() + + @property + def bigquery(self) -> bigquery_options.BigQueryOptions: + """Options to use with the BigQuery engine.""" + return self._bigquery_options + + @property + def display(self) -> display_options.DisplayOptions: + """Options controlling object representation.""" + return self._display_options + + @property + def sampling(self) -> sampling_options.SamplingOptions: + """Options controlling downsampling when downloading data + to memory. The data will be downloaded into memory explicitly + (e.g., to_pandas, to_numpy, values) or implicitly (e.g., + matplotlib plotting). This option can be overriden by + parameters in specific functions.""" + return self._sampling_options + + @property + def compute(self) -> compute_options.ComputeOptions: + """Options controlling object computation.""" + return self._compute_options + + +options = Options() +"""Global options for default session.""" -options = global_options.options -"""Global options for the default session.""" __all__ = ( "Options", "options", - "option_context", - "BigQueryOptions", - "ComputeOptions", - "DisplayOptions", - "ExperimentOptions", - "SamplingOptions", ) + + +option_context = pandas_config.option_context diff --git a/bigframes/_config/auth.py b/bigframes/_config/auth.py deleted file mode 100644 index f1c069b5310..00000000000 --- a/bigframes/_config/auth.py +++ /dev/null @@ -1,86 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from __future__ import annotations - -import os -import threading -from typing import Optional - -import google.auth.credentials -import google.auth.transport.requests -import pydata_google_auth - -import bigframes._config.bigquery_options as bigquery_options - -_SCOPES = ["https://www.googleapis.com/auth/cloud-platform"] - -# Put the lock here rather than in BigQueryOptions so that BigQueryOptions -# remains deepcopy-able. -_AUTH_LOCK = threading.Lock() -_cached_credentials: Optional[google.auth.credentials.Credentials] = None -_cached_project_default: Optional[str] = None - - -_GOOGLE_CLOUD_PROJECT = "GOOGLE_CLOUD_PROJECT" - - -def resolve_credentials_and_project( - options: bigquery_options.BigQueryOptions, -) -> tuple[google.auth.credentials.Credentials, str]: - project = options.project - credentials = options.credentials - if project is None: - project = os.getenv(_GOOGLE_CLOUD_PROJECT) - - if credentials is None: - credentials, cred_project = _get_default_credentials_with_project() - # This might conflict with explicit project, which will be ignored, credentials project - # only used if nothing else specified - if project is None: - project = cred_project - - if project is None: - raise ValueError( - "Project must be set to initialize BigQuery client. " - "Try setting `bigframes.options.bigquery.project` first." - ) - return credentials, project - - -def _get_default_credentials_with_project() -> tuple[ - google.auth.credentials.Credentials, Optional[str] -]: - global _AUTH_LOCK, _cached_credentials, _cached_project_default - - with _AUTH_LOCK: - if _cached_credentials is not None: - return _cached_credentials, _cached_project_default - - _cached_credentials, _cached_project_default = pydata_google_auth.default( - scopes=_SCOPES, use_local_webserver=False - ) - - # Ensure an access token is available. - _cached_credentials.refresh(google.auth.transport.requests.Request()) - - return _cached_credentials, _cached_project_default - - -def reset_default_credentials_and_project(): - global _AUTH_LOCK, _cached_credentials, _cached_project_default - - with _AUTH_LOCK: - _cached_credentials = None - _cached_project_default = None diff --git a/bigframes/_config/bigquery_options.py b/bigframes/_config/bigquery_options.py index 6c5c424240d..d0cce9492bb 100644 --- a/bigframes/_config/bigquery_options.py +++ b/bigframes/_config/bigquery_options.py @@ -16,15 +16,10 @@ from __future__ import annotations -import warnings -from typing import Literal, Optional, Sequence, Tuple +from typing import Optional +import google.api_core.exceptions import google.auth.credentials -import requests.adapters - -import bigframes._importing -import bigframes.enums -import bigframes.exceptions as bfe SESSION_STARTED_MESSAGE = ( "Cannot change '{attribute}' once a session has started. " @@ -32,49 +27,6 @@ ) -UNKNOWN_LOCATION_MESSAGE = "The location '{location}' is set to an unknown value. Did you mean '{possibility}'?" - - -def _get_validated_location(value: Optional[str]) -> Optional[str]: - import bigframes._tools.strings - - if value is None or value in bigframes.constants.ALL_BIGQUERY_LOCATIONS: - return value - - location = str(value) - - location_lowercase = location.lower() - if location_lowercase in bigframes.constants.BIGQUERY_REGIONS: - return location_lowercase - - location_uppercase = location.upper() - if location_uppercase in bigframes.constants.BIGQUERY_MULTIREGIONS: - return location_uppercase - - possibility = min( - bigframes.constants.ALL_BIGQUERY_LOCATIONS, - key=lambda item: bigframes._tools.strings.levenshtein_distance(location, item), - ) - # There are many layers before we get to (possibly) the user's code: - # -> bpd.options.bigquery.location = "us-central-1" - # -> location.setter - # -> _get_validated_location - msg = bfe.format_message( - UNKNOWN_LOCATION_MESSAGE.format(location=location, possibility=possibility) - ) - warnings.warn(msg, stacklevel=3, category=bfe.UnknownLocationWarning) - - return value - - -def _validate_ordering_mode(value: str) -> bigframes.enums.OrderingMode: - if value.casefold() == bigframes.enums.OrderingMode.STRICT.value.casefold(): - return bigframes.enums.OrderingMode.STRICT - if value.casefold() == bigframes.enums.OrderingMode.PARTIAL.value.casefold(): - return bigframes.enums.OrderingMode.PARTIAL - raise ValueError("Ordering mode must be one of 'strict' or 'partial'.") - - class BigQueryOptions: """Encapsulates configuration for working with a session.""" @@ -86,55 +38,21 @@ def __init__( bq_connection: Optional[str] = None, use_regional_endpoints: bool = False, application_name: Optional[str] = None, - kms_key_name: Optional[str] = None, - skip_bq_connection_check: bool = False, - *, - allow_large_results: bool = False, - ordering_mode: Literal["strict", "partial"] = "strict", - client_endpoints_override: Optional[dict] = None, - requests_transport_adapters: Sequence[ - Tuple[str, requests.adapters.BaseAdapter] - ] = (), - enable_polars_execution: bool = False, ): self._credentials = credentials self._project = project - self._location = _get_validated_location(location) + self._location = location self._bq_connection = bq_connection self._use_regional_endpoints = use_regional_endpoints self._application_name = application_name - self._kms_key_name = kms_key_name - self._skip_bq_connection_check = skip_bq_connection_check - self._allow_large_results = allow_large_results - self._requests_transport_adapters = requests_transport_adapters self._session_started = False - # Determines the ordering strictness for the session. - self._ordering_mode = _validate_ordering_mode(ordering_mode) - - if client_endpoints_override is None: - client_endpoints_override = {} - - self._client_endpoints_override = client_endpoints_override - if enable_polars_execution: - bigframes._importing.import_polars() - self._enable_polars_execution = enable_polars_execution @property def application_name(self) -> Optional[str]: """The application name to amend to the user-agent sent to Google APIs. - The application name to amend to the user agent sent to Google APIs. - The recommended format is ``"application-name/major.minor.patch_version"`` + Recommended format is ``"appplication-name/major.minor.patch_version"`` or ``"(gpn:PartnerName;)"`` for official Google partners. - - **Examples:** - - >>> import bigframes.pandas as bpd - >>> bpd.options.bigquery.application_name = "my-app/1.0.0" # doctest: +SKIP - - Returns: - None or str: - Application name as a string if exists; otherwise None. """ return self._application_name @@ -148,19 +66,7 @@ def application_name(self, value: Optional[str]): @property def credentials(self) -> Optional[google.auth.credentials.Credentials]: - """The OAuth2 credentials to use for this client. - - **Examples:** - - >>> import bigframes.pandas as bpd - >>> import google.auth - >>> credentials, project = google.auth.default() # doctest: +SKIP - >>> bpd.options.bigquery.credentials = credentials # doctest: +SKIP - - Returns: - None or google.auth.credentials.Credentials: - google.auth.credentials.Credentials if exists; otherwise None. - """ + """The OAuth2 Credentials to use for this client.""" return self._credentials @credentials.setter @@ -173,38 +79,19 @@ def credentials(self, value: Optional[google.auth.credentials.Credentials]): def location(self) -> Optional[str]: """Default location for job, datasets, and tables. - For more information, see https://cloud.google.com/bigquery/docs/locations BigQuery locations. - - **Examples:** - - >>> import bigframes.pandas as bpd - >>> bpd.options.bigquery.location = "US" # doctest: +SKIP - - Returns: - None or str: - Default location as a string; otherwise None. + See: https://cloud.google.com/bigquery/docs/locations """ return self._location @location.setter def location(self, value: Optional[str]): - if self._session_started and self._location != _get_validated_location(value): + if self._session_started and self._location != value: raise ValueError(SESSION_STARTED_MESSAGE.format(attribute="location")) - self._location = _get_validated_location(value) + self._location = value @property def project(self) -> Optional[str]: - """Google Cloud project ID to use for billing and as the default project. - - **Examples:** - - >>> import bigframes.pandas as bpd - >>> bpd.options.bigquery.project = "my-project" # doctest: +SKIP - - Returns: - None or str: - Google Cloud project ID as a string; otherwise None. - """ + """Google Cloud project ID to use for billing and as the default project.""" return self._project @project.setter @@ -215,27 +102,14 @@ def project(self, value: Optional[str]): @property def bq_connection(self) -> Optional[str]: - """Name of the BigQuery connection to use in the form - ... - - You either need to create the connection in a location of your choice, or - you need the Project Admin IAM role to enable the service to create the - connection for you. - - If this option isn't available, or the project or location isn't provided, - then the default connection project/location/connection_id is used in the session. + """Name of the BigQuery connection to use. Should be of the form ... - If this option isn't provided, or project or location aren't provided, - session will use its default project/location/connection_id as default connection. + You should either have the connection already created in the + location you have chosen, or you should have the Project IAM + Admin role to enable the service to create the connection for you if you + need it. - **Examples:** - - >>> import bigframes.pandas as bpd - >>> bpd.options.bigquery.bq_connection = "my-project.us.my-connection" # doctest: +SKIP - - Returns: - None or str: - Name of the BigQuery connection as a string; otherwise None. + If this option isn't provided, or project or location aren't provided, session will use its default project/location/connection_id as default connection. """ return self._bq_connection @@ -245,104 +119,13 @@ def bq_connection(self, value: Optional[str]): raise ValueError(SESSION_STARTED_MESSAGE.format(attribute="bq_connection")) self._bq_connection = value - @property - def skip_bq_connection_check(self) -> bool: - """Forcibly use the BigQuery connection. - - Setting this flag to True would avoid creating the BigQuery connection - and checking or setting IAM permissions on it. So if the BigQuery - connection (default or user-provided) does not exist, or it does not have - necessary permissions set up to support BigQuery DataFrames operations, - then a runtime error will be reported. - - **Examples:** - - >>> import bigframes.pandas as bpd - >>> bpd.options.bigquery.skip_bq_connection_check = True # doctest: +SKIP - - Returns: - bool: - A boolean value, where True indicates a BigQuery connection is - not created or the connection does not have necessary - permissions set up; otherwise False. - """ - return self._skip_bq_connection_check - - @skip_bq_connection_check.setter - def skip_bq_connection_check(self, value: bool): - if self._session_started and self._skip_bq_connection_check != value: - raise ValueError( - SESSION_STARTED_MESSAGE.format(attribute="skip_bq_connection_check") - ) - self._skip_bq_connection_check = value - - @property - def allow_large_results(self) -> bool: - """ - DEPRECATED: Checks the legacy global setting for allowing large results. - Use ``bpd.options.compute.allow_large_results`` instead. - - Warning: Accessing ``bpd.options.bigquery.allow_large_results`` is deprecated - and this property will be removed in a future version. The configuration for - handling large results has moved. - - Returns: - bool: The value of the deprecated setting. - """ - return self._allow_large_results - - @allow_large_results.setter - def allow_large_results(self, value: bool): - warnings.warn( - "Setting `bpd.options.bigquery.allow_large_results` is deprecated, " - "and will be removed in the future. " - "Please use `bpd.options.compute.allow_large_results = ` instead. " - "The `bpd.options.bigquery.allow_large_results` option is ignored if " - "`bpd.options.compute.allow_large_results` is set.", - FutureWarning, - stacklevel=2, - ) - if self._session_started and self._allow_large_results != value: - raise ValueError( - SESSION_STARTED_MESSAGE.format(attribute="allow_large_results") - ) - - self._allow_large_results = value - @property def use_regional_endpoints(self) -> bool: - """Flag to connect to regional API endpoints for BigQuery API and - BigQuery Storage API. - - .. note:: - Use of regional endpoints is a feature in Preview and available only - in regions "europe-west3", "europe-west8", "europe-west9", - "me-central2", "us-central1", "us-central2", "us-east1", "us-east4", - "us-east5", "us-east7", "us-south1", "us-west1", "us-west2", "us-west3" - and "us-west4". + """Flag to connect to regional API endpoints. - Requires that ``location`` is set. For [supported regions](https://cloud.google.com/bigquery/docs/regional-endpoints), - for example ``europe-west3``, you need to specify - ``location='europe-west3'`` and ``use_regional_endpoints=True``, and - then BigQuery DataFrames would connect to the BigQuery endpoint - ``bigquery.europe-west3.rep.googleapis.com``. For not supported regions, - for example ``asia-northeast1``, when you specify - ``location='asia-northeast1'`` and ``use_regional_endpoints=True``, - the global endpoint ``bigquery.googleapis.com`` would be used, which - does not promise any guarantee on the request remaining within the - location during transit. - - **Examples:** - - >>> import bigframes.pandas as bpd - >>> bpd.options.bigquery.location = "europe-west3" # doctest: +SKIP - >>> bpd.options.bigquery.use_regional_endpoints = True # doctest: +SKIP - - Returns: - bool: - A boolean value, where True indicates that regional endpoints - would be used for BigQuery and BigQuery storage APIs; otherwise - global endpoints would be used. + Requires ``location`` to also be set. For example, set + ``location='asia-northeast1'`` and ``use_regional_endpoints=True`` to + connect to asia-northeast1-bigquery.googleapis.com. """ return self._use_regional_endpoints @@ -352,154 +135,4 @@ def use_regional_endpoints(self, value: bool): raise ValueError( SESSION_STARTED_MESSAGE.format(attribute="use_regional_endpoints") ) - - if value: - msg = bfe.format_message( - "Use of regional endpoints is a feature in preview and " - "available only in selected regions and projects. " - ) - warnings.warn(msg, category=bfe.PreviewWarning, stacklevel=2) - self._use_regional_endpoints = value - - @property - def kms_key_name(self) -> Optional[str]: - """ - Customer managed encryption key used to control encryption of the - data-at-rest in BigQuery. This is of the format - projects/PROJECT_ID/locations/LOCATION/keyRings/KEYRING/cryptoKeys/KEY. - - For more information, see https://cloud.google.com/bigquery/docs/customer-managed-encryption - Customer-managed Cloud KMS keys - - Make sure the project used for Bigquery DataFrames has the - Cloud KMS CryptoKey Encrypter/Decrypter IAM role in the key's project. - For more information, see https://cloud.google.com/bigquery/docs/customer-managed-encryption#assign_role - Assign the Encrypter/Decrypter. - - **Examples:** - - >>> import bigframes.pandas as bpd - >>> bpd.options.bigquery.kms_key_name = "projects/my-project/locations/us/keyRings/my-ring/cryptoKeys/my-key" # doctest: +SKIP - - Returns: - None or str: - Name of the customer managed encryption key as a string; otherwise None. - """ - return self._kms_key_name - - @kms_key_name.setter - def kms_key_name(self, value: str): - if self._session_started and self._kms_key_name != value: - raise ValueError(SESSION_STARTED_MESSAGE.format(attribute="kms_key_name")) - - self._kms_key_name = value - - @property - def ordering_mode(self) -> Literal["strict", "partial"]: - """Controls whether total row order is always maintained for DataFrame/Series. - - **Examples:** - - >>> import bigframes.pandas as bpd - >>> bpd.options.bigquery.ordering_mode = "partial" # doctest: +SKIP - - Returns: - Literal: - A literal string value of either strict or partial ordering mode. - """ - return self._ordering_mode.value - - @ordering_mode.setter - def ordering_mode(self, value: Literal["strict", "partial"]) -> None: - ordering_mode = _validate_ordering_mode(value) - if self._session_started and self._ordering_mode != ordering_mode: - raise ValueError(SESSION_STARTED_MESSAGE.format(attribute="ordering_mode")) - self._ordering_mode = ordering_mode - - @property - def client_endpoints_override(self) -> dict: - """Option that sets the BQ client endpoints addresses directly as a dict. Possible keys are "bqclient", "bqconnectionclient", "bqstoragereadclient".""" - return self._client_endpoints_override - - @client_endpoints_override.setter - def client_endpoints_override(self, value: dict): - msg = bfe.format_message( - "This is an advanced configuration option for directly setting endpoints. " - "Incorrect use may lead to unexpected behavior or system instability. " - "Proceed only if you fully understand its implications." - ) - warnings.warn(msg) - - if self._session_started and self._client_endpoints_override != value: - raise ValueError( - SESSION_STARTED_MESSAGE.format(attribute="client_endpoints_override") - ) - - self._client_endpoints_override = value - - @property - def requests_transport_adapters( - self, - ) -> Sequence[Tuple[str, requests.adapters.BaseAdapter]]: - """Transport adapters for requests-based REST clients such as the - google-cloud-bigquery package. - - For more details, see the explanation in `requests guide to transport - adapters - `_. - - **Examples:** - - Increase the connection pool size using the requests `HTTPAdapter - `_. - - >>> import bigframes.pandas as bpd - >>> bpd.options.bigquery.requests_transport_adapters = ( - ... ("http://", requests.adapters.HTTPAdapter(pool_maxsize=100)), - ... ("https://", requests.adapters.HTTPAdapter(pool_maxsize=100)), - ... ) # doctest: +SKIP - - Returns: - Sequence[Tuple[str, requests.adapters.BaseAdapter]]: - Prefixes and corresponding transport adapters to `mount - `_ - in requests-based REST clients. - """ - return self._requests_transport_adapters - - @requests_transport_adapters.setter - def requests_transport_adapters( - self, value: Sequence[Tuple[str, requests.adapters.BaseAdapter]] - ) -> None: - if self._session_started and self._requests_transport_adapters != value: - raise ValueError( - SESSION_STARTED_MESSAGE.format(attribute="requests_transport_adapters") - ) - self._requests_transport_adapters = value - - @property - def enable_polars_execution(self) -> bool: - """If True, will use polars to execute some simple query plans locally. - - **Examples:** - - >>> import bigframes.pandas as bpd - >>> bpd.options.bigquery.enable_polars_execution = True # doctest: +SKIP - - """ - return self._enable_polars_execution - - @enable_polars_execution.setter - def enable_polars_execution(self, value: bool): - if self._session_started and self._enable_polars_execution != value: - raise ValueError( - SESSION_STARTED_MESSAGE.format(attribute="enable_polars_execution") - ) - if value is True: - msg = bfe.format_message( - "Polars execution is an experimental feature, and may not be stable. Must have polars installed." - ) - warnings.warn(msg, category=bfe.PreviewWarning) - bigframes._importing.import_polars() - self._enable_polars_execution = value diff --git a/bigframes/_config/compute_options.py b/bigframes/_config/compute_options.py index 2ef1e5b7213..20c31d39066 100644 --- a/bigframes/_config/compute_options.py +++ b/bigframes/_config/compute_options.py @@ -15,181 +15,21 @@ """Options for displaying objects.""" import dataclasses -from typing import Any, Dict, Optional +from typing import Optional @dataclasses.dataclass class ComputeOptions: """ - Encapsulates the configuration for compute options. + Encapsulates configuration for compute options. - **Examples:** + 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 `_. - >>> import bigframes.pandas as bpd - >>> df = bpd.read_gbq("bigquery-public-data.ml_datasets.penguins") - - >>> bpd.options.compute.maximum_bytes_billed = 500 # doctest: +SKIP - >>> df.to_pandas() # this should fail # doctest: +SKIP - google.api_core.exceptions.InternalServerError: 500 Query exceeded limit for bytes billed: 500. 10485760 or higher required. - - >>> bpd.options.compute.maximum_bytes_billed = None # reset option # doctest: +SKIP - - To add multiple extra labels to a query configuration, use the `assign_extra_query_labels` - method with keyword arguments: - - >>> bpd.options.compute.assign_extra_query_labels(test1=1, test2="abc") # doctest: +SKIP - >>> bpd.options.compute.extra_query_labels # doctest: +SKIP - {'test1': 1, 'test2': 'abc'} - - Alternatively, you can add labels individually by directly accessing the `extra_query_labels` - dictionary: - - >>> bpd.options.compute.extra_query_labels["test3"] = False # doctest: +SKIP - >>> bpd.options.compute.extra_query_labels # doctest: +SKIP - {'test1': 1, 'test2': 'abc', 'test3': False} - - To remove a label from the configuration, use the `del` keyword on the desired label key: - - >>> del bpd.options.compute.extra_query_labels["test1"] # doctest: +SKIP - >>> bpd.options.compute.extra_query_labels # doctest: +SKIP - {'test2': 'abc', 'test3': False} - """ - - ai_ops_confirmation_threshold: Optional[int] = 0 - """ - 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 - to turn off the guard. - - **Examples:** - - >>> import bigframes.pandas as bpd - >>> bpd.options.compute.ai_ops_confirmation_threshold = 100 # doctest: +SKIP - - Returns: - Optional[int]: Number of rows. - """ - - ai_ops_threshold_autofail: bool = False - """ - 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. - - **Examples:** - - >>> import bigframes.pandas as bpd - >>> bpd.options.compute.ai_ops_threshold_autofail = True # doctest: +SKIP - - Returns: - bool: True if the guard is enabled. - """ - - allow_large_results: Optional[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. - - **Examples:** - - >>> import bigframes.pandas as bpd - >>> bpd.options.compute.allow_large_results = True # doctest: +SKIP - - Returns: - bool | None: True if results > 10 GB are enabled. - """ - enable_multi_query_execution: bool = False - """ - If enabled, large queries may be factored into multiple smaller queries. - - This is 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. - - **Examples:** - - >>> import bigframes.pandas as bpd - >>> bpd.options.compute.enable_multi_query_execution = True # doctest: +SKIP - - Returns: - bool | None: True if enabled. - """ - - extra_query_labels: Dict[str, Any] = dataclasses.field( - default_factory=dict, init=False - ) - """ - Stores additional custom labels for query configuration. - - Returns: - Dict[str, Any] | None: Additional labels. """ maximum_bytes_billed: Optional[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. - - **Examples:** - - >>> import bigframes.pandas as bpd - >>> bpd.options.compute.maximum_bytes_billed = 1000 # doctest: +SKIP - - Returns: - int | None: Number of bytes, if set. - """ - - maximum_result_rows: Optional[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. - - **Examples:** - - >>> import bigframes.pandas as bpd - >>> bpd.options.compute.maximum_result_rows = 1000 # doctest: +SKIP - - Returns: - int | None: Number of rows, if set. - """ - - def assign_extra_query_labels(self, **kwargs: Any) -> None: - """ - Assigns additional custom labels for query configuration. The method updates the - `extra_query_labels` dictionary with new labels provided through keyword arguments. - - Args: - kwargs (Any): - Custom labels provided as keyword arguments. Each key-value pair - in `kwargs` represents a label name and its value. - - Raises: - ValueError: If a key matches one of the reserved attribute names, - specifically 'maximum_bytes_billed' or 'enable_multi_query_execution', - to prevent conflicts with built-in settings. - """ - reserved_keys = ["maximum_bytes_billed", "enable_multi_query_execution"] - for key in kwargs: - if key in reserved_keys: - raise ValueError( - f"'{key}' is a reserved attribute name. Please use " - "a different key for your custom labels to avoid " - "conflicts with built-in settings." - ) - - self.extra_query_labels.update(kwargs) diff --git a/bigframes/_config/display_options.py b/bigframes/_config/display_options.py index 34c5c77d57d..ad3ea3f68cc 100644 --- a/bigframes/_config/display_options.py +++ b/bigframes/_config/display_options.py @@ -15,29 +15,36 @@ """Options for displaying objects.""" import contextlib +import dataclasses +from typing import Literal, Optional -import bigframes_vendored.pandas.core.config_init as vendored_pandas_config import pandas as pd -DisplayOptions = vendored_pandas_config.DisplayOptions +import third_party.bigframes_vendored.pandas.core.config_init as vendored_pandas_config + + +@dataclasses.dataclass +class DisplayOptions: + __doc__ = vendored_pandas_config.display_options_doc + + max_columns: int = 20 + max_rows: int = 25 + progress_bar: Optional[str] = "auto" + repr_mode: Literal["head", "deferred"] = "head" @contextlib.contextmanager -def pandas_repr(display_options: vendored_pandas_config.DisplayOptions): +def pandas_repr(display_options: DisplayOptions): """Use this when visualizing with pandas. This context manager makes sure we reset the pandas options when we're done so that we don't override pandas behavior. """ with pd.option_context( - "display.max_colwidth", - display_options.max_colwidth, "display.max_columns", display_options.max_columns, "display.max_rows", display_options.max_rows, - "display.precision", - display_options.precision, "display.show_dimensions", True, ) as pandas_context: diff --git a/bigframes/_config/experiment_options.py b/bigframes/_config/experiment_options.py deleted file mode 100644 index 8f70c8952f6..00000000000 --- a/bigframes/_config/experiment_options.py +++ /dev/null @@ -1,140 +0,0 @@ -# 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. -# 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 warnings -from typing import Literal, Optional - -import bigframes -import bigframes.exceptions as bfe - - -class ExperimentOptions: - """ - Encapsulates the configuration for experiments - """ - - def __init__(self): - self._sql_compiler: Literal["legacy", "stable", "experimental"] = "stable" - self._enable_python_transpiler: bool = False - - @property - def sql_compiler(self) -> Literal["legacy", "stable", "experimental"]: - """Set to 'experimental' to try out the latest in compilation experiments.. - - **Examples:** - - >>> import bigframes.pandas as bpd - >>> bpd.options.experiments.sql_compiler = 'experimental' # doctest: +SKIP - """ - return self._sql_compiler - - @sql_compiler.setter - def sql_compiler(self, value: Literal["legacy", "stable", "experimental"]): - if value not in ["legacy", "stable", "experimental"]: - raise ValueError( - "sql_compiler must be one of 'legacy', 'stable', or 'experimental'" - ) - if value == "experimental": - msg = bfe.format_message( - "The experimental SQL compiler is still under experiments, and is subject " - "to change in the future." - ) - warnings.warn(msg, category=FutureWarning) - self._sql_compiler = value - - @property - def blob(self) -> bool: - msg = bfe.format_message( - "BigFrames Blob is in preview now. This flag is no longer needed." - ) - warnings.warn(msg, category=bfe.ApiDeprecationWarning) - return True - - @blob.setter - def blob(self, value: bool): - msg = bfe.format_message( - "BigFrames Blob is in preview now. This flag is no longer needed." - ) - warnings.warn(msg, category=bfe.ApiDeprecationWarning) - - @property - def blob_display(self) -> bool: - """Whether to display the blob content in notebook DataFrame preview. Default True.""" - msg = bfe.format_message( - "BigFrames Blob is in preview now. The option has been moved to bigframes.options.display.blob_display." - ) - warnings.warn(msg, category=bfe.ApiDeprecationWarning) - - return bigframes.options.display.blob_display - - @blob_display.setter - def blob_display(self, value: bool): - msg = bfe.format_message( - "BigFrames Blob is in preview now. The option has been moved to bigframes.options.display.blob_display." - ) - warnings.warn(msg, category=bfe.ApiDeprecationWarning) - - bigframes.options.display.blob_display = value - - @property - def blob_display_width(self) -> Optional[int]: - """Width in pixels that the blob constrained to.""" - msg = bfe.format_message( - "BigFrames Blob is in preview now. The option has been moved to bigframes.options.display.blob_display_width." - ) - warnings.warn(msg, category=bfe.ApiDeprecationWarning) - - return bigframes.options.display.blob_display_width - - @blob_display_width.setter - def blob_display_width(self, value: Optional[int]): - msg = bfe.format_message( - "BigFrames Blob is in preview now. The option has been moved to bigframes.options.display.blob_display_width." - ) - warnings.warn(msg, category=bfe.ApiDeprecationWarning) - - bigframes.options.display.blob_display_width = value - - @property - def blob_display_height(self) -> Optional[int]: - """Height in pixels that the blob constrained to.""" - msg = bfe.format_message( - "BigFrames Blob is in preview now. The option has been moved to bigframes.options.display.blob_display_height." - ) - warnings.warn(msg, category=bfe.ApiDeprecationWarning) - - return bigframes.options.display.blob_display_height - - @blob_display_height.setter - def blob_display_height(self, value: Optional[int]): - msg = bfe.format_message( - "BigFrames Blob is in preview now. The option has been moved to bigframes.options.display.blob_display_height." - ) - warnings.warn(msg, category=bfe.ApiDeprecationWarning) - - bigframes.options.display.blob_display_height = value - - @property - def enable_python_transpiler(self) -> bool: - return self._enable_python_transpiler - - @enable_python_transpiler.setter - def enable_python_transpiler(self, value: bool): - if value: - msg = bfe.format_message( - "Python transpiler is an unstable, experimental feature, and not yet fully " - "validated, use at your own risk." - ) - warnings.warn(msg, category=bfe.PythonTranspilerPreviewWarning) - self._enable_python_transpiler = value diff --git a/bigframes/_config/global_options.py b/bigframes/_config/global_options.py deleted file mode 100644 index 8f742608292..00000000000 --- a/bigframes/_config/global_options.py +++ /dev/null @@ -1,186 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -""" -Configuration for BigQuery DataFrames. Do not depend on other parts of BigQuery -DataFrames from this package. -""" - -from __future__ import annotations - -import copy -import threading -from dataclasses import dataclass, field -from typing import Optional - -import bigframes_vendored.pandas._config.config as pandas_config - -import bigframes._config.bigquery_options as bigquery_options -import bigframes._config.compute_options as compute_options -import bigframes._config.display_options as display_options -import bigframes._config.experiment_options as experiment_options -import bigframes._config.sampling_options as sampling_options - - -@dataclass -class ThreadLocalConfig(threading.local): - # If unset, global settings will be used - bigquery_options: Optional[bigquery_options.BigQueryOptions] = None - # Note: use default factory instead of default instance so each thread initializes to default values - display_options: display_options.DisplayOptions = field( - default_factory=display_options.DisplayOptions - ) - sampling_options: sampling_options.SamplingOptions = field( - default_factory=sampling_options.SamplingOptions - ) - compute_options: compute_options.ComputeOptions = field( - default_factory=compute_options.ComputeOptions - ) - experiment_options: experiment_options.ExperimentOptions = field( - default_factory=experiment_options.ExperimentOptions - ) - - -class Options: - """Global options affecting BigQuery DataFrames behavior. - - Do not construct directly. Instead, refer to - :attr:`bigframes.pandas.options`. - """ - - def __init__(self): - self.reset() - - def reset(self) -> Options: - """Reset the option settings to defaults. - - Returns: - bigframes._config.Options: Options object with default values. - """ - self._local = ThreadLocalConfig() - - # BigQuery options are special because they can only be set once per - # session, so we need an indicator as to whether we are using the - # thread-local session or the global session. - self._bigquery_options = bigquery_options.BigQueryOptions() - return self - - def _init_bigquery_thread_local(self): - """Initialize thread-local options, based on current global options.""" - - # Already thread-local, so don't reset any options that have been set - # already. No locks needed since this only modifies thread-local - # variables. - if self._local.bigquery_options is not None: - return - - self._local.bigquery_options = copy.deepcopy(self._bigquery_options) - self._local.bigquery_options._session_started = False - - @property - def bigquery(self) -> bigquery_options.BigQueryOptions: - """Options to use with the BigQuery engine. - - Returns: - bigframes._config.bigquery_options.BigQueryOptions: - Options for BigQuery engine. - """ - if self._local.bigquery_options is not None: - # The only way we can get here is if someone called - # _init_bigquery_thread_local. - return self._local.bigquery_options - - return self._bigquery_options - - @property - def display(self) -> display_options.DisplayOptions: - """Options controlling object representation. - - Returns: - bigframes._config.display_options.DisplayOptions: - Options for controlling object representation. - """ - return self._local.display_options - - @property - def sampling(self) -> sampling_options.SamplingOptions: - """Options controlling downsampling when downloading data - to memory. - - The data can be downloaded into memory explicitly - (e.g., to_pandas, to_numpy, values) or implicitly (e.g., - matplotlib plotting). This option can be overridden by - parameters in specific functions. - - Returns: - bigframes._config.sampling_options.SamplingOptions: - Options for controlling downsampling. - """ - return self._local.sampling_options - - @property - def compute(self) -> compute_options.ComputeOptions: - """Thread-local options controlling object computation. - - Returns: - bigframes._config.compute_options.ComputeOptions: - Thread-local options for controlling object computation - """ - return self._local.compute_options - - @property - def experiments(self) -> experiment_options.ExperimentOptions: - """Options controlling experiments - - Returns: - bigframes._config.experiment_options.ExperimentOptions: - Thread-local options for controlling experiments - """ - return self._local.experiment_options - - @property - def is_bigquery_thread_local(self) -> bool: - """Indicator that we're using a thread-local session. - - A thread-local session can be started by using - `with bigframes.option_context("bigquery.some_option", "some-value"):`. - - Returns: - bool: - A boolean value, where a value is True if a thread-local session - is in use; otherwise False. - """ - return self._local.bigquery_options is not None - - @property - def _allow_large_results(self) -> bool: - """The effective 'allow_large_results' setting. - - This value is `self.compute.allow_large_results` if set (not `None`), - otherwise it defaults to `self.bigquery.allow_large_results`. - - Returns: - bool: - Whether large query results are permitted. - - `True`: The BigQuery result size limit (e.g., 10 GB) is removed. - - `False`: Results are restricted to this limit (potentially faster). - BigQuery will raise an error if this limit is exceeded. - """ - if self.compute.allow_large_results is None: - return self.bigquery.allow_large_results - return self.compute.allow_large_results - - -options = Options() -option_context = pandas_config.option_context diff --git a/bigframes/_config/sampling_options.py b/bigframes/_config/sampling_options.py index 9746e01f31d..1742dabe17a 100644 --- a/bigframes/_config/sampling_options.py +++ b/bigframes/_config/sampling_options.py @@ -14,125 +14,17 @@ """Options for downsampling.""" -from __future__ import annotations - import dataclasses from typing import Literal, Optional +import third_party.bigframes_vendored.pandas.core.config_init as vendored_pandas_config + @dataclasses.dataclass class SamplingOptions: - """ - Encapsulates the configuration for data sampling. - """ + __doc__ = vendored_pandas_config.sampling_options_doc max_download_size: Optional[int] = 500 - """ - Download size threshold in MB. Default 500. - - If value set to None, the download size won't be checked. - - **Examples:** - - >>> import bigframes.pandas as bpd - >>> bpd.options.sampling.max_download_size = 1000 # doctest: +SKIP - """ - enable_downsampling: bool = False - """ - Whether to enable downsampling. Default False. - - If max_download_size is exceeded when downloading data (e.g., to_pandas()), - the data will be downsampled if enable_downsampling is True, otherwise, an - error will be raised. - - **Examples:** - - >>> import bigframes.pandas as bpd - >>> bpd.options.sampling.enable_downsampling = True # doctest: +SKIP - """ - sampling_method: Literal["head", "uniform"] = "uniform" - """ - Downsampling algorithms to be chosen from. Default "uniform". - - The choices are: "head": This algorithm returns a portion of the data from - the beginning. It is fast and requires minimal computations to perform the - downsampling.; "uniform": This algorithm returns uniform random samples of - the data. - - **Examples:** - - >>> import bigframes.pandas as bpd - >>> bpd.options.sampling.sampling_method = "head" # doctest: +SKIP - """ - random_state: Optional[int] = None - """ - The seed for the uniform downsampling algorithm. Default None. - - If provided, the uniform method may take longer to execute and require more - computation. - - **Examples:** - - >>> import bigframes.pandas as bpd - >>> bpd.options.sampling.random_state = 42 # doctest: +SKIP - """ - - def with_max_download_size(self, max_rows: Optional[int]) -> SamplingOptions: - """Configures the maximum download size for data sampling in MB - - Args: - max_rows (None or int): - An int value for the maximum row size. - - Returns: - bigframes._config.sampling_options.SamplingOptions: - The configuration for data sampling. - """ - return SamplingOptions( - max_rows, self.enable_downsampling, self.sampling_method, self.random_state - ) - - def with_method(self, method: Literal["head", "uniform"]) -> SamplingOptions: - """Configures the downsampling algorithms to be chosen from - - Args: - method (None or Literal): - A literal string value of either head or uniform data sampling method. - - Returns: - bigframes._config.sampling_options.SamplingOptions: - The configuration for data sampling. - """ - return SamplingOptions(self.max_download_size, True, method, self.random_state) - - def with_random_state(self, state: Optional[int]) -> SamplingOptions: - """Configures the seed for the uniform downsampling algorithm - - Args: - state (None or int): - An int value for the data sampling random state - - Returns: - bigframes._config.sampling_options.SamplingOptions: - The configuration for data sampling. - """ - return SamplingOptions( - self.max_download_size, - self.enable_downsampling, - self.sampling_method, - state, - ) - - def with_disabled(self) -> SamplingOptions: - """Configures whether to disable downsampling - - Returns: - bigframes._config.sampling_options.SamplingOptions: - The configuration for data sampling. - """ - return SamplingOptions( - self.max_download_size, False, self.sampling_method, self.random_state - ) diff --git a/bigframes/_importing.py b/bigframes/_importing.py deleted file mode 100644 index e88bd77fe86..00000000000 --- a/bigframes/_importing.py +++ /dev/null @@ -1,35 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -import importlib -from types import ModuleType - -import numpy -from packaging import version - -# Keep this in sync with setup.py -POLARS_MIN_VERSION = version.Version("1.7.0") - - -def import_polars() -> ModuleType: - polars_module = importlib.import_module("polars") - # Check for necessary methods instead of the version number because we - # can't trust the polars version until - # https://github.com/pola-rs/polars/issues/23940 is fixed. - try: - polars_module.lit(numpy.int64(100), dtype=polars_module.Int64()) - except TypeError: - raise ImportError( - f"Imported polars version is likely below the minimum version: {POLARS_MIN_VERSION}" - ) - return polars_module diff --git a/bigframes/_magics.py b/bigframes/_magics.py deleted file mode 100644 index f6b69f35ff5..00000000000 --- a/bigframes/_magics.py +++ /dev/null @@ -1,54 +0,0 @@ -# Copyright 2026 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 IPython.core import magic_arguments # type: ignore -from IPython.core.getipython import get_ipython -from IPython.display import display - -import bigframes.pandas - - -@magic_arguments.magic_arguments() -@magic_arguments.argument( - "destination_var", - nargs="?", - help=("If provided, save the output to this variable instead of displaying it."), -) -@magic_arguments.argument( - "--dry_run", - action="store_true", - default=False, - help=( - "Sets query to be a dry run to estimate costs. " - "Defaults to executing the query instead of dry run if this argument is not used." - "Does not work with engine 'bigframes'. " - ), -) -def _cell_magic(line, cell): - ipython = get_ipython() - if ipython is None: - raise RuntimeError("BigQuery magic must be run in an IPython environment.") - - args = magic_arguments.parse_argstring(_cell_magic, line) - if not cell: - print("Query is missing.") - return - pyformat_args = ipython.user_ns - dataframe = bigframes.pandas._read_gbq_colab( - cell, pyformat_args=pyformat_args, dry_run=args.dry_run - ) - if args.destination_var: - ipython.push({args.destination_var: dataframe}) - - display(dataframe) diff --git a/bigframes/_tools/__init__.py b/bigframes/_tools/__init__.py deleted file mode 100644 index ea3bc209d00..00000000000 --- a/bigframes/_tools/__init__.py +++ /dev/null @@ -1,19 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -"""_tools is a collection of helper functions with minimal dependencies. - -Please keep the dependencies used in this subpackage to a minimum to avoid the -risk of circular dependencies. -""" diff --git a/bigframes/_tools/docs.py b/bigframes/_tools/docs.py deleted file mode 100644 index 9ecfd61b3c9..00000000000 --- a/bigframes/_tools/docs.py +++ /dev/null @@ -1,53 +0,0 @@ -# Copyright 2026 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. - - -def inherit_docs(source_class): - """ - A class decorator that copies docstrings from source_class to the - decorated class for any methods or attributes that match names. - """ - - def decorator(target_class): - if not target_class.__doc__ and source_class.__doc__: - target_class.__doc__ = source_class.__doc__ - - for name, source_item in vars(source_class).items(): - if name in vars(target_class): - target_item = getattr(target_class, name) - - if hasattr(target_item, "__doc__") and not target_item.__doc__: - if hasattr(source_item, "__doc__") and source_item.__doc__: - try: - target_item.__doc__ = source_item.__doc__ - except AttributeError: - pass - - underlying = None - if isinstance(target_item, property): - underlying = target_item.fget - elif hasattr(target_item, "__func__"): - underlying = target_item.__func__ - elif hasattr(target_item, "func"): - underlying = getattr(target_item, "func", None) - - if underlying is not None: - try: - underlying.__doc__ = source_item.__doc__ - except AttributeError: - pass - - return target_class - - return decorator diff --git a/bigframes/_tools/strings.py b/bigframes/_tools/strings.py deleted file mode 100644 index 3d9402c68fb..00000000000 --- a/bigframes/_tools/strings.py +++ /dev/null @@ -1,66 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -"""Helper methods for processing strings with minimal dependencies. - -Please keep the dependencies used in this subpackage to a minimum to avoid the -risk of circular dependencies. -""" - -import numpy - - -def levenshtein_distance(left: str, right: str) -> int: - """Compute the edit distance between two strings. - - This is the minumum number of substitutions, insertions, deletions - to get from left string to right string. See: - https://en.wikipedia.org/wiki/Levenshtein_distance - """ - # TODO(tswast): accelerate with numba (if available) if we end up using this - # function in contexts other than when raising an exception or there are too - # many values to compare even in that context. - - distances0 = numpy.zeros(len(right) + 1) - distances1 = numpy.zeros(len(right) + 1) - - # Maximum distance is to drop all characters and then add the other string. - distances0[:] = range(len(right) + 1) - - for left_index in range(len(left)): - # Calculate distance from distances0 to distances1. - - # Edit distance is to delete (i + 1) chars from left to match empty right - distances1[0] = left_index + 1 - # "ab" - for right_index in range(len(right)): - left_char = left[left_index] - right_char = right[right_index] - - deletion_cost = distances0[right_index + 1] + 1 - insertion_cost = distances1[right_index] + 1 - if left_char == right_char: - substitution_cost = distances0[right_index] - else: - substitution_cost = distances0[right_index] + 1 - - distances1[right_index + 1] = min( - deletion_cost, insertion_cost, substitution_cost - ) - - temp = distances0 - distances0 = distances1 - distances1 = temp - - return distances0[len(right)] diff --git a/bigframes/bigquery/__init__.py b/bigframes/bigquery/__init__.py deleted file mode 100644 index ade7535c32b..00000000000 --- a/bigframes/bigquery/__init__.py +++ /dev/null @@ -1,339 +0,0 @@ -# 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. -# 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. - -""" -Access BigQuery-specific operations and namespaces within BigQuery DataFrames. - -This module provides specialized functions and sub-modules that expose BigQuery's -advanced capabilities to DataFrames and Series. It acts as a bridge between the -pandas-compatible API and the full power of BigQuery SQL. - -Key sub-modules include: - -* :mod:`bigframes.bigquery.ai`: Generative and predictive AI functions (Gemini, BQML). -* :mod:`bigframes.bigquery.ml`: Direct access to BigQuery ML model operations. -* :mod:`bigframes.bigquery.obj`: Support for BigQuery object tables. - -This module also provides direct access to optimized BigQuery functions for: - -* **JSON Processing:** High-performance functions like ``json_extract``, ``json_value``, - and ``parse_json`` for handling semi-structured data. -* **Geospatial Analysis:** Comprehensive geographic functions such as ``st_area``, - ``st_distance``, and ``st_centroid`` (``ST_`` prefixed functions). -* **Array Operations:** Tools for working with BigQuery arrays, including ``array_agg`` - and ``array_length``. -* **Vector Search:** Integration with BigQuery's vector search and indexing - capabilities for high-dimensional data. -* **Custom SQL:** The ``sql_scalar`` function allows embedding raw SQL snippets for - advanced operations not yet directly mapped in the API. - -By using these functions, you can leverage BigQuery's high-performance engine for -domain-specific tasks while maintaining a Python-centric development experience. - -For the full list of BigQuery standard SQL functions, see: -https://cloud.google.com/bigquery/docs/reference/standard-sql/functions-reference -""" - -import sys - -from bigframes.bigquery import aead, ai, ml, obj -from bigframes.bigquery._operations.approx_agg import approx_top_count -from bigframes.bigquery._operations.array import array_agg -from bigframes.bigquery._operations.datetime import ( - unix_micros, - unix_millis, - unix_seconds, -) -from bigframes.bigquery._operations.geo import ( - st_area, - st_buffer, - st_centroid, - st_convexhull, - st_difference, - st_distance, - st_intersection, - st_isclosed, - st_length, - st_regionstats, - st_simplify, -) -from bigframes.bigquery._operations.io import load_data -from bigframes.bigquery._operations.json import ( - json_extract, - json_extract_array, - json_extract_string_array, - json_keys, - json_query, - json_query_array, - json_set, - json_value, - json_value_array, - parse_json, - to_json, - to_json_string, -) -from bigframes.bigquery._operations.mathematical import ( - hparam_candidates, - hparam_range, - 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 -from bigframes.bigquery._operations.table import create_external_table -from bigframes.core.logging import log_adapter -from bigframes.operations.googlesql.global_namespace.aead_encryption import ( - deterministic_decrypt_bytes, - deterministic_decrypt_string, - deterministic_encrypt, -) -from bigframes.operations.googlesql.global_namespace.array import ( - array_concat, - array_first, - array_first_n, - array_includes, - array_includes_all, - array_includes_any, - array_is_distinct, - array_last, - array_length, - array_reverse, - array_slice, - array_to_string, - flatten, - generate_array, -) -from bigframes.operations.googlesql.global_namespace.bit import ( - bit_count, -) -from bigframes.operations.googlesql.global_namespace.conversion import ( - bool_, - double, - float64, - int64, - parse_bignumeric, - parse_numeric, - string, -) -from bigframes.operations.googlesql.global_namespace.date import ( - current_date, - date, - date_add, - date_diff, - date_from_unix_date, - date_sub, - date_trunc, - extract, - format_date, - generate_date_array, - last_day, - parse_date, - unix_date, -) - -_functions = [ - # approximate aggregate ops - approx_top_count, - # array ops - array_agg, - array_concat, - array_first, - array_first_n, - array_includes, - array_includes_all, - array_includes_any, - array_is_distinct, - array_last, - array_length, - array_reverse, - array_slice, - array_to_string, - flatten, - generate_array, - # bit ops - bit_count, - # conversion ops - bool_, - double, - float64, - int64, - parse_bignumeric, - parse_numeric, - string, - # date ops - current_date, - date, - date_add, - date_diff, - date_from_unix_date, - date_sub, - date_trunc, - extract, - format_date, - generate_date_array, - last_day, - parse_date, - unix_date, - # datetime ops - unix_micros, - unix_millis, - unix_seconds, - # geo ops - st_area, - st_buffer, - st_centroid, - st_convexhull, - st_difference, - st_distance, - st_intersection, - st_isclosed, - st_length, - st_regionstats, - st_simplify, - # deterministic encryption ops - deterministic_decrypt_bytes, - deterministic_decrypt_string, - deterministic_encrypt, - # json ops - json_extract, - json_extract_array, - json_extract_string_array, - json_query, - json_query_array, - json_set, - json_value, - json_value_array, - parse_json, - to_json, - to_json_string, - # mathematical ops - hparam_candidates, - hparam_range, - rand, - # search ops - create_vector_index, - vector_search, - # sql ops - sql_scalar, - # struct ops - struct, - # table ops - create_external_table, - # io ops - load_data, -] - -_module = sys.modules[__name__] -for f in _functions: - _decorated_object = log_adapter.method_logger(f, custom_base_name="bigquery") - setattr(_module, f.__name__, _decorated_object) - del f - -__all__ = [ - # approximate aggregate ops - "approx_top_count", - # array ops - "array_agg", - "array_concat", - "array_first", - "array_first_n", - "array_includes", - "array_includes_all", - "array_includes_any", - "array_is_distinct", - "array_last", - "array_length", - "array_reverse", - "array_slice", - "array_to_string", - "flatten", - "generate_array", - # bit ops - "bit_count", - # conversion ops - "bool_", - "double", - "float64", - "int64", - "parse_bignumeric", - "parse_numeric", - "string", - # date ops - "current_date", - "date", - "date_add", - "date_diff", - "date_from_unix_date", - "date_sub", - "date_trunc", - "extract", - "format_date", - "generate_date_array", - "last_day", - "parse_date", - "unix_date", - # datetime ops - "unix_micros", - "unix_millis", - "unix_seconds", - # geo ops - "st_area", - "st_buffer", - "st_centroid", - "st_convexhull", - "st_difference", - "st_distance", - "st_intersection", - "st_isclosed", - "st_length", - "st_regionstats", - "st_simplify", - # deterministic encryption ops - "deterministic_decrypt_bytes", - "deterministic_decrypt_string", - "deterministic_encrypt", - # json ops - "json_extract", - "json_extract_array", - "json_extract_string_array", - "json_keys", - "json_query", - "json_query_array", - "json_set", - "json_value", - "json_value_array", - "parse_json", - "to_json", - "to_json_string", - # mathematical ops - "hparam_candidates", - "hparam_range", - "rand", - # search ops - "create_vector_index", - "vector_search", - # sql ops - "sql_scalar", - # struct ops - "struct", - # table ops - "create_external_table", - # io ops - "load_data", - # Modules / SQL namespaces - "aead", - "ai", - "ml", - "obj", -] diff --git a/bigframes/bigquery/_operations/__init__.py b/bigframes/bigquery/_operations/__init__.py deleted file mode 100644 index 6d5e14bcf4a..00000000000 --- a/bigframes/bigquery/_operations/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -# 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. -# 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/bigframes/bigquery/_operations/ai.py b/bigframes/bigquery/_operations/ai.py deleted file mode 100644 index 40d5556de40..00000000000 --- a/bigframes/bigquery/_operations/ai.py +++ /dev/null @@ -1,1254 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -"""This module integrates BigQuery built-in AI functions for use with Series/DataFrame objects, -such as AI.GENERATE_BOOL: -https://cloud.google.com/bigquery/docs/reference/standard-sql/bigqueryml-syntax-ai-generate-bool""" - -from __future__ import annotations - -import json -from typing import Any, Dict, Iterable, List, Literal, Mapping, Optional, Tuple, Union - -import pandas as pd - -from bigframes import dataframe, dtypes, series, session -from bigframes import pandas as bpd -from bigframes.bigquery._operations import obj as bq_obj -from bigframes.bigquery._operations import utils as bq_utils -from bigframes.core import convert -from bigframes.core.compile.sqlglot import sql as sg_sql -from bigframes.core.logging import log_adapter -from bigframes.ml import base as ml_base -from bigframes.ml import core as ml_core -from bigframes.operations import ai_ops, output_schemas - -PROMPT_TYPE = Union[ - str, - series.Series, - pd.Series, - List[Union[str, series.Series, pd.Series]], - Tuple[Union[str, series.Series, pd.Series], ...], -] - - -@log_adapter.method_logger(custom_base_name="bigquery_ai") -def generate( - prompt: PROMPT_TYPE, - *, - connection_id: str | None = None, - endpoint: str | None = None, - request_type: Literal["dedicated", "shared", "unspecified"] | None = None, - model_params: Mapping[Any, Any] | None = None, - output_schema: Mapping[str, str] | None = None, -) -> series.Series: - """ - Returns the AI analysis based on the prompt, which can be any combination of text and unstructured data. - - **Examples:** - - >>> import bigframes.pandas as bpd - >>> import bigframes.bigquery as bbq - >>> country = bpd.Series(["Japan", "Canada"]) - >>> bbq.ai.generate(("What's the capital city of ", country, " one word only")) # doctest: +ELLIPSIS - 0 {'result': 'Tokyo', 'full_response': '{"cand... - 1 {'result': 'Ottawa', 'full_response': '{"can... - dtype: struct>, status: string>[pyarrow] - - >>> bbq.ai.generate(("What's the capital city of ", country, " one word only")).struct.field("result") - 0 Tokyo - 1 Ottawa - Name: result, dtype: string - - You get structured output when the ``output_schema`` parameter is set: - - >>> animals = bpd.Series(["Rabbit", "Spider"]) - >>> bbq.ai.generate(animals, output_schema={"number_of_legs": "INT64", "is_herbivore": "BOOL"}) - 0 {'is_herbivore': True, 'number_of_legs': 4, 'f... - 1 {'is_herbivore': False, 'number_of_legs': 8, '... - dtype: struct>, status: string>[pyarrow] - - Args: - prompt (str | Series | List[str|Series] | Tuple[str|Series, ...]): - A mixture of Series and string literals that specifies the prompt to send to the model. The Series can be BigFrames Series - or pandas Series. - connection_id (str, optional): - Specifies the connection to use to communicate with the model. For example, ``myproject.us.myconnection``. - If not provided, the query uses your end-user credential. - endpoint (str, optional): - Specifies the Vertex AI endpoint to use for the model. For example ``"gemini-2.5-flash"``. You can specify any - generally available or preview Gemini model. If you specify the model name, BigQuery ML automatically identifies and - uses the full endpoint of the model. If you don't specify an ENDPOINT value, BigQuery ML selects a recent stable - version of Gemini to use. - request_type (Literal["dedicated", "shared", "unspecified"]): - Specifies the type of inference request to send to the Gemini model. The request type determines what quota the request uses. - * "dedicated": function only uses Provisioned Throughput quota. The function returns the error Provisioned throughput is not - purchased or is not active if Provisioned Throughput quota isn't available. - * "shared": the function only uses dynamic shared quota (DSQ), even if you have purchased Provisioned Throughput quota. - * "unspecified": If you haven't purchased Provisioned Throughput quota, the function uses DSQ quota. - If you have purchased Provisioned Throughput quota, the function uses the Provisioned Throughput quota first. - If requests exceed the Provisioned Throughput quota, the overflow traffic uses DSQ quota. - model_params (Mapping[Any, Any]): - Provides additional parameters to the model. The MODEL_PARAMS value must conform to the generateContent request body format. - output_schema (Mapping[str, str]): - A mapping value that specifies the schema of the output, in the form {field_name: data_type}. Supported data types include - ``STRING``, ``INT64``, ``FLOAT64``, ``BOOL``, ``ARRAY``, and ``STRUCT``. - - Returns: - bigframes.series.Series: A new struct Series with the result data. The struct contains these fields: - * "result": a STRING value containing the model's response to the prompt. The result is None if the request fails or is filtered by responsible AI. - If you specify an output schema then result is replaced by your custom schema. - * "full_response": a JSON value containing the response from the projects.locations.endpoints.generateContent call to the model. - The generated text is in the text element. - * "status": a STRING value that contains the API response status for the corresponding row. This value is empty if the operation was successful. - """ - - prompt_context, series_list = _separate_context_and_series(prompt) - assert len(series_list) > 0 - - if output_schema is None: - output_schema_str = None - else: - output_schema_str = ", ".join( - [f"{name} {sql_type}" for name, sql_type in output_schema.items()] - ) - # Validate user input - output_schemas.parse_sql_fields(output_schema_str) - - operator = ai_ops.AIGenerate( - prompt_context=tuple(prompt_context), - connection_id=connection_id, - endpoint=endpoint, - request_type=_upper_optional(request_type), - model_params=json.dumps(model_params) if model_params else None, - output_schema=output_schema_str, - ) - - return series_list[0]._apply_nary_op(operator, series_list[1:]) - - -@log_adapter.method_logger(custom_base_name="bigquery_ai") -def generate_bool( - prompt: PROMPT_TYPE, - *, - connection_id: str | None = None, - endpoint: str | None = None, - request_type: Literal["dedicated", "shared", "unspecified"] | None = None, - model_params: Mapping[Any, Any] | None = None, -) -> series.Series: - """ - Returns the AI analysis based on the prompt, which can be any combination of text and unstructured data. - - **Examples:** - - >>> import bigframes.pandas as bpd - >>> import bigframes.bigquery as bbq - >>> df = bpd.DataFrame({ - ... "col_1": ["apple", "bear", "pear"], - ... "col_2": ["fruit", "animal", "animal"] - ... }) - >>> bbq.ai.generate_bool((df["col_1"], " is a ", df["col_2"])) - 0 {'result': True, 'full_response': '{"candidate... - 1 {'result': True, 'full_response': '{"candidate... - 2 {'result': False, 'full_response': '{"candidat... - dtype: struct>, status: string>[pyarrow] - - >>> bbq.ai.generate_bool((df["col_1"], " is a ", df["col_2"])).struct.field("result") - 0 True - 1 True - 2 False - Name: result, dtype: boolean - - Args: - prompt (str | Series | List[str|Series] | Tuple[str|Series, ...]): - A mixture of Series and string literals that specifies the prompt to send to the model. The Series can be BigFrames Series - or pandas Series. - connection_id (str, optional): - Specifies the connection to use to communicate with the model. For example, ``myproject.us.myconnection``. - If not provided, the query uses your end-user credential. - endpoint (str, optional): - Specifies the Vertex AI endpoint to use for the model. For example ``"gemini-2.5-flash"``. You can specify any - generally available or preview Gemini model. If you specify the model name, BigQuery ML automatically identifies and - uses the full endpoint of the model. If you don't specify an ENDPOINT value, BigQuery ML selects a recent stable - version of Gemini to use. - request_type (Literal["dedicated", "shared", "unspecified"]): - Specifies the type of inference request to send to the Gemini model. The request type determines what quota the request uses. - * "dedicated": function only uses Provisioned Throughput quota. The function returns the error Provisioned throughput is not - purchased or is not active if Provisioned Throughput quota isn't available. - * "shared": the function only uses dynamic shared quota (DSQ), even if you have purchased Provisioned Throughput quota. - * "unspecified": If you haven't purchased Provisioned Throughput quota, the function uses DSQ quota. - If you have purchased Provisioned Throughput quota, the function uses the Provisioned Throughput quota first. - If requests exceed the Provisioned Throughput quota, the overflow traffic uses DSQ quota. - model_params (Mapping[Any, Any]): - Provides additional parameters to the model. The MODEL_PARAMS value must conform to the generateContent request body format. - - Returns: - bigframes.series.Series: A new struct Series with the result data. The struct contains these fields: - * "result": a BOOL value containing the model's response to the prompt. The result is None if the request fails or is filtered by responsible AI. - * "full_response": a JSON value containing the response from the projects.locations.endpoints.generateContent call to the model. - The generated text is in the text element. - * "status": a STRING value that contains the API response status for the corresponding row. This value is empty if the operation was successful. - """ - - prompt_context, series_list = _separate_context_and_series(prompt) - assert len(series_list) > 0 - - operator = ai_ops.AIGenerateBool( - prompt_context=tuple(prompt_context), - connection_id=connection_id, - endpoint=endpoint, - request_type=_upper_optional(request_type), - model_params=json.dumps(model_params) if model_params else None, - ) - - return series_list[0]._apply_nary_op(operator, series_list[1:]) - - -@log_adapter.method_logger(custom_base_name="bigquery_ai") -def generate_int( - prompt: PROMPT_TYPE, - *, - connection_id: str | None = None, - endpoint: str | None = None, - request_type: Literal["dedicated", "shared", "unspecified"] | None = None, - model_params: Mapping[Any, Any] | None = None, -) -> series.Series: - """ - Returns the AI analysis based on the prompt, which can be any combination of text and unstructured data. - - **Examples:** - - >>> import bigframes.pandas as bpd - >>> import bigframes.bigquery as bbq - >>> animal = bpd.Series(["Ostrich", "Rabbit", "Spider"]) - >>> bbq.ai.generate_int(("How many legs does a ", animal, " have?")) # doctest: +ELLIPSIS - 0 {'result': 2, 'full_response': '{"candidates":... - 1 {'result': 4, 'full_response': '{"candidates":... - 2 {'result': 8, 'full_response': '{"candidates":... - dtype: struct>, status: string>[pyarrow] - - >>> bbq.ai.generate_int(("How many legs does a ", animal, " have?")).struct.field("result") - 0 2 - 1 4 - 2 8 - Name: result, dtype: Int64 - - Args: - prompt (str | Series | List[str|Series] | Tuple[str|Series, ...]): - A mixture of Series and string literals that specifies the prompt to send to the model. The Series can be BigFrames Series - or pandas Series. - connection_id (str, optional): - Specifies the connection to use to communicate with the model. For example, ``myproject.us.myconnection``. - If not provided, the query uses your end-user credential. - endpoint (str, optional): - Specifies the Vertex AI endpoint to use for the model. For example ``"gemini-2.5-flash"``. You can specify any - generally available or preview Gemini model. If you specify the model name, BigQuery ML automatically identifies and - uses the full endpoint of the model. If you don't specify an ENDPOINT value, BigQuery ML selects a recent stable - version of Gemini to use. - request_type (Literal["dedicated", "shared", "unspecified"]): - Specifies the type of inference request to send to the Gemini model. The request type determines what quota the request uses. - * "dedicated": function only uses Provisioned Throughput quota. The function returns the error Provisioned throughput is not - purchased or is not active if Provisioned Throughput quota isn't available. - * "shared": the function only uses dynamic shared quota (DSQ), even if you have purchased Provisioned Throughput quota. - * "unspecified": If you haven't purchased Provisioned Throughput quota, the function uses DSQ quota. - If you have purchased Provisioned Throughput quota, the function uses the Provisioned Throughput quota first. - If requests exceed the Provisioned Throughput quota, the overflow traffic uses DSQ quota. - model_params (Mapping[Any, Any]): - Provides additional parameters to the model. The MODEL_PARAMS value must conform to the generateContent request body format. - - Returns: - bigframes.series.Series: A new struct Series with the result data. The struct contains these fields: - * "result": an integer (INT64) value containing the model's response to the prompt. The result is None if the request fails or is filtered by responsible AI. - * "full_response": a JSON value containing the response from the projects.locations.endpoints.generateContent call to the model. - The generated text is in the text element. - * "status": a STRING value that contains the API response status for the corresponding row. This value is empty if the operation was successful. - """ - - prompt_context, series_list = _separate_context_and_series(prompt) - assert len(series_list) > 0 - - operator = ai_ops.AIGenerateInt( - prompt_context=tuple(prompt_context), - connection_id=connection_id, - endpoint=endpoint, - request_type=_upper_optional(request_type), - model_params=json.dumps(model_params) if model_params else None, - ) - - return series_list[0]._apply_nary_op(operator, series_list[1:]) - - -@log_adapter.method_logger(custom_base_name="bigquery_ai") -def generate_double( - prompt: PROMPT_TYPE, - *, - connection_id: str | None = None, - endpoint: str | None = None, - request_type: Literal["dedicated", "shared", "unspecified"] | None = None, - model_params: Mapping[Any, Any] | None = None, -) -> series.Series: - """ - Returns the AI analysis based on the prompt, which can be any combination of text and unstructured data. - - **Examples:** - - >>> import bigframes.pandas as bpd - >>> import bigframes.bigquery as bbq - >>> animal = bpd.Series(["Ostrich", "Rabbit", "Spider"]) - >>> bbq.ai.generate_double(("How many legs does a ", animal, " have?")) # doctest: +ELLIPSIS - 0 {'result': 2.0, 'full_response': '{"candidates... - 1 {'result': 4.0, 'full_response': '{"candidates... - 2 {'result': 8.0, 'full_response': '{"candidates... - dtype: struct>, status: string>[pyarrow] - - >>> bbq.ai.generate_double(("How many legs does a ", animal, " have?")).struct.field("result") - 0 2.0 - 1 4.0 - 2 8.0 - Name: result, dtype: Float64 - - Args: - prompt (str | Series | List[str|Series] | Tuple[str|Series, ...]): - A mixture of Series and string literals that specifies the prompt to send to the model. The Series can be BigFrames Series - or pandas Series. - connection_id (str, optional): - Specifies the connection to use to communicate with the model. For example, ``myproject.us.myconnection``. - If not provided, the query uses your end-user credential. - endpoint (str, optional): - Specifies the Vertex AI endpoint to use for the model. For example ``"gemini-2.5-flash"``. You can specify any - generally available or preview Gemini model. If you specify the model name, BigQuery ML automatically identifies and - uses the full endpoint of the model. If you don't specify an ENDPOINT value, BigQuery ML selects a recent stable - version of Gemini to use. - request_type (Literal["dedicated", "shared", "unspecified"]): - Specifies the type of inference request to send to the Gemini model. The request type determines what quota the request uses. - * "dedicated": function only uses Provisioned Throughput quota. The function returns the error Provisioned throughput is not - purchased or is not active if Provisioned Throughput quota isn't available. - * "shared": the function only uses dynamic shared quota (DSQ), even if you have purchased Provisioned Throughput quota. - * "unspecified": If you haven't purchased Provisioned Throughput quota, the function uses DSQ quota. - If you have purchased Provisioned Throughput quota, the function uses the Provisioned Throughput quota first. - If requests exceed the Provisioned Throughput quota, the overflow traffic uses DSQ quota. - model_params (Mapping[Any, Any]): - Provides additional parameters to the model. The MODEL_PARAMS value must conform to the generateContent request body format. - - Returns: - bigframes.series.Series: A new struct Series with the result data. The struct contains these fields: - * "result": an DOUBLE value containing the model's response to the prompt. The result is None if the request fails or is filtered by responsible AI. - * "full_response": a JSON value containing the response from the projects.locations.endpoints.generateContent call to the model. - The generated text is in the text element. - * "status": a STRING value that contains the API response status for the corresponding row. This value is empty if the operation was successful. - """ - - prompt_context, series_list = _separate_context_and_series(prompt) - assert len(series_list) > 0 - - operator = ai_ops.AIGenerateDouble( - prompt_context=tuple(prompt_context), - connection_id=connection_id, - endpoint=endpoint, - request_type=_upper_optional(request_type), - model_params=json.dumps(model_params) if model_params else None, - ) - - return series_list[0]._apply_nary_op(operator, series_list[1:]) - - -@log_adapter.method_logger(custom_base_name="bigquery_ai") -def generate_embedding( - model: Union[ml_base.BaseEstimator, str, pd.Series], - data: Union[dataframe.DataFrame, series.Series, pd.DataFrame, pd.Series], - *, - output_dimensionality: Optional[int] = None, - task_type: Optional[str] = None, - start_second: Optional[float] = None, - end_second: Optional[float] = None, - interval_seconds: Optional[float] = None, - trial_id: Optional[int] = None, -) -> dataframe.DataFrame: - """ - Creates embeddings that describe an entity—for example, a piece of text or an image. - - **Examples:** - - >>> import bigframes.pandas as bpd - >>> import bigframes.bigquery as bbq - >>> df = bpd.DataFrame({"content": ["apple", "bear", "pear"]}) - >>> bbq.ai.generate_embedding( # doctest: +SKIP - ... "project.dataset.model_name", - ... df - ... ) - - Args: - model (ml_base.BaseEstimator or str): - The model to use for text embedding. - data (bigframes.pandas.DataFrame or bigframes.pandas.Series): - The data to generate embeddings for. If a Series is provided, it is - treated as the 'content' column. If a DataFrame is provided, it - must contain a 'content' column, or you must rename the column you - wish to embed to 'content'. - output_dimensionality (int, optional): - An INT64 value that specifies the number of dimensions to use when - generating embeddings. For example, if you specify 256 AS - output_dimensionality, then the embedding output column contains a - 256-dimensional embedding for each input value. To find the - supported range of output dimensions, read about the available - `Google text embedding models `_. - task_type (str, optional): - A STRING literal that specifies the intended downstream application to - help the model produce better quality embeddings. For a list of - supported task types and how to choose which one to use, see `Choose an - embeddings task type `_. - start_second (float, optional): - The second in the video at which to start the embedding. The default value is 0. - end_second (float, optional): - The second in the video at which to end the embedding. The default value is 120. - interval_seconds (float, optional): - The interval to use when creating embeddings. The default value is 16. - trial_id (int, optional): - An INT64 value that identifies the hyperparameter tuning trial that - you want the function to evaluate. The function uses the optimal - trial by default. Only specify this argument if you ran - hyperparameter tuning when creating the model. - - Returns: - bigframes.pandas.DataFrame: - A new DataFrame with the generated embeddings. See the `SQL - reference for AI.GENERATE_EMBEDDING - `_ - for details. - """ - data = _to_dataframe(data, series_rename="content") - model_name, session = bq_utils.get_model_name_and_session(model, data) - table_sql = bq_utils.to_sql(data) - - struct_fields: Dict[str, Any] = {} - if output_dimensionality is not None: - struct_fields["OUTPUT_DIMENSIONALITY"] = output_dimensionality - if task_type is not None: - struct_fields["TASK_TYPE"] = task_type - if start_second is not None: - struct_fields["START_SECOND"] = start_second - if end_second is not None: - struct_fields["END_SECOND"] = end_second - if interval_seconds is not None: - struct_fields["INTERVAL_SECONDS"] = interval_seconds - if trial_id is not None: - struct_fields["TRIAL_ID"] = trial_id - - # Construct the TVF query - query = f""" - SELECT * - FROM AI.GENERATE_EMBEDDING( - MODEL `{model_name}`, - ({table_sql}), - {sg_sql.to_sql(sg_sql.literal(struct_fields))} - ) - """ - - if session is None: - return bpd.read_gbq_query(query) - else: - return session.read_gbq_query(query) - - -@log_adapter.method_logger(custom_base_name="bigquery_ai") -def generate_text( - model: Union[ml_base.BaseEstimator, str, pd.Series], - data: Union[dataframe.DataFrame, series.Series, pd.DataFrame, pd.Series], - *, - temperature: Optional[float] = None, - max_output_tokens: Optional[int] = None, - top_k: Optional[int] = None, - top_p: Optional[float] = None, - stop_sequences: Optional[List[str]] = None, - ground_with_google_search: Optional[bool] = None, - request_type: Optional[str] = None, -) -> dataframe.DataFrame: - """ - Generates text using a BigQuery ML model. - - See the `BigQuery ML GENERATE_TEXT function syntax - `_ - for additional reference. - - **Examples:** - - >>> import bigframes.pandas as bpd - >>> import bigframes.bigquery as bbq - >>> df = bpd.DataFrame({"prompt": ["write a poem about apples"]}) - >>> bbq.ai.generate_text( # doctest: +SKIP - ... "project.dataset.model_name", - ... df - ... ) - - Args: - model (ml_base.BaseEstimator or str): - The model to use for text generation. - data (bigframes.pandas.DataFrame or bigframes.pandas.Series): - The data to generate text for. If a Series is provided, it is - treated as the 'prompt' column. If a DataFrame is provided, it - must contain a 'prompt' column, or you must rename the column you - wish to generate text to 'prompt'. - temperature (float, optional): - A FLOAT64 value that is used for sampling promiscuity. The value - must be in the range ``[0.0, 1.0]``. A lower temperature works well - for prompts that expect a more deterministic and less open-ended - or creative response, while a higher temperature can lead to more - diverse or creative results. A temperature of ``0`` is - deterministic, meaning that the highest probability response is - always selected. - max_output_tokens (int, optional): - An INT64 value that sets the maximum number of tokens in the - generated text. - top_k (int, optional): - An INT64 value that changes how the model selects tokens for - output. A ``top_k`` of ``1`` means the next selected token is the - most probable among all tokens in the model's vocabulary. A - ``top_k`` of ``3`` means that the next token is selected from - among the three most probable tokens by using temperature. The - default value is ``40``. - top_p (float, optional): - A FLOAT64 value that changes how the model selects tokens for - output. Tokens are selected from most probable to least probable - until the sum of their probabilities equals the ``top_p`` value. - For example, if tokens A, B, and C have a probability of 0.3, 0.2, - and 0.1 and the ``top_p`` value is ``0.5``, then the model will - select either A or B as the next token by using temperature. The - default value is ``0.95``. - stop_sequences (List[str], optional): - An ARRAY value that contains the stop sequences for the model. - ground_with_google_search (bool, optional): - A BOOL value that determines whether to ground the model with Google Search. - request_type (str, optional): - A STRING value that contains the request type for the model. - - Returns: - bigframes.pandas.DataFrame: - The generated text. - """ - data = _to_dataframe(data, series_rename="prompt") - model_name, session = bq_utils.get_model_name_and_session(model, data) - table_sql = bq_utils.to_sql(data) - - struct_fields: Dict[ - str, - Union[str, int, float, bool, Mapping[str, str], List[str], Mapping[str, Any]], - ] = {} - if temperature is not None: - struct_fields["TEMPERATURE"] = temperature - if max_output_tokens is not None: - struct_fields["MAX_OUTPUT_TOKENS"] = max_output_tokens - if top_k is not None: - struct_fields["TOP_K"] = top_k - if top_p is not None: - struct_fields["TOP_P"] = top_p - if stop_sequences is not None: - struct_fields["STEP_SEQUENCES"] = stop_sequences - if ground_with_google_search is not None: - struct_fields["GROUND_WITH_GOOGLE_SEARCH"] = ground_with_google_search - if request_type is not None: - struct_fields["REQUEST_TYPE"] = request_type - - query = f""" - SELECT * - FROM AI.GENERATE_TEXT( - MODEL `{model_name}`, - ({table_sql}), - {sg_sql.to_sql(sg_sql.literal(struct_fields))} - ) - """ - - if session is None: - return bpd.read_gbq_query(query) - else: - return session.read_gbq_query(query) - - -@log_adapter.method_logger(custom_base_name="bigquery_ai") -def generate_table( - model: Union[ml_base.BaseEstimator, str, pd.Series], - data: Union[dataframe.DataFrame, series.Series, pd.DataFrame, pd.Series], - *, - output_schema: Union[str, Mapping[str, str]], - temperature: Optional[float] = None, - top_p: Optional[float] = None, - max_output_tokens: Optional[int] = None, - stop_sequences: Optional[List[str]] = None, - request_type: Optional[str] = None, -) -> dataframe.DataFrame: - """ - Generates a table using a BigQuery ML model. - - See the `AI.GENERATE_TABLE function syntax - `_ - for additional reference. - - **Examples:** - - >>> import bigframes.pandas as bpd - >>> import bigframes.bigquery as bbq - >>> # The user is responsible for constructing a DataFrame that contains - >>> # the necessary columns for the model's prompt. For example, a - >>> # DataFrame with a 'prompt' column for text classification. - >>> df = bpd.DataFrame({'prompt': ["some text to classify"]}) - >>> result = bbq.ai.generate_table( # doctest: +SKIP - ... "project.dataset.model_name", - ... data=df, - ... output_schema="category STRING" - ... ) - - Args: - model (ml_base.BaseEstimator or str): - The model to use for table generation. - data (bigframes.pandas.DataFrame or bigframes.pandas.Series): - The data to generate table for. If a Series is provided, it is - treated as the 'prompt' column. If a DataFrame is provided, it - must contain a 'prompt' column, or you must rename the column you - wish to generate table to 'prompt'. - output_schema (str | Mapping[str, str]): - A string defining the output schema (e.g., "col1 STRING, col2 INT64"), - or a mapping value that specifies the schema of the output, in the form {field_name: data_type}. - Supported data types include ``STRING``, ``INT64``, ``FLOAT64``, ``BOOL``, ``ARRAY``, and ``STRUCT``. - temperature (float, optional): - A FLOAT64 value that is used for sampling promiscuity. The value - must be in the range ``[0.0, 1.0]``. - top_p (float, optional): - A FLOAT64 value that changes how the model selects tokens for - output. - max_output_tokens (int, optional): - An INT64 value that sets the maximum number of tokens in the - generated table. - stop_sequences (List[str], optional): - An ARRAY value that contains the stop sequences for the model. - request_type (str, optional): - A STRING value that contains the request type for the model. - - Returns: - bigframes.pandas.DataFrame: - The generated table. - """ - data = _to_dataframe(data, series_rename="prompt") - model_name, session = bq_utils.get_model_name_and_session(model, data) - table_sql = bq_utils.to_sql(data) - - if isinstance(output_schema, Mapping): - output_schema_str = ", ".join( - [f"{name} {sql_type}" for name, sql_type in output_schema.items()] - ) - # Validate user input - output_schemas.parse_sql_fields(output_schema_str) - else: - output_schema_str = output_schema - - struct_fields_bq: Dict[str, Any] = {"output_schema": output_schema_str} - if temperature is not None: - struct_fields_bq["temperature"] = temperature - if top_p is not None: - struct_fields_bq["top_p"] = top_p - if max_output_tokens is not None: - struct_fields_bq["max_output_tokens"] = max_output_tokens - if stop_sequences is not None: - struct_fields_bq["stop_sequences"] = stop_sequences - if request_type is not None: - struct_fields_bq["request_type"] = request_type - - struct_sql = sg_sql.to_sql(sg_sql.literal(struct_fields_bq)) - query = f""" - SELECT * - FROM AI.GENERATE_TABLE( - MODEL `{model_name}`, - ({table_sql}), - {struct_sql} - ) - """ - - if session is None: - return bpd.read_gbq_query(query) - else: - return session.read_gbq_query(query) - - -@log_adapter.method_logger(custom_base_name="bigquery_ai") -def embed( - content: str | series.Series | pd.Series, - *, - endpoint: str | None = None, - model: str | None = None, - task_type: ( - Literal[ - "retrieval_query", - "retrieval_document", - "semantic_similarity", - "classification", - "clustering", - "question_answering", - "fact_verification", - "code_retrieval_query", - ] - | None - ) = None, - title: str | None = None, - model_params: Mapping[Any, Any] | None = None, - connection_id: str | None = None, -) -> series.Series: - """ - Creates embeddings from text or image data in BigQuery. - - **Examples:** - - >>> import bigframes.pandas as bpd - >>> import bigframes.bigquery as bbq - >>> bbq.ai.embed("dog", endpoint="text-embedding-005") # doctest: +ELLIPSIS - 0 {'result': array([ 1.78243860e-03, -1.10658340... - dtype: struct, status: string>[pyarrow] - - >>> s = bpd.Series(['dog']) - >>> bbq.ai.embed(s, endpoint='text-embedding-005') # doctest: +ELLIPSIS - 0 {'result': array([ 1.78243860e-03, -1.10658340... - dtype: struct, status: string>[pyarrow] - - Args: - content (str | Series): - A string literal or a Series (either BigFrames series or pandas Series) that provides the text or image to embed. - endpoint (str, optional): - A string value that specifies a supported Vertex AI embedding model endpoint to use. - The endpoint value that you specify must include the model version, for example, - ``"text-embedding-005"``. If you specify this parameter, you can't specify the - ``model`` parameter. - model (str, optional): - A string value that specifies a built-in embedding model. The only supported value is - ``"embeddinggemma-300m"``. If you specify this parameter, you can't specify the ``endpoint``, - ``title``, ``model_params``, or ``connection_id`` parameters. - task_type (str, optional): - A string literal that specifies the intended downstream application to help the model - produce better quality embeddings. Accepts ``"retrieval_query"``, ``"retrieval_document"``, - ``"semantic_similarity"``, ``"classification"``, ``"clustering"``, ``"question_answering"``, - ``"fact_verification"``, ``"code_retrieval_query"``. - title (str, optional): - A string value that specifies the document title, which the model uses to improve - embedding quality. You can only use this parameter if you specify ``"retrieval_document"`` - for the ``task_type`` value. - model_params (Mapping[Any, Any], optional): - A JSON literal that provides additional parameters to the model. For example, - ``{"outputDimensionality": 768}`` lets you specify the number of dimensions to use when - generating embeddings. - connection_id (str, optional): - A STRING value specifying the connection to use to communicate with the model, in the - format ``PROJECT_ID.LOCATION.CONNECTION_ID``. For example, ``myproject.us.myconnection``. - If not provided, the query uses your end-user credential. - - Returns: - bigframes.series.Series: A new struct Series with the result data. The struct contains these fields: - * "result": an ARRAY value containing the generated embeddings. - * "status": a STRING value that contains the API response status for the corresponding row. This value is empty if the operation was successful. - """ - - operator = ai_ops.AIEmbed( - endpoint=endpoint, - model=model, - task_type=_upper_optional(task_type), - title=title, - model_params=json.dumps(model_params) if model_params else None, - connection_id=connection_id, - ) - - if isinstance(content, str): - return series.Series([content])._apply_unary_op(operator) - elif isinstance(content, pd.Series): - return series.Series(content)._apply_unary_op(operator) - elif isinstance(content, series.Series): - return content._apply_unary_op(operator) - else: - raise ValueError(f"Unsupported 'content' parameter type: {type(content)}") - - -@log_adapter.method_logger(custom_base_name="bigquery_ai") -def if_( - prompt: PROMPT_TYPE, - *, - connection_id: str | None = None, - endpoint: str | None = None, - optimization_mode: Literal["minimize_cost", "maximize_quality"] | None = None, - max_error_ratio: float | None = None, -) -> series.Series: - """ - Evaluates the prompt to True or False. Compared to ``ai.generate_bool()``, this function - provides optimization such that not all rows are evaluated with the LLM. - - **Examples:** - - >>> import bigframes.pandas as bpd - >>> import bigframes.bigquery as bbq - >>> us_state = bpd.Series(["Massachusetts", "Illinois", "Hawaii"]) - >>> bbq.ai.if_((us_state, " has a city called Springfield")) - 0 True - 1 True - 2 False - dtype: boolean - - >>> us_state[bbq.ai.if_((us_state, " has a city called Springfield"))] - 0 Massachusetts - 1 Illinois - dtype: string - - Args: - prompt (str | Series | List[str|Series] | Tuple[str|Series, ...]): - A mixture of Series and string literals that specifies the prompt to send to the model. The Series can be BigFrames Series - or pandas Series. - connection_id (str, optional): - Specifies the connection to use to communicate with the model. For example, ``myproject.us.myconnection``. - If not provided, the query uses your end-user credential. - endpoint (str, optional): - Specifies the Vertex AI endpoint to use for the model. For example ``"gemini-2.5-flash"``. You can specify any - generally available or preview Gemini model. If you specify the model name, BigQuery ML automatically identifies and - uses the full endpoint of the model. If you don't specify an ENDPOINT value, BigQuery ML dynamically chooses a model based on your query to have the - best cost to quality tradeoff for the task. - optimization_mode (Literal["minimize_cost", "maximize_quality"]): - Specifies the optimization strategy to use. Supported values are: - * "minimize_cost" (default): uses a local, distilled model to process the majority of rows, reducing latency and cost. - * "maximize_quality": always uses the remote LLM for inference. - max_error_ratio (float): - A float value between 0.0 and 1.0 that contains the maximum acceptable ratio of row-level inference failures to - rows processed on this function. If this value is exceeded, then the query fails. The default value is 1.0. - This argument isn't supported when ``optimization_mode`` is set to "minimize_cost". - - Returns: - bigframes.series.Series: A new series of bools. - """ - - prompt_context, series_list = _separate_context_and_series(prompt) - assert len(series_list) > 0 - - operator = ai_ops.AIIf( - prompt_context=tuple(prompt_context), - connection_id=connection_id, - endpoint=endpoint, - optimization_mode=_upper_optional(optimization_mode), - max_error_ratio=max_error_ratio, - ) - - return series_list[0]._apply_nary_op(operator, series_list[1:]) - - -@log_adapter.method_logger(custom_base_name="bigquery_ai") -def classify( - input: PROMPT_TYPE, - categories: tuple[str, ...] | list[str], - *, - examples: list[tuple[str, str]] - | list[tuple[str, list[str] | tuple[str, ...]]] - | None = None, - connection_id: str | None = None, - endpoint: str | None = None, - output_mode: Literal["single", "multi"] | None = None, - optimization_mode: Literal["minimize_cost", "maximize_quality"] | None = None, - max_error_ratio: float | None = None, -) -> series.Series: - """ - Classifies a given input into one of the specified categories. It will always return one of the provided categories best fit the prompt input. - - **Examples:** - - >>> import bigframes.pandas as bpd - >>> import bigframes.bigquery as bbq - >>> df = bpd.DataFrame({'creature': ['Cat', 'Salmon']}) - >>> df['type'] = bbq.ai.classify(df['creature'], ['Mammal', 'Fish']) - >>> df - creature type - 0 Cat Mammal - 1 Salmon Fish - - [2 rows x 2 columns] - - Args: - input (str | Series | List[str|Series] | Tuple[str|Series, ...]): - A mixture of Series and string literals that specifies the input to send to the model. The Series can be BigFrames Series - or pandas Series. - categories (tuple[str, ...] | list[str]): - Categories to classify the input into. - examples (list[tuple[str, str]] | list[tuple[str, list[str] | tuple[str, ...]]], optional): - An array that contains representative examples of input strings and the output category - that you expect. If ``output_mode`` is ``multi``, each example output must be a list or tuple of strings. - You can provide examples to help the model understand your intended threshold for a condition with nuanced - or subjective logic. We recommend providing at most 5 examples. - connection_id (str, optional): - Specifies the connection to use to communicate with the model. For example, ``myproject.us.myconnection``. - If not provided, the query uses your end-user credential. - endpoint (str, optional): - A STRING value that specifies the Vertex AI endpoint to use for the model. You can specify any - generally available or preview Gemini model. If you specify the model name, BigQuery ML automatically - identifies and uses the full endpoint of the model. - output_mode (Literal["single", "multi"], optional): - A STRING value that indicates whether a single input can be classified into multiple categories. - Supported values are ``single`` and ``multi``. - optimization_mode (Literal["minimize_cost", "maximize_quality"], optional): - A STRING value that specifies the optimization strategy to use. Supported values are ``minimize_cost`` - and ``maximize_quality``. - max_error_ratio (float, optional): - A value between ``0.0`` and ``1.0`` that contains the maximum acceptable ratio of row-level - inference failures to rows processed on this function. The default value is 1.0. - This argument isn't supported when ``optimization_mode`` is set to ``minimize_cost``. - - Returns: - bigframes.series.Series: A new series of strings (or a series of arrays of strings if ``output_mode`` is specified). - """ - - prompt_context, series_list = _separate_context_and_series(input) - assert len(series_list) > 0 - - if examples is not None: - example_tuples: Any = tuple( - (ex[0], tuple(ex[1]) if isinstance(ex[1], (list, tuple)) else ex[1]) - for ex in examples - ) - else: - example_tuples = None - - operator = ai_ops.AIClassify( - prompt_context=tuple(prompt_context), - categories=tuple(categories), - examples=example_tuples, - connection_id=connection_id, - endpoint=endpoint, - output_mode=output_mode, - optimization_mode=_upper_optional(optimization_mode), - max_error_ratio=max_error_ratio, - ) - - return series_list[0]._apply_nary_op(operator, series_list[1:]) - - -@log_adapter.method_logger(custom_base_name="bigquery_ai") -def score( - prompt: PROMPT_TYPE, - *, - connection_id: str | None = None, - endpoint: str | None = None, - max_error_ratio: float | None = None, -) -> series.Series: - """ - Computes a score based on rubrics described in natural language. It will return a double value. - There is no fixed range for the score returned. To get high quality results, provide a scoring - rubric with examples in the prompt. - - **Examples:** - - >>> import bigframes.pandas as bpd - >>> import bigframes.bigquery as bbq - >>> animal = bpd.Series(["Tiger", "Rabbit", "Blue Whale"]) - >>> bbq.ai.score(("Rank the relative weights of ", animal, " on the scale from 1 to 3")) - 0 2.0 - 1 1.0 - 2 3.0 - dtype: Float64 - - Args: - prompt (str | Series | List[str|Series] | Tuple[str|Series, ...]): - A mixture of Series and string literals that specifies the prompt to send to the model. The Series can be BigFrames Series - or pandas Series. - connection_id (str, optional): - Specifies the connection to use to communicate with the model. For example, ``myproject.us.myconnection``. - If not provided, the query uses your end-user credential. - endpoint (str, optional): - Specifies the Vertex AI endpoint to use for the model. For example ``"gemini-2.5-flash"``. You can specify any - generally available or preview Gemini model. If you specify the model name, BigQuery ML automatically identifies and - uses the full endpoint of the model. If you don't specify an endpoint value, BigQuery ML dynamically chooses a model - based on your query to have the best cost to quality tradeoff for the task. - max_error_ratio (float, optional): - A value between ``0.0`` and ``1.0`` that contains the maximum acceptable ratio of row-level inference failures to - rows processed on this function. If this value is exceeded, then the query fails. - - Returns: - bigframes.series.Series: A new series of double (float) values. - """ - - prompt_context, series_list = _separate_context_and_series(prompt) - assert len(series_list) > 0 - - operator = ai_ops.AIScore( - prompt_context=tuple(prompt_context), - connection_id=connection_id, - endpoint=endpoint, - max_error_ratio=max_error_ratio, - ) - - return series_list[0]._apply_nary_op(operator, series_list[1:]) - - -@log_adapter.method_logger(custom_base_name="bigquery_ai") -def similarity( - content1: str | series.Series | pd.Series, - content2: str | series.Series | pd.Series, - *, - endpoint: str | None = None, - model: str | None = None, - model_params: Mapping[Any, Any] | None = None, - connection_id: str | None = None, -) -> series.Series: - """ - Returns a FLOAT64 value that represents the cosine similarity between the two inputs. - - **Examples:** - - >>> import bigframes.pandas as bpd - >>> import bigframes.bigquery as bbq - >>> df = bpd.DataFrame({'word': ['happy', 'sad']}) - >>> bbq.ai.similarity(df['word'], 'glad', endpoint='text-embedding-005') - 0 0.916601 - 1 0.660579 - Name: word, dtype: Float64 - - Args: - content1 (str | Series): - A string or series that provides the first value to compare. Both a BigFrames Series or a pandas Series are allowed. - content2 (str | Series): - A string or series that provides the second value to compare. Both a BigFrames Series or a pandas Series are allowed. - endpoint (str, optional): - Specifies the Vertex AI endpoint to use for the text embedding model. - If you specify the model name, such as ``'text-embedding-005'``, rather than a URL, then BigQuery ML automatically identifies the model and uses the model's full endpoint. - model (str, optional): - Specifies a built-in text embedding model. The only supported value is the embeddinggemma-300m model. - If you specify this parameter, you can't specify the ``endpoint``, ``model_params``, or ``connection_id`` parameters. - model_params (Mapping[Any, Any], optional): - Provides additional parameters to the model. You can use any of the parameters object fields. - One of these fields, ``outputDimensionality``, lets you specify the number of dimensions to use when generating embeddings. - connection_id (str, optional): - Specifies the connection to use to communicate with the model. For example, ``myproject.us.myconnection``. - - Returns: - bigframes.series.Series: A new series of FLOAT64 values representing the cosine similarity. - """ - - operator = ai_ops.AISimilarity( - endpoint=endpoint, - model=model, - model_params=json.dumps(model_params) if model_params else None, - connection_id=connection_id, - ) - - # Find a unifying session for the subsequent operations. - bf_session = None - if isinstance(content1, series.Series): - bf_session = content1._session - elif isinstance(content2, series.Series): - bf_session = content2._session - - if isinstance(content1, str) and isinstance(content2, str): - content1 = series.Series([content1], session=bf_session) - return content1._apply_binary_op(content2, operator) - elif isinstance(content1, str): - # content2 must be a series - content2 = convert.to_bf_series( - content2, default_index=None, session=bf_session - ) - return content2._apply_binary_op(content1, operator) - else: - # content1 must be a series. - content1 = convert.to_bf_series( - content1, default_index=None, session=bf_session - ) - return content1._apply_binary_op(content2, operator) - - -@log_adapter.method_logger(custom_base_name="bigquery_ai") -def forecast( - df: dataframe.DataFrame | pd.DataFrame, - *, - data_col: str, - timestamp_col: str, - model: str = "TimesFM 2.0", - id_cols: Iterable[str] | None = None, - horizon: int = 10, - confidence_level: float = 0.95, - output_historical_time_series: bool = False, - context_window: int | None = None, -) -> dataframe.DataFrame: - """ - Forecast time series at future horizon. Using Google Research's open source TimesFM(https://github.com/google-research/timesfm) model. - - **Examples:** - - Forecast using a pandas DataFrame: - - >>> import pandas as pd - >>> import bigframes.pandas as bpd - >>> df = pd.DataFrame({"value": [1, 2, 3], "time": pd.to_datetime(["2020-01-01", "2020-01-02", "2020-01-03"])}) - >>> bpd.options.display.progress_bar = None - >>> forecasted_pandas_df = df.bigquery.ai.forecast(data_col="value", timestamp_col="time", horizon=2) - >>> type(forecasted_pandas_df) # doctest: +ELLIPSIS - - - Forecast using a BigFrames DataFrame: - - >>> bf_df = bpd.DataFrame({"value": [1, 2, 3], "time": pd.to_datetime(["2020-01-01", "2020-01-02", "2020-01-03"])}) - >>> forecasted_bf_df = bf_df.bigquery.ai.forecast(data_col="value", timestamp_col="time", horizon=2) - >>> type(forecasted_bf_df) - - - Args: - df (DataFrame): - The dataframe that contains the data that you want to forecast. It could be either a BigFrames Dataframe or - a pandas DataFrame. If it's a pandas DataFrame, the global BigQuery session will be used to load the data. - data_col (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 - timestamp_col (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 - 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_cols (Iterable[str], optional): - 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). - output_historical_time_series (bool, default False): - A BOOL value that determines whether the input data is returned - along with the forecasted data. Set this argument to TRUE to return - input data. The default value is FALSE. - - Returning the input data along with the forecasted data lets you - compare the historical value of the data column with the forecasted - value of the data column, or chart the change in the data column - values over time. - context_window (int, optional): - An int value that specifies the context window length used by BigQuery ML's built-in TimesFM model. - The context window length determines how many of the most recent data points from the input time series are use by the model. - If you don't specify a value, the AI.FORECAST function automatically chooses the smallest possible context window length to use - that is still large enough to cover the number of time series data points in your input data. - - 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 any column ID does not exist in the dataframe. - """ - - if isinstance(df, pd.DataFrame): - # Load the pandas DataFrame with global session - df = bpd.read_pandas(df) - - columns = [timestamp_col, data_col] - if id_cols: - columns += id_cols - for column in columns: - if column not in df.columns: - raise ValueError(f"Column `{column}` not found") - - options: dict[str, Union[int, float, str, Iterable[str]]] = { - "data_col": data_col, - "timestamp_col": timestamp_col, - "model": model, - "horizon": horizon, - "output_historical_time_series": output_historical_time_series, - "confidence_level": confidence_level, - } - if id_cols: - options["id_cols"] = id_cols - if context_window: - options["context_window"] = context_window - - return ml_core.BaseBqml(df._session).ai_forecast(input_data=df, options=options) - - -def _separate_context_and_series( - prompt: PROMPT_TYPE, -) -> Tuple[List[str | None], List[series.Series]]: - """ - Returns the two values. The first value is the prompt with all series replaced by None. The second value is all the series - in the prompt. The original item order is kept. - For example: - Input: ("str1", series1, "str2", "str3", series2) - Output: ["str1", None, "str2", "str3", None], [series1, series2] - """ - if not isinstance(prompt, (str, list, tuple, series.Series, pd.Series)): - raise ValueError(f"Unsupported prompt type: {type(prompt)}") - - if isinstance(prompt, str): - return [None], [series.Series([prompt])] - - if isinstance(prompt, pd.Series): - return [None], [bpd.read_pandas(prompt)] - - if isinstance(prompt, series.Series): - if prompt.dtype == dtypes.OBJ_REF_DTYPE: - # Multi-model support - return [None], [bq_obj.get_access_url(prompt, mode="R")] - return [None], [prompt] - - prompt_context: List[str | None] = [] - series_list: List[series.Series | pd.Series] = [] - - session = None - for item in prompt: - if isinstance(item, str): - prompt_context.append(item) - - elif isinstance(item, (series.Series, pd.Series)): - prompt_context.append(None) - - if isinstance(item, series.Series) and session is None: - # Use the first available BF session if there's any. - session = item._session - series_list.append(item) - - else: - raise TypeError(f"Unsupported type in prompt: {type(item)}") - - if not series_list: - raise ValueError("Please provide at least one Series in the prompt") - - converted_list = [_convert_series(s, session) for s in series_list] - - return prompt_context, converted_list - - -def _convert_series( - s: series.Series | pd.Series, session: session.Session | None -) -> series.Series: - result = convert.to_bf_series(s, default_index=None, session=session) - - if result.dtype == dtypes.OBJ_REF_DTYPE: - # Support multimodal - return bq_obj.get_access_url(result, mode="R") - return result - - -def _to_dataframe( - data: Union[dataframe.DataFrame, series.Series, pd.DataFrame, pd.Series], - series_rename: str, -) -> dataframe.DataFrame: - if isinstance(data, (pd.DataFrame, pd.Series)): - data = bpd.read_pandas(data) - - if isinstance(data, series.Series): - data = data.copy() - data.name = series_rename - return data.to_frame() - elif isinstance(data, dataframe.DataFrame): - return data - - raise ValueError(f"Unsupported data type: {type(data)}") - - -def _upper_optional(value: str | None) -> str | None: - if value is None: - return None - return value.upper() diff --git a/bigframes/bigquery/_operations/approx_agg.py b/bigframes/bigquery/_operations/approx_agg.py deleted file mode 100644 index 73b6fdbb73b..00000000000 --- a/bigframes/bigquery/_operations/approx_agg.py +++ /dev/null @@ -1,58 +0,0 @@ -# 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. -# 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 bigframes.operations.aggregations as agg_ops -import bigframes.series as series - -""" -Approximate functions defined from -https://cloud.google.com/bigquery/docs/reference/standard-sql/approximate_aggregate_functions -""" - - -def approx_top_count( - series: series.Series, - number: int, -) -> series.Series: - """Returns the approximate top elements of `expression` as an array of STRUCTs. - The number parameter specifies the number of elements returned. - - Each `STRUCT` contains two fields. The first field (named `value`) contains an input - value. The second field (named `count`) contains an `INT64` specifying the number - of times the value was returned. - - Returns `NULL` if there are zero input rows. - - **Examples:** - - >>> import bigframes.pandas as bpd - >>> import bigframes.bigquery as bbq - >>> s = bpd.Series(["apple", "apple", "pear", "pear", "pear", "banana"]) - >>> bbq.approx_top_count(s, number=2) - [{'value': 'pear', 'count': 3}, {'value': 'apple', 'count': 2}] - - Args: - series (bigframes.series.Series): - The Series with any data type that the `GROUP BY` clause supports. - number (int): - An integer specifying the number of times the value was returned. - - Returns: - bigframes.series.Series: A new Series with the result data. - """ - if number < 1: - raise ValueError("The number of approx_top_count must be at least 1") - return series._apply_aggregation(agg_ops.ApproxTopCountOp(number=number)) diff --git a/bigframes/bigquery/_operations/array.py b/bigframes/bigquery/_operations/array.py deleted file mode 100644 index 0a3c5d66217..00000000000 --- a/bigframes/bigquery/_operations/array.py +++ /dev/null @@ -1,82 +0,0 @@ -# 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. -# 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. - -""" -Array functions defined from -https://cloud.google.com/bigquery/docs/reference/standard-sql/array_functions -""" - -from __future__ import annotations - -import typing - -import bigframes_vendored.constants as constants - -import bigframes.core.groupby as groupby -import bigframes.operations.aggregations as agg_ops -import bigframes.series as series - -if typing.TYPE_CHECKING: - import bigframes.dataframe as dataframe - - -def array_agg( - obj: groupby.SeriesGroupBy | groupby.DataFrameGroupBy, -) -> series.Series | dataframe.DataFrame: - """Group data and create arrays from selected columns, omitting NULLs to avoid - BigQuery errors (NULLs not allowed in arrays). - - **Examples:** - - >>> import bigframes.pandas as bpd - >>> import bigframes.bigquery as bbq - - For a SeriesGroupBy object: - - >>> lst = ['a', 'a', 'b', 'b', 'a'] - >>> s = bpd.Series([1, 2, 3, 4, np.nan], index=lst) - >>> bbq.array_agg(s.groupby(level=0)) - a [1. 2.] - b [3. 4.] - dtype: list[pyarrow] - - For a DataFrameGroupBy object: - - >>> l = [[1, 2, 3], [1, None, 4], [2, 1, 3], [1, 2, 2]] - >>> df = bpd.DataFrame(l, columns=["a", "b", "c"]) - >>> bbq.array_agg(df.groupby(by=["b"])) - a c - b - 1.0 [2] [3] - 2.0 [1 1] [3 2] - - [2 rows x 2 columns] - - Args: - obj (groupby.SeriesGroupBy | groupby.DataFrameGroupBy): - A GroupBy object to be applied the function. - - Returns: - bigframes.series.Series | bigframes.dataframe.DataFrame: A Series or - DataFrame containing aggregated array columns, and indexed by the - original group columns. - """ - if isinstance(obj, groupby.SeriesGroupBy): - return obj._aggregate(agg_ops.ArrayAggOp()) - elif isinstance(obj, groupby.DataFrameGroupBy): - return obj._aggregate_all(agg_ops.ArrayAggOp(), numeric_only=False) - else: - raise ValueError( - f"Unsupported type {type(obj)} to apply `array_agg` function. {constants.FEEDBACK_LINK}" - ) diff --git a/bigframes/bigquery/_operations/datetime.py b/bigframes/bigquery/_operations/datetime.py deleted file mode 100644 index 99467beb066..00000000000 --- a/bigframes/bigquery/_operations/datetime.py +++ /dev/null @@ -1,91 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from bigframes import operations as ops -from bigframes import series - - -def unix_seconds(input: series.Series) -> series.Series: - """Converts a timestmap series to unix epoch seconds - - **Examples:** - - >>> import bigframes.pandas as bpd - >>> import bigframes.bigquery as bbq - - >>> s = bpd.Series([pd.Timestamp("1970-01-02", tz="UTC"), pd.Timestamp("1970-01-03", tz="UTC")]) - >>> bbq.unix_seconds(s) - 0 86400 - 1 172800 - dtype: Int64 - - Args: - input (bigframes.pandas.Series): - A timestamp series. - - Returns: - bigframes.pandas.Series: A new series of unix epoch in seconds. - - """ - return input._apply_unary_op(ops.UnixSeconds()) - - -def unix_millis(input: series.Series) -> series.Series: - """Converts a timestmap series to unix epoch milliseconds - - **Examples:** - - >>> import bigframes.pandas as bpd - >>> import bigframes.bigquery as bbq - - >>> s = bpd.Series([pd.Timestamp("1970-01-02", tz="UTC"), pd.Timestamp("1970-01-03", tz="UTC")]) - >>> bbq.unix_millis(s) - 0 86400000 - 1 172800000 - dtype: Int64 - - Args: - input (bigframes.pandas.Series): - A timestamp series. - - Returns: - bigframes.pandas.Series: A new series of unix epoch in milliseconds. - - """ - return input._apply_unary_op(ops.UnixMillis()) - - -def unix_micros(input: series.Series) -> series.Series: - """Converts a timestmap series to unix epoch microseconds - - **Examples:** - - >>> import bigframes.pandas as bpd - >>> import bigframes.bigquery as bbq - - >>> s = bpd.Series([pd.Timestamp("1970-01-02", tz="UTC"), pd.Timestamp("1970-01-03", tz="UTC")]) - >>> bbq.unix_micros(s) - 0 86400000000 - 1 172800000000 - dtype: Int64 - - Args: - input (bigframes.pandas.Series): - A timestamp series. - - Returns: - bigframes.pandas.Series: A new series of unix epoch in microseconds. - - """ - return input._apply_unary_op(ops.UnixMicros()) diff --git a/bigframes/bigquery/_operations/geo.py b/bigframes/bigquery/_operations/geo.py deleted file mode 100644 index e9ea711c969..00000000000 --- a/bigframes/bigquery/_operations/geo.py +++ /dev/null @@ -1,756 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from __future__ import annotations - -import json -from typing import Mapping, Optional, Union - -import shapely # type: ignore - -import bigframes.dataframe -import bigframes.geopandas -import bigframes.series -from bigframes import operations as ops - -""" -Search functions defined from -https://cloud.google.com/bigquery/docs/reference/standard-sql/geography_functions -""" - - -def st_area( - series: Union[bigframes.series.Series, bigframes.geopandas.GeoSeries], -) -> bigframes.series.Series: - """ - Returns the area in square meters covered by the polygons in the input - `GEOGRAPHY`. - - If geography_expression is a point or a line, returns zero. If - geography_expression is a collection, returns the area of the polygons - in the collection; if the collection doesn't contain polygons, returns zero. - - - .. note:: - BigQuery's Geography functions, like `st_area`, interpret the geometry - data type as a point set on the Earth's surface. A point set is a set - of points, lines, and polygons on the WGS84 reference spheroid, with - geodesic edges. See: https://cloud.google.com/bigquery/docs/geospatial-data - - - **Examples:** - - >>> import bigframes.geopandas - >>> import bigframes.pandas as bpd - >>> import bigframes.bigquery as bbq - >>> from shapely.geometry import Polygon, LineString, Point - - >>> series = bigframes.geopandas.GeoSeries( - ... [ - ... Polygon([(0.0, 0.0), (0.1, 0.1), (0.0, 0.1)]), - ... Polygon([(0.10, 0.4), (0.9, 0.5), (0.10, 0.5)]), - ... Polygon([(0.1, 0.1), (0.2, 0.1), (0.2, 0.2)]), - ... LineString([(0, 0), (1, 1), (0, 1)]), - ... Point(0, 1), - ... ] - ... ) - >>> series - 0 POLYGON ((0 0, 0.1 0.1, 0 0.1, 0 0)) - 1 POLYGON ((0.1 0.4, 0.9 0.5, 0.1 0.5, 0.1 0.4)) - 2 POLYGON ((0.1 0.1, 0.2 0.1, 0.2 0.2, 0.1 0.1)) - 3 LINESTRING (0 0, 1 1, 0 1) - 4 POINT (0 1) - dtype: geometry - - >>> bbq.st_area(series) - 0 61821689.855985 - 1 494563347.88721 - 2 61821689.855841 - 3 0.0 - 4 0.0 - dtype: Float64 - - Use `round()` to round the outputed areas to the neares ten millions - - >>> bbq.st_area(series).round(-7) - 0 60000000.0 - 1 490000000.0 - 2 60000000.0 - 3 0.0 - 4 0.0 - dtype: Float64 - - Args: - series (bigframes.pandas.Series | bigframes.geopandas.GeoSeries): - A series containing geography objects. - - Returns: - bigframes.pandas.Series: - Series of float representing the areas. - """ - series = series._apply_nary_op(ops.googlesql.ST_AREA, []) - series.name = None - return series - - -def st_buffer( - series: Union[bigframes.series.Series, bigframes.geopandas.GeoSeries], - buffer_radius: float, - num_seg_quarter_circle: float = 8.0, - use_spheroid: bool = False, -) -> bigframes.series.Series: - """ - Computes a `GEOGRAPHY` that represents all points whose distance from the - input `GEOGRAPHY` is less than or equal to `distance` meters. - - .. note:: - BigQuery's Geography functions, like `st_buffer`, interpret the geometry - data type as a point set on the Earth's surface. A point set is a set - of points, lines, and polygons on the WGS84 reference spheroid, with - geodesic edges. See: https://cloud.google.com/bigquery/docs/geospatial-data - - **Examples:** - - >>> import bigframes.geopandas - >>> import bigframes.pandas as bpd - >>> import bigframes.bigquery as bbq - >>> from shapely.geometry import Point - - >>> series = bigframes.geopandas.GeoSeries( - ... [ - ... Point(0, 0), - ... Point(1, 1), - ... ] - ... ) - >>> series - 0 POINT (0 0) - 1 POINT (1 1) - dtype: geometry - - >>> buffer = bbq.st_buffer(series, 100) - >>> bbq.st_area(buffer) > 0 - 0 True - 1 True - dtype: boolean - - Args: - series (bigframes.pandas.Series | bigframes.geopandas.GeoSeries): - A series containing geography objects. - buffer_radius (float): - The distance in meters. - num_seg_quarter_circle (float, optional): - Specifies the number of segments that are used to approximate a - quarter circle. The default value is 8.0. - use_spheroid (bool, optional): - Determines how this function measures distance. If use_spheroid is - FALSE, the function measures distance on the surface of a perfect - sphere. The use_spheroid parameter currently only supports the - value FALSE. The default value of use_spheroid is FALSE. - - Returns: - bigframes.pandas.Series: - A series of geography objects representing the buffered geometries. - """ - op = ops.GeoStBufferOp( - buffer_radius=buffer_radius, - num_seg_quarter_circle=num_seg_quarter_circle, - use_spheroid=use_spheroid, - ) - series = series._apply_unary_op(op) - series.name = None - return series - - -def st_centroid( - series: Union[bigframes.series.Series, bigframes.geopandas.GeoSeries], -) -> bigframes.series.Series: - """ - Computes the geometric centroid of a `GEOGRAPHY` type. - - For `POINT` and `MULTIPOINT` types, this is the arithmetic mean of the - input coordinates. For `LINESTRING` and `POLYGON` types, this is the - center of mass. For `GEOMETRYCOLLECTION` types, this is the center of - mass of the collection's elements. - - .. note:: - BigQuery's Geography functions, like `st_centroid`, interpret the geometry - data type as a point set on the Earth's surface. A point set is a set - of points, lines, and polygons on the WGS84 reference spheroid, with - geodesic edges. See: https://cloud.google.com/bigquery/docs/geospatial-data - - **Examples:** - - >>> import bigframes.geopandas - >>> import bigframes.pandas as bpd - >>> import bigframes.bigquery as bbq - >>> from shapely.geometry import Polygon, LineString, Point - - >>> series = bigframes.geopandas.GeoSeries( - ... [ - ... Polygon([(0.0, 0.0), (0.1, 0.1), (0.0, 0.1)]), - ... LineString([(0, 0), (1, 1), (0, 1)]), - ... Point(0, 1), - ... ] - ... ) - >>> series - 0 POLYGON ((0 0, 0.1 0.1, 0 0.1, 0 0)) - 1 LINESTRING (0 0, 1 1, 0 1) - 2 POINT (0 1) - dtype: geometry - - >>> bbq.st_centroid(series) - 0 POINT (0.03333 0.06667) - 1 POINT (0.49998 0.70712) - 2 POINT (0 1) - dtype: geometry - - Args: - series (bigframes.pandas.Series | bigframes.geopandas.GeoSeries): - A series containing geography objects. - - Returns: - bigframes.pandas.Series: - A series of geography objects representing the centroids. - """ - series = series._apply_nary_op(ops.googlesql.ST_CENTROID, []) - series.name = None - return series - - -def st_convexhull( - series: Union[bigframes.series.Series, bigframes.geopandas.GeoSeries], -) -> bigframes.series.Series: - """ - Computes the convex hull of a `GEOGRAPHY` type. - - The convex hull is the smallest convex set that contains all of the - points in the input `GEOGRAPHY`. - - .. note:: - BigQuery's Geography functions, like `st_convexhull`, interpret the geometry - data type as a point set on the Earth's surface. A point set is a set - of points, lines, and polygons on the WGS84 reference spheroid, with - geodesic edges. See: https://cloud.google.com/bigquery/docs/geospatial-data - - **Examples:** - - >>> import bigframes.geopandas - >>> import bigframes.pandas as bpd - >>> import bigframes.bigquery as bbq - >>> from shapely.geometry import Polygon, LineString, Point - - >>> series = bigframes.geopandas.GeoSeries( - ... [ - ... Polygon([(0.0, 0.0), (0.1, 0.1), (0.0, 0.1)]), - ... LineString([(0, 0), (1, 1), (0, 1)]), - ... Point(0, 1), - ... ] - ... ) - >>> series - 0 POLYGON ((0 0, 0.1 0.1, 0 0.1, 0 0)) - 1 LINESTRING (0 0, 1 1, 0 1) - 2 POINT (0 1) - dtype: geometry - - >>> bbq.st_convexhull(series) - 0 POLYGON ((0 0, 0.1 0.1, 0 0.1, 0 0)) - 1 POLYGON ((0 0, 1 1, 0 1, 0 0)) - 2 POINT (0 1) - dtype: geometry - - Args: - series (bigframes.pandas.Series | bigframes.geopandas.GeoSeries): - A series containing geography objects. - - Returns: - bigframes.pandas.Series: - A series of geography objects representing the convex hulls. - """ - series = series._apply_unary_op(ops.geo_st_convexhull_op) - series.name = None - return series - - -def st_difference( - series: Union[bigframes.series.Series, bigframes.geopandas.GeoSeries], - other: Union[ - bigframes.series.Series, - bigframes.geopandas.GeoSeries, - shapely.geometry.base.BaseGeometry, - ], -) -> bigframes.series.Series: - """ - Returns a `GEOGRAPHY` that represents the point set difference of - `geography_1` and `geography_2`. Therefore, the result consists of the part - of `geography_1` that doesn't intersect with `geography_2`. - - If `geometry_1` is completely contained in `geometry_2`, then `ST_DIFFERENCE` - returns an empty `GEOGRAPHY`. - - .. note:: - BigQuery's Geography functions, like `st_difference`, interpret the geometry - data type as a point set on the Earth's surface. A point set is a set - of points, lines, and polygons on the WGS84 reference spheroid, with - geodesic edges. See: https://cloud.google.com/bigquery/docs/geospatial-data - - **Examples:** - - >>> import bigframes as bpd - >>> import bigframes.bigquery as bbq - >>> import bigframes.geopandas - >>> from shapely.geometry import Polygon, LineString, Point - - We can check two GeoSeries against each other, row by row: - - >>> s1 = bigframes.geopandas.GeoSeries( - ... [ - ... Polygon([(0, 0), (2, 2), (0, 2)]), - ... Polygon([(0, 0), (2, 2), (0, 2)]), - ... LineString([(0, 0), (2, 2)]), - ... LineString([(2, 0), (0, 2)]), - ... Point(0, 1), - ... ], - ... ) - >>> s2 = bigframes.geopandas.GeoSeries( - ... [ - ... Polygon([(0, 0), (1, 1), (0, 1)]), - ... LineString([(1, 0), (1, 3)]), - ... LineString([(2, 0), (0, 2)]), - ... Point(1, 1), - ... Point(0, 1), - ... ], - ... index=range(1, 6), - ... ) - - >>> s1 - 0 POLYGON ((0 0, 2 2, 0 2, 0 0)) - 1 POLYGON ((0 0, 2 2, 0 2, 0 0)) - 2 LINESTRING (0 0, 2 2) - 3 LINESTRING (2 0, 0 2) - 4 POINT (0 1) - dtype: geometry - - >>> s2 - 1 POLYGON ((0 0, 1 1, 0 1, 0 0)) - 2 LINESTRING (1 0, 1 3) - 3 LINESTRING (2 0, 0 2) - 4 POINT (1 1) - 5 POINT (0 1) - dtype: geometry - - >>> bbq.st_difference(s1, s2) - 0 None - 1 POLYGON ((0.99954 1, 2 2, 0 2, 0 1, 0.99954 1)) - 2 LINESTRING (0 0, 1 1.00046, 2 2) - 3 GEOMETRYCOLLECTION EMPTY - 4 POINT (0 1) - 5 None - dtype: geometry - - Additionally, we can check difference of a GeoSeries against a single shapely geometry: - - >>> polygon = Polygon([(0, 0), (10, 0), (10, 10), (0, 0)]) - >>> bbq.st_difference(s1, polygon) - 0 POLYGON ((1.97082 2.00002, 0 2, 0 0, 1.97082 2... - 1 POLYGON ((1.97082 2.00002, 0 2, 0 0, 1.97082 2... - 2 GEOMETRYCOLLECTION EMPTY - 3 LINESTRING (0.99265 1.00781, 0 2) - 4 POINT (0 1) - dtype: geometry - - Args: - series (bigframes.pandas.Series | bigframes.geopandas.GeoSeries): - A series containing geography objects. - other (bigframes.pandas.Series | bigframes.geopandas.GeoSeries | shapely.Geometry): - The series or geometric object to subtract from the geography - objects in ``series``. - - Returns: - bigframes.series.Series: - A GeoSeries of the points in each aligned geometry that are not - in other. - """ - return series._apply_binary_op(other, ops.geo_st_difference_op) - - -def st_distance( - series: Union[bigframes.series.Series, bigframes.geopandas.GeoSeries], - other: Union[ - bigframes.series.Series, - bigframes.geopandas.GeoSeries, - shapely.geometry.base.BaseGeometry, - ], - *, - use_spheroid: bool = False, -) -> bigframes.series.Series: - """ - Returns the shortest distance in meters between two non-empty - ``GEOGRAPHY`` objects. - - **Examples:** - - >>> import bigframes as bpd - >>> import bigframes.bigquery as bbq - >>> import bigframes.geopandas - >>> from shapely.geometry import Polygon, LineString, Point - - We can check two GeoSeries against each other, row by row. - - >>> s1 = bigframes.geopandas.GeoSeries( - ... [ - ... Point(0, 0), - ... Point(0.00001, 0), - ... Point(0.00002, 0), - ... ], - ... ) - >>> s2 = bigframes.geopandas.GeoSeries( - ... [ - ... Point(0.00001, 0), - ... Point(0.00003, 0), - ... Point(0.00005, 0), - ... ], - ... ) - - >>> bbq.st_distance(s1, s2, use_spheroid=True) - 0 1.113195 - 1 2.22639 - 2 3.339585 - dtype: Float64 - - We can also calculate the distance of each geometry and a single shapely geometry: - - >>> bbq.st_distance(s2, Point(0.00001, 0)) - 0 0.0 - 1 2.223902 - 2 4.447804 - dtype: Float64 - - Args: - series (bigframes.pandas.Series | bigframes.geopandas.GeoSeries): - A series containing geography objects. - other (bigframes.pandas.Series | bigframes.geopandas.GeoSeries | shapely.Geometry): - The series or geometric object to calculate the distance in meters - to form the geography objects in ``series``. - use_spheroid (optional, default ``False``): - Determines how this function measures distance. If ``use_spheroid`` - is False, the function measures distance on the surface of a perfect - sphere. If ``use_spheroid`` is True, the function measures distance - on the surface of the `WGS84 spheroid - `_. The - default value of ``use_spheroid`` is False. - - Returns: - bigframes.pandas.Series: - The Series (elementwise) of the smallest distance between - each aligned geometry with other. - """ - return series._apply_binary_op( - other, ops.GeoStDistanceOp(use_spheroid=use_spheroid) - ) - - -def st_intersection( - series: Union[bigframes.series.Series, bigframes.geopandas.GeoSeries], - other: Union[ - bigframes.series.Series, - bigframes.geopandas.GeoSeries, - shapely.geometry.base.BaseGeometry, - ], -) -> bigframes.series.Series: - """ - Returns a `GEOGRAPHY` that represents the point set intersection of the two - input `GEOGRAPHYs`. Thus, every point in the intersection appears in both - `geography_1` and `geography_2`. - - .. note:: - BigQuery's Geography functions, like `st_intersection`, interpret the geometry - data type as a point set on the Earth's surface. A point set is a set - of points, lines, and polygons on the WGS84 reference spheroid, with - geodesic edges. See: https://cloud.google.com/bigquery/docs/geospatial-data - - **Examples:** - - >>> import bigframes as bpd - >>> import bigframes.bigquery as bbq - >>> import bigframes.geopandas - >>> from shapely.geometry import Polygon, LineString, Point - - We can check two GeoSeries against each other, row by row. - - >>> s1 = bigframes.geopandas.GeoSeries( - ... [ - ... Polygon([(0, 0), (2, 2), (0, 2)]), - ... Polygon([(0, 0), (2, 2), (0, 2)]), - ... LineString([(0, 0), (2, 2)]), - ... LineString([(2, 0), (0, 2)]), - ... Point(0, 1), - ... ], - ... ) - >>> s2 = bigframes.geopandas.GeoSeries( - ... [ - ... Polygon([(0, 0), (1, 1), (0, 1)]), - ... LineString([(1, 0), (1, 3)]), - ... LineString([(2, 0), (0, 2)]), - ... Point(1, 1), - ... Point(0, 1), - ... ], - ... index=range(1, 6), - ... ) - - >>> s1 - 0 POLYGON ((0 0, 2 2, 0 2, 0 0)) - 1 POLYGON ((0 0, 2 2, 0 2, 0 0)) - 2 LINESTRING (0 0, 2 2) - 3 LINESTRING (2 0, 0 2) - 4 POINT (0 1) - dtype: geometry - - >>> s2 - 1 POLYGON ((0 0, 1 1, 0 1, 0 0)) - 2 LINESTRING (1 0, 1 3) - 3 LINESTRING (2 0, 0 2) - 4 POINT (1 1) - 5 POINT (0 1) - dtype: geometry - - >>> bbq.st_intersection(s1, s2) - 0 None - 1 POLYGON ((0 0, 0.99954 1, 0 1, 0 0)) - 2 POINT (1 1.00046) - 3 LINESTRING (2 0, 0 2) - 4 GEOMETRYCOLLECTION EMPTY - 5 None - dtype: geometry - - We can also do intersection of each geometry and a single shapely geometry: - - >>> bbq.st_intersection(s1, Polygon([(0, 0), (1, 1), (0, 1)])) - 0 POLYGON ((0 0, 0.99954 1, 0 1, 0 0)) - 1 POLYGON ((0 0, 0.99954 1, 0 1, 0 0)) - 2 LINESTRING (0 0, 0.99954 1) - 3 GEOMETRYCOLLECTION EMPTY - 4 POINT (0 1) - dtype: geometry - - Args: - series (bigframes.pandas.Series | bigframes.geopandas.GeoSeries): - A series containing geography objects. - other (bigframes.pandas.Series | bigframes.geopandas.GeoSeries | shapely.Geometry): - The series or geometric object to intersect with the geography - objects in ``series``. - - Returns: - bigframes.geopandas.GeoSeries: - The Geoseries (elementwise) of the intersection of points in - each aligned geometry with other. - """ - return series._apply_binary_op(other, ops.geo_st_intersection_op) - - -def st_isclosed( - series: Union[bigframes.series.Series, bigframes.geopandas.GeoSeries], -) -> bigframes.series.Series: - """ - Returns TRUE for a non-empty Geography, where each element in the - Geography has an empty boundary. - - .. note:: - BigQuery's Geography functions, like `st_isclosed`, interpret the geometry - data type as a point set on the Earth's surface. A point set is a set - of points, lines, and polygons on the WGS84 reference spheroid, with - geodesic edges. See: https://cloud.google.com/bigquery/docs/geospatial-data - - **Examples:** - - >>> import bigframes.geopandas - >>> import bigframes.pandas as bpd - >>> import bigframes.bigquery as bbq - - >>> from shapely.geometry import Point, LineString, Polygon - - >>> series = bigframes.geopandas.GeoSeries( - ... [ - ... Point(0, 0), # Point - ... LineString([(0, 0), (1, 1)]), # Open LineString - ... LineString([(0, 0), (1, 1), (0, 1), (0, 0)]), # Closed LineString - ... Polygon([(0, 0), (1, 1), (0, 1), (0, 0)]), - ... None, - ... ] - ... ) - >>> series - 0 POINT (0 0) - 1 LINESTRING (0 0, 1 1) - 2 LINESTRING (0 0, 1 1, 0 1, 0 0) - 3 POLYGON ((0 0, 1 1, 0 1, 0 0)) - 4 None - dtype: geometry - - >>> bbq.st_isclosed(series) - 0 True - 1 False - 2 True - 3 False - 4 - dtype: boolean - - Args: - series (bigframes.pandas.Series | bigframes.geopandas.GeoSeries): - A series containing geography objects. - - Returns: - bigframes.pandas.Series: - Series of booleans indicating whether each geometry is closed. - """ - series = series._apply_unary_op(ops.geo_st_isclosed_op) - series.name = None - return series - - -def st_length( - series: Union[bigframes.series.Series, bigframes.geopandas.GeoSeries], - *, - use_spheroid: bool = False, -) -> bigframes.series.Series: - """Returns the total length in meters of the lines in the input GEOGRAPHY. - - If a series element is a point or a polygon, returns zero for that row. - If a series element is a collection, returns the length of the lines - in the collection; if the collection doesn't contain lines, returns - zero. - - The optional use_spheroid parameter determines how this function - measures distance. If use_spheroid is FALSE, the function measures - distance on the surface of a perfect sphere. - - The use_spheroid parameter currently only supports the value FALSE. The - default value of use_spheroid is FALSE. See: - https://cloud.google.com/bigquery/docs/reference/standard-sql/geography_functions#st_length - - **Examples:** - - >>> import bigframes.geopandas - >>> import bigframes.pandas as bpd - >>> import bigframes.bigquery as bbq - - >>> from shapely.geometry import Polygon, LineString, Point, GeometryCollection - - >>> series = bigframes.geopandas.GeoSeries( - ... [ - ... LineString([(0, 0), (1, 0)]), # Length will be approx 1 degree in meters - ... Polygon([(0.0, 0.0), (0.1, 0.1), (0.0, 0.1)]), # Length is 0 - ... Point(0, 1), # Length is 0 - ... GeometryCollection([LineString([(0,0),(0,1)]), Point(1,1)]) # Length of LineString only - ... ] - ... ) - - >>> result = bbq.st_length(series) - >>> result - 0 111195.101177 - 1 0.0 - 2 0.0 - 3 111195.101177 - dtype: Float64 - - Args: - series (bigframes.series.Series | bigframes.geopandas.GeoSeries): - A series containing geography objects. - use_spheroid (bool, optional): - Determines how this function measures distance. - If FALSE (default), measures distance on a perfect sphere. - Currently, only FALSE is supported. - - Returns: - bigframes.series.Series: - Series of floats representing the lengths in meters. - """ - series = series._apply_unary_op(ops.GeoStLengthOp(use_spheroid=use_spheroid)) - series.name = None - return series - - -def st_regionstats( - geography: Union[bigframes.series.Series, bigframes.geopandas.GeoSeries], - raster_id: str, - band: Optional[str] = None, - include: Optional[str] = None, - options: Optional[Mapping[str, Union[str, int, float]]] = None, -) -> bigframes.series.Series: - """Returns statistics summarizing the pixel values of the raster image - referenced by raster_id that intersect with geography. - - The statistics include the count, minimum, maximum, sum, standard - deviation, mean, and area of the valid pixels of the raster band named - band_name. Google Earth Engine computes the results of the function call. - - See: https://cloud.google.com/bigquery/docs/reference/standard-sql/geography_functions#st_regionstats - - Args: - geography (bigframes.series.Series | bigframes.geopandas.GeoSeries): - A series of geography objects to intersect with the raster image. - raster_id (str): - A string that identifies a raster image. The following formats are - supported. A URI from an image table provided by Google Earth Engine - in BigQuery sharing (formerly Analytics Hub). A URI for a readable - GeoTIFF raster file. A Google Earth Engine asset path that - references public catalog data or project-owned assets with read - access. - band (Optional[str]): - A string in one of the following formats: - A single band within the raster image specified by raster_id. A - formula to compute a value from the available bands in the raster - image. The formula uses the Google Earth Engine image expression - syntax. Bands can be referenced by their name, band_name, in - expressions. If you don't specify a band, the first band of the - image is used. - include (Optional[str]): - An optional string formula that uses the Google Earth Engine image - expression syntax to compute a pixel weight. The formula should - return values from 0 to 1. Values outside this range are set to the - nearest limit, either 0 or 1. A value of 0 means that the pixel is - invalid and it's excluded from analysis. A positive value means that - a pixel is valid. Values between 0 and 1 represent proportional - weights for calculations, such as weighted means. - options (Mapping[str, Union[str, int, float]], optional): - A dictionary of options to pass to the function. See the BigQuery - documentation for a list of available options. - - Returns: - bigframes.pandas.Series: - A STRUCT Series containing the computed statistics. - """ - op = ops.GeoStRegionStatsOp( - raster_id=raster_id, - band=band, - include=include, - options=json.dumps(options) if options else None, - ) - return geography._apply_unary_op(op) - - -def st_simplify( - geography: "bigframes.series.Series", - tolerance_meters: float, -) -> "bigframes.series.Series": - """Returns a simplified version of the input geography. - - Args: - geography (bigframes.series.Series): - A Series containing GEOGRAPHY data. - tolerance_meters (float): - A float64 value indicating the tolerance in meters. - - Returns: - a Series containing the simplified GEOGRAPHY data. - """ - return geography._apply_nary_op(ops.googlesql.ST_SIMPLIFY, [tolerance_meters]) diff --git a/bigframes/bigquery/_operations/io.py b/bigframes/bigquery/_operations/io.py deleted file mode 100644 index bf9eae95660..00000000000 --- a/bigframes/bigquery/_operations/io.py +++ /dev/null @@ -1,95 +0,0 @@ -# Copyright 2026 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 typing import Mapping, Optional, Union - -import pandas as pd - -import bigframes.core.compile.sqlglot.sql as sql -import bigframes.core.logging.log_adapter as log_adapter -import bigframes.session -from bigframes.bigquery._operations.table import _get_table_metadata - - -@log_adapter.method_logger(custom_base_name="bigquery_io") -def load_data( - table_name: str, - *, - write_disposition: str = "INTO", - columns: Optional[Mapping[str, str]] = None, - partition_by: Optional[list[str]] = None, - cluster_by: Optional[list[str]] = None, - table_options: Optional[Mapping[str, Union[str, int, float, bool, list]]] = None, - from_files_options: Mapping[str, Union[str, int, float, bool, list]], - with_partition_columns: Optional[Mapping[str, str]] = None, - connection_name: Optional[str] = None, - session: Optional[bigframes.session.Session] = None, -) -> pd.Series: - """ - Loads data into a BigQuery table. - See the `BigQuery LOAD DATA DDL syntax - `_ - for additional reference. - Args: - table_name (str): - The name of the table in BigQuery. - write_disposition (str, default "INTO"): - Whether to replace the table if it already exists ("OVERWRITE") or append to it ("INTO"). - columns (Mapping[str, str], optional): - The table's schema. - partition_by (list[str], optional): - A list of partition expressions to partition the table by. See https://docs.cloud.google.com/bigquery/docs/reference/standard-sql/load-statements#partition_expression. - cluster_by (list[str], optional): - A list of columns to cluster the table by. - table_options (Mapping[str, Union[str, int, float, bool, list]], optional): - The table options. - from_files_options (Mapping[str, Union[str, int, float, bool, list]]): - The options for loading data from files. - with_partition_columns (Mapping[str, str], optional): - The table's partition columns. - connection_name (str, optional): - The connection to use for the table. - session (bigframes.session.Session, optional): - The session to use. If not provided, the default session is used. - Returns: - pandas.Series: - A Series with object dtype containing the table metadata. Reference - the `BigQuery Table REST API reference - `_ - for available fields. - """ - import bigframes.pandas as bpd - - load_data_expr = sql.load_data( - table_name=table_name, - write_disposition=write_disposition, - columns=columns, - partition_by=partition_by, - cluster_by=cluster_by, - table_options=table_options, - from_files_options=from_files_options, - with_partition_columns=with_partition_columns, - connection_name=connection_name, - ) - sql_text = sql.to_sql(load_data_expr) - - if session is None: - bpd.read_gbq_query(sql_text) - session = bpd.get_global_session() - else: - session.read_gbq_query(sql_text) - - return _get_table_metadata(bqclient=session.bqclient, table_name=table_name) diff --git a/bigframes/bigquery/_operations/json.py b/bigframes/bigquery/_operations/json.py deleted file mode 100644 index 8afb234b719..00000000000 --- a/bigframes/bigquery/_operations/json.py +++ /dev/null @@ -1,548 +0,0 @@ -# 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. -# 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. - - -""" -JSON functions defined from -https://cloud.google.com/bigquery/docs/reference/standard-sql/json_functions -""" - -from __future__ import annotations - -import warnings -from typing import Any, Optional, Sequence, Tuple, Union, cast - -import bigframes.core.utils as utils -import bigframes.dtypes -import bigframes.exceptions as bfe -import bigframes.operations as ops -import bigframes.series as series - -from . import array - - -@utils.preview(name="The JSON-related API `json_set`") -def json_set( - input: series.Series, - json_path_value_pairs: Sequence[Tuple[str, Any]], -) -> series.Series: - """Produces a new JSON value within a Series by inserting or replacing values at - specified paths. - - .. warning:: - The JSON-related API `parse_json` is in preview. Its behavior may change in - future versions. - - **Examples:** - - >>> import bigframes.pandas as bpd - >>> import bigframes.bigquery as bbq - - >>> s = bpd.read_gbq("SELECT JSON '{\\\"a\\\": 1}' AS data")["data"] - >>> bbq.json_set(s, json_path_value_pairs=[("$.a", 100), ("$.b", "hi")]) - 0 {"a":100,"b":"hi"} - Name: data, dtype: extension>[pyarrow] - - Args: - input (bigframes.series.Series): - The Series containing JSON data (as native JSON objects or JSON-formatted strings). - json_path_value_pairs (Sequence[Tuple[str, Any]]): - Pairs of JSON path and the new value to insert/replace. - - Returns: - bigframes.series.Series: A new Series with the transformed JSON data. - - """ - # SQLGlot parser does not support the "create_if_missing => true" syntax, so - # create_if_missing is not currently implemented. - - result = input - for json_path_value_pair in json_path_value_pairs: - if len(json_path_value_pair) != 2: - raise ValueError( - "Incorrect format: Expected (, ), but found: " - + f"{json_path_value_pair}" - ) - - json_path, json_value = json_path_value_pair - result = result._apply_binary_op( - json_value, ops.JSONSet(json_path=json_path), alignment="left" - ) - return result - - -def json_extract( - input: series.Series, - json_path: str, -) -> series.Series: - """Extracts a JSON value and converts it to a SQL JSON-formatted ``STRING`` or - ``JSON`` value. This function uses single quotes and brackets to escape invalid - JSONPath characters in JSON keys. - - .. deprecated:: 2.5.0 - The ``json_extract`` is deprecated and will be removed in a future version. - Use ``json_query`` instead. - - **Examples:** - - >>> import bigframes.pandas as bpd - >>> import bigframes.bigquery as bbq - - >>> s = bpd.Series(['{"class": {"students": [{"id": 5}, {"id": 12}]}}']) - >>> bbq.json_extract(s, json_path="$.class") - 0 {"students":[{"id":5},{"id":12}]} - dtype: string - - Args: - input (bigframes.series.Series): - The Series containing JSON data (as native JSON objects or JSON-formatted strings). - json_path (str): - The JSON path identifying the data that you want to obtain from the input. - - Returns: - bigframes.series.Series: A new Series with the JSON or JSON-formatted STRING. - """ - msg = ( - "The `json_extract` is deprecated and will be removed in a future version. " - "Use `json_query` instead." - ) - warnings.warn(bfe.format_message(msg), category=UserWarning) - return input._apply_unary_op(ops.JSONExtract(json_path=json_path)) - - -def json_extract_array( - input: series.Series, - json_path: str = "$", -) -> series.Series: - """Extracts a JSON array and converts it to a SQL array of JSON-formatted - `STRING` or `JSON` values. This function uses single quotes and brackets to - escape invalid JSONPath characters in JSON keys. - - .. deprecated:: 2.5.0 - The ``json_extract_array`` is deprecated and will be removed in a future version. - Use ``json_query_array`` instead. - - **Examples:** - - >>> import bigframes.pandas as bpd - >>> import bigframes.bigquery as bbq - - >>> s = bpd.Series(['[1, 2, 3]', '[4, 5]']) - >>> bbq.json_extract_array(s) - 0 ['1' '2' '3'] - 1 ['4' '5'] - dtype: list[pyarrow] - - >>> s = bpd.Series([ - ... '{"fruits": [{"name": "apple"}, {"name": "cherry"}]}', - ... '{"fruits": [{"name": "guava"}, {"name": "grapes"}]}' - ... ]) - >>> bbq.json_extract_array(s, "$.fruits") - 0 ['{"name":"apple"}' '{"name":"cherry"}'] - 1 ['{"name":"guava"}' '{"name":"grapes"}'] - dtype: list[pyarrow] - - >>> s = bpd.Series([ - ... '{"fruits": {"color": "red", "names": ["apple","cherry"]}}', - ... '{"fruits": {"color": "green", "names": ["guava", "grapes"]}}' - ... ]) - >>> bbq.json_extract_array(s, "$.fruits.names") - 0 ['"apple"' '"cherry"'] - 1 ['"guava"' '"grapes"'] - dtype: list[pyarrow] - - Args: - input (bigframes.series.Series): - The Series containing JSON data (as native JSON objects or JSON-formatted strings). - json_path (str): - The JSON path identifying the data that you want to obtain from the input. - - Returns: - bigframes.series.Series: A new Series with the parsed arrays from the input. - """ - msg = ( - "The `json_extract_array` is deprecated and will be removed in a future version. " - "Use `json_query_array` instead." - ) - warnings.warn(bfe.format_message(msg), category=UserWarning) - return input._apply_unary_op(ops.JSONExtractArray(json_path=json_path)) - - -def json_extract_string_array( - input: series.Series, - json_path: str = "$", - value_dtype: Optional[ - Union[bigframes.dtypes.Dtype, bigframes.dtypes.DtypeString] - ] = None, -) -> series.Series: - """Extracts a JSON array and converts it to a SQL array of `STRING` values. - A `value_dtype` can be provided to further coerce the data type of the - values in the array. This function uses single quotes and brackets to escape - invalid JSONPath characters in JSON keys. - - .. deprecated:: 2.6.0 - The ``json_extract_string_array`` is deprecated and will be removed in a future version. - Use ``json_value_array`` instead. - - **Examples:** - - >>> import bigframes.pandas as bpd - >>> import bigframes.bigquery as bbq - - >>> s = bpd.Series(['[1, 2, 3]', '[4, 5]']) - >>> bbq.json_extract_string_array(s) - 0 ['1' '2' '3'] - 1 ['4' '5'] - dtype: list[pyarrow] - - >>> bbq.json_extract_string_array(s, value_dtype='Int64') - 0 [1 2 3] - 1 [4 5] - dtype: list[pyarrow] - - >>> s = bpd.Series([ - ... '{"fruits": {"color": "red", "names": ["apple","cherry"]}}', - ... '{"fruits": {"color": "green", "names": ["guava", "grapes"]}}' - ... ]) - >>> bbq.json_extract_string_array(s, "$.fruits.names") - 0 ['apple' 'cherry'] - 1 ['guava' 'grapes'] - dtype: list[pyarrow] - - Args: - input (bigframes.series.Series): - The Series containing JSON data (as native JSON objects or JSON-formatted strings). - json_path (str): - The JSON path identifying the data that you want to obtain from the input. - value_dtype (dtype, Optional): - The data type supported by BigFrames DataFrame. - - Returns: - bigframes.series.Series: A new Series with the parsed arrays from the input. - """ - msg = ( - "The `json_extract_string_array` is deprecated and will be removed in a future version. " - "Use `json_value_array` instead." - ) - warnings.warn(bfe.format_message(msg), category=UserWarning) - array_series = input._apply_unary_op( - ops.JSONExtractStringArray(json_path=json_path) - ) - if value_dtype not in [None, bigframes.dtypes.STRING_DTYPE]: - array_items_series = array_series.explode() - if value_dtype == bigframes.dtypes.BOOL_DTYPE: - array_items_series = array_items_series.str.lower() == "true" - else: - array_items_series = array_items_series.astype(value_dtype) - array_series = cast( - series.Series, - array.array_agg( - array_items_series.groupby(level=input.index.names, dropna=False) - ), - ) - return array_series - - -def json_query( - input: series.Series, - json_path: str, -) -> series.Series: - """Extracts a JSON value and converts it to a SQL JSON-formatted ``STRING`` - or ``JSON`` value. This function uses double quotes to escape invalid JSONPath - characters in JSON keys. For example: ``"a.b"``. - - **Examples:** - - >>> import bigframes.pandas as bpd - >>> import bigframes.bigquery as bbq - - >>> s = bpd.Series(['{"class": {"students": [{"id": 5}, {"id": 12}]}}']) - >>> bbq.json_query(s, json_path="$.class") - 0 {"students":[{"id":5},{"id":12}]} - dtype: string - - Args: - input (bigframes.series.Series): - The Series containing JSON data (as native JSON objects or JSON-formatted strings). - json_path (str): - The JSON path identifying the data that you want to obtain from the input. - - Returns: - bigframes.series.Series: A new Series with the JSON or JSON-formatted STRING. - """ - return input._apply_unary_op(ops.JSONQuery(json_path=json_path)) - - -def json_query_array( - input: series.Series, - json_path: str = "$", -) -> series.Series: - """Extracts a JSON array and converts it to a SQL array of JSON-formatted - `STRING` or `JSON` values. This function uses double quotes to escape invalid - JSONPath characters in JSON keys. For example: `"a.b"`. - - **Examples:** - - >>> import bigframes.pandas as bpd - >>> import bigframes.bigquery as bbq - - >>> s = bpd.Series(['[1, 2, 3]', '[4, 5]']) - >>> bbq.json_query_array(s) - 0 ['1' '2' '3'] - 1 ['4' '5'] - dtype: list[pyarrow] - - >>> s = bpd.Series([ - ... '{"fruits": [{"name": "apple"}, {"name": "cherry"}]}', - ... '{"fruits": [{"name": "guava"}, {"name": "grapes"}]}' - ... ]) - >>> bbq.json_query_array(s, "$.fruits") - 0 ['{"name":"apple"}' '{"name":"cherry"}'] - 1 ['{"name":"guava"}' '{"name":"grapes"}'] - dtype: list[pyarrow] - - >>> s = bpd.Series([ - ... '{"fruits": {"color": "red", "names": ["apple","cherry"]}}', - ... '{"fruits": {"color": "green", "names": ["guava", "grapes"]}}' - ... ]) - >>> bbq.json_query_array(s, "$.fruits.names") - 0 ['"apple"' '"cherry"'] - 1 ['"guava"' '"grapes"'] - dtype: list[pyarrow] - - Args: - input (bigframes.series.Series): - The Series containing JSON data (as native JSON objects or JSON-formatted strings). - json_path (str): - The JSON path identifying the data that you want to obtain from the input. - - Returns: - bigframes.series.Series: A new Series with the parsed arrays from the input. - """ - return input._apply_unary_op(ops.JSONQueryArray(json_path=json_path)) - - -def json_value( - input: series.Series, - json_path: str = "$", -) -> series.Series: - """Extracts a JSON scalar value and converts it to a SQL ``STRING`` value. In - addtion, this function: - - Removes the outermost quotes and unescapes the values. - - Returns a SQL ``NULL`` if a non-scalar value is selected. - - Uses double quotes to escape invalid ``JSON_PATH`` characters in JSON keys. - - **Examples:** - - >>> import bigframes.pandas as bpd - >>> import bigframes.bigquery as bbq - - >>> s = bpd.Series(['{"name": "Jakob", "age": "6"}', '{"name": "Jakob", "age": []}']) - >>> bbq.json_value(s, json_path="$.age") - 0 6 - 1 - dtype: string - - Args: - input (bigframes.series.Series): - The Series containing JSON data (as native JSON objects or JSON-formatted strings). - json_path (str): - The JSON path identifying the data that you want to obtain from the input. - - Returns: - bigframes.series.Series: A new Series with the JSON-formatted STRING. - """ - return input._apply_unary_op(ops.JSONValue(json_path=json_path)) - - -def json_value_array( - input: series.Series, - json_path: str = "$", -) -> series.Series: - """ - Extracts a JSON array of scalar values and converts it to a SQL ``ARRAY`` - value. In addition, this function: - - - Removes the outermost quotes and unescapes the values. - - Returns a SQL ``NULL`` if the selected value isn't an array or not an array - containing only scalar values. - - Uses double quotes to escape invalid ``JSON_PATH`` characters in JSON keys. - - **Examples:** - - >>> import bigframes.pandas as bpd - >>> import bigframes.bigquery as bbq - - >>> s = bpd.Series(['[1, 2, 3]', '[4, 5]']) - >>> bbq.json_value_array(s) - 0 ['1' '2' '3'] - 1 ['4' '5'] - dtype: list[pyarrow] - - >>> s = bpd.Series([ - ... '{"fruits": ["apples", "oranges", "grapes"]', - ... '{"fruits": ["guava", "grapes"]}' - ... ]) - >>> bbq.json_value_array(s, "$.fruits") - 0 ['apples' 'oranges' 'grapes'] - 1 ['guava' 'grapes'] - dtype: list[pyarrow] - - >>> s = bpd.Series([ - ... '{"fruits": {"color": "red", "names": ["apple","cherry"]}}', - ... '{"fruits": {"color": "green", "names": ["guava", "grapes"]}}' - ... ]) - >>> bbq.json_value_array(s, "$.fruits.names") - 0 ['apple' 'cherry'] - 1 ['guava' 'grapes'] - dtype: list[pyarrow] - - Args: - input (bigframes.series.Series): - The Series containing JSON data (as native JSON objects or JSON-formatted strings). - json_path (str): - The JSON path identifying the data that you want to obtain from the input. - - Returns: - bigframes.series.Series: A new Series with the parsed arrays from the input. - """ - return input._apply_unary_op(ops.JSONValueArray(json_path=json_path)) - - -def json_keys( - input: series.Series, - max_depth: Optional[int] = None, -) -> series.Series: - """Returns all keys in the root of a JSON object as an ARRAY of STRINGs. - - **Examples:** - - >>> import bigframes.pandas as bpd - >>> import bigframes.bigquery as bbq - - >>> s = bpd.Series(['{"b": {"c": 2}, "a": 1}'], dtype="json") - >>> bbq.json_keys(s) - 0 ['a' 'b' 'b.c'] - dtype: list[pyarrow] - - Args: - input (bigframes.series.Series): - The Series containing JSON data. - max_depth (int, optional): - Specifies the maximum depth of nested fields to search for keys. If not - provided, searched keys at all levels. - - Returns: - bigframes.series.Series: A new Series containing arrays of keys from the input JSON. - """ - return input._apply_unary_op(ops.JSONKeys(max_depth=max_depth)) - - -def to_json( - input: series.Series, -) -> series.Series: - """Converts a series with a JSON value to a JSON-formatted STRING value. - - **Examples:** - - >>> import bigframes.pandas as bpd - >>> import bigframes.bigquery as bbq - - >>> s = bpd.Series([1, 2, 3]) - >>> bbq.to_json(s) - 0 1 - 1 2 - 2 3 - dtype: extension>[pyarrow] - - >>> s = bpd.Series([{"int": 1, "str": "pandas"}, {"int": 2, "str": "numpy"}]) - >>> bbq.to_json(s) - 0 {"int":1,"str":"pandas"} - 1 {"int":2,"str":"numpy"} - dtype: extension>[pyarrow] - - Args: - input (bigframes.series.Series): - The Series containing JSON or JSON-formatted string values. - - Returns: - bigframes.series.Series: A new Series with the JSON value. - """ - return input._apply_unary_op(ops.ToJSON()) - - -def to_json_string( - input: series.Series, -) -> series.Series: - """Converts a series to a JSON-formatted STRING value. - - **Examples:** - - >>> import bigframes.pandas as bpd - >>> import bigframes.bigquery as bbq - - >>> s = bpd.Series([1, 2, 3]) - >>> bbq.to_json_string(s) - 0 1 - 1 2 - 2 3 - dtype: string - - >>> s = bpd.Series([{"int": 1, "str": "pandas"}, {"int": 2, "str": "numpy"}]) - >>> bbq.to_json_string(s) - 0 {"int":1,"str":"pandas"} - 1 {"int":2,"str":"numpy"} - dtype: string - - Args: - input (bigframes.series.Series): - The Series to be converted. - - Returns: - bigframes.series.Series: A new Series with the JSON-formatted STRING value. - """ - return input._apply_unary_op(ops.ToJSONString()) - - -@utils.preview(name="The JSON-related API `parse_json`") -def parse_json( - input: series.Series, -) -> series.Series: - """Converts a series with a JSON-formatted STRING value to a JSON value. - - .. warning:: - The JSON-related API `parse_json` is in preview. Its behavior may change in - future versions. - - **Examples:** - - >>> import bigframes.pandas as bpd - >>> import bigframes.bigquery as bbq - - >>> s = bpd.Series(['{"class": {"students": [{"id": 5}, {"id": 12}]}}']) - >>> s - 0 {"class": {"students": [{"id": 5}, {"id": 12}]}} - dtype: string - >>> bbq.parse_json(s) - 0 {"class":{"students":[{"id":5},{"id":12}]}} - dtype: extension>[pyarrow] - - Args: - input (bigframes.series.Series): - The Series containing JSON-formatted strings). - - Returns: - bigframes.series.Series: A new Series with the JSON value. - """ - return input._apply_unary_op(ops.ParseJSON()) diff --git a/bigframes/bigquery/_operations/mathematical.py b/bigframes/bigquery/_operations/mathematical.py deleted file mode 100644 index 5e6a299f83f..00000000000 --- a/bigframes/bigquery/_operations/mathematical.py +++ /dev/null @@ -1,121 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from __future__ import annotations - -from typing import Sequence - -import bigframes.core.col -import bigframes.core.expression -from bigframes import dtypes -from bigframes import operations as ops -from bigframes.operations import googlesql - - -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`. - """ - return bigframes.core.col.Expression( - bigframes.core.expression.OpExpression(googlesql.RAND, ()) - ) - - -def hparam_range(min: float, max: float) -> bigframes.core.col.Expression: - """ - Defines the minimum and maximum bounds of the search space of continuous - values for a hyperparameter. - - **Examples:** - - >>> import bigframes.pandas as bpd - >>> import bigframes.bigquery as bbq - >>> # Specify a range of values for a hyperparameter. - >>> learn_rate = bbq.hparam_range(0.0001, 1.0) - - Args: - min (float or int): - The minimum bound of the search space. - max (float or int): - The maximum bound of the search space. - - Returns: - bigframes.pandas.api.typing.Expression: - An expression that can be used in model options. - """ - min_expr = bigframes.core.expression.const(min) - max_expr = bigframes.core.expression.const(max) - - op = ops.SqlScalarOp( - _output_type=dtypes.FLOAT_DTYPE, - sql_template="HPARAM_RANGE({0}, {1})", - is_deterministic=True, - ) - return bigframes.core.col.Expression( - bigframes.core.expression.OpExpression(op, (min_expr, max_expr)) - ) - - -def hparam_candidates( - candidates: Sequence[float | str], -) -> bigframes.core.col.Expression: - """ - Specifies the set of discrete values for the hyperparameter. - - **Examples:** - - >>> import bigframes.pandas as bpd - >>> import bigframes.bigquery as bbq - >>> # Specify a set of values for a hyperparameter. - >>> optimizer = bbq.hparam_candidates(['ADAGRAD', 'SGD', 'FTRL']) - - Args: - candidates (Sequence[float | str]): - The set of discrete values for the hyperparameter. - - Returns: - bigframes.pandas.api.typing.Expression: - An expression that can be used in model options. - """ - candidates_expr = bigframes.core.expression.const(tuple(candidates)) - - op = ops.SqlScalarOp( - _output_type=dtypes.STRING_DTYPE, - sql_template="HPARAM_CANDIDATES({0})", - is_deterministic=True, - ) - return bigframes.core.col.Expression( - bigframes.core.expression.OpExpression(op, (candidates_expr,)) - ) diff --git a/bigframes/bigquery/_operations/ml.py b/bigframes/bigquery/_operations/ml.py deleted file mode 100644 index c6ef1f8bb7a..00000000000 --- a/bigframes/bigquery/_operations/ml.py +++ /dev/null @@ -1,576 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from __future__ import annotations - -from typing import List, Mapping, Optional, Union - -import bigframes_vendored.constants -import google.cloud.bigquery -import pandas as pd - -import bigframes.core.col as col -import bigframes.core.logging.log_adapter as log_adapter -import bigframes.core.sql.ml -import bigframes.dataframe as dataframe -import bigframes.ml.base -import bigframes.session -from bigframes.bigquery._operations import utils - - -def _get_model_metadata( - *, - bqclient: google.cloud.bigquery.Client, - model_name: str, -) -> pd.Series: - model_metadata = bqclient.get_model(model_name) - model_dict = model_metadata.to_api_repr() - return pd.Series(model_dict) - - -@log_adapter.method_logger(custom_base_name="bigquery_ml") -def create_model( - model_name: str, - *, - replace: bool = False, - if_not_exists: bool = False, - # TODO(tswast): Also support bigframes.ml transformer classes and/or - # bigframes.pandas functions? - transform: Optional[list[str]] = None, - input_schema: Optional[Mapping[str, str]] = None, - output_schema: Optional[Mapping[str, str]] = None, - connection_name: Optional[str] = None, - options: Optional[ - Mapping[str, Union[str, int, float, bool, list, "col.Expression"]] - ] = None, - training_data: Optional[Union[pd.DataFrame, dataframe.DataFrame, str]] = None, - custom_holiday: Optional[Union[pd.DataFrame, dataframe.DataFrame, str]] = None, - session: Optional[bigframes.session.Session] = None, -) -> pd.Series: - """ - Creates a BigQuery ML model. - - See the `BigQuery ML CREATE MODEL DDL syntax - `_ - for additional reference. - - Args: - model_name (str): - The name of the model in BigQuery. - replace (bool, default False): - Whether to replace the model if it already exists. - if_not_exists (bool, default False): - Whether to ignore the error if the model already exists. - transform (list[str], optional): - A list of SQL transformations for the TRANSFORM clause, which - specifies the preprocessing steps to apply to the input data. - input_schema (Mapping[str, str], optional): - The INPUT clause, which specifies the schema of the input data. - output_schema (Mapping[str, str], optional): - The OUTPUT clause, which specifies the schema of the output data. - connection_name (str, optional): - The connection to use for the model. - options (Mapping[str, Union[str, int, float, bool, list, bigframes.core.col.Expression]], optional): - The OPTIONS clause, which specifies the model options. - training_data (Union[bigframes.pandas.DataFrame, str], optional): - The query or DataFrame to use for training the model. - custom_holiday (Union[bigframes.pandas.DataFrame, str], optional): - The query or DataFrame to use for custom holiday data. - session (bigframes.session.Session, optional): - The session to use. If not provided, the default session is used. - - Returns: - pandas.Series: - A Series with object dtype containing the model metadata. Reference - the `BigQuery Model REST API reference - `_ - for available fields. - - """ - import bigframes.pandas as bpd - - training_data_sql = ( - utils.to_sql(training_data) if training_data is not None else None - ) - custom_holiday_sql = ( - utils.to_sql(custom_holiday) if custom_holiday is not None else None - ) - - # Determine session from DataFrames if not provided - if session is None: - # Try to get session from inputs - dfs = [ - obj - for obj in [training_data, custom_holiday] - if isinstance(obj, dataframe.DataFrame) - ] - if dfs: - session = dfs[0]._session - - sql = bigframes.core.sql.ml.create_model_ddl( - model_name=model_name, - replace=replace, - if_not_exists=if_not_exists, - transform=transform, - input_schema=input_schema, - output_schema=output_schema, - connection_name=connection_name, - options=options, - training_data=training_data_sql, - custom_holiday=custom_holiday_sql, - ) - - if session is None: - bpd.read_gbq_query(sql) - session = bpd.get_global_session() - assert session is not None, ( - f"Missing connection to BigQuery. Please report how you encountered this error at {bigframes_vendored.constants.FEEDBACK_LINK}." - ) - else: - session.read_gbq_query(sql) - - return _get_model_metadata(bqclient=session.bqclient, model_name=model_name) - - -@log_adapter.method_logger(custom_base_name="bigquery_ml") -def evaluate( - model: Union[bigframes.ml.base.BaseEstimator, str, pd.Series], - input_: Optional[Union[pd.DataFrame, dataframe.DataFrame, str]] = None, - *, - perform_aggregation: Optional[bool] = None, - horizon: Optional[int] = None, - confidence_level: Optional[float] = None, -) -> dataframe.DataFrame: - """ - Evaluates a BigQuery ML model. - - See the `BigQuery ML EVALUATE function syntax - `_ - for additional reference. - - Args: - model (bigframes.ml.base.BaseEstimator or str): - The model to evaluate. - input_ (Union[bigframes.pandas.DataFrame, str], optional): - The DataFrame or query to use for evaluation. If not provided, the - evaluation data from training is used. - perform_aggregation (bool, optional): - A BOOL value that indicates the level of evaluation for forecasting - accuracy. If you specify TRUE, then the forecasting accuracy is on - the time series level. If you specify FALSE, the forecasting - accuracy is on the timestamp level. The default value is TRUE. - horizon (int, optional): - An INT64 value that specifies the number of forecasted time points - against which the evaluation metrics are computed. The default value - is the horizon value specified in the CREATE MODEL statement for the - time series model, or 1000 if unspecified. When evaluating multiple - time series at the same time, this parameter applies to each time - series. - confidence_level (float, optional): - 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: - bigframes.pandas.DataFrame: - The evaluation results. - """ - import bigframes.pandas as bpd - - model_name, session = utils.get_model_name_and_session(model, input_) - table_sql = utils.to_sql(input_) if input_ is not None else None - - sql = bigframes.core.sql.ml.evaluate( - model_name=model_name, - table=table_sql, - perform_aggregation=perform_aggregation, - horizon=horizon, - confidence_level=confidence_level, - ) - - if session is None: - return bpd.read_gbq_query(sql) - else: - return session.read_gbq_query(sql) - - -@log_adapter.method_logger(custom_base_name="bigquery_ml") -def predict( - model: Union[bigframes.ml.base.BaseEstimator, str, pd.Series], - input_: Union[pd.DataFrame, dataframe.DataFrame, str], - *, - threshold: Optional[float] = None, - keep_original_columns: Optional[bool] = None, - trial_id: Optional[int] = None, -) -> dataframe.DataFrame: - """ - Runs prediction on a BigQuery ML model. - - See the `BigQuery ML PREDICT function syntax - `_ - for additional reference. - - Args: - model (bigframes.ml.base.BaseEstimator or str): - The model to use for prediction. - input_ (Union[bigframes.pandas.DataFrame, str]): - The DataFrame or query to use for prediction. - threshold (float, optional): - The threshold to use for classification models. - keep_original_columns (bool, optional): - Whether to keep the original columns in the output. - trial_id (int, optional): - An INT64 value that identifies the hyperparameter tuning trial that - you want the function to evaluate. The function uses the optimal - trial by default. Only specify this argument if you ran - hyperparameter tuning when creating the model. - - Returns: - bigframes.pandas.DataFrame: - The prediction results. - """ - import bigframes.pandas as bpd - - model_name, session = utils.get_model_name_and_session(model, input_) - table_sql = utils.to_sql(input_) - - sql = bigframes.core.sql.ml.predict( - model_name=model_name, - table=table_sql, - threshold=threshold, - keep_original_columns=keep_original_columns, - trial_id=trial_id, - ) - - if session is None: - return bpd.read_gbq_query(sql) - else: - return session.read_gbq_query(sql) - - -@log_adapter.method_logger(custom_base_name="bigquery_ml") -def explain_predict( - model: Union[bigframes.ml.base.BaseEstimator, str, pd.Series], - input_: Union[pd.DataFrame, dataframe.DataFrame, str], - *, - top_k_features: Optional[int] = None, - threshold: Optional[float] = None, - integrated_gradients_num_steps: Optional[int] = None, - approx_feature_contrib: Optional[bool] = None, -) -> dataframe.DataFrame: - """ - Runs explainable prediction on a BigQuery ML model. - - See the `BigQuery ML EXPLAIN_PREDICT function syntax - `_ - for additional reference. - - Args: - model (bigframes.ml.base.BaseEstimator or str): - The model to use for prediction. - input_ (Union[bigframes.pandas.DataFrame, str]): - The DataFrame or query to use for prediction. - top_k_features (int, optional): - The number of top features to return. - threshold (float, optional): - The threshold for binary classification models. - integrated_gradients_num_steps (int, optional): - an INT64 value that specifies the number of steps to sample between - the example being explained and its baseline. This value is used to - approximate the integral in integrated gradients attribution - methods. Increasing the value improves the precision of feature - attributions, but can be slower and more computationally expensive. - approx_feature_contrib (bool, optional): - A BOOL value that indicates whether to use an approximate feature - contribution method in the XGBoost model explanation. - - Returns: - bigframes.pandas.DataFrame: - The prediction results with explanations. - """ - import bigframes.pandas as bpd - - model_name, session = utils.get_model_name_and_session(model, input_) - table_sql = utils.to_sql(input_) - - sql = bigframes.core.sql.ml.explain_predict( - model_name=model_name, - table=table_sql, - top_k_features=top_k_features, - threshold=threshold, - integrated_gradients_num_steps=integrated_gradients_num_steps, - approx_feature_contrib=approx_feature_contrib, - ) - - if session is None: - return bpd.read_gbq_query(sql) - else: - return session.read_gbq_query(sql) - - -@log_adapter.method_logger(custom_base_name="bigquery_ml") -def global_explain( - model: Union[bigframes.ml.base.BaseEstimator, str, pd.Series], - *, - class_level_explain: Optional[bool] = None, -) -> dataframe.DataFrame: - """ - Gets global explanations for a BigQuery ML model. - - See the `BigQuery ML GLOBAL_EXPLAIN function syntax - `_ - for additional reference. - - Args: - model (bigframes.ml.base.BaseEstimator or str): - The model to get explanations from. - class_level_explain (bool, optional): - Whether to return class-level explanations. - - Returns: - bigframes.pandas.DataFrame: - The global explanation results. - """ - import bigframes.pandas as bpd - - model_name, session = utils.get_model_name_and_session(model) - sql = bigframes.core.sql.ml.global_explain( - model_name=model_name, - class_level_explain=class_level_explain, - ) - - if session is None: - return bpd.read_gbq_query(sql) - else: - return session.read_gbq_query(sql) - - -@log_adapter.method_logger(custom_base_name="bigquery_ml") -def transform( - model: Union[bigframes.ml.base.BaseEstimator, str, pd.Series], - input_: Union[pd.DataFrame, dataframe.DataFrame, str], -) -> dataframe.DataFrame: - """ - Transforms input data using a BigQuery ML model. - - See the `BigQuery ML TRANSFORM function syntax - `_ - for additional reference. - - Args: - model (bigframes.ml.base.BaseEstimator or str): - The model to use for transformation. - input_ (Union[bigframes.pandas.DataFrame, str]): - The DataFrame or query to use for transformation. - - Returns: - bigframes.pandas.DataFrame: - The transformed data. - """ - import bigframes.pandas as bpd - - model_name, session = utils.get_model_name_and_session(model, input_) - table_sql = utils.to_sql(input_) - - sql = bigframes.core.sql.ml.transform( - model_name=model_name, - table=table_sql, - ) - - if session is None: - return bpd.read_gbq_query(sql) - else: - return session.read_gbq_query(sql) - - -@log_adapter.method_logger(custom_base_name="bigquery_ml") -def generate_text( - model: Union[bigframes.ml.base.BaseEstimator, str, pd.Series], - input_: Union[pd.DataFrame, dataframe.DataFrame, str], - *, - temperature: Optional[float] = None, - max_output_tokens: Optional[int] = None, - top_k: Optional[int] = None, - top_p: Optional[float] = None, - flatten_json_output: Optional[bool] = None, - stop_sequences: Optional[List[str]] = None, - ground_with_google_search: Optional[bool] = None, - request_type: Optional[str] = None, -) -> dataframe.DataFrame: - """ - Generates text using a BigQuery ML model. - - See the `BigQuery ML GENERATE_TEXT function syntax - `_ - for additional reference. - - Args: - model (bigframes.ml.base.BaseEstimator or str): - The model to use for text generation. - input_ (Union[bigframes.pandas.DataFrame, str]): - The DataFrame or query to use for text generation. - temperature (float, optional): - A FLOAT64 value that is used for sampling promiscuity. The value - must be in the range ``[0.0, 1.0]``. A lower temperature works well - for prompts that expect a more deterministic and less open-ended - or creative response, while a higher temperature can lead to more - diverse or creative results. A temperature of ``0`` is - deterministic, meaning that the highest probability response is - always selected. - max_output_tokens (int, optional): - An INT64 value that sets the maximum number of tokens in the - generated text. - top_k (int, optional): - An INT64 value that changes how the model selects tokens for - output. A ``top_k`` of ``1`` means the next selected token is the - most probable among all tokens in the model's vocabulary. A - ``top_k`` of ``3`` means that the next token is selected from - among the three most probable tokens by using temperature. The - default value is ``40``. - top_p (float, optional): - A FLOAT64 value that changes how the model selects tokens for - output. Tokens are selected from most probable to least probable - until the sum of their probabilities equals the ``top_p`` value. - For example, if tokens A, B, and C have a probability of 0.3, 0.2, - and 0.1 and the ``top_p`` value is ``0.5``, then the model will - select either A or B as the next token by using temperature. The - default value is ``0.95``. - flatten_json_output (bool, optional): - A BOOL value that determines the content of the generated JSON column. - stop_sequences (List[str], optional): - An ARRAY value that contains the stop sequences for the model. - ground_with_google_search (bool, optional): - A BOOL value that determines whether to ground the model with Google Search. - request_type (str, optional): - A STRING value that contains the request type for the model. - - Returns: - bigframes.pandas.DataFrame: - The generated text. - """ - import bigframes.pandas as bpd - - model_name, session = utils.get_model_name_and_session(model, input_) - table_sql = utils.to_sql(input_) - - sql = bigframes.core.sql.ml.generate_text( - model_name=model_name, - table=table_sql, - temperature=temperature, - max_output_tokens=max_output_tokens, - top_k=top_k, - top_p=top_p, - flatten_json_output=flatten_json_output, - stop_sequences=stop_sequences, - ground_with_google_search=ground_with_google_search, - request_type=request_type, - ) - - if session is None: - return bpd.read_gbq_query(sql) - else: - return session.read_gbq_query(sql) - - -@log_adapter.method_logger(custom_base_name="bigquery_ml") -def get_insights( - model: Union[bigframes.ml.base.BaseEstimator, str, pd.Series], -) -> dataframe.DataFrame: - """ - Gets insights from a BigQuery ML model. - - See the `BigQuery ML GET_INSIGHTS function syntax - `_ - for additional reference. - - Args: - model (bigframes.ml.base.BaseEstimator, str, or pd.Series): - The model to get insights from. - - Returns: - bigframes.pandas.DataFrame: - The insights. - """ - import bigframes.pandas as bpd - - model_name, session = utils.get_model_name_and_session(model) - - sql = bigframes.core.sql.ml.get_insights( - model_name=model_name, - ) - - if session is None: - return bpd.read_gbq_query(sql) - else: - return session.read_gbq_query(sql) - - -@log_adapter.method_logger(custom_base_name="bigquery_ml") -def generate_embedding( - model: Union[bigframes.ml.base.BaseEstimator, str, pd.Series], - input_: Union[pd.DataFrame, dataframe.DataFrame, str], - *, - flatten_json_output: Optional[bool] = None, - task_type: Optional[str] = None, - output_dimensionality: Optional[int] = None, -) -> dataframe.DataFrame: - """ - Generates text embedding using a BigQuery ML model. - - See the `BigQuery ML GENERATE_EMBEDDING function syntax - `_ - for additional reference. - - Args: - model (bigframes.ml.base.BaseEstimator or str): - The model to use for text embedding. - input_ (Union[bigframes.pandas.DataFrame, str]): - The DataFrame or query to use for text embedding. - flatten_json_output (bool, optional): - A BOOL value that determines the content of the generated JSON column. - task_type (str, optional): - A STRING value that specifies the intended downstream application task. - Supported values are: - - `RETRIEVAL_QUERY` - - `RETRIEVAL_DOCUMENT` - - `SEMANTIC_SIMILARITY` - - `CLASSIFICATION` - - `CLUSTERING` - - `QUESTION_ANSWERING` - - `FACT_VERIFICATION` - - `CODE_RETRIEVAL_QUERY` - output_dimensionality (int, optional): - An INT64 value that specifies the size of the output embedding. - - Returns: - bigframes.pandas.DataFrame: - The generated text embedding. - """ - import bigframes.pandas as bpd - - model_name, session = utils.get_model_name_and_session(model, input_) - table_sql = utils.to_sql(input_) - - sql = bigframes.core.sql.ml.generate_embedding( - model_name=model_name, - table=table_sql, - flatten_json_output=flatten_json_output, - task_type=task_type, - output_dimensionality=output_dimensionality, - ) - - if session is None: - return bpd.read_gbq_query(sql) - else: - return session.read_gbq_query(sql) diff --git a/bigframes/bigquery/_operations/obj.py b/bigframes/bigquery/_operations/obj.py deleted file mode 100644 index ca09d7ab1ce..00000000000 --- a/bigframes/bigquery/_operations/obj.py +++ /dev/null @@ -1,114 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - - -"""This module exposes BigQuery ObjectRef functions. - -See bigframes.bigquery.obj for public docs. -""" - -from __future__ import annotations - -import datetime -from typing import Optional, Sequence, Union - -import numpy as np -import pandas as pd - -import bigframes.core.utils as utils -import bigframes.operations as ops -import bigframes.series as series -from bigframes.core import convert -from bigframes.core.logging import log_adapter - - -@log_adapter.method_logger(custom_base_name="bigquery_obj") -def fetch_metadata( - objectref: series.Series, -) -> series.Series: - """[Preview] The OBJ.FETCH_METADATA function returns Cloud Storage metadata for a partially populated ObjectRef value. - - Args: - objectref (bigframes.pandas.Series): - A partially populated ObjectRef value, in which the uri and authorizer fields are populated and the details field isn't. - - Returns: - bigframes.pandas.Series: A fully populated ObjectRef value. The metadata is provided in the details field of the returned ObjectRef value. - """ - objectref = convert.to_bf_series(objectref, default_index=None) - return objectref._apply_unary_op(ops.obj_fetch_metadata_op) - - -@log_adapter.method_logger(custom_base_name="bigquery_obj") -def get_access_url( - objectref: series.Series, - mode: str, - duration: Optional[Union[datetime.timedelta, pd.Timedelta, np.timedelta64]] = None, -) -> series.Series: - """[Preview] The OBJ.GET_ACCESS_URL function returns JSON that contains reference information for the input ObjectRef value, and also access URLs that you can use to read or modify the Cloud Storage object. - - Args: - objectref (bigframes.pandas.Series): - An ObjectRef value that represents a Cloud Storage object. - mode (str): - A STRING value that identifies the type of URL that you want to be returned. The following values are supported: - 'r': Returns a URL that lets you read the object. - 'rw': Returns two URLs, one that lets you read the object, and one that lets you modify the object. - duration (Union[datetime.timedelta, pandas.Timedelta, numpy.timedelta64], optional): - An optional INTERVAL value that specifies how long the generated access URLs remain valid. You can specify a value between 30 minutes and 6 hours. For example, you could specify INTERVAL 2 HOUR to generate URLs that expire after 2 hours. The default value is 6 hours. - - Returns: - bigframes.pandas.Series: A JSON value that contains the Cloud Storage object reference information from the input ObjectRef value, and also one or more URLs that you can use to access the Cloud Storage object. - """ - objectref = convert.to_bf_series(objectref, default_index=None) - - duration_micros = None - if duration is not None: - duration_micros = utils.timedelta_to_micros(duration) - - return objectref._apply_unary_op( - ops.ObjGetAccessUrl(mode=mode, duration=duration_micros) - ) - - -@log_adapter.method_logger(custom_base_name="bigquery_obj") -def make_ref( - uri_or_json: Union[series.Series, Sequence[str]], - authorizer: Union[series.Series, str, None] = None, -) -> series.Series: - """[Preview] Use the OBJ.MAKE_REF function to create an ObjectRef value that contains reference information for a Cloud Storage object. - - Args: - uri_or_json (bigframes.pandas.Series or str): - A series of STRING values that contains the URI for the Cloud Storage object, for example, gs://mybucket/flowers/12345.jpg. - OR - A series of JSON value that represents a Cloud Storage object. - authorizer (bigframes.pandas.Series or str, optional): - A STRING value that contains the Cloud Resource connection used to access the Cloud Storage object. - Required if ``uri_or_json`` is a URI string. - - Returns: - bigframes.pandas.Series: An ObjectRef value. - """ - uri_or_json = convert.to_bf_series(uri_or_json, default_index=None) - - if authorizer is not None: - # Avoid join problems encountered if we try to convert a literal into Series. - if not isinstance(authorizer, str): - authorizer = convert.to_bf_series(authorizer, default_index=None) - - return uri_or_json._apply_binary_op(authorizer, ops.obj_make_ref_op) - - # If authorizer is not provided, we assume uri_or_json is a JSON objectref - return uri_or_json._apply_unary_op(ops.obj_make_ref_json_op) diff --git a/bigframes/bigquery/_operations/search.py b/bigframes/bigquery/_operations/search.py deleted file mode 100644 index b65eed24753..00000000000 --- a/bigframes/bigquery/_operations/search.py +++ /dev/null @@ -1,249 +0,0 @@ -# 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. -# 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 json -import typing -from typing import Collection, Literal, Mapping, Optional, Union - -import google.cloud.bigquery as bigquery - -import bigframes.ml.utils as utils - -if typing.TYPE_CHECKING: - import bigframes.dataframe as dataframe - import bigframes.series as series - import bigframes.session - -""" -Search functions defined from -https://cloud.google.com/bigquery/docs/reference/standard-sql/search_functions -""" - - -def create_vector_index( - table_id: str, - column_name: str, - *, - replace: bool = False, - index_name: Optional[str] = None, - distance_type="cosine", - stored_column_names: Collection[str] = (), - index_type: str = "ivf", - ivf_options: Optional[Mapping] = None, - tree_ah_options: Optional[Mapping] = None, - session: Optional[bigframes.session.Session] = None, -) -> None: - """ - Creates a new vector index on a column of a table. - - This method calls the `CREATE VECTOR INDEX DDL statement - `_. - - """ - import bigframes.pandas - - if index_name is None: - table_ref = bigquery.TableReference.from_string(table_id) - index_name = table_ref.table_id - - options = { - "index_type": index_type.upper(), - "distance_type": distance_type.upper(), - } - - if ivf_options is not None: - options["ivf_options"] = json.dumps(ivf_options) - - if tree_ah_options is not None: - options["tree_ah_options"] = json.dumps(tree_ah_options) - - sql = bigframes.core.sql.create_vector_index_ddl( - replace=replace, - index_name=index_name, - table_name=table_id, - column_name=column_name, - stored_column_names=stored_column_names, - options=options, - ) - - # Use global read_gbq to execute this for better location autodetection. - if session is None: - read_gbq_query = bigframes.pandas.read_gbq_query - else: - read_gbq_query = session.read_gbq_query - - read_gbq_query(sql) - - -def vector_search( - base_table: str, - column_to_search: str, - query: Union[dataframe.DataFrame, series.Series], - *, - query_column_to_search: Optional[str] = None, - top_k: Optional[int] = None, - distance_type: Optional[Literal["euclidean", "cosine", "dot_product"]] = None, - fraction_lists_to_search: Optional[float] = None, - use_brute_force: Optional[bool] = None, - allow_large_results: Optional[bool] = None, -) -> dataframe.DataFrame: - """ - Conduct vector search which searches embeddings to find semantically similar entities. - - This method calls the `VECTOR_SEARCH() SQL function - `_. - - **Examples:** - - - >>> import bigframes.pandas as bpd - >>> import bigframes.bigquery as bbq - - DataFrame embeddings for which to find nearest neighbors. The ``ARRAY`` column - is used as the search query: - - >>> search_query = bpd.DataFrame({"query_id": ["dog", "cat"], - ... "embedding": [[1.0, 2.0], [3.0, 5.2]]}) - >>> bbq.vector_search( - ... base_table="bigframes-dev.bigframes_tests_sys.base_table", - ... column_to_search="my_embedding", - ... query=search_query, - ... top_k=2).sort_values("id") - query_id embedding id my_embedding distance - 0 dog [1. 2.] 1 [1. 2.] 0.0 - 1 cat [3. 5.2] 2 [2. 4.] 1.56205 - 0 dog [1. 2.] 4 [1. 3.2] 1.2 - 1 cat [3. 5.2] 5 [5. 5.4] 2.009975 - - [4 rows x 5 columns] - - Series embeddings for which to find nearest neighbors: - - >>> search_query = bpd.Series([[1.0, 2.0], [3.0, 5.2]], - ... index=["dog", "cat"], - ... name="embedding") - >>> bbq.vector_search( - ... base_table="bigframes-dev.bigframes_tests_sys.base_table", - ... column_to_search="my_embedding", - ... query=search_query, - ... top_k=2, - ... use_brute_force=True).sort_values("id") - embedding id my_embedding distance - dog [1. 2.] 1 [1. 2.] 0.0 - cat [3. 5.2] 2 [2. 4.] 1.56205 - dog [1. 2.] 4 [1. 3.2] 1.2 - cat [3. 5.2] 5 [5. 5.4] 2.009975 - - [4 rows x 4 columns] - - You can specify the name of the column in the query DataFrame embeddings and distance type. - If you specify query_column_to_search_value, it will use the provided column which contains - the embeddings for which to find nearest neighbors. Otherwiese, it uses the column_to_search value. - - >>> search_query = bpd.DataFrame({"query_id": ["dog", "cat"], - ... "embedding": [[1.0, 2.0], [3.0, 5.2]], - ... "another_embedding": [[0.7, 2.2], [3.3, 5.2]]}) - >>> bbq.vector_search( - ... base_table="bigframes-dev.bigframes_tests_sys.base_table", - ... column_to_search="my_embedding", - ... query=search_query, - ... distance_type="cosine", - ... query_column_to_search="another_embedding", - ... top_k=2).sort_values("id") - query_id embedding another_embedding id my_embedding distance - 1 cat [3. 5.2] [3.3 5.2] 1 [1. 2.] 0.005181 - 1 cat [3. 5.2] [3.3 5.2] 2 [2. 4.] 0.005181 - 0 dog [1. 2.] [0.7 2.2] 3 [1.5 7. ] 0.004697 - 0 dog [1. 2.] [0.7 2.2] 4 [1. 3.2] 0.000013 - - [4 rows x 6 columns] - - Args: - base_table (str): - The table to search for nearest neighbor embeddings. - column_to_search (str): - The name of the base table column to search for nearest neighbor embeddings. - The column must have a type of ``ARRAY``. All elements in the array must be non-NULL. - query (bigframes.dataframe.DataFrame | bigframes.dataframe.Series): - A Series or DataFrame that provides the embeddings for which to find nearest neighbors. - query_column_to_search (str): - Specifies the name of the column in the query that contains the embeddings for which to - find nearest neighbors. The column must have a type of ``ARRAY``. All elements in - the array must be non-NULL and all values in the column must have the same array dimensions - as the values in the ``column_to_search`` column. Can only be set when query is a DataFrame. - top_k (int): - Sepecifies the number of nearest neighbors to return. Default to 10. - distance_type (str, defalt "euclidean"): - Specifies the type of metric to use to compute the distance between two vectors. - Possible values are "euclidean", "cosine" and "dot_product". - Default to "euclidean". - fraction_lists_to_search (float, range in [0.0, 1.0]): - Specifies the percentage of lists to search. Specifying a higher percentage leads to - higher recall and slower performance, and the converse is true when specifying a lower - percentage. It is only used when a vector index is also used. You can only specify - ``fraction_lists_to_search`` when ``use_brute_force`` is set to False. - use_brute_force (bool): - Determines whether to use brute force search by skipping the vector index if one is available. - Default to False. - allow_large_results (bool, optional): - Whether to allow large query results. If ``True``, the query - results can be larger than the maximum response size. - Defaults to ``bpd.options.compute.allow_large_results``. - - Returns: - bigframes.dataframe.DataFrame: A DataFrame containing vector search result. - """ - import bigframes.series - - if ( - isinstance(query, bigframes.series.Series) - and query_column_to_search is not None - ): - raise ValueError( - "You can't specify query_column_to_search when query is a Series." - ) - - # Only populate options if not set to the default value. - # This avoids accidentally setting options that are mutually exclusive. - options = None - if fraction_lists_to_search is not None: - options = {} if options is None else options - options["fraction_lists_to_search"] = fraction_lists_to_search - if use_brute_force is not None: - options = {} if options is None else options - options["use_brute_force"] = use_brute_force - - (query,) = utils.batch_convert_to_dataframe(query) - sql_string, index_col_ids, index_labels = query._to_sql_query(include_index=True) - - sql = bigframes.core.sql.create_vector_search_sql( - sql_string=sql_string, - base_table=base_table, - column_to_search=column_to_search, - query_column_to_search=query_column_to_search, - top_k=top_k, - distance_type=distance_type, - options=options, - ) - if index_col_ids is not None: - df = query._session.read_gbq_query( - sql, index_col=index_col_ids, allow_large_results=allow_large_results - ) - df.index.names = index_labels - else: - df = query._session.read_gbq_query(sql, allow_large_results=allow_large_results) - - return df diff --git a/bigframes/bigquery/_operations/sql.py b/bigframes/bigquery/_operations/sql.py deleted file mode 100644 index 332d558866b..00000000000 --- a/bigframes/bigquery/_operations/sql.py +++ /dev/null @@ -1,147 +0,0 @@ -# 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. -# 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. - -"""SQL escape hatch features.""" - -from __future__ import annotations - -from typing import Optional, Sequence, Union, cast - -import google.cloud.bigquery - -import bigframes.dataframe -import bigframes.dtypes -import bigframes.operations -import bigframes.series -from bigframes.core.compile.sqlglot import sql - - -def _format_names(sql_template: str, dataframe: bigframes.dataframe.DataFrame): - """Turn sql_template from a template that uses names to one that uses - numbers. - """ - names_to_numbers = {name: f"{{{i}}}" for i, name in enumerate(dataframe.columns)} - numbers = [f"{{{i}}}" for i in range(len(dataframe.columns))] - return sql_template.format(*numbers, **names_to_numbers) - - -def sql_scalar( - sql_template: str, - columns: Union[bigframes.dataframe.DataFrame, Sequence[bigframes.series.Series]], - *, - output_dtype: Optional[bigframes.dtypes.Dtype] = None, -) -> bigframes.series.Series: - """Create a Series from a SQL template. - - **Examples:** - - >>> import bigframes.pandas as bpd - >>> import bigframes.bigquery as bbq - - Either pass in a sequence of series, in which case use integers in the - format strings. - - >>> s = bpd.Series(["1.5", "2.5", "3.5"]) - >>> s = s.astype(pd.ArrowDtype(pa.decimal128(38, 9))) - >>> bbq.sql_scalar("ROUND({0}, 0, 'ROUND_HALF_EVEN')", [s]) - 0 2.000000000 - 1 2.000000000 - 2 4.000000000 - dtype: decimal128(38, 9)[pyarrow] - - Or pass in a DataFrame, in which case use the column names in the format - strings. - - >>> df = bpd.DataFrame({"a": ["1.5", "2.5", "3.5"]}) - >>> df = df.astype({"a": pd.ArrowDtype(pa.decimal128(38, 9))}) - >>> bbq.sql_scalar("ROUND({a}, 0, 'ROUND_HALF_EVEN')", df) - 0 2.000000000 - 1 2.000000000 - 2 4.000000000 - dtype: decimal128(38, 9)[pyarrow] - - You can also use the `.bigquery` DataFrame accessor to apply a SQL scalar function. - - Compute SQL scalar using a pandas DataFrame: - - >>> import pandas as pd - >>> df = pd.DataFrame({"x": [1, 2, 3]}) - >>> bpd.options.display.progress_bar = None # doctest: +SKIP - >>> pandas_s = df.bigquery.sql_scalar("POW({0}, 2)") # doctest: +SKIP - >>> type(pandas_s) # doctest: +SKIP - - - Compute SQL scalar using a BigFrames DataFrame: - - >>> bf_df = bpd.DataFrame({"x": [1, 2, 3]}) - >>> bf_s = bf_df.bigquery.sql_scalar("POW({0}, 2)") # doctest: +SKIP - >>> type(bf_s) # doctest: +SKIP - - - - Args: - sql_template (str): - A SQL format string with Python-style {0} placeholders for each of - the Series objects in ``columns``. - columns ( - Sequence[bigframes.pandas.Series] | bigframes.pandas.DataFrame - ): - Series objects representing the column inputs to the - ``sql_template``. Must contain at least one Series. - output_dtype (a BigQuery DataFrames compatible dtype, optional): - If provided, BigQuery DataFrames uses this to determine the output - of the returned Series. This avoids a dry run query. - - Returns: - bigframes.pandas.Series: - A Series with the SQL applied. - - Raises: - ValueError: If ``columns`` is empty. - """ - if isinstance(columns, bigframes.dataframe.DataFrame): - sql_template = _format_names(sql_template, columns) - columns = [ - cast(bigframes.series.Series, columns[column]) for column in columns.columns - ] - - if len(columns) == 0: - raise ValueError("Must provide at least one column in columns") - - base_series = columns[0] - - # To integrate this into our expression trees, we need to get the output - # type, so we do some manual compilation and a dry run query to get that. - # Another benefit of this is that if there is a syntax error in the SQL - # template, then this will fail with an error earlier in the process, - # aiding users in debugging. - if output_dtype is None: - literals_sql = [ - sql.to_sql(sql.literal(None, column.dtype)) for column in columns - ] - select_sql = sql_template.format(*literals_sql) - dry_run_sql = f"SELECT {select_sql}" - - # Use the executor directly, because we want the original column IDs, not - # the user-friendly column names that block.to_sql_query() would produce. - bqclient = base_series._session.bqclient - job = bqclient.query( - dry_run_sql, job_config=google.cloud.bigquery.QueryJobConfig(dry_run=True) - ) - _, output_dtype = bigframes.dtypes.convert_schema_field(job.schema[0]) - - op = bigframes.operations.SqlScalarOp( - _output_type=output_dtype, sql_template=sql_template - ) - return base_series._apply_nary_op(op, columns[1:]) diff --git a/bigframes/bigquery/_operations/struct.py b/bigframes/bigquery/_operations/struct.py deleted file mode 100644 index 2ee760fb8e5..00000000000 --- a/bigframes/bigquery/_operations/struct.py +++ /dev/null @@ -1,61 +0,0 @@ -# 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. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - - -"""This module integrates BigQuery built-in functions for use with DataFrame objects, -such as array functions: -https://cloud.google.com/bigquery/docs/reference/standard-sql/array_functions.""" - -from __future__ import annotations - -import typing - -import bigframes.operations as ops -import bigframes.series as series - -if typing.TYPE_CHECKING: - import bigframes.dataframe as dataframe - - -def struct(value: dataframe.DataFrame) -> series.Series: - """Takes a DataFrame and converts it into a Series of structs with each - struct entry corresponding to a DataFrame row and each struct field - corresponding to a DataFrame column - - **Examples:** - - >>> import bigframes.pandas as bpd - >>> import bigframes.bigquery as bbq - >>> import bigframes.series as series - - >>> srs = series.Series([{"version": 1, "project": "pandas"}, {"version": 2, "project": "numpy"},]) - >>> df = srs.struct.explode() - >>> bbq.struct(df) - 0 {'version': 1, 'project': 'pandas'} - 1 {'version': 2, 'project': 'numpy'} - dtype: struct[pyarrow] - - Args: - value (bigframes.dataframe.DataFrame): - The DataFrame to be converted to a Series of structs - - Returns: - bigframes.series.Series: A new Series with struct entries representing rows of the original DataFrame - """ - block = value._block - block, result_id = block.apply_nary_op( - block.value_columns, ops.StructOp(column_names=tuple(block.column_labels)) - ) - block = block.select_column(result_id).with_column_labels([None]) - return series.Series(block) diff --git a/bigframes/bigquery/_operations/table.py b/bigframes/bigquery/_operations/table.py deleted file mode 100644 index cad025412d5..00000000000 --- a/bigframes/bigquery/_operations/table.py +++ /dev/null @@ -1,101 +0,0 @@ -# Copyright 2026 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 typing import Mapping, Optional, Union - -import google.cloud.bigquery -import pandas as pd - -import bigframes.core.compile.sqlglot.sql as sg_sql -import bigframes.core.logging.log_adapter as log_adapter -import bigframes.session - - -def _get_table_metadata( - *, - bqclient: google.cloud.bigquery.Client, - table_name: str, -) -> pd.Series: - table_metadata = bqclient.get_table(table_name) - table_dict = table_metadata.to_api_repr() - return pd.Series(table_dict) - - -@log_adapter.method_logger(custom_base_name="bigquery_table") -def create_external_table( - table_name: str, - *, - replace: bool = False, - if_not_exists: bool = False, - columns: Optional[Mapping[str, str]] = None, - partition_columns: Optional[Mapping[str, str]] = None, - connection_name: Optional[str] = None, - options: Mapping[str, Union[str, int, float, bool, list]], - session: Optional[bigframes.session.Session] = None, -) -> pd.Series: - """ - Creates a BigQuery external table. - - See the `BigQuery CREATE EXTERNAL TABLE DDL syntax - `_ - for additional reference. - - Args: - table_name (str): - The name of the table in BigQuery. - replace (bool, default False): - Whether to replace the table if it already exists. - if_not_exists (bool, default False): - Whether to ignore the error if the table already exists. - columns (Mapping[str, str], optional): - The table's schema. - partition_columns (Mapping[str, str], optional): - The table's partition columns. - connection_name (str, optional): - The connection to use for the table. - options (Mapping[str, Union[str, int, float, bool, list]]): - The OPTIONS clause, which specifies the table options. - session (bigframes.session.Session, optional): - The session to use. If not provided, the default session is used. - - Returns: - pandas.Series: - A Series with object dtype containing the table metadata. Reference - the `BigQuery Table REST API reference - `_ - for available fields. - """ - import bigframes.pandas as bpd - - sql = sg_sql.to_sql( - sg_sql.create_external_table( - table_name=table_name, - replace=replace, - if_not_exists=if_not_exists, - columns=columns, - partition_columns=partition_columns, - connection_name=connection_name, - options=options, - ) - ) - - if session is None: - bpd.read_gbq_query(sql) - session = bpd.get_global_session() - else: - session.read_gbq_query(sql) - - return _get_table_metadata(bqclient=session.bqclient, table_name=table_name) diff --git a/bigframes/bigquery/_operations/utils.py b/bigframes/bigquery/_operations/utils.py deleted file mode 100644 index 0bae8f47c7a..00000000000 --- a/bigframes/bigquery/_operations/utils.py +++ /dev/null @@ -1,70 +0,0 @@ -# Copyright 2026 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 typing import Optional, Union, cast - -import pandas as pd - -import bigframes -from bigframes import dataframe -from bigframes.ml import base as ml_base - - -def get_model_name_and_session( - model: Union[ml_base.BaseEstimator, str, pd.Series], - # Other dataframe arguments to extract session from - *dataframes: Optional[Union[pd.DataFrame, dataframe.DataFrame, str]], -) -> tuple[str, Optional[bigframes.session.Session]]: - if isinstance(model, pd.Series): - try: - model_ref = model["modelReference"] - model_name = f"{model_ref['projectId']}.{model_ref['datasetId']}.{model_ref['modelId']}" # type: ignore - except KeyError: - raise ValueError("modelReference must be present in the pandas Series.") - elif isinstance(model, str): - model_name = model - else: - if model._bqml_model is None: - raise ValueError("Model must be fitted to be used in ML operations.") - return model._bqml_model.model_name, model._bqml_model.session - - session = None - for df in dataframes: - if isinstance(df, dataframe.DataFrame): - session = df._session - break - - return model_name, session - - -def to_sql(df_or_sql: Union[pd.DataFrame, dataframe.DataFrame, str]) -> str: - """ - Helper to convert DataFrame to SQL string - """ - import bigframes.pandas as bpd - - if isinstance(df_or_sql, str): - return df_or_sql - - if isinstance(df_or_sql, pd.DataFrame): - bf_df = bpd.read_pandas(df_or_sql) - else: - bf_df = cast(dataframe.DataFrame, df_or_sql) - - # Cache dataframes to make sure base table is not a snapshot. - # Cached dataframe creates a full copy, never uses snapshot. - # This is a workaround for internal issue b/310266666. - bf_df.cache() - sql, _, _ = bf_df._to_sql_query(include_index=False) - return sql diff --git a/bigframes/bigquery/aead.py b/bigframes/bigquery/aead.py deleted file mode 100644 index c4243a5c010..00000000000 --- a/bigframes/bigquery/aead.py +++ /dev/null @@ -1,25 +0,0 @@ -# Copyright 2026 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. - -"""AEAD encryption functions""" - -from __future__ import annotations - -from bigframes.operations.googlesql.aead import decrypt_bytes, decrypt_string, encrypt - -__all__ = [ - "decrypt_bytes", - "decrypt_string", - "encrypt", -] diff --git a/bigframes/bigquery/ai.py b/bigframes/bigquery/ai.py deleted file mode 100644 index 6dd3d116635..00000000000 --- a/bigframes/bigquery/ai.py +++ /dev/null @@ -1,89 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -""" -Integrate BigQuery built-in AI functions into your BigQuery DataFrames workflow. - -The ``bigframes.bigquery.ai`` module provides a Pythonic interface to leverage BigQuery ML's -generative AI and predictive functions directly on BigQuery DataFrames and Series objects. -These functions enable you to perform advanced AI tasks at scale without moving data -out of BigQuery. - -Key capabilities include: - -* **Generative AI:** Use :func:`bigframes.bigquery.ai.generate` (Gemini) to - perform text analysis, translation, or - content generation. Specialized versions like - :func:`~bigframes.bigquery.ai.generate_bool`, - :func:`~bigframes.bigquery.ai.generate_int`, and - :func:`~bigframes.bigquery.ai.generate_double` are available for structured - outputs. -* **Embeddings:** Generate vector embeddings for text using - :func:`~bigframes.bigquery.ai.generate_embedding`, which are essential for - semantic search and retrieval-augmented generation (RAG) workflows. -* **Classification and Scoring:** Apply machine learning models to your data for - predictive tasks with :func:`~bigframes.bigquery.ai.classify` and - :func:`~bigframes.bigquery.ai.score`. -* **Forecasting:** Predict future values in time-series data using - :func:`~bigframes.bigquery.ai.forecast`. - -**Example usage:** - - >>> import bigframes.pandas as bpd - >>> import bigframes.bigquery as bbq - - >>> df = bpd.DataFrame({ - ... "text_input": [ - ... "Is this a positive review? The food was terrible.", - ... ], - ... }) # doctest: +SKIP - - >>> # Assuming a Gemini model has been created in BigQuery as 'my_gemini_model' - >>> result = bq.ai.generate_text("my_gemini_model", df["text_input"]) # doctest: +SKIP - -For more information on the underlying BigQuery ML syntax, see: -https://cloud.google.com/bigquery/docs/reference/standard-sql/bigqueryml-syntax-ai-generate-bool -""" - -from bigframes.bigquery._operations.ai import ( - classify, - embed, - forecast, - generate, - generate_bool, - generate_double, - generate_embedding, - generate_int, - generate_table, - generate_text, - if_, - score, - similarity, -) - -__all__ = [ - "classify", - "embed", - "forecast", - "generate", - "generate_bool", - "generate_double", - "generate_embedding", - "generate_int", - "generate_table", - "generate_text", - "if_", - "score", - "similarity", -] diff --git a/bigframes/bigquery/ml.py b/bigframes/bigquery/ml.py deleted file mode 100644 index 9b0d77d5b89..00000000000 --- a/bigframes/bigquery/ml.py +++ /dev/null @@ -1,44 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -"""This module exposes `BigQuery ML -`_ functions -by directly mapping to the equivalent function names in SQL syntax. - -For an interface more familiar to Scikit-Learn users, see :mod:`bigframes.ml`. -""" - -from bigframes.bigquery._operations.ml import ( - create_model, - evaluate, - explain_predict, - generate_embedding, - generate_text, - get_insights, - global_explain, - predict, - transform, -) - -__all__ = [ - "create_model", - "evaluate", - "predict", - "explain_predict", - "global_explain", - "transform", - "generate_text", - "generate_embedding", - "get_insights", -] diff --git a/bigframes/bigquery/obj.py b/bigframes/bigquery/obj.py deleted file mode 100644 index dc2c29e1f3d..00000000000 --- a/bigframes/bigquery/obj.py +++ /dev/null @@ -1,41 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -"""This module integrates BigQuery built-in 'ObjectRef' functions for use with Series/DataFrame objects, -such as OBJ.FETCH_METADATA: -https://docs.cloud.google.com/bigquery/docs/reference/standard-sql/objectref_functions - - -.. warning:: - - This product or feature is subject to the "Pre-GA Offerings Terms" in the - General Service Terms section of the `Service Specific Terms - `_. Pre-GA products and - features are available "as is" and might have limited support. For more - information, see the `launch stage descriptions - `_. - -.. note:: - - To provide feedback or request support for this feature, send an email to - bq-objectref-feedback@google.com. -""" - -from bigframes.bigquery._operations.obj import fetch_metadata, get_access_url, make_ref - -__all__ = [ - "fetch_metadata", - "get_access_url", - "make_ref", -] diff --git a/bigframes/clients.py b/bigframes/clients.py index b724843c133..de2421e499f 100644 --- a/bigframes/clients.py +++ b/bigframes/clients.py @@ -17,60 +17,16 @@ from __future__ import annotations import logging -import textwrap import time -from typing import Optional, cast +from typing import cast, Optional import google.api_core.exceptions -import google.api_core.retry from google.cloud import bigquery_connection_v1, resourcemanager_v3 -from google.iam.v1 import policy_pb2 +from google.iam.v1 import iam_policy_pb2, policy_pb2 logger = logging.getLogger(__name__) -def get_canonical_bq_connection_id( - connection_id: str, default_project: str, default_location: str -) -> str: - """ - Retrieve the full connection id of the form - ... - Use default project, location or connection_id when any of them are missing. - """ - - if "/" in connection_id: - fields = connection_id.split("/") - if ( - len(fields) == 6 - and fields[0] == "projects" - and fields[2] == "locations" - and fields[4] == "connections" - ): - return ".".join((fields[1], fields[3], fields[5])) - else: - if connection_id.count(".") == 2: - return connection_id - - if connection_id.count(".") == 1: - return f"{default_project}.{connection_id}" - - if connection_id.count(".") == 0: - return f"{default_project}.{default_location}.{connection_id}" - - raise ValueError( - textwrap.dedent( - f""" - Invalid connection id format: {connection_id}. - Only the following formats are supported: - .., - ., - , - projects//locations//connections/ - """ - ).strip() - ) - - class BqConnectionManager: """Manager to handle operations with BQ connections.""" @@ -85,12 +41,25 @@ def __init__( self._bq_connection_client = bq_connection_client self._cloud_resource_manager_client = cloud_resource_manager_client + @classmethod + def resolve_full_connection_name( + cls, connection_name: str, default_project: str, default_location: str + ) -> str: + """Retrieve the full connection name of the form ... + Use default project, location or connection_id when any of them are missing.""" + if connection_name.count(".") == 2: + return connection_name + + if connection_name.count(".") == 1: + return f"{default_project}.{connection_name}" + + if connection_name.count(".") == 0: + return f"{default_project}.{default_location}.{connection_name}" + + raise ValueError(f"Invalid connection name format: {connection_name}.") + def create_bq_connection( - self, - project_id: str, - location: str, - connection_id: str, - iam_role: Optional[str] = None, + self, project_id: str, location: str, connection_id: str, iam_role: str ): """Create the BQ connection if not exist. In addition, try to add the IAM role to the connection to ensure required permissions. @@ -104,13 +73,19 @@ def create_bq_connection( iam_role: str of the IAM role that the service account of the created connection needs to aquire. E.g. 'run.invoker', 'aiplatform.user' """ + # TODO(shobs): The below command to enable BigQuery Connection API needs + # to be automated. Disabling for now since most target users would not + # have the privilege to enable API in a project. + # log("Making sure BigQuery Connection API is enabled") + # if os.system("gcloud services enable bigqueryconnection.googleapis.com"): + # raise ValueError("Failed to enable BigQuery Connection API") # If the intended connection does not exist then create it service_account_id = self._get_service_account_if_connection_exists( project_id, location, connection_id ) if service_account_id: logger.info( - f"BQ connection {project_id}.{location}.{connection_id} already exists" + f"Connector {project_id}.{location}.{connection_id} already exists" ) else: connection_name, service_account_id = self._create_bq_connection( @@ -120,34 +95,20 @@ def create_bq_connection( f"Created BQ connection {connection_name} with service account id: {service_account_id}" ) service_account_id = cast(str, service_account_id) - # Ensure IAM role on the BQ connection # https://cloud.google.com/bigquery/docs/reference/standard-sql/remote-functions#grant_permission_on_function - if iam_role: - try: - self._ensure_iam_binding(project_id, service_account_id, iam_role) - except google.api_core.exceptions.PermissionDenied as ex: - ex.message = f"Failed ensuring IAM binding (role={iam_role}, service-account={service_account_id}). {ex.message}" - raise - - # Introduce retries to accommodate transient errors like: - # (1) Etag mismatch, - # which can be caused by concurrent operation on the same resource, and - # manifests with message like: - # google.api_core.exceptions.Aborted: 409 There were concurrent policy - # changes. Please retry the whole read-modify-write with exponential - # backoff. The request's ETag '\007\006\003,\264\304\337\272' did not - # match the current policy's ETag '\007\006\003,\3750&\363'. - # (2) Connection creation, - # for which sometimes it takes a bit for its service account to reflect - # across APIs (e.g. b/397662004, b/386838767), before which, an attempt - # to set an IAM policy for the service account may throw an error like: - # google.api_core.exceptions.InvalidArgument: 400 Service account - # bqcx-*@gcp-sa-bigquery-condel.iam.gserviceaccount.com does not exist. + self._ensure_iam_binding(project_id, service_account_id, iam_role) + + # Introduce retries to accommodate transient errors like etag mismatch, + # which can be caused by concurrent operation on the same resource, and + # manifests with message like: + # google.api_core.exceptions.Aborted: 409 There were concurrent policy + # changes. Please retry the whole read-modify-write with exponential + # backoff. The request's ETag '\007\006\003,\264\304\337\272' did not match + # the current policy's ETag '\007\006\003,\3750&\363'. @google.api_core.retry.Retry( predicate=google.api_core.retry.if_exception_type( - google.api_core.exceptions.Aborted, - google.api_core.exceptions.InvalidArgument, + google.api_core.exceptions.Aborted ), initial=10, maximum=20, @@ -161,9 +122,7 @@ def _ensure_iam_binding( project = f"projects/{project_id}" service_account = f"serviceAccount:{service_account_id}" role = f"roles/{iam_role}" - request = { - "resource": project - } # Use a dictionary to avoid problematic google.iam namespace package. + request = iam_policy_pb2.GetIamPolicyRequest(resource=project) policy = self._cloud_resource_manager_client.get_iam_policy(request=request) # Check if the binding already exists, and if does, do nothing more @@ -175,10 +134,7 @@ def _ensure_iam_binding( # Create a new binding new_binding = policy_pb2.Binding(role=role, members=[service_account]) policy.bindings.append(new_binding) - request = { - "resource": project, - "policy": policy, - } # Use a dictionary to avoid problematic google.iam namespace package. + request = iam_policy_pb2.SetIamPolicyRequest(resource=project, policy=policy) self._cloud_resource_manager_client.set_iam_policy(request=request) # We would wait for the IAM policy change to take effect diff --git a/bigframes/constants.py b/bigframes/constants.py index b6e0b8b2211..a1ffd2b755c 100644 --- a/bigframes/constants.py +++ b/bigframes/constants.py @@ -12,124 +12,18 @@ # See the License for the specific language governing permissions and # limitations under the License. +import datetime + """Constants used across BigQuery DataFrames. This module should not depend on any others in the package. """ -import datetime -import textwrap - -DEFAULT_EXPIRATION = datetime.timedelta(days=7) - -# https://cloud.google.com/bigquery/docs/locations -BIGQUERY_REGIONS = frozenset( - { - "africa-south1", - "asia-east1", - "asia-east2", - "asia-northeast1", - "asia-northeast2", - "asia-northeast3", - "asia-south1", - "asia-south2", - "asia-southeast1", - "asia-southeast2", - "australia-southeast1", - "australia-southeast2", - "europe-central2", - "europe-north1", - "europe-southwest1", - "europe-west1", - "europe-west10", - "europe-west12", - "europe-west2", - "europe-west3", - "europe-west4", - "europe-west6", - "europe-west8", - "europe-west9", - "me-central1", - "me-central2", - "me-west1", - "northamerica-northeast1", - "northamerica-northeast2", - "southamerica-east1", - "southamerica-west1", - "us-central1", - "us-east1", - "us-east4", - "us-east5", - "us-south1", - "us-west1", - "us-west2", - "us-west3", - "us-west4", - } +FEEDBACK_LINK = ( + "Share your usecase with the BigQuery DataFrames team at the " + "https://bit.ly/bigframes-feedback survey." ) -BIGQUERY_MULTIREGIONS = frozenset( - { - "EU", - "US", - } -) -ALL_BIGQUERY_LOCATIONS = frozenset(BIGQUERY_REGIONS.union(BIGQUERY_MULTIREGIONS)) -# https://cloud.google.com/storage/docs/regional-endpoints -REP_ENABLED_BIGQUERY_LOCATIONS = frozenset( - { - "europe-west3", - "europe-west8", - "europe-west9", - "me-central2", - "us-central1", - "us-central2", - "us-east1", - "us-east4", - "us-east5", - "us-east7", - "us-south1", - "us-west1", - "us-west2", - "us-west3", - "us-west4", - } -) +ABSTRACT_METHOD_ERROR_MESSAGE = f"Abstract method. You have likely encountered a bug. Please share this stacktrace and how you reached it with the BigQuery DataFrames team. {FEEDBACK_LINK}" -REP_NOT_ENABLED_BIGQUERY_LOCATIONS = frozenset( - ALL_BIGQUERY_LOCATIONS - REP_ENABLED_BIGQUERY_LOCATIONS -) - -LOCATION_NEEDED_FOR_REP_MESSAGE = textwrap.dedent( - """ - Must set location to use regional endpoints. - You can do it via bigframaes.pandas.options.bigquery.location. - The supported locations can be found at - https://cloud.google.com/bigquery/docs/regional-endpoints#supported-locations. - """ -).strip() - -REP_NOT_SUPPORTED_MESSAGE = textwrap.dedent( - """ - Support for regional endpoints for BigQuery and BigQuery Storage APIs may - not be available in the location {location}. For the supported APIs and - locations see https://cloud.google.com/bigquery/docs/regional-endpoints. - If you have the (deprecated) locational endpoints enabled in your project - (which requires your project to be allowlisted), you can override the - endpoints directly by doing the following: - bigframes.pandas.options.bigquery.client_endpoints_override = {{ - "bqclient": "https://{location}-bigquery.googleapis.com", - "bqconnectionclient": "{location}-bigqueryconnection.googleapis.com", - "bqstoragereadclient": "{location}-bigquerystorage.googleapis.com" - }} - """ -).strip() - -# BigQuery default is 10000, leave 100 for overhead -MAX_COLUMNS = 9900 - -# BigQuery has 1 MB query size limit. Don't want to take up more than a few % of that inlining a table. -# Also must assume that text encoding as literals is much less efficient than in-memory representation. -MAX_INLINE_BYTES = 5000 - -SUGGEST_PEEK_PREVIEW = "Use .peek(n) to preview n arbitrary rows." +DEFAULT_EXPIRATION = datetime.timedelta(days=7) diff --git a/bigframes/core/__init__.py b/bigframes/core/__init__.py index 2f3f15953cd..866be9c4003 100644 --- a/bigframes/core/__init__.py +++ b/bigframes/core/__init__.py @@ -11,7 +11,430 @@ # 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.core.array_value import ArrayValue +from dataclasses import dataclass +import io +import typing +from typing import Iterable, Literal, Optional, Sequence, Tuple -__all__ = ["ArrayValue"] +from google.cloud import bigquery +import ibis +import ibis.expr.types as ibis_types +import pandas + +import bigframes.core.compile as compiled +import bigframes.core.guid +import bigframes.core.nodes as nodes +from bigframes.core.ordering import OrderingColumnReference +import bigframes.core.ordering as orderings +from bigframes.core.window_spec import WindowSpec +import bigframes.dtypes +import bigframes.operations as ops +import bigframes.operations.aggregations as agg_ops +import bigframes.session._io.bigquery + +if typing.TYPE_CHECKING: + from bigframes.session import Session + +ORDER_ID_COLUMN = "bigframes_ordering_id" +PREDICATE_COLUMN = "bigframes_predicate" + + +@dataclass(frozen=True) +class ArrayValue: + """ + ArrayValue is an immutable type representing a 2D array with per-column types. + """ + + node: nodes.BigFrameNode + + @classmethod + def from_ibis( + cls, + session: Session, + table: ibis_types.Table, + columns: Sequence[ibis_types.Value], + hidden_ordering_columns: Sequence[ibis_types.Value], + ordering: orderings.ExpressionOrdering, + ): + node = nodes.ReadGbqNode( + table=table, + table_session=session, + columns=tuple(columns), + hidden_ordering_columns=tuple(hidden_ordering_columns), + ordering=ordering, + ) + return cls(node) + + @classmethod + def from_pandas(cls, pd_df: pandas.DataFrame): + iobytes = io.BytesIO() + # Discard row labels and use simple string ids for columns + column_ids = tuple(str(label) for label in pd_df.columns) + pd_df.reset_index(drop=True).set_axis(column_ids, axis=1).to_feather(iobytes) + node = nodes.ReadLocalNode(iobytes.getvalue(), column_ids=column_ids) + return cls(node) + + @property + def column_ids(self) -> typing.Sequence[str]: + return self.compile().column_ids + + @property + def session(self) -> Session: + required_session = self.node.session + from bigframes import get_global_session + + return self.node.session[0] if required_session else get_global_session() + + def get_column_type(self, key: str) -> bigframes.dtypes.Dtype: + return self.compile().get_column_type(key) + + def compile(self) -> compiled.CompiledArrayValue: + return compiled.compile_node(self.node) + + def shape(self) -> typing.Tuple[int, int]: + """Returns dimensions as (length, width) tuple.""" + width = len(self.compile().columns) + count_expr = self.compile()._to_ibis_expr("unordered").count() + + # Support in-memory engines for hermetic unit tests. + if not self.node.session: + try: + length = ibis.pandas.connect({}).execute(count_expr) + return (length, width) + except Exception: + # Not all cases can be handled by pandas engine + pass + + sql = self.session.ibis_client.compile(count_expr) + row_iterator, _ = self.session._start_query( + sql=sql, + max_results=1, + ) + length = next(row_iterator)[0] + return (length, width) + + def to_sql( + self, + offset_column: typing.Optional[str] = None, + col_id_overrides: typing.Mapping[str, str] = {}, + sorted: bool = False, + ) -> str: + return self.compile().to_sql( + offset_column=offset_column, + col_id_overrides=col_id_overrides, + sorted=sorted, + ) + + def start_query( + self, + job_config: Optional[bigquery.job.QueryJobConfig] = None, + max_results: Optional[int] = None, + *, + sorted: bool = True, + ) -> Tuple[bigquery.table.RowIterator, bigquery.QueryJob]: + """Execute a query and return metadata about the results.""" + # TODO(swast): Cache the job ID so we can look it up again if they ask + # for the results? We'd need a way to invalidate the cache if DataFrame + # becomes mutable, though. Or move this method to the immutable + # expression class. + # TODO(swast): We might want to move this method to Session and/or + # provide our own minimal metadata class. Tight coupling to the + # BigQuery client library isn't ideal, especially if we want to support + # a LocalSession for unit testing. + # TODO(swast): Add a timeout here? If the query is taking a long time, + # maybe we just print the job metadata that we have so far? + sql = self.to_sql(sorted=sorted) # type:ignore + return self.session._start_query( + sql=sql, + job_config=job_config, + max_results=max_results, + ) + + def cached(self, cluster_cols: typing.Sequence[str]) -> ArrayValue: + """Write the ArrayValue to a session table and create a new block object that references it.""" + compiled_value = self.compile() + ibis_expr = compiled_value._to_ibis_expr( + ordering_mode="unordered", expose_hidden_cols=True + ) + tmp_table = self.session._ibis_to_session_table( + ibis_expr, cluster_cols=cluster_cols, api_name="cached" + ) + + table_expression = self.session.ibis_client.table( + f"{tmp_table.project}.{tmp_table.dataset_id}.{tmp_table.table_id}" + ) + new_columns = [table_expression[column] for column in compiled_value.column_ids] + new_hidden_columns = [ + table_expression[column] + for column in compiled_value._hidden_ordering_column_names + ] + return ArrayValue.from_ibis( + self.session, + table_expression, + columns=new_columns, + hidden_ordering_columns=new_hidden_columns, + ordering=compiled_value._ordering, + ) + + # Operations + + def drop_columns(self, columns: Iterable[str]) -> ArrayValue: + return ArrayValue( + nodes.DropColumnsNode(child=self.node, columns=tuple(columns)) + ) + + def filter(self, predicate_id: str, keep_null: bool = False) -> ArrayValue: + """Filter the table on a given expression, the predicate must be a boolean series aligned with the table expression.""" + return ArrayValue( + nodes.FilterNode( + child=self.node, predicate_id=predicate_id, keep_null=keep_null + ) + ) + + def order_by( + self, by: Sequence[OrderingColumnReference], stable: bool = False + ) -> ArrayValue: + return ArrayValue( + nodes.OrderByNode(child=self.node, by=tuple(by), stable=stable) + ) + + def reversed(self) -> ArrayValue: + return ArrayValue(nodes.ReversedNode(child=self.node)) + + def promote_offsets(self, col_id: str) -> ArrayValue: + """ + Convenience function to promote copy of column offsets to a value column. Can be used to reset index. + """ + return ArrayValue(nodes.PromoteOffsetsNode(child=self.node, col_id=col_id)) + + def select_columns(self, column_ids: typing.Sequence[str]) -> ArrayValue: + return ArrayValue( + nodes.SelectNode(child=self.node, column_ids=tuple(column_ids)) + ) + + def concat(self, other: typing.Sequence[ArrayValue]) -> ArrayValue: + """Append together multiple ArrayValue objects.""" + return ArrayValue( + nodes.ConcatNode(children=tuple([self.node, *[val.node for val in other]])) + ) + + def project_unary_op( + self, column_name: str, op: ops.UnaryOp, output_name=None + ) -> ArrayValue: + """Creates a new expression based on this expression with unary operation applied to one column.""" + return ArrayValue( + nodes.ProjectUnaryOpNode( + child=self.node, input_id=column_name, op=op, output_id=output_name + ) + ) + + def project_binary_op( + self, + left_column_id: str, + right_column_id: str, + op: ops.BinaryOp, + output_column_id: str, + ) -> ArrayValue: + """Creates a new expression based on this expression with binary operation applied to two columns.""" + return ArrayValue( + nodes.ProjectBinaryOpNode( + child=self.node, + left_input_id=left_column_id, + right_input_id=right_column_id, + op=op, + output_id=output_column_id, + ) + ) + + def project_ternary_op( + self, + col_id_1: str, + col_id_2: str, + col_id_3: str, + op: ops.TernaryOp, + output_column_id: str, + ) -> ArrayValue: + """Creates a new expression based on this expression with ternary operation applied to three columns.""" + return ArrayValue( + nodes.ProjectTernaryOpNode( + child=self.node, + input_id1=col_id_1, + input_id2=col_id_2, + input_id3=col_id_3, + op=op, + output_id=output_column_id, + ) + ) + + def aggregate( + self, + aggregations: typing.Sequence[typing.Tuple[str, agg_ops.AggregateOp, str]], + by_column_ids: typing.Sequence[str] = (), + dropna: bool = True, + ) -> ArrayValue: + """ + Apply aggregations to the expression. + Arguments: + aggregations: input_column_id, operation, output_column_id tuples + by_column_id: column id of the aggregation key, this is preserved through the transform + dropna: whether null keys should be dropped + """ + return ArrayValue( + nodes.AggregateNode( + child=self.node, + aggregations=tuple(aggregations), + by_column_ids=tuple(by_column_ids), + dropna=dropna, + ) + ) + + def corr_aggregate( + self, corr_aggregations: typing.Sequence[typing.Tuple[str, str, str]] + ) -> ArrayValue: + """ + Get correlations between each lef_column_id and right_column_id, stored in the respective output_column_id. + This uses BigQuery's CORR under the hood, and thus only Pearson's method is used. + Arguments: + corr_aggregations: left_column_id, right_column_id, output_column_id tuples + """ + return ArrayValue( + nodes.CorrNode(child=self.node, corr_aggregations=tuple(corr_aggregations)) + ) + + def project_window_op( + self, + column_name: str, + op: agg_ops.WindowOp, + window_spec: WindowSpec, + output_name=None, + *, + never_skip_nulls=False, + skip_reproject_unsafe: bool = False, + ) -> ArrayValue: + """ + Creates a new expression based on this expression with unary operation applied to one column. + column_name: the id of the input column present in the expression + op: the windowable operator to apply to the input column + window_spec: a specification of the window over which to apply the operator + output_name: the id to assign to the output of the operator, by default will replace input col if distinct output id not provided + never_skip_nulls: will disable null skipping for operators that would otherwise do so + skip_reproject_unsafe: skips the reprojection step, can be used when performing many non-dependent window operations, user responsible for not nesting window expressions, or using outputs as join, filter or aggregation keys before a reprojection + """ + return ArrayValue( + nodes.WindowOpNode( + child=self.node, + column_name=column_name, + op=op, + window_spec=window_spec, + output_name=output_name, + never_skip_nulls=never_skip_nulls, + skip_reproject_unsafe=skip_reproject_unsafe, + ) + ) + + def _reproject_to_table(self) -> ArrayValue: + """ + Internal operators that projects the internal representation into a + new ibis table expression where each value column is a direct + reference to a column in that table expression. Needed after + some operations such as window operations that cannot be used + recursively in projections. + """ + return ArrayValue( + nodes.ReprojectOpNode( + child=self.node, + ) + ) + + def unpivot( + self, + row_labels: typing.Sequence[typing.Hashable], + unpivot_columns: typing.Sequence[ + typing.Tuple[str, typing.Tuple[typing.Optional[str], ...]] + ], + *, + passthrough_columns: typing.Sequence[str] = (), + index_col_ids: typing.Sequence[str] = ["index"], + dtype: typing.Union[ + bigframes.dtypes.Dtype, typing.Tuple[bigframes.dtypes.Dtype, ...] + ] = pandas.Float64Dtype(), + how: typing.Literal["left", "right"] = "left", + ) -> ArrayValue: + """ + Unpivot ArrayValue columns. + + Args: + row_labels: Identifies the source of the row. Must be equal to length to source column list in unpivot_columns argument. + unpivot_columns: Mapping of column id to list of input column ids. Lists of input columns may use None. + passthrough_columns: Columns that will not be unpivoted. Column id will be preserved. + index_col_id (str): The column id to be used for the row labels. + dtype (dtype or list of dtype): Dtype to use for the unpivot columns. If list, must be equal in number to unpivot_columns. + + Returns: + ArrayValue: The unpivoted ArrayValue + """ + return ArrayValue( + nodes.UnpivotNode( + child=self.node, + row_labels=tuple(row_labels), + unpivot_columns=tuple(unpivot_columns), + passthrough_columns=tuple(passthrough_columns), + index_col_ids=tuple(index_col_ids), + dtype=dtype, + how=how, + ) + ) + + def assign(self, source_id: str, destination_id: str) -> ArrayValue: + return ArrayValue( + nodes.AssignNode( + child=self.node, source_id=source_id, destination_id=destination_id + ) + ) + + def assign_constant( + self, + destination_id: str, + value: typing.Any, + dtype: typing.Optional[bigframes.dtypes.Dtype], + ) -> ArrayValue: + return ArrayValue( + nodes.AssignConstantNode( + child=self.node, destination_id=destination_id, value=value, dtype=dtype + ) + ) + + def join( + self, + self_column_ids: typing.Sequence[str], + other: ArrayValue, + other_column_ids: typing.Sequence[str], + *, + how: Literal[ + "inner", + "left", + "outer", + "right", + ], + allow_row_identity_join: bool = True, + ): + return ArrayValue( + nodes.JoinNode( + left_child=self.node, + right_child=other.node, + left_column_ids=tuple(self_column_ids), + right_column_ids=tuple(other_column_ids), + how=how, + allow_row_identity_join=allow_row_identity_join, + ) + ) + + def _uniform_sampling(self, fraction: float) -> ArrayValue: + """Sampling the table on given fraction. + + .. warning:: + The row numbers of result is non-deterministic, avoid to use. + """ + return ArrayValue(nodes.RandomSampleNode(self.node, fraction)) diff --git a/bigframes/core/agg_expressions.py b/bigframes/core/agg_expressions.py deleted file mode 100644 index 6d126c92420..00000000000 --- a/bigframes/core/agg_expressions.py +++ /dev/null @@ -1,231 +0,0 @@ -# Copyright 2023 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 abc -import dataclasses -import functools -import itertools -import typing -from typing import Callable, Hashable, Mapping, Tuple, TypeVar - -import bigframes.core.identifiers as ids -import bigframes.operations.aggregations as agg_ops -from bigframes import dtypes -from bigframes.core import expression, window_spec - -TExpression = TypeVar("TExpression", bound="Aggregation") - - -@dataclasses.dataclass(frozen=True) -class Aggregation(expression.Expression): - """Represents windowing or aggregation over a column.""" - - op: agg_ops.WindowOp = dataclasses.field() - - @property - def column_references(self) -> typing.Tuple[ids.ColumnId, ...]: - return tuple( - itertools.chain.from_iterable( - map(lambda x: x.column_references, self.inputs) - ) - ) - - @functools.cached_property - def is_resolved(self) -> bool: - return all(input.is_resolved for input in self.inputs) - - @functools.cached_property - def output_type(self) -> dtypes.ExpressionType: - if not self.is_resolved: - raise ValueError(f"Type of expression {self.op} has not been fixed.") - - input_types = [input.output_type for input in self.inputs] - - return self.op.output_type(*input_types) - - @property - @abc.abstractmethod - def inputs( - self, - ) -> typing.Tuple[expression.Expression, ...]: ... - - @property - def children(self) -> Tuple[expression.Expression, ...]: - return self.inputs - - @property - def free_variables(self) -> typing.Tuple[Hashable, ...]: - return tuple( - itertools.chain.from_iterable(map(lambda x: x.free_variables, self.inputs)) - ) - - @property - def is_const(self) -> bool: - return all(child.is_const for child in self.inputs) - - @functools.cached_property - def is_scalar_expr(self) -> bool: - return False - - @abc.abstractmethod - def replace_args(self: TExpression, *arg) -> TExpression: ... - - def transform_children( - self: TExpression, t: Callable[[expression.Expression], expression.Expression] - ) -> TExpression: - return self.replace_args(*(t(arg) for arg in self.inputs)) - - def bind_variables( - self: TExpression, - bindings: Mapping[Hashable, expression.Expression], - allow_partial_bindings: bool = False, - ) -> TExpression: - return self.transform_children( - lambda x: x.bind_variables(bindings, allow_partial_bindings) - ) - - def bind_refs( - self: TExpression, - bindings: Mapping[ids.ColumnId, expression.Expression], - allow_partial_bindings: bool = False, - ) -> TExpression: - return self.transform_children( - lambda x: x.bind_refs(bindings, allow_partial_bindings) - ) - - -@dataclasses.dataclass(frozen=True) -class NullaryAggregation(Aggregation): - op: agg_ops.NullaryWindowOp = dataclasses.field() - - @property - def inputs( - self, - ) -> typing.Tuple[expression.Expression, ...]: - return () - - def replace_args(self, *arg) -> NullaryAggregation: - return self - - -@dataclasses.dataclass(frozen=True) -class UnaryAggregation(Aggregation): - op: agg_ops.UnaryWindowOp - arg: expression.Expression - - @property - def inputs( - self, - ) -> typing.Tuple[expression.Expression, ...]: - return (self.arg,) - - def replace_args(self, arg: expression.Expression) -> UnaryAggregation: - return UnaryAggregation( - self.op, - arg, - ) - - -@dataclasses.dataclass(frozen=True) -class BinaryAggregation(Aggregation): - op: agg_ops.BinaryAggregateOp = dataclasses.field() - left: expression.Expression = dataclasses.field() - right: expression.Expression = dataclasses.field() - - @property - def inputs( - self, - ) -> typing.Tuple[expression.Expression, ...]: - return (self.left, self.right) - - def replace_args( - self, larg: expression.Expression, rarg: expression.Expression - ) -> BinaryAggregation: - return BinaryAggregation(self.op, larg, rarg) - - -@dataclasses.dataclass(frozen=True) -class WindowExpression(expression.Expression): - analytic_expr: Aggregation - window: window_spec.WindowSpec - - @property - def column_references(self) -> typing.Tuple[ids.ColumnId, ...]: - return tuple( - itertools.chain.from_iterable( - map(lambda x: x.column_references, self.inputs) - ) - ) - - @functools.cached_property - def is_resolved(self) -> bool: - return all(input.is_resolved for input in self.inputs) - - @property - def output_type(self) -> dtypes.ExpressionType: - return self.analytic_expr.output_type - - @property - def inputs( - self, - ) -> typing.Tuple[expression.Expression, ...]: - # TODO: Maybe make the window spec itself an expression? - return (self.analytic_expr, *self.window.expressions) - - @property - def children(self) -> Tuple[expression.Expression, ...]: - return self.inputs - - @property - def free_variables(self) -> typing.Tuple[Hashable, ...]: - return tuple( - itertools.chain.from_iterable(map(lambda x: x.free_variables, self.inputs)) - ) - - @property - def is_const(self) -> bool: - return all(child.is_const for child in self.inputs) - - @functools.cached_property - def is_scalar_expr(self) -> bool: - return False - - def transform_children( - self: WindowExpression, - t: Callable[[expression.Expression], expression.Expression], - ) -> WindowExpression: - return WindowExpression( - t(self.analytic_expr), # type: ignore - self.window.transform_exprs(t), - ) - - def bind_variables( - self: WindowExpression, - bindings: Mapping[Hashable, expression.Expression], - allow_partial_bindings: bool = False, - ) -> WindowExpression: - return self.transform_children( - lambda x: x.bind_variables(bindings, allow_partial_bindings) - ) - - def bind_refs( - self: WindowExpression, - bindings: Mapping[ids.ColumnId, expression.Expression], - allow_partial_bindings: bool = False, - ) -> WindowExpression: - return self.transform_children( - lambda x: x.bind_refs(bindings, allow_partial_bindings) - ) diff --git a/bigframes/core/array_value.py b/bigframes/core/array_value.py deleted file mode 100644 index d7fb186ae91..00000000000 --- a/bigframes/core/array_value.py +++ /dev/null @@ -1,645 +0,0 @@ -# Copyright 2023 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 datetime -import functools -import typing -from dataclasses import dataclass -from typing import Iterable, List, Mapping, Optional, Sequence, Tuple, Union - -import pandas -import pyarrow as pa - -import bigframes.core.expression as ex -import bigframes.core.guid -import bigframes.core.identifiers as ids -import bigframes.core.nodes as nodes -import bigframes.core.ordering as orderings -import bigframes.core.schema as schemata -import bigframes.core.tree_properties -import bigframes.dtypes -import bigframes.operations as ops -import bigframes.operations.aggregations as agg_ops -from bigframes.core import ( - agg_expressions, - bq_data, - expression_factoring, - join_def, - local_data, -) -from bigframes.core.ordering import OrderingExpression -from bigframes.core.window_spec import WindowSpec - -if typing.TYPE_CHECKING: - from bigframes.session import Session - -ORDER_ID_COLUMN = "bigframes_ordering_id" -PREDICATE_COLUMN = "bigframes_predicate" - - -@dataclass(frozen=True) -class ArrayValue: - """ - ArrayValue is an immutable type representing a 2D array with per-column types. - """ - - node: nodes.BigFrameNode - - @classmethod - def from_pyarrow(cls, arrow_table: pa.Table, session: Session): - data_source = local_data.ManagedArrowTable.from_pyarrow(arrow_table) - return cls.from_managed(source=data_source, session=session) - - @classmethod - def from_managed(cls, source: local_data.ManagedArrowTable, session: Session): - scan_list = nodes.ScanList( - tuple( - nodes.ScanItem(ids.ColumnId(item.column), item.column) - for item in source.schema.items - ) - ) - node = nodes.ReadLocalNode( - source, - session=session, - scan_list=scan_list, - ) - return cls(node) - - @classmethod - def from_range(cls, start, end, step): - return cls( - nodes.FromRangeNode( - start=start.node, - end=end.node, - step=step, - ) - ) - - @classmethod - def from_table( - cls, - table: Union[bq_data.BiglakeIcebergTable, bq_data.GbqNativeTable], - session: Session, - *, - columns: Optional[Sequence[str]] = None, - predicate: Optional[str] = None, - at_time: Optional[datetime.datetime] = None, - primary_key: Sequence[str] = (), - offsets_col: Optional[str] = None, - n_rows: Optional[int] = None, - ): - if offsets_col and primary_key: - raise ValueError("must set at most one of 'offests', 'primary_key'") - - # create ordering from info - ordering = None - if offsets_col: - ordering = orderings.TotalOrdering.from_offset_col(offsets_col) - elif primary_key: - ordering = orderings.TotalOrdering.from_primary_key( - [ids.ColumnId(key_part) for key_part in primary_key] - ) - - bf_schema = schemata.ArraySchema.from_bq_schema( - table.physical_schema, columns=columns - ) - # Scan all columns by default, we define this list as it can be pruned while preserving source_def - scan_list = nodes.ScanList( - tuple( - nodes.ScanItem(ids.ColumnId(item.column), item.column) - for item in bf_schema.items - ) - ) - source_def = bq_data.BigqueryDataSource( - table=table, - schema=bf_schema, - at_time=at_time, - sql_predicate=predicate, - ordering=ordering, - n_rows=n_rows, - ) - return cls.from_bq_data_source(source_def, scan_list, session) - - @classmethod - def from_bq_data_source( - cls, - source: bq_data.BigqueryDataSource, - scan_list: nodes.ScanList, - session: Session, - ): - node = nodes.ReadTableNode( - source=source, - scan_list=scan_list, - table_session=session, - ) - return cls(node) - - @property - def column_ids(self) -> typing.Sequence[str]: - """Returns column ids as strings.""" - return self.schema.names - - @property - def session(self) -> Session: - required_session = self.node.session - from bigframes import get_global_session - - return ( - required_session if (required_session is not None) else get_global_session() - ) - - @functools.cached_property - def schema(self) -> schemata.ArraySchema: - return self.node.schema - - @property - def explicitly_ordered(self) -> bool: - # see BigFrameNode.explicitly_ordered - return self.node.explicitly_ordered - - @property - def order_ambiguous(self) -> bool: - # see BigFrameNode.order_ambiguous - return self.node.order_ambiguous - - @property - def supports_fast_peek(self) -> bool: - return bigframes.core.tree_properties.can_fast_peek(self.node) - - def get_column_type(self, key: str) -> bigframes.dtypes.Dtype: - return self.schema.get_type(key) - - def row_count(self) -> ArrayValue: - """Get number of rows in ArrayValue as a single-entry ArrayValue.""" - return ArrayValue( - nodes.AggregateNode( - child=self.node, - aggregations=( - ( - agg_expressions.NullaryAggregation(agg_ops.size_op), - ids.ColumnId(bigframes.core.guid.generate_guid()), - ), - ), - ) - ) - - # Operations - def filter_by_id(self, predicate_id: str, keep_null: bool = False) -> ArrayValue: - """Filter the table on a given expression, the predicate must be a boolean series aligned with the table expression.""" - predicate: ex.Expression = ex.deref(predicate_id) - if keep_null: - predicate = ops.fillna_op.as_expr(predicate, ex.const(True)) - return self.filter(predicate) - - def filter(self, predicate: ex.Expression): - if predicate.is_scalar_expr: - return ArrayValue(nodes.FilterNode(child=self.node, predicate=predicate)) - else: - arr, filter_ids = self.compute_general_expression([predicate]) - arr = arr.filter_by_id(filter_ids[0]) - return arr.drop_columns(filter_ids) - - def order_by( - self, - by: Sequence[OrderingExpression], - is_total_order: bool = False, - stable: bool = True, - ) -> ArrayValue: - return ArrayValue( - nodes.OrderByNode( - child=self.node, - by=tuple(by), - is_total_order=is_total_order, - stable=stable, - ) - ) - - def reversed(self) -> ArrayValue: - return ArrayValue(nodes.ReversedNode(child=self.node)) - - def slice( - self, start: Optional[int], stop: Optional[int], step: Optional[int] - ) -> ArrayValue: - return ArrayValue( - nodes.SliceNode( - self.node, - start=start, - stop=stop, - step=step if (step is not None) else 1, - ) - ) - - def promote_offsets(self) -> Tuple[ArrayValue, str]: - """ - Convenience function to promote copy of column offsets to a value column. Can be used to reset index. - """ - col_id = self._gen_namespaced_uid() - return ( - ArrayValue( - nodes.PromoteOffsetsNode(child=self.node, col_id=ids.ColumnId(col_id)) - ), - col_id, - ) - - def concat(self, other: typing.Sequence[ArrayValue]) -> ArrayValue: - """Append together multiple ArrayValue objects.""" - return ArrayValue( - nodes.ConcatNode( - children=tuple([self.node, *[val.node for val in other]]), - output_ids=tuple( - ids.ColumnId(bigframes.core.guid.generate_guid()) - for id in self.column_ids - ), - ) - ) - - def compute_values(self, assignments: Sequence[ex.Expression]): - col_ids = self._gen_namespaced_uids(len(assignments)) - ex_id_pairs = tuple( - (ex, ids.ColumnId(id)) for ex, id in zip(assignments, col_ids) - ) - return ( - ArrayValue(nodes.ProjectionNode(child=self.node, assignments=ex_id_pairs)), - col_ids, - ) - - def compute_general_expression(self, assignments: Sequence[ex.Expression]): - """ - Applies arbitrary column expressions to the current execution block. - - This method transforms the logical plan by applying a sequence of expressions that - preserve the length of the input columns. It supports both scalar operations - and window functions. Each expression is assigned a unique internal column identifier. - - Args: - assignments (Sequence[ex.Expression]): A sequence of expression objects - representing the transformations to apply to the columns. - - Returns: - Tuple[ArrayValue, Tuple[str, ...]]: A tuple containing: - - An `ArrayValue` wrapping the new root node of the updated logical plan. - - A tuple of strings representing the unique column IDs generated for - each expression in the assignments. - """ - named_exprs = [ - nodes.ColumnDef(expr, ids.ColumnId.unique()) for expr in assignments - ] - # TODO: Push this to rewrite later to go from block expression to planning form - new_root = expression_factoring.apply_col_exprs_to_plan(self.node, named_exprs) - - target_ids = tuple(named_expr.id for named_expr in named_exprs) - return (ArrayValue(new_root), target_ids) - - def compute_general_reduction( - self, - assignments: Sequence[ex.Expression], - by_column_ids: typing.Sequence[str] = (), - *, - dropna: bool = False, - ): - """ - Applies arbitrary aggregation expressions to the block, optionally grouped by keys. - - This method handles reduction operations (e.g., sum, mean, count) that collapse - multiple input rows into a single scalar value per group. If grouping keys are - provided, the operation is performed per group; otherwise, it is a global reduction. - - Note: Intermediate aggregations (those that are inputs to further aggregations) - must be windowizable. Notably excluded are approx quantile, top count ops. - - Args: - assignments (Sequence[ex.Expression]): A sequence of aggregation expressions - to be calculated. - by_column_ids (typing.Sequence[str], optional): A sequence of column IDs - to use as grouping keys. Defaults to an empty tuple (global reduction). - dropna (bool, optional): If True, rows containing null values in the - `by_column_ids` columns will be filtered out before the reduction - is applied. Defaults to False. - - Returns: - ArrayValue: - The new root node representing the aggregation/group-by result. - """ - plan = self.node - - # shortcircuit to keep things simple if all aggs are simple - # TODO: Fully unify paths once rewriters are strong enough to simplify complexity from full path - def _is_direct_agg(agg_expr): - return isinstance(agg_expr, agg_expressions.Aggregation) and all( - isinstance(child, (ex.DerefOp, ex.ScalarConstantExpression)) - for child in agg_expr.children - ) - - if all(_is_direct_agg(agg) for agg in assignments): - agg_defs = tuple((agg, ids.ColumnId.unique()) for agg in assignments) - return ArrayValue( - nodes.AggregateNode( - child=self.node, - aggregations=agg_defs, # type: ignore - by_column_ids=tuple(map(ex.deref, by_column_ids)), - dropna=dropna, - ) - ) - - if dropna: - for col_id in by_column_ids: - plan = nodes.FilterNode(plan, ops.notnull_op.as_expr(col_id)) - - named_exprs = [ - nodes.ColumnDef(expr, ids.ColumnId.unique()) for expr in assignments - ] - # TODO: Push this to rewrite later to go from block expression to planning form - new_root = expression_factoring.apply_agg_exprs_to_plan( - plan, named_exprs, grouping_keys=[ex.deref(by) for by in by_column_ids] - ) - return ArrayValue(new_root) - - def project_to_id(self, expression: ex.Expression): - array_val, ids = self.compute_values( - [expression], - ) - return array_val, ids[0] - - def assign(self, source_id: str, destination_id: str) -> ArrayValue: - if destination_id in self.column_ids: # Mutate case - exprs = [ - ( - bigframes.core.nodes.AliasedRef( - ex.deref(source_id if (col_id == destination_id) else col_id), - ids.ColumnId(col_id), - ) - ) - for col_id in self.column_ids - ] - else: # append case - self_projection = ( - bigframes.core.nodes.AliasedRef.identity(ids.ColumnId(col_id)) - for col_id in self.column_ids - ) - exprs = [ - *self_projection, - ( - bigframes.core.nodes.AliasedRef( - ex.deref(source_id), ids.ColumnId(destination_id) - ) - ), - ] - return ArrayValue( - nodes.SelectionNode( - child=self.node, - input_output_pairs=tuple(exprs), - ) - ) - - def create_constant( - self, - value: typing.Any, - dtype: typing.Optional[bigframes.dtypes.Dtype], - ) -> Tuple[ArrayValue, str]: - if pandas.isna(value): - # Need to assign a data type when value is NaN. - dtype = dtype or bigframes.dtypes.DEFAULT_DTYPE - - return self.project_to_id(ex.const(value, dtype)) - - def select_columns( - self, column_ids: typing.Sequence[str], allow_renames: bool = False - ) -> ArrayValue: - # This basically just drops and reorders columns - logically a no-op except as a final step - selections = [] - seen = set() - - for id in column_ids: - if id not in seen: - ref = nodes.AliasedRef.identity(ids.ColumnId(id)) - elif allow_renames: - ref = nodes.AliasedRef( - ex.deref(id), ids.ColumnId(bigframes.core.guid.generate_guid()) - ) - else: - raise ValueError( - "Must set allow_renames=True to select columns repeatedly" - ) - selections.append(ref) - seen.add(id) - - return ArrayValue( - nodes.SelectionNode( - child=self.node, - input_output_pairs=tuple(selections), - ) - ) - - def rename_columns(self, col_id_overrides: Mapping[str, str]) -> ArrayValue: - if not col_id_overrides: - return self - output_ids = [col_id_overrides.get(id, id) for id in self.node.schema.names] - return ArrayValue( - nodes.SelectionNode( - self.node, - tuple( - nodes.AliasedRef(ex.DerefOp(old_id), ids.ColumnId(out_id)) - for old_id, out_id in zip(self.node.ids, output_ids) - ), - ) - ) - - def drop_columns(self, columns: Iterable[str]) -> ArrayValue: - return self.select_columns( - [col_id for col_id in self.column_ids if col_id not in columns] - ) - - def aggregate( - self, - aggregations: typing.Sequence[typing.Tuple[agg_expressions.Aggregation, str]], - by_column_ids: typing.Sequence[str] = (), - dropna: bool = True, - ) -> ArrayValue: - """ - Apply aggregations to the expression. - Arguments: - aggregations: input_column_id, operation, output_column_id tuples - by_column_id: column id of the aggregation key, this is preserved through the transform - dropna: whether null keys should be dropped - """ - agg_defs = tuple((agg, ids.ColumnId(name)) for agg, name in aggregations) - return ArrayValue( - nodes.AggregateNode( - child=self.node, - aggregations=agg_defs, - by_column_ids=tuple(map(ex.deref, by_column_ids)), - dropna=dropna, - ) - ) - - def project_window_expr( - self, - expressions: Sequence[agg_expressions.Aggregation], - window: WindowSpec, - ): - id_strings = [self._gen_namespaced_uid() for _ in expressions] - agg_exprs = tuple( - nodes.ColumnDef(expression, ids.ColumnId(id_str)) - for expression, id_str in zip(expressions, id_strings) - ) - - return ( - ArrayValue( - nodes.WindowOpNode( - child=self.node, - agg_exprs=agg_exprs, - window_spec=window, - ) - ), - id_strings, - ) - - def isin( - self, - other: ArrayValue, - lcol: str, - ) -> typing.Tuple[ArrayValue, str]: - assert len(other.column_ids) == 1 - node = nodes.InNode( - self.node, - other.node, - ex.deref(lcol), - indicator_col=ids.ColumnId.unique(), - ) - return ArrayValue(node), node.indicator_col.name - - def relational_join( - self, - other: ArrayValue, - conditions: typing.Tuple[typing.Tuple[str, str], ...] = (), - type: typing.Literal["inner", "outer", "left", "right", "cross"] = "inner", - propogate_order: Optional[bool] = None, - ) -> typing.Tuple[ArrayValue, typing.Tuple[dict[str, str], dict[str, str]]]: - for lcol, rcol in conditions: - ltype = self.get_column_type(lcol) - rtype = other.get_column_type(rcol) - if not bigframes.dtypes.can_compare(ltype, rtype): - raise TypeError( - f"Cannot join with non-comparable join key types: {ltype}, {rtype}" - ) - - l_mapping = { # Identity mapping, only rename right side - lcol.name: lcol.name for lcol in self.node.ids - } - other_node, r_mapping = self.prepare_join_names(other) - join_node = nodes.JoinNode( - left_child=self.node, - right_child=other_node, - conditions=tuple( - (ex.deref(l_mapping[l_col]), ex.deref(r_mapping[r_col])) - for l_col, r_col in conditions - ), - type=type, - nulls_equal=True, # pandas semantics - propogate_order=propogate_order or self.session._strictly_ordered, - ) - return ArrayValue(join_node), (l_mapping, r_mapping) - - def try_row_join( - self, - other: ArrayValue, - conditions: typing.Tuple[typing.Tuple[str, str], ...] = (), - ) -> Optional[ - typing.Tuple[ArrayValue, typing.Tuple[dict[str, str], dict[str, str]]] - ]: - l_mapping = { # Identity mapping, only rename right side - lcol.name: lcol.name for lcol in self.node.ids - } - other_node, r_mapping = self.prepare_join_names(other) - import bigframes.core.rewrite - - result_node = bigframes.core.rewrite.try_row_join( - self.node, other_node, conditions - ) - if result_node is None: - return None - - return ( - ArrayValue(result_node), - (l_mapping, r_mapping), - ) - - def prepare_join_names( - self, other: ArrayValue - ) -> Tuple[bigframes.core.nodes.BigFrameNode, dict[str, str]]: - if set(other.node.ids) & set(self.node.ids): - r_mapping = { # Rename conflicting names - rcol.name: rcol.name - if (rcol.name not in self.column_ids) - else bigframes.core.guid.generate_guid() - for rcol in other.node.ids - } - return ( - nodes.SelectionNode( - other.node, - tuple( - bigframes.core.nodes.AliasedRef( - ex.deref(old_id), ids.ColumnId(new_id) - ) - for old_id, new_id in r_mapping.items() - ), - ), - r_mapping, - ) - else: - return other.node, {id: id for id in other.column_ids} - - def try_legacy_row_join( - self, - other: ArrayValue, - join_type: join_def.JoinType, - join_keys: typing.Tuple[join_def.CoalescedColumnMapping, ...], - mappings: typing.Tuple[join_def.JoinColumnMapping, ...], - ) -> typing.Optional[ArrayValue]: - import bigframes.core.rewrite - - result = bigframes.core.rewrite.legacy_join_as_projection( - self.node, other.node, join_keys, mappings, join_type - ) - if result is not None: - return ArrayValue(result) - return None - - def explode(self, column_ids: typing.Sequence[str]) -> ArrayValue: - assert len(column_ids) > 0 - for column_id in column_ids: - assert bigframes.dtypes.is_array_like(self.get_column_type(column_id)) - - offsets = tuple(ex.deref(id) for id in column_ids) - return ArrayValue(nodes.ExplodeNode(child=self.node, column_ids=offsets)) - - def _uniform_sampling(self, fraction: float) -> ArrayValue: - """Sampling the table on given fraction. - - .. warning:: - The row numbers of result is non-deterministic, avoid to use. - """ - return ArrayValue(nodes.RandomSampleNode(self.node, fraction)) - - # Deterministically generate namespaced ids for new variables - # These new ids are only unique within the current namespace. - # Many operations, such as joins, create new namespaces. See: BigFrameNode.defines_namespace - # When migrating to integer ids, these will generate the next available integer, in order to densely pack ids - # this will help represent variables sets as compact bitsets - def _gen_namespaced_uid(self) -> str: - return self._gen_namespaced_uids(1)[0] - - def _gen_namespaced_uids(self, n: int) -> List[str]: - return [ids.ColumnId.unique().name for _ in range(n)] diff --git a/bigframes/core/backports.py b/bigframes/core/backports.py deleted file mode 100644 index 09ba09731c2..00000000000 --- a/bigframes/core/backports.py +++ /dev/null @@ -1,33 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -"""Helpers for working across versions of different depenencies.""" - -from typing import List - -import pyarrow - - -def pyarrow_struct_type_fields(struct_type: pyarrow.StructType) -> List[pyarrow.Field]: - """StructType.fields was added in pyarrow 18. - - See: https://arrow.apache.org/docs/18.0/python/generated/pyarrow.StructType.html - """ - - if hasattr(struct_type, "fields"): - return struct_type.fields - - return [ - struct_type.field(field_index) for field_index in range(struct_type.num_fields) - ] diff --git a/bigframes/core/bigframe_node.py b/bigframes/core/bigframe_node.py deleted file mode 100644 index c48605dc248..00000000000 --- a/bigframes/core/bigframe_node.py +++ /dev/null @@ -1,390 +0,0 @@ -# Copyright 2023 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 abc -import collections -import dataclasses -import functools -import itertools -import typing -from typing import Callable, Dict, Generator, Iterable, Mapping, Sequence, Tuple - -import bigframes.core.schema as schemata -import bigframes.dtypes -from bigframes.core import expression, field, identifiers - -COLUMN_SET = frozenset[identifiers.ColumnId] - -T = typing.TypeVar("T") - - -@dataclasses.dataclass(eq=False, frozen=True) -class BigFrameNode: - """ - Immutable node for representing 2D typed array as a tree of operators. - - All subclasses must be hashable so as to be usable as caching key. - """ - - @property - def deterministic(self) -> bool: - """Whether this node will evaluates deterministically.""" - return True - - @property - def row_preserving(self) -> bool: - """Whether this node preserves input rows.""" - return True - - @property - def non_local(self) -> bool: - """ - Whether this node combines information across multiple rows instead of processing rows independently. - Used as an approximation for whether the expression may require shuffling to execute (and therefore be expensive). - """ - return False - - @property - def child_nodes(self) -> typing.Sequence[BigFrameNode]: - """Direct children of this node""" - return tuple([]) - - @property - @abc.abstractmethod - def row_count(self) -> typing.Optional[int]: - return None - - @abc.abstractmethod - def remap_refs( - self, mappings: Mapping[identifiers.ColumnId, identifiers.ColumnId] - ) -> BigFrameNode: - """Remap variable references""" - ... - - @property - @abc.abstractmethod - def node_defined_ids(self) -> Tuple[identifiers.ColumnId, ...]: - """The variables defined in this node (as opposed to by child nodes).""" - ... - - @functools.cached_property - def session(self): - sessions = [] - for child in self.child_nodes: - if child.session is not None: - sessions.append(child.session) - unique_sessions = len(set(sessions)) - if unique_sessions > 1: - raise ValueError("Cannot use combine sources from multiple sessions.") - elif unique_sessions == 1: - return sessions[0] - return None - - def _validate(self): - """Validate the local data in the node.""" - return - - @functools.cache - def validate_tree(self) -> bool: - for child in self.child_nodes: - child.validate_tree() - self._validate() - field_list = list(self.fields) - if len(set(field_list)) != len(field_list): - raise ValueError(f"Non unique field ids {list(self.fields)}") - return True - - def _as_tuple(self) -> Tuple: - """Get all fields as tuple.""" - return tuple(getattr(self, field.name) for field in dataclasses.fields(self)) - - def __hash__(self) -> int: - # Custom hash that uses cache to avoid costly recomputation - return self._cached_hash - - def __eq__(self, other) -> bool: - # Custom eq that tries to short-circuit full structural comparison - if not isinstance(other, self.__class__): - return False - if self is other: - return True - if hash(self) != hash(other): - return False - return self._as_tuple() == other._as_tuple() - - # BigFrameNode trees can be very deep so its important avoid recalculating the hash from scratch - # Each subclass of BigFrameNode should use this property to implement __hash__ - # The default dataclass-generated __hash__ method is not cached - @functools.cached_property - def _cached_hash(self): - return hash(self._as_tuple()) - - @property - def roots(self) -> typing.Set[BigFrameNode]: - roots = itertools.chain.from_iterable( - map(lambda child: child.roots, self.child_nodes) - ) - return set(roots) - - # TODO: Store some local data lazily for select, aggregate nodes. - @property - @abc.abstractmethod - def fields(self) -> Sequence[field.Field]: ... - - @property - def ids(self) -> Iterable[identifiers.ColumnId]: - """All output ids from the node.""" - return (field.id for field in self.fields) - - @property - @abc.abstractmethod - def variables_introduced(self) -> int: - """ - Defines number of values created by the current node. Helps represent the "width" of a query - """ - ... - - @property - def relation_ops_created(self) -> int: - """ - Defines the number of relational ops generated by the current node. Used to estimate query planning complexity. - """ - return 1 - - @property - def joins(self) -> bool: - """ - Defines whether the node joins data. - """ - return False - - @property - @abc.abstractmethod - def order_ambiguous(self) -> bool: - """ - Whether row ordering is potentially ambiguous. For example, ReadTable (without a primary key) could be ordered in different ways. - """ - ... - - @property - @abc.abstractmethod - def explicitly_ordered(self) -> bool: - """ - Whether row ordering is potentially ambiguous. For example, ReadTable (without a primary key) could be ordered in different ways. - """ - ... - - @functools.cached_property - def height(self) -> int: - if len(self.child_nodes) == 0: - return 0 - return max(child.height for child in self.child_nodes) + 1 - - @functools.cached_property - def total_variables(self) -> int: - return self.variables_introduced + sum( - map(lambda x: x.total_variables, self.child_nodes) - ) - - @functools.cached_property - def total_relational_ops(self) -> int: - return self.relation_ops_created + sum( - map(lambda x: x.total_relational_ops, self.child_nodes) - ) - - @functools.cached_property - def total_joins(self) -> int: - return int(self.joins) + sum(map(lambda x: x.total_joins, self.child_nodes)) - - @functools.cached_property - def schema(self) -> schemata.ArraySchema: - # TODO: Make schema just a view on fields - return schemata.ArraySchema( - tuple(schemata.SchemaItem(i.id.name, i.dtype) for i in self.fields) - ) - - @property - def planning_complexity(self) -> int: - """ - Empirical heuristic measure of planning complexity. - - Used to determine when to decompose overly complex computations. May require tuning. - """ - return self.total_variables * self.total_relational_ops * (1 + self.total_joins) - - @abc.abstractmethod - def transform_children( - self, t: Callable[[BigFrameNode], BigFrameNode] - ) -> BigFrameNode: - """Apply a function to each child node.""" - ... - - @abc.abstractmethod - def remap_vars( - self, mappings: Mapping[identifiers.ColumnId, identifiers.ColumnId] - ) -> BigFrameNode: - """Remap defined (in this node only) variables.""" - ... - - @property - def defines_namespace(self) -> bool: - """ - If true, this node establishes a new column id namespace. - - If false, this node consumes and produces ids in the namespace - """ - return False - - @property - def referenced_ids(self) -> COLUMN_SET: - return frozenset() - - @functools.cached_property - def defined_variables(self) -> set[str]: - """Full set of variables defined in the namespace, even if not selected.""" - self_defined_variables = set(self.schema.names) - if self.defines_namespace: - return self_defined_variables - return self_defined_variables.union( - *(child.defined_variables for child in self.child_nodes) - ) - - def get_type(self, id: identifiers.ColumnId) -> bigframes.dtypes.Dtype: - return self._dtype_lookup[id] - - # TODO: Deprecate in favor of field_by_id, and eventually, by rich references - @functools.cached_property - def _dtype_lookup(self) -> dict[identifiers.ColumnId, bigframes.dtypes.Dtype]: - return {field.id: field.dtype for field in self.fields} - - @functools.cached_property - 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[expression.Expression]: - """List of expressions. Intended for checking engine compatibility with used ops.""" - return () - - # Plan algorithms - def unique_nodes( - self: BigFrameNode, - ) -> Generator[BigFrameNode, None, None]: - """Walks the tree for unique nodes""" - seen = set() - stack: list[BigFrameNode] = [self] - while stack: - item = stack.pop() - if item not in seen: - yield item - seen.add(item) - stack.extend(item.child_nodes) - - def iter_nodes_topo( - self: BigFrameNode, - ) -> Generator[BigFrameNode, None, None]: - """Returns nodes in reverse topological order, using Kahn's algorithm.""" - child_to_parents: Dict[BigFrameNode, list[BigFrameNode]] = ( - collections.defaultdict(list) - ) - out_degree: Dict[BigFrameNode, int] = collections.defaultdict(int) - - queue: collections.deque["BigFrameNode"] = collections.deque() - for node in list(self.unique_nodes()): - num_children = len(node.child_nodes) - out_degree[node] = num_children - if num_children == 0: - queue.append(node) - for child in node.child_nodes: - child_to_parents[child].append(node) - - while queue: - item = queue.popleft() - yield item - parents = child_to_parents.get(item, []) - for parent in parents: - out_degree[parent] -= 1 - if out_degree[parent] == 0: - queue.append(parent) - - def top_down( - self: BigFrameNode, - transform: Callable[[BigFrameNode], BigFrameNode], - ) -> BigFrameNode: - """ - Perform a top-down transformation of the BigFrameNode tree. - """ - results: Dict[BigFrameNode, BigFrameNode] = {} - # Each stack entry is (node, t_node). t_node is None until transform(node) is called. - stack: list[tuple[BigFrameNode, typing.Optional[BigFrameNode]]] = [(self, None)] - - while stack: - node, t_node = stack[-1] - - if t_node is None: - if node in results: - stack.pop() - continue - t_node = transform(node) - stack[-1] = (node, t_node) - - all_done = True - for child in reversed(t_node.child_nodes): - if child not in results: - stack.append((child, None)) - all_done = False - break - - if all_done: - results[node] = t_node.transform_children(lambda x: results[x]) - stack.pop() - - return results[self] - - def bottom_up( - self: BigFrameNode, - transform: Callable[[BigFrameNode], BigFrameNode], - ) -> BigFrameNode: - """ - Perform a bottom-up transformation of the BigFrameNode tree. - - The `transform` function is applied to each node *after* its children - have been transformed. This allows for transformations that depend - on the results of transforming subtrees. - - Returns the transformed root node. - """ - results: dict[BigFrameNode, BigFrameNode] = {} - for node in list(self.iter_nodes_topo()): - # child nodes have already been transformed - result = node.transform_children(lambda x: results[x]) - result = transform(result) - results[node] = result - - return results[self] - - def reduce_up(self, reduction: Callable[[BigFrameNode, Tuple[T, ...]], T]) -> T: - """Apply a bottom-up reduction to the tree.""" - results: dict[BigFrameNode, T] = {} - for node in list(self.iter_nodes_topo()): - # child nodes have already been transformed - child_results = tuple(results[child] for child in node.child_nodes) - result = reduction(node, child_results) - results[node] = result - - return results[self] diff --git a/bigframes/core/block_transforms.py b/bigframes/core/block_transforms.py index c919b88614d..917edac0ded 100644 --- a/bigframes/core/block_transforms.py +++ b/bigframes/core/block_transforms.py @@ -13,123 +13,17 @@ # limitations under the License. from __future__ import annotations -import functools -import inspect import typing -from typing import Callable, Hashable, Optional, Sequence -import bigframes_vendored.constants as constants import pandas as pd -import bigframes.constants +import bigframes.constants as constants import bigframes.core as core import bigframes.core.blocks as blocks -import bigframes.core.bytecode as bytecode -import bigframes.core.expression as ex import bigframes.core.ordering as ordering -import bigframes.core.window_spec as window_specs -import bigframes.dtypes as dtypes -import bigframes.functions +import bigframes.core.window_spec as windows import bigframes.operations as ops import bigframes.operations.aggregations as agg_ops -from bigframes._config import options -from bigframes.core import agg_expressions, py_expressions - - -def compile_udf( - block: blocks.Block, - func: Callable, - args: tuple = (), - kwargs: dict | None = None, - col_series_args: typing.Mapping[str, str] | None = None, - window_spec: Optional[window_specs.WindowSpec] = None, -) -> ex.Expression: - """Compile a python function to a BigFrames expression in the context of a block.""" - if kwargs is None: - kwargs = {} - expr = bytecode._compile_bytecode_to_py_expr(func) - sig = inspect.signature(func) - - bindings: dict[Hashable, ex.Expression] = {} - - bound_args = sig.bind(*(None, *args), **kwargs) - bound_args.apply_defaults() - bound_params = bound_args.arguments - for name, value in bound_params.items(): - bindings[name] = ex.const(value) - - series_arg = next(iter(sig.parameters.keys())) - - if col_series_args is not None: - expr = py_expressions.resolve_py_exprs( - expr, - series_arg=series_arg, - col_series_args=col_series_args, - window_spec=window_spec, - ) - else: - series_attrs: dict = {} - for i, (col_id, label) in enumerate( - zip(block.value_columns, block.column_labels) - ): - series_attrs[i] = col_id - if label is not None: - series_attrs[label] = col_id - - expr = py_expressions.resolve_py_exprs( - expr, - series_arg=series_arg, - series_attrs=series_attrs, - window_spec=window_spec, - ) - - expr = expr.bind_variables(bindings) - return expr - - -def is_transpiler_eligible(func: typing.Any) -> bool: - """Return True if func is eligible for Python transpilation.""" - return ( - options.experiments.enable_python_transpiler - and callable(func) - and not isinstance(func, bigframes.functions.Udf) - ) - - -def compile_column_udf( - block: blocks.Block, - func: Callable, - column_id: str, - args: tuple = (), - kwargs: dict | None = None, - window_spec: Optional[window_specs.WindowSpec] = None, -) -> tuple[ex.Expression, str]: - """Compile a column-wise python UDF in block context and return (expr, name).""" - sig = inspect.signature(func) - series_arg = next(iter(sig.parameters.keys())) - expr = compile_udf( - block, - func, - args=args, - kwargs=kwargs, - col_series_args={series_arg: column_id}, - window_spec=window_spec, - ) - name = getattr(func, "__name__", "") - return expr, name - - -def apply_to_block_rows( - func: Callable, block: blocks.Block, *args, **kwargs -) -> blocks.Block: - """ - Apply the given function to each row of the block. - - The function is applied to each row of the block, and the result is returned - as a new block with the same index. - """ - expr = compile_udf(block, func, args, kwargs) - return block.project_exprs([expr], labels=[None], drop=True) def equals(block1: blocks.Block, block2: blocks.Block) -> bool: @@ -142,18 +36,23 @@ def equals(block1: blocks.Block, block2: blocks.Block) -> bool: block1 = block1.reset_index(drop=False) block2 = block2.reset_index(drop=False) - joined_block, (lmap, rmap) = block1.join(block2, how="outer") + joined, (lmap, rmap) = block1.index.join(block2.index, how="outer") + joined_block = joined._block - exprs = [] + equality_ids = [] for lcol, rcol in zip(block1.value_columns, block2.value_columns): - exprs.append( - ops.fillna_op.as_expr( - ops.eq_null_match_op.as_expr(lmap[lcol], rmap[rcol]), ex.const(False) - ) + lcolmapped = lmap[lcol] + rcolmapped = rmap[rcol] + joined_block, result_id = joined_block.apply_binary_op( + lcolmapped, rcolmapped, ops.eq_nulls_match_op + ) + joined_block, result_id = joined_block.apply_unary_op( + result_id, ops.partial_right(ops.fillna_op, False) ) + equality_ids.append(result_id) - joined_block = joined_block.project_exprs( - exprs, labels=list(range(len(exprs))), drop=True + joined_block = joined_block.select_columns(equality_ids).with_column_labels( + list(range(len(equality_ids))) ) stacked_block = joined_block.stack() result = stacked_block.get_stat(stacked_block.value_columns[0], agg_ops.all_op) @@ -167,108 +66,58 @@ def indicate_duplicates( if keep not in ["first", "last", False]: raise ValueError("keep must be one of 'first', 'last', or False'") - rownums = agg_expressions.WindowExpression( - agg_expressions.NullaryAggregation( - agg_ops.RowNumberOp(), - ), - window=window_specs.unbound(grouping_keys=tuple(columns)), - ) - count = agg_expressions.WindowExpression( - agg_expressions.NullaryAggregation( - agg_ops.SizeOp(), - ), - window=window_specs.unbound(grouping_keys=tuple(columns)), - ) - if keep == "first": # Count how many copies occur up to current copy of value # Discard this value if there are copies BEFORE - predicate = ops.gt_op.as_expr(rownums, ex.const(0)) + window_spec = windows.WindowSpec( + grouping_keys=tuple(columns), + following=0, + ) elif keep == "last": # Count how many copies occur up to current copy of values # Discard this value if there are copies AFTER - predicate = ops.lt_op.as_expr(rownums, ops.sub_op.as_expr(count, ex.const(1))) + window_spec = windows.WindowSpec( + grouping_keys=tuple(columns), + preceding=0, + ) else: # keep == False # Count how many copies of the value occur in entire series. # Discard this value if there are copies ANYWHERE - predicate = ops.gt_op.as_expr(count, ex.const(1)) - - block = block.project_block_exprs( - [predicate], - labels=[None], - ) - return ( - block, - block.value_columns[-1], + window_spec = windows.WindowSpec(grouping_keys=tuple(columns)) + block, dummy = block.create_constant(1) + block, val_count_col_id = block.apply_window_op( + dummy, + agg_ops.count_op, + window_spec=window_spec, ) - - -def quantile( - block: blocks.Block, - columns: Sequence[str], - qs: Sequence[float], - grouping_column_ids: Sequence[str] = (), - dropna: bool = False, -) -> blocks.Block: - # TODO: handle windowing and more interpolation methods - window = window_specs.unbound( - grouping_keys=tuple(grouping_column_ids), + block, duplicate_indicator = block.apply_unary_op( + val_count_col_id, + ops.partial_right(ops.gt_op, 1), ) - quantile_cols = [] - labels = [] - if len(columns) * len(qs) > bigframes.constants.MAX_COLUMNS: - raise NotImplementedError("Too many aggregates requested.") - for col in columns: - for q in qs: - label = block.col_id_to_label[col] - new_label = (*label, q) if isinstance(label, tuple) else (label, q) - labels.append(new_label) - block, quantile_col = block.apply_window_op( - col, - agg_ops.QuantileOp(q), - window_spec=window, + return ( + block.drop_columns( + ( + dummy, + val_count_col_id, ) - quantile_cols.append(quantile_col) - block = block.aggregate( - tuple( - agg_expressions.UnaryAggregation(agg_ops.AnyValueOp(), ex.deref(col)) - for col in quantile_cols ), - grouping_column_ids, - column_labels=pd.Index(labels), - dropna=dropna, + duplicate_indicator, ) - return block def interpolate(block: blocks.Block, method: str = "linear") -> blocks.Block: - supported_methods = [ - "linear", - "values", - "index", - "nearest", - "zero", - "slinear", - ] - if method not in supported_methods: + if method != "linear": raise NotImplementedError( - f"Method {method} not supported, following interpolate methods supported: {', '.join(supported_methods)}. {constants.FEEDBACK_LINK}" + f"Only 'linear' interpolate method supported. {constants.FEEDBACK_LINK}" ) + backwards_window = windows.WindowSpec(following=0) + forwards_window = windows.WindowSpec(preceding=0) + output_column_ids = [] original_columns = block.value_columns original_labels = block.column_labels - - if method == "linear": # Assumes evenly spaced, ignore index - block, xvalues = block.promote_offsets() - else: - index_columns = block.index_columns - if len(index_columns) != 1: - raise ValueError("only method 'linear' supports multi-index") - xvalues = block.index_columns[0] - if block.index.dtypes[0] not in dtypes.NUMERIC_BIGFRAMES_TYPES_PERMISSIVE: - raise ValueError("Can only interpolate on numeric index.") - + block, offsets = block.promote_offsets() for column in original_columns: # null in same places column is null should_interpolate = block._column_type(column) in [ @@ -276,104 +125,57 @@ def interpolate(block: blocks.Block, method: str = "linear") -> blocks.Block: pd.Int64Dtype(), ] if should_interpolate: - interpolate_method_map = { - "linear": "linear", - "values": "linear", - "index": "linear", - "slinear": "linear", - "zero": "ffill", - "nearest": "nearest", - } - extrapolating_methods = ["linear", "values", "index"] - interpolate_method = interpolate_method_map[method] - do_extrapolate = method in extrapolating_methods - block, interpolated = _interpolate_column( - block, - column, - xvalues, - interpolate_method=interpolate_method, - do_extrapolate=do_extrapolate, + block, notnull = block.apply_unary_op(column, ops.notnull_op) + block, masked_offsets = block.apply_binary_op( + offsets, notnull, ops.partial_arg3(ops.where_op, None) ) - output_column_ids.append(interpolated) - else: - output_column_ids.append(column) - - block = block.select_columns(output_column_ids) - return block.with_column_labels(original_labels) + block, previous_value = block.apply_window_op( + column, agg_ops.LastNonNullOp(), backwards_window + ) + block, next_value = block.apply_window_op( + column, agg_ops.FirstNonNullOp(), forwards_window + ) + block, previous_value_offset = block.apply_window_op( + masked_offsets, + agg_ops.LastNonNullOp(), + backwards_window, + skip_reproject_unsafe=True, + ) + block, next_value_offset = block.apply_window_op( + masked_offsets, + agg_ops.FirstNonNullOp(), + forwards_window, + skip_reproject_unsafe=True, + ) -def _interpolate_column( - block: blocks.Block, - column: str, - x_values: str, - interpolate_method: str, - do_extrapolate: bool = True, -) -> typing.Tuple[blocks.Block, str]: - if interpolate_method not in ["linear", "nearest", "ffill"]: - raise ValueError("interpolate method not supported") - window_ordering = (ordering.OrderingExpression(ex.deref(x_values)),) - backwards_window = window_specs.rows(end=0, ordering=window_ordering) - forwards_window = window_specs.rows(start=0, ordering=window_ordering) - - # Note, this method may - block, notnull = block.apply_unary_op(column, ops.notnull_op) - block, masked_offsets = block.project_expr( - ops.where_op.as_expr(x_values, notnull, ex.const(None)) - ) + block, prediction_id = _interpolate( + block, + previous_value_offset, + previous_value, + next_value_offset, + next_value, + offsets, + ) - block, previous_value = block.apply_window_op( - column, agg_ops.LastNonNullOp(), backwards_window - ) - block, next_value = block.apply_window_op( - column, agg_ops.FirstNonNullOp(), forwards_window - ) - block, previous_value_offset = block.apply_window_op( - masked_offsets, - agg_ops.LastNonNullOp(), - backwards_window, - ) - block, next_value_offset = block.apply_window_op( - masked_offsets, - agg_ops.FirstNonNullOp(), - forwards_window, - ) + block, interpolated_column = block.apply_binary_op( + column, prediction_id, ops.fillna_op + ) + # Pandas performs ffill-like behavior to extrapolate forwards + block, interpolated_and_ffilled = block.apply_binary_op( + interpolated_column, previous_value, ops.fillna_op + ) - if interpolate_method == "linear": - block, prediction_id = _interpolate_points_linear( - block, - previous_value_offset, - previous_value, - next_value_offset, - next_value, - x_values, - ) - elif interpolate_method == "nearest": - block, prediction_id = _interpolate_points_nearest( - block, - previous_value_offset, - previous_value, - next_value_offset, - next_value, - x_values, - ) - else: # interpolate_method == 'ffill': - block, prediction_id = _interpolate_points_ffill( - block, - previous_value_offset, - previous_value, - next_value_offset, - next_value, - x_values, - ) - if do_extrapolate: - block, prediction_id = block.apply_binary_op( - prediction_id, previous_value, ops.fillna_op - ) + output_column_ids.append(interpolated_and_ffilled) + else: + output_column_ids.append(column) - return block.apply_binary_op(column, prediction_id, ops.fillna_op) + # Force reproject since used `skip_project_unsafe` perviously + block = block.select_columns(output_column_ids)._force_reproject() + return block.with_column_labels(original_labels) -def _interpolate_points_linear( +def _interpolate( block: blocks.Block, x0_id: str, y0_id: str, @@ -394,54 +196,12 @@ def _interpolate_points_linear( return block, prediction_id -def _interpolate_points_nearest( - block: blocks.Block, - x0_id: str, - y0_id: str, - x1_id: str, - y1_id: str, - xpredict_id: str, -) -> typing.Tuple[blocks.Block, str]: - """Interpolate by taking the y value of the nearest x value""" - left_diff = ops.sub_op.as_expr(xpredict_id, x0_id) - right_diff = ops.sub_op.as_expr(x1_id, xpredict_id) - # If diffs equal, choose left - choose_left = ops.fillna_op.as_expr( - ops.le_op.as_expr(left_diff, right_diff), ex.const(False) - ) - - nearest = ops.where_op.as_expr(y0_id, choose_left, y1_id) - - is_interpolation = ops.and_op.as_expr( - ops.notnull_op.as_expr(y0_id), ops.notnull_op.as_expr(y1_id) - ) - - return block.project_expr( - ops.where_op.as_expr(nearest, is_interpolation, ex.const(None)) - ) - - -def _interpolate_points_ffill( - block: blocks.Block, - x0_id: str, - y0_id: str, - x1_id: str, - y1_id: str, - xpredict_id: str, -) -> typing.Tuple[blocks.Block, str]: - """Interpolates by using the preceding values""" - # check for existance of y1, otherwise we are extrapolating instead of interpolating - return block.project_expr( - ops.where_op.as_expr(y0_id, ops.notnull_op.as_expr(y1_id), ex.const(None)) - ) - - def drop_duplicates( block: blocks.Block, columns: typing.Sequence[str], keep: str = "first" ) -> blocks.Block: block, dupe_indicator_id = indicate_duplicates(block, columns, keep) block, keep_indicator_id = block.apply_unary_op(dupe_indicator_id, ops.invert_op) - return block.filter_by_id(keep_indicator_id).drop_columns( + return block.filter(keep_indicator_id).drop_columns( (dupe_indicator_id, keep_indicator_id) ) @@ -452,62 +212,54 @@ def value_counts( normalize: bool = False, sort: bool = True, ascending: bool = False, - drop_na: bool = True, - grouping_keys: typing.Sequence[str] = (), + dropna: bool = True, ): - if grouping_keys and drop_na: - # only need this if grouping_keys is involved, otherwise the drop_na in the aggregation will handle it for us - block = dropna(block, columns, how="any") - block = block.aggregate( - aggregations=[agg_expressions.NullaryAggregation(agg_ops.size_op)], - by_column_ids=(*grouping_keys, *columns), - dropna=drop_na and not grouping_keys, + block, dummy = block.create_constant(1) + block, agg_ids = block.aggregate( + by_column_ids=columns, + aggregations=[(dummy, agg_ops.count_op)], + dropna=dropna, + as_index=True, ) - count_id = block.value_columns[0] + count_id = agg_ids[0] if normalize: - unbound_window = window_specs.unbound(grouping_keys=tuple(grouping_keys)) + unbound_window = windows.WindowSpec() block, total_count_id = block.apply_window_op( count_id, agg_ops.sum_op, unbound_window ) block, count_id = block.apply_binary_op(count_id, total_count_id, ops.div_op) if sort: - order_parts = [ordering.ascending_over(id) for id in grouping_keys] - order_parts.extend( + block = block.order_by( [ - ordering.OrderingExpression( - ex.deref(count_id), + ordering.OrderingColumnReference( + count_id, direction=ordering.OrderingDirection.ASC if ascending else ordering.OrderingDirection.DESC, ) ] ) - block = block.order_by(order_parts) - return block.select_column(count_id).with_column_labels( - ["proportion" if normalize else "count"] - ) + return block.select_column(count_id).with_column_labels(["count"]) def pct_change(block: blocks.Block, periods: int = 1) -> blocks.Block: column_labels = block.column_labels - - # Window framing clause is not allowed for analytic function lag. - window_spec = window_specs.unbound() + window_spec = windows.WindowSpec( + preceding=periods if periods > 0 else None, + following=-periods if periods < 0 else None, + ) original_columns = block.value_columns - exprs = [] - for original_col in original_columns: - shift_expr = agg_expressions.WindowExpression( - agg_expressions.UnaryAggregation( - agg_ops.ShiftOp(periods), ex.deref(original_col) - ), - window_spec, - ) - change_expr = ops.sub_op.as_expr(original_col, shift_expr) - pct_change_expr = ops.div_op.as_expr(change_expr, shift_expr) - exprs.append(pct_change_expr) - return block.project_block_exprs(exprs, labels=column_labels, drop=True) + block, shift_columns = block.multi_apply_window_op( + original_columns, agg_ops.ShiftOp(periods), window_spec=window_spec + ) + result_ids = [] + for original_col, shifted_col in zip(original_columns, shift_columns): + block, change_id = block.apply_binary_op(original_col, shifted_col, ops.sub_op) + block, pct_change_id = block.apply_binary_op(change_id, shifted_col, ops.div_op) + result_ids.append(pct_change_id) + return block.select_columns(result_ids).with_column_labels(column_labels) def rank( @@ -515,9 +267,6 @@ def rank( method: str = "average", na_option: str = "keep", ascending: bool = True, - grouping_cols: tuple[str, ...] = (), - columns: tuple[str, ...] = (), - pct: bool = False, ): if method not in ["average", "min", "max", "first", "dense"]: raise ValueError( @@ -526,131 +275,109 @@ def rank( if na_option not in ["keep", "top", "bottom"]: raise ValueError("na_option must be one of 'keep', 'top', or 'bottom'") - columns = columns or tuple(col for col in block.value_columns) - labels = [block.col_id_to_label[id] for id in columns] - - result_exprs = [] + columns = block.value_columns + labels = block.column_labels + # Step 1: Calculate row numbers for each row + # Identify null values to be treated according to na_option param + rownum_col_ids = [] + nullity_col_ids = [] for col in columns: - # Step 1: Calculate row numbers for each row - # Identify null values to be treated according to na_option param - window_ordering = ( - ordering.OrderingExpression( - ex.deref(col), - ordering.OrderingDirection.ASC - if ascending - else ordering.OrderingDirection.DESC, - na_last=(na_option in ["bottom", "keep"]), + block, nullity_col_id = block.apply_unary_op( + col, + ops.isnull_op, + ) + nullity_col_ids.append(nullity_col_id) + window = windows.WindowSpec( + # BigQuery has syntax to reorder nulls with "NULLS FIRST/LAST", but that is unavailable through ibis presently, so must order on a separate nullity expression first. + ordering=( + ordering.OrderingColumnReference( + col, + ordering.OrderingDirection.ASC + if ascending + else ordering.OrderingDirection.DESC, + na_last=(na_option in ["bottom", "keep"]), + ), ), ) # Count_op ignores nulls, so if na_option is "top" or "bottom", we instead count the nullity columns, where nulls have been mapped to bools - target_expr = ( - ex.deref(col) if na_option == "keep" else ops.isnull_op.as_expr(col) + block, rownum_id = block.apply_window_op( + col if na_option == "keep" else nullity_col_id, + agg_ops.dense_rank_op if method == "dense" else agg_ops.count_op, + window_spec=window, + skip_reproject_unsafe=(col != columns[-1]), ) - window_op = agg_ops.dense_rank_op if method == "dense" else agg_ops.count_op - window_spec = ( - window_specs.unbound(grouping_keys=grouping_cols, ordering=window_ordering) - if method == "dense" - else window_specs.rows( - end=0, ordering=window_ordering, grouping_keys=grouping_cols + rownum_col_ids.append(rownum_id) + + # Step 2: Apply aggregate to groups of like input values. + # This step is skipped for method=='first' or 'dense' + if method in ["average", "min", "max"]: + agg_op = { + "average": agg_ops.mean_op, + "min": agg_ops.min_op, + "max": agg_ops.max_op, + }[method] + post_agg_rownum_col_ids = [] + for i in range(len(columns)): + block, result_id = block.apply_window_op( + rownum_col_ids[i], + agg_op, + window_spec=windows.WindowSpec(grouping_keys=(columns[i],)), + skip_reproject_unsafe=(i < (len(columns) - 1)), ) + post_agg_rownum_col_ids.append(result_id) + rownum_col_ids = post_agg_rownum_col_ids + + # Step 3: post processing: mask null values and cast to float + if method in ["min", "max", "first", "dense"]: + # Pandas rank always produces Float64, so must cast for aggregation types that produce ints + block = block.multi_apply_unary_op( + rownum_col_ids, ops.AsTypeOp(pd.Float64Dtype()) ) - result_expr: ex.Expression = agg_expressions.WindowExpression( - agg_expressions.UnaryAggregation(window_op, target_expr), window_spec - ) - if pct: - result_expr = ops.div_op.as_expr( - result_expr, - agg_expressions.WindowExpression( - agg_expressions.UnaryAggregation(agg_ops.max_op, result_expr), - window_specs.unbound(grouping_keys=grouping_cols), - ), - ) - # Step 2: Apply aggregate to groups of like input values. - # This step is skipped for method=='first' or 'dense' - if method in ["average", "min", "max"]: - agg_op = { - "average": agg_ops.mean_op, - "min": agg_ops.min_op, - "max": agg_ops.max_op, - }[method] - result_expr = agg_expressions.WindowExpression( - agg_expressions.UnaryAggregation(agg_op, result_expr), - window_specs.unbound(grouping_keys=(col, *grouping_cols)), - ) - # Pandas masks all values where any grouping column is null - # Note: we use pd.NA instead of float('nan') - if grouping_cols: - predicate = functools.reduce( - ops.and_op.as_expr, - [ops.notnull_op.as_expr(column_id) for column_id in grouping_cols], - ) - result_expr = ops.where_op.as_expr( - result_expr, - predicate, - ex.const(None), + if na_option == "keep": + # For na_option "keep", null inputs must produce null outputs + for i in range(len(columns)): + block, null_const = block.create_constant(pd.NA, dtype=pd.Float64Dtype()) + block, rownum_col_ids[i] = block.apply_ternary_op( + null_const, nullity_col_ids[i], rownum_col_ids[i], ops.where_op ) - # Step 3: post processing: mask null values and cast to float - if method in ["min", "max", "first", "dense"]: - # Pandas rank always produces Float64, so must cast for aggregation types that produce ints - result_expr = ops.AsTypeOp(pd.Float64Dtype()).as_expr(result_expr) - elif na_option == "keep": - # For na_option "keep", null inputs must produce null outputs - result_expr = ops.where_op.as_expr( - ex.const(pd.NA, dtype=pd.Float64Dtype()), - ops.isnull_op.as_expr(col), - result_expr, - ) - result_exprs.append(result_expr) - return block.project_block_exprs(result_exprs, labels=labels, drop=True) + return block.select_columns(rownum_col_ids).with_column_labels(labels) def dropna( block: blocks.Block, column_ids: typing.Sequence[str], - how: str = "any", - thresh: typing.Optional[int] = None, - subset: Optional[typing.Sequence[str]] = None, + how: typing.Literal["all", "any"] = "any", ): """ Drop na entries from block """ - if subset is None: - subset = column_ids - - # Predicates to check for non-null values in the subset of columns - predicates = [ - ops.notnull_op.as_expr(column_id) - for column_id in column_ids - if column_id in subset - ] - - if len(predicates) == 0: - return block - - if thresh is not None: - # Handle single predicate case - if len(predicates) == 1: - count_expr = ops.AsTypeOp(pd.Int64Dtype()).as_expr(predicates[0]) - else: - # Sum the boolean expressions to count non-null values - count_expr = functools.reduce( - lambda a, b: ops.add_op.as_expr( - ops.AsTypeOp(pd.Int64Dtype()).as_expr(a), - ops.AsTypeOp(pd.Int64Dtype()).as_expr(b), - ), - predicates, + if how == "any": + filtered_block = block + for column in column_ids: + filtered_block, result_id = filtered_block.apply_unary_op( + column, ops.notnull_op ) - # Filter rows where count >= thresh - predicate = ops.ge_op.as_expr(count_expr, ex.const(thresh)) - else: - # Only handle 'how' parameter when thresh is not specified - if how == "any": - predicate = functools.reduce(ops.and_op.as_expr, predicates) - else: # "all" - predicate = functools.reduce(ops.or_op.as_expr, predicates) - - return block.filter(predicate) + filtered_block = filtered_block.filter(result_id) + filtered_block = filtered_block.drop_columns([result_id]) + return filtered_block + else: # "all" + filtered_block = block + predicate = None + for column in column_ids: + filtered_block, partial_predicate = filtered_block.apply_unary_op( + column, ops.notnull_op + ) + if predicate: + filtered_block, predicate = filtered_block.apply_binary_op( + partial_predicate, predicate, ops.or_op + ) + else: + predicate = partial_predicate + if predicate: + filtered_block = filtered_block.filter(predicate) + filtered_block = filtered_block.select_columns(block.value_columns) + return filtered_block def nsmallest( @@ -664,22 +391,24 @@ def nsmallest( if keep == "last": block = block.reversed() order_refs = [ - ordering.OrderingExpression( - ex.deref(col_id), direction=ordering.OrderingDirection.ASC + ordering.OrderingColumnReference( + col_id, direction=ordering.OrderingDirection.ASC ) for col_id in column_ids ] - block = block.order_by(order_refs) + block = block.order_by(order_refs, stable=True) if keep in ("first", "last"): return block.slice(0, n) else: # keep == "all": block, counter = block.apply_window_op( column_ids[0], agg_ops.rank_op, - window_spec=window_specs.unbound(ordering=tuple(order_refs)), + window_spec=windows.WindowSpec(ordering=tuple(order_refs)), + ) + block, condition = block.apply_unary_op( + counter, ops.partial_right(ops.le_op, n) ) - block, condition = block.project_expr(ops.le_op.as_expr(counter, ex.const(n))) - block = block.filter_by_id(condition) + block = block.filter(condition) return block.drop_columns([counter, condition]) @@ -694,22 +423,24 @@ def nlargest( if keep == "last": block = block.reversed() order_refs = [ - ordering.OrderingExpression( - ex.deref(col_id), direction=ordering.OrderingDirection.DESC + ordering.OrderingColumnReference( + col_id, direction=ordering.OrderingDirection.DESC ) for col_id in column_ids ] - block = block.order_by(order_refs) + block = block.order_by(order_refs, stable=True) if keep in ("first", "last"): return block.slice(0, n) else: # keep == "all": block, counter = block.apply_window_op( column_ids[0], agg_ops.rank_op, - window_spec=window_specs.unbound(ordering=tuple(order_refs)), + window_spec=windows.WindowSpec(ordering=tuple(order_refs)), ) - block, condition = block.project_expr(ops.le_op.as_expr(counter, ex.const(n))) - block = block.filter_by_id(condition) + block, condition = block.apply_unary_op( + counter, ops.partial_right(ops.le_op, n) + ) + block = block.filter(condition) return block.drop_columns([counter, condition]) @@ -718,23 +449,39 @@ def skew( skew_column_ids: typing.Sequence[str], grouping_column_ids: typing.Sequence[str] = (), ) -> blocks.Block: + original_columns = skew_column_ids column_labels = block.select_columns(original_columns).column_labels + block, delta3_ids = _mean_delta_to_power( + block, 3, original_columns, grouping_column_ids + ) # counts, moment3 for each column aggregations = [] - for col in original_columns: - aggregations.append(skew_expr(ex.deref(col))) + for i, col in enumerate(original_columns): + count_agg = (col, agg_ops.count_op) + moment3_agg = (delta3_ids[i], agg_ops.mean_op) + variance_agg = (col, agg_ops.PopVarOp()) + aggregations.extend([count_agg, moment3_agg, variance_agg]) + + block, agg_ids = block.aggregate( + by_column_ids=grouping_column_ids, aggregations=aggregations + ) + + skew_ids = [] + for i, col in enumerate(original_columns): + # Corresponds to order of aggregations in preceding loop + count_id, moment3_id, var_id = agg_ids[i * 3 : (i * 3) + 3] + block, skew_id = _skew_from_moments_and_count( + block, count_id, moment3_id, var_id + ) + skew_ids.append(skew_id) - block = block.aggregate( - aggregations, grouping_column_ids, column_labels=column_labels - ) + block = block.select_columns(skew_ids).with_column_labels(column_labels) if not grouping_column_ids: - # When ungrouped, transpose result row into a series - # perform transpose last, so as to not invalidate cache - block, index_col = block.create_constant(None, None) - block = block.set_index([index_col]) - return block.transpose(original_row_index=pd.Index([None])) + # When ungrouped, stack everything into single column so can be returned as series + block = block.stack() + block = block.drop_levels([block.index_columns[0]]) return block @@ -745,89 +492,94 @@ def kurt( ) -> blocks.Block: original_columns = skew_column_ids column_labels = block.select_columns(original_columns).column_labels - # counts, moment4 for each column - kurt_exprs = [] - for col in original_columns: - kurt_exprs.append(kurt_expr(ex.deref(col))) - block = block.aggregate( - kurt_exprs, grouping_column_ids, column_labels=column_labels + block, delta4_ids = _mean_delta_to_power( + block, 4, original_columns, grouping_column_ids ) + # counts, moment4 for each column + aggregations = [] + for i, col in enumerate(original_columns): + count_agg = (col, agg_ops.count_op) + moment4_agg = (delta4_ids[i], agg_ops.mean_op) + variance_agg = (col, agg_ops.PopVarOp()) + aggregations.extend([count_agg, moment4_agg, variance_agg]) + + block, agg_ids = block.aggregate( + by_column_ids=grouping_column_ids, aggregations=aggregations + ) + + kurt_ids = [] + for i, col in enumerate(original_columns): + # Corresponds to order of aggregations in preceding loop + count_id, moment4_id, var_id = agg_ids[i * 3 : (i * 3) + 3] + block, kurt_id = _kurt_from_moments_and_count( + block, count_id, moment4_id, var_id + ) + kurt_ids.append(kurt_id) + + block = block.select_columns(kurt_ids).with_column_labels(column_labels) if not grouping_column_ids: - # When ungrouped, transpose result row into a series - # perform transpose last, so as to not invalidate cache - block, index_col = block.create_constant(None, None) - block = block.set_index([index_col]) - return block.transpose(original_row_index=pd.Index([None])) + # When ungrouped, stack everything into single column so can be returned as series + block = block.stack() + block = block.drop_levels([block.index_columns[0]]) return block -def skew_expr(expr: ex.Expression) -> ex.Expression: - delta3_expr = _mean_delta_to_power(3, expr) - count_agg = agg_expressions.UnaryAggregation( - agg_ops.count_op, - expr, - ) - moment3_agg = agg_expressions.UnaryAggregation( - agg_ops.mean_op, - delta3_expr, - ) - variance_agg = agg_expressions.UnaryAggregation( - agg_ops.PopVarOp(), - expr, - ) - return _skew_from_moments_and_count(count_agg, moment3_agg, variance_agg) - - -def kurt_expr(expr: ex.Expression) -> ex.Expression: - delta_4_expr = _mean_delta_to_power(4, expr) - count_agg = agg_expressions.UnaryAggregation(agg_ops.count_op, expr) - moment4_agg = agg_expressions.UnaryAggregation(agg_ops.mean_op, delta_4_expr) - variance_agg = agg_expressions.UnaryAggregation(agg_ops.PopVarOp(), expr) - return _kurt_from_moments_and_count(count_agg, moment4_agg, variance_agg) - - def _mean_delta_to_power( - n_power: int, - col_expr: ex.Expression, -) -> ex.Expression: + block: blocks.Block, + n_power, + column_ids: typing.Sequence[str], + grouping_column_ids: typing.Sequence[str], +) -> typing.Tuple[blocks.Block, typing.Sequence[str]]: """Calculate (x-mean(x))^n. Useful for calculating moment statistics such as skew and kurtosis.""" - mean_expr = agg_expressions.UnaryAggregation(agg_ops.mean_op, col_expr) - delta = ops.sub_op.as_expr(col_expr, mean_expr) - return ops.pow_op.as_expr(delta, ex.const(n_power)) + window = windows.WindowSpec(grouping_keys=tuple(grouping_column_ids)) + block, mean_ids = block.multi_apply_window_op(column_ids, agg_ops.mean_op, window) + delta_ids = [] + cube_op = ops.partial_right(ops.pow_op, n_power) + for val_id, mean_val_id in zip(column_ids, mean_ids): + block, delta_id = block.apply_binary_op(val_id, mean_val_id, ops.sub_op) + block, delta_power_id = block.apply_unary_op(delta_id, cube_op) + block = block.drop_columns([delta_id]) + delta_ids.append(delta_power_id) + return block, delta_ids def _skew_from_moments_and_count( - count: ex.Expression, moment3: ex.Expression, moment2: ex.Expression -) -> ex.Expression: + block: blocks.Block, count_id: str, moment3_id: str, moment2_id: str +) -> typing.Tuple[blocks.Block, str]: # Calculate skew using count, third moment and population variance # See G1 estimator: # https://en.wikipedia.org/wiki/Skewness#Sample_skewness - moments_estimator = ops.div_op.as_expr( - moment3, ops.pow_op.as_expr(moment2, ex.const(3 / 2)) + block, denominator_id = block.apply_unary_op( + moment2_id, ops.partial_right(ops.unsafe_pow_op, 3 / 2) ) - - countminus1 = ops.sub_op.as_expr(count, ex.const(1)) - countminus2 = ops.sub_op.as_expr(count, ex.const(2)) - adjustment = ops.div_op.as_expr( - ops.unsafe_pow_op.as_expr( - ops.mul_op.as_expr(count, countminus1), ex.const(1 / 2) - ), - countminus2, + block, base_id = block.apply_binary_op(moment3_id, denominator_id, ops.div_op) + block, countminus1_id = block.apply_unary_op( + count_id, ops.partial_right(ops.sub_op, 1) ) - - skew = ops.mul_op.as_expr(moments_estimator, adjustment) + block, countminus2_id = block.apply_unary_op( + count_id, ops.partial_right(ops.sub_op, 2) + ) + block, adjustment_id = block.apply_binary_op(count_id, countminus1_id, ops.mul_op) + block, adjustment_id = block.apply_unary_op( + adjustment_id, ops.partial_right(ops.unsafe_pow_op, 1 / 2) + ) + block, adjustment_id = block.apply_binary_op( + adjustment_id, countminus2_id, ops.div_op + ) + block, skew_id = block.apply_binary_op(base_id, adjustment_id, ops.mul_op) # Need to produce NA if have less than 3 data points - cleaned_skew = ops.where_op.as_expr( - skew, ops.ge_op.as_expr(count, ex.const(3)), ex.const(None) + block, na_cond_id = block.apply_unary_op(count_id, ops.partial_right(ops.ge_op, 3)) + block, skew_id = block.apply_binary_op( + skew_id, na_cond_id, ops.partial_arg3(ops.where_op, None) ) - return cleaned_skew + return block, skew_id def _kurt_from_moments_and_count( - count: ex.Expression, moment4: ex.Expression, moment2: ex.Expression -) -> ex.Expression: + block: blocks.Block, count_id: str, moment4_id: str, moment2_id: str +) -> typing.Tuple[blocks.Block, str]: # Kurtosis is often defined as the second standardize moment: moment(4)/moment(2)**2 # Pandas however uses Fisher’s estimator, implemented below # numerator = (count + 1) * (count - 1) * moment4 @@ -835,40 +587,49 @@ def _kurt_from_moments_and_count( # adjustment = 3 * (count - 1) ** 2 / ((count - 2) * (count - 3)) # kurtosis = (numerator / denominator) - adjustment - numerator = ops.mul_op.as_expr( - moment4, - ops.mul_op.as_expr( - ops.sub_op.as_expr(count, ex.const(1)), - ops.add_op.as_expr(count, ex.const(1)), - ), + # Numerator + block, countminus1_id = block.apply_unary_op( + count_id, ops.partial_right(ops.sub_op, 1) ) + block, countplus1_id = block.apply_unary_op( + count_id, ops.partial_right(ops.add_op, 1) + ) + block, num_adj = block.apply_binary_op(countplus1_id, countminus1_id, ops.mul_op) + block, numerator_id = block.apply_binary_op(moment4_id, num_adj, ops.mul_op) # Denominator - countminus2 = ops.sub_op.as_expr(count, ex.const(2)) - countminus3 = ops.sub_op.as_expr(count, ex.const(3)) - - # Denominator - denominator = ops.mul_op.as_expr( - ops.unsafe_pow_op.as_expr(moment2, ex.const(2)), - ops.mul_op.as_expr(countminus2, countminus3), + block, countminus2_id = block.apply_unary_op( + count_id, ops.partial_right(ops.sub_op, 2) + ) + block, countminus3_id = block.apply_unary_op( + count_id, ops.partial_right(ops.sub_op, 3) ) + block, denom_adj = block.apply_binary_op(countminus2_id, countminus3_id, ops.mul_op) + block, popvar_squared = block.apply_unary_op( + moment2_id, ops.partial_right(ops.unsafe_pow_op, 2) + ) + block, denominator_id = block.apply_binary_op(popvar_squared, denom_adj, ops.mul_op) # Adjustment - adj_num = ops.mul_op.as_expr( - ops.unsafe_pow_op.as_expr(ops.sub_op.as_expr(count, ex.const(1)), ex.const(2)), - ex.const(3), + block, countminus1_square = block.apply_unary_op( + countminus1_id, ops.partial_right(ops.unsafe_pow_op, 2) + ) + block, adj_num = block.apply_unary_op( + countminus1_square, ops.partial_right(ops.mul_op, 3) ) - adj_denom = ops.mul_op.as_expr(countminus2, countminus3) - adjustment = ops.div_op.as_expr(adj_num, adj_denom) + block, adj_denom = block.apply_binary_op(countminus2_id, countminus3_id, ops.mul_op) + block, adjustment_id = block.apply_binary_op(adj_num, adj_denom, ops.div_op) # Combine - kurt = ops.sub_op.as_expr(ops.div_op.as_expr(numerator, denominator), adjustment) + block, base_id = block.apply_binary_op(numerator_id, denominator_id, ops.div_op) + block, kurt_id = block.apply_binary_op(base_id, adjustment_id, ops.sub_op) # Need to produce NA if have less than 4 data points - cleaned_kurt = ops.where_op.as_expr( - kurt, ops.ge_op.as_expr(count, ex.const(4)), ex.const(None) + block, na_cond_id = block.apply_unary_op(count_id, ops.partial_right(ops.ge_op, 4)) + block, kurt_id = block.apply_binary_op( + kurt_id, na_cond_id, ops.partial_arg3(ops.where_op, None) ) - return cleaned_kurt + return block, kurt_id def align( @@ -891,14 +652,14 @@ def align_rows( right_block: blocks.Block, join: str = "outer", ): - joined_block, (get_column_left, get_column_right) = left_block.join( - right_block, how=join + joined_index, (get_column_left, get_column_right) = left_block.index.join( + right_block.index, how=join ) left_columns = [get_column_left[col] for col in left_block.value_columns] right_columns = [get_column_right[col] for col in right_block.value_columns] - left_block = joined_block.select_columns(left_columns) - right_block = joined_block.select_columns(right_columns) + left_block = joined_index._block.select_columns(left_columns) + right_block = joined_index._block.select_columns(right_columns) return left_block, right_block @@ -954,8 +715,7 @@ def idxmax(block: blocks.Block) -> blocks.Block: def _idx_extrema( block: blocks.Block, min_or_max: typing.Literal["min", "max"] ) -> blocks.Block: - block._throw_if_null_index("idx") - if len(block.index_columns) > 1: + if len(block.index_columns) != 1: # TODO: Need support for tuple dtype raise NotImplementedError( f"idxmin not support for multi-index. {constants.FEEDBACK_LINK}" @@ -971,13 +731,13 @@ def _idx_extrema( ) # Have to find the min for each order_refs = [ - ordering.OrderingExpression(ex.deref(value_col), direction), + ordering.OrderingColumnReference(value_col, direction), *[ - ordering.OrderingExpression(ex.deref(idx_col)) + ordering.OrderingColumnReference(idx_col) for idx_col in original_block.index_columns ], ] - window_spec = window_specs.unbound(ordering=tuple(order_refs)) + window_spec = windows.WindowSpec(ordering=tuple(order_refs)) idx_col = original_block.index_columns[0] block, result_col = block.apply_window_op( idx_col, agg_ops.first_op, window_spec @@ -990,5 +750,5 @@ def _idx_extrema( # Stack the entire column axis to produce single-column result # Assumption: uniform dtype for stackability return block.aggregate_all_and_stack( - agg_ops.AnyValueOp(), + agg_ops.AnyValueOp(), dtype=block.dtypes[0] ).with_column_labels([original_block.index.name]) diff --git a/bigframes/core/blocks.py b/bigframes/core/blocks.py index 8522a4d97be..e831b42752f 100644 --- a/bigframes/core/blocks.py +++ b/bigframes/core/blocks.py @@ -21,54 +21,29 @@ from __future__ import annotations -import ast -import dataclasses -import datetime import functools import itertools import random import typing +from typing import Iterable, List, Optional, Sequence, Tuple import warnings -from typing import ( - Iterable, - Iterator, - List, - Literal, - Mapping, - Optional, - Sequence, - Tuple, - Union, -) - -import bigframes_vendored.constants as constants + import google.cloud.bigquery as bigquery -import numpy import pandas as pd -import pyarrow as pa -import bigframes.constants +import bigframes.constants as constants import bigframes.core as core -import bigframes.core.agg_expressions as ex_types -import bigframes.core.expression as ex -import bigframes.core.expression as scalars import bigframes.core.guid as guid -import bigframes.core.identifiers -import bigframes.core.join_def as join_defs +import bigframes.core.indexes as indexes +import bigframes.core.joins.name_resolution as join_names import bigframes.core.ordering as ordering -import bigframes.core.pyarrow_utils as pyarrow_utils +import bigframes.core.utils import bigframes.core.utils as utils -import bigframes.core.window_spec as windows import bigframes.dtypes -import bigframes.exceptions as bfe import bigframes.operations as ops import bigframes.operations.aggregations as agg_ops -from bigframes import session -from bigframes._config import sampling_options -from bigframes.core import agg_expressions, local_data -from bigframes.session import dry_runs, execution_spec -from bigframes.session import executor as executors -from bigframes.session._io import pandas as io_pandas +import bigframes.session._io.pandas +import third_party.bigframes_vendored.pandas.io.common as vendored_pandas_io_common # Type constraint for wherever column labels are used Label = typing.Hashable @@ -91,43 +66,18 @@ _MONOTONIC_DECREASING = "monotonic_decreasing" -LevelType = typing.Hashable +LevelType = typing.Union[str, int] LevelsType = typing.Union[LevelType, typing.Sequence[LevelType]] -class PandasBatches(Iterator[pd.DataFrame]): +class BlockHolder(typing.Protocol): """Interface for mutable objects with state represented by a block value object.""" - def __init__( - self, - pandas_batches: Iterator[pd.DataFrame], - total_rows: Optional[int] = 0, - *, - total_bytes_processed: Optional[int] = 0, - ): - self._dataframes: Iterator[pd.DataFrame] = pandas_batches - self._total_rows: Optional[int] = total_rows - self._total_bytes_processed: Optional[int] = total_bytes_processed - - @property - def total_rows(self) -> Optional[int]: - return self._total_rows - - @property - def total_bytes_processed(self) -> Optional[int]: - return self._total_bytes_processed - - def __next__(self) -> pd.DataFrame: - return next(self._dataframes) + def _set_block(self, block: Block): + """Set the underlying block value of the object""" - -@dataclasses.dataclass() -class MaterializationOptions: - downsampling: sampling_options.SamplingOptions = dataclasses.field( - default_factory=sampling_options.SamplingOptions - ) - allow_large_results: Optional[bool] = None - ordered: bool = True + def _get_block(self) -> Block: + """Get the underlying block value of the object""" class Block: @@ -139,19 +89,19 @@ def __init__( index_columns: Iterable[str], column_labels: typing.Union[pd.Index, typing.Iterable[Label]], index_labels: typing.Union[pd.Index, typing.Iterable[Label], None] = None, - *, - value_columns: Optional[Iterable[str]] = None, - transpose_cache: Optional[Block] = None, ): """Construct a block object, will create default index if no index columns specified.""" index_columns = list(index_columns) - if index_labels is not None: + if index_labels: index_labels = list(index_labels) if len(index_labels) != len(index_columns): raise ValueError( - f"'index_columns' (size {len(index_columns)}) and 'index_labels' (size {len(index_labels)}) must have equal length" + "'index_columns' and 'index_labels' must have equal length" ) - + if len(index_columns) == 0: + new_index_col_id = guid.generate_guid() + expr = expr.promote_offsets(new_index_col_id) + index_columns = [new_index_col_id] self._index_columns = tuple(index_columns) # Index labels don't need complicated hierarchical access so can store as tuple self._index_labels = ( @@ -159,13 +109,7 @@ def __init__( if index_labels else tuple([None for _ in index_columns]) ) - if value_columns is None: - value_columns = [ - col_id for col_id in expr.column_ids if col_id not in index_columns - ] - self._expr = self._normalize_expression( - expr, self._index_columns, value_columns - ) + self._expr = self._normalize_expression(expr, self._index_columns) # Use pandas index to more easily replicate column indexing, especially for hierarchical column index self._column_labels = ( column_labels.copy() @@ -184,111 +128,28 @@ def __init__( # TODO(kemppeterson) Add a cache for corr to parallel the single-column stats. self._stats_cache[" ".join(self.index_columns)] = {} - self._transpose_cache: Optional[Block] = transpose_cache - self._view_ref: Optional[bigquery.TableReference] = None - self._view_ref_dry_run: Optional[bigquery.TableReference] = None - - @classmethod - def from_pyarrow( - cls, - data: pa.Table, - session: bigframes.Session, - ) -> Block: - column_labels = data.column_names - - # TODO(tswast): Use array_value.promote_offsets() instead once that node is - # supported by the local engine. - offsets_col = bigframes.core.guid.generate_guid() - index_ids = [offsets_col] - index_labels = [None] - - # TODO(https://github.com/googleapis/python-bigquery-dataframes/issues/859): - # Allow users to specify the "total ordering" column(s) or allow multiple - # such columns. - data = pyarrow_utils.append_offsets(data, offsets_col=offsets_col) - - # from_pyarrow will normalize the types for us. - managed_data = local_data.ManagedArrowTable.from_pyarrow(data) - array_value = core.ArrayValue.from_managed(managed_data, session=session) - block = cls( - array_value, - column_labels=column_labels, - index_columns=index_ids, - index_labels=index_labels, - ) - return block - - @classmethod - def from_local( - cls, - data: pd.DataFrame, - session: bigframes.Session, - *, - cache_transpose: bool = True, - ) -> Block: - # Assumes caller has already converted datatypes to bigframes ones. - pd_data = data - column_labels = pd_data.columns - index_labels = list(pd_data.index.names) - - # unique internal ids - column_ids = [f"column_{i}" for i in range(len(pd_data.columns))] - index_ids = [f"level_{level}" for level in range(pd_data.index.nlevels)] - - pd_data = pd_data.set_axis(column_ids, axis=1) - pd_data = pd_data.reset_index(names=index_ids) - managed_data = local_data.ManagedArrowTable.from_pandas(pd_data) - array_value = core.ArrayValue.from_managed(managed_data, session=session) - block = cls( - array_value, - column_labels=column_labels, - index_columns=index_ids, - index_labels=index_labels, - ) - if cache_transpose: - try: - # this cache will help when aligning on axis=1 - block = block.with_transpose_cache( - cls.from_local(data.T, session, cache_transpose=False) - ) - except Exception: - pass - return block @property - def has_index(self) -> bool: - return len(self._index_columns) > 0 - - @property - def index(self) -> BlockIndexProperties: + def index(self) -> indexes.IndexValue: """Row identities for values in the Block.""" - return BlockIndexProperties(self) + return indexes.IndexValue(self) @functools.cached_property def shape(self) -> typing.Tuple[int, int]: """Returns dimensions as (length, width) tuple.""" - # Support zero-query for hermetic unit tests. - if self.expr.session is None and self.expr.node.row_count: - try: - return self.expr.node.row_count - except Exception: - pass - - row_count = ( - self.session._executor.execute( - self.expr.row_count(), - execution_spec.ExecutionSpec(promise_under_10gb=True, ordered=False), - ) - .batches() - .to_py_scalar() - ) - return (row_count, len(self.value_columns)) + impl_length, _ = self._expr.shape() + return (impl_length, len(self.value_columns)) @property def index_columns(self) -> Sequence[str]: """Column(s) to use as row labels.""" return self._index_columns + @property + def index_labels(self) -> Sequence[Label]: + """Name of column(s) to use as row labels.""" + return self._index_labels + @property def value_columns(self) -> Sequence[str]: """All value columns, mutually exclusive with index columns.""" @@ -315,8 +176,11 @@ def dtypes( return [self.expr.get_column_type(col) for col in self.value_columns] @property - def session(self) -> session.Session: - return self._expr.session + def index_dtypes( + self, + ) -> Sequence[bigframes.dtypes.Dtype]: + """Returns the dtypes of the index columns.""" + return [self.expr.get_column_type(col) for col in self.index_columns] @functools.cached_property def col_id_to_label(self) -> typing.Mapping[str, Label]: @@ -334,26 +198,6 @@ def label_to_col_id(self) -> typing.Mapping[Label, typing.Sequence[str]]: mapping[label] = (*mapping.get(label, ()), id) return mapping - def resolve_label_exact(self, label: Label) -> Optional[str]: - """Returns the column id matching the label if there is exactly - one such column. If there are multiple columns with the same name, - raises an error. If there is no such a column, returns None.""" - matches = self.label_to_col_id.get(label, []) - if len(matches) > 1: - raise ValueError( - f"Multiple columns matching id {label} were found. {constants.FEEDBACK_LINK}" - ) - return matches[0] if len(matches) != 0 else None - - def resolve_label_exact_or_error(self, label: Label) -> str: - """Returns the column id matching the label if there is exactly - one such column. If there are multiple columns with the same name, - raises an error. If there is no such a column, raises an error too.""" - col_id = self.resolve_label_exact(label) - if col_id is None: - raise ValueError(f"Label {label} not found. {constants.FEEDBACK_LINK}") - return col_id - @functools.cached_property def col_id_to_index_name(self) -> typing.Mapping[str, Label]: """Get column label for value columns, or index name for index columns""" @@ -370,10 +214,6 @@ def index_name_to_col_id(self) -> typing.Mapping[Label, typing.Sequence[str]]: mapping[label] = (*mapping.get(label, ()), id) return mapping - @property - def explicitly_ordered(self) -> bool: - return self.expr.explicitly_ordered - def cols_matching_label(self, partial_label: Label) -> typing.Sequence[str]: """ Unlike label_to_col_id, this works with partial labels for multi-index. @@ -394,8 +234,8 @@ def cols_matching_label(self, partial_label: Label) -> typing.Sequence[str]: def order_by( self, - by: typing.Sequence[ordering.OrderingExpression], - stable: bool = True, + by: typing.Sequence[ordering.OrderingColumnReference], + stable: bool = False, ) -> Block: return Block( self._expr.order_by(by, stable=stable), @@ -412,97 +252,57 @@ def reversed(self) -> Block: index_labels=self.index.names, ) - def reset_index( - self, - level: LevelsType = None, - drop: bool = True, - *, - col_level: Union[str, int] = 0, - col_fill: typing.Hashable = "", - allow_duplicates: bool = False, - replacement: Optional[bigframes.enums.DefaultIndexKind] = None, - ) -> Block: + def reset_index(self, drop: bool = True) -> Block: """Reset the index of the block, promoting the old index to a value column. Arguments: - level: the label or index level of the index levels to remove. name: this is the column id for the new value id derived from the old index - allow_duplicates: if false, duplicate col labels will result in error - replacement: if not null, will override default index replacement type Returns: A new Block because dropping index columns can break references from Index classes that point to this block. """ - if level is not None: - # preserve original order, not user provided order - level_ids: Sequence[str] = [ - id for id in self.index_columns if id in self.index.resolve_level(level) - ] - else: - level_ids = self.index_columns - - expr = self._expr - replacement_idx_type = replacement or self.session._default_index_type - if set(self.index_columns) > set(level_ids): - new_index_cols = [col for col in self.index_columns if col not in level_ids] - new_index_labels = [self.col_id_to_index_name[id] for id in new_index_cols] - elif replacement_idx_type == bigframes.enums.DefaultIndexKind.SEQUENTIAL_INT64: - expr, new_index_col_id = expr.promote_offsets() - new_index_cols = [new_index_col_id] - new_index_labels = [None] - elif replacement_idx_type == bigframes.enums.DefaultIndexKind.NULL: - new_index_cols = [] - new_index_labels = [] - else: - raise ValueError(f"Unrecognized default index kind: {replacement_idx_type}") - + block = self + new_index_col_id = guid.generate_guid() + expr = self._expr.promote_offsets(new_index_col_id) if drop: # Even though the index might be part of the ordering, keep that # ordering expression as reset_index shouldn't change the row # order. - expr = expr.drop_columns(level_ids) - return Block( + expr = expr.drop_columns(self.index_columns) + block = Block( expr, - index_columns=new_index_cols, - index_labels=new_index_labels, + index_columns=[new_index_col_id], column_labels=self.column_labels, + index_labels=[None], ) else: # Add index names to column index - col_level_n = ( - col_level - if isinstance(col_level, int) - else self.column_labels.names.index(col_level) - ) + index_labels = self.index.names column_labels_modified = self.column_labels - for position, level_id in enumerate(level_ids): - label = self.col_id_to_index_name[level_id] + for level, label in enumerate(index_labels): if label is None: - if "index" not in self.column_labels and self.index.nlevels <= 1: + if "index" not in self.column_labels and len(index_labels) <= 1: label = "index" else: - label = f"level_{self.index_columns.index(level_id)}" + label = f"level_{level}" - if (not allow_duplicates) and (label in self.column_labels): + if label in self.column_labels: raise ValueError(f"cannot insert {label}, already exists") - if isinstance(self.column_labels, pd.MultiIndex): nlevels = self.column_labels.nlevels - label = tuple( - label if i == col_level_n else col_fill for i in range(nlevels) - ) - + label = tuple(label if i == 0 else "" for i in range(nlevels)) # Create index copy with label inserted # See: https://pandas.pydata.org/docs/reference/api/pandas.Index.insert.html - column_labels_modified = column_labels_modified.insert(position, label) + column_labels_modified = column_labels_modified.insert(level, label) - return Block( - expr.select_columns((*new_index_cols, *level_ids, *self.value_columns)), - index_columns=new_index_cols, - index_labels=new_index_labels, + block = Block( + expr, + index_columns=[new_index_col_id], column_labels=column_labels_modified, + index_labels=[None], ) + return block def set_index( self, @@ -573,243 +373,99 @@ def reorder_levels(self, ids: typing.Sequence[str]): level_names = [self.col_id_to_index_name[index_id] for index_id in ids] return Block(self.expr, ids, self.column_labels, level_names) - def to_arrow( - self, - *, - ordered: bool = True, - allow_large_results: Optional[bool] = None, - ) -> Tuple[pa.Table, Optional[bigquery.QueryJob]]: - """Run query and download results as a pyarrow Table.""" - under_10gb = ( - (not allow_large_results) - if (allow_large_results is not None) - else not bigframes.options._allow_large_results - ) - execute_result = self.session._executor.execute( - self.expr, - execution_spec.ExecutionSpec( - promise_under_10gb=under_10gb, - ordered=ordered, - ), - ) - pa_table = execute_result.batches().to_arrow_table() - - pa_index_labels = [] - for index_level, index_label in enumerate(self._index_labels): - if isinstance(index_label, str): - pa_index_labels.append(index_label) - else: - pa_index_labels.append(f"__index_level_{index_level}__") - - # pa.Table.from_pandas puts index columns last, so update to match. - pa_table = pa_table.select([*self.value_columns, *self.index_columns]) - pa_table = pa_table.rename_columns(list(self.column_labels) + pa_index_labels) - return pa_table, execute_result.query_job + def _to_dataframe(self, result) -> pd.DataFrame: + """Convert BigQuery data to pandas DataFrame with specific dtypes.""" + dtypes = dict(zip(self.index_columns, self.index_dtypes)) + dtypes.update(zip(self.value_columns, self.dtypes)) + return self._expr.session._rows_to_dataframe(result, dtypes) def to_pandas( self, + value_keys: Optional[Iterable[str]] = None, + max_results: Optional[int] = None, max_download_size: Optional[int] = None, sampling_method: Optional[str] = None, random_state: Optional[int] = None, - *, - ordered: bool = True, - allow_large_results: Optional[bool] = None, - ) -> Tuple[pd.DataFrame, Optional[bigquery.QueryJob]]: - """Run query and download results as a pandas DataFrame. - - Args: - max_download_size (int, default None): - Download size threshold in MB. If max_download_size is exceeded when downloading data - (e.g., to_pandas()), the data will be downsampled if - bigframes.options.sampling.enable_downsampling is True, otherwise, an error will be - raised. If set to a value other than None, this will supersede the global config. - sampling_method (str, default None): - Downsampling algorithms to be chosen from, the choices are: "head": This algorithm - returns a portion of the data from the beginning. It is fast and requires minimal - computations to perform the downsampling; "uniform": This algorithm returns uniform - random samples of the data. If set to a value other than None, this will supersede - the global config. - random_state (int, default None): - The seed for the uniform downsampling algorithm. If provided, the uniform method may - take longer to execute and require more computation. If set to a value other than - None, this will supersede the global config. - ordered (bool, default True): - Determines whether the resulting pandas dataframe will be ordered. - Whether the row ordering is deterministics depends on whether session ordering is strict. - - Returns: - pandas.DataFrame, QueryJob - """ - sampling = self._get_sampling_option( - max_download_size, sampling_method, random_state - ) - - return self._materialize_local( - materialize_options=MaterializationOptions( - downsampling=sampling, - allow_large_results=allow_large_results, - ordered=ordered, + ) -> Tuple[pd.DataFrame, bigquery.QueryJob]: + """Run query and download results as a pandas DataFrame.""" + if max_download_size is None: + max_download_size = bigframes.options.sampling.max_download_size + if sampling_method is None: + sampling_method = ( + bigframes.options.sampling.sampling_method + if bigframes.options.sampling.sampling_method is not None + else _UNIFORM ) - ) + if random_state is None: + random_state = bigframes.options.sampling.random_state - def _get_sampling_option( - self, - max_download_size: Optional[int] = None, - sampling_method: Optional[str] = None, - random_state: Optional[int] = None, - ) -> sampling_options.SamplingOptions: - if (sampling_method is not None) and (sampling_method not in _SAMPLING_METHODS): + sampling_method = sampling_method.lower() + if sampling_method not in _SAMPLING_METHODS: raise NotImplementedError( f"The downsampling method {sampling_method} is not implemented, " f"please choose from {','.join(_SAMPLING_METHODS)}." ) - sampling = bigframes.options.sampling.with_max_download_size(max_download_size) - if sampling_method is None: - return sampling.with_disabled() - - return sampling.with_method(sampling_method).with_random_state( # type: ignore - random_state + df, _, query_job = self._compute_and_count( + value_keys=value_keys, + max_results=max_results, + max_download_size=max_download_size, + sampling_method=sampling_method, + random_state=random_state, ) + return df, query_job - def try_peek( - self, n: int = 20, force: bool = False, allow_large_results=None - ) -> typing.Optional[pd.DataFrame]: - if force or self.expr.supports_fast_peek: - # really, we should just block insane peek values and always assume <10gb - under_10gb = ( - (not allow_large_results) - if (allow_large_results is not None) - else not bigframes.options._allow_large_results - ) - result = self.session._executor.execute( - self.expr, - execution_spec.ExecutionSpec(promise_under_10gb=under_10gb, peek=n), - ) - df = result.batches().to_pandas() - return self._copy_index_to_pandas(df) - else: - return None + def to_pandas_batches(self): + """Download results one message at a time.""" + dtypes = dict(zip(self.index_columns, self.index_dtypes)) + dtypes.update(zip(self.value_columns, self.dtypes)) + results_iterator, _ = self._expr.start_query() + for arrow_table in results_iterator.to_arrow_iterable( + bqstorage_client=self._expr.session.bqstoragereadclient + ): + df = bigframes.session._io.pandas.arrow_to_pandas(arrow_table, dtypes) + self._copy_index_to_pandas(df) + yield df - def to_pandas_batches( - self, - page_size: Optional[int] = None, - max_results: Optional[int] = None, - allow_large_results: Optional[bool] = None, - cell_execution_count: Optional[int] = None, - ) -> PandasBatches: - """Download results one message at a time. + def _copy_index_to_pandas(self, df: pd.DataFrame): + """Set the index on pandas DataFrame to match this block. - page_size and max_results determine the size and number of batches, - see https://cloud.google.com/python/docs/reference/bigquery/latest/google.cloud.bigquery.job.QueryJob#google_cloud_bigquery_job_QueryJob_result + Warning: This method modifies ``df`` inplace. """ - - under_10gb = ( - (not allow_large_results) - if (allow_large_results is not None) - else not bigframes.options._allow_large_results - ) - execution_result = self.session._executor.execute( - self.expr, - execution_spec.ExecutionSpec( - promise_under_10gb=under_10gb, - ordered=True, - cell_execution_count=cell_execution_count, - ), - ) - result_batches = execution_result.batches() - - # To reduce the number of edge cases to consider when working with the - # results of this, always return at least one DataFrame. See: - # b/428918844. - try: - empty_arrow_table = self.expr.schema.to_pyarrow().empty_table() - except pa.ArrowNotImplementedError: - # Bug with some pyarrow versions(https://github.com/apache/arrow/issues/45262), - # empty_table only supports base storage types, not extension types. - empty_arrow_table = self.expr.schema.to_pyarrow( - use_storage_types=True - ).empty_table() - empty_val = io_pandas.arrow_to_pandas(empty_arrow_table, self.expr.schema) - dfs = map( - lambda a: a[0], - itertools.zip_longest( - result_batches.to_pandas_batches(page_size, max_results), - [0], - fillvalue=empty_val, - ), - ) - dfs = iter(map(self._copy_index_to_pandas, dfs)) - - total_rows = result_batches.approx_total_rows - if (total_rows is not None) and (max_results is not None): - total_rows = min(total_rows, max_results) - - return PandasBatches( - dfs, - total_rows, - total_bytes_processed=execution_result.total_bytes_processed, - ) - - def _copy_index_to_pandas(self, df: pd.DataFrame) -> pd.DataFrame: - """Set the index on pandas DataFrame to match this block.""" - # Note: If BigQuery DataFrame has null index, a default one will be created for the local materialization. - new_df = df.copy() - if len(self.index_columns) > 0: - new_df.set_index(list(self.index_columns), inplace=True) + if self.index_columns: + df.set_index(list(self.index_columns), inplace=True) # Pandas names is annotated as list[str] rather than the more # general Sequence[Label] that BigQuery DataFrames has. # See: https://github.com/pandas-dev/pandas-stubs/issues/804 - new_df.index.names = self.index.names # type: ignore - new_df.columns = self.column_labels - return new_df + df.index.names = self.index.names # type: ignore - def _materialize_local( - self, materialize_options: MaterializationOptions = MaterializationOptions() - ) -> tuple[pd.DataFrame, Optional[bigquery.QueryJob]]: + def _compute_and_count( + self, + value_keys: Optional[Iterable[str]] = None, + max_results: Optional[int] = None, + max_download_size: Optional[int] = None, + sampling_method: Optional[str] = None, + random_state: Optional[int] = None, + ) -> Tuple[pd.DataFrame, int, bigquery.QueryJob]: """Run query and download results as a pandas DataFrame. Return the total number of results as well.""" # TODO(swast): Allow for dry run and timeout. - under_10gb = ( - (not materialize_options.allow_large_results) - if (materialize_options.allow_large_results is not None) - else (not bigframes.options._allow_large_results) + expr = self._apply_value_keys_to_expr(value_keys=value_keys) + + results_iterator, query_job = expr.start_query(max_results=max_results) + + table_size = ( + expr.session._get_table_size(query_job.destination) / _BYTES_TO_MEGABYTES ) - execute_result = self.session._executor.execute( - self.expr, - execution_spec.ExecutionSpec( - promise_under_10gb=under_10gb, - ordered=materialize_options.ordered, - ), + fraction = ( + max_download_size / table_size + if (max_download_size is not None) and (table_size != 0) + else 2 ) - result_batches = execute_result.batches() - - sample_config = materialize_options.downsampling - if result_batches.approx_total_bytes is not None: - table_mb = result_batches.approx_total_bytes / _BYTES_TO_MEGABYTES - max_download_size = sample_config.max_download_size - fraction = ( - max_download_size / table_mb - if (max_download_size is not None) and (table_mb != 0) - else 2 - ) - else: - # Since we cannot acquire the table size without a query_job, - # we skip the sampling. - if sample_config.enable_downsampling: - msg = bfe.format_message( - "Sampling is disabled and there is no download size limit when 'allow_large_results' is set to " - "False. To prevent downloading excessive data, it is recommended to use the peek() method, or " - "limit the data with methods like .head() or .sample() before proceeding with downloads." - ) - warnings.warn(msg, category=UserWarning) - fraction = 2 - # TODO: Maybe materialize before downsampling - # Some downsampling methods - if fraction < 1 and (result_batches.approx_total_rows is not None): - if not sample_config.enable_downsampling: + if fraction < 1: + if not bigframes.options.sampling.enable_downsampling: raise RuntimeError( - f"The data size ({table_mb:.2f} MB) exceeds the maximum download limit of " + f"The data size ({table_size:.2f} MB) exceeds the maximum download limit of " f"{max_download_size} MB. You can:\n\t* Enable downsampling in global options:\n" "\t\t`bigframes.options.sampling.enable_downsampling = True`\n" "\t* Update the global `max_download_size` option. Please make sure " @@ -818,48 +474,57 @@ def _materialize_local( " # Setting it to None will download all the data\n" f"{constants.FEEDBACK_LINK}" ) - msg = bfe.format_message( - f"The data size ({table_mb:.2f} MB) exceeds the maximum download limit of" - f"({max_download_size} MB). It will be downsampled to {max_download_size} " - "MB for download.\nPlease refer to the documentation for configuring " - "the downloading limit." - ) - warnings.warn(msg, category=UserWarning) - total_rows = result_batches.approx_total_rows - # Remove downsampling config from subsequent invocations, as otherwise could result in many - # iterations if downsampling undershoots - if sample_config.sampling_method == "head": - # Just truncates the result iterator without a follow-up query - raw_df = result_batches.to_pandas(limit=int(total_rows * fraction)) - elif ( - sample_config.sampling_method == "uniform" - and sample_config.random_state is None - ): - # Pushes sample into result without new query - sampled_batches = execute_result.batches(sample_rate=fraction) - raw_df = sampled_batches.to_pandas() - else: # uniform sample with random state requires a full follow-up query - down_sampled_block = self.split( - fracs=(fraction,), - random_state=sample_config.random_state, - sort=False, + + warnings.warn( + f"The data size ({table_size:.2f} MB) exceeds the maximum download limit of" + f"({max_download_size} MB). It will be downsampled to {max_download_size} MB for download." + "\nPlease refer to the documentation for configuring the downloading limit.", + UserWarning, + ) + if sampling_method == _HEAD: + total_rows = int(results_iterator.total_rows * fraction) + results_iterator.max_results = total_rows + df = self._to_dataframe(results_iterator) + + if self.index_columns: + df.set_index(list(self.index_columns), inplace=True) + df.index.names = self.index.names # type: ignore + elif (sampling_method == _UNIFORM) and (random_state is None): + filtered_expr = self.expr._uniform_sampling(fraction) + block = Block( + filtered_expr, + index_columns=self.index_columns, + column_labels=self.column_labels, + index_labels=self.index.names, + ) + df, total_rows, _ = block._compute_and_count(max_download_size=None) + elif sampling_method == _UNIFORM: + block = self._split( + fracs=(max_download_size / table_size,), + random_state=random_state, + preserve_order=True, )[0] - return down_sampled_block._materialize_local( - MaterializationOptions(ordered=materialize_options.ordered) + df, total_rows, _ = block._compute_and_count(max_download_size=None) + else: + # This part should never be called, just in case. + raise NotImplementedError( + f"The downsampling method {sampling_method} is not implemented, " + f"please choose from {','.join(_SAMPLING_METHODS)}." ) else: - raw_df = result_batches.to_pandas() - df = self._copy_index_to_pandas(raw_df) - df.columns = self.column_labels - return df, execute_result.query_job + total_rows = results_iterator.total_rows + df = self._to_dataframe(results_iterator) + self._copy_index_to_pandas(df) + + return df, total_rows, query_job - def split( + def _split( self, ns: Iterable[int] = (), fracs: Iterable[float] = (), *, random_state: Optional[int] = None, - sort: Optional[bool | Literal["random"]] = "random", + preserve_order: Optional[bool] = False, ) -> List[Block]: """Internal function to support splitting Block to multiple parts along index axis. @@ -890,17 +555,15 @@ def split( # Create an ordering col and convert to string block, ordering_col = block.promote_offsets() block, string_ordering_col = block.apply_unary_op( - ordering_col, ops.AsTypeOp(to_type=bigframes.dtypes.STRING_DTYPE) + ordering_col, ops.AsTypeOp("string[pyarrow]") ) # Apply hash method to sum col and order by it. block, string_sum_col = block.apply_binary_op( - string_ordering_col, random_state_col, ops.strconcat_op + string_ordering_col, random_state_col, ops.concat_op ) block, hash_string_sum_col = block.apply_unary_op(string_sum_col, ops.hash_op) - block = block.order_by( - [ordering.OrderingExpression(ex.deref(hash_string_sum_col))] - ) + block = block.order_by([ordering.OrderingColumnReference(hash_string_sum_col)]) intervals = [] cur = 0 @@ -913,22 +576,9 @@ def split( typing.cast(Block, block.slice(start=lower, stop=upper)) for lower, upper in intervals ] - - if sort is True: + if preserve_order: sliced_blocks = [ - sliced_block.order_by( - [ - ordering.OrderingExpression(ex.deref(idx_col)) - for idx_col in sliced_block.index_columns - ] - ) - for sliced_block in sliced_blocks - ] - elif sort is False: - sliced_blocks = [ - sliced_block.order_by( - [ordering.OrderingExpression(ex.deref(ordering_col))] - ) + sliced_block.order_by([ordering.OrderingColumnReference(ordering_col)]) for sliced_block in sliced_blocks ] @@ -942,32 +592,12 @@ def split( return [sliced_block.drop_columns(drop_cols) for sliced_block in sliced_blocks] def _compute_dry_run( - self, - value_keys: Optional[Iterable[str]] = None, - *, - ordered: bool = True, - max_download_size: Optional[int] = None, - sampling_method: Optional[str] = None, - random_state: Optional[int] = None, - ) -> typing.Tuple[pd.Series, bigquery.QueryJob]: - sampling = self._get_sampling_option( - max_download_size, sampling_method, random_state - ) - if sampling.enable_downsampling: - raise NotImplementedError("Dry run with sampling is not supported") - + self, value_keys: Optional[Iterable[str]] = None + ) -> bigquery.QueryJob: expr = self._apply_value_keys_to_expr(value_keys=value_keys) - query_job = self.session._executor.dry_run(expr, ordered) - - column_dtypes = { - col: self.expr.get_column_type(self.resolve_label_exact_or_error(col)) - for col in self.column_labels - } - - dry_run_stats = dry_runs.get_query_stats_with_dtypes( - query_job, column_dtypes, self.index.dtypes, self.expr.node - ) - return dry_run_stats, query_job + job_config = bigquery.QueryJobConfig(dry_run=True) + _, query_job = expr.start_query(job_config=job_config) + return query_job def _apply_value_keys_to_expr(self, value_keys: Optional[Iterable[str]] = None): expr = self._expr @@ -985,26 +615,11 @@ def with_column_labels( f"The column labels size `{len(label_list)} ` should equal to the value" + f"columns size: {len(self.value_columns)}." ) - block = Block( - self._expr, - index_columns=self.index_columns, - column_labels=label_list, - index_labels=self.index.names, - ) - singleton_label = len(list(value)) == 1 and list(value)[0] - if singleton_label is not None and self._transpose_cache is not None: - new_cache, label_id = self._transpose_cache.create_constant(singleton_label) - new_cache = new_cache.set_index([label_id]) - block = block.with_transpose_cache(new_cache) - return block - - def with_transpose_cache(self, transposed: Block): return Block( self._expr, index_columns=self.index_columns, - column_labels=self._column_labels, + column_labels=label_list, index_labels=self.index.names, - transpose_cache=transposed, ) def with_index_labels(self, value: typing.Sequence[Label]) -> Block: @@ -1020,30 +635,23 @@ def with_index_labels(self, value: typing.Sequence[Label]) -> Block: index_labels=tuple(value), ) - def project_expr( - self, expr: ex.Expression, label: Label = None + def apply_unary_op( + self, column: str, op: ops.UnaryOp, result_label: Label = None ) -> typing.Tuple[Block, str]: """ - Apply a scalar expression to the block. Creates a new column to store the result. + Apply a unary op to the block. Creates a new column to store the result. """ - array_val, result_id = self._expr.project_to_id(expr) + # TODO(tbergeron): handle labels safely so callers don't need to + result_id = guid.generate_guid() + expr = self._expr.project_unary_op(column, op, result_id) block = Block( - array_val, + expr, index_columns=self.index_columns, - column_labels=self.column_labels.insert(len(self.column_labels), label), + column_labels=[*self.column_labels, result_label], index_labels=self.index.names, ) return (block, result_id) - def apply_unary_op( - self, column: str, op: ops.UnaryOp, result_label: Label = None - ) -> typing.Tuple[Block, str]: - """ - Apply a unary op to the block. Creates a new column to store the result. - """ - expr = op.as_expr(column) - return self.project_expr(expr, result_label) - def apply_binary_op( self, left_column_id: str, @@ -1051,8 +659,17 @@ def apply_binary_op( op: ops.BinaryOp, result_label: Label = None, ) -> typing.Tuple[Block, str]: - expr = op.as_expr(left_column_id, right_column_id) - return self.project_expr(expr, result_label) + result_id = guid.generate_guid() + expr = self._expr.project_binary_op( + left_column_id, right_column_id, op, result_id + ) + block = Block( + expr, + index_columns=self.index_columns, + column_labels=[*self.column_labels, result_label], + index_labels=self.index.names, + ) + return (block, result_id) def apply_ternary_op( self, @@ -1062,196 +679,92 @@ def apply_ternary_op( op: ops.TernaryOp, result_label: Label = None, ) -> typing.Tuple[Block, str]: - expr = op.as_expr(col_id_1, col_id_2, col_id_3) - return self.project_expr(expr, result_label) - - def apply_nary_op( - self, - columns: Iterable[str], - op: ops.NaryOp, - result_label: Label = None, - ) -> typing.Tuple[Block, str]: - expr = op.as_expr(*columns) - return self.project_expr(expr, result_label) + result_id = guid.generate_guid() + expr = self._expr.project_ternary_op( + col_id_1, col_id_2, col_id_3, op, result_id + ) + block = Block( + expr, + index_columns=self.index_columns, + column_labels=[*self.column_labels, result_label], + index_labels=self.index.names, + ) + return (block, result_id) def multi_apply_window_op( self, columns: typing.Sequence[str], - op: agg_ops.UnaryWindowOp, - window_spec: windows.WindowSpec, + op: agg_ops.WindowOp, + window_spec: core.WindowSpec, *, skip_null_groups: bool = False, + never_skip_nulls: bool = False, ) -> typing.Tuple[Block, typing.Sequence[str]]: - return self.apply_analytic( - agg_exprs=( - agg_expressions.UnaryAggregation(op, ex.deref(col)) for col in columns - ), - window=window_spec, - result_labels=self._get_labels_for_columns(columns), - skip_null_groups=skip_null_groups, - ) + block = self + result_ids = [] + for i, col_id in enumerate(columns): + label = self.col_id_to_label[col_id] + block, result_id = block.apply_window_op( + col_id, + op, + window_spec=window_spec, + skip_reproject_unsafe=(i + 1) < len(columns), + result_label=label, + skip_null_groups=skip_null_groups, + never_skip_nulls=never_skip_nulls, + ) + result_ids.append(result_id) + return block, result_ids def multi_apply_unary_op( self, - op: Union[ops.UnaryOp, ops.NaryOp, ex.Expression], + columns: typing.Sequence[str], + op: ops.UnaryOp, ) -> Block: - if isinstance(op, (ops.UnaryOp, ops.NaryOp)): - input_varname = guid.generate_guid() - expr = op.as_expr(ex.free_var(input_varname)) - else: - input_varnames = op.free_variables - assert len(set(input_varnames)) == 1 - expr = op - input_varname = input_varnames[0] - block = self - - exprs = [ - expr.bind_variables({input_varname: ex.deref(col_id)}) - for col_id in self.value_columns - ] - block = self.project_exprs(exprs, labels=self.column_labels, drop=True) - - # Special case, we can preserve transpose cache for full-frame unary ops - if self._transpose_cache is not None: - new_transpose_cache = self._transpose_cache.multi_apply_unary_op(op) - block = block.with_transpose_cache(new_transpose_cache) + for i, col_id in enumerate(columns): + label = self.col_id_to_label[col_id] + block, result_id = block.apply_unary_op( + col_id, + op, + result_label=label, + ) + block = block.copy_values(result_id, col_id) + block = block.drop_columns([result_id]) return block - def project_exprs( - self, - exprs: Sequence[ex.Expression], - labels: Union[Sequence[Label], pd.Index], - drop=False, - ) -> Block: - new_array, new_cols = self.expr.compute_values(exprs) - if drop: - new_array = new_array.drop_columns(self.value_columns) - - new_val_cols = new_cols if drop else (*self.value_columns, *new_cols) - return Block( - new_array, - index_columns=self.index_columns, - value_columns=new_val_cols, - column_labels=labels - if drop - else self.column_labels.append(pd.Index(labels)), - index_labels=self._index_labels, - ) - - def project_block_exprs( - self, - exprs: Sequence[ex.Expression], - labels: Union[Sequence[Label], pd.Index], - drop=False, - ) -> Block: - """ - Version of the project_exprs that supports mixing analytic and scalar expressions - """ - new_array, _ = self.expr.compute_general_expression(exprs) - if drop: - new_array = new_array.drop_columns(self.value_columns) - - new_array.node.validate_tree() - return Block( - new_array, - index_columns=self.index_columns, - column_labels=labels - if drop - else self.column_labels.append(pd.Index(labels)), - index_labels=self._index_labels, - ) - - def aggregate( - self, - aggregations: typing.Sequence[ex.Expression] = (), - by_column_ids: typing.Sequence[str] = (), - column_labels: Optional[pd.Index] = None, - *, - dropna: bool = True, - ) -> Block: - """ - Apply aggregations to the block. - - Grouping columns will form the index of the result block. - - Arguments: - aggregations: Aggregation expressions to apply - by_column_id: column id of the aggregation key, this is preserved through the transform and used as index. - dropna: whether null keys should be dropped - - Returns: - Block - """ - if column_labels is None: - column_labels = pd.Index(range(len(aggregations))) - - result_expr = self.expr.compute_general_reduction( - aggregations, by_column_ids, dropna=dropna - ) - - grouping_col_labels: typing.List[Label] = [] - if len(by_column_ids) == 0: - # in the absence of grouping columns, there will be a single row output, assign 0 as its row label. - result_expr, label_id = result_expr.create_constant(0, pd.Int64Dtype()) - index_columns = (label_id,) - grouping_col_labels = [None] - else: - index_columns = tuple(by_column_ids) # type: ignore - for by_col_id in by_column_ids: - if by_col_id in self.value_columns: - grouping_col_labels.append(self.col_id_to_label[by_col_id]) - else: - grouping_col_labels.append(self.col_id_to_index_name[by_col_id]) - - return Block( - result_expr, - index_columns=index_columns, - column_labels=column_labels, - index_labels=grouping_col_labels, - ) - def apply_window_op( self, column: str, - op: agg_ops.UnaryWindowOp, - window_spec: windows.WindowSpec, + op: agg_ops.WindowOp, + window_spec: core.WindowSpec, *, result_label: Label = None, skip_null_groups: bool = False, + skip_reproject_unsafe: bool = False, + never_skip_nulls: bool = False, ) -> typing.Tuple[Block, str]: - agg_expr = agg_expressions.UnaryAggregation(op, ex.deref(column)) - block, ids = self.apply_analytic( - [agg_expr], - window_spec, - [result_label], - skip_null_groups=skip_null_groups, - ) - return block, ids[0] - - def apply_analytic( - self, - agg_exprs: Iterable[agg_expressions.Aggregation], - window: windows.WindowSpec, - result_labels: Iterable[Label], - *, - skip_null_groups: bool = False, - ) -> typing.Tuple[Block, Sequence[str]]: block = self if skip_null_groups: - for key in window.grouping_keys: - block = block.filter(ops.notnull_op.as_expr(key)) - expr, result_ids = block._expr.project_window_expr( - tuple(agg_exprs), - window, + for key in window_spec.grouping_keys: + block, not_null_id = block.apply_unary_op(key, ops.notnull_op) + block = block.filter(not_null_id).drop_columns([not_null_id]) + result_id = guid.generate_guid() + expr = block._expr.project_window_op( + column, + op, + window_spec, + result_id, + skip_reproject_unsafe=skip_reproject_unsafe, + never_skip_nulls=never_skip_nulls, ) block = Block( expr, index_columns=self.index_columns, - column_labels=self.column_labels.append(pd.Index(result_labels)), + column_labels=[*self.column_labels, result_label], index_labels=self._index_labels, ) - return (block, result_ids) + return (block, result_id) def copy_values(self, source_column_id: str, destination_column_id: str) -> Block: expr = self.expr.assign(source_column_id, destination_column_id) @@ -1268,7 +781,8 @@ def create_constant( label: Label = None, dtype: typing.Optional[bigframes.dtypes.Dtype] = None, ) -> typing.Tuple[Block, str]: - expr, result_id = self.expr.create_constant(scalar_constant, dtype=dtype) + result_id = guid.generate_guid() + expr = self.expr.assign_constant(result_id, scalar_constant, dtype=dtype) # Create index copy with label inserted # See: https://pandas.pydata.org/docs/reference/api/pandas.Index.insert.html labels = self.column_labels.insert(len(self.column_labels), label) @@ -1291,17 +805,9 @@ def assign_label(self, column_id: str, new_label: Label) -> Block: ) return self.with_column_labels(new_labels) - def filter_by_id(self, column_id: str, keep_null: bool = False): + def filter(self, column_id: str, keep_null: bool = False): return Block( - self._expr.filter_by_id(column_id, keep_null), - index_columns=self.index_columns, - column_labels=self.column_labels, - index_labels=self.index.names, - ) - - def filter(self, predicate: scalars.Expression): - return Block( - self._expr.filter(predicate), + self._expr.filter(column_id, keep_null), index_columns=self.index_columns, column_labels=self.column_labels, index_labels=self.index.names, @@ -1309,46 +815,61 @@ def filter(self, predicate: scalars.Expression): def aggregate_all_and_stack( self, - operation: typing.Union[agg_ops.UnaryAggregateOp, agg_ops.NullaryAggregateOp], + operation: agg_ops.AggregateOp, *, axis: int | str = 0, + value_col_id: str = "values", dropna: bool = True, + dtype: typing.Union[ + bigframes.dtypes.Dtype, typing.Tuple[bigframes.dtypes.Dtype, ...] + ] = pd.Float64Dtype(), ) -> Block: axis_n = utils.get_axis_number(axis) if axis_n == 0: aggregations = [ - ( - agg_expressions.UnaryAggregation(operation, ex.deref(col_id)) - if isinstance(operation, agg_ops.UnaryAggregateOp) - else agg_expressions.NullaryAggregation(operation), - col_id, - ) - for col_id in self.value_columns + (col_id, operation, col_id) for col_id in self.value_columns ] - result_expr, index_id = self.expr.aggregate( - aggregations, dropna=dropna - ).create_constant(None, None) - # Transpose as last operation so that final block has valid transpose cache - return Block( - result_expr, - index_columns=[index_id], - column_labels=self.column_labels, - index_labels=[None], - ).transpose(original_row_index=pd.Index([None]), single_row_mode=True) + result_expr = self.expr.aggregate(aggregations, dropna=dropna).unpivot( + row_labels=self.column_labels.to_list(), + index_col_ids=["index"], + unpivot_columns=tuple([(value_col_id, tuple(self.value_columns))]), + dtype=dtype, + ) + return Block(result_expr, index_columns=["index"], column_labels=[None]) else: # axis_n == 1 - as_array = ops.ToArrayOp().as_expr(*(col for col in self.value_columns)) - reduced = ops.ArrayReduceOp(operation).as_expr(as_array) - block, id = self.project_expr(reduced, None) - return block.select_column(id).with_column_labels(pd.Index([None])) + # using offsets as identity to group on. + # TODO: Allow to promote identity/total_order columns instead for better perf + offset_col = guid.generate_guid() + expr_with_offsets = self.expr.promote_offsets(offset_col) + stacked_expr = expr_with_offsets.unpivot( + row_labels=self.column_labels.to_list(), + index_col_ids=[guid.generate_guid()], + unpivot_columns=[(value_col_id, tuple(self.value_columns))], + passthrough_columns=[*self.index_columns, offset_col], + dtype=dtype, + ) + index_aggregations = [ + (col_id, agg_ops.AnyValueOp(), col_id) + for col_id in [*self.index_columns] + ] + main_aggregation = (value_col_id, operation, value_col_id) + result_expr = stacked_expr.aggregate( + [*index_aggregations, main_aggregation], + by_column_ids=[offset_col], + dropna=dropna, + ) + return Block( + result_expr.drop_columns([offset_col]), + self.index_columns, + column_labels=[None], + index_labels=self.index_labels, + ) def select_column(self, id: str) -> Block: return self.select_columns([id]) def select_columns(self, ids: typing.Sequence[str]) -> Block: - # Allow renames as may end up selecting same columns multiple times - expr = self._expr.select_columns( - [*self.index_columns, *ids], allow_renames=True - ) + expr = self._expr.select_columns([*self.index_columns, *ids]) col_labels = self._get_labels_for_columns(ids) return Block(expr, self.index_columns, col_labels, self.index.names) @@ -1389,36 +910,81 @@ def remap_f(x): col_labels.append(remap_f(col_label)) return self.with_column_labels(col_labels) - def get_stat( + def aggregate( self, - column_id: str, - stat: typing.Union[agg_ops.UnaryAggregateOp, agg_ops.NullaryAggregateOp], - ): + by_column_ids: typing.Sequence[str] = (), + aggregations: typing.Sequence[typing.Tuple[str, agg_ops.AggregateOp]] = (), + *, + as_index: bool = True, + dropna: bool = True, + ) -> typing.Tuple[Block, typing.Sequence[str]]: + """ + Apply aggregations to the block. Callers responsible for setting index column(s) after. + Arguments: + by_column_id: column id of the aggregation key, this is preserved through the transform and used as index. + aggregations: input_column_id, operation tuples + as_index: if True, grouping keys will be index columns in result, otherwise they will be non-index columns. + dropna: whether null keys should be dropped + """ + agg_specs = [ + (input_id, operation, guid.generate_guid()) + for input_id, operation in aggregations + ] + output_col_ids = [agg_spec[2] for agg_spec in agg_specs] + result_expr = self.expr.aggregate(agg_specs, by_column_ids, dropna=dropna) + + aggregate_labels = self._get_labels_for_columns( + [agg[0] for agg in aggregations] + ) + if as_index: + names: typing.List[Label] = [] + for by_col_id in by_column_ids: + if by_col_id in self.value_columns: + names.append(self.col_id_to_label[by_col_id]) + else: + names.append(self.col_id_to_index_name[by_col_id]) + return ( + Block( + result_expr, + index_columns=by_column_ids, + column_labels=aggregate_labels, + index_labels=names, + ), + output_col_ids, + ) + else: # as_index = False + # If as_index=False, drop grouping levels, but keep grouping value columns + by_value_columns = [ + col for col in by_column_ids if col in self.value_columns + ] + by_column_labels = self._get_labels_for_columns(by_value_columns) + labels = (*by_column_labels, *aggregate_labels) + offsets_id = guid.generate_guid() + result_expr_pruned = result_expr.select_columns( + [*by_value_columns, *output_col_ids] + ).promote_offsets(offsets_id) + + return ( + Block( + result_expr_pruned, index_columns=[offsets_id], column_labels=labels + ), + output_col_ids, + ) + + def get_stat(self, column_id: str, stat: agg_ops.AggregateOp): """Gets aggregates immediately, and caches it""" if stat.name in self._stats_cache[column_id]: return self._stats_cache[column_id][stat.name] # TODO: Convert nonstandard stats into standard stats where possible (popvar, etc.) # if getting a standard stat, just go get the rest of them - standard_stats = typing.cast( - typing.Sequence[ - typing.Union[agg_ops.UnaryAggregateOp, agg_ops.NullaryAggregateOp] - ], - self._standard_stats(column_id), - ) + standard_stats = self._standard_stats(column_id) stats_to_fetch = standard_stats if stat in standard_stats else [stat] - aggregations = [ - ( - agg_expressions.UnaryAggregation(stat, ex.deref(column_id)) - if isinstance(stat, agg_ops.UnaryAggregateOp) - else agg_expressions.NullaryAggregation(stat), - stat.name, - ) - for stat in stats_to_fetch - ] + aggregations = [(column_id, stat, stat.name) for stat in stats_to_fetch] expr = self.expr.aggregate(aggregations) - expr, offset_index_id = expr.promote_offsets() + offset_index_id = guid.generate_guid() + expr = expr.promote_offsets(offset_index_id) block = Block( expr, index_columns=[offset_index_id], @@ -1431,95 +997,53 @@ def get_stat( self._stats_cache[column_id].update(stats_map) return stats_map[stat.name] - def get_binary_stat( - self, column_id_left: str, column_id_right: str, stat: agg_ops.BinaryAggregateOp - ): + def get_corr_stat(self, column_id_left: str, column_id_right: str): # TODO(kemppeterson): Clean up the column names for DataFrames.corr support # TODO(kemppeterson): Add a cache here. - aggregations = [ + corr_aggregations = [ ( - agg_expressions.BinaryAggregation( - stat, ex.deref(column_id_left), ex.deref(column_id_right) - ), - f"{stat.name}_{column_id_left}{column_id_right}", + column_id_left, + column_id_right, + "corr_" + column_id_left + column_id_right, ) ] - expr = self.expr.aggregate(aggregations) - expr, offset_index_id = expr.promote_offsets() + expr = self.expr.corr_aggregate(corr_aggregations) + offset_index_id = guid.generate_guid() + expr = expr.promote_offsets(offset_index_id) block = Block( expr, index_columns=[offset_index_id], - column_labels=[a[1] for a in aggregations], + column_labels=[a[2] for a in corr_aggregations], ) df, _ = block.to_pandas() - return df.loc[0, f"{stat.name}_{column_id_left}{column_id_right}"] + return df.loc[0, "corr_" + column_id_left + column_id_right] def summarize( self, column_ids: typing.Sequence[str], - stats: typing.Sequence[ - typing.Union[agg_ops.UnaryAggregateOp, agg_ops.NullaryAggregateOp] - ], + stats: typing.Sequence[agg_ops.AggregateOp], ): """Get a list of stats as a deferred block object.""" - labels = pd.Index([stat.name for stat in stats]) + label_col_id = guid.generate_guid() + labels = [stat.name for stat in stats] aggregations = [ - ( - agg_expressions.UnaryAggregation(stat, ex.deref(col_id)) - if isinstance(stat, agg_ops.UnaryAggregateOp) - else agg_expressions.NullaryAggregation(stat), - f"{col_id}-{stat.name}", - ) + (col_id, stat, f"{col_id}-{stat.name}") for stat in stats for col_id in column_ids ] columns = [ - (tuple(f"{col_id}-{stat.name}" for stat in stats)) for col_id in column_ids + (col_id, tuple(f"{col_id}-{stat.name}" for stat in stats)) + for col_id in column_ids ] - expr, (index_cols, _, _) = unpivot( - self.expr.aggregate(aggregations), + expr = self.expr.aggregate(aggregations).unpivot( labels, unpivot_columns=tuple(columns), + index_col_ids=tuple([label_col_id]), ) - return Block( - expr, - column_labels=self._get_labels_for_columns(column_ids), - index_columns=index_cols, - ) - - def explode( - self, - column_ids: typing.Sequence[str], - ignore_index: Optional[bool], - ) -> Block: - column_ids = [ - column_id - for column_id in column_ids - if bigframes.dtypes.is_array_like(self.expr.get_column_type(column_id)) - ] - if len(column_ids) == 0: - expr = self.expr - else: - expr = self.expr.explode(column_ids) - - if ignore_index: - expr = expr.drop_columns(self.index_columns) - expr, new_index_ids = expr.promote_offsets() - return Block( - expr, - column_labels=self.column_labels, - # Initiates default index creation using the block constructor. - index_columns=[new_index_ids], - ) - else: - return Block( - expr, - column_labels=self.column_labels, - index_columns=self.index_columns, - index_labels=self._index_labels, - ) + labels = self._get_labels_for_columns(column_ids) + return Block(expr, column_labels=labels, index_columns=[label_col_id]) - def _standard_stats(self, column_id) -> typing.Sequence[agg_ops.UnaryAggregateOp]: + def _standard_stats(self, column_id) -> typing.Sequence[agg_ops.AggregateOp]: """ Gets a standard set of stats to preemptively fetch for a column if any other stat is fetched. @@ -1530,10 +1054,10 @@ def _standard_stats(self, column_id) -> typing.Sequence[agg_ops.UnaryAggregateOp """ # TODO: annotate aggregations themself with this information dtype = self.expr.get_column_type(column_id) - stats: list[agg_ops.UnaryAggregateOp] = [agg_ops.count_op] - if bigframes.dtypes.is_orderable(dtype): + stats: list[agg_ops.AggregateOp] = [agg_ops.count_op] + if dtype not in bigframes.dtypes.UNORDERED_DTYPES: stats += [agg_ops.min_op, agg_ops.max_op] - if dtype in bigframes.dtypes.NUMERIC_BIGFRAMES_TYPES_PERMISSIVE: + if dtype in bigframes.dtypes.NUMERIC_BIGFRAMES_TYPES: # Notable exclusions: # prod op tends to cause overflows # Also, var_op is redundant as can be derived from std @@ -1546,57 +1070,114 @@ def _standard_stats(self, column_id) -> typing.Sequence[agg_ops.UnaryAggregateOp return stats - def _get_labels_for_columns(self, column_ids: typing.Sequence[str]) -> pd.Index: + def _get_labels_for_columns(self, column_ids: typing.Sequence[str]): """Get column label for value columns, or index name for index columns""" - indices = [self.value_columns.index(col_id) for col_id in column_ids] - return self.column_labels.take(indices, allow_fill=False) + lookup = self.col_id_to_label + return [lookup.get(col_id, None) for col_id in column_ids] def _normalize_expression( self, expr: core.ArrayValue, - index_columns: Iterable[str], - value_columns: Iterable[str], + index_columns: typing.Sequence[str], + assert_value_size: typing.Optional[int] = None, ): """Normalizes expression by moving index columns to left.""" - normalized_ids = (*index_columns, *value_columns) - if tuple(expr.column_ids) == normalized_ids: - return expr + value_columns = [ + col_id for col_id in expr.column_ids if col_id not in index_columns + ] + if (assert_value_size is not None) and ( + len(value_columns) != assert_value_size + ): + raise ValueError("Unexpected number of value columns.") return expr.select_columns([*index_columns, *value_columns]) - def grouped_head( - self, - by_column_ids: typing.Sequence[str], - value_columns: typing.Sequence[str], - n: int, - ): - window_spec = windows.cumulative_rows(grouping_keys=tuple(by_column_ids)) - - block, result_id = self.apply_window_op( - value_columns[0], - agg_ops.count_op, - window_spec=window_spec, - ) - - cond = ops.lt_op.as_expr(result_id, ex.const(n + 1)) - block, cond_id = block.project_expr(cond) - block = block.filter_by_id(cond_id) - if value_columns: - return block.select_columns(value_columns) - def slice( self, start: typing.Optional[int] = None, stop: typing.Optional[int] = None, - step: int = 1, - ) -> Block: + step: typing.Optional[int] = None, + ) -> bigframes.core.blocks.Block: + if step is None: + step = 1 if step == 0: - raise ValueError("Slice step size must be non-zero") - return Block( - self.expr.slice(start, stop, step), - index_columns=self.index_columns, - column_labels=self.column_labels, - index_labels=self._index_labels, + raise ValueError("slice step cannot be zero") + if step < 0: + reverse_start = (-start - 1) if start else 0 + reverse_stop = (-stop - 1) if stop else None + reverse_step = -step + return self.reversed()._forward_slice( + reverse_start, reverse_stop, reverse_step + ) + return self._forward_slice(start or 0, stop, step) + + def _forward_slice(self, start: int = 0, stop=None, step: int = 1): + """Performs slice but only for positive step size.""" + if step <= 0: + raise ValueError("forward_slice only supports positive step size") + + use_postive_offsets = ( + (start > 0) + or ((stop is not None) and (stop >= 0)) + or ((step > 1) and (start >= 0)) ) + use_negative_offsets = ( + (start < 0) or (stop and (stop < 0)) or ((step > 1) and (start < 0)) + ) + + block = self + + # only generate offsets that are used + positive_offsets = None + negative_offsets = None + + if use_postive_offsets: + block, positive_offsets = self.promote_offsets() + if use_negative_offsets: + block, negative_offsets = block.reversed().promote_offsets() + block = block.reversed() + + conditions = [] + if start != 0: + if start > 0: + op = ops.partial_right(ops.ge_op, start) + assert positive_offsets + block, start_cond = block.apply_unary_op(positive_offsets, op) + else: + op = ops.partial_right(ops.le_op, -start - 1) + assert negative_offsets + block, start_cond = block.apply_unary_op(negative_offsets, op) + conditions.append(start_cond) + if stop is not None: + if stop >= 0: + op = ops.partial_right(ops.lt_op, stop) + assert positive_offsets + block, stop_cond = block.apply_unary_op(positive_offsets, op) + else: + op = ops.partial_right(ops.gt_op, -stop - 1) + assert negative_offsets + block, stop_cond = block.apply_unary_op(negative_offsets, op) + conditions.append(stop_cond) + + if step > 1: + op = ops.partial_right(ops.mod_op, step) + if start >= 0: + op = ops.partial_right(ops.sub_op, start) + assert positive_offsets + block, start_diff = block.apply_unary_op(positive_offsets, op) + else: + op = ops.partial_right(ops.sub_op, -start + 1) + assert negative_offsets + block, start_diff = block.apply_unary_op(negative_offsets, op) + modulo_op = ops.partial_right(ops.mod_op, step) + block, mod = block.apply_unary_op(start_diff, modulo_op) + is_zero_op = ops.partial_right(ops.eq_op, 0) + block, step_cond = block.apply_unary_op(mod, is_zero_op) + conditions.append(step_cond) + + for cond in conditions: + block = block.filter(cond) + + return block.select_columns(self.value_columns) # Using cache to optimize for Jupyter Notebook's behavior where both '__repr__' # and '__repr_html__' are called in a single display action, reducing redundant @@ -1604,74 +1185,53 @@ def slice( @functools.cache def retrieve_repr_request_results( self, max_results: int - ) -> Tuple[pd.DataFrame, int, Optional[bigquery.QueryJob]]: + ) -> Tuple[pd.DataFrame, int, bigquery.QueryJob]: """ Retrieves a pandas dataframe containing only max_results many rows for use with printing methods. Returns a tuple of the dataframe and the overall number of rows of the query. """ - - # head caches full underlying expression, so row_count will be free after - executor = self.session._executor - executor.cached( - array_value=self.expr, - config=executors.CacheConfig(optimize_for="head", if_cached="reuse-strict"), - ) - head_result = self.session._executor.execute( - self.expr.slice(start=None, stop=max_results, step=None), - execution_spec.ExecutionSpec( - promise_under_10gb=True, - ordered=True, - ), - ) - row_count = ( - self.session._executor.execute( - self.expr.row_count(), - execution_spec.ExecutionSpec( - promise_under_10gb=True, - ordered=False, - ), - ) - .batches() - .to_py_scalar() - ) - - head_df = head_result.batches().to_pandas() - return self._copy_index_to_pandas(head_df), row_count, head_result.query_job + # TODO(swast): Select a subset of columns if max_columns is less than the + # number of columns in the schema. + count = self.shape[0] + if count > max_results: + head_block = self.slice(0, max_results) + computed_df, query_job = head_block.to_pandas(max_results=max_results) + else: + head_block = self + computed_df, query_job = head_block.to_pandas() + formatted_df = computed_df.set_axis(self.column_labels, axis=1) + # we reset the axis and substitute the bf index name for the default + formatted_df.index.name = self.index.name + return formatted_df, count, query_job def promote_offsets(self, label: Label = None) -> typing.Tuple[Block, str]: - expr, result_id = self._expr.promote_offsets() + result_id = guid.generate_guid() + expr = self._expr.promote_offsets(result_id) return ( Block( expr, index_columns=self.index_columns, - column_labels=self.column_labels.insert(len(self.column_labels), label), + column_labels=[label, *self.column_labels], index_labels=self._index_labels, ), result_id, ) def add_prefix(self, prefix: str, axis: str | int | None = None) -> Block: - axis_number = utils.get_axis_number("rows" if (axis is None) else axis) + axis_number = bigframes.core.utils.get_axis_number( + "rows" if (axis is None) else axis + ) if axis_number == 0: expr = self._expr - new_index_cols = [] for index_col in self._index_columns: - expr, new_col = expr.project_to_id( - expression=ops.add_op.as_expr( - ex.const(prefix), - ops.AsTypeOp(to_type=bigframes.dtypes.STRING_DTYPE).as_expr( - index_col - ), - ), - ) - new_index_cols.append(new_col) - expr = expr.select_columns((*new_index_cols, *self.value_columns)) - + expr = expr.project_unary_op(index_col, ops.AsTypeOp("string")) + prefix_op = ops.BinopPartialLeft(ops.add_op, prefix) + expr = expr.project_unary_op(index_col, prefix_op) return Block( expr, - index_columns=new_index_cols, + index_columns=self.index_columns, column_labels=self.column_labels, index_labels=self.index.names, ) @@ -1679,24 +1239,18 @@ def add_prefix(self, prefix: str, axis: str | int | None = None) -> Block: return self.rename(columns=lambda label: f"{prefix}{label}") def add_suffix(self, suffix: str, axis: str | int | None = None) -> Block: - axis_number = utils.get_axis_number("rows" if (axis is None) else axis) + axis_number = bigframes.core.utils.get_axis_number( + "rows" if (axis is None) else axis + ) if axis_number == 0: expr = self._expr - new_index_cols = [] for index_col in self._index_columns: - expr, new_col = expr.project_to_id( - expression=ops.add_op.as_expr( - ops.AsTypeOp(to_type=bigframes.dtypes.STRING_DTYPE).as_expr( - index_col - ), - ex.const(suffix), - ), - ) - new_index_cols.append(new_col) - expr = expr.select_columns((*new_index_cols, *self.value_columns)) + expr = expr.project_unary_op(index_col, ops.AsTypeOp("string")) + prefix_op = ops.BinopPartialRight(ops.add_op, suffix) + expr = expr.project_unary_op(index_col, prefix_op) return Block( expr, - index_columns=new_index_cols, + index_columns=self.index_columns, column_labels=self.column_labels, index_labels=self.index.names, ) @@ -1740,26 +1294,23 @@ def pivot( column_ids.append(masked_id) block = block.select_columns(column_ids) - aggregations = [ - agg_expressions.UnaryAggregation(agg_ops.AnyValueOp(), ex.deref(col_id)) - for col_id in column_ids - ] - result_block = block.aggregate( + aggregations = [(col_id, agg_ops.AnyValueOp()) for col_id in column_ids] + result_block, _ = block.aggregate( by_column_ids=self.index_columns, aggregations=aggregations, + as_index=True, dropna=True, ) if values_in_index or len(values) > 1: value_labels = self._get_labels_for_columns(values) column_index = self._create_pivot_column_index(value_labels, columns_values) - return result_block.with_column_labels(column_index) else: - return result_block.with_column_labels(columns_values) + column_index = columns_values - def stack( - self, how="left", levels: int = 1, *, override_labels: Optional[pd.Index] = None - ): + return result_block.with_column_labels(column_index) + + def stack(self, how="left", levels: int = 1): """Unpivot last column axis level into row axis""" if levels == 0: return self @@ -1767,41 +1318,42 @@ def stack( # These are the values that will be turned into rows col_labels, row_labels = utils.split_index(self.column_labels, levels=levels) - row_labels = ( - row_labels.drop_duplicates() if override_labels is None else override_labels - ) + row_labels = row_labels.drop_duplicates() - if col_labels is None: - result_index: pd.Index = pd.Index([None]) - result_col_labels: Sequence[Tuple] = list([()]) - elif (col_labels.nlevels == 1) and all( - col_labels.isna() - ): # isna not implemented for MultiIndex for newer pandas versions - result_index = pd.Index([None]) - result_col_labels = utils.index_as_tuples(col_labels.drop_duplicates()) - else: + row_label_tuples = utils.index_as_tuples(row_labels) + + if col_labels is not None: result_index = col_labels.drop_duplicates().dropna(how="all") result_col_labels = utils.index_as_tuples(result_index) + else: + result_index = pd.Index([None]) + result_col_labels = list([()]) # Get matching columns - unpivot_columns: List[Tuple[Optional[str], ...]] = [] + unpivot_columns: List[Tuple[str, List[str]]] = [] + dtypes = [] for val in result_col_labels: - input_columns, _ = self._create_stack_column(val, row_labels) - unpivot_columns.append(input_columns) - - unpivot_expr, (added_index_columns, _, passthrough_cols) = unpivot( - self._expr, - row_labels=row_labels, + col_id = guid.generate_guid("unpivot_") + input_columns, dtype = self._create_stack_column(val, row_label_tuples) + unpivot_columns.append((col_id, input_columns)) + if dtype: + dtypes.append(dtype or pd.Float64Dtype()) + + added_index_columns = [guid.generate_guid() for _ in range(row_labels.nlevels)] + unpivot_expr = self._expr.unpivot( + row_labels=row_label_tuples, passthrough_columns=self.index_columns, unpivot_columns=unpivot_columns, - join_side=how, + index_col_ids=added_index_columns, + dtype=tuple(dtypes), + how=how, ) new_index_level_names = self.column_labels.names[-levels:] if how == "left": - index_columns = [*passthrough_cols, *added_index_columns] + index_columns = [*self.index_columns, *added_index_columns] index_labels = [*self._index_labels, *new_index_level_names] else: - index_columns = [*added_index_columns, *passthrough_cols] + index_columns = [*added_index_columns, *self.index_columns] index_labels = [*new_index_level_names, *self._index_labels] return Block( @@ -1817,319 +1369,44 @@ def melt( value_vars=typing.Sequence[str], var_names=typing.Sequence[typing.Hashable], value_name: typing.Hashable = "value", - *, - create_offsets_index: bool = True, ): - """ - Unpivot columns to produce longer, narrower dataframe. - Arguments correspond to pandas.melt arguments. - """ # TODO: Implement col_level and ignore_index - value_labels: pd.Index = self.column_labels[ - [self.value_columns.index(col_id) for col_id in value_vars] - ] + unpivot_col_id = guid.generate_guid() + var_col_ids = tuple([guid.generate_guid() for _ in var_names]) + # single unpivot col + unpivot_col = (unpivot_col_id, tuple(value_vars)) + value_labels = [self.col_id_to_label[col_id] for col_id in value_vars] id_labels = [self.col_id_to_label[col_id] for col_id in id_vars] - unpivot_expr, (var_col_ids, unpivot_out, passthrough_cols) = unpivot( - self._expr, + dtype = self._expr.get_column_type(value_vars[0]) + + unpivot_expr = self._expr.unpivot( row_labels=value_labels, passthrough_columns=id_vars, - unpivot_columns=(tuple(value_vars),), # single unpivot col - join_side="right", + unpivot_columns=(unpivot_col,), + index_col_ids=var_col_ids, + dtype=dtype, + how="right", ) - - if create_offsets_index: - unpivot_expr, index_id = unpivot_expr.promote_offsets() - index_cols = [index_id] - else: - index_cols = [] - + index_id = guid.generate_guid() + unpivot_expr = unpivot_expr.promote_offsets(index_id) # Need to reorder to get id_vars before var_col and unpivot_col unpivot_expr = unpivot_expr.select_columns( - [*index_cols, *passthrough_cols, *var_col_ids, *unpivot_out] + [index_id, *id_vars, *var_col_ids, unpivot_col_id] ) return Block( unpivot_expr, column_labels=[*id_labels, *var_names, value_name], - index_columns=index_cols, - ) - - def transpose( - self, - *, - original_row_index: Optional[pd.Index] = None, - single_row_mode: bool = False, - ) -> Block: - """Transpose the block. Will fail if dtypes aren't coercible to a common type or too many rows. - Can provide the original_row_index directly if it is already known, otherwise a query is needed. - """ - if self._transpose_cache is not None: - return self._transpose_cache.with_transpose_cache(self) - - original_col_index = self.column_labels - original_row_index = ( - original_row_index - if original_row_index is not None - else self.index.to_pandas(ordered=True)[0] - ) - original_row_count = len(original_row_index) - if original_row_count > bigframes.constants.MAX_COLUMNS: - raise NotImplementedError( - f"Object has {original_row_count} rows and is too large to transpose." - ) - - # Add row numbers to both axes to disambiguate, clean them up later - block = self - numbered_block = block.with_column_labels( - utils.combine_indices( - block.column_labels, pd.Index(range(len(block.column_labels))) - ) - ) - # TODO: Determine if single row from expression tree (after aggregation without groupby) - if single_row_mode: - numbered_block, offsets = numbered_block.create_constant(0) - else: - numbered_block, offsets = numbered_block.promote_offsets() - - stacked_block = numbered_block.melt( - id_vars=(offsets,), - var_names=( - *[name for name in original_col_index.names], - "col_offset", - ), - value_vars=block.value_columns, - create_offsets_index=False, - ) - row_offset = stacked_block.value_columns[0] - col_labels = stacked_block.value_columns[-2 - original_col_index.nlevels : -2] - col_offset = stacked_block.value_columns[-2] # disambiguator we created earlier - cell_values = stacked_block.value_columns[-1] - # Groupby source column - stacked_block = stacked_block.set_index( - [*col_labels, col_offset] - ) # col index is now row index - result = stacked_block.pivot( - columns=[row_offset], - values=[cell_values], - columns_unique_values=tuple(range(original_row_count)), - ) - # Drop the offsets from both axes before returning - return ( - result.with_column_labels(original_row_index) - .order_by([ordering.ascending_over(result.index_columns[-1])]) - .drop_levels([result.index_columns[-1]]) - .with_transpose_cache(self) + index_columns=[index_id], ) - def _generate_sequence( - self, - start, - stop, - step: int = 1, + def _create_stack_column( + self, col_label: typing.Tuple, stack_labels: typing.Sequence[typing.Tuple] ): - range_expr = self.expr.from_range( - start, - stop, - step, - ) - - return Block( - range_expr, - column_labels=["min"], - index_columns=[], - ) - - def _generate_resample_label( - self, - rule: str, - closed: Optional[Literal["right", "left"]] = None, - label: Optional[Literal["right", "left"]] = None, - on: Optional[Label] = None, - level: typing.Union[LevelType, typing.Sequence[LevelType]] = None, - origin: Union[ - Union[pd.Timestamp, datetime.datetime, numpy.datetime64, int, float, str], - Literal["epoch", "start", "start_day", "end", "end_day"], - ] = "start_day", - ) -> Block: - if not isinstance(rule, str): - raise NotImplementedError( - f"Only offset strings are currently supported for rule, but got {repr(rule)}. {constants.FEEDBACK_LINK}" - ) - - if rule in ("ME", "YE", "QE", "BME", "BA", "BQE", "W"): - raise NotImplementedError( - f"Offset strings 'ME', 'YE', 'QE', 'BME', 'BA', 'BQE', 'W' are not currently supported for rule, but got {repr(rule)}. {constants.FEEDBACK_LINK}" - ) - - if closed == "right": - raise NotImplementedError( - f"Only closed='left' is currently supported. {constants.FEEDBACK_LINK}", - ) - - if label == "right": - raise NotImplementedError( - f"Only label='left' is currently supported. {constants.FEEDBACK_LINK}", - ) - - if origin not in ("epoch", "start", "start_day"): - raise NotImplementedError( - f"Only origin='epoch', 'start', 'start_day' are currently supported, but got {repr(origin)}. {constants.FEEDBACK_LINK}" - ) - - # Validate and resolve the index or column to use for grouping - if on is None: - if len(self.index_columns) == 0: - raise ValueError( - f"No index for resampling. Expected {bigframes.dtypes.DATETIME_DTYPE} or " - f"{bigframes.dtypes.TIMESTAMP_DTYPE} index or 'on' parameter specifying a column." - ) - if len(self.index_columns) > 1 and (level is None): - raise ValueError( - "Multiple indices are not supported for this operation" - " when 'level' is not set." - ) - level = level or 0 - col_id = self.index.resolve_level(level)[0] - if isinstance(level, int): - resample_label = self.index.names[level] - else: - resample_label = level - # Reset index to make the resampling level a column, then drop all other index columns. - # This simplifies processing by focusing solely on the column required for resampling. - block = self.reset_index(drop=False) - block = block.drop_columns( - [col for col in self.index.column_ids if col != col_id] - ) - elif level is not None: - raise ValueError("The Grouper cannot specify both a key and a level!") - else: - matches = self.label_to_col_id.get(on, []) - if len(matches) > 1: - raise ValueError( - f"Multiple columns matching id {on} were found. {constants.FEEDBACK_LINK}" - ) - if len(matches) == 0: - raise KeyError(f"The grouper name {on} is not found") - - col_id = matches[0] - resample_label = on - block = self - if level is None: - dtype = self._column_type(col_id) - elif isinstance(level, int): - dtype = self.index.dtypes[level] - else: - dtype = self.index.dtypes[self.index.names.index(level)] - - if dtype not in ( - bigframes.dtypes.DATETIME_DTYPE, - bigframes.dtypes.TIMESTAMP_DTYPE, - ): - raise TypeError( - f"Invalid column type: {dtype}. Expected types are " - f"{bigframes.dtypes.DATETIME_DTYPE}, or " - f"{bigframes.dtypes.TIMESTAMP_DTYPE}." - ) - - freq = pd.tseries.frequencies.to_offset(rule) - assert freq is not None - - if origin not in ("epoch", "start", "start_day"): - raise ValueError( - "'origin' should be equal to 'epoch', 'start' or 'start_day'" - f". Got '{origin}' instead." - ) - - agg_specs = [ - ( - agg_expressions.UnaryAggregation(agg_ops.min_op, ex.deref(col_id)), - guid.generate_guid(), - ), - ] - origin_block = Block( - block.expr.aggregate(agg_specs, dropna=True), - column_labels=["origin"], - index_columns=[], - ) - - col_level = block.value_columns.index(col_id) - - block = block.merge( - origin_block, how="cross", left_join_ids=[], right_join_ids=[], sort=True - ) - - # After merging, the original column ids are altered. 'col_level' is the index of - # the datetime column used for resampling. 'block.value_columns[-1]' is the - # 'origin' column, which is the minimum datetime value. - block, label_col_id = block.apply_binary_op( - block.value_columns[col_level], - block.value_columns[-1], - op=ops.DatetimeToIntegerLabelOp(freq=freq, closed=closed, origin=origin), - ) - block = block.drop_columns([block.value_columns[-2]]) - - # Generate integer label sequence. - min_agg_specs = [ - ( - ex_types.UnaryAggregation(agg_ops.min_op, ex.deref(label_col_id)), - guid.generate_guid(), - ), - ] - max_agg_specs = [ - ( - ex_types.UnaryAggregation(agg_ops.max_op, ex.deref(label_col_id)), - guid.generate_guid(), - ), - ] - label_start = block.expr.aggregate(min_agg_specs, dropna=True) - label_stop = block.expr.aggregate(max_agg_specs, dropna=True) - - label_block = block._generate_sequence( - start=label_start, - stop=label_stop, - ) - - label_block = label_block.merge( - origin_block, how="cross", left_join_ids=[], right_join_ids=[], sort=True - ) - - block = label_block.merge( - block, - how="left", - left_join_ids=[label_block.value_columns[0]], - right_join_ids=[label_col_id], - sort=True, - ) - - block, resample_label_id = block.apply_binary_op( - block.value_columns[0], - block.value_columns[1], - op=ops.IntegerLabelToDatetimeOp(freq=freq, label=label, origin=origin), - result_label=resample_label, - ) - - # After multiple merges, the columns: - # - block.value_columns[0] is the integer label sequence, - # - block.value_columns[1] is the origin column (minimum datetime value), - # - col_level+2 represents the datetime column used for resampling, - # - block.value_columns[-2] is the integer label column derived from the datetime column. - # These columns are no longer needed. - block = block.drop_columns( - [ - block.value_columns[0], - block.value_columns[1], - block.value_columns[col_level + 2], - block.value_columns[-2], - ] - ) - - return block.set_index([resample_label_id]) - - def _create_stack_column(self, col_label: typing.Tuple, stack_labels: pd.Index): - input_dtypes = [] + dtype = None input_columns: list[Optional[str]] = [] - for uvalue in utils.index_as_tuples(stack_labels): + for uvalue in stack_labels: label_to_match = (*col_label, *uvalue) label_to_match = ( label_to_match[0] if len(label_to_match) == 1 else label_to_match @@ -2137,18 +1414,15 @@ def _create_stack_column(self, col_label: typing.Tuple, stack_labels: pd.Index): matching_ids = self.label_to_col_id.get(label_to_match, []) input_id = matching_ids[0] if len(matching_ids) > 0 else None if input_id: - input_dtypes.append(self._column_type(input_id)) + if dtype and dtype != self._column_type(input_id): + raise NotImplementedError( + "Cannot stack columns with non-matching dtypes." + ) + else: + dtype = self._column_type(input_id) input_columns.append(input_id) # Input column i is the first one that - if len(input_dtypes) > 0: - output_dtype = bigframes.dtypes.lcd_type(*input_dtypes) - if output_dtype is None: - raise NotImplementedError( - "Cannot stack columns with non-matching dtypes." - ) - else: - output_dtype = pd.Float64Dtype() - return tuple(input_columns), output_dtype + return tuple(input_columns), dtype or pd.Float64Dtype() def _column_type(self, col_id: str) -> bigframes.dtypes.Dtype: col_offset = self.value_columns.index(col_id) @@ -2157,8 +1431,8 @@ def _column_type(self, col_id: str) -> bigframes.dtypes.Dtype: @staticmethod def _create_pivot_column_index( - value_labels: pd.Index, columns_values: pd.Index - ) -> pd.Index: + value_labels: Sequence[typing.Hashable], columns_values: pd.Index + ): index_parts = [] for value in value_labels: as_frame = columns_values.to_frame() @@ -2173,24 +1447,29 @@ def _create_pivot_column_index( def _create_pivot_col( block: Block, columns: typing.Sequence[str], value_col: str, value ) -> typing.Tuple[Block, str]: - condition: typing.Optional[ex.Expression] = None + cond_id = "" nlevels = len(columns) for i in range(len(columns)): uvalue_level = value[i] if nlevels > 1 else value if pd.isna(uvalue_level): - equality = ops.isnull_op.as_expr(columns[i]) + block, eq_id = block.apply_unary_op( + columns[i], + ops.isnull_op, + ) else: - equality = ops.eq_op.as_expr(columns[i], ex.const(uvalue_level)) - if condition is not None: - condition = ops.and_op.as_expr(equality, condition) + block, eq_id = block.apply_unary_op( + columns[i], ops.partial_right(ops.eq_op, uvalue_level) + ) + if cond_id: + block, cond_id = block.apply_binary_op(eq_id, cond_id, ops.and_op) else: - condition = equality - - assert condition is not None - return block.project_expr( - ops.where_op.as_expr(value_col, condition, ex.const(None)) + cond_id = eq_id + block, masked_id = block.apply_binary_op( + value_col, cond_id, ops.partial_arg3(ops.where_op, None) ) + return block, masked_id + def _get_unique_values( self, columns: Sequence[str], max_unique_values: int ) -> pd.Index: @@ -2199,17 +1478,9 @@ def _get_unique_values( import bigframes.core.block_transforms as block_tf import bigframes.dataframe as df - if self.explicitly_ordered: - unique_value_block = block_tf.drop_duplicates( - self.select_columns(columns), columns - ) - else: - unique_value_block = self.aggregate(by_column_ids=columns, dropna=False) - col_labels = self._get_labels_for_columns(columns) - unique_value_block = unique_value_block.reset_index( - drop=False - ).with_column_labels(col_labels) - + unique_value_block = block_tf.drop_duplicates( + self.select_columns(columns), columns + ) pd_values = ( df.DataFrame(unique_value_block).head(max_unique_values + 1).to_pandas() ) @@ -2230,10 +1501,8 @@ def concat( blocks: typing.List[Block] = [self, *other] if ignore_index: blocks = [block.reset_index() for block in blocks] - level_names = None - else: - level_names, level_types = _align_indices(blocks) - blocks = [_cast_index(block, level_types) for block in blocks] + + result_labels = _align_indices(blocks) index_nlevels = blocks[0].index.nlevels @@ -2248,38 +1517,12 @@ def concat( result_expr, index_columns=list(result_expr.column_ids)[:index_nlevels], column_labels=aligned_blocks[0].column_labels, - index_labels=level_names, + index_labels=result_labels, ) if ignore_index: result_block = result_block.reset_index() return result_block - def isin(self, other: Block): - # TODO: Support multiple other columns and match on label - assert len(other.value_columns) == 1 - unique_other_values = other.expr.select_columns( - [other.value_columns[0]] - ).aggregate((), by_column_ids=(other.value_columns[0],), dropna=False) - block = self - # for each original column, join with other - for i in range(len(self.value_columns)): - block = block._isin_inner(block.value_columns[i], unique_other_values) - return block - - def _isin_inner(self: Block, col: str, unique_values: core.ArrayValue) -> Block: - expr, matches = self._expr.isin(unique_values, col) - - new_value_cols = tuple( - val_col if val_col != col else matches for val_col in self.value_columns - ) - expr = expr.select_columns((*self.index_columns, *new_value_cols)) - return Block( - expr, - index_columns=self.index_columns, - column_labels=self.column_labels, - index_labels=self._index_labels, - ) - def merge( self, other: Block, @@ -2288,67 +1531,54 @@ def merge( "left", "outer", "right", - "cross", ], left_join_ids: typing.Sequence[str], right_join_ids: typing.Sequence[str], sort: bool, suffixes: tuple[str, str] = ("_x", "_y"), - left_index: bool = False, - right_index: bool = False, ) -> Block: - conditions = tuple( - (lid, rid) for lid, rid in zip(left_join_ids, right_join_ids) + joined_expr = self.expr.join( + left_join_ids, + other.expr, + right_join_ids, + how=how, ) - joined_expr, (get_column_left, get_column_right) = self.expr.relational_join( - other.expr, type=how, conditions=conditions + get_column_left, get_column_right = join_names.JOIN_NAME_REMAPPER( + self.expr.column_ids, other.expr.column_ids ) + result_columns = [] + matching_join_labels = [] - left_post_join_ids = tuple(get_column_left[id] for id in left_join_ids) - right_post_join_ids = tuple(get_column_right[id] for id in right_join_ids) - - if left_index or right_index: - # For some reason pandas coalesces two joining columns if one side is an index. - joined_expr, resolved_join_ids = coalesce_columns( - joined_expr, left_post_join_ids, right_post_join_ids - ) - else: - joined_expr, resolved_join_ids = resolve_col_join_ids( # type: ignore - joined_expr, - left_post_join_ids, - right_post_join_ids, - how=how, - drop=False, + coalesced_ids = [] + for left_id, right_id in zip(left_join_ids, right_join_ids): + coalesced_id = guid.generate_guid() + joined_expr = joined_expr.project_binary_op( + get_column_left[left_id], + get_column_right[right_id], + ops.coalesce_op, + coalesced_id, ) + coalesced_ids.append(coalesced_id) - result_columns = [] - matching_join_labels = [] - - # Select left value columns for col_id in self.value_columns: if col_id in left_join_ids: key_part = left_join_ids.index(col_id) matching_right_id = right_join_ids[key_part] if ( - right_index - or self.col_id_to_label[col_id] + self.col_id_to_label[col_id] == other.col_id_to_label[matching_right_id] ): matching_join_labels.append(self.col_id_to_label[col_id]) - result_columns.append(resolved_join_ids[key_part]) + result_columns.append(coalesced_ids[key_part]) else: result_columns.append(get_column_left[col_id]) else: result_columns.append(get_column_left[col_id]) - - # Select right value columns for col_id in other.value_columns: if col_id in right_join_ids: - if other.col_id_to_label[col_id] in matching_join_labels: + key_part = right_join_ids.index(col_id) + if other.col_id_to_label[matching_right_id] in matching_join_labels: pass - elif left_index: - key_part = right_join_ids.index(col_id) - result_columns.append(resolved_join_ids[key_part]) else: result_columns.append(get_column_right[col_id]) else: @@ -2357,271 +1587,31 @@ def merge( if sort: # sort uses coalesced join keys always joined_expr = joined_expr.order_by( - [ - ordering.OrderingExpression(ex.deref(col_id)) - for col_id in resolved_join_ids - ], + [ordering.OrderingColumnReference(col_id) for col_id in coalesced_ids], + stable=True, ) - left_idx_id_post_join = [get_column_left[id] for id in self.index_columns] - right_idx_id_post_join = [get_column_right[id] for id in other.index_columns] - index_cols = _resolve_index_col( - left_idx_id_post_join, - right_idx_id_post_join, - resolved_join_ids, - left_index, - right_index, - how, - ) - - joined_expr = joined_expr.select_columns(result_columns + index_cols) + joined_expr = joined_expr.select_columns(result_columns) labels = utils.merge_column_labels( self.column_labels, other.column_labels, coalesce_labels=matching_join_labels, suffixes=suffixes, ) + # Constructs default index + offset_index_id = guid.generate_guid() + expr = joined_expr.promote_offsets(offset_index_id) + return Block(expr, index_columns=[offset_index_id], column_labels=labels) - # Construct a default index only if this object and the other both have - # indexes. In other words, joining anything to a NULL index object - # keeps everything as a NULL index. - # - # This keeps us from generating an index if the user joins a large - # BigQuery table against small local data, for example. - if ( - self.index.is_null - or other.index.is_null - or self.session._default_index_type == bigframes.enums.DefaultIndexKind.NULL - ): - return Block(joined_expr, index_columns=[], column_labels=labels) - elif index_cols: - return Block(joined_expr, index_columns=index_cols, column_labels=labels) - else: - expr, offset_index_id = joined_expr.promote_offsets() - index_columns = [offset_index_id] - return Block(expr, index_columns=index_columns, column_labels=labels) - - def _align_both_axes( - self, other: Block, how: str - ) -> Tuple[Block, pd.Index, Sequence[Tuple[ex.RefOrConstant, ex.RefOrConstant]]]: - # Join rows - aligned_block, (get_column_left, get_column_right) = self.join(other, how=how) - # join columns schema - # indexers will be none for exact match - if self.column_labels.equals(other.column_labels): - columns, lcol_indexer, rcol_indexer = self.column_labels, None, None - else: - columns, lcol_indexer, rcol_indexer = self.column_labels.join( - other.column_labels, how=how, return_indexers=True - ) - lcol_indexer = ( - lcol_indexer if (lcol_indexer is not None) else range(len(columns)) - ) - rcol_indexer = ( - rcol_indexer if (rcol_indexer is not None) else range(len(columns)) - ) - - left_input_lookup = lambda index: ( - ex.deref(get_column_left[self.value_columns[index]]) - if index != -1 - else ex.const(None) - ) - righ_input_lookup = lambda index: ( - ex.deref(get_column_right[other.value_columns[index]]) - if index != -1 - else ex.const(None) - ) - - left_inputs = [left_input_lookup(i) for i in lcol_indexer] - right_inputs = [righ_input_lookup(i) for i in rcol_indexer] - return aligned_block, columns, tuple(zip(left_inputs, right_inputs)) # type: ignore - - def _align_axis_0( - self, other: Block, how: str - ) -> Tuple[Block, pd.Index, Sequence[Tuple[ex.DerefOp, ex.DerefOp]]]: - assert len(other.value_columns) == 1 - aligned_block, (get_column_left, get_column_right) = self.join(other, how=how) - - series_column_id = other.value_columns[0] - inputs = tuple( - ( - ex.deref(get_column_left[col]), - ex.deref(get_column_right[series_column_id]), - ) - for col in self.value_columns - ) - return aligned_block, self.column_labels, inputs - - def _align_series_block_axis_1( - self, other: Block, how: str - ) -> Tuple[Block, pd.Index, Sequence[Tuple[ex.RefOrConstant, ex.RefOrConstant]]]: - assert len(other.value_columns) == 1 - if other._transpose_cache is None: - raise ValueError( - "Wrong align method, this approach requires transpose cache" - ) - - # Join rows - aligned_block, (get_column_left, get_column_right) = join_with_single_row( - self, other.transpose() - ) - # join columns schema - # indexers will be none for exact match - if self.column_labels.equals(other.transpose().column_labels): - columns, lcol_indexer, rcol_indexer = self.column_labels, None, None - else: - columns, lcol_indexer, rcol_indexer = self.column_labels.join( - other.transpose().column_labels, how=how, return_indexers=True - ) - lcol_indexer = ( - lcol_indexer if (lcol_indexer is not None) else range(len(columns)) - ) - rcol_indexer = ( - rcol_indexer if (rcol_indexer is not None) else range(len(columns)) - ) - - left_input_lookup = lambda index: ( - ex.deref(get_column_left[self.value_columns[index]]) - if index != -1 - else ex.const(None) - ) - righ_input_lookup = lambda index: ( - ex.deref(get_column_right[other.transpose().value_columns[index]]) - if index != -1 - else ex.const(None) - ) - - left_inputs = [left_input_lookup(i) for i in lcol_indexer] - right_inputs = [righ_input_lookup(i) for i in rcol_indexer] - return aligned_block, columns, tuple(zip(left_inputs, right_inputs)) # type: ignore - - def _align_pd_series_axis_1( - self, other: pd.Series, how: str - ) -> Tuple[Block, pd.Index, Sequence[Tuple[ex.RefOrConstant, ex.RefOrConstant]]]: - if self.column_labels.equals(other.index): - columns, lcol_indexer, rcol_indexer = self.column_labels, None, None - else: - if not (self.column_labels.is_unique and other.index.is_unique): - raise ValueError("Cannot align non-unique indices") - columns, lcol_indexer, rcol_indexer = self.column_labels.join( - other.index, how=how, return_indexers=True - ) - lcol_indexer = ( - lcol_indexer if (lcol_indexer is not None) else range(len(columns)) - ) - rcol_indexer = ( - rcol_indexer if (rcol_indexer is not None) else range(len(columns)) - ) - - left_input_lookup = lambda index: ( - ex.deref(self.value_columns[index]) if index != -1 else ex.const(None) - ) - righ_input_lookup = lambda index: ( - ex.const(other.iloc[index]) if index != -1 else ex.const(None) + def _force_reproject(self) -> Block: + """Forces a reprojection of the underlying tables expression. Used to force predicate/order application before subsequent operations.""" + return Block( + self._expr._reproject_to_table(), + index_columns=self.index_columns, + column_labels=self.column_labels, + index_labels=self.index.names, ) - left_inputs = [left_input_lookup(i) for i in lcol_indexer] - right_inputs = [righ_input_lookup(i) for i in rcol_indexer] - return self, columns, tuple(zip(left_inputs, right_inputs)) # type: ignore - - def _apply_binop( - self, - op: ops.BinaryOp, - inputs: Sequence[Tuple[ex.Expression, ex.Expression]], - labels: pd.Index, - reverse: bool = False, - ) -> Block: - exprs = [] - for left_input, right_input in inputs: - exprs.append( - op.as_expr(right_input, left_input) - if reverse - else op.as_expr(left_input, right_input) - ) - - return self.project_exprs(exprs, labels=labels, drop=True) - - # TODO: Re-implement join in terms of merge (requires also adding remaining merge args) - def join( - self, - other: Block, - *, - how="left", - sort: bool = False, - block_identity_join: bool = False, - always_order: bool = False, - ) -> Tuple[ - Block, - Tuple[Mapping[str, str], Mapping[str, str]], - ]: - """ - Join two blocks objects together, and provide mappings between source columns and output columns. - - Args: - other (Block): - The right operand of the join operation - how (str): - Describes the join type. 'inner', 'outer', 'left', or 'right' - sort (bool): - if true will sort result by index - block_identity_join (bool): - If true, will not convert join to a projection (implicitly assuming unique indices) - always_order (bool): - If true, will always preserve input ordering, even if ordering mode is partial - - Returns: - Block, (left_mapping, right_mapping): Result block and mappers from input column ids to result column ids. - """ - - if not isinstance(other, Block): - # TODO(swast): We need to improve this error message to be more - # actionable for the user. For example, it's possible they - # could call set_index and try again to resolve this error. - raise ValueError( - f"Tried to join with an unexpected type: {type(other)}. {constants.FEEDBACK_LINK}" - ) - - # TODO(swast): Support cross-joins (requires reindexing). - if how not in {"outer", "left", "right", "inner"}: - raise NotImplementedError( - f"Only how='outer','left','right','inner' currently supported. {constants.FEEDBACK_LINK}" - ) - # Handle null index, which only supports row join - # This is the canonical way of aligning on null index, so always allow (ignore block_identity_join) - if self.index.nlevels == other.index.nlevels == 0: - result = try_legacy_row_join(self, other, how=how) or try_new_row_join( - self, other - ) - if result is not None: - return result - raise bigframes.exceptions.NullIndexError( - "Cannot implicitly align objects. Set an explicit index using set_index." - ) - - # Oddly, pandas row-wise join ignores right index names - if ( - not block_identity_join - and (self.index.nlevels == other.index.nlevels) - and (self.index.dtypes == other.index.dtypes) - ): - result = try_legacy_row_join(self, other, how=how) or try_new_row_join( - self, other - ) - if result is not None: - return result - - self._throw_if_null_index("join") - other._throw_if_null_index("join") - if self.index.nlevels == other.index.nlevels == 1: - return join_mono_indexed( - self, other, how=how, sort=sort, propogate_order=always_order - ) - else: # Handles cases where one or both sides are multi-indexed - # Always sort mult-index join - return join_multi_indexed( - self, other, how=how, sort=sort, propogate_order=always_order - ) - def is_monotonic_increasing( self, column_id: typing.Union[str, Sequence[str]] ) -> bool: @@ -2632,24 +1622,24 @@ def is_monotonic_decreasing( ) -> bool: return self._is_monotonic(column_id, increasing=False) - def _array_value_for_output( - self, *, include_index: bool - ) -> Tuple[bigframes.core.ArrayValue, list[str], list[Label]]: + def to_sql_query( + self, include_index: bool + ) -> typing.Tuple[str, list[str], list[Label]]: """ - Creates the expression tree with user-visible column names, such as for - SQL output. + Compiles this DataFrame's expression tree to SQL, optionally + including index columns. Args: include_index (bool): whether to include index columns. Returns: - a tuple of (ArrayValue, index_column_id_list, index_column_label_list). + a tuple of (sql_string, index_column_id_list, index_column_label_list). If include_index is set to False, index_column_id_list and index_column_label_list return empty lists. """ array_value = self._expr - col_labels, idx_labels = list(self.column_labels), list(self.index.names) + col_labels, idx_labels = list(self.column_labels), list(self.index_labels) old_col_ids, old_idx_ids = list(self.value_columns), list(self.index_columns) if not include_index: @@ -2667,88 +1657,39 @@ def _array_value_for_output( # the BigQuery unicode column name feature? substitutions[old_id] = new_id + sql = array_value.to_sql(col_id_overrides=substitutions) return ( - array_value.rename_columns(substitutions), + sql, new_ids[: len(idx_labels)], idx_labels, ) - def to_sql_query( - self, include_index: bool, enable_cache: bool = True - ) -> Tuple[str, list[str], list[Label]]: - """ - Compiles this DataFrame's expression tree to SQL, optionally - including index columns. - - Args: - include_index (bool): - whether to include index columns. - - Returns: - a tuple of (sql_string, index_column_id_list, index_column_label_list). - If include_index is set to False, index_column_id_list and index_column_label_list - return empty lists. - """ - array_value, idx_ids, idx_labels = self._array_value_for_output( - include_index=include_index - ) - - # Note: this uses the sql from the executor, so is coupled tightly to execution - # implementaton. It will reference cached tables instead of original data sources. - # Maybe should just compile raw BFET? Depends on user intent. - sql = self.session._executor.to_sql(array_value, enable_cache=enable_cache) - return ( - sql, - idx_ids, - idx_labels, + def cached(self) -> Block: + """Write the block to a session table and create a new block object that references it.""" + return Block( + self.expr.cached(cluster_cols=self.index_columns), + index_columns=self.index_columns, + column_labels=self.column_labels, + index_labels=self.index_labels, ) - def to_placeholder_table( - self, include_index: bool, *, dry_run: bool = False - ) -> bigquery.TableReference: - """ - Creates a temporary BigQuery VIEW (or empty table if dry_run) with the - SQL corresponding to this block. - """ - if self._view_ref is not None: - return self._view_ref - - # Prefer the real view if it exists, but since dry_run might be called - # many times before the real query, we cache that empty table reference - # with the correct schema too. - if dry_run: - if self._view_ref_dry_run is not None: - return self._view_ref_dry_run - - # Create empty temp table with the right schema. - array_value, _, _ = self._array_value_for_output( - include_index=include_index - ) - temp_table_schema = array_value.schema.to_bigquery() - self._view_ref_dry_run = self.session._create_temp_table( - schema=temp_table_schema - ) - return self._view_ref_dry_run - - # We shouldn't run `to_sql_query` if we have a `dry_run`, because it - # could cause us to make unnecessary API calls to upload local node - # data. - sql, _, _ = self.to_sql_query(include_index=include_index) - self._view_ref = self.session._create_temp_view(sql) - return self._view_ref - - def cached(self, *, force: bool = False, session_aware: bool = False) -> None: - """Write the block to a session table.""" - # use a heuristic for whether something needs to be cached - self.session._executor.cached( - self.expr, - config=executors.CacheConfig( - optimize_for="auto" - if session_aware - else executors.HierarchicalKey(tuple(self.index_columns)), - if_cached="replace" if force else "reuse-any", - ), - ) + def resolve_index_level(self, level: LevelsType) -> typing.Sequence[str]: + if utils.is_list_like(level): + levels = list(level) + else: + levels = [level] + resolved_level_ids = [] + for level_ref in levels: + if isinstance(level_ref, int): + resolved_level_ids.append(self.index_columns[level_ref]) + elif isinstance(level_ref, typing.Hashable): + matching_ids = self.index_name_to_col_id.get(level_ref, []) + if len(matching_ids) != 1: + raise ValueError("level name cannot be found or is ambiguous") + resolved_level_ids.append(matching_ids[0]) + else: + raise ValueError(f"Unexpected level: {level_ref}") + return resolved_level_ids def _is_monotonic( self, column_ids: typing.Union[str, Sequence[str]], increasing: bool @@ -2763,564 +1704,90 @@ def _is_monotonic( return self._stats_cache[column_name][op_name] period = 1 - window_spec = windows.rows() + window = bigframes.core.WindowSpec( + preceding=period, + following=None, + ) # any NaN value means not monotonic block, last_notna_id = self.apply_unary_op(column_ids[0], ops.notnull_op) for column_id in column_ids[1:]: block, notna_id = block.apply_unary_op(column_id, ops.notnull_op) - old_last_notna_id = last_notna_id block, last_notna_id = block.apply_binary_op( - old_last_notna_id, notna_id, ops.and_op + last_notna_id, notna_id, ops.and_op ) - block.drop_columns([notna_id, old_last_notna_id]) # loop over all columns to check monotonicity last_result_id = None for column_id in column_ids[::-1]: block, lag_result_id = block.apply_window_op( - column_id, agg_ops.ShiftOp(period), window_spec + column_id, agg_ops.ShiftOp(period), window ) block, strict_monotonic_id = block.apply_binary_op( column_id, lag_result_id, ops.gt_op if increasing else ops.lt_op ) block, equal_id = block.apply_binary_op(column_id, lag_result_id, ops.eq_op) - block = block.drop_columns([lag_result_id]) if last_result_id is None: block, last_result_id = block.apply_binary_op( equal_id, strict_monotonic_id, ops.or_op ) - block = block.drop_columns([equal_id, strict_monotonic_id]) - else: - block, equal_monotonic_id = block.apply_binary_op( - equal_id, last_result_id, ops.and_op - ) - block = block.drop_columns([equal_id, last_result_id]) - block, last_result_id = block.apply_binary_op( - equal_monotonic_id, strict_monotonic_id, ops.or_op - ) - block = block.drop_columns([equal_monotonic_id, strict_monotonic_id]) + continue + block, equal_monotonic_id = block.apply_binary_op( + equal_id, last_result_id, ops.and_op + ) + block, last_result_id = block.apply_binary_op( + equal_monotonic_id, strict_monotonic_id, ops.or_op + ) - assert last_result_id is not None block, monotonic_result_id = block.apply_binary_op( - last_result_id, - last_notna_id, - ops.and_op, # type: ignore + last_result_id, last_notna_id, ops.and_op # type: ignore ) - if last_result_id is not None: - block = block.drop_columns([last_result_id, last_notna_id]) result = block.get_stat(monotonic_result_id, agg_ops.all_op) self._stats_cache[column_name].update({op_name: result}) return result - def _throw_if_null_index(self, opname: str): - if len(self.index_columns) == 0: - raise bigframes.exceptions.NullIndexError( - f"Cannot do {opname} without an index. Set an index using set_index." - ) - - def _get_rows_as_json_values(self) -> Block: - # Names of the columns to serialize for the row. - # We will use the repr-eval pattern to serialize a value here and - # deserialize in the cloud function. Let's make sure that would work. - column_names = [] - for col in list(self.index_columns) + [col for col in self.column_labels]: - serialized_column_name = repr(col) - try: - ast.literal_eval(serialized_column_name) - except Exception: - raise NameError( - f"Column name type '{type(col).__name__}' is not supported for row serialization." - " Please consider using a name for which literal_eval(repr(name)) works." - ) - - column_names.append(serialized_column_name) - - # column references to form the array of values for the row - column_types = list(self.index.dtypes) + list(self.dtypes) - column_references = [] - for type_, col in zip(column_types, self.expr.column_ids): - if type_ == bigframes.dtypes.BYTES_DTYPE: - column_references.append(ops.ToJSONString().as_expr(col)) - elif type_ == bigframes.dtypes.BOOL_DTYPE: - # cast operator produces True/False, but function template expects lower case - column_references.append( - ops.lower_op.as_expr( - ops.AsTypeOp(bigframes.dtypes.STRING_DTYPE).as_expr(col) - ) - ) - else: - column_references.append( - ops.AsTypeOp(bigframes.dtypes.STRING_DTYPE).as_expr(col) - ) - - # row dtype to use for deserializing the row as pandas series - pandas_row_dtype = bigframes.dtypes.lcd_type(*column_types) - if pandas_row_dtype is None: - pandas_row_dtype = "object" - pandas_row_dtype = str(pandas_row_dtype) - - struct_op = ops.StructOp( - column_names=("names", "types", "values", "indexlength", "dtype") - ) - names_val = ex.const(tuple(column_names)) - types_val = ex.const(tuple(map(str, column_types))) - values_val = ops.ToArrayOp().as_expr(*column_references) - indexlength_val = ex.const(len(self.index_columns)) - dtype_val = ex.const(str(pandas_row_dtype)) - struct_expr = struct_op.as_expr( - names_val, types_val, values_val, indexlength_val, dtype_val - ) - block, col_id = self.project_expr(ops.ToJSONString().as_expr(struct_expr)) - return block.select_column(col_id) - - -class BlockIndexProperties: - """Accessor for the index-related block properties.""" - - def __init__(self, block: Block): - self._block = block - - @property - def _expr(self) -> core.ArrayValue: - return self._block.expr - - @property - def name(self) -> Label: - return self._block._index_labels[0] - - @property - def names(self) -> typing.Sequence[Label]: - return self._block._index_labels - - @property - def nlevels(self) -> int: - return len(self._block._index_columns) - - @property - def dtypes( - self, - ) -> typing.Sequence[bigframes.dtypes.Dtype]: - return [ - self._block.expr.get_column_type(col) for col in self._block.index_columns - ] - - @property - def session(self) -> session.Session: - return self._expr.session - - @property - def column_ids(self) -> Sequence[str]: - """Column(s) to use as row labels.""" - return self._block._index_columns - - @property - def is_null(self) -> bool: - return len(self._block._index_columns) == 0 - - def to_pandas( - self, - *, - ordered: Optional[bool] = None, - allow_large_results: Optional[bool] = None, - ) -> Tuple[pd.Index, Optional[bigquery.QueryJob]]: - """Executes deferred operations and downloads the results.""" - if len(self.column_ids) == 0: - raise bigframes.exceptions.NullIndexError( - "Cannot materialize index, as this object does not have an index. Set index column(s) using set_index." - ) - ordered = ordered if ordered is not None else True - - df, query_job = self._block.select_columns([]).to_pandas( - ordered=ordered, - allow_large_results=allow_large_results, - ) - return df.index, query_job - - def _compute_dry_run( - self, *, ordered: bool = True - ) -> Tuple[pd.Series, bigquery.QueryJob]: - return self._block.select_columns([])._compute_dry_run(ordered=ordered) - - def resolve_level(self, level: LevelsType) -> typing.Sequence[str]: - if utils.is_list_like(level): - levels = list(level) - else: - levels = [level] - resolved_level_ids = [] - for level_ref in levels: - if isinstance(level_ref, int): - resolved_level_ids.append(self._block.index_columns[level_ref]) - elif isinstance(level_ref, typing.Hashable): - matching_ids = self._block.index_name_to_col_id.get(level_ref, []) - if len(matching_ids) != 1: - raise ValueError("level name cannot be found or is ambiguous") - resolved_level_ids.append(matching_ids[0]) - else: - raise ValueError(f"Unexpected level: {level_ref}") - return resolved_level_ids - - def resolve_level_exact(self: BlockIndexProperties, label: Label) -> str: - matches = self._block.index_name_to_col_id.get(label, []) - if len(matches) > 1: - raise ValueError(f"Ambiguous index level name {label}") - if len(matches) == 0: - raise ValueError(f"Cannot resolve index level name {label}") - return matches[0] - - def is_uniquely_named(self: BlockIndexProperties): - return len(set(self.names)) == len(self.names) - - -def try_new_row_join( - left: Block, right: Block -) -> Optional[ - Tuple[ - Block, - Tuple[Mapping[str, str], Mapping[str, str]], - ] -]: - join_keys = tuple( - (left_id, right_id) - for left_id, right_id in zip(left.index_columns, right.index_columns) - ) - join_result = left.expr.try_row_join(right.expr, join_keys) - if join_result is None: # did not succeed - return None - combined_expr, (get_column_left, get_column_right) = join_result - # Keep the left index column, and drop the matching right column - index_cols_post_join = [get_column_left[id] for id in left.index_columns] - combined_expr = combined_expr.drop_columns( - [get_column_right[id] for id in right.index_columns] - ) - block = Block( - combined_expr, - index_columns=index_cols_post_join, - column_labels=left.column_labels.append(right.column_labels), - index_labels=left.index.names, - ) - return ( - block, - (get_column_left, get_column_right), - ) - -def try_legacy_row_join( - left: Block, - right: Block, - *, - how="left", -) -> Optional[ - Tuple[ - Block, - Tuple[Mapping[str, str], Mapping[str, str]], - ] -]: - """Joins two blocks that have a common root expression by merging the projections.""" - left_expr = left.expr - right_expr = right.expr - # Create a new array value, mapping from both, then left, and then right - join_keys = tuple( - join_defs.CoalescedColumnMapping( - left_source_id=left_id, - right_source_id=right_id, - destination_id=guid.generate_guid(), - ) - for left_id, right_id in zip(left.index_columns, right.index_columns) - ) - left_mappings = [ - join_defs.JoinColumnMapping( - source_table=join_defs.JoinSide.LEFT, - source_id=id, - destination_id=guid.generate_guid(), - ) - for id in left.value_columns - ] - right_mappings = [ - join_defs.JoinColumnMapping( - source_table=join_defs.JoinSide.RIGHT, - source_id=id, - destination_id=guid.generate_guid(), - ) - for id in right.value_columns - ] - combined_expr = left_expr.try_legacy_row_join( - right_expr, - join_type=how, - join_keys=join_keys, - mappings=(*left_mappings, *right_mappings), - ) - if combined_expr is None: - return None - get_column_left = {m.source_id: m.destination_id for m in left_mappings} - get_column_right = {m.source_id: m.destination_id for m in right_mappings} - block = Block( - combined_expr, - column_labels=[*left.column_labels, *right.column_labels], - index_columns=(key.destination_id for key in join_keys), - index_labels=left.index.names, - ) - return ( - block, - (get_column_left, get_column_right), - ) +def block_from_local(data) -> Block: + pd_data = pd.DataFrame(data) + columns = pd_data.columns + # Make a flattened version to treat as a table. + if len(pd_data.columns.names) > 1: + pd_data.columns = columns.to_flat_index() -def join_with_single_row( - left: Block, - single_row_block: Block, -) -> Tuple[ - Block, - Tuple[Mapping[str, str], Mapping[str, str]], -]: - """ - Special join case where other is a single row block. - This property is not validated, caller responsible for not passing multi-row block. - Preserves index of the left block, ignoring label of other. - """ - left_expr = left.expr - # ignore index columns by dropping them - right_expr = single_row_block.expr.select_columns(single_row_block.value_columns) - combined_expr, (get_column_left, get_column_right) = left_expr.relational_join( - right_expr, - type="cross", - ) - # Drop original indices from each side. and used the coalesced combination generated by the join. - index_cols_post_join = [get_column_left[id] for id in left.index_columns] - - block = Block( - combined_expr, - index_columns=index_cols_post_join, - column_labels=left.column_labels.append(single_row_block.column_labels), - index_labels=left.index.names, - ) - return ( - block, - (get_column_left, get_column_right), + index_labels = list(pd_data.index.names) + # The ArrayValue layer doesn't know about indexes, so make sure indexes + # are real columns with unique IDs. + pd_data = pd_data.reset_index( + names=[f"level_{level}" for level in range(len(index_labels))] ) - - -def join_mono_indexed( - left: Block, - right: Block, - *, - how="left", - sort: bool = False, - propogate_order: bool = False, -) -> Tuple[ - Block, - Tuple[Mapping[str, str], Mapping[str, str]], -]: - left_expr = left.expr - right_expr = right.expr - - combined_expr, (get_column_left, get_column_right) = left_expr.relational_join( - right_expr, - type=how, - conditions=( - join_defs.JoinCondition(left.index_columns[0], right.index_columns[0]), + pd_data = pd_data.set_axis( + vendored_pandas_io_common.dedup_names( + list(pd_data.columns), is_potential_multiindex=False ), - propogate_order=propogate_order, - ) - - left_index = get_column_left[left.index_columns[0]] - right_index = get_column_right[right.index_columns[0]] - # Drop original indices from each side. and used the coalesced combination generated by the join. - combined_expr, coalesced_join_cols = resolve_col_join_ids( - combined_expr, [left_index], [right_index], how=how - ) - if sort: - combined_expr = combined_expr.order_by( - [ - ordering.OrderingExpression(ex.deref(col_id)) - for col_id in coalesced_join_cols - ] - ) - block = Block( - combined_expr, - index_columns=coalesced_join_cols, - column_labels=[*left.column_labels, *right.column_labels], - index_labels=[left.index.name] - if left.index.name == right.index.name - else [None], - ) - return ( - block, - (get_column_left, get_column_right), + axis="columns", ) + index_ids = pd_data.columns[: len(index_labels)] - -def join_multi_indexed( - left: Block, - right: Block, - *, - how="left", - sort: bool = False, - propogate_order: bool = False, -) -> Tuple[ - Block, - Tuple[Mapping[str, str], Mapping[str, str]], -]: - if not (left.index.is_uniquely_named() and right.index.is_uniquely_named()): - raise ValueError("Joins not supported on indices with non-unique level names") - - common_names = [name for name in left.index.names if name in right.index.names] - if len(common_names) == 0: - raise ValueError("Cannot join without a index level in common.") - - left_only_names = [ - name for name in left.index.names if name not in right.index.names - ] - right_only_names = [ - name for name in right.index.names if name not in left.index.names - ] - - left_join_ids = [left.index.resolve_level_exact(name) for name in common_names] - right_join_ids = [right.index.resolve_level_exact(name) for name in common_names] - - left_expr = left.expr - right_expr = right.expr - - combined_expr, (get_column_left, get_column_right) = left_expr.relational_join( - right_expr, - type=how, - conditions=tuple( - join_defs.JoinCondition(left, right) - for left, right in zip(left_join_ids, right_join_ids) - ), - propogate_order=propogate_order, - ) - - left_ids_post_join = [get_column_left[id] for id in left_join_ids] - right_ids_post_join = [get_column_right[id] for id in right_join_ids] - # Drop original indices from each side. and used the coalesced combination generated by the join. - combined_expr, coalesced_join_cols = resolve_col_join_ids( - combined_expr, left_ids_post_join, right_ids_post_join, how=how - ) - if sort: - combined_expr = combined_expr.order_by( - [ - ordering.OrderingExpression(ex.deref(col_id)) - for col_id in coalesced_join_cols - ] - ) - - if left.index.nlevels == 1: - index_labels = right.index.names - elif right.index.nlevels == 1: - index_labels = left.index.names - else: - index_labels = [*common_names, *left_only_names, *right_only_names] - - def resolve_label_id(label: Label) -> str: - # if name is shared between both blocks, coalesce the values - if label in common_names: - return coalesced_join_cols[common_names.index(label)] - if label in left_only_names: - return get_column_left[left.index.resolve_level_exact(label)] - if label in right_only_names: - return get_column_right[right.index.resolve_level_exact(label)] - raise ValueError(f"Unexpected label: {label}") - - index_columns = [resolve_label_id(label) for label in index_labels] - - block = Block( - combined_expr, - index_columns=index_columns, - column_labels=[*left.column_labels, *right.column_labels], - index_labels=index_labels, - ) - return ( - block, - (get_column_left, get_column_right), - ) - - -# TODO: Rewrite just to return expressions -def resolve_col_join_ids( - expr: core.ArrayValue, - left_ids: typing.Sequence[str], - right_ids: typing.Sequence[str], - how: str, - drop: bool = True, -) -> Tuple[core.ArrayValue, Sequence[str]]: - """ - Collapses and selects the joining column IDs, with the assumption that - the ids are all belong to value columns. - """ - result_ids = [] - for left_id, right_id in zip(left_ids, right_ids): - if how == "left" or how == "inner" or how == "cross": - result_ids.append(left_id) - if drop: - expr = expr.drop_columns([right_id]) - elif how == "right": - result_ids.append(right_id) - if drop: - expr = expr.drop_columns([left_id]) - elif how == "outer": - expr, coalesced_id = expr.project_to_id( - ops.coalesce_op.as_expr(left_id, right_id) - ) - if drop: - expr = expr.drop_columns([left_id, right_id]) - result_ids.append(coalesced_id) - else: - raise ValueError(f"Unexpected join type: {how}. {constants.FEEDBACK_LINK}") - return expr, result_ids - - -def coalesce_columns( - expr: core.ArrayValue, - left_ids: typing.Sequence[str], - right_ids: typing.Sequence[str], -) -> tuple[core.ArrayValue, list[str]]: - result_ids = [] - for left_id, right_id in zip(left_ids, right_ids): - expr, coalesced_id = expr.project_to_id( - ops.coalesce_op.as_expr(left_id, right_id) - ) - result_ids.append(coalesced_id) - - return expr, result_ids - - -def _cast_index(block: Block, dtypes: typing.Sequence[bigframes.dtypes.Dtype]): - original_block = block - result_ids = [] - for idx_id, idx_dtype, target_dtype in zip( - block.index_columns, block.index.dtypes, dtypes - ): - if idx_dtype != target_dtype: - block, result_id = block.apply_unary_op(idx_id, ops.AsTypeOp(target_dtype)) - result_ids.append(result_id) - else: - result_ids.append(idx_id) - - expr = block.expr.select_columns((*result_ids, *original_block.value_columns)) + keys_expr = core.ArrayValue.from_pandas(pd_data) return Block( - expr, - index_columns=result_ids, - column_labels=original_block.column_labels, - index_labels=original_block.index.names, + keys_expr, + column_labels=columns, + index_columns=index_ids, + index_labels=index_labels, ) -### Schema alignment Utils -### TODO: Pull out to separate module? def _align_block_to_schema( block: Block, schema: dict[Label, bigframes.dtypes.Dtype] ) -> Block: - """For a given schema, remap block to schema by reordering columns, and inserting nulls.""" + """For a given schema, remap block to schema by reordering columns and inserting nulls.""" col_ids: typing.Tuple[str, ...] = () for label, dtype in schema.items(): + # TODO: Support casting to lcd type - requires mixed type support matching_ids: typing.Sequence[str] = block.label_to_col_id.get(label, ()) if len(matching_ids) > 0: col_id = matching_ids[-1] - col_dtype = block.expr.get_column_type(col_id) - if dtype != col_dtype: - # If _align_schema worked properly, this should always be an upcast - block, col_id = block.apply_unary_op(col_id, ops.AsTypeOp(dtype)) col_ids = (*col_ids, col_id) else: block, null_column = block.create_constant(None, dtype=dtype) @@ -3338,28 +1805,24 @@ def _align_schema( return functools.reduce(reduction, schemas) -def _align_indices( - blocks: typing.Sequence[Block], -) -> typing.Tuple[typing.Sequence[Label], typing.Sequence[bigframes.dtypes.Dtype]]: - """Validates that the blocks have compatible indices and returns the resulting label names and dtypes.""" +def _align_indices(blocks: typing.Sequence[Block]) -> typing.Sequence[Label]: + """Validates that the blocks have compatible indices and returns the resulting label names.""" names = blocks[0].index.names types = blocks[0].index.dtypes - for block in blocks[1:]: if len(names) != block.index.nlevels: raise NotImplementedError( f"Cannot combine indices with different number of levels. Use 'ignore_index'=True. {constants.FEEDBACK_LINK}" ) + if block.index.dtypes != types: + raise NotImplementedError( + f"Cannot combine different index dtypes. Use 'ignore_index'=True. {constants.FEEDBACK_LINK}" + ) names = [ lname if lname == rname else None for lname, rname in zip(names, block.index.names) ] - types = [ - bigframes.dtypes.lcd_type_or_throw(ltype, rtype) - for ltype, rtype in zip(types, block.index.dtypes) - ] - types = typing.cast(typing.Sequence[bigframes.dtypes.Dtype], types) - return names, types + return names def _combine_schema_inner( @@ -3367,15 +1830,13 @@ def _combine_schema_inner( right: typing.Dict[Label, bigframes.dtypes.Dtype], ) -> typing.Dict[Label, bigframes.dtypes.Dtype]: result = dict() - for label, left_type in left.items(): + for label, type in left.items(): if label in right: - right_type = right[label] - output_type = bigframes.dtypes.lcd_type(left_type, right_type) - if output_type is None: + if type != right[label]: raise ValueError( f"Cannot concat rows with label {label} due to mismatched types. {constants.FEEDBACK_LINK}" ) - result[label] = output_type + result[label] = type return result @@ -3384,20 +1845,15 @@ def _combine_schema_outer( right: typing.Dict[Label, bigframes.dtypes.Dtype], ) -> typing.Dict[Label, bigframes.dtypes.Dtype]: result = dict() - for label, left_type in left.items(): - if label not in right: - result[label] = left_type - else: - right_type = right[label] - output_type = bigframes.dtypes.lcd_type(left_type, right_type) - if output_type is None: - raise NotImplementedError( - f"Cannot concat rows with label {label} due to mismatched types. {constants.FEEDBACK_LINK}" - ) - result[label] = output_type - for label, right_type in right.items(): + for label, type in left.items(): + if (label in right) and (type != right[label]): + raise ValueError( + f"Cannot concat rows with label {label} due to mismatched types. {constants.FEEDBACK_LINK}" + ) + result[label] = type + for label, type in right.items(): if label not in left: - result[label] = right_type + result[label] = type return result @@ -3409,155 +1865,3 @@ def _get_block_schema( for label, dtype in zip(block.column_labels, block.dtypes): result[label] = typing.cast(bigframes.dtypes.Dtype, dtype) return result - - -## Unpivot helpers -def unpivot( - array_value: core.ArrayValue, - row_labels: pd.Index, - unpivot_columns: Sequence[Tuple[Optional[str], ...]], - *, - passthrough_columns: typing.Sequence[str] = (), - join_side: Literal["left", "right"] = "left", -) -> Tuple[core.ArrayValue, Tuple[Tuple[str, ...], Tuple[str, ...], Tuple[str, ...]]]: - """ - Unpivot ArrayValue columns. - - Args: - row_labels: Identifies the source of the row. Must be equal to length to source column list in unpivot_columns argument. - unpivot_columns: Sequence of column ids tuples. Each tuple of columns will be combined into a single output column - passthrough_columns: Columns that will not be unpivoted. Column id will be preserved. - index_col_id (str): The column id to be used for the row labels. - - Returns: - ArrayValue, (index_cols, unpivot_cols, passthrough_cols): The unpivoted ArrayValue and resulting column ids. - """ - # There will be N labels, used to disambiguate which of N source columns produced each output row - labels_array = _pd_index_to_array_value( - session=array_value.session, index=row_labels - ) - - # Unpivot creates N output rows for each input row, labels disambiguate these N rows - # Join_side is necessary to produce desired row ordering - if join_side == "left": - joined_array, (column_mapping, labels_mapping) = array_value.relational_join( - labels_array, type="cross" - ) - else: - joined_array, (labels_mapping, column_mapping) = labels_array.relational_join( - array_value, type="cross" - ) - - new_passthrough_cols = [column_mapping[col] for col in passthrough_columns] - # Last column is offsets - index_col_ids = [labels_mapping[col] for col in labels_array.column_ids[:-1]] - explode_offsets_id = labels_mapping[labels_array.column_ids[-1]] - - # Build the output rows as a case statment that selects between the N input columns - unpivot_exprs: List[ex.Expression] = [] - # Supports producing multiple stacked ouput columns for stacking only part of hierarchical index - for input_ids in unpivot_columns: - col_expr: ex.Expression - if not input_ids: - col_expr = ex.const(None, dtype=bigframes.dtypes.INT_DTYPE) - else: - # row explode offset used to choose the input column - # we use offset instead of label as labels are not necessarily unique - cases = itertools.chain( - *( - ( - ops.eq_op.as_expr(explode_offsets_id, ex.const(i)), - ex.deref(column_mapping[id_or_null]) - if (id_or_null is not None) - else ex.const(None), - ) - for i, id_or_null in enumerate(input_ids) - ) - ) - col_expr = ops.case_when_op.as_expr(*cases) - unpivot_exprs.append(col_expr) - - joined_array, unpivot_col_ids = joined_array.compute_values(unpivot_exprs) - - return joined_array.select_columns( - [*index_col_ids, *unpivot_col_ids, *new_passthrough_cols] - ), (tuple(index_col_ids), tuple(unpivot_col_ids), tuple(new_passthrough_cols)) - - -def _pd_index_to_array_value( - session: session.Session, - index: pd.Index, -) -> core.ArrayValue: - """ - Create an ArrayValue from a list of label tuples. - The last column will be row offsets. - """ - id_gen = bigframes.core.identifiers.standard_id_strings() - col_ids = [next(id_gen) for _ in range(index.nlevels)] - offset_id = next(id_gen) - - rows = [] - labels_as_tuples = utils.index_as_tuples(index) - for row_offset in range(len(index)): - row_label = labels_as_tuples[row_offset] - row_label = (row_label,) if not isinstance(row_label, tuple) else row_label - row = {} - for label_part, col_id in zip(row_label, col_ids): - row[col_id] = label_part if pd.notnull(label_part) else None - row[offset_id] = row_offset - rows.append(row) - - if not rows: - dtypes_list = getattr(index, "dtypes", None) - if dtypes_list is None: - dtypes_list = ( - [index.dtype] if hasattr(index, "dtype") else [pd.Float64Dtype()] - ) - - fields = [] - for col_id, dtype in zip(col_ids, dtypes_list): - try: - pa_type = bigframes.dtypes.bigframes_dtype_to_arrow_dtype(dtype) - except Exception: - pa_type = pa.string() - fields.append(pa.field(col_id, pa_type)) - fields.append(pa.field(offset_id, pa.int64())) - schema = pa.schema(fields) - pt = pa.Table.from_pylist([], schema=schema) - else: - pt = pa.Table.from_pylist(rows) - pt = pt.rename_columns([*col_ids, offset_id]) - - return core.ArrayValue.from_pyarrow(pt, session=session) - - -def _resolve_index_col( - left_index_cols: list[str], - right_index_cols: list[str], - resolved_join_ids: list[str], - left_index: bool, - right_index: bool, - how: typing.Literal[ - "inner", - "left", - "outer", - "right", - "cross", - ], -) -> list[str]: - if left_index and right_index: - if how == "inner" or how == "left": - return left_index_cols - if how == "right": - return right_index_cols - if how == "outer": - return resolved_join_ids - else: - return [] - elif left_index and not right_index: - return right_index_cols - elif right_index and not left_index: - return left_index_cols - else: - # Joining with value columns only. Existing indices will be discarded. - return [] diff --git a/bigframes/core/bq_data.py b/bigframes/core/bq_data.py deleted file mode 100644 index 55ac1270b6c..00000000000 --- a/bigframes/core/bq_data.py +++ /dev/null @@ -1,410 +0,0 @@ -# Copyright 2023 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 concurrent.futures -import dataclasses -import datetime -import functools -import os -import queue -import threading -import typing -from typing import Any, Iterator, List, Literal, Optional, Sequence, Tuple, Union - -import google.cloud.bigquery as bq -import google.cloud.bigquery_storage_v1.types as bq_storage_types -import pyarrow as pa -from google.cloud import bigquery_storage_v1 -from google.protobuf import timestamp_pb2 - -import bigframes.constants -import bigframes.core.schema -from bigframes.core import pyarrow_utils - -if typing.TYPE_CHECKING: - import bigframes.core.ordering as orderings - - -def _resolve_standard_gcp_region(bq_region: str): - """ - Resolve bq regions to standardized - """ - if bq_region.casefold() == "US": - return "us-central1" - elif bq_region.casefold() == "EU": - return "europe-west4" - return bq_region - - -def is_irc_table(table_id: str): - """ - Determines if a table id should be resolved through the iceberg rest catalog. - """ - return len(table_id.split(".")) == 4 - - -def is_compatible( - data_region: Union[GcsRegion, BigQueryRegion], session_location: str -) -> bool: - # based on https://docs.cloud.google.com/bigquery/docs/locations#storage-location-considerations - if isinstance(data_region, BigQueryRegion): - return data_region.name == session_location - else: - assert isinstance(data_region, GcsRegion) - # TODO(b/463675088): Multi-regions don't yet support rest catalog tables - if session_location in bigframes.constants.BIGQUERY_MULTIREGIONS: - return False - return _resolve_standard_gcp_region(session_location) in data_region.included - - -def get_default_bq_region(data_region: Union[GcsRegion, BigQueryRegion]) -> str: - if isinstance(data_region, BigQueryRegion): - return data_region.name - elif isinstance(data_region, GcsRegion): - # should maybe try to track and prefer primary replica? - return data_region.included[0] - - -@dataclasses.dataclass(frozen=True) -class BigQueryRegion: - name: str - - -@dataclasses.dataclass(frozen=True) -class GcsRegion: - # this is the name of gcs regions, which may be names for multi-regions, so shouldn't be compared with non-gcs locations - storage_regions: tuple[str, ...] - # this tracks all the included standard, specific regions (eg us-east1), and should be comparable to bq regions (except non-standard US, EU, omni regions) - included: tuple[str, ...] - - -# what is the line between metadata and core fields? Mostly metadata fields are optional or unreliable, but its fuzzy -@dataclasses.dataclass(frozen=True) -class TableMetadata: - # this size metadata might be stale, don't use where strict correctness is needed - location: Union[BigQueryRegion, GcsRegion] - type: Literal["TABLE", "EXTERNAL", "VIEW", "MATERIALIZE_VIEW", "SNAPSHOT"] - numBytes: Optional[int] = None - numRows: Optional[int] = None - created_time: Optional[datetime.datetime] = None - modified_time: Optional[datetime.datetime] = None - - -@dataclasses.dataclass(frozen=True) -class GbqNativeTable: - project_id: str = dataclasses.field() - dataset_id: str = dataclasses.field() - table_id: str = dataclasses.field() - physical_schema: Tuple[bq.SchemaField, ...] = dataclasses.field() - metadata: TableMetadata = dataclasses.field() - partition_col: Optional[str] = None - cluster_cols: typing.Optional[Tuple[str, ...]] = None - primary_key: Optional[Tuple[str, ...]] = None - - @staticmethod - def from_table(table: bq.Table, columns: Sequence[str] = ()) -> GbqNativeTable: - # Subsetting fields with columns can reduce cost of row-hash default ordering - if columns: - schema = tuple(item for item in table.schema if item.name in columns) - else: - schema = tuple(table.schema) - - metadata = TableMetadata( - numBytes=table.num_bytes, - numRows=table.num_rows, - location=BigQueryRegion(table.location), # type: ignore - type=table.table_type or "TABLE", # type: ignore - created_time=table.created, - modified_time=table.modified, - ) - partition_col = None - if table.range_partitioning: - partition_col = table.range_partitioning.field - elif table.time_partitioning: - partition_col = table.time_partitioning.field - - return GbqNativeTable( - project_id=table.project, - dataset_id=table.dataset_id, - table_id=table.table_id, - physical_schema=schema, - partition_col=partition_col, - cluster_cols=None - if (table.clustering_fields is None) - else tuple(table.clustering_fields), - primary_key=tuple(_get_primary_keys(table)), - metadata=metadata, - ) - - @staticmethod - def from_ref_and_schema( - table_ref: bq.TableReference, - schema: Sequence[bq.SchemaField], - location: str, - table_type: Literal["TABLE"] = "TABLE", - cluster_cols: Optional[Sequence[str]] = None, - ) -> GbqNativeTable: - return GbqNativeTable( - project_id=table_ref.project, - dataset_id=table_ref.dataset_id, - table_id=table_ref.table_id, - metadata=TableMetadata(location=BigQueryRegion(location), type=table_type), - physical_schema=tuple(schema), - cluster_cols=tuple(cluster_cols) if cluster_cols else None, - ) - - @property - def is_physically_stored(self) -> bool: - return self.metadata.type in ["TABLE", "MATERIALIZED_VIEW"] - - def get_table_ref(self) -> bq.TableReference: - return bq.TableReference( - bq.DatasetReference(self.project_id, self.dataset_id), self.table_id - ) - - def get_full_id(self, quoted: bool = False) -> str: - if quoted: - return f"`{self.project_id}`.`{self.dataset_id}`.`{self.table_id}`" - return f"{self.project_id}.{self.dataset_id}.{self.table_id}" - - @property - @functools.cache - def schema_by_id(self): - return {col.name: col for col in self.physical_schema} - - -@dataclasses.dataclass(frozen=True) -class BiglakeIcebergTable: - project_id: str = dataclasses.field() - catalog_id: str = dataclasses.field() - namespace_id: str = dataclasses.field() - table_id: str = dataclasses.field() - physical_schema: Tuple[bq.SchemaField, ...] = dataclasses.field() - cluster_cols: typing.Optional[Tuple[str, ...]] - metadata: TableMetadata - - def get_full_id(self, quoted: bool = False) -> str: - if quoted: - return f"`{self.project_id}`.`{self.catalog_id}`.`{self.namespace_id}`.`{self.table_id}`" - return ( - f"{self.project_id}.{self.catalog_id}.{self.namespace_id}.{self.table_id}" - ) - - @property - @functools.cache - def schema_by_id(self): - return {col.name: col for col in self.physical_schema} - - @property - def partition_col(self) -> Optional[str]: - # TODO: Use iceberg partition metadata - return None - - @property - def dataset_id(self) -> str: - """ - Not a true dataset, but serves as the dataset component of the identifer in sql queries - """ - return f"{self.catalog_id}.{self.namespace_id}" - - @property - def primary_key(self) -> Optional[Tuple[str, ...]]: - return None - - def get_table_ref(self) -> bq.TableReference: - return bq.TableReference( - bq.DatasetReference(self.project_id, self.dataset_id), self.table_id - ) - - -@dataclasses.dataclass(frozen=True) -class BigqueryDataSource: - """ - Google BigQuery Data source. - - This should not be modified once defined, as all attributes contribute to the default ordering. - """ - - def __post_init__(self): - # not all columns need be in schema, eg so can exclude unsupported column types (eg RANGE) - assert set(field.name for field in self.table.physical_schema).issuperset( - self.schema.names - ) - - table: Union[GbqNativeTable, BiglakeIcebergTable] - schema: bigframes.core.schema.ArraySchema - at_time: typing.Optional[datetime.datetime] = None - # Added for backwards compatibility, not validated - sql_predicate: typing.Optional[str] = None - ordering: typing.Optional[orderings.RowOrdering] = None - # Optimization field, must be correct if set, don't put maybe-stale number here - n_rows: Optional[int] = None - - def with_ordering(self, ordering: orderings.RowOrdering) -> BigqueryDataSource: - return dataclasses.replace(self, ordering=ordering) - - -_WORKER_TIME_INCREMENT = 0.05 - - -def _iter_stream( - stream_name: str, - storage_read_client: bigquery_storage_v1.BigQueryReadClient, - result_queue: queue.Queue, - stop_event: threading.Event, -): - reader = storage_read_client.read_rows(stream_name) - for page in reader.rows().pages: - while True: # Alternate between put attempt and checking stop event - try: - result_queue.put(page.to_arrow(), timeout=_WORKER_TIME_INCREMENT) - break - except queue.Full: - if stop_event.is_set(): - return - continue - - -def _iter_streams( - streams: Sequence[bq_storage_types.ReadStream], - storage_read_client: bigquery_storage_v1.BigQueryReadClient, -) -> Iterator[pa.RecordBatch]: - stop_event = threading.Event() - result_queue: queue.Queue = queue.Queue( - len(streams) - ) # each response is large, so small queue is appropriate - - in_progress: list[concurrent.futures.Future] = [] - with concurrent.futures.ThreadPoolExecutor(max_workers=len(streams)) as pool: - try: - for stream in streams: - in_progress.append( - pool.submit( - _iter_stream, - stream.name, - storage_read_client, - result_queue, - stop_event, - ) - ) - - while in_progress: - try: - yield result_queue.get(timeout=0.1) - except queue.Empty: - new_in_progress = [] - for future in in_progress: - if future.done(): - # Call to raise any exceptions - future.result() - else: - new_in_progress.append(future) - in_progress = new_in_progress - finally: - stop_event.set() - - -@dataclasses.dataclass -class ReadResult: - iter: Iterator[pa.RecordBatch] - approx_rows: int - approx_bytes: int - - -def get_arrow_batches( - data: BigqueryDataSource, - columns: Sequence[str], - storage_read_client: bigquery_storage_v1.BigQueryReadClient, - project_id: str, - sample_rate: Optional[float] = None, -) -> ReadResult: - assert isinstance(data.table, GbqNativeTable) - - table_mod_options = {} - read_options_dict: dict[str, Any] = {"selected_fields": list(columns)} - - predicates = [] - if data.sql_predicate: - predicates.append(data.sql_predicate) - if sample_rate is not None: - assert isinstance(sample_rate, float) - predicates.append(f"RAND() < {sample_rate}") - - if predicates: - full_predicates = " AND ".join(f"( {pred} )" for pred in predicates) - read_options_dict["row_restriction"] = full_predicates - - read_options = bq_storage_types.ReadSession.TableReadOptions(**read_options_dict) - - if data.at_time: - snapshot_time = timestamp_pb2.Timestamp() - snapshot_time.FromDatetime(data.at_time) - table_mod_options["snapshot_time"] = snapshot_time - table_mods = bq_storage_types.ReadSession.TableModifiers(**table_mod_options) - - requested_session = bq_storage_types.stream.ReadSession( - table=data.table.get_table_ref().to_bqstorage(), - data_format=bq_storage_types.DataFormat.ARROW, - read_options=read_options, - table_modifiers=table_mods, - ) - if data.ordering is not None: - max_streams = 1 - else: - max_streams = os.cpu_count() or 8 - - # Single stream to maintain ordering - request = bq_storage_types.CreateReadSessionRequest( - parent=f"projects/{project_id}", - read_session=requested_session, - max_stream_count=max_streams, - ) - - session = storage_read_client.create_read_session(request=request) - - if not session.streams: - batches: Iterator[pa.RecordBatch] = iter([]) - else: - batches = _iter_streams(session.streams, storage_read_client) - - def process_batch(pa_batch): - return pyarrow_utils.cast_batch( - pa_batch.select(columns), data.schema.select(columns).to_pyarrow() - ) - - batches = map(process_batch, batches) - - return ReadResult( - batches, session.estimated_row_count, session.estimated_total_bytes_scanned - ) - - -def _get_primary_keys( - table: bq.Table, -) -> List[str]: - """Get primary keys from table if they are set.""" - - primary_keys: List[str] = [] - if ( - (table_constraints := getattr(table, "table_constraints", None)) is not None - and (primary_key := table_constraints.primary_key) is not None - # This will be False for either None or empty list. - # We want primary_keys = None if no primary keys are set. - and (columns := primary_key.columns) - ): - primary_keys = columns if columns is not None else [] - - return primary_keys diff --git a/bigframes/core/bytecode.py b/bigframes/core/bytecode.py deleted file mode 100644 index f657ce707ea..00000000000 --- a/bigframes/core/bytecode.py +++ /dev/null @@ -1,913 +0,0 @@ -# Copyright 2026 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 -import dis -import operator -import sys -from types import ModuleType -from typing import Callable - -import bigframes.core.py_expressions as py_exprs -from bigframes.core import expression -from bigframes.operations import generic_ops - -_BINARY_OP_MAP = { - "+": operator.add, - "-": operator.sub, - "*": operator.mul, - "/": operator.truediv, - "//": operator.floordiv, - "%": operator.mod, - "**": operator.pow, - "[]": operator.getitem, -} - -_COMPARE_OP_MAP = { - "==": operator.eq, - "!=": operator.ne, - "<": operator.lt, - "<=": operator.le, - ">": operator.gt, - ">=": operator.ge, -} - -_OLD_BINARY_OP_MAP = { - "BINARY_ADD": operator.add, - "INPLACE_ADD": operator.add, - "BINARY_SUBTRACT": operator.sub, - "INPLACE_SUBTRACT": operator.sub, - "BINARY_MULTIPLY": operator.mul, - "INPLACE_MULTIPLY": operator.mul, - "BINARY_TRUE_DIVIDE": operator.truediv, - "INPLACE_TRUE_DIVIDE": operator.truediv, - "BINARY_FLOOR_DIVIDE": operator.floordiv, - "INPLACE_FLOOR_DIVIDE": operator.floordiv, - "BINARY_MODULO": operator.mod, - "INPLACE_MODULO": operator.mod, - "BINARY_POWER": operator.pow, - "INPLACE_POWER": operator.pow, -} - - -_NULL = py_exprs.PyObject(None) - - -_RETURN_OPNAMES = {"RETURN_VALUE", "RETURN_CONST"} - -_UNCONDITIONAL_JUMP_OPNAMES = { - "JUMP_FORWARD", - "JUMP_ABSOLUTE", - "JUMP_BACKWARD", - "JUMP_BACKWARD_NO_INTERRUPT", - "JUMP", - "JUMP_NO_INTERRUPT", -} - -_JUMP_IF_FALSE_OPNAMES = { - "POP_JUMP_IF_FALSE", - "POP_JUMP_FORWARD_IF_FALSE", - "POP_JUMP_BACKWARD_IF_FALSE", -} - -_JUMP_IF_TRUE_OPNAMES = { - "POP_JUMP_IF_TRUE", - "POP_JUMP_FORWARD_IF_TRUE", - "POP_JUMP_BACKWARD_IF_TRUE", -} - -_JUMP_IF_NONE_OPNAMES = { - "POP_JUMP_IF_NONE", - "POP_JUMP_FORWARD_IF_NONE", - "POP_JUMP_BACKWARD_IF_NONE", -} - -_JUMP_IF_NOT_NONE_OPNAMES = { - "POP_JUMP_IF_NOT_NONE", - "POP_JUMP_FORWARD_IF_NOT_NONE", - "POP_JUMP_BACKWARD_IF_NOT_NONE", -} - -_CONDITIONAL_JUMP_OPNAMES = ( - _JUMP_IF_FALSE_OPNAMES - | _JUMP_IF_TRUE_OPNAMES - | _JUMP_IF_NONE_OPNAMES - | _JUMP_IF_NOT_NONE_OPNAMES - | { - "JUMP_IF_FALSE_OR_POP", - "JUMP_IF_TRUE_OR_POP", - } -) - -_ALL_JUMP_OPNAMES = _UNCONDITIONAL_JUMP_OPNAMES | _CONDITIONAL_JUMP_OPNAMES - - -@dataclasses.dataclass -class BasicBlock: - start_offset: int - instructions: list[dis.Instruction] - successors: list[int] = dataclasses.field(default_factory=list) - predecessors: list[int] = dataclasses.field(default_factory=list) - - -def get_block_starts(instructions: list[dis.Instruction]) -> set[int]: - starts = {0} - for i, inst in enumerate(instructions): - opname = inst.opname - if opname in _ALL_JUMP_OPNAMES: - if isinstance(inst.argval, int): - starts.add(inst.argval) - if i + 1 < len(instructions): - starts.add(instructions[i + 1].offset) - elif opname in _RETURN_OPNAMES: - if i + 1 < len(instructions): - starts.add(instructions[i + 1].offset) - return starts - - -def get_block_successors(block: BasicBlock, next_offsets: dict[int, int]) -> list[int]: - if not block.instructions: - return [] - last_inst = block.instructions[-1] - opname = last_inst.opname - offset = last_inst.offset - - next_offset = next_offsets.get(offset) - - if opname in _RETURN_OPNAMES: - return [] - - if opname in _UNCONDITIONAL_JUMP_OPNAMES: - return [last_inst.argval] - - if opname in _CONDITIONAL_JUMP_OPNAMES: - successors = [last_inst.argval] - if next_offset is not None: - successors.append(next_offset) - return successors - - if next_offset is not None: - return [next_offset] - return [] - - -def build_cfg( - instructions: list[dis.Instruction], next_offsets: dict[int, int] -) -> dict[int, BasicBlock]: - starts = sorted(list(get_block_starts(instructions))) - - blocks: dict[int, BasicBlock] = {} - for i, start in enumerate(starts): - end = starts[i + 1] if i + 1 < len(starts) else None - block_insts = [ - inst - for inst in instructions - if start <= inst.offset and (end is None or inst.offset < end) - ] - blocks[start] = BasicBlock(start_offset=start, instructions=block_insts) - - for block in blocks.values(): - successors = get_block_successors(block, next_offsets) - block.successors = successors - for succ in successors: - blocks[succ].predecessors.append(block.start_offset) - - return blocks - - -def topological_sort(blocks: dict[int, BasicBlock]) -> list[int]: - in_degree = {offset: len(block.predecessors) for offset, block in blocks.items()} - queue = [offset for offset, deg in in_degree.items() if deg == 0] - order = [] - - while queue: - queue.sort() - curr = queue.pop(0) - order.append(curr) - for succ in blocks[curr].successors: - in_degree[succ] -= 1 - if in_degree[succ] == 0: - queue.append(succ) - - # TODO(b/521549179): Support limited loop analysis (eg unroll loops over a constant range). - if len(order) != len(blocks): - raise ValueError( - "Loops are not supported in the Python function for transpilation." - ) - - return order - - -def merge_values( - pairs: list[tuple[expression.Expression, expression.Expression]], -) -> expression.Expression: - if not pairs: - raise ValueError("Cannot merge empty list of values") - if len(pairs) == 1: - return pairs[0][0] - - val = pairs[-1][0] - for next_val, next_cond in reversed(pairs[:-1]): - val = py_exprs.Call( - py_exprs.PyObject(generic_ops.where_op), (next_val, next_cond, val) - ) - return val - - -def _compile_bytecode_to_py_expr(func: Callable) -> expression.Expression: - instructions = list(dis.get_instructions(func)) - next_offsets = { - inst.offset: next_inst.offset - for inst, next_inst in zip(instructions, instructions[1:]) - } - - blocks = build_cfg(instructions, next_offsets) - order = topological_sort(blocks) - - stack: list[expression.Expression] - local_vars: dict[str, expression.Expression] - - globals_dict = func.__globals__ - import builtins - - builtins_dict = builtins.__dict__ - closure_dict = {} - if func.__closure__: - free_vars = func.__code__.co_freevars - for var, cell in zip(free_vars, func.__closure__): - try: - closure_dict[var] = cell.cell_contents - except ValueError: - pass - - block_outputs: dict[ - int, tuple[list[expression.Expression], dict[str, expression.Expression]] - ] = {} - block_reach_conditions: dict[int, expression.Expression] = { - 0: py_exprs.PyObject(True) - } - edge_conditions: dict[tuple[int, int], expression.Expression] = {} - edge_stacks: dict[tuple[int, int], list[expression.Expression]] = {} - returns: list[tuple[expression.Expression, expression.Expression]] = [] - - co = func.__code__ - param_names = list(co.co_varnames[: co.co_argcount]) - kwonly_argcount = co.co_kwonlyargcount - param_names.extend( - co.co_varnames[co.co_argcount : co.co_argcount + kwonly_argcount] - ) - - initial_local_vars: dict[str, expression.Expression] = { - name: expression.UnboundVariableExpression(name) for name in param_names - } - - for offset in order: - block = blocks[offset] - - reach_cond: expression.Expression - if offset == 0: - reach_cond = py_exprs.PyObject(True) - else: - incoming = [ - edge_conditions[(pred, offset)] - for pred in block.predecessors - if (pred, offset) in edge_conditions - ] - if not incoming: - continue - - reach_cond = incoming[0] - for cond in incoming[1:]: - reach_cond = py_exprs.Call( - py_exprs.PyObject(operator.or_), (reach_cond, cond) - ) - - block_reach_conditions[offset] = reach_cond - - if offset == 0: - stack = [] - local_vars = initial_local_vars.copy() - else: - reachable_preds = [ - pred for pred in block.predecessors if (pred, offset) in edge_stacks - ] - if not reachable_preds: - continue - - h = len(edge_stacks[(reachable_preds[0], offset)]) - stack = [] - for i in range(h): - pairs = [ - (edge_stacks[(p, offset)][i], edge_conditions[(p, offset)]) - for p in reachable_preds - ] - stack.append(merge_values(pairs)) - - all_vars: set[str] = set() - for p in reachable_preds: - all_vars.update(block_outputs[p][1].keys()) - - local_vars = {} - for var in all_vars: - pairs = [ - ( - block_outputs[p][1].get( - var, expression.UnboundVariableExpression(var) - ), - edge_conditions[(p, offset)], - ) - for p in reachable_preds - ] - local_vars[var] = merge_values(pairs) - - jumped = False - for inst in block.instructions: - opname = inst.opname - - match opname: - case "RESUME" | "PRECALL" | "COPY_FREE_VARS" | "NOT_TAKEN" | "NOP": - continue - - case "LOAD_FAST_LOAD_FAST" | "LOAD_FAST_BORROW_LOAD_FAST_BORROW": - var1, var2 = inst.argval - stack.append( - local_vars.get(var1, expression.UnboundVariableExpression(var1)) - ) - stack.append( - local_vars.get(var2, expression.UnboundVariableExpression(var2)) - ) - - case ( - "LOAD_FAST" - | "LOAD_FAST_CHECK" - | "LOAD_FAST_AND_CLEAR" - | "LOAD_FAST_BORROW" - ): - stack.append( - local_vars.get( - inst.argval, - expression.UnboundVariableExpression(inst.argval), - ) - ) - - case "STORE_FAST": - if not stack: - raise ValueError("Stack is empty") - local_vars[inst.argval] = stack.pop() - - case "LOAD_CONST" | "LOAD_SMALL_INT": - stack.append(py_exprs.PyObject(inst.argval)) - - case "LOAD_DEREF" | "LOAD_FROM_DICT_OR_DEREF": - name = inst.argval - found = False - val = None - if name in closure_dict: - val = closure_dict[name] - found = True - elif name in globals_dict: - val = globals_dict[name] - found = True - elif name in builtins_dict: - val = builtins_dict[name] - found = True - - if found: - if isinstance(val, ModuleType): - stack.append(py_exprs.Module(val)) - else: - stack.append(py_exprs.PyObject(val)) - else: - stack.append(expression.UnboundVariableExpression(name)) - - case "LOAD_GLOBAL": - if ( - sys.version_info >= (3, 11) - and inst.arg is not None - and (inst.arg & 1) - ): - stack.append(_NULL) - name = inst.argval - found = False - val = None - if name in closure_dict: - val = closure_dict[name] - found = True - elif name in globals_dict: - val = globals_dict[name] - found = True - elif name in builtins_dict: - val = builtins_dict[name] - found = True - - if found: - if isinstance(val, ModuleType): - stack.append(py_exprs.Module(val)) - else: - stack.append(py_exprs.PyObject(val)) - else: - stack.append(expression.UnboundVariableExpression(name)) - - case "LOAD_ATTR" | "LOAD_METHOD": - if not stack: - raise ValueError("Stack is empty") - target = stack.pop() - stack.append(py_exprs.GetAttr(target, inst.argval)) - - is_method_lookup = (opname == "LOAD_METHOD") or ( - opname == "LOAD_ATTR" - and sys.version_info >= (3, 12) - and inst.arg is not None - and (inst.arg & 1) - ) - if is_method_lookup: - if isinstance(target, py_exprs.Module) or ( - isinstance(target, py_exprs.PyObject) - and isinstance(target.value, type) - ): - stack.append(_NULL) - else: - stack.append(target) - - case "PUSH_NULL": - stack.append(_NULL) - - case "TO_BOOL": - if not stack: - raise ValueError("Stack is empty") - val = stack.pop() - stack.append( - py_exprs.Call( - py_exprs.PyObject(generic_ops.coerce_to_bool_op), - (val,), - ) - ) - - case "FORMAT_SIMPLE": - if not stack: - raise ValueError("Stack is empty") - value = stack.pop() - stack.append(py_exprs.Call(py_exprs.PyObject(str), (value,))) - - case "CONVERT_VALUE": - flags = inst.arg - assert flags is not None - value = stack.pop() - if flags == 1: - stack.append(py_exprs.Call(py_exprs.PyObject(str), (value,))) - else: - raise NotImplementedError( - "repr() and ascii() conversions are not supported" - ) - - case "FORMAT_VALUE": - flags = inst.arg - assert flags is not None - if (flags & 0x04) == 0x04: - stack.pop() - raise NotImplementedError( - "Formatting with specifier is not supported" - ) - - value = stack.pop() - conversion = flags & 0x03 - if conversion == 0 or conversion == 1: - stack.append(py_exprs.Call(py_exprs.PyObject(str), (value,))) - else: - raise NotImplementedError( - "repr() and ascii() conversions are not supported" - ) - - case "FORMAT_WITH_SPEC": - raise NotImplementedError( - "Formatting with specifier is not supported" - ) - - case "BUILD_STRING": - count = inst.arg - assert count is not None - if len(stack) < count: - raise ValueError( - "Stack has fewer elements than BUILD_STRING count" - ) - - if count == 0: - stack.append(py_exprs.PyObject("")) - else: - strings = [stack.pop() for _ in range(count)][::-1] - result = strings[0] - for s in strings[1:]: - result = py_exprs.Call( - py_exprs.PyObject(operator.add), - (result, s), - ) - stack.append(result) - - case "COPY": - idx = inst.arg - if idx is None or idx < 1 or len(stack) < idx: - raise ValueError( - f"Invalid COPY index or stack too small: {idx}" - ) - stack.append(stack[-idx]) - - case "UNARY_NOT": - if not stack: - raise ValueError("Stack is empty") - val = stack.pop() - val_bool = py_exprs.Call( - py_exprs.PyObject(generic_ops.coerce_to_bool_op), - (val,), - ) - stack.append( - py_exprs.Call( - py_exprs.PyObject(operator.not_), - (val_bool,), - ) - ) - - case "SWAP": - idx = inst.arg - if idx is None or idx < 1 or len(stack) < idx: - raise ValueError( - f"Invalid SWAP index or stack too small: {idx}" - ) - stack[-1], stack[-idx] = stack[-idx], stack[-1] - - case "ROT_TWO": - if len(stack) < 2: - raise ValueError("Stack has < 2 elements") - stack[-1], stack[-2] = stack[-2], stack[-1] - - case "ROT_THREE": - if len(stack) < 3: - raise ValueError("Stack has < 3 elements") - stack[-1], stack[-2], stack[-3] = stack[-2], stack[-3], stack[-1] - - case "DUP_TOP": - if not stack: - raise ValueError("Stack is empty") - stack.append(stack[-1]) - - case "BINARY_OP": - if len(stack) < 2: - raise ValueError("Stack is empty") - right = stack.pop() - left = stack.pop() - op_symbol = inst.argrepr - if not op_symbol and isinstance(inst.argval, str): - op_symbol = inst.argval - if op_symbol and op_symbol.endswith("="): - op_symbol = op_symbol[:-1] - - if op_symbol not in _BINARY_OP_MAP: - raise ValueError(f"Unsupported binary operator: {op_symbol}") - stack.append( - py_exprs.Call( - py_exprs.PyObject(_BINARY_OP_MAP[op_symbol]), - (left, right), - ) - ) - - case "BINARY_SUBSCR": - if len(stack) < 2: - raise ValueError("Stack has < 2 elements") - key = stack.pop() - container = stack.pop() - stack.append( - py_exprs.Call( - py_exprs.PyObject(operator.getitem), - (container, key), - ) - ) - - case name if name in _OLD_BINARY_OP_MAP: - if len(stack) < 2: - raise ValueError("Stack has < 2 elements") - right = stack.pop() - left = stack.pop() - stack.append( - py_exprs.Call( - py_exprs.PyObject(_OLD_BINARY_OP_MAP[opname]), - (left, right), - ) - ) - - case "IS_OP": - if len(stack) < 2: - raise ValueError("Stack has < 2 elements") - right = stack.pop() - left = stack.pop() - invert = inst.arg - - def is_none_const(expr) -> bool: - if isinstance(expr, py_exprs.PyObject) and expr.value is None: - return True - if ( - isinstance(expr, expression.ScalarConstantExpression) - and expr.value is None - ): - return True - return False - - if is_none_const(right): - op = ( - generic_ops.isnull_op - if not invert - else generic_ops.notnull_op - ) - stack.append(py_exprs.Call(py_exprs.PyObject(op), (left,))) - elif is_none_const(left): - op = ( - generic_ops.isnull_op - if not invert - else generic_ops.notnull_op - ) - stack.append(py_exprs.Call(py_exprs.PyObject(op), (right,))) - else: - raise NotImplementedError( - "Identity comparison (is/is not) is only supported for None" - ) - - case "COMPARE_OP": - if len(stack) < 2: - raise ValueError("Stack has < 2 elements") - right = stack.pop() - left = stack.pop() - op_symbol = inst.argval - if op_symbol not in _COMPARE_OP_MAP: - raise ValueError(f"Unsupported compare operator: {op_symbol}") - stack.append( - py_exprs.Call( - py_exprs.PyObject(_COMPARE_OP_MAP[op_symbol]), - (left, right), - ) - ) - - case "UNARY_NEGATIVE" | "UNARY_INVERT": - if not stack: - raise ValueError("Stack is empty") - target = stack.pop() - stack.append( - py_exprs.Call( - py_exprs.PyObject( - operator.neg - if opname == "UNARY_NEGATIVE" - else operator.invert - ), - (target,), - ) - ) - - case "UNARY_POSITIVE": - if not stack: - raise ValueError("Stack is empty") - target = stack.pop() - stack.append( - py_exprs.Call(py_exprs.PyObject(operator.pos), (target,)) - ) - - case "CALL_INTRINSIC_1": - if inst.argrepr == "INTRINSIC_UNARY_POSITIVE": - if not stack: - raise ValueError("Stack is empty") - target = stack.pop() - stack.append( - py_exprs.Call(py_exprs.PyObject(operator.pos), (target,)) - ) - else: - raise ValueError(f"Unsupported intrinsic: {inst.argrepr}") - - case "CALL" | "CALL_FUNCTION" | "CALL_METHOD": - num_args = inst.arg - assert num_args is not None - if len(stack) < num_args: - raise ValueError(f"Stack has fewer than {num_args} elements") - args = [stack.pop() for _ in range(num_args)][::-1] - - is_method_call = False - if opname == "CALL" or opname == "CALL_METHOD": - if len(stack) >= 2 and stack[-2] == _NULL: - stack[-1], stack[-2] = stack[-2], stack[-1] - if stack and stack[-1] == _NULL: - stack.pop() - is_method_call = False - else: - is_method_call = True - elif opname == "CALL_FUNCTION": - is_method_call = False - - if is_method_call: - if ( - stack - and stack[-1] != _NULL - and isinstance(stack[-1], expression.Expression) - ): - self_arg = stack.pop() - args = [self_arg] + args - - if not stack: - raise ValueError("Stack is empty") - callable_expr = stack.pop() - stack.append(py_exprs.Call(callable_expr, tuple(args))) - - case "RETURN_VALUE": - if not stack: - raise ValueError("Stack is empty") - returns.append((stack[-1], reach_cond)) - jumped = True - break - - case "RETURN_CONST": - returns.append((py_exprs.PyObject(inst.argval), reach_cond)) - jumped = True - break - - case "POP_TOP": - if stack: - stack.pop() - - case name if name in _UNCONDITIONAL_JUMP_OPNAMES: - dest = inst.argval - edge_conditions[(offset, dest)] = reach_cond - edge_stacks[(offset, dest)] = stack.copy() - jumped = True - break - - case "JUMP_IF_FALSE_OR_POP" | "JUMP_IF_TRUE_OR_POP": - if not stack: - raise ValueError("Stack is empty") - cond_expr = stack[-1] - cond_bool = py_exprs.Call( - py_exprs.PyObject(generic_ops.coerce_to_bool_op), - (cond_expr,), - ) - dest = inst.argval - next_offset = next_offsets.get(inst.offset) - if opname == "JUMP_IF_FALSE_OR_POP": - not_cond_bool = py_exprs.Call( - py_exprs.PyObject(operator.not_), (cond_bool,) - ) - edge_conditions[(offset, dest)] = py_exprs.Call( - py_exprs.PyObject(operator.and_), - (reach_cond, not_cond_bool), - ) - edge_stacks[(offset, dest)] = stack.copy() - if next_offset is not None: - edge_conditions[(offset, next_offset)] = py_exprs.Call( - py_exprs.PyObject(operator.and_), - (reach_cond, cond_bool), - ) - edge_stacks[(offset, next_offset)] = stack[:-1] - else: # JUMP_IF_TRUE_OR_POP - edge_conditions[(offset, dest)] = py_exprs.Call( - py_exprs.PyObject(operator.and_), - (reach_cond, cond_bool), - ) - edge_stacks[(offset, dest)] = stack.copy() - if next_offset is not None: - not_cond_bool = py_exprs.Call( - py_exprs.PyObject(operator.not_), (cond_bool,) - ) - edge_conditions[(offset, next_offset)] = py_exprs.Call( - py_exprs.PyObject(operator.and_), - (reach_cond, not_cond_bool), - ) - edge_stacks[(offset, next_offset)] = stack[:-1] - jumped = True - break - - case name if ( - name in _JUMP_IF_FALSE_OPNAMES or name in _JUMP_IF_TRUE_OPNAMES - ): - if not stack: - raise ValueError("Stack is empty") - cond_expr = stack.pop() - cond_expr = py_exprs.Call( - py_exprs.PyObject(generic_ops.coerce_to_bool_op), - (cond_expr,), - ) - - dest = inst.argval - next_offset = next_offsets.get(inst.offset) - - if opname in _JUMP_IF_FALSE_OPNAMES: - not_cond_expr = py_exprs.Call( - py_exprs.PyObject(operator.not_), (cond_expr,) - ) - edge_conditions[(offset, dest)] = py_exprs.Call( - py_exprs.PyObject(operator.and_), - (reach_cond, not_cond_expr), - ) - edge_stacks[(offset, dest)] = stack.copy() - if next_offset is not None: - edge_conditions[(offset, next_offset)] = py_exprs.Call( - py_exprs.PyObject(operator.and_), - (reach_cond, cond_expr), - ) - edge_stacks[(offset, next_offset)] = stack.copy() - else: # opname in _JUMP_IF_TRUE_OPNAMES - not_cond_expr = py_exprs.Call( - py_exprs.PyObject(operator.not_), (cond_expr,) - ) - edge_conditions[(offset, dest)] = py_exprs.Call( - py_exprs.PyObject(operator.and_), - (reach_cond, cond_expr), - ) - edge_stacks[(offset, dest)] = stack.copy() - if next_offset is not None: - edge_conditions[(offset, next_offset)] = py_exprs.Call( - py_exprs.PyObject(operator.and_), - (reach_cond, not_cond_expr), - ) - edge_stacks[(offset, next_offset)] = stack.copy() - jumped = True - break - - case name if ( - name in _JUMP_IF_NONE_OPNAMES or name in _JUMP_IF_NOT_NONE_OPNAMES - ): - if not stack: - raise ValueError("Stack is empty") - cond_expr = stack.pop() - cond_bool = py_exprs.Call( - py_exprs.PyObject(generic_ops.isnull_op), - (cond_expr,), - ) - - dest = inst.argval - next_offset = next_offsets.get(inst.offset) - - if opname in _JUMP_IF_NONE_OPNAMES: - not_cond_bool = py_exprs.Call( - py_exprs.PyObject(operator.not_), (cond_bool,) - ) - edge_conditions[(offset, dest)] = py_exprs.Call( - py_exprs.PyObject(operator.and_), - (reach_cond, cond_bool), - ) - edge_stacks[(offset, dest)] = stack.copy() - if next_offset is not None: - edge_conditions[(offset, next_offset)] = py_exprs.Call( - py_exprs.PyObject(operator.and_), - (reach_cond, not_cond_bool), - ) - edge_stacks[(offset, next_offset)] = stack.copy() - else: # opname in _JUMP_IF_NOT_NONE_OPNAMES - not_cond_bool = py_exprs.Call( - py_exprs.PyObject(operator.not_), (cond_bool,) - ) - edge_conditions[(offset, dest)] = py_exprs.Call( - py_exprs.PyObject(operator.and_), - (reach_cond, not_cond_bool), - ) - edge_stacks[(offset, dest)] = stack.copy() - if next_offset is not None: - edge_conditions[(offset, next_offset)] = py_exprs.Call( - py_exprs.PyObject(operator.and_), - (reach_cond, cond_bool), - ) - edge_stacks[(offset, next_offset)] = stack.copy() - jumped = True - break - - case name if name in _ALL_JUMP_OPNAMES: - raise ValueError(f"Unsupported jump opcode: {opname}") - - case _: - raise ValueError(f"Unsupported opcode: {opname}") - - if not jumped: - next_offset = next_offsets.get(block.instructions[-1].offset) - if next_offset is not None: - edge_conditions[(offset, next_offset)] = reach_cond - edge_stacks[(offset, next_offset)] = stack.copy() - - block_outputs[offset] = (stack, local_vars) - - if not returns: - raise ValueError("No return value found") - - return merge_values(returns) - - -def py_to_expression(func: Callable) -> expression.Expression: - """ - Try to convert a python function to a BigQuery expression. - - This is "best effort" - if the function contains operations that cannot - be converted to BigQuery expressions, it will raise an Exception. - """ - py_expr = _compile_bytecode_to_py_expr(func) - return py_exprs.resolve_py_exprs(py_expr) diff --git a/bigframes/core/col.py b/bigframes/core/col.py deleted file mode 100644 index 50968dfbf94..00000000000 --- a/bigframes/core/col.py +++ /dev/null @@ -1,204 +0,0 @@ -# Copyright 2026 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -from __future__ import annotations - -import dataclasses -from typing import TYPE_CHECKING, Any, Hashable, Literal - -import bigframes_vendored.pandas.core.col as pd_col -import numpy - -import bigframes.core.expression as bf_expression -import bigframes.operations as bf_ops -import bigframes.operations.aggregations as agg_ops -from bigframes.core import agg_expressions, window_spec - -if TYPE_CHECKING: - import bigframes.operations.datetimes as datetimes - import bigframes.operations.strings as strings - - -# Not to be confused with the Expression class in `bigframes.core.expressions` -# Name collision unintended -@dataclasses.dataclass(frozen=True) -class Expression: - __doc__ = pd_col.Expression.__doc__ - - _value: bf_expression.Expression - - def _apply_unary_op(self, op: bf_ops.UnaryOp) -> Expression: - return Expression(op.as_expr(self._value)) - - def _apply_unary_agg(self, op: agg_ops.UnaryAggregateOp) -> Expression: - # We probably shouldn't need to windowize here, but block apis expect pre-windowized expressions - # Later on, we will probably have col expressions in windowed context, so will need to defer windowization - # instead of automatically applying the default unbound window - agg_expr = op.as_expr(self._value) - return Expression( - agg_expressions.WindowExpression(agg_expr, window_spec.unbound()) - ) - - # alignment is purely for series compatibility, and is ignored here - def _apply_binary_op( - self, - other: Any, - op: bf_ops.BinaryOp, - alignment: Literal["outer", "left"] = "outer", - reverse: bool = False, - ): - if reverse: - return Expression(op.as_expr(_as_bf_expr(other), self._value)) - else: - return Expression(op.as_expr(self._value, _as_bf_expr(other))) - - def __add__(self, other: Any) -> Expression: - return self._apply_binary_op(other, bf_ops.add_op) - - def __radd__(self, other: Any) -> Expression: - return self._apply_binary_op(other, bf_ops.add_op, reverse=True) - - def __sub__(self, other: Any) -> Expression: - return self._apply_binary_op(other, bf_ops.sub_op) - - def __rsub__(self, other: Any) -> Expression: - return self._apply_binary_op(other, bf_ops.sub_op, reverse=True) - - def __mul__(self, other: Any) -> Expression: - return self._apply_binary_op(other, bf_ops.mul_op) - - def __rmul__(self, other: Any) -> Expression: - return self._apply_binary_op(other, bf_ops.mul_op, reverse=True) - - def __truediv__(self, other: Any) -> Expression: - return self._apply_binary_op(other, bf_ops.div_op) - - def __rtruediv__(self, other: Any) -> Expression: - return self._apply_binary_op(other, bf_ops.div_op, reverse=True) - - def __floordiv__(self, other: Any) -> Expression: - return self._apply_binary_op(other, bf_ops.floordiv_op) - - def __rfloordiv__(self, other: Any) -> Expression: - return self._apply_binary_op(other, bf_ops.floordiv_op, reverse=True) - - def __ge__(self, other: Any) -> Expression: - return self._apply_binary_op(other, bf_ops.ge_op) - - def __gt__(self, other: Any) -> Expression: - return self._apply_binary_op(other, bf_ops.gt_op) - - def __le__(self, other: Any) -> Expression: - return self._apply_binary_op(other, bf_ops.le_op) - - def __lt__(self, other: Any) -> Expression: - return self._apply_binary_op(other, bf_ops.lt_op) - - def __eq__(self, other: object) -> Expression: # type: ignore - return self._apply_binary_op(other, bf_ops.eq_op) - - def __ne__(self, other: object) -> Expression: # type: ignore - return self._apply_binary_op(other, bf_ops.ne_op) - - def __mod__(self, other: Any) -> Expression: - return self._apply_binary_op(other, bf_ops.mod_op) - - def __rmod__(self, other: Any) -> Expression: - return self._apply_binary_op(other, bf_ops.mod_op, reverse=True) - - def __and__(self, other: Any) -> Expression: - return self._apply_binary_op(other, bf_ops.and_op) - - def __rand__(self, other: Any) -> Expression: - return self._apply_binary_op(other, bf_ops.and_op, reverse=True) - - def __or__(self, other: Any) -> Expression: - return self._apply_binary_op(other, bf_ops.or_op) - - def __ror__(self, other: Any) -> Expression: - return self._apply_binary_op(other, bf_ops.or_op, reverse=True) - - def __xor__(self, other: Any) -> Expression: - return self._apply_binary_op(other, bf_ops.xor_op) - - def __rxor__(self, other: Any) -> Expression: - return self._apply_binary_op(other, bf_ops.xor_op, reverse=True) - - def __invert__(self) -> Expression: - return self._apply_unary_op(bf_ops.invert_op) - - def sum(self) -> Expression: - return self._apply_unary_agg(agg_ops.sum_op) - - def mean(self) -> Expression: - return self._apply_unary_agg(agg_ops.mean_op) - - def var(self) -> Expression: - return self._apply_unary_agg(agg_ops.var_op) - - def std(self) -> Expression: - return self._apply_unary_agg(agg_ops.std_op) - - def min(self) -> Expression: - return self._apply_unary_agg(agg_ops.min_op) - - def max(self) -> Expression: - return self._apply_unary_agg(agg_ops.max_op) - - @property - def dt(self) -> datetimes.DatetimeSimpleMethods: - import bigframes.operations.datetimes as datetimes - - return datetimes.DatetimeSimpleMethods(self) - - def __array_ufunc__( - self, ufunc: numpy.ufunc, method: str, *inputs, **kwargs - ) -> Expression: - """Used to support numpy ufuncs. - See: https://numpy.org/doc/stable/reference/ufuncs.html - """ - # Only __call__ supported with zero arguments - if method != "__call__" or len(inputs) > 2 or len(kwargs) > 0: - return NotImplemented - - if len(inputs) == 1 and ufunc in bf_ops.NUMPY_TO_OP: - op = bf_ops.NUMPY_TO_OP[ufunc] - return Expression(op.as_expr(self._value)) - if len(inputs) == 2 and ufunc in bf_ops.NUMPY_TO_BINOP: - binop = bf_ops.NUMPY_TO_BINOP[ufunc] - if inputs[0] is self: - return Expression(binop.as_expr(self._value, _as_bf_expr(inputs[1]))) - else: - return Expression(binop.as_expr(_as_bf_expr(inputs[0]), self._value)) - - return NotImplemented - - # keep this last as str declaration can shadow builtins.str - @property - def str(self) -> strings.StringMethods: - import bigframes.operations.strings as strings - - return strings.StringMethods(self) - - -def _as_bf_expr(arg: Any) -> bf_expression.Expression: - if isinstance(arg, Expression): - return arg._value - return bf_expression.const(arg) - - -def col(col_name: Hashable) -> Expression: - return Expression(bf_expression.free_var(col_name)) - - -col.__doc__ = pd_col.col.__doc__ diff --git a/bigframes/core/compile/__init__.py b/bigframes/core/compile/__init__.py index c1b9c5d9022..c86f4463dc0 100644 --- a/bigframes/core/compile/__init__.py +++ b/bigframes/core/compile/__init__.py @@ -11,32 +11,11 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -from __future__ import annotations - -from typing import Literal - -from bigframes.core.compile.api import test_only_ibis_inferred_schema -from bigframes.core.compile.configs import CompileRequest, CompileResult - - -def compile_sql( - request: CompileRequest, - compiler_name: Literal["sqlglot", "ibis"] = "sqlglot", -) -> CompileResult: - """Compiles a BigFrameNode according to the request into SQL.""" - if compiler_name == "sqlglot": - import bigframes.core.compile.sqlglot.compiler as sqlglot_compiler - - return sqlglot_compiler.compile_sql(request) - else: - import bigframes.core.compile.ibis_compiler.ibis_compiler as ibis_compiler - - return ibis_compiler.compile_sql(request) +from bigframes.core.compile.compiled import CompiledArrayValue +from bigframes.core.compile.compiler import compile_node __all__ = [ - "test_only_ibis_inferred_schema", - "CompileRequest", - "CompileResult", - "compile_sql", + "compile_node", + "CompiledArrayValue", ] diff --git a/bigframes/core/compile/api.py b/bigframes/core/compile/api.py deleted file mode 100644 index 82672fc95b5..00000000000 --- a/bigframes/core/compile/api.py +++ /dev/null @@ -1,35 +0,0 @@ -# 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. -# 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 typing import TYPE_CHECKING - -if TYPE_CHECKING: - import bigframes.core.nodes - - -def test_only_ibis_inferred_schema(node: bigframes.core.nodes.BigFrameNode): - """Use only for testing paths to ensure ibis inferred schema does not diverge from bigframes inferred schema.""" - import bigframes.core.rewrite - import bigframes.core.schema - from bigframes.core.compile.ibis_compiler import ibis_compiler - - node = ibis_compiler._replace_unsupported_ops(node) - node = bigframes.core.rewrite.bake_order(node) - ir = ibis_compiler.compile_node(node) - items = tuple( - bigframes.core.schema.SchemaItem(name, ir.get_column_type(ibis_id)) - for name, ibis_id in zip(node.schema.names, ir.column_ids) - ) - return bigframes.core.schema.ArraySchema(items) diff --git a/bigframes/core/compile/compiled.py b/bigframes/core/compile/compiled.py index fea94f6e6ed..1134f1aab01 100644 --- a/bigframes/core/compile/compiled.py +++ b/bigframes/core/compile/compiled.py @@ -13,106 +13,168 @@ # limitations under the License. from __future__ import annotations -import itertools +import functools +import math +import textwrap import typing -from typing import Literal, Optional, Sequence - -import bigframes_vendored.ibis -import bigframes_vendored.ibis.backends.bigquery.backend as ibis_bigquery -import bigframes_vendored.ibis.common.deferred as ibis_deferred # type: ignore -import bigframes_vendored.ibis.expr.datatypes as ibis_dtypes -import bigframes_vendored.ibis.expr.operations as ibis_ops -import bigframes_vendored.ibis.expr.types as ibis_types -import bigframes_vendored.sqlglot.expressions as sge -import pyarrow as pa -from google.cloud import bigquery - -import bigframes.core.agg_expressions as ex_types -import bigframes.core.compile.ibis_compiler.aggregate_compiler as agg_compiler -import bigframes.core.compile.ibis_compiler.scalar_op_compiler as op_compilers -import bigframes.core.compile.ibis_types -import bigframes.core.expression as ex -import bigframes.core.sql -import bigframes.dtypes -from bigframes.core import agg_expressions, rewrite -from bigframes.core.ordering import OrderingExpression +from typing import Collection, Iterable, Literal, Optional, Sequence + +import ibis +import ibis.backends.bigquery as ibis_bigquery +import ibis.expr.datatypes as ibis_dtypes +import ibis.expr.types as ibis_types +import pandas + +import bigframes.constants as constants +import bigframes.core.guid +from bigframes.core.ordering import ( + encode_order_string, + ExpressionOrdering, + IntegerEncoding, + OrderingColumnReference, + reencode_order_string, + StringEncoding, +) +import bigframes.core.utils as utils from bigframes.core.window_spec import WindowSpec +import bigframes.dtypes +import bigframes.operations as ops +import bigframes.operations.aggregations as agg_ops + +ORDER_ID_COLUMN = "bigframes_ordering_id" +PREDICATE_COLUMN = "bigframes_predicate" -op_compiler = op_compilers.scalar_op_compiler +class CompiledArrayValue: + """Immutable BigQuery DataFrames expression tree. + + Note: Usage of this class is considered to be private and subject to change + at any time. + + This class is a wrapper around Ibis expressions. Its purpose is to defer + Ibis projection operations to keep generated SQL small and correct when + mixing and matching columns from different versions of a DataFrame. + + Args: + table: An Ibis table expression. + columns: Ibis value expressions that can be projected as columns. + hidden_ordering_columns: Ibis value expressions to store ordering. + ordering: An ordering property of the data frame. + predicates: A list of filters on the data frame. + """ -# Ibis Implementations -class UnorderedIR: def __init__( self, table: ibis_types.Table, columns: Sequence[ibis_types.Value], + hidden_ordering_columns: Optional[Sequence[ibis_types.Value]] = None, + ordering: ExpressionOrdering = ExpressionOrdering(), + predicates: Optional[Collection[ibis_types.BooleanValue]] = None, ): self._table = table + self._predicates = tuple(predicates) if predicates is not None else () + # TODO: Validate ordering + if not ordering.total_ordering_columns: + raise ValueError("Must have total ordering defined by one or more columns") + self._ordering = ordering # Allow creating a DataFrame directly from an Ibis table expression. # TODO(swast): Validate that each column references the same table (or # no table for literal values). - self._columns = tuple( - column.resolve(table) # type:ignore - # TODO(https://github.com/ibis-project/ibis/issues/7613): use - # public API to refer to Deferred type. - if isinstance(column, ibis_deferred.Deferred) - else column - for column in columns + self._columns = tuple(columns) + + # Meta columns store ordering, or other data that doesn't correspond to dataframe columns + self._hidden_ordering_columns = ( + tuple(hidden_ordering_columns) + if hidden_ordering_columns is not None + else () ) + # To allow for more efficient lookup by column name, create a # dictionary mapping names to column values. self._column_names = {column.get_name(): column for column in self._columns} + self._hidden_ordering_column_names = { + column.get_name(): column for column in self._hidden_ordering_columns + } + ### Validation + value_col_ids = self._column_names.keys() + hidden_col_ids = self._hidden_ordering_column_names.keys() - def to_sql( - self, - order_by: Sequence[OrderingExpression], - limit: Optional[int], - selections: tuple[tuple[ex.DerefOp, str], ...], - ) -> str: - ibis_table = self._to_ibis_expr() - # This set of output transforms maybe should be its own output node?? + all_columns = value_col_ids | hidden_col_ids + ordering_valid = all( + col.column_id in all_columns for col in ordering.all_ordering_columns + ) + if value_col_ids & hidden_col_ids: + raise ValueError( + f"Keys in both hidden and exposed list: {value_col_ids & hidden_col_ids}" + ) + if not ordering_valid: + raise ValueError(f"Illegal ordering keys: {ordering.all_ordering_columns}") - selection_strings = tuple((ref.id.sql, name) for ref, name in selections) + @classmethod + def mem_expr_from_pandas( + cls, + pd_df: pandas.DataFrame, + ) -> CompiledArrayValue: + """ + Builds an in-memory only (SQL only) expr from a pandas dataframe. + """ + # We can't include any hidden columns in the ArrayValue constructor, so + # grab the column names before we add the hidden ordering column. + column_names = [str(column) for column in pd_df.columns] + # Make sure column names are all strings. + pd_df = pd_df.set_axis(column_names, axis="columns") + pd_df = pd_df.assign(**{ORDER_ID_COLUMN: range(len(pd_df))}) - names_preserved = tuple(name for _, name in selections) == tuple( - self.column_ids - ) - is_noop_selection = ( - all((i[0] == i[1] for i in selection_strings)) and names_preserved - ) + # ibis memtable cannot handle NA, must convert to None + pd_df = pd_df.astype("object") # type: ignore + pd_df = pd_df.where(pandas.notnull(pd_df), None) - if order_by or limit or not is_noop_selection: - # selections are (ref.id.sql, name) where ref.id.sql is escaped identifier - to_select = [ - sge.Alias( - this=sge.to_identifier(src, quoted=True), - alias=sge.to_identifier(alias, quoted=True), + # NULL type isn't valid in BigQuery, so retry with an explicit schema in these cases. + keys_memtable = ibis.memtable(pd_df) + schema = keys_memtable.schema() + new_schema = [] + for column_index, column in enumerate(schema): + if column == ORDER_ID_COLUMN: + new_type: ibis_dtypes.DataType = ibis_dtypes.int64 + else: + column_type = schema[column] + # The autodetected type might not be one we can support, such + # as NULL type for empty rows, so convert to a type we do + # support. + new_type = bigframes.dtypes.bigframes_dtype_to_ibis_dtype( + bigframes.dtypes.ibis_dtype_to_bigframes_dtype(column_type) ) - if src != alias - else sge.to_identifier(src, quoted=True) - for src, alias in selection_strings - ] - # Use string formatting for FROM clause to avoid re-parsing potentially complex SQL (like ARRAY>) - # that sqlglot might not handle perfectly when parsing BigQuery dialect strings. - select_sql = sge.Select().select(*to_select).sql(dialect="bigquery") - ibis_sql = ibis_bigquery.Backend().compile(ibis_table) - sql = f"{select_sql} FROM ({ibis_sql}) AS `t`" - - # Single row frames may not have any ordering columns - if len(order_by) > 0: - order_by_clause = bigframes.core.sql.ordering_clause(order_by) - sql += f"\n{order_by_clause}" - if limit is not None: - if not isinstance(limit, int): - raise TypeError(f"Limit param: {limit} must be an int.") - sql += f"\nLIMIT {limit}" - else: - sql = ibis_bigquery.Backend().compile(ibis_table) - return typing.cast(str, sql) + # TODO(swast): Ibis memtable doesn't use backticks in struct + # field names, so spaces and other characters aren't allowed in + # the memtable context. Blocked by + # https://github.com/ibis-project/ibis/issues/7187 + column = f"col_{column_index}" + new_schema.append((column, new_type)) + + # must set non-null column labels. these are not the user-facing labels + pd_df = pd_df.set_axis( + [column for column, _ in new_schema], + axis="columns", + ) + keys_memtable = ibis.memtable(pd_df, schema=ibis.schema(new_schema)) + + return cls( + keys_memtable, + columns=[ + keys_memtable[f"col_{column_index}"].name(column) + for column_index, column in enumerate(column_names) + ], + ordering=ExpressionOrdering( + ordering_value_columns=tuple( + [OrderingColumnReference(ORDER_ID_COLUMN)] + ), + total_ordering_columns=frozenset([ORDER_ID_COLUMN]), + ), + hidden_ordering_columns=(keys_memtable[ORDER_ID_COLUMN],), + ) @property - def columns(self) -> tuple[ibis_types.Value, ...]: + def columns(self) -> typing.Tuple[ibis_types.Value, ...]: return self._columns @property @@ -120,39 +182,62 @@ def column_ids(self) -> typing.Sequence[str]: return tuple(self._column_names.keys()) @property - def _ibis_bindings(self) -> dict[str, ibis_types.Value]: - return {col: self._get_ibis_column(col) for col in self.column_ids} + def _hidden_column_ids(self) -> typing.Sequence[str]: + return tuple(self._hidden_ordering_column_names.keys()) - def projection( - self, - expression_id_pairs: tuple[tuple[ex.Expression, str], ...], - ) -> UnorderedIR: - """Apply an expression to the ArrayValue and assign the output to a column.""" - cannot_inline = any(expr.expensive for expr, _ in expression_id_pairs) - - bindings = {col: self._get_ibis_column(col) for col in self.column_ids} - new_values = [ - op_compiler.compile_expression(expression, bindings).name(id) - for expression, id in expression_id_pairs - ] - result = UnorderedIR(self._table, (*self._columns, *new_values)) - if cannot_inline: - return result._reproject_to_table() - else: - # Cheap ops can defer "SELECT" and inline into later ops - return result + @property + def _reduced_predicate(self) -> typing.Optional[ibis_types.BooleanValue]: + """Returns the frame's predicates as an equivalent boolean value, useful where a single predicate value is preferred.""" + return ( + _reduce_predicate_list(self._predicates).name(PREDICATE_COLUMN) + if self._predicates + else None + ) - def selection( - self, - input_output_pairs: tuple[tuple[ex.DerefOp, str], ...], - ) -> UnorderedIR: - """Apply an expression to the ArrayValue and assign the output to a column.""" - bindings = {col: self._get_ibis_column(col) for col in self.column_ids} - values = [ - op_compiler.compile_expression(input, bindings).name(id) - for input, id in input_output_pairs + @property + def _ibis_order(self) -> Sequence[ibis_types.Value]: + """Returns a sequence of ibis values which can be directly used to order a table expression. Has direction modifiers applied.""" + return _convert_ordering_to_table_values( + {**self._column_names, **self._hidden_ordering_column_names}, + self._ordering.all_ordering_columns, + ) + + def builder(self) -> ArrayValueBuilder: + """Creates a mutable builder for expressions.""" + # Since ArrayValue is intended to be immutable (immutability offers + # potential opportunities for caching, though we might need to introduce + # more node types for that to be useful), we create a builder class. + return ArrayValueBuilder( + self._table, + columns=self._columns, + hidden_ordering_columns=self._hidden_ordering_columns, + ordering=self._ordering, + predicates=self._predicates, + ) + + def drop_columns(self, columns: Iterable[str]) -> CompiledArrayValue: + # Must generate offsets if we are dropping a column that ordering depends on + expr = self + for ordering_column in set(columns).intersection( + [col.column_id for col in self._ordering.ordering_value_columns] + ): + expr = self._hide_column(ordering_column) + + expr_builder = expr.builder() + remain_cols = [ + column for column in expr.columns if column.get_name() not in columns ] - return UnorderedIR(self._table, tuple(values)) + expr_builder.columns = remain_cols + return expr_builder.build() + + def get_column_type(self, key: str) -> bigframes.dtypes.Dtype: + ibis_type = typing.cast( + bigframes.dtypes.IbisDtype, self._get_any_column(key).type() + ) + return typing.cast( + bigframes.dtypes.Dtype, + bigframes.dtypes.ibis_dtype_to_bigframes_dtype(ibis_type), + ) def _get_ibis_column(self, key: str) -> ibis_types.Value: """Gets the Ibis expression for a given column.""" @@ -162,109 +247,549 @@ def _get_ibis_column(self, key: str) -> ibis_types.Value: ) return typing.cast(ibis_types.Value, self._column_names[key]) - def get_column_type(self, key: str) -> bigframes.dtypes.Dtype: - ibis_type = typing.cast( - bigframes.core.compile.ibis_types.IbisDtype, - self._get_ibis_column(key).type(), - ) - return typing.cast( - bigframes.dtypes.Dtype, - bigframes.core.compile.ibis_types.ibis_dtype_to_bigframes_dtype(ibis_type), + def _get_any_column(self, key: str) -> ibis_types.Value: + """Gets the Ibis expression for a given column. Will also get hidden columns.""" + all_columns = {**self._column_names, **self._hidden_ordering_column_names} + if key not in all_columns.keys(): + raise ValueError( + "Column name {} not in set of values: {}".format( + key, all_columns.keys() + ) + ) + return typing.cast(ibis_types.Value, all_columns[key]) + + def _get_hidden_ordering_column(self, key: str) -> ibis_types.Column: + """Gets the Ibis expression for a given hidden column.""" + if key not in self._hidden_ordering_column_names.keys(): + raise ValueError( + "Column name {} not in set of values: {}".format( + key, self._hidden_ordering_column_names.keys() + ) + ) + return typing.cast(ibis_types.Column, self._hidden_ordering_column_names[key]) + + def filter(self, predicate_id: str, keep_null: bool = False) -> CompiledArrayValue: + """Filter the table on a given expression, the predicate must be a boolean series aligned with the table expression.""" + condition = typing.cast( + ibis_types.BooleanValue, self._get_ibis_column(predicate_id) ) + if keep_null: + condition = typing.cast( + ibis_types.BooleanValue, + condition.fillna( + typing.cast(ibis_types.BooleanScalar, ibis_types.literal(True)) + ), + ) + return self._filter(condition) - def _to_ibis_expr( - self, - *, - fraction: Optional[float] = None, - ): + def _filter(self, predicate_value: ibis_types.BooleanValue) -> CompiledArrayValue: + """Filter the table on a given expression, the predicate must be a boolean series aligned with the table expression.""" + expr = self.builder() + expr.ordering = expr.ordering.with_non_sequential() + expr.predicates = [*self._predicates, predicate_value] + return expr.build() + + def order_by( + self, by: Sequence[OrderingColumnReference], stable: bool = False + ) -> CompiledArrayValue: + expr_builder = self.builder() + expr_builder.ordering = self._ordering.with_ordering_columns(by, stable=stable) + return expr_builder.build() + + def reversed(self) -> CompiledArrayValue: + expr_builder = self.builder() + expr_builder.ordering = self._ordering.with_reverse() + return expr_builder.build() + + def _uniform_sampling(self, fraction: float) -> CompiledArrayValue: + """Sampling the table on given fraction. + + .. warning:: + The row numbers of result is non-deterministic, avoid to use. """ - Creates an Ibis table expression representing the DataFrame. + table = self._to_ibis_expr( + "unordered", expose_hidden_cols=True, fraction=fraction + ) + columns = [table[column_name] for column_name in self._column_names] + hidden_ordering_columns = [ + table[column_name] for column_name in self._hidden_ordering_column_names + ] + return CompiledArrayValue( + table, + columns=columns, + hidden_ordering_columns=hidden_ordering_columns, + ordering=self._ordering, + ) - Args: - expose_hidden_cols: - If True, include the hidden ordering columns in the results. + @property + def _offsets(self) -> ibis_types.IntegerColumn: + if not self._ordering.is_sequential: + raise ValueError( + "Expression does not have offsets. Generate them first using project_offsets." + ) + if not self._ordering.total_order_col: + raise ValueError( + "Ordering is invalid. Marked as sequential but no total order columns." + ) + column = self._get_any_column(self._ordering.total_order_col.column_id) + return typing.cast(ibis_types.IntegerColumn, column) - Returns: - An ibis expression representing the data help by the ArrayValue object. + def _project_offsets(self) -> CompiledArrayValue: + """Create a new expression that contains offsets. Should only be executed when offsets are needed for an operations. Has no effect on expression semantics.""" + if self._ordering.is_sequential: + return self + # TODO(tbergeron): Enforce total ordering + table = self._to_ibis_expr( + ordering_mode="offset_col", order_col_name=ORDER_ID_COLUMN + ) + columns = [table[column_name] for column_name in self._column_names] + ordering = ExpressionOrdering( + ordering_value_columns=tuple([OrderingColumnReference(ORDER_ID_COLUMN)]), + total_ordering_columns=frozenset([ORDER_ID_COLUMN]), + integer_encoding=IntegerEncoding(True, is_sequential=True), + ) + return CompiledArrayValue( + table, + columns=columns, + hidden_ordering_columns=[table[ORDER_ID_COLUMN]], + ordering=ordering, + ) + + def _hide_column(self, column_id) -> CompiledArrayValue: + """Pushes columns to hidden columns list. Used to hide ordering columns that have been dropped or destructively mutated.""" + expr_builder = self.builder() + # Need to rename column as caller might be creating a new row with the same name but different values. + # Can avoid this if don't allow callers to determine ids and instead generate unique ones in this class. + new_name = bigframes.core.guid.generate_guid(prefix="bigframes_hidden_") + expr_builder.hidden_ordering_columns = [ + *self._hidden_ordering_columns, + self._get_ibis_column(column_id).name(new_name), + ] + expr_builder.ordering = self._ordering.with_column_remap({column_id: new_name}) + return expr_builder.build() + + def promote_offsets(self, col_id: str) -> CompiledArrayValue: """ - # Special case for empty tables, since we can't create an empty - # projection. - if not self._columns: - return self._table.select([bigframes_vendored.ibis.literal(1)]) + Convenience function to promote copy of column offsets to a value column. Can be used to reset index. + """ + # Special case: offsets already exist + ordering = self._ordering - table = self._table.select(self._columns) - if fraction is not None: - table = table.filter( - bigframes_vendored.ibis.random() < ibis_types.literal(fraction) + if (not ordering.is_sequential) or (not ordering.total_order_col): + return self._project_offsets().promote_offsets(col_id) + expr_builder = self.builder() + expr_builder.columns = [ + self._get_any_column(ordering.total_order_col.column_id).name(col_id), + *self.columns, + ] + return expr_builder.build() + + def select_columns(self, column_ids: typing.Sequence[str]) -> CompiledArrayValue: + """Creates a new expression based on this expression with new columns.""" + columns = [self._get_ibis_column(col_id) for col_id in column_ids] + expr = self + for ordering_column in set(self.column_ids).intersection( + [col_ref.column_id for col_ref in self._ordering.ordering_value_columns] + ): + # Need to hide ordering columns that are being dropped. Alternatively, could project offsets + expr = expr._hide_column(ordering_column) + builder = expr.builder() + builder.columns = list(columns) + new_expr = builder.build() + return new_expr + + def concat(self, other: typing.Sequence[CompiledArrayValue]) -> CompiledArrayValue: + """Append together multiple ArrayValue objects.""" + if len(other) == 0: + return self + tables = [] + prefix_base = 10 + prefix_size = math.ceil(math.log(len(other) + 1, prefix_base)) + # Must normalize all ids to the same encoding size + max_encoding_size = max( + self._ordering.string_encoding.length, + *[expression._ordering.string_encoding.length for expression in other], + ) + for i, expr in enumerate([self, *other]): + ordering_prefix = str(i).zfill(prefix_size) + table = expr._to_ibis_expr( + ordering_mode="string_encoded", order_col_name=ORDER_ID_COLUMN ) - return table + # Rename the value columns based on horizontal offset before applying union. + table = table.select( + [ + table[col].name(f"column_{i}") + if col != ORDER_ID_COLUMN + else ( + ordering_prefix + + reencode_order_string( + table[ORDER_ID_COLUMN], max_encoding_size + ) + ).name(ORDER_ID_COLUMN) + for i, col in enumerate(table.columns) + ] + ) + tables.append(table) + combined_table = ibis.union(*tables) + ordering = ExpressionOrdering( + ordering_value_columns=tuple([OrderingColumnReference(ORDER_ID_COLUMN)]), + total_ordering_columns=frozenset([ORDER_ID_COLUMN]), + string_encoding=StringEncoding(True, prefix_size + max_encoding_size), + ) + return CompiledArrayValue( + combined_table, + columns=[ + combined_table[col] + for col in combined_table.columns + if col != ORDER_ID_COLUMN + ], + hidden_ordering_columns=[combined_table[ORDER_ID_COLUMN]], + ordering=ordering, + ) - def filter(self, predicate: ex.Expression) -> UnorderedIR: - table = self._to_ibis_expr() - condition = op_compiler.compile_expression(predicate, table) - table = table.filter(condition) - return UnorderedIR( - table, tuple(table[column_name] for column_name in self._column_names) + def project_unary_op( + self, column_name: str, op: ops.UnaryOp, output_name=None + ) -> CompiledArrayValue: + """Creates a new expression based on this expression with unary operation applied to one column.""" + value = op._as_ibis(self._get_ibis_column(column_name)).name( + output_name or column_name ) + return self._set_or_replace_by_id(output_name or column_name, value) + + def project_binary_op( + self, + left_column_id: str, + right_column_id: str, + op: ops.BinaryOp, + output_column_id: str, + ) -> CompiledArrayValue: + """Creates a new expression based on this expression with binary operation applied to two columns.""" + value = op( + self._get_ibis_column(left_column_id), + self._get_ibis_column(right_column_id), + ).name(output_column_id) + return self._set_or_replace_by_id(output_column_id, value) + + def project_ternary_op( + self, + col_id_1: str, + col_id_2: str, + col_id_3: str, + op: ops.TernaryOp, + output_column_id: str, + ) -> CompiledArrayValue: + """Creates a new expression based on this expression with ternary operation applied to three columns.""" + value = op( + self._get_ibis_column(col_id_1), + self._get_ibis_column(col_id_2), + self._get_ibis_column(col_id_3), + ).name(output_column_id) + return self._set_or_replace_by_id(output_column_id, value) def aggregate( self, - aggregations: typing.Sequence[tuple[ex_types.Aggregation, str]], - by_column_ids: typing.Sequence[ex.DerefOp] = (), - order_by: typing.Sequence[OrderingExpression] = (), - ) -> UnorderedIR: + aggregations: typing.Sequence[typing.Tuple[str, agg_ops.AggregateOp, str]], + by_column_ids: typing.Sequence[str] = (), + dropna: bool = True, + ) -> CompiledArrayValue: """ Apply aggregations to the expression. Arguments: aggregations: input_column_id, operation, output_column_id tuples - by_column_ids: column ids of the aggregation key, this is preserved through - the transform + by_column_id: column id of the aggregation key, this is preserved through the transform dropna: whether null keys should be dropped - Returns: - OrderedIR: the grouping key is a unique-valued column and has ordering - information. """ - table = self._to_ibis_expr() - bindings = {col: table[col] for col in self.column_ids} + table = self._to_ibis_expr("unordered") stats = { - col_out: agg_compiler.compile_aggregate( - aggregate, - bindings, - order_by=op_compiler._convert_row_ordering_to_table_values( - table, order_by - ), - ) - for aggregate, col_out in aggregations + col_out: agg_op._as_ibis(table[col_in]) + for col_in, agg_op, col_out in aggregations } if by_column_ids: - result = table.group_by((ref.id.sql for ref in by_column_ids)).aggregate( - **stats - ) - return UnorderedIR( - result, columns=tuple(result[key] for key in result.columns) + result = table.group_by(by_column_ids).aggregate(**stats) + # Must have deterministic ordering, so order by the unique "by" column + ordering = ExpressionOrdering( + tuple( + [ + OrderingColumnReference(column_id=column_id) + for column_id in by_column_ids + ] + ), + total_ordering_columns=frozenset(by_column_ids), ) + columns = tuple(result[key] for key in result.columns) + expr = CompiledArrayValue(result, columns=columns, ordering=ordering) + if dropna: + for column_id in by_column_ids: + expr = expr._filter( + ops.notnull_op._as_ibis(expr._get_ibis_column(column_id)) + ) + # Can maybe remove this as Ordering id is redundant as by_column is unique after aggregation + return expr._project_offsets() else: - result = table.aggregate(**stats) - return UnorderedIR( + aggregates = {**stats, ORDER_ID_COLUMN: ibis_types.literal(0)} + result = table.aggregate(**aggregates) + # Ordering is irrelevant for single-row output, but set ordering id regardless as other ops(join etc.) expect it. + ordering = ExpressionOrdering( + ordering_value_columns=tuple( + [OrderingColumnReference(ORDER_ID_COLUMN)] + ), + total_ordering_columns=frozenset([ORDER_ID_COLUMN]), + integer_encoding=IntegerEncoding(is_encoded=True, is_sequential=True), + ) + return CompiledArrayValue( result, columns=[result[col_id] for col_id in [*stats.keys()]], + hidden_ordering_columns=[result[ORDER_ID_COLUMN]], + ordering=ordering, ) - def _uniform_sampling(self, fraction: float) -> UnorderedIR: - """Sampling the table on given fraction. + def corr_aggregate( + self, corr_aggregations: typing.Sequence[typing.Tuple[str, str, str]] + ) -> CompiledArrayValue: + """ + Get correlations between each lef_column_id and right_column_id, stored in the respective output_column_id. + This uses BigQuery's CORR under the hood, and thus only Pearson's method is used. + Arguments: + corr_aggregations: left_column_id, right_column_id, output_column_id tuples + """ + table = self._to_ibis_expr("unordered") + stats = { + col_out: table[col_left].corr(table[col_right], how="pop") + for col_left, col_right, col_out in corr_aggregations + } + aggregates = {**stats, ORDER_ID_COLUMN: ibis_types.literal(0)} + result = table.aggregate(**aggregates) + # Ordering is irrelevant for single-row output, but set ordering id regardless as other ops(join etc.) expect it. + ordering = ExpressionOrdering( + ordering_value_columns=tuple([OrderingColumnReference(ORDER_ID_COLUMN)]), + total_ordering_columns=frozenset([ORDER_ID_COLUMN]), + integer_encoding=IntegerEncoding(is_encoded=True, is_sequential=True), + ) + return CompiledArrayValue( + result, + columns=[result[col_id] for col_id in [*stats.keys()]], + hidden_ordering_columns=[result[ORDER_ID_COLUMN]], + ordering=ordering, + ) - .. warning:: - The row numbers of result is non-deterministic, avoid to use. + def project_window_op( + self, + column_name: str, + op: agg_ops.WindowOp, + window_spec: WindowSpec, + output_name=None, + *, + never_skip_nulls=False, + skip_reproject_unsafe: bool = False, + ) -> CompiledArrayValue: """ - table = self._to_ibis_expr(fraction=fraction) - columns = [table[column_name] for column_name in self._column_names] - return UnorderedIR( - table, - columns=columns, + Creates a new expression based on this expression with unary operation applied to one column. + column_name: the id of the input column present in the expression + op: the windowable operator to apply to the input column + window_spec: a specification of the window over which to apply the operator + output_name: the id to assign to the output of the operator, by default will replace input col if distinct output id not provided + never_skip_nulls: will disable null skipping for operators that would otherwise do so + skip_reproject_unsafe: skips the reprojection step, can be used when performing many non-dependent window operations, user responsible for not nesting window expressions, or using outputs as join, filter or aggregation keys before a reprojection + """ + column = typing.cast(ibis_types.Column, self._get_ibis_column(column_name)) + window = self._ibis_window_from_spec(window_spec, allow_ties=op.handles_ties) + + window_op = op._as_ibis(column, window) + + clauses = [] + if op.skips_nulls and not never_skip_nulls: + clauses.append((column.isnull(), ibis.NA)) + if window_spec.min_periods: + if op.skips_nulls: + # Most operations do not count NULL values towards min_periods + observation_count = agg_ops.count_op._as_ibis(column, window) + else: + # Operations like count treat even NULLs as valid observations for the sake of min_periods + # notnull is just used to convert null values to non-null (FALSE) values to be counted + denulled_value = typing.cast(ibis_types.BooleanColumn, column.notnull()) + observation_count = agg_ops.count_op._as_ibis(denulled_value, window) + clauses.append( + ( + observation_count < ibis_types.literal(window_spec.min_periods), + ibis.NA, + ) + ) + if clauses: + case_statement = ibis.case() + for clause in clauses: + case_statement = case_statement.when(clause[0], clause[1]) + case_statement = case_statement.else_(window_op).end() + window_op = case_statement + + result = self._set_or_replace_by_id(output_name or column_name, window_op) + # TODO(tbergeron): Automatically track analytic expression usage and defer reprojection until required for valid query generation. + return result._reproject_to_table() if not skip_reproject_unsafe else result + + def to_sql( + self, + offset_column: typing.Optional[str] = None, + col_id_overrides: typing.Mapping[str, str] = {}, + sorted: bool = False, + ) -> str: + offsets_id = offset_column or ORDER_ID_COLUMN + + sql = ibis_bigquery.Backend().compile( + self._to_ibis_expr( + ordering_mode="offset_col" + if (offset_column or sorted) + else "unordered", + order_col_name=offsets_id, + col_id_overrides=col_id_overrides, + ) + ) + if sorted: + sql = textwrap.dedent( + f""" + SELECT * EXCEPT (`{offsets_id}`) + FROM ({sql}) + ORDER BY `{offsets_id}` + """ + ) + return typing.cast(str, sql) + + def _to_ibis_expr( + self, + ordering_mode: Literal["string_encoded", "offset_col", "unordered"], + order_col_name: Optional[str] = ORDER_ID_COLUMN, + expose_hidden_cols: bool = False, + fraction: Optional[float] = None, + col_id_overrides: typing.Mapping[str, str] = {}, + ): + """ + Creates an Ibis table expression representing the DataFrame. + + ArrayValue objects are sorted, so the following options are available + to reflect this in the ibis expression. + + * "offset_col": Zero-based offsets are generated as a column, this will + not sort the rows however. + * "string_encoded": An ordered string column is provided in output table. + * "unordered": No ordering information will be provided in output. Only + value columns are projected. + + For offset or ordered column, order_col_name can be used to assign the + output label for the ordering column. If none is specified, the default + column name will be 'bigframes_ordering_id' + + Args: + ordering_mode: + How to construct the Ibis expression from the ArrayValue. See + above for details. + order_col_name: + If the ordering mode outputs a single ordering or offsets + column, use this as the column name. + expose_hidden_cols: + If True, include the hidden ordering columns in the results. + Only compatible with `order_by` and `unordered` + ``ordering_mode``. + col_id_overrides: + overrides the column ids for the result + Returns: + An ibis expression representing the data help by the ArrayValue object. + """ + assert ordering_mode in ( + "string_encoded", + "offset_col", + "unordered", ) + if expose_hidden_cols and ordering_mode in ("ordered_col", "offset_col"): + raise ValueError( + f"Cannot expose hidden ordering columns with ordering_mode {ordering_mode}" + ) + + columns = list(self._columns) + columns_to_drop: list[ + str + ] = [] # Ordering/Filtering columns that will be dropped at end + + if self._reduced_predicate is not None: + columns.append(self._reduced_predicate) + # Usually drop predicate as it is will be all TRUE after filtering + if not expose_hidden_cols: + columns_to_drop.append(self._reduced_predicate.get_name()) + + order_columns = self._create_order_columns( + ordering_mode, order_col_name, expose_hidden_cols + ) + columns.extend(order_columns) + + # Special case for empty tables, since we can't create an empty + # projection. + if not columns: + return ibis.memtable([]) - ## Helpers - def _reproject_to_table(self) -> UnorderedIR: + # Make sure all dtypes are the "canonical" ones for BigFrames. This is + # important for operations like UNION where the schema must match. + table = self._table.select( + bigframes.dtypes.ibis_value_to_canonical_type(column) for column in columns + ) + base_table = table + if self._reduced_predicate is not None: + table = table.filter(base_table[PREDICATE_COLUMN]) + table = table.drop(*columns_to_drop) + if col_id_overrides: + table = table.relabel(col_id_overrides) + if fraction is not None: + table = table.filter(ibis.random() < ibis.literal(fraction)) + return table + + def _create_order_columns( + self, + ordering_mode: str, + order_col_name: Optional[str], + expose_hidden_cols: bool, + ) -> typing.Sequence[ibis_types.Value]: + # Generate offsets if current ordering id semantics are not sufficiently strict + if ordering_mode == "offset_col": + return (self._create_offset_column().name(order_col_name),) + elif ordering_mode == "string_encoded": + return (self._create_string_ordering_column().name(order_col_name),) + elif expose_hidden_cols: + return self._hidden_ordering_columns + return () + + def _create_offset_column(self) -> ibis_types.IntegerColumn: + if self._ordering.total_order_col and self._ordering.is_sequential: + offsets = self._get_any_column(self._ordering.total_order_col.column_id) + return typing.cast(ibis_types.IntegerColumn, offsets) + else: + window = ibis.window(order_by=self._ibis_order) + if self._predicates: + window = window.group_by(self._reduced_predicate) + offsets = ibis.row_number().over(window) + return typing.cast(ibis_types.IntegerColumn, offsets) + + def _create_string_ordering_column(self) -> ibis_types.StringColumn: + if self._ordering.total_order_col and self._ordering.is_string_encoded: + string_order_ids = self._get_any_column( + self._ordering.total_order_col.column_id + ) + return typing.cast(ibis_types.StringColumn, string_order_ids) + if ( + self._ordering.total_order_col + and self._ordering.integer_encoding.is_encoded + ): + # Special case: non-negative integer ordering id can be converted directly to string without regenerating row numbers + int_values = self._get_any_column(self._ordering.total_order_col.column_id) + return encode_order_string( + typing.cast(ibis_types.IntegerColumn, int_values), + ) + else: + # Have to build string from scratch + window = ibis.window(order_by=self._ibis_order) + if self._predicates: + window = window.group_by(self._reduced_predicate) + row_nums = typing.cast( + ibis_types.IntegerColumn, ibis.row_number().over(window) + ) + return encode_order_string(row_nums) + + def _reproject_to_table(self) -> CompiledArrayValue: """ Internal operators that projects the internal representation into a new ibis table expression where each value column is a direct @@ -272,249 +797,325 @@ def _reproject_to_table(self) -> UnorderedIR: some operations such as window operations that cannot be used recursively in projections. """ - table = self._to_ibis_expr() + table = self._to_ibis_expr( + "unordered", + expose_hidden_cols=True, + ) columns = [table[column_name] for column_name in self._column_names] - return UnorderedIR( + ordering_col_ids = [ + ref.column_id for ref in self._ordering.all_ordering_columns + ] + hidden_ordering_columns = [ + table[column_name] + for column_name in self._hidden_ordering_column_names + if column_name in ordering_col_ids + ] + return CompiledArrayValue( table, columns=columns, + hidden_ordering_columns=hidden_ordering_columns, + ordering=self._ordering, ) - @classmethod - def from_polars( - cls, pa_table: pa.Table, schema: Sequence[bigquery.SchemaField] - ) -> UnorderedIR: - """Builds an in-memory only (SQL only) expr from a pyarrow table.""" - import bigframes_vendored.ibis.backends.bigquery.datatypes as third_party_ibis_bqtypes - - # derive the ibis schema from the original pandas schema - keys_memtable = bigframes_vendored.ibis.memtable( - pa_table, - schema=third_party_ibis_bqtypes.BigQuerySchema.to_ibis(list(schema)), + def _ibis_window_from_spec(self, window_spec: WindowSpec, allow_ties: bool = False): + group_by: typing.List[ibis_types.Value] = ( + [ + typing.cast( + ibis_types.Column, _as_identity(self._get_ibis_column(column)) + ) + for column in window_spec.grouping_keys + ] + if window_spec.grouping_keys + else [] ) - return cls( - keys_memtable, - columns=tuple(keys_memtable[key] for key in keys_memtable.columns), + if self._reduced_predicate is not None: + group_by.append(self._reduced_predicate) + if window_spec.ordering: + order_by = _convert_ordering_to_table_values( + {**self._column_names, **self._hidden_ordering_column_names}, + window_spec.ordering, + ) + if not allow_ties: + # Most operator need an unambiguous ordering, so the table's total ordering is appended + order_by = tuple([*order_by, *self._ibis_order]) + elif (window_spec.following is not None) or (window_spec.preceding is not None): + # If window spec has following or preceding bounds, we need to apply an unambiguous ordering. + order_by = tuple(self._ibis_order) + else: + # Unbound grouping window. Suitable for aggregations but not for analytic function application. + order_by = None + return ibis.window( + preceding=window_spec.preceding, + following=window_spec.following, + order_by=order_by, + group_by=group_by, ) - def join( - self: UnorderedIR, - right: UnorderedIR, - conditions: tuple[tuple[str, str], ...], - type: Literal["inner", "outer", "left", "right", "cross"], + def unpivot( + self, + row_labels: typing.Sequence[typing.Hashable], + unpivot_columns: typing.Sequence[ + typing.Tuple[str, typing.Sequence[typing.Optional[str]]] + ], *, - join_nulls: bool = True, - ) -> UnorderedIR: - """Join two expressions by column equality. + passthrough_columns: typing.Sequence[str] = (), + index_col_ids: typing.Sequence[str] = ["index"], + dtype: typing.Union[ + bigframes.dtypes.Dtype, typing.Sequence[bigframes.dtypes.Dtype] + ] = pandas.Float64Dtype(), + how="left", + ) -> CompiledArrayValue: + """ + Unpivot ArrayValue columns. + + Args: + row_labels: Identifies the source of the row. Must be equal to length to source column list in unpivot_columns argument. + unpivot_columns: Mapping of column id to list of input column ids. Lists of input columns may use None. + passthrough_columns: Columns that will not be unpivoted. Column id will be preserved. + index_col_id (str): The column id to be used for the row labels. + dtype (dtype or list of dtype): Dtype to use for the unpivot columns. If list, must be equal in number to unpivot_columns. - Arguments: - left: Expression for left table to join. - left_column_ids: Column IDs (not label) to join by. - right: Expression for right table to join. - right_column_ids: Column IDs (not label) to join by. - how: The type of join to perform. - join_nulls (bool): - If True, will joins NULL keys to each other. Returns: - The joined expression. The resulting columns will be, in order, - first the coalesced join keys, then, all the left columns, and - finally, all the right columns. + ArrayValue: The unpivoted ArrayValue """ - # Shouldn't need to select the column ids explicitly, but it seems that ibis has some - # bug resolving column ids otherwise, potentially because of the "JoinChain" op - left_table = self._to_ibis_expr().select(self.column_ids) - right_table = right._to_ibis_expr().select(right.column_ids) - - join_conditions = [ - _join_condition( - left_table[left_index], right_table[right_index], nullsafe=join_nulls - ) - for left_index, right_index in conditions - ] + if how not in ("left", "right"): + raise ValueError("'how' must be 'left' or 'right'") + table = self._to_ibis_expr("unordered", expose_hidden_cols=True) + row_n = len(row_labels) + hidden_col_ids = self._hidden_ordering_column_names.keys() + if not all( + len(source_columns) == row_n for _, source_columns in unpivot_columns + ): + raise ValueError("Columns and row labels must all be same length.") - combined_table = bigframes_vendored.ibis.join( - left_table, - right_table, - predicates=join_conditions, - how=type, # type: ignore + unpivot_offset_id = bigframes.core.guid.generate_guid("unpivot_offsets_") + unpivot_table = table.cross_join( + ibis.memtable({unpivot_offset_id: range(row_n)}) ) - columns = [combined_table[col.get_name()] for col in self.columns] + [ - combined_table[col.get_name()] for col in right.columns + # Use ibis memtable to infer type of rowlabels (if possible) + # TODO: Allow caller to specify dtype + if isinstance(row_labels[0], tuple): + labels_table = ibis.memtable(row_labels) + labels_ibis_types = [ + labels_table[col].type() for col in labels_table.columns + ] + else: + labels_ibis_types = [ibis.memtable({"col": row_labels})["col"].type()] + labels_dtypes = [ + bigframes.dtypes.ibis_dtype_to_bigframes_dtype(ibis_type) + for ibis_type in labels_ibis_types ] - return UnorderedIR( - combined_table, - columns=columns, - ) - - def isin_join( - self: UnorderedIR, - right: UnorderedIR, - indicator_col: str, - conditions: tuple[str, str], - *, - join_nulls: bool = True, - ) -> UnorderedIR: - """Join two expressions by column equality. - Arguments: - left: Expression for left table to join. - right: Expression for right table to join. - conditions: Id pairs to compare - Returns: - The joined expression. - """ - left_table = self._to_ibis_expr() - right_table = right._to_ibis_expr() - if join_nulls: # nullsafe isin join must actually use "exists" subquery - new_column = ( + label_columns = [] + for label_part, (col_id, label_dtype) in enumerate( + zip(index_col_ids, labels_dtypes) + ): + # interpret as tuples even if it wasn't originally so can apply same logic for multi-column labels + labels_as_tuples = [ + label if isinstance(label, tuple) else (label,) for label in row_labels + ] + cases = [ ( - _join_condition( - left_table[conditions[0]], - right_table[conditions[1]], - nullsafe=True, - ) + i, + bigframes.dtypes.literal_to_ibis_scalar( + label_tuple[label_part], # type:ignore + force_dtype=label_dtype, # type:ignore + ), ) - .any() - .name(indicator_col) + for i, label_tuple in enumerate(labels_as_tuples) + ] + labels_value = ( + typing.cast(ibis_types.IntegerColumn, unpivot_table[unpivot_offset_id]) + .cases(cases, default=None) # type:ignore + .name(col_id) ) + label_columns.append(labels_value) - else: # Can do simpler "in" subquery - new_column = ( - (left_table[conditions[0]]) - .isin((right_table[conditions[1]])) - .fillna(False) - .name(indicator_col) + unpivot_values = [] + for j in range(len(unpivot_columns)): + col_dtype = dtype[j] if utils.is_list_like(dtype) else dtype + result_col, source_cols = unpivot_columns[j] + null_value = bigframes.dtypes.literal_to_ibis_scalar( + None, force_dtype=col_dtype ) - - columns = tuple( - itertools.chain( - (left_table[col.get_name()] for col in self.columns), (new_column,) + ibis_values = [ + ops.AsTypeOp(col_dtype)._as_ibis(unpivot_table[col]) + if col is not None + else null_value + for col in source_cols + ] + cases = [(i, ibis_values[i]) for i in range(len(ibis_values))] + unpivot_value = typing.cast( + ibis_types.IntegerColumn, unpivot_table[unpivot_offset_id] + ).cases( + cases, default=null_value # type:ignore ) - ) + unpivot_values.append(unpivot_value.name(result_col)) - return UnorderedIR( - left_table, - columns=columns, + unpivot_table = unpivot_table.select( + passthrough_columns, + *label_columns, + *unpivot_values, + *hidden_col_ids, + unpivot_offset_id, ) - def project_window_op( - self, - expression: ex_types.Aggregation, - window_spec: WindowSpec, - output_name: str, - ) -> UnorderedIR: - """ - Creates a new expression based on this expression with unary operation applied to one column. - column_name: the id of the input column present in the expression - op: the windowable operator to apply to the input column - window_spec: a specification of the window over which to apply the operator - output_name: the id to assign to the output of the operator - """ - # Cannot nest analytic expressions, so reproject to cte first if needed. - # Also ibis cannot window literals, so need to reproject those (even though this is legal in googlesql) - # See: https://github.com/ibis-project/ibis/issues/9773 - used_exprs = map( - self._compile_expression, - map( - ex.DerefOp, - itertools.chain( - expression.column_references, window_spec.all_referenced_columns + # Extend the original ordering using unpivot_offset_id + old_ordering = self._ordering + if how == "left": + new_ordering = ExpressionOrdering( + ordering_value_columns=tuple( + [ + *old_ordering.ordering_value_columns, + OrderingColumnReference(unpivot_offset_id), + ] ), - ), + total_ordering_columns=frozenset( + [*old_ordering.total_ordering_columns, unpivot_offset_id] + ), + ) + else: # how=="right" + new_ordering = ExpressionOrdering( + ordering_value_columns=tuple( + [ + OrderingColumnReference(unpivot_offset_id), + *old_ordering.ordering_value_columns, + ] + ), + total_ordering_columns=frozenset( + [*old_ordering.total_ordering_columns, unpivot_offset_id] + ), + ) + value_columns = [ + unpivot_table[value_col_id] for value_col_id, _ in unpivot_columns + ] + passthrough_values = [unpivot_table[col] for col in passthrough_columns] + hidden_ordering_columns = [ + unpivot_table[unpivot_offset_id], + *[unpivot_table[hidden_col] for hidden_col in hidden_col_ids], + ] + return CompiledArrayValue( + table=unpivot_table, + columns=[ + *[unpivot_table[col_id] for col_id in index_col_ids], + *value_columns, + *passthrough_values, + ], + hidden_ordering_columns=hidden_ordering_columns, + ordering=new_ordering, ) - can_directly_window = not any( - map(lambda x: is_literal(x) or is_window(x), used_exprs) + + def assign(self, source_id: str, destination_id: str) -> CompiledArrayValue: + return self._set_or_replace_by_id( + destination_id, self._get_ibis_column(source_id) ) - if not can_directly_window: - return self._reproject_to_table().project_window_op( - expression, - window_spec, - output_name, + + def assign_constant( + self, + destination_id: str, + value: typing.Any, + dtype: typing.Optional[bigframes.dtypes.Dtype], + ) -> CompiledArrayValue: + # TODO(b/281587571): Solve scalar constant aggregation problem w/Ibis. + ibis_value = bigframes.dtypes.literal_to_ibis_scalar(value, dtype) + if ibis_value is None: + raise NotImplementedError( + f"Type not supported as scalar value {type(value)}. {constants.FEEDBACK_LINK}" ) + expr = self._set_or_replace_by_id(destination_id, ibis_value) + return expr._reproject_to_table() + + def _set_or_replace_by_id( + self, id: str, new_value: ibis_types.Value + ) -> CompiledArrayValue: + """Safely assign by id while maintaining ordering integrity.""" + # TODO: Split into explicit set and replace methods + ordering_col_ids = [ + col_ref.column_id for col_ref in self._ordering.ordering_value_columns + ] + if id in ordering_col_ids: + return self._hide_column(id)._set_or_replace_by_id(id, new_value) - rewritten_expr = rewrite.simplify_complex_windows( - agg_expressions.WindowExpression(expression, window_spec) - ) - - ibis_expr = op_compiler.compile_expression(rewritten_expr, self._ibis_bindings) - - return UnorderedIR(self._table, (*self.columns, ibis_expr.name(output_name))) - - def _compile_expression(self, expr: ex.Expression): - return op_compiler.compile_expression(expr, self._ibis_bindings) - - -def is_literal(column: ibis_types.Value) -> bool: - # Unfortunately, Literals in ibis are not "Columns"s and therefore can't be aggregated. - return not isinstance(column, ibis_types.Column) - - -def is_window(column: ibis_types.Value) -> bool: - matches = ( - (column) - .op() - .find_topmost( - lambda x: isinstance(x, (ibis_ops.WindowFunction, ibis_ops.Relation)) - ) - ) - return any(isinstance(op, ibis_ops.WindowFunction) for op in matches) - - -def _string_cast_join_cond( - lvalue: ibis_types.Column, rvalue: ibis_types.Column -) -> ibis_types.BooleanColumn: - result = ( - lvalue.cast(ibis_dtypes.str).fill_null(ibis_types.literal("0")) - == rvalue.cast(ibis_dtypes.str).fill_null(ibis_types.literal("0")) - ) & ( - lvalue.cast(ibis_dtypes.str).fill_null(ibis_types.literal("1")) - == rvalue.cast(ibis_dtypes.str).fill_null(ibis_types.literal("1")) - ) - return typing.cast(ibis_types.BooleanColumn, result) - - -def _numeric_join_cond( - lvalue: ibis_types.Column, rvalue: ibis_types.Column -) -> ibis_types.BooleanColumn: - lvalue1 = lvalue.fill_null(ibis_types.literal(0)) - lvalue2 = lvalue.fill_null(ibis_types.literal(1)) - rvalue1 = rvalue.fill_null(ibis_types.literal(0)) - rvalue2 = rvalue.fill_null(ibis_types.literal(1)) - if lvalue.type().is_floating() and rvalue.type().is_floating(): - # NaN aren't equal so need to coalesce as well with diff constants - lvalue1 = ( - typing.cast(ibis_types.FloatingColumn, lvalue) - .isnan() - .ifelse(ibis_types.literal(2), lvalue1) - ) - lvalue2 = ( - typing.cast(ibis_types.FloatingColumn, lvalue) - .isnan() - .ifelse(ibis_types.literal(3), lvalue2) - ) - rvalue1 = ( - typing.cast(ibis_types.FloatingColumn, rvalue) - .isnan() - .ifelse(ibis_types.literal(2), rvalue1) - ) - rvalue2 = ( - typing.cast(ibis_types.FloatingColumn, rvalue) - .isnan() - .ifelse(ibis_types.literal(3), rvalue2) - ) - result = (lvalue1 == rvalue1) & (lvalue2 == rvalue2) - return typing.cast(ibis_types.BooleanColumn, result) - - -def _join_condition( - lvalue: ibis_types.Column, rvalue: ibis_types.Column, nullsafe: bool -) -> ibis_types.BooleanColumn: - if (lvalue.type().is_floating()) and (lvalue.type().is_floating()): - # Need to always make safe join condition to handle nan, even if no nulls - return _numeric_join_cond(lvalue, rvalue) - if nullsafe: - # TODO: Define more coalesce constants for non-numeric types to avoid cast - if (lvalue.type().is_numeric()) and (lvalue.type().is_numeric()): - return _numeric_join_cond(lvalue, rvalue) + builder = self.builder() + if id in self.column_ids: + builder.columns = [ + val if (col_id != id) else new_value.name(id) + for col_id, val in zip(self.column_ids, self._columns) + ] else: - return _string_cast_join_cond(lvalue, rvalue) - return typing.cast(ibis_types.BooleanColumn, lvalue == rvalue) + builder.columns = [*self.columns, new_value.name(id)] + return builder.build() + + +class ArrayValueBuilder: + """Mutable expression class. + Use ArrayValue.builder() to create from a ArrayValue object. + """ + + def __init__( + self, + table: ibis_types.Table, + ordering: ExpressionOrdering, + columns: Collection[ibis_types.Value] = (), + hidden_ordering_columns: Collection[ibis_types.Value] = (), + predicates: Optional[Collection[ibis_types.BooleanValue]] = None, + ): + self.table = table + self.columns = list(columns) + self.hidden_ordering_columns = list(hidden_ordering_columns) + self.ordering = ordering + self.predicates = list(predicates) if predicates is not None else None + + def build(self) -> CompiledArrayValue: + return CompiledArrayValue( + table=self.table, + columns=self.columns, + hidden_ordering_columns=self.hidden_ordering_columns, + ordering=self.ordering, + predicates=self.predicates, + ) + + +def _reduce_predicate_list( + predicate_list: typing.Collection[ibis_types.BooleanValue], +) -> ibis_types.BooleanValue: + """Converts a list of predicates BooleanValues into a single BooleanValue.""" + if len(predicate_list) == 0: + raise ValueError("Cannot reduce empty list of predicates") + if len(predicate_list) == 1: + (item,) = predicate_list + return item + return functools.reduce(lambda acc, pred: acc.__and__(pred), predicate_list) + + +def _convert_ordering_to_table_values( + value_lookup: typing.Mapping[str, ibis_types.Value], + ordering_columns: typing.Sequence[OrderingColumnReference], +) -> typing.Sequence[ibis_types.Value]: + column_refs = ordering_columns + ordering_values = [] + for ordering_col in column_refs: + column = typing.cast(ibis_types.Column, value_lookup[ordering_col.column_id]) + ordering_value = ( + ibis.asc(column) + if ordering_col.direction.is_ascending + else ibis.desc(column) + ) + # Bigquery SQL considers NULLS to be "smallest" values, but we need to override in these cases. + if (not ordering_col.na_last) and (not ordering_col.direction.is_ascending): + # Force nulls to be first + is_null_val = typing.cast(ibis_types.Column, column.isnull()) + ordering_values.append(ibis.desc(is_null_val)) + elif (ordering_col.na_last) and (ordering_col.direction.is_ascending): + # Force nulls to be last + is_null_val = typing.cast(ibis_types.Column, column.isnull()) + ordering_values.append(ibis.asc(is_null_val)) + ordering_values.append(ordering_value) + return ordering_values + + +def _as_identity(value: ibis_types.Value): + # Some types need to be converted to string to enable groupby + if value.type().is_float64() or value.type().is_geospatial(): + return value.cast(ibis_dtypes.str) + return value diff --git a/bigframes/core/compile/compiler.py b/bigframes/core/compile/compiler.py new file mode 100644 index 00000000000..195d830122a --- /dev/null +++ b/bigframes/core/compile/compiler.py @@ -0,0 +1,185 @@ +# Copyright 2023 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 functools +import io +import typing + +import pandas as pd + +import bigframes.core.compile as compiled +import bigframes.core.compile.single_column +import bigframes.core.nodes as nodes + +if typing.TYPE_CHECKING: + import bigframes.core + import bigframes.session + + +@functools.cache +def compile_node(node: nodes.BigFrameNode) -> compiled.CompiledArrayValue: + """Compile node into CompileArrayValue. Caches result.""" + return _compile_node(node) + + +@functools.singledispatch +def _compile_node(node: nodes.BigFrameNode) -> compiled.CompiledArrayValue: + """Defines transformation but isn't cached, always use compile_node instead""" + raise ValueError(f"Can't compile unnrecognized node: {node}") + + +@_compile_node.register +def compile_join(node: nodes.JoinNode): + compiled_left = compile_node(node.left_child) + compiled_right = compile_node(node.right_child) + return bigframes.core.compile.single_column.join_by_column( + compiled_left, + node.left_column_ids, + compiled_right, + node.right_column_ids, + how=node.how, + allow_row_identity_join=node.allow_row_identity_join, + ) + + +@_compile_node.register +def compile_select(node: nodes.SelectNode): + return compile_node(node.child).select_columns(node.column_ids) + + +@_compile_node.register +def compile_drop(node: nodes.DropColumnsNode): + return compile_node(node.child).drop_columns(node.columns) + + +@_compile_node.register +def compile_readlocal(node: nodes.ReadLocalNode): + array_as_pd = pd.read_feather(io.BytesIO(node.feather_bytes)) + return compiled.CompiledArrayValue.mem_expr_from_pandas(array_as_pd) + + +@_compile_node.register +def compile_readgbq(node: nodes.ReadGbqNode): + return compiled.CompiledArrayValue( + node.table, + node.columns, + node.hidden_ordering_columns, + node.ordering, + ) + + +@_compile_node.register +def compile_promote_offsets(node: nodes.PromoteOffsetsNode): + return compile_node(node.child).promote_offsets(node.col_id) + + +@_compile_node.register +def compile_filter(node: nodes.FilterNode): + return compile_node(node.child).filter(node.predicate_id, node.keep_null) + + +@_compile_node.register +def compile_orderby(node: nodes.OrderByNode): + return compile_node(node.child).order_by(node.by, node.stable) + + +@_compile_node.register +def compile_reversed(node: nodes.ReversedNode): + return compile_node(node.child).reversed() + + +@_compile_node.register +def compile_project_unary(node: nodes.ProjectUnaryOpNode): + return compile_node(node.child).project_unary_op( + node.input_id, node.op, node.output_id + ) + + +@_compile_node.register +def compile_project_binary(node: nodes.ProjectBinaryOpNode): + return compile_node(node.child).project_binary_op( + node.left_input_id, node.right_input_id, node.op, node.output_id + ) + + +@_compile_node.register +def compile_project_ternary(node: nodes.ProjectTernaryOpNode): + return compile_node(node.child).project_ternary_op( + node.input_id1, node.input_id2, node.input_id3, node.op, node.output_id + ) + + +@_compile_node.register +def compile_concat(node: nodes.ConcatNode): + compiled_nodes = [compile_node(node) for node in node.children] + return compiled_nodes[0].concat(compiled_nodes[1:]) + + +@_compile_node.register +def compile_aggregate(node: nodes.AggregateNode): + return compile_node(node.child).aggregate( + node.aggregations, node.by_column_ids, node.dropna + ) + + +@_compile_node.register +def compile_corr(node: nodes.CorrNode): + return compile_node(node.child).corr_aggregate(node.corr_aggregations) + + +@_compile_node.register +def compile_window(node: nodes.WindowOpNode): + return compile_node(node.child).project_window_op( + node.column_name, + node.op, + node.window_spec, + node.output_name, + never_skip_nulls=node.never_skip_nulls, + skip_reproject_unsafe=node.skip_reproject_unsafe, + ) + + +@_compile_node.register +def compile_reproject(node: nodes.ReprojectOpNode): + return compile_node(node.child)._reproject_to_table() + + +@_compile_node.register +def compile_unpivot(node: nodes.UnpivotNode): + return compile_node(node.child).unpivot( + node.row_labels, + node.unpivot_columns, + passthrough_columns=node.passthrough_columns, + index_col_ids=node.index_col_ids, + dtype=node.dtype, + how=node.how, + ) + + +@_compile_node.register +def compile_assign(node: nodes.AssignNode): + return compile_node(node.child).assign(node.source_id, node.destination_id) + + +@_compile_node.register +def compile_assign_constant(node: nodes.AssignConstantNode): + return compile_node(node.child).assign_constant( + node.destination_id, node.value, node.dtype + ) + + +@_compile_node.register +def compiler_random_sample(node: nodes.RandomSampleNode): + return compile_node(node.child)._uniform_sampling(node.fraction) diff --git a/bigframes/core/compile/concat.py b/bigframes/core/compile/concat.py deleted file mode 100644 index 742f429f547..00000000000 --- a/bigframes/core/compile/concat.py +++ /dev/null @@ -1,41 +0,0 @@ -# Copyright 2023 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 - -import bigframes_vendored.ibis.expr.api as ibis_api - -import bigframes.core.compile.compiled as compiled - - -def concat_unordered( - items: typing.Sequence[compiled.UnorderedIR], - output_ids: typing.Sequence[str], -) -> compiled.UnorderedIR: - """Append together multiple ArrayValue objects.""" - if len(items) == 1: - return items[0] - tables = [] - for expr in items: - table = expr._to_ibis_expr() - table = table.select( - [table[col].name(id) for id, col in zip(output_ids, table.columns)] - ) - tables.append(table) - combined_table = ibis_api.union(*tables) - return compiled.UnorderedIR( - combined_table, - columns=[combined_table[col] for col in combined_table.columns], - ) diff --git a/bigframes/core/compile/configs.py b/bigframes/core/compile/configs.py deleted file mode 100644 index 62c28f87cae..00000000000 --- a/bigframes/core/compile/configs.py +++ /dev/null @@ -1,37 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -from __future__ import annotations - -import dataclasses -import typing - -import google.cloud.bigquery - -from bigframes.core import nodes, ordering - - -@dataclasses.dataclass(frozen=True) -class CompileRequest: - node: nodes.BigFrameNode - sort_rows: bool - materialize_all_order_keys: bool = False - peek_count: typing.Optional[int] = None - - -@dataclasses.dataclass(frozen=True) -class CompileResult: - sql: str - sql_schema: typing.Sequence[google.cloud.bigquery.SchemaField] - row_order: typing.Optional[ordering.RowOrdering] - encoded_type_refs: str diff --git a/bigframes/core/compile/constants.py b/bigframes/core/compile/constants.py deleted file mode 100644 index 9c307125ab2..00000000000 --- a/bigframes/core/compile/constants.py +++ /dev/null @@ -1,27 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - - -# Datetime constants -UNIT_TO_US_CONVERSION_FACTORS = { - "W": 7 * 24 * 60 * 60 * 1000 * 1000, - "d": 24 * 60 * 60 * 1000 * 1000, - "D": 24 * 60 * 60 * 1000 * 1000, - "h": 60 * 60 * 1000 * 1000, - "m": 60 * 1000 * 1000, - "s": 1000 * 1000, - "ms": 1000, - "us": 1, - "ns": 1e-3, -} diff --git a/bigframes/core/compile/explode.py b/bigframes/core/compile/explode.py deleted file mode 100644 index 59e3a13d023..00000000000 --- a/bigframes/core/compile/explode.py +++ /dev/null @@ -1,73 +0,0 @@ -# Copyright 2023 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 - -import bigframes_vendored.ibis - -import bigframes.core.compile.compiled as compiled -import bigframes.core.expression as ex -import bigframes.core.guid -import bigframes.core.ordering - - -def explode_unordered( - input: compiled.UnorderedIR, - columns: typing.Sequence[ex.DerefOp], - offsets_id: typing.Optional[str], -) -> compiled.UnorderedIR: - table = input._to_ibis_expr() - column_ids = tuple(ref.id.sql for ref in columns) - - # The offset array ensures null represents empty arrays after unnesting. - offset_array_id = bigframes.core.guid.generate_guid("offset_array_") - offset_array = bigframes_vendored.ibis.range( - 0, - bigframes_vendored.ibis.greatest( - 1, # We always want at least 1 element to fill in NULLs for empty arrays. - bigframes_vendored.ibis.least( - *[table[column_id].length() for column_id in column_ids] - ), - ), - 1, - ).name(offset_array_id) - table_w_offset_array = table.select( - offset_array, - *input._column_names, - ) - - unnest_offset_id = offsets_id or bigframes.core.guid.generate_guid("unnest_offset_") - unnest_offset = ( - table_w_offset_array[offset_array_id].unnest().name(unnest_offset_id) - ) - table_w_offset = table_w_offset_array.select( - unnest_offset, - *input._column_names, - ) - - output_cols = tuple(input.column_ids) + ((offsets_id,) if offsets_id else ()) - unnested_columns = [ - table_w_offset[column_id][table_w_offset[unnest_offset_id]].name(column_id) - if column_id in column_ids - else table_w_offset[column_id] - for column_id in output_cols - ] - table_w_unnest = table_w_offset.select(*unnested_columns) - - columns = [table_w_unnest[column_name] for column_name in output_cols] - return compiled.UnorderedIR( - table_w_unnest, - columns=columns, # type: ignore - ) diff --git a/bigframes/core/compile/ibis_compiler/__init__.py b/bigframes/core/compile/ibis_compiler/__init__.py deleted file mode 100644 index 6b9d284c536..00000000000 --- a/bigframes/core/compile/ibis_compiler/__init__.py +++ /dev/null @@ -1,25 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -"""Compiler for BigFrames expression to Ibis expression. - -Make sure to import all ibis_compiler implementations here so that they get -registered. -""" - -from __future__ import annotations - -import bigframes.core.compile.ibis_compiler.operations.generic_ops # noqa: F401 -import bigframes.core.compile.ibis_compiler.operations.geo_ops # noqa: F401 -import bigframes.core.compile.ibis_compiler.scalar_op_registry # noqa: F401 diff --git a/bigframes/core/compile/ibis_compiler/aggregate_compiler.py b/bigframes/core/compile/ibis_compiler/aggregate_compiler.py deleted file mode 100644 index 94607bf04bc..00000000000 --- a/bigframes/core/compile/ibis_compiler/aggregate_compiler.py +++ /dev/null @@ -1,868 +0,0 @@ -# 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. -# 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 functools -import typing -from typing import List, Optional, cast - -import bigframes_vendored.constants as constants -import bigframes_vendored.ibis -import bigframes_vendored.ibis.expr.api as ibis_api -import bigframes_vendored.ibis.expr.datatypes as ibis_dtypes -import bigframes_vendored.ibis.expr.operations as ibis_ops -import bigframes_vendored.ibis.expr.operations.udf as ibis_udf -import bigframes_vendored.ibis.expr.types as ibis_types -import pandas as pd -from bigframes_vendored.ibis.expr import builders as ibis_expr_builders -from bigframes_vendored.ibis.expr.operations import window as ibis_expr_window - -import bigframes.core.compile.ibis_compiler.scalar_op_compiler as scalar_compilers -import bigframes.core.compile.ibis_types as compile_ibis_types -import bigframes.core.utils -import bigframes.core.window_spec as window_spec -import bigframes.operations.aggregations as agg_ops -from bigframes.core import agg_expressions -from bigframes.core.compile import constants as compiler_constants -from bigframes.core.window_spec import RangeWindowBounds, RowsWindowBounds, WindowSpec - -scalar_compiler = scalar_compilers.scalar_op_compiler - - -# TODO(swast): We can remove this if ibis adds general approx_quantile -# See: https://github.com/ibis-project/ibis/issues/9541 -@ibis_udf.agg.builtin -def approx_quantiles(expression: float, number) -> List[float]: - """APPROX_QUANTILES - - https://cloud.google.com/bigquery/docs/reference/standard-sql/approximate_aggregate_functions#approx_quantiles - """ - return [] # pragma: NO COVER - - -def compile_aggregate( - aggregate: agg_expressions.Aggregation, - bindings: typing.Dict[str, ibis_types.Value], - order_by: typing.Sequence[ibis_types.Value] = [], -) -> ibis_types.Value: - if isinstance(aggregate, agg_expressions.NullaryAggregation): - return compile_nullary_agg(aggregate.op) - if isinstance(aggregate, agg_expressions.UnaryAggregation): - input = scalar_compiler.compile_expression(aggregate.arg, bindings=bindings) - if not aggregate.op.order_independent: - return compile_ordered_unary_agg(aggregate.op, input, order_by=order_by) # type: ignore - else: - return compile_unary_agg(aggregate.op, input) # type: ignore - elif isinstance(aggregate, agg_expressions.BinaryAggregation): - left = scalar_compiler.compile_expression(aggregate.left, bindings=bindings) - right = scalar_compiler.compile_expression(aggregate.right, bindings=bindings) - return compile_binary_agg(aggregate.op, left, right) # type: ignore - else: - raise ValueError(f"Unexpected aggregation: {aggregate}") - - -def compile_analytic( - aggregate: agg_expressions.Aggregation, - window: window_spec.WindowSpec, - bindings: typing.Dict[str, ibis_types.Value], -) -> ibis_types.Value: - ibis_window = _ibis_window_from_spec(window, bindings=bindings) - if isinstance(aggregate, agg_expressions.NullaryAggregation): - return compile_nullary_agg(aggregate.op, ibis_window) - elif isinstance(aggregate, agg_expressions.UnaryAggregation): - input = scalar_compiler.compile_expression(aggregate.arg, bindings=bindings) - return compile_unary_agg(aggregate.op, input, ibis_window) # type: ignore - elif isinstance(aggregate, agg_expressions.BinaryAggregation): - raise NotImplementedError("binary analytic operations not yet supported") - else: - raise ValueError(f"Unexpected analytic operation: {aggregate}") - - -@functools.singledispatch -def compile_binary_agg( - op: agg_ops.WindowOp, - left: ibis_types.Column, - right: ibis_types.Column, - window: Optional[window_spec.WindowSpec] = None, -) -> ibis_types.Value: - raise ValueError(f"Can't compile unrecognized operation: {op}") - - -@functools.singledispatch -def compile_unary_agg( - op: agg_ops.WindowOp, - input: ibis_types.Column, - window: Optional[window_spec.WindowSpec] = None, -) -> ibis_types.Value: - raise ValueError(f"Can't compile unrecognized operation: {op}") - - -@functools.singledispatch -def compile_ordered_unary_agg( - op: agg_ops.WindowOp, - input: ibis_types.Column, - window: Optional[window_spec.WindowSpec] = None, - order_by: typing.Sequence[ibis_types.Value] = [], -) -> ibis_types.Value: - raise ValueError(f"Can't compile unrecognized operation: {op}") - - -@functools.singledispatch -def compile_nullary_agg( - op: agg_ops.WindowOp, - window: Optional[window_spec.WindowSpec] = None, -) -> ibis_types.Value: - raise ValueError(f"Can't compile unrecognized operation: {op}") - - -def numeric_op(operation): - @functools.wraps(operation) - def constrained_op( - op, - column: ibis_types.Column, - window=None, - order_by: typing.Sequence[ibis_types.Value] = [], - ): - if column.type().is_boolean(): - column = typing.cast( - ibis_types.NumericColumn, column.cast(ibis_dtypes.int64) - ) - if column.type().is_numeric(): - return operation(op, column, window) - else: - raise ValueError( - f"Numeric operation cannot be applied to type {column.type()}. {constants.FEEDBACK_LINK}" - ) - - return constrained_op - - -### Specific Op implementations Below - - -@compile_nullary_agg.register -def _(op: agg_ops.SizeOp, window=None) -> ibis_types.NumericValue: - return _apply_window_if_present(ibis_ops.count(1), window) - - -@compile_unary_agg.register -def _(op: agg_ops.SizeUnaryOp, _, window=None) -> ibis_types.NumericValue: - return _apply_window_if_present(ibis_ops.count(1), window) - - -@compile_unary_agg.register -@numeric_op -def _( - op: agg_ops.SumOp, - column: ibis_types.NumericColumn, - window=None, -) -> ibis_types.NumericValue: - # Will be null if all inputs are null. Pandas defaults to zero sum though. - bq_sum = _apply_window_if_present(column.sum(), window) - return bq_sum.coalesce(ibis_types.literal(0)) - - -@compile_unary_agg.register -def _( - op: agg_ops.MedianOp, - column: ibis_types.NumericColumn, - window=None, -) -> ibis_types.NumericValue: - return cast(ibis_types.NumericValue, column.approx_median()) - - -@compile_unary_agg.register -@numeric_op -def _( - op: agg_ops.ApproxQuartilesOp, - column: ibis_types.NumericColumn, - window=None, -) -> ibis_types.NumericValue: - # APPROX_QUANTILES has very few allowed windows. - if window is not None: - raise NotImplementedError( - f"Approx Quartiles with windowing is not supported. {constants.FEEDBACK_LINK}" - ) - value = approx_quantiles(column, 4)[op.quartile] # type: ignore - return cast(ibis_types.NumericValue, value) - - -@compile_unary_agg.register -def _( - op: agg_ops.ApproxTopCountOp, - column: ibis_types.Column, - window=None, -) -> ibis_types.ArrayColumn: - # APPROX_TOP_COUNT has very few allowed windows. - if window is not None: - raise NotImplementedError( - f"Approx top count with windowing is not supported. {constants.FEEDBACK_LINK}" - ) - - # Define a user-defined function (UDF) that approximates the top counts of an expression. - # The type of value is dynamically matching the input column. - def approx_top_count(expression, number: ibis_dtypes.int64): # type: ignore - ... - - ibis_return_type = ibis_dtypes.Array( - value_type=ibis_dtypes.Struct.from_tuples( - [("value", column.type()), ("count", ibis_dtypes.int64)] - ) - ) # type: ignore - approx_top_count.__annotations__["return"] = ibis_return_type - udf_op = ibis_ops.udf.agg.builtin(approx_top_count) - - return udf_op(expression=column, number=op.number) # type: ignore - - -@compile_unary_agg.register -@numeric_op -def _( - op: agg_ops.QuantileOp, - column: ibis_types.NumericColumn, - window=None, -) -> ibis_types.NumericValue: - result = column.quantile(op.q) - if op.should_floor_result: - result = result.floor() # type:ignore - - return _apply_window_if_present(result, window) - - -@compile_unary_agg.register -@numeric_op -def _( - op: agg_ops.MeanOp, - column: ibis_types.NumericColumn, - window=None, - # order_by: typing.Sequence[ibis_types.Value] = [], -) -> ibis_types.NumericValue: - result = column.mean().floor() if op.should_floor_result else column.mean() - return _apply_window_if_present(result, window) - - -@compile_unary_agg.register -@numeric_op -def _( - op: agg_ops.ProductOp, - column: ibis_types.NumericColumn, - window=None, -) -> ibis_types.NumericValue: - # Need to short-circuit as log with zeroes is illegal sql - is_zero = cast(ibis_types.BooleanColumn, (column == 0)) - - # There is no product sql aggregate function, so must implement as a sum of logs, and then - # apply power after. Note, log and power base must be equal! This impl uses base 2. - logs = cast( - ibis_types.NumericColumn, - ibis_api.case().when(is_zero, 0).else_(column.abs().log2()).end(), - ) - logs_sum = _apply_window_if_present(logs.sum(), window) - magnitude = cast(ibis_types.NumericValue, ibis_types.literal(2)).pow(logs_sum) - - # Can't determine sign from logs, so have to determine parity of count of negative inputs - is_negative = cast( - ibis_types.NumericColumn, - ibis_api.case().when(column.sign() == -1, 1).else_(0).end(), - ) - negative_count = _apply_window_if_present(is_negative.sum(), window) - negative_count_parity = negative_count % cast( - ibis_types.NumericValue, ibis_types.literal(2) - ) # 1 if result should be negative, otherwise 0 - - any_zeroes = _apply_window_if_present(is_zero.any(), window) - float_result = ( - ibis_api.case() - .when(any_zeroes, ibis_types.literal(0)) - .else_(magnitude * pow(-1, negative_count_parity)) - .end() - ) - return cast(ibis_types.NumericValue, float_result) - - -@compile_unary_agg.register -def _( - op: agg_ops.MaxOp, - column: ibis_types.Column, - window=None, -) -> ibis_types.Value: - return _apply_window_if_present(column.max(), window) - - -@compile_unary_agg.register -def _( - op: agg_ops.MinOp, - column: ibis_types.Column, - window=None, -) -> ibis_types.Value: - return _apply_window_if_present(column.min(), window) - - -@compile_unary_agg.register -@numeric_op -def _( - op: agg_ops.StdOp, - x: ibis_types.NumericColumn, - window=None, -) -> ibis_types.Value: - result = x.std().floor() if op.should_floor_result else x.std() - return _apply_window_if_present(result, window) - - -@compile_unary_agg.register -@numeric_op -def _( - op: agg_ops.VarOp, - x: ibis_types.Column, - window=None, -) -> ibis_types.Value: - return _apply_window_if_present(cast(ibis_types.NumericColumn, x).var(), window) - - -@compile_unary_agg.register -@numeric_op -def _( - op: agg_ops.PopVarOp, - x: ibis_types.Column, - window=None, -) -> ibis_types.Value: - return _apply_window_if_present( - cast(ibis_types.NumericColumn, x).var(how="pop"), window - ) - - -@compile_unary_agg.register -def _( - op: agg_ops.CountOp, - column: ibis_types.Column, - window=None, -) -> ibis_types.IntegerValue: - return _apply_window_if_present(column.count(), window) - - -@compile_unary_agg.register -def _( - op: agg_ops.CutOp, - x: ibis_types.Column, - window=None, -): - out = ibis_api.case() - if isinstance(op.bins, int): - col_min = _apply_window_if_present(x.min(), window) - col_max = _apply_window_if_present(x.max(), window) - adj = (col_max - col_min) * 0.001 - bin_width = (col_max - col_min) / op.bins - - for this_bin in range(op.bins): - if op.labels is False: - value = compile_ibis_types.literal_to_ibis_scalar( - this_bin, - force_dtype=pd.Int64Dtype(), - ) - elif isinstance(op.labels, typing.Iterable): - value = compile_ibis_types.literal_to_ibis_scalar( - list(op.labels)[this_bin], - force_dtype=pd.StringDtype(storage="pyarrow"), - ) - else: - left_adj = adj if this_bin == 0 and op.right else 0 - right_adj = adj if this_bin == op.bins - 1 and not op.right else 0 - - left = col_min + this_bin * bin_width - left_adj - right = col_min + (this_bin + 1) * bin_width + right_adj - - if op.right: - value = ibis_types.struct( - {"left_exclusive": left, "right_inclusive": right} - ) - else: - value = ibis_types.struct( - {"left_inclusive": left, "right_exclusive": right} - ) - if this_bin == op.bins - 1: - case_expr = x.notnull() - else: - if op.right: - case_expr = x <= (col_min + (this_bin + 1) * bin_width) - else: - case_expr = x < (col_min + (this_bin + 1) * bin_width) - out = out.when(case_expr, value) - else: # Interpret as intervals - for this_bin, interval in enumerate(op.bins): - left = compile_ibis_types.literal_to_ibis_scalar(interval[0]) - right = compile_ibis_types.literal_to_ibis_scalar(interval[1]) - if op.right: - condition = (x > left) & (x <= right) - else: - condition = (x >= left) & (x < right) - - if op.labels is False: - value = compile_ibis_types.literal_to_ibis_scalar( - this_bin, - force_dtype=pd.Int64Dtype(), - ) - elif isinstance(op.labels, typing.Iterable): - value = compile_ibis_types.literal_to_ibis_scalar( - list(op.labels)[this_bin], - force_dtype=pd.StringDtype(storage="pyarrow"), - ) - else: - if op.right: - value = ibis_types.struct( - {"left_exclusive": left, "right_inclusive": right} - ) - else: - value = ibis_types.struct( - {"left_inclusive": left, "right_exclusive": right} - ) - - out = out.when(condition, value) - return out.end() - - -@compile_unary_agg.register -@numeric_op -def _( - self: agg_ops.QcutOp, - column: ibis_types.Column, - window=None, -) -> ibis_types.IntegerValue: - if isinstance(self.quantiles, int): - quantiles_ibis = compile_ibis_types.literal_to_ibis_scalar(self.quantiles) - percent_ranks = cast( - ibis_types.FloatingColumn, - _apply_window_if_present(column.percent_rank(), window), - ) - float_bucket = cast(ibis_types.FloatingColumn, (percent_ranks * quantiles_ibis)) - return float_bucket.ceil().clip(lower=_ibis_num(1)) - _ibis_num(1) - else: - percent_ranks = cast( - ibis_types.FloatingColumn, - _apply_window_if_present(column.percent_rank(), window), - ) - out = ibis_api.case() - first_ibis_quantile = compile_ibis_types.literal_to_ibis_scalar( - self.quantiles[0] - ) - out = out.when(percent_ranks < first_ibis_quantile, None) - for bucket_n in range(len(self.quantiles) - 1): - ibis_quantile = compile_ibis_types.literal_to_ibis_scalar( - self.quantiles[bucket_n + 1] - ) - out = out.when( - percent_ranks <= ibis_quantile, - compile_ibis_types.literal_to_ibis_scalar( - bucket_n, force_dtype=pd.Int64Dtype() - ), - ) - out = out.else_(None) - return out.end() # type: ignore - - -@compile_unary_agg.register -def _( - op: agg_ops.NuniqueOp, - column: ibis_types.Column, - window=None, -) -> ibis_types.IntegerValue: - return _apply_window_if_present(column.nunique(), window) - - -@compile_unary_agg.register -def _( - op: agg_ops.AnyValueOp, - column: ibis_types.Column, - window=None, -) -> ibis_types.IntegerValue: - return _apply_window_if_present(column.arbitrary(), window) - - -@compile_unary_agg.register -def _( - op: agg_ops.RankOp, - column: ibis_types.Column, - window=None, -) -> ibis_types.IntegerValue: - # Ibis produces 0-based ranks, while pandas creates 1-based ranks - return _apply_window_if_present(ibis_api.rank(), window) + 1 - - -@compile_unary_agg.register -def _( - op: agg_ops.DenseRankOp, - column: ibis_types.Column, - window=None, -) -> ibis_types.IntegerValue: - # Ibis produces 0-based ranks, while pandas creates 1-based ranks - return _apply_window_if_present(column.dense_rank(), window) + 1 - - -@compile_nullary_agg.register -def _( - op: agg_ops.RowNumberOp, - window=None, -) -> ibis_types.IntegerValue: - return _apply_window_if_present(ibis_api.row_number(), window) - - -@compile_unary_agg.register -def _(op: agg_ops.FirstOp, column: ibis_types.Column, window=None) -> ibis_types.Value: - return _apply_window_if_present(column.first(), window) - - -@compile_unary_agg.register -def _( - op: agg_ops.FirstNonNullOp, - column: ibis_types.Column, - window=None, -) -> ibis_types.Value: - # Ibis FirstNonNullValue expects Value[Any, Columnar], Mypy struggles to see Column as compatible. - return _apply_window_if_present( - ibis_ops.FirstNonNullValue(column).to_expr(), # type: ignore[arg-type] - window, # type: ignore - ) - - -@compile_unary_agg.register -def _( - op: agg_ops.LastOp, - column: ibis_types.Column, - window=None, -) -> ibis_types.Value: - return _apply_window_if_present(column.last(), window) - - -@compile_unary_agg.register -def _( - op: agg_ops.LastNonNullOp, - column: ibis_types.Column, - window=None, -) -> ibis_types.Value: - # Ibis LastNonNullValue expects Value[Any, Columnar], Mypy struggles to see Column as compatible. - return _apply_window_if_present( - ibis_ops.LastNonNullValue(column).to_expr(), # type: ignore[arg-type] - window, # type: ignore - ) - - -@compile_unary_agg.register -def _( - op: agg_ops.ShiftOp, - column: ibis_types.Column, - window=None, -) -> ibis_types.Value: - if op.periods == 0: # No-op - return column - if op.periods > 0: - return _apply_window_if_present(column.lag(op.periods), window) - return _apply_window_if_present(column.lead(-op.periods), window) - - -@compile_unary_agg.register -def _( - op: agg_ops.DiffOp, - column: ibis_types.Column, - window=None, -) -> ibis_types.Value: - shifted = compile_unary_agg(agg_ops.ShiftOp(op.periods), column, window) - if column.type().is_boolean(): - return cast(ibis_types.BooleanColumn, column) != cast( - ibis_types.BooleanColumn, shifted - ) - elif column.type().is_numeric(): - return cast(ibis_types.NumericColumn, column) - cast( - ibis_types.NumericColumn, shifted - ) - else: - raise TypeError(f"Cannot perform diff on type{column.type()}") - - -@compile_unary_agg.register -def _( - op: agg_ops.TimeSeriesDiffOp, - column: ibis_types.Column, - window=None, -) -> ibis_types.Value: - if not column.type().is_timestamp(): - raise TypeError(f"Cannot perform time series diff on type{column.type()}") - - original_column = cast(ibis_types.TimestampColumn, column) - shifted_column = cast( - ibis_types.TimestampColumn, - compile_unary_agg(agg_ops.ShiftOp(op.periods), column, window), - ) - - return original_column.delta(shifted_column, part="microsecond") - - -@compile_unary_agg.register -def _( - op: agg_ops.DateSeriesDiffOp, - column: ibis_types.Column, - window=None, -) -> ibis_types.Value: - if not column.type().is_date(): - raise TypeError(f"Cannot perform date series diff on type{column.type()}") - - original_column = cast(ibis_types.DateColumn, column) - shifted_column = cast( - ibis_types.DateColumn, - compile_unary_agg(agg_ops.ShiftOp(op.periods), column, window), - ) - - conversion_factor = typing.cast( - ibis_types.IntegerValue, compiler_constants.UNIT_TO_US_CONVERSION_FACTORS["D"] - ) - - return ( - original_column.delta(shifted_column, part="day") * conversion_factor - ).floor() - - -@compile_unary_agg.register -def _( - op: agg_ops.AllOp, - column: ibis_types.Column, - window=None, -) -> ibis_types.BooleanValue: - # BQ will return null for empty column, result would be false in pandas. - result = _apply_window_if_present(_is_true(column).all(), window) - literal = ibis_types.literal(True) - - return cast(ibis_types.BooleanScalar, result.fill_null(literal)) - - -@compile_unary_agg.register -def _( - op: agg_ops.AnyOp, - column: ibis_types.Column, - window=None, -) -> ibis_types.BooleanValue: - # BQ will return null for empty column, result would be false in pandas. - result = _apply_window_if_present(_is_true(column).any(), window) - literal = ibis_types.literal(False) - - return cast(ibis_types.BooleanScalar, result.fill_null(literal)) - - -@compile_ordered_unary_agg.register -def _( - op: agg_ops.ArrayAggOp, - column: ibis_types.Column, - window=None, - order_by: typing.Sequence[ibis_types.Value] = [], -) -> ibis_types.ArrayValue: - # BigQuery doesn't currently support using ARRAY_AGG with both window and aggregate - # functions simultaneously. Some aggregate functions (or its equivalent syntax) - # are more important, such as: - # - `IGNORE NULLS` are required to avoid an raised error if the final result - # contains a NULL element. - # - `ORDER BY` are required for the default ordering mode. - # To keep things simpler, windowing support is skipped for now. - if window is not None: - raise NotImplementedError( - f"ArrayAgg with windowing is not supported. {constants.FEEDBACK_LINK}" - ) - - return ibis_ops.ArrayAggregate( - column, # type: ignore - order_by=order_by, # type: ignore - ).to_expr() - - -@compile_ordered_unary_agg.register -def _( - op: agg_ops.StringAggOp, - column: ibis_types.Column, - window=None, - order_by: typing.Sequence[ibis_types.Value] = [], -) -> ibis_types.ArrayValue: - if window is not None: - raise NotImplementedError( - f"StringAgg with windowing is not supported. {constants.FEEDBACK_LINK}" - ) - - return ( - ibis_ops.StringAgg( - column, # type: ignore - sep=op.sep, # type: ignore - order_by=order_by, # type: ignore - ) - .to_expr() - .fill_null(ibis_types.literal("")) - ) - - -@compile_binary_agg.register -def _( - op: agg_ops.CorrOp, left: ibis_types.Column, right: ibis_types.Column, window=None -) -> ibis_types.NumericValue: - # Will be null if all inputs are null. Pandas defaults to zero sum though. - left_numeric = cast(ibis_types.NumericColumn, left) - right_numeric = cast(ibis_types.NumericColumn, right) - bq_corr = _apply_window_if_present( - left_numeric.corr(right_numeric, how="pop"), window - ) - return cast(ibis_types.NumericColumn, bq_corr) - - -@compile_binary_agg.register -def _( - op: agg_ops.CovOp, left: ibis_types.Column, right: ibis_types.Column, window=None -) -> ibis_types.NumericValue: - # Will be null if all inputs are null. Pandas defaults to zero sum though. - left_numeric = cast(ibis_types.NumericColumn, left) - right_numeric = cast(ibis_types.NumericColumn, right) - bq_cov = _apply_window_if_present( - left_numeric.cov(right_numeric, how="sample"), window - ) - return cast(ibis_types.NumericColumn, bq_cov) - - -def _apply_window_if_present(value: ibis_types.Value, window): - return value.over(window) if (window is not None) else value - - -def _ibis_window_from_spec( - window_spec: WindowSpec, bindings: typing.Dict[str, ibis_types.Value] -): - group_by: typing.List[ibis_types.Value] = ( - [ - typing.cast( - ibis_types.Column, - _as_groupable(scalar_compiler.compile_expression(column, bindings)), - ) - for column in window_spec.grouping_keys - ] - if window_spec.grouping_keys - else [] - ) - - # Construct ordering. There are basically 3 main cases - # 1. Order-independent op (aggregation, cut, rank) with unbound window - no ordering clause needed - # 2. Order-independent op (aggregation, cut, rank) with range window - use ordering clause, ties allowed - # 3. Order-depedenpent op (navigation functions, array_agg) or rows bounds - use total row order to break ties. - if window_spec.is_row_bounded: - if not window_spec.ordering: - # If window spec has following or preceding bounds, we need to apply an unambiguous ordering. - raise ValueError("No ordering provided for ordered analytic function") - order_by = scalar_compiler._convert_row_ordering_to_table_values( - bindings, - window_spec.ordering, - ) - - elif window_spec.is_range_bounded: - order_by = [ - scalar_compiler._convert_range_ordering_to_table_value( - bindings, - window_spec.ordering[0], - ) - ] - # The rest if branches are for unbounded windows - elif window_spec.ordering: - # Unbound grouping window. Suitable for aggregations but not for analytic function application. - order_by = scalar_compiler._convert_row_ordering_to_table_values( - bindings, - window_spec.ordering, - ) - else: - order_by = None - - window = bigframes_vendored.ibis.window(order_by=order_by, group_by=group_by) - if window_spec.bounds is not None: - return _add_boundary(window_spec.bounds, window) - return window - - -def _as_groupable(value: ibis_types.Value): - from bigframes.core.compile.ibis_compiler import scalar_op_registry - - # Some types need to be converted to another type to enable groupby - if value.type().is_float64(): - return value.cast(ibis_dtypes.str) - elif value.type().is_geospatial(): - return typing.cast(ibis_types.GeoSpatialColumn, value).as_binary() - elif value.type().is_json(): - return scalar_op_registry.to_json_string(value) - else: - return value - - -def _to_ibis_boundary( - boundary: Optional[int], -) -> Optional[ibis_expr_window.WindowBoundary]: - if boundary is None: - return None - # WindowBoundary expects Value[Any, Any], ibis_types.literal returns Scalar which Mypy doesn't see as compatible. - return ibis_expr_window.WindowBoundary( - ibis_types.literal(boundary if boundary >= 0 else -boundary), # type: ignore[arg-type] - preceding=boundary <= 0, # type:ignore - ) - - -def _add_boundary( - bounds: typing.Union[RowsWindowBounds, RangeWindowBounds], - ibis_window: ibis_expr_builders.LegacyWindowBuilder, -) -> ibis_expr_builders.LegacyWindowBuilder: - if isinstance(bounds, RangeWindowBounds): - return ibis_window.range( - start=_to_ibis_boundary( - None - if bounds.start is None - else bigframes.core.utils.timedelta_to_micros(bounds.start) - ), - end=_to_ibis_boundary( - None - if bounds.end is None - else bigframes.core.utils.timedelta_to_micros(bounds.end) - ), - ) - if isinstance(bounds, RowsWindowBounds): - if bounds.start is not None or bounds.end is not None: - return ibis_window.rows( - start=_to_ibis_boundary(bounds.start), - end=_to_ibis_boundary(bounds.end), - ) - return ibis_window - else: - raise ValueError(f"unrecognized window bounds {bounds}") - - -def _map_to_literal( - original: ibis_types.Value, literal: ibis_types.Scalar -) -> ibis_types.Column: - # Hack required to perform aggregations on literals in ibis, even though bigquery - # will let you directly aggregate literals (eg. 'SELECT COUNT(1) from table1') - return ibis_api.ifelse(original.isnull(), literal, literal) # type: ignore - - -def _ibis_num(number: float): - return typing.cast(ibis_types.NumericValue, ibis_types.literal(number)) - - -def _is_true(column: ibis_types.Column) -> ibis_types.BooleanColumn: - if column.type().is_boolean(): - return cast(ibis_types.BooleanColumn, column) - elif column.type().is_numeric(): - result = cast(ibis_types.NumericColumn, column).__ne__(ibis_types.literal(0)) - return cast(ibis_types.BooleanColumn, result) - elif column.type().is_string(): - result = cast(ibis_types.StringValue, column).length() > ibis_types.literal(0) - return cast(ibis_types.BooleanColumn, result) - else: - # Time and geo values don't have a 'False' value - return cast( - ibis_types.BooleanColumn, _map_to_literal(column, ibis_types.literal(True)) - ) diff --git a/bigframes/core/compile/ibis_compiler/default_ordering.py b/bigframes/core/compile/ibis_compiler/default_ordering.py deleted file mode 100644 index 84ce52851c4..00000000000 --- a/bigframes/core/compile/ibis_compiler/default_ordering.py +++ /dev/null @@ -1,91 +0,0 @@ -# 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. -# 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. - -""" -Private helpers for loading a BigQuery table as a BigQuery DataFrames DataFrame. -""" - -from __future__ import annotations - -from typing import Sequence, cast - -import bigframes_vendored.ibis -import bigframes_vendored.ibis.expr.datatypes as ibis_dtypes -import bigframes_vendored.ibis.expr.operations as ibis_ops -import bigframes_vendored.ibis.expr.types as ibis_types - -import bigframes.core.guid as guid - - -def _convert_to_nonnull_string(column: ibis_types.Value) -> ibis_types.StringValue: - col_type = column.type() - if ( - col_type.is_numeric() - or col_type.is_boolean() - or col_type.is_binary() - or col_type.is_temporal() - ): - result = column.cast(ibis_dtypes.String(nullable=True)) - elif col_type.is_geospatial(): - result = cast(ibis_types.GeoSpatialColumn, column).as_text() - elif col_type.is_string(): - result = column - else: - # TO_JSON_STRING works with all data types, but isn't the most efficient - # Needed for JSON, STRUCT and ARRAY datatypes - result = ibis_ops.ToJsonString(column).to_expr() # type: ignore - # Escape backslashes and use backslash as delineator - escaped = cast( - ibis_types.StringColumn, result.fill_null(ibis_types.literal("")) - ).replace( - "\\", # type: ignore - "\\\\", # type: ignore - ) - return cast(ibis_types.StringColumn, bigframes_vendored.ibis.literal("\\")).concat( - escaped - ) - - -def gen_row_key( - columns: Sequence[ibis_types.Value], -) -> bigframes_vendored.ibis.Value: - ordering_hash_part = guid.generate_guid("bigframes_ordering_") - ordering_hash_part2 = guid.generate_guid("bigframes_ordering_") - ordering_rand_part = guid.generate_guid("bigframes_ordering_") - - # All inputs into hash must be non-null or resulting hash will be null - str_values = list(map(_convert_to_nonnull_string, columns)) - full_row_str = ( - str_values[0].concat(*str_values[1:]) if len(str_values) > 1 else str_values[0] - ) - full_row_hash = ( - full_row_str.hash() - .name(ordering_hash_part) - .cast(ibis_dtypes.String(nullable=True)) - ) - # By modifying value slightly, we get another hash uncorrelated with the first - full_row_hash_p2 = ( - (full_row_str + "_") - .hash() - .name(ordering_hash_part2) - .cast(ibis_dtypes.String(nullable=True)) - ) - # Used to disambiguate between identical rows (which will have identical hash) - random_value = ( - bigframes_vendored.ibis.random() - .name(ordering_rand_part) - .cast(ibis_dtypes.String(nullable=True)) - ) - - return full_row_hash.concat(full_row_hash_p2, random_value) diff --git a/bigframes/core/compile/ibis_compiler/ibis_compiler.py b/bigframes/core/compile/ibis_compiler/ibis_compiler.py deleted file mode 100644 index 938759ae181..00000000000 --- a/bigframes/core/compile/ibis_compiler/ibis_compiler.py +++ /dev/null @@ -1,307 +0,0 @@ -# Copyright 2023 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -from __future__ import annotations - -import dataclasses -import functools -import typing -from typing import Optional, cast - -import bigframes_vendored.ibis.backends.bigquery as ibis_bigquery -import bigframes_vendored.ibis.expr.api as ibis_api -import bigframes_vendored.ibis.expr.datatypes as ibis_dtypes -import bigframes_vendored.ibis.expr.types as ibis_types - -import bigframes.core.compile.compiled as compiled -import bigframes.core.compile.concat as concat_impl -import bigframes.core.compile.configs as configs -import bigframes.core.compile.explode -import bigframes.core.nodes as nodes -import bigframes.core.ordering as bf_ordering -import bigframes.core.rewrite as rewrites -import bigframes.core.rewrite.schema_binding as schema_binding -from bigframes import dtypes, operations -from bigframes.core import bq_data, expression, pyarrow_utils -from bigframes.core.logging import data_types as data_type_logger - -if typing.TYPE_CHECKING: - import bigframes.core - - -def compile_sql(request: configs.CompileRequest) -> configs.CompileResult: - output_names = tuple((expression.DerefOp(id), id.sql) for id in request.node.ids) - result_node = nodes.ResultNode( - request.node, - output_cols=output_names, - limit=request.peek_count, - ) - if request.sort_rows: - # Can only pullup slice if we are doing ORDER BY in outermost SELECT - # Need to do this before replacing unsupported ops, as that will rewrite slice ops - result_node = rewrites.pull_up_limits(result_node) - result_node = cast(nodes.ResultNode, _replace_unsupported_ops(result_node)) - result_node = cast(nodes.ResultNode, result_node.bottom_up(rewrites.simplify_join)) - # prune before pulling up order to avoid unnnecessary row_number() ops - result_node = cast(nodes.ResultNode, rewrites.column_pruning(result_node)) - result_node = rewrites.defer_order( - result_node, output_hidden_row_keys=request.materialize_all_order_keys - ) - if request.sort_rows: - result_node = cast(nodes.ResultNode, rewrites.column_pruning(result_node)) - encoded_type_refs = data_type_logger.encode_type_refs(result_node) - # Have to bind schema as the final step before compilation. - # Probably, should defer even further - result_node = typing.cast( - nodes.ResultNode, schema_binding.bind_schema_to_tree(result_node) - ) - sql = compile_result_node(result_node) - return configs.CompileResult( - sql, - result_node.schema.to_bigquery(), - result_node.order_by, - encoded_type_refs, - ) - - 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)) - encoded_type_refs = data_type_logger.encode_type_refs(result_node) - # Have to bind schema as the final step before compilation. - # Probably, should defer even further - result_node = typing.cast( - nodes.ResultNode, schema_binding.bind_schema_to_tree(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: - output_order = ( - ordering if ordering.referenced_columns.issubset(result_node.ids) else None - ) - assert (not request.materialize_all_order_keys) or (output_order is not None) - return configs.CompileResult( - sql, result_node.schema.to_bigquery(), output_order, encoded_type_refs - ) - - -def _replace_unsupported_ops(node: nodes.BigFrameNode): - # TODO: Run all replacement rules as single bottom-up pass - node = nodes.bottom_up(node, rewrites.rewrite_slice) - node = nodes.bottom_up(node, rewrites.rewrite_timedelta_expressions) - node = nodes.bottom_up(node, rewrites.rewrite_range_rolling) - node = nodes.bottom_up(node, rewrites.lower_udfs) - return node - - -def compile_result_node(root: nodes.ResultNode) -> str: - return compile_node(root.child).to_sql( - order_by=root.order_by.all_ordering_columns if root.order_by else (), - limit=root.limit, - selections=root.output_cols, - ) - - -# TODO: Remove cache when schema no longer requires compilation to derive schema (and therefor only compiles for execution) -@functools.lru_cache(maxsize=5000) -def compile_node(node: nodes.BigFrameNode) -> compiled.UnorderedIR: - """Compile node into CompileArrayValue. Caches result.""" - return node.reduce_up(lambda node, children: _compile_node(node, *children)) - - -@functools.singledispatch -def _compile_node( - node: nodes.BigFrameNode, *compiled_children: compiled.UnorderedIR -) -> compiled.UnorderedIR: - """Defines transformation but isn't cached, always use compile_node instead""" - raise ValueError(f"Can't compile unrecognized node: {node}") - - -@_compile_node.register -def compile_join( - node: nodes.JoinNode, left: compiled.UnorderedIR, right: compiled.UnorderedIR -): - condition_pairs = tuple( - (left.id.sql, right.id.sql) for left, right in node.conditions - ) - return left.join( - right=right, - type=node.type, - conditions=condition_pairs, - join_nulls=node.joins_nulls, - ) - - -@_compile_node.register -def compile_isin( - node: nodes.InNode, left: compiled.UnorderedIR, right: compiled.UnorderedIR -): - return left.isin_join( - right=right, - indicator_col=node.indicator_col.sql, - conditions=(node.left_col.id.sql, list(node.right_child.ids)[0].sql), - join_nulls=node.joins_nulls, - ) - - -@_compile_node.register -def compile_fromrange( - node: nodes.FromRangeNode, start: compiled.UnorderedIR, end: compiled.UnorderedIR -): - # Both start and end are single elements and do not inherently have an order) - start_table = start._to_ibis_expr() - end_table = end._to_ibis_expr() - - start_column = start_table.schema().names[0] - end_column = end_table.schema().names[0] - - # Perform a cross join to avoid errors - joined_table = start_table.cross_join(end_table) - - labels_array_table = ibis_api.range( - joined_table[start_column], joined_table[end_column] + node.step, node.step - ).name(node.output_id.sql) - labels = ( - typing.cast(ibis_types.ArrayValue, labels_array_table) - .as_table() - .unnest([node.output_id.sql]) - ) - return compiled.UnorderedIR( - labels, - columns=[labels[labels.columns[0]]], - ) - - -@_compile_node.register -def compile_readlocal(node: nodes.ReadLocalNode, *args): - offsets = node.offsets_col.sql if node.offsets_col else None - pa_table = node.local_data_source.data - bq_schema = node.schema.to_bigquery() - - pa_table = pa_table.select([item.source_id for item in node.scan_list.items]) - pa_table = pa_table.rename_columns([item.id.sql for item in node.scan_list.items]) - - if offsets: - pa_table = pyarrow_utils.append_offsets(pa_table, offsets) - return compiled.UnorderedIR.from_polars(pa_table, bq_schema) - - -@_compile_node.register -def compile_readtable(node: nodes.ReadTableNode, *args): - from bigframes.core.compile.ibis_compiler import scalar_op_registry - - ibis_table = _table_to_ibis( - node.source, scan_cols=[col.source_id for col in node.scan_list.items] - ) - - # TODO(b/395912450): Remove workaround solution once b/374784249 got resolved. - for scan_item in node.scan_list.items: - if ( - node.source.schema.get_type(scan_item.source_id) == dtypes.JSON_DTYPE - and ibis_table[scan_item.source_id].type() == ibis_dtypes.string - ): - json_column = scalar_op_registry.parse_json( - ibis_table[scan_item.source_id] - ).name(scan_item.source_id) - ibis_table = ibis_table.mutate(json_column) - - return compiled.UnorderedIR( - ibis_table, - tuple( - ibis_table[scan_item.source_id].name(scan_item.id.sql) - for scan_item in node.scan_list.items - ), - ) - - -def _table_to_ibis( - source: bq_data.BigqueryDataSource, - scan_cols: typing.Sequence[str], -) -> ibis_types.Table: - full_table_name = source.table.get_full_id(quoted=False) - # Physical schema might include unused columns, unsupported datatypes like JSON - physical_schema = ibis_bigquery.BigQuerySchema.to_ibis( - list(source.table.physical_schema) - ) - if source.at_time is not None or source.sql_predicate is not None: - import bigframes.session._io.bigquery - - sql = bigframes.session._io.bigquery.to_query( - full_table_name, - columns=scan_cols, - sql_predicate=source.sql_predicate, - time_travel_timestamp=source.at_time, - ) - return ibis_bigquery.Backend().sql(schema=physical_schema, query=sql) - else: - return ibis_api.table(physical_schema, full_table_name).select(scan_cols) - - -@_compile_node.register -def compile_filter(node: nodes.FilterNode, child: compiled.UnorderedIR): - return child.filter(node.predicate) - - -@_compile_node.register -def compile_selection(node: nodes.SelectionNode, child: compiled.UnorderedIR): - selection = tuple((ref, id.sql) for ref, id in node.input_output_pairs) - return child.selection(selection) - - -@_compile_node.register -def compile_projection(node: nodes.ProjectionNode, child: compiled.UnorderedIR): - projections = ((expr, id.sql) for expr, id in node.assignments) - return child.projection(tuple(projections)) - - -@_compile_node.register -def compile_concat(node: nodes.ConcatNode, *children: compiled.UnorderedIR): - output_ids = [id.sql for id in node.output_ids] - return concat_impl.concat_unordered(children, output_ids) - - -@_compile_node.register -def compile_aggregate(node: nodes.AggregateNode, child: compiled.UnorderedIR): - aggs = tuple((agg, id.sql) for agg, id in node.aggregations) - result = child.aggregate(aggs, node.by_column_ids, order_by=node.order_by) - # TODO: Remove dropna field and use filter node instead - if node.dropna: - for key in node.by_column_ids: - if node.child.field_by_id[key.id].nullable: - result = result.filter(operations.notnull_op.as_expr(key)) - return result - - -@_compile_node.register -def compile_window(node: nodes.WindowOpNode, child: compiled.UnorderedIR): - result = child - for cdef in node.agg_exprs: - result = result.project_window_op( - cdef.expression, # type: ignore - node.window_spec, - cdef.id.sql, - ) - return result - - -@_compile_node.register -def compile_explode(node: nodes.ExplodeNode, child: compiled.UnorderedIR): - offsets_col = node.offsets_col.sql if (node.offsets_col is not None) else None - return bigframes.core.compile.explode.explode_unordered( - child, node.column_ids, offsets_col - ) - - -@_compile_node.register -def compile_random_sample(node: nodes.RandomSampleNode, child: compiled.UnorderedIR): - return child._uniform_sampling(node.fraction) diff --git a/bigframes/core/compile/ibis_compiler/operations/__init__.py b/bigframes/core/compile/ibis_compiler/operations/__init__.py deleted file mode 100644 index 9d9f3849abe..00000000000 --- a/bigframes/core/compile/ibis_compiler/operations/__init__.py +++ /dev/null @@ -1,21 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -"""Operation implementations for the Ibis-based compiler. - -This directory structure should reflect the same layout as the -`bigframes/operations` directory where the operations are defined. - -Prefer a few ops per file to keep file sizes manageable for text editors and LLMs. -""" diff --git a/bigframes/core/compile/ibis_compiler/operations/generic_ops.py b/bigframes/core/compile/ibis_compiler/operations/generic_ops.py deleted file mode 100644 index 78f6a0c4de8..00000000000 --- a/bigframes/core/compile/ibis_compiler/operations/generic_ops.py +++ /dev/null @@ -1,38 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -""" -BigFrames -> Ibis compilation for the operations in bigframes.operations.generic_ops. - -Please keep implementations in sequential order by op name. -""" - -from __future__ import annotations - -from bigframes_vendored.ibis.expr import types as ibis_types - -from bigframes.core.compile.ibis_compiler import scalar_op_compiler -from bigframes.operations import generic_ops - -register_unary_op = scalar_op_compiler.scalar_op_compiler.register_unary_op - - -@register_unary_op(generic_ops.notnull_op) -def notnull_op_impl(x: ibis_types.Value): - return x.notnull() - - -@register_unary_op(generic_ops.isnull_op) -def isnull_op_impl(x: ibis_types.Value): - return x.isnull() diff --git a/bigframes/core/compile/ibis_compiler/operations/geo_ops.py b/bigframes/core/compile/ibis_compiler/operations/geo_ops.py deleted file mode 100644 index 772752112a4..00000000000 --- a/bigframes/core/compile/ibis_compiler/operations/geo_ops.py +++ /dev/null @@ -1,192 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from __future__ import annotations - -from typing import cast - -import bigframes_vendored.ibis.expr.datatypes as ibis_dtypes -import bigframes_vendored.ibis.expr.operations.geospatial as ibis_geo -import bigframes_vendored.ibis.expr.operations.udf as ibis_udf -from bigframes_vendored import ibis -from bigframes_vendored.ibis.expr import types as ibis_types - -from bigframes.core.compile.ibis_compiler import scalar_op_compiler -from bigframes.operations import geo_ops as ops - -register_unary_op = scalar_op_compiler.scalar_op_compiler.register_unary_op -register_binary_op = scalar_op_compiler.scalar_op_compiler.register_binary_op - - -# Geo Ops -@register_unary_op(ops.geo_st_astext_op) -def geo_st_astext_op_impl(x: ibis_types.Value): - return cast(ibis_types.GeoSpatialValue, x).as_text() - - -@register_unary_op(ops.geo_st_boundary_op, pass_op=False) -def geo_st_boundary_op_impl(x: ibis_types.Value): - return st_boundary(x) - - -@register_unary_op(ops.GeoStBufferOp, pass_op=True) -def geo_st_buffer_op_impl(x: ibis_types.Value, op: ops.GeoStBufferOp): - return st_buffer( - x, - op.buffer_radius, - op.num_seg_quarter_circle, - op.use_spheroid, - ) - - -@register_unary_op(ops.geo_st_convexhull_op, pass_op=False) -def geo_st_convexhull_op_impl(x: ibis_types.Value): - return st_convexhull(x) - - -@register_binary_op(ops.geo_st_difference_op, pass_op=False) -def geo_st_difference_op_impl(x: ibis_types.Value, y: ibis_types.Value): - return cast(ibis_types.GeoSpatialValue, x).difference( - cast(ibis_types.GeoSpatialValue, y) - ) - - -@register_binary_op(ops.GeoStDistanceOp, pass_op=True) -def geo_st_distance_op_impl( - x: ibis_types.Value, y: ibis_types.Value, op: ops.GeoStDistanceOp -): - return st_distance(x, y, op.use_spheroid) - - -@register_unary_op(ops.geo_st_geogfromtext_op) -def geo_st_geogfromtext_op_impl(x: ibis_types.Value): - # Ibis doesn't seem to provide a dedicated method to cast from string to geography, - # so we use a BigQuery scalar function, st_geogfromtext(), directly. - return st_geogfromtext(x) - - -@register_binary_op(ops.geo_st_geogpoint_op, pass_op=False) -def geo_st_geogpoint_op_impl(x: ibis_types.Value, y: ibis_types.Value): - return cast(ibis_types.NumericValue, x).point(cast(ibis_types.NumericValue, y)) - - -@register_binary_op(ops.geo_st_intersection_op, pass_op=False) -def geo_st_intersection_op_impl(x: ibis_types.Value, y: ibis_types.Value): - return cast(ibis_types.GeoSpatialValue, x).intersection( - cast(ibis_types.GeoSpatialValue, y) - ) - - -@register_unary_op(ops.geo_st_isclosed_op, pass_op=False) -def geo_st_isclosed_op_impl(x: ibis_types.Value): - return st_isclosed(x) - - -@register_unary_op(ops.GeoStRegionStatsOp, pass_op=True) -def geo_st_regionstats_op_impl( - geography: ibis_types.Value, - op: ops.GeoStRegionStatsOp, -): - if op.band: - band = ibis.literal(op.band, type=ibis_dtypes.string()) - else: - band = None - - if op.include: - include = ibis.literal(op.include, type=ibis_dtypes.string()) - else: - include = None - - if op.options: - options = ibis.literal(op.options, type=ibis_dtypes.json()) - else: - options = None - - return ibis_geo.GeoRegionStats( - arg=geography, # type: ignore - raster_id=ibis.literal(op.raster_id, type=ibis_dtypes.string()), # type: ignore - band=band, # type: ignore - include=include, # type: ignore - options=options, # type: ignore - ).to_expr() - - -@register_unary_op(ops.geo_x_op) -def geo_x_op_impl(x: ibis_types.Value): - return cast(ibis_types.GeoSpatialValue, x).x() - - -@register_unary_op(ops.GeoStLengthOp, pass_op=True) -def geo_length_op_impl(x: ibis_types.Value, op: ops.GeoStLengthOp): - # Call the st_length UDF defined in this file (or imported) - return st_length(x, op.use_spheroid) - - -@register_unary_op(ops.geo_y_op) -def geo_y_op_impl(x: ibis_types.Value): - return cast(ibis_types.GeoSpatialValue, x).y() - - -@ibis_udf.scalar.builtin -def st_convexhull(x: ibis_dtypes.geography) -> ibis_dtypes.geography: # type: ignore - """ST_CONVEXHULL""" - ... - - -@ibis_udf.scalar.builtin -def st_geogfromtext(a: str) -> ibis_dtypes.geography: # type: ignore - """Convert string to geography.""" - - -@ibis_udf.scalar.builtin -def st_boundary(a: ibis_dtypes.geography) -> ibis_dtypes.geography: # type: ignore - """Find the boundary of a geography.""" - - -@ibis_udf.scalar.builtin -def st_buffer( - geography: ibis_dtypes.geography, # type: ignore - buffer_radius: ibis_dtypes.Float64, - num_seg_quarter_circle: ibis_dtypes.Float64, - use_spheroid: ibis_dtypes.Boolean, -) -> ibis_dtypes.geography: # type: ignore - ... - - -@ibis_udf.scalar.builtin -def st_distance( - a: ibis_dtypes.geography, # type: ignore - b: ibis_dtypes.geography, # type: ignore - use_spheroid: bool, # type: ignore -) -> ibis_dtypes.float: # type: ignore - """Convert string to geography.""" - - -@ibis_udf.scalar.builtin -def st_length(geog: ibis_dtypes.geography, use_spheroid: bool) -> ibis_dtypes.float: # type: ignore - """ST_LENGTH BQ builtin. This body is never executed.""" - pass - - -@ibis_udf.scalar.builtin -def st_isclosed(a: ibis_dtypes.geography) -> ibis_dtypes.boolean: # type: ignore - """Checks if a geography is closed.""" - - -@ibis_udf.scalar.builtin -def st_simplify( - geography: ibis_dtypes.geography, # type: ignore - tolerance_meters: ibis_dtypes.float, # type: ignore -) -> ibis_dtypes.geography: # type: ignore - ... diff --git a/bigframes/core/compile/ibis_compiler/scalar_op_compiler.py b/bigframes/core/compile/ibis_compiler/scalar_op_compiler.py deleted file mode 100644 index 31a8459923c..00000000000 --- a/bigframes/core/compile/ibis_compiler/scalar_op_compiler.py +++ /dev/null @@ -1,330 +0,0 @@ -# Copyright 2023 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. - -"""To avoid circular imports, this module should _not_ depend on any ops.""" - -from __future__ import annotations - -import functools -import typing -from typing import TYPE_CHECKING - -import bigframes_vendored.ibis -import bigframes_vendored.ibis.expr.operations.generic as ibis_generic -import bigframes_vendored.ibis.expr.types as ibis_types - -import bigframes.core.compile.ibis_types -import bigframes.core.expression as ex -from bigframes.core import agg_expressions, ordering -from bigframes.operations import googlesql as gsql_ops -from bigframes.operations import numeric_ops - -if TYPE_CHECKING: - import bigframes.operations as ops - - -class ExpressionCompiler: - # Mapping of operation name to implemenations - _registry: dict[ - str, - typing.Callable[ - [typing.Sequence[ibis_types.Value], ops.RowOp], ibis_types.Value - ], - ] = {} - - @functools.singledispatchmethod - def compile_expression( - self, - expression: ex.Expression, - bindings: typing.Dict[str, ibis_types.Value], - ) -> ibis_types.Value: - raise NotImplementedError(f"Unrecognized expression: {expression}") - - @compile_expression.register - def _( - self, - expression: ex.ScalarConstantExpression, - bindings: typing.Dict[str, ibis_types.Value], - ) -> ibis_types.Value: - return bigframes.core.compile.ibis_types.literal_to_ibis_scalar( - expression.value, expression.dtype - ) - - @compile_expression.register - def _( - self, - expression: ex.DerefOp, - bindings: typing.Dict[str, ibis_types.Value], - ) -> ibis_types.Value: - if expression.id.sql not in bindings: - raise ValueError(f"Could not resolve unbound variable {expression.id}") - else: - return bindings[expression.id.sql] - - @compile_expression.register - def _( - self, - expression: agg_expressions.WindowExpression, - bindings: typing.Dict[str, ibis_types.Value], - ) -> ibis_types.Value: - import bigframes.core.compile.ibis_compiler.aggregate_compiler as agg_compile - - return agg_compile.compile_analytic( - expression.analytic_expr, expression.window, bindings - ) - - @compile_expression.register - def _( - self, - expression: ex.OpExpression, - bindings: typing.Dict[str, ibis_types.Value], - ) -> ibis_types.Value: - inputs = [ - self.compile_expression(sub_expr, bindings) - for sub_expr in expression.inputs - ] - if isinstance(expression.op, gsql_ops.GoogleSqlScalarOp): - return googlesql_scalar_op_impl( - *inputs, op=expression.op, output_type=expression.output_type - ) - return self.compile_row_op(expression.op, inputs) - - @compile_expression.register - def _( - self, - expression: ex.OmittedArg, - bindings: typing.Dict[str, ibis_types.Value], - ) -> ibis_types.Value: - return bigframes_vendored.ibis.omitted() - - def compile_row_op( - self, op: ops.RowOp, inputs: typing.Sequence[ibis_types.Value] - ) -> ibis_types.Value: - impl = self._registry[op.name] - return impl(inputs, op) - - def register_unary_op( - self, - op_ref: typing.Union[ops.UnaryOp, type[ops.UnaryOp]], - pass_op: bool = False, - ): - """ - Decorator to register a unary op implementation. - - Args: - op_ref (UnaryOp or UnaryOp type): - Class or instance of operator that is implemented by the decorated function. - pass_op (bool): - Set to true if implementation takes the operator object as the last argument. - This is needed for parameterized ops where parameters are part of op object. - """ - key = typing.cast(str, op_ref.name) - - def decorator(impl: typing.Callable[..., ibis_types.Value]): - def normalized_impl(args: typing.Sequence[ibis_types.Value], op: ops.RowOp): - if pass_op: - return impl(args[0], op) - else: - return impl(args[0]) - - self._register(key, normalized_impl) - return impl - - return decorator - - def register_binary_op( - self, - op_ref: typing.Union[ops.BinaryOp, type[ops.BinaryOp]], - pass_op: bool = False, - ): - """ - Decorator to register a binary op implementation. - - Args: - op_ref (BinaryOp or BinaryOp type): - Class or instance of operator that is implemented by the decorated function. - pass_op (bool): - Set to true if implementation takes the operator object as the last argument. - This is needed for parameterized ops where parameters are part of op object. - """ - key = typing.cast(str, op_ref.name) - - def decorator(impl: typing.Callable[..., ibis_types.Value]): - def normalized_impl(args: typing.Sequence[ibis_types.Value], op: ops.RowOp): - if pass_op: - return impl(args[0], args[1], op) - else: - return impl(args[0], args[1]) - - self._register(key, normalized_impl) - return impl - - return decorator - - def register_ternary_op( - self, op_ref: typing.Union[ops.TernaryOp, type[ops.TernaryOp]] - ): - """ - Decorator to register a ternary op implementation. - - Args: - op_ref (TernaryOp or TernaryOp type): - Class or instance of operator that is implemented by the decorated function. - """ - key = typing.cast(str, op_ref.name) - - def decorator(impl: typing.Callable[..., ibis_types.Value]): - def normalized_impl(args: typing.Sequence[ibis_types.Value], op: ops.RowOp): - return impl(args[0], args[1], args[2]) - - self._register(key, normalized_impl) - return impl - - return decorator - - def register_nary_op( - self, op_ref: typing.Union[ops.NaryOp, type[ops.NaryOp]], pass_op: bool = False - ): - """ - Decorator to register a nary op implementation. - - Args: - op_ref (NaryOp or NaryOp type): - Class or instance of operator that is implemented by the decorated function. - pass_op (bool): - Set to true if implementation takes the operator object as the last argument. - This is needed for parameterized ops where parameters are part of op object. - """ - key = typing.cast(str, op_ref.name) - - def decorator(impl: typing.Callable[..., ibis_types.Value]): - def normalized_impl(args: typing.Sequence[ibis_types.Value], op: ops.RowOp): - if pass_op: - return impl(*args, op=op) - else: - return impl(*args) - - self._register(key, normalized_impl) - return impl - - return decorator - - def _register( - self, - op_name: str, - impl: typing.Callable[ - [typing.Sequence[ibis_types.Value], ops.RowOp], ibis_types.Value - ], - ): - if op_name in self._registry: - raise ValueError(f"Operation name {op_name} already registered") - self._registry[op_name] = impl - - def _convert_row_ordering_to_table_values( - self, - value_lookup: typing.Mapping[str, ibis_types.Value], - ordering_columns: typing.Sequence[ordering.OrderingExpression], - ) -> typing.Sequence[ibis_types.Value]: - column_refs = ordering_columns - ordering_values = [] - for ordering_col in column_refs: - expr = self.compile_expression(ordering_col.scalar_expression, value_lookup) - ordering_value = ( - bigframes_vendored.ibis.asc(expr) # type: ignore - if ordering_col.direction.is_ascending - else bigframes_vendored.ibis.desc(expr) # type: ignore - ) - # Bigquery SQL considers NULLS to be "smallest" values, but we need to override in these cases. - if (not ordering_col.na_last) and (not ordering_col.direction.is_ascending): - # Force nulls to be first - is_null_val = typing.cast(ibis_types.Column, expr.isnull()) - ordering_values.append(bigframes_vendored.ibis.desc(is_null_val)) - elif (ordering_col.na_last) and (ordering_col.direction.is_ascending): - # Force nulls to be last - is_null_val = typing.cast(ibis_types.Column, expr.isnull()) - ordering_values.append(bigframes_vendored.ibis.asc(is_null_val)) - ordering_values.append(ordering_value) - return ordering_values - - def _convert_range_ordering_to_table_value( - self, - value_lookup: typing.Mapping[str, ibis_types.Value], - ordering_column: ordering.OrderingExpression, - ) -> ibis_types.Value: - """Converts the ordering for range windows to Ibis references. - - Note that this method is different from `_convert_row_ordering_to_table_values` in - that it does not arrange null values. There are two reasons: - 1. Manipulating null positions requires more than one ordering key, which is forbidden - by SQL window syntax for range rolling. - 2. Pandas does not allow range rolling on timeseries with nulls. - - Therefore, we opt for the simplest approach here: generate the simplest SQL and follow - the BigQuery engine behavior. - """ - expr = self.compile_expression(ordering_column.scalar_expression, value_lookup) - - if ordering_column.direction.is_ascending: - return bigframes_vendored.ibis.asc(expr) # type: ignore - return bigframes_vendored.ibis.desc(expr) # type: ignore - - -# Singleton compiler -scalar_op_compiler = ExpressionCompiler() - - -@scalar_op_compiler.register_unary_op(numeric_ops.isnan_op) -def isnanornull(arg): - return arg.isnan() - - -@scalar_op_compiler.register_unary_op(numeric_ops.isfinite_op) -def isfinite(arg): - return arg.isinf().negate() & arg.isnan().negate() - - -def googlesql_scalar_op_impl( - *operands: ibis_types.Value, op: ops.GoogleSqlScalarOp, output_type -): - final_operands: list[ibis_types.Value] = [] - arg_templates = [] - for i, operand in enumerate(operands): - if i < len(op.args): - arg_spec = op.args[i] - else: - assert op.args[-1].is_vararg, ( - f"Too many arguments, for {op.sql_name}, expected {len(op.args)}" - ) - arg_spec = op.args[-1] - if isinstance(operand.op(), ibis_generic.OmittedArg): - assert arg_spec.optional, "Argument omitted, but not optional" - continue - - target_idx = len(final_operands) - final_operands.append(operand) - if arg_spec.arg_name: - arg_templates.append(f"{arg_spec.arg_name} => {{{target_idx}}}") - else: - arg_templates.append(f"{{{target_idx}}}") - args_template = ", ".join(arg_templates) - sql_template = f"{op.sql_name}({args_template})" - return ibis_generic.SqlScalar( - sql_template, - values=tuple( - typing.cast(ibis_generic.Value, expr.op()) for expr in final_operands - ), - output_type=bigframes.core.compile.ibis_types.bigframes_dtype_to_ibis_dtype( - output_type - ), - ).to_expr() diff --git a/bigframes/core/compile/ibis_compiler/scalar_op_registry.py b/bigframes/core/compile/ibis_compiler/scalar_op_registry.py deleted file mode 100644 index 530d23a8b06..00000000000 --- a/bigframes/core/compile/ibis_compiler/scalar_op_registry.py +++ /dev/null @@ -1,2248 +0,0 @@ -# Copyright 2023 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 functools -import typing -from typing import Any, cast - -import bigframes_vendored.ibis.expr.api as ibis_api -import bigframes_vendored.ibis.expr.datatypes as ibis_dtypes -import bigframes_vendored.ibis.expr.operations.ai_ops as ai_ops -import bigframes_vendored.ibis.expr.operations.generic as ibis_generic -import bigframes_vendored.ibis.expr.operations.udf as ibis_udf -import bigframes_vendored.ibis.expr.types as ibis_types -import numpy as np -import pandas as pd -from bigframes_vendored import ibis - -import bigframes.core.compile.ibis_compiler.default_ordering -import bigframes.core.compile.ibis_types -import bigframes.operations as ops -from bigframes.core.compile.constants import UNIT_TO_US_CONVERSION_FACTORS -from bigframes.core.compile.ibis_compiler.scalar_op_compiler import ( - scalar_op_compiler, # TODO(tswast): avoid import of variables -) - -_ZERO = typing.cast(ibis_types.NumericValue, ibis_types.literal(0)) -_NAN = typing.cast(ibis_types.NumericValue, ibis_types.literal(np.nan)) -_INF = typing.cast(ibis_types.NumericValue, ibis_types.literal(np.inf)) -_NEG_INF = typing.cast(ibis_types.NumericValue, ibis_types.literal(-np.inf)) - -# Approx Highest number you can pass in to EXP function and get a valid FLOAT64 result -# FLOAT64 has 11 exponent bits, so max values is about 2**(2**10) -# ln(2**(2**10)) == (2**10)*ln(2) ~= 709.78, so EXP(x) for x>709.78 will overflow. -_FLOAT64_EXP_BOUND = typing.cast(ibis_types.NumericValue, ibis_types.literal(709.78)) - -_OBJ_REF_STRUCT_SCHEMA = ( - ("uri", ibis_dtypes.String), - ("version", ibis_dtypes.String), - ("authorizer", ibis_dtypes.String), - ("details", ibis_dtypes.JSON), -) -_OBJ_REF_IBIS_DTYPE = ibis_dtypes.Struct.from_tuples(_OBJ_REF_STRUCT_SCHEMA) # type: ignore - - -### Unary Ops -@scalar_op_compiler.register_unary_op(ops.hash_op) -def hash_op_impl(x: ibis_types.Value): - return typing.cast(ibis_types.IntegerValue, x).hash() - - -# Trig Functions -@scalar_op_compiler.register_unary_op(ops.sin_op) -def sin_op_impl(x: ibis_types.Value): - return typing.cast(ibis_types.NumericValue, x).sin() - - -@scalar_op_compiler.register_unary_op(ops.cos_op) -def cos_op_impl(x: ibis_types.Value): - return typing.cast(ibis_types.NumericValue, x).cos() - - -@scalar_op_compiler.register_unary_op(ops.tan_op) -def tan_op_impl(x: ibis_types.Value): - return typing.cast(ibis_types.NumericValue, x).tan() - - -# Inverse trig functions -@scalar_op_compiler.register_unary_op(ops.arcsin_op) -def arcsin_op_impl(x: ibis_types.Value): - numeric_value = typing.cast(ibis_types.NumericValue, x) - domain = numeric_value.abs() <= _ibis_num(1) - return (~domain).ifelse(_NAN, numeric_value.asin()) - - -@scalar_op_compiler.register_unary_op(ops.arccos_op) -def arccos_op_impl(x: ibis_types.Value): - numeric_value = typing.cast(ibis_types.NumericValue, x) - domain = numeric_value.abs() <= _ibis_num(1) - return (~domain).ifelse(_NAN, numeric_value.acos()) - - -@scalar_op_compiler.register_unary_op(ops.arctan_op) -def arctan_op_impl(x: ibis_types.Value): - return typing.cast(ibis_types.NumericValue, x).atan() - - -@scalar_op_compiler.register_binary_op(ops.arctan2_op) -def arctan2_op_impl(x: ibis_types.Value, y: ibis_types.Value): - return typing.cast(ibis_types.NumericValue, x).atan2( - typing.cast(ibis_types.NumericValue, y) - ) - - -# Hyperbolic trig functions -# BQ has these functions, but Ibis doesn't -@scalar_op_compiler.register_unary_op(ops.sinh_op) -def sinh_op_impl(x: ibis_types.Value): - numeric_value = typing.cast(ibis_types.NumericValue, x) - sinh_result = (numeric_value.exp() - (numeric_value.negate()).exp()) / _ibis_num(2) - domain = numeric_value.abs() < _FLOAT64_EXP_BOUND - return (~domain).ifelse(_INF * numeric_value.sign(), sinh_result) - - -@scalar_op_compiler.register_unary_op(ops.cosh_op) -def cosh_op_impl(x: ibis_types.Value): - numeric_value = typing.cast(ibis_types.NumericValue, x) - cosh_result = (numeric_value.exp() + (numeric_value.negate()).exp()) / _ibis_num(2) - domain = numeric_value.abs() < _FLOAT64_EXP_BOUND - return (~domain).ifelse(_INF, cosh_result) - - -@scalar_op_compiler.register_unary_op(ops.tanh_op) -def tanh_op_impl(x: ibis_types.Value): - numeric_value = typing.cast(ibis_types.NumericValue, x) - tanh_result = (numeric_value.exp() - (numeric_value.negate()).exp()) / ( - numeric_value.exp() + (numeric_value.negate()).exp() - ) - # Beyond +-20, is effectively just the sign function - domain = numeric_value.abs() < _ibis_num(20) - return (~domain).ifelse(numeric_value.sign(), tanh_result) - - -@scalar_op_compiler.register_unary_op(ops.arcsinh_op) -def arcsinh_op_impl(x: ibis_types.Value): - numeric_value = typing.cast(ibis_types.NumericValue, x) - sqrt_part = ((numeric_value * numeric_value) + _ibis_num(1)).sqrt() - return (numeric_value.abs() + sqrt_part).ln() * numeric_value.sign() - - -@scalar_op_compiler.register_unary_op(ops.arccosh_op) -def arccosh_op_impl(x: ibis_types.Value): - numeric_value = typing.cast(ibis_types.NumericValue, x) - sqrt_part = ((numeric_value * numeric_value) - _ibis_num(1)).sqrt() - acosh_result = (numeric_value + sqrt_part).ln() - domain = numeric_value >= _ibis_num(1) - return (~domain).ifelse(_NAN, acosh_result) - - -@scalar_op_compiler.register_unary_op(ops.arctanh_op) -def arctanh_op_impl(x: ibis_types.Value): - numeric_value = typing.cast(ibis_types.NumericValue, x) - domain = numeric_value.abs() < _ibis_num(1) - numerator = numeric_value + _ibis_num(1) - denominator = _ibis_num(1) - numeric_value - ln_input = typing.cast(ibis_types.NumericValue, numerator.div(denominator)) - atanh_result = ln_input.ln().div(2) - - out_of_domain = (numeric_value.abs() == _ibis_num(1)).ifelse( - _INF * numeric_value, _NAN - ) - - return (~domain).ifelse(out_of_domain, atanh_result) - - -# Numeric Ops -@scalar_op_compiler.register_unary_op(ops.floor_op) -def floor_op_impl(x: ibis_types.Value): - x_numeric = typing.cast(ibis_types.NumericValue, x) - if x_numeric.type().is_boolean(): - return x_numeric.cast(ibis_dtypes.Int64()).cast(ibis_dtypes.Float64()) - if x_numeric.type().is_integer(): - return x_numeric.cast(ibis_dtypes.Float64()) - if x_numeric.type().is_floating(): - # Default ibis impl tries to cast to integer, which doesn't match pandas and can overflow - return float_floor(x_numeric) - else: # numeric - return x_numeric.floor() - - -@scalar_op_compiler.register_unary_op(ops.ceil_op) -def ceil_op_impl(x: ibis_types.Value): - x_numeric = typing.cast(ibis_types.NumericValue, x) - if x_numeric.type().is_boolean(): - return x_numeric.cast(ibis_dtypes.Int64()).cast(ibis_dtypes.Float64()) - if x_numeric.type().is_integer(): - return x_numeric.cast(ibis_dtypes.Float64()) - if x_numeric.type().is_floating(): - # Default ibis impl tries to cast to integer, which doesn't match pandas and can overflow - return float_ceil(x_numeric) - else: # numeric - return x_numeric.ceil() - - -@scalar_op_compiler.register_unary_op(ops.abs_op) -def abs_op_impl(x: ibis_types.Value): - return typing.cast(ibis_types.NumericValue, x).abs() - - -@scalar_op_compiler.register_unary_op(ops.pos_op) -def pos_op_impl(x: ibis_types.Value): - return typing.cast(ibis_types.NumericValue, x) - - -@scalar_op_compiler.register_unary_op(ops.neg_op) -def neg_op_impl(x: ibis_types.Value): - return typing.cast(ibis_types.NumericValue, x).negate() - - -@scalar_op_compiler.register_unary_op(ops.sqrt_op) -def sqrt_op_impl(x: ibis_types.Value): - numeric_value = typing.cast(ibis_types.NumericValue, x) - domain = numeric_value >= _ZERO - return (~domain).ifelse(_NAN, numeric_value.sqrt()) - - -@scalar_op_compiler.register_unary_op(ops.log10_op) -def log10_op_impl(x: ibis_types.Value): - numeric_value = typing.cast(ibis_types.NumericValue, x) - domain = numeric_value > _ZERO - out_of_domain = (numeric_value == _ZERO).ifelse(_NEG_INF, _NAN) - return (~domain).ifelse(out_of_domain, numeric_value.log10()) - - -@scalar_op_compiler.register_unary_op(ops.ln_op) -def ln_op_impl(x: ibis_types.Value): - numeric_value = typing.cast(ibis_types.NumericValue, x) - domain = numeric_value > _ZERO - out_of_domain = (numeric_value == _ZERO).ifelse(_NEG_INF, _NAN) - return (~domain).ifelse(out_of_domain, numeric_value.ln()) - - -@scalar_op_compiler.register_unary_op(ops.log1p_op) -def log1p_op_impl(x: ibis_types.Value): - return ln_op_impl(_ibis_num(1) + x) - - -@scalar_op_compiler.register_unary_op(ops.exp_op) -def exp_op_impl(x: ibis_types.Value): - numeric_value = typing.cast(ibis_types.NumericValue, x) - domain = numeric_value < _FLOAT64_EXP_BOUND - return (~domain).ifelse(_INF, numeric_value.exp()) - - -@scalar_op_compiler.register_unary_op(ops.expm1_op) -def expm1_op_impl(x: ibis_types.Value): - return exp_op_impl(x) - _ibis_num(1) - - -@scalar_op_compiler.register_unary_op(ops.invert_op) -def invert_op_impl(x: ibis_types.Value): - return x.__invert__() # type: ignore - - -## String Operation -@scalar_op_compiler.register_unary_op(ops.len_op) -def len_op_impl(x: ibis_types.Value): - return typing.cast(ibis_types.StringValue, x).length().cast(ibis_dtypes.int64) - - -@scalar_op_compiler.register_unary_op(ops.reverse_op) -def reverse_op_impl(x: ibis_types.Value): - return typing.cast(ibis_types.StringValue, x).reverse() - - -@scalar_op_compiler.register_unary_op(ops.lower_op) -def lower_op_impl(x: ibis_types.Value): - return typing.cast(ibis_types.StringValue, x).lower() - - -@scalar_op_compiler.register_unary_op(ops.upper_op) -def upper_op_impl(x: ibis_types.Value): - return typing.cast(ibis_types.StringValue, x).upper() - - -@scalar_op_compiler.register_unary_op(ops.StrLstripOp, pass_op=True) -def str_lstrip_op_impl(x: ibis_types.Value, op: ops.StrStripOp): - return str_lstrip_op(x, to_strip=op.to_strip) - - -@scalar_op_compiler.register_unary_op(ops.StrRstripOp, pass_op=True) -def str_rstrip_op_impl(x: ibis_types.Value, op: ops.StrRstripOp): - return str_rstrip_op(x, to_strip=op.to_strip) - - -@scalar_op_compiler.register_unary_op(ops.StrStripOp, pass_op=True) -def str_strip_op_impl(x: ibis_types.Value, op: ops.StrStripOp): - return str_strip_op(x, to_strip=op.to_strip) - - -@scalar_op_compiler.register_unary_op(ops.isnumeric_op) -def isnumeric_op_impl(x: ibis_types.Value): - # catches all members of the Unicode number class, which matches pandas isnumeric - # see https://cloud.google.com/bigquery/docs/reference/standard-sql/string_functions#regexp_contains - # TODO: Validate correctness, my miss eg ⅕ character - return typing.cast(ibis_types.StringValue, x).re_search(r"^(\pN+)$") - - -@scalar_op_compiler.register_unary_op(ops.isalpha_op) -def isalpha_op_impl(x: ibis_types.Value): - return typing.cast(ibis_types.StringValue, x).re_search( - r"^(\p{Lm}|\p{Lt}|\p{Lu}|\p{Ll}|\p{Lo})+$" - ) - - -@scalar_op_compiler.register_unary_op(ops.isdigit_op) -def isdigit_op_impl(x: ibis_types.Value): - return typing.cast(ibis_types.StringValue, x).re_search( - r"^[\p{Nd}\x{00B9}\x{00B2}\x{00B3}\x{2070}\x{2074}-\x{2079}\x{2080}-\x{2089}]+$" - ) - - -@scalar_op_compiler.register_unary_op(ops.isdecimal_op) -def isdecimal_op_impl(x: ibis_types.Value): - return typing.cast(ibis_types.StringValue, x).re_search(r"^(\p{Nd})+$") - - -@scalar_op_compiler.register_unary_op(ops.isalnum_op) -def isalnum_op_impl(x: ibis_types.Value): - return typing.cast(ibis_types.StringValue, x).re_search( - r"^(\p{N}|\p{Lm}|\p{Lt}|\p{Lu}|\p{Ll}|\p{Lo})+$" - ) - - -@scalar_op_compiler.register_unary_op(ops.isspace_op) -def isspace_op_impl(x: ibis_types.Value): - # All characters are whitespace characters, False for empty string - return typing.cast(ibis_types.StringValue, x).re_search(r"^\s+$") - - -@scalar_op_compiler.register_unary_op(ops.islower_op) -def islower_op_impl(x: ibis_types.Value): - # No upper case characters, min one cased character - # See: https://docs.python.org/3/library/stdtypes.html#str - return typing.cast(ibis_types.StringValue, x).re_search(r"\p{Ll}") & ~typing.cast( - ibis_types.StringValue, x - ).re_search(r"\p{Lu}|\p{Lt}") - - -@scalar_op_compiler.register_unary_op(ops.isupper_op) -def isupper_op_impl(x: ibis_types.Value): - # No lower case characters, min one cased character - # See: https://docs.python.org/3/library/stdtypes.html#str - return typing.cast(ibis_types.StringValue, x).re_search(r"\p{Lu}") & ~typing.cast( - ibis_types.StringValue, x - ).re_search(r"\p{Ll}|\p{Lt}") - - -@scalar_op_compiler.register_unary_op(ops.capitalize_op) -def capitalize_op_impl(x: ibis_types.Value): - return typing.cast(ibis_types.StringValue, x).capitalize() - - -@scalar_op_compiler.register_unary_op(ops.StrContainsOp, pass_op=True) -def strcontains_op(x: ibis_types.Value, op: ops.StrContainsOp): - return typing.cast(ibis_types.StringValue, x).contains(op.pat) - - -@scalar_op_compiler.register_unary_op(ops.StrContainsRegexOp, pass_op=True) -def contains_regex_op_impl(x: ibis_types.Value, op: ops.StrContainsRegexOp): - return typing.cast(ibis_types.StringValue, x).re_search(op.pat) - - -@scalar_op_compiler.register_unary_op(ops.StrPadOp, pass_op=True) -def strpad_op_impl(x: ibis_types.Value, op: ops.StrPadOp): - str_val = typing.cast(ibis_types.StringValue, x) - - # SQL pad operations will truncate, we do not want to truncate though. - pad_length = typing.cast( - ibis_types.IntegerValue, ibis_api.greatest(str_val.length(), op.length) - ) - if op.side == "left": - return str_val.lpad(pad_length, op.fillchar) - elif op.side == "right": - return str_val.rpad(pad_length, op.fillchar) - else: # side == both - # Pad more on right side if can't pad both sides equally - two = typing.cast(ibis_types.IntegerValue, 2) - lpad_amount = ((pad_length - str_val.length()) // two) + str_val.length() - return str_val.lpad( - length=typing.cast(ibis_types.IntegerValue, lpad_amount), pad=op.fillchar - ).rpad(pad_length, op.fillchar) - - -@scalar_op_compiler.register_unary_op(ops.ReplaceStrOp, pass_op=True) -def replacestring_op_impl(x: ibis_types.Value, op: ops.ReplaceStrOp): - pat_str_value = typing.cast(ibis_types.StringValue, ibis_types.literal(op.pat)) - repl_str_value = typing.cast(ibis_types.StringValue, ibis_types.literal(op.repl)) - return typing.cast(ibis_types.StringValue, x).replace(pat_str_value, repl_str_value) - - -@scalar_op_compiler.register_unary_op(ops.RegexReplaceStrOp, pass_op=True) -def replaceregex_op_impl(x: ibis_types.Value, op: ops.RegexReplaceStrOp): - return typing.cast(ibis_types.StringValue, x).re_replace(op.pat, op.repl) - - -@scalar_op_compiler.register_unary_op(ops.StartsWithOp, pass_op=True) -def startswith_op_impl(x: ibis_types.Value, op: ops.StartsWithOp): - any_match = None - for pat in op.pat: - pat_match = typing.cast(ibis_types.StringValue, x).startswith(pat) - if any_match is not None: - any_match = any_match | pat_match - else: - any_match = pat_match - return any_match if any_match is not None else ibis_types.literal(False) - - -@scalar_op_compiler.register_unary_op(ops.EndsWithOp, pass_op=True) -def endswith_op_impl(x: ibis_types.Value, op: ops.EndsWithOp): - any_match = None - for pat in op.pat: - pat_match = typing.cast(ibis_types.StringValue, x).endswith(pat) - if any_match is not None: - any_match = any_match | pat_match - else: - any_match = pat_match - return any_match if any_match is not None else ibis_types.literal(False) - - -@scalar_op_compiler.register_unary_op(ops.StringSplitOp, pass_op=True) -def stringsplit_op_impl(x: ibis_types.Value, op: ops.StringSplitOp): - return typing.cast(ibis_types.StringValue, x).split(delimiter=op.pat) # type: ignore - - -@scalar_op_compiler.register_unary_op(ops.ZfillOp, pass_op=True) -def zfill_op_impl(x: ibis_types.Value, op: ops.ZfillOp): - str_value = typing.cast(ibis_types.StringValue, x) - return ( - ibis_api.case() - .when( - str_value[0] == "-", - "-" - + strpad_op_impl( - str_value.substr(1), - ops.StrPadOp(length=op.width - 1, fillchar="0", side="left"), - ), - ) - .else_( - strpad_op_impl( - str_value, ops.StrPadOp(length=op.width, fillchar="0", side="left") - ) - ) - .end() - ) - - -@scalar_op_compiler.register_unary_op(ops.StrFindOp, pass_op=True) -def find_op_impl(x: ibis_types.Value, op: ops.StrFindOp): - return typing.cast(ibis_types.StringValue, x).find(op.substr, op.start, op.end) - - -@scalar_op_compiler.register_unary_op(ops.StrExtractOp, pass_op=True) -def extract_op_impl(x: ibis_types.Value, op: ops.StrExtractOp): - return typing.cast(ibis_types.StringValue, x).re_extract(op.pat, op.n) - - -@scalar_op_compiler.register_unary_op(ops.StrSliceOp, pass_op=True) -def slice_op_impl(x: ibis_types.Value, op: ops.StrSliceOp): - return typing.cast(ibis_types.StringValue, x)[op.start : op.end] - - -@scalar_op_compiler.register_unary_op(ops.StrRepeatOp, pass_op=True) -def repeat_op_impl(x: ibis_types.Value, op: ops.StrRepeatOp): - return typing.cast(ibis_types.StringValue, x).repeat(op.repeats) - - -## Datetime Ops -@scalar_op_compiler.register_unary_op(ops.day_op) -def day_op_impl(x: ibis_types.Value): - return typing.cast(ibis_types.TimestampValue, x).day().cast(ibis_dtypes.int64) - - -@scalar_op_compiler.register_unary_op(ops.date_op) -def date_op_impl(x: ibis_types.Value): - return typing.cast(ibis_types.TimestampValue, x).date() - - -@scalar_op_compiler.register_unary_op(ops.iso_day_op) -def iso_day_op_impl(x: ibis_types.Value): - # Plus 1 because iso day of week uses 1-based indexing - return dayofweek_op_impl(x) + 1 - - -@scalar_op_compiler.register_unary_op(ops.iso_week_op) -def iso_week_op_impl(x: ibis_types.Value): - return typing.cast(ibis_types.TimestampValue, x).week_of_year() - - -@scalar_op_compiler.register_unary_op(ops.iso_year_op) -def iso_year_op_impl(x: ibis_types.Value): - return typing.cast(ibis_types.TimestampValue, x).iso_year() - - -@scalar_op_compiler.register_unary_op(ops.dayofweek_op) -def dayofweek_op_impl(x: ibis_types.Value): - return ( - typing.cast(ibis_types.TimestampValue, x) - .day_of_week.index() - .cast(ibis_dtypes.int64) - ) - - -@scalar_op_compiler.register_unary_op(ops.dayofyear_op) -def dayofyear_op_impl(x: ibis_types.Value): - return ( - typing.cast(ibis_types.TimestampValue, x).day_of_year().cast(ibis_dtypes.int64) - ) - - -@scalar_op_compiler.register_unary_op(ops.hour_op) -def hour_op_impl(x: ibis_types.Value): - return typing.cast(ibis_types.TimestampValue, x).hour().cast(ibis_dtypes.int64) - - -@scalar_op_compiler.register_unary_op(ops.minute_op) -def minute_op_impl(x: ibis_types.Value): - return typing.cast(ibis_types.TimestampValue, x).minute().cast(ibis_dtypes.int64) - - -@scalar_op_compiler.register_unary_op(ops.month_op) -def month_op_impl(x: ibis_types.Value): - return typing.cast(ibis_types.TimestampValue, x).month().cast(ibis_dtypes.int64) - - -@scalar_op_compiler.register_unary_op(ops.quarter_op) -def quarter_op_impl(x: ibis_types.Value): - return typing.cast(ibis_types.TimestampValue, x).quarter().cast(ibis_dtypes.int64) - - -@scalar_op_compiler.register_unary_op(ops.second_op) -def second_op_impl(x: ibis_types.Value): - return typing.cast(ibis_types.TimestampValue, x).second().cast(ibis_dtypes.int64) - - -@scalar_op_compiler.register_unary_op(ops.StrftimeOp, pass_op=True) -def strftime_op_impl(x: ibis_types.Value, op: ops.StrftimeOp): - return ( - typing.cast(ibis_types.TimestampValue, x) - .strftime(op.date_format) - .cast(ibis_dtypes.str) - ) - - -@scalar_op_compiler.register_unary_op(ops.UnixSeconds) -def unix_seconds_op_impl(x: ibis_types.TimestampValue): - return x.epoch_seconds() - - -@scalar_op_compiler.register_unary_op(ops.UnixMicros) -def unix_micros_op_impl(x: ibis_types.TimestampValue): - return unix_micros(x) - - -@scalar_op_compiler.register_unary_op(ops.UnixMillis) -def unix_millis_op_impl(x: ibis_types.TimestampValue): - return unix_millis(x) - - -@scalar_op_compiler.register_binary_op(ops.timestamp_diff_op) -def timestamp_diff_op_impl(x: ibis_types.TimestampValue, y: ibis_types.TimestampValue): - return x.delta(y, "microsecond") - - -@scalar_op_compiler.register_binary_op(ops.timestamp_add_op) -def timestamp_add_op_impl(x: ibis_types.TimestampValue, y: ibis_types.IntegerValue): - return x + y.to_interval("us") - - -@scalar_op_compiler.register_binary_op(ops.timestamp_sub_op) -def timestamp_sub_op_impl(x: ibis_types.TimestampValue, y: ibis_types.IntegerValue): - return x - y.to_interval("us") - - -@scalar_op_compiler.register_binary_op(ops.date_diff_op) -def date_diff_op_impl(x: ibis_types.DateValue, y: ibis_types.DateValue): - return x.delta(y, "day") * int(UNIT_TO_US_CONVERSION_FACTORS["d"]) # type: ignore - - -@scalar_op_compiler.register_binary_op(ops.date_add_op) -def date_add_op_impl(x: ibis_types.DateValue, y: ibis_types.IntegerValue): - return x.cast(ibis_dtypes.timestamp()) + y.to_interval("us") # type: ignore - - -@scalar_op_compiler.register_binary_op(ops.date_sub_op) -def date_sub_op_impl(x: ibis_types.DateValue, y: ibis_types.IntegerValue): - return x.cast(ibis_dtypes.timestamp()) - y.to_interval("us") # type: ignore - - -@scalar_op_compiler.register_unary_op(ops.FloorDtOp, pass_op=True) -def floor_dt_op_impl(x: ibis_types.Value, op: ops.FloorDtOp): - supported_freqs = ["Y", "Q", "M", "W", "D", "h", "min", "s", "ms", "us", "ns"] - pandas_to_ibis_freqs = {"min": "m"} - if op.freq not in supported_freqs: - raise NotImplementedError( - f"Unsupported freq paramater: {op.freq}" - + " Supported freq parameters are: " - + ",".join(supported_freqs) - ) - if op.freq in pandas_to_ibis_freqs: - ibis_freq = pandas_to_ibis_freqs[op.freq] - else: - ibis_freq = op.freq - result_type = x.type() - result = typing.cast(ibis_types.TimestampValue, x) - result = result.truncate(ibis_freq) # type: ignore - return result.cast(result_type) - - -@scalar_op_compiler.register_binary_op(ops.DatetimeToIntegerLabelOp, pass_op=True) -def datetime_to_integer_label_op_impl( - x: ibis_types.Value, y: ibis_types.Value, op: ops.DatetimeToIntegerLabelOp -): - # Determine if the frequency is fixed by checking if 'op.freq.nanos' is defined. - try: - return datetime_to_integer_label_fixed_frequency(x, y, op) - except ValueError: - return datetime_to_integer_label_non_fixed_frequency(x, y, op) - - -def datetime_to_integer_label_fixed_frequency( - x: ibis_types.Value, y: ibis_types.Value, op: ops.DatetimeToIntegerLabelOp -): - """ - This function handles fixed frequency conversions where the unit can range - from microseconds (us) to days. - """ - us = op.freq.nanos / 1000 - x_int = x.cast(ibis_dtypes.Timestamp(timezone="UTC")).cast(ibis_dtypes.int64) - first = calculate_resample_first(y, op.origin) - x_int_label = (x_int - first) // us - return x_int_label - - -def datetime_to_integer_label_non_fixed_frequency( - x: ibis_types.Value, y: ibis_types.Value, op: ops.DatetimeToIntegerLabelOp -): - """ - This function handles non-fixed frequency conversions for units ranging - from weeks to years. - """ - rule_code = op.freq.rule_code - n = op.freq.n - if rule_code == "W-SUN": # Weekly - us = n * 7 * 24 * 60 * 60 * 1000000 - x = x.truncate("week") + ibis_api.interval(days=6) # type: ignore - y = y.truncate("week") + ibis_api.interval(days=6) # type: ignore - x_int = x.cast(ibis_dtypes.Timestamp(timezone="UTC")).cast(ibis_dtypes.int64) - first = y.cast(ibis_dtypes.Timestamp(timezone="UTC")).cast(ibis_dtypes.int64) - x_int_label = ( - ibis_api.case() - .when(x_int == first, 0) - .else_((x_int - first - 1) // us + 1) # type: ignore - .end() - ) - elif rule_code in ("M", "ME"): # Monthly - x_int = x.year() * 12 + x.month() - 1 # type: ignore - first = y.year() * 12 + y.month() - 1 # type: ignore - x_int_label = ( - ibis_api.case() - .when(x_int == first, 0) - .else_((x_int - first - 1) // n + 1) # type: ignore - .end() - ) - elif rule_code in ("Q-DEC", "QE-DEC"): # Quarterly - x_int = x.year() * 4 + x.quarter() - 1 # type: ignore - first = y.year() * 4 + y.quarter() - 1 # type: ignore - x_int_label = ( - ibis_api.case() - .when(x_int == first, 0) - .else_((x_int - first - 1) // n + 1) # type: ignore - .end() - ) - elif rule_code in ("A-DEC", "Y-DEC", "YE-DEC"): # Yearly - x_int = x.year() # type: ignore - first = y.year() # type: ignore - x_int_label = ( - ibis_api.case() - .when(x_int == first, 0) - .else_((x_int - first - 1) // n + 1) # type: ignore - .end() - ) - else: - raise ValueError(rule_code) - return x_int_label - - -@scalar_op_compiler.register_binary_op(ops.IntegerLabelToDatetimeOp, pass_op=True) -def integer_label_to_datetime_op_impl( - x: ibis_types.Value, y: ibis_types.Value, op: ops.IntegerLabelToDatetimeOp -): - # Determine if the frequency is fixed by checking if 'op.freq.nanos' is defined. - try: - return integer_label_to_datetime_op_fixed_frequency(x, y, op) - except ValueError: - return integer_label_to_datetime_op_non_fixed_frequency(x, y, op) - - -def integer_label_to_datetime_op_fixed_frequency( - x: ibis_types.Value, y: ibis_types.Value, op: ops.IntegerLabelToDatetimeOp -): - """ - This function handles fixed frequency conversions where the unit can range - from microseconds (us) to days. - """ - us = op.freq.nanos / 1000 - - first = calculate_resample_first(y, op.origin) - - x_label = ( - (x * us + first) # type: ignore - .cast(ibis_dtypes.int64) - .to_timestamp(unit="us") - .cast(ibis_dtypes.Timestamp(timezone="UTC")) - .cast(y.type()) - ) - return x_label - - -def integer_label_to_datetime_op_non_fixed_frequency( - x: ibis_types.Value, y: ibis_types.Value, op: ops.IntegerLabelToDatetimeOp -): - """ - This function handles non-fixed frequency conversions for units ranging - from weeks to years. - """ - rule_code = op.freq.rule_code - n = op.freq.n - if rule_code == "W-SUN": # Weekly - us = n * 7 * 24 * 60 * 60 * 1000000 - first = ( - y.cast(ibis_dtypes.Timestamp(timezone="UTC")).truncate("week") # type: ignore - + ibis_api.interval(days=6) - ).cast(ibis_dtypes.int64) - x_label = ( - (x * us + first) # type: ignore - .cast(ibis_dtypes.int64) - .to_timestamp(unit="us") - .cast(ibis_dtypes.Timestamp(timezone="UTC")) - .cast(y.type()) - ) - elif rule_code in ("M", "ME"): # Monthly - one = ibis_types.literal(1) - twelve = ibis_types.literal(12) - first = y.year() * twelve + y.month() - one # type: ignore - - x = x * n + first # type: ignore - year = x // twelve # type: ignore - month = (x % twelve) + one # type: ignore - - next_year = (month == twelve).ifelse(year + one, year) - next_month = (month == twelve).ifelse(one, month + one) - next_month_date = ibis_api.timestamp( - typing.cast(ibis_types.IntegerValue, next_year), - typing.cast(ibis_types.IntegerValue, next_month), - 1, - 0, - 0, - 0, - ) - x_label = next_month_date - ibis_api.interval(days=1) - elif rule_code in ("Q-DEC", "QE-DEC"): # Quarterly - one = ibis_types.literal(1) - three = ibis_types.literal(3) - four = ibis_types.literal(4) - twelve = ibis_types.literal(12) - first = y.year() * four + y.quarter() - one # type: ignore - - x = x * n + first # type: ignore - year = x // four # type: ignore - month = ((x % four) + one) * three # type: ignore - - next_year = (month == twelve).ifelse(year + one, year) - next_month = (month == twelve).ifelse(one, month + one) - next_month_date = ibis_api.timestamp( - typing.cast(ibis_types.IntegerValue, next_year), - typing.cast(ibis_types.IntegerValue, next_month), - 1, - 0, - 0, - 0, - ) - - x_label = next_month_date - ibis_api.interval(days=1) - elif rule_code in ("A-DEC", "Y-DEC", "YE-DEC"): # Yearly - one = ibis_types.literal(1) - first = y.year() # type: ignore - x = x * n + first # type: ignore - next_year = x + one # type: ignore - next_month_date = ibis_api.timestamp( - typing.cast(ibis_types.IntegerValue, next_year), - 1, - 1, - 0, - 0, - 0, - ) - x_label = next_month_date - ibis_api.interval(days=1) - - return x_label.cast(ibis_dtypes.Timestamp(timezone="UTC")).cast(y.type()) - - -def calculate_resample_first(y: ibis_types.Value, origin): - if origin == "epoch": - return ibis_types.literal(0) - elif origin == "start_day": - return ( - y.cast(ibis_dtypes.date) - .cast(ibis_dtypes.Timestamp(timezone="UTC")) - .cast(ibis_dtypes.int64) - ) - elif origin == "start": - return y.cast(ibis_dtypes.Timestamp(timezone="UTC")).cast(ibis_dtypes.int64) - else: - raise ValueError(f"Origin {origin} not supported") - - -@scalar_op_compiler.register_unary_op(ops.time_op) -def time_op_impl(x: ibis_types.Value): - return typing.cast(ibis_types.TimestampValue, x).time() - - -@scalar_op_compiler.register_unary_op(ops.year_op) -def year_op_impl(x: ibis_types.Value): - return typing.cast(ibis_types.TimestampValue, x).year().cast(ibis_dtypes.int64) - - -@scalar_op_compiler.register_unary_op(ops.normalize_op) -def normalize_op_impl(x: ibis_types.Value): - result_type = x.type() - result = x.truncate("D") # type: ignore - return result.cast(result_type) - - -# Parameterized ops -@scalar_op_compiler.register_unary_op(ops.StructFieldOp, pass_op=True) -def struct_field_op_impl(x: ibis_types.Value, op: ops.StructFieldOp): - struct_value = typing.cast(ibis_types.StructValue, x) - if isinstance(op.name_or_index, str): - name = op.name_or_index - else: - name = struct_value.names[op.name_or_index] - - result = struct_value[name] - return result.cast(result.type()(nullable=True)).name(name) - - -def numeric_to_datetime( - x: ibis_types.Value, unit: str, safe: bool = False -) -> ibis_types.TimestampValue: - if not isinstance(x, ibis_types.IntegerValue) and not isinstance( - x, ibis_types.FloatingValue - ): - raise TypeError("Non-numerical types are not supposed to reach this function.") - - if unit not in UNIT_TO_US_CONVERSION_FACTORS: - raise ValueError(f"Cannot convert input with unit '{unit}'.") - x_converted = x * typing.cast( - ibis_types.IntegerValue, UNIT_TO_US_CONVERSION_FACTORS[unit] - ) - x_converted = ( - x_converted.try_cast(ibis_dtypes.int64) # type: ignore - if safe - else x_converted.cast(ibis_dtypes.int64) - ) - - # Note: Due to an issue where casting directly to a timestamp - # without a timezone does not work, we first cast to UTC. This - # approach appears to bypass a potential bug in Ibis's cast function, - # allowing for subsequent casting to a timestamp type without timezone - # information. Further investigation is needed to confirm this behavior. - return x_converted.to_timestamp(unit="us").cast( # type: ignore - ibis_dtypes.Timestamp(timezone="UTC") - ) - - -@scalar_op_compiler.register_unary_op(ops.coerce_to_bool_op) -def coerce_to_bool_op_impl(x: ibis_types.Value): - x_type = x.type() - if x_type.is_boolean(): - res = x - elif x_type.is_numeric(): - res = x != 0 # type: ignore - elif x_type.is_string(): - res = x.length() > 0 # type: ignore - elif x_type.is_binary(): - res = x.length() > 0 # type: ignore - elif isinstance(x_type, ibis_dtypes.Array): - res = x.length() > 0 # type: ignore - else: - res = x.notnull() - - return res.fill_null(False) # type: ignore - - -@scalar_op_compiler.register_unary_op(ops.AsTypeOp, pass_op=True) -def astype_op_impl(x: ibis_types.Value, op: ops.AsTypeOp): - to_type = bigframes.core.compile.ibis_types.bigframes_dtype_to_ibis_dtype( - op.to_type - ) - if isinstance(x, ibis_types.NullScalar): - return ibis_types.null().cast(to_type) - - # When casting DATETIME column into INT column, we need to convert the column into TIMESTAMP first. - if to_type == ibis_dtypes.int64 and x.type() == ibis_dtypes.timestamp: - utc_time_type = ibis_dtypes.Timestamp(timezone="UTC") - x_converted = x.try_cast(utc_time_type) if op.safe else x.cast(utc_time_type) - return bigframes.core.compile.ibis_types.cast_ibis_value( - x_converted, to_type, safe=op.safe - ) - - if to_type == ibis_dtypes.int64 and x.type() == ibis_dtypes.time: - # The conversion unit is set to "us" (microseconds) for consistency - # with pandas converting time64[us][pyarrow] to int64[pyarrow]. - return x.delta(ibis_api.time("00:00:00"), part="microsecond") # type: ignore - - if x.type() == ibis_dtypes.int64: - # The conversion unit is set to "us" (microseconds) for consistency - # with pandas converting int64[pyarrow] to timestamp[us][pyarrow], - # timestamp[us, tz=UTC][pyarrow], and time64[us][pyarrow]. - unit = "us" - x_converted = numeric_to_datetime(x, unit, safe=op.safe) - if to_type == ibis_dtypes.timestamp: - return ( - x_converted.try_cast(ibis_dtypes.Timestamp()) - if op.safe - else x_converted.cast(ibis_dtypes.Timestamp()) - ) - elif to_type == ibis_dtypes.Timestamp(timezone="UTC"): - return x_converted - elif to_type == ibis_dtypes.time: - return x_converted.time() - - # TODO: either inline this function, or push rest of this op into the function - return bigframes.core.compile.ibis_types.cast_ibis_value(x, to_type, safe=op.safe) - - -@scalar_op_compiler.register_unary_op(ops.IsInOp, pass_op=True) -def isin_op_impl(x: ibis_types.Value, op: ops.IsInOp): - contains_nulls = any(is_null(value) for value in op.values) - matchable_ibis_values = [] - for item in op.values: - if not is_null(item): - try: - # we want values that *could* be cast to the dtype, but we don't want - # to actually cast it, as that could be lossy (eg float -> int) - item_inferred_type = ibis_types.literal(item).type() - if ( - x.type().name == item_inferred_type.name - or x.type().is_numeric() - and item_inferred_type.is_numeric() - ): - matchable_ibis_values.append(item) - except TypeError: - pass - - if op.match_nulls and contains_nulls: - return x.isnull() | x.isin(matchable_ibis_values) - else: - return x.isin(matchable_ibis_values).fill_null(ibis.literal(False)) - - -@scalar_op_compiler.register_unary_op(ops.ToDatetimeOp, pass_op=True) -def to_datetime_op_impl(x: ibis_types.Value, op: ops.ToDatetimeOp): - if x.type() == ibis_dtypes.Timestamp(None): # type: ignore - return x # already a timestamp, no-op - elif x.type() in (ibis_dtypes.str, ibis_dtypes.Timestamp("UTC")): # type: ignore - return x.try_cast(ibis_dtypes.Timestamp(None)) # type: ignore - else: - # Numerical inputs. - if op.format: - x = x.cast(ibis_dtypes.str).to_timestamp(op.format) # type: ignore - else: - # The default unit is set to "ns" (nanoseconds) for consistency - # with pandas, where "ns" is the default unit for datetime operations. - unit = op.unit or "ns" - x = numeric_to_datetime(x, unit) - - return x.cast(ibis_dtypes.Timestamp(None)) # type: ignore - - -@scalar_op_compiler.register_unary_op(ops.ToTimestampOp, pass_op=True) -def to_timestamp_op_impl(x: ibis_types.Value, op: ops.ToTimestampOp): - if x.type() == ibis_dtypes.str: - x = ( - typing.cast(ibis_types.StringValue, x).to_timestamp(op.format) - if op.format - else timestamp(x) - ) - elif x.type() == ibis_dtypes.Timestamp(None): # type: ignore - return timestamp(x) - else: - # Numerical inputs. - if op.format: - x = x.cast(ibis_dtypes.str).to_timestamp(op.format) # type: ignore - else: - # The default unit is set to "ns" (nanoseconds) for consistency - # with pandas, where "ns" is the default unit for datetime operations. - unit = op.unit or "ns" - x = numeric_to_datetime(x, unit) - - return x.cast(ibis_dtypes.Timestamp(timezone="UTC")) - - -@scalar_op_compiler.register_unary_op(ops.ToTimedeltaOp, pass_op=True) -def to_timedelta_op_impl(x: ibis_types.Value, op: ops.ToTimedeltaOp): - return ( - typing.cast(ibis_types.NumericValue, x) * UNIT_TO_US_CONVERSION_FACTORS[op.unit] # type: ignore - ).floor() - - -@scalar_op_compiler.register_unary_op(ops.timedelta_floor_op) -def timedelta_floor_op_impl(x: ibis_types.NumericValue): - return ibis_api.case().when(x > ibis.literal(0), x.floor()).else_(x.ceil()).end() - - -@scalar_op_compiler.register_nary_op(ops.RemoteFunctionOp, pass_op=True) -def remote_function_op_impl(*values: ibis_types.Value, op: ops.RemoteFunctionOp): - udf_sig = op.function_def.signature - assert not udf_sig.is_virtual # should have been devirtualized in lowering pass - ibis_py_sig = (tuple(arg.py_type for arg in udf_sig.inputs), udf_sig.output.py_type) - arg_names = tuple(arg.name for arg in udf_sig.inputs) - - @ibis_udf.scalar.builtin( - name=str(op.function_def.routine_ref), - signature=ibis_py_sig, - param_name_overrides=arg_names, - ) - def udf(*inputs): ... - - return udf(*values) - - -@scalar_op_compiler.register_unary_op(ops.MapOp, pass_op=True) -def map_op_impl(x: ibis_types.Value, op: ops.MapOp): - # this should probably be handled by a rewriter - if len(op.mappings) == 0: - return x - - case = ibis_api.case() - for mapping in op.mappings: - case = case.when(x == mapping[0], mapping[1]) - return case.else_(x).end() - - -# Array Ops -@scalar_op_compiler.register_unary_op(ops.ArrayToStringOp, pass_op=True) -def array_to_string_op_impl(x: ibis_types.Value, op: ops.ArrayToStringOp): - return typing.cast(ibis_types.ArrayValue, x).join(op.delimiter) - - -@scalar_op_compiler.register_unary_op(ops.GetItemOp, pass_op=True) -def getitem_op_impl(x: ibis_types.Value, op: ops.GetItemOp): - if x.type().is_struct(): - struct_value = typing.cast(ibis_types.StructValue, x) - if isinstance(op.key, str): - name = op.key - else: - name = struct_value.names[op.key] - result = struct_value[name] - return result.cast(result.type()(nullable=True)).name(name) - elif x.type().is_array(): - key = typing.cast(int, op.key) - res = typing.cast(ibis_types.ArrayValue, x)[key] - return res - elif x.type().is_string(): - key = typing.cast(int, op.key) - res = typing.cast(ibis_types.StringValue, x)[key] - return _null_or_value(res, res != ibis_types.literal("")) - else: - raise TypeError(f"Cannot subscript input of type {x.type()}") - - -@scalar_op_compiler.register_binary_op(ops.DynamicGetItemOp) -def dynamic_getitem_op_impl(left: ibis_types.Value, right: ibis_types.Value): - if left.type().is_array(): - int_right = typing.cast(ibis_types.IntegerValue, right) - return typing.cast(ibis_types.ArrayValue, left)[int_right] - elif left.type().is_string(): - scalar_right = typing.cast(ibis_types.IntegerScalar, right) - res = typing.cast(ibis_types.StringValue, left)[scalar_right] - return _null_or_value(res, res != ibis_types.literal("")) - else: - raise TypeError(f"Cannot dynamically subscript input of type {left.type()}") - - -@scalar_op_compiler.register_unary_op(ops.ArraySliceOp, pass_op=True) -def array_slice_op_impl(x: ibis_types.Value, op: ops.ArraySliceOp): - res = typing.cast(ibis_types.ArrayValue, x)[op.start : op.stop : op.step] - if x.type().is_string(): - return _null_or_value(res, res != ibis_types.literal("")) - else: - return res - - -@scalar_op_compiler.register_nary_op(ops.ToArrayOp, pass_op=False) -def to_arry_op_impl(*values: ibis_types.Value): - do_upcast_bool = any(t.type().is_numeric() for t in values) - if do_upcast_bool: - values = tuple( - val.cast(ibis_dtypes.int64) if val.type().is_boolean() else val - for val in values - ) - return ibis_api.array(values) - - -@scalar_op_compiler.register_unary_op(ops.ArrayReduceOp, pass_op=True) -def array_reduce_op_impl(x: ibis_types.Value, op: ops.ArrayReduceOp): - import bigframes.core.compile.ibis_compiler.aggregate_compiler as agg_compilers - - if op.aggregation.order_independent: - return typing.cast(ibis_types.ArrayValue, x).reduce( - lambda arr_vals: agg_compilers.compile_unary_agg( - op.aggregation, typing.cast(ibis_types.Column, arr_vals) - ) - ) - else: - return typing.cast(ibis_types.ArrayValue, x).reduce( - lambda arr_vals: agg_compilers.compile_ordered_unary_agg( - op.aggregation, typing.cast(ibis_types.Column, arr_vals) - ) - ) - - -@scalar_op_compiler.register_unary_op(ops.ArrayMapOp, pass_op=True) -def array_map_op_impl(x: ibis_types.Value, op: ops.ArrayMapOp): - return typing.cast(ibis_types.ArrayValue, x).map( - lambda arr_vals: scalar_op_compiler.compile_row_op(op.map_op, (arr_vals,)) - ) - - -# JSON Ops -@scalar_op_compiler.register_binary_op(ops.JSONSet, pass_op=True) -def json_set_op_impl(x: ibis_types.Value, y: ibis_types.Value, op: ops.JSONSet): - return json_set(json_obj=x, json_path=op.json_path, json_value=y) - - -@scalar_op_compiler.register_unary_op(ops.JSONExtract, pass_op=True) -def json_extract_op_impl(x: ibis_types.Value, op: ops.JSONExtract): - # Define a user-defined function whose returned type is dynamically matching the input. - def json_extract(json_or_json_string, json_path: ibis_dtypes.str): # type: ignore - """Extracts a JSON value and converts it to a SQL JSON-formatted STRING or JSON value.""" - ... - - return_type = x.type() - json_extract.__annotations__["return"] = return_type - json_extract_op = ibis_udf.scalar.builtin(json_extract) - return json_extract_op(json_or_json_string=x, json_path=op.json_path) - - -@scalar_op_compiler.register_unary_op(ops.JSONExtractArray, pass_op=True) -def json_extract_array_op_impl(x: ibis_types.Value, op: ops.JSONExtractArray): - # Define a user-defined function whose returned type is dynamically matching the input. - def json_extract_array(json_or_json_string, json_path: ibis_dtypes.str): # type: ignore - """Extracts a JSON value and converts it to a SQL JSON-formatted STRING or JSON value.""" - ... - - return_type = x.type() - json_extract_array.__annotations__["return"] = ibis_dtypes.Array[return_type] # type: ignore - json_extract_op = ibis_udf.scalar.builtin(json_extract_array) - return json_extract_op(json_or_json_string=x, json_path=op.json_path) - - -@scalar_op_compiler.register_unary_op(ops.JSONExtractStringArray, pass_op=True) -def json_extract_string_array_op_impl( - x: ibis_types.Value, op: ops.JSONExtractStringArray -): - return json_extract_string_array(json_obj=x, json_path=op.json_path) - - -@scalar_op_compiler.register_unary_op(ops.JSONQuery, pass_op=True) -def json_query_op_impl(x: ibis_types.Value, op: ops.JSONQuery): - # Define a user-defined function whose returned type is dynamically matching the input. - def json_query(json_or_json_string, json_path: ibis_dtypes.str): # type: ignore - """Extracts a JSON value and converts it to a SQL JSON-formatted STRING or JSON value.""" - ... - - return_type = x.type() - json_query.__annotations__["return"] = return_type - json_query_op = ibis_udf.scalar.builtin(json_query) - return json_query_op(json_or_json_string=x, json_path=op.json_path) - - -@scalar_op_compiler.register_unary_op(ops.JSONQueryArray, pass_op=True) -def json_query_array_op_impl(x: ibis_types.Value, op: ops.JSONQueryArray): - # Define a user-defined function whose returned type is dynamically matching the input. - def json_query_array(json_or_json_string, json_path: ibis_dtypes.str): # type: ignore - """Extracts a JSON value and converts it to a SQL JSON-formatted STRING or JSON value.""" - ... - - return_type = x.type() - json_query_array.__annotations__["return"] = ibis_dtypes.Array[return_type] # type: ignore - json_query_op = ibis_udf.scalar.builtin(json_query_array) - return json_query_op(json_or_json_string=x, json_path=op.json_path) - - -@scalar_op_compiler.register_unary_op(ops.ParseJSON, pass_op=True) -def parse_json_op_impl(x: ibis_types.Value, op: ops.ParseJSON): - return parse_json(json_str=x) - - -@scalar_op_compiler.register_unary_op(ops.ToJSON, pass_op=True) -def to_json_op_impl(x: ibis_types.Value, op: ops.ToJSON): - if x.type() == ibis_dtypes.string: - return parse_json_in_safe(x) if op.safe else parse_json(x) - return x.isnull().ifelse(ibis.null().cast(ibis_dtypes.json), to_json(x)) - - -@scalar_op_compiler.register_unary_op(ops.JSONDecode, pass_op=True) -def json_decode_op_impl(x: ibis_types.Value, op: ops.JSONDecode): - to_type = bigframes.core.compile.ibis_types.bigframes_dtype_to_ibis_dtype( - op.to_type - ) - if to_type == ibis_dtypes.int64: - return cast_json_to_int64_in_safe(x) if op.safe else cast_json_to_int64(x) - if to_type == ibis_dtypes.float64: - return cast_json_to_float64_in_safe(x) if op.safe else cast_json_to_float64(x) - if to_type == ibis_dtypes.bool: - return cast_json_to_bool_in_safe(x) if op.safe else cast_json_to_bool(x) - if to_type == ibis_dtypes.string: - return cast_json_to_string_in_safe(x) if op.safe else cast_json_to_string(x) - raise TypeError(f"Cannot cast from JSON to type {to_type}") - - -@scalar_op_compiler.register_unary_op(ops.ToJSONString) -def to_json_string_op_impl(x: ibis_types.Value): - return to_json_string(value=x) - - -@scalar_op_compiler.register_unary_op(ops.JSONValue, pass_op=True) -def json_value_op_impl(x: ibis_types.Value, op: ops.JSONValue): - return json_value(json_obj=x, json_path=op.json_path) - - -@scalar_op_compiler.register_unary_op(ops.JSONValueArray, pass_op=True) -def json_value_array_op_impl(x: ibis_types.Value, op: ops.JSONValueArray): - return json_value_array(json_obj=x, json_path=op.json_path) - - -@scalar_op_compiler.register_unary_op(ops.JSONKeys, pass_op=True) -def json_keys_op_impl(x: ibis_types.Value, op: ops.JSONKeys): - return json_keys(x, op.max_depth) - - -# Blob Ops -@scalar_op_compiler.register_unary_op(ops.obj_fetch_metadata_op) -def obj_fetch_metadata_op_impl(obj_ref: ibis_types.Value): - return obj_fetch_metadata(obj_ref=obj_ref) - - -@scalar_op_compiler.register_unary_op(ops.ObjGetAccessUrl, pass_op=True) -def obj_get_access_url_op_impl(obj_ref: ibis_types.Value, op: ops.ObjGetAccessUrl): - if op.duration is not None: - duration_value = cast( - ibis_types.IntegerValue, ibis_types.literal(op.duration) - ).to_interval("us") - return obj_get_access_url_with_duration( - obj_ref=obj_ref, mode=op.mode, duration=duration_value - ) - return obj_get_access_url(obj_ref=obj_ref, mode=op.mode) - - -### Binary Ops -def short_circuit_nulls(type_override: typing.Optional[ibis_dtypes.DataType] = None): - """Wraps a binary operator to generate nulls of the expected type if either input is a null scalar.""" - - def short_circuit_nulls_inner(binop): - @functools.wraps(binop) - def wrapped_binop(x: ibis_types.Value, y: ibis_types.Value): - if isinstance(x, ibis_types.NullScalar): - return ibis_types.null().cast(type_override or y.type()) - elif isinstance(y, ibis_types.NullScalar): - return ibis_types.null().cast(type_override or x.type()) - else: - return binop(x, y) - - return wrapped_binop - - return short_circuit_nulls_inner - - -@scalar_op_compiler.register_binary_op(ops.strconcat_op) -def concat_op( - x: ibis_types.Value, - y: ibis_types.Value, -): - x_string = typing.cast(ibis_types.StringValue, x) - y_string = typing.cast(ibis_types.StringValue, y) - return x_string.concat(y_string) - - -@scalar_op_compiler.register_binary_op(ops.eq_op) -def eq_op( - x: ibis_types.Value, - y: ibis_types.Value, -): - x, y = _coerce_bools(x, y) - return x == y - - -@scalar_op_compiler.register_binary_op(ops.eq_null_match_op) -def eq_nulls_match_op( - x: ibis_types.Value, - y: ibis_types.Value, -): - """Variant of eq_op where nulls match each other. Only use where dtypes are known to be same.""" - x, y = _coerce_bools(x, y) - literal = ibis_types.literal("$NULL_SENTINEL$") - if hasattr(x, "fill_null"): - left = x.cast(ibis_dtypes.str).fill_null(literal) - right = y.cast(ibis_dtypes.str).fill_null(literal) - else: - left = x.cast(ibis_dtypes.str).fill_null(literal) - right = y.cast(ibis_dtypes.str).fill_null(literal) - - return left == right - - -@scalar_op_compiler.register_binary_op(ops.ne_op) -def ne_op( - x: ibis_types.Value, - y: ibis_types.Value, -): - x, y = _coerce_bools(x, y) - return x != y - - -def _null_or_value(value: ibis_types.Value, where_value: ibis_types.BooleanValue): - return ibis_api.ifelse( - where_value, - value, - ibis_types.null(), - ) - - -def _coerce_bools(x: ibis_types.Value, y: ibis_types.Value, *, always: bool = False): - if x.type().is_boolean() and (always or not y.type().is_boolean()): - x = x.cast(ibis_dtypes.int64) - if y.type().is_boolean() and (always or not x.type().is_boolean()): - y = y.cast(ibis_dtypes.int64) - return x, y - - -@scalar_op_compiler.register_binary_op(ops.and_op) -def and_op( - x: ibis_types.Value, - y: ibis_types.Value, -): - # Workaround issue https://github.com/ibis-project/ibis/issues/7775 by - # implementing three-valued logic ourselves. For AND, when we encounter a - # NULL value, we only know when the result is FALSE, otherwise the result - # is unknown (NULL). See: truth table at - # https://en.wikibooks.org/wiki/Structured_Query_Language/NULLs_and_the_Three_Valued_Logic#AND,_OR - if isinstance(x, ibis_types.NullScalar): - return _null_or_value(y, y == ibis_types.literal(False)) - - if isinstance(y, ibis_types.NullScalar): - return _null_or_value(x, x == ibis_types.literal(False)) - return typing.cast(ibis_types.BooleanValue, x) & typing.cast( - ibis_types.BooleanValue, y - ) - - -@scalar_op_compiler.register_binary_op(ops.or_op) -def or_op( - x: ibis_types.Value, - y: ibis_types.Value, -): - # Workaround issue https://github.com/ibis-project/ibis/issues/7775 by - # implementing three-valued logic ourselves. For OR, when we encounter a - # NULL value, we only know when the result is TRUE, otherwise the result - # is unknown (NULL). See: truth table at - # https://en.wikibooks.org/wiki/Structured_Query_Language/NULLs_and_the_Three_Valued_Logic#AND,_OR - if isinstance(x, ibis_types.NullScalar): - return _null_or_value(y, y == ibis_types.literal(True)) - - if isinstance(y, ibis_types.NullScalar): - return _null_or_value(x, x == ibis_types.literal(True)) - return typing.cast(ibis_types.BooleanValue, x) | typing.cast( - ibis_types.BooleanValue, y - ) - - -@scalar_op_compiler.register_binary_op(ops.xor_op) -def xor_op( - x: ibis_types.Value, - y: ibis_types.Value, -): - return typing.cast(ibis_types.BooleanValue, x) ^ typing.cast( - ibis_types.BooleanValue, y - ) - - -@scalar_op_compiler.register_binary_op(ops.add_op) -@short_circuit_nulls() -def add_op( - x: ibis_types.Value, - y: ibis_types.Value, -): - x, y = _coerce_bools(x, y) - if isinstance(x, ibis_types.NullScalar) or isinstance(x, ibis_types.NullScalar): - return ibis_types.null() - - if x.type().is_boolean() and y.type().is_boolean(): - x, y = _coerce_bools(x, y, always=True) - return ( - typing.cast(ibis_types.NumericValue, x) - + typing.cast(ibis_types.NumericValue, x) - ).cast(ibis_dtypes.Boolean) - - x, y = _coerce_bools(x, y) - return x + y # type: ignore - - -@scalar_op_compiler.register_binary_op(ops.sub_op) -@short_circuit_nulls() -def sub_op( - x: ibis_types.Value, - y: ibis_types.Value, -): - x, y = _coerce_bools(x, y) - return typing.cast(ibis_types.NumericValue, x) - typing.cast( - ibis_types.NumericValue, y - ) - - -@scalar_op_compiler.register_binary_op(ops.mul_op) -@short_circuit_nulls() -def mul_op( - x: ibis_types.Value, - y: ibis_types.Value, -): - if x.type().is_boolean() and y.type().is_boolean(): - x, y = _coerce_bools(x, y, always=True) - return ( - typing.cast(ibis_types.NumericValue, x) - * typing.cast(ibis_types.NumericValue, x) - ).cast(ibis_dtypes.Boolean) - x, y = _coerce_bools(x, y) - return typing.cast(ibis_types.NumericValue, x) * typing.cast( - ibis_types.NumericValue, y - ) - - -@scalar_op_compiler.register_binary_op(ops.div_op) -@short_circuit_nulls(ibis_dtypes.float) -def div_op( - x: ibis_types.Value, - y: ibis_types.Value, -): - x, y = _coerce_bools(x, y) - return typing.cast(ibis_types.NumericValue, x) / typing.cast( - ibis_types.NumericValue, y - ) - - -@scalar_op_compiler.register_binary_op(ops.pow_op) -@short_circuit_nulls(ibis_dtypes.float) -def pow_op( - x: ibis_types.Value, - y: ibis_types.Value, -): - x, y = _coerce_bools(x, y) - if x.type().is_integer() and y.type().is_integer(): - return _int_pow_op(x, y) - else: - return _float_pow_op(x, y) - - -@scalar_op_compiler.register_binary_op(ops.unsafe_pow_op) -@short_circuit_nulls(ibis_dtypes.float) -def unsafe_pow_op( - x: ibis_types.Value, - y: ibis_types.Value, -): - """For internal use only - where domain and overflow checks are not needed.""" - x, y = _coerce_bools(x, y) - return typing.cast(ibis_types.NumericValue, x) ** typing.cast( - ibis_types.NumericValue, y - ) - - -def _int_pow_op( - x: ibis_types.Value, - y: ibis_types.Value, -): - # Need to avoid any error cases - should produce NaN instead - # See: https://cloud.google.com/bigquery/docs/reference/standard-sql/mathematical_functions#pow - x_as_decimal = typing.cast( - ibis_types.NumericValue, - x.cast(ibis_dtypes.Decimal(precision=38, scale=9, nullable=True)), - ) - y_val = typing.cast(ibis_types.NumericValue, y) - - # BQ POW() function outputs FLOAT64, which can lose precision. - # Therefore, we do math in NUMERIC and cast back down after. - # Also, explicit bounds checks, pandas will silently overflow. - pow_result = x_as_decimal**y_val - overflow_cond = (pow_result > _ibis_num((2**63) - 1)) | ( - pow_result < _ibis_num(-(2**63)) - ) - - return ( - ibis_api.case() - .when((overflow_cond), ibis_types.null()) - .else_(pow_result.cast(ibis_dtypes.int64)) - .end() - ) - - -def _float_pow_op( - x: ibis_types.Value, - y: ibis_types.Value, -): - # Most conditions here seek to prevent calling BQ POW with inputs that would generate errors. - # See: https://cloud.google.com/bigquery/docs/reference/standard-sql/mathematical_functions#pow - x_val = typing.cast(ibis_types.NumericValue, x) - y_val = typing.cast(ibis_types.NumericValue, y) - - overflow_cond = (x_val != _ZERO) & ((y_val * x_val.abs().ln()) > _FLOAT64_EXP_BOUND) - - # Float64 lose integer precision beyond 2**53, beyond this insufficient precision to get parity - exp_too_big = y_val.abs() > _ibis_num(2**53) - # Treat very large exponents as +=INF - norm_exp = exp_too_big.ifelse(_INF * y_val.sign(), y_val) - - pow_result = x_val**norm_exp - - # This cast is dangerous, need to only excuted where y_val has been bounds-checked - # Ibis needs try_cast binding to bq safe_cast - exponent_is_whole = y_val.cast(ibis_dtypes.int64) == y_val - odd_exponent = (x_val < _ZERO) & ( - y_val.cast(ibis_dtypes.int64) % _ibis_num(2) == _ibis_num(1) - ) - infinite_base = x_val.abs() == _INF - - return ( - ibis_api.case() - # Might be able to do something more clever with x_val==0 case - .when(y_val == _ZERO, _ibis_num(1)) - .when( - x_val == _ibis_num(1), _ibis_num(1) - ) # Need to ignore exponent, even if it is NA - .when( - (x_val == _ZERO) & (y_val < _ZERO), _INF - ) # This case would error POW function in BQ - .when(infinite_base, pow_result) - .when( - exp_too_big, pow_result - ) # Bigquery can actually handle the +-inf cases gracefully - .when((x_val < _ZERO) & (~exponent_is_whole), _NAN) - .when( - overflow_cond, _INF * odd_exponent.ifelse(_ibis_num(-1), _ibis_num(1)) - ) # finite overflows would cause bq to error - .else_(pow_result) - .end() - ) - - -@scalar_op_compiler.register_binary_op(ops.lt_op) -@short_circuit_nulls(ibis_dtypes.bool) -def lt_op( - x: ibis_types.Value, - y: ibis_types.Value, -): - x, y = _coerce_bools(x, y) - return x < y - - -@scalar_op_compiler.register_binary_op(ops.le_op) -@short_circuit_nulls(ibis_dtypes.bool) -def le_op( - x: ibis_types.Value, - y: ibis_types.Value, -): - x, y = _coerce_bools(x, y) - return x <= y - - -@scalar_op_compiler.register_binary_op(ops.gt_op) -@short_circuit_nulls(ibis_dtypes.bool) -def gt_op( - x: ibis_types.Value, - y: ibis_types.Value, -): - x, y = _coerce_bools(x, y) - return x > y - - -@scalar_op_compiler.register_binary_op(ops.ge_op) -@short_circuit_nulls(ibis_dtypes.bool) -def ge_op( - x: ibis_types.Value, - y: ibis_types.Value, -): - x, y = _coerce_bools(x, y) - return x >= y - - -@scalar_op_compiler.register_binary_op(ops.floordiv_op) -@short_circuit_nulls(ibis_dtypes.int) -def floordiv_op( - x: ibis_types.Value, - y: ibis_types.Value, -): - if x.type().is_boolean(): - x = x.cast(ibis_dtypes.int64) - elif y.type().is_boolean(): - y = y.cast(ibis_dtypes.int64) - x_numeric = typing.cast(ibis_types.NumericValue, x) - y_numeric = typing.cast(ibis_types.NumericValue, y) - floordiv_expr = x_numeric // y_numeric - - # DIV(N, 0) will error in bigquery, but needs to return 0 for int, and inf for float in BQ so we short-circuit in this case. - # Multiplying left by zero propogates nulls. - zero_result = _INF if (x.type().is_floating() or y.type().is_floating()) else _ZERO - return ( - ibis_api.case() - .when(y_numeric == _ZERO, zero_result * x_numeric) - .else_(floordiv_expr) - .end() - ) - - -def _is_bignumeric(x: ibis_types.Value): - if not isinstance(x, ibis_types.DecimalValue): - return False - # Should be exactly 76 for bignumeric - return x.precision > 70 # type: ignore - - -def _is_numeric(x: ibis_types.Value): - # either big-numeric or numeric - return isinstance(x, ibis_types.DecimalValue) - - -@scalar_op_compiler.register_binary_op(ops.mod_op) -@short_circuit_nulls() -def mod_op( - x: ibis_types.Value, - y: ibis_types.Value, -): - # Hacky short-circuit to avoid passing zero-literal to sql backend, evaluate locally instead to null. - op = y.op() - if isinstance(op, ibis_generic.Literal) and op.value == 0: - return ibis_types.null().cast(x.type()) - - x, y = _coerce_bools(x, y) - if x.type().is_integer() and y.type().is_integer(): - # both are ints, no casting necessary - return _int_mod( - typing.cast(ibis_types.IntegerValue, x), - typing.cast(ibis_types.IntegerValue, y), - ) - - else: - # bigquery doens't support float mod, so just cast to bignumeric and hope for the best - x_numeric = typing.cast( - ibis_types.DecimalValue, - x.cast(ibis_dtypes.Decimal(precision=76, scale=38, nullable=True)), - ) - y_numeric = typing.cast( - ibis_types.DecimalValue, - y.cast(ibis_dtypes.Decimal(precision=76, scale=38, nullable=True)), - ) - mod_numeric = _bignumeric_mod(x_numeric, y_numeric) # type: ignore - - # Cast back down based on original types - if _is_bignumeric(x) or _is_bignumeric(y): - return mod_numeric - if _is_numeric(x) or _is_numeric(y): - return mod_numeric.cast(ibis_dtypes.Decimal(38, 9)) - else: - return mod_numeric.cast(ibis_dtypes.float64) - - -def _bignumeric_mod( - x: ibis_types.IntegerValue, - y: ibis_types.IntegerValue, -): - # Hacky short-circuit to avoid passing zero-literal to sql backend, evaluate locally instead to null. - op = y.op() - if isinstance(op, ibis_generic.Literal) and op.value == 0: - return ibis_types.null().cast(x.type()) - - bq_mod = x % y # Bigquery will maintain x sign here - - # In BigQuery returned value has the same sign as X. In pandas, the sign of y is used, so we need to flip the result if sign(x) != sign(y) - return ( - ibis_api.case() - .when( - y == _ZERO, - _NAN * x, - ) # Dummy op to propogate nulls and type from x arg - .when( - (y < _ZERO) & (bq_mod > _ZERO), (y + bq_mod) - ) # Convert positive result to negative - .when( - (y > _ZERO) & (bq_mod < _ZERO), (y + bq_mod) - ) # Convert negative result to positive - .else_(bq_mod) - .end() - ) - - -def _int_mod( - x: ibis_types.IntegerValue, - y: ibis_types.IntegerValue, -): - # Hacky short-circuit to avoid passing zero-literal to sql backend, evaluate locally instead to null. - op = y.op() - if isinstance(op, ibis_generic.Literal) and op.value == 0: - return ibis_types.null().cast(x.type()) - - bq_mod = x % y # Bigquery will maintain x sign here - - # In BigQuery returned value has the same sign as X. In pandas, the sign of y is used, so we need to flip the result if sign(x) != sign(y) - return ( - ibis_api.case() - .when( - y == _ZERO, - _ZERO * x, - ) # Dummy op to propogate nulls and type from x arg - .when( - (y < _ZERO) & (bq_mod > _ZERO), (y + bq_mod) - ) # Convert positive result to negative - .when( - (y > _ZERO) & (bq_mod < _ZERO), (y + bq_mod) - ) # Convert negative result to positive - .else_(bq_mod) - .end() - ) - - -@scalar_op_compiler.register_binary_op(ops.fillna_op) -def fillna_op( - x: ibis_types.Value, - y: ibis_types.Value, -): - return x.fill_null(typing.cast(ibis_types.Scalar, y)) - - -@scalar_op_compiler.register_binary_op(ops.round_op) -def round_op(x: ibis_types.Value, y: ibis_types.Value): - if x.type().is_integer(): - # bq produces float64, but pandas returns int - return ( - typing.cast(ibis_types.NumericValue, x) - .round(digits=typing.cast(ibis_types.IntegerValue, y)) - .cast(ibis_dtypes.int64) - ) - return typing.cast(ibis_types.NumericValue, x).round( - digits=typing.cast(ibis_types.IntegerValue, y) - ) - - -@scalar_op_compiler.register_binary_op(ops.coalesce_op) -def coalesce_impl( - x: ibis_types.Value, - y: ibis_types.Value, -): - if x.name("name").equals(y.name("name")): - return x - else: - return ibis_api.coalesce(x, y) - - -@scalar_op_compiler.register_binary_op(ops.maximum_op) -def maximum_impl( - value: ibis_types.Value, - lower: ibis_types.Value, -): - # Note: propagates nulls - return ( - ibis_api.case().when(lower.isnull() | (value < lower), lower).else_(value).end() - ) - - -@scalar_op_compiler.register_binary_op(ops.minimum_op) -def minimum_impl( - value: ibis_types.Value, - upper: ibis_types.Value, -): - # Note: propagates nulls - return ( - ibis_api.case().when(upper.isnull() | (value > upper), upper).else_(value).end() - ) - - -@scalar_op_compiler.register_binary_op(ops.cosine_distance_op) -def cosine_distance_impl( - vector1: ibis_types.Value, - vector2: ibis_types.Value, -): - return vector_distance(vector1, vector2, "COSINE") - - -@scalar_op_compiler.register_binary_op(ops.euclidean_distance_op) -def euclidean_distance_impl( - vector1: ibis_types.Value, - vector2: ibis_types.Value, -): - return vector_distance(vector1, vector2, "EUCLIDEAN") - - -@scalar_op_compiler.register_binary_op(ops.manhattan_distance_op) -def manhattan_distance_impl( - vector1: ibis_types.Value, - vector2: ibis_types.Value, -): - return vector_distance(vector1, vector2, "MANHATTAN") - - -# Blob Ops -@scalar_op_compiler.register_binary_op(ops.obj_make_ref_op) -def obj_make_ref_op(x: ibis_types.Value, y: ibis_types.Value): - return obj_make_ref(uri=x, authorizer=y) - - -@scalar_op_compiler.register_unary_op(ops.obj_make_ref_json_op) -def obj_make_ref_json_op(x: ibis_types.Value): - return obj_make_ref_json(objectref_json=x) - - -# Ternary Operations -@scalar_op_compiler.register_ternary_op(ops.where_op) -def where_op( - original: ibis_types.Value, - condition: ibis_types.Value, - replacement: ibis_types.Value, -) -> ibis_types.Value: - """Returns x if y is true, otherwise returns z.""" - return ibis_api.case().when(condition, original).else_(replacement).end() # type: ignore - - -@scalar_op_compiler.register_ternary_op(ops.clip_op) -def clip_op( - original: ibis_types.Value, - lower: ibis_types.Value, - upper: ibis_types.Value, -) -> ibis_types.Value: - """Clips value to lower and upper bounds.""" - if isinstance(lower, ibis_types.NullScalar) and ( - not isinstance(upper, ibis_types.NullScalar) - ): - return ibis_api.least(original, upper) - elif (not isinstance(lower, ibis_types.NullScalar)) and isinstance( - upper, ibis_types.NullScalar - ): - return ibis_api.greatest(original, lower) - elif isinstance(lower, ibis_types.NullScalar) and ( - isinstance(upper, ibis_types.NullScalar) - ): - return original - else: - # Note: Pandas has unchanged behavior when upper bound and lower bound are flipped. This implementation requires that lower_bound < upper_bound - return ibis_api.greatest(ibis_api.least(original, upper), lower) - - -# N-ary Operations -@scalar_op_compiler.register_nary_op(ops.case_when_op) -def case_when_op(*cases_and_outputs: ibis_types.Value) -> ibis_types.Value: - # ibis can handle most type coercions, but we need to force bool -> int - # TODO: dispatch coercion depending on bigframes dtype schema - result_values = cases_and_outputs[1::2] - do_upcast_bool = any(t.type().is_numeric() for t in result_values) - if do_upcast_bool: - # Just need to upcast to int, ibis can handle further coercion - result_values = tuple( - val.cast(ibis_dtypes.int64) if val.type().is_boolean() else val - for val in result_values - ) - - case_val = ibis_api.case() - for predicate, output in zip(cases_and_outputs[::2], result_values): - case_val = case_val.when(predicate, output) - return case_val.end() # type: ignore - - -@scalar_op_compiler.register_nary_op(ops.SqlScalarOp, pass_op=True) -def sql_scalar_op_impl(*operands: ibis_types.Value, op: ops.SqlScalarOp): - return ibis_generic.SqlScalar( - op.sql_template, - values=tuple(typing.cast(ibis_generic.Value, expr.op()) for expr in operands), - output_type=bigframes.core.compile.ibis_types.bigframes_dtype_to_ibis_dtype( - op.output_type() - ), - ).to_expr() - - -@scalar_op_compiler.register_nary_op(ops.StructOp, pass_op=True) -def struct_op_impl( - *values: ibis_types.Value, op: ops.StructOp -) -> ibis_types.StructValue: - data = {} - for i, value in enumerate(values): - data[op.column_names[i]] = value - - return ibis_types.struct(data) - - -@scalar_op_compiler.register_nary_op(ops.AIGenerate, pass_op=True) -def ai_generate( - *values: ibis_types.Value, op: ops.AIGenerate -) -> ibis_types.StructValue: - return ai_ops.AIGenerate( - _construct_prompt(values, op.prompt_context), # type: ignore - op.connection_id, # type: ignore - op.endpoint, # type: ignore - op.request_type, # type: ignore - op.model_params, # type: ignore - op.output_schema, # type: ignore - ).to_expr() - - -@scalar_op_compiler.register_nary_op(ops.AIGenerateBool, pass_op=True) -def ai_generate_bool( - *values: ibis_types.Value, op: ops.AIGenerateBool -) -> ibis_types.StructValue: - return ai_ops.AIGenerateBool( - _construct_prompt(values, op.prompt_context), # type: ignore - op.connection_id, # type: ignore - op.endpoint, # type: ignore - op.request_type, # type: ignore - op.model_params, # type: ignore - ).to_expr() - - -@scalar_op_compiler.register_nary_op(ops.AIGenerateInt, pass_op=True) -def ai_generate_int( - *values: ibis_types.Value, op: ops.AIGenerateInt -) -> ibis_types.StructValue: - return ai_ops.AIGenerateInt( - _construct_prompt(values, op.prompt_context), # type: ignore - op.connection_id, # type: ignore - op.endpoint, # type: ignore - op.request_type, # type: ignore - op.model_params, # type: ignore - ).to_expr() - - -@scalar_op_compiler.register_nary_op(ops.AIGenerateDouble, pass_op=True) -def ai_generate_double( - *values: ibis_types.Value, op: ops.AIGenerateDouble -) -> ibis_types.StructValue: - return ai_ops.AIGenerateDouble( - _construct_prompt(values, op.prompt_context), # type: ignore - op.connection_id, # type: ignore - op.endpoint, # type: ignore - op.request_type, # type: ignore - op.model_params, # type: ignore - ).to_expr() - - -@scalar_op_compiler.register_unary_op(ops.AIEmbed, pass_op=True) -def ai_embed(value: ibis_types.Value, op: ops.AIEmbed) -> ibis_types.StructValue: - return ai_ops.AIEmbed( - value, # type: ignore - connection_id=op.connection_id, # type: ignore - endpoint=op.endpoint, # type: ignore - model=op.model, # type: ignore - task_type=op.task_type, # type: ignore - title=op.title, # type: ignore - model_params=op.model_params, # type: ignore - ).to_expr() - - -@scalar_op_compiler.register_nary_op(ops.AIIf, pass_op=True) -def ai_if(*values: ibis_types.Value, op: ops.AIIf) -> ibis_types.StructValue: - return ai_ops.AIIf( - _construct_prompt(values, op.prompt_context), # type: ignore - op.connection_id, # type: ignore - op.endpoint, # type: ignore - op.optimization_mode, # type: ignore - op.max_error_ratio, # type: ignore - ).to_expr() - - -@scalar_op_compiler.register_nary_op(ops.AIClassify, pass_op=True) -def ai_classify( - *values: ibis_types.Value, op: ops.AIClassify -) -> ibis_types.StructValue: - return ai_ops.AIClassify( - _construct_prompt(values, op.prompt_context), # type: ignore - op.categories, # type: ignore - _construct_examples(op.examples), # type: ignore - op.connection_id, # type: ignore - op.endpoint, # type: ignore - op.output_mode, # type: ignore - op.optimization_mode, # type: ignore - op.max_error_ratio, # type: ignore - ).to_expr() - - -@scalar_op_compiler.register_nary_op(ops.AIScore, pass_op=True) -def ai_score(*values: ibis_types.Value, op: ops.AIScore) -> ibis_types.StructValue: - return ai_ops.AIScore( - _construct_prompt(values, op.prompt_context), # type: ignore - op.connection_id, # type: ignore - op.endpoint, # type: ignore - op.max_error_ratio, # type: ignore - ).to_expr() - - -@scalar_op_compiler.register_binary_op(ops.AISimilarity, pass_op=True) -def ai_similarity( - content1: ibis_types.Value, content2: ibis_types.Value, op: ops.AISimilarity -) -> ibis_types.Value: - return ai_ops.AISimilarity( - content1, # type: ignore - content2, # type: ignore - op.endpoint, # type: ignore - op.model, # type: ignore - op.model_params, # type: ignore - op.connection_id, # type: ignore - ).to_expr() - - -def _construct_prompt( - col_refs: tuple[ibis_types.Value], prompt_context: tuple[str | None] -) -> ibis_types.StructValue: - prompt: dict[str, ibis_types.Value | str] = {} - column_ref_idx = 0 - - for idx, elem in enumerate(prompt_context): - if elem is None: - prompt[f"_field_{idx + 1}"] = col_refs[column_ref_idx] - column_ref_idx += 1 - else: - prompt[f"_field_{idx + 1}"] = elem - - return ibis.struct(prompt) - - -def _construct_examples( - examples: tuple[tuple[str, str | tuple[str, ...]], ...] | None, -) -> ibis_types.ArrayValue | None: - if examples is None: - return None - - results: list[ibis_types.StructValue] = [] - - for example in examples: - value: Any = example[1] - if isinstance(example[1], (list, tuple)): - value = list(example[1]) - - ibis_example = ibis.struct({"_field_1": example[0], "_field_2": value}) - results.append(ibis_example) - - return ibis.array(results) - - -@scalar_op_compiler.register_nary_op(ops.RowKey, pass_op=True) -def rowkey_op_impl(*values: ibis_types.Value, op: ops.RowKey) -> ibis_types.Value: - return bigframes.core.compile.ibis_compiler.default_ordering.gen_row_key(values) - - -# Helpers -def is_null(value) -> bool: - # float NaN/inf should be treated as distinct from 'true' null values - return typing.cast(bool, pd.isna(value)) and not isinstance(value, float) - - -def _ibis_num(number: float): - return typing.cast(ibis_types.NumericValue, ibis_types.literal(number)) - - -@ibis_udf.scalar.builtin -def timestamp(a) -> ibis_dtypes.timestamp: # type: ignore - """Convert string or a datetime to timestamp.""" - - -@ibis_udf.scalar.builtin -def unix_millis(a: ibis_dtypes.timestamp) -> int: # type: ignore - """Convert a timestamp to milliseconds""" - - -@ibis_udf.scalar.builtin -def unix_micros(a: ibis_dtypes.timestamp) -> int: # type: ignore - """Convert a timestamp to microseconds""" - - -# Need these because ibis otherwise tries to do casts to int that can fail -@ibis_udf.scalar.builtin(name="floor") -def float_floor(a: float) -> float: - """Convert string to timestamp.""" - return 0 # pragma: NO COVER - - -@ibis_udf.scalar.builtin(name="ceil") -def float_ceil(a: float) -> float: - """Convert string to timestamp.""" - return 0 # pragma: NO COVER - - -@ibis_udf.scalar.builtin(name="parse_json") -def parse_json(json_str: str) -> ibis_dtypes.JSON: # type: ignore[empty-body] - """Converts a JSON-formatted STRING value to a JSON value.""" - - -@ibis_udf.scalar.builtin(name="SAFE.PARSE_JSON") -def parse_json_in_safe(json_str: str) -> ibis_dtypes.JSON: # type: ignore[empty-body] - """Converts a JSON-formatted STRING value to a JSON value in the safe mode.""" - - -@ibis_udf.scalar.builtin(name="json_set") -def json_set( # type: ignore[empty-body] - json_obj: ibis_dtypes.JSON, json_path: ibis_dtypes.String, json_value -) -> ibis_dtypes.JSON: - """Produces a new SQL JSON value with the specified JSON data inserted or replaced.""" - - -@ibis_udf.scalar.builtin(name="json_extract_string_array") -def json_extract_string_array( # type: ignore[empty-body] - json_obj: ibis_dtypes.JSON, json_path: ibis_dtypes.String -) -> ibis_dtypes.Array[ibis_dtypes.String]: - """Extracts a JSON array and converts it to a SQL ARRAY of STRINGs.""" - - -@ibis_udf.scalar.builtin(name="to_json") -def to_json(json_obj) -> ibis_dtypes.JSON: # type: ignore[empty-body] - """Convert to JSON.""" - - -@ibis_udf.scalar.builtin(name="to_json_string") -def to_json_string(value) -> ibis_dtypes.String: # type: ignore[empty-body] - """Convert value to JSON-formatted string.""" - - -@ibis_udf.scalar.builtin(name="json_keys") -def json_keys( # type: ignore[empty-body] - json_obj: ibis_dtypes.JSON, - max_depth: ibis_dtypes.Int64, -) -> ibis_dtypes.Array[ibis_dtypes.String]: - """Extracts unique JSON keys from a JSON expression.""" - - -@ibis_udf.scalar.builtin(name="json_value") -def json_value( # type: ignore[empty-body] - json_obj: ibis_dtypes.JSON, json_path: ibis_dtypes.String -) -> ibis_dtypes.String: - """Retrieve value of a JSON field as plain STRING.""" - - -@ibis_udf.scalar.builtin(name="json_value_array") -def json_value_array( # type: ignore[empty-body] - json_obj: ibis_dtypes.JSON, json_path: ibis_dtypes.String -) -> ibis_dtypes.Array[ibis_dtypes.String]: - """Extracts a JSON array and converts it to a SQL ARRAY of STRINGs.""" - - -@ibis_udf.scalar.builtin(name="INT64") -def cast_json_to_int64(json_str: ibis_dtypes.JSON) -> ibis_dtypes.Int64: # type: ignore[empty-body] - """Converts a JSON number to a SQL INT64 value.""" - - -@ibis_udf.scalar.builtin(name="SAFE.INT64") -def cast_json_to_int64_in_safe(json_str: ibis_dtypes.JSON) -> ibis_dtypes.Int64: # type: ignore[empty-body] - """Converts a JSON number to a SQL INT64 value in the safe mode.""" - - -@ibis_udf.scalar.builtin(name="FLOAT64") -def cast_json_to_float64(json_str: ibis_dtypes.JSON) -> ibis_dtypes.Float64: # type: ignore[empty-body] - """Attempts to convert a JSON value to a SQL FLOAT64 value.""" - - -@ibis_udf.scalar.builtin(name="SAFE.FLOAT64") -def cast_json_to_float64_in_safe(json_str: ibis_dtypes.JSON) -> ibis_dtypes.Float64: # type: ignore[empty-body] - """Attempts to convert a JSON value to a SQL FLOAT64 value.""" - - -@ibis_udf.scalar.builtin(name="BOOL") -def cast_json_to_bool(json_str: ibis_dtypes.JSON) -> ibis_dtypes.Boolean: # type: ignore[empty-body] - """Attempts to convert a JSON value to a SQL BOOL value.""" - - -@ibis_udf.scalar.builtin(name="SAFE.BOOL") -def cast_json_to_bool_in_safe(json_str: ibis_dtypes.JSON) -> ibis_dtypes.Boolean: # type: ignore[empty-body] - """Attempts to convert a JSON value to a SQL BOOL value.""" - - -@ibis_udf.scalar.builtin(name="STRING") -def cast_json_to_string(json_str: ibis_dtypes.JSON) -> ibis_dtypes.String: # type: ignore[empty-body] - """Attempts to convert a JSON value to a SQL STRING value.""" - - -@ibis_udf.scalar.builtin(name="SAFE.STRING") -def cast_json_to_string_in_safe(json_str: ibis_dtypes.JSON) -> ibis_dtypes.String: # type: ignore[empty-body] - """Attempts to convert a JSON value to a SQL STRING value.""" - - -@ibis_udf.scalar.builtin(name="ML.DISTANCE") -def vector_distance(vector1, vector2, type: str) -> ibis_dtypes.Float64: # type: ignore[empty-body] - """Computes the distance between two vectors using specified type ("EUCLIDEAN", "MANHATTAN", or "COSINE")""" - - -@ibis_udf.scalar.builtin(name="OBJ.FETCH_METADATA") -def obj_fetch_metadata(obj_ref: _OBJ_REF_IBIS_DTYPE) -> _OBJ_REF_IBIS_DTYPE: # type: ignore - """Fetch metadata from ObjectRef Struct.""" - - -@ibis_udf.scalar.builtin(name="OBJ.MAKE_REF") -def obj_make_ref(uri: str, authorizer: str) -> _OBJ_REF_IBIS_DTYPE: # type: ignore - """Make ObjectRef Struct from uri and connection.""" - - -@ibis_udf.scalar.builtin(name="OBJ.MAKE_REF") -def obj_make_ref_json(objectref_json: ibis_dtypes.JSON) -> _OBJ_REF_IBIS_DTYPE: # type: ignore - """Make ObjectRef Struct from json.""" - - -@ibis_udf.scalar.builtin(name="OBJ.GET_ACCESS_URL") -# Stub for BigQuery UDF, empty body is intentional. -# _OBJ_REF_IBIS_DTYPE is a variable holding a type, Mypy complains about it being used as type hint. -def obj_get_access_url( # type: ignore[empty-body] - obj_ref: _OBJ_REF_IBIS_DTYPE, # type: ignore[valid-type] - mode: ibis_dtypes.String, -) -> ibis_dtypes.JSON: - """Get access url (as ObjectRefRumtime JSON) from ObjectRef.""" - - -@ibis_udf.scalar.builtin(name="OBJ.GET_ACCESS_URL") -def obj_get_access_url_with_duration(obj_ref, mode, duration) -> ibis_dtypes.JSON: # type: ignore - """Get access url (as ObjectRefRumtime JSON) from ObjectRef.""" - - -@ibis_udf.scalar.builtin(name="ltrim") -def str_lstrip_op( # type: ignore[empty-body] - x: ibis_dtypes.String, to_strip: ibis_dtypes.String -) -> ibis_dtypes.String: - """Remove leading and trailing characters.""" - - -@ibis_udf.scalar.builtin(name="rtrim") -def str_rstrip_op( # type: ignore[empty-body] - x: ibis_dtypes.String, to_strip: ibis_dtypes.String -) -> ibis_dtypes.String: - """Remove leading and trailing characters.""" - - -@ibis_udf.scalar.builtin(name="trim") -def str_strip_op( # type: ignore[empty-body] - x: ibis_dtypes.String, to_strip: ibis_dtypes.String -) -> ibis_dtypes.String: - """Remove leading and trailing characters.""" diff --git a/bigframes/core/compile/ibis_types.py b/bigframes/core/compile/ibis_types.py deleted file mode 100644 index 788f6db4435..00000000000 --- a/bigframes/core/compile/ibis_types.py +++ /dev/null @@ -1,431 +0,0 @@ -# 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. -# 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 typing import Dict, Iterable, Optional, Tuple, Union, cast - -import bigframes_vendored.constants as constants -import bigframes_vendored.ibis -import bigframes_vendored.ibis.expr.datatypes as ibis_dtypes -import bigframes_vendored.ibis.expr.types as ibis_types -import db_dtypes # type: ignore -import geopandas as gpd # type: ignore -import pandas as pd -import pyarrow as pa - -import bigframes.dtypes - -# Type hints for Ibis data types supported by BigQuery DataFrame -IbisDtype = Union[ - ibis_dtypes.Boolean, - ibis_dtypes.Float64, - ibis_dtypes.Int64, - ibis_dtypes.String, - ibis_dtypes.Date, - ibis_dtypes.Time, - ibis_dtypes.Timestamp, - ibis_dtypes.Binary, - ibis_dtypes.Decimal, - ibis_dtypes.GeoSpatial, - ibis_dtypes.JSON, -] - -IBIS_GEO_TYPE = ibis_dtypes.GeoSpatial(geotype="geography", srid=4326, nullable=True) - - -BIDIRECTIONAL_MAPPINGS: Iterable[Tuple[IbisDtype, bigframes.dtypes.Dtype]] = ( - (ibis_dtypes.boolean, pd.BooleanDtype()), - (ibis_dtypes.date, pd.ArrowDtype(pa.date32())), - (ibis_dtypes.float64, pd.Float64Dtype()), - (ibis_dtypes.int64, pd.Int64Dtype()), - (ibis_dtypes.string, pd.StringDtype(storage="pyarrow")), - (ibis_dtypes.time, pd.ArrowDtype(pa.time64("us"))), - (ibis_dtypes.Timestamp(timezone=None), pd.ArrowDtype(pa.timestamp("us"))), - ( - ibis_dtypes.Timestamp(timezone="UTC"), - pd.ArrowDtype(pa.timestamp("us", tz="UTC")), - ), - (ibis_dtypes.binary, pd.ArrowDtype(pa.binary())), - ( - ibis_dtypes.Decimal(precision=38, scale=9, nullable=True), - pd.ArrowDtype(pa.decimal128(38, 9)), - ), - ( - ibis_dtypes.Decimal(precision=76, scale=38, nullable=True), - pd.ArrowDtype(pa.decimal256(76, 38)), - ), - ( - IBIS_GEO_TYPE, - gpd.array.GeometryDtype(), - ), - (ibis_dtypes.json, pd.ArrowDtype(db_dtypes.JSONArrowType())), -) - -BIGFRAMES_TO_IBIS: Dict[bigframes.dtypes.Dtype, ibis_dtypes.DataType] = { - pandas: ibis for ibis, pandas in BIDIRECTIONAL_MAPPINGS -} -BIGFRAMES_TO_IBIS.update({bigframes.dtypes.TIMEDELTA_DTYPE: ibis_dtypes.int64}) -IBIS_TO_BIGFRAMES: Dict[ibis_dtypes.DataType, bigframes.dtypes.Dtype] = { - ibis: pandas for ibis, pandas in BIDIRECTIONAL_MAPPINGS -} -# Allow REQUIRED fields to map correctly. -IBIS_TO_BIGFRAMES.update( - {ibis.copy(nullable=False): pandas for ibis, pandas in BIDIRECTIONAL_MAPPINGS} -) -IBIS_TO_BIGFRAMES.update( - { - # TODO: Interval - } -) - - -def cast_ibis_value( - value: ibis_types.Value, to_type: ibis_dtypes.DataType, safe: bool = False -) -> ibis_types.Value: - """Perform compatible type casts of ibis values - - Args: - value: - Ibis value, which could be a literal, scalar, or column - - to_type: - The Ibis type to cast to - - Returns: - A new Ibis value of type to_type - - Raises: - TypeError: if the type cast cannot be executed""" - # normalize to nullable, which doesn't impact compatibility - value_type = value.type().copy(nullable=True) - if value_type == to_type: - return value - # casts that just work - # TODO(bmil): add to this as more casts are verified - good_casts = { - ibis_dtypes.bool: (ibis_dtypes.int64,), - ibis_dtypes.int64: ( - ibis_dtypes.bool, - ibis_dtypes.float64, - ibis_dtypes.string, - ibis_dtypes.Decimal(precision=38, scale=9), - ibis_dtypes.Decimal(precision=76, scale=38), - ibis_dtypes.time, - ibis_dtypes.timestamp, - ibis_dtypes.Timestamp(timezone="UTC"), - ), - ibis_dtypes.float64: ( - ibis_dtypes.string, - ibis_dtypes.int64, - ibis_dtypes.Decimal(precision=38, scale=9), - ibis_dtypes.Decimal(precision=76, scale=38), - ), - ibis_dtypes.string: ( - ibis_dtypes.int64, - ibis_dtypes.float64, - ibis_dtypes.Decimal(precision=38, scale=9), - ibis_dtypes.Decimal(precision=76, scale=38), - ibis_dtypes.binary, - ibis_dtypes.date, - ibis_dtypes.timestamp, - ibis_dtypes.Timestamp(timezone="UTC"), - ), - ibis_dtypes.date: ( - ibis_dtypes.string, - ibis_dtypes.timestamp, - ibis_dtypes.Timestamp(timezone="UTC"), - ), - ibis_dtypes.Decimal(precision=38, scale=9): ( - ibis_dtypes.float64, - ibis_dtypes.int64, - ibis_dtypes.Decimal(precision=76, scale=38), - ), - ibis_dtypes.Decimal(precision=76, scale=38): ( - ibis_dtypes.float64, - ibis_dtypes.int64, - ibis_dtypes.Decimal(precision=38, scale=9), - ), - ibis_dtypes.time: ( - ibis_dtypes.int64, - ibis_dtypes.string, - ), - ibis_dtypes.timestamp: ( - ibis_dtypes.date, - ibis_dtypes.int64, - ibis_dtypes.string, - ibis_dtypes.time, - ibis_dtypes.Timestamp(timezone="UTC"), - ), - ibis_dtypes.Timestamp(timezone="UTC"): ( - ibis_dtypes.date, - ibis_dtypes.int64, - ibis_dtypes.string, - ibis_dtypes.time, - ibis_dtypes.timestamp, - ), - ibis_dtypes.binary: (ibis_dtypes.string,), - ibis_dtypes.point: (IBIS_GEO_TYPE,), - ibis_dtypes.geometry: (IBIS_GEO_TYPE,), - ibis_dtypes.geography: (IBIS_GEO_TYPE,), - ibis_dtypes.linestring: (IBIS_GEO_TYPE,), - ibis_dtypes.polygon: (IBIS_GEO_TYPE,), - ibis_dtypes.multilinestring: (IBIS_GEO_TYPE,), - ibis_dtypes.multipoint: (IBIS_GEO_TYPE,), - ibis_dtypes.multipolygon: (IBIS_GEO_TYPE,), - } - - if value_type in good_casts: - if to_type in good_casts[value_type]: - return value.try_cast(to_type) if safe else value.cast(to_type) - else: - # this should never happen - raise TypeError( - f"Unexpected value type {value_type}. {constants.FEEDBACK_LINK}" - ) - - # casts that need some encouragement - - # BigQuery casts bools to lower case strings. Capitalize the result to match Pandas - # TODO(bmil): remove this workaround after fixing Ibis - if value_type == ibis_dtypes.bool and to_type == ibis_dtypes.string: - if safe: - return cast(ibis_types.StringValue, value.try_cast(to_type)).capitalize() - else: - return cast(ibis_types.StringValue, value.cast(to_type)).capitalize() - - if value_type == ibis_dtypes.bool and to_type == ibis_dtypes.float64: - if safe: - return value.try_cast(ibis_dtypes.int64).try_cast(ibis_dtypes.float64) - else: - return value.cast(ibis_dtypes.int64).cast(ibis_dtypes.float64) - - if value_type == ibis_dtypes.float64 and to_type == ibis_dtypes.bool: - return value != ibis_types.literal(0) - - raise TypeError( - f"Unsupported cast {value_type} to {to_type}. {constants.FEEDBACK_LINK}" - ) - - -def bigframes_dtype_to_ibis_dtype( - bigframes_dtype: bigframes.dtypes.Dtype, -) -> ibis_dtypes.DataType: - """Converts a BigQuery DataFrames supported dtype to an Ibis dtype. - - Args: - bigframes_dtype: - A dtype supported by BigQuery DataFrame - - Returns: - IbisDtype: The corresponding Ibis type - - Raises: - ValueError: If passed a dtype not supported by BigQuery DataFrames. - """ - if bigframes_dtype in BIGFRAMES_TO_IBIS.keys(): - return BIGFRAMES_TO_IBIS[bigframes_dtype] - - elif isinstance(bigframes_dtype, pd.ArrowDtype) and bigframes_dtype.pyarrow_dtype: - return _arrow_dtype_to_ibis_dtype(bigframes_dtype.pyarrow_dtype) - - else: - raise ValueError(f"Datatype has no ibis type mapping: {bigframes_dtype}") - - -def ibis_dtype_to_bigframes_dtype( - ibis_dtype: ibis_dtypes.DataType, -) -> bigframes.dtypes.Dtype: - """Converts an Ibis dtype to a BigQuery DataFrames dtype - - Args: - ibis_dtype: The ibis dtype used to represent this type, which - should in turn correspond to an underlying BigQuery type - - Returns: - The supported BigQuery DataFrames dtype, which may be provided by - pandas, numpy, or db_types - - Raises: - ValueError: if passed an unexpected type - """ - # Special cases: Ibis supports variations on these types, but currently - # our IO returns them as objects. Eventually, we should support them as - # ArrowDType (and update the IO accordingly) - if isinstance(ibis_dtype, ibis_dtypes.Array): - return pd.ArrowDtype(_ibis_dtype_to_arrow_dtype(ibis_dtype)) - - if isinstance(ibis_dtype, ibis_dtypes.Struct): - return pd.ArrowDtype(_ibis_dtype_to_arrow_dtype(ibis_dtype)) - - # BigQuery only supports integers of size 64 bits. - if isinstance(ibis_dtype, ibis_dtypes.Integer): - return pd.Int64Dtype() - - if isinstance(ibis_dtype, ibis_dtypes.JSON): - return bigframes.dtypes.JSON_DTYPE - - if isinstance(ibis_dtype, ibis_dtypes.GeoSpatial): - return gpd.array.GeometryDtype() - - if ibis_dtype in IBIS_TO_BIGFRAMES: - return IBIS_TO_BIGFRAMES[ibis_dtype] - elif isinstance(ibis_dtype, ibis_dtypes.Decimal): - # Temporary workaround for ibis decimal issue (b/323387826) - if ibis_dtype.precision is not None and ibis_dtype.precision >= 76: - return pd.ArrowDtype(pa.decimal256(76, 38)) - else: - return pd.ArrowDtype(pa.decimal128(38, 9)) - elif isinstance(ibis_dtype, ibis_dtypes.Null): - # Fallback to STRING for NULL values for most flexibility in SQL. - return IBIS_TO_BIGFRAMES[ibis_dtypes.string] - else: - raise ValueError( - f"Unexpected Ibis data type {ibis_dtype}. {constants.FEEDBACK_LINK}" - ) - - -def _ibis_dtype_to_arrow_dtype(ibis_dtype: ibis_dtypes.DataType) -> pa.DataType: - """Private utility to convert ibis dtype to equivalent arrow type.""" - if isinstance(ibis_dtype, ibis_dtypes.Array): - return pa.list_( - _ibis_dtype_to_arrow_dtype(ibis_dtype.value_type.copy(nullable=True)) - ) - - if isinstance(ibis_dtype, ibis_dtypes.Struct): - return pa.struct( - [ - pa.field( - name, - _ibis_dtype_to_arrow_dtype(dtype), - nullable=not pa.types.is_list(_ibis_dtype_to_arrow_dtype(dtype)), - ) - for name, dtype in ibis_dtype.fields.items() - ] - ) - - if ibis_dtype in IBIS_TO_BIGFRAMES: - dtype = IBIS_TO_BIGFRAMES[ibis_dtype] - # Note: arrow mappings are incomplete, no geography type - return bigframes.dtypes.bigframes_dtype_to_arrow_dtype(dtype) - else: - raise ValueError( - f"Unexpected Ibis data type {ibis_dtype}. {constants.FEEDBACK_LINK}" - ) - - -_ARROW_TO_IBIS = { - mapping.arrow_dtype: bigframes_dtype_to_ibis_dtype(mapping.dtype) - for mapping in bigframes.dtypes.SIMPLE_TYPES - if mapping.arrow_dtype is not None -} - - -def _arrow_dtype_to_ibis_dtype(arrow_dtype: pa.DataType) -> ibis_dtypes.DataType: - if arrow_dtype == pa.null(): - # Used for empty local dataframes where pyarrow has null type - return ibis_dtypes.float64 - if pa.types.is_struct(arrow_dtype): - struct_dtype = cast(pa.StructType, arrow_dtype) - return ibis_dtypes.Struct.from_tuples( - [ - (field.name, _arrow_dtype_to_ibis_dtype(field.type)) - for field in struct_dtype - ] - ) - if pa.types.is_list(arrow_dtype): - list_dtype = cast(pa.ListType, arrow_dtype) - value_dtype = list_dtype.value_type - value_ibis_type = _arrow_dtype_to_ibis_dtype(value_dtype) - return ibis_dtypes.Array(value_type=value_ibis_type) - elif arrow_dtype in _ARROW_TO_IBIS: - return _ARROW_TO_IBIS[arrow_dtype] - else: - raise ValueError(f"Unexpected arrow type: {arrow_dtype}") - - -def literal_to_ibis_scalar( - literal, force_dtype: Optional[bigframes.dtypes.Dtype] = None, validate: bool = True -): - """Accept any literal and, if possible, return an Ibis Scalar - expression with a BigQuery DataFrames compatible data type - - Args: - literal: - any value accepted by Ibis - force_dtype: - force the value to a specific dtype - validate: - If true, will raise ValueError if type cannot be stored in a - BigQuery DataFrames object. If used as a subexpression, this should - be disabled. - - Returns: - An ibis Scalar supported by BigQuery DataFrame - - Raises: - ValueError: if passed literal cannot be coerced to a - BigQuery DataFrames compatible scalar - """ - # Special case: Can create nulls for non-bidirectional types - if (force_dtype == gpd.array.GeometryDtype()) and pd.isna(literal): - # Ibis has bug for casting nulltype to geospatial, so we perform intermediate cast first - geotype = ibis_dtypes.GeoSpatial(geotype="geography", srid=4326, nullable=True) - return bigframes_vendored.ibis.literal(None, geotype) - - ibis_dtype = bigframes_dtype_to_ibis_dtype(force_dtype) if force_dtype else None - - if pd.api.types.is_list_like(literal): - # "correct" way would be to use ibis.array, but this produces invalid BQ SQL syntax - return tuple(literal) - - if not pd.api.types.is_list_like(literal) and pd.isna(literal): - if ibis_dtype: - return bigframes_vendored.ibis.null().cast(ibis_dtype) - else: - return bigframes_vendored.ibis.null() - - scalar_expr = bigframes_vendored.ibis.literal(literal) - if ibis_dtype: - scalar_expr = bigframes_vendored.ibis.literal(literal, ibis_dtype) - elif scalar_expr.type().is_floating(): - scalar_expr = bigframes_vendored.ibis.literal(literal, ibis_dtypes.float64) - elif scalar_expr.type().is_integer(): - scalar_expr = bigframes_vendored.ibis.literal(literal, ibis_dtypes.int64) - elif scalar_expr.type().is_decimal(): - scalar_expr_type = cast(ibis_dtypes.Decimal, scalar_expr.type()) - precision = scalar_expr_type.precision - scale = scalar_expr_type.scale - if (not precision and not scale) or ( - precision and scale and scale <= 9 and precision + (9 - scale) <= 38 - ): - scalar_expr = bigframes_vendored.ibis.literal( - literal, ibis_dtypes.decimal(precision=38, scale=9) - ) - elif precision and scale and scale <= 38 and precision + (38 - scale) <= 76: - scalar_expr = bigframes_vendored.ibis.literal( - literal, ibis_dtypes.decimal(precision=76, scale=38) - ) - else: - raise TypeError( - "BigQuery's decimal data type supports a maximum precision of 76 and a maximum scale of 38." - f"Current precision: {precision}. Current scale: {scale}" - ) - - # TODO(bmil): support other literals that can be coerced to compatible types - if validate and (scalar_expr.type() not in BIGFRAMES_TO_IBIS.values()): - raise ValueError( - f"Literal did not coerce to a supported data type: {scalar_expr.type()}. {constants.FEEDBACK_LINK}" - ) - - return scalar_expr diff --git a/bigframes/core/compile/polars/__init__.py b/bigframes/core/compile/polars/__init__.py deleted file mode 100644 index 027582c7ded..00000000000 --- a/bigframes/core/compile/polars/__init__.py +++ /dev/null @@ -1,44 +0,0 @@ -# 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. -# 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. - -"""Compiler for BigFrames expression to Polars LazyFrame expression. - -Make sure to import all polars implementations here so that they get registered. -""" - -from __future__ import annotations - -import warnings - -# The ops imports appear first so that the implementations can be registered. -# polars shouldn't be needed at import time, as register is a no-op if polars -# isn't installed. -import bigframes.core.compile.polars.operations.array_ops # noqa: F401 -import bigframes.core.compile.polars.operations.generic_ops # noqa: F401 -import bigframes.core.compile.polars.operations.numeric_ops # noqa: F401 -import bigframes.core.compile.polars.operations.struct_ops # noqa: F401 - -try: - import bigframes._importing - - # Use import_polars() instead of importing directly so that we check the - # version numbers. - bigframes._importing.import_polars() - - from bigframes.core.compile.polars.compiler import PolarsCompiler - - __all__ = ["PolarsCompiler"] -except Exception as exc: - msg = f"Polars compiler not available as there was an exception importing polars. Details: {str(exc)}" - warnings.warn(msg) diff --git a/bigframes/core/compile/polars/compiler.py b/bigframes/core/compile/polars/compiler.py deleted file mode 100644 index 65b217602b9..00000000000 --- a/bigframes/core/compile/polars/compiler.py +++ /dev/null @@ -1,1148 +0,0 @@ -# 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. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -from __future__ import annotations - -import dataclasses -import functools -import itertools -from typing import TYPE_CHECKING, Literal, Optional, Sequence, Tuple, Type, cast - -import pandas as pd - -import bigframes.core -import bigframes.core.expression as ex -import bigframes.core.guid as guid -import bigframes.core.rewrite -import bigframes.core.rewrite.schema_binding -import bigframes.dtypes -import bigframes.operations as ops -import bigframes.operations.aggregations as agg_ops -import bigframes.operations.array_ops as arr_ops -import bigframes.operations.bool_ops as bool_ops -import bigframes.operations.comparison_ops as comp_ops -import bigframes.operations.date_ops as date_ops -import bigframes.operations.datetime_ops as dt_ops -import bigframes.operations.frequency_ops as freq_ops -import bigframes.operations.generic_ops as gen_ops -import bigframes.operations.json_ops as json_ops -import bigframes.operations.numeric_ops as num_ops -import bigframes.operations.remote_function_ops as remote_function_ops -import bigframes.operations.string_ops as string_ops -import bigframes.operations.struct_ops as struct_ops -from bigframes.core import agg_expressions, identifiers, nodes, ordering, window_spec -from bigframes.core.compile.polars import lowering - -polars_installed = True -if TYPE_CHECKING: - import polars as pl -else: - try: - import bigframes._importing - - # Use import_polars() instead of importing directly so that we check - # the version numbers. - pl = bigframes._importing.import_polars() - except Exception: - polars_installed = False - - -def register_op(op: Type): - """Register a compilation from BigFrames to Ibis. - - This decorator can be used, even if Polars is not installed. - - Args: - op: The type of the operator the wrapped function compiles. - """ - - def decorator(func): - if polars_installed: - # Ignore the type because compile_op is a generic Callable, so - # register isn't available according to mypy. - return PolarsExpressionCompiler.compile_op.register(op)(func) # type: ignore - else: - return func - - return decorator - - -if polars_installed: - _FREQ_MAPPING = { - "Y": "1y", - "Q": "1q", - "M": "1mo", - "W": "1w", - "D": "1d", - "h": "1h", - "min": "1m", - "s": "1s", - "ms": "1ms", - "us": "1us", - "ns": "1ns", - } - - _DTYPE_MAPPING = { - # Direct mappings - bigframes.dtypes.INT_DTYPE: pl.Int64(), - bigframes.dtypes.FLOAT_DTYPE: pl.Float64(), - bigframes.dtypes.BOOL_DTYPE: pl.Boolean(), - bigframes.dtypes.STRING_DTYPE: pl.String(), - bigframes.dtypes.NUMERIC_DTYPE: pl.Decimal(38, 9), - bigframes.dtypes.BIGNUMERIC_DTYPE: pl.Decimal(76, 38), - bigframes.dtypes.BYTES_DTYPE: pl.Binary(), - bigframes.dtypes.DATE_DTYPE: pl.Date(), - bigframes.dtypes.DATETIME_DTYPE: pl.Datetime(time_zone=None), - bigframes.dtypes.TIMESTAMP_DTYPE: pl.Datetime(time_zone="UTC"), - bigframes.dtypes.TIME_DTYPE: pl.Time(), - bigframes.dtypes.TIMEDELTA_DTYPE: pl.Duration(), - # Indirect mappings - bigframes.dtypes.GEO_DTYPE: pl.String(), - bigframes.dtypes.JSON_DTYPE: pl.String(), - } - - def _bigframes_dtype_to_polars_dtype( - dtype: bigframes.dtypes.ExpressionType, - ) -> pl.DataType: - if dtype is None: - return pl.Null() - if bigframes.dtypes.is_struct_like(dtype): - return pl.Struct( - [ - pl.Field(name, _bigframes_dtype_to_polars_dtype(type)) - for name, type in bigframes.dtypes.get_struct_fields(dtype).items() - ] - ) - if bigframes.dtypes.is_array_like(dtype): - return pl.List( - inner=_bigframes_dtype_to_polars_dtype( - bigframes.dtypes.get_array_inner_type(dtype) - ) - ) - else: - return _DTYPE_MAPPING[dtype] - - @dataclasses.dataclass(frozen=True) - class PolarsExpressionCompiler: - """ - Simple compiler for converting bigframes expressions to polars expressions. - - Should be extended to dispatch based on bigframes schema types. - """ - - _expr_types: dict[int, bigframes.dtypes.ExpressionType] = dataclasses.field( - default_factory=dict, init=False, compare=False - ) - - def compile_expression(self, expression: ex.Expression) -> pl.Expr: - res = self._compile_expression(expression) - self._expr_types[id(res)] = expression.output_type - return res - - @functools.singledispatchmethod - def _compile_expression(self, expression: ex.Expression) -> pl.Expr: - raise NotImplementedError(f"Cannot compile expression: {expression}") - - @_compile_expression.register - def _( - self, - expression: ex.ScalarConstantExpression, - ) -> pl.Expr: - value = expression.value - if not isinstance(value, float) and pd.isna(value): # type: ignore - value = None - if expression.dtype is None: - return pl.lit(None) - - # Polars lit does not handle pandas timedelta well at v1.36 - if isinstance(value, pd.Timedelta): - value = value.to_pytimedelta() - - return pl.lit(value, _bigframes_dtype_to_polars_dtype(expression.dtype)) - - @_compile_expression.register - def _( - self, - expression: ex.DerefOp, - ) -> pl.Expr: - return pl.col(expression.id.sql) - - @_compile_expression.register - def _( - self, - expression: ex.ResolvedDerefOp, - ) -> pl.Expr: - return pl.col(expression.id.sql) - - @_compile_expression.register - def _( - self, - expression: ex.OpExpression, - ) -> pl.Expr: - import datetime - - import pyarrow as pa - - op = expression.op - - # Polars panics on nulls from pandas objects in timezone-aware - # datetimes for certain ops. Convert to timezone-naive temporarily - # to avoid this issue. - # TODO(tswast): Remove workaround when - # https://github.com/pola-rs/polars/issues/27862 has been fixed. - is_problematic_op = type(op) in ( - date_ops.YearOp, - date_ops.QuarterOp, - date_ops.MonthOp, - date_ops.DayOp, - date_ops.IsoWeekOp, - ) - - if is_problematic_op and len(expression.inputs) == 1: - input_expr = expression.inputs[0] - if ( - input_expr.is_resolved - and isinstance(input_expr.output_type, pd.ArrowDtype) - and isinstance( - input_expr.output_type.pyarrow_dtype, pa.TimestampType - ) - and input_expr.output_type.pyarrow_dtype.tz is not None - ): - tz_str = input_expr.output_type.pyarrow_dtype.tz - if tz_str == "UTC": - dummy_tz = datetime.timezone.utc - else: - try: - from zoneinfo import ZoneInfo - - dummy_tz = ZoneInfo(tz_str) # type: ignore - except Exception: - dummy_tz = datetime.timezone.utc - - dummy_val = datetime.datetime(1970, 1, 1, tzinfo=dummy_tz) - - compiled_input = self.compile_expression(input_expr) - filled_input = compiled_input.fill_null(dummy_val) - compiled_op_with_fill = self.compile_op(op, filled_input) - - return ( - pl.when(compiled_input.is_null()) - .then(None) - .otherwise(compiled_op_with_fill) - ) - - # TODO: Complete the implementation - args = tuple(map(self.compile_expression, expression.inputs)) - return self.compile_op(op, *args) - - @functools.singledispatchmethod - def compile_op(self, op: ops.ScalarOp, *args: pl.Expr) -> pl.Expr: - raise NotImplementedError(f"Polars compiler hasn't implemented {op}") - - @compile_op.register(gen_ops.InvertOp) - def _(self, op: ops.ScalarOp, input: pl.Expr) -> pl.Expr: - return input.not_() - - @compile_op.register(num_ops.AbsOp) - def _(self, op: ops.ScalarOp, input: pl.Expr) -> pl.Expr: - return input.abs() - - @compile_op.register(num_ops.FloorOp) - def _(self, op: ops.ScalarOp, input: pl.Expr) -> pl.Expr: - return input.floor() - - @compile_op.register(num_ops.CeilOp) - def _(self, op: ops.ScalarOp, input: pl.Expr) -> pl.Expr: - return input.ceil() - - @compile_op.register(num_ops.PosOp) - def _(self, op: ops.ScalarOp, input: pl.Expr) -> pl.Expr: - return input.__pos__() - - @compile_op.register(num_ops.NegOp) - def _(self, op: ops.ScalarOp, input: pl.Expr) -> pl.Expr: - return input.__neg__() - - @compile_op.register(bool_ops.AndOp) - def _(self, op: ops.ScalarOp, l_input: pl.Expr, r_input: pl.Expr) -> pl.Expr: - return l_input & r_input - - @compile_op.register(bool_ops.OrOp) - def _(self, op: ops.ScalarOp, l_input: pl.Expr, r_input: pl.Expr) -> pl.Expr: - return l_input | r_input - - @compile_op.register(bool_ops.XorOp) - def _(self, op: ops.ScalarOp, l_input: pl.Expr, r_input: pl.Expr) -> pl.Expr: - return l_input ^ r_input - - @compile_op.register(num_ops.AddOp) - def _(self, op: ops.ScalarOp, l_input: pl.Expr, r_input: pl.Expr) -> pl.Expr: - return l_input + r_input - - @compile_op.register(num_ops.SubOp) - def _(self, op: ops.ScalarOp, l_input: pl.Expr, r_input: pl.Expr) -> pl.Expr: - return l_input - r_input - - @compile_op.register(num_ops.MulOp) - def _(self, op: ops.ScalarOp, l_input: pl.Expr, r_input: pl.Expr) -> pl.Expr: - return l_input * r_input - - @compile_op.register(num_ops.DivOp) - def _(self, op: ops.ScalarOp, l_input: pl.Expr, r_input: pl.Expr) -> pl.Expr: - return l_input / r_input - - @compile_op.register(num_ops.FloorDivOp) - def _(self, op: ops.ScalarOp, l_input: pl.Expr, r_input: pl.Expr) -> pl.Expr: - return l_input // r_input - - @compile_op.register(num_ops.ModOp) - def _(self, op: ops.ScalarOp, l_input: pl.Expr, r_input: pl.Expr) -> pl.Expr: - return l_input % r_input - - @compile_op.register(num_ops.PowOp) - @compile_op.register(num_ops.UnsafePowOp) - def _(self, op: ops.ScalarOp, l_input: pl.Expr, r_input: pl.Expr) -> pl.Expr: - return l_input**r_input - - @compile_op.register(comp_ops.EqOp) - def _(self, op: ops.ScalarOp, l_input: pl.Expr, r_input: pl.Expr) -> pl.Expr: - return l_input.eq(r_input) - - @compile_op.register(comp_ops.EqNullsMatchOp) - def _(self, op: ops.ScalarOp, l_input: pl.Expr, r_input: pl.Expr) -> pl.Expr: - return l_input.eq_missing(r_input) - - @compile_op.register(comp_ops.NeOp) - def _(self, op: ops.ScalarOp, l_input: pl.Expr, r_input: pl.Expr) -> pl.Expr: - return l_input.ne(r_input) - - @compile_op.register(comp_ops.GtOp) - def _(self, op: ops.ScalarOp, l_input: pl.Expr, r_input: pl.Expr) -> pl.Expr: - return l_input > r_input - - @compile_op.register(comp_ops.GeOp) - def _(self, op: ops.ScalarOp, l_input: pl.Expr, r_input: pl.Expr) -> pl.Expr: - return l_input >= r_input - - @compile_op.register(comp_ops.LtOp) - def _(self, op: ops.ScalarOp, l_input: pl.Expr, r_input: pl.Expr) -> pl.Expr: - return l_input < r_input - - @compile_op.register(comp_ops.LeOp) - def _(self, op: ops.ScalarOp, l_input: pl.Expr, r_input: pl.Expr) -> pl.Expr: - return l_input <= r_input - - @compile_op.register(gen_ops.IsInOp) - def _(self, op: ops.ScalarOp, input: pl.Expr) -> pl.Expr: - # TODO: Filter out types that can't be coerced to right type - assert isinstance(op, gen_ops.IsInOp) - assert not op.match_nulls # should be stripped by a lowering step rn - values = pl.Series(op.values, strict=False) - return input.is_in(values) - - @compile_op.register(gen_ops.FillNaOp) - @compile_op.register(gen_ops.CoalesceOp) - def _(self, op: ops.ScalarOp, l_input: pl.Expr, r_input: pl.Expr) -> pl.Expr: - return pl.coalesce(l_input, r_input) - - @compile_op.register(gen_ops.CaseWhenOp) - def _(self, op: ops.ScalarOp, *inputs: pl.Expr) -> pl.Expr: - expr = pl.when(inputs[0]).then(inputs[1]) - for pred, result in zip(inputs[2::2], inputs[3::2]): - expr = expr.when(pred).then(result) # type: ignore - return expr - - @compile_op.register(gen_ops.WhereOp) - def _( - self, - op: ops.ScalarOp, - original: pl.Expr, - condition: pl.Expr, - otherwise: pl.Expr, - ) -> pl.Expr: - return pl.when(condition).then(original).otherwise(otherwise) - - @compile_op.register(gen_ops.CoerceToBoolOp) - def _(self, op: ops.ScalarOp, input: pl.Expr) -> pl.Expr: - assert isinstance(op, gen_ops.CoerceToBoolOp) - from_type = self._expr_types.get(id(input)) - if from_type is None: - return input.cast(pl.Boolean).fill_null(False) - - if from_type == bigframes.dtypes.BOOL_DTYPE: - res = input - elif bigframes.dtypes.is_numeric(from_type): - res = input != 0 - elif from_type == bigframes.dtypes.BYTES_DTYPE: - res = input.bin.size() > 0 - elif bigframes.dtypes.is_string_like(from_type): - res = input.str.len_chars() > 0 - elif bigframes.dtypes.is_array_like(from_type): - res = input.list.len() > 0 - else: - res = input.is_not_null() - - return res.fill_null(False) - - @compile_op.register(gen_ops.AsTypeOp) - def _(self, op: ops.ScalarOp, input: pl.Expr) -> pl.Expr: - assert isinstance(op, gen_ops.AsTypeOp) - # TODO: Polars casting works differently, need to lower instead to specific conversion ops. - # eg. We want "True" instead of "true" for bool to strin - return input.cast(_DTYPE_MAPPING[op.to_type], strict=not op.safe) - - @compile_op.register(string_ops.StrConcatOp) - def _(self, op: ops.ScalarOp, l_input: pl.Expr, r_input: pl.Expr) -> pl.Expr: - assert isinstance(op, string_ops.StrConcatOp) - return pl.concat_str(l_input, r_input) - - @compile_op.register(string_ops.StrContainsOp) - def _(self, op: ops.ScalarOp, input: pl.Expr) -> pl.Expr: - assert isinstance(op, string_ops.StrContainsOp) - return input.str.contains(pattern=op.pat, literal=True) - - @compile_op.register(string_ops.StrContainsRegexOp) - def _(self, op: ops.ScalarOp, input: pl.Expr) -> pl.Expr: - assert isinstance(op, string_ops.StrContainsRegexOp) - return input.str.contains(pattern=op.pat, literal=False) - - @compile_op.register(string_ops.UpperOp) - def _(self, op: ops.ScalarOp, input: pl.Expr) -> pl.Expr: - assert isinstance(op, string_ops.UpperOp) - return input.str.to_uppercase() - - @compile_op.register(string_ops.LowerOp) - def _(self, op: ops.ScalarOp, input: pl.Expr) -> pl.Expr: - assert isinstance(op, string_ops.LowerOp) - return input.str.to_lowercase() - - @compile_op.register(string_ops.ArrayLenOp) - def _(self, op: ops.ScalarOp, input: pl.Expr) -> pl.Expr: - assert isinstance(op, string_ops.ArrayLenOp) - return input.list.len() - - @compile_op.register(string_ops.StrLenOp) - def _(self, op: ops.ScalarOp, input: pl.Expr) -> pl.Expr: - assert isinstance(op, string_ops.StrLenOp) - return input.str.len_chars() - - @compile_op.register(string_ops.StartsWithOp) - def _(self, op: ops.ScalarOp, input: pl.Expr) -> pl.Expr: - assert isinstance(op, string_ops.StartsWithOp) - if len(op.pat) == 1: - return input.str.starts_with(op.pat[0]) - else: - return pl.any_horizontal( - *(input.str.starts_with(pat) for pat in op.pat) - ) - - @compile_op.register(string_ops.EndsWithOp) - def _(self, op: ops.ScalarOp, input: pl.Expr) -> pl.Expr: - assert isinstance(op, string_ops.EndsWithOp) - if len(op.pat) == 1: - return input.str.ends_with(op.pat[0]) - else: - return pl.any_horizontal(*(input.str.ends_with(pat) for pat in op.pat)) - - @compile_op.register(string_ops.CapitalizeOp) - def _(self, op: ops.ScalarOp, input: pl.Expr) -> pl.Expr: - assert isinstance(op, string_ops.CapitalizeOp) - return ( - input.str.slice(0, 1).str.to_uppercase() - + input.str.slice(1).str.to_lowercase() - ) - - @compile_op.register(string_ops.IsAlnumOp) - def _(self, op: ops.ScalarOp, input: pl.Expr) -> pl.Expr: - assert isinstance(op, string_ops.IsAlnumOp) - return input.str.contains(r"^[a-zA-Z0-9]+$") - - @compile_op.register(string_ops.IsAlphaOp) - def _(self, op: ops.ScalarOp, input: pl.Expr) -> pl.Expr: - assert isinstance(op, string_ops.IsAlphaOp) - return input.str.contains(r"^[a-zA-Z]+$") - - @compile_op.register(string_ops.IsDigitOp) - def _(self, op: ops.ScalarOp, input: pl.Expr) -> pl.Expr: - assert isinstance(op, string_ops.IsDigitOp) - return input.str.contains(r"^[0-9]+$") - - @compile_op.register(string_ops.IsSpaceOp) - def _(self, op: ops.ScalarOp, input: pl.Expr) -> pl.Expr: - assert isinstance(op, string_ops.IsSpaceOp) - return input.str.contains(r"^\s+$") - - @compile_op.register(string_ops.IsDecimalOp) - def _(self, op: ops.ScalarOp, input: pl.Expr) -> pl.Expr: - assert isinstance(op, string_ops.IsDecimalOp) - return input.str.contains(r"^[0-9]+$") - - @compile_op.register(string_ops.IsNumericOp) - def _(self, op: ops.ScalarOp, input: pl.Expr) -> pl.Expr: - assert isinstance(op, string_ops.IsNumericOp) - return input.str.contains(r"^[0-9]+$") - - @compile_op.register(string_ops.IsLowerOp) - def _(self, op: ops.ScalarOp, input: pl.Expr) -> pl.Expr: - assert isinstance(op, string_ops.IsLowerOp) - return input.str.contains(r"[a-z]") & ~input.str.contains(r"[A-Z]") - - @compile_op.register(string_ops.IsUpperOp) - def _(self, op: ops.ScalarOp, input: pl.Expr) -> pl.Expr: - assert isinstance(op, string_ops.IsUpperOp) - return input.str.contains(r"[A-Z]") & ~input.str.contains(r"[a-z]") - - @compile_op.register(freq_ops.FloorDtOp) - def _(self, op: ops.ScalarOp, input: pl.Expr) -> pl.Expr: - assert isinstance(op, freq_ops.FloorDtOp) - return input.dt.truncate(every=_FREQ_MAPPING[op.freq]) - - @compile_op.register(dt_ops.StrftimeOp) - def _(self, op: ops.ScalarOp, input: pl.Expr) -> pl.Expr: - assert isinstance(op, dt_ops.StrftimeOp) - return input.dt.strftime(op.date_format) - - @compile_op.register(date_ops.YearOp) - def _(self, op: ops.ScalarOp, input: pl.Expr) -> pl.Expr: - return input.dt.year() - - @compile_op.register(date_ops.QuarterOp) - def _(self, op: ops.ScalarOp, input: pl.Expr) -> pl.Expr: - return input.dt.quarter() - - @compile_op.register(date_ops.MonthOp) - def _(self, op: ops.ScalarOp, input: pl.Expr) -> pl.Expr: - return input.dt.month() - - @compile_op.register(date_ops.DayOfWeekOp) - def _(self, op: ops.ScalarOp, input: pl.Expr) -> pl.Expr: - return input.dt.weekday() - 1 - - @compile_op.register(date_ops.DayOp) - def _(self, op: ops.ScalarOp, input: pl.Expr) -> pl.Expr: - return input.dt.day() - - @compile_op.register(date_ops.IsoYearOp) - def _(self, op: ops.ScalarOp, input: pl.Expr) -> pl.Expr: - return input.dt.iso_year() - - @compile_op.register(date_ops.IsoWeekOp) - def _(self, op: ops.ScalarOp, input: pl.Expr) -> pl.Expr: - return input.dt.week() - - @compile_op.register(date_ops.IsoDayOp) - def _(self, op: ops.ScalarOp, input: pl.Expr) -> pl.Expr: - return input.dt.weekday() - - @compile_op.register(dt_ops.ParseDatetimeOp) - def _(self, op: ops.ScalarOp, input: pl.Expr) -> pl.Expr: - assert isinstance(op, dt_ops.ParseDatetimeOp) - return input.str.to_datetime( - time_unit="us", time_zone=None, ambiguous="earliest" - ) - - @compile_op.register(dt_ops.ParseTimestampOp) - def _(self, op: ops.ScalarOp, input: pl.Expr) -> pl.Expr: - assert isinstance(op, dt_ops.ParseTimestampOp) - return input.str.to_datetime( - time_unit="us", time_zone="UTC", ambiguous="earliest" - ) - - @compile_op.register(json_ops.JSONDecode) - def _(self, op: json_ops.JSONDecode, input: pl.Expr) -> pl.Expr: - assert isinstance(op, json_ops.JSONDecode) - return input.str.json_decode(_DTYPE_MAPPING[op.to_type]) - - @compile_op.register(json_ops.ToJSON) - def _(self, op: json_ops.ToJSON, input: pl.Expr) -> pl.Expr: - from_type = self._expr_types.get(id(input)) - if from_type in ( - bigframes.dtypes.STRING_DTYPE, - bigframes.dtypes.JSON_DTYPE, - ): - return input - else: - return input.cast(pl.String()) - - @compile_op.register(json_ops.ToJSONString) - def _(self, op: json_ops.ToJSONString, input: pl.Expr) -> pl.Expr: - from_type = self._expr_types.get(id(input)) - - def preprocess_binary( - expr: pl.Expr, dtype: bigframes.dtypes.ExpressionType - ) -> pl.Expr: - if dtype == bigframes.dtypes.BYTES_DTYPE: - return expr.bin.encode("base64") - if bigframes.dtypes.is_struct_like(dtype): - fields = bigframes.dtypes.get_struct_fields(dtype) - return pl.struct( - *[ - preprocess_binary( - expr.struct.field(name), field_type - ).alias(name) - for name, field_type in fields.items() - ] - ) - if bigframes.dtypes.is_array_like(dtype): - inner_type = bigframes.dtypes.get_array_inner_type(dtype) - return expr.list.eval(preprocess_binary(pl.element(), inner_type)) - return expr - - preprocessed = preprocess_binary(input, from_type) - - if bigframes.dtypes.is_struct_like(from_type): - result = preprocessed.struct.json_encode() - elif from_type == bigframes.dtypes.INT_DTYPE: - result = preprocessed.cast(pl.String) - elif from_type == bigframes.dtypes.BOOL_DTYPE: - result = ( - pl.when(preprocessed) - .then(pl.lit("true")) - .otherwise(pl.lit("false")) - ) - elif from_type == bigframes.dtypes.BYTES_DTYPE: - result = pl.lit('"') + preprocessed + pl.lit('"') - else: - wrapped = pl.struct(value=preprocessed).struct.json_encode() - result = wrapped.str.slice(9, wrapped.str.len_chars() - 10) - - return pl.when(input.is_null()).then(pl.lit("null")).otherwise(result) - - @compile_op.register(arr_ops.ToArrayOp) - def _(self, op: ops.ToArrayOp, *inputs: pl.Expr) -> pl.Expr: - return pl.concat_list(*inputs) - - @compile_op.register(arr_ops.ArrayReduceOp) - def _(self, op: ops.ArrayReduceOp, input: pl.Expr) -> pl.Expr: - # TODO: Unify this with general aggregation compilation? - if isinstance(op.aggregation, agg_ops.MinOp): - return input.list.min() - if isinstance(op.aggregation, agg_ops.MaxOp): - return input.list.max() - if isinstance(op.aggregation, agg_ops.SumOp): - return input.list.sum() - if isinstance(op.aggregation, agg_ops.MeanOp): - return input.list.mean() - if isinstance(op.aggregation, agg_ops.CountOp): - return input.list.len() - if isinstance(op.aggregation, agg_ops.StdOp): - return input.list.std() - if isinstance(op.aggregation, agg_ops.VarOp): - return input.list.var() - if isinstance(op.aggregation, agg_ops.AnyOp): - return input.list.any() - if isinstance(op.aggregation, agg_ops.AllOp): - return input.list.all() - else: - raise NotImplementedError( - f"Haven't implemented array aggregation: {op.aggregation}" - ) - - @compile_op.register(struct_ops.StructOp) - def _(self, op: struct_ops.StructOp, *inputs: pl.Expr) -> pl.Expr: - return pl.struct(**{col: inp for col, inp in zip(op.column_names, inputs)}) # type: ignore - - @compile_op.register(struct_ops.StructFieldOp) - def _(self, op: struct_ops.StructFieldOp, *inputs: pl.Expr) -> pl.Expr: - return inputs[0].struct[op.name_or_index] - - @compile_op.register(remote_function_ops.PythonUdfOp) - def _(self, op: ops.PythonUdfOp, *inputs: pl.Expr) -> pl.Expr: - from bigframes.functions import function_template - - code = op.function_def.code.to_callable() - if op.function_def.signature.is_row_processor: - - def handler(py_struct): - args = list(py_struct.values()) - series_arg = function_template.get_pd_series(args[0]) - return code(series_arg, *args[1:]) - else: - - def handler(py_struct): - return code(*(field for field in py_struct.values())) - - return pl.struct(*inputs).map_elements( - handler, - return_dtype=_bigframes_dtype_to_polars_dtype(op.output_type()), - skip_nulls=False, - ) - - @dataclasses.dataclass(frozen=True) - class PolarsAggregateCompiler: - scalar_compiler = PolarsExpressionCompiler() - - def get_args( - self, - agg: agg_expressions.Aggregation, - ) -> Sequence[pl.Expr]: - """Prepares arguments for aggregation by compiling them.""" - if isinstance(agg, agg_expressions.NullaryAggregation): - return [] - elif isinstance(agg, agg_expressions.UnaryAggregation): - arg = self.scalar_compiler.compile_expression(agg.arg) - return [arg] - elif isinstance(agg, agg_expressions.BinaryAggregation): - larg = self.scalar_compiler.compile_expression(agg.left) - rarg = self.scalar_compiler.compile_expression(agg.right) - return [larg, rarg] - - raise NotImplementedError( - f"Aggregation {agg} not yet supported in polars engine." - ) - - def compile_agg_expr(self, expr: agg_expressions.Aggregation): - if isinstance(expr, agg_expressions.NullaryAggregation): - inputs: Tuple = () - elif isinstance(expr, agg_expressions.UnaryAggregation): - assert isinstance(expr.arg, ex.DerefOp) - inputs = (expr.arg.id.sql,) - elif isinstance(expr, agg_expressions.BinaryAggregation): - assert isinstance(expr.left, ex.DerefOp) - assert isinstance(expr.right, ex.DerefOp) - inputs = ( - expr.left.id.sql, - expr.right.id.sql, - ) - else: - raise ValueError(f"Unexpected aggregation: {expr.op}") - - return self.compile_agg_op(expr.op, inputs) - - def compile_agg_op( - self, op: agg_ops.WindowOp, inputs: Sequence[str] = [] - ) -> pl.Expr: - if isinstance(op, agg_ops.ProductOp): - # TODO: Fix datatype inconsistency with float/int - return pl.col(*inputs).product() - if isinstance(op, agg_ops.SumOp): - return pl.sum(*inputs) - if isinstance(op, (agg_ops.SizeOp, agg_ops.SizeUnaryOp)): - return pl.len() - if isinstance(op, agg_ops.MeanOp): - return pl.mean(*inputs) - if isinstance(op, agg_ops.MedianOp): - return pl.median(*inputs) - if isinstance(op, agg_ops.AllOp): - return pl.col(inputs).cast(pl.Boolean).all() - if isinstance(op, agg_ops.AnyOp): - return pl.col(inputs).cast(pl.Boolean).any() - if isinstance(op, agg_ops.NuniqueOp): - return pl.col(*inputs).drop_nulls().n_unique() - if isinstance(op, agg_ops.MinOp): - return pl.min(*inputs) - if isinstance(op, agg_ops.MaxOp): - return pl.max(*inputs) - if isinstance(op, agg_ops.CountOp): - return pl.count(*inputs) - if isinstance(op, agg_ops.CorrOp): - return pl.corr( - pl.col(inputs[0]).fill_nan(None), pl.col(inputs[1]).fill_nan(None) - ) - if isinstance(op, agg_ops.CovOp): - return pl.cov( - pl.col(inputs[0]).fill_nan(None), pl.col(inputs[1]).fill_nan(None) - ) - if isinstance(op, agg_ops.StdOp): - return pl.std(inputs[0]) - if isinstance(op, agg_ops.VarOp): - # polars var doesnt' support decimal, so use std instead - return pl.std(inputs[0]).pow(2) - if isinstance(op, agg_ops.PopVarOp): - # polars var doesnt' support decimal, so use std instead - return pl.std(inputs[0], ddof=0).pow(2) - if isinstance(op, agg_ops.FirstNonNullOp): - return pl.col(*inputs).drop_nulls().first() - if isinstance(op, agg_ops.LastNonNullOp): - return pl.col(*inputs).drop_nulls().last() - if isinstance(op, agg_ops.FirstOp): - return pl.col(*inputs).first() - if isinstance(op, agg_ops.LastOp): - return pl.col(*inputs).last() - if isinstance(op, agg_ops.RowNumberOp): - # pl.row_index is not yet stable enough to use here, and only supports polars>=1.32 - return pl.int_range(pl.len(), dtype=pl.Int64) - if isinstance(op, agg_ops.ShiftOp): - return pl.col(*inputs).shift(op.periods) - if isinstance(op, agg_ops.DiffOp): - return pl.col(*inputs) - pl.col(*inputs).shift(op.periods) - if isinstance(op, agg_ops.AnyValueOp): - return pl.max( - *inputs - ) # probably something faster? maybe just get first item? - raise NotImplementedError( - f"Aggregate op {op} not yet supported in polars engine." - ) - - @dataclasses.dataclass(frozen=True) - class PolarsCompiler: - """ - Compiles ArrayValue to polars LazyFrame and executes. - - This feature is in development and is incomplete. - While most node types are supported, this has the following limitations: - 1. GBQ data sources not supported. - 2. Joins do not order rows correctly - 3. Incomplete scalar op support - 4. Incomplete aggregate op support - 5. Incomplete analytic op support - 6. Some complex windowing types not supported (eg. groupby + rolling) - 7. UDFs are not supported. - 8. Returned types may not be entirely consistent with BigQuery backend - 9. Some operations are not entirely lazy - sampling and somse windowing. - """ - - expr_compiler = PolarsExpressionCompiler() - agg_compiler = PolarsAggregateCompiler() - - def compile(self, plan: nodes.BigFrameNode) -> pl.LazyFrame: - if not polars_installed: - raise ValueError( - "Polars is not installed, cannot compile to polars engine." - ) - - # TODO: Create standard way to configure BFET -> BFET rewrites - # Polars has incomplete slice support in lazy mode - node = plan - node = bigframes.core.rewrite.column_pruning(node) - node = nodes.bottom_up(node, bigframes.core.rewrite.rewrite_slice) - node = bigframes.core.rewrite.pull_out_window_order(node) - node = bigframes.core.rewrite.schema_binding.bind_schema_to_tree(node) - node = lowering.lower_ops_to_polars(node) - return self.compile_node(node) - - @functools.singledispatchmethod - def compile_node(self, node: nodes.BigFrameNode) -> pl.LazyFrame: - """Defines transformation but isn't cached, always use compile_node instead""" - raise ValueError(f"Can't compile unrecognized node: {node}") - - @compile_node.register - def compile_readlocal(self, node: nodes.ReadLocalNode): - cols_to_read = { - scan_item.source_id: scan_item.id.sql - for scan_item in node.scan_list.items - } - lazy_frame = cast( - pl.DataFrame, pl.from_arrow(node.local_data_source.data) - ).lazy() - lazy_frame = lazy_frame.select(cols_to_read.keys()).rename(cols_to_read) - if node.offsets_col: - lazy_frame = lazy_frame.with_columns( - [pl.int_range(pl.len(), dtype=pl.Int64).alias(node.offsets_col.sql)] - ) - return lazy_frame - - @compile_node.register - def compile_filter(self, node: nodes.FilterNode): - return self.compile_node(node.child).filter( - self.expr_compiler.compile_expression(node.predicate) - ) - - @compile_node.register - def compile_orderby(self, node: nodes.OrderByNode): - frame = self.compile_node(node.child) - if len(node.by) == 0: - # pragma: no cover - return frame - return self._sort(frame, node.by) - - def _sort( - self, frame: pl.LazyFrame, by: Sequence[ordering.OrderingExpression] - ) -> pl.LazyFrame: - sorted = frame.sort( - [ - self.expr_compiler.compile_expression(by.scalar_expression) - for by in by - ], - descending=[not by.direction.is_ascending for by in by], - nulls_last=[by.na_last for by in by], - maintain_order=True, - ) - return sorted - - @compile_node.register - def compile_reversed(self, node: nodes.ReversedNode): - return self.compile_node(node.child).reverse() - - @compile_node.register - def compile_selection(self, node: nodes.SelectionNode): - return self.compile_node(node.child).select( - **{new.sql: orig.id.sql for orig, new in node.input_output_pairs} - ) - - @compile_node.register - def compile_projection(self, node: nodes.ProjectionNode): - new_cols = [] - for proj_expr, name in node.assignments: - bound_expr = ex.bind_schema_fields(proj_expr, node.child.field_by_id) - new_col = self.expr_compiler.compile_expression(bound_expr).alias( - name.sql - ) - if bound_expr.output_type is None: - new_col = new_col.cast( - _bigframes_dtype_to_polars_dtype(bigframes.dtypes.DEFAULT_DTYPE) - ) - new_cols.append(new_col) - return self.compile_node(node.child).with_columns(new_cols) - - @compile_node.register - def compile_offsets(self, node: nodes.PromoteOffsetsNode): - return self.compile_node(node.child).with_columns( - [pl.int_range(pl.len(), dtype=pl.Int64).alias(node.col_id.sql)] - ) - - @compile_node.register - def compile_join(self, node: nodes.JoinNode): - left = self.compile_node(node.left_child) - right = self.compile_node(node.right_child) - - left_on = [] - right_on = [] - for left_ex, right_ex in node.conditions: - left_ex, right_ex = lowering._coerce_comparables(left_ex, right_ex) - left_on.append(self.expr_compiler.compile_expression(left_ex)) - right_on.append(self.expr_compiler.compile_expression(right_ex)) - - if node.type == "right": - return self._ordered_join( - right, left, "left", right_on, left_on, node.joins_nulls - ).select([id.sql for id in node.ids]) - return self._ordered_join( - left, right, node.type, left_on, right_on, node.joins_nulls - ) - - @compile_node.register - def compile_isin(self, node: nodes.InNode): - left = self.compile_node(node.left_child) - right = self.compile_node(node.right_child).unique() - right = right.with_columns(pl.lit(True).alias(node.indicator_col.sql)) - - right_col = ex.ResolvedDerefOp.from_field(node.right_child.fields[0]) - left_ex, right_ex = lowering._coerce_comparables(node.left_col, right_col) - - left_pl_ex = self.expr_compiler.compile_expression(left_ex) - right_pl_ex = self.expr_compiler.compile_expression(right_ex) - - joined = left.join( - right, - how="left", - left_on=left_pl_ex, - right_on=right_pl_ex, - # Note: join_nulls renamed to nulls_equal for polars 1.24 - join_nulls=node.joins_nulls, # type: ignore - coalesce=False, - ) - passthrough = [pl.col(id) for id in left.columns] - indicator = pl.col(node.indicator_col.sql).fill_null(False) - return joined.select((*passthrough, indicator)) - - def _ordered_join( - self, - left_frame: pl.LazyFrame, - right_frame: pl.LazyFrame, - how: Literal["inner", "outer", "left", "cross"], - left_on: Sequence[pl.Expr], - right_on: Sequence[pl.Expr], - join_nulls: bool, - ): - if how == "right": - # seems to cause seg faults as of v1.30 for no apparent reason - raise ValueError("right join not supported") - left = left_frame.with_columns( - [ - pl.int_range(pl.len()).alias("_bf_join_l"), - ] - ) - right = right_frame.with_columns( - [ - pl.int_range(pl.len()).alias("_bf_join_r"), - ] - ) - if how != "cross": - joined = left.join( - right, - how=how, - left_on=left_on, - right_on=right_on, - # Note: join_nulls renamed to nulls_equal for polars 1.24 - join_nulls=join_nulls, # type: ignore - coalesce=False, - ) - else: - joined = left.join(right, how=how, coalesce=False) - - join_order = ( - ["_bf_join_l", "_bf_join_r"] - if how != "right" - else ["_bf_join_r", "_bf_join_l"] - ) - return joined.sort(join_order, nulls_last=True).drop( - ["_bf_join_l", "_bf_join_r"] - ) - - @compile_node.register - def compile_concat(self, node: nodes.ConcatNode): - child_frames = [self.compile_node(child) for child in node.child_nodes] - child_frames = [ - frame.rename( - {col: id.sql for col, id in zip(frame.columns, node.output_ids)} - ).cast( - { - field.id.sql: _bigframes_dtype_to_polars_dtype(field.dtype) - for field in node.fields - } - ) - for frame in child_frames - ] - df = pl.concat(child_frames) - return df - - @compile_node.register - def compile_agg(self, node: nodes.AggregateNode): - df = self.compile_node(node.child) - if node.dropna and len(node.by_column_ids) > 0: - df = df.filter( - [pl.col(ref.id.sql).is_not_null() for ref in node.by_column_ids] - ) - if node.order_by: - df = self._sort(df, node.order_by) - return self._aggregate(df, node.aggregations, node.by_column_ids) - - def _aggregate( - self, - df: pl.LazyFrame, - aggregations: Sequence[ - Tuple[agg_expressions.Aggregation, identifiers.ColumnId] - ], - grouping_keys: Tuple[ex.DerefOp, ...], - ) -> pl.LazyFrame: - # Need to materialize columns to broadcast constants - agg_inputs = [ - list( - map( - lambda x: x.alias(guid.generate_guid()), - self.agg_compiler.get_args(agg), - ) - ) - for agg, _ in aggregations - ] - - df_agg_inputs = df.with_columns(itertools.chain(*agg_inputs)) - - agg_exprs = [ - self.agg_compiler.compile_agg_op( - agg.op, list(map(lambda x: x.meta.output_name(), inputs)) - ).alias(id.sql) - for (agg, id), inputs in zip(aggregations, agg_inputs) - ] - - if len(grouping_keys) > 0: - group_exprs = [pl.col(ref.id.sql) for ref in grouping_keys] - grouped_df = df_agg_inputs.group_by(group_exprs) - return grouped_df.agg(agg_exprs).sort(group_exprs, nulls_last=True) - else: - return df_agg_inputs.select(agg_exprs) - - @compile_node.register - def compile_explode(self, node: nodes.ExplodeNode): - assert node.offsets_col is None - df = self.compile_node(node.child) - cols = [col.id.sql for col in node.column_ids] - return df.explode(cols) - - @compile_node.register - def compile_sample(self, node: nodes.RandomSampleNode): - df = self.compile_node(node.child) - # Sample is not available on lazyframe - return df.collect().sample(fraction=node.fraction).lazy() - - @compile_node.register - def compile_window(self, node: nodes.WindowOpNode): - df = self.compile_node(node.child) - - window = node.window_spec - # Should have been handled by reweriter - assert len(window.ordering) == 0 - if window.min_periods > 0: - raise NotImplementedError( - "min_period not yet supported for polars engine" - ) - - result = df - for cdef in node.agg_exprs: - assert isinstance(cdef.expression, agg_expressions.Aggregation) - if (window.bounds is None) or (window.is_unbounded): - # polars will automatically broadcast the aggregate to the matching input rows - agg_pl = self.agg_compiler.compile_agg_expr(cdef.expression) - if window.grouping_keys: - agg_pl = agg_pl.over( - self.expr_compiler.compile_expression(key) - for key in window.grouping_keys - ) - result = result.with_columns(agg_pl.alias(cdef.id.sql)) - else: # row-bounded window - window_result = self._calc_row_analytic_func( - result, cdef.expression, node.window_spec, cdef.id.sql - ) - result = pl.concat([result, window_result], how="horizontal") - return result - - def _calc_row_analytic_func( - self, - frame: pl.LazyFrame, - agg_expr: agg_expressions.Aggregation, - window: window_spec.WindowSpec, - name: str, - ) -> pl.LazyFrame: - if not isinstance(window.bounds, window_spec.RowsWindowBounds): - raise NotImplementedError("Only row bounds supported by polars engine") - groupby = None - if len(window.grouping_keys) > 0: - groupby = [ - self.expr_compiler.compile_expression(ref) - for ref in window.grouping_keys - ] - - # Polars API semi-bounded, and any grouped rolling window challenging - # https://github.com/pola-rs/polars/issues/4799 - # https://github.com/pola-rs/polars/issues/8976 - pl_agg_expr = self.agg_compiler.compile_agg_expr(agg_expr).alias(name) - index_col_name = "_bf_pl_engine_offsets" - indexed_df = frame.with_row_index(index_col_name) - # https://docs.pola.rs/api/python/stable/reference/dataframe/api/polars.DataFrame.rolling.html - period_n, offset_n = _get_period_and_offset(window.bounds) - return ( - indexed_df.rolling( - index_column=index_col_name, - period=f"{period_n}i", - offset=f"{offset_n}i" if (offset_n is not None) else None, - group_by=groupby, - ) - .agg(pl_agg_expr) - .select(name) - ) - - -def _get_period_and_offset( - bounds: window_spec.RowsWindowBounds, -) -> tuple[int, Optional[int]]: - # fixed size window - if (bounds.start is not None) and (bounds.end is not None): - return ((bounds.end - bounds.start + 1), bounds.start - 1) - - LARGE_N = 1000000000 - if bounds.start is not None: - return (LARGE_N, bounds.start - 1) - if bounds.end is not None: - return (LARGE_N, None) - raise ValueError("Not a bounded window") diff --git a/bigframes/core/compile/polars/lowering.py b/bigframes/core/compile/polars/lowering.py deleted file mode 100644 index 5b3d9154b73..00000000000 --- a/bigframes/core/compile/polars/lowering.py +++ /dev/null @@ -1,504 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import dataclasses -from typing import cast - -import numpy as np -import pandas as pd - -import bigframes.operations as ops -from bigframes import dtypes -from bigframes.core import bigframe_node, expression -from bigframes.core.rewrite import op_lowering -from bigframes.operations import ( - comparison_ops, - datetime_ops, - generic_ops, - numeric_ops, - string_ops, -) - -# TODO: Would be more precise to actually have separate op set for polars ops (where they diverge from the original ops) - - -@dataclasses.dataclass -class CoerceArgsRule(op_lowering.OpLoweringRule): - op_type: type[ops.BinaryOp] - - @property - def op(self) -> type[ops.ScalarOp]: - return self.op_type - - def lower(self, expr: expression.OpExpression) -> expression.Expression: - assert isinstance(expr.op, self.op_type) - larg, rarg = _coerce_comparables(expr.children[0], expr.children[1]) - return expr.op.as_expr(larg, rarg) - - -class LowerAddRule(op_lowering.OpLoweringRule): - @property - def op(self) -> type[ops.ScalarOp]: - return numeric_ops.AddOp - - def lower(self, expr: expression.OpExpression) -> expression.Expression: - assert isinstance(expr.op, numeric_ops.AddOp) - larg, rarg = expr.children[0], expr.children[1] - - if ( - larg.output_type == dtypes.BOOL_DTYPE - and rarg.output_type == dtypes.BOOL_DTYPE - ): - int_result = expr.op.as_expr( - ops.AsTypeOp(to_type=dtypes.INT_DTYPE).as_expr(larg), - ops.AsTypeOp(to_type=dtypes.INT_DTYPE).as_expr(rarg), - ) - return ops.AsTypeOp(to_type=dtypes.BOOL_DTYPE).as_expr(int_result) - - if dtypes.is_string_like(larg.output_type) and dtypes.is_string_like( - rarg.output_type - ): - return ops.strconcat_op.as_expr(larg, rarg) - - if larg.output_type == dtypes.BOOL_DTYPE: - larg = ops.AsTypeOp(to_type=dtypes.INT_DTYPE).as_expr(larg) - if rarg.output_type == dtypes.BOOL_DTYPE: - rarg = ops.AsTypeOp(to_type=dtypes.INT_DTYPE).as_expr(rarg) - - if ( - larg.output_type == dtypes.DATE_DTYPE - and rarg.output_type == dtypes.TIMEDELTA_DTYPE - ): - larg = ops.AsTypeOp(to_type=dtypes.DATETIME_DTYPE).as_expr(larg) - - if ( - larg.output_type == dtypes.TIMEDELTA_DTYPE - and rarg.output_type == dtypes.DATE_DTYPE - ): - rarg = ops.AsTypeOp(to_type=dtypes.DATETIME_DTYPE).as_expr(rarg) - - return expr.op.as_expr(larg, rarg) - - -class LowerSubRule(op_lowering.OpLoweringRule): - @property - def op(self) -> type[ops.ScalarOp]: - return numeric_ops.SubOp - - def lower(self, expr: expression.OpExpression) -> expression.Expression: - assert isinstance(expr.op, numeric_ops.SubOp) - larg, rarg = expr.children[0], expr.children[1] - - if ( - larg.output_type == dtypes.BOOL_DTYPE - and rarg.output_type == dtypes.BOOL_DTYPE - ): - int_result = expr.op.as_expr( - ops.AsTypeOp(to_type=dtypes.INT_DTYPE).as_expr(larg), - ops.AsTypeOp(to_type=dtypes.INT_DTYPE).as_expr(rarg), - ) - return ops.AsTypeOp(to_type=dtypes.BOOL_DTYPE).as_expr(int_result) - - if larg.output_type == dtypes.BOOL_DTYPE: - larg = ops.AsTypeOp(to_type=dtypes.INT_DTYPE).as_expr(larg) - if rarg.output_type == dtypes.BOOL_DTYPE: - rarg = ops.AsTypeOp(to_type=dtypes.INT_DTYPE).as_expr(rarg) - - if ( - larg.output_type == dtypes.DATE_DTYPE - and rarg.output_type == dtypes.TIMEDELTA_DTYPE - ): - larg = ops.AsTypeOp(to_type=dtypes.DATETIME_DTYPE).as_expr(larg) - - return expr.op.as_expr(larg, rarg) - - -@dataclasses.dataclass -class LowerMulRule(op_lowering.OpLoweringRule): - @property - def op(self) -> type[ops.ScalarOp]: - return numeric_ops.MulOp - - def lower(self, expr: expression.OpExpression) -> expression.Expression: - assert isinstance(expr.op, numeric_ops.MulOp) - larg, rarg = expr.children[0], expr.children[1] - - if ( - larg.output_type == dtypes.BOOL_DTYPE - and rarg.output_type == dtypes.BOOL_DTYPE - ): - int_result = expr.op.as_expr( - ops.AsTypeOp(to_type=dtypes.INT_DTYPE).as_expr(larg), - ops.AsTypeOp(to_type=dtypes.INT_DTYPE).as_expr(rarg), - ) - return ops.AsTypeOp(to_type=dtypes.BOOL_DTYPE).as_expr(int_result) - - if ( - larg.output_type == dtypes.BOOL_DTYPE - and rarg.output_type != dtypes.BOOL_DTYPE - ): - larg = ops.AsTypeOp(to_type=dtypes.INT_DTYPE).as_expr(larg) - if ( - rarg.output_type == dtypes.BOOL_DTYPE - and larg.output_type != dtypes.BOOL_DTYPE - ): - rarg = ops.AsTypeOp(to_type=dtypes.INT_DTYPE).as_expr(rarg) - - return expr.op.as_expr(larg, rarg) - - -class LowerDivRule(op_lowering.OpLoweringRule): - @property - def op(self) -> type[ops.ScalarOp]: - return numeric_ops.DivOp - - def lower(self, expr: expression.OpExpression) -> expression.Expression: - assert isinstance(expr.op, numeric_ops.DivOp) - - dividend = expr.children[0] - divisor = expr.children[1] - - if dividend.output_type == dtypes.TIMEDELTA_DTYPE and dtypes.is_numeric( - divisor.output_type - ): - # exact same as floordiv impl for timedelta - numeric_result = ops.div_op.as_expr( - ops.AsTypeOp(to_type=dtypes.INT_DTYPE).as_expr(dividend), divisor - ) - return _numeric_to_timedelta(numeric_result) - if ( - dividend.output_type == dtypes.BOOL_DTYPE - and divisor.output_type == dtypes.BOOL_DTYPE - ): - int_result = expr.op.as_expr( - ops.AsTypeOp(to_type=dtypes.INT_DTYPE).as_expr(dividend), - ops.AsTypeOp(to_type=dtypes.INT_DTYPE).as_expr(divisor), - ) - return ops.AsTypeOp(to_type=dtypes.BOOL_DTYPE).as_expr(int_result) - - # polars divide doesn't like bools, convert to int always - # convert numerics to float always - if dividend.output_type == dtypes.BOOL_DTYPE: - dividend = ops.AsTypeOp(to_type=dtypes.INT_DTYPE).as_expr(dividend) - elif dividend.output_type in (dtypes.BIGNUMERIC_DTYPE, dtypes.NUMERIC_DTYPE): - dividend = ops.AsTypeOp(to_type=dtypes.FLOAT_DTYPE).as_expr(dividend) - if divisor.output_type == dtypes.BOOL_DTYPE: - divisor = ops.AsTypeOp(to_type=dtypes.INT_DTYPE).as_expr(divisor) - - return numeric_ops.div_op.as_expr(dividend, divisor) - - -class LowerFloorDivRule(op_lowering.OpLoweringRule): - @property - def op(self) -> type[ops.ScalarOp]: - return numeric_ops.FloorDivOp - - def lower(self, expr: expression.OpExpression) -> expression.Expression: - assert isinstance(expr.op, numeric_ops.FloorDivOp) - - dividend = expr.children[0] - divisor = expr.children[1] - - if ( - dividend.output_type == dtypes.TIMEDELTA_DTYPE - and divisor.output_type == dtypes.TIMEDELTA_DTYPE - ): - int_result = expr.op.as_expr( - ops.AsTypeOp(to_type=dtypes.INT_DTYPE).as_expr(dividend), - ops.AsTypeOp(to_type=dtypes.INT_DTYPE).as_expr(divisor), - ) - return int_result - if dividend.output_type == dtypes.TIMEDELTA_DTYPE and dtypes.is_numeric( - divisor.output_type - ): - # this is pretty fragile as zero will break it, and must fit back into int - numeric_result = ops.div_op.as_expr( - ops.AsTypeOp(to_type=dtypes.INT_DTYPE).as_expr(dividend), divisor - ) - return _numeric_to_timedelta(numeric_result) - - if dividend.output_type == dtypes.BOOL_DTYPE: - dividend = ops.AsTypeOp(to_type=dtypes.INT_DTYPE).as_expr(dividend) - if divisor.output_type == dtypes.BOOL_DTYPE: - divisor = ops.AsTypeOp(to_type=dtypes.INT_DTYPE).as_expr(divisor) - - if expr.output_type != dtypes.FLOAT_DTYPE: - # need to guard against zero divisor - # multiply dividend in this case to propagate nulls - return ops.where_op.as_expr( - ops.mul_op.as_expr(dividend, expression.const(0)), - ops.eq_op.as_expr(divisor, expression.const(0)), - numeric_ops.floordiv_op.as_expr(dividend, divisor), - ) - else: - return expr.op.as_expr(dividend, divisor) - - -class LowerModRule(op_lowering.OpLoweringRule): - @property - def op(self) -> type[ops.ScalarOp]: - return numeric_ops.ModOp - - def lower(self, expr: expression.OpExpression) -> expression.Expression: - og_expr = expr - assert isinstance(expr.op, numeric_ops.ModOp) - larg, rarg = expr.children[0], expr.children[1] - - if ( - larg.output_type == dtypes.TIMEDELTA_DTYPE - and rarg.output_type == dtypes.TIMEDELTA_DTYPE - ): - larg_int = ops.AsTypeOp(to_type=dtypes.INT_DTYPE).as_expr(larg) - rarg_int = ops.AsTypeOp(to_type=dtypes.INT_DTYPE).as_expr(rarg) - int_result = expr.op.as_expr(larg_int, rarg_int) - w_zero_handling = ops.where_op.as_expr( - int_result, - ops.ne_op.as_expr(rarg_int, expression.const(0)), - ops.mul_op.as_expr(rarg_int, expression.const(0)), - ) - return ops.AsTypeOp(to_type=dtypes.TIMEDELTA_DTYPE).as_expr(w_zero_handling) - - if larg.output_type == dtypes.BOOL_DTYPE: - larg = ops.AsTypeOp(to_type=dtypes.INT_DTYPE).as_expr(larg) - if rarg.output_type == dtypes.BOOL_DTYPE: - rarg = ops.AsTypeOp(to_type=dtypes.INT_DTYPE).as_expr(rarg) - - wo_bools = expr.op.as_expr(larg, rarg) - - if og_expr.output_type == dtypes.INT_DTYPE: - return ops.where_op.as_expr( - wo_bools, - ops.ne_op.as_expr(rarg, expression.const(0)), - ops.mul_op.as_expr(rarg, expression.const(0)), - ) - return wo_bools - - -class LowerAsTypeRule(op_lowering.OpLoweringRule): - @property - def op(self) -> type[ops.ScalarOp]: - return ops.AsTypeOp - - def lower(self, expr: expression.OpExpression) -> expression.Expression: - assert isinstance(expr.op, ops.AsTypeOp) - return _lower_cast(expr.op, expr.inputs[0]) - - -def invert_bytes(byte_string): - inverted_bytes = ~np.frombuffer(byte_string, dtype=np.uint8) - return inverted_bytes.tobytes() - - -class LowerInvertOp(op_lowering.OpLoweringRule): - @property - def op(self) -> type[ops.ScalarOp]: - return generic_ops.InvertOp - - def lower(self, expr: expression.OpExpression) -> expression.Expression: - assert isinstance(expr.op, generic_ops.InvertOp) - arg = expr.children[0] - if arg.output_type == dtypes.BYTES_DTYPE: - return generic_ops.PyUdfOp(invert_bytes, dtypes.BYTES_DTYPE).as_expr( - expr.inputs[0] - ) - return expr - - -class LowerCeilOp(op_lowering.OpLoweringRule): - @property - def op(self) -> type[ops.ScalarOp]: - return numeric_ops.CeilOp - - def lower(self, expr: expression.OpExpression) -> expression.Expression: - assert isinstance(expr.op, numeric_ops.CeilOp) - arg = expr.children[0] - if arg.output_type in (dtypes.INT_DTYPE, dtypes.BOOL_DTYPE): - return expr.op.as_expr(ops.AsTypeOp(dtypes.FLOAT_DTYPE).as_expr(arg)) - return expr - - -class LowerFloorOp(op_lowering.OpLoweringRule): - @property - def op(self) -> type[ops.ScalarOp]: - return numeric_ops.FloorOp - - def lower(self, expr: expression.OpExpression) -> expression.Expression: - assert isinstance(expr.op, numeric_ops.FloorOp) - arg = expr.children[0] - if arg.output_type in (dtypes.INT_DTYPE, dtypes.BOOL_DTYPE): - return expr.op.as_expr(ops.AsTypeOp(dtypes.FLOAT_DTYPE).as_expr(arg)) - return expr - - -class LowerIsinOp(op_lowering.OpLoweringRule): - @property - def op(self) -> type[ops.ScalarOp]: - return generic_ops.IsInOp - - def lower(self, expr: expression.OpExpression) -> expression.Expression: - assert isinstance(expr.op, generic_ops.IsInOp) - arg = expr.children[0] - new_values = [] - match_nulls = False - for val in expr.op.values: - # coercible, non-coercible - # float NaN/inf should be treated as distinct from 'true' null values - if cast(bool, pd.isna(val)) and not isinstance(val, float): - if expr.op.match_nulls: - match_nulls = True - elif dtypes.is_compatible(val, arg.output_type): - new_values.append(val) - else: - pass - - new_isin = ops.IsInOp(tuple(new_values), match_nulls=False).as_expr(arg) - if match_nulls: - return ops.coalesce_op.as_expr(new_isin, expression.const(True)) - else: - # polars propagates nulls, so need to coalesce to false - return ops.coalesce_op.as_expr(new_isin, expression.const(False)) - - -class LowerLenOp(op_lowering.OpLoweringRule): - @property - def op(self) -> type[ops.ScalarOp]: - return string_ops.LenOp - - def lower(self, expr: expression.OpExpression) -> expression.Expression: - assert isinstance(expr.op, string_ops.LenOp) - arg = expr.children[0] - - if dtypes.is_string_like(arg.output_type): - return string_ops.StrLenOp().as_expr(arg) - elif dtypes.is_array_like(arg.output_type): - return string_ops.ArrayLenOp().as_expr(arg) - else: - raise ValueError(f"Unexpected type: {arg.output_type}") - - -def _coerce_comparables( - expr1: expression.Expression, - expr2: expression.Expression, - *, - bools_only: bool = False, -): - if bools_only: - if ( - expr1.output_type != dtypes.BOOL_DTYPE - and expr2.output_type != dtypes.BOOL_DTYPE - ): - return expr1, expr2 - - target_type = dtypes.coerce_to_common(expr1.output_type, expr2.output_type) - if expr1.output_type != target_type: - expr1 = _lower_cast(ops.AsTypeOp(target_type), expr1) - if expr2.output_type != target_type: - expr2 = _lower_cast(ops.AsTypeOp(target_type), expr2) - return expr1, expr2 - - -def _lower_cast(cast_op: ops.AsTypeOp, arg: expression.Expression): - if arg.output_type == cast_op.to_type: - return arg - if ( - arg.output_type == dtypes.STRING_DTYPE - and cast_op.to_type == dtypes.DATETIME_DTYPE - ): - return datetime_ops.ParseDatetimeOp().as_expr(arg) - if ( - arg.output_type == dtypes.STRING_DTYPE - and cast_op.to_type == dtypes.TIMESTAMP_DTYPE - ): - return datetime_ops.ParseTimestampOp().as_expr(arg) - # date -> string casting - if ( - arg.output_type == dtypes.DATETIME_DTYPE - and cast_op.to_type == dtypes.STRING_DTYPE - ): - return datetime_ops.StrftimeOp("%Y-%m-%d %H:%M:%S").as_expr(arg) - if arg.output_type == dtypes.TIME_DTYPE and cast_op.to_type == dtypes.STRING_DTYPE: - return datetime_ops.StrftimeOp("%H:%M:%S.%6f").as_expr(arg) - if ( - arg.output_type == dtypes.TIMESTAMP_DTYPE - and cast_op.to_type == dtypes.STRING_DTYPE - ): - return datetime_ops.StrftimeOp("%Y-%m-%d %H:%M:%S%.6f%:::z").as_expr(arg) - if arg.output_type == dtypes.BOOL_DTYPE and cast_op.to_type == dtypes.STRING_DTYPE: - # bool -> decimal needs two-step cast - new_arg = ops.AsTypeOp(to_type=dtypes.INT_DTYPE).as_expr(arg) - is_true_cond = ops.eq_op.as_expr(arg, expression.const(True)) - is_false_cond = ops.eq_op.as_expr(arg, expression.const(False)) - return ops.CaseWhenOp().as_expr( - is_true_cond, - expression.const("True"), - is_false_cond, - expression.const("False"), - ) - if arg.output_type == dtypes.BOOL_DTYPE and dtypes.is_numeric(cast_op.to_type): - # bool -> decimal needs two-step cast - new_arg = ops.AsTypeOp(to_type=dtypes.INT_DTYPE).as_expr(arg) - return cast_op.as_expr(new_arg) - if arg.output_type == dtypes.TIME_DTYPE and dtypes.is_numeric(cast_op.to_type): - # polars cast gives nanoseconds, so convert to microseconds - return numeric_ops.floordiv_op.as_expr( - cast_op.as_expr(arg), expression.const(1000) - ) - if dtypes.is_numeric(arg.output_type) and cast_op.to_type == dtypes.TIME_DTYPE: - return cast_op.as_expr(ops.mul_op.as_expr(expression.const(1000), arg)) - return cast_op.as_expr(arg) - - -LOWER_COMPARISONS = tuple( - CoerceArgsRule(op) - for op in ( - comparison_ops.EqOp, - comparison_ops.EqNullsMatchOp, - comparison_ops.NeOp, - comparison_ops.LtOp, - comparison_ops.GtOp, - comparison_ops.LeOp, - comparison_ops.GeOp, - ) -) - -POLARS_LOWERING_RULES = ( - *LOWER_COMPARISONS, - LowerAddRule(), - LowerSubRule(), - LowerMulRule(), - LowerDivRule(), - LowerFloorDivRule(), - LowerModRule(), - LowerAsTypeRule(), - LowerInvertOp(), - LowerIsinOp(), - LowerLenOp(), - LowerCeilOp(), - LowerFloorOp(), -) - - -def lower_ops_to_polars(root: bigframe_node.BigFrameNode) -> bigframe_node.BigFrameNode: - return op_lowering.lower_ops(root, rules=POLARS_LOWERING_RULES) - - -def _numeric_to_timedelta(expr: expression.Expression) -> expression.Expression: - """rounding logic used for emulating timedelta ops""" - rounded_value = ops.where_op.as_expr( - ops.floor_op.as_expr(expr), - ops.gt_op.as_expr(expr, expression.const(0)), - ops.ceil_op.as_expr(expr), - ) - int_value = ops.AsTypeOp(to_type=dtypes.INT_DTYPE).as_expr(rounded_value) - return ops.AsTypeOp(to_type=dtypes.TIMEDELTA_DTYPE).as_expr(int_value) diff --git a/bigframes/core/compile/polars/operations/__init__.py b/bigframes/core/compile/polars/operations/__init__.py deleted file mode 100644 index 26444dcb670..00000000000 --- a/bigframes/core/compile/polars/operations/__init__.py +++ /dev/null @@ -1,21 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -"""Operation implementations for the Polars LazyFrame compiler. - -This directory structure should reflect the same layout as the -`bigframes/operations` directory where the operations are defined. - -Prefer small groups of ops per file to keep file sizes manageable for text editors and LLMs. -""" diff --git a/bigframes/core/compile/polars/operations/array_ops.py b/bigframes/core/compile/polars/operations/array_ops.py deleted file mode 100644 index 1f2960471db..00000000000 --- a/bigframes/core/compile/polars/operations/array_ops.py +++ /dev/null @@ -1,62 +0,0 @@ -# Copyright 2026 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. - -""" -BigFrames -> Polars compilation for the operations in bigframes.operations.array_ops. -""" - -from __future__ import annotations - -from typing import TYPE_CHECKING - -import bigframes.core.compile.polars.compiler as polars_compiler -import bigframes.dtypes as dtypes -from bigframes.operations import generic_ops - -if TYPE_CHECKING: - import polars as pl - - -@polars_compiler.register_op(generic_ops.GetItemOp) -def getitem_op_impl( - compiler: polars_compiler.PolarsExpressionCompiler, - op: generic_ops.GetItemOp, # type: ignore - input: pl.Expr, -) -> pl.Expr: - input_type = compiler._expr_types.get(id(input)) - if input_type is not None and dtypes.is_struct_like(input_type): - if isinstance(op.key, str): - return input.struct.field(op.key) - else: - raise NotImplementedError( - "Referencing a struct field by number not implemented in polars compiler." - ) - elif input_type is not None and dtypes.is_string_like(input_type): - return input.str.slice(op.key, 1) - else: - return input.list.get(op.key) - - -@polars_compiler.register_op(generic_ops.DynamicGetItemOp) -def dynamic_getitem_op_impl( - compiler: polars_compiler.PolarsExpressionCompiler, - op: generic_ops.DynamicGetItemOp, # type: ignore - left: pl.Expr, - right: pl.Expr, -) -> pl.Expr: - left_type = compiler._expr_types.get(id(left)) - if left_type is not None and dtypes.is_string_like(left_type): - return left.str.slice(right, 1) - else: - return left.list.get(right) diff --git a/bigframes/core/compile/polars/operations/generic_ops.py b/bigframes/core/compile/polars/operations/generic_ops.py deleted file mode 100644 index 4051fa49953..00000000000 --- a/bigframes/core/compile/polars/operations/generic_ops.py +++ /dev/null @@ -1,58 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -""" -BigFrames -> Polars compilation for the operations in bigframes.operations.generic_ops. - -Please keep implementations in sequential order by op name. -""" - -from __future__ import annotations - -from typing import TYPE_CHECKING - -import bigframes.core.compile.polars.compiler as polars_compiler -from bigframes.operations import generic_ops - -if TYPE_CHECKING: - import polars as pl - - -@polars_compiler.register_op(generic_ops.NotNullOp) -def notnull_op_impl( - compiler: polars_compiler.PolarsExpressionCompiler, - op: generic_ops.NotNullOp, # type: ignore - input: pl.Expr, -) -> pl.Expr: - return input.is_not_null() - - -@polars_compiler.register_op(generic_ops.IsNullOp) -def isnull_op_impl( - compiler: polars_compiler.PolarsExpressionCompiler, - op: generic_ops.IsNullOp, # type: ignore - input: pl.Expr, -) -> pl.Expr: - return input.is_null() - - -@polars_compiler.register_op(generic_ops.PyUdfOp) -def py_udf_op_impl( - compiler: polars_compiler.PolarsExpressionCompiler, - op: generic_ops.PyUdfOp, # type: ignore - input: pl.Expr, -) -> pl.Expr: - return input.map_elements( - op.fn, return_dtype=polars_compiler._DTYPE_MAPPING[op._output_type] - ) diff --git a/bigframes/core/compile/polars/operations/numeric_ops.py b/bigframes/core/compile/polars/operations/numeric_ops.py deleted file mode 100644 index 440415014e9..00000000000 --- a/bigframes/core/compile/polars/operations/numeric_ops.py +++ /dev/null @@ -1,172 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -""" -BigFrames -> Polars compilation for the operations in bigframes.operations.numeric_ops. - -Please keep implementations in sequential order by op name. -""" - -from __future__ import annotations - -from typing import TYPE_CHECKING - -import bigframes.core.compile.polars.compiler as polars_compiler -from bigframes.operations import numeric_ops - -if TYPE_CHECKING: - import polars as pl - - -@polars_compiler.register_op(numeric_ops.LnOp) -def ln_op_impl( - compiler: polars_compiler.PolarsExpressionCompiler, - op: numeric_ops.LnOp, # type: ignore - input: pl.Expr, -) -> pl.Expr: - import polars as pl - - return pl.when(input <= 0).then(float("nan")).otherwise(input.log()) - - -@polars_compiler.register_op(numeric_ops.Log10Op) -def log10_op_impl( - compiler: polars_compiler.PolarsExpressionCompiler, - op: numeric_ops.Log10Op, # type: ignore - input: pl.Expr, -) -> pl.Expr: - import polars as pl - - return pl.when(input <= 0).then(float("nan")).otherwise(input.log(base=10)) - - -@polars_compiler.register_op(numeric_ops.Log1pOp) -def log1p_op_impl( - compiler: polars_compiler.PolarsExpressionCompiler, - op: numeric_ops.Log1pOp, # type: ignore - input: pl.Expr, -) -> pl.Expr: - import polars as pl - - return pl.when(input <= -1).then(float("nan")).otherwise((input + 1).log()) - - -@polars_compiler.register_op(numeric_ops.SinOp) -def sin_op_impl( - compiler: polars_compiler.PolarsExpressionCompiler, - op: numeric_ops.SinOp, # type: ignore - input: pl.Expr, -) -> pl.Expr: - return input.sin() - - -@polars_compiler.register_op(numeric_ops.CosOp) -def cos_op_impl( - compiler: polars_compiler.PolarsExpressionCompiler, - op: numeric_ops.CosOp, # type: ignore - input: pl.Expr, -) -> pl.Expr: - return input.cos() - - -@polars_compiler.register_op(numeric_ops.TanOp) -def tan_op_impl( - compiler: polars_compiler.PolarsExpressionCompiler, - op: numeric_ops.SinOp, # type: ignore - input: pl.Expr, -) -> pl.Expr: - return input.tan() - - -@polars_compiler.register_op(numeric_ops.SinhOp) -def sinh_op_impl( - compiler: polars_compiler.PolarsExpressionCompiler, - op: numeric_ops.SinOp, # type: ignore - input: pl.Expr, -) -> pl.Expr: - return input.sinh() - - -@polars_compiler.register_op(numeric_ops.CoshOp) -def cosh_op_impl( - compiler: polars_compiler.PolarsExpressionCompiler, - op: numeric_ops.CosOp, # type: ignore - input: pl.Expr, -) -> pl.Expr: - return input.cosh() - - -@polars_compiler.register_op(numeric_ops.TanhOp) -def tanh_op_impl( - compiler: polars_compiler.PolarsExpressionCompiler, - op: numeric_ops.SinOp, # type: ignore - input: pl.Expr, -) -> pl.Expr: - return input.tanh() - - -@polars_compiler.register_op(numeric_ops.ArcsinOp) -def asin_op_impl( - compiler: polars_compiler.PolarsExpressionCompiler, - op: numeric_ops.ArcsinOp, # type: ignore - input: pl.Expr, -) -> pl.Expr: - return input.arcsin() - - -@polars_compiler.register_op(numeric_ops.ArccosOp) -def acos_op_impl( - compiler: polars_compiler.PolarsExpressionCompiler, - op: numeric_ops.ArccosOp, # type: ignore - input: pl.Expr, -) -> pl.Expr: - return input.arccos() - - -@polars_compiler.register_op(numeric_ops.ArctanOp) -def atan_op_impl( - compiler: polars_compiler.PolarsExpressionCompiler, - op: numeric_ops.ArctanOp, # type: ignore - input: pl.Expr, -) -> pl.Expr: - return input.arctan() - - -@polars_compiler.register_op(numeric_ops.SqrtOp) -def sqrt_op_impl( - compiler: polars_compiler.PolarsExpressionCompiler, - op: numeric_ops.SqrtOp, # type: ignore - input: pl.Expr, -) -> pl.Expr: - import polars as pl - - return pl.when(input < 0).then(float("nan")).otherwise(input.sqrt()) - - -@polars_compiler.register_op(numeric_ops.IsNanOp) -def is_nan_op_impl( - compiler: polars_compiler.PolarsExpressionCompiler, - op: numeric_ops.IsNanOp, # type: ignore - input: pl.Expr, -) -> pl.Expr: - return input.is_nan() - - -@polars_compiler.register_op(numeric_ops.IsFiniteOp) -def is_finite_op_impl( - compiler: polars_compiler.PolarsExpressionCompiler, - op: numeric_ops.IsFiniteOp, # type: ignore - input: pl.Expr, -) -> pl.Expr: - return input.is_finite() diff --git a/bigframes/core/compile/polars/operations/struct_ops.py b/bigframes/core/compile/polars/operations/struct_ops.py deleted file mode 100644 index 1573d4aa9b4..00000000000 --- a/bigframes/core/compile/polars/operations/struct_ops.py +++ /dev/null @@ -1,48 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -""" -BigFrames -> Polars compilation for the operations in bigframes.operations.generic_ops. - -Please keep implementations in sequential order by op name. -""" - -from __future__ import annotations - -from typing import TYPE_CHECKING - -import bigframes_vendored.constants - -import bigframes.core.compile.polars.compiler as polars_compiler -from bigframes.operations import struct_ops - -if TYPE_CHECKING: - import polars as pl - - -@polars_compiler.register_op(struct_ops.StructFieldOp) -def struct_field_op_impl( - compiler: polars_compiler.PolarsExpressionCompiler, - op: struct_ops.StructFieldOp, # type: ignore - input: pl.Expr, -) -> pl.Expr: - if isinstance(op.name_or_index, str): - name = op.name_or_index - else: - raise NotImplementedError( - "Referencing a struct field by number not implemented in polars compiler. " - f"{bigframes_vendored.constants.FEEDBACK_LINK}" - ) - - return input.struct.field(name) diff --git a/bigframes/core/compile/row_identity.py b/bigframes/core/compile/row_identity.py new file mode 100644 index 00000000000..2e9bc0527ca --- /dev/null +++ b/bigframes/core/compile/row_identity.py @@ -0,0 +1,200 @@ +# Copyright 2023 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. + +"""Helpers to join ArrayValue objects.""" + +from __future__ import annotations + +import functools +import typing + +import ibis +import ibis.expr.types as ibis_types + +import bigframes.constants as constants +import bigframes.core.compile as compiled +import bigframes.core.joins.name_resolution as naming +import bigframes.core.ordering as orderings + +SUPPORTED_ROW_IDENTITY_HOW = {"outer", "left", "inner"} + + +def join_by_row_identity( + left: compiled.CompiledArrayValue, right: compiled.CompiledArrayValue, *, how: str +) -> compiled.CompiledArrayValue: + """Compute join when we are joining by row identity not a specific column.""" + if how not in SUPPORTED_ROW_IDENTITY_HOW: + raise NotImplementedError( + f"Only how='outer','left','inner' currently supported. {constants.FEEDBACK_LINK}" + ) + + if not left._table.equals(right._table): + raise ValueError( + "Cannot combine objects without an explicit join/merge key. " + f"Left based on: {left._table.compile()}, but " + f"right based on: {right._table.compile()}" + ) + + left_predicates = left._predicates + right_predicates = right._predicates + # TODO(tbergeron): Skip generating these for inner part of join + ( + left_relative_predicates, + right_relative_predicates, + ) = _get_relative_predicates(left_predicates, right_predicates) + + combined_predicates = [] + if left_predicates or right_predicates: + joined_predicates = _join_predicates( + left_predicates, right_predicates, join_type=how + ) + combined_predicates = list(joined_predicates) # builder expects mutable list + + left_mask = left_relative_predicates if how in ["right", "outer"] else None + right_mask = right_relative_predicates if how in ["left", "outer"] else None + + # Public mapping must use JOIN_NAME_REMAPPER to stay in sync with consumers of join result + lpublicmapping, rpublicmapping = naming.JOIN_NAME_REMAPPER( + left.column_ids, right.column_ids + ) + lhiddenmapping, rhiddenmapping = naming.JoinNameRemapper(namespace="hidden")( + left._hidden_column_ids, right._hidden_column_ids + ) + map_left_id = {**lpublicmapping, **lhiddenmapping} + map_right_id = {**rpublicmapping, **rhiddenmapping} + + joined_columns = [ + _mask_value(left._get_ibis_column(key), left_mask).name(map_left_id[key]) + for key in left.column_ids + ] + [ + _mask_value(right._get_ibis_column(key), right_mask).name(map_right_id[key]) + for key in right.column_ids + ] + + # If left isn't being masked, can just use left ordering + if not left_mask: + col_mapping = { + order_ref.column_id: map_left_id[order_ref.column_id] + for order_ref in left._ordering.ordering_value_columns + } + new_ordering = left._ordering.with_column_remap(col_mapping) + else: + ordering_columns = [ + col_ref.with_name(map_left_id[col_ref.column_id]) + for col_ref in left._ordering.ordering_value_columns + ] + [ + col_ref.with_name(map_right_id[col_ref.column_id]) + for col_ref in right._ordering.ordering_value_columns + ] + left_total_order_cols = frozenset( + map_left_id[col] for col in left._ordering.total_ordering_columns + ) + # Assume that left ordering is sufficient since 1:1 join over same base table + join_total_order_cols = left_total_order_cols + new_ordering = orderings.ExpressionOrdering( + tuple(ordering_columns), total_ordering_columns=join_total_order_cols + ) + + hidden_ordering_columns = [ + left._get_hidden_ordering_column(key.column_id).name(map_left_id[key.column_id]) + for key in left._ordering.ordering_value_columns + if key.column_id in left._hidden_ordering_column_names.keys() + ] + [ + right._get_hidden_ordering_column(key.column_id).name( + map_right_id[key.column_id] + ) + for key in right._ordering.ordering_value_columns + if key.column_id in right._hidden_ordering_column_names.keys() + ] + + joined_expr = compiled.CompiledArrayValue( + left._table, + columns=joined_columns, + hidden_ordering_columns=hidden_ordering_columns, + ordering=new_ordering, + predicates=combined_predicates, + ) + return joined_expr + + +def _mask_value( + value: ibis_types.Value, + predicates: typing.Optional[typing.Sequence[ibis_types.BooleanValue]] = None, +): + if predicates: + return ( + ibis.case() + .when(_reduce_predicate_list(predicates), value) + .else_(ibis.null()) + .end() + ) + return value + + +def _join_predicates( + left_predicates: typing.Collection[ibis_types.BooleanValue], + right_predicates: typing.Collection[ibis_types.BooleanValue], + join_type: str = "outer", +) -> typing.Tuple[ibis_types.BooleanValue, ...]: + """Combines predicates lists for each side of a join.""" + if join_type == "outer": + if not left_predicates: + return () + if not right_predicates: + return () + # TODO(tbergeron): Investigate factoring out common predicates + joined_predicates = _reduce_predicate_list(left_predicates).__or__( + _reduce_predicate_list(right_predicates) + ) + return (joined_predicates,) + if join_type == "left": + return tuple(left_predicates) + if join_type == "inner": + _, right_relative_predicates = _get_relative_predicates( + left_predicates, right_predicates + ) + return (*left_predicates, *right_relative_predicates) + else: + raise ValueError( + f"Unsupported join_type: {join_type}. {constants.FEEDBACK_LINK}" + ) + + +def _get_relative_predicates( + left_predicates: typing.Collection[ibis_types.BooleanValue], + right_predicates: typing.Collection[ibis_types.BooleanValue], +) -> tuple[ + typing.Tuple[ibis_types.BooleanValue, ...], + typing.Tuple[ibis_types.BooleanValue, ...], +]: + """Get predicates that apply to only one side of the join. Not strictly necessary but simplifies resulting query.""" + left_relative_predicates = tuple(left_predicates) or () + right_relative_predicates = tuple(right_predicates) or () + if left_predicates and right_predicates: + # Factor out common predicates needed for left/right column masking + left_relative_predicates = tuple(set(left_predicates) - set(right_predicates)) + right_relative_predicates = tuple(set(right_predicates) - set(left_predicates)) + return (left_relative_predicates, right_relative_predicates) + + +def _reduce_predicate_list( + predicate_list: typing.Collection[ibis_types.BooleanValue], +) -> ibis_types.BooleanValue: + """Converts a list of predicates BooleanValues into a single BooleanValue.""" + if len(predicate_list) == 0: + raise ValueError("Cannot reduce empty list of predicates") + if len(predicate_list) == 1: + (item,) = predicate_list + return item + return functools.reduce(lambda acc, pred: acc.__and__(pred), predicate_list) diff --git a/bigframes/core/compile/schema_translator.py b/bigframes/core/compile/schema_translator.py deleted file mode 100644 index 428f94035d6..00000000000 --- a/bigframes/core/compile/schema_translator.py +++ /dev/null @@ -1,35 +0,0 @@ -# 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. -# 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 bigframes_vendored.ibis.expr.api as ibis_api -import bigframes_vendored.ibis.expr.schema as ibis_sch - -import bigframes.core.compile.ibis_types -import bigframes.core.schema as bf_schema -import bigframes.dtypes - - -def convert_bf_schema(schema: bf_schema.ArraySchema) -> ibis_sch.Schema: - """ - Convert bigframes schema to ibis schema. This is unambigous as every bigframes type is backed by a specific SQL/ibis dtype. - """ - names = schema.names - types = [ - bigframes.core.compile.ibis_types.bigframes_dtype_to_ibis_dtype(bf_type) - for bf_type in schema.dtypes - ] - return ibis_api.schema(names=names, types=types) diff --git a/bigframes/core/compile/single_column.py b/bigframes/core/compile/single_column.py new file mode 100644 index 00000000000..b992aa1d1d9 --- /dev/null +++ b/bigframes/core/compile/single_column.py @@ -0,0 +1,181 @@ +# Copyright 2023 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. + +"""Helpers to join ArrayValue objects.""" + +from __future__ import annotations + +import typing +from typing import Literal, Mapping + +import ibis +import ibis.expr.datatypes as ibis_dtypes +import ibis.expr.types as ibis_types + +import bigframes.core.compile as compiled +import bigframes.core.compile.row_identity +import bigframes.core.joins as joining +import bigframes.core.ordering as orderings + + +def join_by_column( + left: compiled.CompiledArrayValue, + left_column_ids: typing.Sequence[str], + right: compiled.CompiledArrayValue, + right_column_ids: typing.Sequence[str], + *, + how: Literal[ + "inner", + "left", + "outer", + "right", + ], + allow_row_identity_join: bool = True, +) -> compiled.CompiledArrayValue: + """Join two expressions by column equality. + + Arguments: + left: Expression for left table to join. + left_column_ids: Column IDs (not label) to join by. + right: Expression for right table to join. + right_column_ids: Column IDs (not label) to join by. + how: The type of join to perform. + allow_row_identity_join (bool): + If True, allow matching by row identity. Set to False to always + perform a true JOIN in generated SQL. + Returns: + The joined expression. The resulting columns will be, in order, + first the coalesced join keys, then, all the left columns, and + finally, all the right columns. + """ + if ( + allow_row_identity_join + and how in bigframes.core.compile.row_identity.SUPPORTED_ROW_IDENTITY_HOW + and left._table.equals(right._table) + # Make sure we're joining on exactly the same column(s), at least with + # regards to value its possible that they both have the same names but + # were modified in different ways. Ignore differences in the names. + and all( + left._get_any_column(lcol) + .name("index") + .equals(right._get_any_column(rcol).name("index")) + for lcol, rcol in zip(left_column_ids, right_column_ids) + ) + ): + return bigframes.core.compile.row_identity.join_by_row_identity( + left, right, how=how + ) + else: + # Value column mapping must use JOIN_NAME_REMAPPER to stay in sync with consumers of join result + l_public_mapping, r_public_mapping = joining.JOIN_NAME_REMAPPER( + left.column_ids, right.column_ids + ) + l_hidden_mapping, r_hidden_mapping = joining.JoinNameRemapper( + namespace="hidden" + )(left._hidden_column_ids, right._hidden_column_ids) + l_mapping = {**l_public_mapping, **l_hidden_mapping} + r_mapping = {**r_public_mapping, **r_hidden_mapping} + + left_table = left._to_ibis_expr( + "unordered", + expose_hidden_cols=True, + col_id_overrides=l_mapping, + ) + right_table = right._to_ibis_expr( + "unordered", + expose_hidden_cols=True, + col_id_overrides=r_mapping, + ) + join_conditions = [ + value_to_join_key(left_table[l_mapping[left_index]]) + == value_to_join_key(right_table[r_mapping[right_index]]) + for left_index, right_index in zip(left_column_ids, right_column_ids) + ] + + combined_table = ibis.join( + left_table, + right_table, + predicates=join_conditions, + how=how, + ) + + # Preserve ordering accross joins. + ordering = join_orderings( + left._ordering, + right._ordering, + l_mapping, + r_mapping, + left_order_dominates=(how != "right"), + ) + + # We could filter out the original join columns, but predicates/ordering + # might still reference them in implicit joins. + columns = [ + combined_table[l_mapping[col.get_name()]] for col in left.columns + ] + [combined_table[r_mapping[col.get_name()]] for col in right.columns] + hidden_ordering_columns = [ + *[ + combined_table[l_hidden_mapping[col.get_name()]] + for col in left._hidden_ordering_columns + ], + *[ + combined_table[r_hidden_mapping[col.get_name()]] + for col in right._hidden_ordering_columns + ], + ] + return compiled.CompiledArrayValue( + combined_table, + columns=columns, + hidden_ordering_columns=hidden_ordering_columns, + ordering=ordering, + ) + + +def value_to_join_key(value: ibis_types.Value): + """Converts nullable values to non-null string SQL will not match null keys together - but pandas does.""" + if not value.type().is_string(): + value = value.cast(ibis_dtypes.str) + return value.fillna(ibis_types.literal("$NULL_SENTINEL$")) + + +def join_orderings( + left: orderings.ExpressionOrdering, + right: orderings.ExpressionOrdering, + left_id_mapping: Mapping[str, str], + right_id_mapping: Mapping[str, str], + left_order_dominates: bool = True, +) -> orderings.ExpressionOrdering: + left_ordering_refs = [ + ref.with_name(left_id_mapping[ref.column_id]) + for ref in left.all_ordering_columns + ] + right_ordering_refs = [ + ref.with_name(right_id_mapping[ref.column_id]) + for ref in right.all_ordering_columns + ] + if left_order_dominates: + joined_refs = [*left_ordering_refs, *right_ordering_refs] + else: + joined_refs = [*right_ordering_refs, *left_ordering_refs] + + left_total_order_cols = frozenset( + [left_id_mapping[id] for id in left.total_ordering_columns] + ) + right_total_order_cols = frozenset( + [right_id_mapping[id] for id in right.total_ordering_columns] + ) + return orderings.ExpressionOrdering( + ordering_value_columns=tuple(joined_refs), + total_ordering_columns=left_total_order_cols | right_total_order_cols, + ) diff --git a/bigframes/core/compile/sqlglot/__init__.py b/bigframes/core/compile/sqlglot/__init__.py deleted file mode 100644 index fa515e4f15a..00000000000 --- a/bigframes/core/compile/sqlglot/__init__.py +++ /dev/null @@ -1,32 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -from __future__ import annotations - -import bigframes.core.compile.sqlglot.expressions.ai_ops # noqa: F401 -import bigframes.core.compile.sqlglot.expressions.array_ops # noqa: F401 -import bigframes.core.compile.sqlglot.expressions.blob_ops # noqa: F401 -import bigframes.core.compile.sqlglot.expressions.bool_ops # noqa: F401 -import bigframes.core.compile.sqlglot.expressions.comparison_ops # noqa: F401 -import bigframes.core.compile.sqlglot.expressions.date_ops # noqa: F401 -import bigframes.core.compile.sqlglot.expressions.datetime_ops # noqa: F401 -import bigframes.core.compile.sqlglot.expressions.generic_ops # noqa: F401 -import bigframes.core.compile.sqlglot.expressions.geo_ops # noqa: F401 -import bigframes.core.compile.sqlglot.expressions.json_ops # noqa: F401 -import bigframes.core.compile.sqlglot.expressions.numeric_ops # noqa: F401 -import bigframes.core.compile.sqlglot.expressions.string_ops # noqa: F401 -import bigframes.core.compile.sqlglot.expressions.struct_ops # noqa: F401 -import bigframes.core.compile.sqlglot.expressions.timedelta_ops # noqa: F401 -from bigframes.core.compile.sqlglot.compiler import compile_sql - -__all__ = ["compile_sql"] diff --git a/bigframes/core/compile/sqlglot/aggregate_compiler.py b/bigframes/core/compile/sqlglot/aggregate_compiler.py deleted file mode 100644 index c0781e260c6..00000000000 --- a/bigframes/core/compile/sqlglot/aggregate_compiler.py +++ /dev/null @@ -1,74 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -from __future__ import annotations - -import bigframes_vendored.sqlglot.expressions as sge - -import bigframes.core.compile.sqlglot.expression_compiler as expression_compiler -from bigframes.core import agg_expressions, window_spec -from bigframes.core.compile.sqlglot.aggregations import ( - binary_compiler, - nullary_compiler, - ordered_unary_compiler, - unary_compiler, -) -from bigframes.core.compile.sqlglot.expressions import typed_expr - - -def compile_aggregate( - aggregate: agg_expressions.Aggregation, - order_by: tuple[sge.Expression, ...], -) -> sge.Expression: - """Compiles BigFrames aggregation expression into SQLGlot expression.""" - if isinstance(aggregate, agg_expressions.NullaryAggregation): - return nullary_compiler.compile(aggregate.op) - if isinstance(aggregate, agg_expressions.UnaryAggregation): - column = typed_expr.TypedExpr( - expression_compiler.expression_compiler.compile_expression(aggregate.arg), - aggregate.arg.output_type, - ) - if not aggregate.op.order_independent: - return ordered_unary_compiler.compile( - aggregate.op, column, order_by=order_by - ) - else: - return unary_compiler.compile(aggregate.op, column) - elif isinstance(aggregate, agg_expressions.BinaryAggregation): - left = typed_expr.TypedExpr( - expression_compiler.expression_compiler.compile_expression(aggregate.left), - aggregate.left.output_type, - ) - right = typed_expr.TypedExpr( - expression_compiler.expression_compiler.compile_expression(aggregate.right), - aggregate.right.output_type, - ) - return binary_compiler.compile(aggregate.op, left, right) - else: - raise ValueError(f"Unexpected aggregation: {aggregate}") - - -def compile_analytic( - aggregate: agg_expressions.Aggregation, - window: window_spec.WindowSpec, -) -> sge.Expression: - if isinstance(aggregate, agg_expressions.NullaryAggregation): - return nullary_compiler.compile(aggregate.op, window) - if isinstance(aggregate, agg_expressions.UnaryAggregation): - column = typed_expr.TypedExpr( - expression_compiler.expression_compiler.compile_expression(aggregate.arg), - aggregate.arg.output_type, - ) - return unary_compiler.compile(aggregate.op, column, window) - else: - raise ValueError(f"Unexpected analytic operation: {aggregate}") diff --git a/bigframes/core/compile/sqlglot/aggregations/__init__.py b/bigframes/core/compile/sqlglot/aggregations/__init__.py deleted file mode 100644 index 0a2669d7a25..00000000000 --- a/bigframes/core/compile/sqlglot/aggregations/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. diff --git a/bigframes/core/compile/sqlglot/aggregations/binary_compiler.py b/bigframes/core/compile/sqlglot/aggregations/binary_compiler.py deleted file mode 100644 index df8437fe76f..00000000000 --- a/bigframes/core/compile/sqlglot/aggregations/binary_compiler.py +++ /dev/null @@ -1,60 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from __future__ import annotations - -import typing - -import bigframes_vendored.sqlglot.expressions as sge - -import bigframes.core.compile.sqlglot.aggregations.op_registration as reg -import bigframes.core.compile.sqlglot.expressions.typed_expr as typed_expr -from bigframes.core import window_spec -from bigframes.core.compile.sqlglot.aggregations.windows import apply_window_if_present -from bigframes.operations import aggregations as agg_ops - -BINARY_OP_REGISTRATION = reg.OpRegistration() - - -def compile( - op: agg_ops.WindowOp, - left: typed_expr.TypedExpr, - right: typed_expr.TypedExpr, - window: typing.Optional[window_spec.WindowSpec] = None, -) -> sge.Expression: - if op.order_independent and (window is not None) and window.is_unbounded: - window = window.without_order() - return BINARY_OP_REGISTRATION[op](op, left, right, window=window) - - -@BINARY_OP_REGISTRATION.register(agg_ops.CorrOp) -def _( - op: agg_ops.CorrOp, - left: typed_expr.TypedExpr, - right: typed_expr.TypedExpr, - window: typing.Optional[window_spec.WindowSpec] = None, -) -> sge.Expression: - result = sge.func("CORR", left.expr, right.expr) - return apply_window_if_present(result, window) - - -@BINARY_OP_REGISTRATION.register(agg_ops.CovOp) -def _( - op: agg_ops.CovOp, - left: typed_expr.TypedExpr, - right: typed_expr.TypedExpr, - window: typing.Optional[window_spec.WindowSpec] = None, -) -> sge.Expression: - result = sge.func("COVAR_SAMP", left.expr, right.expr) - return apply_window_if_present(result, window) diff --git a/bigframes/core/compile/sqlglot/aggregations/nullary_compiler.py b/bigframes/core/compile/sqlglot/aggregations/nullary_compiler.py deleted file mode 100644 index f2f1978908f..00000000000 --- a/bigframes/core/compile/sqlglot/aggregations/nullary_compiler.py +++ /dev/null @@ -1,55 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from __future__ import annotations - -import typing - -import bigframes_vendored.sqlglot.expressions as sge - -import bigframes.core.compile.sqlglot.aggregations.op_registration as reg -from bigframes.core import window_spec -from bigframes.core.compile.sqlglot.aggregations.windows import apply_window_if_present -from bigframes.operations import aggregations as agg_ops - -NULLARY_OP_REGISTRATION = reg.OpRegistration() - - -def compile( - op: agg_ops.WindowOp, - window: typing.Optional[window_spec.WindowSpec] = None, -) -> sge.Expression: - if op.order_independent and (window is not None) and window.is_unbounded: - window = window.without_order() - return NULLARY_OP_REGISTRATION[op](op, window=window) - - -@NULLARY_OP_REGISTRATION.register(agg_ops.SizeOp) -def _( - op: agg_ops.SizeOp, - window: typing.Optional[window_spec.WindowSpec] = None, -) -> sge.Expression: - return apply_window_if_present(sge.func("COUNT", sge.convert(1)), window) - - -@NULLARY_OP_REGISTRATION.register(agg_ops.RowNumberOp) -def _( - op: agg_ops.RowNumberOp, - window: typing.Optional[window_spec.WindowSpec] = None, -) -> sge.Expression: - result: sge.Expression = sge.func("ROW_NUMBER") - if window is None: - # ROW_NUMBER always needs an OVER clause. - return sge.Window(this=result) - 1 - return apply_window_if_present(result, window, include_framing_clauses=False) - 1 diff --git a/bigframes/core/compile/sqlglot/aggregations/op_registration.py b/bigframes/core/compile/sqlglot/aggregations/op_registration.py deleted file mode 100644 index 2b3ba20ef09..00000000000 --- a/bigframes/core/compile/sqlglot/aggregations/op_registration.py +++ /dev/null @@ -1,56 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from __future__ import annotations - -import typing - -from bigframes_vendored.sqlglot import expressions as sge - -from bigframes.operations import aggregations as agg_ops - -# We should've been more specific about input types. Unfortunately, -# MyPy doesn't support more rigorous checks. -CompilationFunc = typing.Callable[..., sge.Expression] - - -class OpRegistration: - def __init__(self) -> None: - self._registered_ops: dict[str, CompilationFunc] = {} - - def register( - self, op: agg_ops.WindowOp | type[agg_ops.WindowOp] - ) -> typing.Callable[[CompilationFunc], CompilationFunc]: - def decorator(item: CompilationFunc): - def arg_checker(*args, **kwargs): - if not isinstance(args[0], agg_ops.WindowOp): - raise ValueError( - "The first parameter must be a window operator. " - f"Got {type(args[0])}" - ) - return item(*args, **kwargs) - - key = str(op) - if key in self._registered_ops: - raise ValueError(f"{key} is already registered") - self._registered_ops[key] = item - return arg_checker - - return decorator - - def __getitem__(self, op: str | agg_ops.WindowOp) -> CompilationFunc: - key = op if isinstance(op, type) else type(op) - if str(key) not in self._registered_ops: - raise ValueError(f"{key} is not registered") - return self._registered_ops[str(key)] diff --git a/bigframes/core/compile/sqlglot/aggregations/ordered_unary_compiler.py b/bigframes/core/compile/sqlglot/aggregations/ordered_unary_compiler.py deleted file mode 100644 index 5feaf794e0b..00000000000 --- a/bigframes/core/compile/sqlglot/aggregations/ordered_unary_compiler.py +++ /dev/null @@ -1,60 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from __future__ import annotations - -import bigframes_vendored.sqlglot.expressions as sge - -import bigframes.core.compile.sqlglot.aggregations.op_registration as reg -import bigframes.core.compile.sqlglot.expressions.typed_expr as typed_expr -from bigframes.operations import aggregations as agg_ops - -ORDERED_UNARY_OP_REGISTRATION = reg.OpRegistration() - - -def compile( - op: agg_ops.WindowOp, - column: typed_expr.TypedExpr, - *, - order_by: tuple[sge.Expression, ...] = (), -) -> sge.Expression: - return ORDERED_UNARY_OP_REGISTRATION[op](op, column, order_by=order_by) - - -@ORDERED_UNARY_OP_REGISTRATION.register(agg_ops.ArrayAggOp) -def _( - op: agg_ops.ArrayAggOp, - column: typed_expr.TypedExpr, - *, - order_by: tuple[sge.Expression, ...], -) -> sge.Expression: - expr = column.expr - if len(order_by) > 0: - expr = sge.Order(this=column.expr, expressions=list(order_by)) - return sge.IgnoreNulls(this=sge.ArrayAgg(this=expr)) - - -@ORDERED_UNARY_OP_REGISTRATION.register(agg_ops.StringAggOp) -def _( - op: agg_ops.StringAggOp, - column: typed_expr.TypedExpr, - *, - order_by: tuple[sge.Expression, ...], -) -> sge.Expression: - expr = column.expr - if len(order_by) > 0: - expr = sge.Order(this=expr, expressions=list(order_by)) - - expr = sge.GroupConcat(this=expr, separator=sge.convert(op.sep)) - return sge.func("COALESCE", expr, sge.convert("")) diff --git a/bigframes/core/compile/sqlglot/aggregations/unary_compiler.py b/bigframes/core/compile/sqlglot/aggregations/unary_compiler.py deleted file mode 100644 index 417faef34aa..00000000000 --- a/bigframes/core/compile/sqlglot/aggregations/unary_compiler.py +++ /dev/null @@ -1,626 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from __future__ import annotations - -import typing - -import bigframes_vendored.sqlglot as sg -import bigframes_vendored.sqlglot.expressions as sge -import pandas as pd - -import bigframes.core.compile.sqlglot.aggregations.op_registration as reg -import bigframes.core.compile.sqlglot.expressions.typed_expr as typed_expr -from bigframes import dtypes -from bigframes.core import window_spec -from bigframes.core.compile.sqlglot import sql -from bigframes.core.compile.sqlglot.aggregations.windows import apply_window_if_present -from bigframes.core.compile.sqlglot.expressions import constants -from bigframes.operations import aggregations as agg_ops - -UNARY_OP_REGISTRATION = reg.OpRegistration() - - -def compile( - op: agg_ops.WindowOp, - column: typed_expr.TypedExpr, - window: typing.Optional[window_spec.WindowSpec] = None, -) -> sge.Expression: - if op.order_independent and (window is not None) and window.is_unbounded: - window = window.without_order() - return UNARY_OP_REGISTRATION[op](op, column, window=window) - - -@UNARY_OP_REGISTRATION.register(agg_ops.AllOp) -def _( - op: agg_ops.AllOp, - column: typed_expr.TypedExpr, - window: typing.Optional[window_spec.WindowSpec] = None, -) -> sge.Expression: - expr = column.expr - if column.dtype != dtypes.BOOL_DTYPE: - expr = sge.NEQ(this=expr, expression=sge.convert(0)) - expr = apply_window_if_present(sge.func("LOGICAL_AND", expr), window) - - # BQ will return null for empty column, result would be true in pandas. - return sge.func("COALESCE", expr, sge.convert(True)) - - -@UNARY_OP_REGISTRATION.register(agg_ops.AnyOp) -def _( - op: agg_ops.AnyOp, - column: typed_expr.TypedExpr, - window: typing.Optional[window_spec.WindowSpec] = None, -) -> sge.Expression: - expr = column.expr - if column.dtype != dtypes.BOOL_DTYPE: - expr = sge.NEQ(this=expr, expression=sge.convert(0)) - expr = apply_window_if_present(sge.func("LOGICAL_OR", expr), window) - - # BQ will return null for empty column, result would be false in pandas. - return sge.func("COALESCE", expr, sge.convert(False)) - - -@UNARY_OP_REGISTRATION.register(agg_ops.ApproxQuartilesOp) -def _( - op: agg_ops.ApproxQuartilesOp, - column: typed_expr.TypedExpr, - window: typing.Optional[window_spec.WindowSpec] = None, -) -> sge.Expression: - if window is not None: - raise NotImplementedError("Approx Quartiles with windowing is not supported.") - # APPROX_QUANTILES returns an array of the quartiles, so we need to index it. - # The op.quartile is 1-based for the quartile, but array is 0-indexed. - # The quartiles are Q0, Q1, Q2, Q3, Q4. op.quartile is 1, 2, or 3. - # The array has 5 elements (for N=4 intervals). - # So we want the element at index `op.quartile`. - approx_quantiles_expr = sge.func("APPROX_QUANTILES", column.expr, sge.convert(4)) - return sge.Bracket( - this=approx_quantiles_expr, - expressions=[sge.func("OFFSET", sge.convert(op.quartile))], - ) - - -@UNARY_OP_REGISTRATION.register(agg_ops.ApproxTopCountOp) -def _( - op: agg_ops.ApproxTopCountOp, - column: typed_expr.TypedExpr, - window: typing.Optional[window_spec.WindowSpec] = None, -) -> sge.Expression: - if window is not None: - raise NotImplementedError("Approx top count with windowing is not supported.") - return sge.func("APPROX_TOP_COUNT", column.expr, sge.convert(op.number)) - - -@UNARY_OP_REGISTRATION.register(agg_ops.AnyValueOp) -def _( - op: agg_ops.AnyValueOp, - column: typed_expr.TypedExpr, - window: typing.Optional[window_spec.WindowSpec] = None, -) -> sge.Expression: - return apply_window_if_present(sge.func("ANY_VALUE", column.expr), window) - - -@UNARY_OP_REGISTRATION.register(agg_ops.CountOp) -def _( - op: agg_ops.CountOp, - column: typed_expr.TypedExpr, - window: typing.Optional[window_spec.WindowSpec] = None, -) -> sge.Expression: - return apply_window_if_present(sge.func("COUNT", column.expr), window) - - -@UNARY_OP_REGISTRATION.register(agg_ops.CutOp) -def _( - op: agg_ops.CutOp, - column: typed_expr.TypedExpr, - window: typing.Optional[window_spec.WindowSpec] = None, -) -> sge.Expression: - if isinstance(op.bins, int): - case_expr = _cut_ops_w_int_bins(op, column, op.bins, window) - else: # Interpret as intervals - case_expr = _cut_ops_w_intervals(op, column, op.bins, window) - return case_expr - - -def _cut_ops_w_int_bins( - op: agg_ops.CutOp, - column: typed_expr.TypedExpr, - bins: int, - window: typing.Optional[window_spec.WindowSpec] = None, -) -> sge.Case: - case_expr = sge.Case() - col_min = apply_window_if_present( - sge.func("MIN", column.expr), window or window_spec.WindowSpec() - ) - col_max = apply_window_if_present( - sge.func("MAX", column.expr), window or window_spec.WindowSpec() - ) - adj: sge.Expression = sge.Sub(this=col_max, expression=col_min) * sge.convert(0.001) - bin_width: sge.Expression = sge.func( - "IEEE_DIVIDE", - sge.Sub(this=col_max, expression=col_min), - sge.convert(bins), - ) - - for this_bin in range(bins): - value: sge.Expression - if op.labels is False: - value = sql.literal(this_bin, dtypes.INT_DTYPE) - elif isinstance(op.labels, typing.Iterable): - value = sql.literal(list(op.labels)[this_bin], dtypes.STRING_DTYPE) - else: - left_adj: sge.Expression = ( - adj if this_bin == 0 and op.right else sge.convert(0) - ) - right_adj: sge.Expression = ( - adj if this_bin == bins - 1 and not op.right else sge.convert(0) - ) - - left: sge.Expression = ( - col_min + sge.convert(this_bin) * bin_width - left_adj - ) - right: sge.Expression = ( - col_min + sge.convert(this_bin + 1) * bin_width + right_adj - ) - if op.right: - left_identifier = sge.Identifier(this="left_exclusive", quoted=True) - right_identifier = sge.Identifier(this="right_inclusive", quoted=True) - else: - left_identifier = sge.Identifier(this="left_inclusive", quoted=True) - right_identifier = sge.Identifier(this="right_exclusive", quoted=True) - - value = sge.Struct( - expressions=[ - sge.PropertyEQ(this=left_identifier, expression=left), - sge.PropertyEQ(this=right_identifier, expression=right), - ] - ) - - condition: sge.Expression - if this_bin == bins - 1: - condition = sge.Is( - this=sge.paren(column.expr, copy=False), - expression=sg.not_(sge.Null(), copy=False), - ) - else: - if op.right: - condition = sge.LTE( - this=column.expr, - expression=(col_min + sge.convert(this_bin + 1) * bin_width), - ) - else: - condition = sge.LT( - this=column.expr, - expression=(col_min + sge.convert(this_bin + 1) * bin_width), - ) - case_expr = case_expr.when(condition, value) - return case_expr - - -def _cut_ops_w_intervals( - op: agg_ops.CutOp, - column: typed_expr.TypedExpr, - bins: typing.Iterable[typing.Tuple[typing.Any, typing.Any]], - window: typing.Optional[window_spec.WindowSpec] = None, -) -> sge.Case: - case_expr = sge.Case() - for this_bin, interval in enumerate(bins): - left: sge.Expression = sql.literal( - interval[0], dtypes.infer_literal_type(interval[0]) - ) - right: sge.Expression = sql.literal( - interval[1], dtypes.infer_literal_type(interval[1]) - ) - condition: sge.Expression - if op.right: - condition = sge.And( - this=sge.GT(this=column.expr, expression=left), - expression=sge.LTE(this=column.expr, expression=right), - ) - else: - condition = sge.And( - this=sge.GTE(this=column.expr, expression=left), - expression=sge.LT(this=column.expr, expression=right), - ) - - value: sge.Expression - if op.labels is False: - value = sql.literal(this_bin, dtypes.INT_DTYPE) - elif isinstance(op.labels, typing.Iterable): - value = sql.literal(list(op.labels)[this_bin], dtypes.STRING_DTYPE) - else: - if op.right: - left_identifier = sge.Identifier(this="left_exclusive", quoted=True) - right_identifier = sge.Identifier(this="right_inclusive", quoted=True) - else: - left_identifier = sge.Identifier(this="left_inclusive", quoted=True) - right_identifier = sge.Identifier(this="right_exclusive", quoted=True) - - value = sge.Struct( - expressions=[ - sge.PropertyEQ(this=left_identifier, expression=left), - sge.PropertyEQ(this=right_identifier, expression=right), - ] - ) - case_expr = case_expr.when(condition, value) - return case_expr - - -@UNARY_OP_REGISTRATION.register(agg_ops.DenseRankOp) -def _( - op: agg_ops.DenseRankOp, - column: typed_expr.TypedExpr, - window: typing.Optional[window_spec.WindowSpec] = None, -) -> sge.Expression: - return apply_window_if_present( - sge.func("DENSE_RANK"), window, include_framing_clauses=False - ) - - -@UNARY_OP_REGISTRATION.register(agg_ops.FirstOp) -def _( - op: agg_ops.FirstOp, - column: typed_expr.TypedExpr, - window: typing.Optional[window_spec.WindowSpec] = None, -) -> sge.Expression: - # FIRST_VALUE in BQ respects nulls by default. - return apply_window_if_present(sge.FirstValue(this=column.expr), window) - - -@UNARY_OP_REGISTRATION.register(agg_ops.FirstNonNullOp) -def _( - op: agg_ops.FirstNonNullOp, - column: typed_expr.TypedExpr, - window: typing.Optional[window_spec.WindowSpec] = None, -) -> sge.Expression: - return apply_window_if_present( - sge.IgnoreNulls(this=sge.FirstValue(this=column.expr)), window - ) - - -@UNARY_OP_REGISTRATION.register(agg_ops.LastOp) -def _( - op: agg_ops.LastOp, - column: typed_expr.TypedExpr, - window: typing.Optional[window_spec.WindowSpec] = None, -) -> sge.Expression: - # LAST_VALUE in BQ respects nulls by default. - return apply_window_if_present(sge.LastValue(this=column.expr), window) - - -@UNARY_OP_REGISTRATION.register(agg_ops.LastNonNullOp) -def _( - op: agg_ops.LastNonNullOp, - column: typed_expr.TypedExpr, - window: typing.Optional[window_spec.WindowSpec] = None, -) -> sge.Expression: - return apply_window_if_present( - sge.IgnoreNulls(this=sge.LastValue(this=column.expr)), window - ) - - -@UNARY_OP_REGISTRATION.register(agg_ops.DiffOp) -def _( - op: agg_ops.DiffOp, - column: typed_expr.TypedExpr, - window: typing.Optional[window_spec.WindowSpec] = None, -) -> sge.Expression: - shift_op_impl = UNARY_OP_REGISTRATION[agg_ops.ShiftOp(0)] - shifted = shift_op_impl(agg_ops.ShiftOp(op.periods), column, window) - if column.dtype == dtypes.BOOL_DTYPE: - return sge.NEQ(this=column.expr, expression=shifted) - - if column.dtype in (dtypes.INT_DTYPE, dtypes.FLOAT_DTYPE): - return sge.Sub(this=column.expr, expression=shifted) - - if column.dtype == dtypes.TIMESTAMP_DTYPE: - return sge.TimestampDiff( - this=column.expr, - expression=shifted, - unit=sge.Identifier(this="MICROSECOND"), - ) - - if column.dtype == dtypes.DATETIME_DTYPE: - return sge.DatetimeDiff( - this=column.expr, - expression=shifted, - unit=sge.Identifier(this="MICROSECOND"), - ) - - if column.dtype == dtypes.DATE_DTYPE: - date_diff = sge.DateDiff( - this=column.expr, expression=shifted, unit=sge.Identifier(this="DAY") - ) - return sge.Cast( - this=sge.Floor(this=date_diff * constants._DAY_TO_MICROSECONDS), - to="INT64", - ) - - raise TypeError(f"Cannot perform diff on type {column.dtype}") - - -@UNARY_OP_REGISTRATION.register(agg_ops.MaxOp) -def _( - op: agg_ops.MaxOp, - column: typed_expr.TypedExpr, - window: typing.Optional[window_spec.WindowSpec] = None, -) -> sge.Expression: - return apply_window_if_present(sge.func("MAX", column.expr), window) - - -@UNARY_OP_REGISTRATION.register(agg_ops.MeanOp) -def _( - op: agg_ops.MeanOp, - column: typed_expr.TypedExpr, - window: typing.Optional[window_spec.WindowSpec] = None, -) -> sge.Expression: - expr = column.expr - if column.dtype == dtypes.BOOL_DTYPE: - expr = sge.Cast(this=expr, to="INT64") - - expr = sge.func("AVG", expr) - - should_floor_result = ( - op.should_floor_result or column.dtype == dtypes.TIMEDELTA_DTYPE - ) - if should_floor_result: - expr = sge.Cast(this=sge.func("FLOOR", expr), to="INT64") - return apply_window_if_present(expr, window) - - -@UNARY_OP_REGISTRATION.register(agg_ops.MedianOp) -def _( - op: agg_ops.MedianOp, - column: typed_expr.TypedExpr, - window: typing.Optional[window_spec.WindowSpec] = None, -) -> sge.Expression: - approx_quantiles = sge.func("APPROX_QUANTILES", column.expr, sge.convert(2)) - return sge.Bracket( - this=approx_quantiles, expressions=[sge.func("OFFSET", sge.convert(1))] - ) - - -@UNARY_OP_REGISTRATION.register(agg_ops.MinOp) -def _( - op: agg_ops.MinOp, - column: typed_expr.TypedExpr, - window: typing.Optional[window_spec.WindowSpec] = None, -) -> sge.Expression: - return apply_window_if_present(sge.func("MIN", column.expr), window) - - -@UNARY_OP_REGISTRATION.register(agg_ops.NuniqueOp) -def _( - op: agg_ops.NuniqueOp, - column: typed_expr.TypedExpr, - window: typing.Optional[window_spec.WindowSpec] = None, -) -> sge.Expression: - return apply_window_if_present( - sge.func("COUNT", sge.Distinct(expressions=[column.expr])), window - ) - - -@UNARY_OP_REGISTRATION.register(agg_ops.PopVarOp) -def _( - op: agg_ops.PopVarOp, - column: typed_expr.TypedExpr, - window: typing.Optional[window_spec.WindowSpec] = None, -) -> sge.Expression: - expr = column.expr - if column.dtype == dtypes.BOOL_DTYPE: - expr = sge.Cast(this=expr, to="INT64") - - expr = sge.func("VAR_POP", expr) - return apply_window_if_present(expr, window) - - -@UNARY_OP_REGISTRATION.register(agg_ops.ProductOp) -def _( - op: agg_ops.ProductOp, - column: typed_expr.TypedExpr, - window: typing.Optional[window_spec.WindowSpec] = None, -) -> sge.Expression: - expr = column.expr - if column.dtype == dtypes.BOOL_DTYPE: - expr = sge.Cast(this=expr, to="INT64") - - # Need to short-circuit as log with zeroes is illegal sql - is_zero = sge.EQ(this=expr, expression=sge.convert(0)) - - # There is no product sql aggregate function, so must implement as a sum of logs, and then - # apply power after. Note, log and power base must be equal! This impl uses natural log. - logs = sge.If( - this=is_zero, - true=sge.convert(0), - false=sge.func("LOG", sge.convert(2), sge.func("ABS", expr)), - ) - logs_sum = apply_window_if_present(sge.func("SUM", logs), window) - magnitude = sge.func("POWER", sge.convert(2), logs_sum) - - # Can't determine sign from logs, so have to determine parity of count of negative inputs - is_negative = ( - sge.Case() - .when( - sge.EQ(this=sge.func("SIGN", expr), expression=sge.convert(-1)), - sge.convert(1), - ) - .else_(sge.convert(0)) - ) - negative_count = apply_window_if_present(sge.func("SUM", is_negative), window) - negative_count_parity = sge.Mod( - this=negative_count, expression=sge.convert(2) - ) # 1 if result should be negative, otherwise 0 - - any_zeroes = apply_window_if_present(sge.func("LOGICAL_OR", is_zero), window) - - float_result = ( - sge.Case() - .when(any_zeroes, sge.convert(0)) - .else_( - sge.Mul( - this=magnitude, - expression=sge.func("POWER", sge.convert(-1), negative_count_parity), - ) - ) - ) - return float_result - - -@UNARY_OP_REGISTRATION.register(agg_ops.QcutOp) -def _( - op: agg_ops.QcutOp, - column: typed_expr.TypedExpr, - window: typing.Optional[window_spec.WindowSpec] = None, -) -> sge.Expression: - percent_ranks_order_by = sge.Ordered(this=column.expr, desc=False) - percent_ranks = apply_window_if_present( - sge.func("PERCENT_RANK"), - window, - include_framing_clauses=False, - order_by_override=[percent_ranks_order_by], - ) - if isinstance(op.quantiles, int): - scaled_rank = percent_ranks * sge.convert(op.quantiles) - # Calculate the 0-based bucket index. - bucket_index = sge.func("CEIL", scaled_rank) - sge.convert(1) - safe_bucket_index = sge.func("GREATEST", bucket_index, 0) - - return sge.If( - this=sge.Is(this=column.expr, expression=sge.Null()), - true=sge.Null(), - false=sge.Cast(this=safe_bucket_index, to="INT64"), - ) - else: - case = sge.Case() - first_quantile = sge.convert(op.quantiles[0]) - case = case.when( - sge.LT(this=percent_ranks, expression=first_quantile), sge.Null() - ) - for bucket_n in range(len(op.quantiles) - 1): - quantile = sge.convert(op.quantiles[bucket_n + 1]) - bucket = sge.convert(bucket_n) - case = case.when(sge.LTE(this=percent_ranks, expression=quantile), bucket) - return case.else_(sge.Null()) - - -@UNARY_OP_REGISTRATION.register(agg_ops.QuantileOp) -def _( - op: agg_ops.QuantileOp, - column: typed_expr.TypedExpr, - window: typing.Optional[window_spec.WindowSpec] = None, -) -> sge.Expression: - expr = column.expr - if column.dtype == dtypes.BOOL_DTYPE: - expr = sge.Cast(this=expr, to="INT64") - - result: sge.Expression = sge.func("PERCENTILE_CONT", expr, sge.convert(op.q)) - if window is None: - # PERCENTILE_CONT is a navigation function, not an aggregate function, - # so it always needs an OVER clause. - result = sge.Window(this=result) - else: - result = apply_window_if_present(result, window) - - if op.should_floor_result or column.dtype == dtypes.TIMEDELTA_DTYPE: - result = sge.Cast(this=sge.func("FLOOR", result), to="INT64") - return result - - -@UNARY_OP_REGISTRATION.register(agg_ops.RankOp) -def _( - op: agg_ops.RankOp, - column: typed_expr.TypedExpr, - window: typing.Optional[window_spec.WindowSpec] = None, -) -> sge.Expression: - return apply_window_if_present( - sge.func("RANK"), window, include_framing_clauses=False - ) - - -@UNARY_OP_REGISTRATION.register(agg_ops.SizeUnaryOp) -def _( - op: agg_ops.SizeUnaryOp, - _, - window: typing.Optional[window_spec.WindowSpec] = None, -) -> sge.Expression: - return apply_window_if_present(sge.func("COUNT", sge.convert(1)), window) - - -@UNARY_OP_REGISTRATION.register(agg_ops.StdOp) -def _( - op: agg_ops.StdOp, - column: typed_expr.TypedExpr, - window: typing.Optional[window_spec.WindowSpec] = None, -) -> sge.Expression: - expr = column.expr - if column.dtype == dtypes.BOOL_DTYPE: - expr = sge.Cast(this=expr, to="INT64") - - expr = sge.func("STDDEV", expr) - if op.should_floor_result or column.dtype == dtypes.TIMEDELTA_DTYPE: - expr = sge.Cast(this=sge.func("FLOOR", expr), to="INT64") - return apply_window_if_present(expr, window) - - -@UNARY_OP_REGISTRATION.register(agg_ops.ShiftOp) -def _( - op: agg_ops.ShiftOp, - column: typed_expr.TypedExpr, - window: typing.Optional[window_spec.WindowSpec] = None, -) -> sge.Expression: - if op.periods == 0: # No-op - return column.expr - if op.periods > 0: - return apply_window_if_present( - sge.func("LAG", column.expr, sge.convert(op.periods)), - window, - include_framing_clauses=False, - ) - return apply_window_if_present( - sge.func("LEAD", column.expr, sge.convert(-op.periods)), - window, - include_framing_clauses=False, - ) - - -@UNARY_OP_REGISTRATION.register(agg_ops.SumOp) -def _( - op: agg_ops.SumOp, - column: typed_expr.TypedExpr, - window: typing.Optional[window_spec.WindowSpec] = None, -) -> sge.Expression: - expr = column.expr - if column.dtype == dtypes.BOOL_DTYPE: - expr = sge.Cast(this=column.expr, to="INT64") - - expr = apply_window_if_present(sge.func("SUM", expr), window) - - # Will be null if all inputs are null. Pandas defaults to zero sum though. - zero = pd.to_timedelta(0) if column.dtype == dtypes.TIMEDELTA_DTYPE else 0 - return sge.func("IFNULL", expr, sql.literal(zero, column.dtype)) - - -@UNARY_OP_REGISTRATION.register(agg_ops.VarOp) -def _( - op: agg_ops.VarOp, - column: typed_expr.TypedExpr, - window: typing.Optional[window_spec.WindowSpec] = None, -) -> sge.Expression: - expr = column.expr - if column.dtype == dtypes.BOOL_DTYPE: - expr = sge.Cast(this=expr, to="INT64") - - expr = sge.func("VAR_SAMP", expr) - return apply_window_if_present(expr, window) diff --git a/bigframes/core/compile/sqlglot/aggregations/windows.py b/bigframes/core/compile/sqlglot/aggregations/windows.py deleted file mode 100644 index cb4a2e70edd..00000000000 --- a/bigframes/core/compile/sqlglot/aggregations/windows.py +++ /dev/null @@ -1,205 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -from __future__ import annotations - -import typing - -import bigframes_vendored.sqlglot.expressions as sge - -import bigframes.core.compile.sqlglot.expression_compiler as expression_compiler -import bigframes.core.expression as ex -import bigframes.core.ordering as ordering_spec -import bigframes.dtypes as dtypes -from bigframes.core import utils, window_spec - - -def apply_window_if_present( - value: sge.Expression, - window: typing.Optional[window_spec.WindowSpec] = None, - include_framing_clauses: bool = True, - order_by_override: typing.Optional[typing.List[sge.Ordered]] = None, -) -> sge.Expression: - if window is None: - return value - - if window.is_row_bounded and not window.ordering: - raise ValueError("No ordering provided for ordered analytic function") - elif ( - not window.is_row_bounded - and not window.is_range_bounded - and not window.ordering - ): - # Unbound grouping window. - order_by = None - elif window.is_range_bounded: - order_by = get_window_order_by((window.ordering[0],)) - order_by = remove_null_ordering_for_range_windows(order_by) - else: - order_by = get_window_order_by(window.ordering) - - order = None - if order_by_override is not None and len(order_by_override) > 0: - order = sge.Order(expressions=order_by_override) - elif order_by: - order = sge.Order(expressions=order_by) - - group_by = ( - [_compile_group_by_key(key) for key in window.grouping_keys] - if window.grouping_keys - else None - ) - - # This is the key change. Don't create a spec for the default window frame - # if there's no ordering. This avoids generating an `ORDER BY NULL` clause. - if window.is_unbounded and not order: - return sge.Window(this=value, partition_by=group_by) - - if window.is_unbounded and not include_framing_clauses: - return sge.Window(this=value, partition_by=group_by, order=order) - - kind = ( - "RANGE" if isinstance(window.bounds, window_spec.RangeWindowBounds) else "ROWS" - ) - - start: typing.Union[int, float, None] = None - end: typing.Union[int, float, None] = None - if isinstance(window.bounds, window_spec.RangeWindowBounds): - if window.bounds.start is not None: - start = utils.timedelta_to_micros(window.bounds.start) - if window.bounds.end is not None: - end = utils.timedelta_to_micros(window.bounds.end) - elif window.bounds: - start = window.bounds.start - end = window.bounds.end - - start_value, start_side = _get_window_bounds(start, is_preceding=True) - end_value, end_side = _get_window_bounds(end, is_preceding=False) - - spec = sge.WindowSpec( - kind=kind, - start=start_value, - start_side=start_side, - end=end_value, - end_side=end_side, - over="OVER", - ) - - return sge.Window(this=value, partition_by=group_by, order=order, spec=spec) - - -def get_window_order_by( - ordering: typing.Tuple[ordering_spec.OrderingExpression, ...], - override_null_order: bool = False, -) -> typing.Optional[tuple[sge.Ordered, ...]]: - """Returns the SQL order by clause for a window specification. - Args: - ordering (Tuple[ordering_spec.OrderingExpression, ...]): - A tuple of ordering specification objects. - override_null_order (bool): - If True, overrides BigQuery's default null ordering behavior, which - is sometimes incompatible with ordered aggregations. The generated SQL - will include extra expressions to correctly enforce NULL FIRST/LAST. - """ - if not ordering: - return None - - order_by = [] - for ordering_spec_item in ordering: - expr = expression_compiler.expression_compiler.compile_expression( - ordering_spec_item.scalar_expression - ) - desc = not ordering_spec_item.direction.is_ascending - nulls_first = not ordering_spec_item.na_last - - if override_null_order: - is_null_expr = sge.Is(this=expr, expression=sge.Null()) - if nulls_first and desc: - order_by.append( - sge.Ordered( - this=is_null_expr, - desc=desc, - nulls_first=nulls_first, - ) - ) - elif (not nulls_first) and (not desc): - order_by.append( - sge.Ordered( - this=is_null_expr, - desc=desc, - nulls_first=nulls_first, - ) - ) - - order_by.append( - sge.Ordered( - this=expr, - desc=desc, - nulls_first=nulls_first, - ) - ) - return tuple(order_by) - - -def remove_null_ordering_for_range_windows( - order_by: typing.Optional[tuple[sge.Ordered, ...]], -) -> typing.Optional[tuple[sge.Ordered, ...]]: - """Removes NULL FIRST/LAST from ORDER BY expressions in RANGE windows. - Here's the support matrix: - ✅ sum(x) over (order by y desc nulls last) - 🚫 sum(x) over (order by y asc nulls last) - ✅ sum(x) over (order by y asc nulls first) - 🚫 sum(x) over (order by y desc nulls first) - """ - if order_by is None: - return None - - new_order_by = [] - for key in order_by: - kargs = key.args - if kargs.get("desc") is True and kargs.get("nulls_first", False): - kargs["nulls_first"] = False - elif kargs.get("desc") is False and not kargs.setdefault("nulls_first", True): - kargs["nulls_first"] = True - new_order_by.append(sge.Ordered(**kargs)) - return tuple(new_order_by) - - -def _get_window_bounds( - value, is_preceding: bool -) -> tuple[typing.Union[str, sge.Expression], typing.Optional[str]]: - """Compiles a single boundary value into its SQL components.""" - if value is None: - side = "PRECEDING" if is_preceding else "FOLLOWING" - return "UNBOUNDED", side - - if value == 0: - return "CURRENT ROW", None - - side = "PRECEDING" if value < 0 else "FOLLOWING" - return sge.convert(abs(value)), side - - -def _compile_group_by_key(key: ex.Expression) -> sge.Expression: - expr = expression_compiler.expression_compiler.compile_expression(key) - # The group_by keys has been rewritten by bind_schema_to_node - assert key.is_scalar_expr and key.is_resolved - - # Some types need to be converted to another type to enable groupby - if key.output_type == dtypes.FLOAT_DTYPE: - expr = sge.Cast(this=expr, to="STRING") - elif key.output_type == dtypes.GEO_DTYPE: - expr = sge.func("ST_ASBINARY", expr) - elif key.output_type == dtypes.JSON_DTYPE: - expr = sge.func("TO_JSON_STRING", expr) - return expr diff --git a/bigframes/core/compile/sqlglot/compiler.py b/bigframes/core/compile/sqlglot/compiler.py deleted file mode 100644 index 393d10ec825..00000000000 --- a/bigframes/core/compile/sqlglot/compiler.py +++ /dev/null @@ -1,378 +0,0 @@ -# Copyright 2023 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -from __future__ import annotations - -import dataclasses -import functools -import typing - -import bigframes_vendored.sqlglot.expressions as sge - -import bigframes.core.compile.sqlglot.aggregate_compiler as aggregate_compiler -import bigframes.core.compile.sqlglot.expression_compiler as expression_compiler -import bigframes.core.ordering as bf_ordering -from bigframes import dtypes -from bigframes.core import ( - expression, - guid, - identifiers, - nodes, - pyarrow_utils, - rewrite, - sql_nodes, -) -from bigframes.core.compile import configs -from bigframes.core.compile.sqlglot import sql, sqlglot_ir -from bigframes.core.compile.sqlglot.aggregations import windows -from bigframes.core.compile.sqlglot.expressions import typed_expr -from bigframes.core.logging import data_types as data_type_logger -from bigframes.core.rewrite import schema_binding - - -def compile_sql(request: configs.CompileRequest) -> configs.CompileResult: - """Compiles a BigFrameNode according to the request into SQL using SQLGlot.""" - - output_names = tuple((expression.DerefOp(id), id.sql) for id in request.node.ids) - result_node = nodes.ResultNode( - request.node, - output_cols=output_names, - limit=request.peek_count, - ) - if request.sort_rows: - # Can only pullup slice if we are doing ORDER BY in outermost SELECT - # Need to do this before replacing unsupported ops, as that will rewrite slice ops - result_node = rewrite.pull_up_limits(result_node) - result_node = typing.cast(nodes.ResultNode, _replace_unsupported_ops(result_node)) - result_node = typing.cast( - nodes.ResultNode, result_node.bottom_up(rewrite.simplify_join) - ) - # prune before pulling up order to avoid unnnecessary row_number() ops - result_node = typing.cast(nodes.ResultNode, rewrite.column_pruning(result_node)) - result_node = rewrite.defer_order( - result_node, output_hidden_row_keys=request.materialize_all_order_keys - ) - if request.sort_rows: - result_node = typing.cast(nodes.ResultNode, rewrite.column_pruning(result_node)) - encoded_type_refs = data_type_logger.encode_type_refs(result_node) - # TODO: Extract CTEs earlier - result_node = typing.cast(nodes.ResultNode, rewrite.extract_ctes(result_node)) - sql = _compile_result_node(result_node) - return configs.CompileResult( - sql, - result_node.schema.to_bigquery(), - result_node.order_by, - encoded_type_refs, - ) - - ordering: typing.Optional[bf_ordering.RowOrdering] = result_node.order_by - result_node = dataclasses.replace(result_node, order_by=None) - result_node = typing.cast(nodes.ResultNode, rewrite.column_pruning(result_node)) - encoded_type_refs = data_type_logger.encode_type_refs(result_node) - # TODO: Extract CTEs earlier - result_node = typing.cast(nodes.ResultNode, rewrite.extract_ctes(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: - output_order = ( - ordering if ordering.referenced_columns.issubset(result_node.ids) else None - ) - assert (not request.materialize_all_order_keys) or (output_order is not None) - return configs.CompileResult( - sql, result_node.schema.to_bigquery(), output_order, encoded_type_refs - ) - - -def _remap_variables( - node: nodes.ResultNode, uid_gen: guid.SequentialUIDGenerator -) -> nodes.ResultNode: - """Remaps `ColumnId`s in the BFET of a `ResultNode` to produce deterministic UIDs.""" - - result_node, _ = rewrite.remap_variables( - node, map(identifiers.ColumnId, uid_gen.get_uid_stream("bfcol_")) - ) - result_node.validate_tree() - return typing.cast(nodes.ResultNode, result_node) - - -def _compile_result_node(root: nodes.ResultNode) -> str: - # Create UIDs to standardize variable names and ensure consistent compilation - # of nodes using the same generator. - uid_gen = guid.SequentialUIDGenerator() - root = _remap_variables(root, uid_gen) - # Remap variables creates too mayn new - # root = rewrite.select_pullup(root, prefer_source_names=False) - root = typing.cast(nodes.ResultNode, rewrite.defer_selection(root)) - - # Have to bind schema as the final step before compilation. - # Probably, should defer even further - root = typing.cast(nodes.ResultNode, schema_binding.bind_schema_to_tree(root)) - - # TODO: Bake all IDs in tree, stop passing uid_gen to emitters - sqlglot_ir_obj = compile_node(rewrite.as_sql_nodes(root, uid_gen), uid_gen) - return sqlglot_ir_obj.sql - - -def compile_node( - node: nodes.BigFrameNode, uid_gen: guid.SequentialUIDGenerator -) -> sqlglot_ir.SQLGlotIR: - """Compiles the given BigFrameNode from bottem-up into SQLGlotIR.""" - bf_to_sqlglot: dict[nodes.BigFrameNode, sqlglot_ir.SQLGlotIR] = {} - child_results: tuple[sqlglot_ir.SQLGlotIR, ...] = () - for current_node in list(node.iter_nodes_topo()): - if current_node.child_nodes == (): - # For leaf node, generates a dumpy child to pass the UID generator. - child_results = tuple([sqlglot_ir.SQLGlotIR.empty(uid_gen=uid_gen)]) - else: - # Child nodes should have been compiled in the reverse topological order. - child_results = tuple( - bf_to_sqlglot[child] for child in current_node.child_nodes - ) - result = _compile_node(current_node, *child_results) - bf_to_sqlglot[current_node] = result - - return bf_to_sqlglot[node] - - -@functools.singledispatch -def _compile_node( - node: nodes.BigFrameNode, *compiled_children: sqlglot_ir.SQLGlotIR -) -> sqlglot_ir.SQLGlotIR: - """Defines transformation but isn't cached, always use compile_node instead""" - raise ValueError(f"Can't compile unrecognized node: {node}") - - -@_compile_node.register -def compile_sql_select(node: sql_nodes.SqlSelectNode, child: sqlglot_ir.SQLGlotIR): - ordering_cols = tuple( - sge.Ordered( - this=expression_compiler.expression_compiler.compile_expression( - ordering.scalar_expression - ), - desc=ordering.direction.is_ascending is False, - nulls_first=ordering.na_last is False, - ) - for ordering in node.sorting - ) - - projected_cols: tuple[tuple[str, sge.Expression], ...] = tuple() - if not node.is_star_selection: - projected_cols = tuple( - ( - cdef.id.sql, - expression_compiler.expression_compiler.compile_expression( - cdef.expression - ), - ) - for cdef in node.selections - ) - - sge_predicates = tuple( - expression_compiler.expression_compiler.compile_expression(expression) - for expression in node.predicates - ) - - return child.select(projected_cols, sge_predicates, ordering_cols, node.limit) - - -@_compile_node.register -def compile_readlocal( - node: nodes.ReadLocalNode, child: sqlglot_ir.SQLGlotIR -) -> sqlglot_ir.SQLGlotIR: - pa_table = node.local_data_source.data - pa_table = pa_table.select([item.source_id for item in node.scan_list.items]) - pa_table = pa_table.rename_columns([item.id.sql for item in node.scan_list.items]) - - offsets = node.offsets_col.sql if node.offsets_col else None - if offsets: - pa_table = pyarrow_utils.append_offsets(pa_table, offsets) - - return sqlglot_ir.SQLGlotIR.from_pyarrow( - pa_table, node.schema, uid_gen=child.uid_gen - ) - - -@_compile_node.register -def compile_readtable(node: sql_nodes.SqlDataSource, child: sqlglot_ir.SQLGlotIR): - table_obj = node.source.table - columns = () if node.is_star_selection else node.source.schema.names - return sqlglot_ir.SQLGlotIR.from_table( - table_obj.project_id, - table_obj.dataset_id, - table_obj.table_id, - uid_gen=child.uid_gen, - columns=columns, - sql_predicate=node.source.sql_predicate, - system_time=node.source.at_time, - ) - - -@_compile_node.register -def compile_join( - node: nodes.JoinNode, left: sqlglot_ir.SQLGlotIR, right: sqlglot_ir.SQLGlotIR -) -> sqlglot_ir.SQLGlotIR: - conditions = tuple( - ( - typed_expr.TypedExpr( - expression_compiler.expression_compiler.compile_expression(left_expr), - left_expr.output_type, - ), - typed_expr.TypedExpr( - expression_compiler.expression_compiler.compile_expression(right_expr), - right_expr.output_type, - ), - ) - for left_expr, right_expr in node.conditions - ) - - return left.join( - right, - join_type=node.type, - conditions=conditions, - joins_nulls=node.joins_nulls, - ) - - -@_compile_node.register -def compile_isin_join( - node: nodes.InNode, left: sqlglot_ir.SQLGlotIR, right: sqlglot_ir.SQLGlotIR -) -> sqlglot_ir.SQLGlotIR: - right_field = node.right_child.fields[0] - conditions = ( - typed_expr.TypedExpr( - expression_compiler.expression_compiler.compile_expression(node.left_col), - node.left_col.output_type, - ), - typed_expr.TypedExpr( - expression_compiler.expression_compiler.compile_expression( - expression.DerefOp(right_field.id) - ), - right_field.dtype, - ), - ) - - return left.isin_join( - right, - indicator_col=node.indicator_col.sql, - conditions=conditions, - joins_nulls=node.joins_nulls, - ) - - -@_compile_node.register -def compile_cte_ref_node(node: sql_nodes.SqlCteRefNode, child: sqlglot_ir.SQLGlotIR): - return sqlglot_ir.SQLGlotIR.from_cte_ref( - node.cte_name, - uid_gen=child.uid_gen, - ) - - -@_compile_node.register -def compile_with_ctes_node( - node: sql_nodes.SqlWithCtesNode, - child: sqlglot_ir.SQLGlotIR, - *ctes: sqlglot_ir.SQLGlotIR, -): - return child.with_ctes(tuple(zip(node.cte_names, ctes))) - - -@_compile_node.register -def compile_concat( - node: nodes.ConcatNode, *children: sqlglot_ir.SQLGlotIR -) -> sqlglot_ir.SQLGlotIR: - assert len(children) >= 1 - uid_gen = children[0].uid_gen - - # BigQuery `UNION` query takes the column names from the first `SELECT` clause. - default_output_ids = [field.id.sql for field in node.child_nodes[0].fields] - output_aliases = [ - (default_output_id, output_id.sql) - for default_output_id, output_id in zip(default_output_ids, node.output_ids) - ] - - return sqlglot_ir.SQLGlotIR.from_union( - [child.expr.as_select_all() for child in children], - output_aliases=output_aliases, - uid_gen=uid_gen, - ) - - -@_compile_node.register -def compile_explode( - node: nodes.ExplodeNode, child: sqlglot_ir.SQLGlotIR -) -> sqlglot_ir.SQLGlotIR: - offsets_col = node.offsets_col.sql if (node.offsets_col is not None) else None - columns = tuple(ref.id.sql for ref in node.column_ids) - return child.explode(columns, offsets_col) - - -@_compile_node.register -def compile_fromrange( - node: nodes.FromRangeNode, start: sqlglot_ir.SQLGlotIR, end: sqlglot_ir.SQLGlotIR -) -> sqlglot_ir.SQLGlotIR: - start_col_id = node.start.fields[0].id - end_col_id = node.end.fields[0].id - - start_expr = expression_compiler.expression_compiler.compile_expression( - expression.DerefOp(start_col_id) - ) - end_expr = expression_compiler.expression_compiler.compile_expression( - expression.DerefOp(end_col_id) - ) - step_expr = sql.literal(node.step, dtypes.INT_DTYPE) - - return start.resample(end, node.output_id.sql, start_expr, end_expr, step_expr) - - -@_compile_node.register -def compile_random_sample( - node: nodes.RandomSampleNode, child: sqlglot_ir.SQLGlotIR -) -> sqlglot_ir.SQLGlotIR: - return child.sample(node.fraction) - - -@_compile_node.register -def compile_aggregate( - node: nodes.AggregateNode, child: sqlglot_ir.SQLGlotIR -) -> sqlglot_ir.SQLGlotIR: - # The BigQuery ordered aggregation cannot support for NULL FIRST/LAST, - # so we need to add extra expressions to enforce the null ordering. - ordering_cols = windows.get_window_order_by(node.order_by, override_null_order=True) - aggregations: tuple[tuple[str, sge.Expression], ...] = tuple( - ( - id.sql, - aggregate_compiler.compile_aggregate( - agg, order_by=ordering_cols if ordering_cols else () - ), - ) - for agg, id in node.aggregations - ) - by_cols: tuple[sge.Expression, ...] = tuple( - expression_compiler.expression_compiler.compile_expression(by_col) - for by_col in node.by_column_ids - ) - - dropna_cols = [] - if node.dropna: - for key, by_col in zip(node.by_column_ids, by_cols): - if node.child.field_by_id[key.id].nullable: - dropna_cols.append(by_col) - - return child.aggregate(aggregations, by_cols, tuple(dropna_cols)) - - -def _replace_unsupported_ops(node: nodes.BigFrameNode): - node = nodes.bottom_up(node, rewrite.rewrite_slice) - node = nodes.bottom_up(node, rewrite.rewrite_range_rolling) - node = nodes.bottom_up(node, rewrite.lower_udfs) - return node diff --git a/bigframes/core/compile/sqlglot/expression_compiler.py b/bigframes/core/compile/sqlglot/expression_compiler.py deleted file mode 100644 index b412249a39f..00000000000 --- a/bigframes/core/compile/sqlglot/expression_compiler.py +++ /dev/null @@ -1,236 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -from __future__ import annotations - -import functools -import typing - -import bigframes_vendored.sqlglot.expressions as sge - -import bigframes.core.agg_expressions as agg_exprs -import bigframes.core.expression as ex -import bigframes.operations as ops -from bigframes.core.compile.sqlglot import sql -from bigframes.core.compile.sqlglot.expressions.typed_expr import TypedExpr - - -class ExpressionCompiler: - # Mapping of operation name to implemenations - _registry: dict[ - str, - typing.Callable[[typing.Sequence[TypedExpr], ops.RowOp], sge.Expression], - ] = {} - - # A set of SQLGlot classes that may need to be parenthesized - SQLGLOT_NEEDS_PARENS = { - # Numeric operations - sge.Add, - sge.Sub, - sge.Mul, - sge.Div, - sge.Mod, - sge.Pow, - # Comparison operations - sge.GTE, - sge.GT, - sge.LTE, - sge.LT, - sge.EQ, - sge.NEQ, - sge.Like, - sge.RegexpLike, - sge.In, - sge.Between, - # Logical operations - sge.And, - sge.Or, - sge.Xor, - # Bitwise operations - sge.BitwiseAnd, - sge.BitwiseOr, - sge.BitwiseXor, - sge.BitwiseLeftShift, - sge.BitwiseRightShift, - sge.BitwiseNot, - # Other operations - sge.Is, - } - - @functools.singledispatchmethod - def compile_expression( - self, - expression: ex.Expression, - ) -> sge.Expression: - """Compiles BigFrames scalar expression into SQLGlot expression.""" - raise NotImplementedError(f"Unrecognized expression: {expression}") - - @compile_expression.register - def _(self, expr: ex.DerefOp) -> sge.Expression: - return sge.Column(this=sge.to_identifier(expr.id.sql, quoted=True)) - - @compile_expression.register - def _(self, expr: ex.ScalarConstantExpression) -> sge.Expression: - return sql.literal(expr.value, expr.dtype) - - @compile_expression.register - def _(self, expr: agg_exprs.WindowExpression) -> sge.Expression: - import bigframes.core.compile.sqlglot.aggregate_compiler as agg_compile - - return agg_compile.compile_analytic( - expr.analytic_expr, - expr.window, - ) - - @compile_expression.register - def _(self, expr: ex.OpExpression) -> sge.Expression: - inputs = tuple( - TypedExpr(self.compile_expression(sub_expr), sub_expr.output_type) - if not isinstance(sub_expr, ex.OmittedArg) - else TypedExpr(sge.Null(), None, is_omitted=True) - for sub_expr in expr.inputs - ) - return self.compile_row_op(expr.op, inputs) - - def compile_row_op( - self, op: ops.RowOp, inputs: typing.Sequence[TypedExpr] - ) -> sge.Expression: - impl = self._registry[op.name] - return impl(inputs, op) - - def register_unary_op( - self, - op_ref: typing.Union[ops.UnaryOp, type[ops.UnaryOp]], - pass_op: bool = False, - ): - """ - Decorator to register a unary op implementation. - - Args: - op_ref (UnaryOp or UnaryOp type): - Class or instance of operator that is implemented by the decorated function. - pass_op (bool): - Set to true if implementation takes the operator object as the last argument. - This is needed for parameterized ops where parameters are part of op object. - """ - key = typing.cast(str, op_ref.name) - - def decorator(impl: typing.Callable[..., sge.Expression]): - def normalized_impl(args: typing.Sequence[TypedExpr], op: ops.RowOp): - if pass_op: - return impl(args[0], op) - else: - return impl(args[0]) - - self._register(key, normalized_impl) - return impl - - return decorator - - def register_binary_op( - self, - op_ref: typing.Union[ops.BinaryOp, type[ops.BinaryOp]], - pass_op: bool = False, - ): - """ - Decorator to register a binary op implementation. - - Args: - op_ref (BinaryOp or BinaryOp type): - Class or instance of operator that is implemented by the decorated function. - pass_op (bool): - Set to true if implementation takes the operator object as the last argument. - This is needed for parameterized ops where parameters are part of op object. - """ - key = typing.cast(str, op_ref.name) - - def decorator(impl: typing.Callable[..., sge.Expression]): - def normalized_impl(args: typing.Sequence[TypedExpr], op: ops.RowOp): - left = self._add_parentheses(args[0]) - right = self._add_parentheses(args[1]) - if pass_op: - return impl(left, right, op) - else: - return impl(left, right) - - self._register(key, normalized_impl) - return impl - - return decorator - - def register_ternary_op( - self, op_ref: typing.Union[ops.TernaryOp, type[ops.TernaryOp]] - ): - """ - Decorator to register a ternary op implementation. - - Args: - op_ref (TernaryOp or TernaryOp type): - Class or instance of operator that is implemented by the decorated function. - """ - key = typing.cast(str, op_ref.name) - - def decorator(impl: typing.Callable[..., sge.Expression]): - def normalized_impl(args: typing.Sequence[TypedExpr], op: ops.RowOp): - return impl(args[0], args[1], args[2]) - - self._register(key, normalized_impl) - return impl - - return decorator - - def register_nary_op( - self, op_ref: typing.Union[ops.NaryOp, type[ops.NaryOp]], pass_op: bool = False - ): - """ - Decorator to register a nary op implementation. - - Args: - op_ref (NaryOp or NaryOp type): - Class or instance of operator that is implemented by the decorated function. - pass_op (bool): - Set to true if implementation takes the operator object as the last argument. - This is needed for parameterized ops where parameters are part of op object. - """ - key = typing.cast(str, op_ref.name) - - def decorator(impl: typing.Callable[..., sge.Expression]): - def normalized_impl(args: typing.Sequence[TypedExpr], op: ops.RowOp): - if pass_op: - return impl(*args, op=op) - else: - return impl(*args) - - self._register(key, normalized_impl) - return impl - - return decorator - - def _register( - self, - op_name: str, - impl: typing.Callable[[typing.Sequence[TypedExpr], ops.RowOp], sge.Expression], - ): - if op_name in self._registry: - raise ValueError(f"Operation name {op_name} already registered") - self._registry[op_name] = impl - - @classmethod - def _add_parentheses(cls, expr: TypedExpr) -> TypedExpr: - if type(expr.expr) in cls.SQLGLOT_NEEDS_PARENS: - return TypedExpr(sge.paren(expr.expr, copy=False), expr.dtype) - return expr - - -# Singleton compiler -expression_compiler = ExpressionCompiler() diff --git a/bigframes/core/compile/sqlglot/expressions/__init__.py b/bigframes/core/compile/sqlglot/expressions/__init__.py deleted file mode 100644 index f42d5c7d99e..00000000000 --- a/bigframes/core/compile/sqlglot/expressions/__init__.py +++ /dev/null @@ -1,21 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -"""Expression implementations for the SQLGlot-based compiler. - -This directory structure should reflect the same layout as the -`bigframes/operations` directory where the expressions are defined. - -Prefer a few ops per file to keep file sizes manageable for text editors and LLMs. -""" diff --git a/bigframes/core/compile/sqlglot/expressions/ai_ops.py b/bigframes/core/compile/sqlglot/expressions/ai_ops.py deleted file mode 100644 index d092f662f0f..00000000000 --- a/bigframes/core/compile/sqlglot/expressions/ai_ops.py +++ /dev/null @@ -1,161 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from __future__ import annotations - -from dataclasses import asdict -from typing import Any - -import bigframes_vendored.sqlglot.expressions as sge - -from bigframes import operations as ops -from bigframes.core.compile.sqlglot import expression_compiler -from bigframes.core.compile.sqlglot.expressions.typed_expr import TypedExpr - -register_nary_op = expression_compiler.expression_compiler.register_nary_op -register_binary_op = expression_compiler.expression_compiler.register_binary_op -register_unary_op = expression_compiler.expression_compiler.register_unary_op - - -@register_nary_op(ops.AIGenerate, pass_op=True) -def _(*exprs: TypedExpr, op: ops.AIGenerate) -> sge.Expression: - args = [_construct_prompt(exprs, op.prompt_context)] + _construct_named_args(op) - - return sge.func("AI.GENERATE", *args) - - -@register_nary_op(ops.AIGenerateBool, pass_op=True) -def _(*exprs: TypedExpr, op: ops.AIGenerateBool) -> sge.Expression: - args = [_construct_prompt(exprs, op.prompt_context)] + _construct_named_args(op) - - return sge.func("AI.GENERATE_BOOL", *args) - - -@register_nary_op(ops.AIGenerateInt, pass_op=True) -def _(*exprs: TypedExpr, op: ops.AIGenerateInt) -> sge.Expression: - args = [_construct_prompt(exprs, op.prompt_context)] + _construct_named_args(op) - - return sge.func("AI.GENERATE_INT", *args) - - -@register_nary_op(ops.AIGenerateDouble, pass_op=True) -def _(*exprs: TypedExpr, op: ops.AIGenerateDouble) -> sge.Expression: - args = [_construct_prompt(exprs, op.prompt_context)] + _construct_named_args(op) - - return sge.func("AI.GENERATE_DOUBLE", *args) - - -@register_unary_op(ops.AIEmbed, pass_op=True) -def _(expr: TypedExpr, op: ops.AIEmbed) -> sge.Expression: - args: list[Any] = [expr.expr] + _construct_named_args(op) - - return sge.func("AI.EMBED", *args) - - -@register_nary_op(ops.AIIf, pass_op=True) -def _(*exprs: TypedExpr, op: ops.AIIf) -> sge.Expression: - args = [_construct_prompt(exprs, op.prompt_context)] + _construct_named_args(op) - - return sge.func("AI.IF", *args) - - -@register_nary_op(ops.AIClassify, pass_op=True) -def _(*exprs: TypedExpr, op: ops.AIClassify) -> sge.Expression: - args = [ - _construct_prompt(exprs, op.prompt_context, param_name="input"), - ] + _construct_named_args(op) - - return sge.func("AI.CLASSIFY", *args) - - -@register_nary_op(ops.AIScore, pass_op=True) -def _(*exprs: TypedExpr, op: ops.AIScore) -> sge.Expression: - args = [_construct_prompt(exprs, op.prompt_context)] + _construct_named_args(op) - - return sge.func("AI.SCORE", *args) - - -@register_binary_op(ops.AISimilarity, pass_op=True) -def _(content1: TypedExpr, content2: TypedExpr, op: ops.AISimilarity) -> sge.Expression: - args = [ - sge.Kwarg(this="content1", expression=content1.expr), - sge.Kwarg(this="content2", expression=content2.expr), - ] + _construct_named_args(op) - - return sge.func("AI.SIMILARITY", *args) - - -def _construct_prompt( - exprs: tuple[TypedExpr, ...], - prompt_context: tuple[str | None, ...], - param_name: str = "prompt", -) -> sge.Kwarg: - prompt: list[str | sge.Expression] = [] - column_ref_idx = 0 - - for elem in prompt_context: - if elem is None: - prompt.append(exprs[column_ref_idx].expr) - column_ref_idx += 1 - else: - prompt.append(sge.Literal.string(elem)) - - # Need Struct rather than tuple syntax, as tuple syntax is ambiguous for single arg - return sge.Kwarg(this=param_name, expression=sge.Struct(expressions=prompt)) - - -def _construct_named_args(op: ops.ScalarOp) -> list[sge.Kwarg]: - args = [] - - op_args = asdict(op) - - for field, value in op_args.items(): - if value is None or field == "prompt_context": - continue - - if field == "categories": - category_literals = [sge.Literal.string(cat) for cat in value] - categories_arg = sge.Kwarg( - this="categories", expression=sge.array(*category_literals) - ) - args.append(categories_arg) - elif field == "model_params": - # model_params is a JSON string, so we need to use the JSON function to pass it as a named argument. - args.append( - sge.Kwarg( - this="model_params", - # sge.JSON requires the SQLGlot version to be at least 25.18.0 - # PARSE_JSON won't work as the function requires a JSON literal. - expression=sge.JSON(this=sge.Literal.string(value)), - ) - ) - elif field == "examples": - example_expressions = [] - for key, val in value: - if isinstance(val, (list, tuple)): - val_expr: sge.Array | sge.Literal = sge.array( - *[sge.Literal.string(v) for v in val] - ) - else: - val_expr = sge.Literal.string(val) - example_expressions.append( - sge.Tuple(expressions=[sge.Literal.string(key), val_expr]) - ) - args.append( - sge.Kwarg(this=field, expression=sge.array(*example_expressions)) - ) - else: - args.append(sge.Kwarg(this=field, expression=sge.convert(value))) - - return args diff --git a/bigframes/core/compile/sqlglot/expressions/array_ops.py b/bigframes/core/compile/sqlglot/expressions/array_ops.py deleted file mode 100644 index 56ffbf24cb3..00000000000 --- a/bigframes/core/compile/sqlglot/expressions/array_ops.py +++ /dev/null @@ -1,193 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from __future__ import annotations - -import typing - -import bigframes_vendored.sqlglot as sg -import bigframes_vendored.sqlglot.expressions as sge -import pandas as pd -import pyarrow as pa - -import bigframes.core.compile.sqlglot.expression_compiler as expression_compiler -import bigframes.dtypes as dtypes -from bigframes import operations as ops -from bigframes.core.compile.sqlglot.expressions.string_ops import ( - string_index, - string_slice, -) -from bigframes.core.compile.sqlglot.expressions.typed_expr import TypedExpr - -register_unary_op = expression_compiler.expression_compiler.register_unary_op -register_nary_op = expression_compiler.expression_compiler.register_nary_op - - -@register_unary_op(ops.GetItemOp, pass_op=True) -def _(expr: TypedExpr, op: ops.GetItemOp) -> sge.Expression: - if dtypes.is_struct_like(expr.dtype): - if isinstance(op.key, str): - name = op.key - else: - pa_type = typing.cast(pd.ArrowDtype, expr.dtype) - pa_struct_type = typing.cast(pa.StructType, pa_type.pyarrow_dtype) - name = pa_struct_type.field(op.key).name - - return sge.Column( - this=sge.to_identifier(name, quoted=True), - catalog=expr.expr, - ) - elif dtypes.is_array_like(expr.dtype): - return sge.Bracket( - this=expr.expr, - expressions=[sge.convert(op.key)], - safe=True, - offset=False, - ) - elif expr.dtype == dtypes.STRING_DTYPE: - return string_index(expr, typing.cast(int, op.key)) - else: - raise TypeError(f"Cannot subscript input of type {expr.dtype}") - - -@register_nary_op(ops.DynamicGetItemOp) # type: ignore[arg-type] -def _(left: TypedExpr, right: TypedExpr) -> sge.Expression: - if dtypes.is_array_like(left.dtype): - return sge.Bracket( - this=left.expr, - expressions=[right.expr], - safe=True, - offset=False, - ) - elif left.dtype == dtypes.STRING_DTYPE: - start_expr = sge.Add(this=right.expr, expression=sge.convert(1)) - sub_str = sge.Substring( - this=left.expr, - start=start_expr, - length=sge.convert(1), - ) - return sge.If( - this=sge.NEQ(this=sub_str, expression=sge.convert("")), - true=sub_str, - false=sge.Null(), - ) - else: - raise TypeError(f"Cannot dynamically subscript input of type {left.dtype}") - - -@register_unary_op(ops.ArrayReduceOp, pass_op=True) -def _(expr: TypedExpr, op: ops.ArrayReduceOp) -> sge.Expression: - sub_expr = sg.to_identifier("bf_arr_reduce_uid") - sub_type = dtypes.get_array_inner_type(expr.dtype) - - if op.aggregation.order_independent: - from bigframes.core.compile.sqlglot.aggregations import unary_compiler - - agg_expr = unary_compiler.compile(op.aggregation, TypedExpr(sub_expr, sub_type)) - else: - from bigframes.core.compile.sqlglot.aggregations import ordered_unary_compiler - - agg_expr = ordered_unary_compiler.compile( - op.aggregation, TypedExpr(sub_expr, sub_type) - ) - - return ( - sge.select(agg_expr) - .from_( - sge.Unnest( - expressions=[expr.expr], - alias=sge.TableAlias(columns=[sub_expr]), - ) - ) - .subquery() - ) - - -@register_unary_op(ops.ArrayMapOp, pass_op=True) -def _(expr: TypedExpr, op: ops.ArrayMapOp) -> sge.Expression: - sub_expr = sg.to_identifier("bf_arr_map_uid") - sub_type = dtypes.get_array_inner_type(expr.dtype) - - # TODO: Expression should be provided instead of invoking compiler manually - map_expr = expression_compiler.expression_compiler.compile_row_op( - op.map_op, (TypedExpr(sub_expr, sub_type),) - ) - - return sge.array( - sge.select(map_expr) - .from_( - sge.Unnest( - expressions=[expr.expr], - alias=sge.TableAlias(columns=[sub_expr]), - ) - ) - .subquery() - ) - - -@register_unary_op(ops.ArraySliceOp, pass_op=True) -def _(expr: TypedExpr, op: ops.ArraySliceOp) -> sge.Expression: - if expr.dtype == dtypes.STRING_DTYPE: - return string_slice(expr, op.start, op.stop) - else: - return _array_slice(expr, op) - - -@register_unary_op(ops.ArrayToStringOp, pass_op=True) -def _(expr: TypedExpr, op: ops.ArrayToStringOp) -> sge.Expression: - return sge.ArrayToString(this=expr.expr, expression=sge.convert(op.delimiter)) - - -@register_nary_op(ops.ToArrayOp) -def _(*exprs: TypedExpr) -> sge.Expression: - do_upcast_bool = any( - dtypes.is_numeric(expr.dtype, include_bool=False) for expr in exprs - ) - if do_upcast_bool: - sg_exprs = [_coerce_bool_to_int(expr) for expr in exprs] - else: - sg_exprs = [expr.expr for expr in exprs] - return sge.Array(expressions=sg_exprs) - - -def _coerce_bool_to_int(typed_expr: TypedExpr) -> sge.Expression: - """Coerce boolean expression to integer.""" - if typed_expr.dtype == dtypes.BOOL_DTYPE: - return sge.Cast(this=typed_expr.expr, to="INT64") - return typed_expr.expr - - -def _array_slice(expr: TypedExpr, op: ops.ArraySliceOp) -> sge.Expression: - # local name for each element in the array - el = sg.to_identifier("el") - # local name for the index in the array - slice_idx = sg.to_identifier("slice_idx") - - conditions: typing.List[sge.Predicate] = [slice_idx >= op.start] - if op.stop is not None: - conditions.append(slice_idx < op.stop) - - selected_elements = ( - sge.select(el) - .from_( - sge.Unnest( - expressions=[expr.expr], - alias=sge.TableAlias(columns=[el]), - offset=slice_idx, - ) - ) - .where(*conditions) - ) - - return sge.array(selected_elements) diff --git a/bigframes/core/compile/sqlglot/expressions/blob_ops.py b/bigframes/core/compile/sqlglot/expressions/blob_ops.py deleted file mode 100644 index 01b4f7a1617..00000000000 --- a/bigframes/core/compile/sqlglot/expressions/blob_ops.py +++ /dev/null @@ -1,52 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from __future__ import annotations - -import bigframes_vendored.sqlglot.expressions as sge - -import bigframes.core.compile.sqlglot.expression_compiler as expression_compiler -from bigframes import operations as ops -from bigframes.core.compile.sqlglot.expressions.typed_expr import TypedExpr - -register_unary_op = expression_compiler.expression_compiler.register_unary_op -register_binary_op = expression_compiler.expression_compiler.register_binary_op - - -@register_unary_op(ops.obj_fetch_metadata_op) -def _(expr: TypedExpr) -> sge.Expression: - return sge.func("OBJ.FETCH_METADATA", expr.expr) - - -@register_unary_op(ops.ObjGetAccessUrl, pass_op=True) -def _(expr: TypedExpr, op: ops.ObjGetAccessUrl) -> sge.Expression: - args = [expr.expr, sge.Literal.string(op.mode)] - if op.duration is not None: - args.append( - sge.Interval( - this=sge.Literal.number(op.duration), - unit=sge.Var(this="MICROSECOND"), - ) - ) - return sge.func("OBJ.GET_ACCESS_URL", *args) - - -@register_binary_op(ops.obj_make_ref_op) -def _(left: TypedExpr, right: TypedExpr) -> sge.Expression: - return sge.func("OBJ.MAKE_REF", left.expr, right.expr) - - -@register_unary_op(ops.obj_make_ref_json_op) -def _(expr: TypedExpr) -> sge.Expression: - return sge.func("OBJ.MAKE_REF", expr.expr) diff --git a/bigframes/core/compile/sqlglot/expressions/bool_ops.py b/bigframes/core/compile/sqlglot/expressions/bool_ops.py deleted file mode 100644 index 7e31646b295..00000000000 --- a/bigframes/core/compile/sqlglot/expressions/bool_ops.py +++ /dev/null @@ -1,86 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from __future__ import annotations - -import bigframes_vendored.sqlglot.expressions as sge - -import bigframes.core.compile.sqlglot.expression_compiler as expression_compiler -from bigframes import dtypes -from bigframes import operations as ops -from bigframes.core.compile.sqlglot import sql -from bigframes.core.compile.sqlglot.expressions.typed_expr import TypedExpr - -register_binary_op = expression_compiler.expression_compiler.register_binary_op - - -@register_binary_op(ops.and_op) -def _(left: TypedExpr, right: TypedExpr) -> sge.Expression: - # For AND, when we encounter a NULL value, we only know when the result is FALSE, - # otherwise the result is unknown (NULL). See: truth table at - # https://en.wikibooks.org/wiki/Structured_Query_Language/NULLs_and_the_Three_Valued_Logic#AND,_OR - if sql.is_null_literal(left.expr): - condition = sge.EQ(this=right.expr, expression=sge.convert(False)) - return sge.If(this=condition, true=right.expr, false=sge.null()) - if sql.is_null_literal(right.expr): - condition = sge.EQ(this=left.expr, expression=sge.convert(False)) - return sge.If(this=condition, true=left.expr, false=sge.null()) - - if left.dtype == dtypes.BOOL_DTYPE and right.dtype == dtypes.BOOL_DTYPE: - return sge.And(this=left.expr, expression=right.expr) - return sge.BitwiseAnd(this=left.expr, expression=right.expr) - - -@register_binary_op(ops.or_op) -def _(left: TypedExpr, right: TypedExpr) -> sge.Expression: - # For OR, when we encounter a NULL value, we only know when the result is TRUE, - # otherwise the result is unknown (NULL). See: truth table at - # https://en.wikibooks.org/wiki/Structured_Query_Language/NULLs_and_the_Three_Valued_Logic#AND,_OR - if sql.is_null_literal(left.expr): - condition = sge.EQ(this=right.expr, expression=sge.convert(True)) - return sge.If(this=condition, true=right.expr, false=sge.null()) - if sql.is_null_literal(right.expr): - condition = sge.EQ(this=left.expr, expression=sge.convert(True)) - return sge.If(this=condition, true=left.expr, false=sge.null()) - - if left.dtype == dtypes.BOOL_DTYPE and right.dtype == dtypes.BOOL_DTYPE: - return sge.Or(this=left.expr, expression=right.expr) - return sge.BitwiseOr(this=left.expr, expression=right.expr) - - -@register_binary_op(ops.xor_op) -def _(left: TypedExpr, right: TypedExpr) -> sge.Expression: - # For XOR, cast NULL operands to BOOLEAN to ensure the resulting expression - # maintains the boolean data type. - left_expr = left.expr - left_dtype = left.dtype - if sql.is_null_literal(left_expr): - left_expr = sge.Cast(this=sge.convert(None), to="BOOLEAN") - left_dtype = dtypes.BOOL_DTYPE - right_expr = right.expr - right_dtype = right.dtype - if sql.is_null_literal(right_expr): - right_expr = sge.Cast(this=sge.convert(None), to="BOOLEAN") - right_dtype = dtypes.BOOL_DTYPE - - if left_dtype == dtypes.BOOL_DTYPE and right_dtype == dtypes.BOOL_DTYPE: - return sge.Or( - this=sge.paren( - sge.And(this=left_expr, expression=sge.Not(this=right_expr)) - ), - expression=sge.paren( - sge.And(this=sge.Not(this=left_expr), expression=right_expr) - ), - ) - return sge.BitwiseXor(this=left.expr, expression=right.expr) diff --git a/bigframes/core/compile/sqlglot/expressions/common.py b/bigframes/core/compile/sqlglot/expressions/common.py deleted file mode 100644 index 067ca070edf..00000000000 --- a/bigframes/core/compile/sqlglot/expressions/common.py +++ /dev/null @@ -1,33 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from __future__ import annotations - -import bigframes_vendored.sqlglot.expressions as sge - - -def round_towards_zero(expr: sge.Expression): - """ - Round a float value to to an integer, always rounding towards zero. - - This is used to handle duration/timedelta emulation mostly. - """ - return sge.Cast( - this=sge.If( - this=sge.GT(this=expr, expression=sge.convert(0)), - true=sge.Floor(this=expr), - false=sge.Ceil(this=expr), - ), - to="INT64", - ) diff --git a/bigframes/core/compile/sqlglot/expressions/comparison_ops.py b/bigframes/core/compile/sqlglot/expressions/comparison_ops.py deleted file mode 100644 index a3331ce6fb5..00000000000 --- a/bigframes/core/compile/sqlglot/expressions/comparison_ops.py +++ /dev/null @@ -1,181 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from __future__ import annotations - -import typing - -import bigframes_vendored.sqlglot as sg -import bigframes_vendored.sqlglot.expressions as sge -import pandas as pd - -import bigframes.core.compile.sqlglot.expression_compiler as expression_compiler -from bigframes import dtypes -from bigframes import operations as ops -from bigframes.core.compile.sqlglot import sql -from bigframes.core.compile.sqlglot.expressions.typed_expr import TypedExpr - -register_unary_op = expression_compiler.expression_compiler.register_unary_op -register_binary_op = expression_compiler.expression_compiler.register_binary_op - - -@register_unary_op(ops.IsInOp, pass_op=True) -def _(expr: TypedExpr, op: ops.IsInOp) -> sge.Expression: - values = [] - # bools are not comparable to non-bools in SQL, so we need to cast the expression to INT64 if the values contain non-bools. - must_upcast_bools = dtypes.is_numeric(expr.dtype, include_bool=False) or any( - dtypes.is_numeric(dtypes.bigframes_type(type(value)), include_bool=False) - for value in op.values - if not _is_null(value) - ) - for value in op.values: - if _is_null(value): - continue - dtype = dtypes.bigframes_type(type(value)) - if dtypes.can_compare(expr.dtype, dtype): - if must_upcast_bools and dtype == dtypes.BOOL_DTYPE: - value = int(value) - values.append(sql.literal(value)) - - sg_lexpr: sge.Expression = expr.expr - if expr.dtype == dtypes.BOOL_DTYPE and must_upcast_bools: - sg_lexpr = sge.cast(expr.expr, "INT64") - - if op.match_nulls: - contains_nulls = any(_is_null(value) for value in op.values) - if contains_nulls: - if len(values) == 0: - return sge.Is(this=sg_lexpr, expression=sge.Null()) - return sge.Is(this=sg_lexpr, expression=sge.Null()) | sge.In( - this=sg_lexpr, expressions=values - ) - - if len(values) == 0: - return sge.convert(False) - - return sge.func( - "COALESCE", sge.In(this=sg_lexpr, expressions=values), sge.convert(False) - ) - - -@register_binary_op(ops.eq_op) -def _(left: TypedExpr, right: TypedExpr) -> sge.Expression: - if sql.is_null_literal(left.expr): - return sge.Is(this=right.expr, expression=sge.Null()) - if sql.is_null_literal(right.expr): - return sge.Is(this=left.expr, expression=sge.Null()) - left_expr = _coerce_bool_to_int(left) - right_expr = _coerce_bool_to_int(right) - return sge.EQ(this=left_expr, expression=right_expr) - - -@register_binary_op(ops.eq_null_match_op) -def _(left: TypedExpr, right: TypedExpr) -> sge.Expression: - left_expr = left.expr - if right.dtype != dtypes.BOOL_DTYPE: - left_expr = _coerce_bool_to_int(left) - - right_expr = right.expr - if left.dtype != dtypes.BOOL_DTYPE: - right_expr = _coerce_bool_to_int(right) - - sentinel = sge.convert("$NULL_SENTINEL$") - left_coalesce = sge.Coalesce( - this=sge.Cast(this=left_expr, to="STRING"), expressions=[sentinel] - ) - right_coalesce = sge.Coalesce( - this=sge.Cast(this=right_expr, to="STRING"), expressions=[sentinel] - ) - return sge.EQ(this=left_coalesce, expression=right_coalesce) - - -@register_binary_op(ops.ge_op) -def _(left: TypedExpr, right: TypedExpr) -> sge.Expression: - if sql.is_null_literal(left.expr) or sql.is_null_literal(right.expr): - return sge.null() - - left_expr = _coerce_bool_to_int(left) - right_expr = _coerce_bool_to_int(right) - return sge.GTE(this=left_expr, expression=right_expr) - - -@register_binary_op(ops.gt_op) -def _(left: TypedExpr, right: TypedExpr) -> sge.Expression: - if sql.is_null_literal(left.expr) or sql.is_null_literal(right.expr): - return sge.null() - - left_expr = _coerce_bool_to_int(left) - right_expr = _coerce_bool_to_int(right) - return sge.GT(this=left_expr, expression=right_expr) - - -@register_binary_op(ops.lt_op) -def _(left: TypedExpr, right: TypedExpr) -> sge.Expression: - if sql.is_null_literal(left.expr) or sql.is_null_literal(right.expr): - return sge.null() - - left_expr = _coerce_bool_to_int(left) - right_expr = _coerce_bool_to_int(right) - return sge.LT(this=left_expr, expression=right_expr) - - -@register_binary_op(ops.le_op) -def _(left: TypedExpr, right: TypedExpr) -> sge.Expression: - if sql.is_null_literal(left.expr) or sql.is_null_literal(right.expr): - return sge.null() - - left_expr = _coerce_bool_to_int(left) - right_expr = _coerce_bool_to_int(right) - return sge.LTE(this=left_expr, expression=right_expr) - - -@register_binary_op(ops.maximum_op) -def _(left: TypedExpr, right: TypedExpr) -> sge.Expression: - return sge.Greatest(expressions=[left.expr, right.expr]) - - -@register_binary_op(ops.minimum_op) -def _(left: TypedExpr, right: TypedExpr) -> sge.Expression: - return sge.Least(this=left.expr, expressions=right.expr) - - -@register_binary_op(ops.ne_op) -def _(left: TypedExpr, right: TypedExpr) -> sge.Expression: - if sql.is_null_literal(left.expr): - return sge.Is( - this=sge.paren(right.expr, copy=False), - expression=sg.not_(sge.Null(), copy=False), - ) - if sql.is_null_literal(right.expr): - return sge.Is( - this=sge.paren(left.expr, copy=False), - expression=sg.not_(sge.Null(), copy=False), - ) - - left_expr = _coerce_bool_to_int(left) - right_expr = _coerce_bool_to_int(right) - return sge.NEQ(this=left_expr, expression=right_expr) - - -# Helpers -def _is_null(value) -> bool: - # float NaN/inf should be treated as distinct from 'true' null values - return typing.cast(bool, pd.isna(value)) and not isinstance(value, float) - - -def _coerce_bool_to_int(typed_expr: TypedExpr) -> sge.Expression: - """Coerce boolean expression to integer.""" - if typed_expr.dtype == dtypes.BOOL_DTYPE: - return sge.Cast(this=typed_expr.expr, to="INT64") - return typed_expr.expr diff --git a/bigframes/core/compile/sqlglot/expressions/constants.py b/bigframes/core/compile/sqlglot/expressions/constants.py deleted file mode 100644 index 5ba4a72279f..00000000000 --- a/bigframes/core/compile/sqlglot/expressions/constants.py +++ /dev/null @@ -1,38 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import math - -import bigframes_vendored.sqlglot.expressions as sge - -_ZERO = sge.Cast(this=sge.convert(0), to="INT64") -_NAN = sge.Cast(this=sge.convert("NaN"), to="FLOAT64") -_INF = sge.Cast(this=sge.convert("Infinity"), to="FLOAT64") -_NEG_INF = sge.Cast(this=sge.convert("-Infinity"), to="FLOAT64") -_DAY_TO_MICROSECONDS = sge.convert(86400000000) - -# Approx Highest number you can pass in to EXP function and get a valid FLOAT64 result -# FLOAT64 has 11 exponent bits, so max values is about 2**(2**10) -# ln(2**(2**10)) == (2**10)*ln(2) ~= 709.78, so EXP(x) for x>709.78 will overflow. -_FLOAT64_EXP_BOUND = sge.convert(709.78) - -# The natural logarithm of the maximum value for a signed 64-bit integer. -# This is used to check for potential overflows in power operations involving integers -# by checking if `exponent * log(base)` exceeds this value. -_INT64_LOG_BOUND = math.log(2**63 - 1) - -# Represents the largest integer N where all integers from -N to N can be -# represented exactly as a float64. Float64 types have a 53-bit significand precision, -# so integers beyond this value may lose precision. -_FLOAT64_MAX_INT_PRECISION = 2**53 diff --git a/bigframes/core/compile/sqlglot/expressions/date_ops.py b/bigframes/core/compile/sqlglot/expressions/date_ops.py deleted file mode 100644 index 2410926887b..00000000000 --- a/bigframes/core/compile/sqlglot/expressions/date_ops.py +++ /dev/null @@ -1,72 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from __future__ import annotations - -import bigframes_vendored.sqlglot.expressions as sge - -import bigframes.core.compile.sqlglot.expression_compiler as expression_compiler -from bigframes import operations as ops -from bigframes.core.compile.sqlglot.expressions.typed_expr import TypedExpr - -register_unary_op = expression_compiler.expression_compiler.register_unary_op - - -@register_unary_op(ops.date_op) -def _(expr: TypedExpr) -> sge.Expression: - return sge.Date(this=expr.expr) - - -@register_unary_op(ops.day_op) -def _(expr: TypedExpr) -> sge.Expression: - return sge.Extract(this=sge.Identifier(this="DAY"), expression=expr.expr) - - -@register_unary_op(ops.dayofweek_op) -def _(expr: TypedExpr) -> sge.Expression: - return dayofweek_op_impl(expr) - - -@register_unary_op(ops.dayofyear_op) -def _(expr: TypedExpr) -> sge.Expression: - return sge.Extract(this=sge.Identifier(this="DAYOFYEAR"), expression=expr.expr) - - -@register_unary_op(ops.iso_day_op) -def _(expr: TypedExpr) -> sge.Expression: - # Plus 1 because iso day of week uses 1-based indexing - return dayofweek_op_impl(expr) + sge.convert(1) - - -@register_unary_op(ops.iso_week_op) -def _(expr: TypedExpr) -> sge.Expression: - return sge.Extract(this=sge.Identifier(this="ISOWEEK"), expression=expr.expr) - - -@register_unary_op(ops.iso_year_op) -def _(expr: TypedExpr) -> sge.Expression: - return sge.Extract(this=sge.Identifier(this="ISOYEAR"), expression=expr.expr) - - -# Helpers -def dayofweek_op_impl(expr: TypedExpr) -> sge.Expression: - # BigQuery SQL Extract(DAYOFWEEK) returns 1 for Sunday through 7 for Saturday. - # We want 0 for Monday through 6 for Sunday to be compatible with Pandas. - extract_expr = sge.Extract( - this=sge.Identifier(this="DAYOFWEEK"), expression=expr.expr - ) - return sge.Cast( - this=sge.Mod(this=extract_expr + sge.convert(5), expression=sge.convert(7)), - to="INT64", - ) diff --git a/bigframes/core/compile/sqlglot/expressions/datetime_ops.py b/bigframes/core/compile/sqlglot/expressions/datetime_ops.py deleted file mode 100644 index 399b3062273..00000000000 --- a/bigframes/core/compile/sqlglot/expressions/datetime_ops.py +++ /dev/null @@ -1,734 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from __future__ import annotations - -import bigframes_vendored.sqlglot.expressions as sge - -import bigframes.core.compile.sqlglot.expression_compiler as expression_compiler -from bigframes import dtypes -from bigframes import operations as ops -from bigframes.core.compile.constants import UNIT_TO_US_CONVERSION_FACTORS -from bigframes.core.compile.sqlglot import sqlglot_types -from bigframes.core.compile.sqlglot.expressions.typed_expr import TypedExpr - -register_unary_op = expression_compiler.expression_compiler.register_unary_op -register_binary_op = expression_compiler.expression_compiler.register_binary_op - - -@register_binary_op(ops.DatetimeToIntegerLabelOp, pass_op=True) -def datetime_to_integer_label_op( - x: TypedExpr, y: TypedExpr, op: ops.DatetimeToIntegerLabelOp -) -> sge.Expression: - # Determine if the frequency is fixed by checking if 'op.freq.nanos' is defined. - try: - return _datetime_to_integer_label_fixed_frequency(x, y, op) - except ValueError: - return _datetime_to_integer_label_non_fixed_frequency(x, y, op) - - -def _datetime_to_integer_label_fixed_frequency( - x: TypedExpr, y: TypedExpr, op: ops.DatetimeToIntegerLabelOp -) -> sge.Expression: - """ - This function handles fixed frequency conversions where the unit can range - from microseconds (us) to days. - """ - us = op.freq.nanos / 1000 - x_int = sge.func( - "UNIX_MICROS", - sge.Cast(this=x.expr, to=sge.DataType(this=sge.DataType.Type.TIMESTAMPTZ)), - ) - first = _calculate_resample_first(y, op.origin) # type: ignore - x_int_label = sge.Cast( - this=sge.Floor( - this=sge.func( - "IEEE_DIVIDE", - sge.Sub(this=x_int, expression=first), - sge.convert(int(us)), - ) - ), - to=sge.DataType.build("INT64"), - ) - return x_int_label - - -def _datetime_to_integer_label_non_fixed_frequency( - x: TypedExpr, y: TypedExpr, op: ops.DatetimeToIntegerLabelOp -) -> sge.Expression: - """ - This function handles non-fixed frequency conversions for units ranging - from weeks to years. - """ - rule_code = op.freq.rule_code - n = op.freq.n - if rule_code == "W-SUN": # Weekly - us = n * 7 * 24 * 60 * 60 * 1000000 - x_trunc = sge.TimestampTrunc(this=x.expr, unit=sge.Var(this="WEEK(MONDAY)")) - y_trunc = sge.TimestampTrunc(this=y.expr, unit=sge.Var(this="WEEK(MONDAY)")) - x_plus_6 = sge.Add( - this=x_trunc, - expression=sge.Interval( - this=sge.convert(6), unit=sge.Identifier(this="DAY") - ), - ) - y_plus_6 = sge.Add( - this=y_trunc, - expression=sge.Interval( - this=sge.convert(6), unit=sge.Identifier(this="DAY") - ), - ) - x_int = sge.func( - "UNIX_MICROS", - sge.Cast( - this=x_plus_6, to=sge.DataType(this=sge.DataType.Type.TIMESTAMPTZ) - ), - ) - first = sge.func( - "UNIX_MICROS", - sge.Cast( - this=y_plus_6, to=sge.DataType(this=sge.DataType.Type.TIMESTAMPTZ) - ), - ) - return sge.Case( - ifs=[ - sge.If( - this=sge.EQ(this=x_int, expression=first), - true=sge.convert(0), - ) - ], - default=sge.Add( - this=sge.Cast( - this=sge.Floor( - this=sge.func( - "IEEE_DIVIDE", - sge.Sub( - this=sge.Sub(this=x_int, expression=first), - expression=sge.convert(1), - ), - sge.convert(us), - ) - ), - to=sge.DataType.build("INT64"), - ), - expression=sge.convert(1), - ), - ) - elif rule_code in ("M", "ME"): # Monthly - x_int = sge.Paren( # type: ignore - this=sge.Add( - this=sge.Mul( - this=sge.Extract( - this=sge.Identifier(this="YEAR"), expression=x.expr - ), - expression=sge.convert(12), - ), - expression=sge.Sub( - this=sge.Extract( - this=sge.Identifier(this="MONTH"), expression=x.expr - ), - expression=sge.convert(1), - ), - ) - ) - first = sge.Paren( # type: ignore - this=sge.Add( - this=sge.Mul( - this=sge.Extract( - this=sge.Identifier(this="YEAR"), expression=y.expr - ), - expression=sge.convert(12), - ), - expression=sge.Sub( - this=sge.Extract( - this=sge.Identifier(this="MONTH"), expression=y.expr - ), - expression=sge.convert(1), - ), - ) - ) - return sge.Case( - ifs=[ - sge.If( - this=sge.EQ(this=x_int, expression=first), - true=sge.convert(0), - ) - ], - default=sge.Add( - this=sge.Cast( - this=sge.Floor( - this=sge.func( - "IEEE_DIVIDE", - sge.Sub( - this=sge.Sub(this=x_int, expression=first), - expression=sge.convert(1), - ), - sge.convert(n), - ) - ), - to=sge.DataType.build("INT64"), - ), - expression=sge.convert(1), - ), - ) - elif rule_code in ("Q-DEC", "QE-DEC"): # Quarterly - x_int = sge.Paren( # type: ignore - this=sge.Add( - this=sge.Mul( - this=sge.Extract( - this=sge.Identifier(this="YEAR"), expression=x.expr - ), - expression=sge.convert(4), - ), - expression=sge.Sub( - this=sge.Extract( - this=sge.Identifier(this="QUARTER"), expression=x.expr - ), - expression=sge.convert(1), - ), - ) - ) - first = sge.Paren( # type: ignore - this=sge.Add( - this=sge.Mul( - this=sge.Extract( - this=sge.Identifier(this="YEAR"), expression=y.expr - ), - expression=sge.convert(4), - ), - expression=sge.Sub( - this=sge.Extract( - this=sge.Identifier(this="QUARTER"), expression=y.expr - ), - expression=sge.convert(1), - ), - ) - ) - return sge.Case( - ifs=[ - sge.If( - this=sge.EQ(this=x_int, expression=first), - true=sge.convert(0), - ) - ], - default=sge.Add( - this=sge.Cast( - this=sge.Floor( - this=sge.func( - "IEEE_DIVIDE", - sge.Sub( - this=sge.Sub(this=x_int, expression=first), - expression=sge.convert(1), - ), - sge.convert(n), - ) - ), - to=sge.DataType.build("INT64"), - ), - expression=sge.convert(1), - ), - ) - elif rule_code in ("A-DEC", "Y-DEC", "YE-DEC"): # Yearly - x_int = sge.Extract(this=sge.Identifier(this="YEAR"), expression=x.expr) - first = sge.Extract(this=sge.Identifier(this="YEAR"), expression=y.expr) - return sge.Case( - ifs=[ - sge.If( - this=sge.EQ(this=x_int, expression=first), - true=sge.convert(0), - ) - ], - default=sge.Add( - this=sge.Cast( - this=sge.Floor( - this=sge.func( - "IEEE_DIVIDE", - sge.Sub( - this=sge.Sub(this=x_int, expression=first), - expression=sge.convert(1), - ), - sge.convert(n), - ) - ), - to=sge.DataType.build("INT64"), - ), - expression=sge.convert(1), - ), - ) - else: - raise ValueError(rule_code) - - -@register_unary_op(ops.FloorDtOp, pass_op=True) -def _(expr: TypedExpr, op: ops.FloorDtOp) -> sge.Expression: - pandas_to_bq_freq_map = { - "Y": "YEAR", - "Q": "QUARTER", - "M": "MONTH", - "W": "WEEK(MONDAY)", - "D": "DAY", - "h": "HOUR", - "min": "MINUTE", - "s": "SECOND", - "ms": "MILLISECOND", - "us": "MICROSECOND", - "ns": "NANOSECOND", - } - if op.freq not in pandas_to_bq_freq_map.keys(): - raise NotImplementedError( - f"Unsupported freq paramater: {op.freq}" - + " Supported freq parameters are: " - + ",".join(pandas_to_bq_freq_map.keys()) - ) - - bq_freq = pandas_to_bq_freq_map[op.freq] - return sge.TimestampTrunc(this=expr.expr, unit=sge.Identifier(this=bq_freq)) - - -def _calculate_resample_first(y: TypedExpr, origin: str) -> sge.Expression: - if origin == "epoch": - return sge.convert(0) - elif origin == "start_day": - return sge.func( - "UNIX_MICROS", - sge.Cast(this=sge.Cast(this=y.expr, to="DATE"), to="TIMESTAMP"), - ) - elif origin == "start": - return sge.func("UNIX_MICROS", sge.Cast(this=y.expr, to="TIMESTAMP")) - else: - raise ValueError(f"Origin {origin} not supported") - - -@register_unary_op(ops.hour_op) -def _(expr: TypedExpr) -> sge.Expression: - return sge.Extract(this=sge.Identifier(this="HOUR"), expression=expr.expr) - - -@register_unary_op(ops.minute_op) -def _(expr: TypedExpr) -> sge.Expression: - return sge.Extract(this=sge.Identifier(this="MINUTE"), expression=expr.expr) - - -@register_unary_op(ops.month_op) -def _(expr: TypedExpr) -> sge.Expression: - return sge.Extract(this=sge.Identifier(this="MONTH"), expression=expr.expr) - - -@register_unary_op(ops.normalize_op) -def _(expr: TypedExpr) -> sge.Expression: - return sge.TimestampTrunc(this=expr.expr, unit=sge.Identifier(this="DAY")) - - -@register_unary_op(ops.quarter_op) -def _(expr: TypedExpr) -> sge.Expression: - return sge.Extract(this=sge.Identifier(this="QUARTER"), expression=expr.expr) - - -@register_unary_op(ops.second_op) -def _(expr: TypedExpr) -> sge.Expression: - return sge.Extract(this=sge.Identifier(this="SECOND"), expression=expr.expr) - - -@register_unary_op(ops.StrftimeOp, pass_op=True) -def _(expr: TypedExpr, op: ops.StrftimeOp) -> sge.Expression: - func_name = "" - if expr.dtype == dtypes.DATE_DTYPE: - func_name = "FORMAT_DATE" - elif expr.dtype == dtypes.DATETIME_DTYPE: - func_name = "FORMAT_DATETIME" - elif expr.dtype == dtypes.TIME_DTYPE: - func_name = "FORMAT_TIME" - elif expr.dtype == dtypes.TIMESTAMP_DTYPE: - func_name = "FORMAT_TIMESTAMP" - - return sge.func(func_name, sge.convert(op.date_format), expr.expr) - - -@register_unary_op(ops.time_op) -def _(expr: TypedExpr) -> sge.Expression: - return sge.func("TIME", expr.expr) - - -@register_unary_op(ops.ToDatetimeOp, pass_op=True) -def _(expr: TypedExpr, op: ops.ToDatetimeOp) -> sge.Expression: - if op.format: - result = expr.expr - if expr.dtype == dtypes.STRING_DTYPE: - return sge.TryCast(this=result, to="DATETIME") - else: - result = sge.Cast(this=result, to="STRING") - result = sge.func( - "PARSE_TIMESTAMP", sge.convert(op.format), result, sge.convert("UTC") - ) - return sge.Cast(this=result, to="DATETIME") - - if expr.dtype == dtypes.TIMESTAMP_DTYPE: - return sge.func("DATETIME", expr.expr, sge.convert("UTC")) - - if expr.dtype in ( - dtypes.STRING_DTYPE, - dtypes.DATETIME_DTYPE, - dtypes.DATE_DTYPE, - ): - return sge.TryCast(this=expr.expr, to="DATETIME") - - value = expr.expr - unit = op.unit or "ns" - factor = UNIT_TO_US_CONVERSION_FACTORS[unit] - if factor != 1: - value = sge.Mul(this=value, expression=sge.convert(factor)) - value = sge.func("TRUNC", value) - return sge.func( - "DATETIME", - sge.func("TIMESTAMP_MICROS", sge.Cast(this=value, to="INT64")), - sge.convert("UTC"), - ) - - -@register_unary_op(ops.ToTimestampOp, pass_op=True) -def _(expr: TypedExpr, op: ops.ToTimestampOp) -> sge.Expression: - if op.format: - result = expr.expr - if expr.dtype != dtypes.STRING_DTYPE: - result = sge.Cast(this=result, to="STRING") - return sge.func( - "PARSE_TIMESTAMP", sge.convert(op.format), result, sge.convert("UTC") - ) - - if expr.dtype in ( - dtypes.STRING_DTYPE, - dtypes.DATETIME_DTYPE, - dtypes.TIMESTAMP_DTYPE, - dtypes.DATE_DTYPE, - ): - return sge.func("TIMESTAMP", expr.expr) - - value = expr.expr - unit = op.unit or "ns" - factor = UNIT_TO_US_CONVERSION_FACTORS[unit] - if factor != 1: - value = sge.Mul(this=value, expression=sge.convert(factor)) - value = sge.func("TRUNC", value) - return sge.Cast( - this=sge.func("TIMESTAMP_MICROS", sge.Cast(this=value, to="INT64")), - to="TIMESTAMP", - ) - - -@register_unary_op(ops.UnixMicros) -def _(expr: TypedExpr) -> sge.Expression: - return sge.func("UNIX_MICROS", expr.expr) - - -@register_unary_op(ops.UnixMillis) -def _(expr: TypedExpr) -> sge.Expression: - return sge.func("UNIX_MILLIS", expr.expr) - - -@register_unary_op(ops.UnixSeconds, pass_op=True) -def _(expr: TypedExpr, op: ops.UnixSeconds) -> sge.Expression: - return sge.func("UNIX_SECONDS", expr.expr) - - -@register_unary_op(ops.year_op) -def _(expr: TypedExpr) -> sge.Expression: - return sge.Extract(this=sge.Identifier(this="YEAR"), expression=expr.expr) - - -@register_binary_op(ops.IntegerLabelToDatetimeOp, pass_op=True) -def integer_label_to_datetime_op( - x: TypedExpr, y: TypedExpr, op: ops.IntegerLabelToDatetimeOp -) -> sge.Expression: - # Determine if the frequency is fixed by checking if 'op.freq.nanos' is defined. - try: - return _integer_label_to_datetime_op_fixed_frequency(x, y, op) - - except ValueError: - # Non-fixed frequency conversions for units ranging from weeks to years. - rule_code = op.freq.rule_code - - if rule_code == "W-SUN": - return _integer_label_to_datetime_op_weekly_freq(x, y, op) - - if rule_code in ("ME", "M"): - return _integer_label_to_datetime_op_monthly_freq(x, y, op) - - if rule_code in ("QE-DEC", "Q-DEC"): - return _integer_label_to_datetime_op_quarterly_freq(x, y, op) - - if rule_code in ("YE-DEC", "A-DEC", "Y-DEC"): - return _integer_label_to_datetime_op_yearly_freq(x, y, op) - - # If the rule_code is not recognized, raise an error here. - raise ValueError(f"Unsupported frequency rule code: {rule_code}") - - -def _integer_label_to_datetime_op_fixed_frequency( - x: TypedExpr, y: TypedExpr, op: ops.IntegerLabelToDatetimeOp -) -> sge.Expression: - """ - This function handles fixed frequency conversions where the unit can range - from microseconds (us) to days. - """ - us = op.freq.nanos / 1000 - first = _calculate_resample_first(y, op.origin) # type: ignore - x_label = sge.Cast( - this=sge.func( - "TIMESTAMP_MICROS", - sge.Cast( - this=sge.Add( - this=sge.Mul( - this=sge.Cast(this=x.expr, to="BIGNUMERIC"), - expression=sge.convert(int(us)), - ), - expression=sge.Cast(this=first, to="BIGNUMERIC"), - ), - to="INT64", - ), - ), - to=sqlglot_types.from_bigframes_dtype(y.dtype), - ) - return x_label - - -def _integer_label_to_datetime_op_weekly_freq( - x: TypedExpr, y: TypedExpr, op: ops.IntegerLabelToDatetimeOp -) -> sge.Expression: - n = op.freq.n - # Calculate microseconds for the weekly interval. - us = n * 7 * 24 * 60 * 60 * 1000000 - first = sge.func( - "UNIX_MICROS", - sge.Add( - this=sge.TimestampTrunc( - this=sge.Cast(this=y.expr, to="TIMESTAMP"), - unit=sge.Var(this="WEEK(MONDAY)"), - ), - expression=sge.Interval( - this=sge.convert(6), unit=sge.Identifier(this="DAY") - ), - ), - ) - return sge.Cast( - this=sge.func( - "TIMESTAMP_MICROS", - sge.Cast( - this=sge.Add( - this=sge.Mul( - this=sge.Cast(this=x.expr, to="BIGNUMERIC"), - expression=sge.convert(us), - ), - expression=sge.Cast(this=first, to="BIGNUMERIC"), - ), - to="INT64", - ), - ), - to=sqlglot_types.from_bigframes_dtype(y.dtype), - ) - - -def _integer_label_to_datetime_op_monthly_freq( - x: TypedExpr, y: TypedExpr, op: ops.IntegerLabelToDatetimeOp -) -> sge.Expression: - n = op.freq.n - one = sge.convert(1) - twelve = sge.convert(12) - first = sge.Sub( # type: ignore - this=sge.Add( - this=sge.Mul( - this=sge.Extract(this="YEAR", expression=y.expr), - expression=twelve, - ), - expression=sge.Extract(this="MONTH", expression=y.expr), - ), - expression=one, - ) - x_val = sge.Add( - this=sge.Mul(this=x.expr, expression=sge.convert(n)), expression=first - ) - year = sge.Cast( - this=sge.Floor(this=sge.func("IEEE_DIVIDE", x_val, twelve)), - to="INT64", - ) - month = sge.Add(this=sge.Mod(this=x_val, expression=twelve), expression=one) - - next_year = sge.Case( - ifs=[ - sge.If( - this=sge.EQ(this=month, expression=twelve), - true=sge.Add(this=year, expression=one), - ) - ], - default=year, - ) - next_month = sge.Case( - ifs=[sge.If(this=sge.EQ(this=month, expression=twelve), true=one)], - default=sge.Add(this=month, expression=one), - ) - next_month_date = sge.func( - "TIMESTAMP", - sge.Anonymous( - this="DATETIME", - expressions=[ - next_year, - next_month, - one, - sge.convert(0), - sge.convert(0), - sge.convert(0), - ], - ), - ) - x_label = sge.Sub( # type: ignore - this=next_month_date, expression=sge.Interval(this=one, unit="DAY") - ) - return sge.Cast(this=x_label, to=sqlglot_types.from_bigframes_dtype(y.dtype)) - - -def _integer_label_to_datetime_op_quarterly_freq( - x: TypedExpr, y: TypedExpr, op: ops.IntegerLabelToDatetimeOp -) -> sge.Expression: - n = op.freq.n - one = sge.convert(1) - three = sge.convert(3) - four = sge.convert(4) - twelve = sge.convert(12) - first = sge.Sub( # type: ignore - this=sge.Add( - this=sge.Mul( - this=sge.Extract(this="YEAR", expression=y.expr), - expression=four, - ), - expression=sge.Extract(this="QUARTER", expression=y.expr), - ), - expression=one, - ) - x_val = sge.Add( - this=sge.Mul(this=x.expr, expression=sge.convert(n)), expression=first - ) - year = sge.Cast( - this=sge.Floor(this=sge.func("IEEE_DIVIDE", x_val, four)), - to="INT64", - ) - month = sge.Mul( # type: ignore - this=sge.Paren( - this=sge.Add(this=sge.Mod(this=x_val, expression=four), expression=one) - ), - expression=three, - ) - - next_year = sge.Case( - ifs=[ - sge.If( - this=sge.EQ(this=month, expression=twelve), - true=sge.Add(this=year, expression=one), - ) - ], - default=year, - ) - next_month = sge.Case( - ifs=[sge.If(this=sge.EQ(this=month, expression=twelve), true=one)], - default=sge.Add(this=month, expression=one), - ) - next_month_date = sge.Anonymous( - this="DATETIME", - expressions=[ - next_year, - next_month, - one, - sge.convert(0), - sge.convert(0), - sge.convert(0), - ], - ) - x_label = sge.Sub( # type: ignore - this=next_month_date, expression=sge.Interval(this=one, unit="DAY") - ) - return sge.Cast(this=x_label, to=sqlglot_types.from_bigframes_dtype(y.dtype)) - - -def _integer_label_to_datetime_op_yearly_freq( - x: TypedExpr, y: TypedExpr, op: ops.IntegerLabelToDatetimeOp -) -> sge.Expression: - n = op.freq.n - one = sge.convert(1) - first = sge.Extract(this="YEAR", expression=y.expr) - x_val = sge.Add( - this=sge.Mul(this=x.expr, expression=sge.convert(n)), expression=first - ) - next_year = sge.Add(this=x_val, expression=one) # type: ignore - next_month_date = sge.func( - "TIMESTAMP", - sge.Anonymous( - this="DATETIME", - expressions=[ - next_year, - one, - one, - sge.convert(0), - sge.convert(0), - sge.convert(0), - ], - ), - ) - x_label = sge.Sub( # type: ignore - this=next_month_date, expression=sge.Interval(this=one, unit="DAY") - ) - return sge.Cast(this=x_label, to=sqlglot_types.from_bigframes_dtype(y.dtype)) - - -@register_binary_op(ops.timestamp_add_op) -def _(left: TypedExpr, right: TypedExpr) -> sge.Expression: - return sge.TimestampAdd( - this=left.expr, expression=right.expr, unit=sge.Var(this="MICROSECOND") - ) - - -@register_binary_op(ops.timestamp_sub_op) -def _(left: TypedExpr, right: TypedExpr) -> sge.Expression: - return sge.TimestampSub( - this=left.expr, expression=right.expr, unit=sge.Var(this="MICROSECOND") - ) - - -@register_binary_op(ops.timestamp_diff_op) -def _(left: TypedExpr, right: TypedExpr) -> sge.Expression: - return sge.TimestampDiff( - this=left.expr, expression=right.expr, unit=sge.Var(this="MICROSECOND") - ) - - -@register_binary_op(ops.date_add_op) -def _(left: TypedExpr, right: TypedExpr) -> sge.Expression: - left_expr = sge.Cast(this=left.expr, to="TIMESTAMP") - return sge.TimestampAdd( - this=left_expr, expression=right.expr, unit=sge.Var(this="MICROSECOND") - ) - - -@register_binary_op(ops.date_sub_op) -def _(left: TypedExpr, right: TypedExpr) -> sge.Expression: - left_expr = sge.Cast(this=left.expr, to="TIMESTAMP") - return sge.TimestampSub( - this=left_expr, expression=right.expr, unit=sge.Var(this="MICROSECOND") - ) - - -@register_binary_op(ops.date_diff_op) -def _(left: TypedExpr, right: TypedExpr) -> sge.Expression: - diff = sge.DateDiff(this=left.expr, expression=right.expr, unit=sge.Var(this="DAY")) - return sge.Mul( - this=diff, - expression=sge.convert(int(UNIT_TO_US_CONVERSION_FACTORS["d"])), - ) diff --git a/bigframes/core/compile/sqlglot/expressions/generic_ops.py b/bigframes/core/compile/sqlglot/expressions/generic_ops.py deleted file mode 100644 index 90c8270ae1d..00000000000 --- a/bigframes/core/compile/sqlglot/expressions/generic_ops.py +++ /dev/null @@ -1,320 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from __future__ import annotations - -import bigframes_vendored.sqlglot as sg -import bigframes_vendored.sqlglot.expressions as sge - -import bigframes.core.compile.sqlglot.expression_compiler as expression_compiler -from bigframes import dtypes -from bigframes import operations as ops -from bigframes.core.compile.sqlglot import sql, sqlglot_types -from bigframes.core.compile.sqlglot.expressions.typed_expr import TypedExpr - -register_unary_op = expression_compiler.expression_compiler.register_unary_op -register_binary_op = expression_compiler.expression_compiler.register_binary_op -register_nary_op = expression_compiler.expression_compiler.register_nary_op -register_ternary_op = expression_compiler.expression_compiler.register_ternary_op - - -@register_unary_op(ops.AsTypeOp, pass_op=True) -def _(expr: TypedExpr, op: ops.AsTypeOp) -> sge.Expression: - from_type = expr.dtype - to_type = op.to_type - sg_to_type = sqlglot_types.from_bigframes_dtype(to_type) - sg_expr = expr.expr - - if to_type == dtypes.INT_DTYPE: - result = _cast_to_int(expr, op) - if result is not None: - return result - - if to_type == dtypes.FLOAT_DTYPE and from_type == dtypes.BOOL_DTYPE: - sg_expr = sql.cast(sg_expr, "INT64", op.safe) - return sql.cast(sg_expr, sg_to_type, op.safe) - - if to_type == dtypes.BOOL_DTYPE: - if from_type == dtypes.BOOL_DTYPE: - return sg_expr - else: - return sge.NEQ(this=sg_expr, expression=sge.convert(0)) - - if to_type == dtypes.STRING_DTYPE: - sg_expr = sql.cast(sg_expr, sg_to_type, op.safe) - if from_type == dtypes.BOOL_DTYPE: - sg_expr = sge.func("INITCAP", sg_expr) - return sg_expr - - if dtypes.is_time_like(to_type) and from_type == dtypes.INT_DTYPE: - sg_expr = sge.func("TIMESTAMP_MICROS", sg_expr) - return sql.cast(sg_expr, sg_to_type, op.safe) - - return sql.cast(sg_expr, sg_to_type, op.safe) - - -@register_unary_op(ops.hash_op) -def _(expr: TypedExpr) -> sge.Expression: - return sge.func("FARM_FINGERPRINT", expr.expr) - - -@register_unary_op(ops.invert_op) -def _(expr: TypedExpr) -> sge.Expression: - if expr.dtype == dtypes.BOOL_DTYPE: - return sge.Not(this=sge.paren(expr.expr)) - return sge.BitwiseNot(this=sge.paren(expr.expr)) - - -@register_nary_op(ops.GoogleSqlScalarOp, pass_op=True) -def _(*operands: TypedExpr, op: ops.GoogleSqlScalarOp) -> sge.Expression: - args: list[sge.Expression] = [] - for i, operand in enumerate(operands): - if i < len(op.args): - arg_spec = op.args[i] - else: - assert op.args[-1].is_vararg, ( - f"Too many arguments, for {op.sql_name}, expected {len(op.args)}" - ) - arg_spec = op.args[-1] - if operand.is_omitted: - assert arg_spec.optional, "Argument omitted, but not optional" - continue - elif arg_spec.arg_name: - args.append(sge.Kwarg(this=arg_spec.arg_name, expression=operand.expr)) - else: - args.append(operand.expr) - return sg.func(op.sql_name, *args) - - -@register_nary_op(ops.SqlScalarOp, pass_op=True) -def _(*operands: TypedExpr, op: ops.SqlScalarOp) -> sge.Expression: - return sg.parse_one( - op.sql_template.format( - *[operand.expr.sql(dialect="bigquery") for operand in operands] - ), - dialect="bigquery", - ) - - -@register_unary_op(ops.isnull_op) -def _(expr: TypedExpr) -> sge.Expression: - return sge.Is(this=sge.paren(expr.expr), expression=sge.Null()) - - -@register_unary_op(ops.MapOp, pass_op=True) -def _(expr: TypedExpr, op: ops.MapOp) -> sge.Expression: - if len(op.mappings) == 0: - return expr.expr - - mappings = [ - ( - sql.literal(key, dtypes.is_compatible(key, expr.dtype)), - sql.literal(value, dtypes.is_compatible(value, expr.dtype)), - ) - for key, value in op.mappings - ] - return sge.Case( - ifs=[ - sge.If( - this=( - sge.EQ(this=expr.expr, expression=key) - if not sql.is_null_literal(key) - else sge.Is(this=expr.expr, expression=sge.Null()) - ), - true=value, - ) - for key, value in mappings - ], - default=expr.expr, - ) - - -@register_unary_op(ops.notnull_op) -def _(expr: TypedExpr) -> sge.Expression: - return sge.Is( - this=sge.paren(expr.expr, copy=False), - expression=sg.not_(sge.Null(), copy=False), - ) - - -@register_unary_op(ops.coerce_to_bool_op) -def _(expr: TypedExpr) -> sge.Expression: - from_type = expr.dtype - sg_expr = expr.expr - - if from_type == dtypes.BOOL_DTYPE: - res = sg_expr - elif dtypes.is_numeric(from_type): - res = sge.NEQ(this=sg_expr, expression=sge.convert(0)) - elif dtypes.is_string_like(from_type): - res = sge.GT(this=sge.func("LENGTH", sg_expr), expression=sge.convert(0)) - elif dtypes.is_array_like(from_type): - res = sge.GT(this=sge.func("ARRAY_LENGTH", sg_expr), expression=sge.convert(0)) - else: - res = sge.Is( - this=sge.paren(sg_expr, copy=False), - expression=sg.not_(sge.Null(), copy=False), - ) - - return sge.Coalesce(this=res, expressions=[sge.convert(False)]) - - -@register_ternary_op(ops.where_op) -def _( - original: TypedExpr, condition: TypedExpr, replacement: TypedExpr -) -> sge.Expression: - return sge.If(this=condition.expr, true=original.expr, false=replacement.expr) - - -@register_ternary_op(ops.clip_op) -def _( - original: TypedExpr, - lower: TypedExpr, - upper: TypedExpr, -) -> sge.Expression: - return sge.Greatest( - this=sge.Least(this=original.expr, expressions=[upper.expr]), - expressions=[lower.expr], - ) - - -@register_binary_op(ops.fillna_op) -def _(left: TypedExpr, right: TypedExpr) -> sge.Expression: - return sge.Coalesce(this=left.expr, expressions=[right.expr]) - - -def _get_remote_function_name(op): - routine_ref = op.function_def.routine_ref - # Quote project, dataset, and routine IDs to avoid keyword clashes. - return ( - f"`{routine_ref.project}`.`{routine_ref.dataset_id}`.`{routine_ref.routine_id}`" - ) - - -@register_nary_op(ops.RemoteFunctionOp, pass_op=True) -def _(*values: TypedExpr, op: ops.RemoteFunctionOp) -> sge.Expression: - return sge.func(_get_remote_function_name(op), *(value.expr for value in values)) - - -@register_nary_op(ops.case_when_op) -def _(*cases_and_outputs: TypedExpr) -> sge.Expression: - # Need to upcast BOOL to INT if any output is numeric - result_values = cases_and_outputs[1::2] - do_upcast_bool = any( - dtypes.is_numeric(t.dtype, include_bool=False) for t in result_values - ) - if do_upcast_bool: - result_values = tuple( - ( - TypedExpr( - sge.Cast(this=val.expr, to="INT64"), - dtypes.INT_DTYPE, - ) - if val.dtype == dtypes.BOOL_DTYPE - else val - ) - for val in result_values - ) - - return sge.Case( - ifs=[ - sge.If(this=predicate.expr, true=output.expr) - for predicate, output in zip(cases_and_outputs[::2], result_values) - ], - ) - - -@register_binary_op(ops.coalesce_op) -def _(left: TypedExpr, right: TypedExpr) -> sge.Expression: - if left.expr == right.expr: - return left.expr - return sge.Coalesce(this=left.expr, expressions=[right.expr]) - - -@register_nary_op(ops.RowKey) -def _(*values: TypedExpr) -> sge.Expression: - # All inputs into hash must be non-null or resulting hash will be null - str_values = [_convert_to_nonnull_string_sqlglot(value) for value in values] - - full_row_hash_p1 = sge.func("FARM_FINGERPRINT", sge.Concat(expressions=str_values)) - - # By modifying value slightly, we get another hash uncorrelated with the first - full_row_hash_p2 = sge.func( - "FARM_FINGERPRINT", sge.Concat(expressions=[*str_values, sge.convert("_")]) - ) - - # Used to disambiguate between identical rows (which will have identical hash) - random_hash_p3 = sge.func("RAND") - - return sge.Concat( - expressions=[ - sge.Cast(this=full_row_hash_p1, to="STRING"), - sge.Cast(this=full_row_hash_p2, to="STRING"), - sge.Cast(this=random_hash_p3, to="STRING"), - ] - ) - - -# Helper functions - - -def _cast_to_int(expr: TypedExpr, op: ops.AsTypeOp) -> sge.Expression | None: - from_type = expr.dtype - sg_expr = expr.expr - # Cannot cast DATETIME to INT directly so need to convert to TIMESTAMP first. - if from_type == dtypes.DATETIME_DTYPE: - sg_expr = sql.cast(sg_expr, "TIMESTAMP", op.safe) - return sge.func("UNIX_MICROS", sg_expr) - if from_type == dtypes.TIMESTAMP_DTYPE: - return sge.func("UNIX_MICROS", sg_expr) - if from_type == dtypes.TIME_DTYPE: - return sge.func( - "TIME_DIFF", - sql.cast(sg_expr, "TIME", op.safe), - sge.convert("00:00:00"), - "MICROSECOND", - ) - if from_type == dtypes.NUMERIC_DTYPE or from_type == dtypes.FLOAT_DTYPE: - sg_expr = sge.func("TRUNC", sg_expr) - return sql.cast(sg_expr, "INT64", op.safe) - return None - - -def _convert_to_nonnull_string_sqlglot(expr: TypedExpr) -> sge.Expression: - col_type = expr.dtype - sg_expr = expr.expr - - if col_type == dtypes.STRING_DTYPE: - result = sg_expr - elif ( - dtypes.is_numeric(col_type) - or dtypes.is_time_or_date_like(col_type) - or col_type == dtypes.BYTES_DTYPE - ): - result = sge.Cast(this=sg_expr, to="STRING") - elif col_type == dtypes.GEO_DTYPE: - result = sge.func("ST_ASTEXT", sg_expr) - else: - # TO_JSON_STRING works with all data types, but isn't the most efficient - # Needed for JSON, STRUCT and ARRAY datatypes - result = sge.func("TO_JSON_STRING", sg_expr) - - # Escape backslashes and use backslash as delineator - escaped = sge.func( - "REPLACE", - sge.func("COALESCE", result, sge.convert("")), - sge.convert("\\"), - sge.convert("\\\\"), - ) - return sge.Concat(expressions=[sge.convert("\\"), escaped]) diff --git a/bigframes/core/compile/sqlglot/expressions/geo_ops.py b/bigframes/core/compile/sqlglot/expressions/geo_ops.py deleted file mode 100644 index 8c353988ae3..00000000000 --- a/bigframes/core/compile/sqlglot/expressions/geo_ops.py +++ /dev/null @@ -1,112 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from __future__ import annotations - -import bigframes_vendored.sqlglot.expressions as sge - -import bigframes.core.compile.sqlglot.expression_compiler as expression_compiler -from bigframes import operations as ops -from bigframes.core.compile.sqlglot.expressions.typed_expr import TypedExpr - -register_unary_op = expression_compiler.expression_compiler.register_unary_op -register_binary_op = expression_compiler.expression_compiler.register_binary_op - - -@register_unary_op(ops.geo_st_astext_op) -def _(expr: TypedExpr) -> sge.Expression: - return sge.func("ST_ASTEXT", expr.expr) - - -@register_unary_op(ops.geo_st_boundary_op) -def _(expr: TypedExpr) -> sge.Expression: - return sge.func("ST_BOUNDARY", expr.expr) - - -@register_unary_op(ops.GeoStBufferOp, pass_op=True) -def _(expr: TypedExpr, op: ops.GeoStBufferOp) -> sge.Expression: - return sge.func( - "ST_BUFFER", - expr.expr, - sge.convert(op.buffer_radius), - sge.convert(op.num_seg_quarter_circle), - sge.convert(op.use_spheroid), - ) - - -@register_unary_op(ops.geo_st_convexhull_op) -def _(expr: TypedExpr) -> sge.Expression: - return sge.func("ST_CONVEXHULL", expr.expr) - - -@register_binary_op(ops.geo_st_geogpoint_op) -def _(left: TypedExpr, right: TypedExpr) -> sge.Expression: - return sge.func("ST_GEOGPOINT", left.expr, right.expr) - - -@register_unary_op(ops.geo_st_geogfromtext_op) -def _(expr: TypedExpr) -> sge.Expression: - return sge.func("SAFE.ST_GEOGFROMTEXT", expr.expr) - - -@register_unary_op(ops.geo_st_isclosed_op) -def _(expr: TypedExpr) -> sge.Expression: - return sge.func("ST_ISCLOSED", expr.expr) - - -@register_unary_op(ops.GeoStLengthOp, pass_op=True) -def _(expr: TypedExpr, op: ops.GeoStLengthOp) -> sge.Expression: - return sge.func("ST_LENGTH", expr.expr) - - -@register_unary_op(ops.GeoStRegionStatsOp, pass_op=True) -def _( - geography: TypedExpr, - op: ops.GeoStRegionStatsOp, -): - args = [geography.expr, sge.convert(op.raster_id)] - if op.band: - args.append(sge.Kwarg(this="band", expression=sge.convert(op.band))) - if op.include: - args.append(sge.Kwarg(this="include", expression=sge.convert(op.include))) - if op.options: - args.append( - sge.Kwarg(this="options", expression=sge.JSON(this=sge.convert(op.options))) - ) - return sge.func("ST_REGIONSTATS", *args) - - -@register_unary_op(ops.geo_x_op) -def _(expr: TypedExpr) -> sge.Expression: - return sge.func("ST_X", expr.expr) - - -@register_unary_op(ops.geo_y_op) -def _(expr: TypedExpr) -> sge.Expression: - return sge.func("ST_Y", expr.expr) - - -@register_binary_op(ops.GeoStDistanceOp, pass_op=True) -def _(left: TypedExpr, right: TypedExpr, op: ops.GeoStDistanceOp) -> sge.Expression: - return sge.func("ST_DISTANCE", left.expr, right.expr, sge.convert(op.use_spheroid)) - - -@register_binary_op(ops.geo_st_difference_op) -def _(left: TypedExpr, right: TypedExpr) -> sge.Expression: - return sge.func("ST_DIFFERENCE", left.expr, right.expr) - - -@register_binary_op(ops.geo_st_intersection_op) -def _(left: TypedExpr, right: TypedExpr) -> sge.Expression: - return sge.func("ST_INTERSECTION", left.expr, right.expr) diff --git a/bigframes/core/compile/sqlglot/expressions/json_ops.py b/bigframes/core/compile/sqlglot/expressions/json_ops.py deleted file mode 100644 index f9a92d3d7a6..00000000000 --- a/bigframes/core/compile/sqlglot/expressions/json_ops.py +++ /dev/null @@ -1,115 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from __future__ import annotations - -import bigframes_vendored.sqlglot.expressions as sge - -import bigframes.core.compile.sqlglot.expression_compiler as expression_compiler -from bigframes import dtypes -from bigframes import operations as ops -from bigframes.core.compile.sqlglot.expressions.typed_expr import TypedExpr - -register_unary_op = expression_compiler.expression_compiler.register_unary_op -register_binary_op = expression_compiler.expression_compiler.register_binary_op - - -@register_unary_op(ops.JSONExtract, pass_op=True) -def _(expr: TypedExpr, op: ops.JSONExtract) -> sge.Expression: - return sge.func("JSON_EXTRACT", expr.expr, sge.convert(op.json_path)) - - -@register_unary_op(ops.JSONExtractArray, pass_op=True) -def _(expr: TypedExpr, op: ops.JSONExtractArray) -> sge.Expression: - return sge.func("JSON_EXTRACT_ARRAY", expr.expr, sge.convert(op.json_path)) - - -@register_unary_op(ops.JSONExtractStringArray, pass_op=True) -def _(expr: TypedExpr, op: ops.JSONExtractStringArray) -> sge.Expression: - return sge.func("JSON_EXTRACT_STRING_ARRAY", expr.expr, sge.convert(op.json_path)) - - -@register_unary_op(ops.JSONKeys, pass_op=True) -def _(expr: TypedExpr, op: ops.JSONKeys) -> sge.Expression: - return sge.func("JSON_KEYS", expr.expr, sge.convert(op.max_depth)) - - -@register_unary_op(ops.JSONQuery, pass_op=True) -def _(expr: TypedExpr, op: ops.JSONQuery) -> sge.Expression: - return sge.func("JSON_QUERY", expr.expr, sge.convert(op.json_path)) - - -@register_unary_op(ops.JSONQueryArray, pass_op=True) -def _(expr: TypedExpr, op: ops.JSONQueryArray) -> sge.Expression: - return sge.func("JSON_QUERY_ARRAY", expr.expr, sge.convert(op.json_path)) - - -@register_unary_op(ops.JSONValue, pass_op=True) -def _(expr: TypedExpr, op: ops.JSONValue) -> sge.Expression: - return sge.func("JSON_VALUE", expr.expr, sge.convert(op.json_path)) - - -@register_unary_op(ops.JSONValueArray, pass_op=True) -def _(expr: TypedExpr, op: ops.JSONValueArray) -> sge.Expression: - return sge.func("JSON_VALUE_ARRAY", expr.expr, sge.convert(op.json_path)) - - -@register_unary_op(ops.ParseJSON) -def _(expr: TypedExpr) -> sge.Expression: - return sge.func("PARSE_JSON", expr.expr) - - -@register_unary_op(ops.ToJSON, pass_op=True) -def _(expr: TypedExpr, op: ops.ToJSON) -> sge.Expression: - from_type = expr.dtype - sg_expr = expr.expr - - # Parsing really should be a distinct operation from serialization, but - # this was the way things were intially launched. - if from_type == dtypes.STRING_DTYPE: - func_name = "SAFE.PARSE_JSON" if op.safe else "PARSE_JSON" - return sge.func(func_name, sg_expr) - else: - return sge.func( - "IF", sg_expr.is_(sge.Null()), sge.Null(), sge.func("TO_JSON", sg_expr) - ) - - -@register_unary_op(ops.JSONDecode, pass_op=True) -def _(expr: TypedExpr, op: ops.JSONDecode) -> sge.Expression: - to_type = op.to_type - sg_expr = expr.expr - func_name = "" - if to_type == dtypes.INT_DTYPE: - func_name = "INT64" - elif to_type == dtypes.FLOAT_DTYPE: - func_name = "FLOAT64" - elif to_type == dtypes.BOOL_DTYPE: - func_name = "BOOL" - elif to_type == dtypes.STRING_DTYPE: - func_name = "STRING" - if func_name: - func_name = "SAFE." + func_name if op.safe else func_name - return sge.func(func_name, sg_expr) - raise TypeError(f"Cannot cast from {dtypes.JSON_DTYPE} to {to_type}") - - -@register_unary_op(ops.ToJSONString) -def _(expr: TypedExpr) -> sge.Expression: - return sge.func("TO_JSON_STRING", expr.expr) - - -@register_binary_op(ops.JSONSet, pass_op=True) -def _(left: TypedExpr, right: TypedExpr, op) -> sge.Expression: - return sge.func("JSON_SET", left.expr, sge.convert(op.json_path), right.expr) diff --git a/bigframes/core/compile/sqlglot/expressions/numeric_ops.py b/bigframes/core/compile/sqlglot/expressions/numeric_ops.py deleted file mode 100644 index d62a93111be..00000000000 --- a/bigframes/core/compile/sqlglot/expressions/numeric_ops.py +++ /dev/null @@ -1,677 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from __future__ import annotations - -import bigframes_vendored.constants as bf_constants -import bigframes_vendored.sqlglot.expressions as sge - -import bigframes.core.compile.sqlglot.expression_compiler as expression_compiler -import bigframes.core.compile.sqlglot.expressions.constants as constants -from bigframes import dtypes -from bigframes import operations as ops -from bigframes.core.compile.sqlglot import sql -from bigframes.core.compile.sqlglot.expressions.common import round_towards_zero -from bigframes.core.compile.sqlglot.expressions.typed_expr import TypedExpr -from bigframes.operations import numeric_ops - -register_unary_op = expression_compiler.expression_compiler.register_unary_op -register_binary_op = expression_compiler.expression_compiler.register_binary_op - - -@register_unary_op(ops.abs_op) -def _(expr: TypedExpr) -> sge.Expression: - return sge.Abs(this=expr.expr) - - -@register_unary_op(ops.arccosh_op) -def _(expr: TypedExpr) -> sge.Expression: - return sge.Case( - ifs=[ - sge.If( - this=expr.expr < sge.convert(1), - true=constants._NAN, - ) - ], - default=sge.func("ACOSH", expr.expr), - ) - - -@register_unary_op(ops.arccos_op) -def _(expr: TypedExpr) -> sge.Expression: - return sge.Case( - ifs=[ - sge.If( - this=sge.func("ABS", expr.expr) > sge.convert(1), - true=constants._NAN, - ) - ], - default=sge.func("ACOS", expr.expr), - ) - - -@register_unary_op(ops.arcsin_op) -def _(expr: TypedExpr) -> sge.Expression: - return sge.Case( - ifs=[ - sge.If( - this=sge.func("ABS", expr.expr) > sge.convert(1), - true=constants._NAN, - ) - ], - default=sge.func("ASIN", expr.expr), - ) - - -@register_unary_op(ops.arcsinh_op) -def _(expr: TypedExpr) -> sge.Expression: - return sge.func("ASINH", expr.expr) - - -@register_binary_op(ops.arctan2_op) -def _(left: TypedExpr, right: TypedExpr) -> sge.Expression: - left_expr = _coerce_bool_to_int(left) - right_expr = _coerce_bool_to_int(right) - return sge.func("ATAN2", left_expr, right_expr) - - -@register_unary_op(ops.arctan_op) -def _(expr: TypedExpr) -> sge.Expression: - return sge.func("ATAN", expr.expr) - - -@register_unary_op(ops.arctanh_op) -def _(expr: TypedExpr) -> sge.Expression: - return sge.Case( - ifs=[ - # |x| < 1: The standard formula - sge.If( - this=sge.func("ABS", expr.expr) < sge.convert(1), - true=sge.func("ATANH", expr.expr), - ), - # |x| > 1: Returns NaN - sge.If( - this=sge.func("ABS", expr.expr) > sge.convert(1), - true=constants._NAN, - ), - ], - # |x| = 1: Returns Infinity or -Infinity - default=sge.Mul(this=constants._INF, expression=expr.expr), - ) - - -@register_unary_op(ops.ceil_op) -def _(expr: TypedExpr) -> sge.Expression: - return sge.Ceil(this=expr.expr) - - -@register_unary_op(ops.cos_op) -def _(expr: TypedExpr) -> sge.Expression: - return sge.func("COS", expr.expr) - - -@register_unary_op(ops.cosh_op) -def _(expr: TypedExpr) -> sge.Expression: - return sge.Case( - ifs=[ - sge.If( - this=sge.func("ABS", expr.expr) > sge.convert(709.78), - true=constants._INF, - ) - ], - default=sge.func("COSH", expr.expr), - ) - - -@register_binary_op(ops.cosine_distance_op) -def _(left: TypedExpr, right: TypedExpr) -> sge.Expression: - return sge.func("ML.DISTANCE", left.expr, right.expr, sge.Literal.string("COSINE")) - - -@register_unary_op(ops.exp_op) -def _(expr: TypedExpr) -> sge.Expression: - return sge.Case( - ifs=[ - sge.If( - this=expr.expr > constants._FLOAT64_EXP_BOUND, - true=constants._INF, - ) - ], - default=sge.func("EXP", expr.expr), - ) - - -@register_unary_op(ops.expm1_op) -def _(expr: TypedExpr) -> sge.Expression: - return sge.If( - this=expr.expr > constants._FLOAT64_EXP_BOUND, - true=constants._INF, - false=sge.func("EXP", expr.expr) - sge.convert(1), - ) - - -@register_unary_op(ops.floor_op) -def _(expr: TypedExpr) -> sge.Expression: - return sge.Floor(this=expr.expr) - - -@register_unary_op(ops.ln_op) -def _(expr: TypedExpr) -> sge.Expression: - return sge.Case( - ifs=[ - sge.If( - this=sge.Is(this=expr.expr, expression=sge.Null()), - true=sge.null(), - ), - # |x| > 0: The standard formula - sge.If( - this=expr.expr > sge.convert(0), - true=sge.Ln(this=expr.expr), - ), - # |x| < 0: Returns NaN - sge.If( - this=expr.expr < sge.convert(0), - true=constants._NAN, - ), - ], - # |x| == 0: Returns -Infinity - default=constants._NEG_INF, - ) - - -@register_unary_op(ops.log10_op) -def _(expr: TypedExpr) -> sge.Expression: - return sge.Case( - ifs=[ - sge.If( - this=sge.Is(this=expr.expr, expression=sge.Null()), - true=sge.null(), - ), - # |x| > 0: The standard formula - sge.If( - this=expr.expr > sge.convert(0), - true=sge.Log(this=sge.convert(10), expression=expr.expr), - ), - # |x| < 0: Returns NaN - sge.If( - this=expr.expr < sge.convert(0), - true=constants._NAN, - ), - ], - # |x| == 0: Returns -Infinity - default=constants._NEG_INF, - ) - - -@register_unary_op(ops.log1p_op) -def _(expr: TypedExpr) -> sge.Expression: - return sge.Case( - ifs=[ - sge.If( - this=sge.Is(this=expr.expr, expression=sge.Null()), - true=sge.null(), - ), - # Domain: |x| > -1 (The standard formula) - sge.If( - this=expr.expr > sge.convert(-1), - true=sge.Ln(this=sge.convert(1) + expr.expr), - ), - # Out of Domain: |x| < -1 (Returns NaN) - sge.If( - this=expr.expr < sge.convert(-1), - true=constants._NAN, - ), - ], - # Boundary: |x| == -1 (Returns -Infinity) - default=constants._NEG_INF, - ) - - -@register_unary_op(ops.neg_op) -def _(expr: TypedExpr) -> sge.Expression: - return sge.Neg(this=sge.paren(expr.expr)) - - -@register_unary_op(ops.pos_op) -def _(expr: TypedExpr) -> sge.Expression: - return expr.expr - - -@register_binary_op(ops.pow_op) -def _(left: TypedExpr, right: TypedExpr) -> sge.Expression: - left_expr = _coerce_bool_to_int(left) - right_expr = _coerce_bool_to_int(right) - if left.dtype == dtypes.INT_DTYPE and right.dtype == dtypes.INT_DTYPE: - return _int_pow_op(left_expr, right_expr) - else: - return _float_pow_op(left_expr, right_expr) - - -def _int_pow_op( - left_expr: sge.Expression, right_expr: sge.Expression -) -> sge.Expression: - if sql.is_null_literal(left_expr) or sql.is_null_literal(right_expr): - return sge.null() - - overflow_cond = sge.and_( - sge.NEQ(this=left_expr, expression=sge.convert(0)), - sge.GT( - this=sge.Mul( - this=right_expr, expression=sge.Ln(this=sge.Abs(this=left_expr)) - ), - expression=sge.convert(constants._INT64_LOG_BOUND), - ), - ) - - return sge.Case( - ifs=[ - sge.If( - this=overflow_cond, - true=sge.Null(), - ) - ], - default=sge.Cast( - this=sge.Pow( - this=sge.Cast( - this=left_expr, to=sge.DataType(this=sge.DataType.Type.DECIMAL) - ), - expression=right_expr, - ), - to="INT64", - ), - ) - - -def _float_pow_op( - left_expr: sge.Expression, right_expr: sge.Expression -) -> sge.Expression: - if sql.is_null_literal(left_expr) or sql.is_null_literal(right_expr): - return sge.null() - - # Most conditions here seek to prevent calling BQ POW with inputs that would generate errors. - # See: https://cloud.google.com/bigquery/docs/reference/standard-sql/mathematical_functions#pow - overflow_cond = sge.and_( - sge.NEQ(this=left_expr, expression=constants._ZERO), - sge.GT( - this=sge.Mul( - this=right_expr, expression=sge.Ln(this=sge.Abs(this=left_expr)) - ), - expression=constants._FLOAT64_EXP_BOUND, - ), - ) - - # Float64 lose integer precision beyond 2**53, beyond this insufficient precision to get parity - exp_too_big = sge.GT( - this=sge.Abs(this=right_expr), - expression=sge.convert(constants._FLOAT64_MAX_INT_PRECISION), - ) - # Treat very large exponents as +=INF - norm_exp = sge.Case( - ifs=[ - sge.If( - this=exp_too_big, - true=sge.Mul(this=constants._INF, expression=sge.Sign(this=right_expr)), - ) - ], - default=right_expr, - ) - - pow_result = sge.Pow(this=left_expr, expression=norm_exp) - - # This cast is dangerous, need to only excuted where y_val has been bounds-checked - # Ibis needs try_cast binding to bq safe_cast - exponent_is_whole = sge.EQ( - this=sge.Cast(this=right_expr, to="INT64"), expression=right_expr - ) - odd_exponent = sge.and_( - sge.LT(this=left_expr, expression=constants._ZERO), - sge.EQ( - this=sge.Mod( - this=sge.Cast(this=right_expr, to="INT64"), expression=sge.convert(2) - ), - expression=sge.convert(1), - ), - ) - infinite_base = sge.EQ(this=sge.Abs(this=left_expr), expression=constants._INF) - - return sge.Case( - ifs=[ - # Might be able to do something more clever with x_val==0 case - sge.If( - this=sge.EQ(this=right_expr, expression=constants._ZERO), - true=sge.convert(1), - ), - sge.If( - this=sge.EQ(this=left_expr, expression=sge.convert(1)), - true=sge.convert(1), - ), # Need to ignore exponent, even if it is NA - sge.If( - this=sge.and_( - sge.EQ(this=left_expr, expression=constants._ZERO), - sge.LT(this=right_expr, expression=constants._ZERO), - ), - true=constants._INF, - ), # This case would error POW function in BQ - sge.If(this=infinite_base, true=pow_result), - sge.If( - this=exp_too_big, true=pow_result - ), # Bigquery can actually handle the +-inf cases gracefully - sge.If( - this=sge.and_( - sge.LT(this=left_expr, expression=constants._ZERO), - sge.Not(this=sge.paren(exponent_is_whole)), - ), - true=constants._NAN, - ), - sge.If( - this=overflow_cond, - true=sge.Mul( - this=constants._INF, - expression=sge.Case( - ifs=[sge.If(this=odd_exponent, true=sge.convert(-1))], - default=sge.convert(1), - ), - ), - ), # finite overflows would cause bq to error - ], - default=pow_result, - ) - - -@register_unary_op(ops.sqrt_op) -def _(expr: TypedExpr) -> sge.Expression: - return sge.Case( - ifs=[ - sge.If( - this=expr.expr < sge.convert(0), - true=constants._NAN, - ) - ], - default=sge.Sqrt(this=expr.expr), - ) - - -@register_unary_op(ops.sin_op) -def _(expr: TypedExpr) -> sge.Expression: - return sge.func("SIN", expr.expr) - - -@register_unary_op(ops.sinh_op) -def _(expr: TypedExpr) -> sge.Expression: - return sge.Case( - ifs=[ - sge.If( - this=sge.func("ABS", expr.expr) > constants._FLOAT64_EXP_BOUND, - true=sge.func("SIGN", expr.expr) * constants._INF, - ) - ], - default=sge.func("SINH", expr.expr), - ) - - -@register_unary_op(ops.tan_op) -def _(expr: TypedExpr) -> sge.Expression: - return sge.func("TAN", expr.expr) - - -@register_unary_op(ops.tanh_op) -def _(expr: TypedExpr) -> sge.Expression: - return sge.func("TANH", expr.expr) - - -@register_binary_op(ops.add_op) -def _(left: TypedExpr, right: TypedExpr) -> sge.Expression: - if sql.is_null_literal(left.expr) or sql.is_null_literal(right.expr): - return sge.null() - - if left.dtype == dtypes.STRING_DTYPE and right.dtype == dtypes.STRING_DTYPE: - # String addition - return sge.Concat(expressions=[left.expr, right.expr]) - - if dtypes.is_numeric(left.dtype) and dtypes.is_numeric(right.dtype): - left_expr = _coerce_bool_to_int(left) - right_expr = _coerce_bool_to_int(right) - return sge.Add(this=left_expr, expression=right_expr) - - if ( - dtypes.is_time_or_date_like(left.dtype) - and right.dtype == dtypes.TIMEDELTA_DTYPE - ): - left_expr = _coerce_date_to_datetime(left) - return sge.TimestampAdd( - this=left_expr, expression=right.expr, unit=sge.Var(this="MICROSECOND") - ) - if ( - dtypes.is_time_or_date_like(right.dtype) - and left.dtype == dtypes.TIMEDELTA_DTYPE - ): - right_expr = _coerce_date_to_datetime(right) - return sge.TimestampAdd( - this=right_expr, expression=left.expr, unit=sge.Var(this="MICROSECOND") - ) - if left.dtype == dtypes.TIMEDELTA_DTYPE and right.dtype == dtypes.TIMEDELTA_DTYPE: - return sge.Add(this=left.expr, expression=right.expr) - - raise TypeError( - f"Cannot add type {left.dtype} and {right.dtype}. {bf_constants.FEEDBACK_LINK}" - ) - - -@register_binary_op(ops.div_op) -def _(left: TypedExpr, right: TypedExpr) -> sge.Expression: - if sql.is_null_literal(left.expr) or sql.is_null_literal(right.expr): - return sge.null() - - left_expr = _coerce_bool_to_int(left) - right_expr = _coerce_bool_to_int(right) - - result = sge.func("IEEE_DIVIDE", left_expr, right_expr) - if left.dtype == dtypes.TIMEDELTA_DTYPE and dtypes.is_numeric(right.dtype): - return round_towards_zero(result) - else: - return result - - -@register_binary_op(ops.euclidean_distance_op) -def _(left: TypedExpr, right: TypedExpr) -> sge.Expression: - return sge.func( - "ML.DISTANCE", left.expr, right.expr, sge.Literal.string("EUCLIDEAN") - ) - - -@register_binary_op(ops.floordiv_op) -def _(left: TypedExpr, right: TypedExpr) -> sge.Expression: - if sql.is_null_literal(left.expr) or sql.is_null_literal(right.expr): - return sge.null() - - left_expr = _coerce_bool_to_int(left) - right_expr = _coerce_bool_to_int(right) - - result: sge.Expression = sge.Cast( - this=sge.Floor(this=sge.func("IEEE_DIVIDE", left_expr, right_expr)), to="INT64" - ) - - # DIV(N, 0) will error in bigquery, but needs to return `0` for int, and - # `inf`` for float in BQ so we short-circuit in this case. - # Multiplying left by zero propogates nulls. - zero_result = ( - constants._INF - if (left.dtype == dtypes.FLOAT_DTYPE or right.dtype == dtypes.FLOAT_DTYPE) - else constants._ZERO - ) - result = sge.Case( - ifs=[ - sge.If( - this=sge.EQ(this=right_expr, expression=constants._ZERO), - true=zero_result * left_expr, - ) - ], - default=result, - ) - - if dtypes.is_numeric(right.dtype) and left.dtype == dtypes.TIMEDELTA_DTYPE: - result = round_towards_zero(sge.func("IEEE_DIVIDE", left_expr, right_expr)) - - return result - - -@register_binary_op(ops.manhattan_distance_op) -def _(left: TypedExpr, right: TypedExpr) -> sge.Expression: - return sge.func( - "ML.DISTANCE", left.expr, right.expr, sge.Literal.string("MANHATTAN") - ) - - -@register_binary_op(ops.mod_op) -def _(left: TypedExpr, right: TypedExpr) -> sge.Expression: - if sql.is_null_literal(left.expr) or sql.is_null_literal(right.expr): - return sge.null() - - # In BigQuery returned value has the same sign as X. In pandas, the sign of y is used, so we need to flip the result if sign(x) != sign(y) - left_expr = _coerce_bool_to_int(left) - right_expr = _coerce_bool_to_int(right) - - # BigQuery MOD function doesn't support float types, so cast to BIGNUMERIC - if left.dtype == dtypes.FLOAT_DTYPE or right.dtype == dtypes.FLOAT_DTYPE: - left_expr = sge.Cast(this=left_expr, to="BIGNUMERIC") - right_expr = sge.Cast(this=right_expr, to="BIGNUMERIC") - - # MOD(N, 0) will error in bigquery, but needs to return null - bq_mod = sge.Mod(this=left_expr, expression=right_expr) - zero_result = ( - constants._NAN - if (left.dtype == dtypes.FLOAT_DTYPE or right.dtype == dtypes.FLOAT_DTYPE) - else constants._ZERO - ) - return sge.Case( - ifs=[ - sge.If( - this=sge.EQ(this=right_expr, expression=constants._ZERO), - true=zero_result * left_expr, - ), - sge.If( - this=sge.and_( - right_expr < constants._ZERO, - bq_mod > constants._ZERO, - ), - true=right_expr + bq_mod, - ), - sge.If( - this=sge.and_( - right_expr > constants._ZERO, - bq_mod < constants._ZERO, - ), - true=right_expr + bq_mod, - ), - ], - default=bq_mod, - ) - - -@register_binary_op(ops.mul_op) -def _(left: TypedExpr, right: TypedExpr) -> sge.Expression: - if sql.is_null_literal(left.expr) or sql.is_null_literal(right.expr): - return sge.null() - - left_expr = _coerce_bool_to_int(left) - right_expr = _coerce_bool_to_int(right) - - result = sge.Mul(this=left_expr, expression=right_expr) - - if (dtypes.is_numeric(left.dtype) and right.dtype == dtypes.TIMEDELTA_DTYPE) or ( - left.dtype == dtypes.TIMEDELTA_DTYPE and dtypes.is_numeric(right.dtype) - ): - return round_towards_zero(result) - else: - return result - - -@register_binary_op(ops.round_op) -def _(expr: TypedExpr, n_digits: TypedExpr) -> sge.Expression: - rounded = sge.Round(this=expr.expr, decimals=n_digits.expr) - if expr.dtype == dtypes.INT_DTYPE: - return sge.Cast(this=rounded, to="INT64") - return rounded - - -@register_binary_op(ops.sub_op) -def _(left: TypedExpr, right: TypedExpr) -> sge.Expression: - if sql.is_null_literal(left.expr) or sql.is_null_literal(right.expr): - return sge.null() - - if dtypes.is_numeric(left.dtype) and dtypes.is_numeric(right.dtype): - left_expr = _coerce_bool_to_int(left) - right_expr = _coerce_bool_to_int(right) - return sge.Sub(this=left_expr, expression=right_expr) - - if ( - dtypes.is_time_or_date_like(left.dtype) - and right.dtype == dtypes.TIMEDELTA_DTYPE - ): - left_expr = _coerce_date_to_datetime(left) - return sge.TimestampSub( - this=left_expr, expression=right.expr, unit=sge.Var(this="MICROSECOND") - ) - if dtypes.is_time_or_date_like(left.dtype) and dtypes.is_time_or_date_like( - right.dtype - ): - left_expr = _coerce_date_to_datetime(left) - right_expr = _coerce_date_to_datetime(right) - return sge.TimestampDiff( - this=left_expr, expression=right_expr, unit=sge.Var(this="MICROSECOND") - ) - - if left.dtype == dtypes.TIMEDELTA_DTYPE and right.dtype == dtypes.TIMEDELTA_DTYPE: - return sge.Sub(this=left.expr, expression=right.expr) - - raise TypeError( - f"Cannot subtract type {left.dtype} and {right.dtype}. {bf_constants.FEEDBACK_LINK}" - ) - - -@register_binary_op(ops.unsafe_pow_op) -def _(left: TypedExpr, right: TypedExpr) -> sge.Expression: - """For internal use only - where domain and overflow checks are not needed.""" - left_expr = _coerce_bool_to_int(left) - right_expr = _coerce_bool_to_int(right) - return sge.Pow(this=left_expr, expression=right_expr) - - -@register_unary_op(numeric_ops.isnan_op) -def isnan(arg: TypedExpr) -> sge.Expression: - return sge.IsNan(this=arg.expr) - - -@register_unary_op(numeric_ops.isfinite_op) -def isfinite(arg: TypedExpr) -> sge.Expression: - return sge.Not( - this=sge.Or( - this=sge.IsInf(this=arg.expr), - expression=sge.IsNan(this=arg.expr), - ), - ) - - -def _coerce_bool_to_int(typed_expr: TypedExpr) -> sge.Expression: - """Coerce boolean expression to integer.""" - if typed_expr.dtype == dtypes.BOOL_DTYPE: - return sge.Cast(this=typed_expr.expr, to="INT64") - return typed_expr.expr - - -def _coerce_date_to_datetime(typed_expr: TypedExpr) -> sge.Expression: - """Coerce date expression to datetime.""" - if typed_expr.dtype == dtypes.DATE_DTYPE: - return sge.Cast(this=typed_expr.expr, to="DATETIME") - return typed_expr.expr diff --git a/bigframes/core/compile/sqlglot/expressions/string_ops.py b/bigframes/core/compile/sqlglot/expressions/string_ops.py deleted file mode 100644 index 65a13a45f8b..00000000000 --- a/bigframes/core/compile/sqlglot/expressions/string_ops.py +++ /dev/null @@ -1,383 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from __future__ import annotations - -import functools -import typing - -import bigframes_vendored.sqlglot.expressions as sge - -import bigframes.core.compile.sqlglot.expression_compiler as expression_compiler -from bigframes import dtypes -from bigframes import operations as ops -from bigframes.core.compile.sqlglot.expressions.typed_expr import TypedExpr - -register_unary_op = expression_compiler.expression_compiler.register_unary_op -register_binary_op = expression_compiler.expression_compiler.register_binary_op - - -@register_unary_op(ops.capitalize_op) -def _(expr: TypedExpr) -> sge.Expression: - return sge.Initcap(this=expr.expr, expression=sge.convert("")) - - -@register_unary_op(ops.StrContainsOp, pass_op=True) -def _(expr: TypedExpr, op: ops.StrContainsOp) -> sge.Expression: - return sge.Like(this=expr.expr, expression=sge.convert(f"%{op.pat}%")) - - -@register_unary_op(ops.StrContainsRegexOp, pass_op=True) -def _(expr: TypedExpr, op: ops.StrContainsRegexOp) -> sge.Expression: - return sge.RegexpLike(this=expr.expr, expression=sge.convert(op.pat)) - - -@register_unary_op(ops.StrExtractOp, pass_op=True) -def _(expr: TypedExpr, op: ops.StrExtractOp) -> sge.Expression: - # Cannot use BigQuery's REGEXP_EXTRACT function, which only allows one - # capturing group. - pat_expr = sge.convert(op.pat) - if op.n == 0: - pat_expr = sge.func("CONCAT", sge.convert(".*?("), pat_expr, sge.convert(").*")) - n = 1 - else: - pat_expr = sge.func("CONCAT", sge.convert(".*?"), pat_expr, sge.convert(".*")) - n = op.n - - rex_replace = sge.func("REGEXP_REPLACE", expr.expr, pat_expr, sge.convert(f"\\{n}")) - rex_contains = sge.func("REGEXP_CONTAINS", expr.expr, sge.convert(op.pat)) - return sge.If(this=rex_contains, true=rex_replace, false=sge.null()) - - -@register_unary_op(ops.StrFindOp, pass_op=True) -def _(expr: TypedExpr, op: ops.StrFindOp) -> sge.Expression: - # INSTR is 1-based, so we need to adjust the start position. - start = sge.convert(op.start + 1) if op.start is not None else sge.convert(1) - if op.end is not None: - # BigQuery's INSTR doesn't support `end`, so we need to use SUBSTR. - return sge.func( - "INSTR", - sge.Substring( - this=expr.expr, - start=start, - length=sge.convert(op.end - (op.start or 0)), - ), - sge.convert(op.substr), - ) - sge.convert(1) - else: - return sge.func( - "INSTR", - expr.expr, - sge.convert(op.substr), - start, - ) - sge.convert(1) - - -@register_unary_op(ops.StrLstripOp, pass_op=True) -def _(expr: TypedExpr, op: ops.StrLstripOp) -> sge.Expression: - return sge.func("LTRIM", expr.expr, sge.convert(op.to_strip)) - - -@register_unary_op(ops.StrRstripOp, pass_op=True) -def _(expr: TypedExpr, op: ops.StrRstripOp) -> sge.Expression: - return sge.func("RTRIM", expr.expr, sge.convert(op.to_strip)) - - -@register_unary_op(ops.StrPadOp, pass_op=True) -def _(expr: TypedExpr, op: ops.StrPadOp) -> sge.Expression: - expr_length = sge.Length(this=expr.expr) - fillchar = sge.convert(op.fillchar) - pad_length = sge.func("GREATEST", expr_length, sge.convert(op.length)) - - if op.side == "left": - return sge.func("LPAD", expr.expr, pad_length, fillchar) - elif op.side == "right": - return sge.func("RPAD", expr.expr, pad_length, fillchar) - else: # side == both - lpad_amount = ( - sge.Cast( - this=sge.Floor( - this=sge.func( - "SAFE_DIVIDE", - sge.Sub(this=pad_length, expression=expr_length), - sge.convert(2), - ) - ), - to="INT64", - ) - + expr_length - ) - return sge.func( - "RPAD", - sge.func("LPAD", expr.expr, lpad_amount, fillchar), - pad_length, - fillchar, - ) - - -@register_unary_op(ops.StrRepeatOp, pass_op=True) -def _(expr: TypedExpr, op: ops.StrRepeatOp) -> sge.Expression: - return sge.Repeat(this=expr.expr, times=sge.convert(op.repeats)) - - -@register_unary_op(ops.EndsWithOp, pass_op=True) -def _(expr: TypedExpr, op: ops.EndsWithOp) -> sge.Expression: - if not op.pat: - return sge.false() - - def to_endswith(pat: str) -> sge.Expression: - return sge.func("ENDS_WITH", expr.expr, sge.convert(pat)) - - conditions = [to_endswith(pat) for pat in op.pat] - return functools.reduce(lambda x, y: sge.Or(this=x, expression=y), conditions) - - -@register_unary_op(ops.isalnum_op) -def _(expr: TypedExpr) -> sge.Expression: - return sge.RegexpLike(this=expr.expr, expression=sge.convert(r"^(\p{N}|\p{L})+$")) - - -@register_unary_op(ops.isalpha_op) -def _(expr: TypedExpr) -> sge.Expression: - return sge.RegexpLike(this=expr.expr, expression=sge.convert(r"^\p{L}+$")) - - -@register_unary_op(ops.isdecimal_op) -def _(expr: TypedExpr) -> sge.Expression: - return sge.RegexpLike(this=expr.expr, expression=sge.convert(r"^(\p{Nd})+$")) - - -@register_unary_op(ops.isdigit_op) -def _(expr: TypedExpr) -> sge.Expression: - regexp_pattern = ( - r"^[\p{Nd}\x{00B9}\x{00B2}\x{00B3}\x{2070}\x{2074}-\x{2079}\x{2080}-\x{2089}]+$" - ) - return sge.RegexpLike(this=expr.expr, expression=sge.convert(regexp_pattern)) - - -@register_unary_op(ops.islower_op) -def _(expr: TypedExpr) -> sge.Expression: - return sge.And( - this=sge.EQ( - this=sge.Lower(this=expr.expr), - expression=expr.expr, - ), - expression=sge.NEQ( - this=sge.Upper(this=expr.expr), - expression=expr.expr, - ), - ) - - -@register_unary_op(ops.isnumeric_op) -def _(expr: TypedExpr) -> sge.Expression: - return sge.RegexpLike(this=expr.expr, expression=sge.convert(r"^\pN+$")) - - -@register_unary_op(ops.isspace_op) -def _(expr: TypedExpr) -> sge.Expression: - return sge.RegexpLike(this=expr.expr, expression=sge.convert(r"^\s+$")) - - -@register_unary_op(ops.isupper_op) -def _(expr: TypedExpr) -> sge.Expression: - return sge.And( - this=sge.EQ( - this=sge.Upper(this=expr.expr), - expression=expr.expr, - ), - expression=sge.NEQ( - this=sge.Lower(this=expr.expr), - expression=expr.expr, - ), - ) - - -@register_unary_op(ops.len_op) -def _(expr: TypedExpr) -> sge.Expression: - if dtypes.is_array_like(expr.dtype): - return sge.func("ARRAY_LENGTH", expr.expr) - - return sge.Length(this=expr.expr) - - -@register_unary_op(ops.lower_op) -def _(expr: TypedExpr) -> sge.Expression: - return sge.Lower(this=expr.expr) - - -@register_unary_op(ops.ReplaceStrOp, pass_op=True) -def _(expr: TypedExpr, op: ops.ReplaceStrOp) -> sge.Expression: - return sge.func("REPLACE", expr.expr, sge.convert(op.pat), sge.convert(op.repl)) - - -@register_unary_op(ops.RegexReplaceStrOp, pass_op=True) -def _(expr: TypedExpr, op: ops.RegexReplaceStrOp) -> sge.Expression: - return sge.func( - "REGEXP_REPLACE", expr.expr, sge.convert(op.pat), sge.convert(op.repl) - ) - - -@register_unary_op(ops.reverse_op) -def _(expr: TypedExpr) -> sge.Expression: - return sge.func("REVERSE", expr.expr) - - -@register_unary_op(ops.StartsWithOp, pass_op=True) -def _(expr: TypedExpr, op: ops.StartsWithOp) -> sge.Expression: - if not op.pat: - return sge.false() - - def to_startswith(pat: str) -> sge.Expression: - return sge.func("STARTS_WITH", expr.expr, sge.convert(pat)) - - conditions = [to_startswith(pat) for pat in op.pat] - return functools.reduce(lambda x, y: sge.Or(this=x, expression=y), conditions) - - -@register_unary_op(ops.StrStripOp, pass_op=True) -def _(expr: TypedExpr, op: ops.StrStripOp) -> sge.Expression: - return sge.Trim(this=expr.expr, expression=sge.convert(op.to_strip)) - - -@register_unary_op(ops.StringSplitOp, pass_op=True) -def _(expr: TypedExpr, op: ops.StringSplitOp) -> sge.Expression: - return sge.Split(this=expr.expr, expression=sge.convert(op.pat)) - - -@register_unary_op(ops.StrSliceOp, pass_op=True) -def _(expr: TypedExpr, op: ops.StrSliceOp) -> sge.Expression: - return string_slice(expr, op.start, op.end) - - -@register_unary_op(ops.upper_op) -def _(expr: TypedExpr) -> sge.Expression: - return sge.Upper(this=expr.expr) - - -@register_binary_op(ops.strconcat_op) -def _(left: TypedExpr, right: TypedExpr) -> sge.Expression: - return sge.Concat(expressions=[left.expr, right.expr]) - - -@register_unary_op(ops.ZfillOp, pass_op=True) -def _(expr: TypedExpr, op: ops.ZfillOp) -> sge.Expression: - length_expr = sge.Greatest( - expressions=[sge.Length(this=expr.expr), sge.convert(op.width)] - ) - return sge.Case( - ifs=[ - sge.If( - this=sge.func( - "STARTS_WITH", - expr.expr, - sge.convert("-"), - ), - true=sge.Concat( - expressions=[ - sge.convert("-"), - sge.func( - "LPAD", - sge.Substring(this=expr.expr, start=sge.convert(2)), - length_expr - 1, - sge.convert("0"), - ), - ] - ), - ) - ], - default=sge.func("LPAD", expr.expr, length_expr, sge.convert("0")), - ) - - -def string_index(expr: TypedExpr, index: int) -> sge.Expression: - sub_str = sge.Substring( - this=expr.expr, - start=sge.convert(index + 1), - length=sge.convert(1), - ) - return sge.If( - this=sge.NEQ(this=sub_str, expression=sge.convert("")), - true=sub_str, - false=sge.Null(), - ) - - -def string_slice( - expr: TypedExpr, op_start: typing.Optional[int], op_end: typing.Optional[int] -) -> sge.Expression: - column_length = sge.Length(this=expr.expr) - if op_start is None: - start = 0 - else: - start = op_start - - start_expr = sge.convert(start) if start < 0 else sge.convert(start + 1) - length_expr: typing.Optional[sge.Expression] - if op_end is None: - length_expr = None - elif op_end < 0: - if start < 0: - start_expr = sge.Greatest( - expressions=[ - sge.convert(1), - column_length + sge.convert(start + 1), - ] - ) - length_expr = sge.Greatest( - expressions=[ - sge.convert(0), - column_length + sge.convert(op_end), - ] - ) - sge.Greatest( - expressions=[ - sge.convert(0), - column_length + sge.convert(start), - ] - ) - else: - length_expr = sge.Greatest( - expressions=[ - sge.convert(0), - column_length + sge.convert(op_end - start), - ] - ) - else: # op.end >= 0 - if start < 0: - start_expr = sge.Greatest( - expressions=[ - sge.convert(1), - column_length + sge.convert(start + 1), - ] - ) - length_expr = sge.Greatest( - expressions=[ - sge.convert(0), - sge.convert(op_end) - - sge.Greatest( - expressions=[ - sge.convert(0), - column_length + sge.convert(start), - ] - ), - ] - ) - else: - length_expr = sge.convert(op_end - start) - - return sge.Substring( - this=expr.expr, - start=start_expr, - length=length_expr, - ) diff --git a/bigframes/core/compile/sqlglot/expressions/struct_ops.py b/bigframes/core/compile/sqlglot/expressions/struct_ops.py deleted file mode 100644 index 01022210182..00000000000 --- a/bigframes/core/compile/sqlglot/expressions/struct_ops.py +++ /dev/null @@ -1,53 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from __future__ import annotations - -import typing - -import bigframes_vendored.sqlglot.expressions as sge -import pandas as pd -import pyarrow as pa - -import bigframes.core.compile.sqlglot.expression_compiler as expression_compiler -from bigframes import operations as ops -from bigframes.core.compile.sqlglot.expressions.typed_expr import TypedExpr - -register_nary_op = expression_compiler.expression_compiler.register_nary_op -register_unary_op = expression_compiler.expression_compiler.register_unary_op - - -@register_unary_op(ops.StructFieldOp, pass_op=True) -def _(expr: TypedExpr, op: ops.StructFieldOp) -> sge.Expression: - if isinstance(op.name_or_index, str): - name = op.name_or_index - else: - pa_type = typing.cast(pd.ArrowDtype, expr.dtype) - pa_struct_type = typing.cast(pa.StructType, pa_type.pyarrow_dtype) - name = pa_struct_type.field(op.name_or_index).name - - return sge.Column( - this=sge.to_identifier(name, quoted=True), - catalog=expr.expr, - ) - - -@register_nary_op(ops.StructOp, pass_op=True) -def _(*exprs: TypedExpr, op: ops.StructOp) -> sge.Struct: - return sge.Struct( - expressions=[ - sge.PropertyEQ(this=sge.to_identifier(col), expression=expr.expr) - for col, expr in zip(op.column_names, exprs) - ] - ) diff --git a/bigframes/core/compile/sqlglot/expressions/timedelta_ops.py b/bigframes/core/compile/sqlglot/expressions/timedelta_ops.py deleted file mode 100644 index fbc982829ca..00000000000 --- a/bigframes/core/compile/sqlglot/expressions/timedelta_ops.py +++ /dev/null @@ -1,44 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from __future__ import annotations - -import bigframes_vendored.sqlglot.expressions as sge - -import bigframes.core.compile.sqlglot.expression_compiler as expression_compiler -from bigframes import dtypes -from bigframes import operations as ops -from bigframes.core.compile.constants import UNIT_TO_US_CONVERSION_FACTORS -from bigframes.core.compile.sqlglot.expressions.typed_expr import TypedExpr - -register_unary_op = expression_compiler.expression_compiler.register_unary_op - - -@register_unary_op(ops.timedelta_floor_op) -def _(expr: TypedExpr) -> sge.Expression: - return sge.Floor(this=expr.expr) - - -@register_unary_op(ops.ToTimedeltaOp, pass_op=True) -def _(expr: TypedExpr, op: ops.ToTimedeltaOp) -> sge.Expression: - value = expr.expr - if expr.dtype == dtypes.TIMEDELTA_DTYPE: - return value - - factor = UNIT_TO_US_CONVERSION_FACTORS[op.unit] - if factor != 1: - value = sge.Mul(this=value, expression=sge.convert(factor)) - if expr.dtype == dtypes.FLOAT_DTYPE: - value = sge.Cast(this=sge.Floor(this=value), to=sge.DataType(this="INT64")) - return value diff --git a/bigframes/core/compile/sqlglot/expressions/typed_expr.py b/bigframes/core/compile/sqlglot/expressions/typed_expr.py deleted file mode 100644 index d8c38c2e718..00000000000 --- a/bigframes/core/compile/sqlglot/expressions/typed_expr.py +++ /dev/null @@ -1,30 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import dataclasses - -import bigframes_vendored.sqlglot.expressions as sge - -from bigframes import dtypes - - -@dataclasses.dataclass(frozen=True) -class TypedExpr: - """SQLGlot expression with type.""" - - expr: sge.Expression - dtype: dtypes.ExpressionType - - # kludge to support optional args in argument lists - is_omitted: bool = False diff --git a/bigframes/core/compile/sqlglot/sql/__init__.py b/bigframes/core/compile/sqlglot/sql/__init__.py deleted file mode 100644 index 751c3cfc3a5..00000000000 --- a/bigframes/core/compile/sqlglot/sql/__init__.py +++ /dev/null @@ -1,41 +0,0 @@ -# Copyright 2026 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.core.compile.sqlglot.sql.base import ( - cast, - identifier, - is_null_literal, - literal, - table, - to_sql, -) -from bigframes.core.compile.sqlglot.sql.ddl import create_external_table, load_data -from bigframes.core.compile.sqlglot.sql.dml import insert, replace - -__all__ = [ - # From base.py - "cast", - "identifier", - "is_null_literal", - "literal", - "table", - "to_sql", - # From ddl.py - "create_external_table", - "load_data", - # From dml.py - "insert", - "replace", -] diff --git a/bigframes/core/compile/sqlglot/sql/base.py b/bigframes/core/compile/sqlglot/sql/base.py deleted file mode 100644 index f77dcbee4d9..00000000000 --- a/bigframes/core/compile/sqlglot/sql/base.py +++ /dev/null @@ -1,147 +0,0 @@ -# Copyright 2026 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 - -import bigframes_vendored.sqlglot as sg -import bigframes_vendored.sqlglot.expressions as sge -import numpy as np -import pandas as pd -import pyarrow as pa -from google.cloud import bigquery - -import bigframes.core.compile.sqlglot.sqlglot_types as sgt -from bigframes import dtypes -from bigframes.core import utils -from bigframes.core.compile.sqlglot.expressions import constants - -# shapely.wkt.dumps was moved to shapely.io.to_wkt in 2.0. -try: - from shapely.io import to_wkt # type: ignore -except ImportError: - from shapely.wkt import dumps # type: ignore - - to_wkt = dumps - - -QUOTED: bool = True -"""Whether to quote identifiers in the generated SQL.""" - -PRETTY: bool = True -"""Whether to pretty-print the generated SQL.""" - -DIALECT = sg.dialects.bigquery.BigQuery -"""The SQL dialect used for generation.""" - - -def to_sql(expr: sge.Expression) -> str: - """Generate SQL string from the given expression.""" - return expr.sql(dialect=DIALECT, pretty=PRETTY) - - -def identifier(id: str) -> sge.Identifier: - """Return a string representing column reference in a SQL.""" - return sge.to_identifier(id, quoted=QUOTED) - - -def literal(value: typing.Any, dtype: dtypes.Dtype | None = None) -> sge.Expression: - """Return a string representing column reference in a SQL.""" - if dtype is None: - dtype = dtypes.infer_literal_type(value) - - sqlglot_type = sgt.from_bigframes_dtype(dtype) if dtype else None - if sqlglot_type is None: - if not pd.isna(value): - raise ValueError(f"Cannot infer SQLGlot type from None dtype: {value}") - return sge.Null() - - if value is None: - if str(sqlglot_type).upper() == "NULL": - return sge.Null() - return cast(sge.Null(), sqlglot_type) - if dtypes.is_struct_like(dtype): - items = [ - literal(value=value[field_name], dtype=field_dtype).as_( - field_name, quoted=True - ) - for field_name, field_dtype in dtypes.get_struct_fields(dtype).items() - ] - return sge.Struct.from_arg_list(items) - elif dtypes.is_array_like(dtype): - value_type = dtypes.get_array_inner_type(dtype) - values = sge.Array( - expressions=[literal(value=v, dtype=value_type) for v in value] - ) - return values if len(value) > 0 else cast(values, sqlglot_type) - elif dtype == dtypes.FLOAT_DTYPE: - if pd.isna(value): - if isinstance(value, (float, np.floating)) and np.isnan(value): - return constants._NAN - return cast(sge.Null(), sqlglot_type) - if np.isinf(value): - return constants._INF if value > 0 else constants._NEG_INF - return sge.convert(value) - elif pd.isna(value) or (isinstance(value, pa.Scalar) and not value.is_valid): - return cast(sge.Null(), sqlglot_type) - elif dtype == dtypes.JSON_DTYPE: - return sge.ParseJSON(this=sge.convert(str(value))) - elif dtype == dtypes.BYTES_DTYPE: - return cast(str(value), sqlglot_type) - elif dtypes.is_time_like(dtype): - if isinstance(value, str): - return cast(sge.convert(value), sqlglot_type) - if isinstance(value, np.generic): - value = value.item() - return cast(sge.convert(value.isoformat()), sqlglot_type) - elif dtype in (dtypes.NUMERIC_DTYPE, dtypes.BIGNUMERIC_DTYPE): - return cast(sge.convert(value), sqlglot_type) - elif dtypes.is_geo_like(dtype): - wkt = value if isinstance(value, str) else to_wkt(value) - return sge.func("ST_GEOGFROMTEXT", sge.convert(wkt)) - elif dtype == dtypes.TIMEDELTA_DTYPE: - return sge.convert(utils.timedelta_to_micros(value)) - else: - if isinstance(value, np.generic): - value = value.item() - if isinstance(value, pa.Scalar): - value = value.as_py() - return sge.convert(value) - - -def cast(arg: typing.Any, to: str, safe: bool = False) -> sge.Cast | sge.TryCast: - """Return a SQL expression that casts the given argument to the specified type.""" - if safe: - return sge.TryCast(this=arg, to=to) - else: - return sge.Cast(this=arg, to=to) - - -def table(table: bigquery.TableReference) -> sge.Table: - """Return a SQLGlot Table expression representing the given BigQuery table reference.""" - return sge.Table( - this=sge.to_identifier(table.table_id, quoted=True), - db=sge.to_identifier(table.dataset_id, quoted=True), - catalog=sge.to_identifier(table.project, quoted=True), - ) - - -def is_null_literal(expr: sge.Expression) -> bool: - """Checks if the given expression is a NULL literal.""" - if isinstance(expr, sge.Null): - return True - if isinstance(expr, sge.Cast) and isinstance(expr.this, sge.Null): - return True - return False diff --git a/bigframes/core/compile/sqlglot/sql/ddl.py b/bigframes/core/compile/sqlglot/sql/ddl.py deleted file mode 100644 index 1a63d016d5e..00000000000 --- a/bigframes/core/compile/sqlglot/sql/ddl.py +++ /dev/null @@ -1,220 +0,0 @@ -# Copyright 2026 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 typing import Mapping, Optional, Union - -import bigframes_vendored.sqlglot as sg -import bigframes_vendored.sqlglot.expressions as sge - -from bigframes.core.compile.sqlglot.sql import base - - -def load_data( - table_name: str, - *, - write_disposition: str = "INTO", - columns: Optional[Mapping[str, str]] = None, - partition_by: Optional[list[str]] = None, - cluster_by: Optional[list[str]] = None, - table_options: Optional[Mapping[str, Union[str, int, float, bool, list]]] = None, - from_files_options: Mapping[str, Union[str, int, float, bool, list]], - with_partition_columns: Optional[Mapping[str, str]] = None, - connection_name: Optional[str] = None, -) -> sge.LoadData: - """Generates the LOAD DATA DDL statement.""" - # We use a Table with a simple identifier for the table name. - # Quoting is handled by the dialect. - table_expr = sge.Table(this=base.identifier(table_name)) - - sge_partition_by = ( - sge.PartitionedByProperty( - this=base.identifier(partition_by[0]) - if len(partition_by) == 1 - else sge.Tuple(expressions=[base.identifier(col) for col in partition_by]) - ) - if partition_by - else None - ) - - sge_cluster_by = ( - sge.Cluster(expressions=[base.identifier(col) for col in cluster_by]) - if cluster_by - else None - ) - - sge_from_files = sge.Tuple( - expressions=[ - sge.Property(this=base.identifier(k), value=base.literal(v)) - for k, v in from_files_options.items() - ] - ) - - sge_connection = base.identifier(connection_name) if connection_name else None - - return sge.LoadData( - this=table_expr, - overwrite=(write_disposition == "OVERWRITE"), - inpath=sge.convert("fake"), # satisfy sqlglot's required inpath arg - columns=_get_sge_schema(columns), - partition_by=sge_partition_by, - cluster_by=sge_cluster_by, - options=_get_sge_properties(table_options), - from_files=sge_from_files, - with_partition_columns=_get_sge_schema(with_partition_columns), - connection=sge_connection, - ) - - -def create_external_table( - table_name: str, - *, - replace: bool = False, - if_not_exists: bool = False, - columns: Optional[Mapping[str, str]] = None, - partition_columns: Optional[Mapping[str, str]] = None, - connection_name: Optional[str] = None, - options: Optional[Mapping[str, Union[str, int, float, bool, list]]] = None, -) -> sge.Create: - """Generates the CREATE EXTERNAL TABLE DDL statement.""" - sge_connection = base.identifier(connection_name) if connection_name else None - - table_expr = sge.Table(this=base.identifier(table_name)) - - # sqlglot.expressions.Create usually takes 'this' (Table or Schema) - sge_schema = _get_sge_schema(columns) - this: sge.Table | sge.Schema - if sge_schema: - sge_schema.set("this", table_expr) - this = sge_schema - else: - this = table_expr - - return sge.Create( - this=this, - kind="EXTERNAL TABLE", - replace=replace, - exists_ok=if_not_exists, - properties=_get_sge_properties(options), - connection=sge_connection, - partition_columns=_get_sge_schema(partition_columns), - ) - - -def _get_sge_schema( - columns: Optional[Mapping[str, str]] = None, -) -> Optional[sge.Schema]: - if not columns: - return None - - return sge.Schema( - this=None, - expressions=[ - sge.ColumnDef( - this=base.identifier(name), - kind=sge.DataType.build(typ, dialect=base.DIALECT), - ) - for name, typ in columns.items() - ], - ) - - -def _get_sge_properties( - options: Optional[Mapping[str, Union[str, int, float, bool, list]]] = None, -) -> Optional[sge.Properties]: - if not options: - return None - - return sge.Properties( - expressions=[ - sge.Property(this=base.identifier(k), value=base.literal(v)) - for k, v in options.items() - ] - ) - - -def _loaddata_sql(self: sg.Generator, expression: sge.LoadData) -> str: - out = ["LOAD DATA"] - if expression.args.get("overwrite"): - out.append("OVERWRITE") - - out.append(f"INTO {self.sql(expression, 'this').strip()}") - - # We ignore inpath as it's just a dummy to satisfy sqlglot requirements - # but BigQuery uses FROM FILES instead. - - columns = self.sql(expression, "columns").strip() - if columns: - out.append(columns) - - partition_by = self.sql(expression, "partition_by").strip() - if partition_by: - out.append(partition_by) - - cluster_by = self.sql(expression, "cluster_by").strip() - if cluster_by: - out.append(cluster_by) - - options = self.sql(expression, "options").strip() - if options: - out.append(options) - - from_files = self.sql(expression, "from_files").strip() - if from_files: - out.append(f"FROM FILES {from_files}") - - with_partition_columns = self.sql(expression, "with_partition_columns").strip() - if with_partition_columns: - out.append(f"WITH PARTITION COLUMNS {with_partition_columns}") - - connection = self.sql(expression, "connection").strip() - if connection: - out.append(f"WITH CONNECTION {connection}") - - return " ".join(out) - - -def _create_sql(self: sg.Generator, expression: sge.Create) -> str: - kind = expression.args.get("kind") - if kind != "EXTERNAL TABLE": - return self.create_sql(expression) - - out = ["CREATE"] - if expression.args.get("replace"): - out.append("OR REPLACE") - out.append("EXTERNAL TABLE") - if expression.args.get("exists_ok"): - out.append("IF NOT EXISTS") - - out.append(self.sql(expression, "this")) - - connection = self.sql(expression, "connection").strip() - if connection: - out.append(f"WITH CONNECTION {connection}") - - partition_columns = self.sql(expression, "partition_columns").strip() - if partition_columns: - out.append(f"WITH PARTITION COLUMNS {partition_columns}") - - properties = self.sql(expression, "properties").strip() - if properties: - out.append(properties) - - return " ".join(out) - - -# Register the transform for BigQuery generator -base.DIALECT.Generator.TRANSFORMS[sge.LoadData] = _loaddata_sql -base.DIALECT.Generator.TRANSFORMS[sge.Create] = _create_sql diff --git a/bigframes/core/compile/sqlglot/sql/dml.py b/bigframes/core/compile/sqlglot/sql/dml.py deleted file mode 100644 index 0f0ae9dff2b..00000000000 --- a/bigframes/core/compile/sqlglot/sql/dml.py +++ /dev/null @@ -1,59 +0,0 @@ -# Copyright 2026 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 - -import bigframes_vendored.sqlglot.expressions as sge -from google.cloud import bigquery - -from bigframes import dtypes -from bigframes.core.compile.sqlglot.sql import base - - -def insert( - query_or_table: typing.Union[sge.Select, sge.Table], - destination: bigquery.TableReference, -) -> sge.Insert: - """Generates an INSERT INTO SQL statement from the given SELECT statement or - table reference.""" - return sge.insert(_as_from_item(query_or_table), base.table(destination)) - - -def replace( - query_or_table: typing.Union[sge.Select, sge.Table], - destination: bigquery.TableReference, -) -> sge.Merge: - """Generates a MERGE statement to replace the contents of the destination table.""" - return sge.Merge( - this=base.table(destination), - using=_as_from_item(query_or_table), - on=base.literal(False, dtypes.BOOL_DTYPE), - whens=sge.Whens( - expressions=[ - sge.When(matched=False, source=True, then=sge.Delete()), - sge.When(matched=False, then=sge.Insert(this=sge.Var(this="ROW"))), - ] - ), - ) - - -def _as_from_item( - query_or_table: typing.Union[sge.Select, sge.Table], -) -> typing.Union[sge.Subquery, sge.Table]: - if isinstance(query_or_table, sge.Select): - return query_or_table.subquery() - else: # table - return query_or_table diff --git a/bigframes/core/compile/sqlglot/sqlglot_ir.py b/bigframes/core/compile/sqlglot/sqlglot_ir.py deleted file mode 100644 index b29a23cd84b..00000000000 --- a/bigframes/core/compile/sqlglot/sqlglot_ir.py +++ /dev/null @@ -1,676 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from __future__ import annotations - -import dataclasses -import datetime -import functools -import typing - -import bigframes_vendored.sqlglot as sg -import bigframes_vendored.sqlglot.expressions as sge -import pyarrow as pa - -import bigframes.core.compile.sqlglot.sqlglot_types as sgt -from bigframes import dtypes -from bigframes.core import guid, local_data, schema -from bigframes.core.compile.sqlglot import sql -from bigframes.core.compile.sqlglot.expressions import typed_expr - -# shapely.wkt.dumps was moved to shapely.io.to_wkt in 2.0. -try: - from shapely.io import to_wkt # type: ignore -except ImportError: - from shapely.wkt import dumps # type: ignore - - to_wkt = dumps - - -class SelectFragment: - def __init__(self, select_expr: sge.Select): - self.select_expr = select_expr - - def as_select_all(self) -> sge.Select: - return self.select_expr - - def select(self, *items: sge.Expression) -> sge.Select: - return sge.Select().select(*items).from_(self.select_expr.subquery()) - - def as_from_item(self) -> sge.Expression: - return self.select_expr.subquery() - - -class TableFragment: - def __init__(self, table: sge.Table | sge.Unnest): - self.table = table - - def as_select_all(self) -> sge.Select: - return sge.Select().select(sge.Star()).from_(self.table) - - def select(self, *items: sge.Expression) -> sge.Select: - return sge.Select().select(*items).from_(self.table) - - def as_from_item(self) -> sge.Expression: - return self.table - - -class DeferredSelectFragment: - def __init__(self, select_supplier: typing.Callable[[sge.Select], sge.Select]): - self.select_supplier = select_supplier - - def as_select_all(self) -> sge.Select: - return self.select_supplier(sge.Select().select(sge.Star())) - - def select(self, *items: sge.Expression) -> sge.Select: - return self.select_supplier(sge.Select().select(*items)) - - def as_from_item(self) -> sge.Expression: - return self.select_supplier(sge.Select().select(sge.Star())).subquery() - - -ExprT = SelectFragment | TableFragment | DeferredSelectFragment - - -@dataclasses.dataclass(frozen=True) -class SQLGlotIR: - """Helper class to build SQLGlot Query and generate SQL string.""" - - expr: ExprT - """The SQLGlot expression representing the query.""" - - uid_gen: guid.SequentialUIDGenerator = guid.SequentialUIDGenerator() - """Generator for unique identifiers.""" - - @property - def sql(self) -> str: - """Generate SQL string from the given expression.""" - return sql.to_sql(self.expr.as_select_all()) - - @classmethod - def empty( - cls, uid_gen: guid.SequentialUIDGenerator = guid.SequentialUIDGenerator() - ) -> SQLGlotIR: - return cls(expr=SelectFragment(sge.select()), uid_gen=uid_gen) - - @classmethod - def from_expr( - cls, - expr: sge.Expression, - uid_gen: guid.SequentialUIDGenerator = guid.SequentialUIDGenerator(), - ) -> SQLGlotIR: - if isinstance(expr, sge.Select): - return cls(expr=SelectFragment(expr), uid_gen=uid_gen) - elif isinstance(expr, (sge.Table, sge.Unnest)): - return cls(expr=TableFragment(expr), uid_gen=uid_gen) - else: - raise ValueError(f"Unsupported expression type: {type(expr)}") - - @classmethod - def from_func( - cls, - select_handler: typing.Callable[[sge.Select], sge.Select], - uid_gen: guid.SequentialUIDGenerator = guid.SequentialUIDGenerator(), - ): - return cls(expr=DeferredSelectFragment(select_handler), uid_gen=uid_gen) - - @classmethod - def from_pyarrow( - cls, - pa_table: pa.Table, - schema: schema.ArraySchema, - uid_gen: guid.SequentialUIDGenerator, - ) -> SQLGlotIR: - """Builds SQLGlot expression from a pyarrow table. - - This is used to represent in-memory data as a SQL query. - """ - dtype_expr = sge.DataType( - this=sge.DataType.Type.STRUCT, - expressions=[ - sge.ColumnDef( - this=sge.to_identifier(field.column, quoted=True), - kind=sgt.from_bigframes_dtype(field.dtype), - ) - for field in schema.items - ], - nested=True, - ) - data_expr = [ - sge.Struct( - expressions=tuple( - sql.literal( - value=value, - dtype=field.dtype, - ) - for value, field in zip(tuple(row_dict.values()), schema.items) - ) - ) - for row_dict in local_data._iter_table(pa_table, schema) - ] - expr = sge.Unnest( - expressions=[ - sge.DataType( - this=sge.DataType.Type.ARRAY, - expressions=[dtype_expr], - nested=True, - values=data_expr, - ), - ], - ) - return cls.from_expr(expr=expr, uid_gen=uid_gen) - - @classmethod - def from_table( - cls, - project_id: str, - dataset_id: str, - table_id: str, - uid_gen: guid.SequentialUIDGenerator | None = None, - columns: typing.Sequence[str] = (), - sql_predicate: typing.Optional[str] = None, - system_time: typing.Optional[datetime.datetime] = None, - ) -> SQLGlotIR: - """Builds a SQLGlotIR expression from a BigQuery table. - - Args: - project_id (str): The project ID of the BigQuery table. - dataset_id (str): The dataset ID of the BigQuery table. - table_id (str): The table ID of the BigQuery table. - uid_gen (guid.SequentialUIDGenerator): A generator for unique identifiers. - columns (typing.Sequence[str]): The names of the columns to select. - sql_predicate (typing.Optional[str]): An optional SQL predicate for filtering. - system_time (typing.Optional[str]): An optional system time for time-travel queries. - """ - version = ( - sge.Version( - this=sge.Identifier(this="SYSTEM_TIME", quoted=False), - expression=sge.Literal.string(system_time.isoformat()), - kind="AS OF", - ) - if system_time - else None - ) - if uid_gen is None: - uid_gen = guid.SequentialUIDGenerator() - table_alias = next(uid_gen.get_uid_stream("bft_")) - table_expr = sge.Table( - this=sql.identifier(table_id), - db=sql.identifier(dataset_id), - catalog=sql.identifier(project_id), - version=version, - alias=sql.identifier(table_alias), - ) - - if not columns and not sql_predicate: - return cls.from_expr(expr=table_expr, uid_gen=uid_gen) - - select_items: list[sge.Identifier | sge.Star] = ( - [sql.identifier(col) for col in columns] if columns else [sge.Star()] - ) - select_expr = sge.Select().select(*select_items).from_(table_expr) - - if sql_predicate: - select_expr = select_expr.where( - sg.parse_one(sql_predicate, dialect=sql.base.DIALECT), append=False - ) - - return cls.from_expr(expr=select_expr, uid_gen=uid_gen) - - @classmethod - def from_cte_ref( - cls, - cte_ref: str, - uid_gen: guid.SequentialUIDGenerator, - ) -> SQLGlotIR: - table_expr = sge.Table( - this=sql.identifier(cte_ref), - ) - return cls.from_expr(expr=table_expr, uid_gen=uid_gen) - - def select( - self, - selections: tuple[tuple[str, sge.Expression], ...] = (), - predicates: tuple[sge.Expression, ...] = (), - sorting: tuple[sge.Ordered, ...] = (), - limit: typing.Optional[int] = None, - ) -> SQLGlotIR: - # TODO: Explicitly insert CTEs into plan - if len(selections) > 0: - to_select = [ - expr - if (isinstance(expr, sge.Alias) and expr.alias == id) - or (isinstance(expr, sge.Column) and expr.name == id) - else sge.Alias( - this=expr.this if isinstance(expr, sge.Alias) else expr, - alias=sql.identifier(id), - ) - for id, expr in selections - ] - new_expr = self.expr.select(*to_select) - else: - new_expr = self.expr.as_select_all() - - if len(sorting) > 0: - new_expr = new_expr.order_by(*sorting) - - if len(predicates) > 0: - condition = _and(predicates) - new_expr = new_expr.where(condition, append=False) - if limit is not None: - new_expr = new_expr.limit(limit) - - return SQLGlotIR.from_expr(expr=new_expr, uid_gen=self.uid_gen) - - @classmethod - def from_unparsed_query( - cls, - query_string: str, - ) -> SQLGlotIR: - """Builds a SQLGlot expression from a query string. Wrapping the query - in a CTE can avoid the query parsing issue for unsupported syntax in - SQLGlot.""" - uid_gen: guid.SequentialUIDGenerator = guid.SequentialUIDGenerator() - cte_name = sql.identifier(next(uid_gen.get_uid_stream("bfcte_"))) - cte = sge.CTE( - this=query_string, - alias=cte_name, - ) - select_expr = sge.Select().select(sge.Star()).from_(sge.Table(this=cte_name)) - select_expr = _set_query_ctes(select_expr, [cte]) - return cls.from_expr(expr=select_expr, uid_gen=uid_gen) - - @classmethod - def from_union( - cls, - selects: typing.Sequence[sge.Select], - output_aliases: typing.Sequence[typing.Tuple[str, str]], - uid_gen: guid.SequentialUIDGenerator, - ) -> SQLGlotIR: - """Builds a SQLGlot expression by unioning of multiple select expressions.""" - assert len(list(selects)) >= 2, ( - f"At least two select expressions must be provided, but got {selects}." - ) - union_expr: sge.Query = selects[0].subquery() - for select in selects[1:]: - union_expr = sge.Union( - this=union_expr, - expression=select.subquery(), - distinct=False, - copy=False, - ) - - selections = [ - sge.Alias( - this=sql.identifier(old_name), - alias=sql.identifier(new_name), - ) - for old_name, new_name in output_aliases - ] - final_select_expr = ( - sge.Select().select(*selections).from_(union_expr.subquery()) - ) - return cls.from_expr(expr=final_select_expr, uid_gen=uid_gen) - - def join( - self, - right: SQLGlotIR, - join_type: typing.Literal["inner", "outer", "left", "right", "cross"], - conditions: tuple[tuple[typed_expr.TypedExpr, typed_expr.TypedExpr], ...], - *, - joins_nulls: bool = True, - ) -> SQLGlotIR: - """Joins the current query with another SQLGlotIR instance.""" - left_from = self.expr.as_from_item() - right_from = right.expr.as_from_item() - - join_on = _and( - tuple( - _join_condition(left, right, joins_nulls) for left, right in conditions - ) - ) - - join_type_str = join_type if join_type != "outer" else "full outer" - return SQLGlotIR.from_func( - lambda select: select.from_(left_from).join( - right_from, on=join_on, join_type=join_type_str - ), - uid_gen=self.uid_gen, - ) - - def isin_join( - self, - right: SQLGlotIR, - indicator_col: str, - conditions: tuple[typed_expr.TypedExpr, typed_expr.TypedExpr], - joins_nulls: bool = True, - ) -> SQLGlotIR: - """Joins the current query with another SQLGlotIR instance.""" - left_from = self.expr.as_from_item() - - new_column: sge.Expression - if joins_nulls: - force_float_domain = False - if ( - conditions[0].dtype == dtypes.FLOAT_DTYPE - or conditions[1].dtype == dtypes.FLOAT_DTYPE - ): - force_float_domain = True - left_expr1, left_expr2 = _value_to_non_null_identity( - conditions[0], force_float_domain - ) - right_expr1, right_expr2 = _value_to_non_null_identity( - conditions[1], force_float_domain - ) - - # Use EXISTS for better performance. - # We use COALESCE on both sides in the WHERE clause as requested. - new_column = sge.Exists( - this=sge.Select() - .select(sge.convert(1)) - .from_(right.expr.as_from_item()) - .where( - sge.and_( - sge.EQ(this=left_expr1, expression=right_expr1), - sge.EQ(this=left_expr2, expression=right_expr2), - ) - ) - ) - else: - new_column = sge.func( - "COALESCE", - sge.In( - this=conditions[0].expr, - expressions=[right._as_subquery()], - ), - sql.literal(False, dtypes.BOOL_DTYPE), - ) - - new_column = sge.Alias( - this=new_column, - alias=sql.identifier(indicator_col), - ) - - new_expr = sge.Select().select(sge.Star(), new_column).from_(left_from) - return SQLGlotIR.from_expr(expr=new_expr, uid_gen=self.uid_gen) - - def explode( - self, - column_names: tuple[str, ...], - offsets_col: typing.Optional[str], - ) -> SQLGlotIR: - """Unnests one or more array columns.""" - num_columns = len(list(column_names)) - assert num_columns > 0, "At least one column must be provided for explode." - if num_columns == 1: - return self._explode_single_column(column_names[0], offsets_col) - else: - return self._explode_multiple_columns(column_names, offsets_col) - - def sample(self, fraction: float) -> SQLGlotIR: - """Uniform samples a fraction of the rows.""" - condition = sge.LT( - this=sge.func("RAND"), - expression=sql.literal(fraction, dtypes.FLOAT_DTYPE), - ) - - new_expr = self.expr.as_select_all().where(condition, append=False) - return SQLGlotIR.from_expr(expr=new_expr, uid_gen=self.uid_gen) - - def aggregate( - self, - aggregations: tuple[tuple[str, sge.Expression], ...], - by_cols: tuple[sge.Expression, ...], - dropna_cols: tuple[sge.Expression, ...], - ) -> SQLGlotIR: - """Applies the aggregation expressions. - - Args: - aggregations: output_column_id, aggregation_expr tuples - by_cols: column expressions for aggregation - dropna_cols: columns whether null keys should be dropped - """ - aggregations_expr = [ - sge.Alias( - this=expr, - alias=sql.identifier(id), - ) - for id, expr in aggregations - ] - - new_expr = self.expr.select(*[*by_cols, *aggregations_expr]).group_by(*by_cols) - - condition = _and( - tuple( - sg.not_(sge.Is(this=drop_col, expression=sge.Null())) - for drop_col in dropna_cols - ) - ) - if condition is not None: - new_expr = new_expr.where(condition, append=False) - return SQLGlotIR.from_expr(expr=new_expr, uid_gen=self.uid_gen) - - def with_ctes( - self, - ctes: tuple[tuple[str, SQLGlotIR], ...], - ) -> SQLGlotIR: - sge_ctes = [ - sge.CTE( - this=cte.expr.as_select_all(), - alias=sql.identifier(cte_name), - ) - for cte_name, cte in ctes - ] - select_expr = _set_query_ctes(self.expr.as_select_all(), sge_ctes) - return SQLGlotIR.from_expr(expr=select_expr, uid_gen=self.uid_gen) - - def resample( - self, - right: SQLGlotIR, - array_col_name: str, - start_expr: sge.Expression, - stop_expr: sge.Expression, - step_expr: sge.Expression, - ) -> SQLGlotIR: - generate_array = sge.func( - "GENERATE_ARRAY", - start_expr, - stop_expr, - step_expr, - ) - - unnested_column_alias = sql.identifier( - next(self.uid_gen.get_uid_stream("bfcol_")) - ) - unnest_expr = sge.Unnest( - expressions=[generate_array], - alias=sge.TableAlias(columns=[unnested_column_alias]), - ) - - final_col_id = sql.identifier(array_col_name) - - # Build final expression by joining everything directly in a single SELECT - new_expr = ( - sge.Select() - .select(unnested_column_alias.as_(final_col_id)) - .from_(self.expr.as_from_item()) - .join(right.expr.as_from_item(), join_type="cross") - .join(unnest_expr, join_type="cross") - ) - - return SQLGlotIR.from_expr(expr=new_expr, uid_gen=self.uid_gen) - - def _explode_single_column( - self, column_name: str, offsets_col: typing.Optional[str] - ) -> SQLGlotIR: - """Helper method to handle the case of exploding a single column.""" - offset = sql.identifier(offsets_col) if offsets_col else None - column = sql.identifier(column_name) - unnested_column_alias = sql.identifier( - next(self.uid_gen.get_uid_stream("bfcol_")) - ) - unnest_expr = sge.Unnest( - expressions=[column], - alias=sge.TableAlias(columns=[unnested_column_alias]), - offset=offset, - ) - selection = sge.Star(replace=[unnested_column_alias.as_(column)]) - - # Use LEFT JOIN to preserve rows when unnesting empty arrays. - new_expr = self.expr.select(selection).join(unnest_expr, join_type="LEFT") - return SQLGlotIR.from_expr(expr=new_expr, uid_gen=self.uid_gen) - - def _explode_multiple_columns( - self, - column_names: tuple[str, ...], - offsets_col: typing.Optional[str], - ) -> SQLGlotIR: - """Helper method to handle the case of exploding multiple columns.""" - offset = sql.identifier(offsets_col) if offsets_col else None - columns = [sql.identifier(column_name) for column_name in column_names] - - # If there are multiple columns, we need to unnest by zipping the arrays: - # https://cloud.google.com/bigquery/docs/arrays#zipping_arrays - column_lengths = [sge.func("ARRAY_LENGTH", column) - 1 for column in columns] - generate_array = sge.func( - "GENERATE_ARRAY", - sge.convert(0), - sge.func("LEAST", *column_lengths), - ) - unnested_offset_alias = sql.identifier( - next(self.uid_gen.get_uid_stream("bfcol_")) - ) - unnest_expr = sge.Unnest( - expressions=[generate_array], - alias=sge.TableAlias(columns=[unnested_offset_alias]), - offset=offset, - ) - selection = sge.Star( - replace=[ - sge.Bracket( - this=column, - expressions=[unnested_offset_alias], - safe=True, - offset=False, - ).as_(column) - for column in columns - ] - ) - # Use LEFT JOIN to preserve rows when unnesting empty arrays. - new_expr = self.expr.select(selection).join(unnest_expr, join_type="LEFT") - return SQLGlotIR.from_expr(expr=new_expr, uid_gen=self.uid_gen) - - def _as_subquery(self) -> sge.Subquery: - # Sometimes explicitly need a subquery, e.g. for IN expressions. - return self.expr.as_select_all().subquery() - - -def _and(conditions: tuple[sge.Expression, ...]) -> typing.Optional[sge.Expression]: - """Chains multiple expressions together using a logical AND.""" - if not conditions: - return None - - return functools.reduce( - lambda left, right: sge.And(this=left, expression=right), conditions - ) - - -def _join_condition( - left: typed_expr.TypedExpr, - right: typed_expr.TypedExpr, - joins_nulls: bool, -) -> typing.Union[sge.EQ, sge.And]: - """Generates a join condition to match pandas's null-handling logic. - - Pandas treats null values as distinct from each other, leading to a - cross-join-like behavior for null keys. In contrast, BigQuery SQL treats - null values as equal, leading to a inner-join-like behavior. - - This function generates the appropriate SQL condition to replicate the - desired pandas behavior in BigQuery. - - Args: - left: The left-side join key. - right: The right-side join key. - joins_nulls: If True, generates complex logic to handle nulls/NaNs. - Otherwise, uses a simple equality check where appropriate. - """ - if not joins_nulls: - return sge.EQ(this=left.expr, expression=right.expr) - - force_float_domain = False - if left.dtype == dtypes.FLOAT_DTYPE or right.dtype == dtypes.FLOAT_DTYPE: - force_float_domain = True - left_expr1, left_expr2 = _value_to_non_null_identity(left, force_float_domain) - right_expr1, right_expr2 = _value_to_non_null_identity(right, force_float_domain) - return sge.And( - this=sge.EQ(this=left_expr1, expression=right_expr1), - expression=sge.EQ(this=left_expr2, expression=right_expr2), - ) - - -def _value_to_non_null_identity( - value: typed_expr.TypedExpr, force_float_domain: bool = False -) -> tuple[sge.Expression, sge.Expression]: - # normal_value -> (normal_value, normal_value) - # null_value -> (0, 1) - # nan_value -> (2, 3) - if dtypes.is_numeric(value.dtype, include_bool=False): - dtype = dtypes.FLOAT_DTYPE if force_float_domain else value.dtype - expr1 = sge.func( - "COALESCE", value.expr, sql.literal(0.0 if force_float_domain else 0, dtype) - ) - expr2 = sge.func( - "COALESCE", value.expr, sql.literal(1.0 if force_float_domain else 1, dtype) - ) - if value.dtype == dtypes.FLOAT_DTYPE: - expr1 = sge.If( - this=sge.IsNan(this=value.expr), - true=sql.literal(2.0, value.dtype), - false=expr1, - ) - expr2 = sge.If( - this=sge.IsNan(this=value.expr), - true=sql.literal(3, value.dtype), - false=expr2, - ) - else: # general case, convert to string and coalesce - expr1 = sge.func( - "COALESCE", - sql.cast(value.expr, "STRING"), - sql.literal("0", dtypes.STRING_DTYPE), - ) - expr2 = sge.func( - "COALESCE", - sql.cast(value.expr, "STRING"), - sql.literal("1", dtypes.STRING_DTYPE), - ) - return expr1, expr2 - - -def _set_query_ctes( - expr: sge.Select, - ctes: list[sge.CTE], -) -> sge.Select: - """Sets the CTEs of a given sge.Select expression.""" - new_expr = expr.copy() - with_expr = sge.With(expressions=ctes) if len(ctes) > 0 else None - - if "with" in new_expr.arg_types.keys(): - new_expr.set("with", with_expr) - elif "with_" in new_expr.arg_types.keys(): - new_expr.set("with_", with_expr) - else: - raise ValueError("The expression does not support CTEs.") - return new_expr diff --git a/bigframes/core/compile/sqlglot/sqlglot_types.py b/bigframes/core/compile/sqlglot/sqlglot_types.py deleted file mode 100644 index d22373b303f..00000000000 --- a/bigframes/core/compile/sqlglot/sqlglot_types.py +++ /dev/null @@ -1,81 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from __future__ import annotations - -import typing - -import bigframes_vendored.constants as constants -import bigframes_vendored.sqlglot as sg -import numpy as np -import pandas as pd -import pyarrow as pa - -import bigframes.dtypes - - -def from_bigframes_dtype( - bigframes_dtype: typing.Union[ - bigframes.dtypes.DtypeString, bigframes.dtypes.Dtype, np.dtype[typing.Any] - ], -) -> str: - if bigframes_dtype == bigframes.dtypes.INT_DTYPE: - return "INT64" - elif bigframes_dtype == bigframes.dtypes.FLOAT_DTYPE: - return "FLOAT64" - elif bigframes_dtype == bigframes.dtypes.STRING_DTYPE: - return "STRING" - elif bigframes_dtype == bigframes.dtypes.BOOL_DTYPE: - return "BOOLEAN" - elif bigframes_dtype == bigframes.dtypes.DATE_DTYPE: - return "DATE" - elif bigframes_dtype == bigframes.dtypes.TIME_DTYPE: - return "TIME" - elif bigframes_dtype == bigframes.dtypes.DATETIME_DTYPE: - return "DATETIME" - elif bigframes_dtype == bigframes.dtypes.TIMESTAMP_DTYPE: - return "TIMESTAMP" - elif bigframes_dtype == bigframes.dtypes.BYTES_DTYPE: - return "BYTES" - elif bigframes_dtype == bigframes.dtypes.NUMERIC_DTYPE: - return "NUMERIC" - elif bigframes_dtype == bigframes.dtypes.BIGNUMERIC_DTYPE: - return "BIGNUMERIC" - elif bigframes_dtype == bigframes.dtypes.JSON_DTYPE: - return "JSON" - elif bigframes_dtype == bigframes.dtypes.GEO_DTYPE: - return "GEOGRAPHY" - elif bigframes_dtype == bigframes.dtypes.TIMEDELTA_DTYPE: - return "INT64" - elif isinstance(bigframes_dtype, pd.ArrowDtype): - if pa.types.is_list(bigframes_dtype.pyarrow_dtype): - inner_bigframes_dtype = bigframes.dtypes.arrow_dtype_to_bigframes_dtype( - bigframes_dtype.pyarrow_dtype.value_type - ) - return f"ARRAY<{from_bigframes_dtype(inner_bigframes_dtype)}>" - elif pa.types.is_struct(bigframes_dtype.pyarrow_dtype): - struct_type = typing.cast(pa.StructType, bigframes_dtype.pyarrow_dtype) - inner_fields: list[str] = [] - for i in range(struct_type.num_fields): - field = struct_type.field(i) - key = sg.to_identifier(field.name).sql("bigquery") - dtype = from_bigframes_dtype( - bigframes.dtypes.arrow_dtype_to_bigframes_dtype(field.type) - ) - inner_fields.append(f"{key} {dtype}") - return "STRUCT<{}>".format(", ".join(inner_fields)) - - raise ValueError( - f"Unsupported type for {bigframes_dtype}. {constants.FEEDBACK_LINK}" - ) diff --git a/bigframes/core/convert.py b/bigframes/core/convert.py deleted file mode 100644 index 1546c2f87ee..00000000000 --- a/bigframes/core/convert.py +++ /dev/null @@ -1,131 +0,0 @@ -# Copyright 2023 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 typing import Optional - -import pandas as pd - -from bigframes import dataframe, series, session -from bigframes.core import global_session, indexes - - -def can_convert_to_series(obj) -> bool: - if isinstance(obj, series.Series): - return True - if isinstance(obj, pd.Series): - return True - if isinstance(obj, indexes.Index): - return True - if isinstance(obj, pd.Index): - return True - if pd.api.types.is_list_like(obj): - return True - - return False - - -def to_bf_series( - obj, - default_index: Optional[indexes.Index], - session: Optional[session.Session] = None, -) -> series.Series: - """ - Convert a an object to a bigframes series - - Args: - obj (list-like or Series): - Object to convert to bigframes Series - default_index (list-like or Index or None): - Index to use if obj has no index - - Returns - bigframes.pandas.Series - """ - if isinstance(obj, series.Series): - return obj.copy() - - if session is None: - session = global_session.get_global_session() - - if isinstance(obj, pd.Series): - return series.Series(obj, session=session) - if isinstance(obj, indexes.Index): - return series.Series(obj, default_index, session=session) - if isinstance(obj, pd.Index): - return series.Series(obj, default_index, session=session) - if pd.api.types.is_dict_like(obj): - return series.Series(obj, session=session) - if pd.api.types.is_list_like(obj): - return series.Series(obj, default_index, session=session) - - raise TypeError(f"Cannot interpret {obj} as series.") - - -def to_pd_series(obj, default_index: pd.Index) -> pd.Series: - """ - Convert a an object to a pandas series - - Args: - obj (list-like or Series): - Object to convert to pandas Series - default_index (list-like or Index or None): - Index to use if obj has no index - - Returns - pandas.Series - """ - if isinstance(obj, series.Series): - return obj.to_pandas() - if isinstance(obj, pd.Series): - return obj - if isinstance(obj, indexes.Index): - return pd.Series(obj.to_pandas(), default_index) - if isinstance(obj, pd.Index): - return pd.Series(obj, default_index) - if pd.api.types.is_dict_like(obj): - return pd.Series(obj) - if pd.api.types.is_list_like(obj): - return pd.Series(obj, default_index) - - raise TypeError(f"Cannot interpret {obj} as series.") - - -def can_convert_to_dataframe(obj) -> bool: - if can_convert_to_series(obj): - return True - - if isinstance(obj, dataframe.DataFrame) or isinstance(obj, pd.DataFrame): - return True - - return False - - -def to_bf_dataframe( - obj, - default_index: Optional[indexes.Index], - session: Optional[session.Session] = None, -) -> dataframe.DataFrame: - if isinstance(obj, dataframe.DataFrame): - return obj.copy() - - if isinstance(obj, pd.DataFrame): - if session is None: - session = global_session.get_global_session() - return dataframe.DataFrame(obj, session=session) - - if can_convert_to_series(obj): - return to_bf_series(obj, default_index, session).to_frame() - - raise TypeError(f"Cannot interpret {obj} as a dataframe.") diff --git a/bigframes/core/eval.py b/bigframes/core/eval.py deleted file mode 100644 index aba0f836b7a..00000000000 --- a/bigframes/core/eval.py +++ /dev/null @@ -1,77 +0,0 @@ -# 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. -# 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 Optional - -import bigframes_vendored.pandas.core.computation.eval as vendored_pandas_eval -import bigframes_vendored.pandas.core.computation.parsing as vendored_pandas_eval_parsing - -import bigframes.dataframe as dataframe -import bigframes.dtypes -import bigframes.series as series - - -def eval(df: dataframe.DataFrame, expr: str, target: Optional[dataframe.DataFrame]): - """ - Evaluate the given python expression - - Args: - df (DataFrame): - Columns of this dataframe will be used to resolve variables in expression. - expr (str): - One or more python expression to evaluate. - target (DataFrame or None): - The evaluation result will be written to the target if provided. - - Returns: - Result of evaluation. - """ - if df._has_index: - index_resolver = { - vendored_pandas_eval_parsing.clean_column_name(str(name)): EvalSeries( - df.index.get_level_values(level).to_series() - ) - for level, name in enumerate(df.index.names) - } - else: - index_resolver = {} - column_resolver = { - vendored_pandas_eval_parsing.clean_column_name(str(name)): EvalSeries(series) - for name, series in df.items() - } - # 3 Levels: user -> logging wrapper -> dataframe -> eval helper (this) - return vendored_pandas_eval.eval( - expr=expr, - level=3, - target=target, - resolvers=(index_resolver, column_resolver), # type: ignore - ) - - -@dataclasses.dataclass -class FakeNumpyArray: - dtype: bigframes.dtypes.Dtype - - -class EvalSeries(series.Series): - """Slight modified series that works better with pandas.eval""" - - def __init__(self, underlying: series.Series): - super().__init__(data=underlying._block) - - @property - def values(self): - """Returns fake numpy array with only dtype property so that eval can determine schema without actually downloading the data.""" - return FakeNumpyArray(self.dtype) diff --git a/bigframes/core/events.py b/bigframes/core/events.py deleted file mode 100644 index d6cef860f6d..00000000000 --- a/bigframes/core/events.py +++ /dev/null @@ -1,299 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from __future__ import annotations - -import asyncio -import concurrent.futures -import dataclasses -import datetime -import threading -import uuid -from typing import Any, Callable, Literal, Optional, Set - -import google.cloud.bigquery._job_helpers -import google.cloud.bigquery.job.query -import google.cloud.bigquery.table - -import bigframes.session.executor - -_DEFAULT: Literal["default"] = "default" - -ProgressBarType = Literal["default", "auto", "notebook", "terminal"] | None -QueryPlanType = list[google.cloud.bigquery.job.query.QueryPlanEntry] | None - - -class Subscriber: - def __init__( - self, - callback: Callable[[EventEnvelope], None], - *, - publisher: Publisher, - ): - self._publisher = publisher - self._callback = callback - self._subscriber_id = uuid.uuid4() - - def __call__(self, *args, **kwargs): - return self._callback(*args, **kwargs) - - def __hash__(self) -> int: - return hash(self._subscriber_id) - - def __eq__(self, value: object): - if not isinstance(value, Subscriber): - return NotImplemented - return value._subscriber_id == self._subscriber_id - - def close(self): - self._publisher.unsubscribe(self) - del self._publisher - del self._callback - - def __enter__(self): - return self - - def __exit__(self, exc_type, exc_value, traceback): - if exc_value is not None: - self( - EventEnvelope( - UnknownErrorEvent( - exc_type=exc_type, - exc_value=exc_value, - traceback=traceback, - ) - ) - ) - self.close() - - -class Publisher: - def __init__(self): - self._subscribers_lock = threading.Lock() - self._subscribers: Set[Subscriber] = set() - self._executor: concurrent.futures.Executor = ( - concurrent.futures.ThreadPoolExecutor() - ) - - def subscribe( - self, - callback: Callable[[EventEnvelope], None], - ) -> Subscriber: - # TODO(b/448176657): figure out how to handle subscribers/publishers in - # a background thread. Maybe subscribers should be thread-local? - subscriber = Subscriber(callback, publisher=self) - with self._subscribers_lock: - self._subscribers.add(subscriber) - return subscriber - - def unsubscribe(self, subscriber: Subscriber): - with self._subscribers_lock: - self._subscribers.remove(subscriber) - - def publish(self, envelope: EventEnvelope | Event): - if not isinstance(envelope, EventEnvelope): - envelope = EventEnvelope(event=envelope) - with self._subscribers_lock: - for subscriber in self._subscribers: - subscriber(envelope) - - async def publish_async(self, envelope: EventEnvelope | Event): - if not isinstance(envelope, EventEnvelope): - envelope = EventEnvelope(event=envelope) - with self._subscribers_lock: - subscribers_snapshot = list(self._subscribers) - loop = asyncio.get_running_loop() - tasks = [ - loop.run_in_executor(self._executor, subscriber, envelope) - for subscriber in subscribers_snapshot - ] - return await asyncio.gather(*tasks, return_exceptions=True) - - -class Event: - pass - - -@dataclasses.dataclass(frozen=True) -class EventEnvelope: - """An envelope that wraps an execution event with metadata and display options. - - Attributes: - event: - The actual execution event details (e.g., ExecutionStarted, BigQuerySentEvent). - progress_bar: - Specifies the style of progress bar to display during execution. - cell_execution_count: - The 1-indexed IPython/Jupyter notebook cell execution number (e.g. the 'x' in 'In [x]'). - This is NOT a job count, but rather the sequential number of the cell execution in the - current notebook session, used to group and filter execution history on a per-cell basis. - """ - - event: Event - progress_bar: ProgressBarType = _DEFAULT - cell_execution_count: Optional[int] = None - - -@dataclasses.dataclass(frozen=True) -class SessionClosed(Event): - session_id: str - - -class ExecutionStarted(Event): - pass - - -class ExecutionRunning(Event): - pass - - -@dataclasses.dataclass(frozen=True) -class ExecutionFinished(Event): - result: bigframes.session.executor.ExecuteResult | None = None - - -@dataclasses.dataclass(frozen=True) -class UnknownErrorEvent(Event): - exc_type: Any - exc_value: Any - traceback: Any - - -@dataclasses.dataclass(frozen=True) -class BigQuerySentEvent(ExecutionRunning): - """Query sent to BigQuery.""" - - query: str - billing_project: str | None = None - location: str | None = None - job_id: str | None = None - request_id: str | None = None - - @classmethod - def from_bqclient( - cls, - event: google.cloud.bigquery._job_helpers.QuerySentEvent, - ): - return cls( - query=event.query, - billing_project=event.billing_project, - location=event.location, - job_id=event.job_id, - request_id=event.request_id, - ) - - -@dataclasses.dataclass(frozen=True) -class BigQueryRetryEvent(ExecutionRunning): - """Query sent another time because the previous attempt failed.""" - - query: str - billing_project: str | None = None - location: str | None = None - job_id: str | None = None - request_id: str | None = None - - @classmethod - def from_bqclient( - cls, - event: google.cloud.bigquery._job_helpers.QueryRetryEvent, - ): - return cls( - query=event.query, - billing_project=event.billing_project, - location=event.location, - job_id=event.job_id, - request_id=event.request_id, - ) - - -@dataclasses.dataclass(frozen=True) -class BigQueryReceivedEvent(ExecutionRunning): - """Query received and acknowledged by the BigQuery API.""" - - billing_project: str | None = None - location: str | None = None - job_id: str | None = None - statement_type: str | None = None - state: str | None = None - query_plan: QueryPlanType = None - created: datetime.datetime | None = None - started: datetime.datetime | None = None - ended: datetime.datetime | None = None - - @classmethod - def from_bqclient( - cls, - event: google.cloud.bigquery._job_helpers.QueryReceivedEvent, - ): - return cls( - billing_project=event.billing_project, - location=event.location, - job_id=event.job_id, - statement_type=event.statement_type, - state=event.state, - query_plan=event.query_plan, - created=event.created, - started=event.started, - ended=event.ended, - ) - - -@dataclasses.dataclass(frozen=True) -class BigQueryFinishedEvent(ExecutionRunning): - """Query finished successfully.""" - - billing_project: str | None = None - location: str | None = None - query_id: str | None = None - job_id: str | None = None - destination: google.cloud.bigquery.table.TableReference | None = None - total_rows: int | None = None - total_bytes_processed: int | None = None - slot_millis: int | None = None - created: datetime.datetime | None = None - started: datetime.datetime | None = None - ended: datetime.datetime | None = None - - @classmethod - def from_bqclient( - cls, - event: google.cloud.bigquery._job_helpers.QueryFinishedEvent, - ): - return cls( - billing_project=event.billing_project, - location=event.location, - query_id=event.query_id, - job_id=event.job_id, - destination=event.destination, - total_rows=event.total_rows, - total_bytes_processed=event.total_bytes_processed, - slot_millis=event.slot_millis, - created=event.created, - started=event.started, - ended=event.ended, - ) - - -@dataclasses.dataclass(frozen=True) -class BigQueryUnknownEvent(ExecutionRunning): - """Got unknown event from the BigQuery client library.""" - - # TODO: should we just skip sending unknown events? - - event: object - - @classmethod - def from_bqclient(cls, event): - return cls(event) diff --git a/bigframes/core/explode.py b/bigframes/core/explode.py deleted file mode 100644 index ddd290b0f84..00000000000 --- a/bigframes/core/explode.py +++ /dev/null @@ -1,36 +0,0 @@ -# Copyright 2023 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. - -"""Utility functions for implementing 'explode' functions.""" - -from typing import Sequence, Union, cast - -import bigframes.core.blocks as blocks -import bigframes.core.utils as utils - - -def check_column( - column: Union[blocks.Label, Sequence[blocks.Label]], -) -> Sequence[blocks.Label]: - if not utils.is_list_like(column): - column_labels = cast(Sequence[blocks.Label], (column,)) - else: - column_labels = cast(Sequence[blocks.Label], tuple(column)) - - if not column_labels: - raise ValueError("column must be nonempty") - if len(column_labels) > len(set(column_labels)): - raise ValueError("column must be unique") - - return column_labels diff --git a/bigframes/core/expression.py b/bigframes/core/expression.py deleted file mode 100644 index 6c27dfc120b..00000000000 --- a/bigframes/core/expression.py +++ /dev/null @@ -1,533 +0,0 @@ -# Copyright 2023 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 abc -import dataclasses -import functools -import itertools -import typing -from typing import Callable, Generator, Hashable, Mapping, TypeVar, Union - -import pandas as pd - -import bigframes.core.identifiers as ids -import bigframes.operations -from bigframes import dtypes -from bigframes.core import field - -if typing.TYPE_CHECKING: - import bigframes.operations - - -def const( - value: typing.Hashable, dtype: dtypes.ExpressionType = None -) -> ScalarConstantExpression: - return ScalarConstantExpression(value, dtype or dtypes.infer_literal_type(value)) - - -def deref(name: str) -> DerefOp: - return DerefOp(ids.ColumnId(name)) - - -def free_var(id: Hashable) -> UnboundVariableExpression: - return UnboundVariableExpression(id) - - -T = TypeVar("T") -TExpression = TypeVar("TExpression", bound="Expression") - - -@dataclasses.dataclass(frozen=True) -class Expression(abc.ABC): - """An expression represents a computation taking N scalar inputs and producing a single output scalar.""" - - @property - def free_variables(self) -> typing.Tuple[Hashable, ...]: - return () - - @property - def children(self) -> typing.Tuple[Expression, ...]: - return () - - @property - def expensive(self) -> bool: - return any( - isinstance(ex, OpExpression) and ex.op.expensive for ex in self.walk() - ) - - @property - def nullable(self) -> bool: - return True - - @property - @abc.abstractmethod - def column_references(self) -> typing.Tuple[ids.ColumnId, ...]: ... - - def remap_column_refs( - self: TExpression, - name_mapping: Mapping[ids.ColumnId, ids.ColumnId], - allow_partial_bindings: bool = False, - ) -> TExpression: - return self.bind_refs( - {old_id: DerefOp(new_id) for old_id, new_id in name_mapping.items()}, # type: ignore - allow_partial_bindings=allow_partial_bindings, - ) - - @property - @abc.abstractmethod - def is_const(self) -> bool: ... - - @property - @abc.abstractmethod - def is_resolved(self) -> bool: - """ - Returns true if and only if the expression's output type and nullability is available. - """ - ... - - @property - @abc.abstractmethod - def output_type(self) -> dtypes.ExpressionType: ... - - @abc.abstractmethod - def bind_refs( - self, - bindings: Mapping[ids.ColumnId, Expression], - allow_partial_bindings: bool = False, - ) -> Expression: - """Replace variables with expression given in `bindings`. - - If allow_partial_bindings is False, validate that all free variables are bound to a new value. - """ - ... - - @abc.abstractmethod - def bind_variables( - self, - bindings: Mapping[Hashable, Expression], - allow_partial_bindings: bool = False, - ) -> Expression: - """Replace variables with expression given in `bindings`. - - If allow_partial_bindings is False, validate that all free variables are bound to a new value. - """ - ... - - @property - def is_bijective(self) -> bool: - return False - - @property - def deterministic(self) -> bool: - return True - - @property - def is_identity(self) -> bool: - """True for identity operation that does not transform input.""" - return False - - @functools.cached_property - def is_scalar_expr(self) -> bool: - """True if expression represents scalar value or expression over scalar values (no windows or aggregations)""" - return all(expr.is_scalar_expr for expr in self.children) - - @abc.abstractmethod - def transform_children( - self, t: Callable[[Expression], Expression] - ) -> Expression: ... - - def bottom_up(self, t: Callable[[Expression], Expression]) -> Expression: - expr = self.transform_children(lambda child: child.bottom_up(t)) - expr = t(expr) - return expr - - def top_down(self, t: Callable[[Expression], Expression]) -> Expression: - expr = t(self) - expr = expr.transform_children(lambda child: child.top_down(t)) - return expr - - def walk(self) -> Generator[Expression, None, None]: - yield self - for child in self.children: - yield from child.children - - -@dataclasses.dataclass(frozen=True) -class ScalarConstantExpression(Expression): - """An expression representing a scalar constant.""" - - # TODO: Further constrain? - value: typing.Hashable - dtype: dtypes.ExpressionType = None - - @property - def is_const(self) -> bool: - return True - - @property - def column_references(self) -> typing.Tuple[ids.ColumnId, ...]: - return () - - @property - def nullable(self) -> bool: - return pd.isna(self.value) # type: ignore - - @property - def is_resolved(self) -> bool: - return True - - @property - def output_type(self) -> dtypes.ExpressionType: - return self.dtype - - def bind_variables( - self, - bindings: Mapping[Hashable, Expression], - allow_partial_bindings: bool = False, - ) -> Expression: - return self - - def bind_refs( - self, - bindings: Mapping[ids.ColumnId, Expression], - allow_partial_bindings: bool = False, - ) -> ScalarConstantExpression: - return self - - @property - def is_bijective(self) -> bool: - # () <-> value - return True - - def __eq__(self, other): - if not isinstance(other, ScalarConstantExpression): - return False - - # With python 3.13 and the pre-release version of pandas, - # NA == NA is NA instead of True - if pd.isna(self.value) and pd.isna(other.value): # type: ignore - return self.dtype == other.dtype - - return self.value == other.value and self.dtype == other.dtype - - def transform_children(self, t: Callable[[Expression], Expression]) -> Expression: - return self - - -@dataclasses.dataclass(frozen=True) -class UnboundVariableExpression(Expression): - """A variable expression representing an unbound variable.""" - - id: Hashable - - @property - def free_variables(self) -> typing.Tuple[Hashable, ...]: - return (self.id,) - - @property - def is_const(self) -> bool: - return False - - @property - def column_references(self) -> typing.Tuple[ids.ColumnId, ...]: - return () - - @property - def is_resolved(self): - return False - - @property - def output_type(self) -> dtypes.ExpressionType: - raise ValueError(f"Type of variable {self.id} has not been fixed.") - - def bind_refs( - self, - bindings: Mapping[ids.ColumnId, Expression], - allow_partial_bindings: bool = False, - ) -> UnboundVariableExpression: - return self - - def bind_variables( - self, - bindings: Mapping[Hashable, Expression], - allow_partial_bindings: bool = False, - ) -> Expression: - if self.id in bindings.keys(): - return bindings[self.id] - elif not allow_partial_bindings: - raise ValueError(f"Variable {self.id} remains unbound") - return self - - @property - def is_bijective(self) -> bool: - return True - - @property - def is_identity(self) -> bool: - return True - - def transform_children(self, t: Callable[[Expression], Expression]) -> Expression: - return self - - -@dataclasses.dataclass(frozen=True) -class DerefOp(Expression): - """An expression that refers to a column by ID.""" - - id: ids.ColumnId - - @property - def column_references(self) -> typing.Tuple[ids.ColumnId, ...]: - return (self.id,) - - @property - def is_const(self) -> bool: - return False - - @property - def nullable(self) -> bool: - # Safe default, need to actually bind input schema to determine - return True - - @property - def is_resolved(self) -> bool: - return False - - @property - def output_type(self) -> dtypes.ExpressionType: - raise ValueError(f"Type of variable {self.id} has not been fixed.") - - def bind_variables( - self, - bindings: Mapping[Hashable, Expression], - allow_partial_bindings: bool = False, - ) -> Expression: - return self - - def bind_refs( - self, - bindings: Mapping[ids.ColumnId, Expression], - allow_partial_bindings: bool = False, - ) -> Expression: - if self.id in bindings.keys(): - return bindings[self.id] - elif not allow_partial_bindings: - raise ValueError(f"Variable {self.id} remains unbound") - return self - - @property - def is_bijective(self) -> bool: - return True - - @property - def is_identity(self) -> bool: - return True - - def transform_children(self, t: Callable[[Expression], Expression]) -> Expression: - return self - - -@dataclasses.dataclass(frozen=True) -class ResolvedDerefOp(DerefOp): - """An expression that refers to a column by ID and resolved with schema bound.""" - - dtype: dtypes.Dtype - is_nullable: bool - - @classmethod - def from_field(cls, f: field.Field): - return cls(id=f.id, dtype=f.dtype, is_nullable=f.nullable) - - @property - def is_resolved(self) -> bool: - return True - - @property - def nullable(self) -> bool: - return self.is_nullable - - @property - def output_type(self) -> dtypes.ExpressionType: - return self.dtype - - -@dataclasses.dataclass(frozen=True) -class OmittedArg(Expression): - """Represents an omitted optional arg used calling a function.""" - - @property - def free_variables(self) -> typing.Tuple[Hashable, ...]: - return () - - @property - def is_const(self) -> bool: - return True - - @property - def column_references(self) -> typing.Tuple[ids.ColumnId, ...]: - return () - - @property - def is_resolved(self): - return True # vacuously - - @property - def output_type(self) -> dtypes.ExpressionType: - return None - - def bind_refs( - self, - bindings: Mapping[ids.ColumnId, Expression], - allow_partial_bindings: bool = False, - ) -> OmittedArg: - return self - - def bind_variables( - self, - bindings: Mapping[Hashable, Expression], - allow_partial_bindings: bool = False, - ) -> Expression: - return self - - @property - def is_bijective(self) -> bool: - return True - - @property - def is_identity(self) -> bool: - return True - - def transform_children(self, t: Callable[[Expression], Expression]) -> Expression: - return self - - -@dataclasses.dataclass(frozen=True) -class OpExpression(Expression): - """An expression representing a scalar operation applied to 1 or more argument sub-expressions.""" - - op: bigframes.operations.ScalarOp - inputs: typing.Tuple[Expression, ...] - - @property - def column_references( - self, - ) -> typing.Tuple[bigframes.core.identifiers.ColumnId, ...]: - return tuple( - itertools.chain.from_iterable( - map(lambda x: x.column_references, self.inputs) - ) - ) - - @property - def free_variables(self) -> typing.Tuple[Hashable, ...]: - return tuple( - itertools.chain.from_iterable(map(lambda x: x.free_variables, self.inputs)) - ) - - @property - def is_const(self) -> bool: - return all(child.is_const for child in self.inputs) - - @property - def children(self): - return self.inputs - - @property - def nullable(self) -> bool: - # This is very conservative, need to label null properties of individual ops to get more precise - null_free = self.is_identity and not any( - child.nullable for child in self.inputs - ) - return not null_free - - @functools.cached_property - def is_resolved(self) -> bool: - return all(input.is_resolved for input in self.inputs) - - @functools.cached_property - def output_type(self) -> dtypes.ExpressionType: - if not self.is_resolved: - raise ValueError(f"Type of expression {self.op.name} has not been fixed.") - - input_types = [input.output_type for input in self.inputs] - - return self.op.output_type(*input_types) - - def bind_variables( - self, - bindings: Mapping[Hashable, Expression], - allow_partial_bindings: bool = False, - ) -> OpExpression: - return OpExpression( - self.op, - tuple( - input.bind_variables( - bindings, allow_partial_bindings=allow_partial_bindings - ) - for input in self.inputs - ), - ) - - def bind_refs( - self, - bindings: Mapping[ids.ColumnId, Expression], - allow_partial_bindings: bool = False, - ) -> OpExpression: - return OpExpression( - self.op, - tuple( - input.bind_refs(bindings, allow_partial_bindings=allow_partial_bindings) - for input in self.inputs - ), - ) - - @property - def is_bijective(self) -> bool: - # TODO: Mark individual functions as bijective? - return all(input.is_bijective for input in self.inputs) and self.op.is_bijective - - @property - def deterministic(self) -> bool: - return ( - all(input.deterministic for input in self.inputs) and self.op.deterministic - ) - - def transform_children(self, t: Callable[[Expression], Expression]) -> Expression: - new_inputs = tuple(t(input) for input in self.inputs) - if new_inputs != self.inputs: - return dataclasses.replace(self, inputs=new_inputs) - return self - - -def bind_schema_fields( - expr: Expression, field_by_id: Mapping[ids.ColumnId, field.Field] -) -> Expression: - """ - Updates `DerefOp` expressions by replacing column IDs with actual schema fields(columns). - - We can only deduct an expression's output type and nullability after binding schema fields to - all its deref expressions. - """ - if expr.is_resolved: - return expr - - expr_by_id = { - id: ResolvedDerefOp.from_field(field) for id, field in field_by_id.items() - } - return expr.bind_refs(expr_by_id) - - -RefOrConstant = Union[DerefOp, ScalarConstantExpression] diff --git a/bigframes/core/expression_factoring.py b/bigframes/core/expression_factoring.py deleted file mode 100644 index 22f1433c8a4..00000000000 --- a/bigframes/core/expression_factoring.py +++ /dev/null @@ -1,467 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - - -import collections -import dataclasses -import functools -import itertools -from typing import ( - Callable, - Dict, - Generator, - Hashable, - Iterable, - Iterator, - Mapping, - Optional, - Sequence, - Tuple, - TypeVar, - cast, -) - -from bigframes.core import ( - agg_expressions, - expression, - graphs, - identifiers, - nodes, - window_spec, -) - -_MAX_INLINE_COMPLEXITY = 10 - -T = TypeVar("T") - - -def unique_nodes( - roots: Sequence[expression.Expression], -) -> Generator[expression.Expression, None, None]: - """Walks the tree for unique nodes""" - seen = set() - stack: list[expression.Expression] = list(roots) - while stack: - item = stack.pop() - if item not in seen: - yield item - seen.add(item) - stack.extend(item.children) - - -def iter_nodes_topo( - roots: Sequence[expression.Expression], -) -> Generator[expression.Expression, None, None]: - """Returns nodes in reverse topological order, using Kahn's algorithm.""" - child_to_parents: Dict[expression.Expression, list[expression.Expression]] = ( - collections.defaultdict(list) - ) - out_degree: Dict[expression.Expression, int] = collections.defaultdict(int) - - queue: collections.deque[expression.Expression] = collections.deque() - for node in unique_nodes(roots): - num_children = len(node.children) - out_degree[node] = num_children - if num_children == 0: - queue.append(node) - for child in node.children: - child_to_parents[child].append(node) - - while queue: - item = queue.popleft() - yield item - parents = child_to_parents.get(item, []) - for parent in parents: - out_degree[parent] -= 1 - if out_degree[parent] == 0: - queue.append(parent) - - -def reduce_up( - roots: Sequence[expression.Expression], - reduction: Callable[[expression.Expression, Tuple[T, ...]], T], -) -> Tuple[T, ...]: - """Apply a bottom-up reduction to the forest.""" - results: dict[expression.Expression, T] = {} - for node in list(iter_nodes_topo(roots)): - # child nodes have already been transformed - child_results = tuple(results[child] for child in node.children) - result = reduction(node, child_results) - results[node] = result - - return tuple(results[root] for root in roots) - - -def apply_col_exprs_to_plan( - plan: nodes.BigFrameNode, col_exprs: Sequence[nodes.ColumnDef] -) -> nodes.BigFrameNode: - target_ids = tuple(named_expr.id for named_expr in col_exprs) - - fragments = fragmentize_expression(col_exprs) - return push_into_tree(plan, fragments, target_ids) - - -def apply_agg_exprs_to_plan( - plan: nodes.BigFrameNode, - agg_defs: Sequence[nodes.ColumnDef], - grouping_keys: Sequence[expression.DerefOp], -) -> nodes.BigFrameNode: - factored_aggs = [factor_aggregation(agg_def) for agg_def in agg_defs] - all_inputs = list( - itertools.chain(*(factored_agg.agg_inputs for factored_agg in factored_aggs)) - ) - window_def = window_spec.WindowSpec(grouping_keys=tuple(grouping_keys)) - windowized_inputs = [ - nodes.ColumnDef(windowize(cdef.expression, window_def), cdef.id) - for cdef in all_inputs - ] - plan = apply_col_exprs_to_plan(plan, windowized_inputs) - all_aggs = list( - itertools.chain(*(factored_agg.agg_exprs for factored_agg in factored_aggs)) - ) - plan = nodes.AggregateNode( - plan, - tuple((cdef.expression, cdef.id) for cdef in all_aggs), # type: ignore - by_column_ids=tuple(grouping_keys), - ) - - post_scalar_exprs = tuple( - (factored_agg.root_scalar_expr for factored_agg in factored_aggs) - ) - plan = nodes.ProjectionNode( - plan, tuple((cdef.expression, cdef.id) for cdef in post_scalar_exprs) - ) - final_ids = itertools.chain( - (ref.id for ref in grouping_keys), (cdef.id for cdef in post_scalar_exprs) - ) - plan = nodes.SelectionNode( - plan, tuple(nodes.AliasedRef.identity(ident) for ident in final_ids) - ) - - return plan - - -@dataclasses.dataclass(frozen=True, eq=False) -class FactoredExpression: - root_expr: expression.Expression - sub_exprs: Tuple[nodes.ColumnDef, ...] - - -def fragmentize_expression( - roots: Sequence[nodes.ColumnDef], -) -> Sequence[nodes.ColumnDef]: - """ - The goal of this functions is to factor out an expression into multiple sub-expressions. - """ - # TODO: Fragmentize a bit less aggressively - factored_exprs = reduce_up([root.expression for root in roots], gather_fragments) - root_exprs = ( - nodes.ColumnDef(factored.root_expr, root.id) - for factored, root in zip(factored_exprs, roots) - ) - return ( - *root_exprs, - *dedupe( - itertools.chain.from_iterable( - factored_expr.sub_exprs for factored_expr in factored_exprs - ) - ), - ) - - -@dataclasses.dataclass(frozen=True, eq=False) -class FactoredAggregation: - """ - A three part recomposition of a general aggregating expression. - - 1. agg_inputs: This is a set of (*col) -> col transformation that preprocess inputs for the aggregations ops - 2. agg_exprs: This is a set of pure aggregations (eg sum, mean, min, max) ops referencing the outputs of (1) - 3. root_scalar_expr: This is the final set, takes outputs of (2), applies scalar expression to produce final result. - """ - - # pure scalar expression - root_scalar_expr: nodes.ColumnDef - # pure agg expression, only refs cols and consts - agg_exprs: Tuple[nodes.ColumnDef, ...] - # can be analytic, scalar op, const, col refs - agg_inputs: Tuple[nodes.ColumnDef, ...] - - -def windowize( - root: expression.Expression, window: window_spec.WindowSpec -) -> expression.Expression: - def windowize_local(expr: expression.Expression): - if isinstance(expr, agg_expressions.Aggregation): - if not expr.op.can_be_windowized: - raise ValueError(f"Op: {expr.op} cannot be windowized.") - return agg_expressions.WindowExpression(expr, window) - if isinstance(expr, agg_expressions.WindowExpression): - raise ValueError(f"Expression {expr} already windowed!") - return expr - - return root.bottom_up(windowize_local) - - -def factor_aggregation(root: nodes.ColumnDef) -> FactoredAggregation: - """ - Factor an aggregation def into three components. - 1. Input column expressions (includes analytic expressions) - 2. The set of underlying primitive aggregations - 3. A final post-aggregate scalar expression - """ - final_aggs = list(dedupe(find_final_aggregations(root.expression))) - agg_inputs = list( - dedupe(itertools.chain.from_iterable(map(find_agg_inputs, final_aggs))) - ) - - agg_input_defs = tuple( - nodes.ColumnDef(expr, identifiers.ColumnId.unique()) for expr in agg_inputs - ) - agg_inputs_dict = { - cdef.expression: expression.DerefOp(cdef.id) for cdef in agg_input_defs - } - - agg_expr_to_ids = {expr: identifiers.ColumnId.unique() for expr in final_aggs} - - isolated_aggs = tuple( - nodes.ColumnDef(sub_expressions(expr, agg_inputs_dict), agg_expr_to_ids[expr]) - for expr in final_aggs - ) - agg_outputs_dict = { - expr: expression.DerefOp(id) for expr, id in agg_expr_to_ids.items() - } - - root_scalar_expr = nodes.ColumnDef( - sub_expressions( - root.expression, - cast( - Mapping[expression.Expression, expression.Expression], agg_outputs_dict - ), - ), - root.id, # type: ignore - ) - - return FactoredAggregation( - root_scalar_expr=root_scalar_expr, - agg_exprs=isolated_aggs, - agg_inputs=agg_input_defs, - ) - - -def sub_expressions( - root: expression.Expression, - replacements: Mapping[expression.Expression, expression.Expression], -) -> expression.Expression: - return root.top_down(lambda x: replacements.get(x, x)) - - -def find_final_aggregations( - root: expression.Expression, -) -> Iterator[agg_expressions.Aggregation]: - if isinstance(root, agg_expressions.Aggregation): - yield root - elif isinstance(root, expression.OpExpression): - for child in root.children: - yield from find_final_aggregations(child) - elif isinstance(root, expression.ScalarConstantExpression): - return - else: - # eg, window expression, column references not allowed - raise ValueError(f"Unexpected node: {root}") - - -def find_agg_inputs( - root: agg_expressions.Aggregation, -) -> Iterator[expression.Expression]: - for child in root.children: - if not isinstance( - child, (expression.DerefOp, expression.ScalarConstantExpression) - ): - yield child - - -def gather_fragments( - root: expression.Expression, fragmentized_children: Sequence[FactoredExpression] -) -> FactoredExpression: - replacements: list[expression.Expression] = [] - named_exprs = [] # root -> leaf dependency order - for child_result in fragmentized_children: - child_expr = child_result.root_expr - is_leaf = isinstance( - child_expr, (expression.DerefOp, expression.ScalarConstantExpression) - ) - is_window_agg = isinstance( - root, agg_expressions.WindowExpression - ) and isinstance(child_expr, agg_expressions.Aggregation) - do_inline = is_leaf | is_window_agg - if not do_inline: - id = identifiers.ColumnId.unique() - replacements.append(expression.DerefOp(id)) - named_exprs.append(nodes.ColumnDef(child_result.root_expr, id)) - named_exprs.extend(child_result.sub_exprs) - else: - replacements.append(child_result.root_expr) - named_exprs.extend(child_result.sub_exprs) - new_root = replace_children(root, replacements) - return FactoredExpression(new_root, tuple(named_exprs)) - - -def replace_children( - root: expression.Expression, new_children: Sequence[expression.Expression] -): - mapping = {root.children[i]: new_children[i] for i in range(len(root.children))} - return root.transform_children(lambda x: mapping.get(x, x)) - - -def push_into_tree( - root: nodes.BigFrameNode, - exprs: Sequence[nodes.ColumnDef], - target_ids: Sequence[identifiers.ColumnId], -) -> nodes.BigFrameNode: - curr_root = root - by_id = {expr.id: expr for expr in exprs} - # id -> id - graph = graphs.DiGraph( - (expr.id for expr in exprs), - ( - (expr.id, child_id) - for expr in exprs - for child_id in expr.expression.column_references - if child_id in by_id.keys() - ), - ) - # TODO: Also prevent inlining expensive or non-deterministic - # We avoid inlining multi-parent ids, as they would be inlined multiple places, potentially increasing work and/or compiled text size - multi_parent_ids = set(id for id in graph.nodes if len(list(graph.parents(id))) > 2) - scalar_ids = set(expr.id for expr in exprs if expr.expression.is_scalar_expr) - - analytic_defs = filter( - lambda x: isinstance(x.expression, agg_expressions.WindowExpression), exprs - ) - analytic_by_window = grouped( - map( - lambda x: (cast(agg_expressions.WindowExpression, x.expression).window, x), - analytic_defs, - ) - ) - - def graph_extract_scalar_exprs() -> Sequence[nodes.ColumnDef]: - results: dict[identifiers.ColumnId, expression.Expression] = dict() - while True: # Will converge as each loop either reduces graph size, or fails to find any candidate and breaks - candidate_ids = list( - id - for id in graph.sinks - if (id in scalar_ids) - and not any( - ( - child in multi_parent_ids - and id in results.keys() - and not is_simple(results[id]) - ) - for child in graph.children(id) - ) - ) - if len(candidate_ids) == 0: - break - for id in candidate_ids: - graph.remove_node(id) - new_exprs = { - id: by_id[id].expression.bind_refs( - results, allow_partial_bindings=True - ) - } - results.update(new_exprs) - # TODO: We can prune expressions that won't be reused here, - return tuple(nodes.ColumnDef(expr, id) for id, expr in results.items()) - - def graph_extract_window_expr() -> Optional[ - Tuple[Sequence[nodes.ColumnDef], window_spec.WindowSpec] - ]: - for id in graph.sinks: - next_def = by_id[id] - if isinstance(next_def.expression, agg_expressions.WindowExpression): - window = next_def.expression.window - window_exprs = [ - cdef - for cdef in analytic_by_window[window] - if cdef.id in graph.sinks - ] - agg_exprs = tuple( - nodes.ColumnDef( - cast( - agg_expressions.WindowExpression, cdef.expression - ).analytic_expr, - cdef.id, - ) - for cdef in window_exprs - ) - for cdef in window_exprs: - graph.remove_node(cdef.id) - return (agg_exprs, window) - - return None - - while not graph.empty: - pre_size = len(graph.nodes) - scalar_exprs = graph_extract_scalar_exprs() - if scalar_exprs: - curr_root = nodes.ProjectionNode( - curr_root, tuple((x.expression, x.id) for x in scalar_exprs) - ) - while result := graph_extract_window_expr(): - defs, window = result - assert len(defs) > 0 - curr_root = nodes.WindowOpNode( - curr_root, - tuple(defs), - window, - ) - if len(graph.nodes) >= pre_size: - raise ValueError("graph didn't shrink") - # TODO: Try to get the ordering right earlier, so can avoid this extra node. - post_ids = (*root.ids, *target_ids) - if tuple(curr_root.ids) != post_ids: - curr_root = nodes.SelectionNode( - curr_root, tuple(nodes.AliasedRef.identity(id) for id in post_ids) - ) - return curr_root - - -@functools.cache -def is_simple(expr: expression.Expression) -> bool: - count = 0 - for part in expr.walk(): - count += 1 - if count > _MAX_INLINE_COMPLEXITY: - return False - return True - - -K = TypeVar("K", bound=Hashable) -V = TypeVar("V") - - -def grouped(values: Iterable[tuple[K, V]]) -> dict[K, list[V]]: - result = collections.defaultdict(list) - for k, v in values: - result[k].append(v) - return result - - -def dedupe(values: Iterable[K]) -> Iterator[K]: - seen = set() - for k in values: - if k not in seen: - seen.add(k) - yield k diff --git a/bigframes/core/field.py b/bigframes/core/field.py deleted file mode 100644 index c5b7dd35559..00000000000 --- a/bigframes/core/field.py +++ /dev/null @@ -1,37 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from __future__ import annotations - -import dataclasses - -from bigframes import dtypes -from bigframes.core import identifiers - - -@dataclasses.dataclass(frozen=True) -class Field: - id: identifiers.ColumnId - dtype: dtypes.Dtype - # Best effort, nullable=True if not certain - nullable: bool = True - - def with_nullable(self) -> Field: - return Field(self.id, self.dtype, nullable=True) - - def with_nonnull(self) -> Field: - return Field(self.id, self.dtype, nullable=False) - - def with_id(self, id: identifiers.ColumnId) -> Field: - return Field(id, self.dtype, nullable=self.nullable) diff --git a/bigframes/core/global_session.py b/bigframes/core/global_session.py index a38280e6447..1f960839a0a 100644 --- a/bigframes/core/global_session.py +++ b/bigframes/core/global_session.py @@ -14,78 +14,31 @@ """Utilities for managing a default, globally available Session object.""" -from __future__ import annotations - import threading -import traceback -import warnings -from typing import TYPE_CHECKING, Callable, Iterable, Optional, TypeVar - -import google.auth.exceptions - -import bigframes.exceptions as bfe +from typing import Callable, Optional, TypeVar -if TYPE_CHECKING: - import bigframes.session +import bigframes._config +import bigframes.session _global_session: Optional[bigframes.session.Session] = None _global_session_lock = threading.Lock() -_global_session_state = threading.local() -_global_session_state.thread_local_session = None - - -def _try_close_session(session: bigframes.session.Session): - """Try to close the session and warn if couldn't.""" - try: - session.close() - except google.auth.exceptions.RefreshError as e: - session_id = session.session_id - location = session._location - project_id = session._project - msg = bfe.format_message( - f"Session cleanup failed for session with id: {session_id}, " - f"location: {location}, project: {project_id}" - ) - warnings.warn(msg, category=bfe.CleanupFailedWarning) - traceback.print_tb(e.__traceback__) def close_session() -> None: """Start a fresh session the next time a function requires a session. - Closes the current session if it was already started, deleting any - temporary tables that were created. + Closes the current session if it was already started. Returns: None """ - # Avoid troubles with circular imports. - import bigframes._config - - global _global_session, _global_session_lock, _global_session_state - - if bigframes._config.options.is_bigquery_thread_local: - if _global_session_state.thread_local_session is not None: - _try_close_session(_global_session_state.thread_local_session) - _global_session_state.thread_local_session = None - - # Currently using thread-local options, so no global lock needed. - # Don't reset options.bigquery, as that's the responsibility - # of the context manager that started it in the first place. The user - # might have explicitly closed the session in the context manager and - # the thread-locality property needs to be retained. - bigframes._config.options.bigquery._session_started = False - - # Don't close the non-thread-local session. - return + global _global_session with _global_session_lock: if _global_session is not None: - _try_close_session(_global_session) + _global_session.close() _global_session = None - # This should be global, not thread-local because of the if clause - # above. bigframes._config.options.bigquery._session_started = False @@ -94,19 +47,7 @@ def get_global_session(): Creates the global session if it does not exist. """ - # Avoid troubles with circular imports. - import bigframes._config - import bigframes.session - - global _global_session, _global_session_lock, _global_session_state - - if bigframes._config.options.is_bigquery_thread_local: - if _global_session_state.thread_local_session is None: - _global_session_state.thread_local_session = bigframes.session.connect( - bigframes._config.options.bigquery - ) - - return _global_session_state.thread_local_session + global _global_session, _global_session_lock with _global_session_lock: if _global_session is None: @@ -120,41 +61,5 @@ def get_global_session(): _T = TypeVar("_T") -def with_default_session(func_: Callable[..., _T], *args, **kwargs) -> _T: - return func_(get_global_session(), *args, **kwargs) - - -def execution_history( - *, - events: Optional[Iterable[bigframes.core.events.Event]] = None, - job_ids: Optional[Iterable[str]] = None, - all_cells: bool = True, -) -> "bigframes.session._ExecutionHistory": - import bigframes.session - - return with_default_session( - bigframes.session.Session.execution_history, - events=events, - job_ids=job_ids, - all_cells=all_cells, - ) - - -class _GlobalSessionContext: - """ - Context manager for testing that sets global session. - """ - - def __init__(self, session: bigframes.session.Session): - self._session = session - - def __enter__(self): - global _global_session, _global_session_lock - with _global_session_lock: - self._previous_session = _global_session - _global_session = self._session - - def __exit__(self, *exc_details): - global _global_session, _global_session_lock - with _global_session_lock: - _global_session = self._previous_session +def with_default_session(func: Callable[..., _T], *args, **kwargs) -> _T: + return func(get_global_session(), *args, **kwargs) diff --git a/bigframes/core/googlesql.py b/bigframes/core/googlesql.py deleted file mode 100644 index 8869fbff3ef..00000000000 --- a/bigframes/core/googlesql.py +++ /dev/null @@ -1,116 +0,0 @@ -# Copyright 2026 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. - -"""Utilities for working with GoogleSqlScalarOps.""" - -from __future__ import annotations - -from typing import TYPE_CHECKING, Any, Optional, Union - -import pandas as pd - -import bigframes.core.col -import bigframes.core.expression as ex -import bigframes.core.global_session as global_session -import bigframes.core.sentinels as sentinels -import bigframes.series as series -from bigframes.operations import googlesql - -if TYPE_CHECKING: - import bigframes.session - - -def _is_pandas_series(arg: Any) -> bool: - return isinstance(arg, pd.Series) - - -def _find_session(*args: Any) -> Optional[bigframes.session.Session]: - import bigframes.core.indexes as indexes - import bigframes.dataframe as dataframe - - for arg in args: - if isinstance(arg, (series.Series, dataframe.DataFrame, indexes.Index)): - return arg._session - return None - - -def _get_session(*args: Any) -> bigframes.session.Session: - session = _find_session(*args) - if session is not None: - return session - return global_session.get_global_session() - - -def apply_googlesql_scalar_op( - op: googlesql.GoogleSqlScalarOp, - *args: Any, -) -> Union[series.Series, bigframes.core.col.Expression]: - """Applies a GoogleSQL scalar operator to the given arguments. - - Handles a mix of Series, Expression, and literal inputs. - - Args: - op (googlesql.GoogleSqlScalarOp): - The operator to apply. - *args (Any): - The arguments to apply the operator to. - - Returns: - bigframes.pandas.Series | bigframes.core.col.Expression: - The result of the operation. If any of ``args`` is a Series, returns - a Series. Otherwise, returns an Expression. - """ - has_pandas_series = any(_is_pandas_series(arg) for arg in args) - - if has_pandas_series: - session = _get_session(*args) - args = tuple( - session.read_pandas(arg) if _is_pandas_series(arg) else arg for arg in args - ) - - # Find the first Series to use for alignment - first_series = None - for arg in args: - if isinstance(arg, series.Series): - first_series = arg - break - - if first_series is not None: - processed_args: list[Union[bigframes.core.col.Expression, series.Series]] = [] - block = first_series._block - for arg in args: - if isinstance(arg, bigframes.core.col.Expression): - block, col_id = block.project_expr(bigframes.core.col._as_bf_expr(arg)) - processed_args.append(series.Series(block.select_column(col_id))) - elif arg is sentinels.Sentinel.ARGUMENT_DEFAULT: - processed_args.append(bigframes.core.col.Expression(ex.OmittedArg())) - else: - processed_args.append(arg) - - # Apply the n-ary op. _apply_nary_op handles alignment of Series and literals. - result = first_series._apply_nary_op(op, processed_args, ignore_self=True) - result.name = None - return result - - # No Series, return an Expression - expr_args = [] - for arg in args: - if isinstance(arg, bigframes.core.col.Expression): - expr_args.append(bigframes.core.col._as_bf_expr(arg)) - elif arg is sentinels.Sentinel.ARGUMENT_DEFAULT: - expr_args.append(ex.OmittedArg()) - else: - expr_args.append(ex.const(arg)) - - return bigframes.core.col.Expression(ex.OpExpression(op, tuple(expr_args))) diff --git a/bigframes/core/graphs.py b/bigframes/core/graphs.py deleted file mode 100644 index b7ce80e3cf0..00000000000 --- a/bigframes/core/graphs.py +++ /dev/null @@ -1,76 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import collections -from typing import Dict, Generic, Hashable, Iterable, Iterator, Tuple, TypeVar - -import bigframes.core.ordered_sets as sets - -T = TypeVar("T", bound=Hashable) - - -class DiGraph(Generic[T]): - def __init__(self, nodes: Iterable[T], edges: Iterable[Tuple[T, T]]): - self._parents: Dict[T, sets.InsertionOrderedSet[T]] = collections.defaultdict( - sets.InsertionOrderedSet - ) - self._children: Dict[T, sets.InsertionOrderedSet[T]] = collections.defaultdict( - sets.InsertionOrderedSet - ) - self._sinks: sets.InsertionOrderedSet[T] = sets.InsertionOrderedSet() - for node in nodes: - self._children[node] - self._parents[node] - self._sinks.add(node) - for src, dst in edges: - assert src in self.nodes - assert dst in self.nodes - self._children[src].add(dst) - self._parents[dst].add(src) - # sinks have no children - if src in self._sinks: - self._sinks.remove(src) - - @property - def nodes(self): - # should be the same set of ids as self._parents - return self._children.keys() - - @property - def sinks(self) -> Iterable[T]: - return self._sinks - - @property - def empty(self): - return len(self.nodes) == 0 - - def parents(self, node: T) -> Iterator[T]: - assert node in self._parents - yield from self._parents[node] - - def children(self, node: T) -> Iterator[T]: - assert node in self._children - yield from self._children[node] - - def remove_node(self, node: T) -> None: - for child in self._children[node]: - self._parents[child].remove(node) - for parent in self._parents[node]: - self._children[parent].remove(node) - if len(self._children[parent]) == 0: - self._sinks.add(parent) - del self._children[node] - del self._parents[node] - if node in self._sinks: - self._sinks.remove(node) diff --git a/bigframes/core/groupby/__init__.py b/bigframes/core/groupby/__init__.py index fe44911858f..2a19a83dd5f 100644 --- a/bigframes/core/groupby/__init__.py +++ b/bigframes/core/groupby/__init__.py @@ -12,7 +12,599 @@ # See the License for the specific language governing permissions and # limitations under the License. -from bigframes.core.groupby.dataframe_group_by import DataFrameGroupBy -from bigframes.core.groupby.series_group_by import SeriesGroupBy +from __future__ import annotations -__all__ = ["DataFrameGroupBy", "SeriesGroupBy"] +import typing + +import pandas as pd + +import bigframes.constants as constants +import bigframes.core as core +import bigframes.core.block_transforms as block_ops +import bigframes.core.blocks as blocks +import bigframes.core.ordering as order +import bigframes.core.utils as utils +import bigframes.core.window as windows +import bigframes.dataframe as df +import bigframes.dtypes as dtypes +import bigframes.operations as ops +import bigframes.operations.aggregations as agg_ops +import bigframes.series as series +import third_party.bigframes_vendored.pandas.core.groupby as vendored_pandas_groupby + + +class DataFrameGroupBy(vendored_pandas_groupby.DataFrameGroupBy): + __doc__ = vendored_pandas_groupby.GroupBy.__doc__ + + def __init__( + self, + block: blocks.Block, + by_col_ids: typing.Sequence[str], + *, + selected_cols: typing.Optional[typing.Sequence[str]] = None, + dropna: bool = True, + as_index: bool = True, + ): + # TODO(tbergeron): Support more group-by expression types + self._block = block + self._col_id_labels = { + value_column: column_label + for value_column, column_label in zip( + block.value_columns, block.column_labels + ) + } + self._by_col_ids = by_col_ids + + self._dropna = dropna + self._as_index = as_index + if selected_cols: + for col in selected_cols: + if col not in self._block.value_columns: + raise ValueError(f"Invalid column selection: {col}") + self._selected_cols = selected_cols + else: + self._selected_cols = [ + col_id + for col_id in self._block.value_columns + if col_id not in self._by_col_ids + ] + + def __getitem__( + self, + key: typing.Union[ + blocks.Label, + typing.Sequence[blocks.Label], + ], + ): + if utils.is_list_like(key): + keys = list(key) + else: + keys = [key] + columns = [ + col_id for col_id, label in self._col_id_labels.items() if label in keys + ] + + if len(columns) > 1 or (not self._as_index): + return DataFrameGroupBy( + self._block, + self._by_col_ids, + selected_cols=columns, + dropna=self._dropna, + as_index=self._as_index, + ) + else: + return SeriesGroupBy( + self._block, + columns[0], + self._by_col_ids, + value_name=self._col_id_labels[columns[0]], + dropna=self._dropna, + ) + + def sum(self, numeric_only: bool = False, *args) -> df.DataFrame: + if not numeric_only: + self._raise_on_non_numeric("sum") + return self._aggregate_all(agg_ops.sum_op, numeric_only=True) + + def mean(self, numeric_only: bool = False, *args) -> df.DataFrame: + if not numeric_only: + self._raise_on_non_numeric("mean") + return self._aggregate_all(agg_ops.mean_op, numeric_only=True) + + def median( + self, numeric_only: bool = False, *, exact: bool = False + ) -> df.DataFrame: + if exact: + raise NotImplementedError( + f"Only approximate median is supported. {constants.FEEDBACK_LINK}" + ) + if not numeric_only: + self._raise_on_non_numeric("median") + return self._aggregate_all(agg_ops.median_op, numeric_only=True) + + def min(self, numeric_only: bool = False, *args) -> df.DataFrame: + return self._aggregate_all(agg_ops.min_op, numeric_only=numeric_only) + + def max(self, numeric_only: bool = False, *args) -> df.DataFrame: + return self._aggregate_all(agg_ops.max_op, numeric_only=numeric_only) + + def std( + self, + *, + numeric_only: bool = False, + ) -> df.DataFrame: + if not numeric_only: + self._raise_on_non_numeric("std") + return self._aggregate_all(agg_ops.std_op, numeric_only=True) + + def var( + self, + *, + numeric_only: bool = False, + ) -> df.DataFrame: + if not numeric_only: + self._raise_on_non_numeric("var") + return self._aggregate_all(agg_ops.var_op, numeric_only=True) + + def skew( + self, + *, + numeric_only: bool = False, + ) -> df.DataFrame: + if not numeric_only: + self._raise_on_non_numeric("skew") + block = block_ops.skew(self._block, self._selected_cols, self._by_col_ids) + return df.DataFrame(block) + + def kurt( + self, + *, + numeric_only: bool = False, + ) -> df.DataFrame: + if not numeric_only: + self._raise_on_non_numeric("kurt") + block = block_ops.kurt(self._block, self._selected_cols, self._by_col_ids) + return df.DataFrame(block) + + kurtosis = kurt + + def all(self) -> df.DataFrame: + return self._aggregate_all(agg_ops.all_op) + + def any(self) -> df.DataFrame: + return self._aggregate_all(agg_ops.any_op) + + def count(self) -> df.DataFrame: + return self._aggregate_all(agg_ops.count_op) + + def cumsum(self, *args, numeric_only: bool = False, **kwargs) -> df.DataFrame: + if not numeric_only: + self._raise_on_non_numeric("cumsum") + return self._apply_window_op(agg_ops.sum_op, numeric_only=True) + + def cummin(self, *args, numeric_only: bool = False, **kwargs) -> df.DataFrame: + return self._apply_window_op(agg_ops.min_op, numeric_only=numeric_only) + + def cummax(self, *args, numeric_only: bool = False, **kwargs) -> df.DataFrame: + return self._apply_window_op(agg_ops.max_op, numeric_only=numeric_only) + + def cumprod(self, *args, **kwargs) -> df.DataFrame: + return self._apply_window_op(agg_ops.product_op, numeric_only=True) + + def shift(self, periods=1) -> series.Series: + window = core.WindowSpec( + grouping_keys=tuple(self._by_col_ids), + preceding=periods if periods > 0 else None, + following=-periods if periods < 0 else None, + ) + return self._apply_window_op(agg_ops.ShiftOp(periods), window=window) + + def diff(self, periods=1) -> series.Series: + window = core.WindowSpec( + grouping_keys=tuple(self._by_col_ids), + preceding=periods if periods > 0 else None, + following=-periods if periods < 0 else None, + ) + return self._apply_window_op(agg_ops.DiffOp(periods), window=window) + + def rolling(self, window: int, min_periods=None) -> windows.Window: + # To get n size window, need current row and n-1 preceding rows. + window_spec = core.WindowSpec( + grouping_keys=tuple(self._by_col_ids), + preceding=window - 1, + following=0, + min_periods=min_periods or window, + ) + block = self._block.order_by( + [order.OrderingColumnReference(col) for col in self._by_col_ids], + stable=True, + ) + return windows.Window( + block, window_spec, self._selected_cols, drop_null_groups=self._dropna + ) + + def expanding(self, min_periods: int = 1) -> windows.Window: + window_spec = core.WindowSpec( + grouping_keys=tuple(self._by_col_ids), + following=0, + min_periods=min_periods, + ) + block = self._block.order_by( + [order.OrderingColumnReference(col) for col in self._by_col_ids], + stable=True, + ) + return windows.Window( + block, window_spec, self._selected_cols, drop_null_groups=self._dropna + ) + + def agg(self, func=None, **kwargs) -> df.DataFrame: + if func: + if isinstance(func, str): + return self._agg_string(func) + elif utils.is_dict_like(func): + return self._agg_dict(func) + elif utils.is_list_like(func): + return self._agg_list(func) + else: + raise NotImplementedError( + f"Aggregate with {func} not supported. {constants.FEEDBACK_LINK}" + ) + else: + return self._agg_named(**kwargs) + + def _agg_string(self, func: str) -> df.DataFrame: + aggregations = [ + (col_id, agg_ops.lookup_agg_func(func)) + for col_id in self._aggregated_columns() + ] + agg_block, _ = self._block.aggregate( + by_column_ids=self._by_col_ids, + aggregations=aggregations, + as_index=self._as_index, + dropna=self._dropna, + ) + return df.DataFrame(agg_block) + + def _agg_dict(self, func: typing.Mapping) -> df.DataFrame: + aggregations: typing.List[typing.Tuple[str, agg_ops.AggregateOp]] = [] + column_labels = [] + + want_aggfunc_level = any(utils.is_list_like(aggs) for aggs in func.values()) + + for label, funcs_for_id in func.items(): + col_id = self._resolve_label(label) + func_list = ( + funcs_for_id if utils.is_list_like(funcs_for_id) else [funcs_for_id] + ) + for f in func_list: + aggregations.append((col_id, agg_ops.lookup_agg_func(f))) + column_labels.append(label) + agg_block, _ = self._block.aggregate( + by_column_ids=self._by_col_ids, + aggregations=aggregations, + as_index=self._as_index, + dropna=self._dropna, + ) + if want_aggfunc_level: + agg_block = agg_block.with_column_labels( + utils.combine_indices( + pd.Index(column_labels), + pd.Index(agg[1].name for agg in aggregations), + ) + ) + else: + agg_block = agg_block.with_column_labels(pd.Index(column_labels)) + return df.DataFrame(agg_block) + + def _agg_list(self, func: typing.Sequence) -> df.DataFrame: + aggregations = [ + (col_id, agg_ops.lookup_agg_func(f)) + for col_id in self._aggregated_columns() + for f in func + ] + column_labels = [ + (col_id, f) for col_id in self._aggregated_columns() for f in func + ] + agg_block, _ = self._block.aggregate( + by_column_ids=self._by_col_ids, + aggregations=aggregations, + as_index=self._as_index, + dropna=self._dropna, + ) + agg_block = agg_block.with_column_labels( + pd.MultiIndex.from_tuples( + column_labels, names=[*self._block.column_labels.names, None] + ) + ) + return df.DataFrame(agg_block) + + def _agg_named(self, **kwargs) -> df.DataFrame: + aggregations = [] + column_labels = [] + for k, v in kwargs.items(): + if not isinstance(k, str): + raise NotImplementedError( + f"Only string aggregate names supported. {constants.FEEDBACK_LINK}" + ) + if not hasattr(v, "column") or not hasattr(v, "aggfunc"): + import bigframes.pandas as bpd + + raise TypeError(f"kwargs values must be {bpd.NamedAgg.__qualname__}") + col_id = self._resolve_label(v.column) + aggregations.append((col_id, agg_ops.lookup_agg_func(v.aggfunc))) + column_labels.append(k) + agg_block, _ = self._block.aggregate( + by_column_ids=self._by_col_ids, + aggregations=aggregations, + as_index=self._as_index, + dropna=self._dropna, + ) + agg_block = agg_block.with_column_labels(column_labels) + return df.DataFrame(agg_block) + + aggregate = agg + + def _raise_on_non_numeric(self, op: str): + if not all( + dtype in dtypes.NUMERIC_BIGFRAMES_TYPES for dtype in self._block.dtypes + ): + raise NotImplementedError( + f"'{op}' does not support non-numeric columns. " + "Set 'numeric_only'=True to ignore non-numeric columns. " + f"{constants.FEEDBACK_LINK}" + ) + return self + + def _aggregated_columns(self, numeric_only: bool = False) -> typing.Sequence[str]: + valid_agg_cols: list[str] = [] + for col_id in self._selected_cols: + is_numeric = self._column_type(col_id) in dtypes.NUMERIC_BIGFRAMES_TYPES + if is_numeric or not numeric_only: + valid_agg_cols.append(col_id) + return valid_agg_cols + + def _column_type(self, col_id: str) -> dtypes.Dtype: + col_offset = self._block.value_columns.index(col_id) + dtype = self._block.dtypes[col_offset] + return dtype + + def _aggregate_all( + self, aggregate_op: agg_ops.AggregateOp, numeric_only: bool = False + ) -> df.DataFrame: + aggregated_col_ids = self._aggregated_columns(numeric_only=numeric_only) + aggregations = [(col_id, aggregate_op) for col_id in aggregated_col_ids] + result_block, _ = self._block.aggregate( + by_column_ids=self._by_col_ids, + aggregations=aggregations, + as_index=self._as_index, + dropna=self._dropna, + ) + return df.DataFrame(result_block) + + def _apply_window_op( + self, + op: agg_ops.WindowOp, + window: typing.Optional[core.WindowSpec] = None, + numeric_only: bool = False, + ): + """Apply window op to groupby. Defaults to grouped cumulative window.""" + window_spec = window or core.WindowSpec( + grouping_keys=tuple(self._by_col_ids), following=0 + ) + columns = self._aggregated_columns(numeric_only=numeric_only) + block, result_ids = self._block.multi_apply_window_op( + columns, op, window_spec=window_spec + ) + block = block.select_columns(result_ids) + return df.DataFrame(block) + + def _resolve_label(self, label: blocks.Label) -> str: + """Resolve label to column id.""" + col_ids = self._block.label_to_col_id.get(label, ()) + if len(col_ids) > 1: + raise ValueError(f"Label {label} is ambiguous") + if len(col_ids) == 0: + raise ValueError(f"Label {label} does not match any columns") + return col_ids[0] + + +class SeriesGroupBy(vendored_pandas_groupby.SeriesGroupBy): + __doc__ = vendored_pandas_groupby.GroupBy.__doc__ + + def __init__( + self, + block: blocks.Block, + value_column: str, + by_col_ids: typing.Sequence[str], + value_name: blocks.Label = None, + dropna=True, + ): + # TODO(tbergeron): Support more group-by expression types + self._block = block + self._value_column = value_column + self._by_col_ids = by_col_ids + self._value_name = value_name + self._dropna = dropna # Applies to aggregations but not windowing + + def all(self) -> series.Series: + return self._aggregate(agg_ops.all_op) + + def any(self) -> series.Series: + return self._aggregate(agg_ops.any_op) + + def min(self, *args) -> series.Series: + return self._aggregate(agg_ops.min_op) + + def max(self, *args) -> series.Series: + return self._aggregate(agg_ops.max_op) + + def count(self) -> series.Series: + return self._aggregate(agg_ops.count_op) + + def sum(self, *args) -> series.Series: + return self._aggregate(agg_ops.sum_op) + + def mean(self, *args) -> series.Series: + return self._aggregate(agg_ops.mean_op) + + def median(self, *args, **kwargs) -> series.Series: + return self._aggregate(agg_ops.mean_op) + + def std(self, *args, **kwargs) -> series.Series: + return self._aggregate(agg_ops.std_op) + + def var(self, *args, **kwargs) -> series.Series: + return self._aggregate(agg_ops.var_op) + + def skew(self, *args, **kwargs) -> series.Series: + block = block_ops.skew(self._block, [self._value_column], self._by_col_ids) + return series.Series(block) + + def kurt(self, *args, **kwargs) -> series.Series: + block = block_ops.kurt(self._block, [self._value_column], self._by_col_ids) + return series.Series(block) + + kurtosis = kurt + + def prod(self, *args) -> series.Series: + return self._aggregate(agg_ops.product_op) + + def agg(self, func=None) -> typing.Union[df.DataFrame, series.Series]: + column_names: list[str] = [] + if isinstance(func, str): + aggregations = [(self._value_column, agg_ops.lookup_agg_func(func))] + column_names = [func] + elif utils.is_list_like(func): + aggregations = [ + (self._value_column, agg_ops.lookup_agg_func(f)) for f in func + ] + column_names = list(func) + else: + raise NotImplementedError( + f"Aggregate with {func} not supported. {constants.FEEDBACK_LINK}" + ) + + agg_block, _ = self._block.aggregate( + by_column_ids=self._by_col_ids, + aggregations=aggregations, + dropna=self._dropna, + ) + + if column_names: + agg_block = agg_block.with_column_labels(column_names) + + if len(aggregations) > 1: + return df.DataFrame(agg_block) + return series.Series(agg_block) + + aggregate = agg + + def cumsum(self, *args, **kwargs) -> series.Series: + return self._apply_window_op( + agg_ops.sum_op, + ) + + def cumprod(self, *args, **kwargs) -> series.Series: + return self._apply_window_op( + agg_ops.product_op, + ) + + def cummax(self, *args, **kwargs) -> series.Series: + return self._apply_window_op( + agg_ops.max_op, + ) + + def cummin(self, *args, **kwargs) -> series.Series: + return self._apply_window_op( + agg_ops.min_op, + ) + + def cumcount(self, *args, **kwargs) -> series.Series: + return self._apply_window_op( + agg_ops.rank_op, + discard_name=True, + )._apply_unary_op(ops.partial_right(ops.sub_op, 1)) + + def shift(self, periods=1) -> series.Series: + """Shift index by desired number of periods.""" + window = core.WindowSpec( + grouping_keys=tuple(self._by_col_ids), + preceding=periods if periods > 0 else None, + following=-periods if periods < 0 else None, + ) + return self._apply_window_op(agg_ops.ShiftOp(periods), window=window) + + def diff(self, periods=1) -> series.Series: + window = core.WindowSpec( + grouping_keys=tuple(self._by_col_ids), + preceding=periods if periods > 0 else None, + following=-periods if periods < 0 else None, + ) + return self._apply_window_op(agg_ops.DiffOp(periods), window=window) + + def rolling(self, window: int, min_periods=None) -> windows.Window: + # To get n size window, need current row and n-1 preceding rows. + window_spec = core.WindowSpec( + grouping_keys=tuple(self._by_col_ids), + preceding=window - 1, + following=0, + min_periods=min_periods or window, + ) + block = self._block.order_by( + [order.OrderingColumnReference(col) for col in self._by_col_ids], + stable=True, + ) + return windows.Window( + block, + window_spec, + [self._value_column], + drop_null_groups=self._dropna, + is_series=True, + ) + + def expanding(self, min_periods: int = 1) -> windows.Window: + window_spec = core.WindowSpec( + grouping_keys=tuple(self._by_col_ids), + following=0, + min_periods=min_periods, + ) + block = self._block.order_by( + [order.OrderingColumnReference(col) for col in self._by_col_ids], + stable=True, + ) + return windows.Window( + block, + window_spec, + [self._value_column], + drop_null_groups=self._dropna, + is_series=True, + ) + + def _aggregate(self, aggregate_op: agg_ops.AggregateOp) -> series.Series: + result_block, _ = self._block.aggregate( + self._by_col_ids, + ((self._value_column, aggregate_op),), + dropna=self._dropna, + ) + + return series.Series(result_block.with_column_labels([self._value_name])) + + def _apply_window_op( + self, + op: agg_ops.WindowOp, + discard_name=False, + window: typing.Optional[core.WindowSpec] = None, + ): + """Apply window op to groupby. Defaults to grouped cumulative window.""" + window_spec = window or core.WindowSpec( + grouping_keys=tuple(self._by_col_ids), following=0 + ) + + label = self._value_name if not discard_name else None + block, result_id = self._block.apply_window_op( + self._value_column, + op, + result_label=label, + window_spec=window_spec, + ) + return series.Series(block.select_column(result_id)) diff --git a/bigframes/core/groupby/aggs.py b/bigframes/core/groupby/aggs.py deleted file mode 100644 index 9d8b957d547..00000000000 --- a/bigframes/core/groupby/aggs.py +++ /dev/null @@ -1,26 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from __future__ import annotations - -from bigframes.core import agg_expressions, expression -from bigframes.operations import aggregations as agg_ops - - -def agg(input: str, op: agg_ops.AggregateOp) -> agg_expressions.Aggregation: - if isinstance(op, agg_ops.UnaryAggregateOp): - return agg_expressions.UnaryAggregation(op, expression.deref(input)) - else: - assert isinstance(op, agg_ops.NullaryAggregateOp) - return agg_expressions.NullaryAggregation(op) diff --git a/bigframes/core/groupby/dataframe_group_by.py b/bigframes/core/groupby/dataframe_group_by.py deleted file mode 100644 index 7cc61d43a02..00000000000 --- a/bigframes/core/groupby/dataframe_group_by.py +++ /dev/null @@ -1,834 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from __future__ import annotations - -import datetime -import typing -from typing import Iterable, Literal, Optional, Sequence, Tuple, Union - -import bigframes_vendored.constants as constants -import bigframes_vendored.pandas.core.groupby as vendored_pandas_groupby -import numpy -import pandas as pd - -import bigframes.core.block_transforms as block_ops -import bigframes.core.block_transforms as block_transforms -import bigframes.core.blocks as blocks -import bigframes.core.ordering as order -import bigframes.core.utils as utils -import bigframes.core.validations as validations -import bigframes.core.window as windows -import bigframes.core.window_spec as window_specs -import bigframes.dataframe as df -import bigframes.dtypes as dtypes -import bigframes.operations -import bigframes.operations.aggregations as agg_ops -import bigframes.series as series -from bigframes import session -from bigframes._tools import docs -from bigframes.core import agg_expressions -from bigframes.core import expression as ex -from bigframes.core.groupby import aggs, group_by, series_group_by -from bigframes.core.logging import log_adapter -from bigframes.core.window import rolling - - -@log_adapter.class_logger -@docs.inherit_docs(vendored_pandas_groupby.DataFrameGroupBy) -class DataFrameGroupBy: - def __init__( - self, - block: blocks.Block, - by_col_ids: typing.Sequence[str], - *, - selected_cols: typing.Optional[typing.Sequence[str]] = None, - dropna: bool = True, - as_index: bool = True, - by_key_is_singular: bool = False, - ): - # TODO(tbergeron): Support more group-by expression types - self._block = block - self._col_id_labels = { - value_column: column_label - for value_column, column_label in zip( - block.value_columns, block.column_labels - ) - } - self._by_col_ids = by_col_ids - self._by_key_is_singular = by_key_is_singular - if by_key_is_singular: - assert len(by_col_ids) == 1, "singular key should be exactly one group key" - - self._dropna = dropna - self._as_index = as_index - if selected_cols: - for col in selected_cols: - if col not in self._block.value_columns: - raise ValueError(f"Invalid column selection: {col}") - self._selected_cols = selected_cols - else: - self._selected_cols = [ - col_id - for col_id in self._block.value_columns - if col_id not in self._by_col_ids - ] - - @property - def _session(self) -> session.Session: - return self._block.session - - def __getitem__( - self, - key: typing.Union[ - blocks.Label, - typing.Sequence[blocks.Label], - ], - ): - import bigframes._tools.strings - - if utils.is_list_like(key): - keys = list(key) - else: - keys = [key] - - bad_keys = [key for key in keys if key not in self._block.column_labels] - - # Raise a KeyError message with the possible correct key(s) - if len(bad_keys) > 0: - possible_key = [] - for bad_key in bad_keys: - possible_key.append( - min( - self._block.column_labels, - key=lambda item: bigframes._tools.strings.levenshtein_distance( - bad_key, item - ), - ) - ) - raise KeyError( - f"Columns not found: {str(bad_keys)[1:-1]}. Did you mean {str(possible_key)[1:-1]}?" - ) - - columns = [ - col_id for col_id, label in self._col_id_labels.items() if label in keys - ] - - if len(columns) > 1 or (not self._as_index): - return DataFrameGroupBy( - self._block, - self._by_col_ids, - selected_cols=columns, - dropna=self._dropna, - as_index=self._as_index, - ) - else: - return series_group_by.SeriesGroupBy( - self._block, - columns[0], - self._by_col_ids, - value_name=self._col_id_labels[columns[0]], - dropna=self._dropna, - ) - - @validations.requires_ordering() - def head(self, n: int = 5) -> df.DataFrame: - block = self._block - if self._dropna: - block = block_ops.dropna(self._block, self._by_col_ids, how="any") - return df.DataFrame( - block.grouped_head( - by_column_ids=self._by_col_ids, - value_columns=self._block.value_columns, - n=n, - ) - ) - - def describe(self, include: None | Literal["all"] = None): - from bigframes.pandas.core.methods import describe - - return df.DataFrame( - describe._describe( - self._block, - self._selected_cols, - include, - as_index=self._as_index, - by_col_ids=self._by_col_ids, - dropna=self._dropna, - ) - ) - - def __iter__(self) -> Iterable[Tuple[blocks.Label, df.DataFrame]]: - for group_keys, filtered_block in group_by.block_groupby_iter( - self._block, - by_col_ids=self._by_col_ids, - by_key_is_singular=self._by_key_is_singular, - dropna=self._dropna, - ): - filtered_df = df.DataFrame(filtered_block) - yield group_keys, filtered_df - - def __len__(self) -> int: - return len(self.agg([])) - - def size(self) -> typing.Union[df.DataFrame, series.Series]: - agg_block = self._block.aggregate( - aggregations=[agg_ops.SizeOp().as_expr()], - by_column_ids=self._by_col_ids, - dropna=self._dropna, - ) - agg_block = agg_block.with_column_labels(pd.Index(["size"])) - dataframe = df.DataFrame(agg_block) - - if self._as_index: - series = dataframe["size"] - return series.rename(None) - else: - return self._convert_index(dataframe) - - def sum(self, numeric_only: bool = False, *args) -> df.DataFrame: - if not numeric_only: - self._raise_on_non_numeric("sum") - return self._aggregate_all(agg_ops.sum_op, numeric_only=True) - - def mean(self, numeric_only: bool = False, *args) -> df.DataFrame: - if not numeric_only: - self._raise_on_non_numeric("mean") - return self._aggregate_all(agg_ops.mean_op, numeric_only=True) - - def median(self, numeric_only: bool = False, *, exact: bool = True) -> df.DataFrame: - if not numeric_only: - self._raise_on_non_numeric("median") - if exact: - return self.quantile(0.5) - return self._aggregate_all(agg_ops.median_op, numeric_only=True) - - def rank( - self, - method="average", - ascending: bool = True, - na_option: str = "keep", - pct: bool = False, - ) -> df.DataFrame: - return df.DataFrame( - block_ops.rank( - self._block, - method, - na_option, - ascending, - grouping_cols=tuple(self._by_col_ids), - columns=tuple(self._selected_cols), - pct=pct, - ) - ) - - def quantile( - self, q: Union[float, Sequence[float]] = 0.5, *, numeric_only: bool = False - ) -> df.DataFrame: - if not numeric_only: - self._raise_on_non_numeric("quantile") - q_cols = tuple( - col - for col in self._selected_cols - if self._column_type(col) in dtypes.NUMERIC_BIGFRAMES_TYPES_PERMISSIVE - ) - multi_q = utils.is_list_like(q) - result = block_ops.quantile( - self._block, - q_cols, - qs=tuple(q) if multi_q else (q,), # type: ignore - grouping_column_ids=self._by_col_ids, - dropna=self._dropna, - ) - result_df = df.DataFrame(result) - if multi_q: - return result_df.stack() - else: - return result_df.droplevel(-1, 1) - - def min(self, numeric_only: bool = False, *args) -> df.DataFrame: - return self._aggregate_all(agg_ops.min_op, numeric_only=numeric_only) - - def max(self, numeric_only: bool = False, *args) -> df.DataFrame: - return self._aggregate_all(agg_ops.max_op, numeric_only=numeric_only) - - def std( - self, - *, - numeric_only: bool = False, - ) -> df.DataFrame: - if not numeric_only: - self._raise_on_non_numeric("std") - return self._aggregate_all(agg_ops.std_op, numeric_only=True) - - def var( - self, - *, - numeric_only: bool = False, - ) -> df.DataFrame: - if not numeric_only: - self._raise_on_non_numeric("var") - return self._aggregate_all(agg_ops.var_op, numeric_only=True) - - def corr( - self, - *, - numeric_only: bool = False, - ) -> df.DataFrame: - if not numeric_only: - self._raise_on_non_numeric("corr") - if len(self._selected_cols) > 30: - raise ValueError( - f"Cannot calculate corr on >30 columns, dataframe has {len(self._selected_cols)} selected columns." - ) - - labels = self._block._get_labels_for_columns(self._selected_cols) - block = self._block - aggregations = [ - agg_expressions.BinaryAggregation( - agg_ops.CorrOp(), ex.deref(left_col), ex.deref(right_col) - ) - for left_col in self._selected_cols - for right_col in self._selected_cols - ] - # unique columns stops - uniq_orig_columns = utils.combine_indices(labels, pd.Index(range(len(labels)))) - result_labels = utils.cross_indices(uniq_orig_columns, uniq_orig_columns) - - block = block.aggregate( - by_column_ids=self._by_col_ids, - aggregations=aggregations, - column_labels=result_labels, - ) - - block = block.stack(levels=labels.nlevels + 1) - # Drop the last level of each index, which was created to guarantee uniqueness - return df.DataFrame(block).droplevel(-1, axis=0).droplevel(-1, axis=1) - - def cov( - self, - *, - numeric_only: bool = False, - ) -> df.DataFrame: - if not numeric_only: - self._raise_on_non_numeric("cov") - if len(self._selected_cols) > 30: - raise ValueError( - f"Cannot calculate cov on >30 columns, dataframe has {len(self._selected_cols)} selected columns." - ) - - labels = self._block._get_labels_for_columns(self._selected_cols) - block = self._block - aggregations = [ - agg_expressions.BinaryAggregation( - agg_ops.CovOp(), ex.deref(left_col), ex.deref(right_col) - ) - for left_col in self._selected_cols - for right_col in self._selected_cols - ] - # unique columns stops - uniq_orig_columns = utils.combine_indices(labels, pd.Index(range(len(labels)))) - result_labels = utils.cross_indices(uniq_orig_columns, uniq_orig_columns) - - block = block.aggregate( - by_column_ids=self._by_col_ids, - aggregations=aggregations, - column_labels=result_labels, - ) - - block = block.stack(levels=labels.nlevels + 1) - # Drop the last level of each index, which was created to guarantee uniqueness - return df.DataFrame(block).droplevel(-1, axis=0).droplevel(-1, axis=1) - - def skew( - self, - *, - numeric_only: bool = False, - ) -> df.DataFrame: - if not numeric_only: - self._raise_on_non_numeric("skew") - block = block_ops.skew(self._block, self._selected_cols, self._by_col_ids) - return df.DataFrame(block) - - def kurt( - self, - *, - numeric_only: bool = False, - ) -> df.DataFrame: - if not numeric_only: - self._raise_on_non_numeric("kurt") - block = block_ops.kurt(self._block, self._selected_cols, self._by_col_ids) - return df.DataFrame(block) - - kurtosis = kurt - - @validations.requires_ordering() - def first(self, numeric_only: bool = False, min_count: int = -1) -> df.DataFrame: - window_spec = window_specs.unbound( - grouping_keys=tuple(self._by_col_ids), - min_periods=min_count if min_count >= 0 else 0, - ) - target_cols, index = self._aggregated_columns(numeric_only) - block, firsts_ids = self._block.multi_apply_window_op( - target_cols, - agg_ops.FirstNonNullOp(), - window_spec=window_spec, - ) - block = block.aggregate( - by_column_ids=self._by_col_ids, - aggregations=tuple( - aggs.agg(firsts_id, agg_ops.AnyValueOp()) for firsts_id in firsts_ids - ), - dropna=self._dropna, - column_labels=index, - ) - return df.DataFrame(block) - - @validations.requires_ordering() - def last(self, numeric_only: bool = False, min_count: int = -1) -> df.DataFrame: - window_spec = window_specs.unbound( - grouping_keys=tuple(self._by_col_ids), - min_periods=min_count if min_count >= 0 else 0, - ) - target_cols, index = self._aggregated_columns(numeric_only) - block, lasts_ids = self._block.multi_apply_window_op( - target_cols, - agg_ops.LastNonNullOp(), - window_spec=window_spec, - ) - block = block.aggregate( - by_column_ids=self._by_col_ids, - aggregations=tuple( - aggs.agg(lasts_id, agg_ops.AnyValueOp()) for lasts_id in lasts_ids - ), - dropna=self._dropna, - column_labels=index, - ) - return df.DataFrame(block) - - def all(self) -> df.DataFrame: - return self._aggregate_all(agg_ops.all_op) - - def any(self) -> df.DataFrame: - return self._aggregate_all(agg_ops.any_op) - - def count(self) -> df.DataFrame: - return self._aggregate_all(agg_ops.count_op) - - def nunique(self) -> df.DataFrame: - return self._aggregate_all(agg_ops.nunique_op) - - @validations.requires_ordering() - def cumcount(self, ascending: bool = True) -> series.Series: - window_spec = ( - window_specs.cumulative_rows(grouping_keys=tuple(self._by_col_ids)) - if ascending - else window_specs.inverse_cumulative_rows( - grouping_keys=tuple(self._by_col_ids) - ) - ) - block, result_ids = self._block.apply_analytic( - [agg_expressions.NullaryAggregation(agg_ops.size_op)], - window=window_spec, - result_labels=[None], - ) - result = series.Series(block.select_columns(result_ids)) - 1 - if self._dropna and (len(self._by_col_ids) == 1): - result = result.mask( - series.Series(block.select_column(self._by_col_ids[0])).isna() - ) - return result - - @validations.requires_ordering() - def cumsum(self, *args, numeric_only: bool = False, **kwargs) -> df.DataFrame: - if not numeric_only: - self._raise_on_non_numeric("cumsum") - return self._apply_window_op(agg_ops.sum_op, numeric_only=True) - - @validations.requires_ordering() - def cummin(self, *args, numeric_only: bool = False, **kwargs) -> df.DataFrame: - return self._apply_window_op(agg_ops.min_op, numeric_only=numeric_only) - - @validations.requires_ordering() - def cummax(self, *args, numeric_only: bool = False, **kwargs) -> df.DataFrame: - return self._apply_window_op(agg_ops.max_op, numeric_only=numeric_only) - - @validations.requires_ordering() - def cumprod(self, *args, **kwargs) -> df.DataFrame: - return self._apply_window_op(agg_ops.product_op, numeric_only=True) - - @validations.requires_ordering() - def shift(self, periods=1) -> series.Series: - # Window framing clause is not allowed for analytic function lag. - window = window_specs.unbound( - grouping_keys=tuple(self._by_col_ids), - ) - return self._apply_window_op(agg_ops.ShiftOp(periods), window=window) - - @validations.requires_ordering() - def diff(self, periods=1) -> series.Series: - # Window framing clause is not allowed for analytic function lag. - window = window_specs.rows( - grouping_keys=tuple(self._by_col_ids), - ) - return self._apply_window_op(agg_ops.DiffOp(periods), window=window) - - def value_counts( - self, - subset: Optional[Sequence[blocks.Label]] = None, - normalize: bool = False, - sort: bool = True, - ascending: bool = False, - dropna: bool = True, - ) -> Union[df.DataFrame, series.Series]: - if subset is None: - columns = self._selected_cols - else: - columns = [ - column - for column in self._block.value_columns - if self._block.col_id_to_label[column] in subset - ] - block = self._block - if self._dropna: # this drops null grouping columns - block = block_ops.dropna(block, self._by_col_ids) - block = block_ops.value_counts( - block, - columns, - normalize=normalize, - sort=sort, - ascending=ascending, - drop_na=dropna, # this drops null value columns - grouping_keys=self._by_col_ids, - ) - if self._as_index: - return series.Series(block) - else: - return series.Series(block).to_frame().reset_index(drop=False) - - @validations.requires_ordering() - def rolling( - self, - window: int | pd.Timedelta | numpy.timedelta64 | datetime.timedelta | str, - min_periods=None, - on: str | None = None, - closed: Literal["right", "left", "both", "neither"] = "right", - ) -> windows.Window: - if isinstance(window, int): - window_spec = window_specs.WindowSpec( - bounds=window_specs.RowsWindowBounds.from_window_size(window, closed), - min_periods=min_periods if min_periods is not None else window, - grouping_keys=tuple(ex.deref(col) for col in self._by_col_ids), - ) - block = self._block.order_by( - [order.ascending_over(col) for col in self._by_col_ids], - ) - skip_agg_col_id = ( - None if on is None else self._block.resolve_label_exact_or_error(on) - ) - return windows.Window( - block, - window_spec, - self._selected_cols, - drop_null_groups=self._dropna, - skip_agg_column_id=skip_agg_col_id, - ) - - return rolling.create_range_window( - self._block, - window, - min_periods=min_periods, - value_column_ids=self._selected_cols, - on=on, - closed=closed, - is_series=False, - grouping_keys=self._by_col_ids, - drop_null_groups=self._dropna, - ) - - @validations.requires_ordering() - def expanding(self, min_periods: int = 1) -> windows.Window: - window_spec = window_specs.cumulative_rows( - grouping_keys=tuple(self._by_col_ids), - min_periods=min_periods, - ) - block = self._block.order_by( - [order.ascending_over(col) for col in self._by_col_ids], - ) - return windows.Window( - block, window_spec, self._selected_cols, drop_null_groups=self._dropna - ) - - def agg(self, func=None, **kwargs) -> typing.Union[df.DataFrame, series.Series]: - if func: - if utils.is_dict_like(func): - return self._agg_dict(func) - elif utils.is_list_like(func): - return self._agg_list(func) - else: - return self.size() if func == "size" else self._agg_func(func) - else: - return self._agg_named(**kwargs) - - def transform(self, func, *args, **kwargs) -> df.DataFrame: - if block_transforms.is_transpiler_eligible(func): - window_spec = window_specs.unbound(grouping_keys=tuple(self._by_col_ids)) - target_cols, labels = self._aggregated_columns() - exprs = [] - for col_id in target_cols: - expr, _ = block_transforms.compile_column_udf( - self._block, - func, - col_id, - args=args, - kwargs=kwargs, - window_spec=window_spec, - ) - exprs.append(expr) - - block = self._block.project_block_exprs( - exprs, - labels=labels, - drop=True, - ) - return df.DataFrame(block) - - raise NotImplementedError( - "DataFrameGroupBy.transform is only supported when experiments.enable_python_transpiler is True and a transpiler-compatible python function is provided." - ) - - def _agg_func(self, func) -> df.DataFrame: - ids, labels = self._aggregated_columns() - aggregations = [] - for col_id in ids: - if block_transforms.is_transpiler_eligible(func): - expr, _ = block_transforms.compile_column_udf(self._block, func, col_id) - aggregations.append(expr) - else: - aggregations.append(aggs.agg(col_id, agg_ops.lookup_agg_func(func)[0])) - - agg_block = self._block.aggregate( - by_column_ids=self._by_col_ids, - aggregations=aggregations, - dropna=self._dropna, - column_labels=labels, - ) - dataframe = df.DataFrame(agg_block) - return dataframe if self._as_index else self._convert_index(dataframe) - - def _agg_dict(self, func: typing.Mapping) -> df.DataFrame: - aggregations: typing.List[ex.Expression] = [] - column_labels = [] - function_labels = [] - - want_aggfunc_level = any(utils.is_list_like(aggs) for aggs in func.values()) - - for label, funcs_for_id in func.items(): - col_id = self._resolve_label(label) - func_list = ( - funcs_for_id if utils.is_list_like(funcs_for_id) else [funcs_for_id] - ) - for f in func_list: - if block_transforms.is_transpiler_eligible(f): - expr, name = block_transforms.compile_column_udf( - self._block, f, col_id - ) - aggregations.append(expr) - column_labels.append(label) - function_labels.append(name) - else: - f_op, f_label = agg_ops.lookup_agg_func(f) - aggregations.append(aggs.agg(col_id, f_op)) - column_labels.append(label) - function_labels.append(f_label) - agg_block = self._block.aggregate( - by_column_ids=self._by_col_ids, - aggregations=aggregations, - dropna=self._dropna, - ) - if want_aggfunc_level: - agg_block = agg_block.with_column_labels( - utils.combine_indices( - pd.Index(column_labels), - pd.Index(function_labels), - ) - ) - else: - agg_block = agg_block.with_column_labels(pd.Index(column_labels)) - dataframe = df.DataFrame(agg_block) - return dataframe if self._as_index else self._convert_index(dataframe) - - def _agg_list(self, func: typing.Sequence) -> df.DataFrame: - ids, labels = self._aggregated_columns() - aggregations = [] - fn_labels = [] - - for f in func: - if block_transforms.is_transpiler_eligible(f): - fn_labels.append(getattr(f, "__name__", "")) - else: - fn_labels.append(agg_ops.lookup_agg_func(f)[1]) - - for col_id in ids: - for f in func: - if block_transforms.is_transpiler_eligible(f): - expr, _ = block_transforms.compile_column_udf( - self._block, f, col_id - ) - aggregations.append(expr) - else: - aggregations.append(aggs.agg(col_id, agg_ops.lookup_agg_func(f)[0])) - - if self._block.column_labels.nlevels > 1: - column_labels = [ - tuple(label) + (fn_lbl,) - for label in labels.to_frame(index=False).to_numpy() - for fn_lbl in fn_labels - ] - else: # Single-level index - column_labels = [ - (label, fn_lbl) for label in labels for fn_lbl in fn_labels - ] - - agg_block = self._block.aggregate( - by_column_ids=self._by_col_ids, - aggregations=aggregations, - dropna=self._dropna, - ) - agg_block = agg_block.with_column_labels( - pd.MultiIndex.from_tuples( - column_labels, names=[*self._block.column_labels.names, None] - ) - ) - dataframe = df.DataFrame(agg_block) - return dataframe if self._as_index else self._convert_index(dataframe) - - def _agg_named(self, **kwargs) -> df.DataFrame: - aggregations = [] - column_labels = [] - for k, v in kwargs.items(): - if not isinstance(k, str): - raise NotImplementedError( - f"Only string aggregate names supported. {constants.FEEDBACK_LINK}" - ) - if not isinstance(v, tuple) or (len(v) != 2): - raise TypeError("kwargs values must be 2-tuples of column, aggfunc") - col_id = self._resolve_label(v[0]) - aggregations.append(aggs.agg(col_id, agg_ops.lookup_agg_func(v[1])[0])) - column_labels.append(k) - agg_block = self._block.aggregate( - by_column_ids=self._by_col_ids, - aggregations=aggregations, - dropna=self._dropna, - ) - agg_block = agg_block.with_column_labels(column_labels) - dataframe = df.DataFrame(agg_block) - return dataframe if self._as_index else self._convert_index(dataframe) - - def _convert_index(self, dataframe: df.DataFrame): - """Convert index levels to columns except where names conflict.""" - levels_to_drop = [ - level for level in dataframe.index.names if level in dataframe.columns - ] - - if len(levels_to_drop) == dataframe.index.nlevels: - return dataframe.reset_index(drop=True) - return dataframe.droplevel(levels_to_drop).reset_index(drop=False) - - aggregate = agg - - def _raise_on_non_numeric(self, op: str): - if not all( - self._column_type(col) in dtypes.NUMERIC_BIGFRAMES_TYPES_PERMISSIVE - for col in self._selected_cols - ): - raise NotImplementedError( - f"'{op}' does not support non-numeric columns. " - "Set 'numeric_only'=True to ignore non-numeric columns. " - f"{constants.FEEDBACK_LINK}" - ) - return self - - def _aggregated_columns( - self, numeric_only: bool = False - ) -> Tuple[typing.Sequence[str], pd.Index]: - valid_agg_cols: list[str] = [] - offsets: list[int] = [] - for i, col_id in enumerate(self._block.value_columns): - is_numeric = ( - self._column_type(col_id) in dtypes.NUMERIC_BIGFRAMES_TYPES_PERMISSIVE - ) - if (col_id in self._selected_cols) and (is_numeric or not numeric_only): - offsets.append(i) - valid_agg_cols.append(col_id) - return valid_agg_cols, self._block.column_labels.take(offsets) - - def _column_type(self, col_id: str) -> dtypes.Dtype: - col_offset = self._block.value_columns.index(col_id) - dtype = self._block.dtypes[col_offset] - return dtype - - def _aggregate_all( - self, aggregate_op: agg_ops.UnaryAggregateOp, numeric_only: bool = False - ) -> df.DataFrame: - aggregated_col_ids, labels = self._aggregated_columns(numeric_only=numeric_only) - aggregations = [aggs.agg(col_id, aggregate_op) for col_id in aggregated_col_ids] - result_block = self._block.aggregate( - by_column_ids=self._by_col_ids, - aggregations=aggregations, - column_labels=labels, - dropna=self._dropna, - ) - dataframe = df.DataFrame(result_block) - return dataframe if self._as_index else self._convert_index(dataframe) - - def _apply_window_op( - self, - op: agg_ops.UnaryWindowOp, - window: typing.Optional[window_specs.WindowSpec] = None, - numeric_only: bool = False, - ): - """Apply window op to groupby. Defaults to grouped cumulative window.""" - window_spec = window or window_specs.cumulative_rows( - grouping_keys=tuple(self._by_col_ids) - ) - columns, labels = self._aggregated_columns(numeric_only=numeric_only) - block, result_ids = self._block.multi_apply_window_op( - columns, - op, - window_spec=window_spec, - ) - block = block.project_exprs( - tuple( - bigframes.operations.where_op.as_expr( - r_col, - bigframes.operations.notnull_op.as_expr(og_col), - ex.const(None), - ) - for og_col, r_col in zip(columns, result_ids) - ), - labels=labels, - drop=True, - ) - - return df.DataFrame(block) - - def _resolve_label(self, label: blocks.Label) -> str: - """Resolve label to column id.""" - col_ids = self._block.label_to_col_id.get(label, ()) - if len(col_ids) > 1: - raise ValueError(f"Label {label} is ambiguous") - if len(col_ids) == 0: - raise ValueError(f"Label {label} does not match any columns") - return col_ids[0] diff --git a/bigframes/core/groupby/group_by.py b/bigframes/core/groupby/group_by.py deleted file mode 100644 index 34786e4fd88..00000000000 --- a/bigframes/core/groupby/group_by.py +++ /dev/null @@ -1,91 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from __future__ import annotations - -import functools -from typing import Sequence - -import pandas as pd - -import bigframes.enums -import bigframes.operations as ops -from bigframes.core import blocks -from bigframes.core import expression as ex - - -def block_groupby_iter( - block: blocks.Block, - *, - by_col_ids: Sequence[str], - by_key_is_singular: bool, - dropna: bool, -): - original_index_columns = block._index_columns - original_index_labels = block._index_labels - by_col_ids = by_col_ids - block = block.reset_index( - level=None, - # Keep the original index columns so they can be recovered. - drop=False, - allow_duplicates=True, - replacement=bigframes.enums.DefaultIndexKind.NULL, - ).set_index( - by_col_ids, - # Keep by_col_ids in-place so the ordering doesn't change. - drop=False, - append=False, - ) - block.cached( - force=True, - # All DataFrames will be filtered by by_col_ids, so - # force block.cached() to cluster by the new index by explicitly - # setting `session_aware=False`. This will ensure that the filters - # are more efficient. - session_aware=False, - ) - keys_block = block.aggregate(by_column_ids=by_col_ids, dropna=dropna) - for chunk in keys_block.to_pandas_batches(): - # Convert to MultiIndex to make sure we get tuples, - # even for singular keys. - by_keys_index = chunk.index - if not isinstance(by_keys_index, pd.MultiIndex): - by_keys_index = pd.MultiIndex.from_frame(by_keys_index.to_frame()) - - for by_keys in by_keys_index: - filtered_block = ( - # To ensure the cache is used, filter first, then reset the - # index before yielding the DataFrame. - block.filter( - functools.reduce( - ops.and_op.as_expr, - ( - ops.eq_op.as_expr(by_col, ex.const(by_key)) - for by_col, by_key in zip(by_col_ids, by_keys) - ), - ), - ).set_index( - original_index_columns, - # We retained by_col_ids in the set_index call above, - # so it's safe to drop the duplicates now. - drop=True, - append=False, - index_labels=original_index_labels, - ) - ) - - if by_key_is_singular: - yield by_keys[0], filtered_block - else: - yield by_keys, filtered_block diff --git a/bigframes/core/groupby/series_group_by.py b/bigframes/core/groupby/series_group_by.py deleted file mode 100644 index fb7845f36d2..00000000000 --- a/bigframes/core/groupby/series_group_by.py +++ /dev/null @@ -1,486 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from __future__ import annotations - -import datetime -import typing -from typing import Iterable, Literal, Sequence, Tuple, Union - -import bigframes_vendored.constants as constants -import bigframes_vendored.pandas.core.groupby as vendored_pandas_groupby -import numpy -import pandas - -import bigframes.core.block_transforms as block_ops -import bigframes.core.block_transforms as block_transforms -import bigframes.core.blocks as blocks -import bigframes.core.ordering as order -import bigframes.core.utils as utils -import bigframes.core.validations as validations -import bigframes.core.window as windows -import bigframes.core.window_spec as window_specs -import bigframes.dataframe as df -import bigframes.dtypes -import bigframes.operations -import bigframes.operations.aggregations as agg_ops -import bigframes.series as series -from bigframes import session -from bigframes._tools import docs -from bigframes.core import expression as ex -from bigframes.core.groupby import aggs, group_by -from bigframes.core.logging import log_adapter -from bigframes.core.window import rolling - - -@log_adapter.class_logger -@docs.inherit_docs(vendored_pandas_groupby.SeriesGroupBy) -class SeriesGroupBy(vendored_pandas_groupby.SeriesGroupBy): - def __init__( - self, - block: blocks.Block, - value_column: str, - by_col_ids: typing.Sequence[str], - value_name: blocks.Label = None, - dropna=True, - *, - by_key_is_singular: bool = False, - ): - # TODO(tbergeron): Support more group-by expression types - self._block = block - self._value_column = value_column - self._by_col_ids = by_col_ids - self._value_name = value_name - self._dropna = dropna # Applies to aggregations but not windowing - - self._by_key_is_singular = by_key_is_singular - if by_key_is_singular: - assert len(by_col_ids) == 1, "singular key should be exactly one group key" - - @property - def _session(self) -> session.Session: - return self._block.session - - @validations.requires_ordering() - def head(self, n: int = 5) -> series.Series: - block = self._block - if self._dropna: - block = block_ops.dropna(self._block, self._by_col_ids, how="any") - return series.Series( - block.grouped_head( - by_column_ids=self._by_col_ids, value_columns=[self._value_column], n=n - ) - ) - - def describe(self, include: None | Literal["all"] = None): - from bigframes.pandas.core.methods import describe - - return df.DataFrame( - describe._describe( - self._block, - columns=[self._value_column], - include=include, - as_index=True, - by_col_ids=self._by_col_ids, - dropna=self._dropna, - ) - ).droplevel(level=0, axis=1) - - def __iter__(self) -> Iterable[Tuple[blocks.Label, series.Series]]: - for group_keys, filtered_block in group_by.block_groupby_iter( - self._block, - by_col_ids=self._by_col_ids, - by_key_is_singular=self._by_key_is_singular, - dropna=self._dropna, - ): - filtered_series = series.Series( - filtered_block.select_column(self._value_column) - ) - filtered_series.name = self._value_name - yield group_keys, filtered_series - - def __len__(self) -> int: - return len(self.agg([])) - - def all(self) -> series.Series: - return self._aggregate(agg_ops.all_op) - - def any(self) -> series.Series: - return self._aggregate(agg_ops.any_op) - - def min(self, *args) -> series.Series: - return self._aggregate(agg_ops.min_op) - - def max(self, *args) -> series.Series: - return self._aggregate(agg_ops.max_op) - - def count(self) -> series.Series: - return self._aggregate(agg_ops.count_op) - - def nunique(self) -> series.Series: - return self._aggregate(agg_ops.nunique_op) - - def sum(self, *args) -> series.Series: - return self._aggregate(agg_ops.sum_op) - - def mean(self, *args) -> series.Series: - return self._aggregate(agg_ops.mean_op) - - def rank( - self, - method="average", - ascending: bool = True, - na_option: str = "keep", - pct: bool = False, - ) -> series.Series: - return series.Series( - block_ops.rank( - self._block, - method, - na_option, - ascending, - grouping_cols=tuple(self._by_col_ids), - columns=(self._value_column,), - pct=pct, - ) - ) - - def median( - self, - *args, - exact: bool = True, - **kwargs, - ) -> series.Series: - if exact: - return self.quantile(0.5) - else: - return self._aggregate(agg_ops.median_op) - - def quantile( - self, q: Union[float, Sequence[float]] = 0.5, *, numeric_only: bool = False - ) -> series.Series: - multi_q = utils.is_list_like(q) - result = block_ops.quantile( - self._block, - (self._value_column,), - qs=tuple(q) if multi_q else (q,), # type: ignore - grouping_column_ids=self._by_col_ids, - dropna=self._dropna, - ) - if multi_q: - return series.Series(result.stack()) - else: - return series.Series(result.stack()).droplevel(-1) - - def std(self, *args, **kwargs) -> series.Series: - return self._aggregate(agg_ops.std_op) - - def var(self, *args, **kwargs) -> series.Series: - return self._aggregate(agg_ops.var_op) - - def size(self) -> series.Series: - agg_block = self._block.aggregate( - aggregations=[agg_ops.SizeOp().as_expr()], - by_column_ids=self._by_col_ids, - dropna=self._dropna, - ) - return series.Series(agg_block.with_column_labels([self._value_name])) - - def skew(self, *args, **kwargs) -> series.Series: - block = block_ops.skew(self._block, [self._value_column], self._by_col_ids) - return series.Series(block) - - def kurt(self, *args, **kwargs) -> series.Series: - block = block_ops.kurt(self._block, [self._value_column], self._by_col_ids) - return series.Series(block) - - kurtosis = kurt - - @validations.requires_ordering() - def first(self, numeric_only: bool = False, min_count: int = -1) -> series.Series: - if numeric_only and not bigframes.dtypes.is_numeric( - self._block.expr.get_column_type(self._value_column) - ): - raise TypeError( - f"Cannot use 'numeric_only' with non-numeric column {self._value_name}." - ) - window_spec = window_specs.unbound( - grouping_keys=tuple(self._by_col_ids), - min_periods=min_count if min_count >= 0 else 0, - ) - block, firsts_id = self._block.apply_window_op( - self._value_column, - agg_ops.FirstNonNullOp(), - window_spec=window_spec, - ) - block = block.aggregate( - (aggs.agg(firsts_id, agg_ops.AnyValueOp()),), - self._by_col_ids, - dropna=self._dropna, - ) - return series.Series(block.with_column_labels([self._value_name])) - - @validations.requires_ordering() - def last(self, numeric_only: bool = False, min_count: int = -1) -> series.Series: - if numeric_only and not bigframes.dtypes.is_numeric( - self._block.expr.get_column_type(self._value_column) - ): - raise TypeError( - f"Cannot use 'numeric_only' with non-numeric column {self._value_name}." - ) - window_spec = window_specs.unbound( - grouping_keys=tuple(self._by_col_ids), - min_periods=min_count if min_count >= 0 else 0, - ) - block, firsts_id = self._block.apply_window_op( - self._value_column, - agg_ops.LastNonNullOp(), - window_spec=window_spec, - ) - block = block.aggregate( - (aggs.agg(firsts_id, agg_ops.AnyValueOp()),), - self._by_col_ids, - dropna=self._dropna, - ) - return series.Series(block.with_column_labels([self._value_name])) - - def prod(self, *args) -> series.Series: - return self._aggregate(agg_ops.product_op) - - def agg(self, func=None) -> typing.Union[df.DataFrame, series.Series]: - if utils.is_dict_like(func): - raise NotImplementedError( - f"Aggregate with {func} not supported. {constants.FEEDBACK_LINK}" - ) - is_single_func = not utils.is_list_like(func) - if is_single_func: - func = [func] - - aggregations = [] - column_labels = [] - for f in func: - if block_transforms.is_transpiler_eligible(f): - expr, name = block_transforms.compile_column_udf( - self._block, f, self._value_column - ) - aggregations.append(expr) - column_labels.append(self._value_name if is_single_func else name) - else: - agg_op, label = agg_ops.lookup_agg_func(f) - aggregations.append(aggs.agg(self._value_column, agg_op)) - column_labels.append(label if not is_single_func else self._value_name) - - agg_block = self._block.aggregate( - by_column_ids=self._by_col_ids, - aggregations=aggregations, - dropna=self._dropna, - ) - - if column_labels: - agg_block = agg_block.with_column_labels(column_labels) - - if len(aggregations) == 1: - return series.Series(agg_block) - return df.DataFrame(agg_block) - - aggregate = agg - - def transform(self, func, *args, **kwargs) -> series.Series: - if block_transforms.is_transpiler_eligible(func): - window_spec = window_specs.unbound(grouping_keys=tuple(self._by_col_ids)) - expr, _ = block_transforms.compile_column_udf( - self._block, - func, - self._value_column, - args=args, - kwargs=kwargs, - window_spec=window_spec, - ) - - block = self._block.project_block_exprs( - [expr], - labels=[self._value_name], - drop=True, - ) - return series.Series(block) - - raise NotImplementedError( - "SeriesGroupBy.transform is only supported when experiments.enable_python_transpiler is True and a transpiler-compatible python function is provided." - ) - - def value_counts( - self, - normalize: bool = False, - sort: bool = True, - ascending: bool = False, - dropna: bool = True, - ) -> Union[df.DataFrame, series.Series]: - columns = [self._value_column] - block = self._block - if self._dropna: # this drops null grouping columns - block = block_ops.dropna(block, self._by_col_ids) - block = block_ops.value_counts( - block, - columns, - normalize=normalize, - sort=sort, - ascending=ascending, - drop_na=dropna, # this drops null value columns - grouping_keys=self._by_col_ids, - ) - # TODO: once as_index=Fales supported, return DataFrame instead by resetting index - # with .to_frame().reset_index(drop=False) - return series.Series(block) - - @validations.requires_ordering() - def cumsum(self, *args, **kwargs) -> series.Series: - return self._apply_window_op( - agg_ops.sum_op, - ) - - @validations.requires_ordering() - def cumprod(self, *args, **kwargs) -> series.Series: - return self._apply_window_op( - agg_ops.product_op, - ) - - @validations.requires_ordering() - def cummax(self, *args, **kwargs) -> series.Series: - return self._apply_window_op( - agg_ops.max_op, - ) - - @validations.requires_ordering() - def cummin(self, *args, **kwargs) -> series.Series: - return self._apply_window_op( - agg_ops.min_op, - ) - - @validations.requires_ordering() - def cumcount(self, *args, **kwargs) -> series.Series: - # TODO: Add nullary op support to implement more cleanly - return ( - self._apply_window_op( - agg_ops.SizeUnaryOp(), - discard_name=True, - ) - - 1 - ) - - @validations.requires_ordering() - def shift(self, periods=1) -> series.Series: - """Shift index by desired number of periods.""" - # Window framing clause is not allowed for analytic function lag. - window = window_specs.rows( - grouping_keys=tuple(self._by_col_ids), - ) - return self._apply_window_op(agg_ops.ShiftOp(periods), window=window) - - @validations.requires_ordering() - def diff(self, periods=1) -> series.Series: - window = window_specs.rows( - grouping_keys=tuple(self._by_col_ids), - ) - return self._apply_window_op(agg_ops.DiffOp(periods), window=window) - - @validations.requires_ordering() - def rolling( - self, - window: int | pandas.Timedelta | numpy.timedelta64 | datetime.timedelta | str, - min_periods=None, - closed: Literal["right", "left", "both", "neither"] = "right", - ) -> windows.Window: - if isinstance(window, int): - window_spec = window_specs.WindowSpec( - bounds=window_specs.RowsWindowBounds.from_window_size(window, closed), - min_periods=min_periods if min_periods is not None else window, - grouping_keys=tuple(ex.deref(col) for col in self._by_col_ids), - ) - block = self._block.order_by( - [order.ascending_over(col) for col in self._by_col_ids], - ) - return windows.Window( - block, - window_spec, - [self._value_column], - drop_null_groups=self._dropna, - is_series=True, - ) - - return rolling.create_range_window( - self._block, - window, - min_periods=min_periods, - value_column_ids=[self._value_column], - closed=closed, - is_series=True, - grouping_keys=self._by_col_ids, - drop_null_groups=self._dropna, - ) - - @validations.requires_ordering() - def expanding(self, min_periods: int = 1) -> windows.Window: - window_spec = window_specs.cumulative_rows( - grouping_keys=tuple(self._by_col_ids), - min_periods=min_periods, - ) - block = self._block.order_by( - [order.ascending_over(col) for col in self._by_col_ids], - ) - return windows.Window( - block, - window_spec, - [self._value_column], - drop_null_groups=self._dropna, - is_series=True, - ) - - def _aggregate(self, aggregate_op: agg_ops.UnaryAggregateOp) -> series.Series: - result_block = self._block.aggregate( - (aggs.agg(self._value_column, aggregate_op),), - self._by_col_ids, - dropna=self._dropna, - ) - - return series.Series(result_block.with_column_labels([self._value_name])) - - def _apply_window_op( - self, - op: agg_ops.UnaryWindowOp, - discard_name=False, - window: typing.Optional[window_specs.WindowSpec] = None, - ) -> series.Series: - """Apply window op to groupby. Defaults to grouped cumulative window.""" - window_spec = window or window_specs.cumulative_rows( - grouping_keys=tuple(self._by_col_ids) - ) - - label = self._value_name if not discard_name else None - block, result_id = self._block.apply_window_op( - self._value_column, - op, - result_label=label, - window_spec=window_spec, - ) - if op.skips_nulls: - block, result_id = block.project_expr( - bigframes.operations.where_op.as_expr( - result_id, - bigframes.operations.notnull_op.as_expr(self._value_column), - ex.const(None), - ), - label, - ) - - return series.Series(block.select_column(result_id)) diff --git a/bigframes/core/guid.py b/bigframes/core/guid.py index f9b666d32ba..4eb6c7a9d62 100644 --- a/bigframes/core/guid.py +++ b/bigframes/core/guid.py @@ -11,36 +11,11 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -import threading -import typing -_GUID_LOCK = threading.Lock() _GUID_COUNTER = 0 def generate_guid(prefix="col_"): - global _GUID_LOCK - with _GUID_LOCK: - global _GUID_COUNTER - _GUID_COUNTER += 1 - return f"bfuid_{prefix}{_GUID_COUNTER}" - - -class SequentialUIDGenerator: - """Produces a sequence of UIDs, such as {"t0", "t1", "c0", "t2", ...}, by - cycling through provided prefixes (e.g., "t" and "c"). - Note: this function is not thread-safe. - """ - - def __init__(self): - self.prefix_counters: typing.Dict[str, int] = {} - - def get_uid_stream(self, prefix: str) -> typing.Generator[str, None, None]: - """Yields a continuous stream of raw UID strings for the given prefix.""" - if prefix not in self.prefix_counters: - self.prefix_counters[prefix] = 0 - - while True: - uid = f"{prefix}{self.prefix_counters[prefix]}" - self.prefix_counters[prefix] += 1 - yield uid + global _GUID_COUNTER + _GUID_COUNTER += 1 + return prefix + str(_GUID_COUNTER) diff --git a/bigframes/core/identifiers.py b/bigframes/core/identifiers.py deleted file mode 100644 index b7ae0e24345..00000000000 --- a/bigframes/core/identifiers.py +++ /dev/null @@ -1,78 +0,0 @@ -# Copyright 2023 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -from __future__ import annotations - -import dataclasses -import functools -import itertools -from typing import Generator - -import bigframes.core.guid - - -def standard_id_strings(prefix: str = "col_") -> Generator[str, None, None]: - i = 0 - while True: - yield f"{prefix}{i}" - i = i + 1 - - -# Used for expression trees -@functools.total_ordering -@dataclasses.dataclass(frozen=True) -class ColumnId: - """Local id without plan-wide id.""" - - name: str - - @property - def sql(self) -> str: - """Returns the unescaped SQL name.""" - return self.name - - @property - def local_normalized(self) -> ColumnId: - """For use in compiler only. Normalizes to ColumnId referring to sql name.""" - return self # == ColumnId(name=self.sql) - - def __lt__(self, other: ColumnId) -> bool: - return self.sql < other.sql - - @classmethod - def unique(cls) -> ColumnId: - return ColumnId(name=bigframes.core.guid.generate_guid()) - - -@dataclasses.dataclass(frozen=True) -class SerialColumnId(ColumnId): - """Id that is assigned a unique serial within the tree.""" - - name: str - id: int - - @property - def sql(self) -> str: - """Returns the unescaped SQL name.""" - return f"{self.name}_{self.id}" - - @property - def local_normalized(self) -> ColumnId: - """For use in compiler only. Normalizes to ColumnId referring to sql name.""" - return ColumnId(name=self.sql) - - -# TODO: Create serial ids locally, so can preserve name info -def anonymous_serial_ids() -> Generator[ColumnId, None, None]: - for i in itertools.count(): - yield SerialColumnId("uid", i) diff --git a/bigframes/core/indexers.py b/bigframes/core/indexers.py index faf76f7d4f0..69048b6845a 100644 --- a/bigframes/core/indexers.py +++ b/bigframes/core/indexers.py @@ -14,45 +14,27 @@ from __future__ import annotations -import numbers import typing -import warnings -from typing import Any, Sequence, Tuple, Union, cast +from typing import List, Tuple, Union -import bigframes_vendored.constants as constants -import bigframes_vendored.ibis.common.exceptions as ibis_exceptions -import numpy as np +import ibis import pandas as pd -import pyarrow as pa -import pyarrow.types # type: ignore +import bigframes.constants as constants import bigframes.core.blocks -import bigframes.core.col -import bigframes.core.expression as ex import bigframes.core.guid as guid import bigframes.core.indexes as indexes import bigframes.core.scalar -import bigframes.core.validations as validations -import bigframes.core.window_spec as windows import bigframes.dataframe -import bigframes.dtypes -import bigframes.exceptions as bfe import bigframes.operations as ops import bigframes.series if typing.TYPE_CHECKING: LocSingleKey = Union[ - bigframes.series.Series, - indexes.Index, - slice, - bigframes.core.scalar.Scalar, - bigframes.core.col.Expression, + bigframes.series.Series, indexes.Index, slice, bigframes.core.scalar.Scalar ] -_DATAFRAME_ILOC_ERROR = "Only DataFrame.iloc[:, col_indexer] = value is supported." - - class LocSeriesIndexer: def __init__(self, series: bigframes.series.Series): self._series = series @@ -81,14 +63,17 @@ def __setitem__(self, key, value) -> None: index_column = block.index_columns[0] # if index == key return value else value_colum - block, result_id = block.project_expr( - ops.where_op.as_expr( - ex.const(value), - ops.eq_op.as_expr(index_column, ex.const(key)), - self._series._value_column, - ) + block, insert_cond = block.apply_unary_op( + index_column, ops.partial_right(ops.eq_op, key) + ) + block, result_id = block.apply_binary_op( + insert_cond, + self._series._value_column, + ops.partial_arg1(ops.where_op, value), + ) + block = block.copy_values(result_id, value_column).drop_columns( + [insert_cond, result_id] ) - block = block.copy_values(result_id, value_column).drop_columns([result_id]) self._series._set_block(block) @@ -110,9 +95,6 @@ def __getitem__( Other key types are not yet supported. """ - if not _is_noop_slice(key): - validations.enforce_ordered(self._series, "iloc") - return _iloc_getitem_series_or_dataframe(self._series, key) @@ -121,9 +103,9 @@ def __init__(self, series: bigframes.series.Series): self._series = series def __getitem__(self, key: int) -> bigframes.core.scalar.Scalar: - if not _is_integer_scalar(key): + if not isinstance(key, int): raise ValueError("Series iAt based indexing can only have integer indexers") - return self._series.iloc[_to_python_int(key)] + return self._series.iloc[key] class AtSeriesIndexer: @@ -155,37 +137,26 @@ def __init__(self, dataframe: bigframes.dataframe.DataFrame): @typing.overload def __getitem__( self, key: LocSingleKey - ) -> Union[bigframes.dataframe.DataFrame, pd.Series]: ... + ) -> Union[bigframes.dataframe.DataFrame, pd.Series]: + ... # Technically this is wrong since we can have duplicate column labels, but # this is expected to be rare. @typing.overload def __getitem__( self, key: Tuple[LocSingleKey, str] - ) -> Union[bigframes.series.Series, bigframes.core.scalar.Scalar]: ... + ) -> Union[bigframes.series.Series, bigframes.core.scalar.Scalar]: + ... def __getitem__(self, key): - # TODO(tbergeron): Pandas will try both splitting 2-tuple into row, index or as 2-part - # row key. We must choose one, so bias towards treating as multi-part row label + # TODO(swast): If the DataFrame has a MultiIndex, we'll need to + # disambiguate this from a single row selection. if isinstance(key, tuple) and len(key) == 2: - is_row_multi_index = self._dataframe.index.nlevels > 1 - is_first_item_list_or_tuple = isinstance(key[0], (tuple, list)) - if not is_row_multi_index or is_first_item_list_or_tuple: - df = typing.cast( - bigframes.dataframe.DataFrame, - _loc_getitem_series_or_dataframe(self._dataframe, key[0]), - ) - - columns = key[1] - if isinstance(columns, bigframes.series.Series): - columns = columns.to_pandas() - if isinstance(columns, pd.Series) and columns.dtype in ( - bool, - pd.BooleanDtype(), - ): - columns = df.columns[typing.cast(pd.Series, columns)] - - return df[columns] + df = typing.cast( + bigframes.dataframe.DataFrame, + _loc_getitem_series_or_dataframe(self._dataframe, key[0]), + ) + return df[key[1]] return typing.cast( bigframes.dataframe.DataFrame, @@ -197,7 +168,14 @@ def __setitem__( key: Tuple[slice, str], value: bigframes.dataframe.SingleItemValue, ): - if isinstance(key, tuple) and len(key) == 2 and _is_noop_slice(key[0]): + if ( + isinstance(key, tuple) + and len(key) == 2 + and isinstance(key[0], slice) + and (key[0].start is None or key[0].start == 0) + and (key[0].step is None or key[0].step == 1) + and key[0].stop is None + ): # TODO(swast): Support setting multiple columns with key[1] as a list # of labels and value as a DataFrame. df = self._dataframe.assign(**{key[1]: value}) @@ -208,15 +186,7 @@ def __setitem__( and isinstance(key[0], bigframes.series.Series) and key[0].dtype == "boolean" ) and pd.api.types.is_scalar(value): - # For integer scalar, if set value to a new column, the dtype would be default to float. - # But if set value to an existing Int64 column, the dtype would still be integer. - # So we need to use different NaN type to match this behavior. - new_column = key[0].map( - { - True: value, - False: pd.NA if key[1] in self._dataframe.columns else None, - } - ) + new_column = key[0].map({True: value, False: None}) try: original_column = self._dataframe[key[1]] except KeyError: @@ -224,7 +194,7 @@ def __setitem__( return try: self._dataframe[key[1]] = new_column.fillna(original_column) - except ibis_exceptions.IbisTypeError: + except ibis.common.exceptions.IbisTypeError: raise TypeError( f"Cannot assign scalar of type {type(value)} to column of type {original_column.dtype}, or index type of series argument does not match dataframe." ) @@ -248,41 +218,8 @@ def __getitem__(self, key) -> Union[bigframes.dataframe.DataFrame, pd.Series]: Other key types are not yet supported. """ - requires_ordering = True - if isinstance(key, tuple): - if len(key) > 0: - row_indexer = key[0] - if _is_noop_slice(row_indexer): - requires_ordering = False - elif _is_noop_slice(key): - requires_ordering = False - - if requires_ordering: - validations.enforce_ordered(self._dataframe, "iloc") - return _iloc_getitem_series_or_dataframe(self._dataframe, key) - def __setitem__( - self, - key: Tuple[ - slice, Union[int, typing.Sequence[int], slice, typing.Sequence[bool]] - ], - value: Union[ - bigframes.dataframe.SingleItemValue, bigframes.dataframe.DataFrame - ], - ): - if not (isinstance(key, tuple) and len(key) == 2): - raise NotImplementedError(_DATAFRAME_ILOC_ERROR) - - row_indexer, col_indexer = key - - if not _is_noop_slice(row_indexer): - raise NotImplementedError(_DATAFRAME_ILOC_ERROR) - - col_offsets = _iloc_col_indexer_to_offsets(self._dataframe, col_indexer) - df = self._dataframe._assign_multi_items_by_offsets(col_offsets, value) - self._dataframe._set_block(df._get_block()) - class IatDataFrameIndexer: def __init__(self, dataframe: bigframes.dataframe.DataFrame): @@ -291,21 +228,19 @@ def __init__(self, dataframe: bigframes.dataframe.DataFrame): def __getitem__(self, key: tuple) -> bigframes.core.scalar.Scalar: error_message = "DataFrame.iat should be indexed by a tuple of exactly 2 ints" # we raise TypeError or ValueError under the same conditions that pandas does - if _is_integer_scalar(key): + if isinstance(key, int): raise TypeError(error_message) if not isinstance(key, tuple): raise ValueError(error_message) - key_values_are_ints = [_is_integer_scalar(key_value) for key_value in key] + key_values_are_ints = [isinstance(key_value, int) for key_value in key] if not all(key_values_are_ints): raise ValueError(error_message) if len(key) != 2: raise TypeError(error_message) - row_idx = _to_python_int(key[0]) - col_idx = _to_python_int(key[1]) - block: bigframes.core.blocks.Block = self._dataframe._block - column_block = block.select_columns([block.value_columns[col_idx]]) + block: bigframes.core.blocks.Block = self._dataframe._block # type: ignore + column_block = block.select_columns([block.value_columns[key[1]]]) column = bigframes.series.Series(column_block) - return column.iloc[row_idx] + return column.iloc[key[0]] class AtDataFrameIndexer: @@ -322,26 +257,18 @@ def __getitem__( return self._dataframe.loc[key] -def _is_noop_slice(key: Any) -> bool: - """Return True if key is a slice selecting all elements in the original order.""" - return ( - isinstance(key, slice) - and (key.start is None or key.start == 0) - and (key.step is None or key.step == 1) - and key.stop is None - ) - - @typing.overload def _loc_getitem_series_or_dataframe( series_or_dataframe: bigframes.series.Series, key -) -> Union[bigframes.core.scalar.Scalar, bigframes.series.Series]: ... +) -> Union[bigframes.core.scalar.Scalar, bigframes.series.Series]: + ... @typing.overload def _loc_getitem_series_or_dataframe( series_or_dataframe: bigframes.dataframe.DataFrame, key -) -> Union[bigframes.dataframe.DataFrame, pd.Series]: ... +) -> Union[bigframes.dataframe.DataFrame, pd.Series]: + ... def _loc_getitem_series_or_dataframe( @@ -353,51 +280,94 @@ def _loc_getitem_series_or_dataframe( pd.Series, bigframes.core.scalar.Scalar, ]: - if _is_noop_slice(key): - return series_or_dataframe.copy() - - if isinstance(key, slice): + if isinstance(key, bigframes.series.Series) and key.dtype == "boolean": + return series_or_dataframe[key] + elif isinstance(key, bigframes.series.Series): + temp_name = guid.generate_guid(prefix="temp_series_name_") + if len(series_or_dataframe.index.names) > 1: + temp_name = series_or_dataframe.index.names[0] + key = key.rename(temp_name) + keys_df = key.to_frame() + keys_df = keys_df.set_index(temp_name, drop=True) + return _perform_loc_list_join(series_or_dataframe, keys_df) + elif isinstance(key, bigframes.core.indexes.Index): + block = key._data._get_block() + block = block.select_columns(()) + keys_df = bigframes.dataframe.DataFrame(block) + return _perform_loc_list_join(series_or_dataframe, keys_df) + elif pd.api.types.is_list_like(key): + key = typing.cast(List, key) + if len(key) == 0: + return typing.cast( + Union[bigframes.dataframe.DataFrame, bigframes.series.Series], + series_or_dataframe.iloc[0:0], + ) + if pd.api.types.is_list_like(key[0]): + original_index_names = series_or_dataframe.index.names + num_index_cols = len(original_index_names) + + entry_col_count_correct = [len(entry) == num_index_cols for entry in key] + if not all(entry_col_count_correct): + # pandas usually throws TypeError in these cases- tuple causes IndexError, but that + # seems like unintended behavior + raise TypeError( + "All entries must be of equal length when indexing by list of listlikes" + ) + temporary_index_names = [ + guid.generate_guid(prefix="temp_loc_index_") + for _ in range(len(original_index_names)) + ] + index_cols_dict = {} + for i in range(num_index_cols): + index_name = temporary_index_names[i] + values = [entry[i] for entry in key] + index_cols_dict[index_name] = values + keys_df = bigframes.dataframe.DataFrame( + index_cols_dict, session=series_or_dataframe._get_block().expr.session + ) + keys_df = keys_df.set_index(temporary_index_names, drop=True) + keys_df = keys_df.rename_axis(original_index_names) + else: + # We can't upload a DataFrame with None as the column name, so set it + # an arbitrary string. + index_name = series_or_dataframe.index.name + index_name_is_none = index_name is None + if index_name_is_none: + index_name = "unnamed_col" + keys_df = bigframes.dataframe.DataFrame( + {index_name: key}, + session=series_or_dataframe._get_block().expr.session, + ) + keys_df = keys_df.set_index(index_name, drop=True) + if index_name_is_none: + keys_df.index.name = None + return _perform_loc_list_join(series_or_dataframe, keys_df) + elif isinstance(key, slice): + if (key.start is None) and (key.stop is None) and (key.step is None): + return series_or_dataframe.copy() raise NotImplementedError( f"loc does not yet support indexing with a slice. {constants.FEEDBACK_LINK}" ) - - if isinstance(key, bigframes.core.col.Expression): - label_to_col_ref = { - label: ex.deref(id) - for id, label in series_or_dataframe._block.col_id_to_label.items() - } - resolved_expr = key._value.bind_variables(label_to_col_ref) - result = series_or_dataframe.copy() - result._set_block(series_or_dataframe._block.filter(resolved_expr)) - return result - if callable(key): + elif callable(key): raise NotImplementedError( f"loc does not yet support indexing with a callable. {constants.FEEDBACK_LINK}" ) - elif isinstance(key, bigframes.series.Series) and key.dtype == "boolean": - return series_or_dataframe[key] - elif ( - isinstance(key, bigframes.series.Series) - or isinstance(key, indexes.Index) - or (pd.api.types.is_list_like(key) and not isinstance(key, tuple)) - ): - index = indexes.Index(key, session=series_or_dataframe._session) - index.names = series_or_dataframe.index.names[: index.nlevels] - return _perform_loc_list_join(series_or_dataframe, index) - elif pd.api.types.is_scalar(key) or isinstance(key, tuple): - index = indexes.Index([key], session=series_or_dataframe._session) - index.names = series_or_dataframe.index.names[: index.nlevels] - result = _perform_loc_list_join(series_or_dataframe, index, drop_levels=True) - - if index.nlevels == series_or_dataframe.index.nlevels: - pandas_result = result.to_pandas() - # although loc[scalar_key] returns multiple results when scalar_key - # is not unique, we download the results here and return the computed - # individual result (as a scalar or pandas series) when the key is unique, - # since we expect unique index keys to be more common. loc[[scalar_key]] - # can be used to retrieve one-item DataFrames or Series. - if len(pandas_result) == 1: - return pandas_result.iloc[0] + elif pd.api.types.is_scalar(key): + index_name = "unnamed_col" + keys_df = bigframes.dataframe.DataFrame( + {index_name: [key]}, session=series_or_dataframe._get_block().expr.session + ) + keys_df = keys_df.set_index(index_name, drop=True) + keys_df.index.name = None + result = _perform_loc_list_join(series_or_dataframe, keys_df) + pandas_result = result.to_pandas() + # although loc[scalar_key] returns multiple results when scalar_key + # is not unique, we download the results here and return the computed + # individual result (as a scalar or pandas series) when the key is unique, + # since we expect unique index keys to be more common. loc[[scalar_key]] + # can be used to retrieve one-item DataFrames or Series. + if len(pandas_result) == 1: + return pandas_result.iloc[0] # when the key is not unique, we return a bigframes data type # as usual for methods that return dataframes/series return result @@ -412,194 +382,54 @@ def _loc_getitem_series_or_dataframe( @typing.overload def _perform_loc_list_join( series_or_dataframe: bigframes.series.Series, - keys_index: indexes.Index, - drop_levels: bool = False, -) -> bigframes.series.Series: ... + keys_df: bigframes.dataframe.DataFrame, +) -> bigframes.series.Series: + ... @typing.overload def _perform_loc_list_join( series_or_dataframe: bigframes.dataframe.DataFrame, - keys_index: indexes.Index, - drop_levels: bool = False, -) -> bigframes.dataframe.DataFrame: ... + keys_df: bigframes.dataframe.DataFrame, +) -> bigframes.dataframe.DataFrame: + ... def _perform_loc_list_join( series_or_dataframe: Union[bigframes.dataframe.DataFrame, bigframes.series.Series], - keys_index: indexes.Index, - drop_levels: bool = False, + keys_df: bigframes.dataframe.DataFrame, ) -> Union[bigframes.series.Series, bigframes.dataframe.DataFrame]: # right join based on the old index so that the matching rows from the user's # original dataframe will be duplicated and reordered appropriately + original_index_names = series_or_dataframe.index.names if isinstance(series_or_dataframe, bigframes.series.Series): - _struct_accessor_check_and_warn(series_or_dataframe, keys_index) original_name = series_or_dataframe.name - name = series_or_dataframe.name if series_or_dataframe.name is not None else 0 + name = series_or_dataframe.name if series_or_dataframe.name is not None else "0" result = typing.cast( bigframes.series.Series, - series_or_dataframe.to_frame()._perform_join_by_index( - keys_index, how="right", always_order=True - )[name], + series_or_dataframe.to_frame()._perform_join_by_index(keys_df, how="right")[ + name + ], ) result = result.rename(original_name) else: - result = series_or_dataframe._perform_join_by_index( - keys_index, how="right", always_order=True - ) - - if drop_levels and series_or_dataframe.index.nlevels > keys_index.nlevels: - # drop common levels - levels_to_drop = [ - name for name in series_or_dataframe.index.names if name in keys_index.names - ] - result = result.droplevel(levels_to_drop) + result = series_or_dataframe._perform_join_by_index(keys_df, how="right") # type: ignore + result = result.rename_axis(original_index_names) return result -def _struct_accessor_check_and_warn( - series: bigframes.series.Series, index: indexes.Index -): - if not bigframes.dtypes.is_struct_like(series.dtype): - # No need to check series that do not have struct values - return - - if not bigframes.dtypes.is_string_like(index.dtype): - # No need to check indexing with non-string values. - return - - if not bigframes.dtypes.is_string_like(series.index.dtype): - msg = bfe.format_message( - "Are you trying to access struct fields? If so, please use Series.struct.field(...) " - "method instead." - ) - # Stack depth from series.__getitem__ to here - warnings.warn(msg, stacklevel=7, category=bfe.BadIndexerKeyWarning) - - -def _to_python_int(value: Any) -> int: - if isinstance(value, pa.Scalar): - return int(value.as_py()) - return int(value) - - -def _iloc_clip_to_offset(index: Any, length: int, name: str) -> int: - """Support negative values for offsets.""" - if not _is_integer_scalar(index): - raise TypeError(f"got unexpected {type(index)} for {name}") - offset = _to_python_int(index) - if offset < 0: - offset += length - - if offset < 0 or offset >= length: - raise IndexError(f"{name} {index} is out-of-bounds") - - return offset - - -def _is_integer_scalar(value: Any) -> bool: - return not ( - isinstance(value, bool) - or isinstance(value, np.bool_) - or (isinstance(value, pa.Scalar) and pyarrow.types.is_boolean(value.type)) - ) and ( - isinstance(value, numbers.Integral) - or (isinstance(value, pa.Scalar) and pyarrow.types.is_integer(value.type)) - ) - - -def _is_boolean_scalar(value: Any) -> bool: - return ( - isinstance(value, bool) - or isinstance(value, np.bool_) - or (isinstance(value, pa.Scalar) and pyarrow.types.is_boolean(value.type)) - ) - - -def _truth_val(value: Any) -> bool: - if value is None or value is pd.NA or pd.isna(value): - return False - if isinstance(value, pa.Scalar): - return bool(value.as_py()) if value.is_valid else False - return bool(value) - - -def _is_boolean_indexer(indexer: Any) -> bool: - if hasattr(indexer, "dtype") and pd.api.types.is_bool_dtype(indexer.dtype): - return True - if ( - hasattr(indexer, "type") - and isinstance(indexer.type, pa.DataType) - and pyarrow.types.is_boolean(indexer.type) - ): - return True - if pd.api.types.is_list_like(indexer): - lst = ( - list(indexer) - if not isinstance(indexer, (bigframes.series.Series, indexes.Index)) - else list(indexer.to_pandas()) - ) - if len(lst) > 0 and all( - _is_boolean_scalar(x) or (x is None) or (x is pd.NA) or pd.isna(x) - for x in lst - ): - return any(_is_boolean_scalar(x) for x in lst) - return False - - -def _iloc_col_indexer_to_offsets( - df: bigframes.dataframe.DataFrame, col_indexer: Any -) -> Sequence[int]: - """Convert col_indexer from one of the many pandas-compatible formats to a list of offsets.""" - n_cols = len(df.columns) - - if _is_integer_scalar(col_indexer): - col_offset = _to_python_int(col_indexer) - return [ - _iloc_clip_to_offset( - col_offset, n_cols, "single positional iloc column indexer" - ) - ] - - elif isinstance(col_indexer, slice): - return list(range(*col_indexer.indices(n_cols))) - - elif _is_boolean_indexer(col_indexer): - col_indexer_list = list(col_indexer) - if len(col_indexer_list) != n_cols: - raise ValueError( - f"Boolean iloc column indexer has wrong length: {len(col_indexer_list)} instead of {n_cols}" - ) - return [i for i, val in enumerate(col_indexer_list) if _truth_val(val)] - - elif pd.api.types.is_list_like(col_indexer): - col_indexer_list = list(col_indexer) - return [ - _iloc_clip_to_offset(idx, n_cols, "iloc column indexer") - for idx in col_indexer_list - ] - - raise TypeError(f"got unexpected {type(col_indexer)} for iloc column indexer") - - -def _iloc_df_from_column_offsets( - df: bigframes.dataframe.DataFrame, key: Sequence[int] -) -> bigframes.dataframe.DataFrame: - block = df._block - selected_ids = tuple(block.value_columns[offset] for offset in key) - return bigframes.dataframe.DataFrame(block.select_columns(selected_ids)) - - @typing.overload def _iloc_getitem_series_or_dataframe( series_or_dataframe: bigframes.series.Series, key -) -> Union[bigframes.series.Series, bigframes.core.scalar.Scalar]: ... +) -> Union[bigframes.series.Series, bigframes.core.scalar.Scalar]: + ... @typing.overload def _iloc_getitem_series_or_dataframe( series_or_dataframe: bigframes.dataframe.DataFrame, key -) -> Union[bigframes.dataframe.DataFrame, pd.Series, bigframes.core.scalar.Scalar]: ... +) -> Union[bigframes.dataframe.DataFrame, pd.Series]: + ... def _iloc_getitem_series_or_dataframe( @@ -611,93 +441,39 @@ def _iloc_getitem_series_or_dataframe( bigframes.core.scalar.Scalar, pd.Series, ]: - if _is_integer_scalar(key): - key_int = _to_python_int(key) - stop_key = key_int + 1 if key_int != -1 else None - internal_slice_result = series_or_dataframe._slice(key_int, stop_key, 1) + if isinstance(key, int): + internal_slice_result = series_or_dataframe._slice(key, key + 1, 1) result_pd_df = internal_slice_result.to_pandas() if result_pd_df.empty: raise IndexError("single positional indexer is out-of-bounds") return result_pd_df.iloc[0] elif isinstance(key, slice): return series_or_dataframe._slice(key.start, key.stop, key.step) + elif isinstance(key, tuple) and len(key) == 0: + return series_or_dataframe + elif isinstance(key, tuple) and len(key) == 1: + return _iloc_getitem_series_or_dataframe(series_or_dataframe, key[0]) + elif ( + isinstance(key, tuple) + and isinstance(series_or_dataframe, bigframes.dataframe.DataFrame) + and len(key) == 2 + ): + return series_or_dataframe.iat[key] elif isinstance(key, tuple): - if len(key) > 2 or ( - len(key) == 2 and isinstance(series_or_dataframe, bigframes.series.Series) - ): - raise pd.errors.IndexingError("Too many indexers") - - if len(key) == 0: - return series_or_dataframe - - if len(key) == 1: - return _iloc_getitem_series_or_dataframe(series_or_dataframe, key[0]) - - # len(key) == 2 - df = typing.cast(bigframes.dataframe.DataFrame, series_or_dataframe) - if _is_integer_scalar(key[0]) and _is_integer_scalar(key[1]): - return df.iat[key] - - row_indexer, column_indexer = key - column_offsets = _iloc_col_indexer_to_offsets(df, column_indexer) - df_subset = _iloc_df_from_column_offsets(df, column_offsets) - - if _is_integer_scalar(column_indexer): - selected_columns = cast( - Union[bigframes.dataframe.DataFrame, bigframes.series.Series], - df_subset[df_subset.columns[0]], - ) - else: - selected_columns = df_subset - - return _iloc_getitem_series_or_dataframe(selected_columns, row_indexer) + raise pd.errors.IndexingError("Too many indexers") elif pd.api.types.is_list_like(key): if len(key) == 0: return typing.cast( Union[bigframes.dataframe.DataFrame, bigframes.series.Series], series_or_dataframe.iloc[0:0], ) - - if _is_boolean_indexer(key): - key_list = ( - list(key) - if not isinstance(key, (bigframes.series.Series, indexes.Index)) - else list(key.to_pandas()) - ) - n_rows = len(series_or_dataframe) - if len(key_list) != n_rows: - raise IndexError( - f"Boolean index has wrong length: {len(key_list)} instead of {n_rows}" - ) - key = [i for i, val in enumerate(key_list) if _truth_val(val)] - if len(key) == 0: - return typing.cast( - Union[bigframes.dataframe.DataFrame, bigframes.series.Series], - series_or_dataframe.iloc[0:0], - ) - else: - key = [_to_python_int(k) for k in list(key)] - - # Check if both positive index and negative index are necessary - if isinstance(key, (bigframes.series.Series, indexes.Index)): - # Avoid data download - is_key_unisigned = False - else: - first_sign = key[0] >= 0 - is_key_unisigned = True - for k in key: - if (k >= 0) != first_sign: - is_key_unisigned = False - break - + df = series_or_dataframe if isinstance(series_or_dataframe, bigframes.series.Series): original_series_name = series_or_dataframe.name series_name = ( - original_series_name if original_series_name is not None else 0 + original_series_name if original_series_name is not None else "0" ) df = series_or_dataframe.to_frame() - else: - df = series_or_dataframe original_index_names = df.index.names temporary_index_names = [ guid.generate_guid(prefix="temp_iloc_index_") @@ -707,32 +483,6 @@ def _iloc_getitem_series_or_dataframe( # set to offset index and use regular loc, then restore index df = df.reset_index(drop=False) - block = df._block - # explicitly set index to offsets, reset_index may not generate offsets in some modes - block, offsets_id = block.promote_offsets("temp_iloc_offsets_") - pos_block = block.set_index([offsets_id]) - - if not is_key_unisigned or key[0] < 0: - neg_block, size_col_id = block.apply_window_op( - offsets_id, - ops.aggregations.SizeUnaryOp(), - window_spec=windows.rows(), - ) - neg_block, neg_index_id = neg_block.apply_binary_op( - offsets_id, size_col_id, ops.SubOp() - ) - - neg_block = neg_block.set_index([neg_index_id]).drop_columns( - [size_col_id, offsets_id] - ) - - if is_key_unisigned: - block = pos_block if key[0] >= 0 else neg_block - else: - block = pos_block.concat([neg_block], how="inner") - - df = bigframes.dataframe.DataFrame(block) - result = df.loc[key] result = result.set_index(temporary_index_names) result = result.rename_axis(original_index_names) @@ -743,6 +493,11 @@ def _iloc_getitem_series_or_dataframe( result = result.rename(original_series_name) return result + + elif isinstance(key, tuple): + raise NotImplementedError( + f"iloc does not yet support indexing with a (row, column) tuple. {constants.FEEDBACK_LINK}" + ) elif callable(key): raise NotImplementedError( f"iloc does not yet support indexing with a callable. {constants.FEEDBACK_LINK}" diff --git a/bigframes/core/indexes/__init__.py b/bigframes/core/indexes/__init__.py index dfe361aa763..184a9ce262f 100644 --- a/bigframes/core/indexes/__init__.py +++ b/bigframes/core/indexes/__init__.py @@ -12,12 +12,9 @@ # See the License for the specific language governing permissions and # limitations under the License. -from bigframes.core.indexes.base import Index -from bigframes.core.indexes.datetimes import DatetimeIndex -from bigframes.core.indexes.multi import MultiIndex +from bigframes.core.indexes.index import Index, IndexValue __all__ = [ "Index", - "MultiIndex", - "DatetimeIndex", + "IndexValue", ] diff --git a/bigframes/core/indexes/base.py b/bigframes/core/indexes/base.py deleted file mode 100644 index 32279d36c9a..00000000000 --- a/bigframes/core/indexes/base.py +++ /dev/null @@ -1,854 +0,0 @@ -# Copyright 2023 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. - -"""An index based on a single column.""" - -from __future__ import annotations - -import functools -import typing -from typing import Hashable, Literal, Optional, Sequence, Union, cast, overload - -import bigframes_vendored.constants as constants -import bigframes_vendored.pandas.core.indexes.base as vendored_pandas_index -import google.cloud.bigquery as bigquery -import numpy as np -import pandas - -import bigframes.core.agg_expressions as ex_types -import bigframes.core.block_transforms as block_ops -import bigframes.core.blocks as blocks -import bigframes.core.expression as ex -import bigframes.core.ordering as order -import bigframes.core.utils as utils -import bigframes.core.validations as validations -import bigframes.dtypes -import bigframes.formatting_helpers as formatter -import bigframes.operations as ops -import bigframes.operations.aggregations as agg_ops -import bigframes.series -import bigframes.session.execution_spec as ex_spec -from bigframes import dtypes -from bigframes._tools import docs - -if typing.TYPE_CHECKING: - import bigframes.dataframe - import bigframes.operations.strings - import bigframes.series - - -@docs.inherit_docs(vendored_pandas_index.Index) -class Index: - _query_job = None - _block: blocks.Block - _linked_frame: Union[ - bigframes.dataframe.DataFrame, bigframes.series.Series, None - ] = None - # Must be above 5000 for pandas to delegate to bigframes for binops - __pandas_priority__ = 12000 - - # Overrided on __new__ to create subclasses like pandas does - def __new__( - cls, - data=None, - dtype=None, - *, - name=None, - session=None, - ): - import bigframes.dataframe as df - import bigframes.series as series - - if isinstance(data, blocks.Block): - block = data.select_columns([]) - elif isinstance(data, df.DataFrame): - raise ValueError("Cannot construct index from dataframe.") - elif isinstance(data, series.Series) or isinstance(data, Index): - if isinstance(data, series.Series): - block = data._block - block = block.set_index(col_ids=[data._value_column]) - elif isinstance(data, Index): - block = data._block - index = Index(data=block) - name = data.name if name is None else name - if name is not None: - index.name = name - if dtype is not None: - bf_dtype = bigframes.dtypes.bigframes_type(dtype) - index = index.astype(bf_dtype) - block = index._block - elif isinstance(data, pandas.Index): - pd_df = pandas.DataFrame(index=data) - block = df.DataFrame(pd_df, session=session)._block - else: - if isinstance(dtype, str) and dtype.lower() == "json": - dtype = bigframes.dtypes.JSON_DTYPE - pd_index = pandas.Index(data=data, dtype=dtype, name=name) - pd_df = pandas.DataFrame(index=pd_index) - block = df.DataFrame(pd_df, session=session)._block - - # TODO: Support more index subtypes - - if len(block._index_columns) > 1: - from bigframes.core.indexes.multi import MultiIndex - - klass: type[Index] = MultiIndex # type hint to make mypy happy - elif _should_create_datetime_index(block): - from bigframes.core.indexes.datetimes import DatetimeIndex - - klass = DatetimeIndex - else: - klass = cls - - result = typing.cast(Index, object.__new__(klass)) - result._query_job = None - result._block = block - block.session._register_object(result) - return result - - @classmethod - def from_frame( - cls, frame: Union[bigframes.series.Series, bigframes.dataframe.DataFrame] - ) -> Index: - if len(frame._block.index_columns) == 0: - raise bigframes.exceptions.NullIndexError( - "Cannot access index properties with Null Index. Set an index using set_index." - ) - frame._block._throw_if_null_index("from_frame") - index = Index(frame._block) - index._linked_frame = frame - return index - - @property - def _session(self): - return self._block.session - - @property - def name(self) -> blocks.Label: - names = self.names - if len(names) == 1: - return self.names[0] - else: - # pandas returns None for MultiIndex.name. - return None - - @name.setter - def name(self, value: blocks.Label): - self.names = [value] - - @property - def names(self) -> typing.Sequence[blocks.Label]: - return self._block._index_labels - - @names.setter - def names(self, values: typing.Sequence[blocks.Label]): - self.rename(values, inplace=True) - - @property - def nlevels(self) -> int: - return len(self._block.index_columns) - - @property - def values(self) -> np.ndarray: - return self.to_numpy() - - @property - def ndim(self) -> int: - return 1 - - @property - def shape(self) -> typing.Tuple[int]: - return (self._block.shape[0],) - - @property - def dtype(self): - dtype = self._block.index.dtypes[0] if self.nlevels == 1 else np.dtype("O") - bigframes.dtypes.warn_on_db_dtypes_json_dtype([dtype]) - return dtype - - @property - def dtypes(self) -> pandas.Series: - dtypes = self._block.index.dtypes - bigframes.dtypes.warn_on_db_dtypes_json_dtype(dtypes) - return pandas.Series( - data=dtypes, - index=typing.cast(typing.Tuple, self._block.index.names), - ) - - def __setitem__(self, key, value) -> None: - """Index objects are immutable. Use Index constructor to create - modified Index.""" - raise TypeError("Index does not support mutable operations") - - @property - def size(self) -> int: - return self.shape[0] - - @property - def empty(self) -> bool: - """Returns True if the Index is empty, otherwise returns False.""" - return self.shape[0] == 0 - - @property - @validations.requires_ordering() - def is_monotonic_increasing(self) -> bool: - return typing.cast( - bool, - self._block.is_monotonic_increasing(self._block.index_columns), - ) - - @property - @validations.requires_ordering() - def is_monotonic_decreasing(self) -> bool: - return typing.cast( - bool, - self._block.is_monotonic_decreasing(self._block.index_columns), - ) - - @property - def is_unique(self) -> bool: - # TODO: Cache this at block level - # Avoid circular imports - return not self.has_duplicates - - @property - def has_duplicates(self) -> bool: - # TODO: Cache this at block level - # Avoid circular imports - import bigframes.core.block_transforms as block_ops - import bigframes.dataframe as df - - duplicates_block, indicator = block_ops.indicate_duplicates( - self._block, self._block.index_columns - ) - duplicates_block = duplicates_block.select_columns( - [indicator] - ).with_column_labels(["is_duplicate"]) - duplicates_df = df.DataFrame(duplicates_block) - return duplicates_df["is_duplicate"].any() - - @property - def T(self) -> Index: - return self.transpose() - - @property - def query_job(self) -> bigquery.QueryJob: - """BigQuery job metadata for the most recent query. - - Returns: - The most recent `QueryJob - `_. - """ - if self._query_job is None: - _, query_job = self._block._compute_dry_run() - self._query_job = query_job - return self._query_job - - def get_loc(self, key) -> typing.Union[int, slice, "bigframes.series.Series"]: - """Get integer location, slice or boolean mask for requested label. - - Args: - key: - The label to search for in the index. - - Returns: - An integer, slice, or boolean mask representing the location(s) of the key. - - Raises: - NotImplementedError: If the index has more than one level. - KeyError: If the key is not found in the index. - """ - if self.nlevels != 1: - raise NotImplementedError("get_loc only supports single-level indexes") - - # Get the index column from the block - index_column = self._block.index_columns[0] - - # Use promote_offsets to get row numbers (similar to argmax/argmin implementation) - block_with_offsets, offsets_id = self._block.promote_offsets( - "temp_get_loc_offsets_" - ) - - # Create expression to find matching positions - match_expr = ops.eq_op.as_expr(ex.deref(index_column), ex.const(key)) - block_with_offsets, match_col_id = block_with_offsets.project_expr(match_expr) - - # Filter to only rows where the key matches - filtered_block = block_with_offsets.filter_by_id(match_col_id) - - # Check if key exists at all by counting - count_agg = ex_types.UnaryAggregation(agg_ops.count_op, ex.deref(offsets_id)) - count_result = filtered_block._expr.aggregate([(count_agg, "count")]) - - count_scalar = ( - self._block.session._executor.execute( - count_result, - ex_spec.ExecutionSpec(promise_under_10gb=True), - ) - .batches() - .to_py_scalar() - ) - - if count_scalar == 0: - raise KeyError(f"'{key}' is not in index") - - # If only one match, return integer position - if count_scalar == 1: - min_agg = ex_types.UnaryAggregation(agg_ops.min_op, ex.deref(offsets_id)) - position_result = filtered_block._expr.aggregate([(min_agg, "position")]) - position_scalar = ( - self._block.session._executor.execute( - position_result, - ex_spec.ExecutionSpec(promise_under_10gb=True), - ) - .batches() - .to_py_scalar() - ) - return int(position_scalar) - - # Handle multiple matches based on index monotonicity - is_monotonic = self.is_monotonic_increasing or self.is_monotonic_decreasing - if is_monotonic: - return self._get_monotonic_slice(filtered_block, offsets_id) - else: - # Return boolean mask for non-monotonic duplicates - mask_block = block_with_offsets.select_columns([match_col_id]) - mask_block = mask_block.reset_index(drop=True) - mask_block = mask_block.with_column_labels([None]) - result_series = bigframes.series.Series(mask_block) - return result_series.astype("boolean") - - def _get_monotonic_slice( - self, filtered_block, offsets_id: __builtins__.str - ) -> slice: - """Helper method to get a slice for monotonic duplicates with an optimized query.""" - # Combine min and max aggregations into a single query for efficiency - min_max_aggs = [ - ( - ex_types.UnaryAggregation(agg_ops.min_op, ex.deref(offsets_id)), - "min_pos", - ), - ( - ex_types.UnaryAggregation(agg_ops.max_op, ex.deref(offsets_id)), - "max_pos", - ), - ] - combined_result = filtered_block._expr.aggregate(min_max_aggs) - - # Execute query and extract positions - result_df = ( - self._block.session._executor.execute( - combined_result, - execution_spec=ex_spec.ExecutionSpec(promise_under_10gb=True), - ) - .batches() - .to_pandas() - ) - min_pos = int(result_df["min_pos"].iloc[0]) - max_pos = int(result_df["max_pos"].iloc[0]) - - # Create slice (stop is exclusive) - return slice(min_pos, max_pos + 1) - - def __repr__(self) -> __builtins__.str: - # Protect against errors with uninitialized Series. See: - # https://github.com/googleapis/python-bigquery-dataframes/issues/728 - if not hasattr(self, "_block"): - return object.__repr__(self) - - # TODO(swast): Add a timeout here? If the query is taking a long time, - # maybe we just print the job metadata that we have so far? - # TODO(swast): Avoid downloading the whole series by using job - # metadata, like we do with DataFrame. - opts = bigframes.options.display - max_results = opts.max_rows - if opts.repr_mode == "deferred": - _, dry_run_query_job = self._block._compute_dry_run() - return formatter.repr_query_job(dry_run_query_job) - - pandas_df, _, query_job = self._block.retrieve_repr_request_results(max_results) - self._query_job = query_job - return repr(pandas_df.index) - - def copy(self, name: Optional[Hashable] = None): - copy_index = Index(self._block) - if name is not None: - copy_index.name = name - return copy_index - - def to_series( - self, index: Optional[Index] = None, name: Optional[Hashable] = None - ) -> bigframes.series.Series: - if self.nlevels != 1: - NotImplementedError( - f"Converting multi-index to series is not yet supported. {constants.FEEDBACK_LINK}" - ) - - import bigframes.series - - name = self.name if name is None else name - if index is None: - return bigframes.series.Series( - data=self, index=self, name=str(name), session=self._session - ) - else: - return bigframes.series.Series( - data=self, - index=Index(index, session=self._session), - name=str(name), - session=self._session, - ) - - def get_level_values(self, level) -> Index: - level_n = level if isinstance(level, int) else self.names.index(level) - block = self._block.drop_levels( - [self._block.index_columns[i] for i in range(self.nlevels) if i != level_n] - ) - return Index(block) - - def _memory_usage(self) -> int: - (n_rows,) = self.shape - return sum( - self.dtypes.map( - lambda dtype: bigframes.dtypes.DTYPE_BYTE_SIZES.get(dtype, 8) * n_rows - ) - ) - - def transpose(self) -> Index: - return self - - def sort_values( - self, - *, - inplace: bool = False, - ascending: bool = True, - kind: str | None = None, - na_position: str = "last", - ) -> Index: - if na_position not in ["first", "last"]: - raise ValueError("Param na_position must be one of 'first' or 'last'") - na_last = na_position == "last" - index_columns = self._block.index_columns - ordering = [ - order.ascending_over(column, na_last) - if ascending - else order.descending_over(column, na_last) - for column in index_columns - ] - is_stable = (kind or constants.DEFAULT_SORT_KIND) in constants.STABLE_SORT_KINDS - return Index(self._block.order_by(ordering, stable=is_stable)) - - def astype( - self, - dtype, - *, - errors: Literal["raise", "null"] = "raise", - ) -> Index: - if errors not in ["raise", "null"]: - raise ValueError("Argument 'errors' must be one of 'raise' or 'null'") - if self.nlevels > 1: - raise TypeError("Multiindex does not support 'astype'") - dtype = bigframes.dtypes.bigframes_type(dtype) - return self._apply_unary_expr( - ops.AsTypeOp(to_type=dtype, safe=(errors == "null")).as_expr( - ex.free_var("arg") - ) - ) - - def all(self) -> bool: - if self.nlevels > 1: - raise TypeError("Multiindex does not support 'all'") - return typing.cast(bool, self._apply_aggregation(agg_ops.all_op)) - - def any(self) -> bool: - if self.nlevels > 1: - raise TypeError("Multiindex does not support 'any'") - return typing.cast(bool, self._apply_aggregation(agg_ops.any_op)) - - def nunique(self) -> int: - return typing.cast(int, self._apply_aggregation(agg_ops.nunique_op)) - - def max(self) -> typing.Any: - return self._apply_aggregation(agg_ops.max_op) - - def min(self) -> typing.Any: - return self._apply_aggregation(agg_ops.min_op) - - @validations.requires_ordering() - def argmax(self) -> int: - block, row_nums = self._block.promote_offsets() - block = block.order_by( - [ - *[order.descending_over(col) for col in self._block.index_columns], - order.ascending_over(row_nums), - ] - ) - import bigframes.series as series - - return typing.cast(int, series.Series(block.select_column(row_nums)).iloc[0]) - - @validations.requires_ordering() - def argmin(self) -> int: - block, row_nums = self._block.promote_offsets() - block = block.order_by( - [ - *[order.ascending_over(col) for col in self._block.index_columns], - order.ascending_over(row_nums), - ] - ) - import bigframes.series as series - - return typing.cast(int, series.Series(block.select_column(row_nums)).iloc[0]) - - def value_counts( - self, - normalize: bool = False, - sort: bool = True, - ascending: bool = False, - *, - dropna: bool = True, - ): - block = block_ops.value_counts( - self._block, - self._block.index_columns, - normalize=normalize, - ascending=ascending, - drop_na=dropna, - ) - import bigframes.series as series - - return series.Series(block) - - def fillna(self, value=None) -> Index: - if self.nlevels > 1: - raise TypeError("Multiindex does not support 'fillna'") - return self._apply_unary_expr( - ops.fillna_op.as_expr(ex.free_var("arg"), ex.const(value)) - ) - - @overload - def rename( - self, - name: Union[blocks.Label, Sequence[blocks.Label]], - ) -> Index: ... - - @overload - def rename( - self, - name: Union[blocks.Label, Sequence[blocks.Label]], - *, - inplace: Literal[False], - ) -> Index: ... - - @overload - def rename( - self, - name: Union[blocks.Label, Sequence[blocks.Label]], - *, - inplace: Literal[True], - ) -> None: ... - - def rename( - self, - name: Union[blocks.Label, Sequence[blocks.Label]], - *, - inplace: bool = False, - ) -> Optional[Index]: - # Tuples are allowed as a label, but we specifically exclude them here. - # This is because tuples are hashable, but we want to treat them as a - # sequence. If name is iterable, we want to assume we're working with a - # MultiIndex. Unfortunately, strings are iterable and we don't want a - # list of all the characters, so specifically exclude the non-tuple - # hashables. - if isinstance(name, blocks.Label) and not isinstance(name, tuple): - names = [name] - else: - names = list(name) - - if len(names) != self.nlevels: - raise ValueError("'name' must be same length as levels") - - new_block = self._block.with_index_labels(names) - - if inplace: - if self._linked_frame is not None: - self._linked_frame._set_block( - self._linked_frame._block.with_index_labels(names) - ) - self._block = new_block - return None - else: - return Index(new_block) - - def drop( - self, - labels: typing.Any, - ) -> Index: - # ignore axis, columns params - block = self._block - level_id = self._block.index_columns[0] - if utils.is_list_like(labels): - block, inverse_condition_id = block.apply_unary_op( - level_id, ops.IsInOp(values=tuple(labels), match_nulls=True) - ) - block, condition_id = block.apply_unary_op( - inverse_condition_id, ops.invert_op - ) - else: - block, condition_id = block.project_expr( - ops.ne_op.as_expr(level_id, ex.const(labels)) - ) - block = block.filter_by_id(condition_id, keep_null=True) - block = block.drop_columns([condition_id]) - return Index(block) - - def dropna(self, how: typing.Literal["all", "any"] = "any") -> Index: - if how not in ("any", "all"): - raise ValueError("'how' must be one of 'any', 'all'") - result = block_ops.dropna(self._block, self._block.index_columns, how=how) - return Index(result) - - def drop_duplicates(self, *, keep: __builtins__.str = "first") -> Index: - block = block_ops.drop_duplicates(self._block, self._block.index_columns, keep) - return Index(block) - - def unique(self, level: Hashable | int | None = None) -> Index: - if level is None: - return self.drop_duplicates() - - return self.get_level_values(level).drop_duplicates() - - def isin(self, values) -> Index: - import bigframes.series as series - - if isinstance(values, (series.Series, Index)): - return Index(self.to_series().isin(values)) - if not utils.is_list_like(values): - raise TypeError( - "only list-like objects are allowed to be passed to " - f"isin(), you passed a [{type(values).__name__}]" - ) - - return self._apply_unary_expr( - ops.IsInOp(values=tuple(values), match_nulls=True).as_expr( - ex.free_var("arg") - ) - ).fillna(value=False) - - def __contains__(self, key) -> bool: - hash(key) # to throw for unhashable values - if self.nlevels == 0: - return False - - if (not isinstance(key, tuple)) or (self.nlevels == 1): - key = (key,) - - match_exprs = [] - for key_part, index_col, dtype in zip( - key, self._block.index_columns, self._block.index.dtypes - ): - key_type = bigframes.dtypes.is_compatible(key_part, dtype) - if key_type is None: - return False - key_expr = ex.const(key_part, key_type) - match_expr = ops.eq_null_match_op.as_expr(ex.deref(index_col), key_expr) - match_exprs.append(match_expr) - - match_expr_final = functools.reduce(ops.and_op.as_expr, match_exprs) - block, match_col = self._block.project_expr(match_expr_final) - return cast(bool, block.get_stat(match_col, agg_ops.AnyOp())) - - def _apply_unary_op(self, op: ops.UnaryOp) -> Index: - return self._apply_unary_expr(op.as_expr(ex.free_var("input"))) - - def _apply_unary_expr( - self, - op: ex.Expression, - ) -> Index: - """Applies a unary operator to the index.""" - if len(op.free_variables) != 1: - raise ValueError("Expression must have exactly 1 unbound variable.") - unbound_variable = op.free_variables[0] - - block = self._block - result_ids = [] - for col in self._block.index_columns: - block, result_id = block.project_expr( - op.bind_variables({unbound_variable: ex.deref(col)}) - ) - result_ids.append(result_id) - - block = block.set_index(result_ids, index_labels=self._block.index.names) - return Index(block) - - def _apply_aggregation(self, op: agg_ops.UnaryAggregateOp) -> typing.Any: - if self.nlevels > 1: - raise NotImplementedError(f"Multiindex does not yet support {op.name}") - column_id = self._block.index_columns[0] - return self._block.get_stat(column_id, op) - - def __getitem__(self, key: int) -> typing.Any: - if isinstance(key, int): - if key != -1: - result_pd_df, _ = self._block.slice(key, key + 1, 1).to_pandas() - else: # special case, want [-1:] instead of [-1:0] - result_pd_df, _ = self._block.slice(key).to_pandas() - if result_pd_df.index.empty: - raise IndexError("single positional indexer is out-of-bounds") - return result_pd_df.index[0] - else: - raise NotImplementedError(f"Index key not supported {key}") - - @overload - def to_pandas( # type: ignore[overload-overlap] - self, - *, - allow_large_results: Optional[bool] = ..., - dry_run: Literal[False] = ..., - ) -> pandas.Index: ... - - @overload - def to_pandas( - self, *, allow_large_results: Optional[bool] = ..., dry_run: Literal[True] = ... - ) -> pandas.Series: ... - - def to_pandas( - self, - *, - allow_large_results: Optional[bool] = None, - dry_run: bool = False, - ) -> pandas.Index | pandas.Series: - """Gets the Index as a pandas Index. - - Args: - allow_large_results (bool, default None): - If not None, overrides the global setting to allow or disallow large query results - over the default size limit of 10 GB. - dry_run (bool, default False): - If this argument is true, this method will not process the data. Instead, it returns - a Pandas series containing dtype and the amount of bytes to be processed. - - Returns: - pandas.Index | pandas.Series: - A pandas Index with all of the labels from this Index. If dry run is set to True, - returns a Series containing dry run statistics. - """ - if dry_run: - dry_run_stats, dry_run_job = self._block.index._compute_dry_run( - ordered=True - ) - self._query_job = dry_run_job - return dry_run_stats - - df, query_job = self._block.index.to_pandas( - ordered=True, allow_large_results=allow_large_results - ) - if query_job: - self._query_job = query_job - return df - - def to_numpy(self, dtype=None, *, allow_large_results=None, **kwargs) -> np.ndarray: - return self.to_pandas(allow_large_results=allow_large_results).to_numpy( - dtype, **kwargs - ) - - __array__ = to_numpy - - def to_list(self, *, allow_large_results: Optional[bool] = None) -> list: - return self.to_pandas(allow_large_results=allow_large_results).to_list() - - def __len__(self): - return self.shape[0] - - def __bool__(self): - raise ValueError( - "Cannot convert Index into bool. Consider using .empty(), .item(), .any(), or .all() methods." - ) - - def item(self): - # Docstring is in third_party/bigframes_vendored/pandas/core/indexes/base.py - return self.to_series().peek(2).item() - - def __eq__(self, other) -> Index: # type: ignore - return self._apply_binary_op(other, ops.eq_op) - - def _apply_binary_op( - self, - other, - op: ops.BinaryOp, - alignment: typing.Literal["outer", "left"] = "outer", - ) -> Index: - # Note: alignment arg is for compatibility with accessors, is ignored as irrelevant for implicit joins. - # TODO: Handle local objects, or objects not implicitly alignable? Gets ambiguous with partial ordering though - if isinstance(other, (bigframes.series.Series, Index)): - other = Index(other) - if other.nlevels != self.nlevels: - raise ValueError("Dimensions do not match") - - lexpr = self._block.expr - rexpr = other._block.expr - join_result = lexpr.try_row_join(rexpr) - if join_result is None: - raise ValueError("Cannot align objects") - - expr, (lmap, rmap) = join_result - - expr, res_ids = expr.compute_values( - [ - op.as_expr(lmap[lid], rmap[rid]) - for lid, rid in zip(lexpr.column_ids, rexpr.column_ids) - ] - ) - labels = self.names if self.names == other.names else [None] * len(res_ids) - return Index( - blocks.Block( - expr.select_columns(res_ids), - index_columns=res_ids, - column_labels=[], - index_labels=labels, - ) - ) - elif ( - isinstance(other, bigframes.dtypes.LOCAL_SCALAR_TYPES) and self.nlevels == 1 - ): - block, id = self._block.project_expr( - op.as_expr(self._block.index_columns[0], ex.const(other)) - ) - return Index(block.set_index([id], index_labels=self.names)) - elif isinstance(other, tuple) and len(other) == self.nlevels: - block = self._block.project_exprs( - [ - op.as_expr(self._block.index_columns[i], ex.const(other[i])) - for i in range(self.nlevels) - ], - labels=[None] * self.nlevels, - drop=True, - ) - return Index(block.set_index(block.value_columns, index_labels=self.names)) - else: - return NotImplemented - - # last so as to not shadow __builtins__.str - @property - def str(self) -> bigframes.operations.strings.StringMethods: - import bigframes.operations.strings - - return bigframes.operations.strings.StringMethods(self) - - -def _should_create_datetime_index(block: blocks.Block) -> bool: - if len(block.index.dtypes) != 1: - return False - - return dtypes.is_datetime_like(block.index.dtypes[0]) diff --git a/bigframes/core/indexes/datetimes.py b/bigframes/core/indexes/datetimes.py deleted file mode 100644 index 763e44be095..00000000000 --- a/bigframes/core/indexes/datetimes.py +++ /dev/null @@ -1,56 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -"""An index based on a single column with a datetime-like data type.""" - -from __future__ import annotations - -from bigframes_vendored.pandas.core.indexes import ( - datetimes as vendored_pandas_datetime_index, -) - -from bigframes._tools import docs -from bigframes.core import expression as ex -from bigframes.core.indexes.base import Index -from bigframes.operations import date_ops - - -@docs.inherit_docs(vendored_pandas_datetime_index.DatetimeIndex) -class DatetimeIndex(Index): - # Must be above 5000 for pandas to delegate to bigframes for binops - __pandas_priority__ = 12000 - - @property - def year(self) -> Index: - return self._apply_unary_expr(date_ops.year_op.as_expr(ex.free_var("arg"))) - - @property - def month(self) -> Index: - return self._apply_unary_expr(date_ops.month_op.as_expr(ex.free_var("arg"))) - - @property - def day(self) -> Index: - return self._apply_unary_expr(date_ops.day_op.as_expr(ex.free_var("arg"))) - - @property - def dayofweek(self) -> Index: - return self._apply_unary_expr(date_ops.dayofweek_op.as_expr(ex.free_var("arg"))) - - @property - def day_of_week(self) -> Index: - return self.dayofweek - - @property - def weekday(self) -> Index: - return self.dayofweek diff --git a/bigframes/core/indexes/index.py b/bigframes/core/indexes/index.py new file mode 100644 index 00000000000..6c66c36062a --- /dev/null +++ b/bigframes/core/indexes/index.py @@ -0,0 +1,599 @@ +# Copyright 2023 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. + +"""An index based on a single column.""" + +from __future__ import annotations + +import typing +from typing import Mapping, Sequence, Tuple, Union + +import numpy as np +import pandas + +import bigframes.constants as constants +import bigframes.core as core +import bigframes.core.block_transforms as block_ops +import bigframes.core.blocks as blocks +import bigframes.core.joins as joining +import bigframes.core.ordering as order +import bigframes.core.utils as utils +import bigframes.dtypes +import bigframes.dtypes as bf_dtypes +import bigframes.operations as ops +import bigframes.operations.aggregations as agg_ops +import third_party.bigframes_vendored.pandas.core.indexes.base as vendored_pandas_index + + +class Index(vendored_pandas_index.Index): + __doc__ = vendored_pandas_index.Index.__doc__ + + def __init__(self, data: blocks.BlockHolder): + self._data = data + + @property + def name(self) -> blocks.Label: + return self.names[0] + + @name.setter + def name(self, value: blocks.Label): + self.names = [value] + + @property + def names(self) -> typing.Sequence[blocks.Label]: + """Returns the names of the Index.""" + return self._data._get_block()._index_labels + + @names.setter + def names(self, values: typing.Sequence[blocks.Label]): + return self._data._set_block(self._block.with_index_labels(values)) + + @property + def nlevels(self) -> int: + return len(self._data._get_block().index_columns) + + @property + def values(self) -> np.ndarray: + return self.to_numpy() + + @property + def ndim(self) -> int: + return 1 + + @property + def shape(self) -> typing.Tuple[int]: + return (self._data._get_block().shape[0],) + + @property + def dtype(self): + return self._block.index_dtypes[0] if self.nlevels == 1 else np.dtype("O") + + @property + def dtypes(self) -> pandas.Series: + return pandas.Series( + data=self._block.index_dtypes, index=self._block.index_labels # type:ignore + ) + + @property + def size(self) -> int: + """Returns the size of the Index.""" + return self.shape[0] + + @property + def empty(self) -> bool: + """Returns True if the Index is empty, otherwise returns False.""" + return self.shape[0] == 0 + + @property + def is_monotonic_increasing(self) -> bool: + """ + Return a boolean if the values are equal or increasing. + + Returns: + bool + """ + return typing.cast( + bool, + self._data._get_block().is_monotonic_increasing( + self._data._get_block().index_columns + ), + ) + + @property + def is_monotonic_decreasing(self) -> bool: + """ + Return a boolean if the values are equal or decreasing. + + Returns: + bool + """ + return typing.cast( + bool, + self._data._get_block().is_monotonic_decreasing( + self._data._get_block().index_columns + ), + ) + + @property + def is_unique(self) -> bool: + # TODO: Cache this at block level + # Avoid circular imports + return not self.has_duplicates + + @property + def has_duplicates(self) -> bool: + # TODO: Cache this at block level + # Avoid circular imports + import bigframes.core.block_transforms as block_ops + import bigframes.dataframe as df + + duplicates_block, indicator = block_ops.indicate_duplicates( + self._block, self._block.index_columns + ) + duplicates_block = duplicates_block.select_columns( + [indicator] + ).with_column_labels(["is_duplicate"]) + duplicates_df = df.DataFrame(duplicates_block) + return duplicates_df["is_duplicate"].any() + + @property + def _block(self) -> blocks.Block: + return self._data._get_block() + + @property + def T(self) -> Index: + return self.transpose() + + def transpose(self) -> Index: + return self + + def sort_values(self, *, ascending: bool = True, na_position: str = "last"): + if na_position not in ["first", "last"]: + raise ValueError("Param na_position must be one of 'first' or 'last'") + direction = ( + order.OrderingDirection.ASC if ascending else order.OrderingDirection.DESC + ) + na_last = na_position == "last" + index_columns = self._block.index_columns + ordering = [ + order.OrderingColumnReference(column, direction=direction, na_last=na_last) + for column in index_columns + ] + return Index._from_block(self._block.order_by(ordering)) + + def astype( + self, + dtype: Union[bigframes.dtypes.DtypeString, bigframes.dtypes.Dtype], + ) -> Index: + if self.nlevels > 1: + raise TypeError("Multiindex does not support 'astype'") + return self._apply_unary_op(ops.AsTypeOp(dtype)) + + def all(self) -> bool: + if self.nlevels > 1: + raise TypeError("Multiindex does not support 'all'") + return typing.cast(bool, self._apply_aggregation(agg_ops.all_op)) + + def any(self) -> bool: + if self.nlevels > 1: + raise TypeError("Multiindex does not support 'any'") + return typing.cast(bool, self._apply_aggregation(agg_ops.any_op)) + + def nunique(self) -> int: + return typing.cast(int, self._apply_aggregation(agg_ops.nunique_op)) + + def max(self) -> typing.Any: + return self._apply_aggregation(agg_ops.max_op) + + def min(self) -> typing.Any: + return self._apply_aggregation(agg_ops.min_op) + + def argmax(self) -> int: + block, row_nums = self._block.promote_offsets() + block = block.order_by( + [ + *[ + order.OrderingColumnReference( + col, direction=order.OrderingDirection.DESC + ) + for col in self._block.index_columns + ], + order.OrderingColumnReference(row_nums), + ] + ) + import bigframes.series as series + + return typing.cast(int, series.Series(block.select_column(row_nums)).iloc[0]) + + def argmin(self) -> int: + block, row_nums = self._block.promote_offsets() + block = block.order_by( + [ + *[ + order.OrderingColumnReference(col) + for col in self._block.index_columns + ], + order.OrderingColumnReference(row_nums), + ] + ) + import bigframes.series as series + + return typing.cast(int, series.Series(block.select_column(row_nums)).iloc[0]) + + def value_counts( + self, + normalize: bool = False, + sort: bool = True, + ascending: bool = False, + *, + dropna: bool = True, + ): + block = block_ops.value_counts( + self._block, + self._block.index_columns, + normalize=normalize, + ascending=ascending, + dropna=dropna, + ) + import bigframes.series as series + + return series.Series(block) + + def fillna(self, value=None) -> Index: + if self.nlevels > 1: + raise TypeError("Multiindex does not support 'fillna'") + return self._apply_unary_op(ops.partial_right(ops.fillna_op, value)) + + def rename(self, name: Union[str, Sequence[str]]) -> Index: + names = [name] if isinstance(name, str) else list(name) + if len(names) != self.nlevels: + raise ValueError("'name' must be same length as levels") + return Index._from_block(self._block.with_index_labels(names)) + + def drop( + self, + labels: typing.Any, + ) -> Index: + # ignore axis, columns params + block = self._block + level_id = self._block.index_columns[0] + if utils.is_list_like(labels): + block, inverse_condition_id = block.apply_unary_op( + level_id, ops.IsInOp(labels, match_nulls=True) + ) + block, condition_id = block.apply_unary_op( + inverse_condition_id, ops.invert_op + ) + else: + block, condition_id = block.apply_unary_op( + level_id, ops.partial_right(ops.ne_op, labels) + ) + block = block.filter(condition_id, keep_null=True) + block = block.drop_columns([condition_id]) + return Index._from_block(block) + + def dropna(self, how: str = "any") -> Index: + if how not in ("any", "all"): + raise ValueError("'how' must be one of 'any', 'all'") + result = block_ops.dropna(self._block, self._block.index_columns, how=how) # type: ignore + return Index._from_block(result) + + def drop_duplicates(self, *, keep: str = "first") -> Index: + block = block_ops.drop_duplicates(self._block, self._block.index_columns, keep) + return Index._from_block(block) + + def isin(self, values) -> Index: + if not utils.is_list_like(values): + raise TypeError( + "only list-like objects are allowed to be passed to " + f"isin(), you passed a [{type(values).__name__}]" + ) + + return self._apply_unary_op(ops.IsInOp(values, match_nulls=True)).fillna( + value=False + ) + + def _apply_unary_op( + self, + op: ops.UnaryOp, + ) -> Index: + """Applies a unary operator to the index.""" + block = self._block + result_ids = [] + for col in self._block.index_columns: + block, result_id = block.apply_unary_op(col, op) + result_ids.append(result_id) + + block = block.set_index(result_ids, index_labels=self._block.index_labels) + return Index._from_block(block) + + def _apply_aggregation(self, op: agg_ops.AggregateOp) -> typing.Any: + if self.nlevels > 1: + raise NotImplementedError(f"Multiindex does not yet support {op.name}") + column_id = self._block.index_columns[0] + return self._block.get_stat(column_id, op) + + def __getitem__(self, key: int) -> typing.Any: + if isinstance(key, int): + result_pd_df, _ = self._block.slice(key, key + 1, 1).to_pandas() + if result_pd_df.empty: + raise IndexError("single positional indexer is out-of-bounds") + return result_pd_df.index[0] + else: + raise NotImplementedError(f"Index key not supported {key}") + + def to_pandas(self) -> pandas.Index: + """Gets the Index as a pandas Index. + + Returns: + pandas.Index: + A pandas Index with all of the labels from this Index. + """ + return IndexValue(self._block).to_pandas() + + def to_numpy(self, dtype=None, **kwargs) -> np.ndarray: + return self.to_pandas().to_numpy(dtype, **kwargs) + + __array__ = to_numpy + + def __len__(self): + return self.shape[0] + + @classmethod + def _from_block(cls, block: blocks.Block) -> Index: + import bigframes.dataframe as df + + return Index(df.DataFrame(block)) + + +class IndexValue: + """An immutable index.""" + + def __init__(self, block: blocks.Block): + self._block = block + + @property + def _expr(self) -> core.ArrayValue: + return self._block.expr + + @property + def name(self) -> blocks.Label: + return self._block._index_labels[0] + + @property + def names(self) -> typing.Sequence[blocks.Label]: + return self._block._index_labels + + @property + def nlevels(self) -> int: + return len(self._block._index_columns) + + @property + def dtypes( + self, + ) -> typing.Sequence[typing.Union[bf_dtypes.Dtype, np.dtype[typing.Any]]]: + return self._block.index_dtypes + + def __repr__(self) -> str: + """Converts an Index to a string.""" + # TODO(swast): Add a timeout here? If the query is taking a long time, + # maybe we just print the job metadata that we have so far? + # TODO(swast): Avoid downloading the whole index by using job + # metadata, like we do with DataFrame. + preview = self.to_pandas() + return repr(preview) + + def to_pandas(self) -> pandas.Index: + """Executes deferred operations and downloads the results.""" + # Project down to only the index column. So the query can be cached to visualize other data. + index_columns = list(self._block.index_columns) + dtypes = dict(zip(index_columns, self.dtypes)) + expr = self._expr.select_columns(index_columns) + results, _ = expr.start_query() + df = expr.session._rows_to_dataframe(results, dtypes) + df = df.set_index(index_columns) + index = df.index + index.names = list(self._block._index_labels) + return index + + def join( + self, + other: IndexValue, + *, + how="left", + sort=False, + block_identity_join: bool = False, + ) -> Tuple[IndexValue, Tuple[Mapping[str, str], Mapping[str, str]],]: + if not isinstance(other, IndexValue): + # TODO(swast): We need to improve this error message to be more + # actionable for the user. For example, it's possible they + # could call set_index and try again to resolve this error. + raise ValueError( + f"Tried to join with an unexpected type: {type(other)}. {constants.FEEDBACK_LINK}" + ) + + # TODO(swast): Support cross-joins (requires reindexing). + if how not in {"outer", "left", "right", "inner"}: + raise NotImplementedError( + f"Only how='outer','left','right','inner' currently supported. {constants.FEEDBACK_LINK}" + ) + if self.nlevels == other.nlevels == 1: + return join_mono_indexed( + self, other, how=how, sort=sort, block_identity_join=block_identity_join + ) + else: + # Always sort mult-index join + return join_multi_indexed( + self, other, how=how, sort=sort, block_identity_join=block_identity_join + ) + + def resolve_level_name(self: IndexValue, label: blocks.Label) -> str: + matches = self._block.index_name_to_col_id.get(label, []) + if len(matches) > 1: + raise ValueError(f"Ambiguous index level name {label}") + if len(matches) == 0: + raise ValueError(f"Cannot resolve index level name {label}") + return matches[0] + + def is_uniquely_named(self: IndexValue): + return len(set(self.names)) == len(self.names) + + +def join_mono_indexed( + left: IndexValue, + right: IndexValue, + *, + how="left", + sort=False, + block_identity_join: bool = False, +) -> Tuple[IndexValue, Tuple[Mapping[str, str], Mapping[str, str]],]: + left_expr = left._block.expr + right_expr = right._block.expr + get_column_left, get_column_right = joining.JOIN_NAME_REMAPPER( + left_expr.column_ids, right_expr.column_ids + ) + combined_expr = left._block.expr.join( + left._block.index_columns, + right._block.expr, + right._block.index_columns, + how=how, + allow_row_identity_join=(not block_identity_join), + ) + # Drop original indices from each side. and used the coalesced combination generated by the join. + left_index = get_column_left[left._block.index_columns[0]] + right_index = get_column_right[right._block.index_columns[0]] + # Drop original indices from each side. and used the coalesced combination generated by the join. + combined_expr, coalesced_join_cols = coalesce_columns( + combined_expr, [left_index], [right_index], how=how + ) + if sort: + combined_expr = combined_expr.order_by( + [order.OrderingColumnReference(col_id) for col_id in coalesced_join_cols] + ) + block = blocks.Block( + combined_expr, + index_columns=coalesced_join_cols, + column_labels=[*left._block.column_labels, *right._block.column_labels], + index_labels=[left.name] if left.name == right.name else [None], + ) + return ( + typing.cast(IndexValue, block.index), + (get_column_left, get_column_right), + ) + + +def join_multi_indexed( + left: IndexValue, + right: IndexValue, + *, + how="left", + sort=False, + block_identity_join: bool = False, +) -> Tuple[IndexValue, Tuple[Mapping[str, str], Mapping[str, str]],]: + if not (left.is_uniquely_named() and right.is_uniquely_named()): + raise ValueError("Joins not supported on indices with non-unique level names") + + common_names = [name for name in left.names if name in right.names] + if len(common_names) == 0: + raise ValueError("Cannot join without a index level in common.") + + left_only_names = [name for name in left.names if name not in right.names] + right_only_names = [name for name in right.names if name not in left.names] + + left_join_ids = [left.resolve_level_name(name) for name in common_names] + right_join_ids = [right.resolve_level_name(name) for name in common_names] + + names_fully_match = len(left_only_names) == 0 and len(right_only_names) == 0 + + left_expr = left._block.expr + right_expr = right._block.expr + get_column_left, get_column_right = joining.JOIN_NAME_REMAPPER( + left_expr.column_ids, right_expr.column_ids + ) + + combined_expr = left_expr.join( + left_join_ids, + right_expr, + right_join_ids, + how=how, + # If we're only joining on a subset of the index columns, we need to + # perform a true join. + allow_row_identity_join=(names_fully_match and not block_identity_join), + ) + left_ids_post_join = [get_column_left[id] for id in left_join_ids] + right_ids_post_join = [get_column_right[id] for id in right_join_ids] + # Drop original indices from each side. and used the coalesced combination generated by the join. + combined_expr, coalesced_join_cols = coalesce_columns( + combined_expr, left_ids_post_join, right_ids_post_join, how=how + ) + if sort: + combined_expr = combined_expr.order_by( + [order.OrderingColumnReference(col_id) for col_id in coalesced_join_cols] + ) + + if left.nlevels == 1: + index_labels = right.names + elif right.nlevels == 1: + index_labels = left.names + else: + index_labels = [*common_names, *left_only_names, *right_only_names] + + def resolve_label_id(label: blocks.Label) -> str: + # if name is shared between both blocks, coalesce the values + if label in common_names: + return coalesced_join_cols[common_names.index(label)] + if label in left_only_names: + return get_column_left[left.resolve_level_name(label)] + if label in right_only_names: + return get_column_right[right.resolve_level_name(label)] + raise ValueError(f"Unexpected label: {label}") + + index_columns = [resolve_label_id(label) for label in index_labels] + + block = blocks.Block( + combined_expr, + index_columns=index_columns, + column_labels=[*left._block.column_labels, *right._block.column_labels], + index_labels=index_labels, + ) + return ( + typing.cast(IndexValue, block.index), + (get_column_left, get_column_right), + ) + + +def coalesce_columns( + expr: core.ArrayValue, + left_ids: typing.Sequence[str], + right_ids: typing.Sequence[str], + how: str, +) -> Tuple[core.ArrayValue, Sequence[str]]: + result_ids = [] + for left_id, right_id in zip(left_ids, right_ids): + if how == "left" or how == "inner": + result_ids.append(left_id) + expr = expr.drop_columns([right_id]) + elif how == "right": + result_ids.append(right_id) + expr = expr.drop_columns([left_id]) + elif how == "outer": + coalesced_id = bigframes.core.guid.generate_guid() + expr = expr.project_binary_op( + left_id, right_id, ops.coalesce_op, coalesced_id + ) + expr = expr.drop_columns([left_id, right_id]) + result_ids.append(coalesced_id) + else: + raise ValueError(f"Unexpected join type: {how}. {constants.FEEDBACK_LINK}") + return expr, result_ids diff --git a/bigframes/core/indexes/multi.py b/bigframes/core/indexes/multi.py deleted file mode 100644 index 0b9681b55f6..00000000000 --- a/bigframes/core/indexes/multi.py +++ /dev/null @@ -1,115 +0,0 @@ -# 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. -# 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 typing import TYPE_CHECKING, Hashable, Iterable, Optional, Sequence, cast - -import bigframes_vendored.pandas.core.indexes.multi as vendored_pandas_multindex -import pandas - -from bigframes._tools import docs -from bigframes.core import blocks -from bigframes.core import expression as ex -from bigframes.core.indexes.base import Index - -if TYPE_CHECKING: - import bigframes.session - - -@docs.inherit_docs(vendored_pandas_multindex.MultiIndex) -class MultiIndex(Index): - @classmethod - def from_tuples( - cls, - tuples: Iterable[tuple[Hashable, ...]], - sortorder: int | None = None, - names: Sequence[Hashable] | Hashable | None = None, - *, - session: Optional[bigframes.session.Session] = None, - ) -> MultiIndex: - pd_index = pandas.MultiIndex.from_tuples(tuples, sortorder, names) - # Index.__new__ should detect multiple levels and properly create a multiindex - return cast(MultiIndex, Index(pd_index, session=session)) - - @classmethod - def from_arrays( - cls, - arrays, - sortorder: int | None = None, - names=None, - *, - session: Optional[bigframes.session.Session] = None, - ) -> MultiIndex: - pd_index = pandas.MultiIndex.from_arrays(arrays, sortorder, names) - # Index.__new__ should detect multiple levels and properly create a multiindex - return cast(MultiIndex, Index(pd_index, session=session)) - - def __eq__(self, other) -> Index: # type: ignore - import bigframes.operations as ops - import bigframes.operations.aggregations as agg_ops - - eq_result = self._apply_binary_op(other, ops.eq_op)._block.expr - - as_array = ops.ToArrayOp().as_expr( - *( - ops.fillna_op.as_expr(col, ex.const(False)) - for col in eq_result.column_ids - ) - ) - reduced = ops.ArrayReduceOp(agg_ops.all_op).as_expr(as_array) - result_expr, result_ids = eq_result.compute_values([reduced]) - return Index( - blocks.Block( - result_expr.select_columns(result_ids), - index_columns=result_ids, - column_labels=(), - index_labels=[None], - ) - ) - - -class MultiIndexAccessor: - """Proxy to MultiIndex constructors to allow a session to be passed in.""" - - def __init__(self, session: bigframes.session.Session): - self._session = session - - def __call__(self, *args, **kwargs) -> MultiIndex: - """Construct a MultiIndex using the associated Session. - - See :class:`bigframes.pandas.MultiIndex`. - """ - return MultiIndex(*args, session=self._session, **kwargs) - - def from_arrays(self, *args, **kwargs) -> MultiIndex: - """Construct a MultiIndex using the associated Session. - - See :func:`bigframes.pandas.MultiIndex.from_arrays`. - """ - return MultiIndex.from_arrays(*args, session=self._session, **kwargs) - - def from_frame(self, *args, **kwargs) -> MultiIndex: - """Construct a MultiIndex using the associated Session. - - See :func:`bigframes.pandas.MultiIndex.from_frame`. - """ - return cast(MultiIndex, MultiIndex.from_frame(*args, **kwargs)) - - def from_tuples(self, *args, **kwargs) -> MultiIndex: - """Construct a MultiIndex using the associated Session. - - See :func:`bigframes.pandas.MultiIndex.from_tuples`. - """ - return MultiIndex.from_tuples(*args, session=self._session, **kwargs) diff --git a/bigframes/core/interchange.py b/bigframes/core/interchange.py deleted file mode 100644 index 4dacedd0142..00000000000 --- a/bigframes/core/interchange.py +++ /dev/null @@ -1,155 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -from __future__ import annotations - -import dataclasses -import functools -from typing import TYPE_CHECKING, Any, Dict, Iterable, Optional, Sequence - -import bigframes.enums -from bigframes.core import blocks - -if TYPE_CHECKING: - import bigframes.dataframe - - -@dataclasses.dataclass(frozen=True) -class InterchangeColumn: - _dataframe: InterchangeDataFrame - _pos: int - - @functools.cache - def _arrow_column(self): - # Conservatively downloads the whole underlying dataframe - # This is much better if multiple columns end up being used, - # but does incur a lot of overhead otherwise. - return self._dataframe._arrow_dataframe().get_column(self._pos) - - def size(self) -> int: - return self._arrow_column().size() - - @property - def offset(self) -> int: - return self._arrow_column().offset - - @property - def dtype(self): - return self._arrow_column().dtype - - @property - def describe_categorical(self): - raise TypeError(f"Column type {self.dtype} is not categorical") - - @property - def describe_null(self): - return self._arrow_column().describe_null - - @property - def null_count(self): - return self._arrow_column().null_count - - @property - def metadata(self) -> Dict[str, Any]: - return self._arrow_column().metadata - - def num_chunks(self) -> int: - return self._arrow_column().num_chunks() - - def get_chunks(self, n_chunks: Optional[int] = None) -> Iterable: - return self._arrow_column().get_chunks(n_chunks=n_chunks) - - def get_buffers(self): - return self._arrow_column().get_buffers() - - -@dataclasses.dataclass(frozen=True) -class InterchangeDataFrame: - """ - Implements the dataframe interchange format. - - Mostly implemented by downloading result to pyarrow, and using pyarrow interchange implementation. - """ - - _value: blocks.Block - - version: int = 0 # version of the protocol - - def __dataframe__( - self, nan_as_null: bool = False, allow_copy: bool = True - ) -> InterchangeDataFrame: - return self - - @classmethod - def _from_bigframes(cls, df: bigframes.dataframe.DataFrame): - block = df._block.with_column_labels( - [str(label) for label in df._block.column_labels] - ) - return cls(block) - - # In future, could potentially rely on executor to refetch batches efficiently with caching, - # but safest for now to just request a single execution and save the whole table. - @functools.cache - def _arrow_dataframe(self): - arrow_table, _ = self._value.reset_index( - replacement=bigframes.enums.DefaultIndexKind.NULL - ).to_arrow(allow_large_results=False) - return arrow_table.__dataframe__() - - @property - def metadata(self): - # Allows round-trip without materialization - return {"bigframes.block": self._value} - - def num_columns(self) -> int: - """ - Return the number of columns in the DataFrame. - """ - return len(self._value.value_columns) - - def num_rows(self) -> Optional[int]: - return self._value.shape[0] - - def num_chunks(self) -> int: - return self._arrow_dataframe().num_chunks() - - def column_names(self) -> Iterable[str]: - return [col for col in self._value.column_labels] - - def get_column(self, i: int) -> InterchangeColumn: - return InterchangeColumn(self, i) - - # For single column getters, we download the whole dataframe still - # This is inefficient in some cases, but more efficient in other - def get_column_by_name(self, name: str) -> InterchangeColumn: - col_id = self._value.resolve_label_exact(name) - assert col_id is not None - pos = self._value.value_columns.index(col_id) - return InterchangeColumn(self, pos) - - def get_columns(self) -> Iterable[InterchangeColumn]: - return [InterchangeColumn(self, i) for i in range(self.num_columns())] - - def select_columns(self, indices: Sequence[int]) -> InterchangeDataFrame: - col_ids = [self._value.value_columns[i] for i in indices] - new_value = self._value.select_columns(col_ids) - return InterchangeDataFrame(new_value) - - def select_columns_by_name(self, names: Sequence[str]) -> InterchangeDataFrame: - col_ids = [self._value.resolve_label_exact(name) for name in names] - assert all(id is not None for id in col_ids) - new_value = self._value.select_columns(col_ids) # type: ignore - return InterchangeDataFrame(new_value) - - def get_chunks(self, n_chunks: Optional[int] = None) -> Iterable: - return self._arrow_dataframe().get_chunks(n_chunks) diff --git a/bigframes/core/join_def.py b/bigframes/core/join_def.py deleted file mode 100644 index cd9c2acd174..00000000000 --- a/bigframes/core/join_def.py +++ /dev/null @@ -1,52 +0,0 @@ -# Copyright 2023 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -from __future__ import annotations - -import dataclasses -import enum -from typing import Literal, NamedTuple - - -class JoinSide(enum.Enum): - LEFT = 0 - RIGHT = 1 - - def inverse(self) -> JoinSide: - if self == JoinSide.LEFT: - return JoinSide.RIGHT - return JoinSide.LEFT - - -JoinType = Literal["inner", "outer", "left", "right", "cross"] - - -class JoinCondition(NamedTuple): - left_id: str - right_id: str - - -@dataclasses.dataclass(frozen=True) -class JoinColumnMapping: - source_table: JoinSide - source_id: str - destination_id: str - - -@dataclasses.dataclass(frozen=True) -class CoalescedColumnMapping: - """Special column mapping used only by implicit joiner only""" - - left_source_id: str - right_source_id: str - destination_id: str diff --git a/bigframes/core/joins/__init__.py b/bigframes/core/joins/__init__.py new file mode 100644 index 00000000000..5d407ec22b2 --- /dev/null +++ b/bigframes/core/joins/__init__.py @@ -0,0 +1,20 @@ +# Copyright 2023 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. + +"""Helpers to join ArrayValue objects.""" + +from bigframes.core.joins.merge import merge +from bigframes.core.joins.name_resolution import JOIN_NAME_REMAPPER, JoinNameRemapper + +__all__ = ("merge", "JoinNameRemapper", "JOIN_NAME_REMAPPER") diff --git a/bigframes/core/joins/merge.py b/bigframes/core/joins/merge.py new file mode 100644 index 00000000000..fac16b36078 --- /dev/null +++ b/bigframes/core/joins/merge.py @@ -0,0 +1,67 @@ +# Copyright 2023 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. + +""" +Functions for Merging Data Structures in BigFrames. +""" + +from __future__ import annotations + +from typing import Literal, Optional + +from bigframes.dataframe import DataFrame +from bigframes.series import Series + + +def merge( + left: DataFrame, + right: DataFrame, + how: Literal[ + "inner", + "left", + "outer", + "right", + ] = "inner", + on: Optional[str] = None, + *, + left_on: Optional[str] = None, + right_on: Optional[str] = None, + sort: bool = False, + suffixes: tuple[str, str] = ("_x", "_y"), +) -> DataFrame: + left = _validate_operand(left) + right = _validate_operand(right) + + return left.merge( + right, + how=how, + on=on, + left_on=left_on, + right_on=right_on, + sort=sort, + suffixes=suffixes, + ) + + +def _validate_operand(obj: DataFrame | Series) -> DataFrame: + if isinstance(obj, DataFrame): + return obj + elif isinstance(obj, Series): + if obj.name is None: + raise ValueError("Cannot merge a Series without a name") + return obj.to_frame() + else: + raise TypeError( + f"Can only merge Series or DataFrame objects, a {type(obj)} was passed" + ) diff --git a/bigframes/core/joins/name_resolution.py b/bigframes/core/joins/name_resolution.py new file mode 100644 index 00000000000..df946b3a590 --- /dev/null +++ b/bigframes/core/joins/name_resolution.py @@ -0,0 +1,46 @@ +# Copyright 2023 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 typing import Mapping, Sequence, Tuple + + +class JoinNameRemapper: + def __init__(self, namespace: str) -> None: + self._namespace = namespace + + def __call__( + self, left_column_ids: Sequence[str], right_column_ids: Sequence[str] + ) -> Tuple[Mapping[str, str], Mapping[str, str]]: + """ + When joining column ids from different namespaces, this function defines how names are remapped. + + Take care to map value column ids and hidden column ids in separate namespaces. This is important because value + column ids must be deterministic as they are referenced by dependent operators. The generation of hidden ids is + dependent on compilation context, and should be completely separated from value column id mappings. + """ + # This naming strategy depends on the number of value columns in source tables. + # This means column id mappings must be adjusted if pushing operations above or below join in transformation + new_left_ids = { + col: f"{self._namespace}_l_{i}" for i, col in enumerate(left_column_ids) + } + new_right_ids = { + col: f"{self._namespace}_r_{i}" for i, col in enumerate(right_column_ids) + } + return new_left_ids, new_right_ids + + +# Defines how column ids are remapped, regardless of join strategy or ordering mode +# Use this remapper for all value column remappings. +JOIN_NAME_REMAPPER = JoinNameRemapper("bfjoin") diff --git a/bigframes/core/local_data.py b/bigframes/core/local_data.py deleted file mode 100644 index c05bda7a7fb..00000000000 --- a/bigframes/core/local_data.py +++ /dev/null @@ -1,523 +0,0 @@ -# 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. -# 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. - -"""Methods that deal with local pandas/pyarrow dataframes.""" - -from __future__ import annotations - -import dataclasses -import functools -import io -import itertools -import json -import uuid -from typing import Any, Callable, Generator, Iterable, Literal, Optional, Union, cast - -import geopandas # type: ignore -import numpy -import numpy as np -import pandas as pd -import pyarrow as pa -import pyarrow.parquet # type: ignore - -import bigframes.core.schema as schemata -import bigframes.dtypes -from bigframes.core import identifiers, pyarrow_utils - - -@dataclasses.dataclass(frozen=True) -class LocalTableMetadata: - total_bytes: int - row_count: int - - @classmethod - def from_arrow(cls, table: pa.Table) -> LocalTableMetadata: - return cls(total_bytes=table.nbytes, row_count=table.num_rows) - - -_MANAGED_STORAGE_TYPES_OVERRIDES: dict[bigframes.dtypes.Dtype, pa.DataType] = { - # wkt to be precise - bigframes.dtypes.GEO_DTYPE: pa.string(), - # Just json as string - bigframes.dtypes.JSON_DTYPE: pa.string(), -} - - -@dataclasses.dataclass(frozen=True) -class ManagedArrowTable: - data: pa.Table = dataclasses.field(hash=False, compare=False) - schema: schemata.ArraySchema = dataclasses.field(hash=False, compare=False) - id: uuid.UUID = dataclasses.field(default_factory=uuid.uuid4) - - @functools.cached_property - def metadata(self) -> LocalTableMetadata: - return LocalTableMetadata.from_arrow(self.data) - - @classmethod - def from_pandas(cls, dataframe: pd.DataFrame) -> ManagedArrowTable: - """Creates managed table from pandas. Ignores index, col names must be unique strings""" - columns: list[pa.ChunkedArray] = [] - fields: list[schemata.SchemaItem] = [] - column_names = list(dataframe.columns) - assert len(column_names) == len(set(column_names)) - - for name, col in dataframe.items(): - new_arr, bf_type = _adapt_pandas_series(col) - columns.append(new_arr) - fields.append(schemata.SchemaItem(str(name), bf_type)) - - mat = ManagedArrowTable( - pa.table(columns, names=column_names), schemata.ArraySchema(tuple(fields)) - ) - mat.validate() - return mat - - @classmethod - def from_pyarrow( - cls, table: pa.Table, schema: Optional[schemata.ArraySchema] = None - ) -> ManagedArrowTable: - if schema is not None: - pa_fields = [] - for item in schema.items: - pa_type = _get_managed_storage_type(item.dtype) - pa_fields.append( - pyarrow.field( - item.column, - pa_type, - nullable=not pyarrow.types.is_list(pa_type), - ) - ) - pa_schema = pyarrow.schema(pa_fields) - # assumption: needed transformations can be handled by simple cast. - mat = ManagedArrowTable(table.cast(pa_schema), schema) - mat.validate() - return mat - else: # infer bigframes schema - columns: list[pa.ChunkedArray] = [] - fields: list[schemata.SchemaItem] = [] - for name, arr in zip(table.column_names, table.columns): - new_arr, bf_type = _adapt_chunked_array(arr) - columns.append(new_arr) - fields.append(schemata.SchemaItem(name, bf_type)) - - mat = ManagedArrowTable( - pa.table(columns, names=table.column_names), - schemata.ArraySchema(tuple(fields)), - ) - mat.validate() - return mat - - def to_arrow( - self, - *, - offsets_col: Optional[str] = None, - geo_format: Literal["wkb", "wkt"] = "wkt", - duration_type: Literal["int", "duration"] = "duration", - json_type: Literal["string"] = "string", - sample_rate: Optional[float] = None, - max_chunksize: Optional[int] = None, - ) -> tuple[pa.Schema, Iterable[pa.RecordBatch]]: - if geo_format != "wkt": - raise NotImplementedError(f"geo format {geo_format} not yet implemented") - assert json_type == "string" - - data = self.data - - # This exists for symmetry with remote sources, but sampling local data like this shouldn't really happen - if sample_rate is not None: - to_take = numpy.random.rand(data.num_rows) < sample_rate - data = data.filter(to_take) - - batches = data.to_batches(max_chunksize=max_chunksize) - schema = self.data.schema - if duration_type == "int": - schema = _schema_durations_to_ints(schema) - batches = map( - functools.partial(pyarrow_utils.cast_batch, schema=schema), batches - ) - - if offsets_col is not None: - return schema.append(pa.field(offsets_col, pa.int64())), _append_offsets( - batches, offsets_col - ) - else: - return schema, batches - - def is_nullable(self, column_id: identifiers.ColumnId) -> bool: - return self.data.column(column_id.name).null_count > 0 - - def to_pyarrow_table( - self, - *, - offsets_col: Optional[str] = None, - geo_format: Literal["wkb", "wkt"] = "wkt", - duration_type: Literal["int", "duration"] = "duration", - json_type: Literal["string"] = "string", - ) -> pa.Table: - schema, batches = self.to_arrow( - offsets_col=offsets_col, - geo_format=geo_format, - duration_type=duration_type, - json_type=json_type, - ) - return pa.Table.from_batches(batches, schema) - - def to_parquet( - self, - dst: Union[str, io.IOBase], - *, - offsets_col: Optional[str] = None, - geo_format: Literal["wkb", "wkt"] = "wkt", - duration_type: Literal["int", "duration"] = "duration", - json_type: Literal["string"] = "string", - ): - pa_table = self.to_pyarrow_table( - offsets_col=offsets_col, - geo_format=geo_format, - duration_type=duration_type, - json_type=json_type, - ) - pyarrow.parquet.write_table(pa_table, where=dst) - - def itertuples( - self, - *, - geo_format: Literal["wkb", "wkt"] = "wkt", - duration_type: Literal["int", "timedelta"] = "timedelta", - json_type: Literal["string", "object"] = "string", - ) -> Iterable[tuple]: - """ - Yield each row as an unlabeled tuple. - - Row-wise iteration of columnar data is slow, avoid if possible. - """ - for row_dict in _iter_table( - self.data, - self.schema, - geo_format=geo_format, - duration_type=duration_type, - json_type=json_type, - ): - yield tuple(row_dict.values()) - - def validate(self): - for bf_field, arrow_field in zip(self.schema.items, self.data.schema): - expected_arrow_type = _get_managed_storage_type(bf_field.dtype) - arrow_type = arrow_field.type - if expected_arrow_type != arrow_type: - raise TypeError( - f"Field {bf_field} has arrow array type: {arrow_type}, expected type: {expected_arrow_type}" - ) - - -# Sequential iterator, but could split into batches and leverage parallelism for speed -def _iter_table( - table: pa.Table, - schema: schemata.ArraySchema, - *, - geo_format: Literal["wkb", "wkt"] = "wkt", - duration_type: Literal["int", "timedelta"] = "timedelta", - json_type: Literal["string", "object"] = "string", -) -> Generator[dict[str, Any], None, None]: - """For when you feel like iterating row-wise over a column store. Don't expect speed.""" - - if geo_format != "wkt": - raise NotImplementedError(f"geo format {geo_format} not yet implemented") - - @functools.singledispatch - def iter_array( - array: pa.Array, dtype: bigframes.dtypes.Dtype - ) -> Generator[Any, None, None]: - values = array.to_pylist() - if dtype == bigframes.dtypes.JSON_DTYPE: - if json_type == "object": - yield from map(lambda x: json.loads(x) if x is not None else x, values) - else: - yield from values - elif dtype == bigframes.dtypes.TIMEDELTA_DTYPE: - if duration_type == "int": - yield from map( - lambda x: ( - ((x.days * 3600 * 24) + x.seconds) * 1_000_000 + x.microseconds - if x is not None - else x - ), - values, - ) - else: - yield from values - else: - yield from values - - @iter_array.register - def _( - array: pa.ListArray, dtype: bigframes.dtypes.Dtype - ) -> Generator[Any, None, None]: - value_generator = iter_array( - array.flatten(), bigframes.dtypes.get_array_inner_type(dtype) - ) - offset_generator = iter_array(array.offsets, bigframes.dtypes.INT_DTYPE) - - start_offset = None - end_offset = None - for offset in offset_generator: - start_offset = end_offset - end_offset = offset - if start_offset is not None: - arr_size = end_offset - start_offset - yield list(itertools.islice(value_generator, arr_size)) - - @iter_array.register - def _( - array: pa.StructArray, dtype: bigframes.dtypes.Dtype - ) -> Generator[Any, None, None]: - # yield from each subarray - sub_generators: dict[str, Generator[Any, None, None]] = {} - for field_name, dtype in bigframes.dtypes.get_struct_fields(dtype).items(): - sub_generators[field_name] = iter_array(array.field(field_name), dtype) - - keys = list(sub_generators.keys()) - is_null_generator = iter_array(array.is_null(), bigframes.dtypes.BOOL_DTYPE) - - for values in zip(is_null_generator, *sub_generators.values()): - is_row_null = values[0] - row_values = values[1:] - if not is_row_null: - yield {key: value for key, value in zip(keys, row_values)} - else: - yield None - - for batch in table.to_batches(): - sub_generators: dict[str, Generator[Any, None, None]] = {} - for field in schema.items: - sub_generators[field.column] = iter_array( - batch.column(field.column), field.dtype - ) - - keys = list(sub_generators.keys()) - for row_values in zip(*sub_generators.values()): - yield {key: value for key, value in zip(keys, row_values)} - - -def _adapt_pandas_series( - series: pd.Series, -) -> tuple[Union[pa.ChunkedArray, pa.Array], bigframes.dtypes.Dtype]: - # Mostly rely on pyarrow conversions, but have to convert geo without its help. - if series.dtype == bigframes.dtypes.GEO_DTYPE: - # geoseries produces eg "POINT (1, 1)", while bq uses style "POINT(1, 1)" - # we normalize to bq style for consistency - series = ( - geopandas.GeoSeries(series) - .to_wkt(rounding_precision=-1) - .str.replace(r"(\w+) \(", repl=r"\1(", regex=True) - ) - return pa.array(series, type=pa.string()), bigframes.dtypes.GEO_DTYPE - try: - pa_arr = pa.array(series) - if isinstance(pa_arr, pa.ChunkedArray): - return _adapt_chunked_array(pa_arr) - return _adapt_arrow_array(pa_arr) - except pa.ArrowInvalid as e: - if series.dtype == np.dtype("O"): - try: - return _adapt_pandas_series(series.astype(bigframes.dtypes.GEO_DTYPE)) - except TypeError: - # Prefer original error - pass - raise e - - -def _adapt_chunked_array( - chunked_array: pa.ChunkedArray, -) -> tuple[pa.ChunkedArray, bigframes.dtypes.Dtype]: - if len(chunked_array.chunks) == 0: - return _adapt_arrow_array(chunked_array.combine_chunks()) - dtype = None - arrays = [] - for chunk in chunked_array.chunks: - array, arr_dtype = _adapt_arrow_array(chunk) - arrays.append(array) - dtype = dtype or arr_dtype - assert dtype is not None - return pa.chunked_array(arrays), dtype - - -def _adapt_arrow_array(array: pa.Array) -> tuple[pa.Array, bigframes.dtypes.Dtype]: - """Normalize the array to managed storage types. Preserve shapes, only transforms values.""" - if array.offset != 0: # Offset arrays don't have all operations implemented - return _adapt_arrow_array(pa.concat_arrays([array])) - - if pa.types.is_struct(array.type): - assert isinstance(array, pa.StructArray) - assert isinstance(array.type, pa.StructType) - arrays = [] - dtypes = [] - pa_fields = [] - for i in range(array.type.num_fields): - field_array, field_type = _adapt_arrow_array(array.field(i)) - arrays.append(field_array) - dtypes.append(field_type) - pa_fields.append(pa.field(array.type.field(i).name, field_array.type)) - struct_array = pa.StructArray.from_arrays( - arrays=arrays, fields=pa_fields, mask=array.is_null() - ) - dtype = bigframes.dtypes.struct_type( - [(field.name, dtype) for field, dtype in zip(pa_fields, dtypes)] - ) - return struct_array, dtype - if pa.types.is_list(array.type): - assert isinstance(array, pa.ListArray) - values, values_type = _adapt_arrow_array(array.values) - new_value = pa.ListArray.from_arrays( - array.offsets, values, mask=array.is_null() - ) - return new_value.fill_null([]), bigframes.dtypes.list_type(values_type) - if array.type == bigframes.dtypes.JSON_ARROW_TYPE: - return _canonicalize_json(array), bigframes.dtypes.JSON_DTYPE - target_type = logical_type_replacements(array.type) - if target_type != array.type: - # TODO: Maybe warn if lossy conversion? - array = array.cast(target_type) - bf_type = bigframes.dtypes.arrow_dtype_to_bigframes_dtype( - target_type, allow_lossless_cast=True - ) - - storage_type = _get_managed_storage_type(bf_type) - if storage_type != array.type: - array = array.cast(storage_type) - return array, bf_type - - -def _canonicalize_json(array: pa.Array) -> pa.Array: - def _canonicalize_scalar(json_string): - if json_string is None: - return None - # This is the canonical form that bq uses when emitting json - # The sorted keys and unambiguous whitespace ensures a 1:1 mapping - # between syntax and semantics. - return json.dumps( - json.loads(json_string), sort_keys=True, separators=(",", ":") - ) - - return pa.array( - [_canonicalize_scalar(value) for value in array.to_pylist()], type=pa.string() - ) - - -def _get_managed_storage_type(dtype: bigframes.dtypes.Dtype) -> pa.DataType: - if dtype in _MANAGED_STORAGE_TYPES_OVERRIDES.keys(): - return _MANAGED_STORAGE_TYPES_OVERRIDES[dtype] - return _physical_type_replacements( - bigframes.dtypes.bigframes_dtype_to_arrow_dtype(dtype) - ) - - -def _recursive_map_types( - f: Callable[[pa.DataType], pa.DataType], -) -> Callable[[pa.DataType], pa.DataType]: - @functools.wraps(f) - def recursive_f(type: pa.DataType) -> pa.DataType: - if pa.types.is_list(type): - new_field_t = recursive_f(type.value_type) - if new_field_t != type.value_type: - return pa.list_(new_field_t) - return type - # polars can produce large lists, and we want to map these down to regular lists - if pa.types.is_large_list(type): - new_field_t = recursive_f(type.value_type) - return pa.list_(new_field_t) - if pa.types.is_struct(type): - struct_type = cast(pa.StructType, type) - new_fields: list[pa.Field] = [] - for i in range(struct_type.num_fields): - field = struct_type.field(i) - new_fields.append(field.with_type(recursive_f(field.type))) - return pa.struct(new_fields) - return f(type) - - return recursive_f - - -@_recursive_map_types -def logical_type_replacements(type: pa.DataType) -> pa.DataType: - if pa.types.is_timestamp(type): - # This is potentially lossy, but BigFrames doesn't support ns - new_tz = "UTC" if (type.tz is not None) else None - return pa.timestamp(unit="us", tz=new_tz) - if pa.types.is_time64(type): - # This is potentially lossy, but BigFrames doesn't support ns - return pa.time64("us") - if pa.types.is_duration(type): - # This is potentially lossy, but BigFrames doesn't support ns - return pa.duration("us") - if pa.types.is_decimal128(type): - return pa.decimal128(38, 9) - if pa.types.is_decimal256(type): - return pa.decimal256(76, 38) - if pa.types.is_large_string(type): - # simple string type can handle the largest strings needed - return pa.string() - if pa.types.is_large_binary(type): - # simple string type can handle the largest strings needed - return pa.binary() - if pa.types.is_dictionary(type): - return logical_type_replacements(type.value_type) - if pa.types.is_null(type): - # null as a type not allowed, default type is float64 for bigframes - return pa.float64() - else: - return type - - -_ARROW_MANAGED_STORAGE_OVERRIDES = { - bigframes.dtypes._BIGFRAMES_TO_ARROW[bf_dtype]: arrow_type - for bf_dtype, arrow_type in _MANAGED_STORAGE_TYPES_OVERRIDES.items() - if bf_dtype in bigframes.dtypes._BIGFRAMES_TO_ARROW -} - - -@_recursive_map_types -def _physical_type_replacements(dtype: pa.DataType) -> pa.DataType: - if dtype in _ARROW_MANAGED_STORAGE_OVERRIDES: - return _ARROW_MANAGED_STORAGE_OVERRIDES[dtype] - return dtype - - -def _append_offsets( - batches: Iterable[pa.RecordBatch], offsets_col_name: str -) -> Iterable[pa.RecordBatch]: - offset = 0 - for batch in batches: - offsets = pa.array( - range(offset, offset + batch.num_rows), size=batch.num_rows, type=pa.int64() - ) - batch_w_offsets = pa.record_batch( - [*batch.columns, offsets], - schema=batch.schema.append(pa.field(offsets_col_name, pa.int64())), - ) - offset += batch.num_rows - yield batch_w_offsets - - -@_recursive_map_types -def _durations_to_ints(type: pa.DataType) -> pa.DataType: - if pa.types.is_duration(type): - return pa.int64() - return type - - -def _schema_durations_to_ints(schema: pa.Schema) -> pa.Schema: - return pa.schema( - pa.field(field.name, _durations_to_ints(field.type)) for field in schema - ) diff --git a/bigframes/core/logging/__init__.py b/bigframes/core/logging/__init__.py deleted file mode 100644 index 5d06124efce..00000000000 --- a/bigframes/core/logging/__init__.py +++ /dev/null @@ -1,17 +0,0 @@ -# Copyright 2026 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 bigframes.core.logging import data_types, log_adapter - -__all__ = ["log_adapter", "data_types"] diff --git a/bigframes/core/logging/data_types.py b/bigframes/core/logging/data_types.py deleted file mode 100644 index 3cb65a5c501..00000000000 --- a/bigframes/core/logging/data_types.py +++ /dev/null @@ -1,165 +0,0 @@ -# Copyright 2026 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 functools - -from bigframes import dtypes -from bigframes.core import agg_expressions, bigframe_node, expression, nodes -from bigframes.core.rewrite import schema_binding - -IGNORED_NODES = ( - nodes.SelectionNode, - nodes.ReadLocalNode, - nodes.ReadTableNode, - nodes.ConcatNode, - nodes.RandomSampleNode, - nodes.FromRangeNode, - nodes.PromoteOffsetsNode, - nodes.ReversedNode, - nodes.SliceNode, - nodes.ResultNode, -) - - -def encode_type_refs(root: bigframe_node.BigFrameNode) -> str: - return f"{root.reduce_up(_encode_type_refs_from_node):x}" - - -def _encode_type_refs_from_node( - node: bigframe_node.BigFrameNode, child_results: tuple[int, ...] -) -> int: - child_result = functools.reduce(lambda x, y: x | y, child_results, 0) - - curr_result = 0 - if isinstance(node, nodes.FilterNode): - curr_result = _encode_type_refs_from_expr(node.predicate, node.child) - elif isinstance(node, nodes.ProjectionNode): - for assignment in node.assignments: - expr = assignment[0] - if isinstance(expr, (expression.DerefOp)): - # Ignore direct assignments in projection nodes. - continue - curr_result = curr_result | _encode_type_refs_from_expr( - assignment[0], node.child - ) - elif isinstance(node, nodes.OrderByNode): - for by in node.by: - curr_result = curr_result | _encode_type_refs_from_expr( - by.scalar_expression, node.child - ) - elif isinstance(node, nodes.JoinNode): - for left, right in node.conditions: - curr_result = ( - curr_result - | _encode_type_refs_from_expr(left, node.left_child) - | _encode_type_refs_from_expr(right, node.right_child) - ) - elif isinstance(node, nodes.InNode): - curr_result = _encode_type_refs_from_expr(node.left_col, node.left_child) - elif isinstance(node, nodes.AggregateNode): - for agg, _ in node.aggregations: - curr_result = curr_result | _encode_type_refs_from_expr(agg, node.child) - elif isinstance(node, nodes.WindowOpNode): - for grouping_key in node.window_spec.grouping_keys: - curr_result = curr_result | _encode_type_refs_from_expr( - grouping_key, node.child - ) - for ordering_expr in node.window_spec.ordering: - curr_result = curr_result | _encode_type_refs_from_expr( - ordering_expr.scalar_expression, node.child - ) - for col_def in node.agg_exprs: - curr_result = curr_result | _encode_type_refs_from_expr( - col_def.expression, node.child - ) - elif isinstance(node, nodes.ExplodeNode): - for col_id in node.column_ids: - curr_result = curr_result | _encode_type_refs_from_expr(col_id, node.child) - elif isinstance(node, IGNORED_NODES): - # Do nothing - pass - else: - # For unseen nodes, do not raise errors as this is the logging path, but - # we should cover those nodes either in the branches above, or place them - # in the IGNORED_NODES collection. - pass - - return child_result | curr_result - - -def _encode_type_refs_from_expr( - expr: expression.Expression, child_node: bigframe_node.BigFrameNode -) -> int: - # TODO(b/409387790): Remove this branch once SQLGlot compiler fully replaces Ibis compiler - if not expr.is_resolved: - if isinstance(expr, agg_expressions.Aggregation): - expr = schema_binding._bind_schema_to_aggregation_expr(expr, child_node) - else: - expr = expression.bind_schema_fields(expr, child_node.field_by_id) - - result = _get_dtype_mask(expr.output_type) - for child_expr in expr.children: - result = result | _encode_type_refs_from_expr(child_expr, child_node) - - return result - - -def _get_dtype_mask(dtype: dtypes.Dtype | None) -> int: - if dtype is None: - # If the dtype is not given, ignore - return 0 - if dtype == dtypes.INT_DTYPE: - return 1 << 1 - if dtype == dtypes.FLOAT_DTYPE: - return 1 << 2 - if dtype == dtypes.BOOL_DTYPE: - return 1 << 3 - if dtype == dtypes.STRING_DTYPE: - return 1 << 4 - if dtype == dtypes.BYTES_DTYPE: - return 1 << 5 - if dtype == dtypes.DATE_DTYPE: - return 1 << 6 - if dtype == dtypes.TIME_DTYPE: - return 1 << 7 - if dtype == dtypes.DATETIME_DTYPE: - return 1 << 8 - if dtype == dtypes.TIMESTAMP_DTYPE: - return 1 << 9 - if dtype == dtypes.TIMEDELTA_DTYPE: - return 1 << 10 - if dtype == dtypes.NUMERIC_DTYPE: - return 1 << 11 - if dtype == dtypes.BIGNUMERIC_DTYPE: - return 1 << 12 - if dtype == dtypes.GEO_DTYPE: - return 1 << 13 - if dtype == dtypes.JSON_DTYPE: - return 1 << 14 - - if dtypes.is_struct_like(dtype): - mask = 1 << 15 - if dtype == dtypes.OBJ_REF_DTYPE: - # obj_ref is a special struct type for multi-modal data. - # It should be double counted as both "struct" and its own type. - mask = mask | (1 << 17) - return mask - - if dtypes.is_array_like(dtype): - return 1 << 16 - - # If an unknown datat type is present, mark it with the least significant bit. - return 1 << 0 diff --git a/bigframes/core/logging/log_adapter.py b/bigframes/core/logging/log_adapter.py deleted file mode 100644 index f8b890b3bf9..00000000000 --- a/bigframes/core/logging/log_adapter.py +++ /dev/null @@ -1,336 +0,0 @@ -# Copyright 2023 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 functools -import inspect -import threading -from typing import List, Optional - -import pandas -from google.cloud import bigquery - -_lock = threading.Lock() - -# The limit is 64 (https://cloud.google.com/bigquery/docs/labels-intro#requirements), -# but leave a few spare for internal labels to be added. -# See internal issue 386825477. -MAX_LABELS_COUNT = 64 - 8 -PANDAS_API_TRACKING_TASK = "pandas_api_tracking" -PANDAS_PARAM_TRACKING_TASK = "pandas_param_tracking" -LOG_OVERRIDE_NAME = "__log_override_name__" - -_api_methods: List = [] -_excluded_methods = ["__setattr__", "__getattr__"] - -# Stack to track method calls -_call_stack: List = [] - - -def submit_pandas_labels( - bq_client: Optional[bigquery.Client], - base_name: str, - method_name: str, - args=(), - kwargs={}, - task: str = PANDAS_API_TRACKING_TASK, -): - """ - Submits usage of API to BigQuery using a simulated failed query. - - This function is designed to capture and log details about the usage of pandas methods, - including class and method names, the count of positional arguments, and any keyword - arguments that match the method's signature. To avoid incurring costs, it simulates a - query execution using a query with syntax errors. - - Args: - bq_client (bigquery.Client): The client used to interact with BigQuery. - base_name (str): The name of the pandas class/module being used. - method_name (str): The name of the method being invoked. - args (tuple): The positional arguments passed to the method. - kwargs (dict): The keyword arguments passed to the method. - task (str): The specific task type for the logging event: - - 'PANDAS_API_TRACKING_TASK': Indicates that the unimplemented feature is a method. - - 'PANDAS_PARAM_TRACKING_TASK': Indicates that the unimplemented feature is a - parameter of a method. - """ - if bq_client is None or ( - method_name.startswith("_") and not method_name.startswith("__") - ): - return - - labels_dict = { - "task": task, - "class_name": base_name.lower(), - "method_name": method_name.lower(), - "args_count": len(args), - } - - # getattr(pandas, "pandas") returns pandas - # so we can also use this for pandas.function - if hasattr(pandas, base_name): - base = getattr(pandas, base_name) - else: - return - - # Omit __call__, because its not implemented on the actual instances of - # DataFrame/Series, only as the constructor. - if method_name != "__call__" and hasattr(base, method_name): - method = getattr(base, method_name) - else: - return - - if kwargs: - # Iterate through the keyword arguments and add them to the labels dictionary if they - # are parameters that are implemented in pandas and the maximum label count has not been reached. - signature = inspect.signature(method) - param_names = [param.name for param in signature.parameters.values()] - - idx = 0 - for key in kwargs.keys(): - if len(labels_dict) >= MAX_LABELS_COUNT: - break - if key in param_names: - labels_dict[f"kwargs_{idx}"] = key.lower() - idx += 1 - - # If this log is for tracking unimplemented parameters and no keyword arguments were - # provided, skip logging. - if len(labels_dict) == 4 and task == PANDAS_PARAM_TRACKING_TASK: - return - - # Run a query with syntax error to avoid cost. - query = "SELECT COUNT(x FROM data_table—" - job_config = bigquery.QueryJobConfig(labels=labels_dict) - bq_client.query(query, job_config=job_config) - - -def class_logger(decorated_cls=None): - """Decorator that adds logging functionality to each method of the class.""" - - def wrap(cls): - for attr_name, attr_value in cls.__dict__.items(): - if callable(attr_value) and (attr_name not in _excluded_methods): - if isinstance(attr_value, staticmethod): - setattr( - cls, - attr_name, - staticmethod(method_logger(attr_value)), - ) - else: - setattr( - cls, - attr_name, - method_logger(attr_value), - ) - elif isinstance(attr_value, property): - setattr( - cls, - attr_name, - property_logger(attr_value), - ) - return cls - - if decorated_cls is None: - # The logger is used with parentheses - return wrap - - # The logger is used without parentheses - return wrap(decorated_cls) - - -def method_logger(method=None, /, *, custom_base_name: Optional[str] = None): - """Decorator that adds logging functionality to a method.""" - - def outer_wrapper(method): - @functools.wraps(method) - def wrapper(*args, **kwargs): - api_method_name = getattr( - method, LOG_OVERRIDE_NAME, method.__name__ - ).lower() - if custom_base_name is None: - qualname_parts = getattr(method, "__qualname__", method.__name__).split( - "." - ) - class_name = qualname_parts[-2] if len(qualname_parts) > 1 else "" - base_name = ( - class_name - if class_name - else "_".join(method.__module__.split(".")[1:]) - ) - else: - base_name = custom_base_name - - full_method_name = f"{base_name.lower()}-{api_method_name}" - # Track directly called methods - if len(_call_stack) == 0: - session = _find_session(*args, **kwargs) - add_api_method(full_method_name, session=session) - - _call_stack.append(full_method_name) - - try: - return method(*args, **kwargs) - except (NotImplementedError, TypeError) as e: - # Log method parameters that are implemented in pandas but either missing (TypeError) - # or not fully supported (NotImplementedError) in BigFrames. - # Logging is currently supported only when we can access the bqclient through - # _block.session.bqclient. - if len(_call_stack) == 1: - submit_pandas_labels( - _get_bq_client(*args, **kwargs), - base_name, - api_method_name, - args, - kwargs, - task=PANDAS_PARAM_TRACKING_TASK, - ) - raise e - finally: - _call_stack.pop() - - return wrapper - - if method is None: - # Called with parentheses - return outer_wrapper - - # Called without parentheses - return outer_wrapper(method) - - -def property_logger(prop): - """Decorator that adds logging functionality to a property.""" - - def shared_wrapper(prop): - @functools.wraps(prop) - def wrapped(*args, **kwargs): - qualname_parts = getattr(prop, "__qualname__", prop.__name__).split(".") - class_name = qualname_parts[-2] if len(qualname_parts) > 1 else "" - property_name = prop.__name__ - full_property_name = f"{class_name.lower()}-{property_name.lower()}" - - if len(_call_stack) == 0: - session = _find_session(*args, **kwargs) - add_api_method(full_property_name, session=session) - - _call_stack.append(full_property_name) - try: - return prop(*args, **kwargs) - finally: - _call_stack.pop() - - return wrapped - - # Apply the wrapper to the getter, setter, and deleter - return property( - shared_wrapper(prop.fget), - shared_wrapper(prop.fset) if prop.fset else None, - shared_wrapper(prop.fdel) if prop.fdel else None, - ) - - -def log_name_override(name: str): - """ - Attaches a custom name to be used by logger. - """ - - def wrapper(func): - setattr(func, LOG_OVERRIDE_NAME, name) - return func - - return wrapper - - -def add_api_method(api_method_name, session=None): - global _lock - global _api_methods - - clean_method_name = api_method_name.replace("<", "").replace(">", "") - - if session is not None and _is_session_initialized(session): - with session._api_methods_lock: - session._api_methods.insert(0, clean_method_name) - session._api_methods = session._api_methods[:MAX_LABELS_COUNT] - else: - with _lock: - # Push the method to the front of the _api_methods list - _api_methods.insert(0, clean_method_name) - # Keep the list length within the maximum limit (adjust MAX_LABELS_COUNT as needed) - _api_methods = _api_methods[:MAX_LABELS_COUNT] - - -def get_and_reset_api_methods(dry_run: bool = False, session=None): - global _lock - methods = [] - - if session is not None and _is_session_initialized(session): - with session._api_methods_lock: - methods.extend(session._api_methods) - if not dry_run: - session._api_methods.clear() - - with _lock: - methods.extend(_api_methods) - - # dry_run might not make a job resource, so only reset the log on real queries. - if not dry_run: - _api_methods.clear() - return methods - - -def _get_bq_client(*args, **kwargs): - # Assumes that on BigFrames API errors (TypeError/NotImplementedError), - # an input arg (likely the first, e.g., 'self') has `_block.session.bqclient` - for argv in args: - if hasattr(argv, "_block"): - return argv._block.session.bqclient - - for kwargv in kwargs.values(): - if hasattr(kwargv, "_block"): - return kwargv._block.session.bqclient - - return None - - -def _is_session_initialized(session): - """Return True if fully initialized. - - Because the method logger could get called before Session.__init__ has a - chance to run, we use the globals in that case. - """ - return hasattr(session, "_api_methods_lock") and hasattr(session, "_api_methods") - - -def _find_session(*args, **kwargs): - # This function cannot import Session at the top level because Session - # imports log_adapter. - from bigframes.session import Session - - session = args[0] if args else None - if ( - session is not None - and isinstance(session, Session) - and _is_session_initialized(session) - ): - return session - - session = kwargs.get("session") - if ( - session is not None - and isinstance(session, Session) - and _is_session_initialized(session) - ): - return session - - return None diff --git a/bigframes/core/nodes.py b/bigframes/core/nodes.py index e88a78fae5c..7b252b164f6 100644 --- a/bigframes/core/nodes.py +++ b/bigframes/core/nodes.py @@ -11,1770 +11,235 @@ # 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 abc -import dataclasses +from dataclasses import dataclass, field import functools -import itertools import typing -from typing import ( - AbstractSet, - Callable, - Iterable, - Mapping, - Optional, - Sequence, - Tuple, - cast, -) +from typing import Optional, Tuple + +import pandas -import bigframes.core.expression as ex -import bigframes.core.slices as slices +import bigframes.core.guid +from bigframes.core.ordering import OrderingColumnReference import bigframes.core.window_spec as window import bigframes.dtypes -from bigframes.core import agg_expressions, bq_data, identifiers, local_data, sequences -from bigframes.core.bigframe_node import COLUMN_SET, BigFrameNode -from bigframes.core.field import Field -from bigframes.core.ordering import OrderingExpression, RowOrdering +import bigframes.operations as ops +import bigframes.operations.aggregations as agg_ops if typing.TYPE_CHECKING: - import bigframes.session - - -# A fixed number of variable to assume for overhead on some operations -OVERHEAD_VARIABLES = 5 - - -@dataclasses.dataclass(frozen=True, eq=True) -class ColumnDef: - expression: ex.Expression - id: identifiers.ColumnId - - -class AdditiveNode: - """Definition of additive - if you drop added_fields, you end up with the descendent. - - .. code-block:: text - - AdditiveNode (fields: a, b, c; added_fields: c) - | - | additive_base - V - BigFrameNode (fields: a, b) - - """ - - @property - @abc.abstractmethod - def added_fields(self) -> Tuple[Field, ...]: ... - - @property - @abc.abstractmethod - def additive_base(self) -> BigFrameNode: ... - - @abc.abstractmethod - def replace_additive_base(self, BigFrameNode) -> BigFrameNode: ... - - -@dataclasses.dataclass(frozen=True, eq=False) -class UnaryNode(BigFrameNode): - child: BigFrameNode - - @property - def child_nodes(self) -> typing.Sequence[BigFrameNode]: - return (self.child,) - - @property - def fields(self) -> Sequence[Field]: - return self.child.fields - - @property - def explicitly_ordered(self) -> bool: - return self.child.explicitly_ordered - - def transform_children( - self, t: Callable[[BigFrameNode], BigFrameNode] - ) -> UnaryNode: - transformed = dataclasses.replace(self, child=t(self.child)) - if self == transformed: - # reusing existing object speeds up eq, and saves a small amount of memory - return self - return transformed - - def replace_child(self, new_child: BigFrameNode) -> UnaryNode: - new_self = dataclasses.replace(self, child=new_child) # type: ignore - return new_self + import ibis.expr.types as ibis_types - @property - def order_ambiguous(self) -> bool: - return self.child.order_ambiguous - - -@dataclasses.dataclass(frozen=True, eq=False) -class SliceNode(UnaryNode): - """Logical slice node conditionally becomes limit or filter over row numbers.""" - - start: Optional[int] - stop: Optional[int] - step: int = 1 - - @property - def row_preserving(self) -> bool: - """Whether this node preserves input rows.""" - return False - - @property - def non_local(self) -> bool: - """ - Whether this node combines information across multiple rows instead of processing rows independently. - Used as an approximation for whether the expression may require shuffling to execute (and therefore be expensive). - """ - return True - - # these are overestimates, more accurate numbers available by converting to concrete limit or analytic+filter ops - @property - def variables_introduced(self) -> int: - return 2 - - @property - def relation_ops_created(self) -> int: - return 2 - - @property - def is_limit(self) -> bool: - """Returns whether this is equivalent to a ORDER BY ... LIMIT N.""" - # TODO: Handle tail case. - return ( - (not self.start) - and (self.step == 1) - and (self.stop is not None) - and (self.stop > 0) - ) - - @property - def is_noop(self) -> bool: - """Returns whether this node doesn't actually change the results.""" - # TODO: Handle tail case. - return ( - ((not self.start) or (self.start == 0)) - and (self.step == 1) - and ((self.stop is None) or (self.stop == self.child.row_count)) - ) - - @property - def row_count(self) -> typing.Optional[int]: - child_length = self.child.row_count - if child_length is None: - return None - return slices.slice_output_rows( - (self.start, self.stop, self.step), child_length - ) - - @property - def node_defined_ids(self) -> Tuple[identifiers.ColumnId, ...]: - return () - - @property - def referenced_ids(self) -> COLUMN_SET: - return frozenset() - - def remap_vars( - self, mappings: Mapping[identifiers.ColumnId, identifiers.ColumnId] - ) -> SliceNode: - return self - - def remap_refs( - self, mappings: Mapping[identifiers.ColumnId, identifiers.ColumnId] - ) -> SliceNode: - return self + import bigframes.core.ordering as orderings + import bigframes.session -@dataclasses.dataclass(frozen=True, eq=False) -class InNode(BigFrameNode, AdditiveNode): +@dataclass(frozen=True) +class BigFrameNode: """ - Special Join Type that only returns rows from the left side, as well as adding a bool column indicating whether a match exists on the right side. + Immutable node for representing 2D typed array as a tree of operators. - Modelled separately from join node, as this operation preserves row identity. + All subclasses must be hashable so as to be usable as caching key. """ - left_child: BigFrameNode - right_child: BigFrameNode - left_col: ex.DerefOp - indicator_col: identifiers.ColumnId - # For matching left_col to right_child[0], if true, nulls match nulls, if false, nulls don't match nulls - nulls_equal: bool = True - - def _validate(self): - assert len(self.right_child.fields) == 1 - @property - def row_preserving(self) -> bool: - return False - - @property - def non_local(self) -> bool: + def deterministic(self) -> bool: + """Whether this node will evaluates deterministically.""" return True @property def child_nodes(self) -> typing.Sequence[BigFrameNode]: - return (self.left_child, self.right_child) - - @property - def order_ambiguous(self) -> bool: - return False - - @property - def explicitly_ordered(self) -> bool: - # Preserves left ordering always - return True - - @property - def added_fields(self) -> Tuple[Field, ...]: - return (Field(self.indicator_col, bigframes.dtypes.BOOL_DTYPE, nullable=False),) - - @property - def fields(self) -> Sequence[Field]: - return sequences.ChainedSequence( - self.left_child.fields, - self.added_fields, - ) + """Direct children of this node""" + return tuple([]) @functools.cached_property - def variables_introduced(self) -> int: - """Defines the number of variables generated by the current node. Used to estimate query planning complexity.""" - return 1 - - @property - def joins(self) -> bool: - return True - - @property - def row_count(self) -> Optional[int]: - return self.left_child.row_count - - @property - def node_defined_ids(self) -> Tuple[identifiers.ColumnId, ...]: - return (self.indicator_col,) - - @property - def referenced_ids(self) -> COLUMN_SET: - return frozenset( - { - self.left_col.id, - } - ) + def session(self): + sessions = [] + for child in self.child_nodes: + if child.session is not None: + sessions.append(child.session) + unique_sessions = len(set(sessions)) + if unique_sessions > 1: + raise ValueError("Cannot use combine sources from multiple sessions.") + elif unique_sessions == 1: + return sessions[0] + return None - @property - def additive_base(self) -> BigFrameNode: - return self.left_child - @property - def joins_nulls(self) -> bool: - return self.nulls_equal +@dataclass(frozen=True) +class UnaryNode(BigFrameNode): + child: BigFrameNode @property - def _node_expressions(self): - return (self.left_col,) - - def replace_additive_base(self, node: BigFrameNode): - return dataclasses.replace(self, left_child=node) - - def transform_children(self, t: Callable[[BigFrameNode], BigFrameNode]) -> InNode: - transformed = dataclasses.replace( - self, left_child=t(self.left_child), right_child=t(self.right_child) - ) - if self == transformed: - # reusing existing object speeds up eq, and saves a small amount of memory - return self - return transformed - - def remap_vars( - self, mappings: Mapping[identifiers.ColumnId, identifiers.ColumnId] - ) -> InNode: - return dataclasses.replace( - self, indicator_col=mappings.get(self.indicator_col, self.indicator_col) - ) - - def remap_refs( - self, mappings: Mapping[identifiers.ColumnId, identifiers.ColumnId] - ) -> InNode: - return dataclasses.replace( - self, - left_col=self.left_col.remap_column_refs( - mappings, allow_partial_bindings=True - ), - ) # type: ignore + def child_nodes(self) -> typing.Sequence[BigFrameNode]: + return (self.child,) -@dataclasses.dataclass(frozen=True, eq=False) +@dataclass(frozen=True) class JoinNode(BigFrameNode): left_child: BigFrameNode right_child: BigFrameNode - conditions: typing.Tuple[typing.Tuple[ex.DerefOp, ex.DerefOp], ...] - type: typing.Literal["inner", "outer", "left", "right", "cross"] - # choose to treat nulls as equal or not for purposes of the join - # pandas treats nulls as equal, sql does not - nulls_equal: bool - propogate_order: bool - - def _validate(self): - assert not (set(self.left_child.ids) & set(self.right_child.ids)), ( - "Join ids collide" - ) - - @property - def row_preserving(self) -> bool: - return False - - @property - def non_local(self) -> bool: - return True + left_column_ids: typing.Tuple[str, ...] + right_column_ids: typing.Tuple[str, ...] + how: typing.Literal[ + "inner", + "left", + "outer", + "right", + ] + allow_row_identity_join: bool = True @property def child_nodes(self) -> typing.Sequence[BigFrameNode]: return (self.left_child, self.right_child) - @property - def order_ambiguous(self) -> bool: - return True - - @property - def explicitly_ordered(self) -> bool: - return self.propogate_order - - @functools.cached_property - def fields(self) -> Sequence[Field]: - left_fields: Iterable[Field] = self.left_child.fields - if self.type in ("right", "outer"): - left_fields = map(lambda x: x.with_nullable(), left_fields) - right_fields: Iterable[Field] = self.right_child.fields - if self.type in ("left", "outer"): - right_fields = map(lambda x: x.with_nullable(), right_fields) - return (*left_fields, *right_fields) - - @property - def joins_nulls(self) -> bool: - return self.nulls_equal - - @functools.cached_property - def variables_introduced(self) -> int: - """Defines the number of variables generated by the current node. Used to estimate query planning complexity.""" - return OVERHEAD_VARIABLES - - @property - def joins(self) -> bool: - return True - - @property - def row_count(self) -> Optional[int]: - if self.type == "cross": - if self.left_child.row_count is None or self.right_child.row_count is None: - return None - return self.left_child.row_count * self.right_child.row_count - - return None - - @property - def node_defined_ids(self) -> Tuple[identifiers.ColumnId, ...]: - return () - - @property - def referenced_ids(self) -> COLUMN_SET: - return frozenset( - itertools.chain.from_iterable( - (*l_cond.column_references, *r_cond.column_references) - for l_cond, r_cond in self.conditions - ) - ) - - @property - 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) - ) - if self == transformed: - # reusing existing object speeds up eq, and saves a small amount of memory - return self - return transformed - - def remap_vars( - self, mappings: Mapping[identifiers.ColumnId, identifiers.ColumnId] - ) -> JoinNode: - return self - def remap_refs( - self, mappings: Mapping[identifiers.ColumnId, identifiers.ColumnId] - ) -> JoinNode: - new_conds = tuple( - ( - l_cond.remap_column_refs(mappings, allow_partial_bindings=True), - r_cond.remap_column_refs(mappings, allow_partial_bindings=True), - ) - for l_cond, r_cond in self.conditions - ) - return dataclasses.replace(self, conditions=new_conds) # type: ignore - - -@dataclasses.dataclass(frozen=True, eq=False) +@dataclass(frozen=True) class ConcatNode(BigFrameNode): - # TODO: Explcitly map column ids from each child? children: Tuple[BigFrameNode, ...] - output_ids: Tuple[identifiers.ColumnId, ...] - - def _validate(self): - if len(self.children) == 0: - raise ValueError("Concat requires at least one input table. Zero provided.") - child_schemas = [child.schema.dtypes for child in self.children] - if not len(set(child_schemas)) == 1: - raise ValueError("All inputs must have identical dtypes. {child_schemas}") @property def child_nodes(self) -> typing.Sequence[BigFrameNode]: return self.children - @property - def order_ambiguous(self) -> bool: - return any(child.order_ambiguous for child in self.children) - - @property - def explicitly_ordered(self) -> bool: - # Consider concat as an ordered operations (even though input frames may not be ordered) - return True - - @property - def fields(self) -> Sequence[Field]: - # TODO: Output names should probably be aligned beforehand or be part of concat definition - # TODO: Handle nullability - return tuple( - Field(id, field.dtype) - for id, field in zip(self.output_ids, self.children[0].fields) - ) - - @functools.cached_property - def variables_introduced(self) -> int: - """Defines the number of variables generated by the current node. Used to estimate query planning complexity.""" - return len(self.schema.items) + OVERHEAD_VARIABLES - - @property - def row_count(self) -> Optional[int]: - sub_counts = [node.row_count for node in self.child_nodes] - total = 0 - for count in sub_counts: - if count is None: - return None - total += count - return total - - @property - def node_defined_ids(self) -> Tuple[identifiers.ColumnId, ...]: - return self.output_ids - - def transform_children( - self, t: Callable[[BigFrameNode], BigFrameNode] - ) -> ConcatNode: - transformed = dataclasses.replace( - self, children=tuple(t(child) for child in self.children) - ) - if self == transformed: - # reusing existing object speeds up eq, and saves a small amount of memory - return self - return transformed - - def remap_vars( - self, mappings: Mapping[identifiers.ColumnId, identifiers.ColumnId] - ) -> ConcatNode: - new_ids = tuple(mappings.get(id, id) for id in self.output_ids) - return dataclasses.replace(self, output_ids=new_ids) - - def remap_refs( - self, mappings: Mapping[identifiers.ColumnId, identifiers.ColumnId] - ) -> ConcatNode: - return self - - -@dataclasses.dataclass(frozen=True, eq=False) -class FromRangeNode(BigFrameNode): - # TODO: Enforce single-row, single column constraint - start: BigFrameNode - end: BigFrameNode - step: int - output_id: identifiers.ColumnId = identifiers.ColumnId("labels") - - @property - def roots(self) -> typing.Set[BigFrameNode]: - return {self} - - @property - def child_nodes(self) -> typing.Sequence[BigFrameNode]: - return (self.start, self.end) - - @property - def order_ambiguous(self) -> bool: - return False - - @property - def explicitly_ordered(self) -> bool: - return True - - @functools.cached_property - def fields(self) -> Sequence[Field]: - return ( - Field(self.output_id, next(iter(self.start.fields)).dtype, nullable=False), - ) - - @functools.cached_property - def variables_introduced(self) -> int: - """Defines the number of variables generated by the current node. Used to estimate query planning complexity.""" - return len(self.schema.items) + OVERHEAD_VARIABLES - - @property - def row_count(self) -> Optional[int]: - return None - - @property - def node_defined_ids(self) -> Tuple[identifiers.ColumnId, ...]: - return (self.output_id,) - - @property - def defines_namespace(self) -> bool: - return True - - def transform_children( - self, t: Callable[[BigFrameNode], BigFrameNode] - ) -> FromRangeNode: - transformed = dataclasses.replace(self, start=t(self.start), end=t(self.end)) - if self == transformed: - # reusing existing object speeds up eq, and saves a small amount of memory - return self - return transformed - - def remap_vars( - self, mappings: Mapping[identifiers.ColumnId, identifiers.ColumnId] - ) -> FromRangeNode: - return dataclasses.replace( - self, output_id=mappings.get(self.output_id, self.output_id) - ) - - def remap_refs( - self, mappings: Mapping[identifiers.ColumnId, identifiers.ColumnId] - ) -> FromRangeNode: - return self - # Input Nodex -# TODO: Most leaf nodes produce fixed column names based on the datasource -# They should support renaming -@dataclasses.dataclass(frozen=True, eq=False) -class LeafNode(BigFrameNode): - @property - def roots(self) -> typing.Set[BigFrameNode]: - return {self} - - @property - def fast_offsets(self) -> bool: - return False - - @property - def fast_ordered_limit(self) -> bool: - return False - - def transform_children(self, t: Callable[[BigFrameNode], BigFrameNode]) -> LeafNode: - return self +@dataclass(frozen=True) +class ReadLocalNode(BigFrameNode): + feather_bytes: bytes + column_ids: typing.Tuple[str, ...] -class ScanItem(typing.NamedTuple): - id: identifiers.ColumnId - source_id: str # Flexible enough for both local data and bq data - - def with_id(self, id: identifiers.ColumnId) -> ScanItem: - return ScanItem(id, self.source_id) - - def with_source_id(self, source_id: str) -> ScanItem: - return ScanItem(self.id, source_id) - - -@dataclasses.dataclass(frozen=True) -class ScanList: - """ - Defines the set of columns to scan from a source, along with the variable to bind the columns to. - """ - - items: typing.Tuple[ScanItem, ...] - - @classmethod - def from_items(cls, items: Iterable[ScanItem]) -> ScanList: - return cls(tuple(items)) - - def filter_cols( - self, - ids: AbstractSet[identifiers.ColumnId], - ) -> ScanList: - """Drop columns from the scan that except those in the 'ids' arg.""" - result = ScanList(tuple(item for item in self.items if item.id in ids)) - if len(result.items) == 0: - # We need to select something, or sql syntax breaks - result = ScanList(self.items[:1]) - return result - - def project( - self, - selections: Mapping[identifiers.ColumnId, identifiers.ColumnId], - ) -> ScanList: - """Project given ids from the scanlist, dropping previous bindings.""" - by_id = {item.id: item for item in self.items} - result = ScanList( - tuple( - by_id[old_id].with_id(new_id) for old_id, new_id in selections.items() - ) - ) - if len(result.items) == 0: - # We need to select something, or sql syntax breaks - result = ScanList((self.items[:1])) - return result - - def remap_source_ids( - self, - mapping: Mapping[str, str], - ) -> ScanList: - items = tuple( - item.with_source_id(mapping.get(item.source_id, item.source_id)) - for item in self.items - ) - return ScanList(items) - - def append( - self, source_id: str, dtype: bigframes.dtypes.Dtype, id: identifiers.ColumnId - ) -> ScanList: - return ScanList((*self.items, ScanItem(id, source_id))) - - -@dataclasses.dataclass(frozen=True, eq=False) -class ReadLocalNode(LeafNode): - # TODO: Track nullability for local data - local_data_source: local_data.ManagedArrowTable - # Mapping of local ids to bfet id. - scan_list: ScanList - session: bigframes.session.Session - # Offsets are generated only if this is non-null - offsets_col: Optional[identifiers.ColumnId] = None - - @property - def fields(self) -> Sequence[Field]: - fields = tuple( - Field( - col_id, - self.local_data_source.schema.get_type(source_id), - nullable=self.local_data_source.is_nullable( - identifiers.ColumnId(source_id) - ), - ) - for col_id, source_id in self.scan_list.items - ) - - if self.offsets_col is not None: - return tuple( - itertools.chain( - fields, - ( - Field( - self.offsets_col, bigframes.dtypes.INT_DTYPE, nullable=False - ), - ), - ) - ) - return fields - - @property - def variables_introduced(self) -> int: - """Defines the number of variables generated by the current node. Used to estimate query planning complexity.""" - return len(self.scan_list.items) + 1 - - @property - def fast_offsets(self) -> bool: - return True - - @property - def fast_ordered_limit(self) -> bool: - return True - - @property - def order_ambiguous(self) -> bool: - return False - - @property - def explicitly_ordered(self) -> bool: - return True - - @property - def row_count(self) -> typing.Optional[int]: - return self.local_data_source.metadata.row_count - - @property - def node_defined_ids(self) -> Tuple[identifiers.ColumnId, ...]: - return tuple(item.id for item in self.fields) - - def remap_vars( - self, mappings: Mapping[identifiers.ColumnId, identifiers.ColumnId] - ) -> ReadLocalNode: - new_scan_list = ScanList( - tuple( - ScanItem(mappings.get(item.id, item.id), item.source_id) - for item in self.scan_list.items - ) - ) - new_offsets_col = ( - mappings.get(self.offsets_col, self.offsets_col) - if (self.offsets_col is not None) - else None - ) - return dataclasses.replace( - self, scan_list=new_scan_list, offsets_col=new_offsets_col - ) - - def remap_refs( - self, mappings: Mapping[identifiers.ColumnId, identifiers.ColumnId] - ) -> ReadLocalNode: - return self - - -@dataclasses.dataclass(frozen=True, eq=False) -class ReadTableNode(LeafNode): - source: bq_data.BigqueryDataSource - # Subset of physical schema column - # Mapping of table schema ids to bfet id. - scan_list: ScanList - - table_session: bigframes.session.Session = dataclasses.field() - - def _validate(self): - # enforce invariants - physical_names = set(map(lambda i: i.name, self.source.table.physical_schema)) - if not set(scan.source_id for scan in self.scan_list.items).issubset( - physical_names - ): - raise ValueError( - f"Requested schema {self.scan_list} cannot be derived from table schemal {self.source.table.physical_schema}" - ) +# TODO: Refactor to take raw gbq object reference +@dataclass(frozen=True) +class ReadGbqNode(BigFrameNode): + table: ibis_types.Table = field() + table_session: bigframes.session.Session = field() + columns: Tuple[ibis_types.Value, ...] = field() + hidden_ordering_columns: Tuple[ibis_types.Value, ...] = field() + ordering: orderings.ExpressionOrdering = field() @property def session(self): - return self.table_session - - @property - def fields(self) -> Sequence[Field]: - return tuple( - Field( - col_id, - self.source.schema.get_type(source_id), - self.source.table.schema_by_id[source_id].is_nullable, - ) - for col_id, source_id in self.scan_list.items - ) - - @property - def relation_ops_created(self) -> int: - # Assume worst case, where readgbq actually has baked in analytic operation to generate index - return 3 - - @property - def fast_offsets(self) -> bool: - # Fast head is only supported when row offsets are available or data is clustered over ordering key. - return (self.source.ordering is not None) and self.source.ordering.is_sequential - - @property - def fast_ordered_limit(self) -> bool: - if self.source.ordering is None: - return False - order_cols = self.source.ordering.all_ordering_columns - # monotonicity would probably be fine - if not all(col.scalar_expression.is_identity for col in order_cols): - return False - order_col_ids = tuple( - cast(ex.DerefOp, col.scalar_expression).id.name for col in order_cols - ) - cluster_col_ids = self.source.table.cluster_cols - if cluster_col_ids is None: - return False - - return order_col_ids == cluster_col_ids[: len(order_col_ids)] - - @property - def order_ambiguous(self) -> bool: - return ( - self.source.ordering is None - ) or not self.source.ordering.is_total_ordering - - @property - def explicitly_ordered(self) -> bool: - return self.source.ordering is not None - - @functools.cached_property - def variables_introduced(self) -> int: - return len(self.scan_list.items) + 1 - - @property - def row_count(self) -> typing.Optional[int]: - return self.source.n_rows - - @property - def node_defined_ids(self) -> Tuple[identifiers.ColumnId, ...]: - return tuple(item.id for item in self.scan_list.items) - - def remap_vars( - self, mappings: Mapping[identifiers.ColumnId, identifiers.ColumnId] - ) -> ReadTableNode: - new_scan_list = ScanList( - tuple( - ScanItem(mappings.get(item.id, item.id), item.source_id) - for item in self.scan_list.items - ) - ) - return dataclasses.replace(self, scan_list=new_scan_list) - - def remap_refs( - self, mappings: Mapping[identifiers.ColumnId, identifiers.ColumnId] - ) -> ReadTableNode: - return self - - def pull_out_order(self): - # Maybe the ordering should be required to always be in the scan list, and then we won't need this? - if self.source.ordering is None: - return self, RowOrdering() - - order_cols = {col.sql for col in self.source.ordering.referenced_columns} - scan_cols = {col.source_id for col in self.scan_list.items} - new_scan_cols = [ - ScanItem( - identifiers.ColumnId.unique(), - source_id=field.name, - ) - for field in self.source.table.physical_schema - if (field.name in order_cols) and (field.name not in scan_cols) - ] - new_scan_list = ScanList(items=(*self.scan_list.items, *new_scan_cols)) - new_order = self.source.ordering.remap_column_refs( - { - identifiers.ColumnId(item.source_id): item.id - for item in new_scan_list.items - }, - allow_partial_bindings=True, - ) - new_node = dataclasses.replace( - self, - scan_list=new_scan_list, - source=self.source.with_ordering(RowOrdering()), - ) - return new_node, new_order - - -@dataclasses.dataclass(frozen=True, eq=False) -class CachedTableNode(ReadTableNode): - # The original BFET subtree that was cached - # note: this isn't a "child" node. - original_node: BigFrameNode = dataclasses.field() + return (self.table_session,) # Unary nodes -@dataclasses.dataclass(frozen=True, eq=False) -class PromoteOffsetsNode(UnaryNode, AdditiveNode): - col_id: identifiers.ColumnId - - @property - def non_local(self) -> bool: - return True - - @property - def fields(self) -> Sequence[Field]: - return sequences.ChainedSequence(self.child.fields, self.added_fields) - - @property - def relation_ops_created(self) -> int: - return 2 - - @functools.cached_property - def variables_introduced(self) -> int: - return 1 - - @property - def row_count(self) -> Optional[int]: - return self.child.row_count - - @property - def node_defined_ids(self) -> Tuple[identifiers.ColumnId, ...]: - return (self.col_id,) +@dataclass(frozen=True) +class DropColumnsNode(UnaryNode): + columns: Tuple[str, ...] - @property - def referenced_ids(self) -> COLUMN_SET: - return frozenset() - - @property - def added_fields(self) -> Tuple[Field, ...]: - return (Field(self.col_id, bigframes.dtypes.INT_DTYPE, nullable=False),) - - @property - def additive_base(self) -> BigFrameNode: - return self.child - - def replace_additive_base(self, node: BigFrameNode) -> PromoteOffsetsNode: - return dataclasses.replace(self, child=node) - def remap_vars( - self, mappings: Mapping[identifiers.ColumnId, identifiers.ColumnId] - ) -> PromoteOffsetsNode: - return dataclasses.replace(self, col_id=mappings.get(self.col_id, self.col_id)) +@dataclass(frozen=True) +class PromoteOffsetsNode(UnaryNode): + col_id: str - def remap_refs( - self, mappings: Mapping[identifiers.ColumnId, identifiers.ColumnId] - ) -> PromoteOffsetsNode: - return self - -@dataclasses.dataclass(frozen=True, eq=False) +@dataclass(frozen=True) class FilterNode(UnaryNode): - # TODO: Infer null constraints from predicate - predicate: ex.Expression - - @property - def row_preserving(self) -> bool: - return False - - @property - def variables_introduced(self) -> int: - return 1 + predicate_id: str + keep_null: bool = False - @property - def row_count(self) -> Optional[int]: - return None - - @property - def node_defined_ids(self) -> Tuple[identifiers.ColumnId, ...]: - return () - @property - def consumed_ids(self) -> COLUMN_SET: - return frozenset(self.ids) | self.referenced_ids - - @property - def referenced_ids(self) -> COLUMN_SET: - return frozenset(self.predicate.column_references) - - @property - def _node_expressions(self): - return (self.predicate,) - - def transform_exprs( - self, fn: Callable[[ex.Expression], ex.Expression] - ) -> FilterNode: - return dataclasses.replace( - self, - predicate=fn(self.predicate), - ) - - def remap_vars( - self, mappings: Mapping[identifiers.ColumnId, identifiers.ColumnId] - ) -> FilterNode: - return self - - def remap_refs( - self, mappings: Mapping[identifiers.ColumnId, identifiers.ColumnId] - ) -> FilterNode: - return dataclasses.replace( - self, - predicate=self.predicate.remap_column_refs( - mappings, allow_partial_bindings=True - ), - ) - - -@dataclasses.dataclass(frozen=True, eq=False) +@dataclass(frozen=True) class OrderByNode(UnaryNode): - by: Tuple[OrderingExpression, ...] - stable: bool = True - # This is an optimization, if true, can discard previous orderings, even if doing a stable sort - # might be a total ordering even if false - is_total_order: bool = False - - @property - def variables_introduced(self) -> int: - return 0 + by: Tuple[OrderingColumnReference, ...] + stable: bool = False - @property - def relation_ops_created(self) -> int: - # Doesnt directly create any relational operations - return 0 - - @property - def explicitly_ordered(self) -> bool: - return True - @property - def row_count(self) -> Optional[int]: - return self.child.row_count - - @property - def node_defined_ids(self) -> Tuple[identifiers.ColumnId, ...]: - return () - - @property - def consumed_ids(self) -> COLUMN_SET: - return frozenset(self.ids) | self.referenced_ids - - @property - def referenced_ids(self) -> COLUMN_SET: - return frozenset( - 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 transform_exprs( - self, fn: Callable[[ex.Expression], ex.Expression] - ) -> OrderByNode: - new_by = cast( - tuple[OrderingExpression, ...], - tuple( - dataclasses.replace( - by_expr, scalar_expression=fn(by_expr.scalar_expression) - ) - for by_expr in self.by - ), - ) - return dataclasses.replace(self, by=new_by) - - def remap_vars( - self, mappings: Mapping[identifiers.ColumnId, identifiers.ColumnId] - ) -> OrderByNode: - return self - - def remap_refs( - self, mappings: Mapping[identifiers.ColumnId, identifiers.ColumnId] - ) -> OrderByNode: - all_refs = set( - itertools.chain.from_iterable(map(lambda x: x.referenced_columns, self.by)) - ) - ref_mapping = {id: ex.DerefOp(mappings[id]) for id in all_refs} - return self.transform_exprs( - lambda ex: ex.bind_refs(ref_mapping, allow_partial_bindings=True) - ) - - -@dataclasses.dataclass(frozen=True, eq=False) +@dataclass(frozen=True) class ReversedNode(UnaryNode): - # useless field to make sure has distinct hash - reversed: bool = True - - @property - def variables_introduced(self) -> int: - return 0 - - @property - def relation_ops_created(self) -> int: - # Doesnt directly create any relational operations - return 0 - - @property - def row_count(self) -> Optional[int]: - return self.child.row_count - - @property - def node_defined_ids(self) -> Tuple[identifiers.ColumnId, ...]: - return () - - @property - def referenced_ids(self) -> COLUMN_SET: - return frozenset() - - def remap_vars( - self, mappings: Mapping[identifiers.ColumnId, identifiers.ColumnId] - ) -> ReversedNode: - return self - - def remap_refs( - self, mappings: Mapping[identifiers.ColumnId, identifiers.ColumnId] - ) -> ReversedNode: - return self - - -class AliasedRef(typing.NamedTuple): - ref: ex.DerefOp - id: identifiers.ColumnId - - @classmethod - def identity(cls, id: identifiers.ColumnId) -> AliasedRef: - return cls(ex.DerefOp(id), id) - - def remap_vars( - self, mappings: Mapping[identifiers.ColumnId, identifiers.ColumnId] - ) -> AliasedRef: - return AliasedRef(self.ref, mappings.get(self.id, self.id)) - - def remap_refs( - self, mappings: Mapping[identifiers.ColumnId, identifiers.ColumnId] - ) -> AliasedRef: - return AliasedRef(ex.DerefOp(mappings.get(self.ref.id, self.ref.id)), self.id) + pass -@dataclasses.dataclass(frozen=True, eq=False) -class SelectionNode(UnaryNode): - input_output_pairs: Tuple[AliasedRef, ...] +@dataclass(frozen=True) +class SelectNode(UnaryNode): + column_ids: typing.Tuple[str, ...] - def _validate(self): - for ref, _ in self.input_output_pairs: - if ref.id not in set(self.child.ids): - raise ValueError(f"Reference to column not in child: {ref.id}") - @functools.cached_property - def fields(self) -> Sequence[Field]: - input_fields_by_id = {field.id: field for field in self.child.fields} - return tuple( - Field( - output, - input_fields_by_id[ref.id].dtype, - input_fields_by_id[ref.id].nullable, - ) - for ref, output in self.input_output_pairs - ) - - @property - def variables_introduced(self) -> int: - # This operation only renames variables, doesn't actually create new ones - return 0 - - @property - def has_multi_referenced_ids(self) -> bool: - referenced = tuple(ref.ref.id for ref in self.input_output_pairs) - return len(referenced) != len(set(referenced)) - - # TODO: Reuse parent namespace - # Currently, Selection node allows renaming an reusing existing names, so it must establish a - # new namespace. - @property - def defines_namespace(self) -> bool: - return True - - @property - def row_count(self) -> Optional[int]: - return self.child.row_count - - @property - def node_defined_ids(self) -> Tuple[identifiers.ColumnId, ...]: - return tuple(id for _, id in self.input_output_pairs) - - @property - 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) +@dataclass(frozen=True) +class ProjectUnaryOpNode(UnaryNode): + input_id: str + op: ops.UnaryOp + output_id: Optional[str] = None - def get_id_mapping(self) -> dict[identifiers.ColumnId, identifiers.ColumnId]: - return {ref.id: id for ref, id in self.input_output_pairs} - def remap_vars( - self, mappings: Mapping[identifiers.ColumnId, identifiers.ColumnId] - ) -> SelectionNode: - new_fields = tuple( - item.remap_vars(mappings) for item in self.input_output_pairs - ) - return dataclasses.replace(self, input_output_pairs=new_fields) +@dataclass(frozen=True) +class ProjectBinaryOpNode(UnaryNode): + left_input_id: str + right_input_id: str + op: ops.BinaryOp + output_id: str - def remap_refs( - self, mappings: Mapping[identifiers.ColumnId, identifiers.ColumnId] - ) -> SelectionNode: - new_fields = tuple( - item.remap_refs(mappings) for item in self.input_output_pairs - ) - return dataclasses.replace(self, input_output_pairs=new_fields) # type: ignore +@dataclass(frozen=True) +class ProjectTernaryOpNode(UnaryNode): + input_id1: str + input_id2: str + input_id3: str + op: ops.TernaryOp + output_id: str -@dataclasses.dataclass(frozen=True, eq=False) -class ProjectionNode(UnaryNode, AdditiveNode): - """Assigns new variables (without modifying existing ones)""" - assignments: typing.Tuple[typing.Tuple[ex.Expression, identifiers.ColumnId], ...] - - def _validate(self): - for expression, _ in self.assignments: - # throws TypeError if invalid - _ = ex.bind_schema_fields(expression, self.child.field_by_id).output_type - assert expression.is_scalar_expr - # Cannot assign to existing variables - append only! - assert all(name not in self.child.schema.names for _, name in self.assignments) - - @functools.cached_property - def added_fields(self) -> Tuple[Field, ...]: - fields = [] - for expr, id in self.assignments: - bound_expr = ex.bind_schema_fields(expr, self.child.field_by_id) - field = Field( - id, - bigframes.dtypes.dtype_for_etype(bound_expr.output_type), - nullable=bound_expr.nullable, - ) - - # Special case until we get better nullability inference in expression objects themselves - if bound_expr.is_identity and not any( - self.child.field_by_id[id].nullable for id in expr.column_references - ): - field = field.with_nonnull() - fields.append(field) - - return tuple(fields) - - @property - def fields(self) -> Sequence[Field]: - return sequences.ChainedSequence(self.child.fields, self.added_fields) - - @property - def variables_introduced(self) -> int: - # ignore passthrough expressions - new_vars = sum(1 for i in self.assignments if not i[0].is_identity) - return new_vars - - @property - def row_count(self) -> Optional[int]: - return self.child.row_count - - @property - def node_defined_ids(self) -> Tuple[identifiers.ColumnId, ...]: - return tuple(id for _, id in self.assignments) - - @property - def consumed_ids(self) -> COLUMN_SET: - return frozenset( - itertools.chain.from_iterable( - i[0].column_references for i in self.assignments - ) - ) - - @property - def referenced_ids(self) -> COLUMN_SET: - return frozenset( - itertools.chain.from_iterable( - ex.column_references for ex, id in self.assignments - ) - ) - - @property - def _node_expressions(self): - return tuple(ex for ex, id in self.assignments) - - @property - def additive_base(self) -> BigFrameNode: - return self.child - - def transform_exprs( - self, fn: Callable[[ex.Expression], ex.Expression] - ) -> ProjectionNode: - new_fields = tuple((fn(ex), id) for ex, id in self.assignments) - return dataclasses.replace(self, assignments=new_fields) - - def replace_additive_base(self, node: BigFrameNode) -> ProjectionNode: - return dataclasses.replace(self, child=node) - - def remap_vars( - self, mappings: Mapping[identifiers.ColumnId, identifiers.ColumnId] - ) -> ProjectionNode: - new_fields = tuple((ex, mappings.get(id, id)) for ex, id in self.assignments) - return dataclasses.replace(self, assignments=new_fields) - - def remap_refs( - self, mappings: Mapping[identifiers.ColumnId, identifiers.ColumnId] - ) -> ProjectionNode: - new_fields = tuple( - (ex.remap_column_refs(mappings, allow_partial_bindings=True), id) - for ex, id in self.assignments - ) - return dataclasses.replace(self, assignments=new_fields) - - -@dataclasses.dataclass(frozen=True, eq=False) +@dataclass(frozen=True) class AggregateNode(UnaryNode): - aggregations: typing.Tuple[ - typing.Tuple[agg_expressions.Aggregation, identifiers.ColumnId], ... - ] - by_column_ids: typing.Tuple[ex.DerefOp, ...] = tuple([]) - order_by: Tuple[OrderingExpression, ...] = () + aggregations: typing.Tuple[typing.Tuple[str, agg_ops.AggregateOp, str], ...] + by_column_ids: typing.Tuple[str, ...] = tuple([]) dropna: bool = True - @property - def row_preserving(self) -> bool: - return False - - @property - def non_local(self) -> bool: - return True - - @functools.cached_property - def fields(self) -> Sequence[Field]: - # TODO: Use child nullability to infer grouping key nullability - by_fields = (self.child.field_by_id[ref.id] for ref in self.by_column_ids) - if self.dropna: - by_fields = (field.with_nonnull() for field in by_fields) - # TODO: Label aggregate ops to determine which are guaranteed non-null - agg_items = ( - Field( - id, - ex.bind_schema_fields(agg, self.child.field_by_id).output_type, - nullable=True, - ) - for agg, id in self.aggregations - ) - return tuple(itertools.chain(by_fields, agg_items)) - - @property - def variables_introduced(self) -> int: - return len(self.aggregations) + len(self.by_column_ids) - - @property - def order_ambiguous(self) -> bool: - return False - - @property - def explicitly_ordered(self) -> bool: - return True - - @property - def row_count(self) -> Optional[int]: - if not self.by_column_ids: - return 1 - return None - - @property - def node_defined_ids(self) -> Tuple[identifiers.ColumnId, ...]: - return tuple(id for _, id in self.aggregations) - - @property - def consumed_ids(self) -> COLUMN_SET: - by_ids = (ref.id for ref in self.by_column_ids) - agg_inputs = itertools.chain.from_iterable( - agg.column_references for agg, _ in self.aggregations - ) - order_ids = itertools.chain.from_iterable( - part.scalar_expression.column_references for part in self.order_by - ) - return frozenset(itertools.chain(by_ids, agg_inputs, order_ids)) - - @property - def has_ordered_ops(self) -> bool: - return not all( - 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: - new_aggs = tuple((agg, mappings.get(id, id)) for agg, id in self.aggregations) - return dataclasses.replace(self, aggregations=new_aggs) - def remap_refs( - self, mappings: Mapping[identifiers.ColumnId, identifiers.ColumnId] - ) -> AggregateNode: - new_aggs = tuple( - (agg.remap_column_refs(mappings, allow_partial_bindings=True), id) - for agg, id in self.aggregations - ) - new_by_ids = tuple(id.remap_column_refs(mappings) for id in self.by_column_ids) - new_order_by = tuple(part.remap_column_refs(mappings) for part in self.order_by) - return dataclasses.replace( - self, by_column_ids=new_by_ids, aggregations=new_aggs, order_by=new_order_by - ) +# TODO: Unify into aggregate +@dataclass(frozen=True) +class CorrNode(UnaryNode): + corr_aggregations: typing.Tuple[typing.Tuple[str, str, str], ...] -@dataclasses.dataclass(frozen=True, eq=False) -class WindowOpNode(UnaryNode, AdditiveNode): - agg_exprs: tuple[ColumnDef, ...] # must be analytic/aggregation op +@dataclass(frozen=True) +class WindowOpNode(UnaryNode): + column_name: str + op: agg_ops.WindowOp window_spec: window.WindowSpec + output_name: typing.Optional[str] = None + never_skip_nulls: bool = False + skip_reproject_unsafe: bool = False - def _validate(self): - """Validate the local data in the node.""" - # Since inner order and row bounds are coupled, rank ops can't be row bounded - for cdef in self.agg_exprs: - assert isinstance(cdef.expression, agg_expressions.Aggregation) - if self.window_spec.is_row_bounded: - assert cdef.expression.op.implicitly_inherits_order - for agg_child in cdef.expression.children: - assert agg_child.is_scalar_expr - for ref in cdef.expression.column_references: - assert ref in self.child.ids - assert not any(field.dtype is None for field in self.added_fields) +@dataclass(frozen=True) +class ReprojectOpNode(UnaryNode): + pass - for window_expr in self.window_spec.expressions: - assert window_expr.is_scalar_expr - @property - def non_local(self) -> bool: - return True - - @property - def fields(self) -> Sequence[Field]: - return sequences.ChainedSequence(self.child.fields, self.added_fields) - - @property - def variables_introduced(self) -> int: - return 1 - - @property - def added_fields(self) -> Tuple[Field, ...]: - return tuple( - Field( - cdef.id, - ex.bind_schema_fields( - cdef.expression, self.child.field_by_id - ).output_type, - ) - for cdef in self.agg_exprs - ) - - @property - def relation_ops_created(self) -> int: - return 2 - - @property - def row_count(self) -> Optional[int]: - return self.child.row_count - - @property - def node_defined_ids(self) -> Tuple[identifiers.ColumnId, ...]: - return tuple(field.id for field in self.added_fields) - - @property - def consumed_ids(self) -> COLUMN_SET: - return frozenset(self.ids) - - @property - def referenced_ids(self) -> COLUMN_SET: - ids_for_aggs = itertools.chain.from_iterable( - cdef.expression.column_references for cdef in self.agg_exprs - ) - return ( - frozenset() - .union(ids_for_aggs) - .union(self.window_spec.all_referenced_columns) - ) - - @property - def inherits_order(self) -> bool: - # does the op both use ordering at all? and if so, can it inherit order? - aggs = ( - typing.cast(agg_expressions.Aggregation, cdef.expression) - for cdef in self.agg_exprs - ) - op_inherits_order = any( - not agg.op.order_independent and agg.op.implicitly_inherits_order - for agg in aggs - ) - # range-bounded windows do not inherit orders because their ordering are - # already defined before rewrite time. - return op_inherits_order or self.window_spec.is_row_bounded - - @property - def additive_base(self) -> BigFrameNode: - return self.child +@dataclass(frozen=True) +class UnpivotNode(UnaryNode): + row_labels: typing.Tuple[typing.Hashable, ...] + unpivot_columns: typing.Tuple[ + typing.Tuple[str, typing.Tuple[typing.Optional[str], ...]], ... + ] + passthrough_columns: typing.Tuple[str, ...] = () + index_col_ids: typing.Tuple[str, ...] = ("index",) + dtype: typing.Union[ + bigframes.dtypes.Dtype, typing.Tuple[bigframes.dtypes.Dtype, ...] + ] = (pandas.Float64Dtype(),) + how: typing.Literal["left", "right"] = "left" - @property - def _node_expressions(self): - return ( - *(cdef.expression for cdef in self.agg_exprs), - *self.window_spec.expressions, - ) - def replace_additive_base(self, node: BigFrameNode) -> WindowOpNode: - return dataclasses.replace(self, child=node) +@dataclass(frozen=True) +class AssignNode(UnaryNode): + source_id: str + destination_id: str - def remap_vars( - self, mappings: Mapping[identifiers.ColumnId, identifiers.ColumnId] - ) -> WindowOpNode: - return dataclasses.replace( - self, - agg_exprs=tuple( - ColumnDef(cdef.expression, mappings.get(cdef.id, cdef.id)) - for cdef in self.agg_exprs - ), - ) - def remap_refs( - self, mappings: Mapping[identifiers.ColumnId, identifiers.ColumnId] - ) -> WindowOpNode: - return dataclasses.replace( - self, - agg_exprs=tuple( - ColumnDef( - cdef.expression.remap_column_refs( - mappings, allow_partial_bindings=True - ), - cdef.id, - ) - for cdef in self.agg_exprs - ), - window_spec=self.window_spec.remap_column_refs( - mappings, allow_partial_bindings=True - ), - ) +@dataclass(frozen=True) +class AssignConstantNode(UnaryNode): + destination_id: str + value: typing.Hashable + dtype: typing.Optional[bigframes.dtypes.Dtype] -@dataclasses.dataclass(frozen=True, eq=False) +@dataclass(frozen=True) class RandomSampleNode(UnaryNode): fraction: float @property def deterministic(self) -> bool: return False - - @property - def row_preserving(self) -> bool: - return False - - @property - def variables_introduced(self) -> int: - return 1 - - @property - def row_count(self) -> Optional[int]: - return None - - @property - def node_defined_ids(self) -> Tuple[identifiers.ColumnId, ...]: - return () - - @property - def referenced_ids(self) -> COLUMN_SET: - return frozenset() - - def remap_vars( - self, mappings: Mapping[identifiers.ColumnId, identifiers.ColumnId] - ) -> RandomSampleNode: - return self - - def remap_refs( - self, mappings: Mapping[identifiers.ColumnId, identifiers.ColumnId] - ) -> RandomSampleNode: - return self - - -# TODO: Explode should create a new column instead of overriding the existing one -@dataclasses.dataclass(frozen=True, eq=False) -class ExplodeNode(UnaryNode): - column_ids: typing.Tuple[ex.DerefOp, ...] - # 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 - - @property - def fields(self) -> Sequence[Field]: - fields = ( - Field( - field.id, - bigframes.dtypes.arrow_dtype_to_bigframes_dtype( - self.child.get_type(field.id).pyarrow_dtype.value_type # type: ignore - ), - nullable=True, - ) - if field.id in set(map(lambda x: x.id, self.column_ids)) - else field - for field in self.child.fields - ) - if self.offsets_col is not None: - return tuple( - itertools.chain( - fields, - ( - Field( - self.offsets_col, bigframes.dtypes.INT_DTYPE, nullable=False - ), - ), - ) - ) - return tuple(fields) - - @property - def relation_ops_created(self) -> int: - return 3 - - @functools.cached_property - def variables_introduced(self) -> int: - return len(self.column_ids) + 1 - - @property - def row_count(self) -> Optional[int]: - return None - - @property - def node_defined_ids(self) -> Tuple[identifiers.ColumnId, ...]: - return (self.offsets_col,) if (self.offsets_col is not None) else () - - @property - 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: - if (self.offsets_col is not None) and self.offsets_col in mappings: - return dataclasses.replace(self, offsets_col=mappings[self.offsets_col]) - return self - - def remap_refs( - self, mappings: Mapping[identifiers.ColumnId, identifiers.ColumnId] - ) -> ExplodeNode: - new_ids = tuple(id.remap_column_refs(mappings) for id in self.column_ids) - return dataclasses.replace(self, column_ids=new_ids) # type: ignore - - -# Introduced during planing/compilation -# TODO: Enforce more strictly that this should never be a child node -@dataclasses.dataclass(frozen=True, eq=False) -class ResultNode(UnaryNode): - output_cols: tuple[tuple[ex.DerefOp, str], ...] - order_by: Optional[RowOrdering] = None - limit: Optional[int] = None - # TODO: CTE definitions - - def _validate(self): - for ref, _ in self.output_cols: - assert ref.id in self.child.ids - - @property - def node_defined_ids(self) -> Tuple[identifiers.ColumnId, ...]: - return () - - def remap_vars( - self, mappings: Mapping[identifiers.ColumnId, identifiers.ColumnId] - ) -> ResultNode: - return self - - def remap_refs( - self, mappings: Mapping[identifiers.ColumnId, identifiers.ColumnId] - ) -> ResultNode: - output_cols = tuple( - (ref.remap_column_refs(mappings), name) for ref, name in self.output_cols - ) - order_by = self.order_by.remap_column_refs(mappings) if self.order_by else None - return dataclasses.replace(self, output_cols=output_cols, order_by=order_by) # type: ignore - - @property - def fields(self) -> Sequence[Field]: - # Fields property here is for output schema, not to be consumed by a parent node. - input_fields_by_id = {field.id: field for field in self.child.fields} - return tuple( - Field( - identifiers.ColumnId(output), - input_fields_by_id[ref.id].dtype, - input_fields_by_id[ref.id].nullable, - ) - for ref, output in self.output_cols - ) - - @property - def consumed_ids(self) -> COLUMN_SET: - out_refs = frozenset(ref.id for ref, _ in self.output_cols) - order_refs = self.order_by.referenced_columns if self.order_by else frozenset() - return out_refs | order_refs - - @property - def row_count(self) -> Optional[int]: - child_count = self.child.row_count - if child_count is None: - return None - if self.limit is None: - return child_count - return min(self.limit, child_count) - - @property - def variables_introduced(self) -> int: - return 0 - - @property - def _node_expressions(self): - return tuple(ref for ref, _ in self.output_cols) - - -@dataclasses.dataclass(frozen=True, eq=False) -class CteNode(UnaryNode): - """ - Semantically a no-op, used to indicate shared subtrees and act as optimization boundary. - """ - - @property - def fields(self) -> Sequence[Field]: - return self.child.fields - - @property - def variables_introduced(self) -> int: - return 0 - - @property - def row_count(self) -> Optional[int]: - return self.child.row_count - - @property - def node_defined_ids(self) -> Tuple[identifiers.ColumnId, ...]: - return () - - def remap_vars( - self, mappings: Mapping[identifiers.ColumnId, identifiers.ColumnId] - ) -> CteNode: - return self - - def remap_refs( - self, mappings: Mapping[identifiers.ColumnId, identifiers.ColumnId] - ) -> CteNode: - return self - - -# Tree operators -def top_down( - root: BigFrameNode, - transform: Callable[[BigFrameNode], BigFrameNode], -) -> BigFrameNode: - """ - Perform a top-down transformation of the BigFrameNode tree. - """ - return root.top_down(transform) - - -def bottom_up( - root: BigFrameNode, - transform: Callable[[BigFrameNode], BigFrameNode], -) -> BigFrameNode: - """ - Perform a bottom-up transformation of the BigFrameNode tree. - - The `transform` function is applied to each node *after* its children - have been transformed. This allows for transformations that depend - on the results of transforming subtrees. - - Returns the transformed root node. - """ - return root.bottom_up(transform) diff --git a/bigframes/core/ordered_sets.py b/bigframes/core/ordered_sets.py deleted file mode 100644 index b09c0ce8e0d..00000000000 --- a/bigframes/core/ordered_sets.py +++ /dev/null @@ -1,139 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -from __future__ import annotations - -from typing import ( - Any, - Dict, - Generic, - Hashable, - Iterable, - Iterator, - MutableSet, - Optional, - TypeVar, -) - -T = TypeVar("T", bound=Hashable) - - -class _ListNode(Generic[T]): - """A private class representing a node in the doubly linked list.""" - - __slots__ = ("value", "prev", "next") - - def __init__( - self, - value: Optional[T], - prev: Optional[_ListNode[T]] = None, - next_node: Optional[_ListNode[T]] = None, - ): - self.value = value - self.prev = prev - self.next = next_node - - -class InsertionOrderedSet(MutableSet[T]): - """ - An ordered set implementation that maintains the order in which elements were - first inserted. It provides O(1) average time complexity for addition, - membership testing, and deletion, similar to Python's built-in set. - """ - - def __init__(self, iterable: Optional[Iterable] = None): - # Dictionary mapping element value -> _ListNode instance for O(1) lookup - self._dict: Dict[T, _ListNode[T]] = {} - - # Sentinel nodes for the doubly linked list. They don't hold actual data. - # head.next is the first element, tail.prev is the last element. - self._head: _ListNode[T] = _ListNode(None) - self._tail: _ListNode[T] = _ListNode(None) - self._head.next = self._tail - self._tail.prev = self._head - - if iterable: - self.update(iterable) - - def __len__(self) -> int: - """Return the number of elements in the set.""" - return len(self._dict) - - def __contains__(self, item: Any) -> bool: - """Check if an item is a member of the set (O(1) average).""" - return item in self._dict - - def __iter__(self) -> Iterator[T]: - """Iterate over the elements in insertion order (O(N)).""" - current = self._head.next - while current is not self._tail: - yield current.value # type: ignore - current = current.next # type: ignore - - def _unlink_node(self, node: _ListNode[T]) -> None: - """Helper to remove a node from the linked list.""" - node.prev.next = node.next # type: ignore - node.next.prev = node.prev # type: ignore - # Clear references to aid garbage collection - node.prev = None - node.next = None - - def _append_node(self, node: _ListNode[T]) -> None: - """Helper to append a node to the end of the linked list.""" - last_node = self._tail.prev - last_node.next = node # type: ignore - node.prev = last_node - node.next = self._tail - self._tail.prev = node - - def add(self, value: T) -> None: - """Add an element to the set. If it exists, its order is unchanged (O(1) average).""" - if value not in self._dict: - new_node = _ListNode(value) - self._dict[value] = new_node - self._append_node(new_node) - - def discard(self, value: T) -> None: - """Remove an element from the set if it is a member (O(1) average).""" - if value in self._dict: - node = self._dict.pop(value) - self._unlink_node(node) - - def remove(self, value: T) -> None: - """Remove an element from the set; raises KeyError if not present (O(1) average).""" - if value not in self._dict: - raise KeyError(f"{value} not found in set") - self.discard(value) - - def update(self, *others: Iterable[T]) -> None: - """Update the set with the union of itself and all others.""" - for other in others: - for item in other: - self.add(item) - - def clear(self) -> None: - """Remove all elements from the set.""" - self._dict.clear() - self._head.next = self._tail - self._tail.prev = self._head - - def _replace_contents(self, source: InsertionOrderedSet) -> InsertionOrderedSet: - """Helper method for inplace operators to transfer content from a result set.""" - self.clear() - for item in source: - self.add(item) - return self - - def __repr__(self) -> str: - """Representation of the set.""" - return f"InsertionOrderedSet({list(self)})" diff --git a/bigframes/core/ordering.py b/bigframes/core/ordering.py index 7ad8b6f567a..2cecd2fe7b2 100644 --- a/bigframes/core/ordering.py +++ b/bigframes/core/ordering.py @@ -14,13 +14,21 @@ from __future__ import annotations -import typing from dataclasses import dataclass, field from enum import Enum -from typing import Callable, Mapping, Optional, Sequence, Set, Union +import math +import typing +from typing import Optional, Sequence + +import ibis.expr.datatypes as ibis_dtypes +import ibis.expr.types as ibis_types + +# TODO(tbergeron): Encode more efficiently +ORDERING_ID_STRING_BASE: int = 10 +# Sufficient to store any value up to 2^63 +DEFAULT_ORDERING_ID_LENGTH: int = math.ceil(63 * math.log(2, ORDERING_ID_STRING_BASE)) -import bigframes.core.expression as expression -import bigframes.core.identifiers as ids +STABLE_SORTS = ["mergesort", "stable"] class OrderingDirection(Enum): @@ -39,60 +47,33 @@ def is_ascending(self) -> bool: @dataclass(frozen=True) -class OrderingExpression: +class OrderingColumnReference: """References a column and how to order with respect to values in that column.""" - scalar_expression: expression.Expression + column_id: str direction: OrderingDirection = OrderingDirection.ASC na_last: bool = True - @property - def referenced_columns(self) -> Set[ids.ColumnId]: - return set(self.scalar_expression.column_references) - - @property - def deterministic(self) -> bool: - return self.scalar_expression.deterministic + def with_name(self, name: str): + return OrderingColumnReference(name, self.direction, self.na_last) - def remap_column_refs( - self, - mapping: Mapping[ids.ColumnId, ids.ColumnId], - allow_partial_bindings: bool = False, - ) -> OrderingExpression: - return self.bind_refs( - {old_id: expression.DerefOp(new_id) for old_id, new_id in mapping.items()}, - allow_partial_bindings=allow_partial_bindings, + def with_reverse(self): + return OrderingColumnReference( + self.column_id, self.direction.reverse(), not self.na_last ) - def bind_refs( - self, - mapping: Mapping[ids.ColumnId, expression.Expression], - allow_partial_bindings: bool = False, - ) -> OrderingExpression: - return OrderingExpression( - self.scalar_expression.bind_refs( - mapping, allow_partial_bindings=allow_partial_bindings - ), - self.direction, - self.na_last, - ) - def with_reverse(self) -> OrderingExpression: - return OrderingExpression( - self.scalar_expression, self.direction.reverse(), not self.na_last - ) +# Encoding classes specify additional properties for some ordering representations +@dataclass(frozen=True) +class StringEncoding: + """String encoded order ids are fixed length and can be concat together in joins.""" - def transform_exprs( - self, t: Callable[[expression.Expression], expression.Expression] - ) -> OrderingExpression: - return OrderingExpression( - t(self.scalar_expression), - self.direction, - self.na_last, - ) + is_encoded: bool = False + # Encoding size must be tracked in order to know what how to combine ordering ids across tables (eg how much to pad when combining different length). + # Also will be needed to determine when length is too large and need to compact ordering id with a ROW_NUMBER operation. + length: int = DEFAULT_ORDERING_ID_LENGTH -# Encoding classes specify additional properties for some ordering representations @dataclass(frozen=True) class IntegerEncoding: """Integer encoded order ids are guaranteed non-negative.""" @@ -102,154 +83,16 @@ class IntegerEncoding: @dataclass(frozen=True) -class RowOrdering: - """Immutable object that holds information about the ordering of rows in a ArrayValue object. May not be unambiguous.""" +class ExpressionOrdering: + """Immutable object that holds information about the ordering of rows in a ArrayValue object.""" - ordering_value_columns: typing.Tuple[OrderingExpression, ...] = () + ordering_value_columns: typing.Tuple[OrderingColumnReference, ...] = () integer_encoding: IntegerEncoding = IntegerEncoding(False) - - @property - def all_ordering_columns(self) -> Sequence[OrderingExpression]: - return list(self.ordering_value_columns) - - @property - def referenced_columns(self) -> Set[ids.ColumnId]: - return set( - col - for part in self.ordering_value_columns - for col in part.referenced_columns - ) - - @property - def is_sequential(self) -> bool: - return self.integer_encoding.is_encoded and self.integer_encoding.is_sequential - - @property - def is_total_ordering(self) -> bool: - return False - - @property - def total_order_col(self) -> Optional[OrderingExpression]: - """Returns column id of columns that defines total ordering, if such as column exists""" - return None - - def with_reverse(self) -> RowOrdering: - """Reverses the ordering.""" - return RowOrdering( - tuple([col.with_reverse() for col in self.ordering_value_columns]), - ) - - def remap_column_refs( - self, - mapping: typing.Mapping[ids.ColumnId, ids.ColumnId], - allow_partial_bindings: bool = False, - ) -> RowOrdering: - new_value_columns = [ - col.remap_column_refs( - mapping, allow_partial_bindings=allow_partial_bindings - ) - for col in self.all_ordering_columns - ] - return RowOrdering( - tuple(new_value_columns), - ) - - def with_non_sequential(self): - """Create a copy that is marked as non-sequential. - - This is useful when filtering, but not sorting, an expression. - """ - if self.integer_encoding.is_sequential: - return RowOrdering( - self.ordering_value_columns, - integer_encoding=IntegerEncoding( - self.integer_encoding.is_encoded, is_sequential=False - ), - ) - - return self - - def with_ordering_columns( - self, - ordering_value_columns: Sequence[OrderingExpression] = (), - ) -> RowOrdering: - """Creates a new ordering that reorders by the given columns. - - Args: - ordering_value_columns: - In decreasing precedence order, the values used to sort the ordering - - Returns: - Modified ExpressionOrdering - """ - - # Truncate to remove any unneded col references after all total order cols included - new_ordering = self._truncate_ordering( - (*ordering_value_columns, *self.ordering_value_columns) - ) - return RowOrdering( - new_ordering, - ) - - def join( - self, - other: RowOrdering, - ) -> RowOrdering: - joined_refs = [*self.all_ordering_columns, *other.all_ordering_columns] - return RowOrdering(tuple(joined_refs)) - - def _truncate_ordering( - self, order_refs: tuple[OrderingExpression, ...] - ) -> tuple[OrderingExpression, ...]: - # Truncate once we refer to a full key in bijective operations - columns_seen: Set[ids.ColumnId] = set() - truncated_refs = [] - for order_part in order_refs: - expr = order_part.scalar_expression - if not set(expr.column_references).issubset(columns_seen): - if expr.is_bijective: - columns_seen.update(expr.column_references) - truncated_refs.append(order_part) - return tuple(truncated_refs) - - -@dataclass(frozen=True) -class TotalOrdering(RowOrdering): - """Immutable object that holds information about the ordering of rows in a ArrayValue object. Guaranteed to be unambiguous.""" - - def __post_init__(self): - assert set(ref.id for ref in self.total_ordering_columns).issubset( - self.referenced_columns - ) - + string_encoding: StringEncoding = StringEncoding(False) # A table has a total ordering defined by the identities of a set of 1 or more columns. # These columns must always be part of the ordering, in order to guarantee that the ordering is total. # Therefore, any modifications(or drops) done to these columns must result in hidden copies being made. - total_ordering_columns: frozenset[expression.DerefOp] = field( - default_factory=frozenset - ) - - @classmethod - def from_offset_col(cls, col: Union[ids.ColumnId, str]) -> TotalOrdering: - col_id = ids.ColumnId(col) if isinstance(col, str) else col - return TotalOrdering( - (ascending_over(col),), - integer_encoding=IntegerEncoding(True, is_sequential=True), - total_ordering_columns=frozenset({expression.DerefOp(col_id)}), - ) - - @classmethod - def from_primary_key(cls, primary_key: Sequence[ids.ColumnId]) -> TotalOrdering: - return TotalOrdering( - tuple(ascending_over(col) for col in primary_key), - total_ordering_columns=frozenset( - {expression.DerefOp(col) for col in primary_key} - ), - ) - - @property - def is_total_ordering(self) -> bool: - return True + total_ordering_columns: frozenset[str] = field(default_factory=frozenset) def with_non_sequential(self): """Create a copy that is marked as non-sequential. @@ -257,7 +100,7 @@ def with_non_sequential(self): This is useful when filtering, but not sorting, an expression. """ if self.integer_encoding.is_sequential: - return TotalOrdering( + return ExpressionOrdering( self.ordering_value_columns, integer_encoding=IntegerEncoding( self.integer_encoding.is_encoded, is_sequential=False @@ -269,103 +112,72 @@ def with_non_sequential(self): def with_ordering_columns( self, - ordering_value_columns: Sequence[OrderingExpression] = (), - ) -> TotalOrdering: + ordering_value_columns: Sequence[OrderingColumnReference] = (), + stable: bool = False, + ) -> ExpressionOrdering: """Creates a new ordering that reorders by the given columns. Args: ordering_value_columns: In decreasing precedence order, the values used to sort the ordering + stable: + If True, will use apply a stable sorting, using the old ordering where + the new ordering produces ties. Otherwise, ties will be resolved in + a performance maximizing way, Returns: Modified ExpressionOrdering """ - - # Truncate to remove any unneded col references after all total order cols included - new_ordering = self._truncate_ordering( - (*ordering_value_columns, *self.ordering_value_columns) - ) - return TotalOrdering( + col_ids_new = [ + ordering_ref.column_id for ordering_ref in ordering_value_columns + ] + if stable: + # Only reference each column once, so discard old referenc if there is a new reference + old_ordering_keep = [ + ordering_ref + for ordering_ref in self.ordering_value_columns + if ordering_ref.column_id not in col_ids_new + ] + else: + # New ordering needs to keep all total ordering columns no matter what. + # All other old ordering references can be discarded as does not need + # to be a stable sort. + old_ordering_keep = [ + ordering_ref + for ordering_ref in self.ordering_value_columns + if (ordering_ref.column_id not in col_ids_new) + and (ordering_ref.column_id in self.total_ordering_columns) + ] + new_ordering = (*ordering_value_columns, *old_ordering_keep) + return ExpressionOrdering( new_ordering, total_ordering_columns=self.total_ordering_columns, ) - def _truncate_ordering( - self, order_refs: tuple[OrderingExpression, ...] - ) -> tuple[OrderingExpression, ...]: - # Truncate once we refer to a full key in bijective operations - must_see = set(ref.id for ref in self.total_ordering_columns) - columns_seen: Set[ids.ColumnId] = set() - truncated_refs = [] - for order_part in order_refs: - expr = order_part.scalar_expression - if not set(expr.column_references).issubset(columns_seen): - if expr.is_bijective: - columns_seen.update(expr.column_references) - truncated_refs.append(order_part) - if columns_seen.issuperset(must_see): - return tuple(truncated_refs) - if len(must_see) == 0: - return () - raise ValueError("Ordering did not contain all total_order_cols") - def with_reverse(self): """Reverses the ordering.""" - return TotalOrdering( + return ExpressionOrdering( tuple([col.with_reverse() for col in self.ordering_value_columns]), total_ordering_columns=self.total_ordering_columns, ) - def remap_column_refs( - self, - mapping: typing.Mapping[ids.ColumnId, ids.ColumnId], - allow_partial_bindings: bool = False, - ): + def with_column_remap(self, mapping: typing.Mapping[str, str]): new_value_columns = [ - col.remap_column_refs( - mapping, allow_partial_bindings=allow_partial_bindings - ) - for col in self.all_ordering_columns + col.with_name(mapping.get(col.column_id, col.column_id)) + for col in self.ordering_value_columns ] new_total_order = frozenset( - expression.DerefOp(mapping.get(col_id.id, col_id.id)) - for col_id in self.total_ordering_columns + mapping.get(col_id, col_id) for col_id in self.total_ordering_columns ) - return TotalOrdering( + return ExpressionOrdering( tuple(new_value_columns), integer_encoding=self.integer_encoding, + string_encoding=self.string_encoding, total_ordering_columns=new_total_order, ) - @typing.overload - def join( - self, - other: TotalOrdering, - ) -> TotalOrdering: ... - - @typing.overload - def join( - self, - other: RowOrdering, - ) -> RowOrdering: ... - - def join( - self, - other: RowOrdering, - ) -> RowOrdering: - joined_refs = [*self.all_ordering_columns, *other.all_ordering_columns] - if isinstance(other, TotalOrdering): - left_total_order_cols = frozenset(self.total_ordering_columns) - right_total_order_cols = frozenset(other.total_ordering_columns) - return TotalOrdering( - ordering_value_columns=tuple(joined_refs), - total_ordering_columns=left_total_order_cols | right_total_order_cols, - ) - else: - return RowOrdering(tuple(joined_refs)) - @property - def total_order_col(self) -> Optional[OrderingExpression]: + def total_order_col(self) -> Optional[OrderingColumnReference]: """Returns column id of columns that defines total ordering, if such as column exists""" if len(self.ordering_value_columns) != 1: return None @@ -374,19 +186,39 @@ def total_order_col(self) -> Optional[OrderingExpression]: return None return order_ref + @property + def is_string_encoded(self) -> bool: + """True if ordering is fully defined by a fixed length string column.""" + return self.string_encoding.is_encoded -# Convenience functions -def ascending_over( - id: Union[ids.ColumnId, str], nulls_last: bool = True -) -> OrderingExpression: - col_id = ids.ColumnId(id) if isinstance(id, str) else id - return OrderingExpression(expression.DerefOp(col_id), na_last=nulls_last) + @property + def is_sequential(self) -> bool: + return self.integer_encoding.is_encoded and self.integer_encoding.is_sequential + + @property + def all_ordering_columns(self) -> Sequence[OrderingColumnReference]: + return list(self.ordering_value_columns) -def descending_over( - id: Union[ids.ColumnId, str], nulls_last: bool = True -) -> OrderingExpression: - col_id = ids.ColumnId(id) if isinstance(id, str) else id - return OrderingExpression( - expression.DerefOp(col_id), direction=OrderingDirection.DESC, na_last=nulls_last +def encode_order_string( + order_id: ibis_types.IntegerColumn, length: int = DEFAULT_ORDERING_ID_LENGTH +) -> ibis_types.StringColumn: + """Converts an order id value to string if it is not already a string. MUST produced fixed-length strings.""" + # This is very inefficient encoding base-10 string uses only 10 characters per byte(out of 256 bit combinations) + # Furthermore, if know tighter bounds on order id are known, can produce smaller strings. + # 19 characters chosen as it can represent any positive Int64 in base-10 + # For missing values, ":" * 19 is used as it is larger than any other value this function produces, so null values will be last. + string_order_id = typing.cast( + ibis_types.StringValue, + order_id.cast(ibis_dtypes.string), + ).lpad(length, "0") + return typing.cast(ibis_types.StringColumn, string_order_id) + + +def reencode_order_string( + order_id: ibis_types.StringColumn, length: int +) -> ibis_types.StringColumn: + return typing.cast( + ibis_types.StringColumn, + (typing.cast(ibis_types.StringValue, order_id).lpad(length, "0")), ) diff --git a/bigframes/core/pruning.py b/bigframes/core/pruning.py deleted file mode 100644 index f98b8eb5d58..00000000000 --- a/bigframes/core/pruning.py +++ /dev/null @@ -1,86 +0,0 @@ -# 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. -# 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 typing import TYPE_CHECKING, Set - -import bigframes.core.expression as ex -import bigframes.core.identifiers as ids -import bigframes.core.nodes -import bigframes.dtypes -import bigframes.operations as ops - -if TYPE_CHECKING: - import bigframes.core.nodes - - -LOW_CARDINALITY_TYPES = [bigframes.dtypes.BOOL_DTYPE] - -COMPARISON_OP_TYPES = tuple( - type(i) - for i in ( - ops.eq_op, - ops.eq_null_match_op, - ops.ne_op, - ops.gt_op, - ops.ge_op, - ops.lt_op, - ops.le_op, - ) -) - - -def cluster_cols_for_predicate( - predicate: ex.Expression, clusterable_cols: Set[ids.ColumnId] -) -> list[ids.ColumnId]: - """Try to determine cluster col candidates that work with given predicates.""" - # TODO: Prioritize based on predicted selectivity (eg. equality conditions are probably very selective) - if isinstance(predicate, ex.DerefOp): - cols = [predicate.id] - elif isinstance(predicate, ex.OpExpression): - op = predicate.op - # TODO: Support geo predicates, which support pruning if clustered (other than st_disjoint) - # https://cloud.google.com/bigquery/docs/reference/standard-sql/geography_functions - if isinstance(op, COMPARISON_OP_TYPES): - cols = cluster_cols_for_comparison(predicate.inputs[0], predicate.inputs[1]) - elif isinstance(op, (type(ops.invert_op))): - cols = cluster_cols_for_predicate(predicate.inputs[0], clusterable_cols) - elif isinstance(op, (type(ops.and_op), type(ops.or_op))): - left_cols = cluster_cols_for_predicate( - predicate.inputs[0], clusterable_cols - ) - right_cols = cluster_cols_for_predicate( - predicate.inputs[1], clusterable_cols - ) - cols = [*left_cols, *[col for col in right_cols if col not in left_cols]] - else: - cols = [] - else: - # Constant - cols = [] - return [col for col in cols if col in clusterable_cols] - - -def cluster_cols_for_comparison( - left_ex: ex.Expression, right_ex: ex.Expression -) -> list[ids.ColumnId]: - # TODO: Try to normalize expressions such that one side is a single variable. - # eg. Convert -cola>=3 to cola<-3 and colb+3 < 4 to colb < 1 - if left_ex.is_const: - # There are some invertible ops that would also be ok - if isinstance(right_ex, ex.DerefOp): - return [right_ex.id] - elif right_ex.is_const: - if isinstance(left_ex, ex.DerefOp): - return [left_ex.id] - return [] diff --git a/bigframes/core/py_expressions.py b/bigframes/core/py_expressions.py deleted file mode 100644 index da937677050..00000000000 --- a/bigframes/core/py_expressions.py +++ /dev/null @@ -1,595 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from __future__ import annotations - -import dataclasses -import itertools -import operator -from types import ModuleType -from typing import Callable, Hashable, Mapping, Optional, Tuple - -import bigframes.core.agg_expressions as agg_exprs -import bigframes.operations.aggregations as agg_ops -import bigframes.operations.python_op_maps as python_op_maps -from bigframes import dtypes -from bigframes.core import identifiers -from bigframes.core import window_spec as window_specs -from bigframes.core.expression import ( - Expression, - OpExpression, - ScalarConstantExpression, - UnboundVariableExpression, - const, - deref, -) -from bigframes.operations import ( - NUMPY_TO_BINOP, - NUMPY_TO_OP, - ScalarOp, - generic_ops, - numeric_ops, -) - -_CALLABLE_TO_OP = { - **NUMPY_TO_OP, - **NUMPY_TO_BINOP, -} - -_BUILTIN_CALLABLES = { - str: generic_ops.AsTypeOp(dtypes.STRING_DTYPE), - abs: numeric_ops.abs_op, -} - - -@dataclasses.dataclass(frozen=True) -class GetAttr(Expression): - input: Expression - attr: str - - @property - def column_references( - self, - ) -> Tuple[identifiers.ColumnId, ...]: - return self.input.column_references - - @property - def free_variables(self) -> tuple[Hashable, ...]: - return self.input.free_variables - - @property - def is_const(self) -> bool: - return False - - @property - def children(self): - return (self.input,) - - @property - def nullable(self) -> bool: - return True - - @property - def is_resolved(self) -> bool: - return False - - @property - def output_type(self) -> dtypes.ExpressionType: - raise ValueError(f"Type of expression {self} has not been fixed.") - - @property - def is_bijective(self) -> bool: - # TODO: Mark individual functions as bijective? - return False - - @property - def deterministic(self) -> bool: - return True - - def transform_children(self, t: Callable[[Expression], Expression]) -> Expression: - new_input = t(self.input) - if new_input != self.input: - return dataclasses.replace(self, input=new_input) - return self - - def bind_variables( - self, - bindings: Mapping[Hashable, Expression], - allow_partial_bindings: bool = False, - ) -> GetAttr: - return GetAttr( - self.input.bind_variables( - bindings, allow_partial_bindings=allow_partial_bindings - ), - self.attr, - ) - - def bind_refs( - self, - bindings: Mapping[identifiers.ColumnId, Expression], - allow_partial_bindings: bool = False, - ) -> GetAttr: - return GetAttr( - self.input.bind_refs( - bindings, allow_partial_bindings=allow_partial_bindings - ), - self.attr, - ) - - -@dataclasses.dataclass(frozen=True) -class GetItem(Expression): - input: Expression - key: Expression - - @property - def column_references(self) -> Tuple[identifiers.ColumnId, ...]: - return self.input.column_references + self.key.column_references - - @property - def free_variables(self) -> tuple[Hashable, ...]: - return self.input.free_variables + self.key.free_variables - - @property - def is_const(self) -> bool: - return False - - @property - def children(self): - return (self.input, self.key) - - @property - def nullable(self) -> bool: - return True - - @property - def is_resolved(self) -> bool: - return False - - @property - def output_type(self) -> dtypes.ExpressionType: - raise ValueError(f"Type of expression {self} has not been fixed.") - - @property - def is_bijective(self) -> bool: - return False - - @property - def deterministic(self) -> bool: - return True - - def transform_children(self, t: Callable[[Expression], Expression]) -> Expression: - new_input = t(self.input) - new_key = t(self.key) - if new_input != self.input or new_key != self.key: - return dataclasses.replace(self, input=new_input, key=new_key) - return self - - def bind_variables( - self, - bindings: Mapping[Hashable, Expression], - allow_partial_bindings: bool = False, - ) -> GetItem: - return GetItem( - self.input.bind_variables( - bindings, allow_partial_bindings=allow_partial_bindings - ), - self.key.bind_variables( - bindings, allow_partial_bindings=allow_partial_bindings - ), - ) - - def bind_refs( - self, - bindings: Mapping[identifiers.ColumnId, Expression], - allow_partial_bindings: bool = False, - ) -> GetItem: - return GetItem( - self.input.bind_refs( - bindings, allow_partial_bindings=allow_partial_bindings - ), - self.key.bind_refs(bindings, allow_partial_bindings=allow_partial_bindings), - ) - - -@dataclasses.dataclass(frozen=True) -class Module(Expression): - """An expression representing a module reference.""" - - module: ModuleType - - @property - def is_const(self) -> bool: - return True - - @property - def column_references(self) -> Tuple[identifiers.ColumnId, ...]: - return () - - @property - def nullable(self) -> bool: - return True # type: ignore - - @property - def is_resolved(self) -> bool: - return False - - @property - def output_type(self) -> dtypes.ExpressionType: - raise ValueError("Module expression does not have a type.") - - def bind_variables( - self, - bindings: Mapping[Hashable, Expression], - allow_partial_bindings: bool = False, - ) -> Expression: - return self - - def bind_refs( - self, - bindings: Mapping[identifiers.ColumnId, Expression], - allow_partial_bindings: bool = False, - ) -> Module: - return self - - @property - def is_bijective(self) -> bool: - # () <-> value - return True - - def transform_children(self, t: Callable[[Expression], Expression]) -> Expression: - return self - - -@dataclasses.dataclass(frozen=True) -class PyObject(Expression): - """An expression representing a module reference.""" - - value: Hashable - - @property - def is_const(self) -> bool: - return True - - @property - def column_references(self) -> Tuple[identifiers.ColumnId, ...]: - return () - - @property - def nullable(self) -> bool: - return True # type: ignore - - @property - def is_resolved(self) -> bool: - return False - - @property - def output_type(self) -> dtypes.ExpressionType: - raise ValueError("PyObject expression does not have a type.") - - def bind_variables( - self, - bindings: Mapping[Hashable, Expression], - allow_partial_bindings: bool = False, - ) -> Expression: - return self - - def bind_refs( - self, - bindings: Mapping[identifiers.ColumnId, Expression], - allow_partial_bindings: bool = False, - ) -> PyObject: - return self - - @property - def is_bijective(self) -> bool: - # () <-> value - return True - - def transform_children(self, t: Callable[[Expression], Expression]) -> Expression: - return self - - -@dataclasses.dataclass(frozen=True) -class Call(Expression): - """An expression representing a scalar constant.""" - - # TODO: Further constrain? - callable: Expression - inputs: Tuple[Expression, ...] - - @property - def column_references( - self, - ) -> Tuple[identifiers.ColumnId, ...]: - return tuple( - itertools.chain.from_iterable( - map(lambda x: x.column_references, self.children) - ) - ) - - @property - def free_variables(self) -> tuple[Hashable, ...]: - return tuple( - itertools.chain.from_iterable( - map(lambda x: x.free_variables, self.children) - ) - ) - - @property - def is_const(self) -> bool: - return False - - @property - def children(self): - return (self.callable, *self.inputs) - - @property - def nullable(self) -> bool: - return True - - @property - def is_resolved(self) -> bool: - return False - - @property - def output_type(self) -> dtypes.ExpressionType: - raise ValueError(f"Type of expression {self} has not been fixed.") - - @property - def is_bijective(self) -> bool: - # TODO: Mark individual functions as bijective? - return False - - @property - def deterministic(self) -> bool: - return True - - def transform_children(self, t: Callable[[Expression], Expression]) -> Expression: - return dataclasses.replace( - self, - callable=t(self.callable), - inputs=tuple(t(input) for input in self.inputs), - ) - - def bind_variables( - self, - bindings: Mapping[Hashable, Expression], - allow_partial_bindings: bool = False, - ) -> Call: - return Call( - callable=self.callable.bind_variables( - bindings, allow_partial_bindings=allow_partial_bindings - ), - inputs=tuple( - input.bind_variables( - bindings, allow_partial_bindings=allow_partial_bindings - ) - for input in self.inputs - ), - ) - - def bind_refs( - self, - bindings: Mapping[identifiers.ColumnId, Expression], - allow_partial_bindings: bool = False, - ) -> Call: - return Call( - callable=self.callable.bind_refs( - bindings, allow_partial_bindings=allow_partial_bindings - ), - inputs=tuple( - input.bind_refs(bindings, allow_partial_bindings=allow_partial_bindings) - for input in self.inputs - ), - ) - - -# TODO: Mode that resolves free variable attrs as columns -def resolve_py_exprs( - expression: Expression, - series_arg: Optional[str] = None, - series_attrs: Mapping[Hashable, str] | None = None, - col_series_args: Mapping[str, str] | None = None, - window_spec: window_specs.WindowSpec | None = None, -) -> Expression: - """ - Replace all PyObject, attribute, item, and call expressions bottom-up. - - This function translates unresolved python expressions (like GetAttr, GetItem, - Call, PyObject) into resolved BigQuery expressions (like OpExpression, DerefOp, - Aggregation, ScalarConstantExpression) by binding them to the specified context. - - Args: - expression: The unresolved python expression to translate. - series_arg: The name of the parameter representing the row (for row-wise UDFs like - apply axis=1) or the DataFrame group (for DataFrameGroupBy.apply). - series_attrs: A mapping of attribute/item names to column IDs for the series_arg. - When GetAttr(series_arg, attr) or GetItem(series_arg, key) is encountered, - it is resolved to deref(column_id). - col_series_args: A mapping of parameter names to column IDs for parameters that - represent a single Series/column directly (for SeriesGroupBy.apply). When - UnboundVariableExpression(arg_name) is encountered and arg_name is in - col_series_args, it is resolved directly to deref(column_id). - window_spec: Optional window spec. When provided, aggregations inside calls will - be converted to WindowExpression using this spec. - - Returns: - The resolved BigQuery Expression. - """ - - def resolve_expr_if_call(expr: Expression) -> Expression: - if isinstance(expr, Call): - return resolve_call(expr, window_spec=window_spec) - return expr - - def resolve_attrs(expr: Expression) -> Expression: - if isinstance(expr, GetAttr): - return _resolve_getattr(expr, series_arg, series_attrs) - if isinstance(expr, GetItem): - return _resolve_getitem(expr, series_arg, series_attrs, col_series_args) - return expr - - def resolve_series_var(expr: Expression) -> Expression: - if ( - col_series_args is not None - and isinstance(expr, UnboundVariableExpression) - and isinstance(expr.id, str) - and expr.id in col_series_args - ): - return deref(col_series_args[expr.id]) - return expr - - def resolve_pyobjs(expr: Expression) -> Expression: - if isinstance(expr, PyObject): - return const(expr.value) - return expr - - wo_calls = expression.bottom_up(resolve_expr_if_call) - wo_attrs = wo_calls.bottom_up(resolve_attrs) - wo_vars = wo_attrs.bottom_up(resolve_series_var) - return wo_vars.bottom_up(resolve_pyobjs) - - -def _resolve_getattr( - expression: GetAttr, - series_arg: Optional[str], - series_attrs: Mapping[Hashable, str] | None, -) -> Expression: - if isinstance(expression.input, Module): - # resolves things like Math.pi - return PyObject(getattr(expression.input.module, expression.attr)) - # Resolve attribute access on the series/row argument - if ( - series_arg is not None - and series_attrs is not None - and isinstance(expression.input, UnboundVariableExpression) - and expression.input.id == series_arg - and expression.attr in series_attrs - ): - return deref(series_attrs[expression.attr]) - return expression - - -def _resolve_getitem( - expression: GetItem, - series_arg: Optional[str], - series_attrs: Mapping[Hashable, str] | None, - col_series_args: Mapping[str, str] | None, -) -> Expression: - # Resolve subscript/item access on the series/row argument - key_val = None - if isinstance(expression.key, PyObject): - key_val = expression.key.value - elif isinstance(expression.key, ScalarConstantExpression): - key_val = expression.key.value - - is_series_var = ( - series_arg is not None - and isinstance(expression.input, UnboundVariableExpression) - and expression.input.id == series_arg - ) - - if is_series_var and series_attrs is not None: - if key_val is None: - raise NotImplementedError("Dynamic column lookup is not supported.") - if key_val in series_attrs: - return deref(series_attrs[key_val]) - else: - raise KeyError(f"Column '{key_val}' not found.") - - is_columnar_var = ( - col_series_args is not None - and isinstance(expression.input, UnboundVariableExpression) - and expression.input.id in col_series_args - ) - - if is_columnar_var: - raise NotImplementedError( - "Subscripting a Series/column is not supported in this UDF context." - ) - - if key_val is not None: - if isinstance(key_val, (str, int)): - return OpExpression(generic_ops.GetItemOp(key_val), (expression.input,)) - else: - raise NotImplementedError( - f"Subscript key of type '{type(key_val).__name__}' is not supported." - ) - else: - return OpExpression( - generic_ops.DynamicGetItemOp(), (expression.input, expression.key) - ) - - -def resolve_call( - call: Call, window_spec: window_specs.WindowSpec | None = None -) -> Expression: - callable = call.callable - if isinstance(callable, GetAttr): - attr = callable.attr - if isinstance(callable.input, Module): - fn = getattr(callable.input.module, attr) - if fn in python_op_maps.PYTHON_TO_BIGFRAMES: - op = python_op_maps.PYTHON_TO_BIGFRAMES[fn] - return OpExpression(op, call.inputs) - if fn in _CALLABLE_TO_OP: - op = _CALLABLE_TO_OP[fn] - return OpExpression(op, call.inputs) - elif isinstance(callable.input, PyObject) and isinstance( - callable.input.value, type - ): - fn = getattr(callable.input.value, attr, None) - if fn in python_op_maps.PYTHON_TO_BIGFRAMES: - op = python_op_maps.PYTHON_TO_BIGFRAMES[fn] - return OpExpression(op, call.inputs) - else: - # Method call on an expression (e.g. df.col.sum() or s.mean()) - try: - agg_op, _ = agg_ops.lookup_agg_func(attr) - - if isinstance(agg_op, agg_ops.UnaryAggregateOp): - agg_expr: agg_exprs.Aggregation = agg_exprs.UnaryAggregation( - agg_op, callable.input - ) - if window_spec is not None: - return agg_exprs.WindowExpression(agg_expr, window_spec) - return agg_expr - elif isinstance(agg_op, agg_ops.NullaryAggregateOp): - agg_expr = agg_exprs.NullaryAggregation(agg_op) - if window_spec is not None: - return agg_exprs.WindowExpression(agg_expr, window_spec) - return agg_expr - except ValueError: - pass - - # Support common scalar method calls on Series/expressions - if (method_op := python_op_maps.SERIES_METHOD_TO_OP.get(attr)) is not None: - if isinstance(method_op, ScalarOp): - return OpExpression(method_op, (callable.input,)) - - elif isinstance(callable, PyObject): - if callable.value == operator.getitem: - return GetItem(call.inputs[0], call.inputs[1]) - if isinstance(callable.value, ScalarOp): - return OpExpression(callable.value, call.inputs) - if callable.value in python_op_maps.PYTHON_TO_BIGFRAMES: - op = python_op_maps.PYTHON_TO_BIGFRAMES[callable.value] # type: ignore - return OpExpression(op, call.inputs) - if callable.value in _BUILTIN_CALLABLES: - return OpExpression(_BUILTIN_CALLABLES[callable.value], call.inputs) - - raise NotImplementedError( - f"No implementation available for call expression: {call}" - ) diff --git a/bigframes/core/pyarrow_utils.py b/bigframes/core/pyarrow_utils.py deleted file mode 100644 index bdbb220b953..00000000000 --- a/bigframes/core/pyarrow_utils.py +++ /dev/null @@ -1,119 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -from typing import Iterable, Iterator - -import pyarrow as pa - - -class BatchBuffer: - """ - FIFO buffer of pyarrow Record batches - - Not thread-safe. - """ - - def __init__(self): - self._buffer: list[pa.RecordBatch] = [] - self._buffer_size: int = 0 - - def __len__(self): - return self._buffer_size - - def append_batch(self, batch: pa.RecordBatch) -> None: - self._buffer.append(batch) - self._buffer_size += batch.num_rows - - def take_as_batches(self, n: int) -> tuple[pa.RecordBatch, ...]: - if n > len(self): - raise ValueError(f"Cannot take {n} rows, only {len(self)} rows in buffer.") - rows_taken = 0 - sub_batches: list[pa.RecordBatch] = [] - while rows_taken < n: - batch = self._buffer.pop(0) - if batch.num_rows > (n - rows_taken): - sub_batches.append(batch.slice(length=n - rows_taken)) - self._buffer.insert(0, batch.slice(offset=n - rows_taken)) - rows_taken += n - rows_taken - else: - sub_batches.append(batch) - rows_taken += batch.num_rows - - self._buffer_size -= n - return tuple(sub_batches) - - def take_rechunked(self, n: int) -> pa.RecordBatch: - return ( - pa.Table.from_batches(self.take_as_batches(n)) - .combine_chunks() - .to_batches()[0] - ) - - -def chunk_by_row_count( - batches: Iterable[pa.RecordBatch], page_size: int -) -> Iterator[tuple[pa.RecordBatch, ...]]: - buffer = BatchBuffer() - for batch in batches: - buffer.append_batch(batch) - while len(buffer) >= page_size: - yield buffer.take_as_batches(page_size) - - # emit final page, maybe smaller - if len(buffer) > 0: - yield buffer.take_as_batches(len(buffer)) - - -def cast_batch(batch: pa.RecordBatch, schema: pa.Schema) -> pa.RecordBatch: - if batch.schema == schema: - return batch - # TODO: Use RecordBatch.cast once min pyarrow>=16.0 - return pa.record_batch( - [arr.cast(type) for arr, type in zip(batch.columns, schema.types)], - schema=schema, - ) - - -def rename_batch(batch: pa.RecordBatch, names: list[str]) -> pa.RecordBatch: - if batch.schema.names == names: - return batch - # TODO: Use RecordBatch.rename_columns once min pyarrow>=16.0 - return pa.RecordBatch.from_arrays(batch.columns, names) - - -def truncate_pyarrow_iterable( - batches: Iterable[pa.RecordBatch], max_results: int -) -> Iterator[pa.RecordBatch]: - total_yielded = 0 - for batch in batches: - if batch.num_rows >= (max_results - total_yielded): - yield batch.slice(length=max_results - total_yielded) - return - else: - yield batch - total_yielded += batch.num_rows - - -def append_offsets( - pa_table: pa.Table, - offsets_col: str, -) -> pa.Table: - return pa_table.append_column( - offsets_col, pa.array(range(pa_table.num_rows), type=pa.int64()) - ) - - -def as_nullable(pa_table: pa.Table): - """Normalizes schema to nullable for value-wise comparisons.""" - nullable_schema = pa.schema(field.with_nullable(True) for field in pa_table.schema) - return pa_table.cast(nullable_schema) diff --git a/bigframes/core/pyformat.py b/bigframes/core/pyformat.py deleted file mode 100644 index dfd91ba1ad0..00000000000 --- a/bigframes/core/pyformat.py +++ /dev/null @@ -1,376 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -"""Helpers for the pyformat feature.""" - -# TODO(tswast): consolidate with pandas-gbq and bigquery-magics. See: -# https://github.com/googleapis/python-bigquery-magics/blob/main/bigquery_magics/pyformat.py - -from __future__ import annotations - -import string -import typing -from typing import Any, Optional, Tuple, Union - -import google.cloud.bigquery -import pandas - -import bigframes.core.local_data -import bigframes.session -from bigframes.core import utils -from bigframes.core.tools import bigquery_schema - -_BQ_TABLE_TYPES = Union[ - google.cloud.bigquery.Table, - google.cloud.bigquery.TableReference, - google.cloud.bigquery.table.TableListItem, -] - - -def _table_to_sql(table: _BQ_TABLE_TYPES) -> str: - # BiglakeIcebergTable IDs have 4 parts. BigFrames packs catalog.namespace - # into the dataset_id. - dataset_parts = table.dataset_id.split(".") - dataset_sql = ".".join(f"`{part}`" for part in dataset_parts) - return f"`{table.project}`.{dataset_sql}.`{table.table_id}`" - - -def _pandas_df_to_sql_dry_run(pd_df: pandas.DataFrame) -> str: - # Ensure there are no duplicate column labels. - # - # Please make sure this stays in sync with the logic used to_gbq(). See - # bigframes.dataframe.DataFrame._prepare_export(). - new_col_labels, new_idx_labels = utils.get_standardized_ids( - pd_df.columns, pd_df.index.names - ) - pd_copy = pd_df.copy() - pd_copy.columns = pandas.Index(new_col_labels) - pd_copy.index.names = new_idx_labels - - managed_table = bigframes.core.local_data.ManagedArrowTable.from_pandas(pd_copy) - bqschema = managed_table.schema.to_bigquery() - return bigquery_schema.to_sql_dry_run(bqschema) - - -def _pandas_df_to_sql( - df_pd: pandas.DataFrame, - *, - name: str, - session: Optional[bigframes.session.Session] = None, - dry_run: bool = False, -) -> str: - if session is None: - if not dry_run: - message = ( - f"Can't embed pandas DataFrame {name} in a SQL " - "string without a bigframes session except if for a dry run." - ) - raise ValueError(message) - - return _pandas_df_to_sql_dry_run(df_pd) - - # Use the _deferred engine to avoid loading data too often during dry run. - df = session.read_pandas(df_pd, write_engine="_deferred") - return _table_to_sql(df._to_placeholder_table(dry_run=dry_run)) - - -def _field_to_template_value( - name: str, - value: Any, - *, - session: Optional[bigframes.session.Session] = None, - dry_run: bool = False, -) -> str: - """Convert value to something embeddable in a SQL string.""" - import bigframes.core.compile.sqlglot.sql as sql # Avoid circular imports - import bigframes.dataframe # Avoid circular imports - - _validate_type(name, value) - - table_types = typing.get_args(_BQ_TABLE_TYPES) - if isinstance(value, table_types): - return _table_to_sql(value) - - if isinstance(value, pandas.DataFrame): - return _pandas_df_to_sql(value, session=session, dry_run=dry_run, name=name) - - if isinstance(value, bigframes.dataframe.DataFrame): - import bigframes.core.bq_data as bq_data - import bigframes.core.nodes as nodes - - # TODO(b/493608478): Remove this workaround for BigLake/Iceberg tables, - # which cannot currently be used in views, once a fix rolls out. - def is_biglake( - node: nodes.BigFrameNode, child_results: Tuple[bool, ...] - ) -> bool: - if isinstance(node, nodes.ReadTableNode): - return isinstance(node.source.table, bq_data.BiglakeIcebergTable) - return any(child_results) - - contains_biglake = value._block.expr.node.reduce_up(is_biglake) - - if contains_biglake: - sql_query, _, _ = value._to_sql_query(include_index=True) - return f"({sql_query})" - - return _table_to_sql(value._to_placeholder_table(dry_run=dry_run)) - - if isinstance(value, str): - return value - - return sql.to_sql(sql.literal(value)) - - -def _validate_type(name: str, value: Any): - """Raises TypeError if value is unsupported.""" - import bigframes.dataframe # Avoid circular imports - import bigframes.dtypes # Avoid circular imports - - if value is None: - return # None can't be used in isinstance, but is a valid literal. - - supported_types = ( - typing.get_args(_BQ_TABLE_TYPES) - + bigframes.dtypes.SUPPORTED_LITERAL_TYPES - + (bigframes.dataframe.DataFrame,) - + (pandas.DataFrame,) - ) - - if not isinstance(value, supported_types): - raise TypeError( - f"{name} has unsupported type: {type(value)}. " - f"Only {supported_types} are supported." - ) - - -def _parse_fields(sql_template: str) -> list[str]: - return [ - field_name - for _, field_name, _, _ in string.Formatter().parse(sql_template) - if field_name is not None - ] - - -def _is_escaped_open_brace(sql_template: str, idx: int, literal_char: str) -> bool: - """Checks if the character at idx in sql_template is an escaped open brace '{{'.""" - return sql_template[idx : idx + 2] == "{{" and literal_char == "{" - - -def _is_escaped_close_brace(sql_template: str, idx: int, literal_char: str) -> bool: - """Checks if the character at idx in sql_template is an escaped close brace '}}'.""" - return sql_template[idx : idx + 2] == "}}" and literal_char == "}" - - -def _consume_literal(sql_template: str, current_idx: int, literal_text: str) -> int: - """Advances current_idx past literal_text in sql_template, accounting for escaped braces. - - A **literal** (or literal text) is the static part of the template string that - does not contain formatting placeholders. The string.Formatter parser resolves - escaped braces ('{{' and '}}') into single braces ('{' and '}') in its output - literal_text. - - This function aligns the resolved literal_text back to the original - sql_template by consuming 2 characters from sql_template ('{{' or '}}') for - every single escaped brace character in literal_text, and 1 character for - everything else. - - Returns: - int: the advanced current_idx in sql_template. - """ - lit_idx = 0 - while lit_idx < len(literal_text): - if _is_escaped_open_brace(sql_template, current_idx, literal_text[lit_idx]): - current_idx += 2 - lit_idx += 1 - elif _is_escaped_close_brace(sql_template, current_idx, literal_text[lit_idx]): - current_idx += 2 - lit_idx += 1 - elif ( - current_idx < len(sql_template) - and sql_template[current_idx] == literal_text[lit_idx] - ): - current_idx += 1 - lit_idx += 1 - else: - raise RuntimeError( - "Internal error: failed to align parsed SQL template with original query. " - f"Expected {literal_text[lit_idx]!r} at position {current_idx} in template, " - f"but found {sql_template[current_idx : current_idx + 2]!r}." - ) - return current_idx - - -def _is_escaped_brace(sql_template: str, idx: int) -> bool: - """Checks if the template has an escaped brace ('{{' or '}}') at the given index.""" - return sql_template[idx : idx + 2] in ("{{", "}}") - - -def _advance_past_field(sql_template: str, current_idx: int) -> int: - """Advances current_idx past the format field starting at current_idx. - - A **field** (or replacement field) is a placeholder in the template enclosed - in braces (e.g., "{my_var}" or "{json_col: { "val": 1 } }"). - - This function assumes current_idx points to the opening '{' of a field. - It parses forward, tracking nested braces to find the matching closing '}' - that terminates the field, while ignoring escaped braces ('{{' and '}}') - which do not affect the nesting level. - - Returns: - int: the index immediately after the closing '}' of the field. - """ - assert sql_template[current_idx] == "{" - brace_count = 1 - current_idx += 1 # past '{' - - while brace_count > 0 and current_idx < len(sql_template): - if _is_escaped_brace(sql_template, current_idx): - current_idx += 2 - elif sql_template[current_idx] == "{": - brace_count += 1 - current_idx += 1 - elif sql_template[current_idx] == "}": - brace_count -= 1 - current_idx += 1 - else: - current_idx += 1 - - return current_idx - - -def _find_all_field_positions(sql_template: str) -> dict[tuple[str, int], int]: - """Finds the character positions of all fields in the sql_template. - - Returns: - dict: a dict mapping (field_name, occurrence_idx) to character index. - """ - formatter = string.Formatter() - current_idx = 0 - seen_counts: dict[str, int] = {} - positions: dict[tuple[str, int], int] = {} - - for literal_text, field_name, _, _ in formatter.parse(sql_template): - current_idx = _consume_literal(sql_template, current_idx, literal_text) - - if field_name is not None: - occurrence_idx = seen_counts.get(field_name, 0) - seen_counts[field_name] = occurrence_idx + 1 - - positions[(field_name, occurrence_idx)] = current_idx - - current_idx = _advance_past_field(sql_template, current_idx) - - return positions - - -def get_error_context_at_pos(sql_template: str, pos: int) -> str: - """Create a helpful 'pointer' to where the problematic position is - in the original SQL. - - This should make the error message a lot friendlier, by providing more - context towards the problematic syntax. - """ - if pos == -1: - return "" - - lines = sql_template.splitlines(keepends=True) - - char_count = 0 - target_line_idx = -1 - for i, line in enumerate(lines): - if char_count <= pos < char_count + len(line): - target_line_idx = i - break - char_count += len(line) - - if target_line_idx == -1: - return "" - - col_offset = pos - char_count - - context_lines = [] - start_line = max(0, target_line_idx - 2) - end_line = min(len(lines), target_line_idx + 3) - - for i in range(start_line, end_line): - line_num = i + 1 - line_content = lines[i].rstrip("\r\n") - if i == target_line_idx: - context_lines.append(f"{line_num:4d}: {line_content}") - indent = 6 + col_offset - context_lines.append(" " * indent + "^") - else: - context_lines.append(f"{line_num:4d}: {line_content}") - - return "\n".join(context_lines) - - -def pyformat( - sql_template: str, - *, - pyformat_args: dict, - session: Optional[bigframes.session.Session] = None, - dry_run: bool = False, -) -> str: - """Unsafe Python-style string formatting of SQL string. - - Only some data types supported. - - Warning: strings are **not** escaped. This allows them to be used in - contexts such as table identifiers, where normal query parameters are not - supported. - - Args: - sql_template (str): - SQL string with 0+ {var_name}-style format options. - pyformat_args (dict): - Variable namespace to use for formatting. - - Raises: - TypeError: if a referenced variable is not of a supported type. - ValueError: - if a referenced variable is not found (KeyError is caught and raised - as ValueError with context). - """ - try: - fields = _parse_fields(sql_template) - except ValueError as e: - raise ValueError( - "Failed to parse SQL template. " - "Did you mean to escape '{' and '}' by doubling them?\n" - f"Error details: {e}" - ) from e - - format_kwargs: dict[str, str] = {} - seen_counts: dict[str, int] = {} - for name in fields: - seen_counts[name] = seen_counts.get(name, 0) + 1 - try: - value = pyformat_args[name] - except KeyError as e: - positions = _find_all_field_positions(sql_template) - occurrence_idx = seen_counts[name] - 1 - pos = positions.get((name, occurrence_idx), -1) - context = get_error_context_at_pos(sql_template, pos) - raise ValueError( - f"Undetected variable {name!r} in SQL template. " - "Did you mean to escape '{' and '}' by doubling them?\n" - f"{context}" - ) from e - - format_kwargs[name] = _field_to_template_value( - name, value, session=session, dry_run=dry_run - ) - - return sql_template.format(**format_kwargs) diff --git a/bigframes/core/reshape/__init__.py b/bigframes/core/reshape/__init__.py index 1dc90d18483..dc61c3baada 100644 --- a/bigframes/core/reshape/__init__.py +++ b/bigframes/core/reshape/__init__.py @@ -11,3 +11,143 @@ # 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 typing import Iterable, Literal, Optional, Union + +import bigframes.constants as constants +import bigframes.core as core +import bigframes.core.utils as utils +import bigframes.dataframe +import bigframes.operations as ops +import bigframes.operations.aggregations as agg_ops +import bigframes.series + + +@typing.overload +def concat( + objs: Iterable[bigframes.series.Series], + *, + axis: typing.Literal["index", 0] = ..., + join=..., + ignore_index=..., +) -> bigframes.series.Series: + ... + + +@typing.overload +def concat( + objs: Iterable[bigframes.dataframe.DataFrame], + *, + axis: typing.Literal["index", 0] = ..., + join=..., + ignore_index=..., +) -> bigframes.dataframe.DataFrame: + ... + + +@typing.overload +def concat( + objs: Iterable[Union[bigframes.dataframe.DataFrame, bigframes.series.Series]], + *, + axis: typing.Literal["columns", 1], + join=..., + ignore_index=..., +) -> bigframes.dataframe.DataFrame: + ... + + +@typing.overload +def concat( + objs: Iterable[Union[bigframes.dataframe.DataFrame, bigframes.series.Series]], + *, + axis=..., + join=..., + ignore_index=..., +) -> Union[bigframes.dataframe.DataFrame, bigframes.series.Series]: + ... + + +def concat( + objs: Iterable[Union[bigframes.dataframe.DataFrame, bigframes.series.Series]], + *, + axis: typing.Union[str, int] = 0, + join: Literal["inner", "outer"] = "outer", + ignore_index: bool = False, +) -> Union[bigframes.dataframe.DataFrame, bigframes.series.Series]: + axis_n = utils.get_axis_number(axis) + if axis_n == 0: + contains_dataframes = any( + isinstance(x, bigframes.dataframe.DataFrame) for x in objs + ) + if not contains_dataframes: + # Special case, all series, so align everything into single column even if labels don't match + series = typing.cast(typing.Iterable[bigframes.series.Series], objs) + names = {s.name for s in series} + # For series case, labels are stripped if they don't all match + if len(names) > 1: + blocks = [s._block.with_column_labels([None]) for s in series] + else: + blocks = [s._block for s in series] + block = blocks[0].concat(blocks[1:], how=join, ignore_index=ignore_index) + return bigframes.series.Series(block) + blocks = [obj._block for obj in objs] + block = blocks[0].concat(blocks[1:], how=join, ignore_index=ignore_index) + return bigframes.dataframe.DataFrame(block) + else: + # Note: does not validate inputs + block_list = [obj._block for obj in objs] + block = block_list[0] + for rblock in block_list[1:]: + combined_index, _ = block.index.join(rblock.index, how=join) + block = combined_index._block + return bigframes.dataframe.DataFrame(block) + + +def cut( + x: bigframes.series.Series, + bins: int, + *, + labels: Optional[bool] = None, +) -> bigframes.series.Series: + if bins <= 0: + raise ValueError("`bins` should be a positive integer.") + + if labels is not False: + raise NotImplementedError( + f"Only labels=False is supported in BigQuery DataFrames so far. {constants.FEEDBACK_LINK}" + ) + return x._apply_window_op(agg_ops.CutOp(bins), window_spec=core.WindowSpec()) + + +def qcut( + x: bigframes.series.Series, + q: typing.Union[int, typing.Sequence[float]], + *, + labels: Optional[bool] = None, + duplicates: typing.Literal["drop", "error"] = "error", +) -> bigframes.series.Series: + if isinstance(q, int) and q <= 0: + raise ValueError("`q` should be a positive integer.") + + if labels is not False: + raise NotImplementedError( + f"Only labels=False is supported in BigQuery DataFrames so far. {constants.FEEDBACK_LINK}" + ) + if duplicates != "drop": + raise NotImplementedError( + f"Only duplicates='drop' is supported in BigQuery DataFrames so far. {constants.FEEDBACK_LINK}" + ) + block = x._block + label = block.col_id_to_label[x._value_column] + block, nullity_id = block.apply_unary_op(x._value_column, ops.notnull_op) + block, result = block.apply_window_op( + x._value_column, + agg_ops.QcutOp(q), + window_spec=core.WindowSpec(grouping_keys=(nullity_id,)), + ) + block, result = block.apply_binary_op( + result, nullity_id, ops.partial_arg3(ops.where_op, None), result_label=label + ) + return bigframes.series.Series(block.select_column(result)) diff --git a/bigframes/core/reshape/api.py b/bigframes/core/reshape/api.py deleted file mode 100644 index adb33427f94..00000000000 --- a/bigframes/core/reshape/api.py +++ /dev/null @@ -1,21 +0,0 @@ -# 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. -# 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 bigframes.core.reshape.concat import concat -from bigframes.core.reshape.encoding import get_dummies -from bigframes.core.reshape.merge import merge -from bigframes.core.reshape.pivot import crosstab -from bigframes.core.reshape.tile import cut, qcut - -__all__ = ["concat", "get_dummies", "merge", "cut", "qcut", "crosstab"] diff --git a/bigframes/core/reshape/concat.py b/bigframes/core/reshape/concat.py deleted file mode 100644 index cc81319ae68..00000000000 --- a/bigframes/core/reshape/concat.py +++ /dev/null @@ -1,102 +0,0 @@ -# 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. -# 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 typing import Iterable, Literal, Union - -import bigframes_vendored.pandas.core.reshape.concat as vendored_pandas_concat - -import bigframes.core.utils as utils -import bigframes.dataframe -import bigframes.series - - -@typing.overload -def concat( - objs: Iterable[bigframes.series.Series], - *, - axis: typing.Literal["index", 0] = ..., - join=..., - ignore_index=..., -) -> bigframes.series.Series: ... - - -@typing.overload -def concat( - objs: Iterable[bigframes.dataframe.DataFrame], - *, - axis: typing.Literal["index", 0] = ..., - join=..., - ignore_index=..., -) -> bigframes.dataframe.DataFrame: ... - - -@typing.overload -def concat( - objs: Iterable[Union[bigframes.dataframe.DataFrame, bigframes.series.Series]], - *, - axis: typing.Literal["columns", 1], - join=..., - ignore_index=..., -) -> bigframes.dataframe.DataFrame: ... - - -@typing.overload -def concat( - objs: Iterable[Union[bigframes.dataframe.DataFrame, bigframes.series.Series]], - *, - axis=..., - join=..., - ignore_index=..., -) -> Union[bigframes.dataframe.DataFrame, bigframes.series.Series]: ... - - -def concat( - objs: Iterable[Union[bigframes.dataframe.DataFrame, bigframes.series.Series]], - *, - axis: typing.Union[str, int] = 0, - join: Literal["inner", "outer"] = "outer", - ignore_index: bool = False, -) -> Union[bigframes.dataframe.DataFrame, bigframes.series.Series]: - axis_n = utils.get_axis_number(axis) - if axis_n == 0: - contains_dataframes = any( - isinstance(x, bigframes.dataframe.DataFrame) for x in objs - ) - if not contains_dataframes: - # Special case, all series, so align everything into single column even if labels don't match - series = typing.cast(typing.Iterable[bigframes.series.Series], objs) - names = {s.name for s in series} - # For series case, labels are stripped if they don't all match - if len(names) > 1: - blocks = [s._block.with_column_labels([None]) for s in series] - else: - blocks = [s._block for s in series] - block = blocks[0].concat(blocks[1:], how=join, ignore_index=ignore_index) - return bigframes.series.Series(block) - blocks = [obj._block for obj in objs] - block = blocks[0].concat(blocks[1:], how=join, ignore_index=ignore_index) - return bigframes.dataframe.DataFrame(block) - else: - # Note: does not validate inputs - block_list = [obj._block for obj in objs] - block = block_list[0] - for rblock in block_list[1:]: - block, _ = block.join(rblock, how=join) - return bigframes.dataframe.DataFrame(block) - - -concat.__doc__ = vendored_pandas_concat.concat.__doc__ diff --git a/bigframes/core/reshape/encoding.py b/bigframes/core/reshape/encoding.py deleted file mode 100644 index 842e4ece264..00000000000 --- a/bigframes/core/reshape/encoding.py +++ /dev/null @@ -1,194 +0,0 @@ -# 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. -# 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 typing -from typing import Any, List, Optional, Tuple, Union - -import bigframes_vendored.constants as constants -import bigframes_vendored.pandas.core.reshape.encoding as vendored_pandas_encoding -import pandas - -from bigframes import operations -from bigframes.core import blocks, expression -from bigframes.dataframe import DataFrame -from bigframes.series import Series - - -def get_dummies( - data: Union[DataFrame, Series], - prefix: Union[List, dict, str, None] = None, - prefix_sep: Union[List, dict, str, None] = "_", - dummy_na: bool = False, - columns: Optional[List] = None, - drop_first: bool = False, - dtype: Any = None, -) -> DataFrame: - # simplify input parameters into per-input-label lists - # also raise errors for invalid parameters - column_labels, prefixes, prefix_seps = _standardize_get_dummies_params( - data, prefix, prefix_sep, columns, dtype - ) - - # combine prefixes into per-column-id list - full_columns_prefixes, columns_ids = _determine_get_dummies_columns_from_labels( - data, column_labels, prefix is not None, prefixes, prefix_seps - ) - - # run queries to compute unique values - block = data._block - max_unique_value = ( - blocks._BQ_MAX_COLUMNS - len(block.value_columns) - len(block.index_columns) - 1 - ) // len(column_labels) - columns_values = [ - block._get_unique_values([col_id], max_unique_value) for col_id in columns_ids - ] - - # for each dummified column, add the content of the output columns via block operations - intermediate_col_ids = [] - for i in range(len(columns_values)): - level = columns_values[i].get_level_values(0).sort_values().dropna() - if drop_first: - level = level[1:] - column_label = full_columns_prefixes[i] - column_id = columns_ids[i] - block, new_intermediate_col_ids = _perform_get_dummies_block_operations( - block, level, column_label, column_id, dummy_na - ) - intermediate_col_ids.extend(new_intermediate_col_ids) - - # drop dummified columns (and the intermediate columns we added) - block = block.drop_columns(columns_ids + intermediate_col_ids) - return DataFrame(block) - - -get_dummies.__doc__ = vendored_pandas_encoding.get_dummies.__doc__ - - -def _standardize_get_dummies_params( - data: Union[DataFrame, Series], - prefix: Union[List, dict, str, None], - prefix_sep: Union[List, dict, str, None], - columns: Optional[List], - dtype: Any, -) -> Tuple[List, List[str], List[str]]: - block = data._block - - if isinstance(data, Series): - columns = [block.column_labels[0]] - if columns is not None and not pandas.api.types.is_list_like(columns): - raise TypeError("Input must be a list-like for parameter `columns`") - if dtype is not None and dtype not in [ - pandas.BooleanDtype, - bool, - "Boolean", - "boolean", - "bool", - ]: - raise NotImplementedError( - f"Only Boolean dtype is currently supported. {constants.FEEDBACK_LINK}" - ) - - if columns is None: - default_dummy_types = [pandas.StringDtype, "string[pyarrow]"] - columns = [] - columns_set = set() - for col_id in block.value_columns: - label = block.col_id_to_label[col_id] - if ( - label not in columns_set - and block.expr.get_column_type(col_id) in default_dummy_types - ): - columns.append(label) - columns_set.add(label) - - column_labels: List = typing.cast(List, columns) - - def parse_prefix_kwarg(kwarg, kwarg_name) -> Optional[List[str]]: - if kwarg is None: - return None - if isinstance(kwarg, str): - return [kwarg] * len(column_labels) - if isinstance(kwarg, dict): - return [kwarg[column] for column in column_labels] - kwarg = typing.cast(List, kwarg) - if pandas.api.types.is_list_like(kwarg) and len(kwarg) != len(column_labels): - raise ValueError( - f"Length of '{kwarg_name}' ({len(kwarg)}) did not match " - f"the length of the columns being encoded ({len(column_labels)})." - ) - if pandas.api.types.is_list_like(kwarg): - return list(map(str, kwarg)) - raise TypeError(f"{kwarg_name} kwarg must be a string, list, or dictionary") - - prefix_seps = parse_prefix_kwarg(prefix_sep or "_", "prefix_sep") - prefix_seps = typing.cast(List, prefix_seps) - prefixes = parse_prefix_kwarg(prefix, "prefix") - if prefixes is None: - prefixes = column_labels - prefixes = typing.cast(List, prefixes) - - return column_labels, prefixes, prefix_seps - - -def _determine_get_dummies_columns_from_labels( - data: Union[DataFrame, Series], - column_labels: List, - prefix_given: bool, - prefixes: List[str], - prefix_seps: List[str], -) -> Tuple[List[str], List[str]]: - block = data._block - - columns_ids = [] - columns_prefixes = [] - for i in range(len(column_labels)): - label = column_labels[i] - empty_prefix = label is None or (isinstance(data, Series) and not prefix_given) - full_prefix = "" if empty_prefix else prefixes[i] + prefix_seps[i] - - for col_id in block.label_to_col_id[label]: - columns_ids.append(col_id) - columns_prefixes.append(full_prefix) - - return columns_prefixes, columns_ids - - -def _perform_get_dummies_block_operations( - block: blocks.Block, - level: pandas.Index, - column_label: str, - column_id: str, - dummy_na: bool, -) -> Tuple[blocks.Block, List[str]]: - intermediate_col_ids = [] - for value in level: - new_column_label = f"{column_label}{value}" - if column_label == "": - new_column_label = value - new_block, new_id = block.project_expr( - operations.eq_op.as_expr(column_id, expression.const(value)) - ) - intermediate_col_ids.append(new_id) - block, _ = new_block.project_expr( - operations.fillna_op.as_expr(new_id, expression.const(False)), - label=new_column_label, - ) - if dummy_na: - # dummy column name for na depends on the dtype - na_string = str(pandas.Index([None], dtype=level.dtype)[0]) - new_column_label = f"{column_label}{na_string}" - block, _ = block.apply_unary_op( - column_id, operations.isnull_op, result_label=new_column_label - ) - return block, intermediate_col_ids diff --git a/bigframes/core/reshape/merge.py b/bigframes/core/reshape/merge.py deleted file mode 100644 index 55e3abe0c6e..00000000000 --- a/bigframes/core/reshape/merge.py +++ /dev/null @@ -1,218 +0,0 @@ -# Copyright 2023 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. - -""" -Functions for Merging Data Structures in BigFrames. -""" - -from __future__ import annotations - -from typing import Literal, Sequence - -import bigframes_vendored.pandas.core.reshape.merge as vendored_pandas_merge -from bigframes_vendored import constants - -from bigframes import dataframe, series -from bigframes.core import blocks, utils - - -def merge( - left: dataframe.DataFrame, - right: dataframe.DataFrame, - how: Literal[ - "inner", - "left", - "outer", - "right", - "cross", - ] = "inner", - on: blocks.Label | Sequence[blocks.Label] | None = None, - *, - left_on: blocks.Label | Sequence[blocks.Label] | None = None, - right_on: blocks.Label | Sequence[blocks.Label] | None = None, - left_index: bool = False, - right_index: bool = False, - sort: bool = False, - suffixes: tuple[str, str] = ("_x", "_y"), -) -> dataframe.DataFrame: - left = _validate_operand(left) - right = _validate_operand(right) - - if how == "cross": - if on is not None: - raise ValueError("'on' is not supported for cross join.") - result_block = left._block.merge( - right._block, - left_join_ids=[], - right_join_ids=[], - suffixes=suffixes, - how=how, - sort=True, - ) - return dataframe.DataFrame(result_block) - - left_join_ids, right_join_ids = _validate_left_right_on( - left, - right, - on, - left_on=left_on, - right_on=right_on, - left_index=left_index, - right_index=right_index, - ) - - block = left._block.merge( - right._block, - how, - left_join_ids, - right_join_ids, - sort=sort, - suffixes=suffixes, - left_index=left_index, - right_index=right_index, - ) - return dataframe.DataFrame(block) - - -merge.__doc__ = vendored_pandas_merge.merge.__doc__ - - -def _validate_operand( - obj: dataframe.DataFrame | series.Series, -) -> dataframe.DataFrame: - import bigframes.dataframe - import bigframes.series - - if isinstance(obj, bigframes.dataframe.DataFrame): - return obj - elif isinstance(obj, bigframes.series.Series): - if obj.name is None: - raise ValueError("Cannot merge a bigframes.series.Series without a name") - return obj.to_frame() - else: - raise TypeError( - f"Can only merge bigframes.series.Series or bigframes.dataframe.DataFrame objects, a {type(obj)} was passed" - ) - - -def _validate_left_right_on( - left: dataframe.DataFrame, - right: dataframe.DataFrame, - on: blocks.Label | Sequence[blocks.Label] | None = None, - *, - left_on: blocks.Label | Sequence[blocks.Label] | None = None, - right_on: blocks.Label | Sequence[blocks.Label] | None = None, - left_index: bool = False, - right_index: bool = False, -) -> tuple[list[str], list[str]]: - # Turn left_on and right_on to lists - if left_on is not None and not isinstance(left_on, (tuple, list)): - left_on = [left_on] - if right_on is not None and not isinstance(right_on, (tuple, list)): - right_on = [right_on] - - if left_index and left.index.nlevels > 1: - raise ValueError( - f"Joining with multi-level index is not supported. {constants.FEEDBACK_LINK}" - ) - if right_index and right.index.nlevels > 1: - raise ValueError( - f"Joining with multi-level index is not supported. {constants.FEEDBACK_LINK}" - ) - - # The following checks are copied from Pandas. - if on is None and left_on is None and right_on is None: - if left_index and right_index: - return list(left._block.index_columns), list(right._block.index_columns) - elif left_index: - raise ValueError("Must pass right_on or right_index=True") - elif right_index: - raise ValueError("Must pass left_on or left_index=True") - else: - # use the common columns - common_cols = left.columns.intersection(right.columns) - if len(common_cols) == 0: - raise ValueError( - "No common columns to perform merge on. " - f"Merge options: left_on={left_on}, " - f"right_on={right_on}, " - f"left_index={left_index}, " - f"right_index={right_index}" - ) - if ( - not left.columns.join(common_cols, how="inner").is_unique - or not right.columns.join(common_cols, how="inner").is_unique - ): - raise ValueError(f"Data columns not unique: {repr(common_cols)}") - return _to_col_ids(left, common_cols.to_list()), _to_col_ids( - right, common_cols.to_list() - ) - - elif on is not None: - if left_on is not None or right_on is not None: - raise ValueError( - 'Can only pass argument "on" OR "left_on" ' - 'and "right_on", not a combination of both.' - ) - if left_index or right_index: - raise ValueError( - 'Can only pass argument "on" OR "left_index" ' - 'and "right_index", not a combination of both.' - ) - return _to_col_ids(left, on), _to_col_ids(right, on) - - elif left_on is not None: - if left_index: - raise ValueError( - 'Can only pass argument "left_on" OR "left_index" not both.' - ) - if not right_index and right_on is None: - raise ValueError('Must pass "right_on" OR "right_index".') - if right_index: - if len(left_on) != right.index.nlevels: - raise ValueError( - "len(left_on) must equal the number " - 'of levels in the index of "right"' - ) - return _to_col_ids(left, left_on), list(right._block.index_columns) - - elif right_on is not None: - if right_index: - raise ValueError( - 'Can only pass argument "right_on" OR "right_index" not both.' - ) - if not left_index and left_on is None: - raise ValueError('Must pass "left_on" OR "left_index".') - if left_index: - if len(right_on) != left.index.nlevels: - raise ValueError( - "len(right_on) must equal the number " - 'of levels in the index of "left"' - ) - return list(left._block.index_columns), _to_col_ids(right, right_on) - - # The user correctly specified left_on and right_on - if len(right_on) != len(left_on): # type: ignore - raise ValueError("len(right_on) must equal len(left_on)") - - return _to_col_ids(left, left_on), _to_col_ids(right, right_on) - - -def _to_col_ids( - df: dataframe.DataFrame, join_cols: blocks.Label | Sequence[blocks.Label] -) -> list[str]: - if utils.is_list_like(join_cols): - return [df._block.resolve_label_exact_or_error(col) for col in join_cols] - - return [df._block.resolve_label_exact_or_error(join_cols)] diff --git a/bigframes/core/reshape/pivot.py b/bigframes/core/reshape/pivot.py deleted file mode 100644 index 082948728f6..00000000000 --- a/bigframes/core/reshape/pivot.py +++ /dev/null @@ -1,88 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -from __future__ import annotations - -from typing import TYPE_CHECKING, Optional - -import bigframes_vendored.pandas.core.reshape.pivot as vendored_pandas_pivot -import pandas as pd - -import bigframes -from bigframes.core import convert, utils -from bigframes.core.reshape import concat -from bigframes.dataframe import DataFrame - -if TYPE_CHECKING: - import bigframes.session - - -def crosstab( - index, - columns, - values=None, - rownames=None, - colnames=None, - aggfunc=None, - *, - session: Optional[bigframes.session.Session] = None, -) -> DataFrame: - if _is_list_of_lists(index): - index = [ - convert.to_bf_series(subindex, default_index=None, session=session) - for subindex in index - ] - else: - index = [convert.to_bf_series(index, default_index=None, session=session)] - if _is_list_of_lists(columns): - columns = [ - convert.to_bf_series(subcol, default_index=None, session=session) - for subcol in columns - ] - else: - columns = [convert.to_bf_series(columns, default_index=None, session=session)] - - df = concat.concat([*index, *columns], join="inner", axis=1) - # for uniqueness - tmp_index_names = [f"_crosstab_index_{i}" for i in range(len(index))] - tmp_col_names = [f"_crosstab_columns_{i}" for i in range(len(columns))] - df.columns = pd.Index([*tmp_index_names, *tmp_col_names]) - - values = ( - convert.to_bf_series(values, default_index=df.index, session=session) - if values is not None - else 0 - ) - - df["_crosstab_values"] = values - pivot_table = df.pivot_table( - values="_crosstab_values", - index=tmp_index_names, - columns=tmp_col_names, - aggfunc=aggfunc or "count", - sort=False, - fill_value=0 if (aggfunc is None) else None, - ) - # Undo temporary unique level labels - pivot_table.index.names = rownames or [i.name for i in index] - pivot_table.columns.names = colnames or [c.name for c in columns] - return pivot_table - - -def _is_list_of_lists(item) -> bool: - if not utils.is_list_like(item): - return False - return all(convert.can_convert_to_series(subitem) for subitem in item) - - -crosstab.__doc__ = vendored_pandas_pivot.crosstab.__doc__ diff --git a/bigframes/core/reshape/tile.py b/bigframes/core/reshape/tile.py deleted file mode 100644 index 61f869f2797..00000000000 --- a/bigframes/core/reshape/tile.py +++ /dev/null @@ -1,184 +0,0 @@ -# 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. -# 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 typing import TYPE_CHECKING, Optional - -import bigframes_vendored.constants as constants -import bigframes_vendored.pandas.core.reshape.tile as vendored_pandas_tile -import pandas as pd - -import bigframes -import bigframes.constants -import bigframes.core.expression as ex -import bigframes.core.ordering as order -import bigframes.core.utils as utils -import bigframes.core.window_spec as window_specs -import bigframes.dataframe -import bigframes.operations as ops -import bigframes.operations.aggregations as agg_ops -import bigframes.series - -if TYPE_CHECKING: - import bigframes.session - - -def cut( - x, - bins: typing.Union[ - int, - pd.IntervalIndex, - typing.Iterable, - ], - *, - right: typing.Optional[bool] = True, - labels: typing.Union[typing.Iterable[str], bool, None] = None, - session: Optional[bigframes.session.Session] = None, -) -> bigframes.series.Series: - if ( - labels is not None - and labels is not False - and not isinstance(labels, typing.Iterable) - ): - raise ValueError( - "Bin labels must either be False, None or passed in as a list-like argument" - ) - if ( - isinstance(labels, typing.Iterable) - and len(list(labels)) > 0 - and not isinstance(list(labels)[0], str) - ): - raise NotImplementedError( - "When using an iterable for labels, only iterables of strings are supported " - f"but found {type(list(labels)[0])}. {constants.FEEDBACK_LINK}" - ) - - if len(x) == 0: - raise ValueError("Cannot cut empty array.") - - if not isinstance(x, bigframes.series.Series): - x = bigframes.series.Series(x, session=session) - - if isinstance(bins, int): - if bins <= 0: - raise ValueError("`bins` should be a positive integer.") - if isinstance(labels, typing.Iterable): - labels = tuple(labels) - if len(labels) != bins: - raise ValueError( - f"Bin labels({len(labels)}) must be same as the value of bins({bins})" - ) - - op = agg_ops.CutOp(bins, right=right, labels=labels) - return x._apply_window_op(op, window_spec=window_specs.unbound()) - elif isinstance(bins, typing.Iterable): - if isinstance(bins, pd.IntervalIndex): - as_index: pd.IntervalIndex = bins - bins = tuple((bin.left.item(), bin.right.item()) for bin in bins) - # To maintain consistency with pandas' behavior - right = True - labels = None - elif len(list(bins)) == 0: - as_index = pd.IntervalIndex.from_tuples(list(bins)) - bins = tuple() - elif isinstance(list(bins)[0], tuple): - as_index = pd.IntervalIndex.from_tuples(list(bins)) - bins = tuple(bins) - # To maintain consistency with pandas' behavior - right = True - labels = None - elif pd.api.types.is_number(list(bins)[0]): - bins_list = list(bins) - as_index = pd.IntervalIndex.from_breaks(bins_list) - single_type = all([isinstance(n, type(bins_list[0])) for n in bins_list]) - numeric_type = type(bins_list[0]) if single_type else float - bins = tuple( - [ - (numeric_type(bins_list[i]), numeric_type(bins_list[i + 1])) - for i in range(len(bins_list) - 1) - ] - ) - else: - raise ValueError("`bins` iterable should contain tuples or numerics.") - - if as_index.is_overlapping: - raise ValueError("Overlapping IntervalIndex is not accepted.") # TODO: test - - if isinstance(labels, typing.Iterable): - labels = tuple(labels) - if len(labels) != len(as_index): - raise ValueError( - f"Bin labels({len(labels)}) must be same as the number of bin edges" - f"({len(as_index)})" - ) - - if len(as_index) == 0: - dtype = agg_ops.CutOp(bins, right=right, labels=labels).output_type() - return bigframes.series.Series( - [pd.NA] * len(x), - dtype=dtype, - name=x.name, - index=x.index, - session=x._session, - ) - else: - op = agg_ops.CutOp(bins, right=right, labels=labels) - return x._apply_window_op(op, window_spec=window_specs.unbound()) - else: - raise ValueError("`bins` must be an integer or interable.") - - -cut.__doc__ = vendored_pandas_tile.cut.__doc__ - - -def qcut( - x: bigframes.series.Series, - q: typing.Union[int, typing.Sequence[float]], - *, - labels: typing.Optional[bool] = None, - duplicates: typing.Literal["drop", "error"] = "error", -) -> bigframes.series.Series: - if isinstance(q, int) and q <= 0: - raise ValueError("`q` should be a positive integer.") - if utils.is_list_like(q): - q = tuple(q) - - if labels is not False: - raise NotImplementedError( - f"Only labels=False is supported in BigQuery DataFrames so far. {constants.FEEDBACK_LINK}" - ) - if duplicates != "drop": - raise NotImplementedError( - f"Only duplicates='drop' is supported in BigQuery DataFrames so far. {constants.FEEDBACK_LINK}" - ) - block = x._block - label = block.col_id_to_label[x._value_column] - block, nullity_id = block.apply_unary_op(x._value_column, ops.notnull_op) - block, result = block.apply_window_op( - x._value_column, - agg_ops.QcutOp(q), # type: ignore - window_spec=window_specs.unbound( - grouping_keys=(nullity_id,), - ordering=(order.ascending_over(x._value_column),), - ), - ) - block, result = block.project_expr( - ops.where_op.as_expr(result, nullity_id, ex.const(None)), label=label - ) - return bigframes.series.Series(block.select_column(result)) - - -qcut.__doc__ = vendored_pandas_tile.qcut.__doc__ diff --git a/bigframes/core/rewrite/__init__.py b/bigframes/core/rewrite/__init__.py deleted file mode 100644 index ae4b142b1a4..00000000000 --- a/bigframes/core/rewrite/__init__.py +++ /dev/null @@ -1,61 +0,0 @@ -# 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. -# 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 bigframes.core.rewrite.as_sql import as_sql_nodes -from bigframes.core.rewrite.ctes import extract_ctes -from bigframes.core.rewrite.fold_row_count import fold_row_counts -from bigframes.core.rewrite.identifiers import remap_variables -from bigframes.core.rewrite.implicit_align import try_row_join -from bigframes.core.rewrite.legacy_align import legacy_join_as_projection -from bigframes.core.rewrite.nullity import simplify_join -from bigframes.core.rewrite.order import bake_order, defer_order, pull_out_order -from bigframes.core.rewrite.pruning import column_pruning -from bigframes.core.rewrite.scan_reduction import ( - 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.udfs import lower_udfs -from bigframes.core.rewrite.windows import ( - pull_out_window_order, - rewrite_range_rolling, - simplify_complex_windows, -) - -__all__ = [ - "as_sql_nodes", - "extract_ctes", - "legacy_join_as_projection", - "try_row_join", - "rewrite_slice", - "rewrite_timedelta_expressions", - "pull_up_limits", - "pull_out_limit", - "remap_variables", - "defer_order", - "column_pruning", - "rewrite_range_rolling", - "try_reduce_to_table_scan", - "bake_order", - "pull_out_order", - "try_reduce_to_local_scan", - "fold_row_counts", - "pull_out_window_order", - "defer_selection", - "simplify_complex_windows", - "lower_udfs", - "simplify_join", -] diff --git a/bigframes/core/rewrite/as_sql.py b/bigframes/core/rewrite/as_sql.py deleted file mode 100644 index eb823d1fed1..00000000000 --- a/bigframes/core/rewrite/as_sql.py +++ /dev/null @@ -1,308 +0,0 @@ -# Copyright 2026 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -from __future__ import annotations - -import dataclasses -import itertools -from typing import Optional, Sequence, Union - -import bigframes.core.rewrite -from bigframes.core import ( - agg_expressions, - expression, - guid, - identifiers, - nodes, - ordering, - sql_nodes, -) - - -def _limit(select: sql_nodes.SqlSelectNode, limit: int) -> sql_nodes.SqlSelectNode: - new_limit = limit if select.limit is None else min([select.limit, limit]) - return dataclasses.replace(select, limit=new_limit) - - -def _try_sort( - select: sql_nodes.SqlSelectNode, sort_by: Sequence[ordering.OrderingExpression] -) -> Optional[sql_nodes.SqlSelectNode]: - new_order_exprs = [] - for sort_expr in sort_by: - new_expr = _try_bind( - sort_expr.scalar_expression, select.get_id_mapping(), analytic_allowed=False - ) - if new_expr is None: - return None - new_order_exprs.append( - dataclasses.replace(sort_expr, scalar_expression=new_expr) - ) - return dataclasses.replace(select, sorting=tuple(new_order_exprs)) - - -def _sort( - node: nodes.BigFrameNode, sort_by: Sequence[ordering.OrderingExpression] -) -> sql_nodes.SqlSelectNode: - if isinstance(node, sql_nodes.SqlSelectNode): - merged = _try_sort(node, sort_by) - if merged: - return merged - result = _try_sort(_create_noop_select(node), sort_by) - assert result is not None - return result - - -def _try_bind( - expr: expression.Expression, - bindings: dict[identifiers.ColumnId, expression.Expression], - analytic_allowed: bool = False, # means block binding to an analytic even if original is scalar -) -> Optional[expression.Expression]: - if not expr.is_scalar_expr or not analytic_allowed: - for ref in expr.column_references: - if ref in bindings and not bindings[ref].is_scalar_expr: - return None - return expr.bind_refs(bindings) - - -def _try_add_cdefs( - select: sql_nodes.SqlSelectNode, cdefs: Sequence[nodes.ColumnDef] -) -> Optional[sql_nodes.SqlSelectNode]: - # TODO: add up complexity measure while inlining refs - new_defs = [] - for cdef in cdefs: - cdef_expr = cdef.expression - merged_expr = _try_bind( - cdef_expr, select.get_id_mapping(), analytic_allowed=True - ) - if merged_expr is None: - return None - new_defs.append(nodes.ColumnDef(merged_expr, cdef.id)) - - return dataclasses.replace(select, selections=(*select.selections, *new_defs)) - - -def _add_cdefs( - node: nodes.BigFrameNode, cdefs: Sequence[nodes.ColumnDef] -) -> sql_nodes.SqlSelectNode: - if isinstance(node, sql_nodes.SqlSelectNode): - merged = _try_add_cdefs(node, cdefs) - if merged: - return merged - # Otherwise, wrap the child in a SELECT and add the columns - result = _try_add_cdefs(_create_noop_select(node), cdefs) - assert result is not None - return result - - -def _try_add_filter( - select: sql_nodes.SqlSelectNode, predicates: Sequence[expression.Expression] -) -> Optional[sql_nodes.SqlSelectNode]: - # Filter implicitly happens first, so merging it into ths select will modify non-scalar col expressions - if not all(cdef.expression.is_scalar_expr for cdef in select.selections): - return None - if not all( - sort_expr.scalar_expression.is_scalar_expr for sort_expr in select.sorting - ): - return None - # Constraint: filters can only be merged if they are scalar expression after binding - new_predicates = [] - # bind variables, merge predicates - for predicate in predicates: - merged_pred = _try_bind(predicate, select.get_id_mapping()) - if not merged_pred: - return None - new_predicates.append(merged_pred) - return dataclasses.replace(select, predicates=(*select.predicates, *new_predicates)) - - -def _add_filter( - node: nodes.BigFrameNode, predicates: Sequence[expression.Expression] -) -> sql_nodes.SqlSelectNode: - if isinstance(node, sql_nodes.SqlSelectNode): - result = _try_add_filter(node, predicates) - if result: - return result - new_node = _try_add_filter(_create_noop_select(node), predicates) - assert new_node is not None - return new_node - - -def _create_noop_select(node: nodes.BigFrameNode) -> sql_nodes.SqlSelectNode: - return sql_nodes.SqlSelectNode( - node, - selections=tuple( - nodes.ColumnDef(expression.ResolvedDerefOp.from_field(field), field.id) - for field in node.fields - ), - ) - - -def _try_remap_select_cols( - select: sql_nodes.SqlSelectNode, cols: Sequence[nodes.AliasedRef] -): - new_defs = [] - for aliased_ref in cols: - new_defs.append( - nodes.ColumnDef(select.get_id_mapping()[aliased_ref.ref.id], aliased_ref.id) - ) - - return dataclasses.replace(select, selections=tuple(new_defs)) - - -def _remap_select_cols(node: nodes.BigFrameNode, cols: Sequence[nodes.AliasedRef]): - if isinstance(node, sql_nodes.SqlSelectNode): - result = _try_remap_select_cols(node, cols) - if result: - return result - new_node = _try_remap_select_cols(_create_noop_select(node), cols) - assert new_node is not None - return new_node - - -def _get_added_cdefs(node: Union[nodes.ProjectionNode, nodes.WindowOpNode]): - # TODO: InNode - if isinstance(node, nodes.ProjectionNode): - return tuple(nodes.ColumnDef(expr, id) for expr, id in node.assignments) - if isinstance(node, nodes.WindowOpNode): - new_cdefs = [] - for cdef in node.agg_exprs: - assert isinstance(cdef.expression, agg_expressions.Aggregation) - window_expr = agg_expressions.WindowExpression( - cdef.expression, node.window_spec - ) - # TODO: we probably should do this as another step - rewritten_window_expr = bigframes.core.rewrite.simplify_complex_windows( - window_expr - ) - new_cdefs.append(nodes.ColumnDef(rewritten_window_expr, cdef.id)) - return tuple(new_cdefs) - else: - raise ValueError(f"Unexpected node type: {type(node)}") - - -def _as_sql_node(node: nodes.BigFrameNode) -> nodes.BigFrameNode: - # case one, can be converted to select - if isinstance(node, nodes.ReadTableNode): - leaf = sql_nodes.SqlDataSource(source=node.source) - mappings = [ - nodes.AliasedRef(expression.deref(scan_item.source_id), scan_item.id) - for scan_item in node.scan_list.items - ] - return _remap_select_cols(leaf, mappings) - elif isinstance(node, (nodes.ProjectionNode, nodes.WindowOpNode)): - cdefs = _get_added_cdefs(node) - return _add_cdefs(node.child, cdefs) - elif isinstance(node, (nodes.SelectionNode)): - return _remap_select_cols(node.child, node.input_output_pairs) - elif isinstance(node, nodes.FilterNode): - return _add_filter(node.child, [node.predicate]) - elif isinstance(node, nodes.ResultNode): - result = node.child - if node.order_by is not None: - result = _sort(result, node.order_by.all_ordering_columns) - result = _remap_select_cols( - result, - [ - nodes.AliasedRef(ref, identifiers.ColumnId(name)) - for ref, name in node.output_cols - ], - ) - if node.limit is not None: - result = _limit(result, node.limit) # type: ignore - return result - else: - return node - - -# In the future, we will have sql nodes for each of these node types. -_LOGICAL_NODE_TYPES_TO_WRAP = ( - nodes.ReadLocalNode, - nodes.ExplodeNode, - nodes.InNode, - nodes.AggregateNode, - nodes.FromRangeNode, - nodes.ConcatNode, - sql_nodes.SqlSelectNode, -) - - -def _insert_cte_markers(root: nodes.BigFrameNode) -> nodes.BigFrameNode: - # important not to wrap nodes that are already wrapped - wrapped_nodes = set( - node.child for node in root.unique_nodes() if isinstance(node, nodes.CteNode) - ) - # don't wrap child nodes of ConcatNode - union_child_nodes = set( - itertools.chain.from_iterable( - node.child_nodes - for node in root.unique_nodes() - if isinstance(node, nodes.ConcatNode) - ) - ) - - def maybe_insert_cte_marker(node: nodes.BigFrameNode) -> nodes.BigFrameNode: - if node == root: - return node - if ( - isinstance(node, _LOGICAL_NODE_TYPES_TO_WRAP) - and node not in wrapped_nodes - and node not in union_child_nodes - ): - wrapped_nodes.add(node) - return nodes.CteNode(node) - return node - - return root.top_down(maybe_insert_cte_marker) - - -def _extract_ctes_to_with_expr( - root: nodes.BigFrameNode, uid_gen: guid.SequentialUIDGenerator -) -> nodes.BigFrameNode: - topological_ctes = list( - filter( - lambda n: isinstance(n, nodes.CteNode), - root.iter_nodes_topo(), - ) - ) - cte_names = tuple( - next(uid_gen.get_uid_stream("bfcte_")) for _ in range(len(topological_ctes)) - ) - - if len(topological_ctes) == 0: - return root - - mapping = { - cte_node: sql_nodes.SqlCteRefNode(cte_name, tuple(cte_node.fields)) - for cte_node, cte_name in zip(topological_ctes, cte_names) - } - - # Replace all CTEs with CTE references and wrap the new root in a WITH clause - return sql_nodes.SqlWithCtesNode( - root.top_down(lambda x: mapping.get(x, x)), - cte_names, - tuple( - # Mypy loses context that cte_node is a CteNode with a child attribute, despite the isinstance filter above. - cte_node.child.top_down(lambda x: mapping.get(x, x)) # type: ignore[attr-defined] - for cte_node in topological_ctes - ), - ) - - -def as_sql_nodes( - root: nodes.BigFrameNode, uid_gen: guid.SequentialUIDGenerator -) -> nodes.BigFrameNode: - root = nodes.bottom_up(root, _as_sql_node) - # Insert CTE markers to indicate where we want to split the query. - root = _insert_cte_markers(root) - root = _extract_ctes_to_with_expr(root, uid_gen) - return root diff --git a/bigframes/core/rewrite/ctes.py b/bigframes/core/rewrite/ctes.py deleted file mode 100644 index a5afd19bb35..00000000000 --- a/bigframes/core/rewrite/ctes.py +++ /dev/null @@ -1,41 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -from __future__ import annotations - -from collections import defaultdict - -from bigframes.core import nodes - - -def extract_ctes(root: nodes.BigFrameNode) -> nodes.BigFrameNode: - # identify candidates - node_parents: dict[nodes.BigFrameNode, int] = defaultdict(int) - for parent in root.unique_nodes(): - for child in parent.child_nodes: - node_parents[child] += 1 - - # everywhere a multi-parent node is referenced, wrap it in a CTE node - def insert_cte_markers(node: nodes.BigFrameNode) -> nodes.BigFrameNode: - def _add_cte_if_needed(child: nodes.BigFrameNode) -> nodes.BigFrameNode: - if node_parents[child] > 1: - return nodes.CteNode(child) - return child - - if isinstance(node, nodes.CteNode): - # don't re-wrap CTE nodes - return node - - return node.transform_children(_add_cte_if_needed) - - return root.top_down(insert_cte_markers) diff --git a/bigframes/core/rewrite/fold_row_count.py b/bigframes/core/rewrite/fold_row_count.py deleted file mode 100644 index cc0b818fb96..00000000000 --- a/bigframes/core/rewrite/fold_row_count.py +++ /dev/null @@ -1,40 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -from __future__ import annotations - -import pyarrow as pa - -from bigframes.core import local_data, nodes -from bigframes.operations import aggregations - - -def fold_row_counts(node: nodes.BigFrameNode) -> nodes.BigFrameNode: - if not isinstance(node, nodes.AggregateNode): - return node - if len(node.by_column_ids) > 0: - return node - if node.child.row_count is None: - return node - for agg, _ in node.aggregations: - if agg.op != aggregations.size_op: - return node - local_data_source = local_data.ManagedArrowTable.from_pyarrow( - pa.table({"count": pa.array([node.child.row_count], type=pa.int64())}) - ) - scan_list = nodes.ScanList( - tuple(nodes.ScanItem(out_id, "count") for _, out_id in node.aggregations) - ) - return nodes.ReadLocalNode( - local_data_source=local_data_source, scan_list=scan_list, session=node.session - ) diff --git a/bigframes/core/rewrite/identifiers.py b/bigframes/core/rewrite/identifiers.py deleted file mode 100644 index 7b1d1d9a512..00000000000 --- a/bigframes/core/rewrite/identifiers.py +++ /dev/null @@ -1,117 +0,0 @@ -# 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. -# 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.core import identifiers, nodes - - -def _create_mapping_operator( - id_def_remapping_by_node: dict[ - nodes.BigFrameNode, dict[identifiers.ColumnId, identifiers.ColumnId] - ], - id_ref_remapping_by_node: dict[ - nodes.BigFrameNode, dict[identifiers.ColumnId, identifiers.ColumnId] - ], -): - """ - Builds a remapping operator that uses predefined local remappings for ids. - - Args: - id_remapping_by_node: A mapping from nodes to their local remappings. - - Returns: - A remapping operator. - """ - - def _mapping_operator(node: nodes.BigFrameNode) -> nodes.BigFrameNode: - # Step 1: Get the local remapping for the current node. - local_def_remaps = id_def_remapping_by_node[node] - local_ref_remaps = id_ref_remapping_by_node[node] - - result = node.remap_vars(local_def_remaps) - result = result.remap_refs(local_ref_remaps) - - return result - - return _mapping_operator - - -def remap_variables( - root: nodes.BigFrameNode, - id_generator: typing.Iterator[identifiers.ColumnId], -) -> typing.Tuple[ - nodes.BigFrameNode, - dict[identifiers.ColumnId, identifiers.ColumnId], -]: - """Remaps `ColumnId`s in the expression tree to be deterministic and sequential. - - This function performs a post-order traversal. It recursively remaps children - nodes first, then remaps the current node's references and definitions. - - Note: this will convert a DAG to a tree by duplicating shared nodes. - - Args: - root: The root node of the expression tree. - id_generator: An iterator that yields new column IDs. - - Returns: - A tuple of the new root node and a mapping from old to new column IDs - visible to the parent node. - """ - # step 1: defined remappings for each individual unique node - # step 2: top down traversal to apply remappings (mappings are value-based, so bottom-up doesn't work) - - id_def_remaps: dict[ - nodes.BigFrameNode, dict[identifiers.ColumnId, identifiers.ColumnId] - ] = {} - id_ref_remaps: dict[ - nodes.BigFrameNode, dict[identifiers.ColumnId, identifiers.ColumnId] - ] = {} - for node in root.iter_nodes_topo(): # bottom up - local_def_remaps = { - col_id: next(id_generator) for col_id in node.node_defined_ids - } - id_def_remaps[node] = local_def_remaps - - local_ref_remaps = {} - - # InNode is special case as ID scope inherited purely from left side - inheriting_nodes = ( - [node.child_nodes[0]] - if isinstance(node, nodes.InNode) - else node.child_nodes - ) - for child in inheriting_nodes: # inherit ref and def mappings from children - if not child.defines_namespace: # these nodes represent new id spaces - local_ref_remaps.update( - { - old_id: new_id - for old_id, new_id in id_ref_remaps[child].items() - if old_id in child.ids - } - ) - local_ref_remaps.update(id_def_remaps[child]) - id_ref_remaps[node] = local_ref_remaps - - # have to do top down to preserve node identities - return ( - root.top_down(_create_mapping_operator(id_def_remaps, id_ref_remaps)), - # Only used by unit tests - { - old_id: (id_def_remaps[root] | id_ref_remaps[root])[old_id] - for old_id in root.ids - }, - ) diff --git a/bigframes/core/rewrite/implicit_align.py b/bigframes/core/rewrite/implicit_align.py deleted file mode 100644 index ebd48d82362..00000000000 --- a/bigframes/core/rewrite/implicit_align.py +++ /dev/null @@ -1,220 +0,0 @@ -# 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. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -from __future__ import annotations - -import dataclasses -import itertools -from typing import Optional, Sequence, Set, Tuple - -import bigframes.core.expression -import bigframes.core.identifiers -import bigframes.core.nodes - -# Combination of selects and additive nodes can be merged as an explicit keyless "row join" -ALIGNABLE_NODES = ( - bigframes.core.nodes.SelectionNode, - bigframes.core.nodes.ProjectionNode, - bigframes.core.nodes.WindowOpNode, - bigframes.core.nodes.PromoteOffsetsNode, - bigframes.core.nodes.InNode, -) - - -@dataclasses.dataclass(frozen=True) -class ExpressionSpec: - expression: bigframes.core.expression.Expression - node: bigframes.core.nodes.BigFrameNode - - -def get_expression_spec( - node: bigframes.core.nodes.BigFrameNode, id: bigframes.core.identifiers.ColumnId -) -> ExpressionSpec: - """Normalizes column value by chaining expressions across multiple selection and projection nodes if possible. - This normalization helps identify whether columns are equivalent. - """ - # TODO: While we chain expression fragments from different nodes - # we could further normalize with constant folding and other scalar expression rewrites - expression: bigframes.core.expression.Expression = ( - bigframes.core.expression.DerefOp(id) - ) - curr_node = node - while True: - if isinstance(curr_node, bigframes.core.nodes.SelectionNode): - select_mappings = { - col_id: ref for ref, col_id in curr_node.input_output_pairs - } - expression = expression.bind_refs( - select_mappings, allow_partial_bindings=True - ) - elif isinstance(curr_node, bigframes.core.nodes.ProjectionNode): - proj_mappings = {col_id: expr for expr, col_id in curr_node.assignments} - expression = expression.bind_refs( - proj_mappings, allow_partial_bindings=True - ) - elif isinstance( - curr_node, - ( - bigframes.core.nodes.WindowOpNode, - bigframes.core.nodes.PromoteOffsetsNode, - bigframes.core.nodes.InNode, - ), - ): - if set(expression.column_references).isdisjoint( - field.id for field in curr_node.added_fields - ): - # we don't yet have a way of normalizing window ops into a ExpressionSpec, which only - # handles normalizing scalar expressions at the moment. - pass - else: - return ExpressionSpec(expression, curr_node) - else: - return ExpressionSpec(expression, curr_node) - curr_node = curr_node.child_nodes[0] - - -def try_row_join( - l_node: bigframes.core.nodes.BigFrameNode, - r_node: bigframes.core.nodes.BigFrameNode, - join_keys: Tuple[Tuple[str, str], ...], -) -> Optional[bigframes.core.nodes.BigFrameNode]: - """Joins the two nodes""" - divergent_node = first_shared_descendent( - {l_node, r_node}, descendable_types=ALIGNABLE_NODES - ) - if divergent_node is None: - return None - # check join keys are equivalent by normalizing the expressions as much as posisble - # instead of just comparing ids - for l_key, r_key in join_keys: - # Caller is block, so they still work with raw strings rather than ids - left_id = bigframes.core.identifiers.ColumnId(l_key) - right_id = bigframes.core.identifiers.ColumnId(r_key) - if get_expression_spec(l_node, left_id) != get_expression_spec( - r_node, right_id - ): - return None - - l_node, l_selection = pull_up_selection(l_node, stop=divergent_node) - r_node, r_selection = pull_up_selection( - r_node, stop=divergent_node, rename_vars=True - ) # Rename only right vars to avoid collisions with left vars - combined_selection = l_selection + r_selection - - def _linearize_trees( - base_tree: bigframes.core.nodes.BigFrameNode, - append_tree: bigframes.core.nodes.BigFrameNode, - ) -> bigframes.core.nodes.BigFrameNode: - """Linearize two divergent tree who only diverge through different additive nodes.""" - # base case: append tree does not have any divergent nodes to linearize - if append_tree == divergent_node: - return base_tree - - assert isinstance(append_tree, bigframes.core.nodes.AdditiveNode) - return append_tree.replace_additive_base( - _linearize_trees(base_tree, append_tree.additive_base) - ) - - merged_node = _linearize_trees(l_node, r_node) - return bigframes.core.nodes.SelectionNode(merged_node, combined_selection) - - -def pull_up_selection( - node: bigframes.core.nodes.BigFrameNode, - stop: bigframes.core.nodes.BigFrameNode, - rename_vars: bool = False, -) -> Tuple[ - bigframes.core.nodes.BigFrameNode, - Tuple[bigframes.core.nodes.AliasedRef, ...], -]: - """Remove all selection nodes above the base node. Returns stripped tree. - - Args: - node (BigFrameNode): - The node from which to pull up SelectionNode ops - rename_vars (bool): - If true, will rename projected columns to new unique ids. - - Returns: - BigFrameNode, Selections - """ - if node == stop: # base case - return node, tuple( - bigframes.core.nodes.AliasedRef.identity(field.id) for field in node.fields - ) - - if isinstance(node, bigframes.core.nodes.AdditiveNode): - child_node, child_selections = pull_up_selection( - node.additive_base, stop, rename_vars=rename_vars - ) - mapping = {out: ref.id for ref, out in child_selections} - new_node: bigframes.core.nodes.BigFrameNode = node.replace_additive_base( - child_node - ) - new_node = new_node.remap_refs(mapping) - if rename_vars: - var_renames = { - field.id: bigframes.core.identifiers.ColumnId.unique() - for field in node.added_fields - } - new_node = new_node.remap_vars(var_renames) - else: - var_renames = {} - assert isinstance(new_node, bigframes.core.nodes.AdditiveNode) - added_selections = tuple( - bigframes.core.nodes.AliasedRef.identity(field.id).remap_refs(var_renames) - for field in node.added_fields - ) - new_selection = child_selections + added_selections - return new_node, new_selection - elif isinstance(node, bigframes.core.nodes.SelectionNode): - child_node, child_selections = pull_up_selection( - node.child, stop, rename_vars=rename_vars - ) - mapping = {out: ref.id for ref, out in child_selections} - return child_node, tuple( - ref.remap_refs(mapping) for ref in node.input_output_pairs - ) - raise ValueError(f"Couldn't pull up select from node: {node}") - - -## Traversal helpers -def first_shared_descendent( - roots: Set[bigframes.core.nodes.BigFrameNode], - descendable_types: Tuple[type[bigframes.core.nodes.BigFrameNode], ...], -) -> Optional[bigframes.core.nodes.BigFrameNode]: - if not roots: - return None - if len(roots) == 1: - return next(iter(roots)) - - min_height = min(root.height for root in roots) - - def descend( - root: bigframes.core.nodes.BigFrameNode, - ) -> Sequence[bigframes.core.nodes.BigFrameNode]: - # Special case to not descend into right side of IsInNode - if isinstance(root, bigframes.core.nodes.AdditiveNode): - return (root.additive_base,) - return root.child_nodes - - roots_to_descend = set(root for root in roots if root.height > min_height) - if not roots_to_descend: - roots_to_descend = roots - if any(not isinstance(root, descendable_types) for root in roots_to_descend): - return None - as_is = roots - roots_to_descend - descended = set( - itertools.chain.from_iterable(descend(root) for root in roots_to_descend) - ) - return first_shared_descendent(as_is.union(descended), descendable_types) diff --git a/bigframes/core/rewrite/legacy_align.py b/bigframes/core/rewrite/legacy_align.py deleted file mode 100644 index 26ee71d2ec5..00000000000 --- a/bigframes/core/rewrite/legacy_align.py +++ /dev/null @@ -1,367 +0,0 @@ -# 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. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -from __future__ import annotations - -import dataclasses -import functools -import itertools -from typing import Mapping, Optional, Sequence, Tuple - -import bigframes.core.expression as scalar_exprs -import bigframes.core.identifiers as ids -import bigframes.core.join_def as join_defs -import bigframes.core.nodes as nodes -import bigframes.core.ordering as order -import bigframes.core.rewrite.implicit_align -import bigframes.operations as ops - -Selection = Tuple[Tuple[scalar_exprs.Expression, ids.ColumnId], ...] - -LEGACY_REWRITER_NODES = ( - bigframes.core.nodes.ProjectionNode, - bigframes.core.nodes.SelectionNode, - bigframes.core.nodes.ReversedNode, - bigframes.core.nodes.OrderByNode, - bigframes.core.nodes.FilterNode, -) - - -@dataclasses.dataclass(frozen=True) -class SquashedSelect: - """Squash nodes together until target node, separating out the projection, filter and reordering expressions.""" - - root: nodes.BigFrameNode - columns: Tuple[Tuple[scalar_exprs.Expression, ids.ColumnId], ...] - predicate: Optional[scalar_exprs.Expression] - ordering: Tuple[order.OrderingExpression, ...] - reverse_root: bool = False - - @classmethod - def from_node_span( - cls, node: nodes.BigFrameNode, target: nodes.BigFrameNode - ) -> SquashedSelect: - if node == target: - selection = tuple((scalar_exprs.DerefOp(id), id) for id in node.ids) - return cls(node, selection, None, ()) - - if isinstance(node, nodes.SelectionNode): - return cls.from_node_span(node.child, target).select( - tuple(node.input_output_pairs) - ) - elif isinstance(node, nodes.ProjectionNode): - return cls.from_node_span(node.child, target).project(node.assignments) - elif isinstance(node, nodes.FilterNode): - return cls.from_node_span(node.child, target).filter(node.predicate) - elif isinstance(node, nodes.ReversedNode): - return cls.from_node_span(node.child, target).reverse() - elif isinstance(node, nodes.OrderByNode): - return cls.from_node_span(node.child, target).order_with(node.by) - else: - raise ValueError(f"Cannot rewrite node {node}") - - @property - def column_lookup(self) -> Mapping[ids.ColumnId, scalar_exprs.Expression]: - return {col_id: expr for expr, col_id in self.columns} - - def select( - self, input_output_pairs: Tuple[Tuple[scalar_exprs.DerefOp, ids.ColumnId], ...] - ) -> SquashedSelect: - new_columns = tuple( - ( - input.bind_refs(self.column_lookup), - output, - ) - for input, output in input_output_pairs - ) - return SquashedSelect( - self.root, new_columns, self.predicate, self.ordering, self.reverse_root - ) - - def project( - self, projection: Tuple[Tuple[scalar_exprs.Expression, ids.ColumnId], ...] - ) -> SquashedSelect: - existing_columns = self.columns - new_columns = tuple( - (expr.bind_refs(self.column_lookup), id) for expr, id in projection - ) - return SquashedSelect( - self.root, - (*existing_columns, *new_columns), - self.predicate, - self.ordering, - self.reverse_root, - ) - - def filter(self, predicate: scalar_exprs.Expression) -> SquashedSelect: - if self.predicate is None: - new_predicate = predicate.bind_refs(self.column_lookup) - else: - new_predicate = ops.and_op.as_expr( - self.predicate, predicate.bind_refs(self.column_lookup) - ) - return SquashedSelect( - self.root, self.columns, new_predicate, self.ordering, self.reverse_root - ) - - def reverse(self) -> SquashedSelect: - new_ordering = tuple(expr.with_reverse() for expr in self.ordering) - return SquashedSelect( - self.root, self.columns, self.predicate, new_ordering, not self.reverse_root - ) - - def order_with(self, by: Tuple[order.OrderingExpression, ...]): - adjusted_orderings = [ - order_part.bind_refs(self.column_lookup) for order_part in by - ] - new_ordering = (*adjusted_orderings, *self.ordering) - return SquashedSelect( - self.root, self.columns, self.predicate, new_ordering, self.reverse_root - ) - - def can_merge( - self, - right: SquashedSelect, - join_keys: Tuple[join_defs.CoalescedColumnMapping, ...], - ) -> bool: - """Determines whether the two selections can be merged into a single selection.""" - r_exprs_by_id = {id.name: expr for expr, id in right.columns} - l_exprs_by_id = {id.name: expr for expr, id in self.columns} - l_join_exprs = [ - l_exprs_by_id[join_key.left_source_id] for join_key in join_keys - ] - r_join_exprs = [ - r_exprs_by_id[join_key.right_source_id] for join_key in join_keys - ] - - if self.root != right.root: - return False - if len(l_join_exprs) != len(r_join_exprs): - return False - if any(l_expr != r_expr for l_expr, r_expr in zip(l_join_exprs, r_join_exprs)): - return False - return True - - def merge( - self, - right: SquashedSelect, - join_type: join_defs.JoinType, - join_keys: Tuple[join_defs.CoalescedColumnMapping, ...], - mappings: Tuple[join_defs.JoinColumnMapping, ...], - ) -> SquashedSelect: - if self.root != right.root: - raise ValueError("Cannot merge expressions with different roots") - # Mask columns and remap names to expected schema - lselection = self.columns - rselection = right.columns - if join_type == "inner": - new_predicate = and_predicates(self.predicate, right.predicate) - elif join_type == "outer": - new_predicate = or_predicates(self.predicate, right.predicate) - elif join_type == "left": - new_predicate = self.predicate - elif join_type == "right": - new_predicate = right.predicate - - l_relative, r_relative = relative_predicates(self.predicate, right.predicate) - lmask = l_relative if join_type in {"right", "outer"} else None - rmask = r_relative if join_type in {"left", "outer"} else None - new_columns = merge_expressions( - join_keys, mappings, lselection, rselection, lmask, rmask - ) - - # Reconstruct ordering - reverse_root = self.reverse_root - if join_type == "right": - new_ordering = right.ordering - reverse_root = right.reverse_root - elif join_type == "outer": - if lmask is not None: - prefix = order.OrderingExpression(lmask, order.OrderingDirection.DESC) - left_ordering = tuple( - order.OrderingExpression( - apply_mask(ref.scalar_expression, lmask), - ref.direction, - ref.na_last, - ) - for ref in self.ordering - ) - right_ordering = ( - tuple( - order.OrderingExpression( - apply_mask(ref.scalar_expression, rmask), - ref.direction, - ref.na_last, - ) - for ref in right.ordering - ) - if rmask - else right.ordering - ) - new_ordering = (prefix, *left_ordering, *right_ordering) - else: - new_ordering = self.ordering - elif join_type in {"inner", "left"}: - new_ordering = self.ordering - else: - raise ValueError(f"Unexpected join type {join_type}") - return SquashedSelect( - self.root, new_columns, new_predicate, new_ordering, reverse_root - ) - - def expand(self) -> nodes.BigFrameNode: - # Safest to apply predicates first, as it may filter out inputs that cannot be handled by other expressions - root = self.root - if self.reverse_root: - root = nodes.ReversedNode(child=root) - if self.predicate: - root = nodes.FilterNode(child=root, predicate=self.predicate) - if self.ordering: - root = nodes.OrderByNode(child=root, by=self.ordering) - selection = tuple( - bigframes.core.nodes.AliasedRef.identity(id) for _, id in self.columns - ) - return nodes.SelectionNode( - child=nodes.ProjectionNode(child=root, assignments=self.columns), - input_output_pairs=selection, - ) - - -def legacy_join_as_projection( - l_node: nodes.BigFrameNode, - r_node: nodes.BigFrameNode, - join_keys: Tuple[join_defs.CoalescedColumnMapping, ...], - mappings: Tuple[join_defs.JoinColumnMapping, ...], - how: join_defs.JoinType, -) -> Optional[nodes.BigFrameNode]: - rewrite_common_node = common_selection_root(l_node, r_node) - if rewrite_common_node is not None: - left_side = SquashedSelect.from_node_span(l_node, rewrite_common_node) - right_side = SquashedSelect.from_node_span(r_node, rewrite_common_node) - if not left_side.can_merge(right_side, join_keys): - # Most likely because join keys didn't match - return None - merged = left_side.merge(right_side, how, join_keys, mappings) - assert merged is not None, ( - "Couldn't merge nodes. This shouldn't happen. Please share full stacktrace with the BigQuery DataFrames team at bigframes-feedback@google.com." - ) - return merged.expand() - else: - return None - - -def merge_expressions( - join_keys: Tuple[join_defs.CoalescedColumnMapping, ...], - mappings: Tuple[join_defs.JoinColumnMapping, ...], - lselection: Selection, - rselection: Selection, - lmask: Optional[scalar_exprs.Expression], - rmask: Optional[scalar_exprs.Expression], -) -> Selection: - new_selection: Selection = tuple() - # Assumption is simple ids - l_exprs_by_id = {id.name: expr for expr, id in lselection} - r_exprs_by_id = {id.name: expr for expr, id in rselection} - for key in join_keys: - # Join keys expressions are equivalent on both sides, so can choose either left or right key - assert l_exprs_by_id[key.left_source_id] == r_exprs_by_id[key.right_source_id] - expr = l_exprs_by_id[key.left_source_id] - id = key.destination_id - new_selection = (*new_selection, (expr, ids.ColumnId(id))) - for mapping in mappings: - if mapping.source_table == join_defs.JoinSide.LEFT: - expr = l_exprs_by_id[mapping.source_id] - if lmask is not None: - expr = apply_mask(expr, lmask) - else: # Right - expr = r_exprs_by_id[mapping.source_id] - if rmask is not None: - expr = apply_mask(expr, rmask) - new_selection = (*new_selection, (expr, ids.ColumnId(mapping.destination_id))) - return new_selection - - -def and_predicates( - expr1: Optional[scalar_exprs.Expression], expr2: Optional[scalar_exprs.Expression] -) -> Optional[scalar_exprs.Expression]: - if expr1 is None: - return expr2 - if expr2 is None: - return expr1 - left_predicates = decompose_conjunction(expr1) - right_predicates = decompose_conjunction(expr2) - # remove common predicates - all_predicates = itertools.chain( - left_predicates, [p for p in right_predicates if p not in left_predicates] - ) - return merge_predicates(list(all_predicates)) - - -def or_predicates( - expr1: Optional[scalar_exprs.Expression], expr2: Optional[scalar_exprs.Expression] -) -> Optional[scalar_exprs.Expression]: - if (expr1 is None) or (expr2 is None): - return None - # TODO(tbergeron): Factor out common predicates - return ops.or_op.as_expr(expr1, expr2) - - -def relative_predicates( - expr1: Optional[scalar_exprs.Expression], expr2: Optional[scalar_exprs.Expression] -) -> Tuple[Optional[scalar_exprs.Expression], Optional[scalar_exprs.Expression]]: - left_predicates = decompose_conjunction(expr1) if expr1 else () - right_predicates = decompose_conjunction(expr2) if expr2 else () - left_relative = tuple( - pred for pred in left_predicates if pred not in right_predicates - ) - right_relative = tuple( - pred for pred in right_predicates if pred not in left_predicates - ) - return merge_predicates(left_relative), merge_predicates(right_relative) - - -def apply_mask( - expr: scalar_exprs.Expression, mask: scalar_exprs.Expression -) -> scalar_exprs.Expression: - return ops.where_op.as_expr(expr, mask, scalar_exprs.const(None)) - - -def merge_predicates( - predicates: Sequence[scalar_exprs.Expression], -) -> Optional[scalar_exprs.Expression]: - if len(predicates) == 0: - return None - - return functools.reduce(ops.and_op.as_expr, predicates) - - -def decompose_conjunction( - expr: scalar_exprs.Expression, -) -> Tuple[scalar_exprs.Expression, ...]: - if isinstance(expr, scalar_exprs.OpExpression) and isinstance( - expr.op, type(ops.and_op) - ): - return tuple( - itertools.chain.from_iterable(decompose_conjunction(i) for i in expr.inputs) - ) - else: - return (expr,) - - -def common_selection_root( - l_tree: nodes.BigFrameNode, r_tree: nodes.BigFrameNode -) -> Optional[nodes.BigFrameNode]: - """Find common subtree between join subtrees""" - return bigframes.core.rewrite.implicit_align.first_shared_descendent( - {l_tree, r_tree}, descendable_types=LEGACY_REWRITER_NODES - ) diff --git a/bigframes/core/rewrite/nullity.py b/bigframes/core/rewrite/nullity.py deleted file mode 100644 index 6307b12ec27..00000000000 --- a/bigframes/core/rewrite/nullity.py +++ /dev/null @@ -1,43 +0,0 @@ -# Copyright 2026 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from __future__ import annotations - -import dataclasses - -from bigframes.core import nodes - - -def simplify_join(node: nodes.BigFrameNode) -> nodes.BigFrameNode: - """Simplify a join node by removing nullity checks.""" - # if join conditions are provably non-null, we can set nulls_equal=False - if isinstance(node, nodes.JoinNode): - # even better, we can always make nulls_equal false, but wrap the join keys in coalesce - # to handle nulls correctly, this is more granular than the current implementation - for left_ref, right_ref in node.conditions: - if ( - node.left_child.field_by_id[left_ref.id].nullable - and node.right_child.field_by_id[right_ref.id].nullable - ): - return node - return dataclasses.replace(node, nulls_equal=False) - elif isinstance(node, nodes.InNode): - if ( - node.left_child.field_by_id[node.left_col.id].nullable - and node.right_child.fields[0].nullable - ): - return node - return dataclasses.replace(node, nulls_equal=False) - else: - return node diff --git a/bigframes/core/rewrite/op_lowering.py b/bigframes/core/rewrite/op_lowering.py deleted file mode 100644 index 013fc48c06a..00000000000 --- a/bigframes/core/rewrite/op_lowering.py +++ /dev/null @@ -1,55 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -from __future__ import annotations - -import abc -from typing import Sequence - -import bigframes.operations as ops -from bigframes.core import bigframe_node, expression, nodes - - -class OpLoweringRule(abc.ABC): - @property - @abc.abstractmethod - def op(self) -> type[ops.ScalarOp]: ... - - @abc.abstractmethod - def lower(self, expr: expression.OpExpression) -> expression.Expression: ... - - -def lower_ops( - root: bigframe_node.BigFrameNode, rules: Sequence[OpLoweringRule] -) -> bigframe_node.BigFrameNode: - rules_by_op = {rule.op: rule for rule in rules} - - def lower_expr(expr: expression.Expression): - def lower_expr_step(expr: expression.Expression) -> expression.Expression: - if isinstance(expr, expression.OpExpression): - maybe_rule = rules_by_op.get(expr.op.__class__) - if maybe_rule: - return maybe_rule.lower(expr) - return expr - - return expr.bottom_up(lower_expr_step) - - def lower_node(node: bigframe_node.BigFrameNode) -> bigframe_node.BigFrameNode: - if isinstance( - node, (nodes.ProjectionNode, nodes.FilterNode, nodes.OrderByNode) - ): - return node.transform_exprs(lower_expr) - else: - return node - - return root.bottom_up(lower_node) diff --git a/bigframes/core/rewrite/order.py b/bigframes/core/rewrite/order.py deleted file mode 100644 index b61fca82182..00000000000 --- a/bigframes/core/rewrite/order.py +++ /dev/null @@ -1,461 +0,0 @@ -# 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. -# 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 -import functools -from typing import Mapping, Tuple - -import bigframes.core.nodes -import bigframes.core.ordering -import bigframes.core.window_spec -from bigframes.core import agg_expressions, expression, identifiers -from bigframes.operations import aggregations as agg_ops - - -def defer_order( - root: bigframes.core.nodes.ResultNode, output_hidden_row_keys: bool -) -> bigframes.core.nodes.ResultNode: - new_child, order = _pull_up_order(root.child, order_root=True) - order_by = ( - order.with_ordering_columns(root.order_by.all_ordering_columns) - if root.order_by - else order - ) - if output_hidden_row_keys: - output_names = tuple((expression.DerefOp(id), id.sql) for id in new_child.ids) - else: - output_names = root.output_cols - return dataclasses.replace( - root, output_cols=output_names, child=new_child, order_by=order_by - ) - - -def bake_order( - node: bigframes.core.nodes.BigFrameNode, -) -> bigframes.core.nodes.BigFrameNode: - node, _ = _pull_up_order(node, order_root=False) - return node - - -def pull_out_order( - node: bigframes.core.nodes.BigFrameNode, -) -> Tuple[bigframes.core.nodes.BigFrameNode, bigframes.core.ordering.RowOrdering]: - import bigframes.core.rewrite.slices - - node = node.bottom_up(bigframes.core.rewrite.slices.rewrite_slice) - return _pull_up_order(node, order_root=True) - - -# Makes ordering explicit in window definitions -def _pull_up_order( - root: bigframes.core.nodes.BigFrameNode, - *, - order_root: bool = True, -) -> Tuple[bigframes.core.nodes.BigFrameNode, bigframes.core.ordering.RowOrdering]: - """ - Pull the ordering up, putting full order definition into window ops. - - May create extra colums, which must be removed by callers if they want to preserve original schema. - - Requires the following nodes to be removed/rewritten: SliceNode - - """ - - @functools.cache - def pull_up_order_inner( - node: bigframes.core.nodes.BigFrameNode, - ) -> Tuple[bigframes.core.nodes.BigFrameNode, bigframes.core.ordering.RowOrdering]: - """Pull filter nodes out of a tree section.""" - if isinstance(node, bigframes.core.nodes.ReversedNode): - child_result, child_order = pull_up_order_inner(node.child) - return child_result, child_order.with_reverse() - elif isinstance(node, bigframes.core.nodes.OrderByNode): - # unstable sorts don't care about previous order, total orders override previous order - if (not node.stable) or node.is_total_order: - new_node = remove_order(node.child) - else: - new_node, child_order = pull_up_order_inner(node.child) - - new_by = [] - ids: list[identifiers.ColumnId] = [] - for part in node.by: - if not isinstance( - part.scalar_expression, bigframes.core.expression.DerefOp - ): - id = identifiers.ColumnId.unique() - new_node = bigframes.core.nodes.ProjectionNode( - new_node, ((part.scalar_expression, id),) - ) - new_part = bigframes.core.ordering.OrderingExpression( - bigframes.core.expression.DerefOp(id), - part.direction, - part.na_last, - ) - new_by.append(new_part) - ids.append(id) - else: - new_by.append(part) - ids.append(part.scalar_expression.id) - - if node.is_total_order: - new_order: bigframes.core.ordering.RowOrdering = ( - bigframes.core.ordering.TotalOrdering( - ordering_value_columns=tuple(new_by), - total_ordering_columns=frozenset( - map(lambda x: bigframes.core.expression.DerefOp(x), ids) - ), - ) - ) - elif not node.stable: - new_order = bigframes.core.ordering.RowOrdering( - ordering_value_columns=tuple(new_by), - ) - else: - assert child_order - new_order = child_order.with_ordering_columns(new_by) - return new_node, new_order - elif isinstance(node, bigframes.core.nodes.ProjectionNode): - child_result, child_order = pull_up_order_inner(node.child) - return node.replace_child(child_result), child_order - elif isinstance(node, bigframes.core.nodes.JoinNode): - if node.propogate_order: - return pull_order_join(node) - else: - return ( - dataclasses.replace( - node, - left_child=remove_order_strict(node.left_child), - right_child=remove_order_strict(node.right_child), - ), - bigframes.core.ordering.RowOrdering(), - ) - elif isinstance(node, bigframes.core.nodes.ConcatNode): - return pull_order_concat(node) - elif isinstance(node, bigframes.core.nodes.FromRangeNode): - new_start = remove_order_strict(node.start) - new_end = remove_order_strict(node.end) - - new_node = dataclasses.replace(node, start=new_start, end=new_end) - return node, bigframes.core.ordering.TotalOrdering.from_primary_key( - [node.output_id] - ) - elif isinstance(node, bigframes.core.nodes.ReadLocalNode): - if node.offsets_col is None: - offsets_id = identifiers.ColumnId.unique() - new_root = dataclasses.replace(node, offsets_col=offsets_id) - return new_root, bigframes.core.ordering.TotalOrdering.from_offset_col( - offsets_id - ) - else: - return node, bigframes.core.ordering.TotalOrdering.from_offset_col( - node.offsets_col - ) - elif isinstance(node, bigframes.core.nodes.ReadTableNode): - if node.source.ordering is not None: - return node.pull_out_order() - else: - # No defined ordering - return node, bigframes.core.ordering.RowOrdering() - elif isinstance(node, bigframes.core.nodes.PromoteOffsetsNode): - child_result, child_order = pull_up_order_inner(node.child) - if child_order.is_total_ordering and child_order.is_sequential: - # special case, we can just project the ordering - order_expression = child_order.total_order_col - assert order_expression is not None - order_expression.scalar_expression - new_node = bigframes.core.nodes.ProjectionNode( - child_result, ((order_expression.scalar_expression, node.col_id),) - ) - return new_node, bigframes.core.ordering.TotalOrdering.from_offset_col( - node.col_id - ) - else: - # Otherwise we need to generate offsets - agg = agg_expressions.NullaryAggregation(agg_ops.RowNumberOp()) - col_def = bigframes.core.nodes.ColumnDef(agg, node.col_id) - window_spec = bigframes.core.window_spec.unbound( - ordering=tuple(child_order.all_ordering_columns) - ) - new_offsets_node = bigframes.core.nodes.WindowOpNode( - child_result, (col_def,), window_spec - ) - return ( - new_offsets_node, - bigframes.core.ordering.TotalOrdering.from_offset_col(node.col_id), - ) - elif isinstance(node, bigframes.core.nodes.FilterNode): - child_result, child_order = pull_up_order_inner(node.child) - return node.replace_child(child_result), child_order.with_non_sequential() - elif isinstance(node, bigframes.core.nodes.InNode): - child_result, child_order = pull_up_order_inner(node.left_child) - subquery_result = remove_order_strict(node.right_child) - return ( - dataclasses.replace( - node, left_child=child_result, right_child=subquery_result - ), - child_order, - ) - elif isinstance(node, bigframes.core.nodes.SelectionNode): - child_result, child_order = pull_up_order_inner(node.child) - selected_ids = set(ref.id for ref, _ in node.input_output_pairs) - unselected_order_cols = tuple( - col for col in child_order.referenced_columns if col not in selected_ids - ) - # Create unique ids just to be safe - new_selections = { - col: identifiers.ColumnId.unique() for col in unselected_order_cols - } - all_selections = node.input_output_pairs + tuple( - bigframes.core.nodes.AliasedRef(bigframes.core.expression.DerefOp(k), v) - for k, v in new_selections.items() - ) - new_select_node = dataclasses.replace( - node, child=child_result, input_output_pairs=all_selections - ) - new_order = child_order.remap_column_refs(new_select_node.get_id_mapping()) - return new_select_node, new_order - elif isinstance(node, bigframes.core.nodes.AggregateNode): - if node.has_ordered_ops: - child_result, child_order = pull_up_order_inner(node.child) - new_order_by = child_order.with_ordering_columns(node.order_by) - new_order = bigframes.core.ordering.TotalOrdering.from_primary_key( - [ref.id for ref in node.by_column_ids] - ) - return ( - dataclasses.replace( - node, - child=child_result, - order_by=tuple(new_order_by.all_ordering_columns), - ), - new_order, - ) - else: - child_result = remove_order(node.child) - return node.replace_child( - child_result - ), bigframes.core.ordering.TotalOrdering.from_primary_key( - [ref.id for ref in node.by_column_ids] - ) - elif isinstance(node, bigframes.core.nodes.WindowOpNode): - child_result, child_order = pull_up_order_inner(node.child) - if node.inherits_order: - new_window_order = ( - *node.window_spec.ordering, - *child_order.all_ordering_columns, - ) - new_window_spec = dataclasses.replace( - node.window_spec, ordering=new_window_order - ) - else: - new_window_spec = node.window_spec - return ( - dataclasses.replace( - node, child=child_result, window_spec=new_window_spec - ), - child_order, - ) - elif isinstance(node, bigframes.core.nodes.RandomSampleNode): - child_result, child_order = pull_up_order_inner(node.child) - return node.replace_child(child_result), child_order.with_non_sequential() - elif isinstance(node, bigframes.core.nodes.ExplodeNode): - child_result, child_order = pull_up_order_inner(node.child) - if node.offsets_col is None: - offsets_id = identifiers.ColumnId.unique() - new_explode: bigframes.core.nodes.BigFrameNode = dataclasses.replace( - node, child=child_result, offsets_col=offsets_id - ) - else: - offsets_id = node.offsets_col - new_explode = node.replace_child(child_result) - inner_order = bigframes.core.ordering.TotalOrdering.from_offset_col( - offsets_id - ) - return new_explode, child_order.join(inner_order) - raise ValueError(f"Unexpected node type {type(node).__name__}") - - def pull_order_concat( - node: bigframes.core.nodes.ConcatNode, - ) -> Tuple[ - bigframes.core.nodes.BigFrameNode, bigframes.core.ordering.TotalOrdering - ]: - new_sources = [] - for i, source in enumerate(node.child_nodes): - new_source, order = pull_up_order_inner(source) - offsets_id = identifiers.ColumnId.unique() - table_id = identifiers.ColumnId.unique() - if order.is_total_ordering and order.integer_encoding.is_encoded: - order_expression = order.total_order_col - assert order_expression is not None - new_source = bigframes.core.nodes.ProjectionNode( - new_source, ((order_expression.scalar_expression, offsets_id),) - ) - else: - agg = agg_expressions.NullaryAggregation(agg_ops.RowNumberOp()) - window_spec = bigframes.core.window_spec.unbound( - ordering=tuple(order.all_ordering_columns) - ) - col_def = bigframes.core.nodes.ColumnDef(agg, offsets_id) - new_source = bigframes.core.nodes.WindowOpNode( - new_source, (col_def,), window_spec - ) - new_source = bigframes.core.nodes.ProjectionNode( - new_source, ((bigframes.core.expression.const(i), table_id),) - ) - selection = tuple( - ( - bigframes.core.nodes.AliasedRef.identity(id) - for id in (*source.ids, table_id, offsets_id) - ) - ) - new_source = bigframes.core.nodes.SelectionNode(new_source, selection) - new_sources.append(new_source) - - union_offsets_id = identifiers.ColumnId.unique() - union_table_id = identifiers.ColumnId.unique() - new_ids = (*node.output_ids, union_table_id, union_offsets_id) - new_node = dataclasses.replace( - node, children=tuple(new_sources), output_ids=new_ids - ) - new_ordering = bigframes.core.ordering.TotalOrdering.from_primary_key( - (union_table_id, union_offsets_id) - ) - return new_node, new_ordering - - def pull_order_join( - node: bigframes.core.nodes.JoinNode, - ) -> Tuple[bigframes.core.nodes.BigFrameNode, bigframes.core.ordering.RowOrdering]: - left_child, left_order = pull_up_order_inner(node.left_child) - # as tree is a dag, and pull_up_order_inner is memoized, self-joins can create conflicts in new columns - right_child, right_order = pull_up_order_inner(node.right_child) - conflicts = set(left_child.ids) & set(right_child.ids) - if conflicts: - right_child, mapping = rename_cols(right_child, conflicts) - right_order = right_order.remap_column_refs( - mapping, allow_partial_bindings=True - ) - - if node.type in ("right", "outer"): - # right side is nullable - left_indicator = identifiers.ColumnId.unique() - left_child = bigframes.core.nodes.ProjectionNode( - left_child, ((bigframes.core.expression.const(True), left_indicator),) - ) - left_order = left_order.with_ordering_columns( - [bigframes.core.ordering.descending_over(left_indicator)] - ) - if node.type in ("left", "outer"): - # right side is nullable - right_indicator = identifiers.ColumnId.unique() - right_child = bigframes.core.nodes.ProjectionNode( - right_child, ((bigframes.core.expression.const(True), right_indicator),) - ) - right_order = right_order.with_ordering_columns( - [bigframes.core.ordering.descending_over(right_indicator)] - ) - - new_join = dataclasses.replace( - node, left_child=left_child, right_child=right_child - ) - new_order = ( - left_order.join(right_order) - if (node.type != "right") - else right_order.join(left_order) - ) - return new_join, new_order - - @functools.cache - def remove_order( - node: bigframes.core.nodes.BigFrameNode, - ) -> bigframes.core.nodes.BigFrameNode: - if isinstance( - node, (bigframes.core.nodes.OrderByNode, bigframes.core.nodes.ReversedNode) - ): - return remove_order(node.child) - elif isinstance( - node, - ( - bigframes.core.nodes.WindowOpNode, - bigframes.core.nodes.PromoteOffsetsNode, - ), - ): - if isinstance(node, bigframes.core.nodes.PromoteOffsetsNode): - node = rewrite_promote_offsets(node) - if node.inherits_order: - child_result, child_order = pull_up_order_inner(node.child) - new_window_order = ( - *node.window_spec.ordering, - *child_order.all_ordering_columns, - ) - new_window_spec = dataclasses.replace( - node.window_spec, ordering=new_window_order - ) - return dataclasses.replace( - node, child=child_result, window_spec=new_window_spec - ) - elif isinstance(node, bigframes.core.nodes.AggregateNode): - if node.has_ordered_ops: - child_result, child_order = pull_up_order_inner(node.child) - new_order_by = child_order.with_ordering_columns(node.order_by) - return dataclasses.replace( - node, - child=child_result, - order_by=tuple(new_order_by.all_ordering_columns), - ) - - return node.transform_children(remove_order) - - def remove_order_strict( - node: bigframes.core.nodes.BigFrameNode, - ) -> bigframes.core.nodes.BigFrameNode: - result = remove_order(node) - if result.ids != node.ids: - return bigframes.core.nodes.SelectionNode( - result, - tuple(bigframes.core.nodes.AliasedRef.identity(id) for id in node.ids), - ) - return result - - return ( - pull_up_order_inner(root) - if order_root - else (remove_order(root), bigframes.core.ordering.RowOrdering()) - ) - - -def rewrite_promote_offsets( - node: bigframes.core.nodes.PromoteOffsetsNode, -) -> bigframes.core.nodes.WindowOpNode: - agg = agg_expressions.NullaryAggregation(agg_ops.RowNumberOp()) - window_spec = bigframes.core.window_spec.unbound() - return bigframes.core.nodes.WindowOpNode( - node.child, (bigframes.core.nodes.ColumnDef(agg, node.col_id),), window_spec - ) - - -def rename_cols( - node: bigframes.core.nodes.BigFrameNode, cols: set[identifiers.ColumnId] -) -> Tuple[ - bigframes.core.nodes.BigFrameNode, - Mapping[identifiers.ColumnId, identifiers.ColumnId], -]: - mappings = dict((id, identifiers.ColumnId.unique()) for id in cols) - - result_node = bigframes.core.nodes.SelectionNode( - node, - tuple( - bigframes.core.nodes.AliasedRef.identity(id).remap_vars(mappings) - for id in node.ids - ), - ) - - return result_node, dict(mappings) diff --git a/bigframes/core/rewrite/pruning.py b/bigframes/core/rewrite/pruning.py deleted file mode 100644 index 29744d66cd6..00000000000 --- a/bigframes/core/rewrite/pruning.py +++ /dev/null @@ -1,188 +0,0 @@ -# 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. -# 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 -import functools -import itertools -import typing - -from bigframes.core import identifiers, nodes - - -def column_pruning( - root: nodes.BigFrameNode, -) -> nodes.BigFrameNode: - return nodes.top_down(root, prune_columns) - - -def to_fixed(max_iterations: int = 100): - def decorator(func): - @functools.wraps(func) - def wrapper(*args, **kwargs): - previous_result = None - current_result = func(*args, **kwargs) - attempts = 1 - - while attempts < max_iterations: - if current_result == previous_result: - return current_result - previous_result = current_result - current_result = func(current_result) - attempts += 1 - - return current_result - - return wrapper - - return decorator - - -@to_fixed(max_iterations=100) -def prune_columns(node: nodes.BigFrameNode): - if isinstance(node, nodes.SelectionNode): - result = prune_selection_child(node) - elif isinstance(node, nodes.ResultNode): - result = node.replace_child(prune_node(node.child, node.consumed_ids)) - elif isinstance(node, nodes.AggregateNode): - result = node.replace_child(prune_node(node.child, node.consumed_ids)) - else: - result = node - return result - - -def prune_selection_child( - selection: nodes.SelectionNode, -) -> nodes.BigFrameNode: - child = selection.child - - # Important to check this first - if list(selection.ids) == list(child.ids): - if all(ref.ref.id == ref.id for ref in selection.input_output_pairs): - # selection is no-op so just remove it entirely - return child - - if isinstance(child, nodes.SelectionNode): - return selection.remap_refs( - {id: ref.id for ref, id in child.input_output_pairs} - ).replace_child(child.child) - - elif isinstance(child, nodes.AdditiveNode): - if not set(field.id for field in child.added_fields) & selection.consumed_ids: - return selection.replace_child(child.additive_base) - needed_ids = selection.consumed_ids | child.referenced_ids - if isinstance(child, nodes.ProjectionNode): - # Projection expressions are independent, so can be individually removed from the node - child = dataclasses.replace( - child, - assignments=tuple( - (ex, id) for (ex, id) in child.assignments if id in needed_ids - ), - ) - return selection.replace_child( - child.replace_additive_base(prune_node(child.additive_base, needed_ids)) - ) - elif isinstance(child, nodes.ConcatNode): - indices = [ - list(child.ids).index(ref.id) for ref, _ in selection.input_output_pairs - ] - if len(indices) == 0: - # pushing zero-column selection into concat messes up emitter for now, which doesn't like zero columns - return selection - new_children = [] - for concat_node in child.child_nodes: - cc_ids = tuple(concat_node.ids) - sub_selection = tuple(nodes.AliasedRef.identity(cc_ids[i]) for i in indices) - new_children.append(nodes.SelectionNode(concat_node, sub_selection)) - return nodes.ConcatNode( - children=tuple(new_children), output_ids=tuple(selection.ids) - ) - # Nodes that pass through input columns - elif isinstance( - child, - ( - nodes.RandomSampleNode, - nodes.ReversedNode, - nodes.OrderByNode, - nodes.FilterNode, - nodes.SliceNode, - nodes.JoinNode, - nodes.ExplodeNode, - ), - ): - ids = selection.consumed_ids | child.referenced_ids - return selection.replace_child( - child.transform_children(lambda x: prune_node(x, ids)) - ) - elif isinstance(child, nodes.AggregateNode): - return selection.replace_child(prune_aggregate(child, selection.consumed_ids)) - elif isinstance(child, nodes.LeafNode): - return selection.replace_child(prune_leaf(child, selection.consumed_ids)) - return selection - - -def prune_node( - node: nodes.BigFrameNode, - ids: typing.AbstractSet[identifiers.ColumnId], -): - # This clause is important, ensures idempotency, so can reach fixed point - if not (set(node.ids) - ids): - return node - else: - # If no child ids are needed, probably a size op or numbering op above, keep a single column always - ids_to_keep = tuple(id for id in node.ids if id in ids) or tuple( - itertools.islice(node.ids, 0, 1) - ) - return nodes.SelectionNode( - node, - tuple(nodes.AliasedRef.identity(id) for id in ids_to_keep), - ) - - -def prune_aggregate( - node: nodes.AggregateNode, - used_cols: typing.AbstractSet[identifiers.ColumnId], -) -> nodes.AggregateNode: - pruned_aggs = ( - tuple(agg for agg in node.aggregations if agg[1] in used_cols) - or node.aggregations[0:1] - ) - return dataclasses.replace(node, aggregations=pruned_aggs) - - -@functools.singledispatch -def prune_leaf( - node: nodes.BigFrameNode, - used_cols: typing.AbstractSet[identifiers.ColumnId], -): ... - - -@prune_leaf.register -def prune_readlocal( - node: nodes.ReadLocalNode, - selection: typing.AbstractSet[identifiers.ColumnId], -) -> nodes.ReadLocalNode: - new_scan_list = node.scan_list.filter_cols(selection) - return dataclasses.replace( - node, - scan_list=new_scan_list, - offsets_col=node.offsets_col if (node.offsets_col in selection) else None, - ) - - -@prune_leaf.register -def prune_readtable( - node: nodes.ReadTableNode, - 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/scan_reduction.py b/bigframes/core/rewrite/scan_reduction.py deleted file mode 100644 index da609c1ea1f..00000000000 --- a/bigframes/core/rewrite/scan_reduction.py +++ /dev/null @@ -1,71 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -import dataclasses -import functools -from typing import Optional - -import bigframes.core.rewrite.slices -from bigframes.core import nodes - - -def try_reduce_to_table_scan(root: nodes.BigFrameNode) -> Optional[nodes.ReadTableNode]: - for node in root.unique_nodes(): - if not isinstance(node, (nodes.ReadTableNode, nodes.SelectionNode)): - return None - result = root.bottom_up(merge_scan) - if isinstance(result, nodes.ReadTableNode): - return result - return None - - -def try_reduce_to_local_scan( - node: nodes.BigFrameNode, -) -> Optional[tuple[nodes.ReadLocalNode, Optional[int]]]: - """Create a ReadLocalNode with optional limit, if possible. - - Similar to ReadApiSemiExecutor._try_adapt_plan. - """ - node, limit = bigframes.core.rewrite.slices.pull_out_limit(node) - - if not all( - map( - lambda x: isinstance(x, (nodes.ReadLocalNode, nodes.SelectionNode)), - node.unique_nodes(), - ) - ): - return None - result = node.bottom_up(merge_scan) - if isinstance(result, nodes.ReadLocalNode): - return result, limit - return None - - -@functools.singledispatch -def merge_scan(node: nodes.BigFrameNode) -> nodes.BigFrameNode: - return node - - -@merge_scan.register -def _(node: nodes.SelectionNode) -> nodes.BigFrameNode: - if not isinstance(node.child, (nodes.ReadTableNode, nodes.ReadLocalNode)): - return node - if node.has_multi_referenced_ids: - return node - if isinstance(node, nodes.ReadLocalNode) and node.offsets_col is not None: - return node - selection = { - aliased_ref.ref.id: aliased_ref.id for aliased_ref in node.input_output_pairs - } - new_scan_list = node.child.scan_list.project(selection) - return dataclasses.replace(node.child, scan_list=new_scan_list) diff --git a/bigframes/core/rewrite/schema_binding.py b/bigframes/core/rewrite/schema_binding.py deleted file mode 100644 index 14755d34a41..00000000000 --- a/bigframes/core/rewrite/schema_binding.py +++ /dev/null @@ -1,157 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import dataclasses -import typing - -from bigframes.core import agg_expressions, bigframe_node, nodes, ordering -from bigframes.core import expression as ex - - -def bind_schema_to_tree( - node: bigframe_node.BigFrameNode, -) -> bigframe_node.BigFrameNode: - return nodes.bottom_up(node, bind_schema_to_node) - - -def bind_schema_to_node( - node: bigframe_node.BigFrameNode, -) -> bigframe_node.BigFrameNode: - if isinstance(node, nodes.ProjectionNode): - bound_assignments = tuple( - (ex.bind_schema_fields(expr, node.child.field_by_id), id) - for expr, id in node.assignments - ) - return dataclasses.replace(node, assignments=bound_assignments) - - if isinstance(node, nodes.FilterNode): - bound_predicate = ex.bind_schema_fields(node.predicate, node.child.field_by_id) - return dataclasses.replace(node, predicate=bound_predicate) - - if isinstance(node, nodes.OrderByNode): - bound_bys = [] - for by in node.by: - bound_by = dataclasses.replace( - by, - scalar_expression=ex.bind_schema_fields( - by.scalar_expression, node.child.field_by_id - ), - ) - bound_bys.append(bound_by) - - return dataclasses.replace(node, by=tuple(bound_bys)) - - if isinstance(node, nodes.JoinNode): - conditions = tuple( - ( - ex.ResolvedDerefOp.from_field(node.left_child.field_by_id[left.id]), - ex.ResolvedDerefOp.from_field(node.right_child.field_by_id[right.id]), - ) - for left, right in node.conditions - ) - return dataclasses.replace( - node, - conditions=conditions, - ) - if isinstance(node, nodes.InNode): - return dataclasses.replace( - node, - left_col=ex.ResolvedDerefOp.from_field( - node.left_child.field_by_id[node.left_col.id] - ), - ) - - if isinstance(node, nodes.AggregateNode): - aggregations = [] - for aggregation, id in node.aggregations: - aggregations.append( - (_bind_schema_to_aggregation_expr(aggregation, node.child), id) - ) - - return dataclasses.replace( - node, - aggregations=tuple(aggregations), - ) - - if isinstance(node, nodes.WindowOpNode): - window_spec = dataclasses.replace( - node.window_spec, - grouping_keys=tuple( - typing.cast( - ex.DerefOp, ex.bind_schema_fields(expr, node.child.field_by_id) - ) - for expr in node.window_spec.grouping_keys - ), - ordering=tuple( - ordering.OrderingExpression( - scalar_expression=ex.bind_schema_fields( - expr.scalar_expression, node.child.field_by_id - ), - direction=expr.direction, - na_last=expr.na_last, - ) - for expr in node.window_spec.ordering - ), - ) - return dataclasses.replace( - node, - agg_exprs=tuple( - nodes.ColumnDef( - _bind_schema_to_aggregation_expr(cdef.expression, node.child), # type: ignore - cdef.id, - ) - for cdef in node.agg_exprs - ), - window_spec=window_spec, - ) - - return node - - -def _bind_schema_to_aggregation_expr( - aggregation: agg_expressions.Aggregation, - child: bigframe_node.BigFrameNode, -) -> agg_expressions.Aggregation: - assert isinstance(aggregation, agg_expressions.Aggregation), ( - f"Expected Aggregation, got {type(aggregation)}" - ) - - if isinstance(aggregation, agg_expressions.UnaryAggregation): - return typing.cast( - agg_expressions.Aggregation, - dataclasses.replace( - aggregation, - arg=typing.cast( - ex.RefOrConstant, - ex.bind_schema_fields(aggregation.arg, child.field_by_id), - ), - ), - ) - elif isinstance(aggregation, agg_expressions.BinaryAggregation): - return typing.cast( - agg_expressions.Aggregation, - dataclasses.replace( - aggregation, - left=typing.cast( - ex.RefOrConstant, - ex.bind_schema_fields(aggregation.left, child.field_by_id), - ), - right=typing.cast( - ex.RefOrConstant, - ex.bind_schema_fields(aggregation.right, child.field_by_id), - ), - ), - ) - else: - return aggregation diff --git a/bigframes/core/rewrite/select_pullup.py b/bigframes/core/rewrite/select_pullup.py deleted file mode 100644 index a15aba7663f..00000000000 --- a/bigframes/core/rewrite/select_pullup.py +++ /dev/null @@ -1,177 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import dataclasses -import functools -from typing import cast - -from bigframes.core import expression, identifiers, 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, functools.partial(pull_up_select, prefer_source_names=True) - ) - - -def pull_up_select( - node: nodes.BigFrameNode, prefer_source_names: bool -) -> nodes.BigFrameNode: - if isinstance(node, nodes.LeafNode): - if prefer_source_names and isinstance(node, nodes.ReadTableNode): - return pull_up_source_ids(node) - else: - 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_source_ids(node: nodes.ReadTableNode) -> nodes.BigFrameNode: - if all(id.sql == source_id for id, source_id in node.scan_list.items): - return node - else: - new_scan_list = nodes.ScanList.from_items( - [ - nodes.ScanItem( - identifiers.ColumnId(scan_item.source_id), scan_item.source_id - ) - for scan_item in node.scan_list.items - ] - ) - new_source = dataclasses.replace(node, scan_list=new_scan_list) - new_selection = nodes.SelectionNode( - new_source, - tuple( - nodes.AliasedRef( - expression.DerefOp(identifiers.ColumnId(source_id)), id - ) - for id, source_id in node.scan_list.items - ), - ) - return new_selection - - -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/rewrite/slices.py b/bigframes/core/rewrite/slices.py deleted file mode 100644 index bed3a8a3f3d..00000000000 --- a/bigframes/core/rewrite/slices.py +++ /dev/null @@ -1,239 +0,0 @@ -# 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. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -from __future__ import annotations - -import dataclasses -import functools -from typing import Optional, Sequence, Tuple - -import bigframes.core.expression as scalar_exprs -import bigframes.core.guid as guids -import bigframes.core.identifiers as ids -import bigframes.core.nodes as nodes -import bigframes.core.slices as slices -import bigframes.operations as ops - - -def pull_up_limits(root: nodes.ResultNode) -> nodes.ResultNode: - new_child, pulled_limit = pull_out_limit(root.child) - if new_child == root.child: - return root - elif pulled_limit is None: - return dataclasses.replace(root, child=new_child) - else: - # new child has redundant slice ops removed now - new_limit = min(pulled_limit, root.limit) if root.limit else pulled_limit - return dataclasses.replace(root, child=new_child, limit=new_limit) - - -def pull_out_limit( - root: nodes.BigFrameNode, -) -> Tuple[nodes.BigFrameNode, Optional[int]]: - """ - This is a BQ-sql specific optimization that can be helpful as ORDER BY LIMIT is more efficient than WHERE + ROW_NUMBER(). - - Only use this if writing to an unclustered table. Clustering is not compatible with ORDER BY. - """ - if isinstance(root, nodes.SliceNode): - # head case - # More cases could be handled, but this is by far the most important, as it is used by df.head(), df[:N] - if root.is_limit: - assert not root.start - assert root.step == 1 - assert root.stop is not None - limit = root.stop - new_root, prior_limit = pull_out_limit(root.child) - if (prior_limit is not None) and (prior_limit < limit): - limit = prior_limit - return new_root, limit - if root.is_noop: - new_root, prior_limit = pull_out_limit(root.child) - return new_root, prior_limit - elif ( - isinstance(root, (nodes.SelectionNode, nodes.ProjectionNode)) - and root.row_preserving - ): - new_child, prior_limit = pull_out_limit(root.child) - if prior_limit is not None: - return root.transform_children(lambda _: new_child), prior_limit - # Most ops don't support pulling up slice, like filter, agg, join, etc. - return root, None - - -def rewrite_slice(node: nodes.BigFrameNode): - if not isinstance(node, nodes.SliceNode): - return node - - slice_def = (node.start, node.stop, node.step) - # no-op (eg. df[::1]) - if slices.is_noop(slice_def, node.child.row_count): - return node.child - - # No filtering, just reverse (eg. df[::-1]) - if slices.is_reverse(slice_def, node.child.row_count): - return nodes.ReversedNode(node.child) - - if node.child.row_count: - slice_def = slices.to_forward_offsets(slice_def, node.child.row_count) - return slice_as_filter(node.child, *slice_def) - - -def slice_as_filter( - node: nodes.BigFrameNode, start: Optional[int], stop: Optional[int], step: int -) -> nodes.BigFrameNode: - if ( - ((start is None) or (start >= 0)) - and ((stop is None) or (stop >= 0)) - and (step > 0) - ): - node_w_offset = add_offsets(node) - predicate = convert_simple_slice( - scalar_exprs.DerefOp(node_w_offset.col_id), start or 0, stop, step - ) - filtered = nodes.FilterNode(node_w_offset, predicate) - return drop_cols(filtered, (node_w_offset.col_id,)) - - # fallback cases, generate both forward and backward offsets - if step < 0: - forward_offsets = add_offsets(node) - reversed_offsets = add_offsets(nodes.ReversedNode(forward_offsets)) - dual_indexed = reversed_offsets - else: - reversed_offsets = add_offsets(nodes.ReversedNode(node)) - forward_offsets = add_offsets(nodes.ReversedNode(reversed_offsets)) - dual_indexed = forward_offsets - default_start = 0 if step >= 0 else -1 - predicate = convert_complex_slice( - scalar_exprs.DerefOp(forward_offsets.col_id), - scalar_exprs.DerefOp(reversed_offsets.col_id), - start if (start is not None) else default_start, - stop, - step, - ) - filtered = nodes.FilterNode(dual_indexed, predicate) - return drop_cols(filtered, (forward_offsets.col_id, reversed_offsets.col_id)) - - -def add_offsets(node: nodes.BigFrameNode) -> nodes.PromoteOffsetsNode: - # Allow providing custom id generator? - offsets_id = ids.ColumnId(guids.generate_guid()) - return nodes.PromoteOffsetsNode(node, offsets_id) - - -def drop_cols( - node: nodes.BigFrameNode, drop_cols: Tuple[ids.ColumnId, ...] -) -> nodes.SelectionNode: - # adding a whole node that redefines the schema is a lot of overhead, should do something more efficient - selections = tuple( - nodes.AliasedRef(scalar_exprs.DerefOp(id), id) - for id in node.ids - if id not in drop_cols - ) - return nodes.SelectionNode(node, selections) - - -def convert_simple_slice( - offsets: scalar_exprs.Expression, - start: int = 0, - stop: Optional[int] = None, - step: int = 1, -) -> scalar_exprs.Expression: - """Performs slice but only for positive step size.""" - assert start >= 0 - assert (stop is None) or (stop >= 0) - - conditions = [] - if start > 0: - conditions.append(ops.ge_op.as_expr(offsets, scalar_exprs.const(start))) - if (stop is not None) and (stop >= 0): - conditions.append(ops.lt_op.as_expr(offsets, scalar_exprs.const(stop))) - if step > 1: - start_diff = ops.sub_op.as_expr(offsets, scalar_exprs.const(start)) - step_cond = ops.eq_op.as_expr( - ops.mod_op.as_expr(start_diff, scalar_exprs.const(step)), - scalar_exprs.const(0), - ) - conditions.append(step_cond) - - return merge_predicates(conditions) or scalar_exprs.const(True) - - -def convert_complex_slice( - forward_offsets: scalar_exprs.Expression, - reverse_offsets: scalar_exprs.Expression, - start: int, - stop: Optional[int], - step: int = 1, -) -> scalar_exprs.Expression: - conditions = [] - assert step != 0 - if start or ((start is not None) and step < 0): - if start > 0 and step > 0: - start_cond = ops.ge_op.as_expr(forward_offsets, scalar_exprs.const(start)) - elif start >= 0 and step < 0: - start_cond = ops.le_op.as_expr(forward_offsets, scalar_exprs.const(start)) - elif start < 0 and step > 0: - start_cond = ops.le_op.as_expr( - reverse_offsets, scalar_exprs.const(-start - 1) - ) - else: - assert start < 0 and step < 0 - start_cond = ops.ge_op.as_expr( - reverse_offsets, scalar_exprs.const(-start - 1) - ) - conditions.append(start_cond) - if stop is not None: - if stop >= 0 and step > 0: - stop_cond = ops.lt_op.as_expr(forward_offsets, scalar_exprs.const(stop)) - elif stop >= 0 and step < 0: - stop_cond = ops.gt_op.as_expr(forward_offsets, scalar_exprs.const(stop)) - elif stop < 0 and step > 0: - stop_cond = ops.gt_op.as_expr( - reverse_offsets, scalar_exprs.const(-stop - 1) - ) - else: - assert (stop < 0) and (step < 0) - stop_cond = ops.lt_op.as_expr( - reverse_offsets, scalar_exprs.const(-stop - 1) - ) - conditions.append(stop_cond) - if step != 1: - if step > 1 and start >= 0: - start_diff = ops.sub_op.as_expr(forward_offsets, scalar_exprs.const(start)) - elif step > 1 and start < 0: - start_diff = ops.sub_op.as_expr( - reverse_offsets, scalar_exprs.const(-start + 1) - ) - elif step < 0 and start >= 0: - start_diff = ops.add_op.as_expr(forward_offsets, scalar_exprs.const(start)) - else: - assert step < 0 and start < 0 - start_diff = ops.add_op.as_expr( - reverse_offsets, scalar_exprs.const(-start + 1) - ) - step_cond = ops.eq_op.as_expr( - ops.mod_op.as_expr(start_diff, scalar_exprs.const(step)), - scalar_exprs.const(0), - ) - conditions.append(step_cond) - return merge_predicates(conditions) or scalar_exprs.const(True) - - -def merge_predicates( - predicates: Sequence[scalar_exprs.Expression], -) -> Optional[scalar_exprs.Expression]: - if len(predicates) == 0: - return None - - return functools.reduce(ops.and_op.as_expr, predicates) diff --git a/bigframes/core/rewrite/timedeltas.py b/bigframes/core/rewrite/timedeltas.py deleted file mode 100644 index 7544963732e..00000000000 --- a/bigframes/core/rewrite/timedeltas.py +++ /dev/null @@ -1,266 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from __future__ import annotations - -import dataclasses -import functools -import typing - -from bigframes import dtypes -from bigframes import operations as ops -from bigframes.core import agg_expressions as ex_types -from bigframes.core import expression as ex -from bigframes.core import nodes, schema, utils -from bigframes.operations import aggregations as aggs - - -@dataclasses.dataclass -class _TypedExpr: - expr: ex.Expression - dtype: dtypes.Dtype - - @classmethod - def create_op_expr( - cls, op: typing.Union[ops.ScalarOp, ops.RowOp], *inputs: _TypedExpr - ) -> _TypedExpr: - expr = op.as_expr(*tuple(x.expr for x in inputs)) # type: ignore - dtype = op.output_type(*tuple(x.dtype for x in inputs)) - return cls(expr, dtype) - - -def rewrite_timedelta_expressions(root: nodes.BigFrameNode) -> nodes.BigFrameNode: - """ - Rewrites expressions to properly handle timedelta values, because this type does not exist - in the SQL world. - """ - if isinstance(root, nodes.ProjectionNode): - updated_assignments = tuple( - (_rewrite_expressions(expr, root.schema).expr, column_id) - for expr, column_id in root.assignments - ) - return nodes.ProjectionNode(root.child, updated_assignments) - - if isinstance(root, nodes.FilterNode): - return nodes.FilterNode( - root.child, _rewrite_expressions(root.predicate, root.schema).expr - ) - - if isinstance(root, nodes.OrderByNode): - by = tuple(_rewrite_ordering_expr(x, root.schema) for x in root.by) - return nodes.OrderByNode(root.child, by) - - if isinstance(root, nodes.WindowOpNode): - return nodes.WindowOpNode( - root.child, - tuple( - nodes.ColumnDef( - _rewrite_aggregation(cdef.expression, root.schema), cdef.id - ) - for cdef in root.agg_exprs - ), - root.window_spec, - ) - - if isinstance(root, nodes.AggregateNode): - updated_aggregations = tuple( - (_rewrite_aggregation(agg, root.child.schema), col_id) - for agg, col_id in root.aggregations - ) - return nodes.AggregateNode( - root.child, - updated_aggregations, - root.by_column_ids, - root.order_by, - root.dropna, - ) - - return root - - -def _rewrite_ordering_expr( - expr: nodes.OrderingExpression, schema: schema.ArraySchema -) -> nodes.OrderingExpression: - by = _rewrite_expressions(expr.scalar_expression, schema).expr - return nodes.OrderingExpression(by, expr.direction, expr.na_last) - - -@functools.cache -def _rewrite_expressions(expr: ex.Expression, schema: schema.ArraySchema) -> _TypedExpr: - if isinstance(expr, ex.DerefOp): - return _TypedExpr(expr, schema.get_type(expr.id.sql)) - - if isinstance(expr, ex.ScalarConstantExpression): - return _rewrite_scalar_constant_expr(expr) - - if isinstance(expr, ex.OpExpression): - updated_inputs = tuple( - map(lambda x: _rewrite_expressions(x, schema), expr.inputs) - ) - return _rewrite_op_expr(expr, updated_inputs) - - raise AssertionError(f"Unexpected expression type: {type(expr)}") - - -def _rewrite_scalar_constant_expr(expr: ex.ScalarConstantExpression) -> _TypedExpr: - if expr.value is None: - return _TypedExpr(ex.const(None, expr.dtype), expr.dtype) - if expr.dtype == dtypes.TIMEDELTA_DTYPE: - int_repr = utils.timedelta_to_micros(expr.value) # type: ignore - return _TypedExpr(ex.const(int_repr, expr.dtype), expr.dtype) - - return _TypedExpr(expr, expr.dtype) - - -def _rewrite_op_expr( - expr: ex.OpExpression, inputs: typing.Tuple[_TypedExpr, ...] -) -> _TypedExpr: - if isinstance(expr.op, ops.SubOp): - return _rewrite_sub_op(inputs[0], inputs[1]) - - if isinstance(expr.op, ops.AddOp): - return _rewrite_add_op(inputs[0], inputs[1]) - - if isinstance(expr.op, ops.MulOp): - return _rewrite_mul_op(inputs[0], inputs[1]) - - if isinstance(expr.op, ops.DivOp): - return _rewrite_div_op(inputs[0], inputs[1]) - - if isinstance(expr.op, ops.FloorDivOp): - # We need to re-write floor div because for numerics: int // float => float - # but for timedeltas: int(timedelta) // float => int(timedelta) - return _rewrite_floordiv_op(inputs[0], inputs[1]) - - if isinstance(expr.op, ops.ToTimedeltaOp): - return _rewrite_to_timedelta_op(expr.op, inputs[0]) - - return _TypedExpr.create_op_expr(expr.op, *inputs) - - -def _rewrite_sub_op(left: _TypedExpr, right: _TypedExpr) -> _TypedExpr: - if dtypes.is_datetime_like(left.dtype) and dtypes.is_datetime_like(right.dtype): - return _TypedExpr.create_op_expr(ops.timestamp_diff_op, left, right) - - if dtypes.is_datetime_like(left.dtype) and right.dtype == dtypes.TIMEDELTA_DTYPE: - return _TypedExpr.create_op_expr(ops.timestamp_sub_op, left, right) - - if left.dtype == dtypes.DATE_DTYPE and right.dtype == dtypes.DATE_DTYPE: - return _TypedExpr.create_op_expr(ops.date_diff_op, left, right) - - if left.dtype == dtypes.DATE_DTYPE and right.dtype == dtypes.TIMEDELTA_DTYPE: - return _TypedExpr.create_op_expr(ops.date_sub_op, left, right) - - return _TypedExpr.create_op_expr(ops.sub_op, left, right) - - -def _rewrite_add_op(left: _TypedExpr, right: _TypedExpr) -> _TypedExpr: - if dtypes.is_datetime_like(left.dtype) and right.dtype == dtypes.TIMEDELTA_DTYPE: - return _TypedExpr.create_op_expr(ops.timestamp_add_op, left, right) - - if left.dtype == dtypes.TIMEDELTA_DTYPE and dtypes.is_datetime_like(right.dtype): - # Re-arrange operands such that timestamp is always on the left and timedelta is - # always on the right. - return _TypedExpr.create_op_expr(ops.timestamp_add_op, right, left) - - if left.dtype == dtypes.DATE_DTYPE and right.dtype == dtypes.TIMEDELTA_DTYPE: - return _TypedExpr.create_op_expr(ops.date_add_op, left, right) - - if left.dtype == dtypes.TIMEDELTA_DTYPE and right.dtype == dtypes.DATE_DTYPE: - # Re-arrange operands such that date is always on the left and timedelta is - # always on the right. - return _TypedExpr.create_op_expr(ops.date_add_op, right, left) - - return _TypedExpr.create_op_expr(ops.add_op, left, right) - - -def _rewrite_mul_op(left: _TypedExpr, right: _TypedExpr) -> _TypedExpr: - result = _TypedExpr.create_op_expr(ops.mul_op, left, right) - - if left.dtype == dtypes.TIMEDELTA_DTYPE and dtypes.is_numeric(right.dtype): - return _TypedExpr.create_op_expr(ops.timedelta_floor_op, result) - if dtypes.is_numeric(left.dtype) and right.dtype == dtypes.TIMEDELTA_DTYPE: - return _TypedExpr.create_op_expr(ops.timedelta_floor_op, result) - - return result - - -def _rewrite_div_op(left: _TypedExpr, right: _TypedExpr) -> _TypedExpr: - result = _TypedExpr.create_op_expr(ops.div_op, left, right) - - if left.dtype == dtypes.TIMEDELTA_DTYPE and dtypes.is_numeric(right.dtype): - return _TypedExpr.create_op_expr(ops.timedelta_floor_op, result) - - return result - - -def _rewrite_floordiv_op(left: _TypedExpr, right: _TypedExpr) -> _TypedExpr: - if left.dtype == dtypes.TIMEDELTA_DTYPE and dtypes.is_numeric(right.dtype): - return _TypedExpr.create_op_expr( - ops.timedelta_floor_op, _TypedExpr.create_op_expr(ops.div_op, left, right) - ) - - return _TypedExpr.create_op_expr(ops.floordiv_op, left, right) - - -def _rewrite_to_timedelta_op(op: ops.ToTimedeltaOp, arg: _TypedExpr): - if arg.dtype == dtypes.TIMEDELTA_DTYPE: - # Do nothing for values that are already timedeltas - return arg - - return _TypedExpr.create_op_expr(op, arg) - - -@functools.cache -def _rewrite_aggregation( - aggregation: ex_types.Aggregation, schema: schema.ArraySchema -) -> ex_types.Aggregation: - if not isinstance(aggregation, ex_types.UnaryAggregation): - return aggregation - - if isinstance(aggregation.arg, ex.DerefOp): - input_type = schema.get_type(aggregation.arg.id.sql) - else: - input_type = aggregation.arg.output_type - - if isinstance(aggregation.op, aggs.DiffOp): - if dtypes.is_datetime_like(input_type): - return ex_types.UnaryAggregation( - aggs.TimeSeriesDiffOp(aggregation.op.periods), aggregation.arg - ) - elif input_type == dtypes.DATE_DTYPE: - return ex_types.UnaryAggregation( - aggs.DateSeriesDiffOp(aggregation.op.periods), aggregation.arg - ) - - if isinstance(aggregation.op, aggs.StdOp) and input_type == dtypes.TIMEDELTA_DTYPE: - return ex_types.UnaryAggregation( - aggs.StdOp(should_floor_result=True), aggregation.arg - ) - - if isinstance(aggregation.op, aggs.MeanOp) and input_type == dtypes.TIMEDELTA_DTYPE: - return ex_types.UnaryAggregation( - aggs.MeanOp(should_floor_result=True), aggregation.arg - ) - - if ( - isinstance(aggregation.op, aggs.QuantileOp) - and input_type == dtypes.TIMEDELTA_DTYPE - ): - return ex_types.UnaryAggregation( - aggs.QuantileOp(q=aggregation.op.q, should_floor_result=True), - aggregation.arg, - ) - - return aggregation diff --git a/bigframes/core/rewrite/udfs.py b/bigframes/core/rewrite/udfs.py deleted file mode 100644 index 286a9d9d940..00000000000 --- a/bigframes/core/rewrite/udfs.py +++ /dev/null @@ -1,46 +0,0 @@ -# Copyright 2026 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -from __future__ import annotations - -import dataclasses - -import bigframes.functions.udf_def as udf_def -import bigframes.operations as ops -from bigframes.core import bigframe_node, expression -from bigframes.core.rewrite import op_lowering - - -@dataclasses.dataclass -class LowerRemoteFunctionRule(op_lowering.OpLoweringRule): - @property - def op(self) -> type[ops.ScalarOp]: - return ops.RemoteFunctionOp - - def lower(self, expr: expression.OpExpression) -> expression.Expression: - assert isinstance(expr.op, ops.RemoteFunctionOp) - func_def = expr.op.function_def - devirtualized_expr = ops.RemoteFunctionOp( - func_def.with_devirtualize(), - ).as_expr(*expr.children) - if isinstance(func_def.signature.output, udf_def.VirtualListTypeV1): - return func_def.signature.output.out_expr(devirtualized_expr) - else: - return devirtualized_expr - - -UDF_LOWERING_RULES = (LowerRemoteFunctionRule(),) - - -def lower_udfs(root: bigframe_node.BigFrameNode) -> bigframe_node.BigFrameNode: - return op_lowering.lower_ops(root, rules=UDF_LOWERING_RULES) diff --git a/bigframes/core/rewrite/windows.py b/bigframes/core/rewrite/windows.py deleted file mode 100644 index 4d271a072d4..00000000000 --- a/bigframes/core/rewrite/windows.py +++ /dev/null @@ -1,139 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from __future__ import annotations - -import dataclasses -import functools -import itertools - -import bigframes.dtypes -from bigframes import operations as ops -from bigframes.core import ( - agg_expressions, - expression, - guid, - identifiers, - nodes, - ordering, -) -from bigframes.operations import aggregations as agg_ops - - -def simplify_complex_windows( - window_expr: agg_expressions.WindowExpression, -) -> expression.Expression: - result_expr: expression.Expression = window_expr - agg_expr = window_expr.analytic_expr - window_spec = window_expr.window - clauses: list[tuple[expression.Expression, expression.Expression]] = [] - if window_spec.min_periods and len(agg_expr.inputs) > 0: - if not agg_expr.op.nulls_count_for_min_values: - is_observation = ops.notnull_op.as_expr() - - # Most operations do not count NULL values towards min_periods - per_col_does_count = ( - ops.notnull_op.as_expr(input) for input in agg_expr.inputs - ) - # All inputs must be non-null for observation to count - is_observation = functools.reduce( - lambda x, y: ops.and_op.as_expr(x, y), per_col_does_count - ) - observation_sentinel = ops.AsTypeOp(bigframes.dtypes.INT_DTYPE).as_expr( - is_observation - ) - observation_count_expr = agg_expressions.WindowExpression( - agg_expressions.UnaryAggregation(agg_ops.sum_op, observation_sentinel), - window_spec, - ) - else: - # Operations like count treat even NULLs as valid observations for the sake of min_periods - # notnull is just used to convert null values to non-null (FALSE) values to be counted - is_observation = ops.notnull_op.as_expr(agg_expr.inputs[0]) - observation_count_expr = agg_expressions.WindowExpression( - agg_ops.count_op.as_expr(is_observation), - window_spec, - ) - clauses.append( - ( - ops.lt_op.as_expr( - observation_count_expr, expression.const(window_spec.min_periods) - ), - expression.const(None), - ) - ) - if clauses: - case_inputs = [ - *itertools.chain.from_iterable(clauses), - expression.const(True), - result_expr, - ] - result_expr = ops.CaseWhenOp().as_expr(*case_inputs) - return result_expr - - -def rewrite_range_rolling(node: nodes.BigFrameNode) -> nodes.BigFrameNode: - if not isinstance(node, nodes.WindowOpNode): - return node - - if not node.window_spec.is_range_bounded: - return node - - if len(node.window_spec.ordering) != 1: - raise ValueError( - "Range rolling should only be performed on exactly one column." - ) - - ordering_expr = node.window_spec.ordering[0] - - new_ordering = dataclasses.replace( - ordering_expr, - scalar_expression=ops.UnixMicros().as_expr(ordering_expr.scalar_expression), - ) - - return dataclasses.replace( - node, - window_spec=dataclasses.replace(node.window_spec, ordering=(new_ordering,)), - ) - - -def pull_out_window_order(root: nodes.BigFrameNode) -> nodes.BigFrameNode: - return root.bottom_up(rewrite_window_node) - - -def rewrite_window_node(node: nodes.BigFrameNode) -> nodes.BigFrameNode: - if not isinstance(node, nodes.WindowOpNode): - return node - if len(node.window_spec.ordering) == 0: - return node - else: - offsets_id = guid.generate_guid() - w_offsets = nodes.PromoteOffsetsNode( - node.child, identifiers.ColumnId(offsets_id) - ) - sorted_child = nodes.OrderByNode(w_offsets, node.window_spec.ordering) - new_window_node = dataclasses.replace( - node, - child=sorted_child, - window_spec=node.window_spec.without_order(force=True), - ) - w_resetted_order = nodes.OrderByNode( - new_window_node, - by=(ordering.ascending_over(identifiers.ColumnId(offsets_id)),), - is_total_order=True, - ) - w_offsets_dropped = nodes.SelectionNode( - w_resetted_order, tuple(nodes.AliasedRef.identity(id) for id in node.ids) - ) - return w_offsets_dropped diff --git a/bigframes/core/schema.py b/bigframes/core/schema.py deleted file mode 100644 index ab30b9bff14..00000000000 --- a/bigframes/core/schema.py +++ /dev/null @@ -1,136 +0,0 @@ -# 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. -# 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 functools -import typing -from dataclasses import dataclass -from typing import Dict, Optional, Sequence - -import google.cloud.bigquery -import pyarrow - -import bigframes.dtypes - -ColumnIdentifierType = str - - -@dataclass(frozen=True) -class SchemaItem: - column: ColumnIdentifierType - dtype: bigframes.dtypes.Dtype - - -@dataclass(frozen=True) -class ArraySchema: - items: tuple[SchemaItem, ...] - - def __iter__(self): - yield from self.items - - @classmethod - def from_bq_schema( - cls, - schema: Sequence[google.cloud.bigquery.SchemaField], - column_type_overrides: Optional[Dict[str, bigframes.dtypes.Dtype]] = None, - columns: Optional[Sequence[str]] = None, - ): - if columns: - lookup = {field.name: field for field in schema} - schema = [lookup[col] for col in columns] - if column_type_overrides is None: - column_type_overrides = {} - items = tuple( - SchemaItem(name, column_type_overrides.get(name, dtype)) - for name, dtype in bigframes.dtypes.bf_type_from_type_kind(schema).items() - ) - return ArraySchema(items) - - @property - def names(self) -> typing.Tuple[str, ...]: - return tuple(item.column for item in self.items) - - @property - def dtypes(self) -> typing.Tuple[bigframes.dtypes.Dtype, ...]: - return tuple(item.dtype for item in self.items) - - @functools.cached_property - def _mapping(self) -> typing.Dict[ColumnIdentifierType, bigframes.dtypes.Dtype]: - return {item.column: item.dtype for item in self.items} - - def to_bigquery( - self, overrides: dict[bigframes.dtypes.Dtype, str] = {} - ) -> typing.Tuple[google.cloud.bigquery.SchemaField, ...]: - return tuple( - bigframes.dtypes.convert_to_schema_field( - item.column, item.dtype, overrides=overrides - ) - for item in self.items - ) - - def to_pyarrow(self, use_storage_types: bool = False) -> pyarrow.Schema: - fields = [] - for item in self.items: - pa_type = bigframes.dtypes.bigframes_dtype_to_arrow_dtype(item.dtype) - if use_storage_types: - pa_type = bigframes.dtypes.to_storage_type(pa_type) - fields.append( - pyarrow.field( - item.column, - type=pa_type, - nullable=not pyarrow.types.is_list(pa_type), - ) - ) - return pyarrow.schema(fields) - - def drop(self, columns: typing.Iterable[str]) -> ArraySchema: - return ArraySchema( - tuple(item for item in self.items if item.column not in columns) - ) - - def select(self, columns: typing.Iterable[str]) -> ArraySchema: - return ArraySchema( - tuple(SchemaItem(name, self.get_type(name)) for name in columns) - ) - - def rename(self, mapping: typing.Mapping[str, str]) -> ArraySchema: - return ArraySchema( - tuple( - SchemaItem(mapping.get(item.column, item.column), item.dtype) - for item in self.items - ) - ) - - def append(self, item: SchemaItem): - return ArraySchema(tuple([*self.items, item])) - - def prepend(self, item: SchemaItem): - return ArraySchema(tuple([item, *self.items])) - - def update_dtype( - self, id: ColumnIdentifierType, dtype: bigframes.dtypes.Dtype - ) -> ArraySchema: - return ArraySchema( - tuple( - SchemaItem(id, dtype) if item.column == id else item - for item in self.items - ) - ) - - def get_type(self, id: ColumnIdentifierType): - return self._mapping[id] - - def __len__(self) -> int: - return len(self.items) diff --git a/bigframes/core/sentinels.py b/bigframes/core/sentinels.py deleted file mode 100644 index ff9913f7c6f..00000000000 --- a/bigframes/core/sentinels.py +++ /dev/null @@ -1,31 +0,0 @@ -# Copyright 2026 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. - -"""Sentinel values used throughout BigFrames.""" - -from __future__ import annotations - -import enum - - -class Sentinel(enum.Enum): - """Default values used throughout BigFrames.""" - - """Default value for an optional argument. - - When a parameter is set to this, that parameter is explicitly omitted - from the SQL text. This allows for NULL (None in Python) to be explicitly - passed in to optional parameters. - """ - ARGUMENT_DEFAULT = enum.auto() diff --git a/bigframes/core/sequences.py b/bigframes/core/sequences.py deleted file mode 100644 index 6f1b7e455b0..00000000000 --- a/bigframes/core/sequences.py +++ /dev/null @@ -1,105 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from __future__ import annotations - -import collections.abc -import functools -import itertools -from typing import Iterable, Iterator, Sequence, TypeVar - -ColumnIdentifierType = str - - -T = TypeVar("T") - -# Further optimizations possible: -# * Support mapping operators -# * Support insertions and deletions - - -class ChainedSequence(collections.abc.Sequence[T]): - """ - Memory-optimized sequence from composing chain of existing sequences. - - Will use the provided parts as underlying storage - so do not mutate provided parts. - May merge small underlying parts for better access performance. - """ - - def __init__(self, *parts: Sequence[T]): - # Could build an index that makes random access faster? - self._parts: tuple[Sequence[T], ...] = tuple( - _defrag_parts(_flatten_parts(parts)) - ) - - def __getitem__(self, index): - if isinstance(index, slice): - return tuple(self)[index] - if index < 0: - index = len(self) + index - if index < 0: - raise IndexError("Index out of bounds") - - offset = 0 - for part in self._parts: - if (index - offset) < len(part): - return part[index - offset] - offset += len(part) - raise IndexError("Index out of bounds") - - @functools.cache - def __len__(self): - return sum(map(len, self._parts)) - - def __iter__(self): - for part in self._parts: - yield from part - - -def _flatten_parts(parts: Iterable[Sequence[T]]) -> Iterator[Sequence[T]]: - for part in parts: - if isinstance(part, ChainedSequence): - yield from part._parts - else: - yield part - - -# Should be a cache-friendly chunk size? -_TARGET_SIZE = 128 -_MAX_MERGABLE = 32 - - -def _defrag_parts(parts: Iterable[Sequence[T]]) -> Iterator[Sequence[T]]: - """ - Merge small chunks into larger chunks for better performance. - """ - parts_queue: list[Sequence[T]] = [] - queued_items = 0 - for part in parts: - # too big, just yield from the buffer - if len(part) > _MAX_MERGABLE: - yield from parts_queue - parts_queue = [] - queued_items = 0 - yield part - else: # can be merged, so lets add to the queue - parts_queue.append(part) - queued_items += len(part) - # if queue has reached target size, merge, dump and reset queue - if queued_items >= _TARGET_SIZE: - yield tuple(itertools.chain(*parts_queue)) - parts_queue = [] - queued_items = 0 - - yield from parts_queue diff --git a/bigframes/core/slices.py b/bigframes/core/slices.py deleted file mode 100644 index 68ec79f9fb6..00000000000 --- a/bigframes/core/slices.py +++ /dev/null @@ -1,110 +0,0 @@ -# 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. -# 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 typing import Optional - - -def to_forward_offsets( - slice: tuple[Optional[int], Optional[int], Optional[int]], input_rows: int -) -> tuple[int, Optional[int], int]: - """Redefine the slice to use forward offsets for start and stop indices.""" - step = slice[2] or 1 - stop = slice[1] - start = slice[0] - - # normalize start to positive number - if start is None: - start = 0 if (step > 0) else (input_rows - 1) - elif start < 0: - start = max(0, input_rows + start) - else: # start >= 0 - # Clip start to either beginning or end depending on step direction - start = min(start, input_rows - 1) if step < 0 else start - - if stop is None: - stop = None - elif stop < 0: - if step > 0: - stop = max(0, input_rows + stop) - else: - stop = input_rows + stop if (input_rows + stop >= 0) else None - else: - stop = min(stop, input_rows) - - return (start, stop, step) - - -def remove_unused_parts( - slice: tuple[Optional[int], Optional[int], Optional[int]], input_rows: int -) -> tuple[Optional[int], Optional[int], Optional[int]]: - """Makes a slice component null if it doesn't impact slice semantics.""" - start, stop, step = slice - is_forward = (step is None) or (step > 0) - if start is not None: - if is_forward and ((start == 0) or (start <= -input_rows)): - start = None - elif (not is_forward) and ((start == -1) or (start >= (input_rows - 1))): - start = None - if stop is not None: - if is_forward and (stop >= input_rows): - stop = None - elif (not is_forward) and (stop <= (-input_rows - 1)): - stop = None - if step == 1: - step = None - return start, stop, step - - -def slice_output_rows( - slice: tuple[Optional[int], Optional[int], Optional[int]], input_size: int -) -> int: - """Given input_size, returns the number of rows returned after the slice operation.""" - slice = to_forward_offsets(slice, input_size) - start, stop, step = slice - - if step > 0: - if stop is None: - stop = input_size - length = max(0, (stop - start + step - 1) // step) - else: - if stop is None: - stop = -1 - length = max(0, (start - stop - step - 1) // -step) - return length - - -def is_noop( - slice_def: tuple[Optional[int], Optional[int], Optional[int]], - input_size: Optional[int], -) -> bool: - """Returns true iff the slice op is a no-op returning the input array.""" - if input_size: - start, stop, step = remove_unused_parts(slice_def, input_size) - else: - start, stop, step = slice_def - return (not start) and (stop is None) and ((step is None) or (step == 1)) - - -def is_reverse( - slice_def: tuple[Optional[int], Optional[int], Optional[int]], - input_size: Optional[int], -) -> bool: - """Returns true iff the slice op is a pure reverse op, equivalent to df[::-1]""" - if input_size: - start, stop, step = remove_unused_parts(slice_def, input_size) - else: - start, stop, step = slice_def - return (start is None) and (stop is None) and (step == -1) diff --git a/bigframes/core/sql/__init__.py b/bigframes/core/sql/__init__.py deleted file mode 100644 index b28d5921695..00000000000 --- a/bigframes/core/sql/__init__.py +++ /dev/null @@ -1,244 +0,0 @@ -# Copyright 2023 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. - -""" -Utility functions for SQL construction. -""" - -from __future__ import annotations - -import json -from typing import ( - TYPE_CHECKING, - Any, - Collection, - Iterable, - Mapping, - Optional, - Union, - cast, -) - -import bigframes_vendored.sqlglot.expressions as sge - -from bigframes.core.compile.sqlglot import sql - -if TYPE_CHECKING: - import google.cloud.bigquery as bigquery - - import bigframes.core.ordering - - -# shapely.wkt.dumps was moved to shapely.io.to_wkt in 2.0. -try: - from shapely.io import to_wkt # type: ignore -except ImportError: - from shapely.wkt import dumps # type: ignore - - to_wkt = dumps - - -def identifier(name: str) -> str: - if len(name) > 256: - raise ValueError("Identifier must be less than 256 characters") - return f"`{escape_chars(name)}`" - - -def escape_chars(value: str): - """Escapes all special characters""" - # TODO: Reuse literal's escaping logic instead of re-implementing it here. - # https://cloud.google.com/bigquery/docs/reference/standard-sql/lexical#string_and_bytes_literals - trans_table = str.maketrans( - { - "\a": r"\a", - "\b": r"\b", - "\f": r"\f", - "\n": r"\n", - "\r": r"\r", - "\t": r"\t", - "\v": r"\v", - "\\": r"\\", - "?": r"\?", - '"': r"\"", - "'": r"\'", - "`": r"\`", - } - ) - return value.translate(trans_table) - - -def multi_literal(*values: Any): - literal_strings = [sql.to_sql(sql.literal(i)) for i in values] - return "(" + ", ".join(literal_strings) + ")" - - -def cast_as_string(column_name: str) -> str: - """Return a string representing string casting of a column.""" - - return sge.Cast(this=sge.to_identifier(column_name, quoted=True), to="STRING").sql( - dialect="bigquery" - ) - - -def to_json_string(column_name: str) -> str: - """Return a string representing JSON version of a column.""" - - return f"TO_JSON_STRING({sql.to_sql(sql.identifier(column_name))})" - - -def csv(values: Iterable[str]) -> str: - """Return a string of comma separated values.""" - return ", ".join(values) - - -def infix_op(opname: str, left_arg: str, right_arg: str): - # Maybe should add parentheses?? - return f"{left_arg} {opname} {right_arg}" - - -def is_distinct_sql(columns: Iterable[str], table_ref: bigquery.TableReference) -> str: - table_expr = sge.Table( - this=sge.Identifier(this=table_ref.table_id, quoted=True), - db=sge.Identifier(this=table_ref.dataset_id, quoted=True), - catalog=sge.Identifier(this=table_ref.project, quoted=True), - ) - to_select = [sge.to_identifier(col, quoted=True) for col in columns] - - full_table_sql = ( - sge.Select().select(*to_select).from_(table_expr).sql(dialect="bigquery") - ) - distinct_table_sql = ( - sge.Select() - .select(*to_select) - .distinct() - .from_(table_expr) - .sql(dialect="bigquery") - ) - - is_unique_sql = f"""WITH full_table AS ( - {full_table_sql} - ), - distinct_table AS ( - {distinct_table_sql} - ) - - SELECT (SELECT COUNT(*) FROM full_table) AS `total_count`, - (SELECT COUNT(*) FROM distinct_table) AS `distinct_count` - """ - return is_unique_sql - - -def ordering_clause( - ordering: Iterable[bigframes.core.ordering.OrderingExpression], -) -> str: - import bigframes.core.expression - - parts = [] - for col_ref in ordering: - asc_desc = "ASC" if col_ref.direction.is_ascending else "DESC" - null_clause = "NULLS LAST" if col_ref.na_last else "NULLS FIRST" - ordering_expr = col_ref.scalar_expression - # We don't know how to compile scalar expressions in isolation - if ordering_expr.is_const: - # Probably shouldn't have constants in ordering definition, but best to ignore if somehow they end up here. - continue - assert isinstance(ordering_expr, bigframes.core.expression.DerefOp) - part = f"`{ordering_expr.id.sql}` {asc_desc} {null_clause}" - parts.append(part) - return f"ORDER BY {' ,'.join(parts)}" - - -def create_vector_index_ddl( - *, - replace: bool, - index_name: str, - table_name: str, - column_name: str, - stored_column_names: Collection[str], - options: Mapping[str, Union[str | int | bool | float]] = {}, -) -> str: - """Encode the VECTOR INDEX statement for BigQuery Vector Search.""" - - if replace: - create = "CREATE OR REPLACE VECTOR INDEX " - else: - create = "CREATE VECTOR INDEX IF NOT EXISTS " - - if len(stored_column_names) > 0: - escaped_stored = [ - f"{sql.to_sql(sql.identifier(name))}" for name in stored_column_names - ] - storing = f"STORING({', '.join(escaped_stored)}) " - else: - storing = "" - - rendered_options = ", ".join( - [ - f"{option_name} = {sql.to_sql(sql.literal(option_value))}" - for option_name, option_value in options.items() - ] - ) - - return f""" - {create} {sql.to_sql(sql.identifier(index_name))} - ON {sql.to_sql(sql.identifier(table_name))}({sql.to_sql(sql.identifier(column_name))}) - {storing} - OPTIONS({rendered_options}); - """ - - -def create_vector_search_sql( - sql_string: str, - *, - base_table: str, - column_to_search: str, - query_column_to_search: Optional[str] = None, - top_k: Optional[int] = None, - distance_type: Optional[str] = None, - options: Optional[Mapping[str, Union[str | int | bool | float]]] = None, -) -> str: - """Encode the VECTOR SEARCH statement for BigQuery Vector Search.""" - - vector_search_args = [ - f"TABLE {sql.to_sql(sql.identifier(cast(str, base_table)))}", - f"{sql.to_sql(sql.literal(column_to_search))}", - f"({sql_string})", - ] - - if query_column_to_search is not None: - vector_search_args.append( - f"query_column_to_search => {sql.to_sql(sql.literal(query_column_to_search))}" - ) - - if top_k is not None: - vector_search_args.append(f"top_k=> {sql.to_sql(sql.literal(top_k))}") - - if distance_type is not None: - vector_search_args.append( - f"distance_type => {sql.to_sql(sql.literal(distance_type))}" - ) - - if options is not None: - vector_search_args.append( - f"options => {sql.to_sql(sql.literal(json.dumps(options, indent=None)))}" - ) - - args_str = ",\n".join(vector_search_args) - return f""" - SELECT - query.*, - base.*, - distance, - FROM VECTOR_SEARCH({args_str}) - """ diff --git a/bigframes/core/sql/ml.py b/bigframes/core/sql/ml.py deleted file mode 100644 index 8d971e6c3e8..00000000000 --- a/bigframes/core/sql/ml.py +++ /dev/null @@ -1,309 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from __future__ import annotations - -from typing import Any, Dict, List, Mapping, Optional, Union - -import bigframes.core.col as col -from bigframes.core.compile.sqlglot import sql as sg_sql -from bigframes.core.compile.sqlglot.expression_compiler import expression_compiler - - -def create_model_ddl( - model_name: str, - *, - replace: bool = False, - if_not_exists: bool = False, - transform: Optional[list[str]] = None, - input_schema: Optional[Mapping[str, str]] = None, - output_schema: Optional[Mapping[str, str]] = None, - connection_name: Optional[str] = None, - options: Optional[ - Mapping[str, Union[str, int, float, bool, list, "col.Expression"]] - ] = None, - training_data: Optional[str] = None, - custom_holiday: Optional[str] = None, -) -> str: - """Encode the CREATE MODEL statement. - - See https://docs.cloud.google.com/bigquery/docs/reference/standard-sql/bigqueryml-syntax-create for reference. - """ - - if replace: - create = "CREATE OR REPLACE MODEL " - elif if_not_exists: - create = "CREATE MODEL IF NOT EXISTS " - else: - create = "CREATE MODEL " - - ddl = f"{create}{sg_sql.to_sql(sg_sql.identifier(model_name))}\n" - - # [TRANSFORM (select_list)] - if transform: - ddl += f"TRANSFORM ({', '.join(transform)})\n" - - # [INPUT (field_name field_type) OUTPUT (field_name field_type)] - if input_schema: - inputs = [f"{k} {v}" for k, v in input_schema.items()] - ddl += f"INPUT ({', '.join(inputs)})\n" - - if output_schema: - outputs = [f"{k} {v}" for k, v in output_schema.items()] - ddl += f"OUTPUT ({', '.join(outputs)})\n" - - # [REMOTE WITH CONNECTION {connection_name | DEFAULT}] - if connection_name: - if connection_name.upper() == "DEFAULT": - ddl += "REMOTE WITH CONNECTION DEFAULT\n" - else: - ddl += f"REMOTE WITH CONNECTION {sg_sql.to_sql(sg_sql.identifier(connection_name))}\n" - - # [OPTIONS(model_option_list)] - if options: - rendered_options = [] - for option_name, option_value in options.items(): - if isinstance(option_value, col.Expression): - sg_expr = expression_compiler.compile_expression(option_value._value) - rendered_val = sg_sql.to_sql(sg_expr) - elif isinstance(option_value, (list, tuple)): - # Handle list options like model_registry="vertex_ai" - # wait, usually options are key=value. - # if value is list, it is [val1, val2] - rendered_val = sg_sql.to_sql(sg_sql.literal(list(option_value))) - else: - rendered_val = sg_sql.to_sql(sg_sql.literal(option_value)) - - rendered_options.append(f"{option_name} = {rendered_val}") - - ddl += f"OPTIONS({', '.join(rendered_options)})\n" - - # [AS {query_statement | ( training_data AS (query_statement), custom_holiday AS (holiday_statement) )}] - - if training_data: - if custom_holiday: - # When custom_holiday is present, we need named clauses - parts = [] - parts.append(f"training_data AS ({training_data})") - parts.append(f"custom_holiday AS ({custom_holiday})") - ddl += f"AS (\n {', '.join(parts)}\n)" - else: - # Just training_data is treated as the query_statement - ddl += f"AS {training_data}\n" - - return ddl - - -def _build_struct_sql( - struct_options: Mapping[ - str, - Union[str, int, float, bool, Mapping[str, str], List[str], Mapping[str, Any]], - ], -) -> str: - if not struct_options: - return "" - return f", {sg_sql.to_sql(sg_sql.literal(struct_options))}" - - -def evaluate( - model_name: str, - *, - table: Optional[str] = None, - perform_aggregation: Optional[bool] = None, - horizon: Optional[int] = None, - confidence_level: Optional[float] = None, -) -> str: - """Encode the ML.EVAluate statement. - See https://cloud.google.com/bigquery/docs/reference/standard-sql/bigqueryml-syntax-evaluate for reference. - """ - struct_options: Dict[str, Union[str, int, float, bool]] = {} - if perform_aggregation is not None: - struct_options["perform_aggregation"] = perform_aggregation - if horizon is not None: - struct_options["horizon"] = horizon - if confidence_level is not None: - struct_options["confidence_level"] = confidence_level - - sql = f"SELECT * FROM ML.EVALUATE(MODEL {sg_sql.to_sql(sg_sql.identifier(model_name))}" - if table: - sql += f", ({table})" - - sql += _build_struct_sql(struct_options) - sql += ")\n" - return sql - - -def predict( - model_name: str, - table: str, - *, - threshold: Optional[float] = None, - keep_original_columns: Optional[bool] = None, - trial_id: Optional[int] = None, -) -> str: - """Encode the ML.PREDICT statement. - See https://cloud.google.com/bigquery/docs/reference/standard-sql/bigqueryml-syntax-predict for reference. - """ - struct_options: Dict[str, Union[str, int, float, bool]] = {} - if threshold is not None: - struct_options["threshold"] = threshold - if keep_original_columns is not None: - struct_options["keep_original_columns"] = keep_original_columns - if trial_id is not None: - struct_options["trial_id"] = trial_id - - sql = f"SELECT * FROM ML.PREDICT(MODEL {sg_sql.to_sql(sg_sql.identifier(model_name))}, ({table})" - sql += _build_struct_sql(struct_options) - sql += ")\n" - return sql - - -def explain_predict( - model_name: str, - table: str, - *, - top_k_features: Optional[int] = None, - threshold: Optional[float] = None, - integrated_gradients_num_steps: Optional[int] = None, - approx_feature_contrib: Optional[bool] = None, -) -> str: - """Encode the ML.EXPLAIN_PREDICT statement. - See https://cloud.google.com/bigquery/docs/reference/standard-sql/bigqueryml-syntax-explain-predict for reference. - """ - struct_options: Dict[str, Union[str, int, float, bool]] = {} - if top_k_features is not None: - struct_options["top_k_features"] = top_k_features - if threshold is not None: - struct_options["threshold"] = threshold - if integrated_gradients_num_steps is not None: - struct_options["integrated_gradients_num_steps"] = ( - integrated_gradients_num_steps - ) - if approx_feature_contrib is not None: - struct_options["approx_feature_contrib"] = approx_feature_contrib - - sql = f"SELECT * FROM ML.EXPLAIN_PREDICT(MODEL {sg_sql.to_sql(sg_sql.identifier(model_name))}, ({table})" - sql += _build_struct_sql(struct_options) - sql += ")\n" - return sql - - -def global_explain( - model_name: str, - *, - class_level_explain: Optional[bool] = None, -) -> str: - """Encode the ML.GLOBAL_EXPLAIN statement. - See https://cloud.google.com/bigquery/docs/reference/standard-sql/bigqueryml-syntax-global-explain for reference. - """ - struct_options: Dict[str, Union[str, int, float, bool]] = {} - if class_level_explain is not None: - struct_options["class_level_explain"] = class_level_explain - - sql = f"SELECT * FROM ML.GLOBAL_EXPLAIN(MODEL {sg_sql.to_sql(sg_sql.identifier(model_name))}" - sql += _build_struct_sql(struct_options) - sql += ")\n" - return sql - - -def transform( - model_name: str, - table: str, -) -> str: - """Encode the ML.TRANSFORM statement. - See https://cloud.google.com/bigquery/docs/reference/standard-sql/bigqueryml-syntax-transform for reference. - """ - sql = f"SELECT * FROM ML.TRANSFORM(MODEL {sg_sql.to_sql(sg_sql.identifier(model_name))}, ({table}))\n" - return sql - - -def generate_text( - model_name: str, - table: str, - *, - temperature: Optional[float] = None, - max_output_tokens: Optional[int] = None, - top_k: Optional[int] = None, - top_p: Optional[float] = None, - flatten_json_output: Optional[bool] = None, - stop_sequences: Optional[List[str]] = None, - ground_with_google_search: Optional[bool] = None, - request_type: Optional[str] = None, -) -> str: - """Encode the ML.GENERATE_TEXT statement. - See https://docs.cloud.google.com/bigquery/docs/reference/standard-sql/bigqueryml-syntax-generate-text for reference. - """ - struct_options: Dict[ - str, - Union[str, int, float, bool, Mapping[str, str], List[str], Mapping[str, Any]], - ] = {} - if temperature is not None: - struct_options["temperature"] = temperature - if max_output_tokens is not None: - struct_options["max_output_tokens"] = max_output_tokens - if top_k is not None: - struct_options["top_k"] = top_k - if top_p is not None: - struct_options["top_p"] = top_p - if flatten_json_output is not None: - struct_options["flatten_json_output"] = flatten_json_output - if stop_sequences is not None: - struct_options["stop_sequences"] = stop_sequences - if ground_with_google_search is not None: - struct_options["ground_with_google_search"] = ground_with_google_search - if request_type is not None: - struct_options["request_type"] = request_type - - sql = f"SELECT * FROM ML.GENERATE_TEXT(MODEL {sg_sql.to_sql(sg_sql.identifier(model_name))}, ({table})" - sql += _build_struct_sql(struct_options) - sql += ")\n" - return sql - - -def get_insights( - model_name: str, -) -> str: - """Encode the ML.GET_INSIGHTS statement. - See https://cloud.google.com/bigquery/docs/reference/standard-sql/bigqueryml-syntax-get-insights for reference. - """ - sql = f"SELECT * FROM ML.GET_INSIGHTS(MODEL {sg_sql.to_sql(sg_sql.identifier(model_name))})\n" - return sql - - -def generate_embedding( - model_name: str, - table: str, - *, - flatten_json_output: Optional[bool] = None, - task_type: Optional[str] = None, - output_dimensionality: Optional[int] = None, -) -> str: - """Encode the ML.GENERATE_EMBEDDING statement. - See https://docs.cloud.google.com/bigquery/docs/reference/standard-sql/bigqueryml-syntax-generate-embedding for reference. - """ - struct_options: Dict[ - str, - Union[str, int, float, bool, Mapping[str, str], List[str], Mapping[str, Any]], - ] = {} - if flatten_json_output is not None: - struct_options["flatten_json_output"] = flatten_json_output - if task_type is not None: - struct_options["task_type"] = task_type - if output_dimensionality is not None: - struct_options["output_dimensionality"] = output_dimensionality - - sql = f"SELECT * FROM ML.GENERATE_EMBEDDING(MODEL {sg_sql.to_sql(sg_sql.identifier(model_name))}, ({table})" - sql += _build_struct_sql(struct_options) - sql += ")\n" - return sql diff --git a/bigframes/core/sql_nodes.py b/bigframes/core/sql_nodes.py deleted file mode 100644 index c7a05a082f2..00000000000 --- a/bigframes/core/sql_nodes.py +++ /dev/null @@ -1,300 +0,0 @@ -# Copyright 2026 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from __future__ import annotations - -import dataclasses -import functools -from typing import Callable, Mapping, Optional, Sequence, Tuple - -import bigframes.core.expression as ex -import bigframes.dtypes -from bigframes.core import bq_data, identifiers, nodes -from bigframes.core.ordering import OrderingExpression - -# SQL Nodes are generally terminal, so don't support rich transformation methods -# like remap_vars, remap_refs, etc. -# Still, fields should be defined on them, as typing info is still used for -# dispatching some operators in the emitter, and for validation. - - -# TODO: Join node, union node -@dataclasses.dataclass(frozen=True) -class SqlDataSource(nodes.LeafNode): - source: bq_data.BigqueryDataSource - - @functools.cached_property - def fields(self) -> Sequence[nodes.Field]: - return tuple( - nodes.Field( - identifiers.ColumnId(source_id), - self.source.schema.get_type(source_id), - self.source.table.schema_by_id[source_id].is_nullable, - ) - for source_id in self.source.schema.names - ) - - @property - def is_star_selection(self) -> bool: - return tuple(self.source.schema.names) == tuple( - field.name for field in self.source.table.physical_schema - ) - - @property - def variables_introduced(self) -> int: - # This operation only renames variables, doesn't actually create new ones - return 0 - - @property - def defines_namespace(self) -> bool: - return True - - @property - def explicitly_ordered(self) -> bool: - return False - - @property - def order_ambiguous(self) -> bool: - return True - - @property - def row_count(self) -> Optional[int]: - return self.source.n_rows - - @property - def node_defined_ids(self) -> Tuple[identifiers.ColumnId, ...]: - return tuple(self.ids) - - @property - def consumed_ids(self): - return () - - @property - def _node_expressions(self): - return () - - def remap_vars( - self, mappings: Mapping[identifiers.ColumnId, identifiers.ColumnId] - ) -> SqlSelectNode: - raise NotImplementedError() - - def remap_refs( - self, mappings: Mapping[identifiers.ColumnId, identifiers.ColumnId] - ) -> SqlSelectNode: - raise NotImplementedError() # type: ignore - - -@dataclasses.dataclass(frozen=True) -class SqlWithCtesNode(nodes.BigFrameNode): - # def, name pairs - child: nodes.BigFrameNode - cte_names: tuple[str, ...] - cte_defs: tuple[nodes.BigFrameNode, ...] - - @property - def child_nodes(self) -> Sequence[nodes.BigFrameNode]: - return (self.child, *self.cte_defs) - - @property - def fields(self) -> Sequence[nodes.Field]: - return self.child.fields - - @property - def variables_introduced(self) -> int: - # This operation only renames variables, doesn't actually create new ones - return 0 - - @property - def defines_namespace(self) -> bool: - return True - - @property - def explicitly_ordered(self) -> bool: - return False - - @property - def order_ambiguous(self) -> bool: - return True - - @property - def row_count(self) -> Optional[int]: - return self.child.row_count - - @property - def node_defined_ids(self) -> Tuple[identifiers.ColumnId, ...]: - return tuple(self.ids) - - @property - def consumed_ids(self): - return () - - @property - def _node_expressions(self): - return () - - def remap_vars( - self, mappings: Mapping[identifiers.ColumnId, identifiers.ColumnId] - ) -> SqlWithCtesNode: - raise NotImplementedError() - - def remap_refs( - self, mappings: Mapping[identifiers.ColumnId, identifiers.ColumnId] - ) -> SqlWithCtesNode: - raise NotImplementedError() # type: ignore - - def transform_children( - self, transform: Callable[[nodes.BigFrameNode], nodes.BigFrameNode] - ) -> SqlWithCtesNode: - return SqlWithCtesNode( - transform(self.child), - self.cte_names, - tuple(transform(cte) for cte in self.cte_defs), - ) - - -@dataclasses.dataclass(frozen=True) -class SqlCteRefNode(nodes.LeafNode): - cte_name: str - cte_schema: tuple[nodes.Field, ...] - - @property - def fields(self) -> Sequence[nodes.Field]: - return self.cte_schema - - @property - def variables_introduced(self) -> int: - # This operation only renames variables, doesn't actually create new ones - return 0 - - @property - def defines_namespace(self) -> bool: - return True - - @property - def explicitly_ordered(self) -> bool: - return False - - @property - def order_ambiguous(self) -> bool: - return True - - @property - def row_count(self) -> Optional[int]: - raise NotImplementedError() - - @property - def node_defined_ids(self) -> Tuple[identifiers.ColumnId, ...]: - return tuple(self.ids) - - @property - def consumed_ids(self): - return () - - @property - def _node_expressions(self): - return () - - def remap_vars( - self, mappings: Mapping[identifiers.ColumnId, identifiers.ColumnId] - ) -> SqlCteRefNode: - raise NotImplementedError() - - def remap_refs( - self, mappings: Mapping[identifiers.ColumnId, identifiers.ColumnId] - ) -> SqlCteRefNode: - raise NotImplementedError() # type: ignore - - -@dataclasses.dataclass(frozen=True) -class SqlSelectNode(nodes.UnaryNode): - selections: tuple[nodes.ColumnDef, ...] = () - predicates: tuple[ex.Expression, ...] = () - sorting: tuple[OrderingExpression, ...] = () - limit: Optional[int] = None - - @functools.cached_property - def fields(self) -> Sequence[nodes.Field]: - fields = [] - for cdef in self.selections: - bound_expr = ex.bind_schema_fields(cdef.expression, self.child.field_by_id) - field = nodes.Field( - cdef.id, - bigframes.dtypes.dtype_for_etype(bound_expr.output_type), - nullable=bound_expr.nullable, - ) - - # Special case until we get better nullability inference in expression objects themselves - if bound_expr.is_identity and not any( - self.child.field_by_id[id].nullable - for id in cdef.expression.column_references - ): - field = field.with_nonnull() - fields.append(field) - - return tuple(fields) - - @property - def variables_introduced(self) -> int: - # This operation only renames variables, doesn't actually create new ones - return 0 - - @property - def defines_namespace(self) -> bool: - return True - - @property - def row_count(self) -> Optional[int]: - if self.child.row_count is not None: - if self.limit is not None: - return min([self.limit, self.child.row_count]) - return self.child.row_count - - return None - - @property - def node_defined_ids(self) -> Tuple[identifiers.ColumnId, ...]: - return tuple(cdef.id for cdef in self.selections) - - @property - def consumed_ids(self): - raise NotImplementedError() - - @property - def _node_expressions(self): - raise NotImplementedError() - - @property - def is_star_selection(self) -> bool: - if tuple(self.ids) != tuple(self.child.ids): - return False - for cdef in self.selections: - if not isinstance(cdef.expression, ex.DerefOp): - return False - if cdef.expression.id != cdef.id: - return False - return True - - @functools.cache - def get_id_mapping(self) -> dict[identifiers.ColumnId, ex.Expression]: - return {cdef.id: cdef.expression for cdef in self.selections} - - def remap_vars( - self, mappings: Mapping[identifiers.ColumnId, identifiers.ColumnId] - ) -> SqlSelectNode: - raise NotImplementedError() - - def remap_refs( - self, mappings: Mapping[identifiers.ColumnId, identifiers.ColumnId] - ) -> SqlSelectNode: - raise NotImplementedError() # type: ignore diff --git a/bigframes/core/tools/__init__.py b/bigframes/core/tools/__init__.py deleted file mode 100644 index 38563510a79..00000000000 --- a/bigframes/core/tools/__init__.py +++ /dev/null @@ -1,19 +0,0 @@ -# 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. -# 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 bigframes.core.tools.datetimes import to_datetime - -__all__ = [ - "to_datetime", -] diff --git a/bigframes/core/tools/bigquery_schema.py b/bigframes/core/tools/bigquery_schema.py deleted file mode 100644 index eef7364a1bc..00000000000 --- a/bigframes/core/tools/bigquery_schema.py +++ /dev/null @@ -1,59 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -"""Helpers for working with BigQuery SchemaFields.""" - -from typing import Tuple - -import google.cloud.bigquery - -_LEGACY_TO_GOOGLESQL_TYPES = { - "BOOLEAN": "BOOL", - "INTEGER": "INT64", - "FLOAT": "FLOAT64", -} - - -def _type_to_sql(field: google.cloud.bigquery.SchemaField): - """Turn the type information of the field into SQL. - - Ignores the mode, since this has already been handled by _field_to_sql. - """ - if field.field_type.casefold() in ("record", "struct"): - return _to_struct(field.fields) - - # Map from legacy SQL names (the ones used in the BigQuery schema API) to - # the GoogleSQL types. Importantly, FLOAT is from legacy SQL, but not valid - # in GoogleSQL. See internal issue b/428190014. - type_ = _LEGACY_TO_GOOGLESQL_TYPES.get(field.field_type.upper(), field.field_type) - return type_ - - -def _field_to_sql(field: google.cloud.bigquery.SchemaField): - if field.mode == "REPEATED": - # Unlike other types, ARRAY are represented as mode="REPEATED". To get - # the array type, we use SchemaField object but ignore the mode. - return f"`{field.name}` ARRAY<{_type_to_sql(field)}>" - - return f"`{field.name}` {_type_to_sql(field)}" - - -def _to_struct(bqschema: Tuple[google.cloud.bigquery.SchemaField, ...]): - fields = [_field_to_sql(field) for field in bqschema] - return f"STRUCT<{', '.join(fields)}>" - - -def to_sql_dry_run(bqschema: Tuple[google.cloud.bigquery.SchemaField, ...]): - """Create an empty table expression with the correct schema.""" - return f"UNNEST(ARRAY<{_to_struct(bqschema)}>[])" diff --git a/bigframes/core/tools/datetimes.py b/bigframes/core/tools/datetimes.py deleted file mode 100644 index 0cdda67693d..00000000000 --- a/bigframes/core/tools/datetimes.py +++ /dev/null @@ -1,141 +0,0 @@ -# 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. -# 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 collections.abc import Mapping -from datetime import date, datetime -from typing import TYPE_CHECKING, Optional, Union - -import bigframes_vendored.constants as constants -import bigframes_vendored.pandas.core.tools.datetimes as vendored_pandas_datetimes -import pandas as pd - -import bigframes.dataframe -import bigframes.dtypes -import bigframes.operations as ops -import bigframes.series - -if TYPE_CHECKING: - import bigframes.session - - -def to_datetime( - arg: Union[ - Union[int, float, str, datetime, date], - vendored_pandas_datetimes.local_iterables, - bigframes.series.Series, - bigframes.dataframe.DataFrame, - ], - *, - utc: bool = False, - format: Optional[str] = None, - unit: Optional[str] = None, - session: Optional[bigframes.session.Session] = None, -) -> Union[pd.Timestamp, datetime, bigframes.series.Series]: - if isinstance(arg, (int, float, str, datetime, date)): - return pd.to_datetime( - arg, - utc=utc, - format=format, - unit=unit, - ) - - if isinstance(arg, (Mapping, pd.DataFrame, bigframes.dataframe.DataFrame)): - raise NotImplementedError( - "Conversion of Mapping, pandas.DataFrame, or bigframes.dataframe.DataFrame " - f"to datetime is not implemented. {constants.FEEDBACK_LINK}" - ) - - arg = bigframes.series.Series(arg, session=session) - - if ( - format - and unit - and arg.dtype in (bigframes.dtypes.INT_DTYPE, bigframes.dtypes.FLOAT_DTYPE) - ): # type: ignore - raise ValueError("cannot specify both format and unit") - - if unit and arg.dtype not in ( - bigframes.dtypes.INT_DTYPE, - bigframes.dtypes.FLOAT_DTYPE, - ): # type: ignore - raise NotImplementedError( - f"Unit parameter is not supported for non-numerical input types. {constants.FEEDBACK_LINK}" - ) - - if arg.dtype in ( - bigframes.dtypes.TIMESTAMP_DTYPE, - bigframes.dtypes.DATETIME_DTYPE, - bigframes.dtypes.DATE_DTYPE, - ): - to_type = ( - bigframes.dtypes.TIMESTAMP_DTYPE if utc else bigframes.dtypes.DATETIME_DTYPE - ) - return arg._apply_unary_op(ops.AsTypeOp(to_type=to_type)) # type: ignore - if (not utc) and arg.dtype == bigframes.dtypes.STRING_DTYPE: - if format: - raise NotImplementedError( - f"Customized formats are not supported for string inputs when utc=False. Please set utc=True if possible. {constants.FEEDBACK_LINK}" - ) - - assert unit is None - - # The following operations evaluate individual values to infer a format, - # so cache if needed. - arg = arg._cached(force=False) - - as_datetime = arg._apply_unary_op( # type: ignore - ops.ToDatetimeOp( - format=format, - unit=unit, - ) - ) - failed_datetime_cast = arg.notnull() & as_datetime.isnull() - is_utc = arg._apply_unary_op( - ops.EndsWithOp( - pat=("Z", "-00:00", "+00:00", "-0000", "+0000", "-00", "+00") - ) - ) - - # Cast to DATETIME shall succeed if all inputs are tz-naive. - if not failed_datetime_cast.any(): - return as_datetime - - if is_utc.all(): - return arg._apply_unary_op( # type: ignore - ops.ToTimestampOp( - format=format, - unit=unit, - ) - ) - - raise NotImplementedError( - f"Non-UTC string inputs are not supported when utc=False. Please set utc=True if possible. {constants.FEEDBACK_LINK}" - ) - # If utc: - elif utc: - return arg._apply_unary_op( # type: ignore - ops.ToTimestampOp( - format=format, - unit=unit, - ) - ) - else: - return arg._apply_unary_op( # type: ignore - ops.ToDatetimeOp( - format=format, - unit=unit, - ) - ) diff --git a/bigframes/core/tree_properties.py b/bigframes/core/tree_properties.py deleted file mode 100644 index 225cfc2f437..00000000000 --- a/bigframes/core/tree_properties.py +++ /dev/null @@ -1,155 +0,0 @@ -# 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. -# 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 functools -import itertools -from typing import TYPE_CHECKING, Callable, Dict, Optional, Sequence - -import bigframes.core.nodes as nodes - -if TYPE_CHECKING: - import bigframes.session.execution_cache as execution_cache - - -def is_trivially_executable(node: nodes.BigFrameNode) -> bool: - if local_only(node): - return True - children_trivial = all(is_trivially_executable(child) for child in node.child_nodes) - self_trivial = (not node.non_local) and (node.row_preserving) - return children_trivial and self_trivial - - -def local_only(node: nodes.BigFrameNode) -> bool: - return all(isinstance(node, nodes.ReadLocalNode) for node in node.roots) - - -def can_fast_peek(node: nodes.BigFrameNode) -> bool: - if local_only(node): - return True - children_peekable = all(can_fast_peek(child) for child in node.child_nodes) - self_peekable = not node.non_local - return children_peekable and self_peekable - - -def can_fast_head(node: nodes.BigFrameNode) -> bool: - """Can get head fast if can push head operator down to leafs and operators preserve rows.""" - # To do fast head operation: - # (1) the underlying data must be arranged/indexed according to the logical ordering - # (2) transformations must support pushing down LIMIT or a filter on row numbers - if isinstance(node, nodes.ReadLocalNode): - # always cheap to push slice into local data - return True - if isinstance(node, nodes.ReadTableNode): - return (node.source.ordering is None) or (node.fast_ordered_limit) - if isinstance(node, (nodes.ProjectionNode, nodes.SelectionNode)): - return can_fast_head(node.child) - return False - - -def row_count(node: nodes.BigFrameNode) -> Optional[int]: - """Determine row count from local metadata, return None if unknown.""" - return node.row_count - - -# Replace modified_cost(node) = cost(apply_cache(node)) -def select_cache_target( - root: nodes.BigFrameNode, - min_complexity: float, - max_complexity: float, - cache: execution_cache.ExecutionCache, - heuristic: Callable[[int, int], float], -) -> Optional[nodes.BigFrameNode]: - """Take tree, and return candidate nodes with (# of occurences, post-caching planning complexity). - - heurstic takes two args, node complexity, and node occurence count, in that order - """ - - @functools.cache - def _with_caching(subtree: nodes.BigFrameNode) -> nodes.BigFrameNode: - return cache.subsitute_cached_subplans(subtree) - - def _combine_counts( - left: Dict[nodes.BigFrameNode, int], right: Dict[nodes.BigFrameNode, int] - ) -> Dict[nodes.BigFrameNode, int]: - return { - key: left.get(key, 0) + right.get(key, 0) - for key in itertools.chain(left.keys(), right.keys()) - } - - @functools.cache - def _node_counts_inner( - subtree: nodes.BigFrameNode, - ) -> Dict[nodes.BigFrameNode, int]: - """Helper function to count occurences of duplicate nodes in a subtree. Considers only nodes in a complexity range""" - empty_counts: Dict[nodes.BigFrameNode, int] = {} - subtree_complexity = _with_caching(subtree).planning_complexity - if subtree_complexity >= min_complexity: - child_counts = [_node_counts_inner(child) for child in subtree.child_nodes] - node_counts = functools.reduce(_combine_counts, child_counts, empty_counts) - if subtree_complexity <= max_complexity: - return _combine_counts(node_counts, {subtree: 1}) - else: - return node_counts - return empty_counts - - node_counts = _node_counts_inner(root) - - if len(node_counts) == 0: - raise ValueError("node counts should be non-zero") - - # for each considered node, calculate heuristic value, and return node with max value - return max( - node_counts.keys(), - key=lambda node: heuristic( - _with_caching(node).planning_complexity, node_counts[node] - ), - ) - - -def count_nodes(forest: Sequence[nodes.BigFrameNode]) -> dict[nodes.BigFrameNode, int]: - """ - Counts the number of instances of each subtree present within a forest. - - Memoizes internally to accelerate execution, but cache not persisted (not reused between invocations). - - Args: - forest (Sequence of BigFrameNode): - The roots of each tree in the forest - - Returns: - dict[BigFramesNode, int]: The number of occurences of each subtree. - """ - - def _combine_counts( - left: Dict[nodes.BigFrameNode, int], right: Dict[nodes.BigFrameNode, int] - ) -> Dict[nodes.BigFrameNode, int]: - return { - key: left.get(key, 0) + right.get(key, 0) - for key in itertools.chain(left.keys(), right.keys()) - } - - empty_counts: Dict[nodes.BigFrameNode, int] = {} - - @functools.cache - def _node_counts_inner( - subtree: nodes.BigFrameNode, - ) -> Dict[nodes.BigFrameNode, int]: - """Helper function to count occurences of duplicate nodes in a subtree. Considers only nodes in a complexity range""" - child_counts = [_node_counts_inner(child) for child in subtree.child_nodes] - node_counts = functools.reduce(_combine_counts, child_counts, empty_counts) - return _combine_counts(node_counts, {subtree: 1}) - - counts = [_node_counts_inner(root) for root in forest] - return functools.reduce(_combine_counts, counts, empty_counts) diff --git a/bigframes/core/utils.py b/bigframes/core/utils.py index 641fbcc9ac4..dc7c7090117 100644 --- a/bigframes/core/utils.py +++ b/bigframes/core/utils.py @@ -11,28 +11,18 @@ # 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 datetime -import functools -import re import typing -import warnings from typing import Hashable, Iterable, List -import bigframes_vendored.pandas.io.common as vendored_pandas_io_common -import numpy as np import pandas as pd import typing_extensions -import bigframes.exceptions as bfe +import third_party.bigframes_vendored.pandas.io.common as vendored_pandas_io_common UNNAMED_COLUMN_ID = "bigframes_unnamed_column" UNNAMED_INDEX_ID = "bigframes_unnamed_index" -def is_gcs_path(value) -> typing_extensions.TypeGuard[str]: - return isinstance(value, str) and value.startswith("gs://") - - def get_axis_number(axis: typing.Union[str, int]) -> typing.Literal[0, 1]: if axis in {0, "index", "rows"}: return 0 @@ -41,10 +31,8 @@ def get_axis_number(axis: typing.Union[str, int]) -> typing.Literal[0, 1]: raise ValueError(f"Not a valid axis: {axis}") -def is_list_like( - obj: typing.Any, allow_sets: bool = True -) -> typing_extensions.TypeGuard[typing.Sequence]: - return pd.api.types.is_list_like(obj, allow_sets=allow_sets) +def is_list_like(obj: typing.Any) -> typing_extensions.TypeGuard[typing.Sequence]: + return pd.api.types.is_list_like(obj) def is_dict_like(obj: typing.Any) -> typing_extensions.TypeGuard[typing.Mapping]: @@ -52,7 +40,7 @@ def is_dict_like(obj: typing.Any) -> typing_extensions.TypeGuard[typing.Mapping] def combine_indices(index1: pd.Index, index2: pd.Index) -> pd.MultiIndex: - """Combines indices into multi-index while preserving dtypes, names merging by rows 1:1""" + """Combines indices into multi-index while preserving dtypes, names.""" multi_index = pd.MultiIndex.from_frame( pd.concat([index1.to_frame(index=False), index2.to_frame(index=False)], axis=1) ) @@ -61,20 +49,6 @@ def combine_indices(index1: pd.Index, index2: pd.Index) -> pd.MultiIndex: return multi_index -def cross_indices(index1: pd.Index, index2: pd.Index) -> pd.MultiIndex: - """Combines indices into multi-index while preserving dtypes, names using cross product""" - multi_index = pd.MultiIndex.from_frame( - pd.merge( - left=index1.to_frame(index=False), - right=index2.to_frame(index=False), - how="cross", - ) - ) - # to_frame will produce numbered default names, we don't want these - multi_index.names = [*index1.names, *index2.names] - return multi_index - - def index_as_tuples(index: pd.Index) -> typing.Sequence[typing.Tuple]: if isinstance(index, pd.MultiIndex): return [label for label in index] @@ -96,9 +70,7 @@ def split_index( def get_standardized_ids( - col_labels: Iterable[Hashable], - idx_labels: Iterable[Hashable] = (), - strict: bool = False, + col_labels: Iterable[Hashable], idx_labels: Iterable[Hashable] = () ) -> tuple[list[str], list[str]]: """Get stardardized column ids as column_ids_list, index_ids_list. The standardized_column_id must be valid BQ SQL schema column names, can only be string type and unique. @@ -112,76 +84,26 @@ def get_standardized_ids( Tuple of (standardized_column_ids, standardized_index_ids) """ col_ids = [ - UNNAMED_COLUMN_ID - if pd.isna(col_label) # type: ignore - else label_to_identifier(col_label, strict=strict) + UNNAMED_COLUMN_ID if col_label is None else str(col_label) for col_label in col_labels ] idx_ids = [ - UNNAMED_INDEX_ID - if pd.isna(idx_label) # type: ignore - else label_to_identifier(idx_label, strict=strict) + UNNAMED_INDEX_ID if idx_label is None else str(idx_label) for idx_label in idx_labels ] - ids = disambiguate_ids(idx_ids + col_ids) - - idx_ids, col_ids = ids[: len(idx_ids)], ids[len(idx_ids) :] - - return col_ids, idx_ids - - -def label_to_identifier(label: typing.Hashable, strict: bool = False) -> str: - """ - Convert pandas label to make legal bigquery identifier. May create collisions (should deduplicate after). - Strict mode might not be necessary, but ibis seems to escape non-alphanumeric characters inconsistently. - """ + ids = idx_ids + col_ids # Column values will be loaded as null if the column name has spaces. # https://github.com/googleapis/python-bigquery/issues/1566 - identifier = str(label) - if strict: - identifier = str(label).replace(" ", "_") - identifier = re.sub(r"[^a-zA-Z0-9_]", "", identifier) - if not identifier: - identifier = "id" - elif identifier[0].isdigit(): - # first character must be letter or underscore - identifier = "_" + identifier + ids = [id.replace(" ", "_") for id in ids] - 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. - # _PARTITION, _TABLE_, _FILE_, _ROW_TIMESTAMP, __ROOT__ and _COLIDENTIFIER - if any( - identifier.casefold().startswith(invalid_prefix.casefold()) - for invalid_prefix in ( - "_PARTITION", - "_TABLE_", - "_FILE_", - "_ROW_TIMESTAMP", - "__ROOT__", - "_COLIDENTIFIER", - ) - ): - # Remove leading _ character(s) to avoid collisions with preserved - # prefixes. - identifier = re.sub("^_+", "", identifier) - - return identifier - - -def disambiguate_ids(ids: typing.Sequence[str]) -> typing.List[str]: - """Disambiguate list of ids by adding suffixes where needed. If inputs are legal sql ids, outputs should be as well.""" - return typing.cast( + ids = typing.cast( List[str], vendored_pandas_io_common.dedup_names(ids, is_potential_multiindex=False), ) + idx_ids, col_ids = ids[: len(idx_ids)], ids[len(idx_ids) :] + + return col_ids, idx_ids def merge_column_labels( @@ -215,50 +137,3 @@ def merge_column_labels( result_labels.append(col_label) return pd.Index(result_labels) - - -def preview(*, name: str): - """Decorate to warn of a preview API.""" - - def decorator(func): - msg = f"{name} is in preview. Its behavior may change in future versions." - - @functools.wraps(func) - def wrapper(*args, **kwargs): - warnings.warn(bfe.format_message(msg), category=bfe.PreviewWarning) - return func(*args, **kwargs) - - return wrapper - - return decorator - - -def timedelta_to_micros( - timedelta: typing.Union[pd.Timedelta, datetime.timedelta, np.timedelta64], -) -> int: - if isinstance(timedelta, pd.Timedelta): - # pd.Timedelta.value returns total nanoseconds. - return timedelta.value // 1000 - - if isinstance(timedelta, np.timedelta64): - return timedelta.astype("timedelta64[us]").astype(np.int64) - - if isinstance(timedelta, datetime.timedelta): - return ( - (timedelta.days * 3600 * 24) + timedelta.seconds - ) * 1_000_000 + timedelta.microseconds - - raise TypeError(f"Unrecognized input type: {type(timedelta)}") - - -def get_ipython_execution_count() -> typing.Optional[int]: - """Returns the current IPython cell execution count if running in a notebook, else None.""" - try: - from IPython.core.interactiveshell import InteractiveShell - - if InteractiveShell.initialized(): - ipy = InteractiveShell.instance() - return getattr(ipy, "execution_count", None) - except (ImportError, NameError): - pass - return None diff --git a/bigframes/core/validations.py b/bigframes/core/validations.py deleted file mode 100644 index 84f802cd740..00000000000 --- a/bigframes/core/validations.py +++ /dev/null @@ -1,79 +0,0 @@ -# Copyright 2023 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. - -"""DataFrame is a two dimensional data structure.""" - -from __future__ import annotations - -import functools -from typing import TYPE_CHECKING, Optional, Protocol, Union - -import bigframes_vendored.constants as constants - -import bigframes.exceptions - -if TYPE_CHECKING: - from bigframes import Session - from bigframes.core.blocks import Block - from bigframes.dataframe import DataFrame - from bigframes.series import Series - - -class HasSession(Protocol): - @property - def _session(self) -> Session: ... - - @property - def _block(self) -> Block: ... - - -def requires_index(meth): - @functools.wraps(meth) - def guarded_meth(df: Union[DataFrame, Series], *args, **kwargs): - df._throw_if_null_index(meth.__name__) - return meth(df, *args, **kwargs) - - guarded_meth._validations_requires_index = True # type: ignore - return guarded_meth - - -def requires_ordering(suggestion: Optional[str] = None): - def decorator(meth): - @functools.wraps(meth) - def guarded_meth(object: HasSession, *args, **kwargs): - enforce_ordered(object, meth.__name__, suggestion) - return meth(object, *args, **kwargs) - - guarded_meth._validations_requires_ordering = True # type: ignore - return guarded_meth - - return decorator - - -def enforce_ordered( - object: HasSession, opname: str, suggestion: Optional[str] = None -) -> None: - session = object._session - if session._strictly_ordered or not object._block.expr.order_ambiguous: - # No ambiguity for how to calculate ordering, so no error or warning - return None - if not session._allows_ambiguity: - suggestion_substr = suggestion + " " if suggestion else "" - raise bigframes.exceptions.OrderRequiredError( - f"Op {opname} not supported when strict ordering is disabled. {suggestion_substr}{constants.FEEDBACK_LINK}" - ) - if not object._block.explicitly_ordered: - raise bigframes.exceptions.OrderRequiredError( - f"Op {opname} requires an ordering. Use .sort_values or .sort_index to provide an ordering. {constants.FEEDBACK_LINK}" - ) diff --git a/bigframes/core/window/__init__.py b/bigframes/core/window/__init__.py index 1d888ca7e6a..d3d081124e9 100644 --- a/bigframes/core/window/__init__.py +++ b/bigframes/core/window/__init__.py @@ -1,4 +1,4 @@ -# Copyright 2025 Google LLC +# Copyright 2023 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -12,6 +12,84 @@ # See the License for the specific language governing permissions and # limitations under the License. -from bigframes.core.window.rolling import Window +from __future__ import annotations -__all__ = ["Window"] +import typing + +import bigframes.core as core +import bigframes.core.blocks as blocks +import bigframes.operations.aggregations as agg_ops +import third_party.bigframes_vendored.pandas.core.window.rolling as vendored_pandas_rolling + + +class Window(vendored_pandas_rolling.Window): + __doc__ = vendored_pandas_rolling.Window.__doc__ + + def __init__( + self, + block: blocks.Block, + window_spec: core.WindowSpec, + value_column_ids: typing.Sequence[str], + drop_null_groups: bool = True, + is_series: bool = False, + ): + self._block = block + self._window_spec = window_spec + self._value_column_ids = value_column_ids + self._drop_null_groups = drop_null_groups + self._is_series = is_series + + def count(self): + return self._apply_aggregate(agg_ops.count_op) + + def sum(self): + return self._apply_aggregate(agg_ops.sum_op) + + def mean(self): + return self._apply_aggregate(agg_ops.mean_op) + + def var(self): + return self._apply_aggregate(agg_ops.var_op) + + def std(self): + return self._apply_aggregate(agg_ops.std_op) + + def max(self): + return self._apply_aggregate(agg_ops.max_op) + + def min(self): + return self._apply_aggregate(agg_ops.min_op) + + def _apply_aggregate( + self, + op: agg_ops.AggregateOp, + ): + block = self._block + labels = [block.col_id_to_label[col] for col in self._value_column_ids] + block, result_ids = block.multi_apply_window_op( + self._value_column_ids, + op, + self._window_spec, + skip_null_groups=self._drop_null_groups, + never_skip_nulls=True, + ) + + if self._window_spec.grouping_keys: + original_index_ids = block.index_columns + block = block.reset_index(drop=False) + index_ids = ( + *[col for col in self._window_spec.grouping_keys], + *original_index_ids, + ) + block = block.set_index(col_ids=index_ids) + + if self._is_series: + from bigframes.series import Series + + return Series(block.select_columns(result_ids).with_column_labels(labels)) + else: + from bigframes.dataframe import DataFrame + + return DataFrame( + block.select_columns(result_ids).with_column_labels(labels) + ) diff --git a/bigframes/core/window/ordering.py b/bigframes/core/window/ordering.py deleted file mode 100644 index 0bea585bb04..00000000000 --- a/bigframes/core/window/ordering.py +++ /dev/null @@ -1,86 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from __future__ import annotations - -from functools import singledispatch - -from bigframes.core import expression as ex -from bigframes.core import nodes, ordering - - -@singledispatch -def find_order_direction( - root: nodes.BigFrameNode, column_id: str -) -> ordering.OrderingDirection | None: - """Returns the order of the given column with tree traversal. If the column cannot be found, - or the ordering information is not available, return None. - """ - return None - - -@find_order_direction.register -def _(root: nodes.OrderByNode, column_id: str): - if len(root.by) == 0: - # This is a no-op - return find_order_direction(root.child, column_id) - - # Make sure the window key is the prefix of sorting keys. - order_expr = root.by[0] - scalar_expr = order_expr.scalar_expression - if isinstance(scalar_expr, ex.DerefOp) and scalar_expr.id.name == column_id: - return order_expr.direction - - return None - - -@find_order_direction.register -def _(root: nodes.ReversedNode, column_id: str): - direction = find_order_direction(root.child, column_id) - - if direction is None: - return None - return direction.reverse() - - -@find_order_direction.register -def _(root: nodes.SelectionNode, column_id: str): - for alias_ref in root.input_output_pairs: - if alias_ref.id.name == column_id: - return find_order_direction(root.child, alias_ref.ref.id.name) - - -@find_order_direction.register -def _(root: nodes.FilterNode, column_id: str): - return find_order_direction(root.child, column_id) - - -@find_order_direction.register -def _(root: nodes.InNode, column_id: str): - return find_order_direction(root.left_child, column_id) - - -@find_order_direction.register -def _(root: nodes.WindowOpNode, column_id: str): - return find_order_direction(root.child, column_id) - - -@find_order_direction.register -def _(root: nodes.ProjectionNode, column_id: str): - for expr, ref in root.assignments: - if ref.name == column_id and isinstance(expr, ex.DerefOp): - # This source column is renamed. - return find_order_direction(root.child, expr.id.name) - - return find_order_direction(root.child, column_id) diff --git a/bigframes/core/window/rolling.py b/bigframes/core/window/rolling.py deleted file mode 100644 index a3660954dfb..00000000000 --- a/bigframes/core/window/rolling.py +++ /dev/null @@ -1,275 +0,0 @@ -# Copyright 2023 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 datetime -from typing import TYPE_CHECKING, Literal, Mapping, Sequence, Union - -import bigframes_vendored.pandas.core.window.rolling as vendored_pandas_rolling -import numpy -import pandas - -import bigframes.core.blocks as blocks -import bigframes.operations.aggregations as agg_ops -from bigframes import dtypes -from bigframes._tools import docs -from bigframes.core import agg_expressions, ordering, utils, window_spec -from bigframes.core import expression as ex -from bigframes.core.logging import log_adapter -from bigframes.core.window import ordering as window_ordering - -if TYPE_CHECKING: - import bigframes.dataframe as df - import bigframes.series as series - - -@log_adapter.class_logger -@docs.inherit_docs(vendored_pandas_rolling.Window) -class Window: - def __init__( - self, - block: blocks.Block, - window_spec: window_spec.WindowSpec, - value_column_ids: Sequence[str], - drop_null_groups: bool = True, - is_series: bool = False, - skip_agg_column_id: str | None = None, - ): - self._block = block - self._window_spec = window_spec - self._value_column_ids = value_column_ids - self._drop_null_groups = drop_null_groups - self._is_series = is_series - # The column ID that won't be aggregated on. - # This is equivalent to pandas `on` parameter in rolling() - self._skip_agg_column_id = skip_agg_column_id - - def count(self): - return self._apply_aggregate_op(agg_ops.count_op) - - def sum(self): - return self._apply_aggregate_op(agg_ops.sum_op) - - def mean(self): - return self._apply_aggregate_op(agg_ops.mean_op) - - def var(self): - return self._apply_aggregate_op(agg_ops.var_op) - - def std(self): - return self._apply_aggregate_op(agg_ops.std_op) - - def max(self): - return self._apply_aggregate_op(agg_ops.max_op) - - def min(self): - return self._apply_aggregate_op(agg_ops.min_op) - - def agg(self, func) -> Union[df.DataFrame, series.Series]: - if utils.is_dict_like(func): - return self._agg_dict(func) - elif utils.is_list_like(func): - return self._agg_list(func) - else: - return self._agg_func(func) - - aggregate = agg - - def _agg_func(self, func) -> df.DataFrame: - ids, labels = self._aggregated_columns() - aggregations = [agg(col_id, agg_ops.lookup_agg_func(func)[0]) for col_id in ids] - return self._apply_aggs(aggregations, labels) - - def _agg_dict(self, func: Mapping) -> df.DataFrame: - aggregations: list[agg_expressions.Aggregation] = [] - column_labels = [] - function_labels = [] - - want_aggfunc_level = any(utils.is_list_like(aggs) for aggs in func.values()) - - for label, funcs_for_id in func.items(): - col_id = self._block.label_to_col_id[label][-1] # get last matching column - func_list = ( - funcs_for_id if utils.is_list_like(funcs_for_id) else [funcs_for_id] - ) - for f in func_list: - f_op, f_label = agg_ops.lookup_agg_func(f) - aggregations.append(agg(col_id, f_op)) - column_labels.append(label) - function_labels.append(f_label) - if want_aggfunc_level: - result_labels: pandas.Index = utils.combine_indices( - pandas.Index(column_labels), - pandas.Index(function_labels), - ) - else: - result_labels = pandas.Index(column_labels) - - return self._apply_aggs(aggregations, result_labels) - - def _agg_list(self, func: Sequence) -> df.DataFrame: - ids, labels = self._aggregated_columns() - aggregations = [ - agg(col_id, agg_ops.lookup_agg_func(f)[0]) for col_id in ids for f in func - ] - - if self._is_series: - # if series, no need to rebuild - result_cols_idx = pandas.Index( - [agg_ops.lookup_agg_func(f)[1] for f in func] - ) - else: - if self._block.column_labels.nlevels > 1: - # Restructure MultiIndex for proper format: (idx1, idx2, func) - # rather than ((idx1, idx2), func). - column_labels = [ - tuple(label) + (agg_ops.lookup_agg_func(f)[1],) - for label in labels.to_frame(index=False).to_numpy() - for f in func - ] - else: # Single-level index - column_labels = [ - (label, agg_ops.lookup_agg_func(f)[1]) - for label in labels - for f in func - ] - result_cols_idx = pandas.MultiIndex.from_tuples( - column_labels, names=[*self._block.column_labels.names, None] - ) - return self._apply_aggs(aggregations, result_cols_idx) - - def _apply_aggs( - self, exprs: Sequence[agg_expressions.Aggregation], labels: pandas.Index - ): - block, ids = self._block.apply_analytic( - agg_exprs=exprs, - window=self._window_spec, - result_labels=labels, - skip_null_groups=self._drop_null_groups, - ) - - if self._window_spec.grouping_keys: - original_index_ids = block.index_columns - block = block.reset_index(drop=False) - # grouping keys will always be direct column references, but we should probably - # refactor this class to enforce this statically - index_ids = ( - *[col.id.name for col in self._window_spec.grouping_keys], # type: ignore - *original_index_ids, - ) - block = block.set_index(col_ids=index_ids) - - if self._skip_agg_column_id is not None: - block = block.select_columns([self._skip_agg_column_id, *ids]) - else: - block = block.select_columns(ids).with_column_labels(labels) - - if self._is_series and (len(block.value_columns) == 1): - import bigframes.series as series - - return series.Series(block) - else: - import bigframes.dataframe as df - - return df.DataFrame(block) - - def _apply_aggregate_op( - self, - op: agg_ops.UnaryAggregateOp, - ): - ids, labels = self._aggregated_columns() - aggregations = [agg(col_id, op) for col_id in ids] - return self._apply_aggs(aggregations, labels) - - def _aggregated_columns(self) -> tuple[Sequence[str], pandas.Index]: - agg_col_ids = [ - col_id - for col_id in self._value_column_ids - if col_id != self._skip_agg_column_id - ] - labels: pandas.Index = pandas.Index( - [self._block.col_id_to_label[col] for col in agg_col_ids] - ) - return agg_col_ids, labels - - -def create_range_window( - block: blocks.Block, - window: pandas.Timedelta | numpy.timedelta64 | datetime.timedelta | str, - *, - value_column_ids: Sequence[str] = tuple(), - min_periods: int | None, - on: str | None = None, - closed: Literal["right", "left", "both", "neither"], - is_series: bool, - grouping_keys: Sequence[str] = tuple(), - drop_null_groups: bool = True, -) -> Window: - if on is None: - # Rolling on index - index_dtypes = block.index.dtypes - if len(index_dtypes) > 1: - raise ValueError("Range rolling on MultiIndex is not supported") - if index_dtypes[0] != dtypes.TIMESTAMP_DTYPE: - raise ValueError("Index type should be timestamps with timezones") - rolling_key_col_id = block.index_columns[0] - else: - # Rolling on a specific column - rolling_key_col_id = block.resolve_label_exact_or_error(on) - if block.expr.get_column_type(rolling_key_col_id) != dtypes.TIMESTAMP_DTYPE: - raise ValueError(f"Column {on} type should be timestamps with timezones") - - order_direction = window_ordering.find_order_direction( - block.expr.node, rolling_key_col_id - ) - if order_direction is None: - target_str = "index" if on is None else f"column {on}" - raise ValueError( - f"The {target_str} might not be in a monotonic order. Please sort by {target_str} before rolling." - ) - if isinstance(window, str): - window = pandas.Timedelta(window) - spec = window_spec.WindowSpec( - bounds=window_spec.RangeWindowBounds.from_timedelta_window(window, closed), - min_periods=1 if min_periods is None else min_periods, - ordering=( - ordering.OrderingExpression(ex.deref(rolling_key_col_id), order_direction), - ), - grouping_keys=tuple(ex.deref(col) for col in grouping_keys), - ) - - selected_value_col_ids = ( - value_column_ids if value_column_ids else block.value_columns - ) - # This step must be done after finding the order direction of the window key. - if grouping_keys: - block = block.order_by([ordering.ascending_over(col) for col in grouping_keys]) - - return Window( - block, - spec, - value_column_ids=selected_value_col_ids, - is_series=is_series, - skip_agg_column_id=None if on is None else rolling_key_col_id, - drop_null_groups=drop_null_groups, - ) - - -def agg(input: str, op: agg_ops.AggregateOp) -> agg_expressions.Aggregation: - if isinstance(op, agg_ops.UnaryAggregateOp): - return agg_expressions.UnaryAggregation(op, ex.deref(input)) - else: - assert isinstance(op, agg_ops.NullaryAggregateOp) - return agg_expressions.NullaryAggregation(op) diff --git a/bigframes/core/window_spec.py b/bigframes/core/window_spec.py index 509dd954b9f..3458bfb1b8c 100644 --- a/bigframes/core/window_spec.py +++ b/bigframes/core/window_spec.py @@ -11,305 +11,25 @@ # 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 datetime -import itertools -from dataclasses import dataclass, replace -from typing import Callable, Literal, Mapping, Optional, Sequence, Set, Tuple, Union +from dataclasses import dataclass +import typing -import numpy as np -import pandas as pd - -import bigframes.core.expression as ex -import bigframes.core.identifiers as ids import bigframes.core.ordering as orderings -# Unbound Windows -def unbound( - grouping_keys: Tuple[str, ...] = (), - min_periods: int = 0, - ordering: Tuple[orderings.OrderingExpression, ...] = (), -) -> WindowSpec: - """ - Create an unbound window. - - Args: - grouping_keys: - Columns ids of grouping keys - min_periods (int, default 0): - Minimum number of input rows to generate output. - ordering: - Orders the rows within the window. - - Returns: - WindowSpec - """ - return WindowSpec( - grouping_keys=tuple(map(ex.deref, grouping_keys)), - min_periods=min_periods, - ordering=ordering, - ) - - -### Rows-based Windows -def rows( - grouping_keys: Tuple[str, ...] = (), - start: Optional[int] = None, - end: Optional[int] = None, - min_periods: int = 0, - ordering: Tuple[orderings.OrderingExpression, ...] = (), -) -> WindowSpec: - """ - Create a row-bounded window. - - Args: - grouping_keys: - Columns ids of grouping keys - start: - The window's starting boundary relative to the current row. For example, "-1" means one row prior - "1" means one row after, and "0" means the current row. If None, the window is unbounded from the start. - following: - The window's ending boundary relative to the current row. For example, "-1" means one row prior - "1" means one row after, and "0" means the current row. If None, the window is unbounded until the end. - min_periods (int, default 0): - Minimum number of input rows to generate output. - ordering: - Ordering to apply on top of based dataframe ordering - Returns: - WindowSpec - """ - bounds = RowsWindowBounds( - start=start, - end=end, - ) - return WindowSpec( - grouping_keys=tuple(map(ex.deref, grouping_keys)), - bounds=bounds, - min_periods=min_periods, - ordering=ordering, - ) - - -def cumulative_rows( - grouping_keys: Tuple[str, ...] = (), min_periods: int = 0 -) -> WindowSpec: - """ - Create a expanding window that includes all preceding rows - - Args: - grouping_keys: - Columns ids of grouping keys - min_periods (int, default 0): - Minimum number of input rows to generate output. - Returns: - WindowSpec - """ - bounds = RowsWindowBounds(end=0) - return WindowSpec( - grouping_keys=tuple(map(ex.deref, grouping_keys)), - bounds=bounds, - min_periods=min_periods, - ) - - -def inverse_cumulative_rows( - grouping_keys: Tuple[str, ...] = (), min_periods: int = 0 -) -> WindowSpec: - """ - Create a shrinking window that includes all following rows - - Args: - grouping_keys: - Columns ids of grouping keys - min_periods (int, default 0): - Minimum number of input rows to generate output. - Returns: - WindowSpec - """ - bounds = RowsWindowBounds(start=0) - return WindowSpec( - grouping_keys=tuple(map(ex.deref, grouping_keys)), - bounds=bounds, - min_periods=min_periods, - ) - - -### Struct Classes - - -@dataclass(frozen=True) -class RowsWindowBounds: - start: Optional[int] = None - end: Optional[int] = None - - @classmethod - def from_window_size( - cls, window: int, closed: Literal["right", "left", "both", "neither"] - ) -> RowsWindowBounds: - if closed == "right": - return cls(-(window - 1), 0) - elif closed == "left": - return cls(-window, -1) - elif closed == "both": - return cls(-window, 0) - elif closed == "neither": - return cls(-(window - 1), -1) - else: - raise ValueError(f"Unsupported value for 'closed' parameter: {closed}") - - def __post_init__(self): - if self.start is None: - return - if self.end is None: - return - if self.start > self.end: - raise ValueError( - f"Invalid window: start({self.start}) is greater than end({self.end})" - ) - - -@dataclass(frozen=True) -class RangeWindowBounds: - """Represents a time range window, inclusively bounded by start and end""" - - start: pd.Timedelta | None = None - end: pd.Timedelta | None = None - - @classmethod - def from_timedelta_window( - cls, - window: pd.Timedelta | np.timedelta64 | datetime.timedelta, - closed: Literal["right", "left", "both", "neither"], - ) -> RangeWindowBounds: - window = pd.Timedelta(window) - tick = pd.Timedelta("1us") - zero = pd.Timedelta(0) - - if closed == "right": - return cls(-(window - tick), zero) - elif closed == "left": - return cls(-window, -tick) - elif closed == "both": - return cls(-window, zero) - elif closed == "neither": - return cls(-(window - tick), -tick) - else: - raise ValueError(f"Unsupported value for 'closed' parameter: {closed}") - - def __post_init__(self): - if self.start is None: - return - if self.end is None: - return - if self.start > self.end: - raise ValueError( - f"Invalid window: start({self.start}) is greater than end({self.end})" - ) - - @dataclass(frozen=True) class WindowSpec: """ Specifies a window over which aggregate and analytic function may be applied. - - Attributes: - grouping_keys: A set of columns to group on - bounds: The window boundaries - ordering: A list of columns ids and ordering direction to override base ordering - min_periods: The minimum number of observations in window required to have a value + grouping_keys: set of column ids to group on + preceding: Number of preceding rows in the window + following: Number of preceding rows in the window + ordering: List of columns ids and ordering direction to override base ordering """ - grouping_keys: Tuple[ex.Expression, ...] = tuple() - ordering: Tuple[orderings.OrderingExpression, ...] = tuple() - bounds: Union[RowsWindowBounds, RangeWindowBounds, None] = None + grouping_keys: typing.Tuple[str, ...] = tuple() + ordering: typing.Tuple[orderings.OrderingColumnReference, ...] = tuple() + preceding: typing.Optional[int] = None + following: typing.Optional[int] = None min_periods: int = 0 - - @property - def is_row_bounded(self): - """ - Whether the window is bounded by row offsets. - - This is relevant for determining whether the window requires a total order - to calculate deterministically. - """ - return isinstance(self.bounds, RowsWindowBounds) and ( - (self.bounds.start is not None) or (self.bounds.end is not None) - ) - - @property - def is_range_bounded(self): - """ - Whether the window is bounded by range offsets. - - This is relevant for determining whether the window requires a total order - to calculate deterministically. - """ - return isinstance(self.bounds, RangeWindowBounds) - - @property - def is_unbounded(self): - """ - Whether the window is unbounded. - - This is relevant for determining whether the window requires a total order - to calculate deterministically. - """ - return self.bounds is None or ( - 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]: - """ - Return list of all variables reference ind the window. - """ - ordering_vars = itertools.chain.from_iterable( - item.scalar_expression.column_references for item in self.ordering - ) - grouping_vars = itertools.chain.from_iterable( - item.column_references for item in self.grouping_keys - ) - return set(itertools.chain(grouping_vars, ordering_vars)) - - def without_order(self, force: bool = False) -> WindowSpec: - """Removes ordering clause if ordering isn't required to define bounds.""" - if self.is_row_bounded and not force: - raise ValueError("Cannot remove order from row-bounded window") - return replace(self, ordering=()) - - def remap_column_refs( - self, - mapping: Mapping[ids.ColumnId, ids.ColumnId], - allow_partial_bindings: bool = False, - ) -> WindowSpec: - return WindowSpec( - grouping_keys=tuple( - key.remap_column_refs(mapping, allow_partial_bindings) - for key in self.grouping_keys - ), - ordering=tuple( - order_part.remap_column_refs(mapping, allow_partial_bindings) - for order_part in self.ordering - ), - bounds=self.bounds, - min_periods=self.min_periods, - ) - - def transform_exprs( - self: WindowSpec, t: Callable[[ex.Expression], ex.Expression] - ) -> WindowSpec: - return WindowSpec( - grouping_keys=tuple(t(key) for key in self.grouping_keys), - ordering=tuple( - order_part.transform_exprs(t) for order_part in self.ordering - ), - bounds=self.bounds, - min_periods=self.min_periods, - ) diff --git a/bigframes/dataframe.py b/bigframes/dataframe.py index 8d05ba4ddb0..c5b48822fb1 100644 --- a/bigframes/dataframe.py +++ b/bigframes/dataframe.py @@ -17,19 +17,12 @@ from __future__ import annotations import datetime -import inspect -import itertools import re -import sys import textwrap import typing -import warnings from typing import ( - TYPE_CHECKING, - Any, Callable, Dict, - Hashable, Iterable, List, Literal, @@ -37,82 +30,47 @@ Optional, Sequence, Tuple, - TypeVar, Union, - cast, - overload, ) -import bigframes_vendored.constants as constants -import bigframes_vendored.pandas.core.frame as vendored_pandas_frame -import bigframes_vendored.pandas.pandas._typing as vendored_pandas_typing -import google.api_core.exceptions import google.cloud.bigquery as bigquery -import google.cloud.bigquery.job -import google.cloud.bigquery.table import numpy import pandas -import pyarrow -import tabulate -from pandas.api import extensions as pd_ext -import bigframes.constants +import bigframes +import bigframes._config.display_options as display_options +import bigframes.constants as constants import bigframes.core import bigframes.core.block_transforms as block_ops import bigframes.core.blocks as blocks -import bigframes.core.col -import bigframes.core.convert -import bigframes.core.explode -import bigframes.core.expression as ex import bigframes.core.groupby as groupby import bigframes.core.guid import bigframes.core.indexers as indexers import bigframes.core.indexes as indexes -import bigframes.core.interchange import bigframes.core.ordering as order import bigframes.core.utils as utils -import bigframes.core.validations as validations import bigframes.core.window -import bigframes.core.window_spec as windows import bigframes.dtypes -import bigframes.exceptions as bfe import bigframes.formatting_helpers as formatter -import bigframes.functions import bigframes.operations as ops import bigframes.operations.aggregations as agg_ops -import bigframes.operations.plotting as plotting -import bigframes.operations.structs import bigframes.series +import bigframes.series as bf_series import bigframes.session._io.bigquery -import bigframes.session.execution_spec as ex_spec -from bigframes._tools import docs -from bigframes.core import agg_expressions -from bigframes.core.logging import log_adapter -from bigframes.core.window import rolling -from bigframes.functions import function_typing +import third_party.bigframes_vendored.pandas.core.frame as vendored_pandas_frame +import third_party.bigframes_vendored.pandas.pandas._typing as vendored_pandas_typing -if TYPE_CHECKING: - from _typeshed import SupportsRichComparison - - import bigframes.extensions.bigframes.dataframe_accessor as bigquery_accessor +if typing.TYPE_CHECKING: import bigframes.session - SingleItemValue = Union[ - bigframes.series.Series, - int, - float, - str, - pandas.Timedelta, - Callable, - bigframes.core.col.Expression, - ] - MultiItemValue = Union[ - "DataFrame", Sequence[int | float | str | pandas.Timedelta | Callable] - ] - -U = TypeVar("U") -LevelType = typing.Hashable + +# BigQuery has 1 MB query size limit, 5000 items shouldn't take more than 10% of this depending on data type. +# TODO(tbergeron): Convert to bytes-based limit +MAX_INLINE_DF_SIZE = 5000 + +LevelType = typing.Union[str, int] LevelsType = typing.Union[LevelType, typing.Sequence[LevelType]] +SingleItemValue = Union[bigframes.series.Series, int, float, Callable] ERROR_IO_ONLY_GS_PATHS = f"Only Google Cloud Storage (gs://...) paths are supported. {constants.FEEDBACK_LINK}" ERROR_IO_REQUIRES_WILDCARD = ( @@ -123,13 +81,8 @@ # Inherits from pandas DataFrame so that we can use the same docstrings. -@log_adapter.class_logger -@docs.inherit_docs(vendored_pandas_frame.DataFrame) -class DataFrame: - # internal flag to disable cache at all - _disable_cache_override: bool = False - # Must be above 5000 for pandas to delegate to bigframes for binops - __pandas_priority__ = 15000 +class DataFrame(vendored_pandas_frame.DataFrame): + __doc__ = vendored_pandas_frame.DataFrame.__doc__ def __init__( self, @@ -143,18 +96,10 @@ def __init__( *, session: typing.Optional[bigframes.session.Session] = None, ): - global bigframes - - self._query_job: Optional[google.cloud.bigquery.job.QueryJob] = None - if copy is not None and not copy: raise ValueError( f"DataFrame constructor only supports copy=True. {constants.FEEDBACK_LINK}" ) - # Ignore object dtype if provided, as it provides no additional - # information about what BigQuery type to use. - if dtype is not None and bigframes.dtypes.is_object_like(dtype): - dtype = None # Check to see if constructing from BigQuery-backed objects before # falling back to pandas constructor @@ -169,13 +114,9 @@ def __init__( elif ( utils.is_dict_like(data) and len(data) >= 1 - and any( - isinstance(data[key], bigframes.series.Series) for key in data.keys() - ) + and any(isinstance(data[key], bf_series.Series) for key in data.keys()) ): - if not all( - isinstance(data[key], bigframes.series.Series) for key in data.keys() - ): + if not all(isinstance(data[key], bf_series.Series) for key in data.keys()): # TODO(tbergeron): Support local list/series data by converting to memtable. raise NotImplementedError( f"Cannot mix Series with other types. {constants.FEEDBACK_LINK}" @@ -183,42 +124,36 @@ def __init__( keys = list(data.keys()) first_label, first_series = keys[0], data[keys[0]] block = ( - typing.cast(bigframes.series.Series, first_series) + typing.cast(bf_series.Series, first_series) ._get_block() .with_column_labels([first_label]) ) for key in keys[1:]: - other = typing.cast(bigframes.series.Series, data[key]) + other = typing.cast(bf_series.Series, data[key]) other_block = other._block.with_column_labels([key]) # Pandas will keep original sorting if all indices are aligned. # We cannot detect this easily however, and so always sort on index - block, _ = block.join( # type:ignore - other_block, how="outer", sort=True + result_index, _ = block.index.join( # type:ignore + other_block.index, how="outer", sort=True ) + block = result_index._block if block: - if index is not None: - bf_index = indexes.Index(index) - idx_block = bf_index._block - idx_cols = idx_block.index_columns - block, (_, r_mapping) = block.reset_index().join( - bf_index._block.reset_index(), how="inner" + if index: + raise NotImplementedError( + "DataFrame 'index' constructor parameter not supported " + f"when passing BigQuery-backed objects. {constants.FEEDBACK_LINK}" ) - block = block.set_index([r_mapping[idx_col] for idx_col in idx_cols]) if columns: - column_ids = [ - block.resolve_label_exact_or_error(label) for label in list(columns) - ] - block = block.select_columns(column_ids) # type:ignore + block = block.select_columns(list(columns)) # type:ignore if dtype: - bf_dtype = bigframes.dtypes.bigframes_type(dtype) - block = block.multi_apply_unary_op(ops.AsTypeOp(to_type=bf_dtype)) + block = block.multi_apply_unary_op( + block.value_columns, ops.AsTypeOp(dtype) + ) + self._block = block else: - if isinstance(dtype, str) and dtype.lower() == "json": - dtype = bigframes.dtypes.JSON_DTYPE - import bigframes.pandas pd_dataframe = pandas.DataFrame( @@ -227,16 +162,21 @@ def __init__( columns=columns, # type:ignore dtype=dtype, # type:ignore ) - if session: - block = session.read_pandas(pd_dataframe)._get_block() + if ( + pd_dataframe.size < MAX_INLINE_DF_SIZE + # TODO(swast): Workaround data types limitation in inline data. + and not any( + dt.pyarrow_dtype + for dt in pd_dataframe.dtypes + if isinstance(dt, pandas.ArrowDtype) + ) + ): + self._block = blocks.block_from_local(pd_dataframe) + elif session: + self._block = session.read_pandas(pd_dataframe)._get_block() else: - block = bigframes.pandas.read_pandas(pd_dataframe)._get_block() - - # We use _block as an indicator in __getattr__ and __setattr__ to see - # if the object is fully initialized, so make sure we set the _block - # attribute last. - self._block = block - self._block.session._register_object(self) + self._block = bigframes.pandas.read_pandas(pd_dataframe)._get_block() + self._query_job: Optional[bigquery.QueryJob] = None def __dir__(self): return dir(type(self)) + [ @@ -271,7 +211,15 @@ def _find_indices( return [self._block.value_columns.index(col_id) for col_id in col_ids] def _resolve_label_exact(self, label) -> Optional[str]: - return self._block.resolve_label_exact(label) + """Returns the column id matching the label if there is exactly + one such column. If there are multiple columns with the same name, + raises an error. If there is no such column, returns None.""" + matches = self._block.label_to_col_id.get(label, []) + if len(matches) > 1: + raise ValueError( + f"Multiple columns matching id {label} were found. {constants.FEEDBACK_LINK}" + ) + return matches[0] if len(matches) != 0 else None def _sql_names( self, @@ -293,23 +241,12 @@ def _sql_names( return results @property - @validations.requires_index def index( self, ) -> indexes.Index: - return indexes.Index.from_frame(self) - - @index.setter - def index(self, value): - # TODO: Handle assigning MultiIndex - result = self._assign_single_item("_new_bf_index", value).set_index( - "_new_bf_index" - ) - self._set_block(result._get_block()) - self.index.name = value.name if hasattr(value, "name") else None + return indexes.Index(self) @property - @validations.requires_index def loc(self) -> indexers.LocDataFrameIndexer: return indexers.LocDataFrameIndexer(self) @@ -318,24 +255,20 @@ def iloc(self) -> indexers.ILocDataFrameIndexer: return indexers.ILocDataFrameIndexer(self) @property - @validations.requires_ordering() def iat(self) -> indexers.IatDataFrameIndexer: return indexers.IatDataFrameIndexer(self) @property - @validations.requires_index def at(self) -> indexers.AtDataFrameIndexer: return indexers.AtDataFrameIndexer(self) @property def dtypes(self) -> pandas.Series: - dtypes = self._block.dtypes - bigframes.dtypes.warn_on_db_dtypes_json_dtype(dtypes) - return pandas.Series(data=dtypes, index=self._block.column_labels) + return pandas.Series(data=self._block.dtypes, index=self._block.column_labels) @property def columns(self) -> pandas.Index: - return self._block.column_labels + return self.dtypes.index @columns.setter def columns(self, labels: pandas.Index): @@ -346,10 +279,6 @@ def columns(self, labels: pandas.Index): def shape(self) -> Tuple[int, int]: return self._block.shape - @property - def axes(self) -> list: - return [self.index, self.columns] - @property def size(self) -> int: rows, cols = self.shape @@ -367,132 +296,25 @@ def empty(self) -> bool: def values(self) -> numpy.ndarray: return self.to_numpy() - @property - def bqclient(self) -> bigframes.Session: - """BigQuery REST API Client the DataFrame uses for operations.""" - return self._session.bqclient - @property def _session(self) -> bigframes.Session: return self._get_block().expr.session - @property - def bigquery( - self, - ) -> bigquery_accessor.BigframesBigQueryDataFrameAccessor: - """ - Accessor for BigQuery functionality. - - Returns: - bigframes.extensions.core.dataframe_accessor.BigQueryDataFrameAccessor: - Accessor that exposes BigQuery functionality on a DataFrame, - with method names closer to SQL. - """ - # Import the accessor here to avoid circular imports. - import bigframes.extensions.bigframes.dataframe_accessor - - return bigframes.extensions.bigframes.dataframe_accessor.BigframesBigQueryDataFrameAccessor( - self - ) - - @property - def _has_index(self) -> bool: - return len(self._block.index_columns) > 0 - - @property - @validations.requires_ordering() - def T(self) -> DataFrame: - return DataFrame(self._get_block().transpose()) - - @validations.requires_index - @validations.requires_ordering() - def transpose(self) -> DataFrame: - return self.T - def __len__(self): rows, _ = self.shape return rows - def __bool__(self): - raise ValueError( - "Cannot convert dataframe into bool. Consider using .empty(), .any(), or .all() methods." - ) - def __iter__(self): return iter(self.columns) - def __contains__(self, key) -> bool: - return key in self.columns - def astype( self, - dtype: Union[ - bigframes.dtypes.DtypeString, - bigframes.dtypes.Dtype, - type, - dict[str, Union[bigframes.dtypes.DtypeString, bigframes.dtypes.Dtype]], - ], - *, - errors: Literal["raise", "null"] = "raise", + dtype: Union[bigframes.dtypes.DtypeString, bigframes.dtypes.Dtype], ) -> DataFrame: - if errors not in ["raise", "null"]: - raise ValueError("Arg 'error' must be one of 'raise' or 'null'") - - if isinstance(dtype, dict): - for col in dtype: - if col not in self.columns: - raise KeyError( - f"Only Column Names are allowed in dtypes dict. '{col}' is not in the columns." - ) - - safe_cast = errors == "null" - - exprs: list[ex.Expression] = [] - for col_id, col_label in zip( - self._block.value_columns, self._block.column_labels - ): - from_type = self._block._column_type(col_id) - - if isinstance(dtype, dict): - if col_label not in dtype: - exprs.append(ex.deref(col_id)) - continue - to_type = bigframes.dtypes.bigframes_type(dtype[col_label]) - else: - to_type = bigframes.dtypes.bigframes_type(dtype) - - op: ops.UnaryOp - if to_type == bigframes.dtypes.JSON_DTYPE: - op = ops.ToJSON(safe=safe_cast) - elif from_type == bigframes.dtypes.JSON_DTYPE: - op = ops.JSONDecode(to_type=to_type, safe=safe_cast) - else: - op = ops.AsTypeOp(to_type=to_type, safe=safe_cast) - - exprs.append(op.as_expr(ex.deref(col_id))) - - block = self._block.project_exprs(exprs, labels=self.columns, drop=True) - return DataFrame(block) - - def _should_sql_have_index(self) -> bool: - """Should the SQL we pass to BQML and other I/O include the index?""" - - return self._has_index and ( - self.index.name is not None or len(self.index.names) > 1 - ) - - def _to_placeholder_table( - self, dry_run: bool = False - ) -> google.cloud.bigquery.table.TableReference: - """Compiles this DataFrame's expression tree to SQL and saves it to a - (temporary) view or table (in the case of a dry run). - """ - return self._block.to_placeholder_table( - include_index=self._should_sql_have_index(), dry_run=dry_run - ) + return self._apply_unary_op(ops.AsTypeOp(dtype)) def _to_sql_query( - self, include_index: bool, enable_cache: bool = True + self, include_index: bool ) -> Tuple[str, list[str], list[blocks.Label]]: """Compiles this DataFrame's expression tree to SQL, optionally including index columns. @@ -502,198 +324,68 @@ def _to_sql_query( whether to include index columns. Returns: - Tuple[sql_string, index_column_id_list, index_column_label_list]: + a tuple of (sql_string, index_column_id_list, index_column_label_list). If include_index is set to False, index_column_id_list and index_column_label_list return empty lists. """ - return self._block.to_sql_query(include_index, enable_cache=enable_cache) + return self._block.to_sql_query(include_index) @property def sql(self) -> str: - """Compiles this DataFrame's expression tree to SQL. - - Returns: - str: - string representing the compiled SQL. - """ - try: - include_index = self._should_sql_have_index() - sql, _, _ = self._to_sql_query(include_index=include_index) - return sql - except AttributeError as e: - # Workaround for a development-mode debugging issue: - # An `AttributeError` originating *inside* this @property getter (e.g., due to - # a typo or referencing a non-existent attribute) can be mistakenly intercepted - # by the class's __getattr__ method if one is defined. - # We catch the AttributeError and raise SyntaxError instead to make it clear - # the error originates *here* in the property implementation. - # See: https://stackoverflow.com/questions/50542177/correct-handling-of-attributeerror-in-getattr-when-using-property - raise SyntaxError( - "AttributeError encountered. Please check the implementation for incorrect attribute access." - ) from e + """Compiles this DataFrame's expression tree to SQL.""" + sql, _, _ = self._to_sql_query(include_index=False) + return sql @property - def query_job(self) -> Optional[google.cloud.bigquery.job.QueryJob]: + def query_job(self) -> Optional[bigquery.QueryJob]: """BigQuery job metadata for the most recent query. Returns: - None or google.cloud.bigquery.job.QueryJob: - The most recent `QueryJob - `_. + The most recent `QueryJob + `_. """ if self._query_job is None: self._set_internal_query_job(self._compute_dry_run()) return self._query_job - def memory_usage(self, index: bool = True): - n_rows, _ = self.shape - # like pandas, treat all variable-size objects as just 8-byte pointers, ignoring actual object - column_sizes = self.dtypes.map( - lambda dtype: bigframes.dtypes.DTYPE_BYTE_SIZES.get(dtype, 8) * n_rows - ) - if index and self._has_index: - index_size = pandas.Series([self.index._memory_usage()], index=["Index"]) - column_sizes = pandas.concat([index_size, column_sizes]) - return column_sizes - - def info( - self, - verbose: Optional[bool] = None, - buf=None, - max_cols: Optional[int] = None, - memory_usage: Optional[bool] = None, - show_counts: Optional[bool] = None, - ): - obuf = buf or sys.stdout - - n_rows, n_columns = self.shape - - max_cols = ( - max_cols - if max_cols is not None - else bigframes.options.display.max_info_columns - ) - - show_all_columns = verbose if verbose is not None else (n_columns < max_cols) - - obuf.write(f"{type(self)}\n") - - if self._block.has_index: - index_type = "MultiIndex" if self.index.nlevels > 1 else "Index" - - index_stats = f"{n_rows} entries" - if n_rows > 0: - # These accessses are kind of expensive, maybe should try to skip? - first_indice = self.index[0] - last_indice = self.index[-1] - index_stats += f", {first_indice} to {last_indice}" - obuf.write(f"{index_type}: {index_stats}\n") - else: - obuf.write("NullIndex\n") - - if n_columns == 0: - # We don't display any more information if the dataframe has no columns - obuf.write("Empty DataFrame\n") - return - - dtype_strings = self.dtypes.astype("string") - if show_all_columns: - obuf.write(f"Data columns (total {n_columns} columns):\n") - column_info = self.columns.to_frame(name="Column") - - max_rows = bigframes.options.display.max_info_rows - too_many_rows = n_rows > max_rows if max_rows is not None else False - - if show_counts if show_counts is not None else (not too_many_rows): - non_null_counts = self.count().to_pandas() - column_info["Non-Null Count"] = non_null_counts.map( - lambda x: f"{int(x)} non-null" - ) - - column_info["Dtype"] = dtype_strings - - column_info = column_info.reset_index(drop=True) - column_info.index.name = "#" - - column_info_formatted = tabulate.tabulate(column_info, headers="keys") # type: ignore - obuf.write(column_info_formatted) - obuf.write("\n") - - else: # Just number of columns and first, last - obuf.write( - f"Columns: {n_columns} entries, {self.columns[0]} to {self.columns[-1]}\n" - ) - dtype_counts = dtype_strings.value_counts().sort_index(ascending=True).items() - dtype_counts_formatted = ", ".join( - f"{dtype}({count})" for dtype, count in dtype_counts - ) - obuf.write(f"dtypes: {dtype_counts_formatted}\n") - - show_memory = ( - memory_usage - if memory_usage is not None - else bigframes.options.display.memory_usage - ) - if show_memory: - # TODO: Convert to different units (kb, mb, etc.) - obuf.write(f"memory usage: {self.memory_usage().sum()} bytes\n") - - def select_dtypes(self, include=None, exclude=None) -> DataFrame: - # Create empty pandas dataframe with same schema and then leverage actual pandas implementation - as_pandas = pandas.DataFrame( - { - col_id: pandas.Series([], dtype=dtype) - for col_id, dtype in zip(self._block.value_columns, self._block.dtypes) - } - ) - selected_columns = tuple( - as_pandas.select_dtypes(include=include, exclude=exclude).columns - ) - return DataFrame(self._block.select_columns(selected_columns)) - - def _set_internal_query_job( - self, query_job: Optional[google.cloud.bigquery.job.QueryJob] - ): + def _set_internal_query_job(self, query_job: bigquery.QueryJob): self._query_job = query_job def __getitem__( self, key: Union[ blocks.Label, - List[str], - List[blocks.Label], + Sequence[blocks.Label], # Index of column labels can be treated the same as a sequence of column labels. pandas.Index, bigframes.series.Series, - slice, ], ): # No return type annotations (like pandas) as type cannot always be determined statically + """Gets the specified column(s) from the DataFrame.""" # NOTE: This implements the operations described in # https://pandas.pydata.org/docs/getting_started/intro_tutorials/03_subset_data.html - import bigframes.core.col - import bigframes.pandas - if isinstance(key, bigframes.pandas.Series): + if isinstance(key, bigframes.series.Series): return self._getitem_bool_series(key) - if isinstance(key, slice): - return self.iloc[key] - - if isinstance(key, bigframes.core.col.Expression): - return self.loc[key] - - # TODO(tswast): Fix this pylance warning: Class overlaps "Hashable" - # unsafely and could produce a match at runtime - if isinstance(key, blocks.Label): + if isinstance(key, typing.Hashable): return self._getitem_label(key) + # Select a subset of columns or re-order columns. + # In Ibis after you apply a projection, any column objects from the + # table before the projection can't be combined with column objects + # from the table after the projection. This is because the table after + # a projection is considered a totally separate table expression. + # + # This is unexpected behavior for a pandas user, who expects their old + # Series objects to still work with the new / mutated DataFrame. We + # avoid applying a projection in Ibis until it's absolutely necessary + # to provide pandas-like semantics. + # TODO(swast): Do we need to apply implicit join when doing a + # projection? - if utils.is_list_like(key): - return self._getitem_columns(key) - else: - # TODO(tswast): What case is this supposed to be handling? - return self._getitem_columns([cast(Hashable, key)]) + # Select a number of columns as DF. + key = key if utils.is_list_like(key) else [key] # type:ignore - def _getitem_columns(self, key: Sequence[blocks.Label]) -> DataFrame: selected_ids: Tuple[str, ...] = () for label in key: col_ids = self._block.label_to_col_id[label] @@ -704,9 +396,7 @@ def _getitem_columns(self, key: Sequence[blocks.Label]) -> DataFrame: def _getitem_label(self, key: blocks.Label): col_ids = self._block.cols_matching_label(key) if len(col_ids) == 0: - raise KeyError( - f"{key} not found in DataFrame columns: {self._block.column_labels}" - ) + raise KeyError(key) block = self._block.select_columns(col_ids) if isinstance(self.columns, pandas.MultiIndex): # Multiindex should drop-level if not selecting entire @@ -730,39 +420,20 @@ def _getitem_bool_series(self, key: bigframes.series.Series) -> DataFrame: f"Only boolean series currently supported for indexing. {constants.FEEDBACK_LINK}" ) # TODO: enforce stricter alignment - ( - combined_index, - ( - get_column_left, - get_column_right, - ), - ) = self._block.join(key._block, how="left") - block = combined_index + combined_index, ( + get_column_left, + get_column_right, + ) = self._block.index.join(key._block.index, how="left") + block = combined_index._block filter_col_id = get_column_right[key._value_column] - block = block.filter_by_id(filter_col_id) + block = block.filter(filter_col_id) block = block.drop_columns([filter_col_id]) return DataFrame(block) def __getattr__(self, key: str): - # To allow subclasses to set private attributes before the class is - # fully initialized, protect against recursion errors with - # uninitialized DataFrame objects. Note: this comes at the downside - # that columns with a leading `_` won't be treated as columns. - # - # See: - # https://github.com/googleapis/python-bigquery-dataframes/issues/728 - # and - # https://nedbatchelder.com/blog/201010/surprising_getattr_recursion.html - if key == "_block": - raise AttributeError(key) - if key in self._block.column_labels: return self.__getitem__(key) - - if hasattr(pandas.DataFrame, key): - log_adapter.submit_pandas_labels( - self._block.expr.session.bqclient, self.__class__.__name__, key - ) + elif hasattr(pandas.DataFrame, key): raise AttributeError( textwrap.dedent( f""" @@ -771,56 +442,18 @@ def __getattr__(self, key: str): """ ) ) - raise AttributeError(key) - - def __setattr__(self, key: str, value): - if key == "_block": - object.__setattr__(self, key, value) - return - - # To allow subclasses to set private attributes before the class is - # fully initialized, assume anything set before `_block` is initialized - # is a regular attribute. - if not hasattr(self, "_block"): - object.__setattr__(self, key, value) - return - - # If someone has a column named the same as a normal attribute - # (e.g. index), we want to set the normal attribute, not the column. - # To do that, check if there is a normal attribute by using - # __getattribute__ (not __getattr__, because that includes columns). - # If that returns a value without raising, then we know this is a - # normal attribute and we should prefer that. - try: - object.__getattribute__(self, key) - return object.__setattr__(self, key, value) - except AttributeError: - pass - - # If we made it here, then we know that it's not a regular attribute - # already, so it might be a column to update. Note: we don't allow - # adding new columns using __setattr__, only __setitem__, that way we - # can still add regular new attributes. - if key in self._block.column_labels: - self[key] = value else: - object.__setattr__(self, key, value) + raise AttributeError(key) def __repr__(self) -> str: """Converts a DataFrame to a string. Calls to_pandas. Only represents the first `bigframes.options.display.max_rows`. """ - # Protect against errors with uninitialized DataFrame. See: - # https://github.com/googleapis/python-bigquery-dataframes/issues/728 - if not hasattr(self, "_block"): - return object.__repr__(self) - opts = bigframes.options.display max_results = opts.max_rows if opts.repr_mode == "deferred": - return formatter.repr_query_job(self._compute_dry_run()) - + return formatter.repr_query_job(self.query_job) # TODO(swast): pass max_columns and get the true column count back. Maybe # get 1 more column than we have requested so that pandas can add the # ... for us? @@ -829,62 +462,60 @@ def __repr__(self) -> str: ) self._set_internal_query_job(query_job) - from bigframes.display import plaintext - - return plaintext.create_text_representation( - pandas_df, - row_count, - is_series=False, - has_index=self._has_index, - column_count=len(self.columns), - ) - def _prepare_display_df(self) -> DataFrame: - """Process ObjectRef and JSON/nested JSON columns for display.""" - import bigframes.bigquery as bbq - - df = self - # Arrow/Pandas to_pandas_batches does not support raw JSON/nested JSON - # columns. Pre-serialize them to string format to bypass this limit. - # Using TO_JSON_STRING via SqlScalarOp handles complex nested STRUCT - # types correctly. Use the offset so that we can handle duplicate and - # non-string column names. - json_col_indexes = [ - col_index - for col_index, col in enumerate(df.columns) - if bigframes.dtypes.contains_db_dtypes_json_dtype(df[col].dtype) - ] - if json_col_indexes: - df.iloc[:, json_col_indexes] = cast( - DataFrame, - df.iloc[:, json_col_indexes].apply(bbq.to_json_string), # type: ignore - ) - return df + column_count = len(pandas_df.columns) + + with display_options.pandas_repr(opts): + repr_string = repr(pandas_df) + + # Modify the end of the string to reflect count. + lines = repr_string.split("\n") + pattern = re.compile("\\[[0-9]+ rows x [0-9]+ columns\\]") + if pattern.match(lines[-1]): + lines = lines[:-2] + + if row_count > len(lines) - 1: + lines.append("...") - def _repr_mimebundle_(self, include=None, exclude=None): + lines.append("") + lines.append(f"[{row_count} rows x {column_count} columns]") + return "\n".join(lines) + + def _repr_html_(self) -> str: """ - Custom display method for IPython/Jupyter environments. - This is called by IPython's display system when the object is displayed. + Returns an html string primarily for use by notebooks for displaying + a representation of the DataFrame. Displays 20 rows by default since + many notebooks are not configured for large tables. """ - # TODO(b/467647693): Anywidget integration has been tested in Jupyter, VS Code, and - # BQ Studio, but there is a known compatibility issue with Marimo that needs to be addressed. - from bigframes.display import html + opts = bigframes.options.display + max_results = bigframes.options.display.max_rows + if opts.repr_mode == "deferred": + return formatter.repr_query_job_html(self.query_job) + # TODO(swast): pass max_columns and get the true column count back. Maybe + # get 1 more column than we have requested so that pandas can add the + # ... for us? + pandas_df, row_count, query_job = self._block.retrieve_repr_request_results( + max_results + ) - return html.repr_mimebundle(self, include=include, exclude=exclude) + self._set_internal_query_job(query_job) - def __delitem__(self, key: str): - df = self.drop(columns=[key]) - self._set_block(df._get_block()) + column_count = len(pandas_df.columns) - def __setitem__( - self, - key: str | list[str] | pandas.Index, - value: SingleItemValue | MultiItemValue, - ): - if isinstance(key, (list, pandas.Index)): - df = self._assign_multi_items(key, value) - else: - df = self._assign_single_item(key, value) + with display_options.pandas_repr(opts): + # _repr_html_ stub is missing so mypy thinks it's a Series. Ignore mypy. + html_string = pandas_df._repr_html_() # type:ignore + + html_string += f"[{row_count} rows x {column_count} columns in total]" + return html_string + + def __setitem__(self, key: str, value: SingleItemValue): + """Modify or insert a column into the DataFrame. + + Note: This does **not** modify the original table the DataFrame was + derived from. + """ + df = self._assign_single_item(key, value) self._set_block(df._get_block()) def _apply_binop( @@ -893,142 +524,146 @@ def _apply_binop( op, axis: str | int = "columns", how: str = "outer", - reverse: bool = False, ): - if isinstance(other, bigframes.dtypes.LOCAL_SCALAR_TYPES): - return self._apply_scalar_binop(other, op, reverse=reverse) + if isinstance(other, (float, int)): + return self._apply_scalar_binop(other, op) + elif isinstance(other, bigframes.series.Series): + return self._apply_series_binop(other, op, axis=axis, how=how) elif isinstance(other, DataFrame): - return self._apply_dataframe_binop(other, op, how=how, reverse=reverse) - elif isinstance(other, pandas.DataFrame): - return self._apply_dataframe_binop( - DataFrame(other), op, how=how, reverse=reverse - ) - elif utils.get_axis_number(axis) == 0: - return self._apply_series_binop_axis_0(other, op, how, reverse) - elif utils.get_axis_number(axis) == 1: - return self._apply_series_binop_axis_1(other, op, how, reverse) + return self._apply_dataframe_binop(other, op, how=how) raise NotImplementedError( f"binary operation is not implemented on the second operand of type {type(other).__name__}." f"{constants.FEEDBACK_LINK}" ) - def _apply_scalar_binop( - self, - other: bigframes.dtypes.LOCAL_SCALAR_TYPE, - op: ops.BinaryOp, - reverse: bool = False, - ) -> DataFrame: - if reverse: - expr = op.as_expr( - left_input=ex.const(other), - right_input=ex.free_var("var1"), - ) - else: - expr = op.as_expr( - left_input=ex.free_var("var1"), - right_input=ex.const(other), - ) - return DataFrame(self._block.multi_apply_unary_op(expr)) + def _apply_scalar_binop(self, other: float | int, op: ops.BinaryOp) -> DataFrame: + block = self._block + partial_op = ops.BinopPartialRight(op, other) + for column_id, label in zip( + self._block.value_columns, self._block.column_labels + ): + block, _ = block.apply_unary_op(column_id, partial_op, result_label=label) + block = block.drop_columns([column_id]) + return DataFrame(block) - def _apply_series_binop_axis_0( + def _apply_series_binop( self, - other, + other: bigframes.series.Series, op: ops.BinaryOp, + axis: str | int = "columns", how: str = "outer", - reverse: bool = False, ) -> DataFrame: - bf_series = bigframes.core.convert.to_bf_series( - other, self.index if self._has_index else None, self._session - ) - aligned_block, columns, expr_pairs = self._block._align_axis_0( - bf_series._block, how=how - ) - result = aligned_block._apply_binop( - op, inputs=expr_pairs, labels=columns, reverse=reverse + if axis not in ("columns", "index", 0, 1): + raise ValueError(f"Invalid input: axis {axis}.") + + if axis in ("columns", 1): + raise NotImplementedError( + f"Row Series operations haven't been supported. {constants.FEEDBACK_LINK}" + ) + + joined_index, (get_column_left, get_column_right) = self._block.index.join( + other._block.index, how=how ) - return DataFrame(result) - def _apply_series_binop_axis_1( - self, - other, - op: ops.BinaryOp, - how: str = "outer", - reverse: bool = False, - ) -> DataFrame: - """Align dataframe with pandas series by inlining series values as literals.""" - # If we already know the transposed schema (from the transpose cache), we don't need to materialize rows from other - # Instead, can fully defer execution (as a cross-join) - if ( - isinstance(other, bigframes.series.Series) - and other._block._transpose_cache is not None + series_column_id = other._value_column + series_col = get_column_right[series_column_id] + block = joined_index._block + for column_id, label in zip( + self._block.value_columns, self._block.column_labels ): - aligned_block, columns, expr_pairs = self._block._align_series_block_axis_1( - other._block, how=how - ) - else: - # Fallback path, materialize `other` locally - pd_series = bigframes.core.convert.to_pd_series(other, self.columns) - aligned_block, columns, expr_pairs = self._block._align_pd_series_axis_1( - pd_series, how=how + block, _ = block.apply_binary_op( + get_column_left[column_id], + series_col, + op, + result_label=label, ) - result = aligned_block._apply_binop( - op, inputs=expr_pairs, labels=columns, reverse=reverse - ) - return DataFrame(result) + block = block.drop_columns([get_column_left[column_id]]) + + block = block.drop_columns([series_col]) + block = block.with_index_labels(self.index.names) + return DataFrame(block) def _apply_dataframe_binop( - self, - other: DataFrame, - op: ops.BinaryOp, - how: str = "outer", - reverse: bool = False, + self, other: DataFrame, op: ops.BinaryOp, how: str = "outer" ) -> DataFrame: - aligned_block, columns, expr_pairs = self._block._align_both_axes( - other._block, how=how + # Join rows + joined_index, (get_column_left, get_column_right) = self._block.index.join( + other._block.index, how=how ) - result = aligned_block._apply_binop( - op, inputs=expr_pairs, labels=columns, reverse=reverse + # join columns schema + # indexers will be none for exact match + columns, lcol_indexer, rcol_indexer = self.columns.join( + other.columns, how=how, return_indexers=True ) - return DataFrame(result) + + binop_result_ids = [] + block = joined_index._block + + column_indices = zip( + lcol_indexer if (lcol_indexer is not None) else range(len(columns)), + rcol_indexer if (lcol_indexer is not None) else range(len(columns)), + ) + + for left_index, right_index in column_indices: + if left_index >= 0 and right_index >= 0: # -1 indices indicate missing + left_col_id = self._block.value_columns[left_index] + right_col_id = other._block.value_columns[right_index] + block, result_col_id = block.apply_binary_op( + get_column_left[left_col_id], + get_column_right[right_col_id], + op, + ) + binop_result_ids.append(result_col_id) + elif left_index >= 0: + left_col_id = self._block.value_columns[left_index] + block, result_col_id = block.apply_unary_op( + get_column_left[left_col_id], + ops.partial_right(op, None), + ) + binop_result_ids.append(result_col_id) + elif right_index >= 0: + right_col_id = other._block.value_columns[right_index] + block, result_col_id = block.apply_unary_op( + get_column_right[right_col_id], + ops.partial_left(op, None), + ) + binop_result_ids.append(result_col_id) + else: + # Should not be possible + raise ValueError("No right or left index.") + + block = block.select_columns(binop_result_ids).with_column_labels(columns) + return DataFrame(block) def eq(self, other: typing.Any, axis: str | int = "columns") -> DataFrame: return self._apply_binop(other, ops.eq_op, axis=axis) - def __eq__(self, other) -> DataFrame: # type: ignore - return self.eq(other) - def ne(self, other: typing.Any, axis: str | int = "columns") -> DataFrame: return self._apply_binop(other, ops.ne_op, axis=axis) - def __ne__(self, other) -> DataFrame: # type: ignore - return self.ne(other) + __eq__ = eq # type: ignore - def __invert__(self) -> DataFrame: - return self._apply_unary_op(ops.invert_op) + __ne__ = ne # type: ignore def le(self, other: typing.Any, axis: str | int = "columns") -> DataFrame: return self._apply_binop(other, ops.le_op, axis=axis) - def __le__(self, other) -> DataFrame: - return self.le(other) - def lt(self, other: typing.Any, axis: str | int = "columns") -> DataFrame: return self._apply_binop(other, ops.lt_op, axis=axis) - def __lt__(self, other) -> DataFrame: - return self.lt(other) - def ge(self, other: typing.Any, axis: str | int = "columns") -> DataFrame: return self._apply_binop(other, ops.ge_op, axis=axis) - def __ge__(self, other) -> DataFrame: - return self.ge(other) - def gt(self, other: typing.Any, axis: str | int = "columns") -> DataFrame: return self._apply_binop(other, ops.gt_op, axis=axis) - def __gt__(self, other) -> DataFrame: - return self.gt(other) + __lt__ = lt + + __le__ = le + + __gt__ = gt + + __ge__ = ge def add( self, @@ -1039,20 +674,7 @@ def add( # TODO(swast): Support level parameter with MultiIndex. return self._apply_binop(other, ops.add_op, axis=axis) - def radd( - self, - other: float | int | bigframes.series.Series | DataFrame, - axis: str | int = "columns", - ) -> DataFrame: - # TODO(swast): Support fill_value parameter. - # TODO(swast): Support level parameter with MultiIndex. - return self._apply_binop(other, ops.add_op, axis=axis, reverse=True) - - def __add__(self, other) -> DataFrame: - return self.add(other) - - def __radd__(self, other) -> DataFrame: - return self.radd(other) + __radd__ = __add__ = radd = add def sub( self, @@ -1061,20 +683,16 @@ def sub( ) -> DataFrame: return self._apply_binop(other, ops.sub_op, axis=axis) - subtract = sub - - def __sub__(self, other): - return self.sub(other) + __sub__ = subtract = sub def rsub( self, other: float | int | bigframes.series.Series | DataFrame, axis: str | int = "columns", ) -> DataFrame: - return self._apply_binop(other, ops.sub_op, axis=axis, reverse=True) + return self._apply_binop(other, ops.reverse(ops.sub_op), axis=axis) - def __rsub__(self, other): - return self.rsub(other) + __rsub__ = rsub def mul( self, @@ -1083,20 +701,7 @@ def mul( ) -> DataFrame: return self._apply_binop(other, ops.mul_op, axis=axis) - multiply = mul - - def __mul__(self, other): - return self.mul(other) - - def rmul( - self, - other: float | int | bigframes.series.Series | DataFrame, - axis: str | int = "columns", - ) -> DataFrame: - return self.mul(other, axis=axis) - - def __rmul__(self, other): - return self.rmul(other) + __rmul__ = __mul__ = rmul = multiply = mul def truediv( self, @@ -1105,22 +710,16 @@ def truediv( ) -> DataFrame: return self._apply_binop(other, ops.div_op, axis=axis) - div = divide = truediv - - def __truediv__(self, other): - return self.truediv(other) + div = divide = __truediv__ = truediv def rtruediv( self, other: float | int | bigframes.series.Series | DataFrame, axis: str | int = "columns", ) -> DataFrame: - return self._apply_binop(other, ops.div_op, axis=axis, reverse=True) - - rdiv = rtruediv + return self._apply_binop(other, ops.reverse(ops.div_op), axis=axis) - def __rtruediv__(self, other): - return self.rtruediv(other) + __rtruediv__ = rdiv = rtruediv def floordiv( self, @@ -1129,78 +728,40 @@ def floordiv( ) -> DataFrame: return self._apply_binop(other, ops.floordiv_op, axis=axis) - def __floordiv__(self, other): - return self.floordiv(other) + __floordiv__ = floordiv def rfloordiv( self, other: float | int | bigframes.series.Series | DataFrame, axis: str | int = "columns", ) -> DataFrame: - return self._apply_binop(other, ops.floordiv_op, axis=axis, reverse=True) + return self._apply_binop(other, ops.reverse(ops.floordiv_op), axis=axis) - def __rfloordiv__(self, other): - return self.rfloordiv(other) + __rfloordiv__ = rfloordiv - def mod( - self, - other: int | bigframes.series.Series | DataFrame, - axis: str | int = "columns", - ) -> DataFrame: # type: ignore + def mod(self, other: int | bigframes.series.Series | DataFrame, axis: str | int = "columns") -> DataFrame: # type: ignore return self._apply_binop(other, ops.mod_op, axis=axis) - def __mod__(self, other): - return self.mod(other) + def rmod(self, other: int | bigframes.series.Series | DataFrame, axis: str | int = "columns") -> DataFrame: # type: ignore + return self._apply_binop(other, ops.reverse(ops.mod_op), axis=axis) - def rmod( - self, - other: int | bigframes.series.Series | DataFrame, - axis: str | int = "columns", - ) -> DataFrame: # type: ignore - return self._apply_binop(other, ops.mod_op, axis=axis, reverse=True) + __mod__ = mod - def __rmod__(self, other): - return self.rmod(other) + __rmod__ = rmod def pow( self, other: int | bigframes.series.Series, axis: str | int = "columns" ) -> DataFrame: return self._apply_binop(other, ops.pow_op, axis=axis) - def __pow__(self, other): - return self.pow(other) - def rpow( self, other: int | bigframes.series.Series, axis: str | int = "columns" ) -> DataFrame: - return self._apply_binop(other, ops.pow_op, axis=axis, reverse=True) - - def __rpow__(self, other): - return self.rpow(other) - - def __and__(self, other: bool | int | bigframes.series.Series) -> DataFrame: - return self._apply_binop(other, ops.and_op) - - __rand__ = __and__ - - def __or__(self, other: bool | int | bigframes.series.Series) -> DataFrame: - return self._apply_binop(other, ops.or_op) - - __ror__ = __or__ - - def __xor__(self, other: bool | int | bigframes.series.Series) -> DataFrame: - return self._apply_binop(other, ops.xor_op) - - __rxor__ = __xor__ + return self._apply_binop(other, ops.reverse(ops.pow_op), axis=axis) - def __pos__(self) -> DataFrame: - return self._apply_unary_op(ops.pos_op) + __pow__ = pow - def __neg__(self) -> DataFrame: - return self._apply_unary_op(ops.neg_op) - - def __abs__(self) -> DataFrame: - return self._apply_unary_op(ops.abs_op) + __rpow__ = rpow def align( self, @@ -1286,7 +847,7 @@ def combine( results.append(result) if all([isinstance(val, bigframes.series.Series) for val in results]): - import bigframes.core.reshape.api as rs + import bigframes.core.reshape as rs return rs.concat(results, axis=1) else: @@ -1295,613 +856,61 @@ def combine( def combine_first(self, other: DataFrame): return self._apply_dataframe_binop(other, ops.fillna_op) - def _fast_stat_matrix(self, op: agg_ops.BinaryAggregateOp) -> DataFrame: - """Faster corr, cov calculations, but creates more sql text, so cannot scale to many columns""" - assert len(self.columns) * len(self.columns) < bigframes.constants.MAX_COLUMNS - orig_columns = self.columns - frame = self.copy() - # Replace column names with 0 to n - 1 to keep order - # and avoid the influence of duplicated column name - frame.columns = pandas.Index(range(len(orig_columns))) - frame = frame.astype(bigframes.dtypes.FLOAT_DTYPE) - block = frame._block - - aggregations = [ - agg_expressions.BinaryAggregation( - op, ex.deref(left_col), ex.deref(right_col) - ) - for left_col in block.value_columns - for right_col in block.value_columns - ] - # unique columns stops - uniq_orig_columns = utils.combine_indices( - orig_columns, pandas.Index(range(len(orig_columns))) + def to_pandas( + self, + max_download_size: Optional[int] = None, + sampling_method: Optional[str] = None, + random_state: Optional[int] = None, + ) -> pandas.DataFrame: + """Write DataFrame to pandas DataFrame. + + Args: + max_download_size (int, default None): + Download size threshold in MB. If max_download_size is exceeded when downloading data + (e.g., to_pandas()), the data will be downsampled if + bigframes.options.sampling.enable_downsampling is True, otherwise, an error will be + raised. If set to a value other than None, this will supersede the global config. + sampling_method (str, default None): + Downsampling algorithms to be chosen from, the choices are: "head": This algorithm + returns a portion of the data from the beginning. It is fast and requires minimal + computations to perform the downsampling; "uniform": This algorithm returns uniform + random samples of the data. If set to a value other than None, this will supersede + the global config. + random_state (int, default None): + The seed for the uniform downsampling algorithm. If provided, the uniform method may + take longer to execute and require more computation. If set to a value other than + None, this will supersede the global config. + + Returns: + pandas.DataFrame: A pandas DataFrame with all rows and columns of this DataFrame if the + data_sampling_threshold_mb is not exceeded; otherwise, a pandas DataFrame with + downsampled rows and all columns of this DataFrame. + """ + # TODO(orrbradford): Optimize this in future. Potentially some cases where we can return the stored query job + df, query_job = self._block.to_pandas( + max_download_size=max_download_size, + sampling_method=sampling_method, + random_state=random_state, ) - labels = utils.cross_indices(uniq_orig_columns, uniq_orig_columns) + self._set_internal_query_job(query_job) + return df.set_axis(self._block.column_labels, axis=1, copy=False) - block = block.aggregate(aggregations=aggregations, column_labels=labels) + def to_pandas_batches(self) -> Iterable[pandas.DataFrame]: + """Stream DataFrame results to an iterable of pandas DataFrame""" + return self._block.to_pandas_batches() - block = block.stack(levels=orig_columns.nlevels + 1) - # The aggregate operation crated a index level with just 0, need to drop it - # Also, drop the last level of each index, which was created to guarantee uniqueness - return DataFrame(block).droplevel(0).droplevel(-1, axis=0).droplevel(-1, axis=1) + def _compute_dry_run(self) -> bigquery.QueryJob: + return self._block._compute_dry_run() - def corr(self, method="pearson", min_periods=None, numeric_only=False) -> DataFrame: - if method != "pearson": - raise NotImplementedError( - f"Only Pearson correlation is currently supported. {constants.FEEDBACK_LINK}" - ) - if min_periods: - raise NotImplementedError( - f"min_periods not yet supported. {constants.FEEDBACK_LINK}" - ) + def copy(self) -> DataFrame: + return DataFrame(self._block) - if not numeric_only: - frame = self._raise_on_non_numeric("corr") - else: - frame = self._drop_non_numeric() - - if len(frame.columns) <= 30: - return frame._fast_stat_matrix(agg_ops.CorrOp()) - - frame = frame.copy() - orig_columns = frame.columns - # Replace column names with 0 to n - 1 to keep order - # and avoid the influence of duplicated column name - frame.columns = pandas.Index(range(len(orig_columns))) - frame = frame.astype(bigframes.dtypes.FLOAT_DTYPE) - block = frame._block - - # A new column that uniquely identifies each row - block, ordering_col = frame._block.promote_offsets(label="_bigframes_idx") - - val_col_ids = [ - col_id for col_id in block.value_columns if col_id != ordering_col - ] - - block = block.melt( - [ordering_col], val_col_ids, ["_bigframes_variable"], "_bigframes_value" - ) - - block = block.merge( - block, - left_join_ids=[ordering_col], - right_join_ids=[ordering_col], - how="inner", - sort=False, - ) - - frame = DataFrame(block).dropna( - subset=["_bigframes_value_x", "_bigframes_value_y"] - ) - - paired_mean_frame = ( - frame.groupby(["_bigframes_variable_x", "_bigframes_variable_y"]) - .agg( - _bigframes_paired_mean_x=bigframes.pandas.NamedAgg( - column="_bigframes_value_x", aggfunc="mean" - ), - _bigframes_paired_mean_y=bigframes.pandas.NamedAgg( - column="_bigframes_value_y", aggfunc="mean" - ), - ) - .reset_index() - ) - - frame = frame.merge( - paired_mean_frame, on=["_bigframes_variable_x", "_bigframes_variable_y"] - ) - frame["_bigframes_value_x"] -= frame["_bigframes_paired_mean_x"] - frame["_bigframes_value_y"] -= frame["_bigframes_paired_mean_y"] - - frame["_bigframes_dividend"] = ( - frame["_bigframes_value_x"] * frame["_bigframes_value_y"] - ) - frame["_bigframes_x_square"] = ( - frame["_bigframes_value_x"] * frame["_bigframes_value_x"] - ) - frame["_bigframes_y_square"] = ( - frame["_bigframes_value_y"] * frame["_bigframes_value_y"] - ) - - result = ( - frame.groupby(["_bigframes_variable_x", "_bigframes_variable_y"]) - .agg( - _bigframes_dividend_sum=bigframes.pandas.NamedAgg( - column="_bigframes_dividend", aggfunc="sum" - ), - _bigframes_x_square_sum=bigframes.pandas.NamedAgg( - column="_bigframes_x_square", aggfunc="sum" - ), - _bigframes_y_square_sum=bigframes.pandas.NamedAgg( - column="_bigframes_y_square", aggfunc="sum" - ), - ) - .reset_index() - ) - result["_bigframes_corr"] = result["_bigframes_dividend_sum"] / ( - ( - result["_bigframes_x_square_sum"] * result["_bigframes_y_square_sum"] - )._apply_unary_op(ops.sqrt_op) - ) - result = result._pivot( - index="_bigframes_variable_x", - columns="_bigframes_variable_y", - values="_bigframes_corr", - ) - - map_data = { - f"_bigframes_level_{i}": orig_columns.get_level_values(i) - for i in range(orig_columns.nlevels) - } - map_data["_bigframes_keys"] = range(len(orig_columns)) - map_df = bigframes.dataframe.DataFrame( - map_data, - session=self._get_block().expr.session, - ).set_index("_bigframes_keys") - result = result.join(map_df).sort_index() - index_columns = [f"_bigframes_level_{i}" for i in range(orig_columns.nlevels)] - result = result.set_index(index_columns) - result.index.names = orig_columns.names - result.columns = orig_columns - - return result - - def cov(self, *, numeric_only: bool = False) -> DataFrame: - if not numeric_only: - frame = self._raise_on_non_numeric("corr") - else: - frame = self._drop_non_numeric() - - if len(frame.columns) <= 30: - return frame._fast_stat_matrix(agg_ops.CovOp()) - - frame = frame.copy() - orig_columns = frame.columns - # Replace column names with 0 to n - 1 to keep order - # and avoid the influence of duplicated column name - frame.columns = pandas.Index(range(len(orig_columns))) - frame = frame.astype(bigframes.dtypes.FLOAT_DTYPE) - block = frame._block - - # A new column that uniquely identifies each row - block, ordering_col = frame._block.promote_offsets(label="_bigframes_idx") - - val_col_ids = [ - col_id for col_id in block.value_columns if col_id != ordering_col - ] - - block = block.melt( - [ordering_col], val_col_ids, ["_bigframes_variable"], "_bigframes_value" - ) - block = block.merge( - block, - left_join_ids=[ordering_col], - right_join_ids=[ordering_col], - how="inner", - sort=False, - ) - - frame = DataFrame(block).dropna( - subset=["_bigframes_value_x", "_bigframes_value_y"] - ) - - paired_mean_frame = ( - frame.groupby(["_bigframes_variable_x", "_bigframes_variable_y"]) - .agg( - _bigframes_paired_mean_x=bigframes.pandas.NamedAgg( - column="_bigframes_value_x", aggfunc="mean" - ), - _bigframes_paired_mean_y=bigframes.pandas.NamedAgg( - column="_bigframes_value_y", aggfunc="mean" - ), - ) - .reset_index() - ) - - frame = frame.merge( - paired_mean_frame, on=["_bigframes_variable_x", "_bigframes_variable_y"] - ) - frame["_bigframes_value_x"] -= frame["_bigframes_paired_mean_x"] - frame["_bigframes_value_y"] -= frame["_bigframes_paired_mean_y"] - - frame["_bigframes_dividend"] = ( - frame["_bigframes_value_x"] * frame["_bigframes_value_y"] - ) - - result = ( - frame.groupby(["_bigframes_variable_x", "_bigframes_variable_y"]) - .agg( - _bigframes_dividend_sum=bigframes.pandas.NamedAgg( - column="_bigframes_dividend", aggfunc="sum" - ), - _bigframes_dividend_count=bigframes.pandas.NamedAgg( - column="_bigframes_dividend", aggfunc="count" - ), - ) - .reset_index() - ) - result["_bigframes_cov"] = result["_bigframes_dividend_sum"] / ( - result["_bigframes_dividend_count"] - 1 - ) - result = result._pivot( - index="_bigframes_variable_x", - columns="_bigframes_variable_y", - values="_bigframes_cov", - ) - - map_data = { - f"_bigframes_level_{i}": orig_columns.get_level_values(i) - for i in range(orig_columns.nlevels) - } - map_data["_bigframes_keys"] = range(len(orig_columns)) - map_df = bigframes.dataframe.DataFrame( - map_data, - session=self._get_block().expr.session, - ).set_index("_bigframes_keys") - result = result.join(map_df).sort_index() - index_columns = [f"_bigframes_level_{i}" for i in range(orig_columns.nlevels)] - result = result.set_index(index_columns) - result.index.names = orig_columns.names - result.columns = orig_columns - - return result - - def corrwith( - self, - other: typing.Union[DataFrame, bigframes.series.Series], - *, - numeric_only: bool = False, - ): - other_frame = other if isinstance(other, DataFrame) else other.to_frame() - if numeric_only: - l_frame = self._drop_non_numeric() - r_frame = other_frame._drop_non_numeric() - else: - l_frame = self._raise_on_non_numeric("corrwith") - r_frame = other_frame._raise_on_non_numeric("corrwith") - - l_block = l_frame.astype(bigframes.dtypes.FLOAT_DTYPE)._block - r_block = r_frame.astype(bigframes.dtypes.FLOAT_DTYPE)._block - - if isinstance(other, DataFrame): - block, labels, expr_pairs = l_block._align_both_axes(r_block, how="inner") - else: - assert isinstance(other, bigframes.series.Series) - block, labels, expr_pairs = l_block._align_axis_0(r_block, how="inner") - - na_cols = l_block.column_labels.join( - r_block.column_labels, how="outer" - ).difference(labels) - - block = block.aggregate( - aggregations=tuple( - agg_expressions.BinaryAggregation(agg_ops.CorrOp(), left_ex, right_ex) - for left_ex, right_ex in expr_pairs - ), - column_labels=labels, - ) - block = block.project_exprs( - (ex.const(float("nan")),) * len(na_cols), labels=na_cols - ) - block = block.transpose( - original_row_index=pandas.Index([None]), single_row_mode=True - ) - return bigframes.pandas.Series(block) - - def __dataframe__( - self, nan_as_null: bool = False, allow_copy: bool = True - ) -> bigframes.core.interchange.InterchangeDataFrame: - return bigframes.core.interchange.InterchangeDataFrame._from_bigframes(self) - - def to_arrow( - self, - *, - ordered: bool = True, - allow_large_results: Optional[bool] = None, - ) -> pyarrow.Table: - """Write DataFrame to an Arrow table / record batch. - - Args: - ordered (bool, default True): - Determines whether the resulting Arrow table will be ordered. - In some cases, unordered may result in a faster-executing query. - allow_large_results (bool, default None): - If not None, overrides the global setting to allow or disallow large query results - over the default size limit of 10 GB. - - Returns: - pyarrow.Table: A pyarrow Table with all rows and columns of this DataFrame. - """ - msg = bfe.format_message( - "to_arrow is in preview. Types and unnamed or duplicate name columns may " - "change in future." - ) - warnings.warn(msg, category=bfe.PreviewWarning) - - pa_table, query_job = self._block.to_arrow( - ordered=ordered, allow_large_results=allow_large_results - ) - if query_job: - self._set_internal_query_job(query_job) - return pa_table - - @overload - def to_pandas( # type: ignore[overload-overlap] - self, - max_download_size: Optional[int] = ..., - sampling_method: Optional[str] = ..., - random_state: Optional[int] = ..., - *, - ordered: bool = ..., - dry_run: Literal[False] = ..., - allow_large_results: Optional[bool] = ..., - ) -> pandas.DataFrame: ... - - @overload - def to_pandas( - self, - max_download_size: Optional[int] = ..., - sampling_method: Optional[str] = ..., - random_state: Optional[int] = ..., - *, - ordered: bool = ..., - dry_run: Literal[True] = ..., - allow_large_results: Optional[bool] = ..., - ) -> pandas.Series: ... - - def to_pandas( - self, - max_download_size: Optional[int] = None, - sampling_method: Optional[str] = None, - random_state: Optional[int] = None, - *, - ordered: bool = True, - dry_run: bool = False, - allow_large_results: Optional[bool] = None, - ) -> pandas.DataFrame | pandas.Series: - """Write DataFrame to pandas DataFrame. - - **Examples:** - - >>> df = bpd.DataFrame({'col': [4, 2, 2]}) - - Download the data from BigQuery and convert it into an in-memory pandas DataFrame. - - >>> df.to_pandas() - col - 0 4 - 1 2 - 2 2 - - Estimate job statistics without processing or downloading data by using `dry_run=True`. - - >>> df.to_pandas(dry_run=True) # doctest: +SKIP - columnCount 1 - columnDtypes {'col': Int64} - indexLevel 1 - indexDtypes [Int64] - projectId bigframes-dev - location US - jobType QUERY - destinationTable {'projectId': 'bigframes-dev', 'datasetId': '_... - useLegacySql False - referencedTables None - totalBytesProcessed 0 - cacheHit False - statementType SELECT - creationTime 2025-04-02 20:17:12.038000+00:00 - dtype: object - - Args: - max_download_size (int, default None): - .. deprecated:: 2.0.0 - ``max_download_size`` parameter is deprecated. Please use ``to_pandas_batches()`` - method instead. - - Download size threshold in MB. If ``max_download_size`` is exceeded when downloading data, - the data will be downsampled if ``bigframes.options.sampling.enable_downsampling`` is - ``True``, otherwise, an error will be raised. If set to a value other than ``None``, - this will supersede the global config. - sampling_method (str, default None): - .. deprecated:: 2.0.0 - ``sampling_method`` parameter is deprecated. Please use ``sample()`` method instead. - - Downsampling algorithms to be chosen from, the choices are: "head": This algorithm - returns a portion of the data from the beginning. It is fast and requires minimal - computations to perform the downsampling; "uniform": This algorithm returns uniform - random samples of the data. If set to a value other than None, this will supersede - the global config. - random_state (int, default None): - .. deprecated:: 2.0.0 - ``random_state`` parameter is deprecated. Please use ``sample()`` method instead. - - The seed for the uniform downsampling algorithm. If provided, the uniform method may - take longer to execute and require more computation. If set to a value other than - None, this will supersede the global config. - ordered (bool, default True): - Determines whether the resulting pandas dataframe will be ordered. - In some cases, unordered may result in a faster-executing query. - dry_run (bool, default False): - If this argument is true, this method will not process the data. Instead, it returns - a Pandas Series containing dry run statistics - allow_large_results (bool, default None): - If not None, overrides the global setting to allow or disallow large query results - over the default size limit of 10 GB. - - Returns: - pandas.DataFrame: A pandas DataFrame with all rows and columns of this DataFrame if the - data_sampling_threshold_mb is not exceeded; otherwise, a pandas DataFrame with - downsampled rows and all columns of this DataFrame. If dry_run is set, a pandas - Series containing dry run statistics will be returned. - """ - if max_download_size is not None: - msg = bfe.format_message( - "DEPRECATED: The `max_download_size` parameters for `DataFrame.to_pandas()` " - "are deprecated and will be removed soon. Please use `DataFrame.to_pandas_batches()`." - ) - warnings.warn(msg, category=FutureWarning) - if sampling_method is not None or random_state is not None: - msg = bfe.format_message( - "DEPRECATED: The `sampling_method` and `random_state` parameters for " - "`DataFrame.to_pandas()` are deprecated and will be removed soon. " - "Please use `DataFrame.sample().to_pandas()` instead for sampling." - ) - warnings.warn(msg, category=FutureWarning, stacklevel=2) - - if dry_run: - dry_run_stats, dry_run_job = self._block._compute_dry_run( - max_download_size=max_download_size, - sampling_method=sampling_method, - random_state=random_state, - ordered=ordered, - ) - self._set_internal_query_job(dry_run_job) - return dry_run_stats - - df, query_job = self._block.to_pandas( - max_download_size=max_download_size, - sampling_method=sampling_method, - random_state=random_state, - ordered=ordered, - allow_large_results=allow_large_results, - ) - if query_job: - self._set_internal_query_job(query_job) - df.columns = self._block.column_labels - return df - - def to_pandas_batches( - self, - page_size: Optional[int] = None, - max_results: Optional[int] = None, - *, - allow_large_results: Optional[bool] = None, - cell_execution_count: Optional[int] = None, - ) -> blocks.PandasBatches: - """Stream DataFrame results to an iterable of pandas DataFrame. - - page_size and max_results determine the size and number of batches, - see https://cloud.google.com/python/docs/reference/bigquery/latest/google.cloud.bigquery.job.QueryJob#google_cloud_bigquery_job_QueryJob_result - - **Examples:** - - >>> df = bpd.DataFrame({'col': [4, 3, 2, 2, 3]}) - - Iterate through the results in batches, limiting the total rows yielded - across all batches via `max_results`: - - >>> for df_batch in df.to_pandas_batches(max_results=3): - ... print(df_batch) - col - 0 4 - 1 3 - 2 2 - - Alternatively, control the approximate size of each batch using `page_size` - and fetch batches manually using `next()`: - - >>> it = df.to_pandas_batches(page_size=2) - >>> next(it) - col - 0 4 - 1 3 - >>> next(it) - col - 2 2 - 3 2 - - Args: - page_size (int, default None): - The maximum number of rows of each batch. Non-positive values are ignored. - max_results (int, default None): - The maximum total number of rows of all batches. - allow_large_results (bool, default None): - If not None, overrides the global setting to allow or disallow large query results - over the default size limit of 10 GB. - - Returns: - Iterable[pandas.DataFrame]: - An iterable of smaller dataframes which combine to - form the original dataframe. Results stream from bigquery, - see https://cloud.google.com/python/docs/reference/bigquery/latest/google.cloud.bigquery.table.RowIterator#google_cloud_bigquery_table_RowIterator_to_arrow_iterable - """ - return self._to_pandas_batches( - page_size=page_size, - max_results=max_results, - allow_large_results=allow_large_results, - cell_execution_count=cell_execution_count, - ) - - def _to_pandas_batches( - self, - page_size: Optional[int] = None, - max_results: Optional[int] = None, - *, - allow_large_results: Optional[bool] = None, - cell_execution_count: Optional[int] = None, - ) -> blocks.PandasBatches: - return self._block.to_pandas_batches( - page_size=page_size, - max_results=max_results, - allow_large_results=allow_large_results, - cell_execution_count=cell_execution_count, - ) - - def _compute_dry_run(self) -> google.cloud.bigquery.job.QueryJob: - _, query_job = self._block._compute_dry_run() - return query_job - - def copy(self) -> DataFrame: - return DataFrame(self._block) - - @validations.requires_ordering(bigframes.constants.SUGGEST_PEEK_PREVIEW) def head(self, n: int = 5) -> DataFrame: return typing.cast(DataFrame, self.iloc[:n]) - @validations.requires_ordering() def tail(self, n: int = 5) -> DataFrame: return typing.cast(DataFrame, self.iloc[-n:]) - def peek( - self, n: int = 5, *, force: bool = True, allow_large_results=None - ) -> pandas.DataFrame: - """ - Preview n arbitrary rows from the dataframe. No guarantees about row selection or ordering. - ``DataFrame.peek(force=False)`` will always be very fast, but will not succeed if data requires - full data scanning. Using ``force=True`` will always succeed, but may be perform queries. - Query results will be cached so that future steps will benefit from these queries. - - Args: - n (int, default 5): - The number of rows to select from the dataframe. Which N rows are returned is non-deterministic. - force (bool, default True): - If the data cannot be peeked efficiently, the dataframe will instead be fully materialized as part - of the operation if ``force=True``. If ``force=False``, the operation will throw a ValueError. - allow_large_results (bool, default None): - If not None, overrides the global setting to allow or disallow large query results - over the default size limit of 10 GB. - Returns: - pandas.DataFrame: A pandas DataFrame with n rows. - - Raises: - ValueError: If force=False and data cannot be efficiently peeked. - """ - maybe_result = self._block.try_peek(n, allow_large_results=allow_large_results) - if maybe_result is None: - if force: - self._cached() - maybe_result = self._block.try_peek( - n, force=True, allow_large_results=allow_large_results - ) - assert maybe_result is not None - else: - raise ValueError( - "Cannot peek efficiently when data has aggregates, joins or window functions applied. Use force=True to fully compute dataframe." - ) - maybe_result.columns = self._block.column_labels - return maybe_result - def nlargest( self, n: int, @@ -1910,8 +919,6 @@ def nlargest( ) -> DataFrame: if keep not in ("first", "last", "all"): raise ValueError("'keep must be one of 'first', 'last', or 'all'") - if keep != "all": - validations.enforce_ordered(self, "nlargest") column_ids = self._sql_names(columns) return DataFrame(block_ops.nlargest(self._block, n, column_ids, keep=keep)) @@ -1923,40 +930,9 @@ def nsmallest( ) -> DataFrame: if keep not in ("first", "last", "all"): raise ValueError("'keep must be one of 'first', 'last', or 'all'") - if keep != "all": - validations.enforce_ordered(self, "nlargest") column_ids = self._sql_names(columns) return DataFrame(block_ops.nsmallest(self._block, n, column_ids, keep=keep)) - def insert( - self, - loc: int, - column: blocks.Label, - value: SingleItemValue, - allow_duplicates: bool = False, - ): - column_count = len(self.columns) - if loc > column_count: - raise IndexError( - f"Column index {loc} is out of bounds with {column_count} total columns." - ) - if (column in self.columns) and not allow_duplicates: - raise ValueError(f"cannot insert {column}, already exists") - - temp_column = bigframes.core.guid.generate_guid(prefix=str(column)) - df = self._assign_single_item(temp_column, value) - - block = df._get_block() - value_columns = typing.cast(List, block.value_columns) - value_columns, new_column = value_columns[:-1], value_columns[-1] - value_columns.insert(loc, new_column) - - block = block.select_columns(value_columns) - block = block.rename(columns={temp_column: column}) - - self._set_block(block) - - @overload def drop( self, labels: typing.Any = None, @@ -1965,31 +941,7 @@ def drop( index: typing.Any = None, columns: Union[blocks.Label, Sequence[blocks.Label]] = None, level: typing.Optional[LevelType] = None, - inplace: Literal[False] = False, - ) -> DataFrame: ... - - @overload - def drop( - self, - labels: typing.Any = None, - *, - axis: typing.Union[int, str] = 0, - index: typing.Any = None, - columns: Union[blocks.Label, Sequence[blocks.Label]] = None, - level: typing.Optional[LevelType] = None, - inplace: Literal[True], - ) -> None: ... - - def drop( - self, - labels: typing.Any = None, - *, - axis: typing.Union[int, str] = 0, - index: typing.Any = None, - columns: Union[blocks.Label, Sequence[blocks.Label]] = None, - level: typing.Optional[LevelType] = None, - inplace: bool = False, - ) -> Optional[DataFrame]: + ) -> DataFrame: if labels: if index or columns: raise ValueError("Cannot specify both 'labels' and 'index'/'columns") @@ -2001,72 +953,45 @@ def drop( block = self._block if index is not None: - self._throw_if_null_index("drop(axis=0)") level_id = self._resolve_levels(level or 0)[0] if utils.is_list_like(index): - # Only tuple is treated as multi-index value combinations - if isinstance(index, tuple): - if level is not None: - raise ValueError("Multi-index tuple can't specify level.") - condition_id = None - for i, idx in enumerate(index): - level_id = self._resolve_levels(i)[0] - block, condition_id_cur = block.project_expr( - ops.ne_op.as_expr(level_id, ex.const(idx)) - ) - if condition_id: - block, condition_id = block.apply_binary_op( - condition_id, condition_id_cur, ops.or_op - ) - else: - condition_id = condition_id_cur - - condition_id = typing.cast(str, condition_id) - else: - block, inverse_condition_id = block.apply_unary_op( - level_id, ops.IsInOp(values=tuple(index), match_nulls=True) - ) - block, condition_id = block.apply_unary_op( - inverse_condition_id, ops.invert_op - ) + block, inverse_condition_id = block.apply_unary_op( + level_id, ops.IsInOp(index, match_nulls=True) + ) + block, condition_id = block.apply_unary_op( + inverse_condition_id, ops.invert_op + ) elif isinstance(index, indexes.Index): - dropped_block = self._drop_by_index(index)._get_block() - if inplace: - self._set_block(dropped_block) - return None - return DataFrame(dropped_block) + return self._drop_by_index(index) else: - block, condition_id = block.project_expr( - ops.ne_op.as_expr(level_id, ex.const(index)) + block, condition_id = block.apply_unary_op( + level_id, ops.partial_right(ops.ne_op, index) ) - block = block.filter_by_id(condition_id, keep_null=True).select_columns( + block = block.filter(condition_id, keep_null=True).select_columns( self._block.value_columns ) if columns: block = block.drop_columns(self._sql_names(columns)) if index is None and not columns: raise ValueError("Must specify 'labels' or 'index'/'columns") - - if inplace: - self._set_block(block) - return None - else: - return DataFrame(block) + return DataFrame(block) def _drop_by_index(self, index: indexes.Index) -> DataFrame: - block = index._block + block = index._data._get_block() block, ordering_col = block.promote_offsets() - joined_index, (get_column_left, get_column_right) = self._block.join(block) + joined_index, (get_column_left, get_column_right) = self._block.index.join( + block.index + ) new_ordering_col = get_column_right[ordering_col] - drop_block = joined_index + drop_block = joined_index._block drop_block, drop_col = drop_block.apply_unary_op( new_ordering_col, ops.isnull_op, ) - drop_block = drop_block.filter_by_id(drop_col) + drop_block = drop_block.filter(drop_col) original_columns = [ get_column_left[column] for column in self._block.value_columns ] @@ -2119,63 +1044,17 @@ def reorder_levels(self, order: LevelsType, axis: int | str = 0): raise ValueError("Columns must be a multiindex to reorder levels.") def _resolve_levels(self, level: LevelsType) -> typing.Sequence[str]: - return self._block.index.resolve_level(level) - - @overload - def rename(self, *, columns: Mapping[blocks.Label, blocks.Label]) -> DataFrame: ... - - @overload - def rename( - self, *, columns: Mapping[blocks.Label, blocks.Label], inplace: Literal[False] - ) -> DataFrame: ... + return self._block.resolve_index_level(level) - @overload - def rename( - self, *, columns: Mapping[blocks.Label, blocks.Label], inplace: Literal[True] - ) -> None: ... - - def rename( - self, *, columns: Mapping[blocks.Label, blocks.Label], inplace: bool = False - ) -> Optional[DataFrame]: + def rename(self, *, columns: Mapping[blocks.Label, blocks.Label]) -> DataFrame: block = self._block.rename(columns=columns) - - if inplace: - self._block = block - return None - else: - return DataFrame(block) - - @overload - def rename_axis( - self, - mapper: typing.Union[blocks.Label, typing.Sequence[blocks.Label]], - ) -> DataFrame: ... - - @overload - def rename_axis( - self, - mapper: typing.Union[blocks.Label, typing.Sequence[blocks.Label]], - *, - inplace: Literal[False], - **kwargs, - ) -> DataFrame: ... - - @overload - def rename_axis( - self, - mapper: typing.Union[blocks.Label, typing.Sequence[blocks.Label]], - *, - inplace: Literal[True], - **kwargs, - ) -> None: ... + return DataFrame(block) def rename_axis( self, mapper: typing.Union[blocks.Label, typing.Sequence[blocks.Label]], - *, - inplace: bool = False, **kwargs, - ) -> Optional[DataFrame]: + ) -> DataFrame: if len(kwargs) != 0: raise NotImplementedError( f"rename_axis does not currently support any keyword arguments. {constants.FEEDBACK_LINK}" @@ -2185,16 +1064,8 @@ def rename_axis( labels = mapper else: labels = [mapper] + return DataFrame(self._block.with_index_labels(labels)) - block = self._block.with_index_labels(labels) - - if inplace: - self._block = block - return None - else: - return DataFrame(block) - - @validations.requires_ordering() def equals(self, other: typing.Union[bigframes.series.Series, DataFrame]) -> bool: # Must be same object type, same column dtypes, and same label values if not isinstance(other, DataFrame): @@ -2214,17 +1085,10 @@ def assign(self, **kwargs) -> DataFrame: def _assign_single_item( self, k: str, - v: SingleItemValue | MultiItemValue, + v: SingleItemValue, ) -> DataFrame: if isinstance(v, bigframes.series.Series): return self._assign_series_join_on_index(k, v) - elif isinstance(v, bigframes.core.col.Expression): - label_to_col_ref = { - label: ex.deref(id) for id, label in self._block.col_id_to_label.items() - } - resolved_expr = v._value.bind_variables(label_to_col_ref) - block = self._block.project_block_exprs([resolved_expr], labels=[k]) - return DataFrame(block) elif isinstance(v, bigframes.dataframe.DataFrame): v_df_col_count = len(v._block.value_columns) if v_df_col_count != 1: @@ -2239,95 +1103,10 @@ def _assign_single_item( elif utils.is_list_like(v): return self._assign_single_item_listlike(k, v) else: - return self._assign_scalar(k, v) # type: ignore - - def _assign_single_item_by_offset( - self, - offset: int, - value: SingleItemValue | MultiItemValue, - ) -> DataFrame: - if isinstance(value, bigframes.series.Series): - return self._assign_series_join_on_index_by_offset(offset, value) - elif isinstance(value, bigframes.core.col.Expression): - label_to_col_ref = { - label: ex.deref(id) for id, label in self._block.col_id_to_label.items() - } - resolved_expr = value._value.bind_variables(label_to_col_ref) - block, new_col_id = self._block.project_expr(resolved_expr) - target_col_id = self._block.value_columns[offset] - block = block.copy_values(new_col_id, target_col_id).drop_columns( - [new_col_id] - ) - return DataFrame(block) - elif isinstance(value, DataFrame): - v_df_col_count = len(value._block.value_columns) - if v_df_col_count != 1: - raise ValueError( - f"Cannot set a DataFrame with {v_df_col_count} columns to the single column at offset {offset}" - ) - return self._assign_series_join_on_index_by_offset( - offset, cast(bigframes.series.Series, value[value.columns[0]]) - ) - elif callable(value): - raise NotImplementedError( - "Callable assignment is not supported by column offset." - ) - elif utils.is_list_like(value): - return self._assign_single_item_listlike_by_offset(offset, value) - else: - return self._assign_scalar_by_offset(offset, value) # type: ignore - - def _assign_multi_items_helper( - self, - k: Sequence[Any] | pandas.Index, - v: SingleItemValue | MultiItemValue, - assign_single_fn: Callable[[DataFrame, Any, Any], DataFrame], - ) -> DataFrame: - value_sources: Sequence[Any] = [] - if isinstance(v, DataFrame): - value_sources = [v[col] for col in v.columns] - elif isinstance(v, bigframes.series.Series): - # For behavior consistency with Pandas. - raise ValueError("Columns must be same length as key") - elif isinstance(v, Sequence): - value_sources = v - else: - # We assign the same scalar value to all target columns. - value_sources = [v] * len(k) - - if len(value_sources) != len(k): - raise ValueError("Columns must be same length as key") - - # Repeatedly assign columns in order. - result = assign_single_fn(self, k[0], value_sources[0]) - for target, source in zip(k[1:], value_sources[1:]): - result = assign_single_fn(result, target, source) - return result - - def _assign_multi_items( - self, - k: list[str] | pandas.Index, - v: SingleItemValue | MultiItemValue, - ) -> DataFrame: - return self._assign_multi_items_helper(k, v, DataFrame._assign_single_item) - - def _assign_multi_items_by_offsets( - self, - k: Sequence[int], - v: SingleItemValue | MultiItemValue, - ) -> DataFrame: - return self._assign_multi_items_helper( - k, v, DataFrame._assign_single_item_by_offset - ) + return self._assign_scalar(k, v) - _assign_multi_items_by_offset = _assign_multi_items_by_offsets - _assign_multi_items_by_label = _assign_multi_items - _assign_multi_items_by_labels = _assign_multi_items - - def _assign_single_item_listlike_to_col_ids( - self, col_ids: Sequence[str], label: Optional[str], value: Sequence - ) -> DataFrame: - given_rows = len(value) + def _assign_single_item_listlike(self, k: str, v: Sequence) -> DataFrame: + given_rows = len(v) actual_rows = len(self) assigning_to_empty_df = len(self.columns) == 0 and actual_rows == 0 if not assigning_to_empty_df and given_rows != actual_rows: @@ -2335,13 +1114,8 @@ def _assign_single_item_listlike_to_col_ids( f"Length of values ({given_rows}) does not match length of index ({actual_rows})" ) - temp_col_name = ( - label - if label is not None - else bigframes.core.guid.generate_guid("listlike_col_") - ) - local_df = DataFrame( - {temp_col_name: value}, session=self._get_block().expr.session + local_df = bigframes.dataframe.DataFrame( + {k: v}, session=self._get_block().expr.session ) # local_df is likely (but not guaranteed) to be cached locally # since the original list came from memory and so is probably < MAX_INLINE_DF_SIZE @@ -2350,55 +1124,27 @@ def _assign_single_item_listlike_to_col_ids( original_index_column_ids = self._block.index_columns self_block = self._block.reset_index(drop=False) if assigning_to_empty_df: - if label is None: - raise ValueError( - "Label required when assigning listlike to empty DataFrame." - ) if len(self._block.index_columns) > 1: # match error raised by pandas here raise ValueError( "Assigning listlike to a first column under multiindex is not supported." ) - result_block = new_column_block.with_index_labels(self._block.index.names) - result_block = result_block.with_column_labels([label]) + result_block = new_column_block.with_index_labels(self._block.index_labels) + result_block = result_block.with_column_labels([k]) else: - ( - result_block, - ( - get_column_left, - get_column_right, - ), - ) = self_block.join(new_column_block, how="left", block_identity_join=True) + result_index, (get_column_left, get_column_right,) = self_block.index.join( + new_column_block.index, how="left", block_identity_join=True + ) + result_block = result_index._block result_block = result_block.set_index( [get_column_left[col_id] for col_id in original_index_column_ids], - index_labels=self._block.index.names, + index_labels=self._block.index_labels, ) - src_col = get_column_right[new_column_block.value_columns[0]] - # Check to see if key exists, and modify in place - for col_id in col_ids: - result_block = result_block.copy_values( - src_col, get_column_left[col_id] - ) - if len(col_ids) > 0: - result_block = result_block.drop_columns([src_col]) return DataFrame(result_block) - def _assign_single_item_listlike(self, k: str, v: Sequence) -> DataFrame: - col_ids = self._block.cols_matching_label(k) - return self._assign_single_item_listlike_to_col_ids(col_ids, k, v) - - def _assign_single_item_listlike_by_offset( - self, offset: int, value: Sequence - ) -> DataFrame: - col_ids = [self._block.value_columns[offset]] - return self._assign_single_item_listlike_to_col_ids(col_ids, None, value) + def _assign_scalar(self, label: str, value: Union[int, float]) -> DataFrame: + col_ids = self._block.cols_matching_label(label) - def _assign_scalar_to_col_ids( - self, - col_ids: Sequence[str], - label: Optional[str], - value: Union[int, float, str], - ) -> DataFrame: block, constant_col_id = self._block.create_constant(value, label) for col_id in col_ids: block = block.copy_values(constant_col_id, col_id) @@ -2408,131 +1154,37 @@ def _assign_scalar_to_col_ids( return DataFrame(block) - def _assign_scalar(self, label: str, value: Union[int, float, str]) -> DataFrame: - col_ids = self._block.cols_matching_label(label) - return self._assign_scalar_to_col_ids(col_ids, label, value) - - def _assign_scalar_by_offset( - self, offset: int, value: Union[int, float, str] - ) -> DataFrame: - col_ids = [self._block.value_columns[offset]] - return self._assign_scalar_to_col_ids(col_ids, None, value) - - def _assign_series_join_on_index_to_col_ids( - self, - column_ids: Sequence[str], - label: Optional[str], - series: bigframes.series.Series, + def _assign_series_join_on_index( + self, label: str, series: bigframes.series.Series ) -> DataFrame: - block, (get_column_left, get_column_right) = self._block.join( - series._block, how="left" + joined_index, (get_column_left, get_column_right) = self._block.index.join( + series._block.index, how="left" ) - mapped_column_ids = [get_column_left[col_id] for col_id in column_ids] + column_ids = [ + get_column_left[col_id] for col_id in self._block.cols_matching_label(label) + ] + block = joined_index._block source_column = get_column_right[series._value_column] - # Replace each column matching the ids - for column_id in mapped_column_ids: - block = block.copy_values(source_column, column_id) - if label is not None: - block = block.assign_label(column_id, label) + # Replace each column matching the label + for column_id in column_ids: + block = block.copy_values(source_column, column_id).assign_label( + column_id, label + ) - if not mapped_column_ids: - if label is None: - raise ValueError( - "Label required when appending a new column from Series." - ) + if not column_ids: # Append case, so new column needs appropriate label block = block.assign_label(source_column, label) else: # Update case, remove after copying into columns block = block.drop_columns([source_column]) - return DataFrame(block.with_index_labels(self._block.index.names)) - - def _assign_series_join_on_index( - self, label: str, series: bigframes.series.Series - ) -> DataFrame: - column_ids = self._block.cols_matching_label(label) - return self._assign_series_join_on_index_to_col_ids(column_ids, label, series) - - def _assign_series_join_on_index_by_offset( - self, offset: int, series: bigframes.series.Series - ) -> DataFrame: - column_ids = [self._block.value_columns[offset]] - return self._assign_series_join_on_index_to_col_ids(column_ids, None, series) + return DataFrame(block.with_index_labels(self.index.names)) - @overload # type: ignore[override] - def reset_index( - self, - level: blocks.LevelsType = ..., - drop: bool = ..., - inplace: Literal[False] = ..., - col_level: Union[int, str] = ..., - col_fill: Hashable = ..., - allow_duplicates: Optional[bool] = ..., - names: Union[None, Hashable, Sequence[Hashable]] = ..., - ) -> DataFrame: ... - - @overload - def reset_index( - self, - level: blocks.LevelsType = ..., - drop: bool = ..., - inplace: Literal[True] = ..., - col_level: Union[int, str] = ..., - col_fill: Hashable = ..., - allow_duplicates: Optional[bool] = ..., - names: Union[None, Hashable, Sequence[Hashable]] = ..., - ) -> None: ... - - @overload - def reset_index( - self, - level: blocks.LevelsType = None, - drop: bool = False, - inplace: bool = ..., - col_level: Union[int, str] = ..., - col_fill: Hashable = ..., - allow_duplicates: Optional[bool] = ..., - names: Union[None, Hashable, Sequence[Hashable]] = ..., - ) -> Optional[DataFrame]: ... - - def reset_index( - self, - level: blocks.LevelsType = None, - drop: bool = False, - inplace: bool = False, - col_level: Union[int, str] = 0, - col_fill: Hashable = "", - allow_duplicates: Optional[bool] = None, - names: Union[None, Hashable, Sequence[Hashable]] = None, - ) -> Optional[DataFrame]: - block = self._block - if names is not None: - if isinstance(names, blocks.Label) and not isinstance(names, tuple): - names = [names] - else: - names = list(names) - - if len(names) != self.index.nlevels: - raise ValueError("'names' must be same length as levels") - - block = block.with_index_labels(names) - if allow_duplicates is None: - allow_duplicates = False - block = block.reset_index( - level, - drop, - col_level=col_level, - col_fill=col_fill, - allow_duplicates=allow_duplicates, - ) - if inplace: - self._set_block(block) - return None - else: - return DataFrame(block) + def reset_index(self, *, drop: bool = False) -> DataFrame: + block = self._block.reset_index(drop) + return DataFrame(block) def set_index( self, @@ -2552,101 +1204,30 @@ def set_index( col_ids_strs: List[str] = [col_id for col_id in col_ids if col_id is not None] return DataFrame(self._block.set_index(col_ids_strs, append=append, drop=drop)) - @overload # type: ignore[override] - def sort_index( - self, - *, - ascending: bool = ..., - inplace: Literal[False] = ..., - kind: str | None = ..., - na_position: Literal["first", "last"] = ..., - ) -> DataFrame: ... - - @overload - def sort_index( - self, - *, - ascending: bool = ..., - inplace: Literal[True] = ..., - kind: str | None = ..., - na_position: Literal["first", "last"] = ..., - ) -> None: ... - def sort_index( - self, - *, - axis: Union[int, str] = 0, - ascending: bool = True, - inplace: bool = False, - kind: str | None = None, - na_position: Literal["first", "last"] = "last", - ) -> Optional[DataFrame]: - if utils.get_axis_number(axis) == 0: - if na_position not in ["first", "last"]: - raise ValueError("Param na_position must be one of 'first' or 'last'") - na_last = na_position == "last" - index_columns = self._block.index_columns - ordering = [ - order.ascending_over(column, na_last) - if ascending - else order.descending_over(column, na_last) - for column in index_columns - ] - is_stable = ( - kind or constants.DEFAULT_SORT_KIND - ) in constants.STABLE_SORT_KINDS - block = self._block.order_by(ordering, stable=is_stable) - else: # axis=1 - _, indexer = self.columns.sort_values( - return_indexer=True, - ascending=ascending, - na_position=na_position, # type: ignore - ) - block = self._block.select_columns( - [self._block.value_columns[i] for i in indexer] - ) - if inplace: - self._set_block(block) - return None - else: - return DataFrame(block) - - @overload # type: ignore[override] - def sort_values( - self, - by: str | typing.Sequence[str], - *, - inplace: Literal[False] = ..., - ascending: bool | typing.Sequence[bool] = ..., - kind: str | None = ..., - na_position: typing.Literal["first", "last"] = ..., - ) -> DataFrame: ... - - @overload - def sort_values( - self, - by: str | typing.Sequence[str], - *, - inplace: Literal[True] = ..., - ascending: bool | typing.Sequence[bool] = ..., - kind: str | None = ..., - na_position: typing.Literal["first", "last"] = ..., - ) -> None: ... + self, ascending: bool = True, na_position: Literal["first", "last"] = "last" + ) -> DataFrame: + if na_position not in ["first", "last"]: + raise ValueError("Param na_position must be one of 'first' or 'last'") + direction = ( + order.OrderingDirection.ASC if ascending else order.OrderingDirection.DESC + ) + na_last = na_position == "last" + index_columns = self._block.index_columns + ordering = [ + order.OrderingColumnReference(column, direction=direction, na_last=na_last) + for column in index_columns + ] + return DataFrame(self._block.order_by(ordering)) def sort_values( self, by: str | typing.Sequence[str], *, - inplace: bool = False, ascending: bool | typing.Sequence[bool] = True, - kind: str | None = None, + kind: str = "quicksort", na_position: typing.Literal["first", "last"] = "last", - ) -> Optional[DataFrame]: - if isinstance(by, (bigframes.series.Series, indexes.Index, DataFrame)): - raise KeyError( - f"Invalid key type: {type(by).__name__}. Please provide valid column name(s)." - ) - + ) -> DataFrame: if na_position not in {"first", "last"}: raise ValueError("Param na_position must be one of 'first' or 'last'") @@ -2664,31 +1245,20 @@ def sort_values( ordering = [] for i in range(len(sort_labels)): column_id = sort_column_ids[i] - is_ascending = sort_directions[i] + direction = ( + order.OrderingDirection.ASC + if sort_directions[i] + else order.OrderingDirection.DESC + ) na_last = na_position == "last" ordering.append( - order.ascending_over(column_id, na_last) - if is_ascending - else order.descending_over(column_id, na_last) + order.OrderingColumnReference( + column_id, direction=direction, na_last=na_last + ) ) - is_stable = (kind or constants.DEFAULT_SORT_KIND) in constants.STABLE_SORT_KINDS - block = self._block.order_by(ordering, stable=is_stable) - if inplace: - self._set_block(block) - return None - else: - return DataFrame(block) - - def eval(self, expr: str) -> DataFrame: - import bigframes.core.eval as bf_eval - - return bf_eval.eval(self, expr, target=self) - - def query(self, expr: str) -> DataFrame: - import bigframes.core.eval as bf_eval - - eval_result = bf_eval.eval(self, expr, target=None) - return self[eval_result] + return DataFrame( + self._block.order_by(ordering, stable=kind in order.STABLE_SORTS) + ) def value_counts( self, @@ -2706,7 +1276,7 @@ def value_counts( normalize=normalize, sort=sort, ascending=ascending, - drop_na=dropna, + dropna=dropna, ) return bigframes.series.Series(block) @@ -2718,18 +1288,6 @@ def add_suffix(self, suffix: str, axis: int | str | None = None) -> DataFrame: axis = 1 if axis is None else axis return DataFrame(self._get_block().add_suffix(suffix, axis)) - def take( - self, indices: typing.Sequence[int], axis: int | str | None = 0, **kwargs - ) -> DataFrame: - if not utils.is_list_like(indices): - raise ValueError("indices should be a list-like object.") - if axis == 0 or axis == "index": - return typing.cast(DataFrame, self.iloc[indices]) - elif axis == 1 or axis == "columns": - return typing.cast(DataFrame, self.iloc[:, indices]) - else: - raise ValueError(f"No axis named {axis} for object type DataFrame") - def filter( self, items: typing.Optional[typing.Iterable] = None, @@ -2755,34 +1313,34 @@ def _filter_rows( ) -> DataFrame: if len(self._block.index_columns) > 1: raise NotImplementedError( - f"Method filter does not support rows multiindex. {constants.FEEDBACK_LINK}" + "Method filter does not support rows multiindex. {constants.FEEDBACK_LINK}" ) if (like is not None) or (regex is not None): block = self._block block, label_string_id = block.apply_unary_op( self._block.index_columns[0], - ops.AsTypeOp(to_type=pandas.StringDtype(storage="pyarrow")), + ops.AsTypeOp(pandas.StringDtype(storage="pyarrow")), ) if like is not None: block, mask_id = block.apply_unary_op( - label_string_id, ops.StrContainsOp(pat=like) + label_string_id, ops.ContainsStringOp(pat=like) ) else: # regex assert regex is not None block, mask_id = block.apply_unary_op( - label_string_id, ops.StrContainsRegexOp(pat=regex) + label_string_id, ops.ContainsRegexOp(pat=regex) ) - block = block.filter_by_id(mask_id) + block = block.filter(mask_id) block = block.select_columns(self._block.value_columns) return DataFrame(block) elif items is not None: # Behavior matches pandas 2.1+, older pandas versions would reindex block = self._block block, mask_id = block.apply_unary_op( - self._block.index_columns[0], ops.IsInOp(values=tuple(items)) + self._block.index_columns[0], ops.IsInOp(values=list(items)) ) - block = block.filter_by_id(mask_id) + block = block.filter(mask_id) block = block.select_columns(self._block.value_columns) return DataFrame(block) else: @@ -2801,8 +1359,7 @@ def label_filter(label): if like: return like in label_str else: # regex - # TODO(b/340891296): fix type error - return re.match(regex, label_str) is not None # type: ignore + return re.match(regex, label_str) is not None cols = [ col_id @@ -2843,7 +1400,6 @@ def reindex( if columns is not None: return self._reindex_columns(columns) - @validations.requires_index def _reindex_rows( self, index, @@ -2854,7 +1410,7 @@ def _reindex_rows( raise ValueError("Original index must be unique to reindex") keep_original_names = False if isinstance(index, indexes.Index): - new_indexer = DataFrame(data=index._block)[[]] + new_indexer = DataFrame(data=index._data._get_block())[[]] else: if not isinstance(index, pandas.Index): keep_original_names = True @@ -2863,7 +1419,7 @@ def _reindex_rows( raise NotImplementedError( "Cannot reindex with index with different nlevels" ) - new_indexer = DataFrame(index=index, session=self._session)[[]] + new_indexer = DataFrame(index=index)[[]] # multiindex join is senstive to index names, so we will set all these result = new_indexer.rename_axis(range(new_indexer.index.nlevels)).join( self.rename_axis(range(self.index.nlevels)), @@ -2877,11 +1433,6 @@ def _reindex_rows( def _reindex_columns(self, columns): block = self._block new_column_index, indexer = self.columns.reindex(columns) - - if indexer is None: - # The new index is the same as the old one. Do nothing. - return self - result_cols = [] for label, index in zip(columns, indexer): if index >= 0: @@ -2895,44 +1446,22 @@ def _reindex_columns(self, columns): result_df.columns = new_column_index return result_df - @validations.requires_index def reindex_like(self, other: DataFrame, *, validate: typing.Optional[bool] = None): return self.reindex(index=other.index, columns=other.columns, validate=validate) - @validations.requires_ordering() - @validations.requires_index def interpolate(self, method: str = "linear") -> DataFrame: - if method == "pad": - return self.ffill() result = block_ops.interpolate(self._block, method) return DataFrame(result) def fillna(self, value=None) -> DataFrame: return self._apply_binop(value, ops.fillna_op, how="left") - def replace( - self, to_replace: typing.Any, value: typing.Any = None, *, regex: bool = False - ): - if utils.is_dict_like(value): - return self.apply( - lambda x: ( - x.replace(to_replace=to_replace, value=value[x.name], regex=regex) - if (x.name in value) - else x - ) - ) - return self.apply( - lambda x: x.replace(to_replace=to_replace, value=value, regex=regex) - ) - - @validations.requires_ordering() def ffill(self, *, limit: typing.Optional[int] = None) -> DataFrame: - window = windows.rows(start=None if limit is None else -limit, end=0) + window = bigframes.core.WindowSpec(preceding=limit, following=0) return self._apply_window_op(agg_ops.LastNonNullOp(), window) - @validations.requires_ordering() def bfill(self, *, limit: typing.Optional[int] = None) -> DataFrame: - window = windows.rows(start=0, end=limit) + window = bigframes.core.WindowSpec(preceding=0, following=limit) return self._apply_window_op(agg_ops.FirstNonNullOp(), window) def isin(self, values) -> DataFrame: @@ -2943,9 +1472,7 @@ def isin(self, values) -> DataFrame: if label in values.keys(): value_for_key = values[label] block, result_id = block.apply_unary_op( - col, - ops.IsInOp(values=tuple(value_for_key), match_nulls=True), - label, + col, ops.IsInOp(value_for_key, match_nulls=True), label ) result_ids.append(result_id) else: @@ -2953,10 +1480,10 @@ def isin(self, values) -> DataFrame: False, label=label, dtype=pandas.BooleanDtype() ) result_ids.append(result_id) - return DataFrame(block.select_columns(result_ids)) + return DataFrame(block.select_columns(result_ids)).fillna(value=False) elif utils.is_list_like(values): - return self._apply_unary_op( - ops.IsInOp(values=tuple(values), match_nulls=True) + return self._apply_unary_op(ops.IsInOp(values, match_nulls=True)).fillna( + value=False ) else: raise TypeError( @@ -2985,163 +1512,41 @@ def itertuples( for item in df.itertuples(index=index, name=name): yield item - def _apply_callable(self, condition): - """Executes the possible callable condition as needed.""" - if callable(condition): - # When it's a bigframes function. - if isinstance(condition, bigframes.functions.Udf): - return self.apply(condition, axis=1) - - # When it's a plain Python function. - return condition(self) - - # When it's not a callable. - return condition - - def where(self, cond, other=None): - if self.columns.nlevels > 1: - raise NotImplementedError( - "The dataframe.where() method does not support multi-column." - ) - - # Execute it with the DataFrame when cond or/and other is callable. - # It can be either a plain python function or remote/managed function. - cond = self._apply_callable(cond) - other = self._apply_callable(other) - - if isinstance(other, bigframes.series.Series): - raise ValueError("Seires is not a supported replacement type!") - - aligned_block, (_, _) = self._block.join(cond._block, how="left") - # No left join is needed when 'other' is None or constant. - if isinstance(other, bigframes.dataframe.DataFrame): - aligned_block, (_, _) = aligned_block.join(other._block, how="left") - self_len = len(self._block.value_columns) - cond_len = len(cond._block.value_columns) - - ids = aligned_block.value_columns[:self_len] - labels = aligned_block.column_labels[:self_len] - self_col = {x: ex.deref(y) for x, y in zip(labels, ids)} - - if isinstance(cond, bigframes.series.Series): - # This is when 'cond' is a valid series. - y = aligned_block.value_columns[self_len] - cond_col = {x: ex.deref(y) for x in self_col.keys()} - else: - # This is when 'cond' is a dataframe. - ids = aligned_block.value_columns[self_len : self_len + cond_len] - labels = aligned_block.column_labels[self_len : self_len + cond_len] - cond_col = {x: ex.deref(y) for x, y in zip(labels, ids)} - - if isinstance(other, DataFrame): - other_len = len(self._block.value_columns) - ids = aligned_block.value_columns[-other_len:] - labels = aligned_block.column_labels[-other_len:] - other_col = {x: ex.deref(y) for x, y in zip(labels, ids)} - else: - # This is when 'other' is None or constant. - labels = aligned_block.column_labels[:self_len] - other_col = {x: ex.const(other) for x in labels} # type: ignore - - result_series = {} - for x, self_id in self_col.items(): - cond_id = cond_col[x] if x in cond_col else ex.const(False) - other_id = other_col[x] if x in other_col else ex.const(None) - result_block, result_id = aligned_block.project_expr( - ops.where_op.as_expr(self_id, cond_id, other_id) - ) - series = bigframes.series.Series( - result_block.select_column(result_id).with_column_labels([x]) - ) - result_series[x] = series - - result = DataFrame(result_series) - result.columns.name = self.columns.name - result.columns.names = self.columns.names - return result - - def mask(self, cond, other=None): - return self.where(~self._apply_callable(cond), other=other) - def dropna( self, *, axis: int | str = 0, - how: str = "any", - thresh: typing.Optional[int] = None, - subset: typing.Union[None, blocks.Label, Sequence[blocks.Label]] = None, inplace: bool = False, + how: str = "any", ignore_index=False, ) -> DataFrame: if inplace: raise NotImplementedError( - f"'inplace'=True not supported. {constants.FEEDBACK_LINK}" + "'inplace'=True not supported. {constants.FEEDBACK_LINK}" ) - - # Check if both thresh and how are explicitly provided - if thresh is not None: - # cannot specify both thresh and how parameters - if how != "any": - raise TypeError( - "You cannot set both the how and thresh arguments at the same time." - ) - else: - # Only validate 'how' when thresh is not provided - if how not in ("any", "all"): - raise ValueError("'how' must be one of 'any', 'all'") + if how not in ("any", "all"): + raise ValueError("'how' must be one of 'any', 'all'") axis_n = utils.get_axis_number(axis) - if subset is not None and axis_n != 0: - raise NotImplementedError( - f"subset only supported when axis=0. {constants.FEEDBACK_LINK}" - ) - if axis_n == 0: - # subset needs to be converted into column IDs, not column labels. - if subset is None: - subset_ids = None - elif not utils.is_list_like(subset): - subset_ids = [id_ for id_ in self._block.label_to_col_id[subset]] - else: - subset_ids = [ - id_ - for label in subset - for id_ in self._block.label_to_col_id[label] - ] - - result = block_ops.dropna( - self._block, - self._block.value_columns, - how=how, - thresh=thresh, - subset=subset_ids, - ) # type: ignore + result = block_ops.dropna(self._block, self._block.value_columns, how=how) # type: ignore if ignore_index: result = result.reset_index() return DataFrame(result) else: - if thresh is not None: - # Keep columns with at least 'thresh' non-null values - notnull_block = self._block.multi_apply_unary_op(ops.notnull_op) - notnull_counts = DataFrame(notnull_block).sum().to_pandas() - - keep_columns = [ - col - for col, count in zip(self._block.value_columns, notnull_counts) - if count >= thresh - ] - else: - isnull_block = self._block.multi_apply_unary_op(ops.isnull_op) - if how == "any": - null_locations = DataFrame(isnull_block).any().to_pandas() - else: # 'all' - null_locations = DataFrame(isnull_block).all().to_pandas() - keep_columns = [ - col - for col, to_drop in zip(self._block.value_columns, null_locations) - if not to_drop - ] + isnull_block = self._block.multi_apply_unary_op( + self._block.value_columns, ops.isnull_op + ) + if how == "any": + null_locations = DataFrame(isnull_block).any().to_pandas() + else: # 'all' + null_locations = DataFrame(isnull_block).all().to_pandas() + keep_columns = [ + col + for col, to_drop in zip(self._block.value_columns, null_locations) + if not to_drop + ] return DataFrame(self._block.select_columns(keep_columns)) def any( @@ -3154,8 +1559,10 @@ def any( frame = self._raise_on_non_boolean("any") else: frame = self._drop_non_bool() - block = frame._block.aggregate_all_and_stack(agg_ops.any_op, axis=axis) - return bigframes.series.Series(block) + block = frame._block.aggregate_all_and_stack( + agg_ops.any_op, dtype=pandas.BooleanDtype(), axis=axis + ) + return bigframes.series.Series(block.select_column("values")) def all( self, axis: typing.Union[str, int] = 0, *, bool_only: bool = False @@ -3164,8 +1571,10 @@ def all( frame = self._raise_on_non_boolean("all") else: frame = self._drop_non_bool() - block = frame._block.aggregate_all_and_stack(agg_ops.all_op, axis=axis) - return bigframes.series.Series(block) + block = frame._block.aggregate_all_and_stack( + agg_ops.all_op, dtype=pandas.BooleanDtype(), axis=axis + ) + return bigframes.series.Series(block.select_column("values")) def sum( self, axis: typing.Union[str, int] = 0, *, numeric_only: bool = False @@ -3175,7 +1584,7 @@ def sum( else: frame = self._drop_non_numeric() block = frame._block.aggregate_all_and_stack(agg_ops.sum_op, axis=axis) - return bigframes.series.Series(block) + return bigframes.series.Series(block.select_column("values")) def mean( self, axis: typing.Union[str, int] = 0, *, numeric_only: bool = False @@ -3185,46 +1594,21 @@ def mean( else: frame = self._drop_non_numeric() block = frame._block.aggregate_all_and_stack(agg_ops.mean_op, axis=axis) - return bigframes.series.Series(block) + return bigframes.series.Series(block.select_column("values")) def median( - self, *, numeric_only: bool = False, exact: bool = True + self, *, numeric_only: bool = False, exact: bool = False ) -> bigframes.series.Series: - if not numeric_only: - frame = self._raise_on_non_numeric("median") - else: - frame = self._drop_non_numeric() if exact: - result = frame.quantile() - result.name = None - return result - else: - block = frame._block.aggregate_all_and_stack(agg_ops.median_op) - return bigframes.series.Series(block) - - def quantile( - self, q: Union[float, Sequence[float]] = 0.5, *, numeric_only: bool = False - ): + raise NotImplementedError( + f"Only approximate median is supported. {constants.FEEDBACK_LINK}" + ) if not numeric_only: frame = self._raise_on_non_numeric("median") else: frame = self._drop_non_numeric() - multi_q = utils.is_list_like(q) - result = block_ops.quantile( - frame._block, - frame._block.value_columns, - qs=tuple(q) if multi_q else (q,), # type: ignore - ) - if multi_q: - return DataFrame(result.stack()).droplevel(0) - else: - # Drop the last level, which contains q, unnecessary since only one q - result = result.with_column_labels(result.column_labels.droplevel(-1)) - result, index_col = result.create_constant(q, None) - result = result.set_index([index_col]) - return bigframes.series.Series( - result.transpose(original_row_index=pandas.Index([q])) - ) + block = frame._block.aggregate_all_and_stack(agg_ops.median_op) + return bigframes.series.Series(block.select_column("values")) def std( self, axis: typing.Union[str, int] = 0, *, numeric_only: bool = False @@ -3234,7 +1618,7 @@ def std( else: frame = self._drop_non_numeric() block = frame._block.aggregate_all_and_stack(agg_ops.std_op, axis=axis) - return bigframes.series.Series(block) + return bigframes.series.Series(block.select_column("values")) def var( self, axis: typing.Union[str, int] = 0, *, numeric_only: bool = False @@ -3244,7 +1628,7 @@ def var( else: frame = self._drop_non_numeric() block = frame._block.aggregate_all_and_stack(agg_ops.var_op, axis=axis) - return bigframes.series.Series(block) + return bigframes.series.Series(block.select_column("values")) def min( self, axis: typing.Union[str, int] = 0, *, numeric_only: bool = False @@ -3254,7 +1638,7 @@ def min( else: frame = self._drop_non_numeric() block = frame._block.aggregate_all_and_stack(agg_ops.min_op, axis=axis) - return bigframes.series.Series(block) + return bigframes.series.Series(block.select_column("values")) def max( self, axis: typing.Union[str, int] = 0, *, numeric_only: bool = False @@ -3264,7 +1648,7 @@ def max( else: frame = self._drop_non_numeric() block = frame._block.aggregate_all_and_stack(agg_ops.max_op, axis=axis) - return bigframes.series.Series(block) + return bigframes.series.Series(block.select_column("values")) def prod( self, axis: typing.Union[str, int] = 0, *, numeric_only: bool = False @@ -3274,7 +1658,7 @@ def prod( else: frame = self._drop_non_numeric() block = frame._block.aggregate_all_and_stack(agg_ops.product_op, axis=axis) - return bigframes.series.Series(block) + return bigframes.series.Series(block.select_column("values")) product = prod @@ -3284,89 +1668,45 @@ def count(self, *, numeric_only: bool = False) -> bigframes.series.Series: else: frame = self._drop_non_numeric() block = frame._block.aggregate_all_and_stack(agg_ops.count_op) - return bigframes.series.Series(block) + return bigframes.series.Series(block.select_column("values")) def nunique(self) -> bigframes.series.Series: block = self._block.aggregate_all_and_stack(agg_ops.nunique_op) - return bigframes.series.Series(block) - - def agg(self, func) -> DataFrame | bigframes.series.Series: - if utils.is_dict_like(func): - # Must check dict-like first because dictionaries are list-like - # according to Pandas. - - aggs = [] - labels = [] - funcnames = [] - for col_label, agg_func in func.items(): - agg_func_list = agg_func if utils.is_list_like(agg_func) else [agg_func] - col_id = self._block.resolve_label_exact(col_label) - if col_id is None: - raise KeyError(f"Column {col_label} does not exist") - for agg_func in agg_func_list: - op_and_label = agg_ops.lookup_agg_func(agg_func) - agg_expr = ( - agg_expressions.UnaryAggregation( - op_and_label[0], ex.deref(col_id) - ) - if isinstance(op_and_label[0], agg_ops.UnaryAggregateOp) - else agg_expressions.NullaryAggregation(op_and_label[0]) - ) - aggs.append(agg_expr) - labels.append(col_label) - funcnames.append(op_and_label[1]) - - # if any list in dict values, format output differently - if any(utils.is_list_like(v) for v in func.values()): - new_index, _ = self.columns.reindex(labels) - new_index = utils.combine_indices(new_index, pandas.Index(funcnames)) - agg_block = self._block.aggregate( - aggregations=aggs, column_labels=new_index - ) - return DataFrame(agg_block).stack().droplevel(0, axis="index") - else: - new_index, _ = self.columns.reindex(labels) - agg_block = self._block.aggregate( - aggregations=aggs, column_labels=new_index - ) - return bigframes.series.Series( - agg_block.transpose( - single_row_mode=True, original_row_index=pandas.Index([None]) - ) + return bigframes.series.Series(block.select_column("values")) + + def agg( + self, func: str | typing.Sequence[str] + ) -> DataFrame | bigframes.series.Series: + if utils.is_list_like(func): + if any( + dtype not in bigframes.dtypes.NUMERIC_BIGFRAMES_TYPES + for dtype in self.dtypes + ): + raise NotImplementedError( + f"Multiple aggregations only supported on numeric columns. {constants.FEEDBACK_LINK}" ) - elif utils.is_list_like(func): - aggregations = [agg_ops.lookup_agg_func(f)[0] for f in func] - - for dtype, agg in itertools.product(self.dtypes, aggregations): - agg.output_type( - dtype - ) # Raises exception if the agg does not support the dtype. - + aggregations = [agg_ops.lookup_agg_func(f) for f in func] return DataFrame( self._block.summarize( self._block.value_columns, aggregations, ) ) - - else: # function name string + else: return bigframes.series.Series( - self._block.aggregate_all_and_stack(agg_ops.lookup_agg_func(func)[0]) + self._block.aggregate_all_and_stack( + agg_ops.lookup_agg_func(typing.cast(str, func)) + ) ) aggregate = agg - @validations.requires_index - @validations.requires_ordering() def idxmin(self) -> bigframes.series.Series: return bigframes.series.Series(block_ops.idxmin(self._block)) - @validations.requires_index - @validations.requires_ordering() def idxmax(self) -> bigframes.series.Series: return bigframes.series.Series(block_ops.idxmax(self._block)) - @validations.requires_ordering() def melt( self, id_vars: typing.Optional[typing.Iterable[typing.Hashable]] = None, @@ -3405,10 +1745,16 @@ def melt( self._block.melt(id_col_ids, val_col_ids, var_name, value_name) ) - def describe(self, include: None | Literal["all"] = None) -> DataFrame: - from bigframes.pandas.core.methods import describe - - return typing.cast(DataFrame, describe.describe(self, include)) + def describe(self) -> DataFrame: + df_numeric = self._drop_non_numeric(keep_bool=False) + if len(df_numeric.columns) == 0: + raise NotImplementedError( + f"df.describe() currently only supports numeric values. {constants.FEEDBACK_LINK}" + ) + result = df_numeric.agg( + ["count", "mean", "std", "min", "25%", "50%", "75%", "max"] + ) + return typing.cast(DataFrame, result) def skew(self, *, numeric_only: bool = False): if not numeric_only: @@ -3476,93 +1822,6 @@ def pivot( ) -> DataFrame: return self._pivot(columns=columns, index=index, values=values) - def pivot_table( - self, - values: typing.Optional[ - typing.Union[blocks.Label, Sequence[blocks.Label]] - ] = None, - index: typing.Optional[ - typing.Union[blocks.Label, Sequence[blocks.Label]] - ] = None, - columns: typing.Union[blocks.Label, Sequence[blocks.Label]] = None, - aggfunc: str = "mean", - fill_value=None, - margins: bool = False, - dropna: bool = True, - margins_name: Hashable = "All", - observed: bool = False, - sort: bool = True, - ) -> DataFrame: - if margins: - raise NotImplementedError( - "DataFrame.pivot_table margins arg not supported. {constants.FEEDBACK_LINK}" - ) - if not dropna: - raise NotImplementedError( - "DataFrame.pivot_table dropna arg not supported. {constants.FEEDBACK_LINK}" - ) - if margins_name != "All": - raise NotImplementedError( - "DataFrame.pivot_table margins_name arg not supported. {constants.FEEDBACK_LINK}" - ) - if observed: - raise NotImplementedError( - "DataFrame.pivot_table observed arg not supported. {constants.FEEDBACK_LINK}" - ) - - if isinstance(index, Iterable) and not ( - isinstance(index, blocks.Label) and index in self.columns - ): - index = list(index) - else: - index = [index] - - if isinstance(columns, Iterable) and not ( - isinstance(columns, blocks.Label) and columns in self.columns - ): - columns = list(columns) - else: - columns = [columns] - - if isinstance(values, Iterable) and not ( - isinstance(values, blocks.Label) and values in self.columns - ): - values = list(values) - else: - values = [values] - - # Unlike pivot, pivot_table has values always ordered. - values.sort(key=lambda val: typing.cast("SupportsRichComparison", val)) - - keys = index + columns - agged = self.groupby(keys, dropna=True)[values].agg(aggfunc) - - if isinstance(agged, bigframes.series.Series): - agged = agged.to_frame() - - agged = agged.dropna(how="all") - - if len(values) == 1: - agged = agged.rename(columns={agged.columns[0]: values[0]}) - - agged = agged.reset_index() - - pivoted = agged.pivot( - columns=columns, - index=index, - values=values if len(values) > 1 else None, - ) - if fill_value is not None: - pivoted = pivoted.fillna(fill_value) - if sort: - pivoted = pivoted.sort_index() - - # TODO: Remove the reordering step once the issue is resolved. - # The pivot_table method results in multi-index columns that are always ordered. - # However, the order of the pivoted result columns is not guaranteed to be sorted. - # Sort and reorder. - return pivoted.sort_index(axis=1) # type: ignore - def stack(self, level: LevelsType = -1): if not isinstance(self.columns, pandas.MultiIndex): if level not in [0, -1, self.columns.name]: @@ -3576,7 +1835,7 @@ def _stack_mono(self): def _stack_multi(self, level: LevelsType = -1): n_levels = self.columns.nlevels - if not utils.is_list_like(level): + if isinstance(level, int) or isinstance(level, str): level = [level] level_indices = [] for level_ref in level: @@ -3586,7 +1845,7 @@ def _stack_multi(self, level: LevelsType = -1): else: level_indices.append(level_ref) else: # str - level_indices.append(self.columns.names.index(level_ref)) # type: ignore + level_indices.append(self.columns.names.index(level_ref)) new_order = [ *[i for i in range(n_levels) if i not in level_indices], @@ -3601,10 +1860,8 @@ def _stack_multi(self, level: LevelsType = -1): block = block.stack(levels=len(level)) return DataFrame(block) - @validations.requires_index - @validations.requires_ordering() def unstack(self, level: LevelsType = -1): - if not utils.is_list_like(level): + if isinstance(level, int) or isinstance(level, str): level = [level] block = self._block @@ -3627,16 +1884,14 @@ def unstack(self, level: LevelsType = -1): ) return DataFrame(pivot_block) - def _drop_non_numeric(self, permissive=True) -> DataFrame: - numeric_types = ( - set(bigframes.dtypes.NUMERIC_BIGFRAMES_TYPES_PERMISSIVE) - if permissive - else set(bigframes.dtypes.NUMERIC_BIGFRAMES_TYPES_RESTRICTIVE) - ) + def _drop_non_numeric(self, keep_bool=True) -> DataFrame: + types_to_keep = set(bigframes.dtypes.NUMERIC_BIGFRAMES_TYPES) + if not keep_bool: + types_to_keep -= set(bigframes.dtypes.BOOL_BIGFRAMES_TYPES) non_numeric_cols = [ col_id for col_id, dtype in zip(self._block.value_columns, self._block.dtypes) - if dtype not in numeric_types + if dtype not in types_to_keep ] return DataFrame(self._block.drop_columns(non_numeric_cols)) @@ -3650,7 +1905,7 @@ def _drop_non_bool(self) -> DataFrame: def _raise_on_non_numeric(self, op: str): if not all( - dtype in bigframes.dtypes.NUMERIC_BIGFRAMES_TYPES_PERMISSIVE + dtype in bigframes.dtypes.NUMERIC_BIGFRAMES_TYPES for dtype in self._block.dtypes ): raise NotImplementedError( @@ -3678,289 +1933,129 @@ def merge( "left", "outer", "right", - "cross", ] = "inner", + # TODO(garrettwu): Currently can take inner, outer, left and right. To support + # cross joins on: Union[blocks.Label, Sequence[blocks.Label], None] = None, *, left_on: Union[blocks.Label, Sequence[blocks.Label], None] = None, right_on: Union[blocks.Label, Sequence[blocks.Label], None] = None, - left_index: bool = False, - right_index: bool = False, sort: bool = False, suffixes: tuple[str, str] = ("_x", "_y"), ) -> DataFrame: - from bigframes.core.reshape import merge + if on is None: + if left_on is None or right_on is None: + raise ValueError("Must specify `on` or `left_on` + `right_on`.") + else: + if left_on is not None or right_on is not None: + raise ValueError( + "Can not pass both `on` and `left_on` + `right_on` params." + ) + left_on, right_on = on, on - return merge.merge( - self, - right, + if utils.is_list_like(left_on): + left_on = list(left_on) # type: ignore + else: + left_on = [left_on] + + if utils.is_list_like(right_on): + right_on = list(right_on) # type: ignore + else: + right_on = [right_on] + + left_join_ids = [] + for label in left_on: # type: ignore + left_col_id = self._resolve_label_exact(label) + # 0 elements already throws an exception + if not left_col_id: + raise ValueError(f"No column {label} found in self.") + left_join_ids.append(left_col_id) + + right_join_ids = [] + for label in right_on: # type: ignore + right_col_id = right._resolve_label_exact(label) + if not right_col_id: + raise ValueError(f"No column {label} found in other.") + right_join_ids.append(right_col_id) + + block = self._block.merge( + right._block, how, - on, - left_on=left_on, - right_on=right_on, - left_index=left_index, - right_index=right_index, + left_join_ids, + right_join_ids, sort=sort, suffixes=suffixes, ) + return DataFrame(block) def join( - self, - other: Union[DataFrame, bigframes.series.Series], - on: Optional[str] = None, - how: str = "left", - lsuffix: str = "", - rsuffix: str = "", + self, other: DataFrame, *, on: Optional[str] = None, how: str = "left" ) -> DataFrame: - if isinstance(other, bigframes.series.Series): - other = other.to_frame() - left, right = self, other - - col_intersection = left.columns.intersection(right.columns) - - if not col_intersection.empty: - if lsuffix == rsuffix == "": - raise ValueError( - f"columns overlap but no suffix specified: {col_intersection}" - ) - - if how == "cross": - if on is not None: - raise ValueError("'on' is not supported for cross join.") - result_block = left._block.merge( - right._block, - left_join_ids=[], - right_join_ids=[], - suffixes=(lsuffix, rsuffix), - how="cross", - sort=True, + if not left.columns.intersection(right.columns).empty: + raise NotImplementedError( + f"Deduping column names is not implemented. {constants.FEEDBACK_LINK}" ) - return DataFrame(result_block) # Join left columns with right index if on is not None: - if left._has_index and (on in left.index.names): - if on in left.columns: - raise ValueError( - f"'{on}' is both an index level and a column label, which is ambiguous." - ) - else: - raise NotImplementedError( - f"Joining on index level '{on}' is not yet supported. {constants.FEEDBACK_LINK}" - ) - if (left.columns == on).sum() > 1: - raise ValueError(f"The column label '{on}' is not unique.") - if other._block.index.nlevels != 1: raise ValueError( "Join on columns must match the index level of the other DataFrame. Join on column with multi-index haven't been supported." ) - - return self._join_on_key( - other, - on=on, - how=how, - lsuffix=lsuffix, - rsuffix=rsuffix, - should_duplicate_on_key=(on in col_intersection), - ) - - # Join left index with right index - if left._block.index.nlevels != right._block.index.nlevels: - raise ValueError("Index to join on must have the same number of levels.") - - return left._perform_join_by_index(right, how=how)._add_join_suffix( - left.columns, right.columns, lsuffix=lsuffix, rsuffix=rsuffix - ) - - def _join_on_key( - self, - other: DataFrame, - on: str, - how: str, - lsuffix: str, - rsuffix: str, - should_duplicate_on_key: bool, - ) -> DataFrame: - left, right = self.copy(), other - # Replace all columns names with unique names for reordering. - left_col_original_names = left.columns - on_col_name = "bigframes_left_col_on" - dup_on_col_name = "bigframes_left_col_on_dup" - left_col_temp_names = [ - f"bigframes_left_col_name_{i}" if col_name != on else on_col_name - for i, col_name in enumerate(left_col_original_names) - ] - left.columns = pandas.Index(left_col_temp_names) - # if on column is also in right df, we need to duplicate the column - # and set it to be the first column - if should_duplicate_on_key: - left[dup_on_col_name] = left[on_col_name] - on_col_name = dup_on_col_name - left_col_temp_names = [on_col_name] + left_col_temp_names - left = left[left_col_temp_names] - - # Switch left index with on column - left_idx_original_names = left.index.names if left._has_index else () - left_idx_names_in_cols = [ - f"bigframes_left_idx_name_{i}" for i in range(len(left_idx_original_names)) - ] - if left._has_index: + # Switch left index with on column + left_columns = left.columns + left_idx_original_names = left.index.names + left_idx_names_in_cols = [ + f"bigframes_left_idx_name_{i}" for i in range(len(left.index.names)) + ] left.index.names = left_idx_names_in_cols - left = left.reset_index(drop=False) - left = left.set_index(on_col_name) - - right_col_original_names = right.columns - right_col_temp_names = [ - f"bigframes_right_col_name_{i}" - for i in range(len(right_col_original_names)) - ] - right.columns = pandas.Index(right_col_temp_names) + left = left.reset_index(drop=False) + left = left.set_index(on) - # Join on index and switch back - combined_df = left._perform_join_by_index(right, how=how) - combined_df.index.name = on_col_name - combined_df = combined_df.reset_index(drop=False) - combined_df = combined_df.set_index(left_idx_names_in_cols) + # Join on index and switch back + combined_df = left._perform_join_by_index(right, how=how) + combined_df.index.name = on + combined_df = combined_df.reset_index(drop=False) + combined_df = combined_df.set_index(left_idx_names_in_cols) - # To be consistent with Pandas - if combined_df._has_index: + # To be consistent with Pandas combined_df.index.names = ( left_idx_original_names if how in ("inner", "left") else ([None] * len(combined_df.index.names)) ) - # Reorder columns - combined_df = combined_df[left_col_temp_names + right_col_temp_names] - return combined_df._add_join_suffix( - left_col_original_names, - right_col_original_names, - lsuffix=lsuffix, - rsuffix=rsuffix, - extra_col=on if on_col_name == dup_on_col_name else None, - ) - - def _perform_join_by_index( - self, - other: Union[DataFrame, indexes.Index], - *, - how: str = "left", - always_order: bool = False, - ): - block, _ = self._block.join( - other._block, how=how, block_identity_join=True, always_order=always_order - ) - return DataFrame(block) - - def _add_join_suffix( - self, - left_columns, - right_columns, - lsuffix: str = "", - rsuffix: str = "", - extra_col: typing.Optional[str] = None, - ): - """Applies suffixes to overlapping column names to mimic a pandas join. - - This method identifies columns that are common to both a "left" and "right" - set of columns and renames them using the provided suffixes. Columns that - are not in the intersection are kept with their original names. - - Args: - left_columns (pandas.Index): - The column labels from the left DataFrame. - right_columns (pandas.Index): - The column labels from the right DataFrame. - lsuffix (str): - The suffix to apply to overlapping column names from the left side. - rsuffix (str): - The suffix to apply to overlapping column names from the right side. - extra_col (typing.Optional[str]): - An optional column name to prepend to the final list of columns. - This argument is used specifically to match the behavior of a - pandas join. When a join key (i.e., the 'on' column) exists - in both the left and right DataFrames, pandas creates two versions - of that column: one copy keeps its original name and is placed as - the first column, while the other instances receive the normal - suffix. Passing the join key's name here replicates that behavior. - - Returns: - DataFrame: - A new DataFrame with the columns renamed to resolve overlaps. - """ - combined_df = self.copy() - col_intersection = left_columns.intersection(right_columns) - final_col_names = [] if extra_col is None else [extra_col] - for col_name in left_columns: - if col_name in col_intersection: - final_col_names.append(f"{col_name}{lsuffix}") - else: - final_col_names.append(col_name) + # Reorder columns + combined_df = combined_df[list(left_columns) + list(right.columns)] + return combined_df - for col_name in right_columns: - if col_name in col_intersection: - final_col_names.append(f"{col_name}{rsuffix}") - else: - final_col_names.append(col_name) - combined_df.columns = pandas.Index(final_col_names) - return combined_df + # Join left index with right index + if left._block.index.nlevels != right._block.index.nlevels: + raise ValueError("Index to join on must have the same number of levels.") - @validations.requires_ordering() - def rolling( - self, - window: int | pandas.Timedelta | numpy.timedelta64 | datetime.timedelta | str, - min_periods=None, - on: str | None = None, - closed: Literal["right", "left", "both", "neither"] = "right", - ) -> bigframes.core.window.Window: - if isinstance(window, int): - window_def = windows.WindowSpec( - bounds=windows.RowsWindowBounds.from_window_size(window, closed), - min_periods=min_periods if min_periods is not None else window, - ) - skip_agg_col_id = ( - None if on is None else self._block.resolve_label_exact_or_error(on) - ) - return bigframes.core.window.Window( - self._block, - window_def, - self._block.value_columns, - skip_agg_column_id=skip_agg_col_id, - ) + return left._perform_join_by_index(right, how=how) - return rolling.create_range_window( - self._block, - window, - min_periods=min_periods, - on=on, - closed=closed, - is_series=False, + def _perform_join_by_index(self, other: DataFrame, *, how: str = "left"): + combined_index, _ = self._block.index.join( + other._block.index, how=how, block_identity_join=True ) + return DataFrame(combined_index._block) - @validations.requires_ordering() - def expanding(self, min_periods: int = 1) -> bigframes.core.window.Window: - window = windows.cumulative_rows(min_periods=min_periods) + def rolling(self, window: int, min_periods=None) -> bigframes.core.window.Window: + # To get n size window, need current row and n-1 preceding rows. + window_spec = bigframes.core.WindowSpec( + preceding=window - 1, following=0, min_periods=min_periods or window + ) return bigframes.core.window.Window( - self._block, window, self._block.value_columns + self._block, window_spec, self._block.value_columns ) - def pipe( - self, - func: Union[Callable[..., U], tuple[Callable[..., U], str]], - *args, - **kwargs, - ) -> U: - import bigframes_vendored.pandas.core.common as common - - return common.pipe(self, func, *args, **kwargs) - - def get(self, key, default=None): - try: - return self[key] - except (KeyError, ValueError, IndexError): - return default + def expanding(self, min_periods: int = 1) -> bigframes.core.window.Window: + window_spec = bigframes.core.WindowSpec(following=0, min_periods=min_periods) + return bigframes.core.window.Window( + self._block, window_spec, self._block.value_columns + ) def groupby( self, @@ -3968,7 +2063,6 @@ def groupby( blocks.Label, bigframes.series.Series, typing.Sequence[typing.Union[blocks.Label, bigframes.series.Series]], - None, ] = None, *, level: typing.Optional[LevelsType] = None, @@ -3984,24 +2078,17 @@ def groupby( else: raise TypeError("You have to supply one of 'by' and 'level'") - @validations.requires_index def _groupby_level( self, level: LevelsType, as_index: bool = True, dropna: bool = True, ): - if utils.is_list_like(level): - by_key_is_singular = False - else: - by_key_is_singular = True - return groupby.DataFrameGroupBy( self._block, by_col_ids=self._resolve_levels(level), as_index=as_index, dropna=dropna, - by_key_is_singular=by_key_is_singular, ) def _groupby_series( @@ -4014,30 +2101,26 @@ def _groupby_series( as_index: bool = True, dropna: bool = True, ): - # Pandas makes a distinction between groupby with a list of keys - # versus groupby with a single item in some methods, like __iter__. if not isinstance(by, bigframes.series.Series) and utils.is_list_like(by): by = list(by) - by_key_is_singular = False else: by = [typing.cast(typing.Union[blocks.Label, bigframes.series.Series], by)] - by_key_is_singular = True block = self._block col_ids: typing.Sequence[str] = [] for key in by: if isinstance(key, bigframes.series.Series): - ( - block, - ( - get_column_left, - get_column_right, - ), - ) = block.join(key._block, how="inner" if dropna else "left") + combined_index, ( + get_column_left, + get_column_right, + ) = block.index.join( + key._block.index, how="inner" if dropna else "left" + ) col_ids = [ *[get_column_left[value] for value in col_ids], get_column_right[key._value_column], ] + block = combined_index._block else: # Interpret as index level or column name col_matches = block.label_to_col_id.get(key, []) @@ -4054,55 +2137,11 @@ def _groupby_series( by_col_ids=col_ids, as_index=as_index, dropna=dropna, - by_key_is_singular=by_key_is_singular, ) def abs(self) -> DataFrame: return self._apply_unary_op(ops.abs_op) - def round(self, decimals: Union[int, dict[Hashable, int]] = 0) -> DataFrame: - is_mapping = utils.is_dict_like(decimals) - if not (is_mapping or isinstance(decimals, int)): - raise TypeError("'decimals' must be either a dict-like or integer.") - block = self._block - exprs = [] - for label, col_id, dtype in zip( - block.column_labels, block.value_columns, block.dtypes - ): - if dtype in set(bigframes.dtypes.NUMERIC_BIGFRAMES_TYPES_PERMISSIVE) - { - bigframes.dtypes.BOOL_DTYPE - }: - if is_mapping: - decimals_dict = typing.cast(dict[typing.Hashable, int], decimals) - if label in decimals_dict: - exprs.append( - ops.round_op.as_expr( - col_id, - ex.const( - decimals_dict[label], - dtype=bigframes.dtypes.INT_DTYPE, # type: ignore - ), - ) - ) - else: - exprs.append(ex.deref(col_id)) - else: - exprs.append( - ops.round_op.as_expr( - col_id, - ex.const( - typing.cast(int, decimals), - dtype=bigframes.dtypes.INT_DTYPE, - ), - ) - ) - else: - exprs.append(ex.deref(col_id)) - - return DataFrame( - block.project_exprs(exprs, labels=block.column_labels, drop=True) - ) - def isna(self) -> DataFrame: return self._apply_unary_op(ops.isnull_op) @@ -4113,57 +2152,56 @@ def notna(self) -> DataFrame: notnull = notna - @validations.requires_ordering() def cumsum(self): is_numeric_types = [ - (dtype in bigframes.dtypes.NUMERIC_BIGFRAMES_TYPES_PERMISSIVE) + (dtype in bigframes.dtypes.NUMERIC_BIGFRAMES_TYPES) for _, dtype in self.dtypes.items() ] if not all(is_numeric_types): raise ValueError("All values must be numeric to apply cumsum.") return self._apply_window_op( agg_ops.sum_op, - windows.cumulative_rows(), + bigframes.core.WindowSpec(following=0), ) - @validations.requires_ordering() def cumprod(self) -> DataFrame: is_numeric_types = [ - (dtype in bigframes.dtypes.NUMERIC_BIGFRAMES_TYPES_PERMISSIVE) + (dtype in bigframes.dtypes.NUMERIC_BIGFRAMES_TYPES) for _, dtype in self.dtypes.items() ] if not all(is_numeric_types): raise ValueError("All values must be numeric to apply cumsum.") return self._apply_window_op( agg_ops.product_op, - windows.cumulative_rows(), + bigframes.core.WindowSpec(following=0), ) - @validations.requires_ordering() def cummin(self) -> DataFrame: return self._apply_window_op( agg_ops.min_op, - windows.cumulative_rows(), + bigframes.core.WindowSpec(following=0), ) - @validations.requires_ordering() def cummax(self) -> DataFrame: return self._apply_window_op( agg_ops.max_op, - windows.cumulative_rows(), + bigframes.core.WindowSpec(following=0), ) - @validations.requires_ordering() def shift(self, periods: int = 1) -> DataFrame: - window_spec = windows.rows() - return self._apply_window_op(agg_ops.ShiftOp(periods), window_spec) + window = bigframes.core.WindowSpec( + preceding=periods if periods > 0 else None, + following=-periods if periods < 0 else None, + ) + return self._apply_window_op(agg_ops.ShiftOp(periods), window) - @validations.requires_ordering() def diff(self, periods: int = 1) -> DataFrame: - window_spec = windows.rows() - return self._apply_window_op(agg_ops.DiffOp(periods), window_spec) + window = bigframes.core.WindowSpec( + preceding=periods if periods > 0 else None, + following=-periods if periods < 0 else None, + ) + return self._apply_window_op(agg_ops.DiffOp(periods), window) - @validations.requires_ordering() def pct_change(self, periods: int = 1) -> DataFrame: # Future versions of pandas will not perfrom ffill automatically df = self.ffill() @@ -4171,39 +2209,22 @@ def pct_change(self, periods: int = 1) -> DataFrame: def _apply_window_op( self, - op: agg_ops.UnaryWindowOp, - window_spec: windows.WindowSpec, + op: agg_ops.WindowOp, + window_spec: bigframes.core.WindowSpec, ): block, result_ids = self._block.multi_apply_window_op( self._block.value_columns, op, window_spec=window_spec, ) - if op.skips_nulls: - block = block.project_exprs( - tuple( - bigframes.operations.where_op.as_expr( - r_col, - bigframes.operations.notnull_op.as_expr(og_col), - ex.const(None), - ) - for og_col, r_col in zip(self._block.value_columns, result_ids) - ), - labels=self._block.column_labels, - drop=True, - ) - else: - block = block.select_columns(result_ids) - return DataFrame(block) + return DataFrame(block.select_columns(result_ids)) - @validations.requires_ordering() def sample( self, n: Optional[int] = None, frac: Optional[float] = None, *, random_state: Optional[int] = None, - sort: Optional[bool | Literal["random"]] = "random", ) -> DataFrame: if n is not None and frac is not None: raise ValueError("Only one of 'n' or 'frac' parameter can be specified.") @@ -4211,31 +2232,7 @@ def sample( ns = (n,) if n is not None else () fracs = (frac,) if frac is not None else () return DataFrame( - self._block.split(ns=ns, fracs=fracs, random_state=random_state, sort=sort)[ - 0 - ] - ) - - def explode( - self, - column: typing.Union[blocks.Label, typing.Sequence[blocks.Label]], - *, - ignore_index: Optional[bool] = False, - ) -> DataFrame: - column_labels = bigframes.core.explode.check_column(column) - - column_ids = [self._resolve_label_exact(label) for label in column_labels] - missing = [ - column_labels[i] for i in range(len(column_ids)) if column_ids[i] is None - ] - if len(missing) > 0: - raise KeyError(f"None of {missing} are in the columns") - - return DataFrame( - self._block.explode( - column_ids=typing.cast(typing.Sequence[str], tuple(column_ids)), - ignore_index=ignore_index, - ) + self._block._split(ns=ns, fracs=fracs, random_state=random_state)[0] ) def _split( @@ -4250,149 +2247,75 @@ def _split( At most one of ns and fracs can be passed in. If neither, default to ns = (1,). Return a list of sampled DataFrames. """ - blocks = self._block.split(ns=ns, fracs=fracs, random_state=random_state) + blocks = self._block._split(ns=ns, fracs=fracs, random_state=random_state) return [DataFrame(block) for block in blocks] - @validations.requires_ordering() - def resample( - self, - rule: str, - *, - closed: Optional[Literal["right", "left"]] = None, - label: Optional[Literal["right", "left"]] = None, - on: blocks.Label = None, - level: Optional[LevelsType] = None, - origin: Union[ - Union[ - pandas.Timestamp, datetime.datetime, numpy.datetime64, int, float, str - ], - Literal["epoch", "start", "start_day", "end", "end_day"], - ] = "start_day", - ) -> bigframes.core.groupby.DataFrameGroupBy: - block = self._block._generate_resample_label( - rule=rule, - closed=closed, - label=label, - on=on, - level=level, - origin=origin, - ) - df = DataFrame(block) - return df.groupby(level=0) - - @classmethod - def from_dict( - cls, - data: dict, - orient: str = "columns", - dtype=None, - columns=None, - ) -> DataFrame: - return cls(pandas.DataFrame.from_dict(data, orient, dtype, columns)) # type: ignore - - @classmethod - def from_records( - cls, - data, - index=None, - exclude=None, - columns=None, - coerce_float: bool = False, - nrows: int | None = None, - ) -> DataFrame: - return cls( - pandas.DataFrame.from_records( - data, index, exclude, columns, coerce_float, nrows - ) - ) - def to_csv( - self, - path_or_buf=None, - sep=",", - *, - header: bool = True, - index: bool = True, - allow_large_results: Optional[bool] = None, - ) -> Optional[str]: + self, path_or_buf: str, sep=",", *, header: bool = True, index: bool = True + ) -> None: # TODO(swast): Can we support partition columns argument? # TODO(chelsealin): Support local file paths. # TODO(swast): Some warning that wildcard is recommended for large # query results? See: # https://cloud.google.com/bigquery/docs/exporting-data#limit_the_exported_file_size - if not utils.is_gcs_path(path_or_buf): - pd_df = self.to_pandas(allow_large_results=allow_large_results) - return pd_df.to_csv(path_or_buf, sep=sep, header=header, index=index) + if not path_or_buf.startswith("gs://"): + raise NotImplementedError(ERROR_IO_ONLY_GS_PATHS) if "*" not in path_or_buf: raise NotImplementedError(ERROR_IO_REQUIRES_WILDCARD) - export_array, id_overrides = self._prepare_export( - index=index and self._has_index, - ordering_id=bigframes.session._io.bigquery.IO_ORDERING_ID, + result_table = self._run_io_query( + index=index, ordering_id=bigframes.session._io.bigquery.IO_ORDERING_ID ) - options: dict[str, Union[bool, str]] = { - "field_delimiter": sep, - "header": header, - } - result = self._session._executor.execute( - export_array.rename_columns(id_overrides), - ex_spec.ExecutionSpec( - ex_spec.GcsOutputSpec( - uri=path_or_buf, format="csv", export_options=tuple(options.items()) - ) - ), + export_data_statement = bigframes.session._io.bigquery.create_export_csv_statement( + f"{result_table.project}.{result_table.dataset_id}.{result_table.table_id}", + uri=path_or_buf, + field_delimiter=sep, + header=header, ) - self._set_internal_query_job(result.query_job) - return None + _, query_job = self._block.expr.session._start_query(export_data_statement) + self._set_internal_query_job(query_job) def to_json( self, - path_or_buf=None, - orient: Optional[ - Literal["split", "records", "index", "columns", "values", "table"] - ] = None, + path_or_buf: str, + orient: Literal[ + "split", "records", "index", "columns", "values", "table" + ] = "columns", *, lines: bool = False, index: bool = True, - allow_large_results: Optional[bool] = None, - ) -> Optional[str]: + ) -> None: # TODO(swast): Can we support partition columns argument? - if not utils.is_gcs_path(path_or_buf): - pd_df = self.to_pandas(allow_large_results=allow_large_results) - return pd_df.to_json( - path_or_buf, - orient=orient, - lines=lines, - index=index, - default_handler=str, - ) + # TODO(chelsealin): Support local file paths. + if not path_or_buf.startswith("gs://"): + raise NotImplementedError(ERROR_IO_ONLY_GS_PATHS) + if "*" not in path_or_buf: raise NotImplementedError(ERROR_IO_REQUIRES_WILDCARD) + if lines is True and orient != "records": + raise ValueError( + "'lines' keyword is only valid when 'orient' is 'records'." + ) + # TODO(ashleyxu) Support lines=False for small tables with arrays and TO_JSON_STRING. # See: https://cloud.google.com/bigquery/docs/reference/standard-sql/json_functions#to_json_string if lines is False: raise NotImplementedError( - f"Only newline-delimited JSON is supported. Add `lines=True` to your function call. {constants.FEEDBACK_LINK}" - ) - - if lines is True and orient != "records": - raise ValueError( - "'lines' keyword is only valid when 'orient' is 'records'." + f"Only newline delimited JSON format is supported. {constants.FEEDBACK_LINK}" ) - export_array, id_overrides = self._prepare_export( - index=index and self._has_index, - ordering_id=bigframes.session._io.bigquery.IO_ORDERING_ID, + result_table = self._run_io_query( + index=index, ordering_id=bigframes.session._io.bigquery.IO_ORDERING_ID ) - result = self._session._executor.execute( - export_array.rename_columns(id_overrides), - ex_spec.ExecutionSpec( - ex_spec.GcsOutputSpec(uri=path_or_buf, format="json", export_options=()) - ), + export_data_statement = bigframes.session._io.bigquery.create_export_data_statement( + f"{result_table.project}.{result_table.dataset_id}.{result_table.table_id}", + uri=path_or_buf, + format="JSON", + export_options={}, ) - self._set_internal_query_job(result.query_job) - return None + _, query_job = self._block.expr.session._start_query(export_data_statement) + self._set_internal_query_job(query_job) def to_gbq( self, @@ -4401,13 +2324,25 @@ def to_gbq( if_exists: Optional[Literal["fail", "replace", "append"]] = None, index: bool = True, ordering_id: Optional[str] = None, - clustering_columns: Union[pandas.Index, Iterable[typing.Hashable]] = (), - labels: dict[str, str] = {}, ) -> str: - index = index and self._has_index - temp_table_ref = None + dispositions = { + "fail": bigquery.WriteDisposition.WRITE_EMPTY, + "replace": bigquery.WriteDisposition.WRITE_TRUNCATE, + "append": bigquery.WriteDisposition.WRITE_APPEND, + } if destination_table is None: + # TODO(swast): If there have been no modifications to the DataFrame + # since the last time it was written (cached), then return that. + # For `read_gbq` nodes, return the underlying table clone. + destination_table = bigframes.session._io.bigquery.create_temp_table( + self._session.bqclient, + self._session._anonymous_dataset, + # TODO(swast): allow custom expiration times, probably via session configuration. + datetime.datetime.now(datetime.timezone.utc) + + constants.DEFAULT_EXPIRATION, + ) + if if_exists is not None and if_exists != "replace": raise ValueError( f"Got invalid value {repr(if_exists)} for if_exists. " @@ -4416,20 +2351,7 @@ def to_gbq( ) if_exists = "replace" - # The client code owns this table reference now - temp_table_ref = ( - self._session._anon_dataset_manager.generate_unique_resource_id() - ) - destination_table = f"{temp_table_ref.project}.{temp_table_ref.dataset_id}.{temp_table_ref.table_id}" - - table_parts = destination_table.split(".") - default_project = self._block.expr.session.bqclient.project - - if len(table_parts) == 2: - destination_dataset = f"{default_project}.{table_parts[0]}" - elif len(table_parts) == 3: - destination_dataset = f"{table_parts[0]}.{table_parts[1]}" - else: + if "." not in destination_table: raise ValueError( f"Got invalid value for destination_table {repr(destination_table)}. " "Should be of the form 'datasetId.tableId' or 'projectId.datasetId.tableId'." @@ -4438,98 +2360,45 @@ def to_gbq( if if_exists is None: if_exists = "fail" - valid_if_exists = ["fail", "replace", "append"] - if if_exists not in valid_if_exists: + if if_exists not in dispositions: raise ValueError( f"Got invalid value {repr(if_exists)} for if_exists. " - f"Valid options include None or one of {valid_if_exists}." + f"Valid options include None or one of {dispositions.keys()}." ) - try: - self._session.bqclient.get_dataset(destination_dataset) - except google.api_core.exceptions.NotFound: - self._session.bqclient.create_dataset(destination_dataset, exists_ok=True) - - clustering_fields = self._map_clustering_columns( - clustering_columns, index=index - ) - - export_array, id_overrides = self._prepare_export( - index=index and self._has_index, ordering_id=ordering_id - ) - destination: bigquery.table.TableReference = ( - bigquery.table.TableReference.from_string( + job_config = bigquery.QueryJobConfig( + write_disposition=dispositions[if_exists], + destination=bigquery.table.TableReference.from_string( destination_table, - default_project=default_project, - ) - ) - - result = self._session._executor.execute( - export_array.rename_columns(id_overrides), - ex_spec.ExecutionSpec( - ex_spec.TableOutputSpec( - destination, - cluster_cols=tuple(clustering_fields), - if_exists=if_exists, - ) + default_project=self._block.expr.session.bqclient.project, ), ) - assert result.query_job is not None - self._set_internal_query_job(result.query_job) - - # The query job should have finished, so there should be always be a result table. - result_table = result.query_job.destination - assert result_table is not None - - if temp_table_ref: - bigframes.session._io.bigquery.set_table_expiration( - self._session.bqclient, - temp_table_ref, - datetime.datetime.now(datetime.timezone.utc) - + bigframes.constants.DEFAULT_EXPIRATION, - ) - - if len(labels) != 0: - table = bigquery.Table(result_table) - table.labels = labels - self._session.bqclient.update_table(table, ["labels"]) + self._run_io_query(index=index, ordering_id=ordering_id, job_config=job_config) return destination_table def to_numpy( - self, - dtype=None, - copy=False, - na_value=pd_ext.no_default, - *, - allow_large_results=None, - **kwargs, + self, dtype=None, copy=False, na_value=None, **kwargs ) -> numpy.ndarray: - return self.to_pandas(allow_large_results=allow_large_results).to_numpy( - dtype, copy, na_value, **kwargs - ) + return self.to_pandas().to_numpy(dtype, copy, na_value, **kwargs) - def __array__(self, dtype=None, copy: Optional[bool] = None) -> numpy.ndarray: - if copy is False: - raise ValueError("Cannot convert to array without copy.") - return self.to_numpy(dtype=dtype) + __array__ = to_numpy def to_parquet( self, - path=None, + path: str, *, compression: Optional[Literal["snappy", "gzip"]] = "snappy", index: bool = True, - allow_large_results: Optional[bool] = None, - ) -> Optional[bytes]: + ) -> None: # TODO(swast): Can we support partition columns argument? # TODO(chelsealin): Support local file paths. # TODO(swast): Some warning that wildcard is recommended for large # query results? See: # https://cloud.google.com/bigquery/docs/exporting-data#limit_the_exported_file_size - if not utils.is_gcs_path(path): - pd_df = self.to_pandas(allow_large_results=allow_large_results) - return pd_df.to_parquet(path, compression=compression, index=index) + if not path.startswith("gs://"): + raise NotImplementedError(ERROR_IO_ONLY_GS_PATHS) + if "*" not in path: raise NotImplementedError(ERROR_IO_REQUIRES_WILDCARD) @@ -4540,22 +2409,17 @@ def to_parquet( if compression: export_options["compression"] = compression.upper() - export_array, id_overrides = self._prepare_export( - index=index and self._has_index, - ordering_id=bigframes.session._io.bigquery.IO_ORDERING_ID, + result_table = self._run_io_query( + index=index, ordering_id=bigframes.session._io.bigquery.IO_ORDERING_ID ) - result = self._session._executor.execute( - export_array.rename_columns(id_overrides), - ex_spec.ExecutionSpec( - ex_spec.GcsOutputSpec( - uri=path, - format="parquet", - export_options=tuple(export_options.items()), - ) - ), + export_data_statement = bigframes.session._io.bigquery.create_export_data_statement( + f"{result_table.project}.{result_table.dataset_id}.{result_table.table_id}", + uri=path, + format="PARQUET", + export_options=export_options, ) - self._set_internal_query_job(result.query_job) - return None + _, query_job = self._block.expr.session._start_query(export_data_statement) + self._set_internal_query_job(query_job) def to_dict( self, @@ -4563,25 +2427,12 @@ def to_dict( "dict", "list", "series", "split", "tight", "records", "index" ] = "dict", into: type[dict] = dict, - *, - allow_large_results: Optional[bool] = None, **kwargs, ) -> dict | list[dict]: - return self.to_pandas(allow_large_results=allow_large_results).to_dict( - orient=orient, into=into, **kwargs - ) # type: ignore + return self.to_pandas().to_dict(orient, into, **kwargs) # type: ignore - def to_excel( - self, - excel_writer, - sheet_name: str = "Sheet1", - *, - allow_large_results: Optional[bool] = None, - **kwargs, - ) -> None: - return self.to_pandas(allow_large_results=allow_large_results).to_excel( - excel_writer, sheet_name=sheet_name, **kwargs - ) + def to_excel(self, excel_writer, sheet_name: str = "Sheet1", **kwargs) -> None: + return self.to_pandas().to_excel(excel_writer, sheet_name, **kwargs) def to_latex( self, @@ -4589,29 +2440,16 @@ def to_latex( columns: Sequence | None = None, header: bool | Sequence[str] = True, index: bool = True, - *, - allow_large_results: Optional[bool] = None, **kwargs, ) -> str | None: - return self.to_pandas(allow_large_results=allow_large_results).to_latex( - buf, - columns=typing.cast(typing.Optional[list[str]], columns), - header=typing.cast(typing.Union[bool, list[str]], header), - index=index, - **kwargs, # type: ignore + return self.to_pandas().to_latex( + buf, columns=columns, header=header, index=index, **kwargs # type: ignore ) def to_records( - self, - index: bool = True, - column_dtypes=None, - index_dtypes=None, - *, - allow_large_results=None, + self, index: bool = True, column_dtypes=None, index_dtypes=None ) -> numpy.recarray: - return self.to_pandas(allow_large_results=allow_large_results).to_records( - index, column_dtypes, index_dtypes - ) + return self.to_pandas().to_records(index, column_dtypes, index_dtypes) def to_string( self, @@ -4634,83 +2472,27 @@ def to_string( min_rows: int | None = None, max_colwidth: int | None = None, encoding: str | None = None, - *, - allow_large_results: Optional[bool] = None, ) -> str | None: - return self.to_pandas(allow_large_results=allow_large_results).to_string( - buf, - columns=columns, # type: ignore - col_space=col_space, - header=header, # type: ignore - index=index, - na_rep=na_rep, - formatters=formatters, - float_format=float_format, - sparsify=sparsify, - index_names=index_names, - justify=justify, - max_rows=max_rows, - max_cols=max_cols, - show_dimensions=show_dimensions, - decimal=decimal, - line_width=line_width, - min_rows=min_rows, - max_colwidth=max_colwidth, - encoding=encoding, - ) - - def to_html( - self, - buf=None, - columns: Sequence[str] | None = None, - col_space=None, - header: bool = True, - index: bool = True, - na_rep: str = "NaN", - formatters=None, - float_format=None, - sparsify: bool | None = None, - index_names: bool = True, - justify: str | None = None, - max_rows: int | None = None, - max_cols: int | None = None, - show_dimensions: bool = False, - decimal: str = ".", - bold_rows: bool = True, - classes: str | list | tuple | None = None, - escape: bool = True, - notebook: bool = False, - border: int | None = None, - table_id: str | None = None, - render_links: bool = False, - encoding: str | None = None, - *, - allow_large_results: bool | None = None, - ) -> str: - return self.to_pandas(allow_large_results=allow_large_results).to_html( + return self.to_pandas().to_string( buf, - columns=columns, # type: ignore - col_space=col_space, - header=header, - index=index, - na_rep=na_rep, - formatters=formatters, - float_format=float_format, - sparsify=sparsify, - index_names=index_names, - justify=justify, # type: ignore - max_rows=max_rows, - max_cols=max_cols, - show_dimensions=show_dimensions, - decimal=decimal, - bold_rows=bold_rows, - classes=classes, - escape=escape, - notebook=notebook, - border=border, - table_id=table_id, - render_links=render_links, - encoding=encoding, + columns, # type: ignore + col_space, + header, # type: ignore + index, + na_rep, + formatters, + float_format, + sparsify, + index_names, + justify, + max_rows, + max_cols, + show_dimensions, + decimal, + line_width, + min_rows, + max_colwidth, + encoding, ) def to_markdown( @@ -4718,302 +2500,97 @@ def to_markdown( buf=None, mode: str = "wt", index: bool = True, - *, - allow_large_results: Optional[bool] = None, **kwargs, ) -> str | None: - return self.to_pandas(allow_large_results=allow_large_results).to_markdown( - buf, mode=mode, index=index, **kwargs - ) # type: ignore + return self.to_pandas().to_markdown(buf, mode, index, **kwargs) # type: ignore - def to_pickle(self, path, *, allow_large_results=None, **kwargs) -> None: - return self.to_pandas(allow_large_results=allow_large_results).to_pickle( - path, **kwargs - ) + def to_pickle(self, path, **kwargs) -> None: + return self.to_pandas().to_pickle(path, **kwargs) - def to_orc(self, path=None, *, allow_large_results=None, **kwargs) -> bytes | None: - as_pandas = self.to_pandas(allow_large_results=allow_large_results) + def to_orc(self, path=None, **kwargs) -> bytes | None: + as_pandas = self.to_pandas() # to_orc only works with default index as_pandas_default_index = as_pandas.reset_index() return as_pandas_default_index.to_orc(path, **kwargs) def _apply_unary_op(self, operation: ops.UnaryOp) -> DataFrame: - block = self._block.multi_apply_unary_op(operation) + block = self._block.multi_apply_unary_op(self._block.value_columns, operation) return DataFrame(block) - def _map_clustering_columns( - self, - clustering_columns: Union[pandas.Index, Iterable[typing.Hashable]], - index: bool, - ) -> List[str]: - """Maps the provided clustering columns to the existing columns in the DataFrame.""" - - def map_columns_on_occurrence(columns): - mapped_columns = [] - for col in clustering_columns: - if col in columns: - count = columns.count(col) - mapped_columns.extend([col] * count) - return mapped_columns - - if not clustering_columns: - return [] - - if len(list(clustering_columns)) != len(set(clustering_columns)): - raise ValueError("Duplicates are not supported in clustering_columns") - - all_possible_columns = ( - (set(self.columns) | set(self.index.names)) if index else set(self.columns) - ) - missing_columns = set(clustering_columns) - all_possible_columns - if missing_columns: - raise ValueError( - f"Clustering columns not found in DataFrame: {missing_columns}" - ) - - clustering_columns_for_df = map_columns_on_occurrence( - list(self._block.column_labels) - ) - clustering_columns_for_index = ( - map_columns_on_occurrence(list(self.index.names)) if index else [] - ) - - ( - clustering_columns_for_df, - clustering_columns_for_index, - ) = utils.get_standardized_ids( - clustering_columns_for_df, clustering_columns_for_index - ) - - return clustering_columns_for_index + clustering_columns_for_df - - def _prepare_export( - self, index: bool, ordering_id: Optional[str] - ) -> Tuple[bigframes.core.ArrayValue, Dict[str, str]]: + def _create_io_query(self, index: bool, ordering_id: Optional[str]) -> str: + """Create query text representing this dataframe for I/O.""" array_value = self._block.expr - - new_col_labels, new_idx_labels = utils.get_standardized_ids( - self._block.column_labels, self._block.index.names - ) - columns = list(self._block.value_columns) - column_labels = new_col_labels + column_labels = list(self._block.column_labels) # This code drops unnamed indexes to keep consistent with the behavior of # most pandas write APIs. The exception is `pandas.to_csv`, which keeps # unnamed indexes as `Unnamed: 0`. # TODO(chelsealin): check if works for multiple indexes. if index and self.index.name is not None: columns.extend(self._block.index_columns) - column_labels.extend(new_idx_labels) + column_labels.extend(self.index.names) else: array_value = array_value.drop_columns(self._block.index_columns) # Make columns in SQL reflect _labels_ not _ids_. Note: This may use # the arbitrary unicode column labels feature in BigQuery, which is # currently (June 2023) in preview. + # TODO(swast): Handle duplicate and NULL labels. id_overrides = { col_id: col_label for col_id, col_label in zip(columns, column_labels) - if (col_id != col_label) + if col_label and isinstance(col_label, str) } if ordering_id is not None: - array_value, internal_ordering_id = array_value.promote_offsets() - id_overrides[internal_ordering_id] = ordering_id - return array_value, id_overrides - - def map(self, func, na_action: Optional[str] = None) -> DataFrame: - from bigframes._config import options - - if not isinstance(func, bigframes.functions.Udf) and not ( - options.experiments.enable_python_transpiler and callable(func) - ): - raise TypeError("the first argument must be callable") - - if na_action not in {None, "ignore"}: - raise ValueError(f"na_action={na_action} not supported") - - expr = ops.func_to_expr(func).apply(ex.free_var("input")) - if na_action == "ignore": - # True case, predicate, False case - expr = ops.where_op.as_expr( - expr, ops.notnull_op.as_expr(ex.free_var("input")), ex.const(None) + return array_value.to_sql( + offset_column=ordering_id, + col_id_overrides=id_overrides, ) - - return DataFrame(self._block.multi_apply_unary_op(expr)) - - def apply(self, func, *, axis=0, args: typing.Tuple = (), **kwargs): - # In Bigframes BigQuery function, DataFrame '.apply' method is specifically - # designed to work with row-wise or column-wise operations, where the input - # to the applied function should be a Series, not a scalar. - - if utils.get_axis_number(axis) == 1: - msg = bfe.format_message( - "DataFrame.apply with parameter axis=1 scenario is in preview." + else: + return array_value.to_sql( + col_id_overrides=id_overrides, ) - warnings.warn(msg, category=bfe.FunctionAxisOnePreviewWarning) - - from bigframes._config import options - if not isinstance(func, bigframes.functions.Udf) and not ( - options.experiments.enable_python_transpiler and callable(func) - ): - raise ValueError( - "For axis=1 a BigFrames BigQuery function must be used." - ) + def _run_io_query( + self, + index: bool, + ordering_id: Optional[str] = None, + job_config: Optional[bigquery.job.QueryJobConfig] = None, + ) -> bigquery.TableReference: + """Executes a query job presenting this dataframe and returns the destination + table.""" + expr = self._block.expr + session = expr.session + sql = self._create_io_query(index=index, ordering_id=ordering_id) + _, query_job = session._start_query( + sql=sql, job_config=job_config # type: ignore + ) + self._set_internal_query_job(query_job) - if ( - not isinstance(func, bigframes.functions.Udf) - and options.experiments.enable_python_transpiler - and callable(func) - ): - result_block = block_ops.apply_to_block_rows( - func, self._block, *args, **kwargs - ) - return bigframes.series.Series(result_block) - - if func.udf_def.signature.is_row_processor: - # Early check whether the dataframe dtypes are currently supported - # in the bigquery function - # NOTE: Keep in sync with the value converters used in the gcf code - # generated in function_template.py - bigquery_function_supported_dtypes = ( - bigframes.dtypes.INT_DTYPE, - bigframes.dtypes.FLOAT_DTYPE, - bigframes.dtypes.BOOL_DTYPE, - bigframes.dtypes.BYTES_DTYPE, - bigframes.dtypes.STRING_DTYPE, - ) - supported_dtypes_types = tuple( - type(dtype) - for dtype in bigquery_function_supported_dtypes - if not isinstance(dtype, pandas.ArrowDtype) - ) - # Check ArrowDtype separately since multiple BigQuery types map to - # ArrowDtype, including BYTES and TIMESTAMP. - supported_arrow_types = tuple( - dtype.pyarrow_dtype - for dtype in bigquery_function_supported_dtypes - if isinstance(dtype, pandas.ArrowDtype) - ) - supported_dtypes_hints = tuple( - str(dtype) for dtype in bigquery_function_supported_dtypes - ) + # The query job should have finished, so there should be always be a result table. + result_table = query_job.destination + assert result_table is not None + return result_table - for dtype in self.dtypes: - if ( - # Not one of the pandas/numpy types. - not isinstance(dtype, supported_dtypes_types) - # And not one of the arrow types. - and not ( - isinstance(dtype, pandas.ArrowDtype) - and any( - dtype.pyarrow_dtype.equals(arrow_type) - for arrow_type in supported_arrow_types - ) - ) - ): - raise NotImplementedError( - f"DataFrame has a column of dtype '{dtype}' which is not supported with axis=1." - f" Supported dtypes are {supported_dtypes_hints}." - ) - - # Serialize the rows as json values - block = self._get_block() - rows_as_json_series = bigframes.series.Series( - block._get_rows_as_json_values() - ) + def map(self, func, na_action: Optional[str] = None) -> DataFrame: + if not callable(func): + raise TypeError("the first argument must be callable") - # Apply the function - expr = ops.func_to_expr(func).expr - if not ( - isinstance(expr, ex.OpExpression) - and isinstance(expr.op, ops.NaryOp) - ): - raise TypeError(f"Expected OpExpression with NaryOp, got {expr}") - result_series = rows_as_json_series._apply_nary_op( - expr.op, - list(args), - ) + if na_action not in {None, "ignore"}: + raise ValueError(f"na_action={na_action} not supported") - else: - # This is a special case where we are providing not-pandas-like - # extension. If the bigquery function can take one or more - # params (excluding the args) then we assume that here the user - # intention is to use the column values of the dataframe as - # arguments to the function. For this to work the following - # condition must be true: - # 1. The number or input params (excluding the args) in the - # function must be same as the number of columns in the - # dataframe. - # 2. The dtypes of the columns in the dataframe must be - # compatible with the data types of the input params. - # 3. The order of the columns in the dataframe must correspond - # to the order of the input params in the function. - udf_input_dtypes = tuple( - arg.bf_type for arg in func.udf_def.signature.inputs - ) - if not args and len(udf_input_dtypes) != len(self.columns): - raise ValueError( - f"Parameter count mismatch: BigFrames BigQuery function" - f" expected {len(udf_input_dtypes)} parameters but" - f" received {len(self.columns)} DataFrame columns." - ) - if args and len(udf_input_dtypes) != len(self.columns) + len(args): - raise ValueError( - f"Parameter count mismatch: BigFrames BigQuery function" - f" expected {len(udf_input_dtypes)} parameters but" - f" received {len(self.columns) + len(args)} values" - f" ({len(self.columns)} DataFrame columns and" - f" {len(args)} args)." - ) - end_slice = -len(args) if args else None - if udf_input_dtypes[:end_slice] != tuple(self.dtypes.to_list()): - raise ValueError( - f"Data type mismatch for DataFrame columns:" - f" Expected {udf_input_dtypes[:end_slice]}" - f" Received {tuple(self.dtypes)}." - ) - if args: - bq_types = ( - function_typing.sdk_type_from_python_type(type(arg)) - for arg in args - ) - args_dtype = tuple( - function_typing.sdk_type_to_bf_type(bq_type) - for bq_type in bq_types - ) - if udf_input_dtypes[end_slice:] != args_dtype: - raise ValueError( - f"Data type mismatch for 'args' parameter:" - f" Expected {udf_input_dtypes[end_slice:]}" - f" Received {args_dtype}." - ) - - series_list = [self[col] for col in self.columns] - op_list = series_list[1:] + list(args) - result_series = series_list[0]._apply_callable_expr( - ops.func_to_expr(func), op_list - ) - result_series.name = None - - return result_series - - # At this point column-wise or element-wise bigquery function operation will - # be performed (not supported). - if isinstance(func, bigframes.functions.Udf): - raise formatter.create_exception_with_feedback_link( - NotImplementedError, - "BigFrames DataFrame '.apply()' does not support BigFrames " - "BigQuery function for column-wise (i.e. with axis=0) " - "operations, please use a regular python function instead. For " - "element-wise operations of the BigFrames BigQuery function, " - "please use '.map()'.", - ) + # TODO(shobs): Support **kwargs + # Reproject as workaround to applying filter too late. This forces the filter + # to be applied before passing data to remote function, protecting from bad + # inputs causing errors. + reprojected_df = DataFrame(self._block._force_reproject()) + return reprojected_df._apply_unary_op( + ops.RemoteFunctionOp(func, apply_on_null=(na_action is None)) + ) - # Per-column apply + def apply(self, func, *, args: typing.Tuple = (), **kwargs): results = {name: func(col, *args, **kwargs) for name, col in self.items()} - if all( [ isinstance(val, bigframes.series.Series) or utils.is_list_like(val) @@ -5053,7 +2630,7 @@ def duplicated(self, subset=None, keep: str = "first") -> bigframes.series.Serie return bigframes.series.Series( block.select_column( indicator, - ).with_column_labels(pandas.Index([None])), + ) ) def rank( @@ -5063,12 +2640,9 @@ def rank( numeric_only=False, na_option: str = "keep", ascending=True, - pct: bool = False, ) -> DataFrame: df = self._drop_non_numeric() if numeric_only else self - return DataFrame( - block_ops.rank(df._block, method, na_option, ascending, pct=pct) - ) + return DataFrame(block_ops.rank(df._block, method, na_option, ascending)) def first_valid_index(self): return @@ -5081,9 +2655,7 @@ def _slice( stop: typing.Optional[int] = None, step: typing.Optional[int] = None, ) -> DataFrame: - block = self._block.slice( - start=start, stop=stop, step=step if (step is not None) else 1 - ) + block = self._block.slice(start=start, stop=stop, step=step) return DataFrame(block) def __array_ufunc__( @@ -5102,7 +2674,7 @@ def __array_ufunc__( if inputs[0] is self: return self._apply_binop(inputs[1], binop) else: - return self._apply_binop(inputs[0], binop, reverse=True) + return self._apply_binop(inputs[0], ops.reverse(binop)) return NotImplemented @@ -5112,32 +2684,13 @@ def _set_block(self, block: blocks.Block): def _get_block(self) -> blocks.Block: return self._block - def cache(self): - """ - Materializes the DataFrame to a temporary table. - - Useful if the dataframe will be used multiple times, as this will avoid recomputating the shared intermediate value. - - Returns: - bigframes.pandas.DataFrame: DataFrame - """ - return self._cached(force=True) - - def _cached(self, *, force: bool = False) -> DataFrame: - """Materialize dataframe to a temporary table. - No-op if the dataframe represents a trivial transformation of an existing materialization. - Force=True is used for BQML integration where need to copy data rather than use snapshot. - """ - if self._disable_cache_override: - return self - self._block.cached(force=force) - return self + def _cached(self) -> DataFrame: + return DataFrame(self._block.cached()) _DataFrameOrSeries = typing.TypeVar("_DataFrameOrSeries") - @validations.requires_ordering() def dot(self, other: _DataFrameOrSeries) -> _DataFrameOrSeries: - if not isinstance(other, (DataFrame, bigframes.series.Series)): + if not isinstance(other, (DataFrame, bf_series.Series)): raise NotImplementedError( f"Only DataFrame or Series operand is supported. {constants.FEEDBACK_LINK}" ) @@ -5212,75 +2765,9 @@ def get_right_id(id): ) result = result[other_frame.columns] - if isinstance(other, bigframes.series.Series): - # There should be exactly one column in the result - result = result[result.columns[0]].rename() + if isinstance(other, bf_series.Series): + result = result[other.name].rename() return result - @property - def plot(self): - return plotting.PlotAccessor(self) - - def hist( - self, by: typing.Optional[typing.Sequence[str]] = None, bins: int = 10, **kwargs - ): - return self.plot.hist(by=by, bins=bins, **kwargs) - - hist.__doc__ = inspect.getdoc(plotting.PlotAccessor.hist) - - def line( - self, - x: typing.Optional[typing.Hashable] = None, - y: typing.Optional[typing.Hashable] = None, - **kwargs, - ): - return self.plot.line(x=x, y=y, **kwargs) - - line.__doc__ = inspect.getdoc(plotting.PlotAccessor.line) - - def area( - self, - x: typing.Optional[typing.Hashable] = None, - y: typing.Optional[typing.Hashable] = None, - stacked: bool = True, - **kwargs, - ): - return self.plot.area(x=x, y=y, stacked=stacked, **kwargs) - - area.__doc__ = inspect.getdoc(plotting.PlotAccessor.area) - - def bar( - self, - x: typing.Optional[typing.Hashable] = None, - y: typing.Optional[typing.Hashable] = None, - **kwargs, - ): - return self.plot.bar(x=x, y=y, **kwargs) - - bar.__doc__ = inspect.getdoc(plotting.PlotAccessor.bar) - - def scatter( - self, - x: typing.Optional[typing.Hashable] = None, - y: typing.Optional[typing.Hashable] = None, - s: typing.Union[typing.Hashable, typing.Sequence[typing.Hashable]] = None, - c: typing.Union[typing.Hashable, typing.Sequence[typing.Hashable]] = None, - **kwargs, - ): - return self.plot.scatter(x=x, y=y, s=s, c=c, **kwargs) - - scatter.__doc__ = inspect.getdoc(plotting.PlotAccessor.scatter) - - def __matmul__(self, other) -> DataFrame: - return self.dot(other) - - @property - def struct(self): - return bigframes.operations.structs.StructFrameAccessor(self) - - def _throw_if_null_index(self, opname: str): - if not self._has_index: - raise bigframes.exceptions.NullIndexError( - f"DataFrame cannot perform {opname} as it has no index. Set an index using set_index." - ) + __matmul__ = dot diff --git a/bigframes/display/__init__.py b/bigframes/display/__init__.py deleted file mode 100644 index aa1371db564..00000000000 --- a/bigframes/display/__init__.py +++ /dev/null @@ -1,48 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -"""Interactive display objects for BigQuery DataFrames.""" - -from __future__ import annotations - -from typing import Any - - -def __getattr__(name: str) -> Any: - """Lazily import TableWidget to avoid ZMQ port conflicts. - - anywidget and traitlets eagerly initialize kernel communication channels on - import. This can lead to race conditions and ZMQ port conflicts when - multiple Jupyter kernels are started in parallel, such as during notebook - tests. By using __getattr__, we defer the import of TableWidget until it is - explicitly accessed, preventing premature initialization and avoiding port - collisions. - """ - if name == "TableWidget": - try: - import anywidget # noqa - - from bigframes.display.anywidget import TableWidget - - return TableWidget - except Exception: - raise AttributeError( - f"module '{__name__}' has no attribute '{name}'. " - "TableWidget requires anywidget and traitlets to be installed. " - "Please `pip install anywidget traitlets` or `pip install 'bigframes[anywidget]'`." - ) - raise AttributeError(f"module '{__name__}' has no attribute '{name}'") - - -__all__ = ["TableWidget"] diff --git a/bigframes/display/anywidget.py b/bigframes/display/anywidget.py deleted file mode 100644 index 01135d6670b..00000000000 --- a/bigframes/display/anywidget.py +++ /dev/null @@ -1,643 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -"""Interactive, paginated table widget for BigFrames DataFrames.""" - -from __future__ import annotations - -import dataclasses -import functools -import logging - -logger = logging.getLogger(__name__) -import math -import threading -import uuid -import warnings -from importlib import resources -from typing import Any, Iterator, Optional - -import pandas as pd - -import bigframes -import bigframes.dataframe -import bigframes.display.html -import bigframes.dtypes as dtypes -from bigframes.core import blocks - -# anywidget and traitlets are optional dependencies. We don't want the import of -# this module to fail if they aren't installed, though. Instead, we try to -# limit the surface that these packages could affect. This makes unit testing -# easier and ensures we don't accidentally make these required packages. -try: - import anywidget - import traitlets - - _ANYWIDGET_INSTALLED = True -except Exception: - _ANYWIDGET_INSTALLED = False - -_WIDGET_BASE: type[Any] -if _ANYWIDGET_INSTALLED: - _WIDGET_BASE = anywidget.AnyWidget -else: - _WIDGET_BASE = object - - -@dataclasses.dataclass(frozen=True) -class _SortState: - columns: tuple[str, ...] - ascending: tuple[bool, ...] - - -@dataclasses.dataclass -class _ExecutionResult: - df_to_set: Optional[bigframes.dataframe.DataFrame] = None - orderable_cols: Optional[list[str]] = None - batches: Optional[blocks.PandasBatches] = None - batch_iter: Optional[Iterator[pd.DataFrame]] = None - cached_batches: Optional[list[pd.DataFrame]] = None - all_data_loaded: bool = False - total_rows: Optional[int] = None - initial_html: Optional[str] = None - error_message: Optional[str] = None - - -class TableWidget(_WIDGET_BASE): - """An interactive, paginated table widget for BigFrames DataFrames. - - This widget provides a user-friendly way to display and navigate through - large BigQuery DataFrames within a Jupyter environment. - """ - - page = traitlets.Int(0).tag(sync=True) - page_size = traitlets.Int(0).tag(sync=True) - max_columns = traitlets.Int(allow_none=True, default_value=None).tag(sync=True) - row_count = traitlets.Int(allow_none=True, default_value=None).tag(sync=True) - table_html = traitlets.Unicode("").tag(sync=True) - sort_context = traitlets.List(traitlets.Dict(), default_value=[]).tag(sync=True) - orderable_columns = traitlets.List(traitlets.Unicode(), []).tag(sync=True) - _initial_load_complete = traitlets.Bool(False).tag(sync=True) - _batches: Optional[blocks.PandasBatches] = None - _error_message = traitlets.Unicode(allow_none=True, default_value=None).tag( - sync=True - ) - start_execution = traitlets.Bool(False).tag(sync=True) - is_deferred_mode = traitlets.Bool(False).tag(sync=True) - dry_run_info = traitlets.Unicode("").tag(sync=True) - ping = traitlets.Int(0).tag(sync=True) - - def __init__( - self, - dataframe: ( - bigframes.dataframe.DataFrame - | bigframes.session.deferred.DeferredBigQueryDataFrame - ), - dry_run_info: Optional[str] = None, - ): - """Initialize the TableWidget. - - Args: - dataframe: The Bigframes Dataframe to display in the widget. - """ - if not _ANYWIDGET_INSTALLED: - raise ImportError( - "Please `pip install anywidget traitlets` or " - "`pip install 'bigframes[anywidget]'` to use TableWidget." - ) - - # Enable third-party widgets manager in Google Colab environment. - try: - import sys - - if "google.colab" in sys.modules: - from google.colab import output - - output.enable_custom_widget_manager() - except Exception: - pass - - from bigframes.session import deferred - - is_deferred = False - deferred_df = None - df = None - - if isinstance(dataframe, deferred.DeferredBigQueryDataFrame): - is_deferred = True - deferred_df = dataframe - elif bigframes.options.display.repr_mode == "deferred": - is_deferred = True - df = dataframe - else: - df = dataframe - - from bigframes.core.utils import get_ipython_execution_count - - self._cell_execution_count = get_ipython_execution_count() - - super().__init__() - - self.is_deferred_mode = is_deferred - self._deferred_dataframe = deferred_df - self._dataframe = df - - if dry_run_info: - self.dry_run_info = dry_run_info - - # Initialize attributes that might be needed by observers first - self._table_id = str(uuid.uuid4()) - self._all_data_loaded = False - self._batch_iter: Optional[Iterator[pd.DataFrame]] = None - self._cached_batches: list[pd.DataFrame] = [] - self._last_sort_state: Optional[_SortState] = None - self._execution_result: Optional[_ExecutionResult] = None - # Lock to ensure only one thread at a time is updating the table HTML. - self._setting_html_lock = threading.Lock() - - # respect display options for initial page size - initial_page_size = bigframes.options.display.max_rows - initial_max_columns = bigframes.options.display.max_columns - - self.page_size = initial_page_size - self.max_columns = initial_max_columns - - if not self.is_deferred_mode: - self._initialize_from_dataframe() - - # Signals to the frontend that the initial data load is complete. - # Also used as a guard to prevent observers from firing during initialization. - self._initial_load_complete = True - - @traitlets.observe("start_execution") - def _on_start_execution(self, change: dict[str, Any]): - if change["new"]: - import asyncio - - try: - loop = asyncio.get_running_loop() - except RuntimeError: - try: - import tornado.ioloop # type: ignore[import-not-found] - - loop = tornado.ioloop.IOLoop.current().asyncio_loop # type: ignore[attr-defined] - except Exception: - loop = None - - def run_execution(): - try: - self._error_message = None - df = None - if self.is_deferred_mode: - if self._deferred_dataframe is not None: - result = self._deferred_dataframe.execute() - if isinstance(result, bigframes.series.Series): - df = result.to_frame() - elif isinstance(result, bigframes.dataframe.DataFrame): - df = result - else: - raise TypeError( - f"Unexpected result type: {type(result)}" - ) - elif self._dataframe is not None: - df = self._dataframe - else: - df = self._dataframe - - if df is None: - raise ValueError("No DataFrame to execute.") - - df_to_set = df._prepare_display_df() - orderable_cols = self._get_orderable_columns(df_to_set) - - with bigframes.option_context("display.progress_bar", None): - batches = df_to_set.to_pandas_batches( - page_size=self.page_size, - cell_execution_count=self._cell_execution_count, - ) - - total_rows = getattr(batches, "total_rows", None) - - # Fetch the first batch - batch_iter = iter(batches) - try: - initial_batch = next(batch_iter) - cached_batches = [initial_batch] - all_data_loaded = False - except StopIteration: - initial_batch = pd.DataFrame(columns=df_to_set.columns) - cached_batches = [] - all_data_loaded = True - - # Render the HTML - page_data = initial_batch.copy() - start = 0 - if df_to_set._block.has_index: - is_unnamed_single_index = ( - page_data.index.name is None - and not isinstance(page_data.index, pd.MultiIndex) - ) - page_data = page_data.reset_index() - if is_unnamed_single_index and "index" in page_data.columns: - page_data.rename(columns={"index": ""}, inplace=True) - else: - page_data.insert( - 0, "Row", range(start + 1, start + len(page_data) + 1) - ) - - initial_html = bigframes.display.html.render_html( - dataframe=page_data, - table_id=f"table-{self._table_id}", - orderable_columns=orderable_cols, - max_columns=self.max_columns, - ) - - self._execution_result = _ExecutionResult( - df_to_set=df_to_set, - orderable_cols=orderable_cols, - batches=batches, - batch_iter=batch_iter, - cached_batches=cached_batches, - all_data_loaded=all_data_loaded, - total_rows=total_rows, - initial_html=initial_html, - ) - except Exception as e: - logger.warning(f"Error in background execution: {e}") - self._execution_result = _ExecutionResult(error_message=str(e)) - - import sys - - is_colab = "google.colab" in sys.modules - - if loop is not None and loop.is_running() and not is_colab: - loop.call_soon_threadsafe(self._apply_execution_result) - elif is_colab: - # In Google Colab, background thread updates to traitlets are not automatically - # synchronized to the frontend. We rely on the frontend's active pinging - # (which triggers `_on_ping` on the main kernel thread) to apply the result. - pass - else: - self._apply_execution_result() - - self._execution_thread = threading.Thread(target=run_execution, daemon=True) - self._execution_thread.start() - - def _apply_execution_result(self) -> None: - if self._execution_result is None: - return - - result = self._execution_result - self._execution_result = None - - with self.hold_sync(): - if result.error_message is not None: - self._error_message = result.error_message - self.start_execution = False - else: - self._dataframe = result.df_to_set - self.orderable_columns = result.orderable_cols or [] - self._batches = result.batches - self._batch_iter = result.batch_iter - self._cached_batches = result.cached_batches or [] - self._all_data_loaded = result.all_data_loaded - self._last_sort_state = _SortState((), ()) - self.row_count = result.total_rows - self.table_html = result.initial_html or "" - self.is_deferred_mode = False - self.start_execution = False - - @traitlets.observe("ping") - def _on_ping(self, _change: dict[str, Any]): - self._apply_execution_result() - - def _initialize_from_dataframe(self): - if self._dataframe is None: - return - - self.orderable_columns = self._get_orderable_columns(self._dataframe) - - self._initial_load() - - def _get_orderable_columns( - self, dataframe: bigframes.dataframe.DataFrame - ) -> list[str]: - """Determine which columns can be used for client-side sorting.""" - # TODO(b/469861913): Nested columns from structs (e.g., 'struct_col.name') are not currently sortable. - # TODO(b/463754889): Support non-string column labels for sorting. - if not all(isinstance(col, str) for col in dataframe.columns): - return [] - - with warnings.catch_warnings(): - warnings.simplefilter("ignore", bigframes.exceptions.JSONDtypeWarning) - warnings.simplefilter("ignore", category=FutureWarning) - return [ - str(col_name) - for col_name, dtype in dataframe.dtypes.items() - if dtypes.is_orderable(dtype) - ] - - def _initial_load(self) -> None: - """Get initial data and row count.""" - # obtain the row counts - # TODO(b/428238610): Start iterating over the result of `to_pandas_batches()` - # before we get here so that the count might already be cached. - with bigframes.option_context("display.progress_bar", None): - self._reset_batches_for_new_page_size() - - if self._batches is None: - self._error_message = ( - "Could not retrieve data batches. Data might be unavailable or " - "an error occurred." - ) - self.row_count = None - elif self._batches.total_rows is None: - # Total rows is unknown, this is an expected state. - # TODO(b/461536343): Cheaply discover if we have exactly 1 page. - # There are cases where total rows is not set, but there are no additional - # pages. We could disable the "next" button in these cases. - self.row_count = None - else: - self.row_count = self._batches.total_rows - - # get the initial page - self._set_table_html() - - @traitlets.observe("_initial_load_complete") - def _on_initial_load_complete(self, change: dict[str, Any]): - if change["new"]: - self._set_table_html() - - @functools.cached_property - def _esm(self): - """Load JavaScript code from the compiled Angular hybrid bundle.""" - return resources.read_text(bigframes.display, "table_widget_angular.js") - - @functools.cached_property - def _css(self): - """Load CSS code from external file.""" - return resources.read_text(bigframes.display, "table_widget.css") - - @traitlets.validate("page") - def _validate_page(self, proposal: dict[str, Any]) -> int: - """Validate and clamp the page number to a valid range. - - Args: - proposal: A dictionary from the traitlets library containing the - proposed change. The new value is in proposal["value"]. - - Returns: - The validated and clamped page number as an integer. - """ - value = proposal["value"] - - if value < 0: - raise ValueError("Page number cannot be negative.") - - # If truly empty or invalid page size, stay on page 0. - # This handles cases where row_count is 0 or page_size is 0, preventing - # division by zero or nonsensical pagination, regardless of row_count being None. - if self.row_count == 0 or self.page_size == 0: - return 0 - - # If row count is unknown, allow any non-negative page. The previous check - # ensures that invalid page_size (0) is already handled. - if self.row_count is None: - return value - - # Calculate the zero-indexed maximum page number. - max_page = max(0, math.ceil(self.row_count / self.page_size) - 1) - - # Clamp the proposed value to the valid range [0, max_page]. - return max(0, min(value, max_page)) - - @traitlets.validate("page_size") - def _validate_page_size(self, proposal: dict[str, Any]) -> int: - """Validate page size to ensure it's positive and reasonable. - - Args: - proposal: A dictionary from the traitlets library containing the - proposed change. The new value is in proposal["value"]. - - Returns: - The validated page size as an integer. - """ - value = proposal["value"] - - # Ensure page size is positive and within reasonable bounds - if value <= 0: - return self.page_size # Keep current value - - # Cap at reasonable maximum to prevent performance issues - max_page_size = 1000 - return min(value, max_page_size) - - @traitlets.validate("max_columns") - def _validate_max_columns(self, proposal: dict[str, Any]) -> int: - """Validate max columns to ensure it's positive or 0 (for all).""" - value = proposal["value"] - if value is None: - return 0 # Normalize None to 0 for traitlet - return max(0, value) - - def _get_next_batch(self) -> bool: - """ - Gets the next batch of data from the generator and appends to cache. - - Returns: - True if a batch was successfully loaded, False otherwise. - """ - if self._all_data_loaded: - return False - - try: - iterator = self._batch_iterator - batch = next(iterator) - self._cached_batches.append(batch) - return True - except StopIteration: - self._all_data_loaded = True - return False - - @property - def _batch_iterator(self) -> Iterator[pd.DataFrame]: - """Lazily initializes and returns the batch iterator.""" - if self._batch_iter is None: - if self._batches is None: - self._batch_iter = iter([]) - else: - self._batch_iter = iter(self._batches) - return self._batch_iter - - @property - def _cached_data(self) -> pd.DataFrame: - """Combine all cached batches into a single DataFrame.""" - if not self._cached_batches: - if self._dataframe is not None: - return pd.DataFrame(columns=self._dataframe.columns) - return pd.DataFrame() - return pd.concat(self._cached_batches) - - def _reset_batch_cache(self) -> None: - """Resets batch caching attributes.""" - self._cached_batches = [] - self._batch_iter = None - self._all_data_loaded = False - - def _reset_batches_for_new_page_size(self) -> None: - """Reset the batch iterator when page size changes.""" - if self._dataframe is None: - return - with bigframes.option_context("display.progress_bar", None): - self._batches = self._dataframe.to_pandas_batches( - page_size=self.page_size, - cell_execution_count=self._cell_execution_count, - ) - - self._reset_batch_cache() - - def _set_table_html(self) -> None: - """Sets the current html data based on the current page and page size.""" - if self.is_deferred_mode: - return - - new_page = None - with ( - self._setting_html_lock, - bigframes.option_context("display.progress_bar", None), - ): - if self._error_message: - self.table_html = ( - f"
{self._error_message}
" - ) - return - - if self._dataframe is None: - self.table_html = "
Internal Error: DataFrame is missing.
" - return - - # Apply sorting if a column is selected - df_to_display = self._dataframe - sort_columns = [item["column"] for item in self.sort_context] - sort_ascending = [item["ascending"] for item in self.sort_context] - - if sort_columns: - # TODO(b/463715504): Support sorting by index columns. - df_to_display = df_to_display.sort_values( - by=sort_columns, ascending=sort_ascending - ) - - # Reset batches when sorting changes - current_sort_state = _SortState(tuple(sort_columns), tuple(sort_ascending)) - if self._last_sort_state != current_sort_state: - self._batches = df_to_display.to_pandas_batches( - page_size=self.page_size, - cell_execution_count=self._cell_execution_count, - ) - self._reset_batch_cache() - self._last_sort_state = current_sort_state - if self.page != 0: - new_page = 0 # Reset to first page - - if new_page is None: - start = self.page * self.page_size - end = start + self.page_size - - # fetch more data if the requested page is outside our cache - cached_data = self._cached_data - while len(cached_data) < end and not self._all_data_loaded: - if self._get_next_batch(): - cached_data = self._cached_data - else: - break - - # Get the data for the current page - page_data = cached_data.iloc[start:end].copy() - - # Handle case where user navigated beyond available data with unknown row count - is_unknown_count = self.row_count is None - is_beyond_data = ( - self._all_data_loaded and len(page_data) == 0 and self.page > 0 - ) - if is_unknown_count and is_beyond_data: - # Calculate the last valid page (zero-indexed) - total_rows = len(cached_data) - last_valid_page = max(0, math.ceil(total_rows / self.page_size) - 1) - if self.page != last_valid_page: - new_page = last_valid_page - - if new_page is None: - # Handle index display - if self._dataframe._block.has_index: - is_unnamed_single_index = ( - page_data.index.name is None - and not isinstance(page_data.index, pd.MultiIndex) - ) - page_data = page_data.reset_index() - if is_unnamed_single_index and "index" in page_data.columns: - page_data.rename(columns={"index": ""}, inplace=True) - - # Default index - include as "Row" column if no index was present originally - if not self._dataframe._block.has_index: - page_data.insert( - 0, "Row", range(start + 1, start + len(page_data) + 1) - ) - - # Generate HTML table - self.table_html = bigframes.display.html.render_html( - dataframe=page_data, - table_id=f"table-{self._table_id}", - orderable_columns=self.orderable_columns, - max_columns=self.max_columns, - ) - - if new_page is not None: - # Navigate to the new page. This triggers the observer, which will - # re-enter _set_table_html. Since we've released the lock, this is safe. - self.page = new_page - - @traitlets.observe("sort_context") - def _sort_changed(self, _change: dict[str, Any]): - """Handler for when sorting parameters change from the frontend.""" - self._set_table_html() - - @traitlets.observe("page") - def _page_changed(self, _change: dict[str, Any]) -> None: - """Handler for when the page number is changed from the frontend.""" - if not self._initial_load_complete: - return - self._set_table_html() - - @traitlets.observe("page_size") - def _page_size_changed(self, _change: dict[str, Any]) -> None: - """Handler for when the page size is changed from the frontend.""" - if not self._initial_load_complete: - return - # Reset the page to 0 when page size changes to avoid invalid page states - self.page = 0 - # Reset the sort state to default (no sort) - self.sort_context = [] - - # Reset batches to use new page size for future data fetching - self._reset_batches_for_new_page_size() - - # Update the table display - self._set_table_html() - - @traitlets.observe("max_columns") - def _max_columns_changed(self, _change: dict[str, Any]) -> None: - """Handler for when max columns is changed from the frontend.""" - if not self._initial_load_complete: - return - self._set_table_html() diff --git a/bigframes/display/html.py b/bigframes/display/html.py deleted file mode 100644 index 603d53e6866..00000000000 --- a/bigframes/display/html.py +++ /dev/null @@ -1,386 +0,0 @@ -# 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. -# 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. - -"""HTML rendering for DataFrames and other objects.""" - -from __future__ import annotations - -import html -import json -import traceback -import typing -import warnings -from typing import Any, Union - -import pandas as pd -import pandas.api.types - -import bigframes -import bigframes.formatting_helpers as formatter -from bigframes._config import display_options, options -from bigframes.display import plaintext - -if typing.TYPE_CHECKING: - import bigframes.dataframe - import bigframes.series - - -def _is_dtype_numeric(dtype: Any) -> bool: - """Check if a dtype is numeric for alignment purposes.""" - return pandas.api.types.is_numeric_dtype(dtype) - - -def render_html( - *, - dataframe: pd.DataFrame, - table_id: str, - orderable_columns: list[str] | None = None, - max_columns: int | None = None, -) -> str: - """Render a pandas DataFrame to HTML with specific styling.""" - orderable_columns = orderable_columns or [] - classes = "dataframe table table-striped table-hover" - table_html_parts = [f''] - - # Handle column truncation - columns = list(dataframe.columns) - if max_columns is not None and max_columns > 0 and len(columns) > max_columns: - half = max_columns // 2 - left_columns = columns[:half] - # Ensure we don't take more than available if half is 0 or calculation is weird, - # but typical case is safe. - right_count = max_columns - half - right_columns = columns[-right_count:] if right_count > 0 else [] - show_ellipsis = True - else: - left_columns = columns - right_columns = [] - show_ellipsis = False - - table_html_parts.append( - _render_table_header( - dataframe, orderable_columns, left_columns, right_columns, show_ellipsis - ) - ) - table_html_parts.append( - _render_table_body(dataframe, left_columns, right_columns, show_ellipsis) - ) - table_html_parts.append("
") - return "".join(table_html_parts) - - -def _render_table_header( - dataframe: pd.DataFrame, - orderable_columns: list[str], - left_columns: list[Any], - right_columns: list[Any], - show_ellipsis: bool, -) -> str: - """Render the header of the HTML table.""" - header_parts = [" ", " "] - - def render_col_header(col): - th_classes = [] - if col in orderable_columns: - th_classes.append("sortable") - class_str = f'class="{" ".join(th_classes)}"' if th_classes else "" - header_parts.append( - f'
' - f"{html.escape(str(col))}
" - ) - - for col in left_columns: - render_col_header(col) - - if show_ellipsis: - header_parts.append( - '
...
' - ) - - for col in right_columns: - render_col_header(col) - - header_parts.extend([" ", " "]) - return "\n".join(header_parts) - - -def _render_table_body( - dataframe: pd.DataFrame, - left_columns: list[Any], - right_columns: list[Any], - show_ellipsis: bool, -) -> str: - """Render the body of the HTML table.""" - body_parts = [" "] - precision = options.display.precision - - for i in range(len(dataframe)): - body_parts.append(" ") - row = dataframe.iloc[i] - - def render_col_cell(col_name): - value = row[col_name] - dtype = dataframe.dtypes.loc[col_name] # type: ignore - align = "right" if _is_dtype_numeric(dtype) else "left" - - # TODO(b/438181139): Consider semi-exploding ARRAY/STRUCT columns - # into multiple rows/columns like the BQ UI does. - if pandas.api.types.is_scalar(value) and pd.isna(value): - body_parts.append( - f' ' - '<NA>' - ) - else: - if isinstance(value, float): - cell_content = f"{value:.{precision}f}" - else: - cell_content = str(value) - body_parts.append( - f' ' - f"{html.escape(cell_content)}" - ) - - for col in left_columns: - render_col_cell(col) - - if show_ellipsis: - # Ellipsis cell - body_parts.append(' ...') - - for col in right_columns: - render_col_cell(col) - - body_parts.append(" ") - body_parts.append(" ") - return "\n".join(body_parts) - - -def _obj_ref_rt_to_html(obj_ref_rt: str) -> str: - obj_ref_rt_json = json.loads(obj_ref_rt) - obj_ref_details = obj_ref_rt_json["objectref"]["details"] - if "gcs_metadata" in obj_ref_details: - gcs_metadata = obj_ref_details["gcs_metadata"] - content_type = typing.cast(str, gcs_metadata.get("content_type", "")) - if content_type.startswith("image"): - size_str = "" - if options.display.blob_display_width: - size_str = f' width="{options.display.blob_display_width}"' - if options.display.blob_display_height: - size_str = size_str + f' height="{options.display.blob_display_height}"' - url = obj_ref_rt_json["access_urls"]["read_url"] - return f'' - - return f"uri: {obj_ref_rt_json['objectref']['uri']}, authorizer: {obj_ref_rt_json['objectref']['authorizer']}" - - -def create_html_representation( - obj: Union[bigframes.dataframe.DataFrame, bigframes.series.Series], - pandas_df: pd.DataFrame, - total_rows: int, - total_columns: int, -) -> str: - """Create an HTML representation of the DataFrame or Series.""" - import bigframes.series - - opts = options.display - with display_options.pandas_repr(opts): - if isinstance(obj, bigframes.series.Series): - pd_series = pandas_df.iloc[:, 0] - try: - html_string = pd_series._repr_html_() - except AttributeError: - html_string = f"
{pd_series.to_string()}
" - - is_truncated = total_rows is not None and total_rows > len(pandas_df) - if is_truncated: - html_string += f"

[{total_rows} rows]

" - return html_string - else: - # _repr_html_ stub is missing so mypy thinks it's a Series. Ignore mypy. - html_string = pandas_df._repr_html_() # type:ignore - - html_string += f"[{total_rows} rows x {total_columns} columns in total]" - return html_string - - -def _get_obj_metadata( - obj: Union[bigframes.dataframe.DataFrame, bigframes.series.Series], -) -> tuple[bool, bool]: - import bigframes.series - - is_series = isinstance(obj, bigframes.series.Series) - if is_series: - has_index = len(obj._block.index_columns) > 0 - else: - has_index = obj._has_index - return is_series, has_index - - -def get_anywidget_bundle( - obj: Union[bigframes.dataframe.DataFrame, bigframes.series.Series], - include=None, - exclude=None, - dry_run_info: str | None = None, -) -> tuple[dict[str, Any], dict[str, Any]]: - """ - Helper method to create and return the anywidget mimebundle. - This function encapsulates the logic for anywidget display. - """ - import bigframes.series - from bigframes import display - - if isinstance(obj, bigframes.series.Series): - df = obj.to_frame() - else: - df = obj - - from bigframes.session import deferred - - if ( - not isinstance(df, deferred.DeferredBigQueryDataFrame) - and bigframes.options.display.repr_mode != "deferred" - ): - display_df = df._prepare_display_df() - else: - display_df = df - - widget = display.TableWidget(display_df, dry_run_info=dry_run_info) - widget_repr_result = widget._repr_mimebundle_(include=include, exclude=exclude) - - if isinstance(widget_repr_result, tuple): - widget_repr, widget_metadata = widget_repr_result - else: - widget_repr = widget_repr_result - widget_metadata = {} - - widget_repr = dict(widget_repr) - - # Use cached data from widget to render HTML and plain text versions. - cached_pd = widget._cached_data - total_rows = widget.row_count - total_columns = len(df.columns) - - if dry_run_info: - widget_repr["text/plain"] = dry_run_info - else: - widget_repr["text/html"] = create_html_representation( - obj, - cached_pd, - total_rows, - total_columns, - ) - is_series, has_index = _get_obj_metadata(obj) - widget_repr["text/plain"] = plaintext.create_text_representation( - cached_pd, - total_rows, - is_series=is_series, - has_index=has_index, - column_count=len(df.columns) if not is_series else 0, - ) - - return widget_repr, widget_metadata - - -def repr_mimebundle_deferred( - obj: Union[bigframes.dataframe.DataFrame, bigframes.series.Series], -) -> dict[str, str]: - return { - "text/plain": formatter.repr_query_job(obj._compute_dry_run()), - "text/html": formatter.repr_query_job_html(obj._compute_dry_run()), - } - - -def repr_mimebundle_head( - obj: Union[bigframes.dataframe.DataFrame, bigframes.series.Series], -) -> dict[str, str]: - import bigframes.series - - opts = options.display - if isinstance(obj, bigframes.series.Series): - df = obj.to_frame() - else: - df = obj - - df = df._prepare_display_df() - pandas_df, row_count, query_job = df._block.retrieve_repr_request_results( - opts.max_rows - ) - - obj._set_internal_query_job(query_job) - column_count = len(pandas_df.columns) - - html_string = create_html_representation(obj, pandas_df, row_count, column_count) - - is_series, has_index = _get_obj_metadata(obj) - text_representation = plaintext.create_text_representation( - pandas_df, - row_count, - is_series=is_series, - has_index=has_index, - column_count=len(pandas_df.columns) if not is_series else 0, - ) - - return {"text/html": html_string, "text/plain": text_representation} - - -def repr_mimebundle( - obj: Union[bigframes.dataframe.DataFrame, bigframes.series.Series], - include=None, - exclude=None, -): - """Custom display method for IPython/Jupyter environments.""" - # TODO(b/467647693): Anywidget integration has been tested in Jupyter, VS Code, and - # BQ Studio, but there is a known compatibility issue with Marimo that needs to be addressed. - - opts = options.display - if ( - opts.render_mode == "anywidget" - or opts.repr_mode == "anywidget" - or opts.repr_mode == "deferred" - ): - try: - with bigframes.option_context("display.progress_bar", None): - with warnings.catch_warnings(): - warnings.simplefilter( - "ignore", category=bigframes.exceptions.JSONDtypeWarning - ) - warnings.simplefilter("ignore", category=FutureWarning) - dry_run_info = None - if opts.repr_mode == "deferred": - dry_run_job = obj._compute_dry_run() - dry_run_info = formatter.repr_query_job(dry_run_job) - return get_anywidget_bundle( - obj, - include=include, - exclude=exclude, - dry_run_info=dry_run_info, - ) - except Exception: - # Anywidget is an optional dependency, so warn rather than fail. - # TODO(shuowei): When Anywidget becomes the default for all repr modes, - # remove this warning. - warnings.warn( - "Anywidget mode is not available or failed to load. " - "Please `pip install anywidget traitlets` or " - "`pip install 'bigframes[anywidget]'` to use interactive tables. " - f"Falling back to static HTML. Error: {traceback.format_exc()}" - ) - if opts.repr_mode == "deferred": - return repr_mimebundle_deferred(obj) - - bundle = repr_mimebundle_head(obj) - if opts.render_mode == "plaintext": - bundle.pop("text/html", None) - - return bundle diff --git a/bigframes/display/plaintext.py b/bigframes/display/plaintext.py deleted file mode 100644 index 2f7bc1df07f..00000000000 --- a/bigframes/display/plaintext.py +++ /dev/null @@ -1,102 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -"""Plaintext display representations.""" - -from __future__ import annotations - -import typing - -import pandas -import pandas.io.formats - -from bigframes._config import display_options, options - -if typing.TYPE_CHECKING: - import pandas as pd - - -def create_text_representation( - pandas_df: pd.DataFrame, - total_rows: typing.Optional[int], - is_series: bool, - has_index: bool = True, - column_count: int = 0, -) -> str: - """Create a text representation of the DataFrame or Series. - - Args: - pandas_df: - The pandas DataFrame containing the data to represent. - total_rows: - The total number of rows in the original BigFrames object. - is_series: - Whether the object being represented is a Series. - has_index: - Whether the object has an index to display. - column_count: - The total number of columns in the original BigFrames object. - Only used for DataFrames. - - Returns: - A plaintext string representation. - """ - opts = options.display - - if is_series: - with display_options.pandas_repr(opts): - pd_series = pandas_df.iloc[:, 0] - if not has_index: - repr_string = pd_series.to_string( - length=False, index=False, name=True, dtype=True - ) - else: - repr_string = pd_series.to_string(length=False, name=True, dtype=True) - - lines = repr_string.split("\n") - is_truncated = total_rows is not None and total_rows > len(pandas_df) - - if is_truncated: - lines.append("...") - lines.append("") # Add empty line for spacing only if truncated - lines.append(f"[{total_rows} rows]") - - return "\n".join(lines) - - else: - # DataFrame - with display_options.pandas_repr(opts): - # safe to mutate this, this dict is owned by this code, and does not affect global config - to_string_kwargs = ( - pandas.io.formats.format.get_dataframe_repr_params() # type: ignore - ) - if not has_index: - to_string_kwargs.update({"index": False}) - - # We add our own dimensions string, so don't want pandas to. - to_string_kwargs.update({"show_dimensions": False}) - repr_string = pandas_df.to_string(**to_string_kwargs) - - lines = repr_string.split("\n") - is_truncated = total_rows is not None and total_rows > len(pandas_df) - - if is_truncated: - lines.append("...") - lines.append("") # Add empty line for spacing only if truncated - lines.append(f"[{total_rows or '?'} rows x {column_count} columns]") - else: - # For non-truncated DataFrames, we still need to add dimensions if show_dimensions was False - lines.append("") - lines.append(f"[{total_rows or '?'} rows x {column_count} columns]") - return "\n".join(lines) diff --git a/bigframes/display/table_widget.css b/bigframes/display/table_widget.css deleted file mode 100644 index da0a701d694..00000000000 --- a/bigframes/display/table_widget.css +++ /dev/null @@ -1,247 +0,0 @@ -/** - * Copyright 2025 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/* Increase specificity to override framework styles without !important */ -.bigframes-widget.bigframes-widget { - /* Default Light Mode Variables */ - --bf-bg: white; - --bf-border-color: #ccc; - --bf-error-bg: #fbe; - --bf-error-border: red; - --bf-error-fg: black; - --bf-fg: black; - --bf-header-bg: #f5f5f5; - --bf-null-fg: gray; - --bf-row-even-bg: #f5f5f5; - --bf-row-odd-bg: white; - - background-color: var(--bf-bg); - box-sizing: border-box; - color: var(--bf-fg); - display: flex; - flex-direction: column; - font-family: - '-apple-system', 'BlinkMacSystemFont', 'Segoe UI', 'Roboto', sans-serif; - margin: 0; - padding: 0; -} - -.bigframes-widget * { - box-sizing: border-box; -} - -/* Dark Mode Overrides: - * 1. @media (prefers-color-scheme: dark) - System-wide dark mode - * 2. .bigframes-dark-mode - Explicit class for VSCode theme detection - * 3. html[theme="dark"], body[data-theme="dark"] - Colab/Pantheon manual override - */ -@media (prefers-color-scheme: dark) { - .bigframes-widget.bigframes-widget { - --bf-bg: var(--vscode-editor-background, #202124); - --bf-border-color: #444; - --bf-error-bg: #511; - --bf-error-border: #f88; - --bf-error-fg: #fcc; - --bf-fg: white; - --bf-header-bg: var(--vscode-editor-background, black); - --bf-null-fg: #aaa; - --bf-row-even-bg: #202124; - --bf-row-odd-bg: #383838; - } -} - -.bigframes-widget.bigframes-dark-mode.bigframes-dark-mode, -html[theme='dark'] .bigframes-widget.bigframes-widget, -body[data-theme='dark'] .bigframes-widget.bigframes-widget { - --bf-bg: var(--vscode-editor-background, #202124); - --bf-border-color: #444; - --bf-error-bg: #511; - --bf-error-border: #f88; - --bf-error-fg: #fcc; - --bf-fg: white; - --bf-header-bg: var(--vscode-editor-background, black); - --bf-null-fg: #aaa; - --bf-row-even-bg: #202124; - --bf-row-odd-bg: #383838; -} - -.bigframes-widget .table-container { - background-color: var(--bf-bg); - margin: 0; - max-height: 620px; - overflow: auto; - padding: 0; -} - -.bigframes-widget .footer { - align-items: center; - background-color: var(--bf-bg); - color: var(--bf-fg); - display: flex; - font-size: 0.8rem; - justify-content: space-between; - padding: 8px; -} - -.bigframes-widget .footer > * { - flex: 1; -} - -.bigframes-widget .pagination { - align-items: center; - display: flex; - flex-direction: row; - gap: 4px; - justify-content: center; - padding: 4px; -} - -.bigframes-widget .page-indicator { - margin: 0 8px; -} - -.bigframes-widget .row-count { - margin: 0 8px; -} - -.bigframes-widget .settings { - align-items: center; - display: flex; - flex-direction: row; - gap: 16px; - justify-content: end; -} - -.bigframes-widget .page-size, -.bigframes-widget .max-columns { - align-items: center; - display: flex; - flex-direction: row; - gap: 4px; -} - -.bigframes-widget .page-size label, -.bigframes-widget .max-columns label { - margin-right: 8px; -} - -.bigframes-widget table.bigframes-widget-table, -.bigframes-widget table.dataframe { - background-color: var(--bf-bg); - border: 1px solid var(--bf-border-color); - border-collapse: collapse; - border-spacing: 0; - box-shadow: none; - color: var(--bf-fg); - margin: 0; - outline: none; - text-align: left; - width: auto; /* Fix stretching */ -} - -.bigframes-widget tr { - border: none; -} - -.bigframes-widget th { - background-color: var(--bf-header-bg); - border: 1px solid var(--bf-border-color); - color: var(--bf-fg); - padding: 0; - position: sticky; - text-align: left; - top: 0; - z-index: 1; -} - -.bigframes-widget td { - border: 1px solid var(--bf-border-color); - color: var(--bf-fg); - padding: 0.5em; -} - -.bigframes-widget table tbody tr:nth-child(odd), -.bigframes-widget table tbody tr:nth-child(odd) td { - background-color: var(--bf-row-odd-bg); -} - -.bigframes-widget table tbody tr:nth-child(even), -.bigframes-widget table tbody tr:nth-child(even) td { - background-color: var(--bf-row-even-bg); -} - -.bigframes-widget .bf-header-content { - box-sizing: border-box; - height: 100%; - overflow: auto; - padding: 0.5em; - resize: horizontal; - width: 100%; -} - -.bigframes-widget th .sort-indicator { - padding-left: 4px; - visibility: hidden; -} - -.bigframes-widget th:hover .sort-indicator { - visibility: visible; -} - -.bigframes-widget button { - background-color: transparent; - border: 1px solid currentColor; - border-radius: 4px; - color: inherit; - cursor: pointer; - display: inline-block; - padding: 2px 8px; - text-align: center; - text-decoration: none; - user-select: none; - vertical-align: middle; -} - -.bigframes-widget button:disabled { - opacity: 0.65; - pointer-events: none; -} - -.bigframes-widget .bigframes-error-message { - background-color: var(--bf-error-bg); - border: 1px solid var(--bf-error-border); - border-radius: 4px; - color: var(--bf-error-fg); - font-size: 14px; - margin-bottom: 8px; - padding: 8px; -} - -.bigframes-widget .cell-align-right { - text-align: right; -} - -.bigframes-widget .cell-align-left { - text-align: left; -} - -.bigframes-widget .null-value { - color: var(--bf-null-fg); -} - -.bigframes-widget .debug-info { - border-top: 1px solid var(--bf-border-color); -} diff --git a/bigframes/display/table_widget.js b/bigframes/display/table_widget.js deleted file mode 100644 index 314bf771d0e..00000000000 --- a/bigframes/display/table_widget.js +++ /dev/null @@ -1,350 +0,0 @@ -/* - * Copyright 2025 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -const ModelProperty = { - ERROR_MESSAGE: 'error_message', - ORDERABLE_COLUMNS: 'orderable_columns', - PAGE: 'page', - PAGE_SIZE: 'page_size', - ROW_COUNT: 'row_count', - SORT_CONTEXT: 'sort_context', - TABLE_HTML: 'table_html', - MAX_COLUMNS: 'max_columns', -}; - -const Event = { - CHANGE: 'change', - CHANGE_TABLE_HTML: 'change:table_html', - CLICK: 'click', -}; - -/** - * Renders the interactive table widget. - * @param {{ model: any, el: !HTMLElement }} props - The widget properties. - */ -function render({ model, el }) { - el.classList.add('bigframes-widget'); - - const errorContainer = document.createElement('div'); - errorContainer.classList.add('error-message'); - - const tableContainer = document.createElement('div'); - tableContainer.classList.add('table-container'); - const footer = document.createElement('footer'); - footer.classList.add('footer'); - - /** Detects theme and applies necessary style overrides. */ - function updateTheme() { - const body = document.body; - const isDark = - body.classList.contains('vscode-dark') || - body.classList.contains('theme-dark') || - body.dataset.theme === 'dark' || - body.getAttribute('data-vscode-theme-kind') === 'vscode-dark'; - - if (isDark) { - el.classList.add('bigframes-dark-mode'); - } else { - el.classList.remove('bigframes-dark-mode'); - } - } - - updateTheme(); - // Re-check after mount to ensure parent styling is applied. - setTimeout(updateTheme, 300); - - const observer = new MutationObserver(updateTheme); - observer.observe(document.body, { - attributes: true, - attributeFilter: ['class', 'data-theme', 'data-vscode-theme-kind'], - }); - - // Settings controls container - const settingsContainer = document.createElement('div'); - settingsContainer.classList.add('settings'); - - // Pagination controls - const paginationContainer = document.createElement('div'); - paginationContainer.classList.add('pagination'); - const prevPage = document.createElement('button'); - const pageIndicator = document.createElement('span'); - pageIndicator.classList.add('page-indicator'); - const nextPage = document.createElement('button'); - const rowCountLabel = document.createElement('span'); - rowCountLabel.classList.add('row-count'); - - // Page size controls - const pageSizeContainer = document.createElement('div'); - pageSizeContainer.classList.add('page-size'); - const pageSizeLabel = document.createElement('label'); - const pageSizeInput = document.createElement('select'); - - prevPage.textContent = '<'; - nextPage.textContent = '>'; - pageSizeLabel.textContent = 'Page size:'; - - const pageSizes = [10, 25, 50, 100]; - for (const size of pageSizes) { - const option = document.createElement('option'); - option.value = size; - option.textContent = size; - if (size === model.get(ModelProperty.PAGE_SIZE)) { - option.selected = true; - } - pageSizeInput.appendChild(option); - } - - // Max columns controls - const maxColumnsContainer = document.createElement('div'); - maxColumnsContainer.classList.add('max-columns'); - const maxColumnsLabel = document.createElement('label'); - const maxColumnsInput = document.createElement('select'); - - maxColumnsLabel.textContent = 'Max columns:'; - - // 0 represents "All" (all columns) - const maxColumnOptions = [5, 10, 15, 20, 0]; - for (const cols of maxColumnOptions) { - const option = document.createElement('option'); - option.value = cols; - option.textContent = cols === 0 ? 'All' : cols; - - const currentMax = model.get(ModelProperty.MAX_COLUMNS); - // Handle None/null from python as 0/All - const currentMaxVal = - currentMax === null || currentMax === undefined ? 0 : currentMax; - - if (cols === currentMaxVal) { - option.selected = true; - } - maxColumnsInput.appendChild(option); - } - - function updateButtonStates() { - const currentPage = model.get(ModelProperty.PAGE); - const pageSize = model.get(ModelProperty.PAGE_SIZE); - const rowCount = model.get(ModelProperty.ROW_COUNT); - - if (rowCount === null) { - rowCountLabel.textContent = 'Total rows unknown'; - pageIndicator.textContent = `Page ${(currentPage + 1).toLocaleString()} of many`; - prevPage.disabled = currentPage === 0; - nextPage.disabled = false; - } else if (rowCount === 0) { - rowCountLabel.textContent = '0 total rows'; - pageIndicator.textContent = 'Page 1 of 1'; - prevPage.disabled = true; - nextPage.disabled = true; - } else { - const totalPages = Math.ceil(rowCount / pageSize); - rowCountLabel.textContent = `${rowCount.toLocaleString()} total rows`; - pageIndicator.textContent = `Page ${(currentPage + 1).toLocaleString()} of ${totalPages.toLocaleString()}`; - prevPage.disabled = currentPage === 0; - nextPage.disabled = currentPage >= totalPages - 1; - } - pageSizeInput.value = pageSize; - } - - function handlePageChange(direction) { - const currentPage = model.get(ModelProperty.PAGE); - model.set(ModelProperty.PAGE, currentPage + direction); - model.save_changes(); - } - - function handlePageSizeChange(newSize) { - model.set(ModelProperty.PAGE_SIZE, newSize); - model.set(ModelProperty.PAGE, 0); - model.save_changes(); - } - - let isHeightInitialized = false; - - function handleTableHTMLChange() { - tableContainer.innerHTML = model.get(ModelProperty.TABLE_HTML); - - // After the first render, dynamically set the container height to fit the - // initial page (usually 10 rows) and then lock it. - setTimeout(() => { - if (!isHeightInitialized) { - const table = tableContainer.querySelector('table'); - if (table) { - const tableHeight = table.offsetHeight; - // Add a small buffer(e.g. 2px) for borders to avoid scrollbars. - if (tableHeight > 0) { - tableContainer.style.height = `${tableHeight + 2}px`; - isHeightInitialized = true; - } - } - } - }, 0); - - const sortableColumns = model.get(ModelProperty.ORDERABLE_COLUMNS); - const currentSortContext = model.get(ModelProperty.SORT_CONTEXT) || []; - - const getSortIndex = (colName) => - currentSortContext.findIndex((item) => item.column === colName); - - const headers = tableContainer.querySelectorAll('th'); - headers.forEach((header) => { - const headerDiv = header.querySelector('div'); - const columnName = headerDiv.textContent.trim(); - - if (columnName && sortableColumns.includes(columnName)) { - header.style.cursor = 'pointer'; - - const indicatorSpan = document.createElement('span'); - indicatorSpan.classList.add('sort-indicator'); - indicatorSpan.style.paddingLeft = '5px'; - - // Determine sort indicator and initial visibility - let indicator = '●'; // Default: unsorted (dot) - const sortIndex = getSortIndex(columnName); - - if (sortIndex !== -1) { - const isAscending = currentSortContext[sortIndex].ascending; - indicator = isAscending ? '▲' : '▼'; - indicatorSpan.style.visibility = 'visible'; // Sorted arrows always visible - } else { - indicatorSpan.style.visibility = 'hidden'; - } - indicatorSpan.textContent = indicator; - - const existingIndicator = headerDiv.querySelector('.sort-indicator'); - if (existingIndicator) { - headerDiv.removeChild(existingIndicator); - } - headerDiv.appendChild(indicatorSpan); - - header.addEventListener('mouseover', () => { - if (getSortIndex(columnName) === -1) { - indicatorSpan.style.visibility = 'visible'; - } - }); - header.addEventListener('mouseout', () => { - if (getSortIndex(columnName) === -1) { - indicatorSpan.style.visibility = 'hidden'; - } - }); - - // Add click handler for three-state toggle - header.addEventListener(Event.CLICK, (event) => { - const sortIndex = getSortIndex(columnName); - let newContext = [...currentSortContext]; - - if (event.shiftKey) { - if (sortIndex !== -1) { - // Already sorted. Toggle or Remove. - if (newContext[sortIndex].ascending) { - // Asc -> Desc - // Clone object to avoid mutation issues - newContext[sortIndex] = { - ...newContext[sortIndex], - ascending: false, - }; - } else { - // Desc -> Remove - newContext.splice(sortIndex, 1); - } - } else { - // Not sorted -> Append Asc - newContext.push({ column: columnName, ascending: true }); - } - } else { - // No shift key. Single column mode. - if (sortIndex !== -1 && newContext.length === 1) { - // Already only this column. Toggle or Remove. - if (newContext[sortIndex].ascending) { - newContext[sortIndex] = { - ...newContext[sortIndex], - ascending: false, - }; - } else { - newContext = []; - } - } else { - // Start fresh with this column - newContext = [{ column: columnName, ascending: true }]; - } - } - - model.set(ModelProperty.SORT_CONTEXT, newContext); - model.save_changes(); - }); - } - }); - - updateButtonStates(); - } - - function handleErrorMessageChange() { - const errorMsg = model.get(ModelProperty.ERROR_MESSAGE); - if (errorMsg) { - errorContainer.textContent = errorMsg; - errorContainer.style.display = 'block'; - } else { - errorContainer.style.display = 'none'; - } - } - - prevPage.addEventListener(Event.CLICK, () => handlePageChange(-1)); - nextPage.addEventListener(Event.CLICK, () => handlePageChange(1)); - pageSizeInput.addEventListener(Event.CHANGE, (e) => { - const newSize = Number(e.target.value); - if (newSize) { - handlePageSizeChange(newSize); - } - }); - - maxColumnsInput.addEventListener(Event.CHANGE, (e) => { - const newVal = Number(e.target.value); - model.set(ModelProperty.MAX_COLUMNS, newVal); - model.save_changes(); - }); - - model.on(Event.CHANGE_TABLE_HTML, handleTableHTMLChange); - model.on(`change:${ModelProperty.ROW_COUNT}`, updateButtonStates); - model.on(`change:${ModelProperty.ERROR_MESSAGE}`, handleErrorMessageChange); - model.on(`change:_initial_load_complete`, (val) => { - if (val) updateButtonStates(); - }); - model.on(`change:${ModelProperty.PAGE}`, updateButtonStates); - - paginationContainer.appendChild(prevPage); - paginationContainer.appendChild(pageIndicator); - paginationContainer.appendChild(nextPage); - - pageSizeContainer.appendChild(pageSizeLabel); - pageSizeContainer.appendChild(pageSizeInput); - - maxColumnsContainer.appendChild(maxColumnsLabel); - maxColumnsContainer.appendChild(maxColumnsInput); - - settingsContainer.appendChild(maxColumnsContainer); - settingsContainer.appendChild(pageSizeContainer); - - footer.appendChild(rowCountLabel); - footer.appendChild(paginationContainer); - footer.appendChild(settingsContainer); - - el.appendChild(errorContainer); - el.appendChild(tableContainer); - el.appendChild(footer); - - handleTableHTMLChange(); - handleErrorMessageChange(); -} - -export default { render }; diff --git a/bigframes/display/table_widget_angular.js b/bigframes/display/table_widget_angular.js deleted file mode 100644 index ad1697def54..00000000000 --- a/bigframes/display/table_widget_angular.js +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright 2026 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. - */ - -var Ba=Object.defineProperty,qa=Object.defineProperties,Ua=Object.getOwnPropertyDescriptors,Hi=Object.getOwnPropertySymbols,Za=Object.prototype.hasOwnProperty,$a=Object.prototype.propertyIsEnumerable,zi=(e,t,n)=>t in e?Ba(e,t,{enumerable:!0,configurable:!0,writable:!0,value:n}):e[t]=n,$=(e,t)=>{for(var n in t||={})Za.call(t,n)&&zi(e,n,t[n]);if(Hi)for(var n of Hi(t))$a.call(t,n)&&zi(e,n,t[n]);return e},Q=(e,t)=>qa(e,Ua(t)),R=null,Ft=!1,Wr=1,Qa=null,ne=Symbol("SIGNAL");function m(e){let t=R;return R=e,t}function Wa(){return R}var St={version:0,lastCleanEpoch:0,dirty:!1,producers:void 0,producersTail:void 0,consumers:void 0,consumersTail:void 0,recomputing:!1,consumerAllowSignalWrites:!1,consumerIsAlwaysLive:!1,kind:"unknown",producerMustRecompute:()=>!1,producerRecomputeValue:()=>{},consumerMarkedDirty:()=>{},consumerOnSignalRead:()=>{}};function Wo(e){if(Ft)throw new Error("");if(R===null)return;R.consumerOnSignalRead(e);let t=R.producersTail;if(t!==void 0&&t.producer===e)return;let n,r=R.recomputing;if(r&&(n=t!==void 0?t.nextProducer:R.producers,n!==void 0&&n.producer===e)){R.producersTail=n,n.lastReadVersion=e.version;return}let i=e.consumersTail;if(i!==void 0&&i.consumer===R&&(!r||Ja(i,R)))return;let o=tt(R),s={producer:e,consumer:R,nextProducer:n,prevConsumer:i,lastReadVersion:e.version,nextConsumer:void 0};R.producersTail=s,t!==void 0?t.nextProducer=s:R.producers=s,o&&Xo(e,s)}function Ga(){Wr++}function Go(e){if(!(tt(e)&&!e.dirty)&&!(!e.dirty&&e.lastCleanEpoch===Wr)){if(!e.producerMustRecompute(e)&&!Yr(e)){Bi(e);return}e.producerRecomputeValue(e),Bi(e)}}function Yo(e){if(e.consumers===void 0)return;let t=Ft;Ft=!0;try{for(let n=e.consumers;n!==void 0;n=n.nextConsumer){let r=n.consumer;r.dirty||Ya(r)}}finally{Ft=t}}function Ko(){return R?.consumerAllowSignalWrites!==!1}function Ya(e){e.dirty=!0,Yo(e),e.consumerMarkedDirty?.(e)}function Bi(e){e.dirty=!1,e.lastCleanEpoch=Wr}function Gt(e){return e&&Ka(e),m(e)}function Ka(e){e.producersTail=void 0,e.recomputing=!0}function Gr(e,t){m(t),e&&Xa(e)}function Xa(e){e.recomputing=!1;let t=e.producersTail,n=t!==void 0?t.nextProducer:e.producers;if(n!==void 0){if(tt(e))do n=Kr(n);while(n!==void 0);t!==void 0?t.nextProducer=void 0:e.producers=void 0}}function Yr(e){for(let t=e.producers;t!==void 0;t=t.nextProducer){let n=t.producer,r=t.lastReadVersion;if(r!==n.version||(Go(n),r!==n.version))return!0}return!1}function yn(e){if(tt(e)){let t=e.producers;for(;t!==void 0;)t=Kr(t)}e.producers=void 0,e.producersTail=void 0,e.consumers=void 0,e.consumersTail=void 0}function Xo(e,t){let n=e.consumersTail,r=tt(e);if(n!==void 0?(t.nextConsumer=n.nextConsumer,n.nextConsumer=t):(t.nextConsumer=void 0,e.consumers=t),t.prevConsumer=n,e.consumersTail=t,!r)for(let i=e.producers;i!==void 0;i=i.nextProducer)Xo(i.producer,i)}function Kr(e){let t=e.producer,n=e.nextProducer,r=e.nextConsumer,i=e.prevConsumer;if(e.nextConsumer=void 0,e.prevConsumer=void 0,r!==void 0?r.prevConsumer=i:t.consumersTail=i,i!==void 0)i.nextConsumer=r;else if(t.consumers=r,!tt(t)){let o=t.producers;for(;o!==void 0;)o=Kr(o)}return n}function tt(e){return e.consumerIsAlwaysLive||e.consumers!==void 0}function Jo(e){Qa?.(e)}function Ja(e,t){let n=t.producersTail;if(n!==void 0){let r=t.producers;do{if(r===e)return!0;if(r===n)break;r=r.nextProducer}while(r!==void 0)}return!1}function es(e,t){return Object.is(e,t)}function eu(e,t){let n=Object.create(tu);n.computation=e,t!==void 0&&(n.equal=t);let r=()=>{if(Go(n),Wo(n),n.value===Ht)throw n.error;return n.value};return r[ne]=n,Jo(n),r}var Ln=Symbol("UNSET"),jn=Symbol("COMPUTING"),Ht=Symbol("ERRORED"),tu=Q($({},St),{value:Ln,dirty:!0,error:null,equal:es,kind:"computed",producerMustRecompute(e){return e.value===Ln||e.value===jn},producerRecomputeValue(e){if(e.value===jn)throw new Error("");let t=e.value;e.value=jn;let n=Gt(e),r,i=!1;try{r=e.computation(),m(null),i=t!==Ln&&t!==Ht&&r!==Ht&&e.equal(t,r)}catch(o){r=Ht,e.error=o}finally{Gr(e,n)}if(i){e.value=t;return}e.value=r,e.version++}});function nu(){throw new Error}var ts=nu;function ns(e){ts(e)}function ru(e){ts=e}var iu=null;function ou(e,t){let n=Object.create(au);n.value=e,t!==void 0&&(n.equal=t);let r=()=>su(n);return r[ne]=n,Jo(n),[r,i=>rs(n,i),i=>lu(n,i)]}function su(e){return Wo(e),e.value}function rs(e,t){Ko()||ns(e),e.equal(e.value,t)||(e.value=t,uu(e))}function lu(e,t){Ko()||ns(e),rs(e,t(e.value))}var au=Q($({},St),{equal:es,value:void 0,kind:"signal"});function uu(e){e.version++,Ga(),Yo(e),iu?.(e)}var cu=Q($({},St),{consumerIsAlwaysLive:!0,consumerAllowSignalWrites:!0,dirty:!0,kind:"effect"});function du(e){if(e.dirty=!1,e.version>0&&!Yr(e))return;e.version++;let t=Gt(e);try{e.cleanup(),e.fn()}finally{Gr(e,t)}}function Y(e){return typeof e=="function"}function is(e){let t=e(n=>{Error.call(n),n.stack=new Error().stack});return t.prototype=Object.create(Error.prototype),t.prototype.constructor=t,t}var Fn=is(e=>function(t){e(this),this.message=t?`${t.length} errors occurred during unsubscription: -${t.map((n,r)=>`${r+1}) ${n.toString()}`).join(` - `)}`:"",this.name="UnsubscriptionError",this.errors=t});function or(e,t){if(e){let n=e.indexOf(t);0<=n&&e.splice(n,1)}}var me=class sr{constructor(t){this.initialTeardown=t,this.closed=!1,this._parentage=null,this._finalizers=null}unsubscribe(){let t;if(!this.closed){this.closed=!0;let{_parentage:n}=this;if(n)if(this._parentage=null,Array.isArray(n))for(let o of n)o.remove(this);else n.remove(this);let{initialTeardown:r}=this;if(Y(r))try{r()}catch(o){t=o instanceof Fn?o.errors:[o]}let{_finalizers:i}=this;if(i){this._finalizers=null;for(let o of i)try{qi(o)}catch(s){t=t??[],s instanceof Fn?t=[...t,...s.errors]:t.push(s)}}if(t)throw new Fn(t)}}add(t){var n;if(t&&t!==this)if(this.closed)qi(t);else{if(t instanceof sr){if(t.closed||t._hasParent(this))return;t._addParent(this)}(this._finalizers=(n=this._finalizers)!==null&&n!==void 0?n:[]).push(t)}}_hasParent(t){let{_parentage:n}=this;return n===t||Array.isArray(n)&&n.includes(t)}_addParent(t){let{_parentage:n}=this;this._parentage=Array.isArray(n)?(n.push(t),n):n?[n,t]:t}_removeParent(t){let{_parentage:n}=this;n===t?this._parentage=null:Array.isArray(n)&&or(n,t)}remove(t){let{_finalizers:n}=this;n&&or(n,t),t instanceof sr&&t._removeParent(this)}};me.EMPTY=(()=>{let e=new me;return e.closed=!0,e})();var os=me.EMPTY;function ss(e){return e instanceof me||e&&"closed"in e&&Y(e.remove)&&Y(e.add)&&Y(e.unsubscribe)}function qi(e){Y(e)?e():e.unsubscribe()}var je={onUnhandledError:null,onStoppedNotification:null,Promise:void 0,useDeprecatedSynchronousErrorHandling:!1,useDeprecatedNextContext:!1},Yt={setTimeout(e,t,...n){let{delegate:r}=Yt;return r?.setTimeout?r.setTimeout(e,t,...n):setTimeout(e,t,...n)},clearTimeout(e){let{delegate:t}=Yt;return(t?.clearTimeout||clearTimeout)(e)},delegate:void 0};function fu(e){Yt.setTimeout(()=>{let{onUnhandledError:t}=je;if(t)t(e);else throw e})}function Ui(){}var hu=Xr("C",void 0,void 0);function pu(e){return Xr("E",void 0,e)}function gu(e){return Xr("N",e,void 0)}function Xr(e,t,n){return{kind:e,value:t,error:n}}var ke=null;function zt(e){if(je.useDeprecatedSynchronousErrorHandling){let t=!ke;if(t&&(ke={errorThrown:!1,error:null}),e(),t){let{errorThrown:n,error:r}=ke;if(ke=null,n)throw r}}else e()}function mu(e){je.useDeprecatedSynchronousErrorHandling&&ke&&(ke.errorThrown=!0,ke.error=e)}var Jr=class extends me{constructor(e){super(),this.isStopped=!1,e?(this.destination=e,ss(e)&&e.add(this)):this.destination=wu}static create(e,t,n){return new lr(e,t,n)}next(e){this.isStopped?zn(gu(e),this):this._next(e)}error(e){this.isStopped?zn(pu(e),this):(this.isStopped=!0,this._error(e))}complete(){this.isStopped?zn(hu,this):(this.isStopped=!0,this._complete())}unsubscribe(){this.closed||(this.isStopped=!0,super.unsubscribe(),this.destination=null)}_next(e){this.destination.next(e)}_error(e){try{this.destination.error(e)}finally{this.unsubscribe()}}_complete(){try{this.destination.complete()}finally{this.unsubscribe()}}},vu=Function.prototype.bind;function Hn(e,t){return vu.call(e,t)}var yu=class{constructor(e){this.partialObserver=e}next(e){let{partialObserver:t}=this;if(t.next)try{t.next(e)}catch(n){At(n)}}error(e){let{partialObserver:t}=this;if(t.error)try{t.error(e)}catch(n){At(n)}else At(e)}complete(){let{partialObserver:e}=this;if(e.complete)try{e.complete()}catch(t){At(t)}}},lr=class extends Jr{constructor(e,t,n){super();let r;if(Y(e)||!e)r={next:e??void 0,error:t??void 0,complete:n??void 0};else{let i;this&&je.useDeprecatedNextContext?(i=Object.create(e),i.unsubscribe=()=>this.unsubscribe(),r={next:e.next&&Hn(e.next,i),error:e.error&&Hn(e.error,i),complete:e.complete&&Hn(e.complete,i)}):r=e}this.destination=new yu(r)}};function At(e){je.useDeprecatedSynchronousErrorHandling?mu(e):fu(e)}function bu(e){throw e}function zn(e,t){let{onStoppedNotification:n}=je;n&&Yt.setTimeout(()=>n(e,t))}var wu={closed:!0,next:Ui,error:bu,complete:Ui},_u=typeof Symbol=="function"&&Symbol.observable||"@@observable";function Cu(e){return e}function xu(e){return e.length===0?Cu:e.length===1?e[0]:function(t){return e.reduce((n,r)=>r(n),t)}}var ar=(()=>{class e{constructor(n){n&&(this._subscribe=n)}lift(n){let r=new e;return r.source=this,r.operator=n,r}subscribe(n,r,i){let o=Su(n)?n:new lr(n,r,i);return zt(()=>{let{operator:s,source:l}=this;o.add(s?s.call(o,l):l?this._subscribe(o):this._trySubscribe(o))}),o}_trySubscribe(n){try{return this._subscribe(n)}catch(r){n.error(r)}}forEach(n,r){return r=Zi(r),new r((i,o)=>{let s=new lr({next:l=>{try{n(l)}catch(a){o(a),s.unsubscribe()}},error:o,complete:i});this.subscribe(s)})}_subscribe(n){var r;return(r=this.source)===null||r===void 0?void 0:r.subscribe(n)}[_u](){return this}pipe(...n){return xu(n)(this)}toPromise(n){return n=Zi(n),new n((r,i)=>{let o;this.subscribe(s=>o=s,s=>i(s),()=>r(o))})}}return e.create=t=>new e(t),e})();function Zi(e){var t;return(t=e??je.Promise)!==null&&t!==void 0?t:Promise}function ku(e){return e&&Y(e.next)&&Y(e.error)&&Y(e.complete)}function Su(e){return e&&e instanceof Jr||ku(e)&&ss(e)}function Eu(e){return Y(e?.lift)}function Iu(e){return t=>{if(Eu(t))return t.lift(function(n){try{return e(n,this)}catch(r){this.error(r)}});throw new TypeError("Unable to lift unknown Observable type")}}function Tu(e,t,n,r,i){return new Ou(e,t,n,r,i)}var Ou=class extends Jr{constructor(e,t,n,r,i,o){super(e),this.onFinalize=i,this.shouldUnsubscribe=o,this._next=t?function(s){try{t(s)}catch(l){e.error(l)}}:super._next,this._error=r?function(s){try{r(s)}catch(l){e.error(l)}finally{this.unsubscribe()}}:super._error,this._complete=n?function(){try{n()}catch(s){e.error(s)}finally{this.unsubscribe()}}:super._complete}unsubscribe(){var e;if(!this.shouldUnsubscribe||this.shouldUnsubscribe()){let{closed:t}=this;super.unsubscribe(),!t&&((e=this.onFinalize)===null||e===void 0||e.call(this))}}},Du=is(e=>function(){e(this),this.name="ObjectUnsubscribedError",this.message="object unsubscribed"}),Et=(()=>{class e extends ar{constructor(){super(),this.closed=!1,this.currentObservers=null,this.observers=[],this.isStopped=!1,this.hasError=!1,this.thrownError=null}lift(n){let r=new $i(this,this);return r.operator=n,r}_throwIfClosed(){if(this.closed)throw new Du}next(n){zt(()=>{if(this._throwIfClosed(),!this.isStopped){this.currentObservers||(this.currentObservers=Array.from(this.observers));for(let r of this.currentObservers)r.next(n)}})}error(n){zt(()=>{if(this._throwIfClosed(),!this.isStopped){this.hasError=this.isStopped=!0,this.thrownError=n;let{observers:r}=this;for(;r.length;)r.shift().error(n)}})}complete(){zt(()=>{if(this._throwIfClosed(),!this.isStopped){this.isStopped=!0;let{observers:n}=this;for(;n.length;)n.shift().complete()}})}unsubscribe(){this.isStopped=this.closed=!0,this.observers=this.currentObservers=null}get observed(){var n;return((n=this.observers)===null||n===void 0?void 0:n.length)>0}_trySubscribe(n){return this._throwIfClosed(),super._trySubscribe(n)}_subscribe(n){return this._throwIfClosed(),this._checkFinalizedStatuses(n),this._innerSubscribe(n)}_innerSubscribe(n){let{hasError:r,isStopped:i,observers:o}=this;return r||i?os:(this.currentObservers=null,o.push(n),new me(()=>{this.currentObservers=null,or(o,n)}))}_checkFinalizedStatuses(n){let{hasError:r,thrownError:i,isStopped:o}=this;r?n.error(i):o&&n.complete()}asObservable(){let n=new ar;return n.source=this,n}}return e.create=(t,n)=>new $i(t,n),e})(),$i=class extends Et{constructor(e,t){super(),this.destination=e,this.source=t}next(e){var t,n;(n=(t=this.destination)===null||t===void 0?void 0:t.next)===null||n===void 0||n.call(t,e)}error(e){var t,n;(n=(t=this.destination)===null||t===void 0?void 0:t.error)===null||n===void 0||n.call(t,e)}complete(){var e,t;(t=(e=this.destination)===null||e===void 0?void 0:e.complete)===null||t===void 0||t.call(e)}_subscribe(e){var t,n;return(n=(t=this.source)===null||t===void 0?void 0:t.subscribe(e))!==null&&n!==void 0?n:os}},Mu=class extends Et{constructor(e){super(),this._value=e}get value(){return this.getValue()}_subscribe(e){let t=super._subscribe(e);return!t.closed&&e.next(this._value),t}getValue(){let{hasError:e,thrownError:t,_value:n}=this;if(e)throw t;return this._throwIfClosed(),n}next(e){super.next(this._value=e)}};function Pu(e,t){return Iu((n,r)=>{let i=0;n.subscribe(Tu(r,o=>{r.next(e.call(t,o,i++))}))})}var ur;function ls(){return ur}function fe(e){let t=ur;return ur=e,t}var Nu=Symbol("NotFound");function ei(e){return e===Nu||e?.name==="\u0275NotFound"}var as="https://angular.dev/best-practices/security#preventing-cross-site-scripting-xss",w=class extends Error{code;constructor(e,t){super(Vu(e,t)),this.code=e}};function Au(e){return`NG0${Math.abs(e)}`}function Vu(e,t){return`${Au(e)}${t?": "+t:""}`}var Kt=globalThis;function k(e){for(let t in e)if(e[t]===k)return t;throw Error("")}function us(e){if(typeof e=="string")return e;if(Array.isArray(e))return`[${e.map(us).join(", ")}]`;if(e==null)return""+e;let t=e.overriddenName||e.name;if(t)return`${t}`;let n=e.toString();if(n==null)return""+n;let r=n.indexOf(` -`);return r>=0?n.slice(0,r):n}function Qi(e,t){return e?t?`${e} ${t}`:e:t||""}var Ru=k({__forward_ref__:k});function cs(e){return e.__forward_ref__=cs,e}function F(e){return Lu(e)?e():e}function Lu(e){return typeof e=="function"&&e.hasOwnProperty(Ru)&&e.__forward_ref__===cs}function D(e){return{token:e.token,providedIn:e.providedIn||null,factory:e.factory,value:void 0}}function ti(e){return ju(e,ds)}function ju(e,t){return e.hasOwnProperty(t)&&e[t]||null}function Fu(e){return(e?.[ds]??null)||null}function Wi(e){return e&&e.hasOwnProperty(Gi)?e[Gi]:null}var ds=k({\u0275prov:k}),Gi=k({\u0275inj:k}),E=class{_desc;ngMetadataName="InjectionToken";\u0275prov;constructor(e,t){this._desc=e,this.\u0275prov=void 0,typeof t=="number"?this.__NG_ELEMENT_ID__=t:t!==void 0&&(this.\u0275prov=D({token:this,providedIn:t.providedIn||"root",factory:t.factory}))}get multi(){return this}toString(){return`InjectionToken ${this._desc}`}};function fs(e){return e&&!!e.\u0275providers}var Hu=k({\u0275cmp:k}),zu=k({\u0275dir:k}),Bu=k({\u0275pipe:k}),Yi=k({\u0275fac:k}),ht=k({__NG_ELEMENT_ID__:k}),Ki=k({__NG_ENV_ID__:k});function gt(e){return ni(e,"@Component"),e[Hu]||null}function hs(e){return ni(e,"@Directive"),e[zu]||null}function qu(e){return ni(e,"@Pipe"),e[Bu]||null}function ni(e,t){if(e==null)throw new w(-919,!1)}function ps(e){return typeof e=="string"?e:e==null?"":String(e)}var gs=k({ngErrorCode:k}),Uu=k({ngErrorMessage:k}),Zu=k({ngTokenPath:k});function ms(e,t){return vs("",-200,t)}function ri(e,t){throw new w(-201,!1)}function vs(e,t,n){let r=new w(t,e);return r[gs]=t,r[Uu]=e,n&&(r[Zu]=n),r}function $u(e){return e[gs]}var cr;function ys(){return cr}function z(e){let t=cr;return cr=e,t}function bs(e,t,n){let r=ti(e);if(r&&r.providedIn=="root")return r.value===void 0?r.value=r.factory():r.value;if(n&8)return null;if(t!==void 0)return t;ri(e,"")}var Qu={},Ee=Qu,Wu="__NG_DI_FLAG__",Gu=class{injector;constructor(e){this.injector=e}retrieve(e,t){let n=mt(t)||0;try{return this.injector.get(e,n&8?null:Ee,n)}catch(r){if(ei(r))return r;throw r}}};function Yu(e,t=0){let n=ls();if(n===void 0)throw new w(-203,!1);if(n===null)return bs(e,void 0,t);{let r=Ku(t),i=n.retrieve(e,r);if(ei(i)){if(r.optional)return null;throw i}return i}}function C(e,t=0){return(ys()||Yu)(F(e),t)}function b(e,t){return C(e,mt(t))}function mt(e){return typeof e>"u"||typeof e=="number"?e:0|(e.optional&&8)|(e.host&&1)|(e.self&&2)|(e.skipSelf&&4)}function Ku(e){return{optional:!!(e&8),host:!!(e&1),self:!!(e&2),skipSelf:!!(e&4)}}function dr(e){let t=[];for(let n=0;nArray.isArray(n)?ii(n,t):t(n))}function ws(e,t,n){t>=e.length?e.push(n):e.splice(t,0,n)}function Xt(e,t){return t>=e.length-1?e.pop():e.splice(t,1)[0]}function tc(e,t,n,r){let i=e.length;if(i==t)e.push(n,r);else if(i===1)e.push(r,e[0]),e[0]=n;else{for(i--,e.push(e[i-1],e[i]);i>t;){let o=i-2;e[i]=e[o],i--}e[t]=n,e[t+1]=r}}function nc(e,t,n){let r=It(e,t);return r>=0?e[r|1]=n:(r=~r,tc(e,r,t,n)),r}function Bn(e,t){let n=It(e,t);if(n>=0)return e[n|1]}function It(e,t){return rc(e,t,1)}function rc(e,t,n){let r=0,i=e.length>>n;for(;i!==r;){let o=r+(i-r>>1),s=e[o<t?i=o:r=o+1}return~(i<{n.push(s)};return ii(t,s=>{let l=s;fr(l,o,[],r)&&(i||=[],i.push(l))}),i!==void 0&&Ss(i,o),n}function Ss(e,t){for(let n=0;n{t(o,r)})}}function fr(e,t,n,r){if(e=F(e),!e)return!1;let i=null,o=Wi(e),s=!o&>(e);if(!o&&!s){let a=e.ngModule;if(o=Wi(a),o)i=a;else return!1}else{if(s&&!s.standalone)return!1;i=e}let l=r.has(i);if(s){if(l)return!1;if(r.add(i),s.dependencies){let a=typeof s.dependencies=="function"?s.dependencies():s.dependencies;for(let u of a)fr(u,t,n,r)}}else if(o){if(o.imports!=null&&!l){r.add(i);let u;ii(o.imports,c=>{fr(c,t,n,r)&&(u||=[],u.push(c))}),u!==void 0&&Ss(u,t)}if(!l){let u=vt(i)||(()=>new i);t({provide:i,useFactory:u,deps:Te},i),t({provide:Cs,useValue:i,multi:!0},i),t({provide:bn,useValue:()=>C(i),multi:!0},i)}let a=o.providers;if(a!=null&&!l){let u=e;si(a,c=>{t(c,u)})}}else return!1;return i!==e&&e.providers!==void 0}function si(e,t){for(let n of e)fs(n)&&(n=n.\u0275providers),Array.isArray(n)?si(n,t):t(n)}var sc=k({provide:String,useValue:k});function Es(e){return e!==null&&typeof e=="object"&&sc in e}function lc(e){return!!(e&&e.useExisting)}function ac(e){return!!(e&&e.useFactory)}function Ke(e){return typeof e=="function"}function uc(e){return!!e.useClass}var li=new E(""),Bt={},Xi={},qn;function ai(){return qn===void 0&&(qn=new xs),qn}var ve=class{},ui=class extends ve{parent;source;scopes;records=new Map;_ngOnDestroyHooks=new Set;_onDestroyHooks=[];get destroyed(){return this._destroyed}_destroyed=!1;injectorDefTypes;constructor(e,t,n,r){super(),this.parent=t,this.source=n,this.scopes=r,pr(e,o=>this.processProvider(o)),this.records.set(_s,$e(void 0,this)),r.has("environment")&&this.records.set(ve,$e(void 0,this));let i=this.records.get(li);i!=null&&typeof i.value=="string"&&this.scopes.add(i.value),this.injectorDefTypes=new Set(this.get(Cs,Te,{self:!0}))}retrieve(e,t){let n=mt(t)||0;try{return this.get(e,Ee,n)}catch(r){if(ei(r))return r;throw r}}destroy(){ut(this),this._destroyed=!0;let e=m(null);try{for(let n of this._ngOnDestroyHooks)n.ngOnDestroy();let t=this._onDestroyHooks;this._onDestroyHooks=[];for(let n of t)n()}finally{this.records.clear(),this._ngOnDestroyHooks.clear(),this.injectorDefTypes.clear(),m(e)}}onDestroy(e){return ut(this),this._onDestroyHooks.push(e),()=>this.removeOnDestroy(e)}runInContext(e){ut(this);let t=fe(this),n=z(void 0),r;try{return e()}finally{fe(t),z(n)}}get(e,t=Ee,n){if(ut(this),e.hasOwnProperty(Ki))return e[Ki](this);let r=mt(n),i,o=fe(this),s=z(void 0);try{if(!(r&4)){let a=this.records.get(e);if(a===void 0){let u=pc(e)&&ti(e);u&&this.injectableDefInScope(u)?a=$e(hr(e),Bt):a=null,this.records.set(e,a)}if(a!=null)return this.hydrate(e,a,r)}let l=r&2?ai():this.parent;return t=r&8&&t===Ee?null:t,l.get(e,t)}catch(l){let a=$u(l);throw a===-200||a===-201?new w(a,null):l}finally{z(s),fe(o)}}resolveInjectorInitializers(){let e=m(null),t=fe(this),n=z(void 0),r;try{let i=this.get(bn,Te,{self:!0});for(let o of i)o()}finally{fe(t),z(n),m(e)}}toString(){return"R3Injector[...]"}processProvider(e){e=F(e);let t=Ke(e)?e:F(e&&e.provide),n=dc(e);if(!Ke(e)&&e.multi===!0){let r=this.records.get(t);r||(r=$e(void 0,Bt,!0),r.factory=()=>dr(r.multi),this.records.set(t,r)),t=e,r.multi.push(e)}this.records.set(t,n)}hydrate(e,t,n){let r=m(null);try{if(t.value===Xi)throw ms("");return t.value===Bt&&(t.value=Xi,t.value=t.factory(void 0,n)),typeof t.value=="object"&&t.value&&hc(t.value)&&this._ngOnDestroyHooks.add(t.value),t.value}finally{m(r)}}injectableDefInScope(e){if(!e.providedIn)return!1;let t=F(e.providedIn);return typeof t=="string"?t==="any"||this.scopes.has(t):this.injectorDefTypes.has(t)}removeOnDestroy(e){let t=this._onDestroyHooks.indexOf(e);t!==-1&&this._onDestroyHooks.splice(t,1)}};function hr(e){let t=ti(e),n=t!==null?t.factory:vt(e);if(n!==null)return n;if(e instanceof E)throw new w(-204,!1);if(e instanceof Function)return cc(e);throw new w(-204,!1)}function cc(e){if(e.length>0)throw new w(-204,!1);let t=Fu(e);return t!==null?()=>t.factory(e):()=>new e}function dc(e){if(Es(e))return $e(void 0,e.useValue);{let t=Is(e);return $e(t,Bt)}}function Is(e,t,n){let r;if(Ke(e)){let i=F(e);return vt(i)||hr(i)}else if(Es(e))r=()=>F(e.useValue);else if(ac(e))r=()=>e.useFactory(...dr(e.deps||[]));else if(lc(e))r=(i,o)=>C(F(e.useExisting),o!==void 0&&o&8?8:void 0);else{let i=F(e&&(e.useClass||e.provide));if(fc(e))r=()=>new i(...dr(e.deps));else return vt(i)||hr(i)}return r}function ut(e){if(e.destroyed)throw new w(-205,!1)}function $e(e,t,n=!1){return{factory:e,value:t,multi:n?[]:void 0}}function fc(e){return!!e.deps}function hc(e){return e!==null&&typeof e=="object"&&typeof e.ngOnDestroy=="function"}function pc(e){return typeof e=="function"||typeof e=="object"&&e.ngMetadataName==="InjectionToken"}function pr(e,t){for(let n of e)Array.isArray(n)?pr(n,t):n&&fs(n)?pr(n.\u0275providers,t):t(n)}function Ts(e,t){let n;e instanceof ui?(ut(e),n=e):n=new Gu(e);let r,i=fe(n),o=z(void 0);try{return t()}finally{fe(i),z(o)}}function gc(){return ys()!==void 0||ls()!=null}var le=0,g=1,v=2,A=3,Z=4,W=5,yt=6,Jt=7,O=8,ye=9,ie=10,V=11,bt=12,Ji=13,nt=14,K=15,Oe=16,Qe=17,oe=18,be=19,Os=20,ge=21,Un=22,De=23,q=24,Zn=25,Me=26,H=27,Ds=1,eo=6,Pe=7,en=8,Xe=9,T=10;function Ie(e){return Array.isArray(e)&&typeof e[Ds]=="object"}function ae(e){return Array.isArray(e)&&e[Ds]===!0}function Ms(e){return(e.flags&4)!==0}function wn(e){return e.componentOffset>-1}function Ps(e){return(e.flags&1)===1}function rt(e){return!!e.template}function tn(e){return(e[v]&512)!==0}function it(e){return(e[v]&256)===256}var mc="svg",vc="math";function X(e){for(;Array.isArray(e);)e=e[le];return e}function Ns(e,t){return X(t[e])}function ue(e,t){return X(t[e.index])}function ci(e,t){return e.data[t]}function Ne(e,t){let n=t[e];return Ie(n)?n:n[le]}function yc(e){return(e[v]&4)===4}function di(e){return(e[v]&128)===128}function bc(e){return ae(e[A])}function se(e,t){return t==null?null:e[t]}function As(e){e[Qe]=0}function Vs(e){e[v]&1024||(e[v]|=1024,di(e)&&Tt(e))}function wc(e,t){for(;e>0;)t=t[nt],e--;return t}function nn(e){return!!(e[v]&9216||e[q]?.dirty)}function gr(e){e[ie].changeDetectionScheduler?.notify(8),e[v]&64&&(e[v]|=1024),nn(e)&&Tt(e)}function Tt(e){e[ie].changeDetectionScheduler?.notify(0);let t=Ae(e);for(;t!==null&&!(t[v]&8192||(t[v]|=8192,!di(t)));)t=Ae(t)}function Rs(e,t){if(it(e))throw new w(911,!1);e[ge]===null&&(e[ge]=[]),e[ge].push(t)}function _c(e,t){if(e[ge]===null)return;let n=e[ge].indexOf(t);n!==-1&&e[ge].splice(n,1)}function Ae(e){let t=e[A];return ae(t)?t[A]:t}function Ls(e){return e[Jt]??=[]}function js(e){return e.cleanup??=[]}function Cc(e,t,n,r){let i=Ls(t);i.push(n),e.firstCreatePass&&js(e).push(r,i.length-1)}var y={lFrame:Zs(null),bindingsEnabled:!0,skipHydrationRootTNode:null},mr=!1;function xc(){return y.lFrame.elementDepthCount}function kc(){y.lFrame.elementDepthCount++}function Sc(){y.lFrame.elementDepthCount--}function Ec(){return y.skipHydrationRootTNode!==null}function Ic(e){return y.skipHydrationRootTNode===e}function Tc(){y.skipHydrationRootTNode=null}function S(){return y.lFrame.lView}function U(){return y.lFrame.tView}function qe(e){return y.lFrame.contextLView=e,e[O]}function Ue(e){return y.lFrame.contextLView=null,e}function ce(){let e=Fs();for(;e!==null&&e.type===64;)e=e.parent;return e}function Fs(){return y.lFrame.currentTNode}function Oc(){let e=y.lFrame,t=e.currentTNode;return e.isParent?t:t.parent}function Ot(e,t){let n=y.lFrame;n.currentTNode=e,n.isParent=t}function Hs(){return y.lFrame.isParent}function Dc(){y.lFrame.isParent=!1}function zs(){return mr}function rn(e){let t=mr;return mr=e,t}function Mc(e){return y.lFrame.bindingIndex=e}function _n(){return y.lFrame.bindingIndex++}function Pc(e){let t=y.lFrame,n=t.bindingIndex;return t.bindingIndex=t.bindingIndex+e,n}function Nc(){return y.lFrame.inI18n}function Ac(e,t){let n=y.lFrame;n.bindingIndex=n.bindingRootIndex=e,vr(t)}function Vc(){return y.lFrame.currentDirectiveIndex}function vr(e){y.lFrame.currentDirectiveIndex=e}function Rc(e){let t=y.lFrame.currentDirectiveIndex;return t===-1?null:e[t]}function Bs(){return y.lFrame.currentQueryIndex}function fi(e){y.lFrame.currentQueryIndex=e}function Lc(e){let t=e[g];return t.type===2?t.declTNode:t.type===1?e[W]:null}function qs(e,t,n){if(n&4){let i=t,o=e;for(;(i=i.parent,i===null&&!(n&1))&&(i=Lc(o),!(i===null||(o=o[nt],i.type&10))););if(i===null)return!1;t=i,e=o}let r=y.lFrame=Us();return r.currentTNode=t,r.lView=e,!0}function hi(e){let t=Us(),n=e[g];y.lFrame=t,t.currentTNode=n.firstChild,t.lView=e,t.tView=n,t.contextLView=e,t.bindingIndex=n.bindingStartIndex,t.inI18n=!1}function Us(){let e=y.lFrame,t=e===null?null:e.child;return t===null?Zs(e):t}function Zs(e){let t={currentTNode:null,isParent:!0,lView:null,tView:null,selectedIndex:-1,contextLView:null,elementDepthCount:0,currentNamespace:null,currentDirectiveIndex:-1,bindingRootIndex:-1,bindingIndex:-1,currentQueryIndex:0,parent:e,child:null,inI18n:!1};return e!==null&&(e.child=t),t}function $s(){let e=y.lFrame;return y.lFrame=e.parent,e.currentTNode=null,e.lView=null,e}var Qs=$s;function pi(){let e=$s();e.isParent=!0,e.tView=null,e.selectedIndex=-1,e.contextLView=null,e.elementDepthCount=0,e.currentDirectiveIndex=-1,e.currentNamespace=null,e.bindingRootIndex=-1,e.bindingIndex=-1,e.currentQueryIndex=0}function jc(e){return(y.lFrame.contextLView=wc(e,y.lFrame.contextLView))[O]}function Fe(){return y.lFrame.selectedIndex}function Ve(e){y.lFrame.selectedIndex=e}function Fc(){let e=y.lFrame;return ci(e.tView,e.selectedIndex)}function Hc(){return y.lFrame.currentNamespace}var Ws=!0;function gi(){return Ws}function mi(e){Ws=e}function to(e,t=null,n=null,r){let i=zc(e,t,n,r);return i.resolveInjectorInitializers(),i}function zc(e,t=null,n=null,r,i=new Set){let o=[n||Te,oc(e)],s;return new ui(o,t||ai(),s||null,i)}var Cn=class Gs{static THROW_IF_NOT_FOUND=Ee;static NULL=new xs;static create(t,n){if(Array.isArray(t))return to({name:""},n,t,"");{let r=t.name??"";return to({name:r},t.parent,t.providers,r)}}static \u0275prov=D({token:Gs,providedIn:"any",factory:()=>C(_s)});static __NG_ELEMENT_ID__=-1},we=new E(""),xn=(()=>{class e{static __NG_ELEMENT_ID__=Bc;static __NG_ENV_ID__=n=>n}return e})(),Ys=class extends xn{_lView;constructor(e){super(),this._lView=e}get destroyed(){return it(this._lView)}onDestroy(e){let t=this._lView;return Rs(t,e),()=>_c(t,e)}};function Bc(){return new Ys(S())}var qc=!1,Uc=new E(""),kn=(()=>{class e{taskId=0;pendingTasks=new Set;destroyed=!1;pendingTask=new Mu(!1);debugTaskTracker=b(Uc,{optional:!0});get hasPendingTasks(){return this.destroyed?!1:this.pendingTask.value}get hasPendingTasksObservable(){return this.destroyed?new ar(n=>{n.next(!1),n.complete()}):this.pendingTask}add(){!this.hasPendingTasks&&!this.destroyed&&this.pendingTask.next(!0);let n=this.taskId++;return this.pendingTasks.add(n),this.debugTaskTracker?.add(n),n}has(n){return this.pendingTasks.has(n)}remove(n){this.pendingTasks.delete(n),this.debugTaskTracker?.remove(n),this.pendingTasks.size===0&&this.hasPendingTasks&&this.pendingTask.next(!1)}ngOnDestroy(){this.pendingTasks.clear(),this.hasPendingTasks&&this.pendingTask.next(!1),this.destroyed=!0,this.pendingTask.unsubscribe()}static \u0275prov=D({token:e,providedIn:"root",factory:()=>new e})}return e})(),Zc=class extends Et{__isAsync;destroyRef=void 0;pendingTasks=void 0;constructor(e=!1){super(),this.__isAsync=e,gc()&&(this.destroyRef=b(xn,{optional:!0})??void 0,this.pendingTasks=b(kn,{optional:!0})??void 0)}emit(e){let t=m(null);try{super.next(e)}finally{m(t)}}subscribe(e,t,n){let r=e,i=t||(()=>null),o=n;if(e&&typeof e=="object"){let l=e;r=l.next?.bind(l),i=l.error?.bind(l),o=l.complete?.bind(l)}this.__isAsync&&(i=this.wrapInTimeout(i),r&&(r=this.wrapInTimeout(r)),o&&(o=this.wrapInTimeout(o)));let s=super.subscribe({next:r,error:i,complete:o});return e instanceof me&&e.add(s),s}wrapInTimeout(e){return t=>{let n=this.pendingTasks?.add();setTimeout(()=>{try{e(t)}finally{n!==void 0&&this.pendingTasks?.remove(n)}})}}},pe=Zc;function on(...e){}function Ks(e){let t,n;function r(){e=on;try{n!==void 0&&typeof cancelAnimationFrame=="function"&&cancelAnimationFrame(n),t!==void 0&&clearTimeout(t)}catch{}}return t=setTimeout(()=>{e(),r()}),typeof requestAnimationFrame=="function"&&(n=requestAnimationFrame(()=>{e(),r()})),()=>r()}function $c(e){return queueMicrotask(()=>e()),()=>{e=on}}var vi="isAngularZone",sn=vi+"_ID",Qc=0,He=class yr{hasPendingMacrotasks=!1;hasPendingMicrotasks=!1;isStable=!0;onUnstable=new pe(!1);onMicrotaskEmpty=new pe(!1);onStable=new pe(!1);onError=new pe(!1);constructor(t){let{enableLongStackTrace:n=!1,shouldCoalesceEventChangeDetection:r=!1,shouldCoalesceRunChangeDetection:i=!1,scheduleInRootZone:o=qc}=t;if(typeof Zone>"u")throw new w(908,!1);Zone.assertZonePatched();let s=this;s._nesting=0,s._outer=s._inner=Zone.current,Zone.TaskTrackingZoneSpec&&(s._inner=s._inner.fork(new Zone.TaskTrackingZoneSpec)),n&&Zone.longStackTraceZoneSpec&&(s._inner=s._inner.fork(Zone.longStackTraceZoneSpec)),s.shouldCoalesceEventChangeDetection=!i&&r,s.shouldCoalesceRunChangeDetection=i,s.callbackScheduled=!1,s.scheduleInRootZone=o,Yc(s)}static isInAngularZone(){return typeof Zone<"u"&&Zone.current.get(vi)===!0}static assertInAngularZone(){if(!yr.isInAngularZone())throw new w(909,!1)}static assertNotInAngularZone(){if(yr.isInAngularZone())throw new w(909,!1)}run(t,n,r){return this._inner.run(t,n,r)}runTask(t,n,r,i){let o=this._inner,s=o.scheduleEventTask("NgZoneEvent: "+i,t,Wc,on,on);try{return o.runTask(s,n,r)}finally{o.cancelTask(s)}}runGuarded(t,n,r){return this._inner.runGuarded(t,n,r)}runOutsideAngular(t){return this._outer.run(t)}},Wc={};function yi(e){if(e._nesting==0&&!e.hasPendingMicrotasks&&!e.isStable)try{e._nesting++,e.onMicrotaskEmpty.emit(null)}finally{if(e._nesting--,!e.hasPendingMicrotasks)try{e.runOutsideAngular(()=>e.onStable.emit(null))}finally{e.isStable=!0}}}function Gc(e){if(e.isCheckStableRunning||e.callbackScheduled)return;e.callbackScheduled=!0;function t(){Ks(()=>{e.callbackScheduled=!1,br(e),e.isCheckStableRunning=!0,yi(e),e.isCheckStableRunning=!1})}e.scheduleInRootZone?Zone.root.run(()=>{t()}):e._outer.run(()=>{t()}),br(e)}function Yc(e){let t=()=>{Gc(e)},n=Qc++;e._inner=e._inner.fork({name:"angular",properties:{[vi]:!0,[sn]:n,[sn+n]:!0},onInvokeTask:(r,i,o,s,l,a)=>{if(Xc(a))return r.invokeTask(o,s,l,a);try{return no(e),r.invokeTask(o,s,l,a)}finally{(e.shouldCoalesceEventChangeDetection&&s.type==="eventTask"||e.shouldCoalesceRunChangeDetection)&&t(),ro(e)}},onInvoke:(r,i,o,s,l,a,u)=>{try{return no(e),r.invoke(o,s,l,a,u)}finally{e.shouldCoalesceRunChangeDetection&&!e.callbackScheduled&&!Jc(a)&&t(),ro(e)}},onHasTask:(r,i,o,s)=>{r.hasTask(o,s),i===o&&(s.change=="microTask"?(e._hasPendingMicrotasks=s.microTask,br(e),yi(e)):s.change=="macroTask"&&(e.hasPendingMacrotasks=s.macroTask))},onHandleError:(r,i,o,s)=>(r.handleError(o,s),e.runOutsideAngular(()=>e.onError.emit(s)),!1)})}function br(e){e._hasPendingMicrotasks||(e.shouldCoalesceEventChangeDetection||e.shouldCoalesceRunChangeDetection)&&e.callbackScheduled===!0?e.hasPendingMicrotasks=!0:e.hasPendingMicrotasks=!1}function no(e){e._nesting++,e.isStable&&(e.isStable=!1,e.onUnstable.emit(null))}function ro(e){e._nesting--,yi(e)}var Kc=class{hasPendingMicrotasks=!1;hasPendingMacrotasks=!1;isStable=!0;onUnstable=new pe;onMicrotaskEmpty=new pe;onStable=new pe;onError=new pe;run(e,t,n){return e.apply(t,n)}runGuarded(e,t,n){return e.apply(t,n)}runOutsideAngular(e){return e()}runTask(e,t,n,r){return e.apply(t,n)}};function Xc(e){return Xs(e,"__ignore_ng_zone__")}function Jc(e){return Xs(e,"__scheduler_tick__")}function Xs(e,t){return!Array.isArray(e)||e.length!==1?!1:e[0]?.data?.[t]===!0}var Sn=class{_console=console;handleError(e){this._console.error("ERROR",e)}},Dt=new E("",{factory:()=>{let e=b(He),t=b(ve),n;return r=>{e.runOutsideAngular(()=>{t.destroyed&&!n?setTimeout(()=>{throw r}):(n??=t.get(Sn),n.handleError(r))})}}}),ed={provide:bn,useValue:()=>{let e=b(Sn,{optional:!0})},multi:!0},td=new E("",{factory:()=>{let e=b(we).defaultView;if(!e)return;let t=b(Dt),n=o=>{t(o.reason),o.preventDefault()},r=o=>{o.error?t(o.error):t(new Error(o.message,{cause:o})),o.preventDefault()},i=()=>{e.addEventListener("unhandledrejection",n),e.addEventListener("error",r)};typeof Zone<"u"?Zone.root.run(i):i(),b(xn).onDestroy(()=>{e.removeEventListener("error",r),e.removeEventListener("unhandledrejection",n)})}});function nd(){return oi([ic(()=>{b(td)})])}function j(e,t){let[n,r,i]=ou(e,t?.equal),o=n,s=o[ne];return o.set=r,o.update=i,o.asReadonly=rd.bind(o),o}function rd(){let e=this[ne];if(e.readonlyFn===void 0){let t=()=>this();t[ne]=e,e.readonlyFn=t}return e.readonlyFn}var Js=(()=>{class e{view;node;constructor(n,r){this.view=n,this.node=r}static __NG_ELEMENT_ID__=id}return e})();function id(){return new Js(S(),ce())}var bi=class{},wi=new E("",{factory:()=>!0}),od=new E(""),el=(()=>{class e{static \u0275prov=D({token:e,providedIn:"root",factory:()=>new sd})}return e})(),sd=class{dirtyEffectCount=0;queues=new Map;add(e){this.enqueue(e),this.schedule(e)}schedule(e){e.dirty&&this.dirtyEffectCount++}remove(e){let t=e.zone,n=this.queues.get(t);n.has(e)&&(n.delete(e),e.dirty&&this.dirtyEffectCount--)}enqueue(e){let t=e.zone;this.queues.has(t)||this.queues.set(t,new Set);let n=this.queues.get(t);n.has(e)||n.add(e)}flush(){for(;this.dirtyEffectCount>0;){let e=!1;for(let[t,n]of this.queues)t===null?e||=this.flushQueue(n):e||=t.run(()=>this.flushQueue(n));e||(this.dirtyEffectCount=0)}}flushQueue(e){let t=!1;for(let n of e)n.dirty&&(this.dirtyEffectCount--,t=!0,n.run());return t}},ld=class{[ne];constructor(e){this[ne]=e}destroy(){this[ne].destroy()}};function $n(e,t){let n=t?.injector??b(Cn),r=t?.manualCleanup!==!0?n.get(xn):null,i,o=n.get(Js,null,{optional:!0}),s=n.get(bi);return o!==null?(i=cd(o.view,s,e),r instanceof Ys&&r._lView===o.view&&(r=null)):i=dd(e,n.get(el),s),i.injector=n,r!==null&&(i.onDestroyFns=[r.onDestroy(()=>i.destroy())]),new ld(i)}var tl=Q($({},cu),{cleanupFns:void 0,zone:null,onDestroyFns:null,run(){let e=rn(!1);try{du(this)}finally{rn(e)}},cleanup(){if(!this.cleanupFns?.length)return;let e=m(null);try{for(;this.cleanupFns.length;)this.cleanupFns.pop()()}finally{this.cleanupFns=[],m(e)}}}),ad=Q($({},tl),{consumerMarkedDirty(){this.scheduler.schedule(this),this.notifier.notify(12)},destroy(){if(yn(this),this.onDestroyFns!==null)for(let e of this.onDestroyFns)e();this.cleanup(),this.scheduler.remove(this)}}),ud=Q($({},tl),{consumerMarkedDirty(){this.view[v]|=8192,Tt(this.view),this.notifier.notify(13)},destroy(){if(yn(this),this.onDestroyFns!==null)for(let e of this.onDestroyFns)e();this.cleanup(),this.view[De]?.delete(this)}});function cd(e,t,n){let r=Object.create(ud);return r.view=e,r.zone=typeof Zone<"u"?Zone.current:null,r.notifier=t,r.fn=nl(r,n),e[De]??=new Set,e[De].add(r),r.consumerMarkedDirty(r),r}function dd(e,t,n){let r=Object.create(ad);return r.fn=nl(r,e),r.scheduler=t,r.notifier=n,r.zone=typeof Zone<"u"?Zone.current:null,r.scheduler.add(r),r.notifier.notify(12),r}function nl(e,t){return()=>{t(n=>(e.cleanupFns??=[]).push(n))}}function fd(e){return{toString:e}.toString()}function hd(e){return typeof e=="function"}function rl(e,t,n,r){t!==null?t.applyValueToInputSignal(t,r):e[n]=r}var pd=class{previousValue;currentValue;firstChange;constructor(e,t,n){this.previousValue=e,this.currentValue=t,this.firstChange=n}isFirstChange(){return this.firstChange}};function gd(e){return e.type.prototype.ngOnChanges&&(e.setInput=vd),md}function md(){let e=ol(this),t=e?.current;if(t){let n=e.previous;if(n===Ye)e.previous=t;else for(let r in t)n[r]=t[r];e.current=null,this.ngOnChanges(t)}}function vd(e,t,n,r,i){let o=this.declaredInputs[r],s=ol(e)||yd(e,{previous:Ye,current:null}),l=s.current||(s.current={}),a=s.previous,u=a[o];l[o]=new pd(u&&u.currentValue,n,a===Ye),rl(e,t,i,n)}var il="__ngSimpleChanges__";function ol(e){return e[il]||null}function yd(e,t){return e[il]=t}var io=[],x=function(e,t=null,n){for(let r=0;r=r)break}else t[a]<0&&(e[Qe]+=65536),(l>14>16&&(e[v]&3)===t&&(e[v]+=16384,oo(l,o)):oo(l,o)}var Ge=-1,Mt=class{factory;name;injectImpl;resolving=!1;canSeeViewProviders;multi;componentProviders;index;providerFactory;constructor(e,t,n,r){this.factory=e,this.name=r,this.canSeeViewProviders=t,this.injectImpl=n}};function Cd(e,t,n){let r=0;for(;rt){s=o-1;break}}}for(;o>16}function an(e,t){let n=kd(e),r=t;for(;n>0;)r=r[nt],n--;return r}var wr=!0;function lo(e){let t=wr;return wr=e,t}var Sd=256,al=Sd-1,ul=5,Ed=0,G={};function Id(e,t,n){let r;typeof n=="string"?r=n.charCodeAt(0)||0:n.hasOwnProperty(ht)&&(r=n[ht]),r==null&&(r=n[ht]=Ed++);let i=r&al,o=1<>ul)]|=o}function un(e,t){let n=cl(e,t);if(n!==-1)return n;let r=t[g];r.firstCreatePass&&(e.injectorIndex=t.length,Wn(r.data,e),Wn(t,null),Wn(r.blueprint,null));let i=_i(e,t),o=e.injectorIndex;if(ll(i)){let s=ln(i),l=an(i,t),a=l[g].data;for(let u=0;u<8;u++)t[o+u]=l[s+u]|a[s+u]}return t[o+8]=i,o}function Wn(e,t){e.push(0,0,0,0,0,0,0,0,t)}function cl(e,t){return e.injectorIndex===-1||e.parent&&e.parent.injectorIndex===e.injectorIndex||t[e.injectorIndex+8]===null?-1:e.injectorIndex}function _i(e,t){if(e.parent&&e.parent.injectorIndex!==-1)return e.parent.injectorIndex;let n=0,r=null,i=t;for(;i!==null;){if(r=gl(i),r===null)return Ge;if(n++,i=i[nt],r.injectorIndex!==-1)return r.injectorIndex|n<<16}return Ge}function _r(e,t,n){Id(e,t,n)}function dl(e,t,n){if(n&8||e!==void 0)return e;ri(t,"NodeInjector")}function fl(e,t,n,r){if(n&8&&r===void 0&&(r=null),(n&3)===0){let i=e[ye],o=z(void 0);try{return i?i.get(t,r,n&8):bs(t,r,n&8)}finally{z(o)}}return dl(r,t,n)}function hl(e,t,n,r=0,i){if(e!==null){if(t[v]&2048&&!(r&2)){let s=Md(e,t,n,r,G);if(s!==G)return s}let o=pl(e,t,n,r,G);if(o!==G)return o}return fl(t,n,r,i)}function pl(e,t,n,r,i){let o=Od(n);if(typeof o=="function"){if(!qs(t,e,r))return r&1?dl(i,n,r):fl(t,n,r,i);try{let s;if(s=o(r),s==null&&!(r&8))ri(n);else return s}finally{Qs()}}else if(typeof o=="number"){let s=null,l=cl(e,t),a=Ge,u=r&1?t[K][W]:null;for((l===-1||r&4)&&(a=l===-1?_i(e,t):t[l+8],a===Ge||!uo(r,!1)?l=-1:(s=t[g],l=ln(a),t=an(a,t)));l!==-1;){let c=t[g];if(ao(o,l,c.data)){let d=Td(l,t,n,s,r,u);if(d!==G)return d}a=t[l+8],a!==Ge&&uo(r,t[g].data[l+8]===u)&&ao(o,l,t)?(s=c,l=ln(a),t=an(a,t)):l=-1}}return i}function Td(e,t,n,r,i,o){let s=t[g],l=s.data[e+8],a=r==null?wn(l)&&wr:r!=s&&(l.type&3)!==0,u=i&1&&o===l,c=Zt(l,s,n,a,u);return c!==null?wt(t,s,c,l,i):G}function Zt(e,t,n,r,i){let o=e.providerIndexes,s=t.data,l=o&1048575,a=e.directiveStart,u=e.directiveEnd,c=o>>20,d=r?l:l+c,h=i?l+c:u;for(let f=d;f=a&&p.type===n)return f}if(i){let f=s[a];if(f&&rt(f)&&f.type===n)return a}return null}function wt(e,t,n,r,i){let o=e[n],s=t.data;if(o instanceof Mt){let l=o;if(l.resolving)throw ms("");let a=lo(l.canSeeViewProviders);l.resolving=!0;let u=s[n].type||s[n],c,d=l.injectImpl?z(l.injectImpl):null,h=qs(e,r,0);try{o=e[n]=l.factory(void 0,i,s,e,r),t.firstCreatePass&&n>=r.directiveStart&&bd(n,s[n],t)}finally{d!==null&&z(d),lo(a),l.resolving=!1,Qs()}}return o}function Od(e){if(typeof e=="string")return e.charCodeAt(0)||0;let t=e.hasOwnProperty(ht)?e[ht]:void 0;return typeof t=="number"?t>=0?t&al:Dd:t}function ao(e,t,n){let r=1<>ul)]&r)}function uo(e,t){return!(e&2)&&!(e&1&&t)}var pt=class{_tNode;_lView;constructor(e,t){this._tNode=e,this._lView=t}get(e,t,n){return hl(this._tNode,this._lView,e,mt(n),t)}};function Dd(){return new pt(ce(),S())}function Md(e,t,n,r,i){let o=e,s=t;for(;o!==null&&s!==null&&s[v]&2048&&!tn(s);){let l=pl(o,s,n,r|2,G);if(l!==G)return l;let a=o.parent;if(!a){let u=s[Os];if(u){let c=u.get(n,G,r&-5);if(c!==G)return c}a=gl(s),s=s[nt]}o=a}return i}function gl(e){let t=e[g],n=t.type;return n===2?t.declTNode:n===1?e[W]:null}function Pd(){return ot(ce(),S())}function ot(e,t){return new In(ue(e,t))}var In=(()=>{class e{nativeElement;constructor(n){this.nativeElement=n}static __NG_ELEMENT_ID__=Pd}return e})();function Nd(e){return e instanceof In?e.nativeElement:e}function Ad(){return this._results[Symbol.iterator]()}var Vd=class{_emitDistinctChangesOnly;dirty=!0;_onDirty=void 0;_results=[];_changesDetected=!1;_changes=void 0;length=0;first=void 0;last=void 0;get changes(){return this._changes??=new Et}constructor(e=!1){this._emitDistinctChangesOnly=e}get(e){return this._results[e]}map(e){return this._results.map(e)}filter(e){return this._results.filter(e)}find(e){return this._results.find(e)}reduce(e,t){return this._results.reduce(e,t)}forEach(e){this._results.forEach(e)}some(e){return this._results.some(e)}toArray(){return this._results.slice()}toString(){return this._results.toString()}reset(e,t){this.dirty=!1;let n=ec(e);(this._changesDetected=!Ju(this._results,n,t))&&(this._results=n,this.length=n.length,this.last=n[this.length-1],this.first=n[0])}notifyOnChanges(){this._changes!==void 0&&(this._changesDetected||!this._emitDistinctChangesOnly)&&this._changes.next(this)}onDirty(e){this._onDirty=e}setDirty(){this.dirty=!0,this._onDirty?.()}destroy(){this._changes!==void 0&&(this._changes.complete(),this._changes.unsubscribe())}[Symbol.iterator]=Ad};function ml(e){return(e.flags&128)===128}var vl=function(e){return e[e.OnPush=0]="OnPush",e[e.Eager=1]="Eager",e[e.Default=1]="Default",e}(vl||{}),yl=new Map,Rd=0;function Ld(){return Rd++}function jd(e){yl.set(e[be],e)}function Cr(e){yl.delete(e[be])}var co="__ngContext__";function Je(e,t){Ie(t)?(e[co]=t[be],jd(t)):e[co]=t}function bl(e){return _l(e[bt])}function wl(e){return _l(e[Z])}function _l(e){for(;e!==null&&!ae(e);)e=e[Z];return e}var xr;function Fd(e){xr=e}function Hd(){if(xr!==void 0)return xr;if(typeof document<"u")return document;throw new w(210,!1)}var Cl=new E("",{factory:()=>zd}),zd="ng",xl=new E(""),kl=new E("",{providedIn:"platform",factory:()=>"unknown"}),Sl=new E("",{factory:()=>b(we).body?.querySelector("[ngCspNonce]")?.getAttribute("ngCspNonce")||null}),Bd="r",qd="di",El=!1,Ud=new E("",{factory:()=>El}),fo=new WeakMap;function Zd(e,t){if(e==null||typeof e!="object")return;let n=fo.get(e);n||(n=new WeakSet,fo.set(e,n)),n.add(t)}var $d=(e,t,n,r)=>{};function Qd(e,t,n,r){$d(e,t,n,r)}function Il(e){return(e.flags&32)===32}var Wd=()=>null;function Tl(e,t,n=!1){return Wd(e,t,n)}function Ol(e,t){let n=e.contentQueries;if(n!==null){let r=m(null);try{for(let i=0;ie,createScript:e=>e,createScriptURL:e=>e})}catch{}return Vt}function Tn(e){return Yd()?.createHTML(e)||e}var Rt;function Kd(){if(Rt===void 0&&(Rt=null,Kt.trustedTypes))try{Rt=Kt.trustedTypes.createPolicy("angular#unsafe-bypass",{createHTML:e=>e,createScript:e=>e,createScriptURL:e=>e})}catch{}return Rt}function ho(e){return Kd()?.createHTML(e)||e}var ze=class{changingThisBreaksApplicationSecurity;constructor(e){this.changingThisBreaksApplicationSecurity=e}toString(){return`SafeValue must use [property]=binding: ${this.changingThisBreaksApplicationSecurity} (see ${as})`}},Xd=class extends ze{getTypeName(){return"HTML"}},Jd=class extends ze{getTypeName(){return"Style"}},ef=class extends ze{getTypeName(){return"Script"}},tf=class extends ze{getTypeName(){return"URL"}},nf=class extends ze{getTypeName(){return"ResourceURL"}};function Ce(e){return e instanceof ze?e.changingThisBreaksApplicationSecurity:e}function Ze(e,t){let n=rf(e);if(n!=null&&n!==t){if(n==="ResourceURL"&&t==="URL")return!0;throw new Error(`Required a safe ${t}, got a ${n} (see ${as})`)}return n===t}function rf(e){return e instanceof ze&&e.getTypeName()||null}function of(e){return new Xd(e)}function sf(e){return new Jd(e)}function lf(e){return new ef(e)}function af(e){return new tf(e)}function uf(e){return new nf(e)}function cf(e){let t=new ff(e);return hf()?new df(t):t}var df=class{inertDocumentHelper;constructor(e){this.inertDocumentHelper=e}getInertBodyElement(e){e=""+e;try{let t=new window.DOMParser().parseFromString(Tn(e),"text/html").body;return t===null?this.inertDocumentHelper.getInertBodyElement(e):(t.firstChild?.remove(),t)}catch{return null}}},ff=class{defaultDoc;inertDocument;constructor(e){this.defaultDoc=e,this.inertDocument=this.defaultDoc.implementation.createHTMLDocument("sanitization-inert")}getInertBodyElement(e){let t=this.inertDocument.createElement("template");return t.innerHTML=Tn(e),t}};function hf(){try{return!!new window.DOMParser().parseFromString(Tn(""),"text/html")}catch{return!1}}var pf=/^(?!javascript:)(?:[a-z0-9+.-]+:|[^&:\/?#]*(?:[\/?#]|$))/i;function Dl(e){return e=String(e),e.match(pf)?e:"unsafe:"+e}function de(e){let t={};for(let n of e.split(","))t[n]=!0;return t}function Pt(...e){let t={};for(let n of e)for(let r in n)n.hasOwnProperty(r)&&(t[r]=!0);return t}var Ml=de("area,br,col,hr,img,wbr"),Pl=de("colgroup,dd,dt,li,p,tbody,td,tfoot,th,thead,tr"),Nl=de("rp,rt"),gf=Pt(Nl,Pl),mf=Pt(Pl,de("address,article,aside,blockquote,caption,center,del,details,dialog,dir,div,dl,figure,figcaption,footer,h1,h2,h3,h4,h5,h6,header,hgroup,hr,ins,main,map,menu,nav,ol,pre,section,summary,table,ul")),vf=Pt(Nl,de("a,abbr,acronym,audio,b,bdi,bdo,big,br,cite,code,del,dfn,em,font,i,img,ins,kbd,label,map,mark,picture,q,ruby,rp,rt,s,samp,small,source,span,strike,strong,sub,sup,time,track,tt,u,var,video")),po=Pt(Ml,mf,vf,gf),Al=de("background,cite,href,itemtype,longdesc,poster,src,xlink:href"),yf=de("abbr,accesskey,align,alt,autoplay,axis,bgcolor,border,cellpadding,cellspacing,class,clear,color,cols,colspan,compact,controls,coords,datetime,default,dir,download,face,headers,height,hidden,hreflang,hspace,ismap,itemscope,itemprop,kind,label,lang,language,loop,media,muted,nohref,nowrap,open,preload,rel,rev,role,rows,rowspan,rules,scope,scrolling,shape,size,sizes,span,srclang,srcset,start,summary,tabindex,target,title,translate,type,usemap,valign,value,vspace,width"),bf=de("aria-activedescendant,aria-atomic,aria-autocomplete,aria-busy,aria-checked,aria-colcount,aria-colindex,aria-colspan,aria-controls,aria-current,aria-describedby,aria-details,aria-disabled,aria-dropeffect,aria-errormessage,aria-expanded,aria-flowto,aria-grabbed,aria-haspopup,aria-hidden,aria-invalid,aria-keyshortcuts,aria-label,aria-labelledby,aria-level,aria-live,aria-modal,aria-multiline,aria-multiselectable,aria-orientation,aria-owns,aria-placeholder,aria-posinset,aria-pressed,aria-readonly,aria-relevant,aria-required,aria-roledescription,aria-rowcount,aria-rowindex,aria-rowspan,aria-selected,aria-setsize,aria-sort,aria-valuemax,aria-valuemin,aria-valuenow,aria-valuetext"),wf=Pt(Al,yf,bf),_f=de("script,style,template"),Cf=class{sanitizedSomething=!1;buf=[];sanitizeChildren(e){let t=e.firstChild,n=!0,r=[];for(;t;){if(t.nodeType===Node.ELEMENT_NODE?n=this.startElement(t):t.nodeType===Node.TEXT_NODE?this.chars(t.nodeValue):this.sanitizedSomething=!0,n&&t.firstChild){r.push(t),t=Sf(t);continue}for(;t;){t.nodeType===Node.ELEMENT_NODE&&this.endElement(t);let i=kf(t);if(i){t=i;break}t=r.pop()}}return this.buf.join("")}startElement(e){let t=go(e).toLowerCase();if(!po.hasOwnProperty(t))return this.sanitizedSomething=!0,!_f.hasOwnProperty(t);this.buf.push("<"),this.buf.push(t);let n=e.attributes;for(let r=0;r"),!0}endElement(e){let t=go(e).toLowerCase();po.hasOwnProperty(t)&&!Ml.hasOwnProperty(t)&&(this.buf.push(""))}chars(e){this.buf.push(mo(e))}};function xf(e,t){return(e.compareDocumentPosition(t)&Node.DOCUMENT_POSITION_CONTAINED_BY)!==Node.DOCUMENT_POSITION_CONTAINED_BY}function kf(e){let t=e.nextSibling;if(t&&e!==t.previousSibling)throw Vl(t);return t}function Sf(e){let t=e.firstChild;if(t&&xf(e,t))throw Vl(t);return t}function go(e){let t=e.nodeName;return typeof t=="string"?t:"FORM"}function Vl(e){return new Error(`Failed to sanitize html because the element is clobbered: ${e.outerHTML}`)}var Ef=/[\uD800-\uDBFF][\uDC00-\uDFFF]/g,If=/([^\#-~ |!])/g;function mo(e){return e.replace(/&/g,"&").replace(Ef,function(t){let n=t.charCodeAt(0),r=t.charCodeAt(1);return"&#"+((n-55296)*1024+(r-56320)+65536)+";"}).replace(If,function(t){return"&#"+t.charCodeAt(0)+";"}).replace(//g,">")}var Lt;function Rl(e,t){let n=null;try{Lt=Lt||cf(e);let r=t?String(t):"";n=Lt.getInertBodyElement(r);let i=5,o=r;do{if(i===0)throw new Error("Failed to sanitize html because the input is unstable");i--,r=o,o=n.innerHTML,n=Lt.getInertBodyElement(r)}while(r!==o);let s=new Cf().sanitizeChildren(vo(n)||n);return Tn(s)}finally{if(n){let r=vo(n)||n;for(;r.firstChild;)r.firstChild.remove()}}}function vo(e){return"content"in e&&Tf(e)?e.content:null}function Tf(e){return e.nodeType===Node.ELEMENT_NODE&&e.nodeName==="TEMPLATE"}function Of(e,t){return e.createText(t)}function Df(e,t,n){e.setValue(t,n)}function Ll(e,t,n){return e.createElement(t,n)}function cn(e,t,n,r,i){e.insertBefore(t,n,r,i)}function jl(e,t,n){e.appendChild(t,n)}function yo(e,t,n,r,i){r!==null?cn(e,t,n,r,i):jl(e,t,n)}function Fl(e,t,n,r){e.removeChild(null,t,n,r)}function Mf(e,t,n){e.setAttribute(t,"style",n)}function Pf(e,t,n){n===""?e.removeAttribute(t,"class"):e.setAttribute(t,"class",n)}function Hl(e,t,n){let{mergedAttrs:r,classes:i,styles:o}=n;r!==null&&Cd(e,t,r),i!==null&&Pf(e,t,i),o!==null&&Mf(e,t,o)}var he=function(e){return e[e.NONE=0]="NONE",e[e.HTML=1]="HTML",e[e.STYLE=2]="STYLE",e[e.SCRIPT=3]="SCRIPT",e[e.URL=4]="URL",e[e.RESOURCE_URL=5]="RESOURCE_URL",e}(he||{});function Nf(e){let t=Af();return t?ho(t.sanitize(he.HTML,e)||""):Ze(e,"HTML")?ho(Ce(e)):Rl(Hd(),ps(e))}function Af(){let e=S();return e&&e[ie].sanitizer}var Vf="ng-template";function Rf(e){return e.type===4&&e.value!==Vf}function Sr(e){return(e&1)===0}function bo(e,t){return e?":not("+t.trim()+")":t}function Lf(e){let t=e[0],n=1,r=2,i="",o=!1;for(;n0?'="'+l+'"':"")+"]"}else r&8?i+="."+s:r&4&&(i+=" "+s);else i!==""&&!Sr(s)&&(t+=bo(o,i),i=""),r=s,o=o||!Sr(r);n++}return i!==""&&(t+=bo(o,i)),t}function jf(e){return e.map(Lf).join(",")}function Ff(e){let t=[],n=[],r=1,i=2;for(;r=0;o--){let s=n[o],l=s.parentNode;s===t?(n.splice(o,1),ct.add(s),s.dispatchEvent(new CustomEvent("animationend",{detail:{cancel:!0}}))):(i&&s===i||l&&r&&l!==r)&&(n.splice(o,1),s.dispatchEvent(new CustomEvent("animationend",{detail:{cancel:!0}})),s.parentNode?.removeChild(s))}}function Zf(e,t){let n=Ir.get(e);n?n.includes(t)||n.push(t):Ir.set(e,[t])}var _t=new Set,Ul=function(e){return e[e.CHANGE_DETECTION=0]="CHANGE_DETECTION",e[e.AFTER_NEXT_RENDER=1]="AFTER_NEXT_RENDER",e}(Ul||{}),Dn=new E(""),wo=new Set;function st(e){wo.has(e)||(wo.add(e),performance?.mark?.("mark_feature_usage",{detail:{feature:e}}))}var $f=(()=>{class e{impl=null;execute(){this.impl?.execute()}static \u0275prov=D({token:e,providedIn:"root",factory:()=>new e})}return e})(),Zl=new E("",{factory:()=>({queue:new Set,isScheduled:!1,scheduler:null,injector:b(ve)})});function $l(e,t,n){let r=e.get(Zl);if(Array.isArray(t))for(let i of t)r.queue.add(i),n?.detachedLeaveAnimationFns?.push(i);else r.queue.add(t),n?.detachedLeaveAnimationFns?.push(t);r.scheduler&&r.scheduler(e)}function Qf(e,t){let n=e.get(Zl);if(t.detachedLeaveAnimationFns){for(let r of t.detachedLeaveAnimationFns)n.queue.delete(r);t.detachedLeaveAnimationFns=void 0}}function Wf(e,t){for(let[n,r]of t)$l(e,r.animateFns)}function _o(e,t,n,r){let i=e?.[Me]?.enter;t!==null&&i&&i.has(n.index)&&Wf(r,i)}function We(e,t,n,r,i,o,s,l){if(i!=null){let a,u=!1;ae(i)?a=i:Ie(i)&&(u=!0,i=i[le]);let c=X(i);e===0&&r!==null?(_o(l,r,o,n),s==null?jl(t,r,c):cn(t,r,c,s||null,!0)):e===1&&r!==null?(_o(l,r,o,n),cn(t,r,c,s||null,!0),Uf(o,c)):e===2?(l?.[Me]?.leave?.has(o.index)&&Zf(o,c),ct.delete(c),Co(l,o,n,d=>{if(ct.has(c)){ct.delete(c);return}Fl(t,c,u,d)})):e===3&&(ct.delete(c),Co(l,o,n,()=>{t.destroyNode(c)})),a!=null&&lh(t,e,n,a,o,r,s)}}function Gf(e,t){Ql(e,t),t[le]=null,t[W]=null}function Yf(e,t,n,r,i,o){r[le]=i,r[W]=t,Pn(e,r,n,1,i,o)}function Ql(e,t){t[ie].changeDetectionScheduler?.notify(9),Pn(e,t,t[V],2,null,null)}function Kf(e){let t=e[bt];if(!t)return Gn(e[g],e);for(;t;){let n=null;if(Ie(t))n=t[bt];else{let r=t[T];r&&(n=r)}if(!n){for(;t&&!t[Z]&&t!==e;)Ie(t)&&Gn(t[g],t),t=t[A];t===null&&(t=e),Ie(t)&&Gn(t[g],t),n=t&&t[Z]}t=n}}function Ei(e,t){let n=e[Xe],r=n.indexOf(t);n.splice(r,1)}function Mn(e,t){if(it(t))return;let n=t[V];n.destroyNode&&Pn(e,t,n,3,null,null),Kf(t)}function Gn(e,t){if(it(t))return;let n=m(null);try{t[v]&=-129,t[v]|=256,t[q]&&yn(t[q]),eh(e,t),Jf(e,t),t[g].type===1&&t[V].destroy();let r=t[Oe];if(r!==null&&ae(t[A])){r!==t[A]&&Ei(r,t);let i=t[oe];i!==null&&i.detachView(e)}Cr(t)}finally{m(n)}}function Co(e,t,n,r){let i=e?.[Me];if(i==null||i.leave==null||!i.leave.has(t.index))return r(!1);e&&_t.add(e[be]),$l(n,()=>{if(i.leave&&i.leave.has(t.index)){let o=i.leave.get(t.index),s=[];if(o){for(let l=0;l{e[Me].running=void 0,_t.delete(e[be]),t(!0)});return}t(!1)}function Jf(e,t){let n=e.cleanup,r=t[Jt];if(n!==null)for(let s=0;s=0?r[l]():r[-l].unsubscribe(),s+=2}else{let l=r[n[s+1]];n[s].call(l)}r!==null&&(t[Jt]=null);let i=t[ge];if(i!==null){t[ge]=null;for(let s=0;sH&&ql(e,t,H,!1);let l=s?_.TemplateUpdateStart:_.TemplateCreateStart;x(l,i,n),n(r,i)}finally{Ve(o);let l=s?_.TemplateUpdateEnd:_.TemplateCreateEnd;x(l,i,n)}}function uh(e,t,n){ph(e,t,n),(n.flags&64)===64&&gh(e,t,n)}function Yl(e,t,n=ue){let r=t.localNames;if(r!==null){let i=t.index+1;for(let o=0;onull;function hh(e,t,n,r,i,o){if(e.type&3){let s=ue(e,t);r=o!=null?o(r,e.value||"",n):r,i.setProperty(s,n,r)}else e.type&12}function ph(e,t,n){let r=n.directiveStart,i=n.directiveEnd;wn(n)&&Bf(t,n,e.data[r+n.componentOffset]),e.firstCreatePass||un(n,t);let o=n.initialInputs;for(let s=r;s{Tt(e.lView)},consumerOnSignalRead(){this.lView[q]=this}});function Dh(e){let t=e[q]??Object.create(Mh);return t.lView=e,t}var Mh=Q($({},St),{consumerIsAlwaysLive:!0,kind:"template",consumerMarkedDirty:e=>{let t=Ae(e.lView);for(;t&&!Jl(t[g]);)t=Ae(t);t&&Vs(t)},consumerOnSignalRead(){this.lView[q]=this}});function Jl(e){return e.type!==2}function ea(e){if(e[De]===null)return;let t=!0;for(;t;){let n=!1;for(let r of e[De])r.dirty&&(n=!0,r.zone===null||Zone.current===r.zone?r.run():r.zone.run(()=>r.run()));t=n&&!!(e[v]&8192)}}var Ph=100;function ta(e,t=0){let n=e[ie].rendererFactory,r=!1;r||n.begin?.();try{Nh(e,t)}finally{r||n.end?.()}}function Nh(e,t){let n=zs();try{rn(!0),Or(e,t);let r=0;for(;nn(e);){if(r===Ph)throw new w(103,!1);r++,Or(e,1)}}finally{rn(n)}}function Ah(e,t,n,r){if(it(t))return;let i=t[v],o=!1,s=!1;hi(t);let l=!0,a=null,u=null;o||(Jl(e)?(u=Eh(t),a=Gt(u)):Wa()===null?(l=!1,u=Dh(t),a=Gt(u)):t[q]&&(yn(t[q]),t[q]=null));try{As(t),Mc(e.bindingStartIndex),n!==null&&Gl(e,t,n,2,r);let c=(i&3)===3;if(!o)if(c){let f=e.preOrderCheckHooks;f!==null&&qt(t,f,null)}else{let f=e.preOrderHooks;f!==null&&Ut(t,f,0,null),Qn(t,0)}if(s||Vh(t),ea(t),na(t,0),e.contentQueries!==null&&Ol(e,t),!o)if(c){let f=e.contentCheckHooks;f!==null&&qt(t,f)}else{let f=e.contentHooks;f!==null&&Ut(t,f,1),Qn(t,1)}Lh(e,t);let d=e.components;d!==null&&ia(t,d,0);let h=e.viewQuery;if(h!==null&&kr(2,h,r),!o)if(c){let f=e.viewCheckHooks;f!==null&&qt(t,f)}else{let f=e.viewHooks;f!==null&&Ut(t,f,2),Qn(t,2)}if(e.firstUpdatePass===!0&&(e.firstUpdatePass=!1),t[Un]){for(let f of t[Un])f();t[Un]=null}o||(Kl(t),t[v]&=-73)}catch(c){throw o||Tt(t),c}finally{u!==null&&(Gr(u,a),l&&Th(u)),pi()}}function na(e,t){for(let n=bl(e);n!==null;n=wl(n))for(let r=T;r0&&(e[n-1][Z]=r[Z]);let o=Xt(e,T+t);Gf(r[g],r);let s=o[oe];s!==null&&s.detachView(o[g]),r[A]=null,r[Z]=null,r[v]&=-129}return r}function jh(e,t,n,r){let i=T+r,o=n.length;r>0&&(n[i-1][Z]=t),r-1&&(xt(e,n),Xt(t,n))}this._attachedToViewContainer=!1}Mn(this._lView[g],this._lView)}onDestroy(e){Rs(this._lView,e)}markForCheck(){Di(this._cdRefInjectingView||this._lView,4)}detach(){this._lView[v]&=-129}reattach(){gr(this._lView),this._lView[v]|=128}detectChanges(){this._lView[v]|=1024,ta(this._lView)}checkNoChanges(){}attachToViewContainerRef(){if(this._appRef)throw new w(902,!1);this._attachedToViewContainer=!0}detachFromAppRef(){this._appRef=null;let e=tn(this._lView),t=this._lView[Oe];t!==null&&!e&&Ei(t,this._lView),Ql(this._lView[g],this._lView)}attachToAppRef(e){if(this._attachedToViewContainer)throw new w(902,!1);this._appRef=e;let t=tn(this._lView),n=this._lView[Oe];n!==null&&!t&&aa(n,this._lView),gr(this._lView)}},fn=(()=>{class e{_declarationLView;_declarationTContainer;elementRef;static __NG_ELEMENT_ID__=Fh;constructor(n,r,i){this._declarationLView=n,this._declarationTContainer=r,this.elementRef=i}get ssrId(){return this._declarationTContainer.tView?.ssrId||null}createEmbeddedView(n,r){return this.createEmbeddedViewImpl(n,r)}createEmbeddedViewImpl(n,r,i){let o=Nn(this._declarationLView,this._declarationTContainer,n,{embeddedViewInjector:r,dehydratedView:i});return new Mi(o)}}return e})();function Fh(){return Pi(ce(),S())}function Pi(e,t){return e.type&4?new fn(t,e,ot(e,t)):null}function Vn(e,t,n,r,i){let o=e.data[t];if(o===null)o=Hh(e,t,n,r,i),Nc()&&(o.flags|=32);else if(o.type&64){o.type=n,o.value=r,o.attrs=i;let s=Oc();o.injectorIndex=s===null?-1:s.injectorIndex}return Ot(o,!0),o}function Hh(e,t,n,r,i){let o=Fs(),s=Hs(),l=s?o:o&&o.parent,a=e.data[t]=Bh(e,l,n,t,r,i);return zh(e,a,o,s),a}function zh(e,t,n,r){e.firstChild===null&&(e.firstChild=t),n!==null&&(r?n.child==null&&t.parent!==null&&(n.child=t):n.next===null&&(n.next=t,t.prev=n))}function Bh(e,t,n,r,i,o){let s=t?t.injectorIndex:-1,l=0;return Ec()&&(l|=128),{type:n,index:r,insertBeforeIndex:null,injectorIndex:s,directiveStart:-1,directiveEnd:-1,directiveStylingLast:-1,componentOffset:-1,controlDirectiveIndex:-1,customControlIndex:-1,propertyBindings:null,flags:l,providerIndexes:0,value:i,attrs:o,mergedAttrs:null,localNames:null,initialInputs:null,inputs:null,hostDirectiveInputs:null,outputs:null,hostDirectiveOutputs:null,directiveToIndex:null,tView:null,next:null,prev:null,projectionNext:null,child:null,parent:t,projection:null,styles:null,stylesWithoutHost:null,residualStyles:void 0,classes:null,classesWithoutHost:null,residualClasses:void 0,classBindings:0,styleBindings:0}}function qh(e){let t=e[eo]??[],n=e[A][V],r=[];for(let i of t)i.data[qd]!==void 0?r.push(i):Uh(i,n);e[eo]=r}function Uh(e,t){let n=0,r=e.firstChild;if(r){let i=e.data[Bd];for(;nnull,$h=()=>null;function Dr(e,t){return Zh(e,t)}function ua(e,t,n){return $h(e,t,n)}var Qh=class{},ca=class{},Wh=class{resolveComponentFactory(e){throw new w(917,!1)}},Ni=class{static NULL=new Wh},Ai=class{},Gh=(()=>{class e{static \u0275prov=D({token:e,providedIn:"root",factory:()=>null})}return e})(),Yn={},Yh=class{injector;parentInjector;constructor(e,t){this.injector=e,this.parentInjector=t}get(e,t,n){let r=this.injector.get(e,Yn,n);return r!==Yn||t===Yn?r:this.parentInjector.get(e,t,n)}};function hn(e,t,n){let r=n?e.styles:null,i=n?e.classes:null,o=0;if(t!==null)for(let s=0;s0&&(n.directiveToIndex=new Map);for(let h=0;h0;){let n=e[--t];if(typeof n=="number"&&n<0)return n}return 0}function op(e,t,n){if(n){if(t.exportAs)for(let r=0;rr(X(M[e.index])):e.index;pp(p,t,n,o,l,f,!1)}}return u}function fp(e){return e.startsWith("animation")||e.startsWith("transition")}function hp(e,t,n,r){let i=e.cleanup;if(i!=null)for(let o=0;oa?l[a]:null}typeof s=="string"&&(o+=2)}return null}function pp(e,t,n,r,i,o,s){let l=t.firstCreatePass?js(t):null,a=Ls(n),u=a.length;a.push(i,o),l&&l.push(r,e,u,(u+1)*(s?-1:1))}var Mr=Symbol("BINDING");function gp(e){return e.debugInfo?.className||e.type.name||null}var mp=class extends Ni{ngModule;constructor(e){super(),this.ngModule=e}resolveComponentFactory(e){let t=gt(e);return new fa(t,this.ngModule)}};function vp(e){return Object.keys(e).map(t=>{let[n,r,i]=e[t],o={propName:n,templateName:t,isSignal:(r&On.SignalBased)!==0};return i&&(o.transform=i),o})}function yp(e){return Object.keys(e).map(t=>({propName:e[t],templateName:t}))}function bp(e,t,n){let r=t instanceof ve?t:t?.injector;return r&&e.getStandaloneInjector!==null&&(r=e.getStandaloneInjector(r)||r),r?new Yh(n,r):n}function wp(e){let t=e.get(Ai,null);if(t===null)throw new w(407,!1);let n=e.get(Gh,null),r=e.get(bi,null),i=e.get(Dn,null,{optional:!0});return{rendererFactory:t,sanitizer:n,changeDetectionScheduler:r,ngReflect:!1,tracingService:i}}function _p(e,t){let n=Cp(e);return Ll(t,n,n==="svg"?mc:n==="math"?vc:null)}function Cp(e){return(e.selectors[0][0]||"div").toLowerCase()}var fa=class extends ca{componentDef;ngModule;selector;componentType;ngContentSelectors;isBoundToModule;cachedInputs=null;cachedOutputs=null;get inputs(){return this.cachedInputs??=vp(this.componentDef.inputs),this.cachedInputs}get outputs(){return this.cachedOutputs??=yp(this.componentDef.outputs),this.cachedOutputs}constructor(e,t){super(),this.componentDef=e,this.ngModule=t,this.componentType=e.type,this.selector=jf(e.selectors),this.ngContentSelectors=e.ngContentSelectors??[],this.isBoundToModule=!!t}create(e,t,n,r,i,o){x(_.DynamicComponentStart);let s=m(null);try{let l=this.componentDef,a=bp(l,r||this.ngModule,e),u=wp(a),c=u.tracingService;return c&&c.componentCreate?c.componentCreate(gp(l),()=>this.createComponentRef(u,a,t,n,i,o)):this.createComponentRef(u,a,t,n,i,o)}finally{m(s)}}createComponentRef(e,t,n,r,i,o){let s=this.componentDef,l=xp(r,s,o,i),a=e.rendererFactory.createRenderer(null,s),u=r?ch(a,r,s.encapsulation,t):_p(s,a),c=o?.some(To)||i?.some(f=>typeof f!="function"&&f.bindings.some(To)),d=xi(null,l,null,512|zl(s),null,null,e,a,t,null,Tl(u,t,!0));d[H]=u,hi(d);let h=null;try{let f=lp(H,d,2,"#host",()=>l.directiveRegistry,!0,0);Hl(a,u,f),Je(u,d),uh(l,d,f),Gd(l,f,d),ap(l,f),n!==void 0&&Ep(f,this.ngContentSelectors,n),h=Ne(f.index,d),d[O]=h[O],Oi(l,d,null)}catch(f){throw h!==null&&Cr(h),Cr(d),f}finally{x(_.DynamicComponentEnd),pi()}return new Sp(this.componentType,d,!!c)}};function xp(e,t,n,r){let i=e?["ng-version","21.2.11"]:Ff(t.selectors[0]),o=null,s=null,l=0;if(n)for(let u of n)l+=u[Mr].requiredVars,u.create&&(u.targetIdx=0,(o??=[]).push(u)),u.update&&(u.targetIdx=0,(s??=[]).push(u));if(r)for(let u=0;u{if(n&1&&e)for(let r of e)r.create();if(n&2&&t)for(let r of t)r.update()}}function To(e){let t=e[Mr].kind;return t==="input"||t==="twoWay"}var Sp=class extends Qh{_rootLView;_hasInputBindings;instance;hostView;changeDetectorRef;componentType;location;previousInputValues=null;_tNode;constructor(e,t,n){super(),this._rootLView=t,this._hasInputBindings=n,this._tNode=ci(t[g],H),this.location=ot(this._tNode,t),this.instance=Ne(this._tNode.index,t)[O],this.hostView=this.changeDetectorRef=new Mi(t,void 0),this.componentType=e}setInput(e,t){this._hasInputBindings;let n=this._tNode;if(this.previousInputValues??=new Map,this.previousInputValues.has(e)&&Object.is(this.previousInputValues.get(e),t))return;let r=this._rootLView,i=_h(n,r[g],r,e,t);this.previousInputValues.set(e,t);let o=Ne(n.index,r);Di(o,1)}get injector(){return new pt(this._tNode,this._rootLView)}destroy(){this.hostView.destroy()}onDestroy(e){this.hostView.onDestroy(e)}};function Ep(e,t,n){let r=e.projection=[];for(let i=0;i{class e{static __NG_ELEMENT_ID__=Ip}return e})();function Ip(){let e=ce();return pa(e,S())}var Tp=class ha extends Ri{_lContainer;_hostTNode;_hostLView;constructor(t,n,r){super(),this._lContainer=t,this._hostTNode=n,this._hostLView=r}get element(){return ot(this._hostTNode,this._hostLView)}get injector(){return new pt(this._hostTNode,this._hostLView)}get parentInjector(){let t=_i(this._hostTNode,this._hostLView);if(ll(t)){let n=an(t,this._hostLView),r=ln(t),i=n[g].data[r+8];return new pt(i,n)}else return new pt(null,this._hostLView)}clear(){for(;this.length>0;)this.remove(this.length-1)}get(t){let n=Oo(this._lContainer);return n!==null&&n[t]||null}get length(){return this._lContainer.length-T}createEmbeddedView(t,n,r){let i,o;typeof r=="number"?i=r:r!=null&&(i=r.index,o=r.injector);let s=Dr(this._lContainer,t.ssrId),l=t.createEmbeddedViewImpl(n||{},o,s);return this.insertImpl(l,i,Ct(this._hostTNode,s)),l}createComponent(t,n,r,i,o,s,l){let a=t&&!hd(t),u;if(a)u=n;else{let I=n||{};u=I.index,r=I.injector,i=I.projectableNodes,o=I.environmentInjector||I.ngModuleRef,s=I.directives,l=I.bindings}let c=a?t:new fa(gt(t)),d=r||this.parentInjector;if(!o&&c.ngModule==null){let I=(a?d:this.parentInjector).get(ve,null);I&&(o=I)}let h=gt(c.componentType??{}),f=Dr(this._lContainer,h?.id??null),p=f?.firstChild??null,M=c.create(d,i,p,o,s,l);return this.insertImpl(M.hostView,u,Ct(this._hostTNode,f)),M}insert(t,n){return this.insertImpl(t,n,!0)}insertImpl(t,n,r){let i=t._lView;if(bc(i)){let l=this.indexOf(t);if(l!==-1)this.detach(l);else{let a=i[A],u=new ha(a,a[W],a[A]);u.detach(u.indexOf(t))}}let o=this._adjustIndex(n),s=this._lContainer;return An(s,i,o,r),t.attachToViewContainerRef(),ws(Kn(s),o,t),t}move(t,n){return this.insert(t,n)}indexOf(t){let n=Oo(this._lContainer);return n!==null?n.indexOf(t):-1}remove(t){let n=this._adjustIndex(t,-1),r=xt(this._lContainer,n);r&&(Xt(Kn(this._lContainer),n),Mn(r[g],r))}detach(t){let n=this._adjustIndex(t,-1),r=xt(this._lContainer,n);return r&&Xt(Kn(this._lContainer),n)!=null?new Mi(r):null}_adjustIndex(t,n=0){return t??this.length+n}};function Oo(e){return e[en]}function Kn(e){return e[en]||(e[en]=[])}function pa(e,t){let n,r=t[e.index];return ae(r)?n=r:(n=oa(r,t,null,e),t[e.index]=n,ki(t,n)),Dp(n,t,e,r),new Tp(n,e,t)}function Op(e,t){let n=e[V],r=n.createComment(""),i=ue(t,e),o=n.parentNode(i);return cn(n,o,r,n.nextSibling(i),!1),r}var Dp=Np,Mp=()=>!1;function Pp(e,t,n){return Mp(e,t,n)}function Np(e,t,n,r){if(e[Pe])return;let i;n.type&8?i=X(r):i=Op(t,n),e[Pe]=i}var Ap=class ga{queryList;matches=null;constructor(t){this.queryList=t}clone(){return new ga(this.queryList)}setDirty(){this.queryList.setDirty()}},Vp=class ma{queries;constructor(t=[]){this.queries=t}createEmbeddedView(t){let n=t.queries;if(n!==null){let r=t.contentQueries!==null?t.contentQueries[0]:n.length,i=[];for(let o=0;o0)r.push(s[l/2]);else{let u=o[l+1],c=t[-a];for(let d=T;dt.trim())}function Qp(e,t,n){e.queries===null&&(e.queries=new Lp),e.queries.track(new jp(t,n))}function Li(e,t){return e.queries.getByIndex(t)}function Wp(e,t){let n=e[g],r=Li(n,t);return r.crossesNgTemplate?Pr(n,e,t,[]):ba(n,e,r,t)}var Nr=class{},wa=class extends Nr{injector;componentFactoryResolver=new mp(this);instance=null;constructor(e){super();let t=new ui([...e.providers,{provide:Nr,useValue:this},{provide:Ni,useValue:this.componentFactoryResolver}],e.parent||ai(),e.debugName,new Set(["environment"]));this.injector=t,e.runEnvironmentInitializers&&t.resolveInjectorInitializers()}destroy(){this.injector.destroy()}onDestroy(e){this.injector.onDestroy(e)}};function Gp(e,t,n=null){return new wa({providers:e,parent:t,debugName:n,runEnvironmentInitializers:!0}).injector}var Yp=(()=>{class e{_injector;cachedInjectors=new Map;constructor(n){this._injector=n}getOrCreateStandaloneInjector(n){if(!n.standalone)return null;if(!this.cachedInjectors.has(n)){let r=ks(!1,n.type),i=r.length>0?Gp([r],this._injector,""):null;this.cachedInjectors.set(n,i)}return this.cachedInjectors.get(n)}ngOnDestroy(){try{for(let n of this.cachedInjectors.values())n!==null&&n.destroy()}finally{this.cachedInjectors.clear()}}static \u0275prov=D({token:e,providedIn:"environment",factory:()=>new e(C(ve))})}return e})();function Kp(e){return fd(()=>{let t=tg(e),n=Q($({},t),{decls:e.decls,vars:e.vars,template:e.template,consts:e.consts||null,ngContentSelectors:e.ngContentSelectors,onPush:e.changeDetection===vl.OnPush,directiveDefs:null,pipeDefs:null,dependencies:t.standalone&&e.dependencies||null,getStandaloneInjector:t.standalone?i=>i.get(Yp).getOrCreateStandaloneInjector(n):null,getExternalStyles:null,signals:e.signals??!1,data:e.data||{},encapsulation:e.encapsulation||re.Emulated,styles:e.styles||Te,_:null,schemas:e.schemas||null,tView:null,id:""});t.standalone&&st("NgStandalone"),ng(n);let r=e.dependencies;return n.directiveDefs=Do(r,Xp),n.pipeDefs=Do(r,qu),n.id=rg(n),n})}function Xp(e){return gt(e)||hs(e)}function Jp(e,t){if(e==null)return Ye;let n={};for(let r in e)if(e.hasOwnProperty(r)){let i=e[r],o,s,l,a;Array.isArray(i)?(l=i[0],o=i[1],s=i[2]??o,a=i[3]||null):(o=i,s=i,l=On.None,a=null),n[o]=[r,l,a],t[o]=s}return n}function eg(e){if(e==null)return Ye;let t={};for(let n in e)e.hasOwnProperty(n)&&(t[e[n]]=n);return t}function tg(e){let t={};return{type:e.type,providersResolver:null,viewProvidersResolver:null,factory:null,hostBindings:e.hostBindings||null,hostVars:e.hostVars||0,hostAttrs:e.hostAttrs||null,contentQueries:e.contentQueries||null,declaredInputs:t,inputConfig:e.inputs||Ye,exportAs:e.exportAs||null,standalone:e.standalone??!0,signals:e.signals===!0,selectors:e.selectors||Te,viewQuery:e.viewQuery||null,features:e.features||null,setInput:null,resolveHostDirectives:null,hostDirectives:null,controlDef:null,inputs:Jp(e.inputs,t),outputs:eg(e.outputs),debugInfo:null}}function ng(e){e.features?.forEach(t=>t(e))}function Do(e,t){return e?()=>{let n=typeof e=="function"?e():e,r=[];for(let i of n){let o=t(i);o!==null&&r.push(o)}return r}:null}function rg(e){let t=0,n=typeof e.consts=="function"?"":e.consts,r=[e.selectors,e.ngContentSelectors,e.hostVars,e.hostAttrs,n,e.vars,e.decls,e.encapsulation,e.standalone,e.signals,e.exportAs,JSON.stringify(e.inputs),JSON.stringify(e.outputs),Object.getOwnPropertyNames(e.type.prototype),!!e.contentQueries,!!e.viewQuery];for(let i of r.join("|"))t=Math.imul(31,t)+i.charCodeAt(0)<<0;return t+=2147483648,"c"+t}function ig(e,t,n,r,i,o,s,l){if(n.firstCreatePass){e.mergedAttrs=En(e.mergedAttrs,e.attrs);let c=e.tView=Ci(2,e,i,o,s,n.directiveRegistry,n.pipeRegistry,null,n.schemas,n.consts,null);n.queries!==null&&(n.queries.template(n,e),c.queries=n.queries.embeddedTView(e))}l&&(e.flags|=l),Ot(e,!1);let a=og(n,t,e,r);gi()&&Ii(n,t,a,e),Je(a,t);let u=oa(a,t,a,e);t[r+H]=u,ki(t,u),Pp(u,e,t)}function pn(e,t,n,r,i,o,s,l,a,u,c){let d=n+H,h;if(t.firstCreatePass){if(h=Vn(t,d,4,s||null,l||null),u!=null){let f=se(t.consts,u);h.localNames=[];for(let p=0;p{class e{resolve;reject;initialized=!1;done=!1;donePromise=new Promise((n,r)=>{this.resolve=n,this.reject=r});appInits=b(ug,{optional:!0})??[];injector=b(Cn);constructor(){}runInitializers(){if(this.initialized)return;let n=[];for(let i of this.appInits){let o=Ts(this.injector,i);if(_a(o))n.push(o);else if(ag(o)){let s=new Promise((l,a)=>{o.subscribe({complete:l,error:a})});n.push(s)}}let r=()=>{this.done=!0,this.resolve()};Promise.all(n).then(()=>{r()}).catch(i=>{this.reject(i)}),n.length===0&&r(),this.initialized=!0}static \u0275fac=function(n){return new(n||e)};static \u0275prov=D({token:e,factory:e.\u0275fac,providedIn:"root"})}return e})(),cg=new E("");function dg(){ru(()=>{let e="";throw new w(600,e)})}function fg(e){return e.isBoundToModule}var hg=10,Ar=(()=>{class e{_runningTick=!1;_destroyed=!1;_destroyListeners=[];_views=[];internalErrorHandler=b(Dt);afterRenderManager=b($f);zonelessEnabled=b(wi);rootEffectScheduler=b(el);dirtyFlags=0;tracingSnapshot=null;allTestViews=new Set;autoDetectTestViews=new Set;includeAllTestViews=!1;afterTick=new Et;get allViews(){return[...(this.includeAllTestViews?this.allTestViews:this.autoDetectTestViews).keys(),...this._views]}get destroyed(){return this._destroyed}componentTypes=[];components=[];internalPendingTask=b(kn);get isStable(){return this.internalPendingTask.hasPendingTasksObservable.pipe(Pu(n=>!n))}constructor(){b(Dn,{optional:!0})}whenStable(){let n;return new Promise(r=>{n=this.isStable.subscribe({next:i=>{i&&r()}})}).finally(()=>{n.unsubscribe()})}_injector=b(ve);_rendererFactory=null;get injector(){return this._injector}bootstrap(n,r){return this.bootstrapImpl(n,r)}bootstrapImpl(n,r,i=Cn.NULL){return this._injector.get(He).run(()=>{x(_.BootstrapComponentStart);let o=n instanceof ca;if(!this._injector.get(Ca).done){let h="";throw new w(405,h)}let s;o?s=n:s=this._injector.get(Ni).resolveComponentFactory(n),this.componentTypes.push(s.componentType);let l=fg(s)?void 0:this._injector.get(Nr),a=r||s.selector,u=s.create(i,[],a,l),c=u.location.nativeElement,d=u.injector.get(lg,null);return d?.registerApplication(c),u.onDestroy(()=>{this.detachView(u.hostView),$t(this.components,u),d?.unregisterApplication(c)}),this._loadComponent(u),x(_.BootstrapComponentEnd,u),u})}tick(){this.zonelessEnabled||(this.dirtyFlags|=1),this._tick()}_tick(){x(_.ChangeDetectionStart),this.tracingSnapshot!==null?this.tracingSnapshot.run(Ul.CHANGE_DETECTION,this.tickImpl):this.tickImpl()}tickImpl=()=>{if(this._runningTick)throw x(_.ChangeDetectionEnd),new w(101,!1);let n=m(null);try{this._runningTick=!0,this.synchronize()}finally{this._runningTick=!1,this.tracingSnapshot?.dispose(),this.tracingSnapshot=null,m(n),this.afterTick.next(),x(_.ChangeDetectionEnd)}};synchronize(){this._rendererFactory===null&&!this._injector.destroyed&&(this._rendererFactory=this._injector.get(Ai,null,{optional:!0}));let n=0;for(;this.dirtyFlags!==0&&n++nn(n))){this.dirtyFlags|=2;return}else this.dirtyFlags&=-8}attachView(n){let r=n;this._views.push(r),r.attachToAppRef(this)}detachView(n){let r=n;$t(this._views,r),r.detachFromAppRef()}_loadComponent(n){this.attachView(n.hostView);try{this.tick()}catch(r){this.internalErrorHandler(r)}this.components.push(n),this._injector.get(cg,[]).forEach(r=>r(n))}ngOnDestroy(){if(!this._destroyed)try{this._destroyListeners.forEach(n=>n()),this._views.slice().forEach(n=>n.destroy())}finally{this._destroyed=!0,this._views=[],this._destroyListeners=[]}}onDestroy(n){return this._destroyListeners.push(n),()=>$t(this._destroyListeners,n)}destroy(){if(this._destroyed)throw new w(406,!1);let n=this._injector;n.destroy&&!n.destroyed&&n.destroy()}get viewCount(){return this._views.length}static \u0275fac=function(n){return new(n||e)};static \u0275prov=D({token:e,factory:e.\u0275fac,providedIn:"root"})}return e})();function $t(e,t){let n=e.indexOf(t);n>-1&&e.splice(n,1)}var pg=class{destroy(e){}updateValue(e,t){}swap(e,t){let n=Math.min(e,t),r=Math.max(e,t),i=this.detach(r);if(r-n>1){let o=this.detach(n);this.attach(n,i),this.attach(r,o)}else this.attach(n,i)}move(e,t){this.attach(t,this.detach(e))}};function Xn(e,t,n,r,i){return e===n&&Object.is(t,r)?1:Object.is(i(e,t),i(n,r))?-1:0}function gg(e,t,n,r){let i,o,s=0,l=e.length-1,a;if(Array.isArray(t)){m(r);let u=t.length-1;for(m(null);s<=l&&s<=u;){let c=e.at(s),d=t[s],h=Xn(s,c,s,d,n);if(h!==0){h<0&&e.updateValue(s,d),s++;continue}let f=e.at(l),p=t[u],M=Xn(l,f,u,p,n);if(M!==0){M<0&&e.updateValue(l,p),l--,u--;continue}let I=n(s,c),J=n(l,f),lt=n(s,d);if(Object.is(lt,J)){let Rn=n(u,p);Object.is(Rn,I)?(e.swap(s,l),e.updateValue(l,p),u--,l--):e.move(l,s),e.updateValue(s,d),s++;continue}if(i??=new No,o??=Po(e,s,l,n),Vr(e,i,s,lt))e.updateValue(s,d),s++,l++;else if(o.has(lt))i.set(I,e.detach(s)),l--;else{let Rn=e.create(s,t[s]);e.attach(s,Rn),s++,l++}}for(;s<=u;)Mo(e,i,n,s,t[s]),s++}else if(t!=null){m(r);let u=t[Symbol.iterator]();m(null);let c=u.next();for(;!c.done&&s<=l;){let d=e.at(s),h=c.value,f=Xn(s,d,s,h,n);if(f!==0)f<0&&e.updateValue(s,h),s++,c=u.next();else{i??=new No,o??=Po(e,s,l,n);let p=n(s,h);if(Vr(e,i,s,p))e.updateValue(s,h),s++,l++,c=u.next();else if(!o.has(p))e.attach(s,e.create(s,h)),s++,l++,c=u.next();else{let M=n(s,d);i.set(M,e.detach(s)),l--}}}for(;!c.done;)Mo(e,i,n,e.length,c.value),c=u.next()}for(;s<=l;)e.destroy(e.detach(l--));i?.forEach(u=>{e.destroy(u)})}function Vr(e,t,n,r){return t!==void 0&&t.has(r)?(e.attach(n,t.get(r)),t.delete(r),!0):!1}function Mo(e,t,n,r,i){if(Vr(e,t,r,n(r,i)))e.updateValue(r,i);else{let o=e.create(r,i);e.attach(r,o)}}function Po(e,t,n,r){let i=new Set;for(let o=t;o<=n;o++)i.add(r(o,e.at(o)));return i}var No=class{kvMap=new Map;_vMap=void 0;has(e){return this.kvMap.has(e)}delete(e){if(!this.has(e))return!1;let t=this.kvMap.get(e);return this._vMap!==void 0&&this._vMap.has(t)?(this.kvMap.set(e,this._vMap.get(t)),this._vMap.delete(t)):this.kvMap.delete(e),!0}get(e){return this.kvMap.get(e)}set(e,t){if(this.kvMap.has(e)){let n=this.kvMap.get(e);this._vMap===void 0&&(this._vMap=new Map);let r=this._vMap;for(;r.has(n);)n=r.get(n);r.set(n,t)}else this.kvMap.set(e,t)}forEach(e){for(let[t,n]of this.kvMap)if(e(n,t),this._vMap!==void 0){let r=this._vMap;for(;r.has(n);)n=r.get(n),e(n,t)}}};function Rr(e,t,n,r,i,o,s,l){st("NgControlFlow");let a=S(),u=U(),c=se(u.consts,o);return pn(a,u,e,t,n,r,i,c,256,s,l),xa}function xa(e,t,n,r,i,o,s,l){st("NgControlFlow");let a=S(),u=U(),c=se(u.consts,o);return pn(a,u,e,t,n,r,i,c,512,s,l),xa}function Lr(e,t){st("NgControlFlow");let n=S(),r=_n(),i=n[r]!==_e?n[r]:-1,o=i!==-1?gn(n,H+i):void 0,s=0;if(Nt(n,r,e)){let l=m(null);try{if(o!==void 0&&la(o,s),e!==-1){let a=H+e,u=gn(n,a),c=jr(n[g],a),d=ua(u,c,n),h=Nn(n,c,t,{dehydratedView:d});An(u,h,s,Ct(c,d))}}finally{m(l)}}else if(o!==void 0){let l=sa(o,s);l!==void 0&&(l[O]=t)}}var mg=class{lContainer;$implicit;$index;constructor(e,t,n){this.lContainer=e,this.$implicit=t,this.$index=n}get $count(){return this.lContainer.length-T}};function Ao(e,t){return t}var vg=class{hasEmptyBlock;trackByFn;liveCollection;constructor(e,t,n){this.hasEmptyBlock=e,this.trackByFn=t,this.liveCollection=n}};function Vo(e,t,n,r,i,o,s,l,a,u,c,d,h){st("NgControlFlow");let f=S(),p=U(),M=a!==void 0,I=S(),J=l?s.bind(I[K][O]):s,lt=new vg(M,J);I[H+e]=lt,pn(f,p,e+1,t,n,r,i,se(p.consts,o),256),M&&pn(f,p,e+2,a,u,c,d,se(p.consts,h),512)}var yg=class extends pg{lContainer;hostLView;templateTNode;operationsCounter=void 0;needsIndexUpdate=!1;constructor(e,t,n){super(),this.lContainer=e,this.hostLView=t,this.templateTNode=n}get length(){return this.lContainer.length-T}at(e){return this.getLView(e)[O].$implicit}attach(e,t){let n=t[yt];this.needsIndexUpdate||=e!==this.length,An(this.lContainer,t,e,Ct(this.templateTNode,n)),bg(this.lContainer,e)}detach(e){return this.needsIndexUpdate||=e!==this.length-1,wg(this.lContainer,e),_g(this.lContainer,e)}create(e,t){let n=Dr(this.lContainer,this.templateTNode.tView.ssrId);return Nn(this.hostLView,this.templateTNode,new mg(this.lContainer,t,e),{dehydratedView:n})}destroy(e){Mn(e[g],e)}updateValue(e,t){this.getLView(e)[O].$implicit=t}reset(){this.needsIndexUpdate=!1}updateIndexes(){if(this.needsIndexUpdate)for(let e=0;e0){let o=r[ye];Qf(o,i),_t.delete(r[be]),i.detachedLeaveAnimationFns=void 0}}function wg(e,t){if(e.length<=T)return;let n=T+t,r=e[n],i=r?r[Me]:void 0;i&&i.leave&&i.leave.size>0&&(i.detachedLeaveAnimationFns=[])}function _g(e,t){return xt(e,t)}function Cg(e,t){return sa(e,t)}function jr(e,t){return ci(e,t)}function P(e,t,n,r){let i=S(),o=i[g],s=e+H,l=o.firstCreatePass?up(s,o,2,t,n,r):o.data[s];return yh(l,i,e,t,xg),r!=null&&Yl(i,l),P}function N(){let e=ce(),t=bh(e);return Ic(t)&&Tc(),Sc(),N}function ka(e,t,n,r){return P(e,t,n,r),N(),ka}var xg=(e,t,n,r,i)=>(mi(!0),Ll(t[V],r,Hc()));function Sa(){return S()}function te(e,t,n){let r=S(),i=_n();if(Nt(r,i,t)){let o=U(),s=Fc();hh(s,r,e,t,r[V],n)}return te}var mn="en-US",kg=mn;function Sg(e){typeof e=="string"&&(kg=e.toLowerCase().replace(/_/g,"-"))}function xe(e,t,n){let r=S(),i=U(),o=ce();return(o.type&3||n)&&dp(o,i,r,n,r[V],e,t,cp(o,r,t)),xe}function ee(e=1){return jc(e)}function Ea(e,t,n){return Zp(e,t,n),Ea}function Eg(e){let t=S(),n=U(),r=Bs();fi(r+1);let i=Li(n,r);if(e.dirty&&yc(t)===((i.metadata.flags&2)===2)){if(i.matches===null)e.reset([]);else{let o=Wp(t,r);e.reset(o,Nd),e.notifyOnChanges()}return!0}return!1}function Ig(){return qp(S(),Bs())}function jt(e,t){return e<<17|t<<2}function Re(e){return e>>17&32767}function Tg(e){return(e&2)==2}function Og(e,t){return e&131071|t<<17}function Fr(e){return e|2}function et(e){return(e&131068)>>2}function Jn(e,t){return e&-131069|t<<2}function Dg(e){return(e&1)===1}function Hr(e){return e|1}function Mg(e,t,n,r,i,o){let s=o?t.classBindings:t.styleBindings,l=Re(s),a=et(s);e[r]=n;let u=!1,c;if(Array.isArray(n)){let d=n;c=d[1],(c===null||It(d,c)>0)&&(u=!0)}else c=n;if(i)if(a!==0){let d=Re(e[l+1]);e[r+1]=jt(d,l),d!==0&&(e[d+1]=Jn(e[d+1],r)),e[l+1]=Og(e[l+1],r)}else e[r+1]=jt(l,0),l!==0&&(e[l+1]=Jn(e[l+1],r)),l=r;else e[r+1]=jt(a,0),l===0?l=r:e[a+1]=Jn(e[a+1],r),a=r;u&&(e[r+1]=Fr(e[r+1])),Lo(e,c,r,!0),Lo(e,c,r,!1),Pg(t,c,e,r,o),s=jt(l,a),o?t.classBindings=s:t.styleBindings=s}function Pg(e,t,n,r,i){let o=i?e.residualClasses:e.residualStyles;o!=null&&typeof t=="string"&&It(o,t)>=0&&(n[r+1]=Hr(n[r+1]))}function Lo(e,t,n,r){let i=e[n+1],o=t===null,s=r?Re(i):et(i),l=!1;for(;s!==0&&(l===!1||o);){let a=e[s],u=e[s+1];Ng(a,t)&&(l=!0,e[s+1]=r?Hr(u):Fr(u)),s=r?Re(u):et(u)}l&&(e[n+1]=r?Fr(i):Hr(i))}function Ng(e,t){return e===null||t==null||(Array.isArray(e)?e[1]:e)===t?!0:Array.isArray(e)&&typeof t=="string"?It(e,t)>=0:!1}function Ia(e,t){return Ag(e,t,null,!0),Ia}function Ag(e,t,n,r){let i=S(),o=U(),s=Pc(2);if(o.firstUpdatePass&&Rg(o,e,s,r),t!==_e&&Nt(i,s,t)){let l=o.data[Fe()];zg(o,l,i,i[V],e,i[s+1]=Bg(t,n),r,s)}}function Vg(e,t){return t>=e.expandoStartIndex}function Rg(e,t,n,r){let i=e.data;if(i[n+1]===null){let o=i[Fe()],s=Vg(e,n);qg(o,r)&&t===null&&!s&&(t=!1),t=Lg(i,o,t,r),Mg(i,o,t,n,s,r)}}function Lg(e,t,n,r){let i=Rc(e),o=r?t.residualClasses:t.residualStyles;if(i===null)(r?t.classBindings:t.styleBindings)===0&&(n=er(null,e,t,n,r),n=kt(n,t.attrs,r),o=null);else{let s=t.directiveStylingLast;if(s===-1||e[s]!==i)if(n=er(i,e,t,n,r),o===null){let l=jg(e,t,r);l!==void 0&&Array.isArray(l)&&(l=er(null,e,t,l[1],r),l=kt(l,t.attrs,r),Fg(e,t,r,l))}else o=Hg(e,t,r)}return o!==void 0&&(r?t.residualClasses=o:t.residualStyles=o),n}function jg(e,t,n){let r=n?t.classBindings:t.styleBindings;if(et(r)!==0)return e[Re(r)]}function Fg(e,t,n,r){let i=n?t.classBindings:t.styleBindings;e[Re(i)]=r}function Hg(e,t,n){let r,i=t.directiveEnd;for(let o=1+t.directiveStylingLast;o0;){let a=e[i],u=Array.isArray(a),c=u?a[1]:a,d=c===null,h=n[i+1];h===_e&&(h=d?Te:void 0);let f=d?Bn(h,r):c===r?h:void 0;if(u&&!vn(f)&&(f=Bn(a,r)),vn(f)&&(l=f,s))return l;let p=e[i+1];i=s?Re(p):et(p)}if(t!==null){let a=o?t.residualClasses:t.residualStyles;a!=null&&(l=Bn(a,r))}return l}function vn(e){return e!==void 0}function Bg(e,t){return e==null||e===""||(typeof t=="string"?e=e+t:typeof e=="object"&&(e=us(Ce(e)))),e}function qg(e,t){return(e.flags&(t?8:16))!==0}function B(e,t=""){let n=S(),r=U(),i=e+H,o=r.firstCreatePass?Vn(r,i,1,t,null):r.data[i],s=Ug(r,n,o,t);n[i]=s,gi()&&Ii(r,n,s,o),Ot(o,!1)}var Ug=(e,t,n,r)=>(mi(!0),Of(t[V],r));function Zg(e,t,n,r=""){return Nt(e,_n(),n)?t+ps(n)+r:_e}function Le(e){return Ta("",e),Le}function Ta(e,t,n){let r=S(),i=Zg(r,e,t,n);return i!==_e&&$g(r,Fe(),i),Ta}function $g(e,t,n){let r=Ns(t,e);Df(e[V],r,n)}function Fo(e,t,n){let r=U();r.firstCreatePass&&Oa(t,r.data,r.blueprint,rt(e),n)}function Oa(e,t,n,r,i){if(e=F(e),Array.isArray(e))for(let o=0;o>20;if(Ke(e)||!e.multi){let f=new Mt(u,i,Vi,null),p=nr(a,t,i?c:c+h,d);p===-1?(_r(un(l,s),o,a),tr(o,e,t.length),t.push(a),l.directiveStart++,l.directiveEnd++,i&&(l.providerIndexes+=1048576),n.push(f),s.push(f)):(n[p]=f,s[p]=f)}else{let f=nr(a,t,c+h,d),p=nr(a,t,c,c+h),M=f>=0&&n[f],I=p>=0&&n[p];if(i&&!I||!i&&!M){_r(un(l,s),o,a);let J=Gg(i?Wg:Qg,n.length,i,r,u,e);!i&&I&&(n[p].providerFactory=J),tr(o,e,t.length,0),t.push(a),l.directiveStart++,l.directiveEnd++,i&&(l.providerIndexes+=1048576),n.push(J),s.push(J)}else{let J=Da(n[i?p:f],u,!i&&r);tr(o,e,f>-1?f:p,J)}!i&&r&&I&&n[p].componentProviders++}}}function tr(e,t,n,r){let i=Ke(t),o=uc(t);if(i||o){let s=(o?F(t.useClass):t).prototype.ngOnDestroy;if(s){let l=e.destroyHooks||(e.destroyHooks=[]);if(!i&&t.multi){let a=l.indexOf(n);a===-1?l.push(n,[r,s]):l[a+1].push(r,s)}else l.push(n,s)}}}function Da(e,t,n){return n&&e.componentProviders++,e.multi.push(t)-1}function nr(e,t,n,r){for(let i=n;i{n.providersResolver=(r,i)=>Fo(r,i?i(e):e,!1),t&&(n.viewProvidersResolver=(r,i)=>Fo(r,i?i(t):t,!0))}}var Kg=(()=>{class e{applicationErrorHandler=b(Dt);appRef=b(Ar);taskService=b(kn);ngZone=b(He);zonelessEnabled=b(wi);tracing=b(Dn,{optional:!0});zoneIsDefined=typeof Zone<"u"&&!!Zone.root.run;schedulerTickApplyArgs=[{data:{__scheduler_tick__:!0}}];subscriptions=new me;angularZoneId=this.zoneIsDefined?this.ngZone._inner?.get(sn):null;scheduleInRootZone=!this.zonelessEnabled&&this.zoneIsDefined&&(b(od,{optional:!0})??!1);cancelScheduledCallback=null;useMicrotaskScheduler=!1;runningTick=!1;pendingRenderTaskId=null;constructor(){this.subscriptions.add(this.appRef.afterTick.subscribe(()=>{let n=this.taskService.add();if(!this.runningTick&&(this.cleanup(),!this.zonelessEnabled||this.appRef.includeAllTestViews)){this.taskService.remove(n);return}this.switchToMicrotaskScheduler(),this.taskService.remove(n)})),this.subscriptions.add(this.ngZone.onUnstable.subscribe(()=>{this.runningTick||this.cleanup()}))}switchToMicrotaskScheduler(){this.ngZone.runOutsideAngular(()=>{let n=this.taskService.add();this.useMicrotaskScheduler=!0,queueMicrotask(()=>{this.useMicrotaskScheduler=!1,this.taskService.remove(n)})})}notify(n){if(!this.zonelessEnabled&&n===5)return;switch(n){case 0:{this.appRef.dirtyFlags|=2;break}case 3:case 2:case 4:case 5:case 1:{this.appRef.dirtyFlags|=4;break}case 6:{this.appRef.dirtyFlags|=2;break}case 12:{this.appRef.dirtyFlags|=16;break}case 13:{this.appRef.dirtyFlags|=2;break}case 11:break;default:this.appRef.dirtyFlags|=8}if(this.appRef.tracingSnapshot=this.tracing?.snapshot(this.appRef.tracingSnapshot)??null,!this.shouldScheduleTick())return;let r=this.useMicrotaskScheduler?$c:Ks;this.pendingRenderTaskId=this.taskService.add(),this.scheduleInRootZone?this.cancelScheduledCallback=Zone.root.run(()=>r(()=>this.tick())):this.cancelScheduledCallback=this.ngZone.runOutsideAngular(()=>r(()=>this.tick()))}shouldScheduleTick(){return!(this.appRef.destroyed||this.pendingRenderTaskId!==null||this.runningTick||this.appRef._runningTick||!this.zonelessEnabled&&this.zoneIsDefined&&Zone.current.get(sn+this.angularZoneId))}tick(){if(this.runningTick||this.appRef.destroyed)return;if(this.appRef.dirtyFlags===0){this.cleanup();return}!this.zonelessEnabled&&this.appRef.dirtyFlags&7&&(this.appRef.dirtyFlags|=1);let n=this.taskService.add();try{this.ngZone.run(()=>{this.runningTick=!0,this.appRef._tick()},void 0,this.schedulerTickApplyArgs)}catch(r){this.applicationErrorHandler(r)}finally{this.taskService.remove(n),this.cleanup()}}ngOnDestroy(){this.subscriptions.unsubscribe(),this.cleanup()}cleanup(){if(this.runningTick=!1,this.cancelScheduledCallback?.(),this.cancelScheduledCallback=null,this.pendingRenderTaskId!==null){let n=this.pendingRenderTaskId;this.pendingRenderTaskId=null,this.taskService.remove(n)}}static \u0275fac=function(n){return new(n||e)};static \u0275prov=D({token:e,factory:e.\u0275fac,providedIn:"root"})}return e})();function Xg(){return st("NgZoneless"),oi([...Ma(),[]])}function Ma(){return[{provide:bi,useExisting:Kg},{provide:He,useClass:Kc},{provide:wi,useValue:!0}]}function Jg(){return typeof $localize<"u"&&$localize.locale||mn}var Pa=new E("",{factory:()=>b(Pa,{optional:!0,skipSelf:!0})||Jg()});function Be(e,t){return eu(e,t?.equal)}var Br=new E(""),em=new E("");function at(e){return!e.moduleRef}function tm(e){let t=at(e)?e.r3Injector:e.moduleRef.injector,n=t.get(He);return n.run(()=>{at(e)?e.r3Injector.resolveInjectorInitializers():e.moduleRef.resolveInjectorInitializers();let r=t.get(Dt),i;if(n.runOutsideAngular(()=>{i=n.onError.subscribe({next:r})}),at(e)){let o=()=>t.destroy(),s=e.platformInjector.get(Br);s.add(o),t.onDestroy(()=>{i.unsubscribe(),s.delete(o)})}else{let o=()=>e.moduleRef.destroy(),s=e.platformInjector.get(Br);s.add(o),e.moduleRef.onDestroy(()=>{$t(e.allPlatformModules,e.moduleRef),i.unsubscribe(),s.delete(o)})}return rm(r,n,()=>{let o=t.get(kn),s=o.add(),l=t.get(Ca);return l.runInitializers(),l.donePromise.then(()=>{let a=t.get(Pa,mn);if(Sg(a||mn),!t.get(em,!0))return at(e)?t.get(Ar):(e.allPlatformModules.push(e.moduleRef),e.moduleRef);if(at(e)){let u=t.get(Ar);return e.rootComponent!==void 0&&u.bootstrap(e.rootComponent),u}else return nm?.(e.moduleRef,e.allPlatformModules),e.moduleRef}).finally(()=>{o.remove(s)})})})}var nm;function rm(e,t,n){try{let r=n();return _a(r)?r.catch(i=>{throw t.runOutsideAngular(()=>e(i)),i}):r}catch(r){throw t.runOutsideAngular(()=>e(r)),r}}var Qt=null;function im(e=[],t){return Cn.create({name:t,providers:[{provide:li,useValue:"platform"},{provide:Br,useValue:new Set([()=>Qt=null])},...e]})}function om(e=[]){if(Qt)return Qt;let t=im(e);return Qt=t,dg(),sm(t),t}function sm(e){let t=e.get(xl,null);Ts(e,()=>{t?.forEach(n=>n())})}var lm=1e4,Wm=lm-1e3;function am(e){let{rootComponent:t,appProviders:n,platformProviders:r,platformRef:i}=e;x(_.BootstrapApplicationStart);try{let o=i?.injector??om(r),s=[Ma(),ed,...n||[]],l=new wa({providers:s,parent:o,debugName:"",runEnvironmentInitializers:!1});return tm({r3Injector:l.injector,platformInjector:o,rootComponent:t})}catch(o){return Promise.reject(o)}finally{x(_.BootstrapApplicationEnd)}}var Na=null;function Aa(){return Na}function um(e){Na??=e}var cm=class{};function dm(e,t){t=encodeURIComponent(t);for(let n of e.split(";")){let r=n.indexOf("="),[i,o]=r==-1?[n,""]:[n.slice(0,r),n.slice(r+1)];if(i.trim()===t)return decodeURIComponent(o)}return null}var fm=class{},hm="browser",Va=class{_doc;constructor(e){this._doc=e}manager},qr=(()=>{class e extends Va{constructor(n){super(n)}supports(n){return!0}addEventListener(n,r,i,o){return n.addEventListener(r,i,o),()=>this.removeEventListener(n,r,i,o)}removeEventListener(n,r,i,o){return n.removeEventListener(r,i,o)}static \u0275fac=function(n){return new(n||e)(C(we))};static \u0275prov=D({token:e,factory:e.\u0275fac})}return e})(),Ur=new E(""),Ra=(()=>{class e{_zone;_plugins;_eventNameToPlugin=new Map;constructor(n,r){this._zone=r,n.forEach(s=>{s.manager=this});let i=n.filter(s=>!(s instanceof qr));this._plugins=i.slice().reverse();let o=n.find(s=>s instanceof qr);o&&this._plugins.push(o)}addEventListener(n,r,i,o){return this._findPluginFor(r).addEventListener(n,r,i,o)}getZone(){return this._zone}_findPluginFor(n){let r=this._eventNameToPlugin.get(n);if(r)return r;if(r=this._plugins.find(i=>i.supports(n)),!r)throw new w(5101,!1);return this._eventNameToPlugin.set(n,r),r}static \u0275fac=function(n){return new(n||e)(C(Ur),C(He))};static \u0275prov=D({token:e,factory:e.\u0275fac})}return e})(),rr="ng-app-id";function Ho(e){for(let t of e)t.remove()}function zo(e,t){let n=t.createElement("style");return n.textContent=e,n}function pm(e,t,n,r){let i=e.head?.querySelectorAll(`style[${rr}="${t}"],link[${rr}="${t}"]`);if(i)for(let o of i)o.removeAttribute(rr),o instanceof HTMLLinkElement?r.set(o.href.slice(o.href.lastIndexOf("/")+1),{usage:0,elements:[o]}):o.textContent&&n.set(o.textContent,{usage:0,elements:[o]})}function Zr(e,t){let n=t.createElement("link");return n.setAttribute("rel","stylesheet"),n.setAttribute("href",e),n}var La=(()=>{class e{doc;appId;nonce;inline=new Map;external=new Map;hosts=new Set;constructor(n,r,i,o={}){this.doc=n,this.appId=r,this.nonce=i,pm(n,r,this.inline,this.external),this.hosts.add(n.head)}addStyles(n,r){for(let i of n)this.addUsage(i,this.inline,zo);r?.forEach(i=>this.addUsage(i,this.external,Zr))}removeStyles(n,r){for(let i of n)this.removeUsage(i,this.inline);r?.forEach(i=>this.removeUsage(i,this.external))}addUsage(n,r,i){let o=r.get(n);o?o.usage++:r.set(n,{usage:1,elements:[...this.hosts].map(s=>this.addElement(s,i(n,this.doc)))})}removeUsage(n,r){let i=r.get(n);i&&(i.usage--,i.usage<=0&&(Ho(i.elements),r.delete(n)))}ngOnDestroy(){for(let[,{elements:n}]of[...this.inline,...this.external])Ho(n);this.hosts.clear()}addHost(n){this.hosts.add(n);for(let[r,{elements:i}]of this.inline)i.push(this.addElement(n,zo(r,this.doc)));for(let[r,{elements:i}]of this.external)i.push(this.addElement(n,Zr(r,this.doc)))}removeHost(n){this.hosts.delete(n)}addElement(n,r){return this.nonce&&r.setAttribute("nonce",this.nonce),n.appendChild(r)}static \u0275fac=function(n){return new(n||e)(C(we),C(Cl),C(Sl,8),C(kl))};static \u0275prov=D({token:e,factory:e.\u0275fac})}return e})(),ir={svg:"http://www.w3.org/2000/svg",xhtml:"http://www.w3.org/1999/xhtml",xlink:"http://www.w3.org/1999/xlink",xml:"http://www.w3.org/XML/1998/namespace",xmlns:"http://www.w3.org/2000/xmlns/",math:"http://www.w3.org/1998/Math/MathML"},ji=/%COMP%/g,ja="%COMP%",gm=`_nghost-${ja}`,mm=`_ngcontent-${ja}`,vm=!0,ym=new E("",{factory:()=>vm});function bm(e){return mm.replace(ji,e)}function wm(e){return gm.replace(ji,e)}function Fa(e,t){return t.map(n=>n.replace(ji,e))}var Bo=(()=>{class e{eventManager;sharedStylesHost;appId;removeStylesOnCompDestroy;doc;ngZone;nonce;tracingService;rendererByCompId=new Map;defaultRenderer;constructor(n,r,i,o,s,l,a=null,u=null){this.eventManager=n,this.sharedStylesHost=r,this.appId=i,this.removeStylesOnCompDestroy=o,this.doc=s,this.ngZone=l,this.nonce=a,this.tracingService=u,this.defaultRenderer=new Fi(n,s,l,this.tracingService)}createRenderer(n,r){if(!n||!r)return this.defaultRenderer;let i=this.getOrCreateRenderer(n,r);return i instanceof Zo?i.applyToHost(n):i instanceof $r&&i.applyStyles(),i}getOrCreateRenderer(n,r){let i=this.rendererByCompId,o=i.get(r.id);if(!o){let s=this.doc,l=this.ngZone,a=this.eventManager,u=this.sharedStylesHost,c=this.removeStylesOnCompDestroy,d=this.tracingService;switch(r.encapsulation){case re.Emulated:o=new Zo(a,u,r,this.appId,c,s,l,d);break;case re.ShadowDom:return new Uo(a,n,r,s,l,this.nonce,d,u);case re.ExperimentalIsolatedShadowDom:return new Uo(a,n,r,s,l,this.nonce,d);default:o=new $r(a,u,r,c,s,l,d);break}i.set(r.id,o)}return o}ngOnDestroy(){this.rendererByCompId.clear()}componentReplaced(n){this.rendererByCompId.delete(n)}static \u0275fac=function(n){return new(n||e)(C(Ra),C(La),C(Cl),C(ym),C(we),C(He),C(Sl),C(Dn,8))};static \u0275prov=D({token:e,factory:e.\u0275fac})}return e})(),Fi=class{eventManager;doc;ngZone;tracingService;data=Object.create(null);throwOnSyntheticProps=!0;constructor(e,t,n,r){this.eventManager=e,this.doc=t,this.ngZone=n,this.tracingService=r}destroy(){}destroyNode=null;createElement(e,t){return t?this.doc.createElementNS(ir[t]||t,e):this.doc.createElement(e)}createComment(e){return this.doc.createComment(e)}createText(e){return this.doc.createTextNode(e)}appendChild(e,t){(qo(e)?e.content:e).appendChild(t)}insertBefore(e,t,n){e&&(qo(e)?e.content:e).insertBefore(t,n)}removeChild(e,t){t.remove()}selectRootElement(e,t){let n=typeof e=="string"?this.doc.querySelector(e):e;if(!n)throw new w(-5104,!1);return t||(n.textContent=""),n}parentNode(e){return e.parentNode}nextSibling(e){return e.nextSibling}setAttribute(e,t,n,r){if(r){t=r+":"+t;let i=ir[r];i?e.setAttributeNS(i,t,n):e.setAttribute(t,n)}else e.setAttribute(t,n)}removeAttribute(e,t,n){if(n){let r=ir[n];r?e.removeAttributeNS(r,t):e.removeAttribute(`${n}:${t}`)}else e.removeAttribute(t)}addClass(e,t){e.classList.add(t)}removeClass(e,t){e.classList.remove(t)}setStyle(e,t,n,r){r&(Se.DashCase|Se.Important)?e.style.setProperty(t,n,r&Se.Important?"important":""):e.style[t]=n}removeStyle(e,t,n){n&Se.DashCase?e.style.removeProperty(t):e.style[t]=""}setProperty(e,t,n){e!=null&&(e[t]=n)}setValue(e,t){e.nodeValue=t}listen(e,t,n,r){if(typeof e=="string"&&(e=Aa().getGlobalEventTarget(this.doc,e),!e))throw new w(5102,!1);let i=this.decoratePreventDefault(n);return this.tracingService?.wrapEventListener&&(i=this.tracingService.wrapEventListener(e,t,i)),this.eventManager.addEventListener(e,t,i,r)}decoratePreventDefault(e){return t=>{if(t==="__ngUnwrap__")return e;e(t)===!1&&t.preventDefault()}}};function qo(e){return e.tagName==="TEMPLATE"&&e.content!==void 0}var Uo=class extends Fi{hostEl;sharedStylesHost;shadowRoot;constructor(e,t,n,r,i,o,s,l){super(e,r,i,s),this.hostEl=t,this.sharedStylesHost=l,this.shadowRoot=t.attachShadow({mode:"open"}),this.sharedStylesHost&&this.sharedStylesHost.addHost(this.shadowRoot);let a=n.styles;a=Fa(n.id,a);for(let c of a){let d=document.createElement("style");o&&d.setAttribute("nonce",o),d.textContent=c,this.shadowRoot.appendChild(d)}let u=n.getExternalStyles?.();if(u)for(let c of u){let d=Zr(c,r);o&&d.setAttribute("nonce",o),this.shadowRoot.appendChild(d)}}nodeOrShadowRoot(e){return e===this.hostEl?this.shadowRoot:e}appendChild(e,t){return super.appendChild(this.nodeOrShadowRoot(e),t)}insertBefore(e,t,n){return super.insertBefore(this.nodeOrShadowRoot(e),t,n)}removeChild(e,t){return super.removeChild(null,t)}parentNode(e){return this.nodeOrShadowRoot(super.parentNode(this.nodeOrShadowRoot(e)))}destroy(){this.sharedStylesHost&&this.sharedStylesHost.removeHost(this.shadowRoot)}},$r=class extends Fi{sharedStylesHost;removeStylesOnCompDestroy;styles;styleUrls;constructor(e,t,n,r,i,o,s,l){super(e,i,o,s),this.sharedStylesHost=t,this.removeStylesOnCompDestroy=r;let a=n.styles;this.styles=l?Fa(l,a):a,this.styleUrls=n.getExternalStyles?.(l)}applyStyles(){this.sharedStylesHost.addStyles(this.styles,this.styleUrls)}destroy(){this.removeStylesOnCompDestroy&&_t.size===0&&this.sharedStylesHost.removeStyles(this.styles,this.styleUrls)}},Zo=class extends $r{contentAttr;hostAttr;constructor(e,t,n,r,i,o,s,l){let a=r+"-"+n.id;super(e,t,n,i,o,s,l,a),this.contentAttr=bm(a),this.hostAttr=wm(a)}applyToHost(e){this.applyStyles(),this.setAttribute(e,this.hostAttr,"")}createElement(e,t){let n=super.createElement(e,t);return super.setAttribute(n,this.contentAttr,""),n}},_m=class Ha extends cm{supportsDOMEvents=!0;static makeCurrent(){um(new Ha)}onAndCancel(t,n,r,i){return t.addEventListener(n,r,i),()=>{t.removeEventListener(n,r,i)}}dispatchEvent(t,n){t.dispatchEvent(n)}remove(t){t.remove()}createElement(t,n){return n=n||this.getDefaultDocument(),n.createElement(t)}createHtmlDocument(){return document.implementation.createHTMLDocument("fakeTitle")}getDefaultDocument(){return document}isElementNode(t){return t.nodeType===Node.ELEMENT_NODE}isShadowRoot(t){return t instanceof DocumentFragment}getGlobalEventTarget(t,n){return n==="window"?window:n==="document"?t:n==="body"?t.body:null}getBaseHref(t){let n=Cm();return n==null?null:xm(n)}resetBaseElement(){ft=null}getUserAgent(){return window.navigator.userAgent}getCookie(t){return dm(document.cookie,t)}},ft=null;function Cm(){return ft=ft||document.head.querySelector("base"),ft?ft.getAttribute("href"):null}function xm(e){return new URL(e,document.baseURI).pathname}var km=(()=>{class e{build(){return new XMLHttpRequest}static \u0275fac=function(n){return new(n||e)};static \u0275prov=D({token:e,factory:e.\u0275fac})}return e})(),$o=["alt","control","meta","shift"],Sm={"\b":"Backspace"," ":"Tab","\x7F":"Delete","\x1B":"Escape",Del:"Delete",Esc:"Escape",Left:"ArrowLeft",Right:"ArrowRight",Up:"ArrowUp",Down:"ArrowDown",Menu:"ContextMenu",Scroll:"ScrollLock",Win:"OS"},Em={alt:e=>e.altKey,control:e=>e.ctrlKey,meta:e=>e.metaKey,shift:e=>e.shiftKey},Im=(()=>{class e extends Va{constructor(n){super(n)}supports(n){return e.parseEventName(n)!=null}addEventListener(n,r,i,o){let s=e.parseEventName(r),l=e.eventCallback(s.fullKey,i,this.manager.getZone());return this.manager.getZone().runOutsideAngular(()=>Aa().onAndCancel(n,s.domEventName,l,o))}static parseEventName(n){let r=n.toLowerCase().split("."),i=r.shift();if(r.length===0||!(i==="keydown"||i==="keyup"))return null;let o=e._normalizeKey(r.pop()),s="",l=r.indexOf("code");if(l>-1&&(r.splice(l,1),s="code."),$o.forEach(u=>{let c=r.indexOf(u);c>-1&&(r.splice(c,1),s+=u+".")}),s+=o,r.length!=0||o.length===0)return null;let a={};return a.domEventName=i,a.fullKey=s,a}static matchEventFullKeyCode(n,r){let i=Sm[n.key]||n.key,o="";return r.indexOf("code.")>-1&&(i=n.code,o="code."),i==null||!i?!1:(i=i.toLowerCase(),i===" "?i="space":i==="."&&(i="dot"),$o.forEach(s=>{if(s!==i){let l=Em[s];l(n)&&(o+=s+".")}}),o+=i,o===r)}static eventCallback(n,r,i){return o=>{e.matchEventFullKeyCode(o,n)&&i.runGuarded(()=>r(o))}}static _normalizeKey(n){return n==="esc"?"escape":n}static \u0275fac=function(n){return new(n||e)(C(we))};static \u0275prov=D({token:e,factory:e.\u0275fac})}return e})();async function Tm(e,t){return am(Om(e,t))}function Om(e,t){return{platformRef:t?.platformRef,appProviders:[...Am,...e?.providers??[]],platformProviders:Nm}}function Dm(){_m.makeCurrent()}function Mm(){return new Sn}function Pm(){return Fd(document),document}var Nm=[{provide:kl,useValue:hm},{provide:xl,useValue:Dm,multi:!0},{provide:we,useFactory:Pm}],Am=[{provide:li,useValue:"root"},{provide:Sn,useFactory:Mm},{provide:Ur,useClass:qr,multi:!0},{provide:Ur,useClass:Im,multi:!0},Bo,La,Ra,{provide:Ai,useExisting:Bo},{provide:fm,useClass:km},[]],za=(()=>{class e{static \u0275fac=function(n){return new(n||e)};static \u0275prov=D({token:e,factory:function(n){let r=null;return n?r=new(n||e):r=C(Vm),r},providedIn:"root"})}return e})(),Vm=(()=>{class e extends za{_doc;constructor(n){super(),this._doc=n}sanitize(n,r){if(r==null)return null;switch(n){case he.NONE:return r;case he.HTML:return Ze(r,"HTML")?Ce(r):Rl(this._doc,String(r)).toString();case he.STYLE:return Ze(r,"Style")?Ce(r):r;case he.SCRIPT:if(Ze(r,"Script"))return Ce(r);throw new w(5200,!1);case he.URL:return Ze(r,"URL")?Ce(r):Dl(String(r));case he.RESOURCE_URL:if(Ze(r,"ResourceURL"))return Ce(r);throw new w(5201,!1);default:throw new w(5202,!1)}}bypassSecurityTrustHtml(n){return of(n)}bypassSecurityTrustStyle(n){return sf(n)}bypassSecurityTrustScript(n){return lf(n)}bypassSecurityTrustUrl(n){return af(n)}bypassSecurityTrustResourceUrl(n){return uf(n)}static \u0275fac=function(n){return new(n||e)(C(we))};static \u0275prov=D({token:e,factory:e.\u0275fac,providedIn:"root"})}return e})(),Qo=class Wt{constructor(t){if(this.model=t,t){this.page.set(t.get("page")??0),this.pageSize.set(t.get("page_size")??10),this.maxColumns.set(t.get("max_columns")??0),this.rowCount.set(t.get("row_count")??null),this.tableHtml.set(t.get("table_html")??""),this.sortContext.set(t.get("sort_context")??[]),this.orderableColumns.set(t.get("orderable_columns")??[]);let n=t.get("error_message")??t.get("_error_message")??null;this.errorMessage.set(n),this.startExecution.set(t.get("start_execution")??!1),this.isDeferredMode.set(t.get("is_deferred_mode")??!1),this.dryRunInfo.set(t.get("dry_run_info")??""),this.ping.set(t.get("ping")??0),t.on("change:page",()=>{this.page.set(t.get("page"))}),t.on("change:page_size",()=>{this.pageSize.set(t.get("page_size"))}),t.on("change:max_columns",()=>{this.maxColumns.set(t.get("max_columns"))}),t.on("change:row_count",()=>{this.rowCount.set(t.get("row_count"))}),t.on("change:table_html",()=>{this.tableHtml.set(t.get("table_html"))}),t.on("change:sort_context",()=>{this.sortContext.set(t.get("sort_context"))}),t.on("change:orderable_columns",()=>{this.orderableColumns.set(t.get("orderable_columns"))}),t.on("change:start_execution",()=>{this.startExecution.set(t.get("start_execution")??!1)}),t.on("change:is_deferred_mode",()=>{this.isDeferredMode.set(t.get("is_deferred_mode")??!1)}),t.on("change:dry_run_info",()=>{this.dryRunInfo.set(t.get("dry_run_info")??"")}),t.on("change:ping",()=>{this.ping.set(t.get("ping")??0)});let r=()=>{let i=t.get("error_message")??t.get("_error_message")??null;this.errorMessage.set(i)};t.on("change:error_message",r),t.on("change:_error_message",r)}}page=j(0);pageSize=j(10);maxColumns=j(0);rowCount=j(null);tableHtml=j("");sortContext=j([]);orderableColumns=j([]);errorMessage=j(null);startExecution=j(!1);isDeferredMode=j(!1);dryRunInfo=j("");ping=j(0);setPage(t){this.page.set(t),this.model&&(this.model.set("page",t),this.model.save_changes())}setPageSize(t){this.pageSize.set(t),this.page.set(0),this.model&&(this.model.set("page_size",t),this.model.set("page",0),this.model.save_changes())}setMaxColumns(t){this.maxColumns.set(t),this.model&&(this.model.set("max_columns",t),this.model.save_changes())}setSortContext(t){this.sortContext.set(t),this.model&&(this.model.set("sort_context",t),this.model.save_changes())}setStartExecution(t){this.startExecution.set(t),this.model&&(this.model.set("start_execution",t),this.model.save_changes())}setPing(t){this.ping.set(t),this.model&&(this.model.set("ping",t),this.model.save_changes())}static \u0275fac=function(t){return new(t||Wt)(C("ANYWIDGET_MODEL"))};static \u0275prov=D({token:Wt,factory:Wt.\u0275fac})},Rm=["tableContainer"],Lm=["app-root",""];function jm(e,t){if(e&1&&(P(0,"div",2),B(1),N()),e&2){let n=ee();L(),Le(n.errorMessage())}}function Fm(e,t){e&1&&(ka(0,"span",7),B(1," Run Query "))}function Hm(e,t){e&1&&B(0," Run Query ")}function zm(e,t){if(e&1){let n=Sa();P(0,"div",3)(1,"div",4)(2,"p",5),B(3),N(),P(4,"button",6),xe("click",function(){qe(n);let r=ee();return Ue(r.handleRunQuery())}),Rr(5,Fm,2,0)(6,Hm,1,0),N()()()}if(e&2){let n=ee();L(3),Le(n.dryRunInfo()),L(),te("disabled",n.isLoading()),L(),Lr(n.isLoading()?5:6)}}function Bm(e,t){if(e&1&&(P(0,"option",18),B(1),N()),e&2){let n=t.$implicit;te("value",n),L(),Le(n===0?"All":n)}}function qm(e,t){if(e&1&&(P(0,"option",18),B(1),N()),e&2){let n=t.$implicit;te("value",n),L(),Le(n)}}function Um(e,t){if(e&1){let n=Sa();P(0,"div",8,0),xe("click",function(r){qe(n);let i=ee();return Ue(i.handleTableClick(r))}),N(),P(2,"footer",9)(3,"span",10),B(4),N(),P(5,"div",11)(6,"button",12),xe("click",function(){qe(n);let r=ee();return Ue(r.handlePageChange(-1))}),B(7,"<"),N(),P(8,"span",13),B(9),N(),P(10,"button",12),xe("click",function(){qe(n);let r=ee();return Ue(r.handlePageChange(1))}),B(11,">"),N()(),P(12,"div",14)(13,"div",15)(14,"label",16),B(15,"Max columns:"),N(),P(16,"select",17),xe("change",function(r){qe(n);let i=ee();return Ue(i.handleMaxColumnsChange(r))}),Vo(17,Bm,2,2,"option",18,Ao),N()(),P(19,"div",19)(20,"label",20),B(21,"Page size:"),N(),P(22,"select",21),xe("change",function(r){qe(n);let i=ee();return Ue(i.handlePageSizeChange(r))}),Vo(23,qm,2,2,"option",18,Ao),N()()()()}if(e&2){let n=ee();te("innerHTML",n.sanitizedHtml(),Nf),L(4),Le(n.rowCountText()),L(2),te("disabled",n.prevPageDisabled()),L(3),Le(n.pageIndicatorText()),L(),te("disabled",n.nextPageDisabled()),L(6),te("value",n.maxColumns()),L(),Ro(n.maxColumnOptions),L(5),te("value",n.pageSize()),L(),Ro(n.pageSizeOptions)}}var Zm=class Qr{state=b(Qo);sanitizer=b(za);maxColumnOptions=[5,10,15,20,0];pageSizeOptions=[10,25,50,100];errorMessage=this.state.errorMessage;maxColumns=this.state.maxColumns;pageSize=this.state.pageSize;page=this.state.page;rowCount=this.state.rowCount;isDeferredMode=this.state.isDeferredMode;dryRunInfo=this.state.dryRunInfo;isLoading=j(!1);sanitizedHtml=Be(()=>this.sanitizer.bypassSecurityTrustHtml(this.state.tableHtml()));totalPages=Be(()=>{let t=this.rowCount(),n=this.pageSize();return t!==null&&n>0?Math.ceil(t/n):null});pageIndicatorText=Be(()=>{let t=this.page(),n=this.rowCount(),r=this.totalPages(),i=(t+1).toLocaleString(),o=(r??1).toLocaleString();return`Page ${i} of ${o}`});rowCountText=Be(()=>{let t=this.rowCount();return t===null?"Total rows unknown":t===0?"0 total rows":`${t.toLocaleString()} total rows`});prevPageDisabled=Be(()=>this.page()===0);nextPageDisabled=Be(()=>{let t=this.page(),n=this.rowCount(),r=this.totalPages();return n===null?!1:n===0?!0:r!==null&&t>=r-1});isDarkMode=j(!1);themeObserver=null;tableContainerRef;isHeightInitialized=!1;constructor(){$n(()=>{let t=this.state.tableHtml(),n=this.state.sortContext(),r=this.state.orderableColumns();this.isDeferredMode()&&(this.isHeightInitialized=!1),setTimeout(()=>{this.applySortIndicators(),this.lockInitialHeight()},0)}),$n(()=>{this.state.startExecution()||this.isLoading.set(!1)}),$n(t=>{if(this.state.startExecution()){let n=setInterval(()=>{if(this.state.startExecution()){let r=this.state.ping();this.state.setPing(r+1)}else clearInterval(n)},500);t(()=>{clearInterval(n)})}})}ngOnInit(){this.initThemeDetection()}ngOnDestroy(){this.themeObserver?.disconnect()}handleRunQuery(){this.isLoading.set(!0),this.state.setStartExecution(!0)}handlePageChange(t){let n=this.page()+t;this.state.setPage(n)}handlePageSizeChange(t){let n=t.target,r=Number(n.value);r&&this.state.setPageSize(r)}handleMaxColumnsChange(t){let n=t.target,r=Number(n.value);this.state.setMaxColumns(r)}handleTableClick(t){let n=t.target.closest("th");if(!n)return;let r=n.querySelector("div.bf-header-content");if(!r)return;let i=this.getColumnName(r),o=this.state.orderableColumns();if(!i||!o.includes(i))return;let s=[...this.state.sortContext()],l=s.findIndex(u=>u.column===i),a=[...s];t.shiftKey?l!==-1?a[l].ascending?a[l]=Q($({},a[l]),{ascending:!1}):a.splice(l,1):a.push({column:i,ascending:!0}):l!==-1&&a.length===1?a[l].ascending?a[l]=Q($({},a[l]),{ascending:!1}):a=[]:a=[{column:i,ascending:!0}],this.state.setSortContext(a)}getColumnName(t){let n=t.cloneNode(!0);return n.querySelector(".sort-indicator")?.remove(),n.textContent?.trim()||""}applySortIndicators(){let t=this.tableContainerRef?.nativeElement;if(!t)return;let n=this.state.orderableColumns(),r=this.state.sortContext()||[],i=o=>r.findIndex(s=>s.column===o);t.querySelectorAll("th").forEach(o=>{let s=o.querySelector("div.bf-header-content");if(!s)return;let l=this.getColumnName(s);if(l&&n.includes(l)){let a=s.querySelector(".sort-indicator");a||(a=document.createElement("span"),a.classList.add("sort-indicator"),a.style.paddingLeft="5px",s.appendChild(a));let u=i(l);if(u!==-1){let c=r[u].ascending;a.textContent=c?"\u25B2":"\u25BC",a.style.visibility="visible"}else a.textContent="\u25CF",a.style.visibility="hidden"}})}lockInitialHeight(){if(this.isHeightInitialized)return;let t=this.tableContainerRef?.nativeElement;if(!t)return;let n=t.querySelector("table");if(n&&n.offsetHeight>0){let r=t.offsetHeight;r>0&&(t.style.height=`${r}px`,this.isHeightInitialized=!0)}}initThemeDetection(){this.updateTheme();let t=new MutationObserver(()=>this.updateTheme());t.observe(document.body,{attributes:!0,attributeFilter:["class","data-theme","data-vscode-theme-kind"]}),this.themeObserver=t}updateTheme(){let t=document.body,n=t.classList.contains("vscode-dark")||t.classList.contains("theme-dark")||t.dataset.theme==="dark"||t.getAttribute("data-vscode-theme-kind")==="vscode-dark";this.isDarkMode.set(n)}static \u0275fac=function(t){return new(t||Qr)};static \u0275cmp=Kp({type:Qr,selectors:[["","app-root",""]],viewQuery:function(t,n){if(t&1&&Ea(Rm,5),t&2){let r;Eg(r=Ig())&&(n.tableContainerRef=r.first)}},features:[Yg([Qo])],attrs:Lm,decls:4,vars:4,consts:[["tableContainer",""],[1,"bigframes-widget"],[1,"bigframes-error-message"],[1,"deferred-container"],[1,"deferred-card"],[1,"deferred-estimate"],[1,"run-query-button",3,"click","disabled"],[1,"spinner"],[1,"table-container",3,"click","innerHTML"],[1,"footer"],[1,"row-count"],[1,"pagination"],[3,"click","disabled"],[1,"page-indicator"],[1,"settings"],[1,"max-columns"],["for","max-cols-select"],["id","max-cols-select",3,"change","value"],[3,"value"],[1,"page-size"],["for","page-size-select"],["id","page-size-select",3,"change","value"]],template:function(t,n){t&1&&(P(0,"div",1),Rr(1,jm,2,1,"div",2),Rr(2,zm,7,3,"div",3)(3,Um,25,7),N()),t&2&&(Ia("bigframes-dark-mode",n.isDarkMode()),L(),Lr(n.errorMessage()?1:-1),L(),Lr(n.isDeferredMode()?2:3))},styles:[".bigframes-widget.bigframes-widget[_ngcontent-%COMP%]{--bf-bg: white;--bf-border-color: #ccc;--bf-error-bg: #fbe;--bf-error-border: red;--bf-error-fg: black;--bf-fg: black;--bf-header-bg: #f5f5f5;--bf-null-fg: gray;--bf-row-even-bg: #f5f5f5;--bf-row-odd-bg: white;background-color:var(--bf-bg);box-sizing:border-box;color:var(--bf-fg);display:flex;flex-direction:column;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;margin:0;padding:0;width:100%}.bigframes-widget[_ngcontent-%COMP%] *[_ngcontent-%COMP%]{box-sizing:border-box}@media(prefers-color-scheme:dark){.bigframes-widget.bigframes-widget[_ngcontent-%COMP%]{--bf-bg: var(--vscode-editor-background, #202124);--bf-border-color: #444;--bf-error-bg: #511;--bf-error-border: #f88;--bf-error-fg: #fcc;--bf-fg: white;--bf-header-bg: var(--vscode-editor-background, black);--bf-null-fg: #aaa;--bf-row-even-bg: #202124;--bf-row-odd-bg: #383838}}.bigframes-widget.bigframes-dark-mode.bigframes-dark-mode[_ngcontent-%COMP%]{--bf-bg: var(--vscode-editor-background, #202124);--bf-border-color: #444;--bf-error-bg: #511;--bf-error-border: #f88;--bf-error-fg: #fcc;--bf-fg: white;--bf-header-bg: var(--vscode-editor-background, black);--bf-null-fg: #aaa;--bf-row-even-bg: #202124;--bf-row-odd-bg: #383838}.bigframes-widget[_ngcontent-%COMP%] .table-container[_ngcontent-%COMP%]{background-color:var(--bf-bg);margin:0;overflow:auto;padding:0}.bigframes-widget[_ngcontent-%COMP%] .footer[_ngcontent-%COMP%]{align-items:center;background-color:var(--bf-bg);color:var(--bf-fg);display:flex;font-size:.8rem;justify-content:space-between;padding:8px}.bigframes-widget[_ngcontent-%COMP%] .footer[_ngcontent-%COMP%] > *[_ngcontent-%COMP%]{flex:1}.bigframes-widget[_ngcontent-%COMP%] .pagination[_ngcontent-%COMP%]{align-items:center;display:flex;flex-direction:row;gap:4px;justify-content:center;padding:4px}.bigframes-widget[_ngcontent-%COMP%] .page-indicator[_ngcontent-%COMP%], .bigframes-widget[_ngcontent-%COMP%] .row-count[_ngcontent-%COMP%]{margin:0 8px}.bigframes-widget[_ngcontent-%COMP%] .settings[_ngcontent-%COMP%]{align-items:center;display:flex;flex-direction:row;gap:16px;justify-content:end}.bigframes-widget[_ngcontent-%COMP%] .page-size[_ngcontent-%COMP%], .bigframes-widget[_ngcontent-%COMP%] .max-columns[_ngcontent-%COMP%]{align-items:center;display:flex;flex-direction:row;gap:4px}.bigframes-widget[_ngcontent-%COMP%] .page-size[_ngcontent-%COMP%] label[_ngcontent-%COMP%], .bigframes-widget[_ngcontent-%COMP%] .max-columns[_ngcontent-%COMP%] label[_ngcontent-%COMP%]{margin-right:8px}.bigframes-widget[_ngcontent-%COMP%] table.bigframes-widget-table, .bigframes-widget[_ngcontent-%COMP%] table.dataframe{background-color:var(--bf-bg);border:1px solid var(--bf-border-color);border-collapse:collapse;border-spacing:0;box-shadow:none;color:var(--bf-fg);margin:0;outline:none;text-align:left;width:auto}.bigframes-widget[_ngcontent-%COMP%] tr{border:none}.bigframes-widget[_ngcontent-%COMP%] th{background-color:var(--bf-header-bg);border:1px solid var(--bf-border-color);color:var(--bf-fg);padding:0;position:sticky;text-align:left;top:0;z-index:1}.bigframes-widget[_ngcontent-%COMP%] td{border:1px solid var(--bf-border-color);color:var(--bf-fg);padding:.5em}.bigframes-widget[_ngcontent-%COMP%] table tbody tr:nth-child(odd), .bigframes-widget[_ngcontent-%COMP%] table tbody tr:nth-child(odd) td{background-color:var(--bf-row-odd-bg)}.bigframes-widget[_ngcontent-%COMP%] table tbody tr:nth-child(2n), .bigframes-widget[_ngcontent-%COMP%] table tbody tr:nth-child(2n) td{background-color:var(--bf-row-even-bg)}.bigframes-widget[_ngcontent-%COMP%] .bf-header-content{box-sizing:border-box;height:100%;overflow:auto;padding:.5em;resize:horizontal;width:100%}.bigframes-widget[_ngcontent-%COMP%] th .sort-indicator{padding-left:4px;visibility:hidden}.bigframes-widget[_ngcontent-%COMP%] th:hover .sort-indicator{visibility:visible}.bigframes-widget[_ngcontent-%COMP%] button[_ngcontent-%COMP%]{background-color:transparent;border:1px solid currentColor;border-radius:4px;color:inherit;cursor:pointer;display:inline-block;padding:2px 8px;text-align:center;text-decoration:none;-webkit-user-select:none;user-select:none;vertical-align:middle}.bigframes-widget[_ngcontent-%COMP%] button[_ngcontent-%COMP%]:disabled{opacity:.65;pointer-events:none}.bigframes-widget[_ngcontent-%COMP%] .bigframes-error-message[_ngcontent-%COMP%]{background-color:var(--bf-error-bg);border:1px solid var(--bf-error-border);border-radius:4px;color:var(--bf-error-fg);font-size:14px;margin-bottom:8px;padding:8px}.bigframes-widget[_ngcontent-%COMP%] .cell-align-right{text-align:right}.bigframes-widget[_ngcontent-%COMP%] .cell-align-left{text-align:left}.bigframes-widget[_ngcontent-%COMP%] .null-value{color:var(--bf-null-fg)}.bigframes-widget[_ngcontent-%COMP%] .debug-info{border-top:1px solid var(--bf-border-color)}.bigframes-widget[_ngcontent-%COMP%] .deferred-container[_ngcontent-%COMP%]{align-items:center;display:flex;justify-content:center;min-height:220px;padding:24px;width:100%}.bigframes-widget[_ngcontent-%COMP%] .deferred-card[_ngcontent-%COMP%]{background:linear-gradient(135deg,#fff9,#ffffff4d);border:1px solid rgba(255,255,255,.4);border-radius:16px;box-shadow:0 8px 32px #1f268712;display:flex;flex-direction:column;gap:16px;max-width:500px;padding:32px;text-align:center;transition:all .3s ease-in-out}.bigframes-widget.bigframes-dark-mode[_ngcontent-%COMP%] .deferred-card[_ngcontent-%COMP%]{background:linear-gradient(135deg,#20212499,#2021244d);border:1px solid rgba(255,255,255,.1);box-shadow:0 8px 32px #0000004d}@media(prefers-color-scheme:dark){.bigframes-widget[_ngcontent-%COMP%] .deferred-card[_ngcontent-%COMP%]{background:linear-gradient(135deg,#20212499,#2021244d);border:1px solid rgba(255,255,255,.1);box-shadow:0 8px 32px #0000004d}}.bigframes-widget[_ngcontent-%COMP%] .deferred-title[_ngcontent-%COMP%]{font-size:1.1rem;font-weight:600;margin:0}.bigframes-widget[_ngcontent-%COMP%] .deferred-estimate[_ngcontent-%COMP%]{color:var(--bf-null-fg);font-size:.9rem;margin:0}.bigframes-widget[_ngcontent-%COMP%] .run-query-button[_ngcontent-%COMP%]{align-items:center;background-color:var(--bf-fg);border:1px solid var(--bf-fg);border-radius:8px;color:var(--bf-bg);cursor:pointer;display:inline-flex;font-size:14px;font-weight:600;gap:8px;justify-content:center;padding:10px 20px;transition:transform .2s ease,opacity .2s ease}.bigframes-widget[_ngcontent-%COMP%] .run-query-button[_ngcontent-%COMP%]:hover{opacity:.9;transform:translateY(-1px)}.bigframes-widget[_ngcontent-%COMP%] .run-query-button[_ngcontent-%COMP%]:active{transform:translateY(0)}.bigframes-widget[_ngcontent-%COMP%] .run-query-button[_ngcontent-%COMP%]:disabled{cursor:not-allowed;opacity:.6}.bigframes-widget[_ngcontent-%COMP%] .spinner[_ngcontent-%COMP%]{animation:_ngcontent-%COMP%_spin 1s linear infinite;border:2px solid currentColor;border-radius:50%;border-top-color:transparent;display:inline-block;height:12px;width:12px}@keyframes _ngcontent-%COMP%_spin{to{transform:rotate(360deg)}}"]})};function $m({model:e,el:t}){let n=document.createElement("div");n.setAttribute("app-root",""),t.appendChild(n);let r={providers:[nd(),Xg(),{provide:"ANYWIDGET_MODEL",useValue:e}]};Tm(r).then(i=>{i.bootstrap(Zm,n),n.removeAttribute("app-root")}).catch(i=>console.error(i))}var Gm={render:$m};export{Gm as default}; diff --git a/bigframes/display/table_widget_angular/.editorconfig b/bigframes/display/table_widget_angular/.editorconfig deleted file mode 100644 index f166060da1c..00000000000 --- a/bigframes/display/table_widget_angular/.editorconfig +++ /dev/null @@ -1,17 +0,0 @@ -# Editor configuration, see https://editorconfig.org -root = true - -[*] -charset = utf-8 -indent_style = space -indent_size = 2 -insert_final_newline = true -trim_trailing_whitespace = true - -[*.ts] -quote_type = single -ij_typescript_use_double_quotes = false - -[*.md] -max_line_length = off -trim_trailing_whitespace = false diff --git a/bigframes/display/table_widget_angular/.gitignore b/bigframes/display/table_widget_angular/.gitignore deleted file mode 100644 index 854acd5fc03..00000000000 --- a/bigframes/display/table_widget_angular/.gitignore +++ /dev/null @@ -1,44 +0,0 @@ -# See https://docs.github.com/get-started/getting-started-with-git/ignoring-files for more about ignoring files. - -# Compiled output -/dist -/tmp -/out-tsc -/bazel-out - -# Node -/node_modules -npm-debug.log -yarn-error.log - -# IDEs and editors -.idea/ -.project -.classpath -.c9/ -*.launch -.settings/ -*.sublime-workspace - -# Visual Studio Code -.vscode/* -!.vscode/settings.json -!.vscode/tasks.json -!.vscode/launch.json -!.vscode/extensions.json -!.vscode/mcp.json -.history/* - -# Miscellaneous -/.angular/cache -.sass-cache/ -/connect.lock -/coverage -/libpeerconnection.log -testem.log -/typings -__screenshots__/ - -# System files -.DS_Store -Thumbs.db diff --git a/bigframes/display/table_widget_angular/.prettierrc b/bigframes/display/table_widget_angular/.prettierrc deleted file mode 100644 index d6c16d7ee77..00000000000 --- a/bigframes/display/table_widget_angular/.prettierrc +++ /dev/null @@ -1,12 +0,0 @@ -{ - "printWidth": 100, - "singleQuote": true, - "overrides": [ - { - "files": "*.html", - "options": { - "parser": "angular" - } - } - ] -} diff --git a/bigframes/display/table_widget_angular/README.md b/bigframes/display/table_widget_angular/README.md deleted file mode 100644 index db09b5b9f56..00000000000 --- a/bigframes/display/table_widget_angular/README.md +++ /dev/null @@ -1,48 +0,0 @@ -# TableWidgetAngular - -This project is the Angular-based interactive Table Widget frontend for BigQuery DataFrames (``bigframes``). It is integrated into the Python backend using ``anywidget``. - -This project was generated using [Angular CLI](https://github.com/angular/angular-cli) version 21.2.9. - -## Getting Started - -Ensure you have [Node.js](https://nodejs.org/) installed. - -1. Install dependencies: - ```bash - npm install - ``` - -2. Start the local development server: - ```bash - npm run start - ``` - Navigate to `http://localhost:4200/`. The application will automatically reload when you modify the source files under `src/`. - -## Development & Code Scaffolding - -To generate a new component, directive, or service: -```bash -ng generate component component-name -``` - -For a complete list of available schematics (such as `components`, `directives`, or `pipes`), run: -```bash -ng generate --help -``` - -## Running Tests - -To execute unit tests: -```bash -npm run test -``` - -## Packaging for Python - -Before testing the widget inside a Jupyter notebook or committing changes, compile the Angular app and bundle it so that the Python backend can load it: -```bash -npm run build:widget -``` - -This command compiles the project in production mode and then triggers `bundle.js` (via `esbuild`) to bundle the browser artifacts into a single unified ES module file at `../table_widget_angular.js`. diff --git a/bigframes/display/table_widget_angular/angular.json b/bigframes/display/table_widget_angular/angular.json deleted file mode 100644 index 497168c4c95..00000000000 --- a/bigframes/display/table_widget_angular/angular.json +++ /dev/null @@ -1,73 +0,0 @@ -{ - "$schema": "./node_modules/@angular/cli/lib/config/schema.json", - "version": 1, - "cli": { - "packageManager": "npm" - }, - "newProjectRoot": "projects", - "projects": { - "table-widget-angular": { - "projectType": "application", - "schematics": {}, - "root": "", - "sourceRoot": "src", - "prefix": "app", - "architect": { - "build": { - "builder": "@angular/build:application", - "options": { - "browser": "src/main.ts", - "tsConfig": "tsconfig.app.json", - "assets": [ - { - "glob": "**/*", - "input": "public" - } - ], - "styles": [ - "src/styles.css" - ] - }, - "configurations": { - "production": { - "budgets": [ - { - "type": "initial", - "maximumWarning": "500kB", - "maximumError": "1MB" - }, - { - "type": "anyComponentStyle", - "maximumWarning": "4kB", - "maximumError": "8kB" - } - ], - "outputHashing": "all" - }, - "development": { - "optimization": false, - "extractLicenses": false, - "sourceMap": true - } - }, - "defaultConfiguration": "production" - }, - "serve": { - "builder": "@angular/build:dev-server", - "configurations": { - "production": { - "buildTarget": "table-widget-angular:build:production" - }, - "development": { - "buildTarget": "table-widget-angular:build:development" - } - }, - "defaultConfiguration": "development" - }, - "test": { - "builder": "@angular/build:unit-test" - } - } - } - } -} diff --git a/bigframes/display/table_widget_angular/bundle.js b/bigframes/display/table_widget_angular/bundle.js deleted file mode 100644 index fb97ab8a376..00000000000 --- a/bigframes/display/table_widget_angular/bundle.js +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright 2026 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. - */ - -const esbuild = require('esbuild'); -const path = require('path'); - -const banner = `/* - * Copyright 2026 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. - */ -`; - -esbuild.build({ - entryPoints: [path.resolve(__dirname, 'dist/table-widget-angular/browser/main.js')], - bundle: true, - outfile: path.resolve(__dirname, '../table_widget_angular.js'), - format: 'esm', - logLevel: 'info', - minify: true, - banner: { - js: banner, - }, -}).catch(() => process.exit(1)); diff --git a/bigframes/display/table_widget_angular/package-lock.json b/bigframes/display/table_widget_angular/package-lock.json deleted file mode 100644 index 33540ab8512..00000000000 --- a/bigframes/display/table_widget_angular/package-lock.json +++ /dev/null @@ -1,9591 +0,0 @@ -{ - "name": "table-widget-angular", - "version": "0.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "table-widget-angular", - "version": "0.0.0", - "dependencies": { - "@angular/common": "^22.1.0", - "@angular/compiler": "^22.1.0", - "@angular/core": "^22.1.0", - "@angular/forms": "^22.1.0", - "@angular/platform-browser": "^22.1.0", - "@angular/router": "^22.1.0", - "rxjs": "~7.8.0", - "tslib": "^2.3.0" - }, - "devDependencies": { - "@angular/build": "^22.1.2", - "@angular/cli": "^21.2.16", - "@angular/compiler-cli": "^22.1.0", - "esbuild": "^0.28.0", - "jsdom": "^28.0.0", - "prettier": "^3.8.1", - "typescript": "~5.9.2", - "vitest": "^4.0.8" - } - }, - "node_modules/@acemir/cssom": { - "version": "0.9.31", - "resolved": "https://registry.npmjs.org/@acemir/cssom/-/cssom-0.9.31.tgz", - "integrity": "sha512-ZnR3GSaH+/vJ0YlHau21FjfLYjMpYVIzTD8M8vIEQvIGxeOXyXdzCI140rrCY862p/C/BbzWsjc1dgnM9mkoTA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@algolia/abtesting": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@algolia/abtesting/-/abtesting-1.14.1.tgz", - "integrity": "sha512-Dkj0BgPiLAaim9sbQ97UKDFHJE/880wgStAM18U++NaJ/2Cws34J5731ovJifr6E3Pv4T2CqvMXf8qLCC417Ew==", - "dev": true, - "license": "MIT", - "dependencies": { - "@algolia/client-common": "5.48.1", - "@algolia/requester-browser-xhr": "5.48.1", - "@algolia/requester-fetch": "5.48.1", - "@algolia/requester-node-http": "5.48.1" - }, - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/@algolia/client-abtesting": { - "version": "5.48.1", - "resolved": "https://registry.npmjs.org/@algolia/client-abtesting/-/client-abtesting-5.48.1.tgz", - "integrity": "sha512-LV5qCJdj+/m9I+Aj91o+glYszrzd7CX6NgKaYdTOj4+tUYfbS62pwYgUfZprYNayhkQpVFcrW8x8ZlIHpS23Vw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@algolia/client-common": "5.48.1", - "@algolia/requester-browser-xhr": "5.48.1", - "@algolia/requester-fetch": "5.48.1", - "@algolia/requester-node-http": "5.48.1" - }, - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/@algolia/client-analytics": { - "version": "5.48.1", - "resolved": "https://registry.npmjs.org/@algolia/client-analytics/-/client-analytics-5.48.1.tgz", - "integrity": "sha512-/AVoMqHhPm14CcHq7mwB+bUJbfCv+jrxlNvRjXAuO+TQa+V37N8k1b0ijaRBPdmSjULMd8KtJbQyUyabXOu6Kg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@algolia/client-common": "5.48.1", - "@algolia/requester-browser-xhr": "5.48.1", - "@algolia/requester-fetch": "5.48.1", - "@algolia/requester-node-http": "5.48.1" - }, - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/@algolia/client-common": { - "version": "5.48.1", - "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-5.48.1.tgz", - "integrity": "sha512-VXO+qu2Ep6ota28ktvBm3sG53wUHS2n7bgLWmce5jTskdlCD0/JrV4tnBm1l7qpla1CeoQb8D7ShFhad+UoSOw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/@algolia/client-insights": { - "version": "5.48.1", - "resolved": "https://registry.npmjs.org/@algolia/client-insights/-/client-insights-5.48.1.tgz", - "integrity": "sha512-zl+Qyb0nLg+Y5YvKp1Ij+u9OaPaKg2/EPzTwKNiVyOHnQJlFxmXyUZL1EInczAZsEY8hVpPCLtNfhMhfxluXKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@algolia/client-common": "5.48.1", - "@algolia/requester-browser-xhr": "5.48.1", - "@algolia/requester-fetch": "5.48.1", - "@algolia/requester-node-http": "5.48.1" - }, - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/@algolia/client-personalization": { - "version": "5.48.1", - "resolved": "https://registry.npmjs.org/@algolia/client-personalization/-/client-personalization-5.48.1.tgz", - "integrity": "sha512-r89Qf9Oo9mKWQXumRu/1LtvVJAmEDpn8mHZMc485pRfQUMAwSSrsnaw1tQ3sszqzEgAr1c7rw6fjBI+zrAXTOw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@algolia/client-common": "5.48.1", - "@algolia/requester-browser-xhr": "5.48.1", - "@algolia/requester-fetch": "5.48.1", - "@algolia/requester-node-http": "5.48.1" - }, - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/@algolia/client-query-suggestions": { - "version": "5.48.1", - "resolved": "https://registry.npmjs.org/@algolia/client-query-suggestions/-/client-query-suggestions-5.48.1.tgz", - "integrity": "sha512-TPKNPKfghKG/bMSc7mQYD9HxHRUkBZA4q1PEmHgICaSeHQscGqL4wBrKkhfPlDV1uYBKW02pbFMUhsOt7p4ZpA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@algolia/client-common": "5.48.1", - "@algolia/requester-browser-xhr": "5.48.1", - "@algolia/requester-fetch": "5.48.1", - "@algolia/requester-node-http": "5.48.1" - }, - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/@algolia/client-search": { - "version": "5.48.1", - "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-5.48.1.tgz", - "integrity": "sha512-4Fu7dnzQyQmMFknYwTiN/HxPbH4DyxvQ1m+IxpPp5oslOgz8m6PG5qhiGbqJzH4HiT1I58ecDiCAC716UyVA8Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@algolia/client-common": "5.48.1", - "@algolia/requester-browser-xhr": "5.48.1", - "@algolia/requester-fetch": "5.48.1", - "@algolia/requester-node-http": "5.48.1" - }, - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/@algolia/ingestion": { - "version": "1.48.1", - "resolved": "https://registry.npmjs.org/@algolia/ingestion/-/ingestion-1.48.1.tgz", - "integrity": "sha512-/RFq3TqtXDUUawwic/A9xylA2P3LDMO8dNhphHAUOU51b1ZLHrmZ6YYJm3df1APz7xLY1aht6okCQf+/vmrV9w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@algolia/client-common": "5.48.1", - "@algolia/requester-browser-xhr": "5.48.1", - "@algolia/requester-fetch": "5.48.1", - "@algolia/requester-node-http": "5.48.1" - }, - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/@algolia/monitoring": { - "version": "1.48.1", - "resolved": "https://registry.npmjs.org/@algolia/monitoring/-/monitoring-1.48.1.tgz", - "integrity": "sha512-Of0jTeAZRyRhC7XzDSjJef0aBkgRcvRAaw0ooYRlOw57APii7lZdq+layuNdeL72BRq1snaJhoMMwkmLIpJScw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@algolia/client-common": "5.48.1", - "@algolia/requester-browser-xhr": "5.48.1", - "@algolia/requester-fetch": "5.48.1", - "@algolia/requester-node-http": "5.48.1" - }, - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/@algolia/recommend": { - "version": "5.48.1", - "resolved": "https://registry.npmjs.org/@algolia/recommend/-/recommend-5.48.1.tgz", - "integrity": "sha512-bE7JcpFXzxF5zHwj/vkl2eiCBvyR1zQ7aoUdO+GDXxGp0DGw7nI0p8Xj6u8VmRQ+RDuPcICFQcCwRIJT5tDJFw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@algolia/client-common": "5.48.1", - "@algolia/requester-browser-xhr": "5.48.1", - "@algolia/requester-fetch": "5.48.1", - "@algolia/requester-node-http": "5.48.1" - }, - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/@algolia/requester-browser-xhr": { - "version": "5.48.1", - "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-5.48.1.tgz", - "integrity": "sha512-MK3wZ2koLDnvH/AmqIF1EKbJlhRS5j74OZGkLpxI4rYvNi9Jn/C7vb5DytBnQ4KUWts7QsmbdwHkxY5txQHXVw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@algolia/client-common": "5.48.1" - }, - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/@algolia/requester-fetch": { - "version": "5.48.1", - "resolved": "https://registry.npmjs.org/@algolia/requester-fetch/-/requester-fetch-5.48.1.tgz", - "integrity": "sha512-2oDT43Y5HWRSIQMPQI4tA/W+TN/N2tjggZCUsqQV440kxzzoPGsvv9QP1GhQ4CoDa+yn6ygUsGp6Dr+a9sPPSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@algolia/client-common": "5.48.1" - }, - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/@algolia/requester-node-http": { - "version": "5.48.1", - "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-5.48.1.tgz", - "integrity": "sha512-xcaCqbhupVWhuBP1nwbk1XNvwrGljozutEiLx06mvqDf3o8cHyEgQSHS4fKJM+UAggaWVnnFW+Nne5aQ8SUJXg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@algolia/client-common": "5.48.1" - }, - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/@ampproject/remapping": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", - "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@angular-devkit/architect": { - "version": "0.2102.16", - "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.2102.16.tgz", - "integrity": "sha512-FDUKPpq70nJwGK4CICPD31XmesBEGv57Z+JBCPWrTa5mVZIXCQkeo5waIaNfzAnLdbpd74ULJJ3MDNVt4iaGZg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@angular-devkit/core": "21.2.16", - "rxjs": "7.8.2" - }, - "bin": { - "architect": "bin/cli.js" - }, - "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0", - "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", - "yarn": ">= 1.13.0" - } - }, - "node_modules/@angular-devkit/core": { - "version": "21.2.16", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-21.2.16.tgz", - "integrity": "sha512-bRot0dqonxdSuGzXyOYtVJis/u9CJycrfC/aaxLeMF37gKtWIyCR2KFkMRXAoiV/AKk5/NuuqDNqcQS9w5G3Fg==", - "dev": true, - "license": "MIT", - "dependencies": { - "ajv": "8.18.0", - "ajv-formats": "3.0.1", - "jsonc-parser": "3.3.1", - "picomatch": "4.0.4", - "rxjs": "7.8.2", - "source-map": "0.7.6" - }, - "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0", - "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", - "yarn": ">= 1.13.0" - }, - "peerDependencies": { - "chokidar": "^5.0.0" - }, - "peerDependenciesMeta": { - "chokidar": { - "optional": true - } - } - }, - "node_modules/@angular-devkit/schematics": { - "version": "21.2.16", - "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-21.2.16.tgz", - "integrity": "sha512-3wTn2N6iWxYLrRaFDk3J3a6P3OxL+yvYGoDA7pNKfI+Nu0PpTK8BBwhNQD8L5P3US/QGWTkMNbzZ7XxBBfFP/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@angular-devkit/core": "21.2.16", - "jsonc-parser": "3.3.1", - "magic-string": "0.30.21", - "ora": "9.3.0", - "rxjs": "7.8.2" - }, - "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0", - "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", - "yarn": ">= 1.13.0" - } - }, - "node_modules/@angular/build": { - "version": "22.1.2", - "resolved": "https://registry.npmjs.org/@angular/build/-/build-22.1.2.tgz", - "integrity": "sha512-DE/3o17JTel4EBt2BA4DqJYeBBuz5Ef/kf1jL9YZTyJu4SrLr/HI79K14jFr0VRIxzcqG92FdIzfLDxbOesQsg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@ampproject/remapping": "2.3.0", - "@angular-devkit/architect": "0.2201.2", - "@babel/core": "8.0.1", - "@babel/helper-annotate-as-pure": "8.0.0", - "@babel/helper-split-export-declaration": "7.24.7", - "@inquirer/confirm": "6.1.1", - "@vitejs/plugin-basic-ssl": "2.3.0", - "beasties": "0.4.3", - "browserslist": "^4.26.0", - "esbuild": "0.28.1", - "https-proxy-agent": "9.1.0", - "jsonc-parser": "3.3.1", - "listr2": "10.2.2", - "magic-string": "1.0.0", - "mrmime": "2.0.1", - "oxc-parser": "0.142.0", - "parse5-html-rewriting-stream": "8.0.1", - "picomatch": "4.0.5", - "piscina": "5.2.0", - "rolldown": "1.2.0", - "sass": "1.101.0", - "semver": "7.8.5", - "source-map-support": "0.5.21", - "tinyglobby": "0.2.17", - "vite": "8.1.5", - "watchpack": "2.5.2" - }, - "engines": { - "node": "^22.22.3 || ^24.15.0 || >=26.0.0", - "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", - "yarn": ">= 1.13.0" - }, - "optionalDependencies": { - "lmdb": "3.5.6" - }, - "peerDependencies": { - "@angular/compiler": "^22.0.0", - "@angular/compiler-cli": "^22.0.0", - "@angular/core": "^22.0.0", - "@angular/localize": "^22.0.0", - "@angular/platform-browser": "^22.0.0", - "@angular/platform-server": "^22.0.0", - "@angular/service-worker": "^22.0.0", - "@angular/ssr": "^22.1.2", - "istanbul-lib-instrument": "^6.0.0", - "karma": "^6.4.0", - "less": "^4.2.0", - "ng-packagr": "^22.0.0", - "postcss": "^8.4.0", - "rollup": "^4.0.0", - "tailwindcss": "^2.0.0 || ^3.0.0 || ^4.0.0", - "tslib": "^2.3.0", - "typescript": ">=6.0 <6.1", - "vitest": "^4.0.8" - }, - "peerDependenciesMeta": { - "@angular/core": { - "optional": true - }, - "@angular/localize": { - "optional": true - }, - "@angular/platform-browser": { - "optional": true - }, - "@angular/platform-server": { - "optional": true - }, - "@angular/service-worker": { - "optional": true - }, - "@angular/ssr": { - "optional": true - }, - "istanbul-lib-instrument": { - "optional": true - }, - "karma": { - "optional": true - }, - "less": { - "optional": true - }, - "ng-packagr": { - "optional": true - }, - "postcss": { - "optional": true - }, - "rollup": { - "optional": true - }, - "tailwindcss": { - "optional": true - }, - "vitest": { - "optional": true - } - } - }, - "node_modules/@angular/build/node_modules/@angular-devkit/architect": { - "version": "0.2201.2", - "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.2201.2.tgz", - "integrity": "sha512-RRG3JA3hPH0ypbDIyquZt9DDTP5pOMPgqQ/iLSkok1MZdKiOgpk6FGfXCa1ei72SwlX7lnJdq94d6WWdqpbyKg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@angular-devkit/core": "22.1.2", - "rxjs": "7.8.2" - }, - "bin": { - "architect": "bin/cli.js" - }, - "engines": { - "node": "^22.22.3 || ^24.15.0 || >=26.0.0", - "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", - "yarn": ">= 1.13.0" - } - }, - "node_modules/@angular/build/node_modules/@angular-devkit/core": { - "version": "22.1.2", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-22.1.2.tgz", - "integrity": "sha512-tF1oEE7KPs8I08HJQmH5e4GkLUB3+MXXy8t6gMJULaLFxZYP9K1oXRFLappMpdm9OIbEXOChk23hrho0By9aYg==", - "dev": true, - "license": "MIT", - "dependencies": { - "ajv": "8.20.0", - "ajv-formats": "3.0.1", - "jsonc-parser": "3.3.1", - "picomatch": "4.0.5", - "rxjs": "7.8.2", - "source-map": "0.7.6" - }, - "engines": { - "node": "^22.22.3 || ^24.15.0 || >=26.0.0", - "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", - "yarn": ">= 1.13.0" - }, - "peerDependencies": { - "chokidar": "^5.0.0" - }, - "peerDependenciesMeta": { - "chokidar": { - "optional": true - } - } - }, - "node_modules/@angular/build/node_modules/@inquirer/ansi": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/@inquirer/ansi/-/ansi-2.0.7.tgz", - "integrity": "sha512-3eTuUO1vH2cZm2ZKHeQxnOqlTi9EfZDGgIe3BL3I4u+rJHocr9Fz86M4fjYABPvFnQG/gGK551HqDiIcETwU6Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" - } - }, - "node_modules/@angular/build/node_modules/@inquirer/confirm": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-6.1.1.tgz", - "integrity": "sha512-eb8DBZcz/2qHWQda4rk2JiQk5h9QV/cVHi1yjt0f69WFZMRFn0sJTye3EAP8icut8UDMjQPsaH5KbcOogefrFQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@inquirer/core": "^11.2.1", - "@inquirer/type": "^4.0.7" - }, - "engines": { - "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } - } - }, - "node_modules/@angular/build/node_modules/@inquirer/core": { - "version": "11.2.1", - "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-11.2.1.tgz", - "integrity": "sha512-Qd6GJT1yVyrZZCfN8W2qKF5ApmqryXRhRKCuip8h01x2w/esJQ2XIYc6f9abMIHgKQdBfFTSOdbHRLAhuM09UA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@inquirer/ansi": "^2.0.7", - "@inquirer/figures": "^2.0.7", - "@inquirer/type": "^4.0.7", - "cli-width": "^4.1.0", - "fast-wrap-ansi": "^0.2.0", - "mute-stream": "^3.0.0", - "signal-exit": "^4.1.0" - }, - "engines": { - "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } - } - }, - "node_modules/@angular/build/node_modules/@inquirer/figures": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-2.0.7.tgz", - "integrity": "sha512-aJ8TBPOGB6f/2qziPfElISTCEd5XOYTFckA2SGjhNmiKzfK/u4ot3v0DUzGVdUnKjN10EqnnEPck36BkyfLnJw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" - } - }, - "node_modules/@angular/build/node_modules/@inquirer/type": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-4.0.7.tgz", - "integrity": "sha512-t28inv14nMQ1PhKpsJPY+kEs/c00qzeCOS2gTNRyTjG5d6qsVA2fItxW4hkvGZ5lvanGLdtCzVIx5dwdRpN1+g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } - } - }, - "node_modules/@angular/build/node_modules/agent-base": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-9.0.0.tgz", - "integrity": "sha512-TQf59BsZnytt8GdJKLPfUZ54g/iaUL2OWDSFCCvMOhsHduDQxO8xC4PNeyIkVcA5KwL2phPSv0douC0fgWzmnA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 20" - } - }, - "node_modules/@angular/build/node_modules/ajv": { - "version": "8.20.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", - "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/@angular/build/node_modules/entities": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-8.0.0.tgz", - "integrity": "sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=20.19.0" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/@angular/build/node_modules/https-proxy-agent": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-9.1.0.tgz", - "integrity": "sha512-ag87y7cJJ9/3+GxFr8Oy4O5faDsGRGnBGsJj/YjOSsSx/5eadKLYTMPlzuR6obgoCDDm0abAAZitXXQkMOPSpA==", - "dev": true, - "license": "MIT", - "dependencies": { - "agent-base": "9.0.0", - "debug": "^4.3.4", - "proxy-agent-negotiate": "1.1.0" - }, - "engines": { - "node": ">= 20" - } - }, - "node_modules/@angular/build/node_modules/listr2": { - "version": "10.2.2", - "resolved": "https://registry.npmjs.org/listr2/-/listr2-10.2.2.tgz", - "integrity": "sha512-JtNtbZj8q5BnDMR7trpwvwk3RIrANtIVzEUm8w7amp6xelLgyuq+4WZoTH913XaQAoH/cNdYhaNzBPA2U3xbDw==", - "dev": true, - "license": "MIT", - "dependencies": { - "cli-truncate": "^5.2.0", - "eventemitter3": "^5.0.4", - "log-update": "^6.1.0", - "rfdc": "^1.4.1", - "wrap-ansi": "^10.0.0" - }, - "engines": { - "node": ">=22.13.0" - } - }, - "node_modules/@angular/build/node_modules/magic-string": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-1.0.0.tgz", - "integrity": "sha512-CGvjzMN08iv6w1mm4/x3Gh1hLb4VnyRUA15FFpl6CsCIGGoe36k7kY5KNz9QDbSBN5I/fWHM6ZlIkUTa5xdUEA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.5" - } - }, - "node_modules/@angular/build/node_modules/mute-stream": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-3.0.0.tgz", - "integrity": "sha512-dkEJPVvun4FryqBmZ5KhDo0K9iDXAwn08tMLDinNdRBNPcYEDiWYysLcc6k3mjTMlbP9KyylvRpd4wFtwrT9rw==", - "dev": true, - "license": "ISC", - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/@angular/build/node_modules/parse5-html-rewriting-stream": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/parse5-html-rewriting-stream/-/parse5-html-rewriting-stream-8.0.1.tgz", - "integrity": "sha512-NaRku2aMpUN1Sh1Gyk1KWUh2A7EJx2c6qYzvwsPtqhoHoaURshdrceYK3LunVCm3WHhm6FS7Vcczbvdh3/UIVw==", - "dev": true, - "license": "MIT", - "dependencies": { - "entities": "^8.0.0", - "parse5": "^8.0.0", - "parse5-sax-parser": "^8.0.0" - }, - "funding": { - "url": "https://github.com/inikulin/parse5?sponsor=1" - } - }, - "node_modules/@angular/build/node_modules/picomatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", - "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/@angular/build/node_modules/semver": { - "version": "7.8.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", - "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@angular/build/node_modules/wrap-ansi": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-10.0.0.tgz", - "integrity": "sha512-SGcvg80f0wUy2/fXES19feHMz8E0JoXv2uNgHOu4Dgi2OrCy1lqwFYEJz1BLbDI0exjPMe/ZdzZ/YpGECBG/aQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.2.3", - "string-width": "^8.2.0", - "strip-ansi": "^7.1.2" - }, - "engines": { - "node": ">=20" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/@angular/cli": { - "version": "21.2.16", - "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-21.2.16.tgz", - "integrity": "sha512-/O2Bsy4jae/op06ejyfsL6K4cD4yo7TEH9iesD4UPEvcWTnV8lCdmE2oxbc1WGT3DIsZ00yBQhURSbetDPGFCg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@angular-devkit/architect": "0.2102.16", - "@angular-devkit/core": "21.2.16", - "@angular-devkit/schematics": "21.2.16", - "@inquirer/prompts": "7.10.1", - "@listr2/prompt-adapter-inquirer": "3.0.5", - "@modelcontextprotocol/sdk": "1.26.0", - "@schematics/angular": "21.2.16", - "@yarnpkg/lockfile": "1.1.0", - "algoliasearch": "5.48.1", - "ini": "6.0.0", - "jsonc-parser": "3.3.1", - "listr2": "9.0.5", - "npm-package-arg": "13.0.2", - "pacote": "21.5.1", - "parse5-html-rewriting-stream": "8.0.0", - "semver": "7.7.4", - "yargs": "18.0.0", - "zod": "4.3.6" - }, - "bin": { - "ng": "bin/ng.js" - }, - "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0", - "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", - "yarn": ">= 1.13.0" - } - }, - "node_modules/@angular/common": { - "version": "22.1.0", - "resolved": "https://registry.npmjs.org/@angular/common/-/common-22.1.0.tgz", - "integrity": "sha512-67L8AS00egxwEKnoMhNDxy+TY+eKOwvwa+os0Odq8nLm7+Qh7JnMVeub8hfncpenOFqlC/RUjO2W9H7Gd2veNA==", - "license": "MIT", - "dependencies": { - "tslib": "^2.3.0" - }, - "engines": { - "node": "^22.22.3 || ^24.15.0 || >=26.0.0" - }, - "peerDependencies": { - "@angular/core": "22.1.0", - "rxjs": "^6.5.3 || ^7.4.0" - } - }, - "node_modules/@angular/compiler": { - "version": "22.1.0", - "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-22.1.0.tgz", - "integrity": "sha512-WCmuPnuXgqnqrkbrwqQRyldi1k3rlzNLVDl8ntINF7XWuJh0KfQLEkRK0FCCmBztWJkbGug4RBVnKTWlKhRzCQ==", - "license": "MIT", - "dependencies": { - "tslib": "^2.3.0" - }, - "engines": { - "node": "^22.22.3 || ^24.15.0 || >=26.0.0" - } - }, - "node_modules/@angular/compiler-cli": { - "version": "22.1.0", - "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-22.1.0.tgz", - "integrity": "sha512-jL89dbzkrV8AeLaxedBgT7ErMnbfi2dvwDJuCrUgm3eCNfbcOpGbNxzH+wDgvbRg2Lhj11/u3JPbW50xA6rvvg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/core": "8.0.1", - "@jridgewell/sourcemap-codec": "^1.4.14", - "chokidar": "^5.0.0", - "convert-source-map": "^1.5.1", - "reflect-metadata": "^0.2.0", - "semver": "^7.0.0", - "tslib": "^2.3.0", - "yargs": "^18.0.0" - }, - "bin": { - "ng-xi18n": "bundles/src/bin/ng_xi18n.js", - "ngc": "bundles/src/bin/ngc.js" - }, - "engines": { - "node": "^22.22.3 || ^24.15.0 || >=26.0.0" - }, - "peerDependencies": { - "@angular/compiler": "22.1.0", - "typescript": ">=6.0 <6.1" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@angular/core": { - "version": "22.1.0", - "resolved": "https://registry.npmjs.org/@angular/core/-/core-22.1.0.tgz", - "integrity": "sha512-X5UaMuOCI4HAvSQIs3QtM+5e0Cni16DRaHUIL3BIBd4ZQNnSH3pZ25TsKQ8Jlu/3hAQ9rzV278kNQcecooGJ7g==", - "license": "MIT", - "dependencies": { - "tslib": "^2.3.0" - }, - "engines": { - "node": "^22.22.3 || ^24.15.0 || >=26.0.0" - }, - "peerDependencies": { - "@angular/compiler": "22.1.0", - "rxjs": "^6.5.3 || ^7.4.0", - "zone.js": "~0.15.0 || ~0.16.0" - }, - "peerDependenciesMeta": { - "@angular/compiler": { - "optional": true - }, - "zone.js": { - "optional": true - } - } - }, - "node_modules/@angular/forms": { - "version": "22.1.0", - "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-22.1.0.tgz", - "integrity": "sha512-nWlSM/pPp78Sx/fBM/tFEgZxdfZe50LkCE2/hkO22Fi1UM2maGc43LDsu/s6l0q9hFep4Wj+xa30KXDBS7Cn8A==", - "license": "MIT", - "dependencies": { - "@standard-schema/spec": "^1.0.0", - "tslib": "^2.3.0", - "zod": "^4.0.10" - }, - "engines": { - "node": "^22.22.3 || ^24.15.0 || >=26.0.0" - }, - "peerDependencies": { - "@angular/common": "22.1.0", - "@angular/core": "22.1.0", - "@angular/platform-browser": "22.1.0", - "rxjs": "^6.5.3 || ^7.4.0" - } - }, - "node_modules/@angular/platform-browser": { - "version": "22.1.0", - "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-22.1.0.tgz", - "integrity": "sha512-gqUYDUiPfwbaLYdH8WLnOLl3feo3OcNpnMO08HBHaUdi4TLNkC28xwa9fC6ANyYD22QZ5A3abSg8fmR6upWMwg==", - "license": "MIT", - "dependencies": { - "tslib": "^2.3.0" - }, - "engines": { - "node": "^22.22.3 || ^24.15.0 || >=26.0.0" - }, - "peerDependencies": { - "@angular/animations": "22.1.0", - "@angular/common": "22.1.0", - "@angular/core": "22.1.0" - }, - "peerDependenciesMeta": { - "@angular/animations": { - "optional": true - } - } - }, - "node_modules/@angular/router": { - "version": "22.1.0", - "resolved": "https://registry.npmjs.org/@angular/router/-/router-22.1.0.tgz", - "integrity": "sha512-42Bs0g+tV2gE70Lqnt+VD/+DWbvWwQcg8QgXkTIu3A504tYknrZG/wmvki2AJGyZhmyQ46B4pfXLG4WDP8MFSA==", - "license": "MIT", - "dependencies": { - "tslib": "^2.3.0" - }, - "engines": { - "node": "^22.22.3 || ^24.15.0 || >=26.0.0" - }, - "peerDependencies": { - "@angular/common": "22.1.0", - "@angular/core": "22.1.0", - "@angular/platform-browser": "22.1.0", - "rxjs": "^6.5.3 || ^7.4.0" - } - }, - "node_modules/@asamuzakjp/css-color": { - "version": "5.1.11", - "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-5.1.11.tgz", - "integrity": "sha512-KVw6qIiCTUQhByfTd78h2yD1/00waTmm9uy/R7Ck/ctUyAPj+AEDLkQIdJW0T8+qGgj3j5bpNKK7Q3G+LedJWg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@asamuzakjp/generational-cache": "^1.0.1", - "@csstools/css-calc": "^3.2.0", - "@csstools/css-color-parser": "^4.1.0", - "@csstools/css-parser-algorithms": "^4.0.0", - "@csstools/css-tokenizer": "^4.0.0" - }, - "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0" - } - }, - "node_modules/@asamuzakjp/dom-selector": { - "version": "6.8.1", - "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-6.8.1.tgz", - "integrity": "sha512-MvRz1nCqW0fsy8Qz4dnLIvhOlMzqDVBabZx6lH+YywFDdjXhMY37SmpV1XFX3JzG5GWHn63j6HX6QPr3lZXHvQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@asamuzakjp/nwsapi": "^2.3.9", - "bidi-js": "^1.0.3", - "css-tree": "^3.1.0", - "is-potential-custom-element-name": "^1.0.1", - "lru-cache": "^11.2.6" - } - }, - "node_modules/@asamuzakjp/generational-cache": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@asamuzakjp/generational-cache/-/generational-cache-1.0.1.tgz", - "integrity": "sha512-wajfB8KqzMCN2KGNFdLkReeHncd0AslUSrvHVvvYWuU8ghncRJoA50kT3zP9MVL0+9g4/67H+cdvBskj9THPzg==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0" - } - }, - "node_modules/@asamuzakjp/nwsapi": { - "version": "2.3.9", - "resolved": "https://registry.npmjs.org/@asamuzakjp/nwsapi/-/nwsapi-2.3.9.tgz", - "integrity": "sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/@babel/code-frame": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-8.0.0.tgz", - "integrity": "sha512-dYYg153EyN2Ekbqw2zAsbd6/JR+9N2SEoC7YV2GyyqMM7x9bLDTjBD6XBhSMLH0wtIVyJj03jWNriQhaN+eoCw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-validator-identifier": "^8.0.0", - "js-tokens": "^10.0.0" - }, - "engines": { - "node": "^22.18.0 || >=24.11.0" - } - }, - "node_modules/@babel/code-frame/node_modules/@babel/helper-validator-identifier": { - "version": "8.0.4", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-8.0.4.tgz", - "integrity": "sha512-4wFaiLd0bVo4cIoTXI3zKI038NIWE/cr3jvBjejOVYVxV/m8Ltav1USiGzG1fmS5J2RhgEOgXNNK46cRPnRsrg==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^22.18.0 || >=24.11.0" - } - }, - "node_modules/@babel/compat-data": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-8.0.0.tgz", - "integrity": "sha512-DOjnob/cXOUgDOozCDeq/aK2p5y8dUIVdf6tNhEV1HQRd6I8aQ4f4fbtHRVEvb6lP3BGomrKHiS8ICAASSVQSw==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^22.18.0 || >=24.11.0" - } - }, - "node_modules/@babel/core": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-8.0.1.tgz", - "integrity": "sha512-5FgxM4dLQpMJHSiVATk8foW263dVHQHBVpXYiimNECVWG01f4nFyEbQixeT6Mwvg7TayREJ2gpKl3o2RoMdnqw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^8.0.0", - "@babel/generator": "^8.0.0", - "@babel/helper-compilation-targets": "^8.0.0", - "@babel/helpers": "^8.0.0", - "@babel/parser": "^8.0.0", - "@babel/template": "^8.0.0", - "@babel/traverse": "^8.0.0", - "@babel/types": "^8.0.0", - "@types/gensync": "^1.0.5", - "convert-source-map": "^2.0.0", - "empathic": "^2.0.1", - "gensync": "^1.0.0-beta.2", - "import-meta-resolve": "^4.2.0", - "json5": "^2.2.3", - "obug": "^2.1.1", - "semver": "^7.7.3" - }, - "engines": { - "node": "^22.18.0 || >=24.11.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/babel" - } - }, - "node_modules/@babel/core/node_modules/@babel/helper-string-parser": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-8.0.0.tgz", - "integrity": "sha512-6mJgmFFFIIO82vvoLt9XtRC7/TkzXfts1t/SpRX4IHSzMgqoPYCWesVu1udUPUWioAE/2fcG6WuI8zrkE1gwrg==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^22.18.0 || >=24.11.0" - } - }, - "node_modules/@babel/core/node_modules/@babel/helper-validator-identifier": { - "version": "8.0.4", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-8.0.4.tgz", - "integrity": "sha512-4wFaiLd0bVo4cIoTXI3zKI038NIWE/cr3jvBjejOVYVxV/m8Ltav1USiGzG1fmS5J2RhgEOgXNNK46cRPnRsrg==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^22.18.0 || >=24.11.0" - } - }, - "node_modules/@babel/core/node_modules/@babel/types": { - "version": "8.0.4", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-8.0.4.tgz", - "integrity": "sha512-eY+Yn3dCqTGmyiq2QRU66lA5FL8lqqqvecHt0fF3uHONIa7ToYsaCiWV8lOKqAs0Rb2SjixiKFROngnulPtt2g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-string-parser": "^8.0.0", - "@babel/helper-validator-identifier": "^8.0.4" - }, - "engines": { - "node": "^22.18.0 || >=24.11.0" - } - }, - "node_modules/@babel/core/node_modules/convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true, - "license": "MIT" - }, - "node_modules/@babel/generator": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-8.0.0.tgz", - "integrity": "sha512-NT9NrVwJsbSV6Y2FSstWa71EETOnzrjkL5/wX3D2mYHtKM+qvqB1DvR4D0Setb/gDBsHzRICifwEWMO8CnTF6g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^8.0.0", - "@babel/types": "^8.0.0", - "@jridgewell/gen-mapping": "^0.3.12", - "@jridgewell/trace-mapping": "^0.3.28", - "@types/jsesc": "^2.5.0", - "jsesc": "^3.0.2" - }, - "engines": { - "node": "^22.18.0 || >=24.11.0" - } - }, - "node_modules/@babel/generator/node_modules/@babel/helper-string-parser": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-8.0.0.tgz", - "integrity": "sha512-6mJgmFFFIIO82vvoLt9XtRC7/TkzXfts1t/SpRX4IHSzMgqoPYCWesVu1udUPUWioAE/2fcG6WuI8zrkE1gwrg==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^22.18.0 || >=24.11.0" - } - }, - "node_modules/@babel/generator/node_modules/@babel/helper-validator-identifier": { - "version": "8.0.4", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-8.0.4.tgz", - "integrity": "sha512-4wFaiLd0bVo4cIoTXI3zKI038NIWE/cr3jvBjejOVYVxV/m8Ltav1USiGzG1fmS5J2RhgEOgXNNK46cRPnRsrg==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^22.18.0 || >=24.11.0" - } - }, - "node_modules/@babel/generator/node_modules/@babel/types": { - "version": "8.0.4", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-8.0.4.tgz", - "integrity": "sha512-eY+Yn3dCqTGmyiq2QRU66lA5FL8lqqqvecHt0fF3uHONIa7ToYsaCiWV8lOKqAs0Rb2SjixiKFROngnulPtt2g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-string-parser": "^8.0.0", - "@babel/helper-validator-identifier": "^8.0.4" - }, - "engines": { - "node": "^22.18.0 || >=24.11.0" - } - }, - "node_modules/@babel/helper-annotate-as-pure": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-8.0.0.tgz", - "integrity": "sha512-NSpMkMsvvZqzThJ0p1B02cbtA2ObEyfBvq950bmNkyxsxvcxwhvvCB036rKhlEnuBBo30bOrk13u3FzlKSoRrw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^8.0.0" - }, - "engines": { - "node": "^22.18.0 || >=24.11.0" - } - }, - "node_modules/@babel/helper-annotate-as-pure/node_modules/@babel/helper-string-parser": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-8.0.0.tgz", - "integrity": "sha512-6mJgmFFFIIO82vvoLt9XtRC7/TkzXfts1t/SpRX4IHSzMgqoPYCWesVu1udUPUWioAE/2fcG6WuI8zrkE1gwrg==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^22.18.0 || >=24.11.0" - } - }, - "node_modules/@babel/helper-annotate-as-pure/node_modules/@babel/helper-validator-identifier": { - "version": "8.0.4", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-8.0.4.tgz", - "integrity": "sha512-4wFaiLd0bVo4cIoTXI3zKI038NIWE/cr3jvBjejOVYVxV/m8Ltav1USiGzG1fmS5J2RhgEOgXNNK46cRPnRsrg==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^22.18.0 || >=24.11.0" - } - }, - "node_modules/@babel/helper-annotate-as-pure/node_modules/@babel/types": { - "version": "8.0.4", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-8.0.4.tgz", - "integrity": "sha512-eY+Yn3dCqTGmyiq2QRU66lA5FL8lqqqvecHt0fF3uHONIa7ToYsaCiWV8lOKqAs0Rb2SjixiKFROngnulPtt2g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-string-parser": "^8.0.0", - "@babel/helper-validator-identifier": "^8.0.4" - }, - "engines": { - "node": "^22.18.0 || >=24.11.0" - } - }, - "node_modules/@babel/helper-compilation-targets": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-8.0.0.tgz", - "integrity": "sha512-JwculLABZvyPvyLBpwU/E/IbH2uM3mnxNtIJpxnIfb24y1PrdVxK5Dqjle4DpgqpGRnwgC7G8IkzPdSXZrO1Ew==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/compat-data": "^8.0.0", - "@babel/helper-validator-option": "^8.0.0", - "browserslist": "^4.24.0", - "lru-cache": "^11.0.0", - "semver": "^7.7.3" - }, - "engines": { - "node": "^22.18.0 || >=24.11.0" - } - }, - "node_modules/@babel/helper-globals": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-8.0.0.tgz", - "integrity": "sha512-lLozHOM6sWWlxNo8CYqHy4MBZeTvHXNgVPBfPOGsjPKUzHC2Az9QwB6gxdQmpwHl6GlQtbGgS+lj5887guDiLw==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^22.18.0 || >=24.11.0" - } - }, - "node_modules/@babel/helper-split-export-declaration": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.7.tgz", - "integrity": "sha512-oy5V7pD+UvfkEATUKvIjvIAH/xCzfsFVw7ygW2SI6NClZzquT+mwdTfgfdbUiceh6iQO0CHtCPsyze/MZ2YbAA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-string-parser": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", - "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", - "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-option": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-8.0.0.tgz", - "integrity": "sha512-U4Dybxh4WESWHt5XhBeExi4DrY0/DNK1aHpQbsrQXCUbFHuMweT0TpLEWKvaraV2Y6fS+ZXunsZ8zIuZIgvF2Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^22.18.0 || >=24.11.0" - } - }, - "node_modules/@babel/helpers": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-8.0.0.tgz", - "integrity": "sha512-wfbi91pM3py96oIiJEz7qIpyXDytgr9zQC1HEWwlGNVRAEmItuU/0a41ZUKu1sJGyhhOIpc4t5vk4PYzt8wpsg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/template": "^8.0.0", - "@babel/types": "^8.0.0" - }, - "engines": { - "node": "^22.18.0 || >=24.11.0" - } - }, - "node_modules/@babel/helpers/node_modules/@babel/helper-string-parser": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-8.0.0.tgz", - "integrity": "sha512-6mJgmFFFIIO82vvoLt9XtRC7/TkzXfts1t/SpRX4IHSzMgqoPYCWesVu1udUPUWioAE/2fcG6WuI8zrkE1gwrg==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^22.18.0 || >=24.11.0" - } - }, - "node_modules/@babel/helpers/node_modules/@babel/helper-validator-identifier": { - "version": "8.0.4", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-8.0.4.tgz", - "integrity": "sha512-4wFaiLd0bVo4cIoTXI3zKI038NIWE/cr3jvBjejOVYVxV/m8Ltav1USiGzG1fmS5J2RhgEOgXNNK46cRPnRsrg==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^22.18.0 || >=24.11.0" - } - }, - "node_modules/@babel/helpers/node_modules/@babel/types": { - "version": "8.0.4", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-8.0.4.tgz", - "integrity": "sha512-eY+Yn3dCqTGmyiq2QRU66lA5FL8lqqqvecHt0fF3uHONIa7ToYsaCiWV8lOKqAs0Rb2SjixiKFROngnulPtt2g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-string-parser": "^8.0.0", - "@babel/helper-validator-identifier": "^8.0.4" - }, - "engines": { - "node": "^22.18.0 || >=24.11.0" - } - }, - "node_modules/@babel/parser": { - "version": "8.0.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-8.0.4.tgz", - "integrity": "sha512-srpptsAkEbbNIC/q8nT7o+m6CQe8CJUTV/t7MYc9NnWlgYVtHOb7JH6SorxMhN0kuRJjVqXbKClG6xSbPtzz+g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^8.0.4" - }, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": "^22.18.0 || >=24.11.0" - } - }, - "node_modules/@babel/parser/node_modules/@babel/helper-string-parser": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-8.0.0.tgz", - "integrity": "sha512-6mJgmFFFIIO82vvoLt9XtRC7/TkzXfts1t/SpRX4IHSzMgqoPYCWesVu1udUPUWioAE/2fcG6WuI8zrkE1gwrg==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^22.18.0 || >=24.11.0" - } - }, - "node_modules/@babel/parser/node_modules/@babel/helper-validator-identifier": { - "version": "8.0.4", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-8.0.4.tgz", - "integrity": "sha512-4wFaiLd0bVo4cIoTXI3zKI038NIWE/cr3jvBjejOVYVxV/m8Ltav1USiGzG1fmS5J2RhgEOgXNNK46cRPnRsrg==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^22.18.0 || >=24.11.0" - } - }, - "node_modules/@babel/parser/node_modules/@babel/types": { - "version": "8.0.4", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-8.0.4.tgz", - "integrity": "sha512-eY+Yn3dCqTGmyiq2QRU66lA5FL8lqqqvecHt0fF3uHONIa7ToYsaCiWV8lOKqAs0Rb2SjixiKFROngnulPtt2g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-string-parser": "^8.0.0", - "@babel/helper-validator-identifier": "^8.0.4" - }, - "engines": { - "node": "^22.18.0 || >=24.11.0" - } - }, - "node_modules/@babel/template": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-8.0.0.tgz", - "integrity": "sha512-eAD0QW/AlbamBbw0FeGiwasbCVPq5ncW0HNVyLP3B9czqLyh4gvw+5JTSNt6le9+ziAU7mqDZsKTHf3jTb4chQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^8.0.0", - "@babel/parser": "^8.0.0", - "@babel/types": "^8.0.0" - }, - "engines": { - "node": "^22.18.0 || >=24.11.0" - } - }, - "node_modules/@babel/template/node_modules/@babel/helper-string-parser": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-8.0.0.tgz", - "integrity": "sha512-6mJgmFFFIIO82vvoLt9XtRC7/TkzXfts1t/SpRX4IHSzMgqoPYCWesVu1udUPUWioAE/2fcG6WuI8zrkE1gwrg==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^22.18.0 || >=24.11.0" - } - }, - "node_modules/@babel/template/node_modules/@babel/helper-validator-identifier": { - "version": "8.0.4", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-8.0.4.tgz", - "integrity": "sha512-4wFaiLd0bVo4cIoTXI3zKI038NIWE/cr3jvBjejOVYVxV/m8Ltav1USiGzG1fmS5J2RhgEOgXNNK46cRPnRsrg==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^22.18.0 || >=24.11.0" - } - }, - "node_modules/@babel/template/node_modules/@babel/types": { - "version": "8.0.4", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-8.0.4.tgz", - "integrity": "sha512-eY+Yn3dCqTGmyiq2QRU66lA5FL8lqqqvecHt0fF3uHONIa7ToYsaCiWV8lOKqAs0Rb2SjixiKFROngnulPtt2g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-string-parser": "^8.0.0", - "@babel/helper-validator-identifier": "^8.0.4" - }, - "engines": { - "node": "^22.18.0 || >=24.11.0" - } - }, - "node_modules/@babel/traverse": { - "version": "8.0.4", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-8.0.4.tgz", - "integrity": "sha512-bZnmqzGG8UZneG1lLxBoWIH0G6Gr1D846Yu4/3XnY6FhCndMR49u26nTY08u/dAxWmLWF9vGQOuC+84FfIUoeg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^8.0.0", - "@babel/generator": "^8.0.0", - "@babel/helper-globals": "^8.0.0", - "@babel/parser": "^8.0.4", - "@babel/template": "^8.0.0", - "@babel/types": "^8.0.4", - "obug": "^2.1.1" - }, - "engines": { - "node": "^22.18.0 || >=24.11.0" - } - }, - "node_modules/@babel/traverse/node_modules/@babel/helper-string-parser": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-8.0.0.tgz", - "integrity": "sha512-6mJgmFFFIIO82vvoLt9XtRC7/TkzXfts1t/SpRX4IHSzMgqoPYCWesVu1udUPUWioAE/2fcG6WuI8zrkE1gwrg==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^22.18.0 || >=24.11.0" - } - }, - "node_modules/@babel/traverse/node_modules/@babel/helper-validator-identifier": { - "version": "8.0.4", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-8.0.4.tgz", - "integrity": "sha512-4wFaiLd0bVo4cIoTXI3zKI038NIWE/cr3jvBjejOVYVxV/m8Ltav1USiGzG1fmS5J2RhgEOgXNNK46cRPnRsrg==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^22.18.0 || >=24.11.0" - } - }, - "node_modules/@babel/traverse/node_modules/@babel/types": { - "version": "8.0.4", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-8.0.4.tgz", - "integrity": "sha512-eY+Yn3dCqTGmyiq2QRU66lA5FL8lqqqvecHt0fF3uHONIa7ToYsaCiWV8lOKqAs0Rb2SjixiKFROngnulPtt2g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-string-parser": "^8.0.0", - "@babel/helper-validator-identifier": "^8.0.4" - }, - "engines": { - "node": "^22.18.0 || >=24.11.0" - } - }, - "node_modules/@babel/types": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", - "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.28.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@bramus/specificity": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/@bramus/specificity/-/specificity-2.4.2.tgz", - "integrity": "sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw==", - "dev": true, - "license": "MIT", - "dependencies": { - "css-tree": "^3.0.0" - }, - "bin": { - "specificity": "bin/cli.js" - } - }, - "node_modules/@csstools/color-helpers": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-6.0.2.tgz", - "integrity": "sha512-LMGQLS9EuADloEFkcTBR3BwV/CGHV7zyDxVRtVDTwdI2Ca4it0CCVTT9wCkxSgokjE5Ho41hEPgb8OEUwoXr6Q==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "engines": { - "node": ">=20.19.0" - } - }, - "node_modules/@csstools/css-calc": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-3.2.0.tgz", - "integrity": "sha512-bR9e6o2BDB12jzN/gIbjHa5wLJ4UjD1CB9pM7ehlc0ddk6EBz+yYS1EV2MF55/HUxrHcB/hehAyt5vhsA3hx7w==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT", - "engines": { - "node": ">=20.19.0" - }, - "peerDependencies": { - "@csstools/css-parser-algorithms": "^4.0.0", - "@csstools/css-tokenizer": "^4.0.0" - } - }, - "node_modules/@csstools/css-color-parser": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-4.1.0.tgz", - "integrity": "sha512-U0KhLYmy2GVj6q4T3WaAe6NPuFYCPQoE3b0dRGxejWDgcPp8TP7S5rVdM5ZrFaqu4N67X8YaPBw14dQSYx3IyQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT", - "dependencies": { - "@csstools/color-helpers": "^6.0.2", - "@csstools/css-calc": "^3.2.0" - }, - "engines": { - "node": ">=20.19.0" - }, - "peerDependencies": { - "@csstools/css-parser-algorithms": "^4.0.0", - "@csstools/css-tokenizer": "^4.0.0" - } - }, - "node_modules/@csstools/css-parser-algorithms": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-4.0.0.tgz", - "integrity": "sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT", - "engines": { - "node": ">=20.19.0" - }, - "peerDependencies": { - "@csstools/css-tokenizer": "^4.0.0" - } - }, - "node_modules/@csstools/css-syntax-patches-for-csstree": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.1.3.tgz", - "integrity": "sha512-SH60bMfrRCJF3morcdk57WklujF4Jr/EsQUzqkarfHXEFcAR1gg7fS/chAE922Sehgzc1/+Tz5H3Ypa1HiEKrg==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "peerDependencies": { - "css-tree": "^3.2.1" - }, - "peerDependenciesMeta": { - "css-tree": { - "optional": true - } - } - }, - "node_modules/@csstools/css-tokenizer": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-4.0.0.tgz", - "integrity": "sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT", - "engines": { - "node": ">=20.19.0" - } - }, - "node_modules/@emnapi/core": { - "version": "1.11.2", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.2.tgz", - "integrity": "sha512-TC8MkTuZUtcTSiFeuC0ksCh9QIJ5+F21MvZ4Wn4ORfYaFJ/0dsiudv5tVkejgwZlwQ39jL9WWDe2lz8x0WglOA==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/wasi-threads": "1.2.2", - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/runtime": { - "version": "1.11.2", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.2.tgz", - "integrity": "sha512-kyOl3X0DuTiT1h2ft8r2fYO8JYtU9a9Xis/zBSiGArNaagCOWx90N1k2wxp18czFDH+OgcWGb5ZP/XMt3dcyPA==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/wasi-threads": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.2.tgz", - "integrity": "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.1.tgz", - "integrity": "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.1.tgz", - "integrity": "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.1.tgz", - "integrity": "sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.1.tgz", - "integrity": "sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.1.tgz", - "integrity": "sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.1.tgz", - "integrity": "sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.1.tgz", - "integrity": "sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.1.tgz", - "integrity": "sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.1.tgz", - "integrity": "sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.1.tgz", - "integrity": "sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.1.tgz", - "integrity": "sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.1.tgz", - "integrity": "sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.1.tgz", - "integrity": "sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.1.tgz", - "integrity": "sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.1.tgz", - "integrity": "sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.1.tgz", - "integrity": "sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.1.tgz", - "integrity": "sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.1.tgz", - "integrity": "sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.1.tgz", - "integrity": "sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.1.tgz", - "integrity": "sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.1.tgz", - "integrity": "sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.1.tgz", - "integrity": "sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.1.tgz", - "integrity": "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@exodus/bytes": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/@exodus/bytes/-/bytes-1.15.0.tgz", - "integrity": "sha512-UY0nlA+feH81UGSHv92sLEPLCeZFjXOuHhrIo0HQydScuQc8s0A7kL/UdgwgDq8g8ilksmuoF35YVTNphV2aBQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0" - }, - "peerDependencies": { - "@noble/hashes": "^1.8.0 || ^2.0.0" - }, - "peerDependenciesMeta": { - "@noble/hashes": { - "optional": true - } - } - }, - "node_modules/@gar/promise-retry": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@gar/promise-retry/-/promise-retry-1.0.3.tgz", - "integrity": "sha512-GmzA9ckNokPypTg10pgpeHNQe7ph+iIKKmhKu3Ob9ANkswreCx7R3cKmY781K8QK3AqVL3xVh9A42JvIAbkkSA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/@harperfast/extended-iterable": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@harperfast/extended-iterable/-/extended-iterable-1.0.3.tgz", - "integrity": "sha512-sSAYhQca3rDWtQUHSAPeO7axFIUJOI6hn1gjRC5APVE1a90tuyT8f5WIgRsFhhWA7htNkju2veB9eWL6YHi/Lw==", - "dev": true, - "license": "Apache-2.0", - "optional": true - }, - "node_modules/@hono/node-server": { - "version": "1.19.14", - "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.14.tgz", - "integrity": "sha512-GwtvgtXxnWsucXvbQXkRgqksiH2Qed37H9xHZocE5sA3N8O8O8/8FA3uclQXxXVzc9XBZuEOMK7+r02FmSpHtw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18.14.1" - }, - "peerDependencies": { - "hono": "^4" - } - }, - "node_modules/@inquirer/ansi": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@inquirer/ansi/-/ansi-1.0.2.tgz", - "integrity": "sha512-S8qNSZiYzFd0wAcyG5AXCvUHC5Sr7xpZ9wZ2py9XR88jUz8wooStVx5M6dRzczbBWjic9NP7+rY0Xi7qqK/aMQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/@inquirer/checkbox": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@inquirer/checkbox/-/checkbox-4.3.2.tgz", - "integrity": "sha512-VXukHf0RR1doGe6Sm4F0Em7SWYLTHSsbGfJdS9Ja2bX5/D5uwVOEjr07cncLROdBvmnvCATYEWlHqYmXv2IlQA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@inquirer/ansi": "^1.0.2", - "@inquirer/core": "^10.3.2", - "@inquirer/figures": "^1.0.15", - "@inquirer/type": "^3.0.10", - "yoctocolors-cjs": "^2.1.3" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } - } - }, - "node_modules/@inquirer/confirm": { - "version": "5.1.21", - "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-5.1.21.tgz", - "integrity": "sha512-KR8edRkIsUayMXV+o3Gv+q4jlhENF9nMYUZs9PA2HzrXeHI8M5uDag70U7RJn9yyiMZSbtF5/UexBtAVtZGSbQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@inquirer/core": "^10.3.2", - "@inquirer/type": "^3.0.10" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } - } - }, - "node_modules/@inquirer/core": { - "version": "10.3.2", - "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-10.3.2.tgz", - "integrity": "sha512-43RTuEbfP8MbKzedNqBrlhhNKVwoK//vUFNW3Q3vZ88BLcrs4kYpGg+B2mm5p2K/HfygoCxuKwJJiv8PbGmE0A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@inquirer/ansi": "^1.0.2", - "@inquirer/figures": "^1.0.15", - "@inquirer/type": "^3.0.10", - "cli-width": "^4.1.0", - "mute-stream": "^2.0.0", - "signal-exit": "^4.1.0", - "wrap-ansi": "^6.2.0", - "yoctocolors-cjs": "^2.1.3" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } - } - }, - "node_modules/@inquirer/editor": { - "version": "4.2.23", - "resolved": "https://registry.npmjs.org/@inquirer/editor/-/editor-4.2.23.tgz", - "integrity": "sha512-aLSROkEwirotxZ1pBaP8tugXRFCxW94gwrQLxXfrZsKkfjOYC1aRvAZuhpJOb5cu4IBTJdsCigUlf2iCOu4ZDQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@inquirer/core": "^10.3.2", - "@inquirer/external-editor": "^1.0.3", - "@inquirer/type": "^3.0.10" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } - } - }, - "node_modules/@inquirer/expand": { - "version": "4.0.23", - "resolved": "https://registry.npmjs.org/@inquirer/expand/-/expand-4.0.23.tgz", - "integrity": "sha512-nRzdOyFYnpeYTTR2qFwEVmIWypzdAx/sIkCMeTNTcflFOovfqUk+HcFhQQVBftAh9gmGrpFj6QcGEqrDMDOiew==", - "dev": true, - "license": "MIT", - "dependencies": { - "@inquirer/core": "^10.3.2", - "@inquirer/type": "^3.0.10", - "yoctocolors-cjs": "^2.1.3" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } - } - }, - "node_modules/@inquirer/external-editor": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@inquirer/external-editor/-/external-editor-1.0.3.tgz", - "integrity": "sha512-RWbSrDiYmO4LbejWY7ttpxczuwQyZLBUyygsA9Nsv95hpzUWwnNTVQmAq3xuh7vNwCp07UTmE5i11XAEExx4RA==", - "dev": true, - "license": "MIT", - "dependencies": { - "chardet": "^2.1.1", - "iconv-lite": "^0.7.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } - } - }, - "node_modules/@inquirer/figures": { - "version": "1.0.15", - "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.15.tgz", - "integrity": "sha512-t2IEY+unGHOzAaVM5Xx6DEWKeXlDDcNPeDyUpsRc6CUhBfU3VQOEl+Vssh7VNp1dR8MdUJBWhuObjXCsVpjN5g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/@inquirer/input": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/@inquirer/input/-/input-4.3.1.tgz", - "integrity": "sha512-kN0pAM4yPrLjJ1XJBjDxyfDduXOuQHrBB8aLDMueuwUGn+vNpF7Gq7TvyVxx8u4SHlFFj4trmj+a2cbpG4Jn1g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@inquirer/core": "^10.3.2", - "@inquirer/type": "^3.0.10" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } - } - }, - "node_modules/@inquirer/number": { - "version": "3.0.23", - "resolved": "https://registry.npmjs.org/@inquirer/number/-/number-3.0.23.tgz", - "integrity": "sha512-5Smv0OK7K0KUzUfYUXDXQc9jrf8OHo4ktlEayFlelCjwMXz0299Y8OrI+lj7i4gCBY15UObk76q0QtxjzFcFcg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@inquirer/core": "^10.3.2", - "@inquirer/type": "^3.0.10" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } - } - }, - "node_modules/@inquirer/password": { - "version": "4.0.23", - "resolved": "https://registry.npmjs.org/@inquirer/password/-/password-4.0.23.tgz", - "integrity": "sha512-zREJHjhT5vJBMZX/IUbyI9zVtVfOLiTO66MrF/3GFZYZ7T4YILW5MSkEYHceSii/KtRk+4i3RE7E1CUXA2jHcA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@inquirer/ansi": "^1.0.2", - "@inquirer/core": "^10.3.2", - "@inquirer/type": "^3.0.10" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } - } - }, - "node_modules/@inquirer/prompts": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@inquirer/prompts/-/prompts-7.10.1.tgz", - "integrity": "sha512-Dx/y9bCQcXLI5ooQ5KyvA4FTgeo2jYj/7plWfV5Ak5wDPKQZgudKez2ixyfz7tKXzcJciTxqLeK7R9HItwiByg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@inquirer/checkbox": "^4.3.2", - "@inquirer/confirm": "^5.1.21", - "@inquirer/editor": "^4.2.23", - "@inquirer/expand": "^4.0.23", - "@inquirer/input": "^4.3.1", - "@inquirer/number": "^3.0.23", - "@inquirer/password": "^4.0.23", - "@inquirer/rawlist": "^4.1.11", - "@inquirer/search": "^3.2.2", - "@inquirer/select": "^4.4.2" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } - } - }, - "node_modules/@inquirer/rawlist": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@inquirer/rawlist/-/rawlist-4.1.11.tgz", - "integrity": "sha512-+LLQB8XGr3I5LZN/GuAHo+GpDJegQwuPARLChlMICNdwW7OwV2izlCSCxN6cqpL0sMXmbKbFcItJgdQq5EBXTw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@inquirer/core": "^10.3.2", - "@inquirer/type": "^3.0.10", - "yoctocolors-cjs": "^2.1.3" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } - } - }, - "node_modules/@inquirer/search": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/@inquirer/search/-/search-3.2.2.tgz", - "integrity": "sha512-p2bvRfENXCZdWF/U2BXvnSI9h+tuA8iNqtUKb9UWbmLYCRQxd8WkvwWvYn+3NgYaNwdUkHytJMGG4MMLucI1kA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@inquirer/core": "^10.3.2", - "@inquirer/figures": "^1.0.15", - "@inquirer/type": "^3.0.10", - "yoctocolors-cjs": "^2.1.3" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } - } - }, - "node_modules/@inquirer/select": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/@inquirer/select/-/select-4.4.2.tgz", - "integrity": "sha512-l4xMuJo55MAe+N7Qr4rX90vypFwCajSakx59qe/tMaC1aEHWLyw68wF4o0A4SLAY4E0nd+Vt+EyskeDIqu1M6w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@inquirer/ansi": "^1.0.2", - "@inquirer/core": "^10.3.2", - "@inquirer/figures": "^1.0.15", - "@inquirer/type": "^3.0.10", - "yoctocolors-cjs": "^2.1.3" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } - } - }, - "node_modules/@inquirer/type": { - "version": "3.0.10", - "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-3.0.10.tgz", - "integrity": "sha512-BvziSRxfz5Ov8ch0z/n3oijRSEcEsHnhggm4xFZe93DHcUCTlutlq9Ox4SVENAfcRD22UQq7T/atg9Wr3k09eA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } - } - }, - "node_modules/@isaacs/fs-minipass": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", - "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", - "dev": true, - "license": "ISC", - "dependencies": { - "minipass": "^7.0.4" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.13", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", - "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "dev": true, - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.31", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", - "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@listr2/prompt-adapter-inquirer": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@listr2/prompt-adapter-inquirer/-/prompt-adapter-inquirer-3.0.5.tgz", - "integrity": "sha512-WELs+hj6xcilkloBXYf9XXK8tYEnKsgLj01Xl5ONUJpKjmT5hGVUzNUS5tooUxs7pGMrw+jFD/41WpqW4V3LDA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@inquirer/type": "^3.0.8" - }, - "engines": { - "node": ">=20.0.0" - }, - "peerDependencies": { - "@inquirer/prompts": ">= 3 < 8", - "listr2": "9.0.5" - } - }, - "node_modules/@lmdb/lmdb-darwin-arm64": { - "version": "3.5.6", - "resolved": "https://registry.npmjs.org/@lmdb/lmdb-darwin-arm64/-/lmdb-darwin-arm64-3.5.6.tgz", - "integrity": "sha512-mY5FG4TjPAkY4P0w+OhHaUka5mDh2TX2WKYIwuKzJ1zeW3VvRgxdam/lGJTquI+bthTx5CSHDW+BAQCnNAzkEA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@lmdb/lmdb-darwin-x64": { - "version": "3.5.6", - "resolved": "https://registry.npmjs.org/@lmdb/lmdb-darwin-x64/-/lmdb-darwin-x64-3.5.6.tgz", - "integrity": "sha512-foa+pwitysO8k+xhs7psBFfTKnVgR69NlZRRTHaFVDqphh7AdGpLeyRzKw/ofatr/sN6TiHRRW6mmop0ZrrppQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@lmdb/lmdb-linux-arm": { - "version": "3.5.6", - "resolved": "https://registry.npmjs.org/@lmdb/lmdb-linux-arm/-/lmdb-linux-arm-3.5.6.tgz", - "integrity": "sha512-QR4YRyR5h5Z8eGXrNQjiyo2NNDfqi3tCc9dQG5Is1blCt+qWw1ZoBWhlWAr5d+jshkifMIJjVHzHGKbkKzF8Tw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@lmdb/lmdb-linux-arm64": { - "version": "3.5.6", - "resolved": "https://registry.npmjs.org/@lmdb/lmdb-linux-arm64/-/lmdb-linux-arm64-3.5.6.tgz", - "integrity": "sha512-HmiyFFdJa38s1heCMSooSPaBSFTHJ3C+ERPp28xAPlDX1YiALJVOgbry065nXd8Y7KISWjnw05zpG1RX8IfftA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@lmdb/lmdb-linux-x64": { - "version": "3.5.6", - "resolved": "https://registry.npmjs.org/@lmdb/lmdb-linux-x64/-/lmdb-linux-x64-3.5.6.tgz", - "integrity": "sha512-ADzCuCF2cTNiX9kDScqcz1fjnAkxPpQNneV3KFTdV3wWtVlI2sTGzySoMTgDpinkMMFj1NTJlxA6XR8fwc4hlA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@lmdb/lmdb-win32-arm64": { - "version": "3.5.6", - "resolved": "https://registry.npmjs.org/@lmdb/lmdb-win32-arm64/-/lmdb-win32-arm64-3.5.6.tgz", - "integrity": "sha512-J7A9aEQsQiv0TYtBGL7NDIPp2lOS8nnl+zm4sWZm1xlsTTaQ4PgD096Adzdrk27rw3UxCkDXdCUa4ax41oztBQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@lmdb/lmdb-win32-x64": { - "version": "3.5.6", - "resolved": "https://registry.npmjs.org/@lmdb/lmdb-win32-x64/-/lmdb-win32-x64-3.5.6.tgz", - "integrity": "sha512-1g7G0knRX2iV/voDu54yxrGqw5Dk0w2oIYb7dgJq8IkOi+m7wbD8Q3QpPFjh0C01G58S88dqGn03len6UPCXsg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@modelcontextprotocol/sdk": { - "version": "1.26.0", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.26.0.tgz", - "integrity": "sha512-Y5RmPncpiDtTXDbLKswIJzTqu2hyBKxTNsgKqKclDbhIgg1wgtf1fRuvxgTnRfcnxtvvgbIEcqUOzZrJ6iSReg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@hono/node-server": "^1.19.9", - "ajv": "^8.17.1", - "ajv-formats": "^3.0.1", - "content-type": "^1.0.5", - "cors": "^2.8.5", - "cross-spawn": "^7.0.5", - "eventsource": "^3.0.2", - "eventsource-parser": "^3.0.0", - "express": "^5.2.1", - "express-rate-limit": "^8.2.1", - "hono": "^4.11.4", - "jose": "^6.1.3", - "json-schema-typed": "^8.0.2", - "pkce-challenge": "^5.0.0", - "raw-body": "^3.0.0", - "zod": "^3.25 || ^4.0", - "zod-to-json-schema": "^3.25.1" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@cfworker/json-schema": "^4.1.1", - "zod": "^3.25 || ^4.0" - }, - "peerDependenciesMeta": { - "@cfworker/json-schema": { - "optional": true - }, - "zod": { - "optional": false - } - } - }, - "node_modules/@msgpackr-extract/msgpackr-extract-darwin-arm64": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-arm64/-/msgpackr-extract-darwin-arm64-3.0.4.tgz", - "integrity": "sha512-LCkGo6JDfaBhgST7UpPWgNgLINpcpabaHfyz5OBx75nUYxBsaEPxjnyNjWpeb/xBup/682QnBfRBy2/LvPutZQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@msgpackr-extract/msgpackr-extract-darwin-x64": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-x64/-/msgpackr-extract-darwin-x64-3.0.4.tgz", - "integrity": "sha512-zExlW9zUJKZH/tOtVMttwjKa4Xm/3KcNjnE3dPN92uCktwavMxpgCA3MoJK/DOnTWsQgo224OaST27/mPNAf+w==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@msgpackr-extract/msgpackr-extract-linux-arm": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-arm/-/msgpackr-extract-linux-arm-3.0.4.tgz", - "integrity": "sha512-Tg3yX65f5GbtXLkrYEHE5oibZG9epyYWas7FogTTEJeDEF9JlXJzKgXaNhT3UXlTOeA+AfZpYZYZ0uPj7Cfquw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@msgpackr-extract/msgpackr-extract-linux-arm64": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-arm64/-/msgpackr-extract-linux-arm64-3.0.4.tgz", - "integrity": "sha512-dgX0P/9wGPJeHFBG+ZmhgE6bmtMt7NP5CRBGyyktpopdk/mW4POnrpQsSLtKI1dwpc+pPLuXHDh6vvskyQE/sw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@msgpackr-extract/msgpackr-extract-linux-x64": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-x64/-/msgpackr-extract-linux-x64-3.0.4.tgz", - "integrity": "sha512-8TNXMEjJc3QEy7R/x1INhgiU+XakDAFUzBhaz7+Rbrs8NH5UQeHQxxmzsSBJGyV6I1jW79undiQm8tOI+D+8FQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@msgpackr-extract/msgpackr-extract-win32-x64": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-win32-x64/-/msgpackr-extract-win32-x64-3.0.4.tgz", - "integrity": "sha512-CmCXPQrkbwExx3j946/PtHWHbYJiCRBRDl4BlkRQcJB/YOwQxJRTpoo7aTsortjgoJ1x7opzTSxn7C+ASSLVjQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@napi-rs/nice": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@napi-rs/nice/-/nice-1.1.1.tgz", - "integrity": "sha512-xJIPs+bYuc9ASBl+cvGsKbGrJmS6fAKaSZCnT0lhahT5rhA2VVy9/EcIgd2JhtEuFOJNx7UHNn/qiTPTY4nrQw==", - "dev": true, - "license": "MIT", - "optional": true, - "engines": { - "node": ">= 10" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Brooooooklyn" - }, - "optionalDependencies": { - "@napi-rs/nice-android-arm-eabi": "1.1.1", - "@napi-rs/nice-android-arm64": "1.1.1", - "@napi-rs/nice-darwin-arm64": "1.1.1", - "@napi-rs/nice-darwin-x64": "1.1.1", - "@napi-rs/nice-freebsd-x64": "1.1.1", - "@napi-rs/nice-linux-arm-gnueabihf": "1.1.1", - "@napi-rs/nice-linux-arm64-gnu": "1.1.1", - "@napi-rs/nice-linux-arm64-musl": "1.1.1", - "@napi-rs/nice-linux-ppc64-gnu": "1.1.1", - "@napi-rs/nice-linux-riscv64-gnu": "1.1.1", - "@napi-rs/nice-linux-s390x-gnu": "1.1.1", - "@napi-rs/nice-linux-x64-gnu": "1.1.1", - "@napi-rs/nice-linux-x64-musl": "1.1.1", - "@napi-rs/nice-openharmony-arm64": "1.1.1", - "@napi-rs/nice-win32-arm64-msvc": "1.1.1", - "@napi-rs/nice-win32-ia32-msvc": "1.1.1", - "@napi-rs/nice-win32-x64-msvc": "1.1.1" - } - }, - "node_modules/@napi-rs/nice-android-arm-eabi": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@napi-rs/nice-android-arm-eabi/-/nice-android-arm-eabi-1.1.1.tgz", - "integrity": "sha512-kjirL3N6TnRPv5iuHw36wnucNqXAO46dzK9oPb0wj076R5Xm8PfUVA9nAFB5ZNMmfJQJVKACAPd/Z2KYMppthw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@napi-rs/nice-android-arm64": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@napi-rs/nice-android-arm64/-/nice-android-arm64-1.1.1.tgz", - "integrity": "sha512-blG0i7dXgbInN5urONoUCNf+DUEAavRffrO7fZSeoRMJc5qD+BJeNcpr54msPF6qfDD6kzs9AQJogZvT2KD5nw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@napi-rs/nice-darwin-arm64": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@napi-rs/nice-darwin-arm64/-/nice-darwin-arm64-1.1.1.tgz", - "integrity": "sha512-s/E7w45NaLqTGuOjC2p96pct4jRfo61xb9bU1unM/MJ/RFkKlJyJDx7OJI/O0ll/hrfpqKopuAFDV8yo0hfT7A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@napi-rs/nice-darwin-x64": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@napi-rs/nice-darwin-x64/-/nice-darwin-x64-1.1.1.tgz", - "integrity": "sha512-dGoEBnVpsdcC+oHHmW1LRK5eiyzLwdgNQq3BmZIav+9/5WTZwBYX7r5ZkQC07Nxd3KHOCkgbHSh4wPkH1N1LiQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@napi-rs/nice-freebsd-x64": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@napi-rs/nice-freebsd-x64/-/nice-freebsd-x64-1.1.1.tgz", - "integrity": "sha512-kHv4kEHAylMYmlNwcQcDtXjklYp4FCf0b05E+0h6nDHsZ+F0bDe04U/tXNOqrx5CmIAth4vwfkjjUmp4c4JktQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@napi-rs/nice-linux-arm-gnueabihf": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-arm-gnueabihf/-/nice-linux-arm-gnueabihf-1.1.1.tgz", - "integrity": "sha512-E1t7K0efyKXZDoZg1LzCOLxgolxV58HCkaEkEvIYQx12ht2pa8hoBo+4OB3qh7e+QiBlp1SRf+voWUZFxyhyqg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@napi-rs/nice-linux-arm64-gnu": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-arm64-gnu/-/nice-linux-arm64-gnu-1.1.1.tgz", - "integrity": "sha512-CIKLA12DTIZlmTaaKhQP88R3Xao+gyJxNWEn04wZwC2wmRapNnxCUZkVwggInMJvtVElA+D4ZzOU5sX4jV+SmQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@napi-rs/nice-linux-arm64-musl": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-arm64-musl/-/nice-linux-arm64-musl-1.1.1.tgz", - "integrity": "sha512-+2Rzdb3nTIYZ0YJF43qf2twhqOCkiSrHx2Pg6DJaCPYhhaxbLcdlV8hCRMHghQ+EtZQWGNcS2xF4KxBhSGeutg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@napi-rs/nice-linux-ppc64-gnu": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-ppc64-gnu/-/nice-linux-ppc64-gnu-1.1.1.tgz", - "integrity": "sha512-4FS8oc0GeHpwvv4tKciKkw3Y4jKsL7FRhaOeiPei0X9T4Jd619wHNe4xCLmN2EMgZoeGg+Q7GY7BsvwKpL22Tg==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@napi-rs/nice-linux-riscv64-gnu": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-riscv64-gnu/-/nice-linux-riscv64-gnu-1.1.1.tgz", - "integrity": "sha512-HU0nw9uD4FO/oGCCk409tCi5IzIZpH2agE6nN4fqpwVlCn5BOq0MS1dXGjXaG17JaAvrlpV5ZeyZwSon10XOXw==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@napi-rs/nice-linux-s390x-gnu": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-s390x-gnu/-/nice-linux-s390x-gnu-1.1.1.tgz", - "integrity": "sha512-2YqKJWWl24EwrX0DzCQgPLKQBxYDdBxOHot1KWEq7aY2uYeX+Uvtv4I8xFVVygJDgf6/92h9N3Y43WPx8+PAgQ==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@napi-rs/nice-linux-x64-gnu": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-x64-gnu/-/nice-linux-x64-gnu-1.1.1.tgz", - "integrity": "sha512-/gaNz3R92t+dcrfCw/96pDopcmec7oCcAQ3l/M+Zxr82KT4DljD37CpgrnXV+pJC263JkW572pdbP3hP+KjcIg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@napi-rs/nice-linux-x64-musl": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-x64-musl/-/nice-linux-x64-musl-1.1.1.tgz", - "integrity": "sha512-xScCGnyj/oppsNPMnevsBe3pvNaoK7FGvMjT35riz9YdhB2WtTG47ZlbxtOLpjeO9SqqQ2J2igCmz6IJOD5JYw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@napi-rs/nice-openharmony-arm64": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@napi-rs/nice-openharmony-arm64/-/nice-openharmony-arm64-1.1.1.tgz", - "integrity": "sha512-6uJPRVwVCLDeoOaNyeiW0gp2kFIM4r7PL2MczdZQHkFi9gVlgm+Vn+V6nTWRcu856mJ2WjYJiumEajfSm7arPQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@napi-rs/nice-win32-arm64-msvc": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@napi-rs/nice-win32-arm64-msvc/-/nice-win32-arm64-msvc-1.1.1.tgz", - "integrity": "sha512-uoTb4eAvM5B2aj/z8j+Nv8OttPf2m+HVx3UjA5jcFxASvNhQriyCQF1OB1lHL43ZhW+VwZlgvjmP5qF3+59atA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@napi-rs/nice-win32-ia32-msvc": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@napi-rs/nice-win32-ia32-msvc/-/nice-win32-ia32-msvc-1.1.1.tgz", - "integrity": "sha512-CNQqlQT9MwuCsg1Vd/oKXiuH+TcsSPJmlAFc5frFyX/KkOh0UpBLEj7aoY656d5UKZQMQFP7vJNa1DNUNORvug==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@napi-rs/nice-win32-x64-msvc": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@napi-rs/nice-win32-x64-msvc/-/nice-win32-x64-msvc-1.1.1.tgz", - "integrity": "sha512-vB+4G/jBQCAh0jelMTY3+kgFy00Hlx2f2/1zjMoH821IbplbWZOkLiTYXQkygNTzQJTq5cvwBDgn2ppHD+bglQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@napi-rs/wasm-runtime": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.2.2.tgz", - "integrity": "sha512-JfB4kuJQjaoHuCTseIINHtHWeJnvgEcxjwA5t/Y00ZgaOO1Crz3fjT/p8kT28zA/Caz7oiUMn3d6H2yOVCVwuw==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@tybys/wasm-util": "^0.10.3" - }, - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=23.5.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Brooooooklyn" - }, - "peerDependencies": { - "@emnapi/core": "^1.7.1 || ^2.0.0-alpha.3", - "@emnapi/runtime": "^1.7.1 || ^2.0.0-alpha.3" - } - }, - "node_modules/@npmcli/agent": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@npmcli/agent/-/agent-4.0.2.tgz", - "integrity": "sha512-EUEuWAxnL07Sp5/iC/1X6Xj+XThUvnbei9zfRWZdEXa7lss9RTHMhAHBeg+MZ5To9s/gGaSI+UwZTPdYMvKSeg==", - "dev": true, - "license": "ISC", - "dependencies": { - "agent-base": "^7.1.0", - "http-proxy-agent": "^7.0.0", - "https-proxy-agent": "^7.0.1", - "lru-cache": "^11.2.1", - "socks-proxy-agent": "^8.0.3" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/@npmcli/fs": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-5.0.0.tgz", - "integrity": "sha512-7OsC1gNORBEawOa5+j2pXN9vsicaIOH5cPXxoR6fJOmH6/EXpJB2CajXOu1fPRFun2m1lktEFX11+P89hqO/og==", - "dev": true, - "license": "ISC", - "dependencies": { - "semver": "^7.3.5" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/@npmcli/git": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@npmcli/git/-/git-7.0.2.tgz", - "integrity": "sha512-oeolHDjExNAJAnlYP2qzNjMX/Xi9bmu78C9dIGr4xjobrSKbuMYCph8lTzn4vnW3NjIqVmw/f8BCfouqyJXlRg==", - "dev": true, - "license": "ISC", - "dependencies": { - "@gar/promise-retry": "^1.0.0", - "@npmcli/promise-spawn": "^9.0.0", - "ini": "^6.0.0", - "lru-cache": "^11.2.1", - "npm-pick-manifest": "^11.0.1", - "proc-log": "^6.0.0", - "semver": "^7.3.5", - "which": "^6.0.0" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/@npmcli/git/node_modules/isexe": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-4.0.0.tgz", - "integrity": "sha512-FFUtZMpoZ8RqHS3XeXEmHWLA4thH+ZxCv2lOiPIn1Xc7CxrqhWzNSDzD+/chS/zbYezmiwWLdQC09JdQKmthOw==", - "dev": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=20" - } - }, - "node_modules/@npmcli/git/node_modules/which": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/which/-/which-6.0.1.tgz", - "integrity": "sha512-oGLe46MIrCRqX7ytPUf66EAYvdeMIZYn3WaocqqKZAxrBpkqHfL/qvTyJ/bTk5+AqHCjXmrv3CEWgy368zhRUg==", - "dev": true, - "license": "ISC", - "dependencies": { - "isexe": "^4.0.0" - }, - "bin": { - "node-which": "bin/which.js" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/@npmcli/installed-package-contents": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@npmcli/installed-package-contents/-/installed-package-contents-4.0.0.tgz", - "integrity": "sha512-yNyAdkBxB72gtZ4GrwXCM0ZUedo9nIbOMKfGjt6Cu6DXf0p8y1PViZAKDC8q8kv/fufx0WTjRBdSlyrvnP7hmA==", - "dev": true, - "license": "ISC", - "dependencies": { - "npm-bundled": "^5.0.0", - "npm-normalize-package-bin": "^5.0.0" - }, - "bin": { - "installed-package-contents": "bin/index.js" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/@npmcli/node-gyp": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@npmcli/node-gyp/-/node-gyp-5.0.0.tgz", - "integrity": "sha512-uuG5HZFXLfyFKqg8QypsmgLQW7smiRjVc45bqD/ofZZcR/uxEjgQU8qDPv0s9TEeMUiAAU/GC5bR6++UdTirIQ==", - "dev": true, - "license": "ISC", - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/@npmcli/package-json": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/@npmcli/package-json/-/package-json-7.0.5.tgz", - "integrity": "sha512-iVuTlG3ORq2iaVa1IWUxAO/jIp77tUKBhoMjuzYW2kL4MLN1bi/ofqkZ7D7OOwh8coAx1/S2ge0rMdGv8sLSOQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "@npmcli/git": "^7.0.0", - "glob": "^13.0.0", - "hosted-git-info": "^9.0.0", - "json-parse-even-better-errors": "^5.0.0", - "proc-log": "^6.0.0", - "semver": "^7.5.3", - "spdx-expression-parse": "^4.0.0" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/@npmcli/promise-spawn": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/@npmcli/promise-spawn/-/promise-spawn-9.0.1.tgz", - "integrity": "sha512-OLUaoqBuyxeTqUvjA3FZFiXUfYC1alp3Sa99gW3EUDz3tZ3CbXDdcZ7qWKBzicrJleIgucoWamWH1saAmH/l2Q==", - "dev": true, - "license": "ISC", - "dependencies": { - "which": "^6.0.0" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/@npmcli/promise-spawn/node_modules/isexe": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-4.0.0.tgz", - "integrity": "sha512-FFUtZMpoZ8RqHS3XeXEmHWLA4thH+ZxCv2lOiPIn1Xc7CxrqhWzNSDzD+/chS/zbYezmiwWLdQC09JdQKmthOw==", - "dev": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=20" - } - }, - "node_modules/@npmcli/promise-spawn/node_modules/which": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/which/-/which-6.0.1.tgz", - "integrity": "sha512-oGLe46MIrCRqX7ytPUf66EAYvdeMIZYn3WaocqqKZAxrBpkqHfL/qvTyJ/bTk5+AqHCjXmrv3CEWgy368zhRUg==", - "dev": true, - "license": "ISC", - "dependencies": { - "isexe": "^4.0.0" - }, - "bin": { - "node-which": "bin/which.js" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/@npmcli/redact": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@npmcli/redact/-/redact-4.0.0.tgz", - "integrity": "sha512-gOBg5YHMfZy+TfHArfVogwgfBeQnKbbGo3pSUyK/gSI0AVu+pEiDVcKlQb0D8Mg1LNRZILZ6XG8I5dJ4KuAd9Q==", - "dev": true, - "license": "ISC", - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/@npmcli/run-script": { - "version": "10.0.4", - "resolved": "https://registry.npmjs.org/@npmcli/run-script/-/run-script-10.0.4.tgz", - "integrity": "sha512-mGUWr1uMnf0le2TwfOZY4SFxZGXGfm4Jtay/nwAa2FLNAKXUoUwaGwBMNH36UHPtinWfTSJ3nqFQr0091CxVGg==", - "dev": true, - "license": "ISC", - "dependencies": { - "@npmcli/node-gyp": "^5.0.0", - "@npmcli/package-json": "^7.0.0", - "@npmcli/promise-spawn": "^9.0.0", - "node-gyp": "^12.1.0", - "proc-log": "^6.0.0" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/@oxc-parser/binding-android-arm-eabi": { - "version": "0.142.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-android-arm-eabi/-/binding-android-arm-eabi-0.142.0.tgz", - "integrity": "sha512-ZiRGDutGsv1G6bL/ozy/koC0Sv39T1DqyoC4KD1DOy9ZoACm1O5UWhEK2c02Qdk+4lfLVkvFa/mQ0fm/4h1BtQ==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@oxc-parser/binding-android-arm64": { - "version": "0.142.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-android-arm64/-/binding-android-arm64-0.142.0.tgz", - "integrity": "sha512-WZkvGRLNQTz8lR9zP5nLjUdlroRCopBu3g9zF1p/laE6DzT1UbQo8Rdz5MWhaJUPYg/6gp+jo7HUgsyKaN1FtQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@oxc-parser/binding-darwin-arm64": { - "version": "0.142.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-darwin-arm64/-/binding-darwin-arm64-0.142.0.tgz", - "integrity": "sha512-l4khS8LQOOVYsGRVARo1gSaCT/aBSceUVXgtovWc2+drnxVuDr082WA3OCHVdVzIz5JIrP/y9CWsSKxBDNmYGg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@oxc-parser/binding-darwin-x64": { - "version": "0.142.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-darwin-x64/-/binding-darwin-x64-0.142.0.tgz", - "integrity": "sha512-QBsNF3nqlXmcH2B1YOPqQYmCJoy4HuIjUxGbBO/k5JAJUl68ghU2psRY2zPk+RyBaWqKP/qfL4oaFgEMCdwskA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@oxc-parser/binding-freebsd-x64": { - "version": "0.142.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-freebsd-x64/-/binding-freebsd-x64-0.142.0.tgz", - "integrity": "sha512-b7Q7m4Cqc6XqNhri3R+QhU+GVy646Pn+bkdhrDdWym/Fdi0ZUa+d73H9dm5H91JtbtAQ/z1d8XKMW3oOV8a4tQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@oxc-parser/binding-linux-arm-gnueabihf": { - "version": "0.142.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-0.142.0.tgz", - "integrity": "sha512-3riVS5IhdH3uCZj1Y9ftDQlR0dvLsIlw/edrRqk8JhgNd5K0XSs+UBtgh50N13CAlW9/TXj6sVGXaKNBocd0Yg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@oxc-parser/binding-linux-arm-musleabihf": { - "version": "0.142.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-0.142.0.tgz", - "integrity": "sha512-NmXUOpgpTSkhl795TiXmWppTwmSJ92RC1qvD6e4XOF+slgmo3e6Ah+kEu+6AN8s7NAOEwqGmir58MgSQSWmBSA==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@oxc-parser/binding-linux-arm64-gnu": { - "version": "0.142.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-0.142.0.tgz", - "integrity": "sha512-gc0EXsKtXgerujmU2Bql3u1L1HsSQ2774R83idq/FoNMPVV/RY/1ErFsvnit7KoiP/sLvzQixeUo4Ut0ic0wmw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@oxc-parser/binding-linux-arm64-musl": { - "version": "0.142.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm64-musl/-/binding-linux-arm64-musl-0.142.0.tgz", - "integrity": "sha512-F2XvmWSE0uWpie+jHKKIFgdVOe9ypGhkEZxKx5DuW215K6cbAC274yYaPkcM7EqY4Df3Weyhpcz3lsURyH2LVg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@oxc-parser/binding-linux-ppc64-gnu": { - "version": "0.142.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-0.142.0.tgz", - "integrity": "sha512-wLMbT21U/QxknQsk+VvNF0b9D2/aGWhcaQQQ+VYlE8FwD5+GoWZIPPXNzyHmkYyhm0KB3itL+TBavjMatqNnYA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@oxc-parser/binding-linux-riscv64-gnu": { - "version": "0.142.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-0.142.0.tgz", - "integrity": "sha512-+G8F/4ckwT7FCJV4H2bt09xEzJbjNCfuL4Sp1AYNaFtFMVtgIGMuJlteT82U+K0UIZ/DzAR/LDlMFnEuajG7Kw==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@oxc-parser/binding-linux-riscv64-musl": { - "version": "0.142.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-0.142.0.tgz", - "integrity": "sha512-hTsHtTLxMAfCo+rpF5K3qZJKW2NpPN/CHd4mYB3y7XlSdspHkd2gehDIofP64AacA9nWQw2tY3O7wR6UY8IVOA==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@oxc-parser/binding-linux-s390x-gnu": { - "version": "0.142.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-0.142.0.tgz", - "integrity": "sha512-6y7qYY3TCUDYjqswImdTGl92y+KA/80twALegQPN27kfY+bG7Ib1+L3jbmrCZQx6wrVnai9IPsEZp07I0hx7JQ==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@oxc-parser/binding-linux-x64-gnu": { - "version": "0.142.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-x64-gnu/-/binding-linux-x64-gnu-0.142.0.tgz", - "integrity": "sha512-i69kAWU+2LgoH5bR+zWiiu+UzAw7Oxkwv7COeJTeY19pn4e70nKQcr9Pm6cL2Z0Z54d+gl9qADlK/0yyuCPiBA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@oxc-parser/binding-linux-x64-musl": { - "version": "0.142.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-x64-musl/-/binding-linux-x64-musl-0.142.0.tgz", - "integrity": "sha512-4SQs678MmjYVrmhAgCWD4o0vpaFszXw9xLX5p2Z9MMFcltxiLkA88wQjh80YHjPrXtpyZ2CWI5m+1yNKM0m2Pw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@oxc-parser/binding-openharmony-arm64": { - "version": "0.142.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-openharmony-arm64/-/binding-openharmony-arm64-0.142.0.tgz", - "integrity": "sha512-YHpx9N7Ln3a++Tc8rv+H7mrK1zyJQOAwCFg8LZ3lTs1T5afGWeZrLPhPT9HLnIwSjCyJqPWVMIrMxbjcmBr2oQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@oxc-parser/binding-wasm32-wasi": { - "version": "0.142.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-wasm32-wasi/-/binding-wasm32-wasi-0.142.0.tgz", - "integrity": "sha512-3pLDyY3+oogW73RM5uehNgAiR/Xfb7fvO2Q1Z1gIqZ2+50XDVQmBVlRkHXZTU4gKnQHpwETNsYQVsJ3joVB2iA==", - "cpu": [ - "wasm32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/core": "1.11.2", - "@emnapi/runtime": "1.11.2", - "@napi-rs/wasm-runtime": "^1.1.6" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@oxc-parser/binding-win32-arm64-msvc": { - "version": "0.142.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-0.142.0.tgz", - "integrity": "sha512-Had/VeVY28Oyb0K+Q4FV8KCzoBycIh93oDK6pCbya9lkzdq+ikMHMgBubsdqqlybjJmQRawCQRrnBRHyQwYvcQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@oxc-parser/binding-win32-ia32-msvc": { - "version": "0.142.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-0.142.0.tgz", - "integrity": "sha512-GGi3+YphVHavvgs6gum2UXoNCqzHAmPt/nXkn8ZQZstV2Q1qZD1Mn8fz/nWrDkefHQtrG/+1/XrbMxsBTo6Svw==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@oxc-parser/binding-win32-x64-msvc": { - "version": "0.142.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-win32-x64-msvc/-/binding-win32-x64-msvc-0.142.0.tgz", - "integrity": "sha512-Ny/Wv4Us1LGC/ljwNTp+Hx3r/pH15EFfeDF0p+n898gt+TtRd6C9SccHcuUhDiNTb8s5tt7jdeAMDRQZ4Vq6hg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@oxc-project/types": { - "version": "0.142.0", - "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.142.0.tgz", - "integrity": "sha512-7W+2q5AKQVU36fkaryontrHn3YDt1RyUYXatw9i5H8ocYe2sPKSFB6eS8WNPeRKiN1qAWWZUPm7gwFzJGrccqQ==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/Boshen" - } - }, - "node_modules/@parcel/watcher": { - "version": "2.5.6", - "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.6.tgz", - "integrity": "sha512-tmmZ3lQxAe/k/+rNnXQRawJ4NjxO2hqiOLTHvWchtGZULp4RyFeh6aU4XdOYBFe2KE1oShQTv4AblOs2iOrNnQ==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "dependencies": { - "detect-libc": "^2.0.3", - "is-glob": "^4.0.3", - "node-addon-api": "^7.0.0", - "picomatch": "^4.0.3" - }, - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - }, - "optionalDependencies": { - "@parcel/watcher-android-arm64": "2.5.6", - "@parcel/watcher-darwin-arm64": "2.5.6", - "@parcel/watcher-darwin-x64": "2.5.6", - "@parcel/watcher-freebsd-x64": "2.5.6", - "@parcel/watcher-linux-arm-glibc": "2.5.6", - "@parcel/watcher-linux-arm-musl": "2.5.6", - "@parcel/watcher-linux-arm64-glibc": "2.5.6", - "@parcel/watcher-linux-arm64-musl": "2.5.6", - "@parcel/watcher-linux-x64-glibc": "2.5.6", - "@parcel/watcher-linux-x64-musl": "2.5.6", - "@parcel/watcher-win32-arm64": "2.5.6", - "@parcel/watcher-win32-ia32": "2.5.6", - "@parcel/watcher-win32-x64": "2.5.6" - } - }, - "node_modules/@parcel/watcher-android-arm64": { - "version": "2.5.6", - "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.6.tgz", - "integrity": "sha512-YQxSS34tPF/6ZG7r/Ih9xy+kP/WwediEUsqmtf0cuCV5TPPKw/PQHRhueUo6JdeFJaqV3pyjm0GdYjZotbRt/A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-darwin-arm64": { - "version": "2.5.6", - "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.6.tgz", - "integrity": "sha512-Z2ZdrnwyXvvvdtRHLmM4knydIdU9adO3D4n/0cVipF3rRiwP+3/sfzpAwA/qKFL6i1ModaabkU7IbpeMBgiVEA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-darwin-x64": { - "version": "2.5.6", - "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.6.tgz", - "integrity": "sha512-HgvOf3W9dhithcwOWX9uDZyn1lW9R+7tPZ4sug+NGrGIo4Rk1hAXLEbcH1TQSqxts0NYXXlOWqVpvS1SFS4fRg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-freebsd-x64": { - "version": "2.5.6", - "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.6.tgz", - "integrity": "sha512-vJVi8yd/qzJxEKHkeemh7w3YAn6RJCtYlE4HPMoVnCpIXEzSrxErBW5SJBgKLbXU3WdIpkjBTeUNtyBVn8TRng==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-linux-arm-glibc": { - "version": "2.5.6", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.6.tgz", - "integrity": "sha512-9JiYfB6h6BgV50CCfasfLf/uvOcJskMSwcdH1PHH9rvS1IrNy8zad6IUVPVUfmXr+u+Km9IxcfMLzgdOudz9EQ==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-linux-arm-musl": { - "version": "2.5.6", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.6.tgz", - "integrity": "sha512-Ve3gUCG57nuUUSyjBq/MAM0CzArtuIOxsBdQ+ftz6ho8n7s1i9E1Nmk/xmP323r2YL0SONs1EuwqBp2u1k5fxg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-linux-arm64-glibc": { - "version": "2.5.6", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.6.tgz", - "integrity": "sha512-f2g/DT3NhGPdBmMWYoxixqYr3v/UXcmLOYy16Bx0TM20Tchduwr4EaCbmxh1321TABqPGDpS8D/ggOTaljijOA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-linux-arm64-musl": { - "version": "2.5.6", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.6.tgz", - "integrity": "sha512-qb6naMDGlbCwdhLj6hgoVKJl2odL34z2sqkC7Z6kzir8b5W65WYDpLB6R06KabvZdgoHI/zxke4b3zR0wAbDTA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-linux-x64-glibc": { - "version": "2.5.6", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.6.tgz", - "integrity": "sha512-kbT5wvNQlx7NaGjzPFu8nVIW1rWqV780O7ZtkjuWaPUgpv2NMFpjYERVi0UYj1msZNyCzGlaCWEtzc+exjMGbQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-linux-x64-musl": { - "version": "2.5.6", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.6.tgz", - "integrity": "sha512-1JRFeC+h7RdXwldHzTsmdtYR/Ku8SylLgTU/reMuqdVD7CtLwf0VR1FqeprZ0eHQkO0vqsbvFLXUmYm/uNKJBg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-win32-arm64": { - "version": "2.5.6", - "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.6.tgz", - "integrity": "sha512-3ukyebjc6eGlw9yRt678DxVF7rjXatWiHvTXqphZLvo7aC5NdEgFufVwjFfY51ijYEWpXbqF5jtrK275z52D4Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-win32-ia32": { - "version": "2.5.6", - "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.6.tgz", - "integrity": "sha512-k35yLp1ZMwwee3Ez/pxBi5cf4AoBKYXj00CZ80jUz5h8prpiaQsiRPKQMxoLstNuqe2vR4RNPEAEcjEFzhEz/g==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-win32-x64": { - "version": "2.5.6", - "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.6.tgz", - "integrity": "sha512-hbQlYcCq5dlAX9Qx+kFb0FHue6vbjlf0FrNzSKdYK2APUf7tGfGxQCk2ihEREmbR6ZMc0MVAD5RIX/41gpUzTw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher/node_modules/node-addon-api": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", - "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", - "dev": true, - "license": "MIT", - "optional": true - }, - "node_modules/@rolldown/binding-android-arm64": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.0.tgz", - "integrity": "sha512-9yB1l95IrJuNGDFdOYe79vdApdz6WWBCObE+rQ2LUliYUlcyFwSYIb2xb5/Ifw7dAtMy2ZqNyd8QTSOc7duAKw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-darwin-arm64": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.0.tgz", - "integrity": "sha512-pexNaW9ACLUOaBITOpU6qVu4VrsOFIjTv6bzgu0YUATo4eUJx0V605PxwZfndpPOn0ilqGqvGQ0M8UW0IE24jg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-darwin-x64": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.0.tgz", - "integrity": "sha512-NqKYaq0355ZmNMG4QGpxtEDxsc7tGDhjhCm4PpE0cwnBW+5Il95LJyq414niEiaKLVjnVHBEjSo1wngKxJNiFw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-freebsd-x64": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.0.tgz", - "integrity": "sha512-3vPoHzh6eBTz9IbB0/qZdSr0Qeks2echn+I4cHu2joV74VriPDdldswksEDzrl1mBB+oPRi+67+3Ib59paxIPQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-arm-gnueabihf": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.0.tgz", - "integrity": "sha512-E6NNefZ1bUVmKJq2tJkf45J4Zyczj7qm9rUT7NY+Xo2474Y13qWAwc2tvBt0BAVbmtXR1llkxXg0Ou1jbDf2SQ==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-arm64-gnu": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.0.tgz", - "integrity": "sha512-D+TgkdgM1vu+7/Fpf8+v0ARW+RXEP9Ccazgm8zQ4JFFd9Q7SrYQ2TakU5S5ihazQDgpKyAgZDOcIFsvoHmTZ8w==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-arm64-musl": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.0.tgz", - "integrity": "sha512-wUqdwJBbAv0APN87GecstdMUtLjjNTs0hBALpxETD73mccFxdmt/XeizXDtN5RAlBwNKmI+Tg+blect2G+8IeQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-ppc64-gnu": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.0.tgz", - "integrity": "sha512-9DtF35qR9/NrfhM4oxLplCzVVjE+KKm8Pjemi0i/sdhAWkUasjmSo8WTTubNJClhSHCfyk2yeyoXDQEDPtDAAw==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-s390x-gnu": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.0.tgz", - "integrity": "sha512-RzuHrBh8X8Hntd2N4VR02QGEciq/9JhcZoTpR/Cee6otRrlILGCf3cg2ygHuih+ZebUnWmMrDX6ITI85btO6rQ==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-x64-gnu": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.0.tgz", - "integrity": "sha512-MK7L0018jjh1jR3mh21G2j1zAVcpscJBlPo2z19pRjv2XOYGRhaV4LyiD8HO6nCDdZln9IFgCMIV5yt4E3klGQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-x64-musl": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.0.tgz", - "integrity": "sha512-gyrxLQ9NfGb/9LoVnC4kb9miUghw1mghnkfYvNHSnVIXriabnfgGPUP4RLcJm87q3KgYz4FYUG8IDiWUT+CpSw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-openharmony-arm64": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.0.tgz", - "integrity": "sha512-/6VFMQGRmrhP77KXDC+StIxGzcNp5JOIyYtw0CQ8gPlzhpiIRucYfoM5FaFamHd5BJYIdH86yfP46l1p3WdrFA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-wasm32-wasi": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.2.0.tgz", - "integrity": "sha512-rwdbUL465kisF24WEJLvP3JrEG6E5GRuIHt5wpMwHGERtHe4Wm2CIvtf5gTBgr2tGOHKh5NdKEAFS2VkOPE91g==", - "cpu": [ - "wasm32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/core": "1.11.2", - "@emnapi/runtime": "1.11.2", - "@napi-rs/wasm-runtime": "^1.1.6" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-win32-arm64-msvc": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.0.tgz", - "integrity": "sha512-+5suHwRiKGmhwyUaNT8a5QbrBvLFh2DbO910TEmGRH1aSxwrCezodvGQnulv4uiWEIv1Kq4ypRsJ5+O+ry1DiA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-win32-x64-msvc": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.0.tgz", - "integrity": "sha512-WfFv6/qGufotqBSBzBYwgpCkJBk8Nj7697LL9vTz/XWc67e0r3oewu8iMRwQj3AUL45GVD7wVsPjCsAAtW66Wg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/pluginutils": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", - "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@schematics/angular": { - "version": "21.2.16", - "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-21.2.16.tgz", - "integrity": "sha512-ctvsRartACu77VAM416VlNV3mag7FhU08I/734f4+sS/UZmnhuTM5a4tTTWEI1U7iPeJoBtjreh6LgeP+QZLbQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@angular-devkit/core": "21.2.16", - "@angular-devkit/schematics": "21.2.16", - "jsonc-parser": "3.3.1" - }, - "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0", - "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", - "yarn": ">= 1.13.0" - } - }, - "node_modules/@sigstore/bundle": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@sigstore/bundle/-/bundle-4.0.0.tgz", - "integrity": "sha512-NwCl5Y0V6Di0NexvkTqdoVfmjTaQwoLM236r89KEojGmq/jMls8S+zb7yOwAPdXvbwfKDlP+lmXgAL4vKSQT+A==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@sigstore/protobuf-specs": "^0.5.0" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/@sigstore/core": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/@sigstore/core/-/core-3.2.1.tgz", - "integrity": "sha512-qRsxPnCrbC/puegGxKuynfnxgLiHqWStrSjxkoB4YKqq3Z3s4cyZyj42ZdWFAEblNP65C+rBH8EuREHIXoi83g==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/@sigstore/protobuf-specs": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/@sigstore/protobuf-specs/-/protobuf-specs-0.5.1.tgz", - "integrity": "sha512-/ScWUhhoFasJsSRGTVBwId1loQjjnjAfE4djL6ZhrXRpNCmPTnUKF5Jokd58ILseOMjzET3UrMOtJPS9sYeI0g==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/@sigstore/sign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/@sigstore/sign/-/sign-4.1.1.tgz", - "integrity": "sha512-Hf4xglukg0XXQ2RiD5vSoLjdPe8OBUPA8XeVjUObheuDcWdYWrnH/BNmxZCzkAy68MzmNCxXLeurJvs6hcP2OQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@gar/promise-retry": "^1.0.2", - "@sigstore/bundle": "^4.0.0", - "@sigstore/core": "^3.2.0", - "@sigstore/protobuf-specs": "^0.5.0", - "make-fetch-happen": "^15.0.4", - "proc-log": "^6.1.0" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/@sigstore/tuf": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@sigstore/tuf/-/tuf-4.0.2.tgz", - "integrity": "sha512-TCAzTy0xzdP79EnxSjq9KQ3eaR7+FmudLC6eRKknVKZbV7ZNlGLClAAQb/HMNJ5n2OBNk2GT1tEmU0xuPr+SLQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@sigstore/protobuf-specs": "^0.5.0", - "tuf-js": "^4.1.0" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/@sigstore/verify": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@sigstore/verify/-/verify-3.1.1.tgz", - "integrity": "sha512-qv7+G3J2cc6wwFj3yKvXOamzqhMwSk1ogPGmhpS8iXllcPrJaIIBA+4HbttlHVu1pqWTdmaCH/WE7UOC51kdoA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@sigstore/bundle": "^4.0.0", - "@sigstore/core": "^3.2.1", - "@sigstore/protobuf-specs": "^0.5.0" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/@standard-schema/spec": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", - "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", - "license": "MIT" - }, - "node_modules/@tufjs/canonical-json": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@tufjs/canonical-json/-/canonical-json-2.0.0.tgz", - "integrity": "sha512-yVtV8zsdo8qFHe+/3kw81dSLyF7D576A5cCFCi4X7B39tWT7SekaEFUnvnWJHz+9qO7qJTah1JbrDjWKqFtdWA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^16.14.0 || >=18.0.0" - } - }, - "node_modules/@tufjs/models": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@tufjs/models/-/models-4.1.0.tgz", - "integrity": "sha512-Y8cK9aggNRsqJVaKUlEYs4s7CvQ1b1ta2DVPyAimb0I2qhzjNk+A+mxvll/klL0RlfuIUei8BF7YWiua4kQqww==", - "dev": true, - "license": "MIT", - "dependencies": { - "@tufjs/canonical-json": "2.0.0", - "minimatch": "^10.1.1" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/@tybys/wasm-util": { - "version": "0.10.3", - "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.3.tgz", - "integrity": "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@types/chai": { - "version": "5.2.3", - "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", - "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/deep-eql": "*", - "assertion-error": "^2.0.1" - } - }, - "node_modules/@types/deep-eql": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", - "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/estree": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/gensync": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@types/gensync/-/gensync-1.0.5.tgz", - "integrity": "sha512-MbsRCT7mTikHwKZ0X+LVUTLRrZZRLipTuXEO9qOYO+zmjMVk81axyClMROf6uoPD9MRVu46bx8zoR0Ad9q3NAg==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/jsesc": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@types/jsesc/-/jsesc-2.5.1.tgz", - "integrity": "sha512-9VN+6yxLOPLOav+7PwjZbxiID2bVaeq0ED4qSQmdQTdjnXJSaCVKTR58t15oqH1H5t8Ng2ZX1SabJVoN9Q34bw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@vitejs/plugin-basic-ssl": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-basic-ssl/-/plugin-basic-ssl-2.3.0.tgz", - "integrity": "sha512-bdyo8rB3NnQbikdMpHaML9Z1OZPBu6fFOBo+OtxsBlvMJtysWskmBcnbIDhUqgC8tcxNv/a+BcV5U+2nQMm1OQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.0.0 || ^20.0.0 || >=22.0.0" - }, - "peerDependencies": { - "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/@vitest/expect": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.5.tgz", - "integrity": "sha512-PWBaRY5JoKuRnHlUHfpV/KohFylaDZTupcXN1H9vYryNLOnitSw60Mw9IAE2r67NbwwzBw/Cc/8q9BK3kIX8Kw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@standard-schema/spec": "^1.1.0", - "@types/chai": "^5.2.2", - "@vitest/spy": "4.1.5", - "@vitest/utils": "4.1.5", - "chai": "^6.2.2", - "tinyrainbow": "^3.1.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/mocker": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.5.tgz", - "integrity": "sha512-/x2EmFC4mT4NNzqvC3fmesuV97w5FC903KPmey4gsnJiMQ3Be1IlDKVaDaG8iqaLFHqJ2FVEkxZk5VmeLjIItw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/spy": "4.1.5", - "estree-walker": "^3.0.3", - "magic-string": "^0.30.21" - }, - "funding": { - "url": "https://opencollective.com/vitest" - }, - "peerDependencies": { - "msw": "^2.4.9", - "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "msw": { - "optional": true - }, - "vite": { - "optional": true - } - } - }, - "node_modules/@vitest/pretty-format": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.5.tgz", - "integrity": "sha512-7I3q6l5qr03dVfMX2wCo9FxwSJbPdwKjy2uu/YPpU3wfHvIL4QHwVRp57OfGrDFeUJ8/8QdfBKIV12FTtLn00g==", - "dev": true, - "license": "MIT", - "dependencies": { - "tinyrainbow": "^3.1.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/runner": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.5.tgz", - "integrity": "sha512-2D+o7Pr82IEO46YPpoA/YU0neeyr6FTerQb5Ro7BUnBuv6NQtT/kmVnczngiMEBhzgqz2UZYl5gArejsyERDSQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/utils": "4.1.5", - "pathe": "^2.0.3" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/snapshot": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.5.tgz", - "integrity": "sha512-zypXEt4KH/XgKGPUz4eC2AvErYx0My5hfL8oDb1HzGFpEk1P62bxSohdyOmvz+d9UJwanI68MKwr2EquOaOgMQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/pretty-format": "4.1.5", - "@vitest/utils": "4.1.5", - "magic-string": "^0.30.21", - "pathe": "^2.0.3" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/spy": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.5.tgz", - "integrity": "sha512-2lNOsh6+R2Idnf1TCZqSwYlKN2E/iDlD8sgU59kYVl+OMDmvldO1VDk39smRfpUNwYpNRVn3w4YfuC7KfbBnkQ==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/utils": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.5.tgz", - "integrity": "sha512-76wdkrmfXfqGjueGgnb45ITPyUi1ycZ4IHgC2bhPDUfWHklY/q3MdLOAB+TF1e6xfl8NxNY0ZYaPCFNWSsw3Ug==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/pretty-format": "4.1.5", - "convert-source-map": "^2.0.0", - "tinyrainbow": "^3.1.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/utils/node_modules/convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true, - "license": "MIT" - }, - "node_modules/@yarnpkg/lockfile": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz", - "integrity": "sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==", - "dev": true, - "license": "BSD-2-Clause" - }, - "node_modules/abbrev": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-4.0.0.tgz", - "integrity": "sha512-a1wflyaL0tHtJSmLSOVybYhy22vRih4eduhhrkcjgrWGnRfrZtovJ2FRjxuTtkkj47O/baf0R86QU5OuYpz8fA==", - "dev": true, - "license": "ISC", - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/accepts": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", - "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", - "dev": true, - "license": "MIT", - "dependencies": { - "mime-types": "^3.0.0", - "negotiator": "^1.0.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/agent-base": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", - "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 14" - } - }, - "node_modules/ajv": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", - "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ajv-formats": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", - "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ajv": "^8.0.0" - }, - "peerDependencies": { - "ajv": "^8.0.0" - }, - "peerDependenciesMeta": { - "ajv": { - "optional": true - } - } - }, - "node_modules/algoliasearch": { - "version": "5.48.1", - "resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-5.48.1.tgz", - "integrity": "sha512-Rf7xmeuIo7nb6S4mp4abW2faW8DauZyE2faBIKFaUfP3wnpOvNSbiI5AwVhqBNj0jPgBWEvhyCu0sLjN2q77Rg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@algolia/abtesting": "1.14.1", - "@algolia/client-abtesting": "5.48.1", - "@algolia/client-analytics": "5.48.1", - "@algolia/client-common": "5.48.1", - "@algolia/client-insights": "5.48.1", - "@algolia/client-personalization": "5.48.1", - "@algolia/client-query-suggestions": "5.48.1", - "@algolia/client-search": "5.48.1", - "@algolia/ingestion": "1.48.1", - "@algolia/monitoring": "1.48.1", - "@algolia/recommend": "5.48.1", - "@algolia/requester-browser-xhr": "5.48.1", - "@algolia/requester-fetch": "5.48.1", - "@algolia/requester-node-http": "5.48.1" - }, - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/ansi-escapes": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.3.0.tgz", - "integrity": "sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg==", - "dev": true, - "license": "MIT", - "dependencies": { - "environment": "^1.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ansi-regex": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", - "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/ansi-styles": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", - "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/assertion-error": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", - "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - } - }, - "node_modules/balanced-match": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", - "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "18 || 20 || >=22" - } - }, - "node_modules/baseline-browser-mapping": { - "version": "2.10.27", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.27.tgz", - "integrity": "sha512-zEs/ufmZoUd7WftKpKyXaT6RFxpQ5Qm9xytKRHvJfxFV9DFJkZph9RvJ1LcOUi0Z1ZVijMte65JbILeV+8QQEA==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "baseline-browser-mapping": "dist/cli.cjs" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/beasties": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/beasties/-/beasties-0.4.3.tgz", - "integrity": "sha512-fIIeLOcbAB/K1kb1HBVJoiq1alHL4RCYBSo5e7HzrNkkgMggXR1Vqt/Z9JWnkfe/qdCo66Ux3QRwZioAIBdWRA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "css-select": "^6.0.0", - "css-what": "^7.0.0", - "dom-serializer": "^2.0.0", - "domhandler": "^5.0.3", - "htmlparser2": "^10.0.0", - "picocolors": "^1.1.1", - "postcss": "^8.4.49", - "postcss-media-query-parser": "^0.2.3", - "postcss-safe-parser": "^7.0.1" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/bidi-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz", - "integrity": "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==", - "dev": true, - "license": "MIT", - "dependencies": { - "require-from-string": "^2.0.2" - } - }, - "node_modules/body-parser": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz", - "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==", - "dev": true, - "license": "MIT", - "dependencies": { - "bytes": "^3.1.2", - "content-type": "^1.0.5", - "debug": "^4.4.3", - "http-errors": "^2.0.0", - "iconv-lite": "^0.7.0", - "on-finished": "^2.4.1", - "qs": "^6.14.1", - "raw-body": "^3.0.1", - "type-is": "^2.0.1" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/boolbase": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", - "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", - "dev": true, - "license": "ISC" - }, - "node_modules/brace-expansion": { - "version": "5.0.7", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.7.tgz", - "integrity": "sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^4.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" - } - }, - "node_modules/browserslist": { - "version": "4.28.2", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz", - "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "baseline-browser-mapping": "^2.10.12", - "caniuse-lite": "^1.0.30001782", - "electron-to-chromium": "^1.5.328", - "node-releases": "^2.0.36", - "update-browserslist-db": "^1.2.3" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, - "node_modules/buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/cacache": { - "version": "20.0.4", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-20.0.4.tgz", - "integrity": "sha512-M3Lab8NPYlZU2exsL3bMVvMrMqgwCnMWfdZbK28bn3pK6APT/Te/I8hjRPNu1uwORY9a1eEQoifXbKPQMfMTOA==", - "dev": true, - "license": "ISC", - "dependencies": { - "@npmcli/fs": "^5.0.0", - "fs-minipass": "^3.0.0", - "glob": "^13.0.0", - "lru-cache": "^11.1.0", - "minipass": "^7.0.3", - "minipass-collect": "^2.0.1", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.4", - "p-map": "^7.0.2", - "ssri": "^13.0.0" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/call-bind-apply-helpers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", - "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/call-bound": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", - "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "get-intrinsic": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001791", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001791.tgz", - "integrity": "sha512-yk0l/YSrOnFZk3UROpDLQD9+kC1l4meK/wed583AXrzoarMGJcbRi2Q4RaUYbKxYAsZ8sWmaSa/DsLmdBeI1vQ==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "CC-BY-4.0" - }, - "node_modules/chai": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", - "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/chalk": { - "version": "5.6.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", - "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/chardet": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/chardet/-/chardet-2.1.1.tgz", - "integrity": "sha512-PsezH1rqdV9VvyNhxxOW32/d75r01NY7TQCmOqomRo15ZSOKbpTFVsfjghxo6JloQUCGnH4k1LGu0R4yCLlWQQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/chokidar": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz", - "integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==", - "dev": true, - "license": "MIT", - "dependencies": { - "readdirp": "^5.0.0" - }, - "engines": { - "node": ">= 20.19.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/chownr": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", - "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", - "dev": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=18" - } - }, - "node_modules/cli-cursor": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", - "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", - "dev": true, - "license": "MIT", - "dependencies": { - "restore-cursor": "^5.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/cli-spinners": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-3.4.0.tgz", - "integrity": "sha512-bXfOC4QcT1tKXGorxL3wbJm6XJPDqEnij2gQ2m7ESQuE+/z9YFIWnl/5RpTiKWbMq3EVKR4fRLJGn6DVfu0mpw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18.20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/cli-truncate": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-5.2.0.tgz", - "integrity": "sha512-xRwvIOMGrfOAnM1JYtqQImuaNtDEv9v6oIYAs4LIHwTiKee8uwvIi363igssOC0O5U04i4AlENs79LQLu9tEMw==", - "dev": true, - "license": "MIT", - "dependencies": { - "slice-ansi": "^8.0.0", - "string-width": "^8.2.0" - }, - "engines": { - "node": ">=20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/cli-width": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", - "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">= 12" - } - }, - "node_modules/cliui": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-9.0.1.tgz", - "integrity": "sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w==", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^7.2.0", - "strip-ansi": "^7.1.0", - "wrap-ansi": "^9.0.0" - }, - "engines": { - "node": ">=20" - } - }, - "node_modules/cliui/node_modules/string-width": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", - "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^10.3.0", - "get-east-asian-width": "^1.0.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/cliui/node_modules/wrap-ansi": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", - "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.2.1", - "string-width": "^7.0.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "license": "MIT" - }, - "node_modules/colorette": { - "version": "2.0.20", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", - "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", - "dev": true, - "license": "MIT" - }, - "node_modules/content-disposition": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz", - "integrity": "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/content-type": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", - "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/convert-source-map": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", - "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", - "dev": true, - "license": "MIT" - }, - "node_modules/cookie": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", - "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie-signature": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", - "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.6.0" - } - }, - "node_modules/cors": { - "version": "2.8.6", - "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz", - "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", - "dev": true, - "license": "MIT", - "dependencies": { - "object-assign": "^4", - "vary": "^1" - }, - "engines": { - "node": ">= 0.10" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/css-select": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-6.0.0.tgz", - "integrity": "sha512-rZZVSLle8v0+EY8QAkDWrKhpgt6SA5OtHsgBnsj6ZaLb5dmDVOWUDtQitd9ydxxvEjhewNudS6eTVU7uOyzvXw==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "boolbase": "^1.0.0", - "css-what": "^7.0.0", - "domhandler": "^5.0.3", - "domutils": "^3.2.2", - "nth-check": "^2.1.1" - }, - "funding": { - "url": "https://github.com/sponsors/fb55" - } - }, - "node_modules/css-tree": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.2.1.tgz", - "integrity": "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==", - "dev": true, - "license": "MIT", - "dependencies": { - "mdn-data": "2.27.1", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" - } - }, - "node_modules/css-what": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/css-what/-/css-what-7.0.0.tgz", - "integrity": "sha512-wD5oz5xibMOPHzy13CyGmogB3phdvcDaB5t0W/Nr5Z2O/agcB8YwOz6e2Lsp10pNDzBoDO9nVa3RGs/2BttpHQ==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">= 6" - }, - "funding": { - "url": "https://github.com/sponsors/fb55" - } - }, - "node_modules/cssstyle": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-6.2.0.tgz", - "integrity": "sha512-Fm5NvhYathRnXNVndkUsCCuR63DCLVVwGOOwQw782coXFi5HhkXdu289l59HlXZBawsyNccXfWRYvLzcDCdDig==", - "dev": true, - "license": "MIT", - "dependencies": { - "@asamuzakjp/css-color": "^5.0.1", - "@csstools/css-syntax-patches-for-csstree": "^1.0.28", - "css-tree": "^3.1.0", - "lru-cache": "^11.2.6" - }, - "engines": { - "node": ">=20" - } - }, - "node_modules/data-urls": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-7.0.0.tgz", - "integrity": "sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA==", - "dev": true, - "license": "MIT", - "dependencies": { - "whatwg-mimetype": "^5.0.0", - "whatwg-url": "^16.0.0" - }, - "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0" - } - }, - "node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/decimal.js": { - "version": "10.6.0", - "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", - "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", - "dev": true, - "license": "MIT" - }, - "node_modules/depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/detect-libc": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", - "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=8" - } - }, - "node_modules/dom-serializer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", - "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", - "dev": true, - "license": "MIT", - "dependencies": { - "domelementtype": "^2.3.0", - "domhandler": "^5.0.2", - "entities": "^4.2.0" - }, - "funding": { - "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" - } - }, - "node_modules/domelementtype": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", - "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fb55" - } - ], - "license": "BSD-2-Clause" - }, - "node_modules/domhandler": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", - "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "domelementtype": "^2.3.0" - }, - "engines": { - "node": ">= 4" - }, - "funding": { - "url": "https://github.com/fb55/domhandler?sponsor=1" - } - }, - "node_modules/domutils": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", - "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "dom-serializer": "^2.0.0", - "domelementtype": "^2.3.0", - "domhandler": "^5.0.3" - }, - "funding": { - "url": "https://github.com/fb55/domutils?sponsor=1" - } - }, - "node_modules/dunder-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", - "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "es-errors": "^1.3.0", - "gopd": "^1.2.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", - "dev": true, - "license": "MIT" - }, - "node_modules/electron-to-chromium": { - "version": "1.5.349", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.349.tgz", - "integrity": "sha512-QsWVGyRuY07Aqb234QytTfwd5d9AJlfNIQ5wIOl1L+PZDzI9d9+Fn0FRale/QYlFxt/bUnB0/nLd1jFPGxGK1A==", - "dev": true, - "license": "ISC" - }, - "node_modules/emoji-regex": { - "version": "10.6.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", - "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", - "dev": true, - "license": "MIT" - }, - "node_modules/empathic": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/empathic/-/empathic-2.0.1.tgz", - "integrity": "sha512-YGRs8knHhKHVShLkFET/rWAU8kmHbOV5LwN938RHI0pljAJ1Gf6SzXsSmRaEzcXTtOOmVqJ5+WtQPL5uigY50Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14" - } - }, - "node_modules/encodeurl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", - "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/entities": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", - "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/env-paths": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", - "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/environment": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz", - "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/es-define-property": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", - "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-module-lexer": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.1.0.tgz", - "integrity": "sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/es-object-atoms": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", - "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/esbuild": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.1.tgz", - "integrity": "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.28.1", - "@esbuild/android-arm": "0.28.1", - "@esbuild/android-arm64": "0.28.1", - "@esbuild/android-x64": "0.28.1", - "@esbuild/darwin-arm64": "0.28.1", - "@esbuild/darwin-x64": "0.28.1", - "@esbuild/freebsd-arm64": "0.28.1", - "@esbuild/freebsd-x64": "0.28.1", - "@esbuild/linux-arm": "0.28.1", - "@esbuild/linux-arm64": "0.28.1", - "@esbuild/linux-ia32": "0.28.1", - "@esbuild/linux-loong64": "0.28.1", - "@esbuild/linux-mips64el": "0.28.1", - "@esbuild/linux-ppc64": "0.28.1", - "@esbuild/linux-riscv64": "0.28.1", - "@esbuild/linux-s390x": "0.28.1", - "@esbuild/linux-x64": "0.28.1", - "@esbuild/netbsd-arm64": "0.28.1", - "@esbuild/netbsd-x64": "0.28.1", - "@esbuild/openbsd-arm64": "0.28.1", - "@esbuild/openbsd-x64": "0.28.1", - "@esbuild/openharmony-arm64": "0.28.1", - "@esbuild/sunos-x64": "0.28.1", - "@esbuild/win32-arm64": "0.28.1", - "@esbuild/win32-ia32": "0.28.1", - "@esbuild/win32-x64": "0.28.1" - } - }, - "node_modules/esbuild/node_modules/@esbuild/netbsd-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.1.tgz", - "integrity": "sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/esbuild/node_modules/@esbuild/openbsd-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.1.tgz", - "integrity": "sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/esbuild/node_modules/@esbuild/openharmony-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.1.tgz", - "integrity": "sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", - "dev": true, - "license": "MIT" - }, - "node_modules/estree-walker": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", - "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0" - } - }, - "node_modules/etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/eventemitter3": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz", - "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", - "dev": true, - "license": "MIT" - }, - "node_modules/eventsource": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz", - "integrity": "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==", - "dev": true, - "license": "MIT", - "dependencies": { - "eventsource-parser": "^3.0.1" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/eventsource-parser": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.8.tgz", - "integrity": "sha512-70QWGkr4snxr0OXLRWsFLeRBIRPuQOvt4s8QYjmUlmlkyTZkRqS7EDVRZtzU3TiyDbXSzaOeF0XUKy8PchzukQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/expect-type": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", - "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/exponential-backoff": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.3.tgz", - "integrity": "sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==", - "dev": true, - "license": "Apache-2.0" - }, - "node_modules/express": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", - "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", - "dev": true, - "license": "MIT", - "dependencies": { - "accepts": "^2.0.0", - "body-parser": "^2.2.1", - "content-disposition": "^1.0.0", - "content-type": "^1.0.5", - "cookie": "^0.7.1", - "cookie-signature": "^1.2.1", - "debug": "^4.4.0", - "depd": "^2.0.0", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "etag": "^1.8.1", - "finalhandler": "^2.1.0", - "fresh": "^2.0.0", - "http-errors": "^2.0.0", - "merge-descriptors": "^2.0.0", - "mime-types": "^3.0.0", - "on-finished": "^2.4.1", - "once": "^1.4.0", - "parseurl": "^1.3.3", - "proxy-addr": "^2.0.7", - "qs": "^6.14.0", - "range-parser": "^1.2.1", - "router": "^2.2.0", - "send": "^1.1.0", - "serve-static": "^2.2.0", - "statuses": "^2.0.1", - "type-is": "^2.0.1", - "vary": "^1.1.2" - }, - "engines": { - "node": ">= 18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/express-rate-limit": { - "version": "8.6.1", - "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.6.1.tgz", - "integrity": "sha512-0D493aP61w0TJ2A0wy27riRsO7FMQ7FK+KUHOKCSfPvYo0R55aiC6emCVgFUeShH0fq0ICPVzNcgoS+BsbXQCA==", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "^4.4.3", - "ip-address": "^10.2.0" - }, - "engines": { - "node": ">= 16" - }, - "funding": { - "url": "https://github.com/sponsors/express-rate-limit" - }, - "peerDependencies": { - "express": ">= 4.11" - } - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-string-truncated-width": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/fast-string-truncated-width/-/fast-string-truncated-width-3.0.3.tgz", - "integrity": "sha512-0jjjIEL6+0jag3l2XWWizO64/aZVtpiGE3t0Zgqxv0DPuxiMjvB3M24fCyhZUO4KomJQPj3LTSUnDP3GpdwC0g==", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-string-width": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/fast-string-width/-/fast-string-width-3.0.2.tgz", - "integrity": "sha512-gX8LrtNEI5hq8DVUfRQMbr5lpaS4nMIWV+7XEbXk2b8kiQIizgnlr12B4dA3ZEx3308ze0O4Q1R+cHts8kyUJg==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-string-truncated-width": "^3.0.2" - } - }, - "node_modules/fast-uri": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.5.tgz", - "integrity": "sha512-gHwA1O9LDIcKunMKhObS/HimwtehO1nPUECKAu5TpKgaO19fcWEl4bliWe1jWxVFvIXztJjjQ4L8XQ1EU9f7Jw==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "BSD-3-Clause" - }, - "node_modules/fast-wrap-ansi": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/fast-wrap-ansi/-/fast-wrap-ansi-0.2.2.tgz", - "integrity": "sha512-7F2Fl+TjRSenLqlU3UjSH0iyqopqoZIu7eZVpEirP2g1GtWa2G/ecEmBdgz31+Mxr+ELclgg6sokpSFIQiZ02Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-string-width": "^3.0.2" - } - }, - "node_modules/fdir": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/finalhandler": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", - "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "^4.4.0", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "on-finished": "^2.4.1", - "parseurl": "^1.3.3", - "statuses": "^2.0.1" - }, - "engines": { - "node": ">= 18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/forwarded": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/fresh": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", - "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/fs-minipass": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-3.0.3.tgz", - "integrity": "sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw==", - "dev": true, - "license": "ISC", - "dependencies": { - "minipass": "^7.0.3" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true, - "license": "ISC", - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "node_modules/get-east-asian-width": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.5.0.tgz", - "integrity": "sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/get-intrinsic": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", - "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "function-bind": "^1.1.2", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "math-intrinsics": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", - "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "dev": true, - "license": "MIT", - "dependencies": { - "dunder-proto": "^1.0.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/glob": { - "version": "13.0.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz", - "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "minimatch": "^10.2.2", - "minipass": "^7.1.3", - "path-scurry": "^2.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/gopd": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/has-symbols": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", - "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/hasown": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.3.tgz", - "integrity": "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/hono": { - "version": "4.13.1", - "resolved": "https://registry.npmjs.org/hono/-/hono-4.13.1.tgz", - "integrity": "sha512-kdJoFVv2xmayw6cY09H7AbMJMt8Jn5jdlEdXsP7AGBdF2DIptVlKlOLKXP41yPip4/a3yQPv9gVcJYI8YY04dw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=16.9.0" - } - }, - "node_modules/hosted-git-info": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-9.0.3.tgz", - "integrity": "sha512-Hc+ghLoSt6QaYZUv0WBiIvmMDZuZZ7oaDvdH8MbfOO4lOsxdXLEvuC6ePoGs9H1X9oCLyq6+NVN0MKqD+ydxyg==", - "dev": true, - "license": "ISC", - "dependencies": { - "lru-cache": "^11.1.0" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/html-encoding-sniffer": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-6.0.0.tgz", - "integrity": "sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@exodus/bytes": "^1.6.0" - }, - "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0" - } - }, - "node_modules/htmlparser2": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-10.1.0.tgz", - "integrity": "sha512-VTZkM9GWRAtEpveh7MSF6SjjrpNVNNVJfFup7xTY3UpFtm67foy9HDVXneLtFVt4pMz5kZtgNcvCniNFb1hlEQ==", - "dev": true, - "funding": [ - "https://github.com/fb55/htmlparser2?sponsor=1", - { - "type": "github", - "url": "https://github.com/sponsors/fb55" - } - ], - "license": "MIT", - "dependencies": { - "domelementtype": "^2.3.0", - "domhandler": "^5.0.3", - "domutils": "^3.2.2", - "entities": "^7.0.1" - } - }, - "node_modules/htmlparser2/node_modules/entities": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz", - "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/http-cache-semantics": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", - "integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==", - "dev": true, - "license": "BSD-2-Clause" - }, - "node_modules/http-errors": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", - "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "depd": "~2.0.0", - "inherits": "~2.0.4", - "setprototypeof": "~1.2.0", - "statuses": "~2.0.2", - "toidentifier": "~1.0.1" - }, - "engines": { - "node": ">= 0.8" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/http-proxy-agent": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", - "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", - "dev": true, - "license": "MIT", - "dependencies": { - "agent-base": "^7.1.0", - "debug": "^4.3.4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/https-proxy-agent": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", - "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", - "dev": true, - "license": "MIT", - "dependencies": { - "agent-base": "^7.1.2", - "debug": "4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/iconv-lite": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", - "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", - "dev": true, - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/ignore-walk": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-8.0.0.tgz", - "integrity": "sha512-FCeMZT4NiRQGh+YkeKMtWrOmBgWjHjMJ26WQWrRQyoyzqevdaGSakUaJW5xQYmjLlUVk2qUnCjYVBax9EKKg8A==", - "dev": true, - "license": "ISC", - "dependencies": { - "minimatch": "^10.0.3" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/immutable": { - "version": "5.1.9", - "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.9.tgz", - "integrity": "sha512-m8nVez3rwrgmWxtLMt1ZYXB2Lv7OKYn/disyxAlSDYAlKSlFoPPfIAmAM/M5xqL4m4C/wAPw7S2/CNaUii1Hxg==", - "dev": true, - "license": "MIT" - }, - "node_modules/import-meta-resolve": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.2.0.tgz", - "integrity": "sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==", - "dev": true, - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/ini": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/ini/-/ini-6.0.0.tgz", - "integrity": "sha512-IBTdIkzZNOpqm7q3dRqJvMaldXjDHWkEDfrwGEQTs5eaQMWV+djAhR+wahyNNMAa+qpbDUhBMVt4ZKNwpPm7xQ==", - "dev": true, - "license": "ISC", - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/ip-address": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.4.0.tgz", - "integrity": "sha512-oSK96Grm3aP6OrS263xVxbNDGVL7rzBtYdpGqlDG8iQdoenDoTs/nkki+DflYbAEE8Xl6o5YxhxlrKvI3nqKXQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 12" - } - }, - "node_modules/ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, - "license": "MIT", - "optional": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-fullwidth-code-point": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.1.0.tgz", - "integrity": "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "get-east-asian-width": "^1.3.1" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-interactive": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-2.0.0.tgz", - "integrity": "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-potential-custom-element-name": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", - "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/is-promise": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", - "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/is-unicode-supported": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz", - "integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true, - "license": "ISC" - }, - "node_modules/jose": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.3.tgz", - "integrity": "sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/panva" - } - }, - "node_modules/js-tokens": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-10.0.0.tgz", - "integrity": "sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/jsdom": { - "version": "28.1.0", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-28.1.0.tgz", - "integrity": "sha512-0+MoQNYyr2rBHqO1xilltfDjV9G7ymYGlAUazgcDLQaUf8JDHbuGwsxN6U9qWaElZ4w1B2r7yEGIL3GdeW3Rug==", - "dev": true, - "license": "MIT", - "dependencies": { - "@acemir/cssom": "^0.9.31", - "@asamuzakjp/dom-selector": "^6.8.1", - "@bramus/specificity": "^2.4.2", - "@exodus/bytes": "^1.11.0", - "cssstyle": "^6.0.1", - "data-urls": "^7.0.0", - "decimal.js": "^10.6.0", - "html-encoding-sniffer": "^6.0.0", - "http-proxy-agent": "^7.0.2", - "https-proxy-agent": "^7.0.6", - "is-potential-custom-element-name": "^1.0.1", - "parse5": "^8.0.0", - "saxes": "^6.0.0", - "symbol-tree": "^3.2.4", - "tough-cookie": "^6.0.0", - "undici": "^7.21.0", - "w3c-xmlserializer": "^5.0.0", - "webidl-conversions": "^8.0.1", - "whatwg-mimetype": "^5.0.0", - "whatwg-url": "^16.0.0", - "xml-name-validator": "^5.0.0" - }, - "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0" - }, - "peerDependencies": { - "canvas": "^3.0.0" - }, - "peerDependenciesMeta": { - "canvas": { - "optional": true - } - } - }, - "node_modules/jsesc": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", - "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", - "dev": true, - "license": "MIT", - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/json-parse-even-better-errors": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-5.0.0.tgz", - "integrity": "sha512-ZF1nxZ28VhQouRWhUcVlUIN3qwSgPuswK05s/HIaoetAoE/9tngVmCHjSxmSQPav1nd+lPtTL0YZ/2AFdR/iYQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true, - "license": "MIT" - }, - "node_modules/json-schema-typed": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-8.0.2.tgz", - "integrity": "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==", - "dev": true, - "license": "BSD-2-Clause" - }, - "node_modules/json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "dev": true, - "license": "MIT", - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/jsonc-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.3.1.tgz", - "integrity": "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/jsonparse": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", - "integrity": "sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==", - "dev": true, - "engines": [ - "node >= 0.2.0" - ], - "license": "MIT" - }, - "node_modules/lightningcss": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.33.0.tgz", - "integrity": "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==", - "dev": true, - "license": "MPL-2.0", - "dependencies": { - "detect-libc": "^2.0.3" - }, - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - }, - "optionalDependencies": { - "lightningcss-android-arm64": "1.33.0", - "lightningcss-darwin-arm64": "1.33.0", - "lightningcss-darwin-x64": "1.33.0", - "lightningcss-freebsd-x64": "1.33.0", - "lightningcss-linux-arm-gnueabihf": "1.33.0", - "lightningcss-linux-arm64-gnu": "1.33.0", - "lightningcss-linux-arm64-musl": "1.33.0", - "lightningcss-linux-x64-gnu": "1.33.0", - "lightningcss-linux-x64-musl": "1.33.0", - "lightningcss-win32-arm64-msvc": "1.33.0", - "lightningcss-win32-x64-msvc": "1.33.0" - } - }, - "node_modules/lightningcss-android-arm64": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.33.0.tgz", - "integrity": "sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-darwin-arm64": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.33.0.tgz", - "integrity": "sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-darwin-x64": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.33.0.tgz", - "integrity": "sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-freebsd-x64": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.33.0.tgz", - "integrity": "sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm-gnueabihf": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.33.0.tgz", - "integrity": "sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm64-gnu": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.33.0.tgz", - "integrity": "sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm64-musl": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.33.0.tgz", - "integrity": "sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-x64-gnu": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.33.0.tgz", - "integrity": "sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-x64-musl": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.33.0.tgz", - "integrity": "sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-win32-arm64-msvc": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.33.0.tgz", - "integrity": "sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-win32-x64-msvc": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.33.0.tgz", - "integrity": "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/listr2": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/listr2/-/listr2-9.0.5.tgz", - "integrity": "sha512-ME4Fb83LgEgwNw96RKNvKV4VTLuXfoKudAmm2lP8Kk87KaMK0/Xrx/aAkMWmT8mDb+3MlFDspfbCs7adjRxA2g==", - "dev": true, - "license": "MIT", - "dependencies": { - "cli-truncate": "^5.0.0", - "colorette": "^2.0.20", - "eventemitter3": "^5.0.1", - "log-update": "^6.1.0", - "rfdc": "^1.4.1", - "wrap-ansi": "^9.0.0" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/listr2/node_modules/string-width": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", - "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^10.3.0", - "get-east-asian-width": "^1.0.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/listr2/node_modules/wrap-ansi": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", - "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.2.1", - "string-width": "^7.0.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/lmdb": { - "version": "3.5.6", - "resolved": "https://registry.npmjs.org/lmdb/-/lmdb-3.5.6.tgz", - "integrity": "sha512-j3uE8ReKNyUWDjhfEFSJqE/1DLtfTR5Z8yFzVHvBjAk37wNg7HdScjcv8ttPHRvrdgPQMPWxFFI0SsdBzI5lBw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@harperfast/extended-iterable": "^1.0.3", - "msgpackr": "^1.11.2", - "node-addon-api": "^6.1.0", - "node-gyp-build-optional-packages": "5.2.2", - "ordered-binary": "^1.5.3", - "weak-lru-cache": "^1.2.2" - }, - "bin": { - "download-lmdb-prebuilds": "bin/download-prebuilds.js" - }, - "optionalDependencies": { - "@lmdb/lmdb-darwin-arm64": "3.5.6", - "@lmdb/lmdb-darwin-x64": "3.5.6", - "@lmdb/lmdb-linux-arm": "3.5.6", - "@lmdb/lmdb-linux-arm64": "3.5.6", - "@lmdb/lmdb-linux-x64": "3.5.6", - "@lmdb/lmdb-win32-arm64": "3.5.6", - "@lmdb/lmdb-win32-x64": "3.5.6" - } - }, - "node_modules/log-symbols": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-7.0.1.tgz", - "integrity": "sha512-ja1E3yCr9i/0hmBVaM0bfwDjnGy8I/s6PP4DFp+yP+a+mrHO4Rm7DtmnqROTUkHIkqffC84YY7AeqX6oFk0WFg==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-unicode-supported": "^2.0.0", - "yoctocolors": "^2.1.1" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/log-update": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/log-update/-/log-update-6.1.0.tgz", - "integrity": "sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-escapes": "^7.0.0", - "cli-cursor": "^5.0.0", - "slice-ansi": "^7.1.0", - "strip-ansi": "^7.1.0", - "wrap-ansi": "^9.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/log-update/node_modules/slice-ansi": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.2.tgz", - "integrity": "sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.2.1", - "is-fullwidth-code-point": "^5.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/chalk/slice-ansi?sponsor=1" - } - }, - "node_modules/log-update/node_modules/string-width": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", - "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^10.3.0", - "get-east-asian-width": "^1.0.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/log-update/node_modules/wrap-ansi": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", - "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.2.1", - "string-width": "^7.0.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/lru-cache": { - "version": "11.5.2", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.2.tgz", - "integrity": "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==", - "dev": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": "20 || >=22" - } - }, - "node_modules/magic-string": { - "version": "0.30.21", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", - "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.5" - } - }, - "node_modules/make-fetch-happen": { - "version": "15.0.6", - "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-15.0.6.tgz", - "integrity": "sha512-Je0fLJ0F5atA7F+eIlLzk+Wkcl57JDf4kf+EW8xiP5E31xOQxkIxTbgf1Oi1Lw9tRI9UEMRdI5Vz2xTzoNU1Jw==", - "dev": true, - "license": "ISC", - "dependencies": { - "@gar/promise-retry": "^1.0.0", - "@npmcli/agent": "^4.0.0", - "@npmcli/redact": "^4.0.0", - "cacache": "^20.0.1", - "http-cache-semantics": "^4.1.1", - "minipass": "^7.0.2", - "minipass-fetch": "^5.0.0", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.4", - "negotiator": "^1.0.0", - "proc-log": "^6.0.0", - "ssri": "^13.0.0" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/math-intrinsics": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", - "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/mdn-data": { - "version": "2.27.1", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.27.1.tgz", - "integrity": "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==", - "dev": true, - "license": "CC0-1.0" - }, - "node_modules/media-typer": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", - "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/merge-descriptors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", - "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/mime-db": { - "version": "1.54.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", - "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", - "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "mime-db": "^1.54.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/mimic-function": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", - "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/minimatch": { - "version": "10.2.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", - "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "brace-expansion": "^5.0.5" - }, - "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/minipass": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", - "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", - "dev": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "node_modules/minipass-collect": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-2.0.1.tgz", - "integrity": "sha512-D7V8PO9oaz7PWGLbCACuI1qEOsq7UKfLotx/C0Aet43fCUB/wfQ7DYeq2oR/svFJGYDHPr38SHATeaj/ZoKHKw==", - "dev": true, - "license": "ISC", - "dependencies": { - "minipass": "^7.0.3" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "node_modules/minipass-fetch": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-5.0.2.tgz", - "integrity": "sha512-2d0q2a8eCi2IRg/IGubCNRJoYbA1+YPXAzQVRFmB45gdGZafyivnZ5YSEfo3JikbjGxOdntGFvBQGqaSMXlAFQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "minipass": "^7.0.3", - "minipass-sized": "^2.0.0", - "minizlib": "^3.0.1" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - }, - "optionalDependencies": { - "iconv-lite": "^0.7.2" - } - }, - "node_modules/minipass-flush": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.7.tgz", - "integrity": "sha512-TbqTz9cUwWyHS2Dy89P3ocAGUGxKjjLuR9z8w4WUTGAVgEj17/4nhgo2Du56i0Fm3Pm30g4iA8Lcqctc76jCzA==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/minipass-flush/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/minipass-flush/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true, - "license": "ISC" - }, - "node_modules/minipass-pipeline": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", - "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", - "dev": true, - "license": "ISC", - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/minipass-pipeline/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/minipass-pipeline/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true, - "license": "ISC" - }, - "node_modules/minipass-sized": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-2.0.0.tgz", - "integrity": "sha512-zSsHhto5BcUVM2m1LurnXY6M//cGhVaegT71OfOXoprxT6o780GZd792ea6FfrQkuU4usHZIUczAQMRUE2plzA==", - "dev": true, - "license": "ISC", - "dependencies": { - "minipass": "^7.1.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/minizlib": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.1.0.tgz", - "integrity": "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==", - "dev": true, - "license": "MIT", - "dependencies": { - "minipass": "^7.1.2" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/mrmime": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", - "integrity": "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - } - }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT" - }, - "node_modules/msgpackr": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/msgpackr/-/msgpackr-1.12.1.tgz", - "integrity": "sha512-4EUH9tQHnMmEgzW/MdAP0KIfa1T9AF+htl0ffe2n5vb2EKn9y2co8ccpgWko6S52Jy1PQZKwRnx5/KkYjtd9MQ==", - "dev": true, - "license": "MIT", - "optional": true, - "optionalDependencies": { - "msgpackr-extract": "^3.0.2" - } - }, - "node_modules/msgpackr-extract": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/msgpackr-extract/-/msgpackr-extract-3.0.4.tgz", - "integrity": "sha512-4kmO/MdyUIkLIvTPr8VHLil4AtoKIoniWPIEk5+CDy0xnWC84azhSFmuJ7PxZdsYtiP5kEeQsORAVIeMgxT+Hw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "dependencies": { - "node-gyp-build-optional-packages": "5.2.2" - }, - "bin": { - "download-msgpackr-prebuilds": "bin/download-prebuilds.js" - }, - "optionalDependencies": { - "@msgpackr-extract/msgpackr-extract-darwin-arm64": "3.0.4", - "@msgpackr-extract/msgpackr-extract-darwin-x64": "3.0.4", - "@msgpackr-extract/msgpackr-extract-linux-arm": "3.0.4", - "@msgpackr-extract/msgpackr-extract-linux-arm64": "3.0.4", - "@msgpackr-extract/msgpackr-extract-linux-x64": "3.0.4", - "@msgpackr-extract/msgpackr-extract-win32-x64": "3.0.4" - } - }, - "node_modules/mute-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-2.0.0.tgz", - "integrity": "sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==", - "dev": true, - "license": "ISC", - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/nanoid": { - "version": "3.3.16", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.16.tgz", - "integrity": "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/negotiator": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", - "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/node-addon-api": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-6.1.0.tgz", - "integrity": "sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA==", - "dev": true, - "license": "MIT", - "optional": true - }, - "node_modules/node-gyp": { - "version": "12.4.0", - "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-12.4.0.tgz", - "integrity": "sha512-OMcPNvqTCFUnNaBlmdgq+lfNqY7gTiSmNRDjY3uAXRyudeKZEZxu3CLtjMQrx4zZxCX2b/mpNqTtwuCJgXhHkw==", - "dev": true, - "license": "MIT", - "dependencies": { - "env-paths": "^2.2.0", - "exponential-backoff": "^3.1.1", - "graceful-fs": "^4.2.6", - "nopt": "^9.0.0", - "proc-log": "^6.0.0", - "semver": "^7.3.5", - "tar": "^7.5.4", - "tinyglobby": "^0.2.12", - "undici": "^6.25.0", - "which": "^6.0.0" - }, - "bin": { - "node-gyp": "bin/node-gyp.js" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/node-gyp-build-optional-packages": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-5.2.2.tgz", - "integrity": "sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "detect-libc": "^2.0.1" - }, - "bin": { - "node-gyp-build-optional-packages": "bin.js", - "node-gyp-build-optional-packages-optional": "optional.js", - "node-gyp-build-optional-packages-test": "build-test.js" - } - }, - "node_modules/node-gyp/node_modules/isexe": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-4.0.0.tgz", - "integrity": "sha512-FFUtZMpoZ8RqHS3XeXEmHWLA4thH+ZxCv2lOiPIn1Xc7CxrqhWzNSDzD+/chS/zbYezmiwWLdQC09JdQKmthOw==", - "dev": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=20" - } - }, - "node_modules/node-gyp/node_modules/undici": { - "version": "6.28.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-6.28.0.tgz", - "integrity": "sha512-LIY910g9TI13YS95lrMFrs8Rm/u/irgHeTWoKCoteeJ04CUJ92eEfj0rVn+7VKMPBpUPiUoBKfhNyLI23EE/KA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18.17" - } - }, - "node_modules/node-gyp/node_modules/which": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/which/-/which-6.0.1.tgz", - "integrity": "sha512-oGLe46MIrCRqX7ytPUf66EAYvdeMIZYn3WaocqqKZAxrBpkqHfL/qvTyJ/bTk5+AqHCjXmrv3CEWgy368zhRUg==", - "dev": true, - "license": "ISC", - "dependencies": { - "isexe": "^4.0.0" - }, - "bin": { - "node-which": "bin/which.js" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/node-releases": { - "version": "2.0.38", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.38.tgz", - "integrity": "sha512-3qT/88Y3FbH/Kx4szpQQ4HzUbVrHPKTLVpVocKiLfoYvw9XSGOX2FmD2d6DrXbVYyAQTF2HeF6My8jmzx7/CRw==", - "dev": true, - "license": "MIT" - }, - "node_modules/nopt": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-9.0.0.tgz", - "integrity": "sha512-Zhq3a+yFKrYwSBluL4H9XP3m3y5uvQkB/09CwDruCiRmR/UJYnn9W4R48ry0uGC70aeTPKLynBtscP9efFFcPw==", - "dev": true, - "license": "ISC", - "dependencies": { - "abbrev": "^4.0.0" - }, - "bin": { - "nopt": "bin/nopt.js" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/npm-bundled": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-5.0.0.tgz", - "integrity": "sha512-JLSpbzh6UUXIEoqPsYBvVNVmyrjVZ1fzEFbqxKkTJQkWBO3xFzFT+KDnSKQWwOQNbuWRwt5LSD6HOTLGIWzfrw==", - "dev": true, - "license": "ISC", - "dependencies": { - "npm-normalize-package-bin": "^5.0.0" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/npm-install-checks": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/npm-install-checks/-/npm-install-checks-8.0.0.tgz", - "integrity": "sha512-ScAUdMpyzkbpxoNekQ3tNRdFI8SJ86wgKZSQZdUxT+bj0wVFpsEMWnkXP0twVe1gJyNF5apBWDJhhIbgrIViRA==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "semver": "^7.1.1" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/npm-normalize-package-bin": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-5.0.0.tgz", - "integrity": "sha512-CJi3OS4JLsNMmr2u07OJlhcrPxCeOeP/4xq67aWNai6TNWWbTrlNDgl8NcFKVlcBKp18GPj+EzbNIgrBfZhsag==", - "dev": true, - "license": "ISC", - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/npm-package-arg": { - "version": "13.0.2", - "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-13.0.2.tgz", - "integrity": "sha512-IciCE3SY3uE84Ld8WZU23gAPPV9rIYod4F+rc+vJ7h7cwAJt9Vk6TVsK60ry7Uj3SRS3bqRRIGuTp9YVlk6WNA==", - "dev": true, - "license": "ISC", - "dependencies": { - "hosted-git-info": "^9.0.0", - "proc-log": "^6.0.0", - "semver": "^7.3.5", - "validate-npm-package-name": "^7.0.0" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/npm-packlist": { - "version": "10.0.4", - "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-10.0.4.tgz", - "integrity": "sha512-uMW73iajD8hiH4ZBxEV3HC+eTnppIqwakjOYuvgddnalIw2lJguKviK1pcUJDlIWm1wSJkchpDZDSVVsZEYRng==", - "dev": true, - "license": "ISC", - "dependencies": { - "ignore-walk": "^8.0.0", - "proc-log": "^6.0.0" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/npm-pick-manifest": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-11.0.3.tgz", - "integrity": "sha512-buzyCfeoGY/PxKqmBqn1IUJrZnUi1VVJTdSSRPGI60tJdUhUoSQFhs0zycJokDdOznQentgrpf8LayEHyyYlqQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "npm-install-checks": "^8.0.0", - "npm-normalize-package-bin": "^5.0.0", - "npm-package-arg": "^13.0.0", - "semver": "^7.3.5" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/npm-registry-fetch": { - "version": "19.1.1", - "resolved": "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-19.1.1.tgz", - "integrity": "sha512-TakBap6OM1w0H73VZVDf44iFXsOS3h+L4wVMXmbWOQroZgFhMch0juN6XSzBNlD965yIKvWg2dfu7NSiaYLxtw==", - "dev": true, - "license": "ISC", - "dependencies": { - "@npmcli/redact": "^4.0.0", - "jsonparse": "^1.3.1", - "make-fetch-happen": "^15.0.0", - "minipass": "^7.0.2", - "minipass-fetch": "^5.0.0", - "minizlib": "^3.0.1", - "npm-package-arg": "^13.0.0", - "proc-log": "^6.0.0" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/nth-check": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", - "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "boolbase": "^1.0.0" - }, - "funding": { - "url": "https://github.com/fb55/nth-check?sponsor=1" - } - }, - "node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-inspect": { - "version": "1.13.4", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", - "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/obug": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz", - "integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==", - "dev": true, - "funding": [ - "https://github.com/sponsors/sxzz", - "https://opencollective.com/debug" - ], - "license": "MIT" - }, - "node_modules/on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "dev": true, - "license": "MIT", - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, - "license": "ISC", - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/onetime": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", - "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "mimic-function": "^5.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ora": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/ora/-/ora-9.3.0.tgz", - "integrity": "sha512-lBX72MWFduWEf7v7uWf5DHp9Jn5BI8bNPGuFgtXMmr2uDz2Gz2749y3am3agSDdkhHPHYmmxEGSKH85ZLGzgXw==", - "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "^5.6.2", - "cli-cursor": "^5.0.0", - "cli-spinners": "^3.2.0", - "is-interactive": "^2.0.0", - "is-unicode-supported": "^2.1.0", - "log-symbols": "^7.0.1", - "stdin-discarder": "^0.3.1", - "string-width": "^8.1.0" - }, - "engines": { - "node": ">=20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ordered-binary": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/ordered-binary/-/ordered-binary-1.6.1.tgz", - "integrity": "sha512-QkCdPooczexPLiXIrbVOPYkR3VO3T6v2OyKRkR1Xbhpy7/LAVXwahnRCgRp78Oe/Ehf0C/HATAxfSr6eA1oX+w==", - "dev": true, - "license": "MIT", - "optional": true - }, - "node_modules/oxc-parser": { - "version": "0.142.0", - "resolved": "https://registry.npmjs.org/oxc-parser/-/oxc-parser-0.142.0.tgz", - "integrity": "sha512-kKR+jPiRJYJDexVoziIg/FVGvr1fT1FZSSJOk6tVoMKKSlsf1Cso+cgGCJkOEDWOP174vRntCPFKg+AS7InWvw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@oxc-project/types": "^0.142.0" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - }, - "funding": { - "url": "https://github.com/sponsors/Boshen" - }, - "optionalDependencies": { - "@oxc-parser/binding-android-arm-eabi": "0.142.0", - "@oxc-parser/binding-android-arm64": "0.142.0", - "@oxc-parser/binding-darwin-arm64": "0.142.0", - "@oxc-parser/binding-darwin-x64": "0.142.0", - "@oxc-parser/binding-freebsd-x64": "0.142.0", - "@oxc-parser/binding-linux-arm-gnueabihf": "0.142.0", - "@oxc-parser/binding-linux-arm-musleabihf": "0.142.0", - "@oxc-parser/binding-linux-arm64-gnu": "0.142.0", - "@oxc-parser/binding-linux-arm64-musl": "0.142.0", - "@oxc-parser/binding-linux-ppc64-gnu": "0.142.0", - "@oxc-parser/binding-linux-riscv64-gnu": "0.142.0", - "@oxc-parser/binding-linux-riscv64-musl": "0.142.0", - "@oxc-parser/binding-linux-s390x-gnu": "0.142.0", - "@oxc-parser/binding-linux-x64-gnu": "0.142.0", - "@oxc-parser/binding-linux-x64-musl": "0.142.0", - "@oxc-parser/binding-openharmony-arm64": "0.142.0", - "@oxc-parser/binding-wasm32-wasi": "0.142.0", - "@oxc-parser/binding-win32-arm64-msvc": "0.142.0", - "@oxc-parser/binding-win32-ia32-msvc": "0.142.0", - "@oxc-parser/binding-win32-x64-msvc": "0.142.0" - } - }, - "node_modules/p-map": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-7.0.4.tgz", - "integrity": "sha512-tkAQEw8ysMzmkhgw8k+1U/iPhWNhykKnSk4Rd5zLoPJCuJaGRPo6YposrZgaxHKzDHdDWWZvE/Sk7hsL2X/CpQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/pacote": { - "version": "21.5.1", - "resolved": "https://registry.npmjs.org/pacote/-/pacote-21.5.1.tgz", - "integrity": "sha512-KvcJ9iy3crysCsgqc4+PknH/w6jkrp8JN36mpZBPwNaDRwTfMZD37YzRazNstiZUOhuF5pno9f78n9mEJBavwg==", - "dev": true, - "license": "ISC", - "dependencies": { - "@gar/promise-retry": "^1.0.0", - "@npmcli/git": "^7.0.0", - "@npmcli/installed-package-contents": "^4.0.0", - "@npmcli/package-json": "^7.0.0", - "@npmcli/promise-spawn": "^9.0.0", - "@npmcli/run-script": "^10.0.0", - "cacache": "^20.0.0", - "fs-minipass": "^3.0.0", - "minipass": "^7.0.2", - "npm-package-arg": "^13.0.0", - "npm-packlist": "^10.0.1", - "npm-pick-manifest": "^11.0.1", - "npm-registry-fetch": "^19.0.0", - "proc-log": "^6.0.0", - "sigstore": "^4.0.0", - "ssri": "^13.0.0", - "tar": "^7.4.3" - }, - "bin": { - "pacote": "bin/index.js" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/parse5": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.1.tgz", - "integrity": "sha512-z1e/HMG90obSGeidlli3hj7cbocou0/wa5HacvI3ASx34PecNjNQeaHNo5WIZpWofN9kgkqV1q5YvXe3F0FoPw==", - "dev": true, - "license": "MIT", - "dependencies": { - "entities": "^8.0.0" - }, - "funding": { - "url": "https://github.com/inikulin/parse5?sponsor=1" - } - }, - "node_modules/parse5-html-rewriting-stream": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/parse5-html-rewriting-stream/-/parse5-html-rewriting-stream-8.0.0.tgz", - "integrity": "sha512-wzh11mj8KKkno1pZEu+l2EVeWsuKDfR5KNWZOTsslfUX8lPDZx77m9T0kIoAVkFtD1nx6YF8oh4BnPHvxMtNMw==", - "dev": true, - "license": "MIT", - "dependencies": { - "entities": "^6.0.0", - "parse5": "^8.0.0", - "parse5-sax-parser": "^8.0.0" - }, - "funding": { - "url": "https://github.com/inikulin/parse5?sponsor=1" - } - }, - "node_modules/parse5-html-rewriting-stream/node_modules/entities": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", - "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/parse5-sax-parser": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/parse5-sax-parser/-/parse5-sax-parser-8.0.0.tgz", - "integrity": "sha512-/dQ8UzHZwnrzs3EvDj6IkKrD/jIZyTlB+8XrHJvcjNgRdmWruNdN9i9RK/JtxakmlUdPwKubKPTCqvbTgzGhrw==", - "dev": true, - "license": "MIT", - "dependencies": { - "parse5": "^8.0.0" - }, - "funding": { - "url": "https://github.com/inikulin/parse5?sponsor=1" - } - }, - "node_modules/parse5/node_modules/entities": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-8.0.0.tgz", - "integrity": "sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=20.19.0" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-scurry": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", - "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "lru-cache": "^11.0.0", - "minipass": "^7.1.2" - }, - "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/path-to-regexp": { - "version": "8.4.2", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz", - "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==", - "dev": true, - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/pathe": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", - "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", - "dev": true, - "license": "MIT" - }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true, - "license": "ISC" - }, - "node_modules/picomatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", - "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/piscina": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/piscina/-/piscina-5.2.0.tgz", - "integrity": "sha512-DszUCKeVN/5G5QKo6jAVHL8fmKnkJvQ0ACiVgY7YGCq3TUB2oznAOayvZPIAdEThvhczkXR+qm3IHsNXpFCYfA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=20.x" - }, - "optionalDependencies": { - "@napi-rs/nice": "^1.0.4" - } - }, - "node_modules/pkce-challenge": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.1.tgz", - "integrity": "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=16.20.0" - } - }, - "node_modules/postcss": { - "version": "8.5.23", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.23.tgz", - "integrity": "sha512-g50586zr4bZmwFiTlflMu8E0bDTb5I5gertgwAKmsdUlTQIhZtunzUlD1WSzwcVWPoAVpsrA6vlfCD7oXvRwgg==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "nanoid": "^3.3.16", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/postcss-media-query-parser": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/postcss-media-query-parser/-/postcss-media-query-parser-0.2.3.tgz", - "integrity": "sha512-3sOlxmbKcSHMjlUXQZKQ06jOswE7oVkXPxmZdoB1r5l0q6gTFTQSHxNxOrCccElbW7dxNytifNEo8qidX2Vsig==", - "dev": true, - "license": "MIT" - }, - "node_modules/postcss-safe-parser": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/postcss-safe-parser/-/postcss-safe-parser-7.0.1.tgz", - "integrity": "sha512-0AioNCJZ2DPYz5ABT6bddIqlhgwhpHZ/l65YAYo0BCIn0xiDpsnTHz0gnoTGk0OXZW0JRs+cDwL8u/teRdz+8A==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss-safe-parser" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "engines": { - "node": ">=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "node_modules/prettier": { - "version": "3.8.3", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.3.tgz", - "integrity": "sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw==", - "dev": true, - "license": "MIT", - "bin": { - "prettier": "bin/prettier.cjs" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/prettier/prettier?sponsor=1" - } - }, - "node_modules/proc-log": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-6.1.0.tgz", - "integrity": "sha512-iG+GYldRf2BQ0UDUAd6JQ/RwzaQy6mXmsk/IzlYyal4A4SNFw54MeH4/tLkF4I5WoWG9SQwuqWzS99jaFQHBuQ==", - "dev": true, - "license": "ISC", - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/proxy-addr": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", - "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", - "dev": true, - "license": "MIT", - "dependencies": { - "forwarded": "0.2.0", - "ipaddr.js": "1.9.1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/proxy-agent-negotiate": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-agent-negotiate/-/proxy-agent-negotiate-1.1.0.tgz", - "integrity": "sha512-N8IBcM3UgCVzz2L2Lqv8DVntDnnC8/hiV4nEDUPkqq72TPUgYWjQc+bdZlBPZK9LzPAvOY//gAt0S0DApoOXWQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 20" - }, - "peerDependencies": { - "kerberos": "^2.0.0" - }, - "peerDependenciesMeta": { - "kerberos": { - "optional": true - } - } - }, - "node_modules/punycode": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/qs": { - "version": "6.15.1", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.1.tgz", - "integrity": "sha512-6YHEFRL9mfgcAvql/XhwTvf5jKcOiiupt2FiJxHkiX1z4j7WL8J/jRHYLluORvc1XxB5rV20KoeK00gVJamspg==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "side-channel": "^1.1.0" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/raw-body": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", - "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", - "dev": true, - "license": "MIT", - "dependencies": { - "bytes": "~3.1.2", - "http-errors": "~2.0.1", - "iconv-lite": "~0.7.0", - "unpipe": "~1.0.0" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/readdirp": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.0.0.tgz", - "integrity": "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 20.19.0" - }, - "funding": { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/reflect-metadata": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz", - "integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==", - "dev": true, - "license": "Apache-2.0" - }, - "node_modules/require-from-string": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/restore-cursor": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", - "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", - "dev": true, - "license": "MIT", - "dependencies": { - "onetime": "^7.0.0", - "signal-exit": "^4.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/rfdc": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", - "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", - "dev": true, - "license": "MIT" - }, - "node_modules/rolldown": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.0.tgz", - "integrity": "sha512-u7tgm5l4Yw1iTqUL4EcYOAt7fFvCgQMLeidrnD4GALlC6aOznCjezYajgxeyKw27u0Q5N7fwgCzjVyPIWzwuBA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@oxc-project/types": "=0.140.0", - "@rolldown/pluginutils": "^1.0.0" - }, - "bin": { - "rolldown": "bin/cli.mjs" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - }, - "optionalDependencies": { - "@rolldown/binding-android-arm64": "1.2.0", - "@rolldown/binding-darwin-arm64": "1.2.0", - "@rolldown/binding-darwin-x64": "1.2.0", - "@rolldown/binding-freebsd-x64": "1.2.0", - "@rolldown/binding-linux-arm-gnueabihf": "1.2.0", - "@rolldown/binding-linux-arm64-gnu": "1.2.0", - "@rolldown/binding-linux-arm64-musl": "1.2.0", - "@rolldown/binding-linux-ppc64-gnu": "1.2.0", - "@rolldown/binding-linux-s390x-gnu": "1.2.0", - "@rolldown/binding-linux-x64-gnu": "1.2.0", - "@rolldown/binding-linux-x64-musl": "1.2.0", - "@rolldown/binding-openharmony-arm64": "1.2.0", - "@rolldown/binding-wasm32-wasi": "1.2.0", - "@rolldown/binding-win32-arm64-msvc": "1.2.0", - "@rolldown/binding-win32-x64-msvc": "1.2.0" - } - }, - "node_modules/rolldown/node_modules/@oxc-project/types": { - "version": "0.140.0", - "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.140.0.tgz", - "integrity": "sha512-h5LUOzGArYemnW1NMz/DuuQhBi96J6JL2Bk8zE4kvqxB5Sg3jxmCiH4uyOWHDkiKSt5vWlG4FIwCR/DbstcNRQ==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/Boshen" - } - }, - "node_modules/router": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", - "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "^4.4.0", - "depd": "^2.0.0", - "is-promise": "^4.0.0", - "parseurl": "^1.3.3", - "path-to-regexp": "^8.0.0" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/rxjs": { - "version": "7.8.2", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", - "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.1.0" - } - }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true, - "license": "MIT" - }, - "node_modules/sass": { - "version": "1.101.0", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.101.0.tgz", - "integrity": "sha512-OL3GoQyoUdDt843DpVmDO6y2k1sc5IhUDSpu8XucEI+35neq5QivZ1iuegnpraEVTJXlQGK1gl27zKcTLEPbQw==", - "dev": true, - "license": "MIT", - "dependencies": { - "chokidar": "^5.0.0", - "immutable": "^5.1.5", - "source-map-js": ">=0.6.2 <2.0.0" - }, - "bin": { - "sass": "sass.js" - }, - "engines": { - "node": ">=20.19.0" - }, - "optionalDependencies": { - "@parcel/watcher": "^2.4.1" - } - }, - "node_modules/saxes": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", - "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", - "dev": true, - "license": "ISC", - "dependencies": { - "xmlchars": "^2.2.0" - }, - "engines": { - "node": ">=v12.22.7" - } - }, - "node_modules/semver": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", - "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/send": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", - "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "^4.4.3", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "etag": "^1.8.1", - "fresh": "^2.0.0", - "http-errors": "^2.0.1", - "mime-types": "^3.0.2", - "ms": "^2.1.3", - "on-finished": "^2.4.1", - "range-parser": "^1.2.1", - "statuses": "^2.0.2" - }, - "engines": { - "node": ">= 18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/serve-static": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", - "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", - "dev": true, - "license": "MIT", - "dependencies": { - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "parseurl": "^1.3.3", - "send": "^1.2.0" - }, - "engines": { - "node": ">= 18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", - "dev": true, - "license": "ISC" - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "license": "MIT", - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/side-channel": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", - "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.3", - "side-channel-list": "^1.0.0", - "side-channel-map": "^1.0.1", - "side-channel-weakmap": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-list": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", - "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.4" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-map": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", - "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-weakmap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", - "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3", - "side-channel-map": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/siginfo": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", - "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", - "dev": true, - "license": "ISC" - }, - "node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/sigstore": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/sigstore/-/sigstore-4.1.1.tgz", - "integrity": "sha512-endqECJkfhozrXMK5ngu/UAA0xVcVEFdnHJCElGaExypjW+HK5i6zu3NteLoaX/iFbRUbC3+DjttQs0GARr+5w==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@sigstore/bundle": "^4.0.0", - "@sigstore/core": "^3.2.1", - "@sigstore/protobuf-specs": "^0.5.0", - "@sigstore/sign": "^4.1.1", - "@sigstore/tuf": "^4.0.2", - "@sigstore/verify": "^3.1.1" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/slice-ansi": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-8.0.0.tgz", - "integrity": "sha512-stxByr12oeeOyY2BlviTNQlYV5xOj47GirPr4yA1hE9JCtxfQN0+tVbkxwCtYDQWhEKWFHsEK48ORg5jrouCAg==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.2.3", - "is-fullwidth-code-point": "^5.1.0" - }, - "engines": { - "node": ">=20" - }, - "funding": { - "url": "https://github.com/chalk/slice-ansi?sponsor=1" - } - }, - "node_modules/smart-buffer": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", - "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 6.0.0", - "npm": ">= 3.0.0" - } - }, - "node_modules/socks": { - "version": "2.8.9", - "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.9.tgz", - "integrity": "sha512-LJhUYUvItdQ0LkJTmPeaEObWXAqFyfmP85x0tch/ez9cahmhlBBLbIqDFnvBnUJGagb0JbIQrkBs1wJ+yRYpEw==", - "dev": true, - "license": "MIT", - "dependencies": { - "ip-address": "^10.1.1", - "smart-buffer": "^4.2.0" - }, - "engines": { - "node": ">= 10.0.0", - "npm": ">= 3.0.0" - } - }, - "node_modules/socks-proxy-agent": { - "version": "8.0.5", - "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz", - "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==", - "dev": true, - "license": "MIT", - "dependencies": { - "agent-base": "^7.1.2", - "debug": "^4.3.4", - "socks": "^2.8.3" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/source-map": { - "version": "0.7.6", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", - "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">= 12" - } - }, - "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "dev": true, - "license": "MIT", - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "node_modules/source-map-support/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/spdx-exceptions": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", - "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==", - "dev": true, - "license": "CC-BY-3.0" - }, - "node_modules/spdx-expression-parse": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-4.0.0.tgz", - "integrity": "sha512-Clya5JIij/7C6bRR22+tnGXbc4VKlibKSVj2iHvVeX5iMW7s1SIQlqu699JkODJJIhh/pUu8L0/VLh8xflD+LQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" - } - }, - "node_modules/spdx-license-ids": { - "version": "3.0.23", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.23.tgz", - "integrity": "sha512-CWLcCCH7VLu13TgOH+r8p1O/Znwhqv/dbb6lqWy67G+pT1kHmeD/+V36AVb/vq8QMIQwVShJ6Ssl5FPh0fuSdw==", - "dev": true, - "license": "CC0-1.0" - }, - "node_modules/ssri": { - "version": "13.0.1", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-13.0.1.tgz", - "integrity": "sha512-QUiRf1+u9wPTL/76GTYlKttDEBWV1ga9ZXW8BG6kfdeyyM8LGPix9gROyg9V2+P0xNyF3X2Go526xKFdMZrHSQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "minipass": "^7.0.3" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/stackback": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", - "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", - "dev": true, - "license": "MIT" - }, - "node_modules/statuses": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", - "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/std-env": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.1.0.tgz", - "integrity": "sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/stdin-discarder": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/stdin-discarder/-/stdin-discarder-0.3.2.tgz", - "integrity": "sha512-eCPu1qRxPVkl5605OTWF8Wz40b4Mf45NY5LQmVPQ599knfs5QhASUm9GbJ5BDMDOXgrnh0wyEdvzmL//YMlw0A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/string-width": { - "version": "8.2.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.2.1.tgz", - "integrity": "sha512-IIaP0g3iy9Cyy18w3M9YcaDudujEAVHKt3a3QJg1+sr/oX96TbaGUubG0hJyCjCBThFH+tFpcIyoUHUn1ogaLA==", - "dev": true, - "license": "MIT", - "dependencies": { - "get-east-asian-width": "^1.5.0", - "strip-ansi": "^7.1.2" - }, - "engines": { - "node": ">=20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/strip-ansi": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", - "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.2.2" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/symbol-tree": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", - "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", - "dev": true, - "license": "MIT" - }, - "node_modules/tar": { - "version": "7.5.20", - "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.20.tgz", - "integrity": "sha512-9FcyK4PA6+WbzlTM9WhQm6vB5W7cP7dUiPsv1g7YDwEQnQ1CGpK3MGlKk/ITVWMk05kHZuBhmVhiv8LZoy/PFQ==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/fs-minipass": "^4.0.0", - "chownr": "^3.0.0", - "minipass": "^7.1.2", - "minizlib": "^3.1.0", - "yallist": "^5.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/tar/node_modules/yallist": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", - "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", - "dev": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=18" - } - }, - "node_modules/tinybench": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", - "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", - "dev": true, - "license": "MIT" - }, - "node_modules/tinyexec": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.1.2.tgz", - "integrity": "sha512-dAqSqE/RabpBKI8+h26GfLq6Vb3JVXs30XYQjdMjaj/c2tS8IYYMbIzP599KtRj7c57/wYApb3QjgRgXmrCukA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/tinyglobby": { - "version": "0.2.17", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", - "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", - "dev": true, - "license": "MIT", - "dependencies": { - "fdir": "^6.5.0", - "picomatch": "^4.0.4" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/SuperchupuDev" - } - }, - "node_modules/tinyrainbow": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.0.tgz", - "integrity": "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/tldts": { - "version": "7.0.30", - "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.0.30.tgz", - "integrity": "sha512-ELrFxuqsDdHUwoh0XxDbxuLD3Wnz49Z57IFvTtvWy1hJdcMZjXLIuonjilCiWHlT2GbE4Wlv1wKVTzDFnXH1aw==", - "dev": true, - "license": "MIT", - "dependencies": { - "tldts-core": "^7.0.30" - }, - "bin": { - "tldts": "bin/cli.js" - } - }, - "node_modules/tldts-core": { - "version": "7.0.30", - "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.0.30.tgz", - "integrity": "sha512-uiHN8PIB1VmWyS98eZYja4xzlYqeFZVjb4OuYlJQnZAuJhMw4PbKQOKgHKhBdJR3FE/t5mUQ1Kd80++B+qhD1Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.6" - } - }, - "node_modules/tough-cookie": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.1.tgz", - "integrity": "sha512-LktZQb3IeoUWB9lqR5EWTHgW/VTITCXg4D21M+lvybRVdylLrRMnqaIONLVb5mav8vM19m44HIcGq4qASeu2Qw==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "tldts": "^7.0.5" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/tr46": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-6.0.0.tgz", - "integrity": "sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==", - "dev": true, - "license": "MIT", - "dependencies": { - "punycode": "^2.3.1" - }, - "engines": { - "node": ">=20" - } - }, - "node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "license": "0BSD" - }, - "node_modules/tuf-js": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/tuf-js/-/tuf-js-4.1.0.tgz", - "integrity": "sha512-50QV99kCKH5P/Vs4E2Gzp7BopNV+KzTXqWeaxrfu5IQJBOULRsTIS9seSsOVT8ZnGXzCyx55nYWAi4qJzpZKEQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@tufjs/models": "4.1.0", - "debug": "^4.4.3", - "make-fetch-happen": "^15.0.1" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/type-is": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", - "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", - "dev": true, - "license": "MIT", - "dependencies": { - "content-type": "^1.0.5", - "media-typer": "^1.1.0", - "mime-types": "^3.0.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/typescript": { - "version": "5.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/undici": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-7.29.0.tgz", - "integrity": "sha512-IDxfleLmmbSskfWSUATiN1nfn2rDuvnMOqb5CWR92iIfojA0Ud+ulOAAEQ57LPr9rWmsreUyf5lwyao+7GNNVw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=20.18.1" - } - }, - "node_modules/unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/update-browserslist-db": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", - "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "escalade": "^3.2.0", - "picocolors": "^1.1.1" - }, - "bin": { - "update-browserslist-db": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" - } - }, - "node_modules/validate-npm-package-name": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-7.0.2.tgz", - "integrity": "sha512-hVDIBwsRruT73PbK7uP5ebUt+ezEtCmzZz3F59BSr2F6OVFnJ/6h8liuvdLrQ88Xmnk6/+xGGuq+pG9WwTuy3A==", - "dev": true, - "license": "ISC", - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/vite": { - "version": "8.1.5", - "resolved": "https://registry.npmjs.org/vite/-/vite-8.1.5.tgz", - "integrity": "sha512-7ULLwsCdYx/nRyrpiEwvqb5TFHrMVZyBt+rg/OAXT7rgj/z+DtTDyKFeLAdDkubDVDKD8jOsndmy7m55XcfUsw==", - "dev": true, - "license": "MIT", - "dependencies": { - "lightningcss": "^1.32.0", - "picomatch": "^4.0.5", - "postcss": "^8.5.17", - "rolldown": "~1.1.5", - "tinyglobby": "^0.2.17" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - }, - "peerDependencies": { - "@types/node": "^20.19.0 || >=22.12.0", - "@vitejs/devtools": "^0.3.0", - "esbuild": "^0.27.0 || ^0.28.0", - "jiti": ">=1.21.0", - "less": "^4.0.0", - "sass": "^1.70.0", - "sass-embedded": "^1.70.0", - "stylus": ">=0.54.8", - "sugarss": "^5.0.0", - "terser": "^5.16.0", - "tsx": "^4.8.1", - "yaml": "^2.4.2" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "@vitejs/devtools": { - "optional": true - }, - "esbuild": { - "optional": true - }, - "jiti": { - "optional": true - }, - "less": { - "optional": true - }, - "sass": { - "optional": true - }, - "sass-embedded": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - }, - "tsx": { - "optional": true - }, - "yaml": { - "optional": true - } - } - }, - "node_modules/vite/node_modules/@emnapi/core": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.1.tgz", - "integrity": "sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/wasi-threads": "1.2.2", - "tslib": "^2.4.0" - } - }, - "node_modules/vite/node_modules/@emnapi/runtime": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.1.tgz", - "integrity": "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/vite/node_modules/@oxc-project/types": { - "version": "0.139.0", - "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.139.0.tgz", - "integrity": "sha512-r9gHphtCs+1M7J0pw6Sn/hh/Wpa/iQrOOkrNAlVLF/gHq+/CJmHIWKKUUhdWjcD6CIa8idarspCsASiXCXvFUw==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/Boshen" - } - }, - "node_modules/vite/node_modules/@rolldown/binding-android-arm64": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.1.5.tgz", - "integrity": "sha512-lZg8fqIv2v7FF237bwMgzGZEJvGL79/s5knJ/i6FmsGF4XXlzccZ4jb+TrFIxtSSxFtIpdsgrPZeMk1I9AFcyQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/vite/node_modules/@rolldown/binding-darwin-arm64": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.1.5.tgz", - "integrity": "sha512-51Bnx9pNiMRKSUNtBfySkNJ9vMU9Hh3I1ozDd6gyPPYzaXCfnptUcEZxXGYFn+ul2dtcMUiqGR1Yai2K10uoTw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/vite/node_modules/@rolldown/binding-darwin-x64": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.1.5.tgz", - "integrity": "sha512-Tm+gbfC0aHu1tBA/JvKQh32S0K6YgCHkiAF4/W6xX0K0RmNuc94VeK419dJoE65R5aRxmo+noZQSWrAMF6yb6g==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/vite/node_modules/@rolldown/binding-freebsd-x64": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.1.5.tgz", - "integrity": "sha512-JMzDKCCXq93YccG5gz3hvOs1oXRKAf0XYpfOS88e+wZrC8Iugj6j68867vrYZkvpDDpKn/KoKORThmchMpF6TA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/vite/node_modules/@rolldown/binding-linux-arm-gnueabihf": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.1.5.tgz", - "integrity": "sha512-uML21j2K5TfPGutKxub+M+nLjZIrWjXQ5Grx4lCe/nimTj9B4L63zHpjXLl4y0L3mcm2htEQIb06oCG/szerNw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/vite/node_modules/@rolldown/binding-linux-arm64-gnu": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.1.5.tgz", - "integrity": "sha512-navSiuTMogvnQoZoM/v+l3ZWo50/NTwSHSzheABx/RCnmUPaKwq9qSo4Br2OYRs21+Fz8uFqITZM3H4opOB0/Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/vite/node_modules/@rolldown/binding-linux-arm64-musl": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.1.5.tgz", - "integrity": "sha512-lAryqH7IteztmCXQXk0etKj4wBQ7Gx5S6LjKhsgp9zb8I5bsuvU/2llH1hDQcjsFeqIsovMVN339/8pUDDBXxA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/vite/node_modules/@rolldown/binding-linux-ppc64-gnu": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.1.5.tgz", - "integrity": "sha512-fsK/sNBnxzBlL4O1JNrZakVQxPspqpED5dLtNsZS9oOKmtSpdNIzxH2kkol5HYTWJN47sE20ztMJPxfZ89qGOg==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/vite/node_modules/@rolldown/binding-linux-s390x-gnu": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.1.5.tgz", - "integrity": "sha512-gLYb4BIadlfTOYT5gO503n8zQjXflgzpD0FcyKh0Mzx3rqCZKnHoJWV9xe1KXUJ5lx2JfcSHr/mhzS0PC/McAA==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/vite/node_modules/@rolldown/binding-linux-x64-gnu": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.1.5.tgz", - "integrity": "sha512-FjcpEKUyJygHgs1o50VYNvkt5+7Le/VEdYt0AkRpkL33MnyQfwr8l5mXwMmfmTbyMPr5vJLC+8/Gd9gXnwU1QQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/vite/node_modules/@rolldown/binding-linux-x64-musl": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.1.5.tgz", - "integrity": "sha512-Me+PfPI2TMeOQk0gYWfLQZtTktrmzbr8cDboqX83XKc7UrgAi55gF+2dUkWdxd19n55Essp2yeca+O9N5rBxHg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/vite/node_modules/@rolldown/binding-openharmony-arm64": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.1.5.tgz", - "integrity": "sha512-yc5WrLzXks6zCQfn9Oxr8pORKyl/pF+QjHmW/Qx3qu0oyrrNC+y2JLTU1E2rcWYAmzlnqngWXHQjy51VzW70Vw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/vite/node_modules/@rolldown/binding-wasm32-wasi": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.1.5.tgz", - "integrity": "sha512-VbQGPX2b4r48TAMIM2cjgluIM1HYutm4pcTEJsle7iEP7sB1dFqtPLBVbdLAZCxy1txCcPxf4QFf4v8uvltPqA==", - "cpu": [ - "wasm32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/core": "1.11.1", - "@emnapi/runtime": "1.11.1", - "@napi-rs/wasm-runtime": "^1.1.6" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/vite/node_modules/@rolldown/binding-win32-arm64-msvc": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.1.5.tgz", - "integrity": "sha512-gHv82k63z4qpV5+Q1y/12KrK0ltWBukVDI8nZcbT7Tt/ZlOIVwppazneq0F93oDxTo3IgAMEDIoQh3E2n6mVsw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/vite/node_modules/@rolldown/binding-win32-x64-msvc": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.1.5.tgz", - "integrity": "sha512-tTZuDBPw85tEN5PQi1pnEBzDy0Z49HtScLAbD5t6hyeU92A95pRWaSMw1GZZi/RwgSgUIl0xrSlXIT/9QzvYSA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/vite/node_modules/picomatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", - "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/vite/node_modules/rolldown": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.1.5.tgz", - "integrity": "sha512-t9z29cJjXf/vxQ8dyhCSpt6H6aSwHTk8cT5I3iy6SMXuFpk5mB6PL6XfC8PCwrPTx93udwKUm9HRteAlTGBLiA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@oxc-project/types": "=0.139.0", - "@rolldown/pluginutils": "^1.0.0" - }, - "bin": { - "rolldown": "bin/cli.mjs" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - }, - "optionalDependencies": { - "@rolldown/binding-android-arm64": "1.1.5", - "@rolldown/binding-darwin-arm64": "1.1.5", - "@rolldown/binding-darwin-x64": "1.1.5", - "@rolldown/binding-freebsd-x64": "1.1.5", - "@rolldown/binding-linux-arm-gnueabihf": "1.1.5", - "@rolldown/binding-linux-arm64-gnu": "1.1.5", - "@rolldown/binding-linux-arm64-musl": "1.1.5", - "@rolldown/binding-linux-ppc64-gnu": "1.1.5", - "@rolldown/binding-linux-s390x-gnu": "1.1.5", - "@rolldown/binding-linux-x64-gnu": "1.1.5", - "@rolldown/binding-linux-x64-musl": "1.1.5", - "@rolldown/binding-openharmony-arm64": "1.1.5", - "@rolldown/binding-wasm32-wasi": "1.1.5", - "@rolldown/binding-win32-arm64-msvc": "1.1.5", - "@rolldown/binding-win32-x64-msvc": "1.1.5" - } - }, - "node_modules/vitest": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.5.tgz", - "integrity": "sha512-9Xx1v3/ih3m9hN+SbfkUyy0JAs72ap3r7joc87XL6jwF0jGg6mFBvQ1SrwaX+h8BlkX6Hz9shdd1uo6AF+ZGpg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/expect": "4.1.5", - "@vitest/mocker": "4.1.5", - "@vitest/pretty-format": "4.1.5", - "@vitest/runner": "4.1.5", - "@vitest/snapshot": "4.1.5", - "@vitest/spy": "4.1.5", - "@vitest/utils": "4.1.5", - "es-module-lexer": "^2.0.0", - "expect-type": "^1.3.0", - "magic-string": "^0.30.21", - "obug": "^2.1.1", - "pathe": "^2.0.3", - "picomatch": "^4.0.3", - "std-env": "^4.0.0-rc.1", - "tinybench": "^2.9.0", - "tinyexec": "^1.0.2", - "tinyglobby": "^0.2.15", - "tinyrainbow": "^3.1.0", - "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", - "why-is-node-running": "^2.3.0" - }, - "bin": { - "vitest": "vitest.mjs" - }, - "engines": { - "node": "^20.0.0 || ^22.0.0 || >=24.0.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - }, - "peerDependencies": { - "@edge-runtime/vm": "*", - "@opentelemetry/api": "^1.9.0", - "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", - "@vitest/browser-playwright": "4.1.5", - "@vitest/browser-preview": "4.1.5", - "@vitest/browser-webdriverio": "4.1.5", - "@vitest/coverage-istanbul": "4.1.5", - "@vitest/coverage-v8": "4.1.5", - "@vitest/ui": "4.1.5", - "happy-dom": "*", - "jsdom": "*", - "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "@edge-runtime/vm": { - "optional": true - }, - "@opentelemetry/api": { - "optional": true - }, - "@types/node": { - "optional": true - }, - "@vitest/browser-playwright": { - "optional": true - }, - "@vitest/browser-preview": { - "optional": true - }, - "@vitest/browser-webdriverio": { - "optional": true - }, - "@vitest/coverage-istanbul": { - "optional": true - }, - "@vitest/coverage-v8": { - "optional": true - }, - "@vitest/ui": { - "optional": true - }, - "happy-dom": { - "optional": true - }, - "jsdom": { - "optional": true - }, - "vite": { - "optional": false - } - } - }, - "node_modules/w3c-xmlserializer": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", - "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", - "dev": true, - "license": "MIT", - "dependencies": { - "xml-name-validator": "^5.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/watchpack": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.5.2.tgz", - "integrity": "sha512-6i/00NBjP4yGPs+caKSyRfpTF/8Torsu0MOW3mMzIbhgISFder8i7xbqgHlLMwJrdiN8ndBV3UA1/AfzPSr+jg==", - "dev": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.1.2" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/weak-lru-cache": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/weak-lru-cache/-/weak-lru-cache-1.2.2.tgz", - "integrity": "sha512-DEAoo25RfSYMuTGc9vPJzZcZullwIqRDSI9LOy+fkCJPi6hykCnfKaXTuPBDuXAUcqHXyOgFtHNp/kB2FjYHbw==", - "dev": true, - "license": "MIT", - "optional": true - }, - "node_modules/webidl-conversions": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.1.tgz", - "integrity": "sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=20" - } - }, - "node_modules/whatwg-mimetype": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-5.0.0.tgz", - "integrity": "sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=20" - } - }, - "node_modules/whatwg-url": { - "version": "16.0.1", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-16.0.1.tgz", - "integrity": "sha512-1to4zXBxmXHV3IiSSEInrreIlu02vUOvrhxJJH5vcxYTBDAx51cqZiKdyTxlecdKNSjj8EcxGBxNf6Vg+945gw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@exodus/bytes": "^1.11.0", - "tr46": "^6.0.0", - "webidl-conversions": "^8.0.1" - }, - "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0" - } - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/why-is-node-running": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", - "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", - "dev": true, - "license": "MIT", - "dependencies": { - "siginfo": "^2.0.0", - "stackback": "0.0.2" - }, - "bin": { - "why-is-node-running": "cli.js" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/wrap-ansi/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, - "node_modules/wrap-ansi/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/xml-name-validator": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", - "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=18" - } - }, - "node_modules/xmlchars": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", - "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", - "dev": true, - "license": "MIT" - }, - "node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=10" - } - }, - "node_modules/yargs": { - "version": "18.0.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-18.0.0.tgz", - "integrity": "sha512-4UEqdc2RYGHZc7Doyqkrqiln3p9X2DZVxaGbwhn2pi7MrRagKaOcIKe8L3OxYcbhXLgLFUS3zAYuQjKBQgmuNg==", - "dev": true, - "license": "MIT", - "dependencies": { - "cliui": "^9.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "string-width": "^7.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^22.0.0" - }, - "engines": { - "node": "^20.19.0 || ^22.12.0 || >=23" - } - }, - "node_modules/yargs-parser": { - "version": "22.0.0", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-22.0.0.tgz", - "integrity": "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==", - "dev": true, - "license": "ISC", - "engines": { - "node": "^20.19.0 || ^22.12.0 || >=23" - } - }, - "node_modules/yargs/node_modules/string-width": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", - "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^10.3.0", - "get-east-asian-width": "^1.0.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/yoctocolors": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/yoctocolors/-/yoctocolors-2.1.2.tgz", - "integrity": "sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/yoctocolors-cjs": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/yoctocolors-cjs/-/yoctocolors-cjs-2.1.3.tgz", - "integrity": "sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/zod": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz", - "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/colinhacks" - } - }, - "node_modules/zod-to-json-schema": { - "version": "3.25.2", - "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.2.tgz", - "integrity": "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==", - "dev": true, - "license": "ISC", - "peerDependencies": { - "zod": "^3.25.28 || ^4" - } - } - } -} diff --git a/bigframes/display/table_widget_angular/package.json b/bigframes/display/table_widget_angular/package.json deleted file mode 100644 index 5008d5829e6..00000000000 --- a/bigframes/display/table_widget_angular/package.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "name": "table-widget-angular", - "version": "0.0.0", - "scripts": { - "ng": "ng", - "start": "ng serve", - "build": "ng build", - "watch": "ng build --watch --configuration development", - "test": "ng test", - "build:widget": "ng build --output-hashing none && node bundle.js" - }, - "private": true, - "packageManager": "npm@11.7.0", - "dependencies": { - "@angular/common": "^22.1.0", - "@angular/compiler": "^22.1.0", - "@angular/core": "^22.1.0", - "@angular/forms": "^22.1.0", - "@angular/platform-browser": "^22.1.0", - "@angular/router": "^22.1.0", - "rxjs": "~7.8.0", - "tslib": "^2.3.0" - }, - "devDependencies": { - "@angular/build": "^22.1.2", - "@angular/cli": "^21.2.16", - "@angular/compiler-cli": "^22.1.0", - "esbuild": "^0.28.0", - "jsdom": "^28.0.0", - "prettier": "^3.8.1", - "typescript": "~5.9.2", - "vitest": "^4.0.8" - } -} diff --git a/bigframes/display/table_widget_angular/public/favicon.ico b/bigframes/display/table_widget_angular/public/favicon.ico deleted file mode 100644 index 57614f9c967..00000000000 Binary files a/bigframes/display/table_widget_angular/public/favicon.ico and /dev/null differ diff --git a/bigframes/display/table_widget_angular/src/app/app.spec.ts b/bigframes/display/table_widget_angular/src/app/app.spec.ts deleted file mode 100644 index 75ccf03e436..00000000000 --- a/bigframes/display/table_widget_angular/src/app/app.spec.ts +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright 2026 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 { TestBed } from '@angular/core/testing'; -import { App } from './app'; - -describe('App', () => { - beforeEach(async () => { - await TestBed.configureTestingModule({ - imports: [App], - providers: [{ provide: 'ANYWIDGET_MODEL', useValue: null }] - }).compileComponents(); - }); - - it('should create the app', () => { - const fixture = TestBed.createComponent(App); - const app = fixture.componentInstance; - expect(app).toBeTruthy(); - }); - - it('should render the table container', async () => { - const fixture = TestBed.createComponent(App); - fixture.detectChanges(); - const compiled = fixture.nativeElement as HTMLElement; - expect(compiled.querySelector('.table-container')).toBeTruthy(); - }); -}); diff --git a/bigframes/display/table_widget_angular/src/app/app.ts b/bigframes/display/table_widget_angular/src/app/app.ts deleted file mode 100644 index 60b94d30e78..00000000000 --- a/bigframes/display/table_widget_angular/src/app/app.ts +++ /dev/null @@ -1,704 +0,0 @@ -/* - * Copyright 2026 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 { Component, ElementRef, ViewChild, computed, effect, inject, signal } from '@angular/core'; -import { DomSanitizer } from '@angular/platform-browser'; -import { WidgetStateService } from './widget-state.service'; - -@Component({ - selector: '[app-root]', - standalone: true, - imports: [], - providers: [WidgetStateService], - template: ` -
- @if (errorMessage()) { -
{{ errorMessage() }}
- } - - @if (isDeferredMode()) { -
-
-

{{ dryRunInfo() }}

- -
-
- } @else { -
-
- -
- {{ rowCountText() }} - - - -
-
- - -
- -
- - -
-
-
- } -
- `, - styles: [` - /* Increase specificity to override framework styles without !important */ - .bigframes-widget.bigframes-widget { - /* Default Light Mode Variables */ - --bf-bg: white; - --bf-border-color: #ccc; - --bf-error-bg: #fbe; - --bf-error-border: red; - --bf-error-fg: black; - --bf-fg: black; - --bf-header-bg: #f5f5f5; - --bf-null-fg: gray; - --bf-row-even-bg: #f5f5f5; - --bf-row-odd-bg: white; - - background-color: var(--bf-bg); - box-sizing: border-box; - color: var(--bf-fg); - display: flex; - flex-direction: column; - font-family: - '-apple-system', 'BlinkMacSystemFont', 'Segoe UI', 'Roboto', sans-serif; - margin: 0; - padding: 0; - width: 100%; - } - - .bigframes-widget * { - box-sizing: border-box; - } - - /* Dark Mode Overrides */ - @media (prefers-color-scheme: dark) { - .bigframes-widget.bigframes-widget { - --bf-bg: var(--vscode-editor-background, #202124); - --bf-border-color: #444; - --bf-error-bg: #511; - --bf-error-border: #f88; - --bf-error-fg: #fcc; - --bf-fg: white; - --bf-header-bg: var(--vscode-editor-background, black); - --bf-null-fg: #aaa; - --bf-row-even-bg: #202124; - --bf-row-odd-bg: #383838; - } - } - - .bigframes-widget.bigframes-dark-mode.bigframes-dark-mode { - --bf-bg: var(--vscode-editor-background, #202124); - --bf-border-color: #444; - --bf-error-bg: #511; - --bf-error-border: #f88; - --bf-error-fg: #fcc; - --bf-fg: white; - --bf-header-bg: var(--vscode-editor-background, black); - --bf-null-fg: #aaa; - --bf-row-even-bg: #202124; - --bf-row-odd-bg: #383838; - } - - .bigframes-widget .table-container { - background-color: var(--bf-bg); - margin: 0; - overflow: auto; - padding: 0; - } - - .bigframes-widget .footer { - align-items: center; - background-color: var(--bf-bg); - color: var(--bf-fg); - display: flex; - font-size: 0.8rem; - justify-content: space-between; - padding: 8px; - } - - .bigframes-widget .footer > * { - flex: 1; - } - - .bigframes-widget .pagination { - align-items: center; - display: flex; - flex-direction: row; - gap: 4px; - justify-content: center; - padding: 4px; - } - - .bigframes-widget .page-indicator { - margin: 0 8px; - } - - .bigframes-widget .row-count { - margin: 0 8px; - } - - .bigframes-widget .settings { - align-items: center; - display: flex; - flex-direction: row; - gap: 16px; - justify-content: end; - } - - .bigframes-widget .page-size, - .bigframes-widget .max-columns { - align-items: center; - display: flex; - flex-direction: row; - gap: 4px; - } - - .bigframes-widget .page-size label, - .bigframes-widget .max-columns label { - margin-right: 8px; - } - - /* Dynamic internal elements styles */ - .bigframes-widget ::ng-deep table.bigframes-widget-table, - .bigframes-widget ::ng-deep table.dataframe { - background-color: var(--bf-bg); - border: 1px solid var(--bf-border-color); - border-collapse: collapse; - border-spacing: 0; - box-shadow: none; - color: var(--bf-fg); - margin: 0; - outline: none; - text-align: left; - width: auto; - } - - .bigframes-widget ::ng-deep tr { - border: none; - } - - .bigframes-widget ::ng-deep th { - background-color: var(--bf-header-bg); - border: 1px solid var(--bf-border-color); - color: var(--bf-fg); - padding: 0; - position: sticky; - text-align: left; - top: 0; - z-index: 1; - } - - .bigframes-widget ::ng-deep td { - border: 1px solid var(--bf-border-color); - color: var(--bf-fg); - padding: 0.5em; - } - - .bigframes-widget ::ng-deep table tbody tr:nth-child(odd), - .bigframes-widget ::ng-deep table tbody tr:nth-child(odd) td { - background-color: var(--bf-row-odd-bg); - } - - .bigframes-widget ::ng-deep table tbody tr:nth-child(even), - .bigframes-widget ::ng-deep table tbody tr:nth-child(even) td { - background-color: var(--bf-row-even-bg); - } - - .bigframes-widget ::ng-deep .bf-header-content { - box-sizing: border-box; - height: 100%; - overflow: auto; - padding: 0.5em; - resize: horizontal; - width: 100%; - } - - .bigframes-widget ::ng-deep th .sort-indicator { - padding-left: 4px; - visibility: hidden; - } - - .bigframes-widget ::ng-deep th:hover .sort-indicator { - visibility: visible; - } - - .bigframes-widget button { - background-color: transparent; - border: 1px solid currentColor; - border-radius: 4px; - color: inherit; - cursor: pointer; - display: inline-block; - padding: 2px 8px; - text-align: center; - text-decoration: none; - user-select: none; - vertical-align: middle; - } - - .bigframes-widget button:disabled { - opacity: 0.65; - pointer-events: none; - } - - .bigframes-widget .bigframes-error-message { - background-color: var(--bf-error-bg); - border: 1px solid var(--bf-error-border); - border-radius: 4px; - color: var(--bf-error-fg); - font-size: 14px; - margin-bottom: 8px; - padding: 8px; - } - - .bigframes-widget ::ng-deep .cell-align-right { - text-align: right; - } - - .bigframes-widget ::ng-deep .cell-align-left { - text-align: left; - } - - .bigframes-widget ::ng-deep .null-value { - color: var(--bf-null-fg); - } - - .bigframes-widget ::ng-deep .debug-info { - border-top: 1px solid var(--bf-border-color); - } - - .bigframes-widget .deferred-container { - align-items: center; - display: flex; - justify-content: center; - min-height: 220px; - padding: 24px; - width: 100%; - } - - .bigframes-widget .deferred-card { - background: linear-gradient( - 135deg, - rgba(255, 255, 255, 0.6), - rgba(255, 255, 255, 0.3) - ); - border: 1px solid rgba(255, 255, 255, 0.4); - border-radius: 16px; - box-shadow: 0 8px 32px 0 rgba(31, 38, 135, 0.07); - display: flex; - flex-direction: column; - gap: 16px; - max-width: 500px; - padding: 32px; - text-align: center; - transition: all 0.3s ease-in-out; - } - - .bigframes-widget.bigframes-dark-mode .deferred-card { - background: linear-gradient( - 135deg, - rgba(32, 33, 36, 0.6), - rgba(32, 33, 36, 0.3) - ); - border: 1px solid rgba(255, 255, 255, 0.1); - box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.3); - } - - @media (prefers-color-scheme: dark) { - .bigframes-widget .deferred-card { - background: linear-gradient( - 135deg, - rgba(32, 33, 36, 0.6), - rgba(32, 33, 36, 0.3) - ); - border: 1px solid rgba(255, 255, 255, 0.1); - box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.3); - } - } - - .bigframes-widget .deferred-title { - font-size: 1.1rem; - font-weight: 600; - margin: 0; - } - - .bigframes-widget .deferred-estimate { - color: var(--bf-null-fg); - font-size: 0.9rem; - margin: 0; - } - - .bigframes-widget .run-query-button { - align-items: center; - background-color: var(--bf-fg); - border: 1px solid var(--bf-fg); - border-radius: 8px; - color: var(--bf-bg); - cursor: pointer; - display: inline-flex; - font-size: 14px; - font-weight: 600; - gap: 8px; - justify-content: center; - padding: 10px 20px; - transition: transform 0.20s ease, opacity 0.20s ease; - } - - .bigframes-widget .run-query-button:hover { - opacity: 0.90; - transform: translateY(-1px); - } - - .bigframes-widget .run-query-button:active { - transform: translateY(0); - } - - .bigframes-widget .run-query-button:disabled { - cursor: not-allowed; - opacity: 0.60; - } - - .bigframes-widget .spinner { - animation: spin 1s linear infinite; - border: 2px solid currentColor; - border-radius: 50%; - border-top-color: transparent; - display: inline-block; - height: 12px; - width: 12px; - } - - @keyframes spin { - to { - transform: rotate(360deg); - } - } - `] -}) -export class App { - protected readonly state = inject(WidgetStateService); - private readonly sanitizer = inject(DomSanitizer); - - protected readonly maxColumnOptions = [5, 10, 15, 20, 0]; - protected readonly pageSizeOptions = [10, 25, 50, 100]; - - // State signals - protected readonly errorMessage = this.state.errorMessage; - protected readonly maxColumns = this.state.maxColumns; - protected readonly pageSize = this.state.pageSize; - protected readonly page = this.state.page; - protected readonly rowCount = this.state.rowCount; - protected readonly isDeferredMode = this.state.isDeferredMode; - protected readonly dryRunInfo = this.state.dryRunInfo; - protected readonly isLoading = signal(false); - - // Computed properties for formatting and display states - protected readonly sanitizedHtml = computed(() => - this.sanitizer.bypassSecurityTrustHtml(this.state.tableHtml()) - ); - - protected readonly totalPages = computed(() => { - const count = this.rowCount(); - const size = this.pageSize(); - return count !== null && size > 0 ? Math.ceil(count / size) : null; - }); - - protected readonly pageIndicatorText = computed(() => { - const currentPage = this.page(); - const count = this.rowCount(); - const total = this.totalPages(); - const currentStr = (currentPage + 1).toLocaleString(); - const totalStr = (total ?? 1).toLocaleString(); - return `Page ${currentStr} of ${totalStr}`; - }); - - protected readonly rowCountText = computed(() => { - const count = this.rowCount(); - if (count === null) { - return 'Total rows unknown'; - } - if (count === 0) { - return '0 total rows'; - } - return `${count.toLocaleString()} total rows`; - }); - - protected readonly prevPageDisabled = computed(() => this.page() === 0); - - protected readonly nextPageDisabled = computed(() => { - const currentPage = this.page(); - const count = this.rowCount(); - const total = this.totalPages(); - if (count === null) { - return false; - } - if (count === 0) { - return true; - } - return total !== null && currentPage >= total - 1; - }); - - protected readonly isDarkMode = signal(false); - private themeObserver: MutationObserver | null = null; - - @ViewChild('tableContainer') - tableContainerRef!: ElementRef; - - private isHeightInitialized = false; - - constructor() { - effect(() => { - // Setup dependencies for reactive effect - const _html = this.state.tableHtml(); - const _sort = this.state.sortContext(); - const _orderable = this.state.orderableColumns(); - const deferred = this.isDeferredMode(); - if (deferred) { - this.isHeightInitialized = false; - } - - // Schedule DOM post-processing once the innerHTML render completes - setTimeout(() => { - this.applySortIndicators(); - this.lockInitialHeight(); - }, 0); - }); - - effect(() => { - if (!this.state.startExecution()) { - this.isLoading.set(false); - } - }); - - effect((onCleanup) => { - const executing = this.state.startExecution(); - if (executing) { - const intervalId = setInterval(() => { - if (this.state.startExecution()) { - const currentPing = this.state.ping(); - this.state.setPing(currentPing + 1); - } else { - clearInterval(intervalId); - } - }, 500); - onCleanup(() => { - clearInterval(intervalId); - }); - } - }); - } - - ngOnInit() { - this.initThemeDetection(); - } - - ngOnDestroy() { - this.themeObserver?.disconnect(); - } - - protected handleRunQuery() { - this.isLoading.set(true); - this.state.setStartExecution(true); - } - - protected handlePageChange(direction: number) { - const nextPage = this.page() + direction; - this.state.setPage(nextPage); - } - - protected handlePageSizeChange(event: Event) { - const select = event.target as HTMLSelectElement; - const newSize = Number(select.value); - if (newSize) { - this.state.setPageSize(newSize); - } - } - - protected handleMaxColumnsChange(event: Event) { - const select = event.target as HTMLSelectElement; - const maxCols = Number(select.value); - this.state.setMaxColumns(maxCols); - } - - protected handleTableClick(event: MouseEvent) { - const target = event.target as HTMLElement; - const header = target.closest('th'); - if (!header) return; - - const headerDiv = header.querySelector( - 'div.bf-header-content' - ) as HTMLElement | null; - if (!headerDiv) return; - - const columnName = this.getColumnName(headerDiv); - const sortableColumns = this.state.orderableColumns(); - if (!columnName || !sortableColumns.includes(columnName)) return; - - const currentSortContext = [...this.state.sortContext()]; - const sortIndex = currentSortContext.findIndex( - (item) => item.column === columnName - ); - let newContext = [...currentSortContext]; - - if (event.shiftKey) { - if (sortIndex !== -1) { - // Toggle: Asc -> Desc -> Unsorted - if (newContext[sortIndex].ascending) { - newContext[sortIndex] = { - ...newContext[sortIndex], - ascending: false - }; - } else { - newContext.splice(sortIndex, 1); - } - } else { - newContext.push({ column: columnName, ascending: true }); - } - } else { - // Single column sort mode - if (sortIndex !== -1 && newContext.length === 1) { - // Toggle: Asc -> Desc -> Unsorted - if (newContext[sortIndex].ascending) { - newContext[sortIndex] = { - ...newContext[sortIndex], - ascending: false - }; - } else { - newContext = []; - } - } else { - newContext = [{ column: columnName, ascending: true }]; - } - } - - this.state.setSortContext(newContext); - } - - private getColumnName(headerDiv: HTMLElement): string { - const clone = headerDiv.cloneNode(true) as HTMLElement; - clone.querySelector('.sort-indicator')?.remove(); - return clone.textContent?.trim() || ''; - } - - private applySortIndicators() { - const container = this.tableContainerRef?.nativeElement; - if (!container) return; - - const sortableColumns = this.state.orderableColumns(); - const currentSortContext = this.state.sortContext() || []; - - const getSortIndex = (colName: string) => - currentSortContext.findIndex((item) => item.column === colName); - - const headers = container.querySelectorAll('th'); - headers.forEach((header: HTMLElement) => { - const headerDiv = header.querySelector( - 'div.bf-header-content' - ) as HTMLElement | null; - if (!headerDiv) return; - - const columnName = this.getColumnName(headerDiv); - if (columnName && sortableColumns.includes(columnName)) { - - let indicatorSpan = headerDiv.querySelector( - '.sort-indicator' - ) as HTMLElement; - if (!indicatorSpan) { - indicatorSpan = document.createElement('span'); - indicatorSpan.classList.add('sort-indicator'); - indicatorSpan.style.paddingLeft = '5px'; - headerDiv.appendChild(indicatorSpan); - } - - const sortIndex = getSortIndex(columnName); - if (sortIndex !== -1) { - const isAscending = currentSortContext[sortIndex].ascending; - indicatorSpan.textContent = isAscending ? '▲' : '▼'; - indicatorSpan.style.visibility = 'visible'; - } else { - indicatorSpan.textContent = '●'; - indicatorSpan.style.visibility = 'hidden'; - } - } - }); - } - - private lockInitialHeight() { - if (this.isHeightInitialized) return; - const container = this.tableContainerRef?.nativeElement; - if (!container) return; - - const table = container.querySelector('table'); - if (table && (table as HTMLElement).offsetHeight > 0) { - const currentHeight = container.offsetHeight; - if (currentHeight > 0) { - container.style.height = `${currentHeight}px`; - this.isHeightInitialized = true; - } - } - } - - private initThemeDetection() { - this.updateTheme(); - const observer = new MutationObserver(() => this.updateTheme()); - observer.observe(document.body, { - attributes: true, - attributeFilter: ['class', 'data-theme', 'data-vscode-theme-kind'], - }); - this.themeObserver = observer; - } - - private updateTheme() { - const body = document.body; - const isDark = - body.classList.contains('vscode-dark') || - body.classList.contains('theme-dark') || - body.dataset['theme'] === 'dark' || - body.getAttribute('data-vscode-theme-kind') === 'vscode-dark'; - this.isDarkMode.set(isDark); - } -} diff --git a/bigframes/display/table_widget_angular/src/app/widget-state.service.spec.ts b/bigframes/display/table_widget_angular/src/app/widget-state.service.spec.ts deleted file mode 100644 index 563f9fa75a5..00000000000 --- a/bigframes/display/table_widget_angular/src/app/widget-state.service.spec.ts +++ /dev/null @@ -1,129 +0,0 @@ -/* - * Copyright 2026 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 { TestBed } from '@angular/core/testing'; -import { vi } from 'vitest'; -import { WidgetStateService } from './widget-state.service'; - -describe('WidgetStateService', () => { - let service: WidgetStateService; - let mockModel: any; - let mockListeners: { [key: string]: Function }; - - beforeEach(() => { - mockListeners = {}; - mockModel = { - get: vi.fn().mockImplementation((prop: string) => { - if (prop === 'page') return 2; - if (prop === 'page_size') return 25; - if (prop === 'max_columns') return 10; - if (prop === 'row_count') return 150; - if (prop === 'table_html') return '
'; - if (prop === 'sort_context') { - return [{ column: 'col1', ascending: true }]; - } - if (prop === 'orderable_columns') { - return ['col1', 'col2']; - } - if (prop === 'error_message') return 'initial error'; - return null; - }), - set: vi.fn(), - save_changes: vi.fn(), - on: vi.fn().mockImplementation( - (event: string, callback: Function) => { - mockListeners[event] = callback; - } - ) - }; - - TestBed.configureTestingModule({ - providers: [ - WidgetStateService, - { provide: 'ANYWIDGET_MODEL', useValue: mockModel } - ] - }); - service = TestBed.inject(WidgetStateService); - }); - - it('should be created', () => { - expect(service).toBeTruthy(); - }); - - it('should initialize signals from model values', () => { - expect(service.page()).toBe(2); - expect(service.pageSize()).toBe(25); - expect(service.maxColumns()).toBe(10); - expect(service.rowCount()).toBe(150); - expect(service.tableHtml()).toBe('
'); - expect(service.sortContext()).toEqual([ - { column: 'col1', ascending: true } - ]); - expect(service.orderableColumns()).toEqual(['col1', 'col2']); - expect(service.errorMessage()).toBe('initial error'); - }); - - it('should update signals when model triggers change events', () => { - mockModel.get.mockImplementation((prop: string) => { - if (prop === 'page') return 5; - if (prop === 'page_size') return 50; - return null; - }); - - mockListeners['change:page'](); - mockListeners['change:page_size'](); - - expect(service.page()).toBe(5); - expect(service.pageSize()).toBe(50); - }); - - it('should support dual-listen pattern for error messages', () => { - // 1. Check error_message change - mockModel.get.mockImplementation((prop: string) => { - if (prop === 'error_message') return 'new error'; - return null; - }); - mockListeners['change:error_message'](); - expect(service.errorMessage()).toBe('new error'); - - // 2. Check _error_message change - mockModel.get.mockImplementation((prop: string) => { - if (prop === '_error_message') return 'new private error'; - return null; - }); - mockListeners['change:_error_message'](); - expect(service.errorMessage()).toBe('new private error'); - }); - - it('should write updates back to model on setter methods', () => { - service.setPage(4); - expect(mockModel.set).toHaveBeenCalledWith('page', 4); - expect(mockModel.save_changes).toHaveBeenCalled(); - - service.setPageSize(100); - expect(mockModel.set).toHaveBeenCalledWith('page_size', 100); - expect(mockModel.set).toHaveBeenCalledWith('page', 0); - - service.setMaxColumns(15); - expect(mockModel.set).toHaveBeenCalledWith('max_columns', 15); - - service.setSortContext([{ column: 'col2', ascending: false }]); - expect(mockModel.set).toHaveBeenCalledWith( - 'sort_context', - [{ column: 'col2', ascending: false }] - ); - }); -}); diff --git a/bigframes/display/table_widget_angular/src/app/widget-state.service.ts b/bigframes/display/table_widget_angular/src/app/widget-state.service.ts deleted file mode 100644 index 54eff6eb948..00000000000 --- a/bigframes/display/table_widget_angular/src/app/widget-state.service.ts +++ /dev/null @@ -1,157 +0,0 @@ -/* - * Copyright 2026 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 { Injectable, Inject, signal } from '@angular/core'; - -export interface SortItem { - column: string; - ascending: boolean; -} - -@Injectable() -export class WidgetStateService { - readonly page = signal(0); - readonly pageSize = signal(10); - readonly maxColumns = signal(0); - readonly rowCount = signal(null); - readonly tableHtml = signal(''); - readonly sortContext = signal([]); - readonly orderableColumns = signal([]); - readonly errorMessage = signal(null); - readonly startExecution = signal(false); - readonly isDeferredMode = signal(false); - readonly dryRunInfo = signal(''); - readonly ping = signal(0); - - constructor(@Inject('ANYWIDGET_MODEL') private model: any) { - if (model) { - // Initialize from the model - this.page.set(model.get('page') ?? 0); - this.pageSize.set(model.get('page_size') ?? 10); - this.maxColumns.set(model.get('max_columns') ?? 0); - this.rowCount.set(model.get('row_count') ?? null); - this.tableHtml.set(model.get('table_html') ?? ''); - this.sortContext.set(model.get('sort_context') ?? []); - this.orderableColumns.set(model.get('orderable_columns') ?? []); - const initialError = - model.get('error_message') ?? - model.get('_error_message') ?? - null; - this.errorMessage.set(initialError); - this.startExecution.set(model.get('start_execution') ?? false); - this.isDeferredMode.set(model.get('is_deferred_mode') ?? false); - this.dryRunInfo.set(model.get('dry_run_info') ?? ''); - this.ping.set(model.get('ping') ?? 0); - - // Register event listeners for anywidget updates - model.on('change:page', () => { - this.page.set(model.get('page')); - }); - model.on('change:page_size', () => { - this.pageSize.set(model.get('page_size')); - }); - model.on('change:max_columns', () => { - this.maxColumns.set(model.get('max_columns')); - }); - model.on('change:row_count', () => { - this.rowCount.set(model.get('row_count')); - }); - model.on('change:table_html', () => { - this.tableHtml.set(model.get('table_html')); - }); - model.on('change:sort_context', () => { - this.sortContext.set(model.get('sort_context')); - }); - model.on('change:orderable_columns', () => { - this.orderableColumns.set(model.get('orderable_columns')); - }); - model.on('change:start_execution', () => { - this.startExecution.set(model.get('start_execution') ?? false); - }); - model.on('change:is_deferred_mode', () => { - this.isDeferredMode.set(model.get('is_deferred_mode') ?? false); - }); - model.on('change:dry_run_info', () => { - this.dryRunInfo.set(model.get('dry_run_info') ?? ''); - }); - model.on('change:ping', () => { - this.ping.set(model.get('ping') ?? 0); - }); - - // Robust dual-listen pattern for error messages (with/without underscore) - const handleErrorChange = () => { - const err = - model.get('error_message') ?? - model.get('_error_message') ?? - null; - this.errorMessage.set(err); - }; - model.on('change:error_message', handleErrorChange); - model.on('change:_error_message', handleErrorChange); - } - } - - setPage(page: number) { - this.page.set(page); - if (this.model) { - this.model.set('page', page); - this.model.save_changes(); - } - } - - setPageSize(pageSize: number) { - this.pageSize.set(pageSize); - this.page.set(0); - if (this.model) { - this.model.set('page_size', pageSize); - // Reset to page 0 on page size change - this.model.set('page', 0); - this.model.save_changes(); - } - } - - setMaxColumns(maxColumns: number) { - this.maxColumns.set(maxColumns); - if (this.model) { - this.model.set('max_columns', maxColumns); - this.model.save_changes(); - } - } - - setSortContext(context: SortItem[]) { - this.sortContext.set(context); - if (this.model) { - this.model.set('sort_context', context); - this.model.save_changes(); - } - } - - setStartExecution(startExecution: boolean) { - this.startExecution.set(startExecution); - if (this.model) { - this.model.set('start_execution', startExecution); - this.model.save_changes(); - } - } - - setPing(ping: number) { - this.ping.set(ping); - if (this.model) { - this.model.set('ping', ping); - this.model.save_changes(); - } - } -} diff --git a/bigframes/display/table_widget_angular/src/index.html b/bigframes/display/table_widget_angular/src/index.html deleted file mode 100644 index f5dda01b48a..00000000000 --- a/bigframes/display/table_widget_angular/src/index.html +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - TableWidgetAngular - - - - - -
- - diff --git a/bigframes/display/table_widget_angular/src/main.ts b/bigframes/display/table_widget_angular/src/main.ts deleted file mode 100644 index 3d515bb3d34..00000000000 --- a/bigframes/display/table_widget_angular/src/main.ts +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright 2026 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 { createApplication } from '@angular/platform-browser'; -import { App } from './app/app'; -import { ApplicationConfig, provideBrowserGlobalErrorListeners, provideZonelessChangeDetection } from '@angular/core'; - -function render({ model, el }: { model: any, el: HTMLElement }) { - // Create a container for the Angular app - const appRoot = document.createElement('div'); - appRoot.setAttribute('app-root', ''); - el.appendChild(appRoot); - - const appConfig: ApplicationConfig = { - providers: [ - provideBrowserGlobalErrorListeners(), - provideZonelessChangeDetection(), - { provide: 'ANYWIDGET_MODEL', useValue: model } - ] - }; - - createApplication(appConfig) - .then((appRef) => { - appRef.bootstrap(App, appRoot); - appRoot.removeAttribute('app-root'); - }) - .catch((err) => console.error(err)); -} - -export default { render }; diff --git a/bigframes/display/table_widget_angular/src/styles.css b/bigframes/display/table_widget_angular/src/styles.css deleted file mode 100644 index 95b248dae0a..00000000000 --- a/bigframes/display/table_widget_angular/src/styles.css +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright 2026 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. - */ - -/* You can add global styles to this file, and also import other style files */ diff --git a/bigframes/display/table_widget_angular/tsconfig.app.json b/bigframes/display/table_widget_angular/tsconfig.app.json deleted file mode 100644 index 264f459bf87..00000000000 --- a/bigframes/display/table_widget_angular/tsconfig.app.json +++ /dev/null @@ -1,15 +0,0 @@ -/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */ -/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */ -{ - "extends": "./tsconfig.json", - "compilerOptions": { - "outDir": "./out-tsc/app", - "types": [] - }, - "include": [ - "src/**/*.ts" - ], - "exclude": [ - "src/**/*.spec.ts" - ] -} diff --git a/bigframes/display/table_widget_angular/tsconfig.json b/bigframes/display/table_widget_angular/tsconfig.json deleted file mode 100644 index 2ab7442758f..00000000000 --- a/bigframes/display/table_widget_angular/tsconfig.json +++ /dev/null @@ -1,33 +0,0 @@ -/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */ -/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */ -{ - "compileOnSave": false, - "compilerOptions": { - "strict": true, - "noImplicitOverride": true, - "noPropertyAccessFromIndexSignature": true, - "noImplicitReturns": true, - "noFallthroughCasesInSwitch": true, - "skipLibCheck": true, - "isolatedModules": true, - "experimentalDecorators": true, - "importHelpers": true, - "target": "ES2022", - "module": "preserve" - }, - "angularCompilerOptions": { - "enableI18nLegacyMessageIdFormat": false, - "strictInjectionParameters": true, - "strictInputAccessModifiers": true, - "strictTemplates": true - }, - "files": [], - "references": [ - { - "path": "./tsconfig.app.json" - }, - { - "path": "./tsconfig.spec.json" - } - ] -} diff --git a/bigframes/display/table_widget_angular/tsconfig.spec.json b/bigframes/display/table_widget_angular/tsconfig.spec.json deleted file mode 100644 index d38370633f6..00000000000 --- a/bigframes/display/table_widget_angular/tsconfig.spec.json +++ /dev/null @@ -1,15 +0,0 @@ -/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */ -/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */ -{ - "extends": "./tsconfig.json", - "compilerOptions": { - "outDir": "./out-tsc/spec", - "types": [ - "vitest/globals" - ] - }, - "include": [ - "src/**/*.d.ts", - "src/**/*.spec.ts" - ] -} diff --git a/bigframes/dtypes.py b/bigframes/dtypes.py index 3cc7e918aa0..cd35e380c02 100644 --- a/bigframes/dtypes.py +++ b/bigframes/dtypes.py @@ -14,24 +14,21 @@ """Mappings for Pandas dtypes supported by BigQuery DataFrames package""" -import datetime -import decimal import textwrap import typing -import warnings -from dataclasses import dataclass -from typing import Any, Dict, List, Literal, Sequence, Union +from typing import Any, Dict, Iterable, Literal, Tuple, Union -import bigframes_vendored.constants as constants -import db_dtypes # type: ignore import geopandas as gpd # type: ignore -import google.cloud.bigquery +import google.cloud.bigquery as bigquery +import ibis +import ibis.expr.datatypes as ibis_dtypes +import ibis.expr.types as ibis_types import numpy as np import pandas as pd import pyarrow as pa -import shapely.geometry # type: ignore -import bigframes.exceptions +import bigframes.constants as constants +import third_party.bigframes_vendored.google_cloud_bigquery._pandas_helpers as gcb3p_pandas_helpers # Type hints for Pandas dtypes supported by BigQuery DataFrame Dtype = Union[ @@ -40,1030 +37,390 @@ pd.Int64Dtype, pd.StringDtype, pd.ArrowDtype, - gpd.array.GeometryDtype, ] -DTYPES = typing.get_args(Dtype) -# Represents both column types (dtypes) and local-only types -# None represents the type of a None scalar. -ExpressionType = typing.Optional[Dtype] - -# Convert to arrow when in array or struct -INT_DTYPE = pd.Int64Dtype() -FLOAT_DTYPE = pd.Float64Dtype() -BOOL_DTYPE = pd.BooleanDtype() -# Wrapped arrow dtypes -STRING_DTYPE = pd.StringDtype(storage="pyarrow") -BYTES_DTYPE = pd.ArrowDtype(pa.binary()) -DATE_DTYPE = pd.ArrowDtype(pa.date32()) -TIME_DTYPE = pd.ArrowDtype(pa.time64("us")) -DATETIME_DTYPE = pd.ArrowDtype(pa.timestamp("us")) -TIMESTAMP_DTYPE = pd.ArrowDtype(pa.timestamp("us", tz="UTC")) -TIMEDELTA_DTYPE = pd.ArrowDtype(pa.duration("us")) -NUMERIC_DTYPE = pd.ArrowDtype(pa.decimal128(38, 9)) -BIGNUMERIC_DTYPE = pd.ArrowDtype(pa.decimal256(76, 38)) -# No arrow equivalent -GEO_DTYPE = gpd.array.GeometryDtype() -# JSON -# TODO(https://github.com/pandas-dev/pandas/issues/60958): switch to -# pyarrow.json_(pyarrow.string()) when pandas 3+ and pyarrow 18+ is installed. -JSON_ARROW_TYPE = db_dtypes.JSONArrowType() -JSON_DTYPE = pd.ArrowDtype(JSON_ARROW_TYPE) -OBJ_REF_DTYPE = pd.ArrowDtype( - pa.struct( - ( - pa.field( - "uri", - pa.string(), - ), - pa.field( - "version", - pa.string(), - ), - pa.field( - "authorizer", - pa.string(), - ), - pa.field( - "details", - JSON_ARROW_TYPE, - ), - ) - ) -) - -# Used when storing Null expressions -DEFAULT_DTYPE = FLOAT_DTYPE - -LOCAL_SCALAR_TYPE = Union[ - bool, - np.bool_, - int, - np.integer, - float, - np.floating, - decimal.Decimal, - str, - np.str_, - bytes, - np.bytes_, - datetime.datetime, - pd.Timestamp, - datetime.date, - datetime.time, - pd.Timedelta, - datetime.timedelta, - np.timedelta64, -] -LOCAL_SCALAR_TYPES = typing.get_args(LOCAL_SCALAR_TYPE) - -SUPPORTED_LITERAL_TYPE = typing.Union[ - bytes, - str, - int, - bool, - float, - datetime.datetime, - datetime.date, - datetime.time, - decimal.Decimal, - list, - shapely.geometry.base.BaseGeometry, -] -SUPPORTED_LITERAL_TYPES = typing.get_args(SUPPORTED_LITERAL_TYPE) - - -# Will have a few dtype variants: simple(eg. int, string, bool), complex (eg. list, struct), and virtual (eg. micro intervals, categorical) -@dataclass(frozen=True) -class SimpleDtypeInfo: - """ - A simple dtype maps 1:1 with a database type and is not parameterized. - """ - - dtype: Dtype - arrow_dtype: typing.Optional[pa.DataType] - type_kind: typing.Tuple[ - str, ... - ] # Should all correspond to the same db type. Put preferred canonical sql type name first - logical_bytes: int = ( - 8 # this is approximate only, some types are variably sized, also, compression - ) - orderable: bool = False - clusterable: bool = False - - -# TODO: Missing BQ types: INTERVAL, JSON, RANGE -# TODO: Add mappings to python types -SIMPLE_TYPES = ( - SimpleDtypeInfo( - dtype=INT_DTYPE, - arrow_dtype=pa.int64(), - type_kind=("INTEGER", "INT64"), - orderable=True, - clusterable=True, - ), - SimpleDtypeInfo( - dtype=FLOAT_DTYPE, - arrow_dtype=pa.float64(), - type_kind=("FLOAT", "FLOAT64"), - orderable=True, - ), - SimpleDtypeInfo( - dtype=BOOL_DTYPE, - arrow_dtype=pa.bool_(), - type_kind=( - "BOOLEAN", - "BOOL", - ), - logical_bytes=1, - orderable=True, - clusterable=True, - ), - SimpleDtypeInfo( - dtype=STRING_DTYPE, - arrow_dtype=pa.string(), - type_kind=("STRING",), - orderable=True, - clusterable=True, - ), - SimpleDtypeInfo( - dtype=JSON_DTYPE, - arrow_dtype=db_dtypes.JSONArrowType(), - type_kind=("JSON",), - orderable=False, - clusterable=False, - ), - SimpleDtypeInfo( - dtype=DATE_DTYPE, - arrow_dtype=pa.date32(), - type_kind=("DATE",), - logical_bytes=4, - orderable=True, - clusterable=True, - ), - SimpleDtypeInfo( - dtype=TIME_DTYPE, - arrow_dtype=pa.time64("us"), - type_kind=("TIME",), - orderable=True, - ), - SimpleDtypeInfo( - dtype=DATETIME_DTYPE, - arrow_dtype=pa.timestamp("us"), - type_kind=("DATETIME",), - orderable=True, - clusterable=True, - ), - SimpleDtypeInfo( - dtype=TIMESTAMP_DTYPE, - arrow_dtype=pa.timestamp("us", tz="UTC"), - type_kind=("TIMESTAMP",), - orderable=True, - clusterable=True, - ), - SimpleDtypeInfo( - dtype=BYTES_DTYPE, arrow_dtype=pa.binary(), type_kind=("BYTES",), orderable=True - ), - SimpleDtypeInfo( - dtype=NUMERIC_DTYPE, - arrow_dtype=pa.decimal128(38, 9), - type_kind=("NUMERIC", "DECIMAL"), - logical_bytes=16, - orderable=True, - clusterable=True, - ), - SimpleDtypeInfo( - dtype=BIGNUMERIC_DTYPE, - arrow_dtype=pa.decimal256(76, 38), - type_kind=("BIGNUMERIC", "BIGDECIMAL"), - logical_bytes=32, - orderable=True, - clusterable=True, - ), - # Geo has no corresponding arrow dtype - SimpleDtypeInfo( - dtype=GEO_DTYPE, - arrow_dtype=None, - type_kind=("GEOGRAPHY",), - logical_bytes=40, - clusterable=True, - ), -) +# Corresponds to the pandas concept of numeric type (such as when 'numeric_only' is specified in an operation) +NUMERIC_BIGFRAMES_TYPES = [pd.BooleanDtype(), pd.Float64Dtype(), pd.Int64Dtype()] +# On BQ side, ARRAY, STRUCT, GEOGRAPHY, JSON are not orderable +UNORDERED_DTYPES = [gpd.array.GeometryDtype()] # Type hints for dtype strings supported by BigQuery DataFrame DtypeString = Literal[ "boolean", "Float64", "Int64", - "int64[pyarrow]", "string", "string[pyarrow]", "timestamp[us, tz=UTC][pyarrow]", "timestamp[us][pyarrow]", "date32[day][pyarrow]", "time64[us][pyarrow]", - "decimal128(38, 9)[pyarrow]", - "decimal256(76, 38)[pyarrow]", - "binary[pyarrow]", - "duration[us][pyarrow]", -] - -DTYPE_STRINGS = typing.get_args(DtypeString) - -BOOL_BIGFRAMES_TYPES = [BOOL_DTYPE] - -# Corresponds to the pandas concept of numeric type (such as when 'numeric_only' is specified in an operation) -# Pandas is inconsistent, so two definitions are provided, each used in different contexts -NUMERIC_BIGFRAMES_TYPES_RESTRICTIVE: List[Dtype] = [ - FLOAT_DTYPE, - INT_DTYPE, -] -NUMERIC_BIGFRAMES_TYPES_PERMISSIVE = NUMERIC_BIGFRAMES_TYPES_RESTRICTIVE + [ - BOOL_DTYPE, - NUMERIC_DTYPE, - BIGNUMERIC_DTYPE, ] - -# Temporal types that are considered as "numeric" by Pandas -TEMPORAL_NUMERIC_BIGFRAMES_TYPES: List[Dtype] = [ - DATE_DTYPE, - TIMESTAMP_DTYPE, - DATETIME_DTYPE, +# Type hints for Ibis data types supported by BigQuery DataFrame +IbisDtype = Union[ + ibis_dtypes.Boolean, + ibis_dtypes.Float64, + ibis_dtypes.Int64, + ibis_dtypes.String, + ibis_dtypes.Date, + ibis_dtypes.Time, + ibis_dtypes.Timestamp, ] -TEMPORAL_BIGFRAMES_TYPES = TEMPORAL_NUMERIC_BIGFRAMES_TYPES + [TIME_DTYPE] - - -# dtype predicates - use these to maintain consistency -def is_datetime_like(type_: ExpressionType) -> bool: - return type_ in (DATETIME_DTYPE, TIMESTAMP_DTYPE) +BOOL_BIGFRAMES_TYPES = [pd.BooleanDtype()] -def is_date_like(type_: ExpressionType) -> bool: - return type_ in (DATETIME_DTYPE, TIMESTAMP_DTYPE, DATE_DTYPE) - - -def is_time_like(type_: ExpressionType) -> bool: - return type_ in (DATETIME_DTYPE, TIMESTAMP_DTYPE, TIME_DTYPE) - - -def is_time_or_date_like(type_: ExpressionType) -> bool: - return type_ in (DATE_DTYPE, DATETIME_DTYPE, TIME_DTYPE, TIMESTAMP_DTYPE) - - -def is_geo_like(type_: ExpressionType) -> bool: - return type_ in (GEO_DTYPE,) - - -def is_binary_like(type_: ExpressionType) -> bool: - return type_ in (BOOL_DTYPE, BYTES_DTYPE, INT_DTYPE) - - -def is_object_like(type_: Union[ExpressionType, str]) -> bool: - # See: https://stackoverflow.com/a/40312924/101923 and - # https://numpy.org/doc/stable/reference/generated/numpy.dtype.kind.html - # for the way to identify object type. - return type_ in ("object", "O") or ( - getattr(type_, "kind", None) == "O" - and getattr(type_, "storage", None) != "pyarrow" - ) - - -def is_string_like(type_: ExpressionType) -> bool: - return type_ in (STRING_DTYPE, BYTES_DTYPE) - - -def is_array_like(type_: ExpressionType) -> bool: - return isinstance(type_, pd.ArrowDtype) and isinstance( - type_.pyarrow_dtype, pa.ListType - ) - - -def is_array_string_like(type_: ExpressionType) -> bool: - return ( - isinstance(type_, pd.ArrowDtype) - and isinstance(type_.pyarrow_dtype, pa.ListType) - and pa.types.is_string(type_.pyarrow_dtype.value_type) - ) - - -def is_struct_like(type_: ExpressionType) -> bool: - return isinstance(type_, pd.ArrowDtype) and isinstance( - type_.pyarrow_dtype, pa.StructType - ) - - -def is_json_arrow_type(type_: pa.DataType) -> bool: - return isinstance(type_, db_dtypes.JSONArrowType) or ( - hasattr(pa, "JsonType") and isinstance(type_, pa.JsonType) - ) - - -def is_json_like(type_: ExpressionType) -> bool: - return type_ == JSON_DTYPE or type_ == STRING_DTYPE # Including JSON string - - -def is_json_encoding_type(type_: ExpressionType, strict: bool = False) -> bool: - # Types can be converted into JSON. - # https://cloud.google.com/bigquery/docs/reference/standard-sql/json_functions#json_encodings - if is_array_like(type_): - return is_json_encoding_type(get_array_inner_type(type_), strict=strict) - if is_struct_like(type_): - return all( - is_json_encoding_type(field_type, strict=strict) - for field_type in get_struct_fields(type_).values() - ) - - if strict: - # Strict are the types (mostly) defined by json spec, with no/minimal - # encoding/decoding involved. So no temporal types. - return type_ in ( - INT_DTYPE, - FLOAT_DTYPE, - BOOL_DTYPE, - STRING_DTYPE, - JSON_DTYPE, - ) - else: - # GoogleSQL implementation handles anything but GEO - return type_ != GEO_DTYPE - - -def is_numeric(type_: ExpressionType, include_bool: bool = True) -> bool: - is_numeric = type_ in NUMERIC_BIGFRAMES_TYPES_PERMISSIVE - return is_numeric if include_bool else is_numeric and type_ != BOOL_DTYPE - - -def is_iterable(type_: ExpressionType) -> bool: - return type_ in (STRING_DTYPE, BYTES_DTYPE) or is_array_like(type_) - - -def is_comparable(type_: ExpressionType) -> bool: - return (type_ is not None) and is_orderable(type_) - - -def can_compare(type1: ExpressionType, type2: ExpressionType) -> bool: - try: - coerced_type = coerce_to_common(type1, type2) - return is_comparable(coerced_type) - except TypeError: - return False - - -def get_struct_fields(type_: ExpressionType) -> dict[str, Dtype]: - assert isinstance(type_, pd.ArrowDtype) - assert isinstance(type_.pyarrow_dtype, pa.StructType) - struct_type = type_.pyarrow_dtype - result: dict[str, Dtype] = {} - - # Local import to break circular dependency with core.backports - import bigframes.core.backports - - for field in bigframes.core.backports.pyarrow_struct_type_fields(struct_type): - result[field.name] = arrow_dtype_to_bigframes_dtype(field.type) - return result - - -def get_array_inner_type(type_: ExpressionType) -> Dtype: - assert isinstance(type_, pd.ArrowDtype) - assert isinstance(type_.pyarrow_dtype, pa.ListType) - list_type = type_.pyarrow_dtype - return arrow_dtype_to_bigframes_dtype(list_type.value_type) - - -def list_type(values_type: Dtype) -> Dtype: - """Create a list dtype with given value type.""" - return pd.ArrowDtype(pa.list_(bigframes_dtype_to_arrow_dtype(values_type))) - - -def struct_type(fields: Sequence[tuple[str, Dtype]]) -> Dtype: - """Create a struct dtype with give fields names and types.""" - pa_fields = [ - pa.field(str, bigframes_dtype_to_arrow_dtype(dtype)) for str, dtype in fields - ] - return pd.ArrowDtype(pa.struct(pa_fields)) +# Several operations are restricted to these types. +NUMERIC_BIGFRAMES_TYPES = [pd.BooleanDtype(), pd.Float64Dtype(), pd.Int64Dtype()] +# Type hints for Ibis data types that can be read to Python objects by BigQuery DataFrame +ReadOnlyIbisDtype = Union[ + ibis_dtypes.Binary, + ibis_dtypes.JSON, + ibis_dtypes.Decimal, + ibis_dtypes.GeoSpatial, + ibis_dtypes.Array, + ibis_dtypes.Struct, +] -_ORDERABLE_SIMPLE_TYPES = set( - mapping.dtype for mapping in SIMPLE_TYPES if mapping.orderable +BIDIRECTIONAL_MAPPINGS: Iterable[Tuple[IbisDtype, Dtype]] = ( + (ibis_dtypes.boolean, pd.BooleanDtype()), + (ibis_dtypes.date, pd.ArrowDtype(pa.date32())), + (ibis_dtypes.float64, pd.Float64Dtype()), + (ibis_dtypes.int64, pd.Int64Dtype()), + (ibis_dtypes.string, pd.StringDtype(storage="pyarrow")), + (ibis_dtypes.time, pd.ArrowDtype(pa.time64("us"))), + (ibis_dtypes.Timestamp(timezone=None), pd.ArrowDtype(pa.timestamp("us"))), + ( + ibis_dtypes.Timestamp(timezone="UTC"), + pd.ArrowDtype(pa.timestamp("us", tz="UTC")), + ), ) +BIGFRAMES_TO_IBIS: Dict[Dtype, ibis_dtypes.DataType] = { + pandas: ibis for ibis, pandas in BIDIRECTIONAL_MAPPINGS +} -def is_orderable(type_: ExpressionType) -> bool: - # On BQ side, ARRAY, STRUCT, GEOGRAPHY, JSON are not orderable - return type_ in _ORDERABLE_SIMPLE_TYPES or type_ is TIMEDELTA_DTYPE +IBIS_TO_ARROW: Dict[ibis_dtypes.DataType, pa.DataType] = { + ibis_dtypes.boolean: pa.bool_(), + ibis_dtypes.date: pa.date32(), + ibis_dtypes.float64: pa.float64(), + ibis_dtypes.int64: pa.int64(), + ibis_dtypes.string: pa.string(), + ibis_dtypes.time: pa.time64("us"), + ibis_dtypes.Timestamp(timezone=None): pa.timestamp("us"), + ibis_dtypes.Timestamp(timezone="UTC"): pa.timestamp("us", tz="UTC"), +} +ARROW_TO_IBIS = {arrow: ibis for ibis, arrow in IBIS_TO_ARROW.items()} -_CLUSTERABLE_SIMPLE_TYPES = set( - mapping.dtype for mapping in SIMPLE_TYPES if mapping.clusterable +IBIS_TO_BIGFRAMES: Dict[ibis_dtypes.DataType, Union[Dtype, np.dtype[Any]]] = { + ibis: pandas for ibis, pandas in BIDIRECTIONAL_MAPPINGS +} +# Allow REQUIRED fields to map correctly. +IBIS_TO_BIGFRAMES.update( + {ibis.copy(nullable=False): pandas for ibis, pandas in BIDIRECTIONAL_MAPPINGS} +) +IBIS_TO_BIGFRAMES.update( + { + ibis_dtypes.binary: np.dtype("O"), + ibis_dtypes.json: np.dtype("O"), + ibis_dtypes.Decimal(precision=38, scale=9, nullable=True): np.dtype("O"), + ibis_dtypes.Decimal(precision=76, scale=38, nullable=True): np.dtype("O"), + ibis_dtypes.GeoSpatial( + geotype="geography", srid=4326, nullable=True + ): gpd.array.GeometryDtype(), + # TODO: Interval + } ) - - -def is_clusterable(type_: ExpressionType) -> bool: - # https://cloud.google.com/bigquery/docs/clustered-tables#cluster_column_types - # This is based on default database type mapping, could in theory represent in non-default bq type to cluster. - return type_ in _CLUSTERABLE_SIMPLE_TYPES - - -def is_bool_coercable(type_: ExpressionType) -> bool: - # TODO: Implement more bool coercions - return ( - (type_ is None) - or is_numeric(type_) - or is_string_like(type_) - or is_array_like(type_) - ) - BIGFRAMES_STRING_TO_BIGFRAMES: Dict[DtypeString, Dtype] = { - typing.cast(DtypeString, mapping.dtype.name): mapping.dtype - for mapping in SIMPLE_TYPES + typing.cast(DtypeString, dtype.name): dtype for dtype in BIGFRAMES_TO_IBIS.keys() } # special case - string[pyarrow] doesn't include the storage in its name, and both -# "string" and "string[pyarrow]" are accepted -BIGFRAMES_STRING_TO_BIGFRAMES["string[pyarrow]"] = STRING_DTYPE +# "string" and "string[pyarrow] are accepted" +BIGFRAMES_STRING_TO_BIGFRAMES["string[pyarrow]"] = pd.StringDtype(storage="pyarrow") -# special case - both "Int64" and "int64[pyarrow]" are accepted -BIGFRAMES_STRING_TO_BIGFRAMES["int64[pyarrow]"] = INT_DTYPE -BIGFRAMES_STRING_TO_BIGFRAMES["duration[us][pyarrow]"] = TIMEDELTA_DTYPE +def ibis_dtype_to_bigframes_dtype( + ibis_dtype: ibis_dtypes.DataType, +) -> Union[Dtype, np.dtype[Any]]: + """Converts an Ibis dtype to a BigQuery DataFrames dtype -# For the purposes of dataframe.memory_usage -DTYPE_BYTE_SIZES = { - type_info.dtype: type_info.logical_bytes for type_info in SIMPLE_TYPES -} - -### Conversion Functions + Args: + ibis_dtype: The ibis dtype used to represent this type, which + should in turn correspond to an underlying BigQuery type + Returns: + The supported BigQuery DataFrames dtype, which may be provided by + pandas, numpy, or db_types -def dtype_for_etype(etype: ExpressionType) -> Dtype: - if etype is None: - return DEFAULT_DTYPE + Raises: + ValueError: if passed an unexpected type + """ + # Special cases: Ibis supports variations on these types, but currently + # our IO returns them as objects. Eventually, we should support them as + # ArrowDType (and update the IO accordingly) + if isinstance(ibis_dtype, ibis_dtypes.Array): + return np.dtype("O") + + if isinstance(ibis_dtype, ibis_dtypes.Struct): + return pd.ArrowDtype(ibis_dtype_to_arrow_dtype(ibis_dtype)) + + # BigQuery only supports integers of size 64 bits. + if isinstance(ibis_dtype, ibis_dtypes.Integer): + return pd.Int64Dtype() + + if ibis_dtype in IBIS_TO_BIGFRAMES: + return IBIS_TO_BIGFRAMES[ibis_dtype] + elif isinstance(ibis_dtype, ibis_dtypes.Null): + # Fallback to STRING for NULL values for most flexibility in SQL. + return IBIS_TO_BIGFRAMES[ibis_dtypes.string] else: - return etype - - -# Mapping between arrow and bigframes types are necessary because arrow types are used for structured types, but not all primitive types, -# so conversion are needed when data is nested or unnested. Also, sometimes local data is stored as arrow. -_ARROW_TO_BIGFRAMES = { - mapping.arrow_dtype: mapping.dtype - for mapping in SIMPLE_TYPES - if mapping.arrow_dtype is not None -} - -# Include types that aren't 1:1 to BigQuery but allowed to be loaded in to BigQuery: -_ARROW_TO_BIGFRAMES_LOSSLESS = { - pa.int8(): INT_DTYPE, - pa.int16(): INT_DTYPE, - pa.int32(): INT_DTYPE, - pa.uint8(): INT_DTYPE, - pa.uint16(): INT_DTYPE, - pa.uint32(): INT_DTYPE, - # uint64 is omitted because uint64 -> BigQuery INT64 is a lossy conversion. - pa.float16(): FLOAT_DTYPE, - pa.float32(): FLOAT_DTYPE, - # TODO(tswast): Can we support datetime/timestamp/time with units larger - # than microseconds? -} - + raise ValueError( + f"Unexpected Ibis data type {ibis_dtype}. {constants.FEEDBACK_LINK}" + ) -def arrow_dtype_to_bigframes_dtype( - arrow_dtype: pa.DataType, allow_lossless_cast: bool = False -) -> Dtype: - """ - Convert an arrow type into the pandas-y type used to represent it in BigFrames. - Args: - arrow_dtype: Arrow data type. - allow_lossless_cast: Allow lossless conversions, such as int32 to int64. - """ - if allow_lossless_cast and arrow_dtype in _ARROW_TO_BIGFRAMES_LOSSLESS: - return _ARROW_TO_BIGFRAMES_LOSSLESS[arrow_dtype] +def ibis_dtype_to_arrow_dtype(ibis_dtype: ibis_dtypes.DataType) -> pa.DataType: + if isinstance(ibis_dtype, ibis_dtypes.Array): + return pa.list_(ibis_dtype_to_arrow_dtype(ibis_dtype.value_type)) - if arrow_dtype in _ARROW_TO_BIGFRAMES: - return _ARROW_TO_BIGFRAMES[arrow_dtype] + if isinstance(ibis_dtype, ibis_dtypes.Struct): + return pa.struct( + [ + (name, ibis_dtype_to_arrow_dtype(dtype)) + for name, dtype in ibis_dtype.fields.items() + ] + ) - if pa.types.is_list(arrow_dtype): - return pd.ArrowDtype(arrow_dtype) + if ibis_dtype in IBIS_TO_ARROW: + return IBIS_TO_ARROW[ibis_dtype] + else: + raise ValueError( + f"Unexpected Ibis data type {ibis_dtype}. {constants.FEEDBACK_LINK}" + ) - if pa.types.is_struct(arrow_dtype): - return pd.ArrowDtype(arrow_dtype) - if pa.types.is_duration(arrow_dtype): - return TIMEDELTA_DTYPE +def ibis_value_to_canonical_type(value: ibis_types.Value) -> ibis_types.Value: + """Converts an Ibis expression to canonical type. - # BigFrames doesn't distinguish between string and large_string because the - # largest string (2 GB) is already larger than the largest BigQuery row. - if pa.types.is_string(arrow_dtype) or pa.types.is_large_string(arrow_dtype): - return STRING_DTYPE + This is useful in cases where multiple types correspond to the same BigFrames dtype. + """ + ibis_type = value.type() + # Allow REQUIRED fields to be joined with NULLABLE fields. + nullable_type = ibis_type.copy(nullable=True) + return value.cast(nullable_type).name(value.get_name()) - if arrow_dtype == pa.null(): - return DEFAULT_DTYPE - # Allow both db_dtypes.JSONArrowType() and pa.json_(pa.string()) - if is_json_arrow_type(arrow_dtype): - return JSON_DTYPE +def ibis_table_to_canonical_types(table: ibis_types.Table) -> ibis_types.Table: + """Converts an Ibis table expression to canonical types. - # No other types matched. - raise TypeError( - f"Unexpected Arrow data type {arrow_dtype}. {constants.FEEDBACK_LINK}" - ) - - -_BIGFRAMES_TO_ARROW = { - mapping.dtype: mapping.arrow_dtype - for mapping in SIMPLE_TYPES - if mapping.arrow_dtype is not None -} -# unidirectional mapping -_BIGFRAMES_TO_ARROW[GEO_DTYPE] = pa.string() + This is useful in cases where multiple types correspond to the same BigFrames dtype. + """ + casted_columns = [] + for column_name in table.columns: + column = typing.cast(ibis_types.Value, table[column_name]) + casted_columns.append(ibis_value_to_canonical_type(column)) + return table.select(*casted_columns) -def bigframes_dtype_to_arrow_dtype( - bigframes_dtype: Dtype, -) -> pa.DataType: - if bigframes_dtype in _BIGFRAMES_TO_ARROW: - return _BIGFRAMES_TO_ARROW[bigframes_dtype] - if isinstance(bigframes_dtype, pd.ArrowDtype): - if pa.types.is_duration(bigframes_dtype.pyarrow_dtype): - return bigframes_dtype.pyarrow_dtype - if pa.types.is_list(bigframes_dtype.pyarrow_dtype): - return bigframes_dtype.pyarrow_dtype - if pa.types.is_struct(bigframes_dtype.pyarrow_dtype): - return bigframes_dtype.pyarrow_dtype - else: - raise TypeError( - f"No arrow conversion for {bigframes_dtype}. {constants.FEEDBACK_LINK}" +def arrow_dtype_to_ibis_dtype(arrow_dtype: pa.DataType) -> ibis_dtypes.DataType: + if pa.types.is_struct(arrow_dtype): + struct_dtype = typing.cast(pa.StructType, arrow_dtype) + return ibis_dtypes.Struct.from_tuples( + [ + (field.name, arrow_dtype_to_ibis_dtype(field.type)) + for field in struct_dtype + ] ) - -def to_storage_type( - arrow_type: pa.DataType, -): - """Some pyarrow versions don't support extension types fully, such as for empty table generation.""" - if isinstance(arrow_type, pa.ExtensionType): - return arrow_type.storage_type - if pa.types.is_list(arrow_type): - assert isinstance(arrow_type, pa.ListType) - return pa.list_(to_storage_type(arrow_type.value_type)) - if pa.types.is_struct(arrow_type): - assert isinstance(arrow_type, pa.StructType) - - # Local import to break circular dependency with core.backports - import bigframes.core.backports - - return pa.struct( - field.with_type(to_storage_type(field.type)) - for field in bigframes.core.backports.pyarrow_struct_type_fields(arrow_type) - ) - return arrow_type - - -def arrow_type_to_literal( - arrow_type: pa.DataType, -) -> Any: - """Create a representative literal value for an arrow type.""" - if pa.types.is_list(arrow_type): - return [arrow_type_to_literal(arrow_type.value_type)] - - # Local import to break circular dependency with core.backports - import bigframes.core.backports - - if pa.types.is_struct(arrow_type): - return { - field.name: arrow_type_to_literal(field.type) - for field in bigframes.core.backports.pyarrow_struct_type_fields(arrow_type) - } - if pa.types.is_string(arrow_type): - return "string" - if pa.types.is_binary(arrow_type): - return b"bytes" - if pa.types.is_floating(arrow_type): - return 1.0 - if pa.types.is_integer(arrow_type): - return 1 - if pa.types.is_boolean(arrow_type): - return True - if pa.types.is_date(arrow_type): - return datetime.date(2025, 1, 1) - if pa.types.is_timestamp(arrow_type): - return datetime.datetime( - 2025, - 1, - 1, - 1, - 1, - tzinfo=datetime.timezone.utc if arrow_type.tz is not None else None, + if arrow_dtype in ARROW_TO_IBIS: + return ARROW_TO_IBIS[arrow_dtype] + else: + raise ValueError( + f"Unexpected Arrow data type {arrow_dtype}. {constants.FEEDBACK_LINK}" ) - if pa.types.is_decimal(arrow_type): - return decimal.Decimal("1.0") - if pa.types.is_time(arrow_type): - return datetime.time(1, 1, 1) - - raise TypeError( - f"No literal conversion for {arrow_type}. {constants.FEEDBACK_LINK}" - ) -def bigframes_type(dtype) -> Dtype: - """Convert type object to canoncial bigframes dtype.""" - if _is_bigframes_dtype(dtype): - return dtype - elif isinstance(dtype, str): - return _dtype_from_string(dtype) - elif isinstance(dtype, type): - return _infer_dtype_from_python_type(dtype) - elif isinstance(dtype, pa.DataType): - return arrow_dtype_to_bigframes_dtype(dtype) - else: - raise TypeError( - f"Cannot infer supported datatype for: {dtype}. {constants.FEEDBACK_LINK}" - ) +def bigframes_dtype_to_ibis_dtype( + bigframes_dtype: Union[DtypeString, Dtype, np.dtype[Any]] +) -> ibis_dtypes.DataType: + """Converts a BigQuery DataFrames supported dtype to an Ibis dtype. + Args: + bigframes_dtype: + A dtype supported by BigQuery DataFrame -def _is_bigframes_dtype(dtype) -> bool: - """True iff dtyps is a canonical bigframes dtype""" - # have to be quite strict, as pyarrow dtypes equal their string form, and we don't consider that a canonical form. - if (type(dtype), dtype) in set( - (type(item.dtype), item.dtype) for item in SIMPLE_TYPES - ): - return True - if isinstance(dtype, pd.ArrowDtype): - try: - _ = arrow_dtype_to_bigframes_dtype(dtype.pyarrow_dtype) - return True - except TypeError: - return False - return False - - -def _infer_dtype_from_python_type(type_: type) -> Dtype: - if type_ in (datetime.timedelta, pd.Timedelta, np.timedelta64): - # Must check timedelta type first. Otherwise other branchs will be evaluated to true - # E.g. np.timedelta64 is a sublcass as np.integer - return TIMEDELTA_DTYPE - if issubclass(type_, (bool, np.bool_)): - return BOOL_DTYPE - if issubclass(type_, (int, np.integer)): - return INT_DTYPE - if issubclass(type_, (float, np.floating)): - return FLOAT_DTYPE - if issubclass(type_, decimal.Decimal): - return NUMERIC_DTYPE - if issubclass(type_, (str, np.str_)): - return STRING_DTYPE - if issubclass(type_, (bytes, np.bytes_)): - return BYTES_DTYPE - if issubclass(type_, datetime.date): - return DATE_DTYPE - if issubclass(type_, datetime.time): - return TIME_DTYPE - if issubclass(type_, shapely.geometry.base.BaseGeometry): - return GEO_DTYPE - else: - raise TypeError( - f"No matching datatype for python type: {type_}. {constants.FEEDBACK_LINK}" - ) + Returns: + IbisDtype: The corresponding Ibis type + Raises: + ValueError: If passed a dtype not supported by BigQuery DataFrames. + """ + if isinstance(bigframes_dtype, pd.ArrowDtype): + return arrow_dtype_to_ibis_dtype(bigframes_dtype.pyarrow_dtype) -def _dtype_from_string(dtype_string: str) -> typing.Optional[Dtype]: - if str(dtype_string) in BIGFRAMES_STRING_TO_BIGFRAMES: - return BIGFRAMES_STRING_TO_BIGFRAMES[ - typing.cast(DtypeString, str(dtype_string)) + type_string = str(bigframes_dtype) + if type_string in BIGFRAMES_STRING_TO_BIGFRAMES: + bigframes_dtype = BIGFRAMES_STRING_TO_BIGFRAMES[ + typing.cast(DtypeString, type_string) ] - if isinstance(dtype_string, str) and dtype_string.lower() == "json": - return JSON_DTYPE - - raise TypeError( - textwrap.dedent( - f""" - Unexpected data type string {dtype_string}. The following - dtypes are supppted: 'boolean','Float64','Int64', - 'int64[pyarrow]','string','string[pyarrow]', - 'timestamp[us, tz=UTC][pyarrow]','timestamp[us][pyarrow]', - 'date32[day][pyarrow]','time64[us][pyarrow]'. - The following pandas.ExtensionDtype are supported: - pandas.BooleanDtype(), pandas.Float64Dtype(), + else: + raise ValueError( + textwrap.dedent( + f""" + Unexpected data type {bigframes_dtype}. The following + str dtypes are supppted: 'boolean','Float64','Int64', 'string', + 'tring[pyarrow]','timestamp[us, tz=UTC][pyarrow]', + 'timestamp[us][pyarrow]','date32[day][pyarrow]', + 'time64[us][pyarrow]'. The following pandas.ExtensionDtype are + supported: pandas.BooleanDtype(), pandas.Float64Dtype(), pandas.Int64Dtype(), pandas.StringDtype(storage="pyarrow"), - pandas.ArrowDtype(pa.date32()), pandas.ArrowDtype(pa.time64("us")), - pandas.ArrowDtype(pa.timestamp("us")), - pandas.ArrowDtype(pa.timestamp("us", tz="UTC")). + pd.ArrowDtype(pa.date32()), pd.ArrowDtype(pa.time64("us")), + pd.ArrowDtype(pa.timestamp("us")), + pd.ArrowDtype(pa.timestamp("us", tz="UTC")). {constants.FEEDBACK_LINK} """ + ) ) - ) - -def infer_literal_type(literal) -> typing.Optional[Dtype]: - # Maybe also normalize literal to canonical python representation to remove this burden from compilers? - if isinstance(literal, pa.Scalar): - return arrow_dtype_to_bigframes_dtype(literal.type) - if pd.api.types.is_dict_like(literal): - fields = [] - for key in literal.keys(): - field_type = bigframes_dtype_to_arrow_dtype( - infer_literal_type(literal[key]) - ) - fields.append( - pa.field(key, field_type, nullable=(not pa.types.is_list(field_type))) - ) - return pd.ArrowDtype(pa.struct(fields)) - if pd.api.types.is_list_like(literal): - element_types = [infer_literal_type(i) for i in literal] - common_type = lcd_type(*element_types) - return list_type(common_type) - if pd.isna(literal): - return None # Null value without a definite type - # Make sure to check datetime before date as datetimes are also dates - if isinstance(literal, (datetime.datetime, pd.Timestamp)): - if literal.tzinfo is not None: - return TIMESTAMP_DTYPE - else: - return DATETIME_DTYPE - from_python_type = _infer_dtype_from_python_type(type(literal)) - if from_python_type is not None: - return from_python_type - else: - raise TypeError(f"Unable to infer type for value: {literal}") + return BIGFRAMES_TO_IBIS[bigframes_dtype] -def infer_literal_arrow_type(literal) -> typing.Optional[pa.DataType]: - if pd.isna(literal): - return None # Null value without a definite type - return bigframes_dtype_to_arrow_dtype(infer_literal_type(literal)) +def literal_to_ibis_scalar( + literal, force_dtype: typing.Optional[Dtype] = None, validate: bool = True +): + """Accept any literal and, if possible, return an Ibis Scalar + expression with a BigQuery DataFrames compatible data type + Args: + literal: + any value accepted by Ibis + force_dtype: + force the value to a specific dtype + validate: + If true, will raise ValueError if type cannot be stored in a + BigQuery DataFrames object. If used as a subexpression, this should + be disabled. + + Returns: + An ibis Scalar supported by BigQuery DataFrame + + Raises: + ValueError: if passed literal cannot be coerced to a + BigQuery DataFrames compatible scalar + """ + ibis_dtype = BIGFRAMES_TO_IBIS[force_dtype] if force_dtype else None -_TK_TO_BIGFRAMES = { - type_kind: mapping.dtype - for mapping in SIMPLE_TYPES - for type_kind in mapping.type_kind -} -_BIGFRAMES_TO_TK = {mapping.dtype: mapping.type_kind[0] for mapping in SIMPLE_TYPES} - - -def convert_schema_field( - field: google.cloud.bigquery.SchemaField, -) -> typing.Tuple[str, Dtype]: - is_repeated = field.mode == "REPEATED" - if field.field_type == "RECORD": - if field.description == OBJ_REF_DESCRIPTION_TAG: - bf_dtype = OBJ_REF_DTYPE # type: ignore - if is_repeated: - pa_type = pa.list_(bigframes_dtype_to_arrow_dtype(bf_dtype)) - bf_dtype = pd.ArrowDtype(pa_type) - return field.name, bf_dtype - - mapped_fields = map(convert_schema_field, field.fields) - fields = [] - for name, dtype in mapped_fields: - arrow_type = bigframes_dtype_to_arrow_dtype(dtype) - fields.append( - pa.field(name, arrow_type, nullable=not pa.types.is_list(arrow_type)) - ) - pa_struct = pa.struct(fields) - pa_type = pa.list_(pa_struct) if is_repeated else pa_struct - return field.name, pd.ArrowDtype(pa_type) - elif ( - field.field_type == "INTEGER" - and field.description is not None - and field.description.endswith(TIMEDELTA_DESCRIPTION_TAG) - ): - return field.name, TIMEDELTA_DTYPE - elif field.field_type in _TK_TO_BIGFRAMES: - if is_repeated: - pa_type = pa.list_( - bigframes_dtype_to_arrow_dtype(_TK_TO_BIGFRAMES[field.field_type]) + if pd.api.types.is_list_like(literal): + if validate: + raise ValueError( + f"List types can't be stored in BigQuery DataFrames. {constants.FEEDBACK_LINK}" ) - return field.name, pd.ArrowDtype(pa_type) - return field.name, _TK_TO_BIGFRAMES[field.field_type] - else: - raise TypeError(f"Cannot handle type: {field.field_type}") - - -def convert_to_schema_field( - name: str, bigframes_dtype: Dtype, overrides: dict[Dtype, str] = {} -) -> google.cloud.bigquery.SchemaField: - if bigframes_dtype in overrides: - return google.cloud.bigquery.SchemaField(name, overrides[bigframes_dtype]) - if bigframes_dtype in _BIGFRAMES_TO_TK: - return google.cloud.bigquery.SchemaField( - name, _BIGFRAMES_TO_TK[bigframes_dtype] + # "correct" way would be to use ibis.array, but this produces invalid BQ SQL syntax + return tuple(literal) + if not pd.api.types.is_list_like(literal) and pd.isna(literal): + if ibis_dtype: + return ibis.null().cast(ibis_dtype) + else: + return ibis.null() + + scalar_expr = ibis.literal(literal) + if ibis_dtype: + scalar_expr = ibis.literal(literal, ibis_dtype) + elif scalar_expr.type().is_floating(): + scalar_expr = ibis.literal(literal, ibis_dtypes.float64) + elif scalar_expr.type().is_integer(): + scalar_expr = ibis.literal(literal, ibis_dtypes.int64) + + # TODO(bmil): support other literals that can be coerced to compatible types + if validate and (scalar_expr.type() not in BIGFRAMES_TO_IBIS.values()): + raise ValueError( + f"Literal did not coerce to a supported data type: {literal}. {constants.FEEDBACK_LINK}" ) - if isinstance(bigframes_dtype, pd.ArrowDtype): - if pa.types.is_list(bigframes_dtype.pyarrow_dtype): - inner_type = arrow_dtype_to_bigframes_dtype( - bigframes_dtype.pyarrow_dtype.value_type - ) - inner_field = convert_to_schema_field(name, inner_type, overrides) - return google.cloud.bigquery.SchemaField( - name, - inner_field.field_type, - mode="REPEATED", - fields=inner_field.fields, - description=inner_field.description, - ) - if pa.types.is_struct(bigframes_dtype.pyarrow_dtype): - inner_fields: list[google.cloud.bigquery.SchemaField] = [] - struct_type = typing.cast(pa.StructType, bigframes_dtype.pyarrow_dtype) - for i in range(struct_type.num_fields): - field = struct_type.field(i) - inner_bf_type = arrow_dtype_to_bigframes_dtype(field.type) - inner_fields.append( - convert_to_schema_field(field.name, inner_bf_type, overrides) - ) - - if bigframes_dtype == OBJ_REF_DTYPE: - return google.cloud.bigquery.SchemaField( - name, - "RECORD", - fields=inner_fields, - description=OBJ_REF_DESCRIPTION_TAG, - ) - - return google.cloud.bigquery.SchemaField( - name, "RECORD", fields=inner_fields - ) - if bigframes_dtype.pyarrow_dtype == pa.duration("us"): - # Timedeltas are represented as integers in microseconds. - return google.cloud.bigquery.SchemaField( - name, "INTEGER", description=TIMEDELTA_DESCRIPTION_TAG - ) - raise TypeError( - f"No arrow conversion for {bigframes_dtype}. {constants.FEEDBACK_LINK}" - ) + return scalar_expr -def bf_type_from_type_kind( - bq_schema: Sequence[google.cloud.bigquery.SchemaField], -) -> typing.Dict[str, Dtype]: - """Converts bigquery sql type to the default bigframes dtype.""" - return {name: dtype for name, dtype in map(convert_schema_field, bq_schema)} - - -def is_dtype(scalar: typing.Any, dtype: Dtype) -> bool: - """Captures whether a scalar can be losslessly represented by a dtype.""" - if pd.isna(scalar): - return True - if pd.api.types.is_bool_dtype(dtype): - return pd.api.types.is_bool(scalar) - if pd.api.types.is_float_dtype(dtype): - return pd.api.types.is_float(scalar) - if pd.api.types.is_integer_dtype(dtype): - return pd.api.types.is_integer(scalar) - if isinstance(dtype, pd.StringDtype): - return isinstance(scalar, str) - if isinstance(dtype, pd.ArrowDtype): - pa_type = dtype.pyarrow_dtype - return is_patype(scalar, pa_type) - return False - - -# string is binary -def is_patype(scalar: typing.Any, pa_type: pa.DataType) -> bool: - """Determine whether a scalar's type matches a given pyarrow type.""" - if pa_type == pa.time64("us"): - return isinstance(scalar, datetime.time) - elif pa_type == pa.timestamp("us"): - if isinstance(scalar, datetime.datetime): - return not scalar.tzinfo - if isinstance(scalar, pd.Timestamp): - return not scalar.tzinfo - elif pa_type == pa.timestamp("us", tz="UTC"): - if isinstance(scalar, datetime.datetime): - return scalar.tzinfo == datetime.timezone.utc - if isinstance(scalar, pd.Timestamp): - return scalar.tzinfo == datetime.timezone.utc - elif pa_type == pa.date32(): - return isinstance(scalar, datetime.date) - elif pa_type == pa.binary(): - return isinstance(scalar, bytes) - elif pa_type == pa.decimal128(38, 9): - # decimal.Decimal is a superset, but ibis performs out-of-bounds and loss-of-precision checks - return isinstance(scalar, decimal.Decimal) - elif pa_type == pa.decimal256(76, 38): - # decimal.Decimal is a superset, but ibis performs out-of-bounds and loss-of-precision checks - return isinstance(scalar, decimal.Decimal) - return False - - -# Utilities for type coercion, and compatibility -def is_compatible(scalar: typing.Any, dtype: Dtype) -> typing.Optional[Dtype]: - """Whether scalar can be compare to items of dtype (though maybe requiring coercion). Returns the datatype that must be used for the comparison""" - if is_dtype(scalar, dtype): - return dtype - elif pd.api.types.is_numeric_dtype(dtype): - # Implicit conversion currently only supported for numeric types - if pd.api.types.is_bool(scalar): - return lcd_type(BOOL_DTYPE, dtype) - if pd.api.types.is_float(scalar): - return lcd_type(FLOAT_DTYPE, dtype) - if pd.api.types.is_integer(scalar): - return lcd_type(INT_DTYPE, dtype) - if isinstance(scalar, decimal.Decimal): - # TODO: Check context to see if can use NUMERIC instead of BIGNUMERIC - return lcd_type(BIGNUMERIC_DTYPE, dtype) - return None - - -def lcd_type(*dtypes: Dtype) -> Dtype: - if len(dtypes) < 1: - raise ValueError("at least one dypes should be provided") - - unique_dtypes = set(dtypes) - if None in unique_dtypes: - unique_dtypes.remove(None) - - if len(unique_dtypes) == 0: - return None - if len(unique_dtypes) == 1: - return next(iter(unique_dtypes)) - - # Implicit conversion currently only supported for numeric types - hierarchy: list[Dtype] = [ - BOOL_DTYPE, - INT_DTYPE, - NUMERIC_DTYPE, - BIGNUMERIC_DTYPE, - FLOAT_DTYPE, - ] - if any([dtype not in hierarchy for dtype in unique_dtypes]): - return None - lcd_index = max([hierarchy.index(dtype) for dtype in unique_dtypes]) - return hierarchy[lcd_index] - - -def coerce_to_common(etype1: ExpressionType, etype2: ExpressionType) -> ExpressionType: - """Coerce types to a common type or throw a TypeError""" - if etype1 is not None and etype2 is not None: - common_supertype = lcd_type(etype1, etype2) - if common_supertype is not None: - return common_supertype - if can_coerce(etype1, etype2): - return etype2 - if can_coerce(etype2, etype1): - return etype1 - raise TypeError(f"Cannot coerce {etype1} and {etype2} to a common type.") - - -def can_coerce(source_type: ExpressionType, target_type: ExpressionType) -> bool: - if source_type is None: - return True # None can be coerced to any supported type - else: - return (source_type == STRING_DTYPE) and ( - target_type in TEMPORAL_BIGFRAMES_TYPES + [JSON_DTYPE] - ) +def cast_ibis_value( + value: ibis_types.Value, to_type: ibis_dtypes.DataType +) -> ibis_types.Value: + """Perform compatible type casts of ibis values -def lcd_type_or_throw(dtype1: Dtype, dtype2: Dtype) -> Dtype: - result = lcd_type(dtype1, dtype2) - if result is None: - raise NotImplementedError( - f"BigFrames cannot upcast {dtype1} and {dtype2} to common type. {constants.FEEDBACK_LINK}" + Args: + value: + Ibis value, which could be a literal, scalar, or column + + to_type: + The Ibis type to cast to + + Returns: + A new Ibis value of type to_type + + Raises: + TypeError: if the type cast cannot be executed""" + if value.type() == to_type: + return value + # casts that just work + # TODO(bmil): add to this as more casts are verified + good_casts = { + ibis_dtypes.bool: (ibis_dtypes.int64,), + ibis_dtypes.int64: ( + ibis_dtypes.bool, + ibis_dtypes.float64, + ibis_dtypes.string, + ), + ibis_dtypes.float64: (ibis_dtypes.string, ibis_dtypes.int64), + ibis_dtypes.string: (ibis_dtypes.int64, ibis_dtypes.float64), + ibis_dtypes.date: (ibis_dtypes.string,), + ibis_dtypes.Decimal(precision=38, scale=9): (ibis_dtypes.float64,), + ibis_dtypes.Decimal(precision=76, scale=38): (ibis_dtypes.float64,), + ibis_dtypes.time: (), + ibis_dtypes.timestamp: (ibis_dtypes.Timestamp(timezone="UTC"),), + ibis_dtypes.Timestamp(timezone="UTC"): (ibis_dtypes.timestamp,), + } + + value = ibis_value_to_canonical_type(value) + if value.type() in good_casts: + if to_type in good_casts[value.type()]: + return value.cast(to_type) + else: + # this should never happen + raise TypeError( + f"Unexpected value type {value.type()}. {constants.FEEDBACK_LINK}" ) - return result + # casts that need some encouragement -TIMEDELTA_DESCRIPTION_TAG = "#microseconds" -OBJ_REF_DESCRIPTION_TAG = "bigframes_dtype: OBJ_REF_DTYPE" + # BigQuery casts bools to lower case strings. Capitalize the result to match Pandas + # TODO(bmil): remove this workaround after fixing Ibis + if value.type() == ibis_dtypes.bool and to_type == ibis_dtypes.string: + return typing.cast(ibis_types.StringValue, value.cast(to_type)).capitalize() + if value.type() == ibis_dtypes.bool and to_type == ibis_dtypes.float64: + return value.cast(ibis_dtypes.int64).cast(ibis_dtypes.float64) -def contains_db_dtypes_json_arrow_type(type_): - if isinstance(type_, db_dtypes.JSONArrowType): - return True - - if isinstance(type_, pa.ListType): - return contains_db_dtypes_json_arrow_type(type_.value_type) - - if isinstance(type_, pa.StructType): - # Local import to break circular dependency with core.backports - import bigframes.core.backports - - return any( - contains_db_dtypes_json_arrow_type(field.type) - for field in bigframes.core.backports.pyarrow_struct_type_fields(type_) - ) - return False - - -def contains_db_dtypes_json_dtype(dtype): - if not isinstance(dtype, pd.ArrowDtype): - return False - - return contains_db_dtypes_json_arrow_type(dtype.pyarrow_dtype) + if value.type() == ibis_dtypes.float64 and to_type == ibis_dtypes.bool: + return value != ibis_types.literal(0) + raise TypeError( + f"Unsupported cast {value.type()} to {to_type}. {constants.FEEDBACK_LINK}" + ) -def warn_on_db_dtypes_json_dtype(dtypes): - """Warn that the JSON dtype is changing. - Note: only call this function if the user is explicitly checking the - dtypes. - """ - if any(contains_db_dtypes_json_dtype(dtype) for dtype in dtypes): - msg = bigframes.exceptions.format_message( - "JSON columns will be represented as pandas.ArrowDtype(pyarrow.json_()) " - "instead of using `db_dtypes` in the future when available in pandas " - "(https://github.com/pandas-dev/pandas/issues/60958) and pyarrow." - ) - warnings.warn(msg, bigframes.exceptions.JSONDtypeWarning) +def to_pandas_dtypes_overrides(schema: Iterable[bigquery.SchemaField]) -> Dict: + """For each STRUCT field, make sure we specify the full type to use.""" + # TODO(swast): Also override ARRAY fields. + dtypes = {} + for field in schema: + if field.field_type == "RECORD" and field.mode != "REPEATED": + # TODO(swast): We're using a private API here. Would likely be + # better if we called `to_arrow()` and converted to a pandas + # DataFrame ourselves from that. + dtypes[field.name] = pd.ArrowDtype( + gcb3p_pandas_helpers.bq_to_arrow_data_type(field) + ) + return dtypes diff --git a/bigframes/enums.py b/bigframes/enums.py deleted file mode 100644 index 3aaf6020206..00000000000 --- a/bigframes/enums.py +++ /dev/null @@ -1,41 +0,0 @@ -# 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. -# 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. - -"""Public enums used across BigQuery DataFrames.""" - -# NOTE: This module should not depend on any others in the package. - -import enum - - -class OrderingMode(enum.Enum): - """Values used to determine the ordering mode. - - Default is 'strict'. - """ - - STRICT = "strict" - PARTIAL = "partial" - - -class DefaultIndexKind(enum.Enum): - """Sentinel values used to override default indexing behavior.""" - - #: Use consecutive integers as the index. This is ``0``, ``1``, ``2``, ..., - #: ``n - 3``, ``n - 2``, ``n - 1``, where ``n`` is the number of items in - #: the index. - SEQUENTIAL_INT64 = enum.auto() - - # A completely null index incapable of indexing or alignment. - NULL = enum.auto() diff --git a/bigframes/exceptions.py b/bigframes/exceptions.py deleted file mode 100644 index dea8a55f9b5..00000000000 --- a/bigframes/exceptions.py +++ /dev/null @@ -1,154 +0,0 @@ -# Copyright 2023 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. - -"""Public exceptions and warnings used across BigQuery DataFrames.""" - -import textwrap - -# NOTE: This module should not depend on any others in the package. - - -# Uses UserWarning for backwards compatibility with warning without a category -# set. -class DefaultLocationWarning(UserWarning): - """No location was specified, so using a default one.""" - - -class UnknownLocationWarning(Warning): - """The location is set to an unknown value.""" - - -class CleanupFailedWarning(Warning): - """Bigframes failed to clean up a table or function resource.""" - - -class DefaultIndexWarning(Warning): - """Default index may cause unexpected costs.""" - - -class PreviewWarning(Warning): - """The feature is in preview.""" - - -class NullIndexPreviewWarning(PreviewWarning): - """Unused. Kept for backwards compatibility. - - Was used when null index feature was in preview. - """ - - -class NullIndexError(ValueError): - """Object has no index.""" - - -class OrderingModePartialPreviewWarning(PreviewWarning): - """Unused. Kept for backwards compatibility. - - Was used when ordering mode 'partial' was in preview. - """ - - -class OrderRequiredError(ValueError): - """Operation requires total row ordering to be enabled.""" - - -class QueryComplexityError(RuntimeError): - """Query plan is too complex to execute.""" - - -class OperationAbortedError(RuntimeError): - """Operation is aborted.""" - - -class MaximumResultRowsExceeded(RuntimeError): - """Maximum number of rows in the result was exceeded.""" - - -class TranspilationError(RuntimeError): - """Failed to transpile a Python function to BigFrames Expression.""" - - -class TimeTravelDisabledWarning(Warning): - """A query was reattempted without time travel.""" - - -class TimeTravelCacheWarning(Warning): - """Reads from the same table twice in the same session pull time travel from cache.""" - - -class AmbiguousWindowWarning(Warning): - """A query may produce nondeterministic results as the window may be ambiguously ordered. - - Deprecated. Kept for backwards compatibility for code that filters warnings - from this category. - """ - - -class UnknownDataTypeWarning(Warning): - """Data type is unknown.""" - - -class ApiDeprecationWarning(FutureWarning): - """The API has been deprecated.""" - - -class BadIndexerKeyWarning(Warning): - """The indexer key is not used correctly.""" - - -class ObsoleteVersionWarning(Warning): - """The BigFrames version is too old.""" - - -class FunctionAxisOnePreviewWarning(PreviewWarning): - """Remote Function and Managed UDF with axis=1 preview.""" - - -class JSONDtypeWarning(PreviewWarning): - """JSON dtype will be pd.ArrowDtype(pa.json_()) in the future.""" - - -class FunctionConflictTypeHintWarning(UserWarning): - """Conflicting type hints in a BigFrames function.""" - - -class FunctionPackageVersionWarning(PreviewWarning): - """ - Warns that package versions in remote function or managed function may not - match local or specified versions, which might cause unexpected behavior. - """ - - -class PythonTranspilerPreviewWarning(PreviewWarning): - """Python Transpiler is a preview feature.""" - - -def format_message(message: str, fill: bool = True): - """[Private] Formats a warning message. - - :meta private: - - Args: - message: The warning message string. - fill: Whether to wrap the message text using `textwrap.fill`. - Defaults to True. Set to False to prevent wrapping, - especially if the message already contains newlines. - - Returns: - The formatted message string. If `fill` is True, the message will be wrapped - to fit the terminal width. - """ - if fill: - message = textwrap.fill(message) - return message diff --git a/bigframes/extensions/__init__.py b/bigframes/extensions/__init__.py deleted file mode 100644 index 58d482ea386..00000000000 --- a/bigframes/extensions/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -# Copyright 2026 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/bigframes/extensions/bigframes/__init__.py b/bigframes/extensions/bigframes/__init__.py deleted file mode 100644 index 439a8189ded..00000000000 --- a/bigframes/extensions/bigframes/__init__.py +++ /dev/null @@ -1,27 +0,0 @@ -# Copyright 2026 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 bigframes.extensions.bigframes.dataframe_accessor import ( - BigframesAIAccessor, - BigframesBigQueryDataFrameAccessor, -) -from bigframes.extensions.bigframes.series_accessor import ( - BigframesBigQuerySeriesAccessor, -) - -__all__ = [ - "BigframesAIAccessor", - "BigframesBigQueryDataFrameAccessor", - "BigframesBigQuerySeriesAccessor", -] diff --git a/bigframes/extensions/bigframes/dataframe_accessor.py b/bigframes/extensions/bigframes/dataframe_accessor.py deleted file mode 100644 index f706c19ef2d..00000000000 --- a/bigframes/extensions/bigframes/dataframe_accessor.py +++ /dev/null @@ -1,71 +0,0 @@ -# Copyright 2026 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 typing import TypeVar, cast - -import bigframes.dataframe -import bigframes.extensions.core.dataframe_accessor as core_accessor -import bigframes.series -from bigframes.core.logging import log_adapter - -T = TypeVar("T", bound="bigframes.dataframe.DataFrame") -S = TypeVar("S", bound="bigframes.series.Series") - - -@log_adapter.class_logger -class BigframesAIAccessor(core_accessor.AIAccessor[T, S]): - """ - BigFrames DataFrame accessor for BigQuery AI functions. - """ - - def __init__(self, bf_obj: T): - super().__init__(bf_obj) - - def _bf_from_dataframe( - self, session: bigframes.session.Session | None - ) -> bigframes.dataframe.DataFrame: - return self._obj - - def _to_dataframe(self, bf_df: bigframes.dataframe.DataFrame) -> T: - return cast(T, bf_df) - - def _to_series(self, bf_series: bigframes.series.Series) -> S: - return cast(S, bf_series) - - -@log_adapter.class_logger -class BigframesBigQueryDataFrameAccessor(core_accessor.BigQueryDataFrameAccessor[T, S]): - """ - BigFrames DataFrame accessor for BigQuery DataFrames functionality. - """ - - def __init__(self, bf_obj: T): - super().__init__(bf_obj) - - @property - def ai(self) -> BigframesAIAccessor: - return BigframesAIAccessor(self._obj) - - def _bf_from_dataframe( - self, session: bigframes.session.Session | None - ) -> bigframes.dataframe.DataFrame: - return self._obj - - def _to_dataframe(self, bf_df: bigframes.dataframe.DataFrame) -> T: - return cast(T, bf_df) - - def _to_series(self, bf_series: bigframes.series.Series) -> S: - return cast(S, bf_series) diff --git a/bigframes/extensions/bigframes/series_accessor.py b/bigframes/extensions/bigframes/series_accessor.py deleted file mode 100644 index c9026595d97..00000000000 --- a/bigframes/extensions/bigframes/series_accessor.py +++ /dev/null @@ -1,87 +0,0 @@ -# Copyright 2026 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. -# -# DO NOT MODIFY THIS FILE DIRECTLY. -# This file was generated by the script: scripts/generate_bigframes_bigquery.py -# - -from __future__ import annotations - -from typing import Optional, TypeVar, cast - -from bigframes import dataframe, series, session -from bigframes.core.logging import log_adapter -from bigframes.extensions.core import series_accessor as core_accessor - -T = TypeVar("T", bound="dataframe.DataFrame") -S = TypeVar("S", bound="series.Series") - - -@log_adapter.class_logger -class BigframesBigQuerySeriesAccessor(core_accessor.BigQuerySeriesAccessor[T, S]): - def __init__(self, bf_obj: S): - super().__init__(bf_obj) - - def _bf_from_series( - self, session: Optional[session.Session] = None - ) -> series.Series: - return self._obj - - def _to_dataframe(self, bf_df: dataframe.DataFrame) -> T: - return cast(T, bf_df) - - def _to_series(self, bf_series: series.Series) -> S: - return cast(S, bf_series) - - @property - def aead(self) -> BigframesAeadSeriesAccessor[T, S]: - return BigframesAeadSeriesAccessor(self._obj) - - @property - def ai(self) -> BigframesAiSeriesAccessor[T, S]: - return BigframesAiSeriesAccessor(self._obj) - - -@log_adapter.class_logger -class BigframesAeadSeriesAccessor(core_accessor.AeadSeriesAccessor[T, S]): - def __init__(self, bf_obj: S): - super().__init__(bf_obj) - - def _bf_from_series( - self, session: Optional[session.Session] = None - ) -> series.Series: - return self._obj - - def _to_dataframe(self, bf_df: dataframe.DataFrame) -> T: - return cast(T, bf_df) - - def _to_series(self, bf_series: series.Series) -> S: - return cast(S, bf_series) - - -@log_adapter.class_logger -class BigframesAiSeriesAccessor(core_accessor.AiSeriesAccessor[T, S]): - def __init__(self, bf_obj: S): - super().__init__(bf_obj) - - def _bf_from_series( - self, session: Optional[session.Session] = None - ) -> series.Series: - return self._obj - - def _to_dataframe(self, bf_df: dataframe.DataFrame) -> T: - return cast(T, bf_df) - - def _to_series(self, bf_series: series.Series) -> S: - return cast(S, bf_series) diff --git a/bigframes/extensions/core/__init__.py b/bigframes/extensions/core/__init__.py deleted file mode 100644 index 41b554c99ef..00000000000 --- a/bigframes/extensions/core/__init__.py +++ /dev/null @@ -1,20 +0,0 @@ -# Copyright 2026 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 bigframes.extensions.core.dataframe_accessor import ( - AIAccessor, - BigQueryDataFrameAccessor, -) - -__all__ = ["AIAccessor", "BigQueryDataFrameAccessor"] diff --git a/bigframes/extensions/core/abstract_series_accessor.py b/bigframes/extensions/core/abstract_series_accessor.py deleted file mode 100644 index 22d09861877..00000000000 --- a/bigframes/extensions/core/abstract_series_accessor.py +++ /dev/null @@ -1,50 +0,0 @@ -# Copyright 2026 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. -# -# DO NOT MODIFY THIS FILE DIRECTLY. -# This file was generated by the script: scripts/generate_bigframes_bigquery.py -# - -from __future__ import annotations - -import abc -from typing import ( - Generic, - Optional, - TypeVar, -) - -from bigframes import dataframe, series, session - -T = TypeVar("T") -S = TypeVar("S") - - -class AbstractBigQuerySeriesAccessor(abc.ABC, Generic[T, S]): - def __init__(self, obj: S): - self._obj = obj - - @abc.abstractmethod - def _bf_from_series( - self, session: Optional[session.Session] = None - ) -> series.Series: - """Convert the accessor's object to a BigFrames Series.""" - - @abc.abstractmethod - def _to_dataframe(self, bf_df: dataframe.DataFrame) -> T: - """Convert a BigFrames DataFrame to the accessor's object type.""" - - @abc.abstractmethod - def _to_series(self, bf_series: series.Series) -> S: - """Convert a BigFrames Series to the accessor's object type.""" diff --git a/bigframes/extensions/core/dataframe_accessor.py b/bigframes/extensions/core/dataframe_accessor.py deleted file mode 100644 index e490aa907dc..00000000000 --- a/bigframes/extensions/core/dataframe_accessor.py +++ /dev/null @@ -1,340 +0,0 @@ -# Copyright 2026 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 abc -from typing import ( - TYPE_CHECKING, - Any, - Generic, - Iterable, - List, - Literal, - Mapping, - Tuple, - TypeVar, - Union, -) - -if TYPE_CHECKING: - import pandas as pd - - import bigframes.dataframe - import bigframes.series - import bigframes.session - - PROMPT_TYPE = Union[ - str, - bigframes.series.Series, - pd.Series, - List[Union[str, bigframes.series.Series, pd.Series]], - Tuple[Union[str, bigframes.series.Series, pd.Series], ...], - ] -else: - PROMPT_TYPE = Any - -T = TypeVar("T") -S = TypeVar("S") - - -class AbstractBigQueryDataFrameAccessor(abc.ABC, Generic[T, S]): - @abc.abstractmethod - def _bf_from_dataframe( - self, session: bigframes.session.Session | None - ) -> bigframes.dataframe.DataFrame: - """Convert the accessor's object to a BigFrames DataFrame.""" - - @abc.abstractmethod - def _to_dataframe(self, bf_df: bigframes.dataframe.DataFrame) -> T: - """Convert a BigFrames DataFrame to the accessor's object type.""" - - @abc.abstractmethod - def _to_series(self, bf_series: bigframes.series.Series) -> S: - """Convert a BigFrames Series to the accessor's object type.""" - - -class AIAccessor(AbstractBigQueryDataFrameAccessor[T, S]): - """ - DataFrame accessor for BigQuery AI functions. - """ - - def __init__(self, obj: T): - self._obj = obj - - def forecast( - self, - *, - data_col: str, - timestamp_col: str, - model: str = "TimesFM 2.0", - id_cols: Iterable[str] | None = None, - horizon: int = 10, - confidence_level: float = 0.95, - context_window: int | None = None, - output_historical_time_series: bool = False, - session: bigframes.session.Session | None = None, - ) -> T: - """ - Forecast time series at future horizon using BigQuery AI.FORECAST. - - This is an accessor for :func:`bigframes.bigquery.ai.forecast`. See that - function's documentation for detailed parameter descriptions and examples. - """ - import bigframes.bigquery.ai - - bf_df = self._bf_from_dataframe(session) - result = bigframes.bigquery.ai.forecast( - bf_df, - data_col=data_col, - timestamp_col=timestamp_col, - model=model, - id_cols=id_cols, - horizon=horizon, - confidence_level=confidence_level, - context_window=context_window, - output_historical_time_series=output_historical_time_series, - ) - return self._to_dataframe(result) - - def generate( - self, - prompt: PROMPT_TYPE, - *, - connection_id: str | None = None, - endpoint: str | None = None, - request_type: Literal["dedicated", "shared", "unspecified"] | None = None, - model_params: Mapping[Any, Any] | None = None, - output_schema: Mapping[str, str] | None = None, - ) -> S: - """ - Returns the AI analysis based on the prompt, which can be any combination of text and unstructured data. - - This is an accessor for :func:`bigframes.bigquery.ai.generate`. See that - function's documentation for detailed parameter descriptions and examples. - """ - import bigframes.bigquery.ai - - result = bigframes.bigquery.ai.generate( - prompt, - connection_id=connection_id, - endpoint=endpoint, - request_type=request_type, - model_params=model_params, - output_schema=output_schema, - ) - return self._to_series(result) - - def generate_bool( - self, - prompt: PROMPT_TYPE, - *, - connection_id: str | None = None, - endpoint: str | None = None, - request_type: Literal["dedicated", "shared", "unspecified"] | None = None, - model_params: Mapping[Any, Any] | None = None, - ) -> S: - """ - Returns the AI analysis based on the prompt, which can be any combination of text and unstructured data. - - This is an accessor for :func:`bigframes.bigquery.ai.generate_bool`. See that - function's documentation for detailed parameter descriptions and examples. - """ - import bigframes.bigquery.ai - - result = bigframes.bigquery.ai.generate_bool( - prompt, - connection_id=connection_id, - endpoint=endpoint, - request_type=request_type, - model_params=model_params, - ) - return self._to_series(result) - - def generate_int( - self, - prompt: PROMPT_TYPE, - *, - connection_id: str | None = None, - endpoint: str | None = None, - request_type: Literal["dedicated", "shared", "unspecified"] | None = None, - model_params: Mapping[Any, Any] | None = None, - ) -> S: - """ - Returns the AI analysis based on the prompt, which can be any combination of text and unstructured data. - - This is an accessor for :func:`bigframes.bigquery.ai.generate_int`. See that - function's documentation for detailed parameter descriptions and examples. - """ - import bigframes.bigquery.ai - - result = bigframes.bigquery.ai.generate_int( - prompt, - connection_id=connection_id, - endpoint=endpoint, - request_type=request_type, - model_params=model_params, - ) - return self._to_series(result) - - def generate_double( - self, - prompt: PROMPT_TYPE, - *, - connection_id: str | None = None, - endpoint: str | None = None, - request_type: Literal["dedicated", "shared", "unspecified"] | None = None, - model_params: Mapping[Any, Any] | None = None, - ) -> S: - """ - Returns the AI analysis based on the prompt, which can be any combination of text and unstructured data. - - This is an accessor for :func:`bigframes.bigquery.ai.generate_double`. See that - function's documentation for detailed parameter descriptions and examples. - """ - import bigframes.bigquery.ai - - result = bigframes.bigquery.ai.generate_double( - prompt, - connection_id=connection_id, - endpoint=endpoint, - request_type=request_type, - model_params=model_params, - ) - return self._to_series(result) - - def classify( - self, - input: PROMPT_TYPE, - categories: tuple[str, ...] | list[str], - *, - examples: list[tuple[str, str]] - | list[tuple[str, list[str] | tuple[str, ...]]] - | None = None, - connection_id: str | None = None, - endpoint: str | None = None, - output_mode: Literal["single", "multi"] | None = None, - optimization_mode: Literal["minimize_cost", "maximize_quality"] | None = None, - max_error_ratio: float | None = None, - ) -> S: - """ - Classifies a given input into one of the specified categories. It will always return one of the provided categories best fit the prompt input. - - This is an accessor for :func:`bigframes.bigquery.ai.classify`. See that - function's documentation for detailed parameter descriptions and examples. - """ - import bigframes.bigquery.ai - - result = bigframes.bigquery.ai.classify( - input, - categories, - examples=examples, - connection_id=connection_id, - endpoint=endpoint, - output_mode=output_mode, - optimization_mode=optimization_mode, - max_error_ratio=max_error_ratio, - ) - return self._to_series(result) - - def if_( - self, - prompt: PROMPT_TYPE, - *, - connection_id: str | None = None, - endpoint: str | None = None, - optimization_mode: Literal["minimize_cost", "maximize_quality"] | None = None, - max_error_ratio: float | None = None, - ) -> S: - """ - Evaluates the prompt to True or False. Compared to ``ai.generate_bool()``, this function - provides optimization such that not all rows are evaluated with the LLM. - - This is an accessor for :func:`bigframes.bigquery.ai.if_`. See that - function's documentation for detailed parameter descriptions and examples. - """ - import bigframes.bigquery.ai - - result = bigframes.bigquery.ai.if_( - prompt, - connection_id=connection_id, - endpoint=endpoint, - optimization_mode=optimization_mode, - max_error_ratio=max_error_ratio, - ) - return self._to_series(result) - - def score( - self, - prompt: PROMPT_TYPE, - *, - connection_id: str | None = None, - endpoint: str | None = None, - max_error_ratio: float | None = None, - ) -> S: - """ - Computes a score based on rubrics described in natural language. It will return a double value. - - This is an accessor for :func:`bigframes.bigquery.ai.score`. See that - function's documentation for detailed parameter descriptions and examples. - """ - import bigframes.bigquery.ai - - result = bigframes.bigquery.ai.score( - prompt, - connection_id=connection_id, - endpoint=endpoint, - max_error_ratio=max_error_ratio, - ) - return self._to_series(result) - - -class BigQueryDataFrameAccessor(AbstractBigQueryDataFrameAccessor[T, S]): - """ - DataFrame accessor for BigQuery DataFrames functionality. - """ - - def __init__(self, obj: T): - self._obj = obj - - @property - @abc.abstractmethod - def ai(self) -> AIAccessor: - """ - Accessor for BigQuery AI functions. - - Returns: - AIAccessor: Accessor for BigQuery AI functions. - """ - - def sql_scalar( - self, - sql_template: str, - *, - output_dtype=None, - session: bigframes.session.Session | None = None, - ) -> S: - """ - Compute a new Series by applying a SQL scalar function to the DataFrame. - - This is an accessor for :func:`bigframes.bigquery.sql_scalar`. See that - function's documentation for detailed parameter descriptions and examples. - """ - import bigframes.bigquery - - bf_df = self._bf_from_dataframe(session) - result = bigframes.bigquery.sql_scalar( - sql_template, bf_df, output_dtype=output_dtype - ) - return self._to_series(result) diff --git a/bigframes/extensions/core/series_accessor.py b/bigframes/extensions/core/series_accessor.py deleted file mode 100644 index 5aa50905c8f..00000000000 --- a/bigframes/extensions/core/series_accessor.py +++ /dev/null @@ -1,1229 +0,0 @@ -# Copyright 2026 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. -# -# DO NOT MODIFY THIS FILE DIRECTLY. -# This file was generated by the script: scripts/generate_bigframes_bigquery.py -# - -from __future__ import annotations - -import abc -import datetime -from typing import ( - Any, - Literal, - Optional, - TypeVar, - Union, - cast, -) - -from bigframes import series, session -from bigframes.core import col, sentinels -from bigframes.extensions.core import abstract_series_accessor, series_mixins - -T = TypeVar("T") -S = TypeVar("S") - - -class BigQuerySeriesAccessor( - abstract_series_accessor.AbstractBigQuerySeriesAccessor[T, S] -): - """Series accessor for BigQuery functions.""" - - @property - @abc.abstractmethod - def aead(self) -> AeadSeriesAccessor[T, S]: - """Accessor for BigQuery aead functions.""" - - @property - @abc.abstractmethod - def ai(self) -> AiSeriesAccessor[T, S]: - """Accessor for BigQuery ai functions.""" - - def deterministic_decrypt_bytes( - self, - ciphertext: Union[ - series.Series, - col.Expression, - Union[Literal[sentinels.Sentinel.ARGUMENT_DEFAULT], bytes], - ], - additional_data: Union[ - series.Series, - col.Expression, - Union[Literal[sentinels.Sentinel.ARGUMENT_DEFAULT], bytes], - ], - *, - session: Optional[session.Session] = None, - ) -> S: - """Uses the matching key from `keyset` to decrypt `ciphertext` and verifies the integrity of the data using `additional_data`. Returns an error if decryption fails.""" - from bigframes.operations.googlesql.global_namespace.aead_encryption import ( - deterministic_decrypt_bytes as deterministic_decrypt_bytes_impl, - ) - - # Resolve session from other arguments if not passed - if session is None: - from bigframes.core import googlesql - - session = googlesql._find_session( - ciphertext, - additional_data, - ) - - bf_series = self._bf_from_series(session) - result = deterministic_decrypt_bytes_impl( - bf_series, - ciphertext, - additional_data, - ) - return self._to_series(cast(series.Series, result)) - - def deterministic_decrypt_string( - self, - ciphertext: Union[ - series.Series, - col.Expression, - Union[Literal[sentinels.Sentinel.ARGUMENT_DEFAULT], bytes], - ], - additional_data: Union[ - series.Series, - col.Expression, - Union[Literal[sentinels.Sentinel.ARGUMENT_DEFAULT], str], - ], - *, - session: Optional[session.Session] = None, - ) -> S: - """Like `DETERMINISTIC_DECRYPT_BYTES`, but where plaintext is of type STRING.""" - from bigframes.operations.googlesql.global_namespace.aead_encryption import ( - deterministic_decrypt_string as deterministic_decrypt_string_impl, - ) - - # Resolve session from other arguments if not passed - if session is None: - from bigframes.core import googlesql - - session = googlesql._find_session( - ciphertext, - additional_data, - ) - - bf_series = self._bf_from_series(session) - result = deterministic_decrypt_string_impl( - bf_series, - ciphertext, - additional_data, - ) - return self._to_series(cast(series.Series, result)) - - def deterministic_encrypt( - self, - plaintext: Union[ - series.Series, - col.Expression, - Union[Literal[sentinels.Sentinel.ARGUMENT_DEFAULT], bytes, str], - ], - additional_data: Union[ - series.Series, - col.Expression, - Union[Literal[sentinels.Sentinel.ARGUMENT_DEFAULT], bytes, str], - ], - *, - session: Optional[session.Session] = None, - ) -> S: - """Encrypts `plaintext` using the primary cryptographic key in `keyset` using deterministic AEAD. The algorithm of the primary key must be `DETERMINISTIC_AEAD_AES_SIV_CMAC_256`. Binds the ciphertext to the context defined by `additional_data`. Returns `NULL` if any input is `NULL`.""" - from bigframes.operations.googlesql.global_namespace.aead_encryption import ( - deterministic_encrypt as deterministic_encrypt_impl, - ) - - # Resolve session from other arguments if not passed - if session is None: - from bigframes.core import googlesql - - session = googlesql._find_session( - plaintext, - additional_data, - ) - - bf_series = self._bf_from_series(session) - result = deterministic_encrypt_impl( - bf_series, - plaintext, - additional_data, - ) - return self._to_series(cast(series.Series, result)) - - def array_concat( - self, - array_expression_2: Union[ - series.Series, - col.Expression, - Union[Any, Literal[sentinels.Sentinel.ARGUMENT_DEFAULT]], - ], - *, - session: Optional[session.Session] = None, - ) -> S: - """Concatenates one or more arrays with the same element type into a single array.""" - from bigframes.operations.googlesql.global_namespace.array import ( - array_concat as array_concat_impl, - ) - - # Resolve session from other arguments if not passed - if session is None: - from bigframes.core import googlesql - - session = googlesql._find_session( - array_expression_2, - ) - - bf_series = self._bf_from_series(session) - result = array_concat_impl( - bf_series, - array_expression_2, - ) - return self._to_series(cast(series.Series, result)) - - def array_first( - self, - *, - session: Optional[session.Session] = None, - ) -> S: - """Takes an array and returns the first element in the array.""" - from bigframes.operations.googlesql.global_namespace.array import ( - array_first as array_first_impl, - ) - - bf_series = self._bf_from_series(session) - result = array_first_impl( - bf_series, - ) - return self._to_series(cast(series.Series, result)) - - def array_first_n( - self, - n: Union[ - series.Series, - col.Expression, - Union[Literal[sentinels.Sentinel.ARGUMENT_DEFAULT], int], - ], - *, - session: Optional[session.Session] = None, - ) -> S: - """Returns a prefix of `input_array` consisting of the first `n` elements.""" - from bigframes.operations.googlesql.global_namespace.array import ( - array_first_n as array_first_n_impl, - ) - - # Resolve session from other arguments if not passed - if session is None: - from bigframes.core import googlesql - - session = googlesql._find_session( - n, - ) - - bf_series = self._bf_from_series(session) - result = array_first_n_impl( - bf_series, - n, - ) - return self._to_series(cast(series.Series, result)) - - def array_includes( - self, - search_value: Union[ - series.Series, - col.Expression, - Union[Any, Literal[sentinels.Sentinel.ARGUMENT_DEFAULT]], - ], - *, - session: Optional[session.Session] = None, - ) -> S: - """Takes an array and returns `TRUE` if there is an element in the array that is equal to the search_value.""" - from bigframes.operations.googlesql.global_namespace.array import ( - array_includes as array_includes_impl, - ) - - # Resolve session from other arguments if not passed - if session is None: - from bigframes.core import googlesql - - session = googlesql._find_session( - search_value, - ) - - bf_series = self._bf_from_series(session) - result = array_includes_impl( - bf_series, - search_value, - ) - return self._to_series(cast(series.Series, result)) - - def array_includes_all( - self, - search_values: Union[ - series.Series, - col.Expression, - Union[Any, Literal[sentinels.Sentinel.ARGUMENT_DEFAULT]], - ], - *, - session: Optional[session.Session] = None, - ) -> S: - """Takes an array to search and an array of search values. Returns `TRUE` if all search values are in the array to search, otherwise returns `FALSE`.""" - from bigframes.operations.googlesql.global_namespace.array import ( - array_includes_all as array_includes_all_impl, - ) - - # Resolve session from other arguments if not passed - if session is None: - from bigframes.core import googlesql - - session = googlesql._find_session( - search_values, - ) - - bf_series = self._bf_from_series(session) - result = array_includes_all_impl( - bf_series, - search_values, - ) - return self._to_series(cast(series.Series, result)) - - def array_includes_any( - self, - search_values: Union[ - series.Series, - col.Expression, - Union[Any, Literal[sentinels.Sentinel.ARGUMENT_DEFAULT]], - ], - *, - session: Optional[session.Session] = None, - ) -> S: - """Takes an array to search and an array of search values. Returns `TRUE` if any search values are in the array to search, otherwise returns `FALSE`.""" - from bigframes.operations.googlesql.global_namespace.array import ( - array_includes_any as array_includes_any_impl, - ) - - # Resolve session from other arguments if not passed - if session is None: - from bigframes.core import googlesql - - session = googlesql._find_session( - search_values, - ) - - bf_series = self._bf_from_series(session) - result = array_includes_any_impl( - bf_series, - search_values, - ) - return self._to_series(cast(series.Series, result)) - - def array_is_distinct( - self, - *, - session: Optional[session.Session] = None, - ) -> S: - """Returns `TRUE` if the array contains no repeated elements, using the same equality comparison logic as `SELECT DISTINCT`.""" - from bigframes.operations.googlesql.global_namespace.array import ( - array_is_distinct as array_is_distinct_impl, - ) - - bf_series = self._bf_from_series(session) - result = array_is_distinct_impl( - bf_series, - ) - return self._to_series(cast(series.Series, result)) - - def array_last( - self, - *, - session: Optional[session.Session] = None, - ) -> S: - """Takes an array and returns the last element in the array.""" - from bigframes.operations.googlesql.global_namespace.array import ( - array_last as array_last_impl, - ) - - bf_series = self._bf_from_series(session) - result = array_last_impl( - bf_series, - ) - return self._to_series(cast(series.Series, result)) - - def array_length( - self, - *, - session: Optional[session.Session] = None, - ) -> S: - """Compute the length of each array element in the Series. - - **Examples:** - - >>> import bigframes.pandas as bpd - >>> import bigframes.bigquery as bbq - - >>> s = bpd.Series([[1, 2, 8, 3], [], [3, 4]]) - >>> bbq.array_length(s) - 0 4 - 1 0 - 2 2 - dtype: Int64 - - You can call this function using the Series `bigquery` accessor. - - >>> s.bigquery.array_length() - 0 4 - 1 0 - 2 2 - dtype: Int64 - - You can also use this accessor on a pandas Series after importing bigframes. - - >>> import bigframes - >>> import pandas as pd - >>> ps = pd.Series([[1, 2, 8, 3], [], [3, 4]]) - >>> ps.bigquery.array_length() - 0 4 - 1 0 - 2 2 - dtype: Int64 - - You can also apply this function directly to Series using `apply`. - - >>> s.apply(bbq.array_length, by_row=False) - 0 4 - 1 0 - 2 2 - dtype: Int64 - - Args: - series (bigframes.series.Series): A Series with array columns. - - Returns: - bigframes.series.Series: A Series of integer values indicating - the length of each element in the Series. - """ - from bigframes.operations.googlesql.global_namespace.array import ( - array_length as array_length_impl, - ) - - bf_series = self._bf_from_series(session) - result = array_length_impl( - bf_series, - ) - return self._to_series(cast(series.Series, result)) - - def array_reverse( - self, - *, - session: Optional[session.Session] = None, - ) -> S: - """Returns the input `ARRAY` with elements in reverse order.""" - from bigframes.operations.googlesql.global_namespace.array import ( - array_reverse as array_reverse_impl, - ) - - bf_series = self._bf_from_series(session) - result = array_reverse_impl( - bf_series, - ) - return self._to_series(cast(series.Series, result)) - - def array_slice( - self, - start_offset: Union[ - series.Series, - col.Expression, - Union[Literal[sentinels.Sentinel.ARGUMENT_DEFAULT], int], - ], - end_offset: Union[ - series.Series, - col.Expression, - Union[Literal[sentinels.Sentinel.ARGUMENT_DEFAULT], int], - ], - *, - session: Optional[session.Session] = None, - ) -> S: - """Returns an array containing zero or more consecutive elements from the input array.""" - from bigframes.operations.googlesql.global_namespace.array import ( - array_slice as array_slice_impl, - ) - - # Resolve session from other arguments if not passed - if session is None: - from bigframes.core import googlesql - - session = googlesql._find_session( - start_offset, - end_offset, - ) - - bf_series = self._bf_from_series(session) - result = array_slice_impl( - bf_series, - start_offset, - end_offset, - ) - return self._to_series(cast(series.Series, result)) - - def array_to_string( - self, - delimiter: Union[ - series.Series, - col.Expression, - Union[Literal[sentinels.Sentinel.ARGUMENT_DEFAULT], bytes, str], - ], - null_text: Union[ - series.Series, - col.Expression, - Union[Literal[sentinels.Sentinel.ARGUMENT_DEFAULT], bytes, str], - ] = sentinels.Sentinel.ARGUMENT_DEFAULT, - *, - session: Optional[session.Session] = None, - ) -> S: - """Converts array elements within a Series into delimited strings. - - **Examples:** - - >>> import bigframes.pandas as bpd - >>> import bigframes.bigquery as bbq - - >>> s = bpd.Series([["H", "i", "!"], ["Hello", "World"], np.nan, [], ["Hi"]]) - >>> bbq.array_to_string(s, delimiter=", ") - 0 H, i, ! - 1 Hello, World - 2 - 3 - 4 Hi - dtype: string - - You can call this function using the Series `bigquery` accessor. - - >>> s.bigquery.array_to_string(delimiter=", ") - 0 H, i, ! - 1 Hello, World - 2 - 3 - 4 Hi - dtype: string - - You can also use this accessor on a pandas Series after importing bigframes. - - >>> import bigframes - >>> import pandas as pd - >>> ps = pd.Series([["H", "i", "!"], ["Hello", "World"], None, [], ["Hi"]]) - >>> ps.bigquery.array_to_string(delimiter=", ") - 0 H, i, ! - 1 Hello, World - 2 - 3 - 4 Hi - dtype: string - - Args: - series (bigframes.series.Series): A Series containing arrays. - delimiter (str): The string used to separate array elements. - null_text (str, optional): The string to replace any NULL values in the array with. - - Returns: - bigframes.series.Series: A Series containing delimited strings. - """ - from bigframes.operations.googlesql.global_namespace.array import ( - array_to_string as array_to_string_impl, - ) - - # Resolve session from other arguments if not passed - if session is None: - from bigframes.core import googlesql - - session = googlesql._find_session( - delimiter, - null_text, - ) - - bf_series = self._bf_from_series(session) - result = array_to_string_impl( - bf_series, - delimiter, - null_text, - ) - return self._to_series(cast(series.Series, result)) - - def flatten( - self, - depth: Union[ - series.Series, - col.Expression, - Union[Literal[sentinels.Sentinel.ARGUMENT_DEFAULT], int], - ] = sentinels.Sentinel.ARGUMENT_DEFAULT, - *, - session: Optional[session.Session] = None, - ) -> S: - """Takes an array of nested data and flattens a specific part of it into a single, flat array with the [array elements field access operator][array-el-field-operator]. Returns `NULL` if the input value is `NULL`.""" - from bigframes.operations.googlesql.global_namespace.array import ( - flatten as flatten_impl, - ) - - # Resolve session from other arguments if not passed - if session is None: - from bigframes.core import googlesql - - session = googlesql._find_session( - depth, - ) - - bf_series = self._bf_from_series(session) - result = flatten_impl( - bf_series, - depth, - ) - return self._to_series(cast(series.Series, result)) - - def bit_count( - self, - *, - session: Optional[session.Session] = None, - ) -> S: - """The input, `expression`, must be an integer or `BYTES`. Returns the number of bits that are set in the input expression. For signed integers, this is the number of bits in two's complement form.""" - from bigframes.operations.googlesql.global_namespace.bit import ( - bit_count as bit_count_impl, - ) - - bf_series = self._bf_from_series(session) - result = bit_count_impl( - bf_series, - ) - return self._to_series(cast(series.Series, result)) - - def bool_( - self, - *, - session: Optional[session.Session] = None, - ) -> S: - """Converts a JSON boolean to a SQL BOOL value.""" - from bigframes.operations.googlesql.global_namespace.conversion import ( - bool_ as bool__impl, - ) - - bf_series = self._bf_from_series(session) - result = bool__impl( - bf_series, - ) - return self._to_series(cast(series.Series, result)) - - def double( - self, - wide_number_mode: Union[ - series.Series, - col.Expression, - Union[Literal[sentinels.Sentinel.ARGUMENT_DEFAULT], str], - ] = sentinels.Sentinel.ARGUMENT_DEFAULT, - *, - session: Optional[session.Session] = None, - ) -> S: - """Converts a JSON number to a SQL FLOAT64 value.""" - from bigframes.operations.googlesql.global_namespace.conversion import ( - double as double_impl, - ) - - # Resolve session from other arguments if not passed - if session is None: - from bigframes.core import googlesql - - session = googlesql._find_session( - wide_number_mode, - ) - - bf_series = self._bf_from_series(session) - result = double_impl( - bf_series, - wide_number_mode, - ) - return self._to_series(cast(series.Series, result)) - - def float64( - self, - wide_number_mode: Union[ - series.Series, - col.Expression, - Union[Literal[sentinels.Sentinel.ARGUMENT_DEFAULT], str], - ] = sentinels.Sentinel.ARGUMENT_DEFAULT, - *, - session: Optional[session.Session] = None, - ) -> S: - """Converts a JSON number to a SQL FLOAT64 value.""" - from bigframes.operations.googlesql.global_namespace.conversion import ( - float64 as float64_impl, - ) - - # Resolve session from other arguments if not passed - if session is None: - from bigframes.core import googlesql - - session = googlesql._find_session( - wide_number_mode, - ) - - bf_series = self._bf_from_series(session) - result = float64_impl( - bf_series, - wide_number_mode, - ) - return self._to_series(cast(series.Series, result)) - - def int64( - self, - *, - session: Optional[session.Session] = None, - ) -> S: - """Converts a JSON number to a SQL INT64 value.""" - from bigframes.operations.googlesql.global_namespace.conversion import ( - int64 as int64_impl, - ) - - bf_series = self._bf_from_series(session) - result = int64_impl( - bf_series, - ) - return self._to_series(cast(series.Series, result)) - - def parse_bignumeric( - self, - *, - session: Optional[session.Session] = None, - ) -> S: - """Converts a STRING to a BIGNUMERIC value.""" - from bigframes.operations.googlesql.global_namespace.conversion import ( - parse_bignumeric as parse_bignumeric_impl, - ) - - bf_series = self._bf_from_series(session) - result = parse_bignumeric_impl( - bf_series, - ) - return self._to_series(cast(series.Series, result)) - - def parse_numeric( - self, - *, - session: Optional[session.Session] = None, - ) -> S: - """Converts a STRING to a NUMERIC value.""" - from bigframes.operations.googlesql.global_namespace.conversion import ( - parse_numeric as parse_numeric_impl, - ) - - bf_series = self._bf_from_series(session) - result = parse_numeric_impl( - bf_series, - ) - return self._to_series(cast(series.Series, result)) - - def string( - self, - timezone: Union[ - series.Series, - col.Expression, - Union[Literal[sentinels.Sentinel.ARGUMENT_DEFAULT], str], - ] = sentinels.Sentinel.ARGUMENT_DEFAULT, - *, - session: Optional[session.Session] = None, - ) -> S: - """Converts a value to a STRING value.""" - from bigframes.operations.googlesql.global_namespace.conversion import ( - string as string_impl, - ) - - # Resolve session from other arguments if not passed - if session is None: - from bigframes.core import googlesql - - session = googlesql._find_session( - timezone, - ) - - bf_series = self._bf_from_series(session) - result = string_impl( - bf_series, - timezone, - ) - return self._to_series(cast(series.Series, result)) - - def date( - self, - time_zone_expression: Union[ - series.Series, - col.Expression, - Union[Literal[sentinels.Sentinel.ARGUMENT_DEFAULT], str], - ] = sentinels.Sentinel.ARGUMENT_DEFAULT, - year: Union[ - series.Series, - col.Expression, - Union[Literal[sentinels.Sentinel.ARGUMENT_DEFAULT], int], - ] = sentinels.Sentinel.ARGUMENT_DEFAULT, - month: Union[ - series.Series, - col.Expression, - Union[Literal[sentinels.Sentinel.ARGUMENT_DEFAULT], int], - ] = sentinels.Sentinel.ARGUMENT_DEFAULT, - day: Union[ - series.Series, - col.Expression, - Union[Literal[sentinels.Sentinel.ARGUMENT_DEFAULT], int], - ] = sentinels.Sentinel.ARGUMENT_DEFAULT, - *, - session: Optional[session.Session] = None, - ) -> S: - """Constructs or extracts a date.""" - from bigframes.operations.googlesql.global_namespace.date import ( - date as date_impl, - ) - - # Resolve session from other arguments if not passed - if session is None: - from bigframes.core import googlesql - - session = googlesql._find_session( - time_zone_expression, - year, - month, - day, - ) - - bf_series = self._bf_from_series(session) - result = date_impl( - bf_series, - time_zone_expression, - year, - month, - day, - ) - return self._to_series(cast(series.Series, result)) - - def date_add( - self, - int64_expression: Union[ - series.Series, - col.Expression, - Union[Literal[sentinels.Sentinel.ARGUMENT_DEFAULT], int], - ], - date_part: Union[ - series.Series, - col.Expression, - Union[Any, Literal[sentinels.Sentinel.ARGUMENT_DEFAULT]], - ], - *, - session: Optional[session.Session] = None, - ) -> S: - """Adds a specified time interval to a DATE.""" - from bigframes.operations.googlesql.global_namespace.date import ( - date_add as date_add_impl, - ) - - # Resolve session from other arguments if not passed - if session is None: - from bigframes.core import googlesql - - session = googlesql._find_session( - int64_expression, - date_part, - ) - - bf_series = self._bf_from_series(session) - result = date_add_impl( - bf_series, - int64_expression, - date_part, - ) - return self._to_series(cast(series.Series, result)) - - def date_diff( - self, - start_date: Union[ - series.Series, - col.Expression, - Union[Literal[sentinels.Sentinel.ARGUMENT_DEFAULT], datetime.date], - ], - granularity: Union[ - series.Series, - col.Expression, - Union[Any, Literal[sentinels.Sentinel.ARGUMENT_DEFAULT]], - ], - *, - session: Optional[session.Session] = None, - ) -> S: - """Gets the number of unit boundaries between two DATE values (end_date - start_date) at a particular time granularity.""" - from bigframes.operations.googlesql.global_namespace.date import ( - date_diff as date_diff_impl, - ) - - # Resolve session from other arguments if not passed - if session is None: - from bigframes.core import googlesql - - session = googlesql._find_session( - start_date, - granularity, - ) - - bf_series = self._bf_from_series(session) - result = date_diff_impl( - bf_series, - start_date, - granularity, - ) - return self._to_series(cast(series.Series, result)) - - def date_from_unix_date( - self, - *, - session: Optional[session.Session] = None, - ) -> S: - """Interprets an INT64 expression as the number of days since 1970-01-01.""" - from bigframes.operations.googlesql.global_namespace.date import ( - date_from_unix_date as date_from_unix_date_impl, - ) - - bf_series = self._bf_from_series(session) - result = date_from_unix_date_impl( - bf_series, - ) - return self._to_series(cast(series.Series, result)) - - def date_sub( - self, - int64_expression: Union[ - series.Series, - col.Expression, - Union[Literal[sentinels.Sentinel.ARGUMENT_DEFAULT], int], - ], - date_part: Union[ - series.Series, - col.Expression, - Union[Any, Literal[sentinels.Sentinel.ARGUMENT_DEFAULT]], - ], - *, - session: Optional[session.Session] = None, - ) -> S: - """Subtracts a specified time interval from a DATE.""" - from bigframes.operations.googlesql.global_namespace.date import ( - date_sub as date_sub_impl, - ) - - # Resolve session from other arguments if not passed - if session is None: - from bigframes.core import googlesql - - session = googlesql._find_session( - int64_expression, - date_part, - ) - - bf_series = self._bf_from_series(session) - result = date_sub_impl( - bf_series, - int64_expression, - date_part, - ) - return self._to_series(cast(series.Series, result)) - - def date_trunc( - self, - granularity: Union[ - series.Series, - col.Expression, - Union[Any, Literal[sentinels.Sentinel.ARGUMENT_DEFAULT]], - ], - *, - session: Optional[session.Session] = None, - ) -> S: - """Truncates a DATE, DATETIME, or TIMESTAMP value at a particular granularity.""" - from bigframes.operations.googlesql.global_namespace.date import ( - date_trunc as date_trunc_impl, - ) - - # Resolve session from other arguments if not passed - if session is None: - from bigframes.core import googlesql - - session = googlesql._find_session( - granularity, - ) - - bf_series = self._bf_from_series(session) - result = date_trunc_impl( - bf_series, - granularity, - ) - return self._to_series(cast(series.Series, result)) - - def extract( - self, - part: Union[ - series.Series, - col.Expression, - Union[Any, Literal[sentinels.Sentinel.ARGUMENT_DEFAULT]], - ], - time_zone: Union[ - series.Series, - col.Expression, - Union[Literal[sentinels.Sentinel.ARGUMENT_DEFAULT], str], - ] = sentinels.Sentinel.ARGUMENT_DEFAULT, - *, - session: Optional[session.Session] = None, - ) -> S: - """Returns the value corresponding to the specified date part.""" - from bigframes.operations.googlesql.global_namespace.date import ( - extract as extract_impl, - ) - - # Resolve session from other arguments if not passed - if session is None: - from bigframes.core import googlesql - - session = googlesql._find_session( - part, - time_zone, - ) - - bf_series = self._bf_from_series(session) - result = extract_impl( - bf_series, - part, - time_zone, - ) - return self._to_series(cast(series.Series, result)) - - def format_date( - self, - format_string: Union[ - series.Series, - col.Expression, - Union[Literal[sentinels.Sentinel.ARGUMENT_DEFAULT], str], - ], - *, - session: Optional[session.Session] = None, - ) -> S: - """Formats a DATE value according to a specified format string.""" - from bigframes.operations.googlesql.global_namespace.date import ( - format_date as format_date_impl, - ) - - # Resolve session from other arguments if not passed - if session is None: - from bigframes.core import googlesql - - session = googlesql._find_session( - format_string, - ) - - bf_series = self._bf_from_series(session) - result = format_date_impl( - format_string, - bf_series, - ) - return self._to_series(cast(series.Series, result)) - - def last_day( - self, - date_part: Union[ - series.Series, - col.Expression, - Union[Any, Literal[sentinels.Sentinel.ARGUMENT_DEFAULT]], - ] = sentinels.Sentinel.ARGUMENT_DEFAULT, - *, - session: Optional[session.Session] = None, - ) -> S: - """Returns the last day from a date expression. This is commonly used to return the last day of the month.""" - from bigframes.operations.googlesql.global_namespace.date import ( - last_day as last_day_impl, - ) - - # Resolve session from other arguments if not passed - if session is None: - from bigframes.core import googlesql - - session = googlesql._find_session( - date_part, - ) - - bf_series = self._bf_from_series(session) - result = last_day_impl( - bf_series, - date_part, - ) - return self._to_series(cast(series.Series, result)) - - def parse_date( - self, - format_string: Union[ - series.Series, - col.Expression, - Union[Literal[sentinels.Sentinel.ARGUMENT_DEFAULT], str], - ], - *, - session: Optional[session.Session] = None, - ) -> S: - """Converts a STRING value to a DATE value.""" - from bigframes.operations.googlesql.global_namespace.date import ( - parse_date as parse_date_impl, - ) - - # Resolve session from other arguments if not passed - if session is None: - from bigframes.core import googlesql - - session = googlesql._find_session( - format_string, - ) - - bf_series = self._bf_from_series(session) - result = parse_date_impl( - format_string, - bf_series, - ) - return self._to_series(cast(series.Series, result)) - - def unix_date( - self, - *, - session: Optional[session.Session] = None, - ) -> S: - """Returns the number of days since 1970-01-01.""" - from bigframes.operations.googlesql.global_namespace.date import ( - unix_date as unix_date_impl, - ) - - bf_series = self._bf_from_series(session) - result = unix_date_impl( - bf_series, - ) - return self._to_series(cast(series.Series, result)) - - -class AeadSeriesAccessor(abstract_series_accessor.AbstractBigQuerySeriesAccessor[T, S]): - """Series accessor for BigQuery aead functions.""" - - def decrypt_bytes( - self, - ciphertext: Union[ - series.Series, - col.Expression, - Union[Literal[sentinels.Sentinel.ARGUMENT_DEFAULT], bytes], - ], - additional_data: Union[ - series.Series, - col.Expression, - Union[Literal[sentinels.Sentinel.ARGUMENT_DEFAULT], bytes], - ], - *, - session: Optional[session.Session] = None, - ) -> S: - """Uses the matching key from keyset to decrypt ciphertext and verifies the integrity of the data using additional_data. Returns an error if decryption or verification fails.""" - from bigframes.operations.googlesql.aead import ( - decrypt_bytes as decrypt_bytes_impl, - ) - - # Resolve session from other arguments if not passed - if session is None: - from bigframes.core import googlesql - - session = googlesql._find_session( - ciphertext, - additional_data, - ) - - bf_series = self._bf_from_series(session) - result = decrypt_bytes_impl( - bf_series, - ciphertext, - additional_data, - ) - return self._to_series(cast(series.Series, result)) - - def decrypt_string( - self, - ciphertext: Union[ - series.Series, - col.Expression, - Union[Literal[sentinels.Sentinel.ARGUMENT_DEFAULT], bytes], - ], - additional_data: Union[ - series.Series, - col.Expression, - Union[Literal[sentinels.Sentinel.ARGUMENT_DEFAULT], str], - ], - *, - session: Optional[session.Session] = None, - ) -> S: - """Like AEAD.DECRYPT_BYTES, but where additional_data is of type STRING.""" - from bigframes.operations.googlesql.aead import ( - decrypt_string as decrypt_string_impl, - ) - - # Resolve session from other arguments if not passed - if session is None: - from bigframes.core import googlesql - - session = googlesql._find_session( - ciphertext, - additional_data, - ) - - bf_series = self._bf_from_series(session) - result = decrypt_string_impl( - bf_series, - ciphertext, - additional_data, - ) - return self._to_series(cast(series.Series, result)) - - def encrypt( - self, - plaintext: Union[ - series.Series, - col.Expression, - Union[Literal[sentinels.Sentinel.ARGUMENT_DEFAULT], bytes, str], - ], - additional_data: Union[ - series.Series, - col.Expression, - Union[Literal[sentinels.Sentinel.ARGUMENT_DEFAULT], bytes, str], - ], - *, - session: Optional[session.Session] = None, - ) -> S: - """Encrypts plaintext using the primary cryptographic key in keyset. The algorithm of the primary key must be AEAD_AES_GCM_256. Binds the ciphertext to the context defined by additional_data. Returns NULL if any input is NULL.""" - from bigframes.operations.googlesql.aead import encrypt as encrypt_impl - - # Resolve session from other arguments if not passed - if session is None: - from bigframes.core import googlesql - - session = googlesql._find_session( - plaintext, - additional_data, - ) - - bf_series = self._bf_from_series(session) - result = encrypt_impl( - bf_series, - plaintext, - additional_data, - ) - return self._to_series(cast(series.Series, result)) - - -class AiSeriesAccessor(series_mixins.AIMixin[T, S]): - """Series accessor for BigQuery ai functions.""" diff --git a/bigframes/extensions/core/series_mixins.py b/bigframes/extensions/core/series_mixins.py deleted file mode 100644 index 4d1b61ecb0c..00000000000 --- a/bigframes/extensions/core/series_mixins.py +++ /dev/null @@ -1,196 +0,0 @@ -# Copyright 2026 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 typing import Any, List, Literal, Mapping, TypeVar - -import pandas as pd - -from bigframes import series -from bigframes import session as bf_session -from bigframes.bigquery import ai -from bigframes.extensions.core import abstract_series_accessor -from bigframes.ml import base as ml_base - -T = TypeVar("T") -S = TypeVar("S") - - -class AIMixin(abstract_series_accessor.AbstractBigQuerySeriesAccessor[T, S]): - def generate_embedding( - self, - model: ml_base.BaseEstimator | str | pd.Series, - *, - output_dimensionality: int | None = None, - task_type: str | None = None, - start_second: float | None = None, - end_second: float | None = None, - interval_seconds: float | None = None, - trial_id: int | None = None, - session: bf_session.Session | None = None, - ) -> T: - """ - Creates embeddings that describe an entity — for example, a piece of text or an image. - - This is an accessor for :func:`bigframes.bigquery.ai.generate_embedding`. See that - function's documentation for detailed parameter descriptions and examples. - """ - - bf_series = self._bf_from_series(session) - result = ai.generate_embedding( - model, - bf_series, - output_dimensionality=output_dimensionality, - task_type=task_type, - start_second=start_second, - end_second=end_second, - interval_seconds=interval_seconds, - trial_id=trial_id, - ) - return self._to_dataframe(result) - - def generate_text( - self, - model: ml_base.BaseEstimator | str | pd.Series, - *, - temperature: float | None = None, - max_output_tokens: int | None = None, - top_k: int | None = None, - top_p: float | None = None, - stop_sequences: List[str] | None = None, - ground_with_google_search: bool | None = None, - request_type: str | None = None, - session: bf_session.Session | None = None, - ) -> T: - """ - Generates text using a BigQuery ML model. - - This is an accessor for :func:`bigframes.bigquery.ai.generate_text`. See that - function's documentation for detailed parameter descriptions and examples. - """ - bf_series = self._bf_from_series(session) - result = ai.generate_text( - model, - bf_series, - temperature=temperature, - max_output_tokens=max_output_tokens, - top_k=top_k, - top_p=top_p, - stop_sequences=stop_sequences, - ground_with_google_search=ground_with_google_search, - request_type=request_type, - ) - return self._to_dataframe(result) - - def generate_table( - self, - model: ml_base.BaseEstimator | str | pd.Series, - *, - output_schema: str | Mapping[str, str], - temperature: float | None = None, - top_p: float | None = None, - max_output_tokens: int | None = None, - stop_sequences: List[str] | None = None, - request_type: str | None = None, - session: bf_session.Session | None = None, - ) -> T: - """ - Generates a table using a BigQuery ML model. - - This is an accessor for :func:`bigframes.bigquery.ai.generate_table`. See that - function's documentation for detailed parameter descriptions and examples. - """ - bf_series = self._bf_from_series(session) - result = ai.generate_table( - model, - bf_series, - output_schema=output_schema, - temperature=temperature, - top_p=top_p, - max_output_tokens=max_output_tokens, - stop_sequences=stop_sequences, - request_type=request_type, - ) - return self._to_dataframe(result) - - def embed( - self, - *, - endpoint: str | None = None, - model: str | None = None, - task_type: ( - Literal[ - "retrieval_query", - "retrieval_document", - "semantic_similarity", - "classification", - "clustering", - "question_answering", - "fact_verification", - "code_retrieval_query", - ] - | None - ) = None, - title: str | None = None, - model_params: Mapping[Any, Any] | None = None, - connection_id: str | None = None, - session: bf_session.Session | None = None, - ) -> S: - """ - Creates embeddings from text or image data in BigQuery. - - This is an accessor for :func:`bigframes.bigquery.ai.embed`. See that - function's documentation for detailed parameter descriptions and examples. - """ - - bf_series = self._bf_from_series(session) - result = ai.embed( - bf_series, - endpoint=endpoint, - model=model, - task_type=task_type, - title=title, - model_params=model_params, - connection_id=connection_id, - ) - return self._to_series(result) - - def similarity( - self, - other: str | series.Series | pd.Series, - *, - endpoint: str | None = None, - model: str | None = None, - model_params: Mapping[Any, Any] | None = None, - connection_id: str | None = None, - session: bf_session.Session | None = None, - ) -> S: - """ - Returns a FLOAT64 value that represents the cosine similarity between the two inputs. - - This is an accessor for :func:`bigframes.bigquery.ai.similarity`. See that - function's documentation for detailed parameter descriptions and examples. - """ - - bf_series = self._bf_from_series(session) - result = ai.similarity( - bf_series, - other, - endpoint=endpoint, - model=model, - model_params=model_params, - connection_id=connection_id, - ) - return self._to_series(result) diff --git a/bigframes/extensions/pandas/__init__.py b/bigframes/extensions/pandas/__init__.py deleted file mode 100644 index 6af1f769b5b..00000000000 --- a/bigframes/extensions/pandas/__init__.py +++ /dev/null @@ -1,31 +0,0 @@ -# Copyright 2026 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. - -""" -BigQuery DataFrames automatically registers a pandas extenstion when imported. -This allows you to use the power of the BigQuery engine with pandas objects -directly. -""" - -from bigframes.extensions.pandas.dataframe_accessor import ( - PandasBigQueryDataFrameAccessor, -) -from bigframes.extensions.pandas.series_accessor import ( - PandasBigQuerySeriesAccessor, -) - -__all__ = [ - "PandasBigQueryDataFrameAccessor", - "PandasBigQuerySeriesAccessor", -] diff --git a/bigframes/extensions/pandas/dataframe_accessor.py b/bigframes/extensions/pandas/dataframe_accessor.py deleted file mode 100644 index 512134cac03..00000000000 --- a/bigframes/extensions/pandas/dataframe_accessor.py +++ /dev/null @@ -1,83 +0,0 @@ -# Copyright 2026 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 typing import TypeVar, cast - -import pandas -import pandas.api.extensions - -import bigframes.core.global_session as bf_session -import bigframes.dataframe -import bigframes.pandas as bpd -from bigframes.core.logging import log_adapter -from bigframes.extensions.core.dataframe_accessor import ( - AIAccessor, - BigQueryDataFrameAccessor, -) - -T = TypeVar("T", bound="pandas.DataFrame") -S = TypeVar("S", bound="pandas.Series") - - -@log_adapter.class_logger -class PandasAIAccessor(AIAccessor[T, S]): - """ - Pandas DataFrame accessor for BigQuery AI functions. - """ - - def __init__(self, pandas_obj: T): - super().__init__(pandas_obj) - - def _bf_from_dataframe( - self, session: bigframes.session.Session | None - ) -> bigframes.dataframe.DataFrame: - if session is None: - session = bf_session.get_global_session() - - return cast(bpd.DataFrame, session.read_pandas(self._obj)) - - def _to_dataframe(self, bf_df: bigframes.dataframe.DataFrame) -> T: - return cast(T, bf_df.to_pandas(ordered=True)) - - def _to_series(self, bf_series: bigframes.series.Series) -> S: - return cast(S, bf_series.to_pandas(ordered=True)) - - -@pandas.api.extensions.register_dataframe_accessor("bigquery") -@log_adapter.class_logger -class PandasBigQueryDataFrameAccessor(BigQueryDataFrameAccessor[T, S]): - """ - Pandas DataFrame accessor for BigQuery DataFrames functionality. - - This accessor is registered under the ``bigquery`` namespace on pandas DataFrame objects. - """ - - def __init__(self, pandas_obj: T): - super().__init__(pandas_obj) - - @property - def ai(self) -> PandasAIAccessor: - return PandasAIAccessor(self._obj) - - def _bf_from_dataframe(self, session) -> bigframes.dataframe.DataFrame: - if session is None: - session = bf_session.get_global_session() - - return cast(bpd.DataFrame, session.read_pandas(self._obj)) - - def _to_dataframe(self, bf_df: bigframes.dataframe.DataFrame) -> T: - return cast(T, bf_df.to_pandas(ordered=True)) - - def _to_series(self, bf_series: bigframes.series.Series) -> S: - return cast(S, bf_series.to_pandas(ordered=True)) diff --git a/bigframes/extensions/pandas/series_accessor.py b/bigframes/extensions/pandas/series_accessor.py deleted file mode 100644 index 9c33996c421..00000000000 --- a/bigframes/extensions/pandas/series_accessor.py +++ /dev/null @@ -1,98 +0,0 @@ -# Copyright 2026 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. -# -# DO NOT MODIFY THIS FILE DIRECTLY. -# This file was generated by the script: scripts/generate_bigframes_bigquery.py -# - -from __future__ import annotations - -from typing import Optional, TypeVar, cast - -import pandas -import pandas.api.extensions - -from bigframes import dataframe, series, session -from bigframes.core import global_session as bf_session -from bigframes.core.logging import log_adapter -from bigframes.extensions.core import series_accessor as core_accessor - -T = TypeVar("T", bound="pandas.DataFrame") -S = TypeVar("S", bound="pandas.Series") - - -@pandas.api.extensions.register_series_accessor("bigquery") -@log_adapter.class_logger -class PandasBigQuerySeriesAccessor(core_accessor.BigQuerySeriesAccessor[T, S]): - def __init__(self, pandas_obj: S): - super().__init__(pandas_obj) - - def _bf_from_series( - self, session: Optional[session.Session] = None - ) -> series.Series: - if session is None: - session = bf_session.get_global_session() - return cast(series.Series, session.read_pandas(self._obj)) - - def _to_dataframe(self, bf_df: dataframe.DataFrame) -> T: - return cast(T, bf_df.to_pandas(ordered=True)) - - def _to_series(self, bf_series: series.Series) -> S: - return cast(S, bf_series.to_pandas(ordered=True)) - - @property - def aead(self) -> PandasAeadSeriesAccessor[T, S]: - return PandasAeadSeriesAccessor(self._obj) - - @property - def ai(self) -> PandasAiSeriesAccessor[T, S]: - return PandasAiSeriesAccessor(self._obj) - - -@log_adapter.class_logger -class PandasAeadSeriesAccessor(core_accessor.AeadSeriesAccessor[T, S]): - def __init__(self, pandas_obj: S): - super().__init__(pandas_obj) - - def _bf_from_series( - self, session: Optional[session.Session] = None - ) -> series.Series: - if session is None: - session = bf_session.get_global_session() - return cast(series.Series, session.read_pandas(self._obj)) - - def _to_dataframe(self, bf_df: dataframe.DataFrame) -> T: - return cast(T, bf_df.to_pandas(ordered=True)) - - def _to_series(self, bf_series: series.Series) -> S: - return cast(S, bf_series.to_pandas(ordered=True)) - - -@log_adapter.class_logger -class PandasAiSeriesAccessor(core_accessor.AiSeriesAccessor[T, S]): - def __init__(self, pandas_obj: S): - super().__init__(pandas_obj) - - def _bf_from_series( - self, session: Optional[session.Session] = None - ) -> series.Series: - if session is None: - session = bf_session.get_global_session() - return cast(series.Series, session.read_pandas(self._obj)) - - def _to_dataframe(self, bf_df: dataframe.DataFrame) -> T: - return cast(T, bf_df.to_pandas(ordered=True)) - - def _to_series(self, bf_series: series.Series) -> S: - return cast(S, bf_series.to_pandas(ordered=True)) diff --git a/bigframes/features.py b/bigframes/features.py deleted file mode 100644 index 287dbcb0a4e..00000000000 --- a/bigframes/features.py +++ /dev/null @@ -1,42 +0,0 @@ -# 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. -# 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 typing import Tuple - - -class PandasVersions: - """Version comparisons for pandas package""" - - def __init__(self): - self._installed_version = None - - @property - def installed_version(self) -> Tuple[str, ...]: - """pandas version""" - if self._installed_version is None: - import pandas - - self._installed_version = tuple(pandas.__version__.split(".")) - return self._installed_version - - @property - def is_arrow_list_dtype_usable(self): - """True if pandas.ArrowDtype is usable.""" - version = self.installed_version - return version[0] != "1" - - -PANDAS_VERSIONS = PandasVersions() diff --git a/bigframes/formatting_helpers.py b/bigframes/formatting_helpers.py index 9ab25951932..752aeb7a10b 100644 --- a/bigframes/formatting_helpers.py +++ b/bigframes/formatting_helpers.py @@ -13,23 +13,20 @@ # limitations under the License. """Shared helper functions for formatting jobs related info.""" - -from __future__ import annotations +# TODO(orrbradford): cleanup up typings and documenttion in this file import datetime -import html import random -from typing import TYPE_CHECKING, Any, Optional, Type, Union +from typing import Any, Optional, Union -import bigframes_vendored.constants as constants import google.api_core.exceptions as api_core_exceptions import google.cloud.bigquery as bigquery import humanize +import IPython +import IPython.display as display +import ipywidgets as widgets -import bigframes._config - -if TYPE_CHECKING: - import bigframes.core.events +import bigframes.constants as constants GenericJob = Union[ bigquery.LoadJob, bigquery.ExtractJob, bigquery.QueryJob, bigquery.CopyJob @@ -47,194 +44,128 @@ def add_feedback_link( exception: Union[ api_core_exceptions.RetryError, api_core_exceptions.GoogleAPICallError - ], + ] ): exception.message = exception.message + f" {constants.FEEDBACK_LINK}" -def create_exception_with_feedback_link( - exception: Type[Exception], - arg: str = "", -): - if arg: - return exception(arg + f" {constants.FEEDBACK_LINK}") - - return exception(constants.FEEDBACK_LINK) - - -def repr_query_job(query_job: Optional[bigquery.QueryJob]): - """Return query job as a formatted string. +def repr_query_job_html(query_job: Optional[bigquery.QueryJob]): + """Return query job in html format. Args: - query_job: + query_job (bigquery.QueryJob, Optional): The job representing the execution of the query on the server. Returns: - Formatted string. + Pywidget html table. """ if query_job is None: - return "No job information available" + return display.HTML("No job information available") if query_job.dry_run: - return ( - f"Computation deferred. Computation will process " - f"{get_formatted_bytes(query_job.total_bytes_processed)}" + return display.HTML( + f"Computation deferred. Computation will process {get_formatted_bytes(query_job.total_bytes_processed)}" ) - res = "Query Job Info" + table_html = "" + table_html += "" for key, value in query_job_prop_pairs.items(): job_val = getattr(query_job, value) if job_val is not None: - res += "\n" if key == "Job Id": # add link to job - res += f"""Job url: { - get_job_url( - project_id=query_job.project, - location=query_job.location, - job_id=query_job.job_id, - ) - }""" + table_html += f"""""" elif key == "Slot Time": - res += f"""{key}: {get_formatted_time(job_val)}""" + table_html += ( + f"""""" + ) elif key == "Bytes Processed": - res += f"""{key}: {get_formatted_bytes(job_val)}""" + table_html += f"""""" else: - res += f"""{key}: {job_val}""" - return res + table_html += f"""""" + table_html += "
{key}{job_val}
{key}{get_formatted_time(job_val)}
{key}{get_formatted_bytes(job_val)}
{key}{job_val}
" + return widgets.HTML(table_html) -def repr_query_job_html(query_job: Optional[bigquery.QueryJob]): - """Return query job as a formatted html string. +def repr_query_job(query_job: Optional[bigquery.QueryJob]): + """Return query job as a formatted string. Args: query_job: The job representing the execution of the query on the server. Returns: - Html string. + Pywidget html table. """ if query_job is None: return "No job information available" if query_job.dry_run: - return ( - f"Computation deferred. Computation will process " - f"{get_formatted_bytes(query_job.total_bytes_processed)}" - ) - - # We can reuse the plaintext repr for now or make a nicer table. - # For deferred mode consistency, let's just wrap the text in a pre - # block or similar, but the request implies we want a distinct HTML - # representation if possible. - # However, existing repr_query_job returns a simple string. - # Let's format it as a simple table or list. - - res = "

Query Job Info

    " + return f"Computation deferred. Computation will process {get_formatted_bytes(query_job.total_bytes_processed)}" + res = "Query Job Info" for key, value in query_job_prop_pairs.items(): job_val = getattr(query_job, value) if job_val is not None: + res += "\n" if key == "Job Id": # add link to job - url = get_job_url( - project_id=query_job.project, - location=query_job.location, - job_id=query_job.job_id, - ) - res += ( - f'
  • Job: ' - f"{query_job.job_id}
  • " - ) + res += f"""Job url: {get_job_url(query_job)}""" elif key == "Slot Time": - res += f"
  • {key}: {get_formatted_time(job_val)}
  • " + res += f"""{key}: {get_formatted_time(job_val)}""" elif key == "Bytes Processed": - res += f"
  • {key}: {get_formatted_bytes(job_val)}
  • " + res += f"""{key}: {get_formatted_bytes(job_val)}""" else: - res += f"
  • {key}: {job_val}
  • " - res += "
" + res += f"""{key}: {job_val}""" return res -current_display_id: Optional[str] = None - - -def create_progress_callback(): - # bind potentially thread-local config to the callback so that it uses the user thread - # config even if callback is invoked from a worker thread. - display_opts = bigframes._config.options.display - - def progress_callback( - envelope: Any, - ): - """Displays a progress bar while the query is running""" - global current_display_id - - try: - import bigframes._config - import bigframes.core.events - except ImportError: - # Since this gets called from __del__, skip if the import fails to avoid - # ImportError: sys.meta_path is None, Python is likely shutting down. - # This will allow cleanup to continue. - return - - # Publisher.publish automatically wraps raw Event objects in an - # EventEnvelope, ensuring subscribers receive a consistent contract. - assert isinstance(envelope, bigframes.core.events.EventEnvelope) - event = envelope.event - progress_bar = envelope.progress_bar - - if progress_bar == bigframes.core.events._DEFAULT: - progress_bar = display_opts.progress_bar - - if progress_bar == "auto": - progress_bar = "notebook" if in_ipython() else "terminal" +def wait_for_query_job( + query_job: bigquery.QueryJob, + max_results: Optional[int] = None, + progress_bar: Optional[str] = None, +) -> bigquery.table.RowIterator: + """Return query results. Displays a progress bar while the query is running + Args: + query_job (bigquery.QueryJob, Optional): + The job representing the execution of the query on the server. + max_results (int, Optional): + The maximum number of rows the row iterator should return. + progress_bar (str, Optional): + Which progress bar to show. + Returns: + A row iterator over the query results. + """ + if progress_bar == "auto": + progress_bar = "notebook" if in_ipython() else "terminal" + try: if progress_bar == "notebook": - import IPython.display as display - - display_html = None - - if isinstance(event, bigframes.core.events.ExecutionStarted): - # Start a new context for progress output. - current_display_id = None - - elif isinstance(event, bigframes.core.events.BigQuerySentEvent): - display_html = render_bqquery_sent_event_html(event) - - elif isinstance(event, bigframes.core.events.BigQueryRetryEvent): - display_html = render_bqquery_retry_event_html(event) - - elif isinstance(event, bigframes.core.events.BigQueryReceivedEvent): - display_html = render_bqquery_received_event_html(event) - - elif isinstance(event, bigframes.core.events.BigQueryFinishedEvent): - display_html = render_bqquery_finished_event_html(event) - - elif isinstance(event, bigframes.core.events.SessionClosed): - display_html = f"Session {event.session_id} closed." - - if display_html: - if current_display_id: - display.update_display( - display.HTML(display_html), - display_id=current_display_id, - ) - else: - current_display_id = str(random.random()) - display.display( - display.HTML(display_html), - display_id=current_display_id, - ) - + display_id = str(random.random()) + loading_bar = display.HTML(get_query_job_loading_html(query_job)) + display.display(loading_bar, display_id=display_id) + query_result = query_job.result(max_results=max_results) + query_job.reload() + display.update_display( + display.HTML(get_query_job_loading_html(query_job)), + display_id=display_id, + ) elif progress_bar == "terminal": - message = None - - if isinstance(event, bigframes.core.events.BigQuerySentEvent): - message = render_bqquery_sent_event_plaintext(event) - print(message) - elif isinstance(event, bigframes.core.events.BigQueryRetryEvent): - message = render_bqquery_retry_event_plaintext(event) - print(message) - elif isinstance(event, bigframes.core.events.BigQueryReceivedEvent): - message = render_bqquery_received_event_plaintext(event) - print(message) - elif isinstance(event, bigframes.core.events.BigQueryFinishedEvent): - message = render_bqquery_finished_event_plaintext(event) - print(message) - - return progress_callback + initial_loading_bar = get_query_job_loading_string(query_job) + print(initial_loading_bar) + query_result = query_job.result(max_results=max_results) + query_job.reload() + if initial_loading_bar != get_query_job_loading_string(query_job): + print(get_query_job_loading_string(query_job)) + else: + # No progress bar. + query_result = query_job.result(max_results=max_results) + query_job.reload() + return query_result + except api_core_exceptions.RetryError as exc: + add_feedback_link(exc) + raise + except api_core_exceptions.GoogleAPICallError as exc: + add_feedback_link(exc) + raise + except KeyboardInterrupt: + query_job.cancel() + print( + f"Requested cancellation for {query_job.job_type.capitalize()}" + f" job {query_job.job_id} in location {query_job.location}..." + ) + # begin the cancel request before immediately rethrowing + raise def wait_for_job(job: GenericJob, progress_bar: Optional[str] = None): @@ -250,16 +181,13 @@ def wait_for_job(job: GenericJob, progress_bar: Optional[str] = None): try: if progress_bar == "notebook": - import IPython.display as display - display_id = str(random.random()) loading_bar = display.HTML(get_base_job_loading_html(job)) display.display(loading_bar, display_id=display_id) job.result() job.reload() display.update_display( - display.HTML(get_base_job_loading_html(job)), - display_id=display_id, + display.HTML(get_base_job_loading_html(job)), display_id=display_id ) elif progress_bar == "terminal": inital_loading_bar = get_base_job_loading_string(job) @@ -288,80 +216,24 @@ def wait_for_job(job: GenericJob, progress_bar: Optional[str] = None): raise -def render_query_references( - *, - project_id: Optional[str], - location: Optional[str], - job_id: Optional[str], - request_id: Optional[str], -) -> str: - query_id = "" - if request_id and not job_id: - query_id = f" with request ID {project_id}:{location}.{request_id}" - return query_id - - -def render_job_link_html( - *, - project_id: Optional[str], - location: Optional[str], - job_id: Optional[str], -) -> str: - job_url = get_job_url( - project_id=project_id, - location=location, - job_id=job_id, - ) - if job_url: - job_link = ( - f' [' - f"Job {project_id}:{location}.{job_id} details]" - ) - else: - job_link = "" - return job_link - - -def render_job_link_plaintext( - *, - project_id: Optional[str], - location: Optional[str], - job_id: Optional[str], -) -> str: - job_url = get_job_url( - project_id=project_id, - location=location, - job_id=job_id, - ) - if job_url: - job_link = f" Job {project_id}:{location}.{job_id} details: {job_url}" - else: - job_link = "" - return job_link - - -def get_job_url( - *, - project_id: Optional[str], - location: Optional[str], - job_id: Optional[str], -): +def get_job_url(query_job: GenericJob): """Return url to the query job in cloud console. - + Args: + query_job (GenericJob): + The job representing the execution of the query on the server. Returns: String url. """ - if project_id is None or location is None or job_id is None: + if ( + query_job.project is None + or query_job.location is None + or query_job.job_id is None + ): return None - return ( - f"https://console.cloud.google.com/bigquery?project={project_id}" - f"&j=bq:{location}:{job_id}&page=queryresults" - ) + return f"""https://console.cloud.google.com/bigquery?project={query_job.project}&j=bq:{query_job.location}:{query_job.job_id}&page=queryresults""" -def render_bqquery_sent_event_html( - event: bigframes.core.events.BigQuerySentEvent, -) -> str: +def get_query_job_loading_html(query_job: bigquery.QueryJob): """Return progress bar html string Args: query_job (bigquery.QueryJob): @@ -369,205 +241,18 @@ def render_bqquery_sent_event_html( Returns: Html string. """ - - job_link = render_job_link_html( - project_id=event.billing_project, - location=event.location, - job_id=event.job_id, - ) - query_id = render_query_references( - project_id=event.billing_project, - location=event.location, - job_id=event.job_id, - request_id=event.request_id, - ) - query_text_details = ( - f"
SQL
"
-        f"{html.escape(event.query)}
" - ) - - return f""" - Query started{query_id}.{job_link}{query_text_details} - """ + return f"""Query job {query_job.job_id} is {query_job.state}. {get_bytes_processed_string(query_job.total_bytes_processed)}Open Job""" -def render_bqquery_sent_event_plaintext( - event: bigframes.core.events.BigQuerySentEvent, -) -> str: - """Return progress bar html string +def get_query_job_loading_string(query_job: bigquery.QueryJob): + """Return progress bar string Args: query_job (bigquery.QueryJob): The job representing the execution of the query on the server. Returns: - Html string. - """ - - job_link = render_job_link_plaintext( - project_id=event.billing_project, - location=event.location, - job_id=event.job_id, - ) - query_id = render_query_references( - project_id=event.billing_project, - location=event.location, - job_id=event.job_id, - request_id=event.request_id, - ) - - return f"Query started{query_id}.{job_link}" - - -def render_bqquery_retry_event_html( - event: bigframes.core.events.BigQueryRetryEvent, -) -> str: - """Return progress bar html string for retry event.""" - - job_link = render_job_link_html( - project_id=event.billing_project, - location=event.location, - job_id=event.job_id, - ) - query_id = render_query_references( - project_id=event.billing_project, - location=event.location, - job_id=event.job_id, - request_id=event.request_id, - ) - query_text_details = ( - f"
SQL
"
-        f"{html.escape(event.query)}
" - ) - - return f""" - Retrying query{query_id}.{job_link}{query_text_details} - """ - - -def render_bqquery_retry_event_plaintext( - event: bigframes.core.events.BigQueryRetryEvent, -) -> str: - """Return progress bar plaintext string for retry event.""" - - job_link = render_job_link_plaintext( - project_id=event.billing_project, - location=event.location, - job_id=event.job_id, - ) - query_id = render_query_references( - project_id=event.billing_project, - location=event.location, - job_id=event.job_id, - request_id=event.request_id, - ) - return f"Retrying query{query_id}.{job_link}" - - -def render_bqquery_received_event_html( - event: bigframes.core.events.BigQueryReceivedEvent, -) -> str: - """Return progress bar html string for received event.""" - - job_link = render_job_link_html( - project_id=event.billing_project, - location=event.location, - job_id=event.job_id, - ) - query_id = render_query_references( - project_id=event.billing_project, - location=event.location, - job_id=event.job_id, - request_id=None, - ) - - query_plan_details = "" - if event.query_plan: - plan_str = "\n".join([str(entry) for entry in event.query_plan]) - query_plan_details = ( - f"
Query Plan
"
-            f"{html.escape(plan_str)}
" - ) - - return f""" - Query{query_id} is {event.state}.{job_link}{query_plan_details} - """ - - -def render_bqquery_received_event_plaintext( - event: bigframes.core.events.BigQueryReceivedEvent, -) -> str: - """Return progress bar plaintext string for received event.""" - - job_link = render_job_link_plaintext( - project_id=event.billing_project, - location=event.location, - job_id=event.job_id, - ) - query_id = render_query_references( - project_id=event.billing_project, - location=event.location, - job_id=event.job_id, - request_id=None, - ) - return f"Query{query_id} is {event.state}.{job_link}" - - -def render_bqquery_finished_event_html( - event: bigframes.core.events.BigQueryFinishedEvent, -) -> str: - """Return progress bar html string for finished event.""" - - bytes_str = "" - if event.total_bytes_processed is not None: - bytes_str = f" {humanize.naturalsize(event.total_bytes_processed)}" - - slot_time_str = "" - if event.slot_millis is not None: - slot_time = datetime.timedelta(milliseconds=event.slot_millis) - slot_time_str = f" in {humanize.naturaldelta(slot_time)} of slot time" - - job_link = render_job_link_html( - project_id=event.billing_project, - location=event.location, - job_id=event.job_id, - ) - query_id = render_query_references( - project_id=event.billing_project, - location=event.location, - job_id=event.job_id, - request_id=None, - ) - return f""" - Query processed{bytes_str}{slot_time_str}{query_id}.{job_link} + String """ - - -def render_bqquery_finished_event_plaintext( - event: bigframes.core.events.BigQueryFinishedEvent, -) -> str: - """Return progress bar plaintext string for finished event.""" - - bytes_str = "" - if event.total_bytes_processed is not None: - size_str = humanize.naturalsize(event.total_bytes_processed) - bytes_str = f" {size_str} processed." - - slot_time_str = "" - if event.slot_millis is not None: - slot_time = datetime.timedelta(milliseconds=event.slot_millis) - slot_time_str = f" Slot time: {humanize.naturaldelta(slot_time)}." - - job_link = render_job_link_plaintext( - project_id=event.billing_project, - location=event.location, - job_id=event.job_id, - ) - query_id = render_query_references( - project_id=event.billing_project, - location=event.location, - job_id=event.job_id, - request_id=None, - ) - return f"Query{query_id} finished.{bytes_str}{slot_time_str}{job_link}" + return f"""Query job {query_job.job_id} is {query_job.state}.{get_bytes_processed_string(query_job.total_bytes_processed)} \n{get_job_url(query_job)}""" def get_base_job_loading_html(job: GenericJob): @@ -578,15 +263,7 @@ def get_base_job_loading_html(job: GenericJob): Returns: Html string. """ - return f"""{job.job_type.capitalize()} job {job.job_id} is { - job.state - }. Open Job""" + return f"""{job.job_type.capitalize()} job {job.job_id} is {job.state}. Open Job""" def get_base_job_loading_string(job: GenericJob): @@ -597,13 +274,7 @@ def get_base_job_loading_string(job: GenericJob): Returns: String """ - return f"""{job.job_type.capitalize()} job {job.job_id} is {job.state}. \n{ - get_job_url( - project_id=job.job_id, - location=job.location, - job_id=job.job_id, - ) - }""" + return f"""{job.job_type.capitalize()} job {job.job_id} is {job.state}. \n{get_job_url(job)}""" def get_formatted_time(val): @@ -615,8 +286,7 @@ def get_formatted_time(val): Duration string """ try: - delta = datetime.timedelta(milliseconds=float(val)) - return humanize.naturaldelta(delta) + return humanize.naturaldelta(datetime.timedelta(milliseconds=float(val))) except Exception: return val @@ -635,10 +305,7 @@ def get_formatted_bytes(val): def get_bytes_processed_string(val: Any): - """Try to get bytes processed string. - - Return empty if passed non int value. - """ + """Try to get bytes processed string. Return empty if passed non int value""" bytes_processed_string = "" if isinstance(val, int): bytes_processed_string = f"""{get_formatted_bytes(val)} processed. """ @@ -647,8 +314,4 @@ def get_bytes_processed_string(val: Any): def in_ipython(): """Return True iff we're in a colab-like IPython.""" - try: - import IPython - except (ImportError, NameError): - return False return hasattr(IPython.get_ipython(), "kernel") diff --git a/bigframes/functions/__init__.py b/bigframes/functions/__init__.py deleted file mode 100644 index 86119717d7b..00000000000 --- a/bigframes/functions/__init__.py +++ /dev/null @@ -1,18 +0,0 @@ -# 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. -# 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 bigframes.functions.function import Udf - -__all__ = [ - "Udf", -] diff --git a/bigframes/functions/_function_client.py b/bigframes/functions/_function_client.py deleted file mode 100644 index 69f99b50276..00000000000 --- a/bigframes/functions/_function_client.py +++ /dev/null @@ -1,582 +0,0 @@ -# Copyright 2023 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 logging -import os -import re -import shutil -import tempfile -import textwrap -import types -import warnings -from typing import Any, cast - -import google.api_core.exceptions -import google.api_core.retry -import requests -from google.cloud import bigquery, functions_v2 - -import bigframes.exceptions as bfe -import bigframes.formatting_helpers as bf_formatting -import bigframes.functions.function_template as bff_template -import bigframes.functions.udf_def as udf_def -from bigframes.functions import _utils - -logger = logging.getLogger(__name__) - -# https://cloud.google.com/sdk/gcloud/reference/functions/deploy#--ingress-settings -_INGRESS_SETTINGS_MAP = types.MappingProxyType( - { - "all": functions_v2.ServiceConfig.IngressSettings.ALLOW_ALL, - "internal-only": functions_v2.ServiceConfig.IngressSettings.ALLOW_INTERNAL_ONLY, - "internal-and-gclb": functions_v2.ServiceConfig.IngressSettings.ALLOW_INTERNAL_AND_GCLB, - } -) - -# https://cloud.google.com/functions/docs/reference/rest/v2/projects.locations.functions#vpconnectoregresssettings -_VPC_EGRESS_SETTINGS_MAP = types.MappingProxyType( - { - "all": functions_v2.ServiceConfig.VpcConnectorEgressSettings.ALL_TRAFFIC, - "private-ranges-only": functions_v2.ServiceConfig.VpcConnectorEgressSettings.PRIVATE_RANGES_ONLY, - "unspecified": functions_v2.ServiceConfig.VpcConnectorEgressSettings.VPC_CONNECTOR_EGRESS_SETTINGS_UNSPECIFIED, - } -) - -# BQ managed functions (@udf) currently only support Python 3.11. -_MANAGED_FUNC_PYTHON_VERSION = "python-3.11" - - -class FunctionClient: - # TODO(b/392707725): Convert all necessary parameters for cloud function - # deployment into method parameters. - def __init__( - self, - gcp_project_id: str, - bq_location: str, - bq_client: bigquery.Client, - bq_connection_manager, - cloud_functions_client: functions_v2.FunctionServiceClient, - publisher, - ): - self._gcp_project_id = gcp_project_id - self._bq_location = bq_location - self._bq_client = bq_client - self._bq_connection_manager = bq_connection_manager - self._publisher = publisher - self._cloud_functions_client = cloud_functions_client - - self._cf_location = _utils.gcf_location_from_bq_location(bq_location) - - @property - def cloudfunctions_region(self) -> str: - return self._cf_location - - def _create_bq_connection( - self, - connection_id: str, - bq_project_id: str, - ) -> None: - self._bq_connection_manager.create_bq_connection( - bq_project_id, - self._bq_location, - connection_id, - "run.invoker", - ) - - def _ensure_dataset_exists(self, dataset_ref: bigquery.DatasetReference) -> None: - # Make sure the dataset exists, i.e. if it doesn't exist, go ahead and - # create it. - try: - # This check does not require bigquery.datasets.create IAM - # permission. So, if the data set already exists, then user can work - # without having that permission. - self._bq_client.get_dataset(dataset_ref) - except google.api_core.exceptions.NotFound: - # This requires bigquery.datasets.create IAM permission. - dataset = bigquery.Dataset(dataset_ref) - dataset.location = self._bq_location - self._bq_client.create_dataset(dataset, exists_ok=True) - - def _create_bq_function(self, create_function_ddl: str) -> None: - # TODO(swast): plumb through the original, user-facing api_name. - import bigframes.session._io.bigquery - - _, query_job = bigframes.session._io.bigquery.start_query_with_job( - self._bq_client, - create_function_ddl, - job_config=bigquery.QueryJobConfig(), - location=self._bq_location, - project=None, - timeout=None, - metrics=None, - publisher=self._publisher, - ) - logger.info(f"Created bigframes function {query_job.ddl_target_routine}") - - def _format_function_options(self, function_options: dict) -> str: - def format_val(val): - if isinstance(val, str): - return f"'{val}'" - if isinstance(val, (list, tuple)): - return str(list(val)) - return str(val) - - return ", ".join( - [ - f"{key}={format_val(val)}" - for key, val in function_options.items() - if val is not None - ] - ) - - def create_bq_remote_function( - self, - routine_ref: bigquery.RoutineReference, - udf_def: udf_def.RemoteFunctionConfig, - maybe_reuse: bool, - try_create_connection: bool, - ): - """Create a BigQuery remote function given the artifacts of a user defined - function and the http endpoint of a corresponding cloud function.""" - - if maybe_reuse: - existing_rf_spec = self.get_remote_function_specs(routine_ref) - if existing_rf_spec and existing_rf_spec == udf_def: - logger.info(f"Remote function {str(routine_ref)} already exists.") - return - - if try_create_connection: - self._create_bq_connection(udf_def.connection_id, routine_ref.project) - - # Create BQ function - # https://cloud.google.com/bigquery/docs/reference/standard-sql/remote-functions#create_a_remote_function_2 - - remote_function_options = { - "endpoint": udf_def.endpoint, - "max_batching_rows": udf_def.max_batching_rows, - } - - if udf_def.bq_metadata: - # We are using the description field to store this structured - # bigframes specific metadata for the lack of a better option - remote_function_options["description"] = udf_def.bq_metadata - - remote_function_options_str = self._format_function_options( - remote_function_options - ) - - import bigframes.core.sql - import bigframes.core.utils - - # removes anything that isn't letter, number or underscore - _validate_routine_name(routine_ref.routine_id) - bq_function_name_escaped = bigframes.core.sql.identifier(routine_ref.routine_id) - create_function_ddl = f""" - CREATE OR REPLACE FUNCTION `{routine_ref.project}.{routine_ref.dataset_id}`.{bq_function_name_escaped}({udf_def.signature.to_sql_input_signature()}) - RETURNS {udf_def.signature.with_devirtualize().output.sql_type} - REMOTE WITH CONNECTION `{routine_ref.project}.{self._bq_location}.{udf_def.connection_id}` - OPTIONS ({remote_function_options_str})""" - - logger.info(f"Creating BQ remote function: {create_function_ddl}") - - self._ensure_dataset_exists( - bigquery.DatasetReference(routine_ref.project, routine_ref.dataset_id) - ) - self._create_bq_function(create_function_ddl) - - def provision_bq_managed_function( - self, - routine_ref: bigquery.RoutineReference, - config: udf_def.ManagedFunctionConfig, - ): - """Create a BigQuery managed function.""" - - # TODO(b/406283812): Expose the capability to pass down - # capture_references=True in the public udf API. - if ( - config.capture_references - and (python_version := _utils.get_python_version()) - != _MANAGED_FUNC_PYTHON_VERSION - ): - raise bf_formatting.create_exception_with_feedback_link( - NotImplementedError, - f"Capturing references for udf is currently supported only in Python version {_MANAGED_FUNC_PYTHON_VERSION}, you are running {python_version}.", - ) - - # Create BQ managed function. - bq_function_args = config.signature.to_sql_input_signature() - bq_function_return_type = config.signature.with_devirtualize().output.sql_type - - managed_function_options: dict[str, Any] = { - "runtime_version": _MANAGED_FUNC_PYTHON_VERSION, - "entry_point": "bigframes_handler", - } - if config.max_batching_rows: - managed_function_options["max_batching_rows"] = config.max_batching_rows - if config.container_cpu: - managed_function_options["container_cpu"] = config.container_cpu - if config.container_memory: - managed_function_options["container_memory"] = config.container_memory - - # Augment user package requirements with any internal package - # requirements. - packages = _utils.get_updated_package_requirements( - config.code.package_requirements or [], - config.signature.is_row_processor, - config.capture_references, - ignore_package_version=True, - ) - if packages: - managed_function_options["packages"] = packages - managed_function_options_str = self._format_function_options( - managed_function_options - ) - - persistent_func_id = ( - f"`{routine_ref.project}.{routine_ref.dataset_id}.{routine_ref.routine_id}`" - ) - - with_connection_clause = ( - ( - f"WITH CONNECTION `{routine_ref.project}.{self._bq_location}.{config.bq_connection_id}`" - ) - if config.bq_connection_id - else "" - ) - - # Generate the complete Python code block for the managed Python UDF, - # including the user's function, necessary imports, and the BigQuery - # handler wrapper. - python_code_block = bff_template.generate_managed_function_code( - config.code, config.signature, config.capture_references - ) - - create_function_ddl = ( - textwrap.dedent( - f""" - CREATE OR REPLACE FUNCTION {persistent_func_id}({bq_function_args}) - RETURNS {bq_function_return_type} - LANGUAGE python - {with_connection_clause} - OPTIONS ({managed_function_options_str}) - AS r''' - __UDF_PLACE_HOLDER__ - ''' - """ - ) - .strip() - .replace("__UDF_PLACE_HOLDER__", python_code_block) - ) - - self._ensure_dataset_exists( - bigquery.DatasetReference(routine_ref.project, routine_ref.dataset_id) - ) - self._create_bq_function(create_function_ddl) - - def get_cloud_function_fully_qualified_parent(self): - "Get the fully qualilfied parent for a cloud function." - return self._cloud_functions_client.common_location_path( - self._gcp_project_id, self._cf_location - ) - - def get_cloud_function_fully_qualified_name(self, name): - "Get the fully qualilfied name for a cloud function." - return self._cloud_functions_client.function_path( - self._gcp_project_id, self._cf_location, name - ) - - def get_cloud_function_endpoint(self, name) -> str | None: - """Get the http endpoint of a cloud function if it exists.""" - fully_qualified_name = self.get_cloud_function_fully_qualified_name(name) - try: - response = self._cloud_functions_client.get_function( - name=fully_qualified_name - ) - return response.service_config.uri - except google.api_core.exceptions.NotFound: - pass - return None - - def _generate_cloud_function_code( - self, - code_def: udf_def.CodeDef, - directory, - *, - udf_signature: udf_def.UdfSignature, - ): - """Generate the cloud function code for a given user defined function.""" - - # requirements.txt - if code_def.package_requirements: - requirements_txt = os.path.join(directory, "requirements.txt") - with open(requirements_txt, "w") as f: - f.write("\n".join(code_def.package_requirements)) - - # main.py - entry_point = bff_template.generate_cloud_function_main_code( - code_def, - directory, - udf_signature=udf_signature, - ) - return entry_point - - @google.api_core.retry.Retry( - predicate=google.api_core.retry.if_exception_type(ValueError), - initial=1.0, - maximum=10.0, - multiplier=2.0, - deadline=300.0, # Wait up to 5 minutes for propagation - ) - def _get_cloud_function_endpoint_with_retry(self, name): - endpoint = self.get_cloud_function_endpoint(name) - if not endpoint: - # Raising ValueError triggers the retry predicate - raise ValueError(f"Endpoint for {name} not yet available.") - return endpoint - - def create_cloud_function( - self, - name: str, - func_def: udf_def.CloudRunFunctionConfig, - ) -> str: - """Create a cloud function from the given user defined function.""" - - config = func_def - - # Build and deploy folder structure containing cloud function - with tempfile.TemporaryDirectory() as scratch_dir: - # Keep the generated sources in a subdirectory so the archive can be - # written inside the 0700 TemporaryDirectory. shutil.make_archive - # appends ".zip" to base_name, so archiving `directory` into itself - # would leave a world-readable copy of the (pickled) user code as a - # sibling of the temp dir that also survives the cleanup. - directory = os.path.join(scratch_dir, "src") - os.mkdir(directory) - entry_point = self._generate_cloud_function_code( - config.code, - directory, - udf_signature=config.signature, - ) - archive_path = shutil.make_archive( - os.path.join(scratch_dir, "source"), "zip", directory - ) - - # We are creating cloud function source code from the currently running - # python version. Use the same version to deploy. This is necessary - # because cloudpickle serialization done in one python version and - # deserialization done in another python version doesn't work. - # TODO(shobs): Figure out how to achieve version compatibility, specially - # when pickle (internally used by cloudpickle) guarantees that: - # https://docs.python.org/3/library/pickle.html#:~:text=The%20pickle%20serialization%20format%20is,unique%20breaking%20change%20language%20boundary. - python_version = _utils.get_python_version(is_compat=True) - - # Determine an upload URL for user code - upload_url_request = functions_v2.GenerateUploadUrlRequest( - kms_key_name=config.kms_key_name - ) - upload_url_request.parent = self.get_cloud_function_fully_qualified_parent() - upload_url_response = self._cloud_functions_client.generate_upload_url( - request=upload_url_request - ) - - # Upload the code to GCS - with open(archive_path, "rb") as f: - response = requests.put( - upload_url_response.upload_url, - data=f, - headers={"content-type": "application/zip"}, - ) - if response.status_code != 200: - raise bf_formatting.create_exception_with_feedback_link( - RuntimeError, - f"Failed to upload user code. code={response.status_code}, reason={response.reason}, text={response.text}", - ) - - # Deploy Cloud Function - create_function_request = functions_v2.CreateFunctionRequest() - create_function_request.parent = ( - self.get_cloud_function_fully_qualified_parent() - ) - create_function_request.function_id = name - function = functions_v2.Function() - function.name = self.get_cloud_function_fully_qualified_name(name) - function.build_config = functions_v2.BuildConfig() - function.build_config.runtime = python_version - function.build_config.entry_point = entry_point - function.build_config.source = functions_v2.Source() - function.build_config.source.storage_source = functions_v2.StorageSource() - function.build_config.source.storage_source.bucket = ( - upload_url_response.storage_source.bucket - ) - function.build_config.source.storage_source.object_ = ( - upload_url_response.storage_source.object_ - ) - if config.docker_repository is not None: - function.build_config.docker_repository = config.docker_repository - - if config.cloud_build_service_account is not None: - canonical_cloud_build_service_account = ( - config.cloud_build_service_account - if "/" in config.cloud_build_service_account - else f"projects/{self._gcp_project_id}/serviceAccounts/{config.cloud_build_service_account}" - ) - function.build_config.service_account = ( - canonical_cloud_build_service_account - ) - - function.service_config = functions_v2.ServiceConfig() - if config.memory_mib is not None: - function.service_config.available_memory = f"{config.memory_mib}Mi" - if config.cpus is not None: - function.service_config.available_cpu = str(config.cpus) - if config.timeout_seconds is not None: - if config.timeout_seconds > 1200: - raise bf_formatting.create_exception_with_feedback_link( - ValueError, - "BigQuery remote function can wait only up to 20 minutes" - ", see for more details " - "https://cloud.google.com/bigquery/quotas#remote_function_limits.", - ) - function.service_config.timeout_seconds = config.timeout_seconds - if config.max_instance_count is not None: - function.service_config.max_instance_count = config.max_instance_count - if config.vpc_connector is not None: - function.service_config.vpc_connector = config.vpc_connector - vpc_connector_egress_settings = config.vpc_connector_egress_settings - if config.vpc_connector_egress_settings is None: - msg = bfe.format_message( - "The 'vpc_connector_egress_settings' was not specified. Defaulting to 'private-ranges-only'.", - ) - warnings.warn(msg, category=UserWarning) - vpc_connector_egress_settings = "private-ranges-only" - if config.vpc_connector_egress_settings not in _VPC_EGRESS_SETTINGS_MAP: - raise bf_formatting.create_exception_with_feedback_link( - ValueError, - f"'{config.vpc_connector_egress_settings}' is not one of the supported vpc egress settings values: {list(_VPC_EGRESS_SETTINGS_MAP)}", - ) - function.service_config.vpc_connector_egress_settings = cast( - functions_v2.ServiceConfig.VpcConnectorEgressSettings, - _VPC_EGRESS_SETTINGS_MAP[vpc_connector_egress_settings], - ) - if config.cloud_run_service_account: - function.service_config.service_account_email = ( - config.cloud_run_service_account - ) - if config.concurrency: - function.service_config.max_instance_request_concurrency = ( - config.concurrency - ) - - # Functions framework use environment variables to pass config to gunicorn - # See https://github.com/GoogleCloudPlatform/functions-framework-python/issues/241 - # Code: https://github.com/GoogleCloudPlatform/functions-framework-python/blob/v3.10.1/src/functions_framework/_http/gunicorn.py#L37-L43 - env_vars = {} - if config.workers: - env_vars["WORKERS"] = str(config.workers) - if config.threads: - env_vars["THREADS"] = str(config.threads) - if env_vars: - function.service_config.environment_variables = env_vars - - if config.ingress_settings not in _INGRESS_SETTINGS_MAP: - raise bf_formatting.create_exception_with_feedback_link( - ValueError, - f"'{config.ingress_settings}' not one of the supported ingress settings values: {list(_INGRESS_SETTINGS_MAP)}", - ) - function.service_config.ingress_settings = cast( - functions_v2.ServiceConfig.IngressSettings, - _INGRESS_SETTINGS_MAP[config.ingress_settings], - ) - if config.kms_key_name: - function.kms_key_name = config.kms_key_name - create_function_request.function = function - - # Create the cloud function and wait for it to be ready to use - endpoint = None - try: - operation = self._cloud_functions_client.create_function( - request=create_function_request - ) - # operation.result() returns the Function object upon completion - function_obj = operation.result() - endpoint = function_obj.service_config.uri - - # Cleanup - os.remove(archive_path) - except google.api_core.exceptions.AlreadyExists: - # b/437124912: The most likely scenario is that - # `create_function` had a retry due to a network issue. The - # retried request then fails because the first call actually - # succeeded, but we didn't get the successful response back. - # - # Since the function name was randomly chosen to avoid - # conflicts, we know the AlreadyExist can only happen because - # we created it. This error is safe to ignore. - pass - - # Fetch the endpoint with retries if it wasn't returned by the operation - if not endpoint: - try: - endpoint = self._get_cloud_function_endpoint_with_retry(name) - except Exception as e: - raise bf_formatting.create_exception_with_feedback_link( - ValueError, f"Couldn't fetch the http endpoint: {e}" - ) - - logger.info(f"Successfully created cloud function {name} with uri ({endpoint})") - return endpoint - - def get_remote_function_specs( - self, remote_function_name: bigquery.RoutineReference - ) -> udf_def.RemoteFunctionConfig | None: - """Check whether a remote function already exists for the udf.""" - try: - routine = self._bq_client.get_routine(str(remote_function_name)) - if routine.reference == remote_function_name: - try: - return udf_def.RemoteFunctionConfig.from_bq_routine(routine) - except udf_def.ReturnTypeMissingError: - # The remote function exists, but it's missing a return type. - # Something is wrong with the function, so we should replace it. - return None - except google.api_core.exceptions.NotFound: - # The dataset might not exist, in which case the remote function doesn't, either. - # Note: list_routines doesn't make an API request until we iterate on the response object. - pass - return None - - def delete_routine(self, routine_name: bigquery.RoutineReference) -> None: - self._bq_client.delete_routine(str(routine_name), not_found_ok=True) - - def delete_cloud_function(self, cloud_function_name: str) -> None: - try: - self._cloud_functions_client.delete_function( - name=self.get_cloud_function_fully_qualified_name(cloud_function_name) - ) - except google.api_core.exceptions.NotFound: - # The dataset might not exist, in which case the remote function doesn't, either. - pass - - -def _validate_routine_name(name: str) -> None: - """Validate that the given name is a valid BigQuery routine name.""" - # Routine IDs can contain only letters (a-z, A-Z), numbers (0-9), or underscores (_) - # must also start with a letter or underscore only - if not re.match(r"^[a-zA-Z_][a-zA-Z0-9_]*$", name): - raise ValueError( - "Routine ID can contain only letters (a-z, A-Z), numbers (0-9), or underscores (_)" - ) diff --git a/bigframes/functions/_function_session.py b/bigframes/functions/_function_session.py deleted file mode 100644 index 2bc2b597372..00000000000 --- a/bigframes/functions/_function_session.py +++ /dev/null @@ -1,912 +0,0 @@ -# Copyright 2023 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 functools -import logging -import random -import string -import threading -import time -import warnings -from typing import ( - TYPE_CHECKING, - Literal, - Optional, - Sequence, - Union, -) - -from google.cloud import ( - bigquery, -) - -import bigframes.exceptions as bfe -import bigframes.formatting_helpers as bf_formatting -from bigframes import clients -from bigframes.functions import _function_client, _utils, udf_def -from bigframes.functions import function as bq_functions -from bigframes.functions._utils import ( - _BIGFRAMES_FUNCTION_PREFIX, - _BQ_FUNCTION_NAME_SEPERATOR, - _GCF_FUNCTION_NAME_SEPERATOR, -) - -if TYPE_CHECKING: - from bigframes.session import anonymous_dataset - - -_DEFAULT_FUNCTION_MEMORY_MIB = 1024 - - -logger = logging.getLogger(__name__) - - -class FunctionSession: - """Session to manage bigframes functions.""" - - def __init__( - self, - functions_client: _function_client.FunctionClient, - dataset_manager: anonymous_dataset.AnonymousDatasetManager, - default_connection: str, - location: str, - session_id: str, - manage_connections: bool, - ): - self._temp_cloud_functions: set[str] = set() - self._temp_remote_functions: set[bigquery.RoutineReference] = set() - - # Lock to synchronize the update of the session artifacts - self._artifacts_lock = threading.Lock() - - self._deployed_routines: set[bytes] = set() - self._deploying_routines: set[bytes] = set() - - self._function_client: _function_client.FunctionClient = functions_client - self._dataset_manager: anonymous_dataset.AnonymousDatasetManager = ( - dataset_manager - ) - self._default_connection: str = default_connection - self._location: str = location - self._session_id: str = session_id - self._manage_connections: bool = manage_connections - - @property - def session_id(self) -> str: - return self._session_id - - @property - def default_dataset(self) -> bigquery.DatasetReference: - # We defer this as a property since this can actually take a query to determine - # which dataset it is. - return self._dataset_manager.dataset - - def _resolve_dataset_reference( - self, - dataset: Optional[str], - ) -> bigquery.DatasetReference: - """ - Resolves the dataset reference for the bigframes function. - """ - return ( - bigquery.DatasetReference.from_string( - dataset, default_project=self.default_dataset.project - ) - if dataset - else self.default_dataset - ) - - def _resolve_routine_reference( - self, - function_name: str, - dataset: Optional[bigquery.DatasetReference] = None, - ) -> bigquery.RoutineReference: - """Resolves the routine reference for a BQ routine.""" - dataset_ref = dataset if dataset else self.default_dataset - return dataset_ref.routine(function_name) - - def _resolve_bigquery_connection_id( - self, - dataset_ref: bigquery.DatasetReference, - bigquery_connection: Optional[str] = None, - ) -> str: - """Resolves BigQuery connection id.""" - if not bigquery_connection: - bigquery_connection = self._default_connection - - bigquery_connection = clients.get_canonical_bq_connection_id( - bigquery_connection, - default_project=dataset_ref.project, - default_location=self._location, - ) - # Guaranteed to be the form of .. - ( - gcp_project_id, - bq_connection_location, - bq_connection_id, - ) = bigquery_connection.split(".") - if gcp_project_id.casefold() != dataset_ref.project.casefold(): - raise bf_formatting.create_exception_with_feedback_link( - ValueError, - "The project_id does not match BigQuery connection " - f"gcp_project_id: {dataset_ref.project}.", - ) - if bq_connection_location.casefold() != self._location.casefold(): - raise bf_formatting.create_exception_with_feedback_link( - ValueError, - "The location does not match BigQuery connection location: " - f"{self._location}.", - ) - return bq_connection_id - - def _add_temp_cloud_function(self, gcf_path: str): - with self._artifacts_lock: - self._temp_cloud_functions.add(gcf_path) - - def _add_temp_remote_function(self, bqrf_routine: bigquery.RoutineReference): - with self._artifacts_lock: - self._temp_remote_functions.add(bqrf_routine) - - def _deploy_managed_function( - self, - config: udf_def.ManagedFunctionConfig, - name: str, - temp: bool, - dataset: Optional[bigquery.DatasetReference] = None, - ) -> udf_def.BigqueryUdf: - routine_ref = self._resolve_routine_reference(name, dataset=dataset) - if temp: - self._add_temp_remote_function(routine_ref) - self._function_client.provision_bq_managed_function( - routine_ref=routine_ref, config=config - ) - return udf_def.BigqueryUdf( - routine_ref=routine_ref, - signature=config.signature, - ) - - def _deploy_udf( - self, - bq_udf: udf_def.PythonUdf, - ) -> udf_def.BigqueryUdf: - """Deploys a UDF to BigQuery if not already deployed.""" - udf_hash = bq_udf.stable_hash() - - config = bq_udf.to_managed_function_config() - bq_function_name = get_managed_function_name(config, self.session_id) - routine_ref = self._resolve_routine_reference(bq_function_name) - while True: - with self._artifacts_lock: - if udf_hash in self._deployed_routines: - return udf_def.BigqueryUdf( - routine_ref=routine_ref, - signature=bq_udf.signature, - ) - - if udf_hash not in self._deploying_routines: - self._deploying_routines.add(udf_hash) - break - - time.sleep(0.1) - try: - self._function_client.provision_bq_managed_function( - routine_ref=routine_ref, config=config - ) - except Exception: - with self._artifacts_lock: - self._deploying_routines.discard(udf_hash) - raise - self._add_temp_remote_function(routine_ref) - with self._artifacts_lock: - self._deploying_routines.discard(udf_hash) - self._deployed_routines.add(udf_hash) - return udf_def.BigqueryUdf( - routine_ref=routine_ref, - signature=bq_udf.signature, - ) - - def clean_up(self): - """Delete function artifacts in the current session.""" - with self._artifacts_lock: - for bqrf_routine in self._temp_remote_functions: - self._function_client.delete_routine(bqrf_routine) - for gcf_name in self._temp_cloud_functions: - self._function_client.delete_cloud_function(gcf_name) - - self._temp_remote_functions.clear() - self._temp_cloud_functions.clear() - - # Inspired by @udf decorator implemented in ibis-bigquery package - # https://github.com/ibis-project/ibis-bigquery/blob/main/ibis_bigquery/udf/__init__.py - # which has moved as @js to the ibis package - # https://github.com/ibis-project/ibis/blob/master/ibis/backends/bigquery/udf/__init__.py - def remote_function( - self, - *, - input_types: Union[None, type, Sequence[type]] = None, - output_type: Optional[type] = None, - dataset: Optional[str] = None, - bigquery_connection: Optional[str] = None, - reuse: bool = True, - name: Optional[str] = None, - packages: Optional[Sequence[str]] = None, - cloud_function_service_account: str, - cloud_function_kms_key_name: Optional[str] = None, - cloud_function_docker_repository: Optional[str] = None, - max_batching_rows: Optional[int] = None, - cloud_function_timeout: Optional[int] = 600, - cloud_function_max_instances: Optional[int] = None, - cloud_function_vpc_connector: Optional[str] = None, - cloud_function_vpc_connector_egress_settings: Optional[ - Literal["all", "private-ranges-only", "unspecified"] - ] = None, - cloud_function_memory_mib: Optional[int] = None, - cloud_function_cpus: Optional[float] = None, - cloud_function_ingress_settings: Literal[ - "all", "internal-only", "internal-and-gclb" - ] = "internal-only", - cloud_build_service_account: Optional[str] = None, - ): - """Decorator to turn a user defined function into a BigQuery remote function. - - .. deprecated:: 0.0.1 - This is an internal method. Please use :func:`bigframes.pandas.remote_function` instead. - - .. warning:: - To use remote functions with Bigframes 2.0 and onwards, please (preferred) - set an explicit user-managed ``cloud_function_service_account`` or (discouraged) - set ``cloud_function_service_account`` to use the Compute Engine service account - by setting it to `"default"`. - See, https://cloud.google.com/functions/docs/securing/function-identity. - - .. note:: - Please make sure following is setup before using this API: - - 1. Have the below APIs enabled for your project: - - * BigQuery Connection API - * Cloud Functions API - * Cloud Run API - * Cloud Build API - * Artifact Registry API - * Cloud Resource Manager API - - This can be done from the cloud console (change `PROJECT_ID` to yours): - https://console.cloud.google.com/apis/enableflow?apiid=bigqueryconnection.googleapis.com,cloudfunctions.googleapis.com,run.googleapis.com,cloudbuild.googleapis.com,artifactregistry.googleapis.com,cloudresourcemanager.googleapis.com&project=PROJECT_ID - - Or from the gcloud CLI: - - `$ gcloud services enable bigqueryconnection.googleapis.com cloudfunctions.googleapis.com run.googleapis.com cloudbuild.googleapis.com artifactregistry.googleapis.com cloudresourcemanager.googleapis.com` - - 2. Have following IAM roles enabled for you: - - * BigQuery Data Editor (roles/bigquery.dataEditor) - * BigQuery Connection Admin (roles/bigquery.connectionAdmin) - * Cloud Functions Developer (roles/cloudfunctions.developer) - * Service Account User (roles/iam.serviceAccountUser) on the service account `PROJECT_NUMBER-compute@developer.gserviceaccount.com` - * Storage Object Viewer (roles/storage.objectViewer) - * Project IAM Admin (roles/resourcemanager.projectIamAdmin) (Only required if the bigquery connection being used is not pre-created and is created dynamically with user credentials.) - - 3. Either the user has setIamPolicy privilege on the project, or a BigQuery connection is pre-created with necessary IAM role set: - - 1. To create a connection, follow https://cloud.google.com/bigquery/docs/reference/standard-sql/remote-functions#create_a_connection - 2. To set up IAM, follow https://cloud.google.com/bigquery/docs/reference/standard-sql/remote-functions#grant_permission_on_function - - Alternatively, the IAM could also be setup via the gcloud CLI: - - `$ gcloud projects add-iam-policy-binding PROJECT_ID --member="serviceAccount:CONNECTION_SERVICE_ACCOUNT_ID" --role="roles/run.invoker"`. - - Args: - input_types (type or sequence(type), Optional): - For scalar user defined function it should be the input type or - sequence of input types. The supported scalar input types are - `bool`, `bytes`, `float`, `int`, `str`. For row processing user - defined function (i.e. functions that receive a single input - representing a row in form of a Series), type `Series` should be - specified. - output_type (type, Optional): - Data type of the output in the user defined function. If the - user defined function returns an array, then `list[type]` should - be specified. The supported output types are `bool`, `bytes`, - `float`, `int`, `str`, `list[bool]`, `list[float]`, `list[int]` - and `list[str]`. - dataset (str, Optional): - Dataset in which to create a BigQuery remote function. It should be in - `.` or `` format. If this - parameter is not provided then session dataset id is used. - bigquery_connection (str, Optional): - Name of the BigQuery connection in the form of `CONNECTION_ID` or - `LOCATION.CONNECTION_ID` or `PROJECT_ID.LOCATION.CONNECTION_ID`. - If this param is not provided then the bigquery connection from the session - would be used. If it is pre created in the same location as the - `bigquery_client.location` then it would be used, otherwise it is created - dynamically using the `bigquery_connection_client` assuming the user has necessary - priviliges. The PROJECT_ID should be the same as the BigQuery connection project. - reuse (bool, Optional): - Reuse the remote function if already exists. - `True` by default, which will result in reusing an existing remote - function and corresponding cloud function that was previously - created (if any) for the same udf. - Please note that for an unnamed (i.e. created without an explicit - `name` argument) remote function, the BigQuery DataFrames - session id is attached in the cloud artifacts names. So for the - effective reuse across the sessions it is recommended to create - the remote function with an explicit `name`. - Setting it to `False` would force creating a unique remote function. - If the required remote function does not exist then it would be - created irrespective of this param. - name (str, Optional): - Explicit name of the persisted BigQuery remote function. Use it with - caution, because two users working in the same project and dataset - could overwrite each other's remote functions if they use the same - persistent name. When an explicit name is provided, any session - specific clean up (``bigframes.session.Session.close``/ - ``bigframes.pandas.close_session``/ - ``bigframes.pandas.reset_session``/ - ``bigframes.pandas.clean_up_by_session_id``) does not clean up - the function, and leaves it for the user to manage the function - and the associated cloud function directly. - packages (str[], Optional): - Explicit name of the external package dependencies. Each dependency - is added to the `requirements.txt` as is, and can be of the form - supported in https://pip.pypa.io/en/stable/reference/requirements-file-format/. - cloud_function_service_account (str): - Service account to use for the cloud functions. If "default" provided then - the default service account would be used. See - https://cloud.google.com/functions/docs/securing/function-identity - for more details. Please make sure the service account has the - necessary IAM permissions configured as described in - https://cloud.google.com/functions/docs/reference/iam/roles#additional-configuration. - cloud_function_kms_key_name (str, Optional): - Customer managed encryption key to protect cloud functions and - related data at rest. This is of the format - projects/PROJECT_ID/locations/LOCATION/keyRings/KEYRING/cryptoKeys/KEY. - Read https://cloud.google.com/functions/docs/securing/cmek for - more details including granting necessary service accounts - access to the key. - cloud_function_docker_repository (str, Optional): - Docker repository created with the same encryption key as - `cloud_function_kms_key_name` to store encrypted artifacts - created to support the cloud function. This is of the format - projects/PROJECT_ID/locations/LOCATION/repositories/REPOSITORY_NAME. - For more details see - https://cloud.google.com/functions/docs/securing/cmek#before_you_begin. - max_batching_rows (int, Optional): - The maximum number of rows to be batched for processing in the - BQ remote function. Default value is 1000. A lower number can be - passed to avoid timeouts in case the user code is too complex to - process large number of rows fast enough. A higher number can be - used to increase throughput in case the user code is fast enough. - `None` can be passed to let BQ remote functions service apply - default batching. See for more details - https://cloud.google.com/bigquery/docs/remote-functions#limiting_number_of_rows_in_a_batch_request. - cloud_function_timeout (int, Optional): - The maximum amount of time (in seconds) BigQuery should wait for - the cloud function to return a response. See for more details - https://cloud.google.com/functions/docs/configuring/timeout. - Please note that even though the cloud function (2nd gen) itself - allows seeting up to 60 minutes of timeout, BigQuery remote - function can wait only up to 20 minutes, see for more details - https://cloud.google.com/bigquery/quotas#remote_function_limits. - By default BigQuery DataFrames uses a 10 minute timeout. `None` - can be passed to let the cloud functions default timeout take effect. - cloud_function_max_instances (int, Optional): - The maximumm instance count for the cloud function created. This - can be used to control how many cloud function instances can be - active at max at any given point of time. Lower setting can help - control the spike in the billing. Higher setting can help - support processing larger scale data. When not specified, cloud - function's default setting applies. For more details see - https://cloud.google.com/functions/docs/configuring/max-instances. - cloud_function_vpc_connector (str, Optional): - The VPC connector you would like to configure for your cloud - function. This is useful if your code needs access to data or - service(s) that are on a VPC network. See for more details - https://cloud.google.com/functions/docs/networking/connecting-vpc. - cloud_function_vpc_connector_egress_settings (str, Optional): - Egress settings for the VPC connector, controlling what outbound - traffic is routed through the VPC connector. - Options are: `all`, `private-ranges-only`, or `unspecified`. - If not specified, `private-ranges-only` is used by default. - See for more details - https://cloud.google.com/run/docs/configuring/vpc-connectors#egress-job. - cloud_function_memory_mib (int, Optional): - The amounts of memory (in mebibytes) to allocate for the cloud - function (2nd gen) created. This also dictates a corresponding - amount of allocated CPU for the function. By default a memory of - 1024 MiB is set for the cloud functions created to support - BigQuery DataFrames remote function. If you want to let the - default memory of cloud functions be allocated, pass `None`. See - for more details - https://cloud.google.com/functions/docs/configuring/memory. - cloud_function_cpus (float, Optional): - The number of cpus to allocate for the cloud - function (2nd gen) created. - https://docs.cloud.google.com/run/docs/configuring/services/cpu. - cloud_function_ingress_settings (str, Optional): - Ingress settings controls dictating what traffic can reach the - function. Options are: `all`, `internal-only`, or `internal-and-gclb`. - If no setting is provided, `internal-only` will be used by default. - See for more details - https://cloud.google.com/functions/docs/networking/network-settings#ingress_settings. - cloud_build_service_account (str, Optional): - Service account in the fully qualified format - `projects/PROJECT_ID/serviceAccounts/SERVICE_ACCOUNT_EMAIL`, or - just the SERVICE_ACCOUNT_EMAIL. The latter would be interpreted - as belonging to the BigQuery DataFrames session project. This is - to be used by Cloud Build to build the function source code into - a deployable artifact. If not provided, the default Cloud Build - service account is used. See - https://cloud.google.com/build/docs/cloud-build-service-account - for more details. - """ - # If the user forces the cloud function service argument to None, throw - # an exception - if cloud_function_service_account is None: - raise ValueError( - 'You must provide a user managed cloud_function_service_account, or "default" if you would like to let the default service account be used.' - ) - - # BQ remote function must be persisted, for which we need a dataset. - # https://cloud.google.com/bigquery/docs/reference/standard-sql/remote-functions#:~:text=You%20cannot%20create%20temporary%20remote%20functions. - dataset_ref = self._resolve_dataset_reference(dataset) - # A connection is required for BQ remote function. - # https://cloud.google.com/bigquery/docs/reference/standard-sql/remote-functions#create_a_remote_function - bq_connection_id = self._resolve_bigquery_connection_id( - dataset_ref, bigquery_connection - ) - - # If any CMEK is intended then check that a docker repository is also specified. - if ( - cloud_function_kms_key_name is not None - and cloud_function_docker_repository is None - ): - raise bf_formatting.create_exception_with_feedback_link( - ValueError, - "cloud_function_docker_repository must be specified with cloud_function_kms_key_name." - " For more details see https://cloud.google.com/functions/docs/securing/cmek#before_you_begin.", - ) - - # A VPC connector is required to specify VPC egress settings. - if ( - cloud_function_vpc_connector_egress_settings is not None - and cloud_function_vpc_connector is None - ): - raise bf_formatting.create_exception_with_feedback_link( - ValueError, - "cloud_function_vpc_connector must be specified before cloud_function_vpc_connector_egress_settings.", - ) - - if cloud_function_ingress_settings is None: - cloud_function_ingress_settings = "internal-only" - msg = bfe.format_message( - "The `cloud_function_ingress_settings` is being set to 'internal-only' by default." - ) - warnings.warn(msg, category=UserWarning, stacklevel=2) - - def wrapper(func): - nonlocal input_types, output_type - - ### Step 1: Validate inputs and package into cloud run function, remote function defs. ### - if not callable(func): - raise bf_formatting.create_exception_with_feedback_link( - TypeError, f"func must be a callable, got {func}" - ) - - udf_sig = _utils.get_func_signature( - func, - input_types, - output_type, - ).to_remote_function_compatible() - - full_package_requirements = _utils.get_updated_package_requirements( - packages or [], udf_sig.is_row_processor - ) - memory_mib = cloud_function_memory_mib or _DEFAULT_FUNCTION_MEMORY_MIB - - # assumption is most bigframes functions are cpu bound, single-threaded and many won't release GIL - # therefore, want to allocate a worker for each cpu, and allow a concurrent request per worker - expected_milli_cpus = ( - int(cloud_function_cpus * 1000) - if (cloud_function_cpus is not None) - else _infer_milli_cpus_from_memory(memory_mib) - ) - workers = -( - expected_milli_cpus // -1000 - ) # ceil(cpus) without invoking floats - threads = 4 # (per worker) - # max concurrency==1 for vcpus < 1 hard limit from cloud run - concurrency = (workers * threads) if (expected_milli_cpus >= 1000) else 1 - - ### Step 1: Create resources or fetch existing matching resources. ### - cloud_func_spec = udf_def.CloudRunFunctionConfig( - code=udf_def.CodeDef.from_func(func, full_package_requirements), - signature=udf_sig, - timeout_seconds=cloud_function_timeout, - max_instance_count=cloud_function_max_instances, - vpc_connector=cloud_function_vpc_connector, - vpc_connector_egress_settings=cloud_function_vpc_connector_egress_settings - or "private-ranges-only", - memory_mib=memory_mib, - cpus=cloud_function_cpus, - ingress_settings=cloud_function_ingress_settings, - workers=workers, - threads=threads, - concurrency=concurrency, - kms_key_name=cloud_function_kms_key_name, - docker_repository=cloud_function_docker_repository, - cloud_build_service_account=cloud_build_service_account, - cloud_run_service_account=( - None - if (cloud_function_service_account == "default") - else cloud_function_service_account - ), - ) - uniq_suffix = None - if not reuse: - uniq_suffix = "".join( - random.choices(string.ascii_lowercase + string.digits, k=4) - ) - cf_name = get_cloud_function_name( - cloud_func_spec, - # only session scope a temp unnamed function - session_id=self.session_id if (name is None) else None, - uniq_suffix=uniq_suffix, - ) - if not name: - self._add_temp_cloud_function(cf_name) - - # Create remote function that points at the cloud function - cf_endpoint = None - if reuse is not None: - cf_endpoint = self._function_client.get_cloud_function_endpoint(cf_name) - - # If the endpoint is empty, the function might exist but the URL propagation is pending. - # Running create_cloud_function will handle AlreadyExists and retry endpoint fetching. - if not cf_endpoint: - cf_endpoint = self._function_client.create_cloud_function( - cf_name, cloud_func_spec - ) - else: - logger.info(f"Cloud function {cf_name} already exists.") - - remote_function_config = udf_def.RemoteFunctionConfig( - endpoint=cf_endpoint, - connection_id=bq_connection_id, - max_batching_rows=max_batching_rows or 1000, - signature=udf_sig, - bq_metadata=udf_sig.protocol_metadata, - ) - remote_function_name = name or get_bigframes_function_name( - remote_function_config, - session_id=self.session_id, - uniq_suffix=uniq_suffix, - ) - routine_ref = self._resolve_routine_reference( - remote_function_name, dataset=dataset_ref - ) - if not name: - self._add_temp_remote_function(routine_ref) - - self._function_client.create_bq_remote_function( - udf_def=remote_function_config, - routine_ref=routine_ref, - maybe_reuse=reuse, - try_create_connection=self._manage_connections, - ) - - udf_definition = udf_def.BigqueryUdf( - routine_ref=routine_ref, - signature=udf_sig, - ) - decorator = functools.wraps(func) - if udf_sig.is_row_processor: - msg = bfe.format_message("input_types=Series is in preview.") - warnings.warn(msg, stacklevel=1, category=bfe.PreviewWarning) - - cf_full_path = ( - self._function_client.get_cloud_function_fully_qualified_name(cf_name) - ) - return decorator( - bq_functions.BigqueryCallableRoutine( - udf_definition, - self._function_client._bq_client, - cloud_function_ref=cf_full_path, - local_func=func, - is_managed=False, - ) - ) - - return wrapper - - def deploy_remote_function( - self, - func, - **kwargs, - ): - """Orchestrates the creation of a BigQuery remote function that deploys immediately. - - This method ensures that the remote function is created and available for - use in BigQuery as soon as this call is made. - - Args: - kwargs: - All arguments are passed directly to - :meth:`~bigframes.session.Session.remote_function`. Please see - its docstring for parameter details. - - Returns: - A wrapped remote function, usable in - :meth:`~bigframes.series.Series.apply`. - """ - # TODO(tswast): If we update remote_function to defer deployment, update - # this method to deploy immediately. - return self.remote_function(**kwargs)(func) - - def udf( - self, - input_types: type | Sequence[type] | None = None, - output_type: type | None = None, - dataset: str | None = None, - bigquery_connection: str | None = None, - name: str | None = None, - packages: Sequence[str] | None = None, - max_batching_rows: int | None = None, - container_cpu: Optional[float] = None, - container_memory: Optional[str] = None, - *, - _force_deploy: bool = False, - ): - """Decorator to turn a Python user defined function (udf) into a - BigQuery managed function. - - .. note:: - This feature is in preview. The code in the udf must be - (1) self-contained, i.e. it must not contain any - references to an import or variable defined outside the function - body, and - (2) Python 3.11 compatible, as that is the environment - in which the code is executed in the cloud. - - .. note:: - Please have following IAM roles enabled for you: - - * BigQuery Data Editor (roles/bigquery.dataEditor) - - Args: - input_types (type or sequence(type), Optional): - For scalar user defined function it should be the input type or - sequence of input types. The supported scalar input types are - `bool`, `bytes`, `float`, `int`, `str`. - output_type (type, Optional): - Data type of the output in the user defined function. If the - user defined function returns an array, then `list[type]` should - be specified. The supported output types are `bool`, `bytes`, - `float`, `int`, `str`, `list[bool]`, `list[float]`, `list[int]` - and `list[str]`. - dataset (str, Optional): - Dataset in which to create a BigQuery managed function. It - should be in `.` or `` - format. If this parameter is not provided then session dataset - id is used. - bigquery_connection (str, Optional): - Name of the BigQuery connection. It is used to provide an - identity to the serverless instances running the user code. It - helps BigQuery manage and track the resources used by the udf. - This connection is required for internet access and for - interacting with other GCP services. To access GCP services, the - appropriate IAM permissions must also be granted to the - connection's Service Account. When it defaults to None, the udf - will be created without any connection. A udf without a - connection has no internet access and no access to other GCP - services. - name (str, Optional): - Explicit name of the persisted BigQuery managed function. Use it - with caution, because more than one users working in the same - project and dataset could overwrite each other's managed - functions if they use the same persistent name. When an explicit - name is provided, any session specific clean up ( - ``bigframes.session.Session.close``/ - ``bigframes.pandas.close_session``/ - ``bigframes.pandas.reset_session``/ - ``bigframes.pandas.clean_up_by_session_id``) does not clean up - the function, and leaves it for the user to manage the function - directly. - packages (str[], Optional): - Explicit name of the external package dependencies. Each - dependency is added to the `requirements.txt` as is, and can be - of the form supported in - https://pip.pypa.io/en/stable/reference/requirements-file-format/. - max_batching_rows (int, Optional): - The maximum number of rows in each batch. If you specify - max_batching_rows, BigQuery determines the number of rows in a - batch, up to the max_batching_rows limit. If max_batching_rows - is not specified, the number of rows to batch is determined - automatically. - container_cpu (float, Optional): - The CPU limits for containers that run Python UDFs. By default, - the CPU allocated is 0.33 vCPU. See details at - https://cloud.google.com/bigquery/docs/user-defined-functions-python#configure-container-limits. - container_memory (str, Optional): - The memory limits for containers that run Python UDFs. By - default, the memory allocated to each container instance is - 512 MiB. See details at - https://cloud.google.com/bigquery/docs/user-defined-functions-python#configure-container-limits. - """ - - warnings.warn("udf is in preview.", category=bfe.PreviewWarning, stacklevel=5) - # BQ managed function must be persisted, for which we need a dataset. - dataset_ref = self._resolve_dataset_reference(dataset) - - # A connection is optional for BQ managed function. - bq_connection_id = ( - self._resolve_bigquery_connection_id(dataset_ref, bigquery_connection) - if bigquery_connection - else None - ) - - # TODO(b/399129906): Write a method for the repeated part in the wrapper - # for both managed function and remote function. - def wrapper(func): - nonlocal input_types, output_type - - if not callable(func): - raise bf_formatting.create_exception_with_feedback_link( - TypeError, f"func must be a callable, got {func}" - ) - - udf_sig = _utils.get_func_signature( - func, - input_types, - output_type, - ) - - code_def = udf_def.CodeDef.from_func(func, package_requirements=packages) - requirements = udf_def.RuntimeRequirements( - container_cpu=container_cpu, - container_memory=container_memory, - bq_connection_id=bq_connection_id, - max_batching_rows=max_batching_rows, - packages=tuple(packages) if packages else (), - ) - if udf_sig.is_row_processor: - msg = bfe.format_message("input_types=Series is in preview.") - warnings.warn(msg, stacklevel=1, category=bfe.PreviewWarning) - - if ( - not name and not dataset and not _force_deploy - ): # session-owned resource - deferred deployment - udf_definition = udf_def.PythonUdf( - signature=udf_sig, - code=code_def, - requirements=requirements, - ) - return bq_functions.UdfRoutine(func=func, _udf_def=udf_definition) - else: # deploy immediately - config = udf_def.ManagedFunctionConfig( - code=code_def, - signature=udf_sig, - max_batching_rows=max_batching_rows, - container_cpu=container_cpu, - container_memory=container_memory, - bq_connection_id=bq_connection_id, - capture_references=False, - ) - function_name = name or get_managed_function_name( - config, self.session_id - ) - rf_def = self._deploy_managed_function( - config, - name=function_name, - temp=(name is None), - dataset=dataset_ref, - ) - return bq_functions.BigqueryCallableRoutine( - rf_def, - self._function_client._bq_client, - local_func=func, - is_managed=True, - ) - - return wrapper - - def deploy_udf( - self, - func, - **kwargs, - ): - """Orchestrates the creation of a BigQuery UDF that deploys immediately. - - This method ensures that the UDF is created and available for - use in BigQuery as soon as this call is made. - - Args: - func: - Function to deploy. - kwargs: - All arguments are passed directly to - :meth:`~bigframes.session.Session.udf`. Please see - its docstring for parameter details. - - Returns: - A wrapped Python user defined function, usable in - :meth:`~bigframes.series.Series.apply`. - """ - return self.udf(_force_deploy=True, **kwargs)(func) - - -def get_cloud_function_name( - function_def: udf_def.CloudRunFunctionConfig, session_id=None, uniq_suffix=False -): - """ - Get a name for the cloud function for the given user defined function. - - If make_unique is True, append a random suffix to the name. - """ - parts = [_BIGFRAMES_FUNCTION_PREFIX] - if session_id: - parts.append(session_id) - parts.append(function_def.stable_hash().hex()) - if uniq_suffix: - parts.append(uniq_suffix) - return _GCF_FUNCTION_NAME_SEPERATOR.join(parts) - - -def get_bigframes_function_name( - function: udf_def.RemoteFunctionConfig, session_id, uniq_suffix=None -): - """Get a name for the bigframes function for the given user defined function.""" - parts = [_BIGFRAMES_FUNCTION_PREFIX, session_id, function.stable_hash().hex()] - if uniq_suffix: - parts.append(uniq_suffix) - return _BQ_FUNCTION_NAME_SEPERATOR.join(parts) - - -def get_managed_function_name( - function_def: udf_def.ManagedFunctionConfig, - session_id: str | None = None, -): - """Get a name for the bigframes managed function for the given user defined function.""" - parts = [_BIGFRAMES_FUNCTION_PREFIX] - if session_id: - parts.append(session_id) - parts.append(function_def.stable_hash().hex()) - return _BQ_FUNCTION_NAME_SEPERATOR.join(parts) - - -def _infer_milli_cpus_from_memory(memory_mib: int) -> int: - # observed values, not formally documented by cloud run functions - if memory_mib < 128: - raise ValueError("Cloud run supports at minimum 128MiB per instance") - elif memory_mib == 128: - return 83 - elif memory_mib <= 256: - return 167 - elif memory_mib <= 512: - return 333 - elif memory_mib <= 1024: - return 583 - elif memory_mib <= 2048: - return 1000 - elif memory_mib <= 8192: - return 2000 - elif memory_mib <= 16384: - return 4000 - elif memory_mib <= 32768: - return 8000 - else: - raise ValueError("Cloud run supports at most 32768MiB per instance") diff --git a/bigframes/functions/_utils.py b/bigframes/functions/_utils.py deleted file mode 100644 index 358f20b2ab4..00000000000 --- a/bigframes/functions/_utils.py +++ /dev/null @@ -1,358 +0,0 @@ -# Copyright 2023 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 collections -import hashlib -import inspect -import json -import sys -import typing -import warnings -from typing import Any, Mapping, Optional, Sequence, Set, cast - -import cloudpickle -import google.api_core.exceptions -import numpy -import pandas -import pyarrow -from google.cloud import bigquery, functions_v2 -from packaging.requirements import Requirement - -import bigframes.exceptions as bfe -import bigframes.formatting_helpers as bf_formatting -from bigframes.functions import function_typing, udf_def - -# Naming convention for the function artifacts -_BIGFRAMES_FUNCTION_PREFIX = "bigframes" -_BQ_FUNCTION_NAME_SEPERATOR = "_" -_GCF_FUNCTION_NAME_SEPERATOR = "-" - -# Protocol version 4 is available in python version 3.4 and above -# https://docs.python.org/3/library/pickle.html#data-stream-format -_pickle_protocol_version = 4 - - -def gcf_location_from_bq_location(bq_location: str) -> str: - """Get the cloud functions region that corresponds to a BQ location.""" - bq_location = bq_location.lower() - - # BigQuery has multi region but cloud functions does not. - # Any region in the multi region that supports cloud functions should work - # https://cloud.google.com/functions/docs/locations - if bq_location == "us": - return "us-central1" - elif bq_location == "eu": - return "europe-west1" - - return bq_location - - -def _package_existed(package_requirements: list[str], package: str) -> bool: - """Checks if a package (regardless of version) exists in a given list.""" - if not package_requirements: - return False - - return Requirement(package).name in { - Requirement(req).name for req in package_requirements - } - - -def get_updated_package_requirements( - package_requirements: Sequence[str] = (), - is_row_processor: bool = False, - capture_references: bool = True, - ignore_package_version: bool = False, -) -> Sequence[str]: - requirements: list[str] = [] - if capture_references: - requirements.append(f"cloudpickle=={cloudpickle.__version__}") - - if is_row_processor: - if ignore_package_version: - # TODO(jialuo): Add back the version after b/410924784 is resolved. - # Due to current limitations on the packages version in Python UDFs, - # we use `ignore_package_version` to optionally omit the version for - # managed functions only. - msg = bfe.format_message( - "numpy, pandas, and pyarrow versions in the function execution" - " environment may not precisely match your local environment." - ) - warnings.warn(msg, category=bfe.FunctionPackageVersionWarning) - requirements.append("pandas") - requirements.append("pyarrow") - requirements.append("numpy") - else: - # bigframes function will send an entire row of data as json, which - # would be converted to a pandas series and processed Ensure numpy - # versions match to avoid unpickling problems. See internal issue - # b/347934471. - requirements.append(f"pandas=={pandas.__version__}") - requirements.append(f"pyarrow=={pyarrow.__version__}") - requirements.append(f"numpy=={numpy.__version__}") - - if not requirements: - return list(package_requirements) - - result = list(package_requirements) - for package in requirements: - if not _package_existed(result, package): - result.append(package) - - return sorted(result) - - -def clean_up_by_session_id( - bqclient: bigquery.Client, - gcfclient: functions_v2.FunctionServiceClient, - dataset: bigquery.DatasetReference, - session_id: str, -): - """Delete remote function artifacts for a session id, where the session id - was not necessarily created in the current runtime. This is useful if the - user worked with a BigQuery DataFrames session previously and remembered the - session id, and now wants to clean up its temporary resources at a later - point in time. - """ - - # First clean up the BQ remote functions and then the underlying cloud - # functions, so that at no point we are left with a remote function that is - # pointing to a cloud function that does not exist - - endpoints_to_be_deleted: Set[str] = set() - match_prefix = "".join( - [ - _BIGFRAMES_FUNCTION_PREFIX, - _BQ_FUNCTION_NAME_SEPERATOR, - session_id, - _BQ_FUNCTION_NAME_SEPERATOR, - ] - ) - for routine in bqclient.list_routines(dataset): - routine = cast(bigquery.Routine, routine) - - # skip past the routines not belonging to the given session id, or - # non-remote-function routines - if ( - routine.type_ != bigquery.RoutineType.SCALAR_FUNCTION - or not cast(str, routine.routine_id).startswith(match_prefix) - or not routine.remote_function_options - or not routine.remote_function_options.endpoint - ): - continue - - # Let's forgive the edge case possibility that the BQ remote function - # may have been deleted at the same time directly by the user - bqclient.delete_routine(routine, not_found_ok=True) - endpoints_to_be_deleted.add(routine.remote_function_options.endpoint) - - # Now clean up the cloud functions - bq_location = bqclient.get_dataset(dataset).location - gcf_location = gcf_location_from_bq_location(bq_location) - parent_path = gcfclient.common_location_path( - project=dataset.project, location=gcf_location - ) - for gcf in gcfclient.list_functions(parent=parent_path): - # skip past the cloud functions not attached to any BQ remote function - # belonging to the given session id - if gcf.service_config.uri not in endpoints_to_be_deleted: - continue - - # Let's forgive the edge case possibility that the cloud function - # may have been deleted at the same time directly by the user - try: - gcfclient.delete_function(name=gcf.name) - except google.api_core.exceptions.NotFound: - pass - - -def routine_ref_to_string_for_query(routine_ref: bigquery.RoutineReference) -> str: - return f"`{routine_ref.project}.{routine_ref.dataset_id}`.{routine_ref.routine_id}" - - -# Deprecated: Use CodeDef.stable_hash() instead. -def get_hash(def_, package_requirements=None): - "Get hash (32 digits alphanumeric) of a function." - # There is a known cell-id sensitivity of the cloudpickle serialization in - # notebooks https://github.com/cloudpipe/cloudpickle/issues/538. Because of - # this, if a cell contains a udf decorated with @remote_function, a unique - # cloudpickle code is generated every time the cell is run, creating new - # cloud artifacts every time. This is slow and wasteful. - # A workaround of the same can be achieved by replacing the filename in the - # code object to a static value - # https://github.com/cloudpipe/cloudpickle/issues/120#issuecomment-338510661. - # - # To respect the user code/environment let's make this modification on a - # copy of the udf, not on the original udf itself. - def_copy = cloudpickle.loads(cloudpickle.dumps(def_)) - def_copy.__code__ = def_copy.__code__.replace( - co_filename="bigframes_place_holder_filename" - ) - - def_repr = cloudpickle.dumps(def_copy, protocol=_pickle_protocol_version) - if package_requirements: - for p in sorted(package_requirements): - def_repr += p.encode() - return hashlib.md5(def_repr).hexdigest() - - -def get_python_output_type_str_from_bigframes_metadata( - metadata_text: str, -) -> Optional[str]: - try: - metadata_dict = json.loads(metadata_text) - except (TypeError, json.decoder.JSONDecodeError): - return None - try: - return metadata_dict["value"]["python_array_output_type"] - except KeyError: - return None - - -def get_python_output_type_from_bigframes_metadata( - metadata_text: str, -) -> Optional[type]: - output_type_str = get_python_output_type_str_from_bigframes_metadata(metadata_text) - - for ( - python_output_array_type - ) in function_typing.RF_SUPPORTED_ARRAY_OUTPUT_PYTHON_TYPES: - if python_output_array_type.__name__ == output_type_str: - return list[python_output_array_type] # type: ignore - - return None - - -def get_bigframes_metadata(*, python_output_type: Optional[type] = None) -> str: - # Let's keep the actual metadata inside one level of nesting so that in - # future we can use a top level key "version" (parallel to "value"), based - # on which "value" can be interpreted according to the "version". The - # absence of "version" should be interpreted as default version. - inner_metadata = {} - if typing.get_origin(python_output_type) is list: - python_output_array_type = typing.get_args(python_output_type)[0] - if ( - python_output_array_type - in function_typing.RF_SUPPORTED_ARRAY_OUTPUT_PYTHON_TYPES - ): - inner_metadata["python_array_output_type"] = ( - python_output_array_type.__name__ - ) - - metadata = {"value": inner_metadata} - metadata_ser = json.dumps(metadata) - - # let's make sure the serialized value is deserializable - if ( - get_python_output_type_from_bigframes_metadata(metadata_ser) - != python_output_type - ): - raise bf_formatting.create_exception_with_feedback_link( - ValueError, f"python_output_type {python_output_type} is not serializable." - ) - - return metadata_ser - - -def get_python_version(is_compat: bool = False) -> str: - # Cloud Run functions use the 'compat' format (e.g., python311, see more - # from https://cloud.google.com/functions/docs/runtime-support#python), - # while managed functions use the standard format (e.g., python-3.11). - major = sys.version_info.major - minor = sys.version_info.minor - return f"python{major}{minor}" if is_compat else f"python-{major}.{minor}" - - -def has_conflict_input_type( - signature: inspect.Signature, - input_types: Sequence[Any], -) -> bool: - """Checks if the parameters have any conflict with the input_types.""" - params = list(signature.parameters.values()) - - if len(params) != len(input_types): - return True - - # Check for conflicts type hints. - for i, param in enumerate(params): - if param.annotation is not inspect.Parameter.empty: - if param.annotation != input_types[i]: - return True - - # No conflicts were found after checking all parameters. - return False - - -def has_conflict_output_type( - signature: inspect.Signature, - output_type: Any, -) -> bool: - """Checks if the return type annotation conflicts with the output_type.""" - return_annotation = signature.return_annotation - - if return_annotation is inspect.Parameter.empty: - return False - - return return_annotation != output_type - - -def get_func_signature( - func, - input_types: type | Sequence[type] | None = None, - output_type: type | None = None, -) -> udf_def.UdfSignature: - if sys.version_info >= (3, 10): - # Add `eval_str = True` so that deferred annotations are turned into their - # corresponding type objects. Need Python 3.10 for eval_str parameter. - # https://docs.python.org/3/library/inspect.html#inspect.signature - signature_kwargs: Mapping[str, Any] = {"eval_str": True} - else: - signature_kwargs = {} # type: ignore - - py_sig = resolve_signature( - inspect.signature(func, **signature_kwargs), - input_types, - output_type, - ) - return udf_def.UdfSignature.from_py_signature(py_sig) - - -def resolve_signature( - py_sig: inspect.Signature, - input_types: type | Sequence[type] | None = None, - output_type: type | None = None, -) -> inspect.Signature: - if input_types is not None: - if not isinstance(input_types, collections.abc.Sequence): - input_types = [input_types] - if has_conflict_input_type(py_sig, input_types): - msg = bfe.format_message( - "Conflicting input types detected, using the one from the decorator." - ) - warnings.warn(msg, category=bfe.FunctionConflictTypeHintWarning) - py_sig = py_sig.replace( - parameters=[ - par.replace(annotation=itype) - for par, itype in zip(py_sig.parameters.values(), input_types) - ] - ) - if output_type: - if has_conflict_output_type(py_sig, output_type): - msg = bfe.format_message( - "Conflicting return type detected, using the one from the decorator." - ) - warnings.warn(msg, category=bfe.FunctionConflictTypeHintWarning) - py_sig = py_sig.replace(return_annotation=output_type) - - return py_sig diff --git a/bigframes/functions/function.py b/bigframes/functions/function.py deleted file mode 100644 index b3a56dafcef..00000000000 --- a/bigframes/functions/function.py +++ /dev/null @@ -1,253 +0,0 @@ -# Copyright 2023 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from __future__ import annotations - -import dataclasses -import logging -from typing import TYPE_CHECKING, Callable, Optional, Protocol, Union, runtime_checkable - -import google.api_core.exceptions -from google.cloud import bigquery - -import bigframes.formatting_helpers as bf_formatting -from bigframes.functions import _function_session as bff_session -from bigframes.functions import function_typing, udf_def - -if TYPE_CHECKING: - from bigframes.session import Session - -logger = logging.getLogger(__name__) - - -class UnsupportedTypeError(ValueError): - def __init__(self, type_, supported_types): - self.type = type_ - self.supported_types = supported_types - - -class DatasetMissingError(ValueError): - pass - - -def get_routine_reference( - routine_ref_str: str, bigquery_client: bigquery.Client, session: Optional[Session] -) -> bigquery.RoutineReference: - try: - # Handle cases ".." and - # ".". - return bigquery.RoutineReference.from_string( - routine_ref_str, - default_project=bigquery_client.project, - ) - except ValueError: - # Handle case of "". - if not session: - raise DatasetMissingError - - dataset_ref = bigquery.DatasetReference( - bigquery_client.project, session._anonymous_dataset.dataset_id - ) - return dataset_ref.routine(routine_ref_str) - - -def remote_function(*args, **kwargs): - import bigframes - - function_session = bigframes.get_global_session()._function_session - return function_session.remote_function(*args, **kwargs) - - -remote_function.__doc__ = bff_session.FunctionSession.remote_function.__doc__ - - -def udf(*args, **kwargs): - import bigframes - - function_session = bigframes.get_global_session()._function_session - return function_session.udf(*args, **kwargs) - - -udf.__doc__ = bff_session.FunctionSession.udf.__doc__ - - -def _try_import_routine( - routine: bigquery.Routine, bq_client: bigquery.Client -) -> BigqueryCallableRoutine: - udf_def = _routine_as_udf_def(routine) - is_remote = ( - hasattr(routine, "remote_function_options") and routine.remote_function_options - ) - return BigqueryCallableRoutine(udf_def, bq_client, is_managed=not is_remote) - - -def _try_import_row_routine( - routine: bigquery.Routine, bq_client: bigquery.Client -) -> BigqueryCallableRoutine: - udf_def = _routine_as_udf_def(routine, is_row_processor=True) - - is_remote = ( - hasattr(routine, "remote_function_options") and routine.remote_function_options - ) - return BigqueryCallableRoutine(udf_def, bq_client, is_managed=not is_remote) - - -def _routine_as_udf_def( - routine: bigquery.Routine, is_row_processor: bool = False -) -> udf_def.BigqueryUdf: - try: - return udf_def.BigqueryUdf.from_routine( - routine, is_row_processor=is_row_processor - ) - except udf_def.ReturnTypeMissingError: - raise bf_formatting.create_exception_with_feedback_link( - ValueError, "Function return type must be specified." - ) - except function_typing.UnsupportedTypeError as e: - raise bf_formatting.create_exception_with_feedback_link( - ValueError, - f"Type {e.type} not supported, supported types are {e.supported_types}.", - ) - - -def read_gbq_function( - function_name: str, - *, - session: Session, - is_row_processor: bool = False, -): - """ - Read an existing BigQuery function and prepare it for use in future queries. - """ - bigquery_client = session.bqclient - - try: - routine_ref = get_routine_reference(function_name, bigquery_client, session) - except DatasetMissingError: - raise bf_formatting.create_exception_with_feedback_link( - ValueError, - "Project and dataset must be provided, either directly or via session.", - ) - - # Find the routine and get its arguments. - try: - routine = bigquery_client.get_routine(routine_ref) - except google.api_core.exceptions.NotFound: - raise bf_formatting.create_exception_with_feedback_link( - ValueError, f"Unknown function '{routine_ref}'." - ) - - # TODO(493293086): Deprecate is_row_processor. - if is_row_processor: - return _try_import_row_routine(routine, bigquery_client) - else: - return _try_import_routine(routine, bigquery_client) - - -@runtime_checkable -class Udf(Protocol): - """ - Protocol for all BigFrames user-defined functions. - - Has @runtime_checkable so functions like df.apply() can dispatch UDFs with isinstance() checks. - """ - - @property - def udf_def(self) -> Union[udf_def.BigqueryUdf, udf_def.PythonUdf]: ... - - -class BigqueryCallableRoutine: - """ - A reference to a routine in the context of a session. - - Can be used both directly as a callable, or as an input to dataframe ops that take a callable. - """ - - def __init__( - self, - udf_def: udf_def.BigqueryUdf, - bq_client: bigquery.Client, - *, - local_func: Optional[Callable] = None, - cloud_function_ref: Optional[str] = None, - is_managed: bool = False, - ): - self._udf_def = udf_def - self._bq_client = bq_client - self._local_fun = local_func - self._cloud_function = cloud_function_ref - self._is_managed = is_managed - - def __call__(self, *args, **kwargs): - if self._local_fun: - return self._local_fun(*args, **kwargs) - # avoid circular imports - import bigframes.session._io.bigquery as bf_io_bigquery - from bigframes.core.compile.sqlglot import sql as sg_sql - - args_string = ", ".join([sg_sql.to_sql(sg_sql.literal(v)) for v in args]) - sql = f"SELECT `{str(self._udf_def.routine_ref)}`({args_string})" - row_iterator = bf_io_bigquery.start_query_job_optional( - self._bq_client, - sql=sql, - job_config=bigquery.QueryJobConfig(), - ) # type: ignore - return list(row_iterator.to_arrow().to_pydict().values())[0][0] - - @property - def bigframes_bigquery_function(self) -> str: - return str(self._udf_def.routine_ref) - - @property - def bigframes_remote_function(self): - return None if self._is_managed else str(self._udf_def.routine_ref) - - @property - def is_row_processor(self) -> bool: - return self.udf_def.signature.is_row_processor - - @property - def udf_def(self) -> udf_def.BigqueryUdf: - return self._udf_def - - @property - def bigframes_cloud_function(self) -> Optional[str]: - return self._cloud_function - - @property - def input_dtypes(self): - return tuple(arg.bf_type for arg in self.udf_def.signature.inputs) - - @property - def output_dtype(self): - return self.udf_def.signature.output.bf_type - - @property - def bigframes_bigquery_function_output_dtype(self): - return self.udf_def.signature.output.emulating_type.bf_type - - -@dataclasses.dataclass(frozen=True) -class UdfRoutine: - func: Callable - # Try not to depend on this, bq managed function creation will be deferred later - # And this ref will be replaced with requirements rather to support lazy creation - _udf_def: Union[udf_def.BigqueryUdf, udf_def.PythonUdf] - - def __call__(self, *args, **kwargs): - return self.func(*args, **kwargs) - - @property - def udf_def(self) -> Union[udf_def.BigqueryUdf, udf_def.PythonUdf]: - return self._udf_def diff --git a/bigframes/functions/function_template.py b/bigframes/functions/function_template.py deleted file mode 100644 index 598de7c853d..00000000000 --- a/bigframes/functions/function_template.py +++ /dev/null @@ -1,386 +0,0 @@ -# Copyright 2023 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 inspect -import logging -import os -import re -import textwrap - -from bigframes.functions import udf_def - -logger = logging.getLogger(__name__) - - -# Placeholder variables for testing. -input_types = ("STRING",) -output_type = "STRING" - - -# Convert inputs to BigQuery JSON. See: -# https://cloud.google.com/bigquery/docs/remote-functions#json_encoding_of_sql_data_type -# and -# https://cloud.google.com/bigquery/docs/reference/standard-sql/json_functions#to_json_string -def convert_call(input_types, call): - for type_, arg in zip(input_types, call): - yield convert_from_bq_json(type_, arg) - - -def convert_from_bq_json(type_, arg): - import base64 - import collections - - converters = collections.defaultdict(lambda: lambda value: value) # type: ignore - converters["BYTES"] = base64.b64decode - converter = converters[type_] - return converter(arg) if arg is not None else None - - -def convert_to_bq_json(type_, arg): - import base64 - import collections - - converters = collections.defaultdict(lambda: lambda value: value) # type: ignore - converters["BYTES"] = lambda value: base64.b64encode(value).decode("utf-8") - converter = converters[type_] - return converter(arg) if arg is not None else None - - -# get_pd_series is the inverse of Block._get_rows_as_json_values -# NOTE: Keep in sync with the list of supported types in DataFrame.apply. -def get_pd_series(row): - import ast - import base64 - import json - from typing import Callable, cast - - import pandas as pd - - row_json = json.loads(row) - col_names = row_json["names"] - col_types = row_json["types"] - col_values = row_json["values"] - index_length = row_json["indexlength"] - dtype = row_json["dtype"] - - # At this point we are assuming that col_names, col_types and col_values are - # arrays of the same length, representing column names, types and values for - # one row of data - - # column names are not necessarily strings - # they are serialized as repr(name) at source - evaluated_col_names = [] - for col_name in col_names: - try: - col_name = ast.literal_eval(col_name) - except Exception as ex: - raise NameError(f"Failed to evaluate column name from '{col_name}': {ex}") - evaluated_col_names.append(col_name) - col_names = evaluated_col_names - - # Supported converters for pandas to python types - value_converters = { - "boolean": lambda val: val == "true", - "Int64": int, - "Float64": float, - "string": str, - "binary[pyarrow]": base64.b64decode, - } - - def convert_value(value, value_type): - value_converter = cast(Callable, value_converters.get(value_type)) - if value_converter is None: - raise ValueError(f"Don't know how to handle type '{value_type}'") - if value is None: - return None - return value_converter(value) - - index_values = [ - pd.Series([convert_value(col_values[i], col_types[i])], dtype=col_types[i])[0] - for i in range(index_length) - ] - - data_col_names = col_names[index_length:] - data_col_types = col_types[index_length:] - data_col_values = col_values[index_length:] - data_col_values = [ - pd.Series([convert_value(a, data_col_types[i])], dtype=data_col_types[i])[0] - for i, a in enumerate(data_col_values) - ] - - row_index = index_values[0] if len(index_values) == 1 else tuple(index_values) - row_series = pd.Series( - data_col_values, index=data_col_names, name=row_index, dtype=dtype - ) - return row_series - - -def udf(*args): - """Dummy function to use as a placeholder for function code in templates.""" - pass - - -# We want to build a cloud function that works for BQ remote functions, -# where we receive `calls` in json which is a batch of rows from BQ SQL. -# The number and the order of values in each row is expected to exactly -# match to the number and order of arguments in the udf , e.g. if the udf is -# def foo(x: int, y: str): -# ... -# then the http request body could look like -# { -# ... -# "calls" : [ -# [123, "hello"], -# [456, "world"] -# ] -# ... -# } -# https://cloud.google.com/bigquery/docs/reference/standard-sql/remote-functions#input_format -def udf_http(request): - global input_types, output_type - import json - import traceback - - from flask import jsonify - - try: - request_json = request.get_json(silent=True) - calls = request_json["calls"] - replies = [] - for call in calls: - reply = convert_to_bq_json( - output_type, udf(*convert_call(input_types, call)) - ) - if type(reply) is list: - # Since the BQ remote function does not support array yet, - # return a json serialized version of the reply - reply = json.dumps(reply) - replies.append(reply) - return_json = json.dumps({"replies": replies}) - return return_json - except Exception: - return jsonify({"errorMessage": traceback.format_exc()}), 400 - - -def udf_http_row_processor(request): - global output_type - import json - import math - import traceback - - import pandas as pd - from flask import jsonify - - try: - request_json = request.get_json(silent=True) - calls = request_json["calls"] - replies = [] - for call in calls: - reply = convert_to_bq_json( - output_type, udf(get_pd_series(call[0]), *call[1:]) - ) - if type(reply) is list: - # Since the BQ remote function does not support array yet, - # return a json serialized version of the reply. - # Numpy types are not json serializable, so use their Python - # values instead. - reply = [val.item() if hasattr(val, "item") else val for val in reply] - reply = json.dumps(reply) - elif isinstance(reply, float) and (math.isnan(reply) or math.isinf(reply)): - # Json serialization of the special float values (nan, inf, -inf) - # is not in strict compliance of the JSON specification - # https://docs.python.org/3/library/json.html#basic-usage. - # Let's convert them to a quoted string representation ("NaN", - # "Infinity", "-Infinity" respectively) which is handled by - # BigQuery - reply = json.dumps(reply) - elif pd.isna(reply): - # Pandas N/A values are not json serializable, so use a python - # equivalent instead - reply = None - elif hasattr(reply, "item"): - # Numpy types are not json serializable, so use its Python - # value instead - reply = reply.item() - replies.append(reply) - return_json = json.dumps({"replies": replies}) - return return_json - except Exception: - return jsonify({"errorMessage": traceback.format_exc()}), 400 - - -def generate_udf_code(code_def: udf_def.CodeDef, directory: str): - """Generate serialized code using cloudpickle given a udf.""" - udf_code_file_name = "udf.py" - udf_pickle_file_name = "udf.cloudpickle" - - # original code, only for debugging purpose - if code_def.function_source: - udf_code_file_path = os.path.join(directory, udf_code_file_name) - with open(udf_code_file_path, "w") as f: - f.write(code_def.function_source) - - # serialized udf - udf_pickle_file_path = os.path.join(directory, udf_pickle_file_name) - # TODO(b/345433300): try io.BytesIO to avoid writing to the file system - with open(udf_pickle_file_path, "wb") as f: - f.write(code_def.pickled_code) - - return udf_code_file_name, udf_pickle_file_name - - -def generate_cloud_function_main_code( - code_def: udf_def.CodeDef, - directory: str, - *, - udf_signature: udf_def.UdfSignature, -): - """Get main.py code for the cloud function for the given user defined function.""" - - # Pickle the udf with all its dependencies - udf_code_file, udf_pickle_file = generate_udf_code(code_def, directory) - - input_types = tuple(arg.sql_type for arg in udf_signature.inputs) - output_type = udf_signature.output.sql_type - - code_blocks = [ - f"""\ -import cloudpickle - -# original udf code is in {udf_code_file} -# serialized udf code is in {udf_pickle_file} -with open("{udf_pickle_file}", "rb") as f: - udf = cloudpickle.load(f) - -input_types = {repr(input_types)} -output_type = {repr(output_type)} -""" - ] - - # For converting scalar outputs to the correct type. - code_blocks.append(inspect.getsource(convert_to_bq_json)) - - if udf_signature.is_row_processor: - code_blocks.append(inspect.getsource(get_pd_series)) - handler_func_name = "udf_http_row_processor" - code_blocks.append(inspect.getsource(udf_http_row_processor)) - else: - code_blocks.append(inspect.getsource(convert_call)) - code_blocks.append(inspect.getsource(convert_from_bq_json)) - handler_func_name = "udf_http" - code_blocks.append(inspect.getsource(udf_http)) - - main_py = os.path.join(directory, "main.py") - with open(main_py, "w") as f: - f.writelines(code_blocks) - logger.debug(f"Wrote {os.path.abspath(main_py)}:\n{open(main_py).read()}") - - return handler_func_name - - -def generate_managed_function_code( - code_def: udf_def.CodeDef, - signature: udf_def.UdfSignature, - capture_references: bool, -) -> str: - """Generates the Python code block for managed Python UDF.""" - - udf_name = "unpickled_udf" - if capture_references: - # This code path ensures that if the udf body contains any - # references to variables and/or imports outside the body, they are - # captured as well. - func_code = textwrap.dedent( - f""" - import cloudpickle - {udf_name} = cloudpickle.loads({code_def.pickled_code!r}) - """ - ) - else: - # This code path ensures that if the udf body is self contained, - # i.e. there are no references to variables or imports outside the - # body. - assert code_def.function_source is not None - assert code_def.entry_point is not None - func_code = code_def.function_source - udf_name = code_def.entry_point - match = re.search(r"^def ", func_code, flags=re.MULTILINE) - if match is None: - raise ValueError("The UDF is not defined correctly.") - func_code = func_code[match.start() :] - - if signature.is_row_processor: - udf_code = textwrap.dedent(inspect.getsource(get_pd_series)) - udf_code = udf_code[udf_code.index("def") :] - bigframes_handler_code = textwrap.dedent( - f""" - def bigframes_handler(str_arg): - return {udf_name}({get_pd_series.__name__}(str_arg)) - """ - ) - - params = list(arg.name for arg in signature.inputs) - additional_params = params[1:] - - # Build the parameter list for the new handler function definition. - # e.g., "str_arg, y: bool, z" - handler_def_parts = ["str_arg"] - handler_def_parts.extend(additional_params) - handler_def_str = ", ".join(handler_def_parts) - - # Build the argument list for the call to the original UDF. - # e.g., "get_pd_series(str_arg), y, z" - udf_call_parts = [f"{get_pd_series.__name__}(str_arg)"] - udf_call_parts.extend(additional_params) - udf_call_str = ", ".join(udf_call_parts) - - bigframes_handler_code = textwrap.dedent( - f""" - def bigframes_handler({handler_def_str}): - return {udf_name}({udf_call_str}) - """ - ) - - else: - udf_code = "" - bigframes_handler_code = textwrap.dedent( - f""" - def bigframes_handler(*args): - return {udf_name}(*args) - """ - ) - - udf_code_block = [] - if code_def.package_requirements: - # Include package requirements as comments to help force a new - # BigQuery UDF definition when only package requirements change. - packages_comment = "# Packages: " + ", ".join( - sorted(code_def.package_requirements) - ) - udf_code_block.append(packages_comment) - - if not capture_references and signature.is_row_processor: - # Enable postponed evaluation of type annotations. This converts all - # type hints to strings at runtime, which is necessary for correctly - # handling the type annotation of pandas.Series after the UDF code is - # serialized for remote execution. See more from b/445182819. - udf_code_block.append("from __future__ import annotations") - - udf_code_block.append(udf_code) - udf_code_block.append(func_code) - udf_code_block.append(bigframes_handler_code) - - return textwrap.dedent("\n".join(udf_code_block)) diff --git a/bigframes/functions/function_typing.py b/bigframes/functions/function_typing.py deleted file mode 100644 index a64b3992b68..00000000000 --- a/bigframes/functions/function_typing.py +++ /dev/null @@ -1,137 +0,0 @@ -# Copyright 2023 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 typing import Any, Type, get_args, get_origin - -from google.cloud import bigquery - -import bigframes.dtypes - -# Input and output types supported by BigQuery DataFrames remote functions. -# TODO(shobs): Extend the support to all types supported by BQ remote functions -# https://cloud.google.com/bigquery/docs/remote-functions#limitations -RF_SUPPORTED_IO_PYTHON_TYPES = { - bool: bigquery.StandardSqlDataType(type_kind=bigquery.StandardSqlTypeNames.BOOL), - bytes: bigquery.StandardSqlDataType(type_kind=bigquery.StandardSqlTypeNames.BYTES), - float: bigquery.StandardSqlDataType( - type_kind=bigquery.StandardSqlTypeNames.FLOAT64 - ), - int: bigquery.StandardSqlDataType(type_kind=bigquery.StandardSqlTypeNames.INT64), - str: bigquery.StandardSqlDataType(type_kind=bigquery.StandardSqlTypeNames.STRING), -} - -# Support array output types in BigQuery DataFrames remote functions even though -# it is not currently (2024-10-06) supported in BigQuery remote functions. -# https://cloud.google.com/bigquery/docs/remote-functions#limitations -# TODO(b/284515241): remove this special handling when BigQuery remote functions -# support array. -RF_SUPPORTED_ARRAY_OUTPUT_PYTHON_TYPES = {bool, float, int, str} - -DEFAULT_RF_TYPE = RF_SUPPORTED_IO_PYTHON_TYPES[float] - -RF_SUPPORTED_IO_BIGQUERY_TYPEKINDS = { - "BOOLEAN", - "BOOL", - "BYTES", - "FLOAT", - "FLOAT64", - "INT64", - "INTEGER", - "STRING", - "ARRAY", -} - - -TIMEDELTA_DESCRIPTION_TAG = "#microseconds" - - -class UnsupportedTypeError(ValueError): - def __init__(self, type_, supported_types): - self.type = type_ - self.supported_types = supported_types - - types_to_format = supported_types - if isinstance(supported_types, dict): - types_to_format = supported_types.keys() - - supported_types_str = ", ".join( - sorted( - [ - getattr(supported, "__name__", supported) - for supported in types_to_format - ] - ) - ) - - super().__init__( - f"'{getattr(type_, '__name__', type_)}' must be one of the supported types ({supported_types_str}) " - "or a list of one of those types." - ) - - -def sdk_type_from_python_type( - t: type, allow_lists: bool = True -) -> bigquery.StandardSqlDataType: - if (get_origin(t) is list) and allow_lists: - return sdk_array_output_type_from_python_type(t) - if t not in RF_SUPPORTED_IO_PYTHON_TYPES: - raise UnsupportedTypeError(t, RF_SUPPORTED_IO_PYTHON_TYPES) - return RF_SUPPORTED_IO_PYTHON_TYPES[t] - - -def sdk_array_output_type_from_python_type(t: type) -> bigquery.StandardSqlDataType: - array_of = get_args(t)[0] - if array_of not in RF_SUPPORTED_ARRAY_OUTPUT_PYTHON_TYPES: - raise UnsupportedTypeError(array_of, RF_SUPPORTED_ARRAY_OUTPUT_PYTHON_TYPES) - inner_type = RF_SUPPORTED_IO_PYTHON_TYPES[array_of] - return bigquery.StandardSqlDataType( - type_kind=bigquery.StandardSqlTypeNames.ARRAY, array_element_type=inner_type - ) - - -def sdk_type_to_bf_type( - sdk_type: bigquery.StandardSqlDataType, -) -> bigframes.dtypes.Dtype: - if sdk_type.array_element_type is not None: - return bigframes.dtypes.list_type( - sdk_type_to_bf_type(sdk_type.array_element_type) - ) - if sdk_type.struct_type is not None: - raise ValueError("Cannot handle struct types in remote function") - assert sdk_type.type_kind is not None - return bigframes.dtypes._TK_TO_BIGFRAMES[sdk_type.type_kind.name] - - -def sdk_type_to_py_type( - sdk_type: bigquery.StandardSqlDataType, -) -> Type[Any]: - if sdk_type.array_element_type is not None: - return list[sdk_type_to_py_type(sdk_type.array_element_type)] # type: ignore - if sdk_type.struct_type is not None: - raise ValueError("Cannot handle struct types in remote function") - for key, value in RF_SUPPORTED_IO_PYTHON_TYPES.items(): - if value == sdk_type: - return key - raise ValueError(f"Cannot handle {sdk_type} in remote function") - - -def sdk_type_to_sql_string( - sdk_type: bigquery.StandardSqlDataType, -) -> str: - if sdk_type.array_element_type is not None: - return f"ARRAY<{sdk_type_to_sql_string(sdk_type.array_element_type)}>" - if sdk_type.struct_type is not None: - raise ValueError("Cannot handle struct types in remote function") - assert sdk_type.type_kind is not None - return sdk_type.type_kind.name diff --git a/bigframes/functions/udf_def.py b/bigframes/functions/udf_def.py deleted file mode 100644 index 70e0406a6f6..00000000000 --- a/bigframes/functions/udf_def.py +++ /dev/null @@ -1,617 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -from __future__ import annotations - -import dataclasses -import functools -import inspect -import io -import os -import textwrap -import warnings -from typing import Any, Optional, Sequence, Type, cast, get_args, get_origin - -import cloudpickle -import google_crc32c -import pandas as pd -from google.cloud import bigquery - -import bigframes.dtypes -import bigframes.exceptions as bfe -import bigframes.formatting_helpers as bf_formatting -from bigframes.functions import function_typing - -# Protocol version 4 is available in python version 3.4 and above -# https://docs.python.org/3/library/pickle.html#data-stream-format -_pickle_protocol_version = 4 - - -class ReturnTypeMissingError(ValueError): - pass - - -@dataclasses.dataclass(frozen=True) -class UdfArg: - name: str = dataclasses.field() - dtype: DirectScalarType | RowSeriesInputFieldV1 - - def __post_init__(self): - assert isinstance(self.name, str) - assert isinstance(self.dtype, (DirectScalarType, RowSeriesInputFieldV1)) - - @classmethod - def from_py_param(cls, param: inspect.Parameter) -> UdfArg: - if param.annotation == pd.Series: - return cls(param.name, RowSeriesInputFieldV1()) - return cls(param.name, DirectScalarType(param.annotation)) - - @classmethod - def from_sdk(cls, arg: bigquery.RoutineArgument) -> UdfArg: - assert arg.name is not None - - if arg.data_type is None: - msg = bfe.format_message( - "The function has one or more missing input data types. BigQuery DataFrames " - f"will assume default data type {function_typing.DEFAULT_RF_TYPE} for them." - ) - warnings.warn(msg, category=bfe.UnknownDataTypeWarning) - sdk_type = function_typing.DEFAULT_RF_TYPE - else: - sdk_type = arg.data_type - return cls(arg.name, DirectScalarType.from_sdk_type(sdk_type)) - - @property - def py_type(self) -> type: - return self.dtype.py_type - - @property - def bf_type(self) -> bigframes.dtypes.Dtype: - return self.dtype.bf_type - - @property - def sql_type(self) -> str: - return self.dtype.sql_type - - def stable_hash(self) -> bytes: - hash_val = google_crc32c.Checksum() - hash_val.update(self.name.encode()) - hash_val.update(self.dtype.stable_hash()) - return hash_val.digest() - - -@dataclasses.dataclass(frozen=True) -class DirectScalarType: - """ - Represents a scalar value that is passed directly to the remote function. - - For these values, BigQuery handles the serialization and deserialization without any additional processing. - """ - - _py_type: type - - @property - def py_type(self) -> type: - return self._py_type - - @property - def bf_type(self) -> bigframes.dtypes.Dtype: - return function_typing.sdk_type_to_bf_type( - function_typing.sdk_type_from_python_type(self._py_type) - ) - - @property - def sql_type(self) -> str: - sdk_type = function_typing.sdk_type_from_python_type(self._py_type) - return function_typing.sdk_type_to_sql_string(sdk_type) - - def stable_hash(self) -> bytes: - hash_val = google_crc32c.Checksum() - hash_val.update(self._py_type.__name__.encode()) - return hash_val.digest() - - @classmethod - def from_sdk_type(cls, sdk_type: bigquery.StandardSqlDataType) -> DirectScalarType: - return cls(function_typing.sdk_type_to_py_type(sdk_type)) - - @property - def emulating_type(self) -> DirectScalarType: - return self - - -@dataclasses.dataclass(frozen=True) -class VirtualListTypeV1: - """ - Represents a list of scalar values that is emulated as a JSON array string in the remote function. - - Only works as output paramter right now where array -> string in function runtime, and then string -> array in SQL post-processing (defined in out_expr()). - """ - - _PROTOCOL_ID = "virtual_list_v1" - - inner_dtype: DirectScalarType - - @property - def py_type(self) -> Type[list[Any]]: - return list[self.inner_dtype.py_type] # type: ignore - - @property - def bf_type(self) -> bigframes.dtypes.Dtype: - return bigframes.dtypes.list_type(self.inner_dtype.bf_type) - - @property - def emulating_type(self) -> DirectScalarType: - # Regardless of list inner type, string is used to emulate the list in the remote function. - return DirectScalarType(str) - - def out_expr( - self, expr: bigframes.core.expression.Expression - ) -> bigframes.core.expression.Expression: - # essentially we are undoing json.dumps in sql - import bigframes.operations as ops - - as_str_list = ops.JSONValueArray(json_path="$").as_expr(expr) - if self.inner_dtype.py_type is str: - return as_str_list - elif self.inner_dtype.py_type is bool: - # hack so we don't need to make ArrayMap support general expressions yet - # with b/495513753 we can map the equality operator instead - return ops.ArrayMapOp(ops.IsInOp(values=("true",))).as_expr(as_str_list) - else: - return ops.ArrayMapOp(ops.AsTypeOp(self.inner_dtype.bf_type)).as_expr( - as_str_list - ) - - @property - def sql_type(self) -> str: - return f"ARRAY<{self.inner_dtype.sql_type}>" - - def stable_hash(self) -> bytes: - hash_val = google_crc32c.Checksum() - hash_val.update(self._PROTOCOL_ID.encode()) - hash_val.update(self.inner_dtype.stable_hash()) - return hash_val.digest() - - -@dataclasses.dataclass(frozen=True) -class RowSeriesInputFieldV1: - """ - Used to handle functions that logically take a series as an input, but handled via a string protocol in the remote function. - - For these, the serialization is dependent on index metadata, which must be provided by the caller. - """ - - _PROTOCOL_ID = "row_series_input_v1" - - @property - def py_type(self) -> type: - return pd.Series - - @property - def bf_type(self) -> bigframes.dtypes.Dtype: - # Code paths shouldn't hit this. - raise ValueError("Series does not have a corresponding BigFrames type.") - - @property - def sql_type(self) -> str: - return "STRING" - - @property - def emulating_type(self) -> DirectScalarType: - # Regardless of list inner type, string is used to emulate the list in the remote function. - return DirectScalarType(str) - - def stable_hash(self) -> bytes: - hash_val = google_crc32c.Checksum() - hash_val.update(self._PROTOCOL_ID.encode()) - return hash_val.digest() - - -@dataclasses.dataclass(frozen=True) -class UdfSignature: - """ - Represents the mapping of input types from bigframes to sql to python and back. - """ - - inputs: tuple[UdfArg, ...] = dataclasses.field() - output: DirectScalarType | VirtualListTypeV1 - - def __post_init__(self): - # Validate inputs and outputs are of the correct types. - assert all(isinstance(arg, UdfArg) for arg in self.inputs) - assert isinstance(self.output, (DirectScalarType, VirtualListTypeV1)) - - def to_sql_input_signature(self) -> str: - return ",".join( - f"{field.name} {field.sql_type}" - for field in self.with_devirtualize().inputs - ) - - @property - def protocol_metadata(self) -> str | None: - import bigframes.functions._utils - - if isinstance(self.output, VirtualListTypeV1): - return bigframes.functions._utils.get_bigframes_metadata( - python_output_type=self.output.py_type - ) - return None - - @property - def is_virtual(self) -> bool: - dtypes = (self.output,) + tuple(arg.dtype for arg in self.inputs) - return not all(isinstance(dtype, DirectScalarType) for dtype in dtypes) - - @property - def is_row_processor(self) -> bool: - return any(isinstance(arg.dtype, RowSeriesInputFieldV1) for arg in self.inputs) - - def with_devirtualize(self) -> UdfSignature: - return UdfSignature( - inputs=tuple( - UdfArg(arg.name, arg.dtype.emulating_type) for arg in self.inputs - ), - output=self.output.emulating_type, - ) - - # TODO(493293086): Deprecate is_row_processor. - @classmethod - def from_routine( - cls, routine: bigquery.Routine, is_row_processor: bool = False - ) -> UdfSignature: - import bigframes.functions._utils - - ## Handle return type - if routine.return_type is None: - raise ReturnTypeMissingError( - f"Routine {routine} has no return type. Routine properties: {routine._properties}" - ) - - bq_return_type = cast(bigquery.StandardSqlDataType, routine.return_type) - - return_type: DirectScalarType | VirtualListTypeV1 = ( - DirectScalarType.from_sdk_type(bq_return_type) - ) - if ( - python_output_type - := bigframes.functions._utils.get_python_output_type_from_bigframes_metadata( - routine.description - ) - ): - if bq_return_type.type_kind != "STRING": - raise bf_formatting.create_exception_with_feedback_link( - TypeError, - "An explicit output_type should be provided only for a BigQuery function with STRING output.", - ) - - if get_origin(python_output_type) is list: - inner_type = get_args(python_output_type)[0] - return_type = VirtualListTypeV1(DirectScalarType(inner_type)) - else: - raise bf_formatting.create_exception_with_feedback_link( - TypeError, - "Currently only list of a type is supported as python output type.", - ) - - ## Handle input types - udf_fields = [] - - for i, argument in enumerate(routine.arguments): - if is_row_processor and i == 0: - if argument.data_type.type_kind == "STRING": - udf_fields.append(UdfArg(argument.name, RowSeriesInputFieldV1())) - else: - raise ValueError( - "Row processor functions must have STRING input type as first argument." - ) - udf_fields.append(UdfArg.from_sdk(argument)) - - return cls( - inputs=tuple(udf_fields), - output=return_type, - ) - - @classmethod - def from_py_signature(cls, signature: inspect.Signature): - import bigframes.series - - input_types: list[UdfArg] = [] - for parameter in signature.parameters.values(): - if parameter.annotation is inspect.Signature.empty: - raise bf_formatting.create_exception_with_feedback_link( - ValueError, - "'input_types' was not set and parameter " - f"'{parameter.name}' is missing a type annotation. " - "Types are required to use udfs.", - ) - if parameter.annotation is bigframes.series.Series: - raise TypeError( - "Argument type hint must be Pandas Series, not BigFrames Series." - ) - - input_types.append(UdfArg.from_py_param(parameter)) - - if signature.return_annotation is inspect.Signature.empty: - raise bf_formatting.create_exception_with_feedback_link( - ValueError, - "'output_type' was not set and function is missing a " - "return type annotation. Types are required to use " - "udfs.", - ) - - output_type = DirectScalarType(signature.return_annotation) - return cls(tuple(input_types), output_type) - - def to_remote_function_compatible(self) -> UdfSignature: - # need to virtualize list outputs - if isinstance(self.output, DirectScalarType): - if get_origin(self.output.py_type) is list: - inner_py_type = get_args(self.output.py_type)[0] - return UdfSignature( - inputs=self.inputs, - output=VirtualListTypeV1(DirectScalarType(inner_py_type)), - ) - return self - - def stable_hash(self) -> bytes: - hash_val = google_crc32c.Checksum() - for input_type in self.inputs: - hash_val.update(input_type.stable_hash()) - hash_val.update(self.output.stable_hash()) - return hash_val.digest() - - -@dataclasses.dataclass(frozen=True) -class RuntimeRequirements: - container_cpu: Optional[float] = None - container_memory: Optional[str] = None - bq_connection_id: Optional[str] = None - max_batching_rows: Optional[int] = None - packages: tuple[str, ...] = () - - def stable_hash(self) -> bytes: - hash_val = google_crc32c.Checksum() - if self.container_cpu is not None: - hash_val.update(str(self.container_cpu).encode()) - if self.container_memory is not None: - hash_val.update(str(self.container_memory).encode()) - if self.bq_connection_id is not None: - hash_val.update(str(self.bq_connection_id).encode()) - if self.max_batching_rows is not None: - hash_val.update(str(self.max_batching_rows).encode()) - if self.packages: - for p in sorted(self.packages): - hash_val.update(p.encode()) - return hash_val.digest() - - -@dataclasses.dataclass(frozen=True) -class BigqueryUdf: - """ - Represents the information needed to call a BigQuery remote function - not a full spec. - """ - - routine_ref: bigquery.RoutineReference = dataclasses.field() - signature: UdfSignature - - def with_devirtualize(self) -> BigqueryUdf: - if not self.signature.is_virtual: - return self - return BigqueryUdf( - routine_ref=self.routine_ref, - signature=self.signature.with_devirtualize(), - ) - - @classmethod - def from_routine( - cls, routine: bigquery.Routine, is_row_processor: bool = False - ) -> BigqueryUdf: - signature = UdfSignature.from_routine( - routine, is_row_processor=is_row_processor - ) - return cls(routine.reference, signature=signature) - - -@dataclasses.dataclass(frozen=True) -class PythonUdf: - """ - Represents user-requested Python UDF semantics, including the code and runtime requirements. - """ - - signature: UdfSignature - code: CodeDef - requirements: RuntimeRequirements = dataclasses.field( - default_factory=RuntimeRequirements - ) - - def stable_hash(self) -> bytes: - hash_val = google_crc32c.Checksum() - hash_val.update(self.code.stable_hash()) - hash_val.update(self.signature.stable_hash()) - hash_val.update(self.requirements.stable_hash()) - return hash_val.digest() - - def to_managed_function_config(self) -> ManagedFunctionConfig: - return ManagedFunctionConfig( - code=self.code, - signature=self.signature, - max_batching_rows=self.requirements.max_batching_rows, - container_cpu=self.requirements.container_cpu, - container_memory=self.requirements.container_memory, - bq_connection_id=self.requirements.bq_connection_id, - capture_references=False, - ) - - -@dataclasses.dataclass(frozen=True) -class CodeDef: - # Produced by cloudpickle, not compatible across python versions - pickled_code: bytes - # This is just the function itself, and does not include referenced objects/functions/modules - function_source: Optional[str] - entry_point: Optional[str] - package_requirements: tuple[str, ...] - - @classmethod - def from_func(cls, func, package_requirements: Sequence[str] | None = None): - bytes_io = io.BytesIO() - cloudpickle.dump(func, bytes_io, protocol=_pickle_protocol_version) - source = None - entry_point = None - try: - # dedent is hacky, but works for some nested functions - source = textwrap.dedent(inspect.getsource(func)) - entry_point = func.__name__ - except OSError: - pass - return cls( - pickled_code=bytes_io.getvalue(), - function_source=source, - entry_point=entry_point, - package_requirements=tuple(package_requirements or []), - ) - - @functools.cache - def stable_hash(self) -> bytes: - # There is a known cell-id sensitivity of the cloudpickle serialization in - # notebooks https://github.com/cloudpipe/cloudpickle/issues/538. Because of - # this, if a cell contains a udf decorated with @remote_function, a unique - # cloudpickle code is generated every time the cell is run, creating new - # cloud artifacts every time. This is slow and wasteful. - # A workaround of the same can be achieved by replacing the filename in the - # code object to a static value - # https://github.com/cloudpipe/cloudpickle/issues/120#issuecomment-338510661. - # - # To respect the user code/environment let's make this modification on a - # copy of the udf, not on the original udf itself. - def_copy = cloudpickle.loads(self.pickled_code) - def_copy.__code__ = def_copy.__code__.replace( - co_filename="bigframes_place_holder_filename" - ) - - normalized_pickled_code = cloudpickle.dumps( - def_copy, protocol=_pickle_protocol_version - ) - - hash_val = google_crc32c.Checksum() - hash_val.update(normalized_pickled_code) - - if self.package_requirements: - for p in sorted(self.package_requirements): - hash_val.update(p.encode()) - - return hash_val.digest() - - def to_callable(self): - """ - Reconstructs the python callable from the pickled code. - - Assumption: package_requirements match local environment - """ - return cloudpickle.loads(self.pickled_code) - - -@dataclasses.dataclass(frozen=True) -class ManagedFunctionConfig: - code: CodeDef - signature: UdfSignature - max_batching_rows: Optional[int] - container_cpu: Optional[float] - container_memory: Optional[str] - bq_connection_id: Optional[str] - # capture_refernces=True -> deploy as cloudpickle - # capture_references=False -> deploy as source - capture_references: bool = False - - def stable_hash(self) -> bytes: - hash_val = google_crc32c.Checksum() - hash_val.update(self.code.stable_hash()) - hash_val.update(self.signature.stable_hash()) - hash_val.update(str(self.max_batching_rows).encode()) - hash_val.update(str(self.container_cpu).encode()) - hash_val.update(str(self.container_memory).encode()) - hash_val.update(str(self.bq_connection_id).encode()) - hash_val.update(str(self.capture_references).encode()) - return hash_val.digest() - - -@dataclasses.dataclass(frozen=True) -class CloudRunFunctionConfig: - code: CodeDef - signature: UdfSignature - timeout_seconds: int | None - max_instance_count: int | None - vpc_connector: str | None - vpc_connector_egress_settings: str - memory_mib: int | None - cpus: float | None - ingress_settings: str - workers: int | None - threads: int | None - concurrency: int | None - kms_key_name: str | None - docker_repository: str | None - cloud_build_service_account: str | None - cloud_run_service_account: str | None - - def stable_hash(self) -> bytes: - hash_val = google_crc32c.Checksum() - hash_val.update(self.code.stable_hash()) - hash_val.update(self.signature.stable_hash()) - hash_val.update(str(self.timeout_seconds).encode()) - hash_val.update(str(self.max_instance_count).encode()) - hash_val.update(str(self.vpc_connector).encode()) - hash_val.update(str(self.vpc_connector_egress_settings).encode()) - hash_val.update(str(self.memory_mib).encode()) - hash_val.update(str(self.cpus).encode()) - hash_val.update(str(self.ingress_settings).encode()) - hash_val.update(str(self.workers).encode()) - hash_val.update(str(self.threads).encode()) - hash_val.update(str(self.concurrency).encode()) - hash_val.update(str(self.kms_key_name).encode()) - hash_val.update(str(self.docker_repository).encode()) - hash_val.update(str(self.cloud_build_service_account).encode()) - hash_val.update(str(self.cloud_run_service_account).encode()) - return hash_val.digest() - - -@dataclasses.dataclass(frozen=True) -class RemoteFunctionConfig: - """ - Represents the information needed to create a BigQuery remote function. - """ - - endpoint: str - signature: UdfSignature - connection_id: str - max_batching_rows: int - bq_metadata: str | None = None - - @classmethod - def from_bq_routine(cls, routine: bigquery.Routine) -> RemoteFunctionConfig: - return cls( - endpoint=routine.remote_function_options.endpoint, - connection_id=os.path.basename(routine.remote_function_options.connection), - signature=UdfSignature.from_routine(routine), - max_batching_rows=routine.remote_function_options.max_batching_rows, - bq_metadata=routine.description, - ) - - def stable_hash(self) -> bytes: - hash_val = google_crc32c.Checksum() - hash_val.update(self.endpoint.encode()) - hash_val.update(self.signature.stable_hash()) - hash_val.update(self.connection_id.encode()) - hash_val.update(str(self.max_batching_rows).encode()) - hash_val.update(str(self.bq_metadata).encode()) - return hash_val.digest() diff --git a/bigframes/geopandas/__init__.py b/bigframes/geopandas/__init__.py deleted file mode 100644 index 08966ba9238..00000000000 --- a/bigframes/geopandas/__init__.py +++ /dev/null @@ -1,17 +0,0 @@ -# Copyright 2023 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 bigframes.geopandas.geoseries import GeoSeries - -__all__ = ["GeoSeries"] diff --git a/bigframes/geopandas/geoseries.py b/bigframes/geopandas/geoseries.py deleted file mode 100644 index dc373216b65..00000000000 --- a/bigframes/geopandas/geoseries.py +++ /dev/null @@ -1,130 +0,0 @@ -# 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. -# 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 typing import Optional - -import bigframes_vendored.constants as constants -import bigframes_vendored.geopandas.geoseries as vendored_geoseries -import geopandas.array # type: ignore - -import bigframes.operations as ops -import bigframes.series -import bigframes.session -from bigframes._tools import docs - - -@docs.inherit_docs(vendored_geoseries.GeoSeries) -class GeoSeries(bigframes.series.Series): - def __init__(self, data=None, index=None, **kwargs): - super().__init__( - data=data, index=index, dtype=geopandas.array.GeometryDtype(), **kwargs - ) - - @property - def length(self): - raise NotImplementedError( - "GeoSeries.length is not yet implemented. Please use bigframes.bigquery.st_length(geoseries) instead." - ) - - @property - def x(self) -> bigframes.series.Series: - series = self._apply_unary_op(ops.geo_x_op) - series.name = None - return series - - @property - def y(self) -> bigframes.series.Series: - series = self._apply_unary_op(ops.geo_y_op) - series.name = None - return series - - # GeoSeries.area overrides Series.area with something totally different. - # Ignore this type error, as we are trying to be as close to geopandas as - # we can. - @property - def area(self, crs=None) -> bigframes.series.Series: # type: ignore - raise NotImplementedError( - f"GeoSeries.area is not supported. Use bigframes.bigquery.st_area(series), instead. {constants.FEEDBACK_LINK}" - ) - - @property - def boundary(self) -> bigframes.series.Series: # type: ignore - series = self._apply_unary_op(ops.geo_st_boundary_op) - series.name = None - return series - - @property - def is_closed(self) -> bigframes.series.Series: - # TODO(tswast): GeoPandas doesn't treat Point as closed. Use ST_LENGTH - # when available to filter out "closed" shapes that return false in - # GeoPandas. - raise NotImplementedError( - f"GeoSeries.is_closed is not supported. Use bigframes.bigquery.st_isclosed(series), instead. {constants.FEEDBACK_LINK}" - ) - - @classmethod - def from_wkt( - cls, - data, - index=None, - *, - session: Optional[bigframes.session.Session] = None, - ) -> GeoSeries: - series = bigframes.series.Series(data, index=index, session=session) - - return cls(series._apply_unary_op(ops.geo_st_geogfromtext_op)) - - @classmethod - def from_xy(cls, x, y, index=None, session=None, **kwargs) -> GeoSeries: - # TODO: if either x or y is local and the other is remote. Use the - # session from the remote object. - series_x = bigframes.series.Series(x, index=index, session=session, **kwargs) - series_y = bigframes.series.Series(y, index=index, session=session, **kwargs) - - return cls(series_x._apply_binary_op(series_y, ops.geo_st_geogpoint_op)) - - def to_wkt(self: GeoSeries) -> bigframes.series.Series: - series = self._apply_unary_op(ops.geo_st_astext_op) - series.name = None - return series - - def buffer(self: GeoSeries, distance: float) -> bigframes.series.Series: # type: ignore - raise NotImplementedError( - f"GeoSeries.buffer is not supported. Use bigframes.bigquery.st_buffer(series, distance), instead. {constants.FEEDBACK_LINK}" - ) - - @property - def centroid(self: GeoSeries) -> bigframes.series.Series: # type: ignore - return self._apply_nary_op(ops.googlesql.ST_CENTROID, []) - - @property - def convex_hull(self: GeoSeries) -> bigframes.series.Series: # type: ignore - return self._apply_unary_op(ops.geo_st_convexhull_op) - - def difference(self: GeoSeries, other: GeoSeries) -> bigframes.series.Series: # type: ignore - return self._apply_binary_op(other, ops.geo_st_difference_op) - - def distance(self: GeoSeries, other: GeoSeries) -> bigframes.series.Series: # type: ignore - raise NotImplementedError( - f"GeoSeries.distance is not supported. Use bigframes.bigquery.st_distance(series, other), instead. {constants.FEEDBACK_LINK}" - ) - - def intersection(self: GeoSeries, other: GeoSeries) -> bigframes.series.Series: # type: ignore - return self._apply_binary_op(other, ops.geo_st_intersection_op) - - def simplify(self, tolerance, preserve_topology=True): - raise NotImplementedError( - f"GeoSeries.simplify is not supported. Use bigframes.bigquery.st_simplify(series, tolerance_meters), instead. {constants.FEEDBACK_LINK}" - ) diff --git a/bigframes/ml/__init__.py b/bigframes/ml/__init__.py index 368d272e7b4..55c8709d8d8 100644 --- a/bigframes/ml/__init__.py +++ b/bigframes/ml/__init__.py @@ -12,82 +12,18 @@ # See the License for the specific language governing permissions and # limitations under the License. -"""BigQuery DataFrames ML provides a SKLearn-like API on the BigQuery engine. - -.. code:: python - - from bigframes.ml.linear_model import LinearRegression - model = LinearRegression() - model.fit(feature_columns, label_columns) - model.predict(feature_columns_from_test_data) - -You can also save your fit parameters to BigQuery for later use. - -.. code:: python - - import bigframes.pandas as bpd - model.to_gbq( - your_model_id, # For example: "bqml_tutorial.penguins_model" - replace=True, - ) - saved_model = bpd.read_gbq_model(your_model_id) - saved_model.predict(feature_columns_from_test_data) - -See the `BigQuery ML linear regression tutorial -`_ for a -detailed example. - -See also the references for ``bigframes.ml`` sub-modules: - -* :mod:`bigframes.ml.cluster` -* :mod:`bigframes.ml.compose` -* :mod:`bigframes.ml.decomposition` -* :mod:`bigframes.ml.ensemble` -* :mod:`bigframes.ml.forecasting` -* :mod:`bigframes.ml.imported` -* :mod:`bigframes.ml.impute` -* :mod:`bigframes.ml.linear_model` -* :mod:`bigframes.ml.llm` -* :mod:`bigframes.ml.metrics` -* :mod:`bigframes.ml.model_selection` -* :mod:`bigframes.ml.pipeline` -* :mod:`bigframes.ml.preprocessing` -* :mod:`bigframes.ml.remote` - -Alternatively, check out mod:`bigframes.bigquery.ml` for an interface that is -more similar to the BigQuery ML SQL syntax. -""" - -from bigframes.ml import ( - cluster, - compose, - decomposition, - ensemble, - forecasting, - imported, - impute, - linear_model, - llm, - metrics, - model_selection, - pipeline, - preprocessing, - remote, -) +"""BigQuery DataFrames ML provides a SKLearn-like API on the BigQuery engine.""" __all__ = [ "cluster", "compose", "decomposition", - "ensemble", - "forecasting", - "imported", - "impute", "linear_model", - "llm", "metrics", "model_selection", "pipeline", "preprocessing", - "remote", + "llm", + "forecasting", + "imported", ] diff --git a/bigframes/ml/base.py b/bigframes/ml/base.py index fbfaf6b537c..f2478b1ce23 100644 --- a/bigframes/ml/base.py +++ b/bigframes/ml/base.py @@ -15,32 +15,23 @@ """ Wraps primitives for machine learning with BQML -This library is an evolving attempt to: - -* implement BigQuery DataFrames API for BQML -* follow as close as possible the API design of SKLearn +This library is an evolving attempt to +- implement BigQuery DataFrames API for BQML +- follow as close as possible the API design of SKLearn https://arxiv.org/pdf/1309.0238.pdf - """ import abc -import typing -import warnings -from typing import Optional, TypeVar, Union - -import bigframes_vendored.sklearn.base +from typing import cast, Optional, TypeVar, Union -import bigframes.exceptions as bfe -import bigframes.ml.utils as utils -import bigframes.pandas as bpd -from bigframes._tools import docs from bigframes.ml import core +import bigframes.pandas as bpd +import third_party.bigframes_vendored.sklearn.base -@docs.inherit_docs(bigframes_vendored.sklearn.base.BaseEstimator) -class BaseEstimator(abc.ABC): +class BaseEstimator(third_party.bigframes_vendored.sklearn.base.BaseEstimator, abc.ABC): """ - A BigQuery DataFrames machine learning component follows sklearn API + A BigQuery DataFrames machine learning component following the SKLearn API design Ref: https://bit.ly/3NyhKjN The estimator is the fundamental abstraction for all learning components. This includes learning @@ -51,16 +42,12 @@ class BaseEstimator(abc.ABC): assumed to be the list of hyperparameters. All descendents of this class should implement: - - .. code-block:: python - def __init__(self, hyperparameter_1=default_1, hyperparameter_2=default_2, hyperparameter3, ...): '''Set hyperparameters''' self.hyperparameter_1 = hyperparameter_1 self.hyperparameter_2 = hyperparameter_2 self.hyperparameter3 = hyperparameter3 ... - Note: the object variable names must be exactly the same with parameter names. In order to utilize __repr__. fit(X, y) method is optional. @@ -88,15 +75,12 @@ def fit_transform(self, x_train: Union[DataFrame, Series], y_train: Union[DataFr ... """ - def __init__(self): - self._bqml_model: Optional[core.BqmlModel] = None - def __repr__(self): - """Print the estimator's constructor with all non-default parameter values.""" + """Print the estimator's constructor with all non-default parameter values""" # Estimator pretty printer adapted from Sklearn's, which is in turn an adaption of # the inbuilt pretty-printer in CPython - import bigframes_vendored.cpython._pprint as adapted_pprint + import third_party.bigframes_vendored.cpython._pprint as adapted_pprint prettyprinter = adapted_pprint._EstimatorPrettyPrinter( compact=True, indent=1, indent_at_name=True, n_max_elements_to_show=30 @@ -105,10 +89,12 @@ def __repr__(self): return prettyprinter.pformat(self) -# TODO(garrettwu): refactor to reflect the actual property. Now the class contains .register() method. class Predictor(BaseEstimator): """A BigQuery DataFrames ML Model base class that can be used to predict outputs.""" + def __init__(self): + self._bqml_model: Optional[core.BqmlModel] = None + @abc.abstractmethod def predict(self, X): pass @@ -118,13 +104,13 @@ def predict(self, X): def register(self: _T, vertex_ai_model_id: Optional[str] = None) -> _T: """Register the model to Vertex AI. - After register, go to the Google Cloud console (https://console.cloud.google.com/vertex-ai/models) + After register, go to Google Cloud Console (https://console.cloud.google.com/vertex-ai/models) to manage the model registries. Refer to https://cloud.google.com/vertex-ai/docs/model-registry/introduction for more options. Args: vertex_ai_model_id (Optional[str], default None): - Optional string id as model id in Vertex. If not set, will default to 'bigframes_{bq_model_id}'. + optional string id as model id in Vertex. If not set, will by default to 'bigframes_{bq_model_id}'. Vertex Ai model id will be truncated to 63 characters due to its limitation. Returns: @@ -136,15 +122,11 @@ def register(self: _T, vertex_ai_model_id: Optional[str] = None) -> _T: self._bqml_model = self._create_bqml_model() # type: ignore except AttributeError: raise RuntimeError("A model must be trained before register.") - self._bqml_model = typing.cast(core.BqmlModel, self._bqml_model) + self._bqml_model = cast(core.BqmlModel, self._bqml_model) self._bqml_model.register(vertex_ai_model_id) return self - @abc.abstractmethod - def to_gbq(self, model_name, replace): - pass - class TrainablePredictor(Predictor): """A BigQuery DataFrames ML Model base class that can be used to fit and predict outputs. @@ -159,6 +141,11 @@ def _fit(self, X, y, transforms=None): def score(self, X, y): pass + # TODO(b/291812029): move to Predictor after implement in LLM and imported models + @abc.abstractmethod + def to_gbq(self, model_name, replace): + pass + class SupervisedTrainablePredictor(TrainablePredictor): """A BigQuery DataFrames ML Supervised Model base class that can be used to fit and predict outputs. @@ -169,181 +156,28 @@ class SupervisedTrainablePredictor(TrainablePredictor): def fit( self: _T, - X: utils.ArrayType, - y: utils.ArrayType, + X: Union[bpd.DataFrame, bpd.Series], + y: Union[bpd.DataFrame, bpd.Series], ) -> _T: return self._fit(X, y) -class SupervisedTrainableWithIdColPredictor(SupervisedTrainablePredictor): - """Inherits from SupervisedTrainablePredictor, - but adds an optional id_col parameter to fit().""" - - def __init__(self): - super().__init__() - self.id_col = None - - def _fit( - self, - X: utils.ArrayType, - y: utils.ArrayType, - transforms=None, - id_col: Optional[utils.ArrayType] = None, - ): - return self - - def fit( - self, - X: utils.ArrayType, - y: utils.ArrayType, - transforms=None, - id_col: Optional[utils.ArrayType] = None, - ): - self.id_col = id_col - return self._fit(X, y, transforms=transforms, id_col=self.id_col) - - -class TrainableWithEvaluationPredictor(TrainablePredictor): - """A BigQuery DataFrames ML Model base class that can be used to fit and predict outputs. - - Additional evaluation data can be provided to measure the model in the fit phase.""" - - @abc.abstractmethod - def _fit(self, X, y, transforms=None, X_eval=None, y_eval=None): - pass - - @abc.abstractmethod - def score(self, X, y): - pass - - -class SupervisedTrainableWithEvaluationPredictor(TrainableWithEvaluationPredictor): - """A BigQuery DataFrames ML Supervised Model base class that can be used to fit and predict outputs. - - Need to provide both X and y in supervised tasks. - - Additional X_eval and y_eval can be provided to measure the model in the fit phase. - """ - - _T = TypeVar("_T", bound="SupervisedTrainableWithEvaluationPredictor") - - def fit( - self: _T, - X: utils.ArrayType, - y: utils.ArrayType, - X_eval: Optional[utils.ArrayType] = None, - y_eval: Optional[utils.ArrayType] = None, - ) -> _T: - return self._fit(X, y, X_eval=X_eval, y_eval=y_eval) - - class UnsupervisedTrainablePredictor(TrainablePredictor): """A BigQuery DataFrames ML Unsupervised Model base class that can be used to fit and predict outputs. - Only need to provide X (y is optional and ignored) in unsupervised tasks.""" + Only need to provide both X (y is optional and ignored) in unsupervised tasks.""" _T = TypeVar("_T", bound="UnsupervisedTrainablePredictor") def fit( self: _T, - X: utils.ArrayType, - y: Optional[utils.ArrayType] = None, + X: Union[bpd.DataFrame, bpd.Series], + y: Optional[Union[bpd.DataFrame, bpd.Series]] = None, ) -> _T: return self._fit(X, y) - def fit_predict( - self: _T, - X: utils.ArrayType, - y: Optional[utils.ArrayType] = None, - ) -> _T: - return self.fit(X).predict(X) - - -class RetriableRemotePredictor(BaseEstimator): - def _predict_and_retry( - self, - bqml_model_predict_tvf: core.BqmlModel.TvfDef, - X: bpd.DataFrame, - options: dict, - max_retries: int, - ) -> bpd.DataFrame: - assert self._bqml_model is not None - - df_result: Union[bpd.DataFrame, None] = None # placeholder - df_succ = df_fail = X - for i in range(max_retries + 1): - if i > 0 and df_fail.empty: - break - if i > 0 and df_succ.empty: - msg = bfe.format_message("Can't make any progress, stop retrying.") - warnings.warn(msg, category=RuntimeWarning) - break - - df = bqml_model_predict_tvf.tvf(self._bqml_model, df_fail, options) - - success = df[bqml_model_predict_tvf.status_col].str.len() == 0 - df_succ = df[success] - df_fail = df[~success] - - df_result = ( - bpd.concat([df_result, df_succ]) if df_result is not None else df_succ - ) - - df_result = typing.cast( - bpd.DataFrame, - bpd.concat([df_result, df_fail]) if df_result is not None else df_fail, - ) - return df_result - - -class BaseTransformer(BaseEstimator): - """Transformer base class.""" - - @abc.abstractmethod - def _keys(self): - pass - - def _extract_output_names(self): - """Extract transform output column names. Save the results to self._output_names.""" - assert self._bqml_model is not None - - output_names = [] - for transform_col in self._bqml_model._model._properties["transformColumns"]: - transform_col_dict = typing.cast(dict, transform_col) - # pass the columns that are not transformed - if "transformSql" not in transform_col_dict: - continue - output_names.append(transform_col_dict["name"]) - - self._output_names = output_names - - def __eq__(self, other) -> bool: - return type(self) is type(other) and self._keys() == other._keys() - - def __hash__(self) -> int: - return hash(self._keys()) - - _T = TypeVar("_T", bound="BaseTransformer") - - def to_gbq(self: _T, model_name: str, replace: bool = False) -> _T: - """Save the transformer as a BigQuery model. - - Args: - model_name (str): - The name of the model. - replace (bool, default False): - Determine whether to replace if the model already exists. Default to False. - - Returns: - Saved transformer.""" - if not self._bqml_model: - raise RuntimeError("A transformer must be fitted before it can be saved") - - new_model = self._bqml_model.copy(model_name, replace) - return new_model.session.read_gbq_model(model_name) - -class Transformer(BaseTransformer): +class Transformer(BaseEstimator): """A BigQuery DataFrames Transformer base class that transforms data. Also the transformers can be attached to a pipeline with a predictor.""" @@ -358,13 +192,13 @@ def transform(self, X): def fit_transform( self, - X: utils.ArrayType, - y: Optional[utils.ArrayType] = None, + X: Union[bpd.DataFrame, bpd.Series], + y: Optional[Union[bpd.DataFrame, bpd.Series]] = None, ) -> bpd.DataFrame: return self.fit(X, y).transform(X) -class LabelTransformer(BaseTransformer): +class LabelTransformer(BaseEstimator): """A BigQuery DataFrames Label Transformer base class that transforms data. Also the transformers can be attached to a pipeline with a predictor.""" @@ -379,6 +213,6 @@ def transform(self, y): def fit_transform( self, - y: utils.ArrayType, + y: Union[bpd.DataFrame, bpd.Series], ) -> bpd.DataFrame: return self.fit(y).transform(y) diff --git a/bigframes/ml/cluster.py b/bigframes/ml/cluster.py index f7a84a57e97..772b90f666e 100644 --- a/bigframes/ml/cluster.py +++ b/bigframes/ml/cluster.py @@ -17,96 +17,55 @@ from __future__ import annotations -from typing import List, Literal, Optional, Union +from typing import cast, Dict, List, Optional, Union -import bigframes_vendored.sklearn.cluster._kmeans -import pandas as pd from google.cloud import bigquery import bigframes -import bigframes.pandas as bpd -from bigframes.core.logging import log_adapter from bigframes.ml import base, core, globals, utils - -_BQML_PARAMS_MAPPING = { - "n_clusters": "numClusters", - "init": "kmeansInitializationMethod", - "init_col": "kmeansInitializationColumn", - "distance_type": "distanceType", - "max_iter": "maxIterations", - "tol": "minRelativeProgress", - "warm_start": "warmStart", -} +import bigframes.pandas as bpd +import third_party.bigframes_vendored.sklearn.cluster._kmeans -@log_adapter.class_logger class KMeans( base.UnsupervisedTrainablePredictor, - bigframes_vendored.sklearn.cluster._kmeans.KMeans, + third_party.bigframes_vendored.sklearn.cluster._kmeans.KMeans, ): - __doc__ = bigframes_vendored.sklearn.cluster._kmeans.KMeans.__doc__ - def __init__( - self, - n_clusters: int = 8, - *, - init: Literal["kmeans++", "random", "custom"] = "kmeans++", - init_col: Optional[str] = None, - distance_type: Literal["euclidean", "cosine"] = "euclidean", - max_iter: int = 20, - tol: float = 0.01, - warm_start: bool = False, - ): + __doc__ = third_party.bigframes_vendored.sklearn.cluster._kmeans.KMeans.__doc__ + + def __init__(self, n_clusters: int = 8): self.n_clusters = n_clusters - # allow the alias to be compatible with sklearn - self.init = "kmeans++" if init == "k-means++" else init - self.init_col = init_col - self.distance_type = distance_type - self.max_iter = max_iter - self.tol = tol - self.warm_start = warm_start self._bqml_model: Optional[core.BqmlModel] = None self._bqml_model_factory = globals.bqml_model_factory() @classmethod - def _from_bq(cls, session: bigframes.Session, bq_model: bigquery.Model) -> KMeans: - assert bq_model.model_type == "KMEANS" + def _from_bq(cls, session: bigframes.Session, model: bigquery.Model) -> KMeans: + assert model.model_type == "KMEANS" - kwargs: dict = {} + kwargs = {} - kwargs = utils.retrieve_params_from_bq_model( - cls, bq_model, _BQML_PARAMS_MAPPING - ) + # See https://cloud.google.com/bigquery/docs/reference/rest/v2/models#trainingrun + last_fitting = model.training_runs[-1]["trainingOptions"] + if "numClusters" in last_fitting: + kwargs["n_clusters"] = int(last_fitting["numClusters"]) - model = cls(**kwargs) - model._bqml_model = core.BqmlModel(session, bq_model) - return model + new_kmeans = cls(**kwargs) + new_kmeans._bqml_model = core.BqmlModel(session, model) + return new_kmeans @property - def _bqml_options(self) -> dict: + def _bqml_options(self) -> Dict[str, str | int | float | List[str]]: """The model options as they will be set for BQML""" - options = { - "model_type": "KMEANS", - "num_clusters": self.n_clusters, - "KMEANS_INIT_METHOD": self.init, - "DISTANCE_TYPE": self.distance_type, - "MAX_ITERATIONS": self.max_iter, - "MIN_REL_PROGRESS": self.tol, - "WARM_START": self.warm_start, - } - - if self.init_col is not None: - options["KMEANS_INIT_COL"] = self.init_col - - return options + return {"model_type": "KMEANS", "num_clusters": self.n_clusters} def _fit( self, - X: utils.ArrayType, + X: Union[bpd.DataFrame, bpd.Series], y=None, # ignored transforms: Optional[List[str]] = None, ) -> KMeans: - (X,) = utils.batch_convert_to_dataframe(X) + (X,) = utils.convert_to_dataframe(X) self._bqml_model = self._bqml_model_factory.create_model( X_train=X, @@ -126,57 +85,26 @@ def cluster_centers_(self) -> bpd.DataFrame: def predict( self, - X: utils.ArrayType, + X: Union[bpd.DataFrame, bpd.Series], ) -> bpd.DataFrame: if not self._bqml_model: raise RuntimeError("A model must be fitted before predict") - (X,) = utils.batch_convert_to_dataframe(X, session=self._bqml_model.session) - - return self._bqml_model.predict(X) + (X,) = utils.convert_to_dataframe(X) - def detect_anomalies( - self, - X: Union[bpd.DataFrame, bpd.Series, pd.DataFrame, pd.Series], - *, - contamination: float = 0.1, - ) -> bpd.DataFrame: - """Detect the anomaly data points of the input. - - Args: - X (bigframes.dataframe.DataFrame or bigframes.series.Series): - Series or a DataFrame to detect anomalies. - contamination (float, default 0.1): - Identifies the proportion of anomalies in the training dataset that are used to create the model. - The value must be in the range [0, 0.5]. - - Returns: - bigframes.dataframe.DataFrame: detected DataFrame.""" - if contamination < 0.0 or contamination > 0.5: - raise ValueError( - f"contamination must be [0.0, 0.5], but is {contamination}." - ) - - if not self._bqml_model: - raise RuntimeError("A model must be fitted before detect_anomalies") - - (X,) = utils.batch_convert_to_dataframe(X, session=self._bqml_model.session) - - return self._bqml_model.detect_anomalies( - X, options={"contamination": contamination} - ) + return cast(bpd.DataFrame, self._bqml_model.predict(X)[["CENTROID_ID"]]) def to_gbq(self, model_name: str, replace: bool = False) -> KMeans: """Save the model to BigQuery. Args: model_name (str): - The name of the model. + the name of the model. replace (bool, default False): - Determine whether to replace if the model already exists. Default to False. + whether to replace if the model already exists. Default to False. Returns: - KMeans: Saved model.""" + KMeans: saved model.""" if not self._bqml_model: raise RuntimeError("A model must be fitted before it can be saved") @@ -185,12 +113,12 @@ def to_gbq(self, model_name: str, replace: bool = False) -> KMeans: def score( self, - X: utils.ArrayType, + X: Union[bpd.DataFrame, bpd.Series], y=None, # ignored ) -> bpd.DataFrame: if not self._bqml_model: raise RuntimeError("A model must be fitted before score") - (X,) = utils.batch_convert_to_dataframe(X, session=self._bqml_model.session) + (X,) = utils.convert_to_dataframe(X) return self._bqml_model.evaluate(X) diff --git a/bigframes/ml/compose.py b/bigframes/ml/compose.py index 0d6c58897ac..bf046ff6914 100644 --- a/bigframes/ml/compose.py +++ b/bigframes/ml/compose.py @@ -13,180 +13,68 @@ # limitations under the License. """Build composite transformers on heterogeneous data. This module is styled -after scikit-Learn's compose module: +after Scikit-Learn's compose module: https://scikit-learn.org/stable/modules/classes.html#module-sklearn.compose.""" from __future__ import annotations -import re -import types import typing -from typing import Iterable, List, Optional, Set, Tuple, Union +from typing import List, Optional, Tuple, Union -import bigframes_vendored.sklearn.compose._column_transformer -from bigframes_vendored import constants -from google.cloud import bigquery - -import bigframes.core.utils as core_utils +from bigframes import constants +from bigframes.ml import base, core, globals, preprocessing, utils import bigframes.pandas as bpd -from bigframes.core.compile.sqlglot import sql as sg_sql -from bigframes.core.logging import log_adapter -from bigframes.ml import base, core, globals, impute, preprocessing, utils - -_BQML_TRANSFROM_TYPE_MAPPING = types.MappingProxyType( - { - "ML.STANDARD_SCALER": preprocessing.StandardScaler, - "ML.ONE_HOT_ENCODER": preprocessing.OneHotEncoder, - "ML.MAX_ABS_SCALER": preprocessing.MaxAbsScaler, - "ML.MIN_MAX_SCALER": preprocessing.MinMaxScaler, - "ML.BUCKETIZE": preprocessing.KBinsDiscretizer, - "ML.QUANTILE_BUCKETIZE": preprocessing.KBinsDiscretizer, - "ML.LABEL_ENCODER": preprocessing.LabelEncoder, - "ML.POLYNOMIAL_EXPAND": preprocessing.PolynomialFeatures, - "ML.IMPUTER": impute.SimpleImputer, - } -) - - -class SQLScalarColumnTransformer: - r""" - Wrapper for plain SQL code contained in a ColumnTransformer. - - Create a single column transformer in plain sql. - This transformer can only be used inside ColumnTransformer. - - When creating an instance '{0}' can be used as placeholder - for the column to transform: - - SQLScalarColumnTransformer("{0}+1") - - The default target column gets the prefix 'transformed\_' - but can also be changed when creating an instance: - - SQLScalarColumnTransformer("{0}+1", "inc_{0}") - - **Examples:** - - >>> from bigframes.ml.compose import ColumnTransformer, SQLScalarColumnTransformer - >>> import bigframes.pandas as bpd - - >>> df = bpd.DataFrame({'name': ["James", None, "Mary"], 'city': ["New York", "Boston", None]}) - >>> col_trans = ColumnTransformer([ - ... ("strlen", - ... SQLScalarColumnTransformer("CASE WHEN {0} IS NULL THEN 15 ELSE LENGTH({0}) END"), - ... ['name', 'city']), - ... ]) - >>> col_trans = col_trans.fit(df) - >>> df_transformed = col_trans.transform(df) - >>> df_transformed - transformed_name transformed_city - 0 5 8 - 1 15 6 - 2 4 15 - - [3 rows x 2 columns] - - SQLScalarColumnTransformer can be combined with other transformers, like StandardScaler: - - >>> col_trans = ColumnTransformer([ - ... ("identity", SQLScalarColumnTransformer("{0}", target_column="{0}"), ["col1", "col5"]), - ... ("increment", SQLScalarColumnTransformer("{0}+1", target_column="inc_{0}"), "col2"), - ... ("stdscale", preprocessing.StandardScaler(), "col3"), - ... # ... - ... ]) - - """ - - def __init__(self, sql: str, target_column: str = "transformed_{0}"): - super().__init__() - self._sql = sql - # TODO: More robust unescaping - self._target_column = target_column.replace("`", "") - - def _compile_to_sql( - self, X: bpd.DataFrame, columns: Optional[Iterable[str]] = None - ) -> List[str]: - if columns is None: - columns = X.columns - columns, _ = core_utils.get_standardized_ids(columns) - result = [] - for column in columns: - current_sql = self._sql.format(sg_sql.to_sql(sg_sql.identifier(column))) - current_target_column = sg_sql.to_sql( - sg_sql.identifier(self._target_column.format(column)) - ) - result.append(f"{current_sql} AS {current_target_column}") - return result - - def __repr__(self): - return f"SQLScalarColumnTransformer(sql='{self._sql}', target_column='{self._target_column}')" - - def __eq__(self, other) -> bool: - return type(self) is type(other) and self._keys() == other._keys() - - def __hash__(self) -> int: - return hash(self._keys()) - - def _keys(self): - return (self._sql, self._target_column) - - -# Type hints for transformers contained in ColumnTransformer -SingleColTransformer = Union[ - preprocessing.PreprocessingType, - impute.SimpleImputer, - SQLScalarColumnTransformer, +import third_party.bigframes_vendored.sklearn.compose._column_transformer + +CompilablePreprocessorType = Union[ + preprocessing.OneHotEncoder, + preprocessing.StandardScaler, + preprocessing.MaxAbsScaler, + preprocessing.MinMaxScaler, + preprocessing.KBinsDiscretizer, + preprocessing.LabelEncoder, ] -@log_adapter.class_logger class ColumnTransformer( base.Transformer, - bigframes_vendored.sklearn.compose._column_transformer.ColumnTransformer, + third_party.bigframes_vendored.sklearn.compose._column_transformer.ColumnTransformer, ): __doc__ = ( - bigframes_vendored.sklearn.compose._column_transformer.ColumnTransformer.__doc__ + third_party.bigframes_vendored.sklearn.compose._column_transformer.ColumnTransformer.__doc__ ) def __init__( self, - transformers: Iterable[ + transformers: List[ Tuple[ str, - SingleColTransformer, - Union[str, Iterable[str]], + CompilablePreprocessorType, + Union[str, List[str]], ] ], ): # TODO: if any(transformers) has fitted raise warning - self.transformers = list(transformers) + self.transformers = transformers self._bqml_model: Optional[core.BqmlModel] = None self._bqml_model_factory = globals.bqml_model_factory() # call self.transformers_ to check chained transformers self.transformers_ - def _keys(self): - return (self.transformers, self._bqml_model) - @property def transformers_( self, - ) -> List[ - Tuple[ - str, - SingleColTransformer, - str, - ] - ]: + ) -> List[Tuple[str, CompilablePreprocessorType, str,]]: """The collection of transformers as tuples of (name, transformer, column).""" result: List[ Tuple[ str, - SingleColTransformer, + CompilablePreprocessorType, str, ] ] = [] + column_set: set[str] = set() for entry in self.transformers: name, transformer, column_or_columns = entry columns = ( @@ -196,168 +84,62 @@ def transformers_( ) for column in columns: - result.append((name, transformer, column)) - - return result - - AS_FLEXNAME_SUFFIX_RX = re.compile("^(.*)\\bAS\\s*`[^`]+`\\s*$", re.IGNORECASE) - - @classmethod - def _extract_from_bq_model( - cls, - bq_model: bigquery.Model, - ) -> ColumnTransformer: - """Extract transformers as ColumnTransformer obj from a BQ Model. Keep the _bqml_model field as None.""" - assert "transformColumns" in bq_model._properties - - transformers_set: Set[ - Tuple[ - str, - SingleColTransformer, - Union[str, List[str]], - ] - ] = set() - - def camel_to_snake(name): - name = re.sub("(.)([A-Z][a-z]+)", r"\1_\2", name) - return re.sub("([a-z0-9])([A-Z])", r"\1_\2", name).lower() - - output_names = [] - for transform_col in bq_model._properties["transformColumns"]: - transform_col_dict = typing.cast(dict, transform_col) - # pass the columns that are not transformed - if "transformSql" not in transform_col_dict: - continue - transform_sql: str = transform_col_dict["transformSql"] - - # workaround for bug in bq_model returning " AS `...`" suffix for flexible names - flex_name_match = cls.AS_FLEXNAME_SUFFIX_RX.match(transform_sql) - if flex_name_match: - transform_sql = flex_name_match.group(1) - - output_names.append(transform_col_dict["name"]) - found_transformer = False - for prefix in _BQML_TRANSFROM_TYPE_MAPPING: - if transform_sql.startswith(prefix): - transformer_cls = _BQML_TRANSFROM_TYPE_MAPPING[prefix] - transformers_set.add( - ( - camel_to_snake(transformer_cls.__name__), - # TODO: This is very fragile, use real SQL parser - *transformer_cls._parse_from_sql(transform_sql), # type: ignore - ) - ) - - found_transformer = True - break - if not found_transformer: - if transform_sql.startswith("ML."): + if column in column_set: raise NotImplementedError( - f"Unsupported transformer type. {constants.FEEDBACK_LINK}" - ) - - target_column = transform_col_dict["name"] - sql_transformer = SQLScalarColumnTransformer( - transform_sql.strip(), target_column=target_column - ) - input_column_name = f"?{target_column}" - transformers_set.add( - ( - camel_to_snake(sql_transformer.__class__.__name__), - sql_transformer, - input_column_name, + f"Chained transformers on the same column isn't supported. {constants.FEEDBACK_LINK}" ) - ) - - transformer = cls(transformers=list(transformers_set)) - transformer._output_names = output_names - - return transformer - - def _merge( - self, bq_model: bigquery.Model - ) -> Union[ - ColumnTransformer, Union[preprocessing.PreprocessingType, impute.SimpleImputer] - ]: - """Try to merge the column transformer to a simple transformer. Depends on all the columns in bq_model are transformed with the same transformer.""" - transformers = self.transformers - - assert len(transformers) > 0 - _, transformer_0, column_0 = transformers[0] - if isinstance(transformer_0, SQLScalarColumnTransformer): - return self # SQLScalarColumnTransformer only work inside ColumnTransformer - feature_columns_sorted = sorted( - [ - typing.cast(str, feature_column.name) - for feature_column in bq_model.feature_columns - ] - ) - - if ( - len(transformers) == 1 - and isinstance(transformer_0, preprocessing.PolynomialFeatures) - and sorted(column_0) == feature_columns_sorted - ): - transformer_0._output_names = self._output_names - return transformer_0 - - if not isinstance(column_0, str): - return self - columns = [column_0] - for _, transformer, column in transformers[1:]: - if not isinstance(column, str): - return self - # all transformers are the same - if transformer != transformer_0: - return self - columns.append(column) - # all feature columns are transformed - if sorted(columns) == feature_columns_sorted: - transformer_0._output_names = self._output_names - return transformer_0 + result.append((name, transformer, column)) - return self + return result def _compile_to_sql( self, + columns: List[str], X: bpd.DataFrame, - ) -> List[str]: + ) -> List[Tuple[str, str]]: """Compile this transformer to a list of SQL expressions that can be included in a BQML TRANSFORM clause Args: - X: DataFrame to transform. - - Returns: a list of sql_expr.""" - result = [] - for _, transformer, target_columns in self.transformers: - if isinstance(target_columns, str): - target_columns = [target_columns] - result += transformer._compile_to_sql(X, target_columns) - return result + columns (List[str]): + a list of column names to transform + X (bpd.DataFrame): + The Dataframe with training data. + + Returns: + a list of tuples of (sql_expression, output_name)""" + return [ + transformer._compile_to_sql([column], X=X)[0] + for column in columns + for _, transformer, target_column in self.transformers_ + if column == target_column + ] def fit( self, - X: utils.ArrayType, + X: Union[bpd.DataFrame, bpd.Series], y=None, # ignored ) -> ColumnTransformer: - (X,) = utils.batch_convert_to_dataframe(X) + (X,) = utils.convert_to_dataframe(X) + + compiled_transforms = self._compile_to_sql(X.columns.tolist(), X) + transform_sqls = [transform_sql for transform_sql, _ in compiled_transforms] - transform_sqls = self._compile_to_sql(X) self._bqml_model = self._bqml_model_factory.create_model( X, options={"model_type": "transform_only"}, transforms=transform_sqls, ) - self._extract_output_names() + # The schema of TRANSFORM output is not available in the model API, so save it during fitting + self._output_names = [name for _, name in compiled_transforms] return self - def transform(self, X: utils.ArrayType) -> bpd.DataFrame: + def transform(self, X: Union[bpd.DataFrame, bpd.Series]) -> bpd.DataFrame: if not self._bqml_model: raise RuntimeError("Must be fitted before transform") - (X,) = utils.batch_convert_to_dataframe(X, session=self._bqml_model.session) + (X,) = utils.convert_to_dataframe(X) df = self._bqml_model.transform(X) return typing.cast( diff --git a/bigframes/ml/core.py b/bigframes/ml/core.py index 5a096f305bd..4c5a48cf625 100644 --- a/bigframes/ml/core.py +++ b/bigframes/ml/core.py @@ -16,70 +16,50 @@ from __future__ import annotations -import dataclasses -import datetime -import typing +from typing import Callable, cast, Iterable, Mapping, Optional, Union import uuid -from typing import Callable, Iterable, Mapping, Optional, Union from google.cloud import bigquery -import bigframes.constants as constants -import bigframes.formatting_helpers as formatting_helpers -import bigframes.pandas as bpd -import bigframes.session +import bigframes from bigframes.ml import sql as ml_sql +import bigframes.pandas as bpd -class BaseBqml: - """Base class for BQML functionalities.""" - - def __init__(self, session: bigframes.session.Session): - self._session = session - 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 - ) - - # TODO(b/395912450): Once the limitations with local data are - # resolved, consider setting allow_large_results only when expected - # data size is large. - return self._session.read_gbq_query(result_sql, allow_large_results=True) - - -class BqmlModel(BaseBqml): +class BqmlModel: """Represents an existing BQML model in BigQuery. Wraps the BQML API and SQL interface to expose the functionality needed for BigQuery DataFrames ML. """ - @dataclasses.dataclass - class TvfDef: - tvf: Callable[[BqmlModel, bpd.DataFrame, dict], bpd.DataFrame] - status_col: str - def __init__(self, session: bigframes.Session, model: bigquery.Model): self._session = session self._model = model - model_ref = self._model.reference - assert model_ref is not None - self._sql_generator: ml_sql.ModelManipulationSqlGenerator = ( - ml_sql.ModelManipulationSqlGenerator(model_ref) + self._model_manipulation_sql_generator = ml_sql.ModelManipulationSqlGenerator( + self.model_name ) - def _apply_ml_tvf( + @property + def session(self) -> bigframes.Session: + """Get the BigQuery DataFrames session that this BQML model wrapper is tied to""" + return self._session + + @property + def model_name(self) -> str: + """Get the fully qualified name of the model, i.e. project_id.dataset_id.model_id""" + return f"{self._model.project}.{self._model.dataset_id}.{self._model.model_id}" + + @property + def model(self) -> bigquery.Model: + """Get the BQML model associated with this wrapper""" + return self._model + + def _apply_sql( self, input_data: bpd.DataFrame, - apply_sql_tvf: Callable[[str], str], + func: Callable[[bpd.DataFrame], str], ) -> bpd.DataFrame: - # Used for predict, transform, distance """Helper to wrap a dataframe in a SQL query, keeping the index intact. Args: @@ -90,286 +70,100 @@ def _apply_ml_tvf( the dataframe to be wrapped func (function): - Takes an input sql table value and applies a prediction tvf. The - resulting table value must include all input columns, with new - columns appended to the end. + a function that will accept a SQL string and produce a new SQL + string from which to construct the output dataframe. It must + include the index columns of the input SQL. """ - # TODO: Preserve ordering information? - input_sql, index_col_ids, index_labels = input_data._to_sql_query( - include_index=True - ) + _, index_col_ids, index_labels = input_data._to_sql_query(include_index=True) - result_sql = apply_sql_tvf(input_sql) - df = self._session.read_gbq_query( - result_sql, - index_col=index_col_ids, - # Many ML methods use nested JSON, which isn't yet compatible with - # joining local results. Also, there is a chance that the results - # are greater than 10 GB. - # TODO(b/395912450): Once the limitations with local data are - # resolved, consider setting allow_large_results only when expected - # data size is large. - allow_large_results=True, - ) - if df._has_index: - df.index.names = index_labels - # Restore column labels - df.rename( - columns={ - label: original_label - for label, original_label in zip( - df.columns.values, input_data.columns.values - ) - } - ) - return df + sql = func(input_data) + df = self._session.read_gbq(sql, index_col=index_col_ids) + df.index.names = index_labels - def _keys(self): - return (self._session, self._model) - - def __eq__(self, other): - return isinstance(other, self.__class__) and self._keys() == other._keys() - - def __hash__(self): - return hash(self._keys()) - - @property - def session(self) -> bigframes.Session: - """Get the BigQuery DataFrames session that this BQML model wrapper is tied to""" - return self._session - - @property - def model_name(self) -> str: - """Get the fully qualified name of the model, i.e. project_id.dataset_id.model_id""" - return f"{self._model.project}.{self._model.dataset_id}.{self._model.model_id}" - - @property - def model(self) -> bigquery.Model: - """Get the BQML model associated with this wrapper""" - return self._model - - def recommend(self, input_data: bpd.DataFrame) -> bpd.DataFrame: - return self._apply_ml_tvf( - input_data, - self._sql_generator.ml_recommend, - ) + return df def predict(self, input_data: bpd.DataFrame) -> bpd.DataFrame: - return self._apply_ml_tvf( + # TODO: validate input data schema + return self._apply_sql( input_data, - self._sql_generator.ml_predict, - ) - - def explain_predict( - self, input_data: bpd.DataFrame, options: Mapping[str, int | float] - ) -> bpd.DataFrame: - return self._apply_ml_tvf( - input_data, - 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._sql_generator.ml_global_explain(struct_options=options) - return ( - # TODO(b/395912450): Once the limitations with local data are - # resolved, consider setting allow_large_results only when expected - # data size is large. - self._session.read_gbq_query(sql, allow_large_results=True) - .sort_values(by="attribution", ascending=False) - .set_index("feature") + self._model_manipulation_sql_generator.ml_predict, ) def transform(self, input_data: bpd.DataFrame) -> bpd.DataFrame: - return self._apply_ml_tvf( + # TODO: validate input data schema + return self._apply_sql( input_data, - self._sql_generator.ml_transform, + self._model_manipulation_sql_generator.ml_transform, ) def generate_text( self, input_data: bpd.DataFrame, - options: dict[str, Union[int, float, bool]], + options: Mapping[str, int | float], ) -> bpd.DataFrame: - options["flatten_json_output"] = True - return self._apply_ml_tvf( + # TODO: validate input data schema + return self._apply_sql( input_data, - lambda source_sql: self._sql_generator.ml_generate_text( - source_sql=source_sql, + lambda source_df: self._model_manipulation_sql_generator.ml_generate_text( + source_df=source_df, struct_options=options, ), ) - generate_text_tvf = TvfDef(generate_text, "ml_generate_text_status") - - def generate_embedding( - self, - input_data: bpd.DataFrame, - options: dict[str, Union[int, float, bool]], - ) -> bpd.DataFrame: - options["flatten_json_output"] = True - return self._apply_ml_tvf( - input_data, - lambda source_sql: self._sql_generator.ml_generate_embedding( - source_sql=source_sql, - struct_options=options, - ), - ) - - generate_embedding_tvf = TvfDef(generate_embedding, "ml_generate_embedding_status") - - def generate_table( + def generate_text_embedding( self, input_data: bpd.DataFrame, - options: dict[str, Union[int, float, bool, Mapping]], + options: Mapping[str, int | float], ) -> bpd.DataFrame: - return self._apply_ml_tvf( + # TODO: validate input data schema + return self._apply_sql( input_data, - lambda source_sql: self._sql_generator.ai_generate_table( - source_sql=source_sql, + lambda source_df: self._model_manipulation_sql_generator.ml_generate_text_embedding( + source_df=source_df, struct_options=options, ), ) - generate_table_tvf = TvfDef(generate_table, "status") - - def detect_anomalies( - self, input_data: bpd.DataFrame, options: Mapping[str, int | float] - ) -> bpd.DataFrame: - assert self._model.model_type in ("PCA", "KMEANS", "ARIMA_PLUS") - - return self._apply_ml_tvf( - input_data, - 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._sql_generator.ml_forecast(struct_options=options) - timestamp_col_name = "forecast_timestamp" - index_cols = [timestamp_col_name] - # TODO(b/395912450): Once the limitations with local data are - # resolved, consider setting allow_large_results only when expected - # data size is large. - first_col_name = self._session.read_gbq_query( - sql, allow_large_results=True - ).columns.values[0] - if timestamp_col_name != first_col_name: - index_cols.append(first_col_name) - # TODO(b/395912450): Once the limitations with local data are - # resolved, consider setting allow_large_results only when expected - # data size is large. - return self._session.read_gbq_query( - sql, index_col=index_cols, allow_large_results=True - ).reset_index() - - def explain_forecast(self, options: Mapping[str, int | float]) -> bpd.DataFrame: - sql = self._sql_generator.ml_explain_forecast(struct_options=options) - timestamp_col_name = "time_series_timestamp" - index_cols = [timestamp_col_name] - # TODO(b/395912450): Once the limitations with local data are - # resolved, consider setting allow_large_results only when expected - # data size is large. - first_col_name = self._session.read_gbq_query( - sql, allow_large_results=True - ).columns.values[0] - if timestamp_col_name != first_col_name: - index_cols.append(first_col_name) - # TODO(b/395912450): Once the limitations with local data are - # resolved, consider setting allow_large_results only when expected - # data size is large. - return self._session.read_gbq_query( - sql, index_col=index_cols, allow_large_results=True - ).reset_index() + def forecast(self) -> bpd.DataFrame: + sql = self._model_manipulation_sql_generator.ml_forecast() + return self._session.read_gbq(sql) def evaluate(self, input_data: Optional[bpd.DataFrame] = None): - sql = self._sql_generator.ml_evaluate( - input_data.sql if (input_data is not None) else None - ) + # TODO: validate input data schema + sql = self._model_manipulation_sql_generator.ml_evaluate(input_data) - # TODO(b/395912450): Once the limitations with local data are - # resolved, consider setting allow_large_results only when expected - # data size is large. - return self._session.read_gbq_query(sql, allow_large_results=True) - - def llm_evaluate( - self, - input_data: bpd.DataFrame, - task_type: Optional[str] = None, - ): - sql = self._sql_generator.ml_llm_evaluate(input_data.sql, task_type) - - # TODO(b/395912450): Once the limitations with local data are - # resolved, consider setting allow_large_results only when expected - # data size is large. - return self._session.read_gbq_query(sql, allow_large_results=True) - - def arima_evaluate(self, show_all_candidate_models: bool = False): - sql = self._sql_generator.ml_arima_evaluate(show_all_candidate_models) - - # TODO(b/395912450): Once the limitations with local data are - # resolved, consider setting allow_large_results only when expected - # data size is large. - return self._session.read_gbq_query(sql, allow_large_results=True) - - def arima_coefficients(self) -> bpd.DataFrame: - sql = self._sql_generator.ml_arima_coefficients() - - # TODO(b/395912450): Once the limitations with local data are - # resolved, consider setting allow_large_results only when expected - # data size is large. - return self._session.read_gbq_query(sql, allow_large_results=True) + return self._session.read_gbq(sql) def centroids(self) -> bpd.DataFrame: assert self._model.model_type == "KMEANS" - sql = self._sql_generator.ml_centroids() + sql = self._model_manipulation_sql_generator.ml_centroids() - # TODO(b/395912450): Once the limitations with local data are - # resolved, consider setting allow_large_results only when expected - # data size is large. - return self._session.read_gbq_query( - sql, index_col=["centroid_id", "feature"], allow_large_results=True - ).reset_index() + return self._session.read_gbq(sql) def principal_components(self) -> bpd.DataFrame: assert self._model.model_type == "PCA" - sql = self._sql_generator.ml_principal_components() + sql = self._model_manipulation_sql_generator.ml_principal_components() - # TODO(b/395912450): Once the limitations with local data are - # resolved, consider setting allow_large_results only when expected - # data size is large. - return self._session.read_gbq_query( - sql, - index_col=["principal_component_id", "feature"], - allow_large_results=True, - ).reset_index() + return self._session.read_gbq(sql) def principal_component_info(self) -> bpd.DataFrame: assert self._model.model_type == "PCA" - sql = self._sql_generator.ml_principal_component_info() + sql = self._model_manipulation_sql_generator.ml_principal_component_info() - # TODO(b/395912450): Once the limitations with local data are - # resolved, consider setting allow_large_results only when expected - # data size is large. - return self._session.read_gbq_query(sql, allow_large_results=True) + return self._session.read_gbq(sql) def copy(self, new_model_name: str, replace: bool = False) -> BqmlModel: - job_config = self._session._prepare_copy_job_config() - + job_config = bigquery.job.CopyJobConfig() if replace: job_config.write_disposition = "WRITE_TRUNCATE" copy_job = self._session.bqclient.copy_table( self.model_name, new_model_name, job_config=job_config ) - _start_generic_job(copy_job) + self._session._start_generic_job(copy_job) new_model = self._session.bqclient.get_model(new_model_name) return BqmlModel(self._session, new_model) @@ -377,16 +171,16 @@ def copy(self, new_model_name: str, replace: bool = False) -> BqmlModel: def register(self, vertex_ai_model_id: Optional[str] = None) -> BqmlModel: if vertex_ai_model_id is None: # vertex id needs to start with letters. https://cloud.google.com/vertex-ai/docs/general/resource-naming - vertex_ai_model_id = "bigframes_" + typing.cast(str, self._model.model_id) + vertex_ai_model_id = "bigframes_" + cast(str, self._model.model_id) # 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._sql_generator.alter_model( + sql = self._model_manipulation_sql_generator.alter_model( options={"vertex_ai_model_id": vertex_ai_model_id} ) # Register the model and wait it to finish - self._session._start_query_ml_ddl(sql) + self._session._start_query(sql) self._model = self._session.bqclient.get_model(self.model_name) return self @@ -394,27 +188,24 @@ def register(self, vertex_ai_model_id: Optional[str] = None) -> BqmlModel: class BqmlModelFactory: def __init__(self): - self._model_creation_sql_generator = ml_sql.ModelCreationSqlGenerator() + model_id = self._create_temp_model_id() + self._model_creation_sql_generator = ml_sql.ModelCreationSqlGenerator(model_id) - def _create_model_ref( - self, dataset: bigquery.DatasetReference - ) -> bigquery.ModelReference: - return bigquery.ModelReference.from_string( - f"{dataset.project}.{dataset.dataset_id}.{uuid.uuid4().hex}" - ) + def _create_temp_model_id(self) -> str: + return uuid.uuid4().hex + + def _reset_model_id(self): + self._model_creation_sql_generator._model_id = self._create_temp_model_id() def _create_model_with_sql(self, session: bigframes.Session, sql: str) -> BqmlModel: # fit the model, synchronously - _, job = session._start_query_ml_ddl(sql) + _, job = session._start_query(sql) # real model path in the session specific hidden dataset and table prefix - model_name_full = f"{job.destination.project}.{job.destination.dataset_id}.{job.destination.table_id}" - model = bigquery.Model(model_name_full) - model.expires = ( - datetime.datetime.now(datetime.timezone.utc) + constants.DEFAULT_EXPIRATION - ) - model = session.bqclient.update_model(model, ["expires"]) + model_name_full = f"{job.destination.dataset_id}.{job.destination.table_id}" + model = session.bqclient.get_model(model_name_full) + self._reset_model_id() return BqmlModel(session, model) def create_model( @@ -424,7 +215,7 @@ def create_model( transforms: Optional[Iterable[str]] = None, options: Mapping[str, Union[str, int, float, Iterable[str]]] = {}, ) -> BqmlModel: - """Create a session-temporary BQML model with the CREATE OR REPLACE MODEL statement + """Create a session-temporary BQML model with the CREATE MODEL statement Args: X_train: features columns for training @@ -437,106 +228,45 @@ def create_model( Returns: a BqmlModel, wrapping a trained model in BigQuery """ options = dict(options) - # Cache dataframes to make sure base table is not a snapshot. - # Cached dataframe creates a full copy, never uses snapshot. - # This is a workaround for internal issue b/310266666. if y_train is None: - input_data = X_train.reset_index(drop=True).cache() + input_data = X_train else: - input_data = ( - X_train.join(y_train, how="outer").reset_index(drop=True).cache() - ) + input_data = X_train.join(y_train, how="outer") options.update({"INPUT_LABEL_COLS": y_train.columns.tolist()}) session = X_train._session - if session._bq_kms_key_name: - options.update({"kms_key_name": session._bq_kms_key_name}) - - model_ref = self._create_model_ref(session._anonymous_dataset) sql = self._model_creation_sql_generator.create_model( - source_sql=input_data.sql, - model_ref=model_ref, + source_df=input_data, transforms=transforms, options=options, ) return self._create_model_with_sql(session=session, sql=sql) - def create_llm_remote_model( - self, - X_train: bpd.DataFrame, - y_train: bpd.DataFrame, - connection_name: str, - options: Mapping[str, Union[str, int, float, Iterable[str]]] = {}, - ) -> BqmlModel: - """Create a session-temporary BQML model with the CREATE OR REPLACE MODEL statement - - Args: - X_train: features columns for training - y_train: labels columns for training - options: a dict of options to configure the model. Generates a BQML OPTIONS - clause - connection_name: - a BQ connection to talk with Vertex AI, of the format ... https://cloud.google.com/bigquery/docs/create-cloud-resource-connection - - Returns: a BqmlModel, wrapping a trained model in BigQuery - """ - options = dict(options) - # Cache dataframes to make sure base table is not a snapshot - # cached dataframe creates a full copy, never uses snapshot - input_data = X_train.join(y_train, how="outer").cache() - options.update({"INPUT_LABEL_COLS": y_train.columns.tolist()}) - - session = X_train._session - - model_ref = self._create_model_ref(session._anonymous_dataset) - - sql = self._model_creation_sql_generator.create_llm_remote_model( - source_sql=input_data.sql, - model_ref=model_ref, - options=options, - connection_name=connection_name, - ) - - return self._create_model_with_sql(session=session, sql=sql) - def create_time_series_model( self, X_train: bpd.DataFrame, y_train: bpd.DataFrame, - id_col: Optional[bpd.DataFrame] = None, transforms: Optional[Iterable[str]] = None, options: Mapping[str, Union[str, int, float, Iterable[str]]] = {}, ) -> BqmlModel: - assert X_train.columns.size == 1, ( - "Time series timestamp input must only contain 1 column." - ) - assert y_train.columns.size == 1, ( - "Time stamp data input must only contain 1 column." - ) - assert id_col is None or (id_col is not None and id_col.columns.size == 1), ( - "Time series id input is either None or must only contain 1 column." - ) + assert ( + X_train.columns.size == 1 + ), "Time series timestamp input must only contain 1 column." + assert ( + y_train.columns.size == 1 + ), "Time stamp data input must only contain 1 column." options = dict(options) - # Cache dataframes to make sure base table is not a snapshot - # cached dataframe creates a full copy, never uses snapshot input_data = X_train.join(y_train, how="outer") - if id_col is not None: - input_data = input_data.join(id_col, how="outer") - input_data = input_data.cache() options.update({"TIME_SERIES_TIMESTAMP_COL": X_train.columns.tolist()[0]}) options.update({"TIME_SERIES_DATA_COL": y_train.columns.tolist()[0]}) - if id_col is not None: - options.update({"TIME_SERIES_ID_COL": id_col.columns.tolist()[0]}) session = X_train._session - model_ref = self._create_model_ref(session._anonymous_dataset) sql = self._model_creation_sql_generator.create_model( - source_sql=input_data.sql, - model_ref=model_ref, + source_df=input_data, transforms=transforms, options=options, ) @@ -547,31 +277,21 @@ def create_remote_model( self, session: bigframes.Session, connection_name: str, - input: Mapping[str, str] = {}, - output: Mapping[str, str] = {}, options: Mapping[str, Union[str, int, float, Iterable[str]]] = {}, ) -> BqmlModel: - """Create a session-temporary BQML remote model with the CREATE OR REPLACE MODEL statement + """Create a session-temporary BQML remote model with the CREATE MODEL statement Args: connection_name: a BQ connection to talk with Vertex AI, of the format ... https://cloud.google.com/bigquery/docs/create-cloud-resource-connection - input: - input schema for general remote models - output: - output schema for general remote models options: a dict of options to configure the model. Generates a BQML OPTIONS clause Returns: BqmlModel: a BqmlModel wrapping a trained model in BigQuery """ - model_ref = self._create_model_ref(session._anonymous_dataset) sql = self._model_creation_sql_generator.create_remote_model( connection_name=connection_name, - model_ref=model_ref, - input=input, - output=output, options=options, ) @@ -582,7 +302,7 @@ def create_imported_model( session: bigframes.Session, options: Mapping[str, Union[str, int, float, Iterable[str]]] = {}, ) -> BqmlModel: - """Create a session-temporary BQML imported model with the CREATE OR REPLACE MODEL statement + """Create a session-temporary BQML imported model with the CREATE MODEL statement Args: options: a dict of options to configure the model. Generates a BQML OPTIONS @@ -590,49 +310,8 @@ def create_imported_model( Returns: a BqmlModel, wrapping a trained model in BigQuery """ - model_ref = self._create_model_ref(session._anonymous_dataset) sql = self._model_creation_sql_generator.create_imported_model( - model_ref=model_ref, options=options, ) return self._create_model_with_sql(session=session, sql=sql) - - def create_xgboost_imported_model( - self, - session: bigframes.Session, - input: Mapping[str, str] = {}, - output: Mapping[str, str] = {}, - options: Mapping[str, Union[str, int, float, Iterable[str]]] = {}, - ) -> BqmlModel: - """Create a session-temporary BQML imported model with the CREATE OR REPLACE MODEL statement - - Args: - input: - input schema for imported xgboost models - output: - output schema for imported xgboost models - options: a dict of options to configure the model. Generates a BQML OPTIONS - clause - - Returns: a BqmlModel, wrapping a trained model in BigQuery - """ - model_ref = self._create_model_ref(session._anonymous_dataset) - - sql = self._model_creation_sql_generator.create_xgboost_imported_model( - model_ref=model_ref, - input=input, - output=output, - options=options, - ) - - return self._create_model_with_sql(session=session, sql=sql) - - -def _start_generic_job(job: formatting_helpers.GenericJob): - if bigframes.options.display.progress_bar is not None: - formatting_helpers.wait_for_job( - job, bigframes.options.display.progress_bar - ) # Wait for the job to complete - else: - job.result() diff --git a/bigframes/ml/decomposition.py b/bigframes/ml/decomposition.py index eedf6c09170..8e6be6d28c3 100644 --- a/bigframes/ml/decomposition.py +++ b/bigframes/ml/decomposition.py @@ -17,97 +17,57 @@ from __future__ import annotations -from typing import List, Literal, Optional, Union +from typing import cast, List, Optional, Union -import bigframes_vendored.sklearn.decomposition._mf -import bigframes_vendored.sklearn.decomposition._pca from google.cloud import bigquery -import bigframes.pandas as bpd -import bigframes.session -from bigframes.core.logging import log_adapter +import bigframes from bigframes.ml import base, core, globals, utils - -_BQML_PARAMS_MAPPING = { - "svd_solver": "pcaSolver", - "feedback_type": "feedbackType", - "num_factors": "numFactors", - "user_col": "userColumn", - "item_col": "itemColumn", - "_input_label_columns": "inputLabelColumns", - "l2_reg": "l2Regularization", -} +import bigframes.pandas as bpd +import third_party.bigframes_vendored.sklearn.decomposition._pca -@log_adapter.class_logger class PCA( base.UnsupervisedTrainablePredictor, - bigframes_vendored.sklearn.decomposition._pca.PCA, + third_party.bigframes_vendored.sklearn.decomposition._pca.PCA, ): - __doc__ = bigframes_vendored.sklearn.decomposition._pca.PCA.__doc__ + __doc__ = third_party.bigframes_vendored.sklearn.decomposition._pca.PCA.__doc__ - def __init__( - self, - n_components: Optional[Union[int, float]] = None, - *, - svd_solver: Literal["full", "randomized", "auto"] = "auto", - ): + def __init__(self, n_components: int = 3): self.n_components = n_components - self.svd_solver = svd_solver self._bqml_model: Optional[core.BqmlModel] = None self._bqml_model_factory = globals.bqml_model_factory() @classmethod - def _from_bq( - cls, session: bigframes.session.Session, bq_model: bigquery.Model - ) -> PCA: - assert bq_model.model_type == "PCA" + def _from_bq(cls, session: bigframes.Session, model: bigquery.Model) -> PCA: + assert model.model_type == "PCA" - kwargs = utils.retrieve_params_from_bq_model( - cls, bq_model, _BQML_PARAMS_MAPPING - ) + kwargs = {} - last_fitting = bq_model.training_runs[-1]["trainingOptions"] + # See https://cloud.google.com/bigquery/docs/reference/rest/v2/models#trainingrun + last_fitting = model.training_runs[-1]["trainingOptions"] if "numPrincipalComponents" in last_fitting: kwargs["n_components"] = int(last_fitting["numPrincipalComponents"]) - elif "pcaExplainedVarianceRatio" in last_fitting: - kwargs["n_components"] = float(last_fitting["pcaExplainedVarianceRatio"]) - model = cls(**kwargs) - model._bqml_model = core.BqmlModel(session, bq_model) - return model - - @property - def _bqml_options(self) -> dict: - """The model options as they will be set for BQML""" - options: dict = { - "model_type": "PCA", - "pca_solver": self.svd_solver, - } - - assert self.n_components is not None - if 0 < self.n_components < 1: - options["pca_explained_variance_ratio"] = float(self.n_components) - elif self.n_components >= 1: - options["num_principal_components"] = int(self.n_components) - - return options + new_pca = cls(**kwargs) + new_pca._bqml_model = core.BqmlModel(session, model) + return new_pca def _fit( self, - X: utils.ArrayType, + X: Union[bpd.DataFrame, bpd.Series], y=None, transforms: Optional[List[str]] = None, ) -> PCA: - (X,) = utils.batch_convert_to_dataframe(X) + (X,) = utils.convert_to_dataframe(X) - # To mimic sklearn's behavior - if self.n_components is None: - self.n_components = min(X.shape) self._bqml_model = self._bqml_model_factory.create_model( X_train=X, transforms=transforms, - options=self._bqml_options, + options={ + "model_type": "PCA", + "num_principal_components": self.n_components, + }, ) return self @@ -140,43 +100,17 @@ def explained_variance_ratio_(self) -> bpd.DataFrame: ["principal_component_id", "explained_variance_ratio"] ] - def predict(self, X: utils.ArrayType) -> bpd.DataFrame: + def predict(self, X: Union[bpd.DataFrame, bpd.Series]) -> bpd.DataFrame: if not self._bqml_model: raise RuntimeError("A model must be fitted before predict") - (X,) = utils.batch_convert_to_dataframe(X, session=self._bqml_model.session) + (X,) = utils.convert_to_dataframe(X) - return self._bqml_model.predict(X) - - def detect_anomalies( - self, - X: utils.ArrayType, - *, - contamination: float = 0.1, - ) -> bpd.DataFrame: - """Detect the anomaly data points of the input. - - Args: - X (bigframes.dataframe.DataFrame or bigframes.series.Series): - Series or a DataFrame to detect anomalies. - contamination (float, default 0.1): - Identifies the proportion of anomalies in the training dataset that are used to create the model. - The value must be in the range [0, 0.5]. - - Returns: - bigframes.dataframe.DataFrame: detected DataFrame.""" - if contamination < 0.0 or contamination > 0.5: - raise ValueError( - f"contamination must be [0.0, 0.5], but is {contamination}." - ) - - if not self._bqml_model: - raise RuntimeError("A model must be fitted before detect_anomalies") - - (X,) = utils.batch_convert_to_dataframe(X, session=self._bqml_model.session) - - return self._bqml_model.detect_anomalies( - X, options={"contamination": contamination} + return cast( + bpd.DataFrame, + self._bqml_model.predict(X)[ + ["principal_component_" + str(i + 1) for i in range(self.n_components)] + ], ) def to_gbq(self, model_name: str, replace: bool = False) -> PCA: @@ -184,12 +118,12 @@ def to_gbq(self, model_name: str, replace: bool = False) -> PCA: Args: model_name (str): - The name of the model. + the name of the model. replace (bool, default False): - Determine whether to replace if the model already exists. Default to False. + whether to replace if the model already exists. Default to False. Returns: - PCA: Saved model.""" + PCA: saved model.""" if not self._bqml_model: raise RuntimeError("A model must be fitted before it can be saved") @@ -204,167 +138,5 @@ def score( if not self._bqml_model: raise RuntimeError("A model must be fitted before score") - # TODO(b/291973741): X param is ignored. Update BQML supports input in ML.EVALUATE. + # TODO(b/291973741): X param is ignored. Update BQML supports input in ML.EVALUTE. return self._bqml_model.evaluate() - - -@log_adapter.class_logger -class MatrixFactorization( - base.UnsupervisedTrainablePredictor, - bigframes_vendored.sklearn.decomposition._mf.MatrixFactorization, -): - __doc__ = bigframes_vendored.sklearn.decomposition._mf.MatrixFactorization.__doc__ - - def __init__( - self, - *, - feedback_type: Literal["explicit", "implicit"] = "explicit", - num_factors: int, - user_col: str, - item_col: str, - rating_col: str = "rating", - # TODO: Add support for hyperparameter tuning. - l2_reg: float = 1.0, - ): - feedback_type = feedback_type.lower() # type: ignore - if feedback_type not in ("explicit", "implicit"): - raise ValueError("Expected feedback_type to be `explicit` or `implicit`.") - - self.feedback_type = feedback_type - - if not isinstance(num_factors, int): - raise TypeError( - f"Expected num_factors to be an int, but got {type(num_factors)}." - ) - - if num_factors < 0: - raise ValueError( - f"Expected num_factors to be a positive integer, but got {num_factors}." - ) - - self.num_factors = num_factors - - if not isinstance(user_col, str): - raise TypeError(f"Expected user_col to be a str, but got {type(user_col)}.") - - self.user_col = user_col - - if not isinstance(item_col, str): - raise TypeError(f"Expected item_col to be STR, but got {type(item_col)}.") - - self.item_col = item_col - - if not isinstance(rating_col, str): - raise TypeError( - f"Expected rating_col to be a str, but got {type(rating_col)}." - ) - - self._input_label_columns = [rating_col] - - if not isinstance(l2_reg, (float, int)): - raise TypeError( - f"Expected l2_reg to be a float or int, but got {type(l2_reg)}." - ) - - self.l2_reg = l2_reg - self._bqml_model: Optional[core.BqmlModel] = None - self._bqml_model_factory = globals.bqml_model_factory() - - @property - def rating_col(self) -> str: - """str: The rating column name. Defaults to 'rating'.""" - return self._input_label_columns[0] - - @classmethod - def _from_bq( - cls, session: bigframes.session.Session, bq_model: bigquery.Model - ) -> MatrixFactorization: - assert bq_model.model_type == "MATRIX_FACTORIZATION" - - kwargs = utils.retrieve_params_from_bq_model( - cls, bq_model, _BQML_PARAMS_MAPPING - ) - - model = cls(**kwargs) - model._bqml_model = core.BqmlModel(session, bq_model) - return model - - @property - def _bqml_options(self) -> dict: - """The model options as they will be set for BQML""" - options: dict = { - "model_type": "matrix_factorization", - "feedback_type": self.feedback_type, - "user_col": self.user_col, - "item_col": self.item_col, - "rating_col": self.rating_col, - "l2_reg": self.l2_reg, - } - - if self.num_factors is not None: - options["num_factors"] = self.num_factors - - return options - - def _fit( - self, - X: utils.ArrayType, - y=None, - transforms: Optional[List[str]] = None, - ) -> MatrixFactorization: - if y is not None: - raise ValueError( - "Label column not supported for Matrix Factorization model but y was not `None`" - ) - - (X,) = utils.batch_convert_to_dataframe(X) - - self._bqml_model = self._bqml_model_factory.create_model( - X_train=X, - transforms=transforms, - options=self._bqml_options, - ) - return self - - def predict(self, X: utils.ArrayType) -> bpd.DataFrame: - if not self._bqml_model: - raise RuntimeError("A model must be fitted before recommend") - - (X,) = utils.batch_convert_to_dataframe(X, session=self._bqml_model.session) - - return self._bqml_model.recommend(X) - - def to_gbq(self, model_name: str, replace: bool = False) -> MatrixFactorization: - """Save the model to BigQuery. - - Args: - model_name (str): - The name of the model. - replace (bool, default False): - Determine whether to replace if the model already exists. Default to False. - - Returns: - MatrixFactorization: Saved model.""" - if not self._bqml_model: - raise RuntimeError("A model must be fitted before it can be saved") - - new_model = self._bqml_model.copy(model_name, replace) - return new_model.session.read_gbq_model(model_name) - - def score( - self, - X=None, - y=None, - ) -> bpd.DataFrame: - if not self._bqml_model: - raise RuntimeError("A model must be fitted before score") - - if X is not None and y is not None: - X, y = utils.batch_convert_to_dataframe( - X, y, session=self._bqml_model.session - ) - input_data = X.join(y, how="outer") - else: - input_data = X - - return self._bqml_model.evaluate(input_data) diff --git a/bigframes/ml/ensemble.py b/bigframes/ml/ensemble.py index 5d2b130d7ae..19ca8608ffb 100644 --- a/bigframes/ml/ensemble.py +++ b/bigframes/ml/ensemble.py @@ -12,55 +12,50 @@ # See the License for the specific language governing permissions and # limitations under the License. -"""Ensemble models. This module is styled after scikit-learn's ensemble module: +"""Ensemble models. This module is styled after Scikit-Learn's ensemble module: https://scikit-learn.org/stable/modules/ensemble.html""" from __future__ import annotations -from typing import Dict, List, Literal, Optional +from typing import cast, Dict, List, Literal, Optional, Union -import bigframes_vendored.sklearn.ensemble._forest -import bigframes_vendored.xgboost.sklearn from google.cloud import bigquery -import bigframes.dataframe -import bigframes.session -from bigframes.core.logging import log_adapter +import bigframes from bigframes.ml import base, core, globals, utils +import bigframes.pandas as bpd +import third_party.bigframes_vendored.sklearn.ensemble._forest +import third_party.bigframes_vendored.xgboost.sklearn _BQML_PARAMS_MAPPING = { "booster": "boosterType", - "dart_normalized_type": "dartNormalizeType", "tree_method": "treeMethod", - "colsample_bytree": "colsampleBytree", - "colsample_bylevel": "colsampleBylevel", + "early_stop": "earlyStop", + "colsample_bytree": "colsampleBylevel", + "colsample_bylevel": "colsampleBytree", "colsample_bynode": "colsampleBynode", "gamma": "minSplitLoss", "subsample": "subsample", "reg_alpha": "l1Regularization", "reg_lambda": "l2Regularization", "learning_rate": "learnRate", - "tol": "minRelativeProgress", - "n_estimators": "numParallelTree", + "min_rel_progress": "minRelativeProgress", + "num_parallel_tree": "numParallelTree", "min_tree_child_weight": "minTreeChildWeight", "max_depth": "maxTreeDepth", "max_iterations": "maxIterations", - "enable_global_explain": "enableGlobalExplain", - "xgboost_version": "xgboostVersion", } -@log_adapter.class_logger class XGBRegressor( - base.SupervisedTrainableWithEvaluationPredictor, - bigframes_vendored.xgboost.sklearn.XGBRegressor, + base.SupervisedTrainablePredictor, + third_party.bigframes_vendored.xgboost.sklearn.XGBRegressor, ): - __doc__ = bigframes_vendored.xgboost.sklearn.XGBRegressor.__doc__ + __doc__ = third_party.bigframes_vendored.xgboost.sklearn.XGBRegressor.__doc__ def __init__( self, - n_estimators: int = 1, - *, + num_parallel_tree: int = 1, booster: Literal["gbtree", "dart"] = "gbtree", dart_normalized_type: Literal["tree", "forest"] = "tree", tree_method: Literal["auto", "exact", "approx", "hist"] = "auto", @@ -73,13 +68,14 @@ def __init__( subsample: float = 1.0, reg_alpha: float = 0.0, reg_lambda: float = 1.0, + early_stop: float = True, learning_rate: float = 0.3, max_iterations: int = 20, - tol: float = 0.01, + min_rel_progress: float = 0.01, enable_global_explain: bool = False, xgboost_version: Literal["0.9", "1.1"] = "0.9", ): - self.n_estimators = n_estimators + self.num_parallel_tree = num_parallel_tree self.booster = booster self.dart_normalized_type = dart_normalized_type self.tree_method = tree_method @@ -92,9 +88,10 @@ def __init__( self.subsample = subsample self.reg_alpha = reg_alpha self.reg_lambda = reg_lambda + self.early_stop = early_stop self.learning_rate = learning_rate self.max_iterations = max_iterations - self.tol = tol + self.min_rel_progress = min_rel_progress self.enable_global_explain = enable_global_explain self.xgboost_version = xgboost_version self._bqml_model: Optional[core.BqmlModel] = None @@ -102,17 +99,24 @@ def __init__( @classmethod def _from_bq( - cls, session: bigframes.session.Session, bq_model: bigquery.Model + cls, session: bigframes.Session, model: bigquery.Model ) -> XGBRegressor: - assert bq_model.model_type == "BOOSTED_TREE_REGRESSOR" + assert model.model_type == "BOOSTED_TREE_REGRESSOR" - kwargs = utils.retrieve_params_from_bq_model( - cls, bq_model, _BQML_PARAMS_MAPPING - ) + kwargs = {} + + # See https://cloud.google.com/bigquery/docs/reference/rest/v2/models#trainingrun + last_fitting = model.training_runs[-1]["trainingOptions"] + + dummy_regressor = cls() + for bf_param, bf_value in dummy_regressor.__dict__.items(): + bqml_param = _BQML_PARAMS_MAPPING.get(bf_param) + if bqml_param in last_fitting: + kwargs[bf_param] = type(bf_value)(last_fitting[bqml_param]) - model = cls(**kwargs) - model._bqml_model = core.BqmlModel(session, bq_model) - return model + new_xgb_regressor = cls(**kwargs) + new_xgb_regressor._bqml_model = core.BqmlModel(session, model) + return new_xgb_regressor @property def _bqml_options(self) -> Dict[str, str | int | bool | float | List[str]]: @@ -120,8 +124,7 @@ def _bqml_options(self) -> Dict[str, str | int | bool | float | List[str]]: return { "model_type": "BOOSTED_TREE_REGRESSOR", "data_split_method": "NO_SPLIT", - "early_stop": True, - "num_parallel_tree": self.n_estimators, + "num_parallel_tree": self.num_parallel_tree, "booster_type": self.booster, "tree_method": self.tree_method, "min_tree_child_weight": self.min_tree_child_weight, @@ -133,59 +136,59 @@ def _bqml_options(self) -> Dict[str, str | int | bool | float | List[str]]: "subsample": self.subsample, "l1_reg": self.reg_alpha, "l2_reg": self.reg_lambda, + "early_stop": self.early_stop, "learn_rate": self.learning_rate, "max_iterations": self.max_iterations, - "min_rel_progress": self.tol, + "min_rel_progress": self.min_rel_progress, "enable_global_explain": self.enable_global_explain, "xgboost_version": self.xgboost_version, } def _fit( self, - X: utils.ArrayType, - y: utils.ArrayType, + X: Union[bpd.DataFrame, bpd.Series], + y: Union[bpd.DataFrame, bpd.Series], transforms: Optional[List[str]] = None, - X_eval: Optional[utils.ArrayType] = None, - y_eval: Optional[utils.ArrayType] = None, ) -> XGBRegressor: - X, y = utils.batch_convert_to_dataframe(X, y) - - bqml_options = self._bqml_options - - if X_eval is not None and y_eval is not None: - X_eval, y_eval = utils.batch_convert_to_dataframe(X_eval, y_eval) - X, y, bqml_options = utils.combine_training_and_evaluation_data( - X, y, X_eval, y_eval, bqml_options - ) + X, y = utils.convert_to_dataframe(X, y) self._bqml_model = self._bqml_model_factory.create_model( X, y, transforms=transforms, - options=bqml_options, + options=self._bqml_options, ) return self def predict( self, - X: utils.ArrayType, - ) -> bigframes.dataframe.DataFrame: + X: Union[bpd.DataFrame, bpd.Series], + ) -> bpd.DataFrame: if not self._bqml_model: raise RuntimeError("A model must be fitted before predict") - (X,) = utils.batch_convert_to_dataframe(X, session=self._bqml_model.session) - - return self._bqml_model.predict(X) + (X,) = utils.convert_to_dataframe(X) + + df = self._bqml_model.predict(X) + return cast( + bpd.DataFrame, + df[ + [ + cast(str, field.name) + for field in self._bqml_model.model.label_columns + ] + ], + ) def score( self, - X: utils.ArrayType, - y: utils.ArrayType, + X: Union[bpd.DataFrame, bpd.Series], + y: Union[bpd.DataFrame, bpd.Series], ): + X, y = utils.convert_to_dataframe(X, y) + if not self._bqml_model: raise RuntimeError("A model must be fitted before score") - X, y = utils.batch_convert_to_dataframe(X, y, session=self._bqml_model.session) - input_data = ( X.join(y, how="outer") if (X is not None) and (y is not None) else None ) @@ -196,11 +199,11 @@ def to_gbq(self, model_name: str, replace: bool = False) -> XGBRegressor: Args: model_name (str): - The name of the model. + the name of the model. replace (bool, default False): - Determine whether to replace if the model already exists. Default to False. + whether to replace if the model already exists. Default to False. - Returns: Saved model.""" + Returns: saved model.""" if not self._bqml_model: raise RuntimeError("A model must be fitted before it can be saved") @@ -208,17 +211,16 @@ def to_gbq(self, model_name: str, replace: bool = False) -> XGBRegressor: return new_model.session.read_gbq_model(model_name) -@log_adapter.class_logger class XGBClassifier( - base.SupervisedTrainableWithEvaluationPredictor, - bigframes_vendored.xgboost.sklearn.XGBClassifier, + base.SupervisedTrainablePredictor, + third_party.bigframes_vendored.xgboost.sklearn.XGBClassifier, ): - __doc__ = bigframes_vendored.xgboost.sklearn.XGBClassifier.__doc__ + + __doc__ = third_party.bigframes_vendored.xgboost.sklearn.XGBClassifier.__doc__ def __init__( self, - n_estimators: int = 1, - *, + num_parallel_tree: int = 1, booster: Literal["gbtree", "dart"] = "gbtree", dart_normalized_type: Literal["tree", "forest"] = "tree", tree_method: Literal["auto", "exact", "approx", "hist"] = "auto", @@ -231,13 +233,14 @@ def __init__( subsample: float = 1.0, reg_alpha: float = 0.0, reg_lambda: float = 1.0, + early_stop: bool = True, learning_rate: float = 0.3, max_iterations: int = 20, - tol: float = 0.01, + min_rel_progress: float = 0.01, enable_global_explain: bool = False, xgboost_version: Literal["0.9", "1.1"] = "0.9", ): - self.n_estimators = n_estimators + self.num_parallel_tree = num_parallel_tree self.booster = booster self.dart_normalized_type = dart_normalized_type self.tree_method = tree_method @@ -250,9 +253,10 @@ def __init__( self.subsample = subsample self.reg_alpha = reg_alpha self.reg_lambda = reg_lambda + self.early_stop = early_stop self.learning_rate = learning_rate self.max_iterations = max_iterations - self.tol = tol + self.min_rel_progress = min_rel_progress self.enable_global_explain = enable_global_explain self.xgboost_version = xgboost_version self._bqml_model: Optional[core.BqmlModel] = None @@ -260,17 +264,24 @@ def __init__( @classmethod def _from_bq( - cls, session: bigframes.session.Session, bq_model: bigquery.Model + cls, session: bigframes.Session, model: bigquery.Model ) -> XGBClassifier: - assert bq_model.model_type == "BOOSTED_TREE_CLASSIFIER" + assert model.model_type == "BOOSTED_TREE_CLASSIFIER" - kwargs = utils.retrieve_params_from_bq_model( - cls, bq_model, _BQML_PARAMS_MAPPING - ) + kwargs = {} + + # See https://cloud.google.com/bigquery/docs/reference/rest/v2/models#trainingrun + last_fitting = model.training_runs[-1]["trainingOptions"] + + dummy_classifier = XGBClassifier() + for bf_param, bf_value in dummy_classifier.__dict__.items(): + bqml_param = _BQML_PARAMS_MAPPING.get(bf_param) + if bqml_param is not None: + kwargs[bf_param] = type(bf_value)(last_fitting[bqml_param]) - model = cls(**kwargs) - model._bqml_model = core.BqmlModel(session, bq_model) - return model + new_xgb_classifier = cls(**kwargs) + new_xgb_classifier._bqml_model = core.BqmlModel(session, model) + return new_xgb_classifier @property def _bqml_options(self) -> Dict[str, str | int | bool | float | List[str]]: @@ -278,8 +289,7 @@ def _bqml_options(self) -> Dict[str, str | int | bool | float | List[str]]: return { "model_type": "BOOSTED_TREE_CLASSIFIER", "data_split_method": "NO_SPLIT", - "early_stop": True, - "num_parallel_tree": self.n_estimators, + "num_parallel_tree": self.num_parallel_tree, "booster_type": self.booster, "tree_method": self.tree_method, "min_tree_child_weight": self.min_tree_child_weight, @@ -291,55 +301,56 @@ def _bqml_options(self) -> Dict[str, str | int | bool | float | List[str]]: "subsample": self.subsample, "l1_reg": self.reg_alpha, "l2_reg": self.reg_lambda, + "early_stop": self.early_stop, "learn_rate": self.learning_rate, "max_iterations": self.max_iterations, - "min_rel_progress": self.tol, + "min_rel_progress": self.min_rel_progress, "enable_global_explain": self.enable_global_explain, "xgboost_version": self.xgboost_version, } def _fit( self, - X: utils.ArrayType, - y: utils.ArrayType, + X: Union[bpd.DataFrame, bpd.Series], + y: Union[bpd.DataFrame, bpd.Series], transforms: Optional[List[str]] = None, - X_eval: Optional[utils.ArrayType] = None, - y_eval: Optional[utils.ArrayType] = None, ) -> XGBClassifier: - X, y = utils.batch_convert_to_dataframe(X, y) - - bqml_options = self._bqml_options - - if X_eval is not None and y_eval is not None: - X_eval, y_eval = utils.batch_convert_to_dataframe(X_eval, y_eval) - X, y, bqml_options = utils.combine_training_and_evaluation_data( - X, y, X_eval, y_eval, bqml_options - ) + X, y = utils.convert_to_dataframe(X, y) self._bqml_model = self._bqml_model_factory.create_model( X, y, transforms=transforms, - options=bqml_options, + options=self._bqml_options, ) return self - def predict(self, X: utils.ArrayType) -> bigframes.dataframe.DataFrame: + def predict(self, X: Union[bpd.DataFrame, bpd.Series]) -> bpd.DataFrame: if not self._bqml_model: raise RuntimeError("A model must be fitted before predict") - (X,) = utils.batch_convert_to_dataframe(X, session=self._bqml_model.session) - return self._bqml_model.predict(X) + (X,) = utils.convert_to_dataframe(X) + + df = self._bqml_model.predict(X) + return cast( + bpd.DataFrame, + df[ + [ + cast(str, field.name) + for field in self._bqml_model.model.label_columns + ] + ], + ) def score( self, - X: utils.ArrayType, - y: utils.ArrayType, + X: Union[bpd.DataFrame, bpd.Series], + y: Union[bpd.DataFrame, bpd.Series], ): if not self._bqml_model: raise RuntimeError("A model must be fitted before score") - X, y = utils.batch_convert_to_dataframe(X, y, session=self._bqml_model.session) + X, y = utils.convert_to_dataframe(X, y) input_data = ( X.join(y, how="outer") if (X is not None) and (y is not None) else None @@ -351,12 +362,12 @@ def to_gbq(self, model_name: str, replace: bool = False) -> XGBClassifier: Args: model_name (str): - The name of the model. + the name of the model. replace (bool, default False): - Determine whether to replace if the model already exists. Default to False. + whether to replace if the model already exists. Default to False. Returns: - XGBClassifier: Saved model.""" + XGBClassifier: saved model.""" if not self._bqml_model: raise RuntimeError("A model must be fitted before it can be saved") @@ -364,32 +375,34 @@ def to_gbq(self, model_name: str, replace: bool = False) -> XGBClassifier: return new_model.session.read_gbq_model(model_name) -@log_adapter.class_logger class RandomForestRegressor( - base.SupervisedTrainableWithEvaluationPredictor, - bigframes_vendored.sklearn.ensemble._forest.RandomForestRegressor, + base.SupervisedTrainablePredictor, + third_party.bigframes_vendored.sklearn.ensemble._forest.RandomForestRegressor, ): - __doc__ = bigframes_vendored.sklearn.ensemble._forest.RandomForestRegressor.__doc__ + + __doc__ = ( + third_party.bigframes_vendored.sklearn.ensemble._forest.RandomForestRegressor.__doc__ + ) def __init__( self, - n_estimators: int = 100, - *, + num_parallel_tree: int = 100, tree_method: Literal["auto", "exact", "approx", "hist"] = "auto", min_tree_child_weight: int = 1, - colsample_bytree: float = 1.0, - colsample_bylevel: float = 1.0, - colsample_bynode: float = 0.8, - gamma: float = 0.0, + colsample_bytree=1.0, + colsample_bylevel=1.0, + colsample_bynode=0.8, + gamma=0.00, max_depth: int = 15, - subsample: float = 0.8, - reg_alpha: float = 0.0, - reg_lambda: float = 1.0, - tol: float = 0.01, - enable_global_explain: bool = False, + subsample=0.8, + reg_alpha=0.0, + reg_lambda=1.0, + early_stop=True, + min_rel_progress=0.01, + enable_global_explain=False, xgboost_version: Literal["0.9", "1.1"] = "0.9", ): - self.n_estimators = n_estimators + self.num_parallel_tree = num_parallel_tree self.tree_method = tree_method self.min_tree_child_weight = min_tree_child_weight self.colsample_bytree = colsample_bytree @@ -400,7 +413,8 @@ def __init__( self.subsample = subsample self.reg_alpha = reg_alpha self.reg_lambda = reg_lambda - self.tol = tol + self.early_stop = early_stop + self.min_rel_progress = min_rel_progress self.enable_global_explain = enable_global_explain self.xgboost_version = xgboost_version self._bqml_model: Optional[core.BqmlModel] = None @@ -408,25 +422,31 @@ def __init__( @classmethod def _from_bq( - cls, session: bigframes.session.Session, bq_model: bigquery.Model + cls, session: bigframes.Session, model: bigquery.Model ) -> RandomForestRegressor: - assert bq_model.model_type == "RANDOM_FOREST_REGRESSOR" + assert model.model_type == "RANDOM_FOREST_REGRESSOR" - kwargs = utils.retrieve_params_from_bq_model( - cls, bq_model, _BQML_PARAMS_MAPPING - ) + kwargs = {} + + # See https://cloud.google.com/bigquery/docs/reference/rest/v2/models#trainingrun + last_fitting = model.training_runs[-1]["trainingOptions"] + + dummy_model = cls() + for bf_param, bf_value in dummy_model.__dict__.items(): + bqml_param = _BQML_PARAMS_MAPPING.get(bf_param) + if bqml_param in last_fitting: + kwargs[bf_param] = type(bf_value)(last_fitting[bqml_param]) - model = cls(**kwargs) - model._bqml_model = core.BqmlModel(session, bq_model) - return model + new_random_forest_regressor = cls(**kwargs) + new_random_forest_regressor._bqml_model = core.BqmlModel(session, model) + return new_random_forest_regressor @property def _bqml_options(self) -> Dict[str, str | int | bool | float | List[str]]: """The model options as they will be set for BQML""" return { "model_type": "RANDOM_FOREST_REGRESSOR", - "early_stop": True, - "num_parallel_tree": self.n_estimators, + "num_parallel_tree": self.num_parallel_tree, "tree_method": self.tree_method, "min_tree_child_weight": self.min_tree_child_weight, "colsample_bytree": self.colsample_bytree, @@ -437,7 +457,8 @@ def _bqml_options(self) -> Dict[str, str | int | bool | float | List[str]]: "subsample": self.subsample, "l1_reg": self.reg_alpha, "l2_reg": self.reg_lambda, - "min_rel_progress": self.tol, + "early_stop": self.early_stop, + "min_rel_progress": self.min_rel_progress, "data_split_method": "NO_SPLIT", "enable_global_explain": self.enable_global_explain, "xgboost_version": self.xgboost_version, @@ -445,50 +466,50 @@ def _bqml_options(self) -> Dict[str, str | int | bool | float | List[str]]: def _fit( self, - X: utils.ArrayType, - y: utils.ArrayType, + X: Union[bpd.DataFrame, bpd.Series], + y: Union[bpd.DataFrame, bpd.Series], transforms: Optional[List[str]] = None, - X_eval: Optional[utils.ArrayType] = None, - y_eval: Optional[utils.ArrayType] = None, ) -> RandomForestRegressor: - X, y = utils.batch_convert_to_dataframe(X, y) - - bqml_options = self._bqml_options - - if X_eval is not None and y_eval is not None: - X_eval, y_eval = utils.batch_convert_to_dataframe(X_eval, y_eval) - X, y, bqml_options = utils.combine_training_and_evaluation_data( - X, y, X_eval, y_eval, bqml_options - ) + X, y = utils.convert_to_dataframe(X, y) self._bqml_model = self._bqml_model_factory.create_model( X, y, transforms=transforms, - options=bqml_options, + options=self._bqml_options, ) return self def predict( self, - X: utils.ArrayType, - ) -> bigframes.dataframe.DataFrame: + X: Union[bpd.DataFrame, bpd.Series], + ) -> bpd.DataFrame: if not self._bqml_model: raise RuntimeError("A model must be fitted before predict") - (X,) = utils.batch_convert_to_dataframe(X, session=self._bqml_model.session) - return self._bqml_model.predict(X) + (X,) = utils.convert_to_dataframe(X) + + df = self._bqml_model.predict(X) + return cast( + bpd.DataFrame, + df[ + [ + cast(str, field.name) + for field in self._bqml_model.model.label_columns + ] + ], + ) def score( self, - X: utils.ArrayType, - y: utils.ArrayType, + X: Union[bpd.DataFrame, bpd.Series], + y: Union[bpd.DataFrame, bpd.Series], ): """Calculate evaluation metrics of the model. .. note:: - Output matches that of the BigQuery ML.EVALUATE function. + Output matches that of the BigQuery ML.EVALUTE function. See: https://cloud.google.com/bigquery/docs/reference/standard-sql/bigqueryml-syntax-evaluate#regression_models for the outputs relevant to this model type. @@ -504,7 +525,7 @@ def score( if not self._bqml_model: raise RuntimeError("A model must be fitted before score") - X, y = utils.batch_convert_to_dataframe(X, y, session=self._bqml_model.session) + X, y = utils.convert_to_dataframe(X, y) input_data = ( X.join(y, how="outer") if (X is not None) and (y is not None) else None @@ -516,12 +537,12 @@ def to_gbq(self, model_name: str, replace: bool = False) -> RandomForestRegresso Args: model_name (str): - The name of the model. + the name of the model. replace (bool, default False): - Determine whether to replace if the model already exists. Default to False. + whether to replace if the model already exists. Default to False. Returns: - RandomForestRegressor: Saved model.""" + RandomForestRegressor: saved model.""" if not self._bqml_model: raise RuntimeError("A model must be fitted before it can be saved") @@ -529,17 +550,18 @@ def to_gbq(self, model_name: str, replace: bool = False) -> RandomForestRegresso return new_model.session.read_gbq_model(model_name) -@log_adapter.class_logger class RandomForestClassifier( - base.SupervisedTrainableWithEvaluationPredictor, - bigframes_vendored.sklearn.ensemble._forest.RandomForestClassifier, + base.SupervisedTrainablePredictor, + third_party.bigframes_vendored.sklearn.ensemble._forest.RandomForestClassifier, ): - __doc__ = bigframes_vendored.sklearn.ensemble._forest.RandomForestClassifier.__doc__ + + __doc__ = ( + third_party.bigframes_vendored.sklearn.ensemble._forest.RandomForestClassifier.__doc__ + ) def __init__( self, - n_estimators: int = 100, - *, + num_parallel_tree: int = 100, tree_method: Literal["auto", "exact", "approx", "hist"] = "auto", min_tree_child_weight: int = 1, colsample_bytree: float = 1.0, @@ -550,11 +572,12 @@ def __init__( subsample: float = 0.8, reg_alpha: float = 0.0, reg_lambda: float = 1.0, - tol: float = 0.01, - enable_global_explain: bool = False, + early_stop=True, + min_rel_progress: float = 0.01, + enable_global_explain=False, xgboost_version: Literal["0.9", "1.1"] = "0.9", ): - self.n_estimators = n_estimators + self.num_parallel_tree = num_parallel_tree self.tree_method = tree_method self.min_tree_child_weight = min_tree_child_weight self.colsample_bytree = colsample_bytree @@ -565,7 +588,8 @@ def __init__( self.subsample = subsample self.reg_alpha = reg_alpha self.reg_lambda = reg_lambda - self.tol = tol + self.early_stop = early_stop + self.min_rel_progress = min_rel_progress self.enable_global_explain = enable_global_explain self.xgboost_version = xgboost_version self._bqml_model: Optional[core.BqmlModel] = None @@ -573,25 +597,31 @@ def __init__( @classmethod def _from_bq( - cls, session: bigframes.session.Session, bq_model: bigquery.Model + cls, session: bigframes.Session, model: bigquery.Model ) -> RandomForestClassifier: - assert bq_model.model_type == "RANDOM_FOREST_CLASSIFIER" + assert model.model_type == "RANDOM_FOREST_CLASSIFIER" - kwargs = utils.retrieve_params_from_bq_model( - cls, bq_model, _BQML_PARAMS_MAPPING - ) + kwargs = {} + + # See https://cloud.google.com/bigquery/docs/reference/rest/v2/models#trainingrun + last_fitting = model.training_runs[-1]["trainingOptions"] - model = cls(**kwargs) - model._bqml_model = core.BqmlModel(session, bq_model) - return model + dummy_model = RandomForestClassifier() + for bf_param, bf_value in dummy_model.__dict__.items(): + bqml_param = _BQML_PARAMS_MAPPING.get(bf_param) + if bqml_param is not None: + kwargs[bf_param] = type(bf_value)(last_fitting[bqml_param]) + + new_random_forest_classifier = cls(**kwargs) + new_random_forest_classifier._bqml_model = core.BqmlModel(session, model) + return new_random_forest_classifier @property def _bqml_options(self) -> Dict[str, str | int | bool | float | List[str]]: """The model options as they will be set for BQML""" return { "model_type": "RANDOM_FOREST_CLASSIFIER", - "early_stop": True, - "num_parallel_tree": self.n_estimators, + "num_parallel_tree": self.num_parallel_tree, "tree_method": self.tree_method, "min_tree_child_weight": self.min_tree_child_weight, "colsample_bytree": self.colsample_bytree, @@ -602,7 +632,8 @@ def _bqml_options(self) -> Dict[str, str | int | bool | float | List[str]]: "subsample": self.subsample, "l1_reg": self.reg_alpha, "l2_reg": self.reg_lambda, - "min_rel_progress": self.tol, + "early_stop": self.early_stop, + "min_rel_progress": self.min_rel_progress, "data_split_method": "NO_SPLIT", "enable_global_explain": self.enable_global_explain, "xgboost_version": self.xgboost_version, @@ -610,50 +641,50 @@ def _bqml_options(self) -> Dict[str, str | int | bool | float | List[str]]: def _fit( self, - X: utils.ArrayType, - y: utils.ArrayType, + X: Union[bpd.DataFrame, bpd.Series], + y: Union[bpd.DataFrame, bpd.Series], transforms: Optional[List[str]] = None, - X_eval: Optional[utils.ArrayType] = None, - y_eval: Optional[utils.ArrayType] = None, ) -> RandomForestClassifier: - X, y = utils.batch_convert_to_dataframe(X, y) - - bqml_options = self._bqml_options - - if X_eval is not None and y_eval is not None: - X_eval, y_eval = utils.batch_convert_to_dataframe(X_eval, y_eval) - X, y, bqml_options = utils.combine_training_and_evaluation_data( - X, y, X_eval, y_eval, bqml_options - ) + X, y = utils.convert_to_dataframe(X, y) self._bqml_model = self._bqml_model_factory.create_model( X, y, transforms=transforms, - options=bqml_options, + options=self._bqml_options, ) return self def predict( self, - X: utils.ArrayType, - ) -> bigframes.dataframe.DataFrame: + X: Union[bpd.DataFrame, bpd.Series], + ) -> bpd.DataFrame: if not self._bqml_model: raise RuntimeError("A model must be fitted before predict") - (X,) = utils.batch_convert_to_dataframe(X, session=self._bqml_model.session) - return self._bqml_model.predict(X) + (X,) = utils.convert_to_dataframe(X) + + df = self._bqml_model.predict(X) + return cast( + bpd.DataFrame, + df[ + [ + cast(str, field.name) + for field in self._bqml_model.model.label_columns + ] + ], + ) def score( self, - X: utils.ArrayType, - y: utils.ArrayType, + X: Union[bpd.DataFrame, bpd.Series], + y: Union[bpd.DataFrame, bpd.Series], ): """Calculate evaluation metrics of the model. .. note:: - Output matches that of the BigQuery ML.EVALUATE function. + Output matches that of the BigQuery ML.EVALUTE function. See: https://cloud.google.com/bigquery/docs/reference/standard-sql/bigqueryml-syntax-evaluate#classification_models for the outputs relevant to this model type. @@ -669,7 +700,7 @@ def score( if not self._bqml_model: raise RuntimeError("A model must be fitted before score") - X, y = utils.batch_convert_to_dataframe(X, y, session=self._bqml_model.session) + X, y = utils.convert_to_dataframe(X, y) input_data = ( X.join(y, how="outer") if (X is not None) and (y is not None) else None @@ -681,12 +712,12 @@ def to_gbq(self, model_name: str, replace: bool = False) -> RandomForestClassifi Args: model_name (str): - The name of the model. + the name of the model. replace (bool, default False): - Determine whether to replace if the model already exists. Default to False. + whether to replace if the model already exists. Default to False. Returns: - RandomForestClassifier: Saved model.""" + RandomForestClassifier: saved model.""" if not self._bqml_model: raise RuntimeError("A model must be fitted before it can be saved") diff --git a/bigframes/ml/forecasting.py b/bigframes/ml/forecasting.py index bfdd736f855..8e309d5e736 100644 --- a/bigframes/ml/forecasting.py +++ b/bigframes/ml/forecasting.py @@ -16,450 +16,138 @@ from __future__ import annotations -from typing import List, Optional +from typing import cast, Dict, List, Optional, Union from google.cloud import bigquery -import bigframes.pandas as bpd -import bigframes.session -from bigframes.core.logging import log_adapter +import bigframes from bigframes.ml import base, core, globals, utils +import bigframes.pandas as bpd -_BQML_PARAMS_MAPPING = { - "horizon": "horizon", - "auto_arima": "autoArima", - "auto_arima_max_order": "autoArimaMaxOrder", - "auto_arima_min_order": "autoArimaMinOrder", - "order": "nonSeasonalOrder", - "data_frequency": "dataFrequency", - "include_drift": "includeDrift", - "holiday_region": "holidayRegion", - "clean_spikes_and_dips": "cleanSpikesAndDips", - "adjust_step_changes": "adjustStepChanges", - "forecast_limit_upper_bound": "forecastLimitUpperBound", - "forecast_limit_lower_bound": "forecastLimitLowerBound", - "time_series_length_fraction": "timeSeriesLengthFraction", - "min_time_series_length": "minTimeSeriesLength", - "max_time_series_length": "maxTimeSeriesLength", - "decompose_time_series": "decomposeTimeSeries", - "trend_smoothing_window_size": "trendSmoothingWindowSize", -} - - -@log_adapter.class_logger -class ARIMAPlus(base.SupervisedTrainableWithIdColPredictor): - """Time Series ARIMA Plus model. - - Args: - horizon (int, default 1,000): - The number of time points to forecast. Default to 1,000, max value 10,000. - - auto_arima (bool, default True): - Determines whether the training process uses auto.ARIMA or not. If True, training automatically finds the best non-seasonal order (that is, the p, d, q tuple) and decides whether or not to include a linear drift term when d is 1. - - auto_arima_max_order (int or None, default None): - The maximum value for the sum of non-seasonal p and q. - - auto_arima_min_order (int or None, default None): - The minimum value for the sum of non-seasonal p and q. - - data_frequency (str, default "auto_frequency"): - The data frequency of the input time series. - Possible values are "auto_frequency", "per_minute", "hourly", "daily", "weekly", "monthly", "quarterly", "yearly" - - include_drift (bool, default False): - Determines whether the model should include a linear drift term or not. The drift term is applicable when non-seasonal d is 1. - - holiday_region (str or None, default None): - The geographical region based on which the holiday effect is applied in modeling. By default, holiday effect modeling isn't used. - Possible values see https://cloud.google.com/bigquery/docs/reference/standard-sql/bigqueryml-syntax-create-time-series#holiday_region. - - clean_spikes_and_dips (bool, default True): - Determines whether or not to perform automatic spikes and dips detection and cleanup in the model training pipeline. The spikes and dips are replaced with local linear interpolated values when they're detected. - - adjust_step_changes (bool, default True): - Determines whether or not to perform automatic step change detection and adjustment in the model training pipeline. - - forecast_limit_upper_bound (float or None, default None): - The upper bound of the forecasting values. When you specify the ``forecast_limit_upper_bound`` option, all of the forecast values must be less than the specified value. - For example, if you set ``forecast_limit_upper_bound`` to 100, then all of the forecast values are less than 100. - Also, all values greater than or equal to the ``forecast_limit_upper_bound`` value are excluded from modelling. - The forecasting limit ensures that forecasts stay within limits. - - forecast_limit_lower_bound (float or None, default None): - The lower bound of the forecasting values where the minimum value allowed is 0. When you specify the ``forecast_limit_lower_bound`` option, all of the forecast values must be greater than the specified value. - For example, if you set ``forecast_limit_lower_bound`` to 0, then all of the forecast values are larger than 0. Also, all values less than or equal to the ``forecast_limit_lower_bound`` value are excluded from modelling. - The forecasting limit ensures that forecasts stay within limits. - - time_series_length_fraction (float or None, default None): - The fraction of the interpolated length of the time series that's used to model the time series trend component. All of the time points of the time series are used to model the non-trend component. - - min_time_series_length (int or None, default None): - The minimum number of time points that are used in modeling the trend component of the time series. - - max_time_series_length (int or None, default None): - The maximum number of time points in a time series that can be used in modeling the trend component of the time series. - - trend_smoothing_window_size (int or None, default None): - The smoothing window size for the trend component. - - decompose_time_series (bool, default True): - Determines whether the separate components of both the history and forecast parts of the time series (such as holiday effect and seasonal components) are saved in the model. - """ - - def __init__( - self, - *, - horizon: int = 1000, - auto_arima: bool = True, - auto_arima_max_order: Optional[int] = None, - auto_arima_min_order: Optional[int] = None, - data_frequency: str = "auto_frequency", - include_drift: bool = False, - holiday_region: Optional[str] = None, - clean_spikes_and_dips: bool = True, - adjust_step_changes: bool = True, - forecast_limit_lower_bound: Optional[float] = None, - forecast_limit_upper_bound: Optional[float] = None, - time_series_length_fraction: Optional[float] = None, - min_time_series_length: Optional[int] = None, - max_time_series_length: Optional[int] = None, - trend_smoothing_window_size: Optional[int] = None, - decompose_time_series: bool = True, - ): - self.horizon = horizon - self.auto_arima = auto_arima - self.auto_arima_max_order = auto_arima_max_order - self.auto_arima_min_order = auto_arima_min_order - self.data_frequency = data_frequency - self.include_drift = include_drift - self.holiday_region = holiday_region - self.clean_spikes_and_dips = clean_spikes_and_dips - self.adjust_step_changes = adjust_step_changes - self.forecast_limit_upper_bound = forecast_limit_upper_bound - self.forecast_limit_lower_bound = forecast_limit_lower_bound - self.time_series_length_fraction = time_series_length_fraction - self.min_time_series_length = min_time_series_length - self.max_time_series_length = max_time_series_length - self.trend_smoothing_window_size = trend_smoothing_window_size - self.decompose_time_series = decompose_time_series - # TODO(garrettwu) add order and seasonalities params, which need struct/array +_PREDICT_OUTPUT_COLUMNS = ["forecast_timestamp", "forecast_value"] + + +class ARIMAPlus(base.SupervisedTrainablePredictor): + """Time Series ARIMA Plus model.""" + def __init__(self): self._bqml_model: Optional[core.BqmlModel] = None self._bqml_model_factory = globals.bqml_model_factory() @classmethod - def _from_bq( - cls, session: bigframes.session.Session, bq_model: bigquery.Model - ) -> ARIMAPlus: - assert bq_model.model_type == "ARIMA_PLUS" + def _from_bq(cls, session: bigframes.Session, model: bigquery.Model) -> ARIMAPlus: + assert model.model_type == "ARIMA_PLUS" - kwargs = utils.retrieve_params_from_bq_model( - cls, bq_model, _BQML_PARAMS_MAPPING - ) + kwargs: Dict[str, str | int | bool | float | List[str]] = {} - model = cls(**kwargs) - model._bqml_model = core.BqmlModel(session, bq_model) - return model + new_arima_plus = cls(**kwargs) + new_arima_plus._bqml_model = core.BqmlModel(session, model) + return new_arima_plus @property - def _bqml_options(self) -> dict: + def _bqml_options(self) -> Dict[str, str | int | bool | float | List[str]]: """The model options as they will be set for BQML.""" - options = { - "model_type": "ARIMA_PLUS", - "horizon": self.horizon, - "auto_arima": self.auto_arima, - "data_frequency": self.data_frequency, - "clean_spikes_and_dips": self.clean_spikes_and_dips, - "adjust_step_changes": self.adjust_step_changes, - "decompose_time_series": self.decompose_time_series, - } - - if self.auto_arima_max_order is not None: - options["auto_arima_max_order"] = self.auto_arima_max_order - if self.auto_arima_min_order is not None: - options["auto_arima_min_order"] = self.auto_arima_min_order - if self.holiday_region is not None: - options["holiday_region"] = self.holiday_region - if self.time_series_length_fraction is not None: - options["time_series_length_fraction"] = self.time_series_length_fraction - if self.min_time_series_length is not None: - options["min_time_series_length"] = self.min_time_series_length - if self.max_time_series_length is not None: - options["max_time_series_length"] = self.max_time_series_length - if self.trend_smoothing_window_size is not None: - options["trend_smoothing_window_size"] = self.trend_smoothing_window_size - - if self.include_drift: - options["include_drift"] = True - if self.forecast_limit_upper_bound is not None: - options["forecast_limit_upper_bound"] = self.forecast_limit_upper_bound - if self.forecast_limit_lower_bound is not None: - options["forecast_limit_lower_bound"] = self.forecast_limit_lower_bound - - return options + return {"model_type": "ARIMA_PLUS"} def _fit( self, - X: utils.ArrayType, - y: utils.ArrayType, + X: Union[bpd.DataFrame, bpd.Series], + y: Union[bpd.DataFrame, bpd.Series], transforms: Optional[List[str]] = None, - id_col: Optional[utils.ArrayType] = None, - ) -> ARIMAPlus: + ): """Fit the model to training data. Args: - X (bigframes.dataframe.DataFrame or bigframes.series.Series, - or pandas.core.frame.DataFrame or pandas.core.series.Series): - A dataframe or series of training timestamp. - y (bigframes.dataframe.DataFrame, or bigframes.series.Series, - or pandas.core.frame.DataFrame, or pandas.core.series.Series): + X (bigframes.dataframe.DataFrame or bigframes.series.Series): + A dataframe of training timestamp. + + y (bigframes.dataframe.DataFrame or bigframes.series.Series): Target values for training. transforms (Optional[List[str]], default None): Do not use. Internal param to be deprecated. Use bigframes.ml.pipeline instead. - id_col (Optional[bigframes.dataframe.DataFrame] - or Optional[bigframes.series.Series] - or Optional[pandas.core.frame.DataFrame] - or Optional[pandas.core.frame.Series] - or None, default None): - An optional dataframe or series of training id col. Returns: ARIMAPlus: Fitted estimator. """ - X, y = utils.batch_convert_to_dataframe(X, y) - if X.columns.size != 1: - raise ValueError("Time series timestamp input X contain at least 1 column.") + raise ValueError( + "Time series timestamp input X must only contain 1 column." + ) if y.columns.size != 1: raise ValueError("Time series data input y must only contain 1 column.") - if id_col is not None: - (id_col,) = utils.batch_convert_to_dataframe(id_col) - - if id_col.columns.size != 1: - raise ValueError( - "Time series id input id_col must only contain 1 column." - ) + X, y = utils.convert_to_dataframe(X, y) self._bqml_model = self._bqml_model_factory.create_time_series_model( X, y, - id_col=id_col, transforms=transforms, options=self._bqml_options, ) - return self - - def predict( - self, X=None, *, horizon: int = 3, confidence_level: float = 0.95 - ) -> bpd.DataFrame: - """Forecast time series at future horizon. - - .. note:: - Output matches that of the BigQuery ML.FORECAST function. - See: https://cloud.google.com/bigquery/docs/reference/standard-sql/bigqueryml-syntax-forecast + def predict(self, X=None) -> bpd.DataFrame: + """Predict the closest cluster for each sample in X. Args: X (default None): ignored, to be compatible with other APIs. - horizon (int, default: 3): - an int value that specifies the number of time points to forecast. - The default value is 3, and the maximum value is 1000. - confidence_level (float, default 0.95): - A float value that specifies percentage of the future values that fall in the prediction interval. - The valid input range is [0.0, 1.0). Returns: bigframes.dataframe.DataFrame: The predicted DataFrames. Which - contains 2 columns: "forecast_timestamp", "id" as optional, and "forecast_value". + contains 2 columns "forecast_timestamp" and "forecast_value". """ - if horizon < 1 or horizon > 1000: - raise ValueError(f"horizon must be [1, 1000], but is {horizon}.") - if confidence_level < 0.0 or confidence_level >= 1.0: - raise ValueError( - f"confidence_level must be [0.0, 1.0), but is {confidence_level}." - ) - if not self._bqml_model: raise RuntimeError("A model must be fitted before predict") - return self._bqml_model.forecast( - options={"horizon": horizon, "confidence_level": confidence_level} - ) - - def predict_explain( - self, X=None, *, horizon: int = 3, confidence_level: float = 0.95 - ) -> bpd.DataFrame: - """Explain Forecast time series at future horizon. - - .. note:: - - Output matches that of the BigQuery ML.EXPLAIN_FORECAST function. - See: https://cloud.google.com/bigquery/docs/reference/standard-sql/bigqueryml-syntax-explain-forecast - - Args: - X (default None): - ignored, to be compatible with other APIs. - horizon (int, default: 3): - an int value that specifies the number of time points to forecast. - The default value is 3, and the maximum value is 1000. - confidence_level (float, default 0.95): - A float value that specifies percentage of the future values that fall in the prediction interval. - The valid input range is [0.0, 1.0). - - Returns: - bigframes.dataframe.DataFrame: The predicted DataFrames. - """ - if horizon < 1: - raise ValueError(f"horizon must be at least 1, but is {horizon}.") - if confidence_level < 0.0 or confidence_level >= 1.0: - raise ValueError( - f"confidence_level must be [0.0, 1.0), but is {confidence_level}." - ) - - if not self._bqml_model: - raise RuntimeError("A model must be fitted before predict") - - return self._bqml_model.explain_forecast( - options={"horizon": horizon, "confidence_level": confidence_level} - ) - - @property - def coef_( - self, - ) -> bpd.DataFrame: - """Inspect the coefficients of the model. - - ..note:: - - Output matches that of the ML.ARIMA_COEFFICIENTS function. - See: https://cloud.google.com/bigquery/docs/reference/standard-sql/bigqueryml-syntax-arima-coefficients - for the outputs relevant to this model type. - - Returns: - bigframes.dataframe.DataFrame: - A DataFrame with the coefficients for the model. - """ - - if not self._bqml_model: - raise RuntimeError("A model must be fitted before inspect coefficients") - return self._bqml_model.arima_coefficients() - - def detect_anomalies( - self, - X: utils.ArrayType, - *, - anomaly_prob_threshold: float = 0.95, - ) -> bpd.DataFrame: - """Detect the anomaly data points of the input. - - Args: - X (bigframes.dataframe.DataFrame or bigframes.series.Series or pandas.core.frame.DataFrame or pandas.core.series.Series): - Series or a DataFrame to detect anomalies. - anomaly_prob_threshold (float, default 0.95): - Identifies the custom threshold to use for anomaly detection. The value must be in the range [0, 1), with a default value of 0.95. - - Returns: - bigframes.dataframe.DataFrame: Detected DataFrame.""" - if anomaly_prob_threshold < 0.0 or anomaly_prob_threshold >= 1.0: - raise ValueError( - f"anomaly_prob_threshold must be [0.0, 1.0), but is {anomaly_prob_threshold}." - ) - - if not self._bqml_model: - raise RuntimeError("A model must be fitted before detect_anomalies") - - (X,) = utils.batch_convert_to_dataframe(X, session=self._bqml_model.session) - - return self._bqml_model.detect_anomalies( - X, options={"anomaly_prob_threshold": anomaly_prob_threshold} + return cast( + bpd.DataFrame, + self._bqml_model.forecast()[_PREDICT_OUTPUT_COLUMNS], ) def score( self, - X: utils.ArrayType, - y: utils.ArrayType, - id_col: Optional[utils.ArrayType] = None, + X: Union[bpd.DataFrame, bpd.Series], + y: Union[bpd.DataFrame, bpd.Series], ) -> bpd.DataFrame: """Calculate evaluation metrics of the model. .. note:: - Output matches that of the BigQuery ML.EVALUATE function. + Output matches that of the BigQuery ML.EVALUTE function. See: https://cloud.google.com/bigquery/docs/reference/standard-sql/bigqueryml-syntax-evaluate#time_series_models for the outputs relevant to this model type. Args: - X (bigframes.dataframe.DataFrame or bigframes.series.Series - or pandas.core.frame.DataFrame or pandas.core.series.Series): - A dataframe or series only contains 1 column as + X (bigframes.dataframe.DataFrame or bigframes.series.Series): + A BigQuery DataFrame only contains 1 column as evaluation timestamp. The timestamp must be within the horizon of the model, which by default is 1000 data points. - y (bigframes.dataframe.DataFrame or bigframes.series.Series - or pandas.core.frame.DataFrame or pandas.core.series.Series): - A dataframe or series only contains 1 column as + y (bigframes.dataframe.DataFrame or bigframes.series.Series): + A BigQuery DataFrame only contains 1 column as evaluation numeric values. - id_col (Optional[bigframes.dataframe.DataFrame] - or Optional[bigframes.series.Series] - or Optional[pandas.core.frame.DataFrame] - or Optional[pandas.core.series.Series] - or None, default None): - An optional dataframe or series contains at least 1 column as - evaluation id column. Returns: bigframes.dataframe.DataFrame: A DataFrame as evaluation result. """ if not self._bqml_model: raise RuntimeError("A model must be fitted before score") - X, y = utils.batch_convert_to_dataframe(X, y, session=self._bqml_model.session) + X, y = utils.convert_to_dataframe(X, y) input_data = X.join(y, how="outer") - if id_col is not None: - (id_col,) = utils.batch_convert_to_dataframe(id_col) - input_data = input_data.join(id_col, how="outer") - return self._bqml_model.evaluate(input_data) - def summary( - self, - show_all_candidate_models: bool = False, - ) -> bpd.DataFrame: - """Summary of the evaluation metrics of the time series model. - - .. note:: - - Output matches that of the BigQuery ML.ARIMA_EVALUATE function. - See: https://cloud.google.com/bigquery/docs/reference/standard-sql/bigqueryml-syntax-arima-evaluate - for the outputs relevant to this model type. - - Args: - show_all_candidate_models (bool, default to False): - Whether to show evaluation metrics or an error message for either - all candidate models or for only the best model with the lowest - AIC. Default to False. - - Returns: - bigframes.dataframe.DataFrame: A DataFrame as evaluation result. - """ - if not self._bqml_model: - raise RuntimeError("A model must be fitted before score") - return self._bqml_model.arima_evaluate(show_all_candidate_models) - def to_gbq(self, model_name: str, replace: bool = False) -> ARIMAPlus: """Save the model to BigQuery. Args: model_name (str): - The name of the model. + the name of the model. replace (bool, default False): - Determine whether to replace if the model already exists. Default to False. + whether to replace if the model already exists. Default to False. Returns: - ARIMAPlus: Saved model.""" + ARIMAPlus: saved model.""" if not self._bqml_model: raise RuntimeError("A model must be fitted before it can be saved") diff --git a/bigframes/ml/globals.py b/bigframes/ml/globals.py index 62cfdbef72f..c139476daaa 100644 --- a/bigframes/ml/globals.py +++ b/bigframes/ml/globals.py @@ -19,17 +19,6 @@ _BASE_SQL_GENERATOR = sql.BaseSqlGenerator() _BQML_MODEL_FACTORY = core.BqmlModelFactory() -_REMOTE_MODEL_SUPPORTED_DTYPES = ( - "bool", - "string", - "int64", - "float64", - "array", - "array", - "array", - "array", -) - def base_sql_generator() -> sql.BaseSqlGenerator: """Base SQL Generator.""" diff --git a/bigframes/ml/imported.py b/bigframes/ml/imported.py index ca83b0ee568..fb8aa98befd 100644 --- a/bigframes/ml/imported.py +++ b/bigframes/ml/imported.py @@ -16,33 +16,28 @@ from __future__ import annotations -import typing -from typing import Mapping, Optional +from typing import cast, Optional, Union from google.cloud import bigquery -import bigframes.pandas as bpd -import bigframes.session -from bigframes.core.logging import log_adapter +import bigframes from bigframes.ml import base, core, globals, utils +import bigframes.pandas as bpd -@log_adapter.class_logger class TensorFlowModel(base.Predictor): """Imported TensorFlow model. Args: - model_path (str): - Cloud Storage path that holds the model files. session (BigQuery Session): - BQ session to create the model. - """ + BQ session to create the model + model_path (str): + GCS path that holds the model files.""" def __init__( self, - model_path: str, - *, - session: Optional[bigframes.session.Session] = None, + session: Optional[bigframes.Session] = None, + model_path: Optional[str] = None, ): self.session = session or bpd.get_global_session() self.model_path = model_path @@ -57,72 +52,77 @@ def _create_bqml_model(self): @classmethod def _from_bq( - cls, session: bigframes.session.Session, bq_model: bigquery.Model + cls, session: bigframes.Session, model: bigquery.Model ) -> TensorFlowModel: - assert bq_model.model_type == "TENSORFLOW" + assert model.model_type == "TENSORFLOW" - model = cls(session=session, model_path="") - model._bqml_model = core.BqmlModel(session, bq_model) - return model + tf_model = cls(session=session, model_path=None) + tf_model._bqml_model = core.BqmlModel(session, model) + return tf_model - def predict(self, X: utils.ArrayType) -> bpd.DataFrame: + def predict(self, X: Union[bpd.DataFrame, bpd.Series]) -> bpd.DataFrame: """Predict the result from input DataFrame. Args: - X (bigframes.dataframe.DataFrame or bigframes.series.Series or pandas.core.frame.DataFrame or pandas.core.series.Series): - Input DataFrame. Schema is defined by the model. + X (bigframes.dataframe.DataFrame): + Input DataFrame, schema is defined by the model. Returns: - bigframes.dataframe.DataFrame: Output DataFrame. Schema is defined by the model. - """ + bigframes.dataframe.DataFrame: Output DataFrame, schema is defined by the model.""" if not self._bqml_model: if self.model_path is None: raise ValueError("Model GCS path must be provided.") self._bqml_model = self._create_bqml_model() - self._bqml_model = typing.cast(core.BqmlModel, self._bqml_model) - - (X,) = utils.batch_convert_to_dataframe(X) - - return self._bqml_model.predict(X) + self._bqml_model = cast(core.BqmlModel, self._bqml_model) + + (X,) = utils.convert_to_dataframe(X) + + df = self._bqml_model.predict(X) + return cast( + bpd.DataFrame, + df[ + [ + cast(str, field.name) + for field in self._bqml_model.model.label_columns + ] + ], + ) def to_gbq(self, model_name: str, replace: bool = False) -> TensorFlowModel: """Save the model to BigQuery. Args: model_name (str): - The name of the model. + the name of the model. replace (bool, default False): - Default to False. + whether to replace if the model already exists. Default to False. Returns: - TensorFlowModel: Saved model.""" + TensorFlowModel: saved model.""" if not self._bqml_model: if self.model_path is None: raise ValueError("Model GCS path must be provided.") self._bqml_model = self._create_bqml_model() - self._bqml_model = typing.cast(core.BqmlModel, self._bqml_model) + self._bqml_model = cast(core.BqmlModel, self._bqml_model) new_model = self._bqml_model.copy(model_name, replace) return new_model.session.read_gbq_model(model_name) -@log_adapter.class_logger class ONNXModel(base.Predictor): """Imported Open Neural Network Exchange (ONNX) model. Args: - model_path (str): - Cloud Storage path that holds the model files. session (BigQuery Session): - BQ session to create the model. - """ + BQ session to create the model + model_path (str): + Cloud Storage path that holds the model files.""" def __init__( self, - model_path: str, - *, - session: Optional[bigframes.session.Session] = None, + session: Optional[bigframes.Session] = None, + model_path: Optional[str] = None, ): self.session = session or bpd.get_global_session() self.model_path = model_path @@ -136,172 +136,58 @@ def _create_bqml_model(self): ) @classmethod - def _from_bq( - cls, session: bigframes.session.Session, bq_model: bigquery.Model - ) -> ONNXModel: - assert bq_model.model_type == "ONNX" + def _from_bq(cls, session: bigframes.Session, model: bigquery.Model) -> ONNXModel: + assert model.model_type == "ONNX" - model = cls(session=session, model_path="") - model._bqml_model = core.BqmlModel(session, bq_model) - return model + onnx_model = cls(session=session, model_path=None) + onnx_model._bqml_model = core.BqmlModel(session, model) + return onnx_model - def predict(self, X: utils.ArrayType) -> bpd.DataFrame: + def predict(self, X: Union[bpd.DataFrame, bpd.Series]) -> bpd.DataFrame: """Predict the result from input DataFrame. Args: - X (bigframes.dataframe.DataFrame or bigframes.series.Series or pandas.core.frame.DataFrame or pandas.core.series.Series): - Input DataFrame or Series. Schema is defined by the model. + X (bigframes.dataframe.DataFrame or bigframes.series.Series): + Input DataFrame or Series, schema is defined by the model. Returns: - bigframes.dataframe.DataFrame: Output DataFrame, schema is defined by the model. - """ - - if not self._bqml_model: - if self.model_path is None: - raise ValueError("Model GCS path must be provided.") - self._bqml_model = self._create_bqml_model() - self._bqml_model = typing.cast(core.BqmlModel, self._bqml_model) - - (X,) = utils.batch_convert_to_dataframe(X, session=self._bqml_model.session) + bigframes.dataframe.DataFrame: Output DataFrame, schema is defined by the model.""" - return self._bqml_model.predict(X) - - def to_gbq(self, model_name: str, replace: bool = False) -> ONNXModel: - """Save the model to BigQuery. - - Args: - model_name (str): - The name of the model. - replace (bool, default False): - Determine whether to replace if the model already exists. Default to False. - - Returns: - ONNXModel: Saved model.""" if not self._bqml_model: if self.model_path is None: raise ValueError("Model GCS path must be provided.") self._bqml_model = self._create_bqml_model() - self._bqml_model = typing.cast(core.BqmlModel, self._bqml_model) - - new_model = self._bqml_model.copy(model_name, replace) - return new_model.session.read_gbq_model(model_name) - - -@log_adapter.class_logger -class XGBoostModel(base.Predictor): - """Imported XGBoost model. - - .. warning:: - - Imported XGBoost models have the several limitations. See: - https://cloud.google.com/bigquery/docs/reference/standard-sql/bigqueryml-syntax-create-xgboost#limitations - - Args: - model_path (str): - Cloud Storage path that holds the model files. - input (Dict, default None): - Specify the model input schema information when you - create the XGBoost model. The input should be the format of - {field_name: field_type}. Input is optional only if feature_names - and feature_types are both specified in the model file. Supported types - are "bool", "string", "int64", "float64", "array", "array", "array", "array". - output (Dict, default None): - Specify the model output schema information when you - create the XGBoost model. The input should be the format of - {field_name: field_type}. Output is optional only if feature_names - and feature_types are both specified in the model file. Supported types - are "bool", "string", "int64", "float64", "array", "array", "array", "array". - session (BigQuery Session): - BQ session to create the model. - """ - - def __init__( - self, - model_path: str, - *, - input: Optional[Mapping[str, str]] = None, - output: Optional[Mapping[str, str]] = None, - session: Optional[bigframes.session.Session] = None, - ): - self.session = session or bpd.get_global_session() - self.model_path = model_path - self.input = input - self.output = output - self._bqml_model: Optional[core.BqmlModel] = None - self._bqml_model_factory = globals.bqml_model_factory() - - def _create_bqml_model(self): - options = {"model_type": "XGBOOST", "model_path": self.model_path} - - if not self.input and not self.output: - return self._bqml_model_factory.create_imported_model( - session=self.session, options=options - ) - if not self.input or not self.output: - raise ValueError("input and output must both or neigher be set.") - self.input = { - k: utils.standardize_type(v, globals._REMOTE_MODEL_SUPPORTED_DTYPES) - for k, v in self.input.items() - } - self.output = { - k: utils.standardize_type(v, globals._REMOTE_MODEL_SUPPORTED_DTYPES) - for k, v in self.output.items() - } - - return self._bqml_model_factory.create_xgboost_imported_model( - session=self.session, - input=self.input, - output=self.output, - options=options, + self._bqml_model = cast(core.BqmlModel, self._bqml_model) + + (X,) = utils.convert_to_dataframe(X) + + df = self._bqml_model.predict(X) + return cast( + bpd.DataFrame, + df[ + [ + cast(str, field.name) + for field in self._bqml_model.model.label_columns + ] + ], ) - @classmethod - def _from_bq( - cls, session: bigframes.session.Session, bq_model: bigquery.Model - ) -> XGBoostModel: - assert bq_model.model_type == "XGBOOST" - - model = cls(session=session, model_path="") - model._bqml_model = core.BqmlModel(session, bq_model) - return model - - def predict(self, X: utils.ArrayType) -> bpd.DataFrame: - """Predict the result from input DataFrame. - - Args: - X (bigframes.dataframe.DataFrame or bigframes.series.Series or pandas.core.frame.DataFrame or pandas.core.series.Series): - Input DataFrame or Series. Schema is defined by the model. - - Returns: - bigframes.dataframe.DataFrame: Output DataFrame. Schema is defined by the model. - """ - - if not self._bqml_model: - if self.model_path is None: - raise ValueError("Model GCS path must be provided.") - self._bqml_model = self._create_bqml_model() - self._bqml_model = typing.cast(core.BqmlModel, self._bqml_model) - - (X,) = utils.batch_convert_to_dataframe(X, session=self._bqml_model.session) - - return self._bqml_model.predict(X) - - def to_gbq(self, model_name: str, replace: bool = False) -> XGBoostModel: + def to_gbq(self, model_name: str, replace: bool = False) -> ONNXModel: """Save the model to BigQuery. Args: model_name (str): - The name of the model. + the name of the model. replace (bool, default False): - Determine whether to replace if the model already exists. Default to False. + whether to replace if the model already exists. Default to False. Returns: - XGBoostModel: Saved model.""" + ONNXModel: saved model.""" if not self._bqml_model: if self.model_path is None: raise ValueError("Model GCS path must be provided.") self._bqml_model = self._create_bqml_model() - self._bqml_model = typing.cast(core.BqmlModel, self._bqml_model) + self._bqml_model = cast(core.BqmlModel, self._bqml_model) new_model = self._bqml_model.copy(model_name, replace) return new_model.session.read_gbq_model(model_name) diff --git a/bigframes/ml/impute.py b/bigframes/ml/impute.py deleted file mode 100644 index 77314c360ad..00000000000 --- a/bigframes/ml/impute.py +++ /dev/null @@ -1,121 +0,0 @@ -# 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. -# 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. - -"""Transformers for missing value imputation. This module is styled after -scikit-learn's preprocessing module: https://scikit-learn.org/stable/modules/impute.html.""" - -from __future__ import annotations - -import typing -from typing import Iterable, List, Literal, Optional - -import bigframes_vendored.sklearn.impute._base - -import bigframes.core.utils as core_utils -import bigframes.pandas as bpd -from bigframes.core.logging import log_adapter -from bigframes.ml import base, core, globals, utils - - -@log_adapter.class_logger -class SimpleImputer( - base.Transformer, - bigframes_vendored.sklearn.impute._base.SimpleImputer, -): - __doc__ = bigframes_vendored.sklearn.impute._base.SimpleImputer.__doc__ - - def __init__( - self, - strategy: Literal["mean", "median", "most_frequent"] = "mean", - ): - self.strategy = strategy - self._bqml_model: Optional[core.BqmlModel] = None - self._bqml_model_factory = globals.bqml_model_factory() - self._base_sql_generator = globals.base_sql_generator() - - def _keys(self): - return (self._bqml_model, self.strategy) - - def _compile_to_sql( - self, - X: bpd.DataFrame, - columns: Optional[Iterable[str]] = None, - ) -> List[str]: - """Compile this transformer to a list of SQL expressions that can be included in - a BQML TRANSFORM clause - - Args: - X: DataFrame to transform. - columns: transform columns. If None, transform all columns in X. - - Returns: a list of tuples sql_expr.""" - if columns is None: - columns = X.columns - columns, _ = core_utils.get_standardized_ids(columns) - return [ - self._base_sql_generator.ml_imputer( - column, self.strategy, f"imputer_{column}" - ) - for column in columns - ] - - @classmethod - def _parse_from_sql(cls, sql: str) -> tuple[SimpleImputer, str]: - """Parse SQL to tuple(SimpleImputer, column_label). - - Args: - sql: SQL string of format "ML.IMPUTER({col_label}, {strategy}) OVER()" - - Returns: - tuple(SimpleImputer, column_label)""" - s = sql[sql.find("(") + 1 : sql.find(")")] - col_label, strategy = s.split(", ") - return cls(strategy[1:-1]), _unescape_id(col_label) # type: ignore[arg-type] - - def fit( - self, - X: utils.ArrayType, - y=None, # ignored - ) -> SimpleImputer: - (X,) = utils.batch_convert_to_dataframe(X) - - transform_sqls = self._compile_to_sql(X) - self._bqml_model = self._bqml_model_factory.create_model( - X, - options={"model_type": "transform_only"}, - transforms=transform_sqls, - ) - - self._extract_output_names() - return self - - def transform(self, X: utils.ArrayType) -> bpd.DataFrame: - if not self._bqml_model: - raise RuntimeError("Must be fitted before transform") - - (X,) = utils.batch_convert_to_dataframe(X, session=self._bqml_model.session) - - df = self._bqml_model.transform(X) - return typing.cast( - bpd.DataFrame, - df[self._output_names], - ) - - -def _unescape_id(id: str) -> str: - """Very simple conversion to removed ` characters from ids. - - A proper sql parser should be used instead. - """ - return id.removeprefix("`").removesuffix("`") diff --git a/bigframes/ml/linear_model.py b/bigframes/ml/linear_model.py index d35a2d45ecb..f11879500b9 100644 --- a/bigframes/ml/linear_model.py +++ b/bigframes/ml/linear_model.py @@ -17,17 +17,16 @@ from __future__ import annotations -from typing import Dict, List, Literal, Optional, Union +from typing import cast, Dict, List, Literal, Optional, Union -import bigframes_vendored.constants as constants -import bigframes_vendored.sklearn.linear_model._base -import bigframes_vendored.sklearn.linear_model._logistic from google.cloud import bigquery -import bigframes.pandas as bpd -import bigframes.session -from bigframes.core.logging import log_adapter +import bigframes +import bigframes.constants as constants from bigframes.ml import base, core, globals, utils +import bigframes.pandas as bpd +import third_party.bigframes_vendored.sklearn.linear_model._base +import third_party.bigframes_vendored.sklearn.linear_model._logistic _BQML_PARAMS_MAPPING = { "optimize_strategy": "optimizationStrategy", @@ -35,51 +34,49 @@ "l1_reg": "l1Regularization", "l2_reg": "l2Regularization", "max_iterations": "maxIterations", - "learning_rate_strategy": "learnRateStrategy", - "learning_rate": "learnRate", - "tol": "minRelativeProgress", - "ls_init_learning_rate": "initialLearnRate", + "learn_rate_strategy": "learnRateStrategy", + "learn_rate": "learnRate", + "early_stop": "earlyStop", + "min_rel_progress": "minRelativeProgress", + "ls_init_learn_rate": "initialLearnRate", "warm_start": "warmStart", "calculate_p_values": "calculatePValues", "enable_global_explain": "enableGlobalExplain", + "category_encoding_method": "categoryEncodingMethod", } -@log_adapter.class_logger class LinearRegression( - base.SupervisedTrainableWithEvaluationPredictor, - bigframes_vendored.sklearn.linear_model._base.LinearRegression, + base.SupervisedTrainablePredictor, + third_party.bigframes_vendored.sklearn.linear_model._base.LinearRegression, ): - __doc__ = bigframes_vendored.sklearn.linear_model._base.LinearRegression.__doc__ + __doc__ = ( + third_party.bigframes_vendored.sklearn.linear_model._base.LinearRegression.__doc__ + ) def __init__( self, - *, optimize_strategy: Literal[ "auto_strategy", "batch_gradient_descent", "normal_equation" - ] = "auto_strategy", + ] = "normal_equation", fit_intercept: bool = True, - l1_reg: Optional[float] = None, l2_reg: float = 0.0, max_iterations: int = 20, - warm_start: bool = False, - learning_rate: Optional[float] = None, - learning_rate_strategy: Literal["line_search", "constant"] = "line_search", - tol: float = 0.01, - ls_init_learning_rate: Optional[float] = None, + learn_rate_strategy: Literal["line_search", "constant"] = "line_search", + early_stop: bool = True, + min_rel_progress: float = 0.01, + ls_init_learn_rate: float = 0.1, calculate_p_values: bool = False, enable_global_explain: bool = False, ): self.optimize_strategy = optimize_strategy self.fit_intercept = fit_intercept - self.l1_reg = l1_reg self.l2_reg = l2_reg self.max_iterations = max_iterations - self.warm_start = warm_start - self.learning_rate = learning_rate - self.learning_rate_strategy = learning_rate_strategy - self.tol = tol - self.ls_init_learning_rate = ls_init_learning_rate + self.learn_rate_strategy = learn_rate_strategy + self.early_stop = early_stop + self.min_rel_progress = min_rel_progress + self.ls_init_learn_rate = ls_init_learn_rate self.calculate_p_values = calculate_p_values self.enable_global_explain = enable_global_explain self._bqml_model: Optional[core.BqmlModel] = None @@ -87,151 +84,87 @@ def __init__( @classmethod def _from_bq( - cls, session: bigframes.session.Session, bq_model: bigquery.Model + cls, session: bigframes.Session, model: bigquery.Model ) -> LinearRegression: - assert bq_model.model_type == "LINEAR_REGRESSION" + assert model.model_type == "LINEAR_REGRESSION" - kwargs = utils.retrieve_params_from_bq_model( - cls, bq_model, _BQML_PARAMS_MAPPING - ) + # TODO(bmil): construct a standard way to extract these properties + kwargs = {} - model = cls(**kwargs) - model._bqml_model = core.BqmlModel(session, bq_model) - return model + # See https://cloud.google.com/bigquery/docs/reference/rest/v2/models#trainingrun + last_fitting = model.training_runs[-1]["trainingOptions"] + + dummy_linear = cls() + for bf_param, bf_value in dummy_linear.__dict__.items(): + bqml_param = _BQML_PARAMS_MAPPING.get(bf_param) + if bqml_param in last_fitting: + kwargs[bf_param] = type(bf_value)(last_fitting[bqml_param]) + + new_linear_regression = cls(**kwargs) + new_linear_regression._bqml_model = core.BqmlModel(session, model) + return new_linear_regression @property - def _bqml_options(self) -> dict: + def _bqml_options(self) -> Dict[str, str | int | bool | float | List[str]]: """The model options as they will be set for BQML""" - options = { + # TODO: Support l1_reg, warm_start, and learn_rate with error catching. + return { "model_type": "LINEAR_REG", "data_split_method": "NO_SPLIT", "optimize_strategy": self.optimize_strategy, "fit_intercept": self.fit_intercept, "l2_reg": self.l2_reg, "max_iterations": self.max_iterations, - "learn_rate_strategy": self.learning_rate_strategy, - "min_rel_progress": self.tol, + "learn_rate_strategy": self.learn_rate_strategy, + "early_stop": self.early_stop, + "min_rel_progress": self.min_rel_progress, + "ls_init_learn_rate": self.ls_init_learn_rate, "calculate_p_values": self.calculate_p_values, "enable_global_explain": self.enable_global_explain, } - if self.l1_reg is not None: - options["l1_reg"] = self.l1_reg - if self.learning_rate is not None: - options["learn_rate"] = self.learning_rate - if self.ls_init_learning_rate is not None: - options["ls_init_learn_rate"] = self.ls_init_learning_rate - # Even presenting warm_start returns error for NORMAL_EQUATION optimizer - if self.warm_start: - options["warm_start"] = self.warm_start - - return options def _fit( self, - X: utils.ArrayType, - y: utils.ArrayType, + X: Union[bpd.DataFrame, bpd.Series], + y: Union[bpd.DataFrame, bpd.Series], transforms: Optional[List[str]] = None, - X_eval: Optional[utils.ArrayType] = None, - y_eval: Optional[utils.ArrayType] = None, ) -> LinearRegression: - X, y = utils.batch_convert_to_dataframe(X, y) - - bqml_options = self._bqml_options - - if X_eval is not None and y_eval is not None: - X_eval, y_eval = utils.batch_convert_to_dataframe(X_eval, y_eval) - X, y, bqml_options = utils.combine_training_and_evaluation_data( - X, y, X_eval, y_eval, bqml_options - ) + X, y = utils.convert_to_dataframe(X, y) self._bqml_model = self._bqml_model_factory.create_model( X, y, transforms=transforms, - options=bqml_options, + options=self._bqml_options, ) return self - def predict(self, X: utils.ArrayType) -> bpd.DataFrame: - if not self._bqml_model: - raise RuntimeError("A model must be fitted before predict") - (X,) = utils.batch_convert_to_dataframe(X, session=self._bqml_model.session) - - return self._bqml_model.predict(X) - - def predict_explain( - self, - X: utils.ArrayType, - *, - top_k_features: int = 5, - ) -> bpd.DataFrame: - """ - Explain predictions for a linear regression model. - - .. note:: - Output matches that of the BigQuery ML.EXPLAIN_PREDICT function. - See: https://cloud.google.com/bigquery/docs/reference/standard-sql/bigqueryml-syntax-explain-predict - - Args: - X (bigframes.dataframe.DataFrame or bigframes.series.Series or - pandas.core.frame.DataFrame or pandas.core.series.Series): - Series or a DataFrame to explain its predictions. - top_k_features (int, default 5): - an INT64 value that specifies how many top feature attribution - pairs are generated for each row of input data. The features are - ranked by the absolute values of their attributions. - - By default, top_k_features is set to 5. If its value is greater - than the number of features in the training data, the - attributions of all features are returned. - - Returns: - bigframes.pandas.DataFrame: - The predicted DataFrames with explanation columns. - """ - if top_k_features < 1: - raise ValueError( - f"top_k_features must be at least 1, but is {top_k_features}." - ) - + def predict(self, X: Union[bpd.DataFrame, bpd.Series]) -> bpd.DataFrame: if not self._bqml_model: raise RuntimeError("A model must be fitted before predict") - (X,) = utils.batch_convert_to_dataframe(X, session=self._bqml_model.session) - - return self._bqml_model.explain_predict( - X, options={"top_k_features": top_k_features} + (X,) = utils.convert_to_dataframe(X) + + df = self._bqml_model.predict(X) + return cast( + bpd.DataFrame, + df[ + [ + cast(str, field.name) + for field in self._bqml_model.model.label_columns + ] + ], ) - def global_explain( - self, - ) -> bpd.DataFrame: - """ - Provide explanations for an entire linear regression model. - - .. note:: - Output matches that of the BigQuery ML.GLOBAL_EXPLAIN function. - See: https://cloud.google.com/bigquery/docs/reference/standard-sql/bigqueryml-syntax-global-explain - - Returns: - bigframes.pandas.DataFrame: - Dataframes containing feature importance values and corresponding attributions, designed to provide a global explanation of feature influence. - """ - - if not self._bqml_model: - raise RuntimeError("A model must be fitted before predict") - - return self._bqml_model.global_explain({}) - def score( self, - X: utils.ArrayType, - y: utils.ArrayType, + X: Union[bpd.DataFrame, bpd.Series], + y: Union[bpd.DataFrame, bpd.Series], ) -> bpd.DataFrame: if not self._bqml_model: raise RuntimeError("A model must be fitted before score") - X, y = utils.batch_convert_to_dataframe(X, y, session=self._bqml_model.session) + X, y = utils.convert_to_dataframe(X, y) input_data = X.join(y, how="outer") return self._bqml_model.evaluate(input_data) @@ -241,12 +174,12 @@ def to_gbq(self, model_name: str, replace: bool = False) -> LinearRegression: Args: model_name (str): - The name of the model. + the name of the model. replace (bool, default False): - Determine whether to replace if the model already exists. Default to False. + whether to replace if the model already exists. Default to False. Returns: - LinearRegression: Saved model.""" + LinearRegression: saved model.""" if not self._bqml_model: raise RuntimeError("A model must be fitted before it can be saved") @@ -254,193 +187,106 @@ def to_gbq(self, model_name: str, replace: bool = False) -> LinearRegression: return new_model.session.read_gbq_model(model_name) -@log_adapter.class_logger class LogisticRegression( - base.SupervisedTrainableWithEvaluationPredictor, - bigframes_vendored.sklearn.linear_model._logistic.LogisticRegression, + base.SupervisedTrainablePredictor, + third_party.bigframes_vendored.sklearn.linear_model._logistic.LogisticRegression, ): __doc__ = ( - bigframes_vendored.sklearn.linear_model._logistic.LogisticRegression.__doc__ + third_party.bigframes_vendored.sklearn.linear_model._logistic.LogisticRegression.__doc__ ) - # TODO(ashleyxu) support class_weight in the constructor. + # TODO(ashleyxu) support class_weights in the constructor. def __init__( self, - *, - optimize_strategy: Literal[ - "auto_strategy", "batch_gradient_descent" - ] = "auto_strategy", fit_intercept: bool = True, - l1_reg: Optional[float] = None, - l2_reg: float = 0.0, - max_iterations: int = 20, - warm_start: bool = False, - learning_rate: Optional[float] = None, - learning_rate_strategy: Literal["line_search", "constant"] = "line_search", - tol: float = 0.01, - ls_init_learning_rate: Optional[float] = None, - calculate_p_values: bool = False, - enable_global_explain: bool = False, - class_weight: Optional[Union[Literal["balanced"], Dict[str, float]]] = None, + class_weights: Optional[Union[Literal["balanced"], Dict[str, float]]] = None, ): - self.optimize_strategy = optimize_strategy self.fit_intercept = fit_intercept - self.l1_reg = l1_reg - self.l2_reg = l2_reg - self.max_iterations = max_iterations - self.warm_start = warm_start - self.learning_rate = learning_rate - self.learning_rate_strategy = learning_rate_strategy - self.tol = tol - self.ls_init_learning_rate = ls_init_learning_rate - self.calculate_p_values = calculate_p_values - self.enable_global_explain = enable_global_explain - self.class_weight = class_weight - self._auto_class_weight = class_weight == "balanced" + self.class_weights = class_weights + self._auto_class_weight = class_weights == "balanced" self._bqml_model: Optional[core.BqmlModel] = None self._bqml_model_factory = globals.bqml_model_factory() @classmethod def _from_bq( - cls, session: bigframes.session.Session, bq_model: bigquery.Model + cls, session: bigframes.Session, model: bigquery.Model ) -> LogisticRegression: - assert bq_model.model_type == "LOGISTIC_REGRESSION" + assert model.model_type == "LOGISTIC_REGRESSION" - kwargs = utils.retrieve_params_from_bq_model( - cls, bq_model, _BQML_PARAMS_MAPPING - ) + kwargs = {} - last_fitting = bq_model.training_runs[-1]["trainingOptions"] + # See https://cloud.google.com/bigquery/docs/reference/rest/v2/models#trainingrun + last_fitting = model.training_runs[-1]["trainingOptions"] + if "fitIntercept" in last_fitting: + kwargs["fit_intercept"] = last_fitting["fitIntercept"] if last_fitting["autoClassWeights"]: - kwargs["class_weight"] = "balanced" - # TODO(ashleyxu) support class_weight in the constructor. + kwargs["class_weights"] = "balanced" + # TODO(ashleyxu) support class_weights in the constructor. # if "labelClassWeights" in last_fitting: - # kwargs["class_weight"] = last_fitting["labelClassWeights"] + # kwargs["class_weights"] = last_fitting["labelClassWeights"] - model = cls(**kwargs) - model._bqml_model = core.BqmlModel(session, bq_model) - return model + new_logistic_regression = cls(**kwargs) + new_logistic_regression._bqml_model = core.BqmlModel(session, model) + return new_logistic_regression @property - def _bqml_options(self) -> dict: + def _bqml_options(self) -> Dict[str, str | int | float | List[str]]: """The model options as they will be set for BQML""" - options = { + return { "model_type": "LOGISTIC_REG", "data_split_method": "NO_SPLIT", "fit_intercept": self.fit_intercept, "auto_class_weights": self._auto_class_weight, - "optimize_strategy": self.optimize_strategy, - "l2_reg": self.l2_reg, - "max_iterations": self.max_iterations, - "learn_rate_strategy": self.learning_rate_strategy, - "min_rel_progress": self.tol, - "calculate_p_values": self.calculate_p_values, - "enable_global_explain": self.enable_global_explain, - # TODO(ashleyxu): support class_weight (struct array as dict in our API) - # "class_weight": self.class_weight, + # TODO(ashleyxu): support class_weights (struct array as dict in our API) + # "class_weights": self.class_weights, } - if self.l1_reg is not None: - options["l1_reg"] = self.l1_reg - if self.learning_rate is not None: - options["learn_rate"] = self.learning_rate - if self.ls_init_learning_rate is not None: - options["ls_init_learn_rate"] = self.ls_init_learning_rate - # Even presenting warm_start returns error for NORMAL_EQUATION optimizer - if self.warm_start: - options["warm_start"] = self.warm_start - - return options def _fit( self, - X: utils.ArrayType, - y: utils.ArrayType, + X: Union[bpd.DataFrame, bpd.Series], + y: Union[bpd.DataFrame, bpd.Series], transforms: Optional[List[str]] = None, - X_eval: Optional[utils.ArrayType] = None, - y_eval: Optional[utils.ArrayType] = None, ) -> LogisticRegression: - X, y = utils.batch_convert_to_dataframe(X, y) - - bqml_options = self._bqml_options - - if X_eval is not None and y_eval is not None: - X_eval, y_eval = utils.batch_convert_to_dataframe(X_eval, y_eval) - X, y, bqml_options = utils.combine_training_and_evaluation_data( - X, y, X_eval, y_eval, bqml_options - ) + """Fit model with transforms.""" + X, y = utils.convert_to_dataframe(X, y) self._bqml_model = self._bqml_model_factory.create_model( X, y, transforms=transforms, - options=bqml_options, + options=self._bqml_options, ) return self def predict( self, - X: utils.ArrayType, + X: Union[bpd.DataFrame, bpd.Series], ) -> bpd.DataFrame: if not self._bqml_model: raise RuntimeError("A model must be fitted before predict") - (X,) = utils.batch_convert_to_dataframe(X, session=self._bqml_model.session) - - return self._bqml_model.predict(X) - - def predict_explain( - self, - X: utils.ArrayType, - *, - top_k_features: int = 5, - ) -> bpd.DataFrame: - """ - Explain predictions for a logistic regression model. - - .. note:: - Output matches that of the BigQuery ML.EXPLAIN_PREDICT function. - See: https://cloud.google.com/bigquery/docs/reference/standard-sql/bigqueryml-syntax-explain-predict - - Args: - X (bigframes.dataframe.DataFrame or bigframes.series.Series or - pandas.core.frame.DataFrame or pandas.core.series.Series): - Series or a DataFrame to explain its predictions. - top_k_features (int, default 5): - an INT64 value that specifies how many top feature attribution - pairs are generated for each row of input data. The features are - ranked by the absolute values of their attributions. - - By default, top_k_features is set to 5. If its value is greater - than the number of features in the training data, the - attributions of all features are returned. - - Returns: - bigframes.pandas.DataFrame: - The predicted DataFrames with explanation columns. - """ - if top_k_features < 1: - raise ValueError( - f"top_k_features must be at least 1, but is {top_k_features}." - ) - - if not self._bqml_model: - raise RuntimeError("A model must be fitted before predict") - - (X,) = utils.batch_convert_to_dataframe(X, session=self._bqml_model.session) - - return self._bqml_model.explain_predict( - X, options={"top_k_features": top_k_features} + (X,) = utils.convert_to_dataframe(X) + + df = self._bqml_model.predict(X) + return cast( + bpd.DataFrame, + df[ + [ + cast(str, field.name) + for field in self._bqml_model.model.label_columns + ] + ], ) def score( self, - X: utils.ArrayType, - y: utils.ArrayType, + X: Union[bpd.DataFrame, bpd.Series], + y: Union[bpd.DataFrame, bpd.Series], ) -> bpd.DataFrame: if not self._bqml_model: raise RuntimeError("A model must be fitted before score") - X, y = utils.batch_convert_to_dataframe(X, y, session=self._bqml_model.session) + X, y = utils.convert_to_dataframe(X, y) input_data = X.join(y, how="outer") return self._bqml_model.evaluate(input_data) @@ -450,19 +296,19 @@ def to_gbq(self, model_name: str, replace: bool = False) -> LogisticRegression: Args: model_name (str): - The name of the model. + the name of the model. replace (bool, default False): - Determine whether to replace if the model already exists. Default to False. + whether to replace if the model already exists. Default to False. Returns: - LogisticRegression: Saved model.""" + LogisticRegression: saved model.""" if not self._bqml_model: raise RuntimeError("A model must be fitted before it can be saved") - # TODO(ashleyxu): support class_weight (struct array as dict in our API) - if self.class_weight not in (None, "balanced"): + # TODO(ashleyxu): support class_weights (struct array as dict in our API) + if self.class_weights not in (None, "balanced"): raise NotImplementedError( - f"class_weight is not supported yet. {constants.FEEDBACK_LINK}" + f"class_weights is not supported yet. {constants.FEEDBACK_LINK}" ) new_model = self._bqml_model.copy(model_name, replace) diff --git a/bigframes/ml/llm.py b/bigframes/ml/llm.py index e99c7a41d00..3cfc28e61f8 100644 --- a/bigframes/ml/llm.py +++ b/bigframes/ml/llm.py @@ -16,1063 +16,279 @@ from __future__ import annotations -import typing -import warnings -from typing import Iterable, Literal, Mapping, Optional, Union +from typing import cast, Literal, Optional, Union -import bigframes_vendored.constants as constants -from google.cloud import bigquery - -import bigframes.bigquery as bbq -import bigframes.dataframe -import bigframes.series -from bigframes import dtypes, exceptions -from bigframes.core import blocks, global_session -from bigframes.core.logging import log_adapter +import bigframes +from bigframes import clients, constants +from bigframes.core import blocks from bigframes.ml import base, core, globals, utils +import bigframes.pandas as bpd -_BQML_PARAMS_MAPPING = { - "max_iterations": "maxIterations", -} - -_TEXT_EMBEDDING_005_ENDPOINT = "text-embedding-005" -_TEXT_EMBEDDING_004_ENDPOINT = "text-embedding-004" -_TEXT_MULTILINGUAL_EMBEDDING_002_ENDPOINT = "text-multilingual-embedding-002" -_TEXT_EMBEDDING_ENDPOINTS = ( - _TEXT_EMBEDDING_005_ENDPOINT, - _TEXT_EMBEDDING_004_ENDPOINT, - _TEXT_MULTILINGUAL_EMBEDDING_002_ENDPOINT, -) - -_MULTIMODAL_EMBEDDING_001_ENDPOINT = "multimodalembedding@001" - -_GEMINI_1P5_PRO_PREVIEW_ENDPOINT = "gemini-1.5-pro-preview-0514" -_GEMINI_1P5_PRO_FLASH_PREVIEW_ENDPOINT = "gemini-1.5-flash-preview-0514" -_GEMINI_1P5_PRO_001_ENDPOINT = "gemini-1.5-pro-001" -_GEMINI_1P5_PRO_002_ENDPOINT = "gemini-1.5-pro-002" -_GEMINI_1P5_FLASH_001_ENDPOINT = "gemini-1.5-flash-001" -_GEMINI_1P5_FLASH_002_ENDPOINT = "gemini-1.5-flash-002" -_GEMINI_2_FLASH_EXP_ENDPOINT = "gemini-2.0-flash-exp" -_GEMINI_2_FLASH_001_ENDPOINT = "gemini-2.0-flash-001" -_GEMINI_2_FLASH_LITE_001_ENDPOINT = "gemini-2.0-flash-lite-001" -_GEMINI_2P5_PRO_PREVIEW_ENDPOINT = "gemini-2.5-pro-preview-05-06" -_GEMINI_2P5_PRO_ENDPOINT = "gemini-2.5-pro" -_GEMINI_2P5_FLASH_ENDPOINT = "gemini-2.5-flash" -_GEMINI_2P5_FLASH_LITE_ENDPOINT = "gemini-2.5-flash-lite" -_GEMINI_3P1_FLASH_LITE_ENDPOINT = "gemini-3.1-flash-lite" -_GEMINI_3P5_FLASH_ENDPOINT = "gemini-3.5-flash" - -_GEMINI_ENDPOINTS = ( - _GEMINI_1P5_PRO_PREVIEW_ENDPOINT, - _GEMINI_1P5_PRO_FLASH_PREVIEW_ENDPOINT, - _GEMINI_1P5_PRO_001_ENDPOINT, - _GEMINI_1P5_PRO_002_ENDPOINT, - _GEMINI_1P5_FLASH_001_ENDPOINT, - _GEMINI_1P5_FLASH_002_ENDPOINT, - _GEMINI_2_FLASH_EXP_ENDPOINT, - _GEMINI_2_FLASH_001_ENDPOINT, - _GEMINI_2_FLASH_LITE_001_ENDPOINT, - _GEMINI_2P5_PRO_ENDPOINT, - _GEMINI_2P5_FLASH_ENDPOINT, - _GEMINI_2P5_FLASH_LITE_ENDPOINT, - _GEMINI_3P1_FLASH_LITE_ENDPOINT, - _GEMINI_3P5_FLASH_ENDPOINT, -) -_GEMINI_PREVIEW_ENDPOINTS = ( - _GEMINI_1P5_PRO_PREVIEW_ENDPOINT, - _GEMINI_1P5_PRO_FLASH_PREVIEW_ENDPOINT, - _GEMINI_2_FLASH_EXP_ENDPOINT, -) -_GEMINI_FINE_TUNE_SCORE_ENDPOINTS = ( - _GEMINI_1P5_PRO_002_ENDPOINT, - _GEMINI_1P5_FLASH_002_ENDPOINT, - _GEMINI_2_FLASH_001_ENDPOINT, - _GEMINI_2_FLASH_LITE_001_ENDPOINT, -) -_GEMINI_MULTIMODAL_ENDPOINTS = ( - _GEMINI_1P5_PRO_001_ENDPOINT, - _GEMINI_1P5_PRO_002_ENDPOINT, - _GEMINI_1P5_FLASH_001_ENDPOINT, - _GEMINI_1P5_FLASH_002_ENDPOINT, - _GEMINI_2_FLASH_EXP_ENDPOINT, - _GEMINI_2_FLASH_001_ENDPOINT, - _GEMINI_2_FLASH_LITE_001_ENDPOINT, - _GEMINI_2P5_PRO_ENDPOINT, - _GEMINI_2P5_FLASH_ENDPOINT, - _GEMINI_2P5_FLASH_LITE_ENDPOINT, - _GEMINI_3P1_FLASH_LITE_ENDPOINT, - _GEMINI_3P5_FLASH_ENDPOINT, -) - -_CLAUDE_3_SONNET_ENDPOINT = "claude-3-sonnet" -_CLAUDE_3_HAIKU_ENDPOINT = "claude-3-haiku" -_CLAUDE_3_5_SONNET_ENDPOINT = "claude-3-5-sonnet" -_CLAUDE_3_OPUS_ENDPOINT = "claude-3-opus" -_CLAUDE_3_ENDPOINTS = ( - _CLAUDE_3_SONNET_ENDPOINT, - _CLAUDE_3_HAIKU_ENDPOINT, - _CLAUDE_3_5_SONNET_ENDPOINT, - _CLAUDE_3_OPUS_ENDPOINT, -) - -_MODEL_NOT_SUPPORTED_WARNING = ( - "Model name '{model_name}' is not supported. " - "We are currently aware of the following models: {known_models}. " - "However, model names can change, and the supported models may be outdated. " - "You should use this model name only if you are sure that it is supported in BigQuery." -) - -_REMOVE_DEFAULT_MODEL_WARNING = "Since upgrading the default model can cause unintended breakages, the default model will be removed in BigFrames 3.0. Please supply an explicit model to avoid this message." +_REMOTE_TEXT_GENERATOR_MODEL_ENDPOINT = "text-bison" +_REMOTE_TEXT_GENERATOR_32K_MODEL_ENDPOINT = "text-bison-32k" +_TEXT_GENERATE_RESULT_COLUMN = "ml_generate_text_llm_result" -_GEMINI_MULTIMODAL_MODEL_NOT_SUPPORTED_WARNING = ( - "The model '{model_name}' may not be fully supported by GeminiTextGenerator for Multimodal prompts. " - "GeminiTextGenerator is known to support the following models for Multimodal prompts: {known_models}. " - "If you proceed with '{model_name}', it might not work as expected or could lead to errors with multimodal inputs." +_REMOTE_EMBEDDING_GENERATOR_MODEL_ENDPOINT = "textembedding-gecko" +_REMOTE_EMBEDDING_GENERATOR_MUlTILINGUAL_MODEL_ENDPOINT = ( + "textembedding-gecko-multilingual" ) +_EMBED_TEXT_RESULT_COLUMN = "text_embedding" -_MODEL_DEPRECATE_WARNING = ( - "'{model_name}' is going to be deprecated. Use '{new_model_name}' ({link}) instead." -) - - -@log_adapter.class_logger -class TextEmbeddingGenerator(base.RetriableRemotePredictor): - """Text embedding generator LLM model. - .. note:: - text-embedding-004 is going to be deprecated. Use text-embedding-005(https://cloud.google.com/python/docs/reference/bigframes/latest/bigframes.ml.llm.TextEmbeddingGenerator) instead. +class PaLM2TextGenerator(base.Predictor): + """PaLM2 text generator LLM model. Args: - model_name (str, Default to "text-embedding-004"): - The model for text embedding. Possible values are "text-embedding-005", "text-embedding-004" - or "text-multilingual-embedding-002". text-embedding models returns model embeddings for text inputs. - text-multilingual-embedding models returns model embeddings for text inputs which support over 100 languages. - If no setting is provided, "text-embedding-004" will be used by - default and a warning will be issued. + model_name (str, Default to "text-bison"): + The model for natural language tasks. “text-bison” returns model fine-tuned to follow natural language instructions + and is suitable for a variety of language tasks. "text-bison-32k" supports up to 32k tokens per request. + Default to "text-bison". session (bigframes.Session or None): BQ session to create the model. If None, use the global default session. connection_name (str or None): Connection to connect with remote service. str of the format ... - If None, use default connection in session context. + if None, use default connection in session context. BigQuery DataFrame will try to create the connection and attach + permission if the connection isn't fully setup. """ def __init__( self, - *, - model_name: Optional[ - Literal[ - "text-embedding-005", - "text-embedding-004", - "text-multilingual-embedding-002", - ] - ] = None, + model_name: Literal["text-bison", "text-bison-32k"] = "text-bison", session: Optional[bigframes.Session] = None, connection_name: Optional[str] = None, ): - if model_name is None: - model_name = "text-embedding-004" - msg = exceptions.format_message(_REMOVE_DEFAULT_MODEL_WARNING) - warnings.warn(msg, category=FutureWarning, stacklevel=2) self.model_name = model_name - self.session = session or global_session.get_global_session() - self.connection_name = connection_name - - self._bqml_model_factory = globals.bqml_model_factory() - self._bqml_model: core.BqmlModel = self._create_bqml_model() - - def _create_bqml_model(self): - # Parse and create connection if needed. - self.connection_name = self.session._create_bq_connection( - connection=self.connection_name, iam_role="aiplatform.user" + self.session = session or bpd.get_global_session() + self._bq_connection_manager = clients.BqConnectionManager( + self.session.bqconnectionclient, self.session.resourcemanagerclient ) - if self.model_name not in _TEXT_EMBEDDING_ENDPOINTS: - msg = exceptions.format_message( - _MODEL_NOT_SUPPORTED_WARNING.format( - model_name=self.model_name, - known_models=", ".join(_TEXT_EMBEDDING_ENDPOINTS), - ) - ) - warnings.warn(msg) - if self.model_name == "text-embedding-004": - msg = exceptions.format_message( - _MODEL_DEPRECATE_WARNING.format( - model_name=self.model_name, - new_model_name="text-embedding-005", - link="https://cloud.google.com/python/docs/reference/bigframes/latest/bigframes.ml.llm.TextEmbeddingGenerator", - ) - ) - warnings.warn(msg) - - options = { - "endpoint": self.model_name, - } - return self._bqml_model_factory.create_remote_model( - session=self.session, connection_name=self.connection_name, options=options - ) - - @classmethod - def _from_bq( - cls, session: bigframes.Session, bq_model: bigquery.Model - ) -> TextEmbeddingGenerator: - assert bq_model.model_type == "MODEL_TYPE_UNSPECIFIED" - assert "remoteModelInfo" in bq_model._properties - assert "endpoint" in bq_model._properties["remoteModelInfo"] - assert "connection" in bq_model._properties["remoteModelInfo"] - - # Parse the remote model endpoint - bqml_endpoint = bq_model._properties["remoteModelInfo"]["endpoint"] - model_connection = bq_model._properties["remoteModelInfo"]["connection"] - model_endpoint = bqml_endpoint.split("/")[-1] - - model = cls( - session=session, - model_name=model_endpoint, # type: ignore - connection_name=model_connection, + connection_name = connection_name or self.session._bq_connection + self.connection_name = self._bq_connection_manager.resolve_full_connection_name( + connection_name, + default_project=self.session._project, + default_location=self.session._location, ) - model._bqml_model = core.BqmlModel(session, bq_model) - return model - - def predict( - self, X: utils.ArrayType, *, max_retries: int = 0 - ) -> bigframes.dataframe.DataFrame: - """Predict the result from input DataFrame. - - Args: - X (bigframes.dataframe.DataFrame or bigframes.series.Series or pandas.core.frame.DataFrame or pandas.core.series.Series): - Input DataFrame or Series, can contain one or more columns. If multiple columns are in the DataFrame, it must contain a "content" column for prediction. - - max_retries (int, default 0): - Max number of retries if the prediction for any rows failed. Each try needs to make progress (i.e. has successfully predicted rows) to continue the retry. - Each retry will append newly succeeded rows. When the max retries are reached, the remaining rows (the ones without successful predictions) will be appended to the end of the result. - - Returns: - bigframes.dataframe.DataFrame: DataFrame of shape (n_samples, n_input_columns + n_prediction_columns). Returns predicted values. - """ - if max_retries < 0: - raise ValueError( - f"max_retries must be larger than or equal to 0, but is {max_retries}." - ) - - (X,) = utils.batch_convert_to_dataframe(X, session=self._bqml_model.session) - - if len(X.columns) == 1: - # BQML identified the column by name - col_label = typing.cast(blocks.Label, X.columns[0]) - X = X.rename(columns={col_label: "content"}) - - options: dict = {} - - return self._predict_and_retry( - core.BqmlModel.generate_embedding_tvf, - X, - options=options, - max_retries=max_retries, - ) - - def to_gbq(self, model_name: str, replace: bool = False) -> TextEmbeddingGenerator: - """Save the model to BigQuery. - - Args: - model_name (str): - The name of the model. - replace (bool, default False): - Determine whether to replace if the model already exists. Default to False. - - Returns: - TextEmbeddingGenerator: Saved model.""" - - new_model = self._bqml_model.copy(model_name, replace) - return new_model.session.read_gbq_model(model_name) - - -@log_adapter.class_logger -class MultimodalEmbeddingGenerator(base.RetriableRemotePredictor): - """Multimodal embedding generator LLM model. - - .. note:: - BigFrames ObjectRef 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: - model_name (str, Default to "multimodalembedding@001"): - The model for multimodal embedding. Can set to "multimodalembedding@001". Multimodal-embedding models returns model embeddings for text, image and video inputs. - If no setting is provided, "multimodalembedding@001" will be used by - default and a warning will be issued. - session (bigframes.Session or None): - BQ session to create the model. If None, use the global default session. - connection_name (str or None): - Connection to connect with remote service. str of the format ... - If None, use default connection in session context. - """ - - def __init__( - self, - *, - model_name: Optional[Literal["multimodalembedding@001"]] = None, - session: Optional[bigframes.Session] = None, - connection_name: Optional[str] = None, - ): - if model_name is None: - model_name = "multimodalembedding@001" - msg = exceptions.format_message(_REMOVE_DEFAULT_MODEL_WARNING) - warnings.warn(msg, category=FutureWarning, stacklevel=2) - self.model_name = model_name - self.session = session or global_session.get_global_session() - self.connection_name = connection_name - self._bqml_model_factory = globals.bqml_model_factory() self._bqml_model: core.BqmlModel = self._create_bqml_model() def _create_bqml_model(self): # Parse and create connection if needed. - self.connection_name = self.session._create_bq_connection( - connection=self.connection_name, iam_role="aiplatform.user" - ) - - if self.model_name != _MULTIMODAL_EMBEDDING_001_ENDPOINT: - msg = exceptions.format_message( - _MODEL_NOT_SUPPORTED_WARNING.format( - model_name=self.model_name, - known_models=_MULTIMODAL_EMBEDDING_001_ENDPOINT, - ) - ) - warnings.warn(msg) - - options = { - "endpoint": self.model_name, - } - return self._bqml_model_factory.create_remote_model( - session=self.session, connection_name=self.connection_name, options=options - ) - - @classmethod - def _from_bq( - cls, session: bigframes.Session, bq_model: bigquery.Model - ) -> MultimodalEmbeddingGenerator: - assert bq_model.model_type == "MODEL_TYPE_UNSPECIFIED" - assert "remoteModelInfo" in bq_model._properties - assert "endpoint" in bq_model._properties["remoteModelInfo"] - assert "connection" in bq_model._properties["remoteModelInfo"] - - # Parse the remote model endpoint - bqml_endpoint = bq_model._properties["remoteModelInfo"]["endpoint"] - model_connection = bq_model._properties["remoteModelInfo"]["connection"] - model_endpoint = bqml_endpoint.split("/")[-1] - - model = cls( - session=session, - model_name=model_endpoint, # type: ignore - connection_name=model_connection, - ) - - model._bqml_model = core.BqmlModel(session, bq_model) - return model - - def predict( - self, X: utils.ArrayType, *, max_retries: int = 0 - ) -> bigframes.dataframe.DataFrame: - """Predict the result from input DataFrame. - - Args: - X (bigframes.dataframe.DataFrame or bigframes.series.Series or pandas.core.frame.DataFrame or pandas.core.series.Series): - Input DataFrame or Series, can contain one or more columns. If multiple columns are in the DataFrame, it must contain a "content" column for prediction. - The content column must be of string type or BigFrames `ObjectRef `_ of image or video. - - max_retries (int, default 0): - Max number of retries if the prediction for any rows failed. Each try needs to make progress (i.e. has successfully predicted rows) to continue the retry. - Each retry will append newly succeeded rows. When the max retries are reached, the remaining rows (the ones without successful predictions) will be appended to the end of the result. - - Returns: - bigframes.dataframe.DataFrame: DataFrame of shape (n_samples, n_input_columns + n_prediction_columns). Returns predicted values. - """ - if max_retries < 0: + if not self.connection_name: raise ValueError( - f"max_retries must be larger than or equal to 0, but is {max_retries}." + "Must provide connection_name, either in constructor or through session options." ) - - (X,) = utils.batch_convert_to_dataframe(X, session=self._bqml_model.session) - - if len(X.columns) == 1: - # BQML identified the column by name - col_label = typing.cast(blocks.Label, X.columns[0]) - X = X.rename(columns={col_label: "content"}) - - # TODO(garrettwu): remove transform to ObjRefRuntime when BQML supports ObjRef as input - if X["content"].dtype == dtypes.OBJ_REF_DTYPE: - X["content"] = bbq.obj.get_access_url(X["content"], mode="r") - - options: dict = {} - - return self._predict_and_retry( - core.BqmlModel.generate_embedding_tvf, - X, - options=options, - max_retries=max_retries, - ) - - def to_gbq( - self, model_name: str, replace: bool = False - ) -> MultimodalEmbeddingGenerator: - """Save the model to BigQuery. - - Args: - model_name (str): - The name of the model. - replace (bool, default False): - Determine whether to replace if the model already exists. Default to False. - - Returns: - MultimodalEmbeddingGenerator: Saved model.""" - - new_model = self._bqml_model.copy(model_name, replace) - return new_model.session.read_gbq_model(model_name) - - -@log_adapter.class_logger -class GeminiTextGenerator(base.RetriableRemotePredictor): - """Gemini text generator LLM model. - - .. note:: - gemini-1.5-X are going to be deprecated. Use gemini-2.5-X (https://cloud.google.com/python/docs/reference/bigframes/latest/bigframes.ml.llm.GeminiTextGenerator) instead. - - Args: - model_name (str, Default to "gemini-2.0-flash-001"): - The model for natural language tasks. Accepted values are - "gemini-1.5-pro-preview-0514", "gemini-1.5-flash-preview-0514", - "gemini-1.5-pro-001", "gemini-1.5-pro-002", "gemini-1.5-flash-001", - "gemini-1.5-flash-002", "gemini-2.0-flash-exp", - "gemini-2.0-flash-lite-001", "gemini-2.0-flash-001", - "gemini-2.5-pro", "gemini-2.5-flash", "gemini-2.5-flash-lite", - "gemini-3.1-flash-lite" and "gemini-3.5-flash". - If no setting is provided, "gemini-2.0-flash-001" will be used by - default and a warning will be issued. - - .. note:: - "gemini-1.5-X" is going to be deprecated. Please use gemini-2.5-X instead. For example, "gemini-2.5-flash". - "gemini-2.0-flash-exp", "gemini-1.5-pro-preview-0514" and "gemini-1.5-flash-preview-0514" 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). - - session (bigframes.Session or None): - BQ session to create the model. If None, use the global default session. - connection_name (str or None): - Connection to connect with remote service. str of the format ... - If None, use default connection in session context. BigQuery DataFrame will try to create the connection and attach - permission if the connection isn't fully set up. - max_iterations (Optional[int], Default to 300): - The number of steps to run when performing supervised tuning. - """ - - def __init__( - self, - *, - model_name: Optional[ - Literal[ - "gemini-1.5-pro-preview-0514", - "gemini-1.5-flash-preview-0514", - "gemini-1.5-pro-001", - "gemini-1.5-pro-002", - "gemini-1.5-flash-001", - "gemini-1.5-flash-002", - "gemini-2.0-flash-exp", - "gemini-2.0-flash-001", - "gemini-2.0-flash-lite-001", - "gemini-2.5-pro", - "gemini-2.5-flash", - "gemini-2.5-flash-lite", - "gemini-3.1-flash-lite", - "gemini-3.5-flash", - ] - ] = None, - session: Optional[bigframes.Session] = None, - connection_name: Optional[str] = None, - max_iterations: int = 300, - ): - if model_name in _GEMINI_PREVIEW_ENDPOINTS: - msg = exceptions.format_message( - f'Model {model_name} 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)." + connection_name_parts = self.connection_name.split(".") + if len(connection_name_parts) != 3: + raise ValueError( + f"connection_name must be of the format .., got {self.connection_name}." ) - warnings.warn(msg, category=exceptions.PreviewWarning) - - if model_name is None: - model_name = "gemini-2.0-flash-001" - msg = exceptions.format_message(_REMOVE_DEFAULT_MODEL_WARNING) - warnings.warn(msg, category=FutureWarning, stacklevel=2) - - self.model_name = model_name - self.session = session or global_session.get_global_session() - self.max_iterations = max_iterations - self.connection_name = connection_name - - self._bqml_model_factory = globals.bqml_model_factory() - self._bqml_model: core.BqmlModel = self._create_bqml_model() - - def _create_bqml_model(self): - # Parse and create connection if needed. - self.connection_name = self.session._create_bq_connection( - connection=self.connection_name, iam_role="aiplatform.user" + self._bq_connection_manager.create_bq_connection( + project_id=connection_name_parts[0], + location=connection_name_parts[1], + connection_id=connection_name_parts[2], + iam_role="aiplatform.user", ) - - if self.model_name not in _GEMINI_ENDPOINTS: - msg = exceptions.format_message( - _MODEL_NOT_SUPPORTED_WARNING.format( - model_name=self.model_name, - known_models=", ".join(_GEMINI_ENDPOINTS), - ) - ) - warnings.warn(msg) - if self.model_name.startswith("gemini-1.5"): - msg = exceptions.format_message( - _MODEL_DEPRECATE_WARNING.format( - model_name=self.model_name, - new_model_name="gemini-2.5-X", - link="https://cloud.google.com/python/docs/reference/bigframes/latest/bigframes.ml.llm.GeminiTextGenerator", - ) + if self.model_name == _REMOTE_TEXT_GENERATOR_MODEL_ENDPOINT: + options = { + "endpoint": _REMOTE_TEXT_GENERATOR_MODEL_ENDPOINT, + } + elif self.model_name == _REMOTE_TEXT_GENERATOR_32K_MODEL_ENDPOINT: + options = { + "endpoint": _REMOTE_TEXT_GENERATOR_32K_MODEL_ENDPOINT, + } + else: + raise ValueError( + f"Model name {self.model_name} is not supported. We only support {_REMOTE_TEXT_GENERATOR_MODEL_ENDPOINT} and {_REMOTE_TEXT_GENERATOR_32K_MODEL_ENDPOINT}." ) - warnings.warn(msg) - - options = {"endpoint": self.model_name} - return self._bqml_model_factory.create_remote_model( session=self.session, connection_name=self.connection_name, options=options ) - @classmethod - def _from_bq( - cls, session: bigframes.Session, bq_model: bigquery.Model - ) -> GeminiTextGenerator: - assert bq_model.model_type == "MODEL_TYPE_UNSPECIFIED" - assert "remoteModelInfo" in bq_model._properties - assert "endpoint" in bq_model._properties["remoteModelInfo"] - assert "connection" in bq_model._properties["remoteModelInfo"] - - # Parse the remote model endpoint - bqml_endpoint = bq_model._properties["remoteModelInfo"]["endpoint"] - model_connection = bq_model._properties["remoteModelInfo"]["connection"] - model_endpoint = bqml_endpoint.split("/")[-1] - - model = cls( - model_name=model_endpoint, session=session, connection_name=model_connection - ) - model._bqml_model = core.BqmlModel(session, bq_model) - return model - - @property - def _bqml_options(self) -> dict: - """The model options as they will be set for BQML""" - options = { - "max_iterations": self.max_iterations, - "data_split_method": "NO_SPLIT", - } - return options - - def fit( - self, - X: utils.ArrayType, - y: utils.ArrayType, - ) -> GeminiTextGenerator: - """Fine tune GeminiTextGenerator model. Only support "gemini-1.5-pro-002", - "gemini-1.5-flash-002", "gemini-2.0-flash-001", - and "gemini-2.0-flash-lite-001"models for now. - - .. 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: - X (bigframes.dataframe.DataFrame or bigframes.series.Series): - DataFrame of shape (n_samples, n_features). Training data. - y (bigframes.dataframe.DataFrame or bigframes.series.Series: - Training labels. - - Returns: - GeminiTextGenerator: Fitted estimator. - """ - if self.model_name not in _GEMINI_FINE_TUNE_SCORE_ENDPOINTS: - msg = exceptions.format_message( - "fit() only supports gemini-1.5-pro-002, gemini-1.5-flash-002, gemini-2.0-flash-001, or gemini-2.0-flash-lite-001 model." - ) - warnings.warn(msg) - - X, y = utils.batch_convert_to_dataframe(X, y) - - options = self._bqml_options - options["endpoint"] = self.model_name - options["prompt_col"] = X.columns.tolist()[0] - - self._bqml_model = self._bqml_model_factory.create_llm_remote_model( - X, - y, - options=options, - connection_name=typing.cast(str, self.connection_name), - ) - return self - def predict( self, - X: utils.ArrayType, - *, - temperature: float = 0.9, - max_output_tokens: int = 8192, + X: Union[bpd.DataFrame, bpd.Series], + temperature: float = 0.0, + max_output_tokens: int = 128, top_k: int = 40, - top_p: float = 1.0, - ground_with_google_search: bool = False, - max_retries: int = 0, - prompt: Optional[Iterable[Union[str, bigframes.series.Series]]] = None, - output_schema: Optional[Mapping[str, str]] = None, - ) -> bigframes.dataframe.DataFrame: + top_p: float = 0.95, + ) -> bpd.DataFrame: """Predict the result from input DataFrame. Args: - X (bigframes.dataframe.DataFrame or bigframes.series.Series or pandas.core.frame.DataFrame or pandas.core.series.Series): - Input DataFrame or Series, can contain one or more columns. If multiple columns are in the DataFrame, the "prompt" column, or created by "prompt" parameter, is used for prediction. + X (bigframes.dataframe.DataFrame or bigframes.series.Series): + Input DataFrame or Series, which needs to contain a column with name "prompt". Only the column will be used as input. Prompts can include preamble, questions, suggestions, instructions, or examples. - temperature (float, default 0.9): - The temperature is used for sampling during the response generation, which occurs when topP and topK are applied. Temperature controls the degree of randomness in token selection. Lower temperatures are good for prompts that require a more deterministic and less open-ended or creative response, while higher temperatures can lead to more diverse or creative results. A temperature of 0 is deterministic: the highest probability response is always selected. - Default 0.9. Possible values [0.0, 1.0]. + temperature (float, default 0.0): + The temperature is used for sampling during the response generation, which occurs when topP and topK are applied. + Temperature controls the degree of randomness in token selection. Lower temperatures are good for prompts that expect a true or correct response, + while higher temperatures can lead to more diverse or unexpected results. A temperature of 0 is deterministic: + the highest probability token is always selected. For most use cases, try starting with a temperature of 0.2. + Default 0. Possible values [0.0, 1.0]. - max_output_tokens (int, default 8192): - Maximum number of tokens that can be generated in the response. A token is approximately four characters. 100 tokens correspond to roughly 60-80 words. - Specify a lower value for shorter responses and a higher value for potentially longer responses. - Default 8192. Possible values are in the range [1, 8192]. + max_output_tokens (int, default 128): + Maximum number of tokens that can be generated in the response. Specify a lower value for shorter responses and a higher value for longer responses. + A token may be smaller than a word. A token is approximately four characters. 100 tokens correspond to roughly 60-80 words. + Default 128. Possible values [1, 1024]. top_k (int, default 40): - Top-K changes how the model selects tokens for output. A top-K of 1 means the next selected token is the most probable among all tokens in the model's vocabulary (also called greedy decoding), while a top-K of 3 means that the next token is selected from among the three most probable tokens by using temperature. - For each token selection step, the top-K tokens with the highest probabilities are sampled. Then tokens are further filtered based on top-P with the final token selected using temperature sampling. + Top-k changes how the model selects tokens for output. A top-k of 1 means the selected token is the most probable among all tokens + in the model's vocabulary (also called greedy decoding), while a top-k of 3 means that the next token is selected from among the 3 most probable tokens (using temperature). + For each token selection step, the top K tokens with the highest probabilities are sampled. Then tokens are further filtered based on topP with the final token selected using temperature sampling. Specify a lower value for less random responses and a higher value for more random responses. Default 40. Possible values [1, 40]. - top_p (float, default 0.95): - Top-P changes how the model selects tokens for output. Tokens are selected from the most (see top-K) to least probable until the sum of their probabilities equals the top-P value. For example, if tokens A, B, and C have a probability of 0.3, 0.2, and 0.1 and the top-P value is 0.5, then the model will select either A or B as the next token by using temperature and excludes C as a candidate. + top_p (float, default 0.95):: + Top-p changes how the model selects tokens for output. Tokens are selected from most K (see topK parameter) probable to least until the sum of their probabilities equals the top-p value. + For example, if tokens A, B, and C have a probability of 0.3, 0.2, and 0.1 and the top-p value is 0.5, then the model will select either A or B as the next token (using temperature) + and not consider C at all. Specify a lower value for less random responses and a higher value for more random responses. - Default 1.0. Possible values [0.0, 1.0]. - - ground_with_google_search (bool, default False): - Enables Grounding with Google Search for the Vertex AI model. When set - to True, the model incorporates relevant information from Google Search - results into its responses, enhancing their accuracy and factualness. - This feature provides an additional column, `ml_generate_text_grounding_result`, - in the response output, detailing the sources used for grounding. - Note: Using this feature may impact billing costs. Refer to the pricing - page for details: https://cloud.google.com/vertex-ai/generative-ai/pricing#google_models - The default is `False`. - - max_retries (int, default 0): - Max number of retries if the prediction for any rows failed. Each try needs to make progress (i.e. has successfully predicted rows) to continue the retry. - Each retry will append newly succeeded rows. When the max retries are reached, the remaining rows (the ones without successful predictions) will be appended to the end of the result. + Default 0.95. Possible values [0.0, 1.0]. - prompt (Iterable of str or bigframes.series.Series, or None, default None): - .. note:: - BigFrames ObjectRef 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). - Construct a prompt struct column for prediction based on the input. The input must be an Iterable that can take string literals, - such as "summarize", string column(s) of X, such as X["str_col"], or `ObjectRef column(s) `_ of X, such as X["objectref_col"]. - It creates a struct column of the items of the iterable, and use the concatenated result as the input prompt. No-op if set to None. - output_schema (Mapping[str, str] or None, default None): - The schema used to generate structured output as a bigframes DataFrame. The schema is a string key-value pair of :. - Supported types are int64, float64, bool, string, array and struct. If None, output text result. Returns: - bigframes.dataframe.DataFrame: DataFrame of shape (n_samples, n_input_columns + n_prediction_columns). Returns predicted values. - """ + bigframes.dataframe.DataFrame: Output DataFrame with only 1 column as the output text results.""" # Params reference: https://cloud.google.com/vertex-ai/docs/generative-ai/learn/models if temperature < 0.0 or temperature > 1.0: raise ValueError(f"temperature must be [0.0, 1.0], but is {temperature}.") - - if max_output_tokens not in range(1, 8193): + if max_output_tokens not in range(1, 1025): raise ValueError( - f"max_output_token must be [1, 8192] for Gemini model, but is {max_output_tokens}." + f"max_output_token must be [1, 1024], but is {max_output_tokens}." ) - if top_k not in range(1, 41): raise ValueError(f"top_k must be [1, 40], but is {top_k}.") - if top_p < 0.0 or top_p > 1.0: raise ValueError(f"top_p must be [0.0, 1.0], but is {top_p}.") - if max_retries < 0: - raise ValueError( - f"max_retries must be larger than or equal to 0, but is {max_retries}." - ) - - session = self._bqml_model.session - (X,) = utils.batch_convert_to_dataframe(X, session=session) - - if prompt: - if self.model_name not in _GEMINI_MULTIMODAL_ENDPOINTS: - msg = exceptions.format_message( - _GEMINI_MULTIMODAL_MODEL_NOT_SUPPORTED_WARNING.format( - model_name=self.model_name, - known_models=", ".join(_GEMINI_MULTIMODAL_ENDPOINTS), - ) - ) - warnings.warn(msg) + (X,) = utils.convert_to_dataframe(X) - df_prompt = X[[X.columns[0]]].rename( - columns={X.columns[0]: "bigframes_placeholder_col"} + if len(X.columns) != 1: + raise ValueError( + f"Only support one column as input. {constants.FEEDBACK_LINK}" ) - for i, item in enumerate(prompt): - # must be distinct str column labels to construct a struct - if isinstance(item, str): - label = f"input_{i}" - else: # Series - label = f"input_{i}_{item.name}" - - # TODO(garrettwu): remove transform to ObjRefRuntime when BQML supports ObjRef as input - if ( - isinstance(item, bigframes.series.Series) - and item.dtype == dtypes.OBJ_REF_DTYPE - ): - item = bbq.obj.get_access_url(item, mode="r") - - df_prompt[label] = item - df_prompt = df_prompt.drop(columns="bigframes_placeholder_col") - X["prompt"] = bbq.struct(df_prompt) - if len(X.columns) == 1: - # BQML identified the column by name - col_label = typing.cast(blocks.Label, X.columns[0]) - X = X.rename(columns={col_label: "prompt"}) + # BQML identified the column by name + col_label = cast(blocks.Label, X.columns[0]) + X = X.rename(columns={col_label: "prompt"}) - options: dict = { + options = { "temperature": temperature, "max_output_tokens": max_output_tokens, - # "top_k": top_k, # TODO(garrettwu): the option is deprecated in Gemini 1.5 forward. + "top_k": top_k, "top_p": top_p, - "ground_with_google_search": ground_with_google_search, + "flatten_json_output": True, } - if output_schema: - output_schema = { - k: utils.standardize_type(v) for k, v in output_schema.items() - } - options["output_schema"] = output_schema - return self._predict_and_retry( - core.BqmlModel.generate_table_tvf, - X, - options=options, - max_retries=max_retries, - ) - - return self._predict_and_retry( - core.BqmlModel.generate_text_tvf, - X, - options=options, - max_retries=max_retries, + df = self._bqml_model.generate_text(X, options) + return cast( + bpd.DataFrame, + df[[_TEXT_GENERATE_RESULT_COLUMN]], ) - def score( - self, - X: utils.ArrayType, - y: utils.ArrayType, - task_type: Literal[ - "text_generation", "classification", "summarization", "question_answering" - ] = "text_generation", - ) -> bigframes.dataframe.DataFrame: - """Calculate evaluation metrics of the model. Only support - "gemini-1.5-pro-002", "gemini-1.5-flash-002", - "gemini-2.0-flash-lite-001", and "gemini-2.0-flash-001". - - .. 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). - - .. note:: - - Output matches that of the BigQuery ML.EVALUATE function. - See: https://cloud.google.com/bigquery/docs/reference/standard-sql/bigqueryml-syntax-evaluate#remote-model-llm - for the outputs relevant to this model type. - - Args: - X (bigframes.dataframe.DataFrame or bigframes.series.Series or pandas.core.frame.DataFrame or pandas.core.series.Series): - A BigQuery DataFrame as evaluation data, which contains only one column of input_text - that contains the prompt text to use when evaluating the model. - y (bigframes.dataframe.DataFrame or bigframes.series.Series or pandas.core.frame.DataFrame or pandas.core.series.Series): - A BigQuery DataFrame as evaluation labels, which contains only one column of output_text - that you would expect to be returned by the model. - task_type (str): - The type of the task for LLM model. Default to "text_generation". - Possible values: "text_generation", "classification", "summarization", and "question_answering". - - Returns: - bigframes.dataframe.DataFrame: The DataFrame as evaluation result. - """ - if not self._bqml_model: - raise RuntimeError("A model must be fitted before score") - - if self.model_name not in _GEMINI_FINE_TUNE_SCORE_ENDPOINTS: - msg = exceptions.format_message( - "score() only supports gemini-1.5-pro-002, gemini-1.5-flash-2, gemini-2.0-flash-001, and gemini-2.0-flash-lite-001 model." - ) - warnings.warn(msg) - - X, y = utils.batch_convert_to_dataframe(X, y, session=self._bqml_model.session) - - if len(X.columns) != 1 or len(y.columns) != 1: - raise ValueError( - f"Only support one column as input for X and y. {constants.FEEDBACK_LINK}" - ) - - # BQML identified the column by name - X_col_label = typing.cast(blocks.Label, X.columns[0]) - y_col_label = typing.cast(blocks.Label, y.columns[0]) - X = X.rename(columns={X_col_label: "input_text"}) - y = y.rename(columns={y_col_label: "output_text"}) - - input_data = X.join(y, how="outer") - - return self._bqml_model.llm_evaluate(input_data, task_type) - - def to_gbq(self, model_name: str, replace: bool = False) -> GeminiTextGenerator: - """Save the model to BigQuery. - - Args: - model_name (str): - The name of the model. - replace (bool, default False): - Determine whether to replace if the model already exists. Default to False. - - Returns: - GeminiTextGenerator: Saved model.""" - - new_model = self._bqml_model.copy(model_name, replace) - return new_model.session.read_gbq_model(model_name) - - -@log_adapter.class_logger -class Claude3TextGenerator(base.RetriableRemotePredictor): - """Claude3 text generator LLM model. - - Go to Google Cloud Console -> Vertex AI -> Model Garden page to enable the models before use. Must have the Consumer Procurement Entitlement Manager Identity and Access Management (IAM) role to enable the models. - https://cloud.google.com/vertex-ai/generative-ai/docs/partner-models/use-partner-models#grant-permissions - .. 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). - - - .. note:: - - The models only available in specific regions. Check https://cloud.google.com/vertex-ai/generative-ai/docs/partner-models/use-claude#regions for details. - - .. note:: - - claude-3-sonnet model is deprecated. Use other models instead. +class PaLM2TextEmbeddingGenerator(base.Predictor): + """PaLM2 text embedding generator LLM model. Args: - model_name (str, Default to "claude-3-sonnet"): - The model for natural language tasks. Possible values are "claude-3-sonnet", "claude-3-haiku", "claude-3-5-sonnet" and "claude-3-opus". - "claude-3-sonnet" (deprecated) is Anthropic's dependable combination of skills and speed. It is engineered to be dependable for scaled AI deployments across a variety of use cases. - "claude-3-haiku" is Anthropic's fastest, most compact vision and text model for near-instant responses to simple queries, meant for seamless AI experiences mimicking human interactions. - "claude-3-5-sonnet" (deprecated) is Anthropic's most powerful AI model and maintains the speed and cost of Claude 3 Sonnet, which is a mid-tier model. - "claude-3-opus" (deprecated) is Anthropic's second-most powerful AI model, with strong performance on highly complex tasks. - https://cloud.google.com/vertex-ai/generative-ai/docs/partner-models/use-claude#available-claude-models - If no setting is provided, "claude-3-sonnet" will be used by default - and a warning will be issued. + model_name (str, Default to "textembedding-gecko"): + The model for text embedding. “textembedding-gecko” returns model embeddings for text inputs. + "textembedding-gecko-multilingual" returns model embeddings for text inputs which support over 100 languages + Default to "textembedding-gecko". session (bigframes.Session or None): BQ session to create the model. If None, use the global default session. connection_name (str or None): - Connection to connect with remote service. str of the format ... - If None, use default connection in session context. BigQuery DataFrame will try to create the connection and attach - permission if the connection isn't fully set up. + connection to connect with remote service. str of the format ... + if None, use default connection in session context. """ def __init__( self, - *, - model_name: Optional[ - Literal[ - "claude-3-sonnet", - "claude-3-haiku", - "claude-3-5-sonnet", - "claude-3-opus", - ] - ] = None, + model_name: Literal[ + "textembedding-gecko", "textembedding-gecko-multilingual" + ] = "textembedding-gecko", session: Optional[bigframes.Session] = None, connection_name: Optional[str] = None, ): - if model_name is None: - model_name = "claude-3-sonnet" - msg = exceptions.format_message(_REMOVE_DEFAULT_MODEL_WARNING) - warnings.warn(msg, category=FutureWarning, stacklevel=2) self.model_name = model_name - self.session = session or global_session.get_global_session() - self.connection_name = connection_name + self.session = session or bpd.get_global_session() + self._bq_connection_manager = clients.BqConnectionManager( + self.session.bqconnectionclient, self.session.resourcemanagerclient + ) + + connection_name = connection_name or self.session._bq_connection + self.connection_name = self._bq_connection_manager.resolve_full_connection_name( + connection_name, + default_project=self.session._project, + default_location=self.session._location, + ) self._bqml_model_factory = globals.bqml_model_factory() self._bqml_model: core.BqmlModel = self._create_bqml_model() def _create_bqml_model(self): # Parse and create connection if needed. - self.connection_name = self.session._create_bq_connection( - connection=self.connection_name, iam_role="aiplatform.user" + if not self.connection_name: + raise ValueError( + "Must provide connection_name, either in constructor or through session options." + ) + connection_name_parts = self.connection_name.split(".") + if len(connection_name_parts) != 3: + raise ValueError( + f"connection_name must be of the format .., got {self.connection_name}." + ) + self._bq_connection_manager.create_bq_connection( + project_id=connection_name_parts[0], + location=connection_name_parts[1], + connection_id=connection_name_parts[2], + iam_role="aiplatform.user", ) - - if self.model_name not in _CLAUDE_3_ENDPOINTS: - msg = exceptions.format_message( - _MODEL_NOT_SUPPORTED_WARNING.format( - model_name=self.model_name, - known_models=", ".join(_CLAUDE_3_ENDPOINTS), - ) + if self.model_name == "textembedding-gecko": + options = { + "endpoint": _REMOTE_EMBEDDING_GENERATOR_MODEL_ENDPOINT, + } + elif self.model_name == _REMOTE_EMBEDDING_GENERATOR_MUlTILINGUAL_MODEL_ENDPOINT: + options = { + "endpoint": _REMOTE_EMBEDDING_GENERATOR_MUlTILINGUAL_MODEL_ENDPOINT, + } + else: + raise ValueError( + f"Model name {self.model_name} is not supported. We only support {_REMOTE_EMBEDDING_GENERATOR_MODEL_ENDPOINT} and {_REMOTE_EMBEDDING_GENERATOR_MUlTILINGUAL_MODEL_ENDPOINT}." ) - warnings.warn(msg) - options = { - "endpoint": self.model_name, - } return self._bqml_model_factory.create_remote_model( session=self.session, connection_name=self.connection_name, options=options ) - @classmethod - def _from_bq( - cls, session: bigframes.Session, bq_model: bigquery.Model - ) -> Claude3TextGenerator: - assert bq_model.model_type == "MODEL_TYPE_UNSPECIFIED" - assert "remoteModelInfo" in bq_model._properties - assert "endpoint" in bq_model._properties["remoteModelInfo"] - assert "connection" in bq_model._properties["remoteModelInfo"] - - # Parse the remote model endpoint - bqml_endpoint = bq_model._properties["remoteModelInfo"]["endpoint"] - model_connection = bq_model._properties["remoteModelInfo"]["connection"] - model_endpoint = bqml_endpoint.split("/")[-1] - - kwargs = utils.retrieve_params_from_bq_model( - cls, bq_model, _BQML_PARAMS_MAPPING - ) - - model = cls( - **kwargs, - session=session, - model_name=model_endpoint, - connection_name=model_connection, - ) - model._bqml_model = core.BqmlModel(session, bq_model) - return model - - @property - def _bqml_options(self) -> dict: - """The model options as they will be set for BQML""" - options = { - "data_split_method": "NO_SPLIT", - } - return options - - def predict( - self, - X: utils.ArrayType, - *, - max_output_tokens: int = 128, - top_k: int = 40, - top_p: float = 0.95, - max_retries: int = 0, - ) -> bigframes.dataframe.DataFrame: + def predict(self, X: Union[bpd.DataFrame, bpd.Series]) -> bpd.DataFrame: """Predict the result from input DataFrame. Args: - X (bigframes.dataframe.DataFrame or bigframes.series.Series or pandas.core.frame.DataFrame or pandas.core.series.Series): - Input DataFrame or Series, can contain one or more columns. If multiple columns are in the DataFrame, it must contain a "prompt" column for prediction. - Prompts can include preamble, questions, suggestions, instructions, or examples. - - max_output_tokens (int, default 128): - Maximum number of tokens that can be generated in the response. Specify a lower value for shorter responses and a higher value for longer responses. - A token may be smaller than a word. A token is approximately four characters. 100 tokens correspond to roughly 60-80 words. - Default 128. Possible values are in the range [1, 4096]. - - top_k (int, default 40): - Top-k changes how the model selects tokens for output. A top-k of 1 means the selected token is the most probable among all tokens - in the model's vocabulary (also called greedy decoding), while a top-k of 3 means that the next token is selected from among the 3 most probable tokens (using temperature). - For each token selection step, the top K tokens with the highest probabilities are sampled. Then tokens are further filtered based on topP with the final token selected using temperature sampling. - Specify a lower value for less random responses and a higher value for more random responses. - Default 40. Possible values [1, 40]. - - top_p (float, default 0.95):: - Top-p changes how the model selects tokens for output. Tokens are selected from most K (see topK parameter) probable to least until the sum of their probabilities equals the top-p value. - For example, if tokens A, B, and C have a probability of 0.3, 0.2, and 0.1 and the top-p value is 0.5, then the model will select either A or B as the next token (using temperature) - and not consider C at all. - Specify a lower value for less random responses and a higher value for more random responses. - Default 0.95. Possible values [0.0, 1.0]. - - max_retries (int, default 0): - Max number of retries if the prediction for any rows failed. Each try needs to make progress (i.e. has successfully predicted rows) to continue the retry. - Each retry will append newly succeeded rows. When the max retries are reached, the remaining rows (the ones without successful predictions) will be appended to the end of the result. - + X (bigframes.dataframe.DataFrame or bigframes.series.Series): + Input DataFrame, which needs to contain a column with name "content". Only the column will be used as input. Content can include preamble, questions, suggestions, instructions, or examples. Returns: - bigframes.dataframe.DataFrame: DataFrame of shape (n_samples, n_input_columns + n_prediction_columns). Returns predicted values. + bigframes.dataframe.DataFrame: Output DataFrame with only 1 column as the output embedding results """ # Params reference: https://cloud.google.com/vertex-ai/docs/generative-ai/learn/models - if max_output_tokens not in range(1, 4097): - raise ValueError( - f"max_output_token must be [1, 4096], but is {max_output_tokens}." - ) - - if top_k not in range(1, 41): - raise ValueError(f"top_k must be [1, 40], but is {top_k}.") - - if top_p < 0.0 or top_p > 1.0: - raise ValueError(f"top_p must be [0.0, 1.0], but is {top_p}.") + (X,) = utils.convert_to_dataframe(X) - if max_retries < 0: + if len(X.columns) != 1: raise ValueError( - f"max_retries must be larger than or equal to 0, but is {max_retries}." + f"Only support one column as input. {constants.FEEDBACK_LINK}" ) - (X,) = utils.batch_convert_to_dataframe(X, session=self._bqml_model.session) - - if len(X.columns) == 1: - # BQML identified the column by name - col_label = typing.cast(blocks.Label, X.columns[0]) - X = X.rename(columns={col_label: "prompt"}) + # BQML identified the column by name + col_label = cast(blocks.Label, X.columns[0]) + X = X.rename(columns={col_label: "content"}) options = { - "max_output_tokens": max_output_tokens, - "top_k": top_k, - "top_p": top_p, + "flatten_json_output": True, } - - return self._predict_and_retry( - core.BqmlModel.generate_text_tvf, - X, - options=options, - max_retries=max_retries, + df = self._bqml_model.generate_text_embedding(X, options) + return cast( + bpd.DataFrame, + df[[_EMBED_TEXT_RESULT_COLUMN]], ) - - def to_gbq(self, model_name: str, replace: bool = False) -> Claude3TextGenerator: - """Save the model to BigQuery. - - Args: - model_name (str): - The name of the model. - replace (bool, default False): - Determine whether to replace if the model already exists. Default to False. - - Returns: - Claude3TextGenerator: Saved model.""" - - new_model = self._bqml_model.copy(model_name, replace) - return new_model.session.read_gbq_model(model_name) diff --git a/bigframes/ml/loader.py b/bigframes/ml/loader.py index 76975752457..805747c49ba 100644 --- a/bigframes/ml/loader.py +++ b/bigframes/ml/loader.py @@ -17,24 +17,18 @@ from types import MappingProxyType from typing import Union -import bigframes_vendored.constants as constants from google.cloud import bigquery -import bigframes.session +import bigframes +import bigframes.constants as constants from bigframes.ml import ( cluster, - compose, - core, decomposition, ensemble, forecasting, imported, - impute, linear_model, - llm, pipeline, - preprocessing, - utils, ) _BQML_MODEL_TYPE_MAPPING = MappingProxyType( @@ -42,7 +36,6 @@ "LINEAR_REGRESSION": linear_model.LinearRegression, "LOGISTIC_REGRESSION": linear_model.LogisticRegression, "KMEANS": cluster.KMeans, - "MATRIX_FACTORIZATION": decomposition.MatrixFactorization, "PCA": decomposition.PCA, "BOOSTED_TREE_REGRESSOR": ensemble.XGBRegressor, "BOOSTED_TREE_CLASSIFIER": ensemble.XGBClassifier, @@ -51,43 +44,13 @@ "RANDOM_FOREST_CLASSIFIER": ensemble.RandomForestClassifier, "TENSORFLOW": imported.TensorFlowModel, "ONNX": imported.ONNXModel, - "XGBOOST": imported.XGBoostModel, - } -) - -_BQML_ENDPOINT_TYPE_MAPPING = MappingProxyType( - { - llm._GEMINI_1P5_PRO_PREVIEW_ENDPOINT: llm.GeminiTextGenerator, - llm._GEMINI_1P5_PRO_FLASH_PREVIEW_ENDPOINT: llm.GeminiTextGenerator, - llm._GEMINI_1P5_PRO_001_ENDPOINT: llm.GeminiTextGenerator, - llm._GEMINI_1P5_PRO_002_ENDPOINT: llm.GeminiTextGenerator, - llm._GEMINI_1P5_FLASH_001_ENDPOINT: llm.GeminiTextGenerator, - llm._GEMINI_1P5_FLASH_002_ENDPOINT: llm.GeminiTextGenerator, - llm._GEMINI_2_FLASH_EXP_ENDPOINT: llm.GeminiTextGenerator, - llm._GEMINI_2_FLASH_001_ENDPOINT: llm.GeminiTextGenerator, - llm._GEMINI_2_FLASH_LITE_001_ENDPOINT: llm.GeminiTextGenerator, - llm._GEMINI_2P5_PRO_PREVIEW_ENDPOINT: llm.GeminiTextGenerator, - llm._GEMINI_2P5_FLASH_ENDPOINT: llm.GeminiTextGenerator, - llm._GEMINI_2P5_FLASH_LITE_ENDPOINT: llm.GeminiTextGenerator, - llm._GEMINI_2P5_PRO_ENDPOINT: llm.GeminiTextGenerator, - llm._GEMINI_3P1_FLASH_LITE_ENDPOINT: llm.GeminiTextGenerator, - llm._GEMINI_3P5_FLASH_ENDPOINT: llm.GeminiTextGenerator, - llm._CLAUDE_3_HAIKU_ENDPOINT: llm.Claude3TextGenerator, - llm._CLAUDE_3_SONNET_ENDPOINT: llm.Claude3TextGenerator, - llm._CLAUDE_3_5_SONNET_ENDPOINT: llm.Claude3TextGenerator, - llm._CLAUDE_3_OPUS_ENDPOINT: llm.Claude3TextGenerator, - llm._TEXT_EMBEDDING_005_ENDPOINT: llm.TextEmbeddingGenerator, - llm._TEXT_EMBEDDING_004_ENDPOINT: llm.TextEmbeddingGenerator, - llm._TEXT_MULTILINGUAL_EMBEDDING_002_ENDPOINT: llm.TextEmbeddingGenerator, - llm._MULTIMODAL_EMBEDDING_001_ENDPOINT: llm.MultimodalEmbeddingGenerator, } ) def from_bq( - session: bigframes.session.Session, bq_model: bigquery.Model + session: bigframes.Session, bq_model: bigquery.Model ) -> Union[ - decomposition.MatrixFactorization, decomposition.PCA, cluster.KMeans, linear_model.LinearRegression, @@ -99,14 +62,7 @@ def from_bq( ensemble.RandomForestClassifier, imported.TensorFlowModel, imported.ONNXModel, - imported.XGBoostModel, - llm.Claude3TextGenerator, - llm.TextEmbeddingGenerator, - llm.MultimodalEmbeddingGenerator, pipeline.Pipeline, - compose.ColumnTransformer, - preprocessing.PreprocessingType, - impute.SimpleImputer, ]: """Load a BQML model to BigQuery DataFrames ML. @@ -117,37 +73,16 @@ def from_bq( Returns: A BigQuery DataFrames ML model object. """ - if bq_model.model_type == "TRANSFORM_ONLY": - return _transformer_from_bq(session, bq_model) - if _is_bq_model_pipeline(bq_model): return pipeline.Pipeline._from_bq(session, bq_model) return _model_from_bq(session, bq_model) -def _transformer_from_bq(session: bigframes.session.Session, bq_model: bigquery.Model): - transformer = compose.ColumnTransformer._extract_from_bq_model(bq_model)._merge( - bq_model - ) - transformer._bqml_model = core.BqmlModel(session, bq_model) - - return transformer - - -def _model_from_bq(session: bigframes.session.Session, bq_model: bigquery.Model): +def _model_from_bq(session: bigframes.Session, bq_model: bigquery.Model): if bq_model.model_type in _BQML_MODEL_TYPE_MAPPING: return _BQML_MODEL_TYPE_MAPPING[bq_model.model_type]._from_bq( # type: ignore - session=session, bq_model=bq_model - ) - if _is_bq_model_remote(bq_model): - # Parse the remote model endpoint - bqml_endpoint = bq_model._properties["remoteModelInfo"]["endpoint"] - model_endpoint = bqml_endpoint.split("/")[-1] - model_name, _ = utils.parse_model_endpoint(model_endpoint) - - return _BQML_ENDPOINT_TYPE_MAPPING[model_name]._from_bq( # type: ignore - session=session, bq_model=bq_model + session=session, model=bq_model ) raise NotImplementedError( @@ -157,11 +92,3 @@ def _model_from_bq(session: bigframes.session.Session, bq_model: bigquery.Model) def _is_bq_model_pipeline(bq_model: bigquery.Model) -> bool: return "transformColumns" in bq_model._properties - - -def _is_bq_model_remote(bq_model: bigquery.Model) -> bool: - return ( - bq_model.model_type == "MODEL_TYPE_UNSPECIFIED" - and "remoteModelInfo" in bq_model._properties - and "endpoint" in bq_model._properties["remoteModelInfo"] - ) diff --git a/bigframes/ml/metrics.py b/bigframes/ml/metrics.py new file mode 100644 index 00000000000..5731b946ca9 --- /dev/null +++ b/bigframes/ml/metrics.py @@ -0,0 +1,331 @@ +# Copyright 2023 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. + +"""Metrics functions for evaluating models. This module is styled after +Scikit-Learn's metrics module: https://scikit-learn.org/stable/modules/metrics.html.""" + +import inspect +import typing +from typing import Tuple, Union + +import numpy as np +import pandas as pd +import sklearn.metrics as sklearn_metrics # type: ignore + +import bigframes.constants as constants +from bigframes.ml import utils +import bigframes.pandas as bpd +import third_party.bigframes_vendored.sklearn.metrics._classification as vendored_mertics_classification +import third_party.bigframes_vendored.sklearn.metrics._ranking as vendored_mertics_ranking +import third_party.bigframes_vendored.sklearn.metrics._regression as vendored_metrics_regression + + +def r2_score( + y_true: Union[bpd.DataFrame, bpd.Series], + y_pred: Union[bpd.DataFrame, bpd.Series], + force_finite=True, +) -> float: + y_true_series, y_pred_series = utils.convert_to_series(y_true, y_pred) + + # total sum of squares + # (dataframe, scalar) binops + # TODO(tbergeron): These stats are eagerly evaluated. Move to lazy representation once scalar subqueries supported. + delta_from_mean = y_true_series - y_true_series.mean() + ss_total = (delta_from_mean * delta_from_mean).sum() + + # residual sum of squares + # (scalar, scalar) binops + delta_from_pred = y_true_series - y_pred_series + ss_res = (delta_from_pred * delta_from_pred).sum() + + if force_finite and ss_total == 0: + return 0.0 if ss_res > 0 else 1.0 + + return 1 - (ss_res / ss_total) + + +r2_score.__doc__ = inspect.getdoc(vendored_metrics_regression.r2_score) + + +def accuracy_score( + y_true: Union[bpd.DataFrame, bpd.Series], + y_pred: Union[bpd.DataFrame, bpd.Series], + normalize=True, +) -> float: + # TODO(ashleyxu): support sample_weight as the parameter + y_true_series, y_pred_series = utils.convert_to_series(y_true, y_pred) + + # Compute accuracy for each possible representation + # TODO(ashleyxu): add multilabel classification support where y_type + # starts with "multilabel" + score = (y_true_series == y_pred_series).astype(pd.Int64Dtype()) + + if normalize: + return score.mean() + else: + return score.sum() + + +accuracy_score.__doc__ = inspect.getdoc(vendored_mertics_classification.accuracy_score) + + +def roc_curve( + y_true: Union[bpd.DataFrame, bpd.Series], + y_score: Union[bpd.DataFrame, bpd.Series], + drop_intermediate: bool = True, +) -> Tuple[bpd.Series, bpd.Series, bpd.Series]: + # TODO(bmil): Add multi-class support + # TODO(bmil): Add multi-label support + + # TODO(bmil): Implement drop_intermediate + if drop_intermediate: + raise NotImplementedError( + f"drop_intermediate is not yet implemented. {constants.FEEDBACK_LINK}" + ) + + y_true_series, y_score_series = utils.convert_to_series(y_true, y_score) + + session = y_true_series._block.expr.session + + # We operate on rows, so, remove the index if there is one + # TODO(bmil): check that the indexes are equivalent before removing + + y_true_series = typing.cast(bpd.Series, y_true_series.reset_index(drop=True)) + y_score_series = typing.cast(bpd.Series, y_score_series.reset_index(drop=True)) + + df = bpd.DataFrame( + { + "y_true": y_true_series, + "y_score": y_score_series, + } + ) + + total_positives = y_true_series.sum() + total_negatives = y_true_series.count() - total_positives + + df = df.sort_values(by="y_score", ascending=False) + df["cum_tp"] = df["y_true"].cumsum() + # have to astype("Int64") as not supported boolean cumsum yet. + df["cum_fp"] = ( + (~typing.cast(bpd.Series, df["y_true"].astype("boolean"))) + .astype("Int64") + .cumsum() + ) + + # produce just one data point per y_score + df = df.drop_duplicates(subset="y_score", keep="last") + df = df.sort_values(by="y_score", ascending=False) + + df["tpr"] = typing.cast(bpd.Series, df["cum_tp"]) / total_positives + df["fpr"] = typing.cast(bpd.Series, df["cum_fp"]) / total_negatives + df["thresholds"] = typing.cast(bpd.Series, df["y_score"].astype("Float64")) + + # sklearn includes an extra datapoint for the origin with threshold np.inf + # having problems with concating inline + df_origin = session.read_pandas( + pd.DataFrame({"tpr": [0.0], "fpr": [0.0], "thresholds": np.inf}) + ) + df = typing.cast(bpd.DataFrame, bpd.concat([df_origin, df], ignore_index=True)) + df = df.reset_index(drop=True) + + return ( + typing.cast(bpd.Series, df["fpr"]), + typing.cast(bpd.Series, df["tpr"]), + typing.cast(bpd.Series, df["thresholds"]), + ) + + +roc_curve.__doc__ = inspect.getdoc(vendored_mertics_ranking.roc_curve) + + +def roc_auc_score( + y_true: Union[bpd.DataFrame, bpd.Series], y_score: Union[bpd.DataFrame, bpd.Series] +) -> float: + # TODO(bmil): Add multi-class support + # TODO(bmil): Add multi-label support + y_true_series, y_score_series = utils.convert_to_series(y_true, y_score) + + fpr, tpr, _ = roc_curve(y_true_series, y_score_series, drop_intermediate=False) + + # TODO(bmil): remove this once bigframes supports the necessary operations + pd_fpr = fpr.to_pandas() + pd_tpr = tpr.to_pandas() + + # Use the trapezoid rule to compute the area under the ROC curve + width_diff = pd_fpr.diff().iloc[1:].reset_index(drop=True) + height_avg = (pd_tpr.iloc[:-1] + pd_tpr.iloc[1:].reset_index(drop=True)) / 2 + return (width_diff * height_avg).sum() + + +roc_auc_score.__doc__ = inspect.getdoc(vendored_mertics_ranking.roc_auc_score) + + +def auc( + x: Union[bpd.DataFrame, bpd.Series], + y: Union[bpd.DataFrame, bpd.Series], +) -> float: + x_series, y_series = utils.convert_to_series(x, y) + + # TODO(b/286410053) Support ML exceptions and error handling. + auc = sklearn_metrics.auc(x_series.to_pandas(), y_series.to_pandas()) + return auc + + +auc.__doc__ = inspect.getdoc(vendored_mertics_ranking.auc) + + +def confusion_matrix( + y_true: Union[bpd.DataFrame, bpd.Series], + y_pred: Union[bpd.DataFrame, bpd.Series], +) -> pd.DataFrame: + # TODO(ashleyxu): support labels and sample_weight parameters + y_true_series, y_pred_series = utils.convert_to_series(y_true, y_pred) + + y_true_series = y_true_series.rename("y_true") + confusion_df = y_true_series.to_frame().assign(y_pred=y_pred_series) + confusion_df = confusion_df.assign(dummy=0) + groupby_count = ( + confusion_df.groupby(by=["y_true", "y_pred"], as_index=False) + .count() + .to_pandas() + ) + + unique_values = sorted( + set(groupby_count["y_true"]).union(set(groupby_count["y_pred"])) + ) + + confusion_matrix = pd.DataFrame( + 0, index=pd.Index(unique_values), columns=pd.Index(unique_values), dtype=int + ) + + # Loop through the result by rows and columns + for _, row in groupby_count.iterrows(): + y_true = row["y_true"] + y_pred = row["y_pred"] + count = row["dummy"] + confusion_matrix[y_pred][y_true] = count + + return confusion_matrix + + +confusion_matrix.__doc__ = inspect.getdoc( + vendored_mertics_classification.confusion_matrix +) + + +def recall_score( + y_true: Union[bpd.DataFrame, bpd.Series], + y_pred: Union[bpd.DataFrame, bpd.Series], + average: str = "binary", +) -> pd.Series: + # TODO(ashleyxu): support more average type, default to "binary" + if average is not None: + raise NotImplementedError( + f"Only average=None is supported. {constants.FEEDBACK_LINK}" + ) + + y_true_series, y_pred_series = utils.convert_to_series(y_true, y_pred) + + is_accurate = y_true_series == y_pred_series + unique_labels = ( + bpd.concat([y_true_series, y_pred_series], join="outer") + .drop_duplicates() + .sort_values() + ) + index = unique_labels.to_list() + + recall = ( + is_accurate.groupby(y_true_series).sum() + / is_accurate.groupby(y_true_series).count() + ).to_pandas() + + recall_score = pd.Series(0, index=index) + for i in recall_score.index: + recall_score.loc[i] = recall.loc[i] + + return recall_score + + +recall_score.__doc__ = inspect.getdoc(vendored_mertics_classification.recall_score) + + +def precision_score( + y_true: Union[bpd.DataFrame, bpd.Series], + y_pred: Union[bpd.DataFrame, bpd.Series], + average: str = "binary", +) -> pd.Series: + # TODO(ashleyxu): support more average type, default to "binary" + if average is not None: + raise NotImplementedError( + f"Only average=None is supported. {constants.FEEDBACK_LINK}" + ) + + y_true_series, y_pred_series = utils.convert_to_series(y_true, y_pred) + + is_accurate = y_true_series == y_pred_series + unique_labels = ( + bpd.concat([y_true_series, y_pred_series], join="outer") + .drop_duplicates() + .sort_values() + ) + index = unique_labels.to_list() + + precision = ( + is_accurate.groupby(y_pred_series).sum() + / is_accurate.groupby(y_pred_series).count() + ).to_pandas() + + precision_score = pd.Series(0, index=index) + for i in precision.index: + precision_score.loc[i] = precision.loc[i] + + return precision_score + + +precision_score.__doc__ = inspect.getdoc( + vendored_mertics_classification.precision_score +) + + +def f1_score( + y_true: Union[bpd.DataFrame, bpd.Series], + y_pred: Union[bpd.DataFrame, bpd.Series], + average: str = "binary", +) -> pd.Series: + # TODO(ashleyxu): support more average type, default to "binary" + y_true_series, y_pred_series = utils.convert_to_series(y_true, y_pred) + + if average is not None: + raise NotImplementedError( + f"Only average=None is supported. {constants.FEEDBACK_LINK}" + ) + + recall = recall_score(y_true_series, y_pred_series, average=None) + precision = precision_score(y_true_series, y_pred_series, average=None) + + f1_score = pd.Series(0, index=recall.index) + for index in recall.index: + if precision[index] + recall[index] != 0: + f1_score[index] = ( + 2 + * (precision[index] * recall[index]) + / (precision[index] + recall[index]) + ) + else: + f1_score[index] = 0 + + return f1_score + + +f1_score.__doc__ = inspect.getdoc(vendored_mertics_classification.f1_score) diff --git a/bigframes/ml/metrics/__init__.py b/bigframes/ml/metrics/__init__.py deleted file mode 100644 index f6c7d5e52f4..00000000000 --- a/bigframes/ml/metrics/__init__.py +++ /dev/null @@ -1,43 +0,0 @@ -# Copyright 2023 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 bigframes.ml.metrics import pairwise -from bigframes.ml.metrics._metrics import ( - accuracy_score, - auc, - confusion_matrix, - f1_score, - mean_absolute_error, - mean_squared_error, - precision_score, - r2_score, - recall_score, - roc_auc_score, - roc_curve, -) - -__all__ = [ - "r2_score", - "recall_score", - "accuracy_score", - "roc_curve", - "roc_auc_score", - "auc", - "confusion_matrix", - "precision_score", - "f1_score", - "mean_absolute_error", - "mean_squared_error", - "pairwise", -] diff --git a/bigframes/ml/metrics/_metrics.py b/bigframes/ml/metrics/_metrics.py deleted file mode 100644 index 1f69d60e317..00000000000 --- a/bigframes/ml/metrics/_metrics.py +++ /dev/null @@ -1,409 +0,0 @@ -# Copyright 2023 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. - -"""Metrics functions for evaluating models. This module is styled after -scikit-learn's metrics module: https://scikit-learn.org/stable/modules/metrics.html.""" - -from __future__ import annotations - -import inspect -import typing -from typing import Literal, Tuple, Union, overload - -import bigframes_vendored.constants as constants -import bigframes_vendored.sklearn.metrics._classification as vendored_metrics_classification -import bigframes_vendored.sklearn.metrics._ranking as vendored_metrics_ranking -import bigframes_vendored.sklearn.metrics._regression as vendored_metrics_regression -import numpy as np -import pandas as pd - -import bigframes.pandas as bpd -from bigframes.ml import utils - - -def r2_score( - y_true: Union[bpd.DataFrame, bpd.Series], - y_pred: Union[bpd.DataFrame, bpd.Series], - *, - force_finite=True, -) -> float: - y_true_series, y_pred_series = utils.batch_convert_to_series(y_true, y_pred) - - # total sum of squares - # (dataframe, scalar) binops - # TODO(tbergeron): These stats are eagerly evaluated. Move to lazy representation once scalar subqueries supported. - delta_from_mean = y_true_series - y_true_series.mean() - ss_total = (delta_from_mean * delta_from_mean).sum() - - # residual sum of squares - # (scalar, scalar) binops - delta_from_pred = y_true_series - y_pred_series - ss_res = (delta_from_pred * delta_from_pred).sum() - - if force_finite and ss_total == 0: - return 0.0 if ss_res > 0 else 1.0 - - return 1 - (ss_res / ss_total) - - -r2_score.__doc__ = inspect.getdoc(vendored_metrics_regression.r2_score) - - -def accuracy_score( - y_true: Union[bpd.DataFrame, bpd.Series], - y_pred: Union[bpd.DataFrame, bpd.Series], - *, - normalize=True, -) -> float: - # TODO(ashleyxu): support sample_weight as the parameter - y_true_series, y_pred_series = utils.batch_convert_to_series(y_true, y_pred) - - # Compute accuracy for each possible representation - # TODO(ashleyxu): add multilabel classification support where y_type - # starts with "multilabel" - score = (y_true_series == y_pred_series).astype(pd.Int64Dtype()) - - if normalize: - return score.mean() - else: - return score.sum() - - -accuracy_score.__doc__ = inspect.getdoc(vendored_metrics_classification.accuracy_score) - - -def roc_curve( - y_true: Union[bpd.DataFrame, bpd.Series], - y_score: Union[bpd.DataFrame, bpd.Series], - *, - drop_intermediate: bool = True, -) -> Tuple[bpd.Series, bpd.Series, bpd.Series]: - # TODO(bmil): Add multi-class support - # TODO(bmil): Add multi-label support - - # TODO(bmil): Implement drop_intermediate - if drop_intermediate: - raise NotImplementedError( - f"drop_intermediate is not yet implemented. {constants.FEEDBACK_LINK}" - ) - - y_true_series, y_score_series = utils.batch_convert_to_series(y_true, y_score) - - session = y_true_series._block.expr.session - - # We operate on rows, so, remove the index if there is one - # TODO(bmil): check that the indexes are equivalent before removing - - y_true_series = typing.cast(bpd.Series, y_true_series.reset_index(drop=True)) - y_score_series = typing.cast(bpd.Series, y_score_series.reset_index(drop=True)) - - df = bpd.DataFrame( - { - "y_true": y_true_series, - "y_score": y_score_series, - } - ) - - total_positives = y_true_series.sum() - total_negatives = y_true_series.count() - total_positives - - df = df.sort_values(by="y_score", ascending=False) - df["cum_tp"] = df["y_true"].cumsum() - # have to astype("Int64") as not supported boolean cumsum yet. - df["cum_fp"] = ( - (~typing.cast(bpd.Series, df["y_true"].astype("boolean"))) - .astype("Int64") - .cumsum() - ) - - # produce just one data point per y_score - df = df.drop_duplicates(subset="y_score", keep="last") - df = df.sort_values(by="y_score", ascending=False) - - df["tpr"] = typing.cast(bpd.Series, df["cum_tp"]) / total_positives - df["fpr"] = typing.cast(bpd.Series, df["cum_fp"]) / total_negatives - df["thresholds"] = typing.cast(bpd.Series, df["y_score"].astype("Float64")) - - # sklearn includes an extra datapoint for the origin with threshold np.inf - # having problems with concating inline - df_origin = session.read_pandas( - pd.DataFrame({"tpr": [0.0], "fpr": [0.0], "thresholds": np.inf}) - ) - df = typing.cast(bpd.DataFrame, bpd.concat([df_origin, df], ignore_index=True)) - df = df.reset_index(drop=True) - - return ( - typing.cast(bpd.Series, df["fpr"]), - typing.cast(bpd.Series, df["tpr"]), - typing.cast(bpd.Series, df["thresholds"]), - ) - - -roc_curve.__doc__ = inspect.getdoc(vendored_metrics_ranking.roc_curve) - - -def roc_auc_score( - y_true: Union[bpd.DataFrame, bpd.Series], y_score: Union[bpd.DataFrame, bpd.Series] -) -> float: - # TODO(bmil): Add multi-class support - # TODO(bmil): Add multi-label support - y_true_series, y_score_series = utils.batch_convert_to_series(y_true, y_score) - - fpr, tpr, _ = roc_curve(y_true_series, y_score_series, drop_intermediate=False) - - # Use the trapezoid rule to compute the area under the ROC curve - width_diff = fpr.diff().iloc[1:].reset_index(drop=True) - height_avg = (tpr.iloc[:-1] + tpr.iloc[1:].reset_index(drop=True)) / 2 - return typing.cast(float, (width_diff * height_avg).sum()) - - -roc_auc_score.__doc__ = inspect.getdoc(vendored_metrics_ranking.roc_auc_score) - - -def auc( - x: Union[bpd.DataFrame, bpd.Series], - y: Union[bpd.DataFrame, bpd.Series], -) -> float: - x_series, y_series = utils.batch_convert_to_series(x, y) - - x_pandas = x_series.to_pandas() - y_pandas = y_series.to_pandas() - return vendored_metrics_ranking.auc(x_pandas, y_pandas) - - -auc.__doc__ = inspect.getdoc(vendored_metrics_ranking.auc) - - -def confusion_matrix( - y_true: Union[bpd.DataFrame, bpd.Series], - y_pred: Union[bpd.DataFrame, bpd.Series], -) -> pd.DataFrame: - # TODO(ashleyxu): support labels and sample_weight parameters - y_true_series, y_pred_series = utils.batch_convert_to_series(y_true, y_pred) - - y_true_series = y_true_series.rename("y_true") - confusion_df = y_true_series.to_frame().assign(y_pred=y_pred_series) - confusion_df = confusion_df.assign(dummy=0) - groupby_count = ( - confusion_df.groupby(by=["y_true", "y_pred"], as_index=False) - .count() - .to_pandas() - ) - - unique_values = sorted( - set(groupby_count["y_true"]).union(set(groupby_count["y_pred"])) - ) - - confusion_matrix = pd.DataFrame( - 0, index=pd.Index(unique_values), columns=pd.Index(unique_values), dtype=int - ) - - # Loop through the result by rows and columns - for _, row in groupby_count.iterrows(): - y_true = row["y_true"] - y_pred = row["y_pred"] - count = row["dummy"] - confusion_matrix.at[y_true, y_pred] = count - - return confusion_matrix - - -confusion_matrix.__doc__ = inspect.getdoc( - vendored_metrics_classification.confusion_matrix -) - - -def recall_score( - y_true: Union[bpd.DataFrame, bpd.Series], - y_pred: Union[bpd.DataFrame, bpd.Series], - *, - average: typing.Optional[str] = "binary", -) -> pd.Series: - # TODO(ashleyxu): support more average type, default to "binary" - if average is not None: - raise NotImplementedError( - f"Only average=None is supported. {constants.FEEDBACK_LINK}" - ) - - y_true_series, y_pred_series = utils.batch_convert_to_series(y_true, y_pred) - - is_accurate = y_true_series == y_pred_series - unique_labels = ( - bpd.concat([y_true_series, y_pred_series], join="outer") - .drop_duplicates() - .sort_values(inplace=False) - ) - index = unique_labels.to_list() - - recall = ( - is_accurate.groupby(y_true_series).sum() - / is_accurate.groupby(y_true_series).count() - ).to_pandas() - - recall_score = pd.Series(0.0, index=index) - for i in recall_score.index: - recall_score.loc[i] = recall.loc[i] - - return recall_score - - -recall_score.__doc__ = inspect.getdoc(vendored_metrics_classification.recall_score) - - -@overload -def precision_score( - y_true: bpd.DataFrame | bpd.Series, - y_pred: bpd.DataFrame | bpd.Series, - *, - pos_label: int | float | bool | str = ..., - average: Literal["binary"] = ..., -) -> float: ... - - -@overload -def precision_score( - y_true: bpd.DataFrame | bpd.Series, - y_pred: bpd.DataFrame | bpd.Series, - *, - pos_label: int | float | bool | str = ..., - average: None = ..., -) -> pd.Series: ... - - -def precision_score( - y_true: bpd.DataFrame | bpd.Series, - y_pred: bpd.DataFrame | bpd.Series, - *, - pos_label: int | float | bool | str = 1, - average: Literal["binary"] | None = "binary", -) -> pd.Series | float: - y_true_series, y_pred_series = utils.batch_convert_to_series(y_true, y_pred) - - if average is None: - return _precision_score_per_label(y_true_series, y_pred_series) - - if average == "binary": - return _precision_score_binary_pos_only(y_true_series, y_pred_series, pos_label) - - raise NotImplementedError( - f"Unsupported 'average' param value: {average}. {constants.FEEDBACK_LINK}" - ) - - -precision_score.__doc__ = inspect.getdoc( - vendored_metrics_classification.precision_score -) - - -def _precision_score_per_label(y_true: bpd.Series, y_pred: bpd.Series) -> pd.Series: - is_accurate = y_true == y_pred - unique_labels = ( - bpd.concat([y_true, y_pred], join="outer") - .drop_duplicates() - .sort_values(inplace=False) - ) - index = unique_labels.to_list() - - precision = ( - is_accurate.groupby(y_pred).sum() / is_accurate.groupby(y_pred).count() - ).to_pandas() - - precision_score = pd.Series(0.0, index=index) - for i in precision.index: - precision_score.loc[i] = precision.loc[i] - - return precision_score - - -def _precision_score_binary_pos_only( - y_true: bpd.Series, y_pred: bpd.Series, pos_label: int | float | bool | str -) -> float: - unique_labels = bpd.concat([y_true, y_pred]).unique(keep_order=False) - - if unique_labels.count() != 2: - raise ValueError( - "Target is multiclass but average='binary'. Please choose another average setting." - ) - - if not (unique_labels == pos_label).any(): - raise ValueError( - f"pos_labe={pos_label} is not a valid label. It should be one of {unique_labels.to_list()}" - ) - - target_elem_idx = y_pred == pos_label - is_accurate = y_pred[target_elem_idx] == y_true[target_elem_idx] - - return is_accurate.sum() / is_accurate.count() - - -def f1_score( - y_true: Union[bpd.DataFrame, bpd.Series], - y_pred: Union[bpd.DataFrame, bpd.Series], - *, - average: typing.Optional[str] = "binary", -) -> pd.Series: - # TODO(ashleyxu): support more average type, default to "binary" - y_true_series, y_pred_series = utils.batch_convert_to_series(y_true, y_pred) - - if average is not None: - raise NotImplementedError( - f"Only average=None is supported. {constants.FEEDBACK_LINK}" - ) - - recall = recall_score(y_true_series, y_pred_series, average=None) - precision = precision_score(y_true_series, y_pred_series, average=None) - - f1_score = pd.Series(0.0, index=recall.index) - for index in recall.index: - if precision[index] + recall[index] != 0: - f1_score[index] = ( - 2 - * (precision[index] * recall[index]) - / (precision[index] + recall[index]) - ) - else: - f1_score[index] = 0 - - return f1_score - - -f1_score.__doc__ = inspect.getdoc(vendored_metrics_classification.f1_score) - - -def mean_squared_error( - y_true: Union[bpd.DataFrame, bpd.Series], - y_pred: Union[bpd.DataFrame, bpd.Series], -) -> float: - y_true_series, y_pred_series = utils.batch_convert_to_series(y_true, y_pred) - - return (y_pred_series - y_true_series).pow(2).sum() / len(y_true_series) - - -mean_squared_error.__doc__ = inspect.getdoc( - vendored_metrics_regression.mean_squared_error -) - - -def mean_absolute_error( - y_true: Union[bpd.DataFrame, bpd.Series], - y_pred: Union[bpd.DataFrame, bpd.Series], -) -> float: - y_true_series, y_pred_series = utils.batch_convert_to_series(y_true, y_pred) - - return (y_pred_series - y_true_series).abs().sum() / len(y_true_series) - - -mean_absolute_error.__doc__ = inspect.getdoc( - vendored_metrics_regression.mean_absolute_error -) diff --git a/bigframes/ml/metrics/pairwise.py b/bigframes/ml/metrics/pairwise.py deleted file mode 100644 index 41785a8462d..00000000000 --- a/bigframes/ml/metrics/pairwise.py +++ /dev/null @@ -1,82 +0,0 @@ -# Copyright 2023 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 inspect -from typing import Union - -import bigframes_vendored.sklearn.metrics.pairwise as vendored_metrics_pairwise - -import bigframes.operations as ops -import bigframes.pandas as bpd -from bigframes.ml import utils - - -def paired_cosine_distances( - X: Union[bpd.DataFrame, bpd.Series], Y: Union[bpd.DataFrame, bpd.Series] -) -> bpd.DataFrame: - X, Y = utils.batch_convert_to_series(X, Y) - joined_block, _ = X._block.join(Y._block, how="outer") - - result_block, _ = joined_block.project_expr( - ops.cosine_distance_op.as_expr( - joined_block.value_columns[0], joined_block.value_columns[1] - ), - label="cosine_distance", - ) - return bpd.DataFrame(result_block) - - -paired_cosine_distances.__doc__ = inspect.getdoc( - vendored_metrics_pairwise.paired_cosine_distances -) - - -def paired_manhattan_distance( - X: Union[bpd.DataFrame, bpd.Series], Y: Union[bpd.DataFrame, bpd.Series] -) -> bpd.DataFrame: - X, Y = utils.batch_convert_to_series(X, Y) - joined_block, _ = X._block.join(Y._block, how="outer") - - result_block, _ = joined_block.project_expr( - ops.manhattan_distance_op.as_expr( - joined_block.value_columns[0], joined_block.value_columns[1] - ), - label="manhattan_distance", - ) - return bpd.DataFrame(result_block) - - -paired_manhattan_distance.__doc__ = inspect.getdoc( - vendored_metrics_pairwise.paired_manhattan_distance -) - - -def paired_euclidean_distances( - X: Union[bpd.DataFrame, bpd.Series], Y: Union[bpd.DataFrame, bpd.Series] -) -> bpd.DataFrame: - X, Y = utils.batch_convert_to_series(X, Y) - joined_block, _ = X._block.join(Y._block, how="outer") - - result_block, _ = joined_block.project_expr( - ops.euclidean_distance_op.as_expr( - joined_block.value_columns[0], joined_block.value_columns[1] - ), - label="euclidean_distance", - ) - return bpd.DataFrame(result_block) - - -paired_euclidean_distances.__doc__ = inspect.getdoc( - vendored_metrics_pairwise.paired_euclidean_distances -) diff --git a/bigframes/ml/model_selection.py b/bigframes/ml/model_selection.py index 57e07d89301..443b9e7be6e 100644 --- a/bigframes/ml/model_selection.py +++ b/bigframes/ml/model_selection.py @@ -13,33 +13,48 @@ # limitations under the License. """Functions for test/train split and model tuning. This module is styled after -scikit-learn's model_selection module: +Scikit-Learn's model_selection module: https://scikit-learn.org/stable/modules/classes.html#module-sklearn.model_selection.""" -import inspect -import time -import typing -from itertools import chain as _chain -from typing import Generator, List, Optional, Union -import bigframes_vendored.sklearn.model_selection._split as vendored_model_selection_split -import bigframes_vendored.sklearn.model_selection._validation as vendored_model_selection_validation -import pandas as pd +import typing +from typing import List, Union -import bigframes.pandas as bpd -from bigframes._tools import docs -from bigframes.core.logging import log_adapter from bigframes.ml import utils +import bigframes.pandas as bpd def train_test_split( - *arrays: utils.ArrayType, + *arrays: Union[bpd.DataFrame, bpd.Series], test_size: Union[float, None] = None, train_size: Union[float, None] = None, random_state: Union[int, None] = None, - stratify: Union[bpd.Series, None] = None, - shuffle: bool = True, ) -> List[Union[bpd.DataFrame, bpd.Series]]: + """Splits dataframes or series into random train and test subsets. + + Args: + *arrays (bigframes.dataframe.DataFrame or bigframes.series.Series): + A sequence of BigQuery DataFrames or Series that can be joined on + their indexes + test_size (default None): + The proportion of the dataset to include in the test split. If + None, this will default to the complement of train_size. If both + are none, it will be set to 0.25. + train_size (default None): + The proportion of the dataset to include in the train split. If + None, this will default to the complement of test_size. + random_state (default None): + A seed to use for randomly choosing the rows of the split. If not + set, a random split will be generated each time. + + Returns: + List[Union[bigframes.dataframe.DataFrame, bigframes.series.Series]]: A list of BigQuery DataFrames or Series. + """ + + # TODO(garrettwu): Scikit-Learn throws an error when the dataframes don't have the same + # number of rows. We probably want to do something similar. Now the implementation is based + # on index. We'll move to based on ordering first. + if test_size is None: if train_size is None: test_size = 0.25 @@ -59,167 +74,23 @@ def train_test_split( f"The sum of train_size and test_size exceeds 1.0. train_size: {train_size}. test_size: {test_size}" ) - if not shuffle: - if stratify is not None: - raise ValueError( - "Stratified train/test split is not implemented for shuffle=False" - ) - bf_arrays = list(utils.batch_convert_to_bf_equivalent(*arrays)) - - total_rows = len(bf_arrays[0]) - train_rows = int(total_rows * train_size) - test_rows = total_rows - train_rows - - return list( - _chain.from_iterable( - [ - [bf_array.head(train_rows), bf_array.tail(test_rows)] - for bf_array in bf_arrays - ] - ) - ) + dfs = list(utils.convert_to_dataframe(*arrays)) - dfs = list(utils.batch_convert_to_dataframe(*arrays)) - - def _stratify_split(df: bpd.DataFrame, stratify: bpd.Series) -> List[bpd.DataFrame]: - """Split a single DF according to the stratify Series.""" - stratify = stratify.rename("bigframes_stratify_col") # avoid name conflicts - merged_df = df.join(stratify.to_frame(), how="outer") - - train_dfs, test_dfs = [], [] - uniq = stratify.value_counts().index - for value in uniq: - cur = merged_df[merged_df["bigframes_stratify_col"] == value] - train, test = train_test_split( - cur, - test_size=test_size, - train_size=train_size, - random_state=random_state, - ) - train_dfs.append(train) - test_dfs.append(test) - - train_df = typing.cast( - bpd.DataFrame, bpd.concat(train_dfs).drop(columns="bigframes_stratify_col") - ) - test_df = typing.cast( - bpd.DataFrame, bpd.concat(test_dfs).drop(columns="bigframes_stratify_col") - ) - return [train_df, test_df] - - joined_df = dfs[0] - for df in dfs[1:]: - joined_df = joined_df.join(df, how="outer") - if stratify is None: - joined_df_train, joined_df_test = joined_df._split( - fracs=(train_size, test_size), random_state=random_state - ) - else: - joined_df_train, joined_df_test = _stratify_split(joined_df, stratify) - - results = [] - for array in arrays: - columns = array.name if isinstance(array, bpd.Series) else array.columns - results.append(joined_df_train[columns]) - results.append(joined_df_test[columns]) + split_dfs = dfs[0]._split(fracs=(train_size, test_size), random_state=random_state) + train_index = split_dfs[0].index + test_index = split_dfs[1].index - return results + split_dfs += typing.cast( + List[bpd.DataFrame], + [df.loc[index] for df in dfs[1:] for index in (train_index, test_index)], + ) + # convert back to Series. + results: List[Union[bpd.DataFrame, bpd.Series]] = [] + for i, array in enumerate(arrays): + if isinstance(array, bpd.Series): + results += utils.convert_to_series(split_dfs[2 * i], split_dfs[2 * i + 1]) + else: + results += (split_dfs[2 * i], split_dfs[2 * i + 1]) -train_test_split.__doc__ = inspect.getdoc( - vendored_model_selection_split.train_test_split -) - - -@log_adapter.class_logger -@docs.inherit_docs(vendored_model_selection_split.KFold) -class KFold: - def __init__(self, n_splits: int = 5, *, random_state: Union[int, None] = None): - if n_splits < 2: - raise ValueError(f"n_splits must be at least 2. Got {n_splits}") - self._n_splits = n_splits - self._random_state = random_state - - def get_n_splits(self) -> int: - return self._n_splits - - def split( - self, - X: utils.ArrayType, - y: Union[utils.ArrayType, None] = None, - ) -> Generator[tuple[Union[bpd.DataFrame, bpd.Series, None], ...], None, None]: - X_df = next(utils.batch_convert_to_dataframe(X)) - y_df_or = next(utils.batch_convert_to_dataframe(y)) if y is not None else None - joined_df = X_df.join(y_df_or, how="outer") if y_df_or is not None else X_df - - fracs = (1 / self._n_splits,) * self._n_splits - - dfs = joined_df._split(fracs=fracs, random_state=self._random_state) - - for i in range(len(dfs)): - train_df = bpd.concat(dfs[:i] + dfs[i + 1 :]) - test_df = dfs[i] - - X_train = train_df[X_df.columns] - y_train = train_df[y_df_or.columns] if y_df_or is not None else None - - X_test = test_df[X_df.columns] - y_test = test_df[y_df_or.columns] if y_df_or is not None else None - - yield ( - KFold._convert_to_bf_type(X_train, X), - KFold._convert_to_bf_type(X_test, X), - KFold._convert_to_bf_type(y_train, y), - KFold._convert_to_bf_type(y_test, y), - ) - - @staticmethod - def _convert_to_bf_type( - input, - type_instance: Union[bpd.DataFrame, bpd.Series, pd.DataFrame, pd.Series, None], - ) -> Union[bpd.DataFrame, bpd.Series, None]: - if isinstance(type_instance, pd.Series) or isinstance( - type_instance, bpd.Series - ): - return next(utils.batch_convert_to_series(input)) - - if isinstance(type_instance, pd.DataFrame) or isinstance( - type_instance, bpd.DataFrame - ): - return next(utils.batch_convert_to_dataframe(input)) - - return None - - -def cross_validate( - estimator, - X: utils.ArrayType, - y: Union[utils.ArrayType, None] = None, - *, - cv: Optional[Union[int, KFold]] = None, -) -> dict[str, list]: - if cv is None: - cv = KFold(n_splits=5) - elif isinstance(cv, int): - cv = KFold(n_splits=cv) - - result: dict[str, list] = {"test_score": [], "fit_time": [], "score_time": []} - for X_train, X_test, y_train, y_test in cv.split(X, y): # type: ignore - fit_start_time = time.perf_counter() - estimator.fit(X_train, y_train) - fit_time = time.perf_counter() - fit_start_time - - score_start_time = time.perf_counter() - score = estimator.score(X_test, y_test) - score_time = time.perf_counter() - score_start_time - - result["test_score"].append(score) - result["fit_time"].append(fit_time) - result["score_time"].append(score_time) - - return result - - -cross_validate.__doc__ = inspect.getdoc( - vendored_model_selection_validation.cross_validate -) + return results diff --git a/bigframes/ml/pipeline.py b/bigframes/ml/pipeline.py index 59057fb2faf..ad0b3fae111 100644 --- a/bigframes/ml/pipeline.py +++ b/bigframes/ml/pipeline.py @@ -12,37 +12,28 @@ # See the License for the specific language governing permissions and # limitations under the License. -"""For composing estimators together. This module is styled after scikit-learn's +"""For composing estimators together. This module is styled after Scikit-Learn's pipeline module: https://scikit-learn.org/stable/modules/pipeline.html.""" + from __future__ import annotations -from typing import List, Optional, Tuple +from typing import cast, List, Optional, Tuple, Union -import bigframes_vendored.constants as constants -import bigframes_vendored.sklearn.pipeline from google.cloud import bigquery -import bigframes.dataframe -import bigframes.session -from bigframes.core.logging import log_adapter -from bigframes.ml import ( - base, - compose, - forecasting, - impute, - loader, - preprocessing, - utils, -) - - -@log_adapter.class_logger +import bigframes +import bigframes.constants as constants +from bigframes.ml import base, compose, forecasting, loader, preprocessing, utils +import bigframes.pandas as bpd +import third_party.bigframes_vendored.sklearn.pipeline + + class Pipeline( base.BaseEstimator, - bigframes_vendored.sklearn.pipeline.Pipeline, + third_party.bigframes_vendored.sklearn.pipeline.Pipeline, ): - __doc__ = bigframes_vendored.sklearn.pipeline.Pipeline.__doc__ + __doc__ = third_party.bigframes_vendored.sklearn.pipeline.Pipeline.__doc__ def __init__(self, steps: List[Tuple[str, base.BaseEstimator]]): self.steps = steps @@ -63,8 +54,6 @@ def __init__(self, steps: List[Tuple[str, base.BaseEstimator]]): preprocessing.MinMaxScaler, preprocessing.KBinsDiscretizer, preprocessing.LabelEncoder, - preprocessing.PolynomialFeatures, - impute.SimpleImputer, ), ): self._transform = transform @@ -91,42 +80,42 @@ def __init__(self, steps: List[Tuple[str, base.BaseEstimator]]): self._estimator = estimator @classmethod - def _from_bq( - cls, session: bigframes.session.Session, bq_model: bigquery.Model - ) -> Pipeline: - col_transformer = compose.ColumnTransformer._extract_from_bq_model(bq_model) - transform = col_transformer._merge(bq_model) + def _from_bq(cls, session: bigframes.Session, bq_model: bigquery.Model) -> Pipeline: + col_transformer = _extract_as_column_transformer(bq_model) + transform = _merge_column_transformer(bq_model, col_transformer) estimator = loader._model_from_bq(session, bq_model) return cls([("transform", transform), ("estimator", estimator)]) def fit( self, - X: utils.BigFramesArrayType, - y: Optional[utils.BigFramesArrayType] = None, + X: Union[bpd.DataFrame, bpd.Series], + y: Optional[Union[bpd.DataFrame, bpd.Series]] = None, ) -> Pipeline: - (X,) = utils.batch_convert_to_dataframe(X) + (X,) = utils.convert_to_dataframe(X) + + compiled_transforms = self._transform._compile_to_sql(X.columns.tolist(), X=X) + transform_sqls = [transform_sql for transform_sql, _ in compiled_transforms] - transform_sqls = self._transform._compile_to_sql(X) if y is not None: # If labels columns are present, they should pass through un-transformed - (y,) = utils.batch_convert_to_dataframe(y) + (y,) = utils.convert_to_dataframe(y) transform_sqls.extend(y.columns.tolist()) self._estimator._fit(X=X, y=y, transforms=transform_sqls) return self - def predict(self, X: utils.ArrayType) -> bigframes.dataframe.DataFrame: + def predict(self, X: Union[bpd.DataFrame, bpd.Series]) -> bpd.DataFrame: return self._estimator.predict(X) def score( self, - X: utils.BigFramesArrayType, - y: Optional[utils.BigFramesArrayType] = None, - ) -> bigframes.dataframe.DataFrame: - (X,) = utils.batch_convert_to_dataframe(X) + X: Union[bpd.DataFrame, bpd.Series], + y: Optional[Union[bpd.DataFrame, bpd.Series]] = None, + ) -> bpd.DataFrame: + (X,) = utils.convert_to_dataframe(X) if y is not None: - (y,) = utils.batch_convert_to_dataframe(y) + (y,) = utils.convert_to_dataframe(y) return self._estimator.score(X=X, y=y) @@ -135,15 +124,122 @@ def to_gbq(self, model_name: str, replace: bool = False) -> Pipeline: Args: model_name (str): - The name of the model(pipeline). + the name of the model(pipeline). replace (bool, default False): - Whether to replace if the model(pipeline) already exists. Default to False. + whether to replace if the model(pipeline) already exists. Default to False. Returns: - Pipeline: Saved model(pipeline).""" + Pipeline: saved model(pipeline).""" if not self._estimator._bqml_model: raise RuntimeError("A model must be fitted before it can be saved") new_model = self._estimator._bqml_model.copy(model_name, replace) return new_model.session.read_gbq_model(model_name) + + +def _extract_as_column_transformer( + bq_model: bigquery.Model, +) -> compose.ColumnTransformer: + """Extract transformers as ColumnTransformer obj from a BQ Model.""" + assert "transformColumns" in bq_model._properties + + transformers: List[ + Tuple[ + str, + Union[ + preprocessing.OneHotEncoder, + preprocessing.StandardScaler, + preprocessing.MaxAbsScaler, + preprocessing.MinMaxScaler, + preprocessing.KBinsDiscretizer, + preprocessing.LabelEncoder, + ], + Union[str, List[str]], + ] + ] = [] + for transform_col in bq_model._properties["transformColumns"]: + # pass the columns that are not transformed + if "transformSql" not in transform_col: + continue + + transform_sql: str = cast(dict, transform_col)["transformSql"] + if transform_sql.startswith("ML.STANDARD_SCALER"): + transformers.append( + ( + "standard_scaler", + *preprocessing.StandardScaler._parse_from_sql(transform_sql), + ) + ) + elif transform_sql.startswith("ML.ONE_HOT_ENCODER"): + transformers.append( + ( + "ont_hot_encoder", + *preprocessing.OneHotEncoder._parse_from_sql(transform_sql), + ) + ) + elif transform_sql.startswith("ML.MAX_ABS_SCALER"): + transformers.append( + ( + "max_abs_scaler", + *preprocessing.MaxAbsScaler._parse_from_sql(transform_sql), + ) + ) + elif transform_sql.startswith("ML.MIN_MAX_SCALER"): + transformers.append( + ( + "min_max_scaler", + *preprocessing.MinMaxScaler._parse_from_sql(transform_sql), + ) + ) + elif transform_sql.startswith("ML.BUCKETIZE"): + transformers.append( + ( + "k_bins_discretizer", + *preprocessing.KBinsDiscretizer._parse_from_sql(transform_sql), + ) + ) + elif transform_sql.startswith("ML.LABEL_ENCODER"): + transformers.append( + ( + "label_encoder", + *preprocessing.LabelEncoder._parse_from_sql(transform_sql), + ) + ) + else: + raise NotImplementedError( + f"Unsupported transformer type. {constants.FEEDBACK_LINK}" + ) + + return compose.ColumnTransformer(transformers=transformers) + + +def _merge_column_transformer( + bq_model: bigquery.Model, column_transformer: compose.ColumnTransformer +) -> Union[ + compose.ColumnTransformer, + preprocessing.StandardScaler, + preprocessing.OneHotEncoder, + preprocessing.MaxAbsScaler, + preprocessing.MinMaxScaler, + preprocessing.KBinsDiscretizer, + preprocessing.LabelEncoder, +]: + """Try to merge the column transformer to a simple transformer.""" + transformers = column_transformer.transformers_ + + assert len(transformers) > 0 + _, transformer_0, column_0 = transformers[0] + columns = [column_0] + for _, transformer, column in transformers[1:]: + # all transformers are the same + if transformer != transformer_0: + return column_transformer + columns.append(column) + # all feature columns are transformed + if sorted( + [cast(str, feature_column.name) for feature_column in bq_model.feature_columns] + ) == sorted(columns): + return transformer_0 + + return column_transformer diff --git a/bigframes/ml/preprocessing.py b/bigframes/ml/preprocessing.py index 28272fd6a02..5f44d402184 100644 --- a/bigframes/ml/preprocessing.py +++ b/bigframes/ml/preprocessing.py @@ -13,57 +13,55 @@ # limitations under the License. """Transformers that prepare data for other estimators. This module is styled after -scikit-learn's preprocessing module: https://scikit-learn.org/stable/modules/preprocessing.html.""" +Scikit-Learn's preprocessing module: https://scikit-learn.org/stable/modules/preprocessing.html.""" from __future__ import annotations import typing -from typing import Iterable, List, Literal, Optional, Union +from typing import Any, cast, List, Literal, Optional, Tuple, Union -import bigframes_vendored.sklearn.preprocessing._data -import bigframes_vendored.sklearn.preprocessing._discretization -import bigframes_vendored.sklearn.preprocessing._encoder -import bigframes_vendored.sklearn.preprocessing._label -import bigframes_vendored.sklearn.preprocessing._polynomial - -import bigframes.core.utils as core_utils -import bigframes.pandas as bpd -from bigframes.core.logging import log_adapter from bigframes.ml import base, core, globals, utils +import bigframes.pandas as bpd +import third_party.bigframes_vendored.sklearn.preprocessing._data +import third_party.bigframes_vendored.sklearn.preprocessing._discretization +import third_party.bigframes_vendored.sklearn.preprocessing._encoder +import third_party.bigframes_vendored.sklearn.preprocessing._label -@log_adapter.class_logger class StandardScaler( base.Transformer, - bigframes_vendored.sklearn.preprocessing._data.StandardScaler, + third_party.bigframes_vendored.sklearn.preprocessing._data.StandardScaler, ): - __doc__ = bigframes_vendored.sklearn.preprocessing._data.StandardScaler.__doc__ + __doc__ = ( + third_party.bigframes_vendored.sklearn.preprocessing._data.StandardScaler.__doc__ + ) def __init__(self): self._bqml_model: Optional[core.BqmlModel] = None self._bqml_model_factory = globals.bqml_model_factory() self._base_sql_generator = globals.base_sql_generator() - def _keys(self): - return (self._bqml_model,) + # TODO(garrettwu): implement __hash__ + def __eq__(self, other: Any) -> bool: + return type(other) is StandardScaler and self._bqml_model == other._bqml_model - def _compile_to_sql( - self, X: bpd.DataFrame, columns: Optional[Iterable[str]] = None - ) -> List[str]: + def _compile_to_sql(self, columns: List[str], X=None) -> List[Tuple[str, str]]: """Compile this transformer to a list of SQL expressions that can be included in a BQML TRANSFORM clause Args: - X: DataFrame to transform. - columns: transform columns. If None, transform all columns in X. + columns: + a list of column names to transform. + X (default None): + Ignored. - Returns: a list of tuples sql_expr.""" - if columns is None: - columns = X.columns - columns, _ = core_utils.get_standardized_ids(columns) + Returns: a list of tuples of (sql_expression, output_name)""" return [ - self._base_sql_generator.ml_standard_scaler( - column, f"standard_scaled_{column}" + ( + self._base_sql_generator.ml_standard_scaler( + column, f"standard_scaled_{column}" + ), + f"standard_scaled_{column}", ) for column in columns ] @@ -78,30 +76,33 @@ def _parse_from_sql(cls, sql: str) -> tuple[StandardScaler, str]: Returns: tuple(StandardScaler, column_label)""" col_label = sql[sql.find("(") + 1 : sql.find(")")] - return cls(), _unescape_id(col_label) + return cls(), col_label def fit( self, - X: utils.ArrayType, + X: Union[bpd.DataFrame, bpd.Series], y=None, # ignored ) -> StandardScaler: - (X,) = utils.batch_convert_to_dataframe(X) + (X,) = utils.convert_to_dataframe(X) + + compiled_transforms = self._compile_to_sql(X.columns.tolist()) + transform_sqls = [transform_sql for transform_sql, _ in compiled_transforms] - transform_sqls = self._compile_to_sql(X) self._bqml_model = self._bqml_model_factory.create_model( X, options={"model_type": "transform_only"}, transforms=transform_sqls, ) - self._extract_output_names() + # The schema of TRANSFORM output is not available in the model API, so save it during fitting + self._output_names = [name for _, name in compiled_transforms] return self - def transform(self, X: utils.ArrayType) -> bpd.DataFrame: + def transform(self, X: Union[bpd.DataFrame, bpd.Series]) -> bpd.DataFrame: if not self._bqml_model: raise RuntimeError("Must be fitted before transform") - (X,) = utils.batch_convert_to_dataframe(X, session=self._bqml_model.session) + (X,) = utils.convert_to_dataframe(X) df = self._bqml_model.transform(X) return typing.cast( @@ -110,38 +111,40 @@ def transform(self, X: utils.ArrayType) -> bpd.DataFrame: ) -@log_adapter.class_logger class MaxAbsScaler( base.Transformer, - bigframes_vendored.sklearn.preprocessing._data.MaxAbsScaler, + third_party.bigframes_vendored.sklearn.preprocessing._data.MaxAbsScaler, ): - __doc__ = bigframes_vendored.sklearn.preprocessing._data.MaxAbsScaler.__doc__ + __doc__ = ( + third_party.bigframes_vendored.sklearn.preprocessing._data.MaxAbsScaler.__doc__ + ) def __init__(self): self._bqml_model: Optional[core.BqmlModel] = None self._bqml_model_factory = globals.bqml_model_factory() self._base_sql_generator = globals.base_sql_generator() - def _keys(self): - return (self._bqml_model,) + # TODO(garrettwu): implement __hash__ + def __eq__(self, other: Any) -> bool: + return type(other) is MaxAbsScaler and self._bqml_model == other._bqml_model - def _compile_to_sql( - self, X: bpd.DataFrame, columns: Optional[Iterable[str]] = None - ) -> List[str]: + def _compile_to_sql(self, columns: List[str], X=None) -> List[Tuple[str, str]]: """Compile this transformer to a list of SQL expressions that can be included in a BQML TRANSFORM clause Args: - X: DataFrame to transform. - columns: transform columns. If None, transform all columns in X. + columns: + a list of column names to transform. + X (default None): + Ignored. - Returns: a list of tuples sql_expr.""" - if columns is None: - columns = X.columns - columns, _ = core_utils.get_standardized_ids(columns) + Returns: a list of tuples of (sql_expression, output_name)""" return [ - self._base_sql_generator.ml_max_abs_scaler( - column, f"max_abs_scaled_{column}" + ( + self._base_sql_generator.ml_max_abs_scaler( + column, f"max_abs_scaled_{column}" + ), + f"max_abs_scaled_{column}", ) for column in columns ] @@ -155,32 +158,34 @@ def _parse_from_sql(cls, sql: str) -> tuple[MaxAbsScaler, str]: Returns: tuple(MaxAbsScaler, column_label)""" - # TODO: Use real sql parser col_label = sql[sql.find("(") + 1 : sql.find(")")] - return cls(), _unescape_id(col_label) + return cls(), col_label def fit( self, - X: utils.ArrayType, + X: Union[bpd.DataFrame, bpd.Series], y=None, # ignored ) -> MaxAbsScaler: - (X,) = utils.batch_convert_to_dataframe(X) + (X,) = utils.convert_to_dataframe(X) + + compiled_transforms = self._compile_to_sql(X.columns.tolist()) + transform_sqls = [transform_sql for transform_sql, _ in compiled_transforms] - transform_sqls = self._compile_to_sql(X) self._bqml_model = self._bqml_model_factory.create_model( X, options={"model_type": "transform_only"}, transforms=transform_sqls, ) - self._extract_output_names() + # The schema of TRANSFORM output is not available in the model API, so save it during fitting + self._output_names = [name for _, name in compiled_transforms] return self - def transform(self, X: utils.ArrayType) -> bpd.DataFrame: + def transform(self, X: Union[bpd.DataFrame, bpd.Series]) -> bpd.DataFrame: if not self._bqml_model: raise RuntimeError("Must be fitted before transform") - (X,) = utils.batch_convert_to_dataframe(X, session=self._bqml_model.session) + (X,) = utils.convert_to_dataframe(X) df = self._bqml_model.transform(X) return typing.cast( @@ -189,38 +194,40 @@ def transform(self, X: utils.ArrayType) -> bpd.DataFrame: ) -@log_adapter.class_logger class MinMaxScaler( base.Transformer, - bigframes_vendored.sklearn.preprocessing._data.MinMaxScaler, + third_party.bigframes_vendored.sklearn.preprocessing._data.MinMaxScaler, ): - __doc__ = bigframes_vendored.sklearn.preprocessing._data.MinMaxScaler.__doc__ + __doc__ = ( + third_party.bigframes_vendored.sklearn.preprocessing._data.MinMaxScaler.__doc__ + ) def __init__(self): self._bqml_model: Optional[core.BqmlModel] = None self._bqml_model_factory = globals.bqml_model_factory() self._base_sql_generator = globals.base_sql_generator() - def _keys(self): - return (self._bqml_model,) + # TODO(garrettwu): implement __hash__ + def __eq__(self, other: Any) -> bool: + return type(other) is MinMaxScaler and self._bqml_model == other._bqml_model - def _compile_to_sql( - self, X: bpd.DataFrame, columns: Optional[Iterable[str]] = None - ) -> List[str]: + def _compile_to_sql(self, columns: List[str], X=None) -> List[Tuple[str, str]]: """Compile this transformer to a list of SQL expressions that can be included in a BQML TRANSFORM clause Args: - X: DataFrame to transform. - columns: transform columns. If None, transform all columns in X. + columns: + a list of column names to transform. + X (default None): + Ignored. - Returns: a list of tuples sql_expr.""" - if columns is None: - columns = X.columns - columns, _ = core_utils.get_standardized_ids(columns) + Returns: a list of tuples of (sql_expression, output_name)""" return [ - self._base_sql_generator.ml_min_max_scaler( - column, f"min_max_scaled_{column}" + ( + self._base_sql_generator.ml_min_max_scaler( + column, f"min_max_scaled_{column}" + ), + f"min_max_scaled_{column}", ) for column in columns ] @@ -234,32 +241,34 @@ def _parse_from_sql(cls, sql: str) -> tuple[MinMaxScaler, str]: Returns: tuple(MinMaxScaler, column_label)""" - # TODO: Use real sql parser col_label = sql[sql.find("(") + 1 : sql.find(")")] - return cls(), _unescape_id(col_label) + return cls(), col_label def fit( self, - X: utils.ArrayType, + X: Union[bpd.DataFrame, bpd.Series], y=None, # ignored ) -> MinMaxScaler: - (X,) = utils.batch_convert_to_dataframe(X) + (X,) = utils.convert_to_dataframe(X) + + compiled_transforms = self._compile_to_sql(X.columns.tolist()) + transform_sqls = [transform_sql for transform_sql, _ in compiled_transforms] - transform_sqls = self._compile_to_sql(X) self._bqml_model = self._bqml_model_factory.create_model( X, options={"model_type": "transform_only"}, transforms=transform_sqls, ) - self._extract_output_names() + # The schema of TRANSFORM output is not available in the model API, so save it during fitting + self._output_names = [name for _, name in compiled_transforms] return self - def transform(self, X: utils.ArrayType) -> bpd.DataFrame: + def transform(self, X: Union[bpd.DataFrame, bpd.Series]) -> bpd.DataFrame: if not self._bqml_model: raise RuntimeError("Must be fitted before transform") - (X,) = utils.batch_convert_to_dataframe(X, session=self._bqml_model.session) + (X,) = utils.convert_to_dataframe(X) df = self._bqml_model.transform(X) return typing.cast( @@ -268,18 +277,23 @@ def transform(self, X: utils.ArrayType) -> bpd.DataFrame: ) -@log_adapter.class_logger class KBinsDiscretizer( base.Transformer, - bigframes_vendored.sklearn.preprocessing._discretization.KBinsDiscretizer, + third_party.bigframes_vendored.sklearn.preprocessing._discretization.KBinsDiscretizer, ): - __doc__ = bigframes_vendored.sklearn.preprocessing._discretization.KBinsDiscretizer.__doc__ + __doc__ = ( + third_party.bigframes_vendored.sklearn.preprocessing._discretization.KBinsDiscretizer.__doc__ + ) def __init__( self, n_bins: int = 5, strategy: Literal["uniform", "quantile"] = "quantile", ): + if strategy != "uniform": + raise NotImplementedError( + f"Only strategy = 'uniform' is supported now, input is {strategy}." + ) if n_bins < 2: raise ValueError( f"n_bins has to be larger than or equal to 2, input is {n_bins}." @@ -290,97 +304,89 @@ def __init__( self._bqml_model_factory = globals.bqml_model_factory() self._base_sql_generator = globals.base_sql_generator() - def _keys(self): - return (self._bqml_model, self.n_bins, self.strategy) + # TODO(garrettwu): implement __hash__ + def __eq__(self, other: Any) -> bool: + return ( + type(other) is KBinsDiscretizer + and self.n_bins == other.n_bins + and self._bqml_model == other._bqml_model + ) def _compile_to_sql( - self, X: bpd.DataFrame, columns: Optional[Iterable[str]] = None - ) -> List[str]: + self, + columns: List[str], + X: bpd.DataFrame, + ) -> List[Tuple[str, str]]: """Compile this transformer to a list of SQL expressions that can be included in a BQML TRANSFORM clause Args: - X: DataFrame to transform. - columns: transform columns. If None, transform all columns in X. + columns: + a list of column names to transform + X: + The Dataframe with training data. - Returns: a list of tuples sql_expr.""" - if columns is None: - columns = X.columns - columns, _ = core_utils.get_standardized_ids(columns) + Returns: a list of tuples of (sql_expression, output_name)""" array_split_points = {} if self.strategy == "uniform": for column in columns: min_value = X[column].min() max_value = X[column].max() - bin_size = (max_value - min_value) / self.n_bins array_split_points[column] = [ min_value + i * bin_size for i in range(self.n_bins - 1) ] - return [ + return [ + ( self._base_sql_generator.ml_bucketize( column, array_split_points[column], f"kbinsdiscretizer_{column}" - ) - for column in columns - ] - - elif self.strategy == "quantile": - return [ - self._base_sql_generator.ml_quantile_bucketize( - column, self.n_bins, f"kbinsdiscretizer_{column}" - ) - for column in columns - ] - - else: - raise ValueError( - f"strategy should be set 'quantile' or 'uniform', but your input is {self.strategy}." + ), + f"kbinsdiscretizer_{column}", ) + for column in columns + ] @classmethod def _parse_from_sql(cls, sql: str) -> tuple[KBinsDiscretizer, str]: """Parse SQL to tuple(KBinsDiscretizer, column_label). Args: - sql: SQL string of format "ML.BUCKETIZE({col_label}, array_split_points, FALSE)" - or ML.QUANTILE_BUCKETIZE({col_label}, num_bucket) OVER()" + sql: SQL string of format "ML.BUCKETIZE({col_label}, array_split_points, FALSE) OVER()" Returns: tuple(KBinsDiscretizer, column_label)""" s = sql[sql.find("(") + 1 : sql.find(")")] + array_split_points = s[s.find("[") + 1 : s.find("]")] col_label = s[: s.find(",")] - - if sql.startswith("ML.QUANTILE_BUCKETIZE"): - num_bins = s.split(",")[1] - return cls(int(num_bins), "quantile"), _unescape_id(col_label) - else: - array_split_points = s[s.find("[") + 1 : s.find("]")] - n_bins = array_split_points.count(",") + 2 - return cls(n_bins, "uniform"), _unescape_id(col_label) + n_bins = array_split_points.count(",") + 2 + return cls(n_bins, "uniform"), col_label def fit( self, - X: utils.ArrayType, + X: Union[bpd.DataFrame, bpd.Series], y=None, # ignored ) -> KBinsDiscretizer: - (X,) = utils.batch_convert_to_dataframe(X) + (X,) = utils.convert_to_dataframe(X) + + compiled_transforms = self._compile_to_sql(X.columns.tolist(), X) + transform_sqls = [transform_sql for transform_sql, _ in compiled_transforms] - transform_sqls = self._compile_to_sql(X) self._bqml_model = self._bqml_model_factory.create_model( X, options={"model_type": "transform_only"}, transforms=transform_sqls, ) - self._extract_output_names() + # The schema of TRANSFORM output is not available in the model API, so save it during fitting + self._output_names = [name for _, name in compiled_transforms] return self - def transform(self, X: utils.ArrayType) -> bpd.DataFrame: + def transform(self, X: Union[bpd.DataFrame, bpd.Series]) -> bpd.DataFrame: if not self._bqml_model: raise RuntimeError("Must be fitted before transform") - (X,) = utils.batch_convert_to_dataframe(X, session=self._bqml_model.session) + (X,) = utils.convert_to_dataframe(X) df = self._bqml_model.transform(X) return typing.cast( @@ -389,16 +395,17 @@ def transform(self, X: utils.ArrayType) -> bpd.DataFrame: ) -@log_adapter.class_logger class OneHotEncoder( base.Transformer, - bigframes_vendored.sklearn.preprocessing._encoder.OneHotEncoder, + third_party.bigframes_vendored.sklearn.preprocessing._encoder.OneHotEncoder, ): # BQML max value https://cloud.google.com/bigquery/docs/reference/standard-sql/bigqueryml-syntax-one-hot-encoder#syntax TOP_K_DEFAULT = 1000000 FREQUENCY_THRESHOLD_DEFAULT = 0 - __doc__ = bigframes_vendored.sklearn.preprocessing._encoder.OneHotEncoder.__doc__ + __doc__ = ( + third_party.bigframes_vendored.sklearn.preprocessing._encoder.OneHotEncoder.__doc__ + ) # All estimators must implement __init__ to document their parameters, even # if they don't have any @@ -419,25 +426,30 @@ def __init__( self._bqml_model_factory = globals.bqml_model_factory() self._base_sql_generator = globals.base_sql_generator() - def _keys(self): - return (self._bqml_model, self.drop, self.min_frequency, self.max_categories) + # TODO(garrettwu): implement __hash__ + def __eq__(self, other: Any) -> bool: + return ( + type(other) is OneHotEncoder + and self._bqml_model == other._bqml_model + and self.drop == other.drop + and self.min_frequency == other.min_frequency + and self.max_categories == other.max_categories + ) - def _compile_to_sql( - self, X: bpd.DataFrame, columns: Optional[Iterable[str]] = None - ) -> List[str]: + def _compile_to_sql(self, columns: List[str], X=None) -> List[Tuple[str, str]]: """Compile this transformer to a list of SQL expressions that can be included in a BQML TRANSFORM clause Args: - X: DataFrame to transform. - columns: transform columns. If None, transform all columns in X. + columns: + a list of column names to transform. + X (default None): + Ignored. + + Returns: a list of tuples of (sql_expression, output_name)""" - Returns: a list of tuples sql_expr.""" - if columns is None: - columns = X.columns - columns, _ = core_utils.get_standardized_ids(columns) drop = self.drop if self.drop is not None else "none" - # minus one here since BQML's implementation always includes index 0, and top_k is on top of that. + # minus one here since BQML's inplimentation always includes index 0, and top_k is on top of that. top_k = ( (self.max_categories - 1) if self.max_categories is not None @@ -449,8 +461,11 @@ def _compile_to_sql( else OneHotEncoder.FREQUENCY_THRESHOLD_DEFAULT ) return [ - self._base_sql_generator.ml_one_hot_encoder( - column, drop, top_k, frequency_threshold, f"onehotencoded_{column}" + ( + self._base_sql_generator.ml_one_hot_encoder( + column, drop, top_k, frequency_threshold, f"onehotencoded_{column}" + ), + f"onehotencoded_{column}", ) for column in columns ] @@ -467,37 +482,40 @@ def _parse_from_sql(cls, sql: str) -> tuple[OneHotEncoder, str]: s = sql[sql.find("(") + 1 : sql.find(")")] col_label, drop_str, top_k, frequency_threshold = s.split(", ") drop = ( - typing.cast(Literal["most_frequent"], "most_frequent") + cast(Literal["most_frequent"], "most_frequent") if drop_str.lower() == "'most_frequent'" else None ) max_categories = int(top_k) + 1 min_frequency = int(frequency_threshold) - return cls(drop, min_frequency, max_categories), _unescape_id(col_label) + return cls(drop, min_frequency, max_categories), col_label def fit( self, - X: utils.ArrayType, + X: Union[bpd.DataFrame, bpd.Series], y=None, # ignored ) -> OneHotEncoder: - (X,) = utils.batch_convert_to_dataframe(X) + (X,) = utils.convert_to_dataframe(X) + + compiled_transforms = self._compile_to_sql(X.columns.tolist()) + transform_sqls = [transform_sql for transform_sql, _ in compiled_transforms] - transform_sqls = self._compile_to_sql(X) self._bqml_model = self._bqml_model_factory.create_model( X, options={"model_type": "transform_only"}, transforms=transform_sqls, ) - self._extract_output_names() + # The schema of TRANSFORM output is not available in the model API, so save it during fitting + self._output_names = [name for _, name in compiled_transforms] return self - def transform(self, X: utils.ArrayType) -> bpd.DataFrame: + def transform(self, X: Union[bpd.DataFrame, bpd.Series]) -> bpd.DataFrame: if not self._bqml_model: raise RuntimeError("Must be fitted before transform") - (X,) = utils.batch_convert_to_dataframe(X, session=self._bqml_model.session) + (X,) = utils.convert_to_dataframe(X) df = self._bqml_model.transform(X) return typing.cast( @@ -506,16 +524,17 @@ def transform(self, X: utils.ArrayType) -> bpd.DataFrame: ) -@log_adapter.class_logger class LabelEncoder( base.LabelTransformer, - bigframes_vendored.sklearn.preprocessing._label.LabelEncoder, + third_party.bigframes_vendored.sklearn.preprocessing._label.LabelEncoder, ): # BQML max value https://cloud.google.com/bigquery/docs/reference/standard-sql/bigqueryml-syntax-one-hot-encoder#syntax TOP_K_DEFAULT = 1000000 FREQUENCY_THRESHOLD_DEFAULT = 0 - __doc__ = bigframes_vendored.sklearn.preprocessing._label.LabelEncoder.__doc__ + __doc__ = ( + third_party.bigframes_vendored.sklearn.preprocessing._label.LabelEncoder.__doc__ + ) # All estimators must implement __init__ to document their parameters, even # if they don't have any @@ -534,23 +553,26 @@ def __init__( self._bqml_model_factory = globals.bqml_model_factory() self._base_sql_generator = globals.base_sql_generator() - def _keys(self): - return (self._bqml_model, self.min_frequency, self.max_categories) + # TODO(garrettwu): implement __hash__ + def __eq__(self, other: Any) -> bool: + return ( + type(other) is LabelEncoder + and self._bqml_model == other._bqml_model + and self.min_frequency == other.min_frequency + and self.max_categories == other.max_categories + ) - def _compile_to_sql( - self, X: bpd.DataFrame, columns: Optional[Iterable[str]] = None - ) -> List[str]: + def _compile_to_sql(self, columns: List[str], X=None) -> List[Tuple[str, str]]: """Compile this transformer to a list of SQL expressions that can be included in a BQML TRANSFORM clause Args: - X: DataFrame to transform. - columns: transform columns. If None, transform all columns in X. + columns: + a list of column names to transform. + X (default None): + Ignored. - Returns: a list of tuples sql_expr.""" - if columns is None: - columns = X.columns - columns, _ = core_utils.get_standardized_ids(columns) + Returns: a list of tuples of (sql_expression, output_name)""" # minus one here since BQML's inplimentation always includes index 0, and top_k is on top of that. top_k = ( @@ -564,8 +586,11 @@ def _compile_to_sql( else LabelEncoder.FREQUENCY_THRESHOLD_DEFAULT ) return [ - self._base_sql_generator.ml_label_encoder( - column, top_k, frequency_threshold, f"labelencoded_{column}" + ( + self._base_sql_generator.ml_label_encoder( + column, top_k, frequency_threshold, f"labelencoded_{column}" + ), + f"labelencoded_{column}", ) for column in columns ] @@ -584,137 +609,35 @@ def _parse_from_sql(cls, sql: str) -> tuple[LabelEncoder, str]: max_categories = int(top_k) + 1 min_frequency = int(frequency_threshold) - return cls(min_frequency, max_categories), _unescape_id(col_label) + return cls(min_frequency, max_categories), col_label def fit( self, - y: utils.ArrayType, + y: Union[bpd.DataFrame, bpd.Series], ) -> LabelEncoder: - (y,) = utils.batch_convert_to_dataframe(y) + (y,) = utils.convert_to_dataframe(y) + + compiled_transforms = self._compile_to_sql(y.columns.tolist()) + transform_sqls = [transform_sql for transform_sql, _ in compiled_transforms] - transform_sqls = self._compile_to_sql(y) self._bqml_model = self._bqml_model_factory.create_model( y, options={"model_type": "transform_only"}, transforms=transform_sqls, ) - self._extract_output_names() + # The schema of TRANSFORM output is not available in the model API, so save it during fitting + self._output_names = [name for _, name in compiled_transforms] return self - def transform(self, y: utils.ArrayType) -> bpd.DataFrame: + def transform(self, y: Union[bpd.DataFrame, bpd.Series]) -> bpd.DataFrame: if not self._bqml_model: raise RuntimeError("Must be fitted before transform") - (y,) = utils.batch_convert_to_dataframe(y, session=self._bqml_model.session) + (y,) = utils.convert_to_dataframe(y) df = self._bqml_model.transform(y) return typing.cast( bpd.DataFrame, df[self._output_names], ) - - -@log_adapter.class_logger -class PolynomialFeatures( - base.Transformer, - bigframes_vendored.sklearn.preprocessing._polynomial.PolynomialFeatures, -): - __doc__ = ( - bigframes_vendored.sklearn.preprocessing._polynomial.PolynomialFeatures.__doc__ - ) - - def __init__(self, degree: int = 2): - if degree not in range(1, 5): - raise ValueError(f"degree has to be [1, 4], input is {degree}.") - self.degree = degree - self._bqml_model: Optional[core.BqmlModel] = None - self._bqml_model_factory = globals.bqml_model_factory() - self._base_sql_generator = globals.base_sql_generator() - - def _keys(self): - return (self._bqml_model, self.degree) - - def _compile_to_sql( - self, X: bpd.DataFrame, columns: Optional[Iterable[str]] = None - ) -> List[str]: - """Compile this transformer to a list of SQL expressions that can be included in - a BQML TRANSFORM clause - - Args: - X: DataFrame to transform. - columns: transform columns. If None, transform all columns in X. - - Returns: a list of tuples sql_expr.""" - if columns is None: - columns = X.columns - columns, _ = core_utils.get_standardized_ids(columns) - output_name = "poly_feat" - return [ - self._base_sql_generator.ml_polynomial_expand( - columns, self.degree, output_name - ) - ] - - @classmethod - def _parse_from_sql(cls, sql: str) -> tuple[PolynomialFeatures, tuple[str, ...]]: - """Parse SQL to tuple(PolynomialFeatures, column_labels). - - Args: - sql: SQL string of format "ML.POLYNOMIAL_EXPAND(STRUCT(col_label0, col_label1, ...), degree)" - - Returns: - tuple(MaxAbsScaler, column_label)""" - col_labels = sql[sql.find("STRUCT(") + 7 : sql.find(")")].split(",") - col_labels = [label.strip() for label in col_labels] - degree = int(sql[sql.rfind(",") + 1 : sql.rfind(")")]) - return cls(degree), tuple(map(_unescape_id, col_labels)) - - def fit( - self, - X: utils.ArrayType, - y=None, # ignored - ) -> PolynomialFeatures: - (X,) = utils.batch_convert_to_dataframe(X) - - transform_sqls = self._compile_to_sql(X) - self._bqml_model = self._bqml_model_factory.create_model( - X, - options={"model_type": "transform_only"}, - transforms=transform_sqls, - ) - - self._extract_output_names() - - return self - - def transform(self, X: utils.ArrayType) -> bpd.DataFrame: - if not self._bqml_model: - raise RuntimeError("Must be fitted before transform") - - (X,) = utils.batch_convert_to_dataframe(X, session=self._bqml_model.session) - - df = self._bqml_model.transform(X) - return typing.cast( - bpd.DataFrame, - df[self._output_names], - ) - - -def _unescape_id(id: str) -> str: - """Very simple conversion to removed ` characters from ids. - - A proper sql parser should be used instead. - """ - return id.removeprefix("`").removesuffix("`") - - -PreprocessingType = Union[ - OneHotEncoder, - StandardScaler, - MaxAbsScaler, - MinMaxScaler, - KBinsDiscretizer, - LabelEncoder, - PolynomialFeatures, -] diff --git a/bigframes/ml/remote.py b/bigframes/ml/remote.py deleted file mode 100644 index f53ea645e92..00000000000 --- a/bigframes/ml/remote.py +++ /dev/null @@ -1,125 +0,0 @@ -# Copyright 2023 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. - -"""BigFrames general remote models.""" - -from __future__ import annotations - -import warnings -from typing import Mapping, Optional - -import bigframes.dataframe -import bigframes.exceptions as bfe -import bigframes.session -from bigframes.core import global_session -from bigframes.core.logging import log_adapter -from bigframes.ml import base, core, globals, utils - -_REMOTE_MODEL_STATUS = "remote_model_status" - - -@log_adapter.class_logger -class VertexAIModel(base.BaseEstimator): - """Remote model from a Vertex AI HTTPS endpoint. User must specify HTTPS endpoint, input schema and output schema. - For more information, see Deploy model on Vertex AI: https://cloud.google.com/bigquery/docs/bigquery-ml-remote-model-tutorial#Deploy-Model-on-Vertex-AI. - - Args: - endpoint (str): - Vertex AI HTTPS endpoint. - input (Mapping): - Input schema: `{column_name: column_type}`. Supported types are "bool", "string", "int64", "float64", "array", "array", "array", "array". - output (Mapping): - Output label schema: `{column_name: column_type}`. Supported the same types as the input. - session (bigframes.Session or None): - BQ session to create the model. If None, use the global default session. - connection_name (str or None): - Connection to connect with remote service. str of the format ... - If None, use default connection in session context. BigQuery DataFrame will try to create the connection and attach - permission if the connection isn't fully set up. - """ - - def __init__( - self, - endpoint: str, - input: Mapping[str, str], - output: Mapping[str, str], - *, - session: Optional[bigframes.session.Session] = None, - connection_name: Optional[str] = None, - ): - self.endpoint = endpoint - self.input = input - self.output = output - self.session = session or global_session.get_global_session() - - self._bq_connection_manager = self.session.bqconnectionmanager - self.connection_name = connection_name - - self._bqml_model_factory = globals.bqml_model_factory() - self._bqml_model: core.BqmlModel = self._create_bqml_model() - - def _create_bqml_model(self): - # Parse and create connection if needed. - self.connection_name = self.session._create_bq_connection( - connection=self.connection_name, iam_role="aiplatform.user" - ) - - options = { - "endpoint": self.endpoint, - } - - self.input = { - k: utils.standardize_type(v, globals._REMOTE_MODEL_SUPPORTED_DTYPES) - for k, v in self.input.items() - } - self.output = { - k: utils.standardize_type(v, globals._REMOTE_MODEL_SUPPORTED_DTYPES) - for k, v in self.output.items() - } - - return self._bqml_model_factory.create_remote_model( - session=self.session, - connection_name=self.connection_name, - input=self.input, - output=self.output, - options=options, - ) - - def predict( - self, - X: utils.ArrayType, - ) -> bigframes.dataframe.DataFrame: - """Predict the result from the input DataFrame. - - Args: - X (bigframes.pandas.DataFrame or bigframes.pandas.Series or pandas.DataFrame or pandas.Series): - Input DataFrame or Series, which needs to comply with the input parameter of the model. - - Returns: - bigframes.pandas.DataFrame: DataFrame of shape (n_samples, n_input_columns + n_prediction_columns). Returns predicted values. - """ - - (X,) = utils.batch_convert_to_dataframe(X, session=self._bqml_model.session) - - df = self._bqml_model.predict(X) - - # unlike LLM models, the general remote model status is null for successful runs. - if (df[_REMOTE_MODEL_STATUS].notna()).any(): - msg = bfe.format_message( - f"Some predictions failed. Check column {_REMOTE_MODEL_STATUS} for " - "detailed status. You may want to filter the failed rows and retry." - ) - warnings.warn(msg, category=RuntimeWarning) - - return df diff --git a/bigframes/ml/sql.py b/bigframes/ml/sql.py index 894fc44b1b3..601b2710999 100644 --- a/bigframes/ml/sql.py +++ b/bigframes/ml/sql.py @@ -16,90 +16,53 @@ Generates SQL queries needed for BigQuery DataFrames ML """ -from typing import Iterable, Literal, Mapping, Optional, Union +from typing import Iterable, Mapping, Optional, Union -import bigframes_vendored.constants as constants -import google.cloud.bigquery +import bigframes.constants as constants +import bigframes.pandas as bpd -from bigframes.core.compile.sqlglot import sql as sg_sql -INDENT_STR = " " - - -# TODO: Add proper escaping logic from core/compile module class BaseSqlGenerator: """Generate base SQL strings for ML. Model name isn't needed in this class.""" # General methods def encode_value(self, v: Union[str, int, float, Iterable[str]]) -> str: """Encode a parameter value for SQL""" - if isinstance(v, (str, int, float)): - return sg_sql.to_sql(sg_sql.literal(v)) + if isinstance(v, str): + return f'"{v}"' + elif isinstance(v, int) or isinstance(v, float): + return f"{v}" elif isinstance(v, Iterable): inner = ", ".join([self.encode_value(x) for x in v]) return f"[{inner}]" else: - raise ValueError( - f"Unexpected value type {type(v)}. {constants.FEEDBACK_LINK}" - ) + raise ValueError(f"Unexpected value type. {constants.FEEDBACK_LINK}") def build_parameters(self, **kwargs: Union[str, int, float, Iterable[str]]) -> str: """Encode a dict of values into a formatted Iterable of key-value pairs for SQL""" + indent_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) + 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: + def build_structs(self, **kwargs: Union[int, float]) -> str: """Encode a dict of values into a formatted STRUCT items for SQL""" - param_strs = [] - for k, v in kwargs.items(): - v_trans = self.build_schema(**v) if isinstance(v, Mapping) else v - - param_strs.append( - f"{sg_sql.to_sql(sg_sql.literal(v_trans))} AS {sg_sql.to_sql(sg_sql.identifier(k))}" - ) - - return "\n" + INDENT_STR + f",\n{INDENT_STR}".join(param_strs) + indent_str = " " + param_strs = [f"{v} AS {k}" for k, v in kwargs.items()] + return "\n" + indent_str + f",\n{indent_str}".join(param_strs) def build_expressions(self, *expr_sqls: str) -> str: """Encode a Iterable of SQL expressions into a formatted Iterable for SQL""" - return "\n" + INDENT_STR + f",\n{INDENT_STR}".join(expr_sqls) - - def build_schema(self, **kwargs: str) -> str: - """Encode a dict of values into a formatted schema type items for SQL""" - param_strs = [ - f"{sg_sql.to_sql(sg_sql.identifier(k))} {v}" for k, v in kwargs.items() - ] - return "\n" + INDENT_STR + f",\n{INDENT_STR}".join(param_strs) + indent_str = " " + return "\n" + indent_str + f",\n{indent_str}".join(expr_sqls) def options(self, **kwargs: Union[str, int, float, Iterable[str]]) -> str: """Encode the OPTIONS clause for BQML""" return f"OPTIONS({self.build_parameters(**kwargs)})" - def struct_options(self, **kwargs: Union[int, float, Mapping]) -> str: + def struct_options(self, **kwargs: Union[int, float]) -> str: """Encode a BQ STRUCT as options.""" return f"STRUCT({self.build_structs(**kwargs)})" - def struct_columns(self, columns: Iterable[str]) -> str: - """Encode a BQ Table columns to a STRUCT.""" - columns_str = ", ".join( - map(lambda x: sg_sql.to_sql(sg_sql.identifier(x)), columns) - ) - return f"STRUCT({columns_str})" - - def input(self, **kwargs: str) -> str: - """Encode a BQML INPUT clause.""" - return f"INPUT({self.build_schema(**kwargs)})" - - def output(self, **kwargs: str) -> str: - """Encode a BQML OUTPUT clause.""" - return f"OUTPUT({self.build_schema(**kwargs)})" - # Connection def connection(self, conn_name: str) -> str: """Encode the REMOTE WITH CONNECTION clause for BQML. conn_name is of the format ...""" @@ -112,47 +75,24 @@ def transform(self, *expr_sqls: str) -> str: def ml_standard_scaler(self, numeric_expr_sql: str, name: str) -> str: """Encode ML.STANDARD_SCALER for BQML""" - return f"""ML.STANDARD_SCALER({sg_sql.to_sql(sg_sql.identifier(numeric_expr_sql))}) OVER() AS {sg_sql.to_sql(sg_sql.identifier(name))}""" + return f"""ML.STANDARD_SCALER({numeric_expr_sql}) OVER() AS {name}""" def ml_max_abs_scaler(self, numeric_expr_sql: str, name: str) -> str: """Encode ML.MAX_ABS_SCALER for BQML""" - return f"""ML.MAX_ABS_SCALER({sg_sql.to_sql(sg_sql.identifier(numeric_expr_sql))}) OVER() AS {sg_sql.to_sql(sg_sql.identifier(name))}""" + return f"""ML.MAX_ABS_SCALER({numeric_expr_sql}) OVER() AS {name}""" def ml_min_max_scaler(self, numeric_expr_sql: str, name: str) -> str: """Encode ML.MIN_MAX_SCALER for BQML""" - return f"""ML.MIN_MAX_SCALER({sg_sql.to_sql(sg_sql.identifier(numeric_expr_sql))}) OVER() AS {sg_sql.to_sql(sg_sql.identifier(name))}""" - - def ml_imputer( - self, - col_name: str, - strategy: str, - name: str, - ) -> str: - """Encode ML.IMPUTER for BQML""" - return f"""ML.IMPUTER({sg_sql.to_sql(sg_sql.identifier(col_name))}, '{strategy}') OVER() AS {sg_sql.to_sql(sg_sql.identifier(name))}""" + return f"""ML.MIN_MAX_SCALER({numeric_expr_sql}) OVER() AS {name}""" def ml_bucketize( - self, - input_id: str, - array_split_points: Iterable[Union[int, float]], - output_id: str, - ) -> str: - """Encode ML.BUCKETIZE for BQML""" - # Use Python value rather than Numpy value to serialization. - points = [ - point.item() if hasattr(point, "item") else point - for point in array_split_points - ] - return f"""ML.BUCKETIZE({sg_sql.to_sql(sg_sql.identifier(input_id))}, {points}, FALSE) AS {sg_sql.to_sql(sg_sql.identifier(output_id))}""" - - def ml_quantile_bucketize( self, numeric_expr_sql: str, - num_bucket: int, + array_split_points: Iterable[Union[int, float]], name: str, ) -> str: - """Encode ML.QUANTILE_BUCKETIZE for BQML""" - return f"""ML.QUANTILE_BUCKETIZE({sg_sql.to_sql(sg_sql.identifier(numeric_expr_sql))}, {num_bucket}) OVER() AS {sg_sql.to_sql(sg_sql.identifier(name))}""" + """Encode ML.MIN_MAX_SCALER for BQML""" + return f"""ML.BUCKETIZE({numeric_expr_sql}, {array_split_points}, FALSE) AS {name}""" def ml_one_hot_encoder( self, @@ -163,9 +103,8 @@ def ml_one_hot_encoder( name: str, ) -> str: """Encode ML.ONE_HOT_ENCODER for BQML. - https://cloud.google.com/bigquery/docs/reference/standard-sql/bigqueryml-syntax-one-hot-encoder for params. - """ - return f"""ML.ONE_HOT_ENCODER({sg_sql.to_sql(sg_sql.identifier(numeric_expr_sql))}, '{drop}', {top_k}, {frequency_threshold}) OVER() AS {sg_sql.to_sql(sg_sql.identifier(name))}""" + https://cloud.google.com/bigquery/docs/reference/standard-sql/bigqueryml-syntax-one-hot-encoder for params.""" + return f"""ML.ONE_HOT_ENCODER({numeric_expr_sql}, '{drop}', {top_k}, {frequency_threshold}) OVER() AS {name}""" def ml_label_encoder( self, @@ -175,143 +114,73 @@ def ml_label_encoder( name: str, ) -> str: """Encode ML.LABEL_ENCODER for BQML. - https://cloud.google.com/bigquery/docs/reference/standard-sql/bigqueryml-syntax-label-encoder for params. - """ - return f"""ML.LABEL_ENCODER({sg_sql.to_sql(sg_sql.identifier(numeric_expr_sql))}, {top_k}, {frequency_threshold}) OVER() AS {sg_sql.to_sql(sg_sql.identifier(name))}""" - - def ml_polynomial_expand( - self, columns: Iterable[str], degree: int, name: str - ) -> str: - """Encode ML.POLYNOMIAL_EXPAND. - https://cloud.google.com/bigquery/docs/reference/standard-sql/bigqueryml-syntax-polynomial-expand - """ - return f"""ML.POLYNOMIAL_EXPAND({self.struct_columns(columns)}, {degree}) AS {sg_sql.to_sql(sg_sql.identifier(name))}""" - - def ml_distance( - self, - col_x: str, - col_y: str, - type: Literal["EUCLIDEAN", "MANHATTAN", "COSINE"], - source_sql: str, - name: str, - ) -> str: - """Encode ML.DISTANCE for BQML. - https://cloud.google.com/bigquery/docs/reference/standard-sql/bigqueryml-syntax-distance - """ - return f"""SELECT *, ML.DISTANCE({sg_sql.to_sql(sg_sql.identifier(col_x))}, {sg_sql.to_sql(sg_sql.identifier(col_y))}, '{type}') AS {sg_sql.to_sql(sg_sql.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})""" + https://cloud.google.com/bigquery/docs/reference/standard-sql/bigqueryml-syntax-label-encoder for params.""" + return f"""ML.LABEL_ENCODER({numeric_expr_sql}, {top_k}, {frequency_threshold}) OVER() AS {name}""" class ModelCreationSqlGenerator(BaseSqlGenerator): """Sql generator for creating a model entity. Model id is the standalone id without project id and dataset id.""" - def _model_id_sql( - self, - model_ref: google.cloud.bigquery.ModelReference, - ): - return f"{sg_sql.to_sql(sg_sql.identifier(model_ref.project))}.{sg_sql.to_sql(sg_sql.identifier(model_ref.dataset_id))}.{sg_sql.to_sql(sg_sql.identifier(model_ref.model_id))}" + def __init__(self, model_id: str): + self._model_id = model_id # Model create and alter def create_model( self, - source_sql: str, - model_ref: google.cloud.bigquery.ModelReference, + source_df: bpd.DataFrame, options: Mapping[str, Union[str, int, float, Iterable[str]]] = {}, transforms: Optional[Iterable[str]] = None, ) -> str: - """Encode the CREATE OR REPLACE MODEL statement for BQML""" - parts = [f"CREATE OR REPLACE MODEL {self._model_id_sql(model_ref)}"] - if transforms: - parts.append(self.transform(*transforms)) - if options: - parts.append(self.options(**options)) - parts.append(f"AS {source_sql}") - return "\n".join(parts) + """Encode the CREATE TEMP MODEL statement for BQML""" + source_sql = source_df.sql + transform_sql = self.transform(*transforms) if transforms is not None else None + options_sql = self.options(**options) - def create_llm_remote_model( - self, - source_sql: str, - connection_name: str, - model_ref: google.cloud.bigquery.ModelReference, - options: Mapping[str, Union[str, int, float, Iterable[str]]] = {}, - ) -> str: - """Encode the CREATE OR REPLACE MODEL statement for BQML""" - parts = [f"CREATE OR REPLACE MODEL {self._model_id_sql(model_ref)}"] - parts.append(self.connection(connection_name)) - if options: - parts.append(self.options(**options)) + parts = [f"CREATE TEMP MODEL `{self._model_id}`"] + if transform_sql: + parts.append(transform_sql) + if options_sql: + parts.append(options_sql) parts.append(f"AS {source_sql}") return "\n".join(parts) def create_remote_model( self, connection_name: str, - model_ref: google.cloud.bigquery.ModelReference, - input: Mapping[str, str] = {}, - output: Mapping[str, str] = {}, options: Mapping[str, Union[str, int, float, Iterable[str]]] = {}, ) -> str: - """Encode the CREATE OR REPLACE MODEL statement for BQML remote model.""" - parts = [f"CREATE OR REPLACE MODEL {self._model_id_sql(model_ref)}"] - if input: - parts.append(self.input(**input)) - if output: - parts.append(self.output(**output)) + """Encode the CREATE TEMP MODEL statement for BQML remote model.""" + options_sql = self.options(**options) + + parts = [f"CREATE TEMP MODEL `{self._model_id}`"] parts.append(self.connection(connection_name)) - if options: - parts.append(self.options(**options)) + if options_sql: + parts.append(options_sql) return "\n".join(parts) def create_imported_model( self, - model_ref: google.cloud.bigquery.ModelReference, options: Mapping[str, Union[str, int, float, Iterable[str]]] = {}, ) -> str: - """Encode the CREATE OR REPLACE MODEL statement for BQML remote model.""" - - parts = [f"CREATE OR REPLACE MODEL {self._model_id_sql(model_ref)}"] - if options: - parts.append(self.options(**options)) - return "\n".join(parts) + """Encode the CREATE TEMP MODEL statement for BQML remote model.""" + options_sql = self.options(**options) - def create_xgboost_imported_model( - self, - model_ref: google.cloud.bigquery.ModelReference, - input: Mapping[str, str] = {}, - output: Mapping[str, str] = {}, - options: Mapping[str, Union[str, int, float, Iterable[str]]] = {}, - ) -> str: - """Encode the CREATE OR REPLACE MODEL statement for BQML remote model.""" - - parts = [f"CREATE OR REPLACE MODEL {self._model_id_sql(model_ref)}"] - if input: - parts.append(self.input(**input)) - if output: - parts.append(self.output(**output)) - if options: - parts.append(self.options(**options)) + parts = [f"CREATE TEMP MODEL `{self._model_id}`"] + if options_sql: + parts.append(options_sql) return "\n".join(parts) class ModelManipulationSqlGenerator(BaseSqlGenerator): """Sql generator for manipulating a model entity. Model name is the full model path of project_id.dataset_id.model_id.""" - def __init__(self, model_ref: google.cloud.bigquery.ModelReference): - self._model_ref = model_ref + def __init__(self, model_name: str): + self._model_name = model_name - def _model_ref_sql(self) -> str: - return f"{sg_sql.to_sql(sg_sql.identifier(self._model_ref.project))}.{sg_sql.to_sql(sg_sql.identifier(self._model_ref.dataset_id))}.{sg_sql.to_sql(sg_sql.identifier(self._model_ref.model_id))}" + def _source_sql(self, source_df: bpd.DataFrame) -> str: + """Return DataFrame sql with index columns.""" + _source_sql, _, _ = source_df._to_sql_query(include_index=True) + return _source_sql # Alter model def alter_model( @@ -321,125 +190,67 @@ def alter_model( """Encode the ALTER MODEL statement for BQML""" options_sql = self.options(**options) - parts = [f"ALTER MODEL {self._model_ref_sql()}"] + parts = [f"ALTER MODEL `{self._model_name}`"] parts.append(f"SET {options_sql}") return "\n".join(parts) # ML prediction TVFs - def ml_recommend(self, source_sql: str) -> str: - """Encode ML.RECOMMEND for BQML""" - return f"""SELECT * FROM ML.RECOMMEND(MODEL {self._model_ref_sql()}, - ({source_sql}))""" - - def ml_predict(self, source_sql: str) -> str: + def ml_predict(self, source_df: bpd.DataFrame) -> str: """Encode ML.PREDICT for BQML""" - return f"""SELECT * FROM ML.PREDICT(MODEL {self._model_ref_sql()}, - ({source_sql}))""" + return f"""SELECT * FROM ML.PREDICT(MODEL `{self._model_name}`, + ({self._source_sql(source_df)}))""" - def ml_explain_predict( - self, source_sql: str, struct_options: Mapping[str, Union[int, float]] - ) -> str: - """Encode ML.EXPLAIN_PREDICT for BQML""" - struct_options_sql = self.struct_options(**struct_options) - return f"""SELECT * FROM ML.EXPLAIN_PREDICT(MODEL {self._model_ref_sql()}, - ({source_sql}), {struct_options_sql})""" - - def ml_global_explain(self, struct_options) -> str: - """Encode ML.GLOBAL_EXPLAIN for BQML""" - struct_options_sql = self.struct_options(**struct_options) - return f"""SELECT * FROM ML.GLOBAL_EXPLAIN(MODEL {self._model_ref_sql()}, - {struct_options_sql})""" - - def ml_forecast(self, struct_options: Mapping[str, Union[int, float]]) -> str: + def ml_forecast(self) -> str: """Encode ML.FORECAST for BQML""" - struct_options_sql = self.struct_options(**struct_options) - return f"""SELECT * FROM ML.FORECAST(MODEL {self._model_ref_sql()}, - {struct_options_sql})""" - - def ml_explain_forecast( - self, struct_options: Mapping[str, Union[int, float]] - ) -> str: - """Encode ML.EXPLAIN_FORECAST for BQML""" - struct_options_sql = self.struct_options(**struct_options) - return f"""SELECT * FROM ML.EXPLAIN_FORECAST(MODEL {self._model_ref_sql()}, - {struct_options_sql})""" + return f"""SELECT * FROM ML.FORECAST(MODEL `{self._model_name}`)""" def ml_generate_text( - self, source_sql: str, struct_options: Mapping[str, Union[int, float]] + self, source_df: bpd.DataFrame, struct_options: Mapping[str, Union[int, float]] ) -> str: """Encode ML.GENERATE_TEXT for BQML""" struct_options_sql = self.struct_options(**struct_options) - return f"""SELECT * FROM ML.GENERATE_TEXT(MODEL {self._model_ref_sql()}, - ({source_sql}), {struct_options_sql})""" + return f"""SELECT * FROM ML.GENERATE_TEXT(MODEL `{self._model_name}`, + ({self._source_sql(source_df)}), {struct_options_sql})""" - def ml_generate_embedding( - self, source_sql: str, struct_options: Mapping[str, Union[int, float]] + def ml_generate_text_embedding( + self, source_df: bpd.DataFrame, struct_options: Mapping[str, Union[int, float]] ) -> str: - """Encode ML.GENERATE_EMBEDDING for BQML""" + """Encode ML.GENERATE_TEXT_EMBEDDING for BQML""" struct_options_sql = self.struct_options(**struct_options) - return f"""SELECT * FROM ML.GENERATE_EMBEDDING(MODEL {self._model_ref_sql()}, - ({source_sql}), {struct_options_sql})""" - - def ml_detect_anomalies( - self, source_sql: str, struct_options: Mapping[str, Union[int, float]] - ) -> str: - """Encode ML.DETECT_ANOMALIES for BQML""" - struct_options_sql = self.struct_options(**struct_options) - return f"""SELECT * FROM ML.DETECT_ANOMALIES(MODEL {self._model_ref_sql()}, - {struct_options_sql}, ({source_sql}))""" + return f"""SELECT * FROM ML.GENERATE_TEXT_EMBEDDING(MODEL `{self._model_name}`, + ({self._source_sql(source_df)}), {struct_options_sql})""" # ML evaluation TVFs - def ml_evaluate(self, source_sql: Optional[str] = None) -> str: + def ml_evaluate(self, source_df: Optional[bpd.DataFrame] = None) -> str: """Encode ML.EVALUATE for BQML""" + if source_df is None: + source_sql = None + else: + # Note: don't need index as evaluate returns a new table + source_sql, _, _ = source_df._to_sql_query(include_index=False) + if source_sql is None: - return f"""SELECT * FROM ML.EVALUATE(MODEL {self._model_ref_sql()})""" + return f"""SELECT * FROM ML.EVALUATE(MODEL `{self._model_name}`)""" else: - return f"""SELECT * FROM ML.EVALUATE(MODEL {self._model_ref_sql()}, + return f"""SELECT * FROM ML.EVALUATE(MODEL `{self._model_name}`, ({source_sql}))""" - def ml_arima_coefficients(self) -> str: - """Encode ML.ARIMA_COEFFICIENTS for BQML""" - return f"""SELECT * FROM ML.ARIMA_COEFFICIENTS(MODEL {self._model_ref_sql()})""" - - # ML evaluation TVFs - def ml_llm_evaluate(self, source_sql: str, task_type: Optional[str] = None) -> str: - """Encode ML.EVALUATE for BQML""" - # Note: don't need index as evaluate returns a new table - return f"""SELECT * FROM ML.EVALUATE(MODEL {self._model_ref_sql()}, - ({source_sql}), STRUCT("{task_type}" AS task_type))""" - - # ML evaluation TVFs - def ml_arima_evaluate(self, show_all_candidate_models: bool = False) -> str: - """Encode ML.ARMIA_EVALUATE for BQML""" - return f"""SELECT * FROM ML.ARIMA_EVALUATE(MODEL {self._model_ref_sql()}, - STRUCT({show_all_candidate_models} AS show_all_candidate_models))""" - def ml_centroids(self) -> str: """Encode ML.CENTROIDS for BQML""" - return f"""SELECT * FROM ML.CENTROIDS(MODEL {self._model_ref_sql()})""" + return f"""SELECT * FROM ML.CENTROIDS(MODEL `{self._model_name}`)""" def ml_principal_components(self) -> str: """Encode ML.PRINCIPAL_COMPONENTS for BQML""" - return ( - f"""SELECT * FROM ML.PRINCIPAL_COMPONENTS(MODEL {self._model_ref_sql()})""" - ) + return f"""SELECT * FROM ML.PRINCIPAL_COMPONENTS(MODEL `{self._model_name}`)""" def ml_principal_component_info(self) -> str: """Encode ML.PRINCIPAL_COMPONENT_INFO for BQML""" - return f"""SELECT * FROM ML.PRINCIPAL_COMPONENT_INFO(MODEL {self._model_ref_sql()})""" + return ( + f"""SELECT * FROM ML.PRINCIPAL_COMPONENT_INFO(MODEL `{self._model_name}`)""" + ) # ML transform TVF, that require a transform_only type model - def ml_transform(self, source_sql: str) -> str: + def ml_transform(self, source_df: bpd.DataFrame) -> str: """Encode ML.TRANSFORM for BQML""" - return f"""SELECT * FROM ML.TRANSFORM(MODEL {self._model_ref_sql()}, - ({source_sql}))""" - - def ai_generate_table( - self, - source_sql: str, - struct_options: Mapping[str, Union[int, float, bool, Mapping]], - ) -> str: - """Encode AI.GENERATE_TABLE for BQML""" - struct_options_sql = self.struct_options(**struct_options) - return f"""SELECT * FROM AI.GENERATE_TABLE(MODEL {self._model_ref_sql()}, - ({source_sql}), {struct_options_sql})""" + return f"""SELECT * FROM ML.TRANSFORM(MODEL `{self._model_name}`, + ({self._source_sql(source_df)}))""" diff --git a/bigframes/ml/utils.py b/bigframes/ml/utils.py index 134020b7167..299282d3337 100644 --- a/bigframes/ml/utils.py +++ b/bigframes/ml/utils.py @@ -13,233 +13,46 @@ # limitations under the License. import typing -from typing import ( - Any, - Generator, - Hashable, - Iterable, - Literal, - Mapping, - Optional, - Tuple, - Union, -) - -import bigframes_vendored.constants as constants -import pandas as pd -from google.cloud import bigquery +from typing import Iterable, Union +import bigframes.constants as constants +from bigframes.core import blocks import bigframes.pandas as bpd -from bigframes.core import convert, guid -from bigframes.session import Session # Internal type alias -ArrayType = Union[bpd.DataFrame, bpd.Series, pd.DataFrame, pd.Series] -BigFramesArrayType = Union[bpd.DataFrame, bpd.Series] - +ArrayType = Union[bpd.DataFrame, bpd.Series] -def batch_convert_to_dataframe( - *input: ArrayType, - session: Optional[Session] = None, -) -> Generator[bpd.DataFrame, None, None]: - """Converts the input to BigFrames DataFrame. - Args: - session: - The session to convert local pandas instances to BigFrames counter-parts. - It is not used if the input itself is already a BigFrame data frame or series. +def convert_to_dataframe(*input: ArrayType) -> Iterable[bpd.DataFrame]: + return (_convert_to_dataframe(frame) for frame in input) - """ - _validate_sessions(*input, session=session) - return ( - convert.to_bf_dataframe(frame, default_index=None, session=session) - for frame in input +def _convert_to_dataframe(frame: ArrayType) -> bpd.DataFrame: + if isinstance(frame, bpd.DataFrame): + return frame + if isinstance(frame, bpd.Series): + return frame.to_frame() + raise ValueError( + f"Unsupported type {type(frame)} to convert to DataFrame. {constants.FEEDBACK_LINK}" ) -def batch_convert_to_series( - *input: ArrayType, session: Optional[Session] = None -) -> Generator[bpd.Series, None, None]: - """Converts the input to BigFrames Series. - - Args: - session: - The session to convert local pandas instances to BigFrames counter-parts. - It is not used if the input itself is already a BigFrame data frame or series. - - """ - _validate_sessions(*input, session=session) - - return ( - convert.to_bf_series( - _get_only_column(frame), default_index=None, session=session - ) - for frame in input - ) - - -def batch_convert_to_bf_equivalent( - *input: ArrayType, session: Optional[Session] = None -) -> Generator[Union[bpd.DataFrame, bpd.Series], None, None]: - """Converts the input to BigFrames DataFrame or Series. - - Args: - session: - The session to convert local pandas instances to BigFrames counter-parts. - It is not used if the input itself is already a BigFrame data frame or series. - - """ - _validate_sessions(*input, session=session) - - for frame in input: - if isinstance(frame, bpd.DataFrame) or isinstance(frame, pd.DataFrame): - yield convert.to_bf_dataframe(frame, default_index=None, session=session) - elif isinstance(frame, bpd.Series) or isinstance(frame, pd.Series): - yield convert.to_bf_series( - _get_only_column(frame), default_index=None, session=session - ) - else: - raise ValueError(f"Unsupported type: {type(frame)}") - - -def _validate_sessions(*input: ArrayType, session: Optional[Session]): - session_ids = set( - i._session.session_id - for i in input - if isinstance(i, bpd.DataFrame) or isinstance(i, bpd.Series) - ) - if len(session_ids) > 1: - raise ValueError("Cannot convert data from multiple sessions") - - -def _get_only_column(input: ArrayType) -> Union[pd.Series, bpd.Series]: - if isinstance(input, pd.Series) or isinstance(input, bpd.Series): - return input - - if len(input.columns) != 1: - raise ValueError( - "To convert into Series, DataFrames can only contain one column. " - f"Try input with only one column. {constants.FEEDBACK_LINK}" - ) - - label = typing.cast(Hashable, input.columns.tolist()[0]) - if isinstance(input, pd.DataFrame): - return typing.cast(pd.Series, input[label]) - return typing.cast(bpd.Series, input[label]) # type: ignore - - -def parse_model_endpoint(model_endpoint: str) -> tuple[str, Optional[str]]: - """Parse model endpoint string to model_name and version.""" - model_name = model_endpoint - version = None - - if model_endpoint.startswith("multimodalembedding"): - return model_name, version - - at_idx = model_endpoint.find("@") - if at_idx != -1: - version = model_endpoint[at_idx + 1 :] - model_name = model_endpoint[:at_idx] - - return model_name, version - +def convert_to_series(*input: ArrayType) -> Iterable[bpd.Series]: + return (_convert_to_series(frame) for frame in input) -def _resolve_param_type(t: type) -> type: - def is_optional(t): - return typing.get_origin(t) is Union and type(None) in typing.get_args(t) - # Optional[type] to type - if is_optional(t): - union_set = set(typing.get_args(t)) - union_set.remove(type(None)) - t = Union[tuple(union_set)] # type: ignore - - # Literal[value0, value1...] to type(value0) - if typing.get_origin(t) is Literal: - return type(typing.get_args(t)[0]) - - return t - - -def retrieve_params_from_bq_model( - cls, bq_model: bigquery.Model, params_mapping: Mapping[str, str] -) -> dict[str, Any]: - """Retrieve parameters of class constructor from BQ model. params_mapping specifies the names mapping param_name -> bqml_name. Params couldn't be found will be ignored.""" - kwargs = {} - - # See https://cloud.google.com/bigquery/docs/reference/rest/v2/models#trainingrun - last_fitting = bq_model.training_runs[-1]["trainingOptions"] - - for bf_param, bf_param_type in typing.get_type_hints(cls.__init__).items(): - bqml_param = params_mapping.get(bf_param) - if bqml_param in last_fitting: - bf_param_type = _resolve_param_type(bf_param_type) - kwargs[bf_param] = bf_param_type(last_fitting[bqml_param]) - - return kwargs - - -def combine_training_and_evaluation_data( - X_train: bpd.DataFrame, - y_train: bpd.DataFrame, - X_eval: bpd.DataFrame, - y_eval: bpd.DataFrame, - bqml_options: dict, -) -> Tuple[bpd.DataFrame, bpd.DataFrame, dict]: - """ - Combine training data and labels with evlauation data and labels, and keep - them differentiated through a split column in the combined data and labels. - """ - - assert X_train.columns.equals(X_eval.columns) - assert y_train.columns.equals(y_eval.columns) - - # create a custom split column for BQML and supply the evaluation - # data along with the training data in a combined single table - # https://cloud.google.com/bigquery/docs/reference/standard-sql/bigqueryml-syntax-create-dnn-models#data_split_col. - split_col = guid.generate_guid() - assert split_col not in X_train.columns - - # To prevent side effects on the input dataframes, we operate on copies - X_train = X_train.copy() - X_eval = X_eval.copy() - - X_train[split_col] = False - X_eval[split_col] = True - - # Rename y columns to avoid collision with X columns during join - y_mapping = {col: guid.generate_guid() + str(col) for col in y_train.columns} - y_train_renamed = y_train.rename(columns=y_mapping) - y_eval_renamed = y_eval.rename(columns=y_mapping) - - # Join X and y first to preserve row alignment - train_combined = X_train.join(y_train_renamed, how="outer") - eval_combined = X_eval.join(y_eval_renamed, how="outer") - - combined = bpd.concat([train_combined, eval_combined]) - - X = combined[X_train.columns] - y = combined[list(y_mapping.values())].rename( - columns={v: k for k, v in y_mapping.items()} - ) - - # create options copy to not mutate the incoming one - bqml_options = bqml_options.copy() - bqml_options["data_split_method"] = "CUSTOM" - bqml_options["data_split_col"] = split_col - - return X, y, bqml_options - - -def standardize_type(v: str, supported_dtypes: Optional[Iterable[str]] = None): - t = v.lower() - t = t.replace("boolean", "bool") - - if supported_dtypes: - if t not in supported_dtypes: +def _convert_to_series(frame: ArrayType) -> bpd.Series: + if isinstance(frame, bpd.DataFrame): + if len(frame.columns) != 1: raise ValueError( - f"Data type {v} is not supported. We only support {', '.join(supported_dtypes)}." + "To convert into Series, DataFrames can only contain one column. " + f"Try input with only one column. {constants.FEEDBACK_LINK}" ) - return t + label = typing.cast(blocks.Label, frame.columns.tolist()[0]) + return typing.cast(bpd.Series, frame[label]) + if isinstance(frame, bpd.Series): + return frame + raise ValueError( + f"Unsupported type {type(frame)} to convert to Series. {constants.FEEDBACK_LINK}" + ) diff --git a/bigframes/operations/__init__.py b/bigframes/operations/__init__.py index 6df8da69b11..a29dd36c72a 100644 --- a/bigframes/operations/__init__.py +++ b/bigframes/operations/__init__.py @@ -14,442 +14,1097 @@ from __future__ import annotations -from bigframes.operations.ai_ops import ( - AIClassify, - AIEmbed, - AIGenerate, - AIGenerateBool, - AIGenerateDouble, - AIGenerateInt, - AIIf, - AIScore, - AISimilarity, -) -from bigframes.operations.array_ops import ( - ArrayMapOp, - ArrayReduceOp, - ArraySliceOp, - ArrayToStringOp, - ToArrayOp, -) -from bigframes.operations.base_ops import ( - BinaryOp, - NaryOp, - RowOp, - ScalarOp, - TernaryOp, - UnaryOp, -) -from bigframes.operations.blob_ops import ( - ObjGetAccessUrl, - obj_fetch_metadata_op, - obj_make_ref_json_op, - obj_make_ref_op, -) -from bigframes.operations.bool_ops import and_op, or_op, xor_op -from bigframes.operations.comparison_ops import ( - eq_null_match_op, - eq_op, - ge_op, - gt_op, - le_op, - lt_op, - ne_op, -) -from bigframes.operations.date_ops import ( - date_diff_op, - day_op, - dayofweek_op, - dayofyear_op, - iso_day_op, - iso_week_op, - iso_year_op, - month_op, - quarter_op, - year_op, -) -from bigframes.operations.datetime_ops import ( - StrftimeOp, - ToDatetimeOp, - ToTimestampOp, - UnixMicros, - UnixMillis, - UnixSeconds, - date_op, - time_op, - timestamp_diff_op, -) -from bigframes.operations.distance_ops import ( - cosine_distance_op, - euclidean_distance_op, - manhattan_distance_op, -) -from bigframes.operations.frequency_ops import ( - DatetimeToIntegerLabelOp, - FloorDtOp, - IntegerLabelToDatetimeOp, -) -from bigframes.operations.generic_ops import ( - AsTypeOp, - CaseWhenOp, - CoerceToBoolOp, - DynamicGetItemOp, - GetItemOp, - IsInOp, - MapOp, - RowKey, - SqlScalarOp, - case_when_op, - clip_op, - coalesce_op, - coerce_to_bool_op, - fillna_op, - hash_op, - invert_op, - isnull_op, - maximum_op, - minimum_op, - notnull_op, - where_op, -) -from bigframes.operations.geo_ops import ( - GeoStBufferOp, - GeoStDistanceOp, - GeoStLengthOp, - GeoStRegionStatsOp, - GeoStSimplifyOp, - geo_st_astext_op, - geo_st_boundary_op, - geo_st_convexhull_op, - geo_st_difference_op, - geo_st_geogfromtext_op, - geo_st_geogpoint_op, - geo_st_intersection_op, - geo_st_isclosed_op, - geo_x_op, - geo_y_op, -) -from bigframes.operations.googlesql import GoogleSqlScalarOp -from bigframes.operations.json_ops import ( - JSONDecode, - JSONExtract, - JSONExtractArray, - JSONExtractStringArray, - JSONKeys, - JSONQuery, - JSONQueryArray, - JSONSet, - JSONValue, - JSONValueArray, - ParseJSON, - ToJSON, - ToJSONString, -) -from bigframes.operations.numeric_ops import ( - AddOp, - DivOp, - FloorDivOp, - MulOp, - SubOp, - abs_op, - add_op, - arccos_op, - arccosh_op, - arcsin_op, - arcsinh_op, - arctan2_op, - arctan_op, - arctanh_op, - ceil_op, - cos_op, - cosh_op, - div_op, - exp_op, - expm1_op, - floor_op, - floordiv_op, - ln_op, - log1p_op, - log10_op, - mod_op, - mul_op, - neg_op, - pos_op, - pow_op, - round_op, - sin_op, - sinh_op, - sqrt_op, - sub_op, - tan_op, - tanh_op, - unsafe_pow_op, -) -from bigframes.operations.numpy_op_maps import NUMPY_TO_BINOP, NUMPY_TO_OP -from bigframes.operations.remote_function_ops import ( - PythonUdfOp, - RemoteFunctionOp, -) -from bigframes.operations.string_ops import ( - EndsWithOp, - RegexReplaceStrOp, - ReplaceStrOp, - StartsWithOp, - StrContainsOp, - StrContainsRegexOp, - StrExtractOp, - StrFindOp, - StringSplitOp, - StrLstripOp, - StrPadOp, - StrRepeatOp, - StrRstripOp, - StrSliceOp, - StrStripOp, - ZfillOp, - capitalize_op, - isalnum_op, - isalpha_op, - isdecimal_op, - isdigit_op, - islower_op, - isnumeric_op, - isspace_op, - isupper_op, - len_op, - lower_op, - reverse_op, - strconcat_op, - upper_op, -) -from bigframes.operations.struct_ops import StructFieldOp, StructOp -from bigframes.operations.time_ops import hour_op, minute_op, normalize_op, second_op -from bigframes.operations.timedelta_ops import ( - ToTimedeltaOp, - date_add_op, - date_sub_op, - timedelta_floor_op, - timestamp_add_op, - timestamp_sub_op, -) -from bigframes.operations.to_op import func_to_expr - -__all__ = [ - # Base ops - "RowOp", - "NaryOp", - "UnaryOp", - "BinaryOp", - "TernaryOp", - "ScalarOp", - # Generic ops - "AsTypeOp", - "case_when_op", - "CaseWhenOp", - "clip_op", - "coalesce_op", - "fillna_op", - "DynamicGetItemOp", - "GetItemOp", - "hash_op", - "invert_op", - "IsInOp", - "isnull_op", - "MapOp", - "maximum_op", - "minimum_op", - "notnull_op", - "CoerceToBoolOp", - "coerce_to_bool_op", - "RowKey", - "SqlScalarOp", - "where_op", - # String ops - "capitalize_op", - "EndsWithOp", - "isalnum_op", - "isalpha_op", - "isdecimal_op", - "isdigit_op", - "islower_op", - "isnumeric_op", - "isspace_op", - "isupper_op", - "len_op", - "lower_op", - "RegexReplaceStrOp", - "ReplaceStrOp", - "reverse_op", - "StartsWithOp", - "strconcat_op", - "StrContainsOp", - "StrContainsRegexOp", - "StrExtractOp", - "StrFindOp", - "StrLstripOp", - "StringSplitOp", - "strip_op", - "StrPadOp", - "StrRepeatOp", - "StrRstripOp", - "StrSliceOp", - "StrStripOp", - "upper_op", - "ZfillOp", - # Date ops - "date_diff_op", - "day_op", - "dayofweek_op", - "dayofyear_op", - "iso_day_op", - "iso_week_op", - "iso_year_op", - "month_op", - "quarter_op", - "year_op", - # Time ops - "hour_op", - "minute_op", - "second_op", - "normalize_op", - # Timedelta ops - "date_add_op", - "date_sub_op", - "timedelta_floor_op", - "timestamp_add_op", - "timestamp_sub_op", - "ToTimedeltaOp", - # Datetime ops - "date_op", - "time_op", - "timestamp_diff_op", - "ToDatetimeOp", - "ToTimestampOp", - "StrftimeOp", - "UnixMicros", - "UnixMillis", - "UnixSeconds", - # Numeric ops - "abs_op", - "add_op", - "AddOp", - "arccos_op", - "arccosh_op", - "arcsin_op", - "arcsinh_op", - "arctan2_op", - "arctan_op", - "arctanh_op", - "ceil_op", - "cos_op", - "cosh_op", - "div_op", - "DivOp", - "exp_op", - "expm1_op", - "floor_op", - "floordiv_op", - "FloorDivOp", - "ln_op", - "log1p_op", - "log10_op", - "mod_op", - "mul_op", - "MulOp", - "neg_op", - "pos_op", - "pow_op", - "round_op", - "sin_op", - "sinh_op", - "sqrt_op", - "sub_op", - "SubOp", - "tan_op", - "tanh_op", - "unsafe_pow_op", - # Array ops - "ArraySliceOp", - "ArrayToStringOp", - # Blob ops - "ObjGetAccessUrl", - "obj_make_ref_json_op", - "obj_make_ref_op", - "obj_fetch_metadata_op", - # Struct ops - "StructFieldOp", - "StructOp", - # Remote Functions ops - "RemoteFunctionOp", - "PythonUdfOp", - # Frequency ops - "DatetimeToIntegerLabelOp", - "FloorDtOp", - "IntegerLabelToDatetimeOp", - # JSON ops - "JSONDecode", - "JSONExtract", - "JSONExtractArray", - "JSONExtractStringArray", - "JSONKeys", - "JSONQuery", - "JSONQueryArray", - "JSONSet", - "JSONValue", - "JSONValueArray", - "ParseJSON", - "ToJSON", - "ToJSONString", - # Bool ops - "and_op", - "or_op", - "xor_op", - # Comparison ops - "eq_null_match_op", - "eq_op", - "ge_op", - "gt_op", - "le_op", - "lt_op", - "ne_op", - # Distance ops - "cosine_distance_op", - "euclidean_distance_op", - "manhattan_distance_op", - # Geo ops - "geo_st_boundary_op", - "geo_st_convexhull_op", - "geo_st_difference_op", - "geo_st_astext_op", - "geo_st_geogfromtext_op", - "geo_st_geogpoint_op", - "geo_st_intersection_op", - "geo_st_isclosed_op", - "geo_x_op", - "geo_y_op", - "GeoStBufferOp", - "GeoStDistanceOp", - "GeoStLengthOp", - "GeoStRegionStatsOp", - "GeoStSimplifyOp", - # AI ops - "AIClassify", - "AIGenerate", - "AIGenerateBool", - "AIGenerateDouble", - "AIGenerateInt", - "AIEmbed", - "AIIf", - "AIScore", - "AISimilarity", - # Helper functions - "func_to_expr", - # Numpy ops mapping - "NUMPY_TO_BINOP", - "NUMPY_TO_OP", - "ToArrayOp", - "ArrayReduceOp", - "ArrayMapOp", - # GoogleSql - "GoogleSqlScalarOp", +import functools +import typing + +import ibis +import ibis.common.exceptions +import ibis.expr.datatypes as ibis_dtypes +import ibis.expr.operations.generic +import ibis.expr.types as ibis_types +import numpy as np +import pandas as pd + +import bigframes.constants as constants +import bigframes.dtypes +import bigframes.dtypes as dtypes + +_ZERO = typing.cast(ibis_types.NumericValue, ibis_types.literal(0)) +_NAN = typing.cast(ibis_types.NumericValue, ibis_types.literal(np.nan)) +_INF = typing.cast(ibis_types.NumericValue, ibis_types.literal(np.inf)) +_NEG_INF = typing.cast(ibis_types.NumericValue, ibis_types.literal(-np.inf)) + +# Approx Highest number you can pass in to EXP function and get a valid FLOAT64 result +# FLOAT64 has 11 exponent bits, so max values is about 2**(2**10) +# ln(2**(2**10)) == (2**10)*ln(2) ~= 709.78, so EXP(x) for x>709.78 will overflow. +_FLOAT64_EXP_BOUND = typing.cast(ibis_types.NumericValue, ibis_types.literal(709.78)) +_INT64_EXP_BOUND = typing.cast(ibis_types.NumericValue, ibis_types.literal(43.6)) + +BinaryOp = typing.Callable[[ibis_types.Value, ibis_types.Value], ibis_types.Value] +TernaryOp = typing.Callable[ + [ibis_types.Value, ibis_types.Value, ibis_types.Value], ibis_types.Value ] + + +### Unary Ops +class UnaryOp: + def _as_ibis(self, x): + raise NotImplementedError( + f"Base class UnaryOp has no implementation. {constants.FEEDBACK_LINK}" + ) + + @property + def is_windowed(self): + return False + + +# Trig Functions +class AbsOp(UnaryOp): + def _as_ibis(self, x: ibis_types.Value): + return typing.cast(ibis_types.NumericValue, x).abs() + + +class SinOp(UnaryOp): + def _as_ibis(self, x: ibis_types.Value): + return typing.cast(ibis_types.NumericValue, x).sin() + + +class CosOp(UnaryOp): + def _as_ibis(self, x: ibis_types.Value): + return typing.cast(ibis_types.NumericValue, x).cos() + + +class TanOp(UnaryOp): + def _as_ibis(self, x: ibis_types.Value): + return typing.cast(ibis_types.NumericValue, x).tan() + + +# Inverse trig functions +class ArcsinOp(UnaryOp): + def _as_ibis(self, x: ibis_types.Value): + numeric_value = typing.cast(ibis_types.NumericValue, x) + domain = numeric_value.abs() <= _ibis_num(1) + return (~domain).ifelse(_NAN, numeric_value.asin()) + + +class ArccosOp(UnaryOp): + def _as_ibis(self, x: ibis_types.Value): + numeric_value = typing.cast(ibis_types.NumericValue, x) + domain = numeric_value.abs() <= _ibis_num(1) + return (~domain).ifelse(_NAN, numeric_value.acos()) + + +class ArctanOp(UnaryOp): + def _as_ibis(self, x: ibis_types.Value): + return typing.cast(ibis_types.NumericValue, x).atan() + + +# Hyperbolic trig functions +# BQ has these functions, but Ibis doesn't +class SinhOp(UnaryOp): + def _as_ibis(self, x: ibis_types.Value): + numeric_value = typing.cast(ibis_types.NumericValue, x) + sinh_result = ( + numeric_value.exp() - (numeric_value.negate()).exp() + ) / _ibis_num(2) + domain = numeric_value.abs() < _FLOAT64_EXP_BOUND + return (~domain).ifelse(_INF * numeric_value.sign(), sinh_result) + + +class CoshOp(UnaryOp): + def _as_ibis(self, x: ibis_types.Value): + numeric_value = typing.cast(ibis_types.NumericValue, x) + cosh_result = ( + numeric_value.exp() + (numeric_value.negate()).exp() + ) / _ibis_num(2) + domain = numeric_value.abs() < _FLOAT64_EXP_BOUND + return (~domain).ifelse(_INF, cosh_result) + + +class TanhOp(UnaryOp): + def _as_ibis(self, x: ibis_types.Value): + numeric_value = typing.cast(ibis_types.NumericValue, x) + tanh_result = (numeric_value.exp() - (numeric_value.negate()).exp()) / ( + numeric_value.exp() + (numeric_value.negate()).exp() + ) + # Beyond +-20, is effectively just the sign function + domain = numeric_value.abs() < _ibis_num(20) + return (~domain).ifelse(numeric_value.sign(), tanh_result) + + +class ArcsinhOp(UnaryOp): + def _as_ibis(self, x: ibis_types.Value): + numeric_value = typing.cast(ibis_types.NumericValue, x) + sqrt_part = ((numeric_value * numeric_value) + _ibis_num(1)).sqrt() + return (numeric_value.abs() + sqrt_part).ln() * numeric_value.sign() + + +class ArccoshOp(UnaryOp): + def _as_ibis(self, x: ibis_types.Value): + numeric_value = typing.cast(ibis_types.NumericValue, x) + sqrt_part = ((numeric_value * numeric_value) - _ibis_num(1)).sqrt() + acosh_result = (numeric_value + sqrt_part).ln() + domain = numeric_value >= _ibis_num(1) + return (~domain).ifelse(_NAN, acosh_result) + + +class ArctanhOp(UnaryOp): + def _as_ibis(self, x: ibis_types.Value): + numeric_value = typing.cast(ibis_types.NumericValue, x) + domain = numeric_value.abs() < _ibis_num(1) + numerator = numeric_value + _ibis_num(1) + denominator = _ibis_num(1) - numeric_value + ln_input = typing.cast(ibis_types.NumericValue, numerator.div(denominator)) + atanh_result = ln_input.ln().div(2) + + out_of_domain = (numeric_value.abs() == _ibis_num(1)).ifelse( + _INF * numeric_value, _NAN + ) + + return (~domain).ifelse(out_of_domain, atanh_result) + + +class SqrtOp(UnaryOp): + def _as_ibis(self, x: ibis_types.Value): + numeric_value = typing.cast(ibis_types.NumericValue, x) + domain = numeric_value >= _ZERO + return (~domain).ifelse(_NAN, numeric_value.sqrt()) + + +class Log10Op(UnaryOp): + def _as_ibis(self, x: ibis_types.Value): + numeric_value = typing.cast(ibis_types.NumericValue, x) + domain = numeric_value > _ZERO + out_of_domain = (numeric_value == _ZERO).ifelse(_NEG_INF, _NAN) + return (~domain).ifelse(out_of_domain, numeric_value.log10()) + + +class LnOp(UnaryOp): + def _as_ibis(self, x: ibis_types.Value): + numeric_value = typing.cast(ibis_types.NumericValue, x) + domain = numeric_value > _ZERO + out_of_domain = (numeric_value == _ZERO).ifelse(_NEG_INF, _NAN) + return (~domain).ifelse(out_of_domain, numeric_value.ln()) + + +class ExpOp(UnaryOp): + def _as_ibis(self, x: ibis_types.Value): + numeric_value = typing.cast(ibis_types.NumericValue, x) + domain = numeric_value < _FLOAT64_EXP_BOUND + return (~domain).ifelse(_INF, numeric_value.exp()) + + +class InvertOp(UnaryOp): + def _as_ibis(self, x: ibis_types.Value): + return typing.cast(ibis_types.NumericValue, x).negate() + + +class IsNullOp(UnaryOp): + def _as_ibis(self, x: ibis_types.Value): + return x.isnull() + + +class LenOp(UnaryOp): + def _as_ibis(self, x: ibis_types.Value): + return typing.cast(ibis_types.StringValue, x).length().cast(ibis_dtypes.int64) + + +class NotNullOp(UnaryOp): + def _as_ibis(self, x: ibis_types.Value): + return x.notnull() + + +class HashOp(UnaryOp): + def _as_ibis(self, x: ibis_types.Value): + return typing.cast(ibis_types.IntegerValue, x).hash() + + +## String Operation +class ReverseOp(UnaryOp): + def _as_ibis(self, x: ibis_types.Value): + return typing.cast(ibis_types.StringValue, x).reverse() + + +class LowerOp(UnaryOp): + def _as_ibis(self, x: ibis_types.Value): + return typing.cast(ibis_types.StringValue, x).lower() + + +class UpperOp(UnaryOp): + def _as_ibis(self, x: ibis_types.Value): + return typing.cast(ibis_types.StringValue, x).upper() + + +class StripOp(UnaryOp): + def _as_ibis(self, x: ibis_types.Value): + return typing.cast(ibis_types.StringValue, x).strip() + + +class IsNumericOp(UnaryOp): + def _as_ibis(self, x: ibis_types.Value): + # catches all members of the Unicode number class, which matches pandas isnumeric + # see https://cloud.google.com/bigquery/docs/reference/standard-sql/string_functions#regexp_contains + # TODO: Validate correctness, my miss eg ⅕ character + return typing.cast(ibis_types.StringValue, x).re_search(r"^(\pN+)$") + + +class IsAlphaOp(UnaryOp): + def _as_ibis(self, x: ibis_types.Value): + return typing.cast(ibis_types.StringValue, x).re_search( + r"^(\p{Lm}|\p{Lt}|\p{Lu}|\p{Ll}|\p{Lo})+$" + ) + + +class IsDigitOp(UnaryOp): + def _as_ibis(self, x: ibis_types.Value): + # Based on docs, should include superscript/subscript-ed numbers + # Tests however pass only when set to Nd unicode class + return typing.cast(ibis_types.StringValue, x).re_search(r"^(\p{Nd})+$") + + +class IsDecimalOp(UnaryOp): + def _as_ibis(self, x: ibis_types.Value): + return typing.cast(ibis_types.StringValue, x).re_search(r"^(\p{Nd})+$") + + +class IsAlnumOp(UnaryOp): + def _as_ibis(self, x: ibis_types.Value): + return typing.cast(ibis_types.StringValue, x).re_search( + r"^(\p{N}|\p{Lm}|\p{Lt}|\p{Lu}|\p{Ll}|\p{Lo})+$" + ) + + +class IsSpaceOp(UnaryOp): + def _as_ibis(self, x: ibis_types.Value): + # All characters are whitespace characters, False for empty string + return typing.cast(ibis_types.StringValue, x).re_search(r"^\s+$") + + +class IsLowerOp(UnaryOp): + def _as_ibis(self, x: ibis_types.Value): + # No upper case characters, min one cased character + # See: https://docs.python.org/3/library/stdtypes.html#str + return typing.cast(ibis_types.StringValue, x).re_search( + r"\p{Ll}" + ) & ~typing.cast(ibis_types.StringValue, x).re_search(r"\p{Lu}|\p{Lt}") + + +class IsUpperOp(UnaryOp): + def _as_ibis(self, x: ibis_types.Value): + # No lower case characters, min one cased character + # See: https://docs.python.org/3/library/stdtypes.html#str + return typing.cast(ibis_types.StringValue, x).re_search( + r"\p{Lu}" + ) & ~typing.cast(ibis_types.StringValue, x).re_search(r"\p{Ll}|\p{Lt}") + + +class RstripOp(UnaryOp): + def _as_ibis(self, x: ibis_types.Value): + return typing.cast(ibis_types.StringValue, x).rstrip() + + +class LstripOp(UnaryOp): + def _as_ibis(self, x: ibis_types.Value): + return typing.cast(ibis_types.StringValue, x).lstrip() + + +class CapitalizeOp(UnaryOp): + def _as_ibis(self, x: ibis_types.Value): + return typing.cast(ibis_types.StringValue, x).capitalize() + + +class ContainsStringOp(UnaryOp): + def __init__(self, pat: str, case: bool = True): + self._pat = pat + + def _as_ibis(self, x: ibis_types.Value): + return typing.cast(ibis_types.StringValue, x).contains(self._pat) + + +class ContainsRegexOp(UnaryOp): + def __init__(self, pat: str): + self._pat = pat + + def _as_ibis(self, x: ibis_types.Value): + return typing.cast(ibis_types.StringValue, x).re_search(self._pat) + + +class StrGetOp(UnaryOp): + def __init__(self, i: int): + self._i = i + + def _as_ibis(self, x: ibis_types.Value): + substr = typing.cast( + ibis_types.StringValue, typing.cast(ibis_types.StringValue, x)[self._i] + ) + return substr.nullif(ibis_types.literal("")) + + +class StrPadOp(UnaryOp): + def __init__( + self, length: int, fillchar: str, side: typing.Literal["both", "left", "right"] + ): + self._length = length + self._fillchar = fillchar + self._side = side + + def _as_ibis(self, x: ibis_types.Value): + str_val = typing.cast(ibis_types.StringValue, x) + + # SQL pad operations will truncate, we do not want to truncate though. + pad_length = ibis.greatest(str_val.length(), self._length) + if self._side == "left": + return str_val.lpad(pad_length, self._fillchar) + elif self._side == "right": + return str_val.rpad(pad_length, self._fillchar) + else: # side == both + # Pad more on right side if can't pad both sides equally + lpad_amount = ((pad_length - str_val.length()) // 2) + str_val.length() + return str_val.lpad(lpad_amount, self._fillchar).rpad( + pad_length, self._fillchar + ) + + +class ReplaceStringOp(UnaryOp): + def __init__(self, pat: str, repl: str): + self._pat = pat + self._repl = repl + + def _as_ibis(self, x: ibis_types.Value): + pat_str_value = typing.cast( + ibis_types.StringValue, ibis_types.literal(self._pat) + ) + repl_str_value = typing.cast( + ibis_types.StringValue, ibis_types.literal(self._pat) + ) + + return typing.cast(ibis_types.StringValue, x).replace( + pat_str_value, repl_str_value + ) + + +class ReplaceRegexOp(UnaryOp): + def __init__(self, pat: str, repl: str): + self._pat = pat + self._repl = repl + + def _as_ibis(self, x: ibis_types.Value): + return typing.cast(ibis_types.StringValue, x).re_replace(self._pat, self._repl) + + +class StartsWithOp(UnaryOp): + def __init__(self, pat: typing.Sequence[str]): + self._pat = pat + + def _as_ibis(self, x: ibis_types.Value): + any_match = None + for pat in self._pat: + pat_match = typing.cast(ibis_types.StringValue, x).startswith(pat) + if any_match is not None: + any_match = any_match | pat_match + else: + any_match = pat_match + return any_match if any_match is not None else ibis_types.literal(False) + + +class EndsWithOp(UnaryOp): + def __init__(self, pat: typing.Sequence[str]): + self._pat = pat + + def _as_ibis(self, x: ibis_types.Value): + any_match = None + for pat in self._pat: + pat_match = typing.cast(ibis_types.StringValue, x).endswith(pat) + if any_match is not None: + any_match = any_match | pat_match + else: + any_match = pat_match + return any_match if any_match is not None else ibis_types.literal(False) + + +class ZfillOp(UnaryOp): + def __init__(self, width: int): + self._width = width + + def _as_ibis(self, x: ibis_types.Value): + str_value = typing.cast(ibis_types.StringValue, x) + return ( + ibis.case() + .when( + str_value[0] == "-", + "-" + + StrPadOp(self._width - 1, "0", "left")._as_ibis(str_value.substr(1)), + ) + .else_(StrPadOp(self._width, "0", "left")._as_ibis(str_value)) + .end() + ) + + +## Datetime Ops +class DayOp(UnaryOp): + def _as_ibis(self, x: ibis_types.Value): + return typing.cast(ibis_types.TimestampValue, x).day().cast(ibis_dtypes.int64) + + +class DateOp(UnaryOp): + def _as_ibis(self, x: ibis_types.Value): + return typing.cast(ibis_types.TimestampValue, x).date() + + +class DayofweekOp(UnaryOp): + def _as_ibis(self, x: ibis_types.Value): + return ( + typing.cast(ibis_types.TimestampValue, x) + .day_of_week.index() + .cast(ibis_dtypes.int64) + ) + + +class HourOp(UnaryOp): + def _as_ibis(self, x: ibis_types.Value): + return typing.cast(ibis_types.TimestampValue, x).hour().cast(ibis_dtypes.int64) + + +class MinuteOp(UnaryOp): + def _as_ibis(self, x: ibis_types.Value): + return ( + typing.cast(ibis_types.TimestampValue, x).minute().cast(ibis_dtypes.int64) + ) + + +class MonthOp(UnaryOp): + def _as_ibis(self, x: ibis_types.Value): + return typing.cast(ibis_types.TimestampValue, x).month().cast(ibis_dtypes.int64) + + +class QuarterOp(UnaryOp): + def _as_ibis(self, x: ibis_types.Value): + return ( + typing.cast(ibis_types.TimestampValue, x).quarter().cast(ibis_dtypes.int64) + ) + + +class SecondOp(UnaryOp): + def _as_ibis(self, x: ibis_types.Value): + return ( + typing.cast(ibis_types.TimestampValue, x).second().cast(ibis_dtypes.int64) + ) + + +class TimeOp(UnaryOp): + def _as_ibis(self, x: ibis_types.Value): + return typing.cast(ibis_types.TimestampValue, x).time() + + +class YearOp(UnaryOp): + def _as_ibis(self, x: ibis_types.Value): + return typing.cast(ibis_types.TimestampValue, x).year().cast(ibis_dtypes.int64) + + +# Parameterized ops +class AsTypeOp(UnaryOp): + def __init__(self, to_type: dtypes.DtypeString | dtypes.Dtype): + self.to_type = bigframes.dtypes.bigframes_dtype_to_ibis_dtype(to_type) + + def _as_ibis(self, x: ibis_types.Value): + if isinstance(x, ibis_types.NullScalar): + return ibis_types.null().cast(self.to_type) + + return bigframes.dtypes.cast_ibis_value(x, self.to_type) + + +class FindOp(UnaryOp): + def __init__(self, sub, start, end): + self._sub = sub + self._start = start + self._end = end + + def _as_ibis(self, x: ibis_types.Value): + return typing.cast(ibis_types.StringValue, x).find( + self._sub, self._start, self._end + ) + + +class ExtractOp(UnaryOp): + def __init__(self, pat: str, n: int = 1): + self._pat = pat + self._n = n + + def _as_ibis(self, x: ibis_types.Value): + return typing.cast(ibis_types.StringValue, x).re_extract(self._pat, self._n) + + +class SliceOp(UnaryOp): + def __init__(self, start, stop): + self._start = start + self._stop = stop + + def _as_ibis(self, x: ibis_types.Value): + return typing.cast(ibis_types.StringValue, x)[self._start : self._stop] + + +class IsInOp(UnaryOp): + def __init__(self, values, match_nulls: bool = True): + self._values = values + self._match_nulls = match_nulls + + def _as_ibis(self, x: ibis_types.Value): + contains_nulls = any(is_null(value) for value in self._values) + matchable_ibis_values = [] + for item in self._values: + if not is_null(item): + try: + # we want values that *could* be cast to the dtype, but we don't want + # to actually cast it, as that could be lossy (eg float -> int) + item_inferred_type = ibis.literal(item).type() + if ( + x.type() == item_inferred_type + or x.type().is_numeric() + and item_inferred_type.is_numeric() + ): + matchable_ibis_values.append(item) + except TypeError: + pass + + if self._match_nulls and contains_nulls: + return x.isnull() | x.isin(matchable_ibis_values) + else: + return x.isin(matchable_ibis_values) + + +class BinopPartialRight(UnaryOp): + def __init__(self, binop: BinaryOp, right_scalar: typing.Any): + self._binop = binop + self._right = dtypes.literal_to_ibis_scalar(right_scalar, validate=False) + + def _as_ibis(self, x): + return self._binop(x, self._right) + + +class BinopPartialLeft(UnaryOp): + def __init__(self, binop: BinaryOp, left_scalar: typing.Any): + self._binop = binop + self._left = dtypes.literal_to_ibis_scalar(left_scalar, validate=False) + + def _as_ibis(self, x): + return self._binop(self._left, x) + + +class RepeatOp(UnaryOp): + def __init__(self, repeats): + self._repeats = repeats + + def _as_ibis(self, x: ibis_types.Value): + return typing.cast(ibis_types.StringValue, x).repeat(self._repeats) + + +class RemoteFunctionOp(UnaryOp): + def __init__(self, func: typing.Callable, apply_on_null=True): + if not hasattr(func, "bigframes_remote_function"): + raise TypeError( + f"only a bigframes remote function is supported as a callable. {constants.FEEDBACK_LINK}" + ) + + self._func = func + self._apply_on_null = apply_on_null + + def _as_ibis(self, x: ibis_types.Value): + x_transformed = self._func(x) + if not self._apply_on_null: + x_transformed = where_op(x, x.isnull(), x_transformed) + return x_transformed + + +abs_op = AbsOp() +invert_op = InvertOp() +isnull_op = IsNullOp() +len_op = LenOp() +notnull_op = NotNullOp() +reverse_op = ReverseOp() +lower_op = LowerOp() +upper_op = UpperOp() +strip_op = StripOp() +isalnum_op = IsAlnumOp() +isalpha_op = IsAlphaOp() +isdecimal_op = IsDecimalOp() +isdigit_op = IsDigitOp() +isnumeric_op = IsNumericOp() +isspace_op = IsSpaceOp() +islower_op = IsLowerOp() +isupper_op = IsUpperOp() +rstrip_op = RstripOp() +lstrip_op = LstripOp() +hash_op = HashOp() +day_op = DayOp() +dayofweek_op = DayofweekOp() +date_op = DateOp() +hour_op = HourOp() +minute_op = MinuteOp() +month_op = MonthOp() +quarter_op = QuarterOp() +second_op = SecondOp() +time_op = TimeOp() +year_op = YearOp() +capitalize_op = CapitalizeOp() + +# Just parameterless unary ops for now +# TODO: Parameter mappings +NUMPY_TO_OP: typing.Final = { + np.sin: SinOp(), + np.cos: CosOp(), + np.tan: TanOp(), + np.arcsin: ArcsinOp(), + np.arccos: ArccosOp(), + np.arctan: ArctanOp(), + np.sinh: SinhOp(), + np.cosh: CoshOp(), + np.tanh: TanhOp(), + np.arcsinh: ArcsinhOp(), + np.arccosh: ArccoshOp(), + np.arctanh: ArctanhOp(), + np.exp: ExpOp(), + np.log: LnOp(), + np.log10: Log10Op(), + np.sqrt: SqrtOp(), + np.abs: AbsOp(), +} + + +### Binary Ops +def short_circuit_nulls(type_override: typing.Optional[ibis_dtypes.DataType] = None): + """Wraps a binary operator to generate nulls of the expected type if either input is a null scalar.""" + + def short_circuit_nulls_inner(binop): + @functools.wraps(binop) + def wrapped_binop(x: ibis_types.Value, y: ibis_types.Value): + if isinstance(x, ibis_types.NullScalar): + return ibis_types.null().cast(type_override or y.type()) + elif isinstance(y, ibis_types.NullScalar): + return ibis_types.null().cast(type_override or x.type()) + else: + return binop(x, y) + + return wrapped_binop + + return short_circuit_nulls_inner + + +def concat_op( + x: ibis_types.Value, + y: ibis_types.Value, +): + x_string = typing.cast(ibis_types.StringValue, x) + y_string = typing.cast(ibis_types.StringValue, y) + return x_string.concat(y_string) + + +def eq_op( + x: ibis_types.Value, + y: ibis_types.Value, +): + return x == y + + +def eq_nulls_match_op( + x: ibis_types.Value, + y: ibis_types.Value, +): + """Variant of eq_op where nulls match each other. Only use where dtypes are known to be same.""" + left = x.cast(ibis_dtypes.str).fillna(ibis_types.literal("$NULL_SENTINEL$")) + right = y.cast(ibis_dtypes.str).fillna(ibis_types.literal("$NULL_SENTINEL$")) + return left == right + + +def ne_op( + x: ibis_types.Value, + y: ibis_types.Value, +): + return x != y + + +def and_op( + x: ibis_types.Value, + y: ibis_types.Value, +): + return typing.cast(ibis_types.BooleanValue, x) & typing.cast( + ibis_types.BooleanValue, y + ) + + +def or_op( + x: ibis_types.Value, + y: ibis_types.Value, +): + return typing.cast(ibis_types.BooleanValue, x) | typing.cast( + ibis_types.BooleanValue, y + ) + + +@short_circuit_nulls() +def add_op( + x: ibis_types.Value, + y: ibis_types.Value, +): + if isinstance(x, ibis_types.NullScalar) or isinstance(x, ibis_types.NullScalar): + return + return typing.cast(ibis_types.NumericValue, x) + typing.cast( + ibis_types.NumericValue, y + ) + + +@short_circuit_nulls() +def sub_op( + x: ibis_types.Value, + y: ibis_types.Value, +): + return typing.cast(ibis_types.NumericValue, x) - typing.cast( + ibis_types.NumericValue, y + ) + + +@short_circuit_nulls() +def mul_op( + x: ibis_types.Value, + y: ibis_types.Value, +): + return typing.cast(ibis_types.NumericValue, x) * typing.cast( + ibis_types.NumericValue, y + ) + + +@short_circuit_nulls(ibis_dtypes.float) +def div_op( + x: ibis_types.Value, + y: ibis_types.Value, +): + return typing.cast(ibis_types.NumericValue, x) / typing.cast( + ibis_types.NumericValue, y + ) + + +@short_circuit_nulls(ibis_dtypes.float) +def pow_op( + x: ibis_types.Value, + y: ibis_types.Value, +): + if x.type().is_integer() and y.type().is_integer(): + return _int_pow_op(x, y) + else: + return _float_pow_op(x, y) + + +@short_circuit_nulls(ibis_dtypes.float) +def unsafe_pow_op( + x: ibis_types.Value, + y: ibis_types.Value, +): + """For internal use only - where domain and overflow checks are not needed.""" + return typing.cast(ibis_types.NumericValue, x) ** typing.cast( + ibis_types.NumericValue, y + ) + + +def _int_pow_op( + x: ibis_types.Value, + y: ibis_types.Value, +): + # Need to avoid any error cases - should produce NaN instead + # See: https://cloud.google.com/bigquery/docs/reference/standard-sql/mathematical_functions#pow + x_as_decimal = typing.cast( + ibis_types.NumericValue, + x.cast(ibis_dtypes.Decimal(precision=38, scale=9, nullable=True)), + ) + y_val = typing.cast(ibis_types.NumericValue, y) + + # BQ POW() function outputs FLOAT64, which can lose precision. + # Therefore, we do math in NUMERIC and cast back down after. + # Also, explicit bounds checks, pandas will silently overflow. + pow_result = x_as_decimal**y_val + overflow_cond = (pow_result > _ibis_num((2**63) - 1)) | ( + pow_result < _ibis_num(-(2**63)) + ) + + return ( + ibis.case() + .when((overflow_cond), ibis.null()) + .else_(pow_result.cast(ibis_dtypes.int64)) + .end() + ) + + +def _float_pow_op( + x: ibis_types.Value, + y: ibis_types.Value, +): + # Most conditions here seek to prevent calling BQ POW with inputs that would generate errors. + # See: https://cloud.google.com/bigquery/docs/reference/standard-sql/mathematical_functions#pow + x_val = typing.cast(ibis_types.NumericValue, x) + y_val = typing.cast(ibis_types.NumericValue, y) + + overflow_cond = (x_val != _ZERO) & ((y_val * x_val.abs().ln()) > _FLOAT64_EXP_BOUND) + + # Float64 lose integer precision beyond 2**53, beyond this insufficient precision to get parity + exp_too_big = y_val.abs() > _ibis_num(2**53) + # Treat very large exponents as +=INF + norm_exp = exp_too_big.ifelse(_INF * y_val.sign(), y_val) + + pow_result = x_val**norm_exp + + # This cast is dangerous, need to only excuted where y_val has been bounds-checked + # Ibis needs try_cast binding to bq safe_cast + exponent_is_whole = y_val.cast(ibis_dtypes.int64) == y_val + odd_exponent = (x_val < _ZERO) & ( + y_val.cast(ibis_dtypes.int64) % _ibis_num(2) == _ibis_num(1) + ) + infinite_base = x_val.abs() == _INF + + return ( + ibis.case() + # Might be able to do something more clever with x_val==0 case + .when(y_val == _ZERO, _ibis_num(1)) + .when( + x_val == _ibis_num(1), _ibis_num(1) + ) # Need to ignore exponent, even if it is NA + .when( + (x_val == _ZERO) & (y_val < _ZERO), _INF + ) # This case would error POW function in BQ + .when(infinite_base, pow_result) + .when( + exp_too_big, pow_result + ) # Bigquery can actually handle the +-inf cases gracefully + .when((x_val < _ZERO) & (~exponent_is_whole), _NAN) + .when( + overflow_cond, _INF * odd_exponent.ifelse(_ibis_num(-1), _ibis_num(1)) + ) # finite overflows would cause bq to error + .else_(pow_result) + .end() + ) + + +@short_circuit_nulls(ibis_dtypes.bool) +def lt_op( + x: ibis_types.Value, + y: ibis_types.Value, +): + return x < y + + +@short_circuit_nulls(ibis_dtypes.bool) +def le_op( + x: ibis_types.Value, + y: ibis_types.Value, +): + return x <= y + + +@short_circuit_nulls(ibis_dtypes.bool) +def gt_op( + x: ibis_types.Value, + y: ibis_types.Value, +): + return x > y + + +@short_circuit_nulls(ibis_dtypes.bool) +def ge_op( + x: ibis_types.Value, + y: ibis_types.Value, +): + return x >= y + + +def coalesce_op( + x: ibis_types.Value, + y: ibis_types.Value, +): + if x.name("name").equals(y.name("name")): + return x + else: + return ibis.coalesce(x, y) + + +@short_circuit_nulls(ibis_dtypes.int) +def floordiv_op( + x: ibis_types.Value, + y: ibis_types.Value, +): + x_numeric = typing.cast(ibis_types.NumericValue, x) + y_numeric = typing.cast(ibis_types.NumericValue, y) + floordiv_expr = x_numeric // y_numeric + + # DIV(N, 0) will error in bigquery, but needs to return 0 for int, and inf for float in BQ so we short-circuit in this case. + # Multiplying left by zero propogates nulls. + zero_result = _INF if (x.type().is_floating() or y.type().is_floating()) else _ZERO + return ( + ibis.case() + .when(y_numeric == _ZERO, zero_result * x_numeric) + .else_(floordiv_expr) + .end() + ) + + +def _is_float(x: ibis_types.Value): + return isinstance(x, (ibis_types.FloatingColumn, ibis_types.FloatingScalar)) + + +@short_circuit_nulls() +def mod_op( + x: ibis_types.Value, + y: ibis_types.Value, +): + is_result_float = _is_float(x) | _is_float(y) + x_numeric = typing.cast( + ibis_types.NumericValue, + x.cast(ibis_dtypes.Decimal(precision=38, scale=9, nullable=True)) + if is_result_float + else x, + ) + y_numeric = typing.cast( + ibis_types.NumericValue, + y.cast(ibis_dtypes.Decimal(precision=38, scale=9, nullable=True)) + if is_result_float + else y, + ) + # Hacky short-circuit to avoid passing zero-literal to sql backend, evaluate locally instead to null. + op = y.op() + if isinstance(op, ibis.expr.operations.generic.Literal) and op.value == 0: + return ibis_types.null().cast(x.type()) + + bq_mod = x_numeric % y_numeric # Bigquery will maintain x sign here + if is_result_float: + bq_mod = typing.cast(ibis_types.NumericValue, bq_mod.cast(ibis_dtypes.float64)) + + # In BigQuery returned value has the same sign as X. In pandas, the sign of y is used, so we need to flip the result if sign(x) != sign(y) + return ( + ibis.case() + .when( + y_numeric == _ZERO, + _NAN * x_numeric if is_result_float else _ZERO * x_numeric, + ) # Dummy op to propogate nulls and type from x arg + .when( + (y_numeric < _ZERO) & (bq_mod > _ZERO), (y_numeric + bq_mod) + ) # Convert positive result to negative + .when( + (y_numeric > _ZERO) & (bq_mod < _ZERO), (y_numeric + bq_mod) + ) # Convert negative result to positive + .else_(bq_mod) + .end() + ) + + +def fillna_op( + x: ibis_types.Value, + y: ibis_types.Value, +): + return x.fillna(typing.cast(ibis_types.Scalar, y)) + + +def round_op(x: ibis_types.Value, y: ibis_types.Value): + return typing.cast(ibis_types.NumericValue, x).round( + digits=typing.cast(ibis_types.IntegerValue, y) + ) + + +def clip_lower( + value: ibis_types.Value, + lower: ibis_types.Value, +): + return ibis.case().when(lower.isnull() | (value < lower), lower).else_(value).end() + + +def clip_upper( + value: ibis_types.Value, + upper: ibis_types.Value, +): + return ibis.case().when(upper.isnull() | (value > upper), upper).else_(value).end() + + +def reverse(op: BinaryOp) -> BinaryOp: + return lambda x, y: op(y, x) + + +def partial_left(op: BinaryOp, scalar: typing.Any) -> UnaryOp: + return BinopPartialLeft(op, scalar) + + +def partial_right(op: BinaryOp, scalar: typing.Any) -> UnaryOp: + return BinopPartialRight(op, scalar) + + +NUMPY_TO_BINOP: typing.Final = { + np.add: add_op, + np.subtract: sub_op, + np.multiply: mul_op, + np.divide: div_op, + np.power: pow_op, +} + + +# Ternary ops +def where_op( + original: ibis_types.Value, + condition: ibis_types.Value, + replacement: ibis_types.Value, +) -> ibis_types.Value: + """Returns x if y is true, otherwise returns z.""" + return ibis.case().when(condition, original).else_(replacement).end() + + +def clip_op( + original: ibis_types.Value, + lower: ibis_types.Value, + upper: ibis_types.Value, +) -> ibis_types.Value: + """Clips value to lower and upper bounds.""" + if isinstance(lower, ibis_types.NullScalar) and ( + not isinstance(upper, ibis_types.NullScalar) + ): + return ( + ibis.case() + .when(upper.isnull() | (original > upper), upper) + .else_(original) + .end() + ) + elif (not isinstance(lower, ibis_types.NullScalar)) and isinstance( + upper, ibis_types.NullScalar + ): + return ( + ibis.case() + .when(lower.isnull() | (original < lower), lower) + .else_(original) + .end() + ) + elif isinstance(lower, ibis_types.NullScalar) and ( + isinstance(upper, ibis_types.NullScalar) + ): + return original + else: + # Note: Pandas has unchanged behavior when upper bound and lower bound are flipped. This implementation requires that lower_bound < upper_bound + return ( + ibis.case() + .when(lower.isnull() | (original < lower), lower) + .when(upper.isnull() | (original > upper), upper) + .else_(original) + .end() + ) + + +def partial_arg1(op: TernaryOp, scalar: typing.Any) -> BinaryOp: + return lambda x, y: op(dtypes.literal_to_ibis_scalar(scalar, validate=False), x, y) + + +def partial_arg2(op: TernaryOp, scalar: typing.Any) -> BinaryOp: + return lambda x, y: op(x, dtypes.literal_to_ibis_scalar(scalar, validate=False), y) + + +def partial_arg3(op: TernaryOp, scalar: typing.Any) -> BinaryOp: + return lambda x, y: op(x, y, dtypes.literal_to_ibis_scalar(scalar, validate=False)) + + +def is_null(value) -> bool: + # float NaN/inf should be treated as distinct from 'true' null values + return typing.cast(bool, pd.isna(value)) and not isinstance(value, float) + + +def _ibis_num(number: float): + return typing.cast(ibis_types.NumericValue, ibis_types.literal(number)) diff --git a/bigframes/operations/_matplotlib/__init__.py b/bigframes/operations/_matplotlib/__init__.py deleted file mode 100644 index caacadf5fed..00000000000 --- a/bigframes/operations/_matplotlib/__init__.py +++ /dev/null @@ -1,40 +0,0 @@ -# Copyright 2023 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 typing - -import bigframes.operations._matplotlib.core as core -import bigframes.operations._matplotlib.hist as hist - -PLOT_TYPES = typing.Union[type[core.SamplingPlot], type[hist.HistPlot]] - -PLOT_CLASSES: dict[str, PLOT_TYPES] = { - "area": core.AreaPlot, - "bar": core.BarPlot, - "barh": core.BarhPlot, - "pie": core.PiePlot, - "line": core.LinePlot, - "scatter": core.ScatterPlot, - "hist": hist.HistPlot, -} - - -def plot(data, kind, **kwargs): - plot_obj = PLOT_CLASSES[kind](data, **kwargs) - plot_obj.generate() - plot_obj.draw() - return plot_obj.result - - -__all__ = ["plot"] diff --git a/bigframes/operations/_matplotlib/core.py b/bigframes/operations/_matplotlib/core.py deleted file mode 100644 index 06fb5235d78..00000000000 --- a/bigframes/operations/_matplotlib/core.py +++ /dev/null @@ -1,194 +0,0 @@ -# Copyright 2023 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 abc -import typing -import warnings - -import bigframes_vendored.constants as constants -import pandas as pd - -import bigframes.dtypes as dtypes -import bigframes.exceptions as bfe - -DEFAULT_SAMPLING_N = 1000 -DEFAULT_SAMPLING_STATE = 0 - - -class MPLPlot(abc.ABC): - @abc.abstractmethod - def generate(self): - pass - - def draw(self) -> None: - # This import can fail with "Matplotlib failed to acquire the - # following lock file" so import here to reduce the chance of - # our parallel test suite from triggering this. - import matplotlib.pyplot as plt - - plt.draw_if_interactive() - - @property - def result(self): - if hasattr(self, "axes"): - return self.axes - else: - raise AttributeError("Axes not defined") - - -class SamplingPlot(MPLPlot): - @property - @abc.abstractmethod - def _kind(self): - pass - - @property - def _sampling_warning_msg(self) -> typing.Optional[str]: - return ( - "To optimize plotting performance, your data has been downsampled to {sampling_n} " - "rows from the original {total_n} rows. This may result in some data points " - "not being displayed. For a more comprehensive view, consider pre-processing " - "your data by aggregating it or selecting the top categories." - ) - - def __init__(self, data, **kwargs) -> None: - self.kwargs = kwargs - self.data = data - - def generate(self) -> None: - plot_data = self._compute_plot_data() - self.axes = plot_data.plot(kind=self._kind, **self.kwargs) - - def _compute_sample_data(self, data): - # TODO: Cache the sampling data in the PlotAccessor. - sampling_n = self.kwargs.pop("sampling_n", DEFAULT_SAMPLING_N) - if self._sampling_warning_msg is not None: - total_n = data.shape[0] - if sampling_n < total_n: - msg = bfe.format_message( - self._sampling_warning_msg.format( - sampling_n=sampling_n, total_n=total_n - ) - ) - warnings.warn(msg, category=UserWarning) - - sampling_random_state = self.kwargs.pop( - "sampling_random_state", DEFAULT_SAMPLING_STATE - ) - return data.sample( - n=sampling_n, - random_state=sampling_random_state, - sort=False, - ).to_pandas() - - def _compute_plot_data(self): - return self._compute_sample_data(self.data) - - -class AreaPlot(SamplingPlot): - @property - def _sampling_warning_msg(self) -> typing.Optional[str]: - return None - - @property - def _kind(self) -> typing.Literal["area"]: - return "area" - - -class BarPlot(SamplingPlot): - @property - def _kind(self) -> typing.Literal["bar"]: - return "bar" - - -class BarhPlot(SamplingPlot): - @property - def _kind(self) -> typing.Literal["barh"]: - return "barh" - - -class PiePlot(SamplingPlot): - @property - def _kind(self) -> typing.Literal["pie"]: - return "pie" - - -class LinePlot(SamplingPlot): - @property - def _kind(self) -> typing.Literal["line"]: - return "line" - - -class ScatterPlot(SamplingPlot): - @property - def _kind(self) -> typing.Literal["scatter"]: - return "scatter" - - @property - def _sampling_warning_msg(self) -> typing.Optional[str]: - return None - - def __init__(self, data, **kwargs) -> None: - super().__init__(data, **kwargs) - - c = self.kwargs.get("c", None) - if self._is_sequence_arg(c): - raise NotImplementedError( - f"Only support a single color string or a column name/posision. {constants.FEEDBACK_LINK}" - ) - - s = self.kwargs.get("s", None) - if self._is_sequence_arg(s): - raise NotImplementedError( - f"Only support a single color string or a column name/posision. {constants.FEEDBACK_LINK}" - ) - - def _compute_plot_data(self): - sample = self._compute_sample_data(self.data) - - # Works around a pandas bug: - # https://github.com/pandas-dev/pandas/commit/45b937d64f6b7b6971856a47e379c7c87af7e00a - c = self.kwargs.get("c", None) - if pd.core.dtypes.common.is_integer(c): - c = self.data.columns[c] - if self._is_column_name(c, sample) and sample[c].dtype == dtypes.STRING_DTYPE: - sample[c] = sample[c].astype("object") - - # To avoid Matplotlib's automatic conversion of `Float64` or `Int64` columns - # to `object` types (which breaks float-like behavior), this code proactively - # converts the column to a compatible format. - s = self.kwargs.get("s", None) - if pd.core.dtypes.common.is_integer(s): - s = self.data.columns[s] - if self._is_column_name(s, sample): - if sample[s].dtype == dtypes.INT_DTYPE: - sample[s] = sample[s].astype("int64") - elif sample[s].dtype == dtypes.FLOAT_DTYPE: - sample[s] = sample[s].astype("float64") - - return sample - - def _is_sequence_arg(self, arg): - return ( - arg is not None - and not isinstance(arg, str) - and isinstance(arg, typing.Iterable) - ) - - def _is_column_name(self, arg, data): - return ( - arg is not None - and pd.core.dtypes.common.is_hashable(arg) - and arg in data.columns - ) diff --git a/bigframes/operations/_matplotlib/hist.py b/bigframes/operations/_matplotlib/hist.py deleted file mode 100644 index 213e2abd775..00000000000 --- a/bigframes/operations/_matplotlib/hist.py +++ /dev/null @@ -1,172 +0,0 @@ -# Copyright 2023 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 itertools -from typing import Literal - -import bigframes_vendored.constants as constants -import numpy as np -import pandas as pd - -import bigframes.operations._matplotlib.core as bfplt - - -class HistPlot(bfplt.MPLPlot): - @property - def _kind(self) -> Literal["hist"]: - return "hist" - - def __init__( - self, - data, - bins: int = 10, - **kwargs, - ) -> None: - self.bins = bins - self.label = kwargs.get("label", None) - self.by = kwargs.pop("by", None) - self.kwargs = kwargs - - if self.by is not None: - raise NotImplementedError( - f"Non-none `by` argument is not yet supported. {constants.FEEDBACK_LINK}" - ) - if not isinstance(self.bins, int): - raise NotImplementedError( - f"Only integer values are supported for the `bins` argument. {constants.FEEDBACK_LINK}" - ) - if kwargs.get("weight", None) is not None: - raise NotImplementedError( - f"Non-none `weight` argument is not yet supported. {constants.FEEDBACK_LINK}" - ) - - self.data = self._compute_plot_data(data) - - def generate(self) -> None: - """ - Calculates weighted histograms through BigQuery and plots them through pandas - native histogram plot. - """ - hist_bars = self._calculate_hist_bars(self.data, self.bins) - bin_edges = self._calculate_bin_edges( - hist_bars, self.bins, self.kwargs.get("range", None) - ) - - weights = { - col_name: hist_bar.values for col_name, hist_bar in hist_bars.items() - } - hist_x = { - col_name: pd.Series( - ( - hist_bar.index.get_level_values("left_exclusive") - + hist_bar.index.get_level_values("right_inclusive") - ) - / 2.0 - ) - for col_name, hist_bar in hist_bars.items() - } - - # Align DataFrames for plotting despite potential differences in column - # lengths, filling shorter columns with zeros. - hist_x_pd = pd.DataFrame( - list(itertools.zip_longest(*hist_x.values())), columns=list(hist_x.keys()) - ).sort_index(axis=1)[self.data.columns.values] - weights_pd = pd.DataFrame( - list(itertools.zip_longest(*weights.values())), columns=list(weights.keys()) - ).sort_index(axis=1)[self.data.columns.values] - - # Prevents pandas from dropping NA values and causing length mismatches by - # filling them with zeros. - hist_x_pd.fillna(0, inplace=True) - weights_pd.fillna(0, inplace=True) - - self.axes = hist_x_pd.plot.hist( - bins=bin_edges, - weights=np.array(weights_pd.values), - **self.kwargs, - ) # type: ignore - - def _compute_plot_data(self, data): - """ - Prepares data for plotting, focusing on numeric data types. - - Raises: - TypeError: If the input data contains no numeric columns. - """ - # Importing at the top of the file causes a circular import. - import bigframes.series as series - - if isinstance(data, series.Series): - label = self.label - if label is None and data.name is None: - label = "" - if label is None: - data = data.to_frame() - else: - data = data.to_frame(name=label) - - # TODO(chelsealin): Support timestamp/date types here. - include_type = ["number"] - numeric_data = data.select_dtypes(include=include_type) - try: - is_empty = numeric_data.columns.empty - except AttributeError: - is_empty = not len(numeric_data) - - if is_empty: - raise TypeError("no numeric data to plot") - - return numeric_data - - @staticmethod - def _calculate_hist_bars(data, bins): - """ - Calculates histogram bars for each column in a BigFrames DataFrame, and - returns a dictionary where keys are column names and values are pandas - Series. The series values are the histogram bins' heights with a - multi-index defining 'left_exclusive' and 'right_inclusive' bin edges. - """ - import bigframes.pandas as bpd - - # TODO: Optimize this by batching multiple jobs into one. - hist_bar = {} - for _, col in enumerate(data.columns): - cutted_data = bpd.cut(data[col], bins=bins, labels=None) - hist_bar[col] = ( - cutted_data.struct.explode() - .value_counts() - .to_pandas() - .sort_index(level="left_exclusive") - ) - return hist_bar - - @staticmethod - def _calculate_bin_edges(hist_bars, bins, range): - """ - Calculate bin edges from the histogram bars. - """ - bin_edges = None - for _, hist_bar in hist_bars.items(): - left = hist_bar.index.get_level_values("left_exclusive") - right = hist_bar.index.get_level_values("right_inclusive") - if bin_edges is None: - bin_edges = left.union(right) - else: - bin_edges = left.union(right).union(bin_edges) - - if bin_edges is None: - return None - - _, bins = np.histogram(bin_edges, bins=bins, range=range) - return bins diff --git a/bigframes/operations/_op_converters.py b/bigframes/operations/_op_converters.py deleted file mode 100644 index 14417a24f6e..00000000000 --- a/bigframes/operations/_op_converters.py +++ /dev/null @@ -1,37 +0,0 @@ -# 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. -# 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.operations as ops - - -def convert_index(key: int) -> ops.GetItemOp: - if key < 0: - raise NotImplementedError("Negative indexing is not supported.") - return ops.GetItemOp(key=key) - - -def convert_slice(key: slice) -> ops.ArraySliceOp: - if key.step is not None and key.step != 1: - raise NotImplementedError(f"Only a step of 1 is allowed, got {key.step}") - - if (key.start is not None and key.start < 0) or ( - key.stop is not None and key.stop < 0 - ): - raise NotImplementedError("Slicing with negative numbers is not allowed.") - - return ops.ArraySliceOp( - start=key.start if key.start is not None else 0, - stop=key.stop, - step=key.step, - ) diff --git a/bigframes/operations/aggregations.py b/bigframes/operations/aggregations.py index f7b89b949a8..465d1887247 100644 --- a/bigframes/operations/aggregations.py +++ b/bigframes/operations/aggregations.py @@ -14,666 +14,477 @@ from __future__ import annotations -import abc -import dataclasses import typing -from typing import TYPE_CHECKING, Callable, ClassVar, Iterable, Optional -import numpy as np -import pandas as pd -import pyarrow as pa +import ibis +import ibis.expr.datatypes as ibis_dtypes +import ibis.expr.types as ibis_types +from pandas import Int64Dtype +import bigframes.constants as constants import bigframes.dtypes as dtypes -import bigframes.operations.type as signatures -from bigframes.core import agg_expressions +import third_party.bigframes_vendored.ibis.expr.operations as vendored_ibis_ops -if TYPE_CHECKING: - from bigframes.core import expression - -@dataclasses.dataclass(frozen=True) class WindowOp: + def _as_ibis(self, value: ibis_types.Column, window=None): + raise NotImplementedError("Base class WindowOp has no implementaiton.") + @property def skips_nulls(self): """Whether the window op skips null rows.""" return True @property - def nulls_count_for_min_values(self) -> bool: - """Whether null values count for min_values.""" - return not self.skips_nulls - - @property - def implicitly_inherits_order(self): - """ - Whether the operator implicitly inherits the underlying array order, should it exist. - - Notably, rank operations do not want to inherit ordering. Even order-independent operations - may inherit order when needed for row bounds. - """ - return True - - @property - def order_independent(self): - """ - True if the output of the operator does not depend on the ordering of input rows. - - Aggregation functions are usually order independent, except array_agg, string_agg. - - Navigation functions are a notable case that are not order independent. - """ + def handles_ties(self): + """Whether the operator can handle ties without nondeterministic output. (eg. rank operator can handle ties but not the count operator)""" return False - @abc.abstractmethod - def output_type( - self, *input_types: dtypes.ExpressionType - ) -> dtypes.ExpressionType: ... - - @property - def can_be_windowized(self): - # this is more of an engine property, but will treat feasibility in bigquery sql as source of truth - return True - - -@dataclasses.dataclass(frozen=True) -class NullaryWindowOp(WindowOp): - @property - def arguments(self) -> int: - return 0 - -@dataclasses.dataclass(frozen=True) -class UnaryWindowOp(WindowOp): - @property - def arguments(self) -> int: - return 1 - - def output_type(self, *input_types: dtypes.ExpressionType) -> dtypes.ExpressionType: - return input_types[0] - - -@dataclasses.dataclass(frozen=True) class AggregateOp(WindowOp): - """Aggregate ops can be applied with or without a window clause.""" - - @property - @abc.abstractmethod - def name(self) -> str: ... - - @property - @abc.abstractmethod - def arguments(self) -> int: ... - - @property - def order_independent(self): - return True - - @property - def uses_total_row_ordering(self): - return False - - -@dataclasses.dataclass(frozen=True) -class NullaryAggregateOp(AggregateOp, NullaryWindowOp): - @property - def arguments(self) -> int: - return 0 - - def as_expr( - self, - *exprs: typing.Union[str, expression.Expression], - ) -> agg_expressions.NullaryAggregation: - from bigframes.core import agg_expressions - - return agg_expressions.NullaryAggregation(self) - - -@dataclasses.dataclass(frozen=True) -class UnaryAggregateOp(AggregateOp, UnaryWindowOp): - @property - def arguments(self) -> int: - return 1 - - def as_expr( - self, - *exprs: typing.Union[str, expression.Expression], - ) -> agg_expressions.UnaryAggregation: - from bigframes.core import agg_expressions - from bigframes.operations.base_ops import _convert_expr_input - - # Keep this in sync with output_type and compilers - inputs: list[expression.Expression] = [] - - for expr in exprs: - inputs.append(_convert_expr_input(expr)) - return agg_expressions.UnaryAggregation( - self, - inputs[0], - ) - - -@dataclasses.dataclass(frozen=True) -class BinaryAggregateOp(AggregateOp): - @property - def arguments(self) -> int: - return 2 - - def as_expr( - self, - *exprs: typing.Union[str, expression.Expression], - ) -> agg_expressions.BinaryAggregation: - from bigframes.core import agg_expressions - from bigframes.operations.base_ops import _convert_expr_input + name = "abstract_aggregate" - # Keep this in sync with output_type and compilers - inputs: list[expression.Expression] = [] + def _as_ibis(self, value: ibis_types.Column, window=None): + raise NotImplementedError("Base class AggregateOp has no implementaiton.") - for expr in exprs: - inputs.append(_convert_expr_input(expr)) - return agg_expressions.BinaryAggregation(self, inputs[0], inputs[1]) - - -@dataclasses.dataclass(frozen=True) -class SizeOp(NullaryAggregateOp): - name: ClassVar[str] = "size" - - def output_type(self, *input_types: dtypes.ExpressionType): - return dtypes.INT_DTYPE - - -# TODO: Remove this temporary hack once nullary ops are better supported in APIs -@dataclasses.dataclass(frozen=True) -class SizeUnaryOp(UnaryAggregateOp): - name: ClassVar[str] = "size" - - @property - def skips_nulls(self): - return False - - def output_type(self, *input_types: dtypes.ExpressionType): - return dtypes.INT_DTYPE - - -@dataclasses.dataclass(frozen=True) -class SumOp(UnaryAggregateOp): - name: ClassVar[str] = "sum" - - def output_type(self, *input_types: dtypes.ExpressionType) -> dtypes.ExpressionType: - if input_types[0] == dtypes.TIMEDELTA_DTYPE: - return dtypes.TIMEDELTA_DTYPE - - if dtypes.is_numeric(input_types[0]): - if pd.api.types.is_bool_dtype(input_types[0]): # type: ignore - return dtypes.INT_DTYPE - return input_types[0] - - raise TypeError(f"Type {input_types[0]} is not numeric or timedelta") - - -@dataclasses.dataclass(frozen=True) -class MedianOp(UnaryAggregateOp): - name: ClassVar[str] = "median" - - @property - def order_independent(self) -> bool: - return True - - def output_type(self, *input_types: dtypes.ExpressionType) -> dtypes.ExpressionType: - # These will change if median is changed to exact implementation. - if not dtypes.is_orderable(input_types[0]): - raise TypeError(f"Type {input_types[0]} is not orderable") - if pd.api.types.is_bool_dtype(input_types[0]): # type: ignore - return dtypes.INT_DTYPE +def numeric_op(operation): + def constrained_op(op, column: ibis_types.Column, window=None): + if column.type().is_boolean(): + column = typing.cast( + ibis_types.NumericColumn, column.cast(ibis_dtypes.int64) + ) + if column.type().is_numeric(): + return operation(op, column, window) else: - return input_types[0] + raise ValueError( + f"Numeric operation cannot be applied to type {column.type()}. {constants.FEEDBACK_LINK}" + ) + return constrained_op -@dataclasses.dataclass(frozen=True) -class QuantileOp(UnaryAggregateOp): - q: float - should_floor_result: bool = False - @property - def name(self): - return f"{int(self.q * 100)}%" +class SumOp(AggregateOp): + name = "sum" - @property - def order_independent(self) -> bool: - return True + @numeric_op + def _as_ibis( + self, column: ibis_types.NumericColumn, window=None + ) -> ibis_types.NumericValue: + # Will be null if all inputs are null. Pandas defaults to zero sum though. + bq_sum = _apply_window_if_present(column.sum(), window) + return ( + ibis.case().when(bq_sum.isnull(), ibis_types.literal(0)).else_(bq_sum).end() + ) - def output_type(self, *input_types: dtypes.ExpressionType) -> dtypes.ExpressionType: - if input_types[0] == dtypes.TIMEDELTA_DTYPE: - return dtypes.TIMEDELTA_DTYPE - return signatures.UNARY_REAL_NUMERIC.output_type(input_types[0]) +class MedianOp(AggregateOp): + name = "median" -@dataclasses.dataclass(frozen=True) -class ApproxQuartilesOp(UnaryAggregateOp): - quartile: int + @numeric_op + def _as_ibis( + self, column: ibis_types.NumericColumn, window=None + ) -> ibis_types.NumericValue: + # PERCENTILE_CONT has very few allowed windows. For example, "window + # framing clause is not allowed for analytic function percentile_cont". + if window is not None: + raise NotImplementedError( + f"Median with windowing is not supported. {constants.FEEDBACK_LINK}" + ) - @property - def name(self): - return f"{self.quartile * 25}%" + # TODO(swast): Allow switching between exact and approximate median. + # For now, the best we can do is an approximate median when we're doing + # an aggregation, as PERCENTILE_CONT is only an analytic function. + return typing.cast(ibis_types.NumericValue, column.approx_median()) + + +class ApproxQuartilesOp(AggregateOp): + def __init__(self, quartile: int): + self.name = f"{quartile*25}%" + self._quartile = quartile + + @numeric_op + def _as_ibis( + self, column: ibis_types.NumericColumn, window=None + ) -> ibis_types.NumericValue: + # PERCENTILE_CONT has very few allowed windows. For example, "window + # framing clause is not allowed for analytic function percentile_cont". + if window is not None: + raise NotImplementedError( + f"Approx Quartiles with windowing is not supported. {constants.FEEDBACK_LINK}" + ) + value = vendored_ibis_ops.ApproximateMultiQuantile( + column, num_bins=4 # type: ignore + ).to_expr()[self._quartile] + return typing.cast(ibis_types.NumericValue, value) - def output_type(self, *input_types: dtypes.ExpressionType) -> dtypes.ExpressionType: - if not dtypes.is_orderable(input_types[0]): - raise TypeError(f"Type {input_types[0]} is not orderable") - return input_types[0] - @property - def can_be_windowized(self): - return False +class MeanOp(AggregateOp): + name = "mean" + @numeric_op + def _as_ibis( + self, column: ibis_types.NumericColumn, window=None + ) -> ibis_types.NumericValue: + return _apply_window_if_present(column.mean(), window) -@dataclasses.dataclass(frozen=True) -class ApproxTopCountOp(UnaryAggregateOp): - name: typing.ClassVar[str] = "approx_top_count" - number: int - def output_type(self, *input_types: dtypes.ExpressionType) -> dtypes.ExpressionType: - if not dtypes.is_orderable(input_types[0]): - raise TypeError(f"Type {input_types[0]} is not orderable") +class ProductOp(AggregateOp): + name = "product" - input_type = input_types[0] - fields = [ - pa.field("value", dtypes.bigframes_dtype_to_arrow_dtype(input_type)), - pa.field("count", pa.int64()), - ] - return pd.ArrowDtype(pa.list_(pa.struct(fields))) + @numeric_op + def _as_ibis( + self, column: ibis_types.NumericColumn, window=None + ) -> ibis_types.NumericValue: + # Need to short-circuit as log with zeroes is illegal sql + is_zero = typing.cast(ibis_types.BooleanColumn, (column == 0)) - @property - def can_be_windowized(self): - return False + # There is no product sql aggregate function, so must implement as a sum of logs, and then + # apply power after. Note, log and power base must be equal! This impl uses base 2. + logs = typing.cast( + ibis_types.NumericColumn, + ibis.case().when(is_zero, 0).else_(column.abs().log2()).end(), + ) + logs_sum = _apply_window_if_present(logs.sum(), window) + magnitude = typing.cast(ibis_types.NumericValue, ibis_types.literal(2)).pow( + logs_sum + ) + # Can't determine sign from logs, so have to determine parity of count of negative inputs + is_negative = typing.cast( + ibis_types.NumericColumn, + ibis.case().when(column.sign() == -1, 1).else_(0).end(), + ) + negative_count = _apply_window_if_present(is_negative.sum(), window) + negative_count_parity = negative_count % typing.cast( + ibis_types.NumericValue, ibis.literal(2) + ) # 1 if result should be negative, otherwise 0 + + any_zeroes = _apply_window_if_present(is_zero.any(), window) + float_result = ( + ibis.case() + .when(any_zeroes, ibis_types.literal(0)) + .else_(magnitude * pow(-1, negative_count_parity)) + .end() + ) + return float_result.cast(column.type()) -@dataclasses.dataclass(frozen=True) -class MeanOp(UnaryAggregateOp): - name: ClassVar[str] = "mean" - should_floor_result: bool = False +class MaxOp(AggregateOp): + name = "max" - def output_type(self, *input_types: dtypes.ExpressionType) -> dtypes.ExpressionType: - if input_types[0] == dtypes.TIMEDELTA_DTYPE: - return dtypes.TIMEDELTA_DTYPE - return signatures.UNARY_REAL_NUMERIC.output_type(input_types[0]) + def _as_ibis(self, column: ibis_types.Column, window=None) -> ibis_types.Value: + return _apply_window_if_present(column.max(), window) -@dataclasses.dataclass(frozen=True) -class ProductOp(UnaryAggregateOp): - name: ClassVar[str] = "product" +class MinOp(AggregateOp): + name = "min" - def output_type(self, *input_types: dtypes.ExpressionType) -> dtypes.ExpressionType: - return signatures.FixedOutputType( - dtypes.is_numeric, dtypes.FLOAT_DTYPE, "numeric" - ).output_type(input_types[0]) + def _as_ibis(self, column: ibis_types.Column, window=None) -> ibis_types.Value: + return _apply_window_if_present(column.min(), window) -@dataclasses.dataclass(frozen=True) -class MaxOp(UnaryAggregateOp): - name: ClassVar[str] = "max" +class StdOp(AggregateOp): + name = "std" - def output_type(self, *input_types: dtypes.ExpressionType) -> dtypes.ExpressionType: - return signatures.TypePreserving(dtypes.is_orderable, "orderable").output_type( - input_types[0] + @numeric_op + def _as_ibis(self, x: ibis_types.Column, window=None) -> ibis_types.Value: + return _apply_window_if_present( + typing.cast(ibis_types.NumericColumn, x).std(), window ) -@dataclasses.dataclass(frozen=True) -class MinOp(UnaryAggregateOp): - name: ClassVar[str] = "min" +class VarOp(AggregateOp): + name = "var" - def output_type(self, *input_types: dtypes.ExpressionType) -> dtypes.ExpressionType: - return signatures.TypePreserving(dtypes.is_orderable, "orderable").output_type( - input_types[0] + @numeric_op + def _as_ibis(self, x: ibis_types.Column, window=None) -> ibis_types.Value: + return _apply_window_if_present( + typing.cast(ibis_types.NumericColumn, x).var(), window ) -@dataclasses.dataclass(frozen=True) -class StdOp(UnaryAggregateOp): - name: ClassVar[str] = "std" - - should_floor_result: bool = False - - def output_type(self, *input_types: dtypes.ExpressionType) -> dtypes.ExpressionType: - if input_types[0] == dtypes.TIMEDELTA_DTYPE: - return dtypes.TIMEDELTA_DTYPE - - return signatures.FixedOutputType( - dtypes.is_numeric, dtypes.FLOAT_DTYPE, "numeric" - ).output_type(input_types[0]) - - -@dataclasses.dataclass(frozen=True) -class VarOp(UnaryAggregateOp): - name: ClassVar[str] = "var" +class PopVarOp(AggregateOp): + name = "popvar" - def output_type(self, *input_types: dtypes.ExpressionType) -> dtypes.ExpressionType: - return signatures.FixedOutputType( - dtypes.is_numeric, dtypes.FLOAT_DTYPE, "numeric" - ).output_type(input_types[0]) - - -@dataclasses.dataclass(frozen=True) -class PopVarOp(UnaryAggregateOp): - name: ClassVar[str] = "popvar" + @numeric_op + def _as_ibis(self, x: ibis_types.Column, window=None) -> ibis_types.Value: + return _apply_window_if_present( + typing.cast(ibis_types.NumericColumn, x).var(how="pop"), window + ) - def output_type(self, *input_types: dtypes.ExpressionType) -> dtypes.ExpressionType: - return signatures.FixedOutputType( - dtypes.is_numeric, dtypes.FLOAT_DTYPE, "numeric" - ).output_type(input_types[0]) +class CountOp(AggregateOp): + name = "count" -@dataclasses.dataclass(frozen=True) -class CountOp(UnaryAggregateOp): - name: ClassVar[str] = "count" + def _as_ibis( + self, column: ibis_types.Column, window=None + ) -> ibis_types.IntegerValue: + return _apply_window_if_present(column.count(), window) @property def skips_nulls(self): return False - def output_type(self, *input_types: dtypes.ExpressionType) -> dtypes.ExpressionType: - return signatures.FixedOutputType( - lambda x: True, dtypes.INT_DTYPE, "" - ).output_type(input_types[0]) - -@dataclasses.dataclass(frozen=True) -class ArrayAggOp(UnaryAggregateOp): - name: ClassVar[str] = "arrayagg" - - @property - def order_independent(self): - return False +class CutOp(WindowOp): + def __init__(self, bins: int): + self._bins_ibis = dtypes.literal_to_ibis_scalar(bins, force_dtype=Int64Dtype()) + self._bins_int = bins + + def _as_ibis(self, x: ibis_types.Column, window=None): + col_min = _apply_window_if_present(x.min(), window) + col_max = _apply_window_if_present(x.max(), window) + bin_width = (col_max - col_min) / self._bins_ibis + out = ibis.case() + for this_bin in range(self._bins_int - 1): + out = out.when( + x <= (col_min + (this_bin + 1) * bin_width), + dtypes.literal_to_ibis_scalar(this_bin, force_dtype=Int64Dtype()), + ) + out = out.when(x.notnull(), self._bins_ibis - 1) + return out.end() @property def skips_nulls(self): - return True - - def output_type(self, *input_types: dtypes.ExpressionType) -> dtypes.ExpressionType: - return dtypes.list_type(input_types[0]) - - -@dataclasses.dataclass(frozen=True) -class StringAggOp(UnaryAggregateOp): - name: ClassVar[str] = "string_agg" - sep: str = "," - - @property - def order_independent(self): return False @property - def skips_nulls(self): + def handles_ties(self): return True - def output_type(self, *input_types: dtypes.ExpressionType) -> dtypes.ExpressionType: - if input_types[0] != dtypes.STRING_DTYPE: - raise TypeError(f"Type {input_types[0]} is not string-like") - return dtypes.STRING_DTYPE - - -@dataclasses.dataclass(frozen=True) -class CutOp(UnaryWindowOp): - # TODO: Unintuitive, refactor into multiple ops? - bins: typing.Union[int, Iterable] - right: Optional[bool] - labels: typing.Union[bool, Iterable[str], None] - @property - def skips_nulls(self): - return False - - def output_type(self, *input_types: dtypes.ExpressionType) -> dtypes.ExpressionType: - if self.labels is False: - return dtypes.INT_DTYPE - elif isinstance(self.labels, Iterable): - return dtypes.STRING_DTYPE +class QcutOp(WindowOp): + def __init__(self, quantiles: typing.Union[int, typing.Sequence[float]]): + self.name = f"qcut-{quantiles}" + self._quantiles = quantiles + + @numeric_op + def _as_ibis( + self, column: ibis_types.Column, window=None + ) -> ibis_types.IntegerValue: + if isinstance(self._quantiles, int): + quantiles_ibis = dtypes.literal_to_ibis_scalar(self._quantiles) + percent_ranks = typing.cast( + ibis_types.FloatingColumn, + _apply_window_if_present(column.percent_rank(), window), + ) + float_bucket = typing.cast( + ibis_types.FloatingColumn, (percent_ranks * quantiles_ibis) + ) + return float_bucket.ceil().clip(lower=_ibis_num(1)) - _ibis_num(1) else: - # Assumption: buckets use same numeric type - if isinstance(self.bins, int): - interval_dtype = pa.float64() - elif len(list(self.bins)) == 0: - interval_dtype = pa.int64() - else: - interval_dtype = dtypes.infer_literal_arrow_type(list(self.bins)[0][0]) - pa_type = pa.struct( - [ - pa.field( - "left_exclusive" if self.right else "left_inclusive", - interval_dtype, - nullable=True, - ), - pa.field( - "right_inclusive" if self.right else "right_exclusive", - interval_dtype, - nullable=True, - ), - ] + percent_ranks = typing.cast( + ibis_types.FloatingColumn, + _apply_window_if_present(column.percent_rank(), window), ) - - return pd.ArrowDtype(pa_type) - - @property - def order_independent(self): - return True - - -@dataclasses.dataclass(frozen=True) -class QcutOp(UnaryWindowOp): # bucket op - quantiles: typing.Union[int, typing.Tuple[float, ...]] - - @property - def name(self): - return f"qcut-{self.quantiles}" + out = ibis.case() + first_ibis_quantile = dtypes.literal_to_ibis_scalar(self._quantiles[0]) + out = out.when(percent_ranks < first_ibis_quantile, None) + for bucket_n in range(len(self._quantiles) - 1): + ibis_quantile = dtypes.literal_to_ibis_scalar( + self._quantiles[bucket_n + 1] + ) + out = out.when( + percent_ranks <= ibis_quantile, + dtypes.literal_to_ibis_scalar(bucket_n, force_dtype=Int64Dtype()), + ) + out = out.else_(None) + return out.end() @property def skips_nulls(self): return False - def output_type(self, *input_types: dtypes.ExpressionType) -> dtypes.ExpressionType: - return signatures.FixedOutputType( - dtypes.is_orderable, dtypes.INT_DTYPE, "orderable" - ).output_type(input_types[0]) - @property - def order_independent(self): + def handles_ties(self): return True -@dataclasses.dataclass(frozen=True) -class NuniqueOp(UnaryAggregateOp): - name: ClassVar[str] = "nunique" +class NuniqueOp(AggregateOp): + name = "nunique" + + def _as_ibis( + self, column: ibis_types.Column, window=None + ) -> ibis_types.IntegerValue: + return _apply_window_if_present(column.nunique(), window) @property def skips_nulls(self): return False - def output_type(self, *input_types: dtypes.ExpressionType) -> dtypes.ExpressionType: - return dtypes.INT_DTYPE - -@dataclasses.dataclass(frozen=True) -class AnyValueOp(UnaryAggregateOp): +class AnyValueOp(AggregateOp): # Warning: only use if all values are equal. Non-deterministic otherwise. # Do not expose to users. For special cases only (e.g. pivot). - name: ClassVar[str] = "any_value" - - @property - def skips_nulls(self): - return True - - -# This should really by a NullaryWindowOp, but APIs don't support that yet. -@dataclasses.dataclass(frozen=True) -class RowNumberOp(NullaryWindowOp): - name: ClassVar[str] = "rownumber" - - @property - def skips_nulls(self): - return False - - def output_type(self, *input_types: dtypes.ExpressionType) -> dtypes.ExpressionType: - return dtypes.INT_DTYPE - + name = "any_value" -@dataclasses.dataclass(frozen=True) -class RankOp(UnaryWindowOp): - name: ClassVar[str] = "rank" + def _as_ibis( + self, column: ibis_types.Column, window=None + ) -> ibis_types.IntegerValue: + return _apply_window_if_present(column.arbitrary(), window) @property def skips_nulls(self): - return False + return True - def output_type(self, *input_types: dtypes.ExpressionType) -> dtypes.ExpressionType: - return dtypes.INT_DTYPE - - @property - def implicitly_inherits_order(self): - return False +class RankOp(WindowOp): + name = "rank" -@dataclasses.dataclass(frozen=True) -class DenseRankOp(UnaryWindowOp): - name: ClassVar[str] = "dense_rank" + def _as_ibis( + self, column: ibis_types.Column, window=None + ) -> ibis_types.IntegerValue: + # Ibis produces 0-based ranks, while pandas creates 1-based ranks + return _apply_window_if_present(column.rank(), window) + 1 @property def skips_nulls(self): return False - def output_type(self, *input_types: dtypes.ExpressionType) -> dtypes.ExpressionType: - return dtypes.INT_DTYPE - @property - def implicitly_inherits_order(self): - return False - + def handles_ties(self): + return True -@dataclasses.dataclass(frozen=True) -class FirstOp(UnaryWindowOp): - name: ClassVar[str] = "first" +class DenseRankOp(WindowOp): + def _as_ibis( + self, column: ibis_types.Column, window=None + ) -> ibis_types.IntegerValue: + # Ibis produces 0-based ranks, while pandas creates 1-based ranks + return _apply_window_if_present(column.dense_rank(), window) + 1 -@dataclasses.dataclass(frozen=True) -class FirstNonNullOp(UnaryWindowOp): @property def skips_nulls(self): return False @property - def nulls_count_for_min_values(self) -> bool: - return False + def handles_ties(self): + return True -@dataclasses.dataclass(frozen=True) -class LastOp(UnaryWindowOp): - name: ClassVar[str] = "last" +class FirstOp(WindowOp): + def _as_ibis(self, column: ibis_types.Column, window=None) -> ibis_types.Value: + return _apply_window_if_present(column.first(), window) -@dataclasses.dataclass(frozen=True) -class LastNonNullOp(UnaryWindowOp): +class FirstNonNullOp(WindowOp): @property def skips_nulls(self): return False - @property - def nulls_count_for_min_values(self) -> bool: - return False - + def _as_ibis(self, column: ibis_types.Column, window=None) -> ibis_types.Value: + return _apply_window_if_present( + vendored_ibis_ops.FirstNonNullValue(column).to_expr(), window # type: ignore + ) -@dataclasses.dataclass(frozen=True) -class ShiftOp(UnaryWindowOp): - periods: int +class LastNonNullOp(WindowOp): @property def skips_nulls(self): return False + def _as_ibis(self, column: ibis_types.Column, window=None) -> ibis_types.Value: + return _apply_window_if_present( + vendored_ibis_ops.LastNonNullValue(column).to_expr(), window # type: ignore + ) -@dataclasses.dataclass(frozen=True) -class DiffOp(UnaryWindowOp): - name: ClassVar[str] = "diff" - periods: int - - @property - def skips_nulls(self): - return False - - def output_type(self, *input_types: dtypes.ExpressionType) -> dtypes.ExpressionType: - if dtypes.is_date_like(input_types[0]): - return dtypes.TIMEDELTA_DTYPE - return super().output_type(*input_types) +class ShiftOp(WindowOp): + def __init__(self, periods: int): + self._periods = periods -@dataclasses.dataclass(frozen=True) -class TimeSeriesDiffOp(UnaryWindowOp): - periods: int + def _as_ibis(self, column: ibis_types.Column, window=None) -> ibis_types.Value: + if self._periods == 0: # No-op + return column + if self._periods > 0: + return _apply_window_if_present(column.lag(self._periods), window) + return _apply_window_if_present(column.lead(-self._periods), window) @property def skips_nulls(self): return False - def output_type(self, *input_types: dtypes.ExpressionType) -> dtypes.ExpressionType: - if dtypes.is_datetime_like(input_types[0]): - return dtypes.TIMEDELTA_DTYPE - raise TypeError(f"expect datetime-like types, but got {input_types[0]}") +class DiffOp(WindowOp): + def __init__(self, periods: int): + self._periods = periods -@dataclasses.dataclass(frozen=True) -class DateSeriesDiffOp(UnaryWindowOp): - periods: int + def _as_ibis(self, column: ibis_types.Column, window=None) -> ibis_types.Value: + shifted = ShiftOp(self._periods)._as_ibis(column, window) + if column.type().is_boolean(): + return typing.cast(ibis_types.BooleanColumn, column) != typing.cast( + ibis_types.BooleanColumn, shifted + ) + elif column.type().is_numeric(): + return typing.cast(ibis_types.NumericColumn, column) - typing.cast( + ibis_types.NumericColumn, shifted + ) + else: + raise TypeError(f"Cannot perform diff on type{column.type()}") @property def skips_nulls(self): return False - def output_type(self, *input_types: dtypes.ExpressionType) -> dtypes.ExpressionType: - if input_types[0] == dtypes.DATE_DTYPE: - return dtypes.TIMEDELTA_DTYPE - raise TypeError(f"expect date type, but got {input_types[0]}") - - -@dataclasses.dataclass(frozen=True) -class AllOp(UnaryAggregateOp): - name: ClassVar[str] = "all" - - def output_type(self, *input_types: dtypes.ExpressionType) -> dtypes.ExpressionType: - return signatures.FixedOutputType( - dtypes.is_bool_coercable, dtypes.BOOL_DTYPE, "convertible to boolean" - ).output_type(input_types[0]) +class AllOp(AggregateOp): + def _as_ibis( + self, column: ibis_types.Column, window=None + ) -> ibis_types.BooleanValue: + # BQ will return null for empty column, result would be true in pandas. + result = _is_true(column).all() + return typing.cast( + ibis_types.BooleanScalar, + _apply_window_if_present(result, window).fillna(ibis_types.literal(True)), + ) -@dataclasses.dataclass(frozen=True) -class AnyOp(UnaryAggregateOp): - name: ClassVar[str] = "any" - def output_type(self, *input_types: dtypes.ExpressionType) -> dtypes.ExpressionType: - return signatures.FixedOutputType( - dtypes.is_bool_coercable, dtypes.BOOL_DTYPE, "convertible to boolean" - ).output_type(input_types[0]) +class AnyOp(AggregateOp): + name = "any" + def _as_ibis( + self, column: ibis_types.Column, window=None + ) -> ibis_types.BooleanValue: + # BQ will return null for empty column, result would be false in pandas. + result = _is_true(column).any() + return typing.cast( + ibis_types.BooleanScalar, + _apply_window_if_present(result, window).fillna(ibis_types.literal(True)), + ) -@dataclasses.dataclass(frozen=True) -class CorrOp(BinaryAggregateOp): - name: ClassVar[str] = "corr" - def output_type(self, *input_types: dtypes.ExpressionType) -> dtypes.ExpressionType: - return signatures.BINARY_REAL_NUMERIC.output_type( - input_types[0], input_types[1] +def _is_true(column: ibis_types.Column) -> ibis_types.BooleanColumn: + if column.type().is_boolean(): + return typing.cast(ibis_types.BooleanColumn, column) + elif column.type().is_numeric(): + result = typing.cast(ibis_types.NumericColumn, column).__ne__( + ibis_types.literal(0) + ) + return typing.cast(ibis_types.BooleanColumn, result) + elif column.type().is_string(): + result = typing.cast( + ibis_types.StringValue, column + ).length() > ibis_types.literal(0) + return typing.cast(ibis_types.BooleanColumn, result) + else: + # Time and geo values don't have a 'False' value + return typing.cast( + ibis_types.BooleanColumn, _map_to_literal(column, ibis_types.literal(True)) ) -@dataclasses.dataclass(frozen=True) -class CovOp(BinaryAggregateOp): - name: ClassVar[str] = "cov" +def _apply_window_if_present(value: ibis_types.Value, window): + return value.over(window) if (window is not None) else value - def output_type(self, *input_types: dtypes.ExpressionType) -> dtypes.ExpressionType: - return signatures.BINARY_REAL_NUMERIC.output_type( - input_types[0], input_types[1] - ) + +def _map_to_literal( + original: ibis_types.Value, literal: ibis_types.Scalar +) -> ibis_types.Column: + # Hack required to perform aggregations on literals in ibis, even though bigquery will let you directly aggregate literals (eg. 'SELECT COUNT(1) from table1') + return ibis.ifelse(original.isnull(), literal, literal) -size_op = SizeOp() sum_op = SumOp() mean_op = MeanOp() median_op = MedianOp() @@ -692,9 +503,7 @@ def output_type(self, *input_types: dtypes.ExpressionType) -> dtypes.ExpressionT # TODO: Alternative names and lookup from numpy function objects -_STRING_TO_AGG_OP: typing.Dict[ - str, typing.Union[UnaryAggregateOp, NullaryAggregateOp] -] = { +_AGGREGATIONS_LOOKUP: dict[str, AggregateOp] = { op.name: op for op in [ sum_op, @@ -713,44 +522,23 @@ def output_type(self, *input_types: dtypes.ExpressionType) -> dtypes.ExpressionT ApproxQuartilesOp(2), ApproxQuartilesOp(3), ] - + [ - # Add size_op separately to avoid Mypy type inference errors. - size_op, - ] -} - -_CALLABLE_TO_AGG_OP: typing.Dict[ - Callable, typing.Union[UnaryAggregateOp, NullaryAggregateOp] -] = { - np.sum: sum_op, - np.mean: mean_op, - np.median: median_op, - np.prod: product_op, - np.max: max_op, - np.min: min_op, - np.std: std_op, - np.var: var_op, - np.all: all_op, - np.any: any_op, - np.unique: nunique_op, - np.size: size_op, - # TODO(b/443252872): Solve - list: ArrayAggOp(), - len: size_op, - sum: sum_op, - min: min_op, - max: max_op, - any: any_op, - all: all_op, } -def lookup_agg_func( - key, -) -> tuple[typing.Union[UnaryAggregateOp, NullaryAggregateOp], str]: - if key in _STRING_TO_AGG_OP: - return (_STRING_TO_AGG_OP[key], key) - if key in _CALLABLE_TO_AGG_OP: - return (_CALLABLE_TO_AGG_OP[key], key.__name__) +def lookup_agg_func(key: str) -> AggregateOp: + if callable(key): + raise NotImplementedError( + "Aggregating with callable object not supported, pass method name as string instead (eg. 'sum' instead of np.sum)." + ) + if not isinstance(key, str): + raise ValueError( + f"Cannot aggregate using object of type: {type(key)}. Use string method name (eg. 'sum')" + ) + if key in _AGGREGATIONS_LOOKUP: + return _AGGREGATIONS_LOOKUP[key] else: raise ValueError(f"Unrecognize aggregate function: {key}") + + +def _ibis_num(number: float): + return typing.cast(ibis_types.NumericValue, ibis_types.literal(number)) diff --git a/bigframes/operations/ai_ops.py b/bigframes/operations/ai_ops.py deleted file mode 100644 index ad2b9850577..00000000000 --- a/bigframes/operations/ai_ops.py +++ /dev/null @@ -1,201 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from __future__ import annotations - -import dataclasses -from typing import ClassVar, Tuple - -import pandas as pd -import pyarrow as pa - -from bigframes import dtypes -from bigframes.operations import base_ops, output_schemas - - -@dataclasses.dataclass(frozen=True) -class AIGenerate(base_ops.NaryOp): - name: ClassVar[str] = "ai_generate" - - prompt_context: Tuple[str | None, ...] - connection_id: str | None = None - endpoint: str | None = None - request_type: str | None = None - model_params: str | None = None - output_schema: str | None = None - - def output_type(self, *input_types: dtypes.ExpressionType) -> dtypes.ExpressionType: - if self.output_schema is None: - output_fields = (pa.field("result", pa.string()),) - else: - output_fields = output_schemas.parse_sql_fields(self.output_schema) - - return pd.ArrowDtype( - pa.struct( - ( - *output_fields, - pa.field("full_response", dtypes.JSON_ARROW_TYPE), - pa.field("status", pa.string()), - ) - ) - ) - - -@dataclasses.dataclass(frozen=True) -class AIGenerateBool(base_ops.NaryOp): - name: ClassVar[str] = "ai_generate_bool" - - prompt_context: Tuple[str | None, ...] - connection_id: str | None = None - endpoint: str | None = None - request_type: str | None = None - model_params: str | None = None - - def output_type(self, *input_types: dtypes.ExpressionType) -> dtypes.ExpressionType: - return pd.ArrowDtype( - pa.struct( - ( - pa.field("result", pa.bool_()), - pa.field("full_response", dtypes.JSON_ARROW_TYPE), - pa.field("status", pa.string()), - ) - ) - ) - - -@dataclasses.dataclass(frozen=True) -class AIGenerateInt(base_ops.NaryOp): - name: ClassVar[str] = "ai_generate_int" - - prompt_context: Tuple[str | None, ...] - connection_id: str | None = None - endpoint: str | None = None - request_type: str | None = None - model_params: str | None = None - - def output_type(self, *input_types: dtypes.ExpressionType) -> dtypes.ExpressionType: - return pd.ArrowDtype( - pa.struct( - ( - pa.field("result", pa.int64()), - pa.field("full_response", dtypes.JSON_ARROW_TYPE), - pa.field("status", pa.string()), - ) - ) - ) - - -@dataclasses.dataclass(frozen=True) -class AIGenerateDouble(base_ops.NaryOp): - name: ClassVar[str] = "ai_generate_double" - - prompt_context: Tuple[str | None, ...] - connection_id: str | None = None - endpoint: str | None = None - request_type: str | None = None - model_params: str | None = None - - def output_type(self, *input_types: dtypes.ExpressionType) -> dtypes.ExpressionType: - return pd.ArrowDtype( - pa.struct( - ( - pa.field("result", pa.float64()), - pa.field("full_response", dtypes.JSON_ARROW_TYPE), - pa.field("status", pa.string()), - ) - ) - ) - - -@dataclasses.dataclass(frozen=True) -class AIEmbed(base_ops.UnaryOp): - name: ClassVar[str] = "ai_embed" - - endpoint: str | None = None - model: str | None = None - task_type: str | None = None - title: str | None = None - model_params: str | None = None - connection_id: str | None = None - - def output_type(self, *input_types: dtypes.ExpressionType) -> dtypes.ExpressionType: - return pd.ArrowDtype( - pa.struct( - ( - pa.field("result", pa.list_(pa.float64())), - pa.field("status", pa.string()), - ) - ) - ) - - -@dataclasses.dataclass(frozen=True) -class AIIf(base_ops.NaryOp): - name: ClassVar[str] = "ai_if" - - prompt_context: Tuple[str | None, ...] - connection_id: str | None = None - endpoint: str | None = None - optimization_mode: str | None = None - max_error_ratio: float | None = None - - def output_type(self, *input_types: dtypes.ExpressionType) -> dtypes.ExpressionType: - return dtypes.BOOL_DTYPE - - -@dataclasses.dataclass(frozen=True) -class AIClassify(base_ops.NaryOp): - name: ClassVar[str] = "ai_classify" - - prompt_context: Tuple[str | None, ...] - categories: tuple[str, ...] - examples: ( - tuple[tuple[str, str], ...] | tuple[tuple[str, tuple[str, ...]], ...] | None - ) = None - connection_id: str | None = None - endpoint: str | None = None - output_mode: str | None = None - optimization_mode: str | None = None - max_error_ratio: float | None = None - - def output_type(self, *input_types: dtypes.ExpressionType) -> dtypes.ExpressionType: - if self.output_mode is not None: - return dtypes.list_type(dtypes.STRING_DTYPE) - return dtypes.STRING_DTYPE - - -@dataclasses.dataclass(frozen=True) -class AIScore(base_ops.NaryOp): - name: ClassVar[str] = "ai_score" - - prompt_context: Tuple[str | None, ...] - connection_id: str | None = None - endpoint: str | None = None - max_error_ratio: float | None = None - - def output_type(self, *input_types: dtypes.ExpressionType) -> dtypes.ExpressionType: - return dtypes.FLOAT_DTYPE - - -@dataclasses.dataclass(frozen=True) -class AISimilarity(base_ops.BinaryOp): - name: ClassVar[str] = "ai_similarity" - - endpoint: str | None = None - model: str | None = None - model_params: str | None = None - connection_id: str | None = None - - def output_type(self, *input_types: dtypes.ExpressionType) -> dtypes.ExpressionType: - return dtypes.FLOAT_DTYPE diff --git a/bigframes/operations/array_ops.py b/bigframes/operations/array_ops.py deleted file mode 100644 index e6f5743989b..00000000000 --- a/bigframes/operations/array_ops.py +++ /dev/null @@ -1,87 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import dataclasses -import functools -import typing - -from bigframes import dtypes -from bigframes.operations import aggregations, base_ops - - -@dataclasses.dataclass(frozen=True) -class ArrayToStringOp(base_ops.UnaryOp): - name: typing.ClassVar[str] = "array_to_string" - delimiter: str - - def output_type(self, *input_types): - input_type = input_types[0] - if not dtypes.is_array_string_like(input_type): - raise TypeError("Input type must be an array of string type.") - return dtypes.STRING_DTYPE - - -@dataclasses.dataclass(frozen=True) -class ArraySliceOp(base_ops.UnaryOp): - name: typing.ClassVar[str] = "array_slice" - start: int - stop: typing.Optional[int] = None - step: typing.Optional[int] = None - - def output_type(self, *input_types): - input_type = input_types[0] - if dtypes.is_string_like(input_type): - return dtypes.STRING_DTYPE - elif dtypes.is_array_like(input_type): - return input_type - else: - raise TypeError("Input type must be an array or string-like type.") - - -class ToArrayOp(base_ops.NaryOp): - name: typing.ClassVar[str] = "array" - - def output_type(self, *input_types: dtypes.ExpressionType) -> dtypes.ExpressionType: - # very permissive, maybe should force caller to do this? - common_type = functools.reduce( - lambda t1, t2: dtypes.coerce_to_common(t1, t2), - input_types, - ) - return dtypes.list_type(common_type) - - -@dataclasses.dataclass(frozen=True) -class ArrayReduceOp(base_ops.UnaryOp): - name: typing.ClassVar[str] = "array_reduce" - aggregation: aggregations.AggregateOp - - def output_type(self, *input_types): - input_type = input_types[0] - assert dtypes.is_array_like(input_type) - inner_type = dtypes.get_array_inner_type(input_type) - return self.aggregation.output_type(inner_type) - - -@dataclasses.dataclass(frozen=True) -class ArrayMapOp(base_ops.UnaryOp): - name: typing.ClassVar[str] = "array_map" - # TODO(b/495513753): Generalize to chained expressions - map_op: base_ops.UnaryOp - - def output_type(self, *input_types): - input_type = input_types[0] - assert dtypes.is_array_like(input_type) - inner_type = dtypes.get_array_inner_type(input_type) - out_inner_type = self.map_op.output_type(inner_type) - return dtypes.list_type(out_inner_type) diff --git a/bigframes/operations/base.py b/bigframes/operations/base.py new file mode 100644 index 00000000000..d33befe4da6 --- /dev/null +++ b/bigframes/operations/base.py @@ -0,0 +1,206 @@ +# Copyright 2023 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 + +import pandas as pd + +import bigframes.constants as constants +import bigframes.core.blocks as blocks +import bigframes.core.scalar as scalars +import bigframes.dtypes +import bigframes.operations as ops +import bigframes.series as series +import bigframes.session +import third_party.bigframes_vendored.pandas.pandas._typing as vendored_pandas_typing + +# BigQuery has 1 MB query size limit, 5000 items shouldn't take more than 10% of this depending on data type. +# TODO(tbergeron): Convert to bytes-based limit +MAX_INLINE_SERIES_SIZE = 5000 + + +class SeriesMethods: + def __init__( + self, + data=None, + index: vendored_pandas_typing.Axes | None = None, + dtype: typing.Optional[ + bigframes.dtypes.DtypeString | bigframes.dtypes.Dtype + ] = None, + name: str | None = None, + copy: typing.Optional[bool] = None, + *, + session: typing.Optional[bigframes.session.Session] = None, + ): + block = None + if copy is not None and not copy: + raise ValueError( + f"Series constructor only supports copy=True. {constants.FEEDBACK_LINK}" + ) + if isinstance(data, blocks.Block): + assert len(data.value_columns) == 1 + assert len(data.column_labels) == 1 + block = data + + elif isinstance(data, SeriesMethods): + block = data._get_block() + + if block: + if name: + if not isinstance(name, typing.Hashable): + raise ValueError( + f"BigQuery DataFrames only supports hashable series names. {constants.FEEDBACK_LINK}" + ) + block = block.with_column_labels([name]) + if index: + raise NotImplementedError( + f"Series 'index' constructor parameter not supported when passing BigQuery-backed objects. {constants.FEEDBACK_LINK}" + ) + if dtype: + block = block.multi_apply_unary_op( + block.value_columns, ops.AsTypeOp(dtype) + ) + self._block = block + + else: + import bigframes.pandas + + pd_series = pd.Series( + data=data, index=index, dtype=dtype, name=name # type:ignore + ) + pd_dataframe = pd_series.to_frame() + if pd_series.name is None: + # to_frame will set default numeric column label if unnamed, but we do not support int column label, so must rename + pd_dataframe = pd_dataframe.set_axis(["unnamed_col"], axis=1) + if ( + pd_dataframe.size < MAX_INLINE_SERIES_SIZE + # TODO(swast): Workaround data types limitation in inline data. + and not any( + dt.pyarrow_dtype + for dt in pd_dataframe.dtypes + if isinstance(dt, pd.ArrowDtype) + ) + ): + self._block = blocks.block_from_local(pd_dataframe) + elif session: + self._block = session.read_pandas(pd_dataframe)._get_block() + else: + # Uses default global session + self._block = bigframes.pandas.read_pandas(pd_dataframe)._get_block() + if pd_series.name is None: + self._block = self._block.with_column_labels([None]) + + @property + def _value_column(self) -> str: + return self._block.value_columns[0] + + @property + def _name(self) -> blocks.Label: + return self._block.column_labels[0] + + @property + def _dtype(self): + return self._block.dtypes[0] + + def _set_block(self, block: blocks.Block): + self._block = block + + def _get_block(self) -> blocks.Block: + return self._block + + def _apply_unary_op( + self, + op: ops.UnaryOp, + ) -> series.Series: + """Applies a unary operator to the series.""" + block, result_id = self._block.apply_unary_op( + self._value_column, op, result_label=self._name + ) + return series.Series(block.select_column(result_id)) + + def _apply_binary_op( + self, + other: typing.Any, + op: ops.BinaryOp, + alignment: typing.Literal["outer", "left"] = "outer", + ) -> series.Series: + """Applies a binary operator to the series and other.""" + if isinstance(other, pd.Series): + # TODO: Convert to BigQuery DataFrames series + raise NotImplementedError( + f"Pandas series not supported supported as operand. {constants.FEEDBACK_LINK}" + ) + if isinstance(other, series.Series): + (left, right, block) = self._align(other, how=alignment) + + block, result_id = block.apply_binary_op( + left, right, op, self._value_column + ) + + name = self._name + if ( + isinstance(other, series.Series) + and other.name != self._name + and alignment == "outer" + ): + name = None + + return series.Series( + block.select_column(result_id).assign_label(result_id, name) + ) + else: + partial_op = ops.BinopPartialRight(op, other) + return self._apply_unary_op(partial_op) + + def _apply_corr_aggregation(self, other: series.Series) -> float: + (left, right, block) = self._align(other, how="outer") + + return block.get_corr_stat(left, right) + + def _align(self, other: series.Series, how="outer") -> tuple[str, str, blocks.Block]: # type: ignore + """Aligns the series value with another scalar or series object. Returns new left column id, right column id and joined tabled expression.""" + values, block = self._align_n( + [ + other, + ], + how, + ) + return (values[0], values[1], block) + + def _align_n( + self, + others: typing.Sequence[typing.Union[series.Series, scalars.Scalar]], + how="outer", + ) -> tuple[typing.Sequence[str], blocks.Block]: + value_ids = [self._value_column] + block = self._block + for other in others: + if isinstance(other, series.Series): + combined_index, ( + get_column_left, + get_column_right, + ) = block.index.join(other._block.index, how=how) + value_ids = [ + *[get_column_left[value] for value in value_ids], + get_column_right[other._value_column], + ] + block = combined_index._block + else: + # Will throw if can't interpret as scalar. + dtype = typing.cast(bigframes.dtypes.Dtype, self._dtype) + block, constant_col_id = block.create_constant(other, dtype=dtype) + value_ids = [*value_ids, constant_col_id] + return (value_ids, block) diff --git a/bigframes/operations/base_ops.py b/bigframes/operations/base_ops.py deleted file mode 100644 index a3a0187d2d1..00000000000 --- a/bigframes/operations/base_ops.py +++ /dev/null @@ -1,208 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from __future__ import annotations - -import dataclasses -import typing - -import bigframes.operations.type as op_typing -from bigframes import dtypes - -if typing.TYPE_CHECKING: - # Avoids circular dependency - import bigframes.core.expression - - -class RowOp(typing.Protocol): - @property - def name(self) -> str: ... - - def output_type( - self, *input_types: dtypes.ExpressionType - ) -> dtypes.ExpressionType: ... - - @property - def is_monotonic(self) -> bool: - """Whether the row operation preserves total ordering. Can be pruned from ordering expressions.""" - ... - - @property - def is_bijective(self) -> bool: - """Whether the operation has a 1:1 mapping between inputs and outputs""" - ... - - @property - def deterministic(self) -> bool: - """Whether the operation is deterministic" (given deterministic inputs)""" - ... - - @property - def expensive(self) -> bool: - """Whether the operation is expensive to calculate. Such ops shouldn't be inlined if referenced multiple places.""" - ... - - -@dataclasses.dataclass(frozen=True) -class ScalarOp: - @property - def name(self) -> str: - raise NotImplementedError("RowOp abstract base class has no implementation") - - def output_type(self, *input_types: dtypes.ExpressionType) -> dtypes.ExpressionType: - raise NotImplementedError("Abstract operation has no output type") - - @property - def is_monotonic(self) -> bool: - """Whether the row operation preserves total ordering. Can be pruned from ordering expressions.""" - return False - - @property - def is_bijective(self) -> bool: - """Whether the operation has a 1:1 mapping between inputs and outputs""" - return False - - @property - def deterministic(self) -> bool: - """Whether the operation is deterministic" (given deterministic inputs)""" - return True - - @property - def expensive(self) -> bool: - return False - - -@dataclasses.dataclass(frozen=True) -class NaryOp(ScalarOp): - def as_expr( - self, - *exprs: typing.Union[str, bigframes.core.expression.Expression], - ) -> bigframes.core.expression.Expression: - import bigframes.core.expression - - # Keep this in sync with output_type and compilers - inputs: list[bigframes.core.expression.Expression] = [] - - for expr in exprs: - inputs.append(_convert_expr_input(expr)) - - return bigframes.core.expression.OpExpression( - self, - tuple(inputs), - ) - - -# These classes can be used to create simple ops that don't take local parameters -# All is needed is a unique name, and to register an implementation in ibis_mappings.py -@dataclasses.dataclass(frozen=True) -class UnaryOp(ScalarOp): - @property - def arguments(self) -> int: - return 1 - - def as_expr( - self, input_id: typing.Union[str, bigframes.core.expression.Expression] = "arg" - ) -> bigframes.core.expression.Expression: - import bigframes.core.expression - - return bigframes.core.expression.OpExpression( - self, (_convert_expr_input(input_id),) - ) - - -@dataclasses.dataclass(frozen=True) -class BinaryOp(ScalarOp): - @property - def arguments(self) -> int: - return 2 - - def as_expr( - self, - left_input: typing.Union[str, bigframes.core.expression.Expression] = "arg1", - right_input: typing.Union[str, bigframes.core.expression.Expression] = "arg2", - ) -> bigframes.core.expression.Expression: - import bigframes.core.expression - - return bigframes.core.expression.OpExpression( - self, - ( - _convert_expr_input(left_input), - _convert_expr_input(right_input), - ), - ) - - -@dataclasses.dataclass(frozen=True) -class TernaryOp(ScalarOp): - @property - def arguments(self) -> int: - return 3 - - def as_expr( - self, - input1: typing.Union[str, bigframes.core.expression.Expression] = "arg1", - input2: typing.Union[str, bigframes.core.expression.Expression] = "arg2", - input3: typing.Union[str, bigframes.core.expression.Expression] = "arg3", - ) -> bigframes.core.expression.Expression: - import bigframes.core.expression - - return bigframes.core.expression.OpExpression( - self, - ( - _convert_expr_input(input1), - _convert_expr_input(input2), - _convert_expr_input(input3), - ), - ) - - -def _convert_expr_input( - input: typing.Union[str, bigframes.core.expression.Expression], -) -> bigframes.core.expression.Expression: - """Allows creating column references with just a string""" - import bigframes.core.expression - - if isinstance(input, str): - return bigframes.core.expression.deref(input) - else: - return input - - -# Operation Factories -def create_unary_op( - name: str, type_signature: op_typing.UnaryTypeSignature -) -> type[UnaryOp]: - return dataclasses.make_dataclass( - name, - [ - ("name", typing.ClassVar[str], name), - ("output_type", typing.ClassVar[typing.Callable], type_signature.as_method), - ], - bases=(UnaryOp,), - frozen=True, - ) - - -def create_binary_op( - name: str, type_signature: op_typing.BinaryTypeSignature -) -> type[BinaryOp]: - return dataclasses.make_dataclass( - name, - [ - ("name", typing.ClassVar[str], name), - ("output_type", typing.ClassVar[typing.Callable], type_signature.as_method), - ], - bases=(BinaryOp,), - frozen=True, - ) diff --git a/bigframes/operations/blob.py b/bigframes/operations/blob.py deleted file mode 100644 index 3666ee66602..00000000000 --- a/bigframes/operations/blob.py +++ /dev/null @@ -1,49 +0,0 @@ -# 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. -# 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 bigframes.dataframe -import bigframes.operations as ops -import bigframes.series -from bigframes.core.logging import log_adapter - -FILE_FOLDER_REGEX = r"^.*\/(.*)$" -FILE_EXT_REGEX = r"(\.[0-9a-zA-Z]+$)" - - -@log_adapter.class_logger -class _BlobAccessor: - """ - Internal blob functions for Series and Index. - """ - - def __init__(self, data: bigframes.series.Series): - self._data = data - - def _get_runtime( - self, mode: str, with_metadata: bool = False - ) -> bigframes.series.Series: - s = ( - self._data._apply_unary_op(ops.obj_fetch_metadata_op) - if with_metadata - else self._data - ) - - return s._apply_unary_op(ops.ObjGetAccessUrl(mode=mode)) - - def _read_url(self) -> bigframes.series.Series: - return self._get_runtime(mode="R")._apply_unary_op( - ops.JSONValue(json_path="$.access_urls.read_url") - ) diff --git a/bigframes/operations/blob_ops.py b/bigframes/operations/blob_ops.py deleted file mode 100644 index 21d645a2fee..00000000000 --- a/bigframes/operations/blob_ops.py +++ /dev/null @@ -1,60 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import dataclasses -import typing - -import bigframes.operations.type as op_typing -from bigframes import dtypes -from bigframes.operations import base_ops - -ObjFetchMetadataOp = base_ops.create_unary_op( - name="obj_fetch_metadata", type_signature=op_typing.BLOB_TRANSFORM -) -obj_fetch_metadata_op = ObjFetchMetadataOp() - - -@dataclasses.dataclass(frozen=True) -class ObjGetAccessUrl(base_ops.UnaryOp): - name: typing.ClassVar[str] = "obj_get_access_url" - mode: str # access mode, e.g. R read, W write, RW read & write - duration: typing.Optional[int] = None # duration in microseconds - - def output_type(self, *input_types): - return dtypes.JSON_DTYPE - - -@dataclasses.dataclass(frozen=True) -class ObjMakeRef(base_ops.BinaryOp): - name: typing.ClassVar[str] = "obj_make_ref" - - def output_type(self, *input_types): - if not all(map(dtypes.is_string_like, input_types)): - raise TypeError("obj_make_ref requires string-like arguments") - - return dtypes.OBJ_REF_DTYPE - - -obj_make_ref_op = ObjMakeRef() - - -@dataclasses.dataclass(frozen=True) -class ObjMakeRefJson(base_ops.UnaryOp): - name: typing.ClassVar[str] = "obj_make_ref_json" - - def output_type(self, *input_types): - return dtypes.OBJ_REF_DTYPE - - -obj_make_ref_json_op = ObjMakeRefJson() diff --git a/bigframes/operations/bool_ops.py b/bigframes/operations/bool_ops.py deleted file mode 100644 index ce4406d8f70..00000000000 --- a/bigframes/operations/bool_ops.py +++ /dev/null @@ -1,26 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - - -import bigframes.operations.type as op_typing -from bigframes.operations import base_ops - -AndOp = base_ops.create_binary_op(name="and", type_signature=op_typing.LOGICAL) -and_op = AndOp() - -OrOp = base_ops.create_binary_op(name="or", type_signature=op_typing.LOGICAL) -or_op = OrOp() - -XorOp = base_ops.create_binary_op(name="xor", type_signature=op_typing.LOGICAL) -xor_op = XorOp() diff --git a/bigframes/operations/comparison_ops.py b/bigframes/operations/comparison_ops.py deleted file mode 100644 index f3c01a3536b..00000000000 --- a/bigframes/operations/comparison_ops.py +++ /dev/null @@ -1,40 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - - -import bigframes.operations.type as op_typing -from bigframes.operations import base_ops - -EqOp = base_ops.create_binary_op(name="eq", type_signature=op_typing.COMPARISON) -eq_op = EqOp() - -EqNullsMatchOp = base_ops.create_binary_op( - name="eq_nulls_match", type_signature=op_typing.COMPARISON -) -eq_null_match_op = EqNullsMatchOp() - -NeOp = base_ops.create_binary_op(name="ne", type_signature=op_typing.COMPARISON) -ne_op = NeOp() - -LtOp = base_ops.create_binary_op(name="lt", type_signature=op_typing.COMPARISON) -lt_op = LtOp() - -GtOp = base_ops.create_binary_op(name="gt", type_signature=op_typing.COMPARISON) -gt_op = GtOp() - -LeOp = base_ops.create_binary_op(name="le", type_signature=op_typing.COMPARISON) -le_op = LeOp() - -GeOp = base_ops.create_binary_op(name="ge", type_signature=op_typing.COMPARISON) -ge_op = GeOp() diff --git a/bigframes/operations/date_ops.py b/bigframes/operations/date_ops.py deleted file mode 100644 index 1dbb244afbc..00000000000 --- a/bigframes/operations/date_ops.py +++ /dev/null @@ -1,92 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import dataclasses -import typing - -import bigframes.operations.type as op_typing -from bigframes import dtypes -from bigframes.operations import base_ops - -DayOp = base_ops.create_unary_op( - name="day", - type_signature=op_typing.DATELIKE_ACCESSOR, -) -day_op = DayOp() - -MonthOp = base_ops.create_unary_op( - name="month", - type_signature=op_typing.DATELIKE_ACCESSOR, -) -month_op = MonthOp() - -YearOp = base_ops.create_unary_op( - name="year", - type_signature=op_typing.DATELIKE_ACCESSOR, -) -year_op = YearOp() - -IsoDayOp = base_ops.create_unary_op( - name="iso_day", type_signature=op_typing.DATELIKE_ACCESSOR -) -iso_day_op = IsoDayOp() - -IsoWeekOp = base_ops.create_unary_op( - name="iso_weeek", - type_signature=op_typing.DATELIKE_ACCESSOR, -) -iso_week_op = IsoWeekOp() - -IsoYearOp = base_ops.create_unary_op( - name="iso_year", - type_signature=op_typing.DATELIKE_ACCESSOR, -) -iso_year_op = IsoYearOp() - -DayOfWeekOp = base_ops.create_unary_op( - name="dayofweek", - type_signature=op_typing.DATELIKE_ACCESSOR, -) -dayofweek_op = DayOfWeekOp() - -DayOfYearOp = base_ops.create_unary_op( - name="dayofyear", - type_signature=op_typing.DATELIKE_ACCESSOR, -) -dayofyear_op = DayOfYearOp() - -QuarterOp = base_ops.create_unary_op( - name="quarter", - type_signature=op_typing.DATELIKE_ACCESSOR, -) -quarter_op = QuarterOp() - - -@dataclasses.dataclass(frozen=True) -class DateDiffOp(base_ops.BinaryOp): - name: typing.ClassVar[str] = "date_diff" - - def output_type(self, *input_types: dtypes.ExpressionType) -> dtypes.ExpressionType: - if input_types[0] is not input_types[1]: - raise TypeError( - f"two inputs have different types. left: {input_types[0]}, right: {input_types[1]}" - ) - - if input_types[0] != dtypes.DATE_DTYPE: - raise TypeError("expected date input") - - return dtypes.TIMEDELTA_DTYPE - - -date_diff_op = DateDiffOp() diff --git a/bigframes/operations/datetime_ops.py b/bigframes/operations/datetime_ops.py deleted file mode 100644 index 702466c4f35..00000000000 --- a/bigframes/operations/datetime_ops.py +++ /dev/null @@ -1,159 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import dataclasses -import typing - -import pandas as pd -import pyarrow as pa - -import bigframes.operations.type as op_typing -from bigframes import dtypes -from bigframes.operations import base_ops - -DateOp = base_ops.create_unary_op( - name="date", - type_signature=op_typing.FixedOutputType( - dtypes.is_date_like, dtypes.DATE_DTYPE, description="date-like" - ), -) -date_op = DateOp() - -TimeOp = base_ops.create_unary_op( - name="time", - type_signature=op_typing.FixedOutputType( - dtypes.is_time_like, dtypes.TIME_DTYPE, description="time-like" - ), -) -time_op = TimeOp() - - -@dataclasses.dataclass(frozen=True) -class ParseDatetimeOp(base_ops.UnaryOp): - # TODO: Support strict format - name: typing.ClassVar[str] = "parse_datetime" - - def output_type(self, *input_types: dtypes.ExpressionType) -> dtypes.ExpressionType: - if input_types[0] != dtypes.STRING_DTYPE: - raise TypeError("expected string input") - return pd.ArrowDtype(pa.timestamp("us", tz=None)) - - -@dataclasses.dataclass(frozen=True) -class ParseTimestampOp(base_ops.UnaryOp): - # TODO: Support strict format - name: typing.ClassVar[str] = "parse_timestamp" - - def output_type(self, *input_types: dtypes.ExpressionType) -> dtypes.ExpressionType: - if input_types[0] != dtypes.STRING_DTYPE: - raise TypeError("expected string input") - return pd.ArrowDtype(pa.timestamp("us", tz="UTC")) - - -@dataclasses.dataclass(frozen=True) -class ToDatetimeOp(base_ops.UnaryOp): - name: typing.ClassVar[str] = "to_datetime" - format: typing.Optional[str] = None - unit: typing.Optional[str] = None - - def output_type(self, *input_types: dtypes.ExpressionType) -> dtypes.ExpressionType: - if input_types[0] not in ( - dtypes.FLOAT_DTYPE, - dtypes.INT_DTYPE, - dtypes.STRING_DTYPE, - dtypes.DATE_DTYPE, - dtypes.TIMESTAMP_DTYPE, - dtypes.DATETIME_DTYPE, - ): - raise TypeError("expected string or numeric input") - return pd.ArrowDtype(pa.timestamp("us", tz=None)) - - -@dataclasses.dataclass(frozen=True) -class ToTimestampOp(base_ops.UnaryOp): - name: typing.ClassVar[str] = "to_timestamp" - format: typing.Optional[str] = None - unit: typing.Optional[str] = None - - def output_type(self, *input_types: dtypes.ExpressionType) -> dtypes.ExpressionType: - # Must be numeric or string - if input_types[0] == dtypes.TIMESTAMP_DTYPE: - raise TypeError("Already tz-aware.") - if input_types[0] not in ( - dtypes.FLOAT_DTYPE, - dtypes.INT_DTYPE, - dtypes.STRING_DTYPE, - dtypes.DATE_DTYPE, - dtypes.DATETIME_DTYPE, - ): - raise TypeError("expected string or numeric input") - return pd.ArrowDtype(pa.timestamp("us", tz="UTC")) - - -@dataclasses.dataclass(frozen=True) -class StrftimeOp(base_ops.UnaryOp): - name: typing.ClassVar[str] = "strftime" - date_format: str - - def output_type(self, *input_types: dtypes.ExpressionType) -> dtypes.ExpressionType: - return dtypes.STRING_DTYPE - - -@dataclasses.dataclass(frozen=True) -class UnixSeconds(base_ops.UnaryOp): - name: typing.ClassVar[str] = "unix_seconds" - - def output_type(self, *input_types: dtypes.ExpressionType) -> dtypes.ExpressionType: - if input_types[0] != dtypes.TIMESTAMP_DTYPE: - raise TypeError("expected timestamp input") - return dtypes.INT_DTYPE - - -@dataclasses.dataclass(frozen=True) -class UnixMillis(base_ops.UnaryOp): - name: typing.ClassVar[str] = "unix_millis" - - def output_type(self, *input_types: dtypes.ExpressionType) -> dtypes.ExpressionType: - if input_types[0] != dtypes.TIMESTAMP_DTYPE: - raise TypeError("expected timestamp input") - return dtypes.INT_DTYPE - - -@dataclasses.dataclass(frozen=True) -class UnixMicros(base_ops.UnaryOp): - name: typing.ClassVar[str] = "unix_micros" - - def output_type(self, *input_types: dtypes.ExpressionType) -> dtypes.ExpressionType: - if input_types[0] != dtypes.TIMESTAMP_DTYPE: - raise TypeError("expected timestamp input") - return dtypes.INT_DTYPE - - -@dataclasses.dataclass(frozen=True) -class TimestampDiff(base_ops.BinaryOp): - name: typing.ClassVar[str] = "timestamp_diff" - - def output_type(self, *input_types: dtypes.ExpressionType) -> dtypes.ExpressionType: - if input_types[0] != input_types[1]: - raise TypeError( - f"two inputs have different types. left: {input_types[0]}, right: {input_types[1]}" - ) - - if not dtypes.is_datetime_like(input_types[0]): - raise TypeError("expected timestamp input") - - return dtypes.TIMEDELTA_DTYPE - - -timestamp_diff_op = TimestampDiff() diff --git a/bigframes/operations/datetimes.py b/bigframes/operations/datetimes.py index b16c596120a..1b20c2d593d 100644 --- a/bigframes/operations/datetimes.py +++ b/bigframes/operations/datetimes.py @@ -14,176 +14,53 @@ from __future__ import annotations -import datetime as dt -from typing import Generic, Literal, Optional, TypeVar - -import bigframes_vendored.pandas.core.arrays.datetimelike as vendored_pandas_datetimelike -import bigframes_vendored.pandas.core.indexes.accessor as vendordt -import pandas - -import bigframes.core.col -import bigframes.core.indexes.base as indices import bigframes.operations as ops -from bigframes import dataframe, dtypes, series -from bigframes._tools import docs -from bigframes.core.logging import log_adapter - -_ONE_DAY = pandas.Timedelta("1D") -_ONE_SECOND = pandas.Timedelta("1s") -_ONE_MICRO = pandas.Timedelta("1us") -_SUPPORTED_FREQS = ("Y", "Q", "M", "W", "D", "h", "min", "s", "ms", "us") - - -T = TypeVar("T", series.Series, indices.Index, bigframes.core.col.Expression) - - -# Simpler base class for datetime properties, excludes isocalendar, unit, tz -class DatetimeSimpleMethods(Generic[T]): - def __init__(self, data: T): - self._data: T = data - - # Date accessors - @property - def day(self) -> T: - return self._data._apply_unary_op(ops.day_op) - - @property - def dayofweek(self) -> T: - return self._data._apply_unary_op(ops.dayofweek_op) - - @property - def day_of_week(self) -> T: - return self.dayofweek - - @property - def weekday(self) -> T: - return self.dayofweek +import bigframes.operations.base +import bigframes.series as series +import third_party.bigframes_vendored.pandas.core.indexes.accessor as vendordt - @property - def dayofyear(self) -> T: - return self._data._apply_unary_op(ops.dayofyear_op) - @property - def day_of_year(self) -> T: - return self.dayofyear +class DatetimeMethods( + bigframes.operations.base.SeriesMethods, vendordt.DatetimeProperties +): + __doc__ = vendordt.DatetimeProperties.__doc__ @property - def date(self) -> T: - return self._data._apply_unary_op(ops.date_op) + def day(self) -> series.Series: + return self._apply_unary_op(ops.day_op) @property - def quarter(self) -> T: - return self._data._apply_unary_op(ops.quarter_op) + def dayofweek(self) -> series.Series: + return self._apply_unary_op(ops.dayofweek_op) @property - def year(self) -> T: - return self._data._apply_unary_op(ops.year_op) + def date(self) -> series.Series: + return self._apply_unary_op(ops.date_op) @property - def month(self) -> T: - return self._data._apply_unary_op(ops.month_op) + def hour(self) -> series.Series: + return self._apply_unary_op(ops.hour_op) - # Time accessors @property - def hour(self) -> T: - return self._data._apply_unary_op(ops.hour_op) + def minute(self) -> series.Series: + return self._apply_unary_op(ops.minute_op) @property - def minute(self) -> T: - return self._data._apply_unary_op(ops.minute_op) + def month(self) -> series.Series: + return self._apply_unary_op(ops.month_op) @property - def second(self) -> T: - return self._data._apply_unary_op(ops.second_op) + def second(self) -> series.Series: + return self._apply_unary_op(ops.second_op) @property - def time(self) -> T: - return self._data._apply_unary_op(ops.time_op) + def time(self) -> series.Series: + return self._apply_unary_op(ops.time_op) - # Timedelta accessors @property - def days(self) -> T: - self._check_dtype(dtypes.TIMEDELTA_DTYPE) - - return self._data._apply_binary_op(_ONE_DAY, ops.floordiv_op) + def quarter(self) -> series.Series: + return self._apply_unary_op(ops.quarter_op) @property - def seconds(self) -> T: - self._check_dtype(dtypes.TIMEDELTA_DTYPE) - - return self._data._apply_binary_op(_ONE_DAY, ops.mod_op) // _ONE_SECOND # type: ignore - - @property - def microseconds(self) -> T: - self._check_dtype(dtypes.TIMEDELTA_DTYPE) - - return self._data._apply_binary_op(_ONE_SECOND, ops.mod_op) // _ONE_MICRO # type: ignore - - def total_seconds(self) -> T: - self._check_dtype(dtypes.TIMEDELTA_DTYPE) - - return self._data._apply_binary_op(_ONE_SECOND, ops.div_op) - - def _check_dtype(self, target_dtype: dtypes.Dtype): - if isinstance(self._data, (indices.Index, series.Series)): - if self._data.dtype != target_dtype: - raise TypeError( - f"Expect dtype: {target_dtype}, but got {self._data.dtype}" - ) - return - - def tz_localize(self, tz: Literal["UTC"] | None) -> T: - if tz == "UTC": - return self._data._apply_unary_op(ops.ToTimestampOp()) - - if tz is None: - return self._data._apply_unary_op(ops.ToDatetimeOp()) - - raise ValueError(f"Unsupported timezone {tz}") - - def day_name(self) -> T: - return self.strftime("%A") - - def strftime(self, date_format: str) -> T: - return self._data._apply_unary_op(ops.StrftimeOp(date_format=date_format)) - - def normalize(self) -> T: - return self._data._apply_unary_op(ops.normalize_op) - - def floor(self, freq: str) -> T: - if freq not in _SUPPORTED_FREQS: - raise ValueError(f"freq must be one of {_SUPPORTED_FREQS}") - return self._data._apply_unary_op(ops.FloorDtOp(freq=freq)) # type: ignore - - -# this is the version used by series.dt, and the one that shows up in reference docs -@log_adapter.class_logger -@docs.inherit_docs(vendordt.DatetimeProperties) -@docs.inherit_docs(vendored_pandas_datetimelike.DatelikeOps) -class DatetimeMethods(DatetimeSimpleMethods[bigframes.series.Series]): - def __init__(self, data: series.Series): - super().__init__(data) - - @property - def tz(self) -> Optional[dt.timezone]: - # Assumption: pyarrow dtype - tz_string = self._data._dtype.pyarrow_dtype.tz - if tz_string == "UTC": - return dt.timezone.utc - elif tz_string is None: - return None - else: - raise ValueError(f"Unexpected timezone {tz_string}") - - @property - def unit(self) -> str: - # Assumption: pyarrow dtype - return self._data._dtype.pyarrow_dtype.unit - - def isocalendar(self) -> dataframe.DataFrame: - iso_ops = [ops.iso_year_op, ops.iso_week_op, ops.iso_day_op] - labels = pandas.Index(["year", "week", "day"]) - block = self._data._block.project_exprs( - [op.as_expr(self._data._value_column) for op in iso_ops], labels, drop=True - ) - return dataframe.DataFrame(block) + def year(self) -> series.Series: + return self._apply_unary_op(ops.year_op) diff --git a/bigframes/operations/distance_ops.py b/bigframes/operations/distance_ops.py deleted file mode 100644 index 435308f9c40..00000000000 --- a/bigframes/operations/distance_ops.py +++ /dev/null @@ -1,32 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - - -import bigframes.operations.type as op_typing -from bigframes.operations import base_ops - -CosineDistanceOp = base_ops.create_binary_op( - name="ml_cosine_distance", type_signature=op_typing.VECTOR_METRIC -) -cosine_distance_op = CosineDistanceOp() - -ManhattanDistanceOp = base_ops.create_binary_op( - name="ml_manhattan_distance", type_signature=op_typing.VECTOR_METRIC -) -manhattan_distance_op = ManhattanDistanceOp() - -EuclidDistanceOp = base_ops.create_binary_op( - name="ml_euclidean_distance", type_signature=op_typing.VECTOR_METRIC -) -euclidean_distance_op = EuclidDistanceOp() diff --git a/bigframes/operations/frequency_ops.py b/bigframes/operations/frequency_ops.py deleted file mode 100644 index b94afa72710..00000000000 --- a/bigframes/operations/frequency_ops.py +++ /dev/null @@ -1,74 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import dataclasses -import datetime -import typing - -import numpy as np -import pandas as pd -from pandas.tseries import offsets - -from bigframes import dtypes -from bigframes.operations import base_ops - - -@dataclasses.dataclass(frozen=True) -class FloorDtOp(base_ops.UnaryOp): - name: typing.ClassVar[str] = "floor_dt" - freq: typing.Literal[ - "Y", - "Q", - "M", - "W", - "D", - "h", - "min", - "s", - "ms", - "us", - ] - - def output_type(self, *input_types): - if not dtypes.is_datetime_like(input_types[0]): - raise TypeError("dt floor requires datetime-like arguments") - return input_types[0] - - -@dataclasses.dataclass(frozen=True) -class DatetimeToIntegerLabelOp(base_ops.BinaryOp): - name: typing.ClassVar[str] = "datetime_to_integer_label" - freq: offsets.DateOffset - closed: typing.Optional[typing.Literal["right", "left"]] - origin: typing.Union[ - typing.Union[pd.Timestamp, datetime.datetime, np.datetime64, int, float, str], - typing.Literal["epoch", "start", "start_day", "end", "end_day"], - ] - - def output_type(self, *input_types): - return dtypes.INT_DTYPE - - -@dataclasses.dataclass(frozen=True) -class IntegerLabelToDatetimeOp(base_ops.BinaryOp): - name: typing.ClassVar[str] = "integer_label_to_datetime" - freq: offsets.DateOffset - label: typing.Optional[typing.Literal["right", "left"]] - origin: typing.Union[ - typing.Union[pd.Timestamp, datetime.datetime, np.datetime64, int, float, str], - typing.Literal["epoch", "start", "start_day", "end", "end_day"], - ] - - def output_type(self, *input_types): - return input_types[1] diff --git a/bigframes/operations/generic_ops.py b/bigframes/operations/generic_ops.py deleted file mode 100644 index 9b226ad28d8..00000000000 --- a/bigframes/operations/generic_ops.py +++ /dev/null @@ -1,510 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import dataclasses -import functools -import typing - -import bigframes.operations.type as op_typing -from bigframes import dtypes -from bigframes.operations import base_ops - -InvertOp = base_ops.create_unary_op( - name="invert", - type_signature=op_typing.TypePreserving( - dtypes.is_binary_like, - description="binary-like", - ), -) -invert_op = InvertOp() - -IsNullOp = base_ops.create_unary_op( - name="isnull", - type_signature=op_typing.FixedOutputType( - lambda x: True, dtypes.BOOL_DTYPE, description="nullable" - ), -) -isnull_op = IsNullOp() - -NotNullOp = base_ops.create_unary_op( - name="notnull", - type_signature=op_typing.FixedOutputType( - lambda x: True, dtypes.BOOL_DTYPE, description="nullable" - ), -) -notnull_op = NotNullOp() - - -# Semantics match Python's truth value testing (truthy and falsey objects). -# See https://docs.python.org/3/library/stdtypes.html#truth-value-testing -CoerceToBoolOp = base_ops.create_unary_op( - name="coerce_to_bool", - type_signature=op_typing.FixedOutputType( - dtypes.is_bool_coercable, dtypes.BOOL_DTYPE, description="coercable to bool" - ), -) -CoerceToBoolOp.__doc__ = ( - "Coerce a value to a boolean, matching Python's truth value testing semantics " - "(truthy/falsey). See https://docs.python.org/3/library/stdtypes.html#truth-value-testing" -) -coerce_to_bool_op = CoerceToBoolOp() - -HashOp = base_ops.create_unary_op( - name="hash", - type_signature=op_typing.FixedOutputType( - dtypes.is_string_like, dtypes.INT_DTYPE, description="string-like" - ), -) -hash_op = HashOp() - -# source, dest type -_VALID_CASTS = set( - ( - # INT casts - ( - dtypes.BOOL_DTYPE, - dtypes.INT_DTYPE, - ), - ( - dtypes.FLOAT_DTYPE, - dtypes.INT_DTYPE, - ), - ( - dtypes.NUMERIC_DTYPE, - dtypes.INT_DTYPE, - ), - ( - dtypes.BIGNUMERIC_DTYPE, - dtypes.INT_DTYPE, - ), - ( - dtypes.TIME_DTYPE, - dtypes.INT_DTYPE, - ), - ( - dtypes.DATETIME_DTYPE, - dtypes.INT_DTYPE, - ), - ( - dtypes.TIMESTAMP_DTYPE, - dtypes.INT_DTYPE, - ), - ( - dtypes.TIMEDELTA_DTYPE, - dtypes.INT_DTYPE, - ), - ( - dtypes.STRING_DTYPE, - dtypes.INT_DTYPE, - ), - # Float casts - ( - dtypes.BOOL_DTYPE, - dtypes.FLOAT_DTYPE, - ), - ( - dtypes.NUMERIC_DTYPE, - dtypes.FLOAT_DTYPE, - ), - ( - dtypes.BIGNUMERIC_DTYPE, - dtypes.FLOAT_DTYPE, - ), - ( - dtypes.INT_DTYPE, - dtypes.FLOAT_DTYPE, - ), - ( - dtypes.STRING_DTYPE, - dtypes.FLOAT_DTYPE, - ), - # Bool casts - ( - dtypes.INT_DTYPE, - dtypes.BOOL_DTYPE, - ), - ( - dtypes.FLOAT_DTYPE, - dtypes.BOOL_DTYPE, - ), - # String casts - ( - dtypes.BYTES_DTYPE, - dtypes.STRING_DTYPE, - ), - ( - dtypes.BOOL_DTYPE, - dtypes.STRING_DTYPE, - ), - ( - dtypes.FLOAT_DTYPE, - dtypes.STRING_DTYPE, - ), - ( - dtypes.TIME_DTYPE, - dtypes.STRING_DTYPE, - ), - ( - dtypes.INT_DTYPE, - dtypes.STRING_DTYPE, - ), - ( - dtypes.DATETIME_DTYPE, - dtypes.STRING_DTYPE, - ), - ( - dtypes.TIMESTAMP_DTYPE, - dtypes.STRING_DTYPE, - ), - ( - dtypes.DATE_DTYPE, - dtypes.STRING_DTYPE, - ), - # bytes casts - ( - dtypes.STRING_DTYPE, - dtypes.BYTES_DTYPE, - ), - # decimal casts - ( - dtypes.STRING_DTYPE, - dtypes.NUMERIC_DTYPE, - ), - ( - dtypes.INT_DTYPE, - dtypes.NUMERIC_DTYPE, - ), - ( - dtypes.FLOAT_DTYPE, - dtypes.NUMERIC_DTYPE, - ), - ( - dtypes.BIGNUMERIC_DTYPE, - dtypes.NUMERIC_DTYPE, - ), - # big decimal casts - ( - dtypes.STRING_DTYPE, - dtypes.BIGNUMERIC_DTYPE, - ), - ( - dtypes.INT_DTYPE, - dtypes.BIGNUMERIC_DTYPE, - ), - ( - dtypes.FLOAT_DTYPE, - dtypes.BIGNUMERIC_DTYPE, - ), - ( - dtypes.NUMERIC_DTYPE, - dtypes.BIGNUMERIC_DTYPE, - ), - # time casts - ( - dtypes.INT_DTYPE, - dtypes.TIME_DTYPE, - ), - ( - dtypes.DATETIME_DTYPE, - dtypes.TIME_DTYPE, - ), - ( - dtypes.TIMESTAMP_DTYPE, - dtypes.TIME_DTYPE, - ), - # date casts - ( - dtypes.STRING_DTYPE, - dtypes.DATE_DTYPE, - ), - ( - dtypes.DATETIME_DTYPE, - dtypes.DATE_DTYPE, - ), - ( - dtypes.TIMESTAMP_DTYPE, - dtypes.DATE_DTYPE, - ), - # datetime casts - ( - dtypes.DATE_DTYPE, - dtypes.DATETIME_DTYPE, - ), - ( - dtypes.STRING_DTYPE, - dtypes.DATETIME_DTYPE, - ), - ( - dtypes.TIMESTAMP_DTYPE, - dtypes.DATETIME_DTYPE, - ), - ( - dtypes.INT_DTYPE, - dtypes.DATETIME_DTYPE, - ), - # timestamp casts - ( - dtypes.DATE_DTYPE, - dtypes.TIMESTAMP_DTYPE, - ), - ( - dtypes.STRING_DTYPE, - dtypes.TIMESTAMP_DTYPE, - ), - ( - dtypes.DATETIME_DTYPE, - dtypes.TIMESTAMP_DTYPE, - ), - ( - dtypes.INT_DTYPE, - dtypes.TIMESTAMP_DTYPE, - ), - # timedelta casts - ( - dtypes.INT_DTYPE, - dtypes.TIMEDELTA_DTYPE, - ), - ) -) - - -def _valid_scalar_cast(src: dtypes.Dtype, dst: dtypes.Dtype): - if src == dst: - return True - elif (src, dst) in _VALID_CASTS: - return True - return False - - -def _valid_cast(src: dtypes.Dtype, dst: dtypes.Dtype): - if src == dst: - return True - # TODO: Might need to be more strict within list/array context - if dtypes.is_array_like(src) and dtypes.is_array_like(dst): - src_inner = dtypes.get_array_inner_type(src) - dst_inner = dtypes.get_array_inner_type(dst) - return _valid_cast(src_inner, dst_inner) - if dtypes.is_struct_like(src) and dtypes.is_struct_like(dst): - src_fields = dtypes.get_struct_fields(src) - dst_fields = dtypes.get_struct_fields(dst) - if len(src_fields) != len(dst_fields): - return False - for (_, src_dtype), (_, dst_dtype) in zip( - src_fields.items(), dst_fields.items() - ): - if not _valid_cast(src_dtype, dst_dtype): - return False - return True - - return _valid_scalar_cast(src, dst) - - -@dataclasses.dataclass(frozen=True) -class AsTypeOp(base_ops.UnaryOp): - name: typing.ClassVar[str] = "astype" - # TODO: Convert strings to dtype earlier - to_type: dtypes.Dtype - safe: bool = False - - def output_type(self, *input_types): - if not _valid_cast(input_types[0], self.to_type): - raise TypeError(f"Cannot cast {input_types[0]} to {self.to_type}") - - return self.to_type - - -@dataclasses.dataclass(frozen=True) -class IsInOp(base_ops.UnaryOp): - name: typing.ClassVar[str] = "is_in" - values: typing.Tuple - match_nulls: bool = True - - def output_type(self, *input_types): - return dtypes.BOOL_DTYPE - - -@dataclasses.dataclass(frozen=True) -class MapOp(base_ops.UnaryOp): - name = "map_values" - mappings: typing.Tuple[typing.Tuple[typing.Hashable, typing.Hashable], ...] - - def output_type(self, *input_types): - return input_types[0] - - -FillNaOp = base_ops.create_binary_op(name="fillna", type_signature=op_typing.COERCE) -fillna_op = FillNaOp() - -MaximumOp = base_ops.create_binary_op(name="maximum", type_signature=op_typing.COERCE) -maximum_op = MaximumOp() - -MinimumOp = base_ops.create_binary_op(name="minimum", type_signature=op_typing.COERCE) -minimum_op = MinimumOp() - -CoalesceOp = base_ops.create_binary_op(name="coalesce", type_signature=op_typing.COERCE) -coalesce_op = CoalesceOp() - - -@dataclasses.dataclass(frozen=True) -class WhereOp(base_ops.TernaryOp): - name: typing.ClassVar[str] = "where" - - def output_type(self, *input_types: dtypes.ExpressionType) -> dtypes.ExpressionType: - if input_types[1] != dtypes.BOOL_DTYPE: - raise TypeError("where condition must be a boolean") - return dtypes.coerce_to_common(input_types[0], input_types[2]) - - -where_op = WhereOp() - - -@dataclasses.dataclass(frozen=True) -class ClipOp(base_ops.TernaryOp): - name: typing.ClassVar[str] = "clip" - - def output_type(self, *input_types: dtypes.ExpressionType) -> dtypes.ExpressionType: - return dtypes.coerce_to_common( - input_types[0], dtypes.coerce_to_common(input_types[1], input_types[2]) - ) - - -clip_op = ClipOp() - - -class CaseWhenOp(base_ops.NaryOp): - name: typing.ClassVar[str] = "switch" - - def output_type(self, *input_types: dtypes.ExpressionType) -> dtypes.ExpressionType: - assert len(input_types) % 2 == 0 - # predicate1, output1, predicate2, output2... - if not all(map(lambda x: x == dtypes.BOOL_DTYPE, input_types[::2])): - raise TypeError(f"Case inputs {input_types[::2]} must be boolean-valued") - output_expr_types = input_types[1::2] - return functools.reduce( - lambda t1, t2: dtypes.coerce_to_common(t1, t2), - output_expr_types, - ) - - -case_when_op = CaseWhenOp() - - -# Really doesn't need to be its own op, but allows us to try to get the most compact representation -@dataclasses.dataclass(frozen=True) -class RowKey(base_ops.NaryOp): - name: typing.ClassVar[str] = "rowkey" - - def output_type(self, *input_types: dtypes.ExpressionType) -> dtypes.ExpressionType: - return dtypes.STRING_DTYPE - - @property - def is_bijective(self) -> bool: - """Whether the operation has a 1:1 mapping between inputs and outputs""" - return True - - @property - def deterministic(self) -> bool: - return False - - -@dataclasses.dataclass(frozen=True) -class SqlScalarOp(base_ops.NaryOp): - """An escape to SQL, representing a single column.""" - - 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): - """Represents a local UDF.""" - - name: typing.ClassVar[str] = "py_udf" - fn: typing.Callable - _output_type: dtypes.ExpressionType - - def output_type(self, *input_types: dtypes.ExpressionType) -> dtypes.ExpressionType: - return self._output_type - - -@dataclasses.dataclass(frozen=True) -class GetItemOp(base_ops.UnaryOp): - """Represents subscripting with a statically-known key (e.g. `obj[1]` or `obj["field"]`). - - We must keep this static UnaryOp separate from DynamicGetItemOp (a BinaryOp) - primarily to support Struct field subscripting. Because the return type of a Struct - field lookup depends on the specific field being accessed, and type resolution - (output_type) only has access to input types rather than input values, we must store - the static key inside the operation instance to infer the correct output type. - """ - - name: typing.ClassVar[str] = "getitem" - key: typing.Union[str, int] - - def output_type(self, *input_types): - input_type = input_types[0] - if dtypes.is_struct_like(input_type): - pa_type = input_type.pyarrow_dtype - pa_result_type = pa_type[self.key].type - return dtypes.arrow_dtype_to_bigframes_dtype(pa_result_type) - elif dtypes.is_array_like(input_type): - if not isinstance(self.key, int): - raise TypeError("Array index must be an integer") - return dtypes.arrow_dtype_to_bigframes_dtype( - input_type.pyarrow_dtype.value_type - ) - elif dtypes.is_string_like(input_type): - if not isinstance(self.key, int): - raise TypeError("String index must be an integer") - return dtypes.STRING_DTYPE - else: - raise TypeError(f"Cannot subscript input of type {input_type}") - - -@dataclasses.dataclass(frozen=True) -class DynamicGetItemOp(base_ops.BinaryOp): - """Represents subscripting with a dynamic key expression (e.g. `obj[expr]`). - - Unlike GetItemOp, this operates on 2 dynamic inputs (the container and the key). - Because SQL/BigQuery does not support dynamic struct field access (struct paths must - be statically declared), this operation is only supported for array and string - subscripting, where output type inference does not require knowing the runtime - index value. - """ - - name: typing.ClassVar[str] = "dynamic_getitem" - - def output_type(self, *input_types): - left_type = input_types[0] - right_type = input_types[1] - if not dtypes.is_numeric(right_type): - raise TypeError(f"Subscript index must be numeric type, got {right_type}") - - if dtypes.is_array_like(left_type): - return dtypes.arrow_dtype_to_bigframes_dtype( - left_type.pyarrow_dtype.value_type - ) - elif dtypes.is_string_like(left_type): - return dtypes.STRING_DTYPE - else: - raise TypeError(f"Cannot dynamically subscript input of type {left_type}") diff --git a/bigframes/operations/geo_ops.py b/bigframes/operations/geo_ops.py deleted file mode 100644 index a965ddca2b9..00000000000 --- a/bigframes/operations/geo_ops.py +++ /dev/null @@ -1,160 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import dataclasses -from typing import Optional - -import bigframes.operations.type as op_typing -from bigframes import dtypes -from bigframes.operations import base_ops - -GeoStAstextOp = base_ops.create_unary_op( - name="geo_st_astext", - type_signature=op_typing.FixedOutputType( - dtypes.is_geo_like, dtypes.STRING_DTYPE, description="geo-like" - ), -) -geo_st_astext_op = GeoStAstextOp() - -GeoStBoundaryOp = base_ops.create_unary_op( - name="geo_st_boundary", - type_signature=op_typing.FixedOutputType( - dtypes.is_geo_like, dtypes.GEO_DTYPE, description="geo-like" - ), -) -geo_st_boundary_op = GeoStBoundaryOp() - -GeoStCentroidOp = base_ops.create_unary_op( - name="geo_st_centroid", - type_signature=op_typing.FixedOutputType( - dtypes.is_geo_like, dtypes.GEO_DTYPE, description="geo-like" - ), -) -geo_st_centroid_op = GeoStCentroidOp() - -GeoStConvexhullOp = base_ops.create_unary_op( - name="geo_st_convexhull", - type_signature=op_typing.FixedOutputType( - dtypes.is_geo_like, dtypes.GEO_DTYPE, description="geo-like" - ), -) -geo_st_convexhull_op = GeoStConvexhullOp() - -GeoStDifferenceOp = base_ops.create_binary_op( - name="geo_st_difference", type_signature=op_typing.BinaryGeo() -) -geo_st_difference_op = GeoStDifferenceOp() - -GeoStGeogfromtextOp = base_ops.create_unary_op( - name="geo_st_geogfromtext", - type_signature=op_typing.FixedOutputType( - dtypes.is_string_like, dtypes.GEO_DTYPE, description="string-like" - ), -) -geo_st_geogfromtext_op = GeoStGeogfromtextOp() - -GeoStGeogpointOp = base_ops.create_binary_op( - name="geo_st_geogpoint", type_signature=op_typing.BinaryNumericGeo() -) -geo_st_geogpoint_op = GeoStGeogpointOp() - -GeoStIsclosedOp = base_ops.create_unary_op( - name="geo_st_isclosed", - type_signature=op_typing.FixedOutputType( - dtypes.is_geo_like, dtypes.BOOL_DTYPE, description="geo-like" - ), -) -geo_st_isclosed_op = GeoStIsclosedOp() - -GeoXOp = base_ops.create_unary_op( - name="geo_x", - type_signature=op_typing.FixedOutputType( - dtypes.is_geo_like, dtypes.FLOAT_DTYPE, description="geo-like" - ), -) -geo_x_op = GeoXOp() - -GeoYOp = base_ops.create_unary_op( - name="geo_y", - type_signature=op_typing.FixedOutputType( - dtypes.is_geo_like, dtypes.FLOAT_DTYPE, description="geo-like" - ), -) -geo_y_op = GeoYOp() - -GeoStIntersectionOp = base_ops.create_binary_op( - name="geo_st_intersection", type_signature=op_typing.BinaryGeo() -) -geo_st_intersection_op = GeoStIntersectionOp() - - -@dataclasses.dataclass(frozen=True) -class GeoStBufferOp(base_ops.UnaryOp): - name = "st_buffer" - buffer_radius: float - num_seg_quarter_circle: float - use_spheroid: bool - - def output_type(self, *input_types: dtypes.ExpressionType) -> dtypes.ExpressionType: - return dtypes.GEO_DTYPE - - -@dataclasses.dataclass(frozen=True) -class GeoStDistanceOp(base_ops.BinaryOp): - name = "st_distance" - use_spheroid: bool - - def output_type(self, *input_types: dtypes.ExpressionType) -> dtypes.ExpressionType: - return dtypes.FLOAT_DTYPE - - -@dataclasses.dataclass(frozen=True) -class GeoStLengthOp(base_ops.UnaryOp): - name = "geo_st_length" - use_spheroid: bool = False - - def output_type(self, *input_types: dtypes.ExpressionType) -> dtypes.ExpressionType: - return dtypes.FLOAT_DTYPE - - -@dataclasses.dataclass(frozen=True) -class GeoStRegionStatsOp(base_ops.UnaryOp): - """See: https://cloud.google.com/bigquery/docs/reference/standard-sql/geography_functions#st_regionstats""" - - name = "geo_st_regionstats" - raster_id: str - band: Optional[str] - include: Optional[str] - options: Optional[str] - - def output_type(self, *input_types: dtypes.ExpressionType) -> dtypes.ExpressionType: - return dtypes.struct_type( - [ - ("min", dtypes.FLOAT_DTYPE), - ("max", dtypes.FLOAT_DTYPE), - ("sum", dtypes.FLOAT_DTYPE), - ("count", dtypes.INT_DTYPE), - ("mean", dtypes.FLOAT_DTYPE), - ("area", dtypes.FLOAT_DTYPE), - ] - ) - - -@dataclasses.dataclass(frozen=True) -class GeoStSimplifyOp(base_ops.UnaryOp): - name = "st_simplify" - tolerance_meters: float - - def output_type(self, *input_types: dtypes.ExpressionType) -> dtypes.ExpressionType: - return dtypes.GEO_DTYPE diff --git a/bigframes/operations/googlesql/__init__.py b/bigframes/operations/googlesql/__init__.py deleted file mode 100644 index edec5b84f8e..00000000000 --- a/bigframes/operations/googlesql/__init__.py +++ /dev/null @@ -1,106 +0,0 @@ -# Copyright 2026 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - - -from __future__ import annotations - -import dataclasses -import typing - -import bigframes.operations as ops -from bigframes import dtypes - - -@dataclasses.dataclass(frozen=True) -class ArgSpec: - arg_name: str | None = None - optional: bool = False - is_vararg: bool = False - const_only: bool = False - - -@dataclasses.dataclass(frozen=True) -class OpSignature: - # Detailed specs for each parameter. This is particularly relevant for ren - arg_specs: typing.Sequence[ArgSpec] - resolve_return_type: typing.Any - has_varargs: bool = False - - -# Eventually we should migrate every op over to this that can be directly emitted 1:1 as a sql op -# This will allow us to fully lower to pure SQL dialect expressions and emitting sql text is trivial. -@dataclasses.dataclass(frozen=True) -class GoogleSqlScalarOp(ops.NaryOp): - name: typing.ClassVar[str] = "googlesql_scalar" - - # syntax - sql_name: str - args: tuple[ArgSpec, ...] - # typing - signature: typing.Callable[..., dtypes.ExpressionType] - - # semantics - is_deterministic: bool = True - - @property - def deterministic(self) -> bool: - return self.is_deterministic - - def output_type(self, *input_types: dtypes.ExpressionType) -> dtypes.ExpressionType: - return self.signature(*input_types) - - -RAND = GoogleSqlScalarOp( - "RAND", args=(), is_deterministic=False, signature=lambda: dtypes.FLOAT_DTYPE -) - - -def _check_geo_input( - t: dtypes.ExpressionType, out: dtypes.ExpressionType -) -> dtypes.ExpressionType: - if t is not None and not dtypes.is_geo_like(t): - raise TypeError(f"Type {t} is not supported. Type must be geo-like") - return out - - -def _check_simplify_inputs( - geo: dtypes.ExpressionType, tol: dtypes.ExpressionType -) -> dtypes.ExpressionType: - if geo is not None and not dtypes.is_geo_like(geo): - raise TypeError(f"Type {geo} is not supported. Type must be geo-like") - if tol is not None and not dtypes.is_numeric(tol): - raise TypeError(f"Type {tol} is not supported. Type must be numeric") - return dtypes.GEO_DTYPE - - -ST_AREA = GoogleSqlScalarOp( - "ST_AREA", - args=(ArgSpec(),), - is_deterministic=True, - signature=lambda geo: _check_geo_input(geo, dtypes.FLOAT_DTYPE), -) - -ST_CENTROID = GoogleSqlScalarOp( - "ST_CENTROID", - args=(ArgSpec(),), - is_deterministic=True, - signature=lambda geo: _check_geo_input(geo, dtypes.GEO_DTYPE), -) - -ST_SIMPLIFY = GoogleSqlScalarOp( - "ST_SIMPLIFY", - args=(ArgSpec(), ArgSpec()), - is_deterministic=True, - signature=_check_simplify_inputs, -) diff --git a/bigframes/operations/googlesql/aead.py b/bigframes/operations/googlesql/aead.py deleted file mode 100644 index f719d7d6989..00000000000 --- a/bigframes/operations/googlesql/aead.py +++ /dev/null @@ -1,122 +0,0 @@ -# Copyright 2026 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. -# -# DO NOT MODIFY THIS FILE DIRECTLY. -# This file was generated from: scripts/data/sql-functions/aead.yaml -# by the script: scripts/generate_bigframes_bigquery.py - -from __future__ import annotations - -from typing import Literal, Union - -import bigframes.core.col -import bigframes.core.googlesql -import bigframes.core.sentinels as sentinels -import bigframes.series as series -from bigframes import dtypes -from bigframes.operations import googlesql - -_DECRYPT_BYTES_OP = googlesql.GoogleSqlScalarOp( - "AEAD.DECRYPT_BYTES", - args=(googlesql.ArgSpec(), googlesql.ArgSpec(), googlesql.ArgSpec()), - signature=lambda *args: dtypes.BYTES_DTYPE, -) -_DECRYPT_STRING_OP = googlesql.GoogleSqlScalarOp( - "AEAD.DECRYPT_STRING", - args=(googlesql.ArgSpec(), googlesql.ArgSpec(), googlesql.ArgSpec()), - signature=lambda *args: dtypes.STRING_DTYPE, -) -_ENCRYPT_OP = googlesql.GoogleSqlScalarOp( - "AEAD.ENCRYPT", - args=(googlesql.ArgSpec(), googlesql.ArgSpec(), googlesql.ArgSpec()), - signature=lambda *args: dtypes.BYTES_DTYPE, -) - - -def decrypt_bytes( - keyset: Union[ - series.Series, - bigframes.core.col.Expression, - Union[Literal[sentinels.Sentinel.ARGUMENT_DEFAULT], bytes, dict], - ], - ciphertext: Union[ - series.Series, - bigframes.core.col.Expression, - Union[Literal[sentinels.Sentinel.ARGUMENT_DEFAULT], bytes], - ], - additional_data: Union[ - series.Series, - bigframes.core.col.Expression, - Union[Literal[sentinels.Sentinel.ARGUMENT_DEFAULT], bytes], - ], -) -> Union[series.Series, bigframes.core.col.Expression]: - """Uses the matching key from keyset to decrypt ciphertext and verifies the integrity of the data using additional_data. Returns an error if decryption or verification fails.""" - return bigframes.core.googlesql.apply_googlesql_scalar_op( - _DECRYPT_BYTES_OP, - keyset, - ciphertext, - additional_data, - ) - - -def decrypt_string( - keyset: Union[ - series.Series, - bigframes.core.col.Expression, - Union[Literal[sentinels.Sentinel.ARGUMENT_DEFAULT], bytes, dict], - ], - ciphertext: Union[ - series.Series, - bigframes.core.col.Expression, - Union[Literal[sentinels.Sentinel.ARGUMENT_DEFAULT], bytes], - ], - additional_data: Union[ - series.Series, - bigframes.core.col.Expression, - Union[Literal[sentinels.Sentinel.ARGUMENT_DEFAULT], str], - ], -) -> Union[series.Series, bigframes.core.col.Expression]: - """Like AEAD.DECRYPT_BYTES, but where additional_data is of type STRING.""" - return bigframes.core.googlesql.apply_googlesql_scalar_op( - _DECRYPT_STRING_OP, - keyset, - ciphertext, - additional_data, - ) - - -def encrypt( - keyset: Union[ - series.Series, - bigframes.core.col.Expression, - Union[Literal[sentinels.Sentinel.ARGUMENT_DEFAULT], bytes, dict], - ], - plaintext: Union[ - series.Series, - bigframes.core.col.Expression, - Union[Literal[sentinels.Sentinel.ARGUMENT_DEFAULT], bytes, str], - ], - additional_data: Union[ - series.Series, - bigframes.core.col.Expression, - Union[Literal[sentinels.Sentinel.ARGUMENT_DEFAULT], bytes, str], - ], -) -> Union[series.Series, bigframes.core.col.Expression]: - """Encrypts plaintext using the primary cryptographic key in keyset. The algorithm of the primary key must be AEAD_AES_GCM_256. Binds the ciphertext to the context defined by additional_data. Returns NULL if any input is NULL.""" - return bigframes.core.googlesql.apply_googlesql_scalar_op( - _ENCRYPT_OP, - keyset, - plaintext, - additional_data, - ) diff --git a/bigframes/operations/googlesql/global_namespace/__init__.py b/bigframes/operations/googlesql/global_namespace/__init__.py deleted file mode 100644 index 58d482ea386..00000000000 --- a/bigframes/operations/googlesql/global_namespace/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -# Copyright 2026 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/bigframes/operations/googlesql/global_namespace/aead_encryption.py b/bigframes/operations/googlesql/global_namespace/aead_encryption.py deleted file mode 100644 index 4613ddd7e6d..00000000000 --- a/bigframes/operations/googlesql/global_namespace/aead_encryption.py +++ /dev/null @@ -1,122 +0,0 @@ -# Copyright 2026 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. -# -# DO NOT MODIFY THIS FILE DIRECTLY. -# This file was generated from: scripts/data/sql-functions/global_namespace/aead_encryption.yaml -# by the script: scripts/generate_bigframes_bigquery.py - -from __future__ import annotations - -from typing import Literal, Union - -import bigframes.core.col -import bigframes.core.googlesql -import bigframes.core.sentinels as sentinels -import bigframes.series as series -from bigframes import dtypes -from bigframes.operations import googlesql - -_DETERMINISTIC_DECRYPT_BYTES_OP = googlesql.GoogleSqlScalarOp( - "DETERMINISTIC_DECRYPT_BYTES", - args=(googlesql.ArgSpec(), googlesql.ArgSpec(), googlesql.ArgSpec()), - signature=lambda *args: dtypes.BYTES_DTYPE, -) -_DETERMINISTIC_DECRYPT_STRING_OP = googlesql.GoogleSqlScalarOp( - "DETERMINISTIC_DECRYPT_STRING", - args=(googlesql.ArgSpec(), googlesql.ArgSpec(), googlesql.ArgSpec()), - signature=lambda *args: dtypes.STRING_DTYPE, -) -_DETERMINISTIC_ENCRYPT_OP = googlesql.GoogleSqlScalarOp( - "DETERMINISTIC_ENCRYPT", - args=(googlesql.ArgSpec(), googlesql.ArgSpec(), googlesql.ArgSpec()), - signature=lambda *args: dtypes.BYTES_DTYPE, -) - - -def deterministic_decrypt_bytes( - keyset: Union[ - series.Series, - bigframes.core.col.Expression, - Union[Literal[sentinels.Sentinel.ARGUMENT_DEFAULT], bytes, dict], - ], - ciphertext: Union[ - series.Series, - bigframes.core.col.Expression, - Union[Literal[sentinels.Sentinel.ARGUMENT_DEFAULT], bytes], - ], - additional_data: Union[ - series.Series, - bigframes.core.col.Expression, - Union[Literal[sentinels.Sentinel.ARGUMENT_DEFAULT], bytes], - ], -) -> Union[series.Series, bigframes.core.col.Expression]: - """Uses the matching key from `keyset` to decrypt `ciphertext` and verifies the integrity of the data using `additional_data`. Returns an error if decryption fails.""" - return bigframes.core.googlesql.apply_googlesql_scalar_op( - _DETERMINISTIC_DECRYPT_BYTES_OP, - keyset, - ciphertext, - additional_data, - ) - - -def deterministic_decrypt_string( - keyset: Union[ - series.Series, - bigframes.core.col.Expression, - Union[Literal[sentinels.Sentinel.ARGUMENT_DEFAULT], bytes, dict], - ], - ciphertext: Union[ - series.Series, - bigframes.core.col.Expression, - Union[Literal[sentinels.Sentinel.ARGUMENT_DEFAULT], bytes], - ], - additional_data: Union[ - series.Series, - bigframes.core.col.Expression, - Union[Literal[sentinels.Sentinel.ARGUMENT_DEFAULT], str], - ], -) -> Union[series.Series, bigframes.core.col.Expression]: - """Like `DETERMINISTIC_DECRYPT_BYTES`, but where plaintext is of type STRING.""" - return bigframes.core.googlesql.apply_googlesql_scalar_op( - _DETERMINISTIC_DECRYPT_STRING_OP, - keyset, - ciphertext, - additional_data, - ) - - -def deterministic_encrypt( - keyset: Union[ - series.Series, - bigframes.core.col.Expression, - Union[Literal[sentinels.Sentinel.ARGUMENT_DEFAULT], bytes, dict], - ], - plaintext: Union[ - series.Series, - bigframes.core.col.Expression, - Union[Literal[sentinels.Sentinel.ARGUMENT_DEFAULT], bytes, str], - ], - additional_data: Union[ - series.Series, - bigframes.core.col.Expression, - Union[Literal[sentinels.Sentinel.ARGUMENT_DEFAULT], bytes, str], - ], -) -> Union[series.Series, bigframes.core.col.Expression]: - """Encrypts `plaintext` using the primary cryptographic key in `keyset` using deterministic AEAD. The algorithm of the primary key must be `DETERMINISTIC_AEAD_AES_SIV_CMAC_256`. Binds the ciphertext to the context defined by `additional_data`. Returns `NULL` if any input is `NULL`.""" - return bigframes.core.googlesql.apply_googlesql_scalar_op( - _DETERMINISTIC_ENCRYPT_OP, - keyset, - plaintext, - additional_data, - ) diff --git a/bigframes/operations/googlesql/global_namespace/array.py b/bigframes/operations/googlesql/global_namespace/array.py deleted file mode 100644 index 94adbad1839..00000000000 --- a/bigframes/operations/googlesql/global_namespace/array.py +++ /dev/null @@ -1,892 +0,0 @@ -# Copyright 2026 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. -# -# DO NOT MODIFY THIS FILE DIRECTLY. -# This file was generated from: scripts/data/sql-functions/global_namespace/array.yaml -# by the script: scripts/generate_bigframes_bigquery.py - -from __future__ import annotations - -import decimal -from typing import Any, Literal, Union - -import bigframes.core.col -import bigframes.core.googlesql -import bigframes.core.sentinels as sentinels -import bigframes.series as series -from bigframes import dtypes -from bigframes.operations import googlesql - - -def _ARRAY_CONCAT_SIG(*args): - # Pad args with None to match max expected args - args = args + (None,) * (2 - len(args)) - # Try matching impl 0 - any1_val = None - match_ok = True - if match_ok and args[0] is not None: - if not dtypes.is_array_like(args[0]): - match_ok = False - else: - inner = dtypes.get_array_inner_type(args[0]) - if any1_val is not None: - try: - any1_val = dtypes.coerce_to_common(any1_val, inner) - except TypeError: - match_ok = False - else: - any1_val = inner - if match_ok and args[1] is not None: - if not dtypes.is_array_like(args[1]): - match_ok = False - else: - inner = dtypes.get_array_inner_type(args[1]) - if any1_val is not None: - try: - any1_val = dtypes.coerce_to_common(any1_val, inner) - except TypeError: - match_ok = False - else: - any1_val = inner - if match_ok: - if any1_val is not None: - return dtypes.list_type(any1_val) - else: - return None - - raise TypeError( - f"Could not find matching signature for array_concat with argument types: {[str(t) for t in args]}" - ) - - -_ARRAY_CONCAT_OP = googlesql.GoogleSqlScalarOp( - "ARRAY_CONCAT", - args=(googlesql.ArgSpec(), googlesql.ArgSpec()), - signature=_ARRAY_CONCAT_SIG, -) - - -def _ARRAY_FIRST_SIG(*args): - # Pad args with None to match max expected args - args = args + (None,) * (1 - len(args)) - # Try matching impl 0 - any1_val = None - match_ok = True - if match_ok and args[0] is not None: - if not dtypes.is_array_like(args[0]): - match_ok = False - else: - inner = dtypes.get_array_inner_type(args[0]) - if any1_val is not None: - try: - any1_val = dtypes.coerce_to_common(any1_val, inner) - except TypeError: - match_ok = False - else: - any1_val = inner - if match_ok: - return any1_val - - raise TypeError( - f"Could not find matching signature for array_first with argument types: {[str(t) for t in args]}" - ) - - -_ARRAY_FIRST_OP = googlesql.GoogleSqlScalarOp( - "ARRAY_FIRST", - args=(googlesql.ArgSpec(),), - signature=_ARRAY_FIRST_SIG, -) - - -def _ARRAY_FIRST_N_SIG(*args): - # Pad args with None to match max expected args - args = args + (None,) * (2 - len(args)) - # Try matching impl 0 - any1_val = None - match_ok = True - if match_ok and args[0] is not None: - if not dtypes.is_array_like(args[0]): - match_ok = False - else: - inner = dtypes.get_array_inner_type(args[0]) - if any1_val is not None: - try: - any1_val = dtypes.coerce_to_common(any1_val, inner) - except TypeError: - match_ok = False - else: - any1_val = inner - if match_ok and args[1] is not None: - try: - if dtypes.coerce_to_common(args[1], dtypes.INT_DTYPE) != dtypes.INT_DTYPE: - match_ok = False - except TypeError: - match_ok = False - if match_ok: - if any1_val is not None: - return dtypes.list_type(any1_val) - else: - return None - - raise TypeError( - f"Could not find matching signature for array_first_n with argument types: {[str(t) for t in args]}" - ) - - -_ARRAY_FIRST_N_OP = googlesql.GoogleSqlScalarOp( - "ARRAY_FIRST_N", - args=(googlesql.ArgSpec(), googlesql.ArgSpec()), - signature=_ARRAY_FIRST_N_SIG, -) -_ARRAY_INCLUDES_OP = googlesql.GoogleSqlScalarOp( - "ARRAY_INCLUDES", - args=(googlesql.ArgSpec(), googlesql.ArgSpec()), - signature=lambda *args: dtypes.BOOL_DTYPE, -) -_ARRAY_INCLUDES_ALL_OP = googlesql.GoogleSqlScalarOp( - "ARRAY_INCLUDES_ALL", - args=(googlesql.ArgSpec(), googlesql.ArgSpec()), - signature=lambda *args: dtypes.BOOL_DTYPE, -) -_ARRAY_INCLUDES_ANY_OP = googlesql.GoogleSqlScalarOp( - "ARRAY_INCLUDES_ANY", - args=(googlesql.ArgSpec(), googlesql.ArgSpec()), - signature=lambda *args: dtypes.BOOL_DTYPE, -) -_ARRAY_IS_DISTINCT_OP = googlesql.GoogleSqlScalarOp( - "ARRAY_IS_DISTINCT", - args=(googlesql.ArgSpec(),), - signature=lambda *args: dtypes.BOOL_DTYPE, -) - - -def _ARRAY_LAST_SIG(*args): - # Pad args with None to match max expected args - args = args + (None,) * (1 - len(args)) - # Try matching impl 0 - any1_val = None - match_ok = True - if match_ok and args[0] is not None: - if not dtypes.is_array_like(args[0]): - match_ok = False - else: - inner = dtypes.get_array_inner_type(args[0]) - if any1_val is not None: - try: - any1_val = dtypes.coerce_to_common(any1_val, inner) - except TypeError: - match_ok = False - else: - any1_val = inner - if match_ok: - return any1_val - - raise TypeError( - f"Could not find matching signature for array_last with argument types: {[str(t) for t in args]}" - ) - - -_ARRAY_LAST_OP = googlesql.GoogleSqlScalarOp( - "ARRAY_LAST", - args=(googlesql.ArgSpec(),), - signature=_ARRAY_LAST_SIG, -) -_ARRAY_LENGTH_OP = googlesql.GoogleSqlScalarOp( - "ARRAY_LENGTH", - args=(googlesql.ArgSpec(),), - signature=lambda *args: dtypes.INT_DTYPE, -) - - -def _ARRAY_REVERSE_SIG(*args): - # Pad args with None to match max expected args - args = args + (None,) * (1 - len(args)) - # Try matching impl 0 - any1_val = None - match_ok = True - if match_ok and args[0] is not None: - if not dtypes.is_array_like(args[0]): - match_ok = False - else: - inner = dtypes.get_array_inner_type(args[0]) - if any1_val is not None: - try: - any1_val = dtypes.coerce_to_common(any1_val, inner) - except TypeError: - match_ok = False - else: - any1_val = inner - if match_ok: - if any1_val is not None: - return dtypes.list_type(any1_val) - else: - return None - - raise TypeError( - f"Could not find matching signature for array_reverse with argument types: {[str(t) for t in args]}" - ) - - -_ARRAY_REVERSE_OP = googlesql.GoogleSqlScalarOp( - "ARRAY_REVERSE", - args=(googlesql.ArgSpec(),), - signature=_ARRAY_REVERSE_SIG, -) - - -def _ARRAY_SLICE_SIG(*args): - # Pad args with None to match max expected args - args = args + (None,) * (3 - len(args)) - # Try matching impl 0 - any1_val = None - match_ok = True - if match_ok and args[0] is not None: - if not dtypes.is_array_like(args[0]): - match_ok = False - else: - inner = dtypes.get_array_inner_type(args[0]) - if any1_val is not None: - try: - any1_val = dtypes.coerce_to_common(any1_val, inner) - except TypeError: - match_ok = False - else: - any1_val = inner - if match_ok and args[1] is not None: - try: - if dtypes.coerce_to_common(args[1], dtypes.INT_DTYPE) != dtypes.INT_DTYPE: - match_ok = False - except TypeError: - match_ok = False - if match_ok and args[2] is not None: - try: - if dtypes.coerce_to_common(args[2], dtypes.INT_DTYPE) != dtypes.INT_DTYPE: - match_ok = False - except TypeError: - match_ok = False - if match_ok: - if any1_val is not None: - return dtypes.list_type(any1_val) - else: - return None - - raise TypeError( - f"Could not find matching signature for array_slice with argument types: {[str(t) for t in args]}" - ) - - -_ARRAY_SLICE_OP = googlesql.GoogleSqlScalarOp( - "ARRAY_SLICE", - args=(googlesql.ArgSpec(), googlesql.ArgSpec(), googlesql.ArgSpec()), - signature=_ARRAY_SLICE_SIG, -) - - -def _ARRAY_TO_STRING_SIG(*args): - # Pad args with None to match max expected args - args = args + (None,) * (3 - len(args)) - # Try matching impl 0 - match_ok = True - if match_ok and args[0] is not None: - if not dtypes.is_array_like(args[0]): - match_ok = False - else: - inner = dtypes.get_array_inner_type(args[0]) - try: - if ( - dtypes.coerce_to_common(inner, dtypes.STRING_DTYPE) - != dtypes.STRING_DTYPE - ): - match_ok = False - except TypeError: - match_ok = False - if match_ok and args[1] is not None: - try: - if ( - dtypes.coerce_to_common(args[1], dtypes.STRING_DTYPE) - != dtypes.STRING_DTYPE - ): - match_ok = False - except TypeError: - match_ok = False - if match_ok and args[2] is not None: - try: - if ( - dtypes.coerce_to_common(args[2], dtypes.STRING_DTYPE) - != dtypes.STRING_DTYPE - ): - match_ok = False - except TypeError: - match_ok = False - if match_ok: - return dtypes.STRING_DTYPE - - # Try matching impl 1 - match_ok = True - if match_ok and args[0] is not None: - if not dtypes.is_array_like(args[0]): - match_ok = False - else: - inner = dtypes.get_array_inner_type(args[0]) - try: - if ( - dtypes.coerce_to_common(inner, dtypes.BYTES_DTYPE) - != dtypes.BYTES_DTYPE - ): - match_ok = False - except TypeError: - match_ok = False - if match_ok and args[1] is not None: - try: - if ( - dtypes.coerce_to_common(args[1], dtypes.BYTES_DTYPE) - != dtypes.BYTES_DTYPE - ): - match_ok = False - except TypeError: - match_ok = False - if match_ok and args[2] is not None: - try: - if ( - dtypes.coerce_to_common(args[2], dtypes.BYTES_DTYPE) - != dtypes.BYTES_DTYPE - ): - match_ok = False - except TypeError: - match_ok = False - if match_ok: - return dtypes.BYTES_DTYPE - - raise TypeError( - f"Could not find matching signature for array_to_string with argument types: {[str(t) for t in args]}" - ) - - -_ARRAY_TO_STRING_OP = googlesql.GoogleSqlScalarOp( - "ARRAY_TO_STRING", - args=(googlesql.ArgSpec(), googlesql.ArgSpec(), googlesql.ArgSpec(optional=True)), - signature=_ARRAY_TO_STRING_SIG, -) - - -def _FLATTEN_SIG(*args): - # Pad args with None to match max expected args - args = args + (None,) * (2 - len(args)) - # Try matching impl 0 - any1_val = None - match_ok = True - if match_ok and args[0] is not None: - if not dtypes.is_array_like(args[0]): - match_ok = False - else: - inner = dtypes.get_array_inner_type(args[0]) - if any1_val is not None: - try: - any1_val = dtypes.coerce_to_common(any1_val, inner) - except TypeError: - match_ok = False - else: - any1_val = inner - if match_ok and args[1] is not None: - try: - if dtypes.coerce_to_common(args[1], dtypes.INT_DTYPE) != dtypes.INT_DTYPE: - match_ok = False - except TypeError: - match_ok = False - if match_ok: - if any1_val is not None: - return dtypes.list_type(any1_val) - else: - return None - - raise TypeError( - f"Could not find matching signature for flatten with argument types: {[str(t) for t in args]}" - ) - - -_FLATTEN_OP = googlesql.GoogleSqlScalarOp( - "FLATTEN", - args=(googlesql.ArgSpec(), googlesql.ArgSpec(arg_name="depth", optional=True)), - signature=_FLATTEN_SIG, -) - - -def _GENERATE_ARRAY_SIG(*args): - # Pad args with None to match max expected args - args = args + (None,) * (3 - len(args)) - # Try matching impl 0 - match_ok = True - if match_ok and args[0] is not None: - try: - if dtypes.coerce_to_common(args[0], dtypes.INT_DTYPE) != dtypes.INT_DTYPE: - match_ok = False - except TypeError: - match_ok = False - if match_ok and args[1] is not None: - try: - if dtypes.coerce_to_common(args[1], dtypes.INT_DTYPE) != dtypes.INT_DTYPE: - match_ok = False - except TypeError: - match_ok = False - if match_ok and args[2] is not None: - try: - if dtypes.coerce_to_common(args[2], dtypes.INT_DTYPE) != dtypes.INT_DTYPE: - match_ok = False - except TypeError: - match_ok = False - if match_ok: - return dtypes.list_type(dtypes.INT_DTYPE) - - # Try matching impl 1 - match_ok = True - if match_ok and args[0] is not None: - try: - if ( - dtypes.coerce_to_common(args[0], dtypes.NUMERIC_DTYPE) - != dtypes.NUMERIC_DTYPE - ): - match_ok = False - except TypeError: - match_ok = False - if match_ok and args[1] is not None: - try: - if ( - dtypes.coerce_to_common(args[1], dtypes.NUMERIC_DTYPE) - != dtypes.NUMERIC_DTYPE - ): - match_ok = False - except TypeError: - match_ok = False - if match_ok and args[2] is not None: - try: - if ( - dtypes.coerce_to_common(args[2], dtypes.NUMERIC_DTYPE) - != dtypes.NUMERIC_DTYPE - ): - match_ok = False - except TypeError: - match_ok = False - if match_ok: - return dtypes.list_type(dtypes.NUMERIC_DTYPE) - - # Try matching impl 2 - match_ok = True - if match_ok and args[0] is not None: - try: - if ( - dtypes.coerce_to_common(args[0], dtypes.FLOAT_DTYPE) - != dtypes.FLOAT_DTYPE - ): - match_ok = False - except TypeError: - match_ok = False - if match_ok and args[1] is not None: - try: - if ( - dtypes.coerce_to_common(args[1], dtypes.FLOAT_DTYPE) - != dtypes.FLOAT_DTYPE - ): - match_ok = False - except TypeError: - match_ok = False - if match_ok and args[2] is not None: - try: - if ( - dtypes.coerce_to_common(args[2], dtypes.FLOAT_DTYPE) - != dtypes.FLOAT_DTYPE - ): - match_ok = False - except TypeError: - match_ok = False - if match_ok: - return dtypes.list_type(dtypes.FLOAT_DTYPE) - - raise TypeError( - f"Could not find matching signature for generate_array with argument types: {[str(t) for t in args]}" - ) - - -_GENERATE_ARRAY_OP = googlesql.GoogleSqlScalarOp( - "GENERATE_ARRAY", - args=(googlesql.ArgSpec(), googlesql.ArgSpec(), googlesql.ArgSpec(optional=True)), - signature=_GENERATE_ARRAY_SIG, -) - - -def array_concat( - array_expression_1: Union[ - series.Series, - bigframes.core.col.Expression, - Union[Any, Literal[sentinels.Sentinel.ARGUMENT_DEFAULT]], - ], - array_expression_2: Union[ - series.Series, - bigframes.core.col.Expression, - Union[Any, Literal[sentinels.Sentinel.ARGUMENT_DEFAULT]], - ], -) -> Union[series.Series, bigframes.core.col.Expression]: - """Concatenates one or more arrays with the same element type into a single array.""" - return bigframes.core.googlesql.apply_googlesql_scalar_op( - _ARRAY_CONCAT_OP, - array_expression_1, - array_expression_2, - ) - - -def array_first( - array_expression: Union[ - series.Series, - bigframes.core.col.Expression, - Union[Any, Literal[sentinels.Sentinel.ARGUMENT_DEFAULT]], - ], -) -> Union[series.Series, bigframes.core.col.Expression]: - """Takes an array and returns the first element in the array.""" - return bigframes.core.googlesql.apply_googlesql_scalar_op( - _ARRAY_FIRST_OP, - array_expression, - ) - - -def array_first_n( - input_array: Union[ - series.Series, - bigframes.core.col.Expression, - Union[Any, Literal[sentinels.Sentinel.ARGUMENT_DEFAULT]], - ], - n: Union[ - series.Series, - bigframes.core.col.Expression, - Union[Literal[sentinels.Sentinel.ARGUMENT_DEFAULT], int], - ], -) -> Union[series.Series, bigframes.core.col.Expression]: - """Returns a prefix of `input_array` consisting of the first `n` elements.""" - return bigframes.core.googlesql.apply_googlesql_scalar_op( - _ARRAY_FIRST_N_OP, - input_array, - n, - ) - - -def array_includes( - array_to_search: Union[ - series.Series, - bigframes.core.col.Expression, - Union[Any, Literal[sentinels.Sentinel.ARGUMENT_DEFAULT]], - ], - search_value: Union[ - series.Series, - bigframes.core.col.Expression, - Union[Any, Literal[sentinels.Sentinel.ARGUMENT_DEFAULT]], - ], -) -> Union[series.Series, bigframes.core.col.Expression]: - """Takes an array and returns `TRUE` if there is an element in the array that is equal to the search_value.""" - return bigframes.core.googlesql.apply_googlesql_scalar_op( - _ARRAY_INCLUDES_OP, - array_to_search, - search_value, - ) - - -def array_includes_all( - array_to_search: Union[ - series.Series, - bigframes.core.col.Expression, - Union[Any, Literal[sentinels.Sentinel.ARGUMENT_DEFAULT]], - ], - search_values: Union[ - series.Series, - bigframes.core.col.Expression, - Union[Any, Literal[sentinels.Sentinel.ARGUMENT_DEFAULT]], - ], -) -> Union[series.Series, bigframes.core.col.Expression]: - """Takes an array to search and an array of search values. Returns `TRUE` if all search values are in the array to search, otherwise returns `FALSE`.""" - return bigframes.core.googlesql.apply_googlesql_scalar_op( - _ARRAY_INCLUDES_ALL_OP, - array_to_search, - search_values, - ) - - -def array_includes_any( - array_to_search: Union[ - series.Series, - bigframes.core.col.Expression, - Union[Any, Literal[sentinels.Sentinel.ARGUMENT_DEFAULT]], - ], - search_values: Union[ - series.Series, - bigframes.core.col.Expression, - Union[Any, Literal[sentinels.Sentinel.ARGUMENT_DEFAULT]], - ], -) -> Union[series.Series, bigframes.core.col.Expression]: - """Takes an array to search and an array of search values. Returns `TRUE` if any search values are in the array to search, otherwise returns `FALSE`.""" - return bigframes.core.googlesql.apply_googlesql_scalar_op( - _ARRAY_INCLUDES_ANY_OP, - array_to_search, - search_values, - ) - - -def array_is_distinct( - array_expression: Union[ - series.Series, - bigframes.core.col.Expression, - Union[Any, Literal[sentinels.Sentinel.ARGUMENT_DEFAULT]], - ], -) -> Union[series.Series, bigframes.core.col.Expression]: - """Returns `TRUE` if the array contains no repeated elements, using the same equality comparison logic as `SELECT DISTINCT`.""" - return bigframes.core.googlesql.apply_googlesql_scalar_op( - _ARRAY_IS_DISTINCT_OP, - array_expression, - ) - - -def array_last( - array_expression: Union[ - series.Series, - bigframes.core.col.Expression, - Union[Any, Literal[sentinels.Sentinel.ARGUMENT_DEFAULT]], - ], -) -> Union[series.Series, bigframes.core.col.Expression]: - """Takes an array and returns the last element in the array.""" - return bigframes.core.googlesql.apply_googlesql_scalar_op( - _ARRAY_LAST_OP, - array_expression, - ) - - -def array_length( - series: Union[ - series.Series, - bigframes.core.col.Expression, - Union[Any, Literal[sentinels.Sentinel.ARGUMENT_DEFAULT]], - ], -) -> Union[series.Series, bigframes.core.col.Expression]: - """Compute the length of each array element in the Series. - - **Examples:** - - >>> import bigframes.pandas as bpd - >>> import bigframes.bigquery as bbq - - >>> s = bpd.Series([[1, 2, 8, 3], [], [3, 4]]) - >>> bbq.array_length(s) - 0 4 - 1 0 - 2 2 - dtype: Int64 - - You can call this function using the Series `bigquery` accessor. - - >>> s.bigquery.array_length() - 0 4 - 1 0 - 2 2 - dtype: Int64 - - You can also use this accessor on a pandas Series after importing bigframes. - - >>> import bigframes - >>> import pandas as pd - >>> ps = pd.Series([[1, 2, 8, 3], [], [3, 4]]) - >>> ps.bigquery.array_length() - 0 4 - 1 0 - 2 2 - dtype: Int64 - - You can also apply this function directly to Series using `apply`. - - >>> s.apply(bbq.array_length, by_row=False) - 0 4 - 1 0 - 2 2 - dtype: Int64 - - Args: - series (bigframes.series.Series): A Series with array columns. - - Returns: - bigframes.series.Series: A Series of integer values indicating - the length of each element in the Series. - """ - return bigframes.core.googlesql.apply_googlesql_scalar_op( - _ARRAY_LENGTH_OP, - series, - ) - - -def array_reverse( - value: Union[ - series.Series, - bigframes.core.col.Expression, - Union[Any, Literal[sentinels.Sentinel.ARGUMENT_DEFAULT]], - ], -) -> Union[series.Series, bigframes.core.col.Expression]: - """Returns the input `ARRAY` with elements in reverse order.""" - return bigframes.core.googlesql.apply_googlesql_scalar_op( - _ARRAY_REVERSE_OP, - value, - ) - - -def array_slice( - array_to_slice: Union[ - series.Series, - bigframes.core.col.Expression, - Union[Any, Literal[sentinels.Sentinel.ARGUMENT_DEFAULT]], - ], - start_offset: Union[ - series.Series, - bigframes.core.col.Expression, - Union[Literal[sentinels.Sentinel.ARGUMENT_DEFAULT], int], - ], - end_offset: Union[ - series.Series, - bigframes.core.col.Expression, - Union[Literal[sentinels.Sentinel.ARGUMENT_DEFAULT], int], - ], -) -> Union[series.Series, bigframes.core.col.Expression]: - """Returns an array containing zero or more consecutive elements from the input array.""" - return bigframes.core.googlesql.apply_googlesql_scalar_op( - _ARRAY_SLICE_OP, - array_to_slice, - start_offset, - end_offset, - ) - - -def array_to_string( - series: Union[ - series.Series, - bigframes.core.col.Expression, - Union[Any, Literal[sentinels.Sentinel.ARGUMENT_DEFAULT]], - ], - delimiter: Union[ - series.Series, - bigframes.core.col.Expression, - Union[Literal[sentinels.Sentinel.ARGUMENT_DEFAULT], bytes, str], - ], - null_text: Union[ - series.Series, - bigframes.core.col.Expression, - Union[Literal[sentinels.Sentinel.ARGUMENT_DEFAULT], bytes, str], - ] = sentinels.Sentinel.ARGUMENT_DEFAULT, -) -> Union[series.Series, bigframes.core.col.Expression]: - """Converts array elements within a Series into delimited strings. - - **Examples:** - - >>> import bigframes.pandas as bpd - >>> import bigframes.bigquery as bbq - - >>> s = bpd.Series([["H", "i", "!"], ["Hello", "World"], np.nan, [], ["Hi"]]) - >>> bbq.array_to_string(s, delimiter=", ") - 0 H, i, ! - 1 Hello, World - 2 - 3 - 4 Hi - dtype: string - - You can call this function using the Series `bigquery` accessor. - - >>> s.bigquery.array_to_string(delimiter=", ") - 0 H, i, ! - 1 Hello, World - 2 - 3 - 4 Hi - dtype: string - - You can also use this accessor on a pandas Series after importing bigframes. - - >>> import bigframes - >>> import pandas as pd - >>> ps = pd.Series([["H", "i", "!"], ["Hello", "World"], None, [], ["Hi"]]) - >>> ps.bigquery.array_to_string(delimiter=", ") - 0 H, i, ! - 1 Hello, World - 2 - 3 - 4 Hi - dtype: string - - Args: - series (bigframes.series.Series): A Series containing arrays. - delimiter (str): The string used to separate array elements. - null_text (str, optional): The string to replace any NULL values in the array with. - - Returns: - bigframes.series.Series: A Series containing delimited strings. - """ - return bigframes.core.googlesql.apply_googlesql_scalar_op( - _ARRAY_TO_STRING_OP, - series, - delimiter, - null_text, - ) - - -def flatten( - array_to_flatten: Union[ - series.Series, - bigframes.core.col.Expression, - Union[Any, Literal[sentinels.Sentinel.ARGUMENT_DEFAULT]], - ], - depth: Union[ - series.Series, - bigframes.core.col.Expression, - Union[Literal[sentinels.Sentinel.ARGUMENT_DEFAULT], int], - ] = sentinels.Sentinel.ARGUMENT_DEFAULT, -) -> Union[series.Series, bigframes.core.col.Expression]: - """Takes an array of nested data and flattens a specific part of it into a single, flat array with the [array elements field access operator][array-el-field-operator]. Returns `NULL` if the input value is `NULL`.""" - return bigframes.core.googlesql.apply_googlesql_scalar_op( - _FLATTEN_OP, - array_to_flatten, - depth, - ) - - -def generate_array( - start_expression: Union[ - series.Series, - bigframes.core.col.Expression, - Union[ - Literal[sentinels.Sentinel.ARGUMENT_DEFAULT], decimal.Decimal, float, int - ], - ], - end_expression: Union[ - series.Series, - bigframes.core.col.Expression, - Union[ - Literal[sentinels.Sentinel.ARGUMENT_DEFAULT], decimal.Decimal, float, int - ], - ], - step_expression: Union[ - series.Series, - bigframes.core.col.Expression, - Union[ - Literal[sentinels.Sentinel.ARGUMENT_DEFAULT], decimal.Decimal, float, int - ], - ] = sentinels.Sentinel.ARGUMENT_DEFAULT, -) -> Union[series.Series, bigframes.core.col.Expression]: - """Returns an array of values. The `start_expression` and `end_expression` parameters determine the inclusive start and end of the array.""" - return bigframes.core.googlesql.apply_googlesql_scalar_op( - _GENERATE_ARRAY_OP, - start_expression, - end_expression, - step_expression, - ) diff --git a/bigframes/operations/googlesql/global_namespace/bit.py b/bigframes/operations/googlesql/global_namespace/bit.py deleted file mode 100644 index e0c22dfc299..00000000000 --- a/bigframes/operations/googlesql/global_namespace/bit.py +++ /dev/null @@ -1,48 +0,0 @@ -# Copyright 2026 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. -# -# DO NOT MODIFY THIS FILE DIRECTLY. -# This file was generated from: scripts/data/sql-functions/global_namespace/bit.yaml -# by the script: scripts/generate_bigframes_bigquery.py - -from __future__ import annotations - -from typing import Any, Literal, Union - -import bigframes.core.col -import bigframes.core.googlesql -import bigframes.core.sentinels as sentinels -import bigframes.series as series -from bigframes import dtypes -from bigframes.operations import googlesql - -_BIT_COUNT_OP = googlesql.GoogleSqlScalarOp( - "BIT_COUNT", - args=(googlesql.ArgSpec(),), - signature=lambda *args: dtypes.INT_DTYPE, -) - - -def bit_count( - expression: Union[ - series.Series, - bigframes.core.col.Expression, - Union[Any, Literal[sentinels.Sentinel.ARGUMENT_DEFAULT], bytes, int], - ], -) -> Union[series.Series, bigframes.core.col.Expression]: - """The input, `expression`, must be an integer or `BYTES`. Returns the number of bits that are set in the input expression. For signed integers, this is the number of bits in two's complement form.""" - return bigframes.core.googlesql.apply_googlesql_scalar_op( - _BIT_COUNT_OP, - expression, - ) diff --git a/bigframes/operations/googlesql/global_namespace/conversion.py b/bigframes/operations/googlesql/global_namespace/conversion.py deleted file mode 100644 index cea4e45d836..00000000000 --- a/bigframes/operations/googlesql/global_namespace/conversion.py +++ /dev/null @@ -1,193 +0,0 @@ -# Copyright 2026 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. -# -# DO NOT MODIFY THIS FILE DIRECTLY. -# This file was generated from: scripts/data/sql-functions/global_namespace/conversion.yaml -# by the script: scripts/generate_bigframes_bigquery.py - -from __future__ import annotations - -import datetime -from typing import Literal, Union - -import bigframes.core.col -import bigframes.core.googlesql -import bigframes.core.sentinels as sentinels -import bigframes.series as series -from bigframes import dtypes -from bigframes.operations import googlesql - -_BOOL_OP = googlesql.GoogleSqlScalarOp( - "BOOL", - args=(googlesql.ArgSpec(),), - signature=lambda *args: dtypes.BOOL_DTYPE, -) -_DOUBLE_OP = googlesql.GoogleSqlScalarOp( - "DOUBLE", - args=( - googlesql.ArgSpec(), - googlesql.ArgSpec(arg_name="wide_number_mode", optional=True), - ), - signature=lambda *args: dtypes.FLOAT_DTYPE, -) -_FLOAT64_OP = googlesql.GoogleSqlScalarOp( - "FLOAT64", - args=( - googlesql.ArgSpec(), - googlesql.ArgSpec(arg_name="wide_number_mode", optional=True), - ), - signature=lambda *args: dtypes.FLOAT_DTYPE, -) -_INT64_OP = googlesql.GoogleSqlScalarOp( - "INT64", - args=(googlesql.ArgSpec(),), - signature=lambda *args: dtypes.INT_DTYPE, -) -_PARSE_BIGNUMERIC_OP = googlesql.GoogleSqlScalarOp( - "PARSE_BIGNUMERIC", - args=(googlesql.ArgSpec(),), - signature=lambda *args: dtypes.BIGNUMERIC_DTYPE, -) -_PARSE_NUMERIC_OP = googlesql.GoogleSqlScalarOp( - "PARSE_NUMERIC", - args=(googlesql.ArgSpec(),), - signature=lambda *args: dtypes.NUMERIC_DTYPE, -) -_STRING_OP = googlesql.GoogleSqlScalarOp( - "STRING", - args=(googlesql.ArgSpec(), googlesql.ArgSpec(optional=True)), - signature=lambda *args: dtypes.STRING_DTYPE, -) - - -def bool_( - json_string_expression: Union[ - series.Series, - bigframes.core.col.Expression, - Union[Literal[sentinels.Sentinel.ARGUMENT_DEFAULT], str], - ], -) -> Union[series.Series, bigframes.core.col.Expression]: - """Converts a JSON boolean to a SQL BOOL value.""" - return bigframes.core.googlesql.apply_googlesql_scalar_op( - _BOOL_OP, - json_string_expression, - ) - - -def double( - json_string_expression: Union[ - series.Series, - bigframes.core.col.Expression, - Union[Literal[sentinels.Sentinel.ARGUMENT_DEFAULT], str], - ], - wide_number_mode: Union[ - series.Series, - bigframes.core.col.Expression, - Union[Literal[sentinels.Sentinel.ARGUMENT_DEFAULT], str], - ] = sentinels.Sentinel.ARGUMENT_DEFAULT, -) -> Union[series.Series, bigframes.core.col.Expression]: - """Converts a JSON number to a SQL FLOAT64 value.""" - return bigframes.core.googlesql.apply_googlesql_scalar_op( - _DOUBLE_OP, - json_string_expression, - wide_number_mode, - ) - - -def float64( - json_string_expression: Union[ - series.Series, - bigframes.core.col.Expression, - Union[Literal[sentinels.Sentinel.ARGUMENT_DEFAULT], str], - ], - wide_number_mode: Union[ - series.Series, - bigframes.core.col.Expression, - Union[Literal[sentinels.Sentinel.ARGUMENT_DEFAULT], str], - ] = sentinels.Sentinel.ARGUMENT_DEFAULT, -) -> Union[series.Series, bigframes.core.col.Expression]: - """Converts a JSON number to a SQL FLOAT64 value.""" - return bigframes.core.googlesql.apply_googlesql_scalar_op( - _FLOAT64_OP, - json_string_expression, - wide_number_mode, - ) - - -def int64( - json_string_expression: Union[ - series.Series, - bigframes.core.col.Expression, - Union[Literal[sentinels.Sentinel.ARGUMENT_DEFAULT], str], - ], -) -> Union[series.Series, bigframes.core.col.Expression]: - """Converts a JSON number to a SQL INT64 value.""" - return bigframes.core.googlesql.apply_googlesql_scalar_op( - _INT64_OP, - json_string_expression, - ) - - -def parse_bignumeric( - string_expression: Union[ - series.Series, - bigframes.core.col.Expression, - Union[Literal[sentinels.Sentinel.ARGUMENT_DEFAULT], str], - ], -) -> Union[series.Series, bigframes.core.col.Expression]: - """Converts a STRING to a BIGNUMERIC value.""" - return bigframes.core.googlesql.apply_googlesql_scalar_op( - _PARSE_BIGNUMERIC_OP, - string_expression, - ) - - -def parse_numeric( - string_expression: Union[ - series.Series, - bigframes.core.col.Expression, - Union[Literal[sentinels.Sentinel.ARGUMENT_DEFAULT], str], - ], -) -> Union[series.Series, bigframes.core.col.Expression]: - """Converts a STRING to a NUMERIC value.""" - return bigframes.core.googlesql.apply_googlesql_scalar_op( - _PARSE_NUMERIC_OP, - string_expression, - ) - - -def string( - expression: Union[ - series.Series, - bigframes.core.col.Expression, - Union[ - Literal[sentinels.Sentinel.ARGUMENT_DEFAULT], - datetime.date, - datetime.datetime, - datetime.time, - str, - ], - ], - timezone: Union[ - series.Series, - bigframes.core.col.Expression, - Union[Literal[sentinels.Sentinel.ARGUMENT_DEFAULT], str], - ] = sentinels.Sentinel.ARGUMENT_DEFAULT, -) -> Union[series.Series, bigframes.core.col.Expression]: - """Converts a value to a STRING value.""" - return bigframes.core.googlesql.apply_googlesql_scalar_op( - _STRING_OP, - expression, - timezone, - ) diff --git a/bigframes/operations/googlesql/global_namespace/date.py b/bigframes/operations/googlesql/global_namespace/date.py deleted file mode 100644 index b6cfc9722b5..00000000000 --- a/bigframes/operations/googlesql/global_namespace/date.py +++ /dev/null @@ -1,412 +0,0 @@ -# Copyright 2026 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. -# -# DO NOT MODIFY THIS FILE DIRECTLY. -# This file was generated from: scripts/data/sql-functions/global_namespace/date.yaml -# by the script: scripts/generate_bigframes_bigquery.py - -from __future__ import annotations - -import datetime -from typing import Any, Literal, Union - -import bigframes.core.col -import bigframes.core.googlesql -import bigframes.core.sentinels as sentinels -import bigframes.series as series -from bigframes import dtypes -from bigframes.operations import googlesql - -_CURRENT_DATE_OP = googlesql.GoogleSqlScalarOp( - "CURRENT_DATE", - args=(googlesql.ArgSpec(optional=True),), - signature=lambda *args: dtypes.DATE_DTYPE, -) -_DATE_OP = googlesql.GoogleSqlScalarOp( - "DATE", - args=( - googlesql.ArgSpec(optional=True), - googlesql.ArgSpec(optional=True), - googlesql.ArgSpec(optional=True), - googlesql.ArgSpec(optional=True), - googlesql.ArgSpec(optional=True), - ), - signature=lambda *args: dtypes.DATE_DTYPE, -) -_DATE_ADD_OP = googlesql.GoogleSqlScalarOp( - "DATE_ADD", - args=(googlesql.ArgSpec(), googlesql.ArgSpec(), googlesql.ArgSpec()), - signature=lambda *args: dtypes.DATE_DTYPE, -) -_DATE_DIFF_OP = googlesql.GoogleSqlScalarOp( - "DATE_DIFF", - args=(googlesql.ArgSpec(), googlesql.ArgSpec(), googlesql.ArgSpec()), - signature=lambda *args: dtypes.INT_DTYPE, -) -_DATE_FROM_UNIX_DATE_OP = googlesql.GoogleSqlScalarOp( - "DATE_FROM_UNIX_DATE", - args=(googlesql.ArgSpec(),), - signature=lambda *args: dtypes.DATE_DTYPE, -) -_DATE_SUB_OP = googlesql.GoogleSqlScalarOp( - "DATE_SUB", - args=(googlesql.ArgSpec(), googlesql.ArgSpec(), googlesql.ArgSpec()), - signature=lambda *args: dtypes.DATE_DTYPE, -) -_DATE_TRUNC_OP = googlesql.GoogleSqlScalarOp( - "DATE_TRUNC", - args=(googlesql.ArgSpec(), googlesql.ArgSpec()), - signature=lambda *args: dtypes.DATE_DTYPE, -) -_EXTRACT_OP = googlesql.GoogleSqlScalarOp( - "EXTRACT", - args=(googlesql.ArgSpec(), googlesql.ArgSpec(), googlesql.ArgSpec(optional=True)), - signature=lambda *args: dtypes.INT_DTYPE, -) -_FORMAT_DATE_OP = googlesql.GoogleSqlScalarOp( - "FORMAT_DATE", - args=(googlesql.ArgSpec(), googlesql.ArgSpec()), - signature=lambda *args: dtypes.STRING_DTYPE, -) -_GENERATE_DATE_ARRAY_OP = googlesql.GoogleSqlScalarOp( - "GENERATE_DATE_ARRAY", - args=( - googlesql.ArgSpec(), - googlesql.ArgSpec(), - googlesql.ArgSpec(optional=True), - googlesql.ArgSpec(optional=True), - ), - signature=lambda *args: dtypes.list_type(dtypes.DATE_DTYPE), -) -_LAST_DAY_OP = googlesql.GoogleSqlScalarOp( - "LAST_DAY", - args=(googlesql.ArgSpec(), googlesql.ArgSpec(optional=True)), - signature=lambda *args: dtypes.DATE_DTYPE, -) -_PARSE_DATE_OP = googlesql.GoogleSqlScalarOp( - "PARSE_DATE", - args=(googlesql.ArgSpec(), googlesql.ArgSpec()), - signature=lambda *args: dtypes.DATE_DTYPE, -) -_UNIX_DATE_OP = googlesql.GoogleSqlScalarOp( - "UNIX_DATE", - args=(googlesql.ArgSpec(),), - signature=lambda *args: dtypes.INT_DTYPE, -) - - -def current_date( - time_zone_expression: Union[ - series.Series, - bigframes.core.col.Expression, - Union[Literal[sentinels.Sentinel.ARGUMENT_DEFAULT], str], - ] = sentinels.Sentinel.ARGUMENT_DEFAULT, -) -> Union[series.Series, bigframes.core.col.Expression]: - """Returns the current date as a DATE object. Parentheses are optional when called with no arguments.""" - return bigframes.core.googlesql.apply_googlesql_scalar_op( - _CURRENT_DATE_OP, - time_zone_expression, - ) - - -def date( - expression: Union[ - series.Series, - bigframes.core.col.Expression, - Union[ - Literal[sentinels.Sentinel.ARGUMENT_DEFAULT], - datetime.date, - datetime.datetime, - str, - ], - ] = sentinels.Sentinel.ARGUMENT_DEFAULT, - time_zone_expression: Union[ - series.Series, - bigframes.core.col.Expression, - Union[Literal[sentinels.Sentinel.ARGUMENT_DEFAULT], str], - ] = sentinels.Sentinel.ARGUMENT_DEFAULT, - year: Union[ - series.Series, - bigframes.core.col.Expression, - Union[Literal[sentinels.Sentinel.ARGUMENT_DEFAULT], int], - ] = sentinels.Sentinel.ARGUMENT_DEFAULT, - month: Union[ - series.Series, - bigframes.core.col.Expression, - Union[Literal[sentinels.Sentinel.ARGUMENT_DEFAULT], int], - ] = sentinels.Sentinel.ARGUMENT_DEFAULT, - day: Union[ - series.Series, - bigframes.core.col.Expression, - Union[Literal[sentinels.Sentinel.ARGUMENT_DEFAULT], int], - ] = sentinels.Sentinel.ARGUMENT_DEFAULT, -) -> Union[series.Series, bigframes.core.col.Expression]: - """Constructs or extracts a date.""" - return bigframes.core.googlesql.apply_googlesql_scalar_op( - _DATE_OP, - expression, - time_zone_expression, - year, - month, - day, - ) - - -def date_add( - date_expression: Union[ - series.Series, - bigframes.core.col.Expression, - Union[Literal[sentinels.Sentinel.ARGUMENT_DEFAULT], datetime.date], - ], - int64_expression: Union[ - series.Series, - bigframes.core.col.Expression, - Union[Literal[sentinels.Sentinel.ARGUMENT_DEFAULT], int], - ], - date_part: Union[ - series.Series, - bigframes.core.col.Expression, - Union[Any, Literal[sentinels.Sentinel.ARGUMENT_DEFAULT]], - ], -) -> Union[series.Series, bigframes.core.col.Expression]: - """Adds a specified time interval to a DATE.""" - return bigframes.core.googlesql.apply_googlesql_scalar_op( - _DATE_ADD_OP, - date_expression, - int64_expression, - date_part, - ) - - -def date_diff( - end_date: Union[ - series.Series, - bigframes.core.col.Expression, - Union[Literal[sentinels.Sentinel.ARGUMENT_DEFAULT], datetime.date], - ], - start_date: Union[ - series.Series, - bigframes.core.col.Expression, - Union[Literal[sentinels.Sentinel.ARGUMENT_DEFAULT], datetime.date], - ], - granularity: Union[ - series.Series, - bigframes.core.col.Expression, - Union[Any, Literal[sentinels.Sentinel.ARGUMENT_DEFAULT]], - ], -) -> Union[series.Series, bigframes.core.col.Expression]: - """Gets the number of unit boundaries between two DATE values (end_date - start_date) at a particular time granularity.""" - return bigframes.core.googlesql.apply_googlesql_scalar_op( - _DATE_DIFF_OP, - end_date, - start_date, - granularity, - ) - - -def date_from_unix_date( - int64_expression: Union[ - series.Series, - bigframes.core.col.Expression, - Union[Literal[sentinels.Sentinel.ARGUMENT_DEFAULT], int], - ], -) -> Union[series.Series, bigframes.core.col.Expression]: - """Interprets an INT64 expression as the number of days since 1970-01-01.""" - return bigframes.core.googlesql.apply_googlesql_scalar_op( - _DATE_FROM_UNIX_DATE_OP, - int64_expression, - ) - - -def date_sub( - date_expression: Union[ - series.Series, - bigframes.core.col.Expression, - Union[Literal[sentinels.Sentinel.ARGUMENT_DEFAULT], datetime.date], - ], - int64_expression: Union[ - series.Series, - bigframes.core.col.Expression, - Union[Literal[sentinels.Sentinel.ARGUMENT_DEFAULT], int], - ], - date_part: Union[ - series.Series, - bigframes.core.col.Expression, - Union[Any, Literal[sentinels.Sentinel.ARGUMENT_DEFAULT]], - ], -) -> Union[series.Series, bigframes.core.col.Expression]: - """Subtracts a specified time interval from a DATE.""" - return bigframes.core.googlesql.apply_googlesql_scalar_op( - _DATE_SUB_OP, - date_expression, - int64_expression, - date_part, - ) - - -def date_trunc( - date_value: Union[ - series.Series, - bigframes.core.col.Expression, - Union[Literal[sentinels.Sentinel.ARGUMENT_DEFAULT], datetime.date], - ], - granularity: Union[ - series.Series, - bigframes.core.col.Expression, - Union[Any, Literal[sentinels.Sentinel.ARGUMENT_DEFAULT]], - ], -) -> Union[series.Series, bigframes.core.col.Expression]: - """Truncates a DATE, DATETIME, or TIMESTAMP value at a particular granularity.""" - return bigframes.core.googlesql.apply_googlesql_scalar_op( - _DATE_TRUNC_OP, - date_value, - granularity, - ) - - -def extract( - date_expression: Union[ - series.Series, - bigframes.core.col.Expression, - Union[ - Literal[sentinels.Sentinel.ARGUMENT_DEFAULT], - datetime.date, - datetime.datetime, - datetime.time, - ], - ], - part: Union[ - series.Series, - bigframes.core.col.Expression, - Union[Any, Literal[sentinels.Sentinel.ARGUMENT_DEFAULT]], - ], - time_zone: Union[ - series.Series, - bigframes.core.col.Expression, - Union[Literal[sentinels.Sentinel.ARGUMENT_DEFAULT], str], - ] = sentinels.Sentinel.ARGUMENT_DEFAULT, -) -> Union[series.Series, bigframes.core.col.Expression]: - """Returns the value corresponding to the specified date part.""" - return bigframes.core.googlesql.apply_googlesql_scalar_op( - _EXTRACT_OP, - date_expression, - part, - time_zone, - ) - - -def format_date( - format_string: Union[ - series.Series, - bigframes.core.col.Expression, - Union[Literal[sentinels.Sentinel.ARGUMENT_DEFAULT], str], - ], - date_expr: Union[ - series.Series, - bigframes.core.col.Expression, - Union[Literal[sentinels.Sentinel.ARGUMENT_DEFAULT], datetime.date], - ], -) -> Union[series.Series, bigframes.core.col.Expression]: - """Formats a DATE value according to a specified format string.""" - return bigframes.core.googlesql.apply_googlesql_scalar_op( - _FORMAT_DATE_OP, - format_string, - date_expr, - ) - - -def generate_date_array( - start_date: Union[ - series.Series, - bigframes.core.col.Expression, - Union[Literal[sentinels.Sentinel.ARGUMENT_DEFAULT], datetime.date], - ], - end_date: Union[ - series.Series, - bigframes.core.col.Expression, - Union[Literal[sentinels.Sentinel.ARGUMENT_DEFAULT], datetime.date], - ], - int64_expression: Union[ - series.Series, - bigframes.core.col.Expression, - Union[Literal[sentinels.Sentinel.ARGUMENT_DEFAULT], int], - ] = sentinels.Sentinel.ARGUMENT_DEFAULT, - date_part: Union[ - series.Series, - bigframes.core.col.Expression, - Union[Any, Literal[sentinels.Sentinel.ARGUMENT_DEFAULT]], - ] = sentinels.Sentinel.ARGUMENT_DEFAULT, -) -> Union[series.Series, bigframes.core.col.Expression]: - """Generates an array of dates in a range.""" - return bigframes.core.googlesql.apply_googlesql_scalar_op( - _GENERATE_DATE_ARRAY_OP, - start_date, - end_date, - int64_expression, - date_part, - ) - - -def last_day( - date_expression: Union[ - series.Series, - bigframes.core.col.Expression, - Union[Literal[sentinels.Sentinel.ARGUMENT_DEFAULT], datetime.date], - ], - date_part: Union[ - series.Series, - bigframes.core.col.Expression, - Union[Any, Literal[sentinels.Sentinel.ARGUMENT_DEFAULT]], - ] = sentinels.Sentinel.ARGUMENT_DEFAULT, -) -> Union[series.Series, bigframes.core.col.Expression]: - """Returns the last day from a date expression. This is commonly used to return the last day of the month.""" - return bigframes.core.googlesql.apply_googlesql_scalar_op( - _LAST_DAY_OP, - date_expression, - date_part, - ) - - -def parse_date( - format_string: Union[ - series.Series, - bigframes.core.col.Expression, - Union[Literal[sentinels.Sentinel.ARGUMENT_DEFAULT], str], - ], - date_string: Union[ - series.Series, - bigframes.core.col.Expression, - Union[Literal[sentinels.Sentinel.ARGUMENT_DEFAULT], str], - ], -) -> Union[series.Series, bigframes.core.col.Expression]: - """Converts a STRING value to a DATE value.""" - return bigframes.core.googlesql.apply_googlesql_scalar_op( - _PARSE_DATE_OP, - format_string, - date_string, - ) - - -def unix_date( - date_expression: Union[ - series.Series, - bigframes.core.col.Expression, - Union[Literal[sentinels.Sentinel.ARGUMENT_DEFAULT], datetime.date], - ], -) -> Union[series.Series, bigframes.core.col.Expression]: - """Returns the number of days since 1970-01-01.""" - return bigframes.core.googlesql.apply_googlesql_scalar_op( - _UNIX_DATE_OP, - date_expression, - ) diff --git a/bigframes/operations/json_ops.py b/bigframes/operations/json_ops.py deleted file mode 100644 index c9b5849f9ed..00000000000 --- a/bigframes/operations/json_ops.py +++ /dev/null @@ -1,240 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import dataclasses -import typing - -import pandas as pd -import pyarrow as pa - -from bigframes import dtypes -from bigframes.operations import base_ops - - -@dataclasses.dataclass(frozen=True) -class JSONExtract(base_ops.UnaryOp): - name: typing.ClassVar[str] = "json_extract" - json_path: str - - def output_type(self, *input_types): - input_type = input_types[0] - if not dtypes.is_json_like(input_type): - raise TypeError( - "Input type must be a valid JSON object or JSON-formatted string type." - + f" Received type: {input_type}" - ) - return input_type - - -@dataclasses.dataclass(frozen=True) -class JSONQueryArray(base_ops.UnaryOp): - name: typing.ClassVar[str] = "json_query_array" - json_path: str - - def output_type(self, *input_types): - input_type = input_types[0] - if not dtypes.is_json_like(input_type): - raise TypeError( - "Input type must be a valid JSON object or JSON-formatted string type." - + f" Received type: {input_type}" - ) - return pd.ArrowDtype( - pa.list_(dtypes.bigframes_dtype_to_arrow_dtype(input_type)) - ) - - -@dataclasses.dataclass(frozen=True) -class JSONExtractArray(base_ops.UnaryOp): - name: typing.ClassVar[str] = "json_extract_array" - json_path: str - - def output_type(self, *input_types): - input_type = input_types[0] - if not dtypes.is_json_like(input_type): - raise TypeError( - "Input type must be a valid JSON object or JSON-formatted string type." - + f" Received type: {input_type}" - ) - return pd.ArrowDtype( - pa.list_(dtypes.bigframes_dtype_to_arrow_dtype(input_type)) - ) - - -@dataclasses.dataclass(frozen=True) -class JSONExtractStringArray(base_ops.UnaryOp): - name: typing.ClassVar[str] = "json_extract_string_array" - json_path: str - - def output_type(self, *input_types): - input_type = input_types[0] - if not dtypes.is_json_like(input_type): - raise TypeError( - "Input type must be a valid JSON object or JSON-formatted string type." - + f" Received type: {input_type}" - ) - return pd.ArrowDtype( - pa.list_(dtypes.bigframes_dtype_to_arrow_dtype(dtypes.STRING_DTYPE)) - ) - - -@dataclasses.dataclass(frozen=True) -class ParseJSON(base_ops.UnaryOp): - name: typing.ClassVar[str] = "parse_json" - - def output_type(self, *input_types): - input_type = input_types[0] - if input_type != dtypes.STRING_DTYPE: - raise TypeError( - "Input type must be a valid JSON-formatted string type." - + f" Received type: {input_type}" - ) - return dtypes.JSON_DTYPE - - -@dataclasses.dataclass(frozen=True) -class ToJSON(base_ops.UnaryOp): - name: typing.ClassVar[str] = "to_json" - safe: bool = True - - def output_type(self, *input_types): - input_type = input_types[0] - if not dtypes.is_json_encoding_type(input_type, strict=True): - raise TypeError( - "The value to be assigned must be a type that can be encoded as JSON." - + f"Received type: {input_type}" - ) - return dtypes.JSON_DTYPE - - -@dataclasses.dataclass(frozen=True) -class ToJSONString(base_ops.UnaryOp): - name: typing.ClassVar[str] = "to_json_string" - - def output_type(self, *input_types): - input_type = input_types[0] - if not dtypes.is_json_encoding_type(input_type): - raise TypeError( - "The value to be assigned must be a type that can be encoded as JSON." - + f"Received type: {input_type}" - ) - return dtypes.STRING_DTYPE - - -@dataclasses.dataclass(frozen=True) -class JSONSet(base_ops.BinaryOp): - name: typing.ClassVar[str] = "json_set" - json_path: str - - def output_type(self, *input_types): - left_type = input_types[0] - right_type = input_types[1] - if not dtypes.is_json_like(left_type): - raise TypeError( - "Input type must be a valid JSON object or JSON-formatted string type." - + f" Received type: {left_type}" - ) - if not dtypes.is_json_encoding_type(right_type): - raise TypeError( - "The value to be assigned must be a type that can be encoded as JSON." - + f"Received type: {right_type}" - ) - - return dtypes.JSON_DTYPE - - -@dataclasses.dataclass(frozen=True) -class JSONValue(base_ops.UnaryOp): - name: typing.ClassVar[str] = "json_value" - json_path: str - - def output_type(self, *input_types): - input_type = input_types[0] - if not dtypes.is_json_like(input_type): - raise TypeError( - "Input type must be a valid JSON object or JSON-formatted string type." - + f" Received type: {input_type}" - ) - return dtypes.STRING_DTYPE - - -@dataclasses.dataclass(frozen=True) -class JSONValueArray(base_ops.UnaryOp): - name: typing.ClassVar[str] = "json_value_array" - json_path: str - - def output_type(self, *input_types): - input_type = input_types[0] - if not dtypes.is_json_like(input_type): - raise TypeError( - "Input type must be a valid JSON object or JSON-formatted string type." - + f" Received type: {input_type}" - ) - return pd.ArrowDtype( - pa.list_(dtypes.bigframes_dtype_to_arrow_dtype(dtypes.STRING_DTYPE)) - ) - - -@dataclasses.dataclass(frozen=True) -class JSONQuery(base_ops.UnaryOp): - name: typing.ClassVar[str] = "json_query" - json_path: str - - def output_type(self, *input_types): - input_type = input_types[0] - if not dtypes.is_json_like(input_type): - raise TypeError( - "Input type must be a valid JSON object or JSON-formatted string type." - + f" Received type: {input_type}" - ) - return input_type - - -@dataclasses.dataclass(frozen=True) -class JSONKeys(base_ops.UnaryOp): - name: typing.ClassVar[str] = "json_keys" - max_depth: typing.Optional[int] = None - - def output_type(self, *input_types): - input_type = input_types[0] - if input_type != dtypes.JSON_DTYPE: - raise TypeError( - "Input type must be a valid JSON object or JSON-formatted string type." - + f" Received type: {input_type}" - ) - return pd.ArrowDtype( - pa.list_(dtypes.bigframes_dtype_to_arrow_dtype(dtypes.STRING_DTYPE)) - ) - - -@dataclasses.dataclass(frozen=True) -class JSONDecode(base_ops.UnaryOp): - name: typing.ClassVar[str] = "json_decode" - to_type: dtypes.Dtype - safe: bool = True - - def output_type(self, *input_types): - input_type = input_types[0] - if not dtypes.is_json_like(input_type): - raise TypeError( - "Input type must be a valid JSON object or JSON-formatted string type." - + f" Received type: {input_type}" - ) - if self.to_type not in ( - dtypes.INT_DTYPE, - dtypes.FLOAT_DTYPE, - dtypes.BOOL_DTYPE, - dtypes.STRING_DTYPE, - ): - raise TypeError(f"Cannot cast from {dtypes.JSON_DTYPE} to {self.to_type}") - return self.to_type diff --git a/bigframes/operations/lists.py b/bigframes/operations/lists.py deleted file mode 100644 index c0ff8d51650..00000000000 --- a/bigframes/operations/lists.py +++ /dev/null @@ -1,46 +0,0 @@ -# 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. -# 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 inspect -from typing import Union - -import bigframes_vendored.pandas.core.arrays.arrow.accessors as vendoracessors - -import bigframes.operations as ops -import bigframes.series as series -from bigframes._tools import docs -from bigframes.core.logging import log_adapter -from bigframes.operations._op_converters import convert_index, convert_slice - - -@log_adapter.class_logger -@docs.inherit_docs(vendoracessors.ListAccessor) -class ListAccessor: - def __init__(self, data: series.Series): - self._data = data - - def len(self): - return self._data._apply_unary_op(ops.len_op) - - def __getitem__(self, key: Union[int, slice]) -> series.Series: - if isinstance(key, int): - return self._data._apply_unary_op(convert_index(key)) - elif isinstance(key, slice): - return self._data._apply_unary_op(convert_slice(key)) - else: - raise ValueError(f"key must be an int or slice, got {type(key).__name__}") - - __getitem__.__doc__ = inspect.getdoc(vendoracessors.ListAccessor.__getitem__) diff --git a/bigframes/operations/numeric_ops.py b/bigframes/operations/numeric_ops.py deleted file mode 100644 index af1eef74527..00000000000 --- a/bigframes/operations/numeric_ops.py +++ /dev/null @@ -1,366 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import dataclasses -import typing - -import bigframes.operations.type as op_typing -from bigframes import dtypes -from bigframes.operations import base_ops - -SinOp = base_ops.create_unary_op( - name="sin", type_signature=op_typing.UNARY_REAL_NUMERIC -) -sin_op = SinOp() - -CosOp = base_ops.create_unary_op( - name="cos", type_signature=op_typing.UNARY_REAL_NUMERIC -) -cos_op = CosOp() - -TanOp = base_ops.create_unary_op( - name="tan", type_signature=op_typing.UNARY_REAL_NUMERIC -) -tan_op = TanOp() - -ArcsinOp = base_ops.create_unary_op( - name="arcsin", type_signature=op_typing.UNARY_REAL_NUMERIC -) -arcsin_op = ArcsinOp() - -ArccosOp = base_ops.create_unary_op( - name="arccos", type_signature=op_typing.UNARY_REAL_NUMERIC -) -arccos_op = ArccosOp() - -ArctanOp = base_ops.create_unary_op( - name="arctan", type_signature=op_typing.UNARY_REAL_NUMERIC -) -arctan_op = ArctanOp() - -SinhOp = base_ops.create_unary_op( - name="sinh", type_signature=op_typing.UNARY_REAL_NUMERIC -) -sinh_op = SinhOp() - -CoshOp = base_ops.create_unary_op( - name="cosh", type_signature=op_typing.UNARY_REAL_NUMERIC -) -cosh_op = CoshOp() - -TanhOp = base_ops.create_unary_op( - name="tanh", type_signature=op_typing.UNARY_REAL_NUMERIC -) -tanh_op = TanhOp() - -ArcsinhOp = base_ops.create_unary_op( - name="arcsinh", type_signature=op_typing.UNARY_REAL_NUMERIC -) -arcsinh_op = ArcsinhOp() - -ArccoshOp = base_ops.create_unary_op( - name="arccosh", type_signature=op_typing.UNARY_REAL_NUMERIC -) -arccosh_op = ArccoshOp() - -ArctanhOp = base_ops.create_unary_op( - name="arctanh", type_signature=op_typing.UNARY_REAL_NUMERIC -) -arctanh_op = ArctanhOp() - -FloorOp = base_ops.create_unary_op( - name="floor", type_signature=op_typing.UNARY_REAL_NUMERIC -) -floor_op = FloorOp() - -CeilOp = base_ops.create_unary_op( - name="ceil", type_signature=op_typing.UNARY_REAL_NUMERIC -) -ceil_op = CeilOp() - -AbsOp = base_ops.create_unary_op( - name="abs", type_signature=op_typing.UNARY_NUMERIC_AND_TIMEDELTA -) -abs_op = AbsOp() - -PosOp = base_ops.create_unary_op( - name="pos", type_signature=op_typing.UNARY_NUMERIC_AND_TIMEDELTA -) -pos_op = PosOp() - -NegOp = base_ops.create_unary_op( - name="neg", type_signature=op_typing.UNARY_NUMERIC_AND_TIMEDELTA -) -neg_op = NegOp() - -ExpOp = base_ops.create_unary_op( - name="exp", type_signature=op_typing.UNARY_REAL_NUMERIC -) -exp_op = ExpOp() - -Expm1Op = base_ops.create_unary_op( - name="expm1", type_signature=op_typing.UNARY_REAL_NUMERIC -) -expm1_op = Expm1Op() - -LnOp = base_ops.create_unary_op(name="log", type_signature=op_typing.UNARY_REAL_NUMERIC) -ln_op = LnOp() - -Log10Op = base_ops.create_unary_op( - name="log10", type_signature=op_typing.UNARY_REAL_NUMERIC -) -log10_op = Log10Op() - -Log1pOp = base_ops.create_unary_op( - name="log1p", type_signature=op_typing.UNARY_REAL_NUMERIC -) -log1p_op = Log1pOp() - -SqrtOp = base_ops.create_unary_op( - name="sqrt", type_signature=op_typing.UNARY_REAL_NUMERIC -) -sqrt_op = SqrtOp() - - -@dataclasses.dataclass(frozen=True) -class AddOp(base_ops.BinaryOp): - name: typing.ClassVar[str] = "add" - - def output_type(self, *input_types): - left_type = input_types[0] - right_type = input_types[1] - # TODO: Binary/bytes addition requires impl - if all(map(lambda t: t == dtypes.STRING_DTYPE, input_types)): - # String addition - return input_types[0] - - # Temporal addition. - if dtypes.is_datetime_like(left_type) and right_type == dtypes.TIMEDELTA_DTYPE: - return left_type - if left_type == dtypes.TIMEDELTA_DTYPE and dtypes.is_datetime_like(right_type): - return right_type - - if left_type == dtypes.DATE_DTYPE and right_type == dtypes.TIMEDELTA_DTYPE: - return dtypes.DATETIME_DTYPE - - if left_type == dtypes.TIMEDELTA_DTYPE and right_type == dtypes.DATE_DTYPE: - return dtypes.DATETIME_DTYPE - - if left_type == dtypes.TIMEDELTA_DTYPE and right_type == dtypes.TIMEDELTA_DTYPE: - return dtypes.TIMEDELTA_DTYPE - - if (left_type is None or dtypes.is_numeric(left_type)) and ( - right_type is None or dtypes.is_numeric(right_type) - ): - # Numeric addition - return dtypes.coerce_to_common(left_type, right_type) - raise TypeError(f"Cannot add dtypes {left_type} and {right_type}") - - -add_op = AddOp() - - -@dataclasses.dataclass(frozen=True) -class SubOp(base_ops.BinaryOp): - name: typing.ClassVar[str] = "sub" - - # Note: this is actualyl a vararg op, but we don't model that yet - def output_type(self, *input_types): - left_type = input_types[0] - right_type = input_types[1] - - if left_type == dtypes.DATETIME_DTYPE and right_type == dtypes.DATETIME_DTYPE: - return dtypes.TIMEDELTA_DTYPE - - if left_type == dtypes.TIMESTAMP_DTYPE and right_type == dtypes.TIMESTAMP_DTYPE: - return dtypes.TIMEDELTA_DTYPE - - if left_type == dtypes.DATE_DTYPE and right_type == dtypes.DATE_DTYPE: - return dtypes.TIMEDELTA_DTYPE - - if dtypes.is_datetime_like(left_type) and right_type == dtypes.TIMEDELTA_DTYPE: - return left_type - - if left_type == dtypes.DATE_DTYPE and right_type == dtypes.TIMEDELTA_DTYPE: - return dtypes.DATETIME_DTYPE - - if left_type == dtypes.TIMEDELTA_DTYPE and right_type == dtypes.TIMEDELTA_DTYPE: - return dtypes.TIMEDELTA_DTYPE - - if left_type == dtypes.BOOL_DTYPE and right_type == dtypes.BOOL_DTYPE: - raise TypeError(f"Cannot subtract dtypes {left_type} and {right_type}") - - if (left_type is None or dtypes.is_numeric(left_type)) and ( - right_type is None or dtypes.is_numeric(right_type) - ): - # Numeric subtraction - return dtypes.coerce_to_common(left_type, right_type) - - raise TypeError(f"Cannot subtract dtypes {left_type} and {right_type}") - - -sub_op = SubOp() - - -@dataclasses.dataclass(frozen=True) -class MulOp(base_ops.BinaryOp): - name: typing.ClassVar[str] = "mul" - - def output_type(self, *input_types: dtypes.ExpressionType) -> dtypes.ExpressionType: - left_type = input_types[0] - right_type = input_types[1] - - if left_type == dtypes.TIMEDELTA_DTYPE and right_type in ( - dtypes.INT_DTYPE, - dtypes.FLOAT_DTYPE, - ): - return dtypes.TIMEDELTA_DTYPE - if ( - left_type in (dtypes.INT_DTYPE, dtypes.FLOAT_DTYPE) - and right_type == dtypes.TIMEDELTA_DTYPE - ): - return dtypes.TIMEDELTA_DTYPE - - if (left_type is None or dtypes.is_numeric(left_type)) and ( - right_type is None or dtypes.is_numeric(right_type) - ): - return dtypes.coerce_to_common(left_type, right_type) - - raise TypeError(f"Cannot multiply dtypes {left_type} and {right_type}") - - -mul_op = MulOp() - - -@dataclasses.dataclass(frozen=True) -class DivOp(base_ops.BinaryOp): - name: typing.ClassVar[str] = "div" - - def output_type(self, *input_types: dtypes.ExpressionType) -> dtypes.ExpressionType: - left_type = input_types[0] - right_type = input_types[1] - - if left_type == dtypes.TIMEDELTA_DTYPE and dtypes.is_numeric(right_type): - # will fail outright if result undefined or otherwise can't be coerced back into an int - return dtypes.TIMEDELTA_DTYPE - - if left_type == dtypes.TIMEDELTA_DTYPE and right_type == dtypes.TIMEDELTA_DTYPE: - return dtypes.FLOAT_DTYPE - - if left_type == dtypes.BOOL_DTYPE and right_type == dtypes.BOOL_DTYPE: - raise TypeError(f"Cannot divide dtypes {left_type} and {right_type}") - - if (left_type is None or dtypes.is_numeric(left_type)) and ( - right_type is None or dtypes.is_numeric(right_type) - ): - lcd_type = dtypes.coerce_to_common(left_type, right_type) - # Real numeric ops produce floats on int input - return dtypes.FLOAT_DTYPE if lcd_type == dtypes.INT_DTYPE else lcd_type - - raise TypeError(f"Cannot divide dtypes {left_type} and {right_type}") - - -div_op = DivOp() - - -@dataclasses.dataclass(frozen=True) -class FloorDivOp(base_ops.BinaryOp): - name: typing.ClassVar[str] = "floordiv" - - def output_type(self, *input_types: dtypes.ExpressionType) -> dtypes.ExpressionType: - left_type = input_types[0] - right_type = input_types[1] - - if left_type == dtypes.TIMEDELTA_DTYPE and right_type == dtypes.TIMEDELTA_DTYPE: - return dtypes.INT_DTYPE - - if left_type == dtypes.TIMEDELTA_DTYPE and dtypes.is_numeric(right_type): - return dtypes.TIMEDELTA_DTYPE - - if left_type == dtypes.BOOL_DTYPE and right_type == dtypes.BOOL_DTYPE: - raise TypeError(f"Cannot floor divide dtypes {left_type} and {right_type}") - - if (left_type is None or dtypes.is_numeric(left_type)) and ( - right_type is None or dtypes.is_numeric(right_type) - ): - return dtypes.coerce_to_common(left_type, right_type) - - raise TypeError(f"Cannot floor divide dtypes {left_type} and {right_type}") - - -floordiv_op = FloorDivOp() - - -@dataclasses.dataclass(frozen=True) -class ModOp(base_ops.BinaryOp): - name: typing.ClassVar[str] = "mod" - - def output_type(self, *input_types: dtypes.ExpressionType) -> dtypes.ExpressionType: - left_type = input_types[0] - right_type = input_types[1] - - if left_type == dtypes.TIMEDELTA_DTYPE and right_type == dtypes.TIMEDELTA_DTYPE: - return dtypes.TIMEDELTA_DTYPE - if left_type in ( - dtypes.NUMERIC_DTYPE, - dtypes.BIGNUMERIC_DTYPE, - ) or right_type in (dtypes.NUMERIC_DTYPE, dtypes.BIGNUMERIC_DTYPE): - raise TypeError(f"Cannot mod dtypes {left_type} and {right_type}") - - if left_type == dtypes.BOOL_DTYPE and right_type == dtypes.BOOL_DTYPE: - raise TypeError(f"Cannot mod dtypes {left_type} and {right_type}") - - if (left_type is None or dtypes.is_numeric(left_type)) and ( - right_type is None or dtypes.is_numeric(right_type) - ): - return dtypes.coerce_to_common(left_type, right_type) - - raise TypeError(f"Cannot mod dtypes {left_type} and {right_type}") - - -mod_op = ModOp() - -PowOp = base_ops.create_binary_op(name="pow", type_signature=op_typing.BINARY_NUMERIC) -pow_op = PowOp() - -Arctan2Op = base_ops.create_binary_op( - name="arctan2", type_signature=op_typing.BINARY_REAL_NUMERIC -) -arctan2_op = Arctan2Op() - -RoundOp = base_ops.create_binary_op( - name="round", type_signature=op_typing.BINARY_NUMERIC -) -round_op = RoundOp() - -UnsafePowOp = base_ops.create_binary_op( - name="unsafe_pow_op", type_signature=op_typing.BINARY_REAL_NUMERIC -) -unsafe_pow_op = UnsafePowOp() - -IsNanOp = base_ops.create_unary_op( - name="isnan", - type_signature=op_typing.FixedOutputType( - dtypes.is_numeric, dtypes.BOOL_DTYPE, "numeric" - ), -) -isnan_op = IsNanOp() - -IsFiniteOp = base_ops.create_unary_op( - name="isfinite", - type_signature=op_typing.FixedOutputType( - dtypes.is_numeric, dtypes.BOOL_DTYPE, "numeric" - ), -) -isfinite_op = IsFiniteOp() diff --git a/bigframes/operations/numpy_op_maps.py b/bigframes/operations/numpy_op_maps.py deleted file mode 100644 index 791e2eb8901..00000000000 --- a/bigframes/operations/numpy_op_maps.py +++ /dev/null @@ -1,57 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import numpy as np - -from bigframes.operations import base_ops, generic_ops, numeric_ops - -# Just parameterless unary ops for now -# TODO: Parameter mappings -NUMPY_TO_OP: dict[np.ufunc, base_ops.UnaryOp] = { - np.sin: numeric_ops.sin_op, - np.cos: numeric_ops.cos_op, - np.tan: numeric_ops.tan_op, - np.arcsin: numeric_ops.arcsin_op, - np.arccos: numeric_ops.arccos_op, - np.arctan: numeric_ops.arctan_op, - np.sinh: numeric_ops.sinh_op, - np.cosh: numeric_ops.cosh_op, - np.tanh: numeric_ops.tanh_op, - np.arcsinh: numeric_ops.arcsinh_op, - np.arccosh: numeric_ops.arccosh_op, - np.arctanh: numeric_ops.arctanh_op, - np.exp: numeric_ops.exp_op, - np.log: numeric_ops.ln_op, - np.log10: numeric_ops.log10_op, - np.sqrt: numeric_ops.sqrt_op, - np.abs: numeric_ops.abs_op, - np.floor: numeric_ops.floor_op, - np.ceil: numeric_ops.ceil_op, - np.log1p: numeric_ops.log1p_op, - np.expm1: numeric_ops.expm1_op, - np.isnan: numeric_ops.isnan_op, - np.isfinite: numeric_ops.isfinite_op, -} - - -NUMPY_TO_BINOP: dict[np.ufunc, base_ops.BinaryOp] = { - np.add: numeric_ops.add_op, - np.subtract: numeric_ops.sub_op, - np.multiply: numeric_ops.mul_op, - np.divide: numeric_ops.div_op, - np.power: numeric_ops.pow_op, - np.arctan2: numeric_ops.arctan2_op, - np.maximum: generic_ops.maximum_op, - np.minimum: generic_ops.minimum_op, -} diff --git a/bigframes/operations/output_schemas.py b/bigframes/operations/output_schemas.py deleted file mode 100644 index ff9c9883dc0..00000000000 --- a/bigframes/operations/output_schemas.py +++ /dev/null @@ -1,90 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import pyarrow as pa - - -def parse_sql_type(sql: str) -> pa.DataType: - """ - Parses a SQL type string to its PyArrow equivalence: - - For example: - "STRING" -> pa.string() - "ARRAY" -> pa.list_(pa.int64()) - "STRUCT, y BOOL>" -> pa.struct( - ( - pa.field("x", pa.list_(pa.float64())), - pa.field("y", pa.bool_()), - ) - ) - """ - sql = sql.strip() - - if sql.upper() == "STRING": - return pa.string() - - if sql.upper() == "INT64": - return pa.int64() - - if sql.upper() == "FLOAT64": - return pa.float64() - - if sql.upper() == "BOOL": - return pa.bool_() - - if sql.upper().startswith("ARRAY<") and sql.endswith(">"): - inner_type = sql[len("ARRAY<") : -1] - return pa.list_(parse_sql_type(inner_type)) - - if sql.upper().startswith("STRUCT<") and sql.endswith(">"): - inner_fields = parse_sql_fields(sql[len("STRUCT<") : -1]) - return pa.struct(inner_fields) - - raise ValueError(f"Unsupported SQL type: {sql}") - - -def parse_sql_fields(sql: str) -> tuple[pa.Field]: - sql = sql.strip() - - start_idx = 0 - nested_depth = 0 - fields: list[pa.field] = [] - - for end_idx in range(len(sql)): - c = sql[end_idx] - - if c == "<": - nested_depth += 1 - elif c == ">": - nested_depth -= 1 - elif c == "," and nested_depth == 0: - field = sql[start_idx:end_idx] - fields.append(parse_sql_field(field)) - start_idx = end_idx + 1 - - # Append the last field - fields.append(parse_sql_field(sql[start_idx:])) - - return tuple(sorted(fields, key=lambda f: f.name)) - - -def parse_sql_field(sql: str) -> pa.Field: - sql = sql.strip() - - space_idx = sql.find(" ") - - if space_idx == -1: - raise ValueError(f"Invalid struct field: {sql}") - - return pa.field(sql[:space_idx].strip(), parse_sql_type(sql[space_idx:])) diff --git a/bigframes/operations/plotting.py b/bigframes/operations/plotting.py deleted file mode 100644 index ecaa28e9747..00000000000 --- a/bigframes/operations/plotting.py +++ /dev/null @@ -1,108 +0,0 @@ -# Copyright 2023 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 typing - -import bigframes_vendored.constants as constants -import bigframes_vendored.pandas.plotting._core as vendordt - -import bigframes.operations._matplotlib as bfplt -from bigframes._tools import docs -from bigframes.core.logging import log_adapter - - -@log_adapter.class_logger -@docs.inherit_docs(vendordt.PlotAccessor) -class PlotAccessor: - _common_kinds = ("line", "area", "hist", "bar", "barh", "pie") - _dataframe_kinds = ("scatter", "hexbin,") - _all_kinds = _common_kinds + _dataframe_kinds - - def __call__(self, **kwargs): - import bigframes.series as series - - if kwargs.pop("backend", None) is not None: - raise NotImplementedError( - f"Only support matplotlib backend for now. {constants.FEEDBACK_LINK}" - ) - - kind = kwargs.pop("kind", "line") - if kind not in self._all_kinds: - raise NotImplementedError( - f"{kind} is not a valid plot kind supported for now. {constants.FEEDBACK_LINK}" - ) - - data = self._parent.copy() - if kind in self._dataframe_kinds and isinstance(data, series.Series): - raise ValueError(f"plot kind {kind} can only be used for data frames") - - return bfplt.plot(data, kind=kind, **kwargs) - - def __init__(self, data) -> None: - self._parent = data - - def hist( - self, by: typing.Optional[typing.Sequence[str]] = None, bins: int = 10, **kwargs - ): - return self(kind="hist", by=by, bins=bins, **kwargs) - - def line( - self, - x: typing.Optional[typing.Hashable] = None, - y: typing.Optional[typing.Hashable] = None, - **kwargs, - ): - return self(kind="line", x=x, y=y, **kwargs) - - def area( - self, - x: typing.Optional[typing.Hashable] = None, - y: typing.Optional[typing.Hashable] = None, - stacked: bool = True, - **kwargs, - ): - return self(kind="area", x=x, y=y, stacked=stacked, **kwargs) - - def bar( - self, - x: typing.Optional[typing.Hashable] = None, - y: typing.Optional[typing.Hashable] = None, - **kwargs, - ): - return self(kind="bar", x=x, y=y, **kwargs) - - def barh( - self, - x: typing.Optional[typing.Hashable] = None, - y: typing.Optional[typing.Hashable] = None, - **kwargs, - ): - return self(kind="barh", x=x, y=y, **kwargs) - - def pie( - self, - y: typing.Optional[typing.Hashable] = None, - **kwargs, - ): - return self(kind="pie", y=y, **kwargs) - - def scatter( - self, - x: typing.Optional[typing.Hashable] = None, - y: typing.Optional[typing.Hashable] = None, - s: typing.Union[typing.Hashable, typing.Sequence[typing.Hashable]] = None, - c: typing.Union[typing.Hashable, typing.Sequence[typing.Hashable]] = None, - **kwargs, - ): - return self(kind="scatter", x=x, y=y, s=s, c=c, **kwargs) diff --git a/bigframes/operations/python_op_maps.py b/bigframes/operations/python_op_maps.py deleted file mode 100644 index b4c58e14c7b..00000000000 --- a/bigframes/operations/python_op_maps.py +++ /dev/null @@ -1,126 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import math -import operator -from typing import Optional - -import bigframes.operations -from bigframes.operations import ( - aggregations, - array_ops, - bool_ops, - comparison_ops, - generic_ops, - numeric_ops, - string_ops, -) - -PYTHON_TO_BIGFRAMES = { - ## operators - operator.add: numeric_ops.add_op, - operator.sub: numeric_ops.sub_op, - operator.mul: numeric_ops.mul_op, - operator.truediv: numeric_ops.div_op, - operator.floordiv: numeric_ops.floordiv_op, - operator.mod: numeric_ops.mod_op, - operator.pow: numeric_ops.pow_op, - operator.pos: numeric_ops.pos_op, - operator.neg: numeric_ops.neg_op, - operator.abs: numeric_ops.abs_op, - operator.eq: comparison_ops.eq_op, - operator.ne: comparison_ops.ne_op, - operator.gt: comparison_ops.gt_op, - operator.lt: comparison_ops.lt_op, - operator.ge: comparison_ops.ge_op, - operator.le: comparison_ops.le_op, - operator.and_: bool_ops.and_op, - operator.or_: bool_ops.or_op, - operator.xor: bool_ops.xor_op, - operator.invert: generic_ops.invert_op, - operator.not_: generic_ops.invert_op, - ## math - math.log: numeric_ops.ln_op, - math.log10: numeric_ops.log10_op, - math.log1p: numeric_ops.log1p_op, - math.expm1: numeric_ops.expm1_op, - math.sin: numeric_ops.sin_op, - math.cos: numeric_ops.cos_op, - math.tan: numeric_ops.tan_op, - math.sinh: numeric_ops.sinh_op, - math.cosh: numeric_ops.cosh_op, - math.tanh: numeric_ops.tanh_op, - math.asin: numeric_ops.arcsin_op, - math.acos: numeric_ops.arccos_op, - math.atan: numeric_ops.arctan_op, - math.floor: numeric_ops.floor_op, - math.ceil: numeric_ops.ceil_op, - ## str - str.upper: string_ops.upper_op, - str.lower: string_ops.lower_op, - str.isalnum: string_ops.isalnum_op, - str.isalpha: string_ops.isalpha_op, - str.isdecimal: string_ops.isdecimal_op, - str.isdigit: string_ops.isdigit_op, - str.isnumeric: string_ops.isnumeric_op, - str.isspace: string_ops.isspace_op, - str.islower: string_ops.islower_op, - str.isupper: string_ops.isupper_op, - str.capitalize: string_ops.capitalize_op, - ## builtins - len: string_ops.len_op, - abs: numeric_ops.abs_op, - pow: numeric_ops.pow_op, - ### builtins -- iterable - all: array_ops.ArrayReduceOp(aggregations.all_op), # type: ignore - any: array_ops.ArrayReduceOp(aggregations.any_op), # type: ignore - sum: array_ops.ArrayReduceOp(aggregations.sum_op), # type: ignore - min: array_ops.ArrayReduceOp(aggregations.min_op), # type: ignore - max: array_ops.ArrayReduceOp(aggregations.max_op), # type: ignore -} - - -def python_callable_to_op(obj) -> Optional[bigframes.operations.RowOp]: - if obj in PYTHON_TO_BIGFRAMES: - return PYTHON_TO_BIGFRAMES[obj] - return None - - -SERIES_METHOD_TO_OP = { - "abs": numeric_ops.abs_op, - "sqrt": numeric_ops.sqrt_op, - "sin": numeric_ops.sin_op, - "cos": numeric_ops.cos_op, - "tan": numeric_ops.tan_op, - "log": numeric_ops.ln_op, - "log10": numeric_ops.log10_op, - "exp": numeric_ops.exp_op, - "floor": numeric_ops.floor_op, - "ceil": numeric_ops.ceil_op, - "isnull": generic_ops.isnull_op, - "isna": generic_ops.isnull_op, - "notnull": generic_ops.notnull_op, - "notna": generic_ops.notnull_op, - "upper": string_ops.upper_op, - "lower": string_ops.lower_op, - "isalnum": string_ops.isalnum_op, - "isalpha": string_ops.isalpha_op, - "isdecimal": string_ops.isdecimal_op, - "isdigit": string_ops.isdigit_op, - "isnumeric": string_ops.isnumeric_op, - "isspace": string_ops.isspace_op, - "islower": string_ops.islower_op, - "isupper": string_ops.isupper_op, - "capitalize": string_ops.capitalize_op, -} diff --git a/bigframes/operations/remote_function_ops.py b/bigframes/operations/remote_function_ops.py deleted file mode 100644 index 3ce77d51c61..00000000000 --- a/bigframes/operations/remote_function_ops.py +++ /dev/null @@ -1,45 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import dataclasses -import typing - -from bigframes.functions import udf_def -from bigframes.operations import base_ops - - -@dataclasses.dataclass(frozen=True) -class PythonUdfOp(base_ops.NaryOp): - name: typing.ClassVar[str] = "python_udf" - function_def: udf_def.PythonUdf - - @property - def expensive(self) -> bool: - return True - - def output_type(self, *input_types): - return self.function_def.signature.output.bf_type - - -@dataclasses.dataclass(frozen=True) -class RemoteFunctionOp(base_ops.NaryOp): - name: typing.ClassVar[str] = "remote_function" - function_def: udf_def.BigqueryUdf - - @property - def expensive(self) -> bool: - return True - - def output_type(self, *input_types): - return self.function_def.signature.output.bf_type diff --git a/bigframes/operations/string_ops.py b/bigframes/operations/string_ops.py deleted file mode 100644 index 21d23416314..00000000000 --- a/bigframes/operations/string_ops.py +++ /dev/null @@ -1,283 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import dataclasses -import typing - -import pandas as pd -import pyarrow as pa - -import bigframes.operations.type as op_typing -from bigframes import dtypes -from bigframes.operations import base_ops - -LenOp = base_ops.create_unary_op( - name="len", - type_signature=op_typing.FixedOutputType( - dtypes.is_iterable, dtypes.INT_DTYPE, description="iterable" - ), -) -len_op = LenOp() - -## Specialized len ops for compile-time lowering -StrLenOp = base_ops.create_unary_op( - name="strlen", - type_signature=op_typing.FixedOutputType( - dtypes.is_string_like, dtypes.INT_DTYPE, description="string-like" - ), -) -str_len_op = StrLenOp() - -ArrayLenOp = base_ops.create_unary_op( - name="arraylen", - type_signature=op_typing.FixedOutputType( - dtypes.is_array_like, dtypes.INT_DTYPE, description="array-like" - ), -) -array_len_op = ArrayLenOp() - -ReverseOp = base_ops.create_unary_op( - name="reverse", type_signature=op_typing.STRING_TRANSFORM -) -reverse_op = ReverseOp() - -LowerOp = base_ops.create_unary_op( - name="lower", type_signature=op_typing.STRING_TRANSFORM -) -lower_op = LowerOp() - -UpperOp = base_ops.create_unary_op( - name="upper", type_signature=op_typing.STRING_TRANSFORM -) -upper_op = UpperOp() - -IsAlnumOp = base_ops.create_unary_op( - name="isalnum", type_signature=op_typing.STRING_PREDICATE -) -isalnum_op = IsAlnumOp() - -IsAlphaOp = base_ops.create_unary_op( - name="isalpha", type_signature=op_typing.STRING_PREDICATE -) -isalpha_op = IsAlphaOp() - -IsDecimalOp = base_ops.create_unary_op( - name="isdecimal", type_signature=op_typing.STRING_PREDICATE -) -isdecimal_op = IsDecimalOp() - -IsDigitOp = base_ops.create_unary_op( - name="isdigit", type_signature=op_typing.STRING_PREDICATE -) -isdigit_op = IsDigitOp() - -IsNumericOp = base_ops.create_unary_op( - name="isnumeric", type_signature=op_typing.STRING_PREDICATE -) -isnumeric_op = IsNumericOp() - -IsSpaceOp = base_ops.create_unary_op( - name="isspace", type_signature=op_typing.STRING_PREDICATE -) -isspace_op = IsSpaceOp() - -IsLowerOp = base_ops.create_unary_op( - name="islower", type_signature=op_typing.STRING_PREDICATE -) -islower_op = IsLowerOp() - -IsUpperOp = base_ops.create_unary_op( - name="isupper", type_signature=op_typing.STRING_PREDICATE -) -isupper_op = IsUpperOp() - -CapitalizeOp = base_ops.create_unary_op( - name="capitalize", type_signature=op_typing.STRING_TRANSFORM -) -capitalize_op = CapitalizeOp() - - -@dataclasses.dataclass(frozen=True) -class StrContainsOp(base_ops.UnaryOp): - name: typing.ClassVar[str] = "str_contains" - pat: str - - def output_type(self, *input_types): - return op_typing.STRING_PREDICATE.output_type(input_types[0]) - - -@dataclasses.dataclass(frozen=True) -class StrContainsRegexOp(base_ops.UnaryOp): - name: typing.ClassVar[str] = "str_contains_regex" - pat: str - - def output_type(self, *input_types): - return op_typing.STRING_PREDICATE.output_type(input_types[0]) - - -@dataclasses.dataclass(frozen=True) -class StrPadOp(base_ops.UnaryOp): - name: typing.ClassVar[str] = "str_pad" - length: int - fillchar: str - side: typing.Literal["both", "left", "right"] - - def output_type(self, *input_types): - return op_typing.STRING_TRANSFORM.output_type(input_types[0]) - - -@dataclasses.dataclass(frozen=True) -class StrStripOp(base_ops.UnaryOp): - name: typing.ClassVar[str] = "str_strip" - to_strip: str - - def output_type(self, *input_types): - return op_typing.STRING_TRANSFORM.output_type(input_types[0]) - - -@dataclasses.dataclass(frozen=True) -class StrLstripOp(base_ops.UnaryOp): - name: typing.ClassVar[str] = "str_lstrip" - to_strip: str - - def output_type(self, *input_types): - return op_typing.STRING_TRANSFORM.output_type(input_types[0]) - - -@dataclasses.dataclass(frozen=True) -class StrRstripOp(base_ops.UnaryOp): - name: typing.ClassVar[str] = "str_rstrip" - to_strip: str - - def output_type(self, *input_types): - return op_typing.STRING_TRANSFORM.output_type(input_types[0]) - - -@dataclasses.dataclass(frozen=True) -class ReplaceStrOp(base_ops.UnaryOp): - name: typing.ClassVar[str] = "str_replace" - pat: str - repl: str - - def output_type(self, *input_types): - return op_typing.STRING_TRANSFORM.output_type(input_types[0]) - - -@dataclasses.dataclass(frozen=True) -class RegexReplaceStrOp(base_ops.UnaryOp): - name: typing.ClassVar[str] = "str_rereplace" - pat: str - repl: str - - def output_type(self, *input_types): - return op_typing.STRING_TRANSFORM.output_type(input_types[0]) - - -@dataclasses.dataclass(frozen=True) -class StartsWithOp(base_ops.UnaryOp): - name: typing.ClassVar[str] = "str_startswith" - pat: typing.Sequence[str] - - def output_type(self, *input_types): - return op_typing.STRING_PREDICATE.output_type(input_types[0]) - - -@dataclasses.dataclass(frozen=True) -class StringSplitOp(base_ops.UnaryOp): - name: typing.ClassVar[str] = "str_split" - pat: typing.Sequence[str] - - def output_type(self, *input_types): - input_type = input_types[0] - if not isinstance(input_type, pd.StringDtype): - raise TypeError("field accessor input must be a string type") - arrow_type = dtypes.bigframes_dtype_to_arrow_dtype(input_type) - return pd.ArrowDtype(pa.list_(arrow_type)) - - -@dataclasses.dataclass(frozen=True) -class EndsWithOp(base_ops.UnaryOp): - name: typing.ClassVar[str] = "str_endswith" - pat: typing.Sequence[str] - - def output_type(self, *input_types): - return op_typing.STRING_PREDICATE.output_type(input_types[0]) - - -@dataclasses.dataclass(frozen=True) -class ZfillOp(base_ops.UnaryOp): - name: typing.ClassVar[str] = "str_zfill" - width: int - - def output_type(self, *input_types): - return op_typing.STRING_TRANSFORM.output_type(input_types[0]) - - -@dataclasses.dataclass(frozen=True) -class StrFindOp(base_ops.UnaryOp): - name: typing.ClassVar[str] = "str_find" - substr: str - start: typing.Optional[int] - end: typing.Optional[int] - - def output_type(self, *input_types): - signature = op_typing.FixedOutputType( - dtypes.is_string_like, dtypes.INT_DTYPE, "string-like" - ) - return signature.output_type(input_types[0]) - - -@dataclasses.dataclass(frozen=True) -class StrExtractOp(base_ops.UnaryOp): - name: typing.ClassVar[str] = "str_extract" - pat: str - n: int = 1 - - def output_type(self, *input_types): - return op_typing.STRING_TRANSFORM.output_type(input_types[0]) - - -@dataclasses.dataclass(frozen=True) -class StrSliceOp(base_ops.UnaryOp): - name: typing.ClassVar[str] = "str_slice" - start: typing.Optional[int] - end: typing.Optional[int] - - def output_type(self, *input_types): - return op_typing.STRING_TRANSFORM.output_type(input_types[0]) - - -@dataclasses.dataclass(frozen=True) -class StrRepeatOp(base_ops.UnaryOp): - name: typing.ClassVar[str] = "str_repeat" - repeats: int - - def output_type(self, *input_types): - return op_typing.STRING_TRANSFORM.output_type(input_types[0]) - - -@dataclasses.dataclass(frozen=True) -class StrConcatOp(base_ops.BinaryOp): - name: typing.ClassVar[str] = "str_concat" - - # Note: this is actualyl a vararg op, but we don't model that yet - def output_type(self, *input_types): - if not all(map(dtypes.is_string_like, input_types)): - raise TypeError("string concat requires string-like arguments") - if len(set(input_types)) != 1: - raise TypeError("string concat requires like-typed arguments") - return input_types[0] - - -strconcat_op = StrConcatOp() diff --git a/bigframes/operations/strings.py b/bigframes/operations/strings.py index ad9bfd6da40..0545ea34d6a 100644 --- a/bigframes/operations/strings.py +++ b/bigframes/operations/strings.py @@ -15,20 +15,14 @@ from __future__ import annotations import re -from typing import Generic, Hashable, Literal, Optional, TypeVar, Union +from typing import cast, Literal, Optional, Union -import bigframes_vendored.constants as constants -import bigframes_vendored.pandas.core.strings.accessor as vendorstr - -import bigframes.core.col -import bigframes.core.indexes.base as indices +import bigframes.constants as constants import bigframes.dataframe as df import bigframes.operations as ops -import bigframes.operations.aggregations as agg_ops +import bigframes.operations.base import bigframes.series as series -from bigframes._tools import docs -from bigframes.core.logging import log_adapter -from bigframes.operations._op_converters import convert_index, convert_slice +import third_party.bigframes_vendored.pandas.core.strings.accessor as vendorstr # Maps from python to re2 REGEXP_FLAGS = { @@ -37,170 +31,127 @@ re.DOTALL: "s", } -T = TypeVar("T", series.Series, indices.Index, bigframes.core.col.Expression) - - -@log_adapter.class_logger -@docs.inherit_docs(vendorstr.StringMethods) -class StringMethods(Generic[T]): - def __init__(self, data: T): - self._data: T = data - def __getitem__(self, key: Union[int, slice]) -> T: - if isinstance(key, int): - return self._data._apply_unary_op(convert_index(key)) - elif isinstance(key, slice): - return self._data._apply_unary_op(convert_slice(key)) - else: - raise ValueError(f"key must be an int or slice, got {type(key).__name__}") +class StringMethods(bigframes.operations.base.SeriesMethods, vendorstr.StringMethods): + __doc__ = vendorstr.StringMethods.__doc__ def find( self, sub: str, start: Optional[int] = None, end: Optional[int] = None, - ) -> T: - return self._data._apply_unary_op( - ops.StrFindOp(substr=sub, start=start, end=end) - ) - - def len(self) -> T: - return self._data._apply_unary_op(ops.len_op) + ) -> series.Series: + return self._apply_unary_op(ops.FindOp(sub, start, end)) - def lower(self) -> T: - return self._data._apply_unary_op(ops.lower_op) + def len(self) -> series.Series: + return self._apply_unary_op(ops.len_op) - def reverse(self) -> T: - """Reverse strings in the Series. + def lower(self) -> series.Series: + return self._apply_unary_op(ops.lower_op) - **Examples:** - - >>> import bigframes.pandas as bpd - >>> s = bpd.Series(["apple", "banana", "", pd.NA]) - >>> s.str.reverse() - 0 elppa - 1 ananab - 2 - 3 - dtype: string - - Returns: - bigframes.series.Series: A Series of booleans indicating whether the given - pattern matches the start of each string element. - """ + def reverse(self) -> series.Series: + """Reverse strings in the Series.""" # reverse method is in ibis, not pandas. - return self._data._apply_unary_op(ops.reverse_op) + return self._apply_unary_op(ops.reverse_op) def slice( self, start: Optional[int] = None, stop: Optional[int] = None, - ) -> T: - return self._data._apply_unary_op(ops.StrSliceOp(start=start, end=stop)) + ) -> series.Series: + return self._apply_unary_op(ops.SliceOp(start, stop)) - def strip(self, to_strip: Optional[str] = None) -> T: - return self._data._apply_unary_op( - ops.StrStripOp(to_strip=" \n\t" if to_strip is None else to_strip) - ) + def strip(self) -> series.Series: + return self._apply_unary_op(ops.strip_op) - def upper(self) -> T: - return self._data._apply_unary_op(ops.upper_op) + def upper(self) -> series.Series: + return self._apply_unary_op(ops.upper_op) - def isnumeric(self) -> T: - return self._data._apply_unary_op(ops.isnumeric_op) + def isnumeric(self) -> series.Series: + return self._apply_unary_op(ops.isnumeric_op) def isalpha( self, - ) -> T: - return self._data._apply_unary_op(ops.isalpha_op) + ) -> series.Series: + return self._apply_unary_op(ops.isalpha_op) def isdigit( self, - ) -> T: - return self._data._apply_unary_op(ops.isdigit_op) + ) -> series.Series: + return self._apply_unary_op(ops.isdigit_op) def isdecimal( self, - ) -> T: - return self._data._apply_unary_op(ops.isdecimal_op) + ) -> series.Series: + return self._apply_unary_op(ops.isdecimal_op) def isalnum( self, - ) -> T: - return self._data._apply_unary_op(ops.isalnum_op) + ) -> series.Series: + return self._apply_unary_op(ops.isalnum_op) def isspace( self, - ) -> T: - return self._data._apply_unary_op(ops.isspace_op) + ) -> series.Series: + return self._apply_unary_op(ops.isspace_op) def islower( self, - ) -> T: - return self._data._apply_unary_op(ops.islower_op) + ) -> series.Series: + return self._apply_unary_op(ops.islower_op) def isupper( self, - ) -> T: - return self._data._apply_unary_op(ops.isupper_op) + ) -> series.Series: + return self._apply_unary_op(ops.isupper_op) - def rstrip(self, to_strip: Optional[str] = None) -> T: - return self._data._apply_unary_op( - ops.StrRstripOp(to_strip=" \n\t" if to_strip is None else to_strip) - ) + def rstrip(self) -> series.Series: + return self._apply_unary_op(ops.rstrip_op) - def lstrip(self, to_strip: Optional[str] = None) -> T: - return self._data._apply_unary_op( - ops.StrLstripOp(to_strip=" \n\t" if to_strip is None else to_strip) - ) + def lstrip(self) -> series.Series: + return self._apply_unary_op(ops.lstrip_op) - def repeat(self, repeats: int) -> T: - return self._data._apply_unary_op(ops.StrRepeatOp(repeats=repeats)) + def repeat(self, repeats: int) -> series.Series: + return self._apply_unary_op(ops.RepeatOp(repeats)) - def capitalize(self) -> T: - return self._data._apply_unary_op(ops.capitalize_op) + def capitalize(self) -> series.Series: + return self._apply_unary_op(ops.capitalize_op) - def match(self, pat, case=True, flags=0) -> T: + def match(self, pat, case=True, flags=0) -> series.Series: # \A anchors start of entire string rather than start of any line in multiline mode adj_pat = rf"\A{pat}" - return self.contains(pat=adj_pat, case=case, flags=flags) + return self.contains(adj_pat, case=case, flags=flags) - def fullmatch(self, pat, case=True, flags=0) -> T: + def fullmatch(self, pat, case=True, flags=0) -> series.Series: # \A anchors start of entire string rather than start of any line in multiline mode # \z likewise anchors to the end of the entire multiline string adj_pat = rf"\A{pat}\z" - return self.contains(pat=adj_pat, case=case, flags=flags) + return self.contains(adj_pat, case=case, flags=flags) - def get(self, i: int) -> T: - return self._data._apply_unary_op(ops.GetItemOp(key=i)) + def get(self, i: int) -> series.Series: + return self._apply_unary_op(ops.StrGetOp(i)) - def pad(self, width, side="left", fillchar=" ") -> T: - return self._data._apply_unary_op( - ops.StrPadOp(length=width, fillchar=fillchar, side=side) - ) + def pad(self, width, side="left", fillchar=" ") -> series.Series: + return self._apply_unary_op(ops.StrPadOp(width, fillchar, side)) - def ljust(self, width, fillchar=" ") -> T: - return self._data._apply_unary_op( - ops.StrPadOp(length=width, fillchar=fillchar, side="right") - ) + def ljust(self, width, fillchar=" ") -> series.Series: + return self._apply_unary_op(ops.StrPadOp(width, fillchar, "right")) - def rjust(self, width, fillchar=" ") -> T: - return self._data._apply_unary_op( - ops.StrPadOp(length=width, fillchar=fillchar, side="left") - ) + def rjust(self, width, fillchar=" ") -> series.Series: + return self._apply_unary_op(ops.StrPadOp(width, fillchar, "left")) def contains( self, pat, case: bool = True, flags: int = 0, *, regex: bool = True - ) -> T: + ) -> series.Series: if not case: - return self.contains(pat=pat, flags=flags | re.IGNORECASE, regex=True) + return self.contains(pat, flags=flags | re.IGNORECASE, regex=True) if regex: re2flags = _parse_flags(flags) if re2flags: pat = re2flags + pat - return self._data._apply_unary_op(ops.StrContainsRegexOp(pat=pat)) + return self._apply_unary_op(ops.ContainsRegexOp(pat)) else: - return self._data._apply_unary_op(ops.StrContainsOp(pat=pat)) + return self._apply_unary_op(ops.ContainsStringOp(pat)) def extract(self, pat: str, flags: int = 0) -> df.DataFrame: re2flags = _parse_flags(flags) @@ -210,19 +161,21 @@ def extract(self, pat: str, flags: int = 0) -> df.DataFrame: if compiled.groups == 0: raise ValueError("No capture groups in 'pat'") - results: dict[Hashable, series.Series] = {} + results: list[str] = [] + block = self._block for i in range(compiled.groups): labels = [ label for label, groupn in compiled.groupindex.items() if i + 1 == groupn ] - label = labels[0] if labels else i - result = self._data._apply_unary_op( - ops.StrExtractOp(pat=pat, n=i + 1), + label = labels[0] if labels else str(i) + block, id = block.apply_unary_op( + self._value_column, ops.ExtractOp(pat, i + 1), result_label=label ) - results[label] = series.Series(result) - return df.DataFrame(results) + results.append(id) + block = block.select_columns(results) + return df.DataFrame(block) def replace( self, @@ -232,92 +185,61 @@ def replace( case: Optional[bool] = None, flags: int = 0, regex: bool = False, - ) -> T: - if isinstance(pat, re.Pattern): - assert isinstance(pat.pattern, str) - pat_str = pat.pattern - flags = pat.flags | flags - else: - pat_str = pat - + ) -> series.Series: + is_compiled = isinstance(pat, re.Pattern) + patstr = cast(str, pat.pattern if is_compiled else pat) # type: ignore if case is False: - return self.replace(pat_str, repl, flags=flags | re.IGNORECASE, regex=True) + return self.replace(pat, repl, flags=flags | re.IGNORECASE, regex=True) if regex: re2flags = _parse_flags(flags) if re2flags: - pat_str = re2flags + pat_str - return self._data._apply_unary_op( - ops.RegexReplaceStrOp(pat=pat_str, repl=repl) - ) + patstr = re2flags + patstr + return self._apply_unary_op(ops.ReplaceRegexOp(patstr, repl)) else: - if isinstance(pat, re.Pattern): + if is_compiled: raise ValueError( "Must set 'regex'=True if using compiled regex pattern." ) - return self._data._apply_unary_op(ops.ReplaceStrOp(pat=pat_str, repl=repl)) + return self._apply_unary_op(ops.ReplaceStringOp(patstr, repl)) def startswith( self, pat: Union[str, tuple[str, ...]], - ) -> T: + ) -> series.Series: if not isinstance(pat, tuple): pat = (pat,) - return self._data._apply_unary_op(ops.StartsWithOp(pat=pat)) + return self._apply_unary_op(ops.StartsWithOp(pat)) def endswith( self, pat: Union[str, tuple[str, ...]], - ) -> T: + ) -> series.Series: if not isinstance(pat, tuple): pat = (pat,) - return self._data._apply_unary_op(ops.EndsWithOp(pat=pat)) - - def split( - self, - pat: str = " ", - regex: Union[bool, None] = None, - ) -> T: - if regex is True or (regex is None and len(pat) > 1): - raise NotImplementedError( - "Regular expressions aren't currently supported. Please set " - + f"`regex=False` and try again. {constants.FEEDBACK_LINK}" - ) - return self._data._apply_unary_op(ops.StringSplitOp(pat=pat)) + return self._apply_unary_op(ops.EndsWithOp(pat)) - def zfill(self, width: int) -> T: - return self._data._apply_unary_op(ops.ZfillOp(width=width)) + def zfill(self, width: int) -> series.Series: + return self._apply_unary_op(ops.ZfillOp(width)) - def center(self, width: int, fillchar: str = " ") -> T: - return self._data._apply_unary_op( - ops.StrPadOp(length=width, fillchar=fillchar, side="both") - ) + def center(self, width: int, fillchar: str = " ") -> series.Series: + return self._apply_unary_op(ops.StrPadOp(width, fillchar, "both")) def cat( self, - others: Union[str, indices.Index, series.Series], + others: Union[str, series.Series], *, join: Literal["outer", "left"] = "left", - ) -> T: - return self._data._apply_binary_op(others, ops.strconcat_op, alignment=join) - - def join(self, sep: str) -> T: - return self._data._apply_unary_op( - ops.ArrayReduceOp(aggregation=agg_ops.StringAggOp(sep=sep)) - ) + ) -> series.Series: + return self._apply_binary_op(others, ops.concat_op, alignment=join) def _parse_flags(flags: int) -> Optional[str]: re2flags = [] for reflag, re2flag in REGEXP_FLAGS.items(): - if flags & reflag: + if flags & flags: re2flags.append(re2flag) flags = flags ^ reflag - # re2 handles unicode fine by default - # most compiled re in python will have unicode set - if re.U and flags: - flags = flags ^ re.U - # Remaining flags couldn't be mapped to re2 engine if flags: raise NotImplementedError( diff --git a/bigframes/operations/struct_ops.py b/bigframes/operations/struct_ops.py deleted file mode 100644 index de51efd8a48..00000000000 --- a/bigframes/operations/struct_ops.py +++ /dev/null @@ -1,65 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import dataclasses -import typing - -import pandas as pd -import pyarrow as pa - -from bigframes import dtypes -from bigframes.operations import base_ops - - -@dataclasses.dataclass(frozen=True) -class StructFieldOp(base_ops.UnaryOp): - name: typing.ClassVar[str] = "struct_field" - name_or_index: typing.Union[str, int] - - def output_type(self, *input_types): - input_type = input_types[0] - if not isinstance(input_type, pd.ArrowDtype): - raise TypeError("field accessor input must be a struct type") - - pa_type = input_type.pyarrow_dtype - if not isinstance(pa_type, pa.StructType): - raise TypeError("field accessor input must be a struct type") - - pa_result_type = pa_type[self.name_or_index].type - return dtypes.arrow_dtype_to_bigframes_dtype(pa_result_type) - - -@dataclasses.dataclass(frozen=True) -class StructOp(base_ops.NaryOp): - name: typing.ClassVar[str] = "struct" - column_names: tuple[str, ...] - - def output_type(self, *input_types: dtypes.ExpressionType) -> dtypes.ExpressionType: - num_input_types = len(input_types) - # value1, value2, ... - assert num_input_types == len(self.column_names) - fields = [] - - for i in range(num_input_types): - arrow_type = dtypes.bigframes_dtype_to_arrow_dtype(input_types[i]) - fields.append( - pa.field( - self.column_names[i], - arrow_type, - nullable=(not pa.types.is_list(arrow_type)), - ) - ) - return pd.ArrowDtype( - pa.struct(fields) - ) # [(name1, value1), (name2, value2), ...] diff --git a/bigframes/operations/structs.py b/bigframes/operations/structs.py index c5446510a6d..506a5577094 100644 --- a/bigframes/operations/structs.py +++ b/bigframes/operations/structs.py @@ -14,79 +14,48 @@ from __future__ import annotations -import bigframes_vendored.pandas.core.arrays.arrow.accessors as vendoracessors -import pandas as pd +import typing + +import ibis.expr.types as ibis_types import bigframes.dataframe import bigframes.operations +import bigframes.operations.base import bigframes.series -from bigframes._tools import docs -from bigframes.core import backports -from bigframes.core.logging import log_adapter +import third_party.bigframes_vendored.pandas.core.arrays.arrow.accessors as vendoracessors + + +class _StructField(bigframes.operations.UnaryOp): + def __init__(self, name_or_index: str | int): + self._name_or_index = name_or_index + + def _as_ibis(self, x: ibis_types.Value): + struct_value = typing.cast(ibis_types.StructValue, x) + if isinstance(self._name_or_index, str): + name = self._name_or_index + else: + name = struct_value.names[self._name_or_index] + return struct_value[name].name(name) -@log_adapter.class_logger -@docs.inherit_docs(vendoracessors.StructAccessor) -class StructAccessor: - def __init__(self, data: bigframes.series.Series): - self._data = data +class StructAccessor( + bigframes.operations.base.SeriesMethods, vendoracessors.StructAccessor +): + __doc__ = vendoracessors.StructAccessor.__doc__ def field(self, name_or_index: str | int) -> bigframes.series.Series: - series = self._data._apply_unary_op( - bigframes.operations.StructFieldOp(name_or_index) - ) + series = self._apply_unary_op(_StructField(name_or_index)) if isinstance(name_or_index, str): name = name_or_index else: - struct_field = self._data._dtype.pyarrow_dtype[name_or_index] + struct_field = self._dtype.pyarrow_dtype[name_or_index] name = struct_field.name return series.rename(name) def explode(self) -> bigframes.dataframe.DataFrame: import bigframes.pandas - pa_type = self._data._dtype.pyarrow_dtype + pa_type = self._dtype.pyarrow_dtype return bigframes.pandas.concat( - [ - self.field(field.name) - for field in backports.pyarrow_struct_type_fields(pa_type) - ], - axis="columns", - ) - - @property - def dtypes(self) -> pd.Series: - pa_type = self._data._dtype.pyarrow_dtype - return pd.Series( - data=[ - pd.ArrowDtype(field.type) - for field in backports.pyarrow_struct_type_fields(pa_type) - ], - index=[ - field.name for field in backports.pyarrow_struct_type_fields(pa_type) - ], + [self.field(i) for i in range(pa_type.num_fields)], axis="columns" ) - - -@log_adapter.class_logger -@docs.inherit_docs(vendoracessors.StructFrameAccessor) -class StructFrameAccessor: - __doc__ = vendoracessors.StructAccessor.__doc__ - - def __init__(self, data: bigframes.dataframe.DataFrame) -> None: - self._parent = data - - def explode(self, column, *, separator: str = ".") -> bigframes.dataframe.DataFrame: - df = self._parent - column_labels = bigframes.core.explode.check_column(column) - - for label in column_labels: - position = df.columns.to_list().index(label) - df = df.drop(columns=label) - subfields = self._parent[label].struct.explode() - for subfield in reversed(subfields.columns): - df.insert( - position, f"{label}{separator}{subfield}", subfields[subfield] - ) - - return df diff --git a/bigframes/operations/time_ops.py b/bigframes/operations/time_ops.py deleted file mode 100644 index 3b6845053a7..00000000000 --- a/bigframes/operations/time_ops.py +++ /dev/null @@ -1,44 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import bigframes.operations.type as op_typing -from bigframes import dtypes -from bigframes.operations import base_ops - -HourOp = base_ops.create_unary_op( - name="hour", - type_signature=op_typing.TIMELIKE_ACCESSOR, -) -hour_op = HourOp() - -MinuteOp = base_ops.create_unary_op( - name="minute", - type_signature=op_typing.TIMELIKE_ACCESSOR, -) -minute_op = MinuteOp() - -SecondOp = base_ops.create_unary_op( - name="second", - type_signature=op_typing.TIMELIKE_ACCESSOR, -) -second_op = SecondOp() - -NormalizeOp = base_ops.create_unary_op( - name="normalize", - type_signature=op_typing.TypePreserving( - dtypes.is_time_like, - description="time-like", - ), -) -normalize_op = NormalizeOp() diff --git a/bigframes/operations/timedelta_ops.py b/bigframes/operations/timedelta_ops.py deleted file mode 100644 index 5e9a1189e44..00000000000 --- a/bigframes/operations/timedelta_ops.py +++ /dev/null @@ -1,145 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - - -import dataclasses -import typing - -from bigframes import dtypes -from bigframes.operations import base_ops - - -@dataclasses.dataclass(frozen=True) -class ToTimedeltaOp(base_ops.UnaryOp): - name: typing.ClassVar[str] = "to_timedelta" - unit: typing.Literal["us", "ms", "s", "m", "h", "d", "W"] - - def output_type(self, *input_types: dtypes.ExpressionType) -> dtypes.ExpressionType: - if input_types[0] in ( - dtypes.INT_DTYPE, - dtypes.FLOAT_DTYPE, - dtypes.TIMEDELTA_DTYPE, - ): - return dtypes.TIMEDELTA_DTYPE - raise TypeError("expected integer or float input") - - -@dataclasses.dataclass(frozen=True) -class TimedeltaFloorOp(base_ops.UnaryOp): - """Floors the numeric value to the nearest integer and use it to represent a timedelta. - - This operator is only meant to be used during expression tree rewrites. Do not use it anywhere else! - """ - - name: typing.ClassVar[str] = "timedelta_floor" - - def output_type(self, *input_types: dtypes.ExpressionType) -> dtypes.ExpressionType: - input_type = input_types[0] - if dtypes.is_numeric(input_type) or input_type == dtypes.TIMEDELTA_DTYPE: - return dtypes.TIMEDELTA_DTYPE - raise TypeError(f"unsupported type: {input_type}") - - -timedelta_floor_op = TimedeltaFloorOp() - - -@dataclasses.dataclass(frozen=True) -class TimestampAddOp(base_ops.BinaryOp): - name: typing.ClassVar[str] = "timestamp_add" - - def output_type(self, *input_types: dtypes.ExpressionType) -> dtypes.ExpressionType: - # timestamp + timedelta => timestamp - if ( - dtypes.is_datetime_like(input_types[0]) - and input_types[1] == dtypes.TIMEDELTA_DTYPE - ): - return input_types[0] - # timedelta + timestamp => timestamp - if input_types[0] == dtypes.TIMEDELTA_DTYPE and dtypes.is_datetime_like( - input_types[1] - ): - return input_types[1] - - raise TypeError( - f"unsupported types for timestamp_add. left: {input_types[0]} right: {input_types[1]}" - ) - - -timestamp_add_op = TimestampAddOp() - - -@dataclasses.dataclass(frozen=True) -class TimestampSubOp(base_ops.BinaryOp): - name: typing.ClassVar[str] = "timestamp_sub" - - def output_type(self, *input_types: dtypes.ExpressionType) -> dtypes.ExpressionType: - # timestamp - timedelta => timestamp - if ( - dtypes.is_datetime_like(input_types[0]) - and input_types[1] == dtypes.TIMEDELTA_DTYPE - ): - return input_types[0] - - raise TypeError( - f"unsupported types for timestamp_sub. left: {input_types[0]} right: {input_types[1]}" - ) - - -timestamp_sub_op = TimestampSubOp() - - -@dataclasses.dataclass(frozen=True) -class DateAddOp(base_ops.BinaryOp): - name: typing.ClassVar[str] = "date_add" - - def output_type(self, *input_types: dtypes.ExpressionType) -> dtypes.ExpressionType: - # date + timedelta => timestamp without timezone - if ( - input_types[0] == dtypes.DATE_DTYPE - and input_types[1] == dtypes.TIMEDELTA_DTYPE - ): - return dtypes.DATETIME_DTYPE - # timedelta + date => timestamp without timezone - if ( - input_types[0] == dtypes.TIMEDELTA_DTYPE - and input_types[1] == dtypes.DATE_DTYPE - ): - return dtypes.DATETIME_DTYPE - - raise TypeError( - f"unsupported types for date_add. left: {input_types[0]} right: {input_types[1]}" - ) - - -date_add_op = DateAddOp() - - -@dataclasses.dataclass(frozen=True) -class DateSubOp(base_ops.BinaryOp): - name: typing.ClassVar[str] = "date_sub" - - def output_type(self, *input_types: dtypes.ExpressionType) -> dtypes.ExpressionType: - # date - timedelta => timestamp without timezone - if ( - input_types[0] == dtypes.DATE_DTYPE - and input_types[1] == dtypes.TIMEDELTA_DTYPE - ): - return dtypes.DATETIME_DTYPE - - raise TypeError( - f"unsupported types for date_sub. left: {input_types[0]} right: {input_types[1]}" - ) - - -date_sub_op = DateSubOp() diff --git a/bigframes/operations/to_op.py b/bigframes/operations/to_op.py deleted file mode 100644 index 4f97a61e3c0..00000000000 --- a/bigframes/operations/to_op.py +++ /dev/null @@ -1,201 +0,0 @@ -# Copyright 2026 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -from __future__ import annotations - -import dataclasses -import inspect -import typing - -import bigframes.core.expression as ex -from bigframes._config import options -from bigframes.exceptions import TranspilationError -from bigframes.functions import Udf -from bigframes.functions.udf_def import BigqueryUdf, PythonUdf -from bigframes.operations import base_ops, remote_function_ops - -ArgKind = typing.Literal[ - "positional_only", - "positional_or_keyword", - "keyword_only", - "var_positional", - "var_keyword", -] - -_ARGKIND_MAP: dict[inspect._ParameterKind, ArgKind] = { - inspect.Parameter.POSITIONAL_ONLY: "positional_only", - inspect.Parameter.POSITIONAL_OR_KEYWORD: "positional_or_keyword", - inspect.Parameter.VAR_POSITIONAL: "var_positional", - inspect.Parameter.KEYWORD_ONLY: "keyword_only", - inspect.Parameter.VAR_KEYWORD: "var_keyword", -} - - -@dataclasses.dataclass(frozen=True) -class ArgumentSpec: - """ - Information about a single argument to a function - """ - - name: str - default_value: typing.Any - argkind: ArgKind - - @property - def is_positional(self) -> bool: - return self.argkind in ["positional_only", "positional_or_keyword"] - - @property - def is_keyword(self) -> bool: - return self.argkind in ["keyword_only", "positional_or_keyword"] - - @property - def is_var_positional(self) -> bool: - return self.argkind == "var_positional" - - @property - def is_var_keyword(self) -> bool: - return self.argkind == "var_keyword" - - @property - def is_varargs(self) -> bool: - return self.is_var_positional - - -@dataclasses.dataclass(frozen=True) -class CallableExpression: - """ - Encodes a calling convention and an expression to bind arguments to. - """ - - expr: ex.Expression - arg_specs: typing.Sequence[ArgumentSpec] - - @classmethod - def from_callable(cls, func: typing.Callable) -> CallableExpression: - sig = inspect.signature(func) - arg_specs = [] - for name, param in sig.parameters.items(): - arg_specs.append( - ArgumentSpec( - name=name, - default_value=param.default, - argkind=_ARGKIND_MAP[param.kind], - ) - ) - - from bigframes.core.bytecode import py_to_expression - - try: - expr = py_to_expression(func) - except Exception as ex: - raise TranspilationError(f"Failed to transpile function {func}") from ex - return cls(expr=expr, arg_specs=arg_specs) - - def apply(self, *args, **kwargs) -> ex.Expression: - """ - Apply the arguments to the expression. - - All args are expected to be column references, or scalars. - """ - return self.bind_partial(*args, _offset=0, **kwargs).expr - - def bind_partial( - self, - *args, - _offset: int = 0, - **kwargs, - ) -> CallableExpression: - """ - Bind a subset of arguments and return a new CallableExpression with the remaining unbound arguments. - """ - bindings: dict[typing.Hashable, ex.Expression] = {} - pos_idx = 0 - allowed_params = self.arg_specs[_offset:] - allowed_names = {spec.name for spec in allowed_params} - - # Validate unexpected keyword arguments - for key in kwargs: - if key not in allowed_names: - raise TypeError(f"got an unexpected keyword argument '{key}'") - - def to_expr(val): - if isinstance(val, ex.Expression): - return val - return ex.const(val) - - for spec in allowed_params: - if spec.is_varargs: - raise NotImplementedError( - "varargs in compiled python functions is not supported" - ) - - if pos_idx < len(args): - if spec.name in kwargs: - raise TypeError( - f"got multiple values for keyword argument '{spec.name}'" - ) - bindings[spec.name] = to_expr(args[pos_idx]) - pos_idx += 1 - elif spec.name in kwargs: - bindings[spec.name] = to_expr(kwargs[spec.name]) - elif spec.default_value is not inspect.Parameter.empty: - bindings[spec.name] = to_expr(spec.default_value) - else: - raise TypeError(f"missing required argument: '{spec.name}'") - - if pos_idx < len(args): - raise TypeError( - f"too many positional arguments: expected {len(allowed_params)}, got {len(args)}" - ) - - new_expr = self.expr.bind_variables(bindings, allow_partial_bindings=True) - remaining_specs = list(self.arg_specs[:_offset]) - return CallableExpression(expr=new_expr, arg_specs=remaining_specs) - - -def func_to_expr(op) -> CallableExpression: - """ - Convert various bigframes, python functions into bigframes CallableExpression. - """ - if isinstance(op, Udf): - bq_op: base_ops.NaryOp - if isinstance(op.udf_def, BigqueryUdf): - bq_op = remote_function_ops.RemoteFunctionOp(function_def=op.udf_def) - elif isinstance(op.udf_def, PythonUdf): - bq_op = remote_function_ops.PythonUdfOp(function_def=op.udf_def) - else: - raise TypeError(f"Unsupported UDF definition: {op.udf_def}") - - inputs_expr = tuple( - ex.free_var(arg.name) for arg in op.udf_def.signature.inputs - ) - expr = ex.OpExpression(bq_op, inputs_expr) - - arg_specs = [ - ArgumentSpec( - name=arg.name, - default_value=inspect.Parameter.empty, - # Udf specs don't have concept of positional only or keyword only yet, - # so default to positional_or_keyword. - argkind="positional_or_keyword", - ) - for arg in op.udf_def.signature.inputs - ] - return CallableExpression(expr=expr, arg_specs=arg_specs) - - elif options.experiments.enable_python_transpiler and callable(op): - return CallableExpression.from_callable(op) - - else: - raise TypeError(f"Unsupported function type: {op}") diff --git a/bigframes/operations/type.py b/bigframes/operations/type.py deleted file mode 100644 index 0ddf3a113fc..00000000000 --- a/bigframes/operations/type.py +++ /dev/null @@ -1,258 +0,0 @@ -# Copyright 2023 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 abc -import dataclasses -from typing import Callable - -import bigframes.dtypes -from bigframes.dtypes import ExpressionType - - -@dataclasses.dataclass -class TypeSignature(abc.ABC): - """ - Type Signature represent a mapping from input types to output type. - - Type signatures should throw a TypeError if the input types cannot be handled by the operation. - """ - - @property - @abc.abstractmethod - def as_method(self): - """Convert the signature into an object method. Convenience function for constructing ops that use the signature.""" - ... - - def __call__(self, *args, **kwargs): - return self.as_method(*args, **kwargs) - - -class UnaryTypeSignature(TypeSignature): - @abc.abstractmethod - def output_type(self, input_type: ExpressionType) -> ExpressionType: ... - - @property - def as_method(self): - def meth(_, *input_types: ExpressionType) -> ExpressionType: - assert len(input_types) == 1 - return self.output_type(input_types[0]) - - return meth - - -class BinaryTypeSignature(TypeSignature): - @abc.abstractmethod - def output_type( - self, left_type: ExpressionType, right_type: ExpressionType - ) -> ExpressionType: ... - - @property - def as_method(self): - def meth(_, *input_types: ExpressionType) -> ExpressionType: - assert len(input_types) == 2 - return self.output_type(input_types[0], input_types[1]) - - return meth - - -@dataclasses.dataclass -class TypePreserving(UnaryTypeSignature): - type_predicate: Callable[[ExpressionType], bool] - description: str - - def output_type(self, input_type: ExpressionType) -> ExpressionType: - if not self.type_predicate(input_type): - raise TypeError( - f"Type {input_type} is not supported. Type must be {self.description}" - ) - return input_type - - -@dataclasses.dataclass -class FixedOutputType(UnaryTypeSignature): - type_predicate: Callable[[ExpressionType], bool] - fixed_type: ExpressionType - description: str - - def output_type(self, input_type: ExpressionType) -> ExpressionType: - if (input_type is not None) and not self.type_predicate(input_type): - raise TypeError( - f"Type {input_type} is not supported. Type must be {self.description}" - ) - return self.fixed_type - - -@dataclasses.dataclass -class UnaryRealNumeric(UnaryTypeSignature): - """Type signature for real-valued functions like exp, log, sin, tan.""" - - def output_type(self, type: ExpressionType) -> ExpressionType: - if type is None: - return bigframes.dtypes.FLOAT_DTYPE - if not bigframes.dtypes.is_numeric(type): - raise TypeError(f"Type {type} is not numeric") - if type in (bigframes.dtypes.INT_DTYPE, bigframes.dtypes.BOOL_DTYPE): - # Real numeric ops produce floats on int input - return bigframes.dtypes.FLOAT_DTYPE - return type - - -@dataclasses.dataclass -class BinaryNumeric(BinaryTypeSignature): - """Type signature for numeric functions like multiply, modulo that can map ints to ints.""" - - def output_type( - self, left_type: ExpressionType, right_type: ExpressionType - ) -> ExpressionType: - if (left_type is not None) and not bigframes.dtypes.is_numeric(left_type): - raise TypeError(f"Type {left_type} is not numeric") - if (right_type is not None) and not bigframes.dtypes.is_numeric(right_type): - raise TypeError(f"Type {right_type} is not numeric") - return bigframes.dtypes.coerce_to_common(left_type, right_type) - - -@dataclasses.dataclass -@dataclasses.dataclass -class BinaryGeo(BinaryTypeSignature): - """Type signature for geo functions like difference that can map geo to geo.""" - - def output_type( - self, left_type: ExpressionType, right_type: ExpressionType - ) -> ExpressionType: - if (left_type is not None) and not bigframes.dtypes.is_geo_like(left_type): - raise TypeError(f"Type {left_type} is not geo") - if (right_type is not None) and not bigframes.dtypes.is_geo_like(right_type): - raise TypeError(f"Type {right_type} is not numeric") - return bigframes.dtypes.GEO_DTYPE - - -class BinaryNumericGeo(BinaryTypeSignature): - """Type signature for geo functions like from_xy that can map ints to ints.""" - - def output_type( - self, left_type: ExpressionType, right_type: ExpressionType - ) -> ExpressionType: - if (left_type is not None) and not bigframes.dtypes.is_numeric(left_type): - raise TypeError(f"Type {left_type} is not numeric") - if (right_type is not None) and not bigframes.dtypes.is_numeric(right_type): - raise TypeError(f"Type {right_type} is not numeric") - return bigframes.dtypes.GEO_DTYPE - - -@dataclasses.dataclass -class BinaryRealNumeric(BinaryTypeSignature): - """Type signature for real-valued functions like divide, arctan2, pow.""" - - def output_type( - self, left_type: ExpressionType, right_type: ExpressionType - ) -> ExpressionType: - if (left_type is not None) and not bigframes.dtypes.is_numeric(left_type): - raise TypeError(f"Type {left_type} is not numeric") - if (right_type is not None) and not bigframes.dtypes.is_numeric(right_type): - raise TypeError(f"Type {right_type} is not numeric") - lcd_type = bigframes.dtypes.coerce_to_common(left_type, right_type) - if lcd_type == bigframes.dtypes.INT_DTYPE: - # Real numeric ops produce floats on int input - return bigframes.dtypes.FLOAT_DTYPE - return lcd_type - - -@dataclasses.dataclass -class CoerceCommon(BinaryTypeSignature): - """Attempt to coerce inputs to a compatible type.""" - - def output_type( - self, left_type: ExpressionType, right_type: ExpressionType - ) -> ExpressionType: - return bigframes.dtypes.coerce_to_common(left_type, right_type) - - -@dataclasses.dataclass -class Comparison(BinaryTypeSignature): - """Type signature for comparison operators.""" - - def output_type( - self, left_type: ExpressionType, right_type: ExpressionType - ) -> ExpressionType: - if not bigframes.dtypes.can_compare(left_type, right_type): - raise TypeError(f"Types {left_type} and {right_type} are not comparable") - return bigframes.dtypes.BOOL_DTYPE - - -@dataclasses.dataclass -class Logical(BinaryTypeSignature): - """Type signature for logical operators like AND, OR and NOT.""" - - def output_type( - self, left_type: ExpressionType, right_type: ExpressionType - ) -> ExpressionType: - if left_type is None or right_type is None: - return bigframes.dtypes.BOOL_DTYPE - if not bigframes.dtypes.is_binary_like(left_type): - raise TypeError(f"Type {left_type} is not binary") - if not bigframes.dtypes.is_binary_like(right_type): - raise TypeError(f"Type {right_type} is not binary") - if left_type != right_type: - raise TypeError( - f"Bitwise operands {left_type} and {right_type} do not match" - ) - return left_type - - -@dataclasses.dataclass -class VectorMetric(BinaryTypeSignature): - """Type signature for logical operators like AND, OR and NOT.""" - - def output_type( - self, left_type: ExpressionType, right_type: ExpressionType - ) -> ExpressionType: - if not bigframes.dtypes.is_array_like(left_type): - raise TypeError(f"Type {left_type} is not array-like") - if not bigframes.dtypes.is_array_like(right_type): - raise TypeError(f"Type {right_type} is not array-like") - if left_type != right_type: - raise TypeError( - f"Vector op operands {left_type} and {right_type} do not match" - ) - return bigframes.dtypes.FLOAT_DTYPE - - -# Common type signatures -UNARY_NUMERIC = TypePreserving(bigframes.dtypes.is_numeric, description="numeric") -UNARY_NUMERIC_AND_TIMEDELTA = TypePreserving( - lambda x: bigframes.dtypes.is_numeric(x) or x is bigframes.dtypes.TIMEDELTA_DTYPE, - description="numeric_and_timedelta", -) -UNARY_REAL_NUMERIC = UnaryRealNumeric() -BINARY_NUMERIC = BinaryNumeric() -BINARY_REAL_NUMERIC = BinaryRealNumeric() -BLOB_TRANSFORM = TypePreserving(bigframes.dtypes.is_struct_like, description="blob") -COMPARISON = Comparison() -COERCE = CoerceCommon() -LOGICAL = Logical() -STRING_TRANSFORM = TypePreserving( - bigframes.dtypes.is_string_like, description="numeric" -) -STRING_PREDICATE = FixedOutputType( - bigframes.dtypes.is_string_like, - bigframes.dtypes.BOOL_DTYPE, - description="string-like", -) -DATELIKE_ACCESSOR = FixedOutputType( - bigframes.dtypes.is_date_like, bigframes.dtypes.INT_DTYPE, description="date-like" -) -TIMELIKE_ACCESSOR = FixedOutputType( - bigframes.dtypes.is_time_like, bigframes.dtypes.INT_DTYPE, description="time-like" -) -VECTOR_METRIC = VectorMetric() diff --git a/bigframes/pandas/__init__.py b/bigframes/pandas/__init__.py index b88816ab5ab..1c52b103fbe 100644 --- a/bigframes/pandas/__init__.py +++ b/bigframes/pandas/__init__.py @@ -12,385 +12,629 @@ # See the License for the specific language governing permissions and # limitations under the License. -""" -The primary entry point for the BigQuery DataFrames (BigFrames) pandas-compatible API. +"""BigQuery DataFrames provides a DataFrame API backed by the BigQuery engine.""" -**BigQuery DataFrames** provides a Pythonic DataFrame and machine learning (ML) API -powered by the BigQuery engine. The ``bigframes.pandas`` module implements a large -subset of the pandas API, allowing you to perform large-scale data analysis -using familiar pandas syntax while the computations are executed in the cloud. +from __future__ import annotations -**Key Features:** +from collections import namedtuple +import inspect +import typing +from typing import ( + Any, + Callable, + Dict, + IO, + Iterable, + List, + Literal, + MutableSequence, + Optional, + Sequence, + Tuple, + Union, +) -* **Petabyte-Scale Scalability:** Handle datasets that exceed local memory by - offloading computation to the BigQuery distributed engine. -* **Pandas Compatibility:** Use common pandas methods like - :func:`~bigframes.pandas.DataFrame.groupby`, - :func:`~bigframes.pandas.DataFrame.merge`, - :func:`~bigframes.pandas.DataFrame.pivot_table`, and more on BigQuery-backed - :class:`~bigframes.pandas.DataFrame` objects. -* **Direct BigQuery Integration:** Read from and write to BigQuery tables and - queries with :func:`bigframes.pandas.read_gbq` and - :func:`bigframes.pandas.DataFrame.to_gbq`. -* **User-defined Functions (UDFs):** Effortlessly deploy Python functions - functions using the :func:`bigframes.pandas.remote_function` and - :func:`bigframes.pandas.udf` decorators. -* **Data Ingestion:** Support for various formats including CSV, Parquet, JSON, - and Arrow via :func:`bigframes.pandas.read_csv`, - :func:`bigframes.pandas.read_parquet`, etc., which are automatically uploaded - to BigQuery for processing. Convert any pandas DataFrame into a BigQuery - DataFrame using :func:`bigframes.pandas.read_pandas`. +from google.cloud import bigquery +import numpy +import pandas +from pandas._typing import ( + CompressionOptions, + FilePath, + ReadPickleBuffer, + StorageOptions, +) -**Example usage:** +import bigframes._config as config +import bigframes.constants as constants +import bigframes.core.blocks +import bigframes.core.global_session as global_session +import bigframes.core.indexes +import bigframes.core.reshape +import bigframes.dataframe +import bigframes.operations as ops +import bigframes.series +import bigframes.session +import bigframes.session.clients +import third_party.bigframes_vendored.pandas.core.reshape.concat as vendored_pandas_concat +import third_party.bigframes_vendored.pandas.core.reshape.encoding as vendored_pandas_encoding +import third_party.bigframes_vendored.pandas.core.reshape.merge as vendored_pandas_merge +import third_party.bigframes_vendored.pandas.core.reshape.tile as vendored_pandas_tile - >>> import bigframes.pandas as bpd -Initialize session and set options. +# Include method definition so that the method appears in our docs for +# bigframes.pandas general functions. +@typing.overload +def concat( + objs: Iterable[bigframes.series.Series], + *, + axis: typing.Literal["index", 0] = ..., + join=..., + ignore_index=..., +) -> bigframes.series.Series: + ... - >>> bpd.options.bigquery.project = "your-project-id" # doctest: +SKIP -Load data from a BigQuery public dataset. +@typing.overload +def concat( + objs: Iterable[bigframes.dataframe.DataFrame], + *, + axis: typing.Literal["index", 0] = ..., + join=..., + ignore_index=..., +) -> bigframes.dataframe.DataFrame: + ... + - >>> df = bpd.read_gbq("bigquery-public-data.usa_names.usa_1910_2013") # doctest: +SKIP +@typing.overload +def concat( + objs: Iterable[Union[bigframes.dataframe.DataFrame, bigframes.series.Series]], + *, + axis: typing.Literal["columns", 1], + join=..., + ignore_index=..., +) -> bigframes.dataframe.DataFrame: + ... -Perform familiar pandas operations that execute in the cloud. - >>> top_names = ( - ... df.groupby("name") - ... .agg({"number": "sum"}) - ... .sort_values("number", ascending=False) - ... .head(10) - ... ) # doctest: +SKIP +@typing.overload +def concat( + objs: Iterable[Union[bigframes.dataframe.DataFrame, bigframes.series.Series]], + *, + axis=..., + join=..., + ignore_index=..., +) -> Union[bigframes.dataframe.DataFrame, bigframes.series.Series]: + ... -Bring the final, aggregated results back to local memory if needed. - >>> local_df = top_names.to_pandas() # doctest: +SKIP +def concat( + objs: Iterable[Union[bigframes.dataframe.DataFrame, bigframes.series.Series]], + *, + axis: typing.Union[str, int] = 0, + join: Literal["inner", "outer"] = "outer", + ignore_index: bool = False, +) -> Union[bigframes.dataframe.DataFrame, bigframes.series.Series]: + return bigframes.core.reshape.concat( + objs=objs, axis=axis, join=join, ignore_index=ignore_index + ) -BigQuery DataFrames is designed for data scientists and analysts who need the -power of BigQuery with the ease of use of pandas. It eliminates the "data -movement bottleneck" by keeping your data in BigQuery for processing. -""" -from __future__ import annotations +concat.__doc__ = vendored_pandas_concat.concat.__doc__ -import collections -import datetime -import inspect -import sys -import typing -from typing import Literal, Optional, Sequence, Union -import bigframes_vendored.pandas.core.tools.datetimes as vendored_pandas_datetimes -import pandas +def cut( + x: bigframes.series.Series, + bins: int, + *, + labels: Optional[bool] = None, +) -> bigframes.series.Series: + return bigframes.core.reshape.cut( + x, + bins, + labels=labels, + ) -import bigframes._config as config -import bigframes.core.global_session as global_session -import bigframes.core.indexes -import bigframes.dataframe -import bigframes.functions._utils as bff_utils -import bigframes.series -import bigframes.session -import bigframes.session._io.bigquery -import bigframes.version -from bigframes.core.col import col -from bigframes.core.logging import log_adapter -from bigframes.core.reshape.api import concat, crosstab, cut, get_dummies, merge, qcut -from bigframes.pandas import api -from bigframes.pandas.core.api import to_timedelta -from bigframes.pandas.io.api import ( - _from_glob_path, - _read_gbq_colab, - read_arrow, - read_avro, - read_csv, - read_gbq, - read_gbq_function, - read_gbq_model, - read_gbq_query, - read_gbq_table, - read_json, - read_orc, - read_pandas, - read_parquet, - read_pickle, -) -try: - import resource -except ImportError: - # resource is only available on Unix-like systems. - # https://docs.python.org/3/library/resource.html - resource = None # type: ignore +cut.__doc__ = vendored_pandas_tile.cut.__doc__ -def remote_function( - # Make sure that the input/output types, and dataset can be used - # positionally. This avoids the worst of the breaking change from 1.x to - # 2.x while still preventing possible mixups between consecutive str - # parameters. - input_types: Union[None, type, Sequence[type]] = None, - output_type: Optional[type] = None, - dataset: Optional[str] = None, +def get_dummies( + data: Union[DataFrame, Series], + prefix: Union[List, dict, str, None] = None, + prefix_sep: Union[List, dict, str, None] = "_", + dummy_na: bool = False, + columns: Optional[List] = None, + drop_first: bool = False, + dtype: Any = None, +) -> DataFrame: + # simplify input parameters into per-input-label lists + # also raise errors for invalid parameters + column_labels, prefixes, prefix_seps = _standardize_get_dummies_params( + data, prefix, prefix_sep, columns, dtype + ) + + # combine prefixes into per-column-id list + full_columns_prefixes, columns_ids = _determine_get_dummies_columns_from_labels( + data, column_labels, prefix is not None, prefixes, prefix_seps + ) + + # run queries to compute unique values + block = data._block + max_unique_value = ( + bigframes.core.blocks._BQ_MAX_COLUMNS + - len(block.value_columns) + - len(block.index_columns) + - 1 + ) // len(column_labels) + columns_values = [ + block._get_unique_values([col_id], max_unique_value) for col_id in columns_ids + ] + + # for each dummified column, add the content of the output columns via block operations + intermediate_col_ids = [] + for i in range(len(columns_values)): + level = columns_values[i].get_level_values(0).sort_values().dropna() + if drop_first: + level = level[1:] + column_label = full_columns_prefixes[i] + column_id = columns_ids[i] + block, new_intermediate_col_ids = _perform_get_dummies_block_operations( + block, level, column_label, column_id, dummy_na + ) + intermediate_col_ids.extend(new_intermediate_col_ids) + + # drop dummified columns (and the intermediate columns we added) + block = block.drop_columns(columns_ids + intermediate_col_ids) + return DataFrame(block) + + +get_dummies.__doc__ = vendored_pandas_encoding.get_dummies.__doc__ + + +def _standardize_get_dummies_params( + data: Union[DataFrame, Series], + prefix: Union[List, dict, str, None], + prefix_sep: Union[List, dict, str, None], + columns: Optional[List], + dtype: Any, +) -> Tuple[List, List[str], List[str]]: + block = data._block + + if isinstance(data, Series): + columns = [block.column_labels[0]] + if columns is not None and not pandas.api.types.is_list_like(columns): + raise TypeError("Input must be a list-like for parameter `columns`") + if dtype is not None and dtype not in [ + pandas.BooleanDtype, + bool, + "Boolean", + "boolean", + "bool", + ]: + raise NotImplementedError( + f"Only Boolean dtype is currently supported. {constants.FEEDBACK_LINK}" + ) + + if columns is None: + default_dummy_types = [pandas.StringDtype, "string[pyarrow]"] + columns = [] + columns_set = set() + for col_id in block.value_columns: + label = block.col_id_to_label[col_id] + if ( + label not in columns_set + and block.expr.get_column_type(col_id) in default_dummy_types + ): + columns.append(label) + columns_set.add(label) + + column_labels: List = typing.cast(List, columns) + + def parse_prefix_kwarg(kwarg, kwarg_name) -> Optional[List[str]]: + if kwarg is None: + return None + if isinstance(kwarg, str): + return [kwarg] * len(column_labels) + if isinstance(kwarg, dict): + return [kwarg[column] for column in column_labels] + kwarg = typing.cast(List, kwarg) + if pandas.api.types.is_list_like(kwarg) and len(kwarg) != len(column_labels): + raise ValueError( + f"Length of '{kwarg_name}' ({len(kwarg)}) did not match " + f"the length of the columns being encoded ({len(column_labels)})." + ) + if pandas.api.types.is_list_like(kwarg): + return list(map(str, kwarg)) + raise TypeError(f"{kwarg_name} kwarg must be a string, list, or dictionary") + + prefix_seps = parse_prefix_kwarg(prefix_sep or "_", "prefix_sep") + prefix_seps = typing.cast(List, prefix_seps) + prefixes = parse_prefix_kwarg(prefix, "prefix") + if prefixes is None: + prefixes = column_labels + prefixes = typing.cast(List, prefixes) + + return column_labels, prefixes, prefix_seps + + +def _determine_get_dummies_columns_from_labels( + data: Union[DataFrame, Series], + column_labels: List, + prefix_given: bool, + prefixes: List[str], + prefix_seps: List[str], +) -> Tuple[List[str], List[str]]: + block = data._block + + columns_ids = [] + columns_prefixes = [] + for i in range(len(column_labels)): + label = column_labels[i] + empty_prefix = label is None or (isinstance(data, Series) and not prefix_given) + full_prefix = "" if empty_prefix else prefixes[i] + prefix_seps[i] + + for col_id in block.label_to_col_id[label]: + columns_ids.append(col_id) + columns_prefixes.append(full_prefix) + + return columns_prefixes, columns_ids + + +def _perform_get_dummies_block_operations( + block: bigframes.core.blocks.Block, + level: pandas.Index, + column_label: str, + column_id: str, + dummy_na: bool, +) -> Tuple[bigframes.core.blocks.Block, List[str]]: + intermediate_col_ids = [] + for value in level: + new_column_label = f"{column_label}{value}" + if column_label == "": + new_column_label = value + new_block, new_id = block.apply_unary_op( + column_id, ops.BinopPartialLeft(ops.eq_op, value) + ) + intermediate_col_ids.append(new_id) + block, _ = new_block.apply_unary_op( + new_id, + ops.BinopPartialRight(ops.fillna_op, False), + result_label=new_column_label, + ) + if dummy_na: + # dummy column name for na depends on the dtype + na_string = str(pandas.Index([None], dtype=level.dtype)[0]) + new_column_label = f"{column_label}{na_string}" + block, _ = block.apply_unary_op( + column_id, ops.isnull_op, result_label=new_column_label + ) + return block, intermediate_col_ids + + +def qcut( + x: bigframes.series.Series, + q: int, *, - bigquery_connection: Optional[str] = None, - reuse: bool = True, - name: Optional[str] = None, - packages: Optional[Sequence[str]] = None, - cloud_function_service_account: str, - cloud_function_kms_key_name: Optional[str] = None, - cloud_function_docker_repository: Optional[str] = None, - max_batching_rows: Optional[int] = 1000, - cloud_function_timeout: Optional[int] = 600, - cloud_function_max_instances: Optional[int] = None, - cloud_function_vpc_connector: Optional[str] = None, - cloud_function_vpc_connector_egress_settings: Optional[ - Literal["all", "private-ranges-only", "unspecified"] + labels: Optional[bool] = None, + duplicates: typing.Literal["drop", "error"] = "error", +) -> bigframes.series.Series: + return bigframes.core.reshape.qcut(x, q, labels=labels, duplicates=duplicates) + + +qcut.__doc__ = vendored_pandas_tile.qcut.__doc__ + + +def merge( + left: DataFrame, + right: DataFrame, + how: Literal[ + "inner", + "left", + "outer", + "right", + ] = "inner", + on: Optional[str] = None, + *, + left_on: Optional[str] = None, + right_on: Optional[str] = None, + sort: bool = False, + suffixes: tuple[str, str] = ("_x", "_y"), +) -> DataFrame: + return bigframes.core.joins.merge( + left, + right, + how=how, + on=on, + left_on=left_on, + right_on=right_on, + sort=sort, + suffixes=suffixes, + ) + + +merge.__doc__ = vendored_pandas_merge.merge.__doc__ + + +def _set_default_session_location_if_possible(query): + # Set the location as per the query if this is the first query the user is + # running and: + # (1) Default session has not started yet, and + # (2) Location is not set yet, and + # (3) Use of regional endpoints is not set. + # If query is a table name, then it would be the location of the table. + # If query is a SQL with a table, then it would be table's location. + # If query is a SQL with no table, then it would be the BQ default location. + if ( + options.bigquery._session_started + or options.bigquery.location + or options.bigquery.use_regional_endpoints + ): + return + + clients_provider = bigframes.session.clients.ClientsProvider( + project=options.bigquery.project, + location=options.bigquery.location, + use_regional_endpoints=options.bigquery.use_regional_endpoints, + credentials=options.bigquery.credentials, + application_name=options.bigquery.application_name, + ) + + bqclient = clients_provider.bqclient + + if bigframes.session._is_query(query): + job = bqclient.query(query, bigquery.QueryJobConfig(dry_run=True)) + options.bigquery.location = job.location + else: + table = bqclient.get_table(query) + options.bigquery.location = table.location + + +# Note: the following methods are duplicated from Session. This duplication +# enables the following: +# +# 1. Static type checking knows the argument and return types, which is +# difficult to do with decorators. Aside: When we require Python 3.10, we +# can use Concatenate for generic typing in decorators. See: +# https://stackoverflow.com/a/68290080/101923 +# 2. docstrings get processed by static processing tools, such as VS Code's +# autocomplete. +# 3. Positional arguments function as expected. If we were to pull in the +# methods directly from Session, a Session object would need to be the first +# argument, even if we allow a default value. +# 4. Allows to set BigQuery options for the BigFrames session based on the +# method and its arguments. + + +def read_csv( + filepath_or_buffer: str | IO["bytes"], + *, + sep: Optional[str] = ",", + header: Optional[int] = 0, + names: Optional[ + Union[MutableSequence[Any], numpy.ndarray[Any, Any], Tuple[Any, ...], range] ] = None, - cloud_function_memory_mib: Optional[int] = None, - cloud_function_cpus: Optional[float] = None, - cloud_function_ingress_settings: Literal[ - "all", "internal-only", "internal-and-gclb" - ] = "internal-only", - cloud_build_service_account: Optional[str] = None, -): + index_col: Optional[ + Union[int, str, Sequence[Union[str, int]], Literal[False]] + ] = None, + usecols: Optional[ + Union[ + MutableSequence[str], + Tuple[str, ...], + Sequence[int], + pandas.Series, + pandas.Index, + numpy.ndarray[Any, Any], + Callable[[Any], bool], + ] + ] = None, + dtype: Optional[Dict] = None, + engine: Optional[ + Literal["c", "python", "pyarrow", "python-fwf", "bigquery"] + ] = None, + encoding: Optional[str] = None, + **kwargs, +) -> bigframes.dataframe.DataFrame: return global_session.with_default_session( - bigframes.session.Session.remote_function, - input_types=input_types, - output_type=output_type, - dataset=dataset, - bigquery_connection=bigquery_connection, - reuse=reuse, - name=name, - packages=packages, - cloud_function_service_account=cloud_function_service_account, - cloud_function_kms_key_name=cloud_function_kms_key_name, - cloud_function_docker_repository=cloud_function_docker_repository, - max_batching_rows=max_batching_rows, - cloud_function_timeout=cloud_function_timeout, - cloud_function_max_instances=cloud_function_max_instances, - cloud_function_vpc_connector=cloud_function_vpc_connector, - cloud_function_vpc_connector_egress_settings=cloud_function_vpc_connector_egress_settings, - cloud_function_memory_mib=cloud_function_memory_mib, - cloud_function_cpus=cloud_function_cpus, - cloud_function_ingress_settings=cloud_function_ingress_settings, - cloud_build_service_account=cloud_build_service_account, + bigframes.session.Session.read_csv, + filepath_or_buffer=filepath_or_buffer, + sep=sep, + header=header, + names=names, + index_col=index_col, + usecols=usecols, + dtype=dtype, + engine=engine, + encoding=encoding, + **kwargs, ) -remote_function.__doc__ = inspect.getdoc(bigframes.session.Session.remote_function) +read_csv.__doc__ = inspect.getdoc(bigframes.session.Session.read_csv) -def deploy_remote_function( - func, +def read_json( + path_or_buf: str | IO["bytes"], + *, + orient: Literal[ + "split", "records", "index", "columns", "values", "table" + ] = "columns", + dtype: Optional[Dict] = None, + encoding: Optional[str] = None, + lines: bool = False, + engine: Literal["ujson", "pyarrow", "bigquery"] = "ujson", **kwargs, -): +) -> bigframes.dataframe.DataFrame: return global_session.with_default_session( - bigframes.session.Session.deploy_remote_function, - func=func, + bigframes.session.Session.read_json, + path_or_buf=path_or_buf, + orient=orient, + dtype=dtype, + encoding=encoding, + lines=lines, + engine=engine, **kwargs, ) -deploy_remote_function.__doc__ = inspect.getdoc( - bigframes.session.Session.deploy_remote_function -) +read_json.__doc__ = inspect.getdoc(bigframes.session.Session.read_json) -def udf( +def read_gbq( + query_or_table: str, *, - input_types: Union[None, type, Sequence[type]] = None, - output_type: Optional[type] = None, - dataset: Optional[str] = None, - bigquery_connection: Optional[str] = None, - name: Optional[str] = None, - packages: Optional[Sequence[str]] = None, - max_batching_rows: Optional[int] = None, - container_cpu: Optional[float] = None, - container_memory: Optional[str] = None, -): + index_col: Iterable[str] | str = (), + col_order: Iterable[str] = (), + max_results: Optional[int] = None, +) -> bigframes.dataframe.DataFrame: + _set_default_session_location_if_possible(query_or_table) return global_session.with_default_session( - bigframes.session.Session.udf, - input_types=input_types, - output_type=output_type, - dataset=dataset, - bigquery_connection=bigquery_connection, - name=name, - packages=packages, - max_batching_rows=max_batching_rows, - container_cpu=container_cpu, - container_memory=container_memory, + bigframes.session.Session.read_gbq, + query_or_table, + index_col=index_col, + col_order=col_order, + max_results=max_results, ) -udf.__doc__ = inspect.getdoc(bigframes.session.Session.udf) +read_gbq.__doc__ = inspect.getdoc(bigframes.session.Session.read_gbq) -def deploy_udf( - func, - **kwargs, -): +def read_gbq_model(model_name: str): return global_session.with_default_session( - bigframes.session.Session.deploy_udf, - func=func, - **kwargs, + bigframes.session.Session.read_gbq_model, + model_name, ) -deploy_udf.__doc__ = inspect.getdoc(bigframes.session.Session.deploy_udf) +read_gbq_model.__doc__ = inspect.getdoc(bigframes.session.Session.read_gbq_model) -@typing.overload -def to_datetime( - arg: Union[ - vendored_pandas_datetimes.local_iterables, - bigframes.series.Series, - bigframes.dataframe.DataFrame, - ], +def read_gbq_query( + query: str, *, - utc: bool = False, - format: Optional[str] = None, - unit: Optional[str] = None, -) -> bigframes.series.Series: ... + index_col: Iterable[str] | str = (), + col_order: Iterable[str] = (), + max_results: Optional[int] = None, +) -> bigframes.dataframe.DataFrame: + _set_default_session_location_if_possible(query) + return global_session.with_default_session( + bigframes.session.Session.read_gbq_query, + query, + index_col=index_col, + col_order=col_order, + max_results=max_results, + ) -@typing.overload -def to_datetime( - arg: Union[int, float, str, datetime.datetime, datetime.date], - *, - utc: bool = False, - format: Optional[str] = None, - unit: Optional[str] = None, -) -> Union[pandas.Timestamp, datetime.datetime]: ... - - -def to_datetime( - arg: Union[ - Union[int, float, str, datetime.datetime, datetime.date], - vendored_pandas_datetimes.local_iterables, - bigframes.series.Series, - bigframes.dataframe.DataFrame, - ], +read_gbq_query.__doc__ = inspect.getdoc(bigframes.session.Session.read_gbq_query) + + +def read_gbq_table( + query: str, *, - utc: bool = False, - format: Optional[str] = None, - unit: Optional[str] = None, -) -> Union[pandas.Timestamp, datetime.datetime, bigframes.series.Series]: + index_col: Iterable[str] | str = (), + col_order: Iterable[str] = (), + max_results: Optional[int] = None, +) -> bigframes.dataframe.DataFrame: + _set_default_session_location_if_possible(query) return global_session.with_default_session( - bigframes.session.Session.to_datetime, - arg, - utc=utc, - format=format, - unit=unit, + bigframes.session.Session.read_gbq_table, + query, + index_col=index_col, + col_order=col_order, + max_results=max_results, ) -to_datetime.__doc__ = vendored_pandas_datetimes.to_datetime.__doc__ +read_gbq_table.__doc__ = inspect.getdoc(bigframes.session.Session.read_gbq_table) -def get_default_session_id() -> str: - """Gets the session id that is used whenever a custom session - has not been provided. +def read_pandas(pandas_dataframe: pandas.DataFrame) -> bigframes.dataframe.DataFrame: + return global_session.with_default_session( + bigframes.session.Session.read_pandas, + pandas_dataframe, + ) + - It is the session id of the default global session. It is prefixed to - the table id of all temporary tables created in the global session. +read_pandas.__doc__ = inspect.getdoc(bigframes.session.Session.read_pandas) - Returns: - str: - The default global session id, ex. 'sessiona1b2c' - """ - return get_global_session().session_id +def read_pickle( + filepath_or_buffer: FilePath | ReadPickleBuffer, + compression: CompressionOptions = "infer", + storage_options: StorageOptions = None, +): + return global_session.with_default_session( + bigframes.session.Session.read_pickle, + filepath_or_buffer=filepath_or_buffer, + compression=compression, + storage_options=storage_options, + ) -@log_adapter.method_logger -def clean_up_by_session_id( - session_id: str, - location: Optional[str] = None, - project: Optional[str] = None, -) -> None: - """Searches through BigQuery tables and routines and deletes the ones - created during the session with the given session id. The match is - determined by having the session id present in the resource name or - metadata. The cloud functions serving the cleaned up routines are also - cleaned up. - This could be useful if the session object has been lost. - Calling `session.close()` or `bigframes.pandas.close_session()` - is preferred in most cases. +read_pickle.__doc__ = inspect.getdoc(bigframes.session.Session.read_pickle) - Args: - session_id (str): - The session id to clean up. Can be found using - session.session_id or get_default_session_id(). - location (str, default None): - The location of the session to clean up. If given, used - together with project kwarg to determine the dataset - to search through for tables to clean up. +def read_parquet(path: str | IO["bytes"]) -> bigframes.dataframe.DataFrame: + return global_session.with_default_session( + bigframes.session.Session.read_parquet, + path, + ) - project (str, default None): - The project id associated with the session to clean up. - If given, used together with location kwarg to determine - the dataset to search through for tables to clean up. - Returns: - None - """ - session = get_global_session() +read_parquet.__doc__ = inspect.getdoc(bigframes.session.Session.read_parquet) - if (location is None) != (project is None): - raise ValueError( - "Only one of project or location was given. Must specify both or neither." - ) - elif location is None and project is None: - dataset = session._anonymous_dataset - else: - dataset = bigframes.session._io.bigquery.create_bq_dataset_reference( - session.bqclient, - location=location, - project=project, - publisher=session._publisher, - ) - bigframes.session._io.bigquery.delete_tables_matching_session_id( - session.bqclient, dataset, session_id +def remote_function( + input_types: List[type], + output_type: type, + dataset: Optional[str] = None, + bigquery_connection: Optional[str] = None, + reuse: bool = True, + name: Optional[str] = None, + packages: Optional[Sequence[str]] = None, +): + return global_session.with_default_session( + bigframes.session.Session.remote_function, + input_types=input_types, + output_type=output_type, + dataset=dataset, + bigquery_connection=bigquery_connection, + reuse=reuse, + name=name, + packages=packages, ) - bff_utils.clean_up_by_session_id( - session.bqclient, session.cloudfunctionsclient, dataset, session_id + +remote_function.__doc__ = inspect.getdoc(bigframes.session.Session.remote_function) + + +def read_gbq_function(function_name: str): + return global_session.with_default_session( + bigframes.session.Session.read_gbq_function, + function_name=function_name, ) +read_gbq_function.__doc__ = inspect.getdoc(bigframes.session.Session.read_gbq_function) + # pandas dtype attributes NA = pandas.NA -"""Alias for :class:`pandas.NA`.""" - BooleanDtype = pandas.BooleanDtype -"""Alias for :class:`pandas.BooleanDtype`.""" - Float64Dtype = pandas.Float64Dtype -"""Alias for :class:`pandas.Float64Dtype`.""" - Int64Dtype = pandas.Int64Dtype -"""Alias for :class:`pandas.Int64Dtype`.""" - StringDtype = pandas.StringDtype -"""Alias for :class:`pandas.StringDtype`.""" - ArrowDtype = pandas.ArrowDtype -"""Alias for :class:`pandas.ArrowDtype`.""" # Class aliases # TODO(swast): Make these real classes so we can refer to these in type # checking and docstrings. DataFrame = bigframes.dataframe.DataFrame Index = bigframes.core.indexes.Index -MultiIndex = bigframes.core.indexes.MultiIndex -DatetimeIndex = bigframes.core.indexes.DatetimeIndex Series = bigframes.series.Series -__version__ = bigframes.version.__version__ # Other public pandas attributes -NamedAgg = collections.namedtuple("NamedAgg", ["column", "aggfunc"]) +NamedAgg = namedtuple("NamedAgg", ["column", "aggfunc"]) options = config.options """Global :class:`~bigframes._config.Options` to configure BigQuery DataFrames.""" @@ -398,124 +642,35 @@ def clean_up_by_session_id( option_context = config.option_context """Global :class:`~bigframes._config.option_context` to configure BigQuery DataFrames.""" - # Session management APIs -def get_global_session(): - return global_session.get_global_session() - - -get_global_session.__doc__ = global_session.get_global_session.__doc__ - - -def close_session(): - return global_session.close_session() - - -close_session.__doc__ = global_session.close_session.__doc__ - - -def reset_session(): - return global_session.close_session() - - -reset_session.__doc__ = global_session.close_session.__doc__ - - -# SQL Compilation uses recursive algorithms on deep trees -# 10M tree depth should be sufficient to generate any sql that is under bigquery limit -# Note: This limit does not have the desired effect on Python 3.12 in -# which the applicable limit is now hard coded. See: -# https://github.com/python/cpython/issues/112282 -sys.setrecursionlimit(max(10000000, sys.getrecursionlimit())) - -if resource is not None: - soft_limit, hard_limit = resource.getrlimit(resource.RLIMIT_STACK) - if soft_limit < hard_limit or hard_limit == resource.RLIM_INFINITY: - try: - resource.setrlimit(resource.RLIMIT_STACK, (hard_limit, hard_limit)) - except Exception: - pass - -_functions = [ - _from_glob_path, - clean_up_by_session_id, - concat, - crosstab, - cut, - deploy_remote_function, - deploy_udf, - get_default_session_id, - get_dummies, - merge, - qcut, - read_arrow, - read_avro, - read_csv, - read_gbq, - _read_gbq_colab, - read_gbq_function, - read_gbq_model, - read_gbq_query, - read_gbq_table, - read_json, - read_orc, - read_pandas, - read_parquet, - read_pickle, - remote_function, - to_datetime, - to_timedelta, -] +get_global_session = global_session.get_global_session +close_session = global_session.close_session +reset_session = global_session.close_session + # Use __all__ to let type checkers know what is part of the public API. -# Note that static analysis checkers like pylance depend on these being string -# literals, not derived at runtime. -__all__ = [ - # Function names - "clean_up_by_session_id", +__all___ = [ + # Functions "concat", - "crosstab", - "col", - "cut", - "deploy_remote_function", - "deploy_udf", - "get_default_session_id", - "get_dummies", "merge", - "qcut", - "read_arrow", - "read_avro", "read_csv", "read_gbq", - "_read_gbq_colab", "read_gbq_function", "read_gbq_model", - "read_gbq_query", - "read_gbq_table", - "read_json", - "read_orc", "read_pandas", - "read_parquet", "read_pickle", "remote_function", - "to_datetime", - "to_timedelta", - # Other names - "api", # pandas dtype attributes "NA", "BooleanDtype", "Float64Dtype", "Int64Dtype", "StringDtype", - "ArrowDtype", + "ArrowDtype" # Class aliases "DataFrame", "Index", - "MultiIndex", - "DatetimeIndex", "Series", - "__version__", # Other public pandas attributes "NamedAgg", "options", @@ -524,11 +679,4 @@ def reset_session(): "get_global_session", "close_session", "reset_session", - "udf", ] - -_module = sys.modules[__name__] - -for _function in _functions: - _decorated_object = log_adapter.method_logger(_function, custom_base_name="pandas") - setattr(_module, _function.__name__, _decorated_object) diff --git a/bigframes/pandas/api/__init__.py b/bigframes/pandas/api/__init__.py deleted file mode 100644 index 6d181f92c12..00000000000 --- a/bigframes/pandas/api/__init__.py +++ /dev/null @@ -1,21 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -"""BigQuery DataFrames public pandas APIs.""" - -from bigframes.pandas.api import typing - -__all__ = [ - "typing", -] diff --git a/bigframes/pandas/api/typing.py b/bigframes/pandas/api/typing.py deleted file mode 100644 index 8d8d65eddec..00000000000 --- a/bigframes/pandas/api/typing.py +++ /dev/null @@ -1,37 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -"""BigQuery DataFrames public pandas types that aren't exposed in bigframes.pandas. - -Note: These objects aren't intended to be constructed directly. -""" - -from bigframes.core.groupby.dataframe_group_by import DataFrameGroupBy -from bigframes.core.groupby.series_group_by import SeriesGroupBy -from bigframes.core.window import Window -from bigframes.operations.datetimes import DatetimeMethods -from bigframes.operations.plotting import PlotAccessor -from bigframes.operations.strings import StringMethods -from bigframes.operations.structs import StructAccessor, StructFrameAccessor - -__all__ = [ - "DataFrameGroupBy", - "DatetimeMethods", - "PlotAccessor", - "SeriesGroupBy", - "StringMethods", - "StructAccessor", - "StructFrameAccessor", - "Window", -] diff --git a/bigframes/pandas/core/__init__.py b/bigframes/pandas/core/__init__.py deleted file mode 100644 index 0a2669d7a25..00000000000 --- a/bigframes/pandas/core/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. diff --git a/bigframes/pandas/core/api.py b/bigframes/pandas/core/api.py deleted file mode 100644 index 0f3161afcc2..00000000000 --- a/bigframes/pandas/core/api.py +++ /dev/null @@ -1,17 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from bigframes.pandas.core.tools.timedeltas import to_timedelta - -__all__ = ["to_timedelta"] diff --git a/bigframes/pandas/core/methods/__init__.py b/bigframes/pandas/core/methods/__init__.py deleted file mode 100644 index 0a2669d7a25..00000000000 --- a/bigframes/pandas/core/methods/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. diff --git a/bigframes/pandas/core/methods/describe.py b/bigframes/pandas/core/methods/describe.py deleted file mode 100644 index 34c116ba27d..00000000000 --- a/bigframes/pandas/core/methods/describe.py +++ /dev/null @@ -1,127 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from __future__ import annotations - -import typing - -import pandas as pd - -from bigframes import dataframe, dtypes, series -from bigframes.core import agg_expressions, blocks -from bigframes.operations import aggregations - -_DEFAULT_DTYPES = ( - dtypes.NUMERIC_BIGFRAMES_TYPES_RESTRICTIVE + dtypes.TEMPORAL_NUMERIC_BIGFRAMES_TYPES -) - - -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)}") - - block = input._block - - describe_block = _describe(block, columns=block.value_columns, include=include) - # we override default stack behavior, because we want very specific ordering - stack_cols = pd.Index( - [ - "count", - "nunique", - "top", - "freq", - "mean", - "std", - "min", - "25%", - "50%", - "75%", - "max", - ] - ).intersection(describe_block.column_labels.get_level_values(-1)) - if not stack_cols.empty: - describe_block = describe_block.stack(override_labels=stack_cols) - return dataframe.DataFrame(describe_block).droplevel(level=0) - return dataframe.DataFrame(describe_block) - - -def _describe( - block: blocks.Block, - columns: typing.Sequence[str], - include: None | typing.Literal["all"] = None, - *, - as_index: bool = True, - by_col_ids: typing.Sequence[str] = [], - dropna: bool = False, -) -> blocks.Block: - stats: list[agg_expressions.Aggregation] = [] - column_labels: list[typing.Hashable] = [] - - # include=None behaves like include='all' if no numeric columns present - if include is None: - if not any( - block.expr.get_column_type(col) in _DEFAULT_DTYPES for col in columns - ): - include = "all" - - for col_id in columns: - label = block.col_id_to_label[col_id] - dtype = block.expr.get_column_type(col_id) - if include != "all" and dtype not in _DEFAULT_DTYPES: - continue - agg_ops = _get_aggs_for_dtype(dtype) - stats.extend(op.as_expr(col_id) for op in agg_ops) - label_tuple = (label,) if block.column_labels.nlevels == 1 else label - column_labels.extend((*label_tuple, op.name) for op in agg_ops) # type: ignore - - agg_block = block.aggregate( - by_column_ids=by_col_ids, - aggregations=stats, - dropna=dropna, - column_labels=pd.Index(column_labels, name=(*block.column_labels.names, None)), - ) - return agg_block if as_index else agg_block.reset_index(drop=False) - - -def _get_aggs_for_dtype(dtype) -> list[aggregations.UnaryAggregateOp]: - if dtype in dtypes.NUMERIC_BIGFRAMES_TYPES_RESTRICTIVE: - return [ - aggregations.count_op, - aggregations.mean_op, - aggregations.std_op, - aggregations.min_op, - aggregations.ApproxQuartilesOp(1), - aggregations.ApproxQuartilesOp(2), - aggregations.ApproxQuartilesOp(3), - aggregations.max_op, - ] - elif dtype in dtypes.TEMPORAL_NUMERIC_BIGFRAMES_TYPES: - return [aggregations.count_op] - elif dtype in [ - dtypes.STRING_DTYPE, - dtypes.BOOL_DTYPE, - dtypes.BYTES_DTYPE, - dtypes.TIME_DTYPE, - ]: - return [aggregations.count_op, aggregations.nunique_op] - elif dtypes.is_json_like(dtype) or dtype == dtypes.OBJ_REF_DTYPE: - return [aggregations.count_op] - else: - return [] diff --git a/bigframes/pandas/core/tools/__init__.py b/bigframes/pandas/core/tools/__init__.py deleted file mode 100644 index 0a2669d7a25..00000000000 --- a/bigframes/pandas/core/tools/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. diff --git a/bigframes/pandas/core/tools/timedeltas.py b/bigframes/pandas/core/tools/timedeltas.py deleted file mode 100644 index 5d08bec5f7c..00000000000 --- a/bigframes/pandas/core/tools/timedeltas.py +++ /dev/null @@ -1,70 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import typing - -import pandas as pd -import pandas.api.types as pdtypes -from bigframes_vendored.pandas.core.tools import ( - timedeltas as vendored_pandas_timedeltas, -) - -from bigframes import operations as ops -from bigframes import series, session - - -def to_timedelta( - arg, - unit: typing.Optional[vendored_pandas_timedeltas.UnitChoices] = None, - *, - session: typing.Optional[session.Session] = None, -): - if isinstance(arg, series.Series): - canonical_unit = "us" if unit is None else _canonicalize_unit(unit) - return arg._apply_unary_op(ops.ToTimedeltaOp(canonical_unit)) - - if pdtypes.is_list_like(arg): - return to_timedelta(series.Series(arg, session=session), unit, session=session) - - return pd.to_timedelta(arg, unit) - - -to_timedelta.__doc__ = vendored_pandas_timedeltas.to_timedelta.__doc__ - - -def _canonicalize_unit( - unit: vendored_pandas_timedeltas.UnitChoices, -) -> typing.Literal["us", "ms", "s", "m", "h", "d", "W"]: - if unit in {"w", "W"}: - return "W" - - if unit in {"D", "d", "days", "day"}: - return "d" - - if unit in {"hours", "hour", "hr", "h"}: - return "h" - - if unit in {"m", "minute", "min", "minutes"}: - return "m" - - if unit in {"s", "seconds", "sec", "second"}: - return "s" - - if unit in {"ms", "milliseconds", "millisecond", "milli", "millis"}: - return "ms" - - if unit in {"us", "microseconds", "microsecond", "µs", "micro", "micros"}: - return "us" - - raise TypeError(f"Unrecognized unit: {unit}") diff --git a/bigframes/pandas/io/__init__.py b/bigframes/pandas/io/__init__.py deleted file mode 100644 index 6d5e14bcf4a..00000000000 --- a/bigframes/pandas/io/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -# 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. -# 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/bigframes/pandas/io/api.py b/bigframes/pandas/io/api.py deleted file mode 100644 index fa0f503a08b..00000000000 --- a/bigframes/pandas/io/api.py +++ /dev/null @@ -1,727 +0,0 @@ -# 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. -# 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 functools -import inspect -import os -import threading -import typing -import warnings -from typing import ( - IO, - Any, - Callable, - Dict, - Iterable, - Literal, - MutableSequence, - Optional, - Sequence, - Tuple, - Union, - overload, -) - -import bigframes_vendored.constants as constants -import bigframes_vendored.pandas.io.gbq as vendored_pandas_gbq -import numpy -import pandas -import pyarrow as pa -from google.cloud import bigquery -from pandas._typing import ( - CompressionOptions, - FilePath, - ReadPickleBuffer, - StorageOptions, -) - -import bigframes._config as config -import bigframes._importing -import bigframes.core.global_session as global_session -import bigframes.core.indexes -import bigframes.dataframe -import bigframes.enums -import bigframes.series -import bigframes.session -import bigframes.session._io.bigquery -import bigframes.session.clients -import bigframes.session.iceberg -import bigframes.session.metrics -from bigframes.core import bq_data -from bigframes.session import dry_runs - -# Note: the following methods are duplicated from Session. This duplication -# enables the following: -# -# 1. Static type checking knows the argument and return types, which is -# difficult to do with decorators. Aside: When we require Python 3.10, we -# can use Concatenate for generic typing in decorators. See: -# https://stackoverflow.com/a/68290080/101923 -# 2. docstrings get processed by static processing tools, such as VS Code's -# autocomplete. -# 3. Positional arguments function as expected. If we were to pull in the -# methods directly from Session, a Session object would need to be the first -# argument, even if we allow a default value. -# 4. Allows to set BigQuery options for the BigFrames session based on the -# method and its arguments. - - -def read_arrow(pa_table: pa.Table) -> bigframes.dataframe.DataFrame: - """Load a PyArrow Table to a BigQuery DataFrames DataFrame. - - Args: - pa_table (pyarrow.Table): - PyArrow table to load data from. - - Returns: - bigframes.dataframe.DataFrame: - A new DataFrame representing the data from the PyArrow table. - """ - session = global_session.get_global_session() - return session.read_arrow(pa_table=pa_table) - - -def read_avro( - path: str | IO["bytes"], - *, - engine: str = "auto", -) -> bigframes.dataframe.DataFrame: - return global_session.with_default_session( - bigframes.session.Session.read_avro, - path, - engine=engine, - ) - - -read_avro.__doc__ = inspect.getdoc(bigframes.session.Session.read_avro) - - -def read_csv( - filepath_or_buffer: str | IO["bytes"], - *, - sep: Optional[str] = ",", - header: Optional[int] = 0, - names: Optional[ - Union[MutableSequence[Any], numpy.ndarray[Any, Any], Tuple[Any, ...], range] - ] = None, - index_col: Optional[ - Union[ - int, - str, - Sequence[Union[str, int]], - bigframes.enums.DefaultIndexKind, - Literal[False], - ] - ] = None, - usecols: Optional[ - Union[ - MutableSequence[str], - Tuple[str, ...], - Sequence[int], - pandas.Series, - pandas.Index, - numpy.ndarray[Any, Any], - Callable[[Any], bool], - ] - ] = None, - dtype: Optional[Dict] = None, - engine: Optional[ - Literal["c", "python", "pyarrow", "python-fwf", "bigquery"] - ] = None, - encoding: Optional[str] = None, - write_engine: constants.WriteEngineType = "default", - **kwargs, -) -> bigframes.dataframe.DataFrame: - return global_session.with_default_session( - bigframes.session.Session.read_csv, - filepath_or_buffer=filepath_or_buffer, - sep=sep, - header=header, - names=names, - index_col=index_col, - usecols=usecols, - dtype=dtype, - engine=engine, - encoding=encoding, - write_engine=write_engine, - **kwargs, - ) - - -read_csv.__doc__ = inspect.getdoc(bigframes.session.Session.read_csv) - - -def read_json( - path_or_buf: str | IO["bytes"], - *, - orient: Literal[ - "split", "records", "index", "columns", "values", "table" - ] = "columns", - dtype: Optional[Dict] = None, - encoding: Optional[str] = None, - lines: bool = False, - engine: Literal["ujson", "pyarrow", "bigquery"] = "ujson", - write_engine: constants.WriteEngineType = "default", - **kwargs, -) -> bigframes.dataframe.DataFrame: - return global_session.with_default_session( - bigframes.session.Session.read_json, - path_or_buf=path_or_buf, - orient=orient, - dtype=dtype, - encoding=encoding, - lines=lines, - engine=engine, - write_engine=write_engine, - **kwargs, - ) - - -read_json.__doc__ = inspect.getdoc(bigframes.session.Session.read_json) - - -@overload -def read_gbq( # type: ignore[overload-overlap] - query_or_table: str, - *, - index_col: Iterable[str] | str | bigframes.enums.DefaultIndexKind = ..., - columns: Iterable[str] = ..., - configuration: Optional[Dict] = ..., - max_results: Optional[int] = ..., - filters: vendored_pandas_gbq.FiltersType = ..., - use_cache: Optional[bool] = ..., - col_order: Iterable[str] = ..., - dry_run: Literal[False] = ..., - allow_large_results: Optional[bool] = ..., -) -> bigframes.dataframe.DataFrame: ... - - -@overload -def read_gbq( - query_or_table: str, - *, - index_col: Iterable[str] | str | bigframes.enums.DefaultIndexKind = ..., - columns: Iterable[str] = ..., - configuration: Optional[Dict] = ..., - max_results: Optional[int] = ..., - filters: vendored_pandas_gbq.FiltersType = ..., - use_cache: Optional[bool] = ..., - col_order: Iterable[str] = ..., - dry_run: Literal[True] = ..., - allow_large_results: Optional[bool] = ..., -) -> pandas.Series: ... - - -def read_gbq( - query_or_table: str, - *, - index_col: Iterable[str] | str | bigframes.enums.DefaultIndexKind = (), - columns: Iterable[str] = (), - configuration: Optional[Dict] = None, - max_results: Optional[int] = None, - filters: vendored_pandas_gbq.FiltersType = (), - use_cache: Optional[bool] = None, - col_order: Iterable[str] = (), - dry_run: bool = False, - allow_large_results: Optional[bool] = None, -) -> bigframes.dataframe.DataFrame | pandas.Series: - _set_default_session_location_if_possible(query_or_table) - return global_session.with_default_session( - bigframes.session.Session.read_gbq, - query_or_table, - index_col=index_col, - columns=columns, - configuration=configuration, - max_results=max_results, - filters=filters, - use_cache=use_cache, - col_order=col_order, - dry_run=dry_run, - allow_large_results=allow_large_results, - ) - - -read_gbq.__doc__ = inspect.getdoc(bigframes.session.Session.read_gbq) - - -def _run_read_gbq_colab_sessionless_dry_run( - query: str, - *, - pyformat_args: Dict[str, Any], -) -> pandas.Series: - """Run a dry_run without a session.""" - - query_formatted = bigframes.core.pyformat.pyformat( - query, - pyformat_args=pyformat_args, - dry_run=True, - ) - bqclient, _ = _get_bqclient_and_project() - job = _dry_run(query_formatted, bqclient) - return dry_runs.get_query_stats_with_inferred_dtypes(job, (), ()) - - -def _try_read_gbq_colab_sessionless_dry_run( - query: str, - *, - pyformat_args: Dict[str, Any], -) -> Optional[pandas.Series]: - """Run a dry_run without a session, only if the session hasn't yet started.""" - - global _default_location_lock - - # Avoid creating a session just for dry run. We don't want to bind to a - # location too early. This is especially important if the query only refers - # to local data and not any BigQuery tables. - with _default_location_lock: - if not config.options.bigquery._session_started: - return _run_read_gbq_colab_sessionless_dry_run( - query, pyformat_args=pyformat_args - ) - - # Explicitly return None to indicate that we didn't run the dry run query. - return None - - -@overload -def _read_gbq_colab( # type: ignore[overload-overlap] - query_or_table: str, - *, - callback: Optional[Callable[[bigframes.core.events.EventEnvelope], None]] = None, - pyformat_args: Optional[Dict[str, Any]] = None, - dry_run: Literal[False] = False, -) -> bigframes.dataframe.DataFrame: ... - - -@overload -def _read_gbq_colab( - query_or_table: str, - *, - callback: Optional[Callable[[bigframes.core.events.EventEnvelope], None]] = None, - pyformat_args: Optional[Dict[str, Any]] = None, - dry_run: Literal[True], -) -> pandas.Series: ... - - -def _read_gbq_colab( - query_or_table: str, - *, - callback: Optional[Callable[[bigframes.core.events.EventEnvelope], None]] = None, - pyformat_args: Optional[Dict[str, Any]] = None, - dry_run: bool = False, -) -> bigframes.dataframe.DataFrame | pandas.Series: - """A Colab-specific version of read_gbq. - - Calls `_set_default_session_location_if_possible` and then delegates - to `bigframes.session.Session._read_gbq_colab`. - - Args: - query_or_table (str): - SQL query or table ID (table ID not yet supported). - callback (Optional[Callable[[bigframes.core.events.EventEnvelope], None]]): - Callback to receive query execution events. - pyformat_args (Optional[Dict[str, Any]]): - Parameters to format into the query string. - dry_run (bool): - If True, estimates the query results size without returning data. - The return will be a pandas Series with query metadata. - - Returns: - Union[bigframes.dataframe.DataFrame, pandas.Series]: - A BigQuery DataFrame if `dry_run` is False, otherwise a pandas Series. - """ - if pyformat_args is None: - pyformat_args = {} - - # Only try to set the global location if it's not a dry run. We don't want - # to bind to a location too early. This is especially important if the query - # only refers to local data and not any BigQuery tables. - if dry_run: - result = _try_read_gbq_colab_sessionless_dry_run( - query_or_table, pyformat_args=pyformat_args - ) - - if result is not None: - return result - - # If we made it this far, we must have a session that has already - # started. That means we can safely call the "real" _read_gbq_colab, - # which generates slightly nicer SQL. - else: - # Delay formatting the query with the special "session-less" logic. This - # avoids doing unnecessary work if the session already has a location or has - # already started. - create_query = functools.partial( - bigframes.core.pyformat.pyformat, - query_or_table, - pyformat_args=pyformat_args, - dry_run=True, - ) - _set_default_session_location_if_possible_deferred_query(create_query) - if not config.options.bigquery._session_started: - # Don't warning about Polars in SQL cell. - # Related to b/437090788. - try: - bigframes._importing.import_polars() - warnings.simplefilter("ignore", bigframes.exceptions.PreviewWarning) - config.options.bigquery.enable_polars_execution = True - except ImportError: - pass # don't fail if polars isn't available - - return global_session.with_default_session( - bigframes.session.Session._read_gbq_colab, - query_or_table, - callback=callback, - pyformat_args=pyformat_args, - dry_run=dry_run, - ) - - -def read_gbq_model(model_name: str): - return global_session.with_default_session( - bigframes.session.Session.read_gbq_model, - model_name, - ) - - -read_gbq_model.__doc__ = inspect.getdoc(bigframes.session.Session.read_gbq_model) - - -@overload -def read_gbq_query( # type: ignore[overload-overlap] - query: str, - *, - index_col: Iterable[str] | str | bigframes.enums.DefaultIndexKind = ..., - columns: Iterable[str] = ..., - configuration: Optional[Dict] = ..., - max_results: Optional[int] = ..., - use_cache: Optional[bool] = ..., - col_order: Iterable[str] = ..., - filters: vendored_pandas_gbq.FiltersType = ..., - dry_run: Literal[False] = ..., - allow_large_results: Optional[bool] = ..., -) -> bigframes.dataframe.DataFrame: ... - - -@overload -def read_gbq_query( - query: str, - *, - index_col: Iterable[str] | str | bigframes.enums.DefaultIndexKind = ..., - columns: Iterable[str] = ..., - configuration: Optional[Dict] = ..., - max_results: Optional[int] = ..., - use_cache: Optional[bool] = ..., - col_order: Iterable[str] = ..., - filters: vendored_pandas_gbq.FiltersType = ..., - dry_run: Literal[True] = ..., - allow_large_results: Optional[bool] = ..., -) -> pandas.Series: ... - - -def read_gbq_query( - query: str, - *, - index_col: Iterable[str] | str | bigframes.enums.DefaultIndexKind = (), - columns: Iterable[str] = (), - configuration: Optional[Dict] = None, - max_results: Optional[int] = None, - use_cache: Optional[bool] = None, - col_order: Iterable[str] = (), - filters: vendored_pandas_gbq.FiltersType = (), - dry_run: bool = False, - allow_large_results: Optional[bool] = None, -) -> bigframes.dataframe.DataFrame | pandas.Series: - _set_default_session_location_if_possible(query) - return global_session.with_default_session( - bigframes.session.Session.read_gbq_query, - query, - index_col=index_col, - columns=columns, - configuration=configuration, - max_results=max_results, - use_cache=use_cache, - col_order=col_order, - filters=filters, - dry_run=dry_run, - allow_large_results=allow_large_results, - ) - - -read_gbq_query.__doc__ = inspect.getdoc(bigframes.session.Session.read_gbq_query) - - -@overload -def read_gbq_table( # type: ignore[overload-overlap] - query: str, - *, - index_col: Iterable[str] | str | bigframes.enums.DefaultIndexKind = ..., - columns: Iterable[str] = ..., - max_results: Optional[int] = ..., - filters: vendored_pandas_gbq.FiltersType = ..., - use_cache: bool = ..., - col_order: Iterable[str] = ..., - dry_run: Literal[False] = ..., -) -> bigframes.dataframe.DataFrame: ... - - -@overload -def read_gbq_table( - query: str, - *, - index_col: Iterable[str] | str | bigframes.enums.DefaultIndexKind = ..., - columns: Iterable[str] = ..., - max_results: Optional[int] = ..., - filters: vendored_pandas_gbq.FiltersType = ..., - use_cache: bool = ..., - col_order: Iterable[str] = ..., - dry_run: Literal[True] = ..., -) -> pandas.Series: ... - - -def read_gbq_table( - query: str, - *, - index_col: Iterable[str] | str | bigframes.enums.DefaultIndexKind = (), - columns: Iterable[str] = (), - max_results: Optional[int] = None, - filters: vendored_pandas_gbq.FiltersType = (), - use_cache: bool = True, - col_order: Iterable[str] = (), - dry_run: bool = False, -) -> bigframes.dataframe.DataFrame | pandas.Series: - _set_default_session_location_if_possible(query) - return global_session.with_default_session( - bigframes.session.Session.read_gbq_table, - query, - index_col=index_col, - columns=columns, - max_results=max_results, - filters=filters, - use_cache=use_cache, - col_order=col_order, - dry_run=dry_run, - ) - - -read_gbq_table.__doc__ = inspect.getdoc(bigframes.session.Session.read_gbq_table) - - -def read_orc( - path: str | IO["bytes"], - *, - engine: str = "auto", - write_engine: constants.WriteEngineType = "default", -) -> bigframes.dataframe.DataFrame: - return global_session.with_default_session( - bigframes.session.Session.read_orc, - path, - engine=engine, - write_engine=write_engine, - ) - - -read_orc.__doc__ = inspect.getdoc(bigframes.session.Session.read_orc) - - -@typing.overload -def read_pandas( - pandas_dataframe: pandas.DataFrame, - *, - write_engine: constants.WriteEngineType = "default", -) -> bigframes.dataframe.DataFrame: ... - - -@typing.overload -def read_pandas( - pandas_dataframe: pandas.Series, - *, - write_engine: constants.WriteEngineType = "default", -) -> bigframes.series.Series: ... - - -@typing.overload -def read_pandas( - pandas_dataframe: pandas.Index, - *, - write_engine: constants.WriteEngineType = "default", -) -> bigframes.core.indexes.Index: ... - - -def read_pandas( - pandas_dataframe: Union[pandas.DataFrame, pandas.Series, pandas.Index], - *, - write_engine: constants.WriteEngineType = "default", -): - return global_session.with_default_session( - bigframes.session.Session.read_pandas, - pandas_dataframe, - write_engine=write_engine, - ) - - -read_pandas.__doc__ = inspect.getdoc(bigframes.session.Session.read_pandas) - - -def read_pickle( - filepath_or_buffer: FilePath | ReadPickleBuffer, - compression: CompressionOptions = "infer", - storage_options: StorageOptions = None, - *, - write_engine: constants.WriteEngineType = "default", -): - return global_session.with_default_session( - bigframes.session.Session.read_pickle, - filepath_or_buffer=filepath_or_buffer, - compression=compression, - storage_options=storage_options, - write_engine=write_engine, - ) - - -read_pickle.__doc__ = inspect.getdoc(bigframes.session.Session.read_pickle) - - -def read_parquet( - path: str | IO["bytes"], - *, - engine: str = "auto", - write_engine: constants.WriteEngineType = "default", -) -> bigframes.dataframe.DataFrame: - return global_session.with_default_session( - bigframes.session.Session.read_parquet, - path, - engine=engine, - write_engine=write_engine, - ) - - -read_parquet.__doc__ = inspect.getdoc(bigframes.session.Session.read_parquet) - - -def read_gbq_function( - function_name: str, - is_row_processor: bool = False, -): - return global_session.with_default_session( - bigframes.session.Session.read_gbq_function, - function_name=function_name, - is_row_processor=is_row_processor, - ) - - -read_gbq_function.__doc__ = inspect.getdoc(bigframes.session.Session.read_gbq_function) - - -def _from_glob_path( - path: str, *, connection: Optional[str] = None, name: Optional[str] = None -) -> bigframes.dataframe.DataFrame: - return global_session.with_default_session( - bigframes.session.Session._from_glob_path, - path=path, - connection=connection, - name=name, - ) - - -_from_glob_path.__doc__ = inspect.getdoc(bigframes.session.Session._from_glob_path) - -_default_location_lock = threading.Lock() - - -def _get_bqclient_and_project() -> Tuple[bigquery.Client, str]: - # Address circular imports in doctest due to bigframes/session/__init__.py - # containing a lot of logic and samples. - import bigframes._config.auth - from bigframes.session import clients - - credentials, project = bigframes._config.auth.resolve_credentials_and_project( - config.options.bigquery - ) - - clients_provider = clients.ClientsProvider( - project=project, - location=config.options.bigquery.location, - use_regional_endpoints=config.options.bigquery.use_regional_endpoints, - credentials=credentials, - application_name=config.options.bigquery.application_name, - bq_kms_key_name=config.options.bigquery.kms_key_name, - client_endpoints_override=config.options.bigquery.client_endpoints_override, - requests_transport_adapters=config.options.bigquery.requests_transport_adapters, - ) - return clients_provider.bqclient, project - - -def _dry_run(query, bqclient) -> bigquery.QueryJob: - # Address circular imports in doctest due to bigframes/session/__init__.py - # containing a lot of logic and samples. - from bigframes.session import metrics as bf_metrics - - job = bqclient.query(query, bigquery.QueryJobConfig(dry_run=True)) - - # Fix for b/435183833. Log metrics even if a Session isn't available. - if bf_metrics.LOGGING_NAME_ENV_VAR in os.environ: - metrics = bf_metrics.ExecutionMetrics() - metrics.count_job_stats(job) - return job - - -def _set_default_session_location_if_possible(query): - _set_default_session_location_if_possible_deferred_query(lambda: query) - - -def _set_default_session_location_if_possible_deferred_query(create_query): - # Address circular imports in doctest due to bigframes/session/__init__.py - # containing a lot of logic and samples. - from bigframes.session._io import bigquery - - # Set the location as per the query if this is the first query the user is - # running and: - # (1) Default session has not started yet, and - # (2) Location is not set yet, and - # (3) Use of regional endpoints is not set. - # If query is a table name, then it would be the location of the table. - # If query is a SQL with a table, then it would be table's location. - # If query is a SQL with no table, then it would be the BQ default location. - global _default_location_lock - - with _default_location_lock: - if ( - config.options.bigquery._session_started - or config.options.bigquery.location - or config.options.bigquery.use_regional_endpoints - ): - return - - query = create_query() - bqclient, default_project = _get_bqclient_and_project() - - if bigquery.is_query(query): - # Intentionally run outside of the session so that we can detect the - # location before creating the session. Since it's a dry_run, labels - # aren't necessary. - job = _dry_run(query, bqclient) - config.options.bigquery.location = job.location - elif bq_data.is_irc_table(query): - irc_table = bigframes.session.iceberg.get_table( - default_project, query, bqclient._credentials - ) - config.options.bigquery.location = bq_data.get_default_bq_region( - irc_table.metadata.location - ) - else: - table = bqclient.get_table(query) - config.options.bigquery.location = table.location diff --git a/bigframes/remote_function.py b/bigframes/remote_function.py new file mode 100644 index 00000000000..a39cd033f69 --- /dev/null +++ b/bigframes/remote_function.py @@ -0,0 +1,903 @@ +# Copyright 2023 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 functools +import hashlib +import inspect +import logging +import os +import random +import shutil +import string +import subprocess +import sys +import tempfile +import textwrap +from typing import List, NamedTuple, Optional, Sequence, TYPE_CHECKING + +import requests + +if TYPE_CHECKING: + from bigframes.session import Session + +import cloudpickle +import google.api_core.exceptions +import google.api_core.retry +from google.cloud import ( + bigquery, + bigquery_connection_v1, + functions_v2, + resourcemanager_v3, +) +import google.iam.v1 +from ibis.backends.bigquery.compiler import compiles +from ibis.backends.bigquery.datatypes import BigQueryType +from ibis.expr.datatypes.core import DataType as IbisDataType +from ibis.expr.datatypes.core import dtype as python_type_to_bigquery_type +import ibis.expr.operations as ops +import ibis.expr.rules as rlz + +from bigframes import clients +import bigframes.constants as constants + +logger = logging.getLogger(__name__) + +# Protocol version 4 is available in python version 3.4 and above +# https://docs.python.org/3/library/pickle.html#data-stream-format +_pickle_protocol_version = 4 + +# Input and output types supported by BigQuery DataFrames remote functions. +# TODO(shobs): Extend the support to all types supported by BQ remote functions +# https://cloud.google.com/bigquery/docs/remote-functions#limitations +SUPPORTED_IO_PYTHON_TYPES = {bool, float, int, str} +SUPPORTED_IO_BIGQUERY_TYPEKINDS = { + "BOOLEAN", + "BOOL", + "FLOAT", + "FLOAT64", + "INT64", + "INTEGER", + "STRING", +} + + +def get_remote_function_locations(bq_location): + """Get BQ location and cloud functions region given a BQ client.""" + # TODO(shobs, b/274647164): Find the best way to determine default location. + # For now let's assume that if no BQ location is set in the client then it + # defaults to US multi region + bq_location = bq_location.lower() if bq_location else "us" + + # Cloud function should be in the same region as the bigquery remote function + cloud_function_region = bq_location + + # BigQuery has multi region but cloud functions does not. + # Any region in the multi region that supports cloud functions should work + # https://cloud.google.com/functions/docs/locations + if bq_location == "us": + cloud_function_region = "us-central1" + elif bq_location == "eu": + cloud_function_region = "europe-west1" + + return bq_location, cloud_function_region + + +def _get_hash(def_, package_requirements=None): + "Get hash (32 digits alphanumeric) of a function." + def_repr = cloudpickle.dumps(def_, protocol=_pickle_protocol_version) + if package_requirements: + for p in sorted(package_requirements): + def_repr += p.encode() + return hashlib.md5(def_repr).hexdigest() + + +def _run_system_command(command): + program = subprocess.Popen( + [command], stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True + ) + stdout, stderr = program.communicate() + exit_code = program.wait() + if exit_code: + raise RuntimeError( + f"Command: {command}\nOutput: {stdout.decode()}\nError: {stderr.decode()}" + f"{constants.FEEDBACK_LINK}" + ) + + +def routine_ref_to_string_for_query(routine_ref: bigquery.RoutineReference) -> str: + return f"`{routine_ref.project}.{routine_ref.dataset_id}`.{routine_ref.routine_id}" + + +class IbisSignature(NamedTuple): + parameter_names: List[str] + input_types: List[Optional[IbisDataType]] + output_type: IbisDataType + + +def get_cloud_function_name(def_, uniq_suffix=None, package_requirements=None): + "Get a name for the cloud function for the given user defined function." + cf_name = _get_hash(def_, package_requirements) + cf_name = f"bigframes-{cf_name}" # for identification + if uniq_suffix: + cf_name = f"{cf_name}-{uniq_suffix}" + return cf_name + + +def get_remote_function_name(def_, uniq_suffix=None, package_requirements=None): + "Get a name for the BQ remote function for the given user defined function." + bq_rf_name = _get_hash(def_, package_requirements) + bq_rf_name = f"bigframes_{bq_rf_name}" # for identification + if uniq_suffix: + bq_rf_name = f"{bq_rf_name}_{uniq_suffix}" + return bq_rf_name + + +class RemoteFunctionClient: + # Wait time (in seconds) for an IAM binding to take effect after creation + _iam_wait_seconds = 120 + + def __init__( + self, + gcp_project_id, + cloud_function_region, + cloud_functions_client, + bq_location, + bq_dataset, + bq_client, + bq_connection_client, + bq_connection_id, + cloud_resource_manager_client, + ): + self._gcp_project_id = gcp_project_id + self._cloud_function_region = cloud_function_region + self._cloud_functions_client = cloud_functions_client + self._bq_location = bq_location + self._bq_dataset = bq_dataset + self._bq_client = bq_client + self._bq_connection_id = bq_connection_id + self._bq_connection_manager = clients.BqConnectionManager( + bq_connection_client, cloud_resource_manager_client + ) + + def create_bq_remote_function( + self, input_args, input_types, output_type, endpoint, bq_function_name + ): + """Create a BigQuery remote function given the artifacts of a user defined + function and the http endpoint of a corresponding cloud function.""" + self._bq_connection_manager.create_bq_connection( + self._gcp_project_id, + self._bq_location, + self._bq_connection_id, + "run.invoker", + ) + + # Create BQ function + # https://cloud.google.com/bigquery/docs/reference/standard-sql/remote-functions#create_a_remote_function_2 + bq_function_args = [] + bq_function_return_type = BigQueryType.from_ibis(output_type) + # We are expecting the input type annotations to be 1:1 with the input args + for idx, name in enumerate(input_args): + bq_function_args.append( + f"{name} {BigQueryType.from_ibis(input_types[idx])}" + ) + create_function_ddl = f""" + CREATE OR REPLACE FUNCTION `{self._gcp_project_id}.{self._bq_dataset}`.{bq_function_name}({','.join(bq_function_args)}) + RETURNS {bq_function_return_type} + REMOTE WITH CONNECTION `{self._gcp_project_id}.{self._bq_location}.{self._bq_connection_id}` + OPTIONS ( + endpoint = "{endpoint}", + max_batching_rows = 1000 + )""" + + logger.info(f"Creating BQ remote function: {create_function_ddl}") + + # Make sure the dataset exists + dataset = bigquery.Dataset( + bigquery.DatasetReference.from_string( + self._bq_dataset, default_project=self._gcp_project_id + ) + ) + dataset.location = self._bq_location + self._bq_client.create_dataset(dataset, exists_ok=True) + + # TODO: Use session._start_query() so we get progress bar + query_job = self._bq_client.query(create_function_ddl) # Make an API request. + query_job.result() # Wait for the job to complete. + + logger.info(f"Created remote function {query_job.ddl_target_routine}") + + def get_cloud_function_fully_qualified_parent(self): + "Get the fully qualilfied parent for a cloud function." + return self._cloud_functions_client.common_location_path( + self._gcp_project_id, self._cloud_function_region + ) + + def get_cloud_function_fully_qualified_name(self, name): + "Get the fully qualilfied name for a cloud function." + return self._cloud_functions_client.function_path( + self._gcp_project_id, self._cloud_function_region, name + ) + + def get_cloud_function_endpoint(self, name): + """Get the http endpoint of a cloud function if it exists.""" + fully_qualified_name = self.get_cloud_function_fully_qualified_name(name) + try: + response = self._cloud_functions_client.get_function( + name=fully_qualified_name + ) + return response.service_config.uri + except google.api_core.exceptions.NotFound: + pass + return None + + def generate_udf_code(self, def_, dir): + """Generate serialized bytecode using cloudpickle given a udf.""" + udf_code_file_name = "udf.py" + udf_bytecode_file_name = "udf.cloudpickle" + + # original code, only for debugging purpose + udf_code = textwrap.dedent(inspect.getsource(def_)) + udf_code_file_path = os.path.join(dir, udf_code_file_name) + with open(udf_code_file_path, "w") as f: + f.write(udf_code) + + # serialized bytecode + udf_bytecode_file_path = os.path.join(dir, udf_bytecode_file_name) + with open(udf_bytecode_file_path, "wb") as f: + cloudpickle.dump(def_, f, protocol=_pickle_protocol_version) + + return udf_code_file_name, udf_bytecode_file_name + + def generate_cloud_function_main_code(self, def_, dir): + """Get main.py code for the cloud function for the given user defined function.""" + + # Pickle the udf with all its dependencies + udf_code_file, udf_bytecode_file = self.generate_udf_code(def_, dir) + handler_func_name = "udf_http" + + # We want to build a cloud function that works for BQ remote functions, + # where we receive `calls` in json which is a batch of rows from BQ SQL. + # The number and the order of values in each row is expected to exactly + # match to the number and order of arguments in the udf , e.g. if the udf is + # def foo(x: int, y: str): + # ... + # then the http request body could look like + # { + # ... + # "calls" : [ + # [123, "hello"], + # [456, "world"] + # ] + # ... + # } + # https://cloud.google.com/bigquery/docs/reference/standard-sql/remote-functions#input_format + code_template = textwrap.dedent( + """\ + import cloudpickle + import json + + # original udf code is in {udf_code_file} + # serialized udf code is in {udf_bytecode_file} + with open("{udf_bytecode_file}", "rb") as f: + udf = cloudpickle.load(f) + + def {handler_func_name}(request): + request_json = request.get_json(silent=True) + calls = request_json["calls"] + replies = [] + for call in calls: + reply = udf(*call) + replies.append(reply) + return_json = json.dumps({{"replies" : replies}}) + return return_json + """ + ) + + code = code_template.format( + udf_code_file=udf_code_file, + udf_bytecode_file=udf_bytecode_file, + handler_func_name=handler_func_name, + ) + + main_py = os.path.join(dir, "main.py") + with open(main_py, "w") as f: + f.write(code) + logger.debug(f"Wrote {os.path.abspath(main_py)}:\n{open(main_py).read()}") + + return handler_func_name + + def generate_cloud_function_code(self, def_, dir, package_requirements=None): + """Generate the cloud function code for a given user defined function.""" + + # requirements.txt + requirements = ["cloudpickle >= 2.1.0"] + if package_requirements: + requirements.extend(package_requirements) + requirements = sorted(requirements) + requirements_txt = os.path.join(dir, "requirements.txt") + with open(requirements_txt, "w") as f: + f.write("\n".join(requirements)) + + # main.py + entry_point = self.generate_cloud_function_main_code(def_, dir) + return entry_point + + def create_cloud_function(self, def_, cf_name, package_requirements=None): + """Create a cloud function from the given user defined function.""" + + # Build and deploy folder structure containing cloud function + with tempfile.TemporaryDirectory() as dir: + entry_point = self.generate_cloud_function_code( + def_, dir, package_requirements + ) + archive_path = shutil.make_archive(dir, "zip", dir) + + # We are creating cloud function source code from the currently running + # python version. Use the same version to deploy. This is necessary + # because cloudpickle serialization done in one python version and + # deserialization done in another python version doesn't work. + # TODO(shobs): Figure out how to achieve version compatibility, specially + # when pickle (internally used by cloudpickle) guarantees that: + # https://docs.python.org/3/library/pickle.html#:~:text=The%20pickle%20serialization%20format%20is,unique%20breaking%20change%20language%20boundary. + python_version = "python{}{}".format( + sys.version_info.major, sys.version_info.minor + ) + + # Determine an upload URL for user code + upload_url_request = functions_v2.GenerateUploadUrlRequest() + upload_url_request.parent = self.get_cloud_function_fully_qualified_parent() + upload_url_response = self._cloud_functions_client.generate_upload_url( + request=upload_url_request + ) + + # Upload the code to GCS + with open(archive_path, "rb") as f: + response = requests.put( + upload_url_response.upload_url, + data=f, + headers={"content-type": "application/zip"}, + ) + if response.status_code != 200: + raise RuntimeError( + "Failed to upload user code. code={}, reason={}, text={}".format( + response.status_code, response.reason, response.text + ) + ) + + # Deploy Cloud Function + create_function_request = functions_v2.CreateFunctionRequest() + create_function_request.parent = ( + self.get_cloud_function_fully_qualified_parent() + ) + create_function_request.function_id = cf_name + function = functions_v2.Function() + function.name = self.get_cloud_function_fully_qualified_name(cf_name) + function.build_config = functions_v2.BuildConfig() + function.build_config.runtime = python_version + function.build_config.entry_point = entry_point + function.build_config.source = functions_v2.Source() + function.build_config.source.storage_source = functions_v2.StorageSource() + function.build_config.source.storage_source.bucket = ( + upload_url_response.storage_source.bucket + ) + function.build_config.source.storage_source.object_ = ( + upload_url_response.storage_source.object_ + ) + function.service_config = functions_v2.ServiceConfig() + function.service_config.available_memory = "1024M" + function.service_config.timeout_seconds = 600 + create_function_request.function = function + + # Create the cloud function and wait for it to be ready to use + operation = self._cloud_functions_client.create_function( + request=create_function_request + ) + operation.result() + + # Cleanup + os.remove(archive_path) + + # Fetch the endpoint of the just created function + endpoint = self.get_cloud_function_endpoint(cf_name) + if not endpoint: + raise ValueError( + f"Couldn't fetch the http endpoint. {constants.FEEDBACK_LINK}" + ) + + logger.info( + f"Successfully created cloud function {cf_name} with uri ({endpoint})" + ) + return endpoint + + def provision_bq_remote_function( + self, + def_, + input_types, + output_type, + reuse, + name, + package_requirements, + ): + """Provision a BigQuery remote function.""" + # If reuse of any existing function with the same name (indicated by the + # same hash of its source code) is not intended, then attach a unique + # suffix to the intended function name to make it unique. + uniq_suffix = None + if not reuse: + uniq_suffix = "".join( + random.choices(string.ascii_lowercase + string.digits, k=8) + ) + + # Derive the name of the cloud function underlying the intended BQ + # remote function + cloud_function_name = get_cloud_function_name( + def_, uniq_suffix, package_requirements + ) + cf_endpoint = self.get_cloud_function_endpoint(cloud_function_name) + + # Create the cloud function if it does not exist + if not cf_endpoint: + cf_endpoint = self.create_cloud_function( + def_, cloud_function_name, package_requirements + ) + else: + logger.info(f"Cloud function {cloud_function_name} already exists.") + + # Derive the name of the remote function + remote_function_name = name + if not remote_function_name: + remote_function_name = get_remote_function_name( + def_, uniq_suffix, package_requirements + ) + rf_endpoint, rf_conn = self.get_remote_function_specs(remote_function_name) + + # Create the BQ remote function in following circumstances: + # 1. It does not exist + # 2. It exists but the existing remote function has different + # configuration than intended + if not rf_endpoint or ( + rf_endpoint != cf_endpoint or rf_conn != self._bq_connection_id + ): + input_args = inspect.getargs(def_.__code__).args + if len(input_args) != len(input_types): + raise ValueError( + "Exactly one type should be provided for every input arg." + ) + self.create_bq_remote_function( + input_args, input_types, output_type, cf_endpoint, remote_function_name + ) + else: + logger.info(f"Remote function {remote_function_name} already exists.") + + return remote_function_name, cloud_function_name + + def get_remote_function_specs(self, remote_function_name): + """Check whether a remote function already exists for the udf.""" + http_endpoint = None + bq_connection = None + routines = self._bq_client.list_routines( + f"{self._gcp_project_id}.{self._bq_dataset}" + ) + try: + for routine in routines: + if routine.reference.routine_id == remote_function_name: + # TODO(shobs): Use first class properties when they are available + # https://github.com/googleapis/python-bigquery/issues/1552 + rf_options = routine._properties.get("remoteFunctionOptions") + if rf_options: + http_endpoint = rf_options.get("endpoint") + bq_connection = rf_options.get("connection") + if bq_connection: + bq_connection = os.path.basename(bq_connection) + break + except google.api_core.exceptions.NotFound: + # The dataset might not exist, in which case the http_endpoint doesn't, either. + # Note: list_routines doesn't make an API request until we iterate on the response object. + pass + return (http_endpoint, bq_connection) + + +def remote_function_node( + routine_ref: bigquery.RoutineReference, ibis_signature: IbisSignature +): + """Creates an Ibis node representing a remote function call.""" + + fields = { + name: rlz.value(type_) if type_ else rlz.any + for name, type_ in zip( + ibis_signature.parameter_names, ibis_signature.input_types + ) + } + + try: + fields["output_type"] = rlz.shape_like("args", dtype=ibis_signature.output_type) # type: ignore + except TypeError: + fields["output_dtype"] = property(lambda _: ibis_signature.output_type) + fields["output_shape"] = rlz.shape_like("args") + + node = type(routine_ref_to_string_for_query(routine_ref), (ops.ValueOp,), fields) # type: ignore + + @compiles(node) + def compile_node(t, op): + return "{}({})".format(node.__name__, ", ".join(map(t.translate, op.args))) + + def f(*args, **kwargs): + return node(*args, **kwargs).to_expr() + + f.bigframes_remote_function = str(routine_ref) # type: ignore + + return f + + +class UnsupportedTypeError(ValueError): + def __init__(self, type_, supported_types): + self.type = type_ + self.supported_types = supported_types + + +def ibis_type_from_python_type(t: type) -> IbisDataType: + if t not in SUPPORTED_IO_PYTHON_TYPES: + raise UnsupportedTypeError(t, SUPPORTED_IO_PYTHON_TYPES) + return python_type_to_bigquery_type(t) + + +def ibis_type_from_type_kind(tk: bigquery.StandardSqlTypeNames) -> IbisDataType: + if tk not in SUPPORTED_IO_BIGQUERY_TYPEKINDS: + raise UnsupportedTypeError(tk, SUPPORTED_IO_BIGQUERY_TYPEKINDS) + return BigQueryType.to_ibis(tk) + + +def ibis_signature_from_python_signature( + signature: inspect.Signature, + input_types: Sequence[type], + output_type: type, +) -> IbisSignature: + return IbisSignature( + parameter_names=list(signature.parameters.keys()), + input_types=[ibis_type_from_python_type(t) for t in input_types], + output_type=ibis_type_from_python_type(output_type), + ) + + +class ReturnTypeMissingError(ValueError): + pass + + +def ibis_signature_from_routine(routine: bigquery.Routine) -> IbisSignature: + if not routine.return_type: + raise ReturnTypeMissingError + + return IbisSignature( + parameter_names=[arg.name for arg in routine.arguments], + input_types=[ + ibis_type_from_type_kind(arg.data_type.type_kind) if arg.data_type else None + for arg in routine.arguments + ], + output_type=ibis_type_from_type_kind(routine.return_type.type_kind), + ) + + +class DatasetMissingError(ValueError): + pass + + +def get_routine_reference( + routine_ref_str: str, bigquery_client: bigquery.Client, session: Optional[Session] +) -> bigquery.RoutineReference: + try: + # Handle cases ".." and + # ".". + return bigquery.RoutineReference.from_string( + routine_ref_str, + default_project=bigquery_client.project, + ) + except ValueError: + # Handle case of "". + if not session: + raise DatasetMissingError + + dataset_ref = bigquery.DatasetReference( + bigquery_client.project, session._session_dataset_id + ) + return dataset_ref.routine(routine_ref_str) + + +# Inspired by @udf decorator implemented in ibis-bigquery package +# https://github.com/ibis-project/ibis-bigquery/blob/main/ibis_bigquery/udf/__init__.py +# which has moved as @js to the ibis package +# https://github.com/ibis-project/ibis/blob/master/ibis/backends/bigquery/udf/__init__.py +def remote_function( + input_types: Sequence[type], + output_type: type, + session: Optional[Session] = None, + bigquery_client: Optional[bigquery.Client] = None, + bigquery_connection_client: Optional[ + bigquery_connection_v1.ConnectionServiceClient + ] = None, + cloud_functions_client: Optional[functions_v2.FunctionServiceClient] = None, + resource_manager_client: Optional[resourcemanager_v3.ProjectsClient] = None, + dataset: Optional[str] = None, + bigquery_connection: Optional[str] = None, + reuse: bool = True, + name: Optional[str] = None, + packages: Optional[Sequence[str]] = None, +): + """Decorator to turn a user defined function into a BigQuery remote function. + + .. deprecated:: 0.0.1 + This is an internal method. Please use :func:`bigframes.pandas.remote_function` instead. + + .. note:: + Please make sure following is setup before using this API: + + 1. Have the below APIs enabled for your project: + + * BigQuery Connection API + * Cloud Functions API + * Cloud Run API + * Cloud Build API + * Artifact Registry API + * Cloud Resource Manager API + + This can be done from the cloud console (change `PROJECT_ID` to yours): + https://console.cloud.google.com/apis/enableflow?apiid=bigqueryconnection.googleapis.com,cloudfunctions.googleapis.com,run.googleapis.com,cloudbuild.googleapis.com,artifactregistry.googleapis.com,cloudresourcemanager.googleapis.com&project=PROJECT_ID + + Or from the gcloud CLI: + + `$ gcloud services enable bigqueryconnection.googleapis.com cloudfunctions.googleapis.com run.googleapis.com cloudbuild.googleapis.com artifactregistry.googleapis.com cloudresourcemanager.googleapis.com` + + 2. Have following IAM roles enabled for you: + + * BigQuery Data Editor (roles/bigquery.dataEditor) + * BigQuery Connection Admin (roles/bigquery.connectionAdmin) + * Cloud Functions Developer (roles/cloudfunctions.developer) + * Service Account User (roles/iam.serviceAccountUser) on the service account `PROJECT_NUMBER-compute@developer.gserviceaccount.com` + * Storage Object Viewer (roles/storage.objectViewer) + * Project IAM Admin (roles/resourcemanager.projectIamAdmin) (Only required if the bigquery connection being used is not pre-created and is created dynamically with user credentials.) + + 3. Either the user has setIamPolicy privilege on the project, or a BigQuery connection is pre-created with necessary IAM role set: + + 1. To create a connection, follow https://cloud.google.com/bigquery/docs/reference/standard-sql/remote-functions#create_a_connection + 2. To set up IAM, follow https://cloud.google.com/bigquery/docs/reference/standard-sql/remote-functions#grant_permission_on_function + + Alternatively, the IAM could also be setup via the gcloud CLI: + + `$ gcloud projects add-iam-policy-binding PROJECT_ID --member="serviceAccount:CONNECTION_SERVICE_ACCOUNT_ID" --role="roles/run.invoker"`. + + Args: + input_types list(type): + List of input data types in the user defined function. + output_type type: + Data type of the output in the user defined function. + session (bigframes.Session, Optional): + BigQuery DataFrames session to use for getting default project, + dataset and BigQuery connection. + bigquery_client (google.cloud.bigquery.Client, Optional): + Client to use for BigQuery operations. If this param is not provided + then bigquery client from the session would be used. + bigquery_connection_client (google.cloud.bigquery_connection_v1.ConnectionServiceClient, Optional): + Client to use for cloud functions operations. If this param is not + provided then functions client from the session would be used. + cloud_functions_client (google.cloud.functions_v2.FunctionServiceClient, Optional): + Client to use for BigQuery connection operations. If this param is + not provided then bigquery connection client from the session would + be used. + resource_manager_client (google.cloud.resourcemanager_v3.ProjectsClient, Optional): + Client to use for cloud resource management operations, e.g. for + getting and setting IAM roles on cloud resources. If this param is + not provided then resource manager client from the session would be + used. + dataset (str, Optional.): + Dataset in which to create a BigQuery remote function. It should be in + `.` or `` format. If this + parameter is not provided then session dataset id is used. + bigquery_connection (str, Optional): + Name of the BigQuery connection in the form of `CONNECTION_ID` or + `LOCATION.CONNECTION_ID` or `PROJECT_ID.LOCATION.CONNECTION_ID`. + If this param is not provided then the bigquery connection from the session + would be used. If it is pre created in the same location as the + `bigquery_client.location` then it would be used, otherwise it is created + dynamically using the `bigquery_connection_client` assuming the user has necessary + priviliges. The PROJECT_ID should be the same as the BigQuery connection project. + reuse (bool, Optional): + Reuse the remote function if is already exists. + `True` by default, which results in reusing an existing remote + function and corresponding cloud function (if any) that was + previously created for the same udf. + Setting it to `False` forces the creation of a unique remote function. + If the required remote function does not exist then it would be + created irrespective of this param. + name (str, Optional): + Explicit name of the persisted BigQuery remote function. Use it with + caution, because two users working in the same project and dataset + could overwrite each other's remote functions if they use the same + persistent name. + packages (str[], Optional): + Explicit name of the external package dependencies. Each dependency + is added to the `requirements.txt` as is, and can be of the form + supported in https://pip.pypa.io/en/stable/reference/requirements-file-format/. + + """ + import bigframes.pandas as bpd + + session = session or bpd.get_global_session() + + # A BigQuery client is required to perform BQ operations + if not bigquery_client: + bigquery_client = session.bqclient + if not bigquery_client: + raise ValueError( + "A bigquery client must be provided, either directly or via session. " + f"{constants.FEEDBACK_LINK}" + ) + + # A BigQuery connection client is required to perform BQ connection operations + if not bigquery_connection_client: + bigquery_connection_client = session.bqconnectionclient + if not bigquery_connection_client: + raise ValueError( + "A bigquery connection client must be provided, either directly or via session. " + f"{constants.FEEDBACK_LINK}" + ) + + # A cloud functions client is required to perform cloud functions operations + if not cloud_functions_client: + cloud_functions_client = session.cloudfunctionsclient + if not cloud_functions_client: + raise ValueError( + "A cloud functions client must be provided, either directly or via session. " + f"{constants.FEEDBACK_LINK}" + ) + + # A resource manager client is required to get/set IAM operations + if not resource_manager_client: + resource_manager_client = session.resourcemanagerclient + if not resource_manager_client: + raise ValueError( + "A resource manager client must be provided, either directly or via session. " + f"{constants.FEEDBACK_LINK}" + ) + + # BQ remote function must be persisted, for which we need a dataset + # https://cloud.google.com/bigquery/docs/reference/standard-sql/remote-functions#:~:text=You%20cannot%20create%20temporary%20remote%20functions. + if dataset: + dataset_ref = bigquery.DatasetReference.from_string( + dataset, default_project=bigquery_client.project + ) + else: + dataset_ref = bigquery.DatasetReference.from_string( + session._session_dataset_id, default_project=bigquery_client.project + ) + + bq_location, cloud_function_region = get_remote_function_locations( + bigquery_client.location + ) + + # A connection is required for BQ remote function + # https://cloud.google.com/bigquery/docs/reference/standard-sql/remote-functions#create_a_remote_function + if not bigquery_connection: + bigquery_connection = session._bq_connection # type: ignore + + bigquery_connection = clients.BqConnectionManager.resolve_full_connection_name( + bigquery_connection, + default_project=dataset_ref.project, + default_location=bq_location, + ) + # Guaranteed to be the form of .. + ( + gcp_project_id, + bq_connection_location, + bq_connection_id, + ) = bigquery_connection.split(".") + if gcp_project_id.casefold() != dataset_ref.project.casefold(): + raise ValueError( + "The project_id does not match BigQuery connection gcp_project_id: " + f"{dataset_ref.project}." + ) + if bq_connection_location.casefold() != bq_location.casefold(): + raise ValueError( + "The location does not match BigQuery connection location: " + f"{bq_location}." + ) + + def wrapper(f): + if not callable(f): + raise TypeError("f must be callable, got {}".format(f)) + + signature = inspect.signature(f) + ibis_signature = ibis_signature_from_python_signature( + signature, input_types, output_type + ) + + remote_function_client = RemoteFunctionClient( + dataset_ref.project, + cloud_function_region, + cloud_functions_client, + bq_location, + dataset_ref.dataset_id, + bigquery_client, + bigquery_connection_client, + bq_connection_id, + resource_manager_client, + ) + + rf_name, cf_name = remote_function_client.provision_bq_remote_function( + f, + ibis_signature.input_types, + ibis_signature.output_type, + reuse, + name, + packages, + ) + + node = remote_function_node(dataset_ref.routine(rf_name), ibis_signature) + + node = functools.wraps(f)(node) + node.__signature__ = signature + node.bigframes_cloud_function = ( + remote_function_client.get_cloud_function_fully_qualified_name(cf_name) + ) + + return node + + return wrapper + + +def read_gbq_function( + function_name: str, + session: Optional[Session] = None, + bigquery_client: Optional[bigquery.Client] = None, +): + """ + Read an existing BigQuery function and prepare it for use in future queries. + """ + + # A BigQuery client is required to perform BQ operations + if not bigquery_client and session: + bigquery_client = session.bqclient + if not bigquery_client: + raise ValueError( + "A bigquery client must be provided, either directly or via session. " + f"{constants.FEEDBACK_LINK}" + ) + + try: + routine_ref = get_routine_reference(function_name, bigquery_client, session) + except DatasetMissingError: + raise ValueError( + "Project and dataset must be provided, either directly or via session. " + f"{constants.FEEDBACK_LINK}" + ) + + # Find the routine and get its arguments. + try: + routine = bigquery_client.get_routine(routine_ref) + except google.api_core.exceptions.NotFound: + raise ValueError(f"Unknown function '{routine_ref}'. {constants.FEEDBACK_LINK}") + + try: + ibis_signature = ibis_signature_from_routine(routine) + except ReturnTypeMissingError: + raise ValueError( + "Function return type must be specified. {constants.FEEDBACK_LINK}" + ) + except UnsupportedTypeError as e: + raise ValueError( + f"Type {e.type} not supported, supported types are {e.supported_types}. " + f"{constants.FEEDBACK_LINK}" + ) + + return remote_function_node(routine_ref, ibis_signature) diff --git a/bigframes/series.py b/bigframes/series.py index 1a56c76b023..032bdf6c429 100644 --- a/bigframes/series.py +++ b/bigframes/series.py @@ -16,231 +16,71 @@ from __future__ import annotations -import datetime -import functools import itertools import numbers import textwrap import typing -import warnings -from typing import ( - Any, - Callable, - Iterable, - List, - Literal, - Mapping, - Optional, - Sequence, - Tuple, - TypeVar, - Union, - cast, - overload, -) +from typing import Any, Mapping, Optional, Tuple, Union -import bigframes_vendored.constants as constants -import bigframes_vendored.pandas.core.series as vendored_pandas_series -import google.cloud.bigquery.job +import google.cloud.bigquery as bigquery import numpy import pandas -import pyarrow as pa +import pandas.core.dtypes.common import typing_extensions -from pandas.api import extensions as pd_ext +import bigframes.constants as constants import bigframes.core import bigframes.core.block_transforms as block_ops import bigframes.core.blocks as blocks -import bigframes.core.col -import bigframes.core.expression as ex -import bigframes.core.identifiers as ids +import bigframes.core.groupby as groupby import bigframes.core.indexers import bigframes.core.indexes as indexes -import bigframes.core.ordering as order +from bigframes.core.ordering import ( + OrderingColumnReference, + OrderingDirection, + STABLE_SORTS, +) import bigframes.core.scalar as scalars import bigframes.core.utils as utils -import bigframes.core.validations as validations import bigframes.core.window -import bigframes.core.window_spec as windows +import bigframes.core.window_spec import bigframes.dataframe import bigframes.dtypes -import bigframes.exceptions as bfe import bigframes.formatting_helpers as formatter -import bigframes.functions import bigframes.operations as ops import bigframes.operations.aggregations as agg_ops -import bigframes.operations.lists as lists -import bigframes.operations.plotting as plotting -import bigframes.operations.python_op_maps as python_ops +import bigframes.operations.base +import bigframes.operations.datetimes as dt +import bigframes.operations.strings as strings import bigframes.operations.structs as structs -import bigframes.session -from bigframes._tools import docs -from bigframes.core import agg_expressions, groupby -from bigframes.core.logging import log_adapter -from bigframes.core.window import rolling - -if typing.TYPE_CHECKING: - import bigframes.extensions.bigframes.series_accessor as series_bigquery_accessor - import bigframes.geopandas.geoseries - import bigframes.operations.datetimes as datetimes - import bigframes.operations.strings as strings - +import third_party.bigframes_vendored.pandas.core.series as vendored_pandas_series -U = TypeVar("U") LevelType = typing.Union[str, int] LevelsType = typing.Union[LevelType, typing.Sequence[LevelType]] -_bigquery_function_recommendation_message = ( - "Your functions could not be applied directly to the Series." - " Try converting it to a BigFrames BigQuery function." -) - -_list = list # Type alias to escape Series.list property - - -@log_adapter.class_logger -@docs.inherit_docs(vendored_pandas_series.Series) -class Series: - # Must be above 5000 for pandas to delegate to bigframes for binops - __pandas_priority__ = 13000 - - # Ensure mypy can more robustly determine the type of self._block since it - # gets set in various places. - _block: blocks.Block - - def __init__( - self, - data=None, - index=None, - dtype: Optional[bigframes.dtypes.DtypeString | bigframes.dtypes.Dtype] = None, - name: str | None = None, - copy: Optional[bool] = None, - *, - session: Optional[bigframes.session.Session] = None, - ): - self._query_job: Optional[google.cloud.bigquery.job.QueryJob] = None - import bigframes.pandas - - # Ignore object dtype if provided, as it provides no additional - # information about what BigQuery type to use. - if dtype is not None and bigframes.dtypes.is_object_like(dtype): - dtype = None - - read_pandas_func = ( - session.read_pandas - if (session is not None) - else (lambda x: bigframes.pandas.read_pandas(x)) - ) - - block: typing.Optional[blocks.Block] = None - if (name is not None) and not isinstance(name, typing.Hashable): - raise ValueError( - f"BigQuery DataFrames only supports hashable series names. {constants.FEEDBACK_LINK}" - ) - if copy is not None and not copy: - raise ValueError( - f"Series constructor only supports copy=True. {constants.FEEDBACK_LINK}" - ) - - if isinstance(data, blocks.Block): - block = data - elif isinstance(data, bigframes.pandas.Series): - block = data._get_block() - # special case where data is local scalar, but index is bigframes index (maybe very big) - elif ( - not utils.is_list_like(data) and not isinstance(data, indexes.Index) - ) and isinstance(index, indexes.Index): - block = index._block - block, _ = block.create_constant(data) - block = block.with_column_labels([None]) - # prevents no-op reindex later - index = None - elif isinstance(data, indexes.Index) or isinstance(index, indexes.Index): - data = indexes.Index(data, dtype=dtype, name=name, session=session) - # set to none as it has already been applied, avoid re-cast later - if data.nlevels != 1: - raise NotImplementedError("Cannot interpret multi-index as Series.") - # Reset index to promote index columns to value columns, set default index - data_block = data._block.reset_index(drop=False).with_column_labels( - data.names - ) - if index is not None: # Align data and index by offset - bf_index = indexes.Index(index, session=session) - idx_block = bf_index._block.reset_index( - drop=False - ) # reset to align by offsets, and then reset back - idx_cols = idx_block.value_columns - data_block, (l_mapping, _) = idx_block.join(data_block, how="left") - data_block = data_block.set_index([l_mapping[col] for col in idx_cols]) - data_block = data_block.with_index_labels(bf_index.names) - # prevents no-op reindex later - index = None - block = data_block - - if block: - assert len(block.value_columns) == 1 - assert len(block.column_labels) == 1 - if index is not None: # reindexing operation - bf_index = indexes.Index(index) - idx_block = bf_index._block - idx_cols = idx_block.index_columns - block, _ = idx_block.join(block, how="left") - block = block.with_index_labels(bf_index.names) - if name: - block = block.with_column_labels([name]) - if dtype: - bf_dtype = bigframes.dtypes.bigframes_type(dtype) - block = block.multi_apply_unary_op(ops.AsTypeOp(to_type=bf_dtype)) - else: - if isinstance(dtype, str) and dtype.lower() == "json": - dtype = bigframes.dtypes.JSON_DTYPE - - pd_series = pandas.Series( - data=data, - index=index, # type:ignore - dtype=dtype, # type:ignore - name=name, - ) - block = read_pandas_func(pd_series)._get_block() # type:ignore - - assert block is not None - self._block: blocks.Block = block - - self._block.session._register_object(self) +class Series(bigframes.operations.base.SeriesMethods, vendored_pandas_series.Series): + def __init__(self, *args, **kwargs): + self._query_job: Optional[bigquery.QueryJob] = None + super().__init__(*args, **kwargs) @property - def dt(self) -> datetimes.DatetimeMethods: - import bigframes.operations.datetimes as datetimes - - return datetimes.DatetimeMethods(self) + def dt(self) -> dt.DatetimeMethods: + return dt.DatetimeMethods(self._block) @property def dtype(self): - bigframes.dtypes.warn_on_db_dtypes_json_dtype([self._dtype]) return self._dtype @property def dtypes(self): - bigframes.dtypes.warn_on_db_dtypes_json_dtype([self._dtype]) return self._dtype @property - def geo(self) -> bigframes.geopandas.geoseries.GeoSeries: - """ - Accessor object for geography properties of the Series values. - - Returns: - bigframes.geopandas.geoseries.GeoSeries: - An accessor containing geography methods. - - """ - import bigframes.geopandas.geoseries - - return bigframes.geopandas.geoseries.GeoSeries(self) + def index(self) -> indexes.Index: + return indexes.Index(self) @property - @validations.requires_index def loc(self) -> bigframes.core.indexers.LocSeriesIndexer: return bigframes.core.indexers.LocSeriesIndexer(self) @@ -249,12 +89,10 @@ def iloc(self) -> bigframes.core.indexers.IlocSeriesIndexer: return bigframes.core.indexers.IlocSeriesIndexer(self) @property - @validations.requires_ordering() def iat(self) -> bigframes.core.indexers.IatSeriesIndexer: return bigframes.core.indexers.IatSeriesIndexer(self) @property - @validations.requires_index def at(self) -> bigframes.core.indexers.AtSeriesIndexer: return bigframes.core.indexers.AtSeriesIndexer(self) @@ -262,11 +100,6 @@ def at(self) -> bigframes.core.indexers.AtSeriesIndexer: def name(self) -> blocks.Label: return self._name - @name.setter - def name(self, label: blocks.Label): - new_block = self._block.with_column_labels([label]) - self._set_block(new_block) - @property def shape(self) -> typing.Tuple[int]: return (self._block.shape[0],) @@ -283,45 +116,12 @@ def ndim(self) -> int: def empty(self) -> bool: return self.shape[0] == 0 - @property - def hasnans(self) -> bool: - # Note, hasnans is actually a null check, and NaNs don't count for nullable float - return self.isnull().any() - @property def values(self) -> numpy.ndarray: return self.to_numpy() @property - @validations.requires_index - def index(self) -> indexes.Index: - return indexes.Index.from_frame(self) - - @validations.requires_index - def keys(self) -> indexes.Index: - return self.index - - @property - def bigquery( - self, - ) -> series_bigquery_accessor.BigframesBigQuerySeriesAccessor: - """ - Accessor for BigQuery functionality. - - Returns: - bigframes.extensions.core.series_accessor.BigQuerySeriesAccessor: - Accessor that exposes BigQuery functionality on a Series, - with method names closer to SQL. - """ - # Import the accessor here to avoid circular imports. - import bigframes.extensions.bigframes.series_accessor - - return bigframes.extensions.bigframes.series_accessor.BigframesBigQuerySeriesAccessor( - self - ) - - @property - def query_job(self) -> Optional[google.cloud.bigquery.job.QueryJob]: + def query_job(self) -> Optional[bigquery.QueryJob]: """BigQuery job metadata for the most recent query. Returns: @@ -334,121 +134,59 @@ def query_job(self) -> Optional[google.cloud.bigquery.job.QueryJob]: @property def struct(self) -> structs.StructAccessor: - return structs.StructAccessor(self) - - @property - def list(self) -> lists.ListAccessor: - return lists.ListAccessor(self) + return structs.StructAccessor(self._block) @property - @validations.requires_ordering() def T(self) -> Series: return self.transpose() - @property - def _info_axis(self) -> indexes.Index: - return self.index - - @property - def _session(self) -> bigframes.Session: - return self._get_block().expr.session - - @property - def _struct_fields(self) -> List[str]: - if not bigframes.dtypes.is_struct_like(self._dtype): - return [] - - struct_type = typing.cast(pa.StructType, self._dtype.pyarrow_dtype) - return [struct_type.field(i).name for i in range(struct_type.num_fields)] - - @property - def sql(self) -> str: - """Compiles this Series's expression tree to SQL. - - Returns: - A string representing the compiled SQL. - """ - - return self.to_frame().sql - - @validations.requires_ordering() def transpose(self) -> Series: return self - def _set_internal_query_job( - self, query_job: Optional[google.cloud.bigquery.job.QueryJob] - ): + def _set_internal_query_job(self, query_job: bigquery.QueryJob): self._query_job = query_job def __len__(self): return self.shape[0] - def __bool__(self): - raise ValueError( - "Cannot convert Series into bool. Consider using .empty(), .item(), .any(), or .all() methods." - ) - def __iter__(self) -> typing.Iterator: return itertools.chain.from_iterable( - map(lambda x: x.squeeze(axis=1), self._block.to_pandas_batches()) + map(lambda x: x.index, self._block.to_pandas_batches()) ) - def __contains__(self, key) -> bool: - return key in self.index - def copy(self) -> Series: return Series(self._block) - @overload - def rename( - self, - index: Union[blocks.Label, Mapping[Any, Any]] = None, - ) -> Series: ... - - @overload - def rename( - self, - index: Union[blocks.Label, Mapping[Any, Any]] = None, - *, - inplace: Literal[False], - **kwargs, - ) -> Series: ... - - @overload def rename( - self, - index: Union[blocks.Label, Mapping[Any, Any]] = None, - *, - inplace: Literal[True], - **kwargs, - ) -> None: ... - - def rename( - self, - index: Union[blocks.Label, Mapping[Any, Any]] = None, - *, - inplace: bool = False, - **kwargs, - ) -> Optional[Series]: + self, index: Union[blocks.Label, Mapping[Any, Any]] = None, **kwargs + ) -> Series: if len(kwargs) != 0: raise NotImplementedError( f"rename does not currently support any keyword arguments. {constants.FEEDBACK_LINK}" ) + # rename the Series name + if index is None or isinstance( + index, str + ): # Python 3.9 doesn't allow isinstance of Optional + index = typing.cast(Optional[str], index) + block = self._block.with_column_labels([index]) + return Series(block) + # rename the index if isinstance(index, Mapping): index = typing.cast(Mapping[Any, Any], index) block = self._block for k, v in index.items(): new_idx_ids = [] - for idx_id, idx_dtype in zip(block.index_columns, block.index.dtypes): + for idx_id, idx_dtype in zip(block.index_columns, block.index_dtypes): # Will throw if key type isn't compatible with index type, which leads to invalid SQL. block.create_constant(k, dtype=idx_dtype) # Will throw if value type isn't compatible with index type. block, const_id = block.create_constant(v, dtype=idx_dtype) - block, cond_id = block.project_expr( - ops.ne_op.as_expr(idx_id, ex.const(k)) + block, cond_id = block.apply_unary_op( + idx_id, ops.BinopPartialRight(ops.ne_op, k) ) block, new_idx_id = block.apply_ternary_op( idx_id, cond_id, const_id, ops.where_op @@ -457,59 +195,23 @@ def rename( new_idx_ids.append(new_idx_id) block = block.drop_columns([const_id, cond_id]) - block = block.set_index(new_idx_ids, index_labels=block.index.names) + block = block.set_index(new_idx_ids, index_labels=block.index_labels) - if inplace: - self._block = block - return None - else: - return Series(block) + return Series(block) # rename the Series name if isinstance(index, typing.Hashable): index = typing.cast(Optional[str], index) block = self._block.with_column_labels([index]) - - if inplace: - self._block = block - return None - else: - return Series(block) + return Series(block) raise ValueError(f"Unsupported type of parameter index: {type(index)}") - @overload - def rename_axis( - self, - mapper: typing.Union[blocks.Label, typing.Sequence[blocks.Label]], - ) -> Series: ... - - @overload def rename_axis( self, mapper: typing.Union[blocks.Label, typing.Sequence[blocks.Label]], - *, - inplace: Literal[False], - **kwargs, - ) -> Series: ... - - @overload - def rename_axis( - self, - mapper: typing.Union[blocks.Label, typing.Sequence[blocks.Label]], - *, - inplace: Literal[True], **kwargs, - ) -> None: ... - - @validations.requires_index - def rename_axis( - self, - mapper: typing.Union[blocks.Label, typing.Sequence[blocks.Label]], - *, - inplace: bool = False, - **kwargs, - ) -> Optional[Series]: + ) -> Series: if len(kwargs) != 0: raise NotImplementedError( f"rename_axis does not currently support any keyword arguments. {constants.FEEDBACK_LINK}" @@ -519,13 +221,7 @@ def rename_axis( labels = mapper else: labels = [mapper] - - block = self._block.with_index_labels(labels) - if inplace: - self._block = block - return None - else: - return Series(block) + return Series(self._block.with_index_labels(labels)) def equals( self, other: typing.Union[Series, bigframes.dataframe.DataFrame] @@ -535,321 +231,83 @@ def equals( return False return block_ops.equals(self._block, other._block) - @overload # type: ignore[override] - def reset_index( - self, - level: blocks.LevelsType = ..., - *, - name: typing.Optional[str] = ..., - drop: Literal[False] = ..., - inplace: Literal[False] = ..., - allow_duplicates: Optional[bool] = ..., - ) -> bigframes.dataframe.DataFrame: ... - - @overload def reset_index( self, - level: blocks.LevelsType = ..., - *, - name: typing.Optional[str] = ..., - drop: Literal[True] = ..., - inplace: Literal[False] = ..., - allow_duplicates: Optional[bool] = ..., - ) -> Series: ... - - @overload - def reset_index( - self, - level: blocks.LevelsType = ..., - *, - name: typing.Optional[str] = ..., - drop: bool = ..., - inplace: Literal[True] = ..., - allow_duplicates: Optional[bool] = ..., - ) -> None: ... - - @validations.requires_ordering() - def reset_index( - self, - level: blocks.LevelsType = None, *, name: typing.Optional[str] = None, drop: bool = False, - inplace: bool = False, - allow_duplicates: Optional[bool] = None, - ) -> bigframes.dataframe.DataFrame | Series | None: - if allow_duplicates is None: - allow_duplicates = False - block = self._block.reset_index(level, drop, allow_duplicates=allow_duplicates) + ) -> bigframes.dataframe.DataFrame | Series: + block = self._block.reset_index(drop) if drop: - if inplace: - self._set_block(block) - return None return Series(block) else: - if inplace: - raise ValueError( - "Series.reset_index cannot combine inplace=True and drop=False" - ) if name: block = block.assign_label(self._value_column, name) return bigframes.dataframe.DataFrame(block) - def _prepare_display_df(self) -> bigframes.dataframe.DataFrame: - return self.to_frame()._prepare_display_df() - - def _repr_mimebundle_(self, include=None, exclude=None): - """ - Custom display method for IPython/Jupyter environments. - This is called by IPython's display system when the object is displayed. - """ - # TODO(b/467647693): Anywidget integration has been tested in Jupyter, VS Code, and - # BQ Studio, but there is a known compatibility issue with Marimo that needs to be addressed. - from bigframes.display import html - - return html.repr_mimebundle(self, include=include, exclude=exclude) - def __repr__(self) -> str: - # Protect against errors with uninitialized Series. See: - # https://github.com/googleapis/python-bigquery-dataframes/issues/728 - if not hasattr(self, "_block"): - return object.__repr__(self) - # TODO(swast): Add a timeout here? If the query is taking a long time, # maybe we just print the job metadata that we have so far? # TODO(swast): Avoid downloading the whole series by using job # metadata, like we do with DataFrame. opts = bigframes.options.display + max_results = opts.max_rows if opts.repr_mode == "deferred": - return formatter.repr_query_job(self._compute_dry_run()) + return formatter.repr_query_job(self.query_job) - self._cached() - pandas_df, row_count, query_job = self._block.retrieve_repr_request_results( - opts.max_rows - ) + pandas_df, _, query_job = self._block.retrieve_repr_request_results(max_results) self._set_internal_query_job(query_job) - from bigframes.display import plaintext - return plaintext.create_text_representation( - pandas_df, - row_count, - is_series=True, - has_index=len(self._block.index_columns) > 0, - ) + return repr(pandas_df.iloc[:, 0]) def astype( self, dtype: Union[bigframes.dtypes.DtypeString, bigframes.dtypes.Dtype], - *, - errors: Literal["raise", "null"] = "raise", ) -> Series: - if errors not in ["raise", "null"]: - raise ValueError("Argument 'errors' must be one of 'raise' or 'null'") - dtype = bigframes.dtypes.bigframes_type(dtype) - safe = errors == "null" - if dtype == bigframes.dtypes.JSON_DTYPE: - return self._apply_unary_op(bigframes.operations.ToJSON(safe=safe)) - elif self.dtype == bigframes.dtypes.JSON_DTYPE: - return self._apply_unary_op( - bigframes.operations.JSONDecode(to_type=dtype, safe=safe) - ) - else: - return self._apply_unary_op( - bigframes.operations.AsTypeOp(to_type=dtype, safe=safe) - ) + return self._apply_unary_op(bigframes.operations.AsTypeOp(dtype)) def to_pandas( self, max_download_size: Optional[int] = None, sampling_method: Optional[str] = None, random_state: Optional[int] = None, - *, - ordered: bool = True, - dry_run: bool = False, - allow_large_results: Optional[bool] = None, ) -> pandas.Series: """Writes Series to pandas Series. - **Examples:** - - >>> s = bpd.Series([4, 3, 2]) - - Download the data from BigQuery and convert it into an in-memory pandas Series. - - >>> s.to_pandas() - 0 4 - 1 3 - 2 2 - dtype: Int64 - - Estimate job statistics without processing or downloading data by using `dry_run=True`. - - >>> s.to_pandas(dry_run=True) # doctest: +SKIP - columnCount 1 - columnDtypes {None: Int64} - indexLevel 1 - indexDtypes [Int64] - projectId bigframes-dev - location US - jobType QUERY - destinationTable {'projectId': 'bigframes-dev', 'datasetId': '_... - useLegacySql False - referencedTables None - totalBytesProcessed 0 - cacheHit False - statementType SELECT - creationTime 2025-04-03 18:54:59.219000+00:00 - dtype: object - Args: max_download_size (int, default None): - .. deprecated:: 2.0.0 - ``max_download_size`` parameter is deprecated. Please use ``to_pandas_batches()`` - method instead. - - Download size threshold in MB. If ``max_download_size`` is exceeded when downloading data, - the data will be downsampled if ``bigframes.options.sampling.enable_downsampling`` is - ``True``, otherwise, an error will be raised. If set to a value other than ``None``, - this will supersede the global config. + Download size threshold in MB. If max_download_size is exceeded when downloading data + (e.g., to_pandas()), the data will be downsampled if + bigframes.options.sampling.enable_downsampling is True, otherwise, an error will be + raised. If set to a value other than None, this will supersede the global config. sampling_method (str, default None): - .. deprecated:: 2.0.0 - ``sampling_method`` parameter is deprecated. Please use ``sample()`` method instead. - Downsampling algorithms to be chosen from, the choices are: "head": This algorithm returns a portion of the data from the beginning. It is fast and requires minimal computations to perform the downsampling; "uniform": This algorithm returns uniform random samples of the data. If set to a value other than None, this will supersede the global config. random_state (int, default None): - .. deprecated:: 2.0.0 - ``random_state`` parameter is deprecated. Please use ``sample()`` method instead. - The seed for the uniform downsampling algorithm. If provided, the uniform method may take longer to execute and require more computation. If set to a value other than None, this will supersede the global config. - ordered (bool, default True): - Determines whether the resulting pandas series will be ordered. - In some cases, unordered may result in a faster-executing query. - dry_run (bool, default False): - If this argument is true, this method will not process the data. Instead, it returns - a Pandas Series containing dry run job statistics - allow_large_results (bool, default None): - If not None, overrides the global setting to allow or disallow large query results - over the default size limit of 10 GB. Returns: pandas.Series: A pandas Series with all rows of this Series if the data_sampling_threshold_mb - is not exceeded; otherwise, a pandas Series with downsampled rows of the DataFrame. If dry_run - is set to True, a pandas Series containing dry run statistics will be returned. + is not exceeded; otherwise, a pandas Series with downsampled rows of the DataFrame. """ - if max_download_size is not None: - msg = bfe.format_message( - "DEPRECATED: The `max_download_size` parameters for `Series.to_pandas()` " - "are deprecated and will be removed soon. Please use `Series.to_pandas_batches()`." - ) - warnings.warn(msg, category=FutureWarning) - if sampling_method is not None or random_state is not None: - msg = bfe.format_message( - "DEPRECATED: The `sampling_method` and `random_state` parameters for " - "`Series.to_pandas()` are deprecated and will be removed soon. " - "Please use `Series.sample().to_pandas()` instead for sampling." - ) - warnings.warn(msg, category=FutureWarning) - - if dry_run: - dry_run_stats, dry_run_job = self._block._compute_dry_run( - max_download_size=max_download_size, - sampling_method=sampling_method, - random_state=random_state, - ordered=ordered, - ) - - self._set_internal_query_job(dry_run_job) - return dry_run_stats - - # Repeat the to_pandas() call to make mypy deduce type correctly, because mypy cannot resolve - # Literal[True/False] to bool df, query_job = self._block.to_pandas( + (self._value_column,), max_download_size=max_download_size, sampling_method=sampling_method, random_state=random_state, - ordered=ordered, - allow_large_results=allow_large_results, ) - - if query_job: - self._set_internal_query_job(query_job) - - series = df.squeeze(axis=1) + self._set_internal_query_job(query_job) + series = df[self._value_column] series.name = self._name return series - def to_pandas_batches( - self, - page_size: Optional[int] = None, - max_results: Optional[int] = None, - *, - allow_large_results: Optional[bool] = None, - cell_execution_count: Optional[int] = None, - ) -> Iterable[pandas.Series]: - """Stream Series results to an iterable of pandas Series. - - page_size and max_results determine the size and number of batches, - see https://cloud.google.com/python/docs/reference/bigquery/latest/google.cloud.bigquery.job.QueryJob#google_cloud_bigquery_job_QueryJob_result - - **Examples:** - - >>> s = bpd.Series([4, 3, 2, 2, 3]) - - Iterate through the results in batches, limiting the total rows yielded - across all batches via `max_results`: - - >>> for s_batch in s.to_pandas_batches(max_results=3): - ... print(s_batch) - 0 4 - 1 3 - 2 2 - dtype: Int64 - - Alternatively, control the approximate size of each batch using `page_size` - and fetch batches manually using `next()`: - - >>> it = s.to_pandas_batches(page_size=2) - >>> next(it) - 0 4 - 1 3 - dtype: Int64 - >>> next(it) - 2 2 - 3 2 - dtype: Int64 - - Args: - page_size (int, default None): - The maximum number of rows of each batch. Non-positive values are ignored. - max_results (int, default None): - The maximum total number of rows of all batches. - allow_large_results (bool, default None): - If not None, overrides the global setting to allow or disallow large query results - over the default size limit of 10 GB. - - Returns: - Iterable[pandas.Series]: - An iterable of smaller Series which combine to - form the original Series. Results stream from bigquery, - see https://cloud.google.com/python/docs/reference/bigquery/latest/google.cloud.bigquery.table.RowIterator#google_cloud_bigquery_table_RowIterator_to_arrow_iterable - """ - batches = self._block.to_pandas_batches( - page_size=page_size, - max_results=max_results, - allow_large_results=allow_large_results, - cell_execution_count=cell_execution_count, - ) - return map(lambda df: cast(pandas.Series, df.squeeze(1)), batches) - - def _compute_dry_run(self) -> google.cloud.bigquery.job.QueryJob: - _, query_job = self._block._compute_dry_run((self._value_column,)) - return query_job + def _compute_dry_run(self) -> bigquery.QueryJob: + return self._block._compute_dry_run((self._value_column,)) def drop( self, @@ -860,36 +318,32 @@ def drop( columns: Union[blocks.Label, typing.Iterable[blocks.Label]] = None, level: typing.Optional[LevelType] = None, ) -> Series: - if (labels is None) == (index is None): - raise ValueError("Must specify exactly one of 'labels' or 'index'") - - if labels is not None: - index = labels + if labels and index: + raise ValueError("Must specify exacly one of 'labels' or 'index'") + index = labels or index # ignore axis, columns params block = self._block level_id = self._resolve_levels(level or 0)[0] if _is_list_like(index): block, inverse_condition_id = block.apply_unary_op( - level_id, ops.IsInOp(values=tuple(index), match_nulls=True) + level_id, ops.IsInOp(index, match_nulls=True) ) block, condition_id = block.apply_unary_op( inverse_condition_id, ops.invert_op ) else: - block, condition_id = block.project_expr( - ops.ne_op.as_expr(level_id, ex.const(index)) + block, condition_id = block.apply_unary_op( + level_id, ops.partial_right(ops.ne_op, index) ) - block = block.filter_by_id(condition_id, keep_null=True) + block = block.filter(condition_id, keep_null=True) block = block.drop_columns([condition_id]) return Series(block.select_column(self._value_column)) - @validations.requires_index def droplevel(self, level: LevelsType, axis: int | str = 0): resolved_level_ids = self._resolve_levels(level) return Series(self._block.drop_levels(resolved_level_ids)) - @validations.requires_index def swaplevel(self, i: int = -2, j: int = -1): level_i = self._block.index_columns[i] level_j = self._block.index_columns[j] @@ -899,13 +353,12 @@ def swaplevel(self, i: int = -2, j: int = -1): ] return Series(self._block.reorder_levels(reordering)) - @validations.requires_index def reorder_levels(self, order: LevelsType, axis: int | str = 0): resolved_level_ids = self._resolve_levels(order) return Series(self._block.reorder_levels(resolved_level_ids)) def _resolve_levels(self, level: LevelsType) -> typing.Sequence[str]: - return self._block.index.resolve_level(level) + return self._block.resolve_index_level(level) def between(self, left, right, inclusive="both"): if inclusive not in ["both", "neither", "left", "right"]: @@ -918,72 +371,55 @@ def between(self, left, right, inclusive="both"): self._apply_binary_op(right, right_op) ) - def case_when(self, caselist) -> Series: - cases = [] - - for condition, output in itertools.chain(caselist, [(True, self)]): - cases.append(condition) - cases.append(output) - # In pandas, the default value if no case matches is the original value. - # This makes it impossible to change the type of the column, but if - # the condition is always True, we know it will match and no subsequent - # conditions matter (including the fallback to `self`). This break allows - # the type to change (see: internal issue 349926559). - if condition is True: - break - - return self._apply_nary_op( - ops.case_when_op, - cases, - # Self is already included in "others". - ignore_self=True, - ).rename(self.name) - - @validations.requires_ordering() def cumsum(self) -> Series: - return self._apply_window_op(agg_ops.sum_op, windows.cumulative_rows()) + return self._apply_window_op( + agg_ops.sum_op, bigframes.core.window_spec.WindowSpec(following=0) + ) - @validations.requires_ordering() def ffill(self, *, limit: typing.Optional[int] = None) -> Series: - window = windows.rows(start=None if limit is None else -limit, end=0) + window = bigframes.core.window_spec.WindowSpec(preceding=limit, following=0) return self._apply_window_op(agg_ops.LastNonNullOp(), window) pad = ffill - @validations.requires_ordering() def bfill(self, *, limit: typing.Optional[int] = None) -> Series: - window = windows.rows(start=0, end=limit) + window = bigframes.core.window_spec.WindowSpec(preceding=0, following=limit) return self._apply_window_op(agg_ops.FirstNonNullOp(), window) - @validations.requires_ordering() def cummax(self) -> Series: - return self._apply_window_op(agg_ops.max_op, windows.cumulative_rows()) + return self._apply_window_op( + agg_ops.max_op, bigframes.core.window_spec.WindowSpec(following=0) + ) - @validations.requires_ordering() def cummin(self) -> Series: - return self._apply_window_op(agg_ops.min_op, windows.cumulative_rows()) + return self._apply_window_op( + agg_ops.min_op, bigframes.core.window_spec.WindowSpec(following=0) + ) - @validations.requires_ordering() def cumprod(self) -> Series: - return self._apply_window_op(agg_ops.product_op, windows.cumulative_rows()) + return self._apply_window_op( + agg_ops.product_op, bigframes.core.window_spec.WindowSpec(following=0) + ) - @validations.requires_ordering() def shift(self, periods: int = 1) -> Series: - window_spec = windows.rows() - return self._apply_window_op(agg_ops.ShiftOp(periods), window_spec) + window = bigframes.core.window_spec.WindowSpec( + preceding=periods if periods > 0 else None, + following=-periods if periods < 0 else None, + ) + return self._apply_window_op(agg_ops.ShiftOp(periods), window) - @validations.requires_ordering() def diff(self, periods: int = 1) -> Series: - window_spec = windows.rows() - return self._apply_window_op(agg_ops.DiffOp(periods), window_spec) + window = bigframes.core.window_spec.WindowSpec( + preceding=periods if periods > 0 else None, + following=-periods if periods < 0 else None, + ) + return self._apply_window_op(agg_ops.DiffOp(periods), window) - @validations.requires_ordering() def pct_change(self, periods: int = 1) -> Series: # Future versions of pandas will not perfrom ffill automatically series = self.ffill() return Series(block_ops.pct_change(series._block, periods=periods)) - @validations.requires_ordering() def rank( self, axis=0, @@ -991,11 +427,8 @@ def rank( numeric_only=False, na_option: str = "keep", ascending: bool = True, - pct: bool = False, ) -> Series: - return Series( - block_ops.rank(self._block, method, na_option, ascending, pct=pct) - ) + return Series(block_ops.rank(self._block, method, na_option, ascending)) def fillna(self, value=None) -> Series: return self._apply_binary_op(value, ops.fillna_op) @@ -1004,91 +437,44 @@ def replace( self, to_replace: typing.Any, value: typing.Any = None, *, regex: bool = False ): if regex: - # No-op unless to_replace and series dtype are both string type - if not isinstance(to_replace, str) or not isinstance( - self.dtype, pandas.StringDtype - ): - return self - return self._regex_replace(to_replace, value) + if not (isinstance(to_replace, str) and isinstance(value, str)): + raise NotImplementedError( + f"replace regex mode only supports strings for 'to_replace' and 'value'. {constants.FEEDBACK_LINK}" + ) + block, result_col = self._block.apply_unary_op( + self._value_column, + ops.ReplaceRegexOp(to_replace, value), + result_label=self.name, + ) + return Series(block.select_column(result_col)) elif utils.is_dict_like(to_replace): - return self._mapping_replace(to_replace) # type: ignore - elif utils.is_list_like(to_replace): - replace_list = to_replace - else: # Scalar - replace_list = [to_replace] - replace_list = [ - i for i in replace_list if bigframes.dtypes.is_compatible(i, self.dtype) - ] - return self._simple_replace(replace_list, value) if replace_list else self - - def _regex_replace(self, to_replace: str, value: str): - if not bigframes.dtypes.is_dtype(value, self.dtype): raise NotImplementedError( - f"Cannot replace {self.dtype} elements with incompatible item {value} as mixed-type columns not supported. {constants.FEEDBACK_LINK}" + f"Dict 'to_replace' not supported. {constants.FEEDBACK_LINK}" ) - block, result_col = self._block.apply_unary_op( - self._value_column, - ops.RegexReplaceStrOp(to_replace, value), - result_label=self.name, - ) - return Series(block.select_column(result_col)) - - def _simple_replace(self, to_replace_list: typing.Sequence, value): - result_type = bigframes.dtypes.is_compatible(value, self.dtype) - if not result_type: - raise NotImplementedError( - f"Cannot replace {self.dtype} elements with incompatible item {value} as mixed-type columns not supported. {constants.FEEDBACK_LINK}" + elif utils.is_list_like(to_replace): + block, cond = self._block.apply_unary_op( + self._value_column, ops.IsInOp(to_replace) ) - - if result_type != self.dtype: - return self.astype(result_type)._simple_replace(to_replace_list, value) - - block, cond = self._block.apply_unary_op( - self._value_column, ops.IsInOp(tuple(to_replace_list)) - ) - block, result_col = block.project_expr( - ops.where_op.as_expr(ex.const(value), cond, self._value_column), self.name - ) - return Series(block.select_column(result_col)) - - def _mapping_replace(self, mapping: dict[typing.Hashable, typing.Hashable]): - if not mapping: - return self.copy() - - tuples = [] - lcd_types: list[typing.Optional[bigframes.dtypes.Dtype]] = [] - for key, value in mapping.items(): - lcd_type = bigframes.dtypes.is_compatible(key, self.dtype) - if not lcd_type: - continue - if not bigframes.dtypes.is_dtype(value, self.dtype): - raise NotImplementedError( - f"Cannot replace {self.dtype} elements with incompatible item {value} as mixed-type columns not supported. {constants.FEEDBACK_LINK}" - ) - tuples.append((key, value)) - lcd_types.append(lcd_type) - - result_dtype = functools.reduce( - lambda t1, t2: bigframes.dtypes.lcd_type(t1, t2) if (t1 and t2) else None, - lcd_types, - self.dtype, - ) - if not result_dtype: - raise NotImplementedError( - f"Cannot replace {self.dtype} elements with incompatible mapping {mapping} as mixed-type columns not supported. {constants.FEEDBACK_LINK}" + block, result_col = block.apply_binary_op( + cond, + self._value_column, + ops.partial_arg1(ops.where_op, value), + result_label=self.name, ) - block, result = self._block.apply_unary_op( - self._value_column, ops.MapOp(tuple(tuples)) - ) - replaced = Series(block.select_column(result)) - replaced.name = self.name - return replaced + return Series(block.select_column(result_col)) + else: # Scalar + block, cond = self._block.apply_unary_op( + self._value_column, ops.BinopPartialLeft(ops.eq_op, to_replace) + ) + block, result_col = block.apply_binary_op( + cond, + self._value_column, + ops.partial_arg1(ops.where_op, value), + result_label=self.name, + ) + return Series(block.select_column(result_col)) - @validations.requires_ordering() - @validations.requires_index def interpolate(self, method: str = "linear") -> Series: - if method == "pad": - return self.ffill() result = block_ops.interpolate(self._block, method) return Series(result) @@ -1107,64 +493,15 @@ def dropna( result = result.reset_index() return Series(result) - @validations.requires_ordering(bigframes.constants.SUGGEST_PEEK_PREVIEW) def head(self, n: int = 5) -> Series: return typing.cast(Series, self.iloc[0:n]) - @validations.requires_ordering() def tail(self, n: int = 5) -> Series: return typing.cast(Series, self.iloc[-n:]) - def peek( - self, n: int = 5, *, force: bool = True, allow_large_results=None - ) -> pandas.Series: - """ - Preview n arbitrary elements from the series without guarantees about row selection or ordering. - - ``Series.peek(force=False)`` will always be very fast, but will not succeed if data requires - full data scanning. Using ``force=True`` will always succeed, but may be perform queries. - Query results will be cached so that future steps will benefit from these queries. - - Args: - n (int, default 5): - The number of rows to select from the series. Which N rows are returned is non-deterministic. - force (bool, default True): - If the data cannot be peeked efficiently, the series will instead be fully materialized as part - of the operation if ``force=True``. If ``force=False``, the operation will throw a ValueError. - allow_large_results (bool, default None): - If not None, overrides the global setting to allow or disallow large query results - over the default size limit of 10 GB. - Returns: - pandas.Series: A pandas Series with n rows. - - Raises: - ValueError: If force=False and data cannot be efficiently peeked. - """ - maybe_result = self._block.try_peek(n, allow_large_results=allow_large_results) - if maybe_result is None: - if force: - self._cached() - maybe_result = self._block.try_peek( - n, force=True, allow_large_results=allow_large_results - ) - assert maybe_result is not None - else: - raise ValueError( - "Cannot peek efficiently when data has aggregates, joins or window functions applied. Use force=True to fully compute dataframe." - ) - as_series = maybe_result.squeeze(axis=1) - as_series.name = self.name - return as_series - - def item(self): - # Docstring is in third_party/bigframes_vendored/pandas/core/series.py - return self.peek(2).item() - def nlargest(self, n: int = 5, keep: str = "first") -> Series: if keep not in ("first", "last", "all"): raise ValueError("'keep must be one of 'first', 'last', or 'all'") - if keep != "all": - validations.enforce_ordered(self, "nlargest(keep != 'all')") return Series( block_ops.nlargest(self._block, n, [self._value_column], keep=keep) ) @@ -1172,25 +509,20 @@ def nlargest(self, n: int = 5, keep: str = "first") -> Series: def nsmallest(self, n: int = 5, keep: str = "first") -> Series: if keep not in ("first", "last", "all"): raise ValueError("'keep must be one of 'first', 'last', or 'all'") - if keep != "all": - validations.enforce_ordered(self, "nsmallest(keep != 'all')") return Series( block_ops.nsmallest(self._block, n, [self._value_column], keep=keep) ) - def isin(self, values) -> "Series": - if isinstance(values, Series): - return Series(self._block.isin(values._block)) - if isinstance(values, indexes.Index): - return Series(self._block.isin(values.to_series()._block)) + def isin(self, values) -> "Series" | None: if not _is_list_like(values): raise TypeError( "only list-like objects are allowed to be passed to " f"isin(), you passed a [{type(values).__name__}]" ) - return self._apply_unary_op( - ops.IsInOp(values=tuple(values), match_nulls=True) - ).fillna(value=False) + + return self._apply_unary_op(ops.IsInOp(values, match_nulls=True)).fillna( + value=False + ) def isna(self) -> "Series": return self._apply_unary_op(ops.isnull_op) @@ -1212,22 +544,17 @@ def __or__(self, other: bool | int | Series) -> Series: __ror__ = __or__ - def __xor__(self, other: bool | int | Series) -> Series: - return self._apply_binary_op(other, ops.xor_op) - - __rxor__ = __xor__ - - def __add__(self, other: float | int | pandas.Timedelta | Series) -> Series: + def __add__(self, other: float | int | Series) -> Series: return self.add(other) - def __radd__(self, other: float | int | pandas.Timedelta | Series) -> Series: + def __radd__(self, other: float | int | Series) -> Series: return self.radd(other) - def add(self, other: float | int | pandas.Timedelta | Series) -> Series: + def add(self, other: float | int | Series) -> Series: return self._apply_binary_op(other, ops.add_op) - def radd(self, other: float | int | pandas.Timedelta | Series) -> Series: - return self._apply_binary_op(other, ops.add_op, reverse=True) + def radd(self, other: float | int | Series) -> Series: + return self._apply_binary_op(other, ops.reverse(ops.add_op)) def __sub__(self, other: float | int | Series) -> Series: return self.sub(other) @@ -1235,11 +562,11 @@ def __sub__(self, other: float | int | Series) -> Series: def __rsub__(self, other: float | int | Series) -> Series: return self.rsub(other) - def sub(self, other) -> Series: + def sub(self, other: float | int | Series) -> Series: return self._apply_binary_op(other, ops.sub_op) - def rsub(self, other) -> Series: - return self._apply_binary_op(other, ops.sub_op, reverse=True) + def rsub(self, other: float | int | Series) -> Series: + return self._apply_binary_op(other, ops.reverse(ops.sub_op)) subtract = sub @@ -1253,37 +580,39 @@ def mul(self, other: float | int | Series) -> Series: return self._apply_binary_op(other, ops.mul_op) def rmul(self, other: float | int | Series) -> Series: - return self._apply_binary_op(other, ops.mul_op, reverse=True) + return self._apply_binary_op(other, ops.reverse(ops.mul_op)) multiply = mul - def __truediv__(self, other: float | int | pandas.Timedelta | Series) -> Series: + def __truediv__(self, other: float | int | Series) -> Series: return self.truediv(other) - def __rtruediv__(self, other: float | int | pandas.Timedelta | Series) -> Series: + def __rtruediv__(self, other: float | int | Series) -> Series: return self.rtruediv(other) - def truediv(self, other: float | int | pandas.Timedelta | Series) -> Series: + def truediv(self, other: float | int | Series) -> Series: return self._apply_binary_op(other, ops.div_op) - def rtruediv(self, other: float | int | pandas.Timedelta | Series) -> Series: - return self._apply_binary_op(other, ops.div_op, reverse=True) + def rtruediv(self, other: float | int | Series) -> Series: + return self._apply_binary_op(other, ops.reverse(ops.div_op)) + + div = truediv - div = divide = truediv + divide = truediv rdiv = rtruediv - def __floordiv__(self, other: float | int | pandas.Timedelta | Series) -> Series: + def __floordiv__(self, other: float | int | Series) -> Series: return self.floordiv(other) - def __rfloordiv__(self, other: float | int | pandas.Timedelta | Series) -> Series: + def __rfloordiv__(self, other: float | int | Series) -> Series: return self.rfloordiv(other) - def floordiv(self, other: float | int | pandas.Timedelta | Series) -> Series: + def floordiv(self, other: float | int | Series) -> Series: return self._apply_binary_op(other, ops.floordiv_op) - def rfloordiv(self, other: float | int | pandas.Timedelta | Series) -> Series: - return self._apply_binary_op(other, ops.floordiv_op, reverse=True) + def rfloordiv(self, other: float | int | Series) -> Series: + return self._apply_binary_op(other, ops.reverse(ops.floordiv_op)) def __pow__(self, other: float | int | Series) -> Series: return self.pow(other) @@ -1295,12 +624,12 @@ def pow(self, other: float | int | Series) -> Series: return self._apply_binary_op(other, ops.pow_op) def rpow(self, other: float | int | Series) -> Series: - return self._apply_binary_op(other, ops.pow_op, reverse=True) + return self._apply_binary_op(other, ops.reverse(ops.pow_op)) - def __lt__(self, other: float | int | str | Series) -> Series: + def __lt__(self, other: float | int | Series) -> Series: # type: ignore return self.lt(other) - def __le__(self, other: float | int | str | Series) -> Series: + def __le__(self, other: float | int | Series) -> Series: # type: ignore return self.le(other) def lt(self, other) -> Series: @@ -1309,10 +638,10 @@ def lt(self, other) -> Series: def le(self, other) -> Series: return self._apply_binary_op(other, ops.le_op) - def __gt__(self, other: float | int | str | Series) -> Series: + def __gt__(self, other: float | int | Series) -> Series: # type: ignore return self.gt(other) - def __ge__(self, other: float | int | str | Series) -> Series: + def __ge__(self, other: float | int | Series) -> Series: # type: ignore return self.ge(other) def gt(self, other) -> Series: @@ -1331,7 +660,7 @@ def mod(self, other) -> Series: # type: ignore return self._apply_binary_op(other, ops.mod_op) def rmod(self, other) -> Series: # type: ignore - return self._apply_binary_op(other, ops.mod_op, reverse=True) + return self._apply_binary_op(other, ops.reverse(ops.mod_op)) def divmod(self, other) -> Tuple[Series, Series]: # type: ignore # TODO(huanc): when self and other both has dtype int and other contains zeros, @@ -1343,28 +672,10 @@ def rdivmod(self, other) -> Tuple[Series, Series]: # type: ignore # the output should be dtype float, both floordiv and mod returns dtype int in this case. return (self.rfloordiv(other), self.rmod(other)) - def dot(self, other): - return (self * other).sum() - def __matmul__(self, other): - return self.dot(other) - - def __rmatmul__(self, other): - return self.dot(other) - - def combine_first(self, other: Series) -> Series: - result = self._apply_binary_op(other, ops.coalesce_op) - result.name = self.name - return result - - def update(self, other: Union[Series, Sequence, Mapping]) -> None: - result = self._apply_binary_op( - other, ops.coalesce_op, reverse=True, alignment="left" - ) - self._set_block(result._get_block()) + return (self * other).sum() - def __abs__(self) -> Series: - return self.abs() + dot = __matmul__ def abs(self) -> Series: return self._apply_unary_op(ops.abs_op) @@ -1373,8 +684,28 @@ def round(self, decimals=0) -> "Series": return self._apply_binary_op(decimals, ops.round_op) def corr(self, other: Series, method="pearson", min_periods=None) -> float: - # TODO(tbergeron): Validate early that both are numeric - # TODO(tbergeron): Handle partially-numeric columns + """ + Compute the correlation with the other Series. Non-number values are ignored in the + computation. + + Uses the "Pearson" method of correlation. Numbers are converted to float before + calculation, so the result may be unstable. + + Args: + other (Series): + The series with which this is to be correlated. + method (string, default "pearson"): + Correlation method to use - currently only "pearson" is supported. + min_periods (int, default None): + The minimum number of observations needed to return a result. Non-default values + are not yet supported, so a result will be returned for at least two observations. + + Returns: + float; Will return NaN if there are fewer than two numeric pairs, either series has a + variance or covariance of zero, or any input value is infinite. + """ + # TODO(kemppeterson): Validate early that both are numeric + # TODO(kemppeterson): Handle partially-numeric columns if method != "pearson": raise NotImplementedError( f"Only Pearson correlation is currently supported. {constants.FEEDBACK_LINK}" @@ -1383,13 +714,7 @@ def corr(self, other: Series, method="pearson", min_periods=None) -> float: raise NotImplementedError( f"min_periods not yet supported. {constants.FEEDBACK_LINK}" ) - return self._apply_binary_aggregation(other, agg_ops.CorrOp()) - - def autocorr(self, lag: int = 1) -> float: - return self.corr(self.shift(lag)) - - def cov(self, other: Series) -> float: - return self._apply_binary_aggregation(other, agg_ops.CovOp()) + return self._apply_corr_aggregation(other) def all(self) -> bool: return typing.cast(bool, self._apply_aggregation(agg_ops.all_op)) @@ -1425,11 +750,11 @@ def _central_moment(self, n: int) -> float: def agg(self, func: str | typing.Sequence[str]) -> scalars.Scalar | Series: if _is_list_like(func): - if self.dtype not in bigframes.dtypes.NUMERIC_BIGFRAMES_TYPES_PERMISSIVE: + if self.dtype not in bigframes.dtypes.NUMERIC_BIGFRAMES_TYPES: raise NotImplementedError( f"Multiple aggregations only supported on numeric series. {constants.FEEDBACK_LINK}" ) - aggregations = [agg_ops.lookup_agg_func(f)[0] for f in func] + aggregations = [agg_ops.lookup_agg_func(f) for f in func] return Series( self._block.summarize( [self._value_column], @@ -1437,14 +762,12 @@ def agg(self, func: str | typing.Sequence[str]) -> scalars.Scalar | Series: ) ) else: - return self._apply_aggregation(agg_ops.lookup_agg_func(func)[0]) - - aggregate = agg - def describe(self) -> Series: - from bigframes.pandas.core.methods import describe + return self._apply_aggregation( + agg_ops.lookup_agg_func(typing.cast(str, func)) + ) - return cast(Series, describe.describe(self, include="all")) + aggregate = agg def skew(self): count = self.count() @@ -1483,58 +806,41 @@ def kurt(self): def mode(self) -> Series: block = self._block # Approach: Count each value, return each value for which count(x) == max(counts)) - block = block.aggregate( + block, agg_ids = block.aggregate( by_column_ids=[self._value_column], - aggregations=( - agg_expressions.UnaryAggregation( - agg_ops.count_op, ex.deref(self._value_column) - ), - ), + aggregations=((self._value_column, agg_ops.count_op),), + as_index=False, ) - value_count_col_id = block.value_columns[0] + value_count_col_id = agg_ids[0] block, max_value_count_col_id = block.apply_window_op( value_count_col_id, agg_ops.max_op, - window_spec=windows.unbound(), + window_spec=bigframes.core.window_spec.WindowSpec(), ) block, is_mode_col_id = block.apply_binary_op( value_count_col_id, max_value_count_col_id, ops.eq_op, ) - block = block.filter_by_id(is_mode_col_id) - # use temporary name for reset_index to avoid collision, restore after dropping extra columns - block = ( - block.with_index_labels(["mode_temp_internal"]) - .order_by([order.ascending_over(self._value_column)]) - .reset_index(drop=False) + block = block.filter(is_mode_col_id) + mode_values_series = Series( + block.select_column(self._value_column).assign_label( + self._value_column, self.name + ) + ) + return typing.cast( + Series, mode_values_series.sort_values().reset_index(drop=True) ) - block = block.select_column(self._value_column).with_column_labels([self.name]) - mode_values_series = Series(block.select_column(self._value_column)) - return typing.cast(Series, mode_values_series) def mean(self) -> float: return typing.cast(float, self._apply_aggregation(agg_ops.mean_op)) - def median(self, *, exact: bool = True) -> float: + def median(self, *, exact: bool = False) -> float: if exact: - return typing.cast(float, self.quantile(0.5)) - else: - return typing.cast(float, self._apply_aggregation(agg_ops.median_op)) - - def quantile(self, q: Union[float, Sequence[float]] = 0.5) -> Union[Series, float]: - qs = tuple(q) if utils.is_list_like(q) else (q,) - result = block_ops.quantile(self._block, (self._value_column,), qs=qs) - if utils.is_list_like(q): - # Drop the first level, since only one column - result = result.with_column_labels(result.column_labels.droplevel(0)) - result, index_col = result.create_constant(self.name, None) - result = result.set_index([index_col]) - return Series( - result.transpose(original_row_index=pandas.Index([self.name])) + raise NotImplementedError( + f"Only approximate median is supported. {constants.FEEDBACK_LINK}" ) - else: - return cast(float, Series(result).to_pandas().squeeze()) + return typing.cast(float, self._apply_aggregation(agg_ops.median_op)) def sum(self) -> float: return typing.cast(float, self._apply_aggregation(agg_ops.sum_op)) @@ -1553,15 +859,6 @@ def __ne__(self, other: object) -> Series: # type: ignore def __invert__(self) -> Series: return self._apply_unary_op(ops.invert_op) - def __pos__(self) -> Series: - return self._apply_unary_op(ops.pos_op) - - def __neg__(self) -> Series: - return self._apply_unary_op(ops.neg_op) - - def __dir__(self) -> List[str]: - return dir(type(self)) + self._struct_fields - def eq(self, other: object) -> Series: # TODO: enforce stricter alignment return self._apply_binary_op(other, ops.eq_op) @@ -1570,80 +867,52 @@ def ne(self, other: object) -> Series: # TODO: enforce stricter alignment return self._apply_binary_op(other, ops.ne_op) - def items(self): - for batch_df in self._block.to_pandas_batches(): - assert batch_df.shape[1] == 1, ( - f"Expected 1 column in the dataframe, but got {batch_df.shape[1]}." - ) - for item in batch_df.squeeze(axis=1).items(): - yield item - - def _apply_callable(self, condition): - """ "Executes the possible callable condition as needed.""" - if callable(condition): - # When it's a bigframes function. - if isinstance(condition, bigframes.functions.Udf): - return self.apply(condition) - # When it's a plain Python function. - else: - return self.apply(condition, by_row=False) - - # When it's not a callable. - return condition - def where(self, cond, other=None): - cond = self._apply_callable(cond) - other = self._apply_callable(other) - value_id, cond_id, other_id, block = self._align3(cond, other) - block, result_id = block.project_expr( - ops.where_op.as_expr(value_id, cond_id, other_id) + block, result_id = block.apply_ternary_op( + value_id, cond_id, other_id, ops.where_op ) return Series(block.select_column(result_id).with_column_labels([self.name])) - def clip(self, lower=None, upper=None): + def clip(self, lower, upper): if lower is None and upper is None: return self if lower is None: - return self._apply_binary_op(upper, ops.minimum_op, alignment="left") + return self._apply_binary_op(upper, ops.clip_upper, alignment="left") if upper is None: - return self._apply_binary_op(lower, ops.maximum_op, alignment="left") - # special rule to coerce scalar string args to date - value_id, lower_id, upper_id, block = self._align3( - lower, upper, cast_scalars=(bigframes.dtypes.is_date_like(self.dtype)) - ) - block, result_id = block.project_expr( - ops.clip_op.as_expr(value_id, lower_id, upper_id), + return self._apply_binary_op(lower, ops.clip_lower, alignment="left") + value_id, lower_id, upper_id, block = self._align3(lower, upper) + block, result_id = block.apply_ternary_op( + value_id, lower_id, upper_id, ops.clip_op ) return Series(block.select_column(result_id).with_column_labels([self.name])) - @validations.requires_ordering() def argmax(self) -> int: block, row_nums = self._block.promote_offsets() block = block.order_by( [ - order.descending_over(self._value_column), - order.ascending_over(row_nums), + OrderingColumnReference( + self._value_column, direction=OrderingDirection.DESC + ), + OrderingColumnReference(row_nums), ] ) return typing.cast( scalars.Scalar, Series(block.select_column(row_nums)).iloc[0] ) - @validations.requires_ordering() def argmin(self) -> int: block, row_nums = self._block.promote_offsets() block = block.order_by( [ - order.ascending_over(self._value_column), - order.ascending_over(row_nums), + OrderingColumnReference(self._value_column), + OrderingColumnReference(row_nums), ] ) return typing.cast( scalars.Scalar, Series(block.select_column(row_nums)).iloc[0] ) - @validations.requires_index def unstack(self, level: LevelsType = -1): if isinstance(level, int) or isinstance(level, str): level = [level] @@ -1667,43 +936,41 @@ def unstack(self, level: LevelsType = -1): ) return bigframes.dataframe.DataFrame(pivot_block) - @validations.requires_index def idxmax(self) -> blocks.Label: block = self._block.order_by( [ - order.descending_over(self._value_column), + OrderingColumnReference( + self._value_column, direction=OrderingDirection.DESC + ), *[ - order.ascending_over(idx_col) + OrderingColumnReference(idx_col) for idx_col in self._block.index_columns ], ] ) block = block.slice(0, 1) - return indexes.Index(block).to_pandas()[0] + return indexes.Index._from_block(block).to_pandas()[0] - @validations.requires_index def idxmin(self) -> blocks.Label: block = self._block.order_by( [ - order.ascending_over(self._value_column), + OrderingColumnReference(self._value_column), *[ - order.ascending_over(idx_col) + OrderingColumnReference(idx_col) for idx_col in self._block.index_columns ], ] ) block = block.slice(0, 1) - return indexes.Index(block).to_pandas()[0] + return indexes.Index._from_block(block).to_pandas()[0] @property - @validations.requires_ordering() def is_monotonic_increasing(self) -> bool: return typing.cast( bool, self._block.is_monotonic_increasing(self._value_column) ) @property - @validations.requires_ordering() def is_monotonic_decreasing(self) -> bool: return typing.cast( bool, self._block.is_monotonic_decreasing(self._value_column) @@ -1726,23 +993,12 @@ def __getitem__(self, indexer): if isinstance(indexer, Series): (left, right, block) = self._align(indexer, "left") block = block.filter(right) - block = block.select_column(left.id.name) + block = block.select_column(left) return Series(block) return self.loc[indexer] def __getattr__(self, key: str): - # Protect against recursion errors with uninitialized Series objects. - # We use "_block" attribute to check whether the instance is initialized. - # See: - # https://github.com/googleapis/python-bigquery-dataframes/issues/728 - # and - # https://nedbatchelder.com/blog/201010/surprising_getattr_recursion.html - if key == "_block": - raise AttributeError(key) - elif hasattr(pandas.Series, key): - log_adapter.submit_pandas_labels( - self._block.session.bqclient, self.__class__.__name__, key - ) + if hasattr(pandas.Series, key): raise AttributeError( textwrap.dedent( f""" @@ -1751,32 +1007,25 @@ def __getattr__(self, key: str): """ ) ) - elif key in self._struct_fields: - return self.struct.field(key) else: raise AttributeError(key) - def __setitem__(self, key, value) -> None: - """Set item using direct assignment, delegating to .loc indexer.""" - self.loc[key] = value + def _align3(self, other1: Series | scalars.Scalar, other2: Series | scalars.Scalar, how="left") -> tuple[str, str, str, blocks.Block]: # type: ignore + """Aligns the series value with 2 other scalars or series objects. Returns new values and joined tabled expression.""" + values, index = self._align_n([other1, other2], how) + return (values[0], values[1], values[2], index) - def _apply_aggregation( - self, op: agg_ops.UnaryAggregateOp | agg_ops.NullaryAggregateOp - ) -> Any: + def _apply_aggregation(self, op: agg_ops.AggregateOp) -> Any: return self._block.get_stat(self._value_column, op) def _apply_window_op( - self, op: agg_ops.UnaryWindowOp, window_spec: windows.WindowSpec + self, op: agg_ops.WindowOp, window_spec: bigframes.core.window_spec.WindowSpec ): block = self._block block, result_id = block.apply_window_op( self._value_column, op, window_spec=window_spec, result_label=self.name ) - result = Series(block.select_column(result_id)) - if op.skips_nulls: - return result.where(self.notna(), None) - else: - return result + return Series(block.select_column(result_id)) def value_counts( self, @@ -1791,161 +1040,59 @@ def value_counts( [self._value_column], normalize=normalize, ascending=ascending, - drop_na=dropna, + dropna=dropna, ) return Series(block) - @typing.overload # type: ignore[override] - def sort_values( - self, - *, - axis=..., - inplace: Literal[True] = ..., - ascending: bool | typing.Sequence[bool] = ..., - kind: str | None = ..., - na_position: typing.Literal["first", "last"] = ..., - ) -> None: ... - - @typing.overload - def sort_values( - self, - *, - axis=..., - inplace: Literal[False] = ..., - ascending: bool | typing.Sequence[bool] = ..., - kind: str | None = ..., - na_position: typing.Literal["first", "last"] = ..., - ) -> Series: ... - def sort_values( - self, - *, - axis=0, - inplace: bool = False, - ascending=True, - kind: str | None = None, - na_position: typing.Literal["first", "last"] = "last", - ) -> Optional[Series]: - if axis != 0 and axis != "index": - raise ValueError(f"No axis named {axis} for object type Series") + self, *, axis=0, ascending=True, kind: str = "quicksort", na_position="last" + ) -> Series: if na_position not in ["first", "last"]: raise ValueError("Param na_position must be one of 'first' or 'last'") - is_stable = (kind or constants.DEFAULT_SORT_KIND) in constants.STABLE_SORT_KINDS + direction = OrderingDirection.ASC if ascending else OrderingDirection.DESC block = self._block.order_by( [ - order.ascending_over(self._value_column, (na_position == "last")) - if ascending - else order.descending_over(self._value_column, (na_position == "last")) + OrderingColumnReference( + self._value_column, + direction=direction, + na_last=(na_position == "last"), + ) ], - stable=is_stable, + stable=kind in STABLE_SORTS, ) - if inplace: - self._set_block(block) - return None - else: - return Series(block) + return Series(block) - @typing.overload # type: ignore[override] - def sort_index( - self, - *, - axis=..., - inplace: Literal[False] = ..., - ascending=..., - kind: str | None = ..., - na_position=..., - ) -> Series: ... - - @typing.overload - def sort_index( - self, - *, - axis=0, - inplace: Literal[True] = ..., - ascending=..., - kind: str | None = ..., - na_position=..., - ) -> None: ... - - @validations.requires_index - def sort_index( - self, - *, - axis=0, - inplace: bool = False, - ascending=True, - kind: str | None = None, - na_position="last", - ) -> Optional[Series]: + def sort_index(self, *, axis=0, ascending=True, na_position="last") -> Series: # TODO(tbergeron): Support level parameter once multi-index introduced. - if axis != 0 and axis != "index": - raise ValueError(f"No axis named {axis} for object type Series") if na_position not in ["first", "last"]: raise ValueError("Param na_position must be one of 'first' or 'last'") block = self._block + direction = OrderingDirection.ASC if ascending else OrderingDirection.DESC na_last = na_position == "last" ordering = [ - order.ascending_over(column, na_last) - if ascending - else order.descending_over(column, na_last) + OrderingColumnReference(column, direction=direction, na_last=na_last) for column in block.index_columns ] - is_stable = (kind or constants.DEFAULT_SORT_KIND) in constants.STABLE_SORT_KINDS - block = block.order_by(ordering, stable=is_stable) - if inplace: - self._set_block(block) - return None - else: - return Series(block) - - @validations.requires_ordering() - def rolling( - self, - window: int | pandas.Timedelta | numpy.timedelta64 | datetime.timedelta | str, - min_periods: int | None = None, - closed: Literal["right", "left", "both", "neither"] = "right", - ) -> bigframes.core.window.Window: - if isinstance(window, int): - # Rows rolling - window_spec = windows.WindowSpec( - bounds=windows.RowsWindowBounds.from_window_size(window, closed), - min_periods=window if min_periods is None else min_periods, - ) - return bigframes.core.window.Window( - self._block, window_spec, self._block.value_columns, is_series=True - ) + block = block.order_by(ordering) + return Series(block) - return rolling.create_range_window( - block=self._block, - window=window, - min_periods=min_periods, - closed=closed, - is_series=True, + def rolling(self, window: int, min_periods=None) -> bigframes.core.window.Window: + # To get n size window, need current row and n-1 preceding rows. + window_spec = bigframes.core.window_spec.WindowSpec( + preceding=window - 1, following=0, min_periods=min_periods or window + ) + return bigframes.core.window.Window( + self._block, window_spec, self._block.value_columns, is_series=True ) - @validations.requires_ordering() def expanding(self, min_periods: int = 1) -> bigframes.core.window.Window: - window_spec = windows.cumulative_rows(min_periods=min_periods) + window_spec = bigframes.core.window_spec.WindowSpec( + following=0, min_periods=min_periods + ) return bigframes.core.window.Window( self._block, window_spec, self._block.value_columns, is_series=True ) - def pipe( - self, - func: Union[Callable[..., U], tuple[Callable[..., U], str]], - *args, - **kwargs, - ) -> U: - import bigframes_vendored.pandas.core.common as common - - return common.pipe(self, func, *args, **kwargs) - - def get(self, key, default=None): - try: - return self[key] - except (KeyError, ValueError, IndexError): - return default - def groupby( self, by: typing.Union[ @@ -1974,24 +1121,17 @@ def groupby( else: raise TypeError("You have to supply one of 'by' and 'level'") - @validations.requires_index def _groupby_level( self, level: int | str | typing.Sequence[int] | typing.Sequence[str], dropna: bool = True, ) -> bigframes.core.groupby.SeriesGroupBy: - if utils.is_list_like(level): - by_key_is_singular = False - else: - by_key_is_singular = True - return groupby.SeriesGroupBy( self._block, self._value_column, by_col_ids=self._resolve_levels(level), value_name=self.name, dropna=dropna, - by_key_is_singular=by_key_is_singular, ) def _groupby_values( @@ -2003,29 +1143,27 @@ def _groupby_values( ) -> bigframes.core.groupby.SeriesGroupBy: if not isinstance(by, Series) and _is_list_like(by): by = list(by) - by_key_is_singular = False else: by = [typing.cast(typing.Union[blocks.Label, Series], by)] - by_key_is_singular = True block = self._block grouping_cols: typing.Sequence[str] = [] value_col = self._value_column for key in by: if isinstance(key, Series): - ( - block, - ( - get_column_left, - get_column_right, - ), - ) = block.join(key._block, how="inner" if dropna else "left") - - value_col = get_column_left[value_col] + combined_index, ( + get_column_left, + get_column_right, + ) = block.index.join( + key._block.index, how="inner" if dropna else "left" + ) + + value_col = get_column_left[self._value_column] grouping_cols = [ *[get_column_left[value] for value in grouping_cols], get_column_right[key._value_column], ] + block = combined_index._block else: # Interpret as index level matches = block.index_name_to_col_id.get(key, []) @@ -2041,155 +1179,23 @@ def _groupby_values( by_col_ids=grouping_cols, value_name=self.name, dropna=dropna, - by_key_is_singular=by_key_is_singular, ) - def apply( - self, - func, - by_row: typing.Union[typing.Literal["compat"], bool] = "compat", - *, - args: typing.Tuple = (), - ) -> Series: - # Note: This signature differs from pandas.Series.apply. Specifically, - # `args` is keyword-only and `by_row` is a custom parameter here. Full - # alignment would involve breaking changes. However, given that by_row - # is not frequently used, we defer any such changes until there is a - # clear need based on user feedback. - # - # See pandas docs for reference: - # https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.Series.apply.html - - # TODO(shobs, b/274645634): Support convert_dtype, **kwargs + def apply(self, func) -> Series: + # TODO(shobs, b/274645634): Support convert_dtype, args, **kwargs # is actually a ternary op + # Reproject as workaround to applying filter too late. This forces the filter + # to be applied before passing data to remote function, protecting from bad + # inputs causing errors. + reprojected_series = Series(self._block._force_reproject()) + return reprojected_series._apply_unary_op(ops.RemoteFunctionOp(func)) - if by_row not in ["compat", False]: - raise ValueError("Param by_row must be one of 'compat' or False") - - if not callable(func) and not isinstance(func, numpy.ufunc): - raise ValueError( - "Only a ufunc (a function that applies to the entire Series) or" - " a BigFrames BigQuery function that only works on single values" - " are supported." - ) - - # Highest priority: try to map directly to an operator, for eg numpy - # ufuncs, or simple arithmetic/logic operators. - bf_op = python_ops.python_callable_to_op(func) - if bf_op and isinstance(bf_op, ops.UnaryOp): - return self._apply_unary_op(bf_op) - - if by_row: - from bigframes._config import options - - enable_transpile = options.experiments.enable_python_transpiler - return self._apply_by_row( - func, args=args, transpile_enabled=enable_transpile - ) - try: - return func(self) # type: ignore - except Exception as ex: - # This could happen if any of the operators in func is not - # supported on a Series. Let's guide the customer to use a - # bigquery function instead - if hasattr(ex, "message"): - ex.message += f"\n{_bigquery_function_recommendation_message}" - raise - - def _apply_by_row( - self, - func: typing.Callable, - args: typing.Tuple = (), - transpile_enabled: bool = False, - ) -> Series: - """ - Apply callable or deployed udf row-wise on the series. - """ - if not callable(func): - raise ValueError( - "Expected a callable function. If you meant to use a BigQuery function, please wrap it with bigframes.pandas.udf(...)" - ) - try: - expr = ops.func_to_expr(func) - # We get this message even if transpiler could have in theory translated it. - except Exception: - raise ValueError( - "You have passed a functi1on as-is. If your intention is to " - "apply this function in a vectorized way (i.e. to the " - "entire Series as a whole, and you are sure that it " - "performs only the operations that are implemented for a " - "Series (e.g. a chain of arithmetic/logical operations, " - "such as `def foo(s): return s % 2 == 1`), please also " - "specify `by_row=False`. If your function contains " - "arbitrary code, it can only be applied to every element " - "in the Series individually, in which case you must " - "convert it to a BigFrames BigQuery function using " - "`bigframes.pandas.udf`, " - "or `bigframes.pandas.remote_function` before passing." - ) - - result_series = self._apply_callable_expr(expr, args) - # TODO(jialuo): Investigate why `_apply_nary_op` drops the series - # `name`. Manually reassigning it here as a temporary fix. - result_series.name = self.name - - return result_series - - def combine( - self, - other, - func, - ) -> Series: - if not callable(func) and not isinstance(func, numpy.ufunc): - raise ValueError( - "Only a ufunc (a function that applies to the entire Series) or" - " a BigFrames BigQuery function that only works on single values" - " are supported." - ) - - from bigframes._config import options - - if isinstance(func, bigframes.functions.Udf) or ( - options.experiments.enable_python_transpiler and callable(func) - ): - result_series = self._apply_callable_expr(ops.func_to_expr(func), (other,)) - if hasattr(other, "name") and other.name != self._name: # type: ignore - result_series.name = None - else: - result_series.name = self.name - return result_series - - bf_op = python_ops.python_callable_to_op(func) - if bf_op and isinstance(bf_op, ops.BinaryOp): - result_series = self._apply_binary_op(other, bf_op) - return result_series - - # Keep this in sync with .apply - try: - return func(self, other) - except Exception as ex: - # This could happen if any of the operators in func is not - # supported on a Series. Let's guide the customer to use a - # bigquery function instead - if hasattr(ex, "message"): - ex.message += f"\n{_bigquery_function_recommendation_message}" - raise - - @validations.requires_index def add_prefix(self, prefix: str, axis: int | str | None = None) -> Series: return Series(self._get_block().add_prefix(prefix)) - @validations.requires_index def add_suffix(self, suffix: str, axis: int | str | None = None) -> Series: return Series(self._get_block().add_suffix(suffix)) - def take( - self, indices: typing.Sequence[int], axis: int | str | None = 0, **kwargs - ) -> Series: - if not utils.is_list_like(indices): - raise ValueError("indices should be a list-like object.") - return typing.cast(Series, self.iloc[indices]) - def filter( self, items: typing.Optional[typing.Iterable] = None, @@ -2205,46 +1211,47 @@ def filter( ) if len(self._block.index_columns) > 1: raise NotImplementedError( - f"Method filter does not support rows multiindex. {constants.FEEDBACK_LINK}" + "Method filter does not support rows multiindex. {constants.FEEDBACK_LINK}" ) if (like is not None) or (regex is not None): block = self._block block, label_string_id = block.apply_unary_op( self._block.index_columns[0], - ops.AsTypeOp(to_type=pandas.StringDtype(storage="pyarrow")), + ops.AsTypeOp(pandas.StringDtype(storage="pyarrow")), ) if like is not None: block, mask_id = block.apply_unary_op( - label_string_id, ops.StrContainsOp(pat=like) + label_string_id, ops.ContainsStringOp(pat=like) ) else: # regex assert regex is not None block, mask_id = block.apply_unary_op( - label_string_id, ops.StrContainsRegexOp(pat=regex) + label_string_id, ops.ContainsRegexOp(pat=regex) ) - block = block.filter_by_id(mask_id) + block = block.filter(mask_id) block = block.select_columns([self._value_column]) return Series(block) elif items is not None: # Behavior matches pandas 2.1+, older pandas versions would reindex block = self._block block, mask_id = block.apply_unary_op( - self._block.index_columns[0], ops.IsInOp(values=tuple(items)) + self._block.index_columns[0], ops.IsInOp(values=list(items)) ) - block = block.filter_by_id(mask_id) + block = block.filter(mask_id) block = block.select_columns([self._value_column]) return Series(block) else: raise ValueError("Need to provide 'items', 'like', or 'regex'") - @validations.requires_index def reindex(self, index=None, *, validate: typing.Optional[bool] = None): if validate and not self.index.is_unique: raise ValueError("Original index must be unique to reindex") keep_original_names = False if isinstance(index, indexes.Index): - new_indexer = bigframes.dataframe.DataFrame(data=index._block)[[]] + new_indexer = bigframes.dataframe.DataFrame(data=index._data._get_block())[ + [] + ] else: if not isinstance(index, pandas.Index): keep_original_names = True @@ -2267,7 +1274,6 @@ def reindex(self, index=None, *, validate: typing.Optional[bool] = None): )._block return Series(result_block) - @validations.requires_index def reindex_like(self, other: Series, *, validate: typing.Optional[bool] = None): return self.reindex(other.index, validate=validate) @@ -2275,21 +1281,8 @@ def drop_duplicates(self, *, keep: str = "first") -> Series: block = block_ops.drop_duplicates(self._block, (self._value_column,), keep) return Series(block) - def unique(self, keep_order=True) -> Series: - if keep_order: - validations.enforce_ordered(self, "unique(keep_order != False)") - return self.drop_duplicates() - block = self._block.aggregate( - [ - agg_expressions.UnaryAggregation( - agg_ops.AnyValueOp(), ex.deref(self._value_column) - ) - ], - [self._value_column], - column_labels=self._block.column_labels, - dropna=False, - ) - return Series(block.reset_index()) + def unique(self) -> Series: + return self.drop_duplicates() def duplicated(self, keep: str = "first") -> Series: block, indicator = block_ops.indicate_duplicates( @@ -2302,8 +1295,9 @@ def duplicated(self, keep: str = "first") -> Series: ) def mask(self, cond, other=None) -> Series: - cond = self._apply_callable(cond) - other = self._apply_callable(other) + if callable(cond): + cond = self.apply(cond) + if not isinstance(cond, Series): raise TypeError( f"Only bigframes series condition is supported, received {type(cond).__name__}. " @@ -2312,106 +1306,43 @@ def mask(self, cond, other=None) -> Series: return self.where(~cond, other) def to_frame(self, name: blocks.Label = None) -> bigframes.dataframe.DataFrame: - provided_name = name if name is not None else self.name + provided_name = name if name else self.name # To be consistent with Pandas, it assigns 0 as the column name if missing. 0 is the first element of RangeIndex. - column_names: List[blocks.Label] - if provided_name is None or pandas.isna([cast(Any, provided_name)])[0]: - column_names = [0] - else: - column_names = [provided_name] - block = self._block.with_column_labels(column_names) + block = self._block.with_column_labels( + [provided_name] if provided_name else ["0"] + ) return bigframes.dataframe.DataFrame(block) - def to_csv( - self, - path_or_buf=None, - sep=",", - *, - header: bool = True, - index: bool = True, - allow_large_results: Optional[bool] = None, - ) -> Optional[str]: - if utils.is_gcs_path(path_or_buf): - return self.to_frame().to_csv( - path_or_buf, - sep=sep, - header=header, - index=index, - allow_large_results=allow_large_results, - ) - else: - pd_series = self.to_pandas(allow_large_results=allow_large_results) - return pd_series.to_csv( - path_or_buf=path_or_buf, sep=sep, header=header, index=index - ) + def to_csv(self, path_or_buf=None, **kwargs) -> typing.Optional[str]: + # TODO(b/280651142): Implement version that leverages bq export native csv support to bypass local pandas step. + return self.to_pandas().to_csv(path_or_buf, **kwargs) - def to_dict( - self, - into: type[dict] = dict, - *, - allow_large_results: Optional[bool] = None, - ) -> typing.Mapping: - return typing.cast( - dict, - self.to_pandas(allow_large_results=allow_large_results).to_dict(into=into), - ) # type: ignore + def to_dict(self, into: type[dict] = dict) -> typing.Mapping: + return typing.cast(dict, self.to_pandas().to_dict(into)) - def to_excel( - self, excel_writer, sheet_name="Sheet1", *, allow_large_results=None, **kwargs - ) -> None: - return self.to_pandas(allow_large_results=allow_large_results).to_excel( - excel_writer, sheet_name=sheet_name, **kwargs - ) + def to_excel(self, excel_writer, sheet_name="Sheet1", **kwargs) -> None: + return self.to_pandas().to_excel(excel_writer, sheet_name, **kwargs) def to_json( self, path_or_buf=None, - orient: Optional[ - typing.Literal["split", "records", "index", "columns", "values", "table"] - ] = None, - *, - lines: bool = False, - index: bool = True, - allow_large_results: Optional[bool] = None, - ) -> Optional[str]: - if utils.is_gcs_path(path_or_buf): - return self.to_frame().to_json( - path_or_buf=path_or_buf, - orient=orient, - lines=lines, - index=index, - allow_large_results=allow_large_results, - ) - else: - pd_series = self.to_pandas(allow_large_results=allow_large_results) - # Pandas Series.to_json only supports a subset of orients, but bigframes Series.to_json allows all of them. - return pd_series.to_json( - path_or_buf=path_or_buf, - orient=orient, # type: ignore[arg-type] - lines=lines, - index=index, # type: ignore - ) + orient: typing.Literal[ + "split", "records", "index", "columns", "values", "table" + ] = "columns", + **kwargs, + ) -> typing.Optional[str]: + # TODO(b/280651142): Implement version that leverages bq export native csv support to bypass local pandas step. + return self.to_pandas().to_json(path_or_buf, **kwargs) def to_latex( - self, - buf=None, - columns=None, - header=True, - index=True, - *, - allow_large_results=None, - **kwargs, + self, buf=None, columns=None, header=True, index=True, **kwargs ) -> typing.Optional[str]: - return self.to_pandas(allow_large_results=allow_large_results).to_latex( + return self.to_pandas().to_latex( buf, columns=columns, header=header, index=index, **kwargs ) - def tolist( - self, - *, - allow_large_results: Optional[bool] = None, - ) -> _list: - return self.to_pandas(allow_large_results=allow_large_results).to_list() + def tolist(self) -> list: + return self.to_pandas().to_list() to_list = tolist @@ -2420,36 +1351,19 @@ def to_markdown( buf: typing.IO[str] | None = None, mode: str = "wt", index: bool = True, - *, - allow_large_results: Optional[bool] = None, **kwargs, ) -> typing.Optional[str]: - return self.to_pandas(allow_large_results=allow_large_results).to_markdown( - buf, mode=mode, index=index, **kwargs - ) # type: ignore + return self.to_pandas().to_markdown(buf, mode=mode, index=index, **kwargs) # type: ignore def to_numpy( - self, - dtype=None, - copy=False, - na_value=pd_ext.no_default, - *, - allow_large_results=None, - **kwargs, + self, dtype=None, copy=False, na_value=None, **kwargs ) -> numpy.ndarray: - return self.to_pandas(allow_large_results=allow_large_results).to_numpy( - dtype, copy, na_value, **kwargs - ) + return self.to_pandas().to_numpy(dtype, copy, na_value, **kwargs) - def __array__(self, dtype=None, copy: Optional[bool] = None) -> numpy.ndarray: - if copy is False: - raise ValueError("Cannot convert to array without copy.") - return self.to_numpy(dtype=dtype) + __array__ = to_numpy - def to_pickle(self, path, *, allow_large_results=None, **kwargs) -> None: - return self.to_pandas(allow_large_results=allow_large_results).to_pickle( - path, **kwargs - ) + def to_pickle(self, path, **kwargs) -> None: + return self.to_pandas().to_pickle(path, **kwargs) def to_string( self, @@ -2463,10 +1377,8 @@ def to_string( name=False, max_rows=None, min_rows=None, - *, - allow_large_results=None, ) -> typing.Optional[str]: - return self.to_pandas(allow_large_results=allow_large_results).to_string( + return self.to_pandas().to_string( buf, na_rep, float_format, @@ -2479,12 +1391,8 @@ def to_string( min_rows, ) - def to_xarray( - self, - *, - allow_large_results: Optional[bool] = None, - ): - return self.to_pandas(allow_large_results=allow_large_results).to_xarray() + def to_xarray(self): + return self.to_pandas().to_xarray() def _throw_if_index_contains_duplicates( self, error_message: typing.Optional[str] = None @@ -2499,7 +1407,7 @@ def _throw_if_index_contains_duplicates( def map( self, - arg: typing.Union[Mapping, Series, Callable], + arg: typing.Union[Mapping, Series], na_action: Optional[str] = None, *, verify_integrity: bool = False, @@ -2516,78 +1424,19 @@ def map( map_df = map_df.rename(columns={arg.name: self.name}) elif isinstance(arg, Mapping): map_df = bigframes.dataframe.DataFrame( - {"keys": list(arg.keys()), self.name: list(arg.values())}, # type: ignore + {"keys": list(arg.keys()), self.name: list(arg.values())}, session=self._get_block().expr.session, ) map_df = map_df.set_index("keys") elif callable(arg): - # This is for remote function and managed funtion. - from bigframes._config import options - - enable_transpile = options.experiments.enable_python_transpiler - return self._apply_by_row(arg, transpile_enabled=enable_transpile) + return self.apply(arg) else: # Mirroring pandas, call the uncallable object arg() # throws TypeError: object is not callable self_df = self.to_frame(name="series") result_df = self_df.join(map_df, on="series") - result = cast(Series, result_df[self.name]) - result.name = self.name - return result - - @validations.requires_ordering() - def sample( - self, - n: Optional[int] = None, - frac: Optional[float] = None, - *, - random_state: Optional[int] = None, - sort: Optional[bool | Literal["random"]] = "random", - ) -> Series: - if n is not None and frac is not None: - raise ValueError("Only one of 'n' or 'frac' parameter can be specified.") - - ns = (n,) if n is not None else () - fracs = (frac,) if frac is not None else () - return Series( - self._block.split(ns=ns, fracs=fracs, random_state=random_state, sort=sort)[ - 0 - ] - ) - - def explode(self, *, ignore_index: Optional[bool] = False) -> Series: - return Series( - self._block.explode( - column_ids=[self._value_column], ignore_index=ignore_index - ) - ) - - @validations.requires_ordering() - def resample( - self, - rule: str, - *, - closed: Optional[Literal["right", "left"]] = None, - label: Optional[Literal["right", "left"]] = None, - level: Optional[LevelsType] = None, - origin: Union[ - Union[ - pandas.Timestamp, datetime.datetime, numpy.datetime64, int, float, str - ], - Literal["epoch", "start", "start_day", "end", "end_day"], - ] = "start_day", - ) -> bigframes.core.groupby.SeriesGroupBy: - block = self._block._generate_resample_label( - rule=rule, - closed=closed, - label=label, - on=None, - level=level, - origin=origin, - ) - series = Series(block) - return series.groupby(level=0) + return result_df[self.name] def __array_ufunc__( self, ufunc: numpy.ufunc, method: str, *inputs, **kwargs @@ -2606,300 +1455,30 @@ def __array_ufunc__( if inputs[0] is self: return self._apply_binary_op(inputs[1], binop) else: - return self._apply_binary_op(inputs[0], binop, reverse=True) + return self._apply_binary_op(inputs[0], ops.reverse(binop)) return NotImplemented + # Keep this at the bottom of the Series class to avoid + # confusing type checker by overriding str @property - def plot(self): - return plotting.PlotAccessor(self) - - def hist( - self, by: typing.Optional[typing.Sequence[str]] = None, bins: int = 10, **kwargs - ): - return self.plot.hist(by=by, bins=bins, **kwargs) - - def line( - self, - x: typing.Optional[typing.Hashable] = None, - y: typing.Optional[typing.Hashable] = None, - **kwargs, - ): - return self.plot.line(x=x, y=y, **kwargs) - - def area( - self, - x: typing.Optional[typing.Hashable] = None, - y: typing.Optional[typing.Hashable] = None, - stacked: bool = True, - **kwargs, - ): - return self.plot.area(x=x, y=y, stacked=stacked, **kwargs) - - def bar( - self, - x: typing.Optional[typing.Hashable] = None, - y: typing.Optional[typing.Hashable] = None, - **kwargs, - ): - return self.plot.bar(x=x, y=y, **kwargs) + def str(self) -> strings.StringMethods: + return strings.StringMethods(self._block) def _slice( self, start: typing.Optional[int] = None, stop: typing.Optional[int] = None, step: typing.Optional[int] = None, - ) -> Series: - return Series( - self._block.slice( - start=start, stop=stop, step=step if (step is not None) else 1 - ).select_column(self._value_column), - ) - - def cache(self): - """ - Materializes the Series to a temporary table. - - Useful if the series will be used multiple times, as this will avoid recomputating the shared intermediate value. - - Returns: - Series: Self - """ - # Do not use session-aware cashing if user-requested - return self._cached(force=True, session_aware=False) - - def _cached(self, *, force: bool = True, session_aware: bool = True) -> Series: - self._block.cached(force=force, session_aware=session_aware) - return self - - # Keep this at the bottom of the Series class to avoid - # confusing type checker by overriding str - @property - def str(self) -> strings.StringMethods: - import bigframes.operations.strings as strings - - return strings.StringMethods(self) - - @property - def _value_column(self) -> __builtins__.str: - return self._block.value_columns[0] - - @property - def _name(self) -> blocks.Label: - return self._block.column_labels[0] - - @property - def _dtype(self): - return self._block.dtypes[0] - - def _set_block(self, block: blocks.Block): - self._block = block - - def _get_block(self) -> blocks.Block: - return self._block - - def _apply_unary_op( - self, - op: ops.UnaryOp, - ) -> Series: - """Applies a unary operator to the series.""" - block, result_id = self._block.apply_unary_op( - self._value_column, - op, - ) - return Series(block.select_column(result_id), name=self.name) # type: ignore - - def _apply_binary_op( - self, - other: typing.Any, - op: ops.BinaryOp, - alignment: typing.Literal["outer", "left"] = "outer", - reverse: bool = False, - ) -> Series: - """Applies a binary operator to the series and other.""" - if bigframes.core.convert.can_convert_to_series(other): - self_index = indexes.Index(self._block) - other_series = bigframes.core.convert.to_bf_series( - other, self_index, self._block.session - ) - (self_col, other_col, block) = self._align(other_series, how=alignment) - - name = self._name - # Drop name if both objects have name attr, but they don't match - if ( - hasattr(other, "name") - and other_series.name != self._name - and alignment == "outer" - ): - name = None - expr = op.as_expr( - other_col if reverse else self_col, self_col if reverse else other_col - ) - block, result_id = block.project_expr(expr) - block = block.select_column(result_id).with_column_labels([name]) - return Series(block) # type: ignore - - else: # Scalar binop - name = self._name - expr = op.as_expr( - ex.const(other) if reverse else self._value_column, - self._value_column if reverse else ex.const(other), - ) - block, result_id = self._block.project_expr(expr) - block = block.select_column(result_id).with_column_labels([name]) - return Series(block) # type: ignore - - def _apply_nary_op( - self, - op: ops.NaryOp, - others: Sequence[typing.Union[Series, scalars.Scalar]], - ignore_self=False, - ): - """Applies an n-ary operator to the series and others.""" - values, block = self._align_n( - others, ignore_self=ignore_self, cast_scalars=False - ) - block, result_id = block.project_expr(op.as_expr(*values)) - return Series(block.select_column(result_id).with_column_labels([None])) - - def _apply_callable_expr( - self, - callable_expr: bigframes.operations.to_op.CallableExpression, - others: Sequence[typing.Union[Series, scalars.Scalar]], - ignore_self=False, - ): - """Applies a CallableExpression to the series and others.""" - values, block = self._align_n( - others, ignore_self=ignore_self, cast_scalars=False - ) - block, result_id = block.project_expr(callable_expr.apply(*values)) - return Series(block.select_column(result_id).with_column_labels([None])) - - def _apply_binary_aggregation( - self, other: Series, stat: agg_ops.BinaryAggregateOp - ) -> float: - (left, right, block) = self._align(other, how="outer") - assert isinstance(left, ex.DerefOp) - assert isinstance(right, ex.DerefOp) - return block.get_binary_stat(left.id.name, right.id.name, stat) - - AlignedExprT = Union[ex.ScalarConstantExpression, ex.DerefOp, ex.OmittedArg] - - @typing.overload - def _align( - self, other: Series, how="outer" - ) -> tuple[ - ex.DerefOp, - ex.DerefOp, - blocks.Block, - ]: ... - - @typing.overload - def _align( - self, other: typing.Union[Series, scalars.Scalar], how="outer" - ) -> tuple[ - ex.DerefOp, - AlignedExprT, - blocks.Block, - ]: ... - - def _align( - self, other: typing.Union[Series, scalars.Scalar], how="outer" - ) -> tuple[ - ex.DerefOp, - AlignedExprT, - blocks.Block, - ]: - """Aligns the series value with another scalar or series object. Returns new left column id, right column id and joined tabled expression.""" - values, block = self._align_n( - [ - other, - ], - how, - ) - return (typing.cast(ex.DerefOp, values[0]), values[1], block) - - def _align3( - self, - other1: Series | scalars.Scalar, - other2: Series | scalars.Scalar, - how="left", - cast_scalars: bool = True, - ) -> tuple[ex.DerefOp, AlignedExprT, AlignedExprT, blocks.Block]: # type: ignore - """Aligns the series value with 2 other scalars or series objects. Returns new values and joined tabled expression.""" - values, index = self._align_n([other1, other2], how, cast_scalars=cast_scalars) - return ( - typing.cast(ex.DerefOp, values[0]), - values[1], - values[2], - index, + ) -> bigframes.series.Series: + return bigframes.series.Series( + self._block.slice(start=start, stop=stop, step=step).select_column( + self._value_column + ), ) - def _align_n( - self, - others: typing.Sequence[ - typing.Union[Series, bigframes.core.col.Expression, scalars.Scalar] - ], - how="outer", - ignore_self=False, - cast_scalars: bool = False, - ) -> tuple[ - typing.Sequence[Union[ex.ScalarConstantExpression, ex.DerefOp, ex.OmittedArg]], - blocks.Block, - ]: - if ignore_self: - value_ids: List[ - Union[ex.ScalarConstantExpression, ex.DerefOp, ex.OmittedArg] - ] = [] - else: - value_ids = [ex.deref(self._value_column)] - - block = self._block - for other in others: - if isinstance(other, Series): - ( - block, - ( - get_column_left, - get_column_right, - ), - ) = block.join(other._block, how=how) - rebindings = { - ids.ColumnId(old): ids.ColumnId(new) - for old, new in get_column_left.items() - } - remapped_value_ids = ( - value.remap_column_refs(rebindings) for value in value_ids - ) - value_ids = [ - *remapped_value_ids, # type: ignore - ex.deref(get_column_right[other._value_column]), - ] - elif isinstance(other, bigframes.core.col.Expression): - if isinstance(other._value, ex.OmittedArg): - value_ids = [*value_ids, other._value] - continue - - label_to_col_ref = { - label: ex.deref(id) for id, label in block.col_id_to_label.items() - } - resolved_expr = other._value.bind_variables(label_to_col_ref) - block = block.project_block_exprs([resolved_expr], labels=[None]) - value_ids = [*value_ids, ex.deref(block.value_columns[-1])] - else: - # Will throw if can't interpret as scalar. - dtype = typing.cast(bigframes.dtypes.Dtype, self._dtype) - value_ids = [ - *value_ids, - ex.const(other, dtype=dtype if cast_scalars else None), - ] - return (value_ids, block) - - def _throw_if_null_index(self, opname: __builtins__.str): - if len(self._block.index_columns) == 0: - raise bigframes.exceptions.NullIndexError( - f"Series cannot perform {opname} as it has no index. Set an index using set_index." - ) + def _cached(self) -> Series: + return Series(self._block.cached()) def _is_list_like(obj: typing.Any) -> typing_extensions.TypeGuard[typing.Sequence]: diff --git a/bigframes/session/__init__.py b/bigframes/session/__init__.py index e20f61901f9..b49e2469a91 100644 --- a/bigframes/session/__init__.py +++ b/bigframes/session/__init__.py @@ -17,42 +17,45 @@ from __future__ import annotations import datetime -import fnmatch -import inspect import logging import os -import secrets -import threading +import re +import textwrap import typing -import warnings -import weakref -from collections import abc from typing import ( - IO, Any, Callable, Dict, + IO, Iterable, + List, Literal, MutableSequence, Optional, Sequence, Tuple, Union, - overload, ) +import uuid +import warnings -import bigframes_vendored.constants as constants -import bigframes_vendored.google_cloud_bigquery.retry as third_party_gcb_retry -import bigframes_vendored.ibis.backends.bigquery as ibis_bigquery # noqa -import bigframes_vendored.pandas.io.gbq as third_party_pandas_gbq -import bigframes_vendored.pandas.io.parquet as third_party_pandas_parquet -import bigframes_vendored.pandas.io.parsers.readers as third_party_pandas_readers -import bigframes_vendored.pandas.io.pickle as third_party_pandas_pickle +import google.api_core.client_info +import google.api_core.client_options +import google.api_core.exceptions +import google.api_core.gapic_v1.client_info +import google.auth.credentials import google.cloud.bigquery as bigquery +import google.cloud.bigquery_connection_v1 +import google.cloud.bigquery_storage_v1 +import google.cloud.functions_v2 +import google.cloud.resourcemanager_v3 +import google.cloud.storage as storage # type: ignore +import ibis +import ibis.backends.bigquery as ibis_bigquery +import ibis.expr.datatypes as ibis_dtypes +import ibis.expr.types as ibis_types import numpy as np import pandas -import pyarrow as pa from pandas._typing import ( CompressionOptions, FilePath, @@ -60,36 +63,34 @@ StorageOptions, ) -import bigframes._config -import bigframes._config.auth import bigframes._config.bigquery_options as bigquery_options -import bigframes.clients -import bigframes.constants -import bigframes.core -import bigframes.core.events -import bigframes.core.indexes -import bigframes.core.indexes.multi -import bigframes.core.pyformat -import bigframes.formatting_helpers -import bigframes.functions.function as bff -import bigframes.session._io.bigquery as bf_io_bigquery +import bigframes.constants as constants +import bigframes.core as core +import bigframes.core.blocks as blocks +import bigframes.core.guid as guid +from bigframes.core.ordering import IntegerEncoding, OrderingColumnReference +import bigframes.core.ordering as orderings +import bigframes.core.utils as utils +import bigframes.dataframe as dataframe +import bigframes.formatting_helpers as formatting_helpers +from bigframes.remote_function import read_gbq_function as bigframes_rgf +from bigframes.remote_function import remote_function as bigframes_rf +import bigframes.session._io.bigquery as bigframes_io import bigframes.session.clients -import bigframes.session.validation -from bigframes import exceptions as bfe -from bigframes import version -from bigframes.core import blocks, utils -from bigframes.core.logging import log_adapter -from bigframes.functions import _function_client, _function_session -from bigframes.session import bigquery_session, executor, proxy_executor - -# Avoid circular imports. -if typing.TYPE_CHECKING: - import bigframes.dataframe as dataframe - import bigframes.series - import bigframes.streaming.dataframe as streaming_dataframe +import bigframes.version + +# Even though the ibis.backends.bigquery.registry import is unused, it's needed +# to register new and replacement ops with the Ibis BigQuery backend. +import third_party.bigframes_vendored.ibis.backends.bigquery.registry # noqa +import third_party.bigframes_vendored.pandas.io.gbq as third_party_pandas_gbq +import third_party.bigframes_vendored.pandas.io.parquet as third_party_pandas_parquet +import third_party.bigframes_vendored.pandas.io.parsers.readers as third_party_pandas_readers +import third_party.bigframes_vendored.pandas.io.pickle as third_party_pandas_pickle _BIGFRAMES_DEFAULT_CONNECTION_ID = "bigframes-default-connection" +_MAX_CLUSTER_COLUMNS = 4 + # TODO(swast): Need to connect to regional endpoints when performing remote # functions operations (BQ Connection IAM, Cloud Run / Cloud Functions). # Also see if resource manager client library supports regional endpoints. @@ -103,66 +104,14 @@ "UTF-32LE", } -# BigQuery has 1 MB query size limit. Don't want to take up more than a few % of that inlining a table. -# Also must assume that text encoding as literals is much less efficient than in-memory representation. -MAX_INLINE_DF_BYTES = 5000 - logger = logging.getLogger(__name__) -class _ExecutionHistory: - def __init__(self, jobs: list[dict]): - self._df = pandas.DataFrame(jobs) - if self._df.empty: - self._df = pandas.DataFrame( - columns=[ - "job_id", - "query_id", - "job_type", - "status", - "query", - "total_bytes_processed", - "job_url", - ] - ) - - def to_dataframe(self) -> pandas.DataFrame: - """Returns the execution history as a pandas DataFrame.""" - return self._df - - def _repr_html_(self) -> str | None: - import bigframes.formatting_helpers as formatter - - if self._df.empty: - return "
No executions found.
" - - cols = ["job_type", "job_id", "status", "total_bytes_processed", "job_url"] - - # Filter columns to only those that exist in the dataframe - available_cols = [c for c in cols if c in self._df.columns] +def _is_query(query_or_table: str) -> bool: + """Determine if `query_or_table` is a table ID or a SQL string""" + return re.search(r"\s", query_or_table.strip(), re.MULTILINE) is not None - def format_url(url): - return f'Open Job' if url else "" - try: - df_display = self._df[available_cols].copy() - if "total_bytes_processed" in df_display.columns: - df_display["total_bytes_processed"] = df_display[ - "total_bytes_processed" - ].apply(formatter.get_formatted_bytes) - if "job_url" in df_display.columns: - df_display["job_url"] = df_display["job_url"].apply(format_url) - - # Rename job_id to query_id to match user expectations - if "job_id" in df_display.columns: - df_display = df_display.rename(columns={"job_id": "query_id"}) - - return df_display.to_html(escape=False, index=False) - except Exception: - return self._df.to_html() - - -@log_adapter.class_logger class Session( third_party_pandas_gbq.GBQIOMixin, third_party_pandas_parquet.ParquetIOMixin, @@ -177,7 +126,7 @@ class Session( Configuration adjusting how to connect to BigQuery and related APIs. Note that some options are ignored if ``clients_provider`` is set. - clients_provider (bigframes.session.clients.ClientsProvider): + clients_provider (bigframes.session.bigframes.session.clients.ClientsProvider): An object providing client library objects. """ @@ -186,212 +135,48 @@ def __init__( context: Optional[bigquery_options.BigQueryOptions] = None, clients_provider: Optional[bigframes.session.clients.ClientsProvider] = None, ): - # Address circular imports in doctest due to bigframes/session/__init__.py - # containing a lot of logic and samples. - from bigframes.session import anonymous_dataset, clients, loader, metrics - - _warn_if_bf_version_is_obsolete() - - # Publisher needs to be created before the other objects, especially - # the executors, because they access it. - self._publisher = bigframes.core.events.Publisher() - self._publisher.subscribe( - bigframes.formatting_helpers.create_progress_callback() - ) - if context is None: context = bigquery_options.BigQueryOptions() - self._bq_kms_key_name = context.kms_key_name + # TODO(swast): Get location from the environment. + if context is None or context.location is None: + self._location = "US" + warnings.warn( + f"No explicit location is set, so using location {self._location} for the session.", + stacklevel=2, + ) + else: + self._location = context.location # Instantiate a clients provider to help with cloud clients that will be # used in the future operations in the session if clients_provider: - # this path is only for unit testing. Not meant to be used by end users. self._clients_provider = clients_provider - self._location = context.location or "US" - project = "test_project" else: - ( - credentials, - project, - ) = bigframes._config.auth.resolve_credentials_and_project(context) - if context.location is None: - with bigquery.Client( - project=project, - credentials=credentials, - ) as temp_client: - row_iter = temp_client.query_and_wait( - "SELECT 1", - job_config=bigquery.QueryJobConfig(dry_run=True), - ) - self._location = row_iter.location or "US" - msg = bfe.format_message( - f"No explicit location is set, so using location {self._location} for the session." - ) - # User's code - # -> get_global_session() - # -> connect() - # -> Session() - # - # Note: We could also have: - # User's code - # -> read_gbq() - # -> with_default_session() - # -> get_global_session() - # -> connect() - # -> Session() - # but we currently have no way to disambiguate these - # situations. - warnings.warn( - msg, stacklevel=4, category=bfe.DefaultLocationWarning - ) - else: - self._location = context.location - - self._clients_provider = clients.ClientsProvider( - project=project, - credentials=credentials, + self._clients_provider = bigframes.session.clients.ClientsProvider( + project=context.project, location=self._location, use_regional_endpoints=context.use_regional_endpoints, + credentials=context.credentials, application_name=context.application_name, - bq_kms_key_name=self._bq_kms_key_name, - client_endpoints_override=context.client_endpoints_override, - requests_transport_adapters=context.requests_transport_adapters, ) - # TODO(shobs): Remove this logic after https://github.com/ibis-project/ibis/issues/8494 - # has been fixed. The ibis client changes the default query job config - # so we are going to remember the current config and restore it after - # the ibis client has been created - original_default_query_job_config = self.bqclient.default_query_job_config - - self.bqclient.default_query_job_config = original_default_query_job_config + self._create_and_bind_bq_session() + self.ibis_client = typing.cast( + ibis_bigquery.Backend, + ibis.bigquery.connect( + project_id=context.project, + client=self.bqclient, + storage_client=self.bqstoragereadclient, + ), + ) - # Resolve the BQ connection for remote function and Vertex AI integration self._bq_connection = context.bq_connection or _BIGFRAMES_DEFAULT_CONNECTION_ID - self._skip_bq_connection_check = context._skip_bq_connection_check # Now that we're starting the session, don't allow the options to be # changed. context._session_started = True - # unique session identifier, short enough to be human readable - # only needs to be unique among sessions created by the same user - # at the same time in the same region - self._session_id: str = "session" + secrets.token_hex(3) - # store table ids and delete them when the session is closed - - self._api_methods: list[str] = [] - self._api_methods_lock = threading.Lock() - - self._objects: list[ - weakref.ReferenceType[ - Union[ - bigframes.core.indexes.Index, - bigframes.series.Series, - dataframe.DataFrame, - ] - ] - ] = [] - # Whether this session treats objects as totally ordered. - # Will expose as feature later, only False for internal testing - self._strictly_ordered: bool = context.ordering_mode != "partial" - self._allow_ambiguity = not self._strictly_ordered - self._default_index_type = ( - bigframes.enums.DefaultIndexKind.SEQUENTIAL_INT64 - if self._strictly_ordered - else bigframes.enums.DefaultIndexKind.NULL - ) - - self._metrics = metrics.ExecutionMetrics() - self._publisher.subscribe(self._metrics.on_event) - self._anon_dataset_manager = anonymous_dataset.AnonymousDatasetManager( - self._clients_provider.bqclient, - location=self._location, - session_id=self._session_id, - kms_key=self._bq_kms_key_name, - publisher=self._publisher, - ) - self._function_session = _function_session.FunctionSession( - _function_client.FunctionClient( - gcp_project_id=project, - bq_location=self._location, - bq_client=self._clients_provider.bqclient, - bq_connection_manager=bigframes.clients.BqConnectionManager( - self._clients_provider.bqconnectionclient, - self._clients_provider.resourcemanagerclient, - ), - cloud_functions_client=self._clients_provider.cloudfunctionsclient, - publisher=self._publisher, - ), - dataset_manager=self._anon_dataset_manager, - default_connection=self._bq_connection, - location=self._location, - session_id=self._session_id, - manage_connections=not self._skip_bq_connection_check, - ) - # Session temp tables don't support specifying kms key, so use anon dataset if kms key specified - self._session_resource_manager = ( - bigquery_session.SessionResourceManager( - self.bqclient, - self._location, - publisher=self._publisher, - ) - if (self._bq_kms_key_name is None) - else None - ) - self._temp_storage_manager = ( - self._session_resource_manager or self._anon_dataset_manager - ) - self._loader = loader.GbqDataLoader( - session=self, - bqclient=self._clients_provider.bqclient, - storage_manager=self._temp_storage_manager, - write_client=self._clients_provider.bqstoragewriteclient, - default_index_type=self._default_index_type, - scan_index_uniqueness=self._strictly_ordered, - force_total_order=self._strictly_ordered, - metrics=self._metrics, - publisher=self._publisher, - ) - - labels = {} - if not self._strictly_ordered: - labels["bigframes-mode"] = "unordered" - - self._executor: executor.Executor = proxy_executor.DualCompilerProxyExecutor( - bqclient=self._clients_provider.bqclient, - bqstoragereadclient=self._clients_provider.bqstoragereadclient, - loader=self._loader, - storage_manager=self._temp_storage_manager, - metrics=self._metrics, - enable_polars_execution=context.enable_polars_execution, - publisher=self._publisher, - labels=tuple(labels.items()), - function_manager=self._function_session, - ) - - def __del__(self): - """Automatic cleanup of internal resources.""" - self.close() - - def __enter__(self): - """Enter the runtime context of the Session object. - - See [With Statement Context Managers](https://docs.python.org/3/reference/datamodel.html#with-statement-context-managers) - for more details. - """ - return self - - def __exit__(self, *_): - """Exit the runtime context of the Session object. - - See [With Statement Context Managers](https://docs.python.org/3/reference/datamodel.html#with-statement-context-managers) - for more details. - """ - self.close() - @property def bqclient(self): return self._clients_provider.bqclient @@ -412,391 +197,167 @@ def cloudfunctionsclient(self): def resourcemanagerclient(self): return self._clients_provider.resourcemanagerclient - _bq_connection_manager: Optional[bigframes.clients.BqConnectionManager] = None - @property - def bqconnectionmanager(self): - if not self._skip_bq_connection_check and not self._bq_connection_manager: - self._bq_connection_manager = bigframes.clients.BqConnectionManager( - self.bqconnectionclient, self.resourcemanagerclient - ) - return self._bq_connection_manager - - @property - def options(self) -> bigframes._config.Options: - """Options for configuring BigQuery DataFrames. - - Included for compatibility between bpd and Session. - """ - # TODO(tswast): Consider making a separate session-level options object. - return bigframes._config.options - - @property - def session_id(self): - return self._session_id - - @property - def objects( - self, - ) -> Iterable[ - Union[ - bigframes.core.indexes.Index, bigframes.series.Series, dataframe.DataFrame - ] - ]: - still_alive = [i for i in self._objects if i() is not None] - self._objects = still_alive - # Create a set with strong references, be careful not to hold onto this needlessly, as will prevent garbage collection. - return tuple(i() for i in self._objects if i() is not None) # type: ignore + def _session_dataset_id(self): + """A dataset for storing temporary objects local to the session + This is a workaround for remote functions that do not + yet support session-temporary instances.""" + return self._session_dataset.dataset_id @property def _project(self): return self.bqclient.project - @property - def bytes_processed_sum(self): - """The sum of all bytes processed by bigquery jobs using this session.""" - return self._metrics.bytes_processed - - @property - def slot_millis_sum(self): - """The sum of all slot time used by bigquery jobs in this session.""" - return self._metrics.slot_millis - - def execution_history( - self, - *, - events: Optional[Iterable[bigframes.core.events.Event]] = None, - job_ids: Optional[Iterable[str]] = None, - all_cells: bool = True, - ) -> _ExecutionHistory: - """Returns the history of executions initiated by BigFrames in the current session. - - Use `.to_dataframe()` on the result to get a pandas DataFrame. + def __hash__(self): + # Stable hash needed to use in expression tree + return hash(self._session_id) + + def _create_and_bind_bq_session(self): + """Create a BQ session and bind the session id with clients to capture BQ activities: + go/bigframes-transient-data""" + job_config = bigquery.QueryJobConfig(create_session=True) + # Make sure the session is a new one, not one associated with another query. + job_config.use_query_cache = False + query_job = self.bqclient.query( + "SELECT 1", job_config=job_config, location=self._location + ) + query_job.result() # blocks until finished + self._session_id = query_job.session_info.session_id + + # The anonymous dataset is used by BigQuery to write query results and + # session tables. BigQuery DataFrames also writes temp tables directly + # to the dataset, no BigQuery Session required. Note: there is a + # different anonymous dataset per location. See: + # https://cloud.google.com/bigquery/docs/cached-results#how_cached_results_are_stored + query_destination = query_job.destination + self._anonymous_dataset = bigquery.DatasetReference( + query_destination.project, + query_destination.dataset_id, + ) - Args: - events (Iterable[Event], optional): - Filter execution history to only include jobs associated with the given events. - job_ids (Iterable[str], optional): - Filter execution history to only include jobs matching the given job IDs. - all_cells (bool, optional): - If True, do not filter execution history by notebook cell. If False, - and running in Colab/Jupyter, automatically filter history to only include - jobs executed within the current cell. Defaults to True. - """ - jobs = [job.__dict__ for job in self._metrics.jobs] - - if events is not None: - event_job_ids = { - getattr(event, "job_id", None) - for event in events - if getattr(event, "job_id", None) is not None - } - event_query_ids = { - getattr(event, "query_id", None) - for event in events - if getattr(event, "query_id", None) is not None - } - jobs = [ - job - for job in jobs - if ( - job.get("job_id") is not None and job.get("job_id") in event_job_ids - ) - or ( - job.get("query_id") is not None - and job.get("query_id") in event_query_ids - ) + self.bqclient.default_query_job_config = bigquery.QueryJobConfig( + connection_properties=[ + bigquery.ConnectionProperty("session_id", self._session_id) ] - - elif job_ids is not None: - target_job_ids = set(job_ids) - jobs = [ - job - for job in jobs - if ( - job.get("job_id") is not None - and job.get("job_id") in target_job_ids - ) - or ( - job.get("query_id") is not None - and job.get("query_id") in target_job_ids - ) + ) + self.bqclient.default_load_job_config = bigquery.LoadJobConfig( + connection_properties=[ + bigquery.ConnectionProperty("session_id", self._session_id) ] - - elif not all_cells: - from bigframes.core.utils import get_ipython_execution_count - - current_count = get_ipython_execution_count() - if current_count is not None: - jobs = [ - job - for job in jobs - if job.get("cell_execution_count") == current_count - ] - - return _ExecutionHistory(jobs) - - @property - def _allows_ambiguity(self) -> bool: - return self._allow_ambiguity - - @property - def _anonymous_dataset(self): - return self._anon_dataset_manager.dataset - - @property - def bq_connection(self) -> str: - msg = bfe.format_message( - f"""You are using the BigFrames session default connection: {self._bq_connection}, - which can be different from the BigQuery project default connection. - This default connection may change in the future.""" ) - warnings.warn(msg, category=FutureWarning) - return self._bq_connection - def __hash__(self): - # Stable hash needed to use in expression tree - return hash(str(self._session_id)) + # Dataset for storing remote functions, which don't yet + # support proper session temporary storage yet + self._session_dataset = bigquery.Dataset( + f"{self.bqclient.project}.bigframes_temp_{self._location.lower().replace('-', '_')}" + ) + self._session_dataset.location = self._location def close(self): - """Delete resources that were created with this session's session_id. - This includes BigQuery tables, remote functions and cloud functions - serving the remote functions.""" - - # Protect against failure when the Session is a fake for testing or - # failed to initialize. - if anon_dataset_manager := getattr(self, "_anon_dataset_manager", None): - anon_dataset_manager.close() - - if session_resource_manager := getattr(self, "_session_resource_manager", None): - session_resource_manager.close() - - remote_function_session = getattr(self, "_function_session", None) - if remote_function_session: - remote_function_session.clean_up() - - publisher_session = getattr(self, "_publisher", None) - if publisher_session: - publisher_session.publish( - bigframes.core.events.SessionClosed(self.session_id) - ) + """Terminated the BQ session, otherwises the session will be terminated automatically after + 24 hours of inactivity or after 7 days.""" + if self._session_id is not None and self.bqclient is not None: + abort_session_query = "CALL BQ.ABORT_SESSION('{}')".format(self._session_id) + try: + query_job = self.bqclient.query(abort_session_query) + query_job.result() # blocks until finished + except google.api_core.exceptions.BadRequest as exc: + # Ignore the exception when the BQ session itself has expired + # https://cloud.google.com/bigquery/docs/sessions-terminating#auto-terminate_a_session + if not exc.message.startswith( + f"Session {self._session_id} has expired and is no longer available." + ): + raise + except google.auth.exceptions.RefreshError: + # The refresh token may itself have been invalidated or expired + # https://developers.google.com/identity/protocols/oauth2#expiration + # Don't raise the exception in this case while closing the + # BigFrames session, so that the end user has a path for getting + # out of a bad session due to unusable credentials. + pass + self._session_id = None - @overload - def read_gbq( # type: ignore[overload-overlap] - self, - query_or_table: str, - *, - index_col: Iterable[str] | str | bigframes.enums.DefaultIndexKind = ..., - columns: Iterable[str] = ..., - configuration: Optional[Dict] = ..., - max_results: Optional[int] = ..., - filters: third_party_pandas_gbq.FiltersType = ..., - use_cache: Optional[bool] = ..., - col_order: Iterable[str] = ..., - dry_run: Literal[False] = ..., - allow_large_results: Optional[bool] = ..., - ) -> dataframe.DataFrame: ... - - @overload def read_gbq( self, query_or_table: str, *, - index_col: Iterable[str] | str | bigframes.enums.DefaultIndexKind = ..., - columns: Iterable[str] = ..., - configuration: Optional[Dict] = ..., - max_results: Optional[int] = ..., - filters: third_party_pandas_gbq.FiltersType = ..., - use_cache: Optional[bool] = ..., - col_order: Iterable[str] = ..., - dry_run: Literal[True] = ..., - allow_large_results: Optional[bool] = ..., - ) -> pandas.Series: ... - - def read_gbq( - self, - query_or_table: str, - *, - index_col: Iterable[str] | str | bigframes.enums.DefaultIndexKind = (), - columns: Iterable[str] = (), - configuration: Optional[Dict] = None, - max_results: Optional[int] = None, - filters: third_party_pandas_gbq.FiltersType = (), - use_cache: Optional[bool] = None, + index_col: Iterable[str] | str = (), col_order: Iterable[str] = (), - dry_run: bool = False, - allow_large_results: Optional[bool] = None, - ) -> dataframe.DataFrame | pandas.Series: + max_results: Optional[int] = None, + # Add a verify index argument that fails if the index is not unique. + ) -> dataframe.DataFrame: # TODO(b/281571214): Generate prompt to show the progress of read_gbq. - if columns and col_order: - raise ValueError( - "Must specify either columns (preferred) or col_order, not both" - ) - elif col_order: - columns = col_order - - if allow_large_results is None: - allow_large_results = bigframes._config.options._allow_large_results - - if bf_io_bigquery.is_query(query_or_table): - return self._loader.read_gbq_query( # type: ignore # for dry_run overload + if _is_query(query_or_table): + return self._read_gbq_query( query_or_table, index_col=index_col, - columns=columns, - configuration=configuration, + col_order=col_order, max_results=max_results, - use_cache=use_cache, - filters=filters, - dry_run=dry_run, - allow_large_results=allow_large_results, + api_name="read_gbq", ) else: - if configuration is not None: - raise ValueError( - "The 'configuration' argument is not allowed when " - "directly reading from a table. Please remove " - "'configuration' or use a query." - ) - - return self._loader.read_gbq_table( # type: ignore # for dry_run overload + # TODO(swast): Query the snapshot table but mark it as a + # deterministic query so we can avoid serializing if we have a + # unique index. + return self._read_gbq_table( query_or_table, index_col=index_col, - columns=columns, + col_order=col_order, max_results=max_results, - use_cache=use_cache if use_cache is not None else True, - filters=filters, - dry_run=dry_run, + api_name="read_gbq", ) - def _register_object( - self, - object: Union[ - bigframes.core.indexes.Index, bigframes.series.Series, dataframe.DataFrame - ], - ): - self._objects.append(weakref.ref(object)) - - @overload - def _read_gbq_colab( + def _query_to_destination( self, query: str, - *, - callback: Optional[Callable[[bigframes.core.events.EventEnvelope], None]] = ..., - pyformat_args: Optional[Dict[str, Any]] = None, - dry_run: Literal[False] = ..., - ) -> dataframe.DataFrame: ... - - @overload - def _read_gbq_colab( - self, - query: str, - *, - callback: Optional[Callable[[bigframes.core.events.EventEnvelope], None]] = ..., - pyformat_args: Optional[Dict[str, Any]] = None, - dry_run: Literal[True] = ..., - ) -> pandas.Series: ... + index_cols: List[str], + api_name: str, + ) -> Tuple[Optional[bigquery.TableReference], Optional[bigquery.QueryJob]]: + # If a dry_run indicates this is not a query type job, then don't + # bother trying to do a CREATE TEMP TABLE ... AS SELECT ... statement. + dry_run_config = bigquery.QueryJobConfig() + dry_run_config.dry_run = True + _, dry_run_job = self._start_query(query, job_config=dry_run_config) + if dry_run_job.statement_type != "SELECT": + _, query_job = self._start_query(query) + return query_job.destination, query_job + + # Create a table to workaround BigQuery 10 GB query results limit. See: + # internal issue 303057336. + # Since we have a `statement_type == 'SELECT'`, schema should be populated. + schema = typing.cast(Iterable[bigquery.SchemaField], dry_run_job.schema) + cluster_cols = [ + item.name + for item in schema + if (item.name in index_cols) and _can_cluster_bq(item) + ][:_MAX_CLUSTER_COLUMNS] + temp_table = self._create_empty_temp_table(schema, cluster_cols) + + job_config = bigquery.QueryJobConfig() + job_config.labels["bigframes-api"] = api_name + job_config.destination = temp_table - @log_adapter.log_name_override("read_gbq_colab") - def _read_gbq_colab( - self, - query: str, - *, - callback: Optional[ - Callable[[bigframes.core.events.EventEnvelope], None] - ] = None, - pyformat_args: Optional[Dict[str, Any]] = None, - dry_run: bool = False, - ) -> Union[dataframe.DataFrame, pandas.Series]: - """A version of read_gbq that has the necessary default values for use in colab integrations. - - This includes, no ordering, no index, no progress bar, always use string - formatting for embedding local variables / dataframes. - - Args: - query (str): - A SQL query string to execute. Results (if any) are turned into - a DataFrame. - callback (Optional[Callable[[bigframes.core.events.EventEnvelope], None]]): - Callback to receive query execution events. - pyformat_args (dict): - A dictionary of potential variables to replace in ``query``. - Note: strings are _not_ escaped. Use query parameters for these, - instead. Note: unlike read_gbq / read_gbq_query, even if set to - None, this function always assumes {var} refers to a variable - that is supposed to be supplied in this dictionary. - """ - if pyformat_args is None: - pyformat_args = {} - - allow_large_results = bigframes._config.options._allow_large_results - - query = bigframes.core.pyformat.pyformat( - query, - pyformat_args=pyformat_args, - session=self, - dry_run=dry_run, - ) - - def _run_query(): - return self._loader.read_gbq_query( - query=query, - index_col=bigframes.enums.DefaultIndexKind.NULL, - force_total_order=False, - dry_run=typing.cast(Union[Literal[False], Literal[True]], dry_run), - allow_large_results=allow_large_results, - ) - - if callback is not None: - with self._publisher.subscribe(callback): - return _run_query() - return _run_query() - - @overload - def read_gbq_query( # type: ignore[overload-overlap] - self, - query: str, - *, - index_col: Iterable[str] | str | bigframes.enums.DefaultIndexKind = ..., - columns: Iterable[str] = ..., - configuration: Optional[Dict] = ..., - max_results: Optional[int] = ..., - use_cache: Optional[bool] = ..., - col_order: Iterable[str] = ..., - filters: third_party_pandas_gbq.FiltersType = ..., - dry_run: Literal[False] = ..., - allow_large_results: Optional[bool] = ..., - ) -> dataframe.DataFrame: ... - - @overload - def read_gbq_query( - self, - query: str, - *, - index_col: Iterable[str] | str | bigframes.enums.DefaultIndexKind = ..., - columns: Iterable[str] = ..., - configuration: Optional[Dict] = ..., - max_results: Optional[int] = ..., - use_cache: Optional[bool] = ..., - col_order: Iterable[str] = ..., - filters: third_party_pandas_gbq.FiltersType = ..., - dry_run: Literal[True] = ..., - allow_large_results: Optional[bool] = ..., - ) -> pandas.Series: ... + try: + # Write to temp table to workaround BigQuery 10 GB query results + # limit. See: internal issue 303057336. + _, query_job = self._start_query(query, job_config=job_config) + return query_job.destination, query_job + except google.api_core.exceptions.BadRequest: + # Some SELECT statements still aren't compatible with cluster + # tables as the destination. For example, if the query has a + # top-level ORDER BY, this conflicts with our ability to cluster + # the table by the index column(s). + _, query_job = self._start_query(query) + return query_job.destination, query_job def read_gbq_query( self, query: str, *, - index_col: Iterable[str] | str | bigframes.enums.DefaultIndexKind = (), - columns: Iterable[str] = (), - configuration: Optional[Dict] = None, - max_results: Optional[int] = None, - use_cache: Optional[bool] = None, + index_col: Iterable[str] | str = (), col_order: Iterable[str] = (), - filters: third_party_pandas_gbq.FiltersType = (), - dry_run: bool = False, - allow_large_results: Optional[bool] = None, - ) -> dataframe.DataFrame | pandas.Series: + max_results: Optional[int] = None, + ) -> dataframe.DataFrame: """Turn a SQL query into a DataFrame. Note: Because the results are written to a temporary table, ordering by @@ -806,9 +367,11 @@ def read_gbq_query( **Examples:** + >>> import bigframes.pandas as bpd + >>> bpd.options.display.progress_bar = None + Simple query input: - >>> import bigframes.pandas as bpd >>> df = bpd.read_gbq_query(''' ... SELECT ... pitcherFirstName, @@ -816,6 +379,12 @@ def read_gbq_query( ... pitchSpeed, ... FROM `bigquery-public-data.baseball.games_wide` ... ''') + >>> df.head(2) + pitcherFirstName pitcherLastName pitchSpeed + 0 0 + 1 0 + + [2 rows x 3 columns] Preserve ordering in a query input. @@ -833,240 +402,495 @@ def read_gbq_query( ... WHERE year = 2016 ... GROUP BY pitcherFirstName, pitcherLastName ... ''', index_col="rowindex") - >>> print("START_OF_OUTPUT"); df.head(2) # doctest: +ELLIPSIS,+NORMALIZE_WHITESPACE - START_OF_OUTPUT - ... + >>> df.head(2) pitcherFirstName pitcherLastName averagePitchSpeed - ... + rowindex 1 Albertin Chapman 96.514113 2 Zachary Britton 94.591039 [2 rows x 3 columns] See also: :meth:`Session.read_gbq`. - - Args: - query (str): - A SQL query to execute. - index_col (Iterable[str] or str, optional): - The column(s) to use as the index for the DataFrame. This can be - a single column name or a list of column names. If not provided, - a default index will be used. - columns (Iterable[str], optional): - The columns to read from the query result. If not - specified, all columns will be read. - configuration (dict, optional): - A dictionary of query job configuration options. See the - BigQuery REST API documentation for a list of available options: - https://cloud.google.com/bigquery/docs/reference/rest/v2/jobs#configuration.query - max_results (int, optional): - The maximum number of rows to retrieve from the query - result. If not specified, all rows will be loaded. - use_cache (bool, optional): - Whether to use cached results for the query. Defaults to ``True``. - Setting this to ``False`` will force a re-execution of the query. - col_order (Iterable[str], optional): - The desired order of columns in the resulting DataFrame. This - parameter is deprecated and will be removed in a future version. - Use ``columns`` instead. - filters (list[tuple], optional): - A list of filters to apply to the data. Filters are specified - as a list of tuples, where each tuple contains a column name, - an operator (e.g., '==', '!='), and a value. - dry_run (bool, optional): - If ``True``, the function will not actually execute the query but - will instead return statistics about the query. Defaults to - ``False``. - allow_large_results (bool, optional): - Whether to allow large query results. If ``True``, the query - results can be larger than the maximum response size. - Defaults to ``bpd.options.compute.allow_large_results``. - - Returns: - bigframes.pandas.DataFrame or pandas.Series: - A DataFrame representing the result of the query. If ``dry_run`` - is ``True``, a ``pandas.Series`` containing query statistics is - returned. - - Raises: - ValueError: - When both ``columns`` and ``col_order`` are specified. """ # NOTE: This method doesn't (yet) exist in pandas or pandas-gbq, so # these docstrings are inline. - if columns and col_order: - raise ValueError( - "Must specify either columns (preferred) or col_order, not both" - ) - elif col_order: - columns = col_order - - if allow_large_results is None: - allow_large_results = bigframes._config.options._allow_large_results - - return self._loader.read_gbq_query( # type: ignore # for dry_run overload + return self._read_gbq_query( query=query, index_col=index_col, - columns=columns, - configuration=configuration, + col_order=col_order, max_results=max_results, - use_cache=use_cache, - filters=filters, - dry_run=dry_run, - allow_large_results=allow_large_results, + api_name="read_gbq_query", ) - @overload - def read_gbq_table( # type: ignore[overload-overlap] - self, - query: str, - *, - index_col: Iterable[str] | str | bigframes.enums.DefaultIndexKind = ..., - columns: Iterable[str] = ..., - max_results: Optional[int] = ..., - filters: third_party_pandas_gbq.FiltersType = ..., - use_cache: bool = ..., - col_order: Iterable[str] = ..., - dry_run: Literal[False] = ..., - ) -> dataframe.DataFrame: ... - - @overload - def read_gbq_table( + def _read_gbq_query( self, query: str, *, - index_col: Iterable[str] | str | bigframes.enums.DefaultIndexKind = ..., - columns: Iterable[str] = ..., - max_results: Optional[int] = ..., - filters: third_party_pandas_gbq.FiltersType = ..., - use_cache: bool = ..., - col_order: Iterable[str] = ..., - dry_run: Literal[True] = ..., - ) -> pandas.Series: ... + index_col: Iterable[str] | str = (), + col_order: Iterable[str] = (), + max_results: Optional[int] = None, + api_name: str = "read_gbq_query", + ) -> dataframe.DataFrame: + if isinstance(index_col, str): + index_cols = [index_col] + else: + index_cols = list(index_col) + + destination, query_job = self._query_to_destination( + query, index_cols, api_name=api_name + ) + + # If there was no destination table, that means the query must have + # been DDL or DML. Return some job metadata, instead. + if not destination: + return dataframe.DataFrame( + data=pandas.DataFrame( + { + "statement_type": [ + query_job.statement_type if query_job else "unknown" + ], + "job_id": [query_job.job_id if query_job else "unknown"], + "location": [query_job.location if query_job else "unknown"], + } + ), + session=self, + ) + + return self.read_gbq_table( + f"{destination.project}.{destination.dataset_id}.{destination.table_id}", + index_col=index_cols, + col_order=col_order, + max_results=max_results, + ) def read_gbq_table( self, query: str, *, - index_col: Iterable[str] | str | bigframes.enums.DefaultIndexKind = (), - columns: Iterable[str] = (), - max_results: Optional[int] = None, - filters: third_party_pandas_gbq.FiltersType = (), - use_cache: bool = True, + index_col: Iterable[str] | str = (), col_order: Iterable[str] = (), - dry_run: bool = False, - ) -> dataframe.DataFrame | pandas.Series: + max_results: Optional[int] = None, + ) -> dataframe.DataFrame: """Turn a BigQuery table into a DataFrame. **Examples:** + >>> import bigframes.pandas as bpd + >>> bpd.options.display.progress_bar = None + Read a whole table, with arbitrary ordering or ordering corresponding to the primary key(s). - >>> import bigframes.pandas as bpd >>> df = bpd.read_gbq_table("bigquery-public-data.ml_datasets.penguins") + >>> df.head(2) + species island culmen_length_mm \\ + 0 Adelie Penguin (Pygoscelis adeliae) Dream 36.6 + 1 Adelie Penguin (Pygoscelis adeliae) Dream 39.8 + + culmen_depth_mm flipper_length_mm body_mass_g sex + 0 18.4 184.0 3475.0 FEMALE + 1 19.1 184.0 4650.0 MALE + + [2 rows x 7 columns] See also: :meth:`Session.read_gbq`. - - Args: - table_id (str): - The identifier of the BigQuery table to read. - index_col (Iterable[str] or str, optional): - The column(s) to use as the index for the DataFrame. This can be - a single column name or a list of column names. If not provided, - a default index will be used. - columns (Iterable[str], optional): - The columns to read from the table. If not specified, all - columns will be read. - max_results (int, optional): - The maximum number of rows to retrieve from the table. If not - specified, all rows will be loaded. - filters (list[tuple], optional): - A list of filters to apply to the data. Filters are specified - as a list of tuples, where each tuple contains a column name, - an operator (e.g., '==', '!='), and a value. - use_cache (bool, optional): - Whether to use cached results for the query. Defaults to ``True``. - Setting this to ``False`` will force a re-execution of the query. - col_order (Iterable[str], optional): - The desired order of columns in the resulting DataFrame. This - parameter is deprecated and will be removed in a future version. - Use ``columns`` instead. - dry_run (bool, optional): - If ``True``, the function will not actually execute the query but - will instead return statistics about the table. Defaults to - ``False``. - - Returns: - bigframes.pandas.DataFrame or pandas.Series: - A DataFrame representing the contents of the table. If - ``dry_run`` is ``True``, a ``pandas.Series`` containing table - statistics is returned. - - Raises: - ValueError: - When both ``columns`` and ``col_order`` are specified. """ # NOTE: This method doesn't (yet) exist in pandas or pandas-gbq, so # these docstrings are inline. - if columns and col_order: - raise ValueError( - "Must specify either columns (preferred) or col_order, not both" - ) - elif col_order: - columns = col_order - - return self._loader.read_gbq_table( # type: ignore # for dry_run overload - table_id=query, + return self._read_gbq_table( + query=query, index_col=index_col, - columns=columns, + col_order=col_order, max_results=max_results, - use_cache=use_cache, - filters=filters, - dry_run=dry_run, + api_name="read_gbq_table", ) - def read_gbq_table_streaming( - self, table: str - ) -> streaming_dataframe.StreamingDataFrame: - """Turn a BigQuery table into a StreamingDataFrame. + def _read_gbq_table_to_ibis_with_total_ordering( + self, + table_ref: bigquery.table.TableReference, + *, + api_name: str, + ) -> Tuple[ibis_types.Table, Optional[Sequence[str]]]: + """Create a read-only Ibis table expression representing a table. - .. note:: + If we can get a total ordering from the table, such as via primary key + column(s), then return those too so that ordering generation can be + avoided. + """ + if table_ref.dataset_id.upper() == "_SESSION": + # _SESSION tables aren't supported by the tables.get REST API. + return ( + self.ibis_client.sql( + f"SELECT * FROM `_SESSION`.`{table_ref.table_id}`" + ), + None, + ) - The bigframes.streaming module is a preview feature, and subject to change. + table_expression = self.ibis_client.table( + table_ref.table_id, + database=f"{table_ref.project}.{table_ref.dataset_id}", + ) - **Examples:** + # If there are primary keys defined, the query engine assumes these + # columns are unique, even if the constraint is not enforced. We make + # the same assumption and use these columns as the total ordering keys. + table = self.bqclient.get_table(table_ref) + + # TODO(b/305264153): Use public properties to fetch primary keys once + # added to google-cloud-bigquery. + primary_keys = ( + table._properties.get("tableConstraints", {}) + .get("primaryKey", {}) + .get("columns") + ) + + if not primary_keys: + return table_expression, None + else: + # Read from a snapshot since we won't have to copy the table data to create a total ordering. + job_config = bigquery.QueryJobConfig() + job_config.labels["bigframes-api"] = api_name + current_timestamp = list( + self.bqclient.query( + "SELECT CURRENT_TIMESTAMP() AS `current_timestamp`", + job_config=job_config, + ).result() + )[0][0] + table_expression = self.ibis_client.sql( + bigframes_io.create_snapshot_sql(table_ref, current_timestamp) + ) + return table_expression, primary_keys + + def _read_gbq_table( + self, + query: str, + *, + index_col: Iterable[str] | str = (), + col_order: Iterable[str] = (), + max_results: Optional[int] = None, + api_name: str, + ) -> dataframe.DataFrame: + if max_results and max_results <= 0: + raise ValueError("`max_results` should be a positive number.") + + # TODO(swast): Can we re-use the temp table from other reads in the + # session, if the original table wasn't modified? + table_ref = bigquery.table.TableReference.from_string( + query, default_project=self.bqclient.project + ) + + ( + table_expression, + total_ordering_cols, + ) = self._read_gbq_table_to_ibis_with_total_ordering( + table_ref, + api_name=api_name, + ) + + for key in col_order: + if key not in table_expression.columns: + raise ValueError( + f"Column '{key}' of `col_order` not found in this table." + ) - >>> import bigframes.streaming as bst + if isinstance(index_col, str): + index_cols: List[str] = [index_col] + else: + index_cols = list(index_col) - >>> sdf = bst.read_gbq_table("bigquery-public-data.ml_datasets.penguins") + hidden_cols: typing.Sequence[str] = () + + for key in index_cols: + if key not in table_expression.columns: + raise ValueError( + f"Column `{key}` of `index_col` not found in this table." + ) + + # If the index is unique and sortable, then we don't need to generate + # an ordering column. + ordering = None + is_total_ordering = False + + if total_ordering_cols is not None: + # Note: currently, this a table has a total ordering only when the + # primary key(s) are set on a table. The query engine assumes such + # columns are unique, even if not enforced. + is_total_ordering = True + ordering = orderings.ExpressionOrdering( + ordering_value_columns=tuple( + [ + core.OrderingColumnReference(column_id) + for column_id in total_ordering_cols + ] + ), + total_ordering_columns=frozenset(total_ordering_cols), + ) + + if len(index_cols) != 0: + index_labels = typing.cast(List[Optional[str]], index_cols) + else: + # Use the total_ordering_cols to project offsets to use as the default index. + table_expression = table_expression.order_by(index_cols) + default_index_id = guid.generate_guid("bigframes_index_") + default_index_col = ( + ibis.row_number().cast(ibis_dtypes.int64).name(default_index_id) + ) + table_expression = table_expression.mutate( + **{default_index_id: default_index_col} + ) + index_cols = [default_index_id] + index_labels = [None] + elif len(index_cols) != 0: + index_labels = typing.cast(List[Optional[str]], index_cols) + distinct_table = table_expression.select(*index_cols).distinct() + is_unique_sql = f"""WITH full_table AS ( + {self.ibis_client.compile(table_expression)} + ), + distinct_table AS ( + {self.ibis_client.compile(distinct_table)} + ) + + SELECT (SELECT COUNT(*) FROM full_table) AS `total_count`, + (SELECT COUNT(*) FROM distinct_table) AS `distinct_count` + """ + results, query_job = self._start_query(is_unique_sql) + row = next(iter(results)) + + total_count = row["total_count"] + distinct_count = row["distinct_count"] + is_total_ordering = total_count == distinct_count + + ordering = orderings.ExpressionOrdering( + ordering_value_columns=tuple( + [ + core.OrderingColumnReference(column_id) + for column_id in index_cols + ] + ), + total_ordering_columns=frozenset(index_cols), + ) + + # We have a total ordering, so query via "time travel" so that + # the underlying data doesn't mutate. + if is_total_ordering: + # Get the timestamp from the job metadata rather than the query + # text so that the query for determining uniqueness of the ID + # columns can be cached. + current_timestamp = query_job.started + + # The job finished, so we should have a start time. + assert current_timestamp is not None + table_expression = self.ibis_client.sql( + bigframes_io.create_snapshot_sql(table_ref, current_timestamp) + ) + else: + # Make sure when we generate an ordering, the row_number() + # coresponds to the index columns. + table_expression = table_expression.order_by(index_cols) + warnings.warn( + textwrap.dedent( + f""" + Got a non-unique index. A consistent ordering is not + guaranteed. DataFrame has {total_count} rows, + but only {distinct_count} distinct index values. + """, + ) + ) + + # When ordering by index columns, apply limit after ordering to + # make limit more predictable. + if max_results is not None: + table_expression = table_expression.limit(max_results) + else: + if max_results is not None: + # Apply limit before generating rownums and creating temp table + # This makes sure the offsets are valid and limits the number of + # rows for which row numbers must be generated + table_expression = table_expression.limit(max_results) + table_expression, ordering = self._create_sequential_ordering( + table=table_expression, + api_name=api_name, + ) + hidden_cols = ( + (ordering.total_order_col.column_id,) + if ordering.total_order_col + else () + ) + assert len(ordering.ordering_value_columns) > 0 + is_total_ordering = True + # Block constructor will generate default index if passed empty + index_cols = [] + index_labels = [] + + return self._read_gbq_with_ordering( + table_expression=table_expression, + col_order=col_order, + index_cols=index_cols, + index_labels=index_labels, + hidden_cols=hidden_cols, + ordering=ordering, + is_total_ordering=is_total_ordering, + api_name=api_name, + ) + + def _read_gbq_with_ordering( + self, + table_expression: ibis_types.Table, + *, + col_order: Iterable[str] = (), + col_labels: Iterable[Optional[str]] = (), + index_cols: Iterable[str] = (), + index_labels: Iterable[Optional[str]] = (), + hidden_cols: Iterable[str] = (), + ordering: orderings.ExpressionOrdering, + is_total_ordering: bool = False, + api_name: str, + ) -> dataframe.DataFrame: + """Internal helper method that loads DataFrame from Google BigQuery given an ordering column. + + Args: + table_expression: + an ibis table expression to be executed in BigQuery. + col_order: + List of BigQuery column ids in the desired order for results DataFrame. + col_labels: + List of column labels as the column names. + index_cols: + List of index ids to use as the index or multi-index. + index_labels: + List of index labels as names of index. + hidden_cols: + Columns that should be hidden. Ordering columns may (not always) be hidden + ordering: + Column name to be used for ordering. If not supplied, a default ordering is generated. + api_name: + The name of the API method. Returns: - bigframes.streaming.dataframe.StreamingDataFrame: - A StreamingDataFrame representing results of the table. + A DataFrame representing results of the query or table. """ - msg = bfe.format_message( - "The bigframes.streaming module is a preview feature, and subject to change." + index_cols, index_labels = list(index_cols), list(index_labels) + if len(index_cols) != len(index_labels): + raise ValueError( + "Needs same number of index labels are there are index columns. " + f"Got {len(index_labels)}, expected {len(index_cols)}." + ) + + # Logic: + # no total ordering, index -> create sequential order, ordered by index, use for both ordering and index + # total ordering, index -> use ordering as ordering, index as index + + # This code block ensures the existence of a total ordering. + column_keys = list(col_order) + if len(column_keys) == 0: + non_value_columns = set([*index_cols, *hidden_cols]) + column_keys = [ + key for key in table_expression.columns if key not in non_value_columns + ] + if not is_total_ordering: + # Rows are not ordered, we need to generate a default ordering and materialize it + table_expression, ordering = self._create_sequential_ordering( + table=table_expression, + index_cols=index_cols, + api_name=api_name, + ) + index_col_values = [table_expression[index_id] for index_id in index_cols] + if not col_labels: + col_labels = column_keys + return self._read_ibis( + table_expression, + index_col_values, + index_labels, + column_keys, + col_labels, + ordering=ordering, ) - warnings.warn(msg, stacklevel=1, category=bfe.PreviewWarning) - import bigframes.streaming.dataframe as streaming_dataframe + def _read_bigquery_load_job( + self, + filepath_or_buffer: str | IO["bytes"], + table: bigquery.Table, + *, + job_config: bigquery.LoadJobConfig, + index_col: Iterable[str] | str = (), + col_order: Iterable[str] = (), + ) -> dataframe.DataFrame: + if isinstance(index_col, str): + index_cols = [index_col] + else: + index_cols = list(index_col) + + if not job_config.clustering_fields and index_cols: + job_config.clustering_fields = index_cols[:_MAX_CLUSTER_COLUMNS] - df = self._loader.read_gbq_table( - table, - enable_snapshot=False, - index_col=bigframes.enums.DefaultIndexKind.NULL, + if isinstance(filepath_or_buffer, str): + if filepath_or_buffer.startswith("gs://"): + load_job = self.bqclient.load_table_from_uri( + filepath_or_buffer, table, job_config=job_config + ) + else: + with open(filepath_or_buffer, "rb") as source_file: + load_job = self.bqclient.load_table_from_file( + source_file, table, job_config=job_config + ) + else: + load_job = self.bqclient.load_table_from_file( + filepath_or_buffer, table, job_config=job_config + ) + + self._start_generic_job(load_job) + + # The BigQuery REST API for tables.get doesn't take a session ID, so we + # can't get the schema for a temp table that way. + return self.read_gbq_table( + f"{table.project}.{table.dataset_id}.{table.table_id}", + index_col=index_col, + col_order=col_order, + ) + + def _read_ibis( + self, + table_expression: ibis_types.Table, + index_cols: Iterable[ibis_types.Value], + index_labels: Iterable[blocks.Label], + column_keys: Iterable[str], + column_labels: Iterable[blocks.Label], + ordering: orderings.ExpressionOrdering, + ) -> dataframe.DataFrame: + """Turns a table expression (plus index column) into a DataFrame.""" + + columns = list(index_cols) + for key in column_keys: + if key not in table_expression.columns: + raise ValueError(f"Column '{key}' not found in this table.") + columns.append(table_expression[key]) + + non_hidden_ids = [col.get_name() for col in columns] + hidden_ordering_columns = [] + for ref in ordering.all_ordering_columns: + if ref.column_id not in non_hidden_ids: + hidden_ordering_columns.append(table_expression[ref.column_id]) + + block = blocks.Block( + core.ArrayValue.from_ibis( + self, table_expression, columns, hidden_ordering_columns, ordering + ), + index_columns=[index_col.get_name() for index_col in index_cols], + column_labels=column_labels, + index_labels=index_labels, ) - return streaming_dataframe.StreamingDataFrame._from_table_df(df) + return dataframe.DataFrame(block) def read_gbq_model(self, model_name: str): """Loads a BigQuery ML model from BigQuery. **Examples:** + >>> import bigframes.pandas as bpd + >>> bpd.options.display.progress_bar = None + Read an existing BigQuery ML model. - >>> import bigframes.pandas as bpd >>> model_name = "bigframes-dev.bqml_tutorial.penguins_model" >>> model = bpd.read_gbq_model(model_name) @@ -1077,7 +901,7 @@ def read_gbq_model(self, model_name: str): to load from the default project. Returns: - A bigframes.ml Model, Transformer or Pipeline wrapping the model. + A bigframes.ml Model wrapping the model. """ import bigframes.ml.loader @@ -1087,48 +911,17 @@ def read_gbq_model(self, model_name: str): model = self.bqclient.get_model(model_ref) return bigframes.ml.loader.from_bq(self, model) - @typing.overload - def read_pandas( - self, - pandas_dataframe: pandas.Index, - *, - write_engine: constants.WriteEngineType = "default", - ) -> bigframes.core.indexes.Index: ... - - @typing.overload - def read_pandas( - self, - pandas_dataframe: pandas.Series, - *, - write_engine: constants.WriteEngineType = "default", - ) -> bigframes.series.Series: ... - - @typing.overload - def read_pandas( - self, - pandas_dataframe: pandas.DataFrame, - *, - write_engine: constants.WriteEngineType = "default", - ) -> dataframe.DataFrame: ... - - def read_pandas( - self, - pandas_dataframe: Union[pandas.DataFrame, pandas.Series, pandas.Index], - *, - write_engine: constants.WriteEngineType = "default", - ): + def read_pandas(self, pandas_dataframe: pandas.DataFrame) -> dataframe.DataFrame: """Loads DataFrame from a pandas DataFrame. The pandas DataFrame will be persisted as a temporary BigQuery table, which can be automatically recycled after the Session is closed. - .. note:: - Data is inlined in the query SQL if it is small enough (roughly 5MB - or less in memory). Larger size data is loaded to a BigQuery table - instead. - **Examples:** + >>> import bigframes.pandas as bpd + >>> import pandas as pd + >>> bpd.options.display.progress_bar = None >>> d = {'col1': [1, 2], 'col2': [3, 4]} >>> pandas_df = pd.DataFrame(data=d) @@ -1141,125 +934,85 @@ def read_pandas( [2 rows x 2 columns] Args: - pandas_dataframe (pandas.DataFrame, pandas.Series, or pandas.Index): - a pandas DataFrame/Series/Index object to be loaded. - write_engine (str): - How data should be written to BigQuery (if at all). Supported - values: - - * "default": - (Recommended) Select an appropriate mechanism to write data - to BigQuery. Depends on data size and supported data types. - * "bigquery_inline": - Inline data in BigQuery SQL. Use this when you know the data - is small enough to fit within BigQuery's 1 MB query text size - limit. - * "bigquery_load": - Use a BigQuery load job. Use this for larger data sizes. - * "bigquery_streaming": - Use the BigQuery streaming JSON API. Use this if your - workload is such that you exhaust the BigQuery load job - quota and your data cannot be embedded in SQL due to size or - data type limitations. - * "bigquery_write": - [Preview] Use the BigQuery Storage Write API. This feature - is in public preview. - Returns: - An equivalent bigframes.pandas.(DataFrame/Series/Index) object + pandas_dataframe (pandas.DataFrame): + a pandas DataFrame object to be loaded. - Raises: - ValueError: - When the object is not a Pandas DataFrame. + Returns: + bigframes.dataframe.DataFrame: The BigQuery DataFrame. """ - import bigframes.series as series - - # Try to handle non-dataframe pandas objects as well - if isinstance(pandas_dataframe, pandas.Series): - bf_df = self._read_pandas( - pandas.DataFrame(pandas_dataframe), - write_engine=write_engine, - ) - bf_series = series.Series(bf_df._block) - # wrapping into df can set name to 0 so reset to original object name - bf_series.name = pandas_dataframe.name - return bf_series - if isinstance(pandas_dataframe, pandas.Index): - return self._read_pandas( - pandas.DataFrame(index=pandas_dataframe), - write_engine=write_engine, - ).index - if isinstance(pandas_dataframe, pandas.DataFrame): - return self._read_pandas(pandas_dataframe, write_engine=write_engine) - else: - raise ValueError( - f"read_pandas() expects a pandas.DataFrame, but got a {type(pandas_dataframe)}" - ) + return self._read_pandas(pandas_dataframe, "read_pandas") def _read_pandas( - self, - pandas_dataframe: pandas.DataFrame, - *, - write_engine: constants.WriteEngineType = "default", + self, pandas_dataframe: pandas.DataFrame, api_name: str ) -> dataframe.DataFrame: - import bigframes.dataframe as dataframe - - if isinstance(pandas_dataframe, dataframe.DataFrame): - raise ValueError( - "read_pandas() expects a pandas.DataFrame, but got a " - "bigframes.pandas.DataFrame." - ) - - mem_usage = pandas_dataframe.memory_usage(deep=True).sum() - if write_engine == "default": - write_engine = ( - "bigquery_load" - if mem_usage > bigframes.constants.MAX_INLINE_BYTES - else "bigquery_inline" - ) - - if write_engine == "bigquery_inline": - if mem_usage > bigframes.constants.MAX_INLINE_BYTES: - raise ValueError( - f"DataFrame size ({mem_usage} bytes) exceeds the maximum allowed " - f"for inline data ({bigframes.constants.MAX_INLINE_BYTES} bytes)." + col_labels, idx_labels = ( + pandas_dataframe.columns.to_list(), + pandas_dataframe.index.names, + ) + new_col_ids, new_idx_ids = utils.get_standardized_ids(col_labels, idx_labels) + + # Add order column to pandas DataFrame to preserve order in BigQuery + ordering_col = "rowid" + columns = frozenset(col_labels + idx_labels) + suffix = 2 + while ordering_col in columns: + ordering_col = f"rowid_{suffix}" + suffix += 1 + + pandas_dataframe_copy = pandas_dataframe.copy() + pandas_dataframe_copy.index.names = new_idx_ids + pandas_dataframe_copy.columns = pandas.Index(new_col_ids) + pandas_dataframe_copy[ordering_col] = np.arange(pandas_dataframe_copy.shape[0]) + + # Specify the datetime dtypes, which is auto-detected as timestamp types. + schema: list[bigquery.SchemaField] = [] + for column, dtype in zip(pandas_dataframe.columns, pandas_dataframe.dtypes): + if dtype == "timestamp[us][pyarrow]": + schema.append( + bigquery.SchemaField(column, bigquery.enums.SqlTypeNames.DATETIME) ) - return self._read_pandas_inline(pandas_dataframe) - elif write_engine == "bigquery_load": - return self._loader.read_pandas(pandas_dataframe, method="load") - elif write_engine == "bigquery_streaming": - return self._loader.read_pandas(pandas_dataframe, method="stream") - elif write_engine == "bigquery_write": - return self._loader.read_pandas(pandas_dataframe, method="write") - elif write_engine == "_deferred": - import bigframes.dataframe as dataframe - - return dataframe.DataFrame(blocks.Block.from_local(pandas_dataframe, self)) - else: - raise ValueError(f"Got unexpected write_engine '{write_engine}'") - def _read_pandas_inline( - self, pandas_dataframe: pandas.DataFrame - ) -> dataframe.DataFrame: - import bigframes.dataframe as dataframe - - local_block = blocks.Block.from_local(pandas_dataframe, self) - return dataframe.DataFrame(local_block) + # Clustering probably not needed anyways as pandas tables are small + cluster_cols = [ordering_col] - def read_arrow(self, pa_table: pa.Table) -> bigframes.dataframe.DataFrame: - """Load a PyArrow Table to a BigQuery DataFrames DataFrame. + job_config = bigquery.LoadJobConfig(schema=schema) + job_config.clustering_fields = cluster_cols + job_config.labels = {"bigframes-api": api_name} - Args: - pa_table (pyarrow.Table): - PyArrow table to load data from. + load_table_destination = self._create_session_table() + load_job = self.bqclient.load_table_from_dataframe( + pandas_dataframe_copy, + load_table_destination, + job_config=job_config, + ) + self._start_generic_job(load_job) - Returns: - bigframes.dataframe.DataFrame: - A new DataFrame representing the data from the PyArrow table. - """ - import bigframes.dataframe as dataframe + ordering = orderings.ExpressionOrdering( + ordering_value_columns=tuple([OrderingColumnReference(ordering_col)]), + total_ordering_columns=frozenset([ordering_col]), + integer_encoding=IntegerEncoding(True, is_sequential=True), + ) + table_expression = self.ibis_client.sql( + f"SELECT * FROM `{load_table_destination.table_id}`" + ) - local_block = blocks.Block.from_pyarrow(pa_table, self) - return dataframe.DataFrame(local_block) + # b/297590178 Potentially a bug in bqclient.load_table_from_dataframe(), that only when the DF is empty, the index columns disappear in table_expression. + if any( + [new_idx_id not in table_expression.columns for new_idx_id in new_idx_ids] + ): + new_idx_ids, idx_labels = [], [] + + df = self._read_gbq_with_ordering( + table_expression=table_expression, + col_labels=col_labels, + index_cols=new_idx_ids, + index_labels=idx_labels, + hidden_cols=(ordering_col,), + ordering=ordering, + is_total_ordering=True, + api_name=api_name, + ) + return df def read_csv( self, @@ -1271,13 +1024,7 @@ def read_csv( Union[MutableSequence[Any], np.ndarray[Any, Any], Tuple[Any, ...], range] ] = None, index_col: Optional[ - Union[ - int, - str, - Sequence[Union[str, int]], - bigframes.enums.DefaultIndexKind, - Literal[False], - ] + Union[int, str, Sequence[Union[str, int]], Literal[False]] ] = None, usecols: Optional[ Union[ @@ -1295,186 +1042,101 @@ def read_csv( Literal["c", "python", "pyarrow", "python-fwf", "bigquery"] ] = None, encoding: Optional[str] = None, - write_engine: constants.WriteEngineType = "default", - **kwargs, - ) -> dataframe.DataFrame: - bigframes.session.validation.validate_engine_compatibility( - engine=engine, - write_engine=write_engine, - ) - - if engine != "bigquery": - # Using pandas.read_csv by default and warning about potential issues with - # large files. - return self._read_csv_w_pandas_engines( - filepath_or_buffer, - sep=sep, - header=header, - names=names, - index_col=index_col, - usecols=usecols, # type: ignore - dtype=dtype, - engine=engine, - encoding=encoding, - write_engine=write_engine, - **kwargs, - ) - else: - return self._read_csv_w_bigquery_engine( - filepath_or_buffer, - sep=sep, - header=header, - names=names, - index_col=index_col, - usecols=usecols, # type: ignore - dtype=dtype, - encoding=encoding, - ) - - def _read_csv_w_pandas_engines( - self, - filepath_or_buffer, - *, - sep, - header, - names, - index_col, - usecols, - dtype, - engine, - encoding, - write_engine, **kwargs, ) -> dataframe.DataFrame: - """Reads a CSV file using pandas engines into a BigQuery DataFrames. + table = bigquery.Table(self._create_session_table()) - This method serves as the implementation backend for read_csv when the - specified engine is one supported directly by pandas ('c', 'python', - 'pyarrow'). - """ - if isinstance(index_col, bigframes.enums.DefaultIndexKind): - raise NotImplementedError( - f"With index_col={repr(index_col)}, only engine='bigquery' is supported. " - f"{constants.FEEDBACK_LINK}" - ) - if any(arg in kwargs for arg in ("chunksize", "iterator")): - raise NotImplementedError( - "'chunksize' and 'iterator' arguments are not supported. " - f"{constants.FEEDBACK_LINK}" - ) - if isinstance(filepath_or_buffer, str): - self._check_file_size(filepath_or_buffer) + if engine is not None and engine == "bigquery": + if any(param is not None for param in (dtype, names)): + not_supported = ("dtype", "names") + raise NotImplementedError( + f"BigQuery engine does not support these arguments: {not_supported}. " + f"{constants.FEEDBACK_LINK}" + ) - pandas_df = pandas.read_csv( - filepath_or_buffer, - sep=sep, - header=header, - names=names, - index_col=index_col, - usecols=usecols, # type: ignore - dtype=dtype, - engine=engine, - encoding=encoding, - **kwargs, - ) - return self._read_pandas(pandas_df, write_engine=write_engine) # type: ignore + if index_col is not None and ( + not index_col or not isinstance(index_col, str) + ): + raise NotImplementedError( + "BigQuery engine only supports a single column name for `index_col`. " + f"{constants.FEEDBACK_LINK}" + ) - def _read_csv_w_bigquery_engine( - self, - filepath_or_buffer, - *, - sep, - header, - names, - index_col, - usecols, - dtype, - encoding, - ) -> dataframe.DataFrame: - """Reads a CSV file using the BigQuery engine into a BigQuery DataFrames. + # None value for index_col cannot be passed to read_gbq + if index_col is None: + index_col = () + + # usecols should only be an iterable of strings (column names) for use as col_order in read_gbq. + col_order: Tuple[Any, ...] = tuple() + if usecols is not None: + if isinstance(usecols, Iterable) and all( + isinstance(col, str) for col in usecols + ): + col_order = tuple(col for col in usecols) + else: + raise NotImplementedError( + "BigQuery engine only supports an iterable of strings for `usecols`. " + f"{constants.FEEDBACK_LINK}" + ) - This method serves as the implementation backend for read_csv when the - 'bigquery' engine is specified or inferred. It leverages BigQuery's - native CSV loading capabilities, making it suitable for large datasets - that may not fit into local memory. - """ - if dtype is not None and not utils.is_dict_like(dtype): - raise ValueError("dtype should be a dict-like object.") - - if names is not None: - if len(names) != len(set(names)): - raise ValueError("Duplicated names are not allowed.") - if not ( - bigframes.core.utils.is_list_like(names, allow_sets=False) - or isinstance(names, abc.KeysView) - ): - raise ValueError("Names should be an ordered collection.") + if encoding is not None and encoding not in _VALID_ENCODINGS: + raise NotImplementedError( + f"BigQuery engine only supports the following encodings: {_VALID_ENCODINGS}. " + f"{constants.FEEDBACK_LINK}" + ) - if index_col is True: - raise ValueError("The value of index_col couldn't be 'True'") + job_config = bigquery.LoadJobConfig() + job_config.create_disposition = bigquery.CreateDisposition.CREATE_IF_NEEDED + job_config.source_format = bigquery.SourceFormat.CSV + job_config.write_disposition = bigquery.WriteDisposition.WRITE_EMPTY + job_config.autodetect = True + job_config.field_delimiter = sep + job_config.encoding = encoding + job_config.labels = {"bigframes-api": "read_csv"} - # None and False cannot be passed to read_gbq. - if index_col is None or index_col is False: - index_col = () + # We want to match pandas behavior. If header is 0, no rows should be skipped, so we + # do not need to set `skip_leading_rows`. If header is None, then there is no header. + # Setting skip_leading_rows to 0 does that. If header=N and N>0, we want to skip N rows. + if header is None: + job_config.skip_leading_rows = 0 + elif header > 0: + job_config.skip_leading_rows = header - # usecols should only be an iterable of strings (column names) for use as columns in read_gbq. - columns: Tuple[Any, ...] = tuple() - if usecols is not None: - if isinstance(usecols, Iterable) and all( - isinstance(col, str) for col in usecols - ): - columns = tuple(col for col in usecols) - else: + return self._read_bigquery_load_job( + filepath_or_buffer, + table, + job_config=job_config, + index_col=index_col, + col_order=col_order, + ) + else: + if any(arg in kwargs for arg in ("chunksize", "iterator")): raise NotImplementedError( - "BigQuery engine only supports an iterable of strings for `usecols`. " + "'chunksize' and 'iterator' arguments are not supported. " f"{constants.FEEDBACK_LINK}" ) - if encoding is not None and encoding not in _VALID_ENCODINGS: - raise NotImplementedError( - f"BigQuery engine only supports the following encodings: {_VALID_ENCODINGS}. " - f"{constants.FEEDBACK_LINK}" + if isinstance(filepath_or_buffer, str): + self._check_file_size(filepath_or_buffer) + pandas_df = pandas.read_csv( + filepath_or_buffer, + sep=sep, + header=header, + names=names, + index_col=index_col, + usecols=usecols, + dtype=dtype, + engine=engine, + encoding=encoding, + **kwargs, ) - - job_config = bigquery.LoadJobConfig() - job_config.source_format = bigquery.SourceFormat.CSV - job_config.autodetect = True - job_config.field_delimiter = sep - job_config.encoding = encoding - job_config.labels = {"bigframes-api": "read_csv"} - - # b/409070192: When header > 0, pandas and BigFrames returns different column naming. - - # We want to match pandas behavior. If header is 0, no rows should be skipped, so we - # do not need to set `skip_leading_rows`. If header is None, then there is no header. - # Setting skip_leading_rows to 0 does that. If header=N and N>0, we want to skip N rows. - if header is None: - job_config.skip_leading_rows = 0 - elif header > 0: - job_config.skip_leading_rows = header + 1 - - table_id = self._loader.load_file(filepath_or_buffer, job_config=job_config) - df = self._loader.read_gbq_table( - table_id, - index_col=index_col, - columns=columns, - names=names, - index_col_in_columns=True, - ) - - if dtype is not None: - for column, dtype in dtype.items(): - if column in df.columns: - df[column] = df[column].astype(dtype) - return df + return self.read_pandas(pandas_df) def read_pickle( self, filepath_or_buffer: FilePath | ReadPickleBuffer, compression: CompressionOptions = "infer", storage_options: StorageOptions = None, - *, - write_engine: constants.WriteEngineType = "default", ): pandas_obj = pandas.read_pickle( filepath_or_buffer, @@ -1484,137 +1146,27 @@ def read_pickle( if isinstance(pandas_obj, pandas.Series): if pandas_obj.name is None: - pandas_obj.name = 0 - bigframes_df = self._read_pandas(pandas_obj.to_frame()) + pandas_obj.name = "0" + bigframes_df = self.read_pandas(pandas_obj.to_frame()) return bigframes_df[bigframes_df.columns[0]] - return self._read_pandas(pandas_obj, write_engine=write_engine) + return self._read_pandas(pandas_obj, "read_pickle") def read_parquet( self, path: str | IO["bytes"], - *, - engine: str = "auto", - write_engine: constants.WriteEngineType = "default", - ) -> dataframe.DataFrame: - bigframes.session.validation.validate_engine_compatibility( - engine=engine, - write_engine=write_engine, - ) - if engine == "bigquery": - job_config = bigquery.LoadJobConfig() - job_config.source_format = bigquery.SourceFormat.PARQUET - - # Ensure we can load pyarrow.list_ / BQ ARRAY type. - # See internal issue 414374215. - parquet_options = bigquery.ParquetOptions() - parquet_options.enable_list_inference = True - job_config.parquet_options = parquet_options - - job_config.labels = {"bigframes-api": "read_parquet"} - table_id = self._loader.load_file(path, job_config=job_config) - return self._loader.read_gbq_table(table_id) - else: - if "*" in path: - raise ValueError( - "The provided path contains a wildcard character (*), which is not " - "supported by the current engine. To read files from wildcard paths, " - "please use the 'bigquery' engine by setting `engine='bigquery'` in " - "the function call." - ) - - read_parquet_kwargs: Dict[str, Any] = {} - if pandas.__version__.startswith("1."): - read_parquet_kwargs["use_nullable_dtypes"] = True - else: - read_parquet_kwargs["dtype_backend"] = "pyarrow" - - pandas_obj = pandas.read_parquet( - path, - engine=engine, # type: ignore - **read_parquet_kwargs, - ) - return self._read_pandas(pandas_obj, write_engine=write_engine) - - def read_orc( - self, - path: str | IO["bytes"], - *, - engine: str = "auto", - write_engine: constants.WriteEngineType = "default", - ) -> dataframe.DataFrame: - """Load an ORC file to a BigQuery DataFrames DataFrame. - - Args: - path (str or IO): - The path or buffer to the ORC file. Can be a local path or Google Cloud Storage URI. - engine (str, default "auto"): - The engine used to read the file. Supported values: `auto`, `bigquery`, `pyarrow`. - write_engine (str, default "default"): - The write engine used to persist the data to BigQuery if needed. - - Returns: - bigframes.pandas.DataFrame: - A new DataFrame representing the data from the ORC file. - """ - bigframes.session.validation.validate_engine_compatibility( - engine=engine, - write_engine=write_engine, - ) - if engine == "bigquery": - job_config = bigquery.LoadJobConfig() - job_config.source_format = bigquery.SourceFormat.ORC - job_config.labels = {"bigframes-api": "read_orc"} - table_id = self._loader.load_file(path, job_config=job_config) - return self._loader.read_gbq_table(table_id) - elif engine in ("auto", "pyarrow"): - if isinstance(path, str) and "*" in path: - raise ValueError( - "The provided path contains a wildcard character (*), which is not " - "supported by the current engine. To read files from wildcard paths, " - "please use the 'bigquery' engine by setting `engine='bigquery'` in " - "your configuration." - ) - - read_orc_kwargs: Dict[str, Any] = {} - if not pandas.__version__.startswith("1."): - read_orc_kwargs["dtype_backend"] = "pyarrow" - - pandas_obj = pandas.read_orc(path, **read_orc_kwargs) - return self._read_pandas(pandas_obj, write_engine=write_engine) - else: - raise ValueError( - f"Unsupported engine: {repr(engine)}. Supported values: 'auto', 'bigquery', 'pyarrow'." - ) - - def read_avro( - self, - path: str | IO["bytes"], - *, - engine: str = "auto", ) -> dataframe.DataFrame: - """Load an Avro file to a BigQuery DataFrames DataFrame. - - Args: - path (str or IO): - The path or buffer to the Avro file. Can be a local path or Google Cloud Storage URI. - engine (str, default "auto"): - The engine used to read the file. Only `bigquery` is supported for Avro. - - Returns: - bigframes.pandas.DataFrame: - A new DataFrame representing the data from the Avro file. - """ - if engine not in ("auto", "bigquery"): - raise ValueError( - f"Unsupported engine: {repr(engine)}. Supported values: 'auto', 'bigquery'." - ) + # Note: "engine" is omitted because it is redundant. Loading a table + # from a pandas DataFrame will just create another parquet file + load + # job anyway. + table = bigquery.Table(self._create_session_table()) job_config = bigquery.LoadJobConfig() - job_config.use_avro_logical_types = True - job_config.source_format = bigquery.SourceFormat.AVRO - job_config.labels = {"bigframes-api": "read_avro"} - table_id = self._loader.load_file(path, job_config=job_config) - return self._loader.read_gbq_table(table_id) + job_config.create_disposition = bigquery.CreateDisposition.CREATE_IF_NEEDED + job_config.source_format = bigquery.SourceFormat.PARQUET + job_config.write_disposition = bigquery.WriteDisposition.WRITE_EMPTY + job_config.labels = {"bigframes-api": "read_parquet"} + + return self._read_bigquery_load_job(path, table, job_config=job_config) def read_json( self, @@ -1627,14 +1179,12 @@ def read_json( encoding: Optional[str] = None, lines: bool = False, engine: Literal["ujson", "pyarrow", "bigquery"] = "ujson", - write_engine: constants.WriteEngineType = "default", **kwargs, ) -> dataframe.DataFrame: - bigframes.session.validation.validate_engine_compatibility( - engine=engine, - write_engine=write_engine, - ) + table = bigquery.Table(self._create_session_table()) + if engine == "bigquery": + if dtype is not None: raise NotImplementedError( "BigQuery engine does not support the dtype arguments." @@ -1656,13 +1206,18 @@ def read_json( ) job_config = bigquery.LoadJobConfig() + job_config.create_disposition = bigquery.CreateDisposition.CREATE_IF_NEEDED job_config.source_format = bigquery.SourceFormat.NEWLINE_DELIMITED_JSON + job_config.write_disposition = bigquery.WriteDisposition.WRITE_EMPTY job_config.autodetect = True job_config.encoding = encoding job_config.labels = {"bigframes-api": "read_json"} - table_id = self._loader.load_file(path_or_buf, job_config=job_config) - return self._loader.read_gbq_table(table_id) + return self._read_bigquery_load_job( + path_or_buf, + table, + job_config=job_config, + ) else: if any(arg in kwargs for arg in ("chunksize", "iterator")): raise NotImplementedError( @@ -1692,35 +1247,21 @@ def read_json( engine=engine, **kwargs, ) - return self._read_pandas(pandas_df, write_engine=write_engine) + return self.read_pandas(pandas_df) def _check_file_size(self, filepath: str): max_size = 1024 * 1024 * 1024 # 1 GB in bytes if filepath.startswith("gs://"): # GCS file path - bucket_name, blob_path = filepath.split("/", 3)[2:] - - client = self._clients_provider.storageclient + client = storage.Client() + bucket_name, blob_name = filepath.split("/", 3)[2:] bucket = client.bucket(bucket_name) - - list_blobs_params = inspect.signature(bucket.list_blobs).parameters - if "match_glob" in list_blobs_params: - # Modern, efficient method for new library versions - matching_blobs = bucket.list_blobs(match_glob=blob_path) - file_size = sum(blob.size for blob in matching_blobs) - else: - # Fallback method for older library versions - prefix = blob_path.split("*", 1)[0] - all_blobs = bucket.list_blobs(prefix=prefix) - matching_blobs = [ - blob for blob in all_blobs if fnmatch.fnmatch(blob.name, blob_path) - ] - file_size = sum(blob.size for blob in matching_blobs) - elif os.path.exists(filepath): # local file path + blob = bucket.blob(blob_name) + blob.reload() + file_size = blob.size + else: # local file path file_size = os.path.getsize(filepath) - else: - file_size = None - if file_size is not None and file_size > max_size: + if file_size > max_size: # Convert to GB file_size = round(file_size / (1024**3), 1) max_size = int(max_size / 1024**3) @@ -1730,80 +1271,90 @@ def _check_file_size(self, filepath: str): "for large files to avoid loading the file into local memory." ) - def deploy_remote_function( + def _create_session_table(self) -> bigquery.TableReference: + table_name = f"{uuid.uuid4().hex}" + dataset = bigquery.Dataset( + bigquery.DatasetReference(self.bqclient.project, "_SESSION") + ) + return dataset.table(table_name) + + def _create_empty_temp_table( self, - func, - **kwargs, - ): - """Orchestrates the creation of a BigQuery remote function that deploys immediately. + schema: Iterable[bigquery.SchemaField], + cluster_cols: List[str], + ) -> bigquery.TableReference: + # Can't set a table in _SESSION as destination via query job API, so we + # run DDL, instead. + dataset = self._anonymous_dataset + expiration = ( + datetime.datetime.now(datetime.timezone.utc) + constants.DEFAULT_EXPIRATION + ) - This method ensures that the remote function is created and available for - use in BigQuery as soon as this call is made. + table = bigframes_io.create_temp_table( + self.bqclient, + dataset, + expiration, + schema=schema, + cluster_columns=cluster_cols, + ) + return bigquery.TableReference.from_string(table) - Args: - func: - Function to deploy. - kwargs: - All arguments are passed directly to - :meth:`~bigframes.session.Session.remote_function`. Please see - its docstring for parameter details. + def _create_sequential_ordering( + self, + table: ibis_types.Table, + index_cols: Iterable[str] = (), + api_name: str = "", + ) -> Tuple[ibis_types.Table, orderings.ExpressionOrdering]: + # Since this might also be used as the index, don't use the default + # "ordering ID" name. + default_ordering_name = guid.generate_guid("bigframes_ordering_") + default_ordering_col = ( + ibis.row_number().cast(ibis_dtypes.int64).name(default_ordering_name) + ) + table = table.mutate(**{default_ordering_name: default_ordering_col}) + table_ref = self._ibis_to_session_table( + table, + cluster_cols=list(index_cols) + [default_ordering_name], + api_name=api_name, + ) + table = self.ibis_client.table( + f"{table_ref.project}.{table_ref.dataset_id}.{table_ref.table_id}" + ) + ordering_reference = core.OrderingColumnReference(default_ordering_name) + ordering = orderings.ExpressionOrdering( + ordering_value_columns=tuple([ordering_reference]), + total_ordering_columns=frozenset([default_ordering_name]), + integer_encoding=IntegerEncoding(is_encoded=True, is_sequential=True), + ) + return table, ordering - Returns: - A wrapped remote function, usable in - :meth:`~bigframes.series.Series.apply`. - """ - return self._function_session.deploy_remote_function( - func, - **kwargs, + def _ibis_to_session_table( + self, + table: ibis_types.Table, + cluster_cols: Iterable[str], + api_name: str, + ) -> bigquery.TableReference: + destination, _ = self._query_to_destination( + self.ibis_client.compile(table), + index_cols=list(cluster_cols), + api_name=api_name, ) + # There should always be a destination table for this query type. + return typing.cast(bigquery.TableReference, destination) def remote_function( self, - # Make sure that the input/output types, and dataset can be used - # positionally. This avoids the worst of the breaking change from 1.x to - # 2.x while still preventing possible mixups between consecutive str - # parameters. - input_types: Union[None, type, Sequence[type]] = None, - output_type: Optional[type] = None, + input_types: List[type], + output_type: type, dataset: Optional[str] = None, - *, bigquery_connection: Optional[str] = None, reuse: bool = True, name: Optional[str] = None, packages: Optional[Sequence[str]] = None, - cloud_function_service_account: str, - cloud_function_kms_key_name: Optional[str] = None, - cloud_function_docker_repository: Optional[str] = None, - max_batching_rows: Optional[int] = 1000, - cloud_function_timeout: Optional[int] = 600, - cloud_function_max_instances: Optional[int] = None, - cloud_function_vpc_connector: Optional[str] = None, - cloud_function_vpc_connector_egress_settings: Optional[ - Literal["all", "private-ranges-only", "unspecified"] - ] = None, - cloud_function_memory_mib: Optional[int] = None, - cloud_function_cpus: Optional[float] = None, - cloud_function_ingress_settings: Literal[ - "all", "internal-only", "internal-and-gclb" - ] = "internal-only", - cloud_build_service_account: Optional[str] = None, ): """Decorator to turn a user defined function into a BigQuery remote function. Check out the code samples at: https://cloud.google.com/bigquery/docs/remote-functions#bigquery-dataframes. - .. note:: - ``input_types=Series`` scenario is in preview. It currently only - supports dataframe with column types ``Int64``/``Float64``/``boolean``/ - ``string``/``binary[pyarrow]``. - - .. warning:: - To use remote functions with Bigframes 2.0 and onwards, please (preferred) - set an explicit user-managed ``cloud_function_service_account`` or (discouraged) - set ``cloud_function_service_account`` to use the Compute Engine service account - by setting it to `"default"`. - - See, https://cloud.google.com/functions/docs/securing/function-identity. - .. note:: Please make sure following is setup before using this API: @@ -1842,19 +1393,10 @@ def remote_function( `$ gcloud projects add-iam-policy-binding PROJECT_ID --member="serviceAccount:CONNECTION_SERVICE_ACCOUNT_ID" --role="roles/run.invoker"`. Args: - input_types (type or sequence(type), Optional): - For scalar user defined function it should be the input type or - sequence of input types. The supported scalar input types are - `bool`, `bytes`, `float`, `int`, `str`. For row processing user - defined function (i.e. functions that receive a single input - representing a row in form of a Series), type `Series` should be - specified. - output_type (type, Optional): - Data type of the output in the user defined function. If the - user defined function returns an array, then `list[type]` should - be specified. The supported output types are `bool`, `bytes`, - `float`, `int`, `str`, `list[bool]`, `list[float]`, `list[int]` - and `list[str]`. + input_types (list(type)): + List of input data types in the user defined function. + output_type (type): + Data type of the output in the user defined function. dataset (str, Optional): Dataset in which to create a BigQuery remote function. It should be in `.` or `` format. If this @@ -1868,353 +1410,43 @@ def remote_function( reuse (bool, Optional): Reuse the remote function if already exists. `True` by default, which will result in reusing an existing remote - function and corresponding cloud function that was previously - created (if any) for the same udf. - Please note that for an unnamed (i.e. created without an explicit - `name` argument) remote function, the BigQuery DataFrames - session id is attached in the cloud artifacts names. So for the - effective reuse across the sessions it is recommended to create - the remote function with an explicit `name`. + function and corresponding cloud function (if any) that was + previously created for the same udf. Setting it to `False` would force creating a unique remote function. If the required remote function does not exist then it would be created irrespective of this param. name (str, Optional): - Explicit name of the persisted BigQuery remote function. Use it - with caution, because more than one users working in the same - project and dataset could overwrite each other's remote - functions if they use the same persistent name. When an explicit - name is provided, any session specific clean up ( - ``bigframes.session.Session.close``/ - ``bigframes.pandas.close_session``/ - ``bigframes.pandas.reset_session``/ - ``bigframes.pandas.clean_up_by_session_id``) does not clean up - the function, and leaves it for the user to manage the function - and the associated cloud function directly. + Explicit name of the persisted BigQuery remote function. Use it with + caution, because two users working in the same project and dataset + could overwrite each other's remote functions if they use the same + persistent name. packages (str[], Optional): Explicit name of the external package dependencies. Each dependency is added to the `requirements.txt` as is, and can be of the form supported in https://pip.pypa.io/en/stable/reference/requirements-file-format/. - cloud_function_service_account (str): - Service account to use for the cloud functions. If "default" provided - then the default service account would be used. See - https://cloud.google.com/functions/docs/securing/function-identity - for more details. Please make sure the service account has the - necessary IAM permissions configured as described in - https://cloud.google.com/functions/docs/reference/iam/roles#additional-configuration. - cloud_function_kms_key_name (str, Optional): - Customer managed encryption key to protect cloud functions and - related data at rest. This is of the format - projects/PROJECT_ID/locations/LOCATION/keyRings/KEYRING/cryptoKeys/KEY. - Read https://cloud.google.com/functions/docs/securing/cmek for - more details including granting necessary service accounts - access to the key. - cloud_function_docker_repository (str, Optional): - Docker repository created with the same encryption key as - `cloud_function_kms_key_name` to store encrypted artifacts - created to support the cloud function. This is of the format - projects/PROJECT_ID/locations/LOCATION/repositories/REPOSITORY_NAME. - For more details see - https://cloud.google.com/functions/docs/securing/cmek#before_you_begin. - max_batching_rows (int, Optional): - The maximum number of rows to be batched for processing in the - BQ remote function. Default value is 1000. A lower number can be - passed to avoid timeouts in case the user code is too complex to - process large number of rows fast enough. A higher number can be - used to increase throughput in case the user code is fast enough. - `None` can be passed to let BQ remote functions service apply - default batching. See for more details - https://cloud.google.com/bigquery/docs/remote-functions#limiting_number_of_rows_in_a_batch_request. - cloud_function_timeout (int, Optional): - The maximum amount of time (in seconds) BigQuery should wait for - the cloud function to return a response. See for more details - https://cloud.google.com/functions/docs/configuring/timeout. - Please note that even though the cloud function (2nd gen) itself - allows seeting up to 60 minutes of timeout, BigQuery remote - function can wait only up to 20 minutes, see for more details - https://cloud.google.com/bigquery/quotas#remote_function_limits. - By default BigQuery DataFrames uses a 10 minute timeout. `None` - can be passed to let the cloud functions default timeout take effect. - cloud_function_max_instances (int, Optional): - The maximumm instance count for the cloud function created. This - can be used to control how many cloud function instances can be - active at max at any given point of time. Lower setting can help - control the spike in the billing. Higher setting can help - support processing larger scale data. When not specified, cloud - function's default setting applies. For more details see - https://cloud.google.com/functions/docs/configuring/max-instances. - cloud_function_vpc_connector (str, Optional): - The VPC connector you would like to configure for your cloud - function. This is useful if your code needs access to data or - service(s) that are on a VPC network. See for more details - https://cloud.google.com/functions/docs/networking/connecting-vpc. - cloud_function_vpc_connector_egress_settings (str, Optional): - Egress settings for the VPC connector, controlling what outbound - traffic is routed through the VPC connector. - Options are: `all`, `private-ranges-only`, or `unspecified`. - If not specified, `private-ranges-only` is used by default. - See for more details - https://cloud.google.com/run/docs/configuring/vpc-connectors#egress-job. - cloud_function_memory_mib (int, Optional): - The amounts of memory (in mebibytes) to allocate for the cloud - function (2nd gen) created. This also dictates a corresponding - amount of allocated CPU for the function. By default a memory of - 1024 MiB is set for the cloud functions created to support - BigQuery DataFrames remote function. If you want to let the - default memory of cloud functions be allocated, pass `None`. See - for more details - https://cloud.google.com/functions/docs/configuring/memory. - cloud_function_cpus (float, Optional): - The number of cpus to allocate for the cloud - function (2nd gen) created. - https://docs.cloud.google.com/run/docs/configuring/services/cpu. - cloud_function_ingress_settings (str, Optional): - Ingress settings controls dictating what traffic can reach the - function. Options are: `all`, `internal-only`, or `internal-and-gclb`. - If no setting is provided, `internal-only` will be used by default. - See for more details - https://cloud.google.com/functions/docs/networking/network-settings#ingress_settings. - cloud_build_service_account (str, Optional): - Service account in the fully qualified format - `projects/PROJECT_ID/serviceAccounts/SERVICE_ACCOUNT_EMAIL`, or - just the SERVICE_ACCOUNT_EMAIL. The latter would be interpreted - as belonging to the BigQuery DataFrames session project. This is - to be used by Cloud Build to build the function source code into - a deployable artifact. If not provided, the default Cloud Build - service account is used. See - https://cloud.google.com/build/docs/cloud-build-service-account - for more details. Returns: - collections.abc.Callable: - A remote function object pointing to the cloud assets created - in the background to support the remote execution. The cloud assets can be - located through the following properties set in the object: + callable: A remote function object pointing to the cloud assets created + in the background to support the remote execution. The cloud assets can be + located through the following properties set in the object: - `bigframes_cloud_function` - The google cloud function deployed for the user defined code. + `bigframes_cloud_function` - The google cloud function deployed for the user defined code. - `bigframes_remote_function` - The bigquery remote function capable of calling into `bigframes_cloud_function`. + `bigframes_remote_function` - The bigquery remote function capable of calling into `bigframes_cloud_function`. """ - return self._function_session.remote_function( - # User-provided arguments. - input_types=input_types, - output_type=output_type, + return bigframes_rf( + input_types, + output_type, + session=self, dataset=dataset, bigquery_connection=bigquery_connection, reuse=reuse, name=name, packages=packages, - cloud_function_service_account=cloud_function_service_account, - cloud_function_kms_key_name=cloud_function_kms_key_name, - cloud_function_docker_repository=cloud_function_docker_repository, - max_batching_rows=max_batching_rows, - cloud_function_timeout=cloud_function_timeout, - cloud_function_max_instances=cloud_function_max_instances, - cloud_function_vpc_connector=cloud_function_vpc_connector, - cloud_function_vpc_connector_egress_settings=cloud_function_vpc_connector_egress_settings, - cloud_function_memory_mib=cloud_function_memory_mib, - cloud_function_cpus=cloud_function_cpus, - cloud_function_ingress_settings=cloud_function_ingress_settings, - cloud_build_service_account=cloud_build_service_account, - ) - - def deploy_udf( - self, - func, - **kwargs, - ): - """Orchestrates the creation of a BigQuery UDF that deploys immediately. - - This method ensures that the UDF is created and available for - use in BigQuery as soon as this call is made. - - Args: - func: - Function to deploy. - kwargs: - All arguments are passed directly to - :meth:`~bigframes.session.Session.udf`. Please see - its docstring for parameter details. - - Returns: - A wrapped Python user defined function, usable in - :meth:`~bigframes.series.Series.apply`. - """ - return self._function_session.deploy_udf( - func, - **kwargs, - ) - - def udf( - self, - *, - input_types: Union[None, type, Sequence[type]] = None, - output_type: Optional[type] = None, - dataset: Optional[str] = None, - bigquery_connection: Optional[str] = None, - name: Optional[str] = None, - packages: Optional[Sequence[str]] = None, - max_batching_rows: Optional[int] = None, - container_cpu: Optional[float] = None, - container_memory: Optional[str] = None, - ): - """Decorator to turn a Python user defined function (udf) into a - [BigQuery managed user-defined function](https://cloud.google.com/bigquery/docs/user-defined-functions-python). - - .. note:: - This feature is in preview. The code in the udf must be - (1) self-contained, i.e. it must not contain any - references to an import or variable defined outside the function - body, and - (2) Python 3.11 compatible, as that is the environment - in which the code is executed in the cloud. - - .. note:: - Please have BigQuery Data Editor (roles/bigquery.dataEditor) IAM - role enabled for you. - - **Examples:** - - >>> import datetime - - Turning an arbitrary python function into a BigQuery managed python udf: - - >>> bq_name = datetime.datetime.now().strftime("bigframes_%Y%m%d%H%M%S%f") - >>> @bpd.udf(dataset="bigfranes_testing", name=bq_name) # doctest: +SKIP - ... def minutes_to_hours(x: int) -> float: - ... return x/60 - - >>> minutes = bpd.Series([0, 30, 60, 90, 120]) - >>> minutes - 0 0 - 1 30 - 2 60 - 3 90 - 4 120 - dtype: Int64 - - >>> hours = minutes.apply(minutes_to_hours) # doctest: +SKIP - >>> hours # doctest: +SKIP - 0 0.0 - 1 0.5 - 2 1.0 - 3 1.5 - 4 2.0 - dtype: Float64 - - To turn a user defined function with external package dependencies into - a BigQuery managed python udf, you would provide the names of the - packages (optionally with the package version) via `packages` param. - - >>> bq_name = datetime.datetime.now().strftime("bigframes_%Y%m%d%H%M%S%f") - >>> @bpd.udf( # doctest: +SKIP - ... dataset="bigfranes_testing", - ... name=bq_name, - ... packages=["cryptography"] - ... ) - ... def get_hash(input: str) -> str: - ... from cryptography.fernet import Fernet - ... - ... # handle missing value - ... if input is None: - ... input = "" - ... - ... key = Fernet.generate_key() - ... f = Fernet(key) - ... return f.encrypt(input.encode()).decode() - - >>> names = bpd.Series(["Alice", "Bob"]) - >>> hashes = names.apply(get_hash) # doctest: +SKIP - - You can clean-up the BigQuery functions created above using the BigQuery - client from the BigQuery DataFrames session: - - >>> session = bpd.get_global_session() # doctest: +SKIP - >>> session.bqclient.delete_routine(minutes_to_hours.bigframes_bigquery_function) # doctest: +SKIP - >>> session.bqclient.delete_routine(get_hash.bigframes_bigquery_function) # doctest: +SKIP - - Args: - input_types (type or sequence(type), Optional): - For scalar user defined function it should be the input type or - sequence of input types. The supported scalar input types are - `bool`, `bytes`, `float`, `int`, `str`. - output_type (type, Optional): - Data type of the output in the user defined function. If the - user defined function returns an array, then `list[type]` should - be specified. The supported output types are `bool`, `bytes`, - `float`, `int`, `str`, `list[bool]`, `list[float]`, `list[int]` - and `list[str]`. - dataset (str, Optional): - Dataset in which to create a BigQuery managed function. It - should be in `.` or `` - format. - bigquery_connection (str, Optional): - Name of the BigQuery connection. It is used to provide an - identity to the serverless instances running the user code. It - helps BigQuery manage and track the resources used by the udf. - This connection is required for internet access and for - interacting with other GCP services. To access GCP services, the - appropriate IAM permissions must also be granted to the - connection's Service Account. When it defaults to None, the udf - will be created without any connection. A udf without a - connection has no internet access and no access to other GCP - services. - name (str): - Explicit name of the persisted BigQuery managed function. Use it - with caution, because more than one users working in the same - project and dataset could overwrite each other's managed - functions if they use the same persistent name. Please note that - any session specific clean up ( - ``bigframes.session.Session.close``/ - ``bigframes.pandas.close_session``/ - ``bigframes.pandas.reset_session``/ - ``bigframes.pandas.clean_up_by_session_id``) does not clean up - this function, and leaves it for the user to manage the function - directly. - packages (str[], Optional): - Explicit name of the external package dependencies. Each - dependency is added to the `requirements.txt` as is, and can be - of the form supported in - https://pip.pypa.io/en/stable/reference/requirements-file-format/. - max_batching_rows (int, Optional): - The maximum number of rows in each batch. If you specify - max_batching_rows, BigQuery determines the number of rows in a - batch, up to the max_batching_rows limit. If max_batching_rows - is not specified, the number of rows to batch is determined - automatically. - container_cpu (float, Optional): - The CPU limits for containers that run Python UDFs. By default, - the CPU allocated is 0.33 vCPU. See details at - https://cloud.google.com/bigquery/docs/user-defined-functions-python#configure-container-limits. - container_memory (str, Optional): - The memory limits for containers that run Python UDFs. By - default, the memory allocated to each container instance is - 512 MiB. See details at - https://cloud.google.com/bigquery/docs/user-defined-functions-python#configure-container-limits. - Returns: - collections.abc.Callable: - A managed function object pointing to the cloud assets created - in the background to support the remote execution. The cloud - ssets can be located through the following properties set in the - object: - - `bigframes_bigquery_function` - The bigquery managed function - deployed for the user defined code. - """ - return self._function_session.udf( - input_types=input_types, - output_type=output_type, - dataset=dataset, - bigquery_connection=bigquery_connection, - name=name, - packages=packages, - max_batching_rows=max_batching_rows, - container_cpu=container_cpu, - container_memory=container_memory, ) def read_gbq_function( self, function_name: str, - is_row_processor: bool = False, ): """Loads a BigQuery function from BigQuery. @@ -2224,398 +1456,108 @@ def read_gbq_function( The return type of the function must be explicitly specified in the function's original definition even if not otherwise required. - BigQuery Utils provides many public functions under the ``bqutil`` project on Google Cloud Platform project - (See: https://github.com/GoogleCloudPlatform/bigquery-utils/tree/master/udfs#using-the-udfs). - You can checkout Community UDFs to use community-contributed functions. - (See: https://github.com/GoogleCloudPlatform/bigquery-utils/tree/master/udfs/community#community-udfs). - **Examples:** - Use the [cw_lower_case_ascii_only](https://github.com/GoogleCloudPlatform/bigquery-utils/blob/master/udfs/community/README.md#cw_lower_case_ascii_onlystr-string) - function from Community UDFs. - >>> import bigframes.pandas as bpd - >>> func = bpd.read_gbq_function("bqutil.fn.cw_lower_case_ascii_only") - - You can run it on scalar input. Usually you would do so to verify that - it works as expected before applying to all values in a Series. - - >>> func('AURÉLIE') - 'aurÉlie' - - You can apply it to a BigQuery DataFrames Series. - - >>> df = bpd.DataFrame({'id': [1, 2, 3], 'name': ['AURÉLIE', 'CÉLESTINE', 'DAPHNÉ']}) - >>> df - id name - 0 1 AURÉLIE - 1 2 CÉLESTINE - 2 3 DAPHNÉ - - [3 rows x 2 columns] - - >>> df1 = df.assign(new_name=df['name'].apply(func)) - >>> df1 - id name new_name - 0 1 AURÉLIE aurÉlie - 1 2 CÉLESTINE cÉlestine - 2 3 DAPHNÉ daphnÉ - - [3 rows x 3 columns] - - You can even use a function with multiple inputs. For example, - [cw_regexp_replace_5](https://github.com/GoogleCloudPlatform/bigquery-utils/blob/master/udfs/community/README.md#cw_regexp_replace_5haystack-string-regexp-string-replacement-string-offset-int64-occurrence-int64) - from Community UDFs. - - >>> func = bpd.read_gbq_function("bqutil.fn.cw_regexp_replace_5") - >>> func('TestStr123456', 'Str', 'Cad$', 1, 1) - 'TestCad$123456' - - >>> df = bpd.DataFrame({ - ... "haystack" : ["TestStr123456", "TestStr123456Str", "TestStr123456Str"], - ... "regexp" : ["Str", "Str", "Str"], - ... "replacement" : ["Cad$", "Cad$", "Cad$"], - ... "offset" : [1, 1, 1], - ... "occurrence" : [1, 2, 1] - ... }) - >>> df - haystack regexp replacement offset occurrence - 0 TestStr123456 Str Cad$ 1 1 - 1 TestStr123456Str Str Cad$ 1 2 - 2 TestStr123456Str Str Cad$ 1 1 - - [3 rows x 5 columns] - >>> df.apply(func, axis=1) - 0 TestCad$123456 - 1 TestStr123456Cad$ - 2 TestCad$123456Str - dtype: string - - Another use case is to define your own remote function and use it later. - For example, define the remote function: - - >>> @bpd.remote_function(cloud_function_service_account="default") # doctest: +SKIP - ... def tenfold(num: int) -> float: - ... return num * 10 - - Then, read back the deployed BQ remote function: - - >>> tenfold_ref = bpd.read_gbq_function( # doctest: +SKIP - ... tenfold.bigframes_remote_function, - ... ) - - >>> df = bpd.DataFrame({'a': [1, 2], 'b': [3, 4], 'c': [5, 6]}) - >>> df - a b c - 0 1 3 5 - 1 2 4 6 - - [2 rows x 3 columns] - - >>> df['a'].apply(tenfold_ref) # doctest: +SKIP - 0 10.0 - 1 20.0 - Name: a, dtype: Float64 - - It also supports row processing by using `is_row_processor=True`. Please - note, row processor implies that the function has only one input - parameter. + >>> bpd.options.display.progress_bar = None - >>> @bpd.remote_function(cloud_function_service_account="default") # doctest: +SKIP - ... def row_sum(s: pd.Series) -> float: - ... return s['a'] + s['b'] + s['c'] - - >>> row_sum_ref = bpd.read_gbq_function( # doctest: +SKIP - ... row_sum.bigframes_remote_function, - ... is_row_processor=True, - ... ) - - >>> df = bpd.DataFrame({'a': [1, 2], 'b': [3, 4], 'c': [5, 6]}) - >>> df - a b c - 0 1 3 5 - 1 2 4 6 - - [2 rows x 3 columns] - - >>> df.apply(row_sum_ref, axis=1) # doctest: +SKIP - 0 9.0 - 1 12.0 - dtype: Float64 + >>> function_name = "bqutil.fn.cw_lower_case_ascii_only" + >>> func = bpd.read_gbq_function(function_name=function_name) + >>> func.bigframes_remote_function + 'bqutil.fn.cw_lower_case_ascii_only' Args: function_name (str): - The function's name in BigQuery in the format + the function's name in BigQuery in the format `project_id.dataset_id.function_name`, or `dataset_id.function_name` to load from the default project, or `function_name` to load from the default project and the dataset associated with the current session. - is_row_processor (bool, default False): - Whether the function is a row processor. This is set to True - for a function which receives an entire row of a DataFrame as - a pandas Series. Returns: - collections.abc.Callable: - A function object pointing to the BigQuery function read - from BigQuery. + callable: A function object pointing to the BigQuery function read + from BigQuery. - The object is similar to the one created by the `remote_function` - decorator, including the `bigframes_remote_function` property, but - not including the `bigframes_cloud_function` property. + The object is similar to the one created by the `remote_function` + decorator, including the `bigframes_remote_function` property, but + not including the `bigframes_cloud_function` property. """ - return bff.read_gbq_function( + return bigframes_rgf( function_name=function_name, session=self, - is_row_processor=is_row_processor, ) - def _prepare_copy_job_config(self) -> bigquery.CopyJobConfig: - # Create a copy so that we don't mutate the original config passed - job_config = bigquery.CopyJobConfig() - - if self._bq_kms_key_name: - job_config.destination_encryption_configuration = ( - bigquery.EncryptionConfiguration(kms_key_name=self._bq_kms_key_name) - ) - - return job_config - - def _start_query_ml_ddl( + def _start_query( self, sql: str, + job_config: Optional[bigquery.job.QueryJobConfig] = None, + max_results: Optional[int] = None, ) -> Tuple[bigquery.table.RowIterator, bigquery.QueryJob]: """ - Starts BigQuery ML DDL query job (CREATE MODEL/ALTER MODEL/...) and - waits for results. + Starts query job and waits for results. """ - job_config = typing.cast(bigquery.QueryJobConfig, bigquery.QueryJobConfig()) + job_config = self._prepare_job_config(job_config) + query_job = self.bqclient.query(sql, job_config=job_config) + + opts = bigframes.options.display + if opts.progress_bar is not None and not query_job.configuration.dry_run: + results_iterator = formatting_helpers.wait_for_query_job( + query_job, max_results, opts.progress_bar + ) + else: + results_iterator = query_job.result(max_results=max_results) + return results_iterator, query_job + + def _get_table_size(self, destination_table): + table = self.bqclient.get_table(destination_table) + return table.num_bytes + + def _rows_to_dataframe( + self, row_iterator: bigquery.table.RowIterator, dtypes: Dict + ) -> pandas.DataFrame: + arrow_table = row_iterator.to_arrow() + return bigframes.session._io.pandas.arrow_to_pandas(arrow_table, dtypes) + + def _start_generic_job(self, job: formatting_helpers.GenericJob): + if bigframes.options.display.progress_bar is not None: + formatting_helpers.wait_for_job( + job, bigframes.options.display.progress_bar + ) # Wait for the job to complete + else: + job.result() + + def _prepare_job_config( + self, job_config: Optional[bigquery.QueryJobConfig] = None + ) -> bigquery.QueryJobConfig: + if job_config is None: + job_config = self.bqclient.default_query_job_config if bigframes.options.compute.maximum_bytes_billed is not None: job_config.maximum_bytes_billed = ( bigframes.options.compute.maximum_bytes_billed ) - - # BQML expects kms_key_name through OPTIONS and not through job config, - # so we must reset any encryption set in the job config - # https://cloud.google.com/bigquery/docs/customer-managed-encryption#encrypt-model - job_config.destination_encryption_configuration = None - iterator, query_job = bf_io_bigquery.start_query_with_job( - self.bqclient, - sql, - job_config=job_config, - metrics=self._metrics, - location=None, - project=None, - timeout=None, - job_retry=third_party_gcb_retry.DEFAULT_ML_JOB_RETRY, - publisher=self._publisher, - session=self, - ) - return iterator, query_job - - def _from_glob_path( - self, path: str, *, connection: Optional[str] = None, name: Optional[str] = None - ) -> dataframe.DataFrame: - """Create a BigFrames DataFrame that contains a BigFrames ObjectRef column from a global wildcard path.""" - import bigframes.bigquery as bq - - connection = self._create_bq_connection(connection=connection) - table = self._create_object_table(path, connection) - s = bq.obj.make_ref( - self._loader.read_gbq_table(table)["uri"], authorizer=connection - ) - return s.rename(name).to_frame() - - def _create_object_table(self, path: str, connection: str) -> str: - """Create a random id Object Table from the input path and connection.""" - table = str(self._anon_dataset_manager.generate_unique_resource_id()) - - import textwrap - - sql = textwrap.dedent( - f""" - CREATE EXTERNAL TABLE `{table}` - WITH CONNECTION `{connection}` - OPTIONS( - object_metadata = 'SIMPLE', - uris = ['{path}']); - """ - ) - bf_io_bigquery.start_query_with_job( - self.bqclient, - sql, - job_config=bigquery.QueryJobConfig(), - metrics=self._metrics, - location=None, - project=None, - timeout=None, - publisher=self._publisher, - session=self, - ) - - return table - - def _create_temp_view(self, sql: str) -> bigquery.TableReference: - """Create a random id view from the sql string.""" - return self._anon_dataset_manager.create_temp_view(sql) - - def _create_temp_table( - self, schema: Sequence[bigquery.SchemaField], cluster_cols: Sequence[str] = [] - ) -> bigquery.TableReference: - """Allocate a random temporary table with the desired schema.""" - return self._temp_storage_manager.create_temp_table( - schema=schema, cluster_cols=cluster_cols - ) - - def _create_bq_connection( - self, - *, - connection: Optional[str] = None, - iam_role: Optional[str] = None, - ) -> str: - """Create the connection with the session settings and try to attach iam role to the connection SA. - If any of project, location or connection isn't specified, use the session defaults. Returns fully-qualified connection name. - """ - connection = self.bq_connection if not connection else connection - connection = bigframes.clients.get_canonical_bq_connection_id( - connection_id=connection, - default_project=self._project, - default_location=self._location, - ) - connection_parts = connection.split(".") - assert len(connection_parts) == 3 - - self.bqconnectionmanager.create_bq_connection( - project_id=connection_parts[0], - location=connection_parts[1], - connection_id=connection_parts[2], - iam_role=iam_role, - ) - - return connection - - # ========================================================================= - # bigframes.pandas attributes - # - # These are included so that Session and bigframes.pandas can be used - # interchangeably. - # ========================================================================= - def cut(self, *args, **kwargs) -> bigframes.series.Series: - """Cuts a BigQuery DataFrames object. - - Included for compatibility between bpd and Session. - - See :func:`bigframes.pandas.cut` for full documentation. - """ - import bigframes.core.reshape.tile - - return bigframes.core.reshape.tile.cut( - *args, - session=self, - **kwargs, - ) - - def crosstab(self, *args, **kwargs) -> dataframe.DataFrame: - """Compute a simple cross tabulation of two (or more) factors. - - Included for compatibility between bpd and Session. - - See :func:`bigframes.pandas.crosstab` for full documentation. - """ - import bigframes.core.reshape.pivot - - return bigframes.core.reshape.pivot.crosstab( - *args, - session=self, - **kwargs, - ) - - def DataFrame(self, *args, **kwargs): - """Constructs a DataFrame. - - Included for compatibility between bpd and Session. - - See :class:`bigframes.pandas.DataFrame` for full documentation. - """ - import bigframes.dataframe - - return bigframes.dataframe.DataFrame(*args, session=self, **kwargs) - - @property - def MultiIndex(self) -> bigframes.core.indexes.multi.MultiIndexAccessor: - """Constructs a MultiIndex. - - Included for compatibility between bpd and Session. - - See :class:`bigframes.pandas.MulitIndex` for full documentation. - """ - import bigframes.core.indexes.multi - - return bigframes.core.indexes.multi.MultiIndexAccessor(self) - - def Index(self, *args, **kwargs): - """Constructs a Index. - - Included for compatibility between bpd and Session. - - See :class:`bigframes.pandas.Index` for full documentation. - """ - import bigframes.core.indexes - - return bigframes.core.indexes.Index(*args, session=self, **kwargs) - - def Series(self, *args, **kwargs): - """Constructs a Series. - - Included for compatibility between bpd and Session. - - See :class:`bigframes.pandas.Series` for full documentation. - """ - import bigframes.series - - return bigframes.series.Series(*args, session=self, **kwargs) - - def to_datetime( - self, *args, **kwargs - ) -> Union[pandas.Timestamp, datetime.datetime, bigframes.series.Series]: - """Converts a BigQuery DataFrames object to datetime dtype. - - Included for compatibility between bpd and Session. - - See :func:`bigframes.pandas.to_datetime` for full documentation. - """ - import bigframes.core.tools - - return bigframes.core.tools.to_datetime( - *args, - session=self, - **kwargs, - ) - - def to_timedelta(self, *args, **kwargs): - """Converts a BigQuery DataFrames object to timedelta/duration dtype. - - Included for compatibility between bpd and Session. - - See :func:`bigframes.pandas.to_timedelta` for full documentation. - """ - import bigframes.pandas.core.tools.timedeltas - - return bigframes.pandas.core.tools.timedeltas.to_timedelta( - *args, - session=self, - **kwargs, - ) + return job_config def connect(context: Optional[bigquery_options.BigQueryOptions] = None) -> Session: return Session(context) -def _warn_if_bf_version_is_obsolete(): - today = datetime.datetime.today() - release_date = datetime.datetime.strptime(version.__release_date__, "%Y-%m-%d") - if today - release_date > datetime.timedelta(days=365): - msg = f"Your BigFrames version {version.__version__} is more than 1 year old. Please update to the lastest version." - warnings.warn(msg, bfe.ObsoleteVersionWarning) +def _can_cluster_bq(field: bigquery.SchemaField): + # https://cloud.google.com/bigquery/docs/clustered-tables + # Notably, float is excluded + type_ = field.field_type + return type_ in ( + "INTEGER", + "INT64", + "STRING", + "NUMERIC", + "DECIMAL", + "BIGNUMERIC", + "BIGDECIMAL", + "DATE", + "DATETIME", + "TIMESTAMP", + "BOOL", + "BOOLEAN", + ) diff --git a/bigframes/session/_io/bigquery.py b/bigframes/session/_io/bigquery.py new file mode 100644 index 00000000000..badc91e3565 --- /dev/null +++ b/bigframes/session/_io/bigquery.py @@ -0,0 +1,184 @@ +# Copyright 2023 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. + +"""Private module: Helpers for I/O operations.""" + +from __future__ import annotations + +import datetime +import textwrap +import types +from typing import Dict, Iterable, Optional, Union +import uuid + +import google.cloud.bigquery as bigquery + +IO_ORDERING_ID = "bqdf_row_nums" +TEMP_TABLE_PREFIX = "bqdf{date}_{random_id}" + + +def create_export_csv_statement( + table_id: str, uri: str, field_delimiter: str, header: bool +) -> str: + return create_export_data_statement( + table_id, + uri, + "CSV", + { + "field_delimiter": field_delimiter, + "header": header, + }, + ) + + +def create_export_data_statement( + table_id: str, uri: str, format: str, export_options: Dict[str, Union[bool, str]] +) -> str: + all_options: Dict[str, Union[bool, str]] = { + "uri": uri, + "format": format, + # TODO(swast): Does pandas have an option not to overwrite files? + "overwrite": True, + } + all_options.update(export_options) + export_options_str = ", ".join( + format_option(key, value) for key, value in all_options.items() + ) + # Manually generate ORDER BY statement since ibis will not always generate + # it in the top level statement. This causes BigQuery to then run + # non-distributed sort and run out of memory. + return textwrap.dedent( + f""" + EXPORT DATA + OPTIONS ( + {export_options_str} + ) AS + SELECT * EXCEPT ({IO_ORDERING_ID}) + FROM `{table_id}` + ORDER BY {IO_ORDERING_ID} + """ + ) + + +def random_table(dataset: bigquery.DatasetReference) -> bigquery.TableReference: + """Generate a random table ID with BigQuery DataFrames prefix. + Args: + dataset (google.cloud.bigquery.DatasetReference): + The dataset to make the table reference in. Usually the anonymous + dataset for the session. + Returns: + google.cloud.bigquery.TableReference: + Fully qualified table ID of a table that doesn't exist. + """ + now = datetime.datetime.now(datetime.timezone.utc) + random_id = uuid.uuid4().hex + table_id = TEMP_TABLE_PREFIX.format( + date=now.strftime("%Y%m%d"), random_id=random_id + ) + return dataset.table(table_id) + + +def table_ref_to_sql(table: bigquery.TableReference) -> str: + """Format a table reference as escaped SQL.""" + return f"`{table.project}`.`{table.dataset_id}`.`{table.table_id}`" + + +def create_snapshot_sql( + table_ref: bigquery.TableReference, current_timestamp: datetime.datetime +) -> str: + """Query a table via 'time travel' for consistent reads.""" + + # If we have a _SESSION table, assume that it's already a copy. Nothing to do here. + if table_ref.dataset_id.upper() == "_SESSION": + return f"SELECT * FROM `_SESSION`.`{table_ref.table_id}`" + + # If we have an anonymous query results table, it can't be modified and + # there isn't any BigQuery time travel. + if table_ref.dataset_id.startswith("_"): + return f"SELECT * FROM `{table_ref.project}`.`{table_ref.dataset_id}`.`{table_ref.table_id}`" + + return textwrap.dedent( + f""" + SELECT * + FROM `{table_ref.project}`.`{table_ref.dataset_id}`.`{table_ref.table_id}` + FOR SYSTEM_TIME AS OF TIMESTAMP({repr(current_timestamp.isoformat())}) + """ + ) + + +def create_temp_table( + bqclient: bigquery.Client, + dataset: bigquery.DatasetReference, + expiration: datetime.datetime, + *, + schema: Optional[Iterable[bigquery.SchemaField]] = None, + cluster_columns: Optional[list[str]] = None, +) -> str: + """Create an empty table with an expiration in the desired dataset.""" + table_ref = random_table(dataset) + destination = bigquery.Table(table_ref) + destination.expires = expiration + destination.schema = schema + if cluster_columns: + destination.clustering_fields = cluster_columns + bqclient.create_table(destination) + return f"{table_ref.project}.{table_ref.dataset_id}.{table_ref.table_id}" + + +# BigQuery REST API returns types in Legacy SQL format +# https://cloud.google.com/bigquery/docs/data-types but we use Standard SQL +# names +# https://cloud.google.com/bigquery/docs/reference/standard-sql/data-types +BQ_STANDARD_TYPES = types.MappingProxyType( + { + "BOOLEAN": "BOOL", + "INTEGER": "INT64", + "FLOAT": "FLOAT64", + } +) + + +def bq_field_to_type_sql(field: bigquery.SchemaField): + if field.mode == "REPEATED": + nested_type = bq_field_to_type_sql( + bigquery.SchemaField( + field.name, field.field_type, mode="NULLABLE", fields=field.fields + ) + ) + return f"ARRAY<{nested_type}>" + + if field.field_type == "RECORD": + nested_fields_sql = ", ".join( + bq_field_to_sql(child_field) for child_field in field.fields + ) + return f"STRUCT<{nested_fields_sql}>" + + type_ = field.field_type + return BQ_STANDARD_TYPES.get(type_, type_) + + +def bq_field_to_sql(field: bigquery.SchemaField): + name = field.name + type_ = bq_field_to_type_sql(field) + return f"`{name}` {type_}" + + +def bq_schema_to_sql(schema: Iterable[bigquery.SchemaField]): + return ", ".join(bq_field_to_sql(field) for field in schema) + + +def format_option(key: str, value: Union[bool, str]) -> str: + if isinstance(value, bool): + return f"{key}=true" if value else f"{key}=false" + return f"{key}={repr(value)}" diff --git a/bigframes/session/_io/bigquery/__init__.py b/bigframes/session/_io/bigquery/__init__.py deleted file mode 100644 index 3d60bcc8074..00000000000 --- a/bigframes/session/_io/bigquery/__init__.py +++ /dev/null @@ -1,705 +0,0 @@ -# Copyright 2023 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. - -"""Private module: Helpers for BigQuery I/O operations.""" - -from __future__ import annotations - -import datetime -import itertools -import re -import textwrap -import types -import typing -from typing import ( - Dict, - Iterable, - Mapping, - Optional, - Tuple, - Union, -) - -import bigframes_vendored.google_cloud_bigquery.retry as third_party_gcb_retry -import bigframes_vendored.pandas.io.gbq as third_party_pandas_gbq -import google.api_core.exceptions -import google.api_core.retry -import google.cloud.bigquery as bigquery -import google.cloud.bigquery._job_helpers -import google.cloud.bigquery.table - -import bigframes.core.events -import bigframes.core.sql -import bigframes.session.metrics -from bigframes.core.compile.sqlglot import sql as sg_sql -from bigframes.core.logging import log_adapter - -CHECK_DRIVE_PERMISSIONS = ( - "\nCheck https://cloud.google.com/bigquery/docs/" - "query-drive-data#Google_Drive_permissions." -) - - -IO_ORDERING_ID = "bqdf_row_nums" -_LIST_TABLES_LIMIT = 10000 # calls to bqclient.list_tables -# will be limited to this many tables - -_MAX_CLUSTER_COLUMNS = 4 - - -def create_job_configs_labels( - job_configs_labels: Optional[Dict[str, str]], - api_methods: typing.List[str], -) -> Dict[str, str]: - if job_configs_labels is None: - job_configs_labels = {} - else: - job_configs_labels = dict(job_configs_labels) - - if api_methods and "bigframes-api" not in job_configs_labels: - api_methods = list(api_methods) - job_configs_labels["bigframes-api"] = api_methods[0] - del api_methods[0] - - # Make sure we always populate bigframes-api with _something_, even if we - # have a code path which doesn't populate the list of api_methods. See - # internal issue 336521938. - job_configs_labels.setdefault("bigframes-api", "unknown") - - labels = list( - itertools.chain( - job_configs_labels.keys(), - (f"recent-bigframes-api-{i}" for i in range(len(api_methods))), - ) - ) - values = list(itertools.chain(job_configs_labels.values(), api_methods)) - return dict( - zip( - labels[: log_adapter.MAX_LABELS_COUNT], - values[: log_adapter.MAX_LABELS_COUNT], - ) - ) - - -def create_export_data_statement( - table_id: str, - uri: str, - format: str, - export_options: Dict[str, Union[bool, str]], -) -> str: - all_options: Dict[str, Union[bool, str]] = { - "uri": uri, - "format": format.upper(), - # TODO(swast): Does pandas have an option not to overwrite files? - "overwrite": True, - } - all_options.update(export_options) - export_options_str = ", ".join( - format_option(key, value) for key, value in all_options.items() - ) - # Manually generate ORDER BY statement since ibis will not always generate - # it in the top level statement. This causes BigQuery to then run - # non-distributed sort and run out of memory. - return textwrap.dedent( - f""" - EXPORT DATA - OPTIONS ( - {export_options_str} - ) AS - SELECT * EXCEPT ({IO_ORDERING_ID}) - FROM `{table_id}` - ORDER BY {IO_ORDERING_ID} - """ - ) - - -def table_ref_to_sql(table: bigquery.TableReference) -> str: - """Format a table reference as escaped SQL.""" - return f"`{table.project}`.`{table.dataset_id}`.`{table.table_id}`" - - -def create_temp_table( - bqclient: bigquery.Client, - table_ref: bigquery.TableReference, - expiration: datetime.datetime, - *, - schema: Optional[Iterable[bigquery.SchemaField]] = None, - cluster_columns: Optional[list[str]] = None, - kms_key: Optional[str] = None, - session=None, -) -> str: - """Create an empty table with an expiration in the desired session. - - The table will be deleted when the session is closed or the expiration - is reached. - """ - destination = bigquery.Table(table_ref) - destination.expires = expiration - destination.schema = schema - if cluster_columns: - destination.clustering_fields = cluster_columns - if kms_key: - enc_config = bigquery.EncryptionConfiguration(kms_key_name=kms_key) - destination.encryption_configuration = enc_config - # Ok if already exists, since this will only happen from retries - # internal to this method - # as the requested table id has a random UUID4 component. - bqclient.create_table(destination, exists_ok=True) - return f"{table_ref.project}.{table_ref.dataset_id}.{table_ref.table_id}" - - -def create_temp_view( - bqclient: bigquery.Client, - table_ref: bigquery.TableReference, - *, - expiration: datetime.datetime, - sql: str, - session=None, -) -> str: - """Create an empty table with an expiration in the desired session. - - The table will be deleted when the session is closed or the expiration - is reached. - """ - destination = bigquery.Table(table_ref) - destination.expires = expiration - destination.view_query = sql - - # Ok if already exists, since this will only happen from retries - # internal to this method - # as the requested table id has a random UUID4 component. - bqclient.create_table(destination, exists_ok=True) - return f"{table_ref.project}.{table_ref.dataset_id}.{table_ref.table_id}" - - -def set_table_expiration( - bqclient: bigquery.Client, - table_ref: bigquery.TableReference, - expiration: datetime.datetime, -) -> None: - """Set an expiration time for an existing BigQuery table.""" - table = bqclient.get_table(table_ref) - table.expires = expiration - bqclient.update_table(table, ["expires"]) - - -# BigQuery REST API returns types in Legacy SQL format -# https://cloud.google.com/bigquery/docs/data-types but we use Standard SQL -# names -# https://cloud.google.com/bigquery/docs/reference/standard-sql/data-types -BQ_STANDARD_TYPES = types.MappingProxyType( - { - "BOOLEAN": "BOOL", - "INTEGER": "INT64", - "FLOAT": "FLOAT64", - } -) - - -def bq_field_to_type_sql(field: bigquery.SchemaField): - if field.mode == "REPEATED": - nested_type = bq_field_to_type_sql( - bigquery.SchemaField( - field.name, - field.field_type, - mode="NULLABLE", - fields=field.fields, - ) - ) - return f"ARRAY<{nested_type}>" - - if field.field_type == "RECORD": - nested_fields_sql = ", ".join( - bq_field_to_sql(child_field) for child_field in field.fields - ) - return f"STRUCT<{nested_fields_sql}>" - - type_ = field.field_type - return BQ_STANDARD_TYPES.get(type_, type_) - - -def bq_field_to_sql(field: bigquery.SchemaField): - name = field.name - type_ = bq_field_to_type_sql(field) - return f"`{name}` {type_}" - - -def bq_schema_to_sql(schema: Iterable[bigquery.SchemaField]): - return ", ".join(bq_field_to_sql(field) for field in schema) - - -def format_option(key: str, value: Union[bool, str]) -> str: - if isinstance(value, bool): - return f"{key}=true" if value else f"{key}=false" - return f"{key}={repr(value)}" - - -def add_and_trim_labels( - job_config, - session=None, - extra_query_labels: Optional[Mapping[str, str]] = None, -): - """ - Add additional labels to the job configuration and trim the total - number of labels to ensure they do not exceed MAX_LABELS_COUNT labels - per job. - """ - api_methods = log_adapter.get_and_reset_api_methods( - dry_run=job_config.dry_run, session=session - ) - job_config.labels = create_job_configs_labels( - job_configs_labels=job_config.labels, - api_methods=api_methods, - ) - - -def create_bq_event_callback(publisher, cell_execution_count=None): - event_map = { - google.cloud.bigquery._job_helpers.QueryFinishedEvent: ( - bigframes.core.events.BigQueryFinishedEvent - ), - google.cloud.bigquery._job_helpers.QueryReceivedEvent: ( - bigframes.core.events.BigQueryReceivedEvent - ), - google.cloud.bigquery._job_helpers.QueryRetryEvent: ( - bigframes.core.events.BigQueryRetryEvent - ), - google.cloud.bigquery._job_helpers.QuerySentEvent: ( - bigframes.core.events.BigQuerySentEvent - ), - } - - def publish_bq_event(event): - bf_event = bigframes.core.events.BigQueryUnknownEvent(event) - for bq_type, bf_type in event_map.items(): - if isinstance(event, bq_type): - bf_event = bf_type.from_bqclient(event) # type: ignore - break - envelope = bigframes.core.events.EventEnvelope( - event=bf_event, - progress_bar=bigframes.core.events._DEFAULT, - cell_execution_count=cell_execution_count, - ) - publisher.publish(envelope) - - return publish_bq_event - - -def start_query_with_job( - bq_client: bigquery.Client, - sql: str, - *, - job_config: bigquery.QueryJobConfig, - location: Optional[str] = None, - project: Optional[str] = None, - timeout: Optional[float] = None, - metrics: Optional[bigframes.session.metrics.ExecutionMetrics] = None, - # TODO(tswast): We can stop providing our own default once we use a - # google-cloud-bigquery version with - # https://github.com/googleapis/python-bigquery/pull/2256 merged, likely - # version 3.36.0 or later. - job_retry: google.api_core.retry.Retry = (third_party_gcb_retry.DEFAULT_JOB_RETRY), # noqa: E501 - publisher: bigframes.core.events.Publisher, - session=None, - cell_execution_count: Optional[int] = None, -) -> Tuple[google.cloud.bigquery.table.RowIterator, bigquery.QueryJob]: - """ - Starts query job and waits for results. - """ - if cell_execution_count is None: - from bigframes.core.utils import get_ipython_execution_count - - cell_execution_count = get_ipython_execution_count() - - # Note: Ensure no additional labels are added to job_config after this - # point, as `add_and_trim_labels` ensures the label count does not - # exceed MAX_LABELS_COUNT. - add_and_trim_labels(job_config, session=session) - - try: - query_job = bq_client.query( - sql, - job_config=job_config, - location=location, - project=project, - timeout=timeout, - job_retry=job_retry, - ) - except google.api_core.exceptions.Forbidden as ex: - if "Drive credentials" in ex.message: - ex.message += CHECK_DRIVE_PERMISSIONS - raise - - results_iterator = query_job.result() - _publish_events( - query_job=query_job, - total_rows=results_iterator.total_rows, - sql=sql, - publisher=publisher, - metrics=metrics, - cell_execution_count=cell_execution_count, - ) - return results_iterator, query_job - - -def start_query_job_optional( - bq_client: bigquery.Client, - sql: str, - *, - job_config: bigquery.QueryJobConfig, - location: Optional[str] = None, - project: Optional[str] = None, - timeout: Optional[float] = None, - metrics: Optional[bigframes.session.metrics.ExecutionMetrics] = None, - # TODO(tswast): We can stop providing our own default once we use a - # google-cloud-bigquery version with - # https://github.com/googleapis/python-bigquery/pull/2256 merged, likely - # version 3.36.0 or later. - job_retry: google.api_core.retry.Retry = (third_party_gcb_retry.DEFAULT_JOB_RETRY), # noqa: E501 - publisher: Optional[bigframes.core.events.Publisher] = None, - session=None, - cell_execution_count: Optional[int] = None, -) -> google.cloud.bigquery.table.RowIterator: - """ - Run a bigquery query, with job optional. - - See: - https://docs.cloud.google.com/bigquery/docs/running-queries#optional-job-creation - """ - if cell_execution_count is None: - from bigframes.core.utils import get_ipython_execution_count - - cell_execution_count = get_ipython_execution_count() - - add_and_trim_labels(job_config, session=session) - try: - results_iterator = bq_client._query_and_wait_bigframes( - sql, - job_config=job_config, - location=location, - project=project, - api_timeout=timeout, - job_retry=job_retry, - callback=create_bq_event_callback( - publisher, cell_execution_count=cell_execution_count - ) - if publisher - else lambda _: None, - ) - if metrics is not None: - metrics.count_job_stats( - row_iterator=results_iterator, cell_execution_count=cell_execution_count - ) - return results_iterator - except google.api_core.exceptions.Forbidden as ex: - if "Drive credentials" in ex.message: - ex.message += CHECK_DRIVE_PERMISSIONS - raise - - -def _publish_events( - query_job: bigquery.QueryJob, - sql: str, - total_rows: Optional[int], - publisher: bigframes.core.events.Publisher, - metrics: Optional[bigframes.session.metrics.ExecutionMetrics] = None, - cell_execution_count: Optional[int] = None, -): - if not query_job.configuration.dry_run: - publisher.publish( - bigframes.core.events.EventEnvelope( - event=bigframes.core.events.BigQuerySentEvent( - sql, - billing_project=query_job.project, - location=query_job.location, - job_id=query_job.job_id, - request_id=None, - ), - cell_execution_count=cell_execution_count, - ) - ) - if not query_job.configuration.dry_run: - publisher.publish( - bigframes.core.events.EventEnvelope( - event=bigframes.core.events.BigQueryFinishedEvent( - billing_project=query_job.project, - location=query_job.location, - query_id=query_job.query_id, - job_id=query_job.job_id, - destination=query_job.destination, - total_rows=total_rows, - total_bytes_processed=query_job.total_bytes_processed, - slot_millis=query_job.slot_millis, - created=query_job.created, - started=query_job.started, - ended=query_job.ended, - ), - cell_execution_count=cell_execution_count, - ) - ) - - if metrics is not None: - metrics.count_job_stats( - query_job=query_job, cell_execution_count=cell_execution_count - ) - - -def delete_tables_matching_session_id( - client: bigquery.Client, - dataset: bigquery.DatasetReference, - session_id: str, -) -> None: - """Searches within the dataset for tables conforming to the - expected session_id form, and instructs bigquery to delete them. - - Args: - client (bigquery.Client): - The client to use to list tables - dataset (bigquery.DatasetReference): - The dataset to search in - session_id (str): - The session id to match on in the table name - - Returns: - None - """ - - tables = client.list_tables( - dataset, max_results=_LIST_TABLES_LIMIT, page_size=_LIST_TABLES_LIMIT - ) - for table in tables: - split_id = table.table_id.split("_") - if not split_id[0].startswith("bqdf") or len(split_id) < 2: - continue - found_session_id = split_id[1] - if found_session_id == session_id: - client.delete_table(table, not_found_ok=True) - print("Deleting temporary table '{}'.".format(table.table_id)) - - -def create_bq_dataset_reference( - bq_client: bigquery.Client, - location: Optional[str] = None, - project: Optional[str] = None, - *, - publisher: bigframes.core.events.Publisher, -) -> bigquery.DatasetReference: - """Create and identify dataset(s) for temporary BQ resources. - - bq_client project and location will be used unless kwargs "project" - and/or "location" are given. If given, location and project - will be passed through to - https://cloud.google.com/python/docs/reference/bigquery/latest/google.cloud.bigquery.client.Client#google_cloud_bigquery_client_Client_query - - Args: - bq_client (bigquery.Client): - The bigquery.Client to use for the http request to - create the dataset reference. - location (str, default None): - The location of the project to create the dataset in. - project (str, default None): - The project id of the project to create the dataset in. - - Returns: - bigquery.DatasetReference: The constructed reference to the - anonymous dataset. - """ - job_config = google.cloud.bigquery.QueryJobConfig() - - _, query_job = start_query_with_job( - bq_client, - "SELECT 1", - location=location, - job_config=job_config, - project=project, - timeout=None, - metrics=None, - publisher=publisher, - ) - - # The anonymous dataset is used by BigQuery to write query results and - # session tables. BigQuery DataFrames also writes temp tables directly - # to the dataset, no BigQuery Session required. Note: there is a - # different anonymous dataset per location. See: - # https://cloud.google.com/bigquery/docs/cached-results#how_cached_results_are_stored - query_destination = query_job.destination - return bigquery.DatasetReference( - query_destination.project, - query_destination.dataset_id, - ) - - -def is_query(query_or_table: str) -> bool: - """Determine if `query_or_table` is a table ID or a SQL string""" - return re.search(r"\s", query_or_table.strip(), re.MULTILINE) is not None - - -def is_table_with_wildcard_suffix(query_or_table: str) -> bool: - """Determine if `query_or_table` is a table and contains a wildcard - suffix.""" - return not is_query(query_or_table) and query_or_table.endswith("*") - - -def to_query( - query_or_table: str, - columns: Iterable[str], - sql_predicate: Optional[str], - max_results: Optional[int] = None, - time_travel_timestamp: Optional[datetime.datetime] = None, -) -> str: - """Compile query_or_table with conditions(filters, wildcards) to query.""" - if is_query(query_or_table): - from_item = f"({query_or_table})" - else: - # Table ID can have 1, 2, 3, or 4 parts. Quoting all parts to be safe. - # See: - # https://cloud.google.com/bigquery/docs/reference/standard-sql/lexical#identifiers - parts = query_or_table.split(".") - from_item = ".".join(f"`{part}`" for part in parts) - - # TODO(b/338111344): Generate an index based on DefaultIndexKind if we - # don't have index columns specified. - if columns: - # We only reduce the selection if columns is set, but we always - # want to make sure index_cols is also included. - select_clause = "SELECT " + ", ".join( - f"`_bf_source`.`{column}`" for column in columns - ) - else: - select_clause = "SELECT *" - - time_travel_clause = "" - if time_travel_timestamp is not None: - time_travel_literal = sg_sql.to_sql(sg_sql.literal(time_travel_timestamp)) # noqa: E501 - time_travel_clause = f" FOR SYSTEM_TIME AS OF {time_travel_literal}" - - limit_clause = "" - if max_results is not None: - limit_clause = f" LIMIT {sg_sql.to_sql(sg_sql.literal(max_results))}" - - where_clause = f" WHERE {sql_predicate}" if sql_predicate else "" - - return ( - f"{select_clause} " - f"FROM {from_item} AS _bf_source" - f"{time_travel_clause}{where_clause}{limit_clause}" - ) - - -def compile_filters(filters: third_party_pandas_gbq.FiltersType) -> str: - """Compiles a set of filters into a boolean sql expression""" - if not filters: - return "" - filter_string = "" - valid_operators: Mapping[third_party_pandas_gbq.FilterOps, str] = { - "in": "IN", - "not in": "NOT IN", - "LIKE": "LIKE", - "==": "=", - ">": ">", - "<": "<", - ">=": ">=", - "<=": "<=", - "!=": "!=", - } - - # If single layer filter, add another pseudo layer. So the single - # layer represents "and" logic. - filters_list: list = list(filters) - if isinstance(filters_list[0], tuple) and ( - len(filters_list[0]) == 0 or not isinstance(list(filters_list[0])[0], tuple) # noqa: E501 - ): - filter_items = [filters_list] - else: - filter_items = filters_list - - for group in filter_items: - if not isinstance(group, Iterable): - group = [group] - - and_expression = "" - for filter_item in group: - if not isinstance(filter_item, tuple) or (len(filter_item) != 3): - raise ValueError( - f"Elements of filters must be tuples of length 3, " - f"but got {repr(filter_item)}.", - ) - - column, operator, value = filter_item - - if not isinstance(column, str): - raise ValueError( - f"Column name should be a string, but received " - f"'{column}' of type {type(column).__name__}." - ) - - if operator not in valid_operators: - raise ValueError(f"Operator {operator} is not valid.") - - operator_str = valid_operators[operator] - - column_ref = sg_sql.to_sql(sg_sql.identifier(column)) - if operator_str in ["IN", "NOT IN"]: - value_literal = bigframes.core.sql.multi_literal(*value) - else: - value_literal = sg_sql.to_sql(sg_sql.literal(value)) - expression = bigframes.core.sql.infix_op( - operator_str, column_ref, value_literal - ) - if and_expression: - and_expression = bigframes.core.sql.infix_op( - "AND", and_expression, expression - ) - else: - and_expression = expression - - if filter_string: - filter_string = bigframes.core.sql.infix_op( - "OR", filter_string, and_expression - ) - else: - filter_string = and_expression - - return filter_string - - -def select_cluster_cols( - schema: typing.Sequence[bigquery.SchemaField], - cluster_candidates: typing.Sequence[str], -) -> typing.Sequence[str]: - return [ - item.name - for item in schema - if (item.name in cluster_candidates) and _can_cluster_bq(item) - ][:_MAX_CLUSTER_COLUMNS] - - -def _can_cluster_bq(field: bigquery.SchemaField): - # https://cloud.google.com/bigquery/docs/clustered-tables - # Notably, float is excluded - type_ = field.field_type - return type_ in ( - "INTEGER", - "INT64", - "STRING", - "NUMERIC", - "DECIMAL", - "BIGNUMERIC", - "BIGDECIMAL", - "DATE", - "DATETIME", - "TIMESTAMP", - "BOOL", - "BOOLEAN", - ) diff --git a/bigframes/session/_io/bigquery/read_gbq_query.py b/bigframes/session/_io/bigquery/read_gbq_query.py deleted file mode 100644 index cd6368974b6..00000000000 --- a/bigframes/session/_io/bigquery/read_gbq_query.py +++ /dev/null @@ -1,143 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -"""Private helpers for implementing read_gbq_query.""" - -from __future__ import annotations - -from typing import Iterable, Optional, Tuple, cast - -import google.cloud.bigquery.table -import pandas -from google.cloud import bigquery - -import bigframes.core as core -import bigframes.core.blocks as blocks -import bigframes.core.guid -import bigframes.core.schema as schemata -import bigframes.enums -import bigframes.session -from bigframes import dataframe -from bigframes.core import local_data, pyarrow_utils - - -def should_return_query_results(query_job: bigquery.QueryJob) -> bool: - """Returns True if query_job is the kind of query we expect results from. - - If the query was DDL or DML, return some job metadata. See - https://cloud.google.com/bigquery/docs/reference/rest/v2/Job#JobStatistics2.FIELDS.statement_type - for possible statement types. Note that destination table does exist - for some DDL operations such as CREATE VIEW, but we don't want to - read from that. See internal issue b/444282709. - """ - - if query_job.statement_type == "SELECT": - return True - - if query_job.statement_type == "SCRIPT": - # Try to determine if the last statement is a SELECT. Alternatively, we - # could do a jobs.list request using query_job as the parent job and - # try to determine the statement type of the last child job. - return query_job.destination != query_job.ddl_target_table - - return False - - -def create_dataframe_from_query_job_stats( - query_job: Optional[bigquery.QueryJob], *, session: bigframes.session.Session -) -> dataframe.DataFrame: - """Convert a QueryJob into a DataFrame with key statistics about the query. - - Any changes you make here, please try to keep in sync with pandas-gbq. - """ - return dataframe.DataFrame( - data=pandas.DataFrame( - { - "statement_type": [ - query_job.statement_type if query_job else "unknown" - ], - "job_id": [query_job.job_id if query_job else "unknown"], - "location": [query_job.location if query_job else "unknown"], - } - ), - session=session, - ) - - -def create_dataframe_from_row_iterator( - rows: google.cloud.bigquery.table.RowIterator, - *, - session: bigframes.session.Session, - index_col: Iterable[str] | str | bigframes.enums.DefaultIndexKind, - columns: Iterable[str], -) -> dataframe.DataFrame: - """Convert a RowIterator into a DataFrame wrapping a LocalNode. - - This allows us to create a DataFrame from query results, even in the - 'jobless' case where there's no destination table. - """ - pa_table = rows.to_arrow() - bq_schema = list(rows.schema) - is_default_index = not index_col or isinstance( - index_col, bigframes.enums.DefaultIndexKind - ) - - if is_default_index: - # We get a sequential index for free, so use that if no index is specified. - # TODO(tswast): Use array_value.promote_offsets() instead once that node is - # supported by the local engine. - offsets_col = bigframes.core.guid.generate_guid() - pa_table = pyarrow_utils.append_offsets(pa_table, offsets_col=offsets_col) - bq_schema += [bigquery.SchemaField(offsets_col, "INTEGER")] - index_columns: Tuple[str, ...] = (offsets_col,) - index_labels: Tuple[Optional[str], ...] = (None,) - elif isinstance(index_col, str): - index_columns = (index_col,) - index_labels = (index_col,) - else: - index_col = cast(Iterable[str], index_col) - index_columns = tuple(index_col) - index_labels = cast(Tuple[Optional[str], ...], tuple(index_col)) - - # We use the ManagedArrowTable constructor directly, because the - # results of to_arrow() should be the source of truth with regards - # to canonical formats since it comes from either the BQ Storage - # Read API or has been transformed by google-cloud-bigquery to look - # like the output of the BQ Storage Read API. - mat = local_data.ManagedArrowTable( - pa_table, - schemata.ArraySchema.from_bq_schema(bq_schema), - ) - mat.validate() - - column_labels = [ - field.name for field in rows.schema if field.name not in index_columns - ] - - array_value = core.ArrayValue.from_managed(mat, session) - block = blocks.Block( - array_value, - index_columns=index_columns, - column_labels=column_labels, - index_labels=index_labels, - ) - df = dataframe.DataFrame(block) - - if columns: - df = df[list(columns)] - - if not is_default_index: - df = df.sort_index() - - return df diff --git a/bigframes/session/_io/bigquery/read_gbq_table.py b/bigframes/session/_io/bigquery/read_gbq_table.py deleted file mode 100644 index faaf0f01912..00000000000 --- a/bigframes/session/_io/bigquery/read_gbq_table.py +++ /dev/null @@ -1,384 +0,0 @@ -# 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. -# 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. - -""" -Private helpers for loading a BigQuery table as a BigQuery DataFrames DataFrame. -""" - -from __future__ import annotations - -import datetime -import typing -import warnings -from typing import Dict, Iterable, Optional, Sequence, Tuple, Union - -import bigframes_vendored.constants as constants -import google.api_core.exceptions -import google.cloud.bigquery as bigquery - -import bigframes.core -import bigframes.core.events -import bigframes.exceptions as bfe -import bigframes.session._io.bigquery -from bigframes.core import bq_data - -# Avoid circular imports. -if typing.TYPE_CHECKING: - import bigframes.session - - -def _convert_information_schema_table_id_to_table_reference( - table_id: str, - default_project: Optional[str], -) -> bigquery.TableReference: - """Squeeze an INFORMATION_SCHEMA reference into a TableReference. - This is kind-of a hack. INFORMATION_SCHEMA is a view that isn't available - via the tables.get REST API. - """ - parts = table_id.split(".") - parts_casefold = [part.casefold() for part in parts] - dataset_index = parts_casefold.index("INFORMATION_SCHEMA".casefold()) - - if dataset_index == 0: - project = default_project - else: - project = ".".join(parts[:dataset_index]) - - if project is None: - message = ( - "Could not determine project ID. " - "Please provide a project or region in your INFORMATION_SCHEMA table ID, " - "For example, 'region-REGION_NAME.INFORMATION_SCHEMA.JOBS'." - ) - raise ValueError(message) - - dataset = "INFORMATION_SCHEMA" - table_id_short = ".".join(parts[dataset_index + 1 :]) - return bigquery.TableReference( - bigquery.DatasetReference(project, dataset), - table_id_short, - ) - - -def get_information_schema_metadata( - bqclient: bigquery.Client, - table_id: str, - default_project: Optional[str], -) -> bigquery.Table: - job_config = bigquery.QueryJobConfig(dry_run=True) - job = bqclient.query( - f"SELECT * FROM `{table_id}`", - job_config=job_config, - ) - table_ref = _convert_information_schema_table_id_to_table_reference( - table_id=table_id, - default_project=default_project, - ) - table = bigquery.Table.from_api_repr( - { - "tableReference": table_ref.to_api_repr(), - "location": job.location, - # Prevent ourselves from trying to read the table with the BQ - # Storage API. - "type": "VIEW", - } - ) - table.schema = job.schema - return table - - -def is_information_schema(table_id: str): - table_id_casefold = table_id.casefold() - # Include the "."s to ensure we don't have false positives for some user - # defined dataset like MY_INFORMATION_SCHEMA or tables called - # INFORMATION_SCHEMA. - return ( - ".INFORMATION_SCHEMA.".casefold() in table_id_casefold - or table_id_casefold.startswith("INFORMATION_SCHEMA.".casefold()) - ) - - -def is_time_travel_eligible( - bqclient: bigquery.Client, - table: Union[bq_data.GbqNativeTable, bq_data.BiglakeIcebergTable], - columns: Optional[Sequence[str]], - snapshot_time: datetime.datetime, - filter_str: Optional[str] = None, - *, - should_warn: bool, - should_dry_run: bool, - publisher: bigframes.core.events.Publisher, -): - """Check if a table is eligible to use time-travel. - - - Args: - table: BigQuery table to check. - should_warn: - If true, raises a warning when time travel is disabled and the - underlying table is likely mutable. - - Return: - bool: - True if there is a chance that time travel may be supported on this - table. If ``should_dry_run`` is True, then this is validated with a - ``dry_run`` query. - """ - - # user code - # -> pandas.read_gbq_table - # -> with_default_session - # -> session.read_gbq_table - # -> session._read_gbq_table - # -> loader.read_gbq_table - # -> is_time_travel_eligible - stacklevel = 7 - - if isinstance(table, bq_data.GbqNativeTable): - # Anonymous dataset, does not support snapshot ever - if table.dataset_id.startswith("_"): - return False - - # Only true tables support time travel - if table.table_id.endswith("*"): - if should_warn: - msg = bfe.format_message( - "Wildcard tables do not support FOR SYSTEM_TIME AS OF queries. " - "Attempting query without time travel. Be aware that " - "modifications to the underlying data may result in errors or " - "unexpected behavior." - ) - warnings.warn( - msg, category=bfe.TimeTravelDisabledWarning, stacklevel=stacklevel - ) - return False - elif table.metadata.type != "TABLE": - if table.metadata.type == "MATERIALIZED_VIEW": - if should_warn: - msg = bfe.format_message( - "Materialized views do not support FOR SYSTEM_TIME AS OF queries. " - "Attempting query without time travel. Be aware that as materialized views " - "are updated periodically, modifications to the underlying data in the view may " - "result in errors or unexpected behavior." - ) - warnings.warn( - msg, - category=bfe.TimeTravelDisabledWarning, - stacklevel=stacklevel, - ) - return False - elif table.metadata.type == "VIEW": - return False - - # table might support time travel, lets do a dry-run query with time travel - if should_dry_run: - snapshot_sql = bigframes.session._io.bigquery.to_query( - query_or_table=table.get_full_id( - quoted=False - ), # to_query will quote for us - columns=columns or (), - sql_predicate=filter_str, - time_travel_timestamp=snapshot_time, - ) - try: - # If this succeeds, we know that time travel will for sure work. - bigframes.session._io.bigquery.start_query_job_optional( - bq_client=bqclient, - sql=snapshot_sql, - job_config=bigquery.QueryJobConfig(dry_run=True), - location=None, - project=None, - timeout=None, - metrics=None, - publisher=publisher, - ) - return True - - except google.api_core.exceptions.NotFound: - # If system time isn't supported, it returns NotFound error? - # Note that a notfound caused by a simple typo will be - # caught above when the metadata is fetched, not here. - if should_warn: - msg = bfe.format_message( - "NotFound error when reading table with time travel." - " Attempting query without time travel. Warning: Without" - " time travel, modifications to the underlying table may" - " result in errors or unexpected behavior." - ) - warnings.warn( - msg, category=bfe.TimeTravelDisabledWarning, stacklevel=stacklevel - ) - - # If we make it to here, we know for sure that time travel won't work. - return False - else: - # We haven't validated it, but there's a chance that time travel could work. - return True - - -def infer_unique_columns( - table: Union[bq_data.GbqNativeTable, bq_data.BiglakeIcebergTable], - index_cols: Sequence[str], -) -> Tuple[str, ...]: - """Return a set of columns that can provide a unique row key or empty if none can be inferred. - - Note: primary keys are not enforced, but these are assumed to be unique - by the query engine, so we make the same assumption here. - """ - # If index_cols contain the primary_keys, the query engine assumes they are - # provide a unique index. - primary_keys = table.primary_key or () - if (len(primary_keys) > 0) and frozenset(primary_keys) <= frozenset(index_cols): - # Essentially, just reordering the primary key to match the index col order - return tuple(index_col for index_col in index_cols if index_col in primary_keys) - - if primary_keys: - return primary_keys - - return () - - -def check_if_index_columns_are_unique( - bqclient: bigquery.Client, - table: Union[bq_data.GbqNativeTable, bq_data.BiglakeIcebergTable], - index_cols: Sequence[str], - *, - publisher: bigframes.core.events.Publisher, -) -> Tuple[str, ...]: - import bigframes.core.sql - import bigframes.session._io.bigquery - - # TODO(b/337925142): Avoid a "SELECT *" subquery here by ensuring - # table_expression only selects just index_cols. - is_unique_sql = bigframes.core.sql.is_distinct_sql( - index_cols, table.get_table_ref() - ) - job_config = bigquery.QueryJobConfig() - results = bigframes.session._io.bigquery.start_query_job_optional( - bq_client=bqclient, - sql=is_unique_sql, - job_config=job_config, - timeout=None, - location=None, - project=None, - metrics=None, - publisher=publisher, - ) - row = next(iter(results)) - - if row["total_count"] == row["distinct_count"]: - return tuple(index_cols) - return () - - -def get_index_cols( - table: Union[bq_data.GbqNativeTable, bq_data.BiglakeIcebergTable], - index_col: Iterable[str] - | str - | Iterable[int] - | int - | bigframes.enums.DefaultIndexKind, - *, - rename_to_schema: Optional[Dict[str, str]] = None, - default_index_type: bigframes.enums.DefaultIndexKind = bigframes.enums.DefaultIndexKind.SEQUENTIAL_INT64, -) -> Sequence[str]: - """ - If we can get a total ordering from the table, such as via primary key - column(s), then return those too so that ordering generation can be - avoided. - """ - # Transform index_col -> index_cols so we have a variable that is - # always a list of column names (possibly empty). - schema_len = len(table.physical_schema) - - index_cols = [] - if isinstance(index_col, bigframes.enums.DefaultIndexKind): - if index_col == bigframes.enums.DefaultIndexKind.SEQUENTIAL_INT64: - # User has explicity asked for a default, sequential index. - # Use that, even if there are primary keys on the table. - return [] - if index_col == bigframes.enums.DefaultIndexKind.NULL: - return [] - else: - # Note: It's actually quite difficult to mock this out to unit - # test, as it's not possible to subclass enums in Python. See: - # https://stackoverflow.com/a/33680021/101923 - raise NotImplementedError( - f"Got unexpected index_col {repr(index_col)}. {constants.FEEDBACK_LINK}" - ) - elif isinstance(index_col, str): - if rename_to_schema is not None: - index_col = rename_to_schema.get(index_col, index_col) - index_cols = [index_col] - elif isinstance(index_col, int): - if not 0 <= index_col < schema_len: - raise ValueError( - f"Integer index {index_col} is out of bounds " - f"for table with {schema_len} columns (must be >= 0 and < {schema_len})." - ) - index_cols = [table.physical_schema[index_col].name] - elif isinstance(index_col, Iterable): - for item in index_col: - if isinstance(item, str): - if rename_to_schema is not None: - item = rename_to_schema.get(item, item) - index_cols.append(item) - elif isinstance(item, int): - if not 0 <= item < schema_len: - raise ValueError( - f"Integer index {item} is out of bounds " - f"for table with {schema_len} columns (must be >= 0 and < {schema_len})." - ) - index_cols.append(table.physical_schema[item].name) - else: - raise TypeError( - "If index_col is an iterable, it must contain either strings " - "(column names) or integers (column positions)." - ) - else: - raise TypeError( - f"Unsupported type for index_col: {type(index_col).__name__}. Expected" - "an integer, an string, an iterable of strings, or an iterable of integers." - ) - - # If the isn't an index selected, use the primary keys of the table as the - # index. If there are no primary keys, we'll return an empty list. - if len(index_cols) == 0: - primary_keys = table.primary_key or () - - # If table has clustering/partitioning, fail if we haven't been able to - # find index_cols to use. This is to avoid unexpected performance and - # resource utilization because of the default sequential index. See - # internal issue 335727141. - if ( - (table.partition_col is not None or table.cluster_cols) - and not primary_keys - and default_index_type == bigframes.enums.DefaultIndexKind.SEQUENTIAL_INT64 - ): - msg = bfe.format_message( - f"Table '{str(table.get_full_id())}' is clustered and/or " - "partitioned, but BigQuery DataFrames was not able to find a " - "suitable index. To avoid this warning, set at least one of: " - # TODO(b/338037499): Allow max_results to override this too, - # once we make it more efficient. - "`index_col` or `filters`." - ) - warnings.warn(msg, category=bfe.DefaultIndexWarning) - - # If there are primary keys defined, the query engine assumes these - # columns are unique, even if the constraint is not enforced. We make - # the same assumption and use these columns as the total ordering keys. - index_cols = list(primary_keys) - - return index_cols diff --git a/bigframes/session/_io/pandas.py b/bigframes/session/_io/pandas.py index 8d41474d8c4..1af00a2d015 100644 --- a/bigframes/session/_io/pandas.py +++ b/bigframes/session/_io/pandas.py @@ -11,77 +11,34 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -from __future__ import annotations -import dataclasses -import typing -from typing import Collection, Union +from typing import Dict, Union -import bigframes_vendored.constants as constants import geopandas # type: ignore import pandas import pandas.arrays import pyarrow # type: ignore import pyarrow.compute # type: ignore -import pyarrow.types # type: ignore -import bigframes.core.schema -import bigframes.dtypes -import bigframes.features - - -@dataclasses.dataclass(frozen=True) -class DataFrameAndLabels: - df: pandas.DataFrame - column_labels: Collection - index_labels: Collection - ordering_col: str - col_type_overrides: typing.Dict[str, bigframes.dtypes.Dtype] - - -def _arrow_to_pandas_arrowdtype( - column: pyarrow.Array, dtype: pandas.ArrowDtype -) -> pandas.Series: - if ( - pyarrow.types.is_list(dtype.pyarrow_dtype) - and not bigframes.features.PANDAS_VERSIONS.is_arrow_list_dtype_usable - ): - # This version of pandas doesn't really support ArrowDtype - # well. See internal issue 321013333 where array type has - # several problems converting a string. - return pandas.Series( - column.to_pylist(), # type: ignore - dtype="object", - ) - - # Avoid conversion logic if we are backing the pandas Series by the - # arrow array. - return pandas.Series( - pandas.arrays.ArrowExtensionArray(column), # type: ignore - dtype=dtype, - ) +import bigframes.constants def arrow_to_pandas( - arrow_table: Union[pyarrow.Table, pyarrow.RecordBatch], - schema: bigframes.core.schema.ArraySchema, + arrow_table: Union[pyarrow.Table, pyarrow.RecordBatch], dtypes: Dict ): - if len(schema) != arrow_table.num_columns: + if len(dtypes) != arrow_table.num_columns: raise ValueError( - f"Number of types {len(schema)} doesn't match number of columns " - f"{arrow_table.num_columns}. {constants.FEEDBACK_LINK}" + f"Number of types {len(dtypes)} doesn't match number of columns " + f"{arrow_table.num_columns}. {bigframes.constants.FEEDBACK_LINK}" ) serieses = {} for field, column in zip(arrow_table.schema, arrow_table): - dtype = schema.get_type(field.name) + dtype = dtypes[field.name] if dtype == geopandas.array.GeometryDtype(): series = geopandas.GeoSeries.from_wkt( - # Use `to_pylist()` is a workaround for TypeError: object of type - # 'pyarrow.lib.StringScalar' has no len() on older pyarrow, - # geopandas, shapely combinations. - column.to_pylist(), + column, # BigQuery geography type is based on the WGS84 reference ellipsoid. crs="EPSG:4326", ) @@ -89,7 +46,7 @@ def arrow_to_pandas( # Preserve NA/NaN distinction. Note: This is currently needed, even if we use # nullable Float64Dtype in the types_mapper. See: # https://github.com/pandas-dev/pandas/issues/55668 - mask = pyarrow.compute.is_null(column) # type: ignore[attr-defined] + mask = pyarrow.compute.is_null(column) nonnull = pyarrow.compute.fill_null(column, float("nan")) # Regarding type: ignore, this class has been public at this # location since pandas 1.2.0. See: @@ -106,7 +63,7 @@ def arrow_to_pandas( elif dtype == pandas.Int64Dtype(): # Avoid out-of-bounds errors in Pandas 1.5.x, which incorrectly # casts to float64 in an intermediate step. - mask = pyarrow.compute.is_null(column) # type: ignore[attr-defined] + mask = pyarrow.compute.is_null(column) nonnull = pyarrow.compute.fill_null(column, 0) pd_array = pandas.arrays.IntegerArray( nonnull.to_numpy() @@ -117,14 +74,13 @@ def arrow_to_pandas( else mask.to_numpy(zero_copy_only=False), ) series = pandas.Series(pd_array, dtype=dtype) - elif dtype == bigframes.dtypes.STRING_DTYPE: - # Pyarrow may be large_string - # Need to manually cast, as some pandas versions break otherwise - series = column.cast(pyarrow.string()).to_pandas( - types_mapper=lambda _: dtype - ) elif isinstance(dtype, pandas.ArrowDtype): - series = _arrow_to_pandas_arrowdtype(column, dtype) + # Avoid conversion logic if we are backing the pandas Series by the + # arrow array. + series = pandas.Series( + pandas.arrays.ArrowExtensionArray(column), # type: ignore + dtype=dtype, + ) else: series = column.to_pandas(types_mapper=lambda _: dtype) diff --git a/bigframes/session/anonymous_dataset.py b/bigframes/session/anonymous_dataset.py deleted file mode 100644 index ed718ff909f..00000000000 --- a/bigframes/session/anonymous_dataset.py +++ /dev/null @@ -1,199 +0,0 @@ -# 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. -# 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 datetime -import threading -import uuid -import warnings -from concurrent.futures import ThreadPoolExecutor -from typing import List, Optional, Sequence - -import google.cloud.bigquery as bigquery -from google.api_core import retry as api_core_retry - -import bigframes.core.events -import bigframes.exceptions as bfe -import bigframes.session._io.bigquery as bf_io_bigquery -from bigframes import constants -from bigframes.session import temporary_storage - -_TEMP_TABLE_ID_FORMAT = "bqdf{date}_{session_id}_{random_id}" -# UDFs older than this many days are considered stale and will be deleted -# from the anonymous dataset before creating a new UDF. -_UDF_CLEANUP_THRESHOLD_DAYS = 3 - - -class AnonymousDatasetManager(temporary_storage.TemporaryStorageManager): - """ - Responsible for allocating and cleaning up temporary gbq tables used by a BigFrames session. - """ - - def __init__( - self, - bqclient: bigquery.Client, - location: str, - session_id: str, - *, - kms_key: Optional[str] = None, - publisher: bigframes.core.events.Publisher, - ): - self.bqclient = bqclient - self._location = location - self._publisher = publisher - - self.session_id = session_id - self._table_ids: List[bigquery.TableReference] = [] - self._kms_key = kms_key - - self._dataset_lock = threading.Lock() - self._datset_ref: Optional[bigquery.DatasetReference] = None - - @property - def location(self): - return self._location - - @property - def dataset(self) -> bigquery.DatasetReference: - if self._datset_ref is not None: - return self._datset_ref - with self._dataset_lock: - if self._datset_ref is None: - self._datset_ref = bf_io_bigquery.create_bq_dataset_reference( - self.bqclient, - location=self._location, - publisher=self._publisher, - ) - return self._datset_ref - - def _default_expiration(self): - """When should the table expire automatically?""" - return ( - datetime.datetime.now(datetime.timezone.utc) + constants.DEFAULT_EXPIRATION - ) - - def create_temp_table( - self, schema: Sequence[bigquery.SchemaField], cluster_cols: Sequence[str] = [] - ) -> bigquery.TableReference: - """ - Allocates and and creates a table in the anonymous dataset. - The table will be cleaned up by clean_up_tables. - """ - expiration = self._default_expiration() - table = bf_io_bigquery.create_temp_table( - self.bqclient, - self.allocate_temp_table(), - expiration, - schema=schema, - cluster_columns=list(cluster_cols), - kms_key=self._kms_key, - ) - return bigquery.TableReference.from_string(table) - - def create_temp_view(self, sql: str) -> bigquery.TableReference: - """ - Allocates and and creates a view in the anonymous dataset. - The view will be cleaned up by clean_up_tables. - """ - expiration = self._default_expiration() - table = bf_io_bigquery.create_temp_view( - self.bqclient, - self.allocate_temp_table(), - expiration=expiration, - sql=sql, - ) - return bigquery.TableReference.from_string(table) - - def allocate_temp_table(self) -> bigquery.TableReference: - """ - Allocates a unique table id, but does not create the table. - The table will be cleaned up by clean_up_tables. - """ - table_id = self.generate_unique_resource_id() - self._table_ids.append(table_id) - return table_id - - def generate_unique_resource_id(self) -> bigquery.TableReference: - """Generate a random table ID with BigQuery DataFrames prefix. - - This resource will not be cleaned up by this manager. - - Args: - skip_cleanup (bool, default False): - If True, do not add the generated ID to the list of tables - to clean up when the session is closed. - - Returns: - google.cloud.bigquery.TableReference: - Fully qualified table ID of a table that doesn't exist. - """ - now = datetime.datetime.now(datetime.timezone.utc) - random_id = uuid.uuid4().hex - table_id = _TEMP_TABLE_ID_FORMAT.format( - date=now.strftime("%Y%m%d"), session_id=self.session_id, random_id=random_id - ) - return self.dataset.table(table_id) - - def _cleanup_old_udfs(self): - """Clean up old UDFs in the anonymous dataset.""" - dataset = self.dataset - routines = list(self.bqclient.list_routines(dataset)) - cleanup_cutoff_time = datetime.datetime.now( - datetime.timezone.utc - ) - datetime.timedelta(days=_UDF_CLEANUP_THRESHOLD_DAYS) - - for routine in routines: - if ( - routine.created < cleanup_cutoff_time - and routine._properties["routineType"] == "SCALAR_FUNCTION" - ): - try: - self.bqclient.delete_routine( - routine.reference, - not_found_ok=True, - retry=api_core_retry.Retry(timeout=0), - ) - except Exception as e: - msg = bfe.format_message( - f"Unable to clean this old UDF '{routine.reference}': {e}" - ) - warnings.warn(msg, category=bfe.CleanupFailedWarning) - - def close(self): - """Delete tables that were created with this session's session_id.""" - if self._table_ids: - try: - with ThreadPoolExecutor() as executor: - futures = [ - executor.submit( - self.bqclient.delete_table, table_ref, not_found_ok=True - ) - for table_ref in self._table_ids - ] - for future in futures: - future.result() - finally: - self._table_ids.clear() - - try: - # Before closing the session, attempt to clean up any uncollected, - # old Python UDFs residing in the anonymous dataset. These UDFs - # accumulate over time and can eventually exceed resource limits. - # See more from b/450913424. - self._cleanup_old_udfs() - except Exception as e: - # Log a warning on the failure, do not interrupt the workflow. - msg = bfe.format_message( - f"Failed to clean up the old Python UDFs before closing the session: {e}" - ) - warnings.warn(msg, category=bfe.CleanupFailedWarning) diff --git a/bigframes/session/bigquery_session.py b/bigframes/session/bigquery_session.py deleted file mode 100644 index 18f8cdeaff4..00000000000 --- a/bigframes/session/bigquery_session.py +++ /dev/null @@ -1,214 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from __future__ import annotations - -import datetime -import logging -import threading -import uuid -from typing import Callable, Optional, Sequence - -# TODO: Non-ibis implementation -import bigframes_vendored.ibis.backends.bigquery.datatypes as ibis_bq -import google.cloud.bigquery as bigquery - -import bigframes.core.events -import bigframes.session._io.bigquery as bfbqio -from bigframes.core.compile.sqlglot import sql as sg_sql -from bigframes.session import temporary_storage - -KEEPALIVE_QUERY_TIMEOUT_SECONDS = 5.0 - -KEEPALIVE_FREQUENCY = datetime.timedelta(hours=6) - - -logger = logging.getLogger(__name__) - - -class SessionResourceManager(temporary_storage.TemporaryStorageManager): - """ - Responsible for allocating and cleaning up temporary gbq tables used by a BigFrames session. - """ - - def __init__( - self, - bqclient: bigquery.Client, - location: str, - *, - publisher: bigframes.core.events.Publisher, - ): - self.bqclient = bqclient - self._location = location - self._session_id: Optional[str] = None - self._sessiondaemon: Optional[RecurringTaskDaemon] = None - self._session_lock = threading.RLock() - self._publisher = publisher - - @property - def location(self): - return self._location - - def create_temp_table( - self, schema: Sequence[bigquery.SchemaField], cluster_cols: Sequence[str] = [] - ) -> bigquery.TableReference: - """Create a temporary session table. Session is an exclusive resource, so throughput is limited""" - # Can't set a table in _SESSION as destination via query job API, so we - # run DDL, instead. - with self._session_lock: - table_ref = bigquery.TableReference( - bigquery.DatasetReference(self.bqclient.project, "_SESSION"), - f"bqdf_{uuid.uuid4()}", - ) - job_config = bigquery.QueryJobConfig( - connection_properties=[ - bigquery.ConnectionProperty("session_id", self._get_session_id()) - ] - ) - - ibis_schema = ibis_bq.BigQuerySchema.to_ibis(list(schema)) - - fields = [ - f"{sg_sql.to_sql(sg_sql.identifier(name))} {ibis_bq.BigQueryType.from_ibis(ibis_type)}" - for name, ibis_type in ibis_schema.fields.items() - ] - fields_string = ",".join(fields) - - cluster_string = "" - if cluster_cols: - cluster_cols_sql = ", ".join( - f"{sg_sql.to_sql(sg_sql.identifier(cluster_col))}" - for cluster_col in cluster_cols - ) - cluster_string = f"\nCLUSTER BY {cluster_cols_sql}" - - ddl = f"CREATE TEMP TABLE `_SESSION`.{sg_sql.to_sql(sg_sql.identifier(table_ref.table_id))} ({fields_string}){cluster_string}" - - _, job = bfbqio.start_query_with_job( - self.bqclient, - ddl, - job_config=job_config, - location=self.location, - project=None, - timeout=None, - metrics=None, - publisher=self._publisher, - ) - job.result() - # return the fully qualified table, so it can be used outside of the session - destination = job.destination - assert destination is not None, "Failure to create temp table." - return destination - - def close(self): - if self._sessiondaemon is not None: - self._sessiondaemon.stop() - - if self._session_id is not None and self.bqclient is not None: - bfbqio.start_query_job_optional( - self.bqclient, - f"CALL BQ.ABORT_SESSION('{self._session_id}')", - # Assume this is being called in the user thread, so we can access - # this thread-local config. - job_config=bigquery.QueryJobConfig( - labels=dict(bigframes.options.compute.extra_query_labels) - ), - location=self.location, - project=None, - timeout=None, - metrics=None, - publisher=self._publisher, - ) - - def _get_session_id(self) -> str: - if self._session_id: - return self._session_id - with self._session_lock: - if self._session_id is None: - job_config = bigquery.QueryJobConfig(create_session=True) - # Make sure the session is a new one, not one associated with another query. - job_config.use_query_cache = False - _, query_job = bfbqio.start_query_with_job( - self.bqclient, - "SELECT 1", - job_config=job_config, - location=self.location, - project=None, - timeout=None, - metrics=None, - publisher=self._publisher, - ) - query_job.result() # blocks until finished - assert query_job.session_info is not None - assert query_job.session_info.session_id is not None - self._session_id = query_job.session_info.session_id - self._sessiondaemon = RecurringTaskDaemon( - task=self._keep_session_alive, frequency=KEEPALIVE_FREQUENCY - ) - self._sessiondaemon.start() - return query_job.session_info.session_id - else: - return self._session_id - - def _keep_session_alive(self): - # bq sessions will default expire after 24 hours of disuse, but if queried, this is renewed to a maximum of 7 days - with self._session_lock: - job_config = bigquery.QueryJobConfig( - connection_properties=[ - bigquery.ConnectionProperty("session_id", self._get_session_id()) - ] - ) - try: - bfbqio.start_query_job_optional( - self.bqclient, - "SELECT 1", - job_config=job_config, - location=self.location, - project=None, - timeout=KEEPALIVE_QUERY_TIMEOUT_SECONDS, - metrics=None, - publisher=self._publisher, - ) - except Exception as e: - logging.warning("BigQuery session keep-alive query errored : %s", e) - - -class RecurringTaskDaemon: - def __init__(self, task: Callable[[], None], frequency: datetime.timedelta): - self._stop_event = threading.Event() - self._frequency = frequency - self._thread = threading.Thread(target=self._run_loop, daemon=True) - self._task = task - - def start(self): - """Start the daemon. Cannot be restarted once stopped.""" - if self._stop_event.is_set(): - raise RuntimeError("Cannot restart daemon thread.") - self._thread.start() - - def _run_loop(self): - while True: - self._stop_event.wait(self._frequency.total_seconds()) - if self._stop_event.is_set(): - return - try: - self._task() - except Exception as e: - logging.warning("RecurringTaskDaemon task errorred: %s", e) - - def stop(self, timeout_seconds: Optional[float] = None): - """Stop and cleanup the daemon.""" - if self._thread.is_alive(): - self._stop_event.set() - self._thread.join(timeout=timeout_seconds) diff --git a/bigframes/session/bq_caching_executor.py b/bigframes/session/bq_caching_executor.py deleted file mode 100644 index dede318d813..00000000000 --- a/bigframes/session/bq_caching_executor.py +++ /dev/null @@ -1,804 +0,0 @@ -# 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. -# 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 asyncio -import concurrent.futures -import dataclasses -import math -import threading -from typing import Literal, Optional, Sequence, Tuple - -import google.api_core.exceptions -import google.cloud.bigquery_storage_v1 -from google.cloud import bigquery - -import bigframes -import bigframes.constants -import bigframes.core -import bigframes.core.events -import bigframes.core.guid -import bigframes.core.nodes as nodes -import bigframes.core.ordering -import bigframes.core.schema as schemata -import bigframes.core.tree_properties as tree_properties -import bigframes.dtypes -import bigframes.functions._function_session as bff_session -import bigframes.operations as ops -import bigframes.session._io.bigquery as bq_io -import bigframes.session.execution_cache as execution_cache -import bigframes.session.execution_spec as ex_spec -import bigframes.session.metrics -import bigframes.session.planner -import bigframes.session.temporary_storage -from bigframes.core import ( - compile, - expression, - guid, - identifiers, - local_data, - rewrite, -) -from bigframes.core.compile.sqlglot import sql as sg_sql -from bigframes.core.compile.sqlglot import sqlglot_ir -from bigframes.functions import udf_def -from bigframes.session import ( - direct_gbq_execution, - executor, - loader, - local_scan_executor, - read_api_execution, - semi_executor, -) - -# Max complexity that should be executed as a single query -QUERY_COMPLEXITY_LIMIT = 1e7 -# Number of times to factor out subqueries before giving up. -MAX_SUBTREE_FACTORINGS = 5 -_MAX_CLUSTER_COLUMNS = 4 -MAX_SMALL_RESULT_BYTES = 10 * 1024 * 1024 * 1024 # 10G - - -_bg_loop = None -_bg_thread = None -_bg_lock = threading.Lock() - - -def _get_bg_loop(): - global _bg_loop, _bg_thread - with _bg_lock: - if _bg_loop is None: - loop = asyncio.new_event_loop() - _bg_loop = loop - - def run(): - asyncio.set_event_loop(loop) - loop.run_forever() - - _bg_thread = threading.Thread( - target=run, daemon=True, name="bigframes-bg-loop" - ) - _bg_thread.start() - return _bg_loop - - -def _run_sync(coro): - try: - loop = asyncio.get_running_loop() - except RuntimeError: - loop = None - - if loop is None: - return asyncio.run(coro) - else: - bg_loop = _get_bg_loop() - future = asyncio.run_coroutine_threadsafe(coro, bg_loop) - return future.result() - - -class BigQueryCachingExecutor(executor.Executor): - """Computes BigFrames values using BigQuery Engine. - - This executor can cache expressions. If those expressions are executed later, this session - will re-use the pre-existing results from previous executions. - - This class is not thread-safe. - """ - - def __init__( - self, - bqclient: bigquery.Client, - storage_manager: bigframes.session.temporary_storage.TemporaryStorageManager, - bqstoragereadclient: google.cloud.bigquery_storage_v1.BigQueryReadClient, - loader: loader.GbqDataLoader, - *, - metrics: Optional[bigframes.session.metrics.ExecutionMetrics] = None, - enable_polars_execution: bool = False, - publisher: bigframes.core.events.Publisher, - labels: tuple[tuple[str, str], ...] = (), - compiler_name: Literal["ibis", "sqlglot"] = "sqlglot", - cache: Optional[execution_cache.ExecutionCache] = None, - function_manager: bff_session.FunctionSession, - ): - self.bqclient = bqclient - self.storage_manager = storage_manager - self.cache: execution_cache.ExecutionCache = ( - cache or execution_cache.ExecutionCache() - ) - self.metrics = metrics - self.loader = loader - self._enable_polars_execution = enable_polars_execution - self._publisher = publisher - self._compiler_name = compiler_name - - # TODO(tswast): Send events from semi-executors, too. - self._semi_executors: Sequence[semi_executor.SemiExecutor] = ( - read_api_execution.ReadApiSemiExecutor( - bqstoragereadclient=bqstoragereadclient, - project=self.bqclient.project, - ), - local_scan_executor.LocalScanExecutor(), - ) - if enable_polars_execution: - from bigframes.session import polars_executor - - self._semi_executors = ( - *self._semi_executors, - polars_executor.PolarsExecutor(), - ) - self._gbq_executor = direct_gbq_execution.DirectGbqExecutor( - bqclient, - compiler=compiler_name, - bqstoragereadclient=bqstoragereadclient, - metrics=self.metrics, - publisher=self._publisher, - labels=dict(labels), - ) - self._function_manager = function_manager - - def to_sql( - self, - array_value: bigframes.core.ArrayValue, - offset_column: Optional[str] = None, - ordered: bool = False, - enable_cache: bool = True, - ) -> str: - if offset_column: - array_value, _ = array_value.promote_offsets() - node = ( - self._prepare_plan_simplify(array_value.node) - if enable_cache - else array_value.node - ) - node = _run_sync(self._substitute_large_local_sources(node)) - compiled = compile.compile_sql( - compile.CompileRequest(node, sort_rows=ordered), - compiler_name=self._compiler_name, - ) - return compiled.sql - - def execute( - self, - array_value: bigframes.core.ArrayValue, - execution_spec: ex_spec.ExecutionSpec, - ) -> executor.ExecuteResult: - # Need to grab thread local before starting async execution. - execution_spec = execution_spec.with_compute_options(bigframes.options.compute) - return _run_sync( - self._execute_async( - array_value, - execution_spec, - ) - ) - - async def _execute_async( - self, - array_value: bigframes.core.ArrayValue, - execution_spec: ex_spec.ExecutionSpec, - ) -> executor.ExecuteResult: - await self._publisher.publish_async(bigframes.core.events.ExecutionStarted()) - maybe_result = await self._try_execute_semi_executors( - array_value, execution_spec - ) - if maybe_result is not None: - return maybe_result - result = await self._execute_bigquery( - array_value, - execution_spec, - ) - await self._publisher.publish_async( - bigframes.core.events.EventEnvelope( - event=bigframes.core.events.ExecutionFinished(result=result), - cell_execution_count=execution_spec.cell_execution_count, - ) - ) - return result - - async def _try_execute_semi_executors( - self, - array_value: bigframes.core.ArrayValue, - execution_spec: ex_spec.ExecutionSpec, - ) -> Optional[executor.ExecuteResult]: - plan = self._prepare_plan_simplify(array_value.node) - for exec in self._semi_executors: - maybe_result = await exec.execute(plan, execution_spec) - if maybe_result: - await self._publisher.publish_async( - bigframes.core.events.EventEnvelope( - event=bigframes.core.events.ExecutionFinished( - result=maybe_result, - ), - cell_execution_count=execution_spec.cell_execution_count, - ) - ) - return maybe_result - return None - - async def _execute_bigquery( - self, - array_value: bigframes.core.ArrayValue, - execution_spec: ex_spec.ExecutionSpec, - ) -> executor.ExecuteResult: - dest_spec = execution_spec.destination_spec - # Recursive handlers for different cases, maybe extract to explicit interface. - if isinstance(dest_spec, ex_spec.GcsOutputSpec): - execution_spec = dataclasses.replace( - execution_spec, destination_spec=ex_spec.EphemeralTableSpec() - ) - results = await self._execute_bigquery( - array_value, - execution_spec, - ) - await self._export_result_gcs(results, dest_spec) - return results - elif isinstance(dest_spec, ex_spec.TableOutputSpec): - return await self._execute_gbq_table_export( - array_value, - execution_spec, - ) - # Force table creation if result might be large (and user explicitly allowed large results) - elif isinstance(dest_spec, ex_spec.EphemeralTableSpec) or (dest_spec is None): - if not execution_spec.promise_under_10gb: - table = await asyncio.to_thread( - self.storage_manager.create_temp_table, - array_value.schema.to_bigquery(), - ) - execution_spec = dataclasses.replace( - execution_spec, - destination_spec=ex_spec.TableOutputSpec( - table=table, if_exists="append" - ), - ) - # We don't use _execute_gbq_table_export, as this result is internal, not exported. - return await self._execute_gbq_query_only( - array_value, - execution_spec, - ) - # At this point, dst should be unspecified, a specific bq table, or an ephemeral temp table that fits in <10gb - return await self._execute_gbq_query_only( - array_value, - execution_spec, - ) - - async def _execute_gbq_table_export( - self, - array_value: bigframes.core.ArrayValue, - execution_spec: ex_spec.ExecutionSpec, - ) -> executor.ExecuteResult: - dest_spec = execution_spec.destination_spec - assert isinstance(dest_spec, ex_spec.TableOutputSpec) - existing_table = await self._maybe_find_existing_table(dest_spec) - if (existing_table is not None) and _is_schema_match( - existing_table.schema, array_value.schema - ): - # Special DML path - maybe this should be configurable, dml vs query destination has tradeoffs - execution_spec = dataclasses.replace( - execution_spec, destination_spec=ex_spec.EphemeralTableSpec() - ) - results = await self._execute_bigquery( - array_value, - execution_spec, - ) - assert isinstance(results, executor.BQTableExecuteResult) - await self._export_gbq_with_dml(results, dest_spec) - result: executor.ExecuteResult = results - else: - result = await self._execute_gbq_query_only( - array_value, - execution_spec, - ) - - has_special_dtype_col = any( - t in (bigframes.dtypes.TIMEDELTA_DTYPE, bigframes.dtypes.OBJ_REF_DTYPE) - for t in array_value.schema.dtypes - ) - if dest_spec.if_exists != "append" and has_special_dtype_col: - table = await asyncio.to_thread(self.bqclient.get_table, dest_spec.table) - table.schema = array_value.schema.to_bigquery() - await asyncio.to_thread(self.bqclient.update_table, table, ["schema"]) - - return result - - async def _execute_gbq_query_only( - self, - array_value: bigframes.core.ArrayValue, - execution_spec: ex_spec.ExecutionSpec, - ) -> executor.ExecuteResult: - gbq_plan = await self._prepare_plan_bq_execution( - array_value.node, execution_spec.bigquery_config - ) - result = await self._gbq_executor.execute(gbq_plan, execution_spec) - if result is None: - raise ValueError( - f"Couldn't execute plan {array_value.node} with {execution_spec}" - ) - return result - - async def _export_result_gcs( - self, result: executor.ExecuteResult, gcs_export_spec: ex_spec.GcsOutputSpec - ): - query_job = result.query_job - assert query_job is not None - result_table = query_job.destination - assert result_table is not None - export_data_statement = bq_io.create_export_data_statement( - f"{result_table.project}.{result_table.dataset_id}.{result_table.table_id}", - uri=gcs_export_spec.uri, - format=gcs_export_spec.format, - export_options=dict(gcs_export_spec.export_options), - ) - await asyncio.to_thread( - bq_io.start_query_with_job, - self.bqclient, - export_data_statement, - job_config=bigquery.QueryJobConfig(), - metrics=self.metrics, - project=None, - location=None, - timeout=None, - publisher=self._publisher, - ) - - async def _export_gbq_with_dml( - self, result: executor.BQTableExecuteResult, spec: ex_spec.TableOutputSpec - ): - """ - Export the ArrayValue to an existing BigQuery table, using DML. - """ - # b/409086472: Uses DML for table appends and replacements to avoid - # BigQuery `RATE_LIMIT_EXCEEDED` errors, as per quota limits: - # https://cloud.google.com/bigquery/quotas#standard_tables - assert result.query_job is not None - assert result.query_job.destination is not None - ir = sqlglot_ir.SQLGlotIR.from_table( - result.query_job.destination.project, - result.query_job.destination.dataset_id, - result.query_job.destination.table_id, - ) - sql = "" - if spec.if_exists == "append": - sql = sg_sql.to_sql(sg_sql.insert(ir.expr.as_select_all(), spec.table)) - else: # for "replace" - assert spec.if_exists == "replace" - sql = sg_sql.to_sql(sg_sql.replace(ir.expr.as_select_all(), spec.table)) - - await asyncio.to_thread( - bq_io.start_query_with_job, - self.bqclient, - sql, - job_config=bigquery.QueryJobConfig(), - metrics=self.metrics, - publisher=self._publisher, - ) - - def dry_run( - self, array_value: bigframes.core.ArrayValue, ordered: bool = True - ) -> bigquery.QueryJob: - sql = self.to_sql(array_value, ordered=ordered) - job_config = bigquery.QueryJobConfig(dry_run=True) - query_job = self.bqclient.query(sql, job_config=job_config) - return query_job - - def cached( - self, array_value: bigframes.core.ArrayValue, *, config: executor.CacheConfig - ) -> None: - # Get compute options before passing to async method, can be thread-local - bq_compute_options = ex_spec.BqComputeOptions.from_compute_options( - bigframes.options.compute - ) - return _run_sync( - self._cached_async( - array_value, config=config, compute_options=bq_compute_options - ) - ) - - async def _cached_async( - self, - array_value: bigframes.core.ArrayValue, - *, - config: executor.CacheConfig, - compute_options: ex_spec.BqComputeOptions, - ) -> None: - """Write the block to a session table.""" - # First, see if we can reuse the existing cache - # TODO(b/415105423): Provide feedback to user on whether new caching action was deemed necessary - # TODO(b/415105218): Make cached a deferred action - if config.if_cached == "reuse-any": - if self._is_trivially_executable(array_value): - return - elif config.if_cached == "reuse-strict": - # This path basically exists to make sure that repr in head mode is optimized for subsequent repr operations. - if config.optimize_for == "head": - if tree_properties.can_fast_head(array_value.node): - return - else: - raise NotImplementedError( - "if_cached='reuse-strict' currently only supported with optimize_for='head'" - ) - elif config.if_cached != "replace": - raise ValueError(f"Unexpected 'if_cached' arg: {config.if_cached}") - - if config.optimize_for == "auto": - await self._cache_with_session_awareness( - array_value, compute_options=compute_options - ) - elif config.optimize_for == "head": - await self._cache_with_offsets(array_value, compute_options=compute_options) - else: - assert isinstance(config.optimize_for, executor.HierarchicalKey) - await self._cache_with_cluster_cols( - array_value, - cluster_cols=config.optimize_for.columns, - compute_options=compute_options, - ) - - async def _execute_to_cached_table( - self, - plan: nodes.BigFrameNode, - cache_spec: ex_spec.CacheSpec, - compute_options: ex_spec.BqComputeOptions, - ) -> executor.ExecuteResult: - # "ephemeral" temp tables created in the course of exeuction, don't need to be allocated - # materialized ordering only really makes sense for internal temp tables used by caching - cluster_cols = cache_spec.cluster_cols - # Rewrite plan to materialize ordering as extra columns - if cache_spec.ordering == "offsets_col": - order_col_id = guid.generate_guid() - plan = nodes.PromoteOffsetsNode(plan, identifiers.ColumnId(order_col_id)) - cluster_cols = (order_col_id,) - ordering: bigframes.core.ordering.RowOrdering = ( - bigframes.core.ordering.TotalOrdering.from_offset_col(order_col_id) - ) - elif cache_spec.ordering == "order_key": - plan, ordering = rewrite.pull_out_order(plan) - destination_table = await asyncio.to_thread( - self.storage_manager.create_temp_table, - plan.schema.to_bigquery(), - cluster_cols, - ) - arr_value = bigframes.core.ArrayValue(plan) - execution_spec = ex_spec.ExecutionSpec( - destination_spec=ex_spec.TableOutputSpec( - table=destination_table, - cluster_cols=cluster_cols, - if_exists="replace", - ), - bigquery_config=compute_options, - ) - # We don't use _execute_gbq_table_export, as this result is internal, not exported. - result = await self._execute_gbq_query_only( - arr_value, - execution_spec, - ) - assert isinstance(result, executor.BQTableExecuteResult), ( - "expected result to be BQTableExecuteResult" - ) - result._data = dataclasses.replace(result._data, ordering=ordering) - return result - - # Helpers - def _is_trivially_executable(self, array_value: bigframes.core.ArrayValue): - """ - Can the block be evaluated very cheaply? - If True, the array_value probably is not worth caching. - """ - # Once rewriting is available, will want to rewrite before - # evaluating execution cost. - simplified_plan = self._prepare_plan_simplify(array_value.node) - return tree_properties.is_trivially_executable(simplified_plan) - - def _prepare_plan_simplify(self, plan: nodes.BigFrameNode) -> nodes.BigFrameNode: - """Prepare the plan by simplifying it with caches and removing unused operators.""" - plan = self.cache.subsitute_cached_subplans(plan) - plan = rewrite.column_pruning(plan) - plan = plan.top_down(rewrite.fold_row_counts) - return plan - - async def _deploy_undeployed_udfs( - self, plan: nodes.BigFrameNode - ) -> nodes.BigFrameNode: - referenced_udfs = list(set(self._collect_udf_defs(plan))) - deployed_mapping: dict[udf_def.PythonUdf, udf_def.BigqueryUdf] = {} - tasks = [ - asyncio.to_thread( - self._function_manager._deploy_udf, - udf, - ) - for udf in referenced_udfs - ] - results = await asyncio.gather(*tasks) - deployed_mapping = dict(zip(referenced_udfs, results)) - - return self._subsitute_temporary_functions(plan, deployed_mapping) - - def _collect_udf_defs(self, plan: nodes.BigFrameNode) -> list[udf_def.PythonUdf]: - udf_defs: list[udf_def.PythonUdf] = [] - exprs = [ - expr for node in plan.unique_nodes() for expr in node._node_expressions - ] - expr_nodes = [expr for expr in exprs for expr in expr.walk()] - for expr_node in expr_nodes: - if ( - isinstance(expr_node, expression.OpExpression) - and isinstance(expr_node.op, ops.PythonUdfOp) - and isinstance(expr_node.op.function_def, udf_def.PythonUdf) - ): - udf_defs.append(expr_node.op.function_def) - return udf_defs - - def _subsitute_temporary_functions( - self, - plan: nodes.BigFrameNode, - deployed_mapping: dict[udf_def.PythonUdf, udf_def.BigqueryUdf], - ) -> nodes.BigFrameNode: - def replace_udf_expr(e: expression.Expression) -> expression.Expression: - if isinstance(e, expression.OpExpression) and isinstance( - e.op, ops.PythonUdfOp - ): - func_def = e.op.function_def - # We will have already deployed the function - assert func_def in deployed_mapping - deployed_func = deployed_mapping[func_def] - rf_op = ops.RemoteFunctionOp(function_def=deployed_func) - return dataclasses.replace(e, op=rf_op) - return e - - def replace_in_expr(expr: expression.Expression) -> expression.Expression: - return expr.bottom_up(replace_udf_expr) - - def replace_in_node(node: nodes.BigFrameNode) -> nodes.BigFrameNode: - if hasattr(node, "transform_exprs"): - return node.transform_exprs(replace_in_expr) - return node - - return plan.bottom_up(replace_in_node) - - async def _prepare_plan_bq_execution( - self, - plan: nodes.BigFrameNode, - compute_options: Optional[ex_spec.BqComputeOptions] = None, - ) -> nodes.BigFrameNode: - """Prepare the plan for BigQuery execution by caching subtrees and uploading large local sources.""" - plan = await self._deploy_undeployed_udfs(plan) - if compute_options is not None and compute_options.enable_multi_query_execution: - await self._simplify_with_caching(plan, compute_options=compute_options) - plan = self._prepare_plan_simplify(plan) - plan = await self._substitute_large_local_sources(plan) - return plan - - async def _cache_with_cluster_cols( - self, - array_value: bigframes.core.ArrayValue, - cluster_cols: Sequence[str], - compute_options: ex_spec.BqComputeOptions, - ): - """Executes the query and uses the resulting table to rewrite future executions.""" - cluster_cols = [ - col - for col in cluster_cols - if bigframes.dtypes.is_clusterable(array_value.schema.get_type(col)) - ] - cluster_cols = cluster_cols[:_MAX_CLUSTER_COLUMNS] - result = await self._execute_to_cached_table( - array_value.node, - ex_spec.CacheSpec(cluster_cols=tuple(cluster_cols), ordering="order_key"), - compute_options=compute_options, - ) - assert isinstance(result, executor.BQTableExecuteResult) - assert result._data.ordering is not None - self.cache.cache_results_table(array_value.node, result._data) - - async def _cache_with_offsets( - self, - array_value: bigframes.core.ArrayValue, - compute_options: ex_spec.BqComputeOptions, - ): - """Executes the query and uses the resulting table to rewrite future executions.""" - result = await self._execute_to_cached_table( - array_value.node, - ex_spec.CacheSpec(ordering="offsets_col"), - compute_options=compute_options, - ) - assert isinstance(result, executor.BQTableExecuteResult) - assert result._data.ordering is not None - self.cache.cache_results_table(array_value.node, result._data) - - async def _cache_with_session_awareness( - self, - array_value: bigframes.core.ArrayValue, - compute_options: ex_spec.BqComputeOptions, - ) -> None: - session_forest = [obj._block._expr.node for obj in array_value.session.objects] - # These node types are cheap to re-compute - target, cluster_cols = bigframes.session.planner.session_aware_cache_plan( - array_value.node, list(session_forest) - ) - cluster_cols_sql_names = [id.sql for id in cluster_cols] - if len(cluster_cols) > 0: - await self._cache_with_cluster_cols( - bigframes.core.ArrayValue(target), - cluster_cols_sql_names, - compute_options=compute_options, - ) - elif not target.order_ambiguous: - await self._cache_with_offsets( - bigframes.core.ArrayValue(target), - compute_options=compute_options, - ) - else: - await self._cache_with_cluster_cols( - bigframes.core.ArrayValue(target), - [], - compute_options=compute_options, - ) - - async def _simplify_with_caching( - self, plan: nodes.BigFrameNode, compute_options: ex_spec.BqComputeOptions - ): - """Attempts to handle the complexity by caching duplicated subtrees and breaking the query into pieces.""" - # Apply existing caching first - for _ in range(MAX_SUBTREE_FACTORINGS): - if ( - self._prepare_plan_simplify(plan).planning_complexity - < QUERY_COMPLEXITY_LIMIT - ): - return - - did_cache = await self._cache_most_complex_subtree( - plan, compute_options=compute_options - ) - if not did_cache: - return - - async def _cache_most_complex_subtree( - self, node: nodes.BigFrameNode, compute_options: ex_spec.BqComputeOptions - ) -> bool: - # TODO: If query fails, retry with lower complexity limit - selection = tree_properties.select_cache_target( - node, - min_complexity=(QUERY_COMPLEXITY_LIMIT / 500), - max_complexity=QUERY_COMPLEXITY_LIMIT, - cache=self.cache, - # Heuristic: subtree_compleixty * (copies of subtree)^2 - heuristic=lambda complexity, count: ( - math.log(complexity) + 2 * math.log(count) - ), - ) - if selection is None: - # No good subtrees to cache, just return original tree - return False - - await self._cache_with_cluster_cols( - bigframes.core.ArrayValue(selection), - [], - compute_options=compute_options, - ) - return True - - async def _substitute_large_local_sources(self, original_root: nodes.BigFrameNode): - """ - Replace large local sources with the uploaded version of those datasources. - """ - # Step 1: Upload all previously un-uploaded data - needs_upload = [] - for leaf in original_root.unique_nodes(): - if isinstance(leaf, nodes.ReadLocalNode): - if ( - leaf.local_data_source.metadata.total_bytes - > bigframes.constants.MAX_INLINE_BYTES - ): - needs_upload.append(leaf.local_data_source) - - futures: dict[concurrent.futures.Future, local_data.ManagedArrowTable] = dict() - for local_source in needs_upload: - future = self.loader.read_data_async( - local_source, bigframes.core.guid.generate_guid() - ) - futures[future] = local_source - try: - results = await asyncio.gather( - *(asyncio.wrap_future(f) for f in futures.keys()) - ) - for future, result in zip(futures.keys(), results): - self.cache.cache_remote_replacement(futures[future], result) - except Exception as e: - # cancel all futures - for future in futures: - future.cancel() - raise e - - # Step 2: Replace local scans with remote scans - def map_local_scans(node: nodes.BigFrameNode): - if not isinstance(node, nodes.ReadLocalNode): - return node - uploaded_local_data = self.cache.get_uploaded_local_data( - node.local_data_source - ) - if uploaded_local_data is None: - return node - - scan_list = node.scan_list.remap_source_ids( - uploaded_local_data.source_mapping - ) - # offsets_col isn't part of ReadTableNode, so emulate by adding to end of scan_list - if node.offsets_col is not None: - # Offsets are always implicitly the final column of uploaded data - # See: Loader.load_data - scan_list = scan_list.append( - uploaded_local_data.bq_source.table.physical_schema[-1].name, - bigframes.dtypes.INT_DTYPE, - node.offsets_col, - ) - return nodes.ReadTableNode( - uploaded_local_data.bq_source, scan_list, node.session - ) - - return original_root.bottom_up(map_local_scans) - - async def _maybe_find_existing_table( - self, spec: ex_spec.TableOutputSpec - ) -> Optional[bigquery.Table]: - # validate destination table - try: - table = await asyncio.to_thread(self.bqclient.get_table, spec.table) - if spec.if_exists == "fail": - raise ValueError(f"Table already exists: {spec.table.__str__()}") - - if len(spec.cluster_cols) != 0: - if (table.clustering_fields is None) or ( - tuple(table.clustering_fields) != spec.cluster_cols - ): - raise ValueError( - "Table clustering fields cannot be changed after the table has " - f"been created. Requested clustering fields: {spec.cluster_cols}, existing clustering fields: {table.clustering_fields}" - ) - return table - except google.api_core.exceptions.NotFound: - return None - - -def _is_schema_match( - table_schema: Tuple[bigquery.SchemaField, ...], - schema: schemata.ArraySchema, -) -> bool: - if len(table_schema) != len(schema.items): - return False - for field, schema_item in zip(table_schema, schema.items): - if field.name != schema_item.column: - return False - _, field_dtype = bigframes.dtypes.convert_schema_field(field) - if field_dtype != schema_item.dtype: - return False - return True diff --git a/bigframes/session/clients.py b/bigframes/session/clients.py index 49822bac16b..e33413002fe 100644 --- a/bigframes/session/clients.py +++ b/bigframes/session/clients.py @@ -14,51 +14,40 @@ """Clients manages the connection to Google APIs.""" -import threading -from typing import Optional, Sequence, Tuple +import os +import typing +from typing import Optional import google.api_core.client_info import google.api_core.client_options +import google.api_core.exceptions import google.api_core.gapic_v1.client_info import google.auth.credentials -import google.auth.transport.requests import google.cloud.bigquery as bigquery import google.cloud.bigquery_connection_v1 import google.cloud.bigquery_storage_v1 import google.cloud.functions_v2 import google.cloud.resourcemanager_v3 -import google.cloud.storage # type: ignore -import requests +import ibis +import pydata_google_auth -import bigframes.constants import bigframes.version -from . import environment - -_APPLICATION_NAME = f"bigframes/{bigframes.version.__version__} ibis/9.2.0" - +_ENV_DEFAULT_PROJECT = "GOOGLE_CLOUD_PROJECT" +_APPLICATION_NAME = f"bigframes/{bigframes.version.__version__} ibis/{ibis.__version__}" +_SCOPES = ["https://www.googleapis.com/auth/cloud-platform"] # BigQuery is a REST API, which requires the protocol as part of the URL. -_BIGQUERY_REGIONAL_ENDPOINT = "https://bigquery.{location}.rep.googleapis.com" +_BIGQUERY_REGIONAL_ENDPOINT = "https://{location}-bigquery.googleapis.com" # BigQuery Connection and Storage are gRPC APIs, which don't support the # https:// protocol in the API endpoint URL. -_BIGQUERYSTORAGE_REGIONAL_ENDPOINT = "bigquerystorage.{location}.rep.googleapis.com" - +_BIGQUERYCONNECTION_REGIONAL_ENDPOINT = "{location}-bigqueryconnection.googleapis.com" +_BIGQUERYSTORAGE_REGIONAL_ENDPOINT = "{location}-bigquerystorage.googleapis.com" -def _get_application_names(): - apps = [_APPLICATION_NAME] - if environment.is_vscode(): - apps.append("vscode") - if environment.is_vscode_google_cloud_code_extension_installed(): - apps.append(environment.GOOGLE_CLOUD_CODE_EXTENSION_NAME) - elif environment.is_jupyter(): - apps.append("jupyter") - if environment.is_jupyter_bigquery_plugin_installed(): - apps.append(environment.BIGQUERY_JUPYTER_PLUGIN_NAME) - - return " ".join(apps) +def _get_default_credentials_with_project(): + return pydata_google_auth.default(scopes=_SCOPES, use_local_webserver=False) class ClientsProvider: @@ -66,271 +55,143 @@ class ClientsProvider: def __init__( self, - project: str, - credentials: google.auth.credentials.Credentials, - location: Optional[str] = None, - use_regional_endpoints: Optional[bool] = None, - application_name: Optional[str] = None, - bq_kms_key_name: Optional[str] = None, - client_endpoints_override: dict = {}, - *, - requests_transport_adapters: Sequence[ - Tuple[str, requests.adapters.BaseAdapter] - ] = (), + project: Optional[str], + location: Optional[str], + use_regional_endpoints: Optional[bool], + credentials: Optional[google.auth.credentials.Credentials], + application_name: Optional[str], ): + credentials_project = None + if credentials is None: + credentials, credentials_project = _get_default_credentials_with_project() + + # Prefer the project in this order: + # 1. Project explicitly specified by the user + # 2. Project set in the environment + # 3. Project associated with the default credentials + project = ( + project + or os.getenv(_ENV_DEFAULT_PROJECT) + or typing.cast(Optional[str], credentials_project) + ) + + if not project: + raise ValueError( + "Project must be set to initialize BigQuery client. " + "Try setting `bigframes.options.bigquery.project` first." + ) + self._application_name = ( - f"{_get_application_names()} {application_name}" + f"{_APPLICATION_NAME} {application_name}" if application_name - else _get_application_names() + else _APPLICATION_NAME ) self._project = project - - if use_regional_endpoints: - if location is None: - raise ValueError(bigframes.constants.LOCATION_NEEDED_FOR_REP_MESSAGE) - elif ( - location.lower() - not in bigframes.constants.REP_ENABLED_BIGQUERY_LOCATIONS - ): - raise ValueError( - bigframes.constants.REP_NOT_SUPPORTED_MESSAGE.format( - location=location - ) - ) self._location = location self._use_regional_endpoints = use_regional_endpoints - self._requests_transport_adapters = requests_transport_adapters - self._credentials = credentials - self._bq_kms_key_name = bq_kms_key_name - self._client_endpoints_override = client_endpoints_override # cloud clients initialized for lazy load - self._bqclient_lock = threading.Lock() self._bqclient = None - - self._bqconnectionclient_lock = threading.Lock() - self._bqconnectionclient: Optional[ - google.cloud.bigquery_connection_v1.ConnectionServiceClient - ] = None - - self._bqstoragereadclient_lock = threading.Lock() - self._bqstoragereadclient: Optional[ - google.cloud.bigquery_storage_v1.BigQueryReadClient - ] = None - - self._bqstoragewriteclient_lock = threading.Lock() - self._bqstoragewriteclient: Optional[ - google.cloud.bigquery_storage_v1.BigQueryWriteClient - ] = None - - self._cloudfunctionsclient_lock = threading.Lock() - self._cloudfunctionsclient: Optional[ - google.cloud.functions_v2.FunctionServiceClient - ] = None - - self._resourcemanagerclient_lock = threading.Lock() - self._resourcemanagerclient: Optional[ - google.cloud.resourcemanager_v3.ProjectsClient - ] = None - - self._storageclient_lock = threading.Lock() - self._storageclient: Optional[google.cloud.storage.Client] = None - - def _create_bigquery_client(self): - bq_options = None - if "bqclient" in self._client_endpoints_override: - bq_options = google.api_core.client_options.ClientOptions( - api_endpoint=self._client_endpoints_override["bqclient"] - ) - elif self._use_regional_endpoints: - bq_options = google.api_core.client_options.ClientOptions( - api_endpoint=_BIGQUERY_REGIONAL_ENDPOINT.format(location=self._location) - ) - - bq_info = google.api_core.client_info.ClientInfo( - user_agent=self._application_name - ) - - requests_session = google.auth.transport.requests.AuthorizedSession( - self._credentials - ) - for prefix, adapter in self._requests_transport_adapters: - requests_session.mount(prefix, adapter) - - bq_client = bigquery.Client( - client_info=bq_info, - client_options=bq_options, - project=self._project, - location=self._location, - # Use _http so that users can override - # requests options with transport adapters. See internal issue - # b/419106112. - _http=requests_session, - credentials=self._credentials, - ) - - # If a new enough client library is available, we opt-in to the faster - # backend behavior. This only affects code paths where query_and_wait is - # used, which doesn't expose a query job directly. See internal issue - # b/417985981. - if hasattr(bq_client, "default_job_creation_mode"): - bq_client.default_job_creation_mode = "JOB_CREATION_OPTIONAL" - - if self._bq_kms_key_name: - # Note: Key configuration only applies automatically to load and query jobs, not copy jobs. - encryption_config = bigquery.EncryptionConfiguration( - kms_key_name=self._bq_kms_key_name - ) - default_load_job_config = bigquery.LoadJobConfig() - default_query_job_config = bigquery.QueryJobConfig() - default_load_job_config.destination_encryption_configuration = ( - encryption_config - ) - default_query_job_config.destination_encryption_configuration = ( - encryption_config - ) - bq_client.default_load_job_config = default_load_job_config - bq_client.default_query_job_config = default_query_job_config - - return bq_client + self._bqconnectionclient = None + self._bqstoragereadclient = None + self._cloudfunctionsclient = None + self._resourcemanagerclient = None @property def bqclient(self): - with self._bqclient_lock: - if not self._bqclient: - self._bqclient = self._create_bigquery_client() + if not self._bqclient: + bq_options = None + if self._use_regional_endpoints: + bq_options = google.api_core.client_options.ClientOptions( + api_endpoint=_BIGQUERY_REGIONAL_ENDPOINT.format( + location=self._location + ), + ) + bq_info = google.api_core.client_info.ClientInfo( + user_agent=self._application_name + ) + self._bqclient = bigquery.Client( + client_info=bq_info, + client_options=bq_options, + credentials=self._credentials, + project=self._project, + location=self._location, + ) return self._bqclient @property def bqconnectionclient(self): - with self._bqconnectionclient_lock: - if not self._bqconnectionclient: - bqconnection_options = None - if "bqconnectionclient" in self._client_endpoints_override: - bqconnection_options = google.api_core.client_options.ClientOptions( - api_endpoint=self._client_endpoints_override[ - "bqconnectionclient" - ] + if not self._bqconnectionclient: + bqconnection_options = None + if self._use_regional_endpoints: + bqconnection_options = google.api_core.client_options.ClientOptions( + api_endpoint=_BIGQUERYCONNECTION_REGIONAL_ENDPOINT.format( + location=self._location ) - - bqconnection_info = google.api_core.gapic_v1.client_info.ClientInfo( - user_agent=self._application_name ) - self._bqconnectionclient = ( - google.cloud.bigquery_connection_v1.ConnectionServiceClient( - client_info=bqconnection_info, - client_options=bqconnection_options, - credentials=self._credentials, - ) + bqconnection_info = google.api_core.gapic_v1.client_info.ClientInfo( + user_agent=self._application_name + ) + self._bqconnectionclient = ( + google.cloud.bigquery_connection_v1.ConnectionServiceClient( + client_info=bqconnection_info, + client_options=bqconnection_options, + credentials=self._credentials, ) + ) return self._bqconnectionclient @property def bqstoragereadclient(self): - with self._bqstoragereadclient_lock: - if not self._bqstoragereadclient: - bqstorage_options = None - if "bqstoragereadclient" in self._client_endpoints_override: - bqstorage_options = google.api_core.client_options.ClientOptions( - api_endpoint=self._client_endpoints_override[ - "bqstoragereadclient" - ] + if not self._bqstoragereadclient: + bqstorage_options = None + if self._use_regional_endpoints: + bqstorage_options = google.api_core.client_options.ClientOptions( + api_endpoint=_BIGQUERYSTORAGE_REGIONAL_ENDPOINT.format( + location=self._location ) - elif self._use_regional_endpoints: - bqstorage_options = google.api_core.client_options.ClientOptions( - api_endpoint=_BIGQUERYSTORAGE_REGIONAL_ENDPOINT.format( - location=self._location - ) - ) - - bqstorage_info = google.api_core.gapic_v1.client_info.ClientInfo( - user_agent=self._application_name ) - self._bqstoragereadclient = ( - google.cloud.bigquery_storage_v1.BigQueryReadClient( - client_info=bqstorage_info, - client_options=bqstorage_options, - credentials=self._credentials, - ) + bqstorage_info = google.api_core.gapic_v1.client_info.ClientInfo( + user_agent=self._application_name + ) + self._bqstoragereadclient = ( + google.cloud.bigquery_storage_v1.BigQueryReadClient( + client_info=bqstorage_info, + client_options=bqstorage_options, + credentials=self._credentials, ) + ) return self._bqstoragereadclient - @property - def bqstoragewriteclient(self): - with self._bqstoragewriteclient_lock: - if not self._bqstoragewriteclient: - bqstorage_options = None - if "bqstoragewriteclient" in self._client_endpoints_override: - bqstorage_options = google.api_core.client_options.ClientOptions( - api_endpoint=self._client_endpoints_override[ - "bqstoragewriteclient" - ] - ) - elif self._use_regional_endpoints: - bqstorage_options = google.api_core.client_options.ClientOptions( - api_endpoint=_BIGQUERYSTORAGE_REGIONAL_ENDPOINT.format( - location=self._location - ) - ) - - bqstorage_info = google.api_core.gapic_v1.client_info.ClientInfo( - user_agent=self._application_name - ) - self._bqstoragewriteclient = ( - google.cloud.bigquery_storage_v1.BigQueryWriteClient( - client_info=bqstorage_info, - client_options=bqstorage_options, - credentials=self._credentials, - ) - ) - - return self._bqstoragewriteclient - @property def cloudfunctionsclient(self): - with self._cloudfunctionsclient_lock: - if not self._cloudfunctionsclient: - functions_info = google.api_core.gapic_v1.client_info.ClientInfo( - user_agent=self._application_name - ) - self._cloudfunctionsclient = ( - google.cloud.functions_v2.FunctionServiceClient( - client_info=functions_info, - credentials=self._credentials, - ) + if not self._cloudfunctionsclient: + functions_info = google.api_core.gapic_v1.client_info.ClientInfo( + user_agent=self._application_name + ) + self._cloudfunctionsclient = ( + google.cloud.functions_v2.FunctionServiceClient( + client_info=functions_info, + credentials=self._credentials, ) + ) return self._cloudfunctionsclient @property def resourcemanagerclient(self): - with self._resourcemanagerclient_lock: - if not self._resourcemanagerclient: - resourcemanager_info = google.api_core.gapic_v1.client_info.ClientInfo( - user_agent=self._application_name - ) - self._resourcemanagerclient = ( - google.cloud.resourcemanager_v3.ProjectsClient( - credentials=self._credentials, client_info=resourcemanager_info - ) + if not self._resourcemanagerclient: + resourcemanager_info = google.api_core.gapic_v1.client_info.ClientInfo( + user_agent=self._application_name + ) + self._resourcemanagerclient = ( + google.cloud.resourcemanager_v3.ProjectsClient( + credentials=self._credentials, client_info=resourcemanager_info ) + ) return self._resourcemanagerclient - - @property - def storageclient(self): - with self._storageclient_lock: - if not self._storageclient: - storage_info = google.api_core.client_info.ClientInfo( - user_agent=self._application_name - ) - self._storageclient = google.cloud.storage.Client( - client_info=storage_info, - credentials=self._credentials, - ) - - return self._storageclient diff --git a/bigframes/session/deferred.py b/bigframes/session/deferred.py deleted file mode 100644 index 75906e2a124..00000000000 --- a/bigframes/session/deferred.py +++ /dev/null @@ -1,76 +0,0 @@ -# Copyright 2026 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 typing import Any, Callable, Optional, Union - -import pandas as pd - -import bigframes.dataframe -import bigframes.series - - -class DeferredBigQueryDataFrame: - """A proxy object that defers the execution of a BigQuery job until requested.""" - - def __init__( - self, - execution_func: Callable[ - [], - Union[ - bigframes.dataframe.DataFrame, - bigframes.series.Series, - pd.Series, - pd.DataFrame, - ], - ], - ): - self._execution_func = execution_func - self._result: Optional[ - Union[ - bigframes.dataframe.DataFrame, - bigframes.series.Series, - pd.Series, - pd.DataFrame, - ] - ] = None - - @property - def executed(self) -> bool: - return self._result is not None - - def execute( - self, - ) -> Union[ - bigframes.dataframe.DataFrame, - bigframes.series.Series, - pd.Series, - pd.DataFrame, - ]: - """Executes the deferred operation and returns the resulting DataFrame.""" - if self._result is None: - self._result = self._execution_func() - return self._result - - def _repr_mimebundle_(self, include=None, exclude=None): - from bigframes.display.anywidget import TableWidget - - return TableWidget(self)._repr_mimebundle_(include=include, exclude=exclude) # type: ignore - - def __getattr__(self, name: str) -> Any: - raise AttributeError( - f"'{type(self).__name__}' object has no attribute '{name}'. " - "This is a deferred object. Display it to run the query interactively." - ) diff --git a/bigframes/session/direct_gbq_execution.py b/bigframes/session/direct_gbq_execution.py deleted file mode 100644 index bcfc29ba971..00000000000 --- a/bigframes/session/direct_gbq_execution.py +++ /dev/null @@ -1,194 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -from __future__ import annotations - -import asyncio -from typing import Literal, Mapping, Optional, Tuple - -import google.api_core.exceptions -import google.cloud.bigquery.job as bq_job -import google.cloud.bigquery.table as bq_table -import google.cloud.bigquery_storage_v1 -from google.cloud import bigquery - -import bigframes -import bigframes.core.compile -import bigframes.core.events -import bigframes.session._io.bigquery as bq_io -import bigframes.session.metrics -from bigframes import exceptions as bfe -from bigframes.core import bq_data, compile, nodes -from bigframes.core.compile.configs import CompileRequest -from bigframes.session import execution_spec, executor, semi_executor - -_WRITE_DISPOSITIONS = { - "fail": bigquery.WriteDisposition.WRITE_EMPTY, - "replace": bigquery.WriteDisposition.WRITE_TRUNCATE, - "append": bigquery.WriteDisposition.WRITE_APPEND, -} - - -class DirectGbqExecutor(semi_executor.SemiExecutor): - def __init__( - self, - bqclient: bigquery.Client, - bqstoragereadclient: google.cloud.bigquery_storage_v1.BigQueryReadClient, - *, - publisher: bigframes.core.events.Publisher, - compiler: Literal["ibis", "sqlglot"] = "sqlglot", - metrics: Optional[bigframes.session.metrics.ExecutionMetrics] = None, - labels: Mapping[str, str] = {}, - ): - self.bqclient = bqclient - self._compiler_name = compiler - self._bqstoragereadclient = bqstoragereadclient - self._publisher = publisher - self._metrics = metrics - self._labels = labels - - async def execute( - self, - plan: nodes.BigFrameNode, - spec: execution_spec.ExecutionSpec, - ) -> executor.ExecuteResult: - """Just execute whatever plan as is, without further caching or decomposition.""" - compiled = compile.compile_sql( - CompileRequest( - plan, - sort_rows=spec.ordered, - peek_count=spec.peek, - ), - compiler_name=self._compiler_name, - ) - job_config = bigquery.QueryJobConfig() - dest_spec = spec.destination_spec - cluster_cols = None - can_skip_job = True - if isinstance(dest_spec, execution_spec.TableOutputSpec): - job_config.destination = dest_spec.table - job_config.write_disposition = _WRITE_DISPOSITIONS[dest_spec.if_exists] - cluster_cols = dest_spec.cluster_cols if dest_spec.cluster_cols else None - job_config.clustering_fields = cluster_cols - can_skip_job = False - elif isinstance(dest_spec, execution_spec.EphemeralTableSpec): - # Need destination table, but jobless execution might not create a destination table - can_skip_job = False - elif dest_spec is not None: - raise ValueError( - f"Direct GBQ Executor does not support destination: {dest_spec}" - ) - - job_config.labels["bigframes-dtypes"] = compiled.encoded_type_refs - if self._labels: - job_config.labels.update(self._labels) - if spec.bigquery_config is not None: - if spec.bigquery_config.extra_query_labels: - job_config.labels.update(spec.bigquery_config.extra_query_labels) - if spec.bigquery_config.maximum_bytes_billed is not None: - job_config.maximum_bytes_billed = ( - spec.bigquery_config.maximum_bytes_billed - ) - - iterator, query_job = await asyncio.to_thread( - self._run_execute_query, - sql=compiled.sql, - job_config=job_config, - query_with_job=(not can_skip_job), - session=plan.session, - cell_execution_count=spec.cell_execution_count, - ) - result_bq_data = None - if query_job and query_job.destination: - dst = query_job.destination - result_bq_data = bq_data.BigqueryDataSource( - table=bq_data.GbqNativeTable.from_ref_and_schema( - dst, - tuple(compiled.sql_schema), - cluster_cols=cluster_cols or (), - location=iterator.location or self.bqclient.location, - table_type="TABLE", - ), - schema=plan.schema, - ordering=compiled.row_order, - n_rows=iterator.total_rows, - ) - - execution_metadata = executor.ExecutionMetadata.from_iterator_and_job( - iterator, query_job - ) - result_mostly_cached = ( - hasattr(iterator, "_is_almost_completely_cached") - and iterator._is_almost_completely_cached() - ) - - if (isinstance(dest_spec, execution_spec.EphemeralTableSpec)) or ( - (result_bq_data is not None) and not result_mostly_cached - ): - assert result_bq_data is not None, "expected result table but none exists" - return executor.BQTableExecuteResult( - data=result_bq_data, - project_id=self.bqclient.project, - storage_client=self._bqstoragereadclient, - execution_metadata=execution_metadata, - selected_fields=tuple((col, col) for col in plan.schema.names), - ) - else: - return executor.LocalExecuteResult( - data=iterator.to_arrow().select(plan.schema.names), - bf_schema=plan.schema, - execution_metadata=execution_metadata, - ) - - def _run_execute_query( - self, - sql: str, - job_config: bq_job.QueryJobConfig, - query_with_job: bool, - session, - cell_execution_count: Optional[int] = None, - ) -> Tuple[bq_table.RowIterator, Optional[bigquery.QueryJob]]: - """ - Starts BigQuery query job and waits for results. - """ - try: - if query_with_job: - return bq_io.start_query_with_job( - self.bqclient, - sql, - job_config=job_config, - metrics=self._metrics, - publisher=self._publisher, - session=session, - cell_execution_count=cell_execution_count, - ) - else: - return ( - bq_io.start_query_job_optional( - self.bqclient, - sql, - job_config=job_config, - metrics=self._metrics, - publisher=self._publisher, - session=session, - cell_execution_count=cell_execution_count, - ), - None, - ) - except google.api_core.exceptions.BadRequest as e: - # 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 bfe.QueryComplexityError(new_message) from e - else: - raise diff --git a/bigframes/session/dry_runs.py b/bigframes/session/dry_runs.py deleted file mode 100644 index 03688b38cd3..00000000000 --- a/bigframes/session/dry_runs.py +++ /dev/null @@ -1,189 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -from __future__ import annotations - -import copy -from typing import Any, Dict, List, Sequence, Union - -import pandas -from google.cloud import bigquery - -from bigframes import dtypes -from bigframes.core import bigframe_node, bq_data, nodes - - -def get_table_stats( - table: Union[bq_data.GbqNativeTable, bq_data.BiglakeIcebergTable], -) -> pandas.Series: - values: List[Any] = [] - index: List[Any] = [] - - # Indicate that no query is executed. - index.append("isQuery") - values.append(False) - - # Populate column and index types - col_dtypes = dtypes.bf_type_from_type_kind(table.physical_schema) - index.append("columnCount") - values.append(len(col_dtypes)) - index.append("columnDtypes") - values.append(col_dtypes) - - # Add raw BQ schema - index.append("bigquerySchema") - values.append(table.physical_schema) - - index.append("numBytes") - values.append(table.metadata.numBytes) - index.append("numRows") - values.append(table.metadata.numRows) - index.append("location") - values.append(table.metadata.location) - index.append("type") - values.append(table.metadata.type) - - index.append("creationTime") - values.append(table.metadata.created_time) - - index.append("lastModifiedTime") - values.append(table.metadata.modified_time) - - return pandas.Series(values, index=index) - - -def get_query_stats_with_inferred_dtypes( - query_job: bigquery.QueryJob, - value_cols: Sequence[str], - index_cols: Sequence[str], -) -> pandas.Series: - if query_job.schema is None: - # If the schema is not available, don't bother inferring dtypes. - return get_query_stats(query_job) - - col_dtypes = dtypes.bf_type_from_type_kind(query_job.schema) - - if value_cols: - value_col_dtypes = { - col: col_dtypes[col] for col in value_cols if col in col_dtypes - } - else: - # Use every column that is not mentioned as an index column - value_col_dtypes = { - col: dtype - for col, dtype in col_dtypes.items() - if col not in set(index_cols) - } - - index_dtypes = [col_dtypes[col] for col in index_cols] - - return get_query_stats_with_dtypes(query_job, value_col_dtypes, index_dtypes) - - -def get_query_stats_with_dtypes( - query_job: bigquery.QueryJob, - column_dtypes: Dict[str, dtypes.Dtype], - index_dtypes: Sequence[dtypes.Dtype], - expr_root: bigframe_node.BigFrameNode | None = None, -) -> pandas.Series: - """ - Returns important stats from the query job as a Pandas Series. The dtypes information is added too. - - Args: - expr_root (Optional): - The root of the expression tree that may contain local data, whose size is added to the - total bytes count if available. - - """ - index = ["columnCount", "columnDtypes", "indexLevel", "indexDtypes"] - values = [len(column_dtypes), column_dtypes, len(index_dtypes), index_dtypes] - - s = pandas.Series(values, index=index) - - result = pandas.concat([s, get_query_stats(query_job)]) - if expr_root is not None: - result["totalBytesProcessed"] += get_local_bytes(expr_root) - return result - - -def get_query_stats( - query_job: bigquery.QueryJob, -) -> pandas.Series: - """Returns important stats from the query job as a Pandas Series.""" - - index: List[Any] = [] - values: List[Any] = [] - - # Add raw BQ schema - index.append("bigquerySchema") - values.append(query_job.schema) - - job_api_repr = copy.deepcopy(query_job._properties) - - # jobReference might not be populated for "job optional" queries. - job_ref = job_api_repr.get("jobReference", {}) - for key, val in job_ref.items(): - index.append(key) - values.append(val) - - configuration = job_api_repr.get("configuration", {}) - index.append("jobType") - values.append(configuration.get("jobType", None)) - index.append("dispatchedSql") - values.append(configuration.get("query", {}).get("query", None)) - - query_config = configuration.get("query", {}) - for key in ("destinationTable", "useLegacySql"): - index.append(key) - values.append(query_config.get(key, None)) - - statistics = job_api_repr.get("statistics", {}) - query_stats = statistics.get("query", {}) - for key in ( - "referencedTables", - "totalBytesProcessed", - "cacheHit", - "statementType", - ): - index.append(key) - values.append(query_stats.get(key, None)) - - creation_time = statistics.get("creationTime", None) - index.append("creationTime") - values.append( - pandas.Timestamp(creation_time, unit="ms", tz="UTC") - if creation_time is not None - else None - ) - - result = pandas.Series(values, index=index) - if result["totalBytesProcessed"] is None: - result["totalBytesProcessed"] = 0 - else: - result["totalBytesProcessed"] = int(result["totalBytesProcessed"]) - - return result - - -def get_local_bytes(root: bigframe_node.BigFrameNode) -> int: - def get_total_bytes( - root: bigframe_node.BigFrameNode, child_results: tuple[int, ...] - ) -> int: - child_bytes = sum(child_results) - - if isinstance(root, nodes.ReadLocalNode): - return child_bytes + root.local_data_source.data.get_total_buffer_size() - - return child_bytes - - return root.reduce_up(get_total_bytes) diff --git a/bigframes/session/environment.py b/bigframes/session/environment.py deleted file mode 100644 index 940f8deed4a..00000000000 --- a/bigframes/session/environment.py +++ /dev/null @@ -1,101 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - - -import importlib -import json -import os -import pathlib - -Path = pathlib.Path - - -# The identifier for GCP VS Code extension -# https://cloud.google.com/code/docs/vscode/install -GOOGLE_CLOUD_CODE_EXTENSION_NAME = "googlecloudtools.cloudcode" - - -# The identifier for BigQuery Jupyter notebook plugin -# https://cloud.google.com/bigquery/docs/jupyterlab-plugin -BIGQUERY_JUPYTER_PLUGIN_NAME = "bigquery_jupyter_plugin" - - -def _is_vscode_extension_installed(extension_id: str) -> bool: - """ - Checks if a given Visual Studio Code extension is installed. - Args: - extension_id: The ID of the extension (e.g., "ms-python.python"). - Returns: - True if the extension is installed, False otherwise. - """ - try: - # Determine the user's VS Code extensions directory. - user_home = Path.home() - vscode_extensions_dir = user_home / ".vscode" / "extensions" - - # Check if the extensions directory exists. - if not vscode_extensions_dir.exists(): - return False - - # Iterate through the subdirectories in the extensions directory. - extension_dirs = filter( - lambda p: p.is_dir() and p.name.startswith(extension_id + "-"), - vscode_extensions_dir.iterdir(), - ) - for extension_dir in extension_dirs: - # As a more robust check, the manifest file must exist. - manifest_path = extension_dir / "package.json" - if not manifest_path.exists() or not manifest_path.is_file(): - continue - - # Finally, the manifest file must be a valid json - with open(manifest_path, "r", encoding="utf-8") as f: - json.load(f) - - return True - except Exception: - pass - - return False - - -def _is_package_installed(package_name: str) -> bool: - """ - Checks if a Python package is installed. - Args: - package_name: The name of the package to check (e.g., "requests", "numpy"). - Returns: - True if the package is installed, False otherwise. - """ - try: - importlib.import_module(package_name) - return True - except Exception: - return False - - -def is_vscode() -> bool: - return os.getenv("VSCODE_PID") is not None - - -def is_jupyter() -> bool: - return os.getenv("JPY_PARENT_PID") is not None - - -def is_vscode_google_cloud_code_extension_installed() -> bool: - return _is_vscode_extension_installed(GOOGLE_CLOUD_CODE_EXTENSION_NAME) - - -def is_jupyter_bigquery_plugin_installed() -> bool: - return _is_package_installed(BIGQUERY_JUPYTER_PLUGIN_NAME) diff --git a/bigframes/session/execution_cache.py b/bigframes/session/execution_cache.py deleted file mode 100644 index ef4f324afce..00000000000 --- a/bigframes/session/execution_cache.py +++ /dev/null @@ -1,88 +0,0 @@ -# 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. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from __future__ import annotations - -import dataclasses -import weakref -from typing import Mapping, Optional - -from bigframes.core import bq_data, local_data, nodes - -SourceIdMapping = Mapping[str, str] - - -@dataclasses.dataclass(frozen=True) -class UploadedLocalData: - bq_source: bq_data.BigqueryDataSource - source_mapping: SourceIdMapping - - -class ExecutionCache: - def __init__(self): - # effectively two separate caches that don't interact - self._cached_executions: weakref.WeakKeyDictionary[ - nodes.BigFrameNode, bq_data.BigqueryDataSource - ] = weakref.WeakKeyDictionary() - # This upload cache is entirely independent of the plan cache. - self._uploaded_local_data: weakref.WeakKeyDictionary[ - local_data.ManagedArrowTable, - UploadedLocalData, - ] = weakref.WeakKeyDictionary() - - def subsitute_cached_subplans(self, root: nodes.BigFrameNode) -> nodes.BigFrameNode: - def replace_if_cached(node: nodes.BigFrameNode) -> nodes.BigFrameNode: - if node not in self._cached_executions: - return node - # Assumption: GBQ cached table uses field name as bq column name - scan_list = nodes.ScanList( - tuple(nodes.ScanItem(field.id, field.id.sql) for field in node.fields) - ) - bq_data = self._cached_executions[node] - cached_replacement = nodes.CachedTableNode( - source=bq_data, - scan_list=scan_list, - table_session=node.session, - original_node=node, - ) - assert node.schema == cached_replacement.schema - return cached_replacement - - return nodes.top_down(root, replace_if_cached) - - def cache_results_table( - self, - original_root: nodes.BigFrameNode, - data: bq_data.BigqueryDataSource, - ): - self._cached_executions[original_root] = data - - ## Local data upload caching - def cache_remote_replacement( - self, - local_data: local_data.ManagedArrowTable, - bq_data: bq_data.BigqueryDataSource, - ): - # bq table has one extra column for offsets, those are implicit for local data - assert len(local_data.schema.items) + 1 == len(bq_data.table.physical_schema) - mapping = { - local_data.schema.items[i].column: bq_data.table.physical_schema[i].name - for i in range(len(local_data.schema)) - } - self._uploaded_local_data[local_data] = UploadedLocalData(bq_data, mapping) - - def get_uploaded_local_data( - self, local_data: local_data.ManagedArrowTable - ) -> Optional[UploadedLocalData]: - return self._uploaded_local_data.get(local_data) diff --git a/bigframes/session/execution_spec.py b/bigframes/session/execution_spec.py deleted file mode 100644 index 89de6eec902..00000000000 --- a/bigframes/session/execution_spec.py +++ /dev/null @@ -1,146 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from __future__ import annotations - -import dataclasses -from typing import Literal, Mapping, Optional, Union - -from google.cloud import bigquery - -from bigframes._config import ComputeOptions - - -@dataclasses.dataclass(frozen=True) -class BqComputeOptions: - enable_multi_query_execution: bool = True - maximum_bytes_billed: Optional[int] = None - extra_query_labels: tuple[tuple[str, str], ...] = () - - @classmethod - def from_compute_options(cls, compute_options: ComputeOptions) -> BqComputeOptions: - return cls( - enable_multi_query_execution=compute_options.enable_multi_query_execution, - maximum_bytes_billed=compute_options.maximum_bytes_billed, - extra_query_labels=tuple(compute_options.extra_query_labels.items()), - ) - - def push_labels(self, labels: Mapping[str, str]) -> BqComputeOptions: - return dataclasses.replace( - self, - extra_query_labels=tuple(labels.items()) + self.extra_query_labels, - ) - - -@dataclasses.dataclass(frozen=True) -class ExecutionSpec: - # destination for the result of the operation. Executor may also incidentally create other temporary tables for its own purposes. - destination_spec: Union[ - TableOutputSpec, GcsOutputSpec, EphemeralTableSpec, None - ] = None - # If set, the result will be truncated to the given number of rows. Which N rows is - # implementation dependent and not stable. - peek: Optional[int] = None - # Controls whether output iterator is ordered. Cannot be true if destination is not - # guaranteed to be ordered. - ordered: bool = False - # This is an optimization flag for gbq execution, it doesn't change semantics, but if promise is falsely made, errors may occur - promise_under_10gb: bool = False - - # BigQuery specific options - bigquery_config: Optional[BqComputeOptions] = None - cell_execution_count: Optional[int] = None - - def with_bq_labels(self, labels: Mapping[str, str]) -> ExecutionSpec: - bq_config = self.bigquery_config or BqComputeOptions() - return dataclasses.replace(self, bigquery_config=bq_config.push_labels(labels)) - - def with_compute_options(self, compute_options: ComputeOptions) -> ExecutionSpec: - """ - Grabs the current global or thread-local config and binds it to the execution spec. - - Returns a new ExecutionSpec with the current configuration applied. - """ - new_bq_config = BqComputeOptions.from_compute_options(compute_options) - if self.bigquery_config: - # merge labels, new ComputeOptions takes priority for everything else - new_bq_config = new_bq_config.push_labels( - dict(self.bigquery_config.extra_query_labels) - ) - - cell_execution_count = self.cell_execution_count - if cell_execution_count is None: - from bigframes.core.utils import get_ipython_execution_count - - cell_execution_count = get_ipython_execution_count() - - return dataclasses.replace( - self, - bigquery_config=new_bq_config, - cell_execution_count=cell_execution_count, - ) - - -# Used internally by execution -@dataclasses.dataclass(frozen=True) -class EphemeralTableSpec: - """ - Specifies that the result of an operation should be a temporary table of some sort. - - No guarantees on lifetime, may be a session temp table, or a bq-created temp table with <24hr life. - - Used internally when results need temporary staging, because they are large (>10GB), or needed in subsequent operations. - """ - - pass - - -@dataclasses.dataclass(frozen=True) -class CacheSpec: - """ - Specifies that the result of an operation should be a session temp table. - The table will be automatically deleted after the session ends. - """ - - cluster_cols: tuple[ - str, ... - ] = () # if empty, will cluster using order key if ordering_key is set - # Controls ordering and whether extra columns are materialized to preserve ordering - # Any extra columns will be appended to the end of the schema. - # None: ordering may be discarded entirely (ordering metadata will still be provided if ordering is derivable from materialized columns) - # order_rows: the result iterator itself will be ordered. For gbq execution, result cannot exceed 10GB. - # order_key: the result set ordered by a key, may materialize extra columns. - # offsets_col: order the result set by an offsets column, materializes one extra column. - ordering: Literal["order_rows", "offsets_col", "order_key"] | None = None - - -@dataclasses.dataclass(frozen=True) -class TableOutputSpec: - """ - Specifies that the result of an operation should be exported to a specific named table. - - The executor is not responsible for managing lifecycle of the table. - """ - - table: bigquery.TableReference - cluster_cols: tuple[str, ...] = () - if_exists: Literal["fail", "replace", "append"] = "fail" - - -@dataclasses.dataclass(frozen=True) -class GcsOutputSpec: - uri: str - format: Literal["json", "csv", "parquet"] - # sequence of (option, value) pairs - export_options: tuple[tuple[str, Union[bool, str]], ...] diff --git a/bigframes/session/executor.py b/bigframes/session/executor.py deleted file mode 100644 index ba5ac60d74f..00000000000 --- a/bigframes/session/executor.py +++ /dev/null @@ -1,350 +0,0 @@ -# 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. -# 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 abc -import dataclasses -import functools -import itertools -from typing import Iterator, Literal, Optional, Sequence, Union - -import google.cloud.bigquery.table as bq_table -import pandas as pd -import pyarrow -import pyarrow as pa -from google.cloud import bigquery, bigquery_storage_v1 - -import bigframes -import bigframes.core -import bigframes.core.schema -import bigframes.dtypes -import bigframes.session._io.pandas as io_pandas -import bigframes.session.execution_spec as ex_spec -from bigframes.core import bq_data, local_data, pyarrow_utils - -_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`." -) - - -class ResultsIterator(Iterator[pa.RecordBatch]): - """ - Iterator for query results, with some extra metadata attached. - """ - - def __init__( - self, - batches: Iterator[pa.RecordBatch], - schema: bigframes.core.schema.ArraySchema, - total_rows: Optional[int] = 0, - total_bytes: Optional[int] = 0, - ): - self._batches = batches - self._schema = schema - self._total_rows = total_rows - self._total_bytes = total_bytes - - @property - def approx_total_rows(self) -> Optional[int]: - return self._total_rows - - @property - def approx_total_bytes(self) -> Optional[int]: - return self._total_bytes - - def __next__(self) -> pa.RecordBatch: - return next(self._batches) - - @property - def arrow_batches(self) -> Iterator[pyarrow.RecordBatch]: - result_rows = 0 - - for batch in self._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, limit: Optional[int] = None) -> 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. - # Any discrepencies between predicted schema and actual schema will produce errors. - batches = iter(self.arrow_batches) - peek_it = itertools.islice(batches, 0, 1) - peek_value = list(peek_it) - # TODO: Enforce our internal schema on the table for consistency - if len(peek_value) > 0: - batches = itertools.chain(peek_value, batches) # reconstruct - if limit: - batches = pyarrow_utils.truncate_pyarrow_iterable( - batches, max_results=limit - ) - return pyarrow.Table.from_batches(batches) - else: - try: - return self._schema.to_pyarrow().empty_table() - except pa.ArrowNotImplementedError: - # Bug with some pyarrow versions, empty_table only supports base storage types, not extension types. - return self._schema.to_pyarrow(use_storage_types=True).empty_table() - - def to_pandas(self, limit: Optional[int] = None) -> pd.DataFrame: - return io_pandas.arrow_to_pandas(self.to_arrow_table(limit=limit), self._schema) - - def to_pandas_batches( - self, page_size: Optional[int] = None, max_results: Optional[int] = None - ) -> Iterator[pd.DataFrame]: - assert (page_size is None) or (page_size > 0) - assert (max_results is None) or (max_results > 0) - batch_iter: Iterator[Union[pyarrow.Table, pyarrow.RecordBatch]] = ( - self.arrow_batches - ) - if max_results is not None: - batch_iter = pyarrow_utils.truncate_pyarrow_iterable( - batch_iter, max_results - ) - - if page_size is not None: - batches_iter = pyarrow_utils.chunk_by_row_count(batch_iter, page_size) - batch_iter = map( - lambda batches: pyarrow.Table.from_batches(batches), batches_iter - ) - - yield from map( - functools.partial(io_pandas.arrow_to_pandas, schema=self._schema), - batch_iter, - ) - - def to_py_scalar(self): - columns = list(self.to_arrow_table().to_pydict().values()) - if len(columns) != 1: - raise ValueError( - f"Expected single column result, got {len(columns)} columns." - ) - column = columns[0] - if len(column) != 1: - raise ValueError(f"Expected single row result, got {len(column)} rows.") - return column[0] - - -class ExecuteResult(abc.ABC): - @property - @abc.abstractmethod - def execution_metadata(self) -> ExecutionMetadata: ... - - @property - @abc.abstractmethod - def schema(self) -> bigframes.core.schema.ArraySchema: ... - - @abc.abstractmethod - def batches(self, sample_rate: Optional[float] = None) -> ResultsIterator: ... - - @property - def query_job(self) -> Optional[bigquery.QueryJob]: - return self.execution_metadata.query_job - - @property - def total_bytes_processed(self) -> Optional[int]: - return self.execution_metadata.bytes_processed - - -@dataclasses.dataclass(frozen=True) -class ExecutionMetadata: - query_job: Optional[bigquery.QueryJob] = None - bytes_processed: Optional[int] = None - - @classmethod - def from_iterator_and_job( - cls, iterator: bq_table.RowIterator, job: Optional[bigquery.QueryJob] - ) -> ExecutionMetadata: - return cls(query_job=job, bytes_processed=iterator.total_bytes_processed) - - -class LocalExecuteResult(ExecuteResult): - def __init__( - self, - data: pa.Table, - bf_schema: bigframes.core.schema.ArraySchema, - execution_metadata: ExecutionMetadata = ExecutionMetadata(), - ): - self._data = local_data.ManagedArrowTable.from_pyarrow(data, bf_schema) - self._execution_metadata = execution_metadata - - @property - def execution_metadata(self) -> ExecutionMetadata: - return self._execution_metadata - - @property - def schema(self) -> bigframes.core.schema.ArraySchema: - return self._data.schema - - def batches(self, sample_rate: Optional[float] = None) -> ResultsIterator: - return ResultsIterator( - iter(self._data.to_arrow(sample_rate=sample_rate)[1]), - self.schema, - self._data.metadata.row_count, - self._data.metadata.total_bytes, - ) - - -class EmptyExecuteResult(ExecuteResult): - def __init__( - self, - bf_schema: bigframes.core.schema.ArraySchema, - execution_metadata: ExecutionMetadata = ExecutionMetadata(), - ): - self._schema = bf_schema - self._execution_metadata = execution_metadata - - @property - def execution_metadata(self) -> ExecutionMetadata: - return self._execution_metadata - - @property - def schema(self) -> bigframes.core.schema.ArraySchema: - return self._schema - - def batches(self, sample_rate: Optional[float] = None) -> ResultsIterator: - return ResultsIterator(iter([]), self.schema, 0, 0) - - -class BQTableExecuteResult(ExecuteResult): - def __init__( - self, - data: bq_data.BigqueryDataSource, - storage_client: bigquery_storage_v1.BigQueryReadClient, - project_id: str, - *, - execution_metadata: ExecutionMetadata = ExecutionMetadata(), - limit: Optional[int] = None, - selected_fields: Optional[Sequence[tuple[str, str]]] = None, - ): - self._data = data - self._project_id = project_id - self._execution_metadata = execution_metadata - self._storage_client = storage_client - self._limit = limit - self._selected_fields = selected_fields or [ - (name, name) for name in data.schema.names - ] - - @property - def execution_metadata(self) -> ExecutionMetadata: - return self._execution_metadata - - @property - @functools.cache - def schema(self) -> bigframes.core.schema.ArraySchema: - source_ids = [selection[0] for selection in self._selected_fields] - return self._data.schema.select(source_ids).rename(dict(self._selected_fields)) - - def batches(self, sample_rate: Optional[float] = None) -> ResultsIterator: - read_batches = bq_data.get_arrow_batches( - self._data, - [x[0] for x in self._selected_fields], - self._storage_client, - self._project_id, - sample_rate=sample_rate, - ) - arrow_batches: Iterator[pa.RecordBatch] = map( - functools.partial( - pyarrow_utils.rename_batch, names=list(self.schema.names) - ), - read_batches.iter, - ) - approx_bytes: Optional[int] = read_batches.approx_bytes - approx_rows: Optional[int] = self._data.n_rows or read_batches.approx_rows - - if self._limit is not None: - if approx_rows is not None: - approx_rows = min(approx_rows, self._limit) - arrow_batches = pyarrow_utils.truncate_pyarrow_iterable( - arrow_batches, self._limit - ) - - if self._data.sql_predicate: - approx_bytes = None - approx_rows = None - - return ResultsIterator(arrow_batches, self.schema, approx_rows, approx_bytes) - - -@dataclasses.dataclass(frozen=True) -class HierarchicalKey: - columns: tuple[str, ...] - - -@dataclasses.dataclass(frozen=True) -class CacheConfig(abc.ABC): - optimize_for: Union[Literal["auto", "head"], HierarchicalKey] = "auto" - if_cached: Literal["reuse-strict", "reuse-any", "replace"] = "reuse-any" - enable_multi_query_execution: Optional[bool] = None - - -class Executor(abc.ABC): - """ - Interface for an executor, which compiles and executes ArrayValue objects. - """ - - def to_sql( - self, - array_value: bigframes.core.ArrayValue, - offset_column: Optional[str] = None, - ordered: bool = False, - enable_cache: bool = True, - ) -> str: - """ - Convert an ArrayValue to a sql query that will yield its value. - """ - raise NotImplementedError("to_sql not implemented for this executor") - - @abc.abstractmethod - def execute( - self, - array_value: bigframes.core.ArrayValue, - execution_spec: ex_spec.ExecutionSpec, - ) -> ExecuteResult: - """ - Execute the ArrayValue. - """ - ... - - def dry_run( - self, array_value: bigframes.core.ArrayValue, ordered: bool = True - ) -> bigquery.QueryJob: - """ - Dry run executing the ArrayValue. - - Does not actually execute the data but will get stats and indicate any invalid query errors. - """ - raise NotImplementedError("dry_run not implemented for this executor") - - def cached( - self, - array_value: bigframes.core.ArrayValue, - *, - config: CacheConfig, - ) -> None: - raise NotImplementedError("cached not implemented for this executor") diff --git a/bigframes/session/iceberg.py b/bigframes/session/iceberg.py deleted file mode 100644 index 0d2539f5554..00000000000 --- a/bigframes/session/iceberg.py +++ /dev/null @@ -1,207 +0,0 @@ -# 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. -# 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 datetime -import json -import urllib.parse -from typing import List - -import google.auth.transport.requests -import google.cloud.bigquery as bq -import pyiceberg -import pyiceberg.schema -import pyiceberg.types -import requests -from pyiceberg.catalog import load_catalog - -from bigframes.core import bq_data - - -def get_table( - user_project_id: str, full_table_id: str, credentials -) -> bq_data.BiglakeIcebergTable: - table_parts = full_table_id.split(".") - if len(table_parts) != 4: - raise ValueError("Iceberg catalog table must contain exactly 4 parts") - - catalog_project_id, catalog_id, namespace, table = table_parts - - credentials.refresh(google.auth.transport.requests.Request()) - token = credentials.token - - base_uri = "https://biglake.googleapis.com/iceberg/v1/restcatalog" - - # Maybe can drop the pyiceberg dependency at some point, but parsing through raw schema json seems a bit painful - catalog = load_catalog( - f"{catalog_project_id}.{catalog_id}", - **{ - "uri": base_uri, - "header.x-goog-user-project": user_project_id, - "oauth2-server-uri": "https://oauth2.googleapis.com/token", - "token": token, - "warehouse": f"gs://{catalog_id}", - }, - ) - - response = requests.get( - f"{base_uri}/extensions/projects/{urllib.parse.quote(catalog_project_id, safe='')}/catalogs/{urllib.parse.quote(catalog_id, safe='')}", - headers={ - "Authorization": f"Bearer {credentials.token}", - "Content-Type": "application/json", - "header.x-goog-user-project": user_project_id, - }, - ) - response.raise_for_status() - location = _extract_location_from_catalog_extension_data(response) - - iceberg_table = catalog.load_table(f"{namespace}.{table}") - bq_schema = pyiceberg.schema.visit(iceberg_table.schema(), SchemaVisitor()) - # TODO: Handle physical layout to help optimize - # TODO: Use snapshot metadata to get row, byte counts - return bq_data.BiglakeIcebergTable( - catalog_project_id, - catalog_id, - namespace, - table, - physical_schema=bq_schema, # type: ignore - cluster_cols=(), - metadata=bq_data.TableMetadata( - location=location, - type="TABLE", - modified_time=datetime.datetime.fromtimestamp( - iceberg_table.metadata.last_updated_ms / 1000.0 - ), - ), - ) - - -def _extract_location_from_catalog_extension_data(data): - catalog_extension_metadata = json.loads(data.text) - storage_region = catalog_extension_metadata["storage-regions"][ - 0 - ] # assumption: exactly 1 region - replicas = tuple(item["region"] for item in catalog_extension_metadata["replicas"]) - return bq_data.GcsRegion(storage_region, replicas) - - -class SchemaVisitor(pyiceberg.schema.SchemaVisitorPerPrimitiveType[bq.SchemaField]): - # Override returns a tuple of fields instead of a single field, violating supertype signature but intentional for this visitor. - def schema( # type: ignore[override] - self, schema: pyiceberg.schema.Schema, struct_result: bq.SchemaField - ) -> tuple[bq.SchemaField, ...]: - return tuple(f for f in struct_result.fields) - - def struct( - self, struct: pyiceberg.types.StructType, field_results: List[bq.SchemaField] - ) -> bq.SchemaField: - return bq.SchemaField("", "RECORD", fields=field_results) - - def field( - self, field: pyiceberg.types.NestedField, field_result: bq.SchemaField - ) -> bq.SchemaField: - return bq.SchemaField( - field.name, - field_result.field_type, - mode=field_result.mode or "NULLABLE", - fields=field_result.fields, - ) - - def map( - self, - map_type: pyiceberg.types.MapType, - key_result: bq.SchemaField, - value_result: bq.SchemaField, - ) -> bq.SchemaField: - return bq.SchemaField("", "UNKNOWN") - - def list( - self, list_type: pyiceberg.types.ListType, element_result: bq.SchemaField - ) -> bq.SchemaField: - return bq.SchemaField( - "", element_result.field_type, mode="REPEATED", fields=element_result.fields - ) - - def visit_fixed(self, fixed_type: pyiceberg.types.FixedType) -> bq.SchemaField: - return bq.SchemaField("", "UNKNOWN") - - def visit_decimal( - self, decimal_type: pyiceberg.types.DecimalType - ) -> bq.SchemaField: - # BIGNUMERIC not supported in iceberg tables yet, so just assume numeric - return bq.SchemaField("", "NUMERIC") - - def visit_boolean( - self, boolean_type: pyiceberg.types.BooleanType - ) -> bq.SchemaField: - return bq.SchemaField("", "NUMERIC") - - def visit_integer( - self, integer_type: pyiceberg.types.IntegerType - ) -> bq.SchemaField: - return bq.SchemaField("", "INTEGER") - - def visit_long(self, long_type: pyiceberg.types.LongType) -> bq.SchemaField: - return bq.SchemaField("", "INTEGER") - - def visit_float(self, float_type: pyiceberg.types.FloatType) -> bq.SchemaField: - # 32-bit IEEE 754 floating point - return bq.SchemaField("", "FLOAT") - - def visit_double(self, double_type: pyiceberg.types.DoubleType) -> bq.SchemaField: - # 64-bit IEEE 754 floating point - return bq.SchemaField("", "FLOAT") - - def visit_date(self, date_type: pyiceberg.types.DateType) -> bq.SchemaField: - # Date encoded as an int - return bq.SchemaField("", "DATE") - - def visit_time(self, time_type: pyiceberg.types.TimeType) -> bq.SchemaField: - return bq.SchemaField("", "TIME") - - def visit_timestamp( - self, timestamp_type: pyiceberg.types.TimestampType - ) -> bq.SchemaField: - return bq.SchemaField("", "DATETIME") - - def visit_timestamp_ns( - self, timestamp_type: pyiceberg.types.TimestampNanoType - ) -> bq.SchemaField: - return bq.SchemaField("", "UNKNOWN") - - def visit_timestamptz( - self, timestamptz_type: pyiceberg.types.TimestamptzType - ) -> bq.SchemaField: - return bq.SchemaField("", "TIMESTAMP") - - def visit_timestamptz_ns( - self, timestamptz_ns_type: pyiceberg.types.TimestamptzNanoType - ) -> bq.SchemaField: - return bq.SchemaField("", "UNKNOWN") - - def visit_string(self, string_type: pyiceberg.types.StringType) -> bq.SchemaField: - return bq.SchemaField("", "STRING") - - def visit_uuid(self, uuid_type: pyiceberg.types.UUIDType) -> bq.SchemaField: - return bq.SchemaField("", "UNKNOWN") - - def visit_unknown( - self, unknown_type: pyiceberg.types.UnknownType - ) -> bq.SchemaField: - """Type `UnknownType` can be promoted to any primitive type in V3+ tables per the Iceberg spec.""" - return bq.SchemaField("", "UNKNOWN") - - def visit_binary(self, binary_type: pyiceberg.types.BinaryType) -> bq.SchemaField: - return bq.SchemaField("", "BINARY") diff --git a/bigframes/session/loader.py b/bigframes/session/loader.py deleted file mode 100644 index 43f45a500f0..00000000000 --- a/bigframes/session/loader.py +++ /dev/null @@ -1,1572 +0,0 @@ -# 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. -# 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 concurrent -import concurrent.futures -import copy -import dataclasses -import datetime -import io -import itertools -import math -import os -import threading -import typing -import warnings -from typing import ( - IO, - Dict, - Hashable, - Iterable, - Iterator, - List, - Literal, - Optional, - Sequence, - Tuple, - TypeVar, - Union, - cast, - overload, -) - -import bigframes_vendored.constants as constants -import bigframes_vendored.pandas.io.gbq as third_party_pandas_gbq -import google.api_core.exceptions -import google.cloud.bigquery -import google.cloud.bigquery as bigquery -import google.cloud.bigquery.table -import pandas -import pyarrow as pa -from google.cloud import bigquery_storage_v1 -from google.cloud.bigquery.job.load import LoadJob -from google.cloud.bigquery.job.query import QueryJob -from google.cloud.bigquery_storage_v1 import ( - types as bq_storage_types, -) -from google.cloud.bigquery_storage_v1 import ( - writer as bq_storage_writer, -) - -import bigframes._tools -import bigframes._tools.strings -import bigframes.core as core -import bigframes.core.blocks as blocks -import bigframes.core.events -import bigframes.core.schema as schemata -import bigframes.dtypes -import bigframes.exceptions as bfe -import bigframes.formatting_helpers as formatting_helpers -import bigframes.session._io.bigquery as bf_io_bigquery -import bigframes.session._io.bigquery.read_gbq_query as bf_read_gbq_query -import bigframes.session._io.bigquery.read_gbq_table as bf_read_gbq_table -import bigframes.session.iceberg -import bigframes.session.metrics -import bigframes.session.temporary_storage -import bigframes.session.time as session_time -from bigframes.core import ( - bq_data, - guid, - identifiers, - local_data, - nodes, - ordering, - utils, -) -from bigframes.session import dry_runs - -# Avoid circular imports. -if typing.TYPE_CHECKING: - import bigframes.dataframe as dataframe - import bigframes.session - -_PLACEHOLDER_SCHEMA = ( - google.cloud.bigquery.SchemaField("bf_loader_placeholder", "INTEGER"), -) - -_LOAD_JOB_TYPE_OVERRIDES = { - # Json load jobs not supported yet: b/271321143 - bigframes.dtypes.JSON_DTYPE: "STRING", - # Timedelta is emulated using integer in bq type system - bigframes.dtypes.TIMEDELTA_DTYPE: "INTEGER", -} - -_STREAM_JOB_TYPE_OVERRIDES = { - # Timedelta is emulated using integer in bq type system - bigframes.dtypes.TIMEDELTA_DTYPE: "INTEGER", -} - -TABLE_TYPE = Union[bq_data.GbqNativeTable, bq_data.BiglakeIcebergTable] - - -def _to_index_cols( - index_col: Iterable[str] | str | bigframes.enums.DefaultIndexKind = (), -) -> List[str]: - """Convert index_col into a list of column names.""" - if isinstance(index_col, bigframes.enums.DefaultIndexKind): - index_cols: List[str] = [] - elif isinstance(index_col, str): - index_cols = [index_col] - else: - index_cols = list(index_col) - - return index_cols - - -def _check_duplicates(name: str, columns: Optional[Iterable[str]] = None): - """Check for duplicate column names in the provided iterable.""" - if columns is None: - return - columns_list = list(columns) - set_columns = set(columns_list) - if len(columns_list) > len(set_columns): - raise ValueError( - f"The '{name}' argument contains duplicate names. " - f"All column names specified in '{name}' must be unique." - ) - - -def _check_index_col_param( - index_cols: Iterable[str], - columns: Iterable[str], - *, - table_columns: Optional[Iterable[str]] = None, - index_col_in_columns: Optional[bool] = False, -): - """Checks for duplicates in `index_cols` and resolves overlap with `columns`. - - Args: - index_cols (Iterable[str]): - Column names designated as the index columns. - columns (Iterable[str]): - Used column names from table_columns. - table_columns (Iterable[str]): - A full list of column names in the table schema. - index_col_in_columns (bool): - A flag indicating how to handle overlap between `index_cols` and - `columns`. - - If `False`, the two lists must be disjoint (contain no common - elements). An error is raised if any overlap is found. - - If `True`, `index_cols` is expected to be a subset of - `columns`. An error is raised if an index column is not found - in the `columns` list. - """ - _check_duplicates("index_col", index_cols) - - if columns is not None and len(list(columns)) > 0: - set_index = set(list(index_cols) if index_cols is not None else []) - set_columns = set(list(columns) if columns is not None else []) - - if index_col_in_columns: - if not set_index.issubset(set_columns): - raise ValueError( - f"The specified index column(s) were not found: {set_index - set_columns}. " - f"Available columns are: {set_columns}" - ) - else: - if not set_index.isdisjoint(set_columns): - raise ValueError( - "Found column names that exist in both 'index_col' and 'columns' arguments. " - "These arguments must specify distinct sets of columns." - ) - - if not index_col_in_columns and table_columns is not None: - for key in index_cols: - if key not in table_columns: - possibility = min( - table_columns, - key=lambda item: bigframes._tools.strings.levenshtein_distance( - key, item - ), - ) - raise ValueError( - f"Column '{key}' of `index_col` not found in this table. Did you mean '{possibility}'?" - ) - - -def _check_columns_param(columns: Iterable[str], table_columns: Iterable[str]): - """Validates that the specified columns are present in the table columns. - - Args: - columns (Iterable[str]): - Used column names from table_columns. - table_columns (Iterable[str]): - A full list of column names in the table schema. - Raises: - ValueError: If any column in `columns` is not found in the table columns. - """ - for column_name in columns: - if column_name not in table_columns: - possibility = min( - table_columns, - key=lambda item: bigframes._tools.strings.levenshtein_distance( - column_name, item - ), - ) - raise ValueError( - f"Column '{column_name}' is not found. Did you mean '{possibility}'?" - ) - - -def _check_names_param( - names: Iterable[str], - index_col: Iterable[str] - | str - | Iterable[int] - | int - | bigframes.enums.DefaultIndexKind, - columns: Iterable[str], - table_columns: Iterable[str], -): - len_names = len(list(names)) - len_table_columns = len(list(table_columns)) - len_columns = len(list(columns)) - if len_names > len_table_columns: - raise ValueError( - f"Too many columns specified: expected {len_table_columns}" - f" and found {len_names}" - ) - elif len_names < len_table_columns: - if isinstance(index_col, bigframes.enums.DefaultIndexKind) or index_col != (): - raise KeyError( - "When providing both `index_col` and `names`, ensure the " - "number of `names` matches the number of columns in your " - "data." - ) - if len_columns != 0: - # The 'columns' must be identical to the 'names'. If not, raise an error. - if len_columns != len_names: - raise ValueError( - "Number of passed names did not match number of header " - "fields in the file" - ) - if set(list(names)) != set(list(columns)): - raise ValueError("Usecols do not match columns") - - -@dataclasses.dataclass -class GbqDataLoader: - """ - Responsible for loading data into BigFrames using temporary bigquery tables. - - This loader is constrained to loading local data and queries against data sources in the same region as the storage manager. - - - Args: - session (bigframes.session.Session): - The session the data will be loaded into. Objects will not be compatible with other sessions. - bqclient (bigquery.Client): - An object providing client library objects. - storage_manager (bigframes.session.temp_storage.TemporaryGbqStorageManager): - Manages temporary storage used by the loader. - default_index_type (bigframes.enums.DefaultIndexKind): - Determines the index type created for data loaded from gcs or gbq. - scan_index_uniqueness (bool): - Whether the loader will scan index columns to determine whether the values are unique. - This behavior is useful in total ordering mode to use index column as order key. - metrics (bigframes.session.metrics.ExecutionMetrics or None): - Used to record query execution statistics. - """ - - def __init__( - self, - session: bigframes.session.Session, - bqclient: bigquery.Client, - write_client: bigquery_storage_v1.BigQueryWriteClient, - storage_manager: bigframes.session.temporary_storage.TemporaryStorageManager, - default_index_type: bigframes.enums.DefaultIndexKind, - scan_index_uniqueness: bool, - force_total_order: bool, - metrics: Optional[bigframes.session.metrics.ExecutionMetrics] = None, - *, - publisher: bigframes.core.events.Publisher, - ): - self._bqclient = bqclient - self._write_client = write_client - self._storage_manager = storage_manager - self._default_index_type = default_index_type - self._scan_index_uniqueness = scan_index_uniqueness - self._force_total_order = force_total_order - self._df_snapshot: Dict[str, Tuple[datetime.datetime, TABLE_TYPE]] = {} - self._metrics = metrics - self._publisher = publisher - # Unfortunate circular reference, but need to pass reference when constructing objects - self._session = session - self._clock = session_time.BigQuerySyncedClock(bqclient) - self._clock.sync() - self._threadpool = concurrent.futures.ThreadPoolExecutor( - max_workers=1, thread_name_prefix="bigframes-loader" - ) - - def read_data_async( - self, local_data: local_data.ManagedArrowTable, offsets_col: str - ) -> concurrent.futures.Future[bq_data.BigqueryDataSource]: - future = self._threadpool.submit( - self._load_data_or_write_data, local_data, offsets_col - ) - return future - - def read_pandas( - self, - pandas_dataframe: pandas.DataFrame, - method: Literal["load", "stream", "write"], - ) -> dataframe.DataFrame: - # TODO: Push this into from_pandas, along with index flag - from bigframes import dataframe - - val_cols, idx_cols = utils.get_standardized_ids( - pandas_dataframe.columns, pandas_dataframe.index.names, strict=True - ) - prepared_df = pandas_dataframe.reset_index(drop=False).set_axis( - [*idx_cols, *val_cols], axis="columns" - ) - managed_data = local_data.ManagedArrowTable.from_pandas(prepared_df) - block = blocks.Block( - self.read_managed_data(managed_data, method=method), - index_columns=idx_cols, - column_labels=pandas_dataframe.columns, - index_labels=pandas_dataframe.index.names, - ) - return dataframe.DataFrame(block) - - def read_managed_data( - self, - data: local_data.ManagedArrowTable, - method: Literal["load", "stream", "write"], - ) -> core.ArrayValue: - offsets_col = guid.generate_guid("upload_offsets_") - if method == "load": - gbq_source = self.load_data(data, offsets_col=offsets_col) - elif method == "stream": - gbq_source = self.stream_data(data, offsets_col=offsets_col) - elif method == "write": - gbq_source = self.write_data(data, offsets_col=offsets_col) - else: - raise ValueError(f"Unsupported read method {method}") - - return core.ArrayValue.from_bq_data_source( - source=gbq_source, - scan_list=nodes.ScanList( - tuple( - nodes.ScanItem(identifiers.ColumnId(item.column), item.column) - for item in data.schema.items - ) - ), - session=self._session, - ) - - def _load_data_or_write_data( - self, - data: local_data.ManagedArrowTable, - offsets_col: str, - ) -> bq_data.BigqueryDataSource: - """Write local data into BigQuery using the local API if possible, - otherwise use the write API.""" - can_load = all( - _is_dtype_can_load(item.column, item.dtype) for item in data.schema.items - ) - if can_load: - return self.load_data(data, offsets_col=offsets_col) - else: - return self.write_data(data, offsets_col=offsets_col) - - def load_data( - self, - data: local_data.ManagedArrowTable, - offsets_col: str, - ) -> bq_data.BigqueryDataSource: - """Load managed data into bigquery""" - cannot_load_columns = { - item.column: item.dtype - for item in data.schema.items - if not _is_dtype_can_load(item.column, item.dtype) - } - - if cannot_load_columns: - raise NotImplementedError( - f"Nested JSON types are currently unsupported for BigQuery Load API. " - f"Unsupported columns: {cannot_load_columns}. {constants.FEEDBACK_LINK}" - ) - - schema_w_offsets = data.schema.append( - schemata.SchemaItem(offsets_col, bigframes.dtypes.INT_DTYPE) - ) - bq_schema = schema_w_offsets.to_bigquery(_LOAD_JOB_TYPE_OVERRIDES) - - job_config = bigquery.LoadJobConfig() - job_config.source_format = bigquery.SourceFormat.PARQUET - - # Ensure we can load pyarrow.list_ / BQ ARRAY type. - # See internal issue 414374215. - parquet_options = bigquery.ParquetOptions() - parquet_options.enable_list_inference = True - job_config.parquet_options = parquet_options - - job_config.schema = bq_schema - - load_table_destination = self._storage_manager.create_temp_table( - bq_schema, [offsets_col] - ) - - buffer = io.BytesIO() - data.to_parquet( - buffer, - offsets_col=offsets_col, - geo_format="wkt", - duration_type="duration", - json_type="string", - ) - buffer.seek(0) - load_job = self._bqclient.load_table_from_file( - buffer, destination=load_table_destination, job_config=job_config - ) - self._start_generic_job(load_job) - # must get table metadata after load job for accurate metadata - destination_table = self._bqclient.get_table(load_table_destination) - return bq_data.BigqueryDataSource( - bq_data.GbqNativeTable.from_table(destination_table), - schema=schema_w_offsets, - ordering=ordering.TotalOrdering.from_offset_col(offsets_col), - n_rows=data.metadata.row_count, - ) - - def stream_data( - self, - data: local_data.ManagedArrowTable, - offsets_col: str, - ) -> bq_data.BigqueryDataSource: - """Load managed data into bigquery""" - MAX_BYTES = 10000000 # streaming api has 10MB limit - SAFETY_MARGIN = ( - 40 # Perf seems bad for large chunks, so do 40x smaller than max - ) - batch_count = math.ceil( - data.metadata.total_bytes / (MAX_BYTES // SAFETY_MARGIN) - ) - rows_per_batch = math.ceil(data.metadata.row_count / batch_count) - - schema_w_offsets = data.schema.append( - schemata.SchemaItem(offsets_col, bigframes.dtypes.INT_DTYPE) - ) - bq_schema = schema_w_offsets.to_bigquery(_STREAM_JOB_TYPE_OVERRIDES) - load_table_destination = self._storage_manager.create_temp_table( - bq_schema, [offsets_col] - ) - - rows = data.itertuples( - geo_format="wkt", duration_type="int", json_type="object" - ) - rows_w_offsets = ((*row, offset) for offset, row in enumerate(rows)) - - # TODO: don't use batched - batches = _batched(rows_w_offsets, rows_per_batch) - ids_iter = map(str, itertools.count()) - - for batch in batches: - batch_rows = list(batch) - row_ids = itertools.islice(ids_iter, len(batch_rows)) - - for errors in self._bqclient.insert_rows( - load_table_destination, - batch_rows, - selected_fields=bq_schema, - row_ids=row_ids, # used to ensure only-once insertion - ): - if errors: - raise ValueError( - f"Problem loading at least one row from DataFrame: {errors}. {constants.FEEDBACK_LINK}" - ) - destination_table = self._bqclient.get_table(load_table_destination) - return bq_data.BigqueryDataSource( - bq_data.GbqNativeTable.from_table(destination_table), - schema=schema_w_offsets, - ordering=ordering.TotalOrdering.from_offset_col(offsets_col), - n_rows=data.metadata.row_count, - ) - - def write_data( - self, - data: local_data.ManagedArrowTable, - offsets_col: str, - ) -> bq_data.BigqueryDataSource: - """Load managed data into BigQuery using multiple concurrent streams.""" - schema_w_offsets = data.schema.append( - schemata.SchemaItem(offsets_col, bigframes.dtypes.INT_DTYPE) - ) - bq_schema = schema_w_offsets.to_bigquery(_STREAM_JOB_TYPE_OVERRIDES) - bq_table_ref = self._storage_manager.create_temp_table(bq_schema, [offsets_col]) - parent = bq_table_ref.to_bqstorage() - - # Some light benchmarking went into the constants here, not definitive - TARGET_BATCH_BYTES = ( - 5_000_000 # Must stay under the hard 10MB limit per request - ) - rows_per_batch = math.ceil( - data.metadata.row_count * TARGET_BATCH_BYTES / data.metadata.total_bytes - ) - min_batches = math.ceil(data.metadata.row_count / rows_per_batch) - num_streams = min((os.cpu_count() or 4) * 4, min_batches) - - schema, all_batches = data.to_arrow( - offsets_col=offsets_col, - duration_type="int", - max_chunksize=rows_per_batch, - ) - serialized_schema = schema.serialize().to_pybytes() - - def stream_worker( - work: Iterator[pa.RecordBatch], max_outstanding: int = 5 - ) -> str: - requested_stream = bq_storage_types.WriteStream( - type_=bq_storage_types.WriteStream.Type.PENDING - ) - stream = self._write_client.create_write_stream( - parent=parent, write_stream=requested_stream - ) - base_request = bq_storage_types.AppendRowsRequest( - write_stream=stream.name, - ) - base_request.arrow_rows.writer_schema.serialized_schema = serialized_schema - - stream_manager = bq_storage_writer.AppendRowsStream( - client=self._write_client, initial_request_template=base_request - ) - stream_name = stream.name - current_offset = 0 - futures: list[bq_storage_writer.AppendRowsFuture] = [] - - for batch in work: - if len(futures) >= max_outstanding: - row_errors = futures.pop(0).result().row_errors - if row_errors: - raise ValueError( - f"Problem loading rows: {row_errors}. {constants.FEEDBACK_LINK}" - ) - - request = bq_storage_types.AppendRowsRequest(offset=current_offset) - request.arrow_rows.rows.serialized_record_batch = ( - batch.serialize().to_pybytes() - ) - - futures.append(stream_manager.send(request)) - current_offset += batch.num_rows - - for future in futures: - row_errors = future.result().row_errors - if row_errors: - raise ValueError( - f"Problem loading rows: {row_errors}. {constants.FEEDBACK_LINK}" - ) - - stream_manager.close() - self._write_client.finalize_write_stream(name=stream_name) - return stream_name - - shared_batches = ThreadSafeIterator(all_batches) - - stream_names = [] - with concurrent.futures.ThreadPoolExecutor(max_workers=num_streams) as executor: - futures = [] - for _ in range(num_streams): - try: - work = next(shared_batches) - except StopIteration: - break # existing workers have consume all work, don't create more workers - # Guarantee at least a single piece of work for each worker - future = executor.submit( - stream_worker, itertools.chain((work,), shared_batches) - ) - futures.append(future) - - for future in concurrent.futures.as_completed(futures): - stream_name = future.result() - stream_names.append(stream_name) - - # This makes all data from all streams visible in the table at once - commit_request = bq_storage_types.BatchCommitWriteStreamsRequest( - parent=parent, write_streams=stream_names - ) - response = self._write_client.batch_commit_write_streams(commit_request) - for error in response.stream_errors: - raise ValueError(f"Errors commiting stream {error}") - - result_table = bq_data.GbqNativeTable.from_ref_and_schema( - bq_table_ref, - schema=bq_schema, - cluster_cols=[offsets_col], - location=self._storage_manager.location, - table_type="TABLE", - ) - return bq_data.BigqueryDataSource( - result_table, - schema=schema_w_offsets, - ordering=ordering.TotalOrdering.from_offset_col(offsets_col), - n_rows=data.metadata.row_count, - ) - - def _start_generic_job(self, job: formatting_helpers.GenericJob): - if bigframes.options.display.progress_bar is not None: - formatting_helpers.wait_for_job( - job, bigframes.options.display.progress_bar - ) # Wait for the job to complete - else: - job.result() - - if self._metrics is not None and isinstance(job, (QueryJob, LoadJob)): - self._metrics.count_job_stats(query_job=job) - - @overload - def read_gbq_table( # type: ignore[overload-overlap] - self, - table_id: str, - *, - index_col: Iterable[str] - | str - | Iterable[int] - | int - | bigframes.enums.DefaultIndexKind = ..., - columns: Iterable[str] = ..., - names: Optional[Iterable[str]] = ..., - max_results: Optional[int] = ..., - use_cache: bool = ..., - filters: third_party_pandas_gbq.FiltersType = ..., - enable_snapshot: bool = ..., - dry_run: Literal[False] = ..., - force_total_order: Optional[bool] = ..., - n_rows: Optional[int] = None, - index_col_in_columns: bool = False, - publish_execution: bool = True, - ) -> dataframe.DataFrame: ... - - @overload - def read_gbq_table( - self, - table_id: str, - *, - index_col: Iterable[str] - | str - | Iterable[int] - | int - | bigframes.enums.DefaultIndexKind = ..., - columns: Iterable[str] = ..., - names: Optional[Iterable[str]] = ..., - max_results: Optional[int] = ..., - use_cache: bool = ..., - filters: third_party_pandas_gbq.FiltersType = ..., - enable_snapshot: bool = ..., - dry_run: Literal[True] = ..., - force_total_order: Optional[bool] = ..., - n_rows: Optional[int] = None, - index_col_in_columns: bool = False, - publish_execution: bool = True, - ) -> pandas.Series: ... - - def read_gbq_table( - self, - table_id: str, - *, - index_col: Iterable[str] - | str - | Iterable[int] - | int - | bigframes.enums.DefaultIndexKind = (), - columns: Iterable[str] = (), - names: Optional[Iterable[str]] = None, - max_results: Optional[int] = None, - use_cache: bool = True, - filters: third_party_pandas_gbq.FiltersType = (), - enable_snapshot: bool = True, - dry_run: bool = False, - force_total_order: Optional[bool] = None, - n_rows: Optional[int] = None, - index_col_in_columns: bool = False, - publish_execution: bool = True, - ) -> dataframe.DataFrame | pandas.Series: - """Read a BigQuery table into a BigQuery DataFrames DataFrame. - - This method allows you to create a DataFrame from a BigQuery table. - You can specify the columns to load, an index column, and apply - filters. - - Args: - table_id (str): - The identifier of the BigQuery table to read. - index_col (Iterable[str] | str | Iterable[int] | int | bigframes.enums.DefaultIndexKind, optional): - The column(s) to use as the index for the DataFrame. This can be - a single column name or a list of column names. If not provided, - a default index will be used based on the session's - ``default_index_type``. - columns (Iterable[str], optional): - The columns to read from the table. If not specified, all - columns will be read. - names (Optional[Iterable[str]], optional): - A list of column names to use for the resulting DataFrame. This - is useful if you want to rename the columns as you read the - data. - max_results (Optional[int], optional): - The maximum number of rows to retrieve from the table. If not - specified, all rows will be loaded. - use_cache (bool, optional): - Whether to use cached results for the query. Defaults to True. - Setting this to False will force a re-execution of the query. - filters (third_party_pandas_gbq.FiltersType, optional): - A list of filters to apply to the data. Filters are specified - as a list of tuples, where each tuple contains a column name, - an operator (e.g., '==', '!='), and a value. - enable_snapshot (bool, optional): - If True, a snapshot of the table is used to ensure that the - DataFrame is deterministic, even if the underlying table - changes. Defaults to True. - dry_run (bool, optional): - If True, the function will not actually execute the query but - will instead return statistics about the table. Defaults to False. - force_total_order (Optional[bool], optional): - If True, a total ordering is enforced on the DataFrame, which - can be useful for operations that require a stable row order. - If None, the session's default behavior is used. - n_rows (Optional[int], optional): - The number of rows to consider for type inference and other - metadata operations. This does not limit the number of rows - in the final DataFrame. - index_col_in_columns (bool, optional): - Specifies if the ``index_col`` is also present in the ``columns`` - list. Defaults to ``False``. - - * If ``False``, ``index_col`` and ``columns`` must specify - distinct sets of columns. An error will be raised if any - column is found in both. - * If ``True``, the column(s) in ``index_col`` are expected to - also be present in the ``columns`` list. This is useful - when the index is selected from the data columns (e.g., in a - ``read_csv`` scenario). The column will be used as the - DataFrame's index and removed from the list of value columns. - publish_execution (bool, optional): - If True, sends an execution started and stopped event if this - causes a query. Set to False if using read_gbq_table from - another function that is reporting execution. - """ - import bigframes.core.events - import bigframes.dataframe as dataframe - - # --------------------------------- - # Validate and transform parameters - # --------------------------------- - - if max_results and max_results <= 0: - raise ValueError( - f"`max_results` should be a positive number, got {max_results}." - ) - - _check_duplicates("columns", columns) - - columns = list(columns) - include_all_columns = columns is None or len(columns) == 0 - filters = typing.cast(list, list(filters)) - - # --------------------------------- - # Fetch table metadata and validate - # --------------------------------- - - time_travel_timestamp, table = self._get_table_metadata( - table_id=table_id, - default_project=self._bqclient.project, - bq_time=self._clock.get_time(), - use_cache=use_cache, - ) - - if not bq_data.is_compatible( - table.metadata.location, self._storage_manager.location - ): - raise ValueError( - f"Current session is in {self._storage_manager.location} but table '{table.get_full_id()}' is located in {table.metadata.location}" - ) - - table_column_names = [field.name for field in table.physical_schema] - rename_to_schema: Optional[Dict[str, str]] = None - if names is not None: - _check_names_param(names, index_col, columns, table_column_names) - - # Additional unnamed columns is going to set as index columns - len_names = len(list(names)) - len_schema = len(table.physical_schema) - if len(columns) == 0 and len_names < len_schema: - index_col = range(len_schema - len_names) - names = [ - field.name - for field in table.physical_schema[: len_schema - len_names] - ] + list(names) - - assert len_schema >= len_names - assert len_names >= len(columns) - - table_column_names = table_column_names[: len(list(names))] - rename_to_schema = dict(zip(list(names), table_column_names)) - - if len(columns) != 0: - if names is None: - _check_columns_param(columns, table_column_names) - else: - _check_columns_param(columns, names) - names = columns - assert rename_to_schema is not None - columns = [rename_to_schema[renamed_name] for renamed_name in columns] - - # Converting index_col into a list of column names requires - # the table metadata because we might use the primary keys - # when constructing the index. - index_cols = bf_read_gbq_table.get_index_cols( - table=table, - index_col=index_col, - rename_to_schema=rename_to_schema, - default_index_type=self._default_index_type, - ) - _check_index_col_param( - index_cols, - columns, - table_columns=table_column_names, - index_col_in_columns=index_col_in_columns, - ) - if index_col_in_columns and not include_all_columns: - set_index = set(list(index_cols) if index_cols is not None else []) - columns = [col for col in columns if col not in set_index] - - # ----------------------------- - # Optionally, execute the query - # ----------------------------- - - if ( - # max_results introduces non-determinism and limits the cost on - # clustered tables, so fallback to a query. We do this here so that - # the index is consistent with tables that have primary keys, even - # when max_results is set. - max_results is not None - # Views such as INFORMATION_SCHEMA can introduce non-determinism. - # They can update frequently and don't support time travel. - or bf_read_gbq_table.is_information_schema(table_id) - ): - # TODO(b/338111344): If we are running a query anyway, we might as - # well generate ROW_NUMBER() at the same time. - all_columns: Iterable[str] = ( - itertools.chain(index_cols, columns) if columns else () - ) - query = bf_io_bigquery.to_query( - table.get_full_id(quoted=False), - columns=all_columns, - sql_predicate=bf_io_bigquery.compile_filters(filters) - if filters - else None, - max_results=max_results, - # We're executing the query, so we don't need time travel for - # determinism. - time_travel_timestamp=None, - ) - - df = self.read_gbq_query( # type: ignore # for dry_run overload - query, - index_col=index_cols, - columns=columns, - use_cache=use_cache, - dry_run=dry_run, - # If max_results has been set, we almost certainly have < 10 GB - # of results. - allow_large_results=False, - ) - return df - - if dry_run: - return dry_runs.get_table_stats(table) - - # ----------------------------------------- - # Validate table access and features - # ----------------------------------------- - - # Use a time travel to make sure the DataFrame is deterministic, even - # if the underlying table changes. - - # If a dry run query fails with time travel but - # succeeds without it, omit the time travel clause and raise a warning - # about potential non-determinism if the underlying tables are modified. - filter_str = bf_io_bigquery.compile_filters(filters) if filters else None - all_columns = ( - () - if len(columns) == 0 - else (*columns, *[col for col in index_cols if col not in columns]) - ) - - enable_snapshot = enable_snapshot and bf_read_gbq_table.is_time_travel_eligible( - self._bqclient, - table, - all_columns, - time_travel_timestamp, - filter_str, - should_warn=True, - should_dry_run=True, - publisher=self._publisher, - ) - - # ---------------------------- - # Create ordering and validate - # ---------------------------- - - # TODO(b/337925142): Generate a new subquery with just the index_cols - # in the Ibis table expression so we don't have a "SELECT *" subquery - # in the query that checks for index uniqueness. - # TODO(b/338065601): Provide a way to assume uniqueness and avoid this - # check. - primary_key = bf_read_gbq_table.infer_unique_columns( - table=table, - index_cols=index_cols, - ) - - # If non in strict ordering mode, don't go through overhead of scanning index column(s) to determine if unique - if not primary_key and self._scan_index_uniqueness and index_cols: - if publish_execution: - self._publisher.publish( - bigframes.core.events.ExecutionStarted(), - ) - primary_key = bf_read_gbq_table.check_if_index_columns_are_unique( - self._bqclient, - table=table, - index_cols=index_cols, - publisher=self._publisher, - ) - if publish_execution: - self._publisher.publish( - bigframes.core.events.ExecutionFinished(), - ) - - selected_cols = None if include_all_columns else (*index_cols, *columns) - array_value = core.ArrayValue.from_table( - table, - columns=selected_cols, - predicate=filter_str, - at_time=time_travel_timestamp if enable_snapshot else None, - primary_key=primary_key, - session=self._session, - n_rows=n_rows, - ) - # if we don't have a unique index, we order by row hash if we are in strict mode - if ( - # If the user has explicitly selected or disabled total ordering for - # this API call, respect that choice. - (force_total_order is not None and force_total_order) - # If the user has not explicitly selected or disabled total ordering - # for this API call, respect the default choice for the session. - or (force_total_order is None and self._force_total_order) - ): - if not primary_key: - array_value = array_value.order_by( - [ - bigframes.core.ordering.OrderingExpression( - bigframes.operations.RowKey().as_expr( - *(id for id in array_value.column_ids) - ), - # More concise SQL this way. - na_last=False, - ) - ], - is_total_order=True, - ) - - # ---------------------------------------------------- - # Create Default Sequential Index if still have no index - # ---------------------------------------------------- - - # If no index columns provided or found, fall back to session default - if (index_col != bigframes.enums.DefaultIndexKind.NULL) and len( - index_cols - ) == 0: - index_col = self._default_index_type - - index_names: Sequence[Hashable] = index_cols - if index_col == bigframes.enums.DefaultIndexKind.SEQUENTIAL_INT64: - array_value, sequential_index_col = array_value.promote_offsets() - index_cols = [sequential_index_col] - index_names = [None] - - value_columns = [col for col in array_value.column_ids if col not in index_cols] - if names is not None: - assert rename_to_schema is not None - schema_to_rename = {value: key for key, value in rename_to_schema.items()} - if index_col != bigframes.enums.DefaultIndexKind.SEQUENTIAL_INT64: - index_names = [ - schema_to_rename.get(index_col, index_col) - for index_col in index_cols - ] - value_columns = [schema_to_rename.get(col, col) for col in value_columns] - - block = blocks.Block( - array_value, - index_columns=index_cols, - column_labels=value_columns, - index_labels=index_names, - ) - if max_results: - block = block.slice(stop=max_results) - df = dataframe.DataFrame(block) - - # If user provided index columns, should sort over it - if len(index_cols) > 0: - df.sort_index() - return df - - def _get_table_metadata( - self, - *, - table_id: str, - default_project: Optional[str], - bq_time: datetime.datetime, - use_cache: bool = True, - ) -> Tuple[ - datetime.datetime, Union[bq_data.GbqNativeTable, bq_data.BiglakeIcebergTable] - ]: - """Get the table metadata, either from cache or via REST API.""" - - cached_table = self._df_snapshot.get(table_id) - if use_cache and cached_table is not None: - snapshot_timestamp, table = cached_table - - if bf_read_gbq_table.is_time_travel_eligible( - bqclient=self._bqclient, - table=table, - columns=None, - snapshot_time=snapshot_timestamp, - filter_str=None, - # Don't warn, because that will already have been taken care of. - should_warn=False, - should_dry_run=False, - publisher=self._publisher, - ): - # This warning should only happen if the cached snapshot_time will - # have any effect on bigframes (b/437090788). For example, with - # cached query results, such as after re-running a query, time - # travel won't be applied and thus this check is irrelevent. - # - # In other cases, such as an explicit read_gbq_table(), Cache hit - # could be unexpected. See internal issue 329545805. Raise a - # warning with more information about how to avoid the problems - # with the cache. - msg = bfe.format_message( - f"Reading cached table from {snapshot_timestamp} 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()." - ) - # There are many layers before we get to (possibly) the user's code: - # pandas.read_gbq_table - # -> with_default_session - # -> Session.read_gbq_table - # -> _read_gbq_table - # -> _get_snapshot_sql_and_primary_key - # -> get_snapshot_datetime_and_table_metadata - warnings.warn(msg, category=bfe.TimeTravelCacheWarning, stacklevel=7) - - return cached_table - - if bf_read_gbq_table.is_information_schema(table_id): - client_table = bf_read_gbq_table.get_information_schema_metadata( - bqclient=self._bqclient, - table_id=table_id, - default_project=default_project, - ) - table = bq_data.GbqNativeTable.from_table(client_table) - elif bq_data.is_irc_table(table_id): - table = bigframes.session.iceberg.get_table( - self._bqclient.project, table_id, self._bqclient._credentials - ) - else: - table_ref = google.cloud.bigquery.table.TableReference.from_string( - table_id, default_project=default_project - ) - client_table = self._bqclient.get_table(table_ref) - table = bq_data.GbqNativeTable.from_table(client_table) - - # local time will lag a little bit do to network latency - # make sure it is at least table creation time. - # This is relevant if the table was created immediately before loading it here. - if (table.metadata.created_time is not None) and ( - table.metadata.created_time > bq_time - ): - bq_time = table.metadata.created_time - - cached_table = (bq_time, table) - self._df_snapshot[table_id] = cached_table - return cached_table - - def load_file( - self, - filepath_or_buffer: str | IO["bytes"], - *, - job_config: bigquery.LoadJobConfig, - ) -> str: - # Need to create session table beforehand - table = self._storage_manager.create_temp_table(_PLACEHOLDER_SCHEMA) - # but, we just overwrite the placeholder schema immediately with the load job - job_config.write_disposition = bigquery.WriteDisposition.WRITE_TRUNCATE - if isinstance(filepath_or_buffer, str): - filepath_or_buffer = os.path.expanduser(filepath_or_buffer) - if filepath_or_buffer.startswith("gs://"): - load_job = self._bqclient.load_table_from_uri( - filepath_or_buffer, destination=table, job_config=job_config - ) - elif os.path.exists(filepath_or_buffer): # local file path - with open(filepath_or_buffer, "rb") as source_file: - load_job = self._bqclient.load_table_from_file( - source_file, destination=table, job_config=job_config - ) - else: - raise NotImplementedError( - f"BigQuery engine only supports a local file path or GCS path. " - f"{constants.FEEDBACK_LINK}" - ) - else: - load_job = self._bqclient.load_table_from_file( - filepath_or_buffer, destination=table, job_config=job_config - ) - - self._start_generic_job(load_job) - table_id = f"{table.project}.{table.dataset_id}.{table.table_id}" - return table_id - - @overload - def read_gbq_query( # type: ignore[overload-overlap] - self, - query: str, - *, - index_col: Iterable[str] | str | bigframes.enums.DefaultIndexKind = ..., - columns: Iterable[str] = ..., - configuration: Optional[Dict] = ..., - max_results: Optional[int] = ..., - use_cache: Optional[bool] = ..., - filters: third_party_pandas_gbq.FiltersType = ..., - dry_run: Literal[False] = ..., - force_total_order: Optional[bool] = ..., - allow_large_results: bool, - ) -> dataframe.DataFrame: ... - - @overload - def read_gbq_query( - self, - query: str, - *, - index_col: Iterable[str] | str | bigframes.enums.DefaultIndexKind = ..., - columns: Iterable[str] = ..., - configuration: Optional[Dict] = ..., - max_results: Optional[int] = ..., - use_cache: Optional[bool] = ..., - filters: third_party_pandas_gbq.FiltersType = ..., - dry_run: Literal[True] = ..., - force_total_order: Optional[bool] = ..., - allow_large_results: bool, - ) -> pandas.Series: ... - - def read_gbq_query( - self, - query: str, - *, - index_col: Iterable[str] | str | bigframes.enums.DefaultIndexKind = (), - columns: Iterable[str] = (), - configuration: Optional[Dict] = None, - max_results: Optional[int] = None, - use_cache: Optional[bool] = None, - filters: third_party_pandas_gbq.FiltersType = (), - dry_run: bool = False, - force_total_order: Optional[bool] = None, - allow_large_results: bool, - ) -> dataframe.DataFrame | pandas.Series: - configuration = _transform_read_gbq_configuration(configuration) - - if "query" not in configuration: - configuration["query"] = {} - - if "query" in configuration["query"]: - raise ValueError( - "The query statement must not be included in the ", - "'configuration' because it is already provided as", - " a separate parameter.", - ) - - if "useQueryCache" in configuration["query"]: - if use_cache is not None: - raise ValueError( - "'useQueryCache' in 'configuration' conflicts with" - " 'use_cache' parameter. Please specify only one." - ) - else: - configuration["query"]["useQueryCache"] = ( - True if use_cache is None else use_cache - ) - - _check_duplicates("columns", columns) - index_cols = _to_index_cols(index_col) - _check_index_col_param(index_cols, columns) - - filters_copy1, filters_copy2 = itertools.tee(filters) - has_filters = len(list(filters_copy1)) != 0 - filters = typing.cast(third_party_pandas_gbq.FiltersType, filters_copy2) - if has_filters or max_results is not None: - # TODO(b/338111344): If we are running a query anyway, we might as - # well generate ROW_NUMBER() at the same time. - all_columns = itertools.chain(index_cols, columns) if columns else () - query = bf_io_bigquery.to_query( - query, - all_columns, - bf_io_bigquery.compile_filters(filters) if has_filters else None, - max_results=max_results, - # We're executing the query, so we don't need time travel for - # determinism. - time_travel_timestamp=None, - ) - - if dry_run: - job_config = typing.cast( - bigquery.QueryJobConfig, - bigquery.QueryJobConfig.from_api_repr(configuration), - ) - job_config.dry_run = True - query_job = self._bqclient.query(query, job_config=job_config) - if self._metrics is not None: - self._metrics.count_job_stats(query_job=query_job) - return dry_runs.get_query_stats_with_inferred_dtypes( - query_job, list(columns), index_cols - ) - - # We want to make sure we show progress when we actually do execute a - # query. Since we have got this far, we know it's not a dry run. - self._publisher.publish( - bigframes.core.events.ExecutionStarted(), - ) - - query_job_for_metrics: Optional[bigquery.QueryJob] = None - destination: Optional[bigquery.TableReference] = None - - # TODO(b/421161077): If an explicit destination table is set in - # configuration, should we respect that setting? - if allow_large_results: - destination, query_job = self._query_to_destination( - query, - # No cluster candidates as user query might not be clusterable - # (eg because of ORDER BY clause) - cluster_candidates=[], - configuration=configuration, - ) - query_job_for_metrics = query_job - rows: Optional[google.cloud.bigquery.table.RowIterator] = None - else: - job_config = typing.cast( - bigquery.QueryJobConfig, - bigquery.QueryJobConfig.from_api_repr(configuration), - ) - - # TODO(b/420984164): We may want to set a page_size here to limit - # the number of results in the first jobs.query response. - rows = self._start_query_with_job_optional( - query, - job_config=job_config, - ) - - # If there is a query job, fetch it so that we can get the - # statistics and destination table, if needed. - if rows.job_id and rows.location and rows.project: - query_job = cast( - bigquery.QueryJob, - self._bqclient.get_job( - rows.job_id, project=rows.project, location=rows.location - ), - ) - destination = query_job.destination - query_job_for_metrics = query_job - - # We split query execution from results fetching so that we can log - # metrics from either the query job, row iterator, or both. - if self._metrics is not None: - self._metrics.count_job_stats( - query_job=query_job_for_metrics, row_iterator=rows - ) - - # It's possible that there's no job and therefore no corresponding - # destination table. In this case, we must create a local node. - # - # TODO(b/420984164): Tune the threshold for which we download to - # local node. Likely there are a wide range of sizes in which it - # makes sense to download the results beyond the first page, even if - # there is a job and destination table available. - if query_job_for_metrics is None and rows is not None: - df = bf_read_gbq_query.create_dataframe_from_row_iterator( - rows, - session=self._session, - index_col=index_col, - columns=columns, - ) - self._publisher.publish( - bigframes.core.events.ExecutionFinished(), - ) - return df - - # We already checked rows, so if there's no destination table, then - # there are no results to return. - if destination is None: - df = bf_read_gbq_query.create_dataframe_from_query_job_stats( - query_job_for_metrics, - session=self._session, - ) - self._publisher.publish( - bigframes.core.events.ExecutionFinished(), - ) - return df - - # If the query was DDL or DML, return some job metadata. See - # https://cloud.google.com/bigquery/docs/reference/rest/v2/Job#JobStatistics2.FIELDS.statement_type - # for possible statement types. Note that destination table does exist - # for some DDL operations such as CREATE VIEW, but we don't want to - # read from that. See internal issue b/444282709. - if ( - query_job_for_metrics is not None - and not bf_read_gbq_query.should_return_query_results(query_job_for_metrics) - ): - df = bf_read_gbq_query.create_dataframe_from_query_job_stats( - query_job_for_metrics, - session=self._session, - ) - self._publisher.publish( - bigframes.core.events.ExecutionFinished(), - ) - return df - - # Speed up counts by getting counts from result metadata. - if rows is not None: - n_rows = rows.total_rows - elif query_job_for_metrics is not None: - n_rows = query_job_for_metrics.result().total_rows - else: - n_rows = None - - df = self.read_gbq_table( - f"{destination.project}.{destination.dataset_id}.{destination.table_id}", - index_col=index_col, - columns=columns, - use_cache=configuration["query"]["useQueryCache"], - force_total_order=force_total_order, - n_rows=n_rows, - publish_execution=False, - # max_results and filters are omitted because they are already - # handled by to_query(), above. - ) - self._publisher.publish( - bigframes.core.events.ExecutionFinished(), - ) - return df - - def _query_to_destination( - self, - query: str, - cluster_candidates: List[str], - configuration: dict = {"query": {"useQueryCache": True}}, - do_clustering=True, - ) -> Tuple[Optional[bigquery.TableReference], bigquery.QueryJob]: - # If a dry_run indicates this is not a query type job, then don't - # bother trying to do a CREATE TEMP TABLE ... AS SELECT ... statement. - dry_run_config = bigquery.QueryJobConfig() - dry_run_config.dry_run = True - dry_run_job = self._start_query_with_job( - query, - job_config=dry_run_config, - ) - if dry_run_job.statement_type != "SELECT": - query_job = self._start_query_with_job(query) - return query_job.destination, query_job - - # Create a table to workaround BigQuery 10 GB query results limit. See: - # internal issue 303057336. - # Since we have a `statement_type == 'SELECT'`, schema should be populated. - schema = dry_run_job.schema - assert schema is not None - if do_clustering: - cluster_cols = bf_io_bigquery.select_cluster_cols( - schema, cluster_candidates=cluster_candidates - ) - else: - cluster_cols = [] - temp_table = self._storage_manager.create_temp_table(schema, cluster_cols) - - timeout_ms = configuration.get("jobTimeoutMs") or configuration["query"].get( - "timeoutMs" - ) - - # Convert timeout_ms to seconds, ensuring a minimum of 0.1 seconds to avoid - # the program getting stuck on too-short timeouts. - timeout = max(int(timeout_ms) * 1e-3, 0.1) if timeout_ms else None - - job_config = typing.cast( - bigquery.QueryJobConfig, - bigquery.QueryJobConfig.from_api_repr(configuration), - ) - job_config.destination = temp_table - - try: - # Write to temp table to workaround BigQuery 10 GB query results - # limit. See: internal issue 303057336. - job_config.labels["error_caught"] = "true" - query_job = self._start_query_with_job( - query, - job_config=job_config, - timeout=timeout, - ) - return query_job.destination, query_job - except google.api_core.exceptions.BadRequest: - # Some SELECT statements still aren't compatible with cluster - # tables as the destination. For example, if the query has a - # top-level ORDER BY, this conflicts with our ability to cluster - # the table by the index column(s). - query_job = self._start_query_with_job(query, timeout=timeout) - return query_job.destination, query_job - - def _prepare_job_config( - self, - job_config: Optional[google.cloud.bigquery.QueryJobConfig] = None, - ) -> google.cloud.bigquery.QueryJobConfig: - job_config = bigquery.QueryJobConfig() if job_config is None else job_config - - if bigframes.options.compute.maximum_bytes_billed is not None: - # Maybe this should be pushed down into start_query_with_job - job_config.maximum_bytes_billed = ( - bigframes.options.compute.maximum_bytes_billed - ) - - return job_config - - def _start_query_with_job_optional( - self, - sql: str, - *, - job_config: Optional[google.cloud.bigquery.QueryJobConfig] = None, - timeout: Optional[float] = None, - ) -> google.cloud.bigquery.table.RowIterator: - """ - Starts BigQuery query with job optional and waits for results. - - Do not execute dataframe through this API, instead use the executor. - """ - job_config = self._prepare_job_config(job_config) - rows = bf_io_bigquery.start_query_job_optional( - self._bqclient, - sql, - job_config=job_config, - timeout=timeout, - location=None, - project=None, - metrics=None, - publisher=self._publisher, - session=self._session, - ) - return rows - - def _start_query_with_job( - self, - sql: str, - *, - job_config: Optional[google.cloud.bigquery.QueryJobConfig] = None, - timeout: Optional[float] = None, - ) -> bigquery.QueryJob: - """ - Starts BigQuery query job and waits for results. - - Do not execute dataframe through this API, instead use the executor. - """ - job_config = self._prepare_job_config(job_config) - _, query_job = bf_io_bigquery.start_query_with_job( - self._bqclient, - sql, - job_config=job_config, - timeout=timeout, - location=None, - project=None, - metrics=None, - publisher=self._publisher, - session=self._session, - ) - return query_job - - -def _transform_read_gbq_configuration(configuration: Optional[dict]) -> dict: - """ - For backwards-compatibility, convert any previously client-side only - parameters such as timeoutMs to the property name expected by the REST API. - - Makes a copy of configuration if changes are needed. - """ - - if configuration is None: - return {} - - timeout_ms = configuration.get("query", {}).get("timeoutMs") - if timeout_ms is not None: - # Transform timeoutMs to an actual server-side configuration. - # https://github.com/googleapis/python-bigquery-pandas/issues/479 - configuration = copy.deepcopy(configuration) - del configuration["query"]["timeoutMs"] - configuration["jobTimeoutMs"] = timeout_ms - - return configuration - - -def _is_dtype_can_load(name: str, column_type: bigframes.dtypes.Dtype) -> bool: - """ - Determines whether a datatype is supported by bq load jobs. - - Due to a BigQuery IO limitation with loading JSON from Parquet files (b/374784249), - we're using a workaround: storing JSON as strings and then parsing them into JSON - objects. - TODO(b/395912450): Remove workaround solution once b/374784249 got resolved. - """ - # we can handle top-level json, but not nested yet through string conversion - if column_type == bigframes.dtypes.JSON_DTYPE: - return True - - if isinstance( - column_type, pandas.ArrowDtype - ) and bigframes.dtypes.contains_db_dtypes_json_arrow_type( - column_type.pyarrow_dtype - ): - return False - - return True - - -# itertools.batched not available in python <3.12, so we use this instead -def _batched(iterator: Iterable, n: int) -> Iterable: - assert n > 0 - while batch := tuple(itertools.islice(iterator, n)): - yield batch - - -T = TypeVar("T") - - -class ThreadSafeIterator(Iterator[T]): - """A wrapper to make an iterator thread-safe.""" - - def __init__(self, it: Iterable[T]): - self.it = iter(it) - self.lock = threading.Lock() - - def __next__(self): - with self.lock: - return next(self.it) - - def __iter__(self): - return self diff --git a/bigframes/session/local_scan_executor.py b/bigframes/session/local_scan_executor.py deleted file mode 100644 index 22007ec5eb7..00000000000 --- a/bigframes/session/local_scan_executor.py +++ /dev/null @@ -1,65 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -from __future__ import annotations - -from typing import Optional - -from bigframes.core import bigframe_node, rewrite -from bigframes.session import execution_spec, executor, semi_executor - - -class LocalScanExecutor(semi_executor.SemiExecutor): - """ - Executes plans reducible to a arrow table scan. - """ - - async def execute( - self, - plan: bigframe_node.BigFrameNode, - execution_spec: execution_spec.ExecutionSpec, - ) -> Optional[executor.ExecuteResult]: - if execution_spec.destination_spec is not None: - return None - - reduced_result = rewrite.try_reduce_to_local_scan(plan) - if not reduced_result: - return None - - node, limit = reduced_result - peek = execution_spec.peek - if limit is not None: - if peek is None or limit < peek: - peek = limit - - # TODO: Can support some sorting - offsets_col = node.offsets_col.sql if (node.offsets_col is not None) else None - arrow_table = node.local_data_source.to_pyarrow_table(offsets_col=offsets_col) - if peek: - arrow_table = arrow_table.slice(0, peek) - - needed_cols = [item.source_id for item in node.scan_list.items] - if offsets_col is not None: - needed_cols.append(offsets_col) - - arrow_table = arrow_table.select(needed_cols) - arrow_table = arrow_table.rename_columns([id.sql for id in node.ids]) - total_rows = node.row_count - - if (peek is not None) and (total_rows is not None): - total_rows = min(peek, total_rows) - - return executor.LocalExecuteResult( - data=arrow_table, - bf_schema=plan.schema, - ) diff --git a/bigframes/session/metrics.py b/bigframes/session/metrics.py deleted file mode 100644 index a9a444ecb38..00000000000 --- a/bigframes/session/metrics.py +++ /dev/null @@ -1,408 +0,0 @@ -# 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. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from __future__ import annotations - -import dataclasses -import datetime -import os -from typing import Any, Mapping, Optional, Tuple, Union - -import google.cloud.bigquery as bigquery -import google.cloud.bigquery.table as bq_table -from google.cloud.bigquery.job.load import LoadJob -from google.cloud.bigquery.job.query import QueryJob - -LOGGING_NAME_ENV_VAR = "BIGFRAMES_PERFORMANCE_LOG_NAME" - - -@dataclasses.dataclass -class JobMetadata: - job_id: Optional[str] = None - query_id: Optional[str] = None - location: Optional[str] = None - project: Optional[str] = None - creation_time: Optional[datetime.datetime] = None - start_time: Optional[datetime.datetime] = None - end_time: Optional[datetime.datetime] = None - duration_seconds: Optional[float] = None - status: Optional[str] = None - total_bytes_processed: Optional[int] = None - total_slot_ms: Optional[int] = None - job_type: Optional[str] = None - error_result: Optional[Mapping[str, Any]] = None - cached: Optional[bool] = None - job_url: Optional[str] = None - query: Optional[str] = None - destination_table: Optional[str] = None - source_uris: Optional[list[str]] = None - input_files: Optional[int] = None - input_bytes: Optional[int] = None - output_rows: Optional[int] = None - source_format: Optional[str] = None - cell_execution_count: Optional[int] = None - - @classmethod - def from_job( - cls, - query_job: Union[QueryJob, LoadJob], - exec_seconds: Optional[float] = None, - cell_execution_count: Optional[int] = None, - ) -> "JobMetadata": - query_text = getattr(query_job, "query", None) - if query_text and len(query_text) > 1024: - query_text = query_text[:1021] + "..." - - job_id = getattr(query_job, "job_id", None) - job_url = None - if job_id: - job_url = ( - f"https://console.cloud.google.com/bigquery?" - f"project={query_job.project}&j=bq:{query_job.location}:" - f"{job_id}&page=queryresults" - ) - - if cell_execution_count is None: - from bigframes.core.utils import get_ipython_execution_count - - cell_execution_count = get_ipython_execution_count() - - metadata = cls( - job_id=query_job.job_id, - location=query_job.location, - project=query_job.project, - creation_time=query_job.created, - start_time=query_job.started, - end_time=query_job.ended, - duration_seconds=exec_seconds, - status=query_job.state, - job_type=query_job.job_type, - error_result=query_job.error_result, - query=query_text, - job_url=job_url, - cell_execution_count=cell_execution_count, - ) - if isinstance(query_job, QueryJob): - metadata.cached = getattr(query_job, "cache_hit", None) - metadata.destination_table = ( - str(query_job.destination) if query_job.destination else None - ) - metadata.total_bytes_processed = getattr( - query_job, "total_bytes_processed", None - ) - metadata.total_slot_ms = getattr(query_job, "slot_millis", None) - elif isinstance(query_job, LoadJob): - metadata.output_rows = getattr(query_job, "output_rows", None) - metadata.input_files = getattr(query_job, "input_files", None) - metadata.input_bytes = getattr(query_job, "input_bytes", None) - metadata.destination_table = ( - str(query_job.destination) - if getattr(query_job, "destination", None) - else None - ) - if getattr(query_job, "source_uris", None): - metadata.source_uris = list(query_job.source_uris) - if query_job.configuration and hasattr( - query_job.configuration, "source_format" - ): - metadata.source_format = query_job.configuration.source_format - - return metadata - - @classmethod - def from_row_iterator( - cls, - row_iterator: bq_table.RowIterator, - exec_seconds: Optional[float] = None, - cell_execution_count: Optional[int] = None, - ) -> "JobMetadata": - query_text = getattr(row_iterator, "query", None) - if query_text and len(query_text) > 1024: - query_text = query_text[:1021] + "..." - - job_id = getattr(row_iterator, "job_id", None) - job_url = None - if job_id: - project = getattr(row_iterator, "project", "") - location = getattr(row_iterator, "location", "") - job_url = ( - f"https://console.cloud.google.com/bigquery?" - f"project={project}&j=bq:{location}:{job_id}&page=queryresults" - ) - - if cell_execution_count is None: - from bigframes.core.utils import get_ipython_execution_count - - cell_execution_count = get_ipython_execution_count() - - # fmt: off - return cls( - job_id=job_id, - query_id=getattr(row_iterator, "query_id", None), - location=getattr(row_iterator, "location", None), - project=getattr(row_iterator, "project", None), - creation_time=getattr(row_iterator, "created", None), - start_time=getattr(row_iterator, "started", None), - end_time=getattr(row_iterator, "ended", None), - duration_seconds=exec_seconds, - status="DONE", - total_bytes_processed=getattr( - row_iterator, "total_bytes_processed", None - ), - total_slot_ms=getattr(row_iterator, "slot_millis", None), - job_type="query", - cached=getattr(row_iterator, "cache_hit", None), - query=query_text, - job_url=job_url, - cell_execution_count=cell_execution_count, - ) - # fmt: on - - -@dataclasses.dataclass -class ExecutionMetrics: - execution_count: int = 0 - slot_millis: int = 0 - bytes_processed: int = 0 - execution_secs: float = 0 - query_char_count: int = 0 - jobs: list[JobMetadata] = dataclasses.field(default_factory=list) - - # fmt: off - def count_job_stats( - self, - query_job: Optional[Union[QueryJob, LoadJob]] = None, - row_iterator: Optional[bq_table.RowIterator] = None, - *, - cell_execution_count: Optional[int] = None, - ): - if query_job is None: - assert row_iterator is not None - - # TODO(tswast): Pass None after making benchmark publishing robust - # to missing data. - bytes_processed = ( - getattr(row_iterator, "total_bytes_processed", 0) or 0 - ) - query_char_count = len(getattr(row_iterator, "query", "") or "") - slot_millis = getattr(row_iterator, "slot_millis", 0) or 0 - created = getattr(row_iterator, "created", None) - ended = getattr(row_iterator, "ended", None) - exec_seconds = ( - (ended - created).total_seconds() if created and ended else 0.0 - ) - - self.execution_count += 1 - self.query_char_count += query_char_count - self.bytes_processed += bytes_processed - self.slot_millis += slot_millis - self.execution_secs += exec_seconds - - self.jobs.append( - JobMetadata.from_row_iterator( - row_iterator, - exec_seconds=exec_seconds, - cell_execution_count=cell_execution_count, - ) - ) - - elif ( - isinstance(query_job, QueryJob) - and query_job.configuration.dry_run - ): - query_char_count = len(getattr(query_job, "query", "")) - - # TODO(tswast): Pass None after making benchmark publishing robust - # to missing data. - bytes_processed = 0 - slot_millis = 0 - exec_seconds = 0.0 - - elif isinstance(query_job, bigquery.QueryJob): - if (stats := get_performance_stats(query_job)) is not None: - ( - query_char_count, - bytes_processed, - slot_millis, - exec_seconds, - ) = stats - self.execution_count += 1 - self.query_char_count += query_char_count or 0 - self.bytes_processed += bytes_processed or 0 - self.slot_millis += slot_millis or 0 - self.execution_secs += exec_seconds or 0 - - metadata = JobMetadata.from_job( - query_job, - exec_seconds=exec_seconds, - cell_execution_count=cell_execution_count, - ) - self.jobs.append(metadata) - - else: - self.execution_count += 1 - duration = ( - (query_job.ended - query_job.created).total_seconds() - if query_job.ended and query_job.created - else None - ) - self.jobs.append( - JobMetadata.from_job( - query_job, - exec_seconds=duration, - cell_execution_count=cell_execution_count, - ) - ) - - # For pytest runs only, log information about the query job - # to a file in order to create a performance report. - if ( - isinstance(query_job, bigquery.QueryJob) - and not query_job.configuration.dry_run - ): - stats = get_performance_stats(query_job) - if stats: - write_stats_to_disk( - query_char_count=stats[0], - bytes_processed=stats[1], - slot_millis=stats[2], - exec_seconds=stats[3], - ) - elif row_iterator is not None: - bytes_processed = ( - getattr(row_iterator, "total_bytes_processed", 0) or 0 - ) - query_char_count = len(getattr(row_iterator, "query", "") or "") - slot_millis = getattr(row_iterator, "slot_millis", 0) or 0 - created = getattr(row_iterator, "created", None) - ended = getattr(row_iterator, "ended", None) - exec_seconds = ( - (ended - created).total_seconds() if created and ended else 0.0 - ) - write_stats_to_disk( - query_char_count=query_char_count, - bytes_processed=bytes_processed, - slot_millis=slot_millis, - exec_seconds=exec_seconds, - ) - # fmt: on - - def on_event(self, envelope: Any): - try: - import bigframes.core.events - from bigframes.session.executor import LocalExecuteResult - except ImportError: - return - - # Publisher.publish automatically wraps raw Event objects in an - # EventEnvelope, ensuring subscribers receive a consistent contract. - assert isinstance(envelope, bigframes.core.events.EventEnvelope) - event = envelope.event - cell_execution_count = envelope.cell_execution_count - - if isinstance(event, bigframes.core.events.ExecutionFinished): - if event.result and isinstance(event.result, LocalExecuteResult): - self.execution_count += 1 - bytes_processed = event.result.total_bytes_processed or 0 - self.bytes_processed += bytes_processed - - if cell_execution_count is None: - from bigframes.core.utils import get_ipython_execution_count - - cell_execution_count = get_ipython_execution_count() - - metadata = JobMetadata( - job_type="polars", - status="DONE", - total_bytes_processed=bytes_processed, - cell_execution_count=cell_execution_count, - ) - self.jobs.append(metadata) - - -def get_performance_stats( - query_job: bigquery.QueryJob, -) -> Optional[Tuple[int, int, int, float]]: - """Parse the query job for performance stats. - - Return None if the stats do not reflect real work done in bigquery. - """ - if ( - query_job.configuration.dry_run - or query_job.created is None - or query_job.ended is None - ): - return None - - bytes_processed = query_job.total_bytes_processed - if bytes_processed and not isinstance(bytes_processed, int): - return None # filter out mocks - - slot_millis = query_job.slot_millis - if slot_millis and not isinstance(slot_millis, int): - return None # filter out mocks - - execution_secs = (query_job.ended - query_job.created).total_seconds() - query_char_count = len(query_job.query) - - return ( - query_char_count, - # Not every job populates these. For example, slot_millis is missing - # from queries that came from cached results. - bytes_processed if bytes_processed else 0, - slot_millis if slot_millis else 0, - execution_secs, - ) - - -def write_stats_to_disk( - *, - query_char_count: int, - bytes_processed: int, - slot_millis: int, - exec_seconds: float, -): - """For pytest runs only, log information about the query job - to a file in order to create a performance report. - """ - if LOGGING_NAME_ENV_VAR not in os.environ: - return - - # when running notebooks via pytest nbmake and running benchmarks - test_name = os.environ[LOGGING_NAME_ENV_VAR] - current_directory = os.getcwd() - - # store slot milliseconds - slot_file = os.path.join(current_directory, test_name + ".slotmillis") - with open(slot_file, "a") as f: - f.write(str(slot_millis) + "\n") - - # store execution time seconds - exec_time_file = os.path.join( - current_directory, test_name + ".bq_exec_time_seconds" - ) - with open(exec_time_file, "a") as f: - f.write(str(exec_seconds) + "\n") - - # store length of query - query_char_count_file = os.path.join( - current_directory, test_name + ".query_char_count" - ) - with open(query_char_count_file, "a") as f: - f.write(str(query_char_count) + "\n") - - # store bytes processed - bytes_file = os.path.join(current_directory, test_name + ".bytesprocessed") - with open(bytes_file, "a") as f: - f.write(str(bytes_processed) + "\n") diff --git a/bigframes/session/planner.py b/bigframes/session/planner.py deleted file mode 100644 index 2a562abadf1..00000000000 --- a/bigframes/session/planner.py +++ /dev/null @@ -1,85 +0,0 @@ -# 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. -# 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 itertools -from typing import Sequence, Tuple - -import bigframes.core.expression as ex -import bigframes.core.identifiers as ids -import bigframes.core.nodes as nodes -import bigframes.core.pruning as predicate_pruning -import bigframes.core.tree_properties as traversals -import bigframes.dtypes - - -def session_aware_cache_plan( - root: nodes.BigFrameNode, session_forest: Sequence[nodes.BigFrameNode] -) -> Tuple[nodes.BigFrameNode, list[ids.ColumnId]]: - """ - Determines the best node to cache given a target and a list of object roots for objects in a session. - - Returns the node to cache, and optionally a clustering column. - """ - node_counts = traversals.count_nodes(session_forest) - # These node types are cheap to re-compute, so it makes more sense to cache their children. - de_cachable_types = (nodes.FilterNode, nodes.ProjectionNode, nodes.SelectionNode) - caching_target = cur_node = root - caching_target_refs = node_counts.get(caching_target, 0) - - filters: list[ - ex.Expression - ] = [] # accumulate filters into this as traverse downwards - clusterable_cols: set[ids.ColumnId] = set() - while isinstance(cur_node, de_cachable_types): - if isinstance(cur_node, nodes.FilterNode): - # Filter node doesn't define any variables, so no need to chain expressions - filters.append(cur_node.predicate) - elif isinstance(cur_node, nodes.ProjectionNode): - # Projection defines the variables that are used in the filter expressions, need to substitute variables with their scalar expressions - # that instead reference variables in the child node. - bindings = {name: expr for expr, name in cur_node.assignments} - filters = [ - i.bind_refs(bindings, allow_partial_bindings=True) for i in filters - ] - elif isinstance(cur_node, nodes.SelectionNode): - bindings = {output: input for input, output in cur_node.input_output_pairs} - filters = [i.bind_refs(bindings) for i in filters] - else: - raise ValueError(f"Unexpected de-cached node: {cur_node}") - - cur_node = cur_node.child - cur_node_refs = node_counts.get(cur_node, 0) - if cur_node_refs > caching_target_refs: - caching_target, caching_target_refs = cur_node, cur_node_refs - cluster_compatible_cols = { - field.id - for field in cur_node.fields - if bigframes.dtypes.is_clusterable(field.dtype) - } - # Cluster cols only consider the target object and not other sesssion objects - clusterable_cols = set( - itertools.chain.from_iterable( - map( - lambda f: predicate_pruning.cluster_cols_for_predicate( - f, cluster_compatible_cols - ), - filters, - ) - ) - ) - # BQ supports up to 4 cluster columns, just prioritize by alphabetical ordering - # TODO: Prioritize caching columns by estimated filter selectivity - return caching_target, sorted(list(clusterable_cols))[:4] diff --git a/bigframes/session/polars_executor.py b/bigframes/session/polars_executor.py deleted file mode 100644 index f757de130ce..00000000000 --- a/bigframes/session/polars_executor.py +++ /dev/null @@ -1,165 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -from __future__ import annotations - -import itertools -from typing import TYPE_CHECKING, Optional - -import bigframes.operations -from bigframes.core import ( - agg_expressions, - array_value, - bigframe_node, - expression, - nodes, -) -from bigframes.operations import aggregations as agg_ops -from bigframes.operations import ( - bool_ops, - comparison_ops, - date_ops, - frequency_ops, - generic_ops, - numeric_ops, - string_ops, -) -from bigframes.session import execution_spec, 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.ProjectionNode, - nodes.SliceNode, - nodes.AggregateNode, - nodes.FilterNode, - nodes.ConcatNode, - nodes.JoinNode, - nodes.InNode, - nodes.PromoteOffsetsNode, -) - -_COMPATIBLE_SCALAR_OPS = ( - bool_ops.AndOp, - bool_ops.OrOp, - bool_ops.XorOp, - comparison_ops.EqOp, - comparison_ops.EqNullsMatchOp, - comparison_ops.NeOp, - comparison_ops.LtOp, - comparison_ops.GtOp, - comparison_ops.LeOp, - comparison_ops.GeOp, - date_ops.YearOp, - date_ops.QuarterOp, - date_ops.MonthOp, - date_ops.DayOfWeekOp, - date_ops.DayOp, - date_ops.IsoYearOp, - date_ops.IsoWeekOp, - date_ops.IsoDayOp, - frequency_ops.FloorDtOp, - numeric_ops.AddOp, - numeric_ops.SubOp, - numeric_ops.MulOp, - numeric_ops.DivOp, - numeric_ops.CeilOp, - numeric_ops.FloorOp, - numeric_ops.FloorDivOp, - numeric_ops.ModOp, - generic_ops.AsTypeOp, - generic_ops.WhereOp, - generic_ops.CoalesceOp, - generic_ops.FillNaOp, - generic_ops.CaseWhenOp, - generic_ops.InvertOp, - generic_ops.IsInOp, - generic_ops.IsNullOp, - generic_ops.NotNullOp, - string_ops.StartsWithOp, - string_ops.EndsWithOp, - string_ops.StrContainsOp, - string_ops.StrContainsRegexOp, -) -_COMPATIBLE_AGG_OPS = ( - agg_ops.SizeOp, - agg_ops.SizeUnaryOp, - agg_ops.MinOp, - agg_ops.MaxOp, - agg_ops.SumOp, - agg_ops.MeanOp, - agg_ops.CountOp, - agg_ops.VarOp, - agg_ops.PopVarOp, - agg_ops.StdOp, -) - - -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, agg_expressions.Aggregation): - if type(expr.op) not in _COMPATIBLE_AGG_OPS: - return False - if isinstance(expr, expression.Expression): - if not set(map(type, _get_expr_ops(expr))).issubset(_COMPATIBLE_SCALAR_OPS): - return False - return True - - -class PolarsExecutor(semi_executor.SemiExecutor): - def __init__(self): - # This will error out if polars is not installed - from bigframes.core.compile.polars import PolarsCompiler - - self._compiler = PolarsCompiler() - - async def execute( - self, - plan: bigframe_node.BigFrameNode, - execution_spec: execution_spec.ExecutionSpec, - ) -> Optional[executor.ExecuteResult]: - if not self._can_execute(plan): - return None - if execution_spec.destination_spec is not None: - return None - try: - lazy_frame: pl.LazyFrame = self._compiler.compile( - array_value.ArrayValue(plan).node - ) - except Exception: - return None - if execution_spec.peek is not None: - lazy_frame = lazy_frame.limit(execution_spec.peek) - pl_df = await lazy_frame.collect_async() - pa_table = pl_df.to_arrow() - return executor.LocalExecuteResult( - data=pa_table, - bf_schema=plan.schema, - ) - - def _can_execute(self, plan: bigframe_node.BigFrameNode): - return all(_is_node_polars_executable(node) for node in plan.unique_nodes()) diff --git a/bigframes/session/proxy_executor.py b/bigframes/session/proxy_executor.py deleted file mode 100644 index f6c914790cb..00000000000 --- a/bigframes/session/proxy_executor.py +++ /dev/null @@ -1,188 +0,0 @@ -# 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. -# 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 uuid -import warnings -from typing import Optional - -import google.cloud.bigquery as bigquery -import google.cloud.exceptions - -import bigframes.core -import bigframes.functions._function_session as bff_session -from bigframes import exceptions as bfe -from bigframes.session import ( - bq_caching_executor, - execution_cache, - execution_spec, - executor, - loader, - temporary_storage, -) - -_COMPILER_LABEL_KEY = "bigframes-compiler" - - -class DualCompilerProxyExecutor(executor.Executor): - """ - Used to rollout new compiler implementation. - """ - - def __init__( - self, - bqclient: bigquery.Client, - storage_manager: temporary_storage.TemporaryStorageManager, - bqstoragereadclient: google.cloud.bigquery_storage_v1.BigQueryReadClient, - loader: loader.GbqDataLoader, - *, - metrics: Optional[bigframes.session.metrics.ExecutionMetrics] = None, - enable_polars_execution: bool = False, - publisher: bigframes.core.events.Publisher, - function_manager: bff_session.FunctionSession, - labels: tuple[tuple[str, str], ...] = (), - ): - self._enable_polars_execution = enable_polars_execution - shared_cache = execution_cache.ExecutionCache() - self._ibis_executor = bq_caching_executor.BigQueryCachingExecutor( - bqclient, - storage_manager, - bqstoragereadclient, - loader, - metrics=metrics, - enable_polars_execution=self._enable_polars_execution, - publisher=publisher, - labels=labels, - cache=shared_cache, - compiler_name="ibis", - function_manager=function_manager, - ) - self._sqlglot_executor = bq_caching_executor.BigQueryCachingExecutor( - bqclient, - storage_manager, - bqstoragereadclient, - loader, - metrics=metrics, - enable_polars_execution=self._enable_polars_execution, - publisher=publisher, - labels=labels, - cache=shared_cache, - compiler_name="sqlglot", - function_manager=function_manager, - ) - - def to_sql( - self, - array_value: bigframes.core.ArrayValue, - offset_column: Optional[str] = None, - ordered: bool = False, - enable_cache: bool = True, - ) -> str: - """ - Convert an ArrayValue to a sql query that will yield its value. - """ - compiler_option = bigframes.options.experiments.sql_compiler - # Use ibis unless sqlglot explicitly selected, since we can't handle errors resulting - # from use of the sql produced by this method. - if compiler_option == "experimental": - return self._sqlglot_executor.to_sql( - array_value, - offset_column=offset_column, - ordered=ordered, - enable_cache=enable_cache, - ) - # stable or legacy use ibis - # TODO(b/510408650): Use sqlglot by default. - return self._ibis_executor.to_sql( - array_value, - offset_column=offset_column, - ordered=ordered, - enable_cache=enable_cache, - ) - - def execute( - self, - array_value: bigframes.core.ArrayValue, - execution_spec: execution_spec.ExecutionSpec, - ) -> executor.ExecuteResult: - compiler_option = bigframes.options.experiments.sql_compiler - if compiler_option == "legacy": - return self._ibis_executor.execute( - array_value, - execution_spec.with_bq_labels({_COMPILER_LABEL_KEY: "ibis"}), - ) - elif compiler_option == "experimental": - return self._sqlglot_executor.execute( - array_value, - execution_spec.with_bq_labels({_COMPILER_LABEL_KEY: "sqlglot"}), - ) - else: # stable - correlation_id = f"{uuid.uuid1().hex[:12]}" - try: - return self._sqlglot_executor.execute( - array_value, - execution_spec.with_bq_labels( - {_COMPILER_LABEL_KEY: f"sqlglot-{correlation_id}"} - ), - ) - except Exception as e: - msg = bfe.format_message( - f"Compiler ID {correlation_id}: Exception on sqlglot. " - f"Falling back to ibis. Details: {e}" - ) - warnings.warn(msg, category=UserWarning) - return self._ibis_executor.execute( - array_value, - execution_spec.with_bq_labels( - {_COMPILER_LABEL_KEY: f"ibis-{correlation_id}"} - ), - ) - - def dry_run( - self, array_value: bigframes.core.ArrayValue, ordered: bool = True - ) -> bigquery.QueryJob: - """ - Dry run executing the ArrayValue. - - Does not actually execute the data but will get stats and indicate any invalid query errors. - """ - # TODO(b/510408650): Use sqlglot for dry runs when sqlglot has been validated. - return self._ibis_executor.dry_run(array_value, ordered=ordered) - - def cached( - self, - array_value: bigframes.core.ArrayValue, - *, - config: executor.CacheConfig, - ) -> None: - compiler_option = bigframes.options.experiments.sql_compiler - if compiler_option == "legacy": - return self._ibis_executor.cached(array_value, config=config) - elif compiler_option == "experimental": - return self._sqlglot_executor.cached(array_value, config=config) - else: # stable - correlation_id = f"{uuid.uuid1().hex[:12]}" - try: - return self._sqlglot_executor.cached(array_value, config=config) - except Exception as e: - msg = bfe.format_message( - f"Compiler ID {correlation_id}: Exception on sqlglot. " - f"Falling back to ibis. Details: {e}" - ) - warnings.warn(msg, category=UserWarning) - return self._ibis_executor.cached( - array_value, - config=config, - ) diff --git a/bigframes/session/read_api_execution.py b/bigframes/session/read_api_execution.py deleted file mode 100644 index fff8022e40a..00000000000 --- a/bigframes/session/read_api_execution.py +++ /dev/null @@ -1,93 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -from __future__ import annotations - -from typing import Optional - -from google.cloud import bigquery_storage_v1 - -from bigframes.core import bigframe_node, bq_data, nodes, rewrite -from bigframes.session import execution_spec, executor, semi_executor - - -class ReadApiSemiExecutor(semi_executor.SemiExecutor): - """ - Executes plans reducible to a bq table scan by directly reading the table with the read api. - """ - - def __init__( - self, - bqstoragereadclient: bigquery_storage_v1.BigQueryReadClient, - project: str, - ): - self.bqstoragereadclient = bqstoragereadclient - self.project = project - - async def execute( - self, - plan: bigframe_node.BigFrameNode, - execution_spec: execution_spec.ExecutionSpec, - ) -> Optional[executor.ExecuteResult]: - if execution_spec.destination_spec is not None: - return None - - adapt_result = self._try_adapt_plan(plan, execution_spec.ordered) - if not adapt_result: - return None - node, limit = adapt_result - if node.explicitly_ordered and execution_spec.ordered: - return None - - if not isinstance(node.source.table, bq_data.GbqNativeTable): - return None - - if not node.source.table.is_physically_stored: - return None - - peek = execution_spec.peek - if limit is not None: - if peek is None or limit < peek: - peek = limit - - return executor.BQTableExecuteResult( - data=node.source, - project_id=self.project, - storage_client=self.bqstoragereadclient, - limit=peek, - selected_fields=[ - (item.source_id, item.id.sql) for item in node.scan_list.items - ], - ) - - def _try_adapt_plan( - self, - plan: bigframe_node.BigFrameNode, - ordered: bool, - ) -> Optional[tuple[nodes.ReadTableNode, Optional[int]]]: - """ - Tries to simplify the plan to an equivalent single ReadTableNode and a limit. Otherwise, returns None. - """ - plan, limit = rewrite.pull_out_limit(plan) - # bake_order does not allow slice ops - plan = plan.bottom_up(rewrite.rewrite_slice) - if not ordered: - # gets rid of order_by ops - plan = rewrite.bake_order(plan) - read_table_node = rewrite.try_reduce_to_table_scan(plan) - if read_table_node is None: - return None - if (limit is not None) and (read_table_node.source.ordering is not None): - # read api can only use physical ordering to limit, not a logical ordering - return None - return (read_table_node, limit) diff --git a/bigframes/session/semi_executor.py b/bigframes/session/semi_executor.py deleted file mode 100644 index 1f827ce9d93..00000000000 --- a/bigframes/session/semi_executor.py +++ /dev/null @@ -1,32 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -import abc -from typing import Optional - -from bigframes.core import bigframe_node -from bigframes.session import execution_spec, executor - - -# Unstable interface, in development -class SemiExecutor(abc.ABC): - """ - A semi executor executes a subset of possible plans, returns None for unsupported plans. - """ - - async def execute( - self, - plan: bigframe_node.BigFrameNode, - execution_spec: execution_spec.ExecutionSpec, - ) -> Optional[executor.ExecuteResult]: - raise NotImplementedError("execute not implemented for this executor") diff --git a/bigframes/session/temporary_storage.py b/bigframes/session/temporary_storage.py deleted file mode 100644 index 42617c8f6c1..00000000000 --- a/bigframes/session/temporary_storage.py +++ /dev/null @@ -1,29 +0,0 @@ -# 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. -# 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 typing import Protocol, Sequence - -from google.cloud import bigquery - - -class TemporaryStorageManager(Protocol): - @property - def location(self) -> str: ... - - def create_temp_table( - self, schema: Sequence[bigquery.SchemaField], cluster_cols: Sequence[str] = [] - ) -> bigquery.TableReference: ... - - # implementations should be robust to repeatedly closing - def close(self) -> None: ... diff --git a/bigframes/session/time.py b/bigframes/session/time.py deleted file mode 100644 index 1452b2952dc..00000000000 --- a/bigframes/session/time.py +++ /dev/null @@ -1,59 +0,0 @@ -# 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. -# 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 datetime -import threading -import time -from typing import Optional, cast - -import google.cloud.bigquery as bigquery - -MIN_RESYNC_SECONDS = 100 - - -class BigQuerySyncedClock: - """ - Local clock that attempts to synchronize its time with the bigquery service. - """ - - def __init__(self, bqclient: bigquery.Client): - self._bqclient = bqclient - self._sync_lock = threading.Lock() - self._sync_remote_time: Optional[datetime.datetime] = None - self._sync_monotonic_time: Optional[float] = None - - def get_time(self): - if (self._sync_monotonic_time is None) or (self._sync_remote_time is None): - self.sync() - assert self._sync_remote_time is not None - assert self._sync_monotonic_time is not None - return self._sync_remote_time + datetime.timedelta( - seconds=time.monotonic() - self._sync_monotonic_time - ) - - def sync(self): - with self._sync_lock: - if (self._sync_monotonic_time is not None) and ( - time.monotonic() - self._sync_monotonic_time - ) < MIN_RESYNC_SECONDS: - return - current_bq_time = list( - next( - self._bqclient.query_and_wait( - "SELECT CURRENT_TIMESTAMP() AS `current_timestamp`", - ) - ) - )[0] - self._sync_remote_time = cast(datetime.datetime, current_bq_time) - self._sync_monotonic_time = time.monotonic() diff --git a/bigframes/session/validation.py b/bigframes/session/validation.py deleted file mode 100644 index f2e68818442..00000000000 --- a/bigframes/session/validation.py +++ /dev/null @@ -1,38 +0,0 @@ -# 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. -# 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_vendored.constants - - -def validate_engine_compatibility(engine, write_engine): - """Raises NotImplementedError if engine is not compatible with write_engine.""" - - if engine == "bigquery" and write_engine in ( - "bigquery_inline", - "bigquery_streaming", - ): - raise NotImplementedError( - bigframes_vendored.constants.WRITE_ENGINE_REQUIRES_LOCAL_ENGINE_TEMPLATE.format( - engine=repr(engine), - write_engine=repr(write_engine), - ) - ) - - if engine != "bigquery" and write_engine in ("bigquery_external_table",): - raise NotImplementedError( - bigframes_vendored.constants.WRITE_ENGINE_REQUIRES_BIGQUERY_ENGINE_TEMPLATE.format( - engine=repr(engine), - write_engine=repr(write_engine), - ) - ) diff --git a/bigframes/streaming/__init__.py b/bigframes/streaming/__init__.py deleted file mode 100644 index 49687090fe6..00000000000 --- a/bigframes/streaming/__init__.py +++ /dev/null @@ -1,47 +0,0 @@ -# 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. -# 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 inspect -import sys - -import bigframes.core.global_session as global_session -import bigframes.session -import bigframes.streaming.dataframe as streaming_dataframe -from bigframes.core.logging import log_adapter -from bigframes.pandas.io.api import _set_default_session_location_if_possible - - -def read_gbq_table(table: str) -> streaming_dataframe.StreamingDataFrame: - _set_default_session_location_if_possible(table) - return global_session.with_default_session( - bigframes.session.Session.read_gbq_table_streaming, table - ) - - -read_gbq_table.__doc__ = inspect.getdoc( - bigframes.session.Session.read_gbq_table_streaming -) - -StreamingDataFrame = streaming_dataframe.StreamingDataFrame - -_module = sys.modules[__name__] -_functions = [read_gbq_table] - -for _function in _functions: - _decorated_object = log_adapter.method_logger(_function, custom_base_name="pandas") - setattr(_module, _function.__name__, _decorated_object) - -__all__ = ["read_gbq_table", "StreamingDataFrame"] diff --git a/bigframes/streaming/dataframe.py b/bigframes/streaming/dataframe.py deleted file mode 100644 index 98d6da45399..00000000000 --- a/bigframes/streaming/dataframe.py +++ /dev/null @@ -1,574 +0,0 @@ -# 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. -# 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. - -"""Module for bigquery continuous queries""" - -from __future__ import annotations - -import functools -import inspect -import json -import warnings -from abc import abstractmethod -from datetime import date, datetime -from typing import Optional, Union - -import pandas as pd -from google.cloud import bigquery - -import bigframes.exceptions as bfe -import bigframes.session -from bigframes import dataframe -from bigframes.core import nodes -from bigframes.core.logging import log_adapter - - -def _return_type_wrapper(method, cls): - @functools.wraps(method) - def wrapper(*args, **kwargs): - return_value = method(*args, **kwargs) - if isinstance(return_value, dataframe.DataFrame): - return cls._from_table_df(return_value) - return return_value - - return wrapper - - -def _curate_df_doc(doc: Optional[str]): - if not doc: - return doc - - # Remove examples, some are not applicable to StreamingDataFrame - doc = doc[: doc.find("**Examples:**")] + doc[doc.find("Args:") :] - - doc = doc.replace("dataframe.DataFrame", "streaming.StreamingDataFrame") - doc = doc.replace(" DataFrame", " StreamingDataFrame") - - return doc - - -class StreamingBase: - _session: bigframes.session.Session - - @abstractmethod - def _appends_sql( - self, start_timestamp: Optional[Union[int, float, str, datetime, date]] - ) -> str: - pass - - def to_bigtable( - self, - *, - instance: str, - table: str, - service_account_email: Optional[str] = None, - app_profile: Optional[str] = None, - truncate: bool = False, - overwrite: bool = False, - auto_create_column_families: bool = False, - bigtable_options: Optional[dict] = None, - job_id: Optional[str] = None, - job_id_prefix: Optional[str] = None, - start_timestamp: Optional[Union[int, float, str, datetime, date]] = None, - end_timestamp: Optional[Union[int, float, str, datetime, date]] = None, - ) -> bigquery.QueryJob: - """ - Export the StreamingDataFrame as a continue job and returns a - QueryJob object for some management functionality. - - This method requires an existing bigtable preconfigured to - accept the continuous query export statement. For instructions - on export to bigtable, see - https://cloud.google.com/bigquery/docs/export-to-bigtable. - - Args: - instance (str): - The name of the bigtable instance to export to. - table (str): - The name of the bigtable table to export to. - service_account_email (str): - Full name of the service account to run the continuous query. - Example: accountname@projectname.gserviceaccounts.com - If not provided, the user account will be used, but this - limits the lifetime of the continuous query. - app_profile (str, default None): - The bigtable app profile to export to. If None, no app - profile will be used. - truncate (bool, default False): - The export truncate option, see - https://cloud.google.com/bigquery/docs/reference/standard-sql/other-statements#bigtable_export_option - overwrite (bool, default False): - The export overwrite option, see - https://cloud.google.com/bigquery/docs/reference/standard-sql/other-statements#bigtable_export_option - auto_create_column_families (bool, default False): - The auto_create_column_families option, see - https://cloud.google.com/bigquery/docs/reference/standard-sql/other-statements#bigtable_export_option - bigtable_options (dict, default None): - The bigtable options dict, which will be converted to JSON - using json.dumps, see - https://cloud.google.com/bigquery/docs/reference/standard-sql/other-statements#bigtable_export_option - If None, no bigtable_options parameter will be passed. - job_id (str, default None): - If specified, replace the default job id for the query, - see job_id parameter of - https://cloud.google.com/python/docs/reference/bigquery/latest/google.cloud.bigquery.client.Client#google_cloud_bigquery_client_Client_query - job_id_prefix (str, default None): - If specified, a job id prefix for the query, see - job_id_prefix parameter of - https://cloud.google.com/python/docs/reference/bigquery/latest/google.cloud.bigquery.client.Client#google_cloud_bigquery_client_Client_query - start_timestamp (int, float, str, datetime, date, default None): - The starting timestamp for the query. Possible values are to 7 days in the past. If don't specify a timestamp (None), the query will default to the earliest possible time, 7 days ago. If provide a time-zone-naive timestamp, it will be treated as UTC. - Returns: - google.cloud.bigquery.QueryJob: - See https://cloud.google.com/python/docs/reference/bigquery/latest/google.cloud.bigquery.job.QueryJob - The ongoing query job can be managed using this object. - For example, the job can be cancelled or its error status - can be examined. - """ - if not isinstance( - start_timestamp, (int, float, str, datetime, date, type(None)) - ): - raise ValueError( - f"Unsupported start_timestamp type {type(start_timestamp)}" - ) - - return _to_bigtable( - self._appends_sql(start_timestamp), - instance=instance, - table=table, - service_account_email=service_account_email, - session=self._session, - app_profile=app_profile, - truncate=truncate, - overwrite=overwrite, - auto_create_column_families=auto_create_column_families, - bigtable_options=bigtable_options, - job_id=job_id, - job_id_prefix=job_id_prefix, - ) - - def to_pubsub( - self, - *, - topic: str, - service_account_email: str, - job_id: Optional[str] = None, - job_id_prefix: Optional[str] = None, - start_timestamp: Optional[Union[int, float, str, datetime, date]] = None, - ) -> bigquery.QueryJob: - """ - Export the StreamingDataFrame as a continue job and returns a - QueryJob object for some management functionality. - - This method requires an existing pubsub topic. For instructions - on creating a pubsub topic, see - https://cloud.google.com/pubsub/docs/samples/pubsub-quickstart-create-topic?hl=en - - Note that a service account is a requirement for continuous queries - exporting to pubsub. - - Args: - topic (str): - The name of the pubsub topic to export to. - For example: "taxi-rides" - service_account_email (str): - Full name of the service account to run the continuous query. - Example: accountname@projectname.gserviceaccounts.com - job_id (str, default None): - If specified, replace the default job id for the query, - see job_id parameter of - https://cloud.google.com/python/docs/reference/bigquery/latest/google.cloud.bigquery.client.Client#google_cloud_bigquery_client_Client_query - job_id_prefix (str, default None): - If specified, a job id prefix for the query, see - job_id_prefix parameter of - https://cloud.google.com/python/docs/reference/bigquery/latest/google.cloud.bigquery.client.Client#google_cloud_bigquery_client_Client_query - start_timestamp (int, float, str, datetime, date, default None): - The starting timestamp for the query. Possible values are to 7 days in the past. If don't specify a timestamp (None), the query will default to the earliest possible time, 7 days ago. If provide a time-zone-naive timestamp, it will be treated as UTC. - - Returns: - google.cloud.bigquery.QueryJob: - See https://cloud.google.com/python/docs/reference/bigquery/latest/google.cloud.bigquery.job.QueryJob - The ongoing query job can be managed using this object. - For example, the job can be cancelled or its error status - can be examined. - """ - if not isinstance( - start_timestamp, (int, float, str, datetime, date, type(None)) - ): - raise ValueError( - f"Unsupported start_timestamp type {type(start_timestamp)}" - ) - - return _to_pubsub( - self._appends_sql(start_timestamp), - topic=topic, - service_account_email=service_account_email, - session=self._session, - job_id=job_id, - job_id_prefix=job_id_prefix, - ) - - -@log_adapter.class_logger -class StreamingDataFrame(StreamingBase): - __doc__ = ( - _curate_df_doc(dataframe.DataFrame.__doc__) - + """ - .. note:: - - The bigframes.streaming module is a preview feature, and subject to change. - - Currently only supports basic projection, filtering and preview operations. - """ - ) - - # Private constructor - _create_key = object() - - def __init__(self, df: dataframe.DataFrame, *, create_key=0): - if create_key is not StreamingDataFrame._create_key: - raise ValueError( - "StreamingDataFrame class shouldn't be created through constructor. Call bigframes.pandas.read_gbq_table_streaming method to create." - ) - self._df = df - self._df._disable_cache_override = True - - @classmethod - def _from_table_df(cls, df: dataframe.DataFrame) -> StreamingDataFrame: - return cls(df, create_key=cls._create_key) - - @property - def _original_table(self): - def traverse(node: nodes.BigFrameNode): - if isinstance(node, nodes.ReadTableNode): - return node.source.table.get_full_id(quoted=False) - for child in node.child_nodes: - original_table = traverse(child) - if original_table: - return original_table - return None - - return traverse(self._df._block._expr.node) - - def __getitem__(self, *args, **kwargs): - return _return_type_wrapper(self._df.__getitem__, StreamingDataFrame)( - *args, **kwargs - ) - - __getitem__.__doc__ = _curate_df_doc( - inspect.getdoc(dataframe.DataFrame.__getitem__) - ) - - def __setitem__(self, *args, **kwargs): - return _return_type_wrapper(self._df.__setitem__, StreamingDataFrame)( - *args, **kwargs - ) - - __setitem__.__doc__ = _curate_df_doc( - inspect.getdoc(dataframe.DataFrame.__setitem__) - ) - - def rename(self, *args, **kwargs): - return _return_type_wrapper(self._df.rename, StreamingDataFrame)( - *args, **kwargs - ) - - rename.__doc__ = _curate_df_doc(inspect.getdoc(dataframe.DataFrame.rename)) - - def __repr__(self, *args, **kwargs): - return _return_type_wrapper(self._df.__repr__, StreamingDataFrame)( - *args, **kwargs - ) - - __repr__.__doc__ = _curate_df_doc(inspect.getdoc(dataframe.DataFrame.__repr__)) - - def _repr_mimebundle_(self, *args, **kwargs): - return _return_type_wrapper(self._df._repr_mimebundle_, StreamingDataFrame)( - *args, **kwargs - ) - - _repr_mimebundle_.__doc__ = _curate_df_doc( - inspect.getdoc(dataframe.DataFrame._repr_mimebundle_) - ) - - @property - def sql(self): - sql_str, _, _ = self._df._to_sql_query(include_index=False, enable_cache=False) - return sql_str - - sql.__doc__ = _curate_df_doc(inspect.getdoc(dataframe.DataFrame.sql)) - - # Patch for the required APPENDS clause - def _appends_sql( - self, start_timestamp: Optional[Union[int, float, str, datetime, date]] - ) -> str: - sql_str = self.sql - original_table = self._original_table - assert original_table is not None - - # TODO(b/405691193): set start time back to NULL. Now set it slightly after 7 days max interval to avoid the bug. - start_ts_str = ( - str(f"TIMESTAMP('{pd.to_datetime(start_timestamp)}')") - if start_timestamp - else "CURRENT_TIMESTAMP() - (INTERVAL 7 DAY - INTERVAL 5 MINUTE)" - ) - - appends_clause = f"APPENDS(TABLE `{original_table}`, {start_ts_str})" - sql_str = sql_str.replace(f"`{original_table}`", appends_clause) - return sql_str - - @property - def _session(self): - return self._df._session - - _session.__doc__ = _curate_df_doc(inspect.getdoc(dataframe.DataFrame._session)) - - -def _to_bigtable( - query: str, - *, - instance: str, - table: str, - service_account_email: Optional[str] = None, - session: Optional[bigframes.session.Session] = None, - app_profile: Optional[str] = None, - truncate: bool = False, - overwrite: bool = False, - auto_create_column_families: bool = False, - bigtable_options: Optional[dict] = None, - job_id: Optional[str] = None, - job_id_prefix: Optional[str] = None, -) -> bigquery.QueryJob: - """Launches a BigQuery continuous query and returns a - QueryJob object for some management functionality. - - This method requires an existing bigtable preconfigured to - accept the continuous query export statement. For instructions - on export to bigtable, see - https://cloud.google.com/bigquery/docs/export-to-bigtable. - - Args: - query (str): - The sql statement to execute as a continuous function. - For example: "SELECT * FROM dataset.table" - This will be wrapped in an EXPORT DATA statement to - launch a continuous query writing to bigtable. - instance (str): - The name of the bigtable instance to export to. - table (str): - The name of the bigtable table to export to. - service_account_email (str): - Full name of the service account to run the continuous query. - Example: accountname@projectname.gserviceaccounts.com - If not provided, the user account will be used, but this - limits the lifetime of the continuous query. - session (bigframes.session.Session, default None): - The session object to use for the query. This determines - the project id and location of the query. If None, will - default to the bigframes global session. - app_profile (str, default None): - The bigtable app profile to export to. If None, no app - profile will be used. - truncate (bool, default False): - The export truncate option, see - https://cloud.google.com/bigquery/docs/reference/standard-sql/other-statements#bigtable_export_option - overwrite (bool, default False): - The export overwrite option, see - https://cloud.google.com/bigquery/docs/reference/standard-sql/other-statements#bigtable_export_option - auto_create_column_families (bool, default False): - The auto_create_column_families option, see - https://cloud.google.com/bigquery/docs/reference/standard-sql/other-statements#bigtable_export_option - bigtable_options (dict, default None): - The bigtable options dict, which will be converted to JSON - using json.dumps, see - https://cloud.google.com/bigquery/docs/reference/standard-sql/other-statements#bigtable_export_option - If None, no bigtable_options parameter will be passed. - job_id (str, default None): - If specified, replace the default job id for the query, - see job_id parameter of - https://cloud.google.com/python/docs/reference/bigquery/latest/google.cloud.bigquery.client.Client#google_cloud_bigquery_client_Client_query - job_id_prefix (str, default None): - If specified, a job id prefix for the query, see - job_id_prefix parameter of - https://cloud.google.com/python/docs/reference/bigquery/latest/google.cloud.bigquery.client.Client#google_cloud_bigquery_client_Client_query - - Returns: - google.cloud.bigquery.QueryJob: - See https://cloud.google.com/python/docs/reference/bigquery/latest/google.cloud.bigquery.job.QueryJob - The ongoing query job can be managed using this object. - For example, the job can be cancelled or its error status - can be examined. - """ - msg = bfe.format_message( - "The bigframes.streaming module is a preview feature, and subject to change." - ) - warnings.warn(msg, stacklevel=1, category=bfe.PreviewWarning) - - # get default client if not passed - if session is None: - session = bigframes.get_global_session() - bq_client = session.bqclient - - # build export string from parameters - project = bq_client.project - - app_profile_url_string = "" - if app_profile is not None: - app_profile_url_string = f"appProfiles/{app_profile}/" - - bigtable_options_parameter_string = "" - if bigtable_options is not None: - bigtable_options_parameter_string = ( - 'bigtable_options = """' + json.dumps(bigtable_options) + '""",\n' - ) - - sql = ( - "EXPORT DATA\n" - "OPTIONS (\n" - "format = 'CLOUD_BIGTABLE',\n" - f"{bigtable_options_parameter_string}" - f"truncate = {str(truncate)},\n" - f"overwrite = {str(overwrite)},\n" - f"auto_create_column_families = {str(auto_create_column_families)},\n" - f'uri = "https://bigtable.googleapis.com/projects/{project}/instances/{instance}/{app_profile_url_string}tables/{table}"\n' - ")\n" - "AS (\n" - f"{query});" - ) - - # override continuous http parameter - job_config = bigquery.job.QueryJobConfig() - - job_config_dict: dict = {"query": {"continuous": True}} - if service_account_email is not None: - job_config_dict["query"]["connectionProperties"] = { - "key": "service_account", - "value": service_account_email, - } - job_config_filled = job_config.from_api_repr(job_config_dict) - job_config_filled.labels = {"bigframes-api": "streaming_to_bigtable"} - - # begin the query job - query_job = bq_client.query( - sql, - job_config=job_config_filled, # type:ignore - # typing error above is in bq client library - # (should accept abstract job_config, only takes concrete) - job_id=job_id, - job_id_prefix=job_id_prefix, - ) - - # return the query job to the user for lifetime management - return query_job - - -def _to_pubsub( - query: str, - *, - topic: str, - service_account_email: str, - session: Optional[bigframes.session.Session] = None, - job_id: Optional[str] = None, - job_id_prefix: Optional[str] = None, -) -> bigquery.QueryJob: - """Launches a BigQuery continuous query and returns a - QueryJob object for some management functionality. - - This method requires an existing pubsub topic. For instructions - on creating a pubsub topic, see - https://cloud.google.com/pubsub/docs/samples/pubsub-quickstart-create-topic?hl=en - - Note that a service account is a requirement for continuous queries - exporting to pubsub. - - Args: - query (str): - The sql statement to execute as a continuous function. - For example: "SELECT * FROM dataset.table" - This will be wrapped in an EXPORT DATA statement to - launch a continuous query writing to pubsub. - topic (str): - The name of the pubsub topic to export to. - For example: "taxi-rides" - service_account_email (str): - Full name of the service account to run the continuous query. - Example: accountname@projectname.gserviceaccounts.com - session (bigframes.session.Session, default None): - The session object to use for the query. This determines - the project id and location of the query. If None, will - default to the bigframes global session. - job_id (str, default None): - If specified, replace the default job id for the query, - see job_id parameter of - https://cloud.google.com/python/docs/reference/bigquery/latest/google.cloud.bigquery.client.Client#google_cloud_bigquery_client_Client_query - job_id_prefix (str, default None): - If specified, a job id prefix for the query, see - job_id_prefix parameter of - https://cloud.google.com/python/docs/reference/bigquery/latest/google.cloud.bigquery.client.Client#google_cloud_bigquery_client_Client_query - - Returns: - google.cloud.bigquery.QueryJob: - See https://cloud.google.com/python/docs/reference/bigquery/latest/google.cloud.bigquery.job.QueryJob - The ongoing query job can be managed using this object. - For example, the job can be cancelled or its error status - can be examined. - """ - msg = bfe.format_message( - "The bigframes.streaming module is a preview feature, and subject to change." - ) - warnings.warn(msg, stacklevel=1, category=bfe.PreviewWarning) - - # get default client if not passed - if session is None: - session = bigframes.get_global_session() - bq_client = session.bqclient - - # build export string from parameters - sql = ( - "EXPORT DATA\n" - "OPTIONS (\n" - "format = 'CLOUD_PUBSUB',\n" - f'uri = "https://pubsub.googleapis.com/projects/{bq_client.project}/topics/{topic}"\n' - ")\n" - "AS (\n" - f"{query});" - ) - - # override continuous http parameter - job_config = bigquery.job.QueryJobConfig() - job_config_filled = job_config.from_api_repr( - { - "query": { - "continuous": True, - "connectionProperties": { - "key": "service_account", - "value": service_account_email, - }, - } - } - ) - job_config_filled.labels = {"bigframes-api": "streaming_to_pubsub"} - - # begin the query job - query_job = bq_client.query( - sql, - job_config=job_config_filled, # type:ignore - # typing error above is in bq client library - # (should accept abstract job_config, only takes concrete) - job_id=job_id, - job_id_prefix=job_id_prefix, - ) - - # return the query job to the user for lifetime management - return query_job diff --git a/bigframes/testing/__init__.py b/bigframes/testing/__init__.py deleted file mode 100644 index 098a67bddf3..00000000000 --- a/bigframes/testing/__init__.py +++ /dev/null @@ -1,21 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -"""[Experimental] Utilities for testing BigQuery DataFrames. - -These modules are provided for testing the BigQuery DataFrames package. The -interface is not considered stable. -""" - -# Do not import modules contains pytest. (b/490160312) diff --git a/bigframes/testing/compiler_session.py b/bigframes/testing/compiler_session.py deleted file mode 100644 index b248f37cfc8..00000000000 --- a/bigframes/testing/compiler_session.py +++ /dev/null @@ -1,50 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import dataclasses -import typing - -import bigframes.core -import bigframes.core.compile as compile -import bigframes.session.executor - - -@dataclasses.dataclass -class SQLCompilerExecutor(bigframes.session.executor.Executor): - """Executor for SQL compilation using sqlglot.""" - - compiler = compile.sqlglot - - def to_sql( - self, - array_value: bigframes.core.ArrayValue, - offset_column: typing.Optional[str] = None, - ordered: bool = True, - enable_cache: bool = False, - ) -> str: - if offset_column: - array_value, _ = array_value.promote_offsets() - - # Compared with BigQueryCachingExecutor, SQLCompilerExecutor skips - # caching the subtree. - return self.compiler.compile_sql( - compile.CompileRequest(array_value.node, sort_rows=ordered) - ).sql - - def execute( - self, - array_value, - execution_spec, - ): - raise NotImplementedError("SQLCompilerExecutor.execute not implemented") diff --git a/bigframes/testing/engine_utils.py b/bigframes/testing/engine_utils.py deleted file mode 100644 index 385ca7e45cc..00000000000 --- a/bigframes/testing/engine_utils.py +++ /dev/null @@ -1,40 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import asyncio - -import pandas.testing - -from bigframes.core import nodes -from bigframes.session import execution_spec, semi_executor - -SPEC = execution_spec.ExecutionSpec( - ordered=True, -) - - -def assert_equivalence_execution( - node: nodes.BigFrameNode, - engine1: semi_executor.SemiExecutor, - engine2: semi_executor.SemiExecutor, -): - e1_result = asyncio.run(engine1.execute(node, SPEC)) - e2_result = asyncio.run(engine2.execute(node, SPEC)) - assert e1_result is not None - assert e2_result is not None - # Convert to pandas, as pandas has better comparison utils than arrow - assert e1_result.schema == e2_result.schema - e1_table = e1_result.batches().to_pandas() - e2_table = e2_result.batches().to_pandas() - pandas.testing.assert_frame_equal(e1_table, e2_table, rtol=1e-5) diff --git a/bigframes/testing/mocks.py b/bigframes/testing/mocks.py deleted file mode 100644 index f8ad43dd664..00000000000 --- a/bigframes/testing/mocks.py +++ /dev/null @@ -1,187 +0,0 @@ -# Copyright 2023 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 copy -import datetime -import unittest.mock as mock -from typing import Any, Dict, Literal, Optional, Sequence - -import google.auth.credentials -import google.cloud.bigquery -import google.cloud.bigquery.table -import pyarrow -import pytest -from bigframes_vendored.google_cloud_bigquery import _pandas_helpers - -import bigframes -import bigframes.clients -import bigframes.core.global_session -import bigframes.dataframe -import bigframes.session.clients - -"""Utilities for creating test resources.""" - - -TEST_SCHEMA = (google.cloud.bigquery.SchemaField("col", "INTEGER"),) - - -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", -) -> bigframes.Session: - """[Experimental] Create a mock BigQuery DataFrames session that avoids making Google Cloud API calls. - - Intended for unit test environments that don't have access to the network. - """ - credentials = mock.create_autospec( - google.auth.credentials.Credentials, instance=True - ) - - bq_time = datetime.datetime.now() - table_time = bq_time + datetime.timedelta(minutes=1) - - if anonymous_dataset is None: - anonymous_dataset = google.cloud.bigquery.DatasetReference( - "test-project", - "test_dataset", - ) - - if bqclient is None: - bqclient = mock.create_autospec(google.cloud.bigquery.Client, instance=True) - bqclient.project = anonymous_dataset.project - bqclient.location = location - - # Mock the location. - table = mock.create_autospec(google.cloud.bigquery.Table, instance=True) - table._properties = {} - # TODO(tswast): support tables created before and after the session started. - type(table).created = mock.PropertyMock(return_value=table_time) - type(table).location = mock.PropertyMock(return_value=location) - 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 = table_name - type(table).num_rows = mock.PropertyMock(return_value=1000000000) - bqclient.get_table.return_value = table - - queries = [] - job_configs = [] - - def query_mock( - query, - *args, - job_config: Optional[google.cloud.bigquery.QueryJobConfig] = None, - **kwargs, - ): - queries.append(query) - job_configs.append(copy.deepcopy(job_config)) - 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(table_name), - ) - type(query_job).statement_type = mock.PropertyMock(return_value="SELECT") - - if job_config is not None and job_config.create_session: - type(query_job).session_info = google.cloud.bigquery.SessionInfo( - {"sessionId": session_id}, - ) - - if query.startswith("SELECT CURRENT_TIMESTAMP()"): - query_job.result = mock.MagicMock(return_value=[[bq_time]]) - elif "CREATE TEMP TABLE".casefold() in query.casefold(): - type(query_job).destination = mock.PropertyMock( - return_value=anonymous_dataset.table("temp_table_from_session"), - ) - else: - type(query_job).schema = mock.PropertyMock(return_value=table_schema) - - return query_job - - def query_and_wait_mock(query, *args, job_config=None, **kwargs): - queries.append(query) - job_configs.append(copy.deepcopy(job_config)) - - if query.startswith("SELECT CURRENT_TIMESTAMP()"): - return iter([[datetime.datetime.now()]]) - - rows = mock.create_autospec( - google.cloud.bigquery.table.RowIterator, instance=True - ) - row = mock.create_autospec(google.cloud.bigquery.table.Row, instance=True) - rows.__iter__.return_value = [row] - type(rows).schema = mock.PropertyMock(return_value=table_schema) - rows.to_arrow.return_value = pyarrow.Table.from_pydict( - {field.name: [None] for field in table_schema}, - schema=pyarrow.schema( - _pandas_helpers.bq_to_arrow_field(field) for field in table_schema - ), - ) - - if job_config is not None and job_config.destination is None: - # Assume that the query finishes fast enough for jobless mode. - type(rows).job_id = mock.PropertyMock(return_value=None) - - return rows - - bqclient.query.side_effect = query_mock - bqclient.query_and_wait.side_effect = query_and_wait_mock - bqclient._query_and_wait_bigframes.side_effect = query_and_wait_mock - - clients_provider = mock.create_autospec(bigframes.session.clients.ClientsProvider) - type(clients_provider).bqclient = mock.PropertyMock(return_value=bqclient) - clients_provider._credentials = credentials - clients_provider.project = anonymous_dataset.project - - bqoptions = bigframes.BigQueryOptions( - credentials=credentials, - location=location, - ordering_mode=ordering_mode, - ) - session = bigframes.Session(context=bqoptions, clients_provider=clients_provider) - session._bq_connection_manager = mock.create_autospec( - bigframes.clients.BqConnectionManager, instance=True - ) - session._queries = queries # type: ignore - session._job_configs = job_configs # type: ignore - return session - - -def create_dataframe( - monkeypatch: pytest.MonkeyPatch, - *, - session: Optional[bigframes.Session] = None, - data: Optional[Dict[str, Sequence[Any]]] = None, -) -> bigframes.dataframe.DataFrame: - """[Experimental] Create a mock DataFrame that avoids making Google Cloud API calls. - - Intended for unit test environments that don't have access to the network. - """ - if session is None: - session = create_bigquery_session() - - if data is None: - data = {"col": []} - - # Since this may create a ReadLocalNode, the session we explicitly pass in - # might not actually be used. Mock out the global session, too. - monkeypatch.setattr(bigframes.core.global_session, "_global_session", session) - bigframes.options.bigquery._session_started = True - return bigframes.dataframe.DataFrame(data, session=session) diff --git a/bigframes/testing/polars_session.py b/bigframes/testing/polars_session.py deleted file mode 100644 index 2806dab53f9..00000000000 --- a/bigframes/testing/polars_session.py +++ /dev/null @@ -1,141 +0,0 @@ -# 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. -# 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 -import weakref -from typing import Union - -import pandas -import polars - -import bigframes -import bigframes.core.blocks -import bigframes.core.compile.polars -import bigframes.dataframe -import bigframes.session.execution_spec -import bigframes.session.executor -import bigframes.session.metrics -from bigframes.functions import _utils, function, udf_def - - -# Does not support to_sql, dry_run, peek, cached -@dataclasses.dataclass -class TestExecutor(bigframes.session.executor.Executor): - compiler = bigframes.core.compile.polars.PolarsCompiler() - - def execute( - self, - array_value: bigframes.core.ArrayValue, - execution_spec: bigframes.session.execution_spec.ExecutionSpec, - ): - """ - Execute the ArrayValue, storing the result to a temporary session-owned table. - """ - if execution_spec.destination_spec is not None: - raise ValueError( - f"TestExecutor does not support destination spec: {execution_spec.destination_spec}" - ) - lazy_frame: polars.LazyFrame = self.compiler.compile(array_value.node) - if execution_spec.peek is not None: - lazy_frame = lazy_frame.limit(execution_spec.peek) - pa_table = lazy_frame.collect().to_arrow() - # 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.LocalExecuteResult( - data=pa_table, - bf_schema=array_value.schema, - ) - - def cached( - self, - array_value: bigframes.core.ArrayValue, - *, - config, - ) -> None: - return - - -class TestSession(bigframes.session.Session): - def __init__(self): - self._location = None # type: ignore - self._bq_kms_key_name = None # type: ignore - self._clients_provider = None # type: ignore - self._bq_connection = None # type: ignore - self._skip_bq_connection_check = True - self._session_id: str = "test_session" - self._objects: list[ - weakref.ReferenceType[ - Union[ - bigframes.core.indexes.Index, - bigframes.series.Series, - bigframes.dataframe.DataFrame, - ] - ] - ] = [] - self._strictly_ordered: bool = True - self._allow_ambiguity = False # type: ignore - self._default_index_type = bigframes.enums.DefaultIndexKind.SEQUENTIAL_INT64 - self._metrics = bigframes.session.metrics.ExecutionMetrics() - self._function_session = None # type: ignore - self._temp_storage_manager = None # type: ignore - self._executor = TestExecutor() - self._loader = None # type: ignore - - def read_pandas(self, pandas_dataframe, write_engine="default"): - original_input = pandas_dataframe - - # override read_pandas to always keep data local-only - if isinstance(pandas_dataframe, (pandas.Series, pandas.Index)): - pandas_dataframe = pandas_dataframe.to_frame() - - local_block = bigframes.core.blocks.Block.from_local(pandas_dataframe, self) - bf_df = bigframes.dataframe.DataFrame(local_block) - - if isinstance(original_input, pandas.Series): - series = bf_df[bf_df.columns[0]] - series.name = original_input.name - return series - - if isinstance(original_input, pandas.Index): - return bf_df.index - - return bf_df - - def udf( - self, - *, - input_types=None, - output_type=None, - **kwargs, - ): - def wrapper(func): - udf_sig = _utils.get_func_signature( - func, - input_types, - output_type, - ) - - code_def = udf_def.CodeDef.from_func(func) - udf_definition = udf_def.PythonUdf( - signature=udf_sig, - code=code_def, - ) - return function.UdfRoutine(func=func, _udf_def=udf_definition) - - return wrapper - - @property - def bqclient(self): - # prevents logger from trying to call bq upon any errors - return None diff --git a/bigframes/testing/utils.py b/bigframes/testing/utils.py deleted file mode 100644 index 79e99968f58..00000000000 --- a/bigframes/testing/utils.py +++ /dev/null @@ -1,558 +0,0 @@ -# Copyright 2023 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 base64 -import decimal -import re -from typing import Iterable, Optional, Sequence, Set, TypeVar, Union - -import geopandas as gpd # type: ignore -import google.api_core.operation -import numpy as np -import pandas as pd -import pandas.api.types as pd_types -import pyarrow as pa # type: ignore -import pytest -from google.cloud import bigquery, functions_v2 -from google.cloud.functions_v2.types import functions - -import bigframes.functions._utils as bff_utils -import bigframes.pandas as bpd -from bigframes import operations as ops -from bigframes.core import expression as ex - -ML_REGRESSION_METRICS = [ - "mean_absolute_error", - "mean_squared_error", - "mean_squared_log_error", - "median_absolute_error", - "r2_score", - "explained_variance", -] -ML_CLASSFICATION_METRICS = [ - "precision", - "recall", - "accuracy", - "f1_score", - "log_loss", - "roc_auc", -] -ML_GENERATE_TEXT_OUTPUT = [ - "ml_generate_text_llm_result", - "ml_generate_text_status", - "prompt", -] -ML_GENERATE_EMBEDDING_OUTPUT = [ - "ml_generate_embedding_result", - "ml_generate_embedding_statistics", - "ml_generate_embedding_status", - "content", -] -ML_MULTIMODAL_GENERATE_EMBEDDING_OUTPUT = [ - "ml_generate_embedding_result", - "ml_generate_embedding_status", - # start and end sec depend on input format. Images and videos input will contain these 2. - "ml_generate_embedding_start_sec", - "ml_generate_embedding_end_sec", - "content", -] - -SeriesOrIndexT = TypeVar("SeriesOrIndexT", pd.Series, pd.Index) - - -def pandas_major_version() -> int: - match = re.search(r"^v?(\d+)", pd.__version__.strip()) - assert match is not None - return int(match.group(1)) - - -# Prefer this function for tests that run in both ordered and unordered mode -def assert_dfs_equivalent(pd_df: pd.DataFrame, bf_df: bpd.DataFrame, **kwargs): - bf_df_local = bf_df.to_pandas() - ignore_order = not bf_df._session._strictly_ordered - assert_frame_equal(bf_df_local, pd_df, ignore_order=ignore_order, **kwargs) - - -def assert_series_equivalent(pd_series: pd.Series, bf_series: bpd.Series, **kwargs): - bf_df_local = bf_series.to_pandas() - ignore_order = not bf_series._session._strictly_ordered - assert_series_equal(bf_df_local, pd_series, ignore_order=ignore_order, **kwargs) - - -def _normalize_all_nulls(col: pd.Series) -> pd.Series: - if pd_types.is_float_dtype(col.dtype): - col = col.astype("float64").astype("Float64") - elif col.dtype == "object": - if any(isinstance(x, decimal.Decimal) for x in col): - pass - else: - try: - col = col.astype("Float64") - except (TypeError, ValueError, SystemError): - pass - return col - - -def _normalize_index_nulls(idx: pd.Index) -> pd.Index: - if isinstance(idx, pd.MultiIndex): - new_levels = [ - _normalize_index_nulls(idx.get_level_values(i)) for i in range(idx.nlevels) - ] - return pd.MultiIndex.from_arrays(new_levels, names=idx.names) - if idx.hasnans: - if pd_types.is_float_dtype(idx.dtype): - idx = idx.astype("float64").astype("Float64") - return idx - - -def assert_frame_equal( - left: pd.DataFrame, - right: pd.DataFrame, - *, - ignore_order: bool = False, - nulls_are_nan: bool = True, - downcast_object: bool = True, - **kwargs, -): - if ignore_order: - # Sort by a column to get consistent results. - if left.index.name != "rowindex": - left = left.sort_values( - list(left.columns.drop("geography_col", errors="ignore")) - ).reset_index(drop=True) - right = right.sort_values( - list(right.columns.drop("geography_col", errors="ignore")) - ).reset_index(drop=True) - else: - left = left.sort_index() - right = right.sort_index() - - # Pandas sometimes likes to produce object dtype columns - # However, nan/None/Null inconsistency makes comparison futile, convert to typed column - if downcast_object: - left = left.apply(lambda x: x.infer_objects()) - right = right.apply(lambda x: x.infer_objects()) - - if nulls_are_nan: - left = left.apply(_normalize_all_nulls) - right = right.apply(_normalize_all_nulls) - left.index = _normalize_index_nulls(left.index) - right.index = _normalize_index_nulls(right.index) - - pd.testing.assert_frame_equal(left, right, **kwargs) - - -def assert_series_equal( - left: pd.Series, - right: pd.Series, - *, - ignore_order: bool = False, - nulls_are_nan: bool = True, - **kwargs, -): - if ignore_order: - if left.index.name is None: - left = left.sort_values().reset_index(drop=True) - right = right.sort_values().reset_index(drop=True) - else: - left = left.sort_index() - right = right.sort_index() - - if isinstance(left.index, pd.RangeIndex) or pd_types.is_integer_dtype( - left.index.dtype, - ): - left.index = left.index.astype("Int64") - if isinstance(right.index, pd.RangeIndex) or pd_types.is_integer_dtype( - right.index.dtype, - ): - right.index = right.index.astype("Int64") - - if nulls_are_nan: - left = _normalize_all_nulls(left.infer_objects()) - right = _normalize_all_nulls(right.infer_objects()) - left.index = _normalize_index_nulls(left.index) - right.index = _normalize_index_nulls(right.index) - left.name = pd.NA if pd.isna(left.name) else left.name # type: ignore - right.name = pd.NA if pd.isna(right.name) else right.name # type: ignore - - pd.testing.assert_series_equal(left, right, **kwargs) - - -def assert_index_equal(left, right, **kwargs): - pd.testing.assert_index_equal(left, right, **kwargs) - - -def _standardize_index(idx): - return pd.Index(list(idx), name=idx.name) - - -def assert_pandas_index_equal_ignore_index_type(idx0, idx1): - idx0 = _standardize_index(idx0) - idx1 = _standardize_index(idx1) - - pd.testing.assert_index_equal(idx0, idx1) - - -def convert_pandas_dtypes(df: pd.DataFrame, bytes_col: bool): - """Convert pandas dataframe dtypes compatible with bigframes dataframe.""" - - # TODO(chelsealin): updates the function to accept dtypes as input rather than - # hard-code the column names here. - - # Convert basic types columns - df["bool_col"] = df["bool_col"].astype(pd.BooleanDtype()) - df["int64_col"] = df["int64_col"].astype(pd.Int64Dtype()) - df["int64_too"] = df["int64_too"].astype(pd.Int64Dtype()) - df["float64_col"] = df["float64_col"].astype(pd.Float64Dtype()) - df["string_col"] = df["string_col"].astype(pd.StringDtype(storage="pyarrow")) - - if "rowindex" in df.columns: - df["rowindex"] = df["rowindex"].astype(pd.Int64Dtype()) - if "rowindex_2" in df.columns: - df["rowindex_2"] = df["rowindex_2"].astype(pd.Int64Dtype()) - - # Convert time types columns. The `astype` works for Pandas 2.0 but hits an assert - # error at Pandas 1.5. Hence, we have to convert to arrow table and convert back - # to pandas dataframe. - if not isinstance(df["date_col"].dtype, pd.ArrowDtype): - df["date_col"] = pd.to_datetime(df["date_col"], format="%Y-%m-%d") - arrow_table = pa.Table.from_pandas( - pd.DataFrame(df, columns=["date_col"]), - schema=pa.schema([("date_col", pa.date32())]), - ) - df["date_col"] = arrow_table.to_pandas(types_mapper=pd.ArrowDtype)["date_col"] - - if not isinstance(df["datetime_col"].dtype, pd.ArrowDtype): - df["datetime_col"] = pd.to_datetime( - df["datetime_col"], format="%Y-%m-%d %H:%M:%S" - ) - arrow_table = pa.Table.from_pandas( - pd.DataFrame(df, columns=["datetime_col"]), - schema=pa.schema([("datetime_col", pa.timestamp("us"))]), - ) - df["datetime_col"] = arrow_table.to_pandas(types_mapper=pd.ArrowDtype)[ - "datetime_col" - ] - - if not isinstance(df["time_col"].dtype, pd.ArrowDtype): - df["time_col"] = pd.to_datetime(df["time_col"], format="%H:%M:%S.%f") - arrow_table = pa.Table.from_pandas( - pd.DataFrame(df, columns=["time_col"]), - schema=pa.schema([("time_col", pa.time64("us"))]), - ) - df["time_col"] = arrow_table.to_pandas(types_mapper=pd.ArrowDtype)["time_col"] - - if not isinstance(df["timestamp_col"].dtype, pd.ArrowDtype): - df["timestamp_col"] = pd.to_datetime( - df["timestamp_col"], format="%Y-%m-%d %H:%M:%S.%f%Z" - ) - arrow_table = pa.Table.from_pandas( - pd.DataFrame(df, columns=["timestamp_col"]), - schema=pa.schema([("timestamp_col", pa.timestamp("us", tz="UTC"))]), - ) - df["timestamp_col"] = arrow_table.to_pandas(types_mapper=pd.ArrowDtype)[ - "timestamp_col" - ] - - if not isinstance(df["duration_col"].dtype, pd.ArrowDtype): - df["duration_col"] = df["duration_col"].astype(pd.Int64Dtype()) - arrow_table = pa.Table.from_pandas( - pd.DataFrame(df, columns=["duration_col"]), - schema=pa.schema([("duration_col", pa.duration("us"))]), - ) - df["duration_col"] = arrow_table.to_pandas(types_mapper=pd.ArrowDtype)[ - "duration_col" - ] - - # Convert geography types columns. - if "geography_col" in df.columns: - df["geography_col"] = df["geography_col"].astype( - pd.StringDtype(storage="pyarrow") - ) - df["geography_col"] = gpd.GeoSeries.from_wkt( - df["geography_col"].replace({np.nan: None}) - ) - - if bytes_col and not isinstance(df["bytes_col"].dtype, pd.ArrowDtype): - df["bytes_col"] = df["bytes_col"].apply( - lambda value: base64.b64decode(value) if not pd.isnull(value) else value - ) - arrow_table = pa.Table.from_pandas( - pd.DataFrame(df, columns=["bytes_col"]), - schema=pa.schema([("bytes_col", pa.binary())]), - ) - df["bytes_col"] = arrow_table.to_pandas(types_mapper=pd.ArrowDtype)["bytes_col"] - - if not isinstance(df["numeric_col"].dtype, pd.ArrowDtype): - # Convert numeric types column. - df["numeric_col"] = df["numeric_col"].apply( - lambda value: decimal.Decimal(str(value)) if value else None # type: ignore - ) - arrow_table = pa.Table.from_pandas( - pd.DataFrame(df, columns=["numeric_col"]), - schema=pa.schema([("numeric_col", pa.decimal128(38, 9))]), - ) - df["numeric_col"] = arrow_table.to_pandas(types_mapper=pd.ArrowDtype)[ - "numeric_col" - ] - - -def assert_pandas_df_equal_pca_components(actual, expected, **kwargs): - """Compare two pandas dataframes representing PCA components. The columns - required to be present in the dataframes are: - numerical_value: numeric, - categorical_value: List[object(category, value)] - - The index types of `actual` and `expected` are ignored in the comparison. - - Args: - actual: Actual Pandas DataFrame - - expected: Expected Pandas DataFrame - - kwargs: kwargs to use in `pandas.testing.assert_series_equal` per column - """ - # Compare the index, columns and values separately, as the polarity of the - # PCA vectors can be arbitrary - pd.testing.assert_index_equal( - actual.index, expected.index.astype(actual.index.dtype) - ) # dtype agnostic index comparison - pd.testing.assert_index_equal(actual.columns, expected.columns) - for column in expected.columns: - try: - pd.testing.assert_series_equal(actual[column], expected[column], **kwargs) - except AssertionError: - if column not in {"numerical_value", "categorical_value"}: - raise - - # Allow for sign difference per numeric/categorical column - if column == "numerical_value": - actual_ = -actual[column] - expected_ = expected[column] - else: - # In this column each element is an array of objects, where the - # object has attributes "category" and "value". For the sake of - # comparison let's normalize by flipping the polarity of "value". - def normalize_array_of_objects(arr, reverse_polarity=False): - newarr = [] - for element in arr: - newelement = dict(element) - if reverse_polarity: - newelement["value"] = -newelement["value"] - newarr.append(newelement) - return sorted(newarr, key=lambda d: d["category"]) - - actual_ = actual[column].apply(normalize_array_of_objects, args=(True,)) - expected_ = expected[column].apply(normalize_array_of_objects) - - pd.testing.assert_series_equal(actual_, expected_, **kwargs) - - -def assert_pandas_df_equal_pca(actual, expected, **kwargs): - """Compare two pandas dataframes representing PCA predictions. The columns - in the dataframes are expected to be numeric. - - Args: - actual: Actual Pandas DataFrame - - expected: Expected Pandas DataFrame - - kwargs: kwargs to use in `pandas.testing.assert_series_equal` per column - """ - # Compare the index, columns and values separately, as the polarity of the - # PCA vector can be arbitrary - pd.testing.assert_index_equal(actual.index, expected.index) - pd.testing.assert_index_equal(actual.columns, expected.columns) - for column in expected.columns: - try: - pd.testing.assert_series_equal(actual[column], expected[column], **kwargs) - except AssertionError: - # Allow for sign difference per column - pd.testing.assert_series_equal(-actual[column], expected[column], **kwargs) - - -def check_pandas_df_schema_and_index( - pd_df: pd.DataFrame, - columns: Iterable, - index: Optional[Union[int, Iterable]] = None, - col_exact: bool = True, -): - """Check pandas df schema and index. But not the values. - - Args: - pd_df: the input pandas df - columns: target columns to check with - index: int or Iterable or None, default None. If int, only check the length (index size) of the df. If Iterable, check index values match. If None, skip checking index. - col_exact: If True, check the columns param are exact match. Otherwise only check the df contains all of those columns - """ - if col_exact: - assert list(pd_df.columns) == list(columns) - else: - assert set(columns) <= set(pd_df.columns) - - if index is None: - pass - elif isinstance(index, int): - assert len(pd_df) == index - elif isinstance(index, Iterable): - assert list(pd_df.index) == list(index) - else: - raise ValueError("Unsupported index type.") - - -def get_remote_function_endpoints( - bigquery_client: bigquery.Client, dataset_id: str -) -> Set[str]: - """Get endpoints used by the remote functions in a datset""" - endpoints = set() - routines = bigquery_client.list_routines(dataset=dataset_id) - for routine in routines: - rf_options = routine._properties.get("remoteFunctionOptions") - if not rf_options: - continue - rf_endpoint = rf_options.get("endpoint") - if rf_endpoint: - endpoints.add(rf_endpoint) - return endpoints - - -def get_cloud_functions( - functions_client: functions_v2.FunctionServiceClient, - project: str, - location: str, - name: Optional[str] = None, - name_prefix: Optional[str] = None, -) -> Iterable[functions.ListFunctionsResponse]: - """Get the cloud functions in the given project and location.""" - - assert not name or not name_prefix, ( - "Either 'name' or 'name_prefix' can be passed but not both." - ) - - location = bff_utils.gcf_location_from_bq_location(location) - parent = f"projects/{project}/locations/{location}" - request = functions_v2.ListFunctionsRequest(parent=parent) - page_result = functions_client.list_functions(request=request) - for response in page_result: - # If name is provided and it does not match then skip - if bool(name): - full_name = parent + f"/functions/{name}" - if response.name != full_name: - continue - # If name prefix is provided and it does not match then skip - elif bool(name_prefix): - full_name_prefix = parent + f"/functions/{name_prefix}" - if not response.name.startswith(full_name_prefix): - continue - - yield response - - -def delete_cloud_function( - functions_client: functions_v2.FunctionServiceClient, full_name: str -) -> google.api_core.operation.Operation: - """Delete a cloud function with the given fully qualified name.""" - request = functions_v2.DeleteFunctionRequest(name=full_name) - operation = functions_client.delete_function(request=request) - return operation - - -def get_first_file_from_wildcard(path): - return path.replace("*", "000000000000") - - -def cleanup_function_assets( - bigframes_func, - bigquery_client, - cloudfunctions_client=None, - ignore_failures=True, -) -> None: - """Clean up the GCP assets behind a bigframess function.""" - - # Clean up bigframes bigquery function. - try: - bigquery_client.delete_routine(bigframes_func.bigframes_bigquery_function) - except Exception: - # By default don't raise exception in cleanup. - if not ignore_failures: - raise - - if not ignore_failures: - # Make sure that the BQ routins is actually deleted - with pytest.raises(google.api_core.exceptions.NotFound): - bigquery_client.get_routine(bigframes_func.bigframes_bigquery_function) - - # Clean up bigframes cloud run function - if cloudfunctions_client: - # Clean up cloud function - try: - delete_cloud_function( - cloudfunctions_client, bigframes_func.bigframes_cloud_function - ) - except Exception: - # By default don't raise exception in cleanup. - if not ignore_failures: - raise - - if not ignore_failures: - # Make sure the cloud run function is actually deleted - try: - gcf = cloudfunctions_client.get_function( - name=bigframes_func.bigframes_cloud_function - ) - assert gcf.state is functions_v2.Function.State.DELETING - except google.cloud.exceptions.NotFound: - pass - - -def _apply_ops_to_sql( - obj: bpd.DataFrame, - ops_list: Sequence[ex.Expression], - new_names: Sequence[str], -) -> str: - """Applies a list of ops to the given DataFrame and returns the SQL - representing the resulting DataFrame.""" - array_value = obj._block.expr - result, old_names = array_value.compute_values(ops_list) - - # Rename columns for deterministic golden SQL results. - assert len(old_names) == len(new_names) - col_ids = {old_name: new_name for old_name, new_name in zip(old_names, new_names)} - result = result.rename_columns(col_ids).select_columns(new_names) - - sql = result.session._executor.to_sql(result, enable_cache=False) - return sql - - -def _apply_binary_op( - obj: bpd.DataFrame, - op: ops.BinaryOp, - l_arg: str, - r_arg: Union[str, ex.Expression], -) -> str: - """Applies a binary op to the given DataFrame and return the SQL representing - the resulting DataFrame.""" - return _apply_nary_op(obj, op, l_arg, r_arg) - - -def _apply_nary_op( - obj: bpd.DataFrame, - op: Union[ops.BinaryOp, ops.NaryOp], - *args: Union[str, ex.Expression], -) -> str: - """Applies a nary op to the given DataFrame and return the SQL representing - the resulting DataFrame.""" - op_expr = op.as_expr(*args) - sql = _apply_ops_to_sql(obj, [op_expr], [args[0]]) # type: ignore - return sql diff --git a/bigframes/version.py b/bigframes/version.py index db3e62ea976..0a5df274799 100644 --- a/bigframes/version.py +++ b/bigframes/version.py @@ -12,8 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "2.47.0" - -# {x-release-please-start-date} -__release_date__ = "2026-06-12" -# {x-release-please-end} +__version__ = "0.13.0" diff --git a/biome.json b/biome.json deleted file mode 100644 index d30c8687a4c..00000000000 --- a/biome.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "formatter": { - "indentStyle": "space", - "indentWidth": 2 - }, - "javascript": { - "formatter": { - "quoteStyle": "single" - } - }, - "css": { - "formatter": { - "quoteStyle": "single" - } - } -} diff --git a/conftest.py b/conftest.py deleted file mode 100644 index 5d3f116b521..00000000000 --- a/conftest.py +++ /dev/null @@ -1,62 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from __future__ import annotations - -import warnings - -import numpy as np -import pandas as pd -import pyarrow as pa -import pytest - -import bigframes._config - -# Make sure SettingWithCopyWarning is ignored if it exists. -# It was removed in pandas 3.0. -if hasattr(pd.errors, "SettingWithCopyWarning"): - warnings.simplefilter("ignore", pd.errors.SettingWithCopyWarning) - - -@pytest.fixture() -def polars_session_or_bpd(): - # Since the doctest imports fixture is autouse=True, don't skip if polars - # isn't available. - try: - from bigframes.testing import polars_session - - return polars_session.TestSession() - except ImportError: - import bigframes.pandas as bpd - - return bpd - - -@pytest.fixture(autouse=True) -def default_doctest_imports(doctest_namespace, polars_session_or_bpd): - """ - Avoid some boilerplate in pandas-inspired tests. - - See: https://docs.pytest.org/en/stable/how-to/doctest.html#doctest-namespace-fixture - """ - doctest_namespace["np"] = np - doctest_namespace["pd"] = pd - doctest_namespace["pa"] = pa - doctest_namespace["bpd"] = polars_session_or_bpd - bigframes._config.options.display.progress_bar = None - - # TODO(tswast): Consider setting the numpy printoptions here for better - # compatibility across numpy versions. - # https://numpy.org/doc/stable/release/2.0.0-notes.html#representation-of-numpy-scalars-changed - # https://numpy.org/doc/stable/reference/generated/numpy.set_printoptions.html#numpy-set-printoptions diff --git a/docs/README.rst b/docs/README.rst deleted file mode 100644 index a3aef5380bb..00000000000 --- a/docs/README.rst +++ /dev/null @@ -1,94 +0,0 @@ -BigQuery DataFrames (BigFrames) -=============================== - - -|GA| |pypi| |versions| - -BigQuery DataFrames (also known as BigFrames) provides a Pythonic DataFrame -and machine learning (ML) API powered by the BigQuery engine. It provides modules -for many use cases, including: - -* `bigframes.pandas `_ - is a pandas API for analytics. Many workloads can be - migrated from pandas to bigframes by just changing a few imports. -* `bigframes.ml `_ - is a scikit-learn-like API for ML. -* `bigframes.bigquery.ai `_ - are a collection of powerful AI methods, powered by Gemini. - -BigQuery DataFrames is an `open-source package `_. - -.. |GA| image:: https://img.shields.io/badge/support-GA-gold.svg - :target: https://github.com/googleapis/google-cloud-python/blob/main/README.rst#general-availability -.. |pypi| image:: https://img.shields.io/pypi/v/bigframes.svg - :target: https://pypi.org/project/bigframes/ -.. |versions| image:: https://img.shields.io/pypi/pyversions/bigframes.svg - :target: https://pypi.org/project/bigframes/ - -Getting started with BigQuery DataFrames ----------------------------------------- - -The easiest way to get started is to try the -`BigFrames quickstart `_ -in a `notebook in BigQuery Studio `_. - -To use BigFrames in your local development environment, - -1. Run ``pip install --upgrade bigframes`` to install the latest version. - -2. Setup `Application default credentials `_ - for your local development environment enviroment. - -3. Create a `GCP project with the BigQuery API enabled `_. - -4. Use the ``bigframes`` package to query data. - -.. code-block:: python - - import bigframes.pandas as bpd - - bpd.options.bigquery.project = your_gcp_project_id # Optional in BQ Studio. - bpd.options.bigquery.ordering_mode = "partial" # Recommended for performance. - df = bpd.read_gbq("bigquery-public-data.usa_names.usa_1910_2013") - print( - df.groupby("name") - .agg({"number": "sum"}) - .sort_values("number", ascending=False) - .head(10) - .to_pandas() - ) - -Documentation -------------- - -To learn more about BigQuery DataFrames, visit these pages - -* `Introduction to BigQuery DataFrames (BigFrames) `_ -* `Sample notebooks `_ -* `API reference `_ -* `Source code (GitHub) `_ - -License -------- - -BigQuery DataFrames is distributed with the `Apache-2.0 license -`_. - -It also contains code derived from the following third-party packages: - -* `Ibis `_ -* `pandas `_ -* `Python `_ -* `scikit-learn `_ -* `XGBoost `_ -* `SQLGlot `_ - -For details, see the `third_party -`_ -directory. - - -Contact Us ----------- - -For further help and provide feedback, you can email us at `bigframes-feedback@google.com `_. diff --git a/docs/README.rst b/docs/README.rst new file mode 120000 index 00000000000..89a0106941f --- /dev/null +++ b/docs/README.rst @@ -0,0 +1 @@ +../README.rst \ No newline at end of file diff --git a/docs/_templates/autosummary/class.rst b/docs/_templates/autosummary/class.rst deleted file mode 120000 index bd84850996f..00000000000 --- a/docs/_templates/autosummary/class.rst +++ /dev/null @@ -1 +0,0 @@ -../../../third_party/sphinx/ext/autosummary/templates/autosummary/class.rst \ No newline at end of file diff --git a/docs/_templates/autosummary/module.rst b/docs/_templates/autosummary/module.rst deleted file mode 120000 index f330261ac5c..00000000000 --- a/docs/_templates/autosummary/module.rst +++ /dev/null @@ -1 +0,0 @@ -../../../third_party/sphinx/ext/autosummary/templates/autosummary/module.rst \ No newline at end of file diff --git a/docs/CHANGELOG.md b/docs/changelog.md similarity index 100% rename from docs/CHANGELOG.md rename to docs/changelog.md diff --git a/docs/conf.py b/docs/conf.py index 2cc3ffa130d..af8c5efda89 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright 2024 Google LLC +# Copyright 2023 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -24,11 +24,9 @@ # All configuration values have a default; values that are commented out # serve to show the default. -from __future__ import annotations - import os +import shlex import sys -from typing import Any # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the @@ -58,19 +56,14 @@ "sphinx.ext.napoleon", "sphinx.ext.todo", "sphinx.ext.viewcode", - "sphinx_sitemap", - "myst_nb", + "recommonmark", ] -# myst-nb configuration -nb_execution_mode = "off" - # autodoc/autosummary flags autoclass_content = "both" autodoc_default_options = {"members": True} autosummary_generate = True -autosummary_imported_members = True -autosummary_ignore_module_all = True + # Add any paths that contain templates here, relative to this directory. templates_path = ["_templates"] @@ -105,7 +98,7 @@ # # This is also used if you do content translation via gettext catalogs. # Usually you set "language" from the command line for these cases. -language = "en-US" +language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: @@ -121,7 +114,6 @@ "samples/AUTHORING_GUIDE.md", "samples/CONTRIBUTING.md", "samples/snippets/README.rst", - "README.rst", # used for include in overview.rst only ] # The reST default role (used for this markup: `text`) to use for all @@ -156,20 +148,19 @@ # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. -html_theme = "pydata_sphinx_theme" +html_theme = "alabaster" # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. -# https://pydata-sphinx-theme.readthedocs.io/en/stable/user_guide/layout.html#references html_theme_options = { - "github_url": "https://github.com/googleapis/google-cloud-python", - "logo": { - "text": "BigQuery DataFrames (BigFrames)", - }, - "analytics": { - "google_analytics_id": "G-XVSRMCJ37X", - }, + "description": "BigQuery DataFrames provides DataFrame APIs on the BigQuery engine", + "github_user": "googleapis", + "github_repo": "python-bigquery-dataframes", + "github_banner": True, + "font_family": "'Roboto', Georgia, sans", + "head_font_family": "'Roboto', Georgia, serif", + "code_font_family": "'Roboto Mono', 'Consolas', monospace", } # Add any paths that contain custom themes here, relative to this directory. @@ -259,34 +250,21 @@ # Output file base name for HTML help builder. htmlhelp_basename = "bigframes-doc" -# https://sphinx-sitemap.readthedocs.io/en/latest/getting-started.html#usage -html_baseurl = "https://dataframes.bigquery.dev/" -sitemap_locales = [None] - -# We don't have any immediate plans to translate the API reference, so omit the -# language from the URLs. -# https://sphinx-sitemap.readthedocs.io/en/latest/advanced-configuration.html#configuration-customizing-url-scheme -sitemap_url_scheme = "{link}" - # -- Options for warnings ------------------------------------------------------ suppress_warnings = [ - # Allow unknown mimetype so we can use widgets in tutorial notebooks. - "mystnb.unknown_mime_type", # Temporarily suppress this to avoid "more than one target found for # cross-reference" warning, which are intractable for us to avoid while in # a mono-repo. # See https://github.com/sphinx-doc/sphinx/blob # /2a65ffeef5c107c19084fabdd706cdff3f52d93c/sphinx/domains/python.py#L843 - "ref.python", - # Allow external websites to be down occasionally. - "intersphinx.external", + "ref.python" ] # -- Options for LaTeX output --------------------------------------------- -latex_elements: dict[str, Any] = { +latex_elements = { # The paper size ('letterpaper' or 'a4paper'). #'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). @@ -304,7 +282,7 @@ ( root_doc, "bigframes.tex", - "BigQuery DataFrames (BigFrames)", + "bigframes Documentation", author, "manual", ) @@ -339,7 +317,7 @@ ( root_doc, "bigframes", - "BigQuery DataFrames (BigFrames)", + "bigframes Documentation", [author], 1, ) @@ -358,7 +336,7 @@ ( root_doc, "bigframes", - "BigQuery DataFrames (BigFrames)", + "bigframes Documentation", author, "bigframes", "bigframes Library", @@ -381,7 +359,7 @@ # Example configuration for intersphinx: refer to the Python standard library. intersphinx_mapping = { - "python": ("https://docs.python.org/3/", None), + "python": ("https://python.readthedocs.org/en/latest/", None), "google-auth": ("https://googleapis.dev/python/google-auth/latest/", None), "google.api_core": ( "https://googleapis.dev/python/google-api-core/latest/", @@ -390,8 +368,7 @@ "grpc": ("https://grpc.github.io/grpc/python/", None), "proto-plus": ("https://proto-plus-python.readthedocs.io/en/latest/", None), "protobuf": ("https://googleapis.dev/python/protobuf/latest/", None), - # TODO(tswast): re-enable if we can get temporary failures to be ignored. - # "pandas": ("https://pandas.pydata.org/pandas-docs/stable/", None), + "pandas": ("https://pandas.pydata.org/pandas-docs/stable/", None), "pydata-google-auth": ( "https://pydata-google-auth.readthedocs.io/en/latest/", None, diff --git a/docs/index.rst b/docs/index.rst index 51d05e7d368..d239ea3a785 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,85 +1,19 @@ -.. BigQuery DataFrames documentation main file +.. include:: README.rst -Scalable Python Data Analysis with BigQuery DataFrames (BigFrames) -================================================================== - -.. meta:: - :description: BigQuery DataFrames (BigFrames) provides a scalable, pandas-compatible Python API for data analysis and machine learning on petabyte-scale datasets using the BigQuery engine. - -**BigQuery DataFrames** (``bigframes``) is an open-source Python library that brings the power of **distributed computing** to your data science workflow. By providing a familiar **pandas** and **scikit-learn** compatible API, BigFrames allows you to analyze and model massive datasets where they live—directly in **BigQuery**. - -Why Choose BigQuery DataFrames? -------------------------------- - -BigFrames eliminates the "data movement bottleneck." Instead of downloading large datasets to a local environment, BigFrames translates your Python code into optimized SQL, executing complex transformations across the BigQuery fleet. - -* **Petabyte-Scale Scalability:** Effortlessly process datasets that far exceed local memory limits. -* **Familiar Python Ecosystem:** Use the same ``read_gbq``, ``groupby``, ``merge``, and ``pivot_table`` functions you already know from pandas. -* **Integrated Machine Learning:** Access BigQuery ML's powerful algorithms via a scikit-learn-like interface (``bigframes.ml``), including seamless **Gemini AI** integration. -* **Enterprise-Grade Security:** Maintain data governance and security by keeping your data within the BigQuery perimeter. -* **Hybrid Flexibility:** Easily move between distributed BigQuery processing and local pandas analysis with ``to_pandas()``. - -Core Components of BigFrames ----------------------------- - -BigQuery DataFrames is organized into specialized modules designed for the modern data stack: - -1. :mod:`bigframes.pandas`: A high-performance, pandas-compatible API for scalable data exploration, cleaning, and transformation. -2. :mod:`bigframes.bigquery`: Specialized utilities for direct BigQuery resource management, including integrations with Gemini and other AI models in the :mod:`bigframes.bigquery.ai` submodule. - - -Quickstart: Scalable Data Analysis in Seconds ---------------------------------------------- - -Install BigQuery DataFrames via pip: - -.. code-block:: bash - - pip install --upgrade bigframes - -The following example demonstrates how to perform a distributed aggregation on a public dataset with millions of rows using just a few lines of Python: - -.. code-block:: python - - import bigframes.pandas as bpd - - # If running in your local environment or Colab, uncomment these lines and add your GCP project ID - # PROJECT_ID = "bigframes-dev" - # bpd.options.bigquery.project = PROJECT_ID - - # Initialize BigFrames and load a public dataset - df = bpd.read_gbq("bigquery-public-data.usa_names.usa_1910_2013") - - # Perform familiar pandas operations that execute in the cloud - top_names = ( - df.groupby("name") - .agg({"number": "sum"}) - .sort_values("number", ascending=False) - .head(10) - ) - - # Bring the final, aggregated results back to local memory if needed - print(top_names.to_pandas()) - - -Explore the Documentation -------------------------- +API reference +------------- .. toctree:: - :maxdepth: 2 - :caption: User Documentation + :maxdepth: 3 - user_guide/index + reference/index -.. toctree:: - :maxdepth: 2 - :caption: API Reference +Changelog +--------- - reference/index - supported_pandas_apis +For a list of all BigQuery DataFrames releases: .. toctree:: - :maxdepth: 1 - :caption: Community & Updates + :maxdepth: 2 - CHANGELOG + changelog diff --git a/docs/notebooks b/docs/notebooks deleted file mode 120000 index 8f9a5b2e6d2..00000000000 --- a/docs/notebooks +++ /dev/null @@ -1 +0,0 @@ -../notebooks \ No newline at end of file diff --git a/docs/reference/.gitignore b/docs/reference/.gitignore deleted file mode 100644 index 3f127954839..00000000000 --- a/docs/reference/.gitignore +++ /dev/null @@ -1 +0,0 @@ -api/* diff --git a/docs/reference/bigframes.ml/README.rst b/docs/reference/bigframes.ml/README.rst new file mode 100644 index 00000000000..80a1fe97b73 --- /dev/null +++ b/docs/reference/bigframes.ml/README.rst @@ -0,0 +1,125 @@ +BigQuery DataFrames ML +====================== + +As BigQuery DataFrames implements the Pandas API over top of BigQuery, BigQuery +DataFrame ML implements the SKLearn API over top of BigQuery Machine Learning. + +Tutorial +-------- + +Start a session and initialize a dataframe for a BigQuery table + +.. code-block:: python + + import bigframes.pandas + + df = bigframes.pandas.read_gbq("bigquery-public-data.ml_datasets.penguins") + df + +Clean and prepare the data + +.. code-block:: python + + # filter down to the data we want to analyze + adelie_data = df[df.species == "Adelie Penguin (Pygoscelis adeliae)"] + + # drop the columns we don't care about + adelie_data = adelie_data.drop(columns=["species"]) + + # drop rows with nulls to get our training data + training_data = adelie_data.dropna() + + # take a peek at the training data + training_data + +.. code-block:: python + + # pick feature columns and label column + X = training_data[['island', 'culmen_length_mm', 'culmen_depth_mm', 'flipper_length_mm', 'sex']] + y = training_data[['body_mass_g']] + +Use train_test_split to create train and test datasets + +.. code-block:: python + + from bigframes.ml.model_selection import train_test_split + + X_train, X_test, y_train, y_test = train_test_split( + X, y, test_size=0.2) + +Define the model training pipeline + +.. code-block:: python + + from bigframes.ml.linear_model import LinearRegression + from bigframes.ml.pipeline import Pipeline + from bigframes.ml.compose import ColumnTransformer + from bigframes.ml.preprocessing import StandardScaler, OneHotEncoder + + preprocessing = ColumnTransformer([ + ("onehot", OneHotEncoder(), ["island", "species", "sex"]), + ("scaler", StandardScaler(), ["culmen_depth_mm", "culmen_length_mm", "flipper_length_mm"]), + ]) + + model = LinearRegression(fit_intercept=False) + + pipeline = Pipeline([ + ('preproc', preprocessing), + ('linreg', model) + ]) + + # view the pipeline + pipeline + +Train the pipeline + +.. code-block:: python + + pipeline.fit(X_train, y_train) + +Evaluate the model's performance on the test data + +.. code-block:: python + + from bigframes.ml.metrics import r2_score + + y_pred = pipeline.predict(X_test) + + r2_score(y_test, y_pred) + +Make predictions on new data + +.. code-block:: python + + import pandas + + new_penguins = bigframes.pandas.read_pandas( + pandas.DataFrame( + { + "tag_number": [1633, 1672, 1690], + "species": [ + "Adelie Penguin (Pygoscelis adeliae)", + "Adelie Penguin (Pygoscelis adeliae)", + "Adelie Penguin (Pygoscelis adeliae)", + ], + "island": ["Torgersen", "Torgersen", "Dream"], + "culmen_length_mm": [39.5, 38.5, 37.9], + "culmen_depth_mm": [18.8, 17.2, 18.1], + "flipper_length_mm": [196.0, 181.0, 188.0], + "sex": ["MALE", "FEMALE", "FEMALE"], + } + ).set_index("tag_number") + ) + + # view the new data + new_penguins + +.. code-block:: python + + pipeline.predict(new_penguins) + +Save the trained model to BigQuery, so we can load it later + +.. code-block:: python + + pipeline.to_gbq("bqml_tutorial.penguins_model", replace=True) diff --git a/docs/reference/bigframes.ml/cluster.rst b/docs/reference/bigframes.ml/cluster.rst new file mode 100644 index 00000000000..e91a28c0511 --- /dev/null +++ b/docs/reference/bigframes.ml/cluster.rst @@ -0,0 +1,7 @@ +bigframes.ml.cluster +==================== + +.. automodule:: bigframes.ml.cluster + :members: + :inherited-members: + :undoc-members: diff --git a/docs/reference/bigframes.ml/compose.rst b/docs/reference/bigframes.ml/compose.rst new file mode 100644 index 00000000000..9992728362f --- /dev/null +++ b/docs/reference/bigframes.ml/compose.rst @@ -0,0 +1,7 @@ +bigframes.ml.compose +==================== + +.. automodule:: bigframes.ml.compose + :members: + :inherited-members: + :undoc-members: diff --git a/docs/reference/bigframes.ml/decomposition.rst b/docs/reference/bigframes.ml/decomposition.rst new file mode 100644 index 00000000000..ec804ac8cdc --- /dev/null +++ b/docs/reference/bigframes.ml/decomposition.rst @@ -0,0 +1,7 @@ +bigframes.ml.decomposition +========================== + +.. automodule:: bigframes.ml.decomposition + :members: + :inherited-members: + :undoc-members: diff --git a/docs/reference/bigframes.ml/ensemble.rst b/docs/reference/bigframes.ml/ensemble.rst new file mode 100644 index 00000000000..2652ab5aa4d --- /dev/null +++ b/docs/reference/bigframes.ml/ensemble.rst @@ -0,0 +1,7 @@ +bigframes.ml.ensemble +===================== + +.. automodule:: bigframes.ml.ensemble + :members: + :inherited-members: + :undoc-members: diff --git a/docs/reference/bigframes.ml/forecasting.rst b/docs/reference/bigframes.ml/forecasting.rst new file mode 100644 index 00000000000..04015c99117 --- /dev/null +++ b/docs/reference/bigframes.ml/forecasting.rst @@ -0,0 +1,7 @@ +bigframes.ml.forecasting +======================== + +.. automodule:: bigframes.ml.forecasting + :members: + :inherited-members: + :undoc-members: diff --git a/docs/reference/bigframes.ml/imported.rst b/docs/reference/bigframes.ml/imported.rst new file mode 100644 index 00000000000..c151cbda6f1 --- /dev/null +++ b/docs/reference/bigframes.ml/imported.rst @@ -0,0 +1,7 @@ +bigframes.ml.imported +===================== + +.. automodule:: bigframes.ml.imported + :members: + :inherited-members: + :undoc-members: diff --git a/docs/reference/bigframes.ml/index.rst b/docs/reference/bigframes.ml/index.rst new file mode 100644 index 00000000000..f3cbe1174a7 --- /dev/null +++ b/docs/reference/bigframes.ml/index.rst @@ -0,0 +1,32 @@ +.. _bigframes_ml: +.. include:: README.rst + +API Reference +------------- + +.. toctree:: + :maxdepth: 3 + + cluster + + compose + + decomposition + + ensemble + + forecasting + + imported + + linear_model + + llm + + metrics + + model_selection + + pipeline + + preprocessing diff --git a/docs/reference/bigframes.ml/linear_model.rst b/docs/reference/bigframes.ml/linear_model.rst new file mode 100644 index 00000000000..8c6c2765b12 --- /dev/null +++ b/docs/reference/bigframes.ml/linear_model.rst @@ -0,0 +1,7 @@ +bigframes.ml.linear_model +========================= + +.. automodule:: bigframes.ml.linear_model + :members: + :inherited-members: + :undoc-members: diff --git a/docs/reference/bigframes.ml/llm.rst b/docs/reference/bigframes.ml/llm.rst new file mode 100644 index 00000000000..20ae7793e73 --- /dev/null +++ b/docs/reference/bigframes.ml/llm.rst @@ -0,0 +1,7 @@ +bigframes.ml.llm +================ + +.. automodule:: bigframes.ml.llm + :members: + :inherited-members: + :undoc-members: diff --git a/docs/reference/bigframes.ml/metrics.rst b/docs/reference/bigframes.ml/metrics.rst new file mode 100644 index 00000000000..aca11f7e9fc --- /dev/null +++ b/docs/reference/bigframes.ml/metrics.rst @@ -0,0 +1,7 @@ +bigframes.ml.metrics +==================== + +.. automodule:: bigframes.ml.metrics + :members: + :inherited-members: + :undoc-members: diff --git a/docs/reference/bigframes.ml/model_selection.rst b/docs/reference/bigframes.ml/model_selection.rst new file mode 100644 index 00000000000..d662285f990 --- /dev/null +++ b/docs/reference/bigframes.ml/model_selection.rst @@ -0,0 +1,7 @@ +bigframes.ml.model_selection +============================ + +.. automodule:: bigframes.ml.model_selection + :members: + :inherited-members: + :undoc-members: diff --git a/docs/reference/bigframes.ml/pipeline.rst b/docs/reference/bigframes.ml/pipeline.rst new file mode 100644 index 00000000000..22e877dc5b3 --- /dev/null +++ b/docs/reference/bigframes.ml/pipeline.rst @@ -0,0 +1,7 @@ +bigframes.ml.pipeline +===================== + +.. automodule:: bigframes.ml.pipeline + :members: + :inherited-members: + :undoc-members: diff --git a/docs/reference/bigframes.ml/preprocessing.rst b/docs/reference/bigframes.ml/preprocessing.rst new file mode 100644 index 00000000000..eac72da1730 --- /dev/null +++ b/docs/reference/bigframes.ml/preprocessing.rst @@ -0,0 +1,7 @@ +bigframes.ml.preprocessing +========================== + +.. automodule:: bigframes.ml.preprocessing + :members: + :inherited-members: + :undoc-members: diff --git a/docs/reference/bigframes.pandas/frame.rst b/docs/reference/bigframes.pandas/frame.rst new file mode 100644 index 00000000000..a49bcc8f7cb --- /dev/null +++ b/docs/reference/bigframes.pandas/frame.rst @@ -0,0 +1,9 @@ + +========= +DataFrame +========= + +.. autoclass:: bigframes.dataframe.DataFrame + :members: + :inherited-members: + :undoc-members: diff --git a/docs/reference/bigframes.pandas/general_functions.rst b/docs/reference/bigframes.pandas/general_functions.rst new file mode 100644 index 00000000000..4fff9aabf83 --- /dev/null +++ b/docs/reference/bigframes.pandas/general_functions.rst @@ -0,0 +1,8 @@ + +================= +General functions +================= + +.. automodule:: bigframes.pandas + :members: + :undoc-members: diff --git a/docs/reference/bigframes.pandas/groupby.rst b/docs/reference/bigframes.pandas/groupby.rst new file mode 100644 index 00000000000..483340f3487 --- /dev/null +++ b/docs/reference/bigframes.pandas/groupby.rst @@ -0,0 +1,20 @@ + +======= +GroupBy +======= + +DataFrameGroupBy +---------------- + +.. autoclass:: bigframes.core.groupby.DataFrameGroupBy + :members: + :inherited-members: + :undoc-members: + +SeriesGroupBy +------------- + +.. autoclass:: bigframes.core.groupby.SeriesGroupBy + :members: + :inherited-members: + :undoc-members: diff --git a/docs/reference/bigframes.pandas/index.rst b/docs/reference/bigframes.pandas/index.rst new file mode 100644 index 00000000000..c7ff586884e --- /dev/null +++ b/docs/reference/bigframes.pandas/index.rst @@ -0,0 +1,15 @@ + +============================ +BigQuery DataFrames (pandas) +============================ + +.. toctree:: + :maxdepth: 2 + + general_functions + series + frame + indexing + window + groupby + options diff --git a/docs/reference/bigframes.pandas/indexing.rst b/docs/reference/bigframes.pandas/indexing.rst new file mode 100644 index 00000000000..8f7f1947401 --- /dev/null +++ b/docs/reference/bigframes.pandas/indexing.rst @@ -0,0 +1,9 @@ + +============= +Index objects +============= + +.. autoclass:: bigframes.core.indexes.index.Index + :members: + :inherited-members: + :undoc-members: diff --git a/docs/reference/bigframes.pandas/options.rst b/docs/reference/bigframes.pandas/options.rst new file mode 100644 index 00000000000..60af8c826a4 --- /dev/null +++ b/docs/reference/bigframes.pandas/options.rst @@ -0,0 +1,6 @@ + +==================== +Options and settings +==================== + +``bigframes.pandas.options`` is an alias for :data:`bigframes.options`. diff --git a/docs/reference/bigframes.pandas/series.rst b/docs/reference/bigframes.pandas/series.rst new file mode 100644 index 00000000000..e212904f3ff --- /dev/null +++ b/docs/reference/bigframes.pandas/series.rst @@ -0,0 +1,44 @@ + +====== +Series +====== + +.. contents:: Table of Contents + :depth: 2 + :local: + :backlinks: none + +Series +------ + +.. autoclass:: bigframes.series.Series + :members: + :inherited-members: + :undoc-members: + +Accessors +--------- + +Datetime properties +^^^^^^^^^^^^^^^^^^^ + +.. automodule:: bigframes.operations.datetimes + :members: + :inherited-members: + :undoc-members: + +String handling +^^^^^^^^^^^^^^^ + +.. automodule:: bigframes.operations.strings + :members: + :inherited-members: + :undoc-members: + +Struct handling +^^^^^^^^^^^^^^^ + +.. automodule:: bigframes.operations.structs + :members: + :inherited-members: + :undoc-members: diff --git a/docs/reference/bigframes.pandas/window.rst b/docs/reference/bigframes.pandas/window.rst new file mode 100644 index 00000000000..55d911ecf4f --- /dev/null +++ b/docs/reference/bigframes.pandas/window.rst @@ -0,0 +1,9 @@ + +====== +Window +====== + +.. autoclass:: bigframes.core.window.Window + :members: + :inherited-members: + :undoc-members: diff --git a/docs/reference/bigframes/index.rst b/docs/reference/bigframes/index.rst new file mode 100644 index 00000000000..76d64444faf --- /dev/null +++ b/docs/reference/bigframes/index.rst @@ -0,0 +1,19 @@ + +Core objects +============ + +.. toctree:: + :maxdepth: 2 + + options + + +Session +------- + +.. autofunction:: bigframes.connect + +.. autoclass:: bigframes.session.Session + :members: + :inherited-members: + :undoc-members: diff --git a/docs/reference/bigframes/options.rst b/docs/reference/bigframes/options.rst new file mode 100644 index 00000000000..991399eb886 --- /dev/null +++ b/docs/reference/bigframes/options.rst @@ -0,0 +1,16 @@ +Options and settings +==================== + +.. currentmodule:: bigframes + +.. autodata:: options + +.. autoclass:: bigframes._config.Options + +.. autoclass:: bigframes._config.bigquery_options.BigQueryOptions + +.. autoclass:: bigframes._config.display_options.DisplayOptions + +.. autoclass:: bigframes._config.sampling_options.SamplingOptions + +.. autoclass:: bigframes._config.compute_options.ComputeOptions diff --git a/docs/reference/index.rst b/docs/reference/index.rst index 99228010b24..c790831db18 100644 --- a/docs/reference/index.rst +++ b/docs/reference/index.rst @@ -4,57 +4,9 @@ API Reference Refer to these pages for details about the public objects in the ``bigframes`` packages. -.. autosummary:: - :toctree: api +.. toctree:: + :maxdepth: 2 - bigframes._config - bigframes.bigquery - bigframes.bigquery.aead - bigframes.bigquery.ai - bigframes.bigquery.ml - bigframes.bigquery.obj - bigframes.enums - bigframes.exceptions - bigframes.geopandas - bigframes.pandas - bigframes.pandas.api.typing - bigframes.streaming - -Pandas Extensions -~~~~~~~~~~~~~~~~~ - -BigQuery DataFrames provides extensions to pandas DataFrame and Series objects. - -.. autosummary:: - :toctree: api - - bigframes.extensions.core.dataframe_accessor.BigQueryDataFrameAccessor - bigframes.extensions.core.dataframe_accessor.AIAccessor - bigframes.extensions.core.series_accessor.BigQuerySeriesAccessor - bigframes.extensions.core.series_accessor.AeadSeriesAccessor - -ML APIs -~~~~~~~ - -BigQuery DataFrames provides many machine learning modules, inspired by -scikit-learn. - - -.. autosummary:: - :toctree: api - - bigframes.ml - bigframes.ml.cluster - bigframes.ml.compose - bigframes.ml.decomposition - bigframes.ml.ensemble - bigframes.ml.forecasting - bigframes.ml.imported - bigframes.ml.impute - bigframes.ml.linear_model - bigframes.ml.llm - bigframes.ml.metrics - bigframes.ml.model_selection - bigframes.ml.pipeline - bigframes.ml.preprocessing - bigframes.ml.remote + bigframes/index + bigframes.pandas/index + bigframes.ml/index diff --git a/docs/samples b/docs/samples new file mode 120000 index 00000000000..e804737ed3a --- /dev/null +++ b/docs/samples @@ -0,0 +1 @@ +../samples \ No newline at end of file diff --git a/docs/supported_pandas_apis.rst b/docs/supported_pandas_apis.rst deleted file mode 100644 index f4b57f05d10..00000000000 --- a/docs/supported_pandas_apis.rst +++ /dev/null @@ -1,62 +0,0 @@ -Supported pandas APIs -===================== - -The following tables show the pandas APIs that have been implemented (or not) -in BigQuery DataFrames. - -* 'Y' means it implements all parameters. -* 'P' means it implements only some parameters. - -DataFrame ---------- - -.. raw:: html - :file: supported_pandas_apis/bf_dataframe.html - -DataFrameGroupBy ----------------- - -.. raw:: html - :file: supported_pandas_apis/bf_dataframegroupby.html - -Index ------ - -.. raw:: html - :file: supported_pandas_apis/bf_index.html - -pandas module -------------- - -.. raw:: html - :file: supported_pandas_apis/bf_pandas.html - -Series ------- - -.. raw:: html - :file: supported_pandas_apis/bf_series.html - -Series.dt methods ------------------ - -.. raw:: html - :file: supported_pandas_apis/bf_datetimemethods.html - -Series.str methods ------------------- - -.. raw:: html - :file: supported_pandas_apis/bf_stringmethods.html - -SeriesGroupBy -------------- - -.. raw:: html - :file: supported_pandas_apis/bf_seriesgroupby.html - -Window ------- - -.. raw:: html - :file: supported_pandas_apis/bf_window.html diff --git a/docs/supported_pandas_apis/.gitignore b/docs/supported_pandas_apis/.gitignore deleted file mode 100644 index 2d19fc766d9..00000000000 --- a/docs/supported_pandas_apis/.gitignore +++ /dev/null @@ -1 +0,0 @@ -*.html diff --git a/docs/templates/toc.yml b/docs/templates/toc.yml index 394f2a7d3cc..9879721d286 100644 --- a/docs/templates/toc.yml +++ b/docs/templates/toc.yml @@ -3,18 +3,6 @@ name: Overview - href: changelog.md name: Changelog - - items: - - href: summary_overview.html - name: Overview - - href: summary_class.html - name: Classes - - href: summary_method.html - name: Methods - - href: summary_property.html - name: Properties and Attributes - - href: supported_pandas_apis.html - name: Supported pandas APIs - name: BigQuery DataFrames API - items: - items: - name: Options @@ -32,46 +20,18 @@ - name: Session uid: bigframes.session.Session name: Session - - name: Enumerations - uid: bigframes.enums - - name: Exceptions and warnings - uid: bigframes.exceptions name: Core Objects - items: - - items: - - name: DataFrame - uid: bigframes.dataframe.DataFrame - - name: PlotAccessor - uid: bigframes.pandas.api.typing.PlotAccessor - - name: StructAccessor - uid: bigframes.operations.structs.StructFrameAccessor - name: DataFrame + - name: DataFrame + uid: bigframes.dataframe.DataFrame - items: - name: DataFrameGroupBy uid: bigframes.core.groupby.DataFrameGroupBy - name: SeriesGroupBy uid: bigframes.core.groupby.SeriesGroupBy name: Groupby - - name: Index - uid: bigframes.core.indexes.base.Index - - items: - - name: AtDataFrameIndexer - uid: bigframes.core.indexers.AtDataFrameIndexer - - name: AtSeriesIndexer - uid: bigframes.core.indexers.AtSeriesIndexer - - name: IatDataFrameIndexer - uid: bigframes.core.indexers.IatDataFrameIndexer - - name: IatSeriesIndexer - uid: bigframes.core.indexers.IatSeriesIndexer - - name: ILocDataFrameIndexer - uid: bigframes.core.indexers.ILocDataFrameIndexer - - name: IlocSeriesIndexer - uid: bigframes.core.indexers.IlocSeriesIndexer - - name: LocDataFrameIndexer - uid: bigframes.core.indexers.LocDataFrameIndexer - - name: LocSeriesIndexer - uid: bigframes.core.indexers.LocSeriesIndexer - name: Indexers + - name: Indexes + uid: bigframes.core.indexes.index.Index - name: pandas uid: bigframes.pandas - items: @@ -83,10 +43,6 @@ uid: bigframes.operations.strings.StringMethods - name: StructAccessor uid: bigframes.operations.structs.StructAccessor - - name: ListAccessor - uid: bigframes.operations.lists.ListAccessor - - name: PlotAccessor - uid: bigframes.pandas.api.typing.PlotAccessor name: Series - name: Window uid: bigframes.core.window.Window @@ -109,8 +65,6 @@ uid: bigframes.ml.decomposition - name: PCA uid: bigframes.ml.decomposition.PCA - - name: MatrixFactorization - uid: bigframes.ml.decomposition.MatrixFactorization name: decomposition - items: - name: Overview @@ -137,15 +91,7 @@ uid: bigframes.ml.imported.ONNXModel - name: TensorFlowModel uid: bigframes.ml.imported.TensorFlowModel - - name: XGBoostModel - uid: bigframes.ml.imported.XGBoostModel name: imported - - items: - - name: Overview - uid: bigframes.ml.impute - - name: SimpleImputer - uid: bigframes.ml.impute.SimpleImputer - name: impute - items: - name: Overview uid: bigframes.ml.linear_model @@ -157,25 +103,15 @@ - items: - name: Overview uid: bigframes.ml.llm - - name: GeminiTextGenerator - uid: bigframes.ml.llm.GeminiTextGenerator - name: PaLM2TextGenerator uid: bigframes.ml.llm.PaLM2TextGenerator - name: PaLM2TextEmbeddingGenerator uid: bigframes.ml.llm.PaLM2TextEmbeddingGenerator - - name: TextEmbeddingGenerator - uid: bigframes.ml.llm.TextEmbeddingGenerator - - name: Claude3TextGenerator - uid: bigframes.ml.llm.Claude3TextGenerator name: llm - items: - name: metrics uid: bigframes.ml.metrics name: metrics - - items: - - name: metrics.pairwise - uid: bigframes.ml.metrics.pairwise - name: metrics.pairwise - items: - name: model_selection uid: bigframes.ml.model_selection @@ -202,29 +138,6 @@ - name: OneHotEncoder uid: bigframes.ml.preprocessing.OneHotEncoder name: preprocessing - - items: - - name: Overview - uid: bigframes.ml.remote - - name: VertexAIModel - uid: bigframes.ml.remote.VertexAIModel - name: remote name: bigframes.ml - - items: - - name: BigQuery built-in functions - uid: bigframes.bigquery - - name: BigQuery AI Functions - uid: bigframes.bigquery._operations.ai - status: beta - name: bigframes.bigquery - - items: - - name: GeoSeries - uid: bigframes.geopandas.GeoSeries - name: bigframes.geopandas - - items: - - name: Overview - uid: bigframes.streaming - - name: StreamingDataFrame - uid: bigframes.streaming.dataframe.StreamingDataFrame - name: bigframes.streaming - status: beta name: BigQuery DataFrames + status: beta diff --git a/docs/user_guide/index.rst b/docs/user_guide/index.rst deleted file mode 100644 index 0c0935ac40a..00000000000 --- a/docs/user_guide/index.rst +++ /dev/null @@ -1,127 +0,0 @@ -User Guide -********** - -.. include:: ../README.rst - -.. toctree:: - :caption: Guides - :maxdepth: 1 - - Getting Started - Cloud Docs User Guides - -.. toctree:: - :caption: Getting Started - :maxdepth: 1 - - Quickstart Template <../notebooks/getting_started/bq_dataframes_template.ipynb> - Getting Started <../notebooks/getting_started/getting_started_bq_dataframes.ipynb> - Magics <../notebooks/getting_started/magics.ipynb> - ML Fundamentals <../notebooks/getting_started/ml_fundamentals_bq_dataframes.ipynb> - Pandas Extensions <../notebooks/getting_started/pandas_extensions.ipynb> - -.. toctree:: - :caption: DataFrames - :maxdepth: 1 - - Anywidget Mode <../notebooks/dataframes/anywidget_mode.ipynb> - Dataframe <../notebooks/dataframes/dataframe.ipynb> - Index Col Null <../notebooks/dataframes/index_col_null.ipynb> - Integrations <../notebooks/dataframes/integrations.ipynb> - Magics for Python and SQL Interoperability <../notebooks/dataframes/magics_with_local_data.ipynb> - Pypi <../notebooks/dataframes/pypi.ipynb> - -.. toctree:: - :caption: Data Types - :maxdepth: 1 - - Array <../notebooks/data_types/array.ipynb> - Json <../notebooks/data_types/json.ipynb> - Struct <../notebooks/data_types/struct.ipynb> - Timedelta <../notebooks/data_types/timedelta.ipynb> - -.. toctree:: - :caption: Generative AI - :maxdepth: 1 - - AI Functions <../notebooks/generative_ai/ai_functions.ipynb> - AI Functions for Poster Analysis <../notebooks/generative_ai/ai_movie_poster.ipynb> - AI Forecast <../notebooks/generative_ai/bq_dataframes_ai_forecast.ipynb> - LLM Code Generation <../notebooks/generative_ai/bq_dataframes_llm_code_generation.ipynb> - LLM KMeans <../notebooks/generative_ai/bq_dataframes_llm_kmeans.ipynb> - LLM Output Schema <../notebooks/generative_ai/bq_dataframes_llm_output_schema.ipynb> - LLM Vector Search <../notebooks/generative_ai/bq_dataframes_llm_vector_search.ipynb> - Drug Name Generation <../notebooks/generative_ai/bq_dataframes_ml_drug_name_generation.ipynb> - Large Language Models <../notebooks/generative_ai/large_language_models.ipynb> - -.. toctree:: - :caption: Machine Learning - :maxdepth: 1 - - ML Cross Validation <../notebooks/ml/bq_dataframes_ml_cross_validation.ipynb> - Linear Regression <../notebooks/ml/bq_dataframes_ml_linear_regression.ipynb> - Linear Regression BBQ <../notebooks/ml/bq_dataframes_ml_linear_regression_bbq.ipynb> - Linear Regression Big <../notebooks/ml/bq_dataframes_ml_linear_regression_big.ipynb> - Easy Linear Regression <../notebooks/ml/easy_linear_regression.ipynb> - Sklearn Linear Regression <../notebooks/ml/sklearn_linear_regression.ipynb> - Timeseries Analysis <../notebooks/ml/timeseries_analysis.ipynb> - -.. toctree:: - :caption: Visualization - :maxdepth: 1 - - COVID Line Graphs <../notebooks/visualization/bq_dataframes_covid_line_graphs.ipynb> - Tutorial <../notebooks/visualization/tutorial.ipynb> - -.. toctree:: - :caption: Geospatial Data - :maxdepth: 1 - - Geoseries <../notebooks/geo/geoseries.ipynb> - -.. toctree:: - :caption: Regionalized BigQuery - :maxdepth: 1 - - Regionalized <../notebooks/location/regionalized.ipynb> - -.. toctree:: - :caption: Multimodal - :maxdepth: 1 - - Multimodal Dataframe <../notebooks/multimodal/multimodal_dataframe.ipynb> - -.. toctree:: - :caption: Remote Functions - :maxdepth: 1 - - Remote Function <../notebooks/remote_functions/remote_function.ipynb> - Remote Function Usecases <../notebooks/remote_functions/remote_function_usecases.ipynb> - Remote Function Vertex Claude Model <../notebooks/remote_functions/remote_function_vertex_claude_model.ipynb> - -.. toctree:: - :caption: Streaming - :maxdepth: 1 - - Streaming Dataframe <../notebooks/streaming/streaming_dataframe.ipynb> - -.. toctree:: - :caption: Experimental - :maxdepth: 1 - - AI Operators <../notebooks/experimental/ai_operators.ipynb> - Semantic Operators <../notebooks/experimental/semantic_operators.ipynb> - -.. toctree:: - :caption: Apps - :maxdepth: 1 - - Synthetic Data Generation <../notebooks/apps/synthetic_data_generation.ipynb> - -.. toctree:: - :caption: Kaggle - :maxdepth: 1 - - AI Forecast <../notebooks/kaggle/bq_dataframes_ai_forecast.ipynb> - Describe Product Images <../notebooks/kaggle/describe-product-images-with-bigframes-multimodal.ipynb> - Vector Search Over National Jukebox <../notebooks/kaggle/vector-search-with-bigframes-over-national-jukebox.ipynb> diff --git a/mypy.ini b/mypy.ini index e3f44c262ac..901394813aa 100644 --- a/mypy.ini +++ b/mypy.ini @@ -9,9 +9,6 @@ ignore_missing_imports = True [mypy-cloudpickle.*] ignore_missing_imports = True -[mypy-flask] -ignore_missing_imports = True - [mypy-pydata_google_auth] ignore_missing_imports = True @@ -27,23 +24,5 @@ ignore_missing_imports = True [mypy-pyarrow] ignore_missing_imports = True -[mypy-ibis.*] -ignore_missing_imports = True - [mypy-ipywidgets] ignore_missing_imports = True - -[mypy-pyarrow.feather] -ignore_missing_imports = True - -[mypy-google.cloud.pubsub] -ignore_missing_imports = True - -[mypy-google.cloud.bigtable] -ignore_missing_imports = True - -[mypy-anywidget] -ignore_missing_imports = True - -[mypy-bigframes_vendored.*] -ignore_errors = True diff --git a/notebooks/.gitignore b/notebooks/.gitignore deleted file mode 100644 index d9acee9f51d..00000000000 --- a/notebooks/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -.ipynb_checkpoints/ -*.bq_exec_time_seconds -*.bytesprocessed -*.query_char_count -*.slotmillis diff --git a/notebooks/apps/synthetic_data_generation.ipynb b/notebooks/apps/synthetic_data_generation.ipynb deleted file mode 100644 index 00d30fc8a8a..00000000000 --- a/notebooks/apps/synthetic_data_generation.ipynb +++ /dev/null @@ -1,370 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 1, - "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": [ - "# BigQuery DataFrames: Synthetic Data Generation" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "In addition to BigQuery DataFrames (installing which also installs `pandas` as a dependency) we will use\n", - "`faker` library as a building block for synthetic data generation." - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" - }, - "id": "suoG7eWDZARj", - "outputId": "b5c620a9-8f5b-413f-dd38-93448f941846" - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Collecting faker\n", - " Downloading faker-37.1.0-py3-none-any.whl.metadata (15 kB)\n", - "Requirement already satisfied: tzdata in /usr/local/google/home/shuowei/src/python-bigquery-dataframes/venv/lib/python3.10/site-packages (from faker) (2024.2)\n", - "Downloading faker-37.1.0-py3-none-any.whl (1.9 MB)\n", - "\u001b[2K \u001b[38;2;114;156;31m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m1.9/1.9 MB\u001b[0m \u001b[31m55.1 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", - "\u001b[?25hInstalling collected packages: faker\n", - "Successfully installed faker-37.1.0\n" - ] - } - ], - "source": [ - "!pip install faker" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": { - "id": "m3q1oeJALhsG" - }, - "outputs": [ - { - "ename": "NameError", - "evalue": "name 'PROJECT_ID' is not defined", - "output_type": "error", - "traceback": [ - "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[0;31mNameError\u001b[0m Traceback (most recent call last)", - "Cell \u001b[0;32mIn[3], line 2\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[38;5;28;01mimport\u001b[39;00m \u001b[38;5;21;01mbigframes\u001b[39;00m\u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01mpandas\u001b[39;00m \u001b[38;5;28;01mas\u001b[39;00m \u001b[38;5;21;01mbpd\u001b[39;00m\n\u001b[0;32m----> 2\u001b[0m bpd\u001b[38;5;241m.\u001b[39moptions\u001b[38;5;241m.\u001b[39mbigquery\u001b[38;5;241m.\u001b[39mproject \u001b[38;5;241m=\u001b[39m \u001b[43mPROJECT_ID\u001b[49m\n", - "\u001b[0;31mNameError\u001b[0m: name 'PROJECT_ID' is not defined" - ] - } - ], - "source": [ - "import bigframes.pandas as bpd\n", - "bpd.options.bigquery.project = PROJECT_ID" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Let's use `GeminiTextGenerator` for our purpose, which is BigQuery DataFrame's state-of-the-art LLM integration at the time of writing this notebook (Apr 16 2024)." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 69 - }, - "id": "lIYdn1woOS1n", - "outputId": "be474338-44c2-4ce0-955e-d525b8b9c84b" - }, - "outputs": [], - "source": [ - "from bigframes.ml.llm import GeminiTextGenerator\n", - "\n", - "model = GeminiTextGenerator(model_name=\"gemini-2.5-flash\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Craft a prompt for the LLM to indicate the schema of the desired data and hints for the code that could generate such data. " - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 162 - }, - "id": "SSR-lLScLa95", - "outputId": "cbaec34e-6fa6-45b4-e54a-f11ca06b61e1" - }, - "outputs": [], - "source": [ - "prompt = \"\"\"\\\n", - "Write python code to generate a pandas dataframe based on the requirements:\n", - " Column name: Name, type: string, Description: Latin American Names\n", - " Column name: Age, type: int\n", - " Column name: Gender, type: string, Description: Inclusive\n", - "\n", - "Note:\n", - " - Return the code only, no additional texts or comments\n", - " - Use faker library\n", - " - Generate 100 rows\n", - " - The final dataframe should be named 'result_df'.\n", - "\"\"\"\n", - "\n", - "df_prompt = bpd.DataFrame({\"prompt\" : [prompt]})\n", - "df_prompt" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Be accommodating that LLM may not produce a runnable code in the first go and may need some nudging. We will retry by adding the failing code and the exception it throws as additional context in the prompt." - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 277 - }, - "id": "miDe3K4GNvOo", - "outputId": "f2039e80-5ad7-4551-f8b2-7ef714a89d63" - }, - "outputs": [], - "source": [ - "max_tries = 5\n", - "for i in range(max_tries):\n", - " # Get LLM generated code\n", - " df_result = model.predict(df_prompt)\n", - " llm_result = df_result['ml_generate_text_llm_result'].iloc[0]\n", - "\n", - " # Python code comes back as a markdown code block,\n", - " # remove the prefix \"```python\" and suffix \"```\"\n", - " code = llm_result[9:-3]\n", - " print(code)\n", - "\n", - " # Check if the generated code is runnable\n", - " try:\n", - " exec(code)\n", - " break\n", - " except Exception as ex:\n", - " print(ex)\n", - " error_context = f\"\"\"\n", - "Previous code:\n", - "{code}\n", - "\n", - "Had this exception:\n", - "{ex}\"\"\"\n", - "\n", - " # Update the prompt to help LLM correct error\n", - " df_prompt[\"prompt\"] += error_context\n", - "\n", - " # If we have exhausted max tries then stop trying\n", - " if i+1 == max_tries:\n", - " raise Exception(\"Failed to generate runnable code\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Run the generated code and verify that it produced the desired data." - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 424 - }, - "id": "GODcPwX2PBEu", - "outputId": "dec4c872-c464-49e4-cd7f-9442fc977d18" - }, - "outputs": [], - "source": [ - "execution_context = {}\n", - "exec(code, execution_context)\n", - "execution_context.get(\"result_df\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We want to run this code at scale to generate since we want to generate large amount of data. Let's deploy a `remote_function` for this purpose." - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 36 - }, - "id": "n-BsGciNqSwU", - "outputId": "996e5639-a49c-4542-a0dc-ede450e0eb6d" - }, - "outputs": [], - "source": [ - "@bpd.remote_function(packages=['faker', 'pandas'], cloud_function_service_account=\"default\")\n", - "def data_generator(id: int) -> str:\n", - " context = {}\n", - " exec(code, context)\n", - " result_df = context.get(\"result_df\")\n", - " return result_df.to_json(orient=\"records\")\n", - "\n", - "data_generator.bigframes_cloud_function" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Let’s say we want to generate 1 million rows of synthetic data. Since our generated code produces 100 rows in one run, we can initialize an indicator dataframe with 1M/100 = 10K indicator rows. Then we can apply the remote function to produce 100 synthetic data rows for each indicator row." - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 34 - }, - "id": "Odkmev9nsYqA", - "outputId": "4aa7a1fd-0c0d-4412-f326-a20e19f583b5" - }, - "outputs": [], - "source": [ - "desired_num_rows = 1_000_000 # 1 million rows\n", - "batch_size = 100 # used in the prompt\n", - "num_batches = int(desired_num_rows/batch_size)\n", - "\n", - "df = bpd.DataFrame({\"row_id\": range(num_batches)})" - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 34 - }, - "id": "UyBhlJFVsmQC", - "outputId": "29748df5-673b-4320-bb1f-53abaace3b81" - }, - "outputs": [], - "source": [ - "df[\"json_data\"] = df[\"row_id\"].apply(data_generator)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "At this point each item in `df[\"json_data\"]` is a json serialized array of 100 records. Let’s flatten that into 1 record per row using a direct SQL." - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 932 - }, - "id": "6p3eM21qvRvy", - "outputId": "333f4e49-a555-4d2f-b527-02142782b3a7" - }, - "outputs": [], - "source": [ - "sql = f\"\"\"\n", - "WITH T0 AS ({df.sql}),\n", - "T1 AS (\n", - " SELECT PARSE_JSON(json_row) AS json_row\n", - " FROM T0, UNNEST(JSON_EXTRACT_ARRAY(json_data)) AS json_row\n", - ")\n", - "SELECT STRING(json_row.Name) AS Name,\n", - " INT64(json_row.Age) AS Age,\n", - " STRING(json_row.Gender) AS Gender\n", - "FROM T1\n", - "\"\"\"\n", - "df_result = bpd.read_gbq(sql)\n", - "df_result" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "There you have it, 1 million synthetic data rows ready to use, or save them in a BigQuery table for future use." - ] - } - ], - "metadata": { - "colab": { - "provenance": [] - }, - "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.15" - } - }, - "nbformat": 4, - "nbformat_minor": 0 -} diff --git a/notebooks/data_types/array.ipynb b/notebooks/data_types/array.ipynb deleted file mode 100644 index 96c5da5ac6c..00000000000 --- a/notebooks/data_types/array.ipynb +++ /dev/null @@ -1,503 +0,0 @@ -{ - "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": [ - "# Array Data Types\n", - "\n", - "In BigQuery, an [ARRAY](https://cloud.google.com/bigquery/docs/reference/standard-sql/data-types#array_type) (also called a `repeated` column) is an ordered list of zero or more elements of the same, non-`NULL` data type. It's important to note that BigQuery `ARRAY`s cannot contain nested `ARRAY`s. BigQuery DataFrames represents BigQuery `ARRAY` types to `pandas.ArrowDtype(pa.list_(T))`, where `T` is the underlying Arrow type of the array elements.\n", - "\n", - "This notebook illustrates how to work with `ARRAY` columns in BigQuery DataFrames. First, let's import the required packages and perform the necessary setup below." - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [], - "source": [ - "import bigframes.pandas as bpd\n", - "import bigframes.bigquery as bbq\n", - "import pandas as pd\n", - "import pyarrow as pa" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [], - "source": [ - "REGION = \"US\" # @param {type: \"string\"}\n", - "\n", - "bpd.options.display.progress_bar = None\n", - "bpd.options.bigquery.location = REGION" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Create DataFrames with an array column\n", - "\n", - "**Example 1: Creating from a list of lists/tuples**" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "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", - "
NameScores
0Alice[95 88 92]
1Bob[78 81]
2Charlie[ 82 89 94 100]
\n", - "

3 rows × 2 columns

\n", - "
[3 rows x 2 columns in total]" - ], - "text/plain": [ - " Name Scores\n", - "0 Alice [95 88 92]\n", - "1 Bob [78 81]\n", - "2 Charlie [ 82 89 94 100]\n", - "\n", - "[3 rows x 2 columns]" - ] - }, - "execution_count": 4, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "names = [\"Alice\", \"Bob\", \"Charlie\"]\n", - "scores = [\n", - " [95, 88, 92],\n", - " [78, 81],\n", - " [82, 89, 94, 100]\n", - "]\n", - "df = bpd.DataFrame({\"Name\": names, \"Scores\": scores})\n", - "df" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "Name string[pyarrow]\n", - "Scores list[pyarrow]\n", - "dtype: object" - ] - }, - "execution_count": 5, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "df.dtypes" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**Example 2: Defining schema explicitly**" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "0 [95. 88. 92.]\n", - "1 [78. 81.]\n", - "2 [ 82. 89. 94. 100.]\n", - "dtype: list[pyarrow]" - ] - }, - "execution_count": 6, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "bpd.Series(data=scores, dtype=pd.ArrowDtype(pa.list_(pa.float64())))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**Example 3: Reading from a source**" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "0 [{'tables': {'score': 0.9349926710128784, 'val...\n", - "1 [{'tables': {'score': 0.9690881371498108, 'val...\n", - "2 [{'tables': {'score': 0.8667634129524231, 'val...\n", - "3 [{'tables': {'score': 0.9351968765258789, 'val...\n", - "4 [{'tables': {'score': 0.8572560548782349, 'val...\n", - "Name: predicted_default_payment_next_month, dtype: list>>[pyarrow]" - ] - }, - "execution_count": 7, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "bpd.read_gbq(\"bigquery-public-data.ml_datasets.credit_card_default\", max_results=5)[\"predicted_default_payment_next_month\"]" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Operate on `ARRAY` data\n", - "\n", - "BigQuery DataFrames provides two main approaches for operating on list (`ARRAY`) data:\n", - "\n", - "1. **The `Series.list` accessor**: Provides Pandas-like methods for array column manipulation.\n", - "2. **[BigQuery built-in functions](https://cloud.google.com/bigquery/docs/reference/standard-sql/array_functions)**: Allows you to use functions mirroring BigQuery SQL operations, available through the `bigframes.bigquery` module (abbreviated as `bbq` below), such as [`array_agg`](https://cloud.google.com/python/docs/reference/bigframes/latest/bigframes.bigquery#bigframes_bigquery_array_agg) and [`array_length`](https://cloud.google.com/python/docs/reference/bigframes/latest/bigframes.bigquery#bigframes_bigquery_array_length)." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Get the Length of Each Arrray\n", - "\n", - "**Example 1: Using list accessor to get array length**" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "0 3\n", - "1 2\n", - "2 4\n", - "Name: Scores, dtype: Int64" - ] - }, - "execution_count": 8, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "df['Scores'].list.len()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**Example 2: Using BigQuery build-in functions to get array length**" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "0 3\n", - "1 2\n", - "2 4\n", - "Name: Scores, dtype: Int64" - ] - }, - "execution_count": 9, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "bbq.array_length(df['Scores'])" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Access Element at a Specific Index (e.g., First Element) " - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "0 95\n", - "1 78\n", - "2 82\n", - "Name: Scores, dtype: Int64" - ] - }, - "execution_count": 10, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "df['Scores'].list[0]" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Explode/Unnest Array elements into Seperate Rows\n", - "\n", - "The exploded rows preserving original order when in ordering mode. If an array has multiple elements, exploded rows are ordered by the element's index\n", - "within its original array. " - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "0 95\n", - "0 88\n", - "0 92\n", - "1 78\n", - "1 81\n", - "2 82\n", - "2 89\n", - "2 94\n", - "2 100\n", - "Name: Scores, dtype: Int64" - ] - }, - "execution_count": 11, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "scores = df['Scores'].explode()\n", - "scores" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Aggregate elements back into an array" - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "0 [100. 93. 97.]\n", - "1 [83. 86.]\n", - "2 [ 87. 94. 99. 105.]\n", - "Name: Scores, dtype: list[pyarrow]" - ] - }, - "execution_count": 12, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "new_scores = scores + 5.0\n", - "new_scores_arr = bbq.array_agg(new_scores.groupby(level=0))\n", - "new_scores_arr" - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "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", - "
NameScoresNewScores
0Alice[95 88 92][100. 93. 97.]
1Bob[78 81][83. 86.]
2Charlie[ 82 89 94 100][ 87. 94. 99. 105.]
\n", - "

3 rows × 3 columns

\n", - "
[3 rows x 3 columns in total]" - ], - "text/plain": [ - " Name Scores NewScores\n", - "0 Alice [95 88 92] [100. 93. 97.]\n", - "1 Bob [78 81] [83. 86.]\n", - "2 Charlie [ 82 89 94 100] [ 87. 94. 99. 105.]\n", - "\n", - "[3 rows x 3 columns]" - ] - }, - "execution_count": 13, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# Add adjusted scores into the DataFrame. This operation requires an implicit join \n", - "# between the two tables, necessitating a unique index in the DataFrame (guaranteed \n", - "# in the default ordering and index mode).\n", - "df['NewScores'] = new_scores_arr\n", - "df" - ] - } - ], - "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.12.1" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/notebooks/data_types/json.ipynb b/notebooks/data_types/json.ipynb deleted file mode 100644 index f0a8ed4ffee..00000000000 --- a/notebooks/data_types/json.ipynb +++ /dev/null @@ -1,451 +0,0 @@ -{ - "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": [ - "# JSON Data Types\n", - "\n", - "When using BigQuery DataFrames, columns containing data in BigQuery's [JSON](https://cloud.google.com/bigquery/docs/reference/standard-sql/data-types#json_type) format (a lightweight standard) are represented as `pandas.ArrowDtype`. The exact underlying Arrow type depends on your library versions. Older environments typically use `db_dtypes.JSONArrowType()` for compatibility, which is an Arrow extension type acting as a light wrapper around `pa.string()`. In contrast, newer setups (pandas 3.0+ and pyarrow 19.0+) utilize the more recent `pa.json_(pa.string())` representation." - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [], - "source": [ - "import bigframes.pandas as bpd\n", - "import bigframes.bigquery as bbq\n", - "import db_dtypes\n", - "import pandas as pd\n", - "import pyarrow as pa" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [], - "source": [ - "REGION = \"US\" # @param {type: \"string\"}\n", - "\n", - "bpd.options.display.progress_bar = None\n", - "bpd.options.bigquery.location = REGION" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Create Series with JSON columns\n", - "\n", - "**Example 1: Create a Series with a JSON dtype from local data**\n", - "\n", - "This example demonstrates creating a JSON Series from a list of JSON strings. Note that BigQuery standardizes these strings, for instance, by removing extra spaces and ordering dictionary keys. Specifying the `dtype` is essential; if omitted, a string-type Series will be generated." - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "0 1\n", - "1 \"str\"\n", - "2 false\n", - "3 [\"a\",{\"b\":1},null]\n", - "4 {\"a\":{\"b\":[1,2,3],\"c\":true}}\n", - "5 \n", - "dtype: extension>[pyarrow]" - ] - }, - "execution_count": 4, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "json_data = [\n", - " \"1\",\n", - " '\"str\"',\n", - " \"false\",\n", - " '[\"a\", {\"b\": 1}, null]',\n", - " '{\"a\": {\"b\": [1, 2, 3], \"c\": true}}',\n", - " None,\n", - "]\n", - "bpd.Series(json_data, dtype=pd.ArrowDtype(db_dtypes.JSONArrowType()))\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**Example 2: Create a Series with a Nested JSON dtype from local data**\n", - "\n", - "To create a BigQuery DataFrame Series containing `JSON` data nested within a `STRUCT` or `LIST` type, you must represent the `JSON` data in a `pa.array` defined with the `pa.string` type. This workaround is necessary because Pyarrow lacks support for creating structs or lists that directly contain extension types (see [issue](https://github.com/apache/arrow/issues/45262))." - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "0 [{'key': '1'}]\n", - "1 [{'key': None}]\n", - "2 [{'key': '[\"1\",\"3\",\"5\"]'}]\n", - "3 [{'key': '{\"a\":1,\"b\":[\"x\",\"y\"],\"c\":{\"x\":[],\"z\"...\n", - "dtype: list>>>[pyarrow]" - ] - }, - "execution_count": 5, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "list_data = [\n", - " [{\"key\": \"1\"}],\n", - " [{\"key\": None}],\n", - " [{\"key\": '[\"1\",\"3\",\"5\"]'}],\n", - " [{\"key\": '{\"a\":1,\"b\":[\"x\",\"y\"],\"c\":{\"x\":[],\"z\":false}}'}],\n", - "]\n", - "pa_array = pa.array(list_data, type=pa.list_(pa.struct([(\"key\", pa.string())])))\n", - "bpd.Series(\n", - " pd.arrays.ArrowExtensionArray(pa_array),\n", - " dtype=pd.ArrowDtype(\n", - " pa.list_(pa.struct([(\"key\", db_dtypes.JSONArrowType())])),\n", - " ),\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**Example 3: Create a Series with a Nested JSON dtype using BigQuery SQLs**" - ] - }, - { - "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", - "
idstruct_col
01{'data': '{\"b\":100}', 'number': 2}
10{'data': '{\"a\":true}', 'number': 1}
\n", - "

2 rows × 2 columns

\n", - "
[2 rows x 2 columns in total]" - ], - "text/plain": [ - " id struct_col\n", - "0 1 {'data': '{\"b\":100}', 'number': 2}\n", - "1 0 {'data': '{\"a\":true}', 'number': 1}\n", - "\n", - "[2 rows x 2 columns]" - ] - }, - "execution_count": 6, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "sql = \"\"\"\n", - "SELECT 0 AS id, STRUCT(JSON_OBJECT('a', True) AS data, 1 AS number) AS struct_col\n", - "UNION ALL\n", - "SELECT 1, STRUCT(JSON_OBJECT('b', 100), 2),\n", - "\"\"\"\n", - "df = bpd.read_gbq(sql)\n", - "df" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "id Int64\n", - "struct_col struct>,...\n", - "dtype: object" - ] - }, - "execution_count": 7, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "df.dtypes" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Operate on `JSON` data\n", - "\n", - "The `bigframes.bigquery` module (often abbreviated as `bbq`) provides access within BigQuery DataFrames to various **[BigQuery built-in functions](https://cloud.google.com/bigquery/docs/reference/standard-sql/json_functions)**. Examples relevant for JSON data include [`json_extract`](https://cloud.google.com/python/docs/reference/bigframes/latest/bigframes.bigquery#bigframes_bigquery_json_extract) and [`parse_json`](https://cloud.google.com/python/docs/reference/bigframes/latest/bigframes.bigquery#bigframes_bigquery_parse_json)." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Extract JSON data via specific JSON path" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**Example 1: When JSON data is represented as strings**" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": {}, - "outputs": [], - "source": [ - "fruits = [\n", - " '{\"fruits\": [{\"name\": \"apple\"}, {\"name\": \"cherry\"}]}',\n", - " '{\"fruits\": [{\"name\": \"guava\"}, {\"name\": \"grapes\"}]}',\n", - "]" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "0 {\"fruits\": [{\"name\": \"apple\"}, {\"name\": \"cherr...\n", - "1 {\"fruits\": [{\"name\": \"guava\"}, {\"name\": \"grape...\n", - "dtype: string" - ] - }, - "execution_count": 9, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "str_s = bpd.Series(fruits, dtype=\"string\")\n", - "str_s" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "0 {\"name\":\"apple\"}\n", - "1 {\"name\":\"guava\"}\n", - "dtype: string" - ] - }, - "execution_count": 10, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "bbq.json_extract(str_s, \"$.fruits[0]\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**Example 2: When JSON data is stored as JSON type**" - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "0 {\"fruits\":[{\"name\":\"apple\"},{\"name\":\"cherry\"}]}\n", - "1 {\"fruits\":[{\"name\":\"guava\"},{\"name\":\"grapes\"}]}\n", - "dtype: extension>[pyarrow]" - ] - }, - "execution_count": 11, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "json_s = bpd.Series(fruits, dtype=pd.ArrowDtype(db_dtypes.JSONArrowType()))\n", - "json_s" - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "0 {\"name\":\"apple\"}\n", - "1 {\"name\":\"guava\"}\n", - "dtype: extension>[pyarrow]" - ] - }, - "execution_count": 12, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "bbq.json_extract(json_s, \"$.fruits[0]\")\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Extract an array from JSON data" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "0 ['{\"name\":\"apple\"}' '{\"name\":\"cherry\"}']\n", - "1 ['{\"name\":\"guava\"}' '{\"name\":\"grapes\"}']\n", - "dtype: list>>[pyarrow]" - ] - }, - "execution_count": 13, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "bbq.json_extract_array(json_s, \"$.fruits\")" - ] - }, - { - "cell_type": "code", - "execution_count": 14, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "0 ['{\"name\":\"apple\"}' '{\"name\":\"cherry\"}']\n", - "1 ['{\"name\":\"guava\"}' '{\"name\":\"grapes\"}']\n", - "dtype: list[pyarrow]" - ] - }, - "execution_count": 14, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "bbq.json_extract_array(str_s, \"$.fruits\")" - ] - } - ], - "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.12.1" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/notebooks/data_types/struct.ipynb b/notebooks/data_types/struct.ipynb deleted file mode 100644 index 9df0780e307..00000000000 --- a/notebooks/data_types/struct.ipynb +++ /dev/null @@ -1,483 +0,0 @@ -{ - "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": [ - "# Struct Data Types\n", - "\n", - "In BigQuery, a [STRUCT](https://cloud.google.com/bigquery/docs/reference/standard-sql/data-types#struct_type) (also known as a `record`) is a collection of ordered fields, each with a defined data type (required) and an optional field name. BigQuery DataFrames maps BigQuery `STRUCT` types to the pandas equivalent, `pandas.ArrowDtype(pa.struct())`. \n", - "\n", - "This notebook illustrates how to work with `STRUCT` columns in BigQuery DataFrames. First, let's import the required packages and perform the necessary setup below." - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [], - "source": [ - "import bigframes.pandas as bpd\n", - "import bigframes.bigquery as bbq\n", - "import pandas as pd\n", - "import pyarrow as pa" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [], - "source": [ - "REGION = \"US\" # @param {type: \"string\"}\n", - "\n", - "bpd.options.display.progress_bar = None\n", - "bpd.options.bigquery.location = REGION" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Create DataFrames with struct columns\n", - "\n", - "**Example 1: Creating from a list of objects**" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "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", - "
NameAddress
0Alice{'City': 'New York', 'State': 'NY'}
1Bob{'City': 'San Francisco', 'State': 'CA'}
2Charlie{'City': 'Seattle', 'State': 'WA'}
\n", - "

3 rows × 2 columns

\n", - "
[3 rows x 2 columns in total]" - ], - "text/plain": [ - " Name Address\n", - "0 Alice {'City': 'New York', 'State': 'NY'}\n", - "1 Bob {'City': 'San Francisco', 'State': 'CA'}\n", - "2 Charlie {'City': 'Seattle', 'State': 'WA'}\n", - "\n", - "[3 rows x 2 columns]" - ] - }, - "execution_count": 4, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "names = [\"Alice\", \"Bob\", \"Charlie\"]\n", - "addresses = [\n", - " {'City': 'New York', 'State': 'NY'},\n", - " {'City': 'San Francisco', 'State': 'CA'},\n", - " {'City': 'Seattle', 'State': 'WA'}\n", - "]\n", - "df = bpd.DataFrame({'Name': names, 'Address': addresses})\n", - "df" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "Name string[pyarrow]\n", - "Address struct[pyarrow]\n", - "dtype: object" - ] - }, - "execution_count": 5, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "df.dtypes" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**Example 2: Defining schema explicitly**" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "0 {'City': 'New York', 'State': 'NY'}\n", - "1 {'City': 'San Francisco', 'State': 'CA'}\n", - "2 {'City': 'Seattle', 'State': 'WA'}\n", - "dtype: struct[pyarrow]" - ] - }, - "execution_count": 6, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "bpd.Series(\n", - " data=addresses, \n", - " dtype=bpd.ArrowDtype(pa.struct([('City', pa.string()), ('State', pa.string())]))\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**Example 3: Reading from a source**" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "0 [{'tables': {'score': 0.8667634129524231, 'val...\n", - "1 [{'tables': {'score': 0.9351968765258789, 'val...\n", - "2 [{'tables': {'score': 0.8572560548782349, 'val...\n", - "3 [{'tables': {'score': 0.9690881371498108, 'val...\n", - "4 [{'tables': {'score': 0.9349926710128784, 'val...\n", - "Name: predicted_default_payment_next_month, dtype: list>>[pyarrow]" - ] - }, - "execution_count": 7, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "bpd.read_gbq(\"bigquery-public-data.ml_datasets.credit_card_default\", max_results=5)[\"predicted_default_payment_next_month\"]" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Operate on `STRUCT` data\n", - "\n", - "BigQuery DataFrames provides two main approaches for operating on `STRUCT` data:\n", - "\n", - "1. **[The `Series.struct` accessor](https://cloud.google.com/python/docs/reference/bigframes/latest/bigframes.operations.structs.StructAccessor)**: Provides Pandas-like methods for STRUCT column manipulation.\n", - "2. **The `DataFrame.struct` accessor**: Provides Pandas-like methods for all child STRUCT columns manipulation.\n", - "3. **[BigQuery built-in functions](https://cloud.google.com/bigquery/docs/reference/standard-sql/array_functions)**: Allows you to use functions mirroring BigQuery SQL operations, available through the `bigframes.bigquery` module (abbreviated as `bbq` below), such as [`struct`](https://cloud.google.com/python/docs/reference/bigframes/latest/bigframes.bigquery#bigframes_bigquery_struct)." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### View Data Types of Struct Fields" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "City string[pyarrow]\n", - "State string[pyarrow]\n", - "dtype: object" - ] - }, - "execution_count": 8, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "df['Address'].struct.dtypes" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Access a Struct Field by Name" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "0 New York\n", - "1 San Francisco\n", - "2 Seattle\n", - "Name: City, dtype: string" - ] - }, - "execution_count": 9, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "df['Address'].struct.field(\"City\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Extract Struct Fields into a DataFrame\n", - "\n", - "**Example 1: Using Series `.struct` accessor**" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "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", - "
CityState
0New YorkNY
1San FranciscoCA
2SeattleWA
\n", - "

3 rows × 2 columns

\n", - "
[3 rows x 2 columns in total]" - ], - "text/plain": [ - " City State\n", - "0 New York NY\n", - "1 San Francisco CA\n", - "2 Seattle WA\n", - "\n", - "[3 rows x 2 columns]" - ] - }, - "execution_count": 10, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "df['Address'].struct.explode()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**Example 2: Using DataFrame `.struct` accessor while keeping other columns**" - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "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", - "
NameAddress.CityAddress.State
0AliceNew YorkNY
1BobSan FranciscoCA
2CharlieSeattleWA
\n", - "

3 rows × 3 columns

\n", - "
[3 rows x 3 columns in total]" - ], - "text/plain": [ - " Name Address.City Address.State\n", - "0 Alice New York NY\n", - "1 Bob San Francisco CA\n", - "2 Charlie Seattle WA\n", - "\n", - "[3 rows x 3 columns]" - ] - }, - "execution_count": 11, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "df.struct.explode(\"Address\")" - ] - } - ], - "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.12.9" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/notebooks/data_types/timedelta.ipynb b/notebooks/data_types/timedelta.ipynb deleted file mode 100644 index d65c812d83e..00000000000 --- a/notebooks/data_types/timedelta.ipynb +++ /dev/null @@ -1,571 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 1, - "id": "8ebb6e6a", - "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": "c4f3bbfa", - "metadata": {}, - "source": [ - "# BigFrames Timedelta\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", - "
" - ] - }, - { - "cell_type": "markdown", - "id": "f74e2573", - "metadata": {}, - "source": [ - "In this notebook, you will use timedeltas to analyze the taxi trips in NYC. " - ] - }, - { - "cell_type": "markdown", - "id": "8f74dec4", - "metadata": {}, - "source": [ - "# Setup" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "51173665", - "metadata": {}, - "outputs": [], - "source": [ - "import bigframes.pandas as bpd\n", - "\n", - "PROJECT = \"bigframes-dev\" # replace this with your project\n", - "LOCATION = \"us\" # replace this with your location\n", - "\n", - "bpd.options.bigquery.project = PROJECT\n", - "bpd.options.bigquery.location = LOCATION\n", - "bpd.options.display.progress_bar = None\n", - "\n", - "bpd.options.bigquery.ordering_mode = \"partial\"" - ] - }, - { - "cell_type": "markdown", - "id": "d64fd3e3", - "metadata": {}, - "source": [ - "# Timedelta arithmetics and comparisons" - ] - }, - { - "cell_type": "markdown", - "id": "e10bd798", - "metadata": {}, - "source": [ - "First, you load the taxi data from the BigQuery public dataset `bigquery-public-data.new_york_taxi_trips.tlc_yellow_trips_2021`. The size of this table is about 6.3 GB." - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "f1b11138", - "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", - "
vendor_idpickup_datetimedropoff_datetimepassenger_counttrip_distancerate_codestore_and_fwd_flagpayment_typefare_amountextramta_taxtip_amounttolls_amountimp_surchargeairport_feetotal_amountpickup_location_iddropoff_location_iddata_file_yeardata_file_month
012021-06-09 07:44:46+00:002021-06-09 07:45:24+00:0012.2000000001.0N40E-90E-90E-90E-90E-90E-90E-90E-926326320216
122021-06-07 11:59:46+00:002021-06-07 12:00:00+00:0020.0100000003.0N20E-90E-90E-90E-90E-90E-90E-90E-926326320216
222021-06-23 15:03:58+00:002021-06-23 15:04:34+00:0010E-91.0N10E-90E-90E-90E-90E-90E-90E-90E-919319320216
312021-06-12 14:26:55+00:002021-06-12 14:27:08+00:0001.0000000001.0N30E-90E-90E-90E-90E-90E-90E-90E-914314320216
422021-06-15 08:39:01+00:002021-06-15 08:40:36+00:0010E-91.0N10E-90E-90E-90E-90E-90E-90E-90E-919319320216
\n", - "
" - ], - "text/plain": [ - " vendor_id pickup_datetime dropoff_datetime \\\n", - "0 1 2021-06-09 07:44:46+00:00 2021-06-09 07:45:24+00:00 \n", - "1 2 2021-06-07 11:59:46+00:00 2021-06-07 12:00:00+00:00 \n", - "2 2 2021-06-23 15:03:58+00:00 2021-06-23 15:04:34+00:00 \n", - "3 1 2021-06-12 14:26:55+00:00 2021-06-12 14:27:08+00:00 \n", - "4 2 2021-06-15 08:39:01+00:00 2021-06-15 08:40:36+00:00 \n", - "\n", - " passenger_count trip_distance rate_code store_and_fwd_flag payment_type \\\n", - "0 1 2.200000000 1.0 N 4 \n", - "1 2 0.010000000 3.0 N 2 \n", - "2 1 0E-9 1.0 N 1 \n", - "3 0 1.000000000 1.0 N 3 \n", - "4 1 0E-9 1.0 N 1 \n", - "\n", - " fare_amount extra mta_tax tip_amount tolls_amount imp_surcharge \\\n", - "0 0E-9 0E-9 0E-9 0E-9 0E-9 0E-9 \n", - "1 0E-9 0E-9 0E-9 0E-9 0E-9 0E-9 \n", - "2 0E-9 0E-9 0E-9 0E-9 0E-9 0E-9 \n", - "3 0E-9 0E-9 0E-9 0E-9 0E-9 0E-9 \n", - "4 0E-9 0E-9 0E-9 0E-9 0E-9 0E-9 \n", - "\n", - " airport_fee total_amount pickup_location_id dropoff_location_id \\\n", - "0 0E-9 0E-9 263 263 \n", - "1 0E-9 0E-9 263 263 \n", - "2 0E-9 0E-9 193 193 \n", - "3 0E-9 0E-9 143 143 \n", - "4 0E-9 0E-9 193 193 \n", - "\n", - " data_file_year data_file_month \n", - "0 2021 6 \n", - "1 2021 6 \n", - "2 2021 6 \n", - "3 2021 6 \n", - "4 2021 6 " - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "taxi_trips = bpd.read_gbq(\"bigquery-public-data.new_york_taxi_trips.tlc_yellow_trips_2021\").dropna()\n", - "taxi_trips = taxi_trips[taxi_trips['pickup_datetime'].dt.year == 2021]\n", - "taxi_trips.peek(5)" - ] - }, - { - "cell_type": "markdown", - "id": "f5b13623", - "metadata": {}, - "source": [ - "Based on the dataframe content, you calculate the trip durations and store them under the column “trip_duration”. You can see that the values under \"trip_duartion\" are timedeltas." - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "12fc1a5a", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "duration[us][pyarrow]" - ] - }, - "execution_count": 4, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "taxi_trips['trip_duration'] = taxi_trips['dropoff_datetime'] - taxi_trips['pickup_datetime']\n", - "taxi_trips['trip_duration'].dtype" - ] - }, - { - "cell_type": "markdown", - "id": "4b18b8d9", - "metadata": {}, - "source": [ - "To remove data outliers, you filter the taxi_trips to keep only the trips that were less than 2 hours." - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "id": "62b2d42e", - "metadata": {}, - "outputs": [], - "source": [ - "import pandas as pd\n", - "\n", - "taxi_trips = taxi_trips[taxi_trips['trip_duration'] <= pd.Timedelta(\"2h\")]" - ] - }, - { - "cell_type": "markdown", - "id": "665fb8a7", - "metadata": {}, - "source": [ - "Finally, you calculate the average speed of each trip, and find the median speed of all trips." - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "id": "e79e23c3", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "The median speed of an average taxi trip is: 10.58 mph.\n" - ] - } - ], - "source": [ - "average_speed = taxi_trips[\"trip_distance\"] / (taxi_trips['trip_duration'] / pd.Timedelta(\"1h\"))\n", - "print(f\"The median speed of an average taxi trip is: {average_speed.median():.2f} mph.\")" - ] - }, - { - "cell_type": "markdown", - "id": "261dbdf1", - "metadata": {}, - "source": [ - "Given how packed NYC is, a median taxi speed of 10.58 mph totally makes sense." - ] - }, - { - "cell_type": "markdown", - "id": "6122c32e", - "metadata": {}, - "source": [ - "# Use timedelta for rolling aggregation" - ] - }, - { - "cell_type": "markdown", - "id": "05ae6fbb", - "metadata": {}, - "source": [ - "Using your existing dataset, you can now calculate the taxi trip count over a period of two days, and find out when NYC is at its busiest and when it is fast asleep.\n", - "\n", - "First, you pick two workdays (a Thursday and a Friday) as your target dates:" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "id": "7dc50b1c", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Number of records: 255434\n" - ] - } - ], - "source": [ - "import datetime\n", - "\n", - "target_dates = [\n", - " datetime.date(2021, 12, 2), \n", - " datetime.date(2021, 12, 3)\n", - "]\n", - "\n", - "two_day_taxi_trips = taxi_trips[taxi_trips['pickup_datetime'].dt.date.isin(target_dates)]\n", - "print(f\"Number of records: {len(two_day_taxi_trips)}\")\n", - "# Number of records: 255434\n" - ] - }, - { - "cell_type": "markdown", - "id": "a5f39bd8", - "metadata": {}, - "source": [ - "Your next step involves aggregating the number of records associated with each unique \"pickup_datetime\" value. Additionally, the data undergo upsampling to account for any absent timestamps, which are populated with a count of 0." - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "id": "f25b34f5", - "metadata": {}, - "outputs": [], - "source": [ - "import pandas as pd\n", - "\n", - "two_day_trip_count = two_day_taxi_trips['pickup_datetime'].value_counts()\n", - "\n", - "full_index = pd.date_range(\n", - " start='2021-12-02 00:00:00',\n", - " end='2021-12-04 00:00:00',\n", - " freq='s',\n", - " tz='UTC'\n", - ")\n", - "two_day_trip_count = two_day_trip_count.reindex(full_index).fillna(0)" - ] - }, - { - "cell_type": "markdown", - "id": "3394b2ba", - "metadata": {}, - "source": [ - "You'll then calculate the sum of trip counts within a 5-minute rolling window. This involves using the `rolling()` method, which can accept a time window in the form of either a string or a timedelta object." - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "id": "4d5987d8", - "metadata": {}, - "outputs": [], - "source": [ - "two_day_trip_rolling_count = two_day_trip_count.sort_index().rolling(window=\"5m\").sum()" - ] - }, - { - "cell_type": "markdown", - "id": "3811b1fb", - "metadata": {}, - "source": [ - "Finally, you visualize the trip counts throughout the target dates." - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "id": "871c32c5", - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAABS8AAAJeCAYAAABVkfCjAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjYsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvq6yFwwAAAAlwSFlzAAAPYQAAD2EBqD+naQABAABJREFUeJzs3Xd4HOW5NvB7tqv3brn3hm1MN70bCC2FhDRSSE8IpJ+QL5BwEpKQQ0gjJAdySCghIRCKKaaDbdx7t+Um2ep1e5vvj5l3dmZ2V9KuVtJKvn/XlSva3dnVyKxW7zzvUyRZlmUQERERERERERERZRnLaJ8AERERERERERERUSIMXhIREREREREREVFWYvCSiIiIiIiIiIiIshKDl0RERERERERERJSVGLwkIiIiIiIiIiKirMTgJREREREREREREWUlBi+JiIiIiIiIiIgoKzF4SURERERERERERFnJNtonkI2i0SiOHz+OgoICSJI02qdDREREREREREQ0psiyjL6+PtTW1sJiST9/ksHLBI4fP476+vrRPg0iIiIiIiIiIqIx7dixY5gwYULaz2fwMoGCggIAyj9uYWHhKJ9NZoVCIbz66qu47LLLYLfbR/t0aAzge4ZSxfcMpYrvGUoV3zOUKr5nKFV8z1A6+L6hVI3390xvby/q6+u1OFu6GLxMQJSKFxYWjsvgZW5uLgoLC8flLwZlHt8zlCq+ZyhVfM9QqvieoVTxPUOp4nuG0sH3DaXqZHnPDLUlIwf2EBERERERERERUVZi8JKIiIiIiIiIiIiyEoOXRERERERERERElJUYvCQiIiIiIiIiIqKsxOAlERERERERERERZSUGL4mIiIiIiIiIiCgrMXhJREREREREREREWYnBSyIiIiIiIiIiIspKDF4SERERERERERFRVmLwkoiIiIiIiIiIiLISg5dERERERERERESUlRi8JCIiIiIiIiIioqzE4CURERERERERERFlJQYviYiIiIiIiIiIKCsxeElERERERERERERZicFLIiIiIiIiIiIiykoMXhIREREREREREVFWYvCSiIiIiIiIiIiIshKDl0RERERERERERJSVGLwkIiIiIiIiIiKirMTgJRERERERERFRFvnzOw342hObEYnKo30qRKOOwUsiIiIiIiIioixyz4rdeH7rcbyzrw2yLCPKICad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jBu0FWOmdViwBwAAAAAAAEBYovISAAAAAAAAQFgieQkAAAAAAAAgLJG8BAAAAAAAABCWSF4CAAAAAAAACEs9Nnn5xRdf6JxzzlGvXr1kMpn03//+N2C/w+HQzTffrFGjRikuLk69evXST37yE+3Zs6fJuWpqahQXF6fNmzdLkhYsWKBjjjlGUVFRGjhwoF588cWA4++77z4dd9xxSkhIUGZmpn74wx9qw4YNzcbZr18/ffrpp1qwYIHOO+885eTkKC4uTmPGjNErr7wScOyaNWt0wQUXKD8/XyaTSY8++mirvhcrV67USSedpOjoaOXm5urBBx9scsxbb72loUOHKjo6WqNGjdKcOXOOeN4dO3Zo2rRpio2NVWZmpm666SY1NDQEHHOk71VzSkpKdPnllysxMVHJycm6+uqrVVlZ2eb31FaMmUat+f6WlpZq5syZysnJUVRUlAYPHnzEcdNZP9va2lrNnDlTaWlpio+P1wUXXKCioqKAY1ozXtuKMeNWW1urq666SqNGjZLVatUPf/jDJse8/fbbOv3005WRkaHExERNnDhRH3/88RHPzZjpuWNGkl555RUdddRRio2NVU5Ojn7605/qwIEDRzx3Z/xsDcPQn//8Z+Xk5CgmJkZTpkzRpk2bAo5pzXhtq1COmaefflqjR49WYmKi79/thx9+2Gyc4fLZxPUMY8Yf1zOtw5hx43qm9RgzblzPtE0ox42/+++/XyaTSddff32z+8Pl86nHXdMYPdScOXOMW2+91Xj77bcNScY777wTsL+0tNSYMmWK8cYbbxjr1683Fi5caIwbN8449thjm5zr3XffNYYNG2YYhmFs3brViI2NNW688UZj7dq1xt///nfDYrEYH330ke/4qVOnGrNnzzZWr15tLF++3Dj77LONvLw8o7KyMuC8K1asMJKSkoz6+nrjnnvuMW677Tbj66+/NjZv3mw8+uijhtlsNt577z3f8UuWLDF+97vfGa+99pqRnZ1tPPLII0f8PpSVlRlZWVnG5Zdfbqxevdp47bXXjJiYGOOZZ57xHfP1118bFovFePDBB421a9cat912m2Gz2YxVq1a1eN6GhgZj5MiRxpQpU4xly5YZc+bMMdLT041bbrnFd0xrvlfNOfPMM42jjjrKWLRokfHll18aAwcONC699NI2vadgMGbcWvP9raurM8aOHWucffbZxldffWVs27bNWLBggbF8+fLDnruzfrbXXHONkZuba8ybN8/47rvvjAkTJhjHH3+8b39rxmswGDNulZWVxjXXXGM8++yzxtSpU43zzjuvyTHXXXed8cADDxhLliwxNm7caNxyyy2GzWYzvv/++8OemzHTc8fMV199ZZjNZuOxxx4ztm7danz55ZfGiBEjjB/96EeHPXdn/Wzvv/9+Iykpyfjvf/9rrFixwjj33HONfv36GTU1Nb5jjjRegxHKMfO///3P+OCDD4yNGzcaGzZsMP74xz8aNpvNWL16dcB5w+WziesZN8aMG9czrceYceN6pvUYM25cz7RNKMeN15IlS4z8/Hxj9OjRxnXXXddkf7h8PvXEa5oem7z019w/jOYsWbLEkGRs3749YPtPf/pT4+abbzYMwzB+//vfGyNGjAjYf/HFFxtTp05t8bzFxcWGJOPzzz8P2H7XXXcZF198cYuvO/vss40ZM2Y0u69v376t+ofx1FNPGSkpKUZdXZ1v280332wMGTLE9/yiiy4ypk2bFvC68ePHG7/85S9bPO+cOXMMs9lsFBYW+rY9/fTTRmJiou9rBfO9Wrt2rSHJ+Pbbb33bPvzwQ8NkMhm7d+9u9XtqL8bM4b+/Tz/9tNG/f3+jvr7+iOfz6qyfbWlpqWGz2Yy33nrLt23dunWGJGPhwoWGYbRuvLZXTx4z/qZPn97shVtzhg8fbtx5550t7mfMuPXUMfPQQw8Z/fv3D9j2+OOPG717927xXJ31s3W5XEZ2drbx0EMPBXytqKgo47XXXjMMo3Xjtb1CPWYMwzBSUlKMf/7znwHbwuWzieuZphgzXM+0VU8eM/64nmk9xowb1zNtE4pxU1FRYQwaNMiYO3euccoppzSbvAyXz6eeeE3TY6eNB6OsrEwmk0nJycm+bS6XS++//77OO+88SdLChQs1ZcqUgNdNnTpVCxcuPOx5JSk1NTVg+//+9z/feVt63aGvaauFCxfq5JNPlt1uD4h3w4YNOnjwoO+YI72nO+64Q/n5+QHnHTVqlLKysgJeU15erjVr1rT6vC+++KJMJlPAeZOTkzV27FjftilTpshsNmvx4sWtfk9dpaeOmf/973+aOHGiZs6cqaysLI0cOVL33nuvnE6n7zWd9bNdsGCBTCaTCgoKJElLly6Vw+EI+B4PHTpUeXl5vu9xa8ZrV4nEMRMMl8ulioqKgK/NmGleTx0zEydO1M6dOzVnzhwZhqGioiL95z//0dlnn+07prN+tgUFBTKZTFqwYIEkadu2bSosLAw4b1JSksaPHx9w3iON167SGWPG6XTq9ddfV1VVlSZOnBiwL1w+m7ieCV5PHTNczwQvEsdMMLieab2eOma4nmmfjhw3M2fO1LRp05oc6y9cPp964jUNyctWqq2t1c0336xLL71UiYmJvu2LFi2SJI0fP16SVFhYGDAYJCkrK0vl5eWqqalpcl6Xy6Xrr79eJ5xwgkaOHOnbvnv3bq1cuVJnnXVWs/G8+eab+vbbbzVjxox2va+W4vXuO9wx3v2SlJ6ergEDBnTIef2/V0lJSRoyZEjAeTMzMwNeY7ValZqaesTz+n/trtCTx8zWrVv1n//8R06nU3PmzNGf/vQn/e1vf9Nf/vIX32s662cbGxurIUOGyGaz+bbb7faADzTv6xgzXTNmgvHXv/5VlZWVuuiii3zbGDNN9eQxc8IJJ+iVV17RxRdfLLvdruzsbCUlJenJJ5/0HdNZP1ubzaYhQ4YoNjY2YPvhPitbM167QkePmVWrVik+Pl5RUVG65ppr9M4772j48OG+/eH02cT1THB68pjheiY4kTpmgsH1TOv05DHD9UzwOnLcvP766/r+++913333tfj1wunzqSde05C8bAWHw6GLLrpIhmHo6aefDtj37rvv6gc/+IHM5uC+lTNnztTq1av1+uuvB2z/3//+pxNPPLHJLyRJmj9/vmbMmKHnnntOI0aMCOrrdrRZs2Zp3rx5HX7eH/3oR1q/fn2Hn7ez9fQx43K5lJmZqWeffVbHHnusLr74Yt166636xz/+4Tums36248aN0/r169W7d+8OP3dn6uljxt+rr76qO++8U2+++WbAByFjJlBPHzNr167Vddddpz//+c9aunSpPvroIxUUFOiaa67xHdNZP9vevXtr/fr1GjduXIeet7N1xpgZMmSIli9frsWLF+vaa6/V9OnTtXbtWt/+cBozrcH1TKCePma4nmm7nj5m/HE90zo9fcxwPROcjhw3O3fu1HXXXadXXnlF0dHRLR4XTuOmNSLtmobk5RF4/1Fs375dc+fODcjoS+4BfO655/qeZ2dnN1nlq6ioSImJiYqJiQnYPmvWLL3//vuaP3+++vTpc9jzen3++ec655xz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AAAAAAAAAGGJ5CUAAAAAAACAsETyEgAAAAAAAEBYInkJAAAAAAAAICyRvAQAAAAAAAAQlkheAgAAoEVXXXWV8vPz2/w6k8mkWbNmdXxAnSjY9woAAIDOQ/ISAACgB3rxxRdlMpl8/0VHR2vw4MGaNWuWioqKQh1eh/F/j4f7b8GCBaEOFQAAAM2whjoAAAAAhM5dd92lfv36qba2Vl999ZWefvppzZkzR6tXr1ZsbKyee+45uVyuUIcZtJdffjng+UsvvaS5c+c22T5s2LBu/14BAAAiEclLAACAHuyss87S2LFjJUk/+9nPlJaWpocffljvvvuuLr30UtlsthBH2D5XXHFFwPNFixZp7ty5TbYDAAAgPDFtHAAAAD6TJ0+WJG3btk1S830gXS6XHnvsMY0aNUrR0dHKyMjQmWeeqe++++6w5/7LX/4is9msv//975Kk/Px8XXXVVU2OmzRpkiZNmuR7vmDBAplMJr3xxhv64x//qOzsbMXFxencc8/Vzp07g3+zhzj0vRYUFMhkMumvf/2rnnzySfXv31+xsbE644wztHPnThmGobvvvlt9+vRRTEyMzjvvPJWUlDQ574cffqiTTjpJcXFxSkhI0LRp07RmzZoOixsAACCSUXkJAAAAny1btkiS0tLSWjzm6quv1osvvqizzjpLP/vZz9TQ0KAvv/xSixYt8lVxHuq2227Tvffeq2eeeUY///nPg4rtnnvukclk0s0336zi4mI9+uijmjJlipYvX66YmJigztkar7zyiurr6/XrX/9aJSUlevDBB3XRRRdp8uTJWrBggW6++WZt3rxZf//73/W73/1OL7zwgu+1L7/8sqZPn66pU6fqgQceUHV1tZ5++mmdeOKJWrZsGQsEAQAAHAHJSwAAgB6srKxM+/fvV21trb7++mvdddddiomJ0Q9+8INmj58/f75efPFF/eY3v9Fjjz3m2/7b3/5WhmE0+5rf/e53euSRRzR79mxNnz496FhLSkq0bt06JSQkSJKOOeYYXXTRRXruuef0m9/8JujzHsnu3bu1adMmJSUlSZKcTqfuu+8+1dTU6LvvvpPV6r6k3rdvn1555RU9/fTTioqKUmVlpX7zm9/oZz/7mZ599lnf+aZPn64hQ4bo3nvvDdgOAACAppg2DgAA0INNmTJFGRkZys3N1SWXXKL4+Hi988476t27d7PH/9///Z9MJpNuv/32JvtMJlPAc8MwNGvWLD322GP697//3a7EpST95Cc/8SUuJenCCy9UTk6O5syZ067zHsmPf/xjX+JSksaPHy/J3U/Tm7j0bq+vr9fu3bslSXPnzlVpaakuvfRS7d+/3/efxWLR+PHjNX/+/E6NGwAAIBJQeQkAANCDPfnkkxo8eLCsVquysrI0ZMgQmc0t39/esmWLevXqpdTU1COe+6WXXlJlZaWefvppXXrppe2OddCgQQHPTSaTBg4cqIKCgnaf+3Dy8vICnnsTmbm5uc1uP3jwoCRp06ZNkhr7iB4qMTGxQ+MEAACIRCQvAQAAerBx48a12KeyvU444QQtX75cTzzxhC666KImCc9DKzW9nE6nLBZLp8QUjJZiaWm7d/q8y+WS5O57mZ2d3eQ4/6pNAAAANI8rJgAAALTagAED9PHHH6ukpOSI1ZcDBw7Ugw8+qEmTJunMM8/UvHnzAqZ9p6SkqLS0tMnrtm/frv79+zfZ7q1k9DIMQ5s3b9bo0aODezOdbMCAAZKkzMxMTZkyJcTRAAAAdE/0vAQAAECrXXDBBTIMQ3feeWeTfc0t2DN69GjNmTNH69at0znnnKOamhrfvgEDBmjRokWqr6/3bXv//fe1c+fOZr/2Sy+9pIqKCt/z//znP9q7d6/OOuus9rylTjN16lQlJibq3nvvlcPhaLJ/3759IYgKAACge6HyEgAAAK126qmn6sorr9Tjjz+uTZs26cwzz5TL5dKXX36pU089VbNmzWrymgkTJujdd9/V2WefrQsvvFD//e9/ZbPZ9LOf/Uz/+c9/dOaZZ+qiiy7Sli1b9O9//9tXsXio1NRUnXjiiZoxY4aKior06KOPauDAgfr5z3/e2W87KImJiXr66ad15ZVX6phjjtEll1yijIwM7dixQx988IFOOOEEPfHEE6EOEwAAIKxReQkAAIA2mT17th566CFt27ZNN910k+69917V1NTo+OOPb/E1kydP1ptvvqlPPvlEV155pVwul6ZOnaq//e1v2rhxo66//notXLhQ77//vvr06dPsOf74xz9q2rRpuu+++/TYY4/ptNNO07x58xQbG9tZb7XdLrvsMs2bN0+9e/fWQw89pOuuu06vv/66xowZoxkzZoQ6PAAAgLBnMpqb3wMAAACEiQULFujUU0/VW2+9pQsvvDDU4QAAAKALUXkJAAAAAAAAICyRvAQAAAAAAAAQlkheAgAAAAAAAAhL9LwEAAAAAAAAEJaovAQAAAAAAAAQlkheAgAAAAAAAAhLJC8BAAAAAAAAhCWSlwAAAAAAAADCEslLAAAAAAAAAGGJ5CUAAAAAAACAsETyEgAAAAAAAEBYInkJAAAAAAAAICyRvAQAAAAAAAAQlv4fHTWyf+ju86YAAAAASUVORK5CYII=", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "from matplotlib import pyplot as plt\n", - "import matplotlib.dates as mdates\n", - "\n", - "plt.figure(figsize=(16,7))\n", - "ax = plt.gca()\n", - "formatter = mdates.DateFormatter(\"%D %H:%M:%S\")\n", - "ax.xaxis.set_major_formatter(formatter)\n", - "\n", - "ax.tick_params(axis='both', labelsize=10)\n", - "\n", - "two_day_trip_rolling_count.plot(ax=ax, legend=False)\n", - "plt.xlabel(\"Pickup Time\", fontsize=12)\n", - "plt.ylabel(\"Trip count in last 5 minutes\", fontsize=12)\n", - "plt.grid()\n", - "plt.show()\n" - ] - }, - { - "cell_type": "markdown", - "id": "336b78ce", - "metadata": {}, - "source": [ - "The taxi ride count reached its lowest point around 5:00 a.m., and peaked around 7:00 p.m. on a workday. Such is the rhythm of NYC." - ] - } - ], - "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.16" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/notebooks/dataframes/anywidget_mode.ipynb b/notebooks/dataframes/anywidget_mode.ipynb deleted file mode 100644 index 9cae55b26dc..00000000000 --- a/notebooks/dataframes/anywidget_mode.ipynb +++ /dev/null @@ -1,836 +0,0 @@ -{ - "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": [ - "This notebook demonstrates the **anywidget** display mode for BigQuery DataFrames. This mode provides an interactive table experience for exploring your data directly within the notebook.\n", - "\n", - "**Key features:**\n", - "- **Rich DataFrames & Series:** Both DataFrames and Series are displayed as interactive widgets.\n", - "- **Pagination:** Navigate through large datasets page by page without overwhelming the output.\n", - "- **Column Sorting:** Click column headers to toggle between ascending, descending, and unsorted views. Use **Shift + Click** to sort by multiple columns.\n", - "- **Column Resizing:** Drag the dividers between column headers to adjust their width.\n", - "- **Max Columns Control:** Limit the number of displayed columns to improve performance and readability for wide datasets." - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "1bc5aaf3", - "metadata": {}, - "outputs": [], - "source": [ - "bpd.options.bigquery.ordering_mode = \"partial\"\n", - "bpd.options.display.render_mode = \"anywidget\"" - ] - }, - { - "cell_type": "markdown", - "id": "0a354c69", - "metadata": {}, - "source": [ - "Load Sample Data" - ] - }, - { - "cell_type": "markdown", - "id": "interactive-df-header", - "metadata": {}, - "source": [ - "## 1. Interactive DataFrame Display\n", - "Loading a dataset from BigQuery automatically renders the interactive widget." - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "f289d250", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - " Query processed 171.4 MB in 19 seconds of slot time. [Job bigframes-dev:US.50efe672-74c6-4292-98d9-520cba9ca516 details]\n", - " " - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "state gender year name number\n", - " AL F 1910 Annie 482\n", - " AL F 1910 Myrtle 104\n", - " AR F 1910 Lillian 56\n", - " CT F 1910 Anne 38\n", - " CT F 1910 Frances 45\n", - " FL F 1910 Margaret 53\n", - " GA F 1910 Mae 73\n", - " GA F 1910 Beatrice 96\n", - " GA F 1910 Lola 47\n", - " IA F 1910 Viola 49\n", - "...\n", - "\n", - "[5552452 rows x 5 columns]\n" - ] - } - ], - "source": [ - "df = bpd.read_gbq(\"bigquery-public-data.usa_names.usa_1910_2013\")\n", - "print(df)" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "id": "220340b0", - "metadata": {}, - "outputs": [ - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "b1080dddbe4140d2b88ef85566e52955", - "version_major": 2, - "version_minor": 1 - }, - "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", - "
stategenderyearnamenumber
0ALF1910Lillian99
1ALF1910Ruby204
2ALF1910Helen76
3ALF1910Eunice41
4ARF1910Dora42
5CAF1910Edna62
6CAF1910Helen239
7COF1910Alice46
8FLF1910Willie71
9FLF1910Thelma65
\n", - "

10 rows × 5 columns

\n", - "
[5552452 rows x 5 columns in total]" - ], - "text/plain": [ - "state gender year name number\n", - " AL F 1910 Lillian 99\n", - " AL F 1910 Ruby 204\n", - " AL F 1910 Helen 76\n", - " AL F 1910 Eunice 41\n", - " AR F 1910 Dora 42\n", - " CA F 1910 Edna 62\n", - " CA F 1910 Helen 239\n", - " CO F 1910 Alice 46\n", - " FL F 1910 Willie 71\n", - " FL F 1910 Thelma 65\n", - "...\n", - "\n", - "[5552452 rows x 5 columns]" - ] - }, - "execution_count": 5, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "df" - ] - }, - { - "cell_type": "markdown", - "id": "3a73e472", - "metadata": {}, - "source": [ - "## 2. Interactive Series Display\n", - "BigQuery DataFrames `Series` objects now also support the full interactive widget experience, including pagination and formatting." - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "id": "42bb02ab", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - " Query processed 44.4 MB in a moment of slot time.\n", - " " - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "1967\n", - "1981\n", - "2009\n", - "1956\n", - "1960\n", - "2001\n", - "2009\n", - "2003\n", - "1985\n", - "1993\n", - "Name: year, dtype: Int64\n", - "...\n", - "\n", - "[5552452 rows]\n" - ] - } - ], - "source": [ - "test_series = df[\"year\"]\n", - "# Displaying the series triggers the interactive widget\n", - "print(test_series)" - ] - }, - { - "cell_type": "markdown", - "id": "7bcf1bb7", - "metadata": {}, - "source": [ - "Display with Pagination" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "id": "da23e0f3", - "metadata": {}, - "outputs": [ - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "46e836f10d9e47afb4d82b5c7da69660", - "version_major": 2, - "version_minor": 1 - }, - "text/html": [ - "
0    1910\n",
-       "1    1912\n",
-       "2    1912\n",
-       "3    1911\n",
-       "4    1912\n",
-       "5    1910\n",
-       "6    1913\n",
-       "7    1912\n",
-       "8    1913\n",
-       "9    1913

[5552452 rows]

" - ], - "text/plain": [ - "1910\n", - "1912\n", - "1912\n", - "1911\n", - "1912\n", - "1910\n", - "1913\n", - "1912\n", - "1913\n", - "1913\n", - "Name: year, dtype: Int64\n", - "...\n", - "\n", - "[5552452 rows]" - ] - }, - "execution_count": 7, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "test_series" - ] - }, - { - "cell_type": "markdown", - "id": "sorting-intro", - "metadata": {}, - "source": [ - "### Sorting by Column(s)\n", - "You can sort the table by clicking on the headers of columns that have orderable data types (like numbers, strings, and dates). Non-orderable columns (like arrays or structs) do not have sorting controls.\n", - "\n", - "#### Single-Column Sorting\n", - "The sorting control cycles through three states:\n", - "- **Unsorted (no indicator by default, ● on hover):** The default state. Click the header to sort in ascending order.\n", - "- **Ascending (▲):** The data is sorted from smallest to largest. Click again to sort in descending order.\n", - "- **Descending (▼):** The data is sorted from largest to smallest. Click again to return to the unsorted state.\n", - "\n", - "#### Multi-Column Sorting\n", - "You can sort by multiple columns to further refine your view:\n", - "- **Shift + Click:** Hold the `Shift` key while clicking additional column headers to add them to the sort order. \n", - "- Each column in a multi-sort also cycles through the three states (Ascending, Descending, Unsorted).\n", - "- **Indicator visibility:** Sorting indicators (▲, ▼) are always visible for all columns currently included in the sort. The unsorted indicator (●) is only visible when you hover over an unsorted column header." - ] - }, - { - "cell_type": "markdown", - "id": "adjustable-width-intro", - "metadata": {}, - "source": [ - "### Adjustable Column Widths\n", - "You can easily adjust the width of any column in the table. Simply hover your mouse over the vertical dividers between column headers. When the cursor changes to a resize icon, click and drag to expand or shrink the column to your desired width. This allows for better readability and customization of your table view.\n", - "\n", - "### Control Maximum Columns\n", - "You can control the number of columns displayed in the widget using the **Max columns** dropdown in the footer. This is useful for wide DataFrames where you want to focus on a subset of columns or improve rendering performance. Options include 3, 5, 7, 10, 20, or All." - ] - }, - { - "cell_type": "markdown", - "id": "bb15bab6", - "metadata": {}, - "source": [ - "Programmatic Navigation Demo" - ] - }, - { - "cell_type": "markdown", - "id": "programmatic-header", - "metadata": {}, - "source": [ - "## 3. Programmatic Widget Control\n", - "You can also instantiate the `TableWidget` directly for more control, such as checking page counts or driving navigation programmatically." - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "id": "6920d49b", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Total pages: 555246\n" - ] - }, - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "6e5f603b56fb408bb1ea41519ea8702e", - "version_major": 2, - "version_minor": 1 - }, - "text/plain": [ - "" - ] - }, - "execution_count": 8, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "import math\n", - "\n", - "from bigframes.display.anywidget import TableWidget\n", - "\n", - "# Create widget programmatically \n", - "widget = TableWidget(df)\n", - "print(f\"Total pages: {math.ceil(widget.row_count / widget.page_size)}\")\n", - " \n", - "# Display the widget\n", - "widget" - ] - }, - { - "cell_type": "markdown", - "id": "02cbd1be", - "metadata": {}, - "source": [ - "Test Navigation Programmatically" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "id": "12b68f15", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Current page: 0\n", - "After next: 1\n", - "After prev: 0\n" - ] - } - ], - "source": [ - "# Simulate button clicks programmatically\n", - "print(\"Current page:\", widget.page)\n", - "\n", - "# Go to next page\n", - "widget.page = 1\n", - "print(\"After next:\", widget.page)\n", - "\n", - "# Go to previous page\n", - "widget.page = 0\n", - "print(\"After prev:\", widget.page)" - ] - }, - { - "cell_type": "markdown", - "id": "9d310138", - "metadata": {}, - "source": [ - "## 4. Edge Cases\n", - "The widget handles small datasets gracefully, disabling unnecessary pagination controls." - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "id": "a9d5d13a", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Small dataset pages: 1\n" - ] - }, - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "20c94621c4ae4eb5a94fd3596ae8c236", - "version_major": 2, - "version_minor": 1 - }, - "text/plain": [ - "" - ] - }, - "execution_count": 10, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# Test with very small dataset\n", - "small_df = df.sort_values([\"name\", \"year\", \"state\"]).head(5)\n", - "small_widget = TableWidget(small_df)\n", - "print(f\"Small dataset pages: {math.ceil(small_widget.row_count / small_widget.page_size)}\")\n", - "small_widget" - ] - }, - { - "cell_type": "markdown", - "id": "added-cell-2", - "metadata": {}, - "source": [ - "### Displaying Generative AI results containing JSON\n", - "The `AI.GENERATE` function in BigQuery returns results in a JSON column. While BigQuery's JSON type is not natively supported by the underlying Arrow `to_pandas_batches()` method used in anywidget mode ([Apache Arrow issue #45262](https://github.com/apache/arrow/issues/45262)), BigQuery Dataframes automatically converts JSON columns to strings for display. This allows you to view the results of generative AI functions seamlessly." - ] - }, - { - "cell_type": "markdown", - "id": "ai-header", - "metadata": {}, - "source": [ - "## 5. Advanced Data Types (JSON/Structs)\n", - "The `AI.GENERATE` function in BigQuery returns results in a JSON column. BigQuery Dataframes automatically handles complex types like JSON strings for display, allowing you to view generative AI results seamlessly." - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "id": "75000341", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - " Query processed 0 Bytes in a moment of slot time. [Job bigframes-dev:US.job_cpfa9oehjApkQgrbTrKRxTpEtuQX details]\n", - " " - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "d5bf0a9438954c6890b5d8cd16bff7cd", - "version_major": 2, - "version_minor": 1 - }, - "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", - "
resultgcs_pathissuerlanguagepublication_dateclass_internationalclass_usapplication_numberfiling_datepriority_date_eurepresentative_line_1_euapplicant_line_1inventor_line_1title_line_1number
0{\"application_number\":\"18157874.1\",\"class_inte...gs://gcs-public-data--labeled-patents/espacene...EUDE29.08.018E04H 6/12<NA>18157874.121.02.201822.02.2017Liedtke & Partner PatentanwälteSHB Hebezeugbau GmbHVOLGER, AlexanderSTEUERUNGSSYSTEM FÜR AUTOMATISCHE PARKHÄUSEREP 3 366 869 A1
1{\"application_number\":\"18165514.3\",\"class_inte...gs://gcs-public-data--labeled-patents/espacene...EUDE03.10.2018H05B 6/12<NA>18165514.303.04.201830.03.2017<NA>BSH Hausger√§te GmbHAcero Acero, JesusVORRICHTUNG ZUR INDUKTIVEN ENERGIE√úBERTRAGUNGEP 3 383 141 A2
2{\"application_number\":\"18157347.8\",\"class_inte...gs://gcs-public-data--labeled-patents/espacene...EUDE03.10.2018G06F 11/30<NA>18157347.819.02.201831.03.2017Hoffmann EitleFUJITSU LIMITEDKukihara, KensukeMETHOD EXECUTED BY A COMPUTER, INFORMATION PRO...EP 3 382 553 A1
3{\"application_number\":\"18166536.5\",\"class_inte...gs://gcs-public-data--labeled-patents/espacene...EUDE03.10.2018H01L 21/20<NA>18166536.516.02.2016<NA>Scheider, Sascha et alEV Group E. Thallner GmbHKurz, FlorianVORRICHTUNG ZUM BONDEN VON SUBSTRATENEP 3 382 744 A1
4{\"application_number\":\"18171005.4\",\"class_inte...gs://gcs-public-data--labeled-patents/espacene...EUDE03.10.2018A01K 31/00<NA>18171005.405.02.201505.02.2014Stork Bamberger PatentanwälteLinco Food Systems A/SThrane, UffeMASTHÄHNCHENCONTAINER ALS BESTANDTEIL EINER E...EP 3 381 276 A1
\n", - "

5 rows × 15 columns

\n", - "
[5 rows x 15 columns in total]" - ], - "text/plain": [ - " result \\\n", - "{\"application_number\":\"18157874.1\",\"class_inter... \n", - "{\"application_number\":\"18165514.3\",\"class_inter... \n", - "{\"application_number\":\"18157347.8\",\"class_inter... \n", - "{\"application_number\":\"18166536.5\",\"class_inter... \n", - "{\"application_number\":\"18171005.4\",\"class_inter... \n", - "\n", - " gcs_path issuer language \\\n", - "gs://gcs-public-data--labeled-patents/espacenet... EU DE \n", - "gs://gcs-public-data--labeled-patents/espacenet... EU DE \n", - "gs://gcs-public-data--labeled-patents/espacenet... EU DE \n", - "gs://gcs-public-data--labeled-patents/espacenet... EU DE \n", - "gs://gcs-public-data--labeled-patents/espacenet... EU DE \n", - "\n", - "publication_date class_international class_us application_number filing_date \\\n", - " 29.08.018 E04H 6/12 18157874.1 21.02.2018 \n", - " 03.10.2018 H05B 6/12 18165514.3 03.04.2018 \n", - " 03.10.2018 G06F 11/30 18157347.8 19.02.2018 \n", - " 03.10.2018 H01L 21/20 18166536.5 16.02.2016 \n", - " 03.10.2018 A01K 31/00 18171005.4 05.02.2015 \n", - "\n", - "priority_date_eu representative_line_1_eu applicant_line_1 \\\n", - " 22.02.2017 Liedtke & Partner Patentanwälte SHB Hebezeugbau GmbH \n", - " 30.03.2017 BSH Hausgeräte GmbH \n", - " 31.03.2017 Hoffmann Eitle FUJITSU LIMITED \n", - " Scheider, Sascha et al EV Group E. Thallner GmbH \n", - " 05.02.2014 Stork Bamberger Patentanwälte Linco Food Systems A/S \n", - "\n", - " inventor_line_1 title_line_1 \\\n", - " VOLGER, Alexander STEUERUNGSSYSTEM FÜR AUTOMATISCHE PARKHÄUSER \n", - "Acero Acero, Jesus VORRICHTUNG ZUR INDUKTIVEN ENERGIEÜBERTRAGUNG \n", - " Kukihara, Kensuke METHOD EXECUTED BY A COMPUTER, INFORMATION PROC... \n", - " Kurz, Florian VORRICHTUNG ZUM BONDEN VON SUBSTRATEN \n", - " Thrane, Uffe MASTHÄHNCHENCONTAINER ALS BESTANDTEIL EINER EI... \n", - "\n", - " number \n", - "EP 3 366 869 A1 \n", - "EP 3 383 141 A2 \n", - "EP 3 382 553 A1 \n", - "EP 3 382 744 A1 \n", - "EP 3 381 276 A1 \n", - "\n", - "[5 rows x 15 columns]" - ] - }, - "execution_count": 11, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "bpd.read_gbq(\"\"\"\n", - " SELECT\n", - " AI.GENERATE(\n", - " prompt=>(\"Extract the values.\", OBJ.GET_ACCESS_URL(OBJ.FETCH_METADATA(OBJ.MAKE_REF(gcs_path, \"us.bigframes-default-connection\")), \"r\")),\n", - " connection_id=>\"us.bigframes-default-connection\",\n", - " output_schema=>\"publication_date string, class_international string, application_number string, filing_date string\") AS result,\n", - " *\n", - " FROM `bigquery-public-data.labeled_patents.extracted_data`\n", - " LIMIT 5;\n", - "\"\"\")" - ] - } - ], - "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.13.0" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/notebooks/dataframes/dataframe.ipynb b/notebooks/dataframes/dataframe.ipynb index f26b4ff1cf1..c6b276af877 100644 --- a/notebooks/dataframes/dataframe.ipynb +++ b/notebooks/dataframes/dataframe.ipynb @@ -1,27 +1,5 @@ { "cells": [ - { - "cell_type": "code", - "execution_count": 1, - "id": "eeec3428", - "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." - ] - }, { "attachments": {}, "cell_type": "markdown", @@ -34,12 +12,56 @@ { "cell_type": "code", "execution_count": 1, - "id": "96757c59-fc22-420e-a42f-c6cb956110ec", + "id": "72ebb083-f06b-4408-b24d-f349bd0851e3", "metadata": {}, "outputs": [], "source": [ + "# On the instance where you are running jupyter,\n", + "# authenticate with gcloud first:\n", + "#\n", + "# gcloud auth application-default login\n", + "\n", "import bigframes.pandas as bpd\n", "\n", + "bpd.options.bigquery.location = \"US\"" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "96757c59-fc22-420e-a42f-c6cb956110ec", + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "11c27813da5c4d2e8108bf4bd9e7e55d", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "HTML(value='Query job ccb31707-38d2-4d93-8502-e39352f322a3 is RUNNING. Open Job" - ], + "application/vnd.jupyter.widget-view+json": { + "model_id": "d1fce57541264fa1b61e1acbc99393d7", + "version_major": 2, + "version_minor": 0 + }, "text/plain": [ - "" + "HTML(value='Query job fdc644e2-c008-485a-90b2-dc64e5c81f3b is DONE. 193.8 kB processed. \n", " \n", " 0\n", - " d60c6036-0ce1-4c90-8dd9-de3b403c92a8\n", + " e14b6493-9e7f-404f-840a-8a680cc364bf\n", " 2016\n", - " Nationals\n", - " Brewers\n", - " 167\n", + " Marlins\n", + " Cubs\n", + " 187\n", " \n", " \n", " 1\n", - " af72a0b9-65f7-49fb-9b30-d505068bdf6d\n", + " 1f32b347-cbcb-4c31-a145-0e685306d168\n", " 2016\n", - " Reds\n", - " Brewers\n", - " 172\n", + " Marlins\n", + " Cubs\n", + " 189\n", " \n", " \n", " 2\n", - " f57e1271-d217-400a-aea6-2e2d7d6a59a0\n", + " 0c2292d1-7398-48be-bf8e-b41dad5e1a43\n", " 2016\n", - " Orioles\n", - " Rays\n", - " 166\n", + " Braves\n", + " Cubs\n", + " 165\n", " \n", " \n", " 3\n", - " 198f4eed-a29f-41e2-8623-cb261e5ab370\n", + " 8fbec734-a15a-42ab-8d51-60790de7750b\n", " 2016\n", - " Rockies\n", - " Giants\n", - " 182\n", + " Braves\n", + " Cubs\n", + " 222\n", " \n", " \n", " 4\n", - " cb3ef033-dd57-41fd-b206-cdd3bc12c74f\n", + " 89e514d5-fbf5-4b9d-bdac-6ca45bfd18dd\n", " 2016\n", - " Twins\n", - " Indians\n", - " 204\n", + " Phillies\n", + " Cubs\n", + " 164\n", " \n", " \n", " 5\n", - " 4be9f735-a98e-4689-87ce-852cc3a1e79d\n", + " 6a83e76c-dc0d-4924-9d3d-a2e7e0ab5b52\n", " 2016\n", - " Blue Jays\n", - " Orioles\n", - " 184\n", + " Diamondbacks\n", + " Cubs\n", + " 201\n", " \n", " \n", " 6\n", - " 0b2de8c3-11d9-4f0f-a186-25b59f34a5d8\n", + " 76ea8662-c7e6-4c38-8f2a-efe373e428ce\n", " 2016\n", - " Yankees\n", - " Mets\n", - " 182\n", + " Athletics\n", + " Cubs\n", + " 173\n", " \n", " \n", " 7\n", - " 60d80663-6ced-44aa-aad9-0f4bf8d3b4d2\n", + " 66fad23d-6e89-4f99-be29-d49b6e94f95d\n", " 2016\n", - " Red Sox\n", - " Rays\n", - " 191\n", + " Athletics\n", + " Cubs\n", + " 176\n", " \n", " \n", " 8\n", - " 7e1c2095-4fea-454c-8773-096ceb6fb05c\n", + " d977367c-cf0c-4687-95a0-eb4542efcb01\n", " 2016\n", - " Cardinals\n", - " Pirates\n", - " 201\n", + " Rockies\n", + " Cubs\n", + " 180\n", " \n", " \n", " 9\n", - " f7f24ce3-7f9d-4e8a-986e-095db847c4c1\n", + " a87070ff-1084-43ca-a7ba-69278f93ecba\n", " 2016\n", - " Rays\n", - " Twins\n", - " 189\n", + " Cardinals\n", + " Cubs\n", + " 157\n", " \n", " \n", " 10\n", - " 5c26e7fc-c99f-48b4-92c1-4a7208c8cfe9\n", + " ea6b350d-3c1d-4737-878d-4465f66999f6\n", " 2016\n", - " Rays\n", - " Twins\n", - " 177\n", + " Cardinals\n", + " Cubs\n", + " 218\n", " \n", " \n", " 11\n", - " 6d2cab13-dd85-477a-8769-669069f85836\n", + " 46463c50-0f5c-4dca-a661-dd194464e791\n", " 2016\n", - " Royals\n", - " Rays\n", - " 183\n", + " Cardinals\n", + " Cubs\n", + " 160\n", " \n", " \n", " 12\n", - " bca90342-7ddc-468e-b189-d43fad7528ec\n", + " 59134e6d-9d13-49aa-978e-c3c2300eb90f\n", " 2016\n", - " Astros\n", - " Rays\n", - " 194\n", + " Pirates\n", + " Cubs\n", + " 178\n", " \n", " \n", " 13\n", - " 630f4f78-03cc-43c1-9e57-ababb9c11418\n", + " 387630a3-a894-4327-baa1-b24ec1a654d9\n", " 2016\n", - " Dodgers\n", - " Giants\n", - " 178\n", + " Pirates\n", + " Cubs\n", + " 205\n", " \n", " \n", " 14\n", - " c0cf1376-1115-4a2f-b457-3f82bbc41a89\n", + " 5d084e13-94fd-4995-b95a-4801ea3ed556\n", " 2016\n", - " Tigers\n", - " White Sox\n", - " 193\n", + " Giants\n", + " Cubs\n", + " 197\n", " \n", " \n", " 15\n", - " 46463c50-0f5c-4dca-a661-dd194464e791\n", + " 34444c94-03ec-4d12-96af-68b8f399a22f\n", " 2016\n", - " Cardinals\n", + " Reds\n", " Cubs\n", - " 160\n", + " 198\n", " \n", " \n", " 16\n", - " 392ad56d-972e-4f77-98e2-5f8577931cf8\n", + " 9580bffe-22e1-4975-978b-1b13e7505193\n", " 2016\n", - " Giants\n", - " Cardinals\n", - " 169\n", + " Reds\n", + " Cubs\n", + " 188\n", " \n", " \n", " 17\n", - " 307730fa-bbed-4221-b4e6-a2492f546fd5\n", + " 645e6a08-afd6-4677-a5c9-01ef446b0cf3\n", " 2016\n", - " Red Sox\n", - " Twins\n", - " 251\n", + " Reds\n", + " Cubs\n", + " 188\n", " \n", " \n", " 18\n", - " 1cbc558f-7615-4fa9-bf97-7ccd62040d6f\n", + " 08981bd8-d1d7-48e1-8668-9098b8f7fe90\n", " 2016\n", - " Mets\n", - " Braves\n", - " 151\n", + " Reds\n", + " Cubs\n", + " 194\n", " \n", " \n", " 19\n", - " 723348ba-1645-43fc-9e22-92994f7a63bd\n", + " 303703bb-b55f-476d-8faf-bf582169fb1d\n", " 2016\n", - " Athletics\n", - " Twins\n", - " 153\n", + " Padres\n", + " Cubs\n", + " 175\n", " \n", " \n", " 20\n", - " ffbd6ecc-82e1-4e5d-9bd1-4ea210be5992\n", + " 71ab82a4-6e07-430a-b695-1af3bc42ea61\n", " 2016\n", - " Twins\n", - " Marlins\n", - " 185\n", + " Nationals\n", + " Cubs\n", + " 257\n", " \n", " \n", " 21\n", - " f2747230-7df5-4535-a475-a1c823d0d654\n", + " d1a110c2-f6c8-4029-bcd8-2f8a01e1561c\n", " 2016\n", - " Twins\n", - " Yankees\n", - " 180\n", + " Brewers\n", + " Cubs\n", + " 178\n", " \n", " \n", " 22\n", - " db3b6f35-a7a4-430a-8703-2b2f25103e17\n", + " 6d111b57-fa0b-4f24-82df-ff33a26f0252\n", " 2016\n", - " White Sox\n", - " Orioles\n", - " 199\n", + " Brewers\n", + " Cubs\n", + " 171\n", " \n", " \n", " 23\n", - " 5fc8c6f0-a70e-4d1b-877f-eb1ec8e6f636\n", + " a97e9539-bbbd-4e03-bf15-f25ea2c1d923\n", " 2016\n", - " Diamondbacks\n", - " Giants\n", - " 175\n", + " Brewers\n", + " Cubs\n", + " 248\n", " \n", " \n", " 24\n", - " 95d548b6-2da8-4644-812e-b277fec5b91f\n", + " dc0c9218-505c-4725-8c0c-40b72cca0956\n", " 2016\n", - " Braves\n", - " Mets\n", - " 201\n", + " Astros\n", + " Cubs\n", + " 174\n", " \n", " \n", "\n", @@ -328,64 +354,64 @@ ], "text/plain": [ " gameId year homeTeamName awayTeamName \\\n", - "0 d60c6036-0ce1-4c90-8dd9-de3b403c92a8 2016 Nationals Brewers \n", - "1 af72a0b9-65f7-49fb-9b30-d505068bdf6d 2016 Reds Brewers \n", - "2 f57e1271-d217-400a-aea6-2e2d7d6a59a0 2016 Orioles Rays \n", - "3 198f4eed-a29f-41e2-8623-cb261e5ab370 2016 Rockies Giants \n", - "4 cb3ef033-dd57-41fd-b206-cdd3bc12c74f 2016 Twins Indians \n", - "5 4be9f735-a98e-4689-87ce-852cc3a1e79d 2016 Blue Jays Orioles \n", - "6 0b2de8c3-11d9-4f0f-a186-25b59f34a5d8 2016 Yankees Mets \n", - "7 60d80663-6ced-44aa-aad9-0f4bf8d3b4d2 2016 Red Sox Rays \n", - "8 7e1c2095-4fea-454c-8773-096ceb6fb05c 2016 Cardinals Pirates \n", - "9 f7f24ce3-7f9d-4e8a-986e-095db847c4c1 2016 Rays Twins \n", - "10 5c26e7fc-c99f-48b4-92c1-4a7208c8cfe9 2016 Rays Twins \n", - "11 6d2cab13-dd85-477a-8769-669069f85836 2016 Royals Rays \n", - "12 bca90342-7ddc-468e-b189-d43fad7528ec 2016 Astros Rays \n", - "13 630f4f78-03cc-43c1-9e57-ababb9c11418 2016 Dodgers Giants \n", - "14 c0cf1376-1115-4a2f-b457-3f82bbc41a89 2016 Tigers White Sox \n", - "15 46463c50-0f5c-4dca-a661-dd194464e791 2016 Cardinals Cubs \n", - "16 392ad56d-972e-4f77-98e2-5f8577931cf8 2016 Giants Cardinals \n", - "17 307730fa-bbed-4221-b4e6-a2492f546fd5 2016 Red Sox Twins \n", - "18 1cbc558f-7615-4fa9-bf97-7ccd62040d6f 2016 Mets Braves \n", - "19 723348ba-1645-43fc-9e22-92994f7a63bd 2016 Athletics Twins \n", - "20 ffbd6ecc-82e1-4e5d-9bd1-4ea210be5992 2016 Twins Marlins \n", - "21 f2747230-7df5-4535-a475-a1c823d0d654 2016 Twins Yankees \n", - "22 db3b6f35-a7a4-430a-8703-2b2f25103e17 2016 White Sox Orioles \n", - "23 5fc8c6f0-a70e-4d1b-877f-eb1ec8e6f636 2016 Diamondbacks Giants \n", - "24 95d548b6-2da8-4644-812e-b277fec5b91f 2016 Braves Mets \n", + "0 e14b6493-9e7f-404f-840a-8a680cc364bf 2016 Marlins Cubs \n", + "1 1f32b347-cbcb-4c31-a145-0e685306d168 2016 Marlins Cubs \n", + "2 0c2292d1-7398-48be-bf8e-b41dad5e1a43 2016 Braves Cubs \n", + "3 8fbec734-a15a-42ab-8d51-60790de7750b 2016 Braves Cubs \n", + "4 89e514d5-fbf5-4b9d-bdac-6ca45bfd18dd 2016 Phillies Cubs \n", + "5 6a83e76c-dc0d-4924-9d3d-a2e7e0ab5b52 2016 Diamondbacks Cubs \n", + "6 76ea8662-c7e6-4c38-8f2a-efe373e428ce 2016 Athletics Cubs \n", + "7 66fad23d-6e89-4f99-be29-d49b6e94f95d 2016 Athletics Cubs \n", + "8 d977367c-cf0c-4687-95a0-eb4542efcb01 2016 Rockies Cubs \n", + "9 a87070ff-1084-43ca-a7ba-69278f93ecba 2016 Cardinals Cubs \n", + "10 ea6b350d-3c1d-4737-878d-4465f66999f6 2016 Cardinals Cubs \n", + "11 46463c50-0f5c-4dca-a661-dd194464e791 2016 Cardinals Cubs \n", + "12 59134e6d-9d13-49aa-978e-c3c2300eb90f 2016 Pirates Cubs \n", + "13 387630a3-a894-4327-baa1-b24ec1a654d9 2016 Pirates Cubs \n", + "14 5d084e13-94fd-4995-b95a-4801ea3ed556 2016 Giants Cubs \n", + "15 34444c94-03ec-4d12-96af-68b8f399a22f 2016 Reds Cubs \n", + "16 9580bffe-22e1-4975-978b-1b13e7505193 2016 Reds Cubs \n", + "17 645e6a08-afd6-4677-a5c9-01ef446b0cf3 2016 Reds Cubs \n", + "18 08981bd8-d1d7-48e1-8668-9098b8f7fe90 2016 Reds Cubs \n", + "19 303703bb-b55f-476d-8faf-bf582169fb1d 2016 Padres Cubs \n", + "20 71ab82a4-6e07-430a-b695-1af3bc42ea61 2016 Nationals Cubs \n", + "21 d1a110c2-f6c8-4029-bcd8-2f8a01e1561c 2016 Brewers Cubs \n", + "22 6d111b57-fa0b-4f24-82df-ff33a26f0252 2016 Brewers Cubs \n", + "23 a97e9539-bbbd-4e03-bf15-f25ea2c1d923 2016 Brewers Cubs \n", + "24 dc0c9218-505c-4725-8c0c-40b72cca0956 2016 Astros Cubs \n", "\n", " duration_minutes \n", - "0 167 \n", - "1 172 \n", - "2 166 \n", - "3 182 \n", - "4 204 \n", - "5 184 \n", - "6 182 \n", - "7 191 \n", - "8 201 \n", - "9 189 \n", - "10 177 \n", - "11 183 \n", - "12 194 \n", - "13 178 \n", - "14 193 \n", - "15 160 \n", - "16 169 \n", - "17 251 \n", - "18 151 \n", - "19 153 \n", - "20 185 \n", - "21 180 \n", - "22 199 \n", - "23 175 \n", - "24 201 \n", + "0 187 \n", + "1 189 \n", + "2 165 \n", + "3 222 \n", + "4 164 \n", + "5 201 \n", + "6 173 \n", + "7 176 \n", + "8 180 \n", + "9 157 \n", + "10 218 \n", + "11 160 \n", + "12 178 \n", + "13 205 \n", + "14 197 \n", + "15 198 \n", + "16 188 \n", + "17 188 \n", + "18 194 \n", + "19 175 \n", + "20 257 \n", + "21 178 \n", + "22 171 \n", + "23 248 \n", + "24 174 \n", "...\n", "\n", "[2431 rows x 5 columns]" ] }, - "execution_count": 3, + "execution_count": 4, "metadata": {}, "output_type": "execute_result" } @@ -397,7 +423,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 5, "id": "a6b8b3ac-1df8-46ff-ac4f-d6e7657fc80c", "metadata": {}, "outputs": [ @@ -407,7 +433,7 @@ "(2431, 5)" ] }, - "execution_count": 4, + "execution_count": 5, "metadata": {}, "output_type": "execute_result" } @@ -427,7 +453,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 6, "id": "34457cc7-e734-4e3f-9f2b-34cdd4e2aba4", "metadata": { "tags": [] @@ -444,7 +470,7 @@ "dtype: object" ] }, - "execution_count": 5, + "execution_count": 6, "metadata": {}, "output_type": "execute_result" } @@ -455,7 +481,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 7, "id": "b4f4383f-f596-41d8-aad2-2fd68d261cfd", "metadata": {}, "outputs": [ @@ -465,7 +491,7 @@ "Index(['gameId', 'year', 'homeTeamName', 'awayTeamName', 'duration_minutes'], dtype='object')" ] }, - "execution_count": 6, + "execution_count": 7, "metadata": {}, "output_type": "execute_result" } @@ -485,7 +511,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 8, "id": "c7017f3d-869d-42e3-bbd8-b3fbc408c2d0", "metadata": { "tags": [] @@ -493,23 +519,13 @@ "outputs": [ { "data": { - "text/html": [ - "Query job e8a94ab7-7833-43ac-bf14-bfd4310260b9 is DONE. 582.8 kB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job 8b1e4a6c-9f93-4588-9c34-ae324a42fd57 is DONE. 0 Bytes processed. Open Job" - ], + "application/vnd.jupyter.widget-view+json": { + "model_id": "653525aaa4394009ae97f54ba868dcf8", + "version_major": 2, + "version_minor": 0 + }, "text/plain": [ - "" + "HTML(value='Query job c4b8deed-0c47-4ce7-b013-d8b24997851a is DONE. 0 Bytes processed. Open Job" - ], + "application/vnd.jupyter.widget-view+json": { + "model_id": "298f5e9b7b094a4992ae304aba43a479", + "version_major": 2, + "version_minor": 0 + }, "text/plain": [ - "" + "HTML(value='Query job e6830838-99ae-4162-a47f-2185bb9c1f27 is DONE. 193.8 kB processed. \n", " \n", " 0\n", - " d60c6036-0ce1-4c90-8dd9-de3b403c92a8\n", + " e14b6493-9e7f-404f-840a-8a680cc364bf\n", " 2016\n", - " Nationals\n", - " Brewers\n", - " 167\n", - " Nationals vs Brewers\n", + " Marlins\n", + " Cubs\n", + " 187\n", + " Marlins vs Cubs\n", " \n", " \n", " 1\n", - " af72a0b9-65f7-49fb-9b30-d505068bdf6d\n", + " 1f32b347-cbcb-4c31-a145-0e685306d168\n", " 2016\n", - " Reds\n", - " Brewers\n", - " 172\n", - " Reds vs Brewers\n", + " Marlins\n", + " Cubs\n", + " 189\n", + " Marlins vs Cubs\n", " \n", " \n", " 2\n", - " f57e1271-d217-400a-aea6-2e2d7d6a59a0\n", + " 0c2292d1-7398-48be-bf8e-b41dad5e1a43\n", " 2016\n", - " Orioles\n", - " Rays\n", - " 166\n", - " Orioles vs Rays\n", + " Braves\n", + " Cubs\n", + " 165\n", + " Braves vs Cubs\n", " \n", " \n", " 3\n", - " 198f4eed-a29f-41e2-8623-cb261e5ab370\n", + " 8fbec734-a15a-42ab-8d51-60790de7750b\n", " 2016\n", - " Rockies\n", - " Giants\n", - " 182\n", - " Rockies vs Giants\n", + " Braves\n", + " Cubs\n", + " 222\n", + " Braves vs Cubs\n", " \n", " \n", " 4\n", - " cb3ef033-dd57-41fd-b206-cdd3bc12c74f\n", + " 89e514d5-fbf5-4b9d-bdac-6ca45bfd18dd\n", " 2016\n", - " Twins\n", - " Indians\n", - " 204\n", - " Twins vs Indians\n", + " Phillies\n", + " Cubs\n", + " 164\n", + " Phillies vs Cubs\n", " \n", " \n", " 5\n", - " 4be9f735-a98e-4689-87ce-852cc3a1e79d\n", + " 6a83e76c-dc0d-4924-9d3d-a2e7e0ab5b52\n", " 2016\n", - " Blue Jays\n", - " Orioles\n", - " 184\n", - " Blue Jays vs Orioles\n", + " Diamondbacks\n", + " Cubs\n", + " 201\n", + " Diamondbacks vs Cubs\n", " \n", " \n", " 6\n", - " 0b2de8c3-11d9-4f0f-a186-25b59f34a5d8\n", + " 76ea8662-c7e6-4c38-8f2a-efe373e428ce\n", " 2016\n", - " Yankees\n", - " Mets\n", - " 182\n", - " Yankees vs Mets\n", + " Athletics\n", + " Cubs\n", + " 173\n", + " Athletics vs Cubs\n", " \n", " \n", " 7\n", - " 60d80663-6ced-44aa-aad9-0f4bf8d3b4d2\n", + " 66fad23d-6e89-4f99-be29-d49b6e94f95d\n", " 2016\n", - " Red Sox\n", - " Rays\n", - " 191\n", - " Red Sox vs Rays\n", + " Athletics\n", + " Cubs\n", + " 176\n", + " Athletics vs Cubs\n", " \n", " \n", " 8\n", - " 7e1c2095-4fea-454c-8773-096ceb6fb05c\n", + " d977367c-cf0c-4687-95a0-eb4542efcb01\n", " 2016\n", - " Cardinals\n", - " Pirates\n", - " 201\n", - " Cardinals vs Pirates\n", + " Rockies\n", + " Cubs\n", + " 180\n", + " Rockies vs Cubs\n", " \n", " \n", " 9\n", - " f7f24ce3-7f9d-4e8a-986e-095db847c4c1\n", + " a87070ff-1084-43ca-a7ba-69278f93ecba\n", " 2016\n", - " Rays\n", - " Twins\n", - " 189\n", - " Rays vs Twins\n", + " Cardinals\n", + " Cubs\n", + " 157\n", + " Cardinals vs Cubs\n", " \n", " \n", " 10\n", - " 5c26e7fc-c99f-48b4-92c1-4a7208c8cfe9\n", + " ea6b350d-3c1d-4737-878d-4465f66999f6\n", " 2016\n", - " Rays\n", - " Twins\n", - " 177\n", - " Rays vs Twins\n", + " Cardinals\n", + " Cubs\n", + " 218\n", + " Cardinals vs Cubs\n", " \n", " \n", " 11\n", - " 6d2cab13-dd85-477a-8769-669069f85836\n", + " 46463c50-0f5c-4dca-a661-dd194464e791\n", " 2016\n", - " Royals\n", - " Rays\n", - " 183\n", - " Royals vs Rays\n", + " Cardinals\n", + " Cubs\n", + " 160\n", + " Cardinals vs Cubs\n", " \n", " \n", " 12\n", - " bca90342-7ddc-468e-b189-d43fad7528ec\n", + " 59134e6d-9d13-49aa-978e-c3c2300eb90f\n", " 2016\n", - " Astros\n", - " Rays\n", - " 194\n", - " Astros vs Rays\n", + " Pirates\n", + " Cubs\n", + " 178\n", + " Pirates vs Cubs\n", " \n", " \n", " 13\n", - " 630f4f78-03cc-43c1-9e57-ababb9c11418\n", + " 387630a3-a894-4327-baa1-b24ec1a654d9\n", " 2016\n", - " Dodgers\n", - " Giants\n", - " 178\n", - " Dodgers vs Giants\n", + " Pirates\n", + " Cubs\n", + " 205\n", + " Pirates vs Cubs\n", " \n", " \n", " 14\n", - " c0cf1376-1115-4a2f-b457-3f82bbc41a89\n", + " 5d084e13-94fd-4995-b95a-4801ea3ed556\n", " 2016\n", - " Tigers\n", - " White Sox\n", - " 193\n", - " Tigers vs White Sox\n", + " Giants\n", + " Cubs\n", + " 197\n", + " Giants vs Cubs\n", " \n", " \n", " 15\n", - " 46463c50-0f5c-4dca-a661-dd194464e791\n", + " 34444c94-03ec-4d12-96af-68b8f399a22f\n", " 2016\n", - " Cardinals\n", + " Reds\n", " Cubs\n", - " 160\n", - " Cardinals vs Cubs\n", + " 198\n", + " Reds vs Cubs\n", " \n", " \n", " 16\n", - " 392ad56d-972e-4f77-98e2-5f8577931cf8\n", + " 9580bffe-22e1-4975-978b-1b13e7505193\n", " 2016\n", - " Giants\n", - " Cardinals\n", - " 169\n", - " Giants vs Cardinals\n", + " Reds\n", + " Cubs\n", + " 188\n", + " Reds vs Cubs\n", " \n", " \n", " 17\n", - " 307730fa-bbed-4221-b4e6-a2492f546fd5\n", + " 645e6a08-afd6-4677-a5c9-01ef446b0cf3\n", " 2016\n", - " Red Sox\n", - " Twins\n", - " 251\n", - " Red Sox vs Twins\n", + " Reds\n", + " Cubs\n", + " 188\n", + " Reds vs Cubs\n", " \n", " \n", " 18\n", - " 1cbc558f-7615-4fa9-bf97-7ccd62040d6f\n", + " 08981bd8-d1d7-48e1-8668-9098b8f7fe90\n", " 2016\n", - " Mets\n", - " Braves\n", - " 151\n", - " Mets vs Braves\n", + " Reds\n", + " Cubs\n", + " 194\n", + " Reds vs Cubs\n", " \n", " \n", " 19\n", - " 723348ba-1645-43fc-9e22-92994f7a63bd\n", + " 303703bb-b55f-476d-8faf-bf582169fb1d\n", " 2016\n", - " Athletics\n", - " Twins\n", - " 153\n", - " Athletics vs Twins\n", + " Padres\n", + " Cubs\n", + " 175\n", + " Padres vs Cubs\n", " \n", " \n", " 20\n", - " ffbd6ecc-82e1-4e5d-9bd1-4ea210be5992\n", + " 71ab82a4-6e07-430a-b695-1af3bc42ea61\n", " 2016\n", - " Twins\n", - " Marlins\n", - " 185\n", - " Twins vs Marlins\n", + " Nationals\n", + " Cubs\n", + " 257\n", + " Nationals vs Cubs\n", " \n", " \n", " 21\n", - " f2747230-7df5-4535-a475-a1c823d0d654\n", + " d1a110c2-f6c8-4029-bcd8-2f8a01e1561c\n", " 2016\n", - " Twins\n", - " Yankees\n", - " 180\n", - " Twins vs Yankees\n", + " Brewers\n", + " Cubs\n", + " 178\n", + " Brewers vs Cubs\n", " \n", " \n", " 22\n", - " db3b6f35-a7a4-430a-8703-2b2f25103e17\n", + " 6d111b57-fa0b-4f24-82df-ff33a26f0252\n", " 2016\n", - " White Sox\n", - " Orioles\n", - " 199\n", - " White Sox vs Orioles\n", + " Brewers\n", + " Cubs\n", + " 171\n", + " Brewers vs Cubs\n", " \n", " \n", " 23\n", - " 5fc8c6f0-a70e-4d1b-877f-eb1ec8e6f636\n", + " a97e9539-bbbd-4e03-bf15-f25ea2c1d923\n", " 2016\n", - " Diamondbacks\n", - " Giants\n", - " 175\n", - " Diamondbacks vs Giants\n", + " Brewers\n", + " Cubs\n", + " 248\n", + " Brewers vs Cubs\n", " \n", " \n", " 24\n", - " 95d548b6-2da8-4644-812e-b277fec5b91f\n", + " dc0c9218-505c-4725-8c0c-40b72cca0956\n", " 2016\n", - " Braves\n", - " Mets\n", - " 201\n", - " Braves vs Mets\n", + " Astros\n", + " Cubs\n", + " 174\n", + " Astros vs Cubs\n", " \n", " \n", "\n", @@ -789,64 +807,64 @@ ], "text/plain": [ " gameId year homeTeamName awayTeamName \\\n", - "0 d60c6036-0ce1-4c90-8dd9-de3b403c92a8 2016 Nationals Brewers \n", - "1 af72a0b9-65f7-49fb-9b30-d505068bdf6d 2016 Reds Brewers \n", - "2 f57e1271-d217-400a-aea6-2e2d7d6a59a0 2016 Orioles Rays \n", - "3 198f4eed-a29f-41e2-8623-cb261e5ab370 2016 Rockies Giants \n", - "4 cb3ef033-dd57-41fd-b206-cdd3bc12c74f 2016 Twins Indians \n", - "5 4be9f735-a98e-4689-87ce-852cc3a1e79d 2016 Blue Jays Orioles \n", - "6 0b2de8c3-11d9-4f0f-a186-25b59f34a5d8 2016 Yankees Mets \n", - "7 60d80663-6ced-44aa-aad9-0f4bf8d3b4d2 2016 Red Sox Rays \n", - "8 7e1c2095-4fea-454c-8773-096ceb6fb05c 2016 Cardinals Pirates \n", - "9 f7f24ce3-7f9d-4e8a-986e-095db847c4c1 2016 Rays Twins \n", - "10 5c26e7fc-c99f-48b4-92c1-4a7208c8cfe9 2016 Rays Twins \n", - "11 6d2cab13-dd85-477a-8769-669069f85836 2016 Royals Rays \n", - "12 bca90342-7ddc-468e-b189-d43fad7528ec 2016 Astros Rays \n", - "13 630f4f78-03cc-43c1-9e57-ababb9c11418 2016 Dodgers Giants \n", - "14 c0cf1376-1115-4a2f-b457-3f82bbc41a89 2016 Tigers White Sox \n", - "15 46463c50-0f5c-4dca-a661-dd194464e791 2016 Cardinals Cubs \n", - "16 392ad56d-972e-4f77-98e2-5f8577931cf8 2016 Giants Cardinals \n", - "17 307730fa-bbed-4221-b4e6-a2492f546fd5 2016 Red Sox Twins \n", - "18 1cbc558f-7615-4fa9-bf97-7ccd62040d6f 2016 Mets Braves \n", - "19 723348ba-1645-43fc-9e22-92994f7a63bd 2016 Athletics Twins \n", - "20 ffbd6ecc-82e1-4e5d-9bd1-4ea210be5992 2016 Twins Marlins \n", - "21 f2747230-7df5-4535-a475-a1c823d0d654 2016 Twins Yankees \n", - "22 db3b6f35-a7a4-430a-8703-2b2f25103e17 2016 White Sox Orioles \n", - "23 5fc8c6f0-a70e-4d1b-877f-eb1ec8e6f636 2016 Diamondbacks Giants \n", - "24 95d548b6-2da8-4644-812e-b277fec5b91f 2016 Braves Mets \n", + "0 e14b6493-9e7f-404f-840a-8a680cc364bf 2016 Marlins Cubs \n", + "1 1f32b347-cbcb-4c31-a145-0e685306d168 2016 Marlins Cubs \n", + "2 0c2292d1-7398-48be-bf8e-b41dad5e1a43 2016 Braves Cubs \n", + "3 8fbec734-a15a-42ab-8d51-60790de7750b 2016 Braves Cubs \n", + "4 89e514d5-fbf5-4b9d-bdac-6ca45bfd18dd 2016 Phillies Cubs \n", + "5 6a83e76c-dc0d-4924-9d3d-a2e7e0ab5b52 2016 Diamondbacks Cubs \n", + "6 76ea8662-c7e6-4c38-8f2a-efe373e428ce 2016 Athletics Cubs \n", + "7 66fad23d-6e89-4f99-be29-d49b6e94f95d 2016 Athletics Cubs \n", + "8 d977367c-cf0c-4687-95a0-eb4542efcb01 2016 Rockies Cubs \n", + "9 a87070ff-1084-43ca-a7ba-69278f93ecba 2016 Cardinals Cubs \n", + "10 ea6b350d-3c1d-4737-878d-4465f66999f6 2016 Cardinals Cubs \n", + "11 46463c50-0f5c-4dca-a661-dd194464e791 2016 Cardinals Cubs \n", + "12 59134e6d-9d13-49aa-978e-c3c2300eb90f 2016 Pirates Cubs \n", + "13 387630a3-a894-4327-baa1-b24ec1a654d9 2016 Pirates Cubs \n", + "14 5d084e13-94fd-4995-b95a-4801ea3ed556 2016 Giants Cubs \n", + "15 34444c94-03ec-4d12-96af-68b8f399a22f 2016 Reds Cubs \n", + "16 9580bffe-22e1-4975-978b-1b13e7505193 2016 Reds Cubs \n", + "17 645e6a08-afd6-4677-a5c9-01ef446b0cf3 2016 Reds Cubs \n", + "18 08981bd8-d1d7-48e1-8668-9098b8f7fe90 2016 Reds Cubs \n", + "19 303703bb-b55f-476d-8faf-bf582169fb1d 2016 Padres Cubs \n", + "20 71ab82a4-6e07-430a-b695-1af3bc42ea61 2016 Nationals Cubs \n", + "21 d1a110c2-f6c8-4029-bcd8-2f8a01e1561c 2016 Brewers Cubs \n", + "22 6d111b57-fa0b-4f24-82df-ff33a26f0252 2016 Brewers Cubs \n", + "23 a97e9539-bbbd-4e03-bf15-f25ea2c1d923 2016 Brewers Cubs \n", + "24 dc0c9218-505c-4725-8c0c-40b72cca0956 2016 Astros Cubs \n", "\n", - " duration_minutes title \n", - "0 167 Nationals vs Brewers \n", - "1 172 Reds vs Brewers \n", - "2 166 Orioles vs Rays \n", - "3 182 Rockies vs Giants \n", - "4 204 Twins vs Indians \n", - "5 184 Blue Jays vs Orioles \n", - "6 182 Yankees vs Mets \n", - "7 191 Red Sox vs Rays \n", - "8 201 Cardinals vs Pirates \n", - "9 189 Rays vs Twins \n", - "10 177 Rays vs Twins \n", - "11 183 Royals vs Rays \n", - "12 194 Astros vs Rays \n", - "13 178 Dodgers vs Giants \n", - "14 193 Tigers vs White Sox \n", - "15 160 Cardinals vs Cubs \n", - "16 169 Giants vs Cardinals \n", - "17 251 Red Sox vs Twins \n", - "18 151 Mets vs Braves \n", - "19 153 Athletics vs Twins \n", - "20 185 Twins vs Marlins \n", - "21 180 Twins vs Yankees \n", - "22 199 White Sox vs Orioles \n", - "23 175 Diamondbacks vs Giants \n", - "24 201 Braves vs Mets \n", + " duration_minutes title \n", + "0 187 Marlins vs Cubs \n", + "1 189 Marlins vs Cubs \n", + "2 165 Braves vs Cubs \n", + "3 222 Braves vs Cubs \n", + "4 164 Phillies vs Cubs \n", + "5 201 Diamondbacks vs Cubs \n", + "6 173 Athletics vs Cubs \n", + "7 176 Athletics vs Cubs \n", + "8 180 Rockies vs Cubs \n", + "9 157 Cardinals vs Cubs \n", + "10 218 Cardinals vs Cubs \n", + "11 160 Cardinals vs Cubs \n", + "12 178 Pirates vs Cubs \n", + "13 205 Pirates vs Cubs \n", + "14 197 Giants vs Cubs \n", + "15 198 Reds vs Cubs \n", + "16 188 Reds vs Cubs \n", + "17 188 Reds vs Cubs \n", + "18 194 Reds vs Cubs \n", + "19 175 Padres vs Cubs \n", + "20 257 Nationals vs Cubs \n", + "21 178 Brewers vs Cubs \n", + "22 171 Brewers vs Cubs \n", + "23 248 Brewers vs Cubs \n", + "24 174 Astros vs Cubs \n", "...\n", "\n", "[2431 rows x 6 columns]" ] }, - "execution_count": 7, + "execution_count": 8, "metadata": {}, "output_type": "execute_result" } @@ -867,17 +885,19 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 9, "id": "8bbe000a-36f0-4b6f-b403-b9ec28dd608b", "metadata": {}, "outputs": [ { "data": { - "text/html": [ - "Query job ef76c434-c4bc-4b4c-bb06-61521fc85b15 is DONE. 0 Bytes processed. Open Job" - ], + "application/vnd.jupyter.widget-view+json": { + "model_id": "ea2f330fcba44a8ca8c9919641e6a881", + "version_major": 2, + "version_minor": 0 + }, "text/plain": [ - "" + "HTML(value='Query job 2062db30-30ae-42cf-8afa-9b8f3493fd98 is DONE. 0 Bytes processed. Open Job" - ], + "application/vnd.jupyter.widget-view+json": { + "model_id": "3f6c6bb6171c40129d023e08d73a75ad", + "version_major": 2, + "version_minor": 0 + }, "text/plain": [ - "" + "HTML(value='Query job 9c36c84f-e672-46e1-a134-7ef2c2e60b4e is DONE. 0 Bytes processed. \n", " \n", " 0\n", - " d60c6036-0ce1-4c90-8dd9-de3b403c92a8\n", + " e14b6493-9e7f-404f-840a-8a680cc364bf\n", " 2016\n", - " Nationals\n", - " Brewers\n", - " 167\n", - " Nationals vs Brewers\n", + " Marlins\n", + " Cubs\n", + " 187\n", + " Marlins vs Cubs\n", " \n", " \n", " 1\n", - " af72a0b9-65f7-49fb-9b30-d505068bdf6d\n", + " 1f32b347-cbcb-4c31-a145-0e685306d168\n", " 2016\n", - " Reds\n", - " Brewers\n", - " 172\n", - " Reds vs Brewers\n", + " Marlins\n", + " Cubs\n", + " 189\n", + " Marlins vs Cubs\n", " \n", " \n", " 2\n", - " f57e1271-d217-400a-aea6-2e2d7d6a59a0\n", + " 0c2292d1-7398-48be-bf8e-b41dad5e1a43\n", " 2016\n", - " Orioles\n", - " Rays\n", - " 166\n", - " Orioles vs Rays\n", + " Braves\n", + " Cubs\n", + " 165\n", + " Braves vs Cubs\n", " \n", " \n", " 3\n", - " 198f4eed-a29f-41e2-8623-cb261e5ab370\n", + " 8fbec734-a15a-42ab-8d51-60790de7750b\n", " 2016\n", - " Rockies\n", - " Giants\n", - " 182\n", - " Rockies vs Giants\n", + " Braves\n", + " Cubs\n", + " 222\n", + " Braves vs Cubs\n", " \n", " \n", " 4\n", - " cb3ef033-dd57-41fd-b206-cdd3bc12c74f\n", + " 89e514d5-fbf5-4b9d-bdac-6ca45bfd18dd\n", " 2016\n", - " Twins\n", - " Indians\n", - " 204\n", - " Twins vs Indians\n", + " Phillies\n", + " Cubs\n", + " 164\n", + " Phillies vs Cubs\n", " \n", " \n", " 5\n", - " 4be9f735-a98e-4689-87ce-852cc3a1e79d\n", + " 6a83e76c-dc0d-4924-9d3d-a2e7e0ab5b52\n", " 2016\n", - " Blue Jays\n", - " Orioles\n", - " 184\n", - " Blue Jays vs Orioles\n", + " Diamondbacks\n", + " Cubs\n", + " 201\n", + " Diamondbacks vs Cubs\n", " \n", " \n", " 6\n", - " 0b2de8c3-11d9-4f0f-a186-25b59f34a5d8\n", + " 76ea8662-c7e6-4c38-8f2a-efe373e428ce\n", " 2016\n", - " Yankees\n", - " Mets\n", - " 182\n", - " Yankees vs Mets\n", + " Athletics\n", + " Cubs\n", + " 173\n", + " Athletics vs Cubs\n", " \n", " \n", " 7\n", - " 60d80663-6ced-44aa-aad9-0f4bf8d3b4d2\n", + " 66fad23d-6e89-4f99-be29-d49b6e94f95d\n", " 2016\n", - " Red Sox\n", - " Rays\n", - " 191\n", - " Red Sox vs Rays\n", + " Athletics\n", + " Cubs\n", + " 176\n", + " Athletics vs Cubs\n", " \n", " \n", " 8\n", - " 7e1c2095-4fea-454c-8773-096ceb6fb05c\n", + " d977367c-cf0c-4687-95a0-eb4542efcb01\n", " 2016\n", - " Cardinals\n", - " Pirates\n", - " 201\n", - " Cardinals vs Pirates\n", + " Rockies\n", + " Cubs\n", + " 180\n", + " Rockies vs Cubs\n", " \n", " \n", " 9\n", - " f7f24ce3-7f9d-4e8a-986e-095db847c4c1\n", + " a87070ff-1084-43ca-a7ba-69278f93ecba\n", " 2016\n", - " Rays\n", - " Twins\n", - " 189\n", - " Rays vs Twins\n", + " Cardinals\n", + " Cubs\n", + " 157\n", + " Cardinals vs Cubs\n", " \n", " \n", " 10\n", - " 5c26e7fc-c99f-48b4-92c1-4a7208c8cfe9\n", + " ea6b350d-3c1d-4737-878d-4465f66999f6\n", " 2016\n", - " Rays\n", - " Twins\n", - " 177\n", - " Rays vs Twins\n", + " Cardinals\n", + " Cubs\n", + " 218\n", + " Cardinals vs Cubs\n", " \n", " \n", " 11\n", - " 6d2cab13-dd85-477a-8769-669069f85836\n", + " 46463c50-0f5c-4dca-a661-dd194464e791\n", " 2016\n", - " Royals\n", - " Rays\n", - " 183\n", - " Royals vs Rays\n", + " Cardinals\n", + " Cubs\n", + " 160\n", + " Cardinals vs Cubs\n", " \n", " \n", " 12\n", - " bca90342-7ddc-468e-b189-d43fad7528ec\n", + " 59134e6d-9d13-49aa-978e-c3c2300eb90f\n", " 2016\n", - " Astros\n", - " Rays\n", - " 194\n", - " Astros vs Rays\n", + " Pirates\n", + " Cubs\n", + " 178\n", + " Pirates vs Cubs\n", " \n", " \n", " 13\n", - " 630f4f78-03cc-43c1-9e57-ababb9c11418\n", + " 387630a3-a894-4327-baa1-b24ec1a654d9\n", " 2016\n", - " Dodgers\n", - " Giants\n", - " 178\n", - " Dodgers vs Giants\n", + " Pirates\n", + " Cubs\n", + " 205\n", + " Pirates vs Cubs\n", " \n", " \n", " 14\n", - " c0cf1376-1115-4a2f-b457-3f82bbc41a89\n", + " 5d084e13-94fd-4995-b95a-4801ea3ed556\n", " 2016\n", - " Tigers\n", - " White Sox\n", - " 193\n", - " Tigers vs White Sox\n", + " Giants\n", + " Cubs\n", + " 197\n", + " Giants vs Cubs\n", " \n", " \n", " 15\n", - " 46463c50-0f5c-4dca-a661-dd194464e791\n", + " 34444c94-03ec-4d12-96af-68b8f399a22f\n", " 2016\n", - " Cardinals\n", + " Reds\n", " Cubs\n", - " 160\n", - " Cardinals vs Cubs\n", + " 198\n", + " Reds vs Cubs\n", " \n", " \n", " 16\n", - " 392ad56d-972e-4f77-98e2-5f8577931cf8\n", + " 9580bffe-22e1-4975-978b-1b13e7505193\n", " 2016\n", - " Giants\n", - " Cardinals\n", - " 169\n", - " Giants vs Cardinals\n", + " Reds\n", + " Cubs\n", + " 188\n", + " Reds vs Cubs\n", " \n", " \n", " 17\n", - " 307730fa-bbed-4221-b4e6-a2492f546fd5\n", + " 645e6a08-afd6-4677-a5c9-01ef446b0cf3\n", " 2016\n", - " Red Sox\n", - " Twins\n", - " 251\n", - " Red Sox vs Twins\n", + " Reds\n", + " Cubs\n", + " 188\n", + " Reds vs Cubs\n", " \n", " \n", " 18\n", - " 1cbc558f-7615-4fa9-bf97-7ccd62040d6f\n", + " 08981bd8-d1d7-48e1-8668-9098b8f7fe90\n", " 2016\n", - " Mets\n", - " Braves\n", - " 151\n", - " Mets vs Braves\n", + " Reds\n", + " Cubs\n", + " 194\n", + " Reds vs Cubs\n", " \n", " \n", " 19\n", - " 723348ba-1645-43fc-9e22-92994f7a63bd\n", + " 303703bb-b55f-476d-8faf-bf582169fb1d\n", " 2016\n", - " Athletics\n", - " Twins\n", - " 153\n", - " Athletics vs Twins\n", + " Padres\n", + " Cubs\n", + " 175\n", + " Padres vs Cubs\n", " \n", " \n", " 20\n", - " ffbd6ecc-82e1-4e5d-9bd1-4ea210be5992\n", + " 71ab82a4-6e07-430a-b695-1af3bc42ea61\n", " 2016\n", - " Twins\n", - " Marlins\n", - " 185\n", - " Twins vs Marlins\n", + " Nationals\n", + " Cubs\n", + " 257\n", + " Nationals vs Cubs\n", " \n", " \n", " 21\n", - " f2747230-7df5-4535-a475-a1c823d0d654\n", + " d1a110c2-f6c8-4029-bcd8-2f8a01e1561c\n", " 2016\n", - " Twins\n", - " Yankees\n", - " 180\n", - " Twins vs Yankees\n", + " Brewers\n", + " Cubs\n", + " 178\n", + " Brewers vs Cubs\n", " \n", " \n", " 22\n", - " db3b6f35-a7a4-430a-8703-2b2f25103e17\n", + " 6d111b57-fa0b-4f24-82df-ff33a26f0252\n", " 2016\n", - " White Sox\n", - " Orioles\n", - " 199\n", - " White Sox vs Orioles\n", + " Brewers\n", + " Cubs\n", + " 171\n", + " Brewers vs Cubs\n", " \n", " \n", " 23\n", - " 5fc8c6f0-a70e-4d1b-877f-eb1ec8e6f636\n", + " a97e9539-bbbd-4e03-bf15-f25ea2c1d923\n", " 2016\n", - " Diamondbacks\n", - " Giants\n", - " 175\n", - " Diamondbacks vs Giants\n", + " Brewers\n", + " Cubs\n", + " 248\n", + " Brewers vs Cubs\n", " \n", " \n", " 24\n", - " 95d548b6-2da8-4644-812e-b277fec5b91f\n", + " dc0c9218-505c-4725-8c0c-40b72cca0956\n", " 2016\n", - " Braves\n", - " Mets\n", - " 201\n", - " Braves vs Mets\n", + " Astros\n", + " Cubs\n", + " 174\n", + " Astros vs Cubs\n", " \n", " \n", "\n", @@ -1157,64 +1179,64 @@ ], "text/plain": [ " gameId year homeTeamName awayTeamName \\\n", - "0 d60c6036-0ce1-4c90-8dd9-de3b403c92a8 2016 Nationals Brewers \n", - "1 af72a0b9-65f7-49fb-9b30-d505068bdf6d 2016 Reds Brewers \n", - "2 f57e1271-d217-400a-aea6-2e2d7d6a59a0 2016 Orioles Rays \n", - "3 198f4eed-a29f-41e2-8623-cb261e5ab370 2016 Rockies Giants \n", - "4 cb3ef033-dd57-41fd-b206-cdd3bc12c74f 2016 Twins Indians \n", - "5 4be9f735-a98e-4689-87ce-852cc3a1e79d 2016 Blue Jays Orioles \n", - "6 0b2de8c3-11d9-4f0f-a186-25b59f34a5d8 2016 Yankees Mets \n", - "7 60d80663-6ced-44aa-aad9-0f4bf8d3b4d2 2016 Red Sox Rays \n", - "8 7e1c2095-4fea-454c-8773-096ceb6fb05c 2016 Cardinals Pirates \n", - "9 f7f24ce3-7f9d-4e8a-986e-095db847c4c1 2016 Rays Twins \n", - "10 5c26e7fc-c99f-48b4-92c1-4a7208c8cfe9 2016 Rays Twins \n", - "11 6d2cab13-dd85-477a-8769-669069f85836 2016 Royals Rays \n", - "12 bca90342-7ddc-468e-b189-d43fad7528ec 2016 Astros Rays \n", - "13 630f4f78-03cc-43c1-9e57-ababb9c11418 2016 Dodgers Giants \n", - "14 c0cf1376-1115-4a2f-b457-3f82bbc41a89 2016 Tigers White Sox \n", - "15 46463c50-0f5c-4dca-a661-dd194464e791 2016 Cardinals Cubs \n", - "16 392ad56d-972e-4f77-98e2-5f8577931cf8 2016 Giants Cardinals \n", - "17 307730fa-bbed-4221-b4e6-a2492f546fd5 2016 Red Sox Twins \n", - "18 1cbc558f-7615-4fa9-bf97-7ccd62040d6f 2016 Mets Braves \n", - "19 723348ba-1645-43fc-9e22-92994f7a63bd 2016 Athletics Twins \n", - "20 ffbd6ecc-82e1-4e5d-9bd1-4ea210be5992 2016 Twins Marlins \n", - "21 f2747230-7df5-4535-a475-a1c823d0d654 2016 Twins Yankees \n", - "22 db3b6f35-a7a4-430a-8703-2b2f25103e17 2016 White Sox Orioles \n", - "23 5fc8c6f0-a70e-4d1b-877f-eb1ec8e6f636 2016 Diamondbacks Giants \n", - "24 95d548b6-2da8-4644-812e-b277fec5b91f 2016 Braves Mets \n", + "0 e14b6493-9e7f-404f-840a-8a680cc364bf 2016 Marlins Cubs \n", + "1 1f32b347-cbcb-4c31-a145-0e685306d168 2016 Marlins Cubs \n", + "2 0c2292d1-7398-48be-bf8e-b41dad5e1a43 2016 Braves Cubs \n", + "3 8fbec734-a15a-42ab-8d51-60790de7750b 2016 Braves Cubs \n", + "4 89e514d5-fbf5-4b9d-bdac-6ca45bfd18dd 2016 Phillies Cubs \n", + "5 6a83e76c-dc0d-4924-9d3d-a2e7e0ab5b52 2016 Diamondbacks Cubs \n", + "6 76ea8662-c7e6-4c38-8f2a-efe373e428ce 2016 Athletics Cubs \n", + "7 66fad23d-6e89-4f99-be29-d49b6e94f95d 2016 Athletics Cubs \n", + "8 d977367c-cf0c-4687-95a0-eb4542efcb01 2016 Rockies Cubs \n", + "9 a87070ff-1084-43ca-a7ba-69278f93ecba 2016 Cardinals Cubs \n", + "10 ea6b350d-3c1d-4737-878d-4465f66999f6 2016 Cardinals Cubs \n", + "11 46463c50-0f5c-4dca-a661-dd194464e791 2016 Cardinals Cubs \n", + "12 59134e6d-9d13-49aa-978e-c3c2300eb90f 2016 Pirates Cubs \n", + "13 387630a3-a894-4327-baa1-b24ec1a654d9 2016 Pirates Cubs \n", + "14 5d084e13-94fd-4995-b95a-4801ea3ed556 2016 Giants Cubs \n", + "15 34444c94-03ec-4d12-96af-68b8f399a22f 2016 Reds Cubs \n", + "16 9580bffe-22e1-4975-978b-1b13e7505193 2016 Reds Cubs \n", + "17 645e6a08-afd6-4677-a5c9-01ef446b0cf3 2016 Reds Cubs \n", + "18 08981bd8-d1d7-48e1-8668-9098b8f7fe90 2016 Reds Cubs \n", + "19 303703bb-b55f-476d-8faf-bf582169fb1d 2016 Padres Cubs \n", + "20 71ab82a4-6e07-430a-b695-1af3bc42ea61 2016 Nationals Cubs \n", + "21 d1a110c2-f6c8-4029-bcd8-2f8a01e1561c 2016 Brewers Cubs \n", + "22 6d111b57-fa0b-4f24-82df-ff33a26f0252 2016 Brewers Cubs \n", + "23 a97e9539-bbbd-4e03-bf15-f25ea2c1d923 2016 Brewers Cubs \n", + "24 dc0c9218-505c-4725-8c0c-40b72cca0956 2016 Astros Cubs \n", "\n", - " duration_minutes headline \n", - "0 167 Nationals vs Brewers \n", - "1 172 Reds vs Brewers \n", - "2 166 Orioles vs Rays \n", - "3 182 Rockies vs Giants \n", - "4 204 Twins vs Indians \n", - "5 184 Blue Jays vs Orioles \n", - "6 182 Yankees vs Mets \n", - "7 191 Red Sox vs Rays \n", - "8 201 Cardinals vs Pirates \n", - "9 189 Rays vs Twins \n", - "10 177 Rays vs Twins \n", - "11 183 Royals vs Rays \n", - "12 194 Astros vs Rays \n", - "13 178 Dodgers vs Giants \n", - "14 193 Tigers vs White Sox \n", - "15 160 Cardinals vs Cubs \n", - "16 169 Giants vs Cardinals \n", - "17 251 Red Sox vs Twins \n", - "18 151 Mets vs Braves \n", - "19 153 Athletics vs Twins \n", - "20 185 Twins vs Marlins \n", - "21 180 Twins vs Yankees \n", - "22 199 White Sox vs Orioles \n", - "23 175 Diamondbacks vs Giants \n", - "24 201 Braves vs Mets \n", + " duration_minutes headline \n", + "0 187 Marlins vs Cubs \n", + "1 189 Marlins vs Cubs \n", + "2 165 Braves vs Cubs \n", + "3 222 Braves vs Cubs \n", + "4 164 Phillies vs Cubs \n", + "5 201 Diamondbacks vs Cubs \n", + "6 173 Athletics vs Cubs \n", + "7 176 Athletics vs Cubs \n", + "8 180 Rockies vs Cubs \n", + "9 157 Cardinals vs Cubs \n", + "10 218 Cardinals vs Cubs \n", + "11 160 Cardinals vs Cubs \n", + "12 178 Pirates vs Cubs \n", + "13 205 Pirates vs Cubs \n", + "14 197 Giants vs Cubs \n", + "15 198 Reds vs Cubs \n", + "16 188 Reds vs Cubs \n", + "17 188 Reds vs Cubs \n", + "18 194 Reds vs Cubs \n", + "19 175 Padres vs Cubs \n", + "20 257 Nationals vs Cubs \n", + "21 178 Brewers vs Cubs \n", + "22 171 Brewers vs Cubs \n", + "23 248 Brewers vs Cubs \n", + "24 174 Astros vs Cubs \n", "...\n", "\n", "[2431 rows x 6 columns]" ] }, - "execution_count": 8, + "execution_count": 9, "metadata": {}, "output_type": "execute_result" } @@ -1226,7 +1248,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 10, "id": "87eee643-28ac-4f4b-ac61-1f3de9c08a9d", "metadata": {}, "outputs": [], @@ -1236,17 +1258,19 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 11, "id": "fad6d3da-1f40-4c5f-94ec-0bdfe21ca5b6", "metadata": {}, "outputs": [ { "data": { - "text/html": [ - "Query job 051b3d23-5ab2-4022-adfc-f6553eb8532d is DONE. 0 Bytes processed. Open Job" - ], + "application/vnd.jupyter.widget-view+json": { + "model_id": "fae17c24b2be4a47a72cc067e7b38e8c", + "version_major": 2, + "version_minor": 0 + }, "text/plain": [ - "" + "HTML(value='Query job f0ed19be-b1f5-4333-a51f-3c7872a2bbc6 is DONE. 0 Bytes processed. Open Job" - ], + "application/vnd.jupyter.widget-view+json": { + "model_id": "5b93b75abff04a36b186e3894bc9e957", + "version_major": 2, + "version_minor": 0 + }, "text/plain": [ - "" + "HTML(value='Query job 863e72d3-b421-4e53-98bb-9bf634fe9a71 is DONE. 193.8 kB processed. \n", " \n", " 0\n", - " d60c6036-0ce1-4c90-8dd9-de3b403c92a8\n", + " e14b6493-9e7f-404f-840a-8a680cc364bf\n", " 2016\n", - " Nationals\n", - " Brewers\n", - " 167\n", + " Marlins\n", + " Cubs\n", + " 187\n", " \n", " \n", " 1\n", - " af72a0b9-65f7-49fb-9b30-d505068bdf6d\n", + " 1f32b347-cbcb-4c31-a145-0e685306d168\n", " 2016\n", - " Reds\n", - " Brewers\n", - " 172\n", + " Marlins\n", + " Cubs\n", + " 189\n", " \n", " \n", " 2\n", - " f57e1271-d217-400a-aea6-2e2d7d6a59a0\n", + " 0c2292d1-7398-48be-bf8e-b41dad5e1a43\n", " 2016\n", - " Orioles\n", - " Rays\n", - " 166\n", + " Braves\n", + " Cubs\n", + " 165\n", " \n", " \n", " 3\n", - " 198f4eed-a29f-41e2-8623-cb261e5ab370\n", + " 8fbec734-a15a-42ab-8d51-60790de7750b\n", " 2016\n", - " Rockies\n", - " Giants\n", - " 182\n", + " Braves\n", + " Cubs\n", + " 222\n", " \n", " \n", " 4\n", - " cb3ef033-dd57-41fd-b206-cdd3bc12c74f\n", + " 89e514d5-fbf5-4b9d-bdac-6ca45bfd18dd\n", " 2016\n", - " Twins\n", - " Indians\n", - " 204\n", + " Phillies\n", + " Cubs\n", + " 164\n", " \n", " \n", " 5\n", - " 4be9f735-a98e-4689-87ce-852cc3a1e79d\n", + " 6a83e76c-dc0d-4924-9d3d-a2e7e0ab5b52\n", " 2016\n", - " Blue Jays\n", - " Orioles\n", - " 184\n", + " Diamondbacks\n", + " Cubs\n", + " 201\n", " \n", " \n", " 6\n", - " 0b2de8c3-11d9-4f0f-a186-25b59f34a5d8\n", + " 76ea8662-c7e6-4c38-8f2a-efe373e428ce\n", " 2016\n", - " Yankees\n", - " Mets\n", - " 182\n", + " Athletics\n", + " Cubs\n", + " 173\n", " \n", " \n", " 7\n", - " 60d80663-6ced-44aa-aad9-0f4bf8d3b4d2\n", + " 66fad23d-6e89-4f99-be29-d49b6e94f95d\n", " 2016\n", - " Red Sox\n", - " Rays\n", - " 191\n", + " Athletics\n", + " Cubs\n", + " 176\n", " \n", " \n", " 8\n", - " 7e1c2095-4fea-454c-8773-096ceb6fb05c\n", + " d977367c-cf0c-4687-95a0-eb4542efcb01\n", " 2016\n", - " Cardinals\n", - " Pirates\n", - " 201\n", + " Rockies\n", + " Cubs\n", + " 180\n", " \n", " \n", " 9\n", - " f7f24ce3-7f9d-4e8a-986e-095db847c4c1\n", + " a87070ff-1084-43ca-a7ba-69278f93ecba\n", " 2016\n", - " Rays\n", - " Twins\n", - " 189\n", + " Cardinals\n", + " Cubs\n", + " 157\n", " \n", " \n", " 10\n", - " 5c26e7fc-c99f-48b4-92c1-4a7208c8cfe9\n", + " ea6b350d-3c1d-4737-878d-4465f66999f6\n", " 2016\n", - " Rays\n", - " Twins\n", - " 177\n", + " Cardinals\n", + " Cubs\n", + " 218\n", " \n", " \n", " 11\n", - " 6d2cab13-dd85-477a-8769-669069f85836\n", + " 46463c50-0f5c-4dca-a661-dd194464e791\n", " 2016\n", - " Royals\n", - " Rays\n", - " 183\n", + " Cardinals\n", + " Cubs\n", + " 160\n", " \n", " \n", " 12\n", - " bca90342-7ddc-468e-b189-d43fad7528ec\n", + " 59134e6d-9d13-49aa-978e-c3c2300eb90f\n", " 2016\n", - " Astros\n", - " Rays\n", - " 194\n", + " Pirates\n", + " Cubs\n", + " 178\n", " \n", " \n", " 13\n", - " 630f4f78-03cc-43c1-9e57-ababb9c11418\n", + " 387630a3-a894-4327-baa1-b24ec1a654d9\n", " 2016\n", - " Dodgers\n", - " Giants\n", - " 178\n", + " Pirates\n", + " Cubs\n", + " 205\n", " \n", " \n", " 14\n", - " c0cf1376-1115-4a2f-b457-3f82bbc41a89\n", + " 5d084e13-94fd-4995-b95a-4801ea3ed556\n", " 2016\n", - " Tigers\n", - " White Sox\n", - " 193\n", + " Giants\n", + " Cubs\n", + " 197\n", " \n", " \n", " 15\n", - " 46463c50-0f5c-4dca-a661-dd194464e791\n", + " 34444c94-03ec-4d12-96af-68b8f399a22f\n", " 2016\n", - " Cardinals\n", + " Reds\n", " Cubs\n", - " 160\n", + " 198\n", " \n", " \n", " 16\n", - " 392ad56d-972e-4f77-98e2-5f8577931cf8\n", + " 9580bffe-22e1-4975-978b-1b13e7505193\n", " 2016\n", - " Giants\n", - " Cardinals\n", - " 169\n", + " Reds\n", + " Cubs\n", + " 188\n", " \n", " \n", " 17\n", - " 307730fa-bbed-4221-b4e6-a2492f546fd5\n", + " 645e6a08-afd6-4677-a5c9-01ef446b0cf3\n", " 2016\n", - " Red Sox\n", - " Twins\n", - " 251\n", + " Reds\n", + " Cubs\n", + " 188\n", " \n", " \n", " 18\n", - " 1cbc558f-7615-4fa9-bf97-7ccd62040d6f\n", + " 08981bd8-d1d7-48e1-8668-9098b8f7fe90\n", " 2016\n", - " Mets\n", - " Braves\n", - " 151\n", + " Reds\n", + " Cubs\n", + " 194\n", " \n", " \n", " 19\n", - " 723348ba-1645-43fc-9e22-92994f7a63bd\n", + " 303703bb-b55f-476d-8faf-bf582169fb1d\n", " 2016\n", - " Athletics\n", - " Twins\n", - " 153\n", + " Padres\n", + " Cubs\n", + " 175\n", " \n", " \n", " 20\n", - " ffbd6ecc-82e1-4e5d-9bd1-4ea210be5992\n", + " 71ab82a4-6e07-430a-b695-1af3bc42ea61\n", " 2016\n", - " Twins\n", - " Marlins\n", - " 185\n", + " Nationals\n", + " Cubs\n", + " 257\n", " \n", " \n", " 21\n", - " f2747230-7df5-4535-a475-a1c823d0d654\n", + " d1a110c2-f6c8-4029-bcd8-2f8a01e1561c\n", " 2016\n", - " Twins\n", - " Yankees\n", - " 180\n", + " Brewers\n", + " Cubs\n", + " 178\n", " \n", " \n", " 22\n", - " db3b6f35-a7a4-430a-8703-2b2f25103e17\n", + " 6d111b57-fa0b-4f24-82df-ff33a26f0252\n", " 2016\n", - " White Sox\n", - " Orioles\n", - " 199\n", + " Brewers\n", + " Cubs\n", + " 171\n", " \n", " \n", " 23\n", - " 5fc8c6f0-a70e-4d1b-877f-eb1ec8e6f636\n", + " a97e9539-bbbd-4e03-bf15-f25ea2c1d923\n", " 2016\n", - " Diamondbacks\n", - " Giants\n", - " 175\n", + " Brewers\n", + " Cubs\n", + " 248\n", " \n", " \n", " 24\n", - " 95d548b6-2da8-4644-812e-b277fec5b91f\n", + " dc0c9218-505c-4725-8c0c-40b72cca0956\n", " 2016\n", - " Braves\n", - " Mets\n", - " 201\n", + " Astros\n", + " Cubs\n", + " 174\n", " \n", " \n", "\n", @@ -1500,64 +1526,64 @@ ], "text/plain": [ " gameId year homeTeamName awayTeamName \\\n", - "0 d60c6036-0ce1-4c90-8dd9-de3b403c92a8 2016 Nationals Brewers \n", - "1 af72a0b9-65f7-49fb-9b30-d505068bdf6d 2016 Reds Brewers \n", - "2 f57e1271-d217-400a-aea6-2e2d7d6a59a0 2016 Orioles Rays \n", - "3 198f4eed-a29f-41e2-8623-cb261e5ab370 2016 Rockies Giants \n", - "4 cb3ef033-dd57-41fd-b206-cdd3bc12c74f 2016 Twins Indians \n", - "5 4be9f735-a98e-4689-87ce-852cc3a1e79d 2016 Blue Jays Orioles \n", - "6 0b2de8c3-11d9-4f0f-a186-25b59f34a5d8 2016 Yankees Mets \n", - "7 60d80663-6ced-44aa-aad9-0f4bf8d3b4d2 2016 Red Sox Rays \n", - "8 7e1c2095-4fea-454c-8773-096ceb6fb05c 2016 Cardinals Pirates \n", - "9 f7f24ce3-7f9d-4e8a-986e-095db847c4c1 2016 Rays Twins \n", - "10 5c26e7fc-c99f-48b4-92c1-4a7208c8cfe9 2016 Rays Twins \n", - "11 6d2cab13-dd85-477a-8769-669069f85836 2016 Royals Rays \n", - "12 bca90342-7ddc-468e-b189-d43fad7528ec 2016 Astros Rays \n", - "13 630f4f78-03cc-43c1-9e57-ababb9c11418 2016 Dodgers Giants \n", - "14 c0cf1376-1115-4a2f-b457-3f82bbc41a89 2016 Tigers White Sox \n", - "15 46463c50-0f5c-4dca-a661-dd194464e791 2016 Cardinals Cubs \n", - "16 392ad56d-972e-4f77-98e2-5f8577931cf8 2016 Giants Cardinals \n", - "17 307730fa-bbed-4221-b4e6-a2492f546fd5 2016 Red Sox Twins \n", - "18 1cbc558f-7615-4fa9-bf97-7ccd62040d6f 2016 Mets Braves \n", - "19 723348ba-1645-43fc-9e22-92994f7a63bd 2016 Athletics Twins \n", - "20 ffbd6ecc-82e1-4e5d-9bd1-4ea210be5992 2016 Twins Marlins \n", - "21 f2747230-7df5-4535-a475-a1c823d0d654 2016 Twins Yankees \n", - "22 db3b6f35-a7a4-430a-8703-2b2f25103e17 2016 White Sox Orioles \n", - "23 5fc8c6f0-a70e-4d1b-877f-eb1ec8e6f636 2016 Diamondbacks Giants \n", - "24 95d548b6-2da8-4644-812e-b277fec5b91f 2016 Braves Mets \n", + "0 e14b6493-9e7f-404f-840a-8a680cc364bf 2016 Marlins Cubs \n", + "1 1f32b347-cbcb-4c31-a145-0e685306d168 2016 Marlins Cubs \n", + "2 0c2292d1-7398-48be-bf8e-b41dad5e1a43 2016 Braves Cubs \n", + "3 8fbec734-a15a-42ab-8d51-60790de7750b 2016 Braves Cubs \n", + "4 89e514d5-fbf5-4b9d-bdac-6ca45bfd18dd 2016 Phillies Cubs \n", + "5 6a83e76c-dc0d-4924-9d3d-a2e7e0ab5b52 2016 Diamondbacks Cubs \n", + "6 76ea8662-c7e6-4c38-8f2a-efe373e428ce 2016 Athletics Cubs \n", + "7 66fad23d-6e89-4f99-be29-d49b6e94f95d 2016 Athletics Cubs \n", + "8 d977367c-cf0c-4687-95a0-eb4542efcb01 2016 Rockies Cubs \n", + "9 a87070ff-1084-43ca-a7ba-69278f93ecba 2016 Cardinals Cubs \n", + "10 ea6b350d-3c1d-4737-878d-4465f66999f6 2016 Cardinals Cubs \n", + "11 46463c50-0f5c-4dca-a661-dd194464e791 2016 Cardinals Cubs \n", + "12 59134e6d-9d13-49aa-978e-c3c2300eb90f 2016 Pirates Cubs \n", + "13 387630a3-a894-4327-baa1-b24ec1a654d9 2016 Pirates Cubs \n", + "14 5d084e13-94fd-4995-b95a-4801ea3ed556 2016 Giants Cubs \n", + "15 34444c94-03ec-4d12-96af-68b8f399a22f 2016 Reds Cubs \n", + "16 9580bffe-22e1-4975-978b-1b13e7505193 2016 Reds Cubs \n", + "17 645e6a08-afd6-4677-a5c9-01ef446b0cf3 2016 Reds Cubs \n", + "18 08981bd8-d1d7-48e1-8668-9098b8f7fe90 2016 Reds Cubs \n", + "19 303703bb-b55f-476d-8faf-bf582169fb1d 2016 Padres Cubs \n", + "20 71ab82a4-6e07-430a-b695-1af3bc42ea61 2016 Nationals Cubs \n", + "21 d1a110c2-f6c8-4029-bcd8-2f8a01e1561c 2016 Brewers Cubs \n", + "22 6d111b57-fa0b-4f24-82df-ff33a26f0252 2016 Brewers Cubs \n", + "23 a97e9539-bbbd-4e03-bf15-f25ea2c1d923 2016 Brewers Cubs \n", + "24 dc0c9218-505c-4725-8c0c-40b72cca0956 2016 Astros Cubs \n", "\n", " duration_minutes \n", - "0 167 \n", - "1 172 \n", - "2 166 \n", - "3 182 \n", - "4 204 \n", - "5 184 \n", - "6 182 \n", - "7 191 \n", - "8 201 \n", - "9 189 \n", - "10 177 \n", - "11 183 \n", - "12 194 \n", - "13 178 \n", - "14 193 \n", - "15 160 \n", - "16 169 \n", - "17 251 \n", - "18 151 \n", - "19 153 \n", - "20 185 \n", - "21 180 \n", - "22 199 \n", - "23 175 \n", - "24 201 \n", + "0 187 \n", + "1 189 \n", + "2 165 \n", + "3 222 \n", + "4 164 \n", + "5 201 \n", + "6 173 \n", + "7 176 \n", + "8 180 \n", + "9 157 \n", + "10 218 \n", + "11 160 \n", + "12 178 \n", + "13 205 \n", + "14 197 \n", + "15 198 \n", + "16 188 \n", + "17 188 \n", + "18 194 \n", + "19 175 \n", + "20 257 \n", + "21 178 \n", + "22 171 \n", + "23 248 \n", + "24 174 \n", "...\n", "\n", "[2431 rows x 5 columns]" ] }, - "execution_count": 10, + "execution_count": 11, "metadata": {}, "output_type": "execute_result" } @@ -1577,29 +1603,19 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 12, "id": "67a7c35f-80cf-4482-80f9-7f01c7743807", "metadata": {}, "outputs": [ { "data": { - "text/html": [ - "Query job bd315bd7-1f10-4f1b-9997-10a294b1f464 is DONE. 232.7 kB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job 972bf072-22c2-49ef-8764-1c1109dfc0a3 is DONE. 0 Bytes processed. Open Job" - ], + "application/vnd.jupyter.widget-view+json": { + "model_id": "b5b2da9ef7864a51adfbc7d3c85c46b7", + "version_major": 2, + "version_minor": 0 + }, "text/plain": [ - "" + "HTML(value='Query job e387be31-99fc-46a9-9de7-3bb83ff1f4fe is DONE. 174.4 kB processed. Open Job" - ], + "application/vnd.jupyter.widget-view+json": { + "model_id": "32a12de102694ac8bbf0dfa16d17be72", + "version_major": 2, + "version_minor": 0 + }, "text/plain": [ - "" + "HTML(value='Query job af23a9a2-151d-469a-a5c7-588ea60d1602 is DONE. 193.8 kB processed. \n", " \n", " 0\n", - " d60c6036-0ce1-4c90-8dd9-de3b403c92a8\n", + " e14b6493-9e7f-404f-840a-8a680cc364bf\n", " 2016\n", - " Nationals\n", - " Brewers\n", - " 167\n", + " Marlins\n", + " Cubs\n", + " 187\n", " \n", " \n", " 1\n", - " af72a0b9-65f7-49fb-9b30-d505068bdf6d\n", + " 1f32b347-cbcb-4c31-a145-0e685306d168\n", " 2016\n", - " Reds\n", - " Brewers\n", - " 172\n", + " Marlins\n", + " Cubs\n", + " 189\n", " \n", " \n", " 2\n", - " f57e1271-d217-400a-aea6-2e2d7d6a59a0\n", + " 0c2292d1-7398-48be-bf8e-b41dad5e1a43\n", " 2016\n", - " Orioles\n", - " Rays\n", - " 166\n", + " Braves\n", + " Cubs\n", + " 165\n", " \n", " \n", " 3\n", - " 198f4eed-a29f-41e2-8623-cb261e5ab370\n", + " 8fbec734-a15a-42ab-8d51-60790de7750b\n", " 2016\n", - " Rockies\n", - " Giants\n", - " 182\n", + " Braves\n", + " Cubs\n", + " 222\n", " \n", " \n", " 4\n", - " cb3ef033-dd57-41fd-b206-cdd3bc12c74f\n", + " 89e514d5-fbf5-4b9d-bdac-6ca45bfd18dd\n", " 2016\n", - " Twins\n", - " Indians\n", - " 204\n", + " Phillies\n", + " Cubs\n", + " 164\n", " \n", " \n", " 5\n", - " 4be9f735-a98e-4689-87ce-852cc3a1e79d\n", + " 6a83e76c-dc0d-4924-9d3d-a2e7e0ab5b52\n", " 2016\n", - " Blue Jays\n", - " Orioles\n", - " 184\n", + " Diamondbacks\n", + " Cubs\n", + " 201\n", " \n", " \n", " 6\n", - " 0b2de8c3-11d9-4f0f-a186-25b59f34a5d8\n", + " 76ea8662-c7e6-4c38-8f2a-efe373e428ce\n", " 2016\n", - " Yankees\n", - " Mets\n", - " 182\n", + " Athletics\n", + " Cubs\n", + " 173\n", " \n", " \n", " 7\n", - " 60d80663-6ced-44aa-aad9-0f4bf8d3b4d2\n", + " 66fad23d-6e89-4f99-be29-d49b6e94f95d\n", " 2016\n", - " Red Sox\n", - " Rays\n", - " 191\n", + " Athletics\n", + " Cubs\n", + " 176\n", " \n", " \n", " 8\n", - " 7e1c2095-4fea-454c-8773-096ceb6fb05c\n", + " d977367c-cf0c-4687-95a0-eb4542efcb01\n", " 2016\n", - " Cardinals\n", - " Pirates\n", - " 201\n", + " Rockies\n", + " Cubs\n", + " 180\n", " \n", " \n", " 9\n", - " f7f24ce3-7f9d-4e8a-986e-095db847c4c1\n", + " a87070ff-1084-43ca-a7ba-69278f93ecba\n", " 2016\n", - " Rays\n", - " Twins\n", - " 189\n", + " Cardinals\n", + " Cubs\n", + " 157\n", " \n", " \n", " 10\n", - " 5c26e7fc-c99f-48b4-92c1-4a7208c8cfe9\n", + " ea6b350d-3c1d-4737-878d-4465f66999f6\n", " 2016\n", - " Rays\n", - " Twins\n", - " 177\n", + " Cardinals\n", + " Cubs\n", + " 218\n", " \n", " \n", " 11\n", - " 6d2cab13-dd85-477a-8769-669069f85836\n", + " 46463c50-0f5c-4dca-a661-dd194464e791\n", " 2016\n", - " Royals\n", - " Rays\n", - " 183\n", + " Cardinals\n", + " Cubs\n", + " 160\n", " \n", " \n", " 12\n", - " bca90342-7ddc-468e-b189-d43fad7528ec\n", + " 59134e6d-9d13-49aa-978e-c3c2300eb90f\n", " 2016\n", - " Astros\n", - " Rays\n", - " 194\n", + " Pirates\n", + " Cubs\n", + " 178\n", " \n", " \n", " 13\n", - " 630f4f78-03cc-43c1-9e57-ababb9c11418\n", + " 387630a3-a894-4327-baa1-b24ec1a654d9\n", " 2016\n", - " Dodgers\n", - " Giants\n", - " 178\n", + " Pirates\n", + " Cubs\n", + " 205\n", " \n", " \n", " 14\n", - " c0cf1376-1115-4a2f-b457-3f82bbc41a89\n", + " 5d084e13-94fd-4995-b95a-4801ea3ed556\n", " 2016\n", - " Tigers\n", - " White Sox\n", - " 193\n", + " Giants\n", + " Cubs\n", + " 197\n", " \n", " \n", " 15\n", - " 46463c50-0f5c-4dca-a661-dd194464e791\n", + " 34444c94-03ec-4d12-96af-68b8f399a22f\n", " 2016\n", - " Cardinals\n", + " Reds\n", " Cubs\n", - " 160\n", + " 198\n", " \n", " \n", " 16\n", - " 392ad56d-972e-4f77-98e2-5f8577931cf8\n", + " 9580bffe-22e1-4975-978b-1b13e7505193\n", " 2016\n", - " Giants\n", - " Cardinals\n", - " 169\n", + " Reds\n", + " Cubs\n", + " 188\n", " \n", " \n", " 17\n", - " 307730fa-bbed-4221-b4e6-a2492f546fd5\n", + " 645e6a08-afd6-4677-a5c9-01ef446b0cf3\n", " 2016\n", - " Red Sox\n", - " Twins\n", - " 251\n", + " Reds\n", + " Cubs\n", + " 188\n", " \n", " \n", " 18\n", - " 1cbc558f-7615-4fa9-bf97-7ccd62040d6f\n", + " 08981bd8-d1d7-48e1-8668-9098b8f7fe90\n", " 2016\n", - " Mets\n", - " Braves\n", - " 151\n", + " Reds\n", + " Cubs\n", + " 194\n", " \n", " \n", " 19\n", - " 723348ba-1645-43fc-9e22-92994f7a63bd\n", + " 303703bb-b55f-476d-8faf-bf582169fb1d\n", " 2016\n", - " Athletics\n", - " Twins\n", - " 153\n", + " Padres\n", + " Cubs\n", + " 175\n", " \n", " \n", " 20\n", - " ffbd6ecc-82e1-4e5d-9bd1-4ea210be5992\n", + " 71ab82a4-6e07-430a-b695-1af3bc42ea61\n", " 2016\n", - " Twins\n", - " Marlins\n", - " 185\n", + " Nationals\n", + " Cubs\n", + " 257\n", " \n", " \n", " 21\n", - " f2747230-7df5-4535-a475-a1c823d0d654\n", + " d1a110c2-f6c8-4029-bcd8-2f8a01e1561c\n", " 2016\n", - " Twins\n", - " Yankees\n", - " 180\n", + " Brewers\n", + " Cubs\n", + " 178\n", " \n", " \n", " 22\n", - " db3b6f35-a7a4-430a-8703-2b2f25103e17\n", + " 6d111b57-fa0b-4f24-82df-ff33a26f0252\n", " 2016\n", - " White Sox\n", - " Orioles\n", - " 199\n", + " Brewers\n", + " Cubs\n", + " 171\n", " \n", " \n", " 23\n", - " 5fc8c6f0-a70e-4d1b-877f-eb1ec8e6f636\n", + " a97e9539-bbbd-4e03-bf15-f25ea2c1d923\n", " 2016\n", - " Diamondbacks\n", - " Giants\n", - " 175\n", + " Brewers\n", + " Cubs\n", + " 248\n", " \n", " \n", " 24\n", - " 95d548b6-2da8-4644-812e-b277fec5b91f\n", + " dc0c9218-505c-4725-8c0c-40b72cca0956\n", " 2016\n", - " Braves\n", - " Mets\n", - " 201\n", + " Astros\n", + " Cubs\n", + " 174\n", " \n", " \n", "\n", @@ -1853,64 +1871,64 @@ ], "text/plain": [ " gameId year homeTeamName awayTeamName \\\n", - "0 d60c6036-0ce1-4c90-8dd9-de3b403c92a8 2016 Nationals Brewers \n", - "1 af72a0b9-65f7-49fb-9b30-d505068bdf6d 2016 Reds Brewers \n", - "2 f57e1271-d217-400a-aea6-2e2d7d6a59a0 2016 Orioles Rays \n", - "3 198f4eed-a29f-41e2-8623-cb261e5ab370 2016 Rockies Giants \n", - "4 cb3ef033-dd57-41fd-b206-cdd3bc12c74f 2016 Twins Indians \n", - "5 4be9f735-a98e-4689-87ce-852cc3a1e79d 2016 Blue Jays Orioles \n", - "6 0b2de8c3-11d9-4f0f-a186-25b59f34a5d8 2016 Yankees Mets \n", - "7 60d80663-6ced-44aa-aad9-0f4bf8d3b4d2 2016 Red Sox Rays \n", - "8 7e1c2095-4fea-454c-8773-096ceb6fb05c 2016 Cardinals Pirates \n", - "9 f7f24ce3-7f9d-4e8a-986e-095db847c4c1 2016 Rays Twins \n", - "10 5c26e7fc-c99f-48b4-92c1-4a7208c8cfe9 2016 Rays Twins \n", - "11 6d2cab13-dd85-477a-8769-669069f85836 2016 Royals Rays \n", - "12 bca90342-7ddc-468e-b189-d43fad7528ec 2016 Astros Rays \n", - "13 630f4f78-03cc-43c1-9e57-ababb9c11418 2016 Dodgers Giants \n", - "14 c0cf1376-1115-4a2f-b457-3f82bbc41a89 2016 Tigers White Sox \n", - "15 46463c50-0f5c-4dca-a661-dd194464e791 2016 Cardinals Cubs \n", - "16 392ad56d-972e-4f77-98e2-5f8577931cf8 2016 Giants Cardinals \n", - "17 307730fa-bbed-4221-b4e6-a2492f546fd5 2016 Red Sox Twins \n", - "18 1cbc558f-7615-4fa9-bf97-7ccd62040d6f 2016 Mets Braves \n", - "19 723348ba-1645-43fc-9e22-92994f7a63bd 2016 Athletics Twins \n", - "20 ffbd6ecc-82e1-4e5d-9bd1-4ea210be5992 2016 Twins Marlins \n", - "21 f2747230-7df5-4535-a475-a1c823d0d654 2016 Twins Yankees \n", - "22 db3b6f35-a7a4-430a-8703-2b2f25103e17 2016 White Sox Orioles \n", - "23 5fc8c6f0-a70e-4d1b-877f-eb1ec8e6f636 2016 Diamondbacks Giants \n", - "24 95d548b6-2da8-4644-812e-b277fec5b91f 2016 Braves Mets \n", + "0 e14b6493-9e7f-404f-840a-8a680cc364bf 2016 Marlins Cubs \n", + "1 1f32b347-cbcb-4c31-a145-0e685306d168 2016 Marlins Cubs \n", + "2 0c2292d1-7398-48be-bf8e-b41dad5e1a43 2016 Braves Cubs \n", + "3 8fbec734-a15a-42ab-8d51-60790de7750b 2016 Braves Cubs \n", + "4 89e514d5-fbf5-4b9d-bdac-6ca45bfd18dd 2016 Phillies Cubs \n", + "5 6a83e76c-dc0d-4924-9d3d-a2e7e0ab5b52 2016 Diamondbacks Cubs \n", + "6 76ea8662-c7e6-4c38-8f2a-efe373e428ce 2016 Athletics Cubs \n", + "7 66fad23d-6e89-4f99-be29-d49b6e94f95d 2016 Athletics Cubs \n", + "8 d977367c-cf0c-4687-95a0-eb4542efcb01 2016 Rockies Cubs \n", + "9 a87070ff-1084-43ca-a7ba-69278f93ecba 2016 Cardinals Cubs \n", + "10 ea6b350d-3c1d-4737-878d-4465f66999f6 2016 Cardinals Cubs \n", + "11 46463c50-0f5c-4dca-a661-dd194464e791 2016 Cardinals Cubs \n", + "12 59134e6d-9d13-49aa-978e-c3c2300eb90f 2016 Pirates Cubs \n", + "13 387630a3-a894-4327-baa1-b24ec1a654d9 2016 Pirates Cubs \n", + "14 5d084e13-94fd-4995-b95a-4801ea3ed556 2016 Giants Cubs \n", + "15 34444c94-03ec-4d12-96af-68b8f399a22f 2016 Reds Cubs \n", + "16 9580bffe-22e1-4975-978b-1b13e7505193 2016 Reds Cubs \n", + "17 645e6a08-afd6-4677-a5c9-01ef446b0cf3 2016 Reds Cubs \n", + "18 08981bd8-d1d7-48e1-8668-9098b8f7fe90 2016 Reds Cubs \n", + "19 303703bb-b55f-476d-8faf-bf582169fb1d 2016 Padres Cubs \n", + "20 71ab82a4-6e07-430a-b695-1af3bc42ea61 2016 Nationals Cubs \n", + "21 d1a110c2-f6c8-4029-bcd8-2f8a01e1561c 2016 Brewers Cubs \n", + "22 6d111b57-fa0b-4f24-82df-ff33a26f0252 2016 Brewers Cubs \n", + "23 a97e9539-bbbd-4e03-bf15-f25ea2c1d923 2016 Brewers Cubs \n", + "24 dc0c9218-505c-4725-8c0c-40b72cca0956 2016 Astros Cubs \n", "\n", " duration_minutes \n", - "0 167 \n", - "1 172 \n", - "2 166 \n", - "3 182 \n", - "4 204 \n", - "5 184 \n", - "6 182 \n", - "7 191 \n", - "8 201 \n", - "9 189 \n", - "10 177 \n", - "11 183 \n", - "12 194 \n", - "13 178 \n", - "14 193 \n", - "15 160 \n", - "16 169 \n", - "17 251 \n", - "18 151 \n", - "19 153 \n", - "20 185 \n", - "21 180 \n", - "22 199 \n", - "23 175 \n", - "24 201 \n", + "0 187 \n", + "1 189 \n", + "2 165 \n", + "3 222 \n", + "4 164 \n", + "5 201 \n", + "6 173 \n", + "7 176 \n", + "8 180 \n", + "9 157 \n", + "10 218 \n", + "11 160 \n", + "12 178 \n", + "13 205 \n", + "14 197 \n", + "15 198 \n", + "16 188 \n", + "17 188 \n", + "18 194 \n", + "19 175 \n", + "20 257 \n", + "21 178 \n", + "22 171 \n", + "23 248 \n", + "24 174 \n", "...\n", "\n", "[2431 rows x 5 columns]" ] }, - "execution_count": 11, + "execution_count": 12, "metadata": {}, "output_type": "execute_result" } @@ -1931,17 +1949,19 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 13, "id": "3f09ff32-ef43-4fab-a86b-8868afc34363", "metadata": {}, "outputs": [ { "data": { - "text/html": [ - "Query job 3c859587-582d-4b68-8b35-7072b9a42346 is DONE. 0 Bytes processed. Open Job" - ], + "application/vnd.jupyter.widget-view+json": { + "model_id": "30a9e3bd880a4c718ec3a581e0139e21", + "version_major": 2, + "version_minor": 0 + }, "text/plain": [ - "" + "HTML(value='Query job b14b9796-f94d-48c0-a477-13f6b865e11d is DONE. 174.4 kB processed. Open Job" - ], + "application/vnd.jupyter.widget-view+json": { + "model_id": "7a1e5045b4ae4567b7be9f6f7bf39e3a", + "version_major": 2, + "version_minor": 0 + }, "text/plain": [ - "" + "HTML(value='Query job 20f025c3-4d84-49e9-9fbd-d77eeb2c6e04 is RUNNING. \n", " \n", " 0\n", - " d60c6036-0ce1-4c90-8dd9-de3b403c92a8\n", - " Nationals\n", + " e14b6493-9e7f-404f-840a-8a680cc364bf\n", + " Marlins\n", " \n", " \n", " 1\n", - " af72a0b9-65f7-49fb-9b30-d505068bdf6d\n", - " Reds\n", + " 1f32b347-cbcb-4c31-a145-0e685306d168\n", + " Marlins\n", " \n", " \n", " 2\n", - " f57e1271-d217-400a-aea6-2e2d7d6a59a0\n", - " Orioles\n", + " 0c2292d1-7398-48be-bf8e-b41dad5e1a43\n", + " Braves\n", " \n", " \n", " 3\n", - " 198f4eed-a29f-41e2-8623-cb261e5ab370\n", - " Rockies\n", + " 8fbec734-a15a-42ab-8d51-60790de7750b\n", + " Braves\n", " \n", " \n", " 4\n", - " cb3ef033-dd57-41fd-b206-cdd3bc12c74f\n", - " Twins\n", + " 89e514d5-fbf5-4b9d-bdac-6ca45bfd18dd\n", + " Phillies\n", " \n", " \n", " 5\n", - " 4be9f735-a98e-4689-87ce-852cc3a1e79d\n", - " Blue Jays\n", + " 6a83e76c-dc0d-4924-9d3d-a2e7e0ab5b52\n", + " Diamondbacks\n", " \n", " \n", " 6\n", - " 0b2de8c3-11d9-4f0f-a186-25b59f34a5d8\n", - " Yankees\n", + " 76ea8662-c7e6-4c38-8f2a-efe373e428ce\n", + " Athletics\n", " \n", " \n", " 7\n", - " 60d80663-6ced-44aa-aad9-0f4bf8d3b4d2\n", - " Red Sox\n", + " 66fad23d-6e89-4f99-be29-d49b6e94f95d\n", + " Athletics\n", " \n", " \n", " 8\n", - " 7e1c2095-4fea-454c-8773-096ceb6fb05c\n", - " Cardinals\n", + " d977367c-cf0c-4687-95a0-eb4542efcb01\n", + " Rockies\n", " \n", " \n", " 9\n", - " f7f24ce3-7f9d-4e8a-986e-095db847c4c1\n", - " Rays\n", + " a87070ff-1084-43ca-a7ba-69278f93ecba\n", + " Cardinals\n", " \n", " \n", " 10\n", - " 5c26e7fc-c99f-48b4-92c1-4a7208c8cfe9\n", - " Rays\n", + " ea6b350d-3c1d-4737-878d-4465f66999f6\n", + " Cardinals\n", " \n", " \n", " 11\n", - " 6d2cab13-dd85-477a-8769-669069f85836\n", - " Royals\n", + " 46463c50-0f5c-4dca-a661-dd194464e791\n", + " Cardinals\n", " \n", " \n", " 12\n", - " bca90342-7ddc-468e-b189-d43fad7528ec\n", - " Astros\n", + " 59134e6d-9d13-49aa-978e-c3c2300eb90f\n", + " Pirates\n", " \n", " \n", " 13\n", - " 630f4f78-03cc-43c1-9e57-ababb9c11418\n", - " Dodgers\n", + " 387630a3-a894-4327-baa1-b24ec1a654d9\n", + " Pirates\n", " \n", " \n", " 14\n", - " c0cf1376-1115-4a2f-b457-3f82bbc41a89\n", - " Tigers\n", + " 5d084e13-94fd-4995-b95a-4801ea3ed556\n", + " Giants\n", " \n", " \n", " 15\n", - " 46463c50-0f5c-4dca-a661-dd194464e791\n", - " Cardinals\n", + " 34444c94-03ec-4d12-96af-68b8f399a22f\n", + " Reds\n", " \n", " \n", " 16\n", - " 392ad56d-972e-4f77-98e2-5f8577931cf8\n", - " Giants\n", + " 9580bffe-22e1-4975-978b-1b13e7505193\n", + " Reds\n", " \n", " \n", " 17\n", - " 307730fa-bbed-4221-b4e6-a2492f546fd5\n", - " Red Sox\n", + " 645e6a08-afd6-4677-a5c9-01ef446b0cf3\n", + " Reds\n", " \n", " \n", " 18\n", - " 1cbc558f-7615-4fa9-bf97-7ccd62040d6f\n", - " Mets\n", + " 08981bd8-d1d7-48e1-8668-9098b8f7fe90\n", + " Reds\n", " \n", " \n", " 19\n", - " 723348ba-1645-43fc-9e22-92994f7a63bd\n", - " Athletics\n", + " 303703bb-b55f-476d-8faf-bf582169fb1d\n", + " Padres\n", " \n", " \n", " 20\n", - " ffbd6ecc-82e1-4e5d-9bd1-4ea210be5992\n", - " Twins\n", + " 71ab82a4-6e07-430a-b695-1af3bc42ea61\n", + " Nationals\n", " \n", " \n", " 21\n", - " f2747230-7df5-4535-a475-a1c823d0d654\n", - " Twins\n", + " d1a110c2-f6c8-4029-bcd8-2f8a01e1561c\n", + " Brewers\n", " \n", " \n", " 22\n", - " db3b6f35-a7a4-430a-8703-2b2f25103e17\n", - " White Sox\n", + " 6d111b57-fa0b-4f24-82df-ff33a26f0252\n", + " Brewers\n", " \n", " \n", " 23\n", - " 5fc8c6f0-a70e-4d1b-877f-eb1ec8e6f636\n", - " Diamondbacks\n", + " a97e9539-bbbd-4e03-bf15-f25ea2c1d923\n", + " Brewers\n", " \n", " \n", " 24\n", - " 95d548b6-2da8-4644-812e-b277fec5b91f\n", - " Braves\n", + " dc0c9218-505c-4725-8c0c-40b72cca0956\n", + " Astros\n", " \n", " \n", "\n", @@ -2117,37 +2139,37 @@ ], "text/plain": [ " gameId homeTeamName\n", - "0 d60c6036-0ce1-4c90-8dd9-de3b403c92a8 Nationals\n", - "1 af72a0b9-65f7-49fb-9b30-d505068bdf6d Reds\n", - "2 f57e1271-d217-400a-aea6-2e2d7d6a59a0 Orioles\n", - "3 198f4eed-a29f-41e2-8623-cb261e5ab370 Rockies\n", - "4 cb3ef033-dd57-41fd-b206-cdd3bc12c74f Twins\n", - "5 4be9f735-a98e-4689-87ce-852cc3a1e79d Blue Jays\n", - "6 0b2de8c3-11d9-4f0f-a186-25b59f34a5d8 Yankees\n", - "7 60d80663-6ced-44aa-aad9-0f4bf8d3b4d2 Red Sox\n", - "8 7e1c2095-4fea-454c-8773-096ceb6fb05c Cardinals\n", - "9 f7f24ce3-7f9d-4e8a-986e-095db847c4c1 Rays\n", - "10 5c26e7fc-c99f-48b4-92c1-4a7208c8cfe9 Rays\n", - "11 6d2cab13-dd85-477a-8769-669069f85836 Royals\n", - "12 bca90342-7ddc-468e-b189-d43fad7528ec Astros\n", - "13 630f4f78-03cc-43c1-9e57-ababb9c11418 Dodgers\n", - "14 c0cf1376-1115-4a2f-b457-3f82bbc41a89 Tigers\n", - "15 46463c50-0f5c-4dca-a661-dd194464e791 Cardinals\n", - "16 392ad56d-972e-4f77-98e2-5f8577931cf8 Giants\n", - "17 307730fa-bbed-4221-b4e6-a2492f546fd5 Red Sox\n", - "18 1cbc558f-7615-4fa9-bf97-7ccd62040d6f Mets\n", - "19 723348ba-1645-43fc-9e22-92994f7a63bd Athletics\n", - "20 ffbd6ecc-82e1-4e5d-9bd1-4ea210be5992 Twins\n", - "21 f2747230-7df5-4535-a475-a1c823d0d654 Twins\n", - "22 db3b6f35-a7a4-430a-8703-2b2f25103e17 White Sox\n", - "23 5fc8c6f0-a70e-4d1b-877f-eb1ec8e6f636 Diamondbacks\n", - "24 95d548b6-2da8-4644-812e-b277fec5b91f Braves\n", + "0 e14b6493-9e7f-404f-840a-8a680cc364bf Marlins\n", + "1 1f32b347-cbcb-4c31-a145-0e685306d168 Marlins\n", + "2 0c2292d1-7398-48be-bf8e-b41dad5e1a43 Braves\n", + "3 8fbec734-a15a-42ab-8d51-60790de7750b Braves\n", + "4 89e514d5-fbf5-4b9d-bdac-6ca45bfd18dd Phillies\n", + "5 6a83e76c-dc0d-4924-9d3d-a2e7e0ab5b52 Diamondbacks\n", + "6 76ea8662-c7e6-4c38-8f2a-efe373e428ce Athletics\n", + "7 66fad23d-6e89-4f99-be29-d49b6e94f95d Athletics\n", + "8 d977367c-cf0c-4687-95a0-eb4542efcb01 Rockies\n", + "9 a87070ff-1084-43ca-a7ba-69278f93ecba Cardinals\n", + "10 ea6b350d-3c1d-4737-878d-4465f66999f6 Cardinals\n", + "11 46463c50-0f5c-4dca-a661-dd194464e791 Cardinals\n", + "12 59134e6d-9d13-49aa-978e-c3c2300eb90f Pirates\n", + "13 387630a3-a894-4327-baa1-b24ec1a654d9 Pirates\n", + "14 5d084e13-94fd-4995-b95a-4801ea3ed556 Giants\n", + "15 34444c94-03ec-4d12-96af-68b8f399a22f Reds\n", + "16 9580bffe-22e1-4975-978b-1b13e7505193 Reds\n", + "17 645e6a08-afd6-4677-a5c9-01ef446b0cf3 Reds\n", + "18 08981bd8-d1d7-48e1-8668-9098b8f7fe90 Reds\n", + "19 303703bb-b55f-476d-8faf-bf582169fb1d Padres\n", + "20 71ab82a4-6e07-430a-b695-1af3bc42ea61 Nationals\n", + "21 d1a110c2-f6c8-4029-bcd8-2f8a01e1561c Brewers\n", + "22 6d111b57-fa0b-4f24-82df-ff33a26f0252 Brewers\n", + "23 a97e9539-bbbd-4e03-bf15-f25ea2c1d923 Brewers\n", + "24 dc0c9218-505c-4725-8c0c-40b72cca0956 Astros\n", "...\n", "\n", "[2431 rows x 2 columns]" ] }, - "execution_count": 12, + "execution_count": 13, "metadata": {}, "output_type": "execute_result" } @@ -2159,29 +2181,19 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 14, "id": "5331d2c8-7912-4d96-8da1-f64b57374df3", "metadata": {}, "outputs": [ { "data": { - "text/html": [ - "Query job 262a8d65-8eb7-4769-b26d-4a1d93f19950 is DONE. 152.8 kB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job a18f6c86-dbff-4846-8d21-8f8c1d700a80 is DONE. 0 Bytes processed. Open Job" - ], + "application/vnd.jupyter.widget-view+json": { + "model_id": "2b06ade302254b7399d74edca095140c", + "version_major": 2, + "version_minor": 0 + }, "text/plain": [ - "" + "HTML(value='Query job fbd5deef-4c7f-4345-ab6c-28c3e24bd918 is DONE. 193.8 kB processed. Open Job" - ], + "application/vnd.jupyter.widget-view+json": { + "model_id": "e7413e47e1344d498851383a077917ed", + "version_major": 2, + "version_minor": 0 + }, "text/plain": [ - "" + "HTML(value='Query job ba580b64-ca65-4245-b17f-12fd382b2e2b is DONE. 193.8 kB processed. \n", " \n", " 0\n", - " d60c6036-0ce1-4c90-8dd9-de3b403c92a8\n", - " Brewers\n", + " e14b6493-9e7f-404f-840a-8a680cc364bf\n", + " Cubs\n", " \n", " \n", " 1\n", - " af72a0b9-65f7-49fb-9b30-d505068bdf6d\n", - " Brewers\n", + " 1f32b347-cbcb-4c31-a145-0e685306d168\n", + " Cubs\n", " \n", " \n", "\n", @@ -2242,13 +2256,13 @@ ], "text/plain": [ " gameId awayTeamName\n", - "0 d60c6036-0ce1-4c90-8dd9-de3b403c92a8 Brewers\n", - "1 af72a0b9-65f7-49fb-9b30-d505068bdf6d Brewers\n", + "0 e14b6493-9e7f-404f-840a-8a680cc364bf Cubs\n", + "1 1f32b347-cbcb-4c31-a145-0e685306d168 Cubs\n", "\n", "[2 rows x 2 columns]" ] }, - "execution_count": 13, + "execution_count": 14, "metadata": {}, "output_type": "execute_result" } @@ -2260,29 +2274,19 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 15, "id": "a574ad3e-a219-454c-8bb5-c5ed6627f2c6", "metadata": {}, "outputs": [ { "data": { - "text/html": [ - "Query job 1edf3455-802d-4b93-900b-9677cb43955a is DONE. 133.5 kB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job 98cccaa5-e630-4edf-bc15-2823e89aecb6 is DONE. 0 Bytes processed. Open Job" - ], + "application/vnd.jupyter.widget-view+json": { + "model_id": "0e076fa03b6b41878386abcaa9aeb757", + "version_major": 2, + "version_minor": 0 + }, "text/plain": [ - "" + "HTML(value='Query job 3f54256f-7189-400b-8d47-5ce1f6fe92c0 is DONE. 193.8 kB processed. Open Job" - ], + "application/vnd.jupyter.widget-view+json": { + "model_id": "3a87a0d2d7cc4d429191b6e7ceeb8a0b", + "version_major": 2, + "version_minor": 0 + }, "text/plain": [ - "" + "HTML(value='Query job 57211b59-73c2-42d6-88bd-a614a8baf779 is DONE. 193.8 kB processed. \n", " \n", " 0\n", - " d60c6036-0ce1-4c90-8dd9-de3b403c92a8\n", - " Nationals\n", - " Brewers\n", + " e14b6493-9e7f-404f-840a-8a680cc364bf\n", + " Marlins\n", + " Cubs\n", " \n", " \n", " 1\n", - " af72a0b9-65f7-49fb-9b30-d505068bdf6d\n", - " Reds\n", - " Brewers\n", + " 1f32b347-cbcb-4c31-a145-0e685306d168\n", + " Marlins\n", + " Cubs\n", " \n", " \n", "\n", @@ -2346,13 +2352,13 @@ ], "text/plain": [ " gameId homeTeamName awayTeamName\n", - "0 d60c6036-0ce1-4c90-8dd9-de3b403c92a8 Nationals Brewers\n", - "1 af72a0b9-65f7-49fb-9b30-d505068bdf6d Reds Brewers\n", + "0 e14b6493-9e7f-404f-840a-8a680cc364bf Marlins Cubs\n", + "1 1f32b347-cbcb-4c31-a145-0e685306d168 Marlins Cubs\n", "\n", "[2 rows x 3 columns]" ] }, - "execution_count": 14, + "execution_count": 15, "metadata": {}, "output_type": "execute_result" } @@ -2363,29 +2369,19 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 16, "id": "288e7a95-a077-46c4-8fe6-802474c01f8b", "metadata": {}, "outputs": [ { "data": { - "text/html": [ - "Query job dfe4d1ec-9a3d-4877-ab39-bb6f1c38d070 is DONE. 133.5 kB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job 6261a857-d256-4051-8af5-c6b04fb2795f is DONE. 0 Bytes processed. Open Job" - ], + "application/vnd.jupyter.widget-view+json": { + "model_id": "e974466302964b7785881ed1ce96ec75", + "version_major": 2, + "version_minor": 0 + }, "text/plain": [ - "" + "HTML(value='Query job 07ee6beb-b805-4ba9-8cb2-174d4e62ddfb is DONE. 193.8 kB processed. Open Job" - ], + "application/vnd.jupyter.widget-view+json": { + "model_id": "4f07980adb1f4f9eb2e2f80de5f0a174", + "version_major": 2, + "version_minor": 0 + }, "text/plain": [ - "" + "HTML(value='Query job ced45fc3-cfdb-4cd0-96ef-16a29d8d8f0b is DONE. 193.8 kB processed. \n", " \n", " 0\n", - " d60c6036-0ce1-4c90-8dd9-de3b403c92a8\n", - " Nationals\n", - " Brewers\n", + " e14b6493-9e7f-404f-840a-8a680cc364bf\n", + " Marlins\n", + " Cubs\n", " \n", " \n", " 1\n", - " af72a0b9-65f7-49fb-9b30-d505068bdf6d\n", - " Reds\n", - " Brewers\n", + " 1f32b347-cbcb-4c31-a145-0e685306d168\n", + " Marlins\n", + " Cubs\n", " \n", " \n", " 2\n", - " f57e1271-d217-400a-aea6-2e2d7d6a59a0\n", - " Orioles\n", + " 0c2292d1-7398-48be-bf8e-b41dad5e1a43\n", + " Braves\n", " <NA>\n", " \n", " \n", " 3\n", - " 198f4eed-a29f-41e2-8623-cb261e5ab370\n", - " Rockies\n", + " 8fbec734-a15a-42ab-8d51-60790de7750b\n", + " Braves\n", " <NA>\n", " \n", " \n", " 4\n", - " cb3ef033-dd57-41fd-b206-cdd3bc12c74f\n", - " Twins\n", + " 89e514d5-fbf5-4b9d-bdac-6ca45bfd18dd\n", + " Phillies\n", " <NA>\n", " \n", " \n", " 5\n", - " 4be9f735-a98e-4689-87ce-852cc3a1e79d\n", - " Blue Jays\n", + " 6a83e76c-dc0d-4924-9d3d-a2e7e0ab5b52\n", + " Diamondbacks\n", " <NA>\n", " \n", " \n", " 6\n", - " 0b2de8c3-11d9-4f0f-a186-25b59f34a5d8\n", - " Yankees\n", + " 76ea8662-c7e6-4c38-8f2a-efe373e428ce\n", + " Athletics\n", " <NA>\n", " \n", " \n", " 7\n", - " 60d80663-6ced-44aa-aad9-0f4bf8d3b4d2\n", - " Red Sox\n", + " 66fad23d-6e89-4f99-be29-d49b6e94f95d\n", + " Athletics\n", " <NA>\n", " \n", " \n", " 8\n", - " 7e1c2095-4fea-454c-8773-096ceb6fb05c\n", - " Cardinals\n", + " d977367c-cf0c-4687-95a0-eb4542efcb01\n", + " Rockies\n", " <NA>\n", " \n", " \n", " 9\n", - " f7f24ce3-7f9d-4e8a-986e-095db847c4c1\n", - " Rays\n", + " a87070ff-1084-43ca-a7ba-69278f93ecba\n", + " Cardinals\n", " <NA>\n", " \n", " \n", " 10\n", - " 5c26e7fc-c99f-48b4-92c1-4a7208c8cfe9\n", - " Rays\n", + " ea6b350d-3c1d-4737-878d-4465f66999f6\n", + " Cardinals\n", " <NA>\n", " \n", " \n", " 11\n", - " 6d2cab13-dd85-477a-8769-669069f85836\n", - " Royals\n", + " 46463c50-0f5c-4dca-a661-dd194464e791\n", + " Cardinals\n", " <NA>\n", " \n", " \n", " 12\n", - " bca90342-7ddc-468e-b189-d43fad7528ec\n", - " Astros\n", + " 59134e6d-9d13-49aa-978e-c3c2300eb90f\n", + " Pirates\n", " <NA>\n", " \n", " \n", " 13\n", - " 630f4f78-03cc-43c1-9e57-ababb9c11418\n", - " Dodgers\n", + " 387630a3-a894-4327-baa1-b24ec1a654d9\n", + " Pirates\n", " <NA>\n", " \n", " \n", " 14\n", - " c0cf1376-1115-4a2f-b457-3f82bbc41a89\n", - " Tigers\n", + " 5d084e13-94fd-4995-b95a-4801ea3ed556\n", + " Giants\n", " <NA>\n", " \n", " \n", " 15\n", - " 46463c50-0f5c-4dca-a661-dd194464e791\n", - " Cardinals\n", + " 34444c94-03ec-4d12-96af-68b8f399a22f\n", + " Reds\n", " <NA>\n", " \n", " \n", " 16\n", - " 392ad56d-972e-4f77-98e2-5f8577931cf8\n", - " Giants\n", + " 9580bffe-22e1-4975-978b-1b13e7505193\n", + " Reds\n", " <NA>\n", " \n", " \n", " 17\n", - " 307730fa-bbed-4221-b4e6-a2492f546fd5\n", - " Red Sox\n", + " 645e6a08-afd6-4677-a5c9-01ef446b0cf3\n", + " Reds\n", " <NA>\n", " \n", " \n", " 18\n", - " 1cbc558f-7615-4fa9-bf97-7ccd62040d6f\n", - " Mets\n", + " 08981bd8-d1d7-48e1-8668-9098b8f7fe90\n", + " Reds\n", " <NA>\n", " \n", " \n", " 19\n", - " 723348ba-1645-43fc-9e22-92994f7a63bd\n", - " Athletics\n", + " 303703bb-b55f-476d-8faf-bf582169fb1d\n", + " Padres\n", " <NA>\n", " \n", " \n", " 20\n", - " ffbd6ecc-82e1-4e5d-9bd1-4ea210be5992\n", - " Twins\n", + " 71ab82a4-6e07-430a-b695-1af3bc42ea61\n", + " Nationals\n", " <NA>\n", " \n", " \n", " 21\n", - " f2747230-7df5-4535-a475-a1c823d0d654\n", - " Twins\n", + " d1a110c2-f6c8-4029-bcd8-2f8a01e1561c\n", + " Brewers\n", " <NA>\n", " \n", " \n", " 22\n", - " db3b6f35-a7a4-430a-8703-2b2f25103e17\n", - " White Sox\n", + " 6d111b57-fa0b-4f24-82df-ff33a26f0252\n", + " Brewers\n", " <NA>\n", " \n", " \n", " 23\n", - " 5fc8c6f0-a70e-4d1b-877f-eb1ec8e6f636\n", - " Diamondbacks\n", + " a97e9539-bbbd-4e03-bf15-f25ea2c1d923\n", + " Brewers\n", " <NA>\n", " \n", " \n", " 24\n", - " 95d548b6-2da8-4644-812e-b277fec5b91f\n", - " Braves\n", + " dc0c9218-505c-4725-8c0c-40b72cca0956\n", + " Astros\n", " <NA>\n", " \n", " \n", @@ -2587,37 +2585,37 @@ ], "text/plain": [ " gameId homeTeamName awayTeamName\n", - "0 d60c6036-0ce1-4c90-8dd9-de3b403c92a8 Nationals Brewers\n", - "1 af72a0b9-65f7-49fb-9b30-d505068bdf6d Reds Brewers\n", - "2 f57e1271-d217-400a-aea6-2e2d7d6a59a0 Orioles \n", - "3 198f4eed-a29f-41e2-8623-cb261e5ab370 Rockies \n", - "4 cb3ef033-dd57-41fd-b206-cdd3bc12c74f Twins \n", - "5 4be9f735-a98e-4689-87ce-852cc3a1e79d Blue Jays \n", - "6 0b2de8c3-11d9-4f0f-a186-25b59f34a5d8 Yankees \n", - "7 60d80663-6ced-44aa-aad9-0f4bf8d3b4d2 Red Sox \n", - "8 7e1c2095-4fea-454c-8773-096ceb6fb05c Cardinals \n", - "9 f7f24ce3-7f9d-4e8a-986e-095db847c4c1 Rays \n", - "10 5c26e7fc-c99f-48b4-92c1-4a7208c8cfe9 Rays \n", - "11 6d2cab13-dd85-477a-8769-669069f85836 Royals \n", - "12 bca90342-7ddc-468e-b189-d43fad7528ec Astros \n", - "13 630f4f78-03cc-43c1-9e57-ababb9c11418 Dodgers \n", - "14 c0cf1376-1115-4a2f-b457-3f82bbc41a89 Tigers \n", - "15 46463c50-0f5c-4dca-a661-dd194464e791 Cardinals \n", - "16 392ad56d-972e-4f77-98e2-5f8577931cf8 Giants \n", - "17 307730fa-bbed-4221-b4e6-a2492f546fd5 Red Sox \n", - "18 1cbc558f-7615-4fa9-bf97-7ccd62040d6f Mets \n", - "19 723348ba-1645-43fc-9e22-92994f7a63bd Athletics \n", - "20 ffbd6ecc-82e1-4e5d-9bd1-4ea210be5992 Twins \n", - "21 f2747230-7df5-4535-a475-a1c823d0d654 Twins \n", - "22 db3b6f35-a7a4-430a-8703-2b2f25103e17 White Sox \n", - "23 5fc8c6f0-a70e-4d1b-877f-eb1ec8e6f636 Diamondbacks \n", - "24 95d548b6-2da8-4644-812e-b277fec5b91f Braves \n", + "0 e14b6493-9e7f-404f-840a-8a680cc364bf Marlins Cubs\n", + "1 1f32b347-cbcb-4c31-a145-0e685306d168 Marlins Cubs\n", + "2 0c2292d1-7398-48be-bf8e-b41dad5e1a43 Braves \n", + "3 8fbec734-a15a-42ab-8d51-60790de7750b Braves \n", + "4 89e514d5-fbf5-4b9d-bdac-6ca45bfd18dd Phillies \n", + "5 6a83e76c-dc0d-4924-9d3d-a2e7e0ab5b52 Diamondbacks \n", + "6 76ea8662-c7e6-4c38-8f2a-efe373e428ce Athletics \n", + "7 66fad23d-6e89-4f99-be29-d49b6e94f95d Athletics \n", + "8 d977367c-cf0c-4687-95a0-eb4542efcb01 Rockies \n", + "9 a87070ff-1084-43ca-a7ba-69278f93ecba Cardinals \n", + "10 ea6b350d-3c1d-4737-878d-4465f66999f6 Cardinals \n", + "11 46463c50-0f5c-4dca-a661-dd194464e791 Cardinals \n", + "12 59134e6d-9d13-49aa-978e-c3c2300eb90f Pirates \n", + "13 387630a3-a894-4327-baa1-b24ec1a654d9 Pirates \n", + "14 5d084e13-94fd-4995-b95a-4801ea3ed556 Giants \n", + "15 34444c94-03ec-4d12-96af-68b8f399a22f Reds \n", + "16 9580bffe-22e1-4975-978b-1b13e7505193 Reds \n", + "17 645e6a08-afd6-4677-a5c9-01ef446b0cf3 Reds \n", + "18 08981bd8-d1d7-48e1-8668-9098b8f7fe90 Reds \n", + "19 303703bb-b55f-476d-8faf-bf582169fb1d Padres \n", + "20 71ab82a4-6e07-430a-b695-1af3bc42ea61 Nationals \n", + "21 d1a110c2-f6c8-4029-bcd8-2f8a01e1561c Brewers \n", + "22 6d111b57-fa0b-4f24-82df-ff33a26f0252 Brewers \n", + "23 a97e9539-bbbd-4e03-bf15-f25ea2c1d923 Brewers \n", + "24 dc0c9218-505c-4725-8c0c-40b72cca0956 Astros \n", "...\n", "\n", "[2431 rows x 3 columns]" ] }, - "execution_count": 15, + "execution_count": 16, "metadata": {}, "output_type": "execute_result" } @@ -2628,29 +2626,19 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 17, "id": "7ee87a01-2ff5-4021-855d-44b71cf2a225", "metadata": {}, "outputs": [ { "data": { - "text/html": [ - "Query job 9ae1e55b-36d0-4aef-ae39-67a3ad5fdb4d is DONE. 133.5 kB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job 7a1822eb-7db9-4c54-abd5-74cb1cde6121 is DONE. 0 Bytes processed. Open Job" - ], + "application/vnd.jupyter.widget-view+json": { + "model_id": "c83cdc3e86ff4b3694acb25b1eda845a", + "version_major": 2, + "version_minor": 0 + }, "text/plain": [ - "" + "HTML(value='Query job 60335c33-acc9-4a4a-9e08-190fe67ad60e is DONE. 193.8 kB processed. Open Job" - ], + "application/vnd.jupyter.widget-view+json": { + "model_id": "14f1635a78d5414b9533f31436096e6e", + "version_major": 2, + "version_minor": 0 + }, "text/plain": [ - "" + "HTML(value='Query job 8c4e016f-429d-4591-8956-56fb18676334 is DONE. 193.8 kB processed. \n", " \n", " 0\n", - " d60c6036-0ce1-4c90-8dd9-de3b403c92a8\n", - " Nationals\n", - " Brewers\n", + " e14b6493-9e7f-404f-840a-8a680cc364bf\n", + " Marlins\n", + " Cubs\n", " \n", " \n", " 1\n", - " af72a0b9-65f7-49fb-9b30-d505068bdf6d\n", - " Reds\n", - " Brewers\n", + " 1f32b347-cbcb-4c31-a145-0e685306d168\n", + " Marlins\n", + " Cubs\n", " \n", " \n", " 2\n", - " f57e1271-d217-400a-aea6-2e2d7d6a59a0\n", - " Orioles\n", + " 0c2292d1-7398-48be-bf8e-b41dad5e1a43\n", + " Braves\n", " <NA>\n", " \n", " \n", " 3\n", - " 198f4eed-a29f-41e2-8623-cb261e5ab370\n", - " Rockies\n", + " 8fbec734-a15a-42ab-8d51-60790de7750b\n", + " Braves\n", " <NA>\n", " \n", " \n", " 4\n", - " cb3ef033-dd57-41fd-b206-cdd3bc12c74f\n", - " Twins\n", + " 89e514d5-fbf5-4b9d-bdac-6ca45bfd18dd\n", + " Phillies\n", " <NA>\n", " \n", " \n", " 5\n", - " 4be9f735-a98e-4689-87ce-852cc3a1e79d\n", - " Blue Jays\n", + " 6a83e76c-dc0d-4924-9d3d-a2e7e0ab5b52\n", + " Diamondbacks\n", " <NA>\n", " \n", " \n", " 6\n", - " 0b2de8c3-11d9-4f0f-a186-25b59f34a5d8\n", - " Yankees\n", + " 76ea8662-c7e6-4c38-8f2a-efe373e428ce\n", + " Athletics\n", " <NA>\n", " \n", " \n", " 7\n", - " 60d80663-6ced-44aa-aad9-0f4bf8d3b4d2\n", - " Red Sox\n", + " 66fad23d-6e89-4f99-be29-d49b6e94f95d\n", + " Athletics\n", " <NA>\n", " \n", " \n", " 8\n", - " 7e1c2095-4fea-454c-8773-096ceb6fb05c\n", - " Cardinals\n", + " d977367c-cf0c-4687-95a0-eb4542efcb01\n", + " Rockies\n", " <NA>\n", " \n", " \n", " 9\n", - " f7f24ce3-7f9d-4e8a-986e-095db847c4c1\n", - " Rays\n", + " a87070ff-1084-43ca-a7ba-69278f93ecba\n", + " Cardinals\n", " <NA>\n", " \n", " \n", " 10\n", - " 5c26e7fc-c99f-48b4-92c1-4a7208c8cfe9\n", - " Rays\n", + " ea6b350d-3c1d-4737-878d-4465f66999f6\n", + " Cardinals\n", " <NA>\n", " \n", " \n", " 11\n", - " 6d2cab13-dd85-477a-8769-669069f85836\n", - " Royals\n", + " 46463c50-0f5c-4dca-a661-dd194464e791\n", + " Cardinals\n", " <NA>\n", " \n", " \n", " 12\n", - " bca90342-7ddc-468e-b189-d43fad7528ec\n", - " Astros\n", + " 59134e6d-9d13-49aa-978e-c3c2300eb90f\n", + " Pirates\n", " <NA>\n", " \n", " \n", " 13\n", - " 630f4f78-03cc-43c1-9e57-ababb9c11418\n", - " Dodgers\n", + " 387630a3-a894-4327-baa1-b24ec1a654d9\n", + " Pirates\n", " <NA>\n", " \n", " \n", " 14\n", - " c0cf1376-1115-4a2f-b457-3f82bbc41a89\n", - " Tigers\n", + " 5d084e13-94fd-4995-b95a-4801ea3ed556\n", + " Giants\n", " <NA>\n", " \n", " \n", " 15\n", - " 46463c50-0f5c-4dca-a661-dd194464e791\n", - " Cardinals\n", + " 34444c94-03ec-4d12-96af-68b8f399a22f\n", + " Reds\n", " <NA>\n", " \n", " \n", " 16\n", - " 392ad56d-972e-4f77-98e2-5f8577931cf8\n", - " Giants\n", + " 9580bffe-22e1-4975-978b-1b13e7505193\n", + " Reds\n", " <NA>\n", " \n", " \n", " 17\n", - " 307730fa-bbed-4221-b4e6-a2492f546fd5\n", - " Red Sox\n", + " 645e6a08-afd6-4677-a5c9-01ef446b0cf3\n", + " Reds\n", " <NA>\n", " \n", " \n", " 18\n", - " 1cbc558f-7615-4fa9-bf97-7ccd62040d6f\n", - " Mets\n", + " 08981bd8-d1d7-48e1-8668-9098b8f7fe90\n", + " Reds\n", " <NA>\n", " \n", " \n", " 19\n", - " 723348ba-1645-43fc-9e22-92994f7a63bd\n", - " Athletics\n", + " 303703bb-b55f-476d-8faf-bf582169fb1d\n", + " Padres\n", " <NA>\n", " \n", " \n", " 20\n", - " ffbd6ecc-82e1-4e5d-9bd1-4ea210be5992\n", - " Twins\n", + " 71ab82a4-6e07-430a-b695-1af3bc42ea61\n", + " Nationals\n", " <NA>\n", " \n", " \n", " 21\n", - " f2747230-7df5-4535-a475-a1c823d0d654\n", - " Twins\n", + " d1a110c2-f6c8-4029-bcd8-2f8a01e1561c\n", + " Brewers\n", " <NA>\n", " \n", " \n", " 22\n", - " db3b6f35-a7a4-430a-8703-2b2f25103e17\n", - " White Sox\n", + " 6d111b57-fa0b-4f24-82df-ff33a26f0252\n", + " Brewers\n", " <NA>\n", " \n", " \n", " 23\n", - " 5fc8c6f0-a70e-4d1b-877f-eb1ec8e6f636\n", - " Diamondbacks\n", + " a97e9539-bbbd-4e03-bf15-f25ea2c1d923\n", + " Brewers\n", " <NA>\n", " \n", " \n", " 24\n", - " 95d548b6-2da8-4644-812e-b277fec5b91f\n", - " Braves\n", + " dc0c9218-505c-4725-8c0c-40b72cca0956\n", + " Astros\n", " <NA>\n", " \n", " \n", @@ -2852,37 +2842,37 @@ ], "text/plain": [ " gameId homeTeamName awayTeamName\n", - "0 d60c6036-0ce1-4c90-8dd9-de3b403c92a8 Nationals Brewers\n", - "1 af72a0b9-65f7-49fb-9b30-d505068bdf6d Reds Brewers\n", - "2 f57e1271-d217-400a-aea6-2e2d7d6a59a0 Orioles \n", - "3 198f4eed-a29f-41e2-8623-cb261e5ab370 Rockies \n", - "4 cb3ef033-dd57-41fd-b206-cdd3bc12c74f Twins \n", - "5 4be9f735-a98e-4689-87ce-852cc3a1e79d Blue Jays \n", - "6 0b2de8c3-11d9-4f0f-a186-25b59f34a5d8 Yankees \n", - "7 60d80663-6ced-44aa-aad9-0f4bf8d3b4d2 Red Sox \n", - "8 7e1c2095-4fea-454c-8773-096ceb6fb05c Cardinals \n", - "9 f7f24ce3-7f9d-4e8a-986e-095db847c4c1 Rays \n", - "10 5c26e7fc-c99f-48b4-92c1-4a7208c8cfe9 Rays \n", - "11 6d2cab13-dd85-477a-8769-669069f85836 Royals \n", - "12 bca90342-7ddc-468e-b189-d43fad7528ec Astros \n", - "13 630f4f78-03cc-43c1-9e57-ababb9c11418 Dodgers \n", - "14 c0cf1376-1115-4a2f-b457-3f82bbc41a89 Tigers \n", - "15 46463c50-0f5c-4dca-a661-dd194464e791 Cardinals \n", - "16 392ad56d-972e-4f77-98e2-5f8577931cf8 Giants \n", - "17 307730fa-bbed-4221-b4e6-a2492f546fd5 Red Sox \n", - "18 1cbc558f-7615-4fa9-bf97-7ccd62040d6f Mets \n", - "19 723348ba-1645-43fc-9e22-92994f7a63bd Athletics \n", - "20 ffbd6ecc-82e1-4e5d-9bd1-4ea210be5992 Twins \n", - "21 f2747230-7df5-4535-a475-a1c823d0d654 Twins \n", - "22 db3b6f35-a7a4-430a-8703-2b2f25103e17 White Sox \n", - "23 5fc8c6f0-a70e-4d1b-877f-eb1ec8e6f636 Diamondbacks \n", - "24 95d548b6-2da8-4644-812e-b277fec5b91f Braves \n", + "0 e14b6493-9e7f-404f-840a-8a680cc364bf Marlins Cubs\n", + "1 1f32b347-cbcb-4c31-a145-0e685306d168 Marlins Cubs\n", + "2 0c2292d1-7398-48be-bf8e-b41dad5e1a43 Braves \n", + "3 8fbec734-a15a-42ab-8d51-60790de7750b Braves \n", + "4 89e514d5-fbf5-4b9d-bdac-6ca45bfd18dd Phillies \n", + "5 6a83e76c-dc0d-4924-9d3d-a2e7e0ab5b52 Diamondbacks \n", + "6 76ea8662-c7e6-4c38-8f2a-efe373e428ce Athletics \n", + "7 66fad23d-6e89-4f99-be29-d49b6e94f95d Athletics \n", + "8 d977367c-cf0c-4687-95a0-eb4542efcb01 Rockies \n", + "9 a87070ff-1084-43ca-a7ba-69278f93ecba Cardinals \n", + "10 ea6b350d-3c1d-4737-878d-4465f66999f6 Cardinals \n", + "11 46463c50-0f5c-4dca-a661-dd194464e791 Cardinals \n", + "12 59134e6d-9d13-49aa-978e-c3c2300eb90f Pirates \n", + "13 387630a3-a894-4327-baa1-b24ec1a654d9 Pirates \n", + "14 5d084e13-94fd-4995-b95a-4801ea3ed556 Giants \n", + "15 34444c94-03ec-4d12-96af-68b8f399a22f Reds \n", + "16 9580bffe-22e1-4975-978b-1b13e7505193 Reds \n", + "17 645e6a08-afd6-4677-a5c9-01ef446b0cf3 Reds \n", + "18 08981bd8-d1d7-48e1-8668-9098b8f7fe90 Reds \n", + "19 303703bb-b55f-476d-8faf-bf582169fb1d Padres \n", + "20 71ab82a4-6e07-430a-b695-1af3bc42ea61 Nationals \n", + "21 d1a110c2-f6c8-4029-bcd8-2f8a01e1561c Brewers \n", + "22 6d111b57-fa0b-4f24-82df-ff33a26f0252 Brewers \n", + "23 a97e9539-bbbd-4e03-bf15-f25ea2c1d923 Brewers \n", + "24 dc0c9218-505c-4725-8c0c-40b72cca0956 Astros \n", "...\n", "\n", "[2431 rows x 3 columns]" ] }, - "execution_count": 16, + "execution_count": 17, "metadata": {}, "output_type": "execute_result" } @@ -2893,29 +2883,19 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 18, "id": "330ed69c-f122-4af9-bf5e-96e309d3fa0c", "metadata": {}, "outputs": [ { "data": { - "text/html": [ - "Query job ec1c442e-6ea1-461c-ada7-e3dd0454b0ca is DONE. 133.5 kB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job 4ababa83-ad57-4520-b49d-e613256ae2f3 is DONE. 0 Bytes processed. Open Job" - ], + "application/vnd.jupyter.widget-view+json": { + "model_id": "d97026197292402daa7176d5aac8c583", + "version_major": 2, + "version_minor": 0 + }, "text/plain": [ - "" + "HTML(value='Query job 948a3d0b-1c3d-479b-b54f-9a2b2062380e is DONE. 193.8 kB processed. Open Job" - ], + "application/vnd.jupyter.widget-view+json": { + "model_id": "9b987688bfdd49989e0dcae375a33740", + "version_major": 2, + "version_minor": 0 + }, "text/plain": [ - "" + "HTML(value='Query job 20d2a8bb-a876-4729-a439-8c8bbf591051 is DONE. 193.8 kB processed. \n", " \n", " 0\n", - " d60c6036-0ce1-4c90-8dd9-de3b403c92a8\n", - " Nationals\n", - " Brewers\n", + " e14b6493-9e7f-404f-840a-8a680cc364bf\n", + " Marlins\n", + " Cubs\n", " \n", " \n", " 1\n", - " af72a0b9-65f7-49fb-9b30-d505068bdf6d\n", - " Reds\n", - " Brewers\n", + " 1f32b347-cbcb-4c31-a145-0e685306d168\n", + " Marlins\n", + " Cubs\n", " \n", " \n", "\n", @@ -2979,13 +2961,13 @@ ], "text/plain": [ " gameId homeTeamName awayTeamName\n", - "0 d60c6036-0ce1-4c90-8dd9-de3b403c92a8 Nationals Brewers\n", - "1 af72a0b9-65f7-49fb-9b30-d505068bdf6d Reds Brewers\n", + "0 e14b6493-9e7f-404f-840a-8a680cc364bf Marlins Cubs\n", + "1 1f32b347-cbcb-4c31-a145-0e685306d168 Marlins Cubs\n", "\n", "[2 rows x 3 columns]" ] }, - "execution_count": 17, + "execution_count": 18, "metadata": {}, "output_type": "execute_result" } @@ -3005,17 +2987,19 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 19, "id": "5181231e-8a2a-4ac5-a379-6aa5ad4fee89", "metadata": {}, "outputs": [ { "data": { - "text/html": [ - "Query job f0e1bda5-34f5-46e2-a396-289340074f82 is DONE. 0 Bytes processed. Open Job" - ], + "application/vnd.jupyter.widget-view+json": { + "model_id": "862fd15acf82434fb153121c74164b5f", + "version_major": 2, + "version_minor": 0 + }, "text/plain": [ - "" + "HTML(value='Query job ea340371-7874-4590-bb5e-f747f81397de is DONE. 174.4 kB processed. Open Job" - ], + "application/vnd.jupyter.widget-view+json": { + "model_id": "6f9e64af012140619d1f4190b06862e6", + "version_major": 2, + "version_minor": 0 + }, "text/plain": [ - "" + "HTML(value='Query job 5c35116b-1c4a-4cc1-9ddd-172083d09490 is DONE. 193.8 kB processed. \n", " \n", " 0\n", - " d60c6036-0ce1-4c90-8dd9-de3b403c92a8\n", + " e14b6493-9e7f-404f-840a-8a680cc364bf\n", " 2016\n", - " Nationals\n", - " Brewers\n", - " 167\n", + " Marlins\n", + " Cubs\n", + " 187\n", " \n", " \n", " 1\n", - " af72a0b9-65f7-49fb-9b30-d505068bdf6d\n", + " 1f32b347-cbcb-4c31-a145-0e685306d168\n", " 2016\n", - " Reds\n", - " Brewers\n", - " 172\n", + " Marlins\n", + " Cubs\n", + " 189\n", " \n", " \n", " 2\n", - " f57e1271-d217-400a-aea6-2e2d7d6a59a0\n", + " 0c2292d1-7398-48be-bf8e-b41dad5e1a43\n", " 2016\n", - " Orioles\n", - " Rays\n", - " 166\n", + " Braves\n", + " Cubs\n", + " 165\n", " \n", " \n", " 3\n", - " 198f4eed-a29f-41e2-8623-cb261e5ab370\n", + " 8fbec734-a15a-42ab-8d51-60790de7750b\n", " 2016\n", - " Rockies\n", - " Giants\n", - " 182\n", + " Braves\n", + " Cubs\n", + " 222\n", " \n", " \n", " 4\n", - " cb3ef033-dd57-41fd-b206-cdd3bc12c74f\n", + " 89e514d5-fbf5-4b9d-bdac-6ca45bfd18dd\n", " 2016\n", - " Twins\n", - " Indians\n", - " 204\n", + " Phillies\n", + " Cubs\n", + " 164\n", " \n", " \n", " 5\n", - " 4be9f735-a98e-4689-87ce-852cc3a1e79d\n", + " 6a83e76c-dc0d-4924-9d3d-a2e7e0ab5b52\n", " 2016\n", - " Blue Jays\n", - " Orioles\n", - " 184\n", + " Diamondbacks\n", + " Cubs\n", + " 201\n", " \n", " \n", " 6\n", - " 0b2de8c3-11d9-4f0f-a186-25b59f34a5d8\n", + " 76ea8662-c7e6-4c38-8f2a-efe373e428ce\n", " 2016\n", - " Yankees\n", - " Mets\n", - " 182\n", + " Athletics\n", + " Cubs\n", + " 173\n", " \n", " \n", " 7\n", - " 60d80663-6ced-44aa-aad9-0f4bf8d3b4d2\n", + " 66fad23d-6e89-4f99-be29-d49b6e94f95d\n", " 2016\n", - " Red Sox\n", - " Rays\n", - " 191\n", + " Athletics\n", + " Cubs\n", + " 176\n", " \n", " \n", " 8\n", - " 7e1c2095-4fea-454c-8773-096ceb6fb05c\n", + " d977367c-cf0c-4687-95a0-eb4542efcb01\n", " 2016\n", - " Cardinals\n", - " Pirates\n", - " 201\n", + " Rockies\n", + " Cubs\n", + " 180\n", " \n", " \n", " 9\n", - " f7f24ce3-7f9d-4e8a-986e-095db847c4c1\n", + " a87070ff-1084-43ca-a7ba-69278f93ecba\n", " 2016\n", - " Rays\n", - " Twins\n", - " 189\n", + " Cardinals\n", + " Cubs\n", + " 157\n", " \n", " \n", " 10\n", - " 5c26e7fc-c99f-48b4-92c1-4a7208c8cfe9\n", + " ea6b350d-3c1d-4737-878d-4465f66999f6\n", " 2016\n", - " Rays\n", - " Twins\n", - " 177\n", + " Cardinals\n", + " Cubs\n", + " 218\n", " \n", " \n", " 11\n", - " 6d2cab13-dd85-477a-8769-669069f85836\n", + " 46463c50-0f5c-4dca-a661-dd194464e791\n", " 2016\n", - " Royals\n", - " Rays\n", - " 183\n", + " Cardinals\n", + " Cubs\n", + " 160\n", " \n", " \n", " 12\n", - " bca90342-7ddc-468e-b189-d43fad7528ec\n", + " 59134e6d-9d13-49aa-978e-c3c2300eb90f\n", " 2016\n", - " Astros\n", - " Rays\n", - " 194\n", + " Pirates\n", + " Cubs\n", + " 178\n", " \n", " \n", " 13\n", - " 630f4f78-03cc-43c1-9e57-ababb9c11418\n", + " 387630a3-a894-4327-baa1-b24ec1a654d9\n", " 2016\n", - " Dodgers\n", - " Giants\n", - " 178\n", + " Pirates\n", + " Cubs\n", + " 205\n", " \n", " \n", " 14\n", - " c0cf1376-1115-4a2f-b457-3f82bbc41a89\n", + " 5d084e13-94fd-4995-b95a-4801ea3ed556\n", " 2016\n", - " Tigers\n", - " White Sox\n", - " 193\n", + " Giants\n", + " Cubs\n", + " 197\n", " \n", " \n", " 15\n", - " 46463c50-0f5c-4dca-a661-dd194464e791\n", + " 34444c94-03ec-4d12-96af-68b8f399a22f\n", " 2016\n", - " Cardinals\n", + " Reds\n", " Cubs\n", - " 160\n", + " 198\n", " \n", " \n", " 16\n", - " 392ad56d-972e-4f77-98e2-5f8577931cf8\n", + " 9580bffe-22e1-4975-978b-1b13e7505193\n", " 2016\n", - " Giants\n", - " Cardinals\n", - " 169\n", + " Reds\n", + " Cubs\n", + " 188\n", " \n", " \n", " 17\n", - " 307730fa-bbed-4221-b4e6-a2492f546fd5\n", + " 645e6a08-afd6-4677-a5c9-01ef446b0cf3\n", " 2016\n", - " Red Sox\n", - " Twins\n", - " 251\n", + " Reds\n", + " Cubs\n", + " 188\n", " \n", " \n", " 18\n", - " 1cbc558f-7615-4fa9-bf97-7ccd62040d6f\n", + " 08981bd8-d1d7-48e1-8668-9098b8f7fe90\n", " 2016\n", - " Mets\n", - " Braves\n", - " 151\n", + " Reds\n", + " Cubs\n", + " 194\n", " \n", " \n", " 19\n", - " 723348ba-1645-43fc-9e22-92994f7a63bd\n", + " 303703bb-b55f-476d-8faf-bf582169fb1d\n", " 2016\n", - " Athletics\n", - " Twins\n", - " 153\n", + " Padres\n", + " Cubs\n", + " 175\n", " \n", " \n", " 20\n", - " ffbd6ecc-82e1-4e5d-9bd1-4ea210be5992\n", + " 71ab82a4-6e07-430a-b695-1af3bc42ea61\n", " 2016\n", - " Twins\n", - " Marlins\n", - " 185\n", + " Nationals\n", + " Cubs\n", + " 257\n", " \n", " \n", " 21\n", - " f2747230-7df5-4535-a475-a1c823d0d654\n", + " d1a110c2-f6c8-4029-bcd8-2f8a01e1561c\n", " 2016\n", - " Twins\n", - " Yankees\n", - " 180\n", + " Brewers\n", + " Cubs\n", + " 178\n", " \n", " \n", " 22\n", - " db3b6f35-a7a4-430a-8703-2b2f25103e17\n", + " 6d111b57-fa0b-4f24-82df-ff33a26f0252\n", " 2016\n", - " White Sox\n", - " Orioles\n", - " 199\n", + " Brewers\n", + " Cubs\n", + " 171\n", " \n", " \n", " 23\n", - " 5fc8c6f0-a70e-4d1b-877f-eb1ec8e6f636\n", + " a97e9539-bbbd-4e03-bf15-f25ea2c1d923\n", " 2016\n", - " Diamondbacks\n", - " Giants\n", - " 175\n", + " Brewers\n", + " Cubs\n", + " 248\n", " \n", " \n", " 24\n", - " 95d548b6-2da8-4644-812e-b277fec5b91f\n", + " dc0c9218-505c-4725-8c0c-40b72cca0956\n", " 2016\n", - " Braves\n", - " Mets\n", - " 201\n", + " Astros\n", + " Cubs\n", + " 174\n", " \n", " \n", "\n", @@ -3269,64 +3255,64 @@ ], "text/plain": [ " gameId year homeTeamName awayTeamName \\\n", - "0 d60c6036-0ce1-4c90-8dd9-de3b403c92a8 2016 Nationals Brewers \n", - "1 af72a0b9-65f7-49fb-9b30-d505068bdf6d 2016 Reds Brewers \n", - "2 f57e1271-d217-400a-aea6-2e2d7d6a59a0 2016 Orioles Rays \n", - "3 198f4eed-a29f-41e2-8623-cb261e5ab370 2016 Rockies Giants \n", - "4 cb3ef033-dd57-41fd-b206-cdd3bc12c74f 2016 Twins Indians \n", - "5 4be9f735-a98e-4689-87ce-852cc3a1e79d 2016 Blue Jays Orioles \n", - "6 0b2de8c3-11d9-4f0f-a186-25b59f34a5d8 2016 Yankees Mets \n", - "7 60d80663-6ced-44aa-aad9-0f4bf8d3b4d2 2016 Red Sox Rays \n", - "8 7e1c2095-4fea-454c-8773-096ceb6fb05c 2016 Cardinals Pirates \n", - "9 f7f24ce3-7f9d-4e8a-986e-095db847c4c1 2016 Rays Twins \n", - "10 5c26e7fc-c99f-48b4-92c1-4a7208c8cfe9 2016 Rays Twins \n", - "11 6d2cab13-dd85-477a-8769-669069f85836 2016 Royals Rays \n", - "12 bca90342-7ddc-468e-b189-d43fad7528ec 2016 Astros Rays \n", - "13 630f4f78-03cc-43c1-9e57-ababb9c11418 2016 Dodgers Giants \n", - "14 c0cf1376-1115-4a2f-b457-3f82bbc41a89 2016 Tigers White Sox \n", - "15 46463c50-0f5c-4dca-a661-dd194464e791 2016 Cardinals Cubs \n", - "16 392ad56d-972e-4f77-98e2-5f8577931cf8 2016 Giants Cardinals \n", - "17 307730fa-bbed-4221-b4e6-a2492f546fd5 2016 Red Sox Twins \n", - "18 1cbc558f-7615-4fa9-bf97-7ccd62040d6f 2016 Mets Braves \n", - "19 723348ba-1645-43fc-9e22-92994f7a63bd 2016 Athletics Twins \n", - "20 ffbd6ecc-82e1-4e5d-9bd1-4ea210be5992 2016 Twins Marlins \n", - "21 f2747230-7df5-4535-a475-a1c823d0d654 2016 Twins Yankees \n", - "22 db3b6f35-a7a4-430a-8703-2b2f25103e17 2016 White Sox Orioles \n", - "23 5fc8c6f0-a70e-4d1b-877f-eb1ec8e6f636 2016 Diamondbacks Giants \n", - "24 95d548b6-2da8-4644-812e-b277fec5b91f 2016 Braves Mets \n", + "0 e14b6493-9e7f-404f-840a-8a680cc364bf 2016 Marlins Cubs \n", + "1 1f32b347-cbcb-4c31-a145-0e685306d168 2016 Marlins Cubs \n", + "2 0c2292d1-7398-48be-bf8e-b41dad5e1a43 2016 Braves Cubs \n", + "3 8fbec734-a15a-42ab-8d51-60790de7750b 2016 Braves Cubs \n", + "4 89e514d5-fbf5-4b9d-bdac-6ca45bfd18dd 2016 Phillies Cubs \n", + "5 6a83e76c-dc0d-4924-9d3d-a2e7e0ab5b52 2016 Diamondbacks Cubs \n", + "6 76ea8662-c7e6-4c38-8f2a-efe373e428ce 2016 Athletics Cubs \n", + "7 66fad23d-6e89-4f99-be29-d49b6e94f95d 2016 Athletics Cubs \n", + "8 d977367c-cf0c-4687-95a0-eb4542efcb01 2016 Rockies Cubs \n", + "9 a87070ff-1084-43ca-a7ba-69278f93ecba 2016 Cardinals Cubs \n", + "10 ea6b350d-3c1d-4737-878d-4465f66999f6 2016 Cardinals Cubs \n", + "11 46463c50-0f5c-4dca-a661-dd194464e791 2016 Cardinals Cubs \n", + "12 59134e6d-9d13-49aa-978e-c3c2300eb90f 2016 Pirates Cubs \n", + "13 387630a3-a894-4327-baa1-b24ec1a654d9 2016 Pirates Cubs \n", + "14 5d084e13-94fd-4995-b95a-4801ea3ed556 2016 Giants Cubs \n", + "15 34444c94-03ec-4d12-96af-68b8f399a22f 2016 Reds Cubs \n", + "16 9580bffe-22e1-4975-978b-1b13e7505193 2016 Reds Cubs \n", + "17 645e6a08-afd6-4677-a5c9-01ef446b0cf3 2016 Reds Cubs \n", + "18 08981bd8-d1d7-48e1-8668-9098b8f7fe90 2016 Reds Cubs \n", + "19 303703bb-b55f-476d-8faf-bf582169fb1d 2016 Padres Cubs \n", + "20 71ab82a4-6e07-430a-b695-1af3bc42ea61 2016 Nationals Cubs \n", + "21 d1a110c2-f6c8-4029-bcd8-2f8a01e1561c 2016 Brewers Cubs \n", + "22 6d111b57-fa0b-4f24-82df-ff33a26f0252 2016 Brewers Cubs \n", + "23 a97e9539-bbbd-4e03-bf15-f25ea2c1d923 2016 Brewers Cubs \n", + "24 dc0c9218-505c-4725-8c0c-40b72cca0956 2016 Astros Cubs \n", "\n", " duration_minutes \n", - "0 167 \n", - "1 172 \n", - "2 166 \n", - "3 182 \n", - "4 204 \n", - "5 184 \n", - "6 182 \n", - "7 191 \n", - "8 201 \n", - "9 189 \n", - "10 177 \n", - "11 183 \n", - "12 194 \n", - "13 178 \n", - "14 193 \n", - "15 160 \n", - "16 169 \n", - "17 251 \n", - "18 151 \n", - "19 153 \n", - "20 185 \n", - "21 180 \n", - "22 199 \n", - "23 175 \n", - "24 201 \n", + "0 187 \n", + "1 189 \n", + "2 165 \n", + "3 222 \n", + "4 164 \n", + "5 201 \n", + "6 173 \n", + "7 176 \n", + "8 180 \n", + "9 157 \n", + "10 218 \n", + "11 160 \n", + "12 178 \n", + "13 205 \n", + "14 197 \n", + "15 198 \n", + "16 188 \n", + "17 188 \n", + "18 194 \n", + "19 175 \n", + "20 257 \n", + "21 178 \n", + "22 171 \n", + "23 248 \n", + "24 174 \n", "...\n", "\n", "[4862 rows x 5 columns]" ] }, - "execution_count": 18, + "execution_count": 19, "metadata": {}, "output_type": "execute_result" } @@ -3346,29 +3332,19 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 20, "id": "ad1f86f1-890b-462b-b408-b94c073371ff", "metadata": {}, "outputs": [ { "data": { - "text/html": [ - "Query job 0745cde9-9175-4e11-9721-f0c58fae90a2 is DONE. 79.9 kB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job 4c7a65d6-63b7-44b6-8249-171139f907f5 is DONE. 0 Bytes processed. Open Job" - ], + "application/vnd.jupyter.widget-view+json": { + "model_id": "2d064a62c8424a93a0359eea715b4969", + "version_major": 2, + "version_minor": 0 + }, "text/plain": [ - "" + "HTML(value='Query job 79bc2f65-5c7e-470d-b30e-a959838f0ed9 is DONE. 174.4 kB processed. Open Job" - ], + "application/vnd.jupyter.widget-view+json": { + "model_id": "5136bbb1905d4d8d9d953d770f6dadf9", + "version_major": 2, + "version_minor": 0 + }, "text/plain": [ - "" + "HTML(value='Query job 9de0da3d-b43c-4381-8ecb-5b8c6d9d2c8b is DONE. 193.8 kB processed. Open Job" - ], + "application/vnd.jupyter.widget-view+json": { + "model_id": "3823a6fb05e84f8986962d044559accb", + "version_major": 2, + "version_minor": 0 + }, "text/plain": [ - "" + "HTML(value='Query job 15cdbf31-e68a-41f0-9c5b-ea4ce49345f0 is DONE. 0 Bytes processed. Open Job" - ], + "application/vnd.jupyter.widget-view+json": { + "model_id": "6e3bbb5866d244cda7418890ca99766a", + "version_major": 2, + "version_minor": 0 + }, "text/plain": [ - "" + "HTML(value='Query job 1e56a960-3fcd-421a-8500-bc1c099ae7a1 is DONE. 0 Bytes processed. \n", " \n", " 0\n", - " d60c6036-0ce1-4c90-8dd9-de3b403c92a8\n", + " e14b6493-9e7f-404f-840a-8a680cc364bf\n", " 2016\n", - " Nationals\n", - " Brewers\n", - " 167\n", + " Marlins\n", + " Cubs\n", + " 187\n", " \n", " \n", " 1\n", - " af72a0b9-65f7-49fb-9b30-d505068bdf6d\n", + " 1f32b347-cbcb-4c31-a145-0e685306d168\n", " 2016\n", - " Reds\n", - " Brewers\n", - " 172\n", + " Marlins\n", + " Cubs\n", + " 189\n", " \n", " \n", " 2\n", - " f57e1271-d217-400a-aea6-2e2d7d6a59a0\n", + " 0c2292d1-7398-48be-bf8e-b41dad5e1a43\n", " 2016\n", - " Orioles\n", - " Rays\n", - " 166\n", + " Braves\n", + " Cubs\n", + " 165\n", " \n", " \n", " 3\n", - " 198f4eed-a29f-41e2-8623-cb261e5ab370\n", + " 8fbec734-a15a-42ab-8d51-60790de7750b\n", " 2016\n", - " Rockies\n", - " Giants\n", - " 182\n", + " Braves\n", + " Cubs\n", + " 222\n", " \n", " \n", " 4\n", - " cb3ef033-dd57-41fd-b206-cdd3bc12c74f\n", + " 89e514d5-fbf5-4b9d-bdac-6ca45bfd18dd\n", " 2016\n", - " Twins\n", - " Indians\n", - " 204\n", + " Phillies\n", + " Cubs\n", + " 164\n", " \n", " \n", " 5\n", - " 4be9f735-a98e-4689-87ce-852cc3a1e79d\n", + " 6a83e76c-dc0d-4924-9d3d-a2e7e0ab5b52\n", " 2016\n", - " Blue Jays\n", - " Orioles\n", - " 184\n", + " Diamondbacks\n", + " Cubs\n", + " 201\n", " \n", " \n", " 6\n", - " 0b2de8c3-11d9-4f0f-a186-25b59f34a5d8\n", + " 76ea8662-c7e6-4c38-8f2a-efe373e428ce\n", " 2016\n", - " Yankees\n", - " Mets\n", - " 182\n", + " Athletics\n", + " Cubs\n", + " 173\n", " \n", " \n", " 7\n", - " 60d80663-6ced-44aa-aad9-0f4bf8d3b4d2\n", + " 66fad23d-6e89-4f99-be29-d49b6e94f95d\n", " 2016\n", - " Red Sox\n", - " Rays\n", - " 191\n", + " Athletics\n", + " Cubs\n", + " 176\n", " \n", " \n", " 8\n", - " 7e1c2095-4fea-454c-8773-096ceb6fb05c\n", + " d977367c-cf0c-4687-95a0-eb4542efcb01\n", " 2016\n", - " Cardinals\n", - " Pirates\n", - " 201\n", + " Rockies\n", + " Cubs\n", + " 180\n", " \n", " \n", " 9\n", - " f7f24ce3-7f9d-4e8a-986e-095db847c4c1\n", + " a87070ff-1084-43ca-a7ba-69278f93ecba\n", " 2016\n", - " Rays\n", - " Twins\n", - " 189\n", + " Cardinals\n", + " Cubs\n", + " 157\n", " \n", " \n", " 10\n", - " 5c26e7fc-c99f-48b4-92c1-4a7208c8cfe9\n", + " ea6b350d-3c1d-4737-878d-4465f66999f6\n", " 2016\n", - " Rays\n", - " Twins\n", - " 177\n", + " Cardinals\n", + " Cubs\n", + " 218\n", " \n", " \n", " 11\n", - " 6d2cab13-dd85-477a-8769-669069f85836\n", + " 46463c50-0f5c-4dca-a661-dd194464e791\n", " 2016\n", - " Royals\n", - " Rays\n", - " 183\n", + " Cardinals\n", + " Cubs\n", + " 160\n", " \n", " \n", " 12\n", - " bca90342-7ddc-468e-b189-d43fad7528ec\n", + " 59134e6d-9d13-49aa-978e-c3c2300eb90f\n", " 2016\n", - " Astros\n", - " Rays\n", - " 194\n", + " Pirates\n", + " Cubs\n", + " 178\n", " \n", " \n", " 13\n", - " 630f4f78-03cc-43c1-9e57-ababb9c11418\n", + " 387630a3-a894-4327-baa1-b24ec1a654d9\n", " 2016\n", - " Dodgers\n", - " Giants\n", - " 178\n", + " Pirates\n", + " Cubs\n", + " 205\n", " \n", " \n", " 14\n", - " c0cf1376-1115-4a2f-b457-3f82bbc41a89\n", + " 5d084e13-94fd-4995-b95a-4801ea3ed556\n", " 2016\n", - " Tigers\n", - " White Sox\n", - " 193\n", + " Giants\n", + " Cubs\n", + " 197\n", " \n", " \n", " 15\n", - " 46463c50-0f5c-4dca-a661-dd194464e791\n", + " 34444c94-03ec-4d12-96af-68b8f399a22f\n", " 2016\n", - " Cardinals\n", + " Reds\n", " Cubs\n", - " 160\n", + " 198\n", " \n", " \n", " 16\n", - " 392ad56d-972e-4f77-98e2-5f8577931cf8\n", + " 9580bffe-22e1-4975-978b-1b13e7505193\n", " 2016\n", - " Giants\n", - " Cardinals\n", - " 169\n", + " Reds\n", + " Cubs\n", + " 188\n", " \n", " \n", " 17\n", - " 307730fa-bbed-4221-b4e6-a2492f546fd5\n", + " 645e6a08-afd6-4677-a5c9-01ef446b0cf3\n", " 2016\n", - " Red Sox\n", - " Twins\n", - " 251\n", + " Reds\n", + " Cubs\n", + " 188\n", " \n", " \n", " 18\n", - " 1cbc558f-7615-4fa9-bf97-7ccd62040d6f\n", + " 08981bd8-d1d7-48e1-8668-9098b8f7fe90\n", " 2016\n", - " Mets\n", - " Braves\n", - " 151\n", + " Reds\n", + " Cubs\n", + " 194\n", " \n", " \n", " 19\n", - " 723348ba-1645-43fc-9e22-92994f7a63bd\n", + " 303703bb-b55f-476d-8faf-bf582169fb1d\n", " 2016\n", - " Athletics\n", - " Twins\n", - " 153\n", + " Padres\n", + " Cubs\n", + " 175\n", " \n", " \n", " 20\n", - " ffbd6ecc-82e1-4e5d-9bd1-4ea210be5992\n", + " 71ab82a4-6e07-430a-b695-1af3bc42ea61\n", " 2016\n", - " Twins\n", - " Marlins\n", - " 185\n", + " Nationals\n", + " Cubs\n", + " 257\n", " \n", " \n", " 21\n", - " f2747230-7df5-4535-a475-a1c823d0d654\n", + " d1a110c2-f6c8-4029-bcd8-2f8a01e1561c\n", " 2016\n", - " Twins\n", - " Yankees\n", - " 180\n", + " Brewers\n", + " Cubs\n", + " 178\n", " \n", " \n", " 22\n", - " db3b6f35-a7a4-430a-8703-2b2f25103e17\n", + " 6d111b57-fa0b-4f24-82df-ff33a26f0252\n", " 2016\n", - " White Sox\n", - " Orioles\n", - " 199\n", + " Brewers\n", + " Cubs\n", + " 171\n", " \n", " \n", " 23\n", - " 5fc8c6f0-a70e-4d1b-877f-eb1ec8e6f636\n", + " a97e9539-bbbd-4e03-bf15-f25ea2c1d923\n", " 2016\n", - " Diamondbacks\n", - " Giants\n", - " 175\n", + " Brewers\n", + " Cubs\n", + " 248\n", " \n", " \n", " 24\n", - " 95d548b6-2da8-4644-812e-b277fec5b91f\n", + " dc0c9218-505c-4725-8c0c-40b72cca0956\n", " 2016\n", - " Braves\n", - " Mets\n", - " 201\n", + " Astros\n", + " Cubs\n", + " 174\n", " \n", " \n", "\n", @@ -3802,64 +3710,64 @@ ], "text/plain": [ " gameId year HOME TEAM awayTeamName \\\n", - "0 d60c6036-0ce1-4c90-8dd9-de3b403c92a8 2016 Nationals Brewers \n", - "1 af72a0b9-65f7-49fb-9b30-d505068bdf6d 2016 Reds Brewers \n", - "2 f57e1271-d217-400a-aea6-2e2d7d6a59a0 2016 Orioles Rays \n", - "3 198f4eed-a29f-41e2-8623-cb261e5ab370 2016 Rockies Giants \n", - "4 cb3ef033-dd57-41fd-b206-cdd3bc12c74f 2016 Twins Indians \n", - "5 4be9f735-a98e-4689-87ce-852cc3a1e79d 2016 Blue Jays Orioles \n", - "6 0b2de8c3-11d9-4f0f-a186-25b59f34a5d8 2016 Yankees Mets \n", - "7 60d80663-6ced-44aa-aad9-0f4bf8d3b4d2 2016 Red Sox Rays \n", - "8 7e1c2095-4fea-454c-8773-096ceb6fb05c 2016 Cardinals Pirates \n", - "9 f7f24ce3-7f9d-4e8a-986e-095db847c4c1 2016 Rays Twins \n", - "10 5c26e7fc-c99f-48b4-92c1-4a7208c8cfe9 2016 Rays Twins \n", - "11 6d2cab13-dd85-477a-8769-669069f85836 2016 Royals Rays \n", - "12 bca90342-7ddc-468e-b189-d43fad7528ec 2016 Astros Rays \n", - "13 630f4f78-03cc-43c1-9e57-ababb9c11418 2016 Dodgers Giants \n", - "14 c0cf1376-1115-4a2f-b457-3f82bbc41a89 2016 Tigers White Sox \n", - "15 46463c50-0f5c-4dca-a661-dd194464e791 2016 Cardinals Cubs \n", - "16 392ad56d-972e-4f77-98e2-5f8577931cf8 2016 Giants Cardinals \n", - "17 307730fa-bbed-4221-b4e6-a2492f546fd5 2016 Red Sox Twins \n", - "18 1cbc558f-7615-4fa9-bf97-7ccd62040d6f 2016 Mets Braves \n", - "19 723348ba-1645-43fc-9e22-92994f7a63bd 2016 Athletics Twins \n", - "20 ffbd6ecc-82e1-4e5d-9bd1-4ea210be5992 2016 Twins Marlins \n", - "21 f2747230-7df5-4535-a475-a1c823d0d654 2016 Twins Yankees \n", - "22 db3b6f35-a7a4-430a-8703-2b2f25103e17 2016 White Sox Orioles \n", - "23 5fc8c6f0-a70e-4d1b-877f-eb1ec8e6f636 2016 Diamondbacks Giants \n", - "24 95d548b6-2da8-4644-812e-b277fec5b91f 2016 Braves Mets \n", + "0 e14b6493-9e7f-404f-840a-8a680cc364bf 2016 Marlins Cubs \n", + "1 1f32b347-cbcb-4c31-a145-0e685306d168 2016 Marlins Cubs \n", + "2 0c2292d1-7398-48be-bf8e-b41dad5e1a43 2016 Braves Cubs \n", + "3 8fbec734-a15a-42ab-8d51-60790de7750b 2016 Braves Cubs \n", + "4 89e514d5-fbf5-4b9d-bdac-6ca45bfd18dd 2016 Phillies Cubs \n", + "5 6a83e76c-dc0d-4924-9d3d-a2e7e0ab5b52 2016 Diamondbacks Cubs \n", + "6 76ea8662-c7e6-4c38-8f2a-efe373e428ce 2016 Athletics Cubs \n", + "7 66fad23d-6e89-4f99-be29-d49b6e94f95d 2016 Athletics Cubs \n", + "8 d977367c-cf0c-4687-95a0-eb4542efcb01 2016 Rockies Cubs \n", + "9 a87070ff-1084-43ca-a7ba-69278f93ecba 2016 Cardinals Cubs \n", + "10 ea6b350d-3c1d-4737-878d-4465f66999f6 2016 Cardinals Cubs \n", + "11 46463c50-0f5c-4dca-a661-dd194464e791 2016 Cardinals Cubs \n", + "12 59134e6d-9d13-49aa-978e-c3c2300eb90f 2016 Pirates Cubs \n", + "13 387630a3-a894-4327-baa1-b24ec1a654d9 2016 Pirates Cubs \n", + "14 5d084e13-94fd-4995-b95a-4801ea3ed556 2016 Giants Cubs \n", + "15 34444c94-03ec-4d12-96af-68b8f399a22f 2016 Reds Cubs \n", + "16 9580bffe-22e1-4975-978b-1b13e7505193 2016 Reds Cubs \n", + "17 645e6a08-afd6-4677-a5c9-01ef446b0cf3 2016 Reds Cubs \n", + "18 08981bd8-d1d7-48e1-8668-9098b8f7fe90 2016 Reds Cubs \n", + "19 303703bb-b55f-476d-8faf-bf582169fb1d 2016 Padres Cubs \n", + "20 71ab82a4-6e07-430a-b695-1af3bc42ea61 2016 Nationals Cubs \n", + "21 d1a110c2-f6c8-4029-bcd8-2f8a01e1561c 2016 Brewers Cubs \n", + "22 6d111b57-fa0b-4f24-82df-ff33a26f0252 2016 Brewers Cubs \n", + "23 a97e9539-bbbd-4e03-bf15-f25ea2c1d923 2016 Brewers Cubs \n", + "24 dc0c9218-505c-4725-8c0c-40b72cca0956 2016 Astros Cubs \n", "\n", " duration_minutes \n", - "0 167 \n", - "1 172 \n", - "2 166 \n", - "3 182 \n", - "4 204 \n", - "5 184 \n", - "6 182 \n", - "7 191 \n", - "8 201 \n", - "9 189 \n", - "10 177 \n", - "11 183 \n", - "12 194 \n", - "13 178 \n", - "14 193 \n", - "15 160 \n", - "16 169 \n", - "17 251 \n", - "18 151 \n", - "19 153 \n", - "20 185 \n", - "21 180 \n", - "22 199 \n", - "23 175 \n", - "24 201 \n", + "0 187 \n", + "1 189 \n", + "2 165 \n", + "3 222 \n", + "4 164 \n", + "5 201 \n", + "6 173 \n", + "7 176 \n", + "8 180 \n", + "9 157 \n", + "10 218 \n", + "11 160 \n", + "12 178 \n", + "13 205 \n", + "14 197 \n", + "15 198 \n", + "16 188 \n", + "17 188 \n", + "18 194 \n", + "19 175 \n", + "20 257 \n", + "21 178 \n", + "22 171 \n", + "23 248 \n", + "24 174 \n", "...\n", "\n", "[2431 rows x 5 columns]" ] }, - "execution_count": 21, + "execution_count": 22, "metadata": {}, "output_type": "execute_result" } @@ -3870,17 +3778,19 @@ }, { "cell_type": "code", - "execution_count": 22, + "execution_count": 23, "id": "ac3ceabe-4317-453c-9418-826de5094454", "metadata": {}, "outputs": [ { "data": { - "text/html": [ - "Query job c90e0cd4-30e5-427c-8f5f-a0a8c778bc62 is DONE. 0 Bytes processed. Open Job" - ], + "application/vnd.jupyter.widget-view+json": { + "model_id": "0c8aa06c869446f09a41c5dff15dc682", + "version_major": 2, + "version_minor": 0 + }, "text/plain": [ - "" + "HTML(value='Query job f21ef830-99b1-4fce-ab9a-378e12a04587 is DONE. 0 Bytes processed. Open Job" - ], + "application/vnd.jupyter.widget-view+json": { + "model_id": "2c03c2a860c847b5a27d1c3f2188e323", + "version_major": 2, + "version_minor": 0 + }, "text/plain": [ - "" + "HTML(value='Query job 88f63cc1-9691-4e6c-8acf-650f31ab8560 is DONE. 0 Bytes processed. \n", " \n", " 0\n", - " d60c6036-0ce1-4c90-8dd9-de3b403c92a8\n", + " e14b6493-9e7f-404f-840a-8a680cc364bf\n", " 2016\n", - " Nationals\n", - " Brewers\n", - " 167\n", + " Marlins\n", + " Cubs\n", + " 187\n", " \n", " \n", " 1\n", - " af72a0b9-65f7-49fb-9b30-d505068bdf6d\n", + " 1f32b347-cbcb-4c31-a145-0e685306d168\n", " 2016\n", - " Reds\n", - " Brewers\n", - " 172\n", + " Marlins\n", + " Cubs\n", + " 189\n", " \n", " \n", " 2\n", - " f57e1271-d217-400a-aea6-2e2d7d6a59a0\n", + " 0c2292d1-7398-48be-bf8e-b41dad5e1a43\n", " 2016\n", - " Orioles\n", - " Rays\n", - " 166\n", + " Braves\n", + " Cubs\n", + " 165\n", " \n", " \n", " 3\n", - " 198f4eed-a29f-41e2-8623-cb261e5ab370\n", + " 8fbec734-a15a-42ab-8d51-60790de7750b\n", " 2016\n", - " Rockies\n", - " Giants\n", - " 182\n", + " Braves\n", + " Cubs\n", + " 222\n", " \n", " \n", " 4\n", - " cb3ef033-dd57-41fd-b206-cdd3bc12c74f\n", + " 89e514d5-fbf5-4b9d-bdac-6ca45bfd18dd\n", " 2016\n", - " Twins\n", - " Indians\n", - " 204\n", + " Phillies\n", + " Cubs\n", + " 164\n", " \n", " \n", " 5\n", - " 4be9f735-a98e-4689-87ce-852cc3a1e79d\n", + " 6a83e76c-dc0d-4924-9d3d-a2e7e0ab5b52\n", " 2016\n", - " Blue Jays\n", - " Orioles\n", - " 184\n", + " Diamondbacks\n", + " Cubs\n", + " 201\n", " \n", " \n", " 6\n", - " 0b2de8c3-11d9-4f0f-a186-25b59f34a5d8\n", + " 76ea8662-c7e6-4c38-8f2a-efe373e428ce\n", " 2016\n", - " Yankees\n", - " Mets\n", - " 182\n", + " Athletics\n", + " Cubs\n", + " 173\n", " \n", " \n", " 7\n", - " 60d80663-6ced-44aa-aad9-0f4bf8d3b4d2\n", + " 66fad23d-6e89-4f99-be29-d49b6e94f95d\n", " 2016\n", - " Red Sox\n", - " Rays\n", - " 191\n", + " Athletics\n", + " Cubs\n", + " 176\n", " \n", " \n", " 8\n", - " 7e1c2095-4fea-454c-8773-096ceb6fb05c\n", + " d977367c-cf0c-4687-95a0-eb4542efcb01\n", " 2016\n", - " Cardinals\n", - " Pirates\n", - " 201\n", + " Rockies\n", + " Cubs\n", + " 180\n", " \n", " \n", " 9\n", - " f7f24ce3-7f9d-4e8a-986e-095db847c4c1\n", + " a87070ff-1084-43ca-a7ba-69278f93ecba\n", " 2016\n", - " Rays\n", - " Twins\n", - " 189\n", + " Cardinals\n", + " Cubs\n", + " 157\n", " \n", " \n", " 10\n", - " 5c26e7fc-c99f-48b4-92c1-4a7208c8cfe9\n", + " ea6b350d-3c1d-4737-878d-4465f66999f6\n", " 2016\n", - " Rays\n", - " Twins\n", - " 177\n", + " Cardinals\n", + " Cubs\n", + " 218\n", " \n", " \n", " 11\n", - " 6d2cab13-dd85-477a-8769-669069f85836\n", + " 46463c50-0f5c-4dca-a661-dd194464e791\n", " 2016\n", - " Royals\n", - " Rays\n", - " 183\n", + " Cardinals\n", + " Cubs\n", + " 160\n", " \n", " \n", " 12\n", - " bca90342-7ddc-468e-b189-d43fad7528ec\n", + " 59134e6d-9d13-49aa-978e-c3c2300eb90f\n", " 2016\n", - " Astros\n", - " Rays\n", - " 194\n", + " Pirates\n", + " Cubs\n", + " 178\n", " \n", " \n", " 13\n", - " 630f4f78-03cc-43c1-9e57-ababb9c11418\n", + " 387630a3-a894-4327-baa1-b24ec1a654d9\n", " 2016\n", - " Dodgers\n", - " Giants\n", - " 178\n", + " Pirates\n", + " Cubs\n", + " 205\n", " \n", " \n", " 14\n", - " c0cf1376-1115-4a2f-b457-3f82bbc41a89\n", + " 5d084e13-94fd-4995-b95a-4801ea3ed556\n", " 2016\n", - " Tigers\n", - " White Sox\n", - " 193\n", + " Giants\n", + " Cubs\n", + " 197\n", " \n", " \n", " 15\n", - " 46463c50-0f5c-4dca-a661-dd194464e791\n", + " 34444c94-03ec-4d12-96af-68b8f399a22f\n", " 2016\n", - " Cardinals\n", + " Reds\n", " Cubs\n", - " 160\n", + " 198\n", " \n", " \n", " 16\n", - " 392ad56d-972e-4f77-98e2-5f8577931cf8\n", + " 9580bffe-22e1-4975-978b-1b13e7505193\n", " 2016\n", - " Giants\n", - " Cardinals\n", - " 169\n", + " Reds\n", + " Cubs\n", + " 188\n", " \n", " \n", " 17\n", - " 307730fa-bbed-4221-b4e6-a2492f546fd5\n", + " 645e6a08-afd6-4677-a5c9-01ef446b0cf3\n", " 2016\n", - " Red Sox\n", - " Twins\n", - " 251\n", + " Reds\n", + " Cubs\n", + " 188\n", " \n", " \n", " 18\n", - " 1cbc558f-7615-4fa9-bf97-7ccd62040d6f\n", + " 08981bd8-d1d7-48e1-8668-9098b8f7fe90\n", " 2016\n", - " Mets\n", - " Braves\n", - " 151\n", + " Reds\n", + " Cubs\n", + " 194\n", " \n", " \n", " 19\n", - " 723348ba-1645-43fc-9e22-92994f7a63bd\n", + " 303703bb-b55f-476d-8faf-bf582169fb1d\n", " 2016\n", - " Athletics\n", - " Twins\n", - " 153\n", + " Padres\n", + " Cubs\n", + " 175\n", " \n", " \n", " 20\n", - " ffbd6ecc-82e1-4e5d-9bd1-4ea210be5992\n", + " 71ab82a4-6e07-430a-b695-1af3bc42ea61\n", " 2016\n", - " Twins\n", - " Marlins\n", - " 185\n", + " Nationals\n", + " Cubs\n", + " 257\n", " \n", " \n", " 21\n", - " f2747230-7df5-4535-a475-a1c823d0d654\n", + " d1a110c2-f6c8-4029-bcd8-2f8a01e1561c\n", " 2016\n", - " Twins\n", - " Yankees\n", - " 180\n", + " Brewers\n", + " Cubs\n", + " 178\n", " \n", " \n", " 22\n", - " db3b6f35-a7a4-430a-8703-2b2f25103e17\n", + " 6d111b57-fa0b-4f24-82df-ff33a26f0252\n", " 2016\n", - " White Sox\n", - " Orioles\n", - " 199\n", + " Brewers\n", + " Cubs\n", + " 171\n", " \n", " \n", " 23\n", - " 5fc8c6f0-a70e-4d1b-877f-eb1ec8e6f636\n", + " a97e9539-bbbd-4e03-bf15-f25ea2c1d923\n", " 2016\n", - " Diamondbacks\n", - " Giants\n", - " 175\n", + " Brewers\n", + " Cubs\n", + " 248\n", " \n", " \n", " 24\n", - " 95d548b6-2da8-4644-812e-b277fec5b91f\n", + " dc0c9218-505c-4725-8c0c-40b72cca0956\n", " 2016\n", - " Braves\n", - " Mets\n", - " 201\n", + " Astros\n", + " Cubs\n", + " 174\n", " \n", " \n", "\n", @@ -4134,64 +4046,64 @@ ], "text/plain": [ " gameId year homeTeam!@#$%col awayTeamName \\\n", - "0 d60c6036-0ce1-4c90-8dd9-de3b403c92a8 2016 Nationals Brewers \n", - "1 af72a0b9-65f7-49fb-9b30-d505068bdf6d 2016 Reds Brewers \n", - "2 f57e1271-d217-400a-aea6-2e2d7d6a59a0 2016 Orioles Rays \n", - "3 198f4eed-a29f-41e2-8623-cb261e5ab370 2016 Rockies Giants \n", - "4 cb3ef033-dd57-41fd-b206-cdd3bc12c74f 2016 Twins Indians \n", - "5 4be9f735-a98e-4689-87ce-852cc3a1e79d 2016 Blue Jays Orioles \n", - "6 0b2de8c3-11d9-4f0f-a186-25b59f34a5d8 2016 Yankees Mets \n", - "7 60d80663-6ced-44aa-aad9-0f4bf8d3b4d2 2016 Red Sox Rays \n", - "8 7e1c2095-4fea-454c-8773-096ceb6fb05c 2016 Cardinals Pirates \n", - "9 f7f24ce3-7f9d-4e8a-986e-095db847c4c1 2016 Rays Twins \n", - "10 5c26e7fc-c99f-48b4-92c1-4a7208c8cfe9 2016 Rays Twins \n", - "11 6d2cab13-dd85-477a-8769-669069f85836 2016 Royals Rays \n", - "12 bca90342-7ddc-468e-b189-d43fad7528ec 2016 Astros Rays \n", - "13 630f4f78-03cc-43c1-9e57-ababb9c11418 2016 Dodgers Giants \n", - "14 c0cf1376-1115-4a2f-b457-3f82bbc41a89 2016 Tigers White Sox \n", - "15 46463c50-0f5c-4dca-a661-dd194464e791 2016 Cardinals Cubs \n", - "16 392ad56d-972e-4f77-98e2-5f8577931cf8 2016 Giants Cardinals \n", - "17 307730fa-bbed-4221-b4e6-a2492f546fd5 2016 Red Sox Twins \n", - "18 1cbc558f-7615-4fa9-bf97-7ccd62040d6f 2016 Mets Braves \n", - "19 723348ba-1645-43fc-9e22-92994f7a63bd 2016 Athletics Twins \n", - "20 ffbd6ecc-82e1-4e5d-9bd1-4ea210be5992 2016 Twins Marlins \n", - "21 f2747230-7df5-4535-a475-a1c823d0d654 2016 Twins Yankees \n", - "22 db3b6f35-a7a4-430a-8703-2b2f25103e17 2016 White Sox Orioles \n", - "23 5fc8c6f0-a70e-4d1b-877f-eb1ec8e6f636 2016 Diamondbacks Giants \n", - "24 95d548b6-2da8-4644-812e-b277fec5b91f 2016 Braves Mets \n", + "0 e14b6493-9e7f-404f-840a-8a680cc364bf 2016 Marlins Cubs \n", + "1 1f32b347-cbcb-4c31-a145-0e685306d168 2016 Marlins Cubs \n", + "2 0c2292d1-7398-48be-bf8e-b41dad5e1a43 2016 Braves Cubs \n", + "3 8fbec734-a15a-42ab-8d51-60790de7750b 2016 Braves Cubs \n", + "4 89e514d5-fbf5-4b9d-bdac-6ca45bfd18dd 2016 Phillies Cubs \n", + "5 6a83e76c-dc0d-4924-9d3d-a2e7e0ab5b52 2016 Diamondbacks Cubs \n", + "6 76ea8662-c7e6-4c38-8f2a-efe373e428ce 2016 Athletics Cubs \n", + "7 66fad23d-6e89-4f99-be29-d49b6e94f95d 2016 Athletics Cubs \n", + "8 d977367c-cf0c-4687-95a0-eb4542efcb01 2016 Rockies Cubs \n", + "9 a87070ff-1084-43ca-a7ba-69278f93ecba 2016 Cardinals Cubs \n", + "10 ea6b350d-3c1d-4737-878d-4465f66999f6 2016 Cardinals Cubs \n", + "11 46463c50-0f5c-4dca-a661-dd194464e791 2016 Cardinals Cubs \n", + "12 59134e6d-9d13-49aa-978e-c3c2300eb90f 2016 Pirates Cubs \n", + "13 387630a3-a894-4327-baa1-b24ec1a654d9 2016 Pirates Cubs \n", + "14 5d084e13-94fd-4995-b95a-4801ea3ed556 2016 Giants Cubs \n", + "15 34444c94-03ec-4d12-96af-68b8f399a22f 2016 Reds Cubs \n", + "16 9580bffe-22e1-4975-978b-1b13e7505193 2016 Reds Cubs \n", + "17 645e6a08-afd6-4677-a5c9-01ef446b0cf3 2016 Reds Cubs \n", + "18 08981bd8-d1d7-48e1-8668-9098b8f7fe90 2016 Reds Cubs \n", + "19 303703bb-b55f-476d-8faf-bf582169fb1d 2016 Padres Cubs \n", + "20 71ab82a4-6e07-430a-b695-1af3bc42ea61 2016 Nationals Cubs \n", + "21 d1a110c2-f6c8-4029-bcd8-2f8a01e1561c 2016 Brewers Cubs \n", + "22 6d111b57-fa0b-4f24-82df-ff33a26f0252 2016 Brewers Cubs \n", + "23 a97e9539-bbbd-4e03-bf15-f25ea2c1d923 2016 Brewers Cubs \n", + "24 dc0c9218-505c-4725-8c0c-40b72cca0956 2016 Astros Cubs \n", "\n", " duration_minutes \n", - "0 167 \n", - "1 172 \n", - "2 166 \n", - "3 182 \n", - "4 204 \n", - "5 184 \n", - "6 182 \n", - "7 191 \n", - "8 201 \n", - "9 189 \n", - "10 177 \n", - "11 183 \n", - "12 194 \n", - "13 178 \n", - "14 193 \n", - "15 160 \n", - "16 169 \n", - "17 251 \n", - "18 151 \n", - "19 153 \n", - "20 185 \n", - "21 180 \n", - "22 199 \n", - "23 175 \n", - "24 201 \n", + "0 187 \n", + "1 189 \n", + "2 165 \n", + "3 222 \n", + "4 164 \n", + "5 201 \n", + "6 173 \n", + "7 176 \n", + "8 180 \n", + "9 157 \n", + "10 218 \n", + "11 160 \n", + "12 178 \n", + "13 205 \n", + "14 197 \n", + "15 198 \n", + "16 188 \n", + "17 188 \n", + "18 194 \n", + "19 175 \n", + "20 257 \n", + "21 178 \n", + "22 171 \n", + "23 248 \n", + "24 174 \n", "...\n", "\n", "[2431 rows x 5 columns]" ] }, - "execution_count": 22, + "execution_count": 23, "metadata": {}, "output_type": "execute_result" } @@ -4202,17 +4114,19 @@ }, { "cell_type": "code", - "execution_count": 23, + "execution_count": 24, "id": "e73704c9-7aa9-4f10-b414-3417c3ad9eb8", "metadata": {}, "outputs": [ { - "data": { - "text/html": [ - "Query job 63c2d27f-382c-4a43-8fc1-135d9fd66a54 is DONE. 0 Bytes processed. Open Job" - ], + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "4eba676d1e7b4ead892828e33baa8534", + "version_major": 2, + "version_minor": 0 + }, "text/plain": [ - "" + "HTML(value='Query job 6f6e1d12-4202-434a-908a-3d0b34e70656 is DONE. 0 Bytes processed. Open Job" - ], + "application/vnd.jupyter.widget-view+json": { + "model_id": "6c1c07d67cc74b768664575511eb2a7f", + "version_major": 2, + "version_minor": 0 + }, "text/plain": [ - "" + "HTML(value='Query job 05ec231e-a7f0-4e41-ad60-5d8136a2e148 is DONE. 0 Bytes processed. \n", " \n", " 0\n", - " d60c6036-0ce1-4c90-8dd9-de3b403c92a8\n", + " e14b6493-9e7f-404f-840a-8a680cc364bf\n", " 2016\n", - " Nationals\n", - " Brewers\n", - " 167\n", + " Marlins\n", + " Cubs\n", + " 187\n", " \n", " \n", " 1\n", - " af72a0b9-65f7-49fb-9b30-d505068bdf6d\n", + " 1f32b347-cbcb-4c31-a145-0e685306d168\n", " 2016\n", - " Reds\n", - " Brewers\n", - " 172\n", + " Marlins\n", + " Cubs\n", + " 189\n", " \n", " \n", " 2\n", - " f57e1271-d217-400a-aea6-2e2d7d6a59a0\n", + " 0c2292d1-7398-48be-bf8e-b41dad5e1a43\n", " 2016\n", - " Orioles\n", - " Rays\n", - " 166\n", + " Braves\n", + " Cubs\n", + " 165\n", " \n", " \n", " 3\n", - " 198f4eed-a29f-41e2-8623-cb261e5ab370\n", + " 8fbec734-a15a-42ab-8d51-60790de7750b\n", " 2016\n", - " Rockies\n", - " Giants\n", - " 182\n", + " Braves\n", + " Cubs\n", + " 222\n", " \n", " \n", " 4\n", - " cb3ef033-dd57-41fd-b206-cdd3bc12c74f\n", + " 89e514d5-fbf5-4b9d-bdac-6ca45bfd18dd\n", " 2016\n", - " Twins\n", - " Indians\n", - " 204\n", + " Phillies\n", + " Cubs\n", + " 164\n", " \n", " \n", " 5\n", - " 4be9f735-a98e-4689-87ce-852cc3a1e79d\n", + " 6a83e76c-dc0d-4924-9d3d-a2e7e0ab5b52\n", " 2016\n", - " Blue Jays\n", - " Orioles\n", - " 184\n", + " Diamondbacks\n", + " Cubs\n", + " 201\n", " \n", " \n", " 6\n", - " 0b2de8c3-11d9-4f0f-a186-25b59f34a5d8\n", + " 76ea8662-c7e6-4c38-8f2a-efe373e428ce\n", " 2016\n", - " Yankees\n", - " Mets\n", - " 182\n", + " Athletics\n", + " Cubs\n", + " 173\n", " \n", " \n", " 7\n", - " 60d80663-6ced-44aa-aad9-0f4bf8d3b4d2\n", + " 66fad23d-6e89-4f99-be29-d49b6e94f95d\n", " 2016\n", - " Red Sox\n", - " Rays\n", - " 191\n", + " Athletics\n", + " Cubs\n", + " 176\n", " \n", " \n", " 8\n", - " 7e1c2095-4fea-454c-8773-096ceb6fb05c\n", + " d977367c-cf0c-4687-95a0-eb4542efcb01\n", " 2016\n", - " Cardinals\n", - " Pirates\n", - " 201\n", + " Rockies\n", + " Cubs\n", + " 180\n", " \n", " \n", " 9\n", - " f7f24ce3-7f9d-4e8a-986e-095db847c4c1\n", + " a87070ff-1084-43ca-a7ba-69278f93ecba\n", " 2016\n", - " Rays\n", - " Twins\n", - " 189\n", + " Cardinals\n", + " Cubs\n", + " 157\n", " \n", " \n", " 10\n", - " 5c26e7fc-c99f-48b4-92c1-4a7208c8cfe9\n", + " ea6b350d-3c1d-4737-878d-4465f66999f6\n", " 2016\n", - " Rays\n", - " Twins\n", - " 177\n", + " Cardinals\n", + " Cubs\n", + " 218\n", " \n", " \n", " 11\n", - " 6d2cab13-dd85-477a-8769-669069f85836\n", + " 46463c50-0f5c-4dca-a661-dd194464e791\n", " 2016\n", - " Royals\n", - " Rays\n", - " 183\n", + " Cardinals\n", + " Cubs\n", + " 160\n", " \n", " \n", " 12\n", - " bca90342-7ddc-468e-b189-d43fad7528ec\n", + " 59134e6d-9d13-49aa-978e-c3c2300eb90f\n", " 2016\n", - " Astros\n", - " Rays\n", - " 194\n", + " Pirates\n", + " Cubs\n", + " 178\n", " \n", " \n", " 13\n", - " 630f4f78-03cc-43c1-9e57-ababb9c11418\n", + " 387630a3-a894-4327-baa1-b24ec1a654d9\n", " 2016\n", - " Dodgers\n", - " Giants\n", - " 178\n", + " Pirates\n", + " Cubs\n", + " 205\n", " \n", " \n", " 14\n", - " c0cf1376-1115-4a2f-b457-3f82bbc41a89\n", + " 5d084e13-94fd-4995-b95a-4801ea3ed556\n", " 2016\n", - " Tigers\n", - " White Sox\n", - " 193\n", + " Giants\n", + " Cubs\n", + " 197\n", " \n", " \n", " 15\n", - " 46463c50-0f5c-4dca-a661-dd194464e791\n", + " 34444c94-03ec-4d12-96af-68b8f399a22f\n", " 2016\n", - " Cardinals\n", + " Reds\n", " Cubs\n", - " 160\n", + " 198\n", " \n", " \n", " 16\n", - " 392ad56d-972e-4f77-98e2-5f8577931cf8\n", + " 9580bffe-22e1-4975-978b-1b13e7505193\n", " 2016\n", - " Giants\n", - " Cardinals\n", - " 169\n", + " Reds\n", + " Cubs\n", + " 188\n", " \n", " \n", " 17\n", - " 307730fa-bbed-4221-b4e6-a2492f546fd5\n", + " 645e6a08-afd6-4677-a5c9-01ef446b0cf3\n", " 2016\n", - " Red Sox\n", - " Twins\n", - " 251\n", + " Reds\n", + " Cubs\n", + " 188\n", " \n", " \n", " 18\n", - " 1cbc558f-7615-4fa9-bf97-7ccd62040d6f\n", + " 08981bd8-d1d7-48e1-8668-9098b8f7fe90\n", " 2016\n", - " Mets\n", - " Braves\n", - " 151\n", + " Reds\n", + " Cubs\n", + " 194\n", " \n", " \n", " 19\n", - " 723348ba-1645-43fc-9e22-92994f7a63bd\n", + " 303703bb-b55f-476d-8faf-bf582169fb1d\n", " 2016\n", - " Athletics\n", - " Twins\n", - " 153\n", + " Padres\n", + " Cubs\n", + " 175\n", " \n", " \n", " 20\n", - " ffbd6ecc-82e1-4e5d-9bd1-4ea210be5992\n", + " 71ab82a4-6e07-430a-b695-1af3bc42ea61\n", " 2016\n", - " Twins\n", - " Marlins\n", - " 185\n", + " Nationals\n", + " Cubs\n", + " 257\n", " \n", " \n", " 21\n", - " f2747230-7df5-4535-a475-a1c823d0d654\n", + " d1a110c2-f6c8-4029-bcd8-2f8a01e1561c\n", " 2016\n", - " Twins\n", - " Yankees\n", - " 180\n", + " Brewers\n", + " Cubs\n", + " 178\n", " \n", " \n", " 22\n", - " db3b6f35-a7a4-430a-8703-2b2f25103e17\n", + " 6d111b57-fa0b-4f24-82df-ff33a26f0252\n", " 2016\n", - " White Sox\n", - " Orioles\n", - " 199\n", + " Brewers\n", + " Cubs\n", + " 171\n", " \n", " \n", " 23\n", - " 5fc8c6f0-a70e-4d1b-877f-eb1ec8e6f636\n", + " a97e9539-bbbd-4e03-bf15-f25ea2c1d923\n", " 2016\n", - " Diamondbacks\n", - " Giants\n", - " 175\n", + " Brewers\n", + " Cubs\n", + " 248\n", " \n", " \n", " 24\n", - " 95d548b6-2da8-4644-812e-b277fec5b91f\n", + " dc0c9218-505c-4725-8c0c-40b72cca0956\n", " 2016\n", - " Braves\n", - " Mets\n", - " 201\n", + " Astros\n", + " Cubs\n", + " 174\n", " \n", " \n", "\n", @@ -4465,65 +4381,65 @@ "[2431 rows x 5 columns in total]" ], "text/plain": [ - " gameId year team team \\\n", - "0 d60c6036-0ce1-4c90-8dd9-de3b403c92a8 2016 Nationals Brewers \n", - "1 af72a0b9-65f7-49fb-9b30-d505068bdf6d 2016 Reds Brewers \n", - "2 f57e1271-d217-400a-aea6-2e2d7d6a59a0 2016 Orioles Rays \n", - "3 198f4eed-a29f-41e2-8623-cb261e5ab370 2016 Rockies Giants \n", - "4 cb3ef033-dd57-41fd-b206-cdd3bc12c74f 2016 Twins Indians \n", - "5 4be9f735-a98e-4689-87ce-852cc3a1e79d 2016 Blue Jays Orioles \n", - "6 0b2de8c3-11d9-4f0f-a186-25b59f34a5d8 2016 Yankees Mets \n", - "7 60d80663-6ced-44aa-aad9-0f4bf8d3b4d2 2016 Red Sox Rays \n", - "8 7e1c2095-4fea-454c-8773-096ceb6fb05c 2016 Cardinals Pirates \n", - "9 f7f24ce3-7f9d-4e8a-986e-095db847c4c1 2016 Rays Twins \n", - "10 5c26e7fc-c99f-48b4-92c1-4a7208c8cfe9 2016 Rays Twins \n", - "11 6d2cab13-dd85-477a-8769-669069f85836 2016 Royals Rays \n", - "12 bca90342-7ddc-468e-b189-d43fad7528ec 2016 Astros Rays \n", - "13 630f4f78-03cc-43c1-9e57-ababb9c11418 2016 Dodgers Giants \n", - "14 c0cf1376-1115-4a2f-b457-3f82bbc41a89 2016 Tigers White Sox \n", - "15 46463c50-0f5c-4dca-a661-dd194464e791 2016 Cardinals Cubs \n", - "16 392ad56d-972e-4f77-98e2-5f8577931cf8 2016 Giants Cardinals \n", - "17 307730fa-bbed-4221-b4e6-a2492f546fd5 2016 Red Sox Twins \n", - "18 1cbc558f-7615-4fa9-bf97-7ccd62040d6f 2016 Mets Braves \n", - "19 723348ba-1645-43fc-9e22-92994f7a63bd 2016 Athletics Twins \n", - "20 ffbd6ecc-82e1-4e5d-9bd1-4ea210be5992 2016 Twins Marlins \n", - "21 f2747230-7df5-4535-a475-a1c823d0d654 2016 Twins Yankees \n", - "22 db3b6f35-a7a4-430a-8703-2b2f25103e17 2016 White Sox Orioles \n", - "23 5fc8c6f0-a70e-4d1b-877f-eb1ec8e6f636 2016 Diamondbacks Giants \n", - "24 95d548b6-2da8-4644-812e-b277fec5b91f 2016 Braves Mets \n", + " gameId year team team \\\n", + "0 e14b6493-9e7f-404f-840a-8a680cc364bf 2016 Marlins Cubs \n", + "1 1f32b347-cbcb-4c31-a145-0e685306d168 2016 Marlins Cubs \n", + "2 0c2292d1-7398-48be-bf8e-b41dad5e1a43 2016 Braves Cubs \n", + "3 8fbec734-a15a-42ab-8d51-60790de7750b 2016 Braves Cubs \n", + "4 89e514d5-fbf5-4b9d-bdac-6ca45bfd18dd 2016 Phillies Cubs \n", + "5 6a83e76c-dc0d-4924-9d3d-a2e7e0ab5b52 2016 Diamondbacks Cubs \n", + "6 76ea8662-c7e6-4c38-8f2a-efe373e428ce 2016 Athletics Cubs \n", + "7 66fad23d-6e89-4f99-be29-d49b6e94f95d 2016 Athletics Cubs \n", + "8 d977367c-cf0c-4687-95a0-eb4542efcb01 2016 Rockies Cubs \n", + "9 a87070ff-1084-43ca-a7ba-69278f93ecba 2016 Cardinals Cubs \n", + "10 ea6b350d-3c1d-4737-878d-4465f66999f6 2016 Cardinals Cubs \n", + "11 46463c50-0f5c-4dca-a661-dd194464e791 2016 Cardinals Cubs \n", + "12 59134e6d-9d13-49aa-978e-c3c2300eb90f 2016 Pirates Cubs \n", + "13 387630a3-a894-4327-baa1-b24ec1a654d9 2016 Pirates Cubs \n", + "14 5d084e13-94fd-4995-b95a-4801ea3ed556 2016 Giants Cubs \n", + "15 34444c94-03ec-4d12-96af-68b8f399a22f 2016 Reds Cubs \n", + "16 9580bffe-22e1-4975-978b-1b13e7505193 2016 Reds Cubs \n", + "17 645e6a08-afd6-4677-a5c9-01ef446b0cf3 2016 Reds Cubs \n", + "18 08981bd8-d1d7-48e1-8668-9098b8f7fe90 2016 Reds Cubs \n", + "19 303703bb-b55f-476d-8faf-bf582169fb1d 2016 Padres Cubs \n", + "20 71ab82a4-6e07-430a-b695-1af3bc42ea61 2016 Nationals Cubs \n", + "21 d1a110c2-f6c8-4029-bcd8-2f8a01e1561c 2016 Brewers Cubs \n", + "22 6d111b57-fa0b-4f24-82df-ff33a26f0252 2016 Brewers Cubs \n", + "23 a97e9539-bbbd-4e03-bf15-f25ea2c1d923 2016 Brewers Cubs \n", + "24 dc0c9218-505c-4725-8c0c-40b72cca0956 2016 Astros Cubs \n", "\n", " duration_minutes \n", - "0 167 \n", - "1 172 \n", - "2 166 \n", - "3 182 \n", - "4 204 \n", - "5 184 \n", - "6 182 \n", - "7 191 \n", - "8 201 \n", - "9 189 \n", - "10 177 \n", - "11 183 \n", - "12 194 \n", - "13 178 \n", - "14 193 \n", - "15 160 \n", - "16 169 \n", - "17 251 \n", - "18 151 \n", - "19 153 \n", - "20 185 \n", - "21 180 \n", - "22 199 \n", - "23 175 \n", - "24 201 \n", + "0 187 \n", + "1 189 \n", + "2 165 \n", + "3 222 \n", + "4 164 \n", + "5 201 \n", + "6 173 \n", + "7 176 \n", + "8 180 \n", + "9 157 \n", + "10 218 \n", + "11 160 \n", + "12 178 \n", + "13 205 \n", + "14 197 \n", + "15 198 \n", + "16 188 \n", + "17 188 \n", + "18 194 \n", + "19 175 \n", + "20 257 \n", + "21 178 \n", + "22 171 \n", + "23 248 \n", + "24 174 \n", "...\n", "\n", "[2431 rows x 5 columns]" ] }, - "execution_count": 23, + "execution_count": 24, "metadata": {}, "output_type": "execute_result" } @@ -4535,17 +4451,19 @@ }, { "cell_type": "code", - "execution_count": 24, + "execution_count": 25, "id": "1a80f6f8-a172-4d7d-a2f5-e10871da7224", "metadata": {}, "outputs": [ { "data": { - "text/html": [ - "Query job 089a657b-e651-4b17-a4ce-4d7be682a49c is DONE. 0 Bytes processed. Open Job" - ], + "application/vnd.jupyter.widget-view+json": { + "model_id": "62abc80873ca4f96843de16b70ff0724", + "version_major": 2, + "version_minor": 0 + }, "text/plain": [ - "" + "HTML(value='Query job f4b90f3c-381e-470d-8416-54f31f1fbb3a is DONE. 174.4 kB processed. Open Job" - ], + "application/vnd.jupyter.widget-view+json": { + "model_id": "0a92c634fa774082a476c56ac0097adc", + "version_major": 2, + "version_minor": 0 + }, "text/plain": [ - "" + "HTML(value='Query job 5cd5e48d-8b02-4500-94cd-cbd2ee91957e is DONE. 193.8 kB processed. \n", " \n", " 0\n", - " Nationals\n", - " Brewers\n", + " Marlins\n", + " Cubs\n", " \n", " \n", " 1\n", - " Reds\n", - " Brewers\n", + " Marlins\n", + " Cubs\n", " \n", " \n", " 2\n", - " Orioles\n", - " Rays\n", + " Braves\n", + " Cubs\n", " \n", " \n", " 3\n", - " Rockies\n", - " Giants\n", + " Braves\n", + " Cubs\n", " \n", " \n", " 4\n", - " Twins\n", - " Indians\n", + " Phillies\n", + " Cubs\n", " \n", " \n", " 5\n", - " Blue Jays\n", - " Orioles\n", + " Diamondbacks\n", + " Cubs\n", " \n", " \n", " 6\n", - " Yankees\n", - " Mets\n", + " Athletics\n", + " Cubs\n", " \n", " \n", " 7\n", - " Red Sox\n", - " Rays\n", + " Athletics\n", + " Cubs\n", " \n", " \n", " 8\n", - " Cardinals\n", - " Pirates\n", + " Rockies\n", + " Cubs\n", " \n", " \n", " 9\n", - " Rays\n", - " Twins\n", + " Cardinals\n", + " Cubs\n", " \n", " \n", " 10\n", - " Rays\n", - " Twins\n", + " Cardinals\n", + " Cubs\n", " \n", " \n", " 11\n", - " Royals\n", - " Rays\n", + " Cardinals\n", + " Cubs\n", " \n", " \n", " 12\n", - " Astros\n", - " Rays\n", + " Pirates\n", + " Cubs\n", " \n", " \n", " 13\n", - " Dodgers\n", - " Giants\n", + " Pirates\n", + " Cubs\n", " \n", " \n", " 14\n", - " Tigers\n", - " White Sox\n", + " Giants\n", + " Cubs\n", " \n", " \n", " 15\n", - " Cardinals\n", + " Reds\n", " Cubs\n", " \n", " \n", " 16\n", - " Giants\n", - " Cardinals\n", + " Reds\n", + " Cubs\n", " \n", " \n", " 17\n", - " Red Sox\n", - " Twins\n", + " Reds\n", + " Cubs\n", " \n", " \n", " 18\n", - " Mets\n", - " Braves\n", + " Reds\n", + " Cubs\n", " \n", " \n", " 19\n", - " Athletics\n", - " Twins\n", + " Padres\n", + " Cubs\n", " \n", " \n", " 20\n", - " Twins\n", - " Marlins\n", + " Nationals\n", + " Cubs\n", " \n", " \n", " 21\n", - " Twins\n", - " Yankees\n", + " Brewers\n", + " Cubs\n", " \n", " \n", " 22\n", - " White Sox\n", - " Orioles\n", + " Brewers\n", + " Cubs\n", " \n", " \n", " 23\n", - " Diamondbacks\n", - " Giants\n", + " Brewers\n", + " Cubs\n", " \n", " \n", " 24\n", - " Braves\n", - " Mets\n", + " Astros\n", + " Cubs\n", " \n", " \n", "\n", @@ -4720,38 +4640,38 @@ "[2431 rows x 2 columns in total]" ], "text/plain": [ - " team team\n", - "0 Nationals Brewers\n", - "1 Reds Brewers\n", - "2 Orioles Rays\n", - "3 Rockies Giants\n", - "4 Twins Indians\n", - "5 Blue Jays Orioles\n", - "6 Yankees Mets\n", - "7 Red Sox Rays\n", - "8 Cardinals Pirates\n", - "9 Rays Twins\n", - "10 Rays Twins\n", - "11 Royals Rays\n", - "12 Astros Rays\n", - "13 Dodgers Giants\n", - "14 Tigers White Sox\n", - "15 Cardinals Cubs\n", - "16 Giants Cardinals\n", - "17 Red Sox Twins\n", - "18 Mets Braves\n", - "19 Athletics Twins\n", - "20 Twins Marlins\n", - "21 Twins Yankees\n", - "22 White Sox Orioles\n", - "23 Diamondbacks Giants\n", - "24 Braves Mets\n", + " team team\n", + "0 Marlins Cubs\n", + "1 Marlins Cubs\n", + "2 Braves Cubs\n", + "3 Braves Cubs\n", + "4 Phillies Cubs\n", + "5 Diamondbacks Cubs\n", + "6 Athletics Cubs\n", + "7 Athletics Cubs\n", + "8 Rockies Cubs\n", + "9 Cardinals Cubs\n", + "10 Cardinals Cubs\n", + "11 Cardinals Cubs\n", + "12 Pirates Cubs\n", + "13 Pirates Cubs\n", + "14 Giants Cubs\n", + "15 Reds Cubs\n", + "16 Reds Cubs\n", + "17 Reds Cubs\n", + "18 Reds Cubs\n", + "19 Padres Cubs\n", + "20 Nationals Cubs\n", + "21 Brewers Cubs\n", + "22 Brewers Cubs\n", + "23 Brewers Cubs\n", + "24 Astros Cubs\n", "...\n", "\n", "[2431 rows x 2 columns]" ] }, - "execution_count": 24, + "execution_count": 25, "metadata": {}, "output_type": "execute_result" } @@ -4771,17 +4691,19 @@ }, { "cell_type": "code", - "execution_count": 25, + "execution_count": 26, "id": "2414a095-37df-4755-b86c-2031a6cb9d4a", "metadata": {}, "outputs": [ { "data": { - "text/html": [ - "Query job d3b3cd83-d9cf-4c5b-9015-e3979e0857f3 is DONE. 0 Bytes processed. Open Job" - ], + "application/vnd.jupyter.widget-view+json": { + "model_id": "27bab406ba024805b35effa7e01def3d", + "version_major": 2, + "version_minor": 0 + }, "text/plain": [ - "" + "HTML(value='Query job 6b1cf632-7764-49ba-bd5d-cdf31c47e430 is DONE. 174.4 kB processed. Open Job" - ], + "application/vnd.jupyter.widget-view+json": { + "model_id": "fb3f85b83da34242963b478328db9662", + "version_major": 2, + "version_minor": 0 + }, "text/plain": [ - "" + "HTML(value='Query job baa31010-2426-4ea5-9527-8f55473a3f41 is DONE. 193.8 kB processed. \n", " 0\n", " 2016\n", - " 167\n", + " 187\n", " \n", " \n", " 1\n", " 2016\n", - " 172\n", + " 189\n", " \n", " \n", " 2\n", " 2016\n", - " 166\n", + " 165\n", " \n", " \n", " 3\n", " 2016\n", - " 182\n", + " 222\n", " \n", " \n", " 4\n", " 2016\n", - " 204\n", + " 164\n", " \n", " \n", " 5\n", " 2016\n", - " 184\n", + " 201\n", " \n", " \n", " 6\n", " 2016\n", - " 182\n", + " 173\n", " \n", " \n", " 7\n", " 2016\n", - " 191\n", + " 176\n", " \n", " \n", " 8\n", " 2016\n", - " 201\n", + " 180\n", " \n", " \n", " 9\n", " 2016\n", - " 189\n", + " 157\n", " \n", " \n", " 10\n", " 2016\n", - " 177\n", + " 218\n", " \n", " \n", " 11\n", " 2016\n", - " 183\n", + " 160\n", " \n", " \n", " 12\n", " 2016\n", - " 194\n", + " 178\n", " \n", " \n", " 13\n", " 2016\n", - " 178\n", + " 205\n", " \n", " \n", " 14\n", " 2016\n", - " 193\n", + " 197\n", " \n", " \n", " 15\n", " 2016\n", - " 160\n", + " 198\n", " \n", " \n", " 16\n", " 2016\n", - " 169\n", + " 188\n", " \n", " \n", " 17\n", " 2016\n", - " 251\n", + " 188\n", " \n", " \n", " 18\n", " 2016\n", - " 151\n", + " 194\n", " \n", " \n", " 19\n", " 2016\n", - " 153\n", + " 175\n", " \n", " \n", " 20\n", " 2016\n", - " 185\n", + " 257\n", " \n", " \n", " 21\n", " 2016\n", - " 180\n", + " 178\n", " \n", " \n", " 22\n", " 2016\n", - " 199\n", + " 171\n", " \n", " \n", " 23\n", " 2016\n", - " 175\n", + " 248\n", " \n", " \n", " 24\n", " 2016\n", - " 201\n", + " 174\n", " \n", " \n", "\n", @@ -4957,37 +4881,37 @@ ], "text/plain": [ " year duration_minutes\n", - "0 2016 167\n", - "1 2016 172\n", - "2 2016 166\n", - "3 2016 182\n", - "4 2016 204\n", - "5 2016 184\n", - "6 2016 182\n", - "7 2016 191\n", - "8 2016 201\n", - "9 2016 189\n", - "10 2016 177\n", - "11 2016 183\n", - "12 2016 194\n", - "13 2016 178\n", - "14 2016 193\n", - "15 2016 160\n", - "16 2016 169\n", - "17 2016 251\n", - "18 2016 151\n", - "19 2016 153\n", - "20 2016 185\n", - "21 2016 180\n", - "22 2016 199\n", - "23 2016 175\n", - "24 2016 201\n", + "0 2016 187\n", + "1 2016 189\n", + "2 2016 165\n", + "3 2016 222\n", + "4 2016 164\n", + "5 2016 201\n", + "6 2016 173\n", + "7 2016 176\n", + "8 2016 180\n", + "9 2016 157\n", + "10 2016 218\n", + "11 2016 160\n", + "12 2016 178\n", + "13 2016 205\n", + "14 2016 197\n", + "15 2016 198\n", + "16 2016 188\n", + "17 2016 188\n", + "18 2016 194\n", + "19 2016 175\n", + "20 2016 257\n", + "21 2016 178\n", + "22 2016 171\n", + "23 2016 248\n", + "24 2016 174\n", "...\n", "\n", "[2431 rows x 2 columns]" ] }, - "execution_count": 25, + "execution_count": 26, "metadata": {}, "output_type": "execute_result" } @@ -4999,17 +4923,19 @@ }, { "cell_type": "code", - "execution_count": 26, + "execution_count": 27, "id": "7d437c7c-ae74-4f0d-a4f8-10a133f4b61e", "metadata": {}, "outputs": [ { "data": { - "text/html": [ - "Query job e5c2b908-c539-4349-8368-50e61d8e19cd is DONE. 0 Bytes processed. Open Job" - ], + "application/vnd.jupyter.widget-view+json": { + "model_id": "0836e9729f75465e8eb1e73c1071a22d", + "version_major": 2, + "version_minor": 0 + }, "text/plain": [ - "" + "HTML(value='Query job 62ffb914-e1fb-4b12-adb5-09b431e06acf is DONE. 174.4 kB processed. Open Job" - ], + "application/vnd.jupyter.widget-view+json": { + "model_id": "69e853a1f0e44d39adacfefbfee86156", + "version_major": 2, + "version_minor": 0 + }, "text/plain": [ - "" + "HTML(value='Query job 2eaceff3-b00c-42d0-883f-fbe85a70f49b is DONE. 193.8 kB processed. \n", " 0\n", " 2017\n", - " 168\n", + " 188\n", " \n", " \n", " 1\n", " 2017\n", - " 173\n", + " 190\n", " \n", " \n", " 2\n", " 2017\n", - " 167\n", + " 166\n", " \n", " \n", " 3\n", " 2017\n", - " 183\n", + " 223\n", " \n", " \n", " 4\n", " 2017\n", - " 205\n", + " 165\n", " \n", " \n", " 5\n", " 2017\n", - " 185\n", + " 202\n", " \n", " \n", " 6\n", " 2017\n", - " 183\n", + " 174\n", " \n", " \n", " 7\n", " 2017\n", - " 192\n", + " 177\n", " \n", " \n", " 8\n", " 2017\n", - " 202\n", + " 181\n", " \n", " \n", " 9\n", " 2017\n", - " 190\n", + " 158\n", " \n", " \n", " 10\n", " 2017\n", - " 178\n", + " 219\n", " \n", " \n", " 11\n", " 2017\n", - " 184\n", + " 161\n", " \n", " \n", " 12\n", " 2017\n", - " 195\n", + " 179\n", " \n", " \n", " 13\n", " 2017\n", - " 179\n", + " 206\n", " \n", " \n", " 14\n", " 2017\n", - " 194\n", + " 198\n", " \n", " \n", " 15\n", " 2017\n", - " 161\n", + " 199\n", " \n", " \n", " 16\n", " 2017\n", - " 170\n", + " 189\n", " \n", " \n", " 17\n", " 2017\n", - " 252\n", + " 189\n", " \n", " \n", " 18\n", " 2017\n", - " 152\n", + " 195\n", " \n", " \n", " 19\n", " 2017\n", - " 154\n", + " 176\n", " \n", " \n", " 20\n", " 2017\n", - " 186\n", + " 258\n", " \n", " \n", " 21\n", " 2017\n", - " 181\n", + " 179\n", " \n", " \n", " 22\n", " 2017\n", - " 200\n", + " 172\n", " \n", " \n", " 23\n", " 2017\n", - " 176\n", + " 249\n", " \n", " \n", " 24\n", " 2017\n", - " 202\n", + " 175\n", " \n", " \n", "\n", @@ -5185,37 +5113,37 @@ ], "text/plain": [ " year duration_minutes\n", - "0 2017 168\n", - "1 2017 173\n", - "2 2017 167\n", - "3 2017 183\n", - "4 2017 205\n", - "5 2017 185\n", - "6 2017 183\n", - "7 2017 192\n", - "8 2017 202\n", - "9 2017 190\n", - "10 2017 178\n", - "11 2017 184\n", - "12 2017 195\n", - "13 2017 179\n", - "14 2017 194\n", - "15 2017 161\n", - "16 2017 170\n", - "17 2017 252\n", - "18 2017 152\n", - "19 2017 154\n", - "20 2017 186\n", - "21 2017 181\n", - "22 2017 200\n", - "23 2017 176\n", - "24 2017 202\n", + "0 2017 188\n", + "1 2017 190\n", + "2 2017 166\n", + "3 2017 223\n", + "4 2017 165\n", + "5 2017 202\n", + "6 2017 174\n", + "7 2017 177\n", + "8 2017 181\n", + "9 2017 158\n", + "10 2017 219\n", + "11 2017 161\n", + "12 2017 179\n", + "13 2017 206\n", + "14 2017 198\n", + "15 2017 199\n", + "16 2017 189\n", + "17 2017 189\n", + "18 2017 195\n", + "19 2017 176\n", + "20 2017 258\n", + "21 2017 179\n", + "22 2017 172\n", + "23 2017 249\n", + "24 2017 175\n", "...\n", "\n", "[2431 rows x 2 columns]" ] }, - "execution_count": 26, + "execution_count": 27, "metadata": {}, "output_type": "execute_result" } @@ -5234,17 +5162,19 @@ }, { "cell_type": "code", - "execution_count": 27, + "execution_count": 28, "id": "ab429fa5", "metadata": {}, "outputs": [ { "data": { - "text/html": [ - "Query job b6495f3d-619c-429e-8904-5cdc4957d09f is DONE. 77.8 kB processed. Open Job" - ], + "application/vnd.jupyter.widget-view+json": { + "model_id": "39eda5e2b7984f8f90a441c31b62a675", + "version_major": 2, + "version_minor": 0 + }, "text/plain": [ - "" + "HTML(value='Query job f3b7ff8a-ffdc-4f4c-91d3-6c2c702c373d is DONE. 193.8 kB processed. \n", " 0\n", " 2016\n", - " 167\n", + " 187\n", " \n", " \n", " 1\n", " 2016\n", - " 172\n", + " 189\n", " \n", " \n", " 2\n", " 2016\n", - " 166\n", + " 165\n", " \n", " \n", " 3\n", " 2016\n", - " 182\n", + " 222\n", " \n", " \n", " 4\n", " 2016\n", - " 204\n", + " 164\n", " \n", " \n", " ...\n", @@ -5309,27 +5239,27 @@ " \n", " 2426\n", " 2016\n", - " 199\n", + " 156\n", " \n", " \n", " 2427\n", " 2016\n", - " 181\n", + " 185\n", " \n", " \n", " 2428\n", " 2016\n", - " 205\n", + " 243\n", " \n", " \n", " 2429\n", " 2016\n", - " 203\n", + " 184\n", " \n", " \n", " 2430\n", " 2016\n", - " 182\n", + " 185\n", " \n", " \n", "\n", @@ -5338,22 +5268,22 @@ ], "text/plain": [ " year duration_minutes\n", - "0 2016 167\n", - "1 2016 172\n", - "2 2016 166\n", - "3 2016 182\n", - "4 2016 204\n", + "0 2016 187\n", + "1 2016 189\n", + "2 2016 165\n", + "3 2016 222\n", + "4 2016 164\n", "... ... ...\n", - "2426 2016 199\n", - "2427 2016 181\n", - "2428 2016 205\n", - "2429 2016 203\n", - "2430 2016 182\n", + "2426 2016 156\n", + "2427 2016 185\n", + "2428 2016 243\n", + "2429 2016 184\n", + "2430 2016 185\n", "\n", "[2431 rows x 2 columns]" ] }, - "execution_count": 27, + "execution_count": 28, "metadata": {}, "output_type": "execute_result" } @@ -5380,7 +5310,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.1" + "version": "3.10.12" } }, "nbformat": 4, diff --git a/notebooks/dataframes/index_col_null.ipynb b/notebooks/dataframes/index_col_null.ipynb deleted file mode 100644 index f77051e553b..00000000000 --- a/notebooks/dataframes/index_col_null.ipynb +++ /dev/null @@ -1,1401 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 1, - "id": "eeec3428", - "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." - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "id": "47439dbd-4e54-4954-8b16-edc4bcd4f855", - "metadata": {}, - "source": [ - "# Operations with an \"NULL index\" DataFrame\n", - "\n", - "**Note**: This notebook describes a feature that is currently in [preview](https://cloud.google.com/blog/products/gcp/google-cloud-gets-simplified-product-launch-stages). There may be breaking changes to the functionality when using \"NULL index\" objects.\n", - "\n", - "Use the \"NULL\" index for more efficient query generation, but\n", - "some pandas-compatible methods may not be possible without an index." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "96757c59-fc22-420e-a42f-c6cb956110ec", - "metadata": {}, - "outputs": [], - "source": [ - "import bigframes.enums\n", - "import bigframes.exceptions\n", - "import bigframes.pandas as bpd\n", - "\n", - "df = bpd.read_gbq(\n", - " \"bigquery-public-data.baseball.schedules\",\n", - " index_col=bigframes.enums.DefaultIndexKind.NULL,\n", - ")" - ] - }, - { - "cell_type": "markdown", - "id": "d15688e1", - "metadata": {}, - "source": [ - "Use `peek()` to view an arbitrary selection of rows from the DataFrame. This is much more efficient than `head()`, which requires a total ordering for determinism." - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "c93949fb", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "Query job 1b8726ce-c4ea-47fe-a47c-d6fae50d8fb0 is DONE. 582.8 kB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "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", - "
gameIdgameNumberseasonIdyeartypedayNightdurationduration_minuteshomeTeamIdhomeTeamNameawayTeamIdawayTeamNamestartTimeattendancestatuscreated
0e14b6493-9e7f-404f-840a-8a680cc364bf1565de4be-dc80-4849-a7e1-54bc79156cc82016REGD3:0718703556285-bdbb-4576-a06d-42f71f46ddc5Marlins55714da8-fcaf-4574-8443-59bfb511a524Cubs2016-06-26 17:10:00+00:0027318closed2016-10-06 06:25:15+00:00
11f32b347-cbcb-4c31-a145-0e685306d1681565de4be-dc80-4849-a7e1-54bc79156cc82016REGD3:0918903556285-bdbb-4576-a06d-42f71f46ddc5Marlins55714da8-fcaf-4574-8443-59bfb511a524Cubs2016-06-25 20:10:00+00:0029457closed2016-10-06 06:25:15+00:00
20c2292d1-7398-48be-bf8e-b41dad5e1a431565de4be-dc80-4849-a7e1-54bc79156cc82016REGD2:4516512079497-e414-450a-8bf2-29f91de646bfBraves55714da8-fcaf-4574-8443-59bfb511a524Cubs2016-06-11 20:10:00+00:0043114closed2016-10-06 06:25:15+00:00
38fbec734-a15a-42ab-8d51-60790de7750b1565de4be-dc80-4849-a7e1-54bc79156cc82016REGD3:4222212079497-e414-450a-8bf2-29f91de646bfBraves55714da8-fcaf-4574-8443-59bfb511a524Cubs2016-06-12 17:35:00+00:0031625closed2016-10-06 06:25:15+00:00
489e514d5-fbf5-4b9d-bdac-6ca45bfd18dd1565de4be-dc80-4849-a7e1-54bc79156cc82016REGD2:441642142e1ba-3b40-445c-b8bb-f1f8b1054220Phillies55714da8-fcaf-4574-8443-59bfb511a524Cubs2016-06-08 17:05:00+00:0028650closed2016-10-06 06:25:15+00:00
\n", - "
" - ], - "text/plain": [ - " gameId gameNumber \\\n", - "0 e14b6493-9e7f-404f-840a-8a680cc364bf 1 \n", - "1 1f32b347-cbcb-4c31-a145-0e685306d168 1 \n", - "2 0c2292d1-7398-48be-bf8e-b41dad5e1a43 1 \n", - "3 8fbec734-a15a-42ab-8d51-60790de7750b 1 \n", - "4 89e514d5-fbf5-4b9d-bdac-6ca45bfd18dd 1 \n", - "\n", - " seasonId year type dayNight duration \\\n", - "0 565de4be-dc80-4849-a7e1-54bc79156cc8 2016 REG D 3:07 \n", - "1 565de4be-dc80-4849-a7e1-54bc79156cc8 2016 REG D 3:09 \n", - "2 565de4be-dc80-4849-a7e1-54bc79156cc8 2016 REG D 2:45 \n", - "3 565de4be-dc80-4849-a7e1-54bc79156cc8 2016 REG D 3:42 \n", - "4 565de4be-dc80-4849-a7e1-54bc79156cc8 2016 REG D 2:44 \n", - "\n", - " duration_minutes homeTeamId homeTeamName \\\n", - "0 187 03556285-bdbb-4576-a06d-42f71f46ddc5 Marlins \n", - "1 189 03556285-bdbb-4576-a06d-42f71f46ddc5 Marlins \n", - "2 165 12079497-e414-450a-8bf2-29f91de646bf Braves \n", - "3 222 12079497-e414-450a-8bf2-29f91de646bf Braves \n", - "4 164 2142e1ba-3b40-445c-b8bb-f1f8b1054220 Phillies \n", - "\n", - " awayTeamId awayTeamName \\\n", - "0 55714da8-fcaf-4574-8443-59bfb511a524 Cubs \n", - "1 55714da8-fcaf-4574-8443-59bfb511a524 Cubs \n", - "2 55714da8-fcaf-4574-8443-59bfb511a524 Cubs \n", - "3 55714da8-fcaf-4574-8443-59bfb511a524 Cubs \n", - "4 55714da8-fcaf-4574-8443-59bfb511a524 Cubs \n", - "\n", - " startTime attendance status created \n", - "0 2016-06-26 17:10:00+00:00 27318 closed 2016-10-06 06:25:15+00:00 \n", - "1 2016-06-25 20:10:00+00:00 29457 closed 2016-10-06 06:25:15+00:00 \n", - "2 2016-06-11 20:10:00+00:00 43114 closed 2016-10-06 06:25:15+00:00 \n", - "3 2016-06-12 17:35:00+00:00 31625 closed 2016-10-06 06:25:15+00:00 \n", - "4 2016-06-08 17:05:00+00:00 28650 closed 2016-10-06 06:25:15+00:00 " - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "df.peek()" - ] - }, - { - "cell_type": "markdown", - "id": "78e3d27d", - "metadata": {}, - "source": [ - "# Inspect the properties of the DataFrame\n", - "\n", - "Some properties, such as `dtypes`, can be retrieved without executing a query job." - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "38f566c5", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "gameId string[pyarrow]\n", - "gameNumber Int64\n", - "seasonId string[pyarrow]\n", - "year Int64\n", - "type string[pyarrow]\n", - "dayNight string[pyarrow]\n", - "duration string[pyarrow]\n", - "duration_minutes Int64\n", - "homeTeamId string[pyarrow]\n", - "homeTeamName string[pyarrow]\n", - "awayTeamId string[pyarrow]\n", - "awayTeamName string[pyarrow]\n", - "startTime timestamp[us, tz=UTC][pyarrow]\n", - "attendance Int64\n", - "status string[pyarrow]\n", - "created timestamp[us, tz=UTC][pyarrow]\n", - "dtype: object" - ] - }, - "execution_count": 4, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "df.dtypes" - ] - }, - { - "cell_type": "markdown", - "id": "38a59ecc", - "metadata": {}, - "source": [ - "Other properties, such as `shape` require a query. In this case, `shape` runs a `COUNT(1)` query." - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "id": "e3b43d37", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "Query job 0f85f12c-227c-4001-b851-6e9b9087ab7e is DONE. 0 Bytes processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/plain": [ - "(2431, 16)" - ] - }, - "execution_count": 5, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "df.shape" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "id": "13861abc-120c-4db6-ad0c-e414b85d3443", - "metadata": {}, - "source": [ - "## Select a subset of the DataFrame", - "\n", - "Filter columns by selecting a list of columns from the DataFrame.\n", - "\n", - "**Note**: Even with `index_col=bigframes.enums.DefaultIndexKind.NULL`, it is more efficient to do this selection in `read_gbq` / `read_gbq_table` except in cases where the total ordering ID columns can be pruned." - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "id": "05cb36e9-bb75-4f6f-8eb6-e4219df6e1d2", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "Query job efa6b4be-cf60-4951-9125-7d77fb6b6b44 is DONE. 174.4 kB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "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", - "
gameIdyearhomeTeamNameawayTeamNameduration_minutes
0e14b6493-9e7f-404f-840a-8a680cc364bf2016MarlinsCubs187
11f32b347-cbcb-4c31-a145-0e685306d1682016MarlinsCubs189
20c2292d1-7398-48be-bf8e-b41dad5e1a432016BravesCubs165
38fbec734-a15a-42ab-8d51-60790de7750b2016BravesCubs222
489e514d5-fbf5-4b9d-bdac-6ca45bfd18dd2016PhilliesCubs164
\n", - "
" - ], - "text/plain": [ - " gameId year homeTeamName awayTeamName \\\n", - "0 e14b6493-9e7f-404f-840a-8a680cc364bf 2016 Marlins Cubs \n", - "1 1f32b347-cbcb-4c31-a145-0e685306d168 2016 Marlins Cubs \n", - "2 0c2292d1-7398-48be-bf8e-b41dad5e1a43 2016 Braves Cubs \n", - "3 8fbec734-a15a-42ab-8d51-60790de7750b 2016 Braves Cubs \n", - "4 89e514d5-fbf5-4b9d-bdac-6ca45bfd18dd 2016 Phillies Cubs \n", - "\n", - " duration_minutes \n", - "0 187 \n", - "1 189 \n", - "2 165 \n", - "3 222 \n", - "4 164 " - ] - }, - "execution_count": 6, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "column_filtered = df[[\"gameId\", \"year\", \"homeTeamName\", \"awayTeamName\", \"duration_minutes\"]]\n", - "column_filtered.peek()" - ] - }, - { - "cell_type": "markdown", - "id": "d4d52c41", - "metadata": {}, - "source": [ - "Filter by rows using a boolean Series. This Series must be derived from the DataFrame being filtered so that the NULL index can still align correctly." - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "id": "a6b8b3ac-1df8-46ff-ac4f-d6e7657fc80c", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "Query job 0be8e44d-854a-45ca-950b-269280e3de41 is DONE. 582.8 kB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "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", - "
gameIdgameNumberseasonIdyeartypedayNightdurationduration_minuteshomeTeamIdhomeTeamNameawayTeamIdawayTeamNamestartTimeattendancestatuscreated
063f14670-c28e-432b-84ee-1a2c6ac295271565de4be-dc80-4849-a7e1-54bc79156cc82016REGN2:4316303556285-bdbb-4576-a06d-42f71f46ddc5Marlins55714da8-fcaf-4574-8443-59bfb511a524Cubs2016-06-23 23:10:00+00:0025291closed2016-10-06 06:25:15+00:00
1bf4e80d1-3125-44fa-8a89-de93d039d4651565de4be-dc80-4849-a7e1-54bc79156cc82016REGN3:2420403556285-bdbb-4576-a06d-42f71f46ddc5Marlins55714da8-fcaf-4574-8443-59bfb511a524Cubs2016-06-24 23:10:00+00:0024385closed2016-10-06 06:25:15+00:00
2e8af534c-36ed-4ff9-8511-780825fdd0411565de4be-dc80-4849-a7e1-54bc79156cc82016REGN2:5117112079497-e414-450a-8bf2-29f91de646bfBraves55714da8-fcaf-4574-8443-59bfb511a524Cubs2016-06-10 23:35:00+00:0030547closed2016-10-06 06:25:15+00:00
3e599c525-ac42-4b54-928d-7ee5fbe67dd91565de4be-dc80-4849-a7e1-54bc79156cc82016REGN2:451652142e1ba-3b40-445c-b8bb-f1f8b1054220Phillies55714da8-fcaf-4574-8443-59bfb511a524Cubs2016-06-07 23:05:00+00:0027381closed2016-10-06 06:25:15+00:00
4d80ffb65-57a4-42c9-ae1c-2c51d06503361565de4be-dc80-4849-a7e1-54bc79156cc82016REGN3:051852142e1ba-3b40-445c-b8bb-f1f8b1054220Phillies55714da8-fcaf-4574-8443-59bfb511a524Cubs2016-06-06 23:05:00+00:0022162closed2016-10-06 06:25:15+00:00
\n", - "
" - ], - "text/plain": [ - " gameId gameNumber \\\n", - "0 63f14670-c28e-432b-84ee-1a2c6ac29527 1 \n", - "1 bf4e80d1-3125-44fa-8a89-de93d039d465 1 \n", - "2 e8af534c-36ed-4ff9-8511-780825fdd041 1 \n", - "3 e599c525-ac42-4b54-928d-7ee5fbe67dd9 1 \n", - "4 d80ffb65-57a4-42c9-ae1c-2c51d0650336 1 \n", - "\n", - " seasonId year type dayNight duration \\\n", - "0 565de4be-dc80-4849-a7e1-54bc79156cc8 2016 REG N 2:43 \n", - "1 565de4be-dc80-4849-a7e1-54bc79156cc8 2016 REG N 3:24 \n", - "2 565de4be-dc80-4849-a7e1-54bc79156cc8 2016 REG N 2:51 \n", - "3 565de4be-dc80-4849-a7e1-54bc79156cc8 2016 REG N 2:45 \n", - "4 565de4be-dc80-4849-a7e1-54bc79156cc8 2016 REG N 3:05 \n", - "\n", - " duration_minutes homeTeamId homeTeamName \\\n", - "0 163 03556285-bdbb-4576-a06d-42f71f46ddc5 Marlins \n", - "1 204 03556285-bdbb-4576-a06d-42f71f46ddc5 Marlins \n", - "2 171 12079497-e414-450a-8bf2-29f91de646bf Braves \n", - "3 165 2142e1ba-3b40-445c-b8bb-f1f8b1054220 Phillies \n", - "4 185 2142e1ba-3b40-445c-b8bb-f1f8b1054220 Phillies \n", - "\n", - " awayTeamId awayTeamName \\\n", - "0 55714da8-fcaf-4574-8443-59bfb511a524 Cubs \n", - "1 55714da8-fcaf-4574-8443-59bfb511a524 Cubs \n", - "2 55714da8-fcaf-4574-8443-59bfb511a524 Cubs \n", - "3 55714da8-fcaf-4574-8443-59bfb511a524 Cubs \n", - "4 55714da8-fcaf-4574-8443-59bfb511a524 Cubs \n", - "\n", - " startTime attendance status created \n", - "0 2016-06-23 23:10:00+00:00 25291 closed 2016-10-06 06:25:15+00:00 \n", - "1 2016-06-24 23:10:00+00:00 24385 closed 2016-10-06 06:25:15+00:00 \n", - "2 2016-06-10 23:35:00+00:00 30547 closed 2016-10-06 06:25:15+00:00 \n", - "3 2016-06-07 23:05:00+00:00 27381 closed 2016-10-06 06:25:15+00:00 \n", - "4 2016-06-06 23:05:00+00:00 22162 closed 2016-10-06 06:25:15+00:00 " - ] - }, - "execution_count": 7, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "night_games = df[df['dayNight'] == 'N']\n", - "night_games.peek()" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "id": "80e9a2e2-c4c9-4c17-bbd0-06882d7657fe", - "metadata": {}, - "source": [ - "### Join two DataFrames\n", - "\n", - "Even though pandas usually joins by the index, NULL index objects can still be manually joined by a column using the `on` parameter in `merge`." - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "id": "3f09ff32-ef43-4fab-a86b-8868afc34363", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "Query job 5d2c69d2-33fe-4513-923b-fd64f4da098b is DONE. 113.9 kB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "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", - "
gameIdhomeTeamName
0e14b6493-9e7f-404f-840a-8a680cc364bfMarlins
11f32b347-cbcb-4c31-a145-0e685306d168Marlins
20c2292d1-7398-48be-bf8e-b41dad5e1a43Braves
38fbec734-a15a-42ab-8d51-60790de7750bBraves
489e514d5-fbf5-4b9d-bdac-6ca45bfd18ddPhillies
\n", - "
" - ], - "text/plain": [ - " gameId homeTeamName\n", - "0 e14b6493-9e7f-404f-840a-8a680cc364bf Marlins\n", - "1 1f32b347-cbcb-4c31-a145-0e685306d168 Marlins\n", - "2 0c2292d1-7398-48be-bf8e-b41dad5e1a43 Braves\n", - "3 8fbec734-a15a-42ab-8d51-60790de7750b Braves\n", - "4 89e514d5-fbf5-4b9d-bdac-6ca45bfd18dd Phillies" - ] - }, - "execution_count": 8, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "df1 = df[[\"gameId\", \"homeTeamName\"]]\n", - "df1.peek()" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "id": "5331d2c8-7912-4d96-8da1-f64b57374df3", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "Query job b6b70d6d-a490-44d6-ba74-0ee32b4f0a1a is DONE. 582.8 kB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job 68acd168-8b42-44f8-8702-99618935991e is DONE. 94 Bytes processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "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", - "
gameIdawayTeamName
0af72a0b9-65f7-49fb-9b30-d505068bdf6dBrewers
1d60c6036-0ce1-4c90-8dd9-de3b403c92a8Brewers
\n", - "
" - ], - "text/plain": [ - " gameId awayTeamName\n", - "0 af72a0b9-65f7-49fb-9b30-d505068bdf6d Brewers\n", - "1 d60c6036-0ce1-4c90-8dd9-de3b403c92a8 Brewers" - ] - }, - "execution_count": 9, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "df2 = df[[\"gameId\", \"awayTeamName\"]].head(2)\n", - "df2.peek()" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "id": "a574ad3e-a219-454c-8bb5-c5ed6627f2c6", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "Query job 0ac171dd-3859-4589-b7ff-59fd81ec3c3a is DONE. 582.9 kB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job 034f8807-c128-444a-8033-0c95f34b0e32 is DONE. 111 Bytes processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "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", - "
gameIdhomeTeamNameawayTeamName
0af72a0b9-65f7-49fb-9b30-d505068bdf6dRedsBrewers
1d60c6036-0ce1-4c90-8dd9-de3b403c92a8NationalsBrewers
\n", - "
" - ], - "text/plain": [ - " gameId homeTeamName awayTeamName\n", - "0 af72a0b9-65f7-49fb-9b30-d505068bdf6d Reds Brewers\n", - "1 d60c6036-0ce1-4c90-8dd9-de3b403c92a8 Nationals Brewers" - ] - }, - "execution_count": 10, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "merged = df1.merge(df2, on=\"gameId\", how=\"inner\")\n", - "merged.peek()" - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "id": "288e7a95-a077-46c4-8fe6-802474c01f8b", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "Query job 30fd5a60-772c-4ef0-a151-5ab390ff4322 is DONE. 582.9 kB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job 701fa9a8-1ec6-49b9-ac41-228cb34d4c8c is DONE. 114.0 kB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "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", - "
gameIdhomeTeamNameawayTeamName
0039bb40e-7613-4674-a653-584b93e9b21bAmerican League<NA>
178000e12-2ef3-4246-adc1-c8a4d157631cAngels<NA>
2de5555dc-9228-4f7c-88ae-4451e3ffb980Angels<NA>
3f29a2754-004b-436c-91fe-3d86c0bb17a8Angels<NA>
48e5af008-8a07-4f9a-90cb-336ca4c84c71Angels<NA>
\n", - "
" - ], - "text/plain": [ - " gameId homeTeamName awayTeamName\n", - "0 039bb40e-7613-4674-a653-584b93e9b21b American League \n", - "1 78000e12-2ef3-4246-adc1-c8a4d157631c Angels \n", - "2 de5555dc-9228-4f7c-88ae-4451e3ffb980 Angels \n", - "3 f29a2754-004b-436c-91fe-3d86c0bb17a8 Angels \n", - "4 8e5af008-8a07-4f9a-90cb-336ca4c84c71 Angels " - ] - }, - "execution_count": 11, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "merged = df1.merge(df2, on=\"gameId\", how=\"outer\")\n", - "merged.peek()" - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "id": "7ee87a01-2ff5-4021-855d-44b71cf2a225", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "Query job e3d8168c-48e9-4ba9-a916-10259ad9c0ea is DONE. 582.9 kB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job 82d2a5e4-66a8-4478-92de-57d3f806aa76 is DONE. 114.0 kB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "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", - "
gameIdhomeTeamNameawayTeamName
0039bb40e-7613-4674-a653-584b93e9b21bAmerican League<NA>
1f6fcd83c-e130-487c-a0cc-d00b2712d08bAngels<NA>
2fe401dd2-089c-4822-8657-4d510d460f38Angels<NA>
3c894bdee-5dda-49f4-87c8-53b9b9bfcd3bAngels<NA>
4bbda59d9-fd52-4bed-bcfb-2ceed4be997cAngels<NA>
\n", - "
" - ], - "text/plain": [ - " gameId homeTeamName awayTeamName\n", - "0 039bb40e-7613-4674-a653-584b93e9b21b American League \n", - "1 f6fcd83c-e130-487c-a0cc-d00b2712d08b Angels \n", - "2 fe401dd2-089c-4822-8657-4d510d460f38 Angels \n", - "3 c894bdee-5dda-49f4-87c8-53b9b9bfcd3b Angels \n", - "4 bbda59d9-fd52-4bed-bcfb-2ceed4be997c Angels " - ] - }, - "execution_count": 12, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "merged = df1.merge(df2, on=\"gameId\", how=\"left\")\n", - "merged.peek()" - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "id": "330ed69c-f122-4af9-bf5e-96e309d3fa0c", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "Query job 518ed511-606a-42b2-a28d-61a601eccfa7 is DONE. 582.9 kB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job bc381640-74e0-4885-9c32-87805a49f357 is DONE. 111 Bytes processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "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", - "
gameIdhomeTeamNameawayTeamName
0af72a0b9-65f7-49fb-9b30-d505068bdf6dRedsBrewers
1d60c6036-0ce1-4c90-8dd9-de3b403c92a8NationalsBrewers
\n", - "
" - ], - "text/plain": [ - " gameId homeTeamName awayTeamName\n", - "0 af72a0b9-65f7-49fb-9b30-d505068bdf6d Reds Brewers\n", - "1 d60c6036-0ce1-4c90-8dd9-de3b403c92a8 Nationals Brewers" - ] - }, - "execution_count": 13, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "merged = df1.merge(df2, on=\"gameId\", how=\"right\")\n", - "merged.peek()" - ] - }, - { - "cell_type": "markdown", - "id": "162eede7", - "metadata": {}, - "source": [ - "### Download the result as (in-memory) pandas DataFrame\n", - "\n", - "Use the `ordered=False` argument for more efficient query execution." - ] - }, - { - "cell_type": "code", - "execution_count": 17, - "id": "ab429fa5", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "Query job 2d4fbd55-ba6a-46d2-87ae-5da416ad3642 is DONE. 159 Bytes processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "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", - "
gameIdhomeTeamNameawayTeamName
0d60c6036-0ce1-4c90-8dd9-de3b403c92a8NationalsBrewers
1af72a0b9-65f7-49fb-9b30-d505068bdf6dRedsBrewers
\n", - "
" - ], - "text/plain": [ - " gameId homeTeamName awayTeamName\n", - "0 d60c6036-0ce1-4c90-8dd9-de3b403c92a8 Nationals Brewers\n", - "1 af72a0b9-65f7-49fb-9b30-d505068bdf6d Reds Brewers" - ] - }, - "execution_count": 17, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "dfp = merged.to_pandas(ordered=False)\n", - "dfp" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "896212ab", - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "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.11.1" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/notebooks/dataframes/integrations.ipynb b/notebooks/dataframes/integrations.ipynb deleted file mode 100644 index 8c7790b1ea0..00000000000 --- a/notebooks/dataframes/integrations.ipynb +++ /dev/null @@ -1,693 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [], - "source": [ - "# Copyright 2024 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": [ - "# Integrating with BigQuery DataFrames\n", - "\n", - "This notebook demonstrates operations for building applications that integrate with BigQuery DataFrames. Follow these samples to build an integration that accepts a BigQuery DataFrames object or returns one." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Attributing requests initiated by BigQuery DataFrames\n", - "\n", - "Partners are required to attribute API calls to BigQuery and other Google APIs. Where possible, this should be done via the User-Agent string, but can also be done via job labels if your integration doesn't initialize the BigQuery DataFrames session.\n", - "\n", - "### Setting the User-Agent\n", - "\n", - "Set [`bpd.options.bigquery.application_name`](https://cloud.google.com/python/docs/reference/bigframes/latest/bigframes._config.bigquery_options.BigQueryOptions#bigframes__config_bigquery_options_BigQueryOptions_application_name) to a compliant string. Reach out to your Google Partner Engineering team contact for further instructions." - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [], - "source": [ - "import bigframes.pandas as bpd\n", - "\n", - "# Set this to the string informed by your Google Partner Engineering team contact.\n", - "# Note: This can only be set once per session, so is most appropriate for partners\n", - "# who provide a Python + BigQuery DataFrames environment to their customers.\n", - "bpd.options.bigquery.application_name = \"notebook-samples/1.0.0 (GPN:notebook-samples)\"" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/home/swast/src/github.com/googleapis/python-bigquery-dataframes/bigframes/core/global_session.py:103: DefaultLocationWarning: No explicit location is set, so using location US for the session.\n", - " _global_session = bigframes.session.connect(\n" - ] - }, - { - "data": { - "text/html": [ - "Query job 1772ca28-2ef5-425c-87fe-8227aeb9318c is DONE. 0 Bytes processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "import bigframes.pandas as bpd\n", - "\n", - "# Sample data\n", - "df = bpd.DataFrame({\n", - " \"index\": [0, 1, 2, 3, 4],\n", - " \"int_col\": [1, 2, 3, 4, 5],\n", - " \"float_col\": [1.0, -0.5, 0.25, -0.125, 0.0625],\n", - " \"string_col\": [\"a\", \"b\", \"c\", \"d\", \"e\"],\n", - "}).set_index(\"index\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Setting the job label\n", - "\n", - "If your application works with customer-created BigQuery DataFrames objects, you might not be able to set the user-agent header because the session has already started (watch https://github.com/googleapis/python-bigquery-dataframes/issues/833 for updates on this limitation). Instead, attach a label to the jobs your application initiates, such as if you are performing `to_gbq()`on an existing DataFrame, as described below.\n", - "\n", - "Use `bpd.option_context()` so that the labels are only set during the operations your application performs." - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "Query job 33bd5814-b594-4ec4-baba-8f6b6e285e48 is DONE. 0 Bytes processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "with bpd.option_context(\"compute.extra_query_labels\", {\"application-name\": \"notebook-samples\"}):\n", - " table_id = df.to_gbq()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Accepting a BigQuery DataFrames (bigframes) DataFrame\n", - "\n", - "The recommended serialization format for a BigQuery DataFrames (bigframes) DataFrame is a BigQuery table. To write a DataFrame to a BigQuery table, use the `DataFrame.to_gbq()` method. With no `destination_table`, BigQuery DataFrames creates a table in the anonymous dataset corresponding to the BigQuery user & location and returns the corresponding table ID." - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "Query job 1594d97a-1203-4c28-8730-caffb3ac4e9e is DONE. 0 Bytes processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/plain": [ - "'bigframes-dev._63cfa399614a54153cc386c27d6c0c6fdb249f9e.bqdf20250530_session9fdc39_7578d5bd9949422599ccb9e4fe6451be'" - ] - }, - "execution_count": 5, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "table_id = df.to_gbq()\n", - "table_id" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Sharing the table with your application's backend\n", - "\n", - "Tables created in the user's anonymous dataset are only queryable by the user who created them. Many applications authenticate with a [service account](https://cloud.google.com/iam/docs/service-account-overview), which may be different from the end-user running BigQuery DataFrames (bigframes).\n", - "\n", - "Grant your application access to this table by granting your application's service account associated with the customer the `roles/bigquery.dataViewer` role on the [BigQuery table with an IAM policy](https://cloud.google.com/bigquery/docs/control-access-to-resources-iam#grant_access_to_a_table_or_view)." - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "Query job 8afc1538-9779-487a-a063-def5f438ee11 is DONE. 0 Bytes processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - " index int_col float_col string_col\n", - "0 1 2 -0.5000 b\n", - "1 2 3 0.2500 c\n", - "2 0 1 1.0000 a\n", - "3 3 4 -0.1250 d\n", - "4 4 5 0.0625 e\n" - ] - } - ], - "source": [ - "# This sample assumes the client code knows which service account to share with.\n", - "your_service_account_email = \"your-service-account@bigframes-samples.iam.gserviceaccount.com\"\n", - "\n", - "\n", - "def df_to_gbq_plus_workoad(df):\n", - " table_id = df.to_gbq()\n", - "\n", - " bqclient = df.bqclient\n", - " policy = bqclient.get_iam_policy(table_id)\n", - " binding = {\n", - " \"role\": \"roles/bigquery.dataViewer\",\n", - " \"members\": {f\"serviceAccount:{your_service_account_email}\"},\n", - " }\n", - " policy.bindings.append(binding)\n", - " bqclient.set_iam_policy(table_id, policy)\n", - "\n", - " # TODO(developer): Pass table_id to your application and start your workload.\n", - " example_workload(table_id)\n", - "\n", - "\n", - "def example_workload(table_id):\n", - " # For example, for one node workloads, use the client library to read the table\n", - " # as a pandas DataFrame.\n", - " from google.cloud import bigquery\n", - "\n", - " # This sample assumes this client is authenticated as the user\n", - " # your_service_account_email.\n", - " client = bigquery.Client()\n", - " pandas_df = client.list_rows(table_id).to_dataframe()\n", - " print(pandas_df)\n", - "\n", - "\n", - "df_to_gbq_plus_workoad(df)\n" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "Query job b6f68a49-5129-448d-bca3-62a23dced10d is DONE. 0 Bytes processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - " index int_col float_col string_col\n", - "0 3 4 -0.1250 d\n", - "1 1 2 -0.5000 b\n", - "2 4 5 0.0625 e\n", - "3 2 3 0.2500 c\n", - "4 0 1 1.0000 a\n" - ] - } - ], - "source": [ - "# This sample assumes the client code doesn't know which service account to share with.\n", - "\n", - "\n", - "def df_to_gbq_plus_workoad(df):\n", - " table_id = df.to_gbq()\n", - "\n", - " bqclient = df.bqclient\n", - " token = bqclient._http.credentials.token\n", - " project_id = bqclient.project\n", - "\n", - " share_table_and_start_workload(table_id, token, project_id)\n", - "\n", - "\n", - "def share_table_and_start_workload(table_id, token, project_id):\n", - " # This code runs in the backend for your application.\n", - " from google.cloud import bigquery\n", - " import google.oauth2.credentials\n", - "\n", - " # Note: these credentials don't have any way to be refreshed,\n", - " # so only use them long enough to share the table with the\n", - " # service account.\n", - " credentials = google.oauth2.credentials.Credentials(token)\n", - " bqclient = bigquery.Client(\n", - " project=project_id,\n", - " credentials=credentials,\n", - " )\n", - "\n", - " # This is assumed to only be available on the backend.\n", - " your_service_account_email = \"your-service-account@bigframes-samples.iam.gserviceaccount.com\"\n", - " policy = bqclient.get_iam_policy(table_id)\n", - " binding = {\n", - " \"role\": \"roles/bigquery.dataViewer\",\n", - " \"members\": {f\"serviceAccount:{your_service_account_email}\"},\n", - " }\n", - " policy.bindings.append(binding)\n", - " bqclient.set_iam_policy(table_id, policy)\n", - "\n", - " # Now that the table has been shared, bqclient with the temporary token\n", - " # is no longer needed.\n", - " example_workload(table_id)\n", - "\n", - "\n", - "def example_workload(table_id):\n", - " # For example, for one node workloads, use the client library to read the table\n", - " # as a pandas DataFrame.\n", - " from google.cloud import bigquery\n", - "\n", - " # This sample assumes this client is authenticated as the user\n", - " # your_service_account_email.\n", - " client = bigquery.Client()\n", - " pandas_df = client.list_rows(table_id).to_dataframe()\n", - " print(pandas_df)\n", - "\n", - "\n", - "df_to_gbq_plus_workoad(df)\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Preserving order\n", - "\n", - "Depending on your use case, you may want to include the ordering so that it can be restored withing your application." - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "Query job 0f205180-cf26-46e5-950d-109947b7f5a1 is DONE. 0 Bytes processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/plain": [ - "'bigframes-dev._63cfa399614a54153cc386c27d6c0c6fdb249f9e.bqdf20250530_session9fdc39_240520e0723548f18fd3bd5d24cbbf82'" - ] - }, - "execution_count": 8, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "ordering_column = \"ordering_id_maybe_with_some_random_text_to_avoid_collisions\"\n", - "table_id = df.to_gbq(ordering_id=ordering_column)\n", - "table_id" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Creating clustered tables\n", - "\n", - "Large tables can be optimized by passing in `clustering_columns` to create a [clustered table](https://cloud.google.com/bigquery/docs/clustered-tables)." - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "Query job 80177f9a-4f6e-4a4e-97db-f119ea686c62 is DONE. 0 Bytes processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/plain": [ - "'bigframes-dev._63cfa399614a54153cc386c27d6c0c6fdb249f9e.bqdf20250530_session9fdc39_4ca41d2f28f84feca1bbafe9304fd89f'" - ] - }, - "execution_count": 9, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "table_id = df.to_gbq(clustering_columns=(\"index\", \"int_col\"))\n", - "table_id" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Returning a BigQuery DataFrames (bigframes) DataFrame\n", - "\n", - "The recommended way to construct a DataFrame is from a BigQuery table which has a unique primary key. By default a primary key is used as the index, which allows for more efficient queries than the default index generation.\n", - "\n", - "This sample assumes there is a shared dataset that\n", - "\n", - "1. The application can write to and\n", - "2. the bigframes user can read from.\n", - "\n", - "There are many ways an application can [write to a BigQuery table](https://cloud.google.com/bigquery/docs/loading-data), including BigQuery load jobs, DML, streaming REST API, and the BigQuery Write API. Each has different costs, performance, and limitations. Choose the one that best suits your application's needs." - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "Dataset(DatasetReference('bigframes-dev', 'my_dataset'))" - ] - }, - "execution_count": 10, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# The assumption is that there is a shared dataset to work with.\n", - "from google.cloud import bigquery\n", - "\n", - "bqclient = bigquery.Client()\n", - "bqclient.create_dataset(\"my_dataset\", exists_ok=True)" - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "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", - "
statepostal_codepop
unique_index
2MI48105669
3GA303092581
5TX787015373
7CO803012087
11MA021422592
13IL606072630
17MI482012
19NC27701801
23CA926121115
29WA980334952
\n", - "

10 rows × 3 columns

\n", - "
[10 rows x 3 columns in total]" - ], - "text/plain": [ - " state postal_code pop\n", - "unique_index \n", - "2 MI 48105 669\n", - "3 GA 30309 2581\n", - "5 TX 78701 5373\n", - "7 CO 80301 2087\n", - "11 MA 02142 2592\n", - "13 IL 60607 2630\n", - "17 MI 48201 2\n", - "19 NC 27701 801\n", - "23 CA 92612 1115\n", - "29 WA 98033 4952\n", - "\n", - "[10 rows x 3 columns]" - ] - }, - "execution_count": 11, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# For simplicity, this sample assumes your application uses\n", - "# a load job with the CSV file format.\n", - "# See: https://cloud.google.com/bigquery/docs/loading-data-cloud-storage-csv#python\n", - "import datetime\n", - "import io\n", - "import random\n", - "\n", - "\n", - "def create_table_for_bigframes():\n", - " # This code is assumed to run on the application's backend.\n", - " from google.cloud import bigquery\n", - "\n", - " client = bigquery.Client()\n", - "\n", - " # The end-user is expected to have read access to this table.\n", - " table_suffix = f\"{datetime.datetime.now().strftime('%Y%m%d_%H%M%S_%f')}_{random.randrange(1_000_000)}\"\n", - " table_id = f\"{client.project}.my_dataset.integrations_ipynb_{table_suffix}\"\n", - "\n", - " # Best practice: set the primary key to a unique column to use as the\n", - " # index and default ordering in a BigQuery DataFrames (bigframes) DataFrame.\n", - " # Having a unique identity column allows the DataFrame to be constructed\n", - " # more efficiently.\n", - " #\n", - " # Note 1: Even a random UUID would be helpful for efficiency.\n", - " #\n", - " # Note 2: Don't do this if you can't guarantee uniqueness, as the BigQuery\n", - " # query engine uses this property to optimize queries. Non-unique primary\n", - " # keys result in undefined behavior.\n", - " #\n", - " # Note 3: client.create_table doesn't support primary key, so instead\n", - " # use DDL to create the table.\n", - " create_table_ddl = f\"\"\"\n", - " CREATE OR REPLACE TABLE `{table_id}`\n", - " (\n", - " unique_index INT64,\n", - " state STRING,\n", - " postal_code STRING,\n", - " pop INT64,\n", - " PRIMARY KEY (unique_index) NOT ENFORCED\n", - " )\n", - " -- Clustering by the index column can make joins and loc operations more efficient.\n", - " -- Also cluster by columns which are expected to be used as common filters.\n", - " CLUSTER BY unique_index, state\n", - " \"\"\"\n", - " client.query_and_wait(create_table_ddl)\n", - "\n", - " csv_file = io.BytesIO(\n", - "b\"\"\"unique_index,state,postal_code,pop\n", - "2,MI,48105,669\n", - "3,GA,30309,2581\n", - "5,TX,78701,5373\n", - "7,CO,80301,2087\n", - "11,MA,02142,2592\n", - "13,IL,60607,2630\n", - "17,MI,48201,2\n", - "19,NC,27701,801\n", - "23,CA,92612,1115\n", - "29,WA,98033,4952\n", - "\"\"\"\n", - " )\n", - " job_config = bigquery.LoadJobConfig(\n", - " skip_leading_rows=1,\n", - " source_format=bigquery.SourceFormat.CSV,\n", - " )\n", - " load_job = client.load_table_from_file(\n", - " csv_file, table_id, job_config=job_config\n", - " )\n", - " load_job.result() # Waits for the job to complete.\n", - "\n", - " return table_id\n", - "\n", - "\n", - "table_id = create_table_for_bigframes()\n", - "\n", - "\n", - "# This is assumed to run on the client.\n", - "import bigframes.pandas as bpd\n", - "df = bpd.read_gbq_table(table_id, index_col=[\"unique_index\"])\n", - "df" - ] - } - ], - "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.12.10" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/notebooks/dataframes/magics_with_local_data.ipynb b/notebooks/dataframes/magics_with_local_data.ipynb deleted file mode 100644 index 675ac83988b..00000000000 --- a/notebooks/dataframes/magics_with_local_data.ipynb +++ /dev/null @@ -1,2488 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "c5f9e86e", - "metadata": {}, - "outputs": [], - "source": [ - "# Copyright 2026 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": "71383fa0", - "metadata": {}, - "source": [ - "# Unlock SQL and Python interoperability for BigQuery with %%bqsql magic\n", - "\n", - "In this tutorial, you will learn how to seamlessly chain data processing across\n", - "SQL and Python code cells using `%%bqsql` IPython magic and BigQuery DataFrames\n", - "(BigFrames). This interoperability is now available to all Jupyter users,\n", - "whether you're in Colab, JupyterLab, or VS Code. \n", - "\n", - "While we begin by loading a local Excel dataset into a local Pandas DataFrame,\n", - "the main focus is on how you can transition between Pandas' Python-centric API\n", - "and BigQuery's SQL-centric engine. This hybrid workflow combines the best of\n", - "both worlds: the expressive power of SQL for complex transformations and the\n", - "versatile Python ecosystem for visualization and further analysis.\n", - "\n", - "Thanks to open-source packages like Jupyter, Pandas, BigFrames, and the\n", - "[BigQuery sandbox](https://docs.cloud.google.com/bigquery/docs/sandbox), you can\n", - "follow all steps in this guide for free\\* and without a credit card.\n", - "\n", - "_\\*See the [BigQuery sandbox](https://docs.cloud.google.com/bigquery/docs/sandbox) documentation for limitations._\n", - "\n", - "## The %%bqsql Magic\n", - "\n", - "Last year, Google introduced [SQL cells in Colab Enterprise\n", - "notebooks](https://docs.cloud.google.com/colab/docs/sql-cells). Now, with the\n", - "[%%bqsql cell\n", - "magics](https://dataframes.bigquery.dev/notebooks/getting_started/magics.html)\n", - "in BigQuery DataFrames, this same powerful interoperability is available to all\n", - "Jupyter users, whether you're in Colab, JupyterLab, or VS Code. These magics\n", - "allow you to write SQL queries that run directly on local pandas DataFrames,\n", - "BigFrames DataFrames, or BigQuery tables.\n", - "\n", - "\n", - "## Getting Started\n", - "\n", - "To get started,\n", - "\n", - "1. Enable the [BigQuery\n", - " sandbox](https://docs.cloud.google.com/bigquery/docs/sandbox). Make note of your\n", - " Google Cloud project ID.\n", - "\n", - "2. Set up a local Python development environment (see: [Setting up a Python\n", - " development environment](https://docs.cloud.google.com/python/docs/setup)) for\n", - " Google Cloud.\n", - "\n", - "3. Create and activate a venv to isolate Python dependencies.\n", - " On Linux or macOS, use these commands (update to your preferred Python\n", - " version):\n", - "\n", - " ```\n", - " python3.12 -m venv ~/venv\n", - " . ~/venv/bin/activate\n", - " ```\n", - "\n", - "4. Install the Jupyter, bigframes, and python-calamine packages:\n", - "\n", - " ```\n", - " pip install --upgrade jupyterlab bigframes python-calamine\n", - " ```\n", - "\n", - "5. Start Jupyter Lab.\n", - "\n", - " ```\n", - " jupyter lab\n", - " ```\n", - "\n", - "6. Open a web browser to the URL listed in the output. It will be something like\n", - " `http://localhost:8888/lab?token=somesupersecretvaluehere`.\n", - "\n", - "7. Create a new notebook using the Jupyter Lab UI.\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "d00aeb28", - "metadata": {}, - "outputs": [], - "source": [ - "%pip install python-calamine pandas bigframes" - ] - }, - { - "cell_type": "markdown", - "id": "5ba39d0d", - "metadata": {}, - "source": [ - "## Accessing the Dataset\n", - "\n", - "In this tutorial, you'll analyze the [USDA wheat\n", - "data](https://www.ers.usda.gov/data-products/wheat-data). Use the standard\n", - "`requests` package to download the data to a temporary file, mimicking a typical\n", - "local data analysis workflow.\n" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "id": "fb1dfdc2", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "0" - ] - }, - "execution_count": 1, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "\n", - "import tempfile\n", - "\n", - "import requests\n", - "\n", - "url = \"https://www.ers.usda.gov/media/5706/wheat-data-all-years.xlsx?v=52690\"\n", - "\n", - "tmp = tempfile.NamedTemporaryFile(delete=True)\n", - "\n", - "with requests.get(url, stream=True) as r:\n", - " r.raise_for_status()\n", - " for chunk in r.iter_content(chunk_size=8192):\n", - " tmp.write(chunk)\n", - "\n", - "tmp.flush()\n", - "tmp.seek(0)" - ] - }, - { - "cell_type": "markdown", - "id": "50f896bb", - "metadata": {}, - "source": [ - "Use the `pyarrow` `dtype_backend` when preparing local Pandas data for SQL\n", - "processing. This ensures more consistent handling of NULL values and seamless\n", - "schema mapping when you hand off the data to the BigQuery SQL engine. For this\n", - "example, read the 'Table05' sheet, which contains annual wheat supply and\n", - "disappearance data:\n" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "8a8a137b", - "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", - "
Marketing year 1/Time periodBeginning stocksProductionImports 2/Total supply 3/Food useSeed useFeed and residual useTotal domestic use 3/Exports 2/Total disappearance 3/Ending stocks
01950/51MY Jun-May496.01019.011.01526.0580.0--109.0689.0345.01034.0492.0
11951/52MY Jun-May492.0988.030.01510.0585.0--110.0695.0485.01180.0330.0
21952/53MY Jun-May330.01306.024.01660.0578.0--78.0656.0332.0988.0672.0
31953/54MY Jun-May672.01173.06.01851.0556.0--87.0643.0214.0857.0994.0
41954/55MY Jun-May994.0984.03.01981.0552.0--53.0605.0267.0872.01109.0
..........................................
2811/ June–May. Latest data may be preliminary or...<NA><NA><NA><NA><NA><NA><NA><NA><NA><NA><NA><NA>
2822/ Includes flour and selected other products ...<NA><NA><NA><NA><NA><NA><NA><NA><NA><NA><NA><NA>
2833/ Totals may not add due to rounding.<NA><NA><NA><NA><NA><NA><NA><NA><NA><NA><NA><NA>
284Source: USDA, Economic Research Service, based...<NA><NA><NA><NA><NA><NA><NA><NA><NA><NA><NA><NA>
285Updated: May 12, 2026<NA><NA><NA><NA><NA><NA><NA><NA><NA><NA><NA><NA>
\n", - "

286 rows × 13 columns

\n", - "
" - ], - "text/plain": [ - " Marketing year 1/ Time period \\\n", - "0 1950/51 MY Jun-May \n", - "1 1951/52 MY Jun-May \n", - "2 1952/53 MY Jun-May \n", - "3 1953/54 MY Jun-May \n", - "4 1954/55 MY Jun-May \n", - ".. ... ... \n", - "281 1/ June–May. Latest data may be preliminary or... \n", - "282 2/ Includes flour and selected other products ... \n", - "283 3/ Totals may not add due to rounding. \n", - "284 Source: USDA, Economic Research Service, based... \n", - "285 Updated: May 12, 2026 \n", - "\n", - " Beginning stocks Production Imports 2/ Total supply 3/ Food use \\\n", - "0 496.0 1019.0 11.0 1526.0 580.0 \n", - "1 492.0 988.0 30.0 1510.0 585.0 \n", - "2 330.0 1306.0 24.0 1660.0 578.0 \n", - "3 672.0 1173.0 6.0 1851.0 556.0 \n", - "4 994.0 984.0 3.0 1981.0 552.0 \n", - ".. ... ... ... ... ... \n", - "281 \n", - "282 \n", - "283 \n", - "284 \n", - "285 \n", - "\n", - " Seed use Feed and residual use Total domestic use 3/ Exports 2/ \\\n", - "0 -- 109.0 689.0 345.0 \n", - "1 -- 110.0 695.0 485.0 \n", - "2 -- 78.0 656.0 332.0 \n", - "3 -- 87.0 643.0 214.0 \n", - "4 -- 53.0 605.0 267.0 \n", - ".. ... ... ... ... \n", - "281 \n", - "282 \n", - "283 \n", - "284 \n", - "285 \n", - "\n", - " Total disappearance 3/ Ending stocks \n", - "0 1034.0 492.0 \n", - "1 1180.0 330.0 \n", - "2 988.0 672.0 \n", - "3 857.0 994.0 \n", - "4 872.0 1109.0 \n", - ".. ... ... \n", - "281 \n", - "282 \n", - "283 \n", - "284 \n", - "285 \n", - "\n", - "[286 rows x 13 columns]" - ] - }, - "execution_count": 2, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "\n", - "import pandas as pd\n", - "\n", - "df = pd.read_excel(\n", - " tmp,\n", - " sheet_name=\"Table05\",\n", - " dtype_backend=\"pyarrow\",\n", - " engine=\"calamine\",\n", - " header=1, # Skip the first row.\n", - ")\n", - "tmp.close()\n", - "df" - ] - }, - { - "cell_type": "markdown", - "id": "1a7ec573", - "metadata": {}, - "source": [ - "## Preparing the data\n", - "\n", - "Before querying the local DataFrame with SQL, ensure that the column names are\n", - "SQL-friendly. BigQuery supports [flexible column\n", - "names](https://docs.cloud.google.com/bigquery/docs/schemas#flexible-column-names),\n", - "allowing most unicode characters, but special characters like \"/\" and \"\\\" must\n", - "be removed or replaced.\n" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "d5674020", - "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", - "
Marketing year 1Time periodBeginning stocksProductionImports 2Total supply 3Food useSeed useFeed and residual useTotal domestic use 3Exports 2Total disappearance 3Ending stocks
01950/51MY Jun-May496.01019.011.01526.0580.0--109.0689.0345.01034.0492.0
11951/52MY Jun-May492.0988.030.01510.0585.0--110.0695.0485.01180.0330.0
21952/53MY Jun-May330.01306.024.01660.0578.0--78.0656.0332.0988.0672.0
31953/54MY Jun-May672.01173.06.01851.0556.0--87.0643.0214.0857.0994.0
41954/55MY Jun-May994.0984.03.01981.0552.0--53.0605.0267.0872.01109.0
..........................................
2811/ June–May. Latest data may be preliminary or...<NA><NA><NA><NA><NA><NA><NA><NA><NA><NA><NA><NA>
2822/ Includes flour and selected other products ...<NA><NA><NA><NA><NA><NA><NA><NA><NA><NA><NA><NA>
2833/ Totals may not add due to rounding.<NA><NA><NA><NA><NA><NA><NA><NA><NA><NA><NA><NA>
284Source: USDA, Economic Research Service, based...<NA><NA><NA><NA><NA><NA><NA><NA><NA><NA><NA><NA>
285Updated: May 12, 2026<NA><NA><NA><NA><NA><NA><NA><NA><NA><NA><NA><NA>
\n", - "

286 rows × 13 columns

\n", - "
" - ], - "text/plain": [ - " Marketing year 1 Time period \\\n", - "0 1950/51 MY Jun-May \n", - "1 1951/52 MY Jun-May \n", - "2 1952/53 MY Jun-May \n", - "3 1953/54 MY Jun-May \n", - "4 1954/55 MY Jun-May \n", - ".. ... ... \n", - "281 1/ June–May. Latest data may be preliminary or... \n", - "282 2/ Includes flour and selected other products ... \n", - "283 3/ Totals may not add due to rounding. \n", - "284 Source: USDA, Economic Research Service, based... \n", - "285 Updated: May 12, 2026 \n", - "\n", - " Beginning stocks Production Imports 2 Total supply 3 Food use \\\n", - "0 496.0 1019.0 11.0 1526.0 580.0 \n", - "1 492.0 988.0 30.0 1510.0 585.0 \n", - "2 330.0 1306.0 24.0 1660.0 578.0 \n", - "3 672.0 1173.0 6.0 1851.0 556.0 \n", - "4 994.0 984.0 3.0 1981.0 552.0 \n", - ".. ... ... ... ... ... \n", - "281 \n", - "282 \n", - "283 \n", - "284 \n", - "285 \n", - "\n", - " Seed use Feed and residual use Total domestic use 3 Exports 2 \\\n", - "0 -- 109.0 689.0 345.0 \n", - "1 -- 110.0 695.0 485.0 \n", - "2 -- 78.0 656.0 332.0 \n", - "3 -- 87.0 643.0 214.0 \n", - "4 -- 53.0 605.0 267.0 \n", - ".. ... ... ... ... \n", - "281 \n", - "282 \n", - "283 \n", - "284 \n", - "285 \n", - "\n", - " Total disappearance 3 Ending stocks \n", - "0 1034.0 492.0 \n", - "1 1180.0 330.0 \n", - "2 988.0 672.0 \n", - "3 857.0 994.0 \n", - "4 872.0 1109.0 \n", - ".. ... ... \n", - "281 \n", - "282 \n", - "283 \n", - "284 \n", - "285 \n", - "\n", - "[286 rows x 13 columns]" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "df.columns = [name.replace(\"/\", \"\") for name in df.columns]\n", - "df" - ] - }, - { - "cell_type": "markdown", - "id": "b50c5798", - "metadata": {}, - "source": [ - "## Filtering with Pandas\n", - "\n", - "Perform a basic filter using standard Python/Pandas syntax to remove rows with missing data. This represents the initial Python-only stage of a processing chain.\n" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "1dbad481", - "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", - "
Marketing year 1Time periodBeginning stocksProductionImports 2Total supply 3Food useSeed useFeed and residual useTotal domestic use 3Exports 2Total disappearance 3Ending stocks
01950/51MY Jun-May496.01019.011.01526.0580.0--109.0689.0345.01034.0492.0
11951/52MY Jun-May492.0988.030.01510.0585.0--110.0695.0485.01180.0330.0
21952/53MY Jun-May330.01306.024.01660.0578.0--78.0656.0332.0988.0672.0
31953/54MY Jun-May672.01173.06.01851.0556.0--87.0643.0214.0857.0994.0
41954/55MY Jun-May994.0984.03.01981.0552.0--53.0605.0267.0872.01109.0
..........................................
2752025/26MY Jun-May854.7341984.537125.02964.271960.059.7100.01119.7910.02029.7934.571
2762025/26Q1 Jun-Aug854.7341984.53730.5932869.864241.0912.653239.522483.266252.58735.8462134.018
2772025/26Q2 Sep-Nov2134.0180.030.0782164.096245.5839.658-54.047231.191255.802486.9931677.103
2782025/26Q3 Dec-Feb1677.1030.032.3631709.466230.9751.75-24.747207.978201.291409.2691300.197
2792026/27MY Jun-May934.5711561.322140.02635.893960.05980.01099.0775.01874.0761.893
\n", - "

280 rows × 13 columns

\n", - "
" - ], - "text/plain": [ - " Marketing year 1 Time period Beginning stocks Production Imports 2 \\\n", - "0 1950/51 MY Jun-May 496.0 1019.0 11.0 \n", - "1 1951/52 MY Jun-May 492.0 988.0 30.0 \n", - "2 1952/53 MY Jun-May 330.0 1306.0 24.0 \n", - "3 1953/54 MY Jun-May 672.0 1173.0 6.0 \n", - "4 1954/55 MY Jun-May 994.0 984.0 3.0 \n", - ".. ... ... ... ... ... \n", - "275 2025/26 MY Jun-May 854.734 1984.537 125.0 \n", - "276 2025/26 Q1 Jun-Aug 854.734 1984.537 30.593 \n", - "277 2025/26 Q2 Sep-Nov 2134.018 0.0 30.078 \n", - "278 2025/26 Q3 Dec-Feb 1677.103 0.0 32.363 \n", - "279 2026/27 MY Jun-May 934.571 1561.322 140.0 \n", - "\n", - " Total supply 3 Food use Seed use Feed and residual use \\\n", - "0 1526.0 580.0 -- 109.0 \n", - "1 1510.0 585.0 -- 110.0 \n", - "2 1660.0 578.0 -- 78.0 \n", - "3 1851.0 556.0 -- 87.0 \n", - "4 1981.0 552.0 -- 53.0 \n", - ".. ... ... ... ... \n", - "275 2964.271 960.0 59.7 100.0 \n", - "276 2869.864 241.091 2.653 239.522 \n", - "277 2164.096 245.58 39.658 -54.047 \n", - "278 1709.466 230.975 1.75 -24.747 \n", - "279 2635.893 960.0 59 80.0 \n", - "\n", - " Total domestic use 3 Exports 2 Total disappearance 3 Ending stocks \n", - "0 689.0 345.0 1034.0 492.0 \n", - "1 695.0 485.0 1180.0 330.0 \n", - "2 656.0 332.0 988.0 672.0 \n", - "3 643.0 214.0 857.0 994.0 \n", - "4 605.0 267.0 872.0 1109.0 \n", - ".. ... ... ... ... \n", - "275 1119.7 910.0 2029.7 934.571 \n", - "276 483.266 252.58 735.846 2134.018 \n", - "277 231.191 255.802 486.993 1677.103 \n", - "278 207.978 201.291 409.269 1300.197 \n", - "279 1099.0 775.0 1874.0 761.893 \n", - "\n", - "[280 rows x 13 columns]" - ] - }, - "execution_count": 4, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "full_rows = df[~df['Beginning stocks'].isna()]\n", - "full_rows" - ] - }, - { - "cell_type": "markdown", - "id": "e914ce69", - "metadata": {}, - "source": [ - "## Interoperate with SQL using the BigQuery SQL magics (%%bqsql)\n", - "\n", - "The BigQuery DataFrames library provides the `%%bqsql` magic, which acts as the bridge between your Python and SQL environments. It allows the BigQuery query engine to directly reference and query your local Pandas DataFrames (by implicitly uploading them as temporary tables) as well as actual BigQuery tables and external tables in GCS (Parquet, Iceberg, CSV).\n", - "\n", - "To enable this integration in your notebook, load the `bigframes` extension. This is already completed in BigQuery Studio, Colab Enterprise, and Colab notebooks. For other environments, such as VS Code and Jupyter Lab, run the following cell:\n" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "id": "3d837a5e", - "metadata": {}, - "outputs": [], - "source": [ - "%load_ext bigframes\n" - ] - }, - { - "cell_type": "markdown", - "id": "315a53b5", - "metadata": {}, - "source": [ - "To ensure the correct Google Cloud project is billed for query usage, including free tier usage, configure the project ID used by the magics. Even in the free sandbox tier, a project ID is required to allocate query resources. If you don't set it explicitly, BigFrames will try to discover it from your environment (e.g., your Application Default Credentials).\n" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "id": "ffe5757c", - "metadata": {}, - "outputs": [], - "source": [ - "import bigframes.pandas as bpd\n", - "\n", - "PROJECT_ID = \"\" # @param {type:\"string\"}\n", - "bpd.options.bigquery.project = PROJECT_ID\n" - ] - }, - { - "cell_type": "markdown", - "id": "fe174ed2", - "metadata": {}, - "source": [ - "### Querying Local Pandas DataFrames with SQL\n", - "\n", - "With the project configured, you can now run SQL queries directly against your local Pandas DataFrame (`full_rows`) as if it were a table in BigQuery. Simply reference the variable name inside braces `{full_rows}` in your SQL query.\n" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "id": "fbbf52d6", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - " Query processed 0 Bytes. [Job bigframes-dev:US.c3c67902-6a45-492a-9491-a91daddaada1 details]\n", - " " - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Load job c22ec1ce-09da-4ea1-b0a0-f28eee65aa20 is DONE. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "\n", - " Query processed 30.0 kB in a moment of slot time.\n", - " " - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "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", - "
Marketing year 1Time periodBeginning stocksProductionImports 2Total supply 3Food useSeed useFeed and residual useTotal domestic use 3Exports 2Total disappearance 3Ending stocks
01980/81Q2 Sep-Nov2714.00.00.62714.6162.1764.865242.965379.335622.32092.3
11987/88Q2 Sep-Nov2976.4620.04.5252980.987193.04858-79.082171.966308.453480.4192500.568
22014/15Q2 Sep-Nov1907.220.034.5511941.771248.18748.802-92.585204.404207.737412.1411529.63
31976/77Q2 Sep-Nov2385.20.00.52385.7153.064-2.795214.205277.295491.51894.2
41994/95Q2 Sep-Nov2069.4940.021.4232090.917229.29760.954-28.64261.611338.202599.8131491.104
52002/03Q2 Sep-Nov1748.9870.023.0871772.074237.75454.599-74.678217.675234.53452.2051319.869
62007/08Q2 Sep-Nov1716.9270.021.4861738.413245.02659.915-119.882185.059421.416606.4751131.938
72025/26Q2 Sep-Nov2134.0180.030.0782164.096245.5839.658-54.047231.191255.802486.9931677.103
81995/96Q2 Sep-Nov1881.0990.016.2521897.351232.15164.356-98.182198.325360.759559.0841338.267
92001/02Q2 Sep-Nov2155.8140.029.042184.854245.08851.601-23.073273.616287.783561.3991623.455
\n", - "

10 rows × 13 columns

\n", - "
[280 rows x 13 columns in total]" - ], - "text/plain": [ - " Marketing year 1 Time period Beginning stocks Production Imports 2 \\\n", - "0 1980/81 Q2 Sep-Nov 2714.0 0.0 0.6 \n", - "1 1987/88 Q2 Sep-Nov 2976.462 0.0 4.525 \n", - "2 2014/15 Q2 Sep-Nov 1907.22 0.0 34.551 \n", - "3 1976/77 Q2 Sep-Nov 2385.2 0.0 0.5 \n", - "4 1994/95 Q2 Sep-Nov 2069.494 0.0 21.423 \n", - "5 2002/03 Q2 Sep-Nov 1748.987 0.0 23.087 \n", - "6 2007/08 Q2 Sep-Nov 1716.927 0.0 21.486 \n", - "7 2025/26 Q2 Sep-Nov 2134.018 0.0 30.078 \n", - "8 1995/96 Q2 Sep-Nov 1881.099 0.0 16.252 \n", - "9 2001/02 Q2 Sep-Nov 2155.814 0.0 29.04 \n", - "\n", - " Total supply 3 Food use Seed use Feed and residual use \\\n", - "0 2714.6 162.1 76 4.865 \n", - "1 2980.987 193.048 58 -79.082 \n", - "2 1941.771 248.187 48.802 -92.585 \n", - "3 2385.7 153.0 64 -2.795 \n", - "4 2090.917 229.297 60.954 -28.64 \n", - "5 1772.074 237.754 54.599 -74.678 \n", - "6 1738.413 245.026 59.915 -119.882 \n", - "7 2164.096 245.58 39.658 -54.047 \n", - "8 1897.351 232.151 64.356 -98.182 \n", - "9 2184.854 245.088 51.601 -23.073 \n", - "\n", - " Total domestic use 3 Exports 2 Total disappearance 3 Ending stocks \n", - "0 242.965 379.335 622.3 2092.3 \n", - "1 171.966 308.453 480.419 2500.568 \n", - "2 204.404 207.737 412.141 1529.63 \n", - "3 214.205 277.295 491.5 1894.2 \n", - "4 261.611 338.202 599.813 1491.104 \n", - "5 217.675 234.53 452.205 1319.869 \n", - "6 185.059 421.416 606.475 1131.938 \n", - "7 231.191 255.802 486.993 1677.103 \n", - "8 198.325 360.759 559.084 1338.267 \n", - "9 273.616 287.783 561.399 1623.455 \n", - "...\n", - "\n", - "[280 rows x 13 columns]" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "%%bqsql\n", - "SELECT * FROM {full_rows}\n" - ] - }, - { - "cell_type": "markdown", - "id": "2fcd5284", - "metadata": {}, - "source": [ - "You should see the results from full_rows.\n", - "\n", - "\n", - "## Chaining SQL and Python: Saving SQL Results\n", - "\n", - "The true power of the `%%bqsql` magic lies in chaining. By providing a destination variable name as an argument to `%%bqsql` (e.g., `%%bqsql destination_var`), the query result is saved as a BigQuery DataFrame (a.k.a. BigFrames DataFrame) to that variable. \n", - "\n", - "This DataFrame lives on the BigQuery engine but behaves like a Pandas DataFrame in Python. You can immediately use it in subsequent Python cells, or reference it again in another SQL cell. This allows you to build a multi-step, hybrid processing pipeline.\n", - "\n", - "Filter the data to only yearly entries using SQL, and save the result into a new BigFrames DataFrame named `yearly`:\n" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "id": "75fe0e10", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - " Query processed 0 Bytes in a moment of slot time. [Job bigframes-dev:US.71850fc1-147f-44f7-b4c0-b94592f55639 details]\n", - " " - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Load job aaa74c26-b3ff-422f-a670-93222188fe9f is DONE. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "\n", - " Query processed 30.0 kB in a moment of slot time.\n", - " " - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "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", - "
Marketing year 1Time periodBeginning stocksProductionImports 2Total supply 3Food useSeed useFeed and residual useTotal domestic use 3Exports 2Total disappearance 3Ending stocks
01955/56MY Jun-May1109.0937.010.02056.0553.0--51.0604.0322.0926.01130.0
11957/58MY Jun-May1004.0956.010.01970.0547.0--43.0590.0418.01008.0962.0
21954/55MY Jun-May994.0984.03.01981.0552.0--53.0605.0267.0872.01109.0
31951/52MY Jun-May492.0988.030.01510.0585.0--110.0695.0485.01180.0330.0
41956/57MY Jun-May1130.01005.08.02143.0541.0--57.0598.0541.01139.01004.0
51950/51MY Jun-May496.01019.011.01526.0580.0--109.0689.0345.01034.0492.0
61962/63MY Jun-May1420.61092.05.32517.9502.761.434.7598.8649.41248.21269.7
71959/60MY Jun-May1368.01118.07.02493.0558.0--49.0607.0502.01109.01384.0
81963/64MY Jun-May1269.71146.84.02420.5487.964.928.6581.4845.61427.0993.5
91953/54MY Jun-May672.01173.06.01851.0556.0--87.0643.0214.0857.0994.0
\n", - "

10 rows × 13 columns

\n", - "
[77 rows x 13 columns in total]" - ], - "text/plain": [ - " Marketing year 1 Time period Beginning stocks Production Imports 2 \\\n", - "0 1955/56 MY Jun-May 1109.0 937.0 10.0 \n", - "1 1957/58 MY Jun-May 1004.0 956.0 10.0 \n", - "2 1954/55 MY Jun-May 994.0 984.0 3.0 \n", - "3 1951/52 MY Jun-May 492.0 988.0 30.0 \n", - "4 1956/57 MY Jun-May 1130.0 1005.0 8.0 \n", - "5 1950/51 MY Jun-May 496.0 1019.0 11.0 \n", - "6 1962/63 MY Jun-May 1420.6 1092.0 5.3 \n", - "7 1959/60 MY Jun-May 1368.0 1118.0 7.0 \n", - "8 1963/64 MY Jun-May 1269.7 1146.8 4.0 \n", - "9 1953/54 MY Jun-May 672.0 1173.0 6.0 \n", - "\n", - " Total supply 3 Food use Seed use Feed and residual use \\\n", - "0 2056.0 553.0 -- 51.0 \n", - "1 1970.0 547.0 -- 43.0 \n", - "2 1981.0 552.0 -- 53.0 \n", - "3 1510.0 585.0 -- 110.0 \n", - "4 2143.0 541.0 -- 57.0 \n", - "5 1526.0 580.0 -- 109.0 \n", - "6 2517.9 502.7 61.4 34.7 \n", - "7 2493.0 558.0 -- 49.0 \n", - "8 2420.5 487.9 64.9 28.6 \n", - "9 1851.0 556.0 -- 87.0 \n", - "\n", - " Total domestic use 3 Exports 2 Total disappearance 3 Ending stocks \n", - "0 604.0 322.0 926.0 1130.0 \n", - "1 590.0 418.0 1008.0 962.0 \n", - "2 605.0 267.0 872.0 1109.0 \n", - "3 695.0 485.0 1180.0 330.0 \n", - "4 598.0 541.0 1139.0 1004.0 \n", - "5 689.0 345.0 1034.0 492.0 \n", - "6 598.8 649.4 1248.2 1269.7 \n", - "7 607.0 502.0 1109.0 1384.0 \n", - "8 581.4 845.6 1427.0 993.5 \n", - "9 643.0 214.0 857.0 994.0 \n", - "...\n", - "\n", - "[77 rows x 13 columns]" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "%%bqsql yearly\n", - "SELECT *\n", - "FROM {full_rows}\n", - "WHERE STARTS_WITH(`Time period`, 'MY')\n" - ] - }, - { - "cell_type": "markdown", - "id": "19a70e9e", - "metadata": {}, - "source": [ - "### Chaining Step 2: Complex SQL Transformation on the BigFrames DataFrame\n", - "\n", - "Now, you can chain another SQL operation. Reference the `yearly` BigFrames DataFrame that you just created, extract the year using SQL regular expressions, cast it to a timestamp, and save the results into a new BigFrames DataFrame named `timeseries`.\n", - "\n", - "Notice how you are building a chain: Local Pandas -> [SQL filter] -> BigFrames `yearly` -> [SQL transform] -> BigFrames `timeseries`.\n" - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "id": "8fbb5224", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - " Query processed 0 Bytes in a moment of slot time. [Job bigframes-dev:US.fdcdabc9-e1a3-47e6-ab27-9e19d9aaa106 details]\n", - " " - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Load job ec6be16a-722d-4151-b7a6-5e410557577d is DONE. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "\n", - " Query processed 8.3 kB in a moment of slot time.\n", - " " - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "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", - "
Time periodBeginning stocksProductionImports 2Total supply 3Food useSeed useFeed and residual useTotal domestic use 3Exports 2Total disappearance 3Ending stocksyear
0MY Jun-May1004.0956.010.01970.0547.0--43.0590.0418.01008.0962.01957-01-01 00:00:00+00:00
1MY Jun-May672.01173.06.01851.0556.0--87.0643.0214.0857.0994.01953-01-01 00:00:00+00:00
2MY Jun-May496.01019.011.01526.0580.0--109.0689.0345.01034.0492.01950-01-01 00:00:00+00:00
3MY Jun-May330.01306.024.01660.0578.0--78.0656.0332.0988.0672.01952-01-01 00:00:00+00:00
4MY Jun-May962.01457.08.02427.0561.0--48.0609.0450.01059.01368.01958-01-01 00:00:00+00:00
5MY Jun-May994.0984.03.01981.0552.0--53.0605.0267.0872.01109.01954-01-01 00:00:00+00:00
6MY Jun-May492.0988.030.01510.0585.0--110.0695.0485.01180.0330.01951-01-01 00:00:00+00:00
7MY Jun-May1130.01005.08.02143.0541.0--57.0598.0541.01139.01004.01956-01-01 00:00:00+00:00
8MY Jun-May1109.0937.010.02056.0553.0--51.0604.0322.0926.01130.01955-01-01 00:00:00+00:00
9MY Jun-May1368.01118.07.02493.0558.0--49.0607.0502.01109.01384.01959-01-01 00:00:00+00:00
\n", - "

10 rows × 13 columns

\n", - "
[77 rows x 13 columns in total]" - ], - "text/plain": [ - " Time period Beginning stocks Production Imports 2 Total supply 3 \\\n", - "0 MY Jun-May 1004.0 956.0 10.0 1970.0 \n", - "1 MY Jun-May 672.0 1173.0 6.0 1851.0 \n", - "2 MY Jun-May 496.0 1019.0 11.0 1526.0 \n", - "3 MY Jun-May 330.0 1306.0 24.0 1660.0 \n", - "4 MY Jun-May 962.0 1457.0 8.0 2427.0 \n", - "5 MY Jun-May 994.0 984.0 3.0 1981.0 \n", - "6 MY Jun-May 492.0 988.0 30.0 1510.0 \n", - "7 MY Jun-May 1130.0 1005.0 8.0 2143.0 \n", - "8 MY Jun-May 1109.0 937.0 10.0 2056.0 \n", - "9 MY Jun-May 1368.0 1118.0 7.0 2493.0 \n", - "\n", - " Food use Seed use Feed and residual use Total domestic use 3 Exports 2 \\\n", - "0 547.0 -- 43.0 590.0 418.0 \n", - "1 556.0 -- 87.0 643.0 214.0 \n", - "2 580.0 -- 109.0 689.0 345.0 \n", - "3 578.0 -- 78.0 656.0 332.0 \n", - "4 561.0 -- 48.0 609.0 450.0 \n", - "5 552.0 -- 53.0 605.0 267.0 \n", - "6 585.0 -- 110.0 695.0 485.0 \n", - "7 541.0 -- 57.0 598.0 541.0 \n", - "8 553.0 -- 51.0 604.0 322.0 \n", - "9 558.0 -- 49.0 607.0 502.0 \n", - "\n", - " Total disappearance 3 Ending stocks year \n", - "0 1008.0 962.0 1957-01-01 00:00:00+00:00 \n", - "1 857.0 994.0 1953-01-01 00:00:00+00:00 \n", - "2 1034.0 492.0 1950-01-01 00:00:00+00:00 \n", - "3 988.0 672.0 1952-01-01 00:00:00+00:00 \n", - "4 1059.0 1368.0 1958-01-01 00:00:00+00:00 \n", - "5 872.0 1109.0 1954-01-01 00:00:00+00:00 \n", - "6 1180.0 330.0 1951-01-01 00:00:00+00:00 \n", - "7 1139.0 1004.0 1956-01-01 00:00:00+00:00 \n", - "8 926.0 1130.0 1955-01-01 00:00:00+00:00 \n", - "9 1109.0 1384.0 1959-01-01 00:00:00+00:00 \n", - "...\n", - "\n", - "[77 rows x 13 columns]" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "%%bqsql timeseries\n", - "SELECT\n", - " * EXCEPT (`Marketing year 1`),\n", - " TIMESTAMP(CONCAT(\n", - " REGEXP_EXTRACT(`Marketing year 1`, r'([0-9]+)\\/'),\n", - " '-01-01')) AS `year`\n", - "FROM {yearly}\n" - ] - }, - { - "cell_type": "markdown", - "id": "76ba8a7d", - "metadata": {}, - "source": [ - "## Chaining Back to Python: Visualizing BigFrames Data\n", - "\n", - "Now that you've completed some SQL transformations, you can chain back to Python for visualization. Because BigFrames DataFrames implement the Pandas API, you can call standard visualization methods (like `.plot.line()`) directly on the `timeseries` DataFrame without downloading the full dataset first. The computations happen in BigQuery, and only the summarized chart data is sent back to the notebook.\n" - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "id": "d3ff4eec", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - " Query processed 8.8 kB in a moment of slot time.\n", - " " - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Load job c6b4d65a-4555-4efc-9f8b-7156f4c62835 is DONE. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/plain": [ - "" - ] - }, - "execution_count": 12, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "image/png": "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", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "timeseries.set_index('year').sort_index().plot.line()\n" - ] - }, - { - "cell_type": "markdown", - "id": "4b15e937", - "metadata": {}, - "source": [ - "### Downloading to Local Pandas (Optional Handoff)\n", - "\n", - "If you need to use local Python libraries that are not supported by BigFrames (such as custom plotting libraries or local ML frameworks), you can explicitly download the final transformed remote DataFrame into a standard local Pandas DataFrame using `.to_pandas()`:\n" - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "id": "97757974", - "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", - "
Time periodBeginning stocksProductionImports 2Total supply 3Food useSeed useFeed and residual useTotal domestic use 3Exports 2Total disappearance 3Ending stocks
year
1950-01-01 00:00:00+00:00MY Jun-May496.01019.011.01526.0580.0--109.0689.0345.01034.0492.0
1951-01-01 00:00:00+00:00MY Jun-May492.0988.030.01510.0585.0--110.0695.0485.01180.0330.0
1952-01-01 00:00:00+00:00MY Jun-May330.01306.024.01660.0578.0--78.0656.0332.0988.0672.0
1953-01-01 00:00:00+00:00MY Jun-May672.01173.06.01851.0556.0--87.0643.0214.0857.0994.0
1954-01-01 00:00:00+00:00MY Jun-May994.0984.03.01981.0552.0--53.0605.0267.0872.01109.0
.......................................
2022-01-01 00:00:00+00:00MY Jun-May674.4311649.713121.5852445.729971.67768.36975.5031115.549760.6121876.161569.568
2023-01-01 00:00:00+00:00MY Jun-May569.5681803.942137.7982511.308961.30362.04685.6171108.966705.9081814.874696.434
2024-01-01 00:00:00+00:00MY Jun-May696.4341978.697148.9542824.085969.49361.1112.8631143.456825.8951969.351854.734
2025-01-01 00:00:00+00:00MY Jun-May854.7341984.537125.02964.271960.059.7100.01119.7910.02029.7934.571
2026-01-01 00:00:00+00:00MY Jun-May934.5711561.322140.02635.893960.05980.01099.0775.01874.0761.893
\n", - "

77 rows × 12 columns

\n", - "
" - ], - "text/plain": [ - " Time period Beginning stocks Production \\\n", - "year \n", - "1950-01-01 00:00:00+00:00 MY Jun-May 496.0 1019.0 \n", - "1951-01-01 00:00:00+00:00 MY Jun-May 492.0 988.0 \n", - "1952-01-01 00:00:00+00:00 MY Jun-May 330.0 1306.0 \n", - "1953-01-01 00:00:00+00:00 MY Jun-May 672.0 1173.0 \n", - "1954-01-01 00:00:00+00:00 MY Jun-May 994.0 984.0 \n", - "... ... ... ... \n", - "2022-01-01 00:00:00+00:00 MY Jun-May 674.431 1649.713 \n", - "2023-01-01 00:00:00+00:00 MY Jun-May 569.568 1803.942 \n", - "2024-01-01 00:00:00+00:00 MY Jun-May 696.434 1978.697 \n", - "2025-01-01 00:00:00+00:00 MY Jun-May 854.734 1984.537 \n", - "2026-01-01 00:00:00+00:00 MY Jun-May 934.571 1561.322 \n", - "\n", - " Imports 2 Total supply 3 Food use Seed use \\\n", - "year \n", - "1950-01-01 00:00:00+00:00 11.0 1526.0 580.0 -- \n", - "1951-01-01 00:00:00+00:00 30.0 1510.0 585.0 -- \n", - "1952-01-01 00:00:00+00:00 24.0 1660.0 578.0 -- \n", - "1953-01-01 00:00:00+00:00 6.0 1851.0 556.0 -- \n", - "1954-01-01 00:00:00+00:00 3.0 1981.0 552.0 -- \n", - "... ... ... ... ... \n", - "2022-01-01 00:00:00+00:00 121.585 2445.729 971.677 68.369 \n", - "2023-01-01 00:00:00+00:00 137.798 2511.308 961.303 62.046 \n", - "2024-01-01 00:00:00+00:00 148.954 2824.085 969.493 61.1 \n", - "2025-01-01 00:00:00+00:00 125.0 2964.271 960.0 59.7 \n", - "2026-01-01 00:00:00+00:00 140.0 2635.893 960.0 59 \n", - "\n", - " Feed and residual use Total domestic use 3 \\\n", - "year \n", - "1950-01-01 00:00:00+00:00 109.0 689.0 \n", - "1951-01-01 00:00:00+00:00 110.0 695.0 \n", - "1952-01-01 00:00:00+00:00 78.0 656.0 \n", - "1953-01-01 00:00:00+00:00 87.0 643.0 \n", - "1954-01-01 00:00:00+00:00 53.0 605.0 \n", - "... ... ... \n", - "2022-01-01 00:00:00+00:00 75.503 1115.549 \n", - "2023-01-01 00:00:00+00:00 85.617 1108.966 \n", - "2024-01-01 00:00:00+00:00 112.863 1143.456 \n", - "2025-01-01 00:00:00+00:00 100.0 1119.7 \n", - "2026-01-01 00:00:00+00:00 80.0 1099.0 \n", - "\n", - " Exports 2 Total disappearance 3 Ending stocks \n", - "year \n", - "1950-01-01 00:00:00+00:00 345.0 1034.0 492.0 \n", - "1951-01-01 00:00:00+00:00 485.0 1180.0 330.0 \n", - "1952-01-01 00:00:00+00:00 332.0 988.0 672.0 \n", - "1953-01-01 00:00:00+00:00 214.0 857.0 994.0 \n", - "1954-01-01 00:00:00+00:00 267.0 872.0 1109.0 \n", - "... ... ... ... \n", - "2022-01-01 00:00:00+00:00 760.612 1876.161 569.568 \n", - "2023-01-01 00:00:00+00:00 705.908 1814.874 696.434 \n", - "2024-01-01 00:00:00+00:00 825.895 1969.351 854.734 \n", - "2025-01-01 00:00:00+00:00 910.0 2029.7 934.571 \n", - "2026-01-01 00:00:00+00:00 775.0 1874.0 761.893 \n", - "\n", - "[77 rows x 12 columns]" - ] - }, - "execution_count": 13, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "pddf = timeseries.set_index('year').sort_index().to_pandas()\n", - "pddf\n" - ] - }, - { - "cell_type": "markdown", - "id": "9c1242ab", - "metadata": {}, - "source": [ - "## Conclusion: The Power of Hybrid Chaining\n", - "\n", - "By leveraging BigQuery DataFrames and the `%%bqsql` magic, you have built a powerful, interoperable pipeline that seamlessly transitions between SQL and Python.\n", - "\n", - "This hybrid approach offers several key benefits:\n", - "- **Optimal Tool Selection**: Use SQL for what it does best (complex queries, window functions, regex extractions on large sets) and Python for what it does best (visualization, statistical analysis, ML, orchestrating workflow).\n", - "- **Improved Readability**: Instead of massive, unreadable SQL queries with dozens of CTEs, or long, complex Pandas method chains, you can split your pipeline into logical steps, alternating between SQL and Python.\n", - "- **Seamless Scaling**: The exact same `%%bqsql` code can scale from a tiny local Pandas DataFrame to billions of rows in a production BigQuery table. You only need to swap the initial local Pandas DataFrame with a BigQuery DataFrame reference.\n", - "\n", - "\n", - "## Next Steps\n", - "\n", - "In addition to the `%%bqsql` cell magic, BigFrames also registers a **BigQuery Accessor** on standard Pandas DataFrames, allowing you to run SQL scalar functions directly on local pandas data. \n", - "\n", - "For example, you can call powerful Google Cloud community UDFs from [BigQuery Utils](https://github.com/GoogleCloudPlatform/bigquery-utils/tree/master/udfs#bigquery-udfs), [BigFunctions](https://unytics.io/bigfunctions/bigfunctions/#function-categories), or [CARTO Analytics Toolbox for BigQuery](https://docs.carto.com/data-and-analysis/analytics-toolbox-for-bigquery) using `df.bigquery.sql_scalar(...)`:\n" - ] - }, - { - "cell_type": "markdown", - "id": "6a7928bd", - "metadata": {}, - "source": [ - "### Scaling Up: Advanced BigQuery Features\n", - "\n", - "While the BigQuery sandbox offers a powerful environment to test these hybrid Python-SQL workflows for free, some advanced features like BigQuery Machine Learning (BQML) are restricted. By connecting a billing account to your Google Cloud project, you can unlock advanced capabilities such as `ML.FORECAST` (or the `AI.FORECAST` function) to predict time-series data using Google's state-of-the-art foundational models directly from your SQL/Python chain.\n", - "\n", - "### Feedback & Community\n", - "\n", - "The BigFrames team would love to hear your feedback on the hybrid Python-SQL experience:\n", - "* **Email**: [bigframes-feedback@google.com](mailto:bigframes-feedback@google.com)\n", - "* **Issues**: File bug reports or feature requests on the [open-source BigFrames repository](https://github.com/googleapis/google-cloud-python/issues).\n", - "* **Updates**: To receive news and updates, subscribe to the [BigFrames email list](https://docs.google.com/forms/d/10EnDyYdYUW9HvelHYuBRC8L3GdGVl3rX0aroinbRZyc/edit?resourcekey=0-QUsnpzF91gm9hsp04rSA6Q).\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "bc1a6dbe-170e-4380-83da-779f37e1c00a", - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "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.14.3" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/notebooks/dataframes/pypi.ipynb b/notebooks/dataframes/pypi.ipynb deleted file mode 100644 index b1196cca8bf..00000000000 --- a/notebooks/dataframes/pypi.ipynb +++ /dev/null @@ -1,806 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [], - "source": [ - "# Copyright 2024 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": [ - "# Analyzing package downloads from PyPI with BigQuery DataFrames\n", - "\n", - "In this notebook, you'll use the [PyPI public dataset](https://console.cloud.google.com/marketplace/product/gcp-public-data-pypi/pypi) and the [deps.dev public dataset](https://deps.dev/) to visualize Python package downloads for a package and its dependencies." - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [], - "source": [ - "# Choose a package which you want to visualize.\n", - "package_name = \"pandas\"" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [], - "source": [ - "import bigframes.pandas as bpd\n", - "\n", - "# Use `ordering_mode=\"partial\"` for more efficient query generation, but\n", - "# some pandas-compatible methods may not be possible without a total ordering.\n", - "bpd.options.bigquery.ordering_mode = \"partial\"" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Counting downloads and tracking dependencies\n", - "\n", - "The [PyPI `file_downloads`](https://console.cloud.google.com/bigquery?ws=!1m5!1m4!4m3!1sbigquery-public-data!2spypi!3sfile_downloads) table contains a row for each time there is a download request for a package. The [deps.dev Dependencies](https://console.cloud.google.com/bigquery?ws=!1m5!1m4!4m3!1sbigquery-public-data!2sdeps_dev_v1!3sDependencies) table contains a row for each dependency of each package.\n", - "\n", - "When `ordering_mode = \"partial\"`, `read_gbq_table` creates a DataFrame representing the table, but the DataFrame has no native ordering or index." - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": {}, - "outputs": [], - "source": [ - "import bigframes.enums\n", - "\n", - "# Without ordering_mode = \"partial\" it is recommended that you set\n", - "# the \"filters\" parameter to limit the number of rows subsequent queries\n", - "# have to read.\n", - "pypi = bpd.read_gbq_table(\n", - " \"bigquery-public-data.pypi.file_downloads\",\n", - "\n", - " # Using ordering_mode = \"partial\" changes the default index to a \"NULL\"\n", - " # index, meaning no index is available for implicit joins.\n", - " #\n", - " # Setting this explicitly avoids a DefaultIndexWarning.\n", - " index_col=bigframes.enums.DefaultIndexKind.NULL,\n", - ")\n", - "deps = bpd.read_gbq_table(\n", - " \"bigquery-public-data.deps_dev_v1.Dependencies\",\n", - " index_col=bigframes.enums.DefaultIndexKind.NULL,\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Limit to the most recent 7 days of data\n", - "\n", - "The PyPI and deps.dev tables are partitioned by date. Query only the most recent 7 days of data to reduce the number of bytes scanned.\n", - "\n", - "Just as with the default ordering mode, filters can be describe in a pandas-compatible way by passing a Boolean Series to the DataFrame's `__getitem__` accessor." - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": {}, - "outputs": [], - "source": [ - "import datetime\n", - "\n", - "now = datetime.datetime.now(datetime.timezone.utc)\n", - "last_7_days = now - datetime.timedelta(days=7)\n", - "last_30_days = now - datetime.timedelta(days=30)\n", - "pypi = pypi[pypi[\"timestamp\"] > last_7_days]\n", - "deps = deps[deps[\"SnapshotAt\"] > last_30_days] # deps are refreshed less frequently\n", - "deps = deps[deps[\"System\"] == \"PYPI\"]" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n", - "**⚠ Warning**\n", - "\n", - "Without `ordering_mode = \"partial\"`, these filters do not change the number of bytes scanned. Instead, add column and row filters at \"read\" time. For example,\n", - "\n", - "```\n", - "import datetime\n", - "\n", - "last_7_days = datetime.datetime.now(datetime.timezone.utc) - datetime.timedelta(days=7)\n", - "\n", - "# Without ordering_mode = \"partial\", one must limit the data at \"read\" time to reduce bytes scanned.\n", - "pypi = bpd.read_gbq_table(\n", - " \"bigquery-public-data.pypi.file_downloads\",\n", - " columns=[\"timestamp\", \"project\"],\n", - " filters=[(\"timestamp\", \">\", last_7_days)],\n", - ")\n", - "```\n", - "\n", - "`head()` is not available when no ordering has been established. It fails with `OrderRequiredError`. Use `peek()` instead to download a sample of the data. This will be much more efficient, as the query doesn't need to order all rows to determine which are first." - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "Query job 423d9d93-1495-4c76-b8c2-e830a6e19ff4 is DONE. 110.3 MB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "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", - "
timestampcountry_codeurlprojectfiledetailstls_protocoltls_cipher
02024-09-18 18:15:04+00:00US/packages/ff/c8/4cd4b2834012ffc71ae3fd69187f08...aiobreaker{'filename': 'aiobreaker-1.2.0-py3-none-any.wh...{'installer': {'name': 'pip', 'version': '21.1...TLSv1.3TLS_AES_128_GCM_SHA256
12024-09-18 18:29:50+00:00US/packages/21/8e/4562029e179226051cd4aa3135444d...aiobotocore{'filename': 'aiobotocore-1.3.0.tar.gz', 'proj...{'installer': {'name': 'pip', 'version': '24.1...TLSv1.2ECDHE-RSA-AES128-GCM-SHA256
22024-09-18 18:22:14+00:00US/packages/11/16/4226e59bb72e096d9809ccedf349a1...aiobotocore{'filename': 'aiobotocore-2.0.1.tar.gz', 'proj...{'installer': {'name': 'pip', 'version': '24.2...TLSv1.2ECDHE-RSA-AES128-GCM-SHA256
32024-09-18 18:22:08+00:00US/packages/11/16/4226e59bb72e096d9809ccedf349a1...aiobotocore{'filename': 'aiobotocore-2.0.1.tar.gz', 'proj...{'installer': {'name': 'pip', 'version': '24.2...TLSv1.2ECDHE-RSA-AES128-GCM-SHA256
42024-09-18 18:29:22+00:00US/packages/54/b7/453119271cc4c36b07fdeab9b0ff25...aiobotocore{'filename': 'aiobotocore-2.3.3.tar.gz', 'proj...{'installer': {'name': 'pip', 'version': '24.1...TLSv1.2ECDHE-RSA-AES128-GCM-SHA256
\n", - "
" - ], - "text/plain": [ - " timestamp country_code \\\n", - "0 2024-09-18 18:15:04+00:00 US \n", - "1 2024-09-18 18:29:50+00:00 US \n", - "2 2024-09-18 18:22:14+00:00 US \n", - "3 2024-09-18 18:22:08+00:00 US \n", - "4 2024-09-18 18:29:22+00:00 US \n", - "\n", - " url project \\\n", - "0 /packages/ff/c8/4cd4b2834012ffc71ae3fd69187f08... aiobreaker \n", - "1 /packages/21/8e/4562029e179226051cd4aa3135444d... aiobotocore \n", - "2 /packages/11/16/4226e59bb72e096d9809ccedf349a1... aiobotocore \n", - "3 /packages/11/16/4226e59bb72e096d9809ccedf349a1... aiobotocore \n", - "4 /packages/54/b7/453119271cc4c36b07fdeab9b0ff25... aiobotocore \n", - "\n", - " file \\\n", - "0 {'filename': 'aiobreaker-1.2.0-py3-none-any.wh... \n", - "1 {'filename': 'aiobotocore-1.3.0.tar.gz', 'proj... \n", - "2 {'filename': 'aiobotocore-2.0.1.tar.gz', 'proj... \n", - "3 {'filename': 'aiobotocore-2.0.1.tar.gz', 'proj... \n", - "4 {'filename': 'aiobotocore-2.3.3.tar.gz', 'proj... \n", - "\n", - " details tls_protocol \\\n", - "0 {'installer': {'name': 'pip', 'version': '21.1... TLSv1.3 \n", - "1 {'installer': {'name': 'pip', 'version': '24.1... TLSv1.2 \n", - "2 {'installer': {'name': 'pip', 'version': '24.2... TLSv1.2 \n", - "3 {'installer': {'name': 'pip', 'version': '24.2... TLSv1.2 \n", - "4 {'installer': {'name': 'pip', 'version': '24.1... TLSv1.2 \n", - "\n", - " tls_cipher \n", - "0 TLS_AES_128_GCM_SHA256 \n", - "1 ECDHE-RSA-AES128-GCM-SHA256 \n", - "2 ECDHE-RSA-AES128-GCM-SHA256 \n", - "3 ECDHE-RSA-AES128-GCM-SHA256 \n", - "4 ECDHE-RSA-AES128-GCM-SHA256 " - ] - }, - "execution_count": 7, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# Warning: Ensure bpd.options.bigquery.ordering_mode = \"partial\" or else\n", - "# this query() will cause a full table scan because of the sequential index.\n", - "assert bpd.options.bigquery.ordering_mode == \"partial\"\n", - "pypi.peek()" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "Query job 3a421217-59e2-4722-8382-0930f0a3b9ee is DONE. 1.5 MB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "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", - "
SnapshotAtSystemNameVersionDependencyMinimumDepth
02024-08-29 04:39:16.121656+00:00PYPIzxkane-cdk-construct-simple-nat0.2.89{'System': 'PYPI', 'Name': 'attrs', 'Version':...2
12024-08-29 04:39:16.121656+00:00PYPIzxkane-cdk-construct-simple-nat0.2.82{'System': 'PYPI', 'Name': 'attrs', 'Version':...2
22024-08-29 04:39:16.121656+00:00PYPIzxkane-cdk-construct-simple-nat0.2.88{'System': 'PYPI', 'Name': 'attrs', 'Version':...2
32024-08-29 04:39:16.121656+00:00PYPIzxkane-cdk-construct-simple-nat0.2.91{'System': 'PYPI', 'Name': 'attrs', 'Version':...2
42024-08-29 04:39:16.121656+00:00PYPIzxkane-cdk-construct-simple-nat0.2.77{'System': 'PYPI', 'Name': 'attrs', 'Version':...2
\n", - "
" - ], - "text/plain": [ - " SnapshotAt System Name \\\n", - "0 2024-08-29 04:39:16.121656+00:00 PYPI zxkane-cdk-construct-simple-nat \n", - "1 2024-08-29 04:39:16.121656+00:00 PYPI zxkane-cdk-construct-simple-nat \n", - "2 2024-08-29 04:39:16.121656+00:00 PYPI zxkane-cdk-construct-simple-nat \n", - "3 2024-08-29 04:39:16.121656+00:00 PYPI zxkane-cdk-construct-simple-nat \n", - "4 2024-08-29 04:39:16.121656+00:00 PYPI zxkane-cdk-construct-simple-nat \n", - "\n", - " Version Dependency MinimumDepth \n", - "0 0.2.89 {'System': 'PYPI', 'Name': 'attrs', 'Version':... 2 \n", - "1 0.2.82 {'System': 'PYPI', 'Name': 'attrs', 'Version':... 2 \n", - "2 0.2.88 {'System': 'PYPI', 'Name': 'attrs', 'Version':... 2 \n", - "3 0.2.91 {'System': 'PYPI', 'Name': 'attrs', 'Version':... 2 \n", - "4 0.2.77 {'System': 'PYPI', 'Name': 'attrs', 'Version':... 2 " - ] - }, - "execution_count": 8, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "deps.peek()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Find dependencies for pandas\n", - "\n", - "Use assign to add columns to the DataFrame after a scalar operations, such as extracting a sub-field from a `STRUCT` column.\n", - "\n", - "Because the DataFrame has no index, this does not work if the new column belongs to a different table expression." - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "metadata": {}, - "outputs": [], - "source": [ - "deps = deps.assign(DependencyName=deps[\"Dependency\"].struct.field(\"Name\"))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Use an aggregation to identify the unique `DependencyName`s for the `pandas` package. Note: `drop_duplicates()` is not supported, as the order-based behavior such as `keep=\"first\"` is not applicable when using `ordering_mode = \"partial\"`.\n", - "\n", - "A DataFrame with no index still supports aggregation operations. Set `as_index=False` to keep the GROUP BY keys as regular columns, instead of turning them into an index." - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "Query job 5b69917f-9ed7-483a-9241-0083acea9990 is DONE. 1.1 GB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job ac94c55d-ce8e-4694-ad97-55c933cf3053 is DONE. 123 Bytes processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "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", - "
NameDependencyNamesize
0pandaspytz168
1pandasnumpy168
2pandaspython-dateutil168
3pandassix168
4pandastzdata56
\n", - "
" - ], - "text/plain": [ - " Name DependencyName size\n", - "0 pandas pytz 168\n", - "1 pandas numpy 168\n", - "2 pandas python-dateutil 168\n", - "3 pandas six 168\n", - "4 pandas tzdata 56" - ] - }, - "execution_count": 10, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "package_deps = deps[deps[\"Name\"] == package_name].groupby([\"Name\", \"DependencyName\"], as_index=False).size()\n", - "package_deps.peek()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Count downloads for pandas and its dependencies\n", - "\n", - "The previous step created `pandas_deps` with all the dependencies of `pandas` but not pandas itself.\n", - "\n", - "Combine two DataFrames with the same column names with the `bigframes.pandas.concat` function." - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "metadata": {}, - "outputs": [], - "source": [ - "pandas_and_deps = bpd.concat(\n", - " [\n", - " package_deps.drop(columns=[\"Name\", \"size\"]).rename(columns={\"DependencyName\": \"Name\"}),\n", - " bpd.DataFrame({\"Name\": [package_name]}),\n", - " ],\n", - "\n", - " # To join DataFrames that have a NULL index, set ignore_index = True.\n", - " ignore_index=True,\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Since there is no index to implicitly join on, use the `merge` method to join two DataFrames by column name." - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "metadata": {}, - "outputs": [], - "source": [ - "pandas_pypi = pandas_and_deps.merge(pypi, how=\"inner\", left_on=\"Name\", right_on=\"project\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Create a time series to visualize by grouping by the date, extracted from the `timestamp` column.\n", - "\n", - "**Note:** If you don't `peek()` at your data and only do grouped aggregations, BigQuery DataFrames can eliminate unnecessary ordering from the compilation even without `ordering_mode = \"partial\"`.\n", - "\n", - "When BigQuery DataFrames aggregates over columns, those columns provide a\n", - "unique key post-aggregation that is used for ordering. Any ordering applied before is overridden. By aggregating over\n", - "a time series, the line plots will render in the expected order." - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "Query job 57037a4c-5b8b-4f30-a5c6-bfeb9731a38f is DONE. 270.4 GB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job a8ea0b8e-2260-4175-b80d-668a2411c6ad is DONE. 2.0 kB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/plain": [ - "date project \n", - "2024-09-17 numpy 2572982\n", - " pandas 2195692\n", - " python-dateutil 3608119\n", - " pytz 1863133\n", - " six 3064640\n", - " tzdata 991989\n", - "2024-09-18 numpy 13282573\n", - " pandas 10856758\n", - " python-dateutil 17877058\n", - " pytz 9450103\n", - " six 15225000\n", - " tzdata 5230039\n", - "2024-09-19 numpy 13637868\n", - " pandas 11077817\n", - " python-dateutil 18449777\n", - " pytz 9690329\n", - " six 15706263\n", - " tzdata 5473910\n", - "2024-09-20 numpy 12609524\n", - " pandas 10758593\n", - " python-dateutil 17257536\n", - " pytz 9082050\n", - " six 14489456\n", - " tzdata 5206738\n", - "2024-09-21 numpy 8316481\n", - " pandas 7483241\n", - " python-dateutil 11604691\n", - " pytz 5494178\n", - " six 8814983\n", - " tzdata 3141578\n", - "2024-09-22 numpy 7768078\n", - " pandas 6566272\n", - " python-dateutil 10835755\n", - " pytz 5130018\n", - " six 8297507\n", - " tzdata 2811247\n", - "2024-09-23 numpy 12389164\n", - " pandas 10758931\n", - " python-dateutil 17153013\n", - " pytz 9045824\n", - " six 14512209\n", - " tzdata 5214048\n", - "2024-09-24 numpy 10385658\n", - " pandas 8830996\n", - " python-dateutil 14066307\n", - " pytz 7425446\n", - " six 11917222\n", - " tzdata 4550626\n", - "dtype: Int64" - ] - }, - "execution_count": 13, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "pandas_pypi = pandas_pypi.assign(date=pandas_pypi[\"timestamp\"].dt.date)\n", - "downloads_per_day = pandas_pypi.groupby([\"date\", \"project\"]).size()\n", - "\n", - "# Cache after the aggregation so that the aggregation only runs once.\n", - "downloads_per_day.cache()\n", - "downloads_per_day.to_pandas()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "BigQuery DataFrames has several built-in visualization methods. Alternatively, download the time series with the `to_pandas()` method for further analysis and visualization." - ] - }, - { - "cell_type": "code", - "execution_count": 14, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "Query job 319558aa-e092-4fd0-a8aa-447fca216a57 is DONE. 1.6 kB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job e64eb861-bd12-4748-9c29-d97990aa1241 is DONE. 1.2 kB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/plain": [ - "" - ] - }, - "execution_count": 14, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAkoAAAH0CAYAAADCCwIBAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjkuMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8hTgPZAAAACXBIWXMAAA9hAAAPYQGoP6dpAAEAAElEQVR4nOzdd3iTVfvA8e+T3XTvAR0USsveGwUEZSjuhcoWXCgKuMUX9+tgiag/RQFRRETcAspUNpQ9S0tLGd07abPz+yM00peWtpB0wPlcVy7a5Hmec1La5M4597mPZLfb7QiCIAiCIAgXkdV3BwRBEARBEBoqESgJgiAIgiBUQQRKgiAIgiAIVRCBkiAIgiAIQhVEoCQIgiAIglAFESgJgiAIgiBUQQRKgiAIgiAIVRCBkiAIgiAIQhVEoCQIgiAIglAFESgJgiAIgiBUQQRK1fj7778ZPnw4ERERSJLETz/9VKvzZ8yYgSRJF908PT3d02FBEARBEFxGBErV0Ov1dOjQgfnz51/W+dOmTSMjI6PCrXXr1txzzz0u7qkgCIIgCK4mAqVqDB06lDfffJM77rij0seNRiPTpk2jSZMmeHp60qNHDzZu3Oh83MvLi7CwMOctKyuLI0eOMH78+Dp6BoIgCIIgXC4RKF2hSZMmsW3bNpYtW8aBAwe45557GDJkCCdOnKj0+AULFtCyZUuuu+66Ou6pIAiCIAi1JQKlK5Cens7ChQv5/vvvue6662jevDnTpk2jb9++LFy48KLjDQYD33zzjRhNEgRBEIRGQlHfHWjMDh48iNVqpWXLlhXuNxqNBAYGXnT8jz/+SElJCaNHj66rLgqCIAiCcAVEoHQFdDodcrmcxMRE5HJ5hce8vLwuOn7BggXccssthIaG1lUXBUEQBEG4AiJQugKdOnXCarWSnZ1dbc5RamoqGzZs4Jdffqmj3gmCIAiCcKVEoFQNnU5HcnKy8/vU1FT27dtHQEAALVu25MEHH2TUqFHMnDmTTp06kZOTw7p162jfvj0333yz87wvv/yS8PBwhg4dWh9PQxAEQRCEyyDZ7XZ7fXeiIdu4cSMDBgy46P7Ro0ezaNEizGYzb775Jl999RVnz54lKCiInj178tprr9GuXTsAbDYb0dHRjBo1irfeequun4IgCIIgCJepXgOld955h5UrV3Ls2DE8PDzo3bs37777LvHx8Zc87/vvv2f69OmkpaURFxfHu+++y7Bhw5yP2+12/vOf//D5559TWFhInz59+OSTT4iLi3P3UxIEQRAE4SpSr+UBNm3axBNPPMH27dv566+/MJvN3HTTTej1+irP2bp1KyNGjGD8+PHs3buX22+/ndtvv51Dhw45j3nvvff48MMP+fTTT9mxYweenp4MHjwYg8FQF09LEARBEISrRIOaesvJySEkJIRNmzZx/fXXV3rMfffdh16v57fffnPe17NnTzp27Minn36K3W4nIiKCqVOnMm3aNACKiooIDQ1l0aJF3H///XXyXARBEARBaPwaVDJ3UVERAAEBAVUes23bNqZMmVLhvsGDBzs3q01NTSUzM5NBgwY5H/f19aVHjx5s27at0kDJaDRiNBqd39tsNvLz8wkMDESSpCt5SoIgCIIg1BG73U5JSQkRERHIZK6ZNGswgZLNZuPpp5+mT58+tG3btsrjMjMzL6pDFBoaSmZmpvPx8vuqOuZ/vfPOO7z22mtX0n1BEARBEBqI06dP07RpU5dcq8EESk888QSHDh1i8+bNdd72iy++WGGUqqioiKioKE6fPo2Pj0+d90cQBEEQhNorLi4mMjISb29vl12zQQRKkyZN4rfffuPvv/+uNgIMCwsjKyurwn1ZWVmEhYU5Hy+/Lzw8vMIxHTt2rPSaarUatVp90f0+Pj4iUBIEQRCERsaVaTP1uurNbrczadIkfvzxR9avX0+zZs2qPadXr16sW7euwn1//fUXvXr1AqBZs2aEhYVVOKa4uJgdO3Y4jxEEQRAEQaiJeh1ReuKJJ1i6dCk///wz3t7ezhwiX19fPDw8ABg1ahRNmjThnXfeAWDy5Mn069ePmTNncvPNN7Ns2TJ2797NZ599BjiiyKeffpo333yTuLg4mjVrxvTp04mIiOD222+vl+cpCIIgCELjVK+B0ieffAJA//79K9y/cOFCxowZA0B6enqFzPXevXuzdOlSXnnlFV566SXi4uL46aefKiSAP/fcc+j1eiZOnEhhYSF9+/Zl9erVaDQatz8nQRAEQRCuHg2qjlJDUVxcjK+vL0VFRSJHSWjwrFYrZrO5vrshXOXkcjkKhUKUTBEaNHe8fzeIZG5BEC6PTqfjzJkziM87Ql3QarWEh4ejUqnquyuCUGdEoCQIjZTVauXMmTNotVqCg4PFJ33Bbex2OyaTiZycHFJTU4mLi3NZMT9BaOhEoCQIjZTZbMZutxMcHOxc/CAI7uLh4YFSqeTUqVOYTCaR8ylcM8RHAkFo5MRIklBXxCiScC0Sv/WCIAiCIAhVEIGSIAiCIAhCFUSgJAjCNaV///48/fTT9d0NQRAaCZHMLQjCNWXlypUolUqXXa9///507NiROXPmuOyagiA0HCJQEgThqmAymWpU3ycgIKAOeiMIwtVCTL0JgtAg9e/fn0mTJjFp0iR8fX0JCgpi+vTpzuKaMTExvPHGG4waNQofHx8mTpwIwA8//ECbNm1Qq9XExMQwc+bMi6574dSb0Whk2rRpNGnSBE9PT3r06MHGjRsrnLNlyxb69++PVqvF39+fwYMHU1BQwJgxY9i0aRNz585FkiQkSSItLc2dPxZBEOqYCJQEQWiwFi9ejEKhYOfOncydO5dZs2axYMEC5+MffPABHTp0YO/evUyfPp3ExETuvfde7r//fg4ePMiMGTOYPn06ixYtqrKNSZMmsW3bNpYtW8aBAwe45557GDJkCCdOnABg3759DBw4kNatW7Nt2zY2b97M8OHDsVqtzJ07l169ejFhwgQyMjLIyMggMjLS3T8WQRDqkJh6EwShwYqMjGT27NlIkkR8fDwHDx5k9uzZTJgwAYAbbriBqVOnOo9/8MEHGThwINOnTwegZcuWHDlyhPfff9+50faF0tPTWbhwIenp6URERAAwbdo0Vq9ezcKFC3n77bd577336Nq1Kx9//LHzvDZt2ji/VqlUaLVawsLC3PEjEAShnokRJUEQGqyePXtWKKjZq1cvTpw4gdVqBaBr164Vjj969Ch9+vSpcF+fPn0qnHOhgwcPYrVaadmyJV5eXs7bpk2bSElJAf4dURIE4dokRpQEQWi0PD09r+h8nU6HXC4nMTERuVxe4TEvLy8AsT2MIFzjxIiSIAgN1o4dOyp8v337duLi4i4Kasq1atWKLVu2VLhvy5YttGzZstJzOnXqhNVqJTs7mxYtWlS4lU+ltW/fnnXr1lXZR5VKVelolSAIVwcRKAmC0GClp6czZcoUjh8/zrfffsu8efOYPHlylcdPnTqVdevW8cYbb5CUlMTixYv56KOPmDZtWqXHt2zZkgcffJBRo0axcuVKUlNT2blzJ++88w6///47AC+++CK7du3i8ccf58CBAxw7doxPPvmE3NxcwLH6bseOHaSlpZGbm4vNZnP9D0IQhHojAiVBEBqsUaNGUVZWRvfu3XniiSeYPHmyswxAZTp37szy5ctZtmwZbdu25dVXX+X111+vNJG73MKFCxk1ahRTp04lPj6e22+/nV27dhEVFQU4gqk///yT/fv30717d3r16sXPP/+MQuHIXJg2bRpyuZzWrVsTHBxMenq6S38GgiDUL8leXpREcCouLsbX15eioiJ8fHzquzuCUCmDwUBqairNmjVDo9HUd3dczl0Vr3v16sXAgQN58803XXrda8HV/jsnNH7ueP8WI0qCIFwTjEYju3fv5vDhwxWW9wuCIFyKCJQEQbgmrFq1ihtuuIFbb72Vu+++u767IwhCIyHKAwiCG5WaS0ktSiWlKIWUwhROFp4k35hPj7AeDG02lDj/uPruYoP1v9uIXKnbb7+d4uJil15TEISrnwiUBMEFdCYdJ4tOOoKh8/+mFKZwTn+u0uMP5Bzg84Of09y3OYObDWZozFBifGPqttOCIAhCtUSgJAi1UGQsqhAIlX+dVZpV5TkBmgCa+zUn1jeW5n7N8VB4sD59PZvPbialKIWP933Mx/s+plVAKwbHDGZIsyE08WpSh89KEARBqIoIlAShEvmGfOdUWUrRv//mluVWeU6IRwixfrEVgqJY31j8Nf4XHXt7i9spNhWzPn09q9NWs/3cdo7mH+Vo/lHm7JlD+6D2DGk2hJuibyLUM9SdT1UQBEG4BFEeoBKiPMC1wW63k2fIu2h0KKUwhQJjQZXnhXmG0dy3uSMo8m3uCIj8YvFRXf7vSoGhgLXpa1mduppdmbuw4/izlJDoHNqZoTFDGRQ9iECPQOc5Yqm2UNfE75zQ0Lnj/VsESpUQgdLVxW63k1Wa5RwVujAoKjZVndzbxKsJzf2aVwiKmvk2w0vl5db+5pTm8OepP1mTtoa92Xud98skGT3CejCk2RAGRg1EbVeLNy2hTolASWjoRKBUR0Sg1DjZ7DYy9ZnOUaELp8z0Zn2l58gkGZHekRWmypr7NSfGJwatUlvHz+BiGboM1qStYXXaag7nHXber5ApGNZ0GHcG3klCiwQ8tVe2Oawg1IQIlISGzh3v3yJHSWh0rDYr53TnLhodOll0kjJLWaXnyCU5UT5RF02ZxfjGoJar6/gZ1Fy4Vzhj2o5hTNsxpBensyZtDavSVnGi4AS7Mndxg9cNpBal4mvxxUftg7fKG5kkyqMJgiC4igiUhAbLYrNwuuT0RVNmqUWpGK3GSs9RyBTE+MRcNGUW7RONUq6s42fgWlE+UUxoP4EJ7SeQUpjCxtSNKGwK7NgpNhVTbCpGJsnwVnnjo/LBS+UlgiZBEIQrJAIlod6ZrWbSS9IrTJmlFKZwqvgUZpu50nNUMhXNfJs5AqILgqKm3k1Ryhp3QFQTzf2a06RVE1JTUwn3DscoGSkyFWG2mikyFlFkLEImyfBR+eCj9sFT6SmCJkEQhMsgAiWhTmWXZpOYlVhhyiy9OB2L3VLp8R4KD0dA9D9TZk28miCXyeu49w2TRqHBT+NHsEcw+WV6SkzFFBuLsdgslBrzySzJRy6Tnx9p8kWr9ECSJJf2wUMpr/E1+/fvT/v27dFoNCxYsACVSsWjjz7KjBkzSEtLo1mzZuzdu5eOHTsCUFhYiL+/Pxs2bKB///5s3LiRAQMGsHr1al544QWOHTtGr169WLZsGYmJiUyZMoWzZ89yyy23sGDBArRarbPdtm3bArBkyRKUSiWPPfYYr7/+OpIk8frrr7N8+XIOHTpUob8dO3Zk+PDhvPHGG677gQmC0GiIQEmoM2WWMu785U6KjEUXPeap9KwQDJXXIwr3DBcjITVksNjo+sbf9dL2kdcHo1XV/OVk8eLFTJkyhR07drBt2zbGjBlDnz59iIur+ZYuM2bM4KOPPkKr1XLvvfdy7733olarWbp0KTqdjjvuuIN58+bx/PPPV2h3/Pjx7Ny5k927dzNx4kSioqKYMGEC48aN47XXXmPXrl1069YNgL1793LgwAFWrlxZ8x+GIAhXFREoCXXmQM4BioxFeCm9GBwzmFjfWFr4tSDWL5ZQbajLRzmEhqt9+/b85z//ASAuLo6PPvqIdevW1SpQevPNN+nTpw8A48eP58UXXyQlJYXY2FgA7r77bjZs2FAhUIqMjGT27NlIkkR8fDwHDx5k9uzZTJgwgaZNmzJ48GAWLlzoDJQWLlxIv379nNcUBOHaIwIloc7sztoNwPVNr2dG7xn125mrkIdSzpHXB1/yGJvdht5cSrGxCJ1Jh81ucz6mlKvwUXnjo/ZFLVfVKnD1UNZuGrR9+/YVvg8PDyc7O/uyrxEaGopWq60Q0ISGhrJz584K5/Ts2bPC8+rVqxczZ87EarUil8udI0uzZs1CJpOxdOlSZs+eXat+CYJwdRGBklBnErMSAega1rWee3J1kiSpRtNfXmoVoV5+2Ow2dCYdRaYiSkwl2O0W9NYC9KUFqOVqfNQ++Kp8UStcXz5BqayYcC9JEjabDZnMMc16YXk3s7nyhP4LryFJUpXXrI3hw4ejVqv58ccfUalUmM1m7r777lpdQxCEq0u9Jn/8/fffDB8+nIiICCRJ4qeffrrk8WPGjEGSpItubdq0cR4zY8aMix5PSEhw8zMRqmOymjiQcwCALqFd6rk3AjiKbfqofYj0jiTeP56m3k3xVnkjSRJGq5Gc0hySC5NJKUwhpzQHk9Xk9j4FBwcDkJGR4bxv3759Lrv+jh07Kny/fft24uLikMsdI2IKhYLRo0ezcOFCFi5cyP3334+Hh4fL2hcEofGp1xElvV5Phw4dGDduHHfeeWe1x8+dO5f//ve/zu8tFgsdOnTgnnvuqXBcmzZtWLt2rfN7hUIMnNW3Q7mHMFqNBGgCaObTrL67I/wPuUyOr9oXX7UvVpuVElMJRaYi9CY9BosBg8VAdmk2HgoPfNW++Kh83FKXysPDg549e/Lf//6XZs2akZ2dzSuvvOKy66enpzNlyhQeeeQR9uzZw7x585g5c2aFYx5++GFatWoFwJYtW1zWtiAIjVO9RhBDhw5l6NChNT7e19cXX19f5/c//fQTBQUFjB07tsJxCoWCsLAwl/VTuHLl+UldQruIpO0GTi6T46fxw0/jh8VmcRSzNBajN+sps5RRZikjU5+JVqnFV+WoCK6Que6l5Msvv2T8+PF06dKF+Ph43nvvPW666SaXXHvUqFGUlZXRvXt35HI5kydPZuLEiRWOiYuLo3fv3uTn59OjRw+XtCsIQuPVqIdavvjiCwYNGkR0dHSF+0+cOEFERAQajYZevXrxzjvvEBUVVeV1jEYjRuO/lZ6Li6veKFW4PM78pFCRn9SYKGQKAjQBBGgCMNvMFBsdFcBLzaXOW4Y+A0+lJ75qX7xV3tUGTRs3brzovgun3Vu1asXWrVsrPH5hzlL//v353y0qx4wZw5gxYyrcN2PGDGbMmFHhPqVSyZw5c/jkk0+q7J/dbufcuXM8/vjjl3wegiBcGxptoHTu3DlWrVrF0qVLK9zfo0cPFi1aRHx8PBkZGbz22mtcd911HDp0CG9v70qv9c477/Daa6/VRbevSWabmb3ZewGRyN2YKWVKAj0CCfQIdFQANxVRbCymzFKG3qxHb9YjSZIjaFI5gqbGVhQ0JyeHZcuWkZmZedFItSAI16ZGGygtXrwYPz8/br/99gr3XziV1759e3r06EF0dDTLly9n/PjxlV7rxRdfZMqUKc7vi4uLiYyMdEu/r0XH8o5RZinDV+1LC78W9d0dwQWUciVBHkEEeQRhspoc26aYijBajOhMOnQmHZIk4a30blSb9YaEhBAUFMRnn32Gv79/fXdHEIQGoFEGSna7nS+//JKRI0eiUqkueayfnx8tW7YkOTm5ymPUajVqdcPdQb6xK89P6hzSuVG8WQq1o5KrCNYGE6wNxmhx7DlXZCzCZDU1qM16K5vy+1//O6UnCILQKN+1Nm3aRHJycpUjRBfS6XSkpKQQHh5eBz0TKnNhIrdwdVMr1IRoQ2jh14Lmfs0J8ghCKVdis9soMhZxuuQ0x/OPk1eWV99dFQRBqJF6HVHS6XQVRnpSU1PZt28fAQEBREVF8eKLL3L27Fm++uqrCud98cUX9OjRw7nB5YWmTZvG8OHDiY6O5ty5c/znP/9BLpczYsQItz8f4WJWm5W9WSI/6VojSRIahQaNQkOINoQySxnFpmKKjEVYbBYy9Zl4KDzQKrX13VVBEIRLqtdAaffu3QwYMMD5fXme0OjRo1m0aBEZGRmkp6dXOKeoqIgffviBuXPnVnrNM2fOMGLECPLy8ggODqZv375s377dWchOqFtJBUmUmEvwVHoS7x9f390R6oEkSWiVWrRKLaHaUM7qzlJkLCJDn0Gsb6woFyFcU+x2Ox/s/oCs0ixe7P4igR6B9d0loRr1GihVtsz3QosWLbroPl9fX0pLS6s8Z9myZa7omuAi5WUBOoV0cmmtHaFxkiSJMM8wSkwlGCwG8g354o1CuKZsOrOJr444ZkmO5B3h00GfEuVTdfkaof41yhwlofEQ+UnC/1LIFIR6hgKQXZqN2Vr5Xm6CcLWx2CzMSpwFOP4OTpec5qE/HnJu7yQ0TCJQEtzGbreLQpNCpfzV/ngoPbDZbWSWZtZ3dwShTqw8sZLUolT81f78eOuPtA5sTYGxgPFrxrMhfUN9d0+oggiUBLdJKUyh0FiIRq6hTWCb6k8QrhmSJBHhGQFAsbGYElOJ29ucMWMGHTt2dHs7glAZvVnP/H3zAXikwyPE+MawcPBCrmtyHQargac3Ps3y48vruZdCZUSgJLhN+WhSh5AObtlAVWjcNAqNMz8pQ5+BzW6r5x4JgvssPLSQfEM+0T7R3NvyXgC0Si0f3vAhd8bdic1u443tb/Dhng9FPa8GRgRKgtuI/CShOsEewShkCsxWMzmlOfXdHUFwiyx9FosPLwbg6c5PV/jgqJApmNFrBo93cOwt+PnBz3llyysid68BEYGS4BYiP6ke2O1g0tfPrRafgPv378+kSZOYNGkSAf4B9G7Zm3nvzCO3LBejxciSJUvo2rUr3t7ehIWF8cADD5Cdne08f+PGjUiSxLp16+jatStarZbevXtz/PjxCu3897//JTQ0FG9vb8aPH4/BYKjw+K5du7jxxhsJCgrC19eXfv36sWfPngt+nHZmzJhBVFQUarWaiIgInnrqqcv8zxGuZfP3zcdgNdAppBMDowZe9LgkSTzW8TFe7/06cknOLym/8MS6J9CZdPXQW+F/ifXaglukl6STU5aDUqakXVC7+u7OtcFcCm9H1E/bL50DlWeND1+8eDHjx49n586d7Nq1i4mPTCS8aTijxo3CZDLxxhtvEB8fT3Z2NlOmTGHMmDH88ccfFa7x8ssvM3PmTIKDg3n00UcZN24cW7ZsAWD58uXMmDGD+fPn07dvX5YsWcKHH35IbGys8/ySkhJGjx7NvHnzsNvtzJw5k2HDhnHixAm8vb354YcfmD17NsuWLaNNmzZkZmayf/9+1/y8hGtGUkESPyX/BMCULlMuWTfsjrg7CPIIYuqmqWzL2MbYNWOZP3A+IdqQOuqtUBkRKAluUT6a1C6oHRqFpp57IzQ0kZGRzJ49G0mSiI+PZ/+B/Xz16VfcPfJu7nrwLvw0fgDExsby4Ycf0q1bN3Q6HV5eXs5rvPXWW/Tr1w+AF154gZtvvhmDwYBGo2HOnDmMHz/euc3Rm2++ydq1ayuMKt1www0V+vTZZ5/h5+fHpk2buOWWW0hPTycsLIxBgwahVCqJioqie/fubv7JCFebWYmzsGPnpuib6BjSsdrjr2t6HQsHL+TxdY9zLP8YD/3xEJ8O+pRYv9hqzxXcQwRKglvsznTkJ4ltS+qQUusY2amvtmuhZ8+eFT5Z9+3Tlzmz52C1Wlm3dR0LZy3kwIEDFBQUYLM5krzT09Np3bq185z27ds7vy7fyzE7O5uoqCiOHj3Ko48+WqHNXr16sWHDv0uws7KyeOWVV9i4cSPZ2dlYrVZKS0uduwHcc889zJkzh9jYWIYMGcKwYcMYPnw4CoV42RRqZuu5rWw5uwWFTMHTnZ+u8Xltgtrw9bCveWztY5wqPsXIVSOZd8M8Ood2dl9nhSqJHCXBLUQidz2QJMf0V33cXLUNiRkevudhVJ4qvvnmG3bt2sWPP/4IgMlkqnCoUvlvQmx50FUeVNXE6NGj2bdvH3PnzmXr1q3s27ePwMBAZzuRkZEcP36cjz/+GA8PDx5//HGuv/56zGaRZCtUz2qzMmu3o7jk/fH3E+kTWavzI70jWTJ0CR2CO1BsKmbCnxP469Rf7uiqUA0RKAkud053jgx9BgpJQcfgjvXdHaEB2rFjR4Xvt2/fTlxcHMVniynML+Txlx6nS88uJCQkVEjkrqlWrVpV2saFtmzZwlNPPcWwYcNo06YNarWa3NzcCsd4eHgwfPhwPvzwQzZu3Mi2bds4ePBgrfsjXHt+O/kbxwuO46305pH2j1zWNfw1/nx+0+fcEHkDJpuJqRun8vWRr13cU6E6YgxZcLny0aTWga3F7vBCpdLT05kyZQqPPPIIe/bsYd68ecycOZP42HhUKhVLFyxFNl5G8ali3njjjVpff/LkyYwZM4auXbvSp08fvvnmGw4fPlwhmTsuLs65wq64uJhnn30WDw8P5+OLFi3CarXSo0cPtFotX3/9NR4eHkRHR7vkZyBcvcosZczbOw+ACe0nOHPuLoeHwoNZ/Wfxzs53+O74d7y7610y9ZlM6ToFmSTGOuqC+CkLLleeyN0lTEy7CZUbNWoUZWVldO/enSeeeILJkyczceJEgoOD+eLLL/jzlz8Z2nMob7/zNh988EGtr3/fffcxffp0nnvuObp06cKpU6d47LHHKhzzxRdfUFBQQOfOnRk5ciRPPfUUISH/ri7y8/Pj888/p0+fPrRv3561a9fy66+/EhgoNvEVLu3rI1+TVZpFhGcED7R64IqvJ5fJebnHy848p8VHFvP8389jspoufaLgEpJdlAC9SHFxMb6+vhQVFeHj41Pf3Wl0bl55M+kl6cwfOJ/rm15f3925ahkMBlJTU2nWrBkaTeNZWdi/f386duzInDlzqjymwFDAOd05ZJKMFn4tRGX3BqKx/s7VpbyyPG7+8Wb0Zj3vXPcOt8Te4tLr/5ryK69ufRWLzULX0K7MGTAHX7WvS9tozNzx/i1GlASXyi7NJr0kHQmJTiGd6rs7QiPlp/ZDq9Ris9vI0GfUd3cEocY+3f8perOeVgGtGNZsmMuvP7z5cD4Z9AmeSk92Z+1mzOoxZOrFxtLuJAIlwaXKp90SAhLwVnnXc2+ExkqSJMI9w5GQKDGV1MmmuYJwpVKLUlmRtAKAaV2nuS2HqGd4TxYPWUyIRwjJhck8+PuDHM8/Xv2JwmURgZLgUs78JFEWQKjCxo0bLzntVk6j0BDgEQBAhi4Dq83q5p4JwpWZkzgHi91Cv6b96B7u3uKk8QHxfD3sa5r7Nie7LJsxq8ewI2NH9ScKtSYCJcGlnIUmxf5uggsEewSjlCkx28zkluVWf4Ig1JPErETWn16PTJLxTJdn6qTNcK9wFg9dTNfQrujMOh5d+yi/n/y9Ttq+lohASXCZfEM+KUUpAKKCrOAScpmccC9H1e28sjwMFkM1ZwhC3bPb7c7iknfG3Ulzv+Z11rav2pf/u/H/GBwzGIvNwgv/vMAXB79ArNNyHREoCS6zJ8ux83oLvxb4a/zruTfC1cJb5Y23yhs7djL0GeINQGhw1pxaw4HcA3goPHii4xN13r5KruK9699jVOtRAMzZM4e3d7wtpqtdRARKgsuI/CTBXcI8w5BJMkrNpRQaC+u7O4LgZLKamJM4B4CxbccS5BFUL/2QSTKe7fYsz3V7DgmJZceXMXXTVDEK6wIiUBJcprwit8hPElxNJVcRrA0GIKs0C4vNUs89EgSHZceWcVZ3lmCPYEa3Hl3f3WFk65G83+99VDIV69LX8fCfD1NoKKzvbjVqIlASXKLYVOxcnto1TARKgusFaAJQK9RYbVaySrMqPJaWloYkSezbt69+Ovc/Nm7ciCRJFBYW1ndXqjRjxgw6duzo/H7MmDHcfvvt9dafxqjIWMT/Hfg/ACZ1mtRgtmwaHDOYz276DG+VN/tz9jNy1UjOlJyp7241WiJQElxib9Ze7NiJ8Ympt6Fn4eomk2REeEbw8qSXGXXvKPRmfX13yaX69+/P008/7ZZrS5LETz/9VOG+adOmsW7dOre0d61YcHABxaZiWvi14Lbmt9V3dyroEtqFJUOXEO4ZTlpxGg/98RCH8w7Xd7caJREoCS5RPu0m8pMuzWq2UVYi9me6XFqlFpVcBUCGPgOb3VbPPWq8vLy8xL51V+BMyRm+OfoNAFO6TEEuk9dzjy7W3K85Xw/7mnj/ePIMeYxdPZZ/zvxT391qdESgJLiESOT+l8lgIed0CSl7stmz5hQbvj7GT7P3svilLXz61Ea+fHYz3721kz1rTlGSf+0lWvbv359JkyYxadIkfH19CQoKYvr06djtdl5//XXatm170TkdO3Zk+vTpzJgxg++Xfs/6VeuJ849DLpOzceNG53EnT55kwIABaLVaOnTowLZt2ypc54cffqBNmzao1WpiYmKYOXNmhcdjYmJ4++23GTduHN7e3kRFRfHZZ59V+5z++OMPWrZsiYeHBwMGDCAtLa3C43l5eYwYMYImTZqg1Wpp164d3377rfPxMWPGsGnTJubOnYskSUiS5LzGoUOHGDp0KF5eXoSGhjJy5Ehyc/+tKRUTE3NRAc+OHTsyY8YM5+MAd9xxB5IkOb//36k3oXY+3PshZpuZHuE96Nukb313p0oh2hAWDVlEz/CelFnKeHL9k/x44sf67lajoqjvDgiNn96s50jeEeDaSOS22+0YdGaKcsouuJVSfP7rshJztdfIPa0j97SObT+mEN7Cl7iuobToEoKHt+qK+lVmKbvs86+Eh8IDSZJqfPzixYsZP348O3fuZPfu3UycOJGoqCjGjRvHa6+9xq5du+jWrRsAe/fu5cCBA6xcuZKQkBCOHj1KXmEe02dNBwm6NOtCbpYjcHj55Zf54IMPiIuL4+WXX2bEiBEkJyejUChITEzk3nvvZcaMGdx3331s3bqVxx9/nMDAQMaMGePs28yZM3njjTd46aWXWLFiBY899hj9+vUjPj6+0udy+vRp7rzzTp544gkmTpzI7t27mTp1aoVjDAYDXbp04fnnn8fHx4fff/+dkSNH0rx5c7p3787cuXNJSkqibdu2vP766wAEBwdTWFjIDTfcwMMPP8zs2bMpKyvj+eef595772X9+vU1+lnv2rWLkJAQFi5cyJAhQ5DLG97IR2NzOPcwq1JXISExreu0Wv3u1wcvlRcfD/yY/2z9D7+edGyqm6nP5NEOjzb4vjcEIlASrtj+7P1Y7VaaeDVxFgds7Ow2O7pCI0U5ZecDoNIKgZHZcOn6JBovJb7BHvgGe+Bz/l/fYC2+wR7IZBIpe7NJ2pnFueRCMpKLyEgu4p/lJ4hs5U9ct1BiOwSj8qjdn2eZpYweS3tcydO+bDse2FGrRNbIyEhmz56NJEnEx8dz8OBBZs+ezYQJExg8eDALFy50BkoLFy6kX79+xMbGAuDh4YGX0YuoplGUmkvJN+c7aytNmzaNm2++GYDXXnuNNm3akJycTEJCArNmzWLgwIFMnz4dgJYtW3LkyBHef//9CoHSsGHDePzxxwF4/vnnmT17Nhs2bKgyUPrkk09o3ry5c3Sq/Pm8++67zmOaNGnCtGnTnN8/+eSTrFmzhuXLl9O9e3d8fX1RqVRotVrCwsKcx3300Ud06tSJt99+23nfl19+SWRkJElJSbRs2bLan3VwsGO1oJ+fX4VrC5fHbrfzwe4PAMcGtQkBCfXco5pRypW81fctQj1DWXBwAR/v/5is0ixe6fkKCpkIBS5F/HSEK9ZY85OsFhsleQYKs0spzv03CCrOKaM414DVcun8Fy9/daWBkE+wB+pqgpw21zWhzXVN0BUYOLE7mxO7sshJLyH9cD7ph/PZqDxOTLtAWnYLI6ptAArl1TUK0LNnzwqfZHv16sXMmTOxWq1MmDCBcePGMWvWLGQyGUuXLmX27NkXXSPCM4KUohRKTCXYTI7/q/bt2zsfDw93BO3Z2dkkJCRw9OhRbrutYsJtnz59mDNnDlar1TnScuE1JEkiLCyM7OxsAIYOHco//zhyPKKjozl8+DBHjx6lR4+KAWqvXr0qfG+1Wnn77bdZvnw5Z8+exWQyYTQa0WovHVzu37+fDRs24OXlddFjKSkpNQqUBNfaeHoju7N2o5arebLTk/XdnVqRJInJnScTpg3j7Z1v88OJH8guzeaDfh80mBV7DZEIlIQrVp6f1BCn3UwGy79BUHYZRbnnR4iyy9AVGLhUkWeZXMI7UOMMgC4cIfIJ0rgkePHy19Dpxig63RhFYVYpSbuyOLEri8KsUlL25JCyJweVRk5sp2DiuoXSNN4fmbzy1EIPhQc7HqifTTE9FB4uu9bw4cNRq9X8+OOPqFQqzGYzd99990XHqRVqAjWB5Jblkl3mCGSUSqXz8fJAzGarXcL3hdcov075NRYsWEBZWVmlx13K+++/z9y5c5kzZw7t2rXD09OTp59+GpPp0on9Op2O4cOHVxidKlceCMpksouqlZvN1U//CrVnsVmYvccRtD/U6iHCPBvnCN19CfcRog3hub+f45+z/zBuzTg+GviRWLFcBREoCVfEYDFwMPcgUD8jSna7HYPe7AyE/jcoKiu+9BuRQiWrEAj5XBAQeQVokMnqbv7eL1RL91ua0e3mGHJP60jalUXy7ix0BUaObcvk2LZMPLyVtOgSSsvuofiGV8xnkiSp0Xwq3LGjYkC3fft24uLinKM6o0ePZuHChahUKu6//348PP4NxFQqFVarY+ozWBtMkamoRls1tGrVii1btlS4b8uWLbRs2bLGeTtNmjSp9Lq//PLLRc/nf9u57bbbeOihhwBH8JaUlETr1q0rfV7lOnfuzA8//EBMTAwKReUv18HBwWRkZDi/Ly4uJjU1tcIxSqXyomsLtbfyxEpSi1LxV/szvt34+u7OFRkQNYAFgxfw5LonOZx3mJF/jOSTQZ8Q4xtT311rcESgJFyRg7kHMdvMhHiEEOkd6ZY27DY7+iKjM/ipEBRll2KqLl/IU1khALowKNL6qBpcMqMkSQRHeRMc5U3vO5qTkVJI0q5sUhKzKSsxc3DjGQ5uPENgtAcJN3liMVlBU9+9rp309HSmTJnCI488wp49e5g3b16FFWgPP/wwrVq1ArgouImJiWHNmjUcP36cwMBAgjyCSCMNAKPFWGWbU6dOpVu3brzxxhvcd999bNu2jY8++oiPP/74ip7Lo48+ysyZM3n22Wd5+OGHSUxMZNGiRRWOiYuLY8WKFWzduhV/f39mzZpFVlZWhUApJiaGHTt2kJaWhpeXFwEBATzxxBN8/vnnjBgxgueee46AgACSk5NZtmwZCxYsQC6Xc8MNN7Bo0SKGDx+On58fr7766kWBX0xMDOvWraNPnz6o1Wr8/cVejLWlN+uZv28+AI92eBRvlXc99+jKdQjuwJJhS3j0r0c5ozvDyFUj+WjgR3QI7lDfXWtQRKAkXJHdmf/mJ11JwGG12ijJNTgCoezz02PnA6Ga5gv5BJ0PgkI8/v062AO1tuZTJA2NJJOIiPMnIs6f6+6L48zRApJ2ZZK6Lxd9oRFTmYainDKMOjsaTyVqrRKFsuFX/Rg1ahRlZWV0794duVzO5MmTmThxovPxuLg4evfuTX5+/kX5PxMmTGDjxo107doVnU7nyOEJcuTw5JTlYLfbK/1d7Ny5M8uXL+fVV1/ljTfeIDw8nNdff71CIvfliIqK4ocffuCZZ55h3rx5dO/e3VlioNwrr7zCyZMnGTx4MFqtlokTJ3L77bdTVFTkPGbatGmMHj2a1q1bU1ZWRmpqKjExMWzZsoXnn3+em266CaPRSHR0NEOGDEEmc/w/v/jii6SmpnLLLbfg6+vLG2+8cdGI0syZM5kyZQqff/45TZo0uah8gVC9Lw99Sb4hn2ifaO6Jv6e+u+My0T7RLBm2hEnrJnE47zAPr3mY965/jwFRA+q7aw2GZBdbcV+kuLgYX19fioqK8PHxqe/uNGgPr3mYHZk7mN5zOvfG31vt8foiI1kni51L6otyHCNDJXnV5AvJzucLhXjgG3R+iixE6/g6SINCdXUlO1fHbLKSciCDUlseoUFNUMr/nYZTqOTngyYFckXDC5r69+9Px44dL6r9cyG73U5cXByPP/44U6ZMqfaaZquZ5MJkbHYb4V7hBGgCXNhjoZzBYCA1NZVmzZqh0TSyYcwrkKXP4pYfb8FgNTC7/2wGRQ+q7y65XKm5lGmbpvHP2X+QSTJe6v4S9yXcV9/dqjV3vH+LESXhspmtZvbn7Adqlp9kNlpZ9vpODPrKE00d+ULnR4NCzucNBTlGiLz81VUmMV+LlCo5MW2DSE0twT9Ui2RXYNRbMBksWExWdCYrugJQaRSoPRWoPRSN5ueXk5PDsmXLyMzMZOzYsTU6RylXEqINIVOfSZY+Cx+Vj1jyLLjM/H3zMVgNdArpxMCogfXdHbfQKrV8eMOHvLn9TX448QNv7niTzNJMnur0VINLT6hr9fpK8vfff/P++++TmJhIRkYGP/744yU3Zdy4cSMDBlw8HJiRkVGhPsj8+fN5//33yczMpEOHDs7hcMG1DucdxmA1EKAJINY3ttrjzx4vwKA3o9LIiW4XdEFQ5Jgia4j5Qo2BTC5Do1Hh4aXCarFhLLVg0JuxmKyYDI7gqUQCtUaB2lOJykNRp0nqtRUSEkJQUBCfffZZrXJpAjQBFBoLMVgMZOmzaOJ9ceK1INTW8fzj/JT8EwBTu069ql+jFDIF/+n1H0I9Q/l438csOLiALH0Wr/V+DaW88aYwXKl6DZT0ej0dOnRg3Lhx3HnnnTU+7/jx4xWG1EJCQpxff/fdd0yZMoVPP/2UHj16MGfOHAYPHszx48crHCdcuQvrJ9XkxePU4TwAWnYPo98DlRfvE66MXCFD66NC66PCYrZhLDVj0Juxmm0YyywYyyxIkoRaq0CtVaDyUNT5C/+FW45U5nKzASRJItwznNSiVAqNhfip/fBUeV7WtQSh3OzE2dixc1P0TddEkrMkSTzW4THCtGG8tu01fj35KzllOczuPxsv1cX1vK4F9ToWP3ToUN58803uuOOOWp0XEhJCWFiY81ae1Agwa9YsJkyYwNixY2ndujWffvopWq2WL7/80tXdv+ZdmMhdHbvdzqlDjkApqq3YiLMuKJQyPH3VBEZ4ERDuidZHhUwhq1BSIfeMjpI8AyaD5bIDlIZEq9Tir3GMQp3TnxOb5gpXZOu5rWw5twWFTMHTnZ+u7+7UqTvi7uCjgR/hofBge8Z2Rq8eTXZpdn13q140jqSF/9GxY0fCw8O58cYbKywdNplMJCYmMmjQv4l2MpmMQYMGXbQ55oWMRiPFxcUVbsKlWWwW9mbvBWpWaLIwq5SSPAMyhUTTeLE0ua4pVHK8/DUERnjiH6bFw1uFTCZht9kp05kozCol76weXYEBs9HaqIOmUG0ocpkck9VEXllefXdHaKSsNiszdztKVtwffz+RPu4pf9KQ9W3Sl4VDFhKoCSSpIIkH/3iQlMKU+u5WnWtUgVJ4eDiffvopP/zwAz/88AORkZH079+fPXv2AJCbm4vVaiU0NLTCeaGhoWRmZlZ53XfeeQdfX1/nLTLy2vuDqK1j+ccotZTirfKmhV+Lao8vH01qEueHUn1trVBrSCRJQqlW4B2gIbCpF34hWjSeSkf1aauN0mITBZl68s/p0RcasZgbX5FCuUzurJicU5aDyXrpoqOCUJlfT/5KUkES3kpvHmn/SH13p960CWzD18O+JsYnhkx9JiNXjXTOJlwrGlWgFB8fzyOPPEKXLl3o3bs3X375Jb179650H6jaePHFFykqKnLeTp8+7aIeX73Kty3pEtIFuaz6wMc57dZGTLs1FJIkofJQ4BPkQVBTr39rTkkSVosNfZGR/HN68jP0lBYbq61l1ZD4qnzxVHpit9vJ0Gc06hEyoe6VWcqYt3ceABPaT8BP41e/HapnTb2b8tXQr+gQ3IESUwkT/5rImrQ19d2tOtOoAqXKdO/eneTkZACCgoKQy+VkZWVVOCYrK+uSu2ar1Wp8fHwq3IRLq01+kslg4VxyIQDRIj+pQZJkEmqtEt9gR9DkE6hBpXGs9bCYrOgKjOSd1VGQqaesxITN2rCDpvLEbkmS0Jl0FJvEdLpQc18f+Zrs0mwiPCN4oNUD9d2dBsFf48+CmxYwMGogZpuZZzc9y5IjS+q7W3Wi0QdK+/btc24OqVKp6NKlC+vWrXM+brPZWLdu3UW7eQuXz2a3kZh9fiPcsOrzk84mFWKz2PEJ0uAX2jj2IruWyWQSGi8VfqFagpp64R2gcU6Xmo1WSvIN5J7RUZhdikFnxmZrmKM1aoXauclnpj6zRvvBCUJeWR5fHPoCgKc6P4Varq7nHjUcGoWGmf1mMiJhBHbsvLfrPd7b9d5Vv2iiXssD6HQ652gQQGpqKvv27SMgIICoqChefPFFzp49y1dffQXAnDlzaNasGW3atMFgMLBgwQLWr1/Pn3/+6bzGlClTGD16NF27dqV79+7MmTMHvV5f48J1QvVOFJygxFSCVqElISCh2uMvnHa7mmuQXI1kchke3io8vB01mgx6M8ZSR1FLU5kFU5kFKd8xhafxVKDSKJAaUI2mII8gioxFmKwmssuyCfcMr+8uCQ3cJ/s/QW/W0zqwNUObDa3v7jQ4cpmcF7u/SJhnGLMTZ7PkyBKyS7N5q+9bV21QWa+B0u7duysUkCzfqmD06NEsWrSIjIwM0tPTnY+bTCamTp3K2bNn0Wq1tG/fnrVr11a4xn333UdOTg6vvvoqmZmZdOzYkdWrV1+U4C1cvvL6SZ1COlVb/dhut5N+PlAS026Nm1zhKDfg6avGYrZi0Fsw6s3ni1yaMZaaz0/hKdBolSg1crcExmPGjKGwsJCffvqp2mNlkoxwz3BOFZ8ivywfP7UfHgoPl/dJuDqkFqWyImkFANO6TkMmNfpJF7eQJIlxbccRqg3llS2vsCZtDbllucwdMBdftW99d8/l6jVQ6t+//yWTLP93B+7nnnuO5557rtrrTpo0iUmTJl1p94QqOBO5a5CfVJBRSkm+AblCRhNRFuCqoVDK8fKT4+mrwmIqH2kyY7PaMejMGHRmZHJH3pPGU4FC5Z6gqSa8VF74qn0pMhaRocugmW8zMbIpVGpO4hysdiv9mvajW1i3+u5Og3dz7M0EeQTx9IanScxKZPSq0Xwy6BPCva6ukVsRLgu1YrfbnYFSTfKTyqtxR7T0Q3mNbVx7LXCUG5A7yg008cIvVIvGS4kkk7BZ7ZSVmCjILCX/nB5doQGLyUr//v2dH2Z8fX0JCgpi+vTp2O12Xn/9ddq2bXtROx07dmT69OnMmDGDxYsX8/PPPyNJEpIksXHjRmbMmOH8/sJb+YetUG0oMklGmaWMAkNBHf+UhMYgMSuR9afXI5fkTOlS/UbMV8Jus181KzF7hPdg0ZBFhGhDSClK4aE/HuJ4/vH67pZLiV0jhVpJLUol35CPWq6mTWCbao9PPx8oRYuyAG5nt9uxl5XVS9uSh4ej3IDGkadkD7BjKrNg0DvymKwWG6VFJkqLTFhMVhYvXsz48ePZuXMnu3fvZuLEiURFRTFu3Dhee+01du3aRbdujk/0e/fu5cCBA6xcuZKQkBCOHj1KcXExCxcuBCAgIICuXbvy6KOPOvvzzTff8Oqrr9K1qyOYr7BpbmkW3mpvlLJrd+8qoSK73e4sLnln3J3E+lW/d+Xlslpt/DJnHyV5BgaMSiAyIcBtbdWV+IB4vhn2DY+tfYzkwmRGrx7NnAFz6Bnes7675hIiUBJqpTw/qUNwB1Ry1SWPNRksnDtRCIj8pLpgLyvjeOfqp0PdIX5PIpL23xWNjv3klKi1Smy28qDJjKnMgt0GEWFNeP+9D1CqFMTHx3Pw4EFmz57NhAkTGDx4MAsXLnQGSgsXLqRfv37ExjrevDw8PDAajRVKfqhUKry8HPtQbd++nVdeeYXFixdXGJ36301zm3o3rYsfjdAIrElbw8Hcg3goPHi84+Nubevw3+ecr4u/zN1H16ExdLs5Bpm8cU/whHmGsXjoYiavn8zurN08tvYxXu/9OsObD6/vrl2xxv0/I9S5CzfCrc6ZYwXYrI6yAL4hIoH2WiWTSWg8lfiFOMoNSDKJzp26oS8wOacfevXqxYkTJ7BarUyYMIFvv/0Wg8GAyWRi6dKljBs3rkZtpaenc/vttzNt2jTuvffeCo9JkkSEVwQARcYidCada5+o0CiZrCbm7JkDwNi2Y50lJdzBWGpm12+pAITE+IAddv+Rxs9z9qEvNLqt3brio/Lh/278P4bEDMFis/DS5pdYcHBBo59mFCNKQo3Z7XYSM8/nJ9VgfzfntFvbIJE8WwckDw/i9yTWW9s1IZPLkCsdvwsmgwVjqQWNZ8UpsOHDh6NWq/nxxx9RqVSYzWbuvvvuaq+t1+u59dZb6dWrF6+//nqlx3goPAjQBJBvyCdDn0FzZXOxsukat+zYMs7qzhLsEczo1qPd2tbuVacw6M34h3ty17OdSU7MZuM3xzl3opBlb+5k0NjWjT5NQSVX8e717xKqDWXxkcXM3TOXTH0mL3Z/sUa7ODREIlASauxMyRmyy7JRyBS0D25/yWPtdvsF9ZMa/xx8YyBJUoXpr4ZKkiT2HXQEdLoCAyoPBdu3bycuLg653PFCOnr0aBYuXIhKpeL+++/H44JATKVSYbVWLB5pt9t56KGHsNlsLFmy5JKBeYg2hGJTMSaridyyXEK0IW54lkJjUGQs4v8O/B8AkzpNQqt0399PUU4ZBzY4tsfqc1cLZHIZLbuHERLtw5oFh8g9reO3efvpPDia7rc2Q96Ip+Jkkoxp3aYR5hnGe7ve47vj35Fdms2717/bKMtzNN7/CaHOlU+7tQtqh0ahueSx+Rl6dAVG5EpRFkC42Jkzp/nPWy+RlJTEoi++Yt68eUyePNn5+MMPP8z69etZvXr1RdNuMTExHDhwgOPHj5Obm4vZbGbGjBmsXbuW//u//0On05GZmUlmZiZllSS3X7hpbm5ZLkZr45/yEC7P5wc+p9hUTAu/FtzW/Da3trXtxxRsFjuRrQMqfHj0C9Vy13NdaNuvCQB71pzip5l7Kck3uLU/deGh1g/xQb8PUMlUbDi9gYf/fLhRrjoVgZJQY+WBUk2m3cpHk5qIsgBCJUaNGoXVbmbI7Tcw9dmnmfTEk0ycONH5eFxcHL179yYhIYEePXpUOHfChAnEx8fTtWtXgoOD2bJlC5s2bUKn09G7d2/Cw8Odt++++67S9n1UPnipvByb5urEprnXojMlZ1h6bCkAU7pMceu0UEZyISl7spEkx2jS/454KpRy+o2IZ/CEtqg0cjJPFvHdWztJPZDrtj7VlZtibuKzmz7DR+XDgZwDjFw1ktMljWvjeREoCTVWm0KT5flJUY18vl1wD6VSyWef/x+nUzI5vv8Uzz39SoXH7XY7586dqzSJOzg4mD///JOSkhLsdjv9+/dn48aNjvII/3MbM2ZMpe1fuGmu3qwXm+Zegz7c+yFmm5me4T3p26Sv29qx2+xsXuHYqqtVnwgCm3hVeWyLLiHc+3J3QqK9Meot/PHxATavOIHV0rj3UusS2oUlQ5c4q+Q/9MdDHM49XN/dqjERKAk1kqHL4KzuLHJJTseQjpc81lRmISO5CBBlAYRL8/JXI0kSFpMVg84MQE5ODh999BGZmZlu3aNRJVcR7BEMiE1zrzWHcg+xKnUVEhJTu05162KTE4lZZKcVo1TL6T68WbXH+wZ7cOezXehwQyQA+9eeZuUHeyjOrZ8aaa4S6xfL18O+JiEggXxDPmPXjOWfM//Ud7dqRARKQo2UT7u1CmiFp9LzkseWlwXwDfbAL6ThJxcL9UeukOHp59hIU1doxGq1ERISwuuvv85nn32Gv79789sCPQJRyVVYbBayS7Pd2pbQMNjtdj7Y/QEAw5sPr9HG3pfLYrKy7ccUADoPjsbTt2abxsoVMvreG8fQR9uh1irITivmu7d2kbK3cf+OhmhDWDh4Ib3Ce1FmKWNt+tr67lKNiFVvQo1czrYlUWI0SajExo0bK3zv4a3EoDdjMVnRFxjrNF+owqa5hvOb5iob36ocoeY2nt5IYlYiarmaJzs96da29q8/jS7fiJe/mo6DImt9fmzHYIIivfhzwWGyUotZ/X+HaNe/KX3uaoFc2TjHObxUXswfNJ+lR5fyQKsH6rs7NdI4f9JCnatpftKFZQHEtJtQE5Ik4R3gWEVp0JsxGSx12n75prkA5/TnRGL3VcxsMzMrcRYAI1uPdK5+dIfSYhOJq08B0PP25iguc1GLT6AHd0zrTKebogA4uPEMP7yfSGF2qcv6WteUMiWj24xuNNsIiUBJqFZuWS5pxWlISHQK6XTJY/PP6dEXni8LEOdXNx0UGj2lWo6Hl2NLnJJ8Q50HK6Gejk1zDRYD+Yb8Om1bqDsrk1aSVpyGv9qfcW1rVu39cu38LRWzwUpItDctu4Ve0bXkchm972zBzU+0R+OpJCe9hOVv7+LE7iwX9Va4FBEoCdUqz09q6d/S+cm7KuWjSU3j/S/7E5RwbfL0UyGTSVjNNkqLTXXatlKmJNTT8WaWXZqN2Wqu0/YF99Ob9Xy8/2MAHu3wKN4qb7e1lXdOx5F/zgLQ5+44JJlrksVj2gVx3yvdCG/hi9lg5c8Fh9nwzTEsJrEQwZ1EoCRUy7ltSU3ykw6JsgDC5ZHJZXj5O6bg9EUmrOa6XRLtr/bHQ+mBzW4jszSzTtsW3O/LQ1+Sb8gn2ieae+LvcWtbW39IwW6H2E7BRLh4ZN3LX8Ptz3Si67AYkODIP+dY8e5uCjL1Lm1H+JcIlIRq1XQjXFOZhcyU8rIAYtsSofbUngqUGjnY7ZQU1O0UnCRJRHg6Ns0tNhZTYiqps7YF98rSZ/HV4a8AeKbzM27NjUk/nEf64TxkcoledzR3SxsyuYwet8Zy65Md8fBWkndWz/J3dnN8e4Zb2rvWiVVvwiUVGApILnQUS6suUDp9LB+bzY5fqBbf4GuzLIDFaiNXZyKr2OC4lRjJLjaQWfTv16UmK91iArghIYTrWgbho2kcCY11oTyxO/+cHlOZBVOZBbW27n4+GoWGQI9A8sryyNBn4Kn0FJvmXgU+2vcRBquBTiGduCHqBre1Y7PZ2fKD4/WyXf+mbi+PEtk6gPte6c5fXx7m7PFC1i46ypnjBVx/fzxKtUh9cBURKAmXtCd7DwCxvrEEaC49SnQ1b4Jrs9nJL3UEQNnFRrKKDWQWG8gqdgQ/WSWOr3N1RmoyCJKeX8oPe86gkEl0iwlgYKsQbkgIITa46qq914IxY8ZQWFjI1wuXUVpsoiTfiFKjQOaiHI+aCPYIpshYhNlqJqcsh1DtlSXiCvXreP5xfk7+GcDtxSWPbjlH/jk9ak+FY2qsDnj6qrl1cicSV6Wx67dUjm3LJCuthMET2hAYcW2/nriKCJSES9qdWbP93ex2O+mNsCyA3W6nuMxyPtBxBDvO0aALAqHsEiMWW82mgeQyiRBvNSE+GkK91YT6aAj1Of+9jwYJ+OdEDuuOZXMyR8+2k3lsO5nHm78fJSZQyw0JodyQEEL3ZgGoFNfWaMbcuXOx2+1ofdQYSy1YLTZKC414BVx6E2ZXksvkhHuGc7rkNHllefip/FAralYoUGh4ZifOxo6dwTGD6RDcwW3tmAwWdvxyEoBuw5qh8ay7kVCZTKLbzc2IaOHHn18epiBDz4p3dnPd/S1p1TvcrcHhtUAESsIl1bTQZN5ZHfoiEwqVzOXJi5dLb7RUEvwYySoxOEaBzt9vrOE+SpIEgZ5qQn0uCH68HcFPmO+/Xwd4qpBXMwJyfctgXr65NWm5etYfy2bD8Wy2n8wjLa+UL7ek8uWWVLzUCvq2COKGViEMiA8h2Pvqf7P29f13VaWXv5qinDJKS0yovZR1urmyt8obL5UXOpOODH0G0T7R4s2mEdp6ditbzm1BIVMwufNkt7a1Z80pykrM+IZ40LZfE7e2VZUm8f7c93J31i46wukj+WxYcoyzxwvo90A8Ko14u79c4icnVKnEVMLxguNA9flJ5dNuTeL9USjd+4ZmMFvJKalk+qtCIGREZ6x54UI/rZJQbw0hFwRBYT4a5yhQqI+aIC81SrlrR3higjwZ17cZ4/o2Q2e0sPlELuuPZbHheA45JUZWH85k9WHHCqwOTX0ZkBDCwIRQ2kT4uLQfdW3FihW89tprJCcno9Vq6dSpEz///DNPPPEEhYWF/PTTTxTrC2nXrS3jxzzCs1Oexy9Uy7Zt2+jfvz+rVq1i4MCBbutf+aa5yeZk9GY9RcYi/DR+bmtPcD2rzcrMxJkA3B9/P5Heta+MXVMl+Qb2rT0NQO87WyCvx5FgrY+K4ZM6sOfPU+z4JZWknVlkn3JMxQU1dV9JhKuZCJSEKu3N3ovNbiPKO4oQbcglj00/7CjSF30FZQHMVhu5OiNZxUYyiwxk/890WPb5IKiwtOY1brzUCkfw4625YCTo3+An1EdDsLcajZuDu5r2dUjbMIa0DcNms3PoXBHrj2Wz/lg2B84Usf/8bc7aEwR7q7mtbTDDYuRYz08J2u12LKb62WVcoZLVeMQlIyODESNG8N5773HHHXdQUlLCP//8c9EKt+DgYBZ88QV33XUn/a+7gY5d2zFy5EgmTZrk1iCpXPmmudml2WSWZuKl8kIhEy+ZjcWvJ38lqSAJb6U3j7R/xK1tbf8pBavZRkScH806BLm1rZqQZBJdhsQQ3sKPv744TGFWKSv+m0jfe+Noc12EGB2tJfFXL1SppmUBjKVmMpxlAS4dKJ3IKmFXWoEj8Cm5cFrMSJ6+ZonQAGqFrGLuj7dj+ivUR3N+Csxxv5e6cf6Ky2QS7Zv60b6pH08Pakl2sYGNx3NYfyybf044RptWHcqgR3AIKTk6vLRWtDIZP7y8vV76O3FuvxqvssnIyMBisXDnnXcSHR0NQLt27So9dvjwWxg7ehyPPz2BDu074enpyTvvvOOyflcn0COQImMRRquR7NJsIrwi6qxt4fKVWcqYt3ceABPbT3TraGBWWjFJO7NAgr73xDWoICSihR/3vdyddYuPkHYwj01Lj3P2eAH9H0pA7dE4Xxvrg/hJCVWqaX7S6aMF2M+XBfAJqnpD0aJSM7fN30LpJarIKi5MhK5k+iv0fFDk46FoUC9I7hbio+HebpHc2y0So8XKztR8diRnopBZsNvt6IwWihpJdd4OHTowcOBA2rVrx+DBg7npppu4++678ff3r/T42XNn0aZ1W379/Sc2rd2CWl13uVoySUa4VzhpRWkUGArwU/uhVV6bpS8ak6+PfO0IbD0jGNFqhNvasdvtbFlxAoD4HmEERzW8qS2Nl5Jhj7dn39rTbP8xheTEbLJPFTN4QltCohv3FH5dEYGSUKlScylHco8A1Y8opR+u2Wq3tUezKDVZCfJSc1Ob0ArTYeW5QQFaVZ0uBW+M1Ao518UF0y3Sm9TUVCICPTGhoLjMRJ/nOmLn32E5uUzCU63AR63ES6NA4eIcq3IKVc2vK5fL+euvv9i6dSt//vkn8+bN4+WXX2bHjh2VHn/y5EkyszKw2WwknzhJl+6d6jQx1VPpiZ/aj0JjIef052ju2/yaCtIbm7yyPL449AUAT3V+CrXcfYH1yX05ZCQXoVDK6HlbrNvauVKSJNHpxijCm/vy54LDFOca+OG9RHrf1YL2A5qK3+dqiEBJqNT+nP1Y7BbCPcNp4lX1Cg673c6p8kCpmvykVYccSckP9ojimRtbuq6z1zi1Uo6vRk2wtxprkI0Sg8V5s9hs6K029KVGKDWiVSnw0Sjw1ijRKGueV+RqkiTRp08f+vTpw6uvvkp0dDQ//vjjRceZTCYeeugh7rvvPqIjY5nywpN069ad+HYxddr3UM9QSswlGC1G8gx5BHnUfx6KULlP9n+C3qyndWBrhjYb6rZ2rBYbW1emANDxxijn9jsNWVisL/e+3I31Xx0ldX8um5ef4OzxAm4Y1apOyxk0NtdWkRahxmqan5R7RkdpDcoC6IwW/j6RA8DQdmEu66dQkVwmw0+rIjJAS6twb5oHexHircHjfLJ6qclCZrGBE9klHMss4UxBKcVlZmw1rBHlCjt27ODtt99m9+7dpKens3LlSnJycmjVqtVFx7788ssUFRXx4YcfMv3Vl2jerAVPPvNYnW+aq5ApnIUnc0pzxKa5DVRqUSorklYAMK3rNLdWVT+48QzFOWVofVR0uinKbe24msZTydBH23HdfXHIFBKp+3NZ/tYuMk8W1XfXGiwRKAmVcuYnVVNosrwsQNOEAOTKqn+d1h/LxmSxERvkSXxow5vHvxpJkmPaLcxXQ1yoNwlhPjTx88BHo0QmSZitNvL1JtLy9BzJKCY1V0+ezojJ4t5cJx8fH/7++2+GDRtGy5YteeWVV5g5cyZDh1b89L9x40bmzJnDkiVL8PHxQaFUsGjhInbs2sb8jz7GWsP6V65Snp9ks9vI0Is9tRqi2Ymzsdqt9G/an25h3dzWjkFnZvcfaQD0uC220dUokiSJ9gMiuevZLvgEaSjJN/DjB3vY+2c69jr80NRYNK7/XaFOGK1GDuYcBGqRn1TNtiWrDzneWIa0DRPz4fVEpZAR6KUm0EuNzWZHZzo/RVdmxmS1UWIwU2JwjJRolHK8NQp8NEq0KrlL/89atWrF6tWrK31s0aJFzq/79++P2Vxx5Ca+dRynks5hNlrR5RvwdfNeWhcqr610svAkJaYSSkwleKtE0N9QJGYlsuH0BuSSnGe6POPWtnb9noqx1EJgEy8SeoW7tS13Con24d6Xu7Px62MkJ2azdWUyZ08UMGh0azReYiqunBhREi5yIOcAJpuJII8gon2iqzzOoDeTebIYgKhL5CeVmaxsOHZ+2q1t431RuZrIZBI+GiVN/DyID/OmZag3Yb4aPFUKJP4t6pmSo+NIRjHp+aUUlpqwWOunTlO58k1zAYxlFoy1qKnlChqFhgAPx4eCDF0GNnv9/jwEB7vdzszdjuKSd8bdSayf+xKrC7NKObTpLAB97m7R6BefqD0U3PRwG/o9EI9cIePUwTy+e2sn55IL67trDUatA6U9e/Zw8OBB5/c///wzt99+Oy+99BImU93mDQjuUT7t1iW0yyVHEk4fzcdus+MfdumyAJuSsikzW2nq70HbJmI5akMjSRIapZwQbw3NQ7xoFe5DVIAWP61jKxarzU5hqYn0/FKOZhSTkq0ju8SAwWy9qEhkXVCo5Gh9VACU5BvrNL8KHJvmKmVKzDYzOaU5ddq2ULk1aWs4mHsQrULL4x0fd2tbW1cmY7PZiW4XSGSrq2MDcEmSaHt9E+5+oQt+oVp0BUZ+mrWXxNVpYiqOywiUHnnkEZKSkgDHst37778frVbL999/z3PPPefyDgp1rzyRu7r8pPJpt6hqygKUr3YbKqbdGgWF3JEQHhWgpXW4D82DvZzVy+2A3mQhs8hAUlYJxzNLOFtYRrGhbhPCtb5qZHIZNquN0iJjnbULjk1zwzwdCxLyyvIwWAx12r5QkclqYs6eOQCMbTvWrSsSzx4vIHV/LpJMovedLdzWTn0JaurNPS92pWX3UOw2O9t/OsmvH+2v88UTDU2tA6WkpCQ6duwIwPfff8/111/P0qVLWbRoET/88IOr+yfUMbPVzP7s/cCl85PsNjunyrctuUSgZLRYWXc0G4AhYtqt0SlPCA/39aBlqDcJYd408fPAW6NEkiRMVht5OiNpuY6E8LTzCeFmNyday2QS3gGO+jilxSYsdVxs00ftg7fKGzt2MvQZ9TKyJjh8e+xbzurOEuwRzKjWo9zWjt1mZ/P54pJtr4sgINzTbW3VJ5VGwaCxrRkwMgGFUsbpI/l89+ZOzhwvqO+u1ZtaB0p2ux2bzfEiuHbtWoYNGwZAZGQkubm5ru2dUOcO5x3GYDXgp/ajuV/zKo/LPaOjrNiEQi0norlflcdtPpGLzmghzEdDp8iqjxMaB5VCTqCXmmZBnrQJ9yEm0JMATxVKuQyb3U6xwczZwjKOZhZzIquEzCIDeqPFLYGEWqt0bsNQkm+o82AlzDMMmSSj1FxKobGwTtsWHIqMRXx24DMAJnWa5Naq6cd3ZJJ7WodKI6fbLc3c1k5DIEkSrftEcPeLXfEP96S02MQvc/ay87fUOp/qbghqHSh17dqVN998kyVLlrBp0yZuvvlmAFJTUwkNDa3Vtf7++2+GDx9ORIRjk76ffvrpksevXLmSG2+8keDgYHx8fOjVqxdr1qypcMyMGTOQJKnCLSEhoVb9upZdmJ90qRok5WUBIhP8L1kWoHzabUjbsEaf9ChUJJNJ+HgoaeqvJSHMm7gQb8J8NGhVjuClzGwlu8RASo6OoxklnD6fEG61uW60yStAgyRJmI1WDPq6TexWyVUEa4MByCrNwmKz1Gn7Anx+4HOKTcW08GvBbc1vc1s7ZqOV7T+fBKDLsBg8vFVua6shCYzw4p4XupLQOxy7HXb9lsovc/eir+Pp7vpW60Bpzpw57Nmzh0mTJvHyyy/TooVjnnbFihX07t27VtfS6/V06NCB+fPn1+j4v//+mxtvvJE//viDxMREBgwYwPDhw9m7d2+F49q0aUNGRobztnnz5lr161pW00KT5YHSpVa7ma02/jqSBTgCJeHqJUkSHio5IT4aWoR40Trch0h/Lb4eSuQyCYvNRsH5hPAj50o4V1jmkhEguUKG1tfxpqUrMGKr41V5AZoA1Ao1VpuVrNKsOm37Wnem5AxLjy0FYGrXqchlNduU+XLsW5uOvtCId6CG9gOauq2dhkipljNwVCsGjWmFQi3n7PFCvntzJ6eP5Nd31+pMresotW/fvsKqt3Lvv/8+cnntflGHDh16UZG5S5kzZ06F799++21+/vlnfv31Vzp16uS8X6FQEBYm3phry2qzsjfbEXReKpHboDeTleqo4nqp/KRtKXkUlZkJ8lLRLebqWB0i1IxCLsPfU4W/pwqb3U6p0UqJ0UxxmQWjxUquzohSLhHsfeXbPmh9VBj0ZqxmG7pCIz6BVa/AdDWZJCPCM4LUolQKDYX4qf3wVF6duSsNzYd7PsRsM9MzvCd9Ivq4rR19oZE9a04B0OuO5iiU7gvIGrL4nuGExPiw5vPD5J3V8cu8fXQZEk33W5ohc9Mekg2Fy56dRqNBqazbAlU2m42SkhICAiq+CZ84cYKIiAhiY2N58MEHSU9Pv+R1jEYjxcXFFW7XomMFx9Cb9XgrvWnpX/VebKeP5mO3Q0CEp7OmTWXKp91uahOGXEy7XbNkkoSXxpEQHh/mTYSfI5DJLDI4C1xeiQtrKxl0ZkyGup0C0yq1+Gv8AcjQi9pKdeFQ7iFWpa1CQmJq16luXU2745eTWEw2wmJ9aNElxG3tNAb+YZ7c/XwX2lwXAXZIXHWKn2bvRVdwda/8rFGg5O/vT0BAQI1udemDDz5Ap9Nx7733Ou/r0aMHixYtYvXq1XzyySekpqZy3XXXUVJSUuV13nnnHXx9fZ23yMjIuuh+g5OY6chP6hTa6ZLD2DWZdrPa7Px5+N+yAIJQLtBTRYBWhR1Izy/FaK79irWYmJgKI8wqjcK5qacu31jnid0h2hDkMjlGi5F8w7UzJVEf7HY7H+z+AIDhzYeTEOC+HNSc0yUc3ebYVaDP3XGivAmOOmb9H0zgpofboNTIyUgu4rs3d5F28OpdzFWjqbcLX5Dy8vJ48803GTx4ML169QJg27ZtrFmzhunTp7ulk5VZunQpr732Gj///DMhIf9G+RdO5bVv354ePXoQHR3N8uXLGT9+fKXXevHFF5kyZYrz++Li4msyWKpJfpLdZv9325JLTLvtTM0nT2/CT6ukZ+yl6ywJ15YBAwbQoUMHnnz5LUpNFtLySmkR4olcdmUD3F7+aoxlFixmK2UlJrQ+audjixYt4umnn6awsPAKe185hUxBmDaMs7qzZJdm46PyQSW/NhJ+69qG0xtIzEpELVfzZKcn3daO3W5ny4pksENc1xDCYn1dcl2bzc5bfxwlo6iMJ2+Io1V44yzCG9c1lOAob/5ccJic9BJ+n3+ATjdG0eP2WORX2VRcjQKl0aNHO7++6667eP3115k0aZLzvqeeeoqPPvqItWvX8swz7t1jB2DZsmU8/PDDfP/99wwaNOiSx/r5+dGyZUuSk5OrPEatVqNWq6t8/Fpgs9vYk70HuHR+Us7pEspKzCjVcsKbV/3CUb63242tQlFeZX80wpWTJInoQC3J2TqMFiun88uIDtRe0Sd2mVyGl5+aknwD+kITaq0SuaLufvd81b4UGAsoNZeSqc8k0jtSjEC4mNlmZnbibABGth7pLPzpDqcO5nH2eAFyhYyet1ddKqW2Pv/nJF9sTgVg9aFMRnSPYsqNLQn0anzvQX4hWu56tgtbViZzcMMZ9v6VzrnkQm56uE2d5gq6W61fRdasWcOQIUMuun/IkCGsXbvWJZ26lG+//ZaxY8fy7bffOksTXIpOpyMlJYXwcFHs8FKSC5MpMhbhofCgVWCrKo8rn3ZrmuBf5ZuQzWZndfm0Wzsx7Sb8a8yYMWzatIm5c+eiUshpHeHL9CmP0yzYC5lMVqGsx8aNGwHIzs5m+PDheHh40KxZM7755puLrjtr1izatWtHUJg/nXq35rmXnyHztGMqYOPGjYwdO5aioiLntWfMmAHAkiVL6Nq1K97e3oSFhfHAAw+QnZ19Wc9NkiQiPB2lTso3zRVca2XSStKK0/BX+zOu7Ti3tWO12ti60vHhusPAppfcoqk2dqXl896a4wB0ivLDZodvdqTT/4ONLPjnJCY3F2p1B7lSxvX3tWTII21ReSjISi1m+Vu7OLnv6tnep9aBUmBgID///PNF9//8888EBtZuikWn07Fv3z727dsHOGox7du3z5l8/eKLLzJq1L+VVpcuXcqoUaOYOXMmPXr0IDMzk8zMTIqKipzHTJs2jU2bNpGWlsbWrVu54447kMvljBgxorZP9ZqyO9Mx7dYxuCNKWdVJ+TWZdtt7uoCsYiPeagV9WrhvOwGhIrvdjtlgqJdbTXOC5s6dS69evZgwYYKzfMeHc+ayLvEY6xKPcSzlFJMnTyYkJMRZ/2zMmDGcPn2aDRs2sGLFCj7++OOLghmZTMaHH37I4cOHWfjlQjZv/ZtXXn0JY6mZ3r17M2fOHHx8fJxtTps2DQCz2cwbb7zB/v37+emnn0hLS2PMmDGX/X+gVqgJ1Dj+NjL0GVhtdVsx/GqmM+n4eP/HADzW8TG8Vd5ua+vIP+coyCxF46Wk85AYl1wzT2fkyaV7sdrs3NYxgpWP9ea7iT1pE+FDicHCm78fZcicv9lw7PIC9frWvFMI973cjZAYH4ylFlZ9epB/lidhNTe+4O9/1bo8wGuvvcbDDz/Mxo0b6dGjBwA7duxg9erVfP7557W61u7duxkwYIDz+/I8odGjR7No0SIyMjIqrFj77LPPsFgsPPHEEzzxxBPO+8uPBzhz5gwjRowgLy+P4OBg+vbty/bt2wkODq7tU72mXFhosioGnZnMVMeKwEsFSqsOOkaTBrYKQa24NpfS1geL0ciHo++ul7afWrwCpab6pf6+vr6oVCq0Wm2FEh5KrRe5OiOrVv3K//3f/7F27VrCwsJISkpi1apV7Ny5k27dugHwxRdf0KpVxVHPp59+2vl1TEwM/5k+g8lTnuSDgjn4h3vi6+uLJEkXlQ0ZN+7fUYnY2Fg+/PBDunXrhk6nw8vL63J+FARrgykyFWG2mskpy3Hr9NC15MtDX5JvyCfGJ4a7W7rv99xYambnb46psR7Dmzmrv18Jm83OM8v3k1lsIDbYk7fvaIckSfSIDeSXSX1ZkXia99cc52SunrGLdtGvZTDTb2lFixD3BYPu4BPkwZ3TOrP9pxT2rT3NgfVnyEguYvCEtvgGN96puFr/BowZM4ZWrVrx4YcfsnLlSgBatWrF5s2bnYFTTfXv3/+Sn0TLg59y5UPxl7Js2bJa9UFwjEQ4N8INqzo/Kf1oHpwvC+DlX/mbot1uv6Aat5juFGom3FdD4p49vDj5UV5+83169HQsFDl69CgKhYIuXf4N4BMSEvDz86tw/tq1a3nnnXc4duwYxcXFWCwWDAYDJSU61J5Vv8wlJiYyY8YM9u/fT0FBgXN7pvT0dFq3bn1Zz0UmyQj3DCe9OJ28sjz81H5oFFdeL+palqXPYsmRJQA83fnpS456X6nEVacw6Mz4h2lp3TfCJdf8ZFMKfyfloFHK+PjBzniq//2dlMsk7usWxbB24Xy0Ppkvt6SyKSmHzXNyGdkzmqcHxeGnbTwLA+QKGX3ujiOipT/rFh8hJ72E5W/tZMDIVo22vMJlhco9evSoNE9AaJzSitPIN+SjkqloG9S2yuPSD1W/Ce7Bs0WcLSxDq5LTP16M4tUlhVrNU4tX1FvbVyIrK4vHR9/PPQ+M4tb7HiI9v5SYoJoVbkxLS+OWW27hscce46233iIgIIDNmzczfvx4zCYTpUUmrJVU7Nbr9QwePJjBgwfzzTffEBwcTHp6OoMHD8ZkurLd0r1V3viofSg2FnNOf45mPs1EYvcV+GjfRxisBjqFdOKGqBvc1k5xbhn7N5wGoPddLVxSSHH7yTxm/unIS3r91rYkhFW+ys1bo+TFYa0Y0T2Kt/44yl9Hsli0NY2f9p1lyo0teaB7FIpGtDCmWfsg7nu5O38uOEzmySLWfH6Is8eb0OeeFo2uaOcVjSkaDIaLXlB8fBrnUsdrWfloUvvg9qjllb/h2W120o+cz0+6RP2k8tGkAfEhaBrZH0NjJ0lSjaa/6ptKpcJq/Td3x2AwcNttt5GQkMAnH80lNa8MndFCZpGBhIQELBYLiYmJzqm348ePV1jmn5iYiM1mY+bMmcjOlxhYvny5o63z0yY2o1ShTYBjx46Rl5fHf//7X2c5kN27d7vseYZpw9CZdJSZyygwFhCgEdXpL8fx/OP8nOzIi53WdZpbA85tP6Zgs9iJbOV/yQ+ENZVTYuSpb/dis8OdnZtwT9fqtz+JCfLk81Fd2ZKcy+u/HuF4Vgmv/nyYr7efYvotrbkurvF8APUO0HD71E7s/CWVPWtOcejvs2ScLGLIhLb4hbpvA2NXq3V4WlpayqRJkwgJCcHT0xN/f/8KN6HxqUl+Unb6+bIAGjlhLSovC2C321l10FEWQOztJlQlJiaGHTt2kJaWRm5uLhMmTOD06dN8+OGHlBTmozIVk5udRUZ+CcFNYxgyZAiPPPIIO3bsIDExkYcffhgPj3/zHVq0aIHZbGbevHmcPHmSJUuW8OmnnwLg5efYNDcirCk6nY5169aRm5tLaWkpUVFRqFQq53m//PILb7zxhsuep1KuJETrmGrI1meLTXMv06zEWdixMzhmMO2D27utnYyUIpITs0GC3nddeXFJq83OM9/tI7vESFyIF2/e3rZW1+zTIojfn+rLG7e1wV+rJClLx8gvdvLw4t2k5uqvqG91SS6X0euO5tzyZAc0XkryzuhY/vYuknZm1nfXaqzWgdKzzz7L+vXr+eSTT1Cr1SxYsIDXXnuNiIgIvvrqK3f0UXAju93uXPF2qfyk8rIAka0CqiwmdiyzhLS8UtQKGQMSGudctOB+06ZNQy6X07p1a4KDg/nnn3/IyMigdevWhIeHEx8bxcAuCexL3MnZQgPzP/2ciIgI+vXrx5133snEiRMrFJnt0KEDs2bN4t1336Vt27Z88803vPPOO4Bj6bLWV0W3Lj0Y89B47rvvPoKDg3nvvfcIDg5m0aJFfP/997Ru3Zr//ve/fPDBBy59rgGaADQKDVa7lSy92DS3trae3crWc1tRyBRM7jzZbe04ikueAKB173CCml5eIv+FPlqfzObkXDyUcj5+sDNaVe0ncBRyGSN7xbBx2gDG9olBIZNYezSLm2Zv4u0/jlLsgi2A6kp0m0Duf6U7EXF+mI1W/vryCP98l1Tf3aoRyV7LWv9RUVF89dVX9O/fHx8fH/bs2UOLFi1YsmQJ3377LX/88Ye7+lpniouL8fX1paio6KqfSjxTcoahK4eikBRsfWArHorKVyaseHc3WanFDHgoocoEx1l/JfHhuhPc2DqUz0dVHXQJrmEwGEhNTaVZs2ZoGsGUW23Y7XbS80spKjOjkMloEeKF6jKLR9rtdvIz9FjNNjy8VHgH1u3PqtRcSmqRYxVVjG9Mo940ty5/56w2K/f+di9JBUk81Oohnu/+vNvaOrEriz+/OIxCLeeh13vi6XtlOXdbk3N58Isd2O0w854O3NWl+im3mkjO1vHm70fYeNxRoyjIS8XUm+K5t2tko9lP02a1seuPNHb/kcag0a2I7+naRT/ueP+u9StPfn4+sbGxgCMfKT/fkeDbt29f/v77b5d0Sqg75flJbYLaVBkklelMZKU5ygJcan+38mrcYm834UpJkkRTfy0apRyLzUZ6vh6b7fL2b7tw09wynQmzsW5rG124ae453TmxaW4N/XryV5IKkvBWefNI+0fc1o7FbGXbjykAdBkcdcVBUnaxgaeW7cNuh/u6RrosSAJoEeLForHdWTi2G7HBnuTqTLy48iDD521m+8k8l7XjTjK5jB7DYxkxvYfLgyR3qXWgFBsbS2qq49NRQkKCM2ny119/vWjJrtDwlecnXWrbkvTD+WCHwCZeePlX/iKSnK0jKUuHUi4xsFWoW/oqXFvkMsc2J3KZRKnJytnCssve7PbCTXNL8mteINNVyjfNNVlN5JU1jje0+lRmKWPennkATGw3ET+Nn9vaOrD+DCX5Brz81XQYFHVF17JYbTy1bC+5OiMJYd68dlsbF/WyogHxIax5+npevaU1PhoFRzKKuf+z7Tz+TSKn80vd0qarBUQ0npHVWgdKY8eOZf/+/QC88MILzJ8/H41GwzPPPMOzzz7r8g4K7lWen3SpRO5/q3FXvWqnfDSpT4sgfD3cV+NEuLaoFXKiA7RISBSUmsjVXf6yfU9/NZJMwmKyUlZSt7kdCpnCWXgypywHk/XKyg9c7ZYcWUJ2WTYRnhGMaOW+XRVKi03sXpUGQM/bYlGqrmyl7tx1J9h+Mh9PlZz5D3Z268pfpVzGuL7N2PjsAB7qGYVMgj8OZjJw1ibeX3MMvVEsHnCVWmeXXbjp7aBBgzh69KgzT6l9e/etSBBcL1OfyRndGWSSjE4hnSo9xmazO0aUqKYa9/myAGLaTXA1L42ScD8N5wrLyCwqQ6OU4a2pfTAur7BprhG1VlG3m+aqfClUFqI368nQZ9DEqwkK2ZVXfb7a5JXl8eWhLwGY3HlylSVLXGHXb6mYDVaCo7xp2f3KXrv+Tsrhow2O/eHevrMdzYOvPCG8JgI8Vbx5ezse6hnNG78dYUtyHvM3pPD97jM8NySBOzs1QdZI8pcaqit+lYiJieHOO+8UQVIjVD7tlhCQgJeq8j/q7FPFGPRmVBo5obGVlwVIzyvl8Lli5DKJG1uLQElwvUBPFf5aFXYgPb8Uo/ny8ow0XkoUKjl2ux1dgdG1nayGJEmEe4YjSRI6k47j+cc5UXCCMyVnyCvLo8xSJvKXgE/2f4LerKdNYBuGNLt4A3ZXyTun4/A/ZwHoe08LpCsIJjKLDDz9nSMv6YEeUdzWsYmrulljCWE+fD2+B5+N7EJ0oJbsEiPTvt/PHR9vIfFUfp3352pyWYHSpk2bGD58OC1atKBFixbceuut/PPPP67um+Bmzm1LLpWfVF4WoHXVZQFWnZ926xkbQIBn4ym1f7Wo63yb+iBJEk38PNCqFFhtdk7llWK9jOTuCxO7jaVmjGV1Oz2hVqhp4tXEOUpispooMhaRqc/kZOFJjuUfI7UolUx9JsXGYsy2hrX8u3yLF3c5WXSSFUmO6vJTu05FJrlvxG/rDynY7RDbMZiIuMuvAWix2njq273k6020Dvfh1Vsub+sbV5AkiZvahPHnM9fzwtAEvNQK9p8p4q5PtjF52V7OFZbVW98as1qP+3799deMHTuWO++8k6eeegqAzZs3M3DgQBYtWsQDDzzg8k4K7lGTQpPl9ZMutdpN7O1WP5RKJZIkkZOTQ3Bw8DWxRUaop4x0g4Uyg4m0LDMRfh6X9bzlajsGvZn8bDN+wdorGk2oLTVqmno0xWqzYrAYMFjP38wGrFjRmXTo0DmPV0gKPJQeqOVqNHINaoXarQFEZex2OyaTiZycHGQyGSqVez4QzUmcg9VupX/T/nQL6+aWNgDSj+SRfjgPmVyi1x3Nr+haM/9KYmdaPl5qBR+7OS+pptQKOY/2a85dnZvywZrjLE88zc/7zrHmcCaP9mvOI9c3x+MK87GuJbUOlN566y3ee++9CrlKTz31FLNmzeKNN94QgVIjkVuW66ztUlWgVFZiIju9BKh625JzhWXsO12IJMHgNmK1W12Sy+U0bdqUM2fOkJaWVt/dqTMWi40cnZFsO+RqFPhcxuIBu92OvtCI3QZZuQrU2vrPFZKQwOYYZTLZTJit5kpHlCQklHIlSpkSlVyFSqZCLqubNz2tVktUVJRzqxhXSsxKZMPpDcglOc90eab6Ey6TzWZn6w+OXKJ2/Zpe0VYaG45l88lGR2mBd+9qX+P9CetKsLead+9uz8he0bz+6xF2puUzZ+0Jlu86zfNDE7i1Q8Q18QHrStX61eHkyZMMHz78ovtvvfVWXnrpJZd0SnC/PVl7AIjzj8NXXUXu0RFHWYCgSC88/SpPqFx9fjSpW3QAId5XV9HDxsDLy4u4uDjM5oY1ReNuaYczeW/1MQBmDG/NdS1rXwn+1OE8Ni8/gUwhcfNj7fEJqryOWH0qNZeSVJDE8YLjHM933EpMJRcdF+gRSHxAPPH+8SQEJNDcrzkquWtHfeRyOQqFwi1vrDa7jQ92Oaqi3xV3F7F+sS5vo9yxrRnkndWj1iroenPMZV/nXGEZzyzfB8CoXtHc3L7hjqi3beLLd4/05I+Dmbz9x1HOFpYxedk+vtp2ildvaU2HSL/67mKDVutAKTIyknXr1tGiRYsK969du9a5saTQ8NUkP6km026rndNuIom7vsjlcuTya2sY/bYuMew/V8qXW1KZvOIIKx/3q3JX9qq07BxB0pYc0o/ks21FGrdO7tjgPl1rNBp6evekZ1RP4HzF8pJ0DuQcYH/Ofg7kHCCpIIkMUwaHig6BY5AYhUxBq4BWtA9uT4fgDrQPbk+EZ8MdPViTtoZDeYfQKrQ81vExt7VjMljY/stJALrd3MxZW6u2zFYbk5buobDUTLsmvrx8cytXdtMtJEni5vbhDGwVwud/n+TjjSkknirgtvlbuKtzU54fEk+Ij/iwW5laB0pTp07lqaeeYt++ffTu3RuALVu2sGjRIubOnevyDgruUV1+ks1mJ/1Ief2kygOl7BIDu86vphCBklDXXhqWwPGsYrYk5zHhq9388kRf/GuxmECSJK4f0ZJvX9vJmWMFJO/OJq5bw54+liSJaJ9oon2iGd7cMbJfai7lcN5hZ/C0P2c/+YZ8DuYe5GDuQb45+g0AQR5BtA9qT4eQDrQPan/Javx1yWQ1MXeP471jbNuxBHkEua2tvX+mU1ZswjfYg7b9Ln9l2vtrjrMnvRBvjYL5D3RGrWg8H1Q0SjlPDozjnq6RvLf6GCv3nuWHPWdYfSiDxwe0YHzfZg0iz6ohqXWg9NhjjxEWFsbMmTOdVblbtWrFd999x2233ebyDgquV2Qs4kSBYwPIqgKl7LRijHoLaq2CsGaVf1JfczgLux06RvoR4Vf/L7jCtUUhl/HRiM7cOn8zp/PLeGLpHr4a1x1FFaszK+MbrKXL0Gh2/prK5u9PENU2ELVH/ecr1YZWqaVbWDdn8rPdbues7myFUadj+cfILctl/en1rD+9HgC5JKelf0vniFOH4A5EekfW+ajTt8e+5azuLCEeIYxqPcpt7ZTkG9j7VzoAve9qcdk1tP46ksVnfztGpd6/uwNRgZef41Sfwnw1zLqvoyN/6bcj7E0v5P01x1m2K52XhrZiSNuwBjsCWdcu6xXhjjvu4I477nB1X4Q6sidrD3bsNPNtVuWnt/Jpt6YJAciqeOMRe7sJ9c3fU8Xno7py58db2ZqSx1t/HOU/w2u3bUTnm6JJ2plFYVYpO34+yfX3t3RTb+uGJEk09W5KU++mDIsdBoDBYuBo/lH2Z+/nQO4B9mfvJ7ssm6P5Rzmaf5Rlx5cB4K/2rzBd1y6oHVql+wKBImMRnx34DIBJnSa5ta3tP6dgNduIiPOjWYfLG7U6nV/K1PN5SeP6NLsqRtI7Rfnzw6O9+WX/Of676hin88t47Js99GgWwKvDW9MmovIc1mtJ4/roJLhEeX5SzbYtqXzaLV9vYvtJx7TbUFEWwMFuB4sBDEVQVuj413krPH8rArMBtIHgFQxeoeAZ8u/Xqoa1aqYxSAjzYda9HXj06z0s3JJGq3Af7u1a83xJuVLG9SNa8sucfRzadIaEXmGERLtm1/GGQqPQ0Cmkk7MCv91uJ6s0i305+5wjT0fzjlJgLGDTmU1sOrMJAJkko4VfiwqjTtE+0S4rT/DZgc8oNhUT5x/Hrc1vdck1K5N9qpikHVkA9Lm7xWWNlJgsNiZ9u5dig4UOkX68MDTB1d2sNzKZxO2dmnBTm1A+3ZjC//19kh2p+dwybzP3d4tk6k3xBHm5r0J6Q1ejQMnf37/Gv1j5+aICaENX3Ua4pcUmsk85VtZEtal8f7e/jmRitdlpE+HTaIeeK2UxVR3gXHirNBAqgivdw0vpCV4h/948QxwBlFfwxV+rrqKf+xUa0jacyQPjmLvuBK/8eIgWIV50jqp5EcHIhADiuoVyYlcWm5Ye567nu17V2z5IkkSYZxhDPIcwJMZR/dpkNXE0/2iFKbsMfQZJBUkkFSTxfdL3APiofGgf3N4ROAV1oF1wO7xV3rXuw5mSM3x77FsApnSZ4rYSB3a7nc3fO1IN4ntcfhD8zqqj7D9diK+HkvkPdEJVh9vf1BWtSsGUm+K5t1sk/111jN8OZPDtztP8tj+DpwbGMbp3zFX5vKtTo0Bpzpw5bu6GUFd0Jh1H848CVY8olSdxB0V64elb+aeIBru3m816cVDzv4FOVUGOoQjMLth5W5KBxreSm5/jX4UGSvNAnwO6LNBlO26WMjDroSDVcauOyruSoKqKAEt59a9mmTwwjqMZxfx5JItHlyTy65N9Ca3FKp4+d7fg1KE8sk+VcPjvs7Tr39SNvW14VHIVHYI70CG4AyMZCUB2aTYHcg44g6fDeYcpNhWz+exmNp/dDDjqOsX6xjqTxDsEdyDWL7baUacP93yI2WamV3gv+kT0cdvzSt2XS0ZyEQqljB63XV7ZgdWHMli4JQ2Amfd0oKn/1f0hpam/lo8e6Mzo3vm8/usRDp4t4q0/jrJ0ZzovD2vFwFYh11T+kmS/FvY/qKXi4mJ8fX0pKirCx+fqGoLffHYzj619jKZeTVl116pKj/lzwSFO7M6my9Boet52cdXaojIzXd/8C7PVztop/WgR4sLNH+12MJZcXpBjKAJjsWv6ofapPMgpv3n4Vf24ygtqW5DPbgeT7t+gSZ/979e6rAuCqvP/Wmu5T5na94IgKvjfIMo59XdBgKVovNvQ6IwW7vx4C0lZOjpG+rFsYs9areA5uPEMfy9LQqWR88BrPav8oHCtMtvMJOUnOVfXHcg5wBndmYuO81J60S6oXYV8pwvrtR3MOcgDfzyAhMT3w78nPiDeLf21WmwsfW0HxTlldB0WQ49bax8opeeVcvO8fygxWJh4fSwvDWv4pQBcyWazs2LPGd5bfZxcneN157q4IKbf0pqWobUfSXQ3d7x/X1aOks1mIzk5mezs7Iv2/rn++utd0jHBPXZnXjo/yWa1OQpNUnU17nVHszBb7bQM9ap9kHTsDzi1pepAx1AErtgYVKm9vCBH4+sIkuR1nL4nSaD2dtwCq9lSwW53BITlQdOlgip9tmM60FjkuOWdqL4vGr/zgVNoNUFVMMgvrw6Nu3ipFXw+qiu3frSFfacLefnHQ3xwT/saf/ptc30Tjm3LIPtUCVtWJHPT+Nolhl/tlDIlbYLa0CaoDQ+0cuzCkFuWy8Gcg47AKfcAh3IPoTPr2JaxjW0Z25znxvjEOAOnX1N+BWB48+FuC5LAEfgW55Sh9VHR6aaoWp9vtFh5YukeSgwWukT78+xg9/W1oZLJJO7tGsnQtmHM35DCl5tT+edELkPn/sODPaJ4ZlDLWpXlaIxq/W6wfft2HnjgAU6dOnXRZpySJGG1Xt6u3kLdcBaaDKs8PykrrQRjqaMsQGgVZQEue2+3/FT47sGaBUIy5fmAxq+aIKc80Pmf+xvxqEi1JOnf5xnU4tLH2u2OYLTSoOp/Rq702WCz/JuXlZtUfV88AqrPp/IKBW1QnQWf0YGezH+gM6O+3MEPe87QJsKHcX2b1ehcmUyi3wPxrPjvbk7syqJVn3AiEyrP0xMcgjyCGBA1gAFRAwCw2CwkFyb/u8IuZz+nik+RVpxGWnEav6T8AoBarubJTk+6rV8GvZndf6QB0OO2WFSa2v/+vfX7UQ6eLcJfq2TeiE4oa1F64mrjrVHywtAERnSP5O0/jrLmcBZfbTvFz/vO8cygOB7sGX3V/nxq/Zvz6KOP0rVrV37//XfCw8OvqXnKxq7MUsbh3MNA1SNKpw7lAhDZuvKyADqjhU1JOcBl5CftWewIkkLaQNs7Kg9wygMhhcYREAhXRpLAw99xC65m2bvNdj6o+t+RqeyL86n0OWC3Qlm+45ZzrLqOnF/pdz6oancPdHrIVc/yIn3jgnhpWCve/P0ob/1xlJah3vSNq9mS8JBoH9r2a+qYhvs2iftf6Y5ceXW+AbiDQqYgISCBhIAE7uM+AAoNhc6g6UDOAZILkxnfdjxhnu7Lcdz1eyrGUguBTbxI6FX7lbm/HTjHV9tOATDrvo6uqxVnt8P2T6D4LPSaBD6Na9VwdKAn/zeyK1tTcnn91yMcyyxhxq9H+HpHOtNvaU2/lsH13UWXq3WgdOLECVasWHHRFiZCw7c/Zz8Wu4VQbShNvSpPVE0/fOlptw3HsjFZbDQL8iQhrBbz0xYT7P3a8XX/F6C1+5YCC5dJJgNtgOMWUs3SZ5vNESBVCKqqyKcqzXUEyKW5jlv2ETi5EQpOwYCX3BYQj+/bjCMZxazcc5ZJ3+7hlyf61niFZo/bYknZk01hVil7/jxFt5trNiIlVM5P48f1Ta/n+qZ1k5pRmFXKoY1nAUeSfm1XMKbm6nnhh4MAPNa/OQPia7+XYJV2fg5rXnR8vftL6P0k9H4K1C7M9awDvZsH8ftT17FsVzoz/0wiOVvH6C93ckNCCK/c3IrY4Mb1fC6l1oFSjx49SE5OFoFSI3ThtiWVjQTqi4zkpJeXBag8ULpwb7dajSYe/93xBuoVCvFDa9lzocGRycAzyHELbX3pY21Wxyq/8um9kxthy1z4+z1HTtqQ/9Y++b0GJEni7TvakZKtY/+ZIiZ8tZuVj/fGU139y57aQ0Hfe+L484vDJK46RcvuofgGX90rna4mW1cmY7PZiW4bSGSr2k2dGsxWHv9mDzqjhe4xAUy90YUFSFPWw+oXHF8HxEL+Sdj0Luxe6PjQ0Glk3edHXgG5TOLBHtHc0j6CeetOsGhrGuuPZfN3Ug6je8fw1MA4fD0aVh7j5aj1q9OTTz7J1KlTWbRoEYmJiRw4cKDCTWi4yhO5q8pPKh9NCo7yRutzcY5PmcnKhuPZwGVMu+1e6Pi308gGlwAsuJlM7phuC2sLzW+AG1+HYY6d4tn5f/DzE2C1uKVpjVLO/43sSrC3muNZJUxZvg+brWYLfVt0DaFpgj9Wi42/lyVdlJMpNExnkwpI3Z+LJJPofWftP9C/9usRjmYUE+ip4sMRnWq1Jc4l5Z6A5WMcU9YdHoAn98C9XzkCJn02/PY0fNoHktY4pucaEV8PJa/c0po1z1zPDQkhWGx2vticyoAPNvLNjlNYa/g311DV+jfgrrvu4ujRo4wbN45u3brRsWNHOnXq5PxXaJhMVhMHchyBbJX1k6qpxr0pKYdSk5Umfh60a1KLsvZ5KZC6CZCgy+ha9Vu4SnWfAHf8H0hy2L8UVowBSy1LHtRQmK+GTx/qgkouY83hLOatT67ReZIk0W9EPDKFRPrhfFL25Lilf4Lr2G12tqxw/P+2uS6CgIjaVbr/ed9Zvt2ZjiTBnPs7Eubrovpjpfmw9D7HytPInjB8jmPKufVt8PgOGPKuY2FEzjFYei98dSuc2+eatutQ82AvvhzTjcXjutMixIt8vYmXfzzEzR/+w9aU3Pru3mWrdaCUmpp60e3kyZPOf4WG6WDuQUw2EwGaAJr5XJxvYbPaOH30fH5SFYHShXu71WraLXGR498Wg8Cv9kt0hatUh/sdn6jlKjj6K3x7P5j0bmmqS7Q/b97RFoDZa5NYczizRuf5hWrpPDgagM3LkzCVuWfkS3CN4zszyUkvQaWR0/2W2uWVJWfreHGlIy/pyQEtuC7ORUnJVjN8PwbyU8A3Cu77GhQX1OdSqKDno/DUXugzGeRqSP0bPusHKx+BwtOu6Ucd6tcymFWTr2PG8Nb4eig5llnCA5/v4JElu0nPc0FR3zpW60ApOjr6kjehYaouPykztdhRFsBTQUjMxWUBjBYr646en3ZrV4tpN4sR9n3j+Lrr2Np3XLi6tboFHljuqHuVsh6W3OEoKuoG93aNZEzvGACmfLeP45klNTqvy5BofII90BeZ2PlrDSqmC/XCbLKy/SfHh/UuQ2Pw8K55iZAyk5UnvtlDqclKr9hAJg9yYV7S6hccI+pKTxjxraNkRmU8/BzT0k/uhnb3Ou47sAzmdYG1Mxz5fI2IUi5jTJ9mbJzWn9G9opHLJNYczmLQrE28u/oYOmPj+dBR60ApKiqKUaNG8cUXX5CSkuKOPglu4MxPqmJ/t/RDjmm3qNaBla4Q2ZKcS4nRQqiPmk6RNd9Di6O/OhJ5vSMgbnDtOy5c/ZoPgFE/O8pCnN4Bi29xrJhzg5dvbkWv2ED0JisTvtpNYWn1e/MplHL6jXC8cR7YcJqc0zULsIS6te+vdPSFRrwDNLS/oXbbz/znl0MczyohyEvN3BEdkbtqn7+dn8OuBYAEd33uyNOrjl+U49gJGyC6r6MK/+bZ8GEn2PGZY4SqEfH3VPHabW1ZNfk6rosLwmS18cnGFPq/v5GVey6u6t4Q1TpQevvtt9FoNLz77rvExcURGRnJQw89xOeff86JEzWo+ivUObPNzL6cfUDVidynyvOTqtgEd9XB86vd2oTVbqlt+bRb58a1mkOoY5HdYczvjmrfmQdh4RAocv2LqFIuY/6DnWnq70F6fimTlu7FYq2+AGpU60BadAnBbodNS49jb+TJqVcbfZGRPX+mA9DrzuYoarFtzQ+JZ1i++wwyCT68vyMh3i7KSzq5EVY97/h64KuQcHPtzm/SGcb8BiOWQVBLxwfOVc/Cxz3h6G+NLuG7Zag3X43rzoJRXYkJ1JKrM3I8q3F86Kh1oPTQQw/x2WefkZSUxNmzZ3n//fcBePzxx0lIqKb2ilAvjuYdpcxShq/alxZ+F68C0RcZyT2tAyCy9cX5SWarjb+OZgG1rMadewLS/nFsEtt51OV1Xrh2hLWDsavBpynkJcOXQxwLAVwswFPF56O64qGUszk5l/+uqq5YpkPfe+JQauRkpRZzePM5l/dLuHw7fjmJxWgltJkPLbrUvObRiawSXvnpEACTB7akd4uaFSWtVl4KLB/tWOHW/n7o+8zlXUeSHOVUHtsGN89yfJDIS3bscLBwKJzZ7Zr+1hFJkhjUOpQ/n+nHf4a3ZtKAxlFm6LLWPZaWlvLnn38yb9485s6dy4oVK2jbti1PPfWUq/snuEB5flLnkM6V7uhdvtotJLrysgDbT+ZRWGom0FNF92a1qElSPpoUdxP4Xls7sQuXKagFjFsNgS2g6LQjWMo85PJmWoX7MOveDgAs2JzKD4nVj155+qmdm6pu/ymF0uLqp+0E98s9U8LRrY6FJn3viavxQpNSk4XHvtlDmdlK3xZBTLrBRW/aZYWOFW6GQmjaDYbPvfKiqnIFdBvvSPi+/llQeED6NlgwEL4fCwVpLuh43VEpZIzt0wxvTeMoFVPrQKl3794EBgbywgsvYDAYeOGFF8jIyGDv3r3Mnj27Vtf6+++/GT58OBEREUiSxE8//VTtORs3bqRz586o1WpatGjBokWLLjpm/vz5xMTEoNFo6NGjBzt37qxVv6425fu7Vb1tyfn8pCpWu5Xv7XZTm7Caz92bDf8mcXcRSdxCLfhFwthVENrOUV9m0TA4vcvlzQxtF85T598cX/zxIPtOF1Z7Trt+TQiK9MJYamHrypqVGRDcx24/Xw7A7qh7FRZbs7IldrudV348RHK2jhBvNXPud1FektUCK8Y6Np/2aQr3LwWli6bywLFp9g2vwJOJ0PEhQILDK+GjbrDmZUcZAsHlah0oHTt2DE9PTxISEkhISKBVq1b4+9ciufcCer2eDh06MH/+/Bodn5qays0338yAAQPYt28fTz/9NA8//DBr1qxxHvPdd98xZcoU/vOf/7Bnzx46dOjA4MGDyc7Ovqw+NnZWm5U9WXuAyvOTrFYbp48WAJVvW2K12fnz/FLqWhWZPPoLlBU4XizibryMngvXNK8QGPMrNO3uWO3z1W2QssHlzTw9qCWDWoVisth4ZMlusosNlzxeJpfR/4EEkOD49kzOHi9weZ+Emjt1KI8zxwqQKSR63d68xuct332alXvPOvKSRnQiyEtd/Uk1seYlx+pNpfb8CjcXbn1yId8mcPt8ePQfiB0AVhNs+8iR8L31I7fVJLtW1TpQysvLY/369fTs2ZM1a9bQp08fmjRpwgMPPMDnn39eq2sNHTqUN998kzvuuKNGx3/66ac0a9aMmTNn0qpVKyZNmsTdd99dYSRr1qxZTJgwgbFjx9K6dWs+/fRTtFotX375Za36drVIKkhCZ9bhqfQk3j/+osezThZhKrOg8VRWWhZgV1o+uToTvh5KejWvfMSpUuWVuDuPclRmFoTa8vCHUT853gjMekchvmO/u7QJmUxi9n0diAvxIqvYyCNfJ2K0WC95TmgzH9pc1wSATd8ex2qpPhlccD2r1cbWHxyjeh1uiMQnqGab1h7NKObVnx2bg0+9KZ6esbV4XbuU3V86Ks2Do5hqeHvXXPdSwto5/kYe+gFCWjum+/582THCdGhlo0v4bqhqHShJkkT79u156qmnWLFiBatWreLGG2/k+++/59FHH3VHH522bdvGoEGDKtw3ePBgtm3bBoDJZCIxMbHCMTKZjEGDBjmPqYzRaKS4uLjC7WpRPu3WKaQTCtnFq85OHXIM1Ua2Dqh0NVv53m43tg5FWdNS/tnHIH2ro+py55GX2XNBAFSe8MB3kHCL41PzdyNh/3cubcJbo+TzUV3x0SjYm17IKz8eqna7kp63xeLhraQgs5R9a9Nd2h+hZo78c46CzFI0Xkq6DI2p0Tk6o4UnvtmD0WKjf3wwj/Wr+SjUJaX+DX886/j6hlfqftPvFoPg0c1w60fgFQaFpxxTgF/cCOnb67YvV6FaB0p79uxh1qxZ3HrrrQQGBtKrVy8OHDjAk08+ycqVK93RR6fMzExCQ0Mr3BcaGkpxcTFlZWXk5uZitVorPSYzs+pKvO+88w6+vr7OW2RkpFv6Xx8uLDRZmVOX2LbEZrM7A6VaTbuVJ3G3HAI+ETU/TxAqo1DDPYuhwwjHKqIfJzrq07hQTJAn8x7ojEyC7xPPsHhr2iWP13gq6XN3HAC7f0+jOLfMpf0RLs1YZmHnb47in91vaYbao/rSI3a7nZdWHuRkrp5wXw2z7u1Yu1InVclLgeWjwGaBdvfAddOu/JqXQ3b+g+lTe2DAy44Cl2d2wZeD4buH3LKC9FpR60Cpe/fufPvtt7Rs2ZLFixeTm5vrDJ5uu+02d/TR7V588UWKioqct9OnG1/J+MrY7DZnoFRZoUldgZG8M7r/Z++8w6Mouz58b8lueq+QhN47oQvSqwqoKIhKR8WCihUbKr4fVmyAIFJVUEFEVEA6giAt9F5TIL1tskm2zvfHJJuEJJCQsinPfV17ZXZmduY8ky2/Oec854ACQouon3Q0KpVYXTauWjU9m5Rw2qwpS+7dBaISt6D8UKlhxALo8qT8fOPLsOezcj1F76Z+zBzaAoDZf529bW+qpl0CqNvME7PJyj8/i6a5lcmRTdfIzjDhFehMq14luxlbdTCSDcdvoFIq+PqRDni7lLxyd7Fkp8HqR+R8zLphMPzrss9wKysaF+j9qjxDLmyCXJ7l7B8wvwtsfBX0Sfa1rxpSaqGUnJzMoUOH+PTTT7nvvvvw8ChFc9QyEhgYSFxcXIF1cXFxuLu74+TkhK+vLyqVqsh9AgOL94hotVrc3d0LPGoCV1KvkGpIxVHlSCufVoW2R57JLQvgjpNr4S+N3N5u/Vv4o1WXMM/o9Hr5y8MjVO4ULxCUF0olDP1Inh4NsP192DqrXPMwpvRqwP0d6mKxSjzzYzhRycX3pbI1zVUpiDiZxNVj1bfpZ3VCl5jF8R3yzWyPBxujLEFKwKnrabz3xxkAXh3cjE71S1HmpDgsZlg7CRLPy50HxqwCh5LlSVUKbgFyaYJp++SuCFaznEP1VXu50rdJeEFLSqmFUq6IOHLkCD/88AM//PAD4eHh5W5YUXTv3p3t27cXWLd161a6d+8OgEajISwsrMA+VquV7du32/apTeTmJ7Xzb4eDqnC9ity2JUWF3SRJspUFKF3YLSeJO0wkcQsqAIVCzgEZOFt+/u8X8OeLYL11AnbJD69gzgNtaBvsQUqmiakrD6O/RU8qr0AXOgySGz3v+eUCxuzq07+qurJ//WWsZong5l7FNvDOT3q2iWdXhWM0W+nf3J+pvRqWjyFb34ZL2+SaRo+sBrdSfE9WJv4t4NFfYNwGCGwLBp3cO+7rTnK+n1VMRrgdpRZK8fHx9O3bl86dOzN9+nSmT59Op06d6N+/PwkJpevPlJGRwbFjxzh27BggT/8/duwYkZFycuTMmTMZNy6vovNTTz3FlStXePXVVzl37hwLFizgl19+4cUX86qezpgxg8WLF7NixQrOnj3LtGnT0Ov1TJxY+8JAt8pPkssCyIncRZUFOHVdR3RKFk4OKno3LeEU17gzcq8upRo6iCRuQQVy13T5bhmFLM5/e7LcemA5OqhY9HgYvq5azsWm8/Ka47cMq3UaWh93X0cyUgwc+utaudggKJrYK2lcOhwPCrhrVOPbFpeUJInXfz3JtaRM6no68dnD7conL+nICvhvgbx8/0Ko077sx6xoGvaGJ3bLM/Lcg0EXLef7Le4jJ6MLiqXUzbeee+45MjIyOH36NC1ayPH8M2fOMH78eKZPn87q1atLfKzDhw/Tt29f2/MZM2YAMH78eJYvX05MTIxNNAE0aNCAv/76ixdffJEvv/yS4OBgvvvuOwYPzmu2Onr0aBISEnjnnXeIjY2lffv2bN68uVCCd01HkiSbR6mo/KTYy2kYsy04uTngX8+t0PZNOWG3vs39cNKU0DOU601qNrTq3l2VI5LVijU9HUtaWs5DhyUtFUtaGladDktqGhadLmdbKtY0eVkyGHCoXw/Hpk3RNmmKtmkTtE2bovYuh3BAbSJsAmhcZZF0cg0Y0uGh5eUS/gjycGLR4x0Z8+1/bDoVy7wdl3iuf5Mi91VrVPQa3ZS/5p/g+PYomnUNxDfYtcw2CAoiSRJ718j9RFv0CMI3uPD31s18/18Ef52MQa1U8PXYDng6l0Ne0rV/4a+X5OU+b0CrkWU/ZmWhVEK7MdByBPz3jRyCizkOK+6TJ98MfB/8CpeRqe0opFJmIHp4eLBt2zY6d+5cYP3BgwcZNGgQqamp5WmfXdDpdHh4eJCWllZt85UidBHc+9u9OCgd2PfIPhzVBavD7lt3iaNbImnaNYCBEwvmL0mSRL/PdnM1Uc9Xj3RgeLsSJEsaM+Gz5mBIg8fWQeP+5TmcCsWanW0TO9a0HHGTmiN+dPnWp+nyRJFOh1WnK9f8GJWvL9omjWUB1bQp2iZN0DZujNLZudzOUSO58Lc868icDfV7yWEQ7e1/REvCTwcjeX3dSQC+fTyMQa2KvwHYtOgkV44mENjQgwde7oiivDrQCwC4eDiOLd+dRq1V8dj73XDxuHWRyBPRqTz4zT5MFom37mnBlPIIuSVfhcX9ICsZWj0Ao5baP3m7LOgTYfdHcg0oqzmnpMs46PtGxRXLrGAq4ve71B4lq9WKg0PhfBcHBwesItZZZTgcK3uT2vi2KSSSIK+/W1Fht/Nx6VxN1KNRK+nXvIQfltPrZJHkVV8uEFjJSBZLnnfnJqFTWOTkrMvx+EiGslWxVTg7o/LwkB/u7qg8PFB6uOes85TXecrble7uKNRqjFeukH3hAoYLFzFcvIgpKgpLYiKZiYlk7s9X90ShwCEkRBZNTZvkeKGaoKlfH4W61B/fmknTwXLBvVWj5SbMK4bLz53L7qEb0yWUMzE6Vu6P4MWfj7H+mbtoElC0COv1cBOiziQTeyWNs/tjaHmXKI1RXphNFvavk6e3dxwUeluRlJZl4plV4ZgsEoNaBjC5Z4OyG5Gtg9VjZJFUpwOMXFC9RRKAiy8M+0SeTbptFpz7U44MnFwDdz0P3Z+RZ9HVckr9TduvXz+ef/55Vq9eTZ068hfB9evXefHFF+nfv/p4EWo6trIARbQtyUjJJum6Xi4L0LKwUNp0Uk7ivruJH67aEr5FbJW4x8vu3TvE5t1JTcOqS7sprJUTwioU1krDmp5eNu+OSmUTOXlCxzNP/HjKIscmfnKFkLs7Ck3p3fmOzZrhPnRo3rgzMzFcuoTh4kUMFy7IIuriJSyJiZgiIzFFRpKRb5KCwsEBTaNGctiuSRObF0odGFjipqA1ivo9YfwG+OFBuBEOy++Bx38rlxDw2/e25HxsOgeuJjN15WF+f6YnHs6FbxZdvRzpcl8D/l17iX3rLtGgnW+Rs0kFpefEjmjSk7Nx8dTSfmDoLfeVJIlX1x4nKjmLYC8nPhnVruyfCasFfp0MCefALQjGrK5aM9zKim9jGPMjROyDLW/B9SOw83+yp6nfW3INs1o8OafUQmnevHkMHz6c+vXr2wozRkVF0bp1a3744YdyN1BwZ9yqEW5uE9yA+u44uhb+wi91kcnYk3D9cE4S92O33DV9+3YyDx7K8/zcnL9jLFtHdqWzM8pc704+kSN7c/KtzxE6SncPWQS5uNhVYCidnXFq2xantgXbHpiTkzHYPE95AkrKzMRw7hyGc+cKHsfNzeZ9yi+gVJVYxsNu1A2Tm+muHAnxZ2DpEBj3O3jVK9NhHVRKFjzakeHz/uVaUibPrg5n2YTOqIuYlt62bzDn9seSdD2Dfesu039cizKdWwBZ6UaObLoGQLeRDXG4Tc7k0n+v8ffpODQ5/7eiRG2p2foOXNwCakdZULgHlf2YVZF6PWDKdjlCsO1dSI2E35+B/Qtg0OxqlVJRnpRaKIWEhBAeHs62bds4l/Ml3aJFi0KtRQT243rGdWL0MagVatr7tS+0PfJ0zmy3IqbWXk7I4HxcOmqlggEtSpgAn+tNan7vLePahitXiH72udt7flSqAkJH6emBqiiR45GzPkcIqdzc7si7U5VRe3uj7tYNl27dbOskqxXTjRt5AurCBQwXL2C4eg1rejpZ4eFk3VSyQ+3vL+c95eY+NW2CtlEjlI7l2Nm8KuDfAiZtlpvoplzNEUvry5yg6uOq5dtxYYz6Zj97Liby8d/neWNYYRGkVCnpPbYZ6z45wrl9MbToEUSdxp5lOndt5+CfVzFmW/ALdaNZl1vfvB2NTGHOxrMAvHlPC9oGe5bdgKM/yA1nQQ631S26y0GNQaGA1g/K3+cHF8M/H0P8afjhAbk23sDZENja3lZWKneU5KBQKBg4cCADB4qu8FWR3LBbS5+WODsUTAS2mPOVBShCKOV6k+5q7FuyOzFDBpz4RV6+TSXu5GXLQJJwbNMGt0ED88SPZ/7cHk+ULs61M3xUQhRKJZrgYDTBwbj1yyvqKRmNGK5eyxFOOQLqwgVMN25gjo/HHB+Pfu/evAMplWhCQwsJKE1oKApVNXazezeQxdL398uhkmVD5QkGZZzC3aqOB58+1I5nVoXz7T9XaBHkxv0dggvtF9TIg5Y963Bm7w12rzrPw292RlXSPomCAiTf0HN6zw0gpxzALRLkUzONPLvqKGarxD1tghjXvWyeRAAi9sMfL8jLvV+TBURtQa2FHs9C+7FyFfwDi+DyDri8E9o/Cv3erDUtqkoklL766qsSH3D69Ol3bIygfMhN5A4LLHznE3M5DZNBLgvgF1J8WYASh91O/QrGdPBuBPXvLnY3U3w8aet/ByBg5kycO3Yo2fEFJUah0eDYrCmOzZoWWG/JyMgRThcLCChLairGa9cwXrtG+pYtecfRatE2anSTgGqK2t+v+ghY9zowYSP8+CDcOCpPfx77sxxaKAP3tA3ibExj5u28xGu/nqSRn2uRXovu9zfiyrEEkm/oOb49io6DyuFHuxayb90lJKtEg3a+1G3qVex+kiTx8prjXE/Nop6PM3MebFP292pKBPz8KFhN8nT63q+X7XjVFWdvGPw/6DxFroZ/eh0c+0H+7u/xnFzTrJxmmVZVSiSUPv/88wLPExISyMzMxNPTE4DU1FScnZ3x9/cXQqkKcKv+brnVuENb+RS6O4tKzuTUdR1KBQxsWcKwm60S94RbJnGn/PAjksmEU8eOQiRVMipXV5w7dMC5Q951lyQJS2JigZl3hgsXMFy6hJSdTfaZM2SfOVPwOB4eNtEkP+Q8KJVbFf2SdPGRqxGvHgMR/8L3D8DoH6BJ2dIEZgxsytkYHdvPxfPEyiNseO4u/N0KhjAdXRzo8UBjdqw8y6E/r9I4zB93nxqU/FsJRJ1JJuJUEkqlgh4PNL7lvov3XGHb2Xg0aiXzx3bE3bGMeUmGdPl9k5kEQe1g5MIyTVKpEXg3gIeWyTPhtrwFkfvlsNyR5dB3JnQYJ/dkrIGUaFRXr161La9atYoFCxawZMkSmjWT4/7nz59n6tSpPPnkkxVjpaDExGfGE5keiQIFHfwLC5KIW5QFyPUmdWvog4/rraffAnDjmHy3rtLIrthisGToSckpROozeVIJRiGoaBQKBWo/P1z9/HC96y7besliwRQdnSOg5MRxw4ULGK9dw5KWRubhw2QePlzgWOo6QQUSx7VNmqBp2BBlVcgXc3SXSwX8Mk5Oxl09Bh5cDK3uv+NDKpUKPh/Tnvvn/8vlBD3Tfghn1dSuhfohNu8eyLn9Mdy4mMreXy4ybFrbYo4ouBmrVeLfX+Xikq371MUzoPhaYkcikvlo83kA3rm3Ja3rlnHigtUCv06VJwS4Bsgz3DSilpmN4E7ypIlzf8pJ7slX5DZC/y2UC1Y2HVz9yybcRKnl39tvv83atWttIgmgWbNmfP7554waNYpHHy3+B1NQ8eR6k5p7N8dNU/BOPz05m+QbehQKCGlZuMZMqXu75XqTWgyX796LIXXNGqzp6WgaNMC1b+XXWBKUHIVKhaZePTT16kG+HESrwYDxypV8pQvkUJ45NhbzjRjMN2LQ787XBkGtRlO/XiEB5RAcjKKy78wdnGD0j3IF79Pr5EamhgzoeOdtdtwdHVg8rhMj5v/LkYgUZv1+mjkPFAz3KBQK7n6kKb98cIirxxO5eiKRBm19y2NENZ5z+2NIuq5H66ym8z3F10BK1st5SRarxH3t6vBo11uXDigR29+DC5tApZUb3XrULfsxaxoKBbS4T262e2QZ7PpQbg68erRc9HXQB9WjrUsJKbVQiomJwWwu3PjRYrEQFxdXLkYJ7hxbflIRZQFyi0wGNPDA0aWgazomLYujkakoFDD4FtWHbWTr4MQaefkWSdySyUTyihWA7E2q9B9JQbmg1GpxbNECxxYtyH+/bklLk+s/3SSgrDodxkuXMV66TPqmzbb9Fc7OaBs3xuvhh/AcNaryBqDWwIPfgdYVwlfChmfl8Er3p+/4kA39XPn6kQ5MWn6Inw5F0aqOO493r19gH586rrQfGEL435Hs+ekCwc28cNBW40T5SsCYbebA71cA6DSsfqHvqlysVokXfz5GTFo2DX1dCgnVO+LYKvj3S3l5xHzZe1IB5DbEqDY5f8Wh1kDXJ+W2KHvmym1Rru2Bb3tD29HQ723wDLG3lWWm1EKpf//+PPnkk3z33Xd07NgRgCNHjjBt2jRRIqAKcKv8pNz6SfVaF/Ym5c5261TPC3/3EkwZP7kGTHrwbQr17ip2N93GjZhjY1H5+eI+fHhJhiCoRqg8PHAOC8M5LE+YS5KEOS6uQOJ49sWLGC9dRsrMJPvECWJOnMCq1+M9fnzlGatUwX1fgdZdnu7990zIToM+r99xqKBPM39eG9KcOZvO8d4fZ2js70b3RgW9q52GNeDioXjSk7M5vPEq3e+/db5NbefolkgydUY8/Jxo06fwrMJcvtl9md0XEtCqlcx/tGPJi+MWR+QB+ON5ebnXy9D2obIdrxgsOh2RU6ZiunED70fH4vXII6hy8n2rLY4eMPA96DwZdnwAJ36WH6fXQ7dp0GuGvE81pdS390uXLiUwMJBOnTqh1WrRarV06dKFgIAAvvvuu4qwUVBCkrOTuZyWU+Y/oGOBbRaTlehzKYCcyH0zuWG3Ia1LUEhNkgomcRfzIyNJEklLlgLg/fi4qpGzIqhwFAoFDoGBuPbqhc/kydT56CMarltHs/AjNNz4F945eWpxcz605a5VonFyWKDvW/Lz3R/C329AGdovPXF3Q0a0r4PZKvHMqnCikjMLbHfQqug1Rp6JeGxrFEk3Mu74XDWd9ORsjm2VG6H3eKAxKnXRP1EHriTx2RY5L+m94a1oEVTGnl6pkfIMN4tRrh/U982yHa8YrAYD0c88S/aJE1gSE0n48isu9utP3JwPMcXEVMg5KxXPUHjgW3hilxyCsxjg3y/gy/ZyeQGLyc4G3hmlFkp+fn5s3LiR8+fPs2bNGtasWcPZs2fZuHEj/v7Vs4leTSE8Ti4y2NizMV6OBafS3ricKpcFcNcUKguQkG7g0DW5ttKQkuQnXQ+Xq3GrtHJp+2LQ792L4cIFlM7OeI0ZXcrRCGoaCrUabcOG+L/8Mj5TpwIQ+977pK5dW8mGKKD3KzD0Y/n5fwtgw3NyEu8dHU7BRw+2pXVdd5L1Rp74/giZxoLpCQ3a+tKgnS9Wq8Q/qy9Qyl7ktYYDv1/BbLIS1NiDBu2LzudKzDDw3OqjWCW4v0NdRncuY2jHkAGrHwF9AgS0kX/oKyBFQLJaufHa62QeOoTS1ZWAma+jbd4cKTOT5BUruDRwEDden4nh0qVyP3elU6cDjP8Dxv4Cvs3k/nibXoX5XeHsH+XaTLwyuON3Q5MmTRg+fDjDhw+nadOmt3+BoMK5VduS3LIA9Vp6FyoL8PfpWCQJ2oV4UtezBFOYj8heIlqNvGXj0aTvlgDg+fDDqMqpi7Og+qNQKPCb8aIt7Bbz9jukbdhQ+YZ0fRJGfgMKpVwXZu1EMN9Zg2RHBxXfPt4JX1cNZ2N0vLLmRCEx1Gt0U9QaJTcupnL+v9jyGEGNIj5Cx/kD8nXp+VCTIvN3LDl5SfHpBhr7u/LByNZly/OxWuUk/7hT4OIPj6yukCawkiQR9+GHpG/eDA4OBM/7Gu/x42nw2zpCFn+Lc5cuYDaTtn49V+69j6hpT5N5U3X9aodCIc+Am7YP7v1cvr7Jl+Hnx+SK+dGHb3+MKoLIrK1B3DI/KadtSegtqnGXaLZbdhqcWicvhxWfxJ118hSZBw6AWo33+HG3P66gVqFQKPB//TW8xj4CksSN12ei27Sp8g1pPxYeWiGXuDjzu+xZMGbe/nVFUMfTiW8eC8NBpeCvkzEs2HW5wHY3b0fbDK69ay6y84dzHN0SydXjCaTE6rGY7zz8V92RJIl/18qelKZdA/CvV/SN1fydl9hzMREnBxULHu2IS1nzknbMlqe5qzRyD7cKSjxOXraclJXfA1BnzhxbSyKFQoFrr17UW7mC+r/8jNvAgaBQkLFzJxFjH+Xa2EdJ37ETqQyhYbujUkOnSTA9HO5+FdROEPUffNcfNlWPIp41szpULSTNkMb5ZDlm3ymwoFDSJWWREpNTFqBFQQ9Qit7I/iuyt6lEQunEL2DKBL/mENqt2N2SlsreJI97huEQVEMbSArKhEKhIOCtt5BMJlLXrOX6y6+gcHDArbInhbQcDtqf4adH4fJ2uafV2J/vKPm0c31v3hvemjd+O8mnW87TPNCN/vl6JrYbEMLFw3EkRmVwZu+NAq9VKMDN1wlPf2c8A3L/yg9XT+0t23dUd64eS+TGxVRUDkq6jWhU5D77LifyxbYLAMwe2ZqmAWUsdHr8Z9g7V14ePg9CupTteMWQ9tdfxH8sh3n9X30Vj3vvKXI/p7ZtCf76KwxXrpK8bClp638nKzyc6KefRtO4ET6Tp+Bxz7Dq289S6ya3Pek0EXb+D47+WG16xgmhVEM4Gn8UCYn67vXxdSoY289tghvYsHBZgK1n4rBYJVoEuVPP5zYuZ0nKa4AbNrHYJG5jVBTpf8stMbwnTb6D0QhqCwqlksD33kMyGkn7fQPRL84g+OuvcOvTp3INadQPHl8PPz4kVxxefi88/hu4lL7u0diuoZyJSeOH/yJ5/qdjrH+mB4395R91lUrJ/S915OrxRFLjM0mLyyQ1PovUuExMBgu6hCx0CVlEni54TJWDEk9/WTx5BDjnE1FOOLo4VOtp5hazlX3rZG9Sh4GhuHkXnnUbn57N9NXHsErwUFgwo8KKnw1XIqIOyXlpAD1fhHYVk0Op/+8/brw+EwCvcY/jPXHCbV+jbdiAoNmz8X3uOVJWriTlp58xXrpMzMyZJHz5Jd4TxuP10EMoXco/RFgpuNeRSy90fw58m9jbmhIhhFINITfsVlR+Um5ZgKLCbrnVuIeVxJsUfUjuIq12vOUXS/Ky5WC14tKrV6G+YwLBzSiUSoL+9z8kkwndxk1cn/48wd8sKFAxvFII7QoT/pSb6caekJvpPr7+jgoOvnNvKy7EZXDwajJTVx5h/TN34eEk36RoHNU061rw8yZJEpk6I6lxmfIjRzylxWeSlpCFxWQl6bqepOv6QufSOqtl0ZTjifLIFVH+ztWiZtOp3ddJS8jCyV1Dh0GFC0ZarBLPrz5GYoaBZgFuvD+ijF6ItGj4aaw8I6vZPdDvnbIdrxiyz58n+tnnwGTCbcgQAl5/vVSC1sHfX5748OSTpPz0E8krV2KOjSX+w49I/GYhXmMfwfuxx1D7FF/st0rj39zeFpSYUgul+vXrM2nSJCZMmEBoaDlUQRWUC8UVmrSYrESfl8sC3Ny2RJdtYu+lRACGtimBUMr1JrV6AJyKblBpTkkhdZ2cw+QzWXiTBCVDoVZT56OPkEwm0rduI/qZZwn5dhEuXSomHFIsQW1h0mZYORISL8hJp+PWg0/R4aDi0KiVLHi0IyPm/cvVRD3TVx9l6YTOqIoJnykUClw8tLh4aAs1f7VarKQnZ5Mal5UjojJtgiojxYAh00zcVR1xV3WFjuviqS0Yxsv56+briEpl/xTVbL2JQ3/JLbK6DW+IxrHwT9KX2y+y/0oSzhoV8x/tiJOmDOLPqJfb2OjjIaB1hc1wM924QdTUJ7BmZODcqRN1Pvrwjovtqtzc8J06Fe9x40j7/XeSlyzFGBFB0jcLSV66DM8HH8B74kQ0IdW/sGNVRSGVcp7qF198wfLlyzl16hR9+/Zl8uTJ3H///Wi1JegNVk3Q6XR4eHiQlpaGezWYraU36blr9V1YJAtbHtxCkGteTlDU2WQ2fHkMZw8NEz68q8AdzW9Ho3nx5+M09ndl24zetz5JVgp81hzM2TB5a7Hx/IR580mcNw/H1q2pv+aXah0SEFQ+ktFI9HPTydi9G4WzM6HfLca5Y8fbv7C8SY2ClSPkWTquAXIYLqBVqQ9z6noaoxbuI9tk5cneDZk5tEW5mmkyWkjL9T4l5AqoLFLjM8nOKL5mjUKpwN3XsYB48vR3wjPAGRePysuH2vvLRY7viMKnrgsPv9kF5U3n/edCAuOXHUSS4IvR7RnZoQztRKxWWDNOnp7u7AtP7JTr/pQzltRUrj36GMbLl9E2aUy9H35A5VF+xRYli4X0bdtJ+u47sk+elFcqlbgPGYLPlMk4tmxZbueqjlTE73ephVIu4eHhLF++nNWrV2OxWBg7diyTJk2yVeuuzlQ3ofTv9X95attT1HWty+YHNxfYtnfNRY5vj6J5jyD6jyv4Jf3EysNsORPH9H6NmTGoGbfkv4Ww+TXwbwXT/i0yP8malcWlvv2wpKZS9/O5uA8dWuaxCWofVoOB6KefQf/vvyhdXAhdthSntnZoKJsRL4fh4k6Bo6fcXPcOWlr8cfwGz60+CsCXY9ozon3l9A7L1psKeJ9yBVRafCZmY/GzqNQapRy+y00qzyemimsnciekxmWy+v0DWC0Sw6e3L9R/Mk6XzbAv95CkN/JIlxDmPFDG98COD+CfT+QZbuP/uOVklDvFajAQOWkyWUeOoA4IoP5PqytsMoskSWQeOEjSd9+h37vXtt7lrrvwmToF565da+WNakX8ft9xjlLHjh3p2LEjn332GQsWLOC1117jm2++oU2bNkyfPp2JEyfWyn+SPbhVflJuf7ebw256g5ndFxKAElTjzl+Ju1PxSdyp69ZhSU3FISREnuYqENwBSq2W4HlfE/XkU2QePEjklKnUW76s8u+UXf3lnKUfH5Lz81YMh7E/QYO7S3WY+9rV4UyMjm92XebVtSdo6OtKm+CKb+fg6OJAYAMPAhsUPJdkldCnGQrkQuUKKl1iNmajlaToDJKiC1cQd3RxsIXy8ieVe/g74VDKkNj+3y5jtUjUa+1TSCSZLVaeW3WUJL2RFkHuzLqv9N68ApxcK4skgPu+rBCRJFks3HjlVbKOHEHp5kbIt99W6IxfhUKBS7euuHTrSvbZsyR9twTdpk3o//0X/b//4ti6NT5TpuA2cAAKVdXPVavK3LFQMplM/PbbbyxbtoytW7fSrVs3Jk+eTHR0NG+88Qbbtm1j1apV5WmroBhyC03eXD9Jl5hFSmwmCqWCkBYF8x52no/HYLZSz8eZFkG3mWYb+R8knAMHZ2j7cJG7SGYzycvl5rfeE8ajUIt5AoI7R+nkRMg3C4ic+gRZ4eFETppM6IoVlT85wMlLTuj+aSxc3Q0/jIKHlkPzYaU6zMuDmnEuRsfO8wk88f1hNjzbEz83+6QrKJQKXL0ccfVyJPimfFqLxUp6YnbBXKh42RulTzWQrTcRe8VE7JXC+VCuXtoC3iePnFCeu48jypvyoa5fSOHKsQQUSgU9Hijc+27u1gscvJaMi0bF/LEdcHQoww999BFYn9P8uMd0uXZWOSNJEnFzPiR9yxYUDg4Ez5tXqe9VxxYtqPvZp/i9+ALJS5eRum4d2adOcf2FF9DUq4f3pEl4jByBsgalyFQmpf41Cw8PZ9myZaxevRqlUsm4ceP4/PPPad487xN3//3307lz53I1VFA02eZsTibKceqbPUq5s90CG7qjdS7oMt9kKzIZdHvPX643qfUDxdaWSd+6FVNUFCpPTzwfeKC0wxAICqF0cSHk20VETppM9okTRE6aRL3vV6Jt2LByDdG6yq0Y1k6C83/JlYXvX1SqpqkqpYIvH+nAyPn/ciVBz5SVh3m0ayiN/Fxp7OeKh3P5hbTKgkqltNVuuhljtpm0hLzZeLmhvNS4TAyZZjJSDGSkGGw9JXNRKhW4+zkVyIM69c91AFr1rIN3nYLT3Heej7cV6/zwwbY09HO98wHpbuTNcGs6BAa8e+fHugXJS5eS8sMPANT5+CNculbyJIQcNMHBBL7zNr7PPkPKDz+Q/OMqjBERxM6aRcLXX+M9bhxeY0aLTgmlpNRCqXPnzgwcOJBvvvmGkSNH4uBQ+APeoEEDxowZUy4GCm7NiYQTmK1m/J38CXErOOvBFna7qSxAtsnCznPxQAmKTGYmyx2gAcImFbmLJEm2diVejz2G0qkEbVAEghKgcnUldPG3REyciOHMWSLHT6DeD9+jqVevcg1xcISHV8Lvz8CJn2DdVDDo5G7pJcTd0YHF4zoxct6/HI9K5XhUqm2br6uWRn4uNPKXhVMjf1ca+blQx8OpUIKzvdA4qvELcSvUK1KSJDkfKt+svLRcT1S8XNogN08qPw6OKjrf26DAuhupWcz4+RgAj3erx33t6ty5wcZMudJ6Riz4tYAHFoOy/ENQaX/8QfwnnwLg//prVSI3U+3tjd/06fhMnkzq2rUkLV+BOSaGhLlzSVq0CM8xo/EeNx6HANGftSSUWihduXKFerf5knJxcWHZsmV3bJSg5OTPT8rvGTKbLLY7u5uF0u4LCWQaLdT1dKLt7XIljq+W78YC20DdohP1Mw8cJPv0aRSOjng9Wv5ubUHtRuXhQeiSJUSOn4DhwgUiJkyk3vffowmunKToPEPUcm84rRscWgx/zZDFUs8XS3yIRn6u/PJUd346GMnlBD2XEzKIScsmMcNAYoaBA1eTC+zv5KCioZ8Ljfxc5Ye/C439Xanv41K2cFQ5olAocHLV4OSqIahR4XyojFRDvvpQuWG8bDoMDMXZPa/KtMli5bnVR0nJNNG6rjtv3VuGGYJWK6yfBjHHwNlHzi1zLH8vin7fPm688SYA3hMm4DNhQrmfoywoXVzwHj8er7FjSfvrL5KXLMFw8RLJS5aSsvJ73EcMx2fSZLQNG9z+YLWYUgul24kkQeViy0+6qW3JjYupmE1WXDw0+NQt6LrO7e02pHXgrcNuJazEnbQkp/ntAw+g9iq6vpJAUBbUXl6ELltKxOPjMF65QuT48dT74fvKb4+jVMKwT+Qf3T2fwbZ35f6H/WcV+/m4mRZB7ryXr2hihsHMlYQMLidkcCk+g8vxsoC6lqQny2Th9A0dp28UzAlSKCDEy5lGfrJwapTjhWrs54qXS9VpcaFQKnDzdsTN27FQ+6Sb+fTv8xyJSMFNq2b+2I5o1WUQgrs/gjPrQekAo38Ar/p3fqxiyD57lujnpoPJhPuwofi/+kq5n6O8UDg44DlyJB7Dh5OxezdJ3y0h68gR0tb+Stqv63Ab0B+fKVNwatfO3qZWSUoklLy8vEo8gy05Ofn2OwnKBZPFxPGE40Dh/KTIU3lNcPP/7wxmC9vOxgElCLtF/AtJF8HBBdoUnY+Rff48+j17QKksUXl+geBOUfv4ELpsGRHjHscUEUnEhAnUW/l95YcPFAro/w5o3WHbLNj7OWTrYNind1S80FWrpm2wJ22DPQusN1usRCZn2jxPl+MzuJQjptKzzUQmZxKZnMnO8wkFXuftopHDeH6ueSLKz5W6Xk7FFry0N9vPxrHonysAfDyq7e3bKd2KU+tg94fy8r2fQ70e5WBhQYzR14l84gmsej3OXboQ9OGdF5SsTBRKJW59++LWty+Z4UdJ+u47MnbsIH3rNtK3bsO5c2d8pk7BpVcvMWs9HyUSSl988UUFmyG4E04lncJgMeCl9aKhR8EE14hiygLsu5REerYZfzctHUNv4/3J9Sa1GVWs2zp56VIA3AYPEpVhBRWOQ4A/9ZYvJ+IxWSxFTpxIve9X2qeNQ88X5M/FnzPg8BIwpMPIBaAqn8RstUpJQz9XGvq5MpC8xrqSJJGYYczzQCVkyGIqPoPrqVkk640k640culYwqVqrVtLA1yUn/8nV5o1q6OtatmrXZSQ6JZMZv8g3fBN61GdomzJ4Ca+HyyE3gO7PQsfHy8HCgphTUoiaOhVLQiLapk0Jnvc1ymrYqNa5YwecF8zHcOkSSUuWkvbnn2QeOkTmoUNomzXDZ8pk3IcOFTOYKaFQGj9+fEXbIbgD8rctya/+c2emKJUKgm9yd+f2dhvSOvDWSaL6JDi7QV7uNLHIXUwxMaT9tREAH9H8VlBJOAQFEbpieV4YbuIkQlcst0/Yt9Mk2bP025Nw8hcwZsCoZXLydwWhUCjwc9Pi56alW8OCAjHTaOZKPg9UrjfqSqIeg9nKudh0zsWmFzpmXU+nfInked4oHxdNhXoWjGYrz6w6SlqWiXbBHrwxrAx5SboYeYabORsaD4SB75efoTlYs7OJfvoZjFevog4KImTxt9V+Bpm2cWPqzPk//J6fTvLyFaT+8guG8+e58cqrJHz+Bd4TJ+L54AMonQvPhKwtlEgo6XQ6W4VLna5w/Yz8VIdK1jWF3ETum/OTcme7BTbyQOuU9y82WaxsOSOH3YbcLux27EewGCGoPdTpUOQuyStWgtmMc9euOLUpY6NKgaAUaIKDqbd8GRGPPY7hwgUiJ0+m3rJl5doqosS0GQUaV/hlHJzfCKsegjGr5bIClYyzRk3ruh60rlvwOlisEtEpmTkCSm/zRF1KyCA108T11Cyup2bxz4WCYTwPJ4fCYTx/V0K8nFCXQ6+4Dzed43hUKu6OauaN7YhGfYfHNGXJIik9Bnybwagl5T7DTbJYuP7yy2QdPYrS3Z3QbxfhEBBw+xdWExwCAwl4/TV8pz1FyurVJK/8HtONG8T9738kzp+P12OP4fXo2FqZh1qiFiYqlYqYmBj8/f1RKpVF3mFIkoRCocBisVSIoZVJdWhhYraauWv1XWSaM1l731qaeee1IPlz3nEiTiXR/f5GdBycl3y/92Iijy05gLeLhoNv9C/+i06S4Oswuc/VfV9C2IRCu1h0Oi716Ys1M5OQxd/i2qtXeQ9RILgthitXiHh8HJakJBzbtiV06RJUrpUvUAC4+o88Hd2YAXU7waNrwPnWCcxVgWS9MS+El09ARadkUdyvg0alpL6vc8HZeH5uNPRzwUVbslDN5lOxPPWDfLP37eNhDGpVgsbcRSFJ8OtkOPWrXCB06g7wLt9aW5IkETd7NimrVqPQaAhdugTnTqVvZ1OdsGZnk/bbbyQtXYYpKgoAhZMTnqNG4TNhPA51K3nWaQmxWwuTHTt24O0tf+B37txZLicWlI1zyefINGfipnGjsWdeZVuz0cL183JuQuhN+Um5YbfBrQJufTd49R9ZJGncoPWoIndJ+elnrJmZaJs2xaVnzzKORiC4M7QNGxK6dCmR48eTfeIEUU88Sejib1G6lCEZ+E5pcDeM2wA/PgjXD8Pye+Vmum5V2+vg7aKhSwNvujQoKOqyTRauJurz5ULJeVBXEjPINlm5EJfBhbjCbU6CPBzzJZHneaP83LS2m+zIpExeWSvnJU3p2eDORRLIrUlO/QpKNTz8fbmLJICkxd+Rsmo1KBTU+fjjGi+SAJSOjng98gieDz1E+pYtJH73HYYzZ0n5/ntSVq3C/Z5h+EyeUvnV8u1AiYRS7969i1wW2A9bfpJ/GKp8LubruWUBPLX41M37sbBYJf4+nRt2u02yZG4l7rYPFRk+sBoMJH+/EgCfyZPE7AiBXXFs1pTQpUuImDCRrPBwoqY9TciihfYpfBocBhM2wvcjIf40LBsC436vkC71FY2jg4oWQe60CCp4V261SlxPzbIlked6o64kZJCYYSQmLZuYtGz2XEws8Do3rZqGOYU0T1/XkZ5tpkOoJ68NvamPSmk48zvs/J+8fM9n0KD8Pdup69eTMHcuAAEzZ+I+ZHC5n6Mqo1CrcR82DLehQ9Hv20fSd9+Ruf8/dBv+QLfhD1x6343vlCk4depUY38L7jidPTMzk8jISIxGY4H1be3R5bsWUlwj3MhTubPdvAu8aY9EpJCYYcDdUU33hreYIZSRAGf/lJfDik7iTtuwAUtCIuqgINyHla7nlUBQETi2bEnod4uJnDiJzIMHiX7mWYK/WWCf3lYBLWHSZlg5ApKvwNIhcr84v5px561UKgjxdibE25k+zQpuS8002vKg8s/Ki0zOJN1gLlCR3NPZgXljO+Jwp7lON47Buifl5a7TikwRKCsZe/8l5q23AfCePAnvceU/i666oFAocL3rLlzvuousk6dIWrKE9C1b0O/+B/3uf3Bq1w6fqVNw7devWpRKKA2lFkoJCQlMnDiRTZs2Fbm9JuQoVXUsVgtH4otO5LaVBWjtW2D9xpNy2G1gy8BbJ0we+wGsJqgbBkGFRa9ktZK8VPY4eY8bh6KIFjYCgT1watuWkMXfEjllKvp9+7g+/XmCv/4KhT2mbns3hEl/w8qRkHgelg2Fx9dBUM0u6OfprCGsnjdh9QqG8QxmCxFJmTkFNeX8p4c7B1PX8w69fumxOTPcsqBRfxj0QTlYX5Cs06e5Pn06mM2433sv/i+9VO7nqK44tWlN8BefY4yIIGnpMtJ++42s48eJfvY5NA0a4DN5Eu7Dh1fLsglFUWrZ98ILL5CamsqBAwdwcnJi8+bNrFixgiZNmrBhw4Y7MmL+/PnUr18fR0dHunbtysGDB4vdt0+fPigUikKPe+65x7bPhAkTCm0fMmTIHdlWFbmUeol0YzrOameae+e5rVPjM0mLz5LLAjTPm5lgtUr8fTq3Ce4tcgGsVjiyXF4uxpuUsXMnxqtXUbq54flQyZuCCgSVgXPHjoR88w0KR0cydu/m+ksvIZlM9jHGvQ5M3CiLo8xEWH4fRP5nH1vsjFatommAG8PaBPFc/yZ8NKptITFVYkzZ8NOjoLsOPk1g1FK5vUw5YoyOJurJp7BmZuLcvRt1/u9/Nc5LUh5o6tUj6L13abx9Gz5PPIHSzQ3j1avEvPU2lwcMJGnJUiwZhfPYqhul/s/v2LGDuXPn0qlTJ5RKJfXq1eOxxx7j448/Zs6cOaU24Oeff2bGjBnMmjWL8PBw2rVrx+DBg4mPjy9y/3Xr1hETE2N7nDp1CpVKxUM3/WgPGTKkwH6rV68utW1Vldy2JR38O6BW5n1B5JYFCGrsgSZfWYDj0anEpGXjolHRs0lBT1MBru6ClGtyXZjWDxS5S9ISucCk1yOPoHK1Q8KsQHAbXLp2IXj+PBQaDelbt3HjtdeQzGY7GeML4/+A0B5gSJM9TJe22ceWmoAkwYZn5WR5R08Y+zM4eZbrKcwpKURNmYolMRFt8+YEf/21fbyS1Qi1nx/+M16k8c4d+L/yCmp/f8zx8cR/8gmX+vYjfu7nmBMTb3+gKkqphZJer8ffX24Z4OXlRUKCXHejTZs2hIeHl9qAuXPnMnXqVCZOnEjLli1ZuHAhzs7OLM2p+Hwz3t7eBAYG2h5bt27F2dm5kFDSarUF9vOqQbUfistPisjXtiQ/m3J6u/VvEXDrRpq5lbjbjgZNYRGUGR5OVng4CgcHvB579E7NFwgqHNe77qLuV1+CgwO6jZuIefNNJKvVPsY4esBjv8pFEM1ZsGoMnP6NYufeC4pnz2dwck3ODLeV4NOoXA9vzcoi+qlpGK9dQ10niJBFi+xXbqIaonJ1xWfyJBpt20rQ/z5A06AB1vR0kr79lkv9+hMz612MERH2NrPUlFooNWvWjPPnzwPQrl07Fi1axPXr11m4cCFBpWxQaTQaOXLkCAMGDMgzSKlkwIAB7N+/v0THWLJkCWPGjMHlpunAu3btwt/fn2bNmjFt2jSSkpKKPYbBYECn0xV4VFUkSSqy0KTZaOH6BbksQP62JZIk2coC3DLslh4nF8uDYitx53qTPEaOwMG/kvtrCQSlxK1PH+rO/QxUKtJ+30DsrFn2E0saZxizClqOlHMA10yAL9rC78/CybWQUbQHXZCPs3/Ajtny8tCPoWH5zsCWzGauv/QyWcePo/TwIHTx4srvI1hDUGo0eD74IA3/+pPgeV/j1K4dktFI6s8/c3nIUKKff4Gsk6fsbWaJKbVQev7554mJkX94Z82axaZNmwgNDeWrr77i//7v/0p1rMTERCwWCwE3VTcNCAggNjb2tq8/ePAgp06dYsqUKQXWDxkyhJUrV7J9+3Y++ugjdu/ezdChQ4tNNJ8zZw4eHh62R0gV7ll2Ne0qydnJaFVaWvm0sq2/fiEVi8mKq5cW7zp5ovH0DR1RyVk4Oijp3cyv+AMf/R6sZgjuAgGtCm02XLlCxo4dAHhPLFpICQRVDfeBA6n76SegVJK6Zi1xH3xACWrsVgxqjZxP0+VJ2SOSFil/7n6dDJ82gQU9YPNMOL9Z7hsnyCPmBKx7Ql7u8gR0Lt+WSZIkETv7AzJ27ECh0RDyzQK0jcrXW1UbUSiVuA0YQL2fVlPv+5W49L4bJIn0v//m2kMPETPrXXubWCJKnQH32GOP2ZbDwsKIiIjg3LlzhIaG4ut7i/yXCmDJkiW0adOGLl26FFg/ZswY23KbNm1o27YtjRo1YteuXfTv37/QcWbOnMmMGTNsz3U6XZUVS7n5Se382qFR5cXNI3LKAoS29ilQFiDXm9SnqT/OmmL+3VYrhK+QlztNKnKX5GXLQJJw7d8fbcPyL+gmEFQU7kOHIplM3HjtdbmysoMG/9dfs0/NF6UKhn0M/d+ByP1wZRdc2Q1xJ+W6S/Gn4b8FspCqGwYN+0CD3hDcWRZatZGMeLniuSlTvh6DS58LezuSFi0i9eef5YKSn36Cc8eO5X6O2oxCocC5c2dCO3cm+/wFkpZ8h+6vjTi1qx6zQMs8VcDZ2ZmOd/im8vX1RaVSERcXV2B9XFwcgYG3rtSq1+v56aefeP/92zc+bNiwIb6+vly6dKlIoaTVatHao97KHZC/EW5+bGUBCoXdcma7tbnF9by8A1Ij5eTIViMLbTbFx5O2/ncAfCaL5reC6ofH8OFIRiMxb71N8ooVKDQa/Ga8aL8CeVpXaDJQfgDoE+WK+Fd2wdXd8qSKqAPyY/dH4OAM9XrIoqlhbwhoA7VhFpZthls0+DSGh5aX+wy31HW/kfDFlwAEvPkm7oMGlevxBQVxbNaUuh9/jP/zz6P2u0WUowpRondcfm/L7ZibU8G0JGg0GsLCwti+fTsjR44EwGq1sn37dp599tlbvnbNmjUYDIYCHq7iiI6OJikpqdQ5VFWNAvlJAXn5SalxmegSslCqCpYFuBifwZUEPRqVkn7NbxFrz63E3e4RcChc1yTlhx+RTCacOnTAuWPRDXIFgqqO56hRWI1G4t6fTdLixSi0WvyefcbeZsm4+MozTXNnm6Zckz1NV3fLfzMT5dlyuTPmnLzllikNe8teFq8GUNOqIksS/PE8RB+UE+If+Vnu5VaOZPzzDzFvywUlfaZOxVtMUqk0qmqvuKIokVA6evRogefh4eGYzWaaNZPLsl64cAGVSkVYWFhRL78lM2bMYPz48XTq1IkuXbrwxRdfoNfrmZiTBzNu3Djq1q1bqPTAkiVLGDlyJD4+BWd4ZWRk8N577/Hggw8SGBjI5cuXefXVV2ncuDGDB1fv0vNR6VHEZ8WjVqpp65dXDDI37BbU2BONY96/NLfIZK8mvrg5FlMYUncDzucUDy0iiduSoSclp7SCzxThTRJUb7zHjgWTibg5H5I4Ty4h4PvEVHubVRiv+hBWH8LGy6Hx+DM5omkXXPsXspLhzHr5AeARCg3vhoZ9ZQHlWgOSkP/9Ak78BAoVPLQCfBvf9iWlIevkKaJfeBEsFjxGDMdvxovlenxBzaFEQil/I9y5c+fi5ubGihUrbFPuU1JSmDhxIr3uoIP86NGjSUhI4J133iE2Npb27duzefNmW4J3ZGQkyptczOfPn2fv3r1s2bKl0PFUKhUnTpxgxYoVpKamUqdOHQYNGsTs2bOrTXitOHK9SW182+CodrStjywi7AZyd26AoW1u4UkL/x4ki1znxa9Zoc2pa9dgTU9H06ABrn37lnUIAoHd8R4/HqvRSMJnc0mYOxeFxgGfCRPsbVbxKJUQ2Fp+dH8GLCa4fiQvvyn6UE5i+A/yA8C/ZV5+U/27QOtmzxGUnnMbYdt78vLQj6BR+X73GCMjiXrqKaTMTFx69CBo9uwa26dMUHYUUimngNStW5ctW7bQqlXBmVGnTp1i0KBB3Lhxo1wNtAc6nQ4PDw/S0tJwd3e//QsqiTf3vsmGyxuY2mYq0ztOB8BktLBkxh4sZiuPvNPVNuPtSkIG/T7bjVqp4MhbA/FwLsKjZLXIU5R10fDAYmj7cIHNksnEpYGDMMfGEjj7fbxEJW5BDSJh3nwS580DIOCdt2VvU3XEkJGXGH51N8SeLLg9NzG8QU6YrqonhseegiWDwKSHTpPh3pKnc5QEc3Iy1x55BFNEJNqWLai38ntRPLcGURG/36XOitPpdLYik/lJSEggPV1Maa1Iiio0ef18ChazFVdvLV5Bzrb1uUnc3Rv5FC2SAC5ulUWSkze0GF5os27jRsyxsaj8fPEYXni7QFCd8X3maSSjkaRvvyXu/dlyIdXqeDNQXGJ4bqguf2L4Px/LieGh3WXRVNUSwzMScma46eUQ4tCPyvXw1sxMop6ahikiEoe6dQldtEiIJMFtKbVQuv/++5k4cSKfffaZbVr+gQMHeOWVV3jggaLbXgjKTkxGDNczrqNSqGjv3962PvJUXhPc/K7j3LDbsFuF3XKTuNuPBQfHApskSbIVmPR+7HH7dGEXCCoQhUKB34svIBmNJC9fTuw7s1A4OOCZM7Gk2lIoMTwiTzRd/Qf0CXB5u/yAgonhDXrLDX3tEYYyG+Dnx+QwondDOS9JVX5NtyWzmeszXiL7xAlUHh6ELF5cbWZdCexLqYXSwoULefnllxk7diymnGaTarWayZMn88knn5S7gQKZ3PpJLbxb4OIg3wFJkpSvLEBeg8mo5ExOXk9DqYBBLQMKHwwgLRou5uR4hU0otFm/dy+GCxdQOjvjNWZ0+Q1EIKhCKBQK/F97FcloJGXVKmLeeBOlRoP7sGH2Nq388KoHXuOg4zh5Jln8mbz8pogqkhguSfDnixD1H2hzZrg532HT3CIPLxH73vtk7NqFQqsl+Jtv0DZsUG7HF9RsSi2UnJ2dWbBgAZ988gmXL18GoFGjRoVaiAjKl6LalqTGZaJLzEapVlC3Wd602VxvUpcG3vi4FuMJCl8JkhXq9wLfJoU2J323BADPhx9G5eFRXsMQCKocCoWCgLfeRDIZSV2zluuvvAoODrgPHGhv08ofhUKuvB/Q6qbE8JxSBFEH7ZMYvu9rOPYjKJTw0DLwa1quh09csIDUNWtAqaTuZ5+KMieCUnHHlbtcXFxo27bt7XcUlAtF5SdFnpab4Na5qSxAbjXuYsNuFrMslKBIb1LWyVNkHjgAajXe48eVg/UCQdVGoVQS+N57SEYTab//zvUZL6H46kvcavpMT5UDhHaTH31eA6MeIvbD1V2y1yn2pOyBij8jVwxXqCC4U17hy+DOoC5jWP78Ztj6jrw8eA40LlwUuCykrl1L4tdy0n7gO2/jlq+3qEBQEsq3xKmgQkjITOCa7hoKFHTwz7sTijiVCEC91nllAWLTsgmPTAVgcKtiqnFf/BvSY8DZB1rcV2hz0lLZm+RxzzAcqnmRToGgpCiUSoL+739IJhO6jRu5Pv15gr/5Bteed9nbtMpD4wJNBsgPAH0SXPsnL1SXcrWYxPCcGXWlTQyPOyP3ukOCjuOh65PlOpz0Xbts/cR8nnoSr3ztrQSCkiKEUjXgSLzsTWrq1RQPrRwGMxksXL+YCkBovvpJm3O8SWH1vAhwL5igbeNwbhL3o4XuBo1RUaT/LecueU8quu+bQFBTUahU1PnoQySTkfSt24h+5hlCvv0Wl65dbv/imoiLD7S6X35AvsTwnFBdkYnhvfJCdbdKDNcnwurRYMyAej1h2KflmkSedeIE11+cIReUHDkSv+efL7djC2oXQihVA3L7u+XPT4o+n4LVLOHm44hXYOGyAENbF+NNSonIa4NQRNgtedlysFpx6dULx2aFC1AKBDUdhYMDdT/7jOjpz5OxaxdR06YR+t1i0SgVikkMz5lRZ0sM/11+QF5ieIM+stcpNzHcbIRfxsk9Jr3qw+jvy7W2kzEigqgnn0LKysKlZ0+CZr8vCkoK7hghlKoBReYn5ZYFaOVj+wJISDdw6JqctzSkOKEUvhKQ5Ls9n0YFNplTUkhdtw4QzW8FtRuFRkPdL78g+uln0P/7L1FTnyB06ZJq0+28UiiQGP50TmJ4eF7hy+ISwxv0lkP/Ef+C1r3cZ7iZk5KInPoElpQUHFu1IvjLL1A4lF+ZAUHtQwilKk5KdgqXUi8BeUKpQFmAfPlJW87EYpWgbbAHwV7OhQ9mMcHR7+XlIvq6pfy4Cik7G8dWrXCuraEGgSAHpVZL8LyviXpqGpkHDhA5ZSqhy5fhdFNXAkEOKgcI7So/CiWG7y6YGA7yDLdRS8G/ebmZYNXriXryKUyRkTgEBxOyaCFKMSNbUEaEUKrihMeFA9DQoyHejvJdV0psJulJxZcFGNq6mATs85sgIw5c/KDZPQU2WbOySPlBvuvzmTJZuKkFAkDp5ETIgvlETn2CrPBwoiZNJnTlShyble/09RpJsYnhu+WSBJ2n5FUTLwckk4noF18k+9QpVJ6ehCz+FrWvb7kdX1B7qSJ16wXFkVtoslNAXn5SbhPcuk08cdCqAEjRG9l3WV5fbH5SbiXuDo8VygdI/e03LKmpOAQH41YT68cIBHeI0sWFkG8X4di2LZa0NCInTsSQU0NOUApyE8Pv+wKe2gNh48vt0JIkEfPuu+j/2YPC0ZGQhd+gbSAKSgrKByGUqjhFFZqMyNe2JJetZ+OwWCWaB7pR37cIV3PyVbi8Q17uWPALSjKb5SRuwHviBBRq4WgUCPKjcnUldPG3aFu2wJKcTOSEiRivXbO3WYIcEr+eR9qv6+SCknPn4tS+vb1NEtQghFCqwqQb0zmXfA7Iy08yZpu5cSkVgNB8bUtu29stfIX8t1E/8C54p5W+dSumqChUnp54in59AkGRqDw8CF2yBG3TppgTEoiYMBFjdLS9zar1pPz8C4kLFgAQOGsWbv1qeJFQQaUjhFIV5mj8USQkQt1C8XeWp9VezykL4O7riGeAnLCtyzax96JcfLLIsJvZmDfrJKxgErckSbZ2JV6PPorSyamCRiMQVH/UXl6ELluKplEjzLGxRI6fgOnGDXubVWtJ37GT2PfeA8D36afxGv2wnS0S1ESEUKrC5OYn5S8LEFFEWYAdZ+MxWqw08nOhSUARfZjO/yUXhnMNgGZDC2zKPHCQ7NOnUTg64vXo2AoaiUBQc1D7+BC6bCkO9UIxXb9OxISJmOLi7W1WrSPr2DGuz5gBViseDz6A73PP2tskQQ1FCKUqzJHYgvlJ+csChOYrC3Db3m65lbg7PC5P4c1H0pKc5rcP3I/au/xqmQgENRkHf3/qLV+OQ3AwpshIIidOxJyYaG+zag2Gq1eJemoaUnY2Lr3vJujdd8VMXUGFIYRSFSXTlMmZJLneSK5HKSUmk4xkAyq10lYWINNoZveFBKCYIpNJl+XibygKzTLJPn8e/Z49oFTiPWFChY1FIKiJOAQFEbp8OeqgIIxXrhA5cRLmlBR7m1XjMScmEjX1CSypqTi2bk3w3LmioKSgQhFCqYpyLOEYZslMkEsQdV3rAnlht7pNPXHQyGUBdp1PINtkJdTbmZZB7oUPdGS5/LfxAPAMLbApeelSANwGD0ITGopAICgdmuC61Fu+DLWfH4aLF4mcNBlLWpq9zaqxWDL0RD3xJKboaBxCQ0VBSUGlIIRSFaWotiW2sFu+JrgbT8pht6FtAgu7ns0GOPajvHxTJW5TTAxpf20EwGeSaFciENwpmnr1CF2xHJWPD4azZ4mcMhVLRoa9zapxSCYT1194gewzZ1B5exO6+FvUPj63f6FAUEaEUKqi2Brh5hSaNGabickpC5DbtiTbZGHnOTmJtMhq3Gf/gMwkcKsDTQYX2JS8YiWYzTh37YpTm9YVNAqBoHagbdiQ0GVLUXl6kn3yJFFTn8Cq19vbrBqDJEnEvP0O+r17UTg5EbLwGzT16tnbLEEtQQilKojBYuBk4kkgz6MUfS4Fq0XC3c/JVhZgz8VE9EYLdTwcaRfsUfhAuWG3jo+DKq+IpEWnI/WXXwDwmTyp4gYiENQiHJs2JXTpEpTu7mQdPUrUtKexZmXZ26waQcKXX5K2fj2oVNT9fC5Obdva2yRBLUIIpSrIiYQTmKwmfJ18qecu3zUV1QR3U07YbXDrIsJuiRfh2h658WTHcQU2pfz0M9bMTLRNmuDSq1cFjkQgqF04tmxJ6HeLUbq4kHnwINHPPIvVYLC3WdWalNWrSVq4CICg997FrU8f+xokqHUIoVQFyV8/SaFQIEkSkfnqJwEYzVa2no0DiikLkOtNajIIPIJtq61GI8nfrwRE81uBoCJwatuWkMWLUTg7o9+3j+jp05GMRnubVS1J37aN2NkfAOD73LN4jhplZ4sEtREhlKogtv5uOflJyTf0ZKQYUDkoqdvUE4B9lxNJzzbj56YlLNSr4AFM2XlJ3DdV4tZt2IAlIRF1YCDuw4ZV6DgEgtqKc8cOhCz8BoWjI/rd/xA9YwaSyWRvs6oVmeFHuf7Sy2C14vnQQ/g+/bS9TRLUUoRQqmKYLCaOxx8H8vKTcsNudZt6oc4pC7DppNzbbXCrAJTKm7xCZzdAVgq4B0OTgbbVktVK0hK5JID3+PGi9ohAUIG4dOlCyIL5KDQaMrZt5/qrryKZzfY2q1pguHKV6GnTkAwGXPv0IXDWO8L7LbAbQihVMU4nnSbbko2n1pNGno0A8sJureXK2WaLlS1ncprgFjXbLbcSd8dxoFTZVmfs3Inx6lWUbm54PvRQBY5CIBAAuPToQfDXX4GDA+mbNnPjjTeQLBZ7m1WlMcXHEzV1Kpa0NBzbtqXu3M9QqNW3f6FAUEEIoVTFyJ+fpFQoMWaZibkkF7DLrZ908GoyKZkmvJwd6NLgprYj8ecgch8oVPJst3zkepO8xoxB5SqKtAkElYFr794Efz4X1Gp0G/4gZtYsJKvV3mZVSSwZGUQ9+RSm69dxqBdKyMJvUDo729ssQS1HCKUqxs2FJqPOJWO1Snj4O+HpL39hbMzp7TaoZSBq1U3/wtwk7qZDwL2ObXVmeDhZ4eEoHBzwevyxih2EoFgkScIiwi+1DrcBA6j7ycegVJK29ldi3nyLjH/+Ifv8ecwpKUiSZG8T7Y5kNHJ9+nQMZ8+i8vEhdPFi0X9SUCUQ/swqhNlq5mj8USAvkfvm2W5Wq8Tfp+XZbkPb3NTbzZQFx1fJyzdV4s71JrmPGI6Dv3+F2F+bkKxWDJmZZGek5z30GWRnZOQsp5OdkUFWRnreupxlq8WMWqvFyc29wMPR1U1ednfHydUNJzcPHN3y1jlotPYetqAMuA8dimQyceO110n77TfSfvvNtk2h0aD290cdEIBDgD9qP3lZHeCPQ0CAbZtSWzPfA5IkceOtt9Dv24/C2ZmQhQtFWyVBlUEIpSrE+ZTz6E163BzcaOrVFEmSiDidDOTVTzoSmUJCugE3RzU9GvkWPMDp9ZCdBh6h0KifbbXhyhUyduwAwGeSKDCZH4vZjEFftKCRhU9+EVRwG2XwApgNBtINCaQnJpT4NWpNnriyCSjbX3ccCwgveb2D1vGObRSUPx7Dh6N0diZ1zVpMcXGY4+OxJCcjGY2YoqMxRUdzqxKVKg8PWUD5++cTUQWfq7y9USirV7AgYe7n6Db8ASoVwV9+IboFCKoUQihVIXLblnQI6IBKqSIxOgN9qgG1g5I6OWUBcnu7DWwRgEZ9c9gtJ4k7bHyBJO7kZctAknDt3x9tw4YVPg57YDIa8kRMfkFTpPDJyPH4pGMsY+VkB60jjq5uOLq63vTXDUcXednJ1Q2ti6ttm4OjIwa9nqz0NLLT08lK1+U80slKTyMrXVdovdVixmw0kJ6UQHpS6cSVTVTl81g5uroXElq5D7VWK2YYVSBuAwbgNmCA7bnVaMQcn4A5XhZO5rg4THHyX3N8PKb4OMxx8UjZ2VjS0rCkpWG4cKH4E6jVqP38cPDP9UoFoPb3yxNVAf44+PtXmWayyT/8SNLixQAEzZ6NqyiCK6hiCKFUhbg5PykytyxAMy/UDiokSeLvU/Jst6E3F5mMOw1RB0Cphg55SdzmhATS1v8OVP12JZIkYczKuknc5C1n3SSADPmEj9lUtoJ+WheXHHFTUOw45Rc+rq452+VlrYsr6jssseDk6oZnQODtdyT/ddGRpcsRTxnptmXb+ow8cZWdrsNilsVVRpKBjKTEEtumdtAU8Fg5unkUFlWubji5e8jXyF32XAlxdWcoNRo0wXXRBNctdh9JkrDqdLJwsomoOEzx8Zhznpvi47AkJoHZjDkmBnNMzK3P6+p621Cf2senQmec6bZsIe5//wPA74Xn8Xzg/go7l0BwpwihVEWwSlbC48OBvPykiFMF25Ycj07jRlo2LhoVvZrcFHbLLQnQbBi4BdhWJ3//A5LJhFOHDjh37FjBo7g9l48cIOLksaI9P/qMMs0GUqpUOObz3jjl8+oU8vbkW9Y6O6PM54GraigUCrTOzmidnfHwL7m4MmVn5fNUyeKpoPdKV2i9xWzGbDKSkZxERnJSiW1UOTjc1mOVPzTo7utX7cJD9kShUKDy8EDl4YG2SZNi95PMZsyJiTleqTjZU5UrquLibR4rq16PNSMDY0YGxsuXiz+xUona17dg/tRNoT51QABKV9dSC+XMI0e48fIrIEl4jh6Nz5NPlur11RlDpp6MlGS8AuugVFXd7x6BjBBKVYRLqZdIM6ThpHaihU8LDFlmYi4XLAuQ29utb3N/HB3yfbiMejjxs7ycL4nbkqEnZfVqoGp4k87u2cnGeZ/ddj+1RpsnZPKFrByLET5OOcsOjk7Cq5GDQqFA4+SMxqmU4sqQTZZOR3ZGOlm6tDzvVQHBlVZAaFlMJiwmExkpyWSkJJfoXN51guk74Qnqt7O/eK9JKNRqHAIDcQgMxOkW+1ky9LcN9ZkTEsBikfeJj4dTp4o/r7MzDn5+twz1qf38UGg0ABguXybq6WeQjEZc+/Uj8O23asVnV5IkTu/axs4V32LMykLl4IBvSH38GzTEv34j/Os3xC+0Pg6OIrewKlElhNL8+fP55JNPiI2NpV27dnz99dd06dKlyH2XL1/OxIkFZ3RptVqys7NtzyVJYtasWSxevJjU1FTuuusuvvnmG5rc4k7M3uTmJ7X3a4+D0oHLZ+ORrBKeAc54+DkhSRKbcsJuhXq7nVoHBh141YcGfWyrU9euwZqejqZ+fVz79cOexFw6z9+LvgKgabeeBDZumid08oW7tK6uYnaXnVAoFGgcndA4OuHhH3D7FyB/1swGQz5P1S08VrmCKy2V5BvR/Pp/79C4c3f6jJtS4vMJygeVqwsq14a3zFmULBYsyck5nqi4nPBeXqhPDv0lYE1LQ8rMxBgRgTEi4tbn9fFBHeCPOS4ea1oaTu3aUfezT2tFQcnMtFS2fDuPy4f/A0CpUmMxmYi7cpG4Kxdt+ykUSryC6uDfoBF+9Rrg30AWUM7uHvYyvdZj93fnzz//zIwZM1i4cCFdu3bliy++YPDgwZw/fx7/Yqaxu7u7c/78edvzm+9EPv74Y7766itWrFhBgwYNePvttxk8eDBnzpzBsYoq9fyFJiFf2C3Hm3QmRkdkciaODkr6NPMr+GJbEvcEyAlnSCYTySvk5rfekyfZNcyRkZzE75/+D4vJRMOwLtz7/Ksi7FJDUCgUODg64uDoiLtfycpOGDL17F+7ivBNf3Dp0H6uHTtCl5EP0Wn4A0IkVyEUKhVqPz/Ufn5Aq2L3s2Zl5eROFR/qM8fHI5lMWJKSsCTJ322a+vUJXvgNSqdb+b5qBpcO/ceWb78mS5eGUqXmrtGPEXbPSHSJ8cRfvUL8tcvEX7tCwrUr6FNTSL4RTfKNaM79u9t2DFcfX/zzCSf/+o1w9/OvFZ44e2N3oTR37lymTp1q8xItXLiQv/76i6VLl/L6668X+RqFQkFgYNHhBEmS+OKLL3jrrbcYMWIEACtXriQgIID169czZsyYihlIGZAkKa8RbmAnJEmyJXKH5rQtye3t1rupH86afP+2mBNw/QgoHaB9XiFJ3aZNmGNiUPn64jF8eCWNpDAmo4HfP/0AfUoyPsGhDHv2ZSGSajlaZxf6jJtK676D2LFsEVGnT7BvzY+c2rWNvuOn0qhTV/HlX41QOjmhqVcPTb16xe4jSRKWlJQ84ZScgmuvnqi9vIp9TU3AkJnJzuXfcnr3NgB8Q+sz7NmX8KvXAACvwDp4BdahWfeettfoU1OIvyoLJ/lxmdTYGDKSEslISuRK+CHbvloXF/zrNcS/QUP86jXEv0EjvOsEo6oFHrrKxK5X02g0cuTIEWbOnGlbp1QqGTBgAPv37y/2dRkZGdSrVw+r1UrHjh35v//7P1q1ku94rl69SmxsLAPyTb/18PCga9eu7N+/v0ihZDAYMBgMtuc6na48hldiruqukpydjEapobVva5KuZ6BPM6LWKKnTxBOATTnVuAuF3XK9SS3uBVfZ0yRJEknfLQHA+/HH7VakTpIktiz8itjLF3F0dWPkq++gFe0IBDn4htTjobf/x4X/9rLr+yXoEuL4/dMPqN8+jL7jn8C7TvGzwATVC4VCgdrbW6603by5vc2pFKJOn2DzN1+gS4gHhYLOwx+kx0OP3namrIunFw06dKJBh062dYbMTBIirxbwPiVFRWLQ64k6c5KoMydt+9rynuo3kPOeGjTEL7SByHsqA3YVSomJiVgsFgICCuYnBAQEcO7cuSJf06xZM5YuXUrbtm1JS0vj008/pUePHpw+fZrg4GBiY2Ntx7j5mLnbbmbOnDm899575TCiOyPXm9TWry1alZZTOaIoOKcswMW4dC4n6NGolPRrni+8YciAE2vk5bC8vC393r0YLlxA4eyM15jRlTaOmzm04VfO/bsbhVLJfS/OLPF0eEHtQaFQ0Kx7Lxp26MyB9b9w+I91XDt2hBUnnyHs3pF0e2A0GseaH5oR1BzMRiN7f1rBkb/ksiweAYEMefpFgpsXH768HVpnZ4KbtypwDIvZRFJ0lOx9irhC/NUrJERcwZiVVSjvCYUCr6C6OSG7hiLvqZRUO/9c9+7d6d69u+15jx49aNGiBYsWLWL27Nl3dMyZM2cyY8YM23OdTkdISEiZbS0puYncN+cn5c5225gTduvZxBc3x3x3I6fWgjEdvBtBg7ttq23Nbx96CJWHfT4Il48cYM/qFQD0m/Akoa3b2sUOQfXAwdGRnmPG0arPAHYu/5arRw9z6Pe1nN2zk96PTaJZj7tFOE5Q5Ym7colN8+eSFB0JQJv+g+nz+GQ0TuXvSVepHWzCJxfJaiU1PpaE3LBdTghPn5pCyo1oUm5Ec37fP7b9Xb198oRTTgjP3S9AfNZuwq5CydfXF5VKRVxcXIH1cXFxxeYg3YyDgwMdOnTg0qVLALbXxcXFERSUF6aKi4ujffv2RR5Dq9WitWN4KjeRu1NgJwyZJmKvyKG/3PpJuWG3oa1vuiaH8yVx57yxs06eIvO//0Ctxnv8uIofQBEkRkXw11efgiTRbuBQ2g++xy52CKofXoF1eOD1d7l85CA7V3xLWlwsf331Cce3baLfxKfwC61vbxMFgkJYLRYOrl/D/l9XY7VYcPbwZPBTz9OwY+dKtUOhVNrynpp2uynvKZ9wSoi4QkrMDVu9tAJ5T84u+OWG7eqLvCews1DSaDSEhYWxfft2Ro4cCYDVamX79u08++yzJTqGxWLh5MmTDBs2DIAGDRoQGBjI9u3bbcJIp9Nx4MABpk2bVhHDKBPRGdHEZ8ajVqhp59eOqOMpSFYJr0Bn3H2duJqo51xsOmqlgoEt84UTbxyFmGOg0kD7R22rk5bKuUnuw4biUKdOJY8GstJ1rP9kNqbsLIJbtqbvhNpTRE5QfjQK60K9Nu05/Mc6DqxfQ/SZU3z/2nTaD7qHHg8/iqOLq71NFAgASL5xnc3z5xJzSZ6J3bTrXfSf8nSVCmu5eHrRoH0YDdqH2dYZszKJj7hqC9nFX71CYlQEhkw90WdOEX0mr26WnPdUzzbbzq9+Q/zr1Z68J7tLxBkzZjB+/Hg6depEly5d+OKLL9Dr9bZZcOPGjaNu3brMmTMHgPfff59u3brRuHFjUlNT+eSTT4iIiGDKlCmAnPPwwgsv8MEHH9CkSRNbeYA6derYxFhVIjc/qZVvK5zUTkScvgZA6E3epO6NfPB01uS9MNeb1GI4uMj7GqOiSP97CwA+kydXgvUFsZjN/PH5h6TFxeLuF8B9L86s1XchgrKh1mjo9uAYWvbux+6VS7hw4F+Obv6Dc/v+odfY8bTuPUDMoBTYDUmSOLblL/75YRlmowGtswv9Jz1F8559qkXoSuN0i7ynnNl2BfOeLhF35VLeAW7Oe8rxPlUlgVhe2P1XbPTo0SQkJPDOO+8QGxtL+/bt2bx5sy0ZOzIyEmW+L8OUlBSmTp1KbGwsXl5ehIWFsW/fPlq2bGnb59VXX0Wv1/PEE0+QmppKz5492bx5c5WsoZSbn9QpoBOSVSLypvpJm3N7u7XON9stWwcn18rL+SpxJy9bDlYrLj174tisWcUbfxO7Vi4m6vQJHBydGPnq2zXyAyOofNx9/blvxkwiTh5jx7JFJF+PYsvCrzixbTP9Jz5FYOOm9jZRUMtIT07k72++JOLEUQBCW7dj8LQXcPf1u80rqzYF857kmeOS1UpafJxttl3uQ5+SXHTek5d3gVpPfvUb4uFfvfOeFJIkSfY2oqqh0+nw8PAgLS0Nd3f3Cj3X0F+HEp0RzYL+C2hubc8v/3cItVbFlE97EZORTc+PdqJUwME3B+DrmpNHdWgJ/DUDfJvCMwdBocCcksKlvv2QsrMJXb4Ml27dKtTumzm+dRPbvpsPwIiX36Jx58o9v6B2YDGbObr5D/avXYUxKwsUCtr0HUjPR8YLYS6ocCRJ4ty+f9i+ZAEGvR61g4Zej06kw+B7ap13U5+aQsK1K8TlCKeEa5dJiblR5L6F8p7qN8S7bkiFRBwq4vfb7h6l2kysPpbojGiUCiUd/Dtwbrvc4T24mRcqB6XNm9S5vneeSJKkgpW4c1R6yo+rkLKzcWzVCueuXSt1HFFnTrJj2UIA7hr9uBBJggpDpVbT6d77adGzD//8uIwz/+zg5I4tXDjwL3c9/BjtBg4TTUYFFUJWuo5tS77hwv49AAQ2asKQZ2bgU7fyZkhXJVw8vXBpH0b9m/KeEiKuFfA+JRWX96RWE3bv/fR6ZLw9zC8VQijZkdz8pObezXHVuBJ5Sk4GzJvtlht2yzfb7Xo4xJ4ElRbaPQLILQRSfvgBkJvfVqaLMy0+lg1z52C1WGjW42663v9wpZ1bUHtx8fRi6DMzaDtgKDuWLiT+2mV2LFvEye1/02/SUwS3aG1vEwU1iKtHD/P3oq/QpySjUCrp/uAjdL3/YSHKb0Lj5Ezd5i2p2zwvFSY37ykh4mqBiuPGrEy0zi52tLbkCKFkR2xlAQI6ka03EXslDYDQVt7E6bI5EpECwJD8+UlH5BpJtBoJznJ7k9TffsOSmopDcDBugwZVmv3GrEzWf/IB2ek6Aho2ZvBT06t1HFpQ/ajbrAWPzpnLye1/s/en70mIvMbP775O87t60/uxSbh6+9jbREE1xpidxT8/LOX41k0AeNcJZuizLxHYqOo2WK9q5M97atW7P5CT95QQj4OdyvKUFiGU7Ej+QpNRZ5ORJPAKcsHdx4n1+68B0DHUk0CPnCT07DQ4tU5ezqnELZnNchI34D1xQqV14ZasVjbOm0ti5DVcPL0Y8fJbOGirXrK8oOajVKpoN3AYTbv15N+fv+f4ts2c+3c3l48cpPuDY+g4bDgq9a3bRggEN3P9/Fk2z59Lapw887jjsBH0fGScaNxcDiiUymrVqUEIJTuRmJXINd01QBZKh3dcB6BeK9lLtPFkbpHJfN6kE7+AKRP8mkOonAeUvnUrpqgoVJ6eeD7wQKXZv2/Nj1w+/B8qtZrhL72Jm49vpZ1bICgKJzd3Bkx5hjb9BrN92UJiLpzjnx+XcXLnVvqNn1ogl0IgKA6L2cS+Nas49PuvSJIVNx8/hjz9AqGt29nbNIGdEELJTuTmJzXxaoK7gzsROU0N67X2ISnDwMGryQAMyc1PkqR8lbgngkJRoPmt16OPonSqnJ5Y5/b9w3/rfgZg4BPPUadp7WhyKageBDRszCPvfcyZPTv558dlpNyI5tc5s2jcuRt9xk3Bw7/63MkKKpeEyGtsmvcZCRFXAWh5dz/6TXyy2uTSCCoGIZTsRK5Q6hTQiYSodLJ0Rhy0KoIae/JLeDRWCdrU9SDEO6dHUPQhiD8NakdoJze6zTxwkOzTp1FotXg9OrZS7I67com/v/lStv2+B2wxZ4GgKqFQKmnVuz+NO3dj/9pVhG/6g0uH/uPasXA6jxhF5xEPihCKwIbVauHIn+v59+fvsZjNOLm5M3DqszTp2sPepgmqAEIo2YncRO6wgDAiT8tFJoObe6FSK21htyH5Z7vlepNaPQBOXkBeuxLPBx9A7e1d4TbrU1NY/+kHmI0GGrQPo9fYqj+tU1C70Tq70GfcVFr3HcTO5YuIPHWC/WtXcXr3dvqMn0LjTt3EBIRaTlp8LJvmf871c6cBaNixM4OenI6Lp5edLRNUFYRQsgNphjQuplwEZKG0+5cIQA67pWWa2H9ZFk62sgBZKXA6J4k7pxJ39vnz6P/ZA0ol3hMmVLjNZpOJ3z/9gIykRLzqBHPP86+iVIqpsYLqgW9IPUa99T8u/Pcvu77/Dl1CHBs+/R/123Wk74Qn8K4TbG8TBZWMJEmc2rmVnSsWY8rOwsHRib7jp9K670AhngUFEELJDuSG3Rp4NMDV6kHc1dyyAD78fTYOs1WieaAbDf1yGn8e/xnM2eDfCoLlbtTJS+UyAW6DBqEJDa1QeyVJYtviecRcPI/WxYX7X31bxOwF1Q6FQkGz7j1p2KETB9av4fAfv3LteDgrXn6WsHtG0O2B0WicnO1tpqAS0KemsGXRV1wJPwRA3eatGPrMiyJ/TVAkQijZgVyhFBYQRtQZuSyAdx0X3Lwd2bThprBb/krcneQkblNMDGl/bQTkApMVbu9f6zm9ezsKpZJ7X3gdr6C6FX5OgaCicHB0pOeYx2nVpz+7VizmSvghDm34lbN7dnL3Y5Nofldv4VGowVw8sI8ti+eRna5DpVZz15hxhN0zQnjIBcUihJIdyF9oMmJ3XhPc9GwTey7KbUyGtckpCxD5HyScAwdnaCtXvU5esRLMZpy7dMGpTZsKtfXq0cP884Ms1PqMm0L9th0q9HwCQWXhFViH+1+bxeUjB9m1YjGpcTFs/PpTTmzbTL+JT+JXr4G9TRSUI4ZMPTuWLuTMnp0A+NVrwLBnX8I3tL59DRNUeYRQqmQyjBmcSz4HQEe/jmw6cwmA0NY+7DgXj9FipaGfC038c8Juud6k1g+AowcWnY7UX34BwGfK5Aq1Nel6FH9++TGSZKV130F0GHJfhZ5PILAHjcK6UK9New7/+RsHfvuF6LOn+P6152k/+B56PPQojq6u9jZRUEYiTh7j72++JD0pAYVCSecRD9LjobGiEKmgRAihVMkcjT+KVbIS7BqMKtmFrHQTDo4qghp5sGn1FUBO4lYoFJCZDKfXyy8Mk0NsKT/9jDUzE22TJrj06lVhdmZnZPD7J7MxZmVSt3lLBkyZJsIRghqLWqOh2wOjaXl3X3Z/v5QL/+3l6OY/OPfvbno+Mp42fQfWuu7wNQGT0cDeVSsI37QBAM+AIIY8M4O6zVrY2TJBdUIIpUomf35SxCk57BbS3BuD1cquC/FAvmrcx1eDxQCBbaBuR6xGI8nfrwTAuwKb31otFv788iNSYm7g5uvH8BlviDsvQa3A3def+158nchTx9mxbBFJ0ZFs/fZrTm7fTL9JTxHUuJm9TRSUkNjLF9k07zOSb0QD0G7gUO5+bBIax8opzCuoOQihVMnY8pMCOxHxryyUQlt5s/t8AtkmKyHeTrSq415kJW7dhg1YEhJRBwbiMWxYhdm4+4elRJw4ilqrZeQrb+Ps4Vlh5xIIqiKhrdvx+EdfcezvP9m35kdiL19k1Zsv0brvIHo9Mk58JqowFrOZA7/9wn/rfkKyWnHx8mbwk9Np0KGTvU0TVFOEUKpEMk2ZnE6Ui5q1dm3P39fkUFu91j4s3HwWkL1JCoUCru2FpIvg4AJtHkKyWklaIpcE8B43DoVGUyE2nty5hfCNv8u2PDMD//oNK+Q8AkFVR6VWE3bPSJrf1Zs9q5Zzevd2Tu3cwsUD/9Lj4cdoP2gYSpWYKVWVSLoexeb5c4m9LNepa9q9FwMmT8PJzd3OlgmqM0IoVSInEk9glswEOAdgiXAECXzquqB2dWDH2TggX5HJXG9Sm1Hg6E7G9u0Yr15F6eaG58MPVYh918+dYdviBQB0HzWWpl3vqpDzCATVCRdPL4Y8/SJt+g9hx9KFxF+7zM7lizi542/6T3yK4Jat7W1irUeyWjn691/s+XEZZpMRrYsL/Sc/TYu7etvbNEENQAilSuRwbL62JWdyygK09mHvxUT0RgtBHo60C/YEfRKclZMPcytx53qTvMaMQVUBs3B0ifFsmPt/WC1mmnTtQfcHx5T7OQSC6kzdZi14dM5cTm7fwt6fVpIYeY2f33ud5nf15u7HJuLm7WtvE2slusQE/v7mCyJPHQegXtsODJ72vPh/CMoNIZQqkZ51e2K0GGnn155rG5IBuRr3Z8fkFiaDWwWiVCrg2I9gMUJQe6jTgczwo2SFh6NwcMDr8cfK3S5TdjbrP/mAzLRU/Oo3ZOjTM8QMH4GgCJRKFe0GDqVpt7v49+fvOb5tM+f+3c3lwwfo9uAYwu4ZISY+VBKSJHF27y52LF2IIVOPWqOl92OTaDdomJihKyhXhFCqRNr7t6e9f3viruo4l3EYjaMKn3pubFslh92GtQkCqxWOLJdfkOtNyml+6z5iOA7+/uVqk2S1snnB5yRcu4KTuwcjX3kLB0fHcj2HQFDTcHJzZ8CUZ2jTbzDbly0k5sI59qxazqmdW+k34Qnqtw+zt4k1mkxdGtu+m8/FA/sACGrcjCHPzMC7jugaICh/hFCyAxGnc8oCtPDmQEQKumwzvq5awup5wbXdkHwZNG7QehSGK1fI2L4DAJ+JE8vdlv2//sSFA/+iVKkZ8dKbuPuWrxATCGoyAQ0b88h7H3Nmz07++XEZKTHX+XXOLBp16kbf8VNE77AK4Er4If5e+CWZaakoVSq6jxpLlxGjRGK9oMIQQskO5NZPCm3tw8qTcm+3wa0CUCkVeUncbR8GrSvJyz4CScK1Xz+0jRqVqx0XDvzL/rWrABgw9WnqNm9ZrscXCGoDCqWSVr3707hzN/avXU34pg1cPvwf144fofPwUXQZ8SAOWuGlLSvGrEx2fb+Ek9v/BsAnOJShz8wgoGFjO1smqOkIoVTJZKUbiY/QAVC3uRdbdsjlAoa1CYKMeDj3p7xjp4mYExJIWy9P1S/vdiXx166waf5cADoOHU6bvoPK9fgCQW1D6+xCn3FTaN13IDuXLyLy1An++3U1Z/7ZTp9xU2jcubvInblDos+dZvP8uaTFx4FCQdiwEfQcMw51BZVJEQjyI4RSJRN5JlkuCxDsyulUPcl6I17ODnRt4A37vgCrGep2gsA2JM/9HMlkwql9e5w7diw3GzLTUln/yWzMBgP12nag9+MV2zNOIKhN+IbUY9Rb/+PigX/ZtXIJuoR4Nnz2f9Rr24G+E57Ap26IvU2sNphNJvb98gOH/lgHkoS7nz9Dpr1ASKu29jZNUIsQQqmSyQ271Wvlw8aTsQAMbBmAWgGEr5B36jQRS4aelJ9+AsrXm2Qxm9gw9/9IT0zAK6gO9z7/mojtCwTljEKhoGm3njRo34mDv6/h0IZfiThxlJWvPEvHYSPo/uAYNE7O9jazSpPr9U6MvAZAq94D6DvhCbTO4roJKhchlCoRq1Ui6oxcFiCklTd/r5H7vg1tEwRXdkLKNdB6QKsHSF31C1adDk39+rj261cu55ckiW3ffcP1c2fQODkz4pW3RWd0gaACcXB05K7Rj9Oyd392rVjMlfBDHP5jHSe2bcLdLwAXT68CD2dPL1xz/rp4eqF1dql14Tqr1cKhDevY98uPWC1mnNw9GPjEszTp3N3epglqKUIoVSLx13Rk601onNTEqK3Epxtwc1RzVyNf+DUnibvdaCSFA8krcprfTppYbjWNjm7+g1M7t6BQKLn3+VdFCEAgqCS8Autw/2uzuBJ+iJ3LvyU1LobEyGs2b0lxqB00OaLJM5+g8i5CVHnWiPpNqbExbFrwOTfOnwGgUaduDHriWdFbT2BXhFCqRPLKAnix6bRcO2lAiwA0WfFwbqO8U9hEdJs2YY6JQeXri8eIEeVy7msnjrJrxXcA3P3oBNEgUiCwAw07dqZ+u44kXY9Cn5KMPjWlwCMz37IhU4/ZZESXEIcuIe62x3Z0dSvSO+WSz0Pl4uWNo4trlfNSSZLEye1/s2vld5gM2WicnOg74Ula9e5f5WwV1D6EUKpEQpp7kaUzEtzci9lb5dluQ1oHwtHvQbJASFck/xYkfTcTAO/HHkOp1Zb5vCkx1/nziw+RJCutevcn7N77y3xMgUBwZyhVKvxC6+MXWv+W+5mMBjJTU3OEUzL6nOXM1BQyUpNzRJW8zmoxk52RTnZGOknRkbc5vzpHTHni4uWNi0d+IeWFi0ee2KqMWWUZKclsWfQVV4/KLZ6CW7ZmyLQX8fAPqPBzCwQlQQilSqROEy/qNPHieFQq11OzcNao6N3YG7bIYTbCJqLfuxfDhQsonJ3xeqTs/dYMmXrWfzwbg15PUJNmDJjyjLhDEwiqAQ4aLR7+AbcVDJLVSrY+o5B3Ks9DlSeysjPSsVrMpCclkJ6UcFsbtC4uecLJyxsXT0+cCzyXl51c3e4oReD8/r1s+24+2RnpqBwc6DlmHGHDRogWSoIqhRBKdmDTKXm2W9/m/jhG7oa0SHD0hFYjSZr6NABeDz2EysOjTOexWi389eXHJN+IxtXHlxEvvyXqjggENQyFUomTmztObu74htS75b5mk4nMtFwhlSp7p1KS89alpKDPWbaYTBj0egx6Pck3om95XKVKhbNHXh6Vs4cXrl75PFUeeULLQetIdkYGO5Yt5OzeXQD412/E0Gdn3NZ+gcAeCKFUyUiSxKZTcjXuoa0D4fCn8oZ2j5B1/gqZ//0HKhXe48eV+Vx7Vq3g6rEjqDVaRr78Fi6eXmU+pkAgqL6oHRxw9/W/basiSZIwZOpl4ZQqiyebqEpNQZ+WExJMSSYrXYfVYiEjOYmM5KTb2qBxcgIUGLMyUSiUdL3/Ibo9OKZGJKMLaiZCKFUyZ2PSiUjKRKtW0q+OGX7bLG/oNJHkOYsAcL9nGA516pTpPKd3b+fwH+sAGDzteVHmXyAQlBiFQoGjiyuOLq74BN96dqzFbCZTl0pmaioZOQnqch5VvuT0NNlbZTYaMGZlAeAVVIchT8+gTtPmlTEkgeCOEUKpktmc403q3dQP51Or5STu0B4Ys53Rbc7pYTRpUpnOcePCObZ++zUAXe8fTfMed5fNaIFAICgGlVqNm7cvbt6+3CqbSpIkjFlZOTP6MvALbSBSAQTVgiqRMTd//nzq16+Po6MjXbt25eDBg8Xuu3jxYnr16oWXlxdeXl4MGDCg0P4TJkxAoVAUeAwZMqSih1EiNubkJw1t7QfhOUncnSaSvGw5WK249OyJY/M7v8NKT0pkw2f/w2I206hTN+56+NFysFogEAjKhkKhQOvsjHedugQ1biZEkqDaYHeh9PPPPzNjxgxmzZpFeHg47dq1Y/DgwcTHxxe5/65du3jkkUfYuXMn+/fvJyQkhEGDBnH9+vUC+w0ZMoSYmBjbY/Xq1ZUxnFtyKT6dS/EZOKgUDNacBF00OHljDuxF6jo5TFaWdiUmQza/f/oB+tQUfEPqMezZGWL2iEAgEAgEZcDuv6Jz585l6tSpTJw4kZYtW7Jw4UKcnZ1ZunRpkfv/+OOPPP3007Rv357mzZvz3XffYbVa2b59e4H9tFotgYGBtoeXl/0TmTfl9Hbr2dgX5xPfyyvbjyXll3VI2dk4tmyJc9eud3RsSZL4e+FXxF25hJObOyNffVv0khIIBAKBoIzYVSgZjUaOHDnCgAEDbOuUSiUDBgxg//79JTpGZmYmJpMJb2/vAut37dqFv78/zZo1Y9q0aSQlFT8bw2AwoNPpCjwqgtZ1PRjYMoAxzZRwcQsA1laPkPLDD4DsTbrTGkcH16/h/L5/UKpU3DdjJh7+geVmt0AgEAgEtRW7CqXExEQsFgsBAQVTAAMCAoiNjS3RMV577TXq1KlTQGwNGTKElStXsn37dj766CN2797N0KFDsVgsRR5jzpw5eHh42B4hIRXTA61vc38Wj+vEYMMWkKxQvxep/5zAkpqKQ926uA0adEfHvXToP/b+JOc79Zv4FCEt25Sn2QKBQCAQ1Fqq9ay3Dz/8kJ9++oldu3bh6OhoWz9mTF5F6zZt2tC2bVsaNWrErl276N+/f6HjzJw5kxkzZtie63S6ChNLWMy2JG6p/TiSX5H7r3lPnIhCXfp/R0LkNTbO+wyA9oPvod3AoeVnq0AgEAgEtRy7epR8fX1RqVTExRVs+BgXF0dg4K1DR59++ikffvghW7ZsoW3btrfct2HDhvj6+nLp0qUit2u1Wtzd3Qs8KoyLf0N6DDj7kH7dCVNUFCpPTzwfKH3/tUxdGus/no0pO4uQVm3pM25qBRgsEAgEAkHtxa5CSaPREBYWViAROzcxu3v37sW+7uOPP2b27Nls3ryZTp063fY80dHRJCUlERQUVC52l4nDywCQ2o0laekKALzGjkXpXLrEa4vZzB+fz0GXEIdHQCD3vfg6qjvwSAkEAoFAICgeu896mzFjBosXL2bFihWcPXuWadOmodfrmThxIgDjxo1j5syZtv0/+ugj3n77bZYuXUr9+vWJjY0lNjaWjIwMADIyMnjllVf477//uHbtGtu3b2fEiBE0btyYwYMH22WMNlIi4NI2ADIVYWSfPo1Cq8XrsdLXOtq5fBHRZ06hcXJi5Ctv4+RWgV4wgUAgEAhqKXZ3QYwePZqEhATeeecdYmNjad++PZs3b7YleEdGRqLMVwvom2++wWg0MmrUqALHmTVrFu+++y4qlYoTJ06wYsUKUlNTqVOnDoMGDWL27NlotdpKHVshwlcCEjToTdLaTQB4PHA/6ptm7N2OY1s2cnzrJlAoGPbcy6KRpEAgEAgEFYRCkiTJ3kZUNXQ6HR4eHqSlpZVvvtLVPXBgIdked3P1xS9AqaTRpo1o6pVc6ESeOsHa/72FZLXS85HxdB35UPnZJxAIBAJBNaYifr/tHnqrVTToBWN+JHnXVQDcBg0qlUhKjYvlj8/nIFmttOjZhy4jRt3+RQKBQCAQCO4YIZQqGVNMDGl//QWAz+SSN781ZGay/uP3yc5IJ7BREwY++dwdF6cUCAQCgUBQMoRQqmSSV6wEsxnnLl1walOywpCS1crGeZ+SFB2Ji5c3w19+EweNnfOtBAKBQCCoBQihVIlYdDpSf/kFKF3z270/f8+VIwdROTgw4uU3cfP2rSgTBQKBQCAQ5EMIpUok5aefsWZmom3SBJdevUr0mrN7d3Fw/RoABj85naDGzSrSRIFAIBAIBPkQQqkS0YQEo2nQAO/Jk0qUXxR76QJbFn4FQOcRo2jRq29FmygQCAQCgSAfdq+jVJtwHzoUt8GDoQQVGTKSk/j90w8wm4w07NiZnmMerwQLBQKBQCAQ5EcIpUpGoby9E89sNPL7Z/8jIyUZ77ohDHvuFZRKVSVYJxAIBAKBID8i9FbFkCSJLd9+TeylCzi6uDLy1bfRlrIPnEAgEAgEgvJBCKUqxuE/1nF2z04USiX3zZiJV2Ade5skEAgEAkGtRQilKsSV8EP8s2o5AH0nPEFo63b2NUggEAgEglqOEEpVhKToKP766mOQJNr2H0L7QffY2ySBQCAQCGo9QihVAbIy0ln/8fsYs7IIbtGafpOeFO1JBAKBQCCoAgihZGcsZjN/fv4hqXExuPsFcN+MmajUDvY2SyAQCAQCAUIo2Z3d3y8h8tRxHLSOjHzlLZzdPextkkAgEAgEghyEULIjJ7Zv5ujmPwAY+uwM/Oo1sLNFAoFAIBAI8iOEkp2IPnOK7Uu+AeCuhx+jSZcedrZIIBAIBALBzQihZAfS4uPYMPf/sFosNO3ei64PjLa3SQKBQCAQCIpACKVKxpidxe+fzCYrXYd/g0YMmfa8mOEmEAgEAkEVRQilSkSyWtk0by4Jkddw9vBkxMtv4aB1tLdZAoFAIBAIikEIpUpk39rVXDq0H5VazYiX38Td18/eJgkEAoFAILgFansbUJsIaNAIB0cn+k96ijpNW9jbHIFAIBAIBLdBIUmSZG8jqho6nQ4PDw/S0tJwd3cv12PrU1Nw8fQq12MKBAKBQCComN9vEXqrZIRIEggEAoGg+iCEkkAgEAgEAkExCKEkEAgEAoFAUAxCKAkEAoFAIBAUgxBKAoFAIBAIBMUghJJAIBAIBAJBMQihJBAIBAKBQFAMQigJBAKBQCAQFIMQSgKBQCAQCATFIISSQCAQCAQCQTEIoSQQCAQCgUBQDEIoCQQCgUAgEBRDlRBK8+fPp379+jg6OtK1a1cOHjx4y/3XrFlD8+bNcXR0pE2bNmzcuLHAdkmSeOeddwgKCsLJyYkBAwZw8eLFihyCQCAQCASCGojdhdLPP//MjBkzmDVrFuHh4bRr147BgwcTHx9f5P779u3jkUceYfLkyRw9epSRI0cycuRITp06Zdvn448/5quvvmLhwoUcOHAAFxcXBg8eTHZ2dmUNSyAQCAQCQQ1AIUmSZE8DunbtSufOnZk3bx4AVquVkJAQnnvuOV5//fVC+48ePRq9Xs+ff/5pW9etWzfat2/PwoULkSSJOnXq8NJLL/Hyyy8DkJaWRkBAAMuXL2fMmDG3tUmn0+Hh4UFaWhru7u7lNFKBQCAQCAQVSUX8fqvL5Sh3iNFo5MiRI8ycOdO2TqlUMmDAAPbv31/ka/bv38+MGTMKrBs8eDDr168H4OrVq8TGxjJgwADbdg8PD7p27cr+/fuLFEoGgwGDwWB7npaWBsgXXCAQCAQCQfUg93e7PH1AdhVKiYmJWCwWAgICCqwPCAjg3LlzRb4mNja2yP1jY2Nt23PXFbfPzcyZM4f33nuv0PqQkJCSDUQgEAgEAkGVIT09HQ8Pj3I5ll2FUlVh5syZBbxUVquV5ORkfHx8UCgU5XounU5HSEgIUVFRtTKsJ8Zfu8cP4hrU9vGDuAZi/BU3fkmSSE9Pp06dOuV2TLsKJV9fX1QqFXFxcQXWx8XFERgYWORrAgMDb7l/7t+4uDiCgoIK7NO+ffsij6nVatFqtQXWeXp6lmYopcbd3b1WfkByEeOv3eMHcQ1q+/hBjVvk7AAAHfZJREFUXAMx/ooZf3l5knKx66w3jUZDWFgY27dvt62zWq1s376d7t27F/ma7t27F9gfYOvWrbb9GzRoQGBgYIF9dDodBw4cKPaYAoFAIBAIBEVh99DbjBkzGD9+PJ06daJLly588cUX6PV6Jk6cCMC4ceOoW7cuc+bMAeD555+nd+/efPbZZ9xzzz389NNPHD58mG+//RYAhULBCy+8wAcffECTJk1o0KABb7/9NnXq1GHkyJH2GqZAIBAIBIJqiN2F0ujRo0lISOCdd94hNjaW9u3bs3nzZlsydmRkJEplnuOrR48erFq1irfeeos33niDJk2asH79elq3bm3b59VXX0Wv1/PEE0+QmppKz5492bx5M46OjpU+vpvRarXMmjWrUKivtiDGX7vHD+Ia1Pbxg7gGYvzVa/x2r6MkEAgEAoFAUFWxe2VugUAgEAgEgqqKEEoCgUAgEAgExSCEkkAgEAgEAkExCKEkEAgEAoFAUAxCKAkEAoFAIBAUgxBKghqHmMgpEAgEtZvy/B0QQqmGcP78eZ5//nl7m2E3zGazbVmhUGC1Wu1oTdVAXIPaibhRENRmsrOzAfl3oLw+C6KOUg3g+PHj9O/fH71ez4EDB2jbtq29TapUzp8/zyeffIJOp8PLy4tFixbZ26RK5+rVq+zdu5fk5GRatmzJwIEDAflHs7wbO1dFoqKiOHXqFKmpqXTr1o0GDRrY26RKJyMjA61Wi4ODQ635v+cnLi6OyMhIEhMTufvuu3FxcbG3SZVOZGQke/bsISkpie7du9O5c2d7m1SpnDlzhpdeeokXXniBwYMHA+X0HSgJqjXHjh2THB0dpWeeeUaqX7++9Nprr9nbpErl5MmTko+Pj/T4449LEyZMkFq3bi299NJLtu1Wq9WO1lUOJ06ckHx9faWRI0dKTZs2lTp27Cj16dNHSktLkySp5l+DEydOSAEBAVKXLl0ktVothYWFSU8//bS9zapUzpw5Iw0YMED6/vvvJYPBIElSzf+/5+fEiRNS8+bNpfbt20sKhUIaPHiwdPz4cXubVamcOHFCCg4Olvr37y95enpKvXv3lsLDw+1tVqVhtVqlSZMmSe7u7tI999wjbd68ucC2siBCb9WYo0eP0r17d1544QXmzZvHM888wy+//MKJEyfsbVqlkJaWxpQpUxg/fjwrV65k0aJF9O3bF2dnZ9s+Nf2uOjk5mXHjxjF58mR+++03Dh8+zEsvvcTu3bsZNmwYMTExNToUqdPpGD9+PGPGjGHr1q1ERUXx4IMPsnfvXoYMGWJv8yqFiIgIHnzwQf755x/mz5/Phg0bMBqN5Rp6qMpcvHiRwYMHM2rUKH777TcuXrzI2bNnWbx4sb1NqzTOnz/PoEGDGD9+PH/++SenT5/m9OnTnD171t6mVRoKhQIXFxdatGiBVqvl448/ZvPmzbZtZUEIpWrK9evXGTFiBM8995ytYXCPHj0wGo0cPnwYAIvFYk8TK5ykpCTS0tIYM2YMABqNBrPZzJYtWxg8eDD33nsvUVFRQM3N27hx4wZms5nJkycD4ObmRr9+/WjVqhVXrlzhnnvuASjQL7EmkZaWhl6vZ9SoUbi7uxMYGMj06dOZNWsWkZGRPPDAA/Y2sUKxWCz8+uuvNG7cmIMHD+Lp6cn//d//1RqxlJWVxdy5cxk2bBhvv/02ISEhNGrUiHfeeYft27eTnZ1do8cPkJmZyWeffcbw4cN599130Wg01KlTh759+3L58mXeffddVq1aZW8zK4WePXsyYsQI3njjDTQaDXPnzuXw4cPMmTOHa9eu3fFxa+a3Zy3AwcGBBQsW8NFHH9nW9ejRg3vuuYcPPvgAnU6HSqWyo4UVj4eHB2azmfnz5xMfH88777zD0qVLue+++7jnnntITU1lwIABGAyGGu1ZSk9P5+TJk7bnaWlpKJVKPv/8c1JTUwu8R2oa7u7uWK1W9u3bZ1vn4uLCvffey5tvvsmVK1dYsGCBHS2sWFQqFf369WPcuHG0a9eOv/76i4CAAJtYyn3v11SxIEkSJpOJu+66C41GY/vOCwgIIDk5GYPBYGcLKx6VSsWIESN4+umnUavVKJVKZs+ezdq1a7lw4QLbt2/no48+4oUXXrC3qRWOu7s7GzZsICwsjNdeew13d3dGjhzJm2++iaOjI3CHN81lCtwJ7ILFYil23e7du6VGjRpJv/zyS7H71hRMJpO0aNEiKTQ0VBo8eLDk7OwsrV692rb92rVrkpeXV4F1NY2kpCSpf//+0ogRI6Q5c+ZIf/zxh+Tp6Sm9+OKLkiRJ0ujRo6UJEybY2cqKIysrS5o4caI0cOBA6dixY4W2jRw5Uho1apSdrKscTCZTgecGg0EaMmSI1KFDB2nNmjWS0WiUJEmS1q9fbw/zKpwbN27Yls1msyRJknTw4EGpVatWBb7/zpw5U+m2VRZZWVm25ZMnT0qurq7S77//blv3xhtvSB07dpRiY2PtYV6Fk/t/vnDhgtSlSxfb+oEDB0rOzs5S165dpV27dt3x8YVHqRphMpmAouOtuaGVu+++m4CAAJYuXVpgfU0gd/wg3xWo1WomT57M8ePH+fzzz6lfvz7du3e3bTeZTAQFBeHv728vk8ud3GsgSRJWqxVvb2/mzZuHWq1mxYoVvPzyyzz77LPMnTsXAH9/f2JiYuxpcrmi0+m4evUqN27cIDMzE0dHR1566SVOnTrF+++/z8WLF237Ojo60qdPHy5duoRer7ej1eVL/muQlZWFWq223SVbLBY0Gg3r16+3eZbWrVvHU089xbRp07hx44adrS87+cev1+sJCgoC5HIYuR4lq9WKTqcjKysLgDfffJPp06eTmppqL7PLlaI+B5IkIUkSrVu35uLFiwwfPtyWm9ioUSOys7PRarV2trx8uPkzkPs717hxY5ydnYmIiGDcuHGcPn2auXPnUrduXV5++WV27tx5Zycsg4gTVCLnzp2Txo4dKx05cqTYfXLvprZu3SoFBQUVuKOo7tw8fqvVWmAmQ0pKihQWFiYtX77ctu7dd9+VWrRoIUVHR1e6vRVBUdcg15ug0+kknU4nRURE2Pa3Wq3Sgw8+WGAWYHXm5MmTUo8ePaRmzZpJDRs2lJ5//nnp+vXrkiRJ0uHDhyU3Nzfp/vvvl7Zu3Wp7zRNPPCHde++9tplg1Z2irkFcXFyBfXLfEwaDQRo2bJjk4OAgubi43PK7o7pQkvFLkiTt2bNH8vT0lDIzM6V33nlHUqvV0qFDh+xgcflTkmtw8yyv6dOnS6NGjZIyMzMr09QK4VbjNxgMUu/evaXAwEApJCREOnr0qCRJkrR582ZpzJgxBb4fS4MQStWAy5cvSyEhIZKnp6d0//3333bK540bN6SQkBDp5ZdfrhGht5KMX6fTSWPGjJG6desm9ejRQxo9erTk6+tr+6BUd4q7Blartcj/8YULF6SZM2dKXl5e0tmzZyvb3HLn7Nmzkp+fnzRjxgxpz5490uzZs6XOnTtLa9eute1z7NgxqWPHjlLHjh2l1q1bS8OHD5fc3d0LheSqK8Vdg19//VWSpII/jrk3TdOmTZO8vb2lU6dO2cXm8qQ04z9w4IDUqVMnacaMGZJWq5UOHz5sL7PLldJcA0mSJL1eL73xxhuSn59frXgPSJIk/fTTT1L37t0L/c/1ev0dn1cUnKziZGVl8fTTT5ORkUHfvn1Zt24dzs7OvPfee3To0KHY1/3444+0b9+eVq1aVaK15U9Jxm+1WlEqlURFRbF27VoOHTpE/fr1GT9+PM2aNbPzCMpOad8DCQkJLFy4kO+++47ff/+d9u3bV77R5YhOp2PcuHEEBgaycOFC2/ohQ4bg7OzMunXrbO+B6OhowsPD2bFjB8HBwdx333014j1QkmtwMwsWLODZZ5/lyJEjt/yuqA6Udvz//fcfPXr0wMvLi61bt9KxY8fKNrncKe01+OOPP/j111/ZuXMn69evrzXvAbPZTEZGBp6enkD5FJysOQksNRQnJyeGDBnCoEGDePrpp3n66afJzMxk1qxZHD16tND+uSUBHn300WovkqBk41cqlVgsFkJCQpg+fTqrVq3igw8+qBE/kFD694CXlxcTJkzgv//+q/YiCSAlJQVfX1/uvfdeIC9Pa/jw4QVa10iSRHBwMMOHD+eLL77g5ZdfrjHvgZJcg5vveUePHs2lS5eq/Q8klH78devWpWvXruzZs6dGiCQo/TXo2LEjHTp0YMeOHbXqPaBWq20iCcqplt4d+6IEdmPNmjVS//79pfvuu88WWsrOzpZiYmLsa1glUdz4889+qenUtvdA/ryj3PDCsmXLpL59+xZYl1uNvCZS269BScefnJwsSZL8eahplPQapKSkSJJU82Y9l3T8Op2uXM8rPErViFxv0ahRo3jyySfJzMzknXfe4dChQ7z44ouEhYVhMBhqbM2U242/U6dONXr8UPveA7njGDBggO157h1iRkYGycnJtnWzZ89mypQpBbxMNYE7uQb5Z4hWd0o7/ieeeAKTyYSDg4PdbC5vSnsNpk6dislkqjH140o7/smTJ2M2m8vte1BdLkcRVCi5bwCVSmX7AnjooYdQKBR8++23DB06FIvFwt9//11jpn/mp7aPH2rvNcj9Mswdv0KhwGw2o1ar8fDwwM3NDYVCwdtvv81HH33EgQMHUKtr1tfanVyDmiQSavv4QVwDe38PCI9SFcdisaBQKEhLSwOwdQYH2atgsViwWCzs2bOHLl262NPUCqG2jx/ENbh5/LlfgFqtFm9vb958800++eQT9u/fXyNyMYqitl+D2j5+ENfAruMv10CeoFzJrah77do1qW3bttIff/xh22YymaRXXnlFcnBwqDHTn2+mto9fksQ1uNX4v/32W0mhUNSYGkHFUduvQW0fvySJa2Dv8dcsH3U15dq1a2zdupWsrCyaNGnC0KFDAdlzcOXKFe6++27uvfdeW4NTkNV0WFgYhw4dol27dvYyvVyo7eMHcQ3uZPz16tUjLCyMlStX0qJFC3uZXm7U9mtQ28cP4hpU2fFXiPwSlJgTJ05I/v7+Ut++faU+ffpISqVSevzxx6X//vtPkiRJmjp1qjR58uQChcRuLipWnant45ckcQ3uZPy5xMfHV7a5FUJtvwa1ffySJK5BVR6/EEp2JDExUWrXrp305ptv2tZt3LhRUiqV0r333isdOXKkxk3vzE9tH78kiWtwp+OvSdektl+D2j5+SRLXoKqPXyRz25HU1FTUajVjx45FkiSMRiPt27enRYsWHD58mPfff9+WuFYTqe3jB3EN7nT8NanZc22/BrV9/CCuQVUff824ytWU9PR0wsPDiY2NRaFQoNFoyMzMJCQkhM8++4wNGzawdu1ae5tZYdT28YO4BrV9/CCuQW0fP4hrUOXHXyl+K0GRmEwm6fHHH5caN24szZs3T1q9erXk5eUlPf30/7d39zFV1v8fx58HuZEEQ/GuASplChQp3tBqmjFrNLfKtdR0paVW6vpDXWbTZTbcINOWtVWaLS1v/rA2tVytu6mrXIQQIiwaTMc0JylgcjA4ct6/P/x6kuqk/QKO53xej3+Y1zmHfV4vrj/eXtd1rmuhmZktWrTIHnnkEfP5fBF1Tcolruc3Uweu5zdTB67nN1MH13p+DUrd6MSJE1ZcXGyffvpp4OneR44csQULFlhycrLdcsstHc7RPvHEE5afnx+q5XY61/ObqQPX85upA9fzm6mDcMuvQamblJeXW1pammVlZVl0dLSNGjXKNm7caC0tLWZmdvz48Q7PKvP7/TZr1ixbtmyZ+f3+sP9fhOv5zdSB6/nN1IHr+c3UQTjm16DUDX799VfLzMy0ZcuW2dGjR62+vt5mzJhh48aNs0WLFllTU1OH99fW1try5cstKSnJqqqqQrTqzuN6fjN14Hp+M3Xgen4zdRCu+TUodYOKigobOnSolZeXB7a1trbaypUrLTc311asWGHnz583s4s70vz5823EiBFWWloaqiV3Ktfzm6kD1/ObqQPX85upg3DNr0GpG1RXV1t6enrgtus+ny/wc+nSpTZq1Cg7cOBA4P21tbV2/PjxkKy1K7ie30wduJ7fTB24nt9MHYRrfo/Z/56uKV2mtbWV8ePHM2jQIHbt2kWPHj0CTz42M0aOHElOTg5btmwJ9VK7hOv5QR24nh/Ugev5QR2Ea37dR6mL+f1+4uLieO+99zhw4AALFiwACOwYHo+HBx54gPr6+hCvtGu4nh/Ugev5QR24nh/UQTjn16DUxaKiomhvb+fWW29ly5Yt7Nixg1mzZnHq1KnAe44ePUqfPn1ob28P4Uq7huv5QR24nh/Ugev5QR2Ec36deutkfr+/w23VLx1WbG5uprW1lR9//JGZM2cyZMgQ+vbtS3JyMrt37+bgwYNkZ2eHcOWdw/X8oA5czw/qwPX8oA4iKb+OKHWS06dPA39MzQDt7e1ER0dz7Ngxhg8fzg8//MCkSZOorKxk8uTJpKSkMGDAAIqLi6+5HePfcj0/qAPX84M6cD0/qIOIzN+9145HpurqaktMTLQnn3wysO3S3Ubr6uqsX79+NnfuXPP7/YHtl26aFQlPf3Y9v5k6cD2/mTpwPb+ZOojU/Dqi1AmqqqqIj4+noqKCp59+GoAePXrQ1tbGnj17eOyxx9iwYQMej4cePXp0+KzH4wnFkjuV6/lBHbieH9SB6/lBHURqfg1KnSAuLo6kpCSmTJnCwYMHmT9/PgCxsbE8+OCDvPrqq0F3imt557harucHdeB6flAHrucHdRCp+aNDvYBIkJ2dzZgxY5g3bx6xsbFs3ryZJUuWcPbsWXJzc5kzZw4xMTGhXmaXcT0/qAPX84M6cD0/qIOIzR/qc3+RwOv12m233WZlZWXm9Xpt48aNlpycbB6Pxw4fPmxmf5ynjUSu5zdTB67nN1MHruc3UweRml+n3v4jn89HXFwcgwYNorm5meuuu46vvvoKn8/HsGHD2LRpE8BfDjdGCtfzgzpwPT+oA9fzgzqI5Pw69fYv/PLLL5SWltLW1sbQoUMZPXp04DDimDFjqKmpYePGjRw4cICPP/6YiooKioqKiI6OZt26dSFe/X/nen5QB67nB3Xgen5QB87lD/UhrXBx+PBhu/HGGy03N9f69etnY8eOtZ07dwZeX7VqlXk8HktPT7dDhw6ZmVljY6O9+eabVltbG6pldxrX85upA9fzm6kD1/ObqQMX82tQugo1NTWWmppqzz33nDU1NVlJSYnNnj3b5syZ0+HpxwsXLrTi4mIzC497Q1wt1/ObqQPX85upA9fzm6kDV/NrULqC1tZWW7JkiU2bNs1aW1sD2999911LTk6206dPh3B1Xc/1/GbqwPX8ZurA9fxm6sDl/LpG6Qr8fj+pqalkZmYSGxsbeMrxnXfeSUJCAj6f728/c/kzbsKZ6/lBHbieH9SB6/lBHbicX4PSFfTs2ZMpU6aQnp7eYXtSUhIxMTEddo6ysjJycnIiYse4xPX8oA5czw/qwPX8oA5czh8ZKTrZyZMnKS4u5rPPPsPv9wd2jPb29sDdQ8+ePUtjY2PgMytXrmTSpEmcOXMGMwvJujuL6/lBHbieH9SB6/lBHbieP6D7z/Zd28rLy23IkCE2fPhwu/766y0jI8O2b99uZ86cMbM/Lkyrrq62/v37W0NDgxUUFFh8fLyVlJSEcumdwvX8ZurA9fxm6sD1/GbqwPX8l9OgdJn6+nrLyMiw5cuXW21trZ04ccKmT59umZmZ9uKLL1p9fX3gvadOnbKcnBybPn26xcbGRsSO4Xp+M3Xgen4zdeB6fjN14Hr+P9OgdJnKykobOnToX/7Qy5Yts+zsbFuzZo15vV4zM6uqqjKPx2Px8fFWVlYWgtV2Ptfzm6kD1/ObqQPX85upA9fz/5muUbqMz+fjwoULtLS0AHD+/HkAioqKyMvL46233qKmpgaAPn36sHDhQkpLSxk1alSoltypXM8P6sD1/KAOXM8P6sD1/H/mMYuUq606R25uLgkJCXz99dcAtLa2EhcXB8C4ceMYNmwYO3bsAOD333+nZ8+eIVtrV3A9P6gD1/ODOnA9P6gD1/NfzukjSl6vl3PnzvHbb78Ftm3YsIHKykpmzpwJQFxcHBcuXADgrrvuwuv1Bt4b7juG6/lBHbieH9SB6/lBHbie/0qcHZSqqqp46KGHmDhxIpmZmWzbtg2AzMxM1q9fzxdffMHUqVPx+XyBe0HU19fTq1cvLly4EPZfe3Q9P6gD1/ODOnA9P6gD1/NflVBdHBVKlZWVlpycbIsXL7Zt27bZkiVLLCYmxkpLS83MzOv12p49eyw1NdUyMjJsypQpNm3aNOvVq5dVVFSEePX/nev5zdSB6/nN1IHr+c3Ugev5r5Zz1yg1NDQwY8YMMjIyWL9+fWB7Xl4e2dnZvP7664Ft586dY/Xq1TQ0NNCzZ08WLFhAVlZWKJbdaVzPD+rA9fygDlzPD+rA9fz/hnOPMPH5fDQ1NfHwww8DfzyLJj09nYaGBgDs4m0TSExM5OWXX+7wvnDnen5QB67nB3Xgen5QB67n/zfcSgsMHDiQrVu3MmHCBODirdgBUlJSAn98j8dDVFRUhwvbLt2uPdy5nh/Ugev5QR24nh/Ugev5/w3nBiWAm2++Gbg4GcfExAAXJ+f6+vrAewoLC9m0aVPgKv9I2jlczw/qwPX8oA5czw/qwPX8V8u5U2+Xi4qKwswCf/hLU/TKlStZvXo1ZWVlREdHbkWu5wd14Hp+UAeu5wd14Hr+K3HyiNLlLl3LHh0dTVpaGmvXrmXNmjWUlJQwcuTIEK+u67meH9SB6/lBHbieH9SB6/n/ibsj4v9cmpxjYmJ455136N27N9988w2jR48O8cq6h+v5QR24nh/Ugev5QR24nv+fOH9E6ZL8/HwAvvvuO8aOHRvi1XQ/1/ODOnA9P6gD1/ODOnA9/99x7j5K/8Tr9dKrV69QLyNkXM8P6sD1/KAOXM8P6sD1/H+mQUlEREQkCJ16ExEREQlCg5KIiIhIEBqURERERILQoCQiIiIShAYlERERkSA0KImIiIgEoUFJRCLK3XffzaJFi0K9DBGJEBqURMRZ+/btw+Px0NTUFOqliMg1SoOSiIiISBAalEQkbHm9XmbNmkVCQgI33HAD69at6/D6Bx98wNixY0lMTGTQoEHMnDmT+vp6AI4dO0ZeXh4Affr0wePx8PjjjwPg9/spLCwkPT2d+Ph4Ro4cyYcfftit2UTk2qBBSUTC1tKlS9m/fz+7d+/m888/Z9++fZSWlgZe9/l8FBQUUF5ezq5duzh27FhgGEpLS+Ojjz4CoLq6mpMnT7J+/XoACgsLef/993n77beprKxk8eLFPProo+zfv7/bM4pIaOlZbyISlpqbm0lOTmbr1q1MnToVgIaGBlJTU3nqqad47bXX/vKZkpISxo0bx7lz50hISGDfvn3k5eXR2NhIUlISAK2trfTt25cvv/ySO+64I/DZefPm0dLSwvbt27sjnohcI6JDvQARkf+P2tpa2trauP322wPb+vbty4gRIwL/PnToEKtWraK8vJzGxkb8fj8AdXV1ZGVl/e3vrampoaWlhXvvvbfD9ra2NnJycrogiYhcyzQoiUhE8nq95Ofnk5+fz7Zt2+jfvz91dXXk5+fT1tYW9HPNzc0A7N27l5SUlA6vxcXFdemaReTao0FJRMLSTTfdRExMDN9//z2DBw8GoLGxkZ9//pmJEyfy008/cebMGYqKikhLSwMunnq7XGxsLADt7e2BbVlZWcTFxVFXV8fEiRO7KY2IXKs0KIlIWEpISGDu3LksXbqU5ORkBgwYwIoVK4iKuvgdlcGDBxMbG8sbb7zB/PnzOXLkCAUFBR1+x5AhQ/B4PHzyySdMnjyZ+Ph4EhMTefbZZ1m8eDF+v5/x48dz9uxZvv32W3r37s3s2bNDEVdEQkTfehORsPXKK68wYcIE7r//fu655x7Gjx/PmDFjAOjfvz+bN29m586dZGVlUVRUxNq1azt8PiUlhZdeeonnn3+egQMH8swzzwBQUFDACy+8QGFhIZmZmdx3333s3buX9PT0bs8oIqGlb72JiIiIBKEjSiIiIiJBaFASERERCUKDkoiIiEgQGpREREREgtCgJCIiIhKEBiURERGRIDQoiYiIiAShQUlEREQkCA1KIiIiIkFoUBIREREJQoOSiIiISBD/B12b8dOSbSmhAAAAAElFTkSuQmCC", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "downloads_per_day.unstack().plot.line(rot=45, ylabel=\"daily downloads\", ylim=(0, 2e7))" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "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.12.6" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/notebooks/experimental/ai_operators.ipynb b/notebooks/experimental/ai_operators.ipynb deleted file mode 100644 index e054484a0bf..00000000000 --- a/notebooks/experimental/ai_operators.ipynb +++ /dev/null @@ -1,73 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "title-cell", - "metadata": {}, - "source": [ - "# AI Operators (Experimental)" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": { - "id": "UYeZd_I8iouP" - }, - "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": { - "id": "rWJnGj2ViouP" - }, - "source": [ - "All AI functions have moved to the `bigframes.bigquery.ai` module.\n", - "\n", - "The tutorial notebook for AI functions is located at https://github.com/googleapis/python-bigquery-dataframes/blob/main/notebooks/generative_ai/ai_functions.ipynb\n", - "\n", - "For `ai.forecast`, see https://github.com/googleapis/python-bigquery-dataframes/blob/main/notebooks/generative_ai/bq_dataframes_ai_forecast.ipynb" - ] - } - ], - "metadata": { - "colab": { - "include_colab_link": true, - "provenance": [] - }, - "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.17" - } - }, - "nbformat": 4, - "nbformat_minor": 0 -} diff --git a/notebooks/experimental/longer_ml_demo.ipynb b/notebooks/experimental/longer_ml_demo.ipynb new file mode 100644 index 00000000000..793ff58ecdf --- /dev/null +++ b/notebooks/experimental/longer_ml_demo.ipynb @@ -0,0 +1,1925 @@ +{ + "cells": [ + { + "attachments": {}, + "cell_type": "markdown", + "id": "71fbfc47", + "metadata": {}, + "source": [ + "**Note: this notebook requires changes not yet checked in**\n", + "\n", + "# Introduction\n", + "\n", + "This is a prototype for how a minimal SKLearn-like wrapper for BQML might work in BigQuery DataFrames.\n", + "\n", + "Disclaimer - this is not a polished design or a robust implementation, this is a quick prototype to workshop some ideas. Design will be next.\n", + "\n", + "What is BigQuery DataFrame?\n", + "- Pandas API for BigQuery\n", + "- Lets data scientists quickly iterate and prepare their data as they do in Pandas, but executed by BigQuery\n", + "\n", + "What is meant by SKLearn-like?\n", + "- Follow the API design practices from the SKLearn project\n", + " - [API design for machine learning software: experiences from the scikit-learn project](https://arxiv.org/pdf/1309.0238.pdf)\n", + "- Not a copy of, or compatible with, SKLearn\n", + "\n", + "Briefly, patterns taken from SKLearn are:\n", + "- Models and transforms are 'Estimators'\n", + " - A bundle of parameters with a consistent way to initialize/get/set\n", + " - And a .fit(..) method to fit to training data\n", + "- Models additionally have a .predict(..)\n", + "- By default, these objects are transient, making them easy to play around with. No need to give them names or decide how to persist them.\n", + "\n", + "\n", + "Design goals:\n", + "- Zero friction ML capabilities for BigQuery DataFrames users (no extra auth, configuration, etc)\n", + "- Offers first class integration with the Pandas-like BigQuery DataFrames API\n", + "- Uses SKLearn-like design patterns that feel familiar to data scientists\n", + "- Also a first class BigQuery experience\n", + " - Offers BigQuery's scalability and storage / compute management\n", + " - Works naturally with BigQuery's other interfaces, e.g. GUI and SQL\n", + " - BQML features" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "id": "345c2163", + "metadata": {}, + "source": [ + "# Linear regression tutorial\n", + "\n", + "Adapted from the \"Penguin weight\" Linear Regression tutorial for BQML: https://cloud.google.com/bigquery-ml/docs/linear-regression-tutorial\n" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "id": "03c9e168", + "metadata": {}, + "source": [ + "## Setting the scene\n", + "\n", + "Our conservationists have sent us some measurements of penguins found in the Antarctic islands. They say that some of the body mass measurements for the Adelie penguins are missing, and ask if we can use some data science magic to estimate them. Sounds like a job for a linear regression!\n", + "\n", + "Lets take a look at the data..." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "d7a03de2-c0ef-4f80-9cd5-f96e87cf2d54", + "metadata": { + "tags": [] + }, + "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", + "
tag_numberspeciesislandculmen_length_mmculmen_depth_mmflipper_length_mmbody_mass_gsex
01225Gentoo penguin (Pygoscelis papua)Biscoe<NA><NA><NA><NA><NA>
11278Gentoo penguin (Pygoscelis papua)Biscoe42.013.5210.04150.0FEMALE
21275Gentoo penguin (Pygoscelis papua)Biscoe46.513.5210.04550.0FEMALE
31233Gentoo penguin (Pygoscelis papua)Biscoe43.314.0208.04575.0FEMALE
41311Gentoo penguin (Pygoscelis papua)Biscoe47.514.0212.04875.0FEMALE
51316Gentoo penguin (Pygoscelis papua)Biscoe49.114.5212.04625.0FEMALE
61313Gentoo penguin (Pygoscelis papua)Biscoe45.514.5212.04750.0FEMALE
71381Gentoo penguin (Pygoscelis papua)Biscoe47.614.5215.05400.0MALE
81377Gentoo penguin (Pygoscelis papua)Biscoe45.114.5207.05050.0FEMALE
91380Gentoo penguin (Pygoscelis papua)Biscoe45.114.5215.05000.0FEMALE
101257Gentoo penguin (Pygoscelis papua)Biscoe46.214.5209.04800.0FEMALE
111336Gentoo penguin (Pygoscelis papua)Biscoe46.514.5213.04400.0FEMALE
121237Gentoo penguin (Pygoscelis papua)Biscoe43.214.5208.04450.0FEMALE
131302Gentoo penguin (Pygoscelis papua)Biscoe48.515.0219.04850.0FEMALE
141325Gentoo penguin (Pygoscelis papua)Biscoe49.115.0228.05500.0MALE
151285Gentoo penguin (Pygoscelis papua)Biscoe47.515.0218.04950.0FEMALE
161242Gentoo penguin (Pygoscelis papua)Biscoe49.615.0216.04750.0MALE
171246Gentoo penguin (Pygoscelis papua)Biscoe47.715.0216.04750.0FEMALE
181320Gentoo penguin (Pygoscelis papua)Biscoe45.515.0220.05000.0MALE
191244Gentoo penguin (Pygoscelis papua)Biscoe46.415.0216.04700.0FEMALE
\n", + "
[347 rows x 8 columns in total]" + ], + "text/plain": [ + " tag_number species island culmen_length_mm \\\n", + "0 1225 Gentoo penguin (Pygoscelis papua) Biscoe \n", + "1 1278 Gentoo penguin (Pygoscelis papua) Biscoe 42.0 \n", + "2 1275 Gentoo penguin (Pygoscelis papua) Biscoe 46.5 \n", + "3 1233 Gentoo penguin (Pygoscelis papua) Biscoe 43.3 \n", + "4 1311 Gentoo penguin (Pygoscelis papua) Biscoe 47.5 \n", + "5 1316 Gentoo penguin (Pygoscelis papua) Biscoe 49.1 \n", + "6 1313 Gentoo penguin (Pygoscelis papua) Biscoe 45.5 \n", + "7 1381 Gentoo penguin (Pygoscelis papua) Biscoe 47.6 \n", + "8 1377 Gentoo penguin (Pygoscelis papua) Biscoe 45.1 \n", + "9 1380 Gentoo penguin (Pygoscelis papua) Biscoe 45.1 \n", + "10 1257 Gentoo penguin (Pygoscelis papua) Biscoe 46.2 \n", + "11 1336 Gentoo penguin (Pygoscelis papua) Biscoe 46.5 \n", + "12 1237 Gentoo penguin (Pygoscelis papua) Biscoe 43.2 \n", + "13 1302 Gentoo penguin (Pygoscelis papua) Biscoe 48.5 \n", + "14 1325 Gentoo penguin (Pygoscelis papua) Biscoe 49.1 \n", + "15 1285 Gentoo penguin (Pygoscelis papua) Biscoe 47.5 \n", + "16 1242 Gentoo penguin (Pygoscelis papua) Biscoe 49.6 \n", + "17 1246 Gentoo penguin (Pygoscelis papua) Biscoe 47.7 \n", + "18 1320 Gentoo penguin (Pygoscelis papua) Biscoe 45.5 \n", + "19 1244 Gentoo penguin (Pygoscelis papua) Biscoe 46.4 \n", + "20 1390 Gentoo penguin (Pygoscelis papua) Biscoe 50.7 \n", + "21 1379 Gentoo penguin (Pygoscelis papua) Biscoe 47.8 \n", + "22 1267 Gentoo penguin (Pygoscelis papua) Biscoe 50.1 \n", + "23 1389 Gentoo penguin (Pygoscelis papua) Biscoe 47.2 \n", + "24 1269 Gentoo penguin (Pygoscelis papua) Biscoe 49.6 \n", + "\n", + " culmen_depth_mm flipper_length_mm body_mass_g sex \n", + "0 \n", + "1 13.5 210.0 4150.0 FEMALE \n", + "2 13.5 210.0 4550.0 FEMALE \n", + "3 14.0 208.0 4575.0 FEMALE \n", + "4 14.0 212.0 4875.0 FEMALE \n", + "5 14.5 212.0 4625.0 FEMALE \n", + "6 14.5 212.0 4750.0 FEMALE \n", + "7 14.5 215.0 5400.0 MALE \n", + "8 14.5 207.0 5050.0 FEMALE \n", + "9 14.5 215.0 5000.0 FEMALE \n", + "10 14.5 209.0 4800.0 FEMALE \n", + "11 14.5 213.0 4400.0 FEMALE \n", + "12 14.5 208.0 4450.0 FEMALE \n", + "13 15.0 219.0 4850.0 FEMALE \n", + "14 15.0 228.0 5500.0 MALE \n", + "15 15.0 218.0 4950.0 FEMALE \n", + "16 15.0 216.0 4750.0 MALE \n", + "17 15.0 216.0 4750.0 FEMALE \n", + "18 15.0 220.0 5000.0 MALE \n", + "19 15.0 216.0 4700.0 FEMALE \n", + "20 15.0 223.0 5550.0 MALE \n", + "21 15.0 215.0 5650.0 MALE \n", + "22 15.0 225.0 5000.0 MALE \n", + "23 15.5 215.0 4975.0 FEMALE \n", + "24 16.0 225.0 5700.0 MALE \n", + "...\n", + "\n", + "[347 rows x 8 columns]" + ] + }, + "execution_count": 1, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import bigframes.pandas\n", + "\n", + "df = bigframes.pandas.read_gbq(\"bigframes-dev.bqml_tutorial.penguins\")\n", + "df" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "id": "359524c4", + "metadata": {}, + "source": [ + "First we note that while we have a default numbered index generated by BigQuery, actually the penguins are uniquely identified by their tags.\n", + "\n", + "Lets make the data a bit friendlier to work with by setting the tag number column as the index." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "93d01411", + "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", + "
speciesislandculmen_length_mmculmen_depth_mmflipper_length_mmbody_mass_gsex
tag_number
1225Gentoo penguin (Pygoscelis papua)Biscoe<NA><NA><NA><NA><NA>
1278Gentoo penguin (Pygoscelis papua)Biscoe42.013.5210.04150.0FEMALE
1275Gentoo penguin (Pygoscelis papua)Biscoe46.513.5210.04550.0FEMALE
1233Gentoo penguin (Pygoscelis papua)Biscoe43.314.0208.04575.0FEMALE
1311Gentoo penguin (Pygoscelis papua)Biscoe47.514.0212.04875.0FEMALE
1316Gentoo penguin (Pygoscelis papua)Biscoe49.114.5212.04625.0FEMALE
1313Gentoo penguin (Pygoscelis papua)Biscoe45.514.5212.04750.0FEMALE
1381Gentoo penguin (Pygoscelis papua)Biscoe47.614.5215.05400.0MALE
1377Gentoo penguin (Pygoscelis papua)Biscoe45.114.5207.05050.0FEMALE
1380Gentoo penguin (Pygoscelis papua)Biscoe45.114.5215.05000.0FEMALE
1257Gentoo penguin (Pygoscelis papua)Biscoe46.214.5209.04800.0FEMALE
1336Gentoo penguin (Pygoscelis papua)Biscoe46.514.5213.04400.0FEMALE
1237Gentoo penguin (Pygoscelis papua)Biscoe43.214.5208.04450.0FEMALE
1302Gentoo penguin (Pygoscelis papua)Biscoe48.515.0219.04850.0FEMALE
1325Gentoo penguin (Pygoscelis papua)Biscoe49.115.0228.05500.0MALE
1285Gentoo penguin (Pygoscelis papua)Biscoe47.515.0218.04950.0FEMALE
1242Gentoo penguin (Pygoscelis papua)Biscoe49.615.0216.04750.0MALE
1246Gentoo penguin (Pygoscelis papua)Biscoe47.715.0216.04750.0FEMALE
1320Gentoo penguin (Pygoscelis papua)Biscoe45.515.0220.05000.0MALE
1244Gentoo penguin (Pygoscelis papua)Biscoe46.415.0216.04700.0FEMALE
\n", + "
[347 rows x 7 columns in total]" + ], + "text/plain": [ + " species island culmen_length_mm \\\n", + "tag_number \n", + "1225 Gentoo penguin (Pygoscelis papua) Biscoe \n", + "1278 Gentoo penguin (Pygoscelis papua) Biscoe 42.0 \n", + "1275 Gentoo penguin (Pygoscelis papua) Biscoe 46.5 \n", + "1233 Gentoo penguin (Pygoscelis papua) Biscoe 43.3 \n", + "1311 Gentoo penguin (Pygoscelis papua) Biscoe 47.5 \n", + "1316 Gentoo penguin (Pygoscelis papua) Biscoe 49.1 \n", + "1313 Gentoo penguin (Pygoscelis papua) Biscoe 45.5 \n", + "1381 Gentoo penguin (Pygoscelis papua) Biscoe 47.6 \n", + "1377 Gentoo penguin (Pygoscelis papua) Biscoe 45.1 \n", + "1380 Gentoo penguin (Pygoscelis papua) Biscoe 45.1 \n", + "1257 Gentoo penguin (Pygoscelis papua) Biscoe 46.2 \n", + "1336 Gentoo penguin (Pygoscelis papua) Biscoe 46.5 \n", + "1237 Gentoo penguin (Pygoscelis papua) Biscoe 43.2 \n", + "1302 Gentoo penguin (Pygoscelis papua) Biscoe 48.5 \n", + "1325 Gentoo penguin (Pygoscelis papua) Biscoe 49.1 \n", + "1285 Gentoo penguin (Pygoscelis papua) Biscoe 47.5 \n", + "1242 Gentoo penguin (Pygoscelis papua) Biscoe 49.6 \n", + "1246 Gentoo penguin (Pygoscelis papua) Biscoe 47.7 \n", + "1320 Gentoo penguin (Pygoscelis papua) Biscoe 45.5 \n", + "1244 Gentoo penguin (Pygoscelis papua) Biscoe 46.4 \n", + "1390 Gentoo penguin (Pygoscelis papua) Biscoe 50.7 \n", + "1379 Gentoo penguin (Pygoscelis papua) Biscoe 47.8 \n", + "1267 Gentoo penguin (Pygoscelis papua) Biscoe 50.1 \n", + "1389 Gentoo penguin (Pygoscelis papua) Biscoe 47.2 \n", + "1269 Gentoo penguin (Pygoscelis papua) Biscoe 49.6 \n", + "\n", + " culmen_depth_mm flipper_length_mm body_mass_g sex \n", + "tag_number \n", + "1225 \n", + "1278 13.5 210.0 4150.0 FEMALE \n", + "1275 13.5 210.0 4550.0 FEMALE \n", + "1233 14.0 208.0 4575.0 FEMALE \n", + "1311 14.0 212.0 4875.0 FEMALE \n", + "1316 14.5 212.0 4625.0 FEMALE \n", + "1313 14.5 212.0 4750.0 FEMALE \n", + "1381 14.5 215.0 5400.0 MALE \n", + "1377 14.5 207.0 5050.0 FEMALE \n", + "1380 14.5 215.0 5000.0 FEMALE \n", + "1257 14.5 209.0 4800.0 FEMALE \n", + "1336 14.5 213.0 4400.0 FEMALE \n", + "1237 14.5 208.0 4450.0 FEMALE \n", + "1302 15.0 219.0 4850.0 FEMALE \n", + "1325 15.0 228.0 5500.0 MALE \n", + "1285 15.0 218.0 4950.0 FEMALE \n", + "1242 15.0 216.0 4750.0 MALE \n", + "1246 15.0 216.0 4750.0 FEMALE \n", + "1320 15.0 220.0 5000.0 MALE \n", + "1244 15.0 216.0 4700.0 FEMALE \n", + "1390 15.0 223.0 5550.0 MALE \n", + "1379 15.0 215.0 5650.0 MALE \n", + "1267 15.0 225.0 5000.0 MALE \n", + "1389 15.5 215.0 4975.0 FEMALE \n", + "1269 16.0 225.0 5700.0 MALE \n", + "...\n", + "\n", + "[347 rows x 7 columns]" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "df = df.set_index(\"tag_number\")\n", + "df" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "id": "f95fda6a", + "metadata": {}, + "source": [ + "We saw in the first view that there were some missing values. We're especially interested in observations that are missing just the body_mass_g, so lets look at those:" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "941cb6c3-8c54-42ce-a945-4fa604176b2e", + "metadata": { + "tags": [] + }, + "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", + "
speciesislandculmen_length_mmculmen_depth_mmflipper_length_mmbody_mass_gsex
tag_number
1225Gentoo penguin (Pygoscelis papua)Biscoe<NA><NA><NA><NA><NA>
1393Adelie Penguin (Pygoscelis adeliae)Torgersen<NA><NA><NA><NA><NA>
1524Adelie Penguin (Pygoscelis adeliae)Dream41.620.0204.0<NA>MALE
1523Adelie Penguin (Pygoscelis adeliae)Dream38.017.5194.0<NA>FEMALE
1525Adelie Penguin (Pygoscelis adeliae)Dream36.318.5194.0<NA>MALE
\n", + "
[5 rows x 7 columns in total]" + ], + "text/plain": [ + " species island culmen_length_mm \\\n", + "tag_number \n", + "1225 Gentoo penguin (Pygoscelis papua) Biscoe \n", + "1393 Adelie Penguin (Pygoscelis adeliae) Torgersen \n", + "1524 Adelie Penguin (Pygoscelis adeliae) Dream 41.6 \n", + "1523 Adelie Penguin (Pygoscelis adeliae) Dream 38.0 \n", + "1525 Adelie Penguin (Pygoscelis adeliae) Dream 36.3 \n", + "\n", + " culmen_depth_mm flipper_length_mm body_mass_g sex \n", + "tag_number \n", + "1225 \n", + "1393 \n", + "1524 20.0 204.0 MALE \n", + "1523 17.5 194.0 FEMALE \n", + "1525 18.5 194.0 MALE \n", + "\n", + "[5 rows x 7 columns]" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "df[df.body_mass_g.isnull()]" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "id": "a70c2027", + "metadata": {}, + "source": [ + "Here we see three Adelie penguins with tag numbers 1523, 1524, 1525 are missing their body_mass_g but have the other measurements. These are the ones we need to guess. We can do this by training a statistical model on the measurements that we do have, and then using it to predict the missing values.\n", + "\n", + "Our conservationists warned us that trying to generalize across species is a bad idea, so for now lets just try building a model for Adelie penguins. We can revisit it later and see if including the other observations improves the model performance." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "93ff013a", + "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", + "
speciesislandculmen_length_mmculmen_depth_mmflipper_length_mmbody_mass_gsex
tag_number
1172Adelie Penguin (Pygoscelis adeliae)Dream32.115.5188.03050.0FEMALE
1371Adelie Penguin (Pygoscelis adeliae)Biscoe37.716.0183.03075.0FEMALE
1417Adelie Penguin (Pygoscelis adeliae)Torgersen38.617.0188.02900.0FEMALE
1204Adelie Penguin (Pygoscelis adeliae)Dream40.717.0190.03725.0MALE
1251Adelie Penguin (Pygoscelis adeliae)Biscoe37.617.0185.03600.0FEMALE
1422Adelie Penguin (Pygoscelis adeliae)Torgersen35.717.0189.03350.0FEMALE
1394Adelie Penguin (Pygoscelis adeliae)Torgersen40.217.0176.03450.0FEMALE
1163Adelie Penguin (Pygoscelis adeliae)Dream36.417.0195.03325.0FEMALE
1329Adelie Penguin (Pygoscelis adeliae)Biscoe38.117.0181.03175.0FEMALE
1406Adelie Penguin (Pygoscelis adeliae)Torgersen44.118.0210.04000.0MALE
1196Adelie Penguin (Pygoscelis adeliae)Dream36.518.0182.03150.0FEMALE
1228Adelie Penguin (Pygoscelis adeliae)Biscoe41.618.0192.03950.0MALE
1412Adelie Penguin (Pygoscelis adeliae)Torgersen40.318.0195.03250.0FEMALE
1142Adelie Penguin (Pygoscelis adeliae)Dream35.718.0202.03550.0FEMALE
1430Adelie Penguin (Pygoscelis adeliae)Torgersen33.519.0190.03600.0FEMALE
1333Adelie Penguin (Pygoscelis adeliae)Biscoe43.219.0197.04775.0MALE
1414Adelie Penguin (Pygoscelis adeliae)Torgersen38.719.0195.03450.0FEMALE
1197Adelie Penguin (Pygoscelis adeliae)Dream41.119.0182.03425.0MALE
1443Adelie Penguin (Pygoscelis adeliae)Torgersen40.619.0199.04000.0MALE
1295Adelie Penguin (Pygoscelis adeliae)Biscoe41.020.0203.04725.0MALE
\n", + "
[146 rows x 7 columns in total]" + ], + "text/plain": [ + " species island culmen_length_mm \\\n", + "tag_number \n", + "1172 Adelie Penguin (Pygoscelis adeliae) Dream 32.1 \n", + "1371 Adelie Penguin (Pygoscelis adeliae) Biscoe 37.7 \n", + "1417 Adelie Penguin (Pygoscelis adeliae) Torgersen 38.6 \n", + "1204 Adelie Penguin (Pygoscelis adeliae) Dream 40.7 \n", + "1251 Adelie Penguin (Pygoscelis adeliae) Biscoe 37.6 \n", + "1422 Adelie Penguin (Pygoscelis adeliae) Torgersen 35.7 \n", + "1394 Adelie Penguin (Pygoscelis adeliae) Torgersen 40.2 \n", + "1163 Adelie Penguin (Pygoscelis adeliae) Dream 36.4 \n", + "1329 Adelie Penguin (Pygoscelis adeliae) Biscoe 38.1 \n", + "1406 Adelie Penguin (Pygoscelis adeliae) Torgersen 44.1 \n", + "1196 Adelie Penguin (Pygoscelis adeliae) Dream 36.5 \n", + "1228 Adelie Penguin (Pygoscelis adeliae) Biscoe 41.6 \n", + "1412 Adelie Penguin (Pygoscelis adeliae) Torgersen 40.3 \n", + "1142 Adelie Penguin (Pygoscelis adeliae) Dream 35.7 \n", + "1430 Adelie Penguin (Pygoscelis adeliae) Torgersen 33.5 \n", + "1333 Adelie Penguin (Pygoscelis adeliae) Biscoe 43.2 \n", + "1414 Adelie Penguin (Pygoscelis adeliae) Torgersen 38.7 \n", + "1197 Adelie Penguin (Pygoscelis adeliae) Dream 41.1 \n", + "1443 Adelie Penguin (Pygoscelis adeliae) Torgersen 40.6 \n", + "1295 Adelie Penguin (Pygoscelis adeliae) Biscoe 41.0 \n", + "1207 Adelie Penguin (Pygoscelis adeliae) Dream 38.8 \n", + "1349 Adelie Penguin (Pygoscelis adeliae) Biscoe 38.2 \n", + "1350 Adelie Penguin (Pygoscelis adeliae) Biscoe 37.8 \n", + "1351 Adelie Penguin (Pygoscelis adeliae) Biscoe 38.1 \n", + "1116 Adelie Penguin (Pygoscelis adeliae) Dream 37.0 \n", + "\n", + " culmen_depth_mm flipper_length_mm body_mass_g sex \n", + "tag_number \n", + "1172 15.5 188.0 3050.0 FEMALE \n", + "1371 16.0 183.0 3075.0 FEMALE \n", + "1417 17.0 188.0 2900.0 FEMALE \n", + "1204 17.0 190.0 3725.0 MALE \n", + "1251 17.0 185.0 3600.0 FEMALE \n", + "1422 17.0 189.0 3350.0 FEMALE \n", + "1394 17.0 176.0 3450.0 FEMALE \n", + "1163 17.0 195.0 3325.0 FEMALE \n", + "1329 17.0 181.0 3175.0 FEMALE \n", + "1406 18.0 210.0 4000.0 MALE \n", + "1196 18.0 182.0 3150.0 FEMALE \n", + "1228 18.0 192.0 3950.0 MALE \n", + "1412 18.0 195.0 3250.0 FEMALE \n", + "1142 18.0 202.0 3550.0 FEMALE \n", + "1430 19.0 190.0 3600.0 FEMALE \n", + "1333 19.0 197.0 4775.0 MALE \n", + "1414 19.0 195.0 3450.0 FEMALE \n", + "1197 19.0 182.0 3425.0 MALE \n", + "1443 19.0 199.0 4000.0 MALE \n", + "1295 20.0 203.0 4725.0 MALE \n", + "1207 20.0 190.0 3950.0 MALE \n", + "1349 20.0 190.0 3900.0 MALE \n", + "1350 20.0 190.0 4250.0 MALE \n", + "1351 16.5 198.0 3825.0 FEMALE \n", + "1116 16.5 185.0 3400.0 FEMALE \n", + "...\n", + "\n", + "[146 rows x 7 columns]" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# get all the rows with adelie penguins\n", + "adelie_data = df[df.species == \"Adelie Penguin (Pygoscelis adeliae)\"]\n", + "\n", + "# separate out the rows that have a body mass measurement\n", + "training_data = adelie_data[adelie_data.body_mass_g.notnull()]\n", + "\n", + "# we noticed there were also some rows that were missing other values,\n", + "# lets remove these so they don't affect our results\n", + "training_data = training_data.dropna()\n", + "\n", + "# lets take a quick peek and make sure things look right:\n", + "training_data" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "d55a39f9", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "species string[pyarrow]\n", + "island string[pyarrow]\n", + "culmen_length_mm Float64\n", + "culmen_depth_mm Float64\n", + "flipper_length_mm Float64\n", + "body_mass_g Float64\n", + "sex string[pyarrow]\n", + "dtype: object" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# we'll look at the schema too:\n", + "training_data.dtypes" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "id": "59d374b5", + "metadata": {}, + "source": [ + "Great! Now lets configure a linear regression model to predict body mass from the other columns" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "18c4cecf", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "LinearRegression()" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import bigframes.ml.linear_model as ml\n", + "\n", + "model = ml.LinearRegression()\n", + "model" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "id": "6e54a1a2", + "metadata": {}, + "source": [ + "As in SKLearn, an unfitted model object is just a bundle of parameters." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "a2060cf1", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'fit_intercept': True}" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# lets view the parameters\n", + "model.get_params()" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "id": "8e25fe41", + "metadata": {}, + "source": [ + "For this task, really all the default options are fine. But just so we can see how configuration works, lets specify that we want to use gradient descent to find the solution:" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "327e2232", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "LinearRegression()" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "model.optimize_strategy = \"BATCH_GRADIENT_DESCENT\"\n", + "model" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "id": "2c2e0835", + "metadata": {}, + "source": [ + "BigQuery models provide a couple of extra conveniences:\n", + "\n", + "1. By default, they will automatically perform feature engineering on the inputs - encoding our string columns and scaling our numeric columns.\n", + "2. By default, they will also automatically manage the test/training data split for us.\n", + "\n", + "So all we need to do is hook our chosen feature and label columns into the model and call .fit()!" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "085c9a99", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "LinearRegression()" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "X_train = training_data[['island', 'culmen_length_mm', 'culmen_depth_mm', 'flipper_length_mm', 'sex']]\n", + "y_train = training_data[['body_mass_g']]\n", + "model.fit(X_train, y_train)\n", + "model" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "id": "9e76e10c", + "metadata": {}, + "source": [ + "...and there, we've successfully trained a linear regressor model. Lets see how it performs, using the automatic data split:" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "c9458c02", + "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", + "
mean_absolute_errormean_squared_errormean_squared_log_errormedian_absolute_errorr2_scoreexplained_variance
0223.87876378553.6016340.005614181.3309110.6239510.623951
\n", + "
[1 rows x 6 columns in total]" + ], + "text/plain": [ + " mean_absolute_error mean_squared_error mean_squared_log_error \\\n", + "0 223.878763 78553.601634 0.005614 \n", + "\n", + " median_absolute_error r2_score explained_variance \n", + "0 181.330911 0.623951 0.623951 \n", + "\n", + "[1 rows x 6 columns]" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "model.score(X_train, y_train)" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "id": "f0b39603", + "metadata": {}, + "source": [ + "Great! The model seems useful, predicting 62% of the variance.\n", + "\n", + "We realize we made a mistake though - we're trying to predict mass using a linear model, mass will increase with the cube of the penguin's size, whereas our inputs are linear with size. Can we improve our model by cubing them?" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "b94eddc7", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'\\ndef cubify(penguin_df):\\n penguin_df.culmen_length_mm = train_x.culmen_length_mm.pow(3)\\n penguin_df.culmen_depth_mm = train_x.culmen_depth_mm.pow(3)\\n penguin_df.flipper_length_mm = train_x.flipper_length_mm.pow(3)\\n\\ncubify(train_x)\\ntrain_x\\n'" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# SKIP THIS STEP (not yet work working in BigQuery DataFrame)\n", + "\n", + "# lets define a preprocessing step that adjust the linear measurements to use the cube\n", + "'''\n", + "def cubify(penguin_df):\n", + " penguin_df.culmen_length_mm = X_train.culmen_length_mm.pow(3)\n", + " penguin_df.culmen_depth_mm = X_train.culmen_depth_mm.pow(3)\n", + " penguin_df.flipper_length_mm = X_train.flipper_length_mm.pow(3)\n", + "\n", + "cubify(X_train)\n", + "X_train\n", + "'''" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "1b0e3f02", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'\\nmodel.fit(train_x, train_y)\\nmodel.evaluate()\\n'" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# AS ABOVE, SKIP FOR NOW\n", + "'''\n", + "model.fit(X_train, y_train)\n", + "model.evaluate()\n", + "'''" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "id": "45c5e755", + "metadata": {}, + "source": [ + "Now that we're satisfied with our model, lets see what it predicts for those Adelie penguins with no body mass measurement:" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "f21ebc1f", + "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", + "
predicted_body_mass_g
tag_number
13933459.735118
15244304.175638
15233471.668379
15253947.881639
\n", + "
[4 rows x 1 columns in total]" + ], + "text/plain": [ + " predicted_body_mass_g\n", + "tag_number \n", + "1393 3459.735118\n", + "1524 4304.175638\n", + "1523 3471.668379\n", + "1525 3947.881639\n", + "\n", + "[4 rows x 1 columns]" + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Lets predict the missing observations\n", + "missing_body_mass = adelie_data[adelie_data.body_mass_g.isnull()]\n", + "\n", + "model.predict(missing_body_mass)" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "id": "e66bd0b0", + "metadata": {}, + "source": [ + "Because we created it without a name, it was just a temporary model that will disappear after 24 hours. \n", + "\n", + "We decide that this approach is promising, so lets tell BigQuery to save it." + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "c508691b", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "LinearRegression()" + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "model.to_gbq(\"bqml_tutorial.penguins_model\", replace=True)\n", + "model" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "id": "46abef08", + "metadata": {}, + "source": [ + "We can now use this model anywhere in BigQuery with this name. We can also load\n", + "it again in our BigQuery DataFrames session and evaluate or inference it without\n", + "needing to retrain it:" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "0c87e972", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "LinearRegression()" + ] + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "model = bigframes.pandas.read_gbq_model(\"bqml_tutorial.penguins_model\")\n", + "model" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "id": "d6ab8def", + "metadata": {}, + "source": [ + "And of course we can retrain it if we like. Lets make another version that is based on all the penguins, so we can test that assumption we made at the beginning that it would be best to separate them:" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "f4960452", + "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", + "
mean_absolute_errormean_squared_errormean_squared_log_errormedian_absolute_errorr2_scoreexplained_variance
0224.71743379527.8796230.005693169.2358690.6192870.619287
\n", + "
[1 rows x 6 columns in total]" + ], + "text/plain": [ + " mean_absolute_error mean_squared_error mean_squared_log_error \\\n", + "0 224.717433 79527.879623 0.005693 \n", + "\n", + " median_absolute_error r2_score explained_variance \n", + "0 169.235869 0.619287 0.619287 \n", + "\n", + "[1 rows x 6 columns]" + ] + }, + "execution_count": 16, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# This time we'll take all the training data, for all species\n", + "training_data = df[df.body_mass_g.notnull()]\n", + "training_data = training_data.dropna()\n", + "\n", + "# And we'll include species in our features\n", + "X_train = training_data[['species', 'island', 'culmen_length_mm', 'culmen_depth_mm', 'flipper_length_mm', 'sex']]\n", + "y_train = training_data[['body_mass_g']]\n", + "model.fit(X_train, y_train)\n", + "\n", + "# And we'll evaluate it on the Adelie penguins only\n", + "adelie_data = training_data[training_data.species == \"Adelie Penguin (Pygoscelis adeliae)\"]\n", + "X_test = adelie_data[['species', 'island', 'culmen_length_mm', 'culmen_depth_mm', 'flipper_length_mm', 'sex']]\n", + "y_test = adelie_data[['body_mass_g']]\n", + "model.score(X_test, y_test)" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "id": "7d101140", + "metadata": {}, + "source": [ + "It looks like the conservationists were right! Including other species, even though it gave us more training data, worsened prediction on the Adelie penguins." + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "id": "7f3fe50d", + "metadata": {}, + "source": [ + "===============================================\n", + "\n", + "**Everything below this line not yet implemented**" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "id": "62577c72", + "metadata": {}, + "source": [ + "We want to productionalize this model, so lets start publishing it to the vertex model registry ([prerequisites](https://cloud.google.com/bigquery-ml/docs/managing-models-vertex#prerequisites))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b82e79ee", + "metadata": {}, + "outputs": [], + "source": [ + "model.publish(\n", + " registry=\"vertex_ai\",\n", + " vertex_ai_model_version_aliases=[\"experimental\"])" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "id": "69d2482c", + "metadata": {}, + "source": [ + "Now when we fit the model, we can see it published here: https://console.cloud.google.com/vertex-ai/models" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "id": "b97d9b64", + "metadata": {}, + "source": [ + "# Custom feature engineering" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "id": "c837ace9", + "metadata": {}, + "source": [ + "So far, we've relied on BigQuery to do our feature engineering for us. What if we want to do it manually?\n", + "\n", + "BigQuery DataFrames provides a way to do this using Pipelines." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "480cb12f", + "metadata": {}, + "outputs": [], + "source": [ + "from bigframes.ml.pipeline import Pipeline\n", + "from bigframes.ml.preprocessing import StandardScaler\n", + "\n", + "pipe = Pipeline([\n", + " ('scaler', StandardScaler()),\n", + " ('linreg', LinearRegression())\n", + "])\n", + "\n", + "pipe.fit(X_train, y_train)\n", + "pipe.evaluate()" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "id": "9a0e7d19", + "metadata": {}, + "source": [ + "We then can then save the entire pipeline to BigQuery, BigQuery will save this as a single model, with the pre-processing steps embedded in the TRANSFORM property:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0d1831ed", + "metadata": {}, + "outputs": [], + "source": [ + "pipe.to_gbq(\"bqml_tutorial.penguins_pipeline\")" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "id": "f6b60898", + "metadata": {}, + "source": [ + "# Custom data split" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "id": "60ac0174", + "metadata": {}, + "source": [ + "BigQuery has also managed splitting out our training data. What if we want to do this manually?\n", + "\n", + "*TODO: Write this section*" + ] + } + ], + "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.9" + }, + "vscode": { + "interpreter": { + "hash": "a850322d07d9bdc9ec5f301d307e048bcab2390ae395e1cbce9335f4e081e5e2" + } + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/notebooks/experimental/semantic_operators.ipynb b/notebooks/experimental/semantic_operators.ipynb deleted file mode 100644 index 22927e6ef94..00000000000 --- a/notebooks/experimental/semantic_operators.ipynb +++ /dev/null @@ -1,69 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "title-cell", - "metadata": {}, - "source": [ - "# Semantic Operators (Experimental)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "UYeZd_I8iouP" - }, - "outputs": [], - "source": [ - "# Copyright 2024 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": [ - "Semantic Operators have been deprecated since version 1.42.0. Please use AI functions instead.\n", - "\n", - "The tutorial notebook for AI functions is located at https://github.com/googleapis/python-bigquery-dataframes/blob/main/notebooks/generative_ai/ai_functions.ipynb" - ] - } - ], - "metadata": { - "colab": { - "include_colab_link": true, - "provenance": [] - }, - "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.11.9" - } - }, - "nbformat": 4, - "nbformat_minor": 0 -} diff --git a/notebooks/generative_ai/ai_functions.ipynb b/notebooks/generative_ai/ai_functions.ipynb deleted file mode 100644 index 0831ea0412b..00000000000 --- a/notebooks/generative_ai/ai_functions.ipynb +++ /dev/null @@ -1,567 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "acd53f9d", - "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": "e75ce682", - "metadata": {}, - "source": [ - "# BigQuery DataFrames (BigFrames) AI Functions\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", - "
" - ] - }, - { - "cell_type": "markdown", - "id": "aee05821", - "metadata": {}, - "source": [ - "This notebook provides a brief introduction to AI functions in BigQuery Dataframes." - ] - }, - { - "cell_type": "markdown", - "id": "1232f400", - "metadata": {}, - "source": [ - "## Preparation\n", - "\n", - "First, set up your BigFrames environment:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "c9f924aa", - "metadata": {}, - "outputs": [], - "source": [ - "import bigframes.pandas as bpd\n", - "\n", - "PROJECT_ID = \"\" # @param {type:\"string\"}\n", - "\n", - "bpd.options.bigquery.project = PROJECT_ID\n", - "bpd.options.bigquery.ordering_mode = \"partial\"\n", - "bpd.options.display.progress_bar = None" - ] - }, - { - "cell_type": "markdown", - "id": "e2188773", - "metadata": {}, - "source": [ - "## ai.generate\n", - "\n", - "The `ai.generate` function lets you analyze any combination of text and unstructured data from BigQuery. You can mix BigFrames or Pandas series with string literals as your prompt in the form of a tuple. You are also allowed to provide only a series. Here is an example:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "471a47fe", - "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/usr/local/google/home/swast/src/github.com/googleapis/google-cloud-python/packages/bigframes/bigframes/core/global_session.py:113: DefaultLocationWarning: No explicit location is set, so using location US for the session.\n", - " _global_session = bigframes.session.connect(\n", - "/usr/local/google/home/swast/src/github.com/googleapis/google-cloud-python/packages/bigframes/bigframes/dtypes.py:1044: JSONDtypeWarning: JSON columns will be represented as pandas.ArrowDtype(pyarrow.json_())\n", - "instead of using `db_dtypes` in the future when available in pandas\n", - "(https://github.com/pandas-dev/pandas/issues/60958) and pyarrow.\n", - " warnings.warn(msg, bigframes.exceptions.JSONDtypeWarning)\n" - ] - }, - { - "data": { - "text/html": [ - "
0    {\"result\":\"Salad\",\"full_response\":{\"candidates...\n",
-       "1    {\"result\":\"Hotdog\",\"full_response\":{\"candidate...
" - ], - "text/plain": [ - "0 {\"result\":\"Salad\",\"full_response\":{\"candidates...\n", - "1 {\"result\":\"Hotdog\",\"full_response\":{\"candidate...\n", - "Name: 0, dtype: string" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "import bigframes.bigquery as bbq\n", - "\n", - "ingredients1 = bpd.Series([\"Lettuce\", \"Sausage\"])\n", - "ingredients2 = bpd.Series([\"Cucumber\", \"Long Bread\"])\n", - "\n", - "prompt = (\"What's the food made from \", ingredients1, \" and \", ingredients2, \" One word only\")\n", - "bbq.ai.generate(prompt)" - ] - }, - { - "cell_type": "markdown", - "id": "03953835", - "metadata": {}, - "source": [ - "The function returns a series of structs. The `'result'` field holds the answer, while more metadata can be found in the `'full_response'` field. The `'status'` field tells you whether LLM made a successful response for that specific row. " - ] - }, - { - "cell_type": "markdown", - "id": "b606c51f", - "metadata": {}, - "source": [ - "You can also include additional model parameters into your function call, as long as they conform to the structure of `generateContent` [request body format](https://cloud.google.com/vertex-ai/docs/reference/rest/v1/projects.locations.endpoints/generateContent#request-body). In the next example, you use `maxOutputTokens` to limit the length of the generated content." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "4a3229a8", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
0    \n",
-       "1    
" - ], - "text/plain": [ - "0 \n", - "1 \n", - "Name: result, dtype: string" - ] - }, - "execution_count": 4, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "model_params = {\n", - " \"generationConfig\": {\"maxOutputTokens\": 2}\n", - "}\n", - "\n", - "ingredients1 = bpd.Series([\"Lettuce\", \"Sausage\"])\n", - "ingredients2 = bpd.Series([\"Cucumber\", \"Long Bread\"])\n", - "\n", - "prompt = (\"What's the food made from \", ingredients1, \" and \", ingredients2)\n", - "bbq.ai.generate(prompt, model_params=model_params).struct.field(\"result\")" - ] - }, - { - "cell_type": "markdown", - "id": "3acba92d", - "metadata": {}, - "source": [ - "The answers are cut short as expected.\n", - "\n", - "In addition to `ai.generate`, you can use `ai.generate_bool`, `ai.generate_int`, and `ai.generate_double` for other output types." - ] - }, - { - "cell_type": "markdown", - "id": "0bf9f1de", - "metadata": {}, - "source": [ - "## ai.if_\n", - "\n", - "`ai.if_` generates a series of booleans. It's a handy tool for joining and filtering your data, not only because it directly returns boolean values, but also because it provides more optimization during data processing. Here is an example of using `ai.if_`:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "718c6622", - "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", - "
creaturecategory
0Catmammal
1Salmonfish
\n", - "

2 rows × 2 columns

\n", - "
[2 rows x 2 columns in total]" - ], - "text/plain": [ - "creature category\n", - " Cat mammal\n", - " Salmon fish\n", - "\n", - "[2 rows x 2 columns]" - ] - }, - "execution_count": 5, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "creatures = bpd.DataFrame({\"creature\": [\"Cat\", \"Salmon\"]})\n", - "categories = bpd.DataFrame({\"category\": [\"mammal\", \"fish\"]})\n", - "\n", - "joined_df = creatures.merge(categories, how=\"cross\")\n", - "condition = bbq.ai.if_((joined_df[\"creature\"], \" is a \", joined_df[\"category\"]))\n", - "\n", - "# Filter our dataframe\n", - "joined_df = joined_df[condition]\n", - "joined_df" - ] - }, - { - "cell_type": "markdown", - "id": "bb0999df", - "metadata": {}, - "source": [ - "## ai.score" - ] - }, - { - "cell_type": "markdown", - "id": "63b5a59f", - "metadata": {}, - "source": [ - "`ai.score` ranks your input based on the prompt and assigns a double value (i.e. a score) to each item. You can then sort your data based on their scores. For example:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "6875fe36", - "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", - "
animalsrelative_weight
1spider1.0
0tiger7.0
2blue whale10.0
\n", - "

3 rows × 2 columns

\n", - "
[3 rows x 2 columns in total]" - ], - "text/plain": [ - " animals relative_weight\n", - "1 spider 1.0\n", - "0 tiger 7.0\n", - "2 blue whale 10.0\n", - "\n", - "[3 rows x 2 columns]" - ] - }, - "execution_count": 6, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "df = bpd.DataFrame({'animals': ['tiger', 'spider', 'blue whale']})\n", - "\n", - "df['relative_weight'] = bbq.ai.score((\"Rank the relative weight of \", df['animals'], \" on the scale from 1 to 10\"))\n", - "df.sort_values(by='relative_weight')" - ] - }, - { - "cell_type": "markdown", - "id": "1ed0dff1", - "metadata": {}, - "source": [ - "## ai.classify" - ] - }, - { - "cell_type": "markdown", - "id": "c56b91cf", - "metadata": {}, - "source": [ - "`ai.classify` categories your inputs into the specified categories. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "8cfb844b", - "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", - "
animalcategory
0tigermammal
1spideranthropod
2blue whalemammal
3salmonfish
\n", - "

4 rows × 2 columns

\n", - "
[4 rows x 2 columns in total]" - ], - "text/plain": [ - " animal category\n", - "0 tiger mammal\n", - "1 spider anthropod\n", - "2 blue whale mammal\n", - "3 salmon fish\n", - "\n", - "[4 rows x 2 columns]" - ] - }, - "execution_count": 7, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "df = bpd.DataFrame({'animal': ['tiger', 'spider', 'blue whale', 'salmon']})\n", - "\n", - "df['category'] = bbq.ai.classify(df['animal'], categories=['mammal', 'fish', 'anthropod'])\n", - "df" - ] - }, - { - "cell_type": "markdown", - "id": "9e4037bc", - "metadata": {}, - "source": [ - "Note that this function can only return the values that are provided in the `categories` argument. If your categories do not cover all cases, your may get wrong answers:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "2e66110a", - "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", - "
animalcategory
0tigermammal
1spidermammal
\n", - "

2 rows × 2 columns

\n", - "
[2 rows x 2 columns in total]" - ], - "text/plain": [ - " animal category\n", - "0 tiger mammal\n", - "1 spider mammal\n", - "\n", - "[2 rows x 2 columns]" - ] - }, - "execution_count": 8, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "df = bpd.DataFrame({'animal': ['tiger', 'spider']})\n", - "\n", - "df['category'] = bbq.ai.classify(df['animal'], categories=['mammal', 'fish']) # Spider belongs to neither category\n", - "df" - ] - } - ], - "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.14.3" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/notebooks/generative_ai/ai_movie_poster.ipynb b/notebooks/generative_ai/ai_movie_poster.ipynb deleted file mode 100644 index 8f309fa7c49..00000000000 --- a/notebooks/generative_ai/ai_movie_poster.ipynb +++ /dev/null @@ -1,762 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 1, - "id": "7add2e44", - "metadata": { - "id": "XZpKUoHjXw3_" - }, - "outputs": [], - "source": [ - "# Copyright 2026 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": "ee509844", - "metadata": { - "id": "SEKzWP6jW9Oj" - }, - "source": [ - "# Analyzing movie posters with BigQuery Dataframe AI functions" - ] - }, - { - "cell_type": "markdown", - "id": "81b8de8d", - "metadata": {}, - "source": [ - "\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", - "
" - ] - }, - { - "cell_type": "markdown", - "id": "256b6c02", - "metadata": { - "id": "c9CCKXG5XTb-" - }, - "source": [ - "BigQuery Dataframe provides a Pythonic way to use AI functions directly with your dataframes. In this notebook, you will use these functions to analyze old\n", - "movie posters. These posters are images stored in a public Google Cloud Storage bucket: `gs://cloud-samples-data/vertex-ai/dataset-management/datasets/classic-movie-posters`" - ] - }, - { - "cell_type": "markdown", - "id": "3f71d3cb", - "metadata": { - "id": "CUJDa_7MPbL9" - }, - "source": [ - "## Set up" - ] - }, - { - "cell_type": "markdown", - "id": "547145f5", - "metadata": { - "id": "D3iYtBSkYpCK" - }, - "source": [ - "Before you begin, you need to\n", - "\n", - "* Set up your permissions for generative AI functions with [these instructions](https://docs.cloud.google.com/bigquery/docs/permissions-for-ai-functions)\n", - "* Set up your Cloud Resource connection by following [these instructions](https://docs.cloud.google.com/bigquery/docs/create-cloud-resource-connection)\n", - "\n", - "Once you have the permissions set up, import the `bigframes.pandas` package, and\n", - "set your cloud project ID." - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "d9cd6da8", - "metadata": { - "id": "6nqoRHYbPAx3" - }, - "outputs": [], - "source": [ - "import bigframes.pandas as bpd\n", - "\n", - "MY_PROJECT_ID = \"bigframes-dev\" # @param {type:\"string\"}\n", - "LOCATION = \"us\" # @param {type:\"string\"}\n", - "\n", - "bpd.options.bigquery.project = MY_PROJECT_ID\n", - "bpd.options.bigquery.location = LOCATION" - ] - }, - { - "cell_type": "markdown", - "id": "015a63c1", - "metadata": { - "id": "2XHcNHtvPhNW" - }, - "source": [ - "## Load data" - ] - }, - { - "cell_type": "markdown", - "id": "254561e0", - "metadata": { - "id": "eS-9A7DijfoQ" - }, - "source": [ - "First, you load the data from the GCS bucket to a BigQuery Dataframe:" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "47acbbfe", - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 1000 - }, - "id": "ZNPzFjCyPap0", - "outputId": "346d20b2-d615-4094-d24e-2d40e5c90ee2" - }, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - " Query processed 0 Bytes in a moment of slot time.\n", - " " - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/usr/local/google/home/shuowei/src/google-cloud-python/google-cloud-python/packages/bigframes/bigframes/dtypes.py:1044: JSONDtypeWarning: JSON columns will be represented as pandas.ArrowDtype(pyarrow.json_())\n", - "instead of using `db_dtypes` in the future when available in pandas\n", - "(https://github.com/pandas-dev/pandas/issues/60958) and pyarrow.\n", - " warnings.warn(msg, bigframes.exceptions.JSONDtypeWarning)\n" - ] - }, - { - "data": { - "text/html": [ - "\n", - " Query processed 0 Bytes in 18 seconds of slot time.\n", - " " - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "\n", - " Query processed 0 Bytes in 8 seconds of slot time.\n", - " " - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
poster
0
" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "# Replace with your own connection name.\n", - "MY_CONNECTION = 'bigframes-default-connection' # @param {type:\"string\"}\n", - "FULL_CONNECTION_ID = f\"{MY_PROJECT_ID}.{LOCATION}.{MY_CONNECTION}\"\n", - "\n", - "import gcsfs\n", - "import bigframes\n", - "import bigframes.pandas as bpd\n", - "import bigframes.bigquery as bbq\n", - "import json\n", - "from IPython.display import HTML, display\n", - "\n", - "session = bpd.get_global_session()\n", - "\n", - "# Configure global display parameters \n", - "bigframes.options.display.blob_display_width = 200\n", - "\n", - "def get_runtime_json_str(series, mode=\"R\", with_metadata=False):\n", - " s = bbq.obj.fetch_metadata(series) if with_metadata else series\n", - " runtime = bbq.obj.get_access_url(s, mode=mode)\n", - " return bbq.to_json_string(runtime)\n", - "\n", - "def get_read_url(series):\n", - " runtime = bbq.obj.get_access_url(series, mode=\"R\")\n", - " return bbq.json_value(runtime, \"$.access_urls.read_url\")\n", - "\n", - "def render_images(df):\n", - " \"\"\"Helper to display BigFrames DataFrame with rendered image previews.\"\"\"\n", - " from bigframes import dtypes\n", - " if isinstance(df, bpd.Series):\n", - " df = df.to_frame()\n", - " \n", - " object_cols = [col for col, dtype in zip(df.columns, df.dtypes) if dtype == dtypes.OBJ_REF_DTYPE]\n", - " if not object_cols:\n", - " display(df)\n", - " return\n", - "\n", - " limit = bigframes.options.display.max_rows or 10\n", - " view_df = df.head(limit)\n", - " runtime_cols = {\n", - " col: get_runtime_json_str(view_df[col], mode=\"R\", with_metadata=False) \n", - " for col in object_cols\n", - " }\n", - " \n", - " pandas_json_df = bpd.DataFrame(runtime_cols).to_pandas()\n", - " final_pd = view_df.to_pandas()\n", - " width = bigframes.options.display.blob_display_width or 200\n", - " \n", - " def format_cell_html(raw_json):\n", - " if not raw_json: return \"\"\n", - " try:\n", - " obj_rt = json.loads(raw_json)\n", - " if \"access_urls\" not in obj_rt: return \"Error fetching URL\"\n", - " uri = obj_rt.get(\"objectref\", {}).get(\"uri\", \"\")\n", - " url = obj_rt[\"access_urls\"][\"read_url\"]\n", - " if str(uri).lower().endswith((\".png\", \".jpg\", \".jpeg\", \".webp\")):\n", - " return f''\n", - " return f'{uri}'\n", - " except: return \"Format Error\"\n", - "\n", - " for col in object_cols:\n", - " final_pd[col] = pandas_json_df[col].map(format_cell_html)\n", - " display(HTML(final_pd.to_html(escape=False)))\n", - "\n", - "# List files using gcsfs\n", - "fs = gcsfs.GCSFileSystem(anon=True)\n", - "uris = fs.glob(\"gs://cloud-samples-data/vertex-ai/dataset-management/datasets/classic-movie-posters/*\")\n", - "\n", - "# Ensure URIs have gs:// prefix\n", - "uris = [u if u.startswith(\"gs://\") else f\"gs://{u}\" for u in uris]\n", - "\n", - "# Read the URIs into a BigQuery DataFrame\n", - "movies = bpd.read_gbq(f\"SELECT uri FROM UNNEST({uris[:5]}) as uri\")\n", - "\n", - "# Create the object reference column using the fully qualified connection ID\n", - "movies['poster'] = bbq.obj.make_ref(movies['uri'], authorizer=FULL_CONNECTION_ID)\n", - "movies = movies[['poster']]\n", - "render_images(movies.head(1))" - ] - }, - { - "cell_type": "markdown", - "id": "f1096d2f", - "metadata": { - "id": "EfkdDH08QnYw" - }, - "source": [ - "## Extract titles from posters" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "bb30d47c", - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 1000 - }, - "id": "6CoZZ5tSQm1r", - "outputId": "1b3915ce-eb83-4be9-b1c1-d9a326dc9408" - }, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/usr/local/google/home/shuowei/src/google-cloud-python/google-cloud-python/packages/bigframes/bigframes/dtypes.py:1044: JSONDtypeWarning: JSON columns will be represented as pandas.ArrowDtype(pyarrow.json_())\n", - "instead of using `db_dtypes` in the future when available in pandas\n", - "(https://github.com/pandas-dev/pandas/issues/60958) and pyarrow.\n", - " warnings.warn(msg, bigframes.exceptions.JSONDtypeWarning)\n" - ] - }, - { - "data": { - "text/html": [ - "\n", - " Query processed 0 Bytes in 23 seconds of slot time. [Job bigframes-dev:US.job_ZKfuxLQE1U49whg7fgakYFYfiz34 details]\n", - " " - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "\n", - " Query processed 0 Bytes in 40 seconds of slot time. [Job bigframes-dev:US.job_VwLv_BxDFdE4adNx1bpnvvM5vfZd details]\n", - " " - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
postertitle
0The movie title for this poster image is **Au Secours!** (Help!).
" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "import bigframes.bigquery as bbq\n", - "\n", - "movies['title'] = bbq.ai.generate(\n", - " (\"What is the movie title for this poster image?\", get_read_url(movies['poster']))\n", - ").struct.field(\"result\")\n", - "render_images(movies.head(1))" - ] - }, - { - "cell_type": "markdown", - "id": "eb9eb261", - "metadata": { - "id": "cFQHQ9S2lr6t" - }, - "source": [ - "Notice that `ai.generate()` has a `struct` return type, which holds not only the LLM response, but also the status. If you do not provide a field name for your answer, `\"result\"` will be the default name. You can access LLM response content with the struct accessor (e.g. `my_response.struct.filed(\"result\")`);." - ] - }, - { - "cell_type": "markdown", - "id": "ea29eb21", - "metadata": { - "id": "R8kkUhgoS5Xz" - }, - "source": [ - "## Get movie release year\n", - "\n", - "In the example below, you will use `ai.generate_int()` to find the release year for each movie poster:" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "id": "bf426247", - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 976 - }, - "id": "cKZdHq0XS1iW", - "outputId": "72cbad57-4518-4e1e-97bb-333d424dba73" - }, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/usr/local/google/home/shuowei/src/google-cloud-python/google-cloud-python/packages/bigframes/bigframes/dtypes.py:1044: JSONDtypeWarning: JSON columns will be represented as pandas.ArrowDtype(pyarrow.json_())\n", - "instead of using `db_dtypes` in the future when available in pandas\n", - "(https://github.com/pandas-dev/pandas/issues/60958) and pyarrow.\n", - " warnings.warn(msg, bigframes.exceptions.JSONDtypeWarning)\n", - "/usr/local/google/home/shuowei/src/google-cloud-python/google-cloud-python/packages/bigframes/bigframes/core/logging/log_adapter.py:229: ApiDeprecationWarning: The blob accessor is deprecated and will be removed in a future release. Use bigframes.bigquery.obj functions instead.\n", - " return prop(*args, **kwargs)\n" - ] - }, - { - "data": { - "text/html": [ - "\n", - " Query processed 0 Bytes in 51 seconds of slot time. [Job bigframes-dev:US.3cf4ab5b-c360-4b7c-9def-4cd03135a547 details]\n", - " " - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "\n", - " Query processed 1.2 kB in a moment of slot time.\n", - " " - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
postertitleyear
0The movie title is **Au Secours!**1924
\n", - "

1 rows × 3 columns

\n", - "
[1 rows x 3 columns in total]" - ], - "text/plain": [ - " poster \\\n", - "0 {\"access_urls\":{\"expiry_time\":\"2026-05-09T03:1... \n", - "\n", - " title year \n", - "0 The movie title is **Au Secours!** 1924 \n", - "\n", - "[1 rows x 3 columns]" - ] - }, - "execution_count": 5, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "movies['year'] = bbq.ai.generate_int(\n", - " (\"What is the release year for this movie?\", movies['title']),\n", - " endpoint='gemini-2.5-pro'\n", - ").struct.field(\"result\")\n", - "\n", - "movies.head(1)" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "id": "8bf12352", - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 250 - }, - "id": "yqRiNRY8_8fs", - "outputId": "efa60107-6883-4f5c-8e40-43c7287ea7fb" - }, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/usr/local/google/home/shuowei/src/google-cloud-python/google-cloud-python/packages/bigframes/bigframes/dtypes.py:1044: JSONDtypeWarning: JSON columns will be represented as pandas.ArrowDtype(pyarrow.json_())\n", - "instead of using `db_dtypes` in the future when available in pandas\n", - "(https://github.com/pandas-dev/pandas/issues/60958) and pyarrow.\n", - " warnings.warn(msg, bigframes.exceptions.JSONDtypeWarning)\n" - ] - }, - { - "data": { - "text/plain": [ - "poster structSQL
WITH `bfcte_0` AS (\n",
-       "  SELECT\n",
-       "    *\n",
-       "  FROM UNNEST(ARRAY<STRUCT<`bfcol_0` STRING, `bfcol_1` INT64, `bfcol_2` INT64>>[STRUCT(\n",
-       "    'gs://cloud-samples-data/vertex-ai/dataset-management/datasets/classic-movie-posters/au_secours.jpeg',\n",
-       "    0,\n",
-       "    0\n",
-       "  ), STRUCT(\n",
-       "    'gs://cloud-samples-data/vertex-ai/dataset-management/datasets/classic-movie-posters/barque_sortant_du_port.jpeg',\n",
-       "    1,\n",
-       "    1\n",
-       "  ), STRUCT(\n",
-       "    'gs://cloud-samples-data/vertex-ai/dataset-management/datasets/classic-movie-posters/battling_butler.jpg',\n",
-       "    2,\n",
-       "    2\n",
-       "  ), STRUCT(\n",
-       "    'gs://cloud-samples-data/vertex-ai/dataset-management/datasets/classic-movie-posters/brown_of_harvard.jpeg',\n",
-       "    3,\n",
-       "    3\n",
-       "  ), STRUCT(\n",
-       "    'gs://cloud-samples-data/vertex-ai/dataset-management/datasets/classic-movie-posters/der_student_von_prag.jpg',\n",
-       "    4,\n",
-       "    4\n",
-       "  )])\n",
-       ")\n",
-       "SELECT\n",
-       "  `bfcol_1` AS `bfuid_col_60`,\n",
-       "  TO_JSON_STRING(\n",
-       "    OBJ.GET_ACCESS_URL(OBJ.MAKE_REF(`bfcol_0`, 'bigframes-dev.us.bigframes-default-connection'), 'R')\n",
-       "  ) AS `bfuid_col_66`\n",
-       "FROM `bfcte_0`\n",
-       "WHERE\n",
-       "  AI.IF(\n",
-       "    prompt => (\n",
-       "      'The movie ',\n",
-       "      AI.GENERATE(\n",
-       "        prompt => (\n",
-       "          'What is the movie title for this poster image?',\n",
-       "          JSON_VALUE(\n",
-       "            OBJ.GET_ACCESS_URL(OBJ.MAKE_REF(`bfcol_0`, 'bigframes-dev.us.bigframes-default-connection'), 'R'),\n",
-       "            '$.access_urls.read_url'\n",
-       "          )\n",
-       "        ),\n",
-       "        request_type => 'UNSPECIFIED'\n",
-       "      ).`result`,\n",
-       "      ' was made in US'\n",
-       "    ),\n",
-       "    optimization_mode => 'MINIMIZE_COST'\n",
-       "  )\n",
-       "ORDER BY\n",
-       "  `bfcol_2` ASC NULLS LAST\n",
-       "LIMIT 1
\n", - " " - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "\n", - " Query processed 0 Bytes in 3 minutes of slot time. [Job bigframes-dev:US.job_NBILG5qU14Aitas81nPCCtYM9KdM details]\n", - " " - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
postertitleyear
2The movie title for the poster image is **Battling Butler**.1926
" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "us_movies = movies[bbq.ai.if_(\n", - " (\"The movie \", movies['title'], \" was made in US\")\n", - ")]\n", - "render_images(us_movies.head(1))" - ] - } - ], - "metadata": { - "colab": { - "provenance": [] - }, - "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.13.0" - } - }, - "nbformat": 4, - "nbformat_minor": 0 -} diff --git a/notebooks/generative_ai/bq_dataframes_ai_forecast.ipynb b/notebooks/generative_ai/bq_dataframes_ai_forecast.ipynb deleted file mode 100644 index 6f8c95d3a48..00000000000 --- a/notebooks/generative_ai/bq_dataframes_ai_forecast.ipynb +++ /dev/null @@ -1,901 +0,0 @@ -{ - "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": null, - "metadata": {}, - "outputs": [], - "source": [ - "PROJECT = \"bigframes-dev\" # replace with your project\n", - "\n", - "import bigframes.pandas as bpd\n", - "bpd.options.bigquery.project = PROJECT\n", - "bpd.options.display.progress_bar = None\n", - "\n", - "# Optional, but recommended: partial ordering mode can accelerate executions and save costs.\n", - "bpd.options.bigquery.ordering_mode = \"partial\"" - ] - }, - { - "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", - "
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
0201712151647221445012017-12-15 16:47:22+00:0010th St at Fallon St2012017-12-15 16:55:44+00:0010th Ave at E 15th St222144<NA>...<NA>37.797673-122.26299737.792714-122.248781984Male<NA>POINT (-122.263 37.79767)POINT (-122.24878 37.79271)
12017080523460515857122017-08-05 23:46:05+00:0010th St at Fallon St2012017-08-05 23:57:57+00:0010th Ave at E 15th St2221585<NA>...<NA>37.797673-122.26299737.792714-122.24878<NA><NA><NA>POINT (-122.263 37.79767)POINT (-122.24878 37.79271)
22017111114472028802722017-11-11 14:47:20+00:0012th St at 4th Ave2332017-11-11 14:51:53+00:0010th Ave at E 15th St2222880<NA>...<NA>37.795812-122.25555537.792714-122.248781965Female<NA>POINT (-122.25555 37.79581)POINT (-122.24878 37.79271)
32018042517262737557572018-04-25 17:26:27+00:0013th St at Franklin St3382018-04-25 17:39:05+00:0010th Ave at E 15th St2223755<NA>...<NA>37.803189-122.27057937.792714-122.248781982OtherNoPOINT (-122.27058 37.80319)POINT (-122.24878 37.79271)
42018040815560118311052018-04-08 15:56:01+00:0013th St at Franklin St3382018-04-08 16:14:26+00:0010th Ave at E 15th St222183<NA>...<NA>37.803189-122.27057937.792714-122.248781987FemaleNoPOINT (-122.27058 37.80319)POINT (-122.24878 37.79271)
52018041916485015608572018-04-19 16:48:50+00:0013th St at Franklin St3382018-04-19 17:03:08+00:0010th Ave at E 15th St2221560<NA>...<NA>37.803189-122.27057937.792714-122.248781982OtherNoPOINT (-122.27058 37.80319)POINT (-122.24878 37.79271)
62017081020445483912562017-08-10 20:44:54+00:002nd Ave at E 18th St2002017-08-10 21:05:50+00:0010th Ave at E 15th St222839<NA>...<NA>37.800214-122.2538137.792714-122.24878<NA><NA><NA>POINT (-122.25381 37.80021)POINT (-122.24878 37.79271)
7201710122044386666302017-10-12 20:44:38+00:002nd Ave at E 18th St2002017-10-12 20:55:09+00:0010th Ave at E 15th St222666<NA>...<NA>37.800214-122.2538137.792714-122.24878<NA><NA><NA>POINT (-122.25381 37.80021)POINT (-122.24878 37.79271)
82017111818232819603532017-11-18 18:23:28+00:002nd Ave at E 18th St2002017-11-18 18:29:22+00:0010th Ave at E 15th St2221960<NA>...<NA>37.800214-122.2538137.792714-122.248781988Male<NA>POINT (-122.25381 37.80021)POINT (-122.24878 37.79271)
9201708061839175102982017-08-06 18:39:17+00:002nd Ave at E 18th St2002017-08-06 18:44:15+00:0010th Ave at E 15th St222510<NA>...<NA>37.800214-122.2538137.792714-122.248781969Male<NA>POINT (-122.25381 37.80021)POINT (-122.24878 37.79271)
\n", - "

10 rows × 21 columns

\n", - "
[1947417 rows x 21 columns in total]" - ], - "text/plain": [ - " trip_id duration_sec start_date \\\n", - " 20171215164722144 501 2017-12-15 16:47:22+00:00 \n", - "201708052346051585 712 2017-08-05 23:46:05+00:00 \n", - "201711111447202880 272 2017-11-11 14:47:20+00:00 \n", - "201804251726273755 757 2018-04-25 17:26:27+00:00 \n", - " 20180408155601183 1105 2018-04-08 15:56:01+00:00 \n", - "201804191648501560 857 2018-04-19 16:48:50+00:00 \n", - " 20170810204454839 1256 2017-08-10 20:44:54+00:00 \n", - " 20171012204438666 630 2017-10-12 20:44:38+00:00 \n", - "201711181823281960 353 2017-11-18 18:23:28+00:00 \n", - " 20170806183917510 298 2017-08-06 18:39:17+00:00 \n", - "\n", - " start_station_name start_station_id end_date \\\n", - " 10th St at Fallon St 201 2017-12-15 16:55:44+00:00 \n", - " 10th St at Fallon St 201 2017-08-05 23:57:57+00:00 \n", - " 12th St at 4th Ave 233 2017-11-11 14:51:53+00:00 \n", - "13th St at Franklin St 338 2018-04-25 17:39:05+00:00 \n", - "13th St at Franklin St 338 2018-04-08 16:14:26+00:00 \n", - "13th St at Franklin St 338 2018-04-19 17:03:08+00:00 \n", - " 2nd Ave at E 18th St 200 2017-08-10 21:05:50+00:00 \n", - " 2nd Ave at E 18th St 200 2017-10-12 20:55:09+00:00 \n", - " 2nd Ave at E 18th St 200 2017-11-18 18:29:22+00:00 \n", - " 2nd Ave at E 18th St 200 2017-08-06 18:44:15+00:00 \n", - "\n", - " end_station_name end_station_id bike_number zip_code ... \\\n", - "10th Ave at E 15th St 222 144 ... \n", - "10th Ave at E 15th St 222 1585 ... \n", - "10th Ave at E 15th St 222 2880 ... \n", - "10th Ave at E 15th St 222 3755 ... \n", - "10th Ave at E 15th St 222 183 ... \n", - "10th Ave at E 15th St 222 1560 ... \n", - "10th Ave at E 15th St 222 839 ... \n", - "10th Ave at E 15th St 222 666 ... \n", - "10th Ave at E 15th St 222 1960 ... \n", - "10th Ave at E 15th St 222 510 ... \n", - "\n", - "c_subscription_type start_station_latitude start_station_longitude \\\n", - " 37.797673 -122.262997 \n", - " 37.797673 -122.262997 \n", - " 37.795812 -122.255555 \n", - " 37.803189 -122.270579 \n", - " 37.803189 -122.270579 \n", - " 37.803189 -122.270579 \n", - " 37.800214 -122.25381 \n", - " 37.800214 -122.25381 \n", - " 37.800214 -122.25381 \n", - " 37.800214 -122.25381 \n", - "\n", - " end_station_latitude end_station_longitude member_birth_year \\\n", - " 37.792714 -122.24878 1984 \n", - " 37.792714 -122.24878 \n", - " 37.792714 -122.24878 1965 \n", - " 37.792714 -122.24878 1982 \n", - " 37.792714 -122.24878 1987 \n", - " 37.792714 -122.24878 1982 \n", - " 37.792714 -122.24878 \n", - " 37.792714 -122.24878 \n", - " 37.792714 -122.24878 1988 \n", - " 37.792714 -122.24878 1969 \n", - "\n", - " member_gender bike_share_for_all_trip start_station_geom \\\n", - " Male POINT (-122.263 37.79767) \n", - " POINT (-122.263 37.79767) \n", - " Female POINT (-122.25555 37.79581) \n", - " Other No POINT (-122.27058 37.80319) \n", - " Female No POINT (-122.27058 37.80319) \n", - " Other No POINT (-122.27058 37.80319) \n", - " POINT (-122.25381 37.80021) \n", - " POINT (-122.25381 37.80021) \n", - " Male POINT (-122.25381 37.80021) \n", - " Male POINT (-122.25381 37.80021) \n", - "\n", - " end_station_geom \n", - "POINT (-122.24878 37.79271) \n", - "POINT (-122.24878 37.79271) \n", - "POINT (-122.24878 37.79271) \n", - "POINT (-122.24878 37.79271) \n", - "POINT (-122.24878 37.79271) \n", - "POINT (-122.24878 37.79271) \n", - "POINT (-122.24878 37.79271) \n", - "POINT (-122.24878 37.79271) \n", - "POINT (-122.24878 37.79271) \n", - "POINT (-122.24878 37.79271) \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", - "
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
\n", - "

10 rows × 2 columns

\n", - "
[2842 rows x 2 columns in total]" - ], - "text/plain": [ - " trip_hour num_trips\n", - "2018-01-01 00:00:00+00:00 20\n", - "2018-01-01 01:00:00+00:00 25\n", - "2018-01-01 02:00:00+00:00 13\n", - "2018-01-01 03:00:00+00:00 11\n", - "2018-01-01 05:00:00+00:00 4\n", - "2018-01-01 06:00:00+00:00 8\n", - "2018-01-01 07:00:00+00:00 8\n", - "2018-01-01 08:00:00+00:00 20\n", - "2018-01-01 09:00:00+00:00 30\n", - "2018-01-01 10:00:00+00:00 41\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", - "
forecast_timestampforecast_valueconfidence_levelprediction_interval_lower_boundprediction_interval_upper_boundai_forecast_status
02018-04-24 12:00:00+00:00147.0237430.9598.736624195.310862
12018-04-25 00:00:00+00:006.9550320.95-6.09423220.004297
22018-04-26 05:00:00+00:00-37.1965330.95-88.75956614.366499
32018-04-26 14:00:00+00:00115.6351320.9530.120832201.149432
42018-04-27 02:00:00+00:002.5160060.95-69.09559174.127604
52018-04-29 03:00:00+00:0022.5033260.95-38.71437883.721031
62018-04-24 04:00:00+00:00-12.2590790.95-45.37726220.859104
72018-04-24 14:00:00+00:00126.5192110.9596.837778156.200644
82018-04-26 11:00:00+00:00120.905670.9535.781735206.029606
92018-04-27 13:00:00+00:00162.0230260.95103.946307220.099744
\n", - "

10 rows × 6 columns

\n", - "
[168 rows x 6 columns in total]" - ], - "text/plain": [ - " forecast_timestamp forecast_value confidence_level \\\n", - "2018-04-24 12:00:00+00:00 147.023743 0.95 \n", - "2018-04-25 00:00:00+00:00 6.955032 0.95 \n", - "2018-04-26 05:00:00+00:00 -37.196533 0.95 \n", - "2018-04-26 14:00:00+00:00 115.635132 0.95 \n", - "2018-04-27 02:00:00+00:00 2.516006 0.95 \n", - "2018-04-29 03:00:00+00:00 22.503326 0.95 \n", - "2018-04-24 04:00:00+00:00 -12.259079 0.95 \n", - "2018-04-24 14:00:00+00:00 126.519211 0.95 \n", - "2018-04-26 11:00:00+00:00 120.90567 0.95 \n", - "2018-04-27 13:00:00+00:00 162.023026 0.95 \n", - "\n", - " prediction_interval_lower_bound prediction_interval_upper_bound \\\n", - " 98.736624 195.310862 \n", - " -6.094232 20.004297 \n", - " -88.759566 14.366499 \n", - " 30.120832 201.149432 \n", - " -69.095591 74.127604 \n", - " -38.714378 83.721031 \n", - " -45.377262 20.859104 \n", - " 96.837778 156.200644 \n", - " 35.781735 206.029606 \n", - " 103.946307 220.099744 \n", - "\n", - "ai_forecast_status \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "...\n", - "\n", - "[168 rows x 6 columns]" - ] - }, - "execution_count": 6, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "import bigframes.bigquery as bbq\n", - "\n", - "# Using all the data except the last week (2842-168) for training. And predict the last week (168).\n", - "result = bbq.ai.forecast(df_grouped.head(2842-168), timestamp_col=\"trip_hour\", data_col=\"num_trips\", horizon=168) \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": "markdown", - "metadata": {}, - "source": [ - "Plot a line chart and compare with the actual result." - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "" - ] - }, - "execution_count": 8, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAABREAAAKnCAYAAAARNgr5AAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjMsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvZiW1igAAAAlwSFlzAAAPYQAAD2EBqD+naQABAABJREFUeJzs/Xu8LEdd741/qntm1toXdm4n2TvREILEA8EAMfjANio8EBNCRLmJIkeJ5uBLfkEEHlB5zIEQEJADKJegHIWAIsdz9BEOIpeESABJCNcggoICIYFcBZKdZGfvNTNdvz+6q7uqpmet1VU1VbN6fd6v137NuuzV0z0zXd31rc/n+xFSSglCCCGEEEIIIYQQQgiZQ5Z6BwghhBBCCCGEEEIIIcsNi4iEEEIIIYQQQgghhJB1YRGREEIIIYQQQgghhBCyLiwiEkIIIYQQQgghhBBC1oVFREIIIYQQQgghhBBCyLqwiEgIIYQQQgghhBBCCFkXFhEJIYQQQgghhBBCCCHrwiIiIYQQQgghhBBCCCFkXQapd8CVoihw00034T73uQ+EEKl3hxBCCCGEEEIIIYSQLYWUEnfddRdOOOEEZNn6WsMtW0S86aabcOKJJ6beDUIIIYQQQgghhBBCtjQ33ngjfvAHf3Dd/7Nli4j3uc99AJQHuWfPnsR7QwghhBBCCCGEEELI1uLAgQM48cQT6zrbemzZIqKyMO/Zs4dFREIIIYQQQgghhBBCHNlMq0AGqxBCCCGEEEIIIYQQQtaFRURCCCGEEEIIIYQQQsi6sIhICCGEEEIIIYQQQghZly3bE3EzSCkxmUwwnU5T7woh3uR5jsFgsKk+BYQQQgghhBBCCCEh6W0RcW1tDTfffDMOHjyYelcICcbOnTtx/PHHYzQapd4VQgghhBBCCCGEbCN6WUQsigLf/OY3kec5TjjhBIxGI6q3yJZGSom1tTXcfvvt+OY3v4lTTjkFWcZuBIQQQgghhBBCCIlDL4uIa2trKIoCJ554Inbu3Jl6dwgJwo4dOzAcDvGtb30La2trWF1dTb1LhBBCCCGEEEII2Sb0WspEpRbpG/xME0IIIYQQQgghJAWsSBBCCCGEEEIIIYQQQtaFRURCCCGEEEIIIYQQQsi6sIhIgnLxxRfjYQ97WOrdIIQQQgghhBBCCCEBYRGRbMijH/1oPO95z9vU/33hC1+IK6+8crE7RAghhBBCCCGEEEKi0st0ZhIfKSWm0yl2796N3bt3p94dQgghhBBCCCGEEBKQbaNElFLi4NokyT8p5ab389GPfjSe+9zn4rd/+7dx9NFHY9++fbj44osBANdffz2EELjuuuvq/3/HHXdACIGrrroKAHDVVVdBCIEPf/jDOP3007Fjxw485jGPwW233YYPfvCDeNCDHoQ9e/bgl37pl3Dw4MEN9+f888/Hxz72MbzhDW+AEAJCCFx//fX183zwgx/EGWecgZWVFfzjP/7jjJ35/PPPxxOf+ES87GUvw7HHHos9e/bgN37jN7C2tlb/n7/5m7/Baaedhh07duCYY47BWWedhXvuuWfTrxkhhBBCCCGEEEIIWSzbRol473iKU1/y4STP/ZVLzsHO0eZf6ne+8514wQtegGuvvRbXXHMNzj//fJx55pk45ZRTNr2Niy++GG9+85uxc+dOPO1pT8PTnvY0rKys4N3vfjfuvvtuPOlJT8Kb3vQm/M7v/M6623nDG96Ar33ta/iRH/kRXHLJJQCAY489Ftdffz0A4Hd/93fx2te+Fve///1x1FFH1cVMnSuvvBKrq6u46qqrcP311+NXf/VXccwxx+D3f//3cfPNN+PpT386XvOa1+BJT3oS7rrrLnziE5/oVHglhBBCCCGEEEIIIYtl2xQRtxIPechD8NKXvhQAcMopp+DNb34zrrzyyk5FxFe84hU488wzAQAXXHABXvziF+PrX/867n//+wMAnvrUp+KjH/3ohkXEI444AqPRCDt37sS+fftmfn/JJZfgp3/6p9fdxmg0wtvf/nbs3LkTD37wg3HJJZfgRS96EV7+8pfj5ptvxmQywZOf/GScdNJJAIDTTjtt08dJCCGEEEIIIYQQQhbPtiki7hjm+Mol5yR77i485CEPMb4//vjjcdtttzlvY+/evdi5c2ddQFQ/+/SnP91pm208/OEP3/D/PPShD8XOnTvr7/fv34+7774bN954Ix760IfisY99LE477TScc845OPvss/HUpz4VRx11lPe+EUIIIYQQQgghhJAwbJsiohCik6U4JcPh0PheCIGiKJBlZQtL3eo7Ho833IYQYu42fdm1a5fX3+d5jiuuuAJXX301Lr/8crzpTW/C7/3e7+Haa6/FySef7L1/hBBCCCGEEEIIIcSfbROs0geOPfZY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FR/96Ec3LCIeccQRGI1G2LlzJ/bt2zfz+0suuQQ//dM/ve42RqMR3v72t2Pnzp148IMfjEsuuQQvetGL8PKXvxw333wzJpMJnvzkJ+Okk04CAJx22mkbHt/u3btx5JFHAoCxX6997WvxO7/zO/jFX/xFAMAf/MEf4KMf/Sj+6I/+CJdeemn9/573vOfhyU9+cv39S1/6Urzuda+rf3byySfjK1/5Ct761rfimc98Jt797nfj9ttvx2c+8xkcffTRAIAHPOAB9d8/9KEPxUMf+tD6+5e//OV4z3veg/e97314znOegxtuuAG7du3Cz/zMz+A+97kPTjrpJJx++umbeo0JIYQQQgjpA/UEnkVEQkgCCtqZvdk+RcThzlIRmOq5O/CQhzzE+P7444/Hbbfd5ryNvXv3YufOnXUBUf3s05/+dKdttvHwhz98w//z0Ic+FDt3Nq/B/v37cffdd+PGG2/EQx/6UDz2sY/FaaedhnPOOQdnn302nvrUp+Koo47qvC8HDhzATTfdVBdPFWeeeSa++MUvzt3ve+65B1//+tdxwQUX4FnPelb988lkgiOOOAIAcN111+H000+vC4g2d999Ny6++GL8/d//fV0Yvffee3HDDTcAAH76p38aJ510Eu5///vjcY97HB73uMfhSU96kvG6EEIIIYQQ0mcaJSJVQISQ+Khk+HFRQErpla2wXdk+RUQhOlmKU2KHfAghUBQFsqxsYalbfcfj8YbbEELM3aYvu3b5vaZ5nuOKK67A1VdfjcsvvxxvetOb8Hu/93u49tprcfLJJ3vv3zz0/b777rsBAH/6p3+KRzziETP7BwA7duxYd3svfOELccUVV+C1r30tHvCAB2DHjh146lOfWvd+vM997oPPf/7zuOqqq3D55ZfjJS95CS6++GJ85jOfqRWVhBBCCCGE9BlZWwmpAiKExEeJoKUs2yoMchYRu7J9glV6gOqTp/cA1ENWFsVoNMJ0OnX++y9+8Yu499576+8/9alPYffu3TjxxBMBlAXNM888Ey972cvwhS98AaPRCO95z3s6P8+ePXtwwgkn4JOf/KTx809+8pM49dRT5/7d3r17ccIJJ+Ab3/gGHvCABxj/VCHzIQ95CK677jp873vfa93GJz/5SZx//vl40pOehNNOOw379u2bCUcZDAY466yz8JrXvAb/9E//hOuvvx7/8A//AMD/NSaEEEIIIWTZUb0Q2ROREJKCQhNksa2CG9tHidgDduzYgUc+8pF49atfjZNPPhm33XYbLrroooU/7/3udz9ce+21uP7667F79+65lt55rK2t4YILLsBFF12E66+/Hi996UvxnOc8B1mW4dprr8WVV16Js88+G8cddxyuvfZa3H777XjQgx7ktK8vetGL8NKXvhQ/9EM/hIc97GG47LLLcN1119UJzPN42ctehuc+97k44ogj8LjHPQ6HDx/GZz/7WXz/+9/HC17wAjz96U/HK1/5SjzxiU/Eq171Khx//PH4whe+gBNOOAH79+/HKaecgr/927/FE57wBAgh8N/+238zlJ7vf//78Y1vfAM/9VM/haOOOgof+MAHUBQF/vN//s8A2l9jpTwlhBBCCCGkD9R25g1CFAkhZBHodcO1aYHVYZ5uZ7YorFJsMd7+9rdjMpngjDPOwPOe9zy84hWvWPhzvvCFL0Se5zj11FNx7LHH1n3+NstjH/tYnHLKKfipn/op/MIv/AJ+9md/FhdffDGAUj348Y9/HI9//OPxwz/8w7jooovwute9Dueee67Tvj73uc/FC17wAvw//8//g9NOOw0f+tCH8L73vW/DUJr/+l//K/7sz/4Ml112GU477TQ86lGPwjve8Y5aiTgajXD55ZfjuOOOw+Mf/3icdtppePWrX13bnV//+tfjqKOOwo//+I/jCU94As455xz86I/+aL39I488En/7t3+LxzzmMXjQgx6EP/mTP8H//J//Ew9+8IMB+L/GhBBCCCGELDuqdkglIiEkBXprOLZVcENIuTWXgQ4cOIAjjjgCd955J/bs2WP87tChQ/jmN7+Jk08+Gaurq4n2kADA+eefjzvuuAPvfe97U+9KL+BnmxBCCCGEbFVe8n/+GX9+zbdw6vF78IHf+snUu0MI2Wb89w//Ky796NcBANf+v4/F3j2cUwPr19dsqEQkhBBCCCGEELJwmnTmLaljWZe1CROnCVl29KFnPOU56wKLiNucG264Abt37577L6Wtdr39+sQnPpFsvwghhBBCyPLwr7ccwL/eciD1bpBNoCbwk6Jfk/e3fuzreMjLPozrbrwj9a4QQtah0KqIY9qZnWCwyjbnhBNOWDfh+YQTTvDa/jve8Q7nv11vv37gB37AebuEEEIIIaQfjKcFfv6PrwEE8Pn/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", - "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 (3.10.17)", - "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.17" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/notebooks/generative_ai/bq_dataframes_llm_code_generation.ipynb b/notebooks/generative_ai/bq_dataframes_llm_code_generation.ipynb index 527d3c4aaac..0f113b84c6d 100644 --- a/notebooks/generative_ai/bq_dataframes_llm_code_generation.ipynb +++ b/notebooks/generative_ai/bq_dataframes_llm_code_generation.ipynb @@ -1,1305 +1,891 @@ { - "cells": [ - { - "cell_type": "code", - "execution_count": 2, - "metadata": { - "id": "ur8xi4C7S06n" - }, - "outputs": [], - "source": [ - "# Copyright 2022 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": { - "id": "JAPoU8Sm5E6e" - }, - "source": [ - "# Use BigQuery DataFrames with Generative AI for code generation\n", - "\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", - " \"Vertex\n", - " Open in Vertex AI Workbench\n", - " \n", - " \n", - " \n", - " \"BQ\n", - " Open in BQ Studio\n", - " \n", - "
" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "24743cf4a1e1" - }, - "source": [ - "**_NOTE_**: This notebook has been tested in the following environment:\n", - "\n", - "* Python version = 3.10" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "tvgnzT1CKxrO" - }, - "source": [ - "## Overview\n", - "\n", - "Use this notebook to walk through an example use case of generating sample code by using BigQuery DataFrames and its integration with Generative AI support on Vertex AI.\n", - "\n", - "Learn more about [BigQuery DataFrames](https://cloud.google.com/python/docs/reference/bigframes/latest)." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "d975e698c9a4" - }, - "source": [ - "### Objective\n", - "\n", - "In this tutorial, you create a CSV file containing sample code for calling a given set of APIs.\n", - "\n", - "The steps include:\n", - "\n", - "- Defining an LLM model in BigQuery DataFrames, specifically the [Gemini Model](https://cloud.google.com/vertex-ai/generative-ai/docs/learn/models#gemini-models), using `bigframes.ml.llm`.\n", - "- Creating a DataFrame by reading in data from Cloud Storage.\n", - "- Manipulating data in the DataFrame to build LLM prompts.\n", - "- Sending DataFrame prompts to the LLM model using the `predict` method.\n", - "- Creating and using a custom function to transform the output provided by the LLM model response.\n", - "- Exporting the resulting transformed DataFrame as a CSV file." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "08d289fa873f" - }, - "source": [ - "### Dataset\n", - "\n", - "This tutorial uses a dataset listing the names of various pandas DataFrame and Series APIs." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "aed92deeb4a0" - }, - "source": [ - "### Costs\n", - "\n", - "This tutorial uses billable components of Google Cloud:\n", - "\n", - "* BigQuery\n", - "* Generative AI support on Vertex AI\n", - "* Cloud Functions\n", - "\n", - "Learn about [BigQuery compute pricing](https://cloud.google.com/bigquery/pricing#analysis_pricing_models),\n", - "[Generative AI support on Vertex AI pricing](https://cloud.google.com/vertex-ai/pricing#generative_ai_models), and [Cloud Functions pricing](https://cloud.google.com/functions/pricing), and use the [Pricing Calculator](https://cloud.google.com/products/calculator/)\n", - "to generate a cost estimate based on your projected usage." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "i7EUnXsZhAGF" - }, - "source": [ - "## Installation\n", - "\n", - "Install the following packages, which are required to run this notebook:" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": { - "id": "2b4ef9b72d43" - }, - "outputs": [], - "source": [ - "!pip install bigframes --upgrade --quiet" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "BF1j6f9HApxa" - }, - "source": [ - "## Before you begin\n", - "\n", - "Complete the tasks in this section to set up your environment." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "Wbr2aVtFQBcg" - }, - "source": [ - "### Set up your Google Cloud project\n", - "\n", - "**The following steps are required, regardless of your notebook environment.**\n", - "\n", - "1. [Select or create a Google Cloud project](https://console.cloud.google.com/cloud-resource-manager). When you first create an account, you get a $300 credit towards your compute/storage costs.\n", - "\n", - "2. [Make sure that billing is enabled for your project](https://cloud.google.com/billing/docs/how-to/modify-project).\n", - "\n", - "3. [Click here](https://console.cloud.google.com/flows/enableapi?apiid=bigquery.googleapis.com,bigqueryconnection.googleapis.com,cloudfunctions.googleapis.com,run.googleapis.com,artifactregistry.googleapis.com,cloudbuild.googleapis.com,cloudresourcemanager.googleapis.com) to enable the following APIs:\n", - "\n", - " * BigQuery API\n", - " * BigQuery Connection API\n", - " * Cloud Functions API\n", - " * Cloud Run API\n", - " * Artifact Registry API\n", - " * Cloud Build API\n", - " * Cloud Resource Manager API\n", - " * Vertex AI API\n", - "\n", - "4. If you are running this notebook locally, install the [Cloud SDK](https://cloud.google.com/sdk)." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "WReHDGG5g0XY" - }, - "source": [ - "#### Set your project ID\n", - "\n", - "If you don't know your project ID, try the following:\n", - "* Run `gcloud config list`.\n", - "* Run `gcloud projects list`.\n", - "* See the support page: [Locate the project ID](https://support.google.com/googleapi/answer/7014113)." - ] - }, - { - "cell_type": "code", - "execution_count": 27, - "metadata": { - "id": "oM1iC_MfAts1" - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\u001b[1;31mERROR:\u001b[0m (gcloud.config.set) argument VALUE: Must be specified.\n", - "Usage: gcloud config set SECTION/PROPERTY VALUE [optional flags]\n", - " optional flags may be --help | --installation\n", - "\n", - "For detailed information on this command and its flags, run:\n", - " gcloud config set --help\n" - ] - } - ], - "source": [ - "PROJECT_ID = \"\" # @param {type:\"string\"}\n", - "\n", - "# Set the project id\n", - "! gcloud config set project {PROJECT_ID}" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "region" - }, - "source": [ - "#### Set the region\n", - "\n", - "You can also change the `REGION` variable used by BigQuery. Learn more about [BigQuery regions](https://cloud.google.com/bigquery/docs/locations#supported_locations)." - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": { - "id": "eF-Twtc4XGem" - }, - "outputs": [], - "source": [ - "REGION = \"US\" # @param {type: \"string\"}" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "sBCra4QMA2wR" - }, - "source": [ - "### Authenticate your Google Cloud account\n", - "\n", - "Depending on your Jupyter environment, you might have to manually authenticate. Follow the relevant instructions below." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "74ccc9e52986" - }, - "source": [ - "**Vertex AI Workbench**\n", - "\n", - "Do nothing, you are already authenticated." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "de775a3773ba" - }, - "source": [ - "**Local JupyterLab instance**\n", - "\n", - "Uncomment and run the following cell:" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": { - "id": "254614fa0c46" - }, - "outputs": [], - "source": [ - "# ! gcloud auth login" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "ef21552ccea8" - }, - "source": [ - "**Colab**\n", - "\n", - "Uncomment and run the following cell:" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": { - "id": "603adbbf0532" - }, - "outputs": [], - "source": [ - "# from google.colab import auth\n", - "# auth.authenticate_user()" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "960505627ddf" - }, - "source": [ - "### Import libraries" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": { - "id": "PyQmSRbKA8r-" - }, - "outputs": [], - "source": [ - "import bigframes.pandas as bf\n", - "from google.cloud import bigquery\n", - "from google.cloud import bigquery_connection_v1 as bq_connection" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "init_aip:mbsdk,all" - }, - "source": [ - "### Set BigQuery DataFrames options" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "metadata": { - "id": "NPPMuw2PXGeo" - }, - "outputs": [], - "source": [ - "# Note: The project option is not required in all environments.\n", - "# On BigQuery Studio, the project ID is automatically detected.\n", - "bf.options.bigquery.project = PROJECT_ID\n", - "\n", - "# Note: The location option is not required.\n", - "# It defaults to the location of the first table or query\n", - "# passed to read_gbq(). For APIs where a location can't be\n", - "# auto-detected, the location defaults to the \"US\" location.\n", - "bf.options.bigquery.location = REGION" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "DTVtFlqeFbrU" - }, - "source": [ - "If you want to reset the location of the created DataFrame or Series objects, reset the session by executing `bf.close_session()`. After that, you can reuse `bf.options.bigquery.location` to specify another location." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "6eytf4xQHzcF" - }, - "source": [ - "# Define the LLM model\n", - "\n", - "BigQuery DataFrames provides integration with [Gemini Models](https://cloud.google.com/vertex-ai/generative-ai/docs/learn/models#gemini-models) via Vertex AI.\n", - "\n", - "This section walks through a few steps required in order to use the model in your notebook." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "qUjT8nw-jIXp" - }, - "source": [ - "## Define the model\n", - "\n", - "Use `bigframes.ml.llm` to define the model:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "sdjeXFwcHfl7" - }, - "outputs": [ - { - "data": { - "text/html": [ - "Query job 0ee1a08e-788e-4fc7-b061-52c23ab25d5a is DONE. 0 Bytes processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "from bigframes.ml.llm import GeminiTextGenerator\n", - "\n", - "model = GeminiTextGenerator(model_name=\"gemini-2.5-flash\")" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "GbW0oCnU1s1N" - }, - "source": [ - "# Read data from Cloud Storage into BigQuery DataFrames\n", - "\n", - "You can create a BigQuery DataFrames DataFrame by reading data from any of the following locations:\n", - "\n", - "* A local data file\n", - "* Data stored in a BigQuery table\n", - "* A data file stored in Cloud Storage\n", - "* An in-memory pandas DataFrame\n", - "\n", - "In this tutorial, you create BigQuery DataFrames DataFrames by reading two CSV files stored in Cloud Storage, one containing a list of DataFrame API names and one containing a list of Series API names." - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "metadata": { - "id": "SchiTkQGIJog" - }, - "outputs": [], - "source": [ - "df_api = bf.read_csv(\"gs://cloud-samples-data/vertex-ai/bigframe/df.csv\")\n", - "series_api = bf.read_csv(\"gs://cloud-samples-data/vertex-ai/bigframe/series.csv\")" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "7OBjw2nmQY3-" - }, - "source": [ - "Take a peek at a few rows of data for each file:" - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "metadata": { - "id": "QCqgVCIsGGuv" - }, - "outputs": [ - { - "data": { - "text/html": [ - "Query job 48be241c-ee93-4dfa-a9e3-66b64c4b5150 is DONE. 0 Bytes processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job 6af9caa5-4f7a-48f0-a7df-d692ee063b7e is DONE. 0 Bytes processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
API
0values
1dtypes
\n", - "

2 rows × 1 columns

\n", - "
[2 rows x 1 columns in total]" - ], - "text/plain": [ - " API\n", - "0 values\n", - "1 dtypes\n", - "\n", - "[2 rows x 1 columns]" - ] - }, - "execution_count": 12, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "df_api.head(2)" - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "metadata": { - "id": "BGJnZbgEGS5-" - }, - "outputs": [ - { - "data": { - "text/html": [ - "Query job 41e4f2e7-689a-45d9-bf92-4416f5560b81 is DONE. 0 Bytes processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job aae0b164-f786-4734-8c79-2af9805af0cf is DONE. 0 Bytes processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
API
0shape
1size
\n", - "

2 rows × 1 columns

\n", - "
[2 rows x 1 columns in total]" - ], - "text/plain": [ - " API\n", - "0 shape\n", - "1 size\n", - "\n", - "[2 rows x 1 columns]" - ] - }, - "execution_count": 13, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "series_api.head(2)" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "m3ZJEsi7SUKV" - }, - "source": [ - "# Generate code using the LLM model\n", - "\n", - "Prepare the prompts and send them to the LLM model for prediction." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "9EMAqR37AfLS" - }, - "source": [ - "## Prompt design in BigQuery DataFrames\n", - "\n", - "Designing prompts for LLMs is a fast growing area and you can read more in [this documentation](https://cloud.google.com/vertex-ai/docs/generative-ai/learn/introduction-prompt-design).\n", - "\n", - "For this tutorial, you use a simple prompt to ask the LLM model for sample code for each of the API methods (or rows) from the last step's DataFrames. The output is the new DataFrames `df_prompt` and `series_prompt`, which contain the full prompt text." - ] - }, - { - "cell_type": "code", - "execution_count": 14, - "metadata": { - "id": "EDAaIwHpQCDZ" - }, - "outputs": [ - { - "data": { - "text/html": [ - "Query job 17f50c10-aa81-4023-b206-4ba59ddf2269 is DONE. 0 Bytes processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job d6d217aa-a623-4ea4-83fb-8f1b8bfb8e68 is DONE. 0 Bytes processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job a275a107-752e-46f8-be9f-9cb35eb6b0b9 is DONE. 132 Bytes processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/plain": [ - "0 Generate Pandas sample code for DataFrame.values\n", - "1 Generate Pandas sample code for DataFrame.dtypes\n", - "Name: API, dtype: string" - ] - }, - "execution_count": 14, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "df_prompt_prefix = \"Generate Pandas sample code for DataFrame.\"\n", - "series_prompt_prefix = \"Generate Pandas sample code for Series.\"\n", - "\n", - "df_prompt = (df_prompt_prefix + df_api['API'])\n", - "series_prompt = (series_prompt_prefix + series_api['API'])\n", - "\n", - "df_prompt.head(2)" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "rwPLjqW2Ajzh" - }, - "source": [ - "## Make predictions using the LLM model\n", - "\n", - "Use the BigQuery DataFrames DataFrame containing the full prompt text as the input to the `predict` method. The `predict` method calls the LLM model and returns its generated text output back to two new BigQuery DataFrames DataFrames, `df_pred` and `series_pred`.\n", - "\n", - "Note: The predictions might take a few minutes to run." - ] - }, - { - "cell_type": "code", - "execution_count": 15, - "metadata": { - "id": "6i6HkFJZa8na" - }, - "outputs": [ - { - "data": { - "text/html": [ - "Query job 01f95d2d-901d-4edf-bd3a-245d17c31ef6 is DONE. 0 Bytes processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job 55927a6f-b023-479a-b9bf-826abde77111 is DONE. 584 Bytes processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job 445eb0af-f643-40c5-9c1e-25aa3db8374a is DONE. 146 Bytes processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job ddee268c-773a-4dcc-b14c-ebdd90c2c347 is DONE. 0 Bytes processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job d7f1eb26-28b2-44ba-8858-5cd4df8621bd is DONE. 904 Bytes processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job f24d27a5-0e36-4fb5-953b-d09298f83af6 is DONE. 226 Bytes processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "df_pred = model.predict(df_prompt.to_frame(), max_output_tokens=1024)\n", - "series_pred = model.predict(series_prompt.to_frame(), max_output_tokens=1024)" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "89cB8MW4UIdV" - }, - "source": [ - "Once the predictions are processed, take a look at the sample output from the LLM, which provides code samples for the API names listed in the DataFrames dataset." - ] - }, - { - "cell_type": "code", - "execution_count": 16, - "metadata": { - "id": "9A2gw6hP_2nX" - }, - "outputs": [ - { - "data": { - "text/html": [ - "Query job 65599c98-72ad-4088-8b09-f29bf05c164b is DONE. 21.8 kB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "```python\n", - "import pandas as pd\n", - "\n", - "# Create a DataFrame\n", - "df = pd.DataFrame([[1, 2, 3], [4, 5, 6], [7, 8, 9]])\n", - "\n", - "# Get the values as a NumPy array\n", - "values = df.values\n", - "\n", - "# Print the values\n", - "print(values)\n", - "```\n" - ] - } - ], - "source": [ - "print(df_pred['ml_generate_text_llm_result'].iloc[0])" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "Fx4lsNqMorJ-" - }, - "source": [ - "# Manipulate LLM output using a remote function\n", - "\n", - "The output that the LLM provides often contains additional text beyond the code sample itself. Using BigQuery DataFrames, you can deploy custom Python functions that process and transform this output.\n", - "\n" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "d8L7SN03VByG" - }, - "source": [ - "Running the cell below creates a custom function that you can use to process the LLM output data in two ways:\n", - "1. Strip the LLM text output to include only the code block.\n", - "2. Substitute `import pandas as pd` with `import bigframes.pandas as bf` so that the resulting code block works with BigQuery DataFrames." - ] - }, - { - "cell_type": "code", - "execution_count": 17, - "metadata": { - "id": "GskyyUQPowBT" - }, - "outputs": [], - "source": [ - "@bf.remote_function(cloud_function_service_account=\"default\")\n", - "def extract_code(text: str) -> str:\n", - " try:\n", - " res = text[text.find('\\n')+1:text.find('```', 3)]\n", - " res = res.replace(\"import pandas as pd\", \"import bigframes.pandas as bf\")\n", - " if \"import bigframes.pandas as bf\" not in res:\n", - " res = \"import bigframes.pandas as bf\\n\" + res\n", - " return res\n", - " except:\n", - " return \"\"" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "hVQAoqBUOJQf" - }, - "source": [ - "The custom function is deployed as a Cloud Function, and then integrated with BigQuery as a [remote function](https://cloud.google.com/bigquery/docs/remote-functions). Save both of the function names so that you can clean them up at the end of this notebook." - ] - }, - { - "cell_type": "code", - "execution_count": 18, - "metadata": { - "id": "PBlp-C-DOHRO" - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Cloud Function Name projects/swast-scratch/locations/us-central1/functions/bigframes-6e7606963c3f06b8181b3cb9449a4363\n", - "Remote Function Name swast-scratch._63cfa399614a54153cc386c27d6c0c6fdb249f9e.bigframes_6e7606963c3f06b8181b3cb9449a4363\n" - ] - } - ], - "source": [ - "CLOUD_FUNCTION_NAME = format(extract_code.bigframes_cloud_function)\n", - "print(\"Cloud Function Name \" + CLOUD_FUNCTION_NAME)\n", - "REMOTE_FUNCTION_NAME = format(extract_code.bigframes_remote_function)\n", - "print(\"Remote Function Name \" + REMOTE_FUNCTION_NAME)" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "4FEucaiqVs3H" - }, - "source": [ - "Apply the custom function to each LLM output DataFrame to get the processed results:" - ] - }, - { - "cell_type": "code", - "execution_count": 19, - "metadata": { - "id": "bsQ9cmoWo0Ps" - }, - "outputs": [ - { - "data": { - "text/html": [ - "Query job 047903f8-ea67-430a-8281-8fb5a119b779 is DONE. 21.8 kB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job 793df956-0b1a-46ba-bb5e-e428171f3bd0 is DONE. 26.3 kB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "df_code = df_pred.assign(code=df_pred['ml_generate_text_llm_result'].apply(extract_code))\n", - "series_code = series_pred.assign(code=series_pred['ml_generate_text_llm_result'].apply(extract_code))" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "ujQVVuhfWA3y" - }, - "source": [ - "You can see the differences by inspecting the first row of data:" - ] - }, - { - "cell_type": "code", - "execution_count": 20, - "metadata": { - "id": "7yWzjhGy_zcy" - }, - "outputs": [ - { - "data": { - "text/html": [ - "Query job 6974c2b7-2ed9-4564-a80b-57aef6959e19 is DONE. 22.8 kB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "import bigframes.pandas as bf\n", - "\n", - "# Create a DataFrame\n", - "df = pd.DataFrame([[1, 2, 3], [4, 5, 6], [7, 8, 9]])\n", - "\n", - "# Get the values as a NumPy array\n", - "values = df.values\n", - "\n", - "# Print the values\n", - "print(values)\n", - "\n" - ] - } - ], - "source": [ - "print(df_code['code'].iloc[0])" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "GTRdUw-Ro5R1" - }, - "source": [ - "# Save the results to Cloud Storage\n", - "\n", - "BigQuery DataFrames lets you save a BigQuery DataFrames DataFrame as a CSV file in Cloud Storage for further use. Try that now with your processed LLM output data." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "9DQ7eiQxPTi3" - }, - "source": [ - "Create a new Cloud Storage bucket with a unique name:" - ] - }, - { - "cell_type": "code", - "execution_count": 21, - "metadata": { - "id": "-J5LHgS6LLZ0" - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Creating gs://code-samples-773ee0f2-e302-11ee-8298-4201c0a8181f/...\n" - ] - } - ], - "source": [ - "import uuid\n", - "BUCKET_ID = \"code-samples-\" + str(uuid.uuid1())\n", - "\n", - "!gcloud storage buckets create gs://{BUCKET_ID}" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "tyxZXj0UPYUv" - }, - "source": [ - "Use `to_csv` to write each BigQuery DataFrames DataFrame as a CSV file in the Cloud Storage bucket:" - ] - }, - { - "cell_type": "code", - "execution_count": 22, - "metadata": { - "id": "Zs_b5L-4IvER" - }, - "outputs": [ - { - "data": { - "text/html": [ - "Query job 81277037-032f-4557-a46e-1d39702f33d5 is DONE. 22.8 kB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job 8dc5a38c-ac16-44e7-83dd-4187380f780f is DONE. 0 Bytes processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job 9087a758-b1f9-4be7-889b-7761ef0ad966 is DONE. 27.7 kB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job 6126ea72-c6f7-43f0-8888-e1c2a464a8a4 is DONE. 0 Bytes processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "ur8xi4C7S06n" + }, + "outputs": [], + "source": [ + "# Copyright 2022 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": { + "id": "JAPoU8Sm5E6e" + }, + "source": [ + "## Use BigQuery DataFrames with Generative AI for code generation\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", + " \"Vertex\n", + " Open in Vertex AI Workbench\n", + " \n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "24743cf4a1e1" + }, + "source": [ + "**_NOTE_**: This notebook has been tested in the following environment:\n", + "\n", + "* Python version = 3.10" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "tvgnzT1CKxrO" + }, + "source": [ + "## Overview\n", + "\n", + "Use this notebook to walk through an example use case of generating sample code by using BigQuery DataFrames and its integration with Generative AI support on Vertex AI.\n", + "\n", + "Learn more about [BigQuery DataFrames](https://cloud.google.com/python/docs/reference/bigframes/latest)." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "d975e698c9a4" + }, + "source": [ + "### Objective\n", + "\n", + "In this tutorial, you create a CSV file containing sample code for calling a given set of APIs.\n", + "\n", + "The steps include:\n", + "\n", + "- Defining an LLM model in BigQuery DataFrames, specifically the [`text-bison` model of the PaLM API](https://cloud.google.com/vertex-ai/docs/generative-ai/model-reference/text), using `bigframes.ml.llm`.\n", + "- Creating a DataFrame by reading in data from Cloud Storage.\n", + "- Manipulating data in the DataFrame to build LLM prompts.\n", + "- Sending DataFrame prompts to the LLM model using the `predict` method.\n", + "- Creating and using a custom function to transform the output provided by the LLM model response.\n", + "- Exporting the resulting transformed DataFrame as a CSV file." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "08d289fa873f" + }, + "source": [ + "### Dataset\n", + "\n", + "This tutorial uses a dataset listing the names of various pandas DataFrame and Series APIs." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "aed92deeb4a0" + }, + "source": [ + "### Costs\n", + "\n", + "This tutorial uses billable components of Google Cloud:\n", + "\n", + "* BigQuery\n", + "* Generative AI support on Vertex AI\n", + "* Cloud Functions\n", + "\n", + "Learn about [BigQuery compute pricing](https://cloud.google.com/bigquery/pricing#analysis_pricing_models),\n", + "[Generative AI support on Vertex AI pricing](https://cloud.google.com/vertex-ai/pricing#generative_ai_models), and [Cloud Functions pricing](https://cloud.google.com/functions/pricing), and use the [Pricing Calculator](https://cloud.google.com/products/calculator/)\n", + "to generate a cost estimate based on your projected usage." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "i7EUnXsZhAGF" + }, + "source": [ + "## Installation\n", + "\n", + "Install the following packages, which are required to run this notebook:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "2b4ef9b72d43" + }, + "outputs": [], + "source": [ + "!pip install bigframes --upgrade --quiet" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "BF1j6f9HApxa" + }, + "source": [ + "## Before you begin\n", + "\n", + "Complete the tasks in this section to set up your environment." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "Wbr2aVtFQBcg" + }, + "source": [ + "### Set up your Google Cloud project\n", + "\n", + "**The following steps are required, regardless of your notebook environment.**\n", + "\n", + "1. [Select or create a Google Cloud project](https://console.cloud.google.com/cloud-resource-manager). When you first create an account, you get a $300 credit towards your compute/storage costs.\n", + "\n", + "2. [Make sure that billing is enabled for your project](https://cloud.google.com/billing/docs/how-to/modify-project).\n", + "\n", + "3. [Click here](https://console.cloud.google.com/flows/enableapi?apiid=bigquery.googleapis.com,bigqueryconnection.googleapis.com,cloudfunctions.googleapis.com,run.googleapis.com,artifactregistry.googleapis.com,cloudbuild.googleapis.com,cloudresourcemanager.googleapis.com) to enable the following APIs:\n", + "\n", + " * BigQuery API\n", + " * BigQuery Connection API\n", + " * Cloud Functions API\n", + " * Cloud Run API\n", + " * Artifact Registry API\n", + " * Cloud Build API\n", + " * Cloud Resource Manager API\n", + " * Vertex AI API\n", + "\n", + "4. If you are running this notebook locally, install the [Cloud SDK](https://cloud.google.com/sdk)." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "WReHDGG5g0XY" + }, + "source": [ + "#### Set your project ID\n", + "\n", + "If you don't know your project ID, try the following:\n", + "* Run `gcloud config list`.\n", + "* Run `gcloud projects list`.\n", + "* See the support page: [Locate the project ID](https://support.google.com/googleapi/answer/7014113)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "oM1iC_MfAts1" + }, + "outputs": [], + "source": [ + "PROJECT_ID = \"\" # @param {type:\"string\"}\n", + "\n", + "# Set the project id\n", + "! gcloud config set project {PROJECT_ID}" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "region" + }, + "source": [ + "#### Set the region\n", + "\n", + "You can also change the `REGION` variable used by BigQuery. Learn more about [BigQuery regions](https://cloud.google.com/bigquery/docs/locations#supported_locations)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "eF-Twtc4XGem" + }, + "outputs": [], + "source": [ + "REGION = \"US\" # @param {type: \"string\"}" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "sBCra4QMA2wR" + }, + "source": [ + "### Authenticate your Google Cloud account\n", + "\n", + "Depending on your Jupyter environment, you might have to manually authenticate. Follow the relevant instructions below." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "74ccc9e52986" + }, + "source": [ + "**Vertex AI Workbench**\n", + "\n", + "Do nothing, you are already authenticated." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "de775a3773ba" + }, + "source": [ + "**Local JupyterLab instance**\n", + "\n", + "Uncomment and run the following cell:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "254614fa0c46" + }, + "outputs": [], + "source": [ + "# ! gcloud auth login" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "ef21552ccea8" + }, + "source": [ + "**Colab**\n", + "\n", + "Uncomment and run the following cell:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "603adbbf0532" + }, + "outputs": [], + "source": [ + "# from google.colab import auth\n", + "# auth.authenticate_user()" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "960505627ddf" + }, + "source": [ + "### Import libraries" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "PyQmSRbKA8r-" + }, + "outputs": [], + "source": [ + "import bigframes.pandas as bf\n", + "from google.cloud import bigquery\n", + "from google.cloud import bigquery_connection_v1 as bq_connection" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "init_aip:mbsdk,all" + }, + "source": [ + "### Set BigQuery DataFrames options" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "NPPMuw2PXGeo" + }, + "outputs": [], + "source": [ + "bf.options.bigquery.project = PROJECT_ID\n", + "bf.options.bigquery.location = REGION" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "DTVtFlqeFbrU" + }, + "source": [ + "If you want to reset the location of the created DataFrame or Series objects, reset the session by executing `bf.close_session()`. After that, you can reuse `bf.options.bigquery.location` to specify another location." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "6eytf4xQHzcF" + }, + "source": [ + "# Define the LLM model\n", + "\n", + "BigQuery DataFrames provides integration with [`text-bison` model of the PaLM API](https://cloud.google.com/vertex-ai/docs/generative-ai/model-reference/text) via Vertex AI.\n", + "\n", + "This section walks through a few steps required in order to use the model in your notebook." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "rS4VO1TGiO4G" + }, + "source": [ + "## Create a BigQuery Cloud resource connection\n", + "\n", + "You need to create a [Cloud resource connection](https://cloud.google.com/bigquery/docs/create-cloud-resource-connection) to enable BigQuery DataFrames to interact with Vertex AI services." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "KFPjDM4LVh96" + }, + "outputs": [], + "source": [ + "CONN_NAME = \"bqdf-llm\"\n", + "\n", + "client = bq_connection.ConnectionServiceClient()\n", + "new_conn_parent = f\"projects/{PROJECT_ID}/locations/{REGION}\"\n", + "exists_conn_parent = f\"projects/{PROJECT_ID}/locations/{REGION}/connections/{CONN_NAME}\"\n", + "cloud_resource_properties = bq_connection.CloudResourceProperties({})\n", + "\n", + "try:\n", + " request = client.get_connection(\n", + " request=bq_connection.GetConnectionRequest(name=exists_conn_parent)\n", + " )\n", + " CONN_SERVICE_ACCOUNT = f\"serviceAccount:{request.cloud_resource.service_account_id}\"\n", + "except Exception:\n", + " connection = bq_connection.types.Connection(\n", + " {\"friendly_name\": CONN_NAME, \"cloud_resource\": cloud_resource_properties}\n", + " )\n", + " request = bq_connection.CreateConnectionRequest(\n", + " {\n", + " \"parent\": new_conn_parent,\n", + " \"connection_id\": CONN_NAME,\n", + " \"connection\": connection,\n", + " }\n", + " )\n", + " response = client.create_connection(request)\n", + " CONN_SERVICE_ACCOUNT = (\n", + " f\"serviceAccount:{response.cloud_resource.service_account_id}\"\n", + " )\n", + "print(CONN_SERVICE_ACCOUNT)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "W6l6Ol2biU9h" + }, + "source": [ + "## Set permissions for the service account\n", + "\n", + "The resource connection service account requires certain project-level permissions:\n", + " - `roles/aiplatform.user` and `roles/bigquery.connectionUser`: These roles are required for the connection to create a model definition using the LLM model in Vertex AI ([documentation](https://cloud.google.com/bigquery/docs/generate-text#give_the_service_account_access)).\n", + " - `roles/run.invoker`: This role is required for the connection to have read-only access to Cloud Run services that back custom/remote functions ([documentation](https://cloud.google.com/bigquery/docs/remote-functions#grant_permission_on_function)).\n", + "\n", + "Set these permissions by running the following `gcloud` commands:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "d8wja24SVq6s" + }, + "outputs": [], + "source": [ + "!gcloud projects add-iam-policy-binding {PROJECT_ID} --condition=None --no-user-output-enabled --member={CONN_SERVICE_ACCOUNT} --role='roles/bigquery.connectionUser'\n", + "!gcloud projects add-iam-policy-binding {PROJECT_ID} --condition=None --no-user-output-enabled --member={CONN_SERVICE_ACCOUNT} --role='roles/aiplatform.user'\n", + "!gcloud projects add-iam-policy-binding {PROJECT_ID} --condition=None --no-user-output-enabled --member={CONN_SERVICE_ACCOUNT} --role='roles/run.invoker'" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "qUjT8nw-jIXp" + }, + "source": [ + "## Define the model\n", + "\n", + "Use `bigframes.ml.llm` to define the model:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "sdjeXFwcHfl7" + }, + "outputs": [], + "source": [ + "from bigframes.ml.llm import PaLM2TextGenerator\n", + "\n", + "session = bf.get_global_session()\n", + "connection = f\"{PROJECT_ID}.{REGION}.{CONN_NAME}\"\n", + "model = PaLM2TextGenerator(session=session, connection_name=connection)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "GbW0oCnU1s1N" + }, + "source": [ + "# Read data from Cloud Storage into BigQuery DataFrames\n", + "\n", + "You can create a BigQuery DataFrames DataFrame by reading data from any of the following locations:\n", + "\n", + "* A local data file\n", + "* Data stored in a BigQuery table\n", + "* A data file stored in Cloud Storage\n", + "* An in-memory pandas DataFrame\n", + "\n", + "In this tutorial, you create BigQuery DataFrames DataFrames by reading two CSV files stored in Cloud Storage, one containing a list of DataFrame API names and one containing a list of Series API names." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "SchiTkQGIJog" + }, + "outputs": [], + "source": [ + "df_api = bf.read_csv(\"gs://cloud-samples-data/vertex-ai/bigframe/df.csv\")\n", + "series_api = bf.read_csv(\"gs://cloud-samples-data/vertex-ai/bigframe/series.csv\")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "7OBjw2nmQY3-" + }, + "source": [ + "Take a peek at a few rows of data for each file:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "QCqgVCIsGGuv" + }, + "outputs": [], + "source": [ + "df_api.head(2)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "BGJnZbgEGS5-" + }, + "outputs": [], + "source": [ + "series_api.head(2)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "m3ZJEsi7SUKV" + }, + "source": [ + "# Generate code using the LLM model\n", + "\n", + "Prepare the prompts and send them to the LLM model for prediction." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "9EMAqR37AfLS" + }, + "source": [ + "## Prompt design in BigQuery DataFrames\n", + "\n", + "Designing prompts for LLMs is a fast growing area and you can read more in [this documentation](https://cloud.google.com/vertex-ai/docs/generative-ai/learn/introduction-prompt-design).\n", + "\n", + "For this tutorial, you use a simple prompt to ask the LLM model for sample code for each of the API methods (or rows) from the last step's DataFrames. The output is the new DataFrames `df_prompt` and `series_prompt`, which contain the full prompt text." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "EDAaIwHpQCDZ" + }, + "outputs": [], + "source": [ + "df_prompt_prefix = \"Generate Pandas sample code for DataFrame.\"\n", + "series_prompt_prefix = \"Generate Pandas sample code for Series.\"\n", + "\n", + "df_prompt = (df_prompt_prefix + df_api['API'])\n", + "series_prompt = (series_prompt_prefix + series_api['API'])\n", + "\n", + "df_prompt.head(2)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "rwPLjqW2Ajzh" + }, + "source": [ + "## Make predictions using the LLM model\n", + "\n", + "Use the BigQuery DataFrames DataFrame containing the full prompt text as the input to the `predict` method. The `predict` method calls the LLM model and returns its generated text output back to two new BigQuery DataFrames DataFrames, `df_pred` and `series_pred`.\n", + "\n", + "Note: The predictions might take a few minutes to run." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "6i6HkFJZa8na" + }, + "outputs": [], + "source": [ + "df_pred = model.predict(df_prompt.to_frame(), max_output_tokens=1024)\n", + "series_pred = model.predict(series_prompt.to_frame(), max_output_tokens=1024)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "89cB8MW4UIdV" + }, + "source": [ + "Once the predictions are processed, take a look at the sample output from the LLM, which provides code samples for the API names listed in the DataFrames dataset." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "9A2gw6hP_2nX" + }, + "outputs": [], + "source": [ + "print(df_pred['ml_generate_text_llm_result'].iloc[0])" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "Fx4lsNqMorJ-" + }, + "source": [ + "# Manipulate LLM output using a remote function\n", + "\n", + "The output that the LLM provides often contains additional text beyond the code sample itself. Using BigQuery DataFrames, you can deploy custom Python functions that process and transform this output.\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "d8L7SN03VByG" + }, + "source": [ + "Running the cell below creates a custom function that you can use to process the LLM output data in two ways:\n", + "1. Strip the LLM text output to include only the code block.\n", + "2. Substitute `import pandas as pd` with `import bigframes.pandas as bf` so that the resulting code block works with BigQuery DataFrames." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "GskyyUQPowBT" + }, + "outputs": [], + "source": [ + "@bf.remote_function([str], str, bigquery_connection=CONN_NAME)\n", + "def extract_code(text: str):\n", + " try:\n", + " res = text[text.find('\\n')+1:text.find('```', 3)]\n", + " res = res.replace(\"import pandas as pd\", \"import bigframes.pandas as bf\")\n", + " if \"import bigframes.pandas as bf\" not in res:\n", + " res = \"import bigframes.pandas as bf\\n\" + res\n", + " return res\n", + " except:\n", + " return \"\"" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "hVQAoqBUOJQf" + }, + "source": [ + "The custom function is deployed as a Cloud Function, and then integrated with BigQuery as a [remote function](https://cloud.google.com/bigquery/docs/remote-functions). Save both of the function names so that you can clean them up at the end of this notebook." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "PBlp-C-DOHRO" + }, + "outputs": [], + "source": [ + "CLOUD_FUNCTION_NAME = format(extract_code.bigframes_cloud_function)\n", + "print(\"Cloud Function Name \" + CLOUD_FUNCTION_NAME)\n", + "REMOTE_FUNCTION_NAME = format(extract_code.bigframes_remote_function)\n", + "print(\"Remote Function Name \" + REMOTE_FUNCTION_NAME)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "4FEucaiqVs3H" + }, + "source": [ + "Apply the custom function to each LLM output DataFrame to get the processed results:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "bsQ9cmoWo0Ps" + }, + "outputs": [], + "source": [ + "df_code = df_pred.assign(code=df_pred['ml_generate_text_llm_result'].apply(extract_code))\n", + "series_code = series_pred.assign(code=series_pred['ml_generate_text_llm_result'].apply(extract_code))" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "ujQVVuhfWA3y" + }, + "source": [ + "You can see the differences by inspecting the first row of data:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "7yWzjhGy_zcy" + }, + "outputs": [], + "source": [ + "print(df_code['code'].iloc[0])" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "GTRdUw-Ro5R1" + }, + "source": [ + "# Save the results to Cloud Storage\n", + "\n", + "BigQuery DataFrames lets you save a BigQuery DataFrames DataFrame as a CSV file in Cloud Storage for further use. Try that now with your processed LLM output data." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "9DQ7eiQxPTi3" + }, + "source": [ + "Create a new Cloud Storage bucket with a unique name:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "-J5LHgS6LLZ0" + }, + "outputs": [], + "source": [ + "import uuid\n", + "BUCKET_ID = \"code-samples-\" + str(uuid.uuid1())\n", + "\n", + "!gsutil mb gs://{BUCKET_ID}" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "tyxZXj0UPYUv" + }, + "source": [ + "Use `to_csv` to write each BigQuery DataFrames DataFrame as a CSV file in the Cloud Storage bucket:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "Zs_b5L-4IvER" + }, + "outputs": [], + "source": [ + "df_code[[\"code\"]].to_csv(f\"gs://{BUCKET_ID}/df_code*.csv\")\n", + "series_code[[\"code\"]].to_csv(f\"gs://{BUCKET_ID}/series_code*.csv\")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "UDBtDlrTuuh8" + }, + "source": [ + "You can navigate to the Cloud Storage bucket browser to download the two files and view them.\n", + "\n", + "Run the following cell, and then follow the link to your Cloud Storage bucket browser:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "PspCXu-qu_ND" + }, + "outputs": [], + "source": [ + "print(f'https://console.developers.google.com/storage/browser/{BUCKET_ID}/')" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "RGSvUk48RK20" + }, + "source": [ + "# Summary and next steps\n", + "\n", + "You've used BigQuery DataFrames' integration with LLM models (`bigframes.ml.llm`) to generate code samples, and have tranformed LLM output by creating and using a custom function in BigQuery DataFrames.\n", + "\n", + "Learn more about BigQuery DataFrames in the [documentation](https://cloud.google.com/python/docs/reference/bigframes/latest) and find more sample notebooks in the [GitHub repo](https://github.com/googleapis/python-bigquery-dataframes/tree/main/notebooks)." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "TpV-iwP9qw9c" + }, + "source": [ + "## Cleaning up\n", + "\n", + "To clean up all Google Cloud resources used in this project, you can [delete the Google Cloud\n", + "project](https://cloud.google.com/resource-manager/docs/creating-managing-projects#shutting_down_projects) you used for the tutorial.\n", + "\n", + "Otherwise, you can uncomment the remaining cells and run them to delete the individual resources you created in this tutorial:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "yw7A461XLjvW" + }, + "outputs": [], + "source": [ + "# # Delete the BigQuery Connection\n", + "# from google.cloud import bigquery_connection_v1 as bq_connection\n", + "# client = bq_connection.ConnectionServiceClient()\n", + "# CONNECTION_ID = f\"projects/{PROJECT_ID}/locations/{REGION}/connections/{CONN_NAME}\"\n", + "# client.delete_connection(name=CONNECTION_ID)\n", + "# print(f\"Deleted connection '{CONNECTION_ID}'.\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "sx_vKniMq9ZX" + }, + "outputs": [], + "source": [ + "# # Delete the Cloud Function\n", + "# ! gcloud functions delete {CLOUD_FUNCTION_NAME} --quiet\n", + "# # Delete the Remote Function\n", + "# REMOTE_FUNCTION_NAME = REMOTE_FUNCTION_NAME.replace(PROJECT_ID + \".\", \"\")\n", + "# ! bq rm --routine --force=true {REMOTE_FUNCTION_NAME}" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "iQFo6OUBLmi3" + }, + "outputs": [], + "source": [ + "# # Delete the Google Cloud Storage bucket and files\n", + "# ! gsutil rm -r gs://{BUCKET_ID}\n", + "# print(f\"Deleted bucket '{BUCKET_ID}'.\")" + ] } - ], - "source": [ - "df_code[[\"code\"]].to_csv(f\"gs://{BUCKET_ID}/df_code*.csv\")\n", - "series_code[[\"code\"]].to_csv(f\"gs://{BUCKET_ID}/series_code*.csv\")" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "UDBtDlrTuuh8" - }, - "source": [ - "You can navigate to the Cloud Storage bucket browser to download the two files and view them.\n", - "\n", - "Run the following cell, and then follow the link to your Cloud Storage bucket browser:" - ] - }, - { - "cell_type": "code", - "execution_count": 23, - "metadata": { - "id": "PspCXu-qu_ND" - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "https://console.developers.google.com/storage/browser/code-samples-773ee0f2-e302-11ee-8298-4201c0a8181f/\n" - ] + ], + "metadata": { + "colab": { + "provenance": [], + "toc_visible": true + }, + "kernelspec": { + "display_name": "Python 3", + "name": "python3" } - ], - "source": [ - "print(f'https://console.developers.google.com/storage/browser/{BUCKET_ID}/')" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "RGSvUk48RK20" - }, - "source": [ - "# Summary and next steps\n", - "\n", - "You've used BigQuery DataFrames' integration with LLM models (`bigframes.ml.llm`) to generate code samples, and have tranformed LLM output by creating and using a custom function in BigQuery DataFrames.\n", - "\n", - "Learn more about BigQuery DataFrames in the [documentation](https://cloud.google.com/python/docs/reference/bigframes/latest) and find more sample notebooks in the [GitHub repo](https://github.com/googleapis/python-bigquery-dataframes/tree/main/notebooks)." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "TpV-iwP9qw9c" - }, - "source": [ - "## Cleaning up\n", - "\n", - "To clean up all Google Cloud resources used in this project, you can [delete the Google Cloud\n", - "project](https://cloud.google.com/resource-manager/docs/creating-managing-projects#shutting_down_projects) you used for the tutorial.\n", - "\n", - "Otherwise, you can uncomment the remaining cells and run them to delete the individual resources you created in this tutorial:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "bf.close_session()" - ] - }, - { - "cell_type": "code", - "execution_count": 24, - "metadata": { - "id": "yw7A461XLjvW" - }, - "outputs": [], - "source": [ - "# # Delete the BigQuery Connection\n", - "# from google.cloud import bigquery_connection_v1 as bq_connection\n", - "# client = bq_connection.ConnectionServiceClient()\n", - "# CONNECTION_ID = f\"projects/{PROJECT_ID}/locations/{REGION}/connections/{CONN_NAME}\"\n", - "# client.delete_connection(name=CONNECTION_ID)\n", - "# print(f\"Deleted connection '{CONNECTION_ID}'.\")" - ] - }, - { - "cell_type": "code", - "execution_count": 25, - "metadata": { - "id": "sx_vKniMq9ZX" - }, - "outputs": [], - "source": [ - "# # Delete the Cloud Function\n", - "# ! gcloud functions delete {CLOUD_FUNCTION_NAME} --quiet\n", - "# # Delete the Remote Function\n", - "# REMOTE_FUNCTION_NAME = REMOTE_FUNCTION_NAME.replace(PROJECT_ID + \".\", \"\")\n", - "# ! bq rm --routine --force=true {REMOTE_FUNCTION_NAME}" - ] - }, - { - "cell_type": "code", - "execution_count": 26, - "metadata": { - "id": "iQFo6OUBLmi3" - }, - "outputs": [], - "source": [ - "# # Delete the Google Cloud Storage bucket and files\n", - "# ! gcloud storage rm gs://{BUCKET_ID} --recursive\n", - "# print(f\"Deleted bucket '{BUCKET_ID}'.\")" - ] - } - ], - "metadata": { - "colab": { - "provenance": [], - "toc_visible": true - }, - "kernelspec": { - "display_name": "venv (3.10.14)", - "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": 0 + "nbformat": 4, + "nbformat_minor": 0 } diff --git a/notebooks/generative_ai/bq_dataframes_llm_kmeans.ipynb b/notebooks/generative_ai/bq_dataframes_llm_kmeans.ipynb deleted file mode 100644 index 2d5bb46d95e..00000000000 --- a/notebooks/generative_ai/bq_dataframes_llm_kmeans.ipynb +++ /dev/null @@ -1,1758 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 1, - "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." - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Use BigQuery DataFrames to cluster and characterize complaints\n", - "\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", - " \"Vertex\n", - " Open in Vertex AI Workbench\n", - " \n", - " \n", - " \n", - " \"BQ\n", - " Open in BQ Studio\n", - " \n", - "
" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Overview\n", - "\n", - "The goal of this notebook is to demonstrate a comment characterization algorithm for an online business. We will accomplish this using [Google's Embedding Models](https://cloud.google.com/vertex-ai/generative-ai/docs/learn/models#models) and [KMeans clustering](https://en.wikipedia.org/wiki/K-means_clustering) in three steps:\n", - "\n", - "1. Use TextEmbeddingGenerator to [generate text embeddings](https://cloud.google.com/vertex-ai/docs/generative-ai/embeddings/get-text-embeddings) for each of 10000 complaints sent to an online bank. If you're not familiar with what a text embedding is, it's a list of numbers that are like coordinates in an imaginary \"meaning space\" for sentences. (It's like [word embeddings](https://en.wikipedia.org/wiki/Word_embedding), but for more general text.) The important point for our purposes is that similar sentences are close to each other in this imaginary space.\n", - "2. Use KMeans clustering to group together complaints whose text embeddings are near to eachother. This will give us sets of similar complaints, but we don't yet know _why_ these complaints are similar.\n", - "3. Prompt GeminiTextGenerator in English asking what the difference is between the groups of complaints that we got. Thanks to the power of modern LLMs, the response might give us a very good idea of what these complaints are all about, but remember to [\"understand the limits of your dataset and model.\"](https://ai.google/responsibility/responsible-ai-practices/#:~:text=Understand%20the%20limitations%20of%20your%20dataset%20and%20model)\n", - "\n", - "We will tie these pieces together in Python using BigQuery DataFrames. [Click here](https://cloud.google.com/bigquery/docs/dataframes-quickstart) to learn more about BigQuery DataFrames!" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Dataset\n", - "\n", - "This notebook uses the [CFPB Consumer Complaint Database](https://console.cloud.google.com/marketplace/product/cfpb/complaint-database)." - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Costs\n", - "\n", - "This tutorial uses billable components of Google Cloud:\n", - "\n", - "* BigQuery (compute)\n", - "* BigQuery ML\n", - "* Generative AI support on Vertex AI\n", - "\n", - "Learn about [BigQuery compute pricing](https://cloud.google.com/bigquery/pricing#analysis_pricing_models), [Generative AI support on Vertex AI pricing](https://cloud.google.com/vertex-ai/pricing#generative_ai_models),\n", - "and [BigQuery ML pricing](https://cloud.google.com/bigquery/pricing#bqml),\n", - "and use the [Pricing Calculator](https://cloud.google.com/products/calculator/)\n", - "to generate a cost estimate based on your projected usage." - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Before you begin\n", - "\n", - "Complete the tasks in this section to set up your environment." - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Set up your Google Cloud project\n", - "\n", - "**The following steps are required, regardless of your notebook environment.**\n", - "\n", - "1. [Select or create a Google Cloud project](https://console.cloud.google.com/cloud-resource-manager). When you first create an account, you get a $300 credit towards your compute/storage costs.\n", - "\n", - "2. [Make sure that billing is enabled for your project](https://cloud.google.com/billing/docs/how-to/modify-project).\n", - "\n", - "3. [Click here](https://console.cloud.google.com/flows/enableapi?apiid=bigquery.googleapis.com,bigqueryconnection.googleapis.com,aiplatform.googleapis.com) to enable the following APIs:\n", - "\n", - " * BigQuery API\n", - " * BigQuery Connection API\n", - " * Vertex AI API\n", - "\n", - "4. If you are running this notebook locally, install the [Cloud SDK](https://cloud.google.com/sdk)." - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### Set your project ID\n", - "\n", - "**If you don't know your project ID**, see the support page: [Locate the project ID](https://support.google.com/googleapi/answer/7014113)" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [], - "source": [ - "# set your project ID below\n", - "PROJECT_ID = \"\" # @param {type:\"string\"}\n", - "\n", - "# Set the project id in gcloud\n", - "#! gcloud config set project {PROJECT_ID}" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### Authenticate your Google Cloud account\n", - "\n", - "Depending on your Jupyter environment, you might have to manually authenticate. Follow the relevant instructions below." - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**Vertex AI Workbench**\n", - "\n", - "Do nothing, you are already authenticated." - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**Local JupyterLab instance**\n", - "\n", - "Uncomment and run the following cell:" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [], - "source": [ - "# ! gcloud auth login" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**Colab**\n", - "\n", - "Uncomment and run the following cell:" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": {}, - "outputs": [], - "source": [ - "# from google.colab import auth\n", - "# auth.authenticate_user()" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now we are ready to use BigQuery DataFrames!" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": { - "id": "xckgWno6ouHY" - }, - "source": [ - "## Step 1: Text embedding " - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "BigQuery DataFrames setup" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": { - "id": "R7STCS8xB5d2" - }, - "outputs": [], - "source": [ - "import bigframes.pandas as bf\n", - "\n", - "# Note: The project option is not required in all environments.\n", - "# On BigQuery Studio, the project ID is automatically detected.\n", - "bf.options.bigquery.project = PROJECT_ID" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "If you want to reset the location of the created DataFrame or Series objects, reset the session by executing `bf.close_session()`. After that, you can reuse `bf.options.bigquery.location` to specify another location." - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": { - "id": "v6FGschEowht" - }, - "source": [ - "Data Input - read the data from a publicly available BigQuery dataset" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": { - "id": "zDSwoBo1CU3G" - }, - "outputs": [], - "source": [ - "input_df = bf.read_gbq(\"bigquery-public-data.cfpb_complaints.complaint_database\")" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": { - "id": "tYDoaKgJChiq" - }, - "outputs": [ - { - "data": { - "text/html": [ - "Query job 960f637d-89eb-4bbf-a34c-36ed624e8e9a is DONE. 2.3 GB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job 59bb207c-98e1-4dab-8686-320f276b09df is DONE. 63.7 MB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "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", - "
consumer_complaint_narrative
2557016I've been disputing fraud accounts on my credi...
2557686American Express Platinum totally messed up my...
2558170I recently looked at my credit report and noti...
2558545Select Portfolio Servicing contacted my insura...
2558652I checked my credit report and I am upset on w...
\n", - "
" - ], - "text/plain": [ - " consumer_complaint_narrative\n", - "2557016 I've been disputing fraud accounts on my credi...\n", - "2557686 American Express Platinum totally messed up my...\n", - "2558170 I recently looked at my credit report and noti...\n", - "2558545 Select Portfolio Servicing contacted my insura...\n", - "2558652 I checked my credit report and I am upset on w..." - ] - }, - "execution_count": 7, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "issues_df = input_df[[\"consumer_complaint_narrative\"]].dropna()\n", - "issues_df.peek(n=5) # View an arbitrary five complaints" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Downsample DataFrame to 10,000 records for model training." - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": { - "id": "OltYSUEcsSOW" - }, - "outputs": [], - "source": [ - "# Choose 10,000 complaints randomly and store them in a column in a DataFrame\n", - "downsampled_issues_df = issues_df.sample(n=10000)" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": { - "id": "Wl2o-NYMoygb" - }, - "source": [ - "Generate the text embeddings" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "metadata": { - "id": "li38q8FzDDMu" - }, - "outputs": [ - { - "data": { - "text/html": [ - "Query job e4616b5e-b4c0-490c-a249-484f373f89d9 is DONE. 0 Bytes processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "from bigframes.ml.llm import TextEmbeddingGenerator\n", - "\n", - "model = TextEmbeddingGenerator() # No connection id needed" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "metadata": { - "id": "cOuSOQ5FDewD" - }, - "outputs": [ - { - "data": { - "text/html": [ - "Query job 89f96e88-2dd5-4326-8912-925b237e2877 is DONE. 1.3 GB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/usr/local/google/home/garrettwu/src/bigframes/bigframes/core/__init__.py:108: PreviewWarning: Interpreting JSON column(s) as StringDtype. This behavior may change in future versions.\n", - " warnings.warn(\n" - ] - }, - { - "data": { - "text/html": [ - "Query job bcdbfe96-2cce-4269-81f4-0334033b458b is DONE. 20.0 kB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job 3b89850f-4491-4343-912a-7a2fd3137790 is DONE. 0 Bytes processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job a2999e90-8d14-4f4a-99dc-4e769df01837 is DONE. 72.0 MB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "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", - "
ml_generate_embedding_resultml_generate_embedding_statisticsml_generate_embedding_statuscontent
415[ 2.56774724e-02 -1.06168222e-02 3.06945704e-...{\"token_count\":171,\"truncated\":false}DEPT OF EDUCATION/XXXX is stating I was late ...
596[ 5.90653270e-02 -9.31344274e-03 -7.12460047e-...{\"token_count\":668,\"truncated\":false}I alerted my credit card company XX/XX/2017 th...
706[ 0.01298233 0.00130001 0.01800315 0.037078...{\"token_count\":252,\"truncated\":false}Sallie mae is corrupt. \n", - "I have tried to talk t...
804[-1.39777679e-02 1.68943349e-02 5.53999236e-...{\"token_count\":412,\"truncated\":false}In accordance with the Fair Credit Reporting a...
861[ 2.33309343e-02 -2.36528926e-03 3.37129943e-...{\"token_count\":160,\"truncated\":false}Hello, My name is XXXX XXXX XXXX. I have a pro...
1030[ 0.06060313 -0.06495965 -0.03605044 -0.028016...{\"token_count\":298,\"truncated\":false}Hello, I would like to complain about PayPal H...
1582[ 0.01255985 -0.01652482 -0.02638046 0.036858...{\"token_count\":814,\"truncated\":false}Transunion is listing personal information ( n...
1600[ 5.13355099e-02 4.01246967e-03 5.72342947e-...{\"token_count\":653,\"truncated\":false}On XX/XX/XXXX, I called Citizen Bank at XXXX t...
2060[ 6.44792162e-04 4.95899878e-02 4.67925966e-...{\"token_count\":136,\"truncated\":false}Theses names are the known liars that I have s...
2283[ 4.71848622e-02 -8.68239347e-03 5.80501892e-...{\"token_count\":478,\"truncated\":false}My house was hit by a tree XX/XX/2018. My insu...
2421[-2.90394691e-03 -1.81679502e-02 -7.99657404e-...{\"token_count\":389,\"truncated\":false}I became aware of a credit inquiry on my XXXX...
2422[-6.70500053e-03 1.51133696e-02 4.94448021e-...{\"token_count\":124,\"truncated\":false}I have sent numerous letters, police reports a...
2658[ 6.70989677e-02 -3.53626162e-02 1.08648362e-...{\"token_count\":762,\"truncated\":false}This letter concerns two disputes ( chargeback...
2883[-1.28255319e-02 -1.89735275e-02 5.68657108e-...{\"token_count\":71,\"truncated\":false}It is very frustrating that this has been goin...
2951[ 3.23301251e-03 -2.61142217e-02 1.31891826e-...{\"token_count\":95,\"truncated\":false}I, the consumer, in fact, have a right to priv...
2992[-2.22910382e-03 -1.07050659e-02 4.74211425e-...{\"token_count\":407,\"truncated\":false}XXXX XXXX XXXX should not be reporting to Expe...
3969[ 1.58297736e-02 3.01055871e-02 5.60088176e-...{\"token_count\":287,\"truncated\":false}DEAR CFPB ; XXXX ; XXXX ; AND TRANSUNION ; SEE...
4087[ 1.99207035e-03 -7.62321474e-03 7.92114343e-...{\"token_count\":88,\"truncated\":false}This debt was from my identity being stolen I ...
4326[ 3.44273262e-02 -3.36350128e-02 1.91939529e-...{\"token_count\":52,\"truncated\":false}The items that are reflected on my credit repo...
4682[ 2.47727744e-02 -1.77769139e-02 4.63737026e-...{\"token_count\":284,\"truncated\":false}I filed for chapter XXXX bankruptcy on XXXX...
5005[ 2.51834448e-02 -4.92606424e-02 -1.37688573e-...{\"token_count\":17,\"truncated\":false}There are 2 Inquires on my credit report that ...
5144[ 3.26358266e-02 -3.67171178e-03 3.65621522e-...{\"token_count\":105,\"truncated\":false}My mortgage was sold from XXXX XXXX to freed...
6090[ 2.47520711e-02 1.09149124e-02 1.35175223e-...{\"token_count\":545,\"truncated\":false}On XX/XX/XXXX this company received certified...
6449[ 1.86854266e-02 1.31238240e-03 -4.96791191e-...{\"token_count\":104,\"truncated\":false}After hours on the phone with multiple agents,...
6486[ 1.56347770e-02 2.23377198e-02 -1.32683543e-...{\"token_count\":211,\"truncated\":false}On XX/XX/2019 two charges one for XXXX and one...
\n", - "

25 rows × 4 columns

\n", - "
[10000 rows x 4 columns in total]" - ], - "text/plain": [ - " ml_generate_embedding_result \\\n", - "415 [ 2.56774724e-02 -1.06168222e-02 3.06945704e-... \n", - "596 [ 5.90653270e-02 -9.31344274e-03 -7.12460047e-... \n", - "706 [ 0.01298233 0.00130001 0.01800315 0.037078... \n", - "804 [-1.39777679e-02 1.68943349e-02 5.53999236e-... \n", - "861 [ 2.33309343e-02 -2.36528926e-03 3.37129943e-... \n", - "1030 [ 0.06060313 -0.06495965 -0.03605044 -0.028016... \n", - "1582 [ 0.01255985 -0.01652482 -0.02638046 0.036858... \n", - "1600 [ 5.13355099e-02 4.01246967e-03 5.72342947e-... \n", - "2060 [ 6.44792162e-04 4.95899878e-02 4.67925966e-... \n", - "2283 [ 4.71848622e-02 -8.68239347e-03 5.80501892e-... \n", - "2421 [-2.90394691e-03 -1.81679502e-02 -7.99657404e-... \n", - "2422 [-6.70500053e-03 1.51133696e-02 4.94448021e-... \n", - "2658 [ 6.70989677e-02 -3.53626162e-02 1.08648362e-... \n", - "2883 [-1.28255319e-02 -1.89735275e-02 5.68657108e-... \n", - "2951 [ 3.23301251e-03 -2.61142217e-02 1.31891826e-... \n", - "2992 [-2.22910382e-03 -1.07050659e-02 4.74211425e-... \n", - "3969 [ 1.58297736e-02 3.01055871e-02 5.60088176e-... \n", - "4087 [ 1.99207035e-03 -7.62321474e-03 7.92114343e-... \n", - "4326 [ 3.44273262e-02 -3.36350128e-02 1.91939529e-... \n", - "4682 [ 2.47727744e-02 -1.77769139e-02 4.63737026e-... \n", - "5005 [ 2.51834448e-02 -4.92606424e-02 -1.37688573e-... \n", - "5144 [ 3.26358266e-02 -3.67171178e-03 3.65621522e-... \n", - "6090 [ 2.47520711e-02 1.09149124e-02 1.35175223e-... \n", - "6449 [ 1.86854266e-02 1.31238240e-03 -4.96791191e-... \n", - "6486 [ 1.56347770e-02 2.23377198e-02 -1.32683543e-... \n", - "\n", - " ml_generate_embedding_statistics ml_generate_embedding_status \\\n", - "415 {\"token_count\":171,\"truncated\":false} \n", - "596 {\"token_count\":668,\"truncated\":false} \n", - "706 {\"token_count\":252,\"truncated\":false} \n", - "804 {\"token_count\":412,\"truncated\":false} \n", - "861 {\"token_count\":160,\"truncated\":false} \n", - "1030 {\"token_count\":298,\"truncated\":false} \n", - "1582 {\"token_count\":814,\"truncated\":false} \n", - "1600 {\"token_count\":653,\"truncated\":false} \n", - "2060 {\"token_count\":136,\"truncated\":false} \n", - "2283 {\"token_count\":478,\"truncated\":false} \n", - "2421 {\"token_count\":389,\"truncated\":false} \n", - "2422 {\"token_count\":124,\"truncated\":false} \n", - "2658 {\"token_count\":762,\"truncated\":false} \n", - "2883 {\"token_count\":71,\"truncated\":false} \n", - "2951 {\"token_count\":95,\"truncated\":false} \n", - "2992 {\"token_count\":407,\"truncated\":false} \n", - "3969 {\"token_count\":287,\"truncated\":false} \n", - "4087 {\"token_count\":88,\"truncated\":false} \n", - "4326 {\"token_count\":52,\"truncated\":false} \n", - "4682 {\"token_count\":284,\"truncated\":false} \n", - "5005 {\"token_count\":17,\"truncated\":false} \n", - "5144 {\"token_count\":105,\"truncated\":false} \n", - "6090 {\"token_count\":545,\"truncated\":false} \n", - "6449 {\"token_count\":104,\"truncated\":false} \n", - "6486 {\"token_count\":211,\"truncated\":false} \n", - "\n", - " content \n", - "415 DEPT OF EDUCATION/XXXX is stating I was late ... \n", - "596 I alerted my credit card company XX/XX/2017 th... \n", - "706 Sallie mae is corrupt. \n", - "I have tried to talk t... \n", - "804 In accordance with the Fair Credit Reporting a... \n", - "861 Hello, My name is XXXX XXXX XXXX. I have a pro... \n", - "1030 Hello, I would like to complain about PayPal H... \n", - "1582 Transunion is listing personal information ( n... \n", - "1600 On XX/XX/XXXX, I called Citizen Bank at XXXX t... \n", - "2060 Theses names are the known liars that I have s... \n", - "2283 My house was hit by a tree XX/XX/2018. My insu... \n", - "2421 I became aware of a credit inquiry on my XXXX... \n", - "2422 I have sent numerous letters, police reports a... \n", - "2658 This letter concerns two disputes ( chargeback... \n", - "2883 It is very frustrating that this has been goin... \n", - "2951 I, the consumer, in fact, have a right to priv... \n", - "2992 XXXX XXXX XXXX should not be reporting to Expe... \n", - "3969 DEAR CFPB ; XXXX ; XXXX ; AND TRANSUNION ; SEE... \n", - "4087 This debt was from my identity being stolen I ... \n", - "4326 The items that are reflected on my credit repo... \n", - "4682 I filed for chapter XXXX bankruptcy on XXXX... \n", - "5005 There are 2 Inquires on my credit report that ... \n", - "5144 My mortgage was sold from XXXX XXXX to freed... \n", - "6090 On XX/XX/XXXX this company received certified... \n", - "6449 After hours on the phone with multiple agents,... \n", - "6486 On XX/XX/2019 two charges one for XXXX and one... \n", - "...\n", - "\n", - "[10000 rows x 4 columns]" - ] - }, - "execution_count": 10, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# Will take ~3 minutes to compute the embeddings\n", - "predicted_embeddings = model.predict(downsampled_issues_df)\n", - "# Notice the lists of numbers that are our text embeddings for each complaint\n", - "predicted_embeddings" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The model may have encountered errors while calculating embeddings for some rows. Filter out the errored rows before training the model. Alternatively, select these rows and retry the embeddings." - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "Query job 16915c47-ab13-4d06-94aa-9ebdb65d91fe is DONE. 72.0 MB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job 4ab4fbf0-6fd3-4936-9915-cfd7ccd106d1 is DONE. 0 Bytes processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job b11d3794-6bb8-4c47-a91b-dcc472cf4d69 is DONE. 72.4 MB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "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", - "
ml_generate_embedding_resultml_generate_embedding_statisticsml_generate_embedding_statuscontent
415[ 2.56774724e-02 -1.06168222e-02 3.06945704e-...{\"token_count\":171,\"truncated\":false}DEPT OF EDUCATION/XXXX is stating I was late ...
596[ 5.90653270e-02 -9.31344274e-03 -7.12460047e-...{\"token_count\":668,\"truncated\":false}I alerted my credit card company XX/XX/2017 th...
706[ 0.01298233 0.00130001 0.01800315 0.037078...{\"token_count\":252,\"truncated\":false}Sallie mae is corrupt. \n", - "I have tried to talk t...
804[-1.39777679e-02 1.68943349e-02 5.53999236e-...{\"token_count\":412,\"truncated\":false}In accordance with the Fair Credit Reporting a...
861[ 2.33309343e-02 -2.36528926e-03 3.37129943e-...{\"token_count\":160,\"truncated\":false}Hello, My name is XXXX XXXX XXXX. I have a pro...
1030[ 0.06060313 -0.06495965 -0.03605044 -0.028016...{\"token_count\":298,\"truncated\":false}Hello, I would like to complain about PayPal H...
1582[ 0.01255985 -0.01652482 -0.02638046 0.036858...{\"token_count\":814,\"truncated\":false}Transunion is listing personal information ( n...
1600[ 5.13355099e-02 4.01246967e-03 5.72342947e-...{\"token_count\":653,\"truncated\":false}On XX/XX/XXXX, I called Citizen Bank at XXXX t...
2060[ 6.44792162e-04 4.95899878e-02 4.67925966e-...{\"token_count\":136,\"truncated\":false}Theses names are the known liars that I have s...
2283[ 4.71848622e-02 -8.68239347e-03 5.80501892e-...{\"token_count\":478,\"truncated\":false}My house was hit by a tree XX/XX/2018. My insu...
2421[-2.90394691e-03 -1.81679502e-02 -7.99657404e-...{\"token_count\":389,\"truncated\":false}I became aware of a credit inquiry on my XXXX...
2422[-6.70500053e-03 1.51133696e-02 4.94448021e-...{\"token_count\":124,\"truncated\":false}I have sent numerous letters, police reports a...
2658[ 6.70989677e-02 -3.53626162e-02 1.08648362e-...{\"token_count\":762,\"truncated\":false}This letter concerns two disputes ( chargeback...
2883[-1.28255319e-02 -1.89735275e-02 5.68657108e-...{\"token_count\":71,\"truncated\":false}It is very frustrating that this has been goin...
2951[ 3.23301251e-03 -2.61142217e-02 1.31891826e-...{\"token_count\":95,\"truncated\":false}I, the consumer, in fact, have a right to priv...
2992[-2.22910382e-03 -1.07050659e-02 4.74211425e-...{\"token_count\":407,\"truncated\":false}XXXX XXXX XXXX should not be reporting to Expe...
3969[ 1.58297736e-02 3.01055871e-02 5.60088176e-...{\"token_count\":287,\"truncated\":false}DEAR CFPB ; XXXX ; XXXX ; AND TRANSUNION ; SEE...
4087[ 1.99207035e-03 -7.62321474e-03 7.92114343e-...{\"token_count\":88,\"truncated\":false}This debt was from my identity being stolen I ...
4326[ 3.44273262e-02 -3.36350128e-02 1.91939529e-...{\"token_count\":52,\"truncated\":false}The items that are reflected on my credit repo...
4682[ 2.47727744e-02 -1.77769139e-02 4.63737026e-...{\"token_count\":284,\"truncated\":false}I filed for chapter XXXX bankruptcy on XXXX...
5005[ 2.51834448e-02 -4.92606424e-02 -1.37688573e-...{\"token_count\":17,\"truncated\":false}There are 2 Inquires on my credit report that ...
5144[ 3.26358266e-02 -3.67171178e-03 3.65621522e-...{\"token_count\":105,\"truncated\":false}My mortgage was sold from XXXX XXXX to freed...
6090[ 2.47520711e-02 1.09149124e-02 1.35175223e-...{\"token_count\":545,\"truncated\":false}On XX/XX/XXXX this company received certified...
6449[ 1.86854266e-02 1.31238240e-03 -4.96791191e-...{\"token_count\":104,\"truncated\":false}After hours on the phone with multiple agents,...
6486[ 1.56347770e-02 2.23377198e-02 -1.32683543e-...{\"token_count\":211,\"truncated\":false}On XX/XX/2019 two charges one for XXXX and one...
\n", - "

25 rows × 4 columns

\n", - "
[10000 rows x 4 columns in total]" - ], - "text/plain": [ - " ml_generate_embedding_result \\\n", - "415 [ 2.56774724e-02 -1.06168222e-02 3.06945704e-... \n", - "596 [ 5.90653270e-02 -9.31344274e-03 -7.12460047e-... \n", - "706 [ 0.01298233 0.00130001 0.01800315 0.037078... \n", - "804 [-1.39777679e-02 1.68943349e-02 5.53999236e-... \n", - "861 [ 2.33309343e-02 -2.36528926e-03 3.37129943e-... \n", - "1030 [ 0.06060313 -0.06495965 -0.03605044 -0.028016... \n", - "1582 [ 0.01255985 -0.01652482 -0.02638046 0.036858... \n", - "1600 [ 5.13355099e-02 4.01246967e-03 5.72342947e-... \n", - "2060 [ 6.44792162e-04 4.95899878e-02 4.67925966e-... \n", - "2283 [ 4.71848622e-02 -8.68239347e-03 5.80501892e-... \n", - "2421 [-2.90394691e-03 -1.81679502e-02 -7.99657404e-... \n", - "2422 [-6.70500053e-03 1.51133696e-02 4.94448021e-... \n", - "2658 [ 6.70989677e-02 -3.53626162e-02 1.08648362e-... \n", - "2883 [-1.28255319e-02 -1.89735275e-02 5.68657108e-... \n", - "2951 [ 3.23301251e-03 -2.61142217e-02 1.31891826e-... \n", - "2992 [-2.22910382e-03 -1.07050659e-02 4.74211425e-... \n", - "3969 [ 1.58297736e-02 3.01055871e-02 5.60088176e-... \n", - "4087 [ 1.99207035e-03 -7.62321474e-03 7.92114343e-... \n", - "4326 [ 3.44273262e-02 -3.36350128e-02 1.91939529e-... \n", - "4682 [ 2.47727744e-02 -1.77769139e-02 4.63737026e-... \n", - "5005 [ 2.51834448e-02 -4.92606424e-02 -1.37688573e-... \n", - "5144 [ 3.26358266e-02 -3.67171178e-03 3.65621522e-... \n", - "6090 [ 2.47520711e-02 1.09149124e-02 1.35175223e-... \n", - "6449 [ 1.86854266e-02 1.31238240e-03 -4.96791191e-... \n", - "6486 [ 1.56347770e-02 2.23377198e-02 -1.32683543e-... \n", - "\n", - " ml_generate_embedding_statistics ml_generate_embedding_status \\\n", - "415 {\"token_count\":171,\"truncated\":false} \n", - "596 {\"token_count\":668,\"truncated\":false} \n", - "706 {\"token_count\":252,\"truncated\":false} \n", - "804 {\"token_count\":412,\"truncated\":false} \n", - "861 {\"token_count\":160,\"truncated\":false} \n", - "1030 {\"token_count\":298,\"truncated\":false} \n", - "1582 {\"token_count\":814,\"truncated\":false} \n", - "1600 {\"token_count\":653,\"truncated\":false} \n", - "2060 {\"token_count\":136,\"truncated\":false} \n", - "2283 {\"token_count\":478,\"truncated\":false} \n", - "2421 {\"token_count\":389,\"truncated\":false} \n", - "2422 {\"token_count\":124,\"truncated\":false} \n", - "2658 {\"token_count\":762,\"truncated\":false} \n", - "2883 {\"token_count\":71,\"truncated\":false} \n", - "2951 {\"token_count\":95,\"truncated\":false} \n", - "2992 {\"token_count\":407,\"truncated\":false} \n", - "3969 {\"token_count\":287,\"truncated\":false} \n", - "4087 {\"token_count\":88,\"truncated\":false} \n", - "4326 {\"token_count\":52,\"truncated\":false} \n", - "4682 {\"token_count\":284,\"truncated\":false} \n", - "5005 {\"token_count\":17,\"truncated\":false} \n", - "5144 {\"token_count\":105,\"truncated\":false} \n", - "6090 {\"token_count\":545,\"truncated\":false} \n", - "6449 {\"token_count\":104,\"truncated\":false} \n", - "6486 {\"token_count\":211,\"truncated\":false} \n", - "\n", - " content \n", - "415 DEPT OF EDUCATION/XXXX is stating I was late ... \n", - "596 I alerted my credit card company XX/XX/2017 th... \n", - "706 Sallie mae is corrupt. \n", - "I have tried to talk t... \n", - "804 In accordance with the Fair Credit Reporting a... \n", - "861 Hello, My name is XXXX XXXX XXXX. I have a pro... \n", - "1030 Hello, I would like to complain about PayPal H... \n", - "1582 Transunion is listing personal information ( n... \n", - "1600 On XX/XX/XXXX, I called Citizen Bank at XXXX t... \n", - "2060 Theses names are the known liars that I have s... \n", - "2283 My house was hit by a tree XX/XX/2018. My insu... \n", - "2421 I became aware of a credit inquiry on my XXXX... \n", - "2422 I have sent numerous letters, police reports a... \n", - "2658 This letter concerns two disputes ( chargeback... \n", - "2883 It is very frustrating that this has been goin... \n", - "2951 I, the consumer, in fact, have a right to priv... \n", - "2992 XXXX XXXX XXXX should not be reporting to Expe... \n", - "3969 DEAR CFPB ; XXXX ; XXXX ; AND TRANSUNION ; SEE... \n", - "4087 This debt was from my identity being stolen I ... \n", - "4326 The items that are reflected on my credit repo... \n", - "4682 I filed for chapter XXXX bankruptcy on XXXX... \n", - "5005 There are 2 Inquires on my credit report that ... \n", - "5144 My mortgage was sold from XXXX XXXX to freed... \n", - "6090 On XX/XX/XXXX this company received certified... \n", - "6449 After hours on the phone with multiple agents,... \n", - "6486 On XX/XX/2019 two charges one for XXXX and one... \n", - "...\n", - "\n", - "[10000 rows x 4 columns]" - ] - }, - "execution_count": 12, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "successful_rows = (\n", - " (predicted_embeddings[\"ml_generate_embedding_status\"] == \"\")\n", - " # Series.str.len() gives the length of an array.\n", - " # See: https://stackoverflow.com/a/41340543/101923\n", - " & (predicted_embeddings[\"ml_generate_embedding_result\"].str.len() != 0)\n", - ")\n", - "predicted_embeddings = predicted_embeddings[successful_rows]\n", - "predicted_embeddings\n" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We now have the complaints and their text embeddings as two columns in our predicted_embeddings DataFrame." - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": { - "id": "OUZ3NNbzo1Tb" - }, - "source": [ - "## Step 2: Create k-means model and predict clusters" - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "metadata": { - "id": "AhNTnEC5FRz2" - }, - "outputs": [], - "source": [ - "from bigframes.ml.cluster import KMeans\n", - "\n", - "cluster_model = KMeans(n_clusters=10) # We will divide our complaints into 10 groups" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Perform KMeans clustering" - ] - }, - { - "cell_type": "code", - "execution_count": 15, - "metadata": { - "id": "6poSxh-fGJF7" - }, - "outputs": [ - { - "data": { - "text/html": [ - "Query job 3e01544b-9bc2-4298-8f7d-1e9f186ac72f is DONE. 61.6 MB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job 8aca135c-65c3-4804-9c25-0d47fad0beb5 is DONE. 0 Bytes processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job 0b15374d-d34b-4f2e-8a48-b77d7e7757ab is DONE. 72.7 MB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job fed90511-76f8-4aec-a988-e1a4dab711b0 is DONE. 73.2 MB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "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", - "
CENTROID_IDNEAREST_CENTROIDS_DISTANCEml_generate_embedding_resultml_generate_embedding_statisticsml_generate_embedding_statuscontent
31721211[{'CENTROID_ID': 1, 'DISTANCE': 0.756634267893...[ 3.18095312e-02 -3.54472063e-02 -7.13569671e-...{\"token_count\":10,\"truncated\":false}Company did not provide verification and detai...
21374201[{'CENTROID_ID': 1, 'DISTANCE': 0.606628249825...[ 1.91578846e-02 5.55988774e-02 8.88887007e-...{\"token_count\":100,\"truncated\":false}I have already filed a dispute with Consumer A...
23507751[{'CENTROID_ID': 1, 'DISTANCE': 0.606676295233...[ 2.25369893e-02 2.29400061e-02 -6.42273854e-...{\"token_count\":100,\"truncated\":false}I informed Central Financial Control & provide...
29041461[{'CENTROID_ID': 1, 'DISTANCE': 0.596729348974...[ 9.35115516e-02 4.27814946e-03 4.62085977e-...{\"token_count\":100,\"truncated\":false}I received a letter from a collections agency ...
10755711[{'CENTROID_ID': 1, 'DISTANCE': 0.453806107968...[-1.93953840e-03 -5.80236455e-03 8.49655271e-...{\"token_count\":100,\"truncated\":false}I have not done business with this company, i ...
\n", - "
" - ], - "text/plain": [ - " CENTROID_ID NEAREST_CENTROIDS_DISTANCE \\\n", - "3172121 1 [{'CENTROID_ID': 1, 'DISTANCE': 0.756634267893... \n", - "2137420 1 [{'CENTROID_ID': 1, 'DISTANCE': 0.606628249825... \n", - "2350775 1 [{'CENTROID_ID': 1, 'DISTANCE': 0.606676295233... \n", - "2904146 1 [{'CENTROID_ID': 1, 'DISTANCE': 0.596729348974... \n", - "1075571 1 [{'CENTROID_ID': 1, 'DISTANCE': 0.453806107968... \n", - "\n", - " ml_generate_embedding_result \\\n", - "3172121 [ 3.18095312e-02 -3.54472063e-02 -7.13569671e-... \n", - "2137420 [ 1.91578846e-02 5.55988774e-02 8.88887007e-... \n", - "2350775 [ 2.25369893e-02 2.29400061e-02 -6.42273854e-... \n", - "2904146 [ 9.35115516e-02 4.27814946e-03 4.62085977e-... \n", - "1075571 [-1.93953840e-03 -5.80236455e-03 8.49655271e-... \n", - "\n", - " ml_generate_embedding_statistics ml_generate_embedding_status \\\n", - "3172121 {\"token_count\":10,\"truncated\":false} \n", - "2137420 {\"token_count\":100,\"truncated\":false} \n", - "2350775 {\"token_count\":100,\"truncated\":false} \n", - "2904146 {\"token_count\":100,\"truncated\":false} \n", - "1075571 {\"token_count\":100,\"truncated\":false} \n", - "\n", - " content \n", - "3172121 Company did not provide verification and detai... \n", - "2137420 I have already filed a dispute with Consumer A... \n", - "2350775 I informed Central Financial Control & provide... \n", - "2904146 I received a letter from a collections agency ... \n", - "1075571 I have not done business with this company, i ... " - ] - }, - "execution_count": 15, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# Use KMeans clustering to calculate our groups. Will take ~3 minutes.\n", - "cluster_model.fit(predicted_embeddings[[\"ml_generate_embedding_result\"]])\n", - "clustered_result = cluster_model.predict(predicted_embeddings)\n", - "# Notice the CENTROID_ID column, which is the ID number of the group that\n", - "# each complaint belongs to.\n", - "clustered_result.peek(n=5)" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Our DataFrame clustered_result now has an additional column that includes an ID from 1-10 (inclusive) indicating which semantically similar group they belong to." - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": { - "id": "21rNsFMHo8hO" - }, - "source": [ - "## Step 3: Use Gemini to summarize complaint clusters" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Build prompts - we will choose just two of our categories and prompt GeminiTextGenerator to identify their salient characteristics. The prompt is natural language in a python string." - ] - }, - { - "cell_type": "code", - "execution_count": 16, - "metadata": { - "id": "2E7wXM_jGqo6" - }, - "outputs": [ - { - "data": { - "text/html": [ - "Query job d6c61334-255f-43fe-9a8f-9fbf6cdcb2be is DONE. 10.5 MB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job 03a12383-6752-45ca-9b01-36eecc74fb8a is DONE. 10.5 MB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "# Using bigframes, with syntax identical to pandas,\n", - "# filter out the first and second groups\n", - "cluster_1_result = clustered_result[\n", - " clustered_result[\"CENTROID_ID\"] == 1\n", - "][[\"content\"]]\n", - "cluster_1_result_pandas = cluster_1_result.head(5).to_pandas()\n", - "\n", - "cluster_2_result = clustered_result[\n", - " clustered_result[\"CENTROID_ID\"] == 2\n", - "][[\"content\"]]\n", - "cluster_2_result_pandas = cluster_2_result.head(5).to_pandas()" - ] - }, - { - "cell_type": "code", - "execution_count": 17, - "metadata": { - "id": "ZNDiueI9IP5e" - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "comment list 1:\n", - "1. This debt was from my identity being stolen I didnt open any account that resulted in this collection i have completed a police report which can be verified with the XXXX police @ XXXX report # XXXX and i have a notarized identity theft affidavit from ftc please remove this off of my credit and close my file ASAP\n", - "2. On XX/XX/XXXX this company received certified mail asking for validation of debt. On XX/XX/XXXX the company still did not validate debt owed and they did not mark the debt disputed by XX/XX/XXXX through the major credit reporting bureaus. This is a violation of the FDCPA and FCRA. I did send a second letter which the company received on XX/XX/XXXX . A lady from the company called and talked to me about the debt on XX/XX/XXXX but again did not have the credit bureaus mark the item as disputed. The company still violated the laws. Section [ 15 U.S.C. 1681s-2 ] ( 3 ) duty to provide notice of dispute. If the completeness or accuracy of any information furnished by any person to any consumer reporting agency is disputed to such person by a consumer, the person may not furnish the information to any consumer reporting agency without notice that such information is disputed. ( B ) ti me of notice! The notice required under sub paragraph ( A ) shall be provided to the customer prior to, or no later than 30 days after, furnishing the negative information to a consumer reporting agency described in section 603 ( p ). This company violated the state laws. I received no information until XX/XX/XXXX . Therefore by law the company should have the item removed from the credit agencies such as transunion and XXXX . I tried to call the company back about the laws that was broken and left my name no return call. The copy of my credit reports are below and as you can see the items was n't marked disputed. XXXX is marked disputed because on XX/XX/XXXX I myself disputed the information with the credit bureau. The lady stated they did n't receive my dispute letter until XX/XX/XXXX . Included is certified mail reciepts with date, time stamp, and signature of the person who signed for the certified mail on XX/XX/XXXX and XX/XX/XXXX . So again the company violated the laws and I have all the proof. If I have a contract with this company please send to me by mail a contract bearing my signature of the contract.\n", - "3. On XX/XX/2022, Pioneer Credit Recovery of XXXX, NY identified an alleged debt, which I do not owe. \n", - "\n", - "On XX/XX/2022, I wrote a dispute letter to Pioneer, requesting that they stop communication with me, record my dispute, and provide verification of the debt if they believe otherwise. \n", - "\n", - "Pioneer has not responded with verification, but has attempted to collect the debt since then by phone ( XX/XX/2022 ) and mail ( XX/XX/2022 ).\n", - "4. Disputed with the company on several occasions and they still havent provided proof in a timely manner. The FCRA gives the company 30 days to respond. I have not gotten a response.\n", - "5. I am not aware of this XXXX XXXX XXXX XXXX XXXX , XXXX balance. I have never seen anything dealing with this lender. Also, I have been threated that in 30 days they will seek to make a judgement on debt that does not belong to me. I understand that they are looking to offer me a settlement. However, I do not believe the validity of such debt accusation. Furthermore, I will not be limited to the action of court threats when I did not receive any notice of debt based on communication. The amount is {$880.00} from MBNA which was acquired by Bank of America in 2006. I do not claim debt.\n", - "\n", - "comment list 2:\n", - "1. My name is XXXX XXXX XXXX. This issue with a Loan Till Payday account was previously reported to you for collection practices, etc. I had a pay day loan in 2013. At the time, I banked with XXXX XXXX, who advised me that pay day loans are not good, and in the end XXXX closed my bank account, it was involuntary. In the interim, I made payments to the agency. XXXX and XXXX were the primary contacts. On the last payment, due to the fact that I told him I was coming in to pay cash, and they withdrew the funds, electronically, my account was affected. XXXX advised me that the payment made was the last payment and the other ( which was primarily interest remaining ) would be charged off. XXXX later called me and advised that XXXX was not authorized to make that decision and demanded the payment. I do n't understand how one person can cancel the arrangements made by someone else. \n", - "\n", - "In the end, they sold my account. It was reported to you, and that creditor then stated no further collection activity would occur. \n", - "\n", - "Last week I began receiving calls from a collection agency, XXXX XXXX stating I would called for a civil deposition on this account. I do n't even know this agency. Later, I then received another call stating that I needed to hold, and after several clicks was connected to someone at a Mediaction service. I denied the owing the loan and stated it was paid. \n", - "\n", - "Today, I received a call from an outsource service courier about a missed appointment or hearing??? What?? I have no idea who these people are. I called Loan Till Payday and was advised the loan was sold and I needed to settle with the new company. So, does this mean they are continuing to attempt to collect {$200.00}. \n", - "\n", - "I attempted to call the numbers, and now no one picks up just a voicemail. I called the supposed service courier and advised that their number was showing up as a spam/fraud number and that if they were a legitimate company then they should leave their name, location, a number ( not a voicemail ), and the case they are calling me about. I have not been served with any collection documents - why am I being threatened with a deposition??? \n", - "\n", - "Telephone number recently calling me : ( XXXX ) XXXX. \n", - "\n", - "Please help.\n", - "2. I receive 2 or 3 phone calls every day since early XXXX, my references receive calls. I will gladly satisfy this debt however even after 1st telling them the calls haven't stopped as though they are going to intimidate me. If the calls stopped for just 3 or 4 days I would satisfy my obligation but not because they keep calling me as well as my references.\n", - "3. Last month I received a phone call for my husband from XXXX XXXX XXXX saying he owed money and if I did not pay today it would be sent to litigation. The debt was Wachovia/wells Fargo, and account that we have never had. I had my husband call to get more information and they became very nasty with him. I called back asking for documentation on the debt because i did not think it was our debt and they became aggressive. They did email my husband something saying how much he owed, and I called back and asked to be emailed a copy, and the dollar amounts did not match. I called Wells Fargo and went over the above and verified that we have never had an account with them and I sent them the emails the XXXX sent to us and they started a fraud investigation. Yesterday I received another collections letter in the mail from the. Still trying to collect this debt. These people have my husbands full social security number ( we did not give it to them )\n", - "4. A company call XXXX XXXX XXXX came onto my private property on XX/XX/2018 and stole my automobile. I did receive any type of notice saying they collecting on a debt. If they take or threaten to take any nonjudicial action ( i.e, without a court order ) to repossess property when there is no present right to possession of the property they is in violation. l did not receive any type of notice asking if they can enter onto my private property and steal my private automobile.\n", - "5. Navient financial continues to send me erroneous debt collection emails. I have repeatedly asked them to remove my email address and to cease all communication with me. \n", - "I have no relationship with Navient and their continued threatening email is very unsettling. \n", - "\n", - "I just want their erroneous threats to stop. \n", - "\n", - "Below is the latest email I have received from them : Last Day to call this office XXXX by XXXX Regards, XXXX XXXX Team Lead Specialist Charge off Unit XXXX XXXX\n", - "\n" - ] - } - ], - "source": [ - "# Build plain-text prompts to send to Gemini. Use only 5 complaints from each group.\n", - "prompt1 = 'comment list 1:\\n'\n", - "for i in range(5):\n", - " prompt1 += str(i + 1) + '. ' + \\\n", - " cluster_1_result_pandas[\"content\"].iloc[i] + '\\n'\n", - "\n", - "prompt2 = 'comment list 2:\\n'\n", - "for i in range(5):\n", - " prompt2 += str(i + 1) + '. ' + \\\n", - " cluster_2_result_pandas[\"content\"].iloc[i] + '\\n'\n", - "\n", - "print(prompt1)\n", - "print(prompt2)" - ] - }, - { - "cell_type": "code", - "execution_count": 18, - "metadata": { - "id": "BfHGJLirzSvH" - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Please highlight the most obvious difference between the two lists of comments:\n", - "comment list 1:\n", - "1. This debt was from my identity being stolen I didnt open any account that resulted in this collection i have completed a police report which can be verified with the XXXX police @ XXXX report # XXXX and i have a notarized identity theft affidavit from ftc please remove this off of my credit and close my file ASAP\n", - "2. On XX/XX/XXXX this company received certified mail asking for validation of debt. On XX/XX/XXXX the company still did not validate debt owed and they did not mark the debt disputed by XX/XX/XXXX through the major credit reporting bureaus. This is a violation of the FDCPA and FCRA. I did send a second letter which the company received on XX/XX/XXXX . A lady from the company called and talked to me about the debt on XX/XX/XXXX but again did not have the credit bureaus mark the item as disputed. The company still violated the laws. Section [ 15 U.S.C. 1681s-2 ] ( 3 ) duty to provide notice of dispute. If the completeness or accuracy of any information furnished by any person to any consumer reporting agency is disputed to such person by a consumer, the person may not furnish the information to any consumer reporting agency without notice that such information is disputed. ( B ) ti me of notice! The notice required under sub paragraph ( A ) shall be provided to the customer prior to, or no later than 30 days after, furnishing the negative information to a consumer reporting agency described in section 603 ( p ). This company violated the state laws. I received no information until XX/XX/XXXX . Therefore by law the company should have the item removed from the credit agencies such as transunion and XXXX . I tried to call the company back about the laws that was broken and left my name no return call. The copy of my credit reports are below and as you can see the items was n't marked disputed. XXXX is marked disputed because on XX/XX/XXXX I myself disputed the information with the credit bureau. The lady stated they did n't receive my dispute letter until XX/XX/XXXX . Included is certified mail reciepts with date, time stamp, and signature of the person who signed for the certified mail on XX/XX/XXXX and XX/XX/XXXX . So again the company violated the laws and I have all the proof. If I have a contract with this company please send to me by mail a contract bearing my signature of the contract.\n", - "3. On XX/XX/2022, Pioneer Credit Recovery of XXXX, NY identified an alleged debt, which I do not owe. \n", - "\n", - "On XX/XX/2022, I wrote a dispute letter to Pioneer, requesting that they stop communication with me, record my dispute, and provide verification of the debt if they believe otherwise. \n", - "\n", - "Pioneer has not responded with verification, but has attempted to collect the debt since then by phone ( XX/XX/2022 ) and mail ( XX/XX/2022 ).\n", - "4. Disputed with the company on several occasions and they still havent provided proof in a timely manner. The FCRA gives the company 30 days to respond. I have not gotten a response.\n", - "5. I am not aware of this XXXX XXXX XXXX XXXX XXXX , XXXX balance. I have never seen anything dealing with this lender. Also, I have been threated that in 30 days they will seek to make a judgement on debt that does not belong to me. I understand that they are looking to offer me a settlement. However, I do not believe the validity of such debt accusation. Furthermore, I will not be limited to the action of court threats when I did not receive any notice of debt based on communication. The amount is {$880.00} from MBNA which was acquired by Bank of America in 2006. I do not claim debt.\n", - "comment list 2:\n", - "1. My name is XXXX XXXX XXXX. This issue with a Loan Till Payday account was previously reported to you for collection practices, etc. I had a pay day loan in 2013. At the time, I banked with XXXX XXXX, who advised me that pay day loans are not good, and in the end XXXX closed my bank account, it was involuntary. In the interim, I made payments to the agency. XXXX and XXXX were the primary contacts. On the last payment, due to the fact that I told him I was coming in to pay cash, and they withdrew the funds, electronically, my account was affected. XXXX advised me that the payment made was the last payment and the other ( which was primarily interest remaining ) would be charged off. XXXX later called me and advised that XXXX was not authorized to make that decision and demanded the payment. I do n't understand how one person can cancel the arrangements made by someone else. \n", - "\n", - "In the end, they sold my account. It was reported to you, and that creditor then stated no further collection activity would occur. \n", - "\n", - "Last week I began receiving calls from a collection agency, XXXX XXXX stating I would called for a civil deposition on this account. I do n't even know this agency. Later, I then received another call stating that I needed to hold, and after several clicks was connected to someone at a Mediaction service. I denied the owing the loan and stated it was paid. \n", - "\n", - "Today, I received a call from an outsource service courier about a missed appointment or hearing??? What?? I have no idea who these people are. I called Loan Till Payday and was advised the loan was sold and I needed to settle with the new company. So, does this mean they are continuing to attempt to collect {$200.00}. \n", - "\n", - "I attempted to call the numbers, and now no one picks up just a voicemail. I called the supposed service courier and advised that their number was showing up as a spam/fraud number and that if they were a legitimate company then they should leave their name, location, a number ( not a voicemail ), and the case they are calling me about. I have not been served with any collection documents - why am I being threatened with a deposition??? \n", - "\n", - "Telephone number recently calling me : ( XXXX ) XXXX. \n", - "\n", - "Please help.\n", - "2. I receive 2 or 3 phone calls every day since early XXXX, my references receive calls. I will gladly satisfy this debt however even after 1st telling them the calls haven't stopped as though they are going to intimidate me. If the calls stopped for just 3 or 4 days I would satisfy my obligation but not because they keep calling me as well as my references.\n", - "3. Last month I received a phone call for my husband from XXXX XXXX XXXX saying he owed money and if I did not pay today it would be sent to litigation. The debt was Wachovia/wells Fargo, and account that we have never had. I had my husband call to get more information and they became very nasty with him. I called back asking for documentation on the debt because i did not think it was our debt and they became aggressive. They did email my husband something saying how much he owed, and I called back and asked to be emailed a copy, and the dollar amounts did not match. I called Wells Fargo and went over the above and verified that we have never had an account with them and I sent them the emails the XXXX sent to us and they started a fraud investigation. Yesterday I received another collections letter in the mail from the. Still trying to collect this debt. These people have my husbands full social security number ( we did not give it to them )\n", - "4. A company call XXXX XXXX XXXX came onto my private property on XX/XX/2018 and stole my automobile. I did receive any type of notice saying they collecting on a debt. If they take or threaten to take any nonjudicial action ( i.e, without a court order ) to repossess property when there is no present right to possession of the property they is in violation. l did not receive any type of notice asking if they can enter onto my private property and steal my private automobile.\n", - "5. Navient financial continues to send me erroneous debt collection emails. I have repeatedly asked them to remove my email address and to cease all communication with me. \n", - "I have no relationship with Navient and their continued threatening email is very unsettling. \n", - "\n", - "I just want their erroneous threats to stop. \n", - "\n", - "Below is the latest email I have received from them : Last Day to call this office XXXX by XXXX Regards, XXXX XXXX Team Lead Specialist Charge off Unit XXXX XXXX\n", - "\n" - ] - } - ], - "source": [ - "# The plain English request we will make of Gemini\n", - "prompt = (\n", - " \"Please highlight the most obvious difference between \"\n", - " \"the two lists of comments:\\n\" + prompt1 + prompt2\n", - ")\n", - "print(prompt)" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Get a response from Gemini by making a call to Vertex AI using our connection." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "mL5P0_3X04dE" - }, - "outputs": [ - { - "data": { - "text/html": [ - "Query job 3a46cad4-14e5-4137-a042-14380733b467 is DONE. 0 Bytes processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "from bigframes.ml.llm import GeminiTextGenerator\n", - "\n", - "q_a_model = GeminiTextGenerator(model_name=\"gemini-2.5-flash\")" - ] - }, - { - "cell_type": "code", - "execution_count": 20, - "metadata": { - "id": "ICWHsqAW1FNk" - }, - "outputs": [ - { - "data": { - "text/html": [ - "Load job 939037f0-66df-42a4-b301-0b3ba26bae7c is DONE. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "# Make a DataFrame containing only a single row with our prompt for Gemini\n", - "df = bf.DataFrame({\"prompt\": [prompt]})" - ] - }, - { - "cell_type": "code", - "execution_count": 21, - "metadata": { - "id": "gB7e1LXU1pst" - }, - "outputs": [ - { - "data": { - "text/html": [ - "Query job c662b2c7-7185-4681-b7c6-60c81e9c8cd4 is DONE. 8.2 kB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/usr/local/google/home/garrettwu/src/bigframes/bigframes/core/__init__.py:108: PreviewWarning: Interpreting JSON column(s) as StringDtype. This behavior may change in future versions.\n", - " warnings.warn(\n" - ] - }, - { - "data": { - "text/html": [ - "Query job 9a4d6735-c307-4a60-96f9-d81330925e6c is DONE. 2 Bytes processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job 17bde6e6-8b26-48a7-9c57-b7b9752c1f54 is DONE. 1.8 kB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/plain": [ - "\"## Key Differences between Comment Lists 1 and 2:\\n\\n**Comment List 1:**\\n\\n* **Focuses on Legal Violations:** The comments in List 1 primarily focus on how the debt collectors violated specific laws, such as the FDCPA and FCRA, by not validating debt, not marking accounts as disputed, and using illegal collection tactics.\\n* **Detailed Evidence:** Commenters provide detailed evidence of their claims, including dates, reference numbers, police reports, and copies of communications.\\n* **Formal Tone:** The language in List 1 is more formal and uses legal terminology, suggesting the commenters may have a deeper understanding of their rights.\\n* **Emphasis on Debt Accuracy:** Many comments explicitly deny owing the debt and question its validity, requesting proof and demanding removal from credit reports. \\n\\n**Comment List 2:**\\n\\n* **Focus on Harassment and Intimidation:** The comments in List 2 highlight the harassing and intimidating behavior of the debt collectors, such as making multiple calls, contacting references, and threatening legal action.\\n* **Emotional Language:** Commenters express frustration, fear, and anger towards the debt collectors' behavior.\\n* **Less Legal Detail:** While some commenters mention specific laws, they provide less detailed evidence than List 1.\\n* **Uncertainty About Debt:** Several commenters are unsure whether they actually owe the debt, questioning its origin and validity. \\n\\n**Overall:**\\n\\n* List 1 focuses on legal arguments and violations, while List 2 emphasizes emotional distress and improper collection tactics.\\n* List 1 provides more concrete evidence of wrongdoing, while List 2 relies more on personal experiences and descriptions.\\n* Both lists highlight the negative impacts of debt collection practices on individuals.\\n\"" - ] - }, - "execution_count": 21, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# Send the request for Gemini to generate a response to our prompt\n", - "major_difference = q_a_model.predict(df)\n", - "# Gemini's response is the only row in the dataframe result \n", - "major_difference[\"ml_generate_text_llm_result\"].iloc[0]" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We now see GeminiTextGenerator's characterization of the different comment groups. Thanks for using BigQuery DataFrames!" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Summary and next steps\n", - "\n", - "You've used the ML and LLM capabilities of BigQuery DataFrames to help analyze and understand a large dataset of unstructured feedback.\n", - "\n", - "Learn more about BigQuery DataFrames in the [documentation](https://cloud.google.com/python/docs/reference/bigframes/latest) and find more sample notebooks in the [GitHub repo](https://github.com/googleapis/python-bigquery-dataframes/tree/main/notebooks)." - ] - } - ], - "metadata": { - "colab": { - "provenance": [] - }, - "kernelspec": { - "display_name": "venv (3.10.14)", - "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": 4 -} diff --git a/notebooks/generative_ai/bq_dataframes_llm_output_schema.ipynb b/notebooks/generative_ai/bq_dataframes_llm_output_schema.ipynb deleted file mode 100644 index b3e2e4ebc84..00000000000 --- a/notebooks/generative_ai/bq_dataframes_llm_output_schema.ipynb +++ /dev/null @@ -1,905 +0,0 @@ -{ - "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": [ - "# Format LLM output using an output schema\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 BigQuery Studio\n", - " \n", - "
\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "This notebook shows you how to create structured LLM output by specifying an output schema when generating predictions with a Gemini model." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Costs\n", - "\n", - "This tutorial uses billable components of Google Cloud:\n", - "\n", - "* BigQuery (compute)\n", - "* BigQuery ML\n", - "* Generative AI support on Vertex AI\n", - "\n", - "Learn about [BigQuery compute pricing](https://cloud.google.com/bigquery/pricing#analysis_pricing_models), [Generative AI support on Vertex AI pricing](https://cloud.google.com/vertex-ai/generative-ai/pricing),\n", - "and [BigQuery ML pricing](https://cloud.google.com/bigquery/pricing#section-11),\n", - "and use the [Pricing Calculator](https://cloud.google.com/products/calculator/)\n", - "to generate a cost estimate based on your projected usage." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Before you begin\n", - "\n", - "Complete the tasks in this section to set up your environment." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Set up your Google Cloud project\n", - "\n", - "**The following steps are required, regardless of your notebook environment.**\n", - "\n", - "1. [Select or create a Google Cloud project](https://console.cloud.google.com/cloud-resource-manager). When you first create an account, you get a $300 credit towards your compute/storage costs.\n", - "\n", - "2. [Make sure that billing is enabled for your project](https://cloud.google.com/billing/docs/how-to/modify-project).\n", - "\n", - "3. [Click here](https://console.cloud.google.com/flows/enableapi?apiid=bigquery.googleapis.com,bigqueryconnection.googleapis.com,aiplatform.googleapis.com) to enable the following APIs:\n", - "\n", - " * BigQuery API\n", - " * BigQuery Connection API\n", - " * Vertex AI API\n", - "\n", - "4. If you are running this notebook locally, install the [Cloud SDK](https://cloud.google.com/sdk)." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### Authenticate your Google Cloud account\n", - "\n", - "Depending on your Jupyter environment, you might have to manually authenticate. Follow the relevant instructions below.\n", - "\n", - "**BigQuery Studio** or **Vertex AI Workbench**\n", - "\n", - "Do nothing, you are already authenticated.\n", - "\n", - "**Local JupyterLab instance**\n", - "\n", - "Uncomment and run the following cell:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# ! gcloud auth login" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**Colab**\n", - "\n", - "Uncomment and run the following cell:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# from google.colab import auth\n", - "# auth.authenticate_user()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Set up your project" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Set your project and import necessary modules. If you don't know your project ID, see [Locate the project ID](https://support.google.com/googleapi/answer/7014113)." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "PROJECT_ID = \"\" # @param {type:\"string\"}\n", - "import bigframes\n", - "bigframes.options.bigquery.project = PROJECT_ID\n", - "bigframes.options.display.progress_bar = None\n", - "\n", - "import bigframes.pandas as bpd\n", - "from bigframes.ml import llm" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Create a DataFrame and a Gemini model\n", - "Create a simple [DataFrame](https://cloud.google.com/python/docs/reference/bigframes/latest/bigframes.dataframe.DataFrame) of several cities:" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/usr/local/google/home/garrettwu/src/bigframes/bigframes/core/global_session.py:103: DefaultLocationWarning: No explicit location is set, so using location US for the session.\n", - " _global_session = bigframes.session.connect(\n" - ] - }, - { - "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", - "
city
0Seattle
1New York
2Shanghai
\n", - "

3 rows × 1 columns

\n", - "
[3 rows x 1 columns in total]" - ], - "text/plain": [ - " city\n", - "0 Seattle\n", - "1 New York\n", - "2 Shanghai\n", - "\n", - "[3 rows x 1 columns]" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "df = bpd.DataFrame({\"city\": [\"Seattle\", \"New York\", \"Shanghai\"]})\n", - "df" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Connect to a Gemini model using the [`GeminiTextGenerator` class](https://cloud.google.com/python/docs/reference/bigframes/latest/bigframes.ml.llm.GeminiTextGenerator):" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/usr/local/google/home/garrettwu/src/bigframes/bigframes/core/log_adapter.py:175: FutureWarning: Since upgrading the default model can cause unintended breakages, the\n", - "default model will be removed in BigFrames 3.0. Please supply an\n", - "explicit model to avoid this message.\n", - " return method(*args, **kwargs)\n" - ] - } - ], - "source": [ - "gemini = llm.GeminiTextGenerator()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Generate structured output data\n", - "Previously, LLMs could only generate text output. For example, you could generate output that identifies whether a given city is a US city:" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/usr/local/google/home/garrettwu/src/bigframes/bigframes/core/array_value.py:109: PreviewWarning: JSON column interpretation as a custom PyArrow extention in\n", - "`db_dtypes` is a preview feature and subject to change.\n", - " warnings.warn(msg, bfe.PreviewWarning)\n" - ] - }, - { - "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", - "
cityml_generate_text_llm_result
0SeattleYes, Seattle is a city in the United States. I...
1New YorkYes, New York City is a city in the United Sta...
2ShanghaiNo, Shanghai is not a US city. It is a major c...
\n", - "

3 rows × 2 columns

\n", - "
[3 rows x 2 columns in total]" - ], - "text/plain": [ - " city ml_generate_text_llm_result\n", - "0 Seattle Yes, Seattle is a city in the United States. I...\n", - "1 New York Yes, New York City is a city in the United Sta...\n", - "2 Shanghai No, Shanghai is not a US city. It is a major c...\n", - "\n", - "[3 rows x 2 columns]" - ] - }, - "execution_count": 5, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "result = gemini.predict(df, prompt=[df[\"city\"], \"is a US city?\"])\n", - "result[[\"city\", \"ml_generate_text_llm_result\"]]" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The output is text that a human can read. However, if you want the output to be more useful for analysis, it is better to format the output as structured data. This is especially true when you want to have Boolean, integer, or float values to work with instead of string values. Previously, formatting the output in this way wasn't easy.\n", - "\n", - "Now, you can get structured output out-of-the-box by specifying the `output_schema` parameter when calling the Gemini model's [`predict` method](https://cloud.google.com/python/docs/reference/bigframes/latest/bigframes.ml.llm.GeminiTextGenerator#bigframes_ml_llm_GeminiTextGenerator_predict). In the following example, the model output is formatted as Boolean values:" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/usr/local/google/home/garrettwu/src/bigframes/bigframes/core/array_value.py:109: PreviewWarning: JSON column interpretation as a custom PyArrow extention in\n", - "`db_dtypes` is a preview feature and subject to change.\n", - " warnings.warn(msg, bfe.PreviewWarning)\n" - ] - }, - { - "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", - "
cityis_us_city
0SeattleTrue
1New YorkTrue
2ShanghaiFalse
\n", - "

3 rows × 2 columns

\n", - "
[3 rows x 2 columns in total]" - ], - "text/plain": [ - " city is_us_city\n", - "0 Seattle True\n", - "1 New York True\n", - "2 Shanghai False\n", - "\n", - "[3 rows x 2 columns]" - ] - }, - "execution_count": 6, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "result = gemini.predict(df, prompt=[df[\"city\"], \"is a US city?\"], output_schema={\"is_us_city\": \"bool\"})\n", - "result[[\"city\", \"is_us_city\"]]" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "You can also format model output as float or integer values. In the following example, the model output is formatted as float values to show the city's population in millions:" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/usr/local/google/home/garrettwu/src/bigframes/bigframes/core/array_value.py:109: PreviewWarning: JSON column interpretation as a custom PyArrow extention in\n", - "`db_dtypes` is a preview feature and subject to change.\n", - " warnings.warn(msg, bfe.PreviewWarning)\n" - ] - }, - { - "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", - "
citypopulation_in_millions
0Seattle0.75
1New York19.68
2Shanghai26.32
\n", - "

3 rows × 2 columns

\n", - "
[3 rows x 2 columns in total]" - ], - "text/plain": [ - " city population_in_millions\n", - "0 Seattle 0.75\n", - "1 New York 19.68\n", - "2 Shanghai 26.32\n", - "\n", - "[3 rows x 2 columns]" - ] - }, - "execution_count": 7, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "result = gemini.predict(df, prompt=[\"what is the population in millions of\", df[\"city\"]], output_schema={\"population_in_millions\": \"float64\"})\n", - "result[[\"city\", \"population_in_millions\"]]" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "In the following example, the model output is formatted as integer values to show the count of the city's rainy days:" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/usr/local/google/home/garrettwu/src/bigframes/bigframes/core/array_value.py:109: PreviewWarning: JSON column interpretation as a custom PyArrow extention in\n", - "`db_dtypes` is a preview feature and subject to change.\n", - " warnings.warn(msg, bfe.PreviewWarning)\n" - ] - }, - { - "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", - "
cityrainy_days
0Seattle152
1New York123
2Shanghai123
\n", - "

3 rows × 2 columns

\n", - "
[3 rows x 2 columns in total]" - ], - "text/plain": [ - " city rainy_days\n", - "0 Seattle 152\n", - "1 New York 123\n", - "2 Shanghai 123\n", - "\n", - "[3 rows x 2 columns]" - ] - }, - "execution_count": 8, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "result = gemini.predict(df, prompt=[\"how many rainy days per year in\", df[\"city\"]], output_schema={\"rainy_days\": \"int64\"})\n", - "result[[\"city\", \"rainy_days\"]]" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Format output as multiple data types in one prediction\n", - "Within a single prediction, you can generate multiple columns of output that use different data types. \n", - "\n", - "The input doesn't have to be dedicated prompts as long as the output column names are informative to the model." - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/usr/local/google/home/garrettwu/src/bigframes/bigframes/core/array_value.py:109: PreviewWarning: JSON column interpretation as a custom PyArrow extention in\n", - "`db_dtypes` is a preview feature and subject to change.\n", - " warnings.warn(msg, bfe.PreviewWarning)\n" - ] - }, - { - "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", - "
cityis_US_citypopulation_in_millionsrainy_days_per_year
0SeattleTrue0.75152
1New YorkTrue8.8121
2ShanghaiFalse26.32115
\n", - "

3 rows × 4 columns

\n", - "
[3 rows x 4 columns in total]" - ], - "text/plain": [ - " city is_US_city population_in_millions rainy_days_per_year\n", - "0 Seattle True 0.75 152\n", - "1 New York True 8.8 121\n", - "2 Shanghai False 26.32 115\n", - "\n", - "[3 rows x 4 columns]" - ] - }, - "execution_count": 9, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "result = gemini.predict(df, prompt=[df[\"city\"]], output_schema={\"is_US_city\": \"bool\", \"population_in_millions\": \"float64\", \"rainy_days_per_year\": \"int64\"})\n", - "result[[\"city\", \"is_US_city\", \"population_in_millions\", \"rainy_days_per_year\"]]" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Format output as a composite data type" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "You can generate composite data types like arrays and structs. The following example generates a `places_to_visit` column as an array of strings and a `gps_coordinates` column as a struct of floats:" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/usr/local/google/home/garrettwu/src/bigframes/bigframes/core/array_value.py:109: PreviewWarning: JSON column interpretation as a custom PyArrow extention in\n", - "`db_dtypes` is a preview feature and subject to change.\n", - " warnings.warn(msg, bfe.PreviewWarning)\n" - ] - }, - { - "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", - "
cityis_US_citypopulation_in_millionsrainy_days_per_yearplaces_to_visitgps_coordinates
0SeattleTrue0.74150['Space Needle' 'Pike Place Market' 'Museum of...{'latitude': 47.6062, 'longitude': -122.3321}
1New YorkTrue8.4121['Times Square' 'Central Park' 'Statue of Libe...{'latitude': 40.7128, 'longitude': -74.006}
2ShanghaiFalse26.32115['The Bund' 'Yu Garden' 'Shanghai Museum' 'Ori...{'latitude': 31.2304, 'longitude': 121.4737}
\n", - "

3 rows × 6 columns

\n", - "
[3 rows x 6 columns in total]" - ], - "text/plain": [ - " city is_US_city population_in_millions rainy_days_per_year \\\n", - "0 Seattle True 0.74 150 \n", - "1 New York True 8.4 121 \n", - "2 Shanghai False 26.32 115 \n", - "\n", - " places_to_visit \\\n", - "0 ['Space Needle' 'Pike Place Market' 'Museum of... \n", - "1 ['Times Square' 'Central Park' 'Statue of Libe... \n", - "2 ['The Bund' 'Yu Garden' 'Shanghai Museum' 'Ori... \n", - "\n", - " gps_coordinates \n", - "0 {'latitude': 47.6062, 'longitude': -122.3321} \n", - "1 {'latitude': 40.7128, 'longitude': -74.006} \n", - "2 {'latitude': 31.2304, 'longitude': 121.4737} \n", - "\n", - "[3 rows x 6 columns]" - ] - }, - "execution_count": 10, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "result = gemini.predict(df, prompt=[df[\"city\"]], output_schema={\"is_US_city\": \"bool\", \"population_in_millions\": \"float64\", \"rainy_days_per_year\": \"int64\", \"places_to_visit\": \"array\", \"gps_coordinates\": \"struct\"})\n", - "result[[\"city\", \"is_US_city\", \"population_in_millions\", \"rainy_days_per_year\", \"places_to_visit\", \"gps_coordinates\"]]" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Clean up\n", - "\n", - "To clean up all Google Cloud resources used in this project, you can [delete the Google Cloud\n", - "project](https://cloud.google.com/resource-manager/docs/creating-managing-projects#shutting_down_projects) you used for the tutorial.\n", - "\n", - "Otherwise, run the following cell to delete the temporary cloud artifacts created during the BigFrames session:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "bpd.close_session()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Next steps\n", - "\n", - "Learn more about BigQuery DataFrames in the [documentation](https://cloud.google.com/python/docs/reference/bigframes/latest) and find more sample notebooks in the [GitHub repo](https://github.com/googleapis/python-bigquery-dataframes/tree/main/notebooks)." - ] - } - ], - "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/generative_ai/bq_dataframes_llm_vector_search.ipynb b/notebooks/generative_ai/bq_dataframes_llm_vector_search.ipynb deleted file mode 100644 index c9fa39926a9..00000000000 --- a/notebooks/generative_ai/bq_dataframes_llm_vector_search.ipynb +++ /dev/null @@ -1,1790 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 1, - "metadata": { - "id": "TpJu6BBeooES" - }, - "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": { - "id": "EQbZKS7_ooET" - }, - "source": [ - "# Build a Vector Search application using BigQuery DataFrames (aka BigFrames)\n", - "\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", - " \"Vertex\n", - " Open in Vertex AI Workbench\n", - " \n", - " \n", - " \n", - " \"BQ\n", - " Open in BQ Studio\n", - " \n", - "
\n" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "vFMjpPBo9aVv" - }, - "source": [ - "**Author:** Sudipto Guha (Google)\n", - "\n", - "**Last updated:** March 16th 2025" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "SHQ3Gx-oooEU" - }, - "source": [ - "## Overview\n", - "\n", - "This notebook will guide you through a practical example of using [BigFrames](https://github.com/googleapis/python-bigquery-dataframes/issues) to perform [vector search](https://cloud.google.com/bigquery/docs/vector-search-intro) and analysis on a patent dataset within BigQuery. We will leverage Python and BigFrames to efficiently process, analyze, and gain insights from a large-scale dataset without moving data from BigQuery.\n", - "\n", - "Here's a breakdown of what we'll cover:\n", - "\n", - "1. **Data Ingestion and Embedding Generation:**\n", - "We will start by reading a public patent dataset directly from BigQuery into a BigFrames DataFrame.\n", - "We'll demonstrate how to use BigFrames' `TextEmbeddingGenerator` to create text embeddings for the patent abstracts. This process converts the textual data into numerical vectors that capture the semantic meaning of each abstract.\n", - "We'll show how BigFrames efficiently performs this embedding generation within BigQuery, avoiding data transfer to the client-side.\n", - "Finally, we'll store the generated embeddings back into a new BigQuery table for subsequent analysis.\n", - "\n", - "2. **Indexing and Similarity Search:**\n", - "Here we'll create a vector index using BigFrames to enable fast and scalable similarity searches.\n", - "We'll demonstrate how to create an IVF index for efficient approximate nearest neighbor searches.\n", - "We'll then perform a vector search using a sample query string to find patents that are semantically similar to the query. This showcases how vector search goes beyond keyword matching to find relevant results based on meaning.\n", - "\n", - "3. **AI-Powered Summarization with Retrieval Augmented Generation (RAG):**\n", - "To further enhance the analysis, we'll implement a RAG pipeline.\n", - "We'll retrieve the top most similar patents based on the vector search results from step 2.\n", - "We'll use BigFrames' `GeminiTextGenerator` to create a prompt for an LLM to generate a concise summary of the retrieved patents.\n", - "This demonstrates how to combine vector search with generative AI to extract and synthesize meaningful insights from complex patent data.\n", - "\n", - "\n", - "We will tie these pieces together in Python using BigQuery DataFrames. [Click here](https://cloud.google.com/bigquery/docs/dataframes-quickstart) to learn more about BigQuery DataFrames!" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "EHjmqb-0ooEU" - }, - "source": [ - "### Dataset\n", - "\n", - "This notebook uses the [BQ Patents Public Dataset](https://bigquery.cloud.google.com/dataset/patents-public-data:patentsview)." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "AqdihIDJooEU" - }, - "source": [ - "### Costs\n", - "\n", - "This tutorial uses billable components of Google Cloud:\n", - "\n", - "* BigQuery (compute)\n", - "* BigQuery ML\n", - "* Generative AI support on Vertex AI\n", - "\n", - "Learn about [BigQuery compute pricing](https://cloud.google.com/bigquery/pricing#analysis_pricing_models), [Generative AI support on Vertex AI pricing](https://cloud.google.com/vertex-ai/pricing#generative_ai_models),\n", - "and [BigQuery ML pricing](https://cloud.google.com/bigquery/pricing#bqml),\n", - "and use the [Pricing Calculator](https://cloud.google.com/products/calculator/)\n", - "to generate a cost estimate based on your projected usage." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "GqLjnm1hsKGU" - }, - "source": [ - "## Setup & initialization\n", - "\n", - "Make sure you have the required roles and permissions listed below:\n", - "\n", - "For [Vector embedding generation](https://cloud.google.com/bigquery/docs/generate-text-embedding#required_roles)\n", - "\n", - "For [Vector Index creation](https://cloud.google.com/bigquery/docs/vector-index#roles_and_permissions)" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "Z-mvYJUCooEV" - }, - "source": [ - "## Before you begin\n", - "\n", - "Complete the tasks in this section to set up your environment." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "xn-v3mSvooEV" - }, - "source": [ - "### Set up your Google Cloud project\n", - "\n", - "**The following steps are required, regardless of your notebook environment.**\n", - "\n", - "1. [Select or create a Google Cloud project](https://console.cloud.google.com/cloud-resource-manager). When you first create an account, you get a $300 credit towards your compute/storage costs.\n", - "\n", - "2. [Make sure that billing is enabled for your project](https://cloud.google.com/billing/docs/how-to/modify-project).\n", - "\n", - "3. [Click here](https://console.cloud.google.com/flows/enableapi?apiid=bigquery.googleapis.com,bigqueryconnection.googleapis.com,aiplatform.googleapis.com) to enable the following APIs:\n", - "\n", - " * BigQuery API\n", - " * BigQuery Connection API\n", - " * Vertex AI API\n", - "\n", - "4. If you are running this notebook locally, install the [Cloud SDK](https://cloud.google.com/sdk)." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "Ioydzb_8ooEV" - }, - "source": [ - "#### Set your project ID\n", - "\n", - "**If you don't know your project ID**, see the support page: [Locate the project ID](https://support.google.com/googleapi/answer/7014113)" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": { - "executionInfo": { - "elapsed": 2, - "status": "ok", - "timestamp": 1742191597773, - "user": { - "displayName": "", - "userId": "" - }, - "user_tz": -480 - }, - "id": "b8bKCfIiooEV" - }, - "outputs": [], - "source": [ - "# set your project ID below\n", - "PROJECT_ID = \"bigframes-dev\" # @param {type:\"string\"}\n", - "\n", - "# set your region\n", - "REGION = \"US\" # @param {type: \"string\"}\n", - "\n", - "# Set the project id in gcloud\n", - "#! gcloud config set project {PROJECT_ID}" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "GbUgWr6LooEV" - }, - "source": [ - "#### Authenticate your Google Cloud account\n", - "\n", - "Depending on your Jupyter environment, you might have to manually authenticate. Follow the relevant instructions below." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "U7ChP8jUooEV" - }, - "source": [ - "**Vertex AI Workbench**\n", - "\n", - "Do nothing, you are already authenticated." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "VfHOYcZZooEW" - }, - "source": [ - "**Local JupyterLab instance**\n", - "\n", - "Uncomment and run the following cell:" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": { - "id": "3cGhUVM0ooEW" - }, - "outputs": [], - "source": [ - "# ! gcloud auth login" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "AoHnXlg-ooEW" - }, - "source": [ - "**Colab**\n", - "\n", - "Uncomment and run the following cell:" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" - }, - "executionInfo": { - "elapsed": 2, - "status": "ok", - "timestamp": 1742191608487, - "user": { - "displayName": "", - "userId": "" - }, - "user_tz": -480 - }, - "id": "j3lmnsh7ooEW", - "outputId": "eb68daf5-5558-487a-91d2-4b4f9e476da0" - }, - "outputs": [], - "source": [ - "# from google.colab import auth\n", - "# auth.authenticate_user()" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "a9gsyttuooEW" - }, - "source": [ - "Now we are ready to use BigQuery DataFrames!" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "xckgWno6ouHY" - }, - "source": [ - "## Step 1: Data Ingestion and Embedding Generation" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "Hjg9jDN-ooEW" - }, - "source": [ - "Install libraries" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": { - "executionInfo": { - "elapsed": 947, - "status": "ok", - "timestamp": 1742195413800, - "user": { - "displayName": "", - "userId": "" - }, - "user_tz": -480 - }, - "id": "R7STCS8xB5d2" - }, - "outputs": [], - "source": [ - "import bigframes.pandas as bf\n", - "import bigframes.ml as bf_ml\n", - "import bigframes.bigquery as bf_bq\n", - "import bigframes.ml.llm as bf_llm\n", - "\n", - "\n", - "from google.cloud import bigquery\n", - "from google.cloud import storage\n", - "\n", - "# Construct a BigQuery client object.\n", - "client = bigquery.Client()\n", - "\n", - "import pandas as pd\n", - "from IPython.display import Image, display\n", - "from PIL import Image as PILImage\n", - "import io\n", - "\n", - "import json\n", - "from IPython.display import Markdown\n", - "\n", - "# Note: The project option is not required in all environments.\n", - "# On BigQuery Studio, the project ID is automatically detected.\n", - "bf.options.bigquery.project = PROJECT_ID\n", - "bf.options.bigquery.location = REGION\n", - "\n" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "iOFF9hrvs5WE" - }, - "source": [ - "Partial ordering mode allows BigQuery DataFrames to push down many more row and column filters. On large clustered and partitioned tables, this can greatly reduce the number of bytes scanned and computation slots used. This [blog post](https://medium.com/google-cloud/introducing-partial-ordering-mode-for-bigquery-dataframes-bigframes-ec35841d95c0) goes over it in more detail." - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": { - "executionInfo": { - "elapsed": 2, - "status": "ok", - "timestamp": 1742191620533, - "user": { - "displayName": "", - "userId": "" - }, - "user_tz": -480 - }, - "id": "9Gil1Oaas7KA" - }, - "outputs": [], - "source": [ - "bf.options.bigquery.ordering_mode = \"partial\"" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "XGaGyyZsooEW" - }, - "source": [ - "If you want to reset the location of the created DataFrame or Series objects, reset the session by executing `bf.close_session()`. After that, you can reuse `bf.options.bigquery.location` to specify another location." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "v6FGschEowht" - }, - "source": [ - "Data Input - read the data from a publicly available BigQuery dataset" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" - }, - "executionInfo": { - "elapsed": 468, - "status": "ok", - "timestamp": 1742192516923, - "user": { - "displayName": "", - "userId": "" - }, - "user_tz": -480 - }, - "id": "zDSwoBo1CU3G", - "outputId": "83edbc2f-5a23-407b-8890-f968eb31be44" - }, - "outputs": [], - "source": [ - "publications = bf.read_gbq('patents-public-data.google_patents_research.publications')" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 34 - }, - "executionInfo": { - "elapsed": 6697, - "status": "ok", - "timestamp": 1742192524632, - "user": { - "displayName": "", - "userId": "" - }, - "user_tz": -480 - }, - "id": "tYDoaKgJChiq", - "outputId": "9174da29-a051-4a99-e38f-6a2b09cfe4e9" - }, - "outputs": [], - "source": [ - "## create patents base table (subset of 10k out of ~110M records)\n", - "\n", - "keep = (publications.embedding_v1.str.len() > 0) & (publications.title.str.len() > 0) & (publications.abstract.str.len() > 30)\n", - "\n", - "## Choose 10000 random rows to analyze\n", - "publications = publications[keep].peek(10000)" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 556 - }, - "executionInfo": { - "elapsed": 6, - "status": "ok", - "timestamp": 1742191801044, - "user": { - "displayName": "", - "userId": "" - }, - "user_tz": -480 - }, - "id": "XmqdJInztzPl", - "outputId": "ae05f3a6-edeb-423a-c061-c416717e1ec5" - }, - "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", - "
publication_numbertitletitle_translatedabstractabstract_translatedcpccpc_lowcpc_inventive_lowtop_termssimilarurlcountrypublication_descriptioncited_byembedding_v1
0WO-2007022924-B1Pharmaceutical compositions with melting point...FalseThe invention relates to the use of chemical f...False[{'code': 'A61K47/32', 'inventive': True, 'fir...['A61K47/32' 'A61K47/30' 'A61K47/00' 'A61K' 'A...['A61K47/32' 'A61K47/30' 'A61K47/00' 'A61K' 'A...['composition' 'mucosa' 'melting point' 'agent...[{'publication_number': 'WO-2007022924-B1', 'a...https://patents.google.com/patent/WO2007022924B1WIPO (PCT)Amended claims[][ 5.3550040e-02 -9.3632710e-02 1.4337189e-02 ...
1WO-03043855-B1Convenience lighting for interior and exterior...FalseA lighting apparatus for a vehicle(21) include...False[{'code': 'B60Q1/247', 'inventive': True, 'fir...['B60Q1/247' 'B60Q1/24' 'B60Q1/02' 'B60Q1/00' ...['B60Q1/247' 'B60Q1/24' 'B60Q1/02' 'B60Q1/00' ...['vehicle' 'light' 'apparatus defined' 'pillar...[{'publication_number': 'WO-03043855-B1', 'app...https://patents.google.com/patent/WO2003043855B1WIPO (PCT)Amended claims[][ 0.00484032 -0.02695554 -0.20798226 -0.207528...
2AU-2020396918-A2Shot detection and verification systemFalseA shot detection system for a projectile weapo...False[{'code': 'F41A19/01', 'inventive': True, 'fir...['F41A19/01' 'F41A19/00' 'F41A' 'F41' 'F' 'H04...['F41A19/01' 'F41A19/00' 'F41A' 'F41' 'F' 'H04...['interest' 'region' 'property' 'shot' 'test' ...[{'publication_number': 'US-2023228510-A1', 'a...https://patents.google.com/patent/AU2020396918A2AustraliaAmended post open to public inspection[][-1.49729420e-02 -2.27105440e-01 -2.68012730e-...
3PL-347539-A1Concrete mix of increased fire resistanceFalseThe burning resistance of concrete containing ...False[{'code': 'Y02W30/91', 'inventive': False, 'fi...['Y02W30/91' 'Y02W30/50' 'Y02W30/00' 'Y02W' 'Y...['Y02W30/91' 'Y02W30/50' 'Y02W30/00' 'Y02W' 'Y...['fire resistance' 'concrete mix' 'increased f...[{'publication_number': 'DK-1564194-T3', 'appl...https://patents.google.com/patent/PL347539A1PolandApplication[][ 0.01849568 -0.05340371 -0.19257502 -0.174919...
4AU-PS049302-A0Methods and systems (ap53)FalseA charging stand for charging a mobile phone, ...False[{'code': 'H02J7/00', 'inventive': True, 'firs...['H02J7/00' 'H02J' 'H02' 'H' 'H04B1/40' 'H04B1...['H02J7/00' 'H02J' 'H02' 'H' 'H04B1/40' 'H04B1...['connection pin' 'mobile phone' 'cartridge' '...[{'publication_number': 'AU-PS049302-A0', 'app...https://patents.google.com/patent/AUPS049302A0AustraliaApplication filed, as announced in the Gazette...[][ 0.00064732 -0.2136009 0.0040593 -0.024562...
\n", - "
" - ], - "text/plain": [ - " publication_number title \\\n", - "0 WO-2007022924-B1 Pharmaceutical compositions with melting point... \n", - "1 WO-03043855-B1 Convenience lighting for interior and exterior... \n", - "2 AU-2020396918-A2 Shot detection and verification system \n", - "3 PL-347539-A1 Concrete mix of increased fire resistance \n", - "4 AU-PS049302-A0 Methods and systems (ap53) \n", - "\n", - " title_translated abstract \\\n", - "0 False The invention relates to the use of chemical f... \n", - "1 False A lighting apparatus for a vehicle(21) include... \n", - "2 False A shot detection system for a projectile weapo... \n", - "3 False The burning resistance of concrete containing ... \n", - "4 False A charging stand for charging a mobile phone, ... \n", - "\n", - " abstract_translated cpc \\\n", - "0 False [{'code': 'A61K47/32', 'inventive': True, 'fir... \n", - "1 False [{'code': 'B60Q1/247', 'inventive': True, 'fir... \n", - "2 False [{'code': 'F41A19/01', 'inventive': True, 'fir... \n", - "3 False [{'code': 'Y02W30/91', 'inventive': False, 'fi... \n", - "4 False [{'code': 'H02J7/00', 'inventive': True, 'firs... \n", - "\n", - " cpc_low \\\n", - "0 ['A61K47/32' 'A61K47/30' 'A61K47/00' 'A61K' 'A... \n", - "1 ['B60Q1/247' 'B60Q1/24' 'B60Q1/02' 'B60Q1/00' ... \n", - "2 ['F41A19/01' 'F41A19/00' 'F41A' 'F41' 'F' 'H04... \n", - "3 ['Y02W30/91' 'Y02W30/50' 'Y02W30/00' 'Y02W' 'Y... \n", - "4 ['H02J7/00' 'H02J' 'H02' 'H' 'H04B1/40' 'H04B1... \n", - "\n", - " cpc_inventive_low \\\n", - "0 ['A61K47/32' 'A61K47/30' 'A61K47/00' 'A61K' 'A... \n", - "1 ['B60Q1/247' 'B60Q1/24' 'B60Q1/02' 'B60Q1/00' ... \n", - "2 ['F41A19/01' 'F41A19/00' 'F41A' 'F41' 'F' 'H04... \n", - "3 ['Y02W30/91' 'Y02W30/50' 'Y02W30/00' 'Y02W' 'Y... \n", - "4 ['H02J7/00' 'H02J' 'H02' 'H' 'H04B1/40' 'H04B1... \n", - "\n", - " top_terms \\\n", - "0 ['composition' 'mucosa' 'melting point' 'agent... \n", - "1 ['vehicle' 'light' 'apparatus defined' 'pillar... \n", - "2 ['interest' 'region' 'property' 'shot' 'test' ... \n", - "3 ['fire resistance' 'concrete mix' 'increased f... \n", - "4 ['connection pin' 'mobile phone' 'cartridge' '... \n", - "\n", - " similar \\\n", - "0 [{'publication_number': 'WO-2007022924-B1', 'a... \n", - "1 [{'publication_number': 'WO-03043855-B1', 'app... \n", - "2 [{'publication_number': 'US-2023228510-A1', 'a... \n", - "3 [{'publication_number': 'DK-1564194-T3', 'appl... \n", - "4 [{'publication_number': 'AU-PS049302-A0', 'app... \n", - "\n", - " url country \\\n", - "0 https://patents.google.com/patent/WO2007022924B1 WIPO (PCT) \n", - "1 https://patents.google.com/patent/WO2003043855B1 WIPO (PCT) \n", - "2 https://patents.google.com/patent/AU2020396918A2 Australia \n", - "3 https://patents.google.com/patent/PL347539A1 Poland \n", - "4 https://patents.google.com/patent/AUPS049302A0 Australia \n", - "\n", - " publication_description cited_by \\\n", - "0 Amended claims [] \n", - "1 Amended claims [] \n", - "2 Amended post open to public inspection [] \n", - "3 Application [] \n", - "4 Application filed, as announced in the Gazette... [] \n", - "\n", - " embedding_v1 \n", - "0 [ 5.3550040e-02 -9.3632710e-02 1.4337189e-02 ... \n", - "1 [ 0.00484032 -0.02695554 -0.20798226 -0.207528... \n", - "2 [-1.49729420e-02 -2.27105440e-01 -2.68012730e-... \n", - "3 [ 0.01849568 -0.05340371 -0.19257502 -0.174919... \n", - "4 [ 0.00064732 -0.2136009 0.0040593 -0.024562... " - ] - }, - "execution_count": 9, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "## take a look at the sample dataset\n", - "\n", - "publications.head(5)" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "Wl2o-NYMoygb" - }, - "source": [ - "Generate the text embeddings" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 34 - }, - "executionInfo": { - "elapsed": 4528, - "status": "ok", - "timestamp": 1742192047236, - "user": { - "displayName": "", - "userId": "" - }, - "user_tz": -480 - }, - "id": "li38q8FzDDMu", - "outputId": "b8c1bd38-b484-4f71-bd38-927c8677d0c5" - }, - "outputs": [ - { - "data": { - "text/html": [ - "Query job 0e9d9117-4981-4f5c-b785-ed831c08e7aa is DONE. 0 Bytes processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job fa4f1a54-85d4-4030-992e-fddda5edf3e3 is DONE. 0 Bytes processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "from bigframes.ml.llm import TextEmbeddingGenerator\n", - "\n", - "text_model = TextEmbeddingGenerator(\n", - " model_name=\"text-embedding-005\",\n", - " # No connection id needed\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 139 - }, - "executionInfo": { - "elapsed": 126632, - "status": "ok", - "timestamp": 1742192656608, - "user": { - "displayName": "", - "userId": "" - }, - "user_tz": -480 - }, - "id": "b5HHZob_u61B", - "outputId": "c9ecc5fd-5d11-4fd8-f59b-9dce4e12e371" - }, - "outputs": [ - { - "data": { - "text/html": [ - "Load job 70377d71-bb13-46af-80c1-71ef16bf2949 is DONE. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job cc3b609d-b6b7-404f-9447-c76d3a52698b is DONE. 9.5 MB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/usr/local/google/home/swast/src/github.com/googleapis/python-bigquery-dataframes-2/bigframes/core/array_value.py:109: PreviewWarning: JSON column interpretation as a custom PyArrow extention in\n", - "`db_dtypes` is a preview feature and subject to change.\n", - " warnings.warn(msg, bfe.PreviewWarning)\n" - ] - } - ], - "source": [ - "## rename abstract column to content as the desired column on which embedding will be generated\n", - "publications = publications[[\"publication_number\", \"title\", \"abstract\"]].rename(columns={'abstract': 'content'})\n", - "\n", - "## generate the embeddings\n", - "## takes ~2-3 mins to run\n", - "embedding = text_model.predict(publications)[[\"publication_number\", \"title\", \"content\", \"ml_generate_embedding_result\",\"ml_generate_embedding_status\"]]\n", - "\n", - "## filter out rows where the embedding generation failed. the embedding status value is empty if the embedding generation was successful\n", - "embedding = embedding[~embedding[\"ml_generate_embedding_status\"].isnull()]\n" - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 464 - }, - "executionInfo": { - "elapsed": 6715, - "status": "ok", - "timestamp": 1742192727525, - "user": { - "displayName": "", - "userId": "" - }, - "user_tz": -480 - }, - "id": "OIT5FbqAwqG5", - "outputId": "d04c994a-a0c8-44b0-e897-d871036eeb1f" - }, - "outputs": [ - { - "data": { - "text/html": [ - "Query job 5b15fc4a-fa9a-4608-825f-be5af9953a38 is DONE. 71.0 MB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "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", - "
publication_numbertitlecontentml_generate_embedding_resultml_generate_embedding_status
5611WO-2014005277-A1Resource management in a cloud computing envir...Technologies and implementations for managing ...[-2.92946529e-02 -1.24640828e-02 1.27173709e-...
6895AU-2011325479-B27-([1,2,3]triazol-4-yl)-pyrrolo[2,3-b]pyrazine...Compounds of formula I, in which R[-6.45397678e-02 1.19616119e-02 -9.85191786e-...
6IL-45347-A7h-indolizino(5,6,7-ij)isoquinoline derivative...Compounds of the formula:\\n[US3946019A][-3.82784344e-02 -2.31682733e-02 -4.35006060e-...
5923WO-2005111625-A3Method to predict prostate cancerA method for predicting the probability or ris...[ 0.02480386 -0.01648765 0.03873815 -0.025998...
6370US-7868678-B2Configurable differential linesEmbodiments related to configurable differenti...[ 2.71715336e-02 -1.93733890e-02 2.82729534e-...
\n", - "

5 rows × 5 columns

\n", - "
[5 rows x 5 columns in total]" - ], - "text/plain": [ - " publication_number title \\\n", - "5611 WO-2014005277-A1 Resource management in a cloud computing envir... \n", - "6895 AU-2011325479-B2 7-([1,2,3]triazol-4-yl)-pyrrolo[2,3-b]pyrazine... \n", - "6 IL-45347-A 7h-indolizino(5,6,7-ij)isoquinoline derivative... \n", - "5923 WO-2005111625-A3 Method to predict prostate cancer \n", - "6370 US-7868678-B2 Configurable differential lines \n", - "\n", - " content \\\n", - "5611 Technologies and implementations for managing ... \n", - "6895 Compounds of formula I, in which R \n", - "6 Compounds of the formula:\\n[US3946019A] \n", - "5923 A method for predicting the probability or ris... \n", - "6370 Embodiments related to configurable differenti... \n", - "\n", - " ml_generate_embedding_result \\\n", - "5611 [-2.92946529e-02 -1.24640828e-02 1.27173709e-... \n", - "6895 [-6.45397678e-02 1.19616119e-02 -9.85191786e-... \n", - "6 [-3.82784344e-02 -2.31682733e-02 -4.35006060e-... \n", - "5923 [ 0.02480386 -0.01648765 0.03873815 -0.025998... \n", - "6370 [ 2.71715336e-02 -1.93733890e-02 2.82729534e-... \n", - "\n", - " ml_generate_embedding_status \n", - "5611 \n", - "6895 \n", - "6 \n", - "5923 \n", - "6370 \n", - "\n", - "[5 rows x 5 columns]" - ] - }, - "execution_count": 12, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "embedding.head(5)" - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 53 - }, - "executionInfo": { - "elapsed": 6590, - "status": "ok", - "timestamp": 1742192833667, - "user": { - "displayName": "", - "userId": "" - }, - "user_tz": -480 - }, - "id": "GP3ZqX_bxLGq", - "outputId": "fb823ea2-e47c-415f-84d4-543dd3291e15" - }, - "outputs": [ - { - "data": { - "text/html": [ - "Query job 06ce090b-e3f9-4252-b847-45c2a296ca61 is DONE. 70.9 MB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/plain": [ - "'my_dataset.my_embeddings_table'" - ] - }, - "execution_count": 13, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# store embeddings in a BQ table\n", - "DATASET_ID = \"my_dataset\" # @param {type:\"string\"}\n", - "TEXT_EMBEDDING_TABLE_ID = \"my_embeddings_table\" # @param {type:\"string\"}\n", - "embedding.to_gbq(f\"{DATASET_ID}.{TEXT_EMBEDDING_TABLE_ID}\", if_exists='replace')" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "OUZ3NNbzo1Tb" - }, - "source": [ - "## Step 2: Indexing and Similarity Search" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "mvJH2FCmynMm" - }, - "source": [ - "### [Create a Vector Index](https://cloud.google.com/python/docs/reference/bigframes/latest/bigframes.bigquery#bigframes_bigquery_create_vector_index) using BigFrames\n", - "\n", - "\n", - "**Index Type**\n", - "\n", - "The algorithm to use to build the vector index.\n", - "The supported values are IVF and TREE_AH." - ] - }, - { - "cell_type": "code", - "execution_count": 14, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 34 - }, - "executionInfo": { - "elapsed": 3882, - "status": "ok", - "timestamp": 1742193028877, - "user": { - "displayName": "", - "userId": "" - }, - "user_tz": -480 - }, - "id": "6SBVdv6gyU5A", - "outputId": "6583e113-de27-4b44-972d-c1cc061e3c76" - }, - "outputs": [], - "source": [ - "## create vector index (note only works of tables >5000 rows)\n", - "\n", - "bf_bq.create_vector_index(\n", - " table_id = f\"{DATASET_ID}.{TEXT_EMBEDDING_TABLE_ID}\",\n", - " column_name = \"ml_generate_embedding_result\",\n", - " replace= True,\n", - " index_name = \"bf_python_index\",\n", - " distance_type=\"cosine\",\n", - " index_type= \"ivf\"\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "bo8mBbRLzCOA" - }, - "source": [ - "### Vector Search (semantic search) using Vector Index\n", - "\n", - "ANN (approx nearest neighbor) search using the created vector index" - ] - }, - { - "cell_type": "code", - "execution_count": 15, - "metadata": { - "executionInfo": { - "elapsed": 639, - "status": "ok", - "timestamp": 1742194606771, - "user": { - "displayName": "", - "userId": "" - }, - "user_tz": -480 - }, - "id": "v19BJm_wzPdZ" - }, - "outputs": [], - "source": [ - "## Set variable for vector search\n", - "\n", - "TEXT_SEARCH_STRING = \"Chip assemblies employing solder bonds to back-side lands including an electrolytic nickel layer\" ## replace with whatever search string you want to use for the vector search\n", - "FRACTION_LISTS_TO_SEARCH = 0.01" - ] - }, - { - "cell_type": "code", - "execution_count": 16, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 121 - }, - "executionInfo": { - "elapsed": 6927, - "status": "ok", - "timestamp": 1742194625774, - "user": { - "displayName": "", - "userId": "" - }, - "user_tz": -480 - }, - "id": "pAQY1ejpzPap", - "outputId": "485698ad-ac6e-4c93-844e-5d0f30aff13a" - }, - "outputs": [ - { - "data": { - "text/html": [ - "Query job 016ad678-9609-4c78-8f07-3f9887ce67ac is DONE. 0 Bytes processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/usr/local/google/home/swast/src/github.com/googleapis/python-bigquery-dataframes-2/bigframes/core/array_value.py:109: PreviewWarning: JSON column interpretation as a custom PyArrow extention in\n", - "`db_dtypes` is a preview feature and subject to change.\n", - " warnings.warn(msg, bfe.PreviewWarning)\n" - ] - } - ], - "source": [ - "# convert search string to dataframe\n", - "TEXT_SEARCH_DF = bf.DataFrame([TEXT_SEARCH_STRING], columns=['search_string'])\n", - "\n", - "#generate embedding of search query\n", - "search_query = bf.DataFrame(text_model.predict(TEXT_SEARCH_DF))" - ] - }, - { - "cell_type": "code", - "execution_count": 17, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 104 - }, - "executionInfo": { - "elapsed": 5110, - "status": "ok", - "timestamp": 1742194670801, - "user": { - "displayName": "", - "userId": "" - }, - "user_tz": -480 - }, - "id": "sx0AGAdn5FYX", - "outputId": "551ebac3-594f-4303-ca97-5301dfee72bb" - }, - "outputs": [], - "source": [ - "## search the base table for the user's query\n", - "\n", - "vector_search_results = bf_bq.vector_search(\n", - " base_table=f\"{DATASET_ID}.{TEXT_EMBEDDING_TABLE_ID}\",\n", - " column_to_search=\"ml_generate_embedding_result\",\n", - " query=search_query,\n", - " distance_type=\"cosine\",\n", - " query_column_to_search=\"ml_generate_embedding_result\",\n", - " top_k=5,\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": 18, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 270 - }, - "executionInfo": { - "elapsed": 3511, - "status": "ok", - "timestamp": 1742195090670, - "user": { - "displayName": "", - "userId": "" - }, - "user_tz": -480 - }, - "id": "px1v4iJM5L0c", - "outputId": "d107b6e3-a362-42db-c0c2-084d02acd244" - }, - "outputs": [ - { - "data": { - "text/html": [ - "Load job b6b88844-9ed7-4c92-8984-556414592f0b is DONE. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job aa95f59c-7229-4e76-bd2c-3a63deea3285 is DONE. 4.7 kB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "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", - "
querypublication_numbertitle (relevant match)abstract (relevant match)distance
0Chip assemblies employing solder bonds to back...CN-103515336-AChip package, chip arrangement, circuit board ...A chip package is provided, the chip package i...0.287274
0Chip assemblies employing solder bonds to back...US-9548145-B2Microelectronic assembly with multi-layer supp...A method of forming a microelectronic assembly...0.290519
0Chip assemblies employing solder bonds to back...JP-2012074505-ASemiconductor mounting device substrate, semic...To provide a substrate for a semiconductor mou...0.294241
0Chip assemblies employing solder bonds to back...US-2015380164-A1Ceramic electronic componentA ceramic electronic component includes an ele...0.295716
0Chip assemblies employing solder bonds to back...US-2012153447-A1Microelectronic flip chip packages with solder...Processes of assembling microelectronic packag...0.300337
\n", - "

5 rows × 5 columns

\n", - "
[5 rows x 5 columns in total]" - ], - "text/plain": [ - " query publication_number \\\n", - "0 Chip assemblies employing solder bonds to back... CN-103515336-A \n", - "0 Chip assemblies employing solder bonds to back... US-9548145-B2 \n", - "0 Chip assemblies employing solder bonds to back... JP-2012074505-A \n", - "0 Chip assemblies employing solder bonds to back... US-2015380164-A1 \n", - "0 Chip assemblies employing solder bonds to back... US-2012153447-A1 \n", - "\n", - " title (relevant match) \\\n", - "0 Chip package, chip arrangement, circuit board ... \n", - "0 Microelectronic assembly with multi-layer supp... \n", - "0 Semiconductor mounting device substrate, semic... \n", - "0 Ceramic electronic component \n", - "0 Microelectronic flip chip packages with solder... \n", - "\n", - " abstract (relevant match) distance \n", - "0 A chip package is provided, the chip package i... 0.287274 \n", - "0 A method of forming a microelectronic assembly... 0.290519 \n", - "0 To provide a substrate for a semiconductor mou... 0.294241 \n", - "0 A ceramic electronic component includes an ele... 0.295716 \n", - "0 Processes of assembling microelectronic packag... 0.300337 \n", - "\n", - "[5 rows x 5 columns]" - ] - }, - "execution_count": 18, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "## View the returned results based on simalirity with the user's query\n", - "\n", - "vector_search_results[\n", - " [\n", - " 'content',\n", - " 'publication_number',\n", - " 'title',\n", - " 'content_1',\n", - " 'distance',\n", - " ]\n", - "].rename(columns={\n", - " 'content': 'query',\n", - " 'content_1':'abstract (relevant match)' ,\n", - " 'title':'title (relevant match)',\n", - "})" - ] - }, - { - "cell_type": "code", - "execution_count": 19, - "metadata": { - "executionInfo": { - "elapsed": 1622, - "status": "ok", - "timestamp": 1742195139318, - "user": { - "displayName": "", - "userId": "" - }, - "user_tz": -480 - }, - "id": "5fb_O-ne5cvH" - }, - "outputs": [], - "source": [ - "## Brute force result (for comparison)\n", - "\n", - "\n", - "brute_force_result = bf_bq.vector_search(\n", - " base_table=f\"{DATASET_ID}.{TEXT_EMBEDDING_TABLE_ID}\",\n", - " column_to_search=\"ml_generate_embedding_result\",\n", - " query=search_query,\n", - " top_k=5,\n", - " distance_type=\"cosine\",\n", - " use_brute_force=True,\n", - ")\n" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "21rNsFMHo8hO" - }, - "source": [ - "## Step 3: AI-Powered Summarization with Retrieval Augmented Generation (RAG)" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "K3pIQrzB7T_G" - }, - "source": [ - "Patent documents can be dense and time-consuming to digest. AI-Powered Patent Summarization utilizes Retrieval Augmented Generation (RAG) to streamline this process. By retrieving relevant patent information through vector search and then synthesizing it with a large language model, we can generate concise, human-readable summaries, saving valuable time and effort. The code sample below walks through how to set this up continuing with the same user query as the previous use case." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 34 - }, - "executionInfo": { - "elapsed": 4827, - "status": "ok", - "timestamp": 1742195565658, - "user": { - "displayName": "", - "userId": "" - }, - "user_tz": -480 - }, - "id": "jb5rueqU7T5J", - "outputId": "43732836-ebae-4fb3-b28e-bfea51146c72" - }, - "outputs": [ - { - "data": { - "text/html": [ - "Query job 3fabe659-f95b-49cb-b0c7-9d32b09177bf is DONE. 0 Bytes processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "## gemini model\n", - "\n", - "llm_model = bf_llm.GeminiTextGenerator(model_name = \"gemini-2.5-flash\") ## replace with other model as needed" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "41e12JTf70sr" - }, - "source": [ - "We will use the same user query from Section 2, and pass the list of abstracts returned by the vector search into the prompt for the RAG application" - ] - }, - { - "cell_type": "code", - "execution_count": 21, - "metadata": { - "executionInfo": { - "elapsed": 1474, - "status": "ok", - "timestamp": 1742195536109, - "user": { - "displayName": "", - "userId": "" - }, - "user_tz": -480 - }, - "id": "EyP-ZFJK8h-2" - }, - "outputs": [], - "source": [ - "TEMPERATURE = 0.4" - ] - }, - { - "cell_type": "code", - "execution_count": 22, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 72 - }, - "executionInfo": { - "elapsed": 3371, - "status": "ok", - "timestamp": 1742195421813, - "user": { - "displayName": "", - "userId": "" - }, - "user_tz": -480 - }, - "id": "eP99R6SV7Tug", - "outputId": "c34bc931-5be8-410e-ac1f-604df31ef533" - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "['{\"abstract\": \"A chip package is provided, the chip package including: a chip carrier; a chip disposed over and electrically connected to a chip carrier top side; an electrically insulating material disposed over and at least partially surrounding the chip; one or more electrically conductive contact regions formed over the electrically insulating material and in electrical connection with the chip; and another electrically insulating material disposed over a chip carrier bottom side. An electrically conductive contact region on the chip carrier bottom side is released from the further electrically insulating material.\"}', '{\"abstract\": \"A method of forming a microelectronic assembly includes positioning a support structure adjacent to an active region of a device but not extending onto the active region. The support structure has planar sections. Each planar section has a substantially uniform composition. The composition of at least one of the planar sections differs from the composition of at least one of the other planar sections. A lid is positioned in contact with the support structure and extends over the active region. The support structure is bonded to the device and to the lid.\"}', '{\"abstract\": \"To provide a substrate for a semiconductor mounting device capable of obtaining high reliability. In a semiconductor mounting device substrate of the present invention, a semiconductor chip can be surface-mounted by a flip chip connection method on a semiconductor chip mounting region of a first main surface of a multilayer wiring substrate. A plurality of second main surface side solder bumps 52 forming a plate-like component mounting region 53 are formed at a location immediately below the semiconductor chip 21 on the second main surface 13 of the multilayer wiring board 11. A plate-like component 101 mainly composed of an inorganic material is surface-mounted on the multilayer wiring board 11 by a flip chip connection method via a plurality of second main surface side solder bumps 52. A plurality of second main surface side solder bumps 52 are sealed by a second main surface side underfill 107 provided in the gap S <b> 2 between the second main surface 13 and the plate-like component 101. [Selection] Figure 1\"}', '{\"abstract\": \"A ceramic electronic component includes an electronic component body, an inner electrode, and an outer electrode. The outer electrode includes a fired electrode layer and first and second plated layers. The fired electrode layer is disposed on the electronic component body. The first plated layer is disposed on the fired electrode layer. The thickness of the first plated layer is about 3 \\\\u03bcm to about 8 \\\\u03bcm, for example. The first plated layer contains nickel. The second plated layer is disposed on the first plated layer. The thickness of the second plated layer is about 0.025 \\\\u03bcm to about 1 \\\\u03bcm, for example. The second plated layer contains lead.\"}', '{\"abstract\": \"Processes of assembling microelectronic packages with lead frames and/or other suitable substrates are described herein. In one embodiment, a method for fabricating a semiconductor assembly includes forming an attachment area and a non-attachment area on a lead finger of a lead frame. The attachment area is more wettable to the solder ball than the non-attachment area during reflow. The method also includes contacting a solder ball carried by a semiconductor die with the attachment area of the lead finger, reflowing the solder ball while the solder ball is in contact with the attachment area of the lead finger, and controllably collapsing the solder ball to establish an electrical connection between the semiconductor die and the lead finger of the lead frame.\"}']\n" - ] - } - ], - "source": [ - "# Extract strings into a list of JSON strings\n", - "json_strings = [json.dumps({'abstract': s}) for s in vector_search_results['content_1']]\n", - "ALL_ABSTRACTS = json_strings\n", - "\n", - "# Print the result (optional)\n", - "print(ALL_ABSTRACTS)" - ] - }, - { - "cell_type": "code", - "execution_count": 23, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" - }, - "collapsed": true, - "executionInfo": { - "elapsed": 1620, - "status": "ok", - "timestamp": 1742195587180, - "user": { - "displayName": "", - "userId": "" - }, - "user_tz": -480 - }, - "id": "kSNSi1GV8OAD", - "outputId": "37fbc822-1160-4fbd-c7d6-ecb4a16db394" - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\n", - "You are an expert patent analyst. I will provide you the abstracts of the top 5 patents in json format retrieved by a vector search based on a user's query.\n", - "Your task is to analyze these abstracts and generate a concise, coherent summary that encapsulates the core innovations and concepts shared among them.\n", - "\n", - "In your output, share the original user query.\n", - "Then output the concise, coherent summary that encapsulates the core innovations and concepts shared among the top 5 abstracts. The heading for this section should\n", - "be : Summary of the top 5 abstracts that are semantically closest to the user query.\n", - "\n", - "User Query: Chip assemblies employing solder bonds to back-side lands including an electrolytic nickel layer\n", - "Top 5 abstracts: ['{\"abstract\": \"A chip package is provided, the chip package including: a chip carrier; a chip disposed over and electrically connected to a chip carrier top side; an electrically insulating material disposed over and at least partially surrounding the chip; one or more electrically conductive contact regions formed over the electrically insulating material and in electrical connection with the chip; and another electrically insulating material disposed over a chip carrier bottom side. An electrically conductive contact region on the chip carrier bottom side is released from the further electrically insulating material.\"}', '{\"abstract\": \"A method of forming a microelectronic assembly includes positioning a support structure adjacent to an active region of a device but not extending onto the active region. The support structure has planar sections. Each planar section has a substantially uniform composition. The composition of at least one of the planar sections differs from the composition of at least one of the other planar sections. A lid is positioned in contact with the support structure and extends over the active region. The support structure is bonded to the device and to the lid.\"}', '{\"abstract\": \"To provide a substrate for a semiconductor mounting device capable of obtaining high reliability. In a semiconductor mounting device substrate of the present invention, a semiconductor chip can be surface-mounted by a flip chip connection method on a semiconductor chip mounting region of a first main surface of a multilayer wiring substrate. A plurality of second main surface side solder bumps 52 forming a plate-like component mounting region 53 are formed at a location immediately below the semiconductor chip 21 on the second main surface 13 of the multilayer wiring board 11. A plate-like component 101 mainly composed of an inorganic material is surface-mounted on the multilayer wiring board 11 by a flip chip connection method via a plurality of second main surface side solder bumps 52. A plurality of second main surface side solder bumps 52 are sealed by a second main surface side underfill 107 provided in the gap S <b> 2 between the second main surface 13 and the plate-like component 101. [Selection] Figure 1\"}', '{\"abstract\": \"A ceramic electronic component includes an electronic component body, an inner electrode, and an outer electrode. The outer electrode includes a fired electrode layer and first and second plated layers. The fired electrode layer is disposed on the electronic component body. The first plated layer is disposed on the fired electrode layer. The thickness of the first plated layer is about 3 \\\\u03bcm to about 8 \\\\u03bcm, for example. The first plated layer contains nickel. The second plated layer is disposed on the first plated layer. The thickness of the second plated layer is about 0.025 \\\\u03bcm to about 1 \\\\u03bcm, for example. The second plated layer contains lead.\"}', '{\"abstract\": \"Processes of assembling microelectronic packages with lead frames and/or other suitable substrates are described herein. In one embodiment, a method for fabricating a semiconductor assembly includes forming an attachment area and a non-attachment area on a lead finger of a lead frame. The attachment area is more wettable to the solder ball than the non-attachment area during reflow. The method also includes contacting a solder ball carried by a semiconductor die with the attachment area of the lead finger, reflowing the solder ball while the solder ball is in contact with the attachment area of the lead finger, and controllably collapsing the solder ball to establish an electrical connection between the semiconductor die and the lead finger of the lead frame.\"}']\n", - "\n", - "Instructions:\n", - "\n", - "Focus on identifying the common themes and key technological advancements described in the abstracts.\n", - "Synthesize the information into a clear and concise summary, approximately 150-200 words.\n", - "Avoid simply copying phrases from the abstracts. Instead, aim to provide a cohesive overview of the shared concepts.\n", - "Highlight the potential applications and benefits of the described inventions.\n", - "Maintain a professional and objective tone.\n", - "Do not mention the individual patents by number, focus on summarizing the shared concepts.\n", - "\n" - ] - } - ], - "source": [ - "## Setup the LLM prompt\n", - "\n", - "prompt = f\"\"\"\n", - "You are an expert patent analyst. I will provide you the abstracts of the top 5 patents in json format retrieved by a vector search based on a user's query.\n", - "Your task is to analyze these abstracts and generate a concise, coherent summary that encapsulates the core innovations and concepts shared among them.\n", - "\n", - "In your output, share the original user query.\n", - "Then output the concise, coherent summary that encapsulates the core innovations and concepts shared among the top 5 abstracts. The heading for this section should\n", - "be : Summary of the top 5 abstracts that are semantically closest to the user query.\n", - "\n", - "User Query: {TEXT_SEARCH_STRING}\n", - "Top 5 abstracts: {ALL_ABSTRACTS}\n", - "\n", - "Instructions:\n", - "\n", - "Focus on identifying the common themes and key technological advancements described in the abstracts.\n", - "Synthesize the information into a clear and concise summary, approximately 150-200 words.\n", - "Avoid simply copying phrases from the abstracts. Instead, aim to provide a cohesive overview of the shared concepts.\n", - "Highlight the potential applications and benefits of the described inventions.\n", - "Maintain a professional and objective tone.\n", - "Do not mention the individual patents by number, focus on summarizing the shared concepts.\n", - "\"\"\"\n", - "\n", - "print(prompt)" - ] - }, - { - "cell_type": "code", - "execution_count": 24, - "metadata": { - "executionInfo": { - "elapsed": 1, - "status": "ok", - "timestamp": 1742195567707, - "user": { - "displayName": "", - "userId": "" - }, - "user_tz": -480 - }, - "id": "njiQdfkT8Y7V" - }, - "outputs": [], - "source": [ - "## Define a function that will take the input propmpt and run the LLM\n", - "\n", - "def predict(prompt: str, temperature: float = TEMPERATURE) -> str:\n", - " # Create dataframe\n", - " input = bf.DataFrame(\n", - " {\n", - " \"prompt\": [prompt],\n", - " }\n", - " )\n", - "\n", - " # Return response\n", - " return llm_model.predict(input, temperature=temperature).ml_generate_text_llm_result.iloc[0]" - ] - }, - { - "cell_type": "code", - "execution_count": 25, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 426 - }, - "executionInfo": { - "elapsed": 14425, - "status": "ok", - "timestamp": 1742195608280, - "user": { - "displayName": "", - "userId": "" - }, - "user_tz": -480 - }, - "id": "OYYkVYbs8Y0P", - "outputId": "def839e3-3dee-4320-9cb5-cac855ddea6b" - }, - "outputs": [ - { - "data": { - "text/html": [ - "Load job 34f3b649-6e45-46db-a6e5-405ae0a8bf69 is DONE. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job a574725f-64ae-4a19-aac0-959bec0bffeb is DONE. 5.0 kB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/usr/local/google/home/swast/src/github.com/googleapis/python-bigquery-dataframes-2/bigframes/core/array_value.py:109: PreviewWarning: JSON column interpretation as a custom PyArrow extention in\n", - "`db_dtypes` is a preview feature and subject to change.\n", - " warnings.warn(msg, bfe.PreviewWarning)\n" - ] - }, - { - "data": { - "text/markdown": [ - "User Query: Chip assemblies employing solder bonds to back-side lands including an electrolytic nickel layer\n", - "\n", - "Summary of the top 5 abstracts that are semantically closest to the user query:\n", - "\n", - "The abstracts describe various aspects of microelectronic assembly and packaging, with a focus on enhancing reliability and electrical connectivity. A common theme is the use of solder bumps or balls for creating electrical connections between different components, such as semiconductor chips and substrates or lead frames. Several abstracts highlight methods for improving the solderability and wettability of contact regions, often involving the use of multiple layers with differing compositions. The use of electrically insulating materials to provide support and protection to the chip and electrical connections is also described. One abstract specifically mentions a nickel-containing plated layer as part of an outer electrode, suggesting its role in improving the electrical or mechanical properties of the connection. The innovations aim to improve the reliability and performance of microelectronic devices through optimized material selection, assembly processes, and structural designs.\n" - ], - "text/plain": [ - "" - ] - }, - "execution_count": 25, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# Invoke LLM with prompt\n", - "response = predict(prompt, temperature = TEMPERATURE)\n", - "\n", - "# Print results as Markdown\n", - "Markdown(response)" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "sy82XLDfooEb" - }, - "source": [ - "# Summary and next steps\n", - "\n", - "Ready to dive deeper and explore the endless possibilities? Start building your own vector search applications with BigFrames and BigQuery today! Check out our [documentation](https://cloud.google.com/python/docs/reference/bigframes/latest/bigframes.bigquery#bigframes_bigquery_vector_search), explore our sample [notebooks](https://github.com/googleapis/python-bigquery-dataframes/tree/main/notebooks), and unleash the power of vector analytics on your data.\n", - "The BigFrames team would also love to hear from you. If you would like to reach out, please send an email to: bigframes-feedback@google.com or by filing an issue at the [open source BigFrames repository](https://github.com/googleapis/python-bigquery-dataframes/issues). To receive updates about BigFrames, subscribe to the BigFrames email list." - ] - } - ], - "metadata": { - "colab": { - "name": "bq_dataframes_llm_kmeans", - "provenance": [], - "toc_visible": true - }, - "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.16" - } - }, - "nbformat": 4, - "nbformat_minor": 0 -} diff --git a/notebooks/generative_ai/bq_dataframes_ml_drug_name_generation.ipynb b/notebooks/generative_ai/bq_dataframes_ml_drug_name_generation.ipynb index 93ac3f31c14..56d7bd13558 100644 --- a/notebooks/generative_ai/bq_dataframes_ml_drug_name_generation.ipynb +++ b/notebooks/generative_ai/bq_dataframes_ml_drug_name_generation.ipynb @@ -2,7 +2,7 @@ "cells": [ { "cell_type": "code", - "execution_count": 1, + "execution_count": null, "metadata": { "id": "ur8xi4C7S06n" }, @@ -34,12 +34,12 @@ "\n", " \n", " \n", @@ -48,16 +48,21 @@ " \"Vertex\n", " Open in Vertex AI Workbench\n", " \n", - " \n", - " \n", + " \n", "
\n", " \n", - " \"Colab Run in Colab\n", + " \"Colab Run in Colab\n", " \n", " \n", " \n", - " \"GitHub\n", + " \"GitHub\n", " View on GitHub\n", " \n", " \n", - " \n", - " \"BQ\n", - " Open in BQ Studio\n", - " \n", - "
" ] }, + { + "cell_type": "markdown", + "metadata": { + "id": "24743cf4a1e1" + }, + "source": [ + "**_NOTE_**: This notebook has been tested in the following environment:\n", + "\n", + "* Python version = 3.9" + ] + }, { "cell_type": "markdown", "metadata": { @@ -87,7 +92,7 @@ "1. Use `bigframes` to query the FDA dataset of over 100,000 drugs, filtered on the brand name, generic name, and indications & usage columns.\n", "1. Filter this dataset to find prototypical brand names that can be used as examples in prompt tuning.\n", "1. Create a prompt with the user input, general instructions, examples and counter-examples for the desired brand name.\n", - "1. Use the `bigframes.ml.llm.GeminiTextGenerator` to generate choices of brand names." + "1. Use the `bigframes.ml.llm.PaLM2TextGenerator` to generate choices of brand names." ] }, { @@ -133,13 +138,13 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": null, "metadata": { "id": "2b4ef9b72d43" }, "outputs": [], "source": [ - "# !pip install -U --quiet bigframes" + "!pip install -U --quiet bigframes" ] }, { @@ -153,7 +158,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": null, "metadata": { "id": "f200f10a1da3" }, @@ -177,14 +182,15 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": null, "metadata": { "id": "PyQmSRbKA8r-" }, "outputs": [], "source": [ "import bigframes.pandas as bpd\n", - "from bigframes.ml.llm import GeminiTextGenerator\n", + "from google.cloud import bigquery_connection_v1 as bq_connection\n", + "from bigframes.ml.llm import PaLM2TextGenerator\n", "from IPython.display import Markdown" ] }, @@ -220,7 +226,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": null, "metadata": { "id": "254614fa0c46" }, @@ -240,7 +246,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": null, "metadata": { "id": "603adbbf0532" }, @@ -288,27 +294,13 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": null, "metadata": { "id": "oM1iC_MfAts1" }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\u001b[1;31mERROR:\u001b[0m (gcloud.config.set) argument VALUE: Must be specified.\n", - "Usage: gcloud config set SECTION/PROPERTY VALUE [optional flags]\n", - " optional flags may be --help | --installation\n", - "\n", - "For detailed information on this command and its flags, run:\n", - " gcloud config set --help\n" - ] - } - ], + "outputs": [], "source": [ - "# Please fill in these values.\n", - "PROJECT_ID = \"\" # @param {type:\"string\"}\n", + "PROJECT_ID = \"\" # @param {type:\"string\"}\n", "\n", "# Set the project id\n", "! gcloud config set project {PROJECT_ID}" @@ -328,14 +320,17 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": null, "metadata": { "id": "G1vVsPiMsL2X" }, "outputs": [], "source": [ "# Please fill in these values.\n", - "LOCATION = \"us\" # @param {type:\"string\"}" + "LOCATION = \"us\" # @param {type:\"string\"}\n", + "CONNECTION = \"\" # @param {type:\"string\"}\n", + "\n", + "connection_name = f\"{PROJECT_ID}.{LOCATION}.{CONNECTION}\"" ] }, { @@ -347,6 +342,50 @@ "We will now try to use the provided connection, and if it doesn't exist, create a new one. We will also print the service account used." ] }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "56Hw42m6kFrj" + }, + "outputs": [], + "source": [ + "# Initialize client and set request parameters\n", + "client = bq_connection.ConnectionServiceClient()\n", + "new_conn_parent = f\"projects/{PROJECT_ID}/locations/{LOCATION}\"\n", + "exists_conn_parent = f\"projects/{PROJECT_ID}/locations/{LOCATION}/connections/{CONNECTION}\"\n", + "cloud_resource_properties = bq_connection.CloudResourceProperties({})\n", + "\n", + "# Try to connect using provided connection\n", + "try:\n", + " request = client.get_connection(\n", + " request=bq_connection.GetConnectionRequest(name=exists_conn_parent)\n", + " )\n", + " CONN_SERVICE_ACCOUNT = f\"serviceAccount:{request.cloud_resource.service_account_id}\"\n", + "# Create a new connection on error\n", + "except Exception:\n", + " connection = bq_connection.types.Connection(\n", + " {\"friendly_name\": CONNECTION, \"cloud_resource\": cloud_resource_properties}\n", + " )\n", + " request = bq_connection.CreateConnectionRequest(\n", + " {\n", + " \"parent\": new_conn_parent,\n", + " \"connection_id\": CONNECTION,\n", + " \"connection\": connection,\n", + " }\n", + " )\n", + " response = client.create_connection(request)\n", + " CONN_SERVICE_ACCOUNT = (\n", + " f\"serviceAccount:{response.cloud_resource.service_account_id}\"\n", + " )\n", + "# Set service account permissions\n", + "!gcloud projects add-iam-policy-binding {PROJECT_ID} --condition=None --no-user-output-enabled --member={CONN_SERVICE_ACCOUNT} --role='roles/bigquery.connectionUser'\n", + "!gcloud projects add-iam-policy-binding {PROJECT_ID} --condition=None --no-user-output-enabled --member={CONN_SERVICE_ACCOUNT} --role='roles/aiplatform.user'\n", + "!gcloud projects add-iam-policy-binding {PROJECT_ID} --condition=None --no-user-output-enabled --member={CONN_SERVICE_ACCOUNT} --role='roles/run.invoker'\n", + "\n", + "print(CONN_SERVICE_ACCOUNT)" + ] + }, { "cell_type": "markdown", "metadata": { @@ -360,20 +399,13 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": null, "metadata": { "id": "OCccLirpkSRz" }, "outputs": [], "source": [ - "# Note: The project option is not required in all environments.\n", - "# On BigQuery Studio, the project ID is automatically detected.\n", "bpd.options.bigquery.project = PROJECT_ID\n", - "\n", - "# Note: The location option is not required.\n", - "# It defaults to the location of the first table or query\n", - "# passed to read_gbq(). For APIs where a location can't be\n", - "# auto-detected, the location defaults to the \"US\" location.\n", "bpd.options.bigquery.location = LOCATION" ] }, @@ -390,7 +422,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": null, "metadata": { "id": "oxphj2gnuKou" }, @@ -413,25 +445,11 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": null, "metadata": { "id": "0knz5ZWMzed-" }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Provide 10 unique and modern brand names in Markdown bullet point format. Do not provide any additional explanation.\n", - "\n", - "Be creative with the brand names. Don't use English words directly; use variants or invented words.\n", - "\n", - "The generic name is: Entropofloxacin\n", - "\n", - "The indications and usage are: Entropofloxacin is a fluoroquinolone antibiotic that is used to treat a variety of bacterial infections, including: pneumonia, streptococcus infections, salmonella infections, escherichia coli infections, and pseudomonas aeruginosa infections It is taken by mouth or by injection. The dosage and frequency of administration will vary depending on the type of infection being treated. It should be taken for the full course of treatment, even if symptoms improve after a few days. Stopping the medication early may increase the risk of the infection coming back..\n" - ] - } - ], + "outputs": [], "source": [ "zero_shot_prompt = f\"\"\"Provide {NUM_NAMES} unique and modern brand names in Markdown bullet point format. Do not provide any additional explanation.\n", "\n", @@ -446,15 +464,19 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "id": "LCRE2L720f5y" + }, "source": [ - "Next, let's create a helper function to predict with our model. It will take a string input, and add it to a temporary BigFrames DataFrame. It will also return the string extracted from the response DataFrame." + "Next, let's create a helper function to predict with our model. It will take a string input, and add it to a temporary BigFrames `DataFrame`. It will also return the string extracted from the response `DataFrame`." ] }, { "cell_type": "code", - "execution_count": 12, - "metadata": {}, + "execution_count": null, + "metadata": { + "id": "LB3xgDroIxlx" + }, "outputs": [], "source": [ "def predict(prompt: str, temperature: float = TEMPERATURE) -> str:\n", @@ -466,7 +488,7 @@ " )\n", "\n", " # Return response\n", - " return model.predict(input, temperature=temperature).ml_generate_text_llm_result.iloc[0]" + " return model.predict(input, temperature).ml_generate_text_llm_result.iloc[0]" ] }, { @@ -484,96 +506,16 @@ "metadata": { "id": "UW2fQ2k5Hsic" }, - "outputs": [ - { - "data": { - "text/html": [ - "Query job 25b47284-2b28-4cd9-ac9a-90379f818c84 is DONE. 0 Bytes processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job 0efa6f42-6569-4274-ac21-667c7eecefc7 is DONE. 0 Bytes processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job c5e98170-7d58-4aa2-a3a3-6680cd9a54c0 is DONE. 8 Bytes processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job 5fd9d5bf-c731-4b21-b7c9-9b6244ffb412 is DONE. 2 Bytes processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job 36f7e8ec-ee42-4f94-8e38-bdf18b371517 is DONE. 118 Bytes processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/markdown": [ - "- Etherealox\n", - "- Zenithrox\n", - "- Aureox\n", - "- Lucentrox\n", - "- Aethrox\n", - "- Luminex\n", - "- Elysirox\n", - "- Quasarox\n", - "- Novaflux\n", - "- Arcanox" - ], - "text/plain": [ - "" - ] - }, - "execution_count": 22, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ + "# Get BigFrames session\n", + "session = bpd.get_global_session()\n", + "\n", "# Define the model\n", - "model = GeminiTextGenerator(model_name=\"gemini-2.5-flash\")\n", + "model = PaLM2TextGenerator(session=session, connection_name=connection_name)\n", "\n", "# Invoke LLM with prompt\n", - "response = predict(zero_shot_prompt, temperature = TEMPERATURE)\n", + "response = predict(zero_shot_prompt)\n", "\n", "# Print results as Markdown\n", "Markdown(response)" @@ -610,7 +552,7 @@ }, { "cell_type": "code", - "execution_count": 23, + "execution_count": null, "metadata": { "id": "MXdI78SOElyt" }, @@ -632,26 +574,11 @@ }, { "cell_type": "code", - "execution_count": 24, + "execution_count": null, "metadata": { "id": "aQ2iscnhF2cx" }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Provide 10 unique and modern brand names in Markdown bullet point format, related to the drug at the bottom of this prompt.\n", - "\n", - "Be creative with the brand names. Don't use English words directly; use variants or invented words.\n", - "\n", - "First, we will provide 3 examples to help with your thought process.\n", - "\n", - "Then, we will provide the generic name and usage for the drug we'd like you to generate brand names for.\n", - "\n" - ] - } - ], + "outputs": [], "source": [ "prefix_prompt = f\"\"\"Provide {NUM_NAMES} unique and modern brand names in Markdown bullet point format, related to the drug at the bottom of this prompt.\n", "\n", @@ -678,143 +605,15 @@ }, { "cell_type": "code", - "execution_count": 25, + "execution_count": null, "metadata": { "id": "IoO_Bp8wA07N" }, - "outputs": [ - { - "data": { - "text/html": [ - "Query job 542b0ce1-9d56-456f-bcd3-d24a6f0c825a is DONE. 84.4 MB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job 2405ba41-b263-46d3-a0e5-3b5e7ecef6ab is DONE. 0 Bytes processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job b24663ec-8d81-4295-84df-ffb65a6a0f1b is DONE. 3.1 kB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "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", - "
openfda_generic_nameopenfda_brand_nameindications_and_usage
0BENZALKONIUM CHLORIDEmeijer kidsUse - hand washing to decrease bacteria on skin
3OCTINOXATE, TITANIUM DIOXIDECD DIORSKIN STAR Studio Makeup Spectacular Bri...Uses Helps prevent sunburn. If used as directe...
4TRIAMCINOLONE ACETONIDETriamcinolone AcetonideINDICATIONS AND USAGE Triamcinolone Acetonide ...
5BACITRACIN ZINC, NEOMYCIN SULFATE, POLYMYXIN B...Triple AntibioticFirst aid to help prevent infection in minor c...
6RISPERIDONERisperidone1. INDICATIONS AND USAGE Risperidone is an aty...
\n", - "

5 rows × 3 columns

\n", - "
[5 rows x 3 columns in total]" - ], - "text/plain": [ - " openfda_generic_name \\\n", - "0 BENZALKONIUM CHLORIDE \n", - "3 OCTINOXATE, TITANIUM DIOXIDE \n", - "4 TRIAMCINOLONE ACETONIDE \n", - "5 BACITRACIN ZINC, NEOMYCIN SULFATE, POLYMYXIN B... \n", - "6 RISPERIDONE \n", - "\n", - " openfda_brand_name \\\n", - "0 meijer kids \n", - "3 CD DIORSKIN STAR Studio Makeup Spectacular Bri... \n", - "4 Triamcinolone Acetonide \n", - "5 Triple Antibiotic \n", - "6 Risperidone \n", - "\n", - " indications_and_usage \n", - "0 Use - hand washing to decrease bacteria on skin \n", - "3 Uses Helps prevent sunburn. If used as directe... \n", - "4 INDICATIONS AND USAGE Triamcinolone Acetonide ... \n", - "5 First aid to help prevent infection in minor c... \n", - "6 1. INDICATIONS AND USAGE Risperidone is an aty... \n", - "\n", - "[5 rows x 3 columns]" - ] - }, - "execution_count": 25, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "# Query 3 columns of interest from drug label dataset\n", "df = bpd.read_gbq(\"bigquery-public-data.fda_drug.drug_label\",\n", - " columns=[\"openfda_generic_name\", \"openfda_brand_name\", \"indications_and_usage\"])\n", + " col_order=[\"openfda_generic_name\", \"openfda_brand_name\", \"indications_and_usage\"])\n", "\n", "# Exclude any rows with missing data\n", "df = df.dropna()\n", @@ -837,7 +636,7 @@ }, { "cell_type": "code", - "execution_count": 26, + "execution_count": null, "metadata": { "id": "95WDe2eCCeLx" }, @@ -864,89 +663,11 @@ }, { "cell_type": "code", - "execution_count": 27, + "execution_count": null, "metadata": { "id": "2ohZYg7QEyJV" }, - "outputs": [ - { - "data": { - "text/html": [ - "Query job 293c90e0-7fdf-4769-9d8e-f222f35d368e is DONE. 84.4 MB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "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", - "
openfda_generic_nameopenfda_brand_nameindications_and_usage
81748AMPICILLIN SODIUMAmpicillinINDICATIONS AND USAGE Ampicillin for Injection...
730AZTREONAMCayston1 INDICATIONS AND USAGE CAYSTON® is indicated ...
71763TERAZOSIN HYDROCHLORIDETerazosinINDICATIONS AND USAGE Terazosin capsules are i...
\n", - "
" - ], - "text/plain": [ - " openfda_generic_name openfda_brand_name \\\n", - "81748 AMPICILLIN SODIUM Ampicillin \n", - "730 AZTREONAM Cayston \n", - "71763 TERAZOSIN HYDROCHLORIDE Terazosin \n", - "\n", - " indications_and_usage \n", - "81748 INDICATIONS AND USAGE Ampicillin for Injection... \n", - "730 1 INDICATIONS AND USAGE CAYSTON® is indicated ... \n", - "71763 INDICATIONS AND USAGE Terazosin capsules are i... " - ] - }, - "execution_count": 27, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "# Take a sample and convert to a Pandas dataframe for local usage.\n", "df_examples = df.sample(NUM_EXAMPLES, random_state=3).to_pandas()\n", @@ -965,19 +686,11 @@ }, { "cell_type": "code", - "execution_count": 28, + "execution_count": null, "metadata": { "id": "PcJdSaw0EGcW" }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[{'brand_name': 'Ampicillin', 'generic_name': 'AMPICILLIN SODIUM', 'usage': 'INDICATIONS AND USAGE Ampicillin for Injection, USP is indicated in the treatment of infections caused by susceptible strains of the designated organisms in the following conditions: Respiratory Tract Infections caused by Streptococcus pneumoniae. Staphylococcus aureus (penicillinase and nonpenicillinase-producing), H. influenzae, and Group A beta-hemolytic streptococci. Bacterial Meningitis caused by E. coli, Group B streptococci, and other Gram-negative bacteria (Listeria monocytogenes, N. meningitidis). The addition of an aminoglycoside with ampicillin may increase its effectiveness against Gram-negative bacteria. Septicemia and Endocarditis caused by susceptible Gram-positive organisms including Streptococcus spp., penicillin G-susceptible staphylococci, and enterococci. Gram-negative sepsis caused by E. coli, Proteus mirabilis and Salmonella spp. responds to ampicillin. Endocarditis due to enterococcal strains usually respond to intravenous therapy. The addition of an aminoglycoside may enhance the effectiveness of ampicillin when treating streptococcal endocarditis. Urinary Tract Infections caused by sensitive strains of E. coli and Proteus mirabilis. Gastrointestinal Infections caused by Salmonella typhi (typhoid fever), other Salmonella spp., and Shigella spp. (dysentery) usually respond to oral or intravenous therapy. Bacteriology studies to determine the causative organisms and their susceptibility to ampicillin should be performed. Therapy may be instituted prior to obtaining results of susceptibility testing. It is advisable to reserve the parenteral form of this drug for moderately severe and severe infections and for patients who are unable to take the oral forms. A change to oral ampicillin may be made as soon as appropriate. To reduce the development of drug-resistant bacteria and maintain the effectiveness of Ampicillin for Injection, USP and other antibacterial drugs, Ampicillin for Injection, USP should be used only to treat or prevent infections that are proven or strongly suspected to be caused by susceptible bacteria. When culture and susceptibility information are available, they should be considered in selecting or modifying antibacterial therapy. In the absence of such data, local epidemiology and susceptibility patterns may contribute to the empiric selection of therapy. Indicated surgical procedures should be performed.'}, {'brand_name': 'Cayston', 'generic_name': 'AZTREONAM', 'usage': '1 INDICATIONS AND USAGE CAYSTON® is indicated to improve respiratory symptoms in cystic fibrosis (CF) patients with Pseudomonas aeruginosa. Safety and effectiveness have not been established in pediatric patients below the age of 7 years, patients with FEV1 <25% or >75% predicted, or patients colonized with Burkholderia cepacia [see Clinical Studies (14) ]. To reduce the development of drug-resistant bacteria and maintain the effectiveness of CAYSTON and other antibacterial drugs, CAYSTON should be used only to treat patients with CF known to have Pseudomonas aeruginosa in the lungs. CAYSTON is a monobactam antibacterial indicated to improve respiratory symptoms in cystic fibrosis (CF) patients with Pseudomonas aeruginosa. Safety and effectiveness have not been established in pediatric patients below the age of 7 years, patients with FEV1 <25% or >75% predicted, or patients colonized with Burkholderia cepacia. (1)'}, {'brand_name': 'Terazosin', 'generic_name': 'TERAZOSIN HYDROCHLORIDE', 'usage': 'INDICATIONS AND USAGE Terazosin capsules are indicated for the treatment of symptomatic benign prostatic hyperplasia (BPH). There is a rapid response, with approximately 70% of patients experiencing an increase in urinary flow and improvement in symptoms of BPH when treated with terazosin capsules. The long-term effects of terazosin capsules on the incidence of surgery, acute urinary obstruction or other complications of BPH are yet to be determined. Terazosin capsules are also indicated for the treatment of hypertension. Terazosin capsules can be used alone or in combination with other antihypertensive agents such as diuretics or beta-adrenergic blocking agents.'}]\n" - ] - } - ], + "outputs": [], "source": [ "examples = [\n", " {\n", @@ -1006,22 +719,11 @@ }, { "cell_type": "code", - "execution_count": 29, + "execution_count": null, "metadata": { "id": "kzAVsF6wJ93S" }, - "outputs": [ - { - "data": { - "text/plain": [ - "'Generic name: AMPICILLIN SODIUM\\nUsage: INDICATIONS AND USAGE Ampicillin for Injection, USP is indicated in the treatment of infections caused by susceptible strains of the designated organisms in the following conditions: Respiratory Tract Infections caused by Streptococcus pneumoniae. Staphylococcus aureus (penicillinase and nonpenicillinase-producing), H. influenzae, and Group A beta-hemolytic streptococci. Bacterial Meningitis caused by E. coli, Group B streptococci, and other Gram-negative bacteria (Listeria monocytogenes, N. meningitidis). The addition of an aminoglycoside with ampicillin may increase its effectiveness against Gram-negative bacteria. Septicemia and Endocarditis caused by susceptible Gram-positive organisms including Streptococcus spp., penicillin G-susceptible staphylococci, and enterococci. Gram-negative sepsis caused by E. coli, Proteus mirabilis and Salmonella spp. responds to ampicillin. Endocarditis due to enterococcal strains usually respond to intravenous therapy. The addition of an aminoglycoside may enhance the effectiveness of ampicillin when treating streptococcal endocarditis. Urinary Tract Infections caused by sensitive strains of E. coli and Proteus mirabilis. Gastrointestinal Infections caused by Salmonella typhi (typhoid fever), other Salmonella spp., and Shigella spp. (dysentery) usually respond to oral or intravenous therapy. Bacteriology studies to determine the causative organisms and their susceptibility to ampicillin should be performed. Therapy may be instituted prior to obtaining results of susceptibility testing. It is advisable to reserve the parenteral form of this drug for moderately severe and severe infections and for patients who are unable to take the oral forms. A change to oral ampicillin may be made as soon as appropriate. To reduce the development of drug-resistant bacteria and maintain the effectiveness of Ampicillin for Injection, USP and other antibacterial drugs, Ampicillin for Injection, USP should be used only to treat or prevent infections that are proven or strongly suspected to be caused by susceptible bacteria. When culture and susceptibility information are available, they should be considered in selecting or modifying antibacterial therapy. In the absence of such data, local epidemiology and susceptibility patterns may contribute to the empiric selection of therapy. Indicated surgical procedures should be performed.\\nBrand name: Ampicillin\\n\\nGeneric name: AZTREONAM\\nUsage: 1 INDICATIONS AND USAGE CAYSTON® is indicated to improve respiratory symptoms in cystic fibrosis (CF) patients with Pseudomonas aeruginosa. Safety and effectiveness have not been established in pediatric patients below the age of 7 years, patients with FEV1 <25% or >75% predicted, or patients colonized with Burkholderia cepacia [see Clinical Studies (14) ]. To reduce the development of drug-resistant bacteria and maintain the effectiveness of CAYSTON and other antibacterial drugs, CAYSTON should be used only to treat patients with CF known to have Pseudomonas aeruginosa in the lungs. CAYSTON is a monobactam antibacterial indicated to improve respiratory symptoms in cystic fibrosis (CF) patients with Pseudomonas aeruginosa. Safety and effectiveness have not been established in pediatric patients below the age of 7 years, patients with FEV1 <25% or >75% predicted, or patients colonized with Burkholderia cepacia. (1)\\nBrand name: Cayston\\n\\nGeneric name: TERAZOSIN HYDROCHLORIDE\\nUsage: INDICATIONS AND USAGE Terazosin capsules are indicated for the treatment of symptomatic benign prostatic hyperplasia (BPH). There is a rapid response, with approximately 70% of patients experiencing an increase in urinary flow and improvement in symptoms of BPH when treated with terazosin capsules. The long-term effects of terazosin capsules on the incidence of surgery, acute urinary obstruction or other complications of BPH are yet to be determined. Terazosin capsules are also indicated for the treatment of hypertension. Terazosin capsules can be used alone or in combination with other antihypertensive agents such as diuretics or beta-adrenergic blocking agents.\\nBrand name: Terazosin\\n\\n'" - ] - }, - "execution_count": 29, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "example_prompt = \"\"\n", "for example in examples:\n", @@ -1041,21 +743,11 @@ }, { "cell_type": "code", - "execution_count": 30, + "execution_count": null, "metadata": { "id": "OYp6W_XfHTlo" }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Generic name: Entropofloxacin\n", - "Usage: Entropofloxacin is a fluoroquinolone antibiotic that is used to treat a variety of bacterial infections, including: pneumonia, streptococcus infections, salmonella infections, escherichia coli infections, and pseudomonas aeruginosa infections It is taken by mouth or by injection. The dosage and frequency of administration will vary depending on the type of infection being treated. It should be taken for the full course of treatment, even if symptoms improve after a few days. Stopping the medication early may increase the risk of the infection coming back.\n", - "Brand names:\n" - ] - } - ], + "outputs": [], "source": [ "suffix_prompt = f\"\"\"Generic name: {GENERIC_NAME}\n", "Usage: {USAGE}\n", @@ -1075,40 +767,11 @@ }, { "cell_type": "code", - "execution_count": 31, + "execution_count": null, "metadata": { "id": "99xdU7l8C1h8" }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Provide 10 unique and modern brand names in Markdown bullet point format, related to the drug at the bottom of this prompt.\n", - "\n", - "Be creative with the brand names. Don't use English words directly; use variants or invented words.\n", - "\n", - "First, we will provide 3 examples to help with your thought process.\n", - "\n", - "Then, we will provide the generic name and usage for the drug we'd like you to generate brand names for.\n", - "Generic name: AMPICILLIN SODIUM\n", - "Usage: INDICATIONS AND USAGE Ampicillin for Injection, USP is indicated in the treatment of infections caused by susceptible strains of the designated organisms in the following conditions: Respiratory Tract Infections caused by Streptococcus pneumoniae. Staphylococcus aureus (penicillinase and nonpenicillinase-producing), H. influenzae, and Group A beta-hemolytic streptococci. Bacterial Meningitis caused by E. coli, Group B streptococci, and other Gram-negative bacteria (Listeria monocytogenes, N. meningitidis). The addition of an aminoglycoside with ampicillin may increase its effectiveness against Gram-negative bacteria. Septicemia and Endocarditis caused by susceptible Gram-positive organisms including Streptococcus spp., penicillin G-susceptible staphylococci, and enterococci. Gram-negative sepsis caused by E. coli, Proteus mirabilis and Salmonella spp. responds to ampicillin. Endocarditis due to enterococcal strains usually respond to intravenous therapy. The addition of an aminoglycoside may enhance the effectiveness of ampicillin when treating streptococcal endocarditis. Urinary Tract Infections caused by sensitive strains of E. coli and Proteus mirabilis. Gastrointestinal Infections caused by Salmonella typhi (typhoid fever), other Salmonella spp., and Shigella spp. (dysentery) usually respond to oral or intravenous therapy. Bacteriology studies to determine the causative organisms and their susceptibility to ampicillin should be performed. Therapy may be instituted prior to obtaining results of susceptibility testing. It is advisable to reserve the parenteral form of this drug for moderately severe and severe infections and for patients who are unable to take the oral forms. A change to oral ampicillin may be made as soon as appropriate. To reduce the development of drug-resistant bacteria and maintain the effectiveness of Ampicillin for Injection, USP and other antibacterial drugs, Ampicillin for Injection, USP should be used only to treat or prevent infections that are proven or strongly suspected to be caused by susceptible bacteria. When culture and susceptibility information are available, they should be considered in selecting or modifying antibacterial therapy. In the absence of such data, local epidemiology and susceptibility patterns may contribute to the empiric selection of therapy. Indicated surgical procedures should be performed.\n", - "Brand name: Ampicillin\n", - "\n", - "Generic name: AZTREONAM\n", - "Usage: 1 INDICATIONS AND USAGE CAYSTON® is indicated to improve respiratory symptoms in cystic fibrosis (CF) patients with Pseudomonas aeruginosa. Safety and effectiveness have not been established in pediatric patients below the age of 7 years, patients with FEV1 <25% or >75% predicted, or patients colonized with Burkholderia cepacia [see Clinical Studies (14) ]. To reduce the development of drug-resistant bacteria and maintain the effectiveness of CAYSTON and other antibacterial drugs, CAYSTON should be used only to treat patients with CF known to have Pseudomonas aeruginosa in the lungs. CAYSTON is a monobactam antibacterial indicated to improve respiratory symptoms in cystic fibrosis (CF) patients with Pseudomonas aeruginosa. Safety and effectiveness have not been established in pediatric patients below the age of 7 years, patients with FEV1 <25% or >75% predicted, or patients colonized with Burkholderia cepacia. (1)\n", - "Brand name: Cayston\n", - "\n", - "Generic name: TERAZOSIN HYDROCHLORIDE\n", - "Usage: INDICATIONS AND USAGE Terazosin capsules are indicated for the treatment of symptomatic benign prostatic hyperplasia (BPH). There is a rapid response, with approximately 70% of patients experiencing an increase in urinary flow and improvement in symptoms of BPH when treated with terazosin capsules. The long-term effects of terazosin capsules on the incidence of surgery, acute urinary obstruction or other complications of BPH are yet to be determined. Terazosin capsules are also indicated for the treatment of hypertension. Terazosin capsules can be used alone or in combination with other antihypertensive agents such as diuretics or beta-adrenergic blocking agents.\n", - "Brand name: Terazosin\n", - "\n", - "Generic name: Entropofloxacin\n", - "Usage: Entropofloxacin is a fluoroquinolone antibiotic that is used to treat a variety of bacterial infections, including: pneumonia, streptococcus infections, salmonella infections, escherichia coli infections, and pseudomonas aeruginosa infections It is taken by mouth or by injection. The dosage and frequency of administration will vary depending on the type of infection being treated. It should be taken for the full course of treatment, even if symptoms improve after a few days. Stopping the medication early may increase the risk of the infection coming back.\n", - "Brand names:\n" - ] - } - ], + "outputs": [], "source": [ "# Define the prompt\n", "few_shot_prompt = prefix_prompt + example_prompt + suffix_prompt\n", @@ -1128,82 +791,11 @@ }, { "cell_type": "code", - "execution_count": 42, + "execution_count": null, "metadata": { "id": "d4ODRJdvLhlQ" }, - "outputs": [ - { - "data": { - "text/html": [ - "Query job 5c6c3b79-812c-4a6e-876e-ca1ff6230a6e is DONE. 0 Bytes processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job 168d5859-5edb-4702-8192-838ac2c7bc17 is DONE. 8 Bytes processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job 72f07348-4bcd-4042-84ca-396e7651ad03 is DONE. 2 Bytes processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job 70863a3b-8c63-423c-84cd-2804139daf5f is DONE. 679 Bytes processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/markdown": [ - "- **Aerion:** (Derived from \"aer\" meaning air)\n", - "- **Aquazone:** (Combining \"aqua\" for water and \"zone\" for area)\n", - "- **Biosphere:** (Inspired by the concept of a self-contained ecosystem)\n", - "- **Celestial:** (Evoking the vastness and healing power of the universe)\n", - "- **Ethereal:** (Conveying a sense of lightness and transcendence)\n", - "- **Luminary:** (From \"lumen\" meaning light, symbolizing hope and healing)\n", - "- **Quasar:** (Inspired by the powerful and distant cosmic objects)\n", - "- **Sanctuary:** (Creating a sense of safety and refuge)\n", - "- **Zenith:** (Reaching the highest point or peak)\n", - "- **Zephyr:** (Named after the gentle west wind, representing a calming and soothing effect)" - ], - "text/plain": [ - "" - ] - }, - "execution_count": 42, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "response = predict(few_shot_prompt)\n", "\n", @@ -1225,143 +817,15 @@ }, { "cell_type": "code", - "execution_count": 43, + "execution_count": null, "metadata": { "id": "8eAutS41mx6U" }, - "outputs": [ - { - "data": { - "text/html": [ - "Query job b73f92bb-0e58-4fe4-adfb-b948fc5f4647 is DONE. 84.4 MB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job 392dae36-aacb-4753-b28c-dad8291cb153 is DONE. 0 Bytes processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job 7c6ff6ee-db64-4629-a417-846dcecac127 is DONE. 6.3 kB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "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", - "
openfda_generic_nameopenfda_brand_nameindications_and_usage
89MEPHITIS MEPHITICAMEPHITIS MEPHITICAINDICATIONS Condition listed above or as direc...
105ONDANSETRONONDANSETRON1 INDICATIONS AND USAGE Ondansetron Injection,...
124CLOFARABINECLOFARABINE1 INDICATIONS AND USAGE Clofarabine injection ...
273ACETAMINOPHEN AND DIPHENHYDRAMINE HYDROCHLORIDEACETAMINOPHEN AND DIPHENHYDRAMINE HYDROCHLORIDEUses Temporary relief of occasional headaches ...
284OFLOXACINOFLOXACININDICATIONS AND USAGE To reduce the developmen...
\n", - "

5 rows × 3 columns

\n", - "
[5 rows x 3 columns in total]" - ], - "text/plain": [ - " openfda_generic_name \\\n", - "89 MEPHITIS MEPHITICA \n", - "105 ONDANSETRON \n", - "124 CLOFARABINE \n", - "273 ACETAMINOPHEN AND DIPHENHYDRAMINE HYDROCHLORIDE \n", - "284 OFLOXACIN \n", - "\n", - " openfda_brand_name \\\n", - "89 MEPHITIS MEPHITICA \n", - "105 ONDANSETRON \n", - "124 CLOFARABINE \n", - "273 ACETAMINOPHEN AND DIPHENHYDRAMINE HYDROCHLORIDE \n", - "284 OFLOXACIN \n", - "\n", - " indications_and_usage \n", - "89 INDICATIONS Condition listed above or as direc... \n", - "105 1 INDICATIONS AND USAGE Ondansetron Injection,... \n", - "124 1 INDICATIONS AND USAGE Clofarabine injection ... \n", - "273 Uses Temporary relief of occasional headaches ... \n", - "284 INDICATIONS AND USAGE To reduce the developmen... \n", - "\n", - "[5 rows x 3 columns]" - ] - }, - "execution_count": 43, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "# Query 3 columns of interest from drug label dataset\n", "df_missing = bpd.read_gbq(\"bigquery-public-data.fda_drug.drug_label\",\n", - " columns=[\"openfda_generic_name\", \"openfda_brand_name\", \"indications_and_usage\"])\n", + " col_order=[\"openfda_generic_name\", \"openfda_brand_name\", \"indications_and_usage\"])\n", "\n", "# Exclude any rows with missing data\n", "df_missing = df_missing.dropna()\n", @@ -1387,7 +851,7 @@ }, { "cell_type": "code", - "execution_count": 44, + "execution_count": null, "metadata": { "id": "19TvGN1PVmVX" }, @@ -1414,53 +878,16 @@ }, { "cell_type": "code", - "execution_count": 46, + "execution_count": null, "metadata": { "id": "tiSHa5B4aFhw" }, - "outputs": [ - { - "data": { - "text/html": [ - "Query job d216bea6-9b9c-4918-9194-40de2745beca is DONE. 84.4 MB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job 37d88636-b1fb-44da-9504-44144af9624d is DONE. 800 Bytes processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job 0b35db83-5bac-47b4-8a2c-b46a816c0e3e is DONE. 200 Bytes processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ "def batch_predict(\n", " input: bpd.DataFrame, temperature: float = TEMPERATURE\n", ") -> bpd.DataFrame:\n", - " return model.predict(input, temperature=temperature).ml_generate_text_llm_result\n", + " return model.predict(input, temperature).ml_generate_text_llm_result\n", "\n", "\n", "response = batch_predict(df_missing[\"prompt\"])" @@ -1477,73 +904,19 @@ }, { "cell_type": "code", - "execution_count": 50, + "execution_count": null, "metadata": { "id": "TnizdeqBdbZj" }, - "outputs": [ - { - "data": { - "text/html": [ - "Query job 4397b5f3-5058-409c-a361-c9fa715e46ee is DONE. 84.4 MB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job 147ea301-e249-49fb-8280-d61948d5df7f is DONE. 84.4 MB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job 067a2a73-0f36-42a6-973e-074ab8be631a is DONE. 56.7 kB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Generic name: MEPHITIS MEPHITICA\n", - "Brand name: INDICATIONS Condition listed above or as directed by the physician\n", - "Response: **Ephemeral** (Latin root: \"ephemerus,\" meaning \"lasting for a day\")\n", - "\n", - "**Aetheria** (Greek root: \"aither,\" meaning \"upper air, sky\")\n", - "\n", - "**Zenithar** (Combination of \"zenith\" and \"pharma\")\n", - "\n", - "**Celestian** (Latin root: \"celestial,\" meaning \"heavenly\")\n", - "\n", - "**Astralux** (Combination of \"astral\" and \"lux,\" meaning \"light\")\n" - ] - } - ], + "outputs": [], "source": [ "# Pick a sample\n", "k = 0\n", "\n", "# Gather the prompt and response details\n", - "prompt_generic = df_missing[\"openfda_generic_name\"].iloc[k]\n", - "prompt_usage = df_missing[\"indications_and_usage\"].iloc[k]\n", - "response_str = response.iloc[k]\n", + "prompt_generic = df_missing[\"openfda_generic_name\"][k].iloc[0]\n", + "prompt_usage = df_missing[\"indications_and_usage\"][k].iloc[0]\n", + "response_str = response[k].iloc[0]\n", "\n", "# Print details\n", "print(f\"Generic name: {prompt_generic}\")\n", @@ -1561,6 +934,36 @@ "\n", "You've also seen how BigFrames can manage each step of the process, including gathering data, data manipulation, and querying the LLM." ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "Bys6--dVmq7R" + }, + "source": [ + "## Cleaning up\n", + "\n", + "To clean up all Google Cloud resources used in this project, you can [delete the Google Cloud\n", + "project](https://cloud.google.com/resource-manager/docs/creating-managing-projects#shutting_down_projects) you used for the tutorial.\n", + "\n", + "Otherwise, you can uncomment the remaining cells and run them to delete the individual resources you created in this tutorial:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "cIODjOLump_-" + }, + "outputs": [], + "source": [ + "# Delete the BigQuery Connection\n", + "from google.cloud import bigquery_connection_v1 as bq_connection\n", + "client = bq_connection.ConnectionServiceClient()\n", + "CONNECTION_ID = f\"projects/{PROJECT_ID}/locations/{LOCATION}/connections/{CONNECTION}\"\n", + "client.delete_connection(name=CONNECTION_ID)\n", + "print(f\"Deleted connection {CONNECTION_ID}.\")" + ] } ], "metadata": { @@ -1568,21 +971,8 @@ "provenance": [] }, "kernelspec": { - "display_name": "venv", - "language": "python", + "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.12.1" } }, "nbformat": 4, diff --git a/notebooks/generative_ai/large_language_models.ipynb b/notebooks/generative_ai/large_language_models.ipynb index 4ff9a9d3d23..45a46c44af9 100644 --- a/notebooks/generative_ai/large_language_models.ipynb +++ b/notebooks/generative_ai/large_language_models.ipynb @@ -2,13 +2,13 @@ "cells": [ { "cell_type": "code", - "execution_count": 2, + "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "import bigframes.pandas\n", "import pandas as pd\n", - "from bigframes.ml.llm import GeminiTextGenerator" + "from bigframes.ml.llm import PaLM2TextGenerator" ] }, { @@ -16,51 +16,35 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# Define the model" + "## Prerequisites\n", + "Create session and define a BQ connection which we already created and allowlisted. " ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 2, "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/tmp/ipykernel_176683/987800245.py:1: ApiDeprecationWarning: gemini-1.5-X are going to be deprecated. Use gemini-2.0-X (https://cloud.google.com/python/docs/reference/bigframes/latest/bigframes.ml.llm.GeminiTextGenerator) instead. \n", - " model = GeminiTextGenerator(model_name=\"gemini-2.0-flash-001\")\n", - "/usr/local/google/home/shuowei/src/python-bigquery-dataframes/bigframes/ml/llm.py:486: DefaultLocationWarning: No explicit location is set, so using location US for the session.\n", - " self.session = session or global_session.get_global_session()\n" - ] - }, - { - "data": { - "text/html": [ - "Query job 6fa5121a-6da4-4c75-92ec-936799da4513 is DONE. 0 Bytes processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job 74460ae9-3e89-49e7-93ad-bafbb6197a86 is DONE. 0 Bytes processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ - "model = GeminiTextGenerator(model_name=\"gemini-2.5-flash\")" + "session = bigframes.pandas.get_global_session()\n", + "connection = \"bigframes-dev.us.bigframes-ml\"" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Define the model" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "model = PaLM2TextGenerator(session=session, connection_name=connection)" ] }, { @@ -99,39 +83,6 @@ "execution_count": 5, "metadata": {}, "outputs": [ - { - "data": { - "text/html": [ - "Query job 562ca203-3b53-4409-9a23-0a80d3840fcc is DONE. 0 Bytes processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/usr/local/google/home/shuowei/src/python-bigquery-dataframes/bigframes/core/array_value.py:114: PreviewWarning: JSON column interpretation as a custom PyArrow extention in\n", - "`db_dtypes` is a preview feature and subject to change.\n", - " warnings.warn(msg, bfe.PreviewWarning)\n" - ] - }, - { - "data": { - "text/html": [ - "Query job 5a6ceff2-53b5-4a4a-83ff-31bffab1b8b8 is DONE. 14.0 kB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, { "data": { "text/html": [ @@ -154,52 +105,30 @@ " \n", " \n", " ml_generate_text_llm_result\n", - " ml_generate_text_rai_result\n", - " ml_generate_text_status\n", - " prompt\n", " \n", " \n", " \n", " \n", " 0\n", - " BigQuery is a serverless, highly scalable, and...\n", - " <NA>\n", - " \n", - " What is BigQuery?\n", + " BigQuery is a fully managed, petabyte-scale an...\n", " \n", " \n", " 1\n", - " BQML stands for **BigQuery Machine Learning**....\n", - " <NA>\n", - " \n", - " What is BQML?\n", + " BQML stands for BigQuery Machine Learning. It ...\n", " \n", " \n", " 2\n", - " BigQuery DataFrames is a Python client library...\n", - " <NA>\n", - " \n", - " What is BigQuery DataFrame?\n", + " A BigQuery DataFrames is a distributed collecti...\n", " \n", " \n", "\n", "" ], "text/plain": [ - " ml_generate_text_llm_result \\\n", - "0 BigQuery is a serverless, highly scalable, and... \n", - "1 BQML stands for **BigQuery Machine Learning**.... \n", - "2 BigQuery DataFrames is a Python client library... \n", - "\n", - " ml_generate_text_rai_result ml_generate_text_status \\\n", - "0 \n", - "1 \n", - "2 \n", - "\n", - " prompt \n", - "0 What is BigQuery? \n", - "1 What is BQML? \n", - "2 What is BigQuery DataFrame? " + " ml_generate_text_llm_result\n", + "0 BigQuery is a fully managed, petabyte-scale an...\n", + "1 BQML stands for BigQuery Machine Learning. It ...\n", + "2 A BigQuery DataFrames is a distributed collecti..." ] }, "execution_count": 5, @@ -222,16 +151,16 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 6, "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "\"## BigQuery: A serverless data warehouse for large-scale data analysis\\n\\nBigQuery is a serverless, highly-scalable data warehouse designed for analyzing large datasets. It's a cloud-based service offered by Google Cloud Platform (GCP), allowing users to store, manage, and analyze massive amounts of data without managing infrastructure. \\n\\nHere are some key features of BigQuery:\\n\\n**Serverless:** You don't need to worry about provisioning, managing, or scaling servers. BigQuery handles all of this automatically, letting you focus on analyzing your data.\\n\\n**Highly-scalable:** BigQuery can handle datasets of any size, from gigabytes to petabytes. It can also scale up and down automatically to meet your processing needs.\\n\\n**Cost-effective:** You only pay for the resources you use, and there are no upfront costs. Additionally, BigQuery offers several pricing models to fit your needs, including on-demand, flat-rate, and flexible slots.\\n\\n**Easy to use:** BigQuery uses SQL, a standard query language, making it easy to analyze your data. No need to learn a new programming language.\\n\\n**Integrated with GCP:** BigQuery integrates seamlessly with other GCP services, such as Google Cloud Storage, Dataflow, and Kubernetes. This allows you to build powerful data pipelines and workflows.\\n\\n**Secure:** BigQuery uses industry-standard security practices to protect your data.\\n\\nHere are some use cases for BigQuery:\\n\\n* **Data warehousing and analytics:** Store and analyze large datasets for business intelligence and reporting.\\n* **Machine learning:** Train and deploy machine learning models on your data.\\n* **Data integration:** Combine data from multiple sources for analysis.\\n* **Real-time analytics:** Analyze data in real-time for insights and decision-making.\\n\\n**Here are some additional resources that you may find helpful:**\\n\\n* **BigQuery website:** https://cloud.google.com/bigquery\\n* **BigQuery documentation:** https://cloud.google.com/bigquery/docs\\n* **BigQuery tutorial:** https://cloud.google.com/bigquery/docs/tutorials\\n* **BigQuery pricing:** https://cloud.google.com/bigquery/pricing\\n\\nI hope this gives you a good overview of BigQuery. Please let me know if you have any other questions.\"" + "'BigQuery is a fully managed, petabyte-scale analytics data warehouse that enables businesses to analyze all their data very quickly. It is a cloud-based service that offers a pay-as-you-go pricing model. BigQuery is designed to handle large amounts of data and provide fast performance. It is a good choice for businesses that need to analyze large amounts of data quickly and easily.'" ] }, - "execution_count": 5, + "execution_count": 6, "metadata": {}, "output_type": "execute_result" } @@ -239,11 +168,18 @@ "source": [ "pred.iloc[0, 0]" ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] } ], "metadata": { "kernelspec": { - "display_name": "venv", + "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, @@ -257,7 +193,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.15" + "version": "3.10.9" } }, "nbformat": 4, diff --git a/notebooks/generative_ai/museum_art.csv b/notebooks/generative_ai/museum_art.csv deleted file mode 100644 index 37acae65d26..00000000000 --- a/notebooks/generative_ai/museum_art.csv +++ /dev/null @@ -1,14930 +0,0 @@ -object_number,is_highlight,is_public_domain,object_id,department,object_name,title,culture,period,dynasty,reign,portfolio,artist_role,artist_prefix,artist_display_name,artist_display_bio,artist_suffix,artist_alpha_sort,artist_nationality,artist_begin_date,artist_end_date,object_date,object_begin_date,object_end_date,medium,dimensions,credit_line,geography_type,city,state,county,country,region,subregion,locale,locus,excavation,river,classification,rights_and_reproduction,link_resource,metadata_date,repository -2014.247,false,true,646996,Asian Art,Screen,전(傳) 오원 장승업 (1843–1897) 청동기와 화초가 있는 정물화 조선|傳 吾園 張承業 器皿折枝圖 朝鮮|Still life with bronze vessels and flowering plants,Korea,Joseon dynasty (1392–1910),,,,Artist,Attributed to,Jang Seung-eop (pen name: Owon),"Korean, 1843–1897",,Jang Seung-eop,Korean,1843,1897,1894,1894,1894,Ten-panel folding screen; ink on paper,Overall: 77 in. × 14 ft. 2 in. (195.6 × 431.8 cm),"Gift of Mrs. Anita H. Berger, in memory of Ambassador Samuel D. Berger, 2014",,,,,,,,,,,,Screens,,http://www.metmuseum.org/art/collection/search/646996,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2012.329,false,true,77916,Asian Art,Hanging scroll,석지 채용신 학자 초상|石芝 蔡龍臣 學者肖像|Portrait of a scholar,Korea,,,,,Artist,,Chae Yongsin (artist name: Seokji) (ASA),"Korean, 1850–1941",,Chae Yongsin,Korean,1850,1941,dated by inscription to 1924,1924,1924,Hanging scroll; ink and color on silk,Image: 38 1/8 × 21 1/8 in. (96.8 × 53.7 cm) Overall with mounting: 48 × 24 7/16 in. (121.9 × 62.1 cm) Overall with knobs: 48 × 24 3/4 in. (121.9 × 62.9 cm),"Purchase, Friends of Asian Art Gifts, 2012",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/77916,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1994.377,false,true,38009,Asian Art,Painting,,"India (Himachal Pradesh, Jasrota)",,,,,Artist,Attributed to,Nainsukh,active ca. 1735–78,,Nainsukh,Indian,1725,1778,ca. 1745–50,1735,1760,"Ink, opaque watercolor, and gold on paper",Overall: 7 3/4 x 6 1/8 in. (19.7 x 15.6 cm),"Rogers Fund, 1994",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/38009,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1987.424.13,false,true,37988,Asian Art,Folio,,"India (Punjab Hills, Guler)",,,,,Artist,Workshop active in the generation after,Nainsukh,active ca. 1735–78,,Nainsukh,Indian,1725,1778,ca. 1790,1780,1800,Ink and opaque watercolor on paper,9 15/16 x 13 15/16 in. (25.2 x 35.4 cm),"Gift of Cynthia Hazen Polsky, 1987",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/37988,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2008.359.13,false,true,74660,Asian Art,Painting,,"India (Himachal Pradesh, Guler)",,,,,Artist,Attributed to a first-generation master after,Nainsukh,active ca. 1735–78,,Nainsukh,Indian,1725,1778,ca. 1780,1770,1790,Charcoal and opaque watercolor on paper,Image (sight): 5 3/8 x 7 5/8 in. (13.7 x 19.4 cm),"Gift of Subhash Kapoor, in memory of his parents, Smt Shashi Kanta and Shree Parshotam Ram Kapoor, 2008",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/74660,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2008.359.22,false,true,74674,Asian Art,Painting,,"India (Himachal Pradesh, Guler)",,,,,Artist,Attributed to a first-generation master after,Nainsukh,active ca. 1735–78,,Nainsukh,Indian,1725,1778,ca. 1780,1770,1790,"Ink, ocher and underdrawing",Image (sight): 8 3/4 x 11 3/4 in. (22.2 x 29.8 cm),"Gift of Subhash Kapoor, in memory of his parents, Smt Shashi Kanta and Shree Parshotam Ram Kapoor, 2008",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/74674,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2008.359.23,false,true,74675,Asian Art,Drawing,,"India (Pahari Hills, Guler or Kangra)",,,,,Artist,Attributed to a first-generation master after,Nainsukh,active ca. 1735–78,,Nainsukh,Indian,1725,1778,ca. 1775–80,1775,1780,Red ochre and wash on paper,8 3/8 x 11 3/8 in. (21.3 x 28.9 cm),"Gift of Subhash Kapoor, in memory of his parents, Smt Shashi Kanta and Shree Parshotam Ram Kapoor, 2008",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/74675,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2008.359.24,false,true,74677,Asian Art,Painting,,"India (Pahari Hills, Guler or Kangra)",,,,,Artist,Attributed to a follower of,Nainsukh,active ca. 1735–78,,Nainsukh,Indian,1725,1778,ca. 1780,1770,1790,Ink and wash on paper,Image (sight): 10 3/8 x 9 in. (26.4 x 22.9 cm),"Gift of Subhash Kapoor, in memory of his parents, Smt Shashi Kanta and Shree Parshotam Ram Kapoor, 2008",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/74677,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2008.359.30,false,true,74685,Asian Art,Painting,,"India (Pahari Hills, Kangra)",,,,,Artist,Attributed to,Nainsukh,active ca. 1735–78,,Nainsukh,Indian,1725,1778,ca. 1775–80,1765,1790,Ink and transparent watercolor on paper,Image (sight): 6 5/8 x 10 1/4 in. (16.8 x 26 cm) Framed: 16 x 20 in. (40.6 x 50.8 cm),"Gift of Subhash Kapoor, in memory of his parents, Smt Shashi Kanta and Shree Parshotam Ram Kapoor, 2008",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/74685,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2003.375,false,true,65595,Asian Art,Painting,,"India (Rajasthan, Mewar)",,,,,Artist,,Chokha,"Indian, active 1799–ca. 1826",,Chokha,Indian,1799,1826,ca. 1820,1810,1830,"Ink, opaque watercolor, silver, and gold on paper",8 1/4 x 13 1/4 in. (21 x 33.7 cm),"Cynthia Hazen Polsky and Leon B. Polsky Fund, 2003",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/65595,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2006.451,false,true,73261,Asian Art,Painting,,"India (Rajasthan, Mewar)",,,,,Artist,Attributed to,Chokha,"Indian, active 1799–ca. 1826",,Chokha,Indian,1799,1826,ca. 1800–10,1800,1810,"Opaque watercolor, ink and gold on paper",Image: 11 1/2 x 14 7/8 in. (29.2 x 37.8 cm) Page: 12 3/16 x 15 15/16 in. (31 x 40.5 cm),"Purchase, Friends of Asian Art Gifts, 2006",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/73261,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1996.100.5,false,true,38042,Asian Art,Painting,,"Western India, Rajasthan, Udaipur or Devgarh",,,,,Artist,Attributed to,Chokha,"Indian, active 1799–ca. 1826",,Chokha,Indian,1799,1826,ca. 1805–10,1795,1820,"Ink, opaque watercolor, and gold on paper",15 x 16 1/8 in. (38.1 x 41 cm),"Fletcher Fund, 1996",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/38042,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1998.161,false,true,39894,Asian Art,Painting,,"India (Udaipur, Rajasthan)",,,,,Artist,Attributed to,Stipple Master,"Indian, active ca. 1690–1715",,Stipple Master,Indian,1680,1725,ca. 1707–8,1697,1718,"Opaque watercolor, ink and gold on paper",Page: 18 7/8 x 14 7/8 in. (47.9 x 37.8 cm) Image: 9 1/2 x 7 5/16 in. (24.1 x 18.6 cm),"Friends of Asian Art, Purchase, Mrs. Vincent Astor Gift, 1998",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/39894,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2002.177,false,true,64891,Asian Art,Painting,,"Western India, Rajasthan, Udaipur",,,,,Artist,Attributed to,Stipple Master,"Indian, active ca. 1690–1715",,Stipple Master,Indian,1680,1725,ca. 1700–1710,1690,1720,Opaque watercolor and ink on paper,Page: 14 11/16 x 12 1/8 in. (37.3 x 30.8 cm) Image: 13 3/16 x 10 3/4 in. (33.5 x 27.3 cm),"Cynthia Hazen Polsky and Leon B. Polsky Fund, 2002",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/64891,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2003.238,false,true,65590,Asian Art,Painting,,"India (Rajasthan, Mewar)",,,,,Artist,,Stipple Master,"Indian, active ca. 1690–1715",,Stipple Master,Indian,1680,1725,ca. 1705,1695,1715,"Ink, opaque watercolor, silver, and gold on paper",18 x 13 in. (45.7 x 33 cm),"Cynthia Hazen Polsky and Leon B. Polsky Fund, 2003",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/65590,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2003.178a, b",false,true,65594,Asian Art,Painting,,"India (Rajasthan, Kota)",,,,,Artist,,The Kota Master,"Indian, active early 18th century",,Kota Master,Indian,1700,1733,ca. 1720 (recto); ca. 1750–75 (verso),1710,1785,"Ink, opaque watercolor, and gold on paper",recto: 7 1/2 x 4 3/8 in. (19.1 x 11.1 cm) verso: 9 x 5 7/8 in. (22.9 x 14.9 cm),"Cynthia Hazen Polsky and Leon B. Polsky Fund, 2003",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/65594,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1990.230,false,true,40445,Asian Art,Hanging scroll,"이유원, 매화도 조선|李裕元 梅花圖 朝鮮|Plum Branch",Korea,Joseon dynasty (1392–1910),,,,Artist,,Yi Yuwon,"Korean, 1814–1888",,Yi Yuwon,Korean,1814,1888,dated 1888,1888,1888,Hanging scroll; ink on paper,58 1/16 x 37 3/16 in. (147.5 x 94.5 cm),"Purchase, Seymour and Rogers Funds and Bequest of Dorothy Graham Bennett, 1990",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/40445,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1977.448,false,true,45063,Asian Art,Screen panel,,Korea,Joseon dynasty (1392–1910),,,,Artist,,Nam Kye-u,"Korean, 1811–1888",,Nam Kye-u,Korean,1811,1888,,1392,1910,Panel from a six-panel folding screen; ink and color on paper,36 1/4 x 12 3/16 in. (92.1 x 31 cm),"Anonymous Gift, 1977",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45063,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -12.123.1,false,true,40069,Asian Art,Hanging scroll,,China or Korea (?),Joseon dynasty (1392–1910),,,,Artist,In the style of,Muqi,"Chinese, ca. 1210–after 1269",,Muqi,Chinese,1210,1269,,1392,1910,Hanging scroll; ink and color on silk,51 1/4 x 27 1/2 in. (130.2 x 69.9 cm),"Rogers Fund, 1912",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/40069,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -12.123.2,false,true,40070,Asian Art,Hanging scroll,,China or Korea (?),Joseon dynasty (1392–1910),,,,Artist,In the style of,Muqi,"Chinese, ca. 1210–after 1269",,Muqi,Chinese,1210,1269,,1392,1910,Hanging scroll; ink and color on silk,51 1/2 x 27 1/2 in. (130.8 x 69.9 cm),"Rogers Fund, 1912",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/40070,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -57.51.38.1,false,true,37964,Asian Art,Folio,,India (Gujarat),,,,,Artist,,Bhadrabahu,"Indian, died ca. 356 B.C.",,Bhadrabahu,Indian,-0356,-0356,15th century,1400,1499,"Ink, opaque watercolor, and gold on paper",(Average size .1–.71): 4 1/2 x 11 3/8 in. (11.4 x 28.9 cm),"Bequest of Cora Timken Burnett, 1956",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/37964,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -57.51.38.2,false,true,75084,Asian Art,Folio,,India (Gujarat),,,,,Artist,,Bhadrabahu,"Indian, died ca. 356 B.C.",,Bhadrabahu,Indian,-0356,-0356,15th century,1400,1499,"Ink, opaque watercolor, and gold on paper",(Average size .1–.71): 4 1/2 x 11 3/8 in. (11.4 x 28.9 cm),"Bequest of Cora Timken Burnett, 1956",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/75084,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -57.51.38.3,false,true,75085,Asian Art,Folio,,India (Gujarat),,,,,Artist,,Bhadrabahu,"Indian, died ca. 356 B.C.",,Bhadrabahu,Indian,-0356,-0356,15th century,1400,1499,"Ink, opaque watercolor, and gold on paper",(Average size .1–.71): 4 1/2 x 11 3/8 in. (11.4 x 28.9 cm),"Bequest of Cora Timken Burnett, 1956",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/75085,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -57.51.38.4,false,true,75086,Asian Art,Folio,,India (Gujarat),,,,,Artist,,Bhadrabahu,"Indian, died ca. 356 B.C.",,Bhadrabahu,Indian,-0356,-0356,15th century,1400,1499,"Ink, opaque watercolor, and gold on paper",(Average size .1–.71): 4 1/2 x 11 3/8 in. (11.4 x 28.9 cm),"Bequest of Cora Timken Burnett, 1956",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/75086,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -57.51.38.5,false,true,75087,Asian Art,Folio,,India (Gujarat),,,,,Artist,,Bhadrabahu,"Indian, died ca. 356 B.C.",,Bhadrabahu,Indian,-0356,-0356,15th century,1400,1499,"Ink, opaque watercolor, and gold on paper",(Average size .1–.71): 4 1/2 x 11 3/8 in. (11.4 x 28.9 cm),"Bequest of Cora Timken Burnett, 1956",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/75087,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -57.51.38.6,false,true,75088,Asian Art,Folio,,India (Gujarat),,,,,Artist,,Bhadrabahu,"Indian, died ca. 356 B.C.",,Bhadrabahu,Indian,-0356,-0356,15th century,1400,1499,"Ink, opaque watercolor, and gold on paper",(Average size .1–.71): 4 1/2 x 11 3/8 in. (11.4 x 28.9 cm),"Bequest of Cora Timken Burnett, 1956",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/75088,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -57.51.38.7,false,true,75089,Asian Art,Folio,,India (Gujarat),,,,,Artist,,Bhadrabahu,"Indian, died ca. 356 B.C.",,Bhadrabahu,Indian,-0356,-0356,15th century,1400,1499,"Ink, opaque watercolor, and gold on paper",(Average size .1–.71): 4 1/2 x 11 3/8 in. (11.4 x 28.9 cm),"Bequest of Cora Timken Burnett, 1956",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/75089,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -57.51.38.8,false,true,75090,Asian Art,Folio,,India (Gujarat),,,,,Artist,,Bhadrabahu,"Indian, died ca. 356 B.C.",,Bhadrabahu,Indian,-0356,-0356,15th century,1400,1499,"Ink, opaque watercolor, and gold on paper",(Average size .1–.71): 4 1/2 x 11 3/8 in. (11.4 x 28.9 cm),"Bequest of Cora Timken Burnett, 1956",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/75090,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -57.51.38.9,false,true,75091,Asian Art,Folio,,India (Gujarat),,,,,Artist,,Bhadrabahu,"Indian, died ca. 356 B.C.",,Bhadrabahu,Indian,-0356,-0356,15th century,1400,1499,"Ink, opaque watercolor, and gold on paper",(Average size .1–.71): 4 1/2 x 11 3/8 in. (11.4 x 28.9 cm),"Bequest of Cora Timken Burnett, 1956",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/75091,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -57.51.38.10,false,true,75092,Asian Art,Folio,,India (Gujarat),,,,,Artist,,Bhadrabahu,"Indian, died ca. 356 B.C.",,Bhadrabahu,Indian,-0356,-0356,15th century,1400,1499,"Ink, opaque watercolor, and gold on paper",(Average size .1–.71): 4 1/2 x 11 3/8 in. (11.4 x 28.9 cm),"Bequest of Cora Timken Burnett, 1956",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/75092,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -57.51.38.11,false,true,75093,Asian Art,Folio,,India (Gujarat),,,,,Artist,,Bhadrabahu,"Indian, died ca. 356 B.C.",,Bhadrabahu,Indian,-0356,-0356,15th century,1400,1499,"Ink, opaque watercolor, and gold on paper",(Average size .1–.71): 4 1/2 x 11 3/8 in. (11.4 x 28.9 cm),"Bequest of Cora Timken Burnett, 1956",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/75093,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -57.51.38.12,false,true,75094,Asian Art,Folio,,India (Gujarat),,,,,Artist,,Bhadrabahu,"Indian, died ca. 356 B.C.",,Bhadrabahu,Indian,-0356,-0356,15th century,1400,1499,"Ink, opaque watercolor, and gold on paper",(Average size .1–.71): 4 1/2 x 11 3/8 in. (11.4 x 28.9 cm),"Bequest of Cora Timken Burnett, 1956",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/75094,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -57.51.38.13,false,true,75095,Asian Art,Folio,,India (Gujarat),,,,,Artist,,Bhadrabahu,"Indian, died ca. 356 B.C.",,Bhadrabahu,Indian,-0356,-0356,15th century,1400,1499,"Ink, opaque watercolor, and gold on paper",(Average size .1–.71): 4 1/2 x 11 3/8 in. (11.4 x 28.9 cm),"Bequest of Cora Timken Burnett, 1956",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/75095,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -57.51.38.14,false,true,75096,Asian Art,Folio,,India (Gujarat),,,,,Artist,,Bhadrabahu,"Indian, died ca. 356 B.C.",,Bhadrabahu,Indian,-0356,-0356,15th century,1400,1499,"Ink, opaque watercolor, and gold on paper",(Average size .1–.71): 4 1/2 x 11 3/8 in. (11.4 x 28.9 cm),"Bequest of Cora Timken Burnett, 1956",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/75096,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -57.51.38.15,false,true,75097,Asian Art,Folio,,India (Gujarat),,,,,Artist,,Bhadrabahu,"Indian, died ca. 356 B.C.",,Bhadrabahu,Indian,-0356,-0356,15th century,1400,1499,"Ink, opaque watercolor, and gold on paper",(Average size .1–.71): 4 1/2 x 11 3/8 in. (11.4 x 28.9 cm),"Bequest of Cora Timken Burnett, 1956",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/75097,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -57.51.38.16,false,true,75098,Asian Art,Folio,,India (Gujarat),,,,,Artist,,Bhadrabahu,"Indian, died ca. 356 B.C.",,Bhadrabahu,Indian,-0356,-0356,15th century,1400,1499,"Ink, opaque watercolor, and gold on paper",(Average size .1–.71): 4 1/2 x 11 3/8 in. (11.4 x 28.9 cm),"Bequest of Cora Timken Burnett, 1956",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/75098,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -57.51.38.17,false,true,75099,Asian Art,Folio,,India (Gujarat),,,,,Artist,,Bhadrabahu,"Indian, died ca. 356 B.C.",,Bhadrabahu,Indian,-0356,-0356,15th century,1400,1499,"Ink, opaque watercolor, and gold on paper",(Average size .1–.71): 4 1/2 x 11 3/8 in. (11.4 x 28.9 cm),"Bequest of Cora Timken Burnett, 1956",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/75099,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -57.51.38.18,false,true,75100,Asian Art,Folio,,India (Gujarat),,,,,Artist,,Bhadrabahu,"Indian, died ca. 356 B.C.",,Bhadrabahu,Indian,-0356,-0356,15th century,1400,1499,"Ink, opaque watercolor, and gold on paper",(Average size .1–.71): 4 1/2 x 11 3/8 in. (11.4 x 28.9 cm),"Bequest of Cora Timken Burnett, 1956",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/75100,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -57.51.38.19,false,true,75101,Asian Art,Folio,,India (Gujarat),,,,,Artist,,Bhadrabahu,"Indian, died ca. 356 B.C.",,Bhadrabahu,Indian,-0356,-0356,15th century,1400,1499,"Ink, opaque watercolor, and gold on paper",(Average size .1–.71): 4 1/2 x 11 3/8 in. (11.4 x 28.9 cm),"Bequest of Cora Timken Burnett, 1956",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/75101,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -57.51.38.20,false,true,75102,Asian Art,Folio,,India (Gujarat),,,,,Artist,,Bhadrabahu,"Indian, died ca. 356 B.C.",,Bhadrabahu,Indian,-0356,-0356,15th century,1400,1499,"Ink, opaque watercolor, and gold on paper",(Average size .1–.71): 4 1/2 x 11 3/8 in. (11.4 x 28.9 cm),"Bequest of Cora Timken Burnett, 1956",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/75102,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -57.51.38.21,false,true,75103,Asian Art,Folio,,India (Gujarat),,,,,Artist,,Bhadrabahu,"Indian, died ca. 356 B.C.",,Bhadrabahu,Indian,-0356,-0356,15th century,1400,1499,"Ink, opaque watercolor, and gold on paper",(Average size .1–.71): 4 1/2 x 11 3/8 in. (11.4 x 28.9 cm),"Bequest of Cora Timken Burnett, 1956",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/75103,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -57.51.38.22,false,true,75104,Asian Art,Folio,,India (Gujarat),,,,,Artist,,Bhadrabahu,"Indian, died ca. 356 B.C.",,Bhadrabahu,Indian,-0356,-0356,15th century,1400,1499,"Ink, opaque watercolor, and gold on paper",(Average size .1–.71): 4 1/2 x 11 3/8 in. (11.4 x 28.9 cm),"Bequest of Cora Timken Burnett, 1956",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/75104,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -57.51.38.23,false,true,75105,Asian Art,Folio,,India (Gujarat),,,,,Artist,,Bhadrabahu,"Indian, died ca. 356 B.C.",,Bhadrabahu,Indian,-0356,-0356,15th century,1400,1499,"Ink, opaque watercolor, and gold on paper",(Average size .1–.71): 4 1/2 x 11 3/8 in. (11.4 x 28.9 cm),"Bequest of Cora Timken Burnett, 1956",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/75105,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -57.51.38.24,false,true,75106,Asian Art,Folio,,India (Gujarat),,,,,Artist,,Bhadrabahu,"Indian, died ca. 356 B.C.",,Bhadrabahu,Indian,-0356,-0356,15th century,1400,1499,"Ink, opaque watercolor, and gold on paper",(Average size .1–.71): 4 1/2 x 11 3/8 in. (11.4 x 28.9 cm),"Bequest of Cora Timken Burnett, 1956",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/75106,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -57.51.38.25,false,true,75107,Asian Art,Folio,,India (Gujarat),,,,,Artist,,Bhadrabahu,"Indian, died ca. 356 B.C.",,Bhadrabahu,Indian,-0356,-0356,15th century,1400,1499,"Ink, opaque watercolor, and gold on paper",(Average size .1–.71): 4 1/2 x 11 3/8 in. (11.4 x 28.9 cm),"Bequest of Cora Timken Burnett, 1956",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/75107,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -57.51.38.26,false,true,75108,Asian Art,Folio,,India (Gujarat),,,,,Artist,,Bhadrabahu,"Indian, died ca. 356 B.C.",,Bhadrabahu,Indian,-0356,-0356,15th century,1400,1499,"Ink, opaque watercolor, and gold on paper",(Average size .1–.71): 4 1/2 x 11 3/8 in. (11.4 x 28.9 cm),"Bequest of Cora Timken Burnett, 1956",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/75108,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -57.51.38.27,false,true,75109,Asian Art,Folio,,India (Gujarat),,,,,Artist,,Bhadrabahu,"Indian, died ca. 356 B.C.",,Bhadrabahu,Indian,-0356,-0356,15th century,1400,1499,"Ink, opaque watercolor, and gold on paper",(Average size .1–.71): 4 1/2 x 11 3/8 in. (11.4 x 28.9 cm),"Bequest of Cora Timken Burnett, 1956",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/75109,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -57.51.38.28,false,true,75110,Asian Art,Folio,,India (Gujarat),,,,,Artist,,Bhadrabahu,"Indian, died ca. 356 B.C.",,Bhadrabahu,Indian,-0356,-0356,15th century,1400,1499,"Ink, opaque watercolor, and gold on paper",(Average size .1–.71): 4 1/2 x 11 3/8 in. (11.4 x 28.9 cm),"Bequest of Cora Timken Burnett, 1956",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/75110,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -57.51.38.29,false,true,75111,Asian Art,Folio,,India (Gujarat),,,,,Artist,,Bhadrabahu,"Indian, died ca. 356 B.C.",,Bhadrabahu,Indian,-0356,-0356,15th century,1400,1499,"Ink, opaque watercolor, and gold on paper",(Average size .1–.71): 4 1/2 x 11 3/8 in. (11.4 x 28.9 cm),"Bequest of Cora Timken Burnett, 1956",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/75111,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -57.51.38.30,false,true,75112,Asian Art,Folio,,India (Gujarat),,,,,Artist,,Bhadrabahu,"Indian, died ca. 356 B.C.",,Bhadrabahu,Indian,-0356,-0356,15th century,1400,1499,"Ink, opaque watercolor, and gold on paper",(Average size .1–.71): 4 1/2 x 11 3/8 in. (11.4 x 28.9 cm),"Bequest of Cora Timken Burnett, 1956",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/75112,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -57.51.38.31,false,true,75113,Asian Art,Folio,,India (Gujarat),,,,,Artist,,Bhadrabahu,"Indian, died ca. 356 B.C.",,Bhadrabahu,Indian,-0356,-0356,15th century,1400,1499,"Ink, opaque watercolor, and gold on paper",(Average size .1–.71): 4 1/2 x 11 3/8 in. (11.4 x 28.9 cm),"Bequest of Cora Timken Burnett, 1956",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/75113,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -57.51.38.32,false,true,75114,Asian Art,Folio,,India (Gujarat),,,,,Artist,,Bhadrabahu,"Indian, died ca. 356 B.C.",,Bhadrabahu,Indian,-0356,-0356,15th century,1400,1499,"Ink, opaque watercolor, and gold on paper",(Average size .1–.71): 4 1/2 x 11 3/8 in. (11.4 x 28.9 cm),"Bequest of Cora Timken Burnett, 1956",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/75114,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -57.51.38.33,false,true,75115,Asian Art,Folio,,India (Gujarat),,,,,Artist,,Bhadrabahu,"Indian, died ca. 356 B.C.",,Bhadrabahu,Indian,-0356,-0356,15th century,1400,1499,"Ink, opaque watercolor, and gold on paper",(Average size .1–.71): 4 1/2 x 11 3/8 in. (11.4 x 28.9 cm),"Bequest of Cora Timken Burnett, 1956",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/75115,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -57.51.38.34,false,true,75116,Asian Art,Folio,,India (Gujarat),,,,,Artist,,Bhadrabahu,"Indian, died ca. 356 B.C.",,Bhadrabahu,Indian,-0356,-0356,15th century,1400,1499,"Ink, opaque watercolor, and gold on paper",(Average size .1–.71): 4 1/2 x 11 3/8 in. (11.4 x 28.9 cm),"Bequest of Cora Timken Burnett, 1956",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/75116,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -57.51.38.35,false,true,75117,Asian Art,Folio,,India (Gujarat),,,,,Artist,,Bhadrabahu,"Indian, died ca. 356 B.C.",,Bhadrabahu,Indian,-0356,-0356,15th century,1400,1499,"Ink, opaque watercolor, and gold on paper",(Average size .1–.71): 4 1/2 x 11 3/8 in. (11.4 x 28.9 cm),"Bequest of Cora Timken Burnett, 1956",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/75117,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -57.51.38.36,false,true,75118,Asian Art,Folio,,India (Gujarat),,,,,Artist,,Bhadrabahu,"Indian, died ca. 356 B.C.",,Bhadrabahu,Indian,-0356,-0356,15th century,1400,1499,"Ink, opaque watercolor, and gold on paper",(Average size .1–.71): 4 1/2 x 11 3/8 in. (11.4 x 28.9 cm),"Bequest of Cora Timken Burnett, 1956",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/75118,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -57.51.38.37,false,true,75119,Asian Art,Folio,,India (Gujarat),,,,,Artist,,Bhadrabahu,"Indian, died ca. 356 B.C.",,Bhadrabahu,Indian,-0356,-0356,15th century,1400,1499,"Ink, opaque watercolor, and gold on paper",(Average size .1–.71): 4 1/2 x 11 3/8 in. (11.4 x 28.9 cm),"Bequest of Cora Timken Burnett, 1956",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/75119,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -57.51.38.38,false,true,75120,Asian Art,Folio,,India (Gujarat),,,,,Artist,,Bhadrabahu,"Indian, died ca. 356 B.C.",,Bhadrabahu,Indian,-0356,-0356,15th century,1400,1499,"Ink, opaque watercolor, and gold on paper",(Average size .1–.71): 4 1/2 x 11 3/8 in. (11.4 x 28.9 cm),"Bequest of Cora Timken Burnett, 1956",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/75120,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -57.51.38.39,false,true,75121,Asian Art,Folio,,India (Gujarat),,,,,Artist,,Bhadrabahu,"Indian, died ca. 356 B.C.",,Bhadrabahu,Indian,-0356,-0356,15th century,1400,1499,"Ink, opaque watercolor, and gold on paper",(Average size .1–.71): 4 1/2 x 11 3/8 in. (11.4 x 28.9 cm),"Bequest of Cora Timken Burnett, 1956",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/75121,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -57.51.38.40,false,true,75122,Asian Art,Folio,,India (Gujarat),,,,,Artist,,Bhadrabahu,"Indian, died ca. 356 B.C.",,Bhadrabahu,Indian,-0356,-0356,15th century,1400,1499,"Ink, opaque watercolor, and gold on paper",(Average size .1–.71): 4 1/2 x 11 3/8 in. (11.4 x 28.9 cm),"Bequest of Cora Timken Burnett, 1956",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/75122,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -57.51.38.41,false,true,75123,Asian Art,Folio,,India (Gujarat),,,,,Artist,,Bhadrabahu,"Indian, died ca. 356 B.C.",,Bhadrabahu,Indian,-0356,-0356,15th century,1400,1499,"Ink, opaque watercolor, and gold on paper",(Average size .1–.71): 4 1/2 x 11 3/8 in. (11.4 x 28.9 cm),"Bequest of Cora Timken Burnett, 1956",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/75123,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -57.51.38.42,false,true,75124,Asian Art,Folio,,India (Gujarat),,,,,Artist,,Bhadrabahu,"Indian, died ca. 356 B.C.",,Bhadrabahu,Indian,-0356,-0356,15th century,1400,1499,"Ink, opaque watercolor, and gold on paper",(Average size .1–.71): 4 1/2 x 11 3/8 in. (11.4 x 28.9 cm),"Bequest of Cora Timken Burnett, 1956",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/75124,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -57.51.38.43,false,true,75125,Asian Art,Folio,,India (Gujarat),,,,,Artist,,Bhadrabahu,"Indian, died ca. 356 B.C.",,Bhadrabahu,Indian,-0356,-0356,15th century,1400,1499,"Ink, opaque watercolor, and gold on paper",(Average size .1–.71): 4 1/2 x 11 3/8 in. (11.4 x 28.9 cm),"Bequest of Cora Timken Burnett, 1956",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/75125,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -57.51.38.44,false,true,75126,Asian Art,Folio,,India (Gujarat),,,,,Artist,,Bhadrabahu,"Indian, died ca. 356 B.C.",,Bhadrabahu,Indian,-0356,-0356,15th century,1400,1499,"Ink, opaque watercolor, and gold on paper",(Average size .1–.71): 4 1/2 x 11 3/8 in. (11.4 x 28.9 cm),"Bequest of Cora Timken Burnett, 1956",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/75126,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -57.51.38.45,false,true,75127,Asian Art,Folio,,India (Gujarat),,,,,Artist,,Bhadrabahu,"Indian, died ca. 356 B.C.",,Bhadrabahu,Indian,-0356,-0356,15th century,1400,1499,"Ink, opaque watercolor, and gold on paper",(Average size .1–.71): 4 1/2 x 11 3/8 in. (11.4 x 28.9 cm),"Bequest of Cora Timken Burnett, 1956",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/75127,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -57.51.38.46,false,true,75128,Asian Art,Folio,,India (Gujarat),,,,,Artist,,Bhadrabahu,"Indian, died ca. 356 B.C.",,Bhadrabahu,Indian,-0356,-0356,15th century,1400,1499,"Ink, opaque watercolor, and gold on paper",(Average size .1–.71): 4 1/2 x 11 3/8 in. (11.4 x 28.9 cm),"Bequest of Cora Timken Burnett, 1956",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/75128,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -57.51.38.47,false,true,75129,Asian Art,Folio,,India (Gujarat),,,,,Artist,,Bhadrabahu,"Indian, died ca. 356 B.C.",,Bhadrabahu,Indian,-0356,-0356,15th century,1400,1499,"Ink, opaque watercolor, and gold on paper",(Average size .1–.71): 4 1/2 x 11 3/8 in. (11.4 x 28.9 cm),"Bequest of Cora Timken Burnett, 1956",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/75129,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -57.51.38.48,false,true,75130,Asian Art,Folio,,India (Gujarat),,,,,Artist,,Bhadrabahu,"Indian, died ca. 356 B.C.",,Bhadrabahu,Indian,-0356,-0356,15th century,1400,1499,"Ink, opaque watercolor, and gold on paper",(Average size .1–.71): 4 1/2 x 11 3/8 in. (11.4 x 28.9 cm),"Bequest of Cora Timken Burnett, 1956",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/75130,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -57.51.38.49,false,true,75131,Asian Art,Folio,,India (Gujarat),,,,,Artist,,Bhadrabahu,"Indian, died ca. 356 B.C.",,Bhadrabahu,Indian,-0356,-0356,15th century,1400,1499,"Ink, opaque watercolor, and gold on paper",(Average size .1–.71): 4 1/2 x 11 3/8 in. (11.4 x 28.9 cm),"Bequest of Cora Timken Burnett, 1956",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/75131,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -57.51.38.50,false,true,75132,Asian Art,Folio,,India (Gujarat),,,,,Artist,,Bhadrabahu,"Indian, died ca. 356 B.C.",,Bhadrabahu,Indian,-0356,-0356,15th century,1400,1499,"Ink, opaque watercolor, and gold on paper",(Average size .1–.71): 4 1/2 x 11 3/8 in. (11.4 x 28.9 cm),"Bequest of Cora Timken Burnett, 1956",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/75132,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -57.51.38.51,false,true,75133,Asian Art,Folio,,India (Gujarat),,,,,Artist,,Bhadrabahu,"Indian, died ca. 356 B.C.",,Bhadrabahu,Indian,-0356,-0356,15th century,1400,1499,"Ink, opaque watercolor, and gold on paper",(Average size .1–.71): 4 1/2 x 11 3/8 in. (11.4 x 28.9 cm),"Bequest of Cora Timken Burnett, 1956",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/75133,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -57.51.38.52,false,true,75134,Asian Art,Folio,,India (Gujarat),,,,,Artist,,Bhadrabahu,"Indian, died ca. 356 B.C.",,Bhadrabahu,Indian,-0356,-0356,15th century,1400,1499,"Ink, opaque watercolor, and gold on paper",(Average size .1–.71): 4 1/2 x 11 3/8 in. (11.4 x 28.9 cm),"Bequest of Cora Timken Burnett, 1956",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/75134,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -57.51.38.53,false,true,75135,Asian Art,Folio,,India (Gujarat),,,,,Artist,,Bhadrabahu,"Indian, died ca. 356 B.C.",,Bhadrabahu,Indian,-0356,-0356,15th century,1400,1499,"Ink, opaque watercolor, and gold on paper",(Average size .1–.71): 4 1/2 x 11 3/8 in. (11.4 x 28.9 cm),"Bequest of Cora Timken Burnett, 1956",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/75135,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -57.51.38.54,false,true,75136,Asian Art,Folio,,India (Gujarat),,,,,Artist,,Bhadrabahu,"Indian, died ca. 356 B.C.",,Bhadrabahu,Indian,-0356,-0356,15th century,1400,1499,"Ink, opaque watercolor, and gold on paper",(Average size .1–.71): 4 1/2 x 11 3/8 in. (11.4 x 28.9 cm),"Bequest of Cora Timken Burnett, 1956",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/75136,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -57.51.38.55,false,true,75137,Asian Art,Folio,,India (Gujarat),,,,,Artist,,Bhadrabahu,"Indian, died ca. 356 B.C.",,Bhadrabahu,Indian,-0356,-0356,15th century,1400,1499,"Ink, opaque watercolor, and gold on paper",(Average size .1–.71): 4 1/2 x 11 3/8 in. (11.4 x 28.9 cm),"Bequest of Cora Timken Burnett, 1956",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/75137,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -57.51.38.56,false,true,75138,Asian Art,Folio,,India (Gujarat),,,,,Artist,,Bhadrabahu,"Indian, died ca. 356 B.C.",,Bhadrabahu,Indian,-0356,-0356,15th century,1400,1499,"Ink, opaque watercolor, and gold on paper",(Average size .1–.71): 4 1/2 x 11 3/8 in. (11.4 x 28.9 cm),"Bequest of Cora Timken Burnett, 1956",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/75138,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -57.51.38.57,false,true,75139,Asian Art,Folio,,India (Gujarat),,,,,Artist,,Bhadrabahu,"Indian, died ca. 356 B.C.",,Bhadrabahu,Indian,-0356,-0356,15th century,1400,1499,"Ink, opaque watercolor, and gold on paper",(Average size .1–.71): 4 1/2 x 11 3/8 in. (11.4 x 28.9 cm),"Bequest of Cora Timken Burnett, 1956",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/75139,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -57.51.38.58,false,true,75140,Asian Art,Folio,,India (Gujarat),,,,,Artist,,Bhadrabahu,"Indian, died ca. 356 B.C.",,Bhadrabahu,Indian,-0356,-0356,15th century,1400,1499,"Ink, opaque watercolor, and gold on paper",(Average size .1–.71): 4 1/2 x 11 3/8 in. (11.4 x 28.9 cm),"Bequest of Cora Timken Burnett, 1956",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/75140,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -57.51.38.59,false,true,75141,Asian Art,Folio,,India (Gujarat),,,,,Artist,,Bhadrabahu,"Indian, died ca. 356 B.C.",,Bhadrabahu,Indian,-0356,-0356,15th century,1400,1499,"Ink, opaque watercolor, and gold on paper",(Average size .1–.71): 4 1/2 x 11 3/8 in. (11.4 x 28.9 cm),"Bequest of Cora Timken Burnett, 1956",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/75141,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -57.51.38.60,false,true,75142,Asian Art,Folio,,India (Gujarat),,,,,Artist,,Bhadrabahu,"Indian, died ca. 356 B.C.",,Bhadrabahu,Indian,-0356,-0356,15th century,1400,1499,"Ink, opaque watercolor, and gold on paper",(Average size .1–.71): 4 1/2 x 11 3/8 in. (11.4 x 28.9 cm),"Bequest of Cora Timken Burnett, 1956",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/75142,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -57.51.38.61,false,true,75143,Asian Art,Folio,,India (Gujarat),,,,,Artist,,Bhadrabahu,"Indian, died ca. 356 B.C.",,Bhadrabahu,Indian,-0356,-0356,15th century,1400,1499,"Ink, opaque watercolor, and gold on paper",(Average size .1–.71): 4 1/2 x 11 3/8 in. (11.4 x 28.9 cm),"Bequest of Cora Timken Burnett, 1956",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/75143,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -57.51.38.63,false,true,75145,Asian Art,Folio,,India (Gujarat),,,,,Artist,,Bhadrabahu,"Indian, died ca. 356 B.C.",,Bhadrabahu,Indian,-0356,-0356,15th century,1400,1499,"Ink, opaque watercolor, and gold on paper",(Average size .1–.71): 4 1/2 x 11 3/8 in. (11.4 x 28.9 cm),"Bequest of Cora Timken Burnett, 1956",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/75145,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -57.51.38.64,false,true,75146,Asian Art,Folio,,India (Gujarat),,,,,Artist,,Bhadrabahu,"Indian, died ca. 356 B.C.",,Bhadrabahu,Indian,-0356,-0356,15th century,1400,1499,"Ink, opaque watercolor, and gold on paper",(Average size .1–.71): 4 1/2 x 11 3/8 in. (11.4 x 28.9 cm),"Bequest of Cora Timken Burnett, 1956",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/75146,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -57.51.38.65,false,true,75147,Asian Art,Folio,,India (Gujarat),,,,,Artist,,Bhadrabahu,"Indian, died ca. 356 B.C.",,Bhadrabahu,Indian,-0356,-0356,15th century,1400,1499,"Ink, opaque watercolor, and gold on paper",(Average size .1–.71): 4 1/2 x 11 3/8 in. (11.4 x 28.9 cm),"Bequest of Cora Timken Burnett, 1956",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/75147,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -57.51.38.66,false,true,75148,Asian Art,Folio,,India (Gujarat),,,,,Artist,,Bhadrabahu,"Indian, died ca. 356 B.C.",,Bhadrabahu,Indian,-0356,-0356,15th century,1400,1499,"Ink, opaque watercolor, and gold on paper",(Average size .1–.71): 4 1/2 x 11 3/8 in. (11.4 x 28.9 cm),"Bequest of Cora Timken Burnett, 1956",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/75148,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -57.51.38.67,false,true,75149,Asian Art,Folio,,India (Gujarat),,,,,Artist,,Bhadrabahu,"Indian, died ca. 356 B.C.",,Bhadrabahu,Indian,-0356,-0356,15th century,1400,1499,"Ink, opaque watercolor, and gold on paper",(Average size .1–.71): 4 1/2 x 11 3/8 in. (11.4 x 28.9 cm),"Bequest of Cora Timken Burnett, 1956",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/75149,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -57.51.38.68,false,true,75150,Asian Art,Folio,,India (Gujarat),,,,,Artist,,Bhadrabahu,"Indian, died ca. 356 B.C.",,Bhadrabahu,Indian,-0356,-0356,15th century,1400,1499,"Ink, opaque watercolor, and gold on paper",(Average size .1–.71): 4 1/2 x 11 3/8 in. (11.4 x 28.9 cm),"Bequest of Cora Timken Burnett, 1956",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/75150,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -57.51.38.69,false,true,75151,Asian Art,Folio,,India (Gujarat),,,,,Artist,,Bhadrabahu,"Indian, died ca. 356 B.C.",,Bhadrabahu,Indian,-0356,-0356,15th century,1400,1499,"Ink, opaque watercolor, and gold on paper",(Average size .1–.71): 4 1/2 x 11 3/8 in. (11.4 x 28.9 cm),"Bequest of Cora Timken Burnett, 1956",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/75151,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -57.51.38.70,false,true,75152,Asian Art,Folio,,India (Gujarat),,,,,Artist,,Bhadrabahu,"Indian, died ca. 356 B.C.",,Bhadrabahu,Indian,-0356,-0356,15th century,1400,1499,"Ink, opaque watercolor, and gold on paper",(Average size .1–.71): 4 1/2 x 11 3/8 in. (11.4 x 28.9 cm),"Bequest of Cora Timken Burnett, 1956",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/75152,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -57.51.38.71,false,true,75153,Asian Art,Folio,,India (Gujarat),,,,,Artist,,Bhadrabahu,"Indian, died ca. 356 B.C.",,Bhadrabahu,Indian,-0356,-0356,15th century,1400,1499,"Ink, opaque watercolor, and gold on paper",Overall (each): 4 1/2 × 11 3/8 in. (11.4 × 28.9 cm),"Bequest of Cora Timken Burnett, 1956",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/75153,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -15.96.4,false,true,54912,Asian Art,Painting,,Korea,Joseon dynasty (1392–1910),,,,Artist,,Zhao Songxue,"Korean, 17th century",,Zhao Songxue,Korean,1600,1699,17th century,1600,1699,Framed painting; ink on silk,23 5/8 x 14 3/8 in. (60 x 36.5 cm),"Rogers Fund, 1915",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/54912,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.123,false,true,38448,Asian Art,Figure,,Tibet,,,,,Artist,Attributed to,Chosying Dorje (the Tenth Karmapa),1604–1674,,Chosying Dorje,Tibetan,1604,1674,17th century,1600,1699,Ivory,H. 6 3/4 in. (17.1 cm); W. 3 3/8 in. (8.6 cm); D. 3 1/2 in. (8.9 cm); Wt. 1 lb (.5 kg),"Louis V. Bell and Dodge Funds, 1972",,,,,,,,,,,,Sculpture,,http://www.metmuseum.org/art/collection/search/38448,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -15.96.2,false,true,40429,Asian Art,Painting,,Korea,Joseon dynasty (1392–1910),,,,Artist,,Samoje,active late 18th century,,Samoje,Korean,1771,1779,late 18th century,1767,1799,Framed painting; ink and color on silk,17 7/8 x 10 7/8 in. (45.4 x 27.6 cm),"Rogers Fund, 1915",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/40429,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -15.96.6,false,true,40432,Asian Art,Painting,,Korea,Joseon dynasty (1392–1910),,,,Artist,,Samoje,active late 18th century,,Samoje,Korean,1771,1779,late 18th century,1767,1799,Framed painting; ink and color on silk,17 7/8 x 10 7/8 in. (45.4 x 27.6 cm),"Rogers Fund, 1915",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/40432,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2006.104,false,true,73188,Asian Art,Hanging scroll,,Korea,Joseon dynasty (1392–1910),,,,Artist,,Kim Sugyu,"Korean, active late 18th–early 19th century",,Kim Sugyu,Korean,1767,1833,late 18th century,1767,1799,Hanging scroll; ink and color on cotton,Image: 10 x 13 3/4 in. (25.4 x 34.9 cm) Overall with mounting: 43 1/2 x 18 7/8 in. (110.5 x 47.9 cm) Overall with knobs: 43 1/2 x 21 1/8 in. (110.5 x 53.7 cm),"Purchase, Friends of Asian Art Gifts, 2006",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/73188,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2008.359.29,false,true,74683,Asian Art,Painting,,"India (Pahari Hills, Kangra)",,,,,Artist|Artist,First generation after|Possibly,Nainsukh|Fattu,active ca. 1735–78,,Nainsukh|Fattu,Indian,1725,1778,ca. 1785–90,1785,1790,Ochre on paper,Image (sight): 5 1/2 x 8 1/8 in. (14 x 20.6 cm) Framed: 16 x 20 in. (40.6 x 50.8 cm),"Gift of Subhash Kapoor, in memory of his parents, Smt Shashi Kanta and Shree Parshotam Ram Kapoor, 2008",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/74683,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2004.403,false,true,72589,Asian Art,Painting,,"India (Rajasthan, Mewar)",,,,,Artist|Artist,,Stipple Master|Jai Ram,"Indian, active ca. 1690–1715",(?),Stipple Master|Ram Jai,Indian,1680,1725,ca. 1712,1712,1712,"Ink, opaque watercolor, gold, and Basra pearls on paper",8 1/4 x 7 1/2 in. (21 x 19 cm),"Cynthia Hazen Polsky and Leon B. Polsky Fund, 2004",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/72589,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2008.359.9,false,true,74656,Asian Art,Drawing,,"India (Pahari Hills, Guler)",,,,,Artist|Artist,Attributed to|or attributed to,Pandit Seu|Manaku,Indian|active ca. 1725–60,,Seu Pandit|Manaku,Indian,1715,1770,late 18th century,1767,1799,Ink on paper,Image (sight): 6 1/4 x 5 5/8 in. (15.9 x 14.3 cm),"Gift of Subhash Kapoor, in memory of his parents, Smt Shashi Kanta and Shree Parshotam Ram Kapoor, 2008",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/74656,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"1977.375.31a, b",false,true,64046,Asian Art,Rubbing,張旭 「肚痛帖」; 懷素 「寄邊衣」詩|Ji bianyi|Letter about a Stomachache (Du tong tie),China,,,,,Calligrapher,,Zhang Xu,"Chinese, ca. 675–759",,Zhang Xu,Chinese,0665,0769,19th century rubbing of a 10th century stone carving,1800,1899,Ink on paper,"a (sheet, pre-conservation): 13 × 20 1/2 in. (33 × 52 cm) b (sheet, pre-conservation): 12 3/16 × 20 1/2 in. (31 × 52 cm)","Seymour and Rogers Funds, 1977",,,,,,,,,,,,Rubbing,,http://www.metmuseum.org/art/collection/search/64046,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1991.380,false,true,39899,Asian Art,Rubbing,東晉 王羲之 十七日帖 十三世紀拓本|On the Seventeenth Day,China,,,,,Calligrapher,,Wang Xizhi,"Chinese, ca. 303–ca. 361",,WANG XIZHI,Chinese,0303,0361,13th century rubbing of a 4th century text,1200,1299,Album of thirty leaves; ink on paper,Each leaf: 9 5/8 x 5 in. (24.4 x 12.7 cm),"Gift of Mr. and Mrs. Wan-go H. C. Weng, 1991",,,,,,,,,,,,Rubbing,,http://www.metmuseum.org/art/collection/search/39899,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.22,false,true,36145,Asian Art,Handscroll,,China,Qing dynasty (1644–1911),,,,Calligrapher|Calligrapher,,Wang Shu|Yan Zhenqing,"Chinese, 1688–1743|Chinese, 709–785",,Wang Shu|Yan Zhenqing,Chinese|Chinese,1688 |0709,1743 |0785,dated 1729,1729,1729,Handscroll in six sections; ink on paper,Image: 13 7/8 x 282 5/8 in. (35.2 x 717.9 cm) Overall with mounting: 14 1/2 x 457 1/8 in. (36.8 x 1161.1 cm),"Purchase, Bequest of Dorothy Graham Bennett, 1986",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/36145,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.267.255,false,true,49665,Asian Art,Folding fan mounted as an album leaf,近代 丁輔之 荔枝 扇面|Lychees,China,,,,,Artist|Calligrapher,,Ding Fuzhi|Shou Xi,"Chinese, 1879–1946|Chinese",,Ding Fuzhi|Shou Xi,Chinese|Chinese,1879,1946,dated 1941,1941,1941,Folding fan mounted as an album leaf; ink and color on alum paper,7 3/4 x 22 in. (19.7 x 55.9 cm),"Gift of Robert Hatfield Ellsworth, in memory of La Ferne Hatfield Ellsworth, 1986",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49665,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1980.426.1a–l,false,true,36130,Asian Art,Album,清 倣龔賢 十二月令山水圖 冊|Landscapes of the Twelve Months,China,Qing dynasty (1644–1911),,,,Artist|Calligrapher,,Gong Xian|Zhu Xia,"Chinese, 1619–1689|Chinese",,GONG XIAN|Zhu Xia,Chinese|Chinese,1619,1689,ca. 1685,1675,1695,Album of twelve painting leaves; ink on paper,11 7/8 x 24 3/4 in. (30.2 x 62.9 cm),"Gift of Douglas Dillon, 1980",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/36130,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1987.408.1a–j,false,true,36435,Asian Art,Album,,China,Qing dynasty (1644–1911),,,,Artist|Calligrapher,,Zheng Min|Wang Quan,"Chinese, 1633–1683|Chinese",,Zheng Min|Wang Quan,Chinese|Chinese,1633,1683,dated 1688,1688,1688,Album of ten paintings; ink on paper,Each leaf: 10 1/4 x 7 1/2 in. (26 x 19.1 cm),"Edward Elliott Family Collection; Gift of Douglas Dillon, 1987",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/36435,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -22.92,false,true,36036,Asian Art,Hanging scroll,,China,Qing dynasty (1644–1911),,,,Artist|Calligrapher,Attributed to,Jiang Tingxi|Jiang Wuyang,"Chinese, 1669–1732|Chinese",,Jiang Tingxi|Jiang Wuyang,Chinese|Chinese,1669,1732,dated 1724,1724,1724,Hanging scroll; ink and color on silk,44 1/2 x 23 3/4 in. (113 x 60.3 cm),"Gift of Mrs. Anna Woerishoffer, 1922",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/36036,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -55.212,false,true,36072,Asian Art,Handscroll,清 程正揆 江山臥遊圖 卷|Dream Landscape,China,Qing dynasty (1644–1911),,,,Artist|Calligrapher,,Cheng Zhengkui|Wu Dacheng,"Chinese, 1604–1676|Chinese, 1835–1902",(Frontispiece),Cheng Zhengkui|Wu Dacheng,Chinese|Chinese,1604 |1835,1676 |1902,dated 1674,1600,1750,Handscroll; ink and color on silk,9 1/8 x 69 3/4 in. (23.2 x 177.2 cm),"Gift of Harry Lenart, 1955",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/36072,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1983.609,false,true,49133,Asian Art,Hanging scroll,清 倣龔賢 雲山隱居圖 軸|Dwelling among Mountains and Clouds,China,Qing dynasty (1644–1911),,,,Artist|Calligrapher,Inscribed by,Gong Xian|Gong Xian,"Chinese, 1619–1689|Chinese, 1619–1689",,GONG XIAN|GONG XIAN,Chinese|Chinese,1619 |1619,1689 |1689,dated 1685,1685,1685,Hanging scroll; ink on paper,Image: 128 x 44 1/4 in. (325.1 x 112.4 cm) Overall with mounting: 163 x 55 1/4 in. (414 x 140.3 cm) Overall with knobs: 163 x 59 3/4 in. (414 x 151.8 cm),"Purchase, The Dillon Fund Gift, 1983",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49133,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -69.242.16–.21,false,true,65620,Asian Art,Album,清 倣龔賢 山水圖 冊|Landscapes,China,Qing dynasty (1644–1911),,,,Calligrapher|Artist,Inscribed by,Gong Xian|Gong Xian,"Chinese, 1619–1689|Chinese, 1619–1689",,GONG XIAN|GONG XIAN,Chinese|Chinese,1619 |1619,1689 |1689,ca. 1688,1678,1698,Album of six paintings; ink on paper,Each: 8 3/4 x 17 3/8 in. (22.2 x 44.1 cm),"The Sackler Fund, 1969",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/65620,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1978.13,false,true,40021,Asian Art,Hanging scroll,清 王翬 倣李成雪霽圖 軸 紙本|Snow Clearing: Landscape after Li Cheng,China,Qing dynasty (1644–1911),,,,Artist|Calligrapher,Inscribed by,Wang Hui|Wang Hui,"Chinese, 1632–1717|Chinese, 1632–1717",,Wang Hui|Wang Hui,Chinese|Chinese,1632 |1632,1717 |1717,dated 1669,1669,1669,Hanging scroll; ink and color on paper,Image: 44 3/8 x 14 1/8 in. (112.7 x 35.9 cm) Overall with mounting: 89 1/4 x 20 7/8 in. (226.7 x 53 cm) Overall with knobs: 89 1/4 x 24 1/8 in. (226.7 x 61.3 cm),"Ex coll.: C. C. Wang Family, Gift of Mr. and Mrs. Earl Morse, in honor of Professor Wen Fong, 1978",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/40021,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.267.30,false,true,49453,Asian Art,Hanging scroll,清 王緣 趙之謙肖像 軸|Portrait of Zhao Zhiqian,China,Qing dynasty (1644–1911),,,,Artist|Calligrapher,,Wang Yuan|Zhao Zhiqian,"Chinese, active ca. 1862–1908|Chinese, 1829–1884",,Wang Yuan|Zhao Zhiqian,Chinese|Chinese,1862 |1829,1908 |1884,dated 1871,1871,1871,Hanging scroll; ink and color on paper,41 3/4 x 13 1/4 in. (106 x 33.7 cm),"Gift of Robert Hatfield Ellsworth, in memory of La Ferne Hatfield Ellsworth, 1986",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49453,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.134,false,true,49134,Asian Art,Handscroll,清 柳堉 溪山行旅圖 卷|Traveling Amid Streams and Mountains,China,Qing dynasty (1644–1911),,,,Artist|Calligrapher,Inscribed by,Liu Yu|Liu Yu,"Chinese, 1620–after 1689|Chinese, 1620–after 1689",,Liu Yu|Liu Yu,Chinese|Chinese,1640 |1640,1700 |1700,dated 1680,1680,1680,Handscroll; ink on paper,10 3/8 x 200 1/4 in. (26.4 x 508.6 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49134,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2000.665.2a–p,false,true,65075,Asian Art,Album,王翬、楊晉、顧昉、王雲、徐玫 仿古山水圖 冊 紙本|Landscapes after old masters,China,Qing dynasty (1644–1911),,,,Artist|Artist|Artist|Artist|Artist,"leaves a, o, p by|leaves e,f,g,h,i,j by|leaves k,l,m,n by|leaf b by|leaves c, d by",Wang Hui|Yang Jin|Gu Fang|Xu Mei|Wang Yun,"Chinese, 1632–1717|Chinese, 1644–1728|Chinese, active ca. 1690–1720|Chinese, active ca. 1690–1722|Chinese, 1652–after 1735",,Wang Hui|Yang Jin|Gu Fang|Xu Mei|Wang Yun,Chinese,1632 |1644 |1690 |1690 |1652,1717 |1728 |1720 |1722 |1735,dated 1692,1692,1692,Album of sixteen leaves; ink and color on paper,Image (each leaf): 11 x 12 1/8 in. (27.9 x 30.8 cm),"Gift of Marie-Hélène and Guy A. Weill, 2000",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/65075,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.33,false,true,41468,Asian Art,Handscroll,元 吳鎮 蘆灘釣艇圖 卷|Fisherman,China,Yuan dynasty (1271–1368),,,,Artist|Calligrapher,,Wu Zhen|Wu Zhen,"Chinese, 1280–1354|Chinese, 1280–1354",,Wu Zhen|Wu Zhen,Chinese|Chinese,1280 |1280,1354 |1354,ca. 1350,1340,1354,Handscroll; ink on paper,Image: 12 1/4 x 21 3/16 in. (31.1 x 53.8 cm) Overall with mounting: 13 1/4 x 136 1/2 in. (33.7 x 346.7 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/41468,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1988.135,false,true,40507,Asian Art,Handscroll,元 趙孟頫 趙雍 趙麟 吳興趙氏三世人馬圖 卷|Grooms and Horses,China,Yuan dynasty (1271–1368),,,,Artist|Artist|Artist,,Zhao Mengfu|Zhao Yong|Zhao Lin,"Chinese, 1254–1322|Chinese, 1289–after 1360|Chinese, active second half of the 14th century",,Zhao Mengfu|Zhao Yong|ZHAO LIN,Chinese|Chinese|Chinese,1254 |1289 |1350,1322 |1360 |1399,date 1296 and 1359,1296,1359,Handscroll; ink and color on paper,Image: 11 7/8 x 70 1/8 in. (30.2 x 178.1 cm) Overall with mounting: 12 1/4 in. x 29 ft. 2 1/2 in. (31.1 x 890.3 cm),"Gift of John M. Crawford Jr., 1988",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/40507,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1984.475.1,false,true,40052,Asian Art,Handscroll,"南宋 馬和之 詩經小雅鴻雁之什六篇圖 卷|Courtly Odes, Beginning with ""Wild Geese""",China,Southern Song dynasty (1127–1279),,,,Artist|Calligrapher,,Ma Hezhi|Emperor Gaozong,"Chinese, ca. 1130–ca. 1170|Chinese, 1107–1187, r. 1127–1162",and Assistants,MA HEZHI|Gaozong Emperor,Chinese|Chinese,1130 |1107,1170 |1187,12th century,1130,1170,Handscroll in six sections; ink and color on silk,Overall with mounting: 12 3/4 in. × 42 ft. 9 3/4 in. (32.4 × 1304.9 cm),"Edward Elliott Family Collection, Gift of Douglas Dillon, 1984",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/40052,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -13.100.84,false,true,35995,Asian Art,Folding fan mounted as an album leaf,,China,Ming dynasty (1368–1644),,,,Calligrapher,,Wen Congjian,"Chinese, 1574–1648",,Wen Congjian,Chinese,1574,1648,,1575,1644,Folding fan mounted as an album leaf; ink on gold paper,6 1/2 x 20 31/32 in. (16.5 x 53.3 cm),"John Stewart Kennedy Fund, 1913",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/35995,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -13.100.88,false,true,35999,Asian Art,Folding fan mounted as an album leaf,,China,Ming dynasty (1368–1644),,,,Calligrapher,,Yang Wencong,"Chinese, 1597–1645/46",,Yang Wencong,Chinese,1597,1646,,1597,1644,Folding fan mounted as an album leaf; ink on paper,6 3/5 x 20 31/32 in. (16.8 x 53.3 cm),"John Stewart Kennedy Fund, 1913",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/35999,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -13.100.94,false,true,36003,Asian Art,Folding fan mounted as an album leaf,,China,Ming dynasty (1368–1644),,,,Calligrapher,,Rüan Dacheng,"Chinese, active early 17th century",,Rüan Dacheng,Chinese,1550,1700,,1600,1633,Folding fan mounted as an album leaf; ink on gold paper,7 x 19 3/4 in. (17.8 x 50.2 cm),"John Stewart Kennedy Fund, 1913",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/36003,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.62,false,true,45786,Asian Art,Folding fan mounted as an album leaf,,China,Ming dynasty (1368–1644),,,,Artist|Artist|Artist,,Wen Zhengming|Wang Shou|Wang Guxiang,"Chinese, 1470–1559|Chinese, active early 16th century|Chinese, 1501–1568",,Wen Zhengming|Wang Shou|Wang Guxiang,Chinese|Chinese|Chinese,1470 |1500 |1501,1559 |1600 |1568,,1470,1559,Folding fan mounted as an album leaf; ink on gold-flecked paper,6 3/8 x 20 1/4 in. (16.2 x 51.4 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/45786,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.55,false,true,73669,Asian Art,Hanging scroll,清 查昇 行書七律詩 軸|Poem,China,Qing dynasty (1644–1911),,,,Calligrapher,,Zha Sheng,"Chinese, 1650–1707",,Zha Sheng,Chinese,1650,1707,,1650,1707,Hanging scroll; ink on silk,H. 63 1/4 in. (160.6 cm); W. 18 11/16 in. (47.5 cm),"Purchase, C. C. Wang Gift, 2007",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/73669,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.154a–h,false,true,49179,Asian Art,Album,清 石濤(朱若極) 山水圖 冊|Searching for Immortals,China,Qing dynasty (1644–1911),,,,Artist|Calligrapher,Inscribed by,Shitao (Zhu Ruoji)|Shitao (Zhu Ruoji),"Chinese, 1642–1707|Chinese, 1642–1707",,Shitao|Shitao,Chinese|Chinese,1642 |1642,1707 |1707,,1644,1707,Album of eight leaves; ink and color on paper,5 7/8 x 10 3/4 in. (14.9 x 27.3 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49179,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -13.220.11,false,true,49226,Asian Art,Hanging scroll,,China,Qing dynasty (1644–1911),,,,Artist|Artist|Artist,In the Style of|Formerly Attributed to,Li Yin|Unidentified Artist|Ma Kui,"Chinese, active second half of the 17th–early 18th century|Chinese, active ca. 1194–1225",,Li Yin|Unidentified Artist|Ma Kui,Chinese|Chinese,1650 |1194,1825 |1225,,1644,1911,Hanging scroll; ink and color on silk,Image: 65 7/8 x 20 7/8 in. (167.3 x 53 cm); Overall with mounting: 107 3/4 x 28 5/8 in. (273.7 x 72.7 cm); Overall with knobs: 107 3/4 x 32 5/8 in. (273.7 x 82.9 cm),"John Stewart Kennedy Fund, 1913",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49226,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -47.18.120,false,true,51727,Asian Art,Handscroll,,China,Yuan dynasty (1271–1368) (?),,,,Artist|Calligrapher|Artist,Copy after,Unidentified Artist|Guo Zongjiang|Li Gonglin,"Chinese|Chinese, ca. 1041–1106",Chinese|(Frontispiece),Unidentified Artist|Guo Zongjiang|Li Gonglin,Chinese|Chinese,1041,1106,,1271,1368,Handscroll; ink and color on silk,12 5/8 x 184 in. (32.1 x 467.4 cm),"From the Collection of A. W. Bahr, Purchase, Fletcher Fund, 1947",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/51727,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -47.18.93,false,true,51705,Asian Art,Handscroll,,China,Northern Song dynasty (960–1127),,,,Artist|Calligrapher|Artist,Copy after,Unidentified Artist|Emperor Huizong|Han Gan,"Chinese, 1082–1135; r. 1100–25|Chinese, active ca. 742–756",,Unidentified Artist|Huizong Emperor|HAN GAN,Chinese|Chinese,1082 |0742,1082 |0756,,960,1127,Handscroll; ink and color on silk,Image: 12 in. × 20 1/8 in. (30.5 × 51.1 cm),"From the Collection of A. W. Bahr, Purchase, Fletcher Fund, 1947",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/51705,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -18.124.6,false,true,51528,Asian Art,Handscroll,,China,Ming (1368–1644) or Qing (1644–1911) dynasty,,,,Artist|Artist|Calligrapher|Calligrapher|Calligrapher|Calligrapher|Calligrapher|Calligrapher|Calligrapher|Calligrapher|Calligrapher,Formerly Attributed to,Unidentified Artist|Ni Zan|Li Dongyang|Wang Da|Jin Xüan|Wen Peng|Wen Jia|Zhou Tianqiu|Huang Jishui|Dong Qichang|Wang Zhideng,"Chinese, 1306–1374|Chinese, 1447–1503|Chinese, 14th century|Chinese|Chinese, 1498–1573|Chinese, 1501–1583|Chinese, 1514–1595|Chinese|Chinese, 1555–1636|Chinese, 1535–1612",,Unidentified Artist|NI ZAN|Li Dongyang|Wang Da|Jin Xüan|Wen Peng|Wen Jia|Zhou Tianqiu|Huang Jishui|Dong Qichang|Wang Zhideng,Chinese|Chinese|Chinese|Chinese|Chinese|Chinese|Chinese|Chinese|Chinese|Chinese,1306 |1447 |1300 |1498 |1501 |1514 |1555 |1535,1374 |1503 |1399 |1573 |1583 |1595 |1636 |1612,,1368,1911,Handscroll; color on paper,11 x 57 1/2 in. (27.9 x 146.1 cm),"Rogers Fund, 1918",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/51528,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -47.18.33,false,true,51656,Asian Art,Album leaf,,China,Ming (1368–1644) or Qing dynasty (1644–1911),,,,Artist|Artist|Artist,In the Style of|Formerly Attributed to,Unidentified Artist|Li Zhaodao|Qiu Ying,"act 670–730|Chinese, ca. 1495–1552",,Unidentified Artist|Li Zhaodao|Qiu Ying,Chinese|Chinese,0670 |1485,0730 |1562,,1368,1911,Album leaf; ink and color on silk,14 1/8 x 10 5/8 in. (35.9 x 27.0 cm),"From the Collection of A. W. Bahr, Purchase, Fletcher Fund, 1947",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/51656,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"47.18.13a, b",false,true,51644,Asian Art,Handscroll,,China,Ming (1368–1644) or Qing dynasty (1644–1911),,,,Artist|Artist|Artist,b) Formerly Attributed to|a) Formerly Attributed to,Unidentified Artist|Fang Congyi|Xia Gui,"Chinese, ca. 1301–after 1378|Chinese, active ca. 1195–1230",,Unidentified Artist|Fang Congyi|Xia Gui,Chinese|Chinese,1301 |1195,1399 |1230,,1368,1911,Two handscrolls; ink on paper,Image (a): 3 5/8 in. × 46 in. (9.2 × 116.8 cm) Image (b): 3 7/8 in. × 35 in. (9.8 × 88.9 cm),"From the Collection of A. W. Bahr, Purchase, Fletcher Fund, 1947",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/51644,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1979.500.1,false,true,36129,Asian Art,Album,,China,Qing dynasty (1644–1911),,,,Artist|Artist|Artist|Artist|Artist|Artist|Artist|Artist|Calligrapher|Calligrapher,,Wang Jian|Wu Li|Tong Bin|Gao Jian|Zhu Zun|Zhang Shi|Jin Kan|Shen Lang|Xü Fu|Shen Ho,"Chinese, 1609–1677 or 1688|Chinese, 1632–1718|Chinese, 17th century|Chinese, 1634–after 1715|Chinese, 17th century|Chinese, 17th century|Chinese, died 1703|Chinese, 17th century|Chinese, 19th century|Chinese, 17th century",(title page),Wang Jian|WU LI|Tong Bin|Gao Jian|Zhu Zun|Zhang Shi|Jin Kan|Shen Lang|Xü Fu|Shen Ho,Chinese|Chinese|Chinese|Chinese|Chinese|Chinese|Chinese|Chinese|Chinese|Chinese,1609 |1632 |1600 |1634 |1600 |1600 |1600 |1800 |1600,1677 |1718 |1699 |1725 |1699 |1699 |1703 |1699 |1899 |1699,17th century,1644,1699,Album of eight painted leaves; ink and color on paper,Each leaf: 9 x 12 1/4 in. (22.9 x 31.1 cm),"Gift of Douglas Dillon, 1979",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/36129,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -51.14,false,true,51770,Asian Art,Folding fan,,China,,,,,Artist|Calligrapher,,Giuseppe Castiglione|Wang Yudun,"Italian, Milan 1688–1766 Beijing|Chinese, 1692–1758",,"Castiglione, Giuseppe|Wang Yudun",Italian|Chinese,1688 |1692,1766 |1758,18th century,1700,1766,Folding fan; ink and color on paper,H. (sticks) 12 1/4 in. (31.1 cm); W. (open) 20 in. (50.8 cm),"Rogers Fund, 1951",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/51770,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1977.375.14a–d,false,true,64029,Asian Art,Rubbing,唐 颜真卿 顏家廟碑 現代拓片 紙本|Yan Family Temple Stele,China,,,,,Artist,,Yan Zhenqing,"Chinese, 709–785",,Yan Zhenqing,Chinese,0709,0785,20th century,1900,1999,20th-century rubbing of a stele dated 780; ink on paper,98 x 52 x 1 3/4 in. (248.9 x 132.1 x 4.4 cm),"Seymour and Rogers Funds, 1977",,,,,,,,,,,,Rubbing,,http://www.metmuseum.org/art/collection/search/64029,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -CIB8,false,true,60764,Asian Art,Illustrated book,太平山水圖|Landscapes of Taiping Prefecture (Taiping shanshui tu),China,,,,,Artist,,Xiao Yuncong,"Chinese, 1596–1673",,Xiao Yuncong,Chinese,1596,1673,ca. 1650,1640,1660,Woodblock-printed books; ink on paper,H. 8 7/8 in. (22.5 cm); W. 6 in. (15.2 cm); D. 1 in. (2.5 cm),"Rogers Fund, 1924",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/60764,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"1989.363.205a, b",false,true,51902,Asian Art,Hanging scrolls,,China,,,,,Artist,,Wu Changshuo,"Chinese, 1844–1927",,Wu Changshuo,Chinese,1844,1927,20th century,1900,1927,Pair of hanging scrolls; ink on paper,Image (each scroll): 57 13/16 x 10 1/8 in. (146.8 x 25.7 cm) Overall (each scroll): 68 1/2 x 12 11/16 in. (174 x 32.2 cm) Overall with knobs (each scroll): 68 1/2 x 15 in. (174 x 38.1 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/51902,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1977.375.30a–c,false,true,64045,Asian Art,Rubbing,,China,,,,,Artist,,Wang Xizhi,"Chinese, ca. 303–ca. 361",,WANG XIZHI,Chinese,0303,0361,20th century,1900,1999,Ink on paper,,"Seymour and Rogers Funds, 1977",,,,,,,,,,,,Rubbing,,http://www.metmuseum.org/art/collection/search/64045,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.141.1a–i,false,true,36439,Asian Art,Album,唐 傳鍾紹京 楷書靈飛經 冊|Spiritual Flight Sutra,China,Tang dynasty (618–907),,,,Artist,Attributed to,Zhong Shaojing,"Chinese, active ca. 713–41",,Zhong Shaojing,Chinese,0713,0741,ca. 738,728,741,Album of nine leaves; ink on paper,Each leaf: 8 3/16 x 3 1/2 in. (20.8 x 8.9 cm),"Purchase, The Dillon Fund Gift, 1989",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/36439,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.28,false,true,40285,Asian Art,Handscroll,南宋 趙孟堅 行書梅竹詩譜 卷|Poems on Painting Plum Blossoms and Bamboo,China,Song dynasty (960–1279),,,,Artist,,Zhao Mengjian,"Chinese, 1199–before 1267",,Zhao Mengjian,Chinese,1199,1267,dated 1260,1260,1260,Handscroll; ink on paper,Image: 13 3/8 in. × 11 ft. 7 in. (34 × 353.1 cm) Overall with mounting: 13 5/8 in. × 40 ft. 5 11/16 in. (34.6 × 1233.6 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/40285,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.18,false,true,40107,Asian Art,Fan mounted as an album leaf,南宋 理宗趙昀 行書北宋梅堯臣 依韻和資政侍郎雪後登看山亭詩 團扇|Quatrain on Snow-covered West Lake,China,Song dynasty (960–1279),,,,Artist,,Emperor Lizong,"Chinese, 1205–64, r. 1224–64",,LIZONG EMPEROR,Chinese,1205,1264,ca. 1250–60,1250,1260,Round fan mounted as an album leaf; ink on silk,Image: 9 7/8 × 9 7/8 in. (25.1 × 25.1 cm) Mat: 15 1/2 × 14 1/2 in. (39.4 × 36.8 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/40107,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.19,false,true,40180,Asian Art,Fan mounted as an album leaf,,China,Song dynasty (960–1279),,,,Artist,,Emperor Lizong,"Chinese, 1205–64, r. 1224–64",,LIZONG EMPEROR,Chinese,1205,1264,ca. 1260–64,1260,1264,Fan mounted as an album leaf; ink on silk,Image: 9 × 9 5/8 in. (22.9 × 24.4 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/40180,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.23a–c,false,true,40194,Asian Art,Fan mounted as an album leaf,南宋 理宗 趙昀 楷書韓翊 《 潮聲山翠》 聯句 團扇|Couplet from a Poem by Han Hong,China,Song dynasty (960–1279),,,,Artist,,Emperor Lizong,"Chinese, 1205–64, r. 1224–64",,LIZONG EMPEROR,Chinese,1205,1264,1261,1261,1261,"Fan mounted as an album leaf; a) ink on silk, b) ink on paper, c) ink on paper",8 3/16 x 8 11/16 in. (20.8 x 22.1 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/40194,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.9,false,true,40060,Asian Art,Fan mounted as an album leaf,南宋 孝宗 行楷書池上水邊聯句 團扇|Couplet on pond scenery,China,Song dynasty (960–1279),,,,Artist,Attributed to,Emperor Xiaozong,"Chinese, 1127–1194; r. 1163–89",,Xiaozong Emperor,Chinese,1127,1194,ca. 12th century,1127,1194,Fan mounted as an album leaf; ink on silk,9 x 9 5/8 in. (22.9 x 24.4 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/40060,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.6,false,true,40058,Asian Art,Fan mounted as an album leaf,南宋 高宗 草書天山陰雨七絕詩 團扇|Quatrain on Heavenly Mountain,China,Song dynasty (960–1279),,,,Artist,,Emperor Gaozong,"Chinese, 1107–1187, r. 1127–1162",,Gaozong Emperor,Chinese,1107,1187,after 1162,1162,1187,Fan mounted as album leaf; ink on silk,9 1/4 x 9 5/8 in. (23.5 x 24.4 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/40058,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.47,false,true,45684,Asian Art,Folding fan mounted as an album leaf,,China,Ming dynasty (1368–1644),,,,Artist,,Shen Zhou,"Chinese, 1427–1509",,Shen Zhou,Chinese,1427,1509,dated 1493,1493,1493,Folding fan mounted as an album leaf; ink on gold-flecked paper,6 1/2 x 18 1/2 in. (16.5 x 47 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/45684,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.48,false,true,45746,Asian Art,Folding fan mounted as an album leaf,,China,Ming dynasty (1368–1644),,,,Artist,,Wu Kuan,"Chinese, 1435–1504",,Wu Kuan,Chinese,1435,1504,ca. 1498,1488,1508,Folding fan mounted as an album leaf; ink on gold-flecked paper,6 3/4 x 19 in. (17.1 x 49.5 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/45746,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1997.438.2,false,true,39766,Asian Art,Hanging scroll,明 王鏊 贈別詩 軸|Farewell Poem,China,Ming dynasty (1368–1644),,,,Artist,,Wang Ao,"Chinese, 1450–1524",,Wang Ao,Chinese,1450,1524,dated 1498,1498,1498,Hanging scroll; ink on paper,Image: 81 1/2 x 25 in. (207 x 63.5 cm) Overall: 130 x 34 1/2 in. (330.2 x 87.6 cm) Overall with rollers: 130 x 36 1/2 in. (330.2 x 92.7 cm),"The C. C. Wang Family Collection, Gift of C. C. Wang, 1997",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/39766,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.51,false,true,45749,Asian Art,Handscroll,,China,Ming dynasty (1368–1644),,,,Artist,,Zhu Yunming,"Chinese, 1461–1527",,Zhu Yunming,Chinese,1461,1527,dated 1507,1507,1507,Handscroll; ink on gold-flecked paper,Image: 12 7/8 x 371 1/2 in. (32.7 x 943.6 cm) Overall with mounting: 14 5/8 x 463 1/8 in. (37.1 x 1176.3 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/45749,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1988.394.2,false,true,44574,Asian Art,Folding fan mounted as an album leaf,,China,Ming dynasty (1368–1644),,,,Artist,,Tang Yin,"Chinese, 1470–1524",,Tang Yin,Chinese,1470,1524,dated 1522,1522,1522,Folding fan mounted as an album leaf; ink on gold-flecked paper,Image: 6 13/16 x 19 5/8 in. (17.3 x 49.8 cm),"Edward Elliott Family Collection, Douglas Dillon Gift, 1988",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/44574,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.59,false,true,45775,Asian Art,Album leaf,"明 文徵明 致丈人吳愈書 冊頁|Letter to the Artist's Father-in-law, Wu Yu",China,Ming dynasty (1368–1644),,,,Artist,,Wen Zhengming,"Chinese, 1470–1559",,Wen Zhengming,Chinese,1470,1559,ca. 1506–1510,1506,1510,Album leaf; ink on patterned paper,9 x 12 1/8 in. (22.9 x 30.8 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/45775,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.63,false,true,45780,Asian Art,Handscroll,,China,Ming dynasty (1368–1644),,,,Artist,,Wen Zhengming,"Chinese, 1470–1559",,Wen Zhengming,Chinese,1470,1559,dated 1544 and 1547,1544,1547,Handscroll; ink on paper,Image: 9 1/16 x 46 3/8 in. (23 x 117.8 cm) Overall with mounting: 9 5/16 x 288 1/16 in. (23.7 x 731.7 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/45780,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1982.229a–ff,false,true,51865,Asian Art,Album,,China,Ming dynasty (1368–1644),,,,Artist,,Wen Zhengming,"Chinese, 1470–1559",,Wen Zhengming,Chinese,1470,1559,dated 1543,1543,1543,Album of thirty-two pages; ink on paper,H. 13 1/2 in. (34.3 cm); W. 10 3/9 in. (26.2 cm) Album: H. 15 7/8 in. (40.3 cm); W. 10 7/8 in. (27.6 cm); D. 1 in. (2.5 cm),"Gift of John M. Crawford Jr., 1982",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/51865,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2012.166,false,true,77914,Asian Art,Hanging scroll mounted as panel,文彭 行草七律 水墨絹本 鏡框|Poem on Promulgating the Almanac at New Year’s,China,Ming dynasty (1368–1644),,,,Artist,,Wen Peng,"Chinese, 1498–1573",,Wen Peng,Chinese,1498,1573,undated,1498,1573,Hanging scroll remounted as a panel; ink on paper,Image: 63 1/4 x 30 1/2 in. (160.7 x 77.5 cm) Overall with mounting: 77 3/4 x 40 1/4 in. (197.5 x 102.2 cm),"Purchase, Friends of Asian Art Gifts, 2012",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/77914,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.70,false,true,48875,Asian Art,Folding fan mounted as an album leaf,,China,Ming dynasty (1368–1644),,,,Artist,,Wen Peng,"Chinese, 1498–1573",,Wen Peng,Chinese,1498,1573,dated 1567,1567,1567,Folding fan mounted as an album leaf; ink on gold paper,6 x 19 in. (15.2 x 48.3 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/48875,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1979.500.3,false,true,48955,Asian Art,Hanging scroll,,China,Ming dynasty (1368–1644),,,,Artist,,Dong Qichang,"Chinese, 1555–1636",,Dong Qichang,Chinese,1555,1636,ca. 1622–25,1612,1635,Hanging scroll; ink on silk,Image: 64 7/8 x 19 5/8 in. (164.8 x 49.8 cm) Overall with mounting: 98 1/2 x 25 5/8 in. (250.2 x 65.1 cm) Overall with knobs: 98 1/2 x 29 5/8 in. (250.2 x 75.2 cm),"Gift of Douglas Dillon, 1979",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/48955,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.100,false,true,48954,Asian Art,Hanging scroll,,China,Ming dynasty (1368–1644),,,,Artist,,Dong Qichang,"Chinese, 1555–1636",,Dong Qichang,Chinese,1555,1636,after 1632,1632,1636,Hanging scroll; ink on paper,Image: 74 3/8 x 29 1/4 in. (188.9 x 74.3 cm) Overall: 103 3/4 x 34 1/4 in. (263.5 x 87 cm) Overall with knobs: 103 3/4 x 38 in. (263.5 x 96.5 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/48954,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.115,false,true,49025,Asian Art,Folding fan mounted as an album leaf,,China,Ming dynasty (1368–1644),,,,Artist,,Wang Duo,"Chinese, 1592–1652",,Wang Duo,Chinese,1592,1652,dated 1637,1637,1637,Folding fan mounted as an album leaf; ink on gold-flecked paper,6 5/8 x 20 3/4 in. (16.8 x 52.7 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/49025,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.116,false,true,49029,Asian Art,Hanging scroll,明/清 王鐸 行草書日暮孤亭五律詩 軸|Poem on a Riverside Pavilion,China,Ming dynasty (1368–1644),,,,Artist,,Wang Duo,"Chinese, 1592–1652",,Wang Duo,Chinese,1592,1652,dated 1641,1641,1641,Hanging scroll; ink on paper,Image: 138 1/4 x 29 1/4 in. (351.2 x 74.3 cm) Overall: 169 1/2 x 39 in. (430.5 x 99.1 cm) Overall with knobs: 169 1/2 x 43 1/2 in. (430.5 x 110.5 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/49029,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1999.93,false,true,56553,Asian Art,Qin,,China,Ming dynasty (1368–1644),,,,Artist,,Prince Lu,"Chinese, 1628–1644",,Prince Lu,Chinese,1628,1644,1634,1634,1634,"Wood, lacquer, jade, silk strings",W. 10 1/2 in. (26.6 cm); D. 4 1/4 in. (11 cm); L. 46 5/8 in. (118.5 cm),"Purchase, Clara Mertens Bequest, in memory of André Mertens, Seymour Fund, The Boston Foundation Gift, Gift of Elizabeth M. Riley, by exchange, and funds from various donors, 1999",,,,,,,,,,,,Musical instruments,,http://www.metmuseum.org/art/collection/search/56553,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -30.76.201,false,true,41780,Asian Art,Ink tablet,,China,Ming dynasty (1368–1644),,,,Artist,Workshop of,Fang Yulu,active ca. 1570–1619,,Fang Yulu,Chinese,1570,1619,dated 1576,1576,1576,Pine soot and binding medium,Diam. 3 1/2 in. (8.9 cm),"Rogers Fund, 1929",,,,,,,,,,,,Ink,,http://www.metmuseum.org/art/collection/search/41780,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1994.208,false,true,39625,Asian Art,Brush holder,晚明 張希黃 留青竹刻 《醉翁亭記》 詩意圖筆筒|Brush holder with “Ode to the Pavilion of the Inebriated Old Man”,China,Ming dynasty (1368–1644),,,,Artist,,Zhang Xihuang,active early 17th century,,Zhang Xihuang,Chinese,1600,1633,early 17th century,1600,1633,Bamboo,H. 5 1/4 in. (13.4 cm),"Purchase, Mr. and Mrs. John A. Wiley Gift, Seymour Fund, Bequest of Dorothy Graham Bennett and Erich O. Grunebaum Bequest, 1994",,,,,,,,,,,,Bamboo,,http://www.metmuseum.org/art/collection/search/39625,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.87,false,true,48931,Asian Art,Hanging scroll,,China,Ming dynasty (1368–1644),,,,Artist,,Liu Xiang,"Chinese, active mid-17th century",,Liu Xiang,Chinese,1636,1670,16th–17th century,1500,1699,Hanging scroll; ink on paper,Image: 38 9/16 x 10 3/16 in. (97.9 x 25.9 cm) Overall: 72 1/4 x 18 3/16 in. (183.5 x 46.2 cm) Overall with knobs: 72 1/4 x 21 1/4 in. (183.5 x 54 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/48931,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.81,false,true,48928,Asian Art,Folding fan mounted as an album leaf,,China,Ming dynasty (1368–1644),,,,Artist,,Xue Mingyi,"Chinese, active ca. 1538–after 1597",,Xue Mingyi,Chinese,1538,1598,dated 1597,1597,1597,Folding fan mounted as an album leaf; ink on gold-flecked paper,5 3/4 x 18 in. (14.6 x 45.7 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/48928,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -13.100.72,false,true,48959,Asian Art,Folding fan mounted as an album leaf,,China,Qing dynasty (1644–1911),,,,Artist,After,Dong Qichang,"Chinese, 1555–1636",,Dong Qichang,Chinese,1555,1636,17th century or later,1600,1911,Folding fan mounted as an album leaf; ink on gold paper,6 11/16 x 20 1/2 in. (17.0 x 52.1 cm),"John Stewart Kennedy Fund, 1913",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/48959,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.130,false,true,49129,Asian Art,Handscroll,清 法若真 行草書畫說 卷|Discourse on Painting,China,Qing dynasty (1644–1911),,,,Artist,,Fa Ruozhen,"Chinese, 1613–1696",,Fa Ruozhen,Chinese,1613,1696,dated 1667,1667,1667,Handscroll; ink on paper,12 1/4 x 144 1/2 in. (31.1 x 367 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/49129,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.139,false,true,49146,Asian Art,Folding fan mounted as an album leaf,清 朱耷 (八大山人) 李治書 扇頁|Letter by Li Zhi,China,Qing dynasty (1644–1911),,,,Artist,,Bada Shanren (Zhu Da),"Chinese, 1626–1705",,Bada Shanren (Zhu Da),Chinese,1626,1705,dated 1702,1702,1702,Folding fan mounted as an album leaf; ink on paper,6 3/4 x 19 1/2 in. (17.1 x 49.5 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/49146,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1982.458a–e,false,true,49147,Asian Art,Album,明/清 朱耷(八大山人) 致方士琯書 冊 紙本|Letters to Fang Shiguan,China,Qing dynasty (1644–1911),,,,Artist,,Bada Shanren (Zhu Da),"Chinese, 1626–1705",,Bada Shanren (Zhu Da),Chinese,1626,1705,datable to ca. 1688–1705,1688,1705,Album of ten leaves; ink on patterned and plain paper,"Image (leaf a, right letter): 7 3/4 x 5 7/16 in. (19.7 x 13.8 cm) Image (leaf a, left letter): 7 1/4 x 4 9/16 in. (18.4 x 11.6 cm) Image (leaf b, right letter): 7 1/2 x 5 3/16 in. (19.1 x 13.2 cm) Image (leaf b, left letter): 7 x 4 9/16 in. (17.8 x 11.6 cm) Image (leaf c, right letter): 7 1/2 x 5 3/16 in. (19.1 x 13.2 cm) Image (leaf c, left letter): 7 1/4 x 4 9/16 in. (18.4 x 11.6 cm) Image (leaf d, right letter): 7 1/2 x 5 3/16 in. (19.1 x 13.2 cm) Image (leaf d, left letter): 7 1/4 x 4 9/16 in. (18.4 x 11.6 cm) Image (leaf e, right letter): 7 5/8 x 5 3/16 in. (19.4 x 13.2 cm) Image (leaf e, left letter): 7 1/4 x 4 3/4 in. (18.4 x 12.1 cm)","Gift of John M. Crawford Jr., 1982",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/49147,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.147,false,true,49163,Asian Art,Folding fan mounted as an album leaf,,China,Qing dynasty (1644–1911),,,,Artist,,Yun Shouping,"Chinese, 1633–1690",,YUN SHOUPING,Chinese,1633,1690,dated 1680,1680,1680,Folding fan mounted as an album leaf; ink on paper,6 11/16 x 20 in. (17 x 50.8 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/49163,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.196,false,true,51894,Asian Art,Album,"清 金農 論畫雜詩 冊|Poems on Paintings, Written for Ma Yueguan",China,Qing dynasty (1644–1911),,,,Artist,,Jin Nong,"Chinese, 1687–1773",,Jin Nong,Chinese,1687,1773,dated 1754,1754,1754,Album of eleven double leaves; ink on paper,Image: 6 5/8 x 10 3/8 in. (16.8 x 26.4 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/51894,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1980.531,false,true,36132,Asian Art,Hand scroll,,China,Qing dynasty (1644–1911),,,,Artist,,Liu Yong,"Chinese, 1719–1805",,Liu Yong,Chinese,1719,1805,dated 1803,1803,1803,Hand scroll; ink on paper,30 3/4 x 95 1/2 in. (78.1 x 242.6 cm),"Gift of Dr. and Mrs. George Fan, 1980",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/36132,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.203,false,true,51900,Asian Art,Hanging scroll,,China,Qing dynasty (1644–1911),,,,Artist,,Wang Wenzhi,"Chinese, 1730–1802",,Wang Wenzhi,Chinese,1730,1802,dated 1801,1801,1801,Hanging scroll; ink on paper,Image: 65 3/4 x 18 1/2 in. (167 x 47 cm) Overall: 85 x 23 3/4 in. (215.9 x 60.3 cm) Overall with knobs: 85 x 26 1/2 in. (215.9 x 67.3 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/51900,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2010.327a–d,false,true,75814,Asian Art,Hanging scrolls,吳煕載 篆書詩經南山有臺|Nutgrass Grows on the Southern Hills,China,Qing dynasty (1644–1911),,,,Artist,,Wu Xizai,"Chinese, 1799–1870",,Wu Xizai,Chinese,1799,1870,before 1862,1799,1862,Set of four hanging scrolls; ink on paper,each: 12 3/16 x 51 9/16 in. (31 x 131 cm) Overall with mounting (each): 16 1/8 x 64 15/16 in. (41 x 165 cm),"Gift of Judith G. and F Randall Smith, in honor of Wen C. Fong, 2010",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/75814,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2000.514.50,false,true,54308,Asian Art,Panel,清 趙之謙 隸書引首 卷|The Lingshouhua Studio,China,Qing dynasty (1644–1911),,,,Artist,,Zhao Zhiqian,"Chinese, 1829–1884",,Zhao Zhiqian,Chinese,1829,1884,1863,1863,1863,Horizontal panel; ink on paper,13 x 40 3/4 in. (33 x 103.5 cm),"Gift of Shou-cheng Zhang and Xiu-ping Loh Zhang, 2000",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/54308,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2000.345.1, .2",false,true,55495,Asian Art,Hanging scrolls,清 趙之謙 篆書五言 對聯|Couplet,China,Qing dynasty (1644–1911),,,,Artist,,Zhao Zhiqian,"Chinese, 1829–1884",,Zhao Zhiqian,Chinese,1829,1884,dated 1867,1867,1867,Pair of hanging scrolls; ink on paper,Image (each): 71 5/8 x 18 15/16 in. (181.9 x 48.1 cm) Overall with mounting (each): 92 1/2 x 23 1/2 in. (235 x 59.7 cm),"Gift of Judith G. and F Randall Smith, in honor of Maxwell K. Hearn, 2000",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/55495,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.165,false,true,49473,Asian Art,Hanging scroll,清 翁同龢 軸|Tiger Calligraphy,China,Qing dynasty (1644–1911),,,,Artist,,Weng Tonghe,"Chinese, 1830–1904",,Weng Tonghe,Chinese,1830,1904,dated 1890,1890,1890,Hanging scroll; ink on silver-flecked red paper,Image: 51 x 26 1/2 in. (129.5 x 67.3 cm) Overall: 83 1/2 x 31 3/4 in. (212.1 x 80.6 cm) Overall with knobs: 83 1/2 x 35 1/8 in. (212.1 x 89.2 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/49473,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -13.100.69,false,true,35987,Asian Art,Folding fan mounted as an album leaf,,China,Qing dynasty (1644–1911),,,,Artist,,Yu Dongru,"Chinese, active Ming dynasty",,Yu Dongru,Chinese,1350,1650,late 17th century,1667,1699,Folding fan mounted as an album leaf; ink on paper,6 5/8 x 19 5/8 in. (16.8 x 49.8 cm),"John Stewart Kennedy Fund, 1913",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/35987,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1988.324.6,false,true,44570,Asian Art,Album leaf,,China,Qing dynasty (1644–1911),,,,Artist,,Pan Zenggui,"Chinese, active late 19th century",,Pan Zenggui,Chinese,1800,1899,ca. late 19th century,1867,1899,Album leaf of calligraphy in running script; ink and color on paper,9 3/4 x 13 1/2 in. (24.8 x 34.3 cm),"Gift of Robert Hatfield Ellsworth, in memory of La Ferne Hatfield Ellsworth, 1988",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/44570,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.17,false,true,40105,Asian Art,Handscroll,元 耶律楚材 行書贈別劉滿詩 卷|Poem of Farewell to Liu Man,China,Yuan dynasty (1271–1368),,,,Artist,,Yelü Chucai,"Khitan, 1190–1244",,YELÜ CHUCAI,Khitan,1190,1244,dated 1240,1230,1250,Handscroll; ink on paper,Image: 14 1/2 x 111 3/4 in. (36.8 x 283.8 cm) Overall with mounting: 15 in. x 37 ft. 7 11/16 in. (38.1 x 1147.3 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/40105,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.30,false,true,40509,Asian Art,Handscroll,元 趙孟頫 行書右軍四事 卷|Four anecdotes from the life of Wang Xizhi,China,Yuan dynasty (1271–1368),,,,Artist,,Zhao Mengfu,"Chinese, 1254–1322",,Zhao Mengfu,Chinese,1254,1322,1310s,1310,1319,Handscroll; ink on paper,Image: 9 5/8 x 46 1/16 in. (24.4 x 117 cm) Overall with mounting: 10 7/16 in. x 27 ft. 1 1/8 in. (26.5 x 851.2 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/40509,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1994.308,false,true,42329,Asian Art,Handscroll,元 鄭元祐 楷書師孺齋記 卷|Record of the Following One's Ancestor Studio,China,Yuan dynasty (1271–1368),,,,Artist,,Zheng Yuanyou,"Chinese, 1292–1364",,ZHENG YUANYOU,Chinese,1292,1364,dated 1345,1345,1345,Handscroll; ink on paper,10 3/4 x 37 3/4 in. (27.3 x 96 cm),"Purchase, Friends of Asian Art Gifts, 1994",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/42329,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.4,true,true,39918,Asian Art,Handscroll,北宋 黃庭堅 草書廉頗藺相如傳 卷|Biographies of Lian Po and Lin Xiangru,China,Northern Song dynasty (960–1127),,,,Artist,,Huang Tingjian,"Chinese, 1045–1105",,HUANG TINGJIAN,Chinese,1045,1105,ca. 1095,1085,1105,Handscroll; ink on paper,Image: 13 1/4 in. × 60 ft. 4 1/2 in. (33.7 × 1840.2 cm) Overall with mounting: 13 1/2 in. × 71 ft. 5 5/8 in. (34.3 × 2178.4 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/39918,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1984.174,false,true,39919,Asian Art,Handscroll,北宋 米芾 草書吳江舟中詩 卷|Poem Written in a Boat on the Wu River,China,Northern Song dynasty (960–1127),,,,Artist,,Mi Fu,"Chinese, 1052–1107",,MI FU,Chinese,1052,1107,ca. 1095,1085,1105,Handscroll; ink on paper,12 1/4 in. × 18 ft. 3 1/4 in. (31.1 × 556.9 cm),"Gift of John M. Crawford Jr., in honor of Professor Wen Fong, 1984",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/39919,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.12,false,true,40103,Asian Art,Fan mounted as an album leaf,南宋 楊皇后 楷書薄薄殘妝七絕 團扇冊頁 絹本|Quatrain on spring’s radiance,China,Southern Song dynasty (1127–1279),,,,Artist,,Empress Yang Meizi,"Chinese, 1162–1232",,YANG MEIZI,Chinese,1162,1232,early 13th century,1200,1215,Round fan mounted as an album leaf; ink on silk,Image: 9 1/8 x 9 5/8 in. (23.2 x 24.4 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/40103,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.13,false,true,40104,Asian Art,Fan mounted as an album leaf,南宋 楊皇后 楷書瀹雪凝酥七絕 團扇|Quatrain on yellow roses,China,Southern Song dynasty (1127–1279),,,,Artist,,Empress Yang Meizi,"Chinese, 1162–1232",", r. 1202–24;",YANG MEIZI,Chinese,1162,1232,early 13th century,1200,1232,Round fan mounted as an album leaf; ink on silk,9 1/4 x 9 5/8 in. (23.5 x 24.5 cm); with mat: 14 1/2 x 15 1/2 in. (36.8 x 39.4 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/40104,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -30.76.198,false,true,41777,Asian Art,Ink tablet,,China,"late Qing dynasty (1644–1911, early Republic period",,,,Artist,Workshop of,Fang Yulu,active ca. 1570–1619,,Fang Yulu,Chinese,1570,1619,late 19th–early 20th century,1871,1933,Black ink,H. 4 3/8 in. (11.1 cm); W. 4 1/4 in. (10.8 cm),"Rogers Fund, 1929",,,,,,,,,,,,Ink,,http://www.metmuseum.org/art/collection/search/41777,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -30.76.280,false,true,41860,Asian Art,Ink tablet,,China,"late Qing dynasty (1644–1911, early Republic period",,,,Artist,,Fang Yulu,active ca. 1570–1619,,Fang Yulu,Chinese,1570,1619,late 19th–early 20th century,1871,1933,Ink,Diam. 4 7/8 in. (12.4 cm),"Rogers Fund, 1929",,,,,,,,,,,,Ink,,http://www.metmuseum.org/art/collection/search/41860,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -30.76.196,false,true,40018,Asian Art,Ink tablet,,China,"late Qing dynasty (1644–1911, early Republic period",,,,Artist,Workshop of,Cheng Junfang,"Chinese, 1541–ca. 1620",,Cheng Junfang,Chinese,1541,1620,late 19th–early 20th century,1871,1933,Pine soot and binding medium,Diam. 4 1/2 in. (11.4 cm),"Rogers Fund, 1929",,,,,,,,,,,,Ink,,http://www.metmuseum.org/art/collection/search/40018,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -30.76.199,false,true,41778,Asian Art,Ink tablet,,China,"late Qing dynasty (1644–1911, early Republic period",,,,Artist,,Cheng Junfang,"Chinese, 1541–ca. 1620",,Cheng Junfang,Chinese,1541,1620,late 19th–early 20th century,1875,1933,Ink,Diam. 3 1/8 in. (7.9 cm.),"Rogers Fund, 1929",,,,,,,,,,,,Ink,,http://www.metmuseum.org/art/collection/search/41778,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -30.76.203,false,true,41782,Asian Art,Ink tablet,,China,"late Qing dynasty (1644–1911, early Republic period",,,,Artist,,Cheng Junfang,"Chinese, 1541–ca. 1620",,Cheng Junfang,Chinese,1541,1620,late 19th–early 20th century,1871,1933,Ink,Diam. 5 5/8 in. (14.3 cm),"Rogers Fund, 1929",,,,,,,,,,,,Ink,,http://www.metmuseum.org/art/collection/search/41782,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -CP7,false,true,63196,Asian Art,Print,,China,,,,,Artist,Original painted by,Li Gonglin,"Chinese, ca. 1041–1106",,Li Gonglin,Chinese,1041,1106,1677,1677,1677,Polychrome woodblock print; ink and color on paper,9 5/8 x 11 13/16 in. (24.4 x 30 cm),"Rogers Fund, 1924",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/63196,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -CP6,false,true,63185,Asian Art,Print,芥子園畫傳|Mountainside View: Page from The Mustard Seed Garden Manual of Painting,China,,,,,Artist,Original painted by,Juran,"Chinese, active 10th century",,Juran,Chinese,0010,0010,probably 1878 edition,1878,1878,Polychrome woodblock print; ink and color on paper,9 5/8 x 11 13/16 in. (24.4 x 30 cm),"Rogers Fund, 1924",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/63185,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.125,false,true,49047,Asian Art,Hanging scroll,,China,Ming dynasty (1368–1644),,,,Artist,,Zhao Wenchu,"Chinese, 1595–1654",,Zhao Wenchu,Chinese,1595,1654,dated 1627,1627,1627,Hanging scroll; ink and color on paper,Image: 49 13/16 x 20 3/16 in. (126.5 x 51.3 cm) Overall: 98 1/2 x 26 1/2 in. (250.2 x 67.3 cm) Overall with knobs: 98 1/2 x 30 in. (250.2 x 76.2 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/49047,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1971.163,false,true,50549,Asian Art,Figure,,China,Ming dynasty (1368–1644),,,,Artist,,Qiao Bin,"Chinese, active 1481–1507",,Qiao Bin,Chinese,1481,1507,dated 1482,1482,1482,Glazed stoneware,H. 23 3/4 in. (60.3 cm); W. 15 in. (38.1 cm); D. 8 3/8 in. (21.3 cm),"Bequest of Harrison Cady, 1970",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/50549,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -25.227.1,false,true,53924,Asian Art,Group,,China,Ming dynasty (1368–1644),,,,Artist,,Qiao Bin,"Chinese, active 1481–1507",,Qiao Bin,Chinese,1481,1507,dated 1503,1503,1503,Glazed stoneware,H. 14 in. (35.6 cm); W. 17 1/8 in. (43.5 cm); D. 9 in. (22.9 cm),"Fletcher Fund, 1925",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/53924,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -25.227.2,false,true,53925,Asian Art,Figure,,China,Ming dynasty (1368–1644),,,,Artist,,Qiao Bin,"Chinese, active 1481–1507",,Qiao Bin,Chinese,1481,1507,dated 1503,1503,1503,Glazed pottery,H. 11 in. (27.9 cm); W. 4 1/2 in. (11.4 cm); D. 3 5/8 in. (9.2 cm),"Fletcher Fund, 1925",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/53925,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -25.227.3,false,true,53926,Asian Art,Figure,,China,Ming dynasty (1368–1644),,,,Artist,,Qiao Bin,"Chinese, active 1481–1507",,Qiao Bin,Chinese,1481,1507,dated 1503,1503,1503,Glazed pottery,H. 11 in. (27.9 cm); W. 4 5/8 in. (11.7 cm); D. 4 1/2 in. (11.4 cm),"Fletcher Fund, 1925",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/53926,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -25.227.4,false,true,53927,Asian Art,Figure,,China,Ming dynasty (1368–1644),,,,Artist,,Qiao Bin,"Chinese, active 1481–1507",,Qiao Bin,Chinese,1481,1507,dated 1503,1503,1503,Glazed pottery,H. 11 1/2 in. (29.2 cm); W. 4 5/8 in. (11.7 cm); D. 4 1/2 in. (11.4 cm),"Fletcher Fund, 1925",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/53927,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -25.227.5,false,true,53928,Asian Art,Figure,,China,Ming dynasty (1368–1644),,,,Artist,,Qiao Bin,"Chinese, active 1481–1507",,Qiao Bin,Chinese,1481,1507,dated 1503,1503,1503,Glazed pottery,H. 11 1/2 in. (29.2 cm); W. 4 5/8 in. (11.7 cm); D. 4 3/4 in. (12.1 cm),"Fletcher Fund, 1925",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/53928,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -25.227.6,false,true,53929,Asian Art,Figure,,China,Ming dynasty (1368–1644),,,,Artist,,Qiao Bin,"Chinese, active 1481–1507",,Qiao Bin,Chinese,1481,1507,dated 1503,1503,1503,Glazed pottery,H. 7 in. (17.8 cm); W. 4 1/4 in. (10.8 cm); D. 6 1/2 in. (16.5 cm),"Fletcher Fund, 1925",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/53929,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"1982.362a, b",false,true,42323,Asian Art,Teapot,,China,Qing dynasty (1644–1911),,,,Artist,,Shi Dabin,"Chinese, active 1620–40",,Shi Dabin,Chinese,1620,1640,early 17th century,1600,1633,Stoneware (Yixing ware),H. 3 3/4 in. (9.5 cm),"Purchase, Ann Eden Woodward Foundation Gift, 1982",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/42323,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.267.165,false,true,36236,Asian Art,Folding fan mounted as an album leaf,近代 張善子 黃山奇松圖 扇頁|Strange Pine in the Yellow Mountain,China,,,,,Artist,,Zhang Shanzi,(1882–1940),,ZHANG SHANZI,Chinese,1882,1940,dated 1935,1935,1935,Folding fan mounted as an album leaf; ink and color on alum paper,7 5/16 x 20 3/16 in. (18.6 x 51.3 cm),"Gift of Robert Hatfield Ellsworth, in memory of La Ferne Hatfield Ellsworth, 1986",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/36236,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.163,false,true,36098,Asian Art,Hanging scroll,,China,,,,,Artist,After,Chen Hongshou,"Chinese, 1599–1652",,Chen Hongshou,Chinese,1599,1652,20th century,1912,1971,Hanging scroll; ink and color on silk,Image: 84 3/8 x 30 3/4 in. (214.3 x 78.1 cm) Overall with mounting: 127 5/8 x 32 in. (324.2 x 81.3 cm) Overall with rollers: 127 5/8 x 35 in. (324.2 x 88.9 cm),"Gift of Zhang Daqian, 1972",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/36098,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2004.557.2a–d,false,true,72711,Asian Art,Album leaves,清 湯貽汾 墨梅 四冊頁|Blossoming Plum,China,,,,,Artist,,Tang Yifen,"Chinese, 1778–1853",,Tang Yifen,Chinese,1778,1853,Dated 1840,1840,1840,Four album leaves; ink on gold-flecked paper,Image: 9 1/4 x 8 1/2 in. (23.5 x 21.6 cm) Overall with mounting: 17 1/2 x 14 1/2 in. (44.5 x 36.8 cm),"Gift of Mark Shrum Pratt, 2004",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/72711,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2004.557.3a–d,false,true,72712,Asian Art,Hanging scrolls,清 胡遠 山水 四軸|Landscapes of the Four Seasons,China,,,,,Artist,,Hu Yuan,"Chinese, 1823–1886",,Hu Yuan,Chinese,1823,1886,Dated 1875,1875,1875,Set of four hanging scrolls; ink and color on paper,Image: 104 1/2 x 22 1/8 in. (265.4 x 56.2 cm) Overall with mounting (a): 143 x 29 in. (363.2 x 73.7 cm) Overall with mounting (b): 143 3/8 x 29 1/16 in. (364.2 x 73.8 cm) Overall with mounting (c): 143 1/8 x 29 1/16 in. (363.5 x 73.8 cm) Overall with mounting (d): 143 1/2 x 29 in. (364.5 x 73.7 cm) Overall with rollers (each): 33 1/4 in. (84.5 cm),"Gift of Mark Shrum Pratt, 2004",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/72712,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.267.103,false,true,36201,Asian Art,Folding fan mounted as an album leaf,近代 王振聲 友梅圖 扇面|Still-life with Plum,China,,,,,Artist,,Wang Zhensheng,"Chinese, 1842–1922",,Wang Zhensheng,Chinese,1842,1922,early 20th century,1900,1922,Folding fan mounted as an album leaf; ink and color on alum paper,6 1/8 x 19 in. (15.6 x 48.3 cm),"Gift of Robert Hatfield Ellsworth, in memory of La Ferne Hatfield Ellsworth, 1986",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/36201,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1988.324.2,false,true,44567,Asian Art,Hanging scroll,近代 吳昌碩 仙芝天竹圖 軸|Spring Offerings,China,,,,,Artist,,Wu Changshuo,"Chinese, 1844–1927",,Wu Changshuo,Chinese,1844,1927,dated 1919,1919,1919,Hanging scroll; ink and color on paper,Image: 58 x 31 1/2 in. (147.3 x 80 cm) Overall with mounting: 100 3/8 x 39 3/8 in. (255 x 100 cm) Overall with knobs: 100 3/8 x 43 in. (255 x 109.2 cm),"Gift of Robert Hatfield Ellsworth, in memory of La Ferne Hatfield Ellsworth, 1988",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/44567,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.267.124,false,true,49617,Asian Art,Painting,近代 吳昌碩 烹茶圖 橫軸|Brewing Tea,China,,,,,Artist,,Wu Changshuo,"Chinese, 1844–1927",,Wu Changshuo,Chinese,1844,1927,dated 1918,1918,1918,Horizontal painting; ink on paper,15 9/16 x 54 in. (39.5 x 137.2 cm),"Gift of Robert Hatfield Ellsworth, in memory of La Ferne Hatfield Ellsworth, 1986",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49617,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.267.125,false,true,36208,Asian Art,Folding fan mounted as an album leaf,近代 吳昌碩 蘭花 扇面|Orchid,China,,,,,Artist,,Wu Changshuo,"Chinese, 1844–1927",,Wu Changshuo,Chinese,1844,1927,early 20th century,1900,1927,Folding fan mounted as an album leaf; ink and color on alum paper,7 3/8 x 20 3/8 in. (18.7 x 51.8 cm),"Gift of Robert Hatfield Ellsworth, in memory of La Ferne Hatfield Ellsworth, 1986",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/36208,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.267.117,false,true,36205,Asian Art,Folding fan mounted as an album leaf,近代 林紓 合掌峰 扇面|Hezhang Peak,China,,,,,Artist,,Lin Shu,"Chinese, 1852–1924",,Lin Shu,Chinese,1852,1924,dated 1921,1921,1921,Folding fan mounted as an album leaf; ink and color on alum paper,7 11/16 x 21 1/4 in. (19.5 x 54 cm),"Gift of Robert Hatfield Ellsworth, in memory of La Ferne Hatfield Ellsworth, 1986",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/36205,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.267.118,false,true,36206,Asian Art,Handscroll,近代 林紓 詩廬圖 卷|The Poetry Cottage,China,,,,,Artist,,Lin Shu,"Chinese, 1852–1924",,Lin Shu,Chinese,1852,1924,dated 1914,1914,1914,Handscroll; ink on paper,Image: 8 7/8 x 39 in. (22.5 x 99.1 cm) Overall with mounting: 11 x 320 3/8 in. (27.9 x 813.8 cm),"Gift of Robert Hatfield Ellsworth, in memory of La Ferne Hatfield Ellsworth, 1986",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/36206,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.267.128,false,true,36211,Asian Art,Folding fan mounted as an album leaf,近代 吳觀岱 山水人物 扇面|Landscape and Figure,China,,,,,Artist,,Wu Guandai,"Chinese, 1862–1929",,Wu Guandai,Chinese,1862,1929,early 20th century,1900,1929,Folding fan mounted as an album leaf; ink and color on alum paper,7 5/16 x 20 3/4 in. (18.6 x 52.7 cm),"Gift of Robert Hatfield Ellsworth, in memory of La Ferne Hatfield Ellsworth, 1986",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/36211,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.267.127,false,true,36210,Asian Art,Folding fan mounted as an album leaf,近代 楊逸 青松 扇面|Pine,China,,,,,Artist,,Yang Yi,"Chinese, 1864–1929",,Yang Yi,Chinese,1864,1929,dated 1923,1923,1923,Folding fan mounted as an album leaf; ink and color on alum paper,8 x 21 1/2 in. (20.3 x 54.6 cm),"Gift of Robert Hatfield Ellsworth, in memory of La Ferne Hatfield Ellsworth, 1986",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/36210,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.267.136,false,true,36216,Asian Art,Folding fan mounted as an album leaf,近代 顧麟士 臨溪亭子圖 扇面|Pavilion Beside a Rock Garden and Stream,China,,,,,Artist,,Gu Linshi,"Chinese, 1865–1930",,Gu Linshi,Chinese,1865,1930,dated 1921,1921,1921,Folding fan mounted as an album leaf; ink and color on alum paper,7 x 19 5/8 in. (17.8 x 49.8 cm),"Gift of Robert Hatfield Ellsworth, in memory of La Ferne Hatfield Ellsworth, 1986",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/36216,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.267.154,false,true,49629,Asian Art,Hanging scroll,近代 王震 山羊 軸|Two Goats,China,,,,,Artist,,Wang Zhen,"Chinese, 1867–1938",,WANG ZHEN,Chinese,1867,1938,dated 1914,1914,1914,Hanging scroll; ink and color on paper,57 1/2 x 15 5/8 in. (146.1 x 39.7 cm),"Gift of Robert Hatfield Ellsworth, in memory of La Ferne Hatfield Ellsworth, 1986",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49629,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.267.155,false,true,49630,Asian Art,Hanging scroll,近代 王震 漁翁圖 軸|Returning Fisherman,China,,,,,Artist,,Wang Zhen,"Chinese, 1867–1938",,WANG ZHEN,Chinese,1867,1938,dated 1917,1917,1917,Hanging scroll; ink and color on paper,Image: 70 1/8 x 37 1/8 in. (178.1 x 94.3 cm) Overall with mounting: 109 x 43 in. (276.9 x 109.2 cm) Overall with knobs: 109 x 46 3/4 in. (276.9 x 118.7 cm),"Gift of Robert Hatfield Ellsworth, in memory of La Ferne Hatfield Ellsworth, 1986",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49630,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.267.156,false,true,41504,Asian Art,Hanging scroll,近代 王震 佛祖圖 軸|Buddhist Sage,China,,,,,Artist,,Wang Zhen,"Chinese, 1867–1938",,WANG ZHEN,Chinese,1867,1938,dated 1928,1928,1928,Hanging scroll; ink and color on paper,78 1/2 x 36 7/8 in. (199.4 x 93.7 cm),"Gift of Robert Hatfield Ellsworth, in memory of La Ferne Hatfield Ellsworth, 1986",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/41504,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.267.104,false,true,49603,Asian Art,Album leaf,近代 陳衡恪 臨水書閣 冊頁|Studio by the Water,China,,,,,Artist,,Chen Hengke,"Chinese, 1876–1923",,Chen Hengke,Chinese,1876,1923,dated 1921,1921,1921,Album leaf; ink and color on paper,13 1/4 x 18 3/4 in. (33.7 x 47.6 cm),"Gift of Robert Hatfield Ellsworth, in memory of La Ferne Hatfield Ellsworth, 1986",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49603,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.267.105,false,true,49604,Asian Art,Folding fan mounted as an album leaf,近代 陳衡恪 水仙蘭花 扇面|Narcissus and Orchid,China,,,,,Artist,,Chen Hengke,"Chinese, 1876–1923",,Chen Hengke,Chinese,1876,1923,dated 1920,1920,1920,Folding fan mounted as an album leaf; ink and color on alum paper,7 7/8 x 21 2/3 in. (20.0 x 55.0 cm),"Gift of Robert Hatfield Ellsworth, in memory of La Ferne Hatfield Ellsworth, 1986",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49604,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.267.106,false,true,49605,Asian Art,Folding fan mounted as an album leaf,近代 陳衡恪 秋山蕭寺 扇面|Remote Temple on the Autumn Mountain,China,,,,,Artist,,Chen Hengke,"Chinese, 1876–1923",,Chen Hengke,Chinese,1876,1923,dated 1920,1920,1920,Folding fan mounted as an album leaf; ink and color on alum paper,7 15/16 x 22 in. (20.2 x 55.9 cm),"Gift of Robert Hatfield Ellsworth, in memory of La Ferne Hatfield Ellsworth, 1986",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49605,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.267.108,false,true,49607,Asian Art,Album leaf,近代 陳衡恪 玉簪花 冊頁|Plantain Lily,China,,,,,Artist,,Chen Hengke,"Chinese, 1876–1923",,Chen Hengke,Chinese,1876,1923,early 20th century,1900,1923,Album leaf; ink and color on paper,11 x 17 1/4 in. (27.9 x 43.8 cm),"Gift of Robert Hatfield Ellsworth, in memory of La Ferne Hatfield Ellsworth, 1986",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49607,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.267.109,false,true,36202,Asian Art,Album leaf,近代 陳衡恪 梅花 冊頁|Plum,China,,,,,Artist,,Chen Hengke,"Chinese, 1876–1923",,Chen Hengke,Chinese,1876,1923,early 20th century,1900,1923,Album leaf; ink and color on paper,11 x 17 1/4 in. (27.9 x 43.8 cm),"Gift of Robert Hatfield Ellsworth, in memory of La Ferne Hatfield Ellsworth, 1986",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/36202,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.267.110,false,true,49608,Asian Art,Album leaf,近代 陳衡恪 梨花 軸|Pear-blossoms,China,,,,,Artist,,Chen Hengke,"Chinese, 1876–1923",,Chen Hengke,Chinese,1876,1923,early 20th century,1900,1923,Album leaf; ink and color on paper,11 x 17 1/4 in. (27.9 x 43.8 cm),"Gift of Robert Hatfield Ellsworth, in memory of La Ferne Hatfield Ellsworth, 1986",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49608,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"1986.267.107a, b",false,true,49606,Asian Art,Folding fan mounted as album leaves,近代 陳衡恪 山水 扇面兩幀|Landscapes,China,,,,,Artist,,Chen Hengke,"Chinese, 1876–1923",,Chen Hengke,Chinese,1876,1923,early 20th century,1900,1923,Two sides of a folding fan mounted as two album leaves; ink and color on alum paper,5 1/8 x 17 1/8 in. (13.0 x 43.5 cm),"Gift of Robert Hatfield Ellsworth, in memory of La Ferne Hatfield Ellsworth, 1986",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49606,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.267.121,false,true,49614,Asian Art,Folding fan mounted as an album leaf,近代 金城 山水 扇面|Landscape,China,,,,,Artist,,Jin Cheng,"Chinese, 1878–1926",,Jin Cheng,Chinese,1878,1926,early 20th century,1900,1926,Folding fan mounted as an album leaf; ink and color on alum paper,8 3/8 x 26 7/8 in. (21.3 x 68.3 cm),"Gift of Robert Hatfield Ellsworth, in memory of La Ferne Hatfield Ellsworth, 1986",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49614,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.267.122,false,true,49615,Asian Art,Folding fan mounted as an album leaf,近代 金城 牡丹 扇面|Peony,China,,,,,Artist,,Jin Cheng,"Chinese, 1878–1926",,Jin Cheng,Chinese,1878,1926,early 20th century,1900,1926,Folding fan mounted as an album leaf; ink and color on alum paper,8 3/8 x 26 7/8 in. (21.3 x 68.3 cm),"Gift of Robert Hatfield Ellsworth, in memory of La Ferne Hatfield Ellsworth, 1986",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49615,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.267.253,false,true,49663,Asian Art,Album leaf,近代 丁輔之 雜果圖 冊頁|Fruit,China,,,,,Artist,,Ding Fuzhi,"Chinese, 1879–1946",,Ding Fuzhi,Chinese,1879,1946,dated 1945,1945,1945,Album leaf; ink and color on paper,12 1/8 x 25 1/8 in. (30.8 x 63.8 cm),"Gift of Robert Hatfield Ellsworth, in memory of La Ferne Hatfield Ellsworth, 1986",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49663,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.267.254,false,true,49664,Asian Art,Album leaf,近代 丁輔之 冰盤進夏圖 冊頁|Dish of Iced Summer Fruit,China,,,,,Artist,,Ding Fuzhi,"Chinese, 1879–1946",,Ding Fuzhi,Chinese,1879,1946,dated 1945,1945,1945,Album leaf; ink and color on paper,12 1/8 x 25 1/8 in. (30.8 x 63.8 cm),"Gift of Robert Hatfield Ellsworth, in memory of La Ferne Hatfield Ellsworth, 1986",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49664,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.267.145,false,true,49626,Asian Art,Folding fan mounted as an album leaf,近代 俞明 桐窗仕女 扇面|A Beauty at the Window by a Wutong Tree,China,,,,,Artist,,Yu Ming,"Chinese, 1884–1935",,Yu Ming,Chinese,1884,1935,dated 1923,1923,1923,Folding fan mounted as an album leaf; ink and color on alum paper,7 1/2 x 21 7/16 in. (19.1 x 54.5 cm),"Gift of Robert Hatfield Ellsworth, in memory of La Ferne Hatfield Ellsworth, 1986",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49626,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.267.146,false,true,49627,Asian Art,Folding fan mounted as an album leaf,近代 俞明 絲路行旅圖 扇面|Travel on the Silk Road,China,,,,,Artist,,Yu Ming,"Chinese, 1884–1935",,Yu Ming,Chinese,1884,1935,early 20th century,1900,1933,Folding fan mounted as an album leaf; ink and color on alum paper,8 x 21 3/4 in. (20.3 x 55.2 cm),"Gift of Robert Hatfield Ellsworth, in memory of La Ferne Hatfield Ellsworth, 1986",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49627,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.267.147,false,true,36222,Asian Art,Album leaf,近代 俞明 堆雪獅子圖 冊頁|Making a Snow-lion,China,,,,,Artist,,Yu Ming,"Chinese, 1884–1935",,Yu Ming,Chinese,1884,1935,dated 1921,1921,1921,Album leaf; ink and color on paper,14 7/8 x 9 1/4 in. (37.8 x 23.5 cm),"Gift of Robert Hatfield Ellsworth, in memory of La Ferne Hatfield Ellsworth, 1986",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/36222,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.267.149,false,true,36224,Asian Art,Hanging scroll,近代 俞明 米芾拜石圖 軸|Mi Fu at Stone Worship,China,,,,,Artist,,Yu Ming,"Chinese, 1884–1935",,Yu Ming,Chinese,1884,1935,early 20th century,1900,1933,Hanging scroll; ink and color on paper,Overall with mounting: 69 3/4 × 18 1/8 in. (177.2 × 46 cm),"Gift of Robert Hatfield Ellsworth, in memory of La Ferne Hatfield Ellsworth, 1986",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/36224,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.267.141,false,true,49623,Asian Art,Hanging scroll,近代 高奇峰 啄木鳥 軸|Woodpecker,China,,,,,Artist,,Gao Qifeng,"Chinese, 1889–1933",,Gao Qifeng,Chinese,1889,1933,dated 1927,1927,1927,Hanging scroll; ink and color on alum paper,Image: 32 5/8 × 13 3/8 in. (82.9 × 34 cm) Overall with mounting: 75 1/4 × 20 5/16 in. (191.1 × 51.6 cm) Overall with knobs: 75 1/4 × 24 1/8 in. (191.1 × 61.3 cm),"Gift of Robert Hatfield Ellsworth, in memory of La Ferne Hatfield Ellsworth, 1986",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49623,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -13.220.124,false,true,40311,Asian Art,Handscroll,元/明 倣錢選 鮮于樞 歸去來辭 卷|Ode on Returning Home,China,,,,,Artist,After,Qian Xuan,"Chinese, ca. 1235–before 1307",,QIAN XUAN,Chinese,1235,1307,14th–15th century,1300,1499,"Handscroll; ink, color, and gold on paper",Image: 42 x 10 1/4 in. (106.7 x 26 cm) Overall with mounting: 12 1/4 in. x 13 ft. 6 in. (31.1 x 411.5 cm),"John Stewart Kennedy Fund, 1913",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/40311,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1973.121.4,false,true,45652,Asian Art,Handscroll,元 方從義 雲山圖 卷|Cloudy Mountains,China,Yuan (1271–1368),,,,Artist,,Fang Congyi,"Chinese, ca. 1301–after 1378",,Fang Congyi,Chinese,1301,1399,ca. 1360–70,1360,1370,Handscroll; ink and color on paper,Image: 10 3/8 x 57 in. (26.4 x 144.8 cm) Overall with mounting: 10 5/8 x 336 1/4 in. (27 x 854.1 cm),"Ex coll.: C. C. Wang Family, Purchase, Gift of J. Pierpont Morgan, by exchange, 1973",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45652,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1977.78,true,true,39901,Asian Art,Handscroll,唐 韓幹 照夜白圖 卷|Night-Shining White,China,Tang dynasty (618–907),,,,Artist,,Han Gan,"Chinese, active ca. 742–756",,HAN GAN,Chinese,0742,0756,ca. 750,740,760,Handscroll; ink on paper,Image: 12 1/8 x 13 3/8 in. (30.8 x 34 cm) Overall with mounting: 14 in. x 37 ft. 5 1/8 in. (35.4 cm x 11.4 m),"Purchase, The Dillon Fund Gift, 1977",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/39901,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1982.1.1,false,true,39959,Asian Art,Handscroll,金 傳楊邦基 聘金圖 巻|A Diplomatic Mission to the Jin,China,Jin dynasty (1115–1234),,,,Artist,Attributed to,Yang Bangji,"Chinese, ca. 1110–1181",,Yang Bangji,Chinese,1100,1191,ca. late 1150s,1156,1159,Handscroll; ink and color on silk,Image: 10 1/2 in. × 56 in. (26.7 × 142.2 cm) Overall with mounting: 11 5/8 in. × 26 ft. 11 3/4 in. (29.5 × 822.3 cm),"Edward Elliott Family Collection, Purchase, The Dillon Fund Gift, 1982",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/39959,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -49.6.12,false,true,51762,Asian Art,Album leaf,,China,Song dynasty (960–1279),,,,Artist,Attributed to,Xu Daoning,active ca. 1030–67,,Xu Daoning,Chinese,1020,1077,ca. 1000,1030,1067,Album leaf; painted silk,14 x 12 1/4 in. (35.6 x 31.1 cm),"Bequest of Ellis Gray Seymour, 1948",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/51762,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"1973.121.12a, b",false,true,40062,Asian Art,Fan mounted as an album leaf,南宋 傳閻次于 松壑隱棲圖 團扇|Hermitage by a Pine-covered Bluff,China,Song dynasty (960–1279),,,,Artist,Attributed to,Yan Ciyu,"Chinese, act. ca. 1164–81",,YAN CIYU,Chinese,1164,1181,second half of the 12th century,1164,1181,Fan mounted as an album leaf; ink and color on silk,"(a): 8 7/16 x 9 1/16 in. (21.4 x 23 cm), b): 8 1/2 x 9 1/16 in. (21.6 x 23 cm)","Ex coll.: C. C. Wang Family, Purchase, Gift of Mr. and Mrs. Jeremiah Milbank and Gift of Mary Phelps Smith, in memory of Howard Caswell Smith, by exchange, 1973",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/40062,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"1989.363.10a, b",false,true,40057,Asian Art,Handscroll,南宋 李結 西塞漁社圖 卷 |Fisherman's Lodge At Mount Xisai,China,Song dynasty (960–1279),,,,Artist,,Li Jie,"Chinese, 1124– before 1197",,LI JIE,Chinese,1124,1197,ca. 1170,1160,1180,Handscroll; ink and color on silk,Image (a): 16 in. × 53 3/4 in. (40.6 × 136.5 cm) Overall with mounting (a): 16 3/8 in. × 26 ft. 1/8 in. (41.6 × 792.8 cm) Overall with mounting (b): 16 3/8 in. × 27 ft. 9 5/16 in. (41.6 × 846.6 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/40057,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -47.18.32,false,true,40094,Asian Art,Fan mounted as an album leaf,南宋 傳陳居中 胡騎春獵圖 團扇|Nomads hunting with falcons,China,Song dynasty (960–1279),,,,Artist,Attributed to,Chen Juzhong,"Chinese, active ca. 1200–30",,Chen Juzhong,Chinese,1200,1230,early 13th century,1200,1233,Fan mounted as an album leaf; ink and color on silk,9 1/2 x 10 3/4 in. (24.1 x 27.3 cm),"From the Collection of A. W. Bahr, Purchase, Fletcher Fund, 1947",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/40094,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -47.18.30,false,true,36052,Asian Art,Fan mounted as an album leaf,南宋 佚名 倣馬遠 洞天論道圖 團扇|Conversation in a Cave,China,Song dynasty (960–1279),,,,Artist,After,Ma Yuan,"Chinese, active ca. 1190–1225",,MA YUAN,Chinese,1190,1225,13th century,1200,1225,Fan mounted as an album leaf; ink and color on silk,Image: 9 3/4 × 9 15/16 in. (24.8 × 25.2 cm) Mat: 15 1/2 × 15 1/2 in. (39.4 × 39.4 cm),"From the Collection of A. W. Bahr, Purchase, Fletcher Fund, 1947",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/36052,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -49.6.11,false,true,40053,Asian Art,Album leaf,,China,Song dynasty (960–1279),,,,Artist,In the style of,Xia Gui,"Chinese, active ca. 1195–1230",,Xia Gui,Chinese,1195,1230,ca. 1200,1190,1210,Album leaf; ink and color silk,9 1/8 x 9 3/8 in. (23.2 x 23.8 cm),"Bequest of Ellis Gray Seymour, 1948",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/40053,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -47.18.63,false,true,36055,Asian Art,Album leaf,南宋 馬麟 松下高士圖 冊頁 絹本|Landscape with great pine,China,Song dynasty (960–1279),,,,Artist,,Ma Lin,"Chinese, ca. 1180– after 1256",,MA LIN,Chinese,1180,1256,second quarter of the 13th century,1226,1250,Album leaf; ink and color on silk,9 15/16 x 10 1/4 in. (25.2 x 26 cm),"From the Collection of A. W. Bahr, Purchase, Fletcher Fund, 1947",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/36055,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -30.76.290,false,true,44506,Asian Art,Hanging scroll,南宋 金處士 十王圖 軸|Ten Kings of Hell,China,Song dynasty (960–1279),,,,Artist,,Jin Chushi,"Chinese, active late 12th century",,JIN CHUSHI,Chinese,1167,1199,before 1195,1167,1194,One of five of a set of ten hanging scrolls; ink and color on silk,Image: 51 x 19 1/2 in. (129.5 x 49.5 cm) Overall with mounting: 80 × 25 3/8 in. (203.2 × 64.5 cm) Overall with knobs: 80 x 27 1/2 in. (203.2 x 69.9 cm),"Rogers Fund, 1929",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/44506,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -30.76.291,false,true,44507,Asian Art,Hanging scroll,南宋 金處士 十王圖 軸|Ten Kings of Hell,China,Song dynasty (960–1279),,,,Artist,,Jin Chushi,"Chinese, active late 12th century",,JIN CHUSHI,Chinese,1167,1199,before 1195,1167,1194,One of five of a set of ten hanging scrolls; ink and color on silk,Image: 51 x 19 1/2 in. (129.5 x 49.5 cm) Overall with knobs: 80 x 27 1/2 in. (203.2 x 69.9 cm),"Rogers Fund, 1929",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/44507,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -30.76.292,false,true,44508,Asian Art,Hanging scroll,南宋 金處士 十王圖 軸(之三)|Ten Kings of Hell,China,Song dynasty (960–1279),,,,Artist,,Jin Chushi,"Chinese, active late 12th century",,JIN CHUSHI,Chinese,1167,1199,before 1195,1167,1194,One of five of a set of ten hanging scrolls; ink and color on silk,Image: 51 x 19 1/2 in. (129.5 x 49.5 cm) Overall with knobs: 80 x 27 1/2 in. (203.2 x 69.9 cm),"Rogers Fund, 1929",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/44508,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -30.76.293,false,true,44509,Asian Art,Hanging scroll,南宋 金處士 十王圖 軸|Ten Kings of Hell,China,Song dynasty (960–1279),,,,Artist,,Jin Chushi,"Chinese, active late 12th century",,JIN CHUSHI,Chinese,1167,1199,before 1195,1167,1194,One of five of a set of ten hanging scrolls; ink and color on silk,Image: 51 x 19 1/2 in. (129.5 x 49.5 cm) Overall with knobs: 80 x 27 1/2 in. (203.2 x 69.9 cm),"Rogers Fund, 1929",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/44509,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -30.76.294,false,true,44510,Asian Art,Hanging scroll,南宋 金處士 十王圖 軸|Ten Kings of Hell,China,Song dynasty (960–1279),,,,Artist,,Jin Chushi,"Chinese, active late 12th century",,JIN CHUSHI,Chinese,1167,1199,before 1195,1167,1194,One of five of a set of ten hanging scrolls; ink and color on silk,Image: 51 x 19 1/2 in. (129.5 x 49.5 cm) Overall with knobs: 80 x 27 1/2 in. (203.2 x 69.9 cm),"Rogers Fund, 1929",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/44510,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1982.2.1,false,true,40278,Asian Art,Hanging scroll,南宋 傳直翁 藥山李翱問道圖 軸|Meeting between Yaoshan and Li Ao,China,Song dynasty (960–1279),,,,Artist,Attributed to,Zhiweng,"Chinese, active first half of the 13th century",,ZHIWENG,Chinese,1200,1250,before 1256,1200,1255,Horizontal painting mounted as a hanging scroll; ink on paper,Image: 12 1/2 x 33 1/4 in. (31.8 x 84.5 cm) Overall with mounting: 49 1/8 x 34 in. (124.8 x 86.4 cm) Overall with knobs: 49 1/8 x 36 1/4 in. (124.8 x 92.1 cm),"Edward Elliott Family Collection, Purchase, The Dillon Fund Gift, 1982",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/40278,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1990.318,false,true,41479,Asian Art,Handscroll,明 董其昌 荊谿招隱圖 卷|Invitation to Reclusion at Jingxi,China,Ming Dinasty (1368–1644),,,,Artist,,Dong Qichang,"Chinese, 1555–1636",,Dong Qichang,Chinese,1555,1636,dated 1611,1611,1611,Handscroll; ink on paper,Image: 10 1/4 × 36 7/16 in. (26 × 92.6 cm),"Gift of Mr. and Mrs. Wan-go H. C. Weng, 1990",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/41479,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1982.2.3,false,true,45661,Asian Art,Handscroll,明 王紱 江山漁樂圖 卷|Joys of the Fisherman,China,Ming dynasty (1368–1644),,,,Artist,,Wang Fu,"Chinese, 1362–1416",,Wang Fu,Chinese,1362,1416,ca. 1410,1400,1420,Handscroll; ink on paper,Image: 10 5/8 x 22 ft. 7 1/8 in. (27 x 688.7 cm) Overall with mounting: 10 15/16 in. x 38 ft. 3 11/16 in. (27.8 x 1167.6 cm),"Ex coll.: C. C. Wang Family, Edward Elliott Family Collection, Purchase, The Dillon Fund Gift, 1982",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45661,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.141.3,false,true,41478,Asian Art,Handscroll,明 傳謝環 杏園雅集圖 卷|Elegant Gathering in the Apricot Garden,China,Ming dynasty (1368–1644),,,,Artist,After,Xie Huan,"Chinese, 1377–1452",,Xie Huan,Chinese,1377,1452,ca. 1437,1427,1447,Handscroll; ink and color on silk,Image: 14 5/8 x 95 3/4 in. (37.1 x 243.2 cm) Overall with mounting: 14 3/4 in. x 41 ft. 11 1/4 in. (37.5 x 1278.3 cm),"Purchase, The Dillon Fund Gift, 1989",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/41478,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1992.132,false,true,44699,Asian Art,Hanging scroll,明 戴進 雪歸圖 軸|Returning home through the snow,China,Ming dynasty (1368–1644),,,,Artist,,Dai Jin,"Chinese, 1388–1462",,Dai Jin,Chinese,1388,1462,ca. 1455,1445,1465,Hanging scroll; ink and color on silk,Image: 66 x 32 1/2 in. (167.6 x 82.6 cm) Overall with mounting: 118 3/4 x 38 1/2 in. (301.6 x 97.8 cm) Overall with knobs: 118 3/4 x 42 3/4 in. (301.6 x 108.6 cm),"Purchase, John M. Crawford Jr. Bequest, 1992",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/44699,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.235.1,false,true,44590,Asian Art,Hanging scroll,明 夏昶 清風高節圖 軸|Bamboo in Wind,China,Ming dynasty (1368–1644),,,,Artist,,Xia Chang,"Chinese, 1388–1470",,Xia Chang,Chinese,1388,1470,ca. 1460,1450,1470,Hanging scroll; ink on paper,Image: 80 1/16 x 23 1/2 in. (203.4 x 59.7 cm) Overall with mounting: 118 x 29 3/8 in. (299.7 x 74.6 cm) Overall with knobs: 118 x 32 13/16 in. (299.7 x 83.3 cm),"Edward Elliott Family Collection, Gift of Douglas Dillon, 1989",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/44590,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.44,false,true,45675,Asian Art,Handscroll,明 姚綬 文飲圖 卷|Drinking and Composing Poetry,China,Ming dynasty (1368–1644),,,,Artist,,Yao Shou,"Chinese, 1423–1495",,Yao Shou,Chinese,1423,1495,1485,1485,1485,Handscroll; ink on paper,Image: 9 3/16 x 30 3/8 in. (23.3 x 77.2 cm) Colophon: 9 3/16 x 30 in. (23.3 x 76.2 cm) Overall with mounting: 9 11/16 x 299 3/16 in. (24.6 x 759.9 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45675,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1978.5,false,true,36123,Asian Art,Handscroll,,China,Ming dynasty (1368–1644),,,,Artist,After,Zhang Bi,"Chinese, 1425–1487",,Zhang Bi,Chinese,1425,1487,dated 1478,1478,1478,Handscroll; ink on paper,Image: 12 1/4 x 297 1/2 in. (31.1 x 755.7 cm) Overall with mounting: 12 3/4 x 341 in. (32.4 x 866.1 cm),"Purchase, Bequest of Dorothy Graham Bennett, 1978",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/36123,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1979.75.1,false,true,45683,Asian Art,Handscroll,明 沈周 溪山秋色圖 卷|明 沈周 溪山秋色圖 卷明 楷書溪山秋色圖 引首|Autumn Colors among Streams and Mountains,China,Ming dynasty (1368–1644),,,,Artist,,Shen Zhou,"Chinese, 1427–1509",,Shen Zhou,Chinese,1427,1509,ca. 1490–1500,1480,1510,Handscroll; ink on paper,Image: 8 1/8 in. x 21 ft. 1/4 in. (20.6 x 640.7 cm) Overall with mounting: 10 1/4 in. x 36 ft. 4 1/4 in. (26 x 1108.1 cm),"Gift of Douglas Dillon, 1979",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45683,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.46,false,true,45682,Asian Art,Hanging scroll,明 沈周 秋林閒釣圖 軸|Silent Angler in an Autumn Wood,China,Ming dynasty (1368–1644),,,,Artist,,Shen Zhou,"Chinese, 1427–1509",,Shen Zhou,Chinese,1427,1509,dated 1475,1475,1475,Hanging scroll; ink and color on paper,Image: 60 x 24 3/4 in. (152.4 x 62.9 cm) Overall with mounting: 107 x 32 1/8 in. (271.8 x 81.6 cm) Overall with knobs: 107 x 35 1/4 in. (271.8 x 89.5 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45682,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1997.166,false,true,39483,Asian Art,Hanging scroll,明 郭詡 稱書圖 軸|Weighing Books,China,Ming dynasty (1368–1644),,,,Artist,,Guo Xu,"Chinese, 1456–1532",,Guo Xu,Chinese,1456,1532,early 16th century,1500,1532,Hanging scroll; ink and color on silk,Image: 48 3/8 x 28 in. (122.9 x 71.1 cm) Overall with mounting: 103 x 34 1/8 in. (261.6 x 86.7 cm) Overall with knobs: 103 x 38 3/8 in. (261.6 x 97.5 cm),"Friends of Asian Art, Purchase, The B. Y. Lam Foundation Gift, 1997",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/39483,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -67.6.1,false,true,36093,Asian Art,Hanging scroll,,China,Ming dynasty (1368–1644),,,,Artist,After,Tang Yin,"Chinese, 1470–1524",,Tang Yin,Chinese,1470,1524,late 15th–early 16th century,1470,1524,Hanging scroll; ink and color on silk,Image: 28 x 55 in. (71.1 x 139.7 cm),"Purchase, Bequest of Dorothy Graham Bennett, 1967",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/36093,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.53,false,true,45752,Asian Art,Handscroll,,China,Ming dynasty (1368–1644),,,,Artist,After,Tang Yin,"Chinese, 1470–1524",,Tang Yin,Chinese,1470,1524,ca. 1508,1498,1518,Handscroll; ink on paper,Image: 11 11/16 x 42 3/8 in. (29.7 x 107.6 cm) Overall with mounting: 12 x 375 1/4 in. (30.5 x 953.1 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45752,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1982.7.1,false,true,45779,Asian Art,Folding fan mounted as an album leaf,,China,Ming dynasty (1368–1644),,,,Artist,,Wen Zhengming,"Chinese, 1470–1559",,Wen Zhengming,Chinese,1470,1559,dated 1543,1543,1543,Folding fan mounted as an album leaf; ink and color on gold-flecked paper,9 3/4 x 20 3/4 in. (24.8 x 52.7 cm),"Purchase, The Dillon Fund Gift, 1982",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45779,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.303,false,true,44601,Asian Art,Handscroll,明 文徵明 叢桂齋圖 卷|The Cassia Grove Studio,China,Ming dynasty (1368–1644),,,,Artist,,Wen Zhengming,"Chinese, 1470–1559",,Wen Zhengming,Chinese,1470,1559,ca. 1532,1522,1542,Handscroll; ink and color on paper,Image: 12 7/16 x 22 1/8 in. (31.6 x 56.2 cm) Overall with mounting: 14 x 322 11/16 in. (35.6 x 819.6 cm),"Gift of Douglas Dillon, 1989",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/44601,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.60,false,true,45776,Asian Art,Handscroll,明 文徵明 東林避暑圖 卷 |Summer Retreat in the Eastern Grove,China,Ming dynasty (1368–1644),,,,Artist,,Wen Zhengming,"Chinese, 1470–1559",,Wen Zhengming,Chinese,1470,1559,datable to before 1515,1470,1514,Handscroll; ink on paper,Image (painting): 12 1/2 x 42 1/2 in. (31.8 x 108 cm) Image (colophon): 12 1/2 x 38 3/8 in. (31.8 x 97.5 cm) Overall with mounting: 13 x 117 1/2 in. (33 x 298.5 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45776,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1979.458.1a–ii,false,true,39654,Asian Art,Album,"明 文徵明 拙政園圖詩 冊|Garden of the Inept Administrator",China,Ming dynasty (1368–1644),,,,Artist,,Wen Zhengming,"Chinese, 1470–1559",,Wen Zhengming,Chinese,1470,1559,dated 1551,1551,1551,Album of eight painted leaves with facing leaves inscribed with poems; ink on paper,Image: 10 3/8 × 10 3/4 in. (26.4 × 27.3 cm) Image with mounting: 15 3/8 × 16 3/4 in. (39.1 × 42.5 cm) Double leaf unfolded: 15 3/8 × 33 1/2 in. (39.1 × 85.1 cm) Mat: 18 5/16 × 34 in. (46.5 × 86.4 cm),"Gift of Douglas Dillon, 1979",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/39654,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.266.2,false,true,45802,Asian Art,Hanging scroll,明 陳淳 暑園圖 軸|Summer Garden,China,Ming dynasty (1368–1644),,,,Artist,,Chen Chun,"Chinese, 1483–1544",,Chen Chun,Chinese,1483,1544,ca. 1530,1520,1540,Hanging scroll; ink and color on paper,Image: 126 1/8 x 39 1/4 in. (320.4 x 99.7 cm) Overall with mounting: 171 x 48 3/8 in. (434.3 x 122.9 cm) Overall with knobs: 171 x 52 7/8 in. (434.3 x 134.3 cm),"Gift of Douglas Dillon, 1986",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45802,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.266.1a–u,false,true,45797,Asian Art,Album,明 仿陳淳 雜花圖 冊|Garden Flowers,China,Ming dynasty (1368–1644),,,,Artist,After,Chen Chun,"Chinese, 1483–1544",,Chen Chun,Chinese,1483,1544,dated 1540,1540,1540,Album of sixteen paintings and one leaf of calligraphy; ink and color on paper,Image (six leaves): 12 13/16 x 22 9/16 in. (32.5 x 57.3 cm) Image (ten leaves): 13 1/8 x 22 3/4 in. (33.3 x 57.8 cm),"Gift of Douglas Dillon, 1986",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45797,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.371,false,true,44606,Asian Art,Hanging scroll,,China,Ming dynasty (1368–1644),,,,Artist,,Lu Zhi,"Chinese, 1495–1576",,Lu Zhi,Chinese,1495,1576,dated 1556,1556,1556,Hanging scroll; ink and color on paper,Image: 52 3/8 x 24 3/4 in. (133 x 62.9 cm) Overall with mounting: 90 1/2 x 31 in. (229.9 x 78.7 cm) Overall with knobs: 90 1/2 x 35 in. (229.9 x 88.9 cm),"Gift of Michael B. Weisbrod, 1989",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/44606,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.266.3,false,true,45815,Asian Art,Hanging scroll,明 陸治 種菊圖 軸|Planting Chrysanthemums,China,Ming dynasty (1368–1644),,,,Artist,,Lu Zhi,"Chinese, 1495–1576",,Lu Zhi,Chinese,1495,1576,mid-16th century,1534,1566,Hanging scroll; ink and pale color on paper,Image: 42 x 10 3/4 in. (106.7 x 27.3 cm) Overall with mounting: 107 1/4 x 19 in. (272.4 x 48.3 cm) Overall with knobs: 107 1/4 x 22 1/2 in. (272.4 x 57.2 cm),"Edward Elliott Family Collection, Gift of Douglas Dillon, 1986",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45815,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1981.285.9,false,true,36135,Asian Art,Hanging scroll,"明 文嘉 為項元汴畫山水圖 軸|Landscape Dedicated to Xiang Yuanbian",China,Ming dynasty (1368–1644),,,,Artist,,Wen Jia,"Chinese, 1501–1583",,Wen Jia,Chinese,1501,1583,dated 1578,1578,1578,Hanging scroll; ink and color on paper,Image: 46 1/4 x 15 11/16 in. (117.5 x 39.8 cm) Overall with mounting: 90 3/4 x 23 3/8 in. (230.5 x 59.4 cm) Overall with knobs: 90 3/4 x 26 in. (230.5 x 66 cm),"Edward Elliott Family Collection, Purchase, The Dillon Fund Gift, 1981",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/36135,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.83,false,true,45032,Asian Art,Handscroll,明 傳項元汴 秋江圖 卷|River Landscape,China,Ming dynasty (1368–1644),,,,Artist,,Xiang Yuanbian,"Chinese, 1525–1590",,Xiang Yuanbian,Chinese,1525,1590,dated 1578,1578,1578,Handscroll; ink on paper,Image: 11 7/8 x 36 1/2 in. (30.2 x 92.7 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45032,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1985.214.148,false,true,48934,Asian Art,Hanging scroll,明 莫是龍 倣黃公望山水圖 軸|Landscape in the Style of Huang Gongwang,China,Ming dynasty (1368–1644),,,,Artist,,Mo Shilong,"Chinese, 1537–1587",,Mo Shilong,Chinese,1537,1587,dated 1581,1581,1581,Hanging scroll; ink and color on paper,Image: 46 7/8 x 16 1/8 in. (119.1 x 41 cm) Overall with mounting: 90 x 23 1/4 in. (228.6 x 59.1 cm) Overall with knobs: 90 x 26 3/4 in. (228.6 x 67.9 cm),"Gift of Ernest Erickson Foundation, 1985",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/48934,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1982.1.7,false,true,48932,Asian Art,Hanging scroll,明 傳馬守真 蘭石圖 軸|Orchid and Rock,China,Ming dynasty (1368–1644),,,,Artist,Attributed to,Ma Shouzhen,"Chinese, 1548–1604",,Ma Shouzhen,Chinese,1548,1604,dated 1572,1572,1572,Hanging scroll; ink on paper,Image: 20 3/4 x 11 1/2 in. (52.7 x 29.2 cm) Image (with inscription): 27 5/8 x 11 1/2 in. (70.2 x 29.2 cm) Overall with mounting: 81 1/2 x 18 3/4 in. (207 x 47.6 cm) Overall with knobs: 81 1/2 x 23 in. (207 x 58.4 cm),"Edward Elliott Family Collection, Purchase, The Dillon Fund Gift, 1982",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/48932,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -55.211.1,false,true,36070,Asian Art,Handscroll,,China,Ming dynasty (1368–1644),,,,Artist,Attributed to,Dong Qichang,"Chinese, 1555–1636",,Dong Qichang,Chinese,1555,1636,dated 1611 and 1612,1350,1650,Handscroll; ink on satin,10 1/5 x 86 1/4 in. (25.9 x 219.1 cm),"Gift of Wan-go H. C. Weng, 1955",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/36070,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1979.75.2,false,true,48949,Asian Art,Hanging scroll,明 董其昌 溪山樾館圖 軸 紙本|Shaded Dwellings among Streams and Mountains,China,Ming dynasty (1368–1644),,,,Artist,,Dong Qichang,"Chinese, 1555–1636",,Dong Qichang,Chinese,1555,1636,ca. 1622–25,1612,1635,Hanging scroll; ink on paper,Image: 62 3/8 x 28 3/8 in. (158.4 x 72.1 cm) Overall with mounting: 99 x 33 1/8 in. (251.5 x 84.1 cm) Overall with knobs: 99 x 37 in. (251.5 x 94 cm),"Gift of Douglas Dillon, 1979",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/48949,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1979.500.2,false,true,48952,Asian Art,Hanging scroll,,China,Ming dynasty (1368–1644),,,,Artist,,Dong Qichang,"Chinese, 1555–1636",,Dong Qichang,Chinese,1555,1636,dated 1632,1632,1632,Hanging scroll; ink on paper,Image: 60 7/8 x 21 1/2 in. (154.6 x 54.6 cm) Overall with mounting: 105 3/4 x 28 1/4 in. (268.6 x 71.8 cm) Overall with knobs: 105 3/4 x 32 1/4 in. (268.6 x 81.9 cm),"Gift of Douglas Dillon, 1979",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/48952,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.99,false,true,48953,Asian Art,Hanging scroll,明 董其昌 倣倪瓚山水圖 軸|Landscape with Trees in the Manner of Ni Zan (1301–1374),China,Ming dynasty (1368–1644),,,,Artist,,Dong Qichang,"Chinese, 1555–1636",,Dong Qichang,Chinese,1555,1636,ca. 1622–25,1612,1635,Hanging scroll; ink on paper,Image: 45 1/2 x 18 in. (115.6 x 45.7 cm) Overall with mounting: 94 7/8 x 27 1/2 in. (241 x 69.9 cm) Overall with knobs: 94 7/8 x 31 1/2 in. (241 x 80 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/48953,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.102,false,true,48951,Asian Art,Hanging scroll,,China,Ming dynasty (1368–1644),,,,Artist,,Dong Qichang,"Chinese, 1555–1636",(and assistants),Dong Qichang,Chinese,1555,1636,dated 1630,1630,1630,Hanging scroll; ink and color on silk,Overall with mounting: 130 7/8 x 46 1/8 in. (332.4 x 117.2 cm) Image: 91 x 37 in. (231.1 x 94 cm) Overall with knobs: 130 7/8 x 50 1/2 in. (332.4 x 128.3 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/48951,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.372a–h,false,true,41480,Asian Art,Album,明 董其昌 山水圖詩 冊|Landscapes and Poems,China,Ming dynasty (1368–1644),,,,Artist,,Dong Qichang,"Chinese, 1555–1636",,Dong Qichang,Chinese,1555,1636,"17th century, probably after 1625",1625,1636,"Album of eight paintings and eight calligraphies; ink, gold and color on gold-flecked paper",12 5/8 x 9 1/8 in. (32.1 x 23.2 cm),"Gift of Mr. and Mrs. Wan-go H. C. Weng, 1989",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/41480,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.266.5a–k,false,true,48950,Asian Art,Album,明 董其昌 山水圖 冊 紙本|Landscapes after old masters,China,Ming dynasty (1368–1644),,,,Artist,,Dong Qichang,"Chinese, 1555–1636",,Dong Qichang,Chinese,1555,1636,dated 1630,1630,1630,Album of eight leaves; ink on paper,Image (each): 9 5/8 x 6 5/16 in. (24.4 x 16 cm),"Edward Elliott Family Collection, Gift of Douglas Dillon, 1986",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/48950,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.108,false,true,48969,Asian Art,Handscroll,明 張瑞圖 後赤壁賦圖 卷|Second Ode on the Red Cliff,China,Ming dynasty (1368–1644),,,,Artist,,Zhang Ruitu,"Chinese, 1570–1641",,Zhang Ruitu,Chinese,1570,1641,dated 1628,1628,1628,Handscroll; ink on satin,Image: 11 in. x 10 ft. 6 in. (27.9 x 320 cm) Overall with mounting: 12 1/4 in. x 34 ft. 2 3/4 in. (31.1 x 1043.3 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/48969,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -13.100.70,false,true,35988,Asian Art,Folding fan mounted as an album leaf,,China,Ming dynasty (1368–1644),,,,Artist,,Li Liufang,"Chinese, 1575–1629",,Li Liufang,Chinese,1575,1629,dated 1613,1613,1613,Folding fan mounted as an album leaf; ink on gold paper,7 7/32 x 21 7/8 in. (18.4 x 55.6 cm),"John Stewart Kennedy Fund, 1913",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/35988,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1979.26,false,true,39713,Asian Art,Hanging scroll,明 藍瑛 紅友圖 軸|Red Friend,China,Ming dynasty (1368–1644),,,,Artist,,Lan Ying,"Chinese, 1585–1664",,Lan Ying,Chinese,1585,1664,16th– mid-17th century,1500,1650,Hanging scroll; ink and color on paper,Image: 58 5/8 x 18 5/8 in. (148.9 x 47.3 cm) Overall with mounting: 85 3/4 x 25 5/8 in. (217.8 x 65.1 cm) Overall with knobs: 85 3/4 x 29 in. (217.8 x 73.7 cm),"Gift of Mr. and Mrs. Earl Morse, in honor of Douglas Dillon, 1979",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/39713,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.114,false,true,49019,Asian Art,Hanging scroll,明 藍瑛 春江漁隱圖 軸|Hermit-Fisherman on a Spring River,China,Ming dynasty (1368–1644),,,,Artist,,Lan Ying,"Chinese, 1585–1664",,Lan Ying,Chinese,1585,1664,dated 1632,1632,1632,Hanging scroll; ink and color on silk,Image: 72 3/4 x 35 3/4 in. (184.8 x 90.8 cm) Overall with mounting: 98 3/4 x 43 1/8 in. (250.8 x 109.5 cm) Overall with knobs: 98 3/4 x 46 3/8 in. (250.8 x 117.8 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49019,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.2.2a–l,false,true,49021,Asian Art,Album,明 藍瑛 仿宋元山水圖 冊 紙本|Landscapes after Song and Yuan masters,China,Ming dynasty (1368–1644),,,,Artist,,Lan Ying,"Chinese, 1585–1664",,Lan Ying,Chinese,1585,1664,dated 1642,1642,1642,Album of twelve leaves; ink and color on paper,12 7/16 x 9 3/4 in. (31.6 x 24.8 cm),"The Sackler Fund, 1970",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49021,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.120,false,true,39717,Asian Art,Hanging scroll,,China,Ming dynasty (1368–1644),,,,Artist,,Ni Yuanlu,"Chinese, 1593–1644",,Ni Yuanlu,Chinese,1593,1644,16th–mid-17th century,1593,1644,Hanging scroll; ink on silk,Image: 51 1/2 x 17 7/8 in. (130.8 x 45.4 cm) Overall with mounting: 86 3/4 x 25 3/8 in. (220.3 x 64.5 cm) Overall with knobs: 86 3/4 x 28 3/8 in. (220.3 x 72.1 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/39717,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1981.285.3a–h,false,true,49074,Asian Art,Album,"明/清 項聖謨 山水花鳥圖 冊|Landscapes, Flowers and Birds",China,Ming dynasty (1368–1644),,,,Artist,,Xiang Shengmo,"Chinese, 1597–1658",,Xiang Shengmo,Chinese,1597,1658,dated 1639,1639,1639,Album of eight paintings; ink and color on paper,11 1/8 x 8 7/8 in. (28.3 x 22.5 cm),"Edward Elliott Family Collection, Purchase, The Dillon Fund Gift, 1981",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49074,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1992.197,false,true,41470,Asian Art,Hanging scroll,明/清 陳洪綬 準提佛母法像圖 軸|Bodhisattva Guanyin in the Form of the Buddha Mother,China,Ming dynasty (1368–1644),,,,Artist,,Chen Hongshou,"Chinese, 1599–1652",,Chen Hongshou,Chinese,1599,1652,dated 1620,1620,1620,Hanging scroll; ink on paper,Image: 49 1/4 x 19 1/16 in. (125.1 x 48.4 cm) Overall with mounting: 96 3/4 x 24 9/16 in. (245.7 x 62.4 cm) Overall with knobs: 96 3/4 x 28 1/2 in. (245.7 x 72.4 cm),"Purchase, Friends of Asian Art Gifts, 1992",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/41470,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.278.1,false,true,39558,Asian Art,Hanging scroll,明/清 傳陳洪綬 青綠山水圖 軸|Landscape in the Blue-and-Green Manner,China,Ming dynasty (1368–1644),,,,Artist,,Chen Hongshou,"Chinese, 1599–1652",,Chen Hongshou,Chinese,1599,1652,dated 1633,1633,1633,Hanging scroll; ink and color on silk,Image: 92 3/4 x 30 5/8 in. (235.6 x 77.8 cm) Overall with mounting: 126 5/8 x 34 5/8 in. (321.6 x 87.9 cm) Overall with rollers: 126 5/8 x 37 5/8 in. (321.6 x 95.6 cm),"Gift of Mr. and Mrs. Earl Morse, 1972",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/39558,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1985.121a–l,false,true,44759,Asian Art,Album,"明/清 陳洪綬 山水人物花卉圖 冊|Landscapes, Figures, and Flowers",China,Ming dynasty (1368–1644),,,,Artist,,Chen Hongshou,"Chinese, 1599–1652",,Chen Hongshou,Chinese,1599,1652,dated 1618–1622,1618,1622,Album of twelve paintings; ink and color on paper,Each leaf: 8 3/4 x 3 5/8 in. (22.2 x 9.2 cm),"Purchase, Friends of Far Eastern Art Gifts, 1985",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/44759,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.112a–l,false,true,37395,Asian Art,Album,明/清 陳洪綬 橅古圖 冊|Miscellaneous Studies,China,Ming dynasty (1368–1644),,,,Artist,,Chen Hongshou,"Chinese, 1599–1652",,Chen Hongshou,Chinese,1599,1652,one leaf dated 1619,1619,1619,Album of twelve paintings; ink on paper,Image (each leaf): 7 x 7 in. (17.8 x 17.8 cm),"Gift of Mr. and Mrs. Wan-go H. C. Weng, 2005",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/37395,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -13.100.87,false,true,35998,Asian Art,Folding fan mounted as an album leaf,,China,Ming dynasty (1368–1644),,,,Artist,,Shao Mi,active ca 1620–1660,,Shao Mi,Chinese,1620,1660,dated 1640,1640,1640,Folding fan mounted as an album leaf; ink and color on gold-flecked paper,6 1/2 x 18 23/32 in. (16.5 x 47.6 cm),"John Stewart Kennedy Fund, 1913",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/35998,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -13.100.77,false,true,35990,Asian Art,Folding fan mounted as an album leaf,,China,Ming dynasty (1368–1644),,,,Artist,,Wu Bin,active ca. 1583–1626,,Wu Bin,Chinese,1583,1626,dated 1603,1603,1603,Folding fan mounted as an album leaf; ink and color on paper,6 3/8 x 18 23/32 in. (16.2 x 47.6 cm),"John Stewart Kennedy Fund, 1913",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/35990,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.266.4,true,true,48948,Asian Art,Handscroll,明 吳彬 十六羅漢圖 卷|The Sixteen Luohans,China,Ming dynasty (1368–1644),,,,Artist,,Wu Bin,active ca. 1583–1626,,Wu Bin,Chinese,1583,1626,dated 1591,1591,1591,Handscroll; ink and color on paper,Image: 12 5/8 x 163 9/16 in. (32.1 x 415.4 cm) Overall with mounting: 13 1/4 x 398 1/16 in. (33.7 x 1011.1 cm),"Edward Elliott Family Collection, Gift of Douglas Dillon, 1986",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/48948,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -13.220.111,false,true,36022,Asian Art,Hanging scroll,明 顧懿德 倣王蒙玩月圖 軸|Enjoying the Moon: Landscape in the Manner of Wang Meng,China,Ming dynasty (1368–1644),,,,Artist,,Gu Yide,active ca. 1620–1630,,Gu Yide,Chinese,1620,1630,dated 1628,1368,1644,Hanging scroll; ink and color on paper,Image: 61 x 18 1/4 in. (154.9 x 46.4 cm) Overall with mounting: 123 3/4 x 26 3/4 in. (314.3 x 67.9 cm) Overall with knobs: 123 3/4 x 31 in. (314.3 x 78.7 cm),"John Stewart Kennedy Fund, 1913",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/36022,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -13.220.104,false,true,36021,Asian Art,Hanging scroll,,China,Ming dynasty (1368–1644),,,,Artist,Formerly attributed to,Guanxiu,"Chinese, active ca. 940",,Guanxiu,Chinese,0930,0950,15th century?,1400,1499,Hanging scroll; ink and color on silk,Image: 46 3/8 × 18 1/2 in. (117.8 × 47 cm) Overall with mounting: 83 × 24 1/2 in. (210.8 × 62.2 cm) Overall with knobs: 83 × 28 1/4 in. (210.8 × 71.8 cm),"John Stewart Kennedy Fund, 1913",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/36021,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -52.177.20,false,true,36067,Asian Art,Hanging scroll,,China,Ming dynasty (1368–1644),,,,Artist,,Xie Shichen,"Chinese, 1487–ca. 1567",,Xie Shichen,Chinese,1487,1567,dated 1548,1548,1548,Hanging scroll; ink and color on silk,Image: 71 7/8 x 41 7/8 in. (182.6 x 106.4 cm),"Bequest of Katherine S. Dreier, 1952",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/36067,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2008.590a–d,false,true,75250,Asian Art,Hanging scrolls,明 謝時臣 四時佳興圖 軸 四幅|Landscapes of the Four Seasons,China,Ming dynasty (1368–1644),,,,Artist,,Xie Shichen,"Chinese, 1487–ca. 1567",,Xie Shichen,Chinese,1487,1567,dated 1560,1560,1560,Set of four hanging scrolls; ink and color on paper,Image (a): 126 3/8 x 37 in. (321 x 94 cm) Image (b): 127 1/4 x 36 7/8 in. (323.2 x 93.7 cm) Image (c): 126 3/4 x 36 7/8 in. (321.9 x 93.7 cm) Image (d): 126 7/8 x 36 7/8 in. (322.3 x 93.7 cm) Overall with mounting (b): 141 3/4 x 42 3/8 in. (360 x 107.6 cm),"Purchase, The Vincent Astor Foundation Gift, 2008",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/75250,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1980.80,false,true,48901,Asian Art,Handscroll,明 錢榖 蘭亭修禊圖 卷|Gathering at the Orchid Pavilion,China,Ming dynasty (1368–1644),,,,Artist,,Qian Gu,"Chinese, 1508–ca. 1578",,Qian Gu,Chinese,1508,1578,datable to 1560,1560,1560,Handscroll; ink and color on paper,Image: 9 1/2 x 171 1/2 in. (24.1 x 435.6 cm) Overall with mounting: 9 3/4 x 491 3/8 in. (24.8 x 1248.1 cm),"Ex coll.: C. C. Wang Family, Gift of Douglas Dillon, 1980",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/48901,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1993.385,false,true,45676,Asian Art,Hanging scroll,明 林良 二鷹圖 軸|Two hawks in a thicket,China,Ming dynasty (1368–1644),,,,Artist,,Lin Liang,"Chinese, ca. 1416–1480",,Lin Liang,Chinese,1416,1480,mid- 15th century,1416,1480,Hanging scroll; ink and color on silk,Image: 58 5/8 x 32 3/4 in. (148.9 x 83.2 cm) Overall with mounting: 108 1/2 x 39 in. (275.6 x 99.1 cm) Overall with knobs: 108 1/2 x 43 7/8 in. (275.6 x 111.4 cm),"Gift of Bei Shan Tang Foundation, 1993",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45676,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -13.100.51,false,true,51375,Asian Art,Folding fan mounted as an album leaf,,China,Ming dynasty (1368–1644),,,,Artist,,Song Xu,"Chinese, 1525–after 1606",,Song Xu,Chinese,1525,1610,dated 1587,1587,1587,Fan mounted as an album leaf; ink and color on gold paper,7 1/2 x 21 3/4 in. (19.1 x 55.2 cm),"John Stewart Kennedy Fund, 1913",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/51375,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1996.240,false,true,39554,Asian Art,Handscroll,明 丁雲鵬 十八羅漢圖 卷|Eighteen Luohans,China,Ming dynasty (1368–1644),,,,Artist,,Ding Yunpeng,"Chinese, 1547–after 1621",,Ding Yunpeng,Chinese,1547,1621,dated 1609,1609,1609,Handscroll; ink on paper,8 1/4 x 92 in. (21 x 233.7 cm),"Purchase, Friends of Asian Art Gifts, 1996",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/39554,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -67.6.2,false,true,36094,Asian Art,Handscroll,,China,Ming dynasty (1368–1644),,,,Artist,,Zhang Hong,"Chinese, 1577–after 1652",,Zhang Hong,Chinese,1577,1652,dated 1639,1639,1639,Handscroll; ink on paper,10 3/4 x 173 23/32 in. (27.3 x 441.3 cm),"Purchase, Bequest of Dorothy Graham Bennett, 1967",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/36094,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -58.148.1,false,true,36080,Asian Art,Hanging scroll,,China,Ming dynasty (1368–1644),,,,Artist,After,Zhu Duan,"Chinese, act. ca. 1500–21",,Zhu Duan,Chinese,1490,1531,dated 1518,1518,1518,Hanging scroll; ink and color on silk,Image: 35 1/4 x 76 in. (89.5 x 193 cm) Overall with mounting: 116 5/8 x 41 3/16 in. (296.2 x 104.6 cm) Overall with rollers: 116 5/8 x 46 1/8 in. (296.2 x 117.2 cm),"Gift of Alan Priest, 1958",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/36080,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1985.335,false,true,48947,Asian Art,Handscroll,明 王上宮 忠孝圖 卷|Paragons of Loyalty and Filial Piety,China,Ming dynasty (1368–1644),,,,Artist,,Wang Shanggong,"Chinese, active 16th century",,Wang Shanggong,Chinese,1500,1599,dated 1593,1593,1593,Handscroll; ink on paper,Image: 11 1/8 in. x 14 ft. 8 3/4 in. (28.3 x 448.9 cm) Overall with mounting: 12 1/4 in. x 38 ft. 1 15/16 in. (31.1 x 1163.2 cm),"Purchase, Bequest of Dorothy Graham Bennett, 1985",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/48947,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -13.100.83,false,true,35994,Asian Art,Folding fan mounted as an album leaf,,China,Ming dynasty (1368–1644),,,,Artist,,Qi Zhijia,"Chinese, ca. 1595–ca. 1670",,Qi Zhijia,Chinese,1585,1680,dated Spring 1643,1643,1643,Folding fan mounted as an album leaf; ink on paper,6 1/2 x 19 1/2 in. (16.5 x 49.5 cm),"John Stewart Kennedy Fund, 1913",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/35994,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -13.100.95,false,true,48902,Asian Art,Folding fan mounted as an album leaf,,China,Ming dynasty (1368–1644),,,,Artist,,Chen Guan,"Chinese, active ca. 1610–40",,Chen Guan,Chinese,1600,1650,dated 1629,1368,1644,Folding fan mounted as an album leaf; ink and color on paper,6 1/2 x 19 5/8 in. (16.5 x 49.8 cm),"John Stewart Kennedy Fund, 1913",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/48902,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.127,false,true,49095,Asian Art,Handscroll,,China,Ming dynasty (1368–1644),,,,Artist,,Bian Wenyu,"Chinese, active ca. 1611–71",,Bian Wenyu,Chinese,1611,1671,dated 1634,1634,1634,Handscroll; ink on paper,10 1/4 x 42 1/2 in. (26 x 108 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49095,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.219,false,true,48966,Asian Art,Handscroll,明 趙左 谿山無盡圖 卷|Streams and Mountains without End,China,Ming dynasty (1368–1644),,,,Artist,,Zhao Zuo,"Chinese, ca. 1570–after 1630",,Zhao Zuo,Chinese,1560,1630,Dated 1611–12,1611,1612,Handscroll; ink and color on paper,Image: 9 5/8 x 248 5/8 in. (24.4 x 631.5 cm) Overall with mounting: 11 3/4 x 465 3/4 in. (29.8 x 1183 cm),"Purchase, The Dillon Fund Gift, 1976",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/48966,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.37,false,true,51871,Asian Art,Handscroll,,China,Ming dynasty (1368–1644),,,,Artist,Formerly Attributed to,Wang Zhenpeng,"Chinese, active ca. 1275–1330",,WANG ZHENPENG,Chinese,1265,1340,early 15th century,1400,1433,Handscroll; ink on paper,Image: 12 1/4 x 269 in. (31.1 x 683.3 cm) Overall with mounting: 13 3/8 x 443 in. (34 x 1125.2 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/51871,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1991.117.2,false,true,39667,Asian Art,Hanging scroll,明 杜堇 伏生授經圖 軸|The Scholar Fu Sheng Transmitting the Book of Documents,China,Ming dynasty (1368–1644),,,,Artist,,Du Jin,"Chinese, active ca. 1465–1509",,Du Jin,Chinese,1465,1509,15th–mid-16th century,1465,1509,Hanging scroll; ink and color on silk,Image: 57 7/8 × 41 1/8 in. (147 × 104.5 cm) Overall with mounting: 9 ft. 10 3/4 in. × 50 1/4 in. (301.6 × 127.6 cm) Overall with knobs: 9 ft. 10 3/4 in. × 53 in. (301.6 × 134.6 cm),"Gift of Douglas Dillon, 1991",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/39667,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1991.438.4,false,true,44703,Asian Art,Hanging scroll,明 鍾禮 觀瀑圖 軸|Scholar looking at a waterfall,China,Ming dynasty (1368–1644),,,,Artist,,Zhong Li,"Chinese, active ca. 1480–1500",,Zhong Li,Chinese,1480,1500,late 15th century,1480,1499,Hanging scroll; ink and color on silk,Image: 70 x 40 5/8 in. (177.8 x 103.2 cm) Overall with mounting: 10 ft. 5 1/2 in. × 49 5/8 in. (318.8 × 126 cm) Overall with knobs: 10 ft. 5 1/2 in. × 53 1/4 in. (318.8 × 135.3 cm),"From the P. Y. and Kinmay W. Tang Family Collection, Gift of Oscar L. Tang, 1991",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/44703,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -13.220.116,false,true,36024,Asian Art,Hanging scroll,明 居節 石泉圖 軸|The Waterfall,China,Ming dynasty (1368–1644),,,,Artist,,Ju Jie,"Chinese, active ca. 1531–1585",,Ju Jie,Chinese,1531,1585,dated 1559,1368,1644,Hanging scroll; ink on silk,43 1/2 x 9 3/4 in. (110.5 x 24.8 cm),"John Stewart Kennedy Fund, 1913",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/36024,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.82,false,true,48910,Asian Art,Hanging scroll,"明 侯懋功 高山圖 軸 |High Mountains",China,Ming dynasty (1368–1644),,,,Artist,,Hou Maogong,"Chinese, active ca. 1540–1580",,Hou Maogong,Chinese,1540,1580,dated 1569,1569,1569,Hanging scroll; ink and color on paper,Image: 46 5/8 x 11 in. (118.4 x 27.9 cm) Overall with mounting: 78 3/4 x 17 in. (200 x 43.2 cm) Overall with knobs: 78 3/4 x 20 1/2 in. (200 x 52.1 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/48910,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1987.408.2a–n,false,true,36436,Asian Art,Album,清 張風 山水圖 冊 紙本|Landscapes,China,Ming dynasty (1368–1644),,,,Artist,,Zhang Feng,"Chinese, active ca. 1628–1662",,Zhang Feng,Chinese,1618,1662,dated 1644,1644,1644,Album of twelve leaves; ink and color on paper,Each leaf: 6 1/16 x 9 in. (15.4 x 22.9 cm),"Edward Elliott Family Collection, Gift of Douglas Dillon, 1987",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/36436,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2002.328,false,true,65013,Asian Art,Handscroll,清 那爾敦布 順治皇帝進京之隊伍賽馬全圖 卷|Horsemanship Competition for the Shunzhi Emperor,China,Ming dynasty (1368–1644),,,,Artist,,Nardunbu,"Manchu, active mid-17th century",,Nardunbu,Manchu,1600,1699,dated 1662,1662,1662,Handscroll; ink and color on paper,8 x 655 in. (20.3 x 1663.7 cm) Height of painting with paper mounting: 9 3/8 in. (23.8 cm); height of painting with brocade mounting: 12 in. (30.5 cm); frontispiece: 8 x 12 3/8 in. (20.3 x 31.4 cm),"Purchase, The Dillon Fund Gift, 2002",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/65013,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -47.18.130,false,true,45670,Asian Art,Hanging scroll,"一鱖禾蟹圖|Flowers, fish, and crabs",China,Ming dynasty (1368–1644),,,,Artist,,Liu Jie,"Chinese, active mid-16th century",,Liu Jie,Chinese,1534,1566,mid-16th century,1534,1566,Hanging scroll; ink and color on silk,Image: 69 1/4 × 49 1/2 in. (175.9 × 125.7 cm) Overall with knobs: 8 ft. 6 7/8 in. × 54 in. (261.3 × 137.2 cm),"From the Collection of A. W. Bahr, Purchase, Fletcher Fund, 1947",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45670,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -13.100.75,false,true,35989,Asian Art,Folding fan mounted as an album leaf,,China,Ming dynasty (1368–1644),,,,Artist,,Chen Jichun,"Chinese, active mid-17th century",,Chen Jichun,Chinese,1633,1667,1635,1635,1635,Folding fan mounted as an album leaf; ink and color on gold paper,7 11/32 x 21 23/32 in. (18.7 x 55.2 cm),"John Stewart Kennedy Fund, 1913",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/35989,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1983.85,false,true,41191,Asian Art,Hanging scroll,,China,Ming dynasty (1368–1644),,,,Artist,,Ni Jing,"Chinese, active late 14th century",,Ni Jing,Chinese,1367,1399,14th century,1367,1399,Hanging scroll; ink and pale color on paper,Image: 41 15/16 x 9 3/4 in. (106.5 x 24.8 cm) Overall with mounting: 75 3/4 x 14 1/8 in. (192.4 x 35.9 cm) Overall with knobs: 75 3/4 x 15 15/16 in. (192.4 x 40.5 cm),"Purchase, Bequest of Dorothy Graham Bennett, 1983",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/41191,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -12.37.134,false,true,45655,Asian Art,Hanging scroll,"元/明 呂敬甫 花蝶圖 軸 |Flowers",China,Ming dynasty (1368–1644),,,,Artist,Attributed to,Lü Jingfu,"Chinese, active late 14th century",,Lü Jingfu,Chinese,1367,1399,14th century,1368,1399,Hanging scroll; ink and color on silk,40 x 20 7/8 in. (101.6 x 53 cm),"Rogers Fund, 1912",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45655,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.494.4,false,true,39553,Asian Art,Hanging scroll,明 陳子和 古木酒仙圖 軸|Drunken Immortal beneath an old tree,China,Ming dynasty (1368–1644),,,,Artist,,Chen Zihe,"Chinese, active early 16th century",,Chen Zihe,Chinese,1500,1533,early 16th century,1500,1533,Hanging scroll; ink on silk,Image: 69 in. × 40 1/4 in. (175.3 × 102.2 cm) Overall with mounting: 9 ft. 9 in. × 47 3/8 in. (297.2 × 120.3 cm) Overall with knobs: 9 ft. 9 in. × 52 in. (297.2 × 132.1 cm),"Ex coll.: C. C. Wang Family, Gift of Oscar L. Tang Family, 2005",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/39553,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -59.49.1,false,true,36081,Asian Art,Hanging scroll,明 阮祖德 抑齋曾叔祖八十五齡壽像 軸|Portrait of the Artist's Great-Granduncle Yizhai at the Age of Eighty-Five,China,Ming dynasty (1368–1644),,,,Artist,,Ruan Zude,"Chinese, 16th or early 17th century",,Ruan Zude,Chinese,1500,1699,"dated ""xinyou"" (1561 or 1621?)",1561,1621,Hanging scroll; ink and color on silk,Image: 61 3/4 x 37 7/8 in. (156.8 x 96.2 cm),"Seymour Fund, 1959",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/36081,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -59.49.2,false,true,36082,Asian Art,Hanging scroll,明 阮祖德 老婦像 軸|Portrait of an Old Lady,China,Ming dynasty (1368–1644),,,,Artist,,Ruan Zude,"Chinese, 16th or early 17th century",,Ruan Zude,Chinese,1500,1699,"dated ""xinyou"" (1561 or 1621?)",1561,1621,Hanging scroll; ink and color on silk,Image: 61 3/4 x 37 7/8 in. (156.8 x 96.2 cm),"Seymour Fund, 1959",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/36082,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.494.3,false,true,39552,Asian Art,Hanging scroll,明 劉俊 納諫圖 軸|Remonstrating with the emperor,China,Ming dynasty (1368–1644),,,,Artist,,Liu Jun,"Chinese, active ca. 1475–ca. 1505",,Liu Jun,Chinese,1475,1505,late 15th–early 16th century,1475,1505,Hanging scroll; ink and color on silk,Image: 65 1/2 x 41 3/4 in. (166.4 x 106 cm) Overall with mounting: 116 x 50 in. (294.6 x 127 cm) Overall with knobs: 116 x 54 in. (294.6 x 137.2 cm),"Ex coll.: C. C. Wang Family, Gift of Oscar L. Tang Family, 2005",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/39552,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -55.124,false,true,45680,Asian Art,Handscroll,明 傳蔣嵩 冬景山水圖 卷|Winter Landscape,China,Ming dynasty (1368–1644),,,,Artist,Attributed to,Jiang Song,"Chinese, first half of 16th century",,Jiang Song,Chinese,1500,1550,first half of 16th century,1500,1549,Handscroll; ink and color on paper,Image: 12 1/4 x 277 in. (31.1 x 703.6 cm),"Seymour Fund, 1955",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45680,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -47.18.111,false,true,36058,Asian Art,Hanging scroll,,China,Ming dynasty (1368–1644),,,,Artist,Attributed to,Zhou Zonglian,probably Ming dynasty (1368–1644),,Zhou Zonglian,Chinese,1368,1644,dated 1532 or 1592,1532,1592,Hanging scroll; ink on silk,Image: 67 3/4 x 37 1/8 in. (172.1 x 94.3 cm),"From the Collection of A. W. Bahr, Purchase, Fletcher Fund, 1947",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/36058,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -68.195,false,true,36095,Asian Art,Handscroll,明 魏之克(魏克) 金陵四時圖 卷 紙本|Views of Nanjing in the Four Seasons,China,Ming dynasty (1368–1644),,,,Artist,,Wei Zhike,"Chinese, active ca. 1600–after 1636",,Wei Zhike,Chinese,1600,1636,dated 1635,1635,1635,Handscroll; ink and color on paper,Overall with mounting: 12 5/8 in. × 38 ft. 10 in. (32.1 × 1183.6 cm),"Gift of J. T. Tai, 1968",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/36095,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -13.100.79,false,true,35992,Asian Art,Folding fan mounted as an album leaf,,China,Ming dynasty (1368–1644),,,,Artist,,Pan Yunyu,"Chinese, active ca. 15th–16th century",,Pan Yunyu,Chinese,1400,1599,dated 1604?1664?,1604,1604,Folding fan mounted as an album leaf; ink and color on gold paper,6 1/4 x 19 in. (15.9 x 48.3 cm),"John Stewart Kennedy Fund, 1913",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/35992,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1982.1.5,false,true,45671,Asian Art,Hanging scroll,明 徐敬 歲寒清白圖 軸|The Pure Whiteness of Winter,China,Ming dynasty (1368–1644),,,,Artist,,Xu Jing,"Chinese, active first half 15th century",,Xu Jing,Chinese,1400,1450,dated 1441,1441,1441,Hanging scroll; ink on silk,Image: 58 7/8 x 30 in. (149.5 x 76.2 cm) Overall with mounting: 107 3/4 x 37 1/4 in. (273.7 x 94.6 cm) Overall with knobs: 107 3/4 x 41 1/4 in. (273.7 x 104.8 cm),"Ex coll.: C. C. Wang Family, Edward Elliott Family Collection, Purchase, The Dillon Fund Gift, 1982",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45671,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1984.475.3,false,true,45659,Asian Art,Hanging scroll,"元/明 吳伯理 龍松圖 軸|Dragon Pine",China,Ming dynasty (1368–1644),,,,Artist,,Wu Boli,"Chinese, active late 14th–early 15th century",,Wu Boli,Chinese,1350,1450,ca. 1400,1390,1410,Hanging scroll; ink on paper,Image: 48 x 13 1/4 in. (121.9 x 33.7 cm) Overall with mounting: 100 x 18 5/8 in. (254 x 47.3 cm) Overall with knobs: 100 x 21 in. (254 x 53.3 cm),"Edward Elliott Family Collection, Gift of Douglas Dillon, 1984",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45659,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -45.97.9,false,true,51621,Asian Art,Album,清 陸漢 山水八開 冊|Eight Landscapes,China,Qing dynasty (1644–1911),,,,Artist,,Lu Han,"Chinese, died 1722",,Lu Han,Chinese,,1722,1699,1699,1699,Album of eight leaves; ink and color on paper,Image (album): 14 × 10 in. (35.6 × 25.4 cm) Image (double leaf): 14 × 20 in. (35.6 × 50.8 cm) Image: 12 × 9 in. (30.5 × 22.9 cm),"Gift of Mrs. Henry J. Bernheim, 1945",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/51621,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1985.214.149,false,true,49030,Asian Art,Hanging scroll,明/清 王鐸 山水圖 軸|Mountain Landscape,China,Qing dynasty (1644–1911),,,,Artist,,Wang Duo,"Chinese, 1592–1652",,Wang Duo,Chinese,1592,1652,dated 1651,1651,1651,Hanging scroll; ink on paper,46 1/8 x 21 5/8 in. (117.2 x 54.9 cm),"Gift of Ernest Erickson Foundation, 1985",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49030,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.117,false,true,44626,Asian Art,Hanging scroll,明/清 王鐸 山水圖 軸|Landscape,China,Qing dynasty (1644–1911),,,,Artist,,Wang Duo,"Chinese, 1592–1652",,Wang Duo,Chinese,1592,1652,dated 1649,1649,1649,Hanging scroll; ink on satin,Image: 22 x 10 5/8 in. (55.9 x 27 cm) Overall with mounting: 67 x 16 3/4 in. (170.2 x 42.5 cm) Overall with knobs: 67 x 20 3/8 in. (170.2 x 51.8 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/44626,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1980.426.2,false,true,40019,Asian Art,Hanging scroll,清 王時敏 仿黃公望山水圖 軸 絹本|Landscape in the style of Huang Gongwang,China,Qing dynasty (1644–1911),,,,Artist,,Wang Shimin,"Chinese, 1592–1680",,WANG SHIMIN,Chinese,1592,1680,dated 1666,1666,1666,Hanging scroll; ink on paper,Image: 53 x 22 1/4 in. (134.6 x 56.5 cm) Overall with mounting: 87 3/4 x 28 1/4 in. (222.9 x 71.8 cm) Overall with knobs: 87 3/4 x 31 in. (222.9 x 78.7 cm),"Ex coll.: C. C. Wang Family, Gift of Douglas Dillon, 1980",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/40019,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.119,false,true,49106,Asian Art,Folding fan mounted as an album leaf,,China,Qing dynasty (1644–1911),,,,Artist,,Wang Shimin,"Chinese, 1592–1680",,WANG SHIMIN,Chinese,1592,1680,dated 1677,1677,1677,Folding fan mounted as an album leaf; ink and color on white paper,6 3/16 x 19 1/2 in. (15.7 x 49.5 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49106,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2003.394a–i,false,true,65555,Asian Art,Album,清 蕭雲從 山水圖 冊|Landscapes,China,Qing dynasty (1644–1911),,,,Artist,,Xiao Yuncong,"Chinese, 1596–1673",,Xiao Yuncong,Chinese,1596,1673,dated 1668,1668,1668,Album of eight paintings; ink and color on paper,Each leaf: 9 1/8 x 6 3/4 in. (23.2 x 17.1 cm),"Gift of Florence and Herbert Irving Collection, in memory of Douglas Dillon, 2003",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/65555,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -64.268.1,false,true,36089,Asian Art,Album leaf,明/清 項聖謨 秋景圖 冊頁|Autumn Landscape,China,Qing dynasty (1644–1911),,,,Artist,,Xiang Shengmo,"Chinese, 1597–1658",,Xiang Shengmo,Chinese,1597,1658,datable to 1654–55,1654,1655,Leaf from a collective album of many leaves; ink and color on paper,9 3/4 x 13 in. (24.8 x 33 cm),"Seymour Fund, 1964",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/36089,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1981.285.4,false,true,49080,Asian Art,Hanging scroll,明/清 項聖謨 白菊圖 軸|White Chrysanthemums,China,Qing dynasty (1644–1911),,,,Artist,,Xiang Shengmo,"Chinese, 1597–1658",,Xiang Shengmo,Chinese,1597,1658,dated 1654,1654,1654,Hanging scroll; color on paper,Image: 30 7/16 x 15 1/2 in. (77.3 x 39.4 cm) Overall with mounting: 60 x 21 in. (152.4 x 53.3 cm) Overall with knobs: 60 x 24 in. (152.4 x 61 cm),"Edward Elliott Family Collection, Purchase, The Dillon Fund Gift, 1981",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49080,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.133,false,true,40020,Asian Art,Hanging scroll,清 倣弘仁 幽谷泉聲圖 軸|The Sound of Spring in a Lonely Valley,China,Qing dynasty (1644–1911),,,,Artist,After,Hongren,"Chinese, 1610–1664",,HONGREN,Chinese,1610,1664,dated 1661,1661,1661,Hanging scroll; ink on paper,Image: 40 1/2 x 16 1/8 in. (102.9 x 41 cm) Overall with mounting: 76 1/2 x 22 in. (194.3 x 55.9 cm) Overall with knobs: 76 1/2 x 24 1/2 in. (194.3 x 62.2 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/40020,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.129,false,true,39557,Asian Art,Hanging scroll,清 倣髡殘 蒸嵐昏巒圖 軸|Wooded Mountains at Dusk,China,Qing dynasty (1644–1911),,,,Artist,,Kuncan,"Chinese, 1612–1673",,KUNCAN,Chinese,1612,1673,dated 1666,1666,1666,Hanging scroll; ink and color on paper,Image: 49 1/2 x 24 in. (125.7 x 61 cm) Overall with mounting: 101 1/4 x 31 5/8 in. (257.2 x 80.3 cm) Overall with knobs: 101 1/4 x 34 3/4 in. (257.2 x 88.3 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/39557,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2010.54,false,true,75745,Asian Art,Hanging scroll,清 法若真 雲山圖軸|Cloudy Mountains,China,Qing dynasty (1644–1911),,,,Artist,,Fa Ruozhen,"Chinese, 1613–1696",,Fa Ruozhen,Chinese,1613,1696,1684,1684,1684,"Hanging scroll, ink and color on silk",54 1/8 x 27 3/8 in. (137.5 x 69.5 cm),"Purchase, The Vincent Astor Foundation Gift, 2010",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/75745,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.132,false,true,49132,Asian Art,Hanging scroll,清 龔賢 冬景山水圖 軸 紙本|Wintry Mountains,China,Qing dynasty (1644–1911),,,,Artist,,Gong Xian,"Chinese, 1619–1689",,GONG XIAN,Chinese,1619,1689,datable ca. 1679–89,1679,1689,Hanging scroll; ink on paper,Image: 65 1/4 x 19 1/4 in. (165.7 x 48.9 cm) Overall with mounting: 114 1/4 x 26 1/2 in. (290.2 x 67.3 cm) Overall with knobs: 114 1/4 x 30 1/4 in. (290.2 x 76.8 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49132,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1979.499a–l,false,true,41483,Asian Art,Album,清 龔賢 山水圖 冊 紙本|Landscapes and trees,China,Qing dynasty (1644–1911),,,,Artist,,Gong Xian,"Chinese, 1619–1689",,GONG XIAN,Chinese,1619,1689,ca. 1679,1669,1689,Album of twelve leaves; ink on paper,Image (each leaf): 6 1/4 x 7 1/2 in. (15.9 x 19.1 cm),"From the P. Y. and Kinmay W. Tang Family Collection, Gift of Wen and Constance Fong, in honor of Mr. and Mrs. Douglas Dillon, 1979",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/41483,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1981.4.1a–o,true,true,36131,Asian Art,Album,清 龔賢 自題 山水十六開 冊|Ink Landscapes with Poems,China,Qing dynasty (1644–1911),,,,Artist,,Gong Xian,"Chinese, 1619–1689",,GONG XIAN,Chinese,1619,1689,dated 1688,1688,1688,Album of sixteen paintings; ink on paper,10 3/4 x 16 1/8 in. (27.3 x 41 cm),"Gift of Douglas Dillon, 1981",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/36131,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1980.516.2a–c,false,true,51864,Asian Art,Album,清 龔賢 自題山水十六開 冊|Ink Landscapes with Poems,China,Qing dynasty (1644–1911),,,,Artist,,Gong Xian,"Chinese, 1619–1689",,GONG XIAN,Chinese,1619,1689,dated 1688,1688,1688,Album of sixteen paintings; ink on paper,10 13/16 x 14 1/16in. (27.5 x 35.7cm) Overall with mounting: 16 1/8 x 22 7/8in. (41 x 58.1cm) painting/calligraphy: 13 15/16 x 20 9/16in. (35.4 x 52.2cm),"Gift of Douglas Dillon, 1980",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/51864,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1991.256,false,true,41488,Asian Art,Hanging scroll,清 戴本孝 天台異松圖 軸 紙本|The Strange Pines of Mount Tiantai,China,Qing dynasty (1644–1911),,,,Artist,,Dai Benxiao,"Chinese, 1621–1693",,DAI BENXIAO,Chinese,1621,1693,dated 1687,1687,1687,Hanging scroll; ink on paper,Image: 66 7/8 x 30 in. (169.9 x 76.2 cm) Overall with mounting: 122 3/4 x 37 1/16 in. (311.8 x 94.1 cm) Overall with knobs: 122 3/4 x 41 in. (311.8 x 104.1 cm),"Gift of Marie-Hélène and Guy Weill, in honor of Douglas Dillon, 1991",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/41488,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -51.13,false,true,51769,Asian Art,Handscroll,,China,Qing dynasty (1644–1911),,,,Artist,Attributed to,Luo Mu,"Chinese, 1622–1706",,Luo Mu,Chinese,1622,1706,dated 1661,1661,1661,Handscroll; ink on paper,12 7/8 x 262 in. (32.7 x 665.5 cm),"Rogers Fund, 1951",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/51769,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1994.284,false,true,36453,Asian Art,Hanging scroll,清 梅清 雲谷曳杖圖 軸|Strolling in a Misty Valley,China,Qing dynasty (1644–1911),,,,Artist,,Mei Qing,"Chinese, 1623–1697",,MEI QING,Chinese,1623,1697,dated 1649,1649,1649,Hanging scroll; ink on satin,Image: 61 3/8 x 20 1/2 in. (155.9 x 52.1 cm) Overall with mounting: 100 x 26 3/4 in. (254 x 67.9 cm) Overall with knobs: 100 x 30 1/4 in. (254 x 76.8 cm),"Purchase, Soong Family Gift, in memory of Dr. T.V. Soong, 1994",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/36453,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2002.208.1,false,true,37394,Asian Art,Hanging scroll,清 倣梅清 響山泛舟圖 軸|Boating beneath Echo Hill,China,Qing dynasty (1644–1911),,,,Artist,After,Mei Qing,"Chinese, 1623–1697",,MEI QING,Chinese,1623,1697,datable to 1673,1673,1673,Hanging scroll; ink on paper,Image: 53 x 23 1/4 in. (134.6 x 59.1 cm) Overall with mounting: 113 1/2 x 32 1/4 in. (288.3 x 81.9 cm) Overall with knobs: 113 1/2 x 36 1/4 in. (288.3 x 92.1 cm),"Gift of Cécile and Sandy Mactaggart, 2002",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/37394,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2014.721,false,true,39540,Asian Art,Hanging scroll,清 朱耷 (八大山人) 二鷹圖 軸|Two eagles,China,Qing dynasty (1644–1911),,,,Artist,,Bada Shanren (Zhu Da),"Chinese, 1626–1705",,Bada Shanren (Zhu Da),Chinese,1626,1705,dated 1702,1702,1702,Hanging scroll; ink on paper,Image: 73 3/4 x 35 1/2 in. (187.3 x 90.2 cm) Overall with mounting: 122 3/4 x 42 1/2 in. (311.8 x 108 cm) Overall with knobs: 122 3/4 x 46 1/2 in. (311.8 x 118.1 cm),"Gift of Oscar L. Tang Family, 2014",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/39540,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.135,false,true,49143,Asian Art,Handscroll,清 八大山人 (朱耷) 蓮塘戲禽圖 卷|Birds in a lotus pond,China,Qing dynasty (1644–1911),,,,Artist,,Bada Shanren (Zhu Da),"Chinese, 1626–1705",,Bada Shanren (Zhu Da),Chinese,1626,1705,ca. 1690,1680,1700,Handscroll; ink on satin,10 3/4 x 80 3/4 in. (27.3 x 205.1 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49143,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.136,false,true,49144,Asian Art,Hanging scroll,"清 朱耷 (八大山人) 倣王羲之 蘭亭序 軸|After Wang Xizhi's (303?-361?) ""Preface to the Orchid Pavilion Gathering""",China,Qing dynasty (1644–1911),,,,Artist,,Bada Shanren (Zhu Da),"Chinese, 1626–1705",,Bada Shanren (Zhu Da),Chinese,1626,1705,ca. 1694–96,1684,1705,Hanging scroll; ink on paper,Image: 18 3/8 x 10 in. (46.7 x 25.4 cm) Overall with mounting: 66 x 14 1/4 in. (167.6 x 36.2 cm) Overall with knobs: 66 x 17 1/2 in. (167.6 x 44.5 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49144,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.137,false,true,41491,Asian Art,Hanging scroll,清 八大山人(朱耷) 魚石圖 軸|Fish and rocks,China,Qing dynasty (1644–1911),,,,Artist,,Bada Shanren (Zhu Da),"Chinese, 1626–1705",,Bada Shanren (Zhu Da),Chinese,1626,1705,dated 1699,1699,1699,Hanging scroll; ink on paper,Image: 53 1/4 x 24 in. (135.3 x 61 cm) Overall with mounting: 92 1/2 x 31 1/2 in. (235 x 80 cm) Overall with knobs: 92 1/2 x 34 1/4 in. (235 x 87 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/41491,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.138a–l,false,true,49145,Asian Art,Album,清 朱耷 (八大山人) 山水圖 冊|Landscape album,China,Qing dynasty (1644–1911),,,,Artist,,Bada Shanren (Zhu Da),"Chinese, 1626–1705",,Bada Shanren (Zhu Da),Chinese,1626,1705,dated 1699,1699,1699,Album of twelve leaves; ink and color on paper,Image (each): 7 3/8 x 9 1/8 in. (18.7 x 23.2 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49145,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.190,false,true,49149,Asian Art,Hanging scroll,"倣趙伯駒山水圖 軸|Landscape in the Style of Zhao Boju (Fang Zhao Boju shanshui)",China,Qing dynasty (1644–1911),,,,Artist,After,Wang Hui,"Chinese, 1632–1717",,Wang Hui,Chinese,1632,1717,dated 1654,1654,1654,Hanging scroll; ink and color on paper,Image: 23 1/2 x 14 7/8 in. (59.7 x 37.8 cm) Overall with mounting: 76 5/8 x 19 1/2 in. (194.6 x 49.5 cm) Overall with knobs: 76 5/8 x 23 in. (194.6 x 58.4 cm),"Purchase, The Dillon Fund Gift, 1976",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49149,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1978.423,false,true,49151,Asian Art,Handscroll,清 王翬 太行山色圖 卷 絹本|The Colors of Mount Taihang,China,Qing dynasty (1644–1911),,,,Artist,,Wang Hui,"Chinese, 1632–1717",,Wang Hui,Chinese,1632,1717,Dated 1669,1669,1669,Handscroll; ink and color on silk,Image: 10 x 82 1/2 in. (25.3 x 209.4 cm) Overall with mounting: 11 3/4 x 348 in. (29.8 x 883.9 cm),"Ex coll.: C. C. Wang Family, Gift of Douglas Dillon, 1978",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49151,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -13.220.33,false,true,51483,Asian Art,Tapestry,,China,Qing dynasty (1644–1911),,,,Artist,In the Style of,Wang Hui,"Chinese, 1632–1717",,Wang Hui,Chinese,1632,1717,dated 1702,1702,1702,Hanging scroll; ink and color on silk,Image: 46 3/4 x 22 1/4 in. (118.7 x 56.5 cm) Overall with mounting: 100 1/2 x 29 5/8 in. (255.3 x 75.2 cm) Overall with knobs: 100 1/2 x 33 3/4 in. (255.3 x 85.7 cm),"John Stewart Kennedy Fund, 1913",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/51483,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1979.458.2,false,true,49157,Asian Art,Handscroll,清 王翬 倣巨然燕文貴山水圖 卷|Landscape in the Style of Juran and Yan Wengui,China,Qing dynasty (1644–1911),,,,Artist,,Wang Hui,"Chinese, 1632–1717",,Wang Hui,Chinese,1632,1717,Dated 1713,1713,1713,Handscroll; ink and color on paper,Image: 12 1/4 x 158 3/8 in. (31.1 x 402.3 cm) Overall with mounting: 12 7/8 x 359 1/8 in. (32.7 x 912.2 cm),"Ex coll.: C. C. Wang Family, Gift of Douglas Dillon, 1979",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49157,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1979.5a–d,true,true,49156,Asian Art,Handscroll,"清 王翬 等 康熙南巡圖 (卷三: 濟南至泰山) 卷|The Kangxi Emperor's Southern Inspection Tour, Scroll Three: Ji'nan to Mount Tai",China,Qing dynasty (1644–1911),,,,Artist,,Wang Hui,"Chinese, 1632–1717",and assistants,Wang Hui,Chinese,1632,1717,datable to 1698,1698,1698,Handscroll; ink and color on silk,Image: 26 3/4 in. x 45 ft. 8 3/4 in. (67.9 x 1393.8 cm),"Purchase, The Dillon Fund Gift, 1979",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49156,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.141,false,true,49150,Asian Art,Hanging scroll,清 王翬 溪山雨霽圖 軸|Clearing after Rain over Streams and Mountains,China,Qing dynasty (1644–1911),,,,Artist,,Wang Hui,"Chinese, 1632–1717",,Wang Hui,Chinese,1632,1717,dated 1662,1662,1662,Hanging scroll; ink on paper,Image: 44 7/8 x 17 7/8 in. (114 x 45.4 cm) Overall with mounting: 103 1/2 x 24 1/4 in. (262.9 x 61.6 cm) Overall with knobs: 103 1/2 x 27 1/2 in. (262.9 x 69.9 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49150,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.144,false,true,49154,Asian Art,Folding fan mounted as an album leaf,,China,Qing dynasty (1644–1911),,,,Artist,,Wang Hui,"Chinese, 1632–1717",,Wang Hui,Chinese,1632,1717,dated 1695,1695,1695,Folding fan mounted as an album leaf; ink and color on paper,6 7/16 x 19 3/8 in. (16.4 x 49.2 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49154,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1977.81,false,true,49158,Asian Art,Handscroll,清 吳歷 墨井草堂消夏圖 卷|Whiling Away the Summer,China,Qing dynasty (1644–1911),,,,Artist,,Wu Li,"Chinese, 1632–1718",,WU LI,Chinese,1632,1718,dated 1679,1679,1679,Handscroll; ink on paper,Image: 14 5/16 x 105 3/4 in. (36.4 x 268.6 cm) Overall with mounting: 14 11/16 x 393 1/16 in. (37.3 x 998.4 cm),"Ex coll.: C. C. Wang Family, Purchase, Douglas Dillon Gift, 1977",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49158,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -13.220.117,false,true,36025,Asian Art,Hanging scroll,,China,Qing dynasty (1644–1911),,,,Artist,In the Style of,Wu Li,"Chinese, 1632–1718",,WU LI,Chinese,1632,1718,spuriously dated 1703,1703,1703,Hanging scroll; ink on paper,39 5/8 x 19 in. (100.6 x 48.3 cm),"John Stewart Kennedy Fund, 1913",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/36025,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2012.167a–l,false,true,77870,Asian Art,Album,鄭旼 黃山八景 水墨紙本 九開冊|Eight views of the Yellow Mountains,China,Qing dynasty (1644–1911),,,,Artist,,Zheng Min,"Chinese, 1633–1683",,Zheng Min,Chinese,1633,1683,1681,1681,1681,Album of nine leaves of painting and calligraphy; ink on paper,Image (each leaf): 9 1/2 x 5 1/2 in. (24.1 x 14 cm) Overall with mounting (each double leave): 12 1/8 x 14 3/8 in. (30.8 x 36.5 cm),"Purchase, The Vincent Astor Foundation Gift and Susan Dillon Gift, in honor of James C. Y. Watt, 2012",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/77870,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1982.470,false,true,41486,Asian Art,Hanging scroll,清 傳惲壽平 夏夜清荷圖 軸|Lotuses on a Summer Evening,China,Qing dynasty (1644–1911),,,,Artist,,Yun Shouping,"Chinese, 1633–1690",,YUN SHOUPING,Chinese,1633,1690,dated 1684,1684,1684,Hanging scroll; ink and color on paper,Image: 82 5/16 x 38 11/16 in. (209.1 x 98.3 cm) Overall with mounting: 129 x 40 3/8 in. (327.7 x 102.6 cm) Overall with knobs: 129 x 44 3/4 in. (327.7 x 113.7 cm),"Gift of Marie-Hélène and Guy Weill, in honor of Professor Wen Fong, 1982",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/41486,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1981.285.12a–m,false,true,49162,Asian Art,Album,清 惲壽平 倣宋元山水圖 冊|Landscapes in the Manner of Song and Yuan Masters,China,Qing dynasty (1644–1911),,,,Artist,,Yun Shouping,"Chinese, 1633–1690",,YUN SHOUPING,Chinese,1633,1690,dated 1667,1667,1667,Album of ten paintings; ink and color on paper,10 5/8 x 15 5/8 in. (27 x 39.7 cm),"Edward Elliott Family Collection, Purchase, The Dillon Fund Gift, 1981",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49162,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.1.1,false,true,49181,Asian Art,Hanging scroll,清 石濤(朱若極) 黃山三十六峰意圖 軸|Thirty-six Peaks of Mount Huang Recollected,China,Qing dynasty (1644–1911),,,,Artist,,Shitao (Zhu Ruoji),"Chinese, 1642–1707",,Shitao,Chinese,1642,1707,ca. 1705,1695,1707,Hanging scroll; ink on paper,Image: 81 1/16 x 31 in. (205.9 x 78.7 cm) Overall with mounting: 126 x 38 in. (320 x 96.5 cm) Overall with knobs: 126 x 41 1/2 in. (320 x 105.4 cm),"Gift of Douglas Dillon, 1976",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49181,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1978.254,false,true,41492,Asian Art,Hanging scroll,"清 石濤 (朱若極) 花石圖 軸|Hibiscus, Lotus, and Rock",China,Qing dynasty (1644–1911),,,,Artist,,Shitao (Zhu Ruoji),"Chinese, 1642–1707",,Shitao,Chinese,1642,1707,ca. 1705–7,1695,1717,Hanging scroll; ink on paper,Image: 45 9/16 x 22 in. (115.7 x 55.9 cm) Overall with mounting: 88 x 28 1/2 in. (223.5 x 72.4 cm) Overall with knobs: 88 x 31 3/4 in. (223.5 x 80.6 cm),"Gift of Mr. and Mrs. David M. Levitt, by exchange, 1978",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/41492,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1982.126,false,true,49177,Asian Art,Handscroll,清 石濤(朱若極) 遊張公洞圖 卷|Outing to Zhang Gong's Grotto,China,Qing dynasty (1644–1911),,,,Artist,,Shitao (Zhu Ruoji),"Chinese, 1642–1707",,Shitao,Chinese,1642,1707,ca. 1700,1690,1707,Handscroll; ink and color on paper,Image: 18 1/16 x 112 3/4 in. (45.9 x 286.4 cm) Overall with mounting: 18 7/16 x 363 11/16 in. (46.8 x 923.8 cm),"Purchase, The Dillon Fund Gift, 1982",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49177,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1987.202,false,true,49178,Asian Art,Hanging scroll,清 石濤 (朱若極) 秋林人醉圖 軸 紙本|Drunk in Autumn Woods,China,Qing dynasty (1644–1911),,,,Artist,,Shitao (Zhu Ruoji),"Chinese, 1642–1707",,Shitao,Chinese,1642,1707,ca. 1702,1692,1712,Hanging scroll; ink and color on paper,Image: 63 3/8 x 27 3/4 in. (161 x 70.5 cm) Overall with mounting: 107 1/2 x 33 3/8 in. (273.1 x 84.8 cm) Overall with knobs: 107 1/2 x 36 7/8 in. (273.1 x 93.7 cm),"Gift of John M. Crawford Jr., 1987",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49178,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1984.475.2,false,true,49172,Asian Art,Hanging scroll,清 石濤(朱若極) 風雨竹圖 軸|Bamboo in Wind and Rain,China,Qing dynasty (1644–1911),,,,Artist,,Shitao (Zhu Ruoji),"Chinese, 1642–1707",,Shitao,Chinese,1642,1707,ca. 1694,1684,1704,Hanging scroll; ink on paper,Image: 87 3/4 x 30 in. (222.9 x 76.2 cm) Overall with mounting: 132 1/4 x 37 3/8 in. (335.9 x 94.9 cm) Overall with knobs: 132 1/4 x 41 in. (335.9 x 104.1 cm),"Edward Elliott Family Collection, Gift of Douglas Dillon, 1984",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49172,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1985.227.1,false,true,49170,Asian Art,Handscroll,清 石濤 (朱若極) 十六羅漢圖 卷|The Sixteen Luohans,China,Qing dynasty (1644–1911),,,,Artist,,Shitao (Zhu Ruoji),"Chinese, 1642–1707",,Shitao,Chinese,1642,1707,dated 1667,1667,1667,Handscroll; ink on paper,Image: 18 1/4 x 235 3/4 in. (46.4 x 598.8 cm) Overall with mounting: 22 5/16 x 895 in. (56.7 x 2273.3 cm),"Gift of Douglas Dillon, 1985",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49170,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1981.285.13,false,true,49183,Asian Art,Hanging scroll,清 石濤(朱若極) 重陽山水圖 軸|Landscape Painted on the Double Ninth Festival,China,Qing dynasty (1644–1911),,,,Artist,,Shitao (Zhu Ruoji),"Chinese, 1642–1707",,Shitao,Chinese,1642,1707,dated 1705,1705,1705,Hanging scroll; ink and color on paper,Image: 28 3/16 x 16 5/8 in. (71.6 x 42.2 cm) Overall with mounting: 86 1/8 x 23 in. (218.8 x 58.4 cm) Overall with knobs: 86 1/8 x 26 3/4 in. (218.8 x 67.9 cm),"Ex coll.: C. C. Wang Family, Edward Elliott Family Collection, Purchase, The Dillon Fund Gift, 1981",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49183,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.151,false,true,49171,Asian Art,Album leaf,清 石濤(朱若極) 山水人物圖 冊頁|Landscape with Figure,China,Qing dynasty (1644–1911),,,,Artist,,Shitao (Zhu Ruoji),"Chinese, 1642–1707",,Shitao,Chinese,1642,1707,ca. 1678,1668,1688,Album leaf; ink on paper,8 1/2 x 11 1/4 in. (21.6 x 28.6 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49171,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.153,false,true,49175,Asian Art,Folding fan mounted as an album leaf,清 石濤 (朱若極) 山水圖 扇頁|Landscape,China,Qing dynasty (1644–1911),,,,Artist,,Shitao (Zhu Ruoji),"Chinese, 1642–1707",,Shitao,Chinese,1642,1707,dated 1699,1699,1699,Folding fan mounted as an album leaf; ink and color on gold-flecked paper,6 7/8 x 17 1/2 in. (17.5 x 44.5 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49175,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"13.220.37a, b",false,true,36018,Asian Art,Handscroll,,China,Qing dynasty (1644–1911),,,,Artist,In the Style of,Shitao (Zhu Ruoji),"Chinese, 1642–1707",,Shitao,Chinese,1642,1707,19th–early 20th century,1800,1913,Handscroll; ink on paper,19 5/8 in. × 20 ft. 9 3/4 in. (49.8 × 634.4 cm),"John Stewart Kennedy Fund, 1913",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/36018,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.122a–l,false,true,49176,Asian Art,Album,清 石濤 (朱若極) 野色圖 冊|Wilderness Colors,China,Qing dynasty (1644–1911),,,,Artist,,Shitao (Zhu Ruoji),"Chinese, 1642–1707",,Shitao,Chinese,1642,1707,ca. 1700,1690,1707,Album of twelve paintings: ink and color on paper,Image (each leaf): 10 7/8 x 9 1/2 in. (27.6 x 24.1 cm),"The Sackler Fund, 1972",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49176,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.280a–n,false,true,49173,Asian Art,Album,清 石濤 (朱若極) 歸棹 冊 紙本|Returning Home,China,Qing dynasty (1644–1911),,,,Artist,,Shitao (Zhu Ruoji),"Chinese, 1642–1707",,Shitao,Chinese,1642,1707,ca. 1695,1685,1715,Album of twelve leaves; ink and color on paper,Image (each): 6 1/2 × 4 1/8 in. (16.5 × 10.5 cm) Each leaf with painting: 8 5/16 × 5 5/16 in. (21.1 × 13.5 cm) Each double leaf unfolded: 8 5/16 × 10 5/8 in. (21.1 × 27 cm),"From the P. Y. and Kinmay W. Tang Family, Gift of Wen and Constance Fong, in honor of Mr. and Mrs. Douglas Dillon, 1976",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49173,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1977.80,false,true,49187,Asian Art,Handscroll,清 王原祁 輞川圖 卷|Wangchuan Villa,China,Qing dynasty (1644–1911),,,,Artist,,Wang Yuanqi,"Chinese, 1642–1715",,WANG YUANQI,Chinese,1642,1715,dated 1711,1711,1711,Handscroll; ink and color on paper,Image: 14 in. x 17 ft. 10 3/4 in. (35.6 x 545.5 cm) Overall with mounting: 14 3/8 in. x 34 ft. 7 1/2 in. (36.5 x 1055.4 cm),"Ex coll.: C. C. Wang Family, Purchase, Douglas Dillon Gift, 1977",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49187,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2011.574,false,true,62743,Asian Art,Hanging scroll,清 王原祁 為瞻亭畫七發妙劑圖 軸|Landscape for Zhanting,China,Qing dynasty (1644–1911),,,,Artist,,Wang Yuanqi,"Chinese, 1642–1715",,WANG YUANQI,Chinese,1642,1715,dated 1710,1710,1710,Hanging scroll; ink and color on paper,Image: 37 1/2 × 18 1/2 in. (95.3 × 47 cm) Overall with mounting: 92 5/8 × 25 1/4 in. (235.3 × 64.1 cm) Overall with knobs: 92 5/8 × 29 in. (235.3 × 73.7 cm),"Gift of Marie-Hélène Weill and Guy A. Weill, 2011",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/62743,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.278.6,false,true,41487,Asian Art,Hanging scroll,清 王原祁 倣黃公望高克恭山水圖 軸|Landscape in the Styles of Huang Gongwang and Gao Kegong,China,Qing dynasty (1644–1911),,,,Artist,,Wang Yuanqi,"Chinese, 1642–1715",,WANG YUANQI,Chinese,1642,1715,dated 1705,1705,1705,Hanging scroll; ink on paper,Image: 45 1/8 x 21 1/4 in. (114.6 x 54 cm) Overall with mounting: 102 1/4 x 29 in. (259.7 x 73.7 cm) Overall with knobs: 102 1/4 x 32 3/4 in. (259.7 x 83.2 cm),"Ex coll.: C. C. Wang Family, Gift of Mr. and Mrs. Earl Morse, 1972",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/41487,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.156,false,true,49185,Asian Art,Hanging scroll,清 王原祁 倣吳鎮山水圖 軸|Landscape after Wu Zhen,China,Qing dynasty (1644–1911),,,,Artist,,Wang Yuanqi,"Chinese, 1642–1715",,WANG YUANQI,Chinese,1642,1715,dated 1695,1695,1695,Hanging scroll; ink on paper,Image: 42 3/4 x 20 1/4 in. (108.6 x 51.4 cm) Overall with mounting: 84 x 27 1/2 in. (213.4 x 69.9 cm) Overall with knobs: 84 x 31 3/4 in. (213.4 x 80.6 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49185,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.157,false,true,49186,Asian Art,Handscroll,清 王原祁 江國垂綸圖 卷|Fishing in River Country at Blossom Time,China,Qing dynasty (1644–1911),,,,Artist,,Wang Yuanqi,"Chinese, 1642–1715",,WANG YUANQI,Chinese,1642,1715,dated 1709,1709,1709,Handscroll; ink and color on paper,10 1/4 x 57 1/2 in. (26 x 146.1 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49186,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -13.220.31,false,true,36015,Asian Art,Hanging scroll,清 陳書 白鸚鵡圖 軸|Cockatoo,China,Qing dynasty (1644–1911),,,,Artist,,Chen Shu,"Chinese, 1660–1736",,Chen Shu,Chinese,1660,1736,dated 1721,1721,1721,Hanging scroll; ink and color on paper,37 1/16 x 17 3/16 in. (94.1 x 43.7 cm),"John Stewart Kennedy Fund, 1913",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/36015,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -13.220.119,false,true,49239,Asian Art,Hanging scroll,清 華喦 白芍藥圖 軸|White Peony and Rocks,China,Qing dynasty (1644–1911),,,,Artist,,Hua Yan,"Chinese, 1682–1756",,Hua Yan,Chinese,1682,1756,dated 1752,1752,1752,Hanging scroll; ink and color on paper,Image: 50 1/4 x 22 1/2 in. (127.6 x 57.2 cm) Overall with mounting: 115 × 30 3/8 in. (292.1 × 77.2 cm) Overall with knobs: 115 × 34 3/4 in. (292.1 × 88.3 cm),"John Stewart Kennedy Fund, 1913",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49239,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1981.285.8a–f,false,true,49237,Asian Art,Album,清 傳高鳳翰 書畫合璧 冊|Landscapes and Calligraphy,China,Qing dynasty (1644–1911),,,,Artist,,Gao Fenghan,"Chinese, 1683–1749",,Gao Fenghan,Chinese,1683,1749,dated 1736,1736,1736,Album of six paintings; ink and color on paper,Each leaf: 12 1/2 x 9 3/4 in. (31.8 x 24.8 cm),"Edward Elliott Family Collection, Purchase, The Dillon Fund Gift, 1981",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49237,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -13.220.123,false,true,36027,Asian Art,Hanging scroll,清 張庚 倣王蒙山水圖 軸|Landscape After Wang Meng,China,Qing dynasty (1644–1911),,,,Artist,,Zhang Geng,"Chinese, 1685–1760",,Zhang Geng,Chinese,1685,1760,dated 1759,1759,1759,Hanging scroll; ink and color on paper,49 x 15 31/32 in. (124.5 x 40.6 cm),"John Stewart Kennedy Fund, 1913",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/36027,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -42.74.1a–h,false,true,36047,Asian Art,Album,清 張宗蒼 山水 冊 紙本|Miniature landscapes,China,Qing dynasty (1644–1911),,,,Artist,,Zhang Zongcang,"Chinese, 1686–1756",,Zhang Zongcang,Chinese,1686,1756,datable to 1751–54,1741,1764,Album of eight leaves; ink and color on paper,1 7/16 x 1 3/4 in. (3.7 x 4.4 cm),"Fletcher Fund, 1942",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/36047,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1980.426.5a–h,false,true,49240,Asian Art,Album,清 汪士慎 山水花卉圖 冊|Landscapes and Flowers,China,Qing dynasty (1644–1911),,,,Artist,,Wang Shishen,"Chinese, 1686–1759",,Wang Shishen,Chinese,1686,1759,dated 1745,1745,1745,Album of eight paintings; ink and color on paper,Image: 8 x 9 7/8 in. (20.3 x 25.1 cm),"Gift of Douglas Dillon, 1980",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49240,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1997.438.1,false,true,39767,Asian Art,Hanging scroll,清 傳金農 番馬圖 軸|Grooms and Foreign Horses,China,Qing dynasty (1644–1911),,,,Artist,,Jin Nong,"Chinese, 1687–1773",,Jin Nong,Chinese,1687,1773,17th–18th century,1687,1763,Hanging scroll; ink and color on silk,Image: 27 1/2 x 21 3/4 in. (69.9 x 55.2 cm) Overall with mounting: 92 1/2 x 22 7/8 in. (235 x 58.1 cm) Overall with knobs: 92 1/2 x 26 1/4 in. (235 x 66.7 cm),"The C. C. Wang Family Collection, Gift of C. C. Wang, 1997",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/39767,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.160,false,true,49243,Asian Art,Hanging scroll,清 金農 墨梅圖 軸|Blossoming Prunus,China,Qing dynasty (1644–1911),,,,Artist,,Jin Nong,"Chinese, 1687–1773",,Jin Nong,Chinese,1687,1773,dated 1759,1759,1759,Hanging scroll; ink on paper,Image: 49 3/8 x 17 in. (125.4 x 43.2 cm) Overall with mounting: 85 1/2 x 21 in. (217.2 x 53.3 cm) Overall with knobs: 85 1/2 x 23 3/4 in. (217.2 x 60.3 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49243,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.495a–l,false,true,36432,Asian Art,Album,清 金農 梅花圖 冊|Plum Blossoms,China,Qing dynasty (1644–1911),,,,Artist,,Jin Nong,"Chinese, 1687–1773",,Jin Nong,Chinese,1687,1773,dated 1757,1757,1757,Album of twelve leaves; ink on paper,Image: 10 x 11 3/4 in. (25.4 x 29.8 cm),"Edward Elliott Family Collection, Gift of Douglas Dillon, 1986",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/36432,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1997.439a–m,false,true,39764,Asian Art,Album,,China,Qing dynasty (1644–1911),,,,Artist,,Jin Nong,"Chinese, 1687–1773",,Jin Nong,Chinese,1687,1773,dated 1754,1754,1754,Album of twelve paintings; ink and color on paper,Each 11 1/4 x 9 3/8 in. (28.6 x 23.8 cm),"Gift of Mr. and Mrs. Wan-go H. C. Weng, 1997",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/39764,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1981.285.7,false,true,49244,Asian Art,Handscroll,清 鄭燮 蘭竹圖 卷|Orchids and Bamboo,China,Qing dynasty (1644–1911),,,,Artist,,Zheng Xie,"Chinese, 1693–1765",,Zheng Xie,Chinese,1693,1765,dated 1742,1742,1742,Handscroll; ink on paper,Image: 13 3/4 x 147 1/2 in. (34.9 x 374.7 cm) Overall with mounting: 15 1/8 x 295 1/4 in. (38.4 x 749.9 cm),"Edward Elliott Family Collection, Purchase, Dillon Fund Gift, 1981",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49244,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1990.322a–d,false,true,44620,Asian Art,Hanging scrolls,清 鄭燮 遠山煙竹圖 軸|Misty Bamboo on a Distant Mountain,China,Qing dynasty (1644–1911),,,,Artist,,Zheng Xie,"Chinese, 1693–1765",,Zheng Xie,Chinese,1693,1765,dated 1753,1753,1753,Set of four hanging scrolls; ink on paper,Overall with mounting (each): 107 1/4 × 27 in. (272.4 × 68.6 cm),"From the P. Y. and Kinmay W. Tang Family Collection, Gift of Oscar L. Tang, 1990",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/44620,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1971.256,false,true,49247,Asian Art,Hanging scroll,,China,Qing dynasty (1644–1911),,,,Artist,,Li Fangying,"Chinese, 1696–1754",,Li Fangying,Chinese,1696,1754,Dated 1743,1743,1743,Hanging scroll; ink and color on paper,Image: 44 7/8 x 23 3/8 in. (114 x 59.4 cm) Overall with mounting: 99 1/2 x 29 5/8 in. (252.7 x 75.2 cm) Overall with rollers: 99 1/2 x 33 1/4 in. (252.7 x 84.5 cm),"From the P. Y. and Kinmay W. Tang Family Collection, Gift of Professor Wen Fong, 1971",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49247,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.163,false,true,49253,Asian Art,Folding fan mounted as an album leaf,,China,Qing dynasty (1644–1911),,,,Artist,,Wang Chen,"Chinese, 1720–1797",,Wang Chen,Chinese,1720,1797,dated 1788,1788,1788,Folding fan mounted as an album leaf; ink and color on paper,7 1/8 x 20 1/4 in. (18.1 x 51.4 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49253,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.510.1,false,true,73161,Asian Art,Hanging scroll,清 浸月圖 軸|Plum Blossoms in Moonlight,China,Qing dynasty (1644–1911),,,,Artist,,Tong Yu,"Chinese, 1721–1782",,Tong Yu,Chinese,1721,1782,second half of the 18th century,1751,1799,Hanging scroll; ink on paper,Image: 49 5/8 x 11 3/4 in. (126 x 29.8 cm),"The Lin Yutang Family Collection, Gift of Hsiang Ju Lin, in memory of Taiyi Lin Lai, 2005",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/73161,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -13.220.34,false,true,49254,Asian Art,Hanging scroll,清 羅聘 篠園飲酒圖 軸|Drinking in the Bamboo Garden,China,Qing dynasty (1644–1911),,,,Artist,,Luo Ping,"Chinese, 1733–1799",,Luo Ping,Chinese,1733,1799,dated 1773,1773,1773,Hanging scroll; ink and color on paper,Image: 31 1/2 x 21 1/2 in. (80 x 54.6 cm) Overall with mounting: 116 x 27 7/16 in. (294.6 x 69.7 cm) Overall with knobs: 116 x 32 5/8 in. (294.6 x 82.9 cm),"John Stewart Kennedy Fund, 1913",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49254,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1997.438.3,false,true,39765,Asian Art,Hanging scroll,清 雙駿圖 軸|Two Horses,China,Qing dynasty (1644–1911),,,,Artist,,Qian Feng,"Chinese, 1740–1795",,Qian Feng,Chinese,1740,1795,dated 1793,1793,1793,Hanging scroll; ink and color on paper,Image: 48 5/8 x 20 1/4 in. (123.5 x 51.4 cm) Overall with mounting: 87 1/2 x 25 3/4 in. (222.3 x 65.4 cm) Overall with knobs: 87 1/2 x 29 15/16 in. (222.3 x 76 cm),"The C. C. Wang Family Collection, Gift of C. C. Wang, 1997",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/39765,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.267.2a–h,false,true,41494,Asian Art,Album,清 伊秉綬 山水 冊頁八開|Landscapes,China,Qing dynasty (1644–1911),,,,Artist,,Yi Bingshou,"Chinese, 1754–1815",,YI BINGSHOU,Chinese,1754,1815,dated 1814,1814,1814,Album of eight leaves; ink on paper,9 3/4 x 11 5/8 in. (24.8 x 29.5 cm),"Gift of Robert Hatfield Ellsworth, in memory of La Ferne Hatfield Ellsworth, 1986",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/41494,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -13.220.36,false,true,36017,Asian Art,Hanging scroll,清 湯貽汾 山水 軸|Landscape,China,Qing dynasty (1644–1911),,,,Artist,,Tang Yifen,"Chinese, 1778–1853",,Tang Yifen,Chinese,1778,1853,dated 1845,1845,1845,Hanging scroll; ink on paper,Image: 46 x 10 3/4 in. (116.8 x 27.3 cm) Overall with mounting: 97 1/2 x 17 1/2 in. (247.7 x 44.5 cm) Overall with knobs: 97 1/2 x 20 1/8 in. (247.7 x 51.1 cm),"John Stewart Kennedy Fund, 1913",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/36017,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.267.26,false,true,36160,Asian Art,Hanging scroll,清 王素 東山報捷圖 軸|Bringing the Message of Victory to Dongshan,China,Qing dynasty (1644–1911),,,,Artist,,Wang Su,"Chinese, 1794–1877",,Wang Su,Chinese,1794,1877,dated 1862,1862,1862,Hanging scroll; ink and color on paper,56 1/2 x 31 3/4 in. (143.5 x 80.6 cm),"Gift of Robert Hatfield Ellsworth, in memory of La Ferne Hatfield Ellsworth, 1986",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/36160,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.267.16,false,true,36155,Asian Art,Album leaf,清 程庭鷺 山水 冊頁|Landscape,China,Qing dynasty (1644–1911),,,,Artist,,Cheng Tinglu,"Chinese, 1796–1858",,Cheng Tinglu,Chinese,1796,1858,dated 1827,1827,1827,Album leaf; ink and color on paper,9 3/4 x 13 1/2 in. (24.8 x 34.3 cm),"Gift of Robert Hatfield Ellsworth, in memory of La Ferne Hatfield Ellsworth, 1986",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/36155,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.267.21,false,true,49449,Asian Art,Folding fan mounted as an album leaf,清 吳熙載 蟬柳 扇面|Cicada,China,Qing dynasty (1644–1911),,,,Artist,,Wu Xizai,"Chinese, 1799–1870",,Wu Xizai,Chinese,1799,1870,dated 1852,1852,1852,Folding fan mounted as an album leaf; ink and color on alum paper,7 1/8 x 20 3/8 in. (18.1 x 51.8 cm),"Gift of Robert Hatfield Ellsworth, in memory of La Ferne Hatfield Ellsworth, 1986",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49449,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.267.24,false,true,49451,Asian Art,Hanging scroll,清 吳熙載 山水 軸|Landscape,China,Qing dynasty (1644–1911),,,,Artist,,Wu Xizai,"Chinese, 1799–1870",,Wu Xizai,Chinese,1799,1870,dated 1858,1858,1858,Hanging scroll; ink and color on paper,61 1/4 x 17 3/16 in. (155.6 x 43.7 cm),"Gift of Robert Hatfield Ellsworth, in memory of La Ferne Hatfield Ellsworth, 1986",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49451,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1984.215a–m,false,true,49434,Asian Art,Album,清 戴熙 山水 冊頁八開|Landscapes,China,Qing dynasty (1644–1911),,,,Artist,,Dai Xi,"Chinese, 1801–1860",,Dai Xi,Chinese,1801,1860,dated 1848,1848,1848,Album of eight paintings; ink and color on paper,12 1/4 x 7 5/8 in. (31.1 x 19.4 cm),"Purchase, Bequest of Dorothy Graham Bennett, 1984",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49434,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.267.40,false,true,49458,Asian Art,Album leaf,清 張熊 山水 冊頁|Landscape,China,Qing dynasty (1644–1911),,,,Artist,,Zhang Xiong,"Chinese, 1803–1886",,Zhang Xiong,Chinese,1803,1886,dated 1827,1827,1827,Album leaf; ink and color on paper,9 3/4 x 13 1/2 in. (24.8 x 34.3 cm),"Gift of Robert Hatfield Ellsworth, in memory of La Ferne Hatfield Ellsworth, 1986",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49458,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.267.19,false,true,36157,Asian Art,Folding fan mounted as an album leaf,清 劉德六 松鼠葡萄 扇面|Squirrel and Grape,China,Qing dynasty (1644–1911),,,,Artist,,Liu Deliu,"Chinese, 1806–1875",,Liu Deliu,Chinese,1806,1875,dated 1868,1868,1868,Folding fan mounted as an album leaf; ink and color on alum paper,6 15/16 x 20 1/16 in. (17.6 x 51 cm),"Gift of Robert Hatfield Ellsworth, in memory of La Ferne Hatfield Ellsworth, 1986",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/36157,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.267.31,false,true,49454,Asian Art,Folding fan mounted as an album leaf,清 胡遠 山水 扇面|Landscape,China,Qing dynasty (1644–1911),,,,Artist,,Hu Yuan,"Chinese, 1823–1886",,Hu Yuan,Chinese,1823,1886,dated 1885,1885,1885,Folding fan mounted as an album leaf; ink and color on alum paper,6 15/16 x 20 1/4 in. (17.6 x 51.4 cm),"Gift of Robert Hatfield Ellsworth, in memory of La Ferne Hatfield Ellsworth, 1986",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49454,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.267.53,false,true,49468,Asian Art,Album leaf,清 虛谷 秋帆圖 冊頁|Sailing in Autumn,China,Qing dynasty (1644–1911),,,,Artist,,Xu Gu,"Chinese, 1823–1896",,Xu Gu,Chinese,1823,1896,dated 1893,1893,1893,Album leaf; ink and color on paper,14 1/8 x 35 7/8 in. (35.9 x 91.1 cm),"Gift of Robert Hatfield Ellsworth, in memory of La Ferne Hatfield Ellsworth, 1986",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49468,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.267.54,false,true,49469,Asian Art,Folding fan mounted as an album leaf,清 虛谷 松鼠 扇面|Squirrel on an Autumn Branch,China,Qing dynasty (1644–1911),,,,Artist,,Xu Gu,"Chinese, 1823–1896",,Xu Gu,Chinese,1823,1896,ca. 1880s,1870,1890,Folding fan mounted as an album leaf; ink and color on alum paper,7 1/4 x 19 in. (18.4 x 48.3 cm),"Gift of Robert Hatfield Ellsworth, in memory of La Ferne Hatfield Ellsworth, 1986",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49469,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.267.27,false,true,36161,Asian Art,Folding fan mounted as an album leaf,清 趙之謙 芍藥 扇面|Peony,China,Qing dynasty (1644–1911),,,,Artist,,Zhao Zhiqian,"Chinese, 1829–1884",,Zhao Zhiqian,Chinese,1829,1884,dated 1862,1862,1862,Folding fan mounted as an album leaf; ink and color on gold-flecked paper,7 x 20 3/4 in. (17.8 x 52.7 cm),"Gift of Robert Hatfield Ellsworth, in memory of La Ferne Hatfield Ellsworth, 1986",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/36161,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.267.28,false,true,49452,Asian Art,Folding fan mounted as an album leaf,清 趙之謙 芍藥 桃花 扇面|Peach Blossoms and Peony,China,Qing dynasty (1644–1911),,,,Artist,,Zhao Zhiqian,"Chinese, 1829–1884",,Zhao Zhiqian,Chinese,1829,1884,ca. 1860,1850,1870,Folding fan mounted as an album leaf; ink and color on gold-flecked paper,7 1/2 x 21 1/2 in. (19.1 x 54.6 cm),"Gift of Robert Hatfield Ellsworth, in memory of La Ferne Hatfield Ellsworth, 1986",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49452,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.267.77,false,true,36187,Asian Art,Folding fan mounted as an album leaf,清 沙馥 松鼠葡萄 扇面|Squirrel and Grapes,China,Qing dynasty (1644–1911),,,,Artist,,Sha Fu,"Chinese, 1831–1906",,Sha Fu,Chinese,1831,1906,dated 1894,1894,1894,Folding fan mounted as an album leaf; ink and color on alum paper,6 5/8 x 19 3/4 in. (16.8 x 50.2 cm),"Gift of Robert Hatfield Ellsworth, in memory of La Ferne Hatfield Ellsworth, 1986",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/36187,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.267.78,false,true,49474,Asian Art,Folding fan mounted as an album leaf,清 沙馥 人物 扇面|The Peach and Plum Garden,China,Qing dynasty (1644–1911),,,,Artist,,Sha Fu,"Chinese, 1831–1906",,Sha Fu,Chinese,1831,1906,dated 1879,1879,1879,Folding fan mounted as an album leaf; ink and color on alum paper,7 x 21 3/8 in. (17.8 x 54.3 cm),"Gift of Robert Hatfield Ellsworth, in memory of La Ferne Hatfield Ellsworth, 1986",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49474,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.267.80,false,true,49476,Asian Art,Folding fan mounted as an album leaf,清 錢慧安 仕女 扇面|A Beauty,China,Qing dynasty (1644–1911),,,,Artist,,Qian Huian,"Chinese, 1833–1911",,Qian Huian,Chinese,1833,1911,dated 1876,1876,1876,Folding fan mounted as an album leaf; ink and color on alum paper,7 1/4 x 21 in. (18.4 x 53.3 cm),"Gift of Robert Hatfield Ellsworth, in memory of La Ferne Hatfield Ellsworth, 1986",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49476,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.267.45,false,true,49460,Asian Art,Folding fan mounted as an album leaf,清 任薰 高士臨風 扇面|Scholar in the Wind,China,Qing dynasty (1644–1911),,,,Artist,,Ren Xun,"Chinese, 1835–1893",,Ren Xun,Chinese,1835,1893,ca. 1880,1870,1890,Folding fan mounted as an album leaf; ink and color on alum paper,6 9/16 x 9 3/8 in. (16.7 x 23.8 cm),"Gift of Robert Hatfield Ellsworth, in memory of La Ferne Hatfield Ellsworth, 1986",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49460,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.267.46,false,true,49461,Asian Art,Folding fan mounted as an album leaf,清 任薰 海棠小鳥 扇面|Bird on a Rock by a Flowering Branch,China,Qing dynasty (1644–1911),,,,Artist,,Ren Xun,"Chinese, 1835–1893",,Ren Xun,Chinese,1835,1893,dated 1879,1879,1879,Folding fan mounted as an album leaf; ink and color on alum paper,7 x 20 7/8 in. (17.8 x 53.0 cm),"Gift of Robert Hatfield Ellsworth, in memory of La Ferne Hatfield Ellsworth, 1986",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49461,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1985.131.2,false,true,49464,Asian Art,Album leaf,清 任頤 冊頁|Two Birds Perched on a Flowering Rose Bush,China,Qing dynasty (1644–1911),,,,Artist,,Ren Yi (Ren Bonian),"Chinese, 1840–1896",,Ren Yi (Ren Bonian),Chinese,1840,1896,late 19th century,19,19,Album leaf; ink and color on paper,10 7/8 x 10 5/8 in. (27.6 x 27.0 cm),"Bequest of Louisa L. McNeary, 1985",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49464,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1988.324.1,false,true,44566,Asian Art,Hanging scroll,"清 任頤 松鶴圖 軸|Cranes, Pine Tree, and Lichen",China,Qing dynasty (1644–1911),,,,Artist,,Ren Yi (Ren Bonian),"Chinese, 1840–1896",,Ren Yi (Ren Bonian),Chinese,1840,1896,dated 1885,1800,1940,Hanging scroll; ink and color on paper,Image: 57 3/4 x 14 3/4 in. (146.7 x 37.5 cm) Overall with mounting: 95 1/4 x 21 5/8 in. (241.9 x 54.9 cm) Overall with knobs: 95 1/4 x 23 3/4 in. (241.9 x 60.3 cm),"Gift of Robert Hatfield Ellsworth, in memory of La Ferne Hatfield Ellsworth, 1988",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/44566,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2002.208.2,false,true,37393,Asian Art,Hanging scroll,清 任頤 鍾馗 軸|Zhong Kui,China,Qing dynasty (1644–1911),,,,Artist,,Ren Yi (Ren Bonian),"Chinese, 1840–1896",,Ren Yi (Ren Bonian),Chinese,1840,1896,dated 1883,1883,1883,Hanging scroll; ink and color on paper,Image: 67 5/8 x 36 3/4 in. (171.8 x 93.3 cm) Overall with mounting: 120 x 40 5/8 in. (304.8 x 103.2 cm) Overall with knobs: 120 x 44 1/2 in. (304.8 x 113 cm),"Gift of Cécile and Sandy Mactaggart, 2002",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/37393,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.267.48,false,true,49463,Asian Art,Folding fan mounted as an album leaf,清 任頤 童子水牛 扇面|Herdboy and Buffalo,China,Qing dynasty (1644–1911),,,,Artist,,Ren Yi (Ren Bonian),"Chinese, 1840–1896",,Ren Yi (Ren Bonian),Chinese,1840,1896,dated 1890,1890,1890,Folding fan mounted as an album leaf; ink and color on alum paper,7 1/2 x 21 3/8 in. (19.1 x 54.3 cm),"Gift of Robert Hatfield Ellsworth, in memory of La Ferne Hatfield Ellsworth, 1986",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49463,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.267.49,false,true,39715,Asian Art,Folding fan mounted as an album leaf,清 任頤 石上讀書 扇面|Scholar on a Rock,China,Qing dynasty (1644–1911),,,,Artist,,Ren Yi (Ren Bonian),"Chinese, 1840–1896",,Ren Yi (Ren Bonian),Chinese,1840,1896,ca. 1880,1870,1890,Folding fan mounted as an album leaf; ink and color on paper,Image: 7 1/2 x 21 3/16 in. (19.1 x 53.8 cm),"Gift of Robert Hatfield Ellsworth, in memory of La Ferne Hatfield Ellsworth, 1986",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/39715,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.267.50,false,true,49462,Asian Art,Hanging scroll,清 任頤 軸|Man on a Bridge,China,Qing dynasty (1644–1911),,,,Artist,,Ren Yi (Ren Bonian),"Chinese, 1840–1896",,Ren Yi (Ren Bonian),Chinese,1840,1896,dated 1889,1889,1889,Hanging scroll; ink on bark paper,36 7/8 x 24 1/4 in. (93.7 x 61.6 cm),"Gift of Robert Hatfield Ellsworth, in memory of La Ferne Hatfield Ellsworth, 1986",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49462,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.267.79,false,true,49475,Asian Art,Folding fan mounted as an album leaf,清 金? 梅花 扇面|Plum,China,Qing dynasty (1644–1911),,,,Artist,,Jin Lan,"Chinese, 1841–1910",,Jin Lan,Chinese,1841,1910,dated 1886,1886,1886,Folding fan mounted as an album leaf; ink and color on alum paper,7 x 21 in. (17.8 x 53.3 cm),"Gift of Robert Hatfield Ellsworth, in memory of La Ferne Hatfield Ellsworth, 1986",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49475,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.267.68,false,true,36181,Asian Art,Folding fan mounted as an album leaf,清 吳榖祥 山水 扇面|Landscape,China,Qing dynasty (1644–1911),,,,Artist,,Wu Guxiang,"Chinese, 1848–1903",,Wu Guxiang,Chinese,1848,1903,dated 1894,1894,1894,Folding fan mounted as an album leaf; ink on gold paper,7 1/4 x 20 5/16 in. (18.4 x 51.6 cm),"Gift of Robert Hatfield Ellsworth, in memory of La Ferne Hatfield Ellsworth, 1986",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/36181,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.267.66,false,true,36179,Asian Art,Folding fan mounted as an album leaf,清 徐祥 小鳥水仙 扇面|Birds and Narcissus,China,Qing dynasty (1644–1911),,,,Artist,,Xu Xiang,"Chinese, 1850–1899",,Xu Xiang,Chinese,1850,1899,dated 1883,1883,1883,Folding fan mounted as an album leaf; ink and color on alum paper,7 1/4 x 21 in. (18.4 x 53.3 cm),"Gift of Robert Hatfield Ellsworth, in memory of La Ferne Hatfield Ellsworth, 1986",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/36179,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.267.101,false,true,49602,Asian Art,Folding fan mounted as an album leaf,近代 高邕 楊柳 扇面|Willow,China,Qing dynasty (1644–1911),,,,Artist,,Gao Yong,"Chinese, 1850–1921",,Gao Yong,Chinese,1850,1921,dated 1895,1895,1895,Folding fan mounted as an album leaf; ink and color on alum paper,7 1/4 x 19 3/8 in. (18.4 x 49.2 cm),"Gift of Robert Hatfield Ellsworth, in memory of La Ferne Hatfield Ellsworth, 1986",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49602,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.267.143,false,true,41905,Asian Art,Hanging scroll,近代 吳淑娟 仙姑圖 軸|Female Immortals,China,Qing dynasty (1644–1911),,,,Artist,,Wu Shujuan,"Chinese, 1853–1930",,Wu Shujuan,Chinese,1853,1930,dated 1909,1909,1909,Hanging scroll; ink and color on paper,47 3/4 x 20 5/8 in. (121.3 x 52.4 cm),"Gift of Robert Hatfield Ellsworth, in memory of La Ferne Hatfield Ellsworth, 1986",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/41905,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.267.89,false,true,36194,Asian Art,Folding fan mounted as an album leaf,近代 倪田 橋頭隱士 扇面|Scholar on the Bridge,China,Qing dynasty (1644–1911),,,,Artist,,Ni Tian,"Chinese, 1855–1919",,Ni Tian,Chinese,1855,1919,dated 1901,1901,1901,Folding fan mounted as an album leaf; ink and color on alum paper,6 3/8 x 19 5/8 in. (16.2 x 49.8 cm),"Gift of Robert Hatfield Ellsworth, in memory of La Ferne Hatfield Ellsworth, 1986",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/36194,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.267.91,false,true,49594,Asian Art,Hanging scroll,近代 倪田 雙馬圖 軸|Two Horses,China,Qing dynasty (1644–1911),,,,Artist,,Ni Tian,"Chinese, 1855–1919",,Ni Tian,Chinese,1855,1919,dated 1904,1904,1904,Hanging scroll; ink and color on paper,42 7/8 x 19 9/16 in. (108.9 x 49.7 cm),"Gift of Robert Hatfield Ellsworth, in memory of La Ferne Hatfield Ellsworth, 1986",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49594,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.267.17,false,true,36156,Asian Art,Album leaf,清 沈焯 山水 冊頁|Landscape,China,Qing dynasty (1644–1911),,,,Artist,,Shen Zhuo,(active 19th century),,Shen Zhuo,Chinese,1800,1899,dated 1827,1827,1827,Album leaf; ink and color on paper,9 3/4 x 13 1/2 in. (24.8 x 34.3 cm),"Gift of Robert Hatfield Ellsworth, in memory of La Ferne Hatfield Ellsworth, 1986",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/36156,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.267.55a–l,false,true,36171,Asian Art,Album,清 張之萬 山水 冊頁十二開|Landscapes,China,Qing dynasty (1644–1911),,,,Artist,,Zhang Zhiwan,"Chinese, 1811–1897",,Zhang Zhiwan,Chinese,1811,1897,dated 1875,1875,1875,Album of twelve leaves; ink and color on paper,9 5/16 x 9 13/16 in. (23.7 x 24.9 cm),"Gift of Robert Hatfield Ellsworth, in memory of La Ferne Hatfield Ellsworth, 1986",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/36171,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.161,false,true,49241,Asian Art,Hanging scroll,清 傳李鱓 墨竹圖 軸|Ink Bamboo,China,Qing dynasty (1644–1911),,,,Artist,,Li Shan,"Chinese, 1686–ca. 1756",,Li Shan,Chinese,1686,1756,dated 1749,1749,1749,Hanging scroll; ink on paper,Image: 52 x 29 1/8 in. (132.1 x 74 cm) Overall with mounting: 99 1/2 x 32 1/8 in. (252.7 x 81.6 cm) Overall with knobs: 99 1/2 x 35 1/2 in. (252.7 x 90.2 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49241,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.267.41,false,true,36167,Asian Art,Folding fan mounted as an album leaf,清 居巢 蠶蛾 扇面|Silkworm,China,Qing dynasty (1644–1911),,,,Artist,,Zhü Chao,"Chinese, ca. 1823–1889",,Zhü Chao,Chinese,1823,1889,dated 1859,1859,1859,Folding fan mounted as an album leaf; ink and color on alum paper,7 1/4 x 21 in. (18.4 x 53.3 cm),"Gift of Robert Hatfield Ellsworth, in memory of La Ferne Hatfield Ellsworth, 1986",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/36167,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1982.461,false,true,39714,Asian Art,Handscroll,清 佚名 臨袁江瞻園圖 卷|View of a Garden Villa,China,Qing dynasty (1644–1911),,,,Artist,After,Yuan Jiang,active ca.1680–ca.1730,,Yuan Jiang,Chinese,1680,1730,18th century (?),1700,1740,Handscroll; ink and color on silk,Image: 20 1/2 x 116 1/8 in. (52.1 x 295 cm) Overall with mounting: 256 5/8 x 24 7/8 in. (651.8 x 63.2 cm),"From the P. Y. and Kinmay W. Tang Family Collection, Gift of Constance Tang Fong, in honor of her mother, Mrs. P. Y. Tang, 1982",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/39714,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1982.125a–l,false,true,49227,Asian Art,Hanging scrolls,清 袁江 九成宮圖 屏|The Palace of Nine Perfections,China,Qing dynasty (1644–1911),,,,Artist,,Yuan Jiang,active ca.1680–ca.1730,,Yuan Jiang,Chinese,1680,1730,dated 1691,1691,1691,Set of twelve hanging scrolls; ink and color on silk,Image: 81 1/2 x 18 ft. 5 3/4 in. (207 cm x 563.2 cm) Overall with mounting: 94 1/4 x 19 ft. (239.4 x 579.1 cm),"Purchase, The Dillon Fund Gift, 1982",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49227,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.149,false,true,44581,Asian Art,Hanging scroll,,China,Qing dynasty (1644–1911),,,,Artist,,Feng Qiyong,"Chinese, active ca. 1730s",,Feng Qiyong,Chinese,1730,1739,dated 1733,1733,1733,Hanging scroll; ink on silk,Image: 45 x 25 5/16 in. (114.3 x 64.3 cm) Overall with mounting: 98 x 32 1/2 in. (248.9 x 82.6 cm) Overall with knobs: 98 x 36 1/4 in. (248.9 x 92.1 cm),"Purchase, Bequest of Dorothy Graham Bennett, 1989",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/44581,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -69.242.8–.15,false,true,72263,Asian Art,Album,清 樊圻 山水圖 冊 紙本|Landscapes,China,Qing dynasty (1644–1911),,,,Artist,,Fan Qi,"Chinese, 1616–after 1694",,Fan Qi,Chinese,1616,1694,dated 1646,1646,1646,Album of eight leaves; ink and color on paper,Each leaf: 6 5/8 x 8 in. (16.8 x 20.3 cm),"The Sackler Fund, 1969",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/72263,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.131a–h,false,true,49131,Asian Art,Album,清 樊圻 為玉翁作山水圖 冊|Landscapes Painted for Yuweng,China,Qing dynasty (1644–1911),,,,Artist,,Fan Qi,"Chinese, 1616–after 1694",,Fan Qi,Chinese,1616,1694,dated 1673,1673,1673,Album of eight leaves; ink and color on paper,6 x 7 7/16 in. (15.2 x 18.9 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49131,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2008.270,false,true,74818,Asian Art,Handscroll,"清 高簡 說詩圖 卷|Discourse on Poetry",China,Qing dynasty (1644–1911),,,,Artist,,Gao Jian,"Chinese, 1634–after 1715",,Gao Jian,Chinese,1634,1725,dated 1698,1698,1698,Handscroll; ink on paper,Image: 13 11/16 x 35 7/16 in. (34.8 x 90 cm),"Purchase, The Vincent Astor Foundation Gift and The Dillon Fund Gift, 2008",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/74818,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.7,false,true,49234,Asian Art,Hanging scroll,清 傳沈銓 高堂雙壽圖 軸|Flowering Crabapple and Pair of Birds,China,Qing dynasty (1644–1911),,,,Artist,,Shen Nanpin (Japanese: Shin Nanpin),"Chinese, 1682–after 1762",,Shen Nanpin,Chinese,1682,1762,dated 1744,1744,1744,Hanging scroll; ink and color on silk,37 x 42 1/4 in. (94.0 x 107.3 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49234,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.268.81,false,true,36105,Asian Art,Hanging scroll,"桃と月季花(長春花)に鶴図|Cranes, Peach Tree, and Chinese Roses",China,Qing dynasty (1644–1911),,,,Artist,After,Shen Nanpin (Japanese: Shin Nanpin),"Chinese, 1682–after 1762",,Shen Nanpin,Chinese,1682,1762,early 18th century,1700,1733,Hanging scroll; ink and color on silk,Image: 78 1/4 x 39 3/4 in. (198.8 x 101 cm) Overall with mounting: 97 3/4 x 48 1/8 in. (248.3 x 122.2 cm) Overall with knobs: 97 3/4 x 51 1/8 in. (248.3 x 129.9 cm),"The Harry G. C. Packard Collection of Asian Art, Gift of Harry G. C. Packard, and Purchase, Fletcher, Rogers, Harris Brisbane Dick, and Louis V. Bell Funds, Joseph Pulitzer Bequest, and The Annenberg Fund Inc. Gift, 1975",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/36105,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.268.82,false,true,36106,Asian Art,Hanging scroll,,China,Qing dynasty (1644–1911),,,,Artist,,Shen Nanpin (Japanese: Shin Nanpin),"Chinese, 1682–after 1762",,Shen Nanpin,Chinese,1682,1762,1750,1750,1750,Hanging scroll; ink and color on silk,Image: 45 13/16 x 19 11/16 in. (116.4 x 50 cm) Overall with mounting: 82 x 26 1/4 in. (208.3 x 66.7 cm) Overall with knobs: 82 x 28 5/16 in. (208.3 x 71.9 cm),"The Harry G. C. Packard Collection of Asian Art, Gift of Harry G. C. Packard, and Purchase, Fletcher, Rogers, Harris Brisbane Dick, and Louis V. Bell Funds, Joseph Pulitzer Bequest, and The Annenberg Fund Inc. Gift, 1975",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/36106,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.510.2,false,true,73159,Asian Art,Hanging scroll,清 李香君小影 軸|Portrait of Li Xiangjun,China,Qing dynasty (1644–1911),,,,Artist,,Cui He,"Chinese, active 1800–1850",,Cui He,Chinese,1800,1850,dated 1817,1817,1817,Hanging scroll; ink and color on paper,Image: 20 5/8 x 49 in. (52.4 x 124.5 cm) Overall with knobs: 104 1/16 x 30 1/4 in. (264.3 x 76.8 cm),"The Lin Yutang Family Collection, Gift of Hsiang Ju Lin, in memory of Taiyi Lin Lai, 2005",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/73159,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.278.4,false,true,36100,Asian Art,Hanging scroll,清 王鑑 倣黃公望秋山圖 軸 紙本|Landscape in the style of Huang Gongwang,China,Qing dynasty (1644–1911),,,,Artist,,Wang Jian,"Chinese, 1609–1677 or 1688",,Wang Jian,Chinese,1609,1677,dated 1657,1657,1657,Hanging scroll; ink and color on paper,Image: 45 5/8 x 22 1/8 in. (115.9 x 56.2 cm) Overall with mounting: 103 1/4 x 29 in. (262.3 x 73.7 cm) Overall with rollers: 103 1/4 x 32 1/2 in. (262.3 x 82.6 cm),"Gift of Mr. and Mrs. Earl Morse, 1972",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/36100,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.267.76,false,true,36186,Asian Art,Handscroll,清 松年 山水 手卷|Landscape,China,Qing dynasty (1644–1911),,,,Artist,,Song Nian,"Chinese, active 19th century",,Song Nian,Chinese,1800,1899,dated 1898,1898,1898,Handscroll in six sections; ink and color on paper,Image: 13 3/8 x 107 7/8 in. (34 x 274 cm) Overall with mounting: 13 5/8 x 298 3/4 in. (34.6 x 758.8 cm),"Gift of Robert Hatfield Ellsworth, in memory of La Ferne Hatfield Ellsworth, 1986",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/36186,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2000.665.1,false,true,59025,Asian Art,Hanging scroll,清 祁豸佳 倣倪瓚冬林亭子圖 軸|Pavilion amongst Wintry Trees after Ni Zan,China,Qing dynasty (1644–1911),,,,Artist,,Qi Zhijia,"Chinese, ca. 1595–ca. 1670",,Qi Zhijia,Chinese,1585,1680,dated 1661,1661,1661,Hanging scroll; ink on paper,45 5/8 x 20 in. (116 x 50.8 cm),"Gift of Marie-Hélène and Guy A. Weill, 2000",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/59025,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -64.268.3,false,true,36091,Asian Art,Album leaf,清 施霖 山莊圖 冊頁|Mountain Retreat,China,Qing dynasty (1644–1911),,,,Artist,,Shi Lin,"Chinese, active ca. 1630–60",,Shi Lin,Chinese,1610,1670,datable to 1654–55,1654,1655,Leaf from a collective album of many leaves; ink and color on paper,9 3/4 x 12 3/4 in. (24.8 x 32.4 cm),"Seymour Fund, 1964",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/36091,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1993.202,false,true,44720,Asian Art,Hanging scroll,清 倣張風 石橋圖 軸|The Stone Bridge,China,Qing dynasty (1644–1911),,,,Artist,,Zhang Feng,"Chinese, active ca. 1628–1662",,Zhang Feng,Chinese,1618,1662,dated 1661,1661,1661,Hanging scroll; ink on paper,Image: 60 3/4 x 18 5/16 in. (154.3 x 46.5 cm) Overall with mounting: 86 3/4 x 25 3/4 in. (220.3 x 65.4 cm) Overall with knobs: 86 3/4 x 29 1/2 in. (220.3 x 74.9 cm),"Purchase, Friends of Asian Art Gifts, The Dillon Fund Gift and Anonymous Gift, 1993",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/44720,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2001.746.1,false,true,48946,Asian Art,Hanging scroll,清 倣張風 覓菊圖 軸|Plucking Chrysanthemums,China,Qing dynasty (1644–1911),,,,Artist,After,Zhang Feng,"Chinese, active ca. 1628–1662",,Zhang Feng,Chinese,1618,1662,dated 1658,1658,1658,Hanging scroll; ink on paper,Image: 32 1/2 x 12 in. (82.6 x 30.5 cm) Overall with mounting: 70 1/2 x 17 3/8 in. (179.1 x 44.1 cm) Overall with knobs: 70 1/2 x 21 in. (179.1 x 53.3 cm),"The C. C. Wang Family Collection, Gift of C. C. Wang, 2001",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/48946,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -64.268.2,false,true,36090,Asian Art,Album leaf,清 葉欣 白鶴嶺圖 冊頁|White Crane Mountain,China,Qing dynasty (1644–1911),,,,Artist,,Ye Xin,"Chinese, active ca. 1640–1673",,Ye Xin,Chinese,1640,1640,datable to 1654–55,1654,1655,Leaf from a collective album of many leaves; ink and color on paper,9 3/4 x 13 in. (24.8 x 33 cm),"Seymour Fund, 1964",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/36090,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1987.223a–d,false,true,44521,Asian Art,Album,清 葉欣 山水圖 冊 絹本|Landscapes,China,Qing dynasty (1644–1911),,,,Artist,,Ye Xin,"Chinese, active ca. 1640–1673",,Ye Xin,Chinese,1640,1640,dated 1652,1652,1652,Album of four leaves; ink and color on silk,4 5/8 x 5 1/2in. (11.7 x 14cm),"Purchase, Mrs. C. Y. Chen Gift, 1987",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/44521,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -13.100.91,false,true,36001,Asian Art,Folding fan mounted as an album leaf,,China,Qing dynasty (1644–1911),,,,Artist,,Wen Zhi,"Chinese, active mid-17th century",,Wen Zhi,Chinese,1634,1699,dated 1670,1670,1670,Folding fan mounted as an album leaf; ink and color on gold paper,6 1/2 x 19 3/4 in. (16.5 x 50.2 cm),"John Stewart Kennedy Fund, 1913",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/36001,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.50,false,true,73646,Asian Art,Album,清 高岑 擬古山水圖 冊 絹本|Landscapes in the styles of old masters,China,Qing dynasty (1644–1911),,,,Artist,,Gao Cen,"Chinese, active 1643–after 1682",,Gao Cen,Chinese,1643,1682,dated 1667,1667,1667,Album of ten leaves; ink and color on silk,Each leaf: 8 1/8 x 8 3/8 in. (20.5 x 21.3 cm),"Purchase, C. C. Wang Gift, 2007",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/73646,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2003.246,false,true,65640,Asian Art,Hanging scroll,清 袁耀 雪棧行旅圖 軸|Hostelry and Travelers in Snowy Mountains,China,Qing dynasty (1644–1911),,,,Artist,,Yuan Yao,"Chinese, active 1730–after 1778",,Yuan Yao,Chinese,1730,1778,dated 1745,1745,1745,Hanging scroll; ink and color on silk,Image: 67 1/4 x 48 3/4 in. (170.8 x 123.8 cm) Overall with mounting: 121 3/4 x 50 1/2 in. (309.2 x 128.3 cm) Overall with knobs: 121 3/4 x 54 1/2 in. (309.2 x 138.4 cm),"Gift of Mr. and Mrs. C. C. Wang and Family, in memory of Douglas Dillon, 2003",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/65640,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"13.220.127a, b",false,true,49250,Asian Art,Handscrolls,清 弘曆(乾隆皇帝) 鹿角雙幅 卷|Two Paintings of Deer Antlers,China,Qing dynasty (1644–1911),,,,Artist,,Qianlong Emperor,"Chinese, (1711–1799; r. 1736–95)",,Qianlong Emperor,Chinese,1711,1799,dated 1762 and 1767,1762,1767,Two handscrolls; ink and color on paper,a: 9 3/4 × 81 1/4 in. (24.8 × 206.4 cm) b: 9 7/8 × 81 1/4 in. (25.1 × 206.4 cm),"John Stewart Kennedy Fund, 1913",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49250,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1984.16a–c,false,true,49251,Asian Art,Handscroll,"清 徐揚等 乾隆南巡圖, 第四卷﹕黃淮交流|The Qianlong Emperor's Southern Inspection Tour, Scroll Four: The Confluence of the Huai and Yellow Rivers (Qianlong nanxun, juan si: Huang Huai jiaoliu)",China,Qing dynasty (1644–1911),,,,Artist,,Xu Yang,"Chinese, active ca. 1750–after 1776",and assistants,XU YANG,Chinese,1750,1776,dated 1770,1770,1770,"Handscroll; ink and color on silk, lacquer box",27 1/8 x 431 1/4 in. (68.8 x 1096.17 cm),"Purchase, The Dillon Fund Gift, 1984",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49251,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1988.350a–d,false,true,41493,Asian Art,Handscroll,"清 徐揚 等 乾隆南巡圖 (第六卷﹕大運河至蘇州) 卷|The Qianlong Emperor's Southern Inspection Tour, Scroll Six: Entering Suzhou along the Grand Canal",China,Qing dynasty (1644–1911),,,,Artist,,Xu Yang,"Chinese, active ca. 1750–after 1776",and assistants,XU YANG,Chinese,1750,1776,dated 1770,1770,1770,Handscroll; ink and color on silk,Image: 27 1/8 in. x 784 1/2 in. (68.8 x 1994 cm),"Purchase, The Dillon Fund Gift, 1988",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/41493,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1981.122,false,true,36133,Asian Art,Hanging scroll,清 李寅 高閣觀瀑圖 軸|View From a Mountain Pavilion,China,Qing dynasty (1644–1911),,,,Artist,,Li Yin,"Chinese, active second half of the 17th–early 18th century",,Li Yin,Chinese,1650,1825,dated 1700,1700,1700,Hanging scroll; ink and color on silk,Image: 87 3/4 x 44 3/4 in. (222.9 x 113.7 cm) Overall with mounting: 123 1/2 x 47 3/4 in. (313.7 x 121.3 cm) Overall with knobs: 123 1/2 x 52 in. (313.7 x 132.1 cm),"Anonymous Gift, in memory of Maitland F. Griggs, 1981",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/36133,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1985.227.2,false,true,40454,Asian Art,Handscroll,元 張羽材 霖雨圖 卷|Beneficent Rain,China,Yuan dynasty (1271–1368),,,,Artist,,Zhang Yucai,"Chinese, died 1316",,Zhang Yucai,Chinese,,1316,late 13th–early 14th century,1295,1316,Handscroll; ink on silk,Image: 10 9/16 x 107 in. (26.8 x 271.8 cm) Overall with mounting: 11 in. x 24 ft. 11 13/16 in. (27.9 x 753.9 cm),"Gift of Douglas Dillon, 1985",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/40454,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1995.186,false,true,36459,Asian Art,Hanging scroll,元 邊魯 孔雀芙蓉圖 軸|Peacock and Hollyhocks,China,Yuan dynasty (1271–1368),,,,Artist,,Bian Lu,"Chinese, died 1356",,Bian Lu,Chinese,,1356,mid-14th century,1334,1366,Hanging scroll; ink and color on silk,Image: 66 7/8 x 40 1/4 in. (169.9 x 102.2 cm) Overall with mounting: 103 x 41 1/4 in. (261.6 x 104.8 cm) Overall with knobs: 103 x 45 7/8 in. (261.6 x 116.5 cm),"Purchase, The Dillon Fund and The B. Y. Lam Foundation Gifts, 1995",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/36459,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"1973.120.7a, b",false,true,40456,Asian Art,Hanging scrolls,元 李衎 竹石圖 對軸|Bamboo and rocks,China,Yuan dynasty (1271–1368),,,,Artist,,Li Kan,"Chinese, 1245–1320",,LI KAN,Chinese,1245,1320,dated 1318,1318,1318,Pair of hanging scrolls; ink and color on silk,Image (each): 74 3/4 x 21 3/4 in. (189.9 x 55.2 cm) Overall with mounting (each): 106 3/16 x 22 3/8 in. (269.7 x 56.8 cm) Overall with knobs (each): 106 3/16 x 23 in. (269.7 x 58.4 cm),"Ex coll.: C. C. Wang Family, Gift of The Dillon Fund, 1973",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/40456,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1973.120.5,false,true,40508,Asian Art,Handscroll,"元 趙孟頫 雙松平遠圖 卷|Twin Pines, Level Distance",China,Yuan dynasty (1271–1368),,,,Artist,,Zhao Mengfu,"Chinese, 1254–1322",,Zhao Mengfu,Chinese,1254,1322,ca. 1310,1310,1310,Handscroll; ink on paper,Image: 10 9/16 x 42 5/16 in. (26.8 x 107.5 cm) Overall with mounting: 10 15/16 x 25 ft. 7 11/16 in. (27.8 x 781.5 cm),"Ex coll.: C. C. Wang Family, Gift of The Dillon Fund, 1973",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/40508,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1973.121.15a–p,false,true,40511,Asian Art,Album,元 佚名 倣趙孟頫 九歌圖 冊|Nine Songs,China,Yuan dynasty (1271–1368),,,,Artist,After,Zhao Mengfu,"Chinese, 1254–1322",,Zhao Mengfu,Chinese,1254,1322,14th century (?),1300,1368,Album of eleven paintings; ink on paper,10 3/8 x 6 1/4 in. (26.4 x 15.9 cm),"Ex coll.: C. C. Wang Family, Fletcher Fund, 1973",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/40511,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1985.120.1,false,true,41462,Asian Art,Hanging scroll,元 吳鎮 老松圖 軸|Crooked Pine,China,Yuan dynasty (1271–1368),,,,Artist,,Wu Zhen,"Chinese, 1280–1354",,Wu Zhen,Chinese,1280,1354,dated 1335,1335,1335,Hanging scroll; ink on silk,Image: 65 3/8 x 32 1/2 in. (166.1 x 82.6 cm) Overall with mounting: 100 1/2 x 37 1/8 in. (255.3 x 94.3 cm) Overall with knobs: 100 1/2 x 40 1/2 in. (255.3 x 102.9 cm),"Purchase, The Dillon Fund Gift, 1985",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/41462,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2012.526.1,false,true,39547,Asian Art,Hanging scroll,"元 吳鎮 高節凌雲圖 軸|Bamboo, old tree, and rock",China,Yuan dynasty (1271–1368),,,,Artist,,Wu Zhen,"Chinese, 1280–1354",,Wu Zhen,Chinese,1280,1354,dated 1338,1338,1338,Hanging scroll; ink on silk,Image: 65 5/8 x 38 1/2 in. (166.7 x 97.8 cm) Overall with mounting: 117 1/2 x 39 1/2 in. (298.5 x 100.3 cm) Overall with knobs: 117 1/2 x 43 7/8 in. (298.5 x 111.4 cm),"Ex coll.: C. C. Wang Family, Gift of Oscar L. Tang Family, 2012",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/39547,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2006.571,false,true,39546,Asian Art,Hanging scroll,元 柯九思 臨文同墨竹圖 軸|Bamboo after Wen Tong,China,Yuan dynasty (1271–1368),,,,Artist,,Ke Jiusi,"Chinese, 1290–1343",,Ke Jiusi,Chinese,1290,1343,dated 1343,1343,1343,Hanging scroll; ink on silk,Image: 42 3/8 x 18 3/4 in. (107.6 x 47.6 cm) Overall with mounting: 98 1/2 x 26 5/8 in. (250.2 x 67.6 cm) Overall with knobs: 98 1/2 x 30 in. (250.2 x 76.2 cm),"Ex coll.: C. C. Wang Family, Gift of Oscar L. Tang Family, 2006",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/39546,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1973.120.8,false,true,45636,Asian Art,Hanging scroll,元 倪瓚 虞山林壑圖 軸|Woods and Valleys of Mount Yu,China,Yuan dynasty (1271–1368),,,,Artist,,Ni Zan,"Chinese, 1306–1374",,NI ZAN,Chinese,1306,1374,dated 1372,1372,1372,Hanging scroll; ink on paper,Image: 37 1/4 x 14 1/8 in. (94.6 x 35.9 cm) Overall with mounting: 82 x 20 5/8 in. (208.3 x 52.4 cm) Overall with knobs: 82 x 24 5/8 in. (208.3 x 62.5 cm),"Ex coll.: C. C. Wang Family, Gift of The Dillon Fund, 1973",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45636,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.38,false,true,45635,Asian Art,Hanging scroll,元 倪瓚 秋林野興圖 軸|Enjoying the Wilderness in an Autumn Grove,China,Yuan dynasty (1271–1368),,,,Artist,,Ni Zan,"Chinese, 1306–1374",,NI ZAN,Chinese,1306,1374,dated 1339,1339,1339,Hanging scroll; ink on paper,Image: 38 5/8 x 27 1/8 in. (98.1 x 68.9 cm) Overall with mounting: 106 7/8 x 35 7/8 in. (271.5 x 91.1 cm) Overall with knobs: 106 7/8 x 40 in. (271.5 x 101.6 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45635,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.39,false,true,41154,Asian Art,Hanging scroll,元 倪瓚 江渚風林圖 軸|Wind among the Trees on the Riverbank,China,Yuan dynasty (1271–1368),,,,Artist,,Ni Zan,"Chinese, 1306–1374",,NI ZAN,Chinese,1306,1374,dated 1363,1363,1363,Hanging scroll; ink on paper,Image: 23 1/4 x 12 1/4 in. (59.1 x 31.1 cm) Overall with mounting: 102 1/4 x 22 1/4 in. (259.7 x 56.5 cm) Overall with knobs: 102 1/4 x 27 1/2 in. (259.7 x 69.9 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/41154,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1980.426.3,false,true,41185,Asian Art,Hanging scroll,元 張羽 松軒春靄圖 軸|Spring Clouds at the Pine Studio,China,Yuan dynasty (1271–1368),,,,Artist,,Zhang Yu,"Chinese, 1333–1385",,Zhang Yu,Chinese,1333,1333,dated 1366,1366,1366,Hanging scroll; ink and color on paper,Image: 36 1/4 x 12 1/2 in. (92.1 x 31.8 cm) Overall with mounting: 79 1/2 x 18 1/2 in. (201.9 x 47 cm) Overall with knobs: 79 1/2 x 20 7/8 in. (201.9 x 53 cm),"Gift of Douglas Dillon, 1980",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/41185,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1982.1.2,false,true,40515,Asian Art,Hanging scroll,元 李堯夫 蘆葉達摩圖 軸|Bodhidharma crossing the Yangzi River on a reed,China,Yuan dynasty (1271–1368),,,,Artist,,Li Yaofu,"Chinese, active ca. 1300",,LI YAOFU,Chinese,1300,1300,before 1317,1271,1316,Hanging scroll; ink on paper,Image: 33 3/4 × 13 5/16 in. (85.7 × 33.8 cm) Overall with mounting: 61 1/4 × 14 in. (155.6 × 35.6 cm) Overall with knobs: 61 1/4 × 15 7/8 in. (155.6 × 40.3 cm),"Edward Elliott Family Collection, Purchase, The Dillon Fund Gift, 1982",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/40515,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1973.121.5,false,true,41195,Asian Art,Hanging scroll,元 唐棣 松溪歸漁圖 軸|Returning Fishermen,China,Yuan dynasty (1271–1368),,,,Artist,,Tang Di,"Chinese, ca. 1287–1355",,TANG DI,Chinese,1277,1365,dated 1342,1342,1342,Hanging scroll; ink and color on silk,Image: 52 7/8 x 33 7/8 in. (134.3 x 86 cm) Overall with mounting: 98 3/4 x 38 1/2 in. (250.8 x 97.8 cm) Overall with knobs: 98 3/4 x 42 3/4 in. (250.8 x 108.6 cm),"Ex coll.: C. C. Wang Family, Purchase, Bequest of Joseph H. Durkee, by exchange, 1973",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/41195,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.36,false,true,40134,Asian Art,Handscroll,元 唐棣 滕王閣圖 卷|The Pavilion of Prince Teng,China,Yuan dynasty (1271–1368),,,,Artist,,Tang Di,"Chinese, ca. 1287–1355",,TANG DI,Chinese,1277,1365,dated 1352,1352,1352,Handscroll; ink on paper,Image: 10 13/16 x 33 1/4 in. (27.5 x 84.5 cm) Overall with mounting: 11 1/8 x 310 11/16 in. (28.3 x 789.1 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/40134,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1985.214.147,false,true,41194,Asian Art,Hanging scroll,元 唐棣 摩詰詩意圖 軸|Landscape after a poem by Wang Wei,China,Yuan dynasty (1271–1368),,,,Artist,,Tang Di,"Chinese, ca. 1287–1355",,TANG DI,Chinese,1277,1365,dated 1323,1323,1323,Hanging scroll; ink and color on silk,Image: 50 3/4 x 27 1/16 in. (128.9 x 68.7 cm) Overall with mounting: 8 ft. 8 1/8 in. x 33 3/4 in. (264.5 x 85.7 cm) Overall with knobs: 8 ft. 8 1/8 in. x 37 in. (264.5 x 94 cm),"Gift of Ernest Erickson Foundation, 1985",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/41194,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2012.526.2,false,true,39550,Asian Art,Hanging scroll,元 王蒙 素庵圖 軸|The Simple Retreat,China,Yuan dynasty (1271–1368),,,,Artist,,Wang Meng,"Chinese, ca. 1308–1385",,Wang Meng,Chinese,1308,1385,ca. 1370,1360,1380,Hanging scroll; ink and color on paper,Image: 53 3/4 x 17 5/8 in. (136.5 x 44.8 cm) Overall with mounting: 101 1/4 x 24 3/4 in. (257.2 x 62.9 cm) Overall with knobs: 101 1/4 x 28 11/16 in. (257.2 x 72.9 cm),"Ex coll.: C. C. Wang Family, Gift of Oscar L. Tang Family, 2012",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/39550,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.32,false,true,41451,Asian Art,Hanging scroll,,China,Yuan dynasty (1271–1368),,,,Artist,After,Zhao Yong,"Chinese, 1289–after 1360",,Zhao Yong,Chinese,1289,1360,dated 1349,1349,1349,Hanging scroll; ink on paper,Image: 45 x 14 7/8 in. (114.3 x 37.8 cm) Overall with mounting: 83 1/2 x 20 3/8 in. (212.1 x 51.8 cm) Overall with knobs: 83 1/2 x 23 in. (212.1 x 58.4 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/41451,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1973.121.6,false,true,41193,Asian Art,Hanging scroll,元 羅稚川 古木寒鴉圖 軸|Crows in Old Trees,China,Yuan dynasty (1271–1368),,,,Artist,,Luo Zhichuan,"Chinese, active ca. 1300–30",,Luo Zhichuan,Chinese,1290,1340,early 14th century,1300,1330,Hanging scroll; ink and color on silk,Image: 52 x 31 5/8 in. (132.1 x 80.3 cm) Overall with mounting: 9 ft. 6 1/4 in. x 38 3/4 in. (290.2 x 98.4 cm) Overall with knobs: 9 ft. 6 1/4 in. x 42 1/2 in. (290.2 x 108 cm),"Ex coll.: C. C. Wang Family, Purchase, Gift of J. Pierpont Morgan, by exchange, 1973",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/41193,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.345.1,false,true,36430,Asian Art,Hanging scroll,元 佚名 龍虎圖 軸|Tiger,China,Yuan dynasty (1271–1368),,,,Artist,In the style of,Muqi,"Chinese, ca. 1210–after 1269",,Muqi,Chinese,1210,1269,late 13th–14th century,1271,1368,Hanging scroll; ink on silk,31 11/16 x 15 7/8 in. (80.5 x 40.3 cm),"Gift of Mr. and Mrs. Kwan S. Wong, 1986",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/36430,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.345.2,false,true,36431,Asian Art,Hanging scroll,元 龍虎圖 軸|Dragon,China,Yuan dynasty (1271–1368),,,,Artist,In the style of,Muqi,"Chinese, ca. 1210–after 1269",,Muqi,Chinese,1210,1269,late 13th–14th century,1271,1368,Hanging scroll; ink on silk,31 11/16 x 15 7/8 in. (80.5 x 40.3 cm),"Gift of Mr. and Mrs. Kwan S. Wong, 1986",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/36431,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1991.438.1,false,true,44701,Asian Art,Hanging scroll,元 鄧宇 竹石圖 軸|Bamboo and rock,China,Yuan dynasty (1271–1368),,,,Artist,,Deng Yu,"Chinese, ca. 1300–after 1378",,Teng Yu,Chinese,1300,1378,ca. 1360–67,1350,1377,Hanging scroll; ink on paper,Image: 53 3/16 x 16 5/8 in. (135.1 x 42.2 cm) Overall with mounting: 104 1/4 x 23 7/8 in. (264.8 x 60.6 cm) Overall with knobs: 104 1/4 x 27 1/8 in. (264.8 x 68.9 cm),"From the P. Y. and Kinmay W. Tang Family Collection, Gift of Oscar L. Tang, 1991",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/44701,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -66.174a,false,true,40514,Asian Art,Handscroll,元 佚名 臨王振鵬 金明池圖 卷|Dragon Boat Regatta on Jinming Lake,China,Yuan dynasty (1271–1368),,,,Artist,After,Wang Zhenpeng,"Chinese, active ca. 1275–1330",,WANG ZHENPENG,Chinese,1265,1340,14th century (?),1300,1368,Handscroll; ink on silk,Image: 13 1/2 x 17 ft 6 in. (34.3 cm x 53.8 m),"Purchase, Bequest of Dorothy Graham Bennett, 1966",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/40514,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1980.276,false,true,40513,Asian Art,Handscroll,元 王振鵬 維摩不二圖 卷|Vimalakirti and the Doctrine of Nonduality,China,Yuan dynasty (1271–1368),,,,Artist,,Wang Zhenpeng,"Chinese, active ca. 1275–1330",,WANG ZHENPENG,Chinese,1265,1340,dated 1308,1308,1308,Handscroll; ink on silk,Image: 15 7/16 x 85 15/16 in. (39.2 x 218.3 cm) Overall with mounting: 15 13/16 in. x 29 ft. 4 5/8 in. (40.2 x 895.7 cm),"Purchase, The Dillon Fund Gift, 1980",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/40513,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1973.121.13,false,true,42260,Asian Art,Fan mounted as an album leaf,"元 盛懋 秋林漁隱圖 團扇|Recluse Fisherman, Autumn Trees",China,Yuan dynasty (1271–1368),,,,Artist,,Sheng Mou,"Chinese, active ca. 1310–1360",,Sheng Mou,Chinese,1310,1360,dated 1349,1349,1349,Fan mounted as an album leaf; ink and color on silk,Image: 10 1/2 × 13 1/4 in. (26.7 × 33.7 cm),"Ex coll.: C. C. Wang Family, Purchase, Florance Waterbury Bequest and Gift of Mr. and Mrs. Nathan Cummings, by exchange, 1973",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/42260,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.35,false,true,41146,Asian Art,Hanging scroll,元 盛懋 秋林漁隱圖 軸|Recluse Fishing by Autumn Trees,China,Yuan dynasty (1271–1368),,,,Artist,,Sheng Mou,"Chinese, active ca. 1310–1360",,Sheng Mou,Chinese,1310,1360,dated 1350,1350,1350,Hanging scroll; ink on paper,Image: 40 3/8 x 13 1/8 in. (102.6 x 33.3 cm) Overall with mounting: 90 1/2 x 19 3/4 in. (229.9 x 50.2 cm) Overall with knobs: 90 1/2 x 23 in. (229.9 x 58.4 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/41146,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1977.79,false,true,40309,Asian Art,Handscroll,元 錢選 梨花圖 卷|Pear Blossoms,China,Yuan dynasty (1271–1368),,,,Artist,,Qian Xuan,"Chinese, ca. 1235–before 1307",,QIAN XUAN,Chinese,1235,1307,ca. 1280,1270,1290,Handscroll; ink and color on paper,Image: 12 5/16 x 37 7/8 in. (31.3 x 96.2 cm) Overall with mounting: 12 5/8 x 34 ft. 9 1/8 in. (32.1 x 1059.5 cm),"Purchase, The Dillon Fund Gift, 1977",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/40309,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1973.120.6,true,true,40081,Asian Art,Handscroll,元 錢選 王羲之觀鵝圖 卷|Wang Xizhi watching geese,China,Yuan dynasty (1271–1368),,,,Artist,,Qian Xuan,"Chinese, ca. 1235–before 1307",,QIAN XUAN,Chinese,1235,1307,ca. 1295,1285,1305,"Handscroll; ink, color, and gold on paper",Image: 9 1/8 x 36 1/2 in. (23.2 x 92.7 cm) Overall with mounting: 11 x 418 13/16 in. (27.9 x 1063.8 cm),"Ex coll.: C. C. Wang Family, Gift of The Dillon Fund, 1973",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/40081,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1991.438.3,false,true,41476,Asian Art,Album leaf,元 夏永 黃樓圖 冊頁|The Yellow Pavilion,China,Yuan dynasty (1271–1368),,,,Artist,,Xia Yong,"Chinese, active mid-14th century",,XIA YONG,Chinese,1336,1370,ca. 1350,1340,1360,Album leaf; ink on silk,Image: 8 1/8 x 10 1/2 in. (20.6 x 26.7 cm),"Ex coll.: C. C. Wang Family, From the P. Y. and Kinmay W. Tang Family Collection, Gift of Oscar L. Tang, 1991",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/41476,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -47.18.10,false,true,40393,Asian Art,Handscroll,元 周東卿 魚樂圖 卷|The Pleasures of Fishes,China,Yuan dynasty (1271–1368),,,,Artist,,Zhou Dongqing,"Chinese, active late 13th century",,ZHOU DONGQING,Chinese,0013,0013,dated 1291,1291,1291,Handscroll; ink and color on paper,Image: 12 1/8 x 19 ft 4 in. (30.8 cm x 593.7 cm) Overall with mounting: 12 5/8 x 441 3/4 in. (32.1 x 1122 cm),"From the Collection of A. W. Bahr, Purchase, Fletcher Fund, 1947",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/40393,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1973.121.14,false,true,42261,Asian Art,Fan mounted as an album leaf,元 盛著 秋江垂釣圖 團扇|Angling in the Autumn River,China,Yuan dynasty (1271–1368),,,,Artist,,Sheng Zhu,"Chinese, active late 14th century",,Sheng Zhu,Chinese,1300,1400,ca. 1370,1360,1380,Fan mounted as an album leaf; ink and color on silk,Image: 10 3/16 × 10 13/16 in. (25.9 × 27.5 cm),"Ex coll.: C. C. Wang Family, Purchase, Bequest of Martha T. Fiske Collord, in memory of Josiah M. Fiske, Bequest of Mrs. Beekman Hoppin, and Gift of Herman Cooper, by exchange, 1973",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/42261,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1973.121.8,false,true,45647,Asian Art,Hanging scroll,元 趙原 (元) 晴川送客圖 軸|Farewell by a Stream on a Clear Day,China,Yuan dynasty (1271–1368),,,,Artist,,Zhao Yuan,"Chinese, active ca. 1350–75",,Zhao Yuan,Chinese,1350,1350,second half of the 14th century,1350,1399,Hanging scroll; ink on paper,Image: 37 1/2 x 13 7/8 in. (95.3 x 35.2 cm) Overall with mounting: 77 1/4 x 20 1/2 in. (196.2 x 52.1 cm) Overall with knobs: 77 1/4 x 23 3/8 in. (196.2 x 59.4 cm),"Ex coll.: C. C. Wang Family, Purchase, Gift of J. Pierpont Morgan, by exchange, 1973",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45647,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -13.100.22,false,true,35970,Asian Art,Hanging scroll,明 丁雲鵬 山水圖 軸|The Lute-song: Farewell at Xunyang,China,late Ming dynasty (1368–1644),,,,Artist,,Ding Yunpeng,"Chinese, 1547–after 1621",,Ding Yunpeng,Chinese,1547,1621,dated 1585,1585,1585,Hanging scroll; ink and color on paper,Image: 55 5/8 x 18 1/8 in. (141.3 x 46 cm),"John Stewart Kennedy Fund, 1913",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/35970,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1991.438.2,false,true,44702,Asian Art,Fan mounted as an album leaf,元 王蒙 蕭林寂亭圖 團扇|Sparse Trees and Pavilion,China,late Yuan dynasty (1271–1368),,,,Artist,,Wang Meng,"Chinese, ca. 1308–1385",,Wang Meng,Chinese,1308,1385,ca. 1361,1351,1371,Fan mounted as an album leaf; ink on silk,Image: 9 7/8 x 11 1/8 in. (25.1 x 28.3 cm),"Ex coll.: C. C. Wang Family, From the P. Y. and Kinmay W. Tang Family Collection, Gift of Oscar L. Tang, 1991",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/44702,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1996.479a–c,false,true,39895,Asian Art,Handscrolls,北宋 李公麟 孝經圖 卷|The Classic of Filial Piety,China,Northern Song dynasty (960–1127),,,,Artist,,Li Gonglin,"Chinese, ca. 1041–1106",,Li Gonglin,Chinese,1041,1106,ca. 1085,1075,1095,Handscroll; ink and color on silk,"Overall (a, painting): 8 5/8 x 187 1/4 in. (21.9 x 475.6 cm) Overall (b, colophons): 10 3/8 x 208 5/8 in. (26.4 x 529.9 cm) Overall (c, modern copy preserving seventeenth century silk restorations): 9 1/8 x 196 in. (23.2 x 497.8 cm)","Ex coll.: C. C. Wang Family, From the P. Y. and Kinmay W. Tang Family Collection, Gift of Oscar L. Tang Family, 1996",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/39895,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1981.276,true,true,39668,Asian Art,Handscroll,"北宋 郭熙 樹色平遠圖 卷 |Old Trees, Level Distance",China,Northern Song dynasty (960–1127),,,,Artist,,Guo Xi,"Chinese, ca. 1000–ca. 1090",,Guo Xi,Chinese,1000,1090,ca. 1080,1070,1090,Handscroll; ink and color on silk,Image: 14 in. × 41 1/8 in. (35.6 × 104.4 cm) Overall with mounting: 14 3/4 in. × 28 ft. 1/8 in. (37.5 × 853.8 cm),"Gift of John M. Crawford Jr., in honor of Douglas Dillon, 1981",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/39668,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1981.278,true,true,39936,Asian Art,Handscroll,北宋 徽宗 竹禽圖 卷|Finches and bamboo,China,Northern Song dynasty (960–1127),,,,Artist,,Emperor Huizong,"Chinese, 1082–1135; r. 1100–25",,Huizong Emperor,Chinese,1082,1082,early 12th century,1100,1127,Handscroll; ink and color on silk,Image: 13 1/4 × 21 13/16 in. (33.7 × 55.4 cm) Overall with mounting: 13 3/4 in. × 27 ft. 6 5/16 in. (34.9 × 839 cm),"John M. Crawford Jr. Collection, Purchase, Douglas Dillon Gift, 1981",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/39936,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1973.120.1,true,true,39915,Asian Art,Handscroll,北宋 傳屈鼎 夏山圖 卷|Summer Mountains,China,Northern Song dynasty (960–1127),,,,Artist,Attributed to,Qu Ding,"Chinese, active ca. 1023–ca. 1056",,QU DING,Chinese,0950,1150,ca. 1050,1040,1060,Handscroll; ink and color on silk,Image: 17 7/8 × 45 3/8 in. (45.4 × 115.3 cm) Overall with mounting: 18 1/4 in. × 23 ft. 2 in. (46.4 × 706.1 cm),"Ex coll.: C. C. Wang Family, Gift of The Dillon Fund, 1973",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/39915,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1973.121.1,false,true,40007,Asian Art,Handscroll,南宋 米友仁 雲山圖 卷|Cloudy Mountains,China,Southern Song dynasty (1127–1279),,,,Artist,,Mi Youren,"Chinese, 1074–1151",,MI YOUREN,Chinese,1074,1151,before 1200,1127,1199,Handscroll; ink on paper,Image: 10 7/8 × 22 7/16 in. (27.6 × 57 cm) Overall with mounting: 11 3/16 in. × 24 ft. 6 3/16 in. (28.4 × 747.2 cm),"Ex coll.: C. C. Wang Family, Purchase, Gift of J. Pierpont Morgan, by exchange, 1973",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/40007,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1973.120.4,false,true,40284,Asian Art,Handscroll,南宋 趙孟堅 水仙圖 卷|Narcissus,China,Southern Song dynasty (1127–1279),,,,Artist,,Zhao Mengjian,"Chinese, 1199–before 1267",,Zhao Mengjian,Chinese,1199,1267,mid-13th century,1234,1266,Handscroll; ink on paper,Image: 13 1/16 in. × 12 ft. 3 1/4 in. (33.2 × 374 cm) Overall with mounting: 13 7/16 in. × 32 ft. 7 3/16 in. (34.1 × 993.6 cm),"Ex coll.: C. C. Wang Family, Gift of The Dillon Fund, 1973",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/40284,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1973.121.3,false,true,40054,Asian Art,Handscroll,南宋 馬和之 詩經豳風圖 卷|Odes of the State of Bin,China,Southern Song dynasty (1127–1279),,,,Artist,,Ma Hezhi,"Chinese, ca. 1130–ca. 1170",", and Assistants",MA HEZHI,Chinese,1130,1170,mid-12th century,1134,1166,"Handscroll; ink, color, gold and silver on silk",Image: 10 15/16 in. × 21 ft. 9 1/4 in. (27.8 × 663.6 cm) Overall with mounting: 13 13/16 in. × 45 ft. 10 1/2 in. (35.1 × 1398.3 cm),"Ex coll.: C. C. Wang Family, Purchase, Gift of J. Pierpont Morgan, by exchange, 1973",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/40054,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1973.120.2,false,true,40051,Asian Art,Handscroll,南宋 傳李唐 晉文公復國圖 卷|Duke Wen of Jin Recovering His State,China,Southern Song dynasty (1127–1279),,,,Artist,Attributed to,Li Tang,"Chinese, ca. 1070s–ca. 1150s",,LI TANG,Chinese,1070,1159,mid-12th century,1134,1166,Handscroll; ink and color on silk,Image: 11 9/16 in. × 27 ft. 2 in. (29.4 × 828 cm) Overall with mounting: 11 7/8 in. × 40 ft. 9 1/16 in. (30.2 × 1242.2 cm),"Ex coll.: C. C. Wang Family, Gift of The Dillon Fund, 1973",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/40051,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -23.33.5,false,true,36037,Asian Art,Album leaf,南宋 佚名 倣馬遠 松陰玩月圖 冊頁|Viewing the Moon under a Pine Tree,China,Southern Song dynasty (1127–1279),,,,Artist,After,Ma Yuan,"Chinese, active ca. 1190–1225",,MA YUAN,Chinese,1190,1225,early 13th century,1200,1233,Album leaf; ink and color on silk,10 x 10 in. (25.4 x 25.4 cm),"Rogers Fund, 1923",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/36037,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1973.120.9,false,true,40086,Asian Art,Album leaf,南宋 馬遠 高士觀瀑圖 冊頁 絹本|Scholar viewing a waterfall,China,Southern Song dynasty (1127–1279),,,,Artist,,Ma Yuan,"Chinese, active ca. 1190–1225",,MA YUAN,Chinese,1190,1225,late 12th–early 13th century,1190,1225,Album leaf; ink and color on silk,Image: 9 7/8 x 10 1/4 in. (25.1 x 26 cm),"Ex coll.: C. C. Wang Family, Gift of The Dillon Fund, 1973",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/40086,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.493.2,false,true,44638,Asian Art,Folding fan mounted as an album leaf,南宋 馬遠 月下賞梅圖 團扇|Viewing plum blossoms by moonlight,China,Southern Song dynasty (1127–1279),,,,Artist,,Ma Yuan,"Chinese, active ca. 1190–1225",,MA YUAN,Chinese,1190,1225,early 13th century,1200,1233,Fan mounted as an album leaf; ink and color on silk,Image: 9 7/8 x 10 1/2 in. (25.1 x 26.7 cm),"Gift of John M. Crawford Jr., in honor of Alfreda Murck, 1986",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/44638,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -13.100.102,false,true,36005,Asian Art,Album leaf,"南宋 夏珪 山市晴嵐圖 冊頁|Mountain Market, Clearing Mist",China,Southern Song dynasty (1127–1279),,,,Artist,,Xia Gui,"Chinese, active ca. 1195–1230",,Xia Gui,Chinese,1195,1230,early 13th century,1200,1230,Album leaf; ink on silk,9 3/4 x 8 3/8 in. (24.8 x 21.3 cm),"John Stewart Kennedy Fund, 1913",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/36005,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1973.120.10,false,true,40133,Asian Art,Album leaf,南宋 馬麟 蘭花圖 冊頁 絹本|Orchids,China,Southern Song dynasty (1127–1279),,,,Artist,,Ma Lin,"Chinese, ca. 1180– after 1256",,MA LIN,Chinese,1180,1256,second quarter of the 13th century,1226,1250,Album leaf; ink and color on silk,Image: 10 7/16 x 8 7/8 in. (26.5 x 22.5 cm),"Ex coll.: C. C. Wang Family, Gift of The Dillon Fund, 1973",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/40133,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -13.100.110,false,true,39937,Asian Art,Album leaf,宋 傳趙克敻 藻魚圖 冊頁|Fish at play,China,Southern Song dynasty (1127–1279),,,,Artist,Attributed to,Zhao Kexiong,"Chinese, active early 12th century",,Zhao Kexiong,Chinese,0012,0012,12th–late 13th century,1127,1279,Album leaf; ink and color on silk,Image: 8 7/8 x 9 7/8 in. (22.5 x 25.1 cm),"John Stewart Kennedy Fund, 1913",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/39937,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.14,false,true,40090,Asian Art,Fan mounted as an album leaf,南宋 梁楷 澤畔行吟圖 團扇|Poet strolling by a marshy bank,China,Southern Song dynasty (1127–1279),,,,Artist,,Liang Kai,"Chinese, active early 13th century",,Liang Kai,Chinese,1200,1225,early 13th century,1200,1225,Fan mounted as an album leaf; ink on silk,Image: 9 x 9 9/16 in. (22.9 x 24.3 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/40090,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -13.220.98,false,true,36019,Asian Art,Handscroll,,China,Ming (1368–1644)–Qing (1644–1911) dynasty,,,,Artist,In the Style of,Wen Boren,"Chinese, 1502–ca.1575",,Wen Boren,Chinese,1502,1575,dated 1558,1558,1558,Handscroll; ink and color on paper,Image: 7 1/16 × 48 1/2 in. (17.9 × 123.2 cm) Overall with mounting: 9 9/16 in. × 10 ft. 11 1/2 in. (24.3 × 334 cm),"John Stewart Kennedy Fund, 1913",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/36019,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1977.171a–j,false,true,49017,Asian Art,Album,明/清 惲向 仿古山水圖 冊 紙本|Landscapes after old masters,China,Ming (1368–1644) or Qing (1644–1911) dynasty,,,,Artist,,Yun Xiang,"Chinese, 1586–1655",,Yun Xiang,Chinese,1586,1655,datable to 1638 or 1650,1638,1650,Album of ten leaves; ink and color on paper,10 1/4 x 6 in. (26 x 15.2 cm),"Purchase, Douglas Dillon Gift, 1977",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49017,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1981.285.16,false,true,45658,Asian Art,Handscroll,元/明 沈巽 竹石圖 卷|Bamboo grove,China,Yuan (1271–1368) or Ming (1368–1644) dynasty,,,,Artist,,Shen Xun,(active ca. 1370–1400),,Shen Xun,Chinese,1370,1400,late 14th century,1367,1399,Handscroll; ink and color on paper,Image: 9 13/16 x 25 in. (24.9 x 63.5 cm) Overall with mounting: 10 1/8 x 347 5/16 in. (25.7 x 882.2 cm),"Edward Elliott Family Collection, Purchase, The Dillon Fund Gift, 1981",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45658,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1981.285.15,false,true,45650,Asian Art,Handscroll,元 趙原 (元) 倣燕文貴范寬山水圖 卷|Landscape in the Style of Yan Wengui and Fan Kuan,China,Yuan (1271–1368) or Ming (1368–1644) dynasty,,,,Artist,,Zhao Yuan,"Chinese, active ca. 1350–75",,Zhao Yuan,Chinese,1350,1350,late 14th century,1367,1399,Handscroll; ink on paper,Image: 9 13/16 x 30 1/2 in. (24.9 x 77.5 cm) Overall with mounting: 10 1/8 x 347 5/16 in. (25.7 x 882.2 cm),"Edward Elliott Family Collection, Purchase, The Dillon Fund Gift, 1981",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45650,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2003.132,false,true,65385,Asian Art,Folding fan mounted as an album leaf,"清 徐揚 仙山樓閣圖 扇面|Palaces of the Immortals",China,"Qing dynasty (1644–1911), Qianlong period (1736–95)",,,,Artist,,Xu Yang,"Chinese, active ca. 1750–after 1776",,XU YANG,Chinese,1750,1776,dated 1753,1753,1753,"Folding fan mounted as an album leaf; ink, color, and gold on paper",Image: 6 1/4 x 18 1/2 in. (15.9 x 47 cm) Sheet: 15 × 24 1/2 in. (38.1 × 62.2 cm),"Purchase, The B. D. G. Leviton Foundation Gift, in honor of Marie-Hélène and Guy Weill, 2003",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/65385,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.364a–l,false,true,44873,Asian Art,Folding fans mounted as album leaves,明/清 陳洪綬 竹石蛺蝶圖 扇面|Landscapes and Flowers,China,late Ming (1368–1644)–early Qing (1644–1911) dynasty,,,,Artist,,Chen Hongshou,"Chinese, 1599–1652",,Chen Hongshou,Chinese,1599,1652,first half of the 17th century,1600,1649,Twelve folding fans mounted as album leaves; ink and color on gold paper,9 1/2 x 20 1/2 in. (24.1 x 52.1 cm),"Gift of Douglas Dillon, 1989",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/44873,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1973.121.7,false,true,45637,Asian Art,Hanging scroll,元 王蒙 丹崖翠壑圖 軸|Red Cliffs and Green Valleys,China,late Yuan (1271–1368)–early Ming (1368–1644) dynasty,,,,Artist,,Wang Meng,"Chinese, ca. 1308–1385",,Wang Meng,Chinese,1308,1385,ca. 1367,1357,1377,Hanging scroll; ink on paper,Image: 26 3/4 x 13 1/2 in. (67.9 x 34.3 cm) Overall with mounting: 91 1/4 x 21 3/8 in. (231.8 x 54.3 cm) Overall with knobs: 91 1/4 x 25 1/8 in. (231.8 x 63.8 cm),"Ex coll.: C. C. Wang Family, Purchase, Gift of Darius Ogden Mills and Gift of Mrs. Robert Young, by exchange, 1973",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45637,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1997.139,false,true,39488,Asian Art,Figure,,China,Ming dynasty (1368–1644),,,,Artist,,Chen Yanqing,active 15th century,,Chen Yanqing,Chinese,1400,1499,dated 1438,1438,1438,Gilt brass; lost-wax cast,H. 7 1/2 in. (19 cm); W. 4 3/4 in. (12 cm); D. 2 3/4 in. (7 cm),"Purchase, Friends of Asian Art Gifts, 1997",,,,,,,,,,,,Sculpture,,http://www.metmuseum.org/art/collection/search/39488,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -10.221.1,false,true,48837,Asian Art,Figure,,China,Tang dynasty (618–907),,,,Artist,,Jin Renrui,"Chinese, 1608–1661",,Jin Renrui,Chinese,1608,1661,,618,907,Whitish earthenware with brown glaze,H. 15 7/8 in. (40.3 cm),"Rogers Fund, 1910",,,,,,,,,,,,Tomb Pottery,,http://www.metmuseum.org/art/collection/search/48837,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.20,false,true,40185,Asian Art,Fan mounted as an album leaf,南宋 理宗 行書秋深雨過聯句 團扇|Couplet on an Autumn Sky,China,Song dynasty (960–1279),,,,Artist,,Emperor Lizong,"Chinese, 1205–64, r. 1224–64",,LIZONG EMPEROR,Chinese,1205,1264,,1205,1264,Fan mounted as an album leaf; ink on silk,Image: 9 5/16 x 7 in. (23.7 x 17.8 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/40185,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.21,false,true,40187,Asian Art,Fan mounted as an album leaf,南宋 理宗 趙昀 行書錄光宗趙惇題楊補之 《紅梅圖》 賜貴妃詩 團扇|Quatrain on a Spring Garden,China,Song dynasty (960–1279),,,,Artist,,Emperor Lizong,"Chinese, 1205–64, r. 1224–64",,LIZONG EMPEROR,Chinese,1205,1264,,1205,1264,Fan mounted as an album leaf; ink on silk,11 x 9 1/2 in. (27.9 x 24.1 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/40187,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.22,false,true,40188,Asian Art,Fan mounted as an album leaf,南宋 理宗 趙昀 行書 《長苦春來》 七絕詩 團扇|Quatrain on Late Spring,China,Song dynasty (960–1279),,,,Artist,,Emperor Lizong,"Chinese, 1205–64, r. 1224–64",,LIZONG EMPEROR,Chinese,1205,1264,,1205,1264,Fan mounted as an album leaf; ink on silk,Image: 9 5/8 × 9 1/4 in. (24.4 × 23.5 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/40188,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.11,false,true,40061,Asian Art,Fan mounted as an album leaf,南宋 傳光宗 行楷書高標貞色聯句 團扇|Couplet by Han Yu,China,Song dynasty (960–1279),,,,Artist,Attributed to,Emperor Guangzong,"Chinese, 1147–1200, r. 1190–94",,Emperor Guangzong,Chinese,1147,1200,,1147,1200,Fan mounted as an album leaf; ink on silk,8 3/8 x 8 1/4 in. (21.3 x 21 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/40061,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.110,false,true,49006,Asian Art,Album leaf,,China,Ming dynasty (1368–1644),,,,Artist,,Song Jue,(1576–1632),,Song Jue,Chinese,1576,1632,,1575,1632,Album leaf; ink on paper,11 1/2 x 18 in. (29.2 x 45.7 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/49006,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.111,false,true,49009,Asian Art,Album leaf,,China,Ming dynasty (1368–1644),,,,Artist,,Song Jue,(1576–1632),,Song Jue,Chinese,1576,1632,,1576,1632,Album leaf; ink on paper,8 13/16 x 9 5/8 in. (22.4 x 24.4 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/49009,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.94,false,true,48944,Asian Art,Folding fan mounted as an album leaf,,China,Ming dynasty (1368–1644),,,,Artist,,Xing Tong,"Chines, 1551–1612",,Xing Tong,Chinese,1551,1612,,1551,1612,Folding fan mounted as an album leaf; ink on gold paper,6 x 19 in. (15.2 x 48.3 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/48944,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.41,false,true,45660,Asian Art,Album leaf,明 沈度 致鏞翁書 冊頁|Letter to Liang Zhongren,China,Ming dynasty (1368–1644),,,,Artist,,Shen Du,"Chinese, 1357–1434",,Shen Du,Chinese,1357,1434,,1357,1434,Album leaf; ink on paper,Image: 10 1/4 x 13 1/4 in. (26 x 33.7 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/45660,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.49,false,true,45747,Asian Art,Folding fan mounted as an album leaf,,China,Ming dynasty (1368–1644),,,,Artist,,Wang Ao,"Chinese, 1450–1524",,Wang Ao,Chinese,1450,1524,,1450,1524,Folding fan mounted as an album leaf; ink on gold-patterned paper,6 3/4 x 19 3/4 in. (17.1 x 50.2 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/45747,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.50,false,true,45748,Asian Art,Album leaf,,China,Ming dynasty (1368–1644),,,,Artist,,Wang Ao,"Chinese, 1450–1524",,Wang Ao,Chinese,1450,1524,,1450,1524,Album leaf; ink on patterned paper,9 1/2 x 11 1/2 in. (24.1 x 29.2 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/45748,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.52,false,true,42683,Asian Art,Folding fan mounted as an album leaf,,China,Ming dynasty (1368–1644),,,,Artist,,Zhu Yunming,"Chinese, 1461–1527",,Zhu Yunming,Chinese,1461,1527,,1461,1527,Folding fan mounted as an album leaf; ink on gold paper,7 1/2 x 19 1/2 in. (19.1 x 49.5 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/42683,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.66,false,true,45795,Asian Art,Folding fan mounted as an album leaf,,China,Ming dynasty (1368–1644),,,,Artist,,Xu Lin,"Chinese, 1462–1548",,Xu Lin,Chinese,1462,1548,,1462,1548,Folding fan mounted as an album leaf; ink on gold-flecked paper,6 3/8 x 19 in. (16.2 x 48.3 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/45795,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.56,false,true,45763,Asian Art,Album leaf,明 唐寅 致若容書 冊頁|Letter to Xu Shangde,China,Ming dynasty (1368–1644),,,,Artist,,Tang Yin,"Chinese, 1470–1524",,Tang Yin,Chinese,1470,1524,,1470,1524,Album leaf; ink on paper,Image: 10 3/4 × 25 1/4 in. (27.3 × 64.1 cm) Image (with title strip): 10 3/4 × 25 7/8 in. (27.3 × 65.7 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/45763,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.57,false,true,45764,Asian Art,Album leaf,,China,Ming dynasty (1368–1644),,,,Artist,,Tang Yin,"Chinese, 1470–1524",,Tang Yin,Chinese,1470,1524,,1470,1524,Album leaf; ink on gold-flecked paper,10 5/8 x 9 7/8 in. (27 x 25.1cm),"Bequest Of John M. Crawford Jr., 1988",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/45764,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.58,false,true,53796,Asian Art,Album leaf,,China,Ming dynasty (1368–1644),,,,Artist,,Tang Yin,"Chinese, 1470–1524",,Tang Yin,Chinese,1470,1524,,1470,1524,Album leaf; ink on gold-flecked paper,H. 12 1/8 in. (30.8 cm); W. 12 1/8 in. (30.8 cm),"Bequest Of John M. Crawford Jr., 1988",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/53796,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -50.230,false,true,45777,Asian Art,Hanging scroll,明 恭候大駕還自南郊詩 軸|Awaiting the Emperor's Return from the Southern Suburbs,China,Ming dynasty (1368–1644),,,,Artist,,Wen Zhengming,"Chinese, 1470–1559",,Wen Zhengming,Chinese,1470,1559,,1470,1559,Hanging scroll; ink on paper,Overall: 136 x 39 1/4 in. (345.4 x 99.7 cm) Overall with rollers: 40 3/4 in. (103.5 cm),"Anonymous Gift, 1950",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/45777,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.61,false,true,45783,Asian Art,Folding fan mounted as an album leaf,,China,Ming dynasty (1368–1644),,,,Artist,,Wen Zhengming,"Chinese, 1470–1559",,Wen Zhengming,Chinese,1470,1559,,1470,1559,Folding fan mounted as an album leaf; ink on gold-flecked paper,6 3/8 x 19 1/2 in. (16.2 x 49.5 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/45783,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -13.100.50,false,true,45804,Asian Art,Folding fan mounted as an album leaf,明 陳淳 行草重九詩 扇面|Poem on the Double Ninth Festival,China,Ming dynasty (1368–1644),,,,Artist,,Chen Chun,"Chinese, 1483–1544",,Chen Chun,Chinese,1483,1544,,1483,1544,Folding fan mounted as an album leaf; ink on gold paper,Image: 6 3/4 x 20 1/4 in. (17.1 x 51.4 cm),"John Stewart Kennedy Fund, 1913",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/45804,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.67,false,true,45812,Asian Art,Album leaf,,China,Ming dynasty (1368–1644),,,,Artist,,Wang Chong,"Chinese, 1494–1533",,Wang Chong,Chinese,1494,1533,,1494,1533,Album leaf; ink on paper,10 1/8 x 13 3/8 in. (25.7 x 34.0 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/45812,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -13.100.56,false,true,48876,Asian Art,Folding fan mounted as an album leaf,,China,Ming dynasty (1368–1644),,,,Artist,After,Wen Peng,"Chinese, 1498–1573",,Wen Peng,Chinese,1498,1573,,1498,1573,Folding fan mounted as an album leaf; ink on gold-flecked paper,7 1/2 x 21 in. (19.1 x 53.3 cm),"John Stewart Kennedy Fund, 1913",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/48876,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.69,false,true,48874,Asian Art,Album leaf,,China,Ming dynasty (1368–1644),,,,Artist,,Wen Peng,"Chinese, 1498–1573",,Wen Peng,Chinese,1498,1573,,1498,1573,Album leaf; ink on paper,11 1/2 x 15 5/8 in. (29.2 x 39.7 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/48874,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.71,false,true,48877,Asian Art,Folding fan mounted as an album leaf,,China,Ming dynasty (1368–1644),,,,Artist,,Wang Guxiang,"Chinese, 1501–1568",,Wang Guxiang,Chinese,1501,1568,,1501,1568,Folding fan mounted as an album leaf; ink on gold-flecked paper,6 3/8 x 18 5/8 in. (16.2 x 47.3 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/48877,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.72,false,true,48879,Asian Art,Album leaf,,China,Ming dynasty (1368–1644),,,,Artist,,Wen Jia,"Chinese, 1501–1583",,Wen Jia,Chinese,1501,1583,,1501,1583,Album leaf; ink on paper,8 1/2 x 15 1/4 in. (21.6 x 38.7 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/48879,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -13.100.82,false,true,35993,Asian Art,Folding fan mounted as an album leaf,,China,Ming dynasty (1368–1644),,,,Artist,,Yuan Zhi,"Chinese, 1502–1547",,Yuan Zhi,Chinese,1502,1547,,1502,1547,Folding fan mounted as an album leaf; ink on paper,7 3/8 x 21 in. (18.7 x 53.3 cm),"John Stewart Kennedy Fund, 1913",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/35993,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.79,false,true,48904,Asian Art,Album leaf,,China,Ming dynasty (1368–1644),,,,Artist,,Zhou Tianqiu,"Chinese, 1514–1595",,Zhou Tianqiu,Chinese,1514,1595,,1514,1595,Album leaf; ink on paper,7 3/8 x 12 in. (18.7 x 30.5 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/48904,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.80,false,true,48905,Asian Art,Folding fan mounted as an album leaf,,China,Ming dynasty (1368–1644),,,,Artist,,Zhou Tianqiu,"Chinese, 1514–1595",,Zhou Tianqiu,Chinese,1514,1595,,1514,1595,Folding fan mounted as an album leaf; ink on gold-flecked paper,7 3/16 x 21 1/8 in. (18.3 x 53.7 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/48905,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.88,false,true,48915,Asian Art,Folding fan mounted as an album leaf,,China,Ming dynasty (1368–1644),,,,Artist,,Zhang Fengyi,"Chinese, 1527–1613",,Zhang Fengyi,Chinese,1527,1613,,1527,1613,Folding fan mounted as an album leaf; ink on gold paper,6 1/4 x 19 1/2 in. (15.9 x 49.5 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/48915,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.84,false,true,48917,Asian Art,Folding fan mounted as an album leaf,,China,Ming dynasty (1368–1644),,,,Artist,,Wang Zhideng,"Chinese, 1535–1612",,Wang Zhideng,Chinese,1535,1612,,1535,1612,Folding fan mounted as an album leaf; ink on gold paper,7 1/8 x 20 1/4 in. (18.1 x 51.4 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/48917,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.85,false,true,48926,Asian Art,Manuscript fragment,明 王穉登 行草書山水窟殘稿 冊頁|Fragment of a Manuscript,China,Ming dynasty (1368–1644),,,,Artist,,Wang Zhideng,"Chinese, 1535–1612",,Wang Zhideng,Chinese,1535,1612,,1535,1612,Album leaf; ink on ruled paper,Image: 9 5/8 x 15 13/16 in. (24.4 x 40.2 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/48926,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.86,false,true,48927,Asian Art,Album leaf,明 王穉登 行草書札 冊頁|Letter,China,Ming dynasty (1368–1644),,,,Artist,,Wang Zhideng,"Chinese, 1535–1612",,Wang Zhideng,Chinese,1535,1612,,1535,1612,Album leaf; ink on paper,Image: 9 11/16 x 5 5/16 in. (24.6 x 13.5 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/48927,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.90,false,true,48935,Asian Art,Folding fan mounted as an album leaf,,China,Ming dynasty (1368–1644),,,,Artist,,Mo Shilong,"Chinese, 1537–1587",,Mo Shilong,Chinese,1537,1587,,1537,1587,Folding fan mounted as an album leaf; ink on gold-flecked paper,6 x 19 1/16 in. (15.2 x 48.4 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/48935,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.92,false,true,48943,Asian Art,Album leaf,明 焦竤 行草書札 冊頁|Letter,China,Ming dynasty (1368–1644),,,,Artist,,Jiao Hong,"Chinese, 1541–1620",,Jiao Hong,Chinese,1541,1620,,1541,1620,Album leaf; ink on paper,10 5/8 x 6 5/8 in. (27 x 16.8 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/48943,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.93,false,true,45384,Asian Art,Hanging scroll,,China,Ming dynasty (1368–1644),,,,Artist,,Tu Long,"Chinese, 1542–1605",,Tu Long,Chinese,1542,1605,,1542,1605,Hanging scroll; ink on paper,Image: 49 3/4 x 10 3/4 in. (126.4 x 27.3 cm) Overall: 80 1/2 x 18 in. (204.5 x 45.7 cm) Overall with knobs: 80 1/2 x 19 3/4 in. (204.5 x 50.2 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/45384,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.96,false,true,48956,Asian Art,Album leaf,"明 董其昌 行草致陳繼儒書 冊頁 |Letter to Chen Jiru (1558-1635)",China,Ming dynasty (1368–1644),,,,Artist,,Dong Qichang,"Chinese, 1555–1636",,Dong Qichang,Chinese,1555,1636,,1555,1636,Album leaf; ink on ruled paper,9 1/16 x 9 1/16 in. (23 x 23 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/48956,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.97,false,true,48957,Asian Art,Folding fan mounted as an album leaf,,China,Ming dynasty (1368–1644),,,,Artist,,Dong Qichang,"Chinese, 1555–1636",,Dong Qichang,Chinese,1555,1636,,1555,1636,Folding fan mounted as an album leaf; ink on gold paper,6 7/8 x 19 7/8 in. (17.5 x 50.5 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/48957,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.98,false,true,48958,Asian Art,Handscroll,,China,Ming dynasty (1368–1644),,,,Artist,,Dong Qichang,"Chinese, 1555–1636",,Dong Qichang,Chinese,1555,1636,,1555,1636,Handscroll; ink on satin,10 1/2 x 72 5/16 in. (26.7 x 183.7 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/48958,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -13.100.76,false,true,48963,Asian Art,Folding fan mounted as an album leaf,,China,Ming dynasty (1368–1644),,,,Artist,,Chen Jiru,"Chinese, 1558–1635",,Chen Jiru,Chinese,1558,1635,,1368,1644,Folding fan mounted as an album leaf; ink on gold-flecked paper,6 1/2 x 19 1/2 in. (16.5 x 49.5 cm),"John Stewart Kennedy Fund, 1913",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/48963,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.103,false,true,48960,Asian Art,Album,,China,Ming dynasty (1368–1644),,,,Artist,,Chen Jiru,"Chinese, 1558–1635",,Chen Jiru,Chinese,1558,1635,,1558,1635,Album of eight leaves; ink on gold-flecked paper,11 13/16 x 6 1/8 in. (30 x 15.6 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/48960,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.104,false,true,48961,Asian Art,Album,,China,Ming dynasty (1368–1644),,,,Artist,,Chen Jiru,"Chinese, 1558–1635",,Chen Jiru,Chinese,1558,1635,,1558,1635,Album of eight double leaves; ink on paper,Overall (double leaf): 8 5/8 × 11 3/4 in. (21.9 × 29.8 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/48961,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.105,false,true,48962,Asian Art,Folding fan mounted as an album leaf,,China,Ming dynasty (1368–1644),,,,Artist,,Chen Jiru,"Chinese, 1558–1635",,Chen Jiru,Chinese,1558,1635,,1558,1635,Folding fan mounted as an album leaf; ink on gold-flecked paper,7 1/4 x 20 1/4 in. (18.4 x 51.4 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/48962,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.106,false,true,48964,Asian Art,Folding fan mounted as an album leaf,,China,Ming dynasty (1368–1644),,,,Artist,,Wang Heng,"Chinese, 1561–1609",,Wang Heng,Chinese,1561,1609,,1561,1609,Folding fan mounted as an album leaf; ink on gold-flecked paper,7 1/2 x 22 1/4 in. (19.1 x 56.5 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/48964,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"1989.363.109a, b",false,true,48970,Asian Art,Hanging scrolls,,China,Ming dynasty (1368–1644),,,,Artist,,Zhang Ruitu,"Chinese, 1570–1641",,Zhang Ruitu,Chinese,1570,1641,,1570,1641,Pair of hanging scrolls; ink on paper,Image (Each): 111 x 18 in. (281.9 x 45.7 cm) Overall (Each): 127 x 21 in. (322.6 x 53.3 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/48970,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.121,false,true,49043,Asian Art,Folding fan mounted as an album leaf,,China,Ming dynasty (1368–1644),,,,Artist,,Ni Yuanlu,"Chinese, 1593–1644",,Ni Yuanlu,Chinese,1593,1644,,1593,1644,Folding fan mounted as an album leaf; ink on gold paper,6 5/8 x 18 7/8 in. (16.8 x 47.9 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/49043,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.122,false,true,49044,Asian Art,Hanging scroll,明 倪元璐 行草書七絕詩 軸|Calligraphy,China,Ming dynasty (1368–1644),,,,Artist,,Ni Yuanlu,"Chinese, 1593–1644",,Ni Yuanlu,Chinese,1593,1644,,1593,1644,Hanging scroll; ink on paper,Image: 54 7/8 x 24 1/4 in. (139.4 x 61.6 cm) Overall with mounting: 111 x 31 1/2 in. (281.9 x 80 cm) Overall with knobs: 111 x 34 1/2 in. (281.9 x 87.6 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/49044,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -13.100.52,false,true,49097,Asian Art,Folding fan mounted as an album leaf,,China,Ming dynasty (1368–1644),,,,Artist,,Chen Mingxia,"Chinese, 1601–1654",,Chen Mingxia,Chinese,1601,1654,,1601,1654,Folding fan mounted as an album leaf; ink on paper,6 1/2 x 20 1/2 in. (16.5 x 52.1 cm),"John Stewart Kennedy Fund, 1913",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/49097,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.113,false,true,49015,Asian Art,Folding fan mounted as an album leaf,,China,Ming dynasty (1368–1644),,,,Artist,,Zou Zhilin,"Chinese, 1574–ca. 1654",,Zou Zhilin,Chinese,1574,1664,,1574,1644,Folding fan mounted as an album leaf; ink on mica-flecked paper,6 5/16 x 20 5/8 in. (16 x 52.4 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/49015,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -13.100.86,false,true,35997,Asian Art,Folding fan mounted as an album leaf,,China,Ming dynasty (1368–1644),,,,Artist,,Li Qiao,"Chinese, active Ming dynasty",,Li Qiao,Chinese,1350,1650,,1368,1644,Folding fan mounted as an album leaf; ink on paper,6 x 20 1/4 in. (16.5 x 51.4 cm),"John Stewart Kennedy Fund, 1913",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/35997,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.75,false,true,48909,Asian Art,Folding fan mounted as an album leaf,,China,Ming dynasty (1368–1644),,,,Artist,,Ju Jie,"Chinese, active ca. 1531–1585",,Ju Jie,Chinese,1531,1585,,1531,1585,Folding fan mounted as an album leaf; ink on gold paper,6 1/4 x 18 13/16 in. (15.9 x 47.8 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/48909,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.77,false,true,48930,Asian Art,Folding fan mounted as an album leaf,,China,Ming dynasty (1368–1644),,,,Artist,,Chen Yuansu,"Chinese, active late 16th century",,Chen Yuansu,Chinese,1500,1599,,1567,1599,Folding fan mounted as an album leaf; ink on gold paper,6 7/8 x 20 7/8 in. (17.5 x 53 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/48930,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.73,false,true,48897,Asian Art,Folding fan mounted as an album leaf,,China,Ming dynasty (1368–1644),,,,Artist,,Shen Shi,"Chinese, active early 16th century",,Shen Shi,Chinese,1500,1533,,1500,1533,Folding fan mounted as an album leaf; ink on gold-flecked paper,7 1/4 x 19 1/2 in. (18.4 x 49.5 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/48897,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.107,false,true,48965,Asian Art,Folding fan mounted as an album leaf,,China,Ming dynasty (1368–1644),,,,Artist,,Lu Yingyang,"Chinese, active early 17th century",,Lu Yingyang,Chinese,1600,1650,,1600,1633,Folding fan mounted as an album leaf; ink on gold-flecked paper,5 15/16 x 18 3/8 in. (15.1 x 46.7 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/48965,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.150,false,true,49169,Asian Art,Hanging scroll,,China,Qing dynasty (1644–1911),,,,Artist,,Sun Yueban,"Chinese, 1639–1708",,Sun Yueban,Chinese,1639,1708,,1644,1708,Hanging scroll; ink on paper,Image: 52 x 17 in. (132.1 x 43.2 cm) Overall: 77 x 22 3/4 in. (195.6 x 57.8 cm) Overall with knobs: 77 x 26 5/8 in. (195.6 x 67.6 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/49169,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.152,false,true,49174,Asian Art,Folding fan mounted as an album leaf,清 石濤 (朱若極) 五詩 扇頁|Five Poems,China,Qing dynasty (1644–1911),,,,Artist,,Shitao (Zhu Ruoji),"Chinese, 1642–1707",,Shitao,Chinese,1642,1707,,1644,1707,Folding fan mounted as an album leaf; ink on paper,6 7/8 x 17 1/2 in. (17.5 x 44.5 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/49174,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.158,false,true,49219,Asian Art,Folding fan mounted as an album leaf,,China,Qing dynasty (1644–1911),,,,Artist,,Wang Shihong,"Chinese, 1658–1723",,Wang Shihong,Chinese,1658,1723,,1658,1723,Folding fan mounted as an album leaf; ink on paper,6 3/8 x 19 1/4 in. (16.2 x 48.9 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/49219,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.204,false,true,51901,Asian Art,Hanging scroll,,China,Qing dynasty (1644–1911),,,,Artist,,Chen Hongshou,"Chinese, 1768–1822",,Chen Hongshou,Chinese,1768,1822,,1786,1822,Hanging scroll; ink on green paper,Image: 64 7/8 x 15 in. (164.8 x 38.1 cm) Overall with mounting: 88 1/4 x 21 3/4 in. (224.2 x 55.2 cm) Overall with knobs: 88 1/4 x 26 in. (224.2 x 66 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/51901,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"1976.384.2a, b",false,true,36122,Asian Art,Hanging scrolls,清 翁同龢 對軸|Regular-script Calligraphic Couplet,China,Qing dynasty (1644–1911),,,,Artist,,Weng Tonghe,"Chinese, 1830–1904",,Weng Tonghe,Chinese,1830,1904,,1830,1904,Pair of hanging scrolls; ink on red printed paper,Image (each): 92 5/8 x 17 7/8 in. (235.3 x 45.4 cm) Overall with mounting (each): 114 1/2 x 22 in. (290.8 x 55.9 cm),"Seymour Fund, 1976",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/36122,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -54.134.3,false,true,51807,Asian Art,Handscroll fragment,,China,Ming dynasty (?) (1368–1644),,,,Artist,,Wang Zhideng,"Chinese, 1535–1612",,Wang Zhideng,Chinese,1535,1612,,1535,1612,Fragment of scroll; ink on paper,71 1/4 x 14 1/4 in. (181.0 x 36.2 cm),"Gift of the Pierpont Morgan Library, 1954",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/51807,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -13.100.74,false,true,51379,Asian Art,Folding fan mounted as an album leaf,,China,late Ming dynasty (1368–1644),,,,Artist,,Fan Yunlin,"Chinese, 1558–1641",,Fan Yunlin,Chinese,1558,1641,,1558,1641,Folding fan mounted as an album leaf; ink and color on gold paper,7 x 21 in. (17.8 x 53.3 cm),"John Stewart Kennedy Fund, 1913",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/51379,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2000.325,false,true,39534,Asian Art,Handscroll mounted as hanging scroll,南宋 張即之 行書杜甫樂遊原詩殘卷 軸|Excerpt from “Song of Leyou Park”,China,Southern Song dynasty (1127–1279),,,,Artist,,Zhang Jizhi,"Chinese, 1186–1266",,Zhang Jizhi,Chinese,1186,1266,,1186,1266,Section of a handscroll mounted as a hanging scroll; ink on paper,Image: 12 3/4 x 30 1/4 in. (32.4 x 76.8 cm) Overall with mounting: 48 1/2 x 35 1/2 in. (123.2 x 90.2 cm) Overall with knobs: 48 1/2 x 37 1/2 in. (123.2 x 95.3 cm),"Gift of Sylvan Barnet and William Burto, in honor of Tajima Mitsuru, 2000",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/39534,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1994.382,false,true,42158,Asian Art,Brush Holder,清康熙 顧玨 竹雕筆筒|Brush Holder,China,"Qing dynasty (1644–1911), Kangxi period (1662–1722)",,,,Artist,,Gu Jue,active late 17th century,,Gu Jue,Chinese,1675,1700,,1667,1699,Bamboo with hardwood rim and base,H. 7 in. (17.8 cm); D. 6 1/2 in. (16.5 cm),"Purchase, Eileen W. Bamberger Bequest, in memory of her husband, Max Bamberger, 1994",,,,,,,,,,,,Bamboo,,http://www.metmuseum.org/art/collection/search/42158,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.118,false,true,49033,Asian Art,Album leaf,明/清 王鐸 書札 冊頁|Letter,China,late Ming (1368–1644)–early Qing (1644–1911) dynasty,,,,Artist,,Wang Duo,"Chinese, 1592–1652",,Wang Duo,Chinese,1592,1652,,1592,1652,Album leaf; ink on gold-flecked paper,10 3/4 x 6 in. (27.3 x 15.2 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/49033,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.40,false,true,45657,Asian Art,Handscroll,元/明 宋克 草書負郭堂成七律詩 卷|Poem on Retirement,China,late Yuan (1271–1368)–early Ming (1368–1644) dynasty,,,,Artist,,Song Ke,"Chinese, 1327–1387",,Song Ke,Chinese,1327,1387,,1327,1387,Handscroll; ink on gold-flecked paper,Image: 10 1/2 x 27 1/4 in. (26.7 x 69.2 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/45657,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.231,false,true,43294,Asian Art,Book,,China,Qing dynasty (1644–1911),,,,Artist,,Dong Gao,"Chinese, 1740–1818",,Dong Gao,Chinese,1740,1818,,1644,1911,"Jade, rosewood",H. 7 5/8 in. (19.4 cm); W. 5 1/2 in. (14 cm); D. 1 5/8 in. (4.1 cm),"Gift of Edward R. Finch, Jr., 1976",,,,,,,,,,,,Jade,,http://www.metmuseum.org/art/collection/search/43294,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -CP37,false,true,63331,Asian Art,Print,,China,,,,,Artist,Original painted by,Wang Youcheng,"Chinese, 698–759",,Wang Youcheng,Chinese,0698,0759,,0,0,Polychrome woodblock print; ink and color on paper,9 5/8 x 11 13/16 in. (24.4 x 30 cm),"Rogers Fund, 1924",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/63331,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -CP22,false,true,63216,Asian Art,Print,,China,,,,,Artist,Original painted by,Li Cheng,"Chinese, 919–967",,LI CHENG,Chinese,0919,0967,,0,0,Circular fan-shaped woodblock print; ink and color on paper,9 5/8 x 11 13/16 in. (24.4 x 30 cm),"Rogers Fund, 1924",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/63216,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -CP48,false,true,63351,Asian Art,Print,芥子園畫傳|Study Pavilion and Plum Trees: Page from The Mustard Seed Garden Manual of Painting,China,,,,,Artist,Original painted by,Li Cheng,"Chinese, 919–967",,LI CHENG,Chinese,0919,0967,,0,0,Polychrome woodblock print; ink and color on paper,9 5/8 x 11 13/16 in. (24.4 x 30 cm),"Rogers Fund, 1924",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/63351,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -CP33,false,true,63230,Asian Art,Print,,China,,,,,Artist,Original painted by,Wen Youke,"Chinese, died 1079",,Wen Youke,Chinese,1079,1079,,0,0,Polychrome woodblock print; ink and color on paper,9 5/8 x 11 13/16 in. (24.4 x 30 cm),"Rogers Fund, 1924",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/63230,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -CP24,false,true,63218,Asian Art,Print,,China,,,,,Artist,Original painted by,Mi Youren,"Chinese, 1074–1151",,MI YOUREN,Chinese,1074,1151,,0,0,Circular fan-shaped woodblock print; ink and color on paper,9 5/8 x 11 13/16 in. (24.4 x 30 cm),"Rogers Fund, 1924",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/63218,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -CP40,false,true,63336,Asian Art,Print,,China,,,,,Artist,In the Style of,Huang Zujiu,"Chinese, 1269–1354",,Huang Zujiu,Chinese,1269,1354,,0,0,Polychrome woodblock print; ink and color on paper,9 5/8 x 5 13/16 in. (24.4 x 14.8 cm),"Rogers Fund, 1924",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/63336,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -CP34,false,true,63325,Asian Art,Print,,China,,,,,Artist,Original painted by,Ke Jiusi,"Chinese, 1290–1343",,Ke Jiusi,Chinese,1290,1343,,0,0,Polychrome woodblock print; ink and color on paper,9 5/8 x 11 13/16 in. (24.4 x 30 cm),"Rogers Fund, 1924",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/63325,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -CP10,false,true,63204,Asian Art,Print,,China,,,,,Artist,Original painted by,Yunlin,"Chinese, 1301–1374",,Yunlin,Chinese,1301,1374,,1301,1374,Polychrome woodblock print; ink and color on paper,9 5/8 x 11 13/16 in. (24.4 x 30 cm),"Rogers Fund, 1924",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/63204,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -CP25,false,true,63219,Asian Art,Print,,China,,,,,Artist,Original painted by,Ni Zan,"Chinese, 1306–1374",,NI ZAN,Chinese,1306,1374,,0,0,Circular fan-shaped woodblock print; ink and color on paper,9 5/8 x 11 13/16 in. (24.4 x 30 cm),"Rogers Fund, 1924",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/63219,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -CP42,false,true,63338,Asian Art,Print,,China,,,,,Artist,Original painted by,Shen Zhou,"Chinese, 1427–1509",,Shen Zhou,Chinese,1427,1509,,0,0,Polychrome woodblock print; ink and color on paper,9 5/8 x 5 7/8 in. (24.4 x 14.9 cm),"Rogers Fund, 1924",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/63338,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -CP8,false,true,63201,Asian Art,Print,,China,,,,,Artist,Original by,Tang Yin,"Chinese, 1470–1524",,Tang Yin,Chinese,1470,1524,,1470,1524,Polychrome woodblock print; ink and color on paper,9 5/8 x 11 13/16 in. (24.4 x 30 cm),"Rogers Fund, 1924",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/63201,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -CP19,false,true,63213,Asian Art,Print,,China,,,,,Artist,Original painted by,Tang Yin,"Chinese, 1470–1524",,Tang Yin,Chinese,1470,1524,,0,0,Circular fan-shaped woodblock print; ink and color on paper,9 5/8 x 11 13/16 in. (24.4 x 30 cm),"Rogers Fund, 1924",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/63213,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -CP46,false,true,63345,Asian Art,Print,,China,,,,,Artist,Original painted by,Xu Wei,"Chinese, 1521–1593",,Xu Wei,Chinese,1521,1593,,0,0,Polychrome woodblock print; ink and color on paper,9 5/8 x 11 13/16 in. (24.4 x 30 cm),"Rogers Fund, 1924",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/63345,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -CP41,false,true,63337,Asian Art,Print,,China,,,,,Artist,Original painted by,Li Liufang,"Chinese, 1575–1629",,Li Liufang,Chinese,1575,1629,,0,0,Polychrome woodblock print; ink and color on paper,9 5/8 x 5 7/8 in. (24.4 x 14.9 cm),"Rogers Fund, 1924",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/63337,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -CP21,false,true,63215,Asian Art,Print,,China,,,,,Artist,Original painted by,Lan Ying,"Chinese, 1585–1664",,Lan Ying,Chinese,1585,1664,,0,0,Circular fan-shaped woodblock print; ink and color on paper,9 5/8 x 11 13/16 in. (24.4 x 30 cm),"Rogers Fund, 1924",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/63215,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -CP43,false,true,63339,Asian Art,Print,,China,,,,,Artist,Original painted by,Hongren,"Chinese, 1610–1664",,HONGREN,Chinese,1610,1664,,0,0,Polychrome woodblock print; ink and color on paper,9 5/8 x 5 7/8 in. (24.4 x 14.9 cm),"Rogers Fund, 1924",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/63339,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -CP44,false,true,63341,Asian Art,Print,,China,,,,,Artist,Original painted by,Yang Wencong,"Chinese, 1597–1645/46",,Yang Wencong,Chinese,1597,1646,,0,0,Polychrome woodblock print; ink and color on paper,9 5/8 x 5 7/8 in. (24.4 x 14.9 cm),"Rogers Fund, 1924",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/63341,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -CP18,false,true,63212,Asian Art,Print,,China,,,,,Artist,Original painted by,Hu Changbo,"Chinese, active ca. 1601",,Hu Changbo,Chinese,1601,1601,,0,0,Circular fan-shaped woodblock print; ink and color on paper,9 5/8 x 11 13/16 in. (24.4 x 30 cm),"Rogers Fund, 1924",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/63212,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -CP20,false,true,63214,Asian Art,Print,,China,,,,,Artist,Original painted by,Li Gonglin,"Chinese, ca. 1041–1106",,Li Gonglin,Chinese,1041,1106,,0,0,Circular fan-shaped woodblock print; ink and color on paper,9 5/8 x 11 13/16 in. (24.4 x 30 cm),"Rogers Fund, 1924",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/63214,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -CP23,false,true,63217,Asian Art,Print,,China,,,,,Artist,Original painted by,Wang Meng,"Chinese, ca. 1308–1385",,Wang Meng,Chinese,1308,1385,,0,0,Circular fan-shaped woodblock print; ink and color on paper,9 5/8 x 11 13/16 in. (24.4 x 30 cm),"Rogers Fund, 1924",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/63217,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -CP35,false,true,63327,Asian Art,Print,,China,,,,,Artist,Original painted by,Wang Meng,"Chinese, ca. 1308–1385",,Wang Meng,Chinese,1308,1385,,0,0,Polychrome woodblock print; ink and color on paper,9 5/8 x 11 13/16 in. (24.4 x 30 cm),"Rogers Fund, 1924",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/63327,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -CP47,false,true,63348,Asian Art,Print,,China,,,,,Artist,Original painted by,Qing Ji,"Chinese, fl. 1630–1650",,Qing Ji,Chinese,1630,1650,,0,0,Polychrome woodblock print; ink and color on paper,9 5/8 x 11 13/16 in. (24.4 x 30 cm),"Rogers Fund, 1924",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/63348,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -CP15,false,true,63209,Asian Art,Print,,China,,,,,Artist,Original painted by,Huang Guzu,"Chinese, active ca. 900–960",,Huang Guzu,Chinese,0900,0960,,0,0,Polychrome woodblock print; ink and color on paper,9 5/8 x 11 13/16 in. (24.4 x 30 cm),"Rogers Fund, 1924",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/63209,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -CP29,false,true,63226,Asian Art,Print,,China,,,,,Artist,Original painted by,Fan Kuan,"Chinese, active ca. 990–1030",,Fan Kuan,Chinese,0990,1030,,0,0,Circular fan-shaped woodblock print; ink and color on paper,9 5/8 x 11 13/16 in. (24.4 x 30 cm),"Rogers Fund, 1924",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/63226,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -CP9,false,true,63203,Asian Art,Print,,China,,,,,Artist,Original painted by,Xia Gui,"Chinese, active ca. 1195–1230",,Xia Gui,Chinese,1195,1230,,1195,1230,Polychrome woodblock print; ink and color on paper,9 5/8 x 11 13/16 in. (24.4 x 30 cm),"Rogers Fund, 1924",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/63203,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -CP3,false,true,63078,Asian Art,Print,,China,"Qing dynasty (1644–1911), Kangxi period (1662–1722)",,,,Artist,Workshop of,Ding Liangxian,active first half of the 18th century,,Ding Liang-xian,Chinese,1700,1749,,1662,1722,Polychrome woodblock print; ink and color on paper,Image: 13 1/4 × 10 7/8 in. (33.7 × 27.6 cm),"Rogers Fund, 1923",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/63078,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -24.44,false,true,51548,Asian Art,Handscroll,壺人樂聚|Merry Gatherings in the Magic Jar,China,Song dynasty (960–1279),,,,Artist,,Gong Kai,"Chinese, 1222–after 1304",,Gong Kai,Chinese,1222,1310,,1222,1279,Handscroll; ink on paper,11 3/4 in. × 14 ft. 2 in. (29.8 × 431.8 cm),"Rogers Fund, 1924",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/51548,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1973.121.2,false,true,39920,Asian Art,Handscroll,北宋 傳趙令穰 江村秋曉圖 卷|River Village in Autumn Dawn,China,Song dynasty (960–1279),,,,Artist,Attributed to,Zhao Lingrang,"Chinese, active ca. 1070– after 1100",,ZHAO LINGRANG,Chinese,1070,1100,,1070,1170,Handscroll; ink and color on silk,Image: 9 5/16 x 41 in. (23.7 x 104.1 cm),"Ex coll.: C. C. Wang Family, Purchase, Gift of J. Pierpont Morgan, by exchange, 1973",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/39920,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1982.205,false,true,45685,Asian Art,Handscroll,,China,Ming dynasty (1368–1644),,,,Artist,,Shen Zhou,"Chinese, 1427–1509",,Shen Zhou,Chinese,1427,1509,,1427,1509,Handscroll; ink and color on paper,10 13/16 x 198 3/4 in. (27.5 x 504.8 cm),"Purchase, The Dillon Fund Gift, in memory of Phyllis E. Dillon, 1982",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45685,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.65,false,true,45793,Asian Art,Handscroll,明 佚名 怡松圖 卷|Enjoying the Pines,China,Ming dynasty (1368–1644),,,,Artist,Unidentified Artist after,Shen Zhou,"Chinese, 1427–1509",,Shen Zhou,Chinese,1427,1509,,1368,1644,Handscroll frontispiece; ink on gold-flecked paper,Image: 12 3/4 x 57 in. (32.4 x 144.8 cm) Overall with mounting: 13 3/16 x 252 15/16 in. (33.5 x 642.5 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45793,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1980.81,false,true,45773,Asian Art,Album leaves mounted as a handscroll,,China,Ming dynasty (1368–1644),,,,Artist,,Tang Yin,"Chinese, 1470–1524",,Tang Yin,Chinese,1470,1524,,1470,1524,Eight album leaves mounted as a handscroll; ink and color on silk,Image (All leaves): 12 3/4 x 162 7/8 in. (32.4 x 413.7 cm) Overall with mounting: 13 1/16 x 306 5/16 in. (33.2 x 778 cm) Image (Leaf 1): 12 3/4 x 17 in. (32.4 x 43.2 cm) Image (Leaf 2): 12 3/4 x 17 in. (32.4 x 43.2 cm) Image (Leaf 3): 12 3/4 x 16 7/8 in. (32.4 x 42.9 cm) Image (Leaf 4): 12 3/4 x 16 3/4 in. (32.4 x 42.5 cm) Image (Leaf 5): 12 3/4 x 16 7/8 in. (32.4 x 42.9 cm) Image (Leaf 6): 12 3/4 x 16 13/16 in. (32.4 x 42.7 cm) Image (Leaf 7): 12 3/4 x 16 7/8 in. (32.4 x 42.9 cm) Image (Leaf 8): 12 3/4 x 16 7/8 in. (32.4 x 42.9 cm),"Ex coll.: C. C. Wang Family, Gift of Douglas Dillon, 1980",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45773,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1982.7.2,false,true,45772,Asian Art,Folding fan mounted as an album leaf,,China,Ming dynasty (1368–1644),,,,Artist,,Tang Yin,"Chinese, 1470–1524",,Tang Yin,Chinese,1470,1524,,1470,1524,Folding fan mounted as an album leaf; ink and color on gold-flecked paper,20 3/4 x 9 3/4 in. (52.7 x 24.8 cm),"Purchase, The Dillon Fund Gift, 1982",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45772,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -47.18.129,false,true,36062,Asian Art,Handscroll,,China,Ming dynasty (1368–1644),,,,Artist,Attributed to,Tang Yin,"Chinese, 1470–1524",,Tang Yin,Chinese,1470,1524,,1470,1524,Handscroll; ink on silk,Image: 8 × 24 1/4 in. (20.3 × 61.6 cm),"From the Collection of A. W. Bahr, Purchase, Fletcher Fund, 1947",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/36062,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1988.394.1,false,true,44573,Asian Art,Folding fan mounted as an album leaf,,China,Ming dynasty (1368–1644),,,,Artist,,Tang Yin,"Chinese, 1470–1524",,Tang Yin,Chinese,1470,1524,,1470,1524,Folding fan mounted as an album leaf; ink on gold-flecked paper,6 13/16 x 19 5/8 in. (17.3 x 49.8 cm),"Edward Elliott Family Collection, Douglas Dillon Gift, 1988",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/44573,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.54,false,true,45756,Asian Art,Handscroll,,China,Ming dynasty (1368–1644),,,,Artist,After,Tang Yin,"Chinese, 1470–1524",,Tang Yin,Chinese,1470,1524,,1470,1524,Handscroll; ink on paper,Image: 11 7/16 x 59 1/8 in. (29.1 x 150.2 cm) Overall with mounting: 11 7/8 x 289 9/16 in. (30.2 x 735.5 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45756,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.55,false,true,45760,Asian Art,Hanging scroll,,China,Ming dynasty (1368–1644),,,,Artist,,Tang Yin,"Chinese, 1470–1524",,Tang Yin,Chinese,1470,1524,,1470,1524,Hanging scroll; ink on paper,Image: 28 7/16 x 14 9/16 in. (72.2 x 37 cm) Overall with mounting: 64 3/4 x 21 in. (164.5 x 53.3 cm) Overall with knobs: 64 3/4 x 23 3/4 in. (164.5 x 60.3 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45760,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.278.9a–t,false,true,36103,Asian Art,Album,,China,Ming dynasty (1368–1644),,,,Artist,,Wen Zhengming,"Chinese, 1470–1559",,Wen Zhengming,Chinese,1470,1559,,1470,1559,"Album of eight painting leaves with facing sheets inscribed with poems, preceded by a four-sheet title piece; ink on paper",8 1/4 x 7 3/4 in. (21.0 x 19.7 cm) (each album leaf); 8 1/4 x 16 7/8 in. (21.0 x 42.9 cm) (two double calligraphy leaves),"Gift of Mr. and Mrs. Earl Morse, 1972",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/36103,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1991.273,false,true,45821,Asian Art,Hanging scroll,明 陸治 枚乘獨坐圖 軸|Mei Cheng Sitting Alone,China,Ming dynasty (1368–1644),,,,Artist,,Lu Zhi,"Chinese, 1495–1576",,Lu Zhi,Chinese,1495,1576,,1495,1576,Hanging scroll; ink and color on paper,Image: 48 7/8 x 15 1/8 in. (124.1 x 38.4 cm) Overall with mounting: 87 3/8 x 30 1/8 in. (221.9 x 76.5 cm) Overall with knobs: 87 3/8 x 34 1/2 in. (221.9 x 87.6 cm),"Gift of Herbert and Jeanine Coyne, 1991",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45821,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.68,false,true,48873,Asian Art,Folding fan mounted as an album leaf,,China,Ming dynasty (1368–1644),,,,Artist,Attributed to,Lu Zhi,"Chinese, 1495–1576",,Lu Zhi,Chinese,1495,1576,,1495,1576,Folding fan mounted as an album leaf; ink and color on gold paper,6 1/2 x 18 3/8 in. (16.5 x 46.7 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/48873,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.95,false,true,49256,Asian Art,Folding fan mounted as an album leaf,,China,Ming dynasty (1368–1644),,,,Artist,,Zhu Lu,"Chinese, 1553–1632",,Zhu Lu,Chinese,1553,1632,,1552,1632,Folding fan mounted as an album leaf; ink on gold-flecked paper,6 5/8 x 20 5/8 in. (16.8 x 52.4 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49256,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.101,false,true,51872,Asian Art,Hanging scroll,,China,Ming dynasty (1368–1644),,,,Artist,,Dong Qichang,"Chinese, 1555–1636",,Dong Qichang,Chinese,1555,1636,,1555,1636,Hanging scroll; ink on paper,Image: 37 5/8 x 16 3/4 in. (95.6 x 42.5 cm) Overall with mounting: 84 5/8 x 23 in. (214.9 x 58.4 cm) Overall with knobs: 84 5/8 x 25 3/4 in. (214.9 x 65.4 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/51872,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1981.285.2,false,true,48968,Asian Art,Hanging scroll,明 張瑞圖 山水圖 軸|Mountains Along Riverbanks,China,Ming dynasty (1368–1644),,,,Artist,,Zhang Ruitu,"Chinese, 1570–1641",,Zhang Ruitu,Chinese,1570,1641,,1570,1641,Hanging scroll; ink on satin,Image: 65 3/4 x 20 1/4 in. (167 x 51.4 cm) Overall with mounting: 105 x 27 1/2 in. (266.7 x 69.9 cm) Overall with knobs: 105 x 31 1/2 in. (266.7 x 80 cm),"Edward Elliott Family Collection, Purchase, The Dillon Fund Gift, 1981",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/48968,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -13.100.55,false,true,49077,Asian Art,Folding fan mounted as an album leaf,,China,Ming dynasty (1368–1644),,,,Artist,,Xiang Shengmo,"Chinese, 1597–1658",,Xiang Shengmo,Chinese,1597,1658,,1597,1644,Folding fan mounted as an album leaf; ink on gold paper,6 1/2 x 20 in. (16.5 x 50.8 cm),"John Stewart Kennedy Fund, 1913",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49077,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.278.2,false,true,36099,Asian Art,Hanging scroll,,China,Ming dynasty (1368–1644),,,,Artist,After,Chen Hongshou,"Chinese, 1599–1652",,Chen Hongshou,Chinese,1599,1652,,1350,1750,Hanging scroll; ink and color on silk,Image: 63 7/8 x 23 5/8 in. (162.2 x 60 cm) Overall with mounting: 93 1/2 x 27 1/2 in. (237.5 x 69.9 cm) Overall with rollers: 93 1/2 x 31 1/4 in. (237.5 x 79.4 cm),"Gift of Mr. and Mrs. Earl Morse, 1972",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/36099,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1996.308,false,true,39628,Asian Art,Hanging scroll,明 藍孟 溪山雪泛圖 軸 紙本|Boating amid Snowy Streams and Mountains,China,Ming dynasty (1368–1644),,,,Artist,,Lan Meng,ca. 1614–after 1671,,Lan Meng,Chinese,1614,1671,,1614,1644,Hanging scroll; ink and color on silk,Image: 90 x 37 1/4 in. (228.6 x 94.6 cm),"Gift of John and Lili Bussel Family,1996",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/39628,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2000.664,false,true,50168,Asian Art,Hanging scroll,明 史忠 雪景山水圖 軸|Winter Landscape with Fisherman,China,Ming dynasty (1368–1644),,,,Artist,,Shi Zhong,"Chinese, 1438–ca. 1517",,Shi Zhong,Chinese,1438,1517,,1438,1517,Hanging scroll; ink on paper,Image: 56 x 12 5/8 in. (142.2 x 32.1 cm) Overall with mounting: 83 1/2 x 19 1/8 in. (212.1 x 48.6 cm) Overall with knobs: 83 1/2 x 22 1/2 in. (212.1 x 57.2 cm),"Ex coll.: C. C. Wang Family, Gift of C. C. Wang, in honor of Wen C. Fong, 2000",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/50168,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1991.342,false,true,45810,Asian Art,Hanging scroll,明 謝時臣 谿山春曉圖 軸|Spring Morning in the Mountains,China,Ming dynasty (1368–1644),,,,Artist,,Xie Shichen,"Chinese, 1487–ca. 1567",,Xie Shichen,Chinese,1487,1567,,1487,1567,Hanging scroll; ink and color on silk,Image: 76 1/8 x 41 1/4 in. (193.4 x 104.8 cm) Overall with mounting: 112 3/4 x 42 9/16 in. (286.4 x 108.1 cm) Overall with knobs: 112 3/4 x 46 3/8 in. (286.4 x 117.8 cm),"Gift of John N. Loomis, 1991",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45810,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -13.100.81,false,true,45807,Asian Art,Folding fan mounted as an album leaf,明 謝時臣 隱居圖 扇面|Landscape with Figure,China,Ming dynasty (1368–1644),,,,Artist,,Xie Shichen,"Chinese, 1487–ca. 1567",,Xie Shichen,Chinese,1487,1567,,1487,1577,Folding fan mounted as an album leaf; ink on gold paper,7 x 19 3/4 in. (17.8 x 50.2 cm),"John Stewart Kennedy Fund, 1913",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45807,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"1982.1.9a, b",false,true,36138,Asian Art,Hanging scroll,明 傳謝時臣 松溪琴客圖 軸|Listening to the Zither Among Streams and Pines,China,Ming dynasty (1368–1644),,,,Artist,,Xie Shichen,"Chinese, 1487–ca. 1567",,Xie Shichen,Chinese,1487,1567,,1487,1567,Hanging scroll; ink and color on silk,Image: 55 1/2 x 28 in. (141 x 71.1 cm) Overall with mounting: 124 x 36 in. (315 x 91.4 cm) Overall with knobs: 124 x 38 3/4 in. (315 x 98.4 cm),"Edward Elliott Family Collection, Purchase, The Dillon Fund Gift, 1982",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/36138,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.112,false,true,49013,Asian Art,Folding fan mounted as an album leaf,"明 鄒之麟 山水圖 扇面 |Landscape",China,Ming dynasty (1368–1644),,,,Artist,,Zou Zhilin,"Chinese, 1574–ca. 1654",,Zou Zhilin,Chinese,1574,1664,,1574,1644,Folding fan mounted as an album leaf; ink on gold paper,Image: 9 1/2 × 20 1/8 in. (24.1 × 51.1 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49013,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2002.296,false,true,64972,Asian Art,Hanging scroll,明 仿林良 喜上梅梢圖 軸|Birds Amidst Blossoming Plum and Bamboo,China,Ming dynasty (1368–1644),,,,Artist,After,Lin Liang,"Chinese, ca. 1416–1480",,Lin Liang,Chinese,1416,1480,,1416,1480,Hanging scroll; ink and color on silk,40 15/16 x 18 11/16 in. (104 x 47.4 cm),"Purchase, C. C. Wang Gift, 2002",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/64972,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -47.18.19,false,true,36050,Asian Art,Hanging scroll,明 倣林良 蘆鴨圖 軸|Ducks and Reeds,China,Ming dynasty (1368–1644),,,,Artist,After,Lin Liang,"Chinese, ca. 1416–1480",,Lin Liang,Chinese,1416,1480,,1416,1644,Hanging scroll; ink and color on silk,Image: 61 5/8 × 34 5/8 in. (156.5 × 87.9 cm) Overall with mounting: 10 ft. 3/8 in. × 40 3/8 in. (305.8 × 102.6 cm) Overall with knobs: 10 ft. 3/8 in. × 44 1/2 in. (305.8 × 113 cm),"From the Collection of A. W. Bahr, Purchase, Fletcher Fund, 1947",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/36050,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -12.134.8,false,true,51285,Asian Art,Hanging scroll,,China,Ming dynasty (1368–1644),,,,Artist,Attributed to,Qiu Ying,"Chinese, ca. 1495–1552",,Qiu Ying,Chinese,1485,1562,,1495,1552,Hanging scroll; ink and color on silk,Image: 33 in. × 30 3/4 in. (83.8 × 78.1 cm) Overall with mounting: 73 3/4 × 35 5/8 in. (187.3 × 90.5 cm) Overall with knobs: 73 3/4 × 39 in. (187.3 × 99.1 cm),"Rogers Fund, 1912",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/51285,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -13.220.15,false,true,36013,Asian Art,Hanging scroll,明 傳仇英 文玉圖 軸|Lady in a Bamboo Grove,China,Ming dynasty (1368–1644),,,,Artist,Attributed to,Qiu Ying,"Chinese, ca. 1495–1552",,Qiu Ying,Chinese,1485,1562,,1495,1552,Hanging scroll; ink and color on silk,Image: 49 in. × 17 1/2 in. (124.5 × 44.5 cm) Overall with mounting: 9 ft. 5 1/8 in. × 25 3/8 in. (287.3 × 64.5 cm) Overall with knobs: 9 ft. 5 1/8 in. × 29 1/2 in. (287.3 × 74.9 cm),"John Stewart Kennedy Fund, 1913",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/36013,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -47.18.22,false,true,36051,Asian Art,Hanging scroll,,China,Ming dynasty (1368–1644),,,,Artist,,Ma Shida,"Chinese, 15th century (?)",,Ma Shida,Chinese,1400,1500,,1400,1499,Hanging scroll; ink on paper,Overall: 43 3/8 x 18 3/8 in. (110.2 x 46.7 cm) Overall with mounting: 24 1/2 in. (62.2 cm),"From the Collection of A. W. Bahr, Purchase, Fletcher Fund, 1947",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/36051,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1982.1.8,false,true,48942,Asian Art,Hanging scroll,明 劉世儒 雪梅圖 軸|Plum in Snow,China,Ming dynasty (1368–1644),,,,Artist,,Liu Shiru,"Chinese, active 1550–1600",,Liu Shiru,Chinese,1550,1600,,1550,1600,Hanging scroll; ink on silk,Image: 68 3/4 x 24 3/8 in. (174.6 x 61.9 cm) Overall with mounting: 106 1/4 x 30 7/8 in. (269.9 x 78.4 cm) Overall with knobs: 106 1/4 x 33 9/16 in. (269.9 x 85.2 cm),"Edward Elliott Family Collection, Purchase, The Dillon Fund Gift, 1982",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/48942,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -13.220.115,false,true,36023,Asian Art,Hanging scroll,明 傳張路 溪畔漁家圖 軸|Fisherman and Family,China,Ming dynasty (1368–1644),,,,Artist,Attributed to,Zhang Lu,"Chinese, ca. 1490–ca. 1563",,Zhang Lu,Chinese,1490,1563,,1490,1563,Hanging scroll; ink and color on silk,43 3/8 x 31 7/8 in. (110.2 x 81.0 cm),"John Stewart Kennedy Fund, 1913",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/36023,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2001.746.2,false,true,50167,Asian Art,Hanging scrolls,明 杜堇 陶淵明菊花圖 軸|Tao Yuanming Enjoying Chrysanthemums,China,Ming dynasty (1368–1644),,,,Artist,,Du Jin,"Chinese, active ca. 1465–1509",,Du Jin,Chinese,1465,1509,,1465,1509,Hanging scrolls; ink and color on paper,Image: 58 1/4 x 14 3/16 in. (148 x 36 cm) Overall with mounting: 105 1/4 x 21 3/16 in. (267.3 x 53.8 cm) Overall with knobs: 105 1/4 x 24 1/2 in. (267.3 x 62.2 cm),"The C. C. Wang Family Collection, Gift of C. C. Wang, 2001",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/50167,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -13.100.85,false,true,35996,Asian Art,Folding fan mounted as an album leaf,,China,Ming dynasty (1368–1644),,,,Artist,,Zhang Chong,"Chinese, active ca. 1570–1610",,Zhang Chong,Chinese,1570,1610,,1570,1610,Folding fan mounted as an album leaf; ink on gold paper,6 1/2 x 19 1/4 in. (16.5 x 48.9 cm),"John Stewart Kennedy Fund, 1913",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/35996,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1981.285.11,false,true,36136,Asian Art,Hanging scroll,明 張祐 萬古春風圖 軸|Spring Breeze of Myriad Pasts,China,Ming dynasty (1368–1644),,,,Artist,,Zhang You,"Chinese, active mid 15th century",,Zhang You,Chinese,1400,1499,,1434,1466,Hanging scroll; ink on silk,Image: 61 3/4 x 27 1/2 in. (156.8 x 69.9 cm) Overall with mounting: 102 1/4 x 34 3/4 in. (259.7 x 88.3 cm) Overall with knobs: 102 1/4 x 38 in. (259.7 x 96.5 cm),"Edward Elliott Family Collection, Purchase, The Dillon Fund Gift, 1981",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/36136,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1980.414,false,true,41471,Asian Art,Hanging scroll,明 呂紀 秋景花鳥圖 軸|Autumn Landscape with Herons and Ducks,China,Ming dynasty (1368–1644),,,,Artist,,Lü Ji,"Chinese, active late 15th century",,Lü Ji,Chinese,1430,1504,,1467,1499,Hanging scroll; ink and color on silk,Image: 58 1/8 x 21 1/2 in. (147.6 x 54.6 cm) Overall with mounting: 106 1/4 x 27 1/2 in. (269.9 x 69.9 cm) Overall with knobs: 106 1/4 x 30 in. (269.9 x 76.2 cm),"Purchase, Bequest of Dorothy Graham Bennett, 1980",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/41471,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.494.2,false,true,39551,Asian Art,Hanging scroll,明 呂紀 鴛鴦芙蓉圖 軸|Mandarin ducks and cotton rose hibiscus,China,Ming dynasty (1368–1644),,,,Artist,,Lü Ji,"Chinese, active late 15th century",,Lü Ji,Chinese,1430,1504,,1467,1499,Hanging scroll; ink and color on silk,Image: 68 x 39 in. (172.7 x 99.1 cm) Overall with mounting: 116 1/4 x 40 1/4 in. (295.3 x 102.2 cm) Overall with knobs: 116 1/4 x 44 1/2 in. (295.3 x 113 cm),"Ex coll.: C. C. Wang Family, Gift of Oscar L. Tang Family, 2005",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/39551,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -16.151,false,true,36030,Asian Art,Hanging scroll,明 沈碩 仿仇英文玉圖 軸|Lady in a Bamboo Grove after Qiu Ying,China,Ming dynasty (1368–1644),,,,Artist,,Shen Shuo,"Chinese, active 16th–17th century",,Shen Shuo,Chinese,1500,1600,,1544,1644,Hanging scroll; ink and color on silk,Image: 46 1/2 × 19 1/2 in. (118.1 × 49.5 cm) Overall with mounting: 9 ft. 4 in. × 27 5/8 in. (284.5 × 70.2 cm) Overall with knobs: 9 ft. 4 in. × 31 3/4 in. (284.5 × 80.6 cm),"Gift of John C. Ferguson, 1916",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/36030,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -13.100.93,false,true,36002,Asian Art,Folding fan mounted as an album leaf,,China,Qing dynasty (1644–1911),,,,Artist,,Wu Shantao,1609–1690,,Wu Shantao,Chinese,1609,1690,,1644,1690,Folding fan mounted as an album leaf; ink on gold paper,6 1/4 x 18 5/8 in. (15.9 x 47.3 cm),"John Stewart Kennedy Fund, 1913",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/36002,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -13.100.89,false,true,36000,Asian Art,Folding fan mounted as an album leaf,,China,Qing dynasty (1644–1911),,,,Artist,,Zhuang Jiongsheng,"Chinese, born 1626",,Zhuang Jiongsheng,Chinese,1626,1726,,1644,1676,Folding fan mounted as an album leaf; ink on gold paper,6 3/8 x 19 3/4 in. (16.2 x 50.2 cm),"John Stewart Kennedy Fund, 1913",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/36000,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.267.82,false,true,36188,Asian Art,Folding fan mounted as an album leaf,近代 吳石僊 春江煙雨 扇面|Misty Rain on the River in Spring,China,Qing dynasty (1644–1911),,,,Artist,,Wu Shixian,"Chinese, died 1916",,Wu Shixian,Chinese,,1916,,1816,1916,Folding fan mounted as an album leaf; ink on gold-flecked paper,7 1/8 x 20 1/4 in. (18.1 x 51.4 cm),"Gift of Robert Hatfield Ellsworth, in memory of La Ferne Hatfield Ellsworth, 1986",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/36188,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -69.242.7,false,true,49130,Asian Art,Hanging scroll,清 傳查士標 泛棹圖 軸|Old Man Boating on a River,China,Qing dynasty (1644–1911),,,,Artist,,Zha Shibiao,"Chinese, 1615–1698",,Zha Shibiao,Chinese,1615,1698,,1644,1698,Hanging scroll; ink on paper,69 x 26 7/8 in. (175.3 x 68.3 cm),"The Sackler Fund, 1969",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49130,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.142a–l,false,true,39931,Asian Art,Album,清 戴本孝 山水圖 冊 紙本|Landscapes,China,Qing dynasty (1644–1911),,,,Artist,,Dai Benxiao,"Chinese, 1621–1693",,DAI BENXIAO,Chinese,1621,1693,,1621,1691,Album of twelve leaves; ink on paper,Each: 8 7/16 × 6 9/16 in. (21.4 × 16.7 cm),"Purchase, The Dillon Fund Gift, 1989",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/39931,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1981.285.5,false,true,36134,Asian Art,Folding fan mounted as an album leaf,,China,Qing dynasty (1644–1911),,,,Artist,,Mei Qing,"Chinese, 1623–1697",,MEI QING,Chinese,1623,1697,,1623,1697,Folding fan mounted as an album leaf; ink and color on gold paper,Image (each leaf): 7 1/4 x 20 1/16 in. (18.4 x 51 cm),"Edward Elliott Family Collection, Purchase, The Dillon Fund Gift, 1981",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/36134,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -54.34a–l,false,true,51803,Asian Art,Album,,China,Qing dynasty (1644–1911),,,,Artist,Attributed to,Bada Shanren (Zhu Da),"Chinese, 1626–1705",,Bada Shanren (Zhu Da),Chinese,1626,1705,,1644,1705,Album of twelve paintings; ink wash on paper,Image: 7 3/4 in. × 6 in. (19.7 × 15.2 cm) Sheet: 11 in. × 7 1/4 in. (27.9 × 18.4 cm) double leaf: 11 × 14 5/8 in. (27.9 × 37.1 cm),"Seymour Fund, 1954",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/51803,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.140,false,true,49148,Asian Art,Folding fan mounted as an album leaf,,China,Qing dynasty (1644–1911),,,,Artist,,Wang Wu,"Chinese, 1632–1690",,Wang Wu,Chinese,1632,1690,,1632,1690,Folding fan mounted as an album leaf; ink and color on paper,6 3/4 x 20 1/8 in. (17.1 x 51.1 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49148,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1981.285.6,false,true,41485,Asian Art,Hanging scroll,清 吳歷 倣王蒙溪山行旅圖 軸|Travelers Among Streams and Mountains,China,Qing dynasty (1644–1911),,,,Artist,,Wu Li,"Chinese, 1632–1718",,WU LI,Chinese,1632,1718,,1644,1718,Hanging scroll; ink on paper,Image: 23 1/4 x 10 5/8 in. (59.1 x 27 cm) Overall with mounting: 73 1/2 x 18 in. (186.7 x 45.7 cm) Overall with knobs: 73 1/2 x 21 1/4 in. (186.7 x 54 cm),"Ex coll.: C. C. Wang Family, Edward Elliott Family Collection, Purchase, The Dillon Fund Gift, 1981",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/41485,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.16,false,true,49165,Asian Art,Hanging scroll,,China,Qing dynasty (1644–1911),,,,Artist,After,Yun Shouping,"Chinese, 1633–1690",,YUN SHOUPING,Chinese,1633,1690,,1644,1690,Hanging scroll; ink and color on silk,Image: 69 5/8 x 35 in. (176.8 x 88.9 cm) Overall with mounting: 121 1/2 x 43 in. (308.6 x 109.2 cm) Overall with knobs: 121 1/2 x 46 1/2 in. (308.6 x 118.1 cm),"Gift of Mr. and Mrs. Earl Morse, 1972",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49165,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -13.220.35,false,true,36016,Asian Art,Hanging scroll,,China,Qing dynasty (1644–1911),,,,Artist,Attributed to,Yun Shouping,"Chinese, 1633–1690",,YUN SHOUPING,Chinese,1633,1690,,1644,1690,Hanging scroll; ink on paper,38 7/8 x 13 1/8 in. (98.7 x 33.3 cm),"John Stewart Kennedy Fund, 1913",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/36016,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.278.5,false,true,52225,Asian Art,Hanging scroll,,China,Qing dynasty (1644–1911),,,,Artist,,Yun Shouping,"Chinese, 1633–1690",,YUN SHOUPING,Chinese,1633,1690,,1644,1690,Hanging scroll; ink and color on silk,Image: 52 3/4 x 25 1/8 in. (134 x 63.8 cm) Overall with mounting: 88 3/8 x 31 1/4 in. (224.5 x 79.4 cm) Overall with rollers: 88 3/8 x 34 1/2 in. (224.5 x 87.6 cm),"Gift of Mr. and Mrs. Earl Morse, 1972",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/52225,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.148,false,true,49164,Asian Art,Folding fan mounted as an album leaf,,China,Qing dynasty (1644–1911),,,,Artist,,Yun Shouping,"Chinese, 1633–1690",,YUN SHOUPING,Chinese,1633,1690,,1644,1690,Folding fan mounted as an album leaf; color on paper,6 11/16 x 20 in. (17 x 50.8 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49164,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.191,false,true,36120,Asian Art,Hanging scroll,清 陳字 掃象圖 軸|Washing the White Elephant,China,Qing dynasty (1644–1911),,,,Artist,,Chen Zi,"Chinese, 1634–1711",,Chen Zi,Chinese,1634,1711,,1644,1711,Hanging scroll; ink on paper,Image: 34 5/16 x 15 1/16 in. (87.2 x 38.3 cm) Overall with mounting: 86 3/4 x 23 in. (220.3 x 58.4 cm) Overall with knobs: 86 3/4 x 27 1/4 in. (220.3 x 69.2 cm),"Purchase, The Dillon Fund Gift, 1976",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/36120,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1980.426.4,false,true,49182,Asian Art,Hanging scroll,清 石濤(朱若極) 廬山草堂圖 軸|Hermitage in Mount Lu,China,Qing dynasty (1644–1911),,,,Artist,,Shitao (Zhu Ruoji),"Chinese, 1642–1707",,Shitao,Chinese,1642,1707,,1644,1707,Hanging scroll; ink on paper,Image: 37 5/16 x 19 11/16 in. (94.8 x 50 cm) Overall with mounting: 76 1/4 x 26 in. (193.7 x 66 cm) Overall with knobs: 76 1/4 x 29 1/2 in. (193.7 x 74.9 cm),"Gift of Douglas Dillon, 1980",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49182,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.155a–h,false,true,49180,Asian Art,Album,清 石濤(朱若極) 四季山水圖 冊|Landscapes of the Four Seasons,China,Qing dynasty (1644–1911),,,,Artist,,Shitao (Zhu Ruoji),"Chinese, 1642–1707",,Shitao,Chinese,1642,1707,,1644,1707,Album of eight leaves; ink and color on paper,8 1/4 x 12 3/8 in. (21 x 31.4 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49180,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.159,false,true,49238,Asian Art,Hanging scroll,清 高鳳翰 菊石圖 軸|Chrysanthemums by a Rock,China,Qing dynasty (1644–1911),,,,Artist,,Gao Fenghan,"Chinese, 1683–1749",,Gao Fenghan,Chinese,1683,1749,,1683,1749,Hanging scroll; ink and color on paper,Image: 45 1/4 x 21 1/2 in. (114.9 x 54.6 cm) Overall with mounting: 76 x 27 1/2 in. (193 x 69.9 cm) Overall with knobs: 76 x 31 3/4 in. (193 x 80.6 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49238,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.278.8,false,true,36102,Asian Art,Hanging scroll,清 方士庶 山水圖 軸|Landscape,China,Qing dynasty (1644–1911),,,,Artist,,Fang Shishu,"Chinese, 1692–1751",,Fang Shishu,Chinese,1692,1751,,1692,1751,Hanging scroll; ink and color on paper,Image: 26 7/8 x 18 1/4 in. (68.3 x 46.4 cm) Overall with mounting: 95 7/8 x 24 7/8 in. (243.5 x 63.2 cm) Overall with knobs: 95 7/8 x 29 in. (243.5 x 73.7 cm),"Gift of Mr. and Mrs. Earl Morse, 1972",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/36102,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -68.212.2,false,true,36096,Asian Art,Hanging scroll,,China,Qing dynasty (1644–1911),,,,Artist,After,Zheng Xie,"Chinese, 1693–1765",,Zheng Xie,Chinese,1693,1765,,1693,1911,Hanging scroll; ink on paper,67 1/8 x 17 15/16 in. (170.5 x 45.6 cm),"The C. C. Wang Family Collection, Gift of C. C. Wang, 1968",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/36096,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1985.214.150,false,true,52946,Asian Art,Hanging scroll,,China,Qing dynasty (1644–1911),,,,Artist,,Zheng Xie,"Chinese, 1693–1765",,Zheng Xie,Chinese,1693,1765,,1693,1765,Hanging scroll; ink on paper,Image: 55 1/8 x 15 1/2 in. (140 x 39.4 cm) Overall with mounting: 86 x 21 in. (218.4 x 53.3 cm) Overall with knobs: 85 x 24 in. (215.9 x 61 cm),"Gift of Ernest Erickson Foundation, 1985",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/52946,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -52.177.15,false,true,51790,Asian Art,Hanging scroll,,China,Qing dynasty (1644–1911),,,,Artist,,Qian Weicheng,"Chinese, 1720–1772",,Qian Weicheng,Chinese,1720,1772,,1720,1772,Hanging scroll; ink and color on paper,Image: 16 3/4 × 12 1/2 in. (42.5 × 31.8 cm) Overall with mounting: 77 1/4 × 18 3/4 in. (196.2 × 47.6 cm) Overall with knobs: 77 1/4 × 18 7/8 in. (196.2 × 47.9 cm),"Bequest of Katherine S. Dreier, 1952",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/51790,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1988.153.1a–l,false,true,44527,Asian Art,Album,清 錢維城 景數四氣,冬景圖 冊|Winter Landscapes and Flowers,China,Qing dynasty (1644–1911),,,,Artist,,Qian Weicheng,"Chinese, 1720–1772",,Qian Weicheng,Chinese,1720,1772,,1720,1772,Album of twelve paintings; ink and color on paper,Image (each): 8 5/8 × 11 7/8 in. (21.9 × 30.2 cm),"Purchase, The Dillon Fund Gift, 1988",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/44527,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -13.220.118,false,true,36026,Asian Art,Hanging scroll,,China,Qing dynasty (1644–1911),,,,Artist,After,Wang Wenzhi,"Chinese, 1730–1802",,Wang Wenzhi,Chinese,1730,1802,,1644,1911,Hanging scroll; ink and color on silk,17 1/4 x 19 1/2 in. (43.8 x 49.5 cm),"John Stewart Kennedy Fund, 1913",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/36026,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -50.131.1–.9,false,true,42559,Asian Art,Album,,China,Qing dynasty (1644–1911),,,,Artist,,Prince Yongxing,"Chinese, 1752–1823",,Yongxing Prince,Chinese,1752,1823,,1752,1823,Album with nine paintings and nine leaves of calligraphy; ink and color on heavy paper,Each leaf: 4 7/8 x 3 7/8 in. (12.4 x 9.8 cm),"Anonymous Gift, 1950",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/42559,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.267.4,false,true,36147,Asian Art,Hanging scroll,清 改琦 插花圖 軸|Girl Arranging Flowers,China,Qing dynasty (1644–1911),,,,Artist,,Gai Qi,"Chinese, 1773–1828",,Gai Qi,Chinese,1773,1828,,1774,1829,Hanging scroll; ink and color on silk,40 7/8 x 13 1/8 in. (103.8 x 33.3 cm),"Gift of Robert Hatfield Ellsworth, in memory of La Ferne Hatfield Ellsworth, 1986",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/36147,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.267.39,false,true,49457,Asian Art,Fan mounted as an album leaf,清 張熊 蟾蜍 團扇|Flower and Toad,China,Qing dynasty (1644–1911),,,,Artist,,Zhang Xiong,"Chinese, 1803–1886",,Zhang Xiong,Chinese,1803,1886,,1803,1886,Circular fan-shaped album leaf; ink and color on silk,Diam. 10 1/2 in. (26.7 cm),"Gift of Robert Hatfield Ellsworth, in memory of La Ferne Hatfield Ellsworth, 1986",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49457,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.267.9,false,true,49433,Asian Art,Hanging scroll,清 蘇仁山 李凝陽像 軸|The Immortal Li Tieguai,China,Qing dynasty (1644–1911),,,,Artist,,Su Renshan,"Chinese, 1814–1849",,Su Renshan,Chinese,1814,1850,,1814,1849,Hanging scroll; ink on paper,45 1/2 x 15 3/4 in. (115.6 x 40 cm),"Gift of Robert Hatfield Ellsworth, in memory of La Ferne Hatfield Ellsworth, 1986",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49433,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.267.32,false,true,49455,Asian Art,Folding fan mounted as an album leaf,清 胡遠 山水 扇面|Landscape,China,Qing dynasty (1644–1911),,,,Artist,,Hu Yuan,"Chinese, 1823–1886",,Hu Yuan,Chinese,1823,1886,,1823,1886,Folding fan mounted as an album leaf; ink and color on gold-flecked paper,7 1/8 x 20 1/4 in. (18.1 x 51.4 cm),"Gift of Robert Hatfield Ellsworth, in memory of La Ferne Hatfield Ellsworth, 1986",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49455,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.267.33,false,true,49456,Asian Art,Folding fan mounted as an album leaf,清 胡遠 芍藥 扇面|Herbaceous Peony,China,Qing dynasty (1644–1911),,,,Artist,,Hu Yuan,"Chinese, 1823–1886",,Hu Yuan,Chinese,1823,1886,,1823,1886,Folding fan mounted as an album leaf; ink and color on gold paper,7 1/8 x 20 3/4 in. (18.1 x 52.7 cm),"Gift of Robert Hatfield Ellsworth, in memory of La Ferne Hatfield Ellsworth, 1986",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49456,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.267.73a–h,false,true,36184,Asian Art,Album,清 居廉 花卉蟲草 冊頁 八開|Insects and Flowers,China,Qing dynasty (1644–1911),,,,Artist,,Ju Lian,"Chinese, 1828–1904",,Ju Lian,Chinese,1828,1904,,1828,1904,Album of eight leaves; ink and color on paper,12 3/8 x 14 1/4 in. (31.4 x 36.2 cm),"Gift of Robert Hatfield Ellsworth, in memory of La Ferne Hatfield Ellsworth, 1986",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/36184,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.267.74,false,true,49472,Asian Art,Folding fan mounted as an album leaf,清 吳大澂 山水 扇面|Fragrant Mountains,China,Qing dynasty (1644–1911),,,,Artist,,Wu Dacheng,"Chinese, 1835–1902",,Wu Dacheng,Chinese,1835,1902,,1835,1902,Folding fan mounted as an album leaf; ink on alum paper,6 7/8 x 20 7/8 in. (17.5 x 53 cm),"Gift of Robert Hatfield Ellsworth, in memory of La Ferne Hatfield Ellsworth, 1986",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49472,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.267.56,false,true,36172,Asian Art,Fan mounted as an album leaf,清 舒浩 周敦頤愛蓮圖 團扇|Admiring Lotus,China,Qing dynasty (1644–1911),,,,Artist,,Shu Hao,"Chinese, 1850–1899",,Shu Hao,Chinese,1850,1899,,1867,1899,Circular fan-shaped album leaf; ink and color on silk,Diam. 10 1/4 in. (26 cm),"Gift of Robert Hatfield Ellsworth, in memory of La Ferne Hatfield Ellsworth, 1986",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/36172,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.267.71,false,true,49471,Asian Art,Hanging scroll,清 任預 石室參禪圖 軸|Meditation in a Cave,China,Qing dynasty (1644–1911),,,,Artist,,Ren Yu,"Chinese, 1853–1901",,REN YU,Chinese,1853,1901,,1853,1901,Hanging scroll; ink and color on paper,44 7/8 x 15 3/8 in. (114.0 x 39.1 cm),"Gift of Robert Hatfield Ellsworth, in memory of La Ferne Hatfield Ellsworth, 1986",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49471,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.162,false,true,49242,Asian Art,Hanging scroll,,China,Qing dynasty (1644–1911),,,,Artist,,Li Shan,"Chinese, 1686–ca. 1756",,Li Shan,Chinese,1686,1756,,1686,1756,Hanging scroll; ink and color on paper,Image: 53 1/8 x 13 in. (134.9 x 33 cm) Overall with mounting: 75 1/2 x 18 7/8 in. (191.8 x 47.9 cm) Overall with knobs: 75 1/2 x 22 3/8 in. (191.8 x 56.8 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49242,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.267.42,false,true,49459,Asian Art,Folding fan mounted as an album leaf,清 居巢 鳥圖 扇面|Bird,China,Qing dynasty (1644–1911),,,,Artist,,Zhü Chao,"Chinese, ca. 1823–1889",,Zhü Chao,Chinese,1823,1889,,1823,1889,Folding fan mounted as an album leaf; ink and color on gold-flecked paper,7 x 20 7/8 in. (17.8 x 53 cm),"Gift of Robert Hatfield Ellsworth, in memory of La Ferne Hatfield Ellsworth, 1986",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49459,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1991.117.1,false,true,41484,Asian Art,Hanging scroll,清 王鑑 倣巨然《溪山高士圖》 軸 紙本|Lofty Scholar among Streams and Mountains after Juran,China,Qing dynasty (1644–1911),,,,Artist,,Wang Jian,"Chinese, 1609–1677 or 1688",,Wang Jian,Chinese,1609,1677,,1644,1688,Hanging scroll; ink on paper,Image: 72 x 33 in. (182.9 x 83.8 cm) Overall with mounting: 107 1/2 x 38 1/4 in. (273.1 x 97.2 cm) Overall with knobs: 107 1/2 x 42 1/2 in. (273.1 x 108 cm),"Gift of Douglas Dillon, 1991",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/41484,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.164,false,true,49236,Asian Art,Album leaf,,China,Qing dynasty (1644–1911),,,,Artist,,Bian Shoumin,"Chinese, active ca. 1729–50",,Bian Shoumin,Chinese,1729,1750,,1729,1750,Album leaf; ink on paper,8 5/8 x 18 11/16 in. (21.9 x 47.5 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49236,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -13.220.30,false,true,36014,Asian Art,Hanging scroll,清 山水圖 軸|Landscape,China,Qing dynasty (1644–1911),,,,Artist,Attributed to,Zhao Zuo,"Chinese, ca. 1570–after 1630",,Zhao Zuo,Chinese,1560,1630,,1630,1644,Hanging scroll; ink on silk,58 1/4 x 26 1/8 in. (148.0 x 66.4 cm),"John Stewart Kennedy Fund, 1913",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/36014,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1982.1.10,false,true,36139,Asian Art,Hanging scroll,,China,Qing dynasty (1644–1911),,,,Artist,,Gao Xiang,"Chinese, active ca. 1700–1730",,Gao Xiang,Chinese,1700,1730,,1700,1730,Hanging scroll; ink on paper,Image: 34 1/2 x 15 5/16 in. (87.6 x 38.9 cm) Overall with mounting: 80 7/8 x 20 3/4 in. (205.4 x 52.7 cm) Overall with knobs: 80 7/8 x 23 3/8 in. (205.4 x 59.4 cm),"Edward Elliott Family Collection, Purchase, The Dillon Fund Gift, 1982",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/36139,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -13.100.57,false,true,35981,Asian Art,Folding fan mounted as an album leaf,,China,Qing dynasty (1644–1911),,,,Artist,,Shen Hao,"Chinese, active late 17th century",,Shen Hao,Chinese,1671,1699,,1667,1699,Folding fan mounted as an album leaf; ink on gold paper,6 5/8 x 20 1/4 in. (16.8 x 51.4 cm),"John Stewart Kennedy Fund, 1913",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/35981,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -13.220.38a–l,false,true,51484,Asian Art,Album,,China,Qing dynasty (1644–1911),,,,Artist,,Cao Jian,"Chinese, active early 18th century",,Cao Jian,Chinese,1700,1733,,1700,1733,Album of twelve leaves; ink and color on paper,Overall (b–l): 10 1/2 x 11 1/2 in. (26.7 x 29.2 cm) Overall (a): 10 3/8 x 11 5/8 in. (26.4 x 29.5 cm),"John Stewart Kennedy Fund, 1913",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/51484,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1973.121.9,false,true,40080,Asian Art,Hanging scroll,元 王冕 墨梅圖 軸|Fragrant Snow at Broken Bridge,China,Yuan dynasty (1271–1368),,,,Artist,,Wang Mian,"Chinese, 1287–1359",,WANG MIAN,Chinese,1287,1359,,1287,1359,Hanging scroll; ink on silk,Image: 44 1/2 x 19 3/4 in. (113 x 50.2 cm) Overall with colophons: 68 x 19 3/4 in. (172.7 x 50.2 cm) Overall with mounting: 106 1/8 x 25 1/4 in. (269.6 x 64.1 cm) Overall with knobs: 106 1/8 x 29 1/2 in. (269.6 x 74.9 cm),"Ex coll.: C. C. Wang Family, Purchase, Gift of J. Pierpont Morgan, by exchange, 1973",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/40080,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2011.573,false,true,39549,Asian Art,Hanging scroll,"元 姚彥卿 (廷美) 雪山行旅圖 軸|요언경, 눈 덮인 산 속 나그네 중국 원|Traveling through Snow-Covered Mountains",China,Yuan dynasty (1271–1368),,,,Artist,,Yao Yanqing (Tingmei),"Chinese, ca. 1300–after 1360",,YAO YANQING (TINGMEI),Chinese,1300,1370,,1300,1368,Hanging scroll; ink on silk,Image: 38 5/8 x 21 1/4 in. (98.1 x 54 cm),"Ex coll.: C. C. Wang Family, Gift of Oscar L. Tang Family, 2011",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/39549,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.494.1,false,true,39545,Asian Art,Handscroll,元 趙蒼雲 劉晨阮肇入天台山圖 卷|Liu Chen and Ruan Zhao Entering the Tiantai Mountains,China,Yuan dynasty (1271–1368),,,,Artist,,Zhao Cangyun,"Chinese, active late 13th–early 14th century",,ZHAO CANGYUN,Chinese,1267,1333,,1271,1333,Handscroll; ink on paper,Image: 8 7/8 in. x 18 ft. 5 in. (22.5 cm x 564 cm),"Ex coll.: C. C. Wang Family, Gift of Oscar L. Tang Family, 2005",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/39545,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -13.100.47,false,true,35977,Asian Art,Folding fan mounted as an album leaf,,China,Ming dynasty (1368–1644) (?),,,,Artist,,Lu Kezheng (Ming dynasty?),,,Lu Kezheng,Chinese,1350,1650,,1368,1644,Folding fan mounted as an album leaf; ink on gold paper,6 1/2 x 20 in. (16.5 x 50.8 cm),"John Stewart Kennedy Fund, 1913",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/35977,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -49.6.13,false,true,51763,Asian Art,Album leaf,,China,Qing dynasty (?) (1644–1911),,,,Artist,Attributed to,Xü Daoguang,"Chinese, 13th century",,Xü Daoguang,Chinese,1200,1299,,1200,1299,Album leaf; black and grey wash on paper,7 x 11 5/8 in. (17.8 x 29.5 cm),"Bequest of Ellis Gray Seymour, 1948",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/51763,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1973.121.11,false,true,40087,Asian Art,Fan mounted as an album leaf,南宋 傳夏珪 澤畔疾風圖 團扇|Windswept Lakeshore,China,Southern Song dynasty (1127–1279),,,,Artist,Attributed to,Xia Gui,"Chinese, active ca. 1195–1230",,Xia Gui,Chinese,1195,1230,,1195,1230,Fan mounted as an album leaf; ink on silk,10 1/4 x 10 5/8 in. (26 x 27 cm),"Ex coll.: C. C. Wang Family, Purchase, Theodore M. Davis Collection, Bequest of Theodore M. Davis, by exchange, 1973",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/40087,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -13.100.65,false,true,35986,Asian Art,Folding fan mounted as an album leaf,明 (傳)程嘉燧 金箋水墨公鷄圖扇頁|A Rooster near Trees,China,Ming (1368–1644)–Qing (1644–1911) dynasty,,,,Artist,Attributed to,Cheng Jiasui,"Chinese, 1565–1644",,Cheng Jiasui,Chinese,1565,1644,,1368,1911,Folding fan mounted as an album leaf; ink on gold paper,6 1/2 x 20 3/8 in. (16.5 x 51.8 cm),"John Stewart Kennedy Fund, 1913",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/35986,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -47.18.7,false,true,51639,Asian Art,Handscroll,,China,Ming (1368–1644) or Qing (1644–1911) dynasty,,,,Artist,Attributed to,Shen Zhou,"Chinese, 1427–1509",,Shen Zhou,Chinese,1427,1509,,1427,1509,Handscroll; ink on paper,Image: 11 1/2 in. × 14 ft. 10 3/8 in. (29.2 × 453.1 cm),"From the Collection of A. W. Bahr, Purchase, Fletcher Fund, 1947",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/51639,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -68.212.1,false,true,51856,Asian Art,Album leaf mounted as a hanging scroll,,China,Ming (1368–1644) or Qing (1644–1911) dynasty,,,,Artist,In the style of,Shen Zhou,"Chinese, 1427–1509",,Shen Zhou,Chinese,1427,1509,,1644,1911,Album leaf mounted as a hanging scroll; ink and color on paper,60 5/8 x 23 3/4 in. (154.0 x 60.3 cm),"The C. C. Wang Family Collection, Gift of C. C. Wang, 1968",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/51856,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -69.131.9,false,true,51858,Asian Art,Album leaf,,China,Ming (1368–1644) or Qing (1644–1911) dynasty,,,,Artist,Attributed to,Shen Zhou,"Chinese, 1427–1509",,Shen Zhou,Chinese,1427,1509,,1427,1509,Album leaf; ink and color on paper,9 5/8 x 14 7/8 in. (24.4 x 37.8 cm),"Bequest of Walter Carlebach, 1969",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/51858,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -69.131.10,false,true,53601,Asian Art,Album leaf,,China,Ming (1368–1644) or Qing (1644–1911) dynasty,,,,Artist,Attributed to,Shen Zhou,"Chinese, 1427–1509",,Shen Zhou,Chinese,1427,1509,,1427,1509,Album leaf; ink and color on paper,9 9/16 x 14 3/4 in. (24.3 x 37.5 cm),"Bequest of Walter Carlebach, 1969",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/53601,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -69.131.11,false,true,53602,Asian Art,Album leaf,,China,Ming (1368–1644) or Qing (1644–1911) dynasty,,,,Artist,Attributed to,Shen Zhou,"Chinese, 1427–1509",,Shen Zhou,Chinese,1427,1509,,1427,1509,Album leaf; ink and color on paper,9 5/8 x 14 13/16 in. (24.4 x 37.6 cm),"Bequest of Walter Carlebach, 1969",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/53602,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -69.131.12,false,true,53603,Asian Art,Album leaf,,China,Ming (1368–1644) or Qing (1644–1911) dynasty,,,,Artist,Attributed to,Shen Zhou,"Chinese, 1427–1509",,Shen Zhou,Chinese,1427,1509,,1427,1509,Album leaf; ink and color on paper,9 5/8 x 14 13/16 in. (24.4 x 37.6 cm),"Bequest of Walter Carlebach, 1969",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/53603,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -46.146,false,true,42187,Asian Art,Handscroll,,China,Ming (1368–1644) or Qing (1644–1911) dynasty,,,,Artist,Attributed to,Chen Chun,"Chinese, 1483–1544",,Chen Chun,Chinese,1483,1544,,1483,1544,Handscroll; ink and color on paper,12 7/8 in. × 24 ft. 7 1/2 in. (32.7 × 750.6 cm),"Rogers Fund, 1946",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/42187,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -62.102,false,true,51850,Asian Art,Hanging scroll,,China,Ming (1368–1644) or Qing (1644–1911) dynasty,,,,Artist,Attributed to,Dong Qichang,"Chinese, 1555–1636",,Dong Qichang,Chinese,1555,1636,,1555,1636,Hanging scroll; ink on paper,Image: 45 x 19 1/2 in. (114.3 x 49.5 cm),"Gift of Mary Griggs Burke, 1962",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/51850,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -47.18.8,false,true,51640,Asian Art,Handscroll,,China,Ming (1368–1644) or Qing (1644–1911) dynasty,,,,Artist,Attributed to,Zha Shibiao,"Chinese, 1615–1698",,Zha Shibiao,Chinese,1615,1698,,1615,1698,Handscroll; ink and color on paper,Image: 11 3/8 in. × 10 ft. 10 1/4 in. (28.9 × 330.8 cm),"From the Collection of A. W. Bahr, Purchase, Fletcher Fund, 1947",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/51640,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -50.157,false,true,51768,Asian Art,Handscroll,,China,Ming (1368–1644) or Qing (1644–1911) dynasty,,,,Artist,Attributed to,Wang Yuan,"Chinese, ca. 1280–after 1349",,Wang Yuan,Chinese,1270,1359,,1280,1380,Handscroll; ink and color on silk,97 1/2 x 12 5/8 in. (247.7 x 32.1 cm),"Gift of A. W. Bahr, in memory of Dr. Arnold Genthe, 1950",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/51768,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -23.142.1,false,true,51546,Asian Art,Hanging scroll,,China,Song (960–1279) or Qing (1644–1911) dynasty (?),,,,Artist,In the style of,Chen Rong,active 1235–62,,Chen Rong,Chinese,1235,1262,,960,1911,"Hanging scroll, framed; ink on silk",63 x 38 1/2 in. (160 x 97.8 cm),"Fletcher Fund, 1923",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/51546,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.126,false,true,49055,Asian Art,Folding fan mounted as an album leaf,,China,late Ming (1368–1644)– early Qing (1644–1911) dynasty,,,,Artist,,Chen Hongshou,"Chinese, 1599–1652",,Chen Hongshou,Chinese,1599,1652,,1599,1652,Folding fan mounted as an album leaf; ink and mineral color on gold paper,8 x 22 in. (20.3 x 55.9 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49055,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -42.200.5,false,true,51604,Asian Art,Album leaf,,China,Ming dynasty (?) (1368–1644),,,,Artist,,Wen Liang,"Chinese, active 15th century",,Wen Liang,Chinese,1400,1499,15th century,1400,1499,Miniature from album of eleven paintings; ink and color on silk,Image: 5 x 6 3/4 in. (12.7 x 17.1 cm),"Rogers Fund, 1942",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/51604,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -13.100.48,false,true,35978,Asian Art,Folding fan mounted as an album leaf,,China,Ming dynasty (1368–1644),,,,Artist,,Wen Zhongyi,"Chinese, active mid–16th century",,Wen Zhongyi,Chinese,1500,1599,16th century,1534,1566,Folding fan mounted as an album leaf; ink on paper,6 3/5 x 19 3/8 in. (16.8 x 49.2 cm),"John Stewart Kennedy Fund, 1913",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/35978,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -19.165,false,true,36035,Asian Art,Handscroll,,China,Ming dynasty (1368–1644),,,,Artist,Attributed to,Wen Tong,"Chinese, 1019–1079",,Wen Tong,Chinese,1019,1079,16th century,1500,1599,Handscroll; ink and color on silk,Image: 22 x 101 in. (55.9 x 256.5 cm),"Rogers Fund, 1919",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/36035,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.167,false,true,51584,Asian Art,Handscroll,明 佚名 蘇蕙璇璣圖 卷|Lady Su Hui and Her Verse Puzzle,China,Ming dynasty (1368–1644),,,,Artist,In the style of,Qiu Ying,"Chinese, ca. 1495–1552",,Qiu Ying,Chinese,1485,1562,16th century,1500,1599,Handscroll; ink and color on silk,Image: 10 1/16 in. x 10 ft. 2 3/4 in. (25.6 x 311.8 cm),"Gift of George D. Pratt, 1933",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/51584,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.235.3,false,true,44592,Asian Art,Handscroll,明 傳仇英 十六羅漢圖 卷|The Sixteen Luohans,China,Ming dynasty (1368–1644),,,,Artist,Attributed to,Qiu Ying,"Chinese, ca. 1495–1552",,Qiu Ying,Chinese,1485,1562,16th century,1500,1599,Handscroll; ink on paper,Image: 13 9/16 x 206 3/8 in. (34.4 x 524.2 cm) Overall with mounting: 13 7/8 x 468 3/4 in. (35.2 x 1190.6 cm),"Gift of Douglas Dillon, 1989",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/44592,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.235.4,false,true,44593,Asian Art,Handscroll,明 傳仇英 五星二十八宿神形圖 卷|Divinities of the Planets and Constellations,China,Ming dynasty (1368–1644),,,,Artist,Attributed to,Qiu Ying,"Chinese, ca. 1495–1552",,Qiu Ying,Chinese,1485,1562,16th century,1500,1599,Handscroll; ink and color on paper,Image: 7 9/16 x 158 1/4 in. (19.2 x 402 cm) Overall with mounting: 9 3/4 x 376 15/16 in. (24.8 x 957.4 cm),"Gift of Douglas Dillon, 1989",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/44593,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -59.117,false,true,36084,Asian Art,Hanging scroll,明 劉世儒 月下雪梅圖 軸|Flowering Plum in Moonlight and Snow,China,Ming dynasty (1368–1644),,,,Artist,,Liu Shiru,"Chinese, active 1550–1600",,Liu Shiru,Chinese,1550,1600,16th century,1550,1599,Hanging scroll; ink on silk,Image: 60 x 35 5/16 in. (152.4 x 89.7 cm),"Gift of Alan Priest, 1959",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/36084,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1990.6,false,true,44612,Asian Art,Hanging scroll,明 張路 觀畫圖 軸|Studying a Painting,China,Ming dynasty (1368–1644),,,,Artist,,Zhang Lu,"Chinese, ca. 1490–ca. 1563",,Zhang Lu,Chinese,1490,1563,16th century,1500,1563,Hanging scroll; ink and color on silk,Image: 58 5/8 x 38 7/8 in. (148.9 x 98.7 cm) Overall with mounting: 103 x 42 1/8 in. (261.6 x 107 cm) Overall with knobs: 103 x 46 1/4 in. (261.6 x 117.5 cm),"Ex coll.: C. C. Wang Family, Purchase, Bequest of Dorothy Graham Bennett, 1990",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/44612,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -56.115,false,true,51816,Asian Art,Album,,China,late Ming (1368–1644)–early Qing (1644–1911) dynasty,,,,Artist,,Zhang Ruitu,"Chinese, 1570–1641",,Zhang Ruitu,Chinese,1570,1641,17th century,1600,1641,Album of sixteen calligraphies; ink on tan-coated paper,Overall (leaves): 11 3/8 x 7 1/2 in. (28.9 x 19.1 cm) Overall (wood covers): 14 1/2 x 9 1/4 in. (36.8 x 23.5 cm),"Gift of George G. Cobean, 1956",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/51816,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.235.2a–u,false,true,44591,Asian Art,Album,清 王鑑 仿古山水圖 冊 紙本|Landscapes in the styles of ancient masters,China,Qing dynasty (1644–1911),,,,Artist,,Wang Jian,"Chinese, 1609–1677 or 1688",,Wang Jian,Chinese,1609,1677,17th century,1600,1699,Album of eighteen leaves; ink and color on paper,11 3/4 x 12 3/8 in. (29.8 x 31.4 cm),"Edward Elliott Family Collection, Gift of Douglas Dillon Gift, 1989",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/44591,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1983.412.1,false,true,49167,Asian Art,Album leaf,,China,Qing dynasty (1644–1911),,,,Artist,,Yun Bing,"Chinese, active late 17th– early 18th century",,Yun Bing,Chinese,1650,1750,17th century,1644,1699,Album leaf; ink and color on paper,12 1/4 x 13 1/2 in. (31.1 x 34.3 cm),"Purchase, Bequest of Dorothy Graham Bennett, 1983",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49167,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1983.412.2,false,true,49168,Asian Art,Album leaf,,China,Qing dynasty (1644–1911),,,,Artist,,Yun Bing,"Chinese, active late 17th– early 18th century",,Yun Bing,Chinese,1650,1750,17th century,1667,1699,Album leaf; ink and color on paper,12 1/4 x 13 1/2 in. (31.1 x 34.3 cm),"Purchase, Bequest of Dorothy Graham Bennett, 1983",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49168,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"1978.23a, b",false,true,36124,Asian Art,Hanging scrolls,,China,Qing dynasty (1644–1911),,,,Artist,,Liang Tongshu,"Chinese, 1723–1815",,Liang Tongshu,Chinese,1723,1815,18th century,1723,1799,Pair of hanging scrolls; ink on paper,Image (Each): 51 1/4 x 10 1/8 in. (130.2 x 25.7 cm) Overall with mounting (Each): 65 1/4 x 13 in. (165.7 x 33 cm),"Gift of Chan-hua Mao, 1978",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/36124,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.99.1a–d,false,true,40017,Asian Art,Inkstone,清 十八世紀顧二娘款鳳紋硯|Inkstone with phoenix design,China,Qing dynasty (1644–1911),,,,Artist,Attributed to,Gu Erniang,"Chinese, active early 18th century",,Gu Erniang,Chinese,1700,1733,18th century,1700,1799,Limestone,L. 5 1/16 in. (12.9 cm); W. 3 3/4 in. (9.5 cm),"Gift of Lily and Baird Hastings, 1989",,,,,,,,,,,,Inkstone,,http://www.metmuseum.org/art/collection/search/40017,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1984.472,false,true,42324,Asian Art,Vase,,China,Qing dynasty (1644–1911),,,,Artist,,Chen Jinhou,"Chinese, active 18th century",,Chen Jinhou,Chinese,1700,1799,18th century,1700,1799,Stoneware with relief decoration (Yixing ware),H. 10 1/8 in. (25.7 cm),"Gift of Michael Abraham, 1984",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/42324,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.149,false,true,49166,Asian Art,Handscroll,,China,Qing dynasty (1644–1911),,,,Artist,After,Yun Shouping,"Chinese, 1633–1690",,YUN SHOUPING,Chinese,1633,1690,18th century,1700,1799,Handscroll; ink and color on silk,16 1/2 x 255 1/2 in. (41.9 x 649 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49166,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1995.421,false,true,44294,Asian Art,Table screen,"大理石插屏|Table Screen, now converted to a wall panel",China,Qing dynasty (1644–1911),,,,Artist,,Ruan Yuan,"Chinese, 1764–1849",,Ruan Yuan,Chinese,1764,1849,19th century,1800,1899,Marble mounted in wooden frame,11 3/8 x 15 3/4 in. (28.9 x 40 cm),"Purchase, Judith G. and F Randall Smith Gift, 1995",,,,,,,,,,,,Furniture,,http://www.metmuseum.org/art/collection/search/44294,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.267.7a–h,false,true,49432,Asian Art,Album,清 潘思牧 山水 冊頁八開|Landscapes,China,Qing dynasty (1644–1911),,,,Artist,,Pan Simu,"Chinese, 1756–1842",,Pan Simu,Chinese,1756,1842,19th century,1800,1842,Album of eight leaves; ink and color on paper,10 7/8 x 13 3/4 in. (27.6 x 34.9 cm),"Gift of Robert Hatfield Ellsworth, in memory of La Ferne Hatfield Ellsworth, 1986",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49432,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.267.20,false,true,49448,Asian Art,Folding fan mounted as an album leaf,清 吳熙載 杏花 扇面|Apricot,China,Qing dynasty (1644–1911),,,,Artist,,Wu Xizai,"Chinese, 1799–1870",,Wu Xizai,Chinese,1799,1870,19th century,1800,1870,Folding fan mounted as an album leaf; ink and color on alum paper,7 3/8 x 21 1/2 in. (18.7 x 54.6 cm),"Gift of Robert Hatfield Ellsworth, in memory of La Ferne Hatfield Ellsworth, 1986",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49448,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.267.15,false,true,36154,Asian Art,Folding fan mounted as an album leaf,清 汪廷儒 山水 扇頁|Landscape,China,Qing dynasty (1644–1911),,,,Artist,,Wang Tingru,"Chinese, 1804–1852",,Wang Tingru,Chinese,1804,1852,19th century,1804,1852,Folding fan mounted as an album leaf; ink and color on gold-flecked paper,6 1/8 x 20 5/8 in. (15.6 x 52.4 cm),"Gift of Robert Hatfield Ellsworth, in memory of La Ferne Hatfield Ellsworth, 1986",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/36154,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.267.18,false,true,49447,Asian Art,Folding fan mounted as an album leaf,清 任熊 牡丹 扇面|Peony,China,Qing dynasty (1644–1911),,,,Artist,,Ren Xiong,"Chinese, 1823–1857",,Ren Xiong,Chinese,1823,1857,19th century,1823,1857,Folding fan mounted as an album leaf; ink and color on gold-flecked paper,6 15/16 x 21 in. (17.6 x 53.3 cm),"Gift of Robert Hatfield Ellsworth, in memory of La Ferne Hatfield Ellsworth, 1986",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49447,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.267.52,false,true,49467,Asian Art,Hanging scroll,清 虛谷 蝶貓圖 軸|Cat and Butterfly,China,Qing dynasty (1644–1911),,,,Artist,,Xu Gu,"Chinese, 1823–1896",,Xu Gu,Chinese,1823,1896,19th century,1823,1896,Hanging scroll; ink and color on paper,52 1/2 x 25 3/4 in. (133.4 x 65.4 cm),"Gift of Robert Hatfield Ellsworth, in memory of La Ferne Hatfield Ellsworth, 1986",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49467,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.267.47a–h,false,true,36170,Asian Art,Album,"清 任頤 動物花鳥 冊頁八開|Animals, Flowers and Birds",China,Qing dynasty (1644–1911),,,,Artist,,Ren Yi (Ren Bonian),"Chinese, 1840–1896",,Ren Yi (Ren Bonian),Chinese,1840,1896,19th century,1840,1896,Album of eight leaves; ink and color on paper,10 3/16 x 12 3/4 in. (25.9 x 32.4 cm),"Gift of Robert Hatfield Ellsworth, in memory of La Ferne Hatfield Ellsworth, 1986",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/36170,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -56.129.3,false,true,36075,Asian Art,Hanging scroll,,China,Qing dynasty (1644–1911),,,,Artist,,Zhu Ling,"Chinese, active ca. 1820–1850",,Zhu Ling,Chinese,1820,1850,19th century,1820,1850,Hanging scroll; ink and color on paper,36 7/8 x 14 1/2 in. (93.7 x 36.8 cm),"Rogers Fund, 1956",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/36075,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.267.43a–g,false,true,36168,Asian Art,Album,清 杜湘 山水 冊頁七開|Landscapes,China,Qing dynasty (1644–1911),,,,Artist,,Du Xiang,"Chinese, active late 19th century",,Du Xiang,Chinese,1850,1899,19th century,1867,1899,Album of seven leaves; ink and color on paper,10 1/8 x 6 3/8 in. (25.7 x 16.2 cm),"Gift of Robert Hatfield Ellsworth, in memory of La Ferne Hatfield Ellsworth, 1986",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/36168,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.201,false,true,51898,Asian Art,Hanging scroll,,China,,,,,Artist|Artist,After,Zheng Xie|Unidentified Artist,"Chinese, 1693–1765",,Zheng Xie|Unidentified Artist,Chinese,1693,1765,19th–20th century,1800,1999,Hanging scroll; ink on paper,Image: 53 7/16 x 26 in. (135.7 x 66 cm) Overall: 53 3/8 x 26in. (135.6 x 66cm) Overall with knobs: 84 1/4 x 34 5/8 in. (214 x 87.9 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/51898,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.7,false,true,40059,Asian Art,Fan mounted as an album leaf,南宋 傳高宗/孝宗 行楷書輕舠依岸七絕詩 團扇|Quatrain on fishermen,China,Song dynasty (960–1279),,,,Artist|Artist,Attributed to,Emperor Gaozong|Emperor Xiaozong,"Chinese, 1107–1187, r. 1127–1162|Chinese, 1127–1194; r. 1163–89",or,Gaozong Emperor|Xiaozong Emperor,Chinese|Chinese,1107 |1127,1187 |1194,12th century,1107,1187,Fan mounted as album leaf; ink on silk,9 1/4 x 10 in. (23.5 x 25.4 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/40059,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.91,false,true,48912,Asian Art,Hanging scroll,明 傳徐渭 校靜菴文有感詩 軸|Poem Composed after Editing Jingan's Literary Works,China,Ming dynasty (1368–1644),,,,Artist|Artist,After,Xu Wei|Unidentified Artist,"Chinese, 1521–1593",,Xu Wei|Unidentified Artist,Chinese,1521,1593,dated 1576,1576,1576,Hanging scroll; ink on silk,Image: 54 3/8 x 19 in. (138.1 x 48.3 cm) Overall: 87 1/4 x 26 1/4 in. (221.6 x 66.7 cm) Overall with knobs: 87 1/4 x 28 1/2 in. (221.6 x 72.4 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/48912,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -13.100.58,false,true,35982,Asian Art,Folding fan mounted as an album leaf,,China,Ming dynasty (1368–1644),,,,Artist|Artist,After,Wang Zhideng|Unidentified Artist,"Chinese, 1535–1612",,Wang Zhideng|Unidentified Artist,Chinese,1535,1612,spurious date of 1569,1569,1644,Folding fan mounted as an album leaf; ink on paper,6 5/8 x 19 15/32 in. (16.8 x 49.5 cm),"John Stewart Kennedy Fund, 1913",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/35982,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -13.100.64,false,true,35985,Asian Art,Folding fan mounted as an album leaf,,China,Qing dynasty (1644–1911),,,,Artist|Artist,After,Wu Kuan|Unidentified Artist,"Chinese, 1435–1504",,Wu Kuan|Unidentified Artist,Chinese,1435,1504,"18th century or later, spurious date of 1492",1700,1911,Folding fan mounted as an album leaf; ink on gold paper,6 7/8 x 19 15/32 in. (17.5 x 49.5 cm),"John Stewart Kennedy Fund, 1913",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/35985,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -13.100.66,false,true,45784,Asian Art,Folding fan mounted as an album leaf,,China,Qing dynasty (1644–1911),,,,Artist|Artist,After,Wen Zhengming|Unidentified Artist,"Chinese, 1470–1559",,Wen Zhengming|Unidentified Artist,Chinese,1470,1559,17th century or later,1644,1911,Folding fan mounted as an album leaf; ink on gold-flecked paper,7 1/8 x 20 7/8 in. (18.1 x 53 cm),"John Stewart Kennedy Fund, 1913",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/45784,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -13.100.54,false,true,35980,Asian Art,Folding fan mounted as an album leaf,明/清 傳王鐸 行草書 扇頁|Calligraphy,China,Qing dynasty (1644–1911),,,,Artist|Artist,After,Wang Duo|Unidentified Artist,"Chinese, 1592–1652",,Wang Duo|Unidentified Artist,Chinese,1592,1652,"17th century or later, spurious date of 1649",1649,1911,Folding fan mounted as an album leaf; ink on paper,6 3/8 x 20 1/4 in. (16.2 x 51.4 cm),"John Stewart Kennedy Fund, 1913",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/35980,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -13.100.46,false,true,35976,Asian Art,Folding fan mounted as an album leaf,,China,Qing dynasty (1644–1911),,,,Artist|Artist,After,Zha Shibiao|Unidentified Artist,"Chinese, 1615–1698",,Zha Shibiao|Unidentified Artist,Chinese,1615,1698,18th century or later,1700,1911,Folding fan mounted as an album leaf; ink on paper,6 1/4 x 20 7/32 in. (15.9 x 51.4 cm),"John Stewart Kennedy Fund, 1913",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/35976,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1971.124,false,true,51859,Asian Art,Hanging scroll,,China,,,,,Artist|Artist,After,Hongren|Unidentified Artist,"Chinese, 1610–1664",,HONGREN|Unidentified Artist,Chinese,1610,1664,20th century,1900,1999,Hanging scroll; ink and color on paper,Image: 55 x 30 3/8 in. (139.7 x 77.2 cm) Overall with mounting: 111 x 36 7/8 in. (281.9 x 93.7 cm) Overall with rollers: 111 x 41 in. (281.9 x 104.1 cm),"Purchase, Bequest of Dorothy Graham Bennett, 1971",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/51859,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -47.81.1,false,true,36066,Asian Art,Album,,China,,,,,Artist|Artist,Formerly Attributed to,Yao Wenhan|Unidentified Artist,"Chinese, active ca. 1760–1790","Chinese, early 20th century (?)",Yao Wenhan|Unidentified Artist,Chinese,1760,1790,early 20th century (?),1900,1933,Album of twenty-four leaves; ink and color on silk,11 1/8 x 8 3/4 in. (28.3 x 22.2 cm),"Gift of Mrs. Edward S. Harkness, 1947",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/36066,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -47.81.2,false,true,49252,Asian Art,Album,,China,,,,,Artist|Artist,Formerly Attributed to,Unidentified Artist|Yao Wenhan,"Chinese, active ca. 1760–1790","Chinese, early 20th century (?)",Unidentified Artist|Yao Wenhan,Chinese,1760,1790,early 20th century (?),1900,1933,Album of twenty leaves; ink and color on silk,11 1/8 x 8 3/4 in. (28.3 x 22.2 cm),"Gift of Mrs. Edward S. Harkness, 1947",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49252,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -47.18.34,false,true,51657,Asian Art,Album leaf,,China,20th century,,,,Artist|Artist,In the style of,Unidentified Artist|Cui Bai,"Chinese, active ca. 1040–70",,Unidentified Artist|Cui Bai,Chinese,1030,1080,20th century,1900,1947,Album leaf; ink and color on silk,12 3/8 x 10 1/8 in. (31.4 x 25.7 cm),"From the Collection of A. W. Bahr, Purchase, Fletcher Fund, 1947",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/51657,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.121,false,true,40003,Asian Art,Hanging scroll,宋 佚名 倣李成 寒林策驢圖 軸|Travelers in a Wintry Forest,China,Song dynasty (960–1279),,,,Artist|Artist,Traditionally attributed to,Li Cheng|Unidentified Artist,"Chinese, 919–967","Chinese, active early 12th century",LI CHENG|Unidentified Artist,Chinese,0919,0967,early 12th century,1100,1133,Hanging scroll; ink and color on silk,Image: 63 3/4 × 39 1/2 in. (161.9 × 100.3 cm) Overall with mounting: 10 ft. 3 1/2 in. × 49 in. (313.7 × 124.5 cm) Overall with knobs: 10 ft. 3 1/2 in. × 53 3/8 in. (313.7 × 135.6 cm),"Purchase, Fletcher Fund and Bequest of Dorothy Graham Bennett, 1972",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/40003,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1983.12,false,true,40106,Asian Art,Handscroll,南宋 佚名 倣燕文貴 秋山蕭寺圖 卷|Buddhist Temples amid Autumn Mountains,China,Song dynasty (960–1279),,,,Artist|Artist,After,Yan Wengui|Unidentified Artist,"Chinese, 970–1030",,YAN WENGUI|Unidentified Artist,Chinese,0970,1030,late 12th–mid-13th century,1167,1266,Handscroll; ink and pale color on silk,Image: 12 7/8 in. × 10 ft. 6 1/2 in. (32.7 × 321.3 cm) Overall with mounting: 13 1/8 in. × 37 ft. 2 1/8 in. (33.3 × 1133.2 cm),"Purchase, The Dillon Fund Gift, 1983",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/40106,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -18.124.4,false,true,36033,Asian Art,Handscroll,南宋 佚名 百牛圖 卷|One Hundred Buffaloes,China,Song dynasty (960–1279),,,,Artist|Artist,Formerly Attributed to,Unidentified Artist|Jiangcan,"Chinese, active ca. 1200","Chinese, 13th century",Unidentified Artist|Jiangcan,Chinese,1190,1210,13th century,1200,1299,Handscroll; ink on paper,12 1/2 x 87 5/16 in. (31.8 x 221.8 cm),"Rogers Fund, 1918",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/36033,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1978.4,false,true,40004,Asian Art,Handscroll,宋 佚名 倣周文矩 宮中圖 卷|In the Palace,China,Song dynasty (960–1279),,,,Artist|Artist,After,Zhou Wenju|Unidentified Artist,"Chinese, active 940–975","Chinese, active early 12th century",ZHOU WENJU|Unidentified Artist,Chinese,0940,0975,before 1140,960,1139,Handscroll; ink and touches of color on silk,Image: 10 1/4 × 57 3/4 in. (26 × 146.7 cm) Overall with mounting: 10 11/16 in. × 29 ft. 11 7/8 in. (27.1 × 914.1 cm),"Purchase, Douglas Dillon Gift, 1978",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/40004,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -56.151,false,true,40002,Asian Art,Hanging scroll,北宋 佚名 倣范寬山水圖 軸|Landscape in the style of Fan Kuan,China,Song dynasty (960–1279),,,,Artist|Artist,after,Fan Kuan|Unidentified Artist,"Chinese, active ca. 990–1030","Chinese, active 12th century",Fan Kuan|Unidentified Artist,Chinese,0990,1030,early 12th century,1100,1133,Hanging scroll; ink and color on silk,Image: 65 3/8 × 41 1/8 in. (166.1 × 104.5 cm) Overall with mounting: 9 ft. 8 1/2 in. × 49 3/4 in. (295.9 × 126.4 cm) Overall with knobs: 9 ft. 8 1/2 in. × 53 3/4 in. (295.9 × 136.5 cm),"Gift of Irene and Earl Morse, 1956",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/40002,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.16,false,true,40196,Asian Art,Fan mounted as an album leaf,,China,Song dynasty (960–1279),,,,Artist|Artist,Formerly Attributed to,Li Tang|Unidentified Artist,"Chinese, ca. 1070s–ca. 1150s",,LI TANG|Unidentified Artist,Chinese,1070,1159,13th century,1200,1299,Fan mounted as an album leaf; ink and color on silk,9 7/8 x 10 1/8 in. (25.1 x 25.7 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/40196,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.15,false,true,40078,Asian Art,Fan mounted as an album leaf,,China,Song dynasty (960–1279),,,,Artist|Artist,Formerly Attributed to,Lidi|Unidentified Artist,"Chinese, ca. 1110– after 1197",,LIDI|Unidentified Artist,Chinese,1110,1197,13th century,1200,1279,Fan mounted as an album leaf; ink and color on silk,9 3/8 x 9 1/2 in. (23.8 x 24.1 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/40078,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -40.148,false,true,39935,Asian Art,Handscroll,宋 佚名 倣周昉 戲嬰圖 卷|Palace Ladies Bathing Children,China,Song dynasty (960–1279),,,,Artist|Artist,After,Zhou Fang|Unidentified Artist,"Chinese, active ca. 780– ca.810",,ZHOU FANG|Unidentified Artist,Chinese,0780,0810,11th century,1000,1099,Handscroll; ink and color on silk,Image: 12 in. × 19 1/8 in. (30.5 × 48.6 cm) Overall with mounting: 12 7/16 × 44 in. (31.6 × 111.8 cm),"Fletcher Fund, 1940",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/39935,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.64,false,true,45781,Asian Art,Handscroll,,China,Ming dynasty (1368–1644),,,,Artist|Artist,After,Wen Zhengming|Unidentified Artist,"Chinese, 1470–1559",,Wen Zhengming|Unidentified Artist,Chinese,1470,1559,dated 1549,1549,1549,Handscroll; color on paper,Image: 11 x 52 3/8 in. (27.9 x 133 cm) Overall with mounting: 11 5/16 x 240 3/4 in. (28.7 x 611.5 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45781,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.89,false,true,48933,Asian Art,Handscroll,,China,Ming dynasty (1368–1644),,,,Artist|Artist,After,Mo Shilong|Unidentified Artist,"Chinese, 1537–1587",,Mo Shilong|Unidentified Artist,Chinese,1537,1587,dated 1577,1577,1577,Handscroll; ink and color on paper,Image: 8 5/16 x 31 7/8 in. (21.1 x 81 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/48933,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -13.100.63,false,true,49020,Asian Art,Folding fan mounted as an album leaf,,China,Ming dynasty (1368–1644),,,,Artist|Artist,After,Lan Ying|Unidentified Artist,"Chinese, 1585–1664",,Lan Ying|Unidentified Artist,Chinese,1585,1664,"17th century or later, spurious date of 1633",1633,1911,Folding fan mounted as an album leaf; ink and color on gold paper,6 5/8 x 20 1/2 in. (16.8 x 52.1 cm),"John Stewart Kennedy Fund, 1913",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49020,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.123,false,true,51873,Asian Art,Hanging scroll,,China,Ming dynasty (1368–1644),,,,Artist|Artist,After,Ni Yuanlu|Unidentified Artist,"Chinese, 1593–1644",,Ni Yuanlu|Unidentified Artist,Chinese,1593,1644,dated 1638,1638,1638,Hanging scroll; ink and color on paper,Image: 67 x 28 3/4 in. (170.2 x 73 cm) Overall with mounting: 100 1/2 x 34 5/8 in. (255.3 x 87.9 cm) Overall with knobs: 100 1/2 x 38 1/2 in. (255.3 x 97.8 cm),"Bequest of John M. Crawford, Jr., 1988",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/51873,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.74,false,true,48888,Asian Art,Hanging scroll,明 文伯仁 溪山僊館圖 軸|Dwellings of the Immortals Amid Streams and Mountains,China,Ming dynasty (1368–1644),,,,Artist|Artist,After,Wen Boren|Unidentified Artist,"Chinese, 1502–ca.1575",,Wen Boren|Unidentified Artist,Chinese,1502,1575,dated 1531,1531,1531,Hanging scroll; ink and color on paper,Image: 71 x 24 7/8 in. (180.3 x 63.2 cm) Overall with mounting: 105 7/8 x 31 3/16 in. (268.9 x 79.2 cm) Overall with knobs: 105 7/8 x 35 3/4 in. (268.9 x 90.8 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/48888,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -13.100.97,false,true,44451,Asian Art,Fan mounted as an album leaf,"元/明 佚名 舊傳趙雍 倣盛懋 蘇軾後赤壁賦圖 扇|Illustration of Su Shi's ""Second Ode on the Red Cliff""",China,Ming dynasty (1368–1644),,,,Artist|Artist,In the style of,Sheng Mou|Unidentified Artist,"Chinese, active ca. 1310–1360",,Sheng Mou|Unidentified Artist,Chinese,1310,1360,late 14th–early 15th century,1367,1433,Fan mounted as an album leaf; ink and color on silk,Image: 12 3/8 x 12 3/8 in. (31.4 x 31.4 cm),"John Stewart Kennedy Fund, 1913",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/44451,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1990.54,false,true,39556,Asian Art,Handscroll,"明 沈周 , 文徵明 合璧山水圖 卷|Joint Landscape",China,Ming dynasty (1368–1644),,,,Artist|Artist,,Shen Zhou|Wen Zhengming,"Chinese, 1427–1509|Chinese, 1470–1559",,Shen Zhou|Wen Zhengming,Chinese|Chinese,1427 |1470,1509 |1559,ca. 1509 and 1546,1509,1546,Handscroll; ink on paper,"Image: 14 1/2 x 56 ft., 8 5/6 in. (36.8 x 1729.3 cm)","Purchase, The Dillon Fund Gift, 1990",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/39556,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -13.100.67,false,true,48884,Asian Art,Folding fan mounted as an album leaf,,China,Qing dynasty (1644–1911),,,,Artist|Artist,After,Wen Jia|Unidentified Artist,"Chinese, 1501–1583",,Wen Jia|Unidentified Artist,Chinese,1501,1583,17th century or later,1600,1911,Folding fan mounted as an album leaf; ink on gold paper,6 7/8 x 19 1/2 in. (17.5 x 49.5 cm),"John Stewart Kennedy Fund, 1913",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/48884,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -13.100.43,false,true,35974,Asian Art,Folding fan mounted as an album leaf,,China,Qing dynasty (1644–1911),,,,Artist|Artist,After,Wang Shimin|Unidentified Artist,"Chinese, 1592–1680",,WANG SHIMIN|Unidentified Artist,Chinese,1592,1680,"18th century or later, spurious date of 1648",1700,1911,Folding fan mounted as an album leaf; ink on gold paper,6 1/2 x 20 1/2 in. (16.5 x 52.1 cm),"John Stewart Kennedy Fund, 1913",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/35974,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.2.1a–q,false,true,42560,Asian Art,Album,清 倣黃向堅 尋親紀行圖 冊|A Journey in Search of the Artist's Parents,China,Qing dynasty (1644–1911),,,,Artist|Artist,After,Huang Xiangjian|Unidentified Artist,"Chinese, 1609–1673",,Huang Xiangjian|Unidentified Artist,Chinese,1609,1673,Dated 1656,1656,1656,Album of fourteen leaves; ink on paper,15 1/2 x 11 3/8 in. (39.4 x 28.9 cm),"The Sackler Fund, 1970",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/42560,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.1.2,false,true,49127,Asian Art,Hanging scroll,清 倣弘仁 黃山蟠龍松圖 軸|Dragon Pine on Mount Huang,China,Qing dynasty (1644–1911),,,,Artist|Artist,After,Hongren|Unidentified Artist,"Chinese, 1610–1664",,HONGREN|Unidentified Artist,Chinese,1610,1664,ca. 1660,1650,1670,Hanging scroll; ink and pale color on paper,Image: 76 1/4 x 31 in. (193.7 x 78.7 cm) Overall with mounting: 120 1/4 x 38 1/2 in. (305.4 x 97.8 cm) Overall with knobs: 120 1/4 x 41 3/4 in. (305.4 x 106 cm),"Gift of Douglas Dillon, 1976",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49127,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.185,false,true,51887,Asian Art,Handscroll,,China,Qing dynasty (1644–1911),,,,Artist|Artist,After,Hongren|Unidentified Artist,"Chinese, 1610–1664",,HONGREN|Unidentified Artist,Chinese,1610,1664,dated 1661,1661,1661,Handscroll; ink on paper,11 1/4 x 152 1/4 in. (28.6 x 386.7cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/51887,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -13.100.25,false,true,35971,Asian Art,Hanging scroll,,China,Qing dynasty (1644–1911),,,,Artist|Artist,After,Wang Hui|Unidentified Artist,"Chinese, 1632–1717",,Wang Hui|Unidentified Artist,Chinese,1632,1717,"18th century or later, spurious date of 1680",1700,1911,Hanging scroll; ink on silk,14 1/2 x 11 1/8 in. (36.8 x 28.3 cm),"John Stewart Kennedy Fund, 1913",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/35971,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -13.100.40,false,true,35972,Asian Art,Folding fan mounted as an album leaf,,China,Qing dynasty (1644–1911),,,,Artist|Artist,After,Wang Hui|Unidentified Artist,"Chinese, 1632–1717",,Wang Hui|Unidentified Artist,Chinese,1632,1717,"18th century or later, spurious date of 1706",1700,1911,Folding fan mounted as an album leaf; ink and color on paper,6 3/4 x 20 in. (17.1 x 50.8 cm),"John Stewart Kennedy Fund, 1913",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/35972,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -13.100.45,false,true,35975,Asian Art,Folding fan mounted as an album leaf,,China,Qing dynasty (1644–1911),,,,Artist|Artist,After,Wang Hui|Unidentified Artist,"Chinese, 1632–1717",,Wang Hui|Unidentified Artist,Chinese,1632,1717,"18th century or later, spurious date of 1707",1707,1911,Folding fan mounted as an album leaf; ink and color on paper,6 1/2 x 19 1/4 in. (16.5 x 48.9 cm),"John Stewart Kennedy Fund, 1913",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/35975,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.142,false,true,49152,Asian Art,Hanging scroll,,China,Qing dynasty (1644–1911),,,,Artist|Artist,After,Wang Hui|Unidentified Artist,"Chinese, 1632–1717",,Wang Hui|Unidentified Artist,Chinese,1632,1717,dated 1682,1682,1682,Hanging scroll; ink and color on paper,Image: 52 7/8 x 21 1/8 in. (134.3 x 53.7 cm) Overall with mounting: 86 1/2 x 25 1/2 in. (219.7 x 64.8 cm) Overall with knobs: 86 1/2 x 29 3/8 in. (219.7 x 74.6 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49152,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.143,false,true,49153,Asian Art,Hanging scroll,,China,Qing dynasty (1644–1911),,,,Artist|Artist,After,Wang Hui|Unidentified Artist,"Chinese, 1632–1717",,Wang Hui|Unidentified Artist,Chinese,1632,1717,dated 1686,1686,1686,Hanging scroll; ink on paper,Image: 22 1/2 x 16 in. (57.2 x 40.6 cm) Overall with mounting: 72 x 21 5/8 in. (182.9 x 54.9 cm) Overall with knobs: 72 x 25 1/4 in. (182.9 x 64.1 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49153,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -44.138,false,true,53552,Asian Art,Handscroll,,China,Qing dynasty (1644–1911),,,,Artist|Artist,In the style of,Unidentified Artist|Yun Shouping,"Chinese, 1633–1690",,Unidentified Artist|YUN SHOUPING,Chinese,1633,1690,probably 18th–19th century,1700,1899,Handscroll; ink and color on silk,11 15/16 x 102 3/8 in. (30.3 x 260 cm),"Anonymous Gift, 1944",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/53552,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -13.100.41,false,true,51369,Asian Art,Folding fan mounted as an album leaf,,China,Qing dynasty (1644–1911),,,,Artist|Artist,After,Yun Shouping|Unidentified Artist,"Chinese, 1633–1690",,YUN SHOUPING|Unidentified Artist,Chinese,1633,1690,18th century or later,1700,1911,Folding fan mounted as an album leaf; ink on paper,6 7/8 x 20 1/2 in. (17.5 x 52.1 cm),"John Stewart Kennedy Fund, 1913",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/51369,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -13.100.44,false,true,49188,Asian Art,Folding fan mounted as an album leaf,,China,Qing dynasty (1644–1911),,,,Artist|Artist,After,Wang Yuanqi|Unidentified Artist,"Chinese, 1642–1715",,WANG YUANQI|Unidentified Artist,Chinese,1642,1715,"18th century or later, spurious date of 1715",1715,1911,Folding fan mounted as an album leaf; ink on paper,6 1/2 x 19 1/2 in. (16.5 x 49.5 cm),"John Stewart Kennedy Fund, 1913",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49188,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -27.24,false,true,39913,Asian Art,Handscroll,,China,Qing dynasty (1644–1911),,,,Artist|Artist,Formerly Attributed to,Unidentified Artist|Zhao Boju,"Chinese, 11th century",Chinese|in the style of Li Longmian,Unidentified Artist|Zhao Boju,Chinese,1000,1099,18th century or later,1700,1800,Handscroll; ink and color on golden paper,10 3/4 x 41 3/4 in. (27.3 x 106 cm),"Rogers Fund, 1927",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/39913,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.198a–h,false,true,51896,Asian Art,Album,,China,Qing dynasty (1644–1911),,,,Artist|Artist,After,Unidentified Artist|Li Shan,"Chinese, 1686–ca. 1756",,Unidentified Artist|Li Shan,Chinese,1686,1756,dated 1740,1740,1740,Album of eight double leaves; ink and color on paper,Image: 10 5/8 × 13 3/16 in. (27 × 33.5 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/51896,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -13.100.42,false,true,35973,Asian Art,Folding fan mounted as an album leaf,,China,Qing dynasty (1644–1911),,,,Artist|Artist,After,Wang Jian|Unidentified Artist,"Chinese, 1609–1677 or 1688",,Wang Jian|Unidentified Artist,Chinese,1609,1677,"18th century or later, spurious date of 1676",1700,1911,Folding fan mounted as an album leaf; ink and color on paper,6 3/8 x 20 1/2 in. (16.2 x 52.1 cm),"John Stewart Kennedy Fund, 1913",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/35973,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.141.4a–rr,false,true,41481,Asian Art,Album,清 王翬 王時敏 仿古山水圖 冊 紙本|Landscapes after old masters,China,Qing dynasty (1644–1911),,,,Artist|Artist,"leaves k, l by",Wang Hui|Wang Shimin,"Chinese, 1632–1717|Chinese, 1592–1680",,Wang Hui|WANG SHIMIN,Chinese|Chinese,1632 |1592,1717 |1680,dated 1674 and 1677,1674,1677,Album of twelve leaves; ink and color on paper,"Ten paintings by Wang Hui (a–j): 8 5/8 x 13 1/4 in. (22 x 33.8 cm); two paintings by Wang Shimin (k, l): 10 x 13 in. (25.4 x 33 cm)","Purchase, The Dillon Fund Gift, 1989",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/41481,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1979.439a–o,false,true,49113,Asian Art,Album,清 王鑑 仿古山水圖 冊 紙本|Landscapes in the styles of old masters,China,Qing dynasty (1644–1911),,,,Artist|Artist,Title piece by,Wang Jian|Wang Shimin,"Chinese, 1609–1677 or 1688|Chinese, 1592–1680",,Wang Jian|WANG SHIMIN,Chinese|Chinese,1609 |1592,1677 |1680,dated 1668,1668,1668,Album of ten paintings; ink and color on paper,Each leaf: 10 1/8 x 6 1/2 in. (25.7 x 16.5 cm),"Purchase, The Dillon Fund Gift, 1979",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49113,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1987.149,false,true,36433,Asian Art,Handscroll,"清 石濤(朱若極), 張子為 忍菴居士像 卷|Portrait of Ren'an in a Landscape",China,Qing dynasty (1644–1911),,,,Artist|Artist,,Shitao (Zhu Ruoji)|Zhang Ziwei,"Chinese, 1642–1707|Chinese, active late 17th century",,Shitao|Zhang Ziwei,Chinese|Chinese,1642 |1600,1707 |1700,dated 1684,1684,1684,Handscroll; ink and color on paper,22 15/18 x 53 7/8 in. (58 x 136.8 cm),"Purchase, The Dillon Fund Gift, 1987",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/36433,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.481,false,true,51567,Asian Art,Handscroll,,China,Yuan dynasty (1271–1368),,,,Artist|Artist,In the style of,Unidentified Artist|Zhao Mengfu,"Chinese, 1254–1322",,Unidentified Artist|Zhao Mengfu,Chinese,1254,1322,dated 1309,1309,1309,Handscroll; ink and color on silk,15 3/8 in. × 13 ft. 2 1/2 in. (39.1 × 402.6 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/51567,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.476,false,true,36041,Asian Art,Hanging scroll,元 佚名 仕女像 軸|Portrait of a Lady,China,Yuan dynasty (1271–1368),,,,Artist|Artist,In the style of,Unidentified Artist|Li Gonglin,"Chinese, ca. 1041–1106",Chinese,Unidentified Artist|Li Gonglin,Chinese,1041,1106,14th century,1300,1368,Hanging scroll; ink on silk,Image: 31 × 15 in. (78.7 × 38.1 cm) Overall with mounting: 64 × 20 7/8 in. (162.6 × 53 cm) Overall with knobs: 64 × 22 3/16 in. (162.6 × 56.4 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/36041,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -47.18.49,false,true,51668,Asian Art,Handscroll,,China,Song dynasty (?) (960–1279),,,,Artist|Artist,After,Unidentified Artist|Wang Qihan,"Chinese, 10th century",,Unidentified Artist|Wang Qihan,Chinese,0900,0999,10th century,900,999,Handscroll; ink and color on silk,Image: 9 7/8 × 18 3/4 in. (25.1 × 47.6 cm),"From the Collection of A. W. Bahr, Purchase, Fletcher Fund, 1947",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/51668,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.26,false,true,40088,Asian Art,Fan mounted as an album leaf,南宋 佚名 舊傳閻次于 風雨維舟圖 團扇|Boats Moored in Wind and Rain,China,Southern Song dynasty (1127–1279),,,,Artist|Artist,Formerly Attributed to,Yan Ciyu|Unidentified Artist,"Chinese, act. ca. 1164–81","Chinese, 13th century",YAN CIYU|Unidentified Artist,Chinese,1164,1181,13th century,1200,1299,Fan mounted as an album leaf; ink and color on silk,9 3/4 x 10 3/4 in. (24.8 x 27.3 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/40088,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1982.7.3,false,true,36141,Asian Art,Fan mounted as an album leaf,南宋 佚名 倣夏珪 冒雨尋莊圖 團扇|Returning Home in a Driving Rain,China,Southern Song dynasty (1127–1279),,,,Artist|Artist,After,Xia Gui|Unidentified,"Chinese, active ca. 1195–1230",,Xia Gui|Unidentified,Chinese,1195,1230,early 13th century,1200,1233,Fan mounted as an album leaf; ink and color on silk,10 1/16 x 10 3/8 in. (25.6 x 26.4 cm),"Purchase, The Dillon Fund Gift, 1982",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/36141,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1984.274,false,true,40987,Asian Art,Handscroll,南宋 傳劉松年 倣高克明溪山雪意圖 卷|Streams and Mountains Under Fresh Snow,China,Southern Song dynasty (1127–1279),,,,Artist|Artist,Attributed to|traditionally attributed to,Liu Songnian|Gao Keming,"Chinese, active ca 1175–after 1195|ca 1000–1053",,LIU SONGNIAN|GAO KEMING,Chinese,1175 |1000,1195 |1053,ca. late 12th century,1175,1199,Handscroll; ink and color on silk,Image: 16 3/8 in. × 95 in. (41.6 × 241.3 cm) Overall with mounting: 16 3/8 in. × 42 ft. 11 5/16 in. (41.6 × 1308.9 cm),"Gift of John M. Crawford Jr., 1984",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/40987,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -13.100.73,false,true,51378,Asian Art,Folding fan mounted as an album leaf,,China,Ming (1368–1644) or Qing (1644–1911) dynasty,,,,Artist|Artist,After,Shen Zhou|Unidentified Artist,"Chinese, 1427–1509",,Shen Zhou|Unidentified Artist,Chinese,1427,1509,16th century or later,1500,1911,Folding fan mounted as an album leaf; ink on gold paper,6 7/8 x 20 3/4 in. (17.5 x 52.7 cm),"John Stewart Kennedy Fund, 1913",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/51378,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -13.220.93,false,true,51490,Asian Art,Hanging scroll,,China,Ming (1368–1644) or Qing dynasty (1644–1911),,,,Artist|Artist,Formerly Attributed to,Ni Zan|Unidentified Artist,"Chinese, 1306–1374",,NI ZAN|Unidentified Artist,Chinese,1306,1374,dated 1374,1374,1374,Hanging scroll; ink on paper,33 7/8 x 12 7/8 in. (86.0 x 32.7 cm),"John Stewart Kennedy Fund, 1913",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/51490,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -13.220.28,false,true,40422,Asian Art,Hanging scroll,,China,Ming (1368–1644) or Qing dynasty (1644–1911),,,,Artist|Artist,After,Unidentified Artist|Tang Yin,"Chinese, 1470–1524",,Unidentified Artist|Tang Yin,Chinese,1470,1524,ca. 1525,1515,1535,Hanging scroll; ink and color on paper,Image: 89 x 40 1/4 in. (226.1 x 102.2 cm) Overall with mounting: 137 3/4 x 44 in. (349.9 x 111.8 cm) Overall with knobs: 137 3/4 x 48 5/8 in. (349.9 x 123.5 cm),"John Stewart Kennedy Fund, 1913",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/40422,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -19.166,false,true,51535,Asian Art,Handscroll,,China,Ming (1368–1644) or Qing dynasty (1644–1911),,,,Artist|Artist,Formerly Attributed to,Unidentified Artist|Qiu Ying,"Chinese, ca. 1495–1552",,Unidentified Artist|Qiu Ying,Chinese,1485,1562,ca. 1530,1520,1540,Handscroll; ink and color on silk,H. 12 3/8 in. (31.4 cm),"Gift of William Hu, 1919",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/51535,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -13.100.27,false,true,51293,Asian Art,Handscroll,,China,Ming (1368–1644) or Qing dynasty (1644–1911),,,,Artist|Artist,After,Guo Xi|Unidentified Artist,"Chinese, ca. 1000–ca. 1090",,Guo Xi|Unidentified Artist,Chinese,1000,1090,possibly 17th century,1600,1699,Handscroll; ink and color on silk,19 3/8 in. × 19 ft. 1 1/2 in. (49.2 cm),"John Stewart Kennedy Fund, 1913",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/51293,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1977.87,false,true,48945,Asian Art,Handscroll,,China,late Ming (1368–1644)–early Qing (1644–1911) dynasty,,,,Artist|Artist,Formerly Attributed to,Li Zai|Unidentified Artist,"Chinese, active 15th century",,Li Zai|Unidentified Artist,Chinese,1400,1500,"Dated ""yiwei"" (1595? 1655?)",1595,1655,Handscroll; ink and color on paper,Image: 11 x 126 in. (27.9 x 320 cm) Overall with mounting: 13 1/2 x 365 5/8 in. (34.3 x 928.7 cm),"Purchase, John M. Crawford Jr. Gift, 1977",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/48945,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.20.1,false,true,48941,Asian Art,Hanging scroll,清 佚名 肖像 軸|Portrait of a Scholar,China,late Ming (1368–1644)–early Qing (1644–1911) dynasty,,,,Artist|Artist,Formerly Attributed to,Jin Chushi|Unidentified Artist,"Chinese, active late 12th century",,JIN CHUSHI|Unidentified Artist,Chinese,1167,1199,17th–18th century,1600,1799,Hanging scroll; ink and color on paper,Image: 35 3/4 in. × 15 in. (90.8 × 38.1 cm) Overall with mounting: 68 1/2 × 20 3/8 in. (174 × 51.8 cm) Overall with knobs: 68 1/2 × 22 1/16 in. (174 × 56 cm),"Gift of George D. Pratt, 1929",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/48941,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1999.521a–k,false,true,37396,Asian Art,Album,"明/清 陳洪綬、陳字 雜畫 冊 絹本|Figures, flowers, and landscapes",China,late Ming (1368–1644)–early Qing (1644–1911) dynasty,,,,Artist|Artist,leaves a–d by|leaves e–k by,Chen Hongshou|Chen Zi,"Chinese, 1599–1652|Chinese, 1634–1711",,Chen Hongshou|Chen Zi,Chinese|Chinese,1599 |1634,1652 |1711,one leaf dated 1627,1600,1711,Album of eleven leaves; ink and color on silk,Image: 8 3/4 x 8 9/16 in. (22.2 x 21.7 cm),"Gift of Mr. and Mrs. Wan-go H. C. Weng, 1999",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/37396,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -13.100.62,false,true,45814,Asian Art,Folding fan mounted as an album leaf,,China,Ming dynasty (1368–1644),,,,Artist|Artist,After,Wang Chong|Unidentified Artist,"Chinese, 1494–1533",,Wang Chong|Unidentified Artist,Chinese,1494,1533,,1368,1644,Folding fan mounted as an album leaf; ink on paper,6 3/4 x 20 in. (17.1 x 50.8 cm),"John Stewart Kennedy Fund, 1913",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/45814,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -13.100.60,false,true,35983,Asian Art,Folding fan mounted as an album leaf,,China,Ming dynasty (1368–1644),,,,Artist|Artist,After,Peng Nian|Unidentified Artist,"Chinese, 1505–1566",,Peng Nian|Unidentified Artist,Chinese,1505,1566,,1368,1644,Folding fan mounted as an album leaf; ink on paper,6 1/2 x 18 1/2 in. (16.5 x 47.0 cm),"John Stewart Kennedy Fund, 1913",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/35983,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.78,false,true,48903,Asian Art,Hanging scroll,,China,Ming dynasty (1368–1644),,,,Artist|Artist,After,Hai Rui|Unidentified Artist,"Chinese, 1514–1587",,Hai Rui|Unidentified Artist,Chinese,1514,1587,,1514,1587,Hanging scroll; ink on paper,Overall with mounting: 108 3/4 x 28 in. (276.2 x 71.1 cm) Image: 82 3/8 x 20 1/8 in. (209.2 x 51.1 cm) Overall with knobs: 108 3/4 x 31 3/4 in. (276.2 x 80.6 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/48903,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.31,false,true,40510,Asian Art,Hanging scroll,明 佚名 倣趙孟頫 草書湘簾疏織七絕詩 軸|A Summer Idyll,China,Yuan dynasty (1271–1368),,,,Artist|Artist,After,Zhao Mengfu|Unidentified Artist,"Chinese, 1254–1322",Chinese,Zhao Mengfu|Unidentified Artist,Chinese,1254,1322,,1319,1368,"Hanging scroll; ink on silk, 3 columns in large running-cursive script",Image: 52 1/2 x 20 7/8 in. (133.4 x 53 cm) Overall with mounting: 111 1/2 x 29 in. (283.2 x 73.7 cm) Overall with knobs: 111 1/2 x 32 1/4 in. (283.2 x 81.9 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/40510,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.34,false,true,51870,Asian Art,Handscroll,,China,Yuan dynasty (1271–1368),,,,Artist|Artist,Copy after,Zhang Yu|Unidentified Artist,"Chinese, 1283–1350",Chinese,ZHANG YU|Unidentified Artist,Chinese,1283,1350,,1271,1368,Handscroll; ink on paper,Image: 11 3/4 x 63 in. (29.8 x 160 cm) Overall with mounting: 12 1/16 x 192 9/16 in. (30.6 x 489.1 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/51870,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.1,false,true,51869,Asian Art,Handscroll,,China,Ming (1368–1644) or Qing (1644–1911) dynasty,,,,Artist|Artist,Formerly attributed to,Huaisu|Unidentified Artist,"Chinese, 725–785","Chinese, 16th century",Huaisu|Unidentified Artist,Chinese,0725,0785,,1368,1911,Handscroll; ink on silk,Image (1): 10 5/8 x 6 1/8 in. (27 x 15.6 cm) Image (2): 10 3/8 x 7 1/4 in. (26.4 x 18.4 cm) Image (3): 10 5/8 x 13 3/8 in. (27 x 34 cm) Overall with mounting: 11 15/16 x 229 1/8 in. (30.3 x 582 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/51869,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -13.100.80,false,true,48906,Asian Art,Folding fan mounted as an album leaf,,China,late Ming (1368–1644)–early Qing (1644–1911) dynasty,,,,Artist|Artist,After,Zhou Tianqiu|Unidentified Artist,"Chinese, 1514–1595",,Zhou Tianqiu|Unidentified Artist,Chinese,1514,1595,,1368,1644,Folding fan mounted as an album leaf; ink on gold-flecked paper,6 7/8 x 21 1/4 in. (17.5 x 54.0 cm),"John Stewart Kennedy Fund, 1913",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/48906,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -13.100.78,false,true,35991,Asian Art,Folding fan mounted as an album leaf,,China,late Ming (1368–1644)–early Qing (1644–1911) dynasty,,,,Artist|Artist,After,Chen Hongshou|Unidentified Artist,"Chinese, 1599–1652",,Chen Hongshou|Unidentified Artist,Chinese,1599,1652,,1368,1911,Folding fan mounted as an album leaf; ink on paper,6 3/8 x 19 7/8 in. (16.2 x 50.5 cm),"John Stewart Kennedy Fund, 1913",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/35991,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -CP26,false,true,63220,Asian Art,Print,,China,,,,,Artist|Artist,In the Style of|Original painted by,Guo Songzheng|Xiao Yuncong,"Chinese|Chinese, 1596–1673",,Guo Songzheng|Xiao Yuncong,Chinese|Chinese,1596,1673,,0,0,Circular fan-shaped woodblock print; ink and color on paper,9 5/8 x 11 13/16 in. (24.4 x 30 cm),"Rogers Fund, 1924",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/63220,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -CP28,false,true,63225,Asian Art,Print,,China,,,,,Artist|Artist,Original painted by|In the Style of,Shi Daoren|Li Cheng,"Chinese, Qing dynasty|Chinese, 919–967",,Shi Daoren|LI CHENG,Chinese|Chinese,0919,0967,,0,0,Fan-shaped woodblock print; ink and color on paper,9 5/8 x 11 13/16 in. (24.4 x 30 cm),"Rogers Fund, 1924",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/63225,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -CP11,false,true,63205,Asian Art,Print,,China,,,,,Artist|Artist,In the Style of|Original painted by,Hwang Yifeng|Xiao Yuncong,"Chinese, 1269–1354|Chinese, 1596–1673",,Hwang Yifeng|Xiao Yuncong,Chinese|Chinese,1269 |1596,1354 |1673,,0,0,Polychrome woodblock print; ink and color on paper,9 5/8 x 11 13/16 in. (24.4 x 30 cm),"Rogers Fund, 1924",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/63205,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -CP12,false,true,63206,Asian Art,Print,,China,,,,,Artist|Artist,Original painted by|In the style of,Hwa Dang|Wu Zhen,"Chinese, Ming dynasty|Chinese, 1280–1354",,Hwa Dang|Wu Zhen,Chinese|Chinese,1280,1354,,0,0,Polychrome woodblock print; ink and color on paper,9 5/8 x 11 13/16 in. (24.4 x 30 cm),"Rogers Fund, 1924",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/63206,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -CP14,false,true,63208,Asian Art,Print,,China,,,,,Artist|Artist,Original by|In the Style of,Gao Fangshan|Mi Yuanzhang,"Chinese, Yuan dynasty|Chinese, 1051–1107",,Gao Fangshan|Mi Yuanzhang,Chinese|Chinese,1051,1107,,0,0,Polychrome woodblock print; ink and color on paper,9 5/8 x 11 13/16 in. (24.4 x 30 cm),"Rogers Fund, 1924",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/63208,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -CP31,false,true,63228,Asian Art,Print,,China,,,,,Artist|Artist,Original painted by|In the Style of,Gao Fangshan|Mi Yuanzhang,"Chinese, Yuan dynasty|Chinese, 1051–1107",,Gao Fangshan|Mi Yuanzhang,Chinese|Chinese,1051,1107,,0,0,Polychrome woodblock print; ink and color on paper,9 5/8 x 11 13/16 in. (24.4 x 30 cm),"Rogers Fund, 1924",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/63228,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1973.121.10,false,true,40102,Asian Art,Fan mounted as an album leaf,,China,,,,,Artist|Artist,After,Li Song|Unidentified Artist,"Chinese, ca. 1190–1260","Chinese, 13th–15th century?",Li Song|Unidentified Artist,Chinese,1190,1260,,1200,1499,Fan mounted as an album leaf; ink and color on silk,10 3/8 x 10 1/2 in. (26.4 x 26.7 cm),"Ex coll.: C. C. Wang Family, Purchase, Gift of J. Pierpont Morgan, by exchange, 1973",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/40102,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.3,false,true,39917,Asian Art,Hanging scroll mounted as handscroll,,China,Song dynasty (960–1279),,,,Artist|Artist,After,Su Shi|Unidentified Artist,"Chinese, 1037–1101",,Su Shi|Unidentified Artist,Chinese,1037,1101,,1037,1101,Hanging scroll mounted as a handscroll; ink on paper,Image: 21 3/8 in. × 13 in. (54.3 × 33 cm) Overall with mounting: 14 3/4 in. × 34 ft. 10 3/16 in. (37.5 × 1062.2 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/39917,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -13.100.113,false,true,51396,Asian Art,Fan mounted as an album leaf,,China,Song dynasty (960–1279),,,,Artist|Artist,After,Emperor Huizong|Unidentified Artist,"Chinese, 1082–1135; r. 1100–25",,Huizong Emperor|Unidentified Artist,Chinese,1082,1082,,1082,1135,Fan mounted as an album leaf; ink and color on silk,9 x 9 1/4 in. (22.9 x 23.5 cm),"John Stewart Kennedy Fund, 1913",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/51396,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -23.33.1,false,true,51540,Asian Art,Album leaf,,China,Ming dynasty (1368–1644),,,,Artist|Artist,Formerly Attributed to,Unidentified Artist|Gu Kaizhi,344–405,,Unidentified Artist|Gu Kaizhi,Chinese,0344,0405,,1368,1644,Album leaf; ink and color on silk,7 1/8 x 8 7/8 in. (18.1 x 22.5 cm),"Rogers Fund, 1923",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/51540,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -47.18.148,false,true,51746,Asian Art,Fan mounted as an album leaf,,China,Ming dynasty (1368–1644),,,,Artist|Artist,Copy after,Unidentified Artist|Li Anzhong,active 12th century,,Unidentified Artist|Li Anzhong,Chinese,0012,0012,,1368,1644,Fan mounted as an album leaf; ink and color on silk,9 3/4 x 10 in. (24.8 x 25.4 cm),"From the Collection of A. W. Bahr, Purchase, Fletcher Fund, 1947",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/51746,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1981.4.2,false,true,45754,Asian Art,Hanging scroll,,China,Ming dynasty (1368–1644),,,,Artist|Artist,After,Tang Yin|Unidentified Artist,"Chinese, 1470–1524",,Tang Yin|Unidentified Artist,Chinese,1470,1524,,1368,1644,Hanging scroll; ink and color on paper,Image: 53 5/8 x 23 1/8 in. (136.2 x 58.7 cm) Overall with mounting: 109 1/4 x 30 1/4 in. (277.5 x 76.8 cm) Overall with knobs: 109 1/4 x 34 in. (277.5 x 86.4 cm),"Gift of Douglas Dillon, 1981",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45754,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -13.100.53,false,true,45774,Asian Art,Folding fan mounted as an album leaf,,China,Ming dynasty (1368–1644),,,,Artist|Artist,After,Tang Yin|Unidentified Artist,"Chinese, 1470–1524",,Tang Yin|Unidentified Artist,Chinese,1470,1524,,1368,1644,Folding fan mounted as an album leaf; ink and color on gold-flecked paper,7 1/4 x 20 1/8 in. (18.4 x 51.1 cm),"John Stewart Kennedy Fund, 1913",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45774,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -13.100.59,false,true,48872,Asian Art,Folding fan mounted as an album leaf,,China,Ming dynasty (1368–1644),,,,Artist|Artist,After,Lu Zhi|Unidentified Artist,"Chinese, 1495–1576",,Lu Zhi|Unidentified Artist,Chinese,1495,1576,,1368,1644,Folding fan mounted as an album leaf; ink on gold paper,7 1/4 x 19 1/4 in. (18.4 x 48.9 cm),"John Stewart Kennedy Fund, 1913",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/48872,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -47.18.45,false,true,51664,Asian Art,Fan mounted as an album leaf,,China,Ming dynasty (1368–1644),,,,Artist|Artist,In the style of,Unidentified Artist|Yanxiu,"Chinese, early 10th century",,Unidentified Artist|Yanxiu,Chinese,0900,0999,,1368,1644,Fan mounted as an album leaf; ink and color on silk,8 x 8 3/16 in. (20.3 x 20.8 cm),"From the Collection of A. W. Bahr, Purchase, Fletcher Fund, 1947",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/51664,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -47.18.64,false,true,51679,Asian Art,Fan mounted as an album leaf,,China,Ming dynasty (1368–1644),,,,Artist|Artist,Formerly Attributed to,Unidentified Artist|Hao Cheng,"Chinese, early 11th century",,Unidentified Artist|Hao Cheng,Chinese,1000,1035,,1000,1035,Fan mounted as an album leaf; ink and color on silk,8 5/8 x 9 3/16 in. (21.9 x 23.3 cm),"From the Collection of A. W. Bahr, Purchase, Fletcher Fund, 1947",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/51679,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -47.18.62,false,true,51678,Asian Art,Fan mounted as an album leaf,,China,Ming dynasty (1368–1644),,,,Artist|Artist,After,Unidentified Artist|Ma Lin,"Chinese, ca. 1180– after 1256",,Unidentified Artist|MA LIN,Chinese,1180,1256,,1368,1644,Fan mounted as an album leaf; ink and color on silk,9 3/4 x 10 1/16 in. (24.8 x 25.6 cm),"From the Collection of A. W. Bahr, Purchase, Fletcher Fund, 1947",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/51678,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -47.18.41,false,true,45677,Asian Art,Album leaf,元 佚名 倣夏永 呂洞賓過岳陽樓 冊頁|The Immortal Lü Dongbin Appearing over the Yueyang Pavilion,China,Ming dynasty (1368–1644),,,,Artist|Artist,After,Unidentified Artist|Xia Yong,"Chinese, active mid-14th century","Chinese, 15th–16th century?",Unidentified Artist|XIA YONG,Chinese,1336,1370,,1400,1599,Album leaf; ink on silk,Image: 8 5/8 x 7 3/8 in. (21.9 x 18.7 cm),"From the Collection of A. W. Bahr, Purchase, Fletcher Fund, 1947",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45677,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -13.100.61,false,true,35984,Asian Art,Folding fan mounted as an album leaf,,China,Ming dynasty (1368–1644),,,,Artist|Artist,After,Song Maojin|Unidentified Artist,"Chinese, active late 16th century–early 17th century",,Song Maojin|Unidentified Artist,Chinese,1571,1635,,1368,1644,Folding fan mounted as an album leaf; ink and color on gold paper,6 1/4 x 18 1/4 in. (15.9 x 46.4 cm),"John Stewart Kennedy Fund, 1913",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/35984,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -13.220.99e,false,true,51498,Asian Art,Album leaf,,China,Qing dynasty (1644–1911),,,,Artist|Artist,After,Cao Zhibai|Unidentified Artist,"Chinese, died 1355",,Cao Zhibai|Unidentified Artist,Chinese,,1355,,1644,1911,Album leaf; ink and color on silk,11 7/16 x 14 5/8 in. (29.1 x 37.1 cm),"John Stewart Kennedy Fund, 1913",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/51498,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -13.220.20,false,true,51428,Asian Art,Hanging scroll,,China,Qing dynasty (1644–1911),,,,Artist|Artist,After,Unidentified Artist|Zhao Mengfu,"Chinese, 1254–1322",,Unidentified Artist|Zhao Mengfu,Chinese,1254,1322,,1644,1911,Hanging scroll; ink and color on silk,21 1/2 x 13 3/4 in. (54.6 x 34.9 cm),"John Stewart Kennedy Fund, 1913",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/51428,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -13.220.99g,false,true,51500,Asian Art,Album leaf,,China,Qing dynasty (1644–1911),,,,Artist|Artist,After,Zhao Mengfu|Unidentified Artist,"Chinese, 1254–1322",,Zhao Mengfu|Unidentified Artist,Chinese,1254,1322,,1644,1911,Album leaf; ink and color on silk,10 1/16 x 12 in. (25.6 x 30.5 cm),"John Stewart Kennedy Fund, 1913",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/51500,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -13.220.126,false,true,51515,Asian Art,Handscroll,清 倣米芾雲山圖 卷|Mountain Scenery,China,Qing dynasty (1644–1911),,,,Artist|Artist,After,Dong Qichang|Unidentified Artist,"Chinese, 1555–1636",,Dong Qichang|Unidentified Artist,Chinese,1555,1636,,1644,1911,Handscroll; ink on silk,10 1/2 x 86 in. (26.7 x 218.4 cm),"John Stewart Kennedy Fund, 1913",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/51515,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.145,false,true,49159,Asian Art,Album leaf,,China,Qing dynasty (1644–1911),,,,Artist|Artist,After,Yun Shouping|Unidentified Artist,"Chinese, 1633–1690",,YUN SHOUPING|Unidentified Artist,Chinese,1633,1690,,1644,1911,Album leaf; ink and color on paper,8 1/8 x 12 in. (20.6 x 30.5 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49159,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.146,false,true,49160,Asian Art,Album leaf,,China,Qing dynasty (1644–1911),,,,Artist|Artist,After,Yun Shouping|Unidentified Artist,"Chinese, 1633–1690",,YUN SHOUPING|Unidentified Artist,Chinese,1633,1690,,1644,1911,Album leaf; ink and color on paper,11 7/8 x 8 1/8 in. (30.2 x 20.6 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49160,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -13.220.19,false,true,51427,Asian Art,Hanging scroll,,China,Qing dynasty (1644–1911),,,,Artist|Artist,Formerly Attributed to,Unidentified Artist|Fang-hu,"Chinese, active ca. 1380",,Unidentified Artist|Fang-hu,Chinese,1380,1380,,1644,1911,Hanging scroll; ink on paper,33 1/8 x 18 5/8 in. (84.1 x 47.3 cm),"John Stewart Kennedy Fund, 1913",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/51427,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -11.170,false,true,51284,Asian Art,Handscroll,清 佚名 清明上河圖|Going Upriver on the Qingming Festival,China,Qing dynasty (1644–1911),,,,Artist|Artist,After,Unidentified Artist|Qiu Ying,"Chinese, ca. 1495–1552","Chinese, 18th century?",Unidentified Artist|Qiu Ying,Chinese,1485,1562,,1644,1911,Handscroll; ink and color on silk,11 1/2 in. × 21 ft. 2 in. (29.2 × 645.2 cm),"Rogers Fund, 1911",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/51284,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -18.124.5,false,true,51527,Asian Art,Handscroll,,China,Qing dynasty (1644–1911),,,,Artist|Artist,Formerly Attributed to,Unidentified Artist|Gong Kai,"Chinese, 1222–after 1304",,Unidentified Artist|Gong Kai,Chinese,1222,1310,,1644,1911,Handscroll; black and white on paper,H. 12 1/2 in. (31.8 cm),"Rogers Fund, 1918",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/51527,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -13.220.131a–j,false,true,51519,Asian Art,Album,,China,Qing dynasty (1644–1911),,,,Artist|Artist,After,Song Xu|Unidentified Artist,"Chinese, 1525–after 1606",,Song Xu|Unidentified Artist,Chinese,1525,1610,,1644,1911,Album of ten leaves; ink and color on silk,Each painting: 11 1/4 x 11 1/2 in. (28.6 x 29.2 cm),"John Stewart Kennedy Fund, 1913",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/51519,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.160.28,false,true,51574,Asian Art,Hanging scroll,,China,Qing dynasty (1644–1911),,,,Artist|Artist,Formerly attributed to,Gong Ji|Unidentified Artist,"Chinese, Northern Song dynasty",,Gong Ji|Unidentified Artist,Chinese,0960,1127,,1644,1911,Hanging scroll; ink and color on silk,26 1/2 x 16 in. (67.3 x 40.6 cm),"H. O. Havemeyer Collection, Gift of Horace Havemeyer, 1929",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/51574,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -13.220.99f,false,true,51499,Asian Art,Album leaf,,China,Qing dynasty (1644–1911),,,,Artist|Artist,After,Wang Yuan|Unidentified Artist,"Chinese, ca. 1280–after 1349",,Wang Yuan|Unidentified Artist,Chinese,1270,1359,,1644,1911,Album leaf; ink and color on silk,19 3/4 x 12 3/8 in. (50.2 x 31.4 cm),"John Stewart Kennedy Fund, 1913",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/51499,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -47.18.89,false,true,51701,Asian Art,Handscroll,,China,Qing dynasty (1644–1911),,,,Artist|Artist,After,Unidentified Artist|Xia Gui,"Chinese, active ca. 1195–1230",,Unidentified Artist|Xia Gui,Chinese,1195,1230,,1644,1911,Handscroll; ink on silk,14 3/8 x 69 3/4 in. (36.5 x 177.2 cm),"From the Collection of A. W. Bahr, Purchase, Fletcher Fund, 1947",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/51701,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -47.18.5,false,true,51637,Asian Art,Handscroll,,China,Qing dynasty (1644–1911),,,,Artist|Artist,Formerly Attributed to,Unidentified Artist|Zhao Chang,"Chinese, active 10th–11th century",,Unidentified Artist|Zhao Chang,Chinese,0925,1025,,1644,1911,Handscroll; ink and color on silk,Image: 9 15/16 × 79 5/8 in. (25.2 × 202.2 cm),"From the Collection of A. W. Bahr, Purchase, Fletcher Fund, 1947",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/51637,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -13.220.16,false,true,51425,Asian Art,Hanging scroll,,China,Qing dynasty (1644–1911),,,,Artist|Artist,Formerly attributed to,Unidentified Artist|Zhao Lingrang,"Chinese, active ca. 1070– after 1100",,Unidentified Artist|ZHAO LINGRANG,Chinese,1070,1100,,1644,1911,Hanging scroll; ink and color on silk,Image: 34 x 14 1/2 in. (86.4 x 36.8 cm) Overall with mounting: 94 1/2 x 20 5/8 in. (240 x 52.4 cm) Overall with knobs: 94 1/2 x 24 3/4 in. (240 x 62.9 cm),"John Stewart Kennedy Fund, 1913",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/51425,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.461,false,true,50360,Asian Art,Hanging scroll,,China,Yuan dynasty (1271–1368),,,,Artist|Artist,In the style of,Unidentified Artist|Wu Daozi,"Chinese, 689–after 755",,Unidentified Artist|Wu Daozi,Chinese,0689,0760,,1271,1368,Hanging scroll; ink and color on silk,Image: 40 1/2 × 20 3/8 in. (102.9 × 51.8 cm) Overall with mounting: 72 × 27 3/8 in. (182.9 × 69.5 cm) Overall with knobs: 72 × 29 3/8 in. (182.9 × 74.6 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/50360,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -47.18.72,false,true,51687,Asian Art,Album leaf,,China,Song dynasty (?) (960–1279),,,,Artist|Artist,Formerly Attributed to,Unidentified Artist|Mao I,active 12th century,,Unidentified Artist|Mao I,Chinese,0012,0012,,960,1279,Album leaf; ink and color on silk,9 3/4 x 9 15/16 in. (24.8 x 25.2 cm),"From the Collection of A. W. Bahr, Purchase, Fletcher Fund, 1947",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/51687,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -47.18.28,false,true,51654,Asian Art,Album leaf,,China,Song dynasty (?) (960–1279),,,,Artist|Artist,Formerly Attributed to,Unidentified Artist|Ma Lin,"Chinese, ca. 1180– after 1256",,Unidentified Artist|MA LIN,Chinese,1180,1256,,960,1279,Album leaf; ink on silk,9 9/16 x 10 in. (24.3 x 25.4 cm),"From the Collection of A. W. Bahr, Purchase, Fletcher Fund, 1947",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/51654,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -13.100.114,false,true,51397,Asian Art,Album leaf,,China,Song dynasty (?) (960–1279),,,,Artist|Artist,in the style of,Unidentified Artist|Zhao Chang,"Chinese, active 10th–11th century",,Unidentified Artist|Zhao Chang,Chinese,0925,1025,,960,1279,Album leaf; ink and color on silk,8 1/8 x 9 3/8 in. (20.6 x 23.8 cm),"John Stewart Kennedy Fund, 1913",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/51397,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -47.18.95,false,true,51707,Asian Art,Handscroll,,China,Song dynasty (?) (960–1279),,,,Artist|Artist,In the style of,Unidentified Artist|Emperor Gaozong,"Chinese, 1107–1187, r. 1127–1162",,Unidentified Artist|Gaozong Emperor,Chinese,1107,1187,,960,1279,Handscroll; ink and color on silk,10 7/8 x 83 1/8 in. (27.6 x 211.1 cm),"From the Collection of A. W. Bahr, Purchase, Fletcher Fund, 1947",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/51707,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -13.100.34,false,true,51307,Asian Art,Handscroll,,China,Ming dynasty (1368–1644) (?),,,,Artist|Artist,After,Wu Zhen|Unidentified Artist,"Chinese, 1280–1354",,Wu Zhen|Unidentified Artist,Chinese,1280,1354,,1368,1644,Handscroll; ink on silk,13 1/2 in. × 17 ft. 3 3/4 in. (34.3 × 527.7 cm),"John Stewart Kennedy Fund, 1913",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/51307,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -18.124.2,false,true,36032,Asian Art,Handscroll,,China,Ming dynasty (1368–1644) (?),,,,Artist|Artist,Formerly Attributed to,Unidentified Artist|Li Gonglin,"Chinese, ca. 1041–1106",,Unidentified Artist|Li Gonglin,Chinese,1041,1106,,1368,1644,Handscroll; ink on silk,13 3/8 × 85 in. (34 × 215.9 cm),"Rogers Fund, 1918",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/36032,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -47.18.86,false,true,51698,Asian Art,Handscroll,,China,Ming dynasty (?) (1368–1644),,,,Artist|Artist,Copy after,Unidentified Artist|Chen Rong,active 1235–62,,Unidentified Artist|Chen Rong,Chinese,1235,1262,,1368,1644,Handscroll; ink on silk,Overall: 15 5/8 x 115 1/4 in. (39.7 x 292.7 cm),"From the Collection of A. W. Bahr, Purchase, Fletcher Fund, 1947",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/51698,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -47.18.145,false,true,51744,Asian Art,Album leaf,,China,Ming dynasty (?) (1368–1644),,,,Artist|Artist,Copy after,Unidentified Artist|Yan Liben,"Chinese, 640–680",,Unidentified Artist|Yan Liben,Chinese,0640,0680,,1368,1644,Album leaf; ink and color on silk,10 x 5 15/16 in. (25.4 x 15.1 cm),"From the Collection of A. W. Bahr, Purchase, Fletcher Fund, 1947",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/51744,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -18.124.1,false,true,51526,Asian Art,Handscroll,,China,Ming dynasty (?) (1368–1644),,,,Artist|Artist,In the Style of,Unidentified Artist|Wu Zongyuan,"Chinese, died 1050",,Unidentified Artist|Wu Zongyuan,Chinese,0950,1050,,1368,1644,Handscroll; ink and color on silk,20 1/2 in. × 16 ft. 3 15/16 in. (52.1 × 497.7 cm),"Rogers Fund, 1918",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/51526,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.5,false,true,51522,Asian Art,Hanging scroll,,China,Ming dynasty (?) (1368–1644),,,,Artist|Artist,Formerly Attributed to,Unidentified Artist|Li Anzhong,active 12th century,,Unidentified Artist|Li Anzhong,Chinese,0012,0012,,1368,1644,Hanging scroll; color on silk,Image: 44 1/4 × 18 1/4 in. (112.4 × 46.4 cm) Overall with mounting: 79 1/2 × 23 3/4 in. (201.9 × 60.3 cm) Overall with knobs: 79 1/2 × 25 1/2 in. (201.9 × 64.8 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/51522,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -47.18.117,false,true,51724,Asian Art,Handscroll,,China,Ming dynasty (?) (1368–1644),,,,Artist|Artist,In the style of,Unidentified Artist|Wu Zhen,"Chinese, 1280–1354",,Unidentified Artist|Wu Zhen,Chinese,1280,1354,,1368,1644,Handscroll; ink on silk,12 1/2 x 262 in. (31.8 x 665.5 cm),"From the Collection of A. W. Bahr, Purchase, Fletcher Fund, 1947",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/51724,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -47.18.143,false,true,51742,Asian Art,Album leaf,,China,Ming dynasty (?) (1368–1644),,,,Artist|Artist,Copy after (?),Unidentified Artist|Huang Qüan,"Chinese, active ca. 950",,Unidentified Artist|Huang Qüan,Chinese,0940,0960,,1368,1644,Album leaf; ink and color on silk,8 3/4 x 9 1/2 in. (22.2 x 24.1 cm),"From the Collection of A. W. Bahr, Purchase, Fletcher Fund, 1947",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/51742,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"54.134.2a, b",false,true,51806,Asian Art,Handscroll,,China,Ming dynasty (?) (1368–1644),,,,Artist|Artist,In the style of,Unidentified Artist|Qiu Ying,"Chinese, ca. 1495–1552",,Unidentified Artist|Qiu Ying,Chinese,1485,1562,,1368,1644,Handscroll; ink and color on silk,(a.) 14 15/16 x 94 1/2 in. (37.9 x 240 cm); (b.) 51 x 14 3/8 in. (129.5 x 36.5 cm),"Gift of the Pierpont Morgan Library, 1954",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/51806,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -47.18.38,false,true,51660,Asian Art,Fan mounted as an album leaf,,China,Ming dynasty (?) (1368–1644),,,,Artist|Artist,In the style of,Unidentified Artist|Luo Zonggui,"Chinese, active 1228–1234",,Unidentified Artist|Luo Zonggui,Chinese,1228,1234,,1368,1644,Fan mounted as an album leaf; ink and color on silk,9 1/8 x 7 1/8 in. (23.2 x 18.1 cm),"From the Collection of A. W. Bahr, Purchase, Fletcher Fund, 1947",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/51660,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -47.18.40,false,true,51662,Asian Art,Fan mounted as an album leaf,,China,Ming dynasty (?) (1368–1644),,,,Artist|Artist,In the style of,Unidentified Artist|Xia Gui,"Chinese, active ca. 1195–1230",,Unidentified Artist|Xia Gui,Chinese,1195,1230,,1368,1644,Fan mounted as an album leaf; ink on silk,9 1/8 x 9 1/2 in. (23.2 x 24.1 cm),"From the Collection of A. W. Bahr, Purchase, Fletcher Fund, 1947",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/51662,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -47.18.146,false,true,51745,Asian Art,Album leaf,,China,Ming dynasty (?) (1368–1644),,,,Artist|Artist,Copy after (?),Unidentified Artist|Zhou Fang,"Chinese, active ca. 780– ca.810",,Unidentified Artist|ZHOU FANG,Chinese,0780,0810,,1368,1644,Album leaf; ink and color on silk,7 1/2 x 8 1/2 in. (19.1 x 21.6 cm),"From the Collection of A. W. Bahr, Purchase, Fletcher Fund, 1947",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/51745,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -47.18.105,false,true,51715,Asian Art,Hanging scroll,,China,Ming dynasty (?) (1368–1644),,,,Artist|Artist,In the style of,Unidentified Artist|Lü Ji,"Chinese, active late 15th century",,Unidentified Artist|Lü Ji,Chinese,1430,1504,,1368,1644,Hanging scroll; ink and color on silk,75 x 38 7/8 in. (190.5 x 98.7 cm),"From the Collection of A. W. Bahr, Purchase, Fletcher Fund, 1947",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/51715,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -13.100.107,false,true,51393,Asian Art,Fan mounted as an album leaf,,China,Ming dynasty (?) (1368–1644),,,,Artist|Artist,After,Emperor Huizong|Unidentified Artist,"Chinese, 1082–1135; r. 1100–25",,Huizong Emperor|Unidentified Artist,Chinese,1082,1082,,1368,1644,Fan mounted as an album leaf; ink and color on silk,9 1/4 x 9 5/8 in. (23.5 x 24.4 cm),"John Stewart Kennedy Fund, 1913",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/51393,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -47.18.48,false,true,51667,Asian Art,Handscroll,,China,Qing dynasty (?) (1644–1911),,,,Artist|Artist,In the style of,Unidentified Artist|Mi Fu,"Chinese, 1052–1107",,Unidentified Artist|MI FU,Chinese,1052,1107,,1644,1911,Handscroll; ink on silk,13 1/4 x 110 3/16 in. (33.7 x 279.9 cm),"From the Collection of A. W. Bahr, Purchase, Fletcher Fund, 1947",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/51667,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -47.18.139,false,true,51738,Asian Art,Album leaf,,China,Yuan dynasty (?) (1271–1368),,,,Artist|Artist,Copy after,Unidentified Artist|Li Gonglin,"Chinese, ca. 1041–1106",,Unidentified Artist|Li Gonglin,Chinese,1041,1106,,1271,1368,Album leaf; ink and color on silk,12 x 12 5/16 in. (30.5 x 31.3 cm),"From the Collection of A. W. Bahr, Purchase, Fletcher Fund, 1947",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/51738,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -13.220.17,false,true,51426,Asian Art,Hanging scroll,,China,late Qing dynasty (1644–1911),,,,Artist|Artist,In the Style of,Wen Zhengming|Unidentified Artist,"Chinese, 1470–1559",,Wen Zhengming|Unidentified Artist,Chinese,1470,1559,,1500,1599,Hanging scroll; ink and color on paper,26 1/8 x 10 in. (66.4 x 25.4 cm),"John Stewart Kennedy Fund, 1913",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/51426,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -47.18.98,false,true,51710,Asian Art,Handscroll,,China,early Ming dynasty (1368–1644),,,,Artist|Artist,Copy after,Unidentified Artist|Huang Qüan,"Chinese, active ca. 950",,Unidentified Artist|Huang Qüan,Chinese,0940,0960,,1368,1644,Handscroll; ink and color on silk,14 x 87 1/2 in. (35.6 x 222.3 cm),"From the Collection of A. W. Bahr, Purchase, Fletcher Fund, 1947",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/51710,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -13.100.103,false,true,51389,Asian Art,Fan mounted as an album leaf,,China,Song dynasty (960–1279) or later,,,,Artist|Artist,Formerly attributed to,Unidentified Artist|Wu Bing,"Chinese, active 1190–1194",,Unidentified Artist|Wu Bing,Chinese,1190,1194,,960,1300,Fan mounted as an album leaf; Ink and color on silk,12.25 x 12.25 in. (31.1 x 31.1 cm),"John Stewart Kennedy Fund, 1913",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/51389,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -13.100.49,false,true,35979,Asian Art,Folding fan mounted as an album leaf,,China,Ming dynasty (1368–1644) or later,,,,Artist|Artist,After,Wen Zhengming|Unidentified Artist,"Chinese, 1470–1559",,Wen Zhengming|Unidentified Artist,Chinese,1470,1559,,1368,1644,Folding fan mounted as an album leaf; ink and color on gold-flecked paper,7 1/8 x 20 1/2 in. (18.1 x 52.1 cm),"John Stewart Kennedy Fund, 1913",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/35979,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -38.31.2,false,true,51586,Asian Art,Handscroll,,China,Ming dynasty (1368–1644) or earlier,,,,Artist|Artist,In the style of,Unidentified Artist|Zhao Boju,"Chinese, 11th century",,Unidentified Artist|Zhao Boju,Chinese,1000,1099,,1300,1644,Handscroll; ink and color on silk,11 5/8 x 95 1/2 in. (29.5 x 242.6 cm),"Fletcher Fund, 1938",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/51586,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -13.100.98,false,true,51384,Asian Art,Fan mounted as an album leaf,,China,Song (960–1279)–Ming (1368–1644) dynasty,,,,Artist|Artist,After,Emperor Huizong|Unidentified Artist,"Chinese, 1082–1135; r. 1100–25",,Huizong Emperor|Unidentified Artist,Chinese,1082,1082,,960,1644,Fan mounted as an album leaf; ink and color on silk,9 1/8 x 9 1/2 in. (23.2 x 24.1 cm),"John Stewart Kennedy Fund, 1913",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/51384,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -13.220.110,false,true,45782,Asian Art,Hanging scroll,,China,Ming (1368–1644)–Qing (1644–1911) dynasty,,,,Artist|Artist,After,Wen Zhengming|Unidentified Artist,"Chinese, 1470–1559",,Wen Zhengming|Unidentified Artist,Chinese,1470,1559,,1368,1911,Hanging scroll; ink and color on paper,25 x 11 1/2 in. (63.5 x 29.2 cm),"John Stewart Kennedy Fund, 1913",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45782,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -13.220.24,false,true,52080,Asian Art,Handscroll,,China,Ming (1368–1644)–Qing (1644–1911) dynasty,,,,Artist|Artist,In the Style of,Li Gonglin|Unidentified Artist,"Chinese, ca. 1041–1106",,Li Gonglin|Unidentified Artist,Chinese,1041,1106,,1368,1911,Handscroll; ink on paper,11 7/8 in. × 19 ft. 5 1/2 in. (30.2 × 593.1 cm),"John Stewart Kennedy Fund, 1913",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/52080,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -13.220.99a,false,true,51495,Asian Art,Album leaf,,China,Ming (1368–1644)–Qing (1644–1911) dynasty,,,,Artist|Artist,After,Lu Qing|Unidentified Artist,"Chinese, active 1190–1195",,Lu Qing|Unidentified Artist,Chinese,1190,1195,,1368,1911,Album leaf; ink on silk,11 13/16 x 16 in. (30 x 40.6 cm),"John Stewart Kennedy Fund, 1913",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/51495,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -13.220.99b,false,true,36020,Asian Art,Album leaf,,China,Ming (1368–1644)–Qing (1644–1911) dynasty,,,,Artist|Artist,In the Style of,Ma Lin|Unidentified Artist,"Chinese, ca. 1180– after 1256",,MA LIN|Unidentified Artist,Chinese,1180,1256,,1368,1911,Album leaf; ink and color on silk,10 3/4 x 10 3/4 in. (27.3 x 27.3 cm),"John Stewart Kennedy Fund, 1913",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/36020,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -21.18,false,true,51537,Asian Art,Handscroll,,China,Ming (1368–1644) or Qing (1644–1911) dynasty,,,,Artist|Artist,In the style of,Unidentified Artist|Wu Zongyuan,"Chinese, died 1050",,Unidentified Artist|Wu Zongyuan,Chinese,0950,1050,,1368,1911,Handscroll; ink and color on silk,20 1/2 x 75 3/4 in. (52.1 x 192.4 cm),"Rogers Fund, 1921",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/51537,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -13.220.100a–l,false,true,51505,Asian Art,Album leaves,,China,Ming (1368–1644) or Qing (1644–1911) dynasty,,,,Artist|Artist,Border by|Formerly attributed to,Unidentified Artist|Yi Yuanji,"Chinese, died 1066",,Unidentified Artist|Yi Yuanji,Chinese,0966,1066,,1368,1911,Set of twelve album leaves; ink and color on silk,Each: 11 1/2 x 10 5/8 in. (29.2 x 27 cm),"John Stewart Kennedy Fund, 1913",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/51505,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -23.53,false,true,51544,Asian Art,Handscroll,,China,Ming (1368–1644) or Qing (1644–1911) dynasty,,,,Artist|Artist,In the Style of,Unidentified Artist|Zhao Mengfu,"Chinese, 1254–1322",,Unidentified Artist|Zhao Mengfu,Chinese,1254,1322,,1368,1911,Handscroll; ink and color on silk,10 5/8 × 34 1/2 in. (27 × 87.6 cm),"Gift of Lewis Cass Ledyard, 1923",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/51544,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -13.220.95,false,true,51492,Asian Art,Hanging scroll,,China,Ming (1368–1644) or Qing (1644–1911) dynasty,,,,Artist|Artist,In the style of,Unidentified Artist|Zhao Mengfu,"Chinese, 1254–1322",,Unidentified Artist|Zhao Mengfu,Chinese,1254,1322,,1368,1911,Hanging scroll; ink and color on silk,56 x 23 1/2 in. (142.2 x 59.7 cm),"John Stewart Kennedy Fund, 1913",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/51492,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.480,false,true,51566,Asian Art,Handscroll,,China,Ming (1368–1644) or Qing (1644–1911) dynasty,,,,Artist|Artist,In the style of,Unidentified Artist|Zhao Mengfu,"Chinese, 1254–1322",,Unidentified Artist|Zhao Mengfu,Chinese,1254,1322,,1368,1911,Handscroll; ink on silk,13 1/8 in. × 11 ft. 5 in. (33.3 × 348 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/51566,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -13.220.29,false,true,51481,Asian Art,Hanging scroll,,China,Ming (1368–1644) or Qing (1644–1911) dynasty,,,,Artist|Artist,After,Unidentified Artist|Wen Zhengming,"Chinese, 1470–1559",,Unidentified Artist|Wen Zhengming,Chinese,1470,1559,,1368,1911,Hanging scroll; ink on silk,69 x 30 1/2 in. (175.3 x 77.5 cm),"John Stewart Kennedy Fund, 1913",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/51481,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -13.220.113,false,true,51510,Asian Art,Hanging scroll,,China,Ming (1368–1644) or Qing (1644–1911) dynasty,,,,Artist|Artist,Formerly Attributed to,Unidentified Artist|Lu Zhi,"Chinese, 1495–1576",,Unidentified Artist|Lu Zhi,Chinese,1495,1576,,1368,1911,Hanging scroll; ink and color on paper,25 3/8 x 16 1/2 in. (64.5 x 41.9 cm),"John Stewart Kennedy Fund, 1913",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/51510,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.530,false,true,51572,Asian Art,Handscroll,,China,Ming (1368–1644) or Qing (1644–1911) dynasty,,,,Artist|Artist,In the style of,Unidentified Artist|Jing Hao,active ca. 900–960,,Unidentified Artist|Jing Hao,Chinese,0900,0960,,1368,1911,Handscroll; ink and color on silk,16 3/8 x 52 in. (41.6 x 132.1 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/51572,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -47.18.4,false,true,53586,Asian Art,Handscroll,,China,Ming (1368–1644) or Qing (1644–1911) dynasty,,,,Artist|Artist,Formerly Attributed to,Unidentified Artist|Zhao Boju,"Chinese, 11th century",,Unidentified Artist|Zhao Boju,Chinese,1000,1099,,1368,1911,Handscroll; ink and color on silk,Image: 6 3/8 × 34 1/2 in. (16.2 × 87.6 cm),"From the Collection of A. W. Bahr, Purchase, Fletcher Fund, 1947",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/53586,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -13.220.106,false,true,51507,Asian Art,Hanging scroll,,China,Ming (1368–1644) or Qing (1644–1911) dynasty,,,,Artist|Artist,Formerly Attributed to,Unidentified Artist|Zhao Boju,"Chinese, 11th century",,Unidentified Artist|Zhao Boju,Chinese,1000,1099,,1368,1911,Hanging scroll; ink and color on silk,Image: 15 3/4 x 12 1/8 in. (40 x 30.8 cm) Overall (with colophons): 35 1/4 x 12 1/8 in. (89.5 x 30.8 cm) Overall with mounting: 57 1/4 x 14 7/8 in. (145.4 x 37.8 cm),"John Stewart Kennedy Fund, 1913",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/51507,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -13.220.88,false,true,40424,Asian Art,Hanging scroll,,China,Ming (1368–1644) or Qing (1644–1911) dynasty,,,,Artist|Artist,Formerly Attributed to,Unidentified Artist|Huang Qüan,"Chinese, active ca. 950",,Unidentified Artist|Huang Qüan,Chinese,0940,0960,,1368,1911,Hanging scroll,Image: 77 x 37 in. (195.6 x 94 cm) Overall with mounting: 123 x 40 7/8 in. (312.4 x 103.8 cm) Overall with knobs: 123 x 45 1/4 in. (312.4 x 114.9 cm),"John Stewart Kennedy Fund, 1913",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/40424,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -18.25.1,false,true,51524,Asian Art,Handscroll,,China,Ming (1368–1644) or Qing (1644–1911) dynasty,,,,Artist|Artist,Formerly Attributed to,Unidentified Artist|Li Gonglin,"Chinese, ca. 1041–1106",,Unidentified Artist|Li Gonglin,Chinese,1041,1106,,1368,1911,Handscroll; ink on silk,11 3/8 × 58 in. (28.9 × 147.3 cm),"Rogers Fund, 1918",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/51524,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -18.25.2,false,true,51525,Asian Art,Handscroll,,China,Ming (1368–1644) or Qing (1644–1911) dynasty,,,,Artist|Artist,In the style of,Unidentified Artist|Li Gonglin,"Chinese, ca. 1041–1106",,Unidentified Artist|Li Gonglin,Chinese,1041,1106,,1368,1911,Handscroll; ink on silk,13 3/8 x 85 in. (34 x 215.9 cm),"Rogers Fund, 1918",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/51525,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.443,false,true,51559,Asian Art,Hanging scroll,,China,Ming (1368–1644) or Qing (1644–1911) dynasty,,,,Artist|Artist,In the style of,Unidentified Artist|Li Gonglin,"Chinese, ca. 1041–1106",,Unidentified Artist|Li Gonglin,Chinese,1041,1106,,1368,1911,Hanging scroll; color on silk,Image: 47 3/4 × 18 1/4 in. (121.3 × 46.4 cm) Overall with mounting: 84 × 24 1/4 in. (213.4 × 61.6 cm) Overall with knobs: 84 × 26 1/2 in. (213.4 × 67.3 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/51559,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.61.62,false,true,51523,Asian Art,Album leaf,,China,Ming (1368–1644) or Qing (1644–1911) dynasty,,,,Artist|Artist,Formerly Attributed to,Li Gonglin|Unidentified Artist,"Chinese, ca. 1041–1106",,Li Gonglin|Unidentified Artist,Chinese,1041,1106,,1368,1911,Albun leaf; ink and color on silk,Image: 10 1/4 × 10 1/4 in. (26 × 26 cm) Sheet: 12 1/4 × 15 3/4 in. (31.1 × 40 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/51523,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -30.76.296,false,true,51580,Asian Art,Hanging scroll,,China,Ming (1368–1644) or Qing (1644–1911) dynasty,,,,Artist|Artist,In the style of,Unidentified Artist|Lin Liang,"Chinese, ca. 1416–1480",,Unidentified Artist|Lin Liang,Chinese,1416,1480,,1368,1911,Hanging scroll; ink on silk,Image: 37 1/2 × 18 7/8 in. (95.3 × 47.9 cm) Overall with mounting: 68 1/8 × 23 5/8 in. (173 × 60 cm) Overall with knobs: 68 1/8 × 25 5/8 in. (173 × 65.1 cm),"Rogers Fund, 1929",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/51580,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -30.76.297,false,true,53515,Asian Art,Hanging scroll,,China,Ming (1368–1644) or Qing (1644–1911) dynasty,,,,Artist|Artist,In the style of,Unidentified Artist|Lin Liang,"Chinese, ca. 1416–1480",,Unidentified Artist|Lin Liang,Chinese,1416,1480,,1368,1911,Hanging scroll; ink on silk,37 1/2 x 18 7/8 in. (95.3 x 47.9 cm),"Rogers Fund, 1929",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/53515,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -13.220.112,false,true,40420,Asian Art,Hanging scroll,,China,Ming (1368–1644) or Qing (1644–1911) dynasty,,,,Artist|Artist,Signature of,Unidentified Artist|Cui Zizhong,"Chinese, ca. 1595–1644",,Unidentified Artist|Cui Zizhong,Chinese,1595,1644,,1368,1911,Hanging scroll; ink and color on silk,57 x 33 3/4 in. (144.8 x 85.7 cm),"John Stewart Kennedy Fund, 1913",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/40420,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -13.220.96,false,true,51493,Asian Art,Handscroll,,China,Ming (1368–1644) or Qing (1644–1911) dynasty,,,,Artist|Artist,Formerly Attributed to,Guo Xi|Unidentified Artist,"Chinese, ca. 1000–ca. 1090",,Guo Xi|Unidentified Artist,Chinese,1000,1090,,1368,1911,Handscroll; ink on silk,19 in. × 22 ft. 7 in. (48.3 × 688.3 cm),"John Stewart Kennedy Fund, 1913",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/51493,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -13.220.107,false,true,51508,Asian Art,Hanging scroll,,China,Ming (1368–1644) or Qing (1644–1911) dynasty,,,,Artist|Artist,Formerly Attributed to,Unidentified Artist|Fang Congyi,"Chinese, ca. 1301–after 1378",,Unidentified Artist|Fang Congyi,Chinese,1301,1399,,1368,1911,Hanging scroll; ink on paper,Image: 45 × 18 1/2 in. (114.3 × 47 cm) Overall with mounting: 109 × 25 1/4 in. (276.9 × 64.1 cm) Overall with knobs (only one knob): 109 × 29 1/2 in. (276.9 × 74.9 cm),"John Stewart Kennedy Fund, 1913",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/51508,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -13.220.125,false,true,51514,Asian Art,Panels mounted as a handscroll,,China,Ming (1368–1644) or Qing (1644–1911) dynasty,,,,Artist|Artist,Spurious signature of,Unidentified Artist|Wang Zhenpeng,"Chinese, active ca. 1275–1330",,Unidentified Artist|WANG ZHENPENG,Chinese,1265,1340,,1600,1913,Ten panels mounted as a handscroll; ink and color on silk,"Image (each panel, approx.): 13 3/8 × 16 1/4 in. (34 × 41.3 cm)","John Stewart Kennedy Fund, 1913",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/51514,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.521,false,true,51569,Asian Art,Hanging scroll,,China,Ming (1368–1644) or Qing (1644–1911) dynasty,,,,Artist|Artist,Formerly Attributed to,Unidentified Artist|Qian Xuan,"Chinese, ca. 1235–before 1307",,Unidentified Artist|QIAN XUAN,Chinese,1235,1307,,1368,1911,Hanging scroll; ink and color on silk,23 1/8 x 30 in. (58.7 x 76.2 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/51569,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -19.159,false,true,51534,Asian Art,Hanging scroll,,China,Ming (1368–1644) or Qing (1644–1911) dynasty,,,,Artist|Artist,Formerly Attributed to,Unidentified Artist|Emperor Huizong,"Chinese, 1082–1135; r. 1100–25",,Unidentified Artist|Huizong Emperor,Chinese,1082,1082,,1368,1911,Hanging scroll; on silk,46 x 21 in. (116.8 x 53.3 cm),"Rogers Fund, 1918",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/51534,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -13.220.89,false,true,51487,Asian Art,Handscroll,,China,Ming (1368–1644) or Qing (1644–1911) dynasty,,,,Artist|Artist,Formerly Attributed to,Unidentified Artist|Emperor Huizong,"Chinese, 1082–1135; r. 1100–25",,Unidentified Artist|Huizong Emperor,Chinese,1082,1082,,1368,1911,Handscroll; ink and color on silk,12 3/8 x 42 3/4 in. (31.4 x 108.6 cm),"John Stewart Kennedy Fund, 1913",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/51487,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -13.220.22,false,true,51477,Asian Art,Handscroll,,China,Ming (1368–1644) or Qing (1644–1911) dynasty,,,,Artist|Artist,After,Unidentified Artist|Liu Songnian,"Chinese, active ca 1175–after 1195",,Unidentified Artist|LIU SONGNIAN,Chinese,1175,1195,,1368,1911,Handscroll; Ink and color on silk,8 1/2 x 56 1/8 in. (21.6 x 142.6 cm),"John Stewart Kennedy Fund, 1913",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/51477,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.160.30,false,true,51575,Asian Art,Handscroll,,China,Ming (1368–1644) or Qing (1644–1911) dynasty,,,,Artist|Artist,In the style of,Unidentified Artist|Liu Songnian,"Chinese, active ca 1175–after 1195",,Unidentified Artist|LIU SONGNIAN,Chinese,1175,1195,,1368,1911,Handscroll; ink and color on silk,14 3/8 in. × 11 ft. 1 in. (36.5 × 337.8 cm),"H. O. Havemeyer Collection, Gift of Horace Havemeyer, 1929",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/51575,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -47.18.97,false,true,51709,Asian Art,Handscroll,,China,Ming (1368–1644) or Qing (1644–1911) dynasty,,,,Artist|Artist,Attributed to|Copy after,Zhao Mengfu|Shi Daoshi,"Chinese, 1254–1322|Chinese, active 4th century",,Zhao Mengfu|Shi Daoshi,Chinese|Chinese,1254 |0300,1322 |0399,,1368,1911,Handscroll; ink and color on silk,11 5/8 x 104 1/4 in. (29.5 x 264.8 cm),"From the Collection of A. W. Bahr, Purchase, Fletcher Fund, 1947",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/51709,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -45.170.2,false,true,40409,Asian Art,Handscroll,,China,Ming (1368–1644) or Qing dynasty (1644–1911),,,,Artist|Artist,In the Style of,Unidentified Artist|Wang Wei,"Chinese, 699–759",,Unidentified Artist|Wang Wei,Chinese,0699,0759,,1368,1911,Handscroll; ink and color on silk,10 1/2 x 80 3/16 in. (26.7 x 203.7 cm),"Gift of Robert Lehman, 1945",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/40409,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -47.18.73,false,true,51688,Asian Art,Fan mounted as an album leaf,,China,Ming (1368–1644) or Qing dynasty (1644–1911),,,,Artist|Artist,After,Li Cheng|Unidentified Artist,"Chinese, 919–967",,LI CHENG|Unidentified Artist,Chinese,0919,0967,,1368,1911,Fan mounted as an album leaf; ink and color on silk,8 7/8 x 9 3/4 in. (22.5 x 24.8 cm),"From the Collection of A. W. Bahr, Purchase, Fletcher Fund, 1947",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/51688,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -13.100.118,false,true,51400,Asian Art,Fan mounted as an album leaf,,China,Ming (1368–1644) or Qing dynasty (1644–1911),,,,Artist|Artist,Formerly Attributed to,Yan Wengui|Unidentified Artist,"Chinese, 970–1030",,YAN WENGUI|Unidentified Artist,Chinese,0970,1030,,1368,1911,Fan mounted as an album leaf; ink and color on silk,9 1/4 x 9 3/8 in. (23.5 x 23.8 cm),"John Stewart Kennedy Fund, 1913",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/51400,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -47.18.11,false,true,50663,Asian Art,Handscroll,,China,Ming (1368–1644) or Qing dynasty (1644–1911),,,,Artist|Artist,In the style of,Unidentified Artist|Su Shi,"Chinese, 1037–1101",,Unidentified Artist|Su Shi,Chinese,1037,1101,,1368,1911,Handscroll; ink on paper,Image: 11 1/16 in. × 9 ft. 1 7/8 in. (28.1 × 279.1 cm),"From the Collection of A. W. Bahr, Purchase, Fletcher Fund, 1947",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/50663,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -13.100.100,false,true,51387,Asian Art,Fan mounted as an album leaf,,China,Ming (1368–1644) or Qing dynasty (1644–1911),,,,Artist|Artist,Formerly Attributed to,Su Shi|Unidentified Artist,"Chinese, 1037–1101",,Su Shi|Unidentified Artist,Chinese,1037,1101,,1368,1911,Fan mounted as an album leaf; ink on silk,8 x 8 3/8 in. (20.3 x 21.3 cm),"John Stewart Kennedy Fund, 1913",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/51387,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -13.220.4,false,true,51418,Asian Art,Handscroll,,China,Ming (1368–1644) or Qing dynasty (1644–1911),,,,Artist|Artist,In the Style of,Zhao Mengfu|Unidentified Artist,"Chinese, 1254–1322",,Zhao Mengfu|Unidentified Artist,Chinese,1254,1322,,1368,1911,Handscroll; ink and color on silk,12 x 40 1/2 in. (30.5 x 102.9 cm),"John Stewart Kennedy Fund, 1913",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/51418,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -47.18.115,false,true,51723,Asian Art,Handscroll,,China,Ming (1368–1644) or Qing dynasty (1644–1911),,,,Artist|Artist,Copy after,Unidentified Artist|Zhao Mengfu,"Chinese, 1254–1322",,Unidentified Artist|Zhao Mengfu,Chinese,1254,1322,,1368,1644,Handscroll; ink and color on silk,9 x 35 1/4 in. (22.9 x 89.5 cm),"From the Collection of A. W. Bahr, Purchase, Fletcher Fund, 1947",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/51723,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -13.220.26,false,true,51479,Asian Art,Hanging scroll,,China,Ming (1368–1644) or Qing dynasty (1644–1911),,,,Artist|Artist,Formerly Attributed to,Unidentified Artist|Wang Fu,"Chinese, 1362–1416",,Unidentified Artist|Wang Fu,Chinese,1362,1416,,1368,1911,Hanging scroll; ink on paper,Image: 43 7/8 x 14 5/8 in. (111.4 x 37.1 cm) Overall with mounting: 78 1/2 x 20 7/8 in. (199.4 x 53 cm) Overall with knobs: 78 1/2 x 25 3/4 in. (199.4 x 65.4 cm),"John Stewart Kennedy Fund, 1913",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/51479,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -13.100.23,false,true,51291,Asian Art,Hanging scroll,,China,Ming (1368–1644) or Qing dynasty (1644–1911),,,,Artist|Artist,After,Wu Wei|Unidentified Artist,"Chinese, 1459–1508",,Wu Wei|Unidentified Artist,Chinese,1459,1508,,1368,1911,Hanging scroll; ink and color on paper,45 x 17 3/4 in. (114.3 x 45.1 cm),"John Stewart Kennedy Fund, 1913",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/51291,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -47.18.68,false,true,51683,Asian Art,Album leaf,,China,Ming (1368–1644) or Qing dynasty (1644–1911),,,,Artist|Artist,Copy after,Unidentified Artist|Cui Que,"Chinese, 11th century",,Unidentified Artist|Cui Que,Chinese,1000,1099,,1368,1911,Album leaf; ink and color on silk,9 3/4 x 8 3/4 in. (24.8 x 22.2 cm),"From the Collection of A. W. Bahr, Purchase, Fletcher Fund, 1947",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/51683,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -47.136.2,false,true,51751,Asian Art,Handscroll,,China,Ming (1368–1644) or Qing dynasty (1644–1911),,,,Artist|Artist,In the style of,Unidentified Artist|Li Gonglin,"Chinese, ca. 1041–1106",,Unidentified Artist|Li Gonglin,Chinese,1041,1106,,1368,1911,Handscroll; gold on black paper,10 1/2 x 151 1/4 in. (26.7 x 384.2 cm),"Anonymous Gift, 1947",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/51751,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -54.193,false,true,51808,Asian Art,Hanging scroll,,China,Ming (1368–1644) or Qing dynasty (1644–1911),,,,Artist|Artist,In the style of,Unidentified Artist|Gao Kegong,"Chinese (Hui), 1248–1310",,Unidentified Artist|Gao Kegong,Chinese (Hui),1248,1310,,1368,1911,Hanging scroll; ink on silk,47 x 109 1/4 in. (119.4 x 277.5 cm),"Gift of Edgar Worch, in memory of his uncle, Adolphe Worch, of Paris, 1954–1955",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/51808,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -47.18.12,false,true,51643,Asian Art,Handscroll,壺天聚樂圖|Merry Gatherings in the Magic Jar,China,Ming (1368–1644) or Qing dynasty (1644–1911),,,,Artist|Artist,In the Style of,Gong Kai|Unidentified Artist,"Chinese, 1222–after 1304",,Gong Kai|Unidentified Artist,Chinese,1222,1310,,1368,1911,Handscroll; ink on paper,Image: 11 1/2 in. × 12 ft. 3 3/8 in. (29.2 × 374.4 cm),"From the Collection of A. W. Bahr, Purchase, Fletcher Fund, 1947",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/51643,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -47.18.56,false,true,51674,Asian Art,Hanging scroll,,China,Ming (1368–1644) or Qing dynasty (1644–1911),,,,Artist|Artist,Formerly Attributed to,Unidentified Artist|Dong Yuan,"Chinese, active 930s–960s",,Unidentified Artist|DONG YUAN,Chinese,0930,0960,,1368,1911,Hanging scroll; ink on silk,63 1/8 x 19 15/16 in. (160.3 x 50.6 cm),"From the Collection of A. W. Bahr, Purchase, Fletcher Fund, 1947",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/51674,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -47.18.51,false,true,51670,Asian Art,Handscroll,,China,Ming (1368–1644) or Qing dynasty (1644–1911),,,,Artist|Artist,Copy after,Unidentified Artist|Lin Jun,"Chinese, active ca. 1174–90",,Unidentified Artist|Lin Jun,Chinese,1164,1200,,1368,1911,Handscroll; ink and color on silk,11 3/4 x 83 5/8 in. (29.8 x 212.4 cm),"From the Collection of A. W. Bahr, Purchase, Fletcher Fund, 1947",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/51670,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -47.18.14,false,true,51645,Asian Art,Handscroll,,China,Ming (1368–1644) or Qing dynasty (1644–1911),,,,Artist|Artist,Formerly Attributed to,Unidentified Artist|Fang Congyi,"Chinese, ca. 1301–after 1378",,Unidentified Artist|Fang Congyi,Chinese,1301,1399,,1368,1911,Handscroll; ink and color on silk,Overall: 10 1/2 x 106 in. (26.7 x 269.2 cm) Overall with mounting: 11 1/8 in. (28.3 cm),"From the Collection of A. W. Bahr, Purchase, Fletcher Fund, 1947",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/51645,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -47.18.35,false,true,51658,Asian Art,Fan mounted as an album leaf,,China,Ming (1368–1644) or Qing dynasty (1644–1911),,,,Artist|Artist,In the style of,Unidentified Artist|Ma Yuan,"Chinese, active ca. 1190–1225",,Unidentified Artist|MA YUAN,Chinese,1190,1225,,1368,1911,Fan mounted as an album leaf; ink and white pigment on silk,9 5/8 x 10 in. (24.4 x 25.4 cm),"From the Collection of A. W. Bahr, Purchase, Fletcher Fund, 1947",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/51658,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -47.18.138,false,true,51737,Asian Art,Fan mounted as an album leaf,,China,Ming (1368–1644) or Qing dynasty (1644–1911),,,,Artist|Artist,Copy after (?),Unidentified Artist|Xia Gui,"Chinese, active ca. 1195–1230",,Unidentified Artist|Xia Gui,Chinese,1195,1230,,1368,1911,Fan mounted as an album leaf; ink on silk,8 7/16 x 9 7/8 in. (21.4 x 25.1 cm),"From the Collection of A. W. Bahr, Purchase, Fletcher Fund, 1947",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/51737,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -47.18.96,false,true,51708,Asian Art,Handscroll,,China,Ming (1368–1644) or Qing dynasty (1644–1911),,,,Artist|Artist,Copy after,Unidentified Artist|Qian Xuan,"Chinese, ca. 1235–before 1307",,Unidentified Artist|QIAN XUAN,Chinese,1235,1307,,1368,1911,Handscroll; ink and color on silk,13 3/8 x 22 1/2 in. (34.0 x 57.2 cm),"From the Collection of A. W. Bahr, Purchase, Fletcher Fund, 1947",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/51708,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -47.18.82,false,true,51695,Asian Art,Fan mounted as an album leaf,,China,Ming (1368–1644) or Qing dynasty (1644–1911),,,,Artist|Artist,After,Unidentified Artist|Emperor Huizong,"Chinese, 1082–1135; r. 1100–25",,Unidentified Artist|Huizong Emperor,Chinese,1082,1082,,1368,1911,Fan mounted as an album leaf; ink and color on silk,8 5/16 x 9 1/2 in. (21.1 x 24.1 cm),"From the Collection of A. W. Bahr, Purchase, Fletcher Fund, 1947",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/51695,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -47.18.144,false,true,51743,Asian Art,Fan mounted as an album leaf,,China,Ming (1368–1644) or Qing dynasty (1644–1911),,,,Artist|Artist,Copy after (?),Unidentified Artist|Liang Kai,"Chinese, active early 13th century",,Unidentified Artist|Liang Kai,Chinese,1200,1225,,1368,1911,Album leaf; ink and color on silk,9 1/4 x 9 1/3 in. (23.5 x 23.7 cm),"From the Collection of A. W. Bahr, Purchase, Fletcher Fund, 1947",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/51743,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -47.135.1,false,true,51749,Asian Art,Handscroll,,China,Yuan (1271–1368) or Ming (1368–1644) dynasty,,,,Artist|Artist,In the style of,Unidentified Artist|Zhao Yong,"Chinese, 1289–after 1360",Chinese,Unidentified Artist|Zhao Yong,Chinese,1289,1360,,1500,1599,Handscroll; ink and color on silk,10 1/2 x 68 in. (26.7 x 172.7 cm),"Gift of A. W. Bahr, 1947",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/51749,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -13.100.99,false,true,40195,Asian Art,Fan mounted as an album leaf,明 佚名 (舊傳)吳炳 枇杷 扇|Bird on a Loquat Tree,China,probably mid- to late Ming dynasty (1368–1644),,,,Artist|Artist,Formerly Attributed to,Unidentified Artist|Wu Bing,"Chinese, active 1190–1194",,Unidentified Artist|Wu Bing,Chinese,1190,1194,,1368,1644,Fan mounted as an album leaf; ink and color on silk,10 1/2 x 10 3/4 in. (26.7 x 27.3 cm),"John Stewart Kennedy Fund, 1913",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/40195,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.522,false,true,51570,Asian Art,Hanging scroll,,China,Song (960–1279) or Yuan (1271–1368) dynasty (?),,,,Artist|Artist,Formerly Attributed to,Unidentified Artist|Wu Daozi,"Chinese, 689–after 755",,Unidentified Artist|Wu Daozi,Chinese,0689,0760,,960,1368,"Hanging scroll; ink, color, and gold on silk",39 1/2 x 19 5/8 in. (100.3 x 49.8 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/51570,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1982.3.1,false,true,39938,Asian Art,Handscroll,北宋/金 傳黃宗道 舊傳李贊華 獵鹿圖 卷|Stag Hunt,China,Northern Song (960–1127) or Jin (1115–1234) dynasty,,,,Artist|Artist,Attributed to|Formerly Attributed to,Huang Zongdao|Li Zanhua,"Chinese, active ca. 1120|Chinese, 899–936",,HUANG ZONGDAO|LI ZANHUA,Chinese,1120 |0899,1120 |0936,,960,1234,Handscroll; ink and color on paper,Image: 9 11/16 × 31 1/16 in. (24.6 × 78.9 cm) Overall with mounting: 10 1/8 in. × 22 ft. 7 1/4 in. (25.7 × 689 cm),"Edward Elliott Family Collection, Purchase, The Dillon Fund Gift, 1982",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/39938,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -47.18.43a,false,true,36053,Asian Art,Fan mounted as an album leaf,,China,late Yuan (1271–1368)–early Ming (1368–1644) dynasty,,,,Artist|Artist,In the style of,Unidentified Artist|Wang Shen,"Chinese, active 1060–1080",,Unidentified Artist|Wang Shen,Chinese,1060,1080,,1271,1644,Fan mounted as an album leaf; ink and color on silk,9 3/4 x 10 in. (24.8 x 25.4 cm),"From the Collection of A. W. Bahr, Purchase, Fletcher Fund, 1947",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/36053,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -47.18.119,false,true,51726,Asian Art,Handscroll,,China,Five Dynasties (907–960)–early Song (906–1279) dynasty,,,,Artist|Artist,Formerly attributed to,Unidentified Artist|Xiao Zhao,"Chinese, active ca. 1150","Chinese, 10th century (?)",Unidentified Artist|Xiao Zhao,Chinese,1140,1160,,906,1279,Handscroll; ink and color on silk,12 3/4 x 51 3/4 in. (32.4 x 131.4 cm),"From the Collection of A. W. Bahr, Purchase, Fletcher Fund, 1947",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/51726,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -13.220.1,false,true,36011,Asian Art,Handscroll,明 佚名 (舊傳)夏珪 《長江萬里圖》 (前半卷)|River Landscape After Xia Gui,China,Ming dynasty (1368–1644),,,,Artist|Artist,Formerly Attributed to,Xia Gui|Unidentified Artist,"Chinese, active ca. 1195–1230",,Xia Gui|Unidentified Artist,Chinese,1195,1230,15th century,1400,1499,Handscroll; ink and color on silk,Image: 23 3/8 in. × 17 ft. 1 3/8 in. (59.4 × 521.7 cm),"John Stewart Kennedy Fund, 1913",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/36011,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -13.220.18,false,true,45674,Asian Art,Handscroll,明? 佚名 (舊傳)夏珪 長江萬里圖 (後半卷)|River Landscape after Xia Gui,China,Ming dynasty (1368–1644),,,,Artist|Artist,Formerly Attributed to,Xia Gui|Unidentified Artist,"Chinese, active ca. 1195–1230",,Xia Gui|Unidentified Artist,Chinese,1195,1230,15th century,1400,1499,Handscroll; ink and color on silk,Image: 23 3/8 in. × 16 ft. (59.4 × 487.7 cm),"John Stewart Kennedy Fund, 1913",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45674,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -47.18.3,false,true,36049,Asian Art,Handscroll,清 佚名 倣郭熙 溪山無盡圖 卷|Streams and Mountains Without End,China,Qing dynasty (1644–1911),,,,Artist|Artist,Formerly Attributed to,Unidentified Artist|Guo Xi,"Chinese, ca. 1000–ca. 1090",,Unidentified Artist|Guo Xi,Chinese,1000,1090,17th century,1600,1699,Handscroll; ink and color on silk,11 3/16 x 76 3/8 in. (28.4 x 194 cm),"From the Collection of A. W. Bahr, Purchase, Fletcher Fund, 1947",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/36049,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.267.3,false,true,36146,Asian Art,Hanging scroll,清 汪恭 摹趙孟頫肖像 軸|Copy of a Portrait of Zhao Mengfu,China,Qing dynasty (1644–1911),,,,Artist|Artist,Formerly attributed to,Wang Gong|Unidentified Artist,"Chinese, active early 19th century",,Wang Gong|Unidentified Artist,Chinese,1800,1899,19th century,1800,1899,Hanging scroll; ink and color on silk,25 1/8 x 12 1/8 in. (63.8 x 30.8 cm),"Gift of Robert Hatfield Ellsworth, in memory of La Ferne Hatfield Ellsworth, 1986",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/36146,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -13.100.28,false,true,51305,Asian Art,Handscroll,,China,Qing dynasty (1644–1911),,,,Artist|Artist,After,Dong Yuan|Unidentified Artist,"Chinese, active 930s–960s",,DONG YUAN|Unidentified Artist,Chinese,0930,0960,18th–19th century,1700,1899,Handscroll; ink on silk,15 3/8 in. × 23 ft. 6 1/2 in. (39.1 × 717.6 cm),"John Stewart Kennedy Fund, 1913",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/51305,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2001.423,false,true,61941,Asian Art,Folding screen,伝近衛信尋書・伝長谷川宗也絵 葛下絵色紙貼付『和漢朗詠集』屏風|Anthology of Japanese and Chinese Poems (Wakan rōeishū) with Underpainting of Arrowroot Vines,Japan,Edo period (1615–1868),,,,Calligrapher|Artist,Underpainting attributed to,Konoe Nobuhiro|Hasegawa Sōya,"Japanese, 1599–1649|Japanese, born 1590",,Konoe Nobuhiro|Hasegawa Sōya,Japanese|Japanese,1599 |1590,1649 |1690,early 17th century,1600,1633,Six-panel folding screen; ink and color on gilt paper,Image: 65 3/4 x 148 in. (167 x 375.9 cm),"Purchase, several members of The Chairman's Council Gifts, 2001",,,,,,,,,,,,Screens,,http://www.metmuseum.org/art/collection/search/61941,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"1975.268.72, .73",false,true,45234,Asian Art,Screen,"花鳥山水人物図屏風|Calligraphy with Landscapes, Figures, Flowers, and Birds",Japan,Edo period (1615–1868),,,,Artist|Calligrapher,,Nagasawa Rosetsu|Shishin Sōgin,"Japanese, 1754–1799|Japanese, 1726–1786",,Nagasawa Rosetsu|Shishin Sōgin,Japanese|Japanese,1754 |1726,1799 |1786,"second month, 1785",1785,1785,Sheets with calligraphy and painting attached to a pair of six-panel folding screens; ink on paper,Image (each screen): 62 3/8 in. x 11 ft. 7 5/8 in. (158.4 x 354.6 cm),"The Harry G. C. Packard Collection of Asian Art, Gift of Harry G. C. Packard, and Purchase, Fletcher, Rogers, Harris Brisbane Dick, and Louis V. Bell Funds, Joseph Pulitzer Bequest, and The Annenberg Fund Inc. Gift, 1975",,,,,,,,,,,,Screens,,http://www.metmuseum.org/art/collection/search/45234,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.827,false,true,78717,Asian Art,Illustrated book,,Japan,Edo period (1615–1868),,,,Artist|Calligrapher,,Yamaguchi Soken|Minagawa Kien,"Japanese, 1759–1818|Japanese, 1734–1807",,Yamaguchi Soken|Minagawa Kien,Japanese|Japanese,1759 |1734,1818 |1807,1804,1804,1804,Woodblock printed book; ink on paper,10 7/16 × 7 5/16 in. (26.5 × 18.5 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78717,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.826a–c,false,true,78716,Asian Art,Illustrated books,,Japan,Edo period (1615–1868),,,,Artist|Calligrapher,,Yamaguchi Soken|Minagawa Kien,"Japanese, 1759–1818|Japanese, 1734–1807",,Yamaguchi Soken|Minagawa Kien,Japanese|Japanese,1759 |1734,1818 |1807,1800,1800,1800,Set of three woodblock printed books; ink on paper,each: 10 7/16 × 7 3/16 in. (26.5 × 18.3 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78716,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.891,false,true,78781,Asian Art,Illustrated book,,Japan,Edo period (1615–1868),,,,Artist|Calligrapher,,Nagayama Koin (Hirotora)|Shokusanjin (Ōta Nanpo),"Japanese, 1765–1849|Japanese, 1749–1823",", and author",Nagayama Koin (Hirotora),Japanese|Japanese,1765 |1749,1849 |1823,1824,1824,1824,Woodblock printed book; ink and color on paper,10 1/16 × 7 3/16 in. (25.5 × 18.2 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78781,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.787a–c,false,true,78689,Asian Art,Illustrated books,,Japan,Edo period (1615–1868),,,,Calligrapher|Calligrapher,After|After,Hasegawa Myōtei|Sankinshi,"Japanese, active late 17th century|Japanese",,Hasegawa Myōtei|Sankinshi,Japanese|Japanese,1667,1699,1838,1838,1838,Set of three woodblock printed books; ink on paper,each: 10 7/16 × 7 3/8 in. (26.5 × 18.7 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78689,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.794,false,true,78810,Asian Art,Illustrated book,,Japan,Edo period (1615–1868),,,,Artist|Artist|Artist,,Rankō|Watanabe Nangaku|Tetsuzan Sōdon,"Japanese|Japanese, 1763–1813|Japanese, 1532–1617",,Rankō|Watanabe Nangaku|Tetsuzan Sōdon,Japanese|Japanese|Japanese,1763 |1532,1813 |1617,1806,1806,1806,"Woodblock printed book (orihon, accordion-style); ink and color on paper with metallic pigments (?)",9 13/16 × 7 1/16 in. (25 × 18 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78810,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.743,false,true,78645,Asian Art,Illustrated book,『花月帖』|Album of Flowers and the Moon (Kagetsu jō),Japan,Edo period (1615–1868),,,,Artist|Artist|Artist,,Mori Tetsuzan|Maruyama Ōshin|Kawamura Kihō,"Japanese, 1775–1841|Japanese, 1790–1838|Japanese, 1778–1852",,Mori Tetsuzan|Maruyama Ōshin|Kawamura Kihō,Japanese|Japanese|Japanese,1775 |1790 |1778,1841 |1838 |1852,1836,1836,1836,"Woodblock-printed book (orihon, accordion-style); ink and color on paper",9 1/16 × 6 5/16 in. (23 × 16 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78645,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.876,false,true,78766,Asian Art,Illustrated book,,Japan,Edo period (1615–1868),,,,Artist|Artist|Artist|Artist|Artist,,Genki (Komai Ki)|Matsumura Goshun|Watanabe Nangaku|Miguma Shiko|Fusetsu Yujo,"Japanese, 1747–1797|Japanese, 1752–1811|Japanese, 1763–1813|Japanese|Japanese",,Genki|Matsumura Goshun|Watanabe Nangaku|Miguma Shiko|Fusetsu Yujo,Japanese|Japanese|Japanese|Japanese|Japanese,1747 |1752 |1763,1797 |1811 |1813,1793,1793,1793,"Woodblock printed book (orihon, accordion-style); ink and color on paper",11 × 8 3/8 in. (28 × 21.2 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78766,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2013.781a, b",false,true,78683,Asian Art,Illustrated books,,Japan,Edo period (1615–1868),,,,Artist|Artist|Artist|Artist|Artist,,Yosa Buson|Sō Shiseki|Nankei|Rantei|Ōishi Matora,"Japanese, 1716–1783|Japanese, 1715–1786|Japanese|Japanese,|Japanese, 1793–1833",et al,Yosa Buson|Sō Shiseki|Nankei|Rantei|Ōishi Matora,Japanese|Japanese|Japanese|Japanese|Japanese,1716 |1715 |1793,1783 |1786 |1833,"1812, 1814",1812,1814,Set of two woodblock printed books; ink and color on paper,each: 11 × 7 1/2 in. (28 × 19 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78683,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.815,false,true,78705,Asian Art,Illustrated book,,Japan,Edo period (1615–1868),,,,Artist|Artist|Artist|Artist|Artist,,Tsukioka Settei|Ōnishi Chinnen|Teisai Hokuba|Ikeda Koson|Nankō,"Japanese, 1710–1786|1792–1851|Japanese, 1771–1844|Japanese, 1803–1868|Japanese",,Tsukioka Settei|Ōnishi Chinnen|Teisai Hokuba|Ikeda Koson|Nankō,Japanese|Japanese|Japanese|Japanese,1710 |1792 |1771 |1803,1786 |1851 |1844 |1868,1830,1830,1830,"Woodblock printed book (orihon, accordion-style); ink and color on paper",10 1/16 × 6 11/16 in. (25.5 × 17 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78705,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2013.843a, b",false,true,78733,Asian Art,Illustrated books,,Japan,Edo period (1615–1868),,,,Artist|Artist|Artist|Artist,,Nukina Kaioku|Shiokawa Bunrin|Kawabata Gyokushō|Musō Soseki,"Japanese, 1778–1863|Japanese, 1808–1877|Japanese, 1842–1913|Japanese, 1275–1351",", et al",Nukina Kaioku|Shiokawa Bunrin|Kawabata Gyokushō|Musō Soseki,Japanese|Japanese|Japanese|Japanese,1778 |1808 |1842 |1275,1863 |1877 |1913 |1351,postscript dated 1861,1861,1861,Set of two woodblock printed books; ink and color on paper,each: 10 3/16 × 6 11/16 in. (25.8 × 17 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78733,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.873,false,true,78763,Asian Art,Illustrated book,,Japan,Edo period (1615–1868),,,,Artist|Artist|Artist|Artist|Artist|Artist|Artist|Artist|Artist|Artist|Artist|Artist|Artist|Artist|Artist|Artist|Artist|Artist|Artist|Artist|Artist,,Matsumura Goshun|Matsumura Keibun|Suzuki Harushige|Aikawa Minwa|Nagasawa Roshu|Yamaguchi Soken|Okamoto Toyohiko|Oku Bunmei|Ki Chikudō|Gan Tai|Sō Geppō|Shibata Gitō|Fukuchi Hakuei|Kawamura Kihō|Shiba Kōkan|Yoshimura Kōkei|Hatta Koshū|Watanabe Nangaku|Kinoshita Ōju|Maruyama Ōzui|Hara Zaimei,"Japanese, 1752–1811|Japanese, 1779–1843|1747–1818|Japanese, active 1806–1821|Japanese, 1767–1847|Japanese, 1759–1818|1773–1845|Japanese|Japanese, died 1825|Japanese, 1760–1839|Japanese|Japanese, active early 19th century|Japanese, 1778–1852|Japanese, 1747–1818|Japanese|Japanese|Japanese, 1763–1813|Japanese|Japanese|Japanese",", et al",Matsumura Goshun|Matsumura Keibun|Suzuki Harushige|Aikawa Minwa|Nagasawa Roshu|Yamaguchi Soken|Okamoto Toyohiko|Oku Bunmei|Ki Chikudō|Gan Tai|Sō Geppō|Shibata Gitō|Fukuchi Hakuei|Kawamura Kihō|Shiba Kōkan|Yoshimura Kōkei|Hatta Koshū|Watanabe Nangaku|Kinoshita Ōju|Maruyama Ōzui|Hara Zaimei,Japanese|Japanese|Japanese|Japanese|Japanese|Japanese|Japanese|Japanese|Japanese|Japanese|Japanese|Japanese|Japanese|Japanese|Japanese|Japanese|Japanese|Japanese,1752 |1779 |1747 |1806 |1767 |1759 |1773 |1760 |1800 |1778 |1747 |1763,1811 |1843 |1818 |1821 |1847 |1818 |1845 |1825 |1839 |1833 |1852 |1818 |1813,1814,1814,1814,Woodblock printed book; ink and color on paper,10 3/8 × 7 1/2 in. (26.3 × 19 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78763,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.793,false,true,78809,Asian Art,Illustrated book,『男踏歌』|Men’s Stomping Dances (Otoko dōka),Japan,Edo period (1615–1868),,,,Artist|Artist|Artist|Artist,,Kitagawa Utamaro|Rekisentei Eiri|Katsushika Hokusai|Chōbunsai Eishi,"Japanese, 1753?–1806|Japanese, active ca. 1789–1801|Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)|Japanese, 1756–1829",,Kitagawa Utamaro|Rekisentei Eiri|Katsushika Hokusai|Chōbunsai Eishi,Japanese|Japanese|Japanese|Japanese,1753 |1789 |1760 |1756,1806 |1801 |1849 |1829,1798,1798,1798,Woodblock printed book; ink and color on paper,10 1/16 × 7 1/2 in. (25.5 × 19 cm),"Purchase, Mary and James G. Wallach Family Foundation Gift, in honor of John T. Carpenter, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78809,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.870,false,true,78760,Asian Art,Illustrated book,,Japan,Edo period (1615–1868),,,,Artist|Artist|Artist|Artist|Artist,,Kuwagata Keisai|Sakai Hōitsu|Tani Bunchō|Katsushika Hokusai|Ōnishi Chinnen,"Japanese, 1764–1824|Japanese, 1761–1828|Japanese, 1763–1840|Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)|1792–1851",", et al",Kuwagata Keisai|Sakai Hōitsu|Tani Bunchō|Katsushika Hokusai|Ōnishi Chinnen,Japanese|Japanese|Japanese|Japanese|Japanese,1764 |1761 |1763 |1760 |1792,1824 |1828 |1840 |1849 |1851,ca. 1822–34,1822,1834,Woodblock printed book; ink on paper,7 3/8 × 5 1/8 in. (18.7 × 13 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78760,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.744,false,true,78646,Asian Art,Illustrated book,,Japan,Meiji period (1868–1912),,,,Artist|Artist|Artist,,Mori Tetsuzan|Maruyama Ōshin|Kawamura Kihō,"Japanese, 1775–1841|Japanese, 1790–1838|Japanese, 1778–1852",and others,Mori Tetsuzan|Maruyama Ōshin|Kawamura Kihō,Japanese|Japanese|Japanese,1775 |1790 |1778,1841 |1838 |1852,late 19th century reprint of original edition of 1836,1875,1899,"Woodblock printed book (orihon, accordion-style); ink and color on paper",10 1/8 × 6 1/2 in. (25.7 × 16.5 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78646,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2832,false,true,53719,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Calligrapher|Artist|Artist,or,Yokogawa Horitake|Utagawa Kunisada|Utagawa Toyokuni II,"Japanese, 1786–1865|Japanese, 1777–1835",,Yokogawa Horitake|Utagawa Kunisada|Utagawa Toyokuni II,Japanese|Japanese,1786 |1777,1865 |1835,1786–1864,1786,1864,Polychrome woodblock print; ink and color on paper,14 1/4 x 9 5/8 in. (36.2 x 24.4 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/53719,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1856,false,true,45029,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Calligrapher|Artist,after,Senseki|Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Senseki|Katsushika Hokusai,Japanese|Japanese,1760,1849,ca. 1816–20,1816,1820,Polychrome woodblock print; ink and color on paper,H. 14 3/16 in. (36 cm); W. 9 5/8 in. (24.4 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45029,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3116,false,true,37370,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist|Calligrapher,,Tōshūsai Sharaku|Ueda Shikibuchi,"Japanese, active 1794–95|Japanese, 1819–1879",,Tōshūsai Sharaku|Ueda Shikibuchi,Japanese|Japanese,1794 |1819,1795 |1879,1794,1794,1794,"Polychrome woodblock print; ink, color, white mica on paper",UMPN BV,"Harris Brisbane Dick Fund and Rogers Fund, 1949",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37370,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1855,false,true,45028,Asian Art,Print,古今書画鑑 熊谷蓮生坊真跡|Bird-and-Flower Paintings,Japan,Edo period (1615–1868),,,,Artist|Calligrapher,after,Katsushika Hokusai|Kumagai Naozane,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)|Japanese, 1141–1208",,Katsushika Hokusai|Kumagai Naozane,Japanese|Japanese,1760 |1141,1849 |1208,ca. 1816–20,1816,1820,Polychrome woodblock print; ink and color on paper,H. 14 7/16 in. (36.7 cm); W. 10 1/16 in. (25.6 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45028,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP206,false,true,36683,Asian Art,Print,小倉擬百人一首|Album of Eighty-eight Prints from the series Ogura Imitations of One Hundred Poems by One Hundred Poets (Ogura nazorae hyakunin isshu),Japan,Edo period (1615–1868),,,,Artist|Artist|Artist,,Utagawa Kuniyoshi|Utagawa Hiroshige|Utagawa Kunisada,"Japanese, 1797–1861|Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)|Japanese, 1786–1865",,"Utagawa Kuniyoshi|Utagawa, Hiroshige|Utagawa Kunisada",Japanese|Japanese|Japanese,1797 |1797 |1786,1861 |1858 |1865,about 1845–48,1845,1848,Album of 88 polychrome woodblock prints; ink and color on paper,14 × 9 1/4 × 1 in. (35.6 × 23.5 × 2.5 cm),"Gift of Mary L. Cassilly, 1894",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36683,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2011.129a–e,false,true,76559,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist|Artist|Artist|Artist|Artist,,Jukakudō Masakuni|Jugyōdō Umekuni|Juyōdō Minekuni|Jushōdō Fujikuni|Hōgadō Kishikuni,"Japanese, active 1820s|Japanese, active 1820s|Japanese, active 1820s|Japanese, active 1820s|Japanese, active 1820s",,Jukakudō Masakuni|Jugyōdō Umekuni|Juyōdō Minekuni|Jushōdō Fujikuni|Hōgadō Kishikuni,Japanese|Japanese|Japanese|Japanese|Japanese,1820 |1820 |1820 |1820 |1820,1829 |1829 |1829 |1829 |1829,1824,1824,1824,Pentaptych of polychrome woodblock prints,Each sheet (ôban tat-e pentaptych): 14 3/4 x 10 5/8 in. (37.5 x 27 cm),"Purchase, Friends of Asian Art Gifts, in honor of James C. Y. Watt, 2011",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/76559,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1999.457.2,false,true,53711,Asian Art,Prints,,Japan,Meiji period (1868–1912),,,,Artist|Artist|Artist|Artist|Artist,"prints 13,17,18,20,26,29 by|print 2 by|print 28 by|prints 25,27,31,32 by",Yōshū (Hashimoto) Chikanobu|Toyohara Kunichika|Morikawa Chikashige|Hiroaki|Kunimasa,"Japanese, 1838–1912|Japanese, 1835–1900|Japanese, second half of 19th century|Japanese, 1871–1945|Japanese",,Yōshū (Hashimoto) Chikanobu|Toyohara Kunichika|Morikawa Chikashige|Hiroaki|Kunimasa,Japanese|Japanese|Japanese,1838 |1835 |1850 |1871,1912 |1900 |1899 |1945,1883–86,1883,1886,Album of thirty-two triptychs of polychrome woodblock prints; ink and color on paper,Each H. 14 in. (35.6 cm); W. 9 1/4 in. (23.5 cm),"Gift of Eliot C. Nolen, 1999",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/53711,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.3,false,true,663886,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Calligrapher|Artist,,Mokuan Shōtō|Unidentified Artist,"Chinese, 1611–1684","Japanese, active late 17th century",Mokuan Shōtō|Unidentified Artist,Chinese,1611,1684,1676,1676,1676,Hanging scroll; ink and color on paper,Image: 47 in. × 22 3/4 in. (119.4 × 57.8 cm) Overall with mounting: 76 1/4 × 27 15/16 in. (193.7 × 71 cm) Overall with knobs: 76 1/4 × 30 1/4 in. (193.7 × 76.8 cm),"Purchase, Brooke Russell Astor Bequest, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/663886,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.268.131,false,true,45790,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist|Calligrapher,,Katsukawa Shunshō|Tegara no Okamochi,"Japanese, 1726–1792|1734–1812",,Katsukawa Shunshō|Tegara no Okamochi,Japanese|Japanese,1726 |1734,1792 |1812,1798,1798,1798,Hanging scroll; ink and color on paper,33 3/4 x 11 5/16 in. (85.7 x 28.8 cm),"The Harry G. C. Packard Collection of Asian Art, Gift of Harry G. C. Packard, and Purchase, Fletcher, Rogers, Harris Brisbane Dick, and Louis V. Bell Funds, Joseph Pulitzer Bequest, and The Annenberg Fund Inc. Gift, 1975",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45790,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2006.115,false,true,73192,Asian Art,Hanging scroll,布袋図 拄杖擊破三千界。彌勒撫掌笑呵呵,明月清風無。」|Hotei,Japan,Edo period (1615–1868),,,,Artist|Calligrapher,,Kano Takanobu|Tetsuzan Sōdon,"Japanese, 1571–1618|Japanese, 1532–1617",,Kano Takanobu|Tetsuzan Sōdon,Japanese|Japanese,1571 |1532,1618 |1617,dated 1616,1616,1616,Hanging scroll; ink and color on paper,Image: 27 1/2 x 15 in. (69.9 x 38.1 cm) Overall with mounting: 59 1/2 x 18 3/4 in. (151.1 x 47.6 cm) Overall with rollers: 59 1/2 x 20 1/2 in. (151.1 x 52.1 cm),"Funds from various donors, 2006",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/73192,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1979.407.1,false,true,45335,Asian Art,Hanging scroll,松花堂昭乗書・伝俵屋宗達下絵 立葵下絵和歌色紙 藤原興風|Poem by Onakatomi Yoshinobu with Underpainting of Hollyhocks,Japan,Edo period (1615–1868),,,,Calligrapher|Artist,Calligraphy by|Underpainting attributed to,Shōkadō Shōjō|Tawaraya Sōtatsu,"Japanese, 1584?–1639|Japanese, died ca. 1640",,Shōkadō Shōjō|Tawaraya Sōtatsu,Japanese|Japanese,1584 |1540,1639 |1640,early 17th century,1600,1633,"Poem card (shikishi) mounted as a hanging scroll; ink, gold, and silver on colored paper",Image: 7 15/16 x 6 15/16 in. (20.2 x 17.6 cm) Overall with mounting: 53 1/4 x 20 5/8 in. (135.3 x 52.4 cm) Overall with knobs: 53 1/4 x 23 in. (135.3 x 58.4 cm),"Purchase, Mrs. Jackson Burke Gift, 1979",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45335,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.268.133,false,true,45183,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist|Artist|Artist|Artist,,Tani Bunchō|Watanabe Kazan|Sakai Hōitsu|sixty-six others,"Japanese, 1763–1840|Japanese, 1793–1841|Japanese, 1761–1828|Japanese",,Tani Bunchō|Watanabe Kazan|Sakai Hōitsu|sixty-six others,Japanese|Japanese|Japanese|Japanese,1763 |1793 |1761,1840 |1841 |1828,1820,1820,1820,Hanging scroll; ink and color on paper,Image: 31 3/8 x 23 3/8 in. (79.7 x 59.4 cm) Overall with mounting: 64 5/8 x 25 3/4 in. (164.1 x 65.4 cm) Overall with knobs: 64 5/8 x 28 1/8 in. (164.1 x 71.4 cm),"The Harry G. C. Packard Collection of Asian Art, Gift of Harry G. C. Packard, and Purchase, Fletcher, Rogers, Harris Brisbane Dick, and Louis V. Bell Funds, Joseph Pulitzer Bequest, and The Annenberg Fund Inc. Gift, 1975",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45183,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.268.39,false,true,39659,Asian Art,Hanging scroll,『東坡笠屐図』|Su Shi (Dongpo) in a Bamboo Hat and Clogs,Japan,Muromachi period (1392–1573),,,,Artist|Artist|Artist|Artist|Artist,Inscribed by|Inscribed by|Inscribed by|Inscribed by|Inscribed by,Kyūen Ryūchin|Kōshi Ehō|Nankō Sōgen|Zuigan Ryūsei|Chikkō Zengo,"Japanese, died 1498|Japanese, 1414–ca.1465|Japanese, 1378–1463|Japanese, 1384–1460|Japanese, died after 1464",,Kyūen Ryūchin|Kōshi Ehō|Nankō Sōgen|Zuigan Ryūsei|Chikkō Zengo,Japanese|Japanese|Japanese|Japanese|Japanese,1414 |1378 |1384,1498 |1465 |1463 |1460 |1464,before 1460,1392,1459,Hanging scroll; ink on paper,Image: 42 3/4 x 13 1/8 in. (108.6 x 33.3 cm) Overall with mounting: 74 1/2 x 17 5/8 in. (189.2 x 44.8 cm) Overall with rollers: 74 1/2 x 17 5/8 x 19 3/4 in. (189.2 x 44.8 x 50.2 cm),"The Harry G. C. Packard Collection of Asian Art, Gift of Harry G. C. Packard, and Purchase, Fletcher, Rogers, Harris Brisbane Dick, and Louis V. Bell Funds, Joseph Pulitzer Bequest, and The Annenberg Fund Inc. Gift, 1975",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/39659,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JIB50,false,true,57650,Asian Art,Illustrated book,,Japan,Edo period (1615–1868),,,,Artist|Artist|Artist,3 unknown artists:,Aoigaoka Keisei|Utagawa Kuniyoshi|Keisai Eisen,"Japanese, active 1820s–1830s|Japanese, 1797–1861|Japanese, 1790–1848",", Keishin, and Yanagawa",Aoigaoka Keisei|Utagawa Kuniyoshi|Keisai Eisen,Japanese|Japanese|Japanese,1810 |1797 |1790,1840 |1861 |1848,,1615,1868,Ink on paper,8 5/8 × 6 × 3/4 in. (21.9 × 15.2 × 1.9 cm),"Rogers Fund, 1918",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/57650,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -56.116,false,true,44899,Asian Art,Screen,,Japan,Edo period (1615–1868),,,,Calligrapher|Artist,,Shōkadō Shōjō|Maruyama Ōkyo,"Japanese, 1584?–1639|Japanese, 1733–1795",,Shōkadō Shōjō|Maruyama Ōkyo,Japanese|Japanese,1584 |1733,1639 |1795,17th century,1600,1699,Eight-panel folding screen; ink and gold,43 x 129 in. (109.2 x 327.7 cm),"Gift of Mrs. H. F. Stone and Mrs. Leon Durand Bonnet, 1956",,,,,,,,,,,,Screens,,http://www.metmuseum.org/art/collection/search/44899,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.62a–f,false,true,45701,Asian Art,Screen,狩野探幽・狩野尚信・清原雪信 花鳥図屏風|Birds and Flowers,Japan,Edo period (1615–1868),,,,Artist|Artist|Artist,,Kano Tan'yū|Kiyohara Yukinobu|Kano Naonobu,"Japanese, 1602–1674|Japanese, 1643–1682|Japanese, 1607–1650",,Kano Tan'yū|Kiyohara Yukinobu|Kano Naonobu,Japanese|Japanese|Japanese,1602 |1643 |1607,1674 |1682 |1650,17th century,1600,1699,Six-panel folding screen; ink and color on silk,70 in. x 12 ft. 11 1/2 in. (177.8 x 395 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Screens,,http://www.metmuseum.org/art/collection/search/45701,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.213.1–.30,false,true,74463,Asian Art,Album,,Japan,Edo period (1615–1868),,,,Artist|Artist|Artist|Artist|Artist|Artist|Artist|Artist|Artist|Artist|Artist|Artist|Artist|Artist|Artist|Artist|Artist|Artist|Artist|Artist|Artist|Artist|Artist|Artist|Artist|Artist|Artist|Artist,.16: After|.2: After|.3: After|.4: After|.6: After|.7: After|.8: After|.9: After|.10: After|.14: After|.15: After|.17: After|.18: After|.19: After|.20: After|.21: After|.22: After|.23: After|.24: After|.25: After|.26: After|.27: After|.28: After|.29: After|.30: After|.1. After|.13 After,Yan Ciping|Kano Tsunenobu|Yintuoluo|Wang Lipen|Po Citing|Puming|Sheng Mou|Danzhirui|Jin Dashou|Wang Yuan|Muqi|Guo Xi|Zhang Yüehu|Luochuang|Ren Renfa|Luo Xinzhong|Wen Tong|Xia Yong|Fan Anren|Cinshan|Kongshan|Su Xianzu|Li Anzhong|Xuejian|Wang Moji|Daisong|Zhao Mengfu|Chen Rong,"Chibese, active 12th century|Japanese, 1636–1713|Chinese, active 13th century|Chinese, born late Yuan, active early Ming dynasty|Chinese, died 1337(?); active first half of the 14th century|Chinese, active 14th century|Chinese, active ca. 1310–1360|active early 14th century|active 13th century|Chinese, ca. 1280–after 1349|Chinese, ca. 1210–after 1269|Chinese, ca. 1000–ca. 1090|active 13th century|active 13th century|Chinese, 1255–1328|active 13th century|Chinese, 1019–1079|Chinese, active mid-14th century|active 13th century|active 12th century|active 14th century|699–759|Chinese, 727–779|Chinese, 1254–1322|active 1235–62",,Yan Ciping|Kano Tsunenobu|Yintuoluo|Wang Lipen|Po Citing|Puming|Sheng Mou|Danzhirui|Jin Dashou|Wang Yuan|Muqi|Guo Xi|Zhang Yüehu|Luochuang|Ren Renfa|Luo Xinzhong|Wen Tong|XIA YONG|Fan Anren|Cinshan|Kongshan|Su Xianzu|Li Anzhong|Xuejian|Wang Moji|Daisong|Zhao Mengfu|Chen Rong,Chinese|Japanese|Chinese|Chinese|Chinese|Chinese|Chinese|Chinese|Chinese|Chinese|Chinese|Chinese|Chinese|Chinese|Chinese|Chinese|Chinese|Chinese|Chinese|Chinese|Chinese|Chinese|Chinese|Chinese|Chinese,1100 |1636 |0013 |1237 |1300 |1310 |0014 |0013 |1270 |1210 |1000 |0013 |0013 |1255 |0013 |1019 |1336 |0013 |0012 |0014 |0699 |0727 |1254 |1235,1199 |1713 |0013 |1337 |1399 |1360 |0014 |0013 |1359 |1269 |1090 |0013 |0013 |1328 |0013 |1079 |1370 |0013 |0012 |0014 |0759 |0779 |1322 |1262,17th century,1636,1699,Album of thirty paintings; ink and color on silk,11 7/16 x 16 3/4 in. (29 x 42.5 cm),"Gift of Mr. and Mrs. Harry Rubin, 1972",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/74463,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1985.97,false,true,40462,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Calligrapher|Artist,Inscribed by,Tangai Jōgi|Itō Jakuchū,"Japanese, 1693–1764|Japanese, 1716–1800",,Tangai Jōgi|Itō Jakuchū,Japanese|Japanese,1693 |1716,1764 |1800,18th century,1716,1764,Hanging scroll; ink on paper,Image: 49 1/2 x 18 7/8 in. (125.7 x 47.9 cm) Overall with mounting: 75 5/8 x 24 1/4 in. (192.1 x 61.6 cm) Overall with knobs: 75 5/8 x 26 1/2 in. (192.1 x 67.3 cm),"Purchase, Lita Annenberg Hazen Charitable Trust Gift, in honor of Cynthia and Leon Polsky, 1985",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/40462,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JIB94,false,true,45442,Asian Art,Illustrated book,Kyoka Kijin Gazo-shu|Poems on Portraits of the Famous and the Infamous,Japan,Edo period (1615–1868),,,,Artist|Artist|Artist,Attributed to,Utagawa Kunisada|Utagawa Hiroshige|Ryūsen,"Japanese, 1786–1865|Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)|Japanese, active mid–19th century",,"Utagawa Kunisada|Utagawa, Hiroshige|Ryūsen",Japanese|Japanese|Japanese,1786 |1797 |1834,1865 |1858 |1866,19th century,1700,1868,Polychrome Woodblock printed book; gold lacquer on red lacquer ground,8 1/4 × 5 3/4 × 1/2 in. (21 × 14.6 × 1.3 cm),"The Howard Mansfield Collection, Gift of Howard Mansfield, 1936",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/45442,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2342,false,true,54127,Asian Art,Woodblock print,,Japan,,,,,Artist|Calligrapher,,Totoya Hokkei|Shibayamadō,"Japanese, 1780–1850|Japanese",,Totoya Hokkei|Shibayamadō,Japanese|Japanese,1780,1850,19th century,1800,1899,Part of an album of woodblock prints (surimono); ink and color on paper,8 5/16 x 7 1/4 in. (21.1 x 18.4 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54127,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2260,false,true,54030,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Calligrapher|Artist,,Yuyu Hanko|Teisai Hokuba,"Japanese|Japanese, 1771–1844",,Yuyu Hanko|Teisai Hokuba,Japanese|Japanese,1771,1844,19th century,1800,1899,Part of an album of woodblock prints (surimono); ink and color on paper,5 7/16 x 7 3/8 in. (13.8 x 18.7 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54030,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2261,false,true,54031,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Calligrapher|Artist,,Yuyu Hanko|Teisai Hokuba,"Japanese|Japanese, 1771–1844",,Yuyu Hanko|Teisai Hokuba,Japanese|Japanese,1771,1844,19th century,1800,1899,Part of an album of woodblock prints (surimono); ink and color on paper,8 1/4 x 7 3/8 in. (21 x 18.7 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54031,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP205,false,true,36682,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist|Artist|Artist,Nineteen prints by|Two prints by|Three prints by,Kikugawa Eizan|Utagawa Kunisada|Utagawa Kunimaru,"Japanese, 1787–1867|Japanese, 1786–1865|Japanese, 1793–1829",,Kikugawa Eizan|Utagawa Kunisada|Kunimaru,Japanese|Japanese|Japanese,1787 |1786 |1793,1867 |1865 |1829,19th century,1800,1868,Album of 24 polychrome woodblock prints; ink and color on paper,14 5/8 × 9 3/4 × 1 1/4 in. (37.1 × 24.8 × 3.2 cm),"Gift of Mary L. Cassilly, 1894",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36682,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP211,false,true,36688,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist|Artist|Artist,,Utagawa Kuniyoshi|Utagawa Kunisada|Utagawa Hiroshige,"Japanese, 1797–1861|Japanese, 1786–1865|Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa Kuniyoshi|Utagawa Kunisada|Utagawa, Hiroshige",Japanese|Japanese|Japanese,1797 |1786 |1797,1861 |1865 |1858,19th century,1800,1865,Album of 98 polychrome woodblock prints; ink and color on paper,14 1/2 × 9 1/2 × 1 3/4 in. (36.8 × 24.1 × 4.4 cm),"Gift of the Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36688,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP208,false,true,36685,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist|Artist|Artist|Artist|Artist,Twenty-eight prints by|Twenty-six prints by|Three prints by|Three prints by|Three prints by,Utagawa Kuniyoshi|Utagawa Kunisada|Utagawa Kunimaro|Utagawa Hiroshige|Utagawa Kuniteru,"Japanese, 1797–1861|Japanese, 1786–1865|Japanese, active ca. 1840–70|Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)|Japanese, 1830–1874",,"Utagawa Kuniyoshi|Utagawa Kunisada|Utagawa, Kunimaro|Utagawa, Hiroshige|Utagawa Kuniteru",Japanese|Japanese|Japanese|Japanese|Japanese,1797 |1786 |1840 |1797 |1830,1861 |1865 |1870 |1858 |1874,19th century,1800,1868,Album of 58 polychrome woodblock prints; ink and color on paper,14 × 10 × 1 1/2 in. (35.6 × 25.4 × 3.8 cm),"Gift of the Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36685,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1115,false,true,55037,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist|Artist|Artist|Artist|Artist|Artist|Artist|Artist|Artist,,Utagawa Kuniyoshi|Utagawa Kunisada|Utagawa Yoshikazu|Utagawa Yoshitsuna|Utagawa Yoshitora|Utagawa Yoshitsuru|Utagawa Yoshihide|Utagawa (Gountei) Sadahide|Utagawa Yoshitsuya,"Japanese, 1797–1861|Japanese, 1786–1865|Japanese, active ca. 1850–1870|Japanese, active ca. 1850–1860|Japanese, active ca. 1850–80|Japanese, active ca. 1840–1850|Japanese, 1832–1902|Japanese, 1807–1878/79|Japanese, 1822–1866",,"Utagawa Kuniyoshi|Utagawa Kunisada|Utagawa Yoshikazu|Utagawa Yoshitsuna|Utagawa Yoshitora|Utagawa, Yoshitsuru|Utagawa, Yoshihide|Utagawa (Goutei) Sadahide|Utagawa Yoshitsuya",Japanese|Japanese|Japanese|Japanese|Japanese|Japanese|Japanese|Japanese|Japanese,1797 |1786 |1845 |1850 |1845 |1840 |1832 |1807 |1822,1861 |1865 |1870 |1860 |1880 |1850 |1902 |1879 |1866,19th century,1800,1899,Album of 15 triptychs of polychrome woodblock prints; ink and color on paper,13 15/16 × 9 7/8 × 3/4 in. (35.4 × 25.1 × 1.9 cm),"Gift of Harold de Raasloff, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55037,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1984.347,false,true,49001,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist|Calligrapher|Calligrapher,Painted and inscribed by,Rai San'yō|Rai Kyohei (Shunsō)|Rai Baishi,"1780–1832|Japanese, 1756–1834|Japanese, 1759–1843",,Rai San'yō|Rai Kyohei|Rai Baishi,Japanese|Japanese|Japanese,1780 |1756 |1759,1832 |1834 |1843,19th century,1800,1832,Hanging scroll; ink on paper,18 1/2 x 11 5/16 in. (47 x 28.7 cm),"Purchase, Bequest of John L Cadwaldader, Gift of Mrs. Russell Sage, and Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, by exchange, 1984",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49001,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.268.74,false,true,49035,Asian Art,Hanging scroll,"長沢蘆筆・皆川淇園賛 白鶏図|Rooster, Hen and Chicks",Japan,Edo period (1615–1868),,,,Artist|Calligrapher,Attributed to,Nagasawa Rosetsu|Minagawa Kien,"Japanese, 1754–1799|Japanese, 1734–1807",,Nagasawa Rosetsu|Minagawa Kien,Japanese|Japanese,1754 |1734,1799 |1807,late 18th century,1767,1799,Hanging scroll; ink on paper,Image: 49 1/8 x 10 3/4 in. (124.8 x 27.3 cm) Overall with mounting: 81 1/4 x 11 7/8 in. (206.4 x 30.2 cm) Overall with knobs: 81 1/4 x 13 7/8 in. (206.4 x 35.2 cm),"The Harry G. C. Packard Collection of Asian Art, Gift of Harry G. C. Packard, and Purchase, Fletcher, Rogers, Harris Brisbane Dick, and Louis V. Bell Funds, Joseph Pulitzer Bequest, and The Annenberg Fund Inc. Gift, 1975",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49035,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -17.118.72,false,true,62891,Asian Art,Teabowl,,Japan,,,,,Artist,,Widow of Ameya,active early 16th century,,Ameya,Korean,0016,0016,ca. 1550,1540,1560,Clay covered with glaze (Amayaki Raku),H. 3 in. (7.6 cm); Diam. 4 1/2 in. (11.4 cm),"Rogers Fund, 1917",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/62891,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -25.215.5,false,true,63092,Asian Art,Cake dish,,Japan,Edo period (1615–1868),,,,Artist,,Gempin,"Chinese, died 1771",,Gempin,Chinese,1671,1771,1645,1645,1645,Porcelaneous clay covered with a transparent glaze over blue decoration,H. 2 1/2 in. (6.4 cm); W. 6 3/4 in. (17.1 cm); L. 9 in. (22.9 cm),"Fletcher Fund, 1925",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/63092,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.268.83,false,true,49071,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Hi Kangen,active 18th century,,Hi Kangen,Chinese,0018,0018,dated 1756,1756,1756,Hanging scroll; ink and color on paper,35 5/16 x 13 7/16 in. (89.7 x 34.2 cm),"The Harry G. C. Packard Collection of Asian Art, Gift of Harry G. C. Packard, and Purchase, Fletcher, Rogers, Harris Brisbane Dick, and Louis V. Bell Funds, Joseph Pulitzer Bequest, and The Annenberg Fund Inc. Gift, 1975",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49071,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.439,false,true,54893,Asian Art,Hanging scroll,,Japan,Meiji period (1868–1912),,,,Artist,in the tradition of,Wu Daozi,"Chinese, 689–after 755",,Wu Daozi,Chinese,0689,0760,late 19th century,1871,1899,Hanging scroll; color on silk,48 1/2 x 23 1/8 in. (123.2 x 58.7 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/54893,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.268.86,false,true,49069,Asian Art,Hanging scroll,西園方済筆 梧桐下錦鶏図|Pheasant beneath Paulownia Tree,Japan,Edo period (1615–1868),,,,Artist,,Saien Hōsai (Xiyua Fangqi),1736?–?1795,,Hō Sai (Fang Qi),Chinese,1736,1795,18th century,1736,1795,Hanging scroll; ink on paper,38 1/2 x 12 3/16 in. (97.8 x 30.9 cm),"The Harry G. C. Packard Collection of Asian Art, Gift of Harry G. C. Packard, and Purchase, Fletcher, Rogers, Harris Brisbane Dick, and Louis V. Bell Funds, Joseph Pulitzer Bequest, and The Annenberg Fund Inc. Gift, 1975",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49069,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.268.87,false,true,49070,Asian Art,Hanging scroll,西園方済筆 梅に叭叭鳥図|Mynah Bird on Plum Branch,Japan,Edo period (1615–1868),,,,Artist,,Saien Hōsai (Xiyua Fangqi),1736?–?1795,,Hō Sai (Fang Qi),Chinese,1736,1795,18th century,1736,1795,Hanging scroll; ink on paper,36 3/16 x 10 1/2 in. (91.9 x 26.7 cm),"The Harry G. C. Packard Collection of Asian Art, Gift of Harry G. C. Packard, and Purchase, Fletcher, Rogers, Harris Brisbane Dick, and Louis V. Bell Funds, Joseph Pulitzer Bequest, and The Annenberg Fund Inc. Gift, 1975",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49070,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -42.61,false,true,49101,Asian Art,Handscroll,,Japan,Edo period (1615–1868),,,,Artist,In the Style of,Zhou Wenju,"Chinese, active 940–975",,ZHOU WENJU,Chinese,0940,0975,19th century,1800,1868,Handscroll; ink and color on paper,10 3/16 in. × 18 ft. 15/16 in. (25.8 × 551 cm),"Fletcher Fund, 1942",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49101,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.651,false,true,78572,Asian Art,Illustrated book,新玉帖|Album of the New Year (Aratama jō),Japan,Edo period (1615–1868),,,,Artist|Artist,,Ōnishi Chinnen|Tani Bunchō,"1792–1851|Japanese, 1763–1840",", various artists",Ōnishi Chinnen|Tani Bunchō,Japanese|Japanese,1792 |1763,1851 |1840,"early 19th century, before 1829",1800,1825,"Woodblock printed book (orihon, accordion-style; bound); ink, color, and metallic pigments on paper",9 15/16 × 7 5/16 in. (25.3 × 18.5 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78572,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.674,false,true,78595,Asian Art,Illustrated book,艶本 婦慈のゆき|Early to Dawn (Akeyasuki),Japan,Edo period (1615–1868),,,,Artist|Artist,,Ōhara Donshū|Tanaka Nikka,"Japanese, died 1857|Japanese, died 1845",,Ōhara Donshū|Tanaka Nikka,Japanese|Japanese,1757,1857 |1845,ca. 1837,1832,1842,Woodblock printed book; ink and color on paper,9 3/4 × 7 5/16 in. (24.7 × 18.5 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78595,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -13.67.93,false,true,45440,Asian Art,Inrō,冨士形蒔絵印籠|Inrō in the Shape of Mount Fuji with a Crossing Ferry and Procession,Japan,Edo period (1615–1868),,,,Artist|Artist,After|Maki-e by,Hanabusa Itchō|Kajikawa,"Japanese, 1652–1724|Japanese, 1652–1724",,Hanabusa Itchō|Kajikawa,Japanese|Japanese,1652 |1652,1724 |1724,late 18th–early 19th century,1767,1833,"Two cases; lacquered wood with gold and silver takamaki-e, hiramaki-e, togidashimaki-e, cut-out gold foil on nashiji lacquer ground Netsuke: ivory; Ryūgūjō (The Dragon King's undersea palace) in a clam Ojime: agate bead",4 1/8 x 4 11/16 x 1 1/4 in. (10.5 x 11.9 x 3.2 cm),"Rogers Fund, 1913",,,,,,,,,,,,Inrō,,http://www.metmuseum.org/art/collection/search/45440,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JIB32,false,true,57563,Asian Art,Illustrated book,,Japan,Edo period (1615–1868),,,,Artist|Artist,,Katsukawa Shunshō|Kitao Shigemasa,"Japanese, 1726–1792|Japanese, 1739–1820",,Katsukawa Shunshō|Kitao Shigemasa,Japanese|Japanese,1726 |1739,1792 |1820,1776,1776,1776,Ink and color on paper,11 1/8 × 7 3/8 × 3/8 in. (28.3 × 18.7 × 1 cm),"Rogers Fund, 1918",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/57563,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JIB88,false,true,57754,Asian Art,Illustrated book,画本宝能縷|Picture Book of Brocades with Precious Threads (Ehon takara no itosuji),Japan,Edo period (1615–1868),,,,Artist|Artist,,Katsukawa Shunshō|Kitao Shigemasa,"Japanese, 1726–1792|Japanese, 1739–1820","(nos. 1, 3, 6, 8, 11, 12)|(nos. 2, 4, 5, 7, 9, 10)",Katsukawa Shunshō|Kitao Shigemasa,Japanese|Japanese,1726 |1739,1792 |1820,"1786, first month",1786,1786,Polychrome woodblock printed book; ink and color on paper,Overall: 11 5/16 × 8 1/8 in. (28.8 × 20.6 cm),"The Howard Mansfield Collection, Gift of Howard Mansfield, 1936",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/57754,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.822,false,true,78712,Asian Art,Illustrated book,『青楼美人合 姿鏡』|Mirror of Yoshiwara Beauties (Seirō bijin awase sugata kagami),Japan,Edo period (1615–1868),,,,Artist|Artist,,Katsukawa Shunshō|Kitao Shigemasa,"Japanese, 1726–1792|Japanese, 1739–1820",,Katsukawa Shunshō|Kitao Shigemasa,Japanese|Japanese,1726 |1739,1792 |1820,1776,1776,1776,"Woodblock printed book; ink, color, and mica on paper",11 × 7 3/8 in. (28 × 18.8 cm),"Purchase, Mary and James G. Wallach Family Foundation Gift, in honor of John T. Carpenter, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78712,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JIB31,false,true,45021,Asian Art,Illustrated book,Seiro Bijin Awase Sugata Kagami|Mirror of the Beautiful Women of the Yoshiwara Brothels,Japan,Edo period (1615–1868),,,,Artist|Artist,,Kitao Shigemasa|Katsukawa Shunshō,"Japanese, 1739–1820|Japanese, 1726–1792",,Kitao Shigemasa|Katsukawa Shunshō,Japanese|Japanese,1739 |1726,1820 |1792,1776,1776,1776,Polychrome woodblock printed book; ink and color on paper,11 × 7 1/4 × 5/8 in. (27.9 × 18.4 × 1.6 cm),"Rogers Fund, 1918",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/45021,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.334,false,true,73587,Asian Art,Illustrated book,紅毛雜話|Chats on Novelties of Foreign Lands (Kōmōzatsuwa),Japan,Edo period (1615–1868),,,,Artist|Artist,,Shiba Kōkan|Kuwagata Keisai,"Japanese, 1747–1818|Japanese, 1764–1824",,Shiba Kōkan|Kuwagata Keisai,Japanese|Japanese,1747 |1764,1818 |1824,1797,1797,1797,"Five volumes of woodblock printed books bound as one; ink on paper,",Image (a): 8 7/8 x 6 1/4 x 7/8 in. (22.5 x 15.9 x 2.2 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/73587,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.757a–d,false,true,78659,Asian Art,Illustrated books,『光琳百圖』|One Hundred Paintings by Kōrin (Kōrin hyakuzu),Japan,Edo period (1615–1868),,,,Artist|Artist,After,Sakai Hōitsu|Ogata Kōrin,"Japanese, 1761–1828|Japanese, 1658–1716",,Sakai Hōitsu|Kōrin,Japanese|Japanese,1761 |1658,1828 |1716,1815,1815,1815,Set of four woodblock printed books; ink on paper,each: 10 1/4 × 7 3/16 in. (26 × 18.2 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78659,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.819,false,true,78709,Asian Art,Illustrated book,,Japan,Edo period (1615–1868),,,,Artist|Artist,,Katsukawa Shun'ei|Katsukawa Shunshō,"Japanese, 1762–1819|Japanese, 1726–1792",,Katsukawa Shun'ei|Katsukawa Shunshō,Japanese|Japanese,1762 |1726,1819 |1792,1790,1790,1790,Woodblock printed book; ink and color on paper,8 13/16 × 6 1/4 in. (22.4 × 15.8 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78709,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.806,false,true,78696,Asian Art,Illustrated book,,Japan,Edo period (1615–1868),,,,Artist|Artist,or,Hasegawa Settan|Tsukioka Settei,"Japanese, 1778–1843|Japanese, 1710–1786",,Hasegawa Settan|Tsukioka Settei,Japanese|Japanese,1778 |1710,1843 |1786,1829,1829,1829,Woodblock printed book; ink on paper,9 × 6 5/16 in. (22.8 × 16 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78696,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.659,false,true,78580,Asian Art,Illustrated book,南岳文鳳街道雙畫|Nangaku- Bunpō Highway Pictures (Nangaku Bunpō kaidō sōga),Japan,Edo period (1615–1868),,,,Artist|Artist,,Kawamura Bunpō|Watanabe Nangaku,"Japanese, 1779–1821|Japanese, 1763–1813",,Kawamura Bunpō|Watanabe Nangaku,Japanese|Japanese,1779 |1763,1821 |1813,ca. 1811,1806,1816,Woodblock printed book; ink and color on paper,10 1/4 × 6 7/8 in. (26 × 17.5 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78580,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.761,false,true,78663,Asian Art,Illustrated book,『役者三十六歌仙』|The Thirty-Six Immortals of Poetry as Kabuki Actors (Yakusha sanjūrokkasen),Japan,Edo period (1615–1868),,,,Artist|Artist,,Utagawa Kunisada|Totoya Hokkei,"Japanese, 1786–1865|Japanese, 1780–1850",,Utagawa Kunisada|Totoya Hokkei,Japanese|Japanese,1786 |1780,1865 |1850,1835,1835,1835,Woodblock printed book; ink and color on paper,10 1/16 × 7 5/16 in. (25.6 × 18.5 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78663,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"36.100.171a, b",false,true,58349,Asian Art,Box,,Japan,Edo period (1615–1868),,,,Artist|Artist,,Shibata Zeshin|Ikeda Taishin,"Japanese, 1807–1891|Japanese, 1825–1903",,Shibata Zeshin|Ikeda Taishin,Japanese|Japanese,1807 |1825,1891 |1903,1862,1862,1862,"Mokume-nuri, gold, silver, red, black lacquer, takamaki-e, hiramaki-e",H. 2 1/2 in. (6.4 cm); W. 6 in. (15.2 cm); D. 4 1/2 in. (11.4 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Lacquer,,http://www.metmuseum.org/art/collection/search/58349,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JIB5,false,true,57540,Asian Art,Illustrated book,"Nishikizuri onna sanjūrokkasen|Courtiers and Urchins, frontispiece for the album Brocade Prints of the Thirty-six Poetesses",Japan,Edo period (1615–1868),,,,Artist|Artist,,Chōbunsai Eishi|Katsushika Hokusai,"Japanese, 1756–1829|Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Chōbunsai Eishi|Katsushika Hokusai,Japanese|Japanese,1756 |1760,1829 |1849,1801,1801,1801,Ink and color on paper,9 7/8 × 7 3/8 × 3/4 in. (25.1 × 18.7 × 1.9 cm),"Rogers Fund, 1918",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/57540,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.707,false,true,78628,Asian Art,Illustrated book,,Japan,Edo period (1615–1868),,,,Artist|Artist,,Totoya Hokkei|Utagawa Hiroshige,"Japanese, 1780–1850|Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Totoya Hokkei|Utagawa, Hiroshige",Japanese|Japanese,1780 |1797,1850 |1858,1840,1840,1840,Woodblock printed book; ink and color on paper,8 15/16 × 6 5/16 in. (22.7 × 16.1 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78628,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.240,false,true,58926,Asian Art,Inrō,五代目市川団十郎肖像蒔絵印籠|Inrō with Kabuki Actor Ichikawa Danjūrō V,Japan,Edo period (1615–1868),,,,Artist|Artist,Maki-e by|Design by,Jōsensai|Katsukawa Shunshō,"Japanese, active late 18th–early 19th century|Japanese, 1726–1792",,Jōsensai|Katsukawa Shunshō,Japanese|Japanese,1767 |1726,1833 |1792,late 18th–early 19th century,1767,1833,"Five cases; lacquered wood with gold, silver, black, and red togidashimaki-e on black lacquer ground Netsuke: ivory; Nō mask Ojime: lacquer bead",3 1/2 x 1 7/8 x 1 1/4 in. (8.9 x 4.8 x 3.1 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Inrō,,http://www.metmuseum.org/art/collection/search/58926,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JIB51,false,true,57651,Asian Art,Illustrated book,,Japan,Edo period (1615–1868),,,,Artist|Artist,,Utagawa Hiroshige|Uoya Eikichi,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)|Japanese, ca. 1855–1866",,"Utagawa, Hiroshige|Uoya Eikichi",Japanese|Japanese,1797 |1855,1858 |1855,1856–58,1856,1858,Polychrome woodblock print; ink and color on paper,14 1/2 × 10 × 1 1/4 in. (36.8 × 25.4 × 3.2 cm),"Rogers Fund, 1918",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/57651,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JIB118a–d,false,true,57810,Asian Art,Illustrated book,酒井抱一画『光琳百圖』|One Hundred Paintings by Kōrin (Kōrin hyakuzu),Japan,Edo period (1615–1868),,,,Artist|Artist,After,Ogata Kōrin|Sakai Hōitsu,"Japanese, 1658–1716|Japanese, 1761–1828",,Kōrin|Sakai Hōitsu,Japanese|Japanese,1658 |1761,1716 |1828,1815 (first two volumes) and–1826 (two sequel volumes),1815,1826,Four volumes of woodblock printed books; ink on paper,Overall (each volume): H. 10 3/8 in. (26.4 cm); W. 7 3/16 in. (18.3 cm); D. 1 in. (2.5 cm),"The Howard Mansfield Collection, Gift of Howard Mansfield, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/57810,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2795,false,true,57094,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist|Artist,or,Kitao Masanobu (Santō Kyōden)|Kitao Shigemasa,"Japanese, 1761–1816|Japanese, 1739–1820",,Kitao Masanobu (Santō Kyōden)|Kitao Shigemasa,Japanese|Japanese,1761 |1739,1816 |1820,1739–1820,1739,1820,Polychrome woodblock print; ink and color on paper,14 1/8 x 9 7/8 in. (35.9 x 25.1 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/57094,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.146,false,true,73432,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist|Artist,,Utagawa Hiroshige II|Utagawa Kunisada,"Japanese, 1829–1869|Japanese, 1786–1865",,Utagawa Hiroshige II|Utagawa Kunisada,Japanese|Japanese,1829 |1786,1869 |1865,"3rd month, 1861",1861,1861,Triptych of polychrome woodblock prints; ink and color on paper,Image (a): 14 1/2 x 10 in. (36.8 x 25.4 cm) Image (b): 14 5/8 x 10 1/8 in. (37.1 x 25.7 cm) Image (c): 14 3/4 x 10 in. (37.5 x 25.4 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73432,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP186,false,true,36664,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist|Artist,,Chōkōsai Eishō|Yamaguchiya Chūsuke,"Japanese, 1793–99|Japanese, ca. 1793–1809",,Chōkōsai Eishō|Yamaguchiya Chūsuke,Japanese|Japanese,1793 |1783,1799 |1819,probably 1798,1796,1800,Polychrome woodblock print; ink and color on paper,15 5/8 x 10 in. (39.7 x 25.4 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36664,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP202,false,true,36679,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist|Artist,,Utagawa Toyokuni I|Wakasaya Yoichi,"Japanese, 1769–1825|Japanese, ca. 1794–1897",,Utagawa Toyokuni I|Wakasaya Yoichi,Japanese|Japanese,1769 |1784,1825 |1907,ca. 1800,1790,1810,Triptych of polychrome woodblock prints; ink and color on paper,14 x 30 1/8 in. (35.6 x 76.5 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36679,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.276a–c,false,true,73559,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist|Artist,,Utagawa Kunisada|Utagawa Kunitoki,"Japanese, 1786–1865|Japanese, active ca. 1860",,Utagawa Kunisada|Utagawa Kunitoki,Japanese|Japanese,1786 |1850,1865 |1870,"5th month, 1860",1860,1860,Triptych of polychrome woodblock prints; ink and color on paper,Image (a): 10 1/4 x 14 3/4 in. (26 x 37.5 cm) Image (b): 10 1/8 x 14 3/4 in. (25.7 x 37.5 cm) Image (c): 10 1/4 x 14 3/4 in. (26 x 37.5 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73559,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3332,false,true,55480,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist|Artist,,Utagawa Kuniyoshi|Wakasaya Yoichi,"Japanese, 1797–1861|Japanese, ca. 1794–1897",,Utagawa Kuniyoshi|Wakasaya Yoichi,Japanese|Japanese,1797 |1784,1861 |1907,ca. 1840,1830,1850,Polychrome woodblock print; ink and color on paper,Oban yoko-e; 8 7/8 x 14 in. (22.5 x 35.6 cm),"Gift of Lincoln Kirstein, 1959",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55480,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2751,false,true,57017,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist|Artist,,Utagawa Kunisada|Ichiransai Kunitsuna,"Japanese, 1786–1865|Japanese, mid-nineteenth century",,Utagawa Kunisada|Ichiransai Kunitsuna,Japanese|Japanese,1786 |1800,1865 |1899,after 1844,1845,1868,Triptych of polychrome woodblock prints; ink and color on thin paper,14 1/4 x 30 1/2 in. (36.2 x 77.5 cm),"Fletcher Fund, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/57017,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2753,false,true,57019,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist|Artist,,Utagawa Kunisada|Ichiransai Kunitsuna,"Japanese, 1786–1865|Japanese, mid-nineteenth century",,Utagawa Kunisada|Ichiransai Kunitsuna,Japanese|Japanese,1786 |1800,1865 |1899,after 1844,1844,1868,Drawings intended as design for woodblock prints (triptych); ink and color on paper,14 1/4 x 30 1/2 in. (36.2 x 77.5 cm),"Fletcher Fund, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/57019,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1418,false,true,55446,Asian Art,Print,木曽海道六拾九次之内 軽井沢|Karuizawa,Japan,Edo period (1615–1868),,,,Artist|Artist,,Utagawa Hiroshige|Keisai Eisen,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)|Japanese, 1790–1848",,"Utagawa, Hiroshige|Keisai Eisen",Japanese|Japanese,1797 |1790,1858 |1848,ca. 1835,1825,1845,Polychrome woodblock print; ink and color on paper,H. 8 9/16 in. (21.7 cm); W. 13 1/2 in. (34.3 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55446,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1023,false,true,37282,Asian Art,Woodblock print,"歌川広重画 「名所江戸百景 駒形堂吾嬬橋」|“Azuma Bridge from Komagatadō Temple,” from the series One Hundred Famous Views of Edo (Meisho Edo hyakkei, Komagatadō Azumabashi)",Japan,Edo period (1615–1868),,,,Artist|Artist,,Utagawa Hiroshige|Uoya Eikichi,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)|Japanese, ca. 1855–1866",,"Utagawa, Hiroshige|Uoya Eikichi",Japanese|Japanese,1797 |1855,1858 |1855,1857,1857,1857,Polychrome woodblock print; ink and color on paper,H. 13 3/8 in. (34 cm); W. 8 3/4 in. (22.2 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37282,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"93.3.181a, b",false,true,47419,Asian Art,Teapot,,Japan,Edo period (1615–1868),,,,Artist|Artist,,Kentei|Shuhei,"Japanese, 16th–17th century|Japanese, 1788–1839",,Kentei|Shuhei,Japanese|Japanese,1500 |1788,1800 |1839,1790,1700,1799,"Clay, fine and thin, decorated in polychrome enamels (Kyoto ware)",H. 4 1/4 in. (10.8 cm),"Gift of Charles Stewart Smith, 1893",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/47419,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -57.84,false,true,57249,Asian Art,Hanging scroll,,Japan,,,,,Artist|Artist,Attributed to|Formerly Attributed to,Keison|Keishoki,"Japanese, active late 15th– early 16th century|Japanese, active ca. 1500",,Keison|Keishoki,Japanese|Japanese,1467 |1450,1533 |1550,15th–16th century,1467,1533,Hanging scroll; ink on paper,36 x 14 3/8 in. (91.4 x 36.5 cm),"Rogers Fund, 1957",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/57249,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"1975.268.80a, b",false,true,49068,Asian Art,Hanging scrolls,雪竹図|Bamboo in Snow,Japan,Edo period (1615–1868),,,,Artist|Artist,Calligrapher:,Hakujun Shōkō|Taihō Shōkon,1695–1776|1691–1774,,Hakujun Shōkō|Taihō Shōkon,Chinese|Chinese,1695 |1691,1776 |1774,1774,1771,1774,Pair of hanging scrolls; ink on silk,Image (each): 47 1/2 in. × 20 in. (120.7 × 50.8 cm) Overall with mounting (each): 73 3/8 × 25 3/8 in. (186.4 × 64.5 cm) Overall with knobs (each): 73 3/8 × 27 5/8 in. (186.4 × 70.2 cm),"The Harry G. C. Packard Collection of Asian Art, Gift of Harry G. C. Packard, and Purchase, Fletcher, Rogers, Harris Brisbane Dick, and Louis V. Bell Funds, Joseph Pulitzer Bequest, and The Annenberg Fund Inc. Gift, 1975",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49068,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.268.132,false,true,49093,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist|Artist,,Shokusanjin (Ōta Nanpo)|Utamaro II,"Japanese, 1749–1823|Japanese (died 1831?)",", and others",Utamaro II,Japanese|Japanese,1749 |1750,1823 |1850,ca. 1801–6,1791,1806,Hanging scroll; ink and color on paper,Image: 35 5/8 x 12 in. (90.5 x 30.5 cm) Overall: 67 3/4 x 15 5/8 in. (172.1 x 39.7 cm),"The Harry G. C. Packard Collection of Asian Art, Gift of Harry G. C. Packard, and Purchase, Fletcher, Rogers, Harris Brisbane Dick, and Louis V. Bell Funds, Joseph Pulitzer Bequest, and The Annenberg Fund Inc. Gift, 1975",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49093,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.268.112,false,true,49004,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist|Artist,,Tani Bunchō|Shokusanjin (Ōta Nanpo),"Japanese, 1763–1840|Japanese, 1749–1823",,Tani Bunchō,Japanese|Japanese,1763 |1749,1840 |1823,1814,1814,1814,Hanging scroll; ink and color on paper,Image: 47 1/2 × 11 3/16 in. (120.7 × 28.4 cm) Overall with mounting: 76 × 12 1/16 in. (193 × 30.7 cm) Overall with knobs: 76 × 13 13/16 in. (193 × 35.1 cm),"The Harry G. C. Packard Collection of Asian Art, Gift of Harry G. C. Packard, and Purchase, Fletcher, Rogers, Harris Brisbane Dick, and Louis V. Bell Funds, Joseph Pulitzer Bequest, and The Annenberg Fund Inc. Gift, 1975",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49004,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.88,false,true,52988,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist|Artist,Painting by|Inscribed by,Tawaraya Sōtatsu|Takeuchi Toshiharu,"Japanese, died ca. 1640|Japanese, 1611–1647",,Tawaraya Sōtatsu|Takeuchi Toshiharu,Japanese|Japanese,1540 |1611,1640 |1647,ca. 1634,1624,1644,"Poem card (shikishi) mounted as a hanging scroll; ink, color, and gold on paper",Image: 9 11/16 × 8 3/16 in. (24.6 × 20.8 cm) Overall with mounting: 49 5/16 × 16 11/16 in. (125.3 × 42.4 cm) Overall with knobs: 49 5/16 × 18 3/8 in. (125.3 × 46.7 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/52988,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1979.407.2,false,true,45334,Asian Art,Hanging scroll,松花堂昭乗書・伝俵屋宗達下絵 鉄線下絵和歌色紙 藤原興風|Poem by Fujiwara no Okikaze with Underpainting of Clematis,Japan,Edo period (1615–1868),,,,Artist|Artist,Calligraphy by|Underpainting attributed to,Shōkadō Shōjō|Tawaraya Sōtatsu,"Japanese, 1584?–1639|Japanese, died ca. 1640",,Shōkadō Shōjō|Tawaraya Sōtatsu,Japanese|Japanese,1584 |1540,1639 |1640,early 17th century,1600,1633,"Hanging scroll; ink, gold, and silver on colored paper",Image: 7 15/16 x 6 15/16 in. (20.2 x 17.6 cm) Overall with mounting: 53 x 17 15/16 in. (134.6 x 45.6 cm) Overall with knobs: 53 x 20 5/8 in. (134.6 x 52.4 cm),"Purchase, Gift of Mrs. Russell Sage, by exchange, 1979",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45334,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.86,false,true,53246,Asian Art,Hanging scroll,小倉百人一首和歌巻断簡|Two Poems from One Hundred Poems by One Hundred Poets (Ogura hyakunin isshu),Japan,Momoyama period (1573–1615),,,,Artist|Artist,Painting by,Hon'ami Kōetsu|Tawaraya Sōtatsu,"Japanese, 1558–1637|Japanese, died ca. 1640",,Hon'ami Kōetsu|Tawaraya Sōtatsu,Japanese|Japanese,1558 |1540,1637 |1640,ca. 1615–20,1615,1620,"Section of scroll; ink, silver, and gold on paper",Image: 13 in. × 23 3/4 in. (33 × 60.4 cm) Overall with mounting: 49 5/8 × 29 1/8 in. (126 × 73.9 cm) Overall with knobs: 49 5/8 × 31 5/16 in. (126 × 79.5 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/53246,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.268.59,false,true,44861,Asian Art,Hanging scroll,本阿見光悦書・俵屋宗達下絵 桜下絵和歌色紙 鴨長明|Poem by Kamo no Chōmei with Underpainting of Cherry Blossoms,Japan,Momoyama period (1573–1615),,,,Artist|Artist,Underpainting attributed to|Calligraphy by,Tawaraya Sōtatsu|Hon'ami Kōetsu,"Japanese, died ca. 1640|Japanese, 1558–1637",,Tawaraya Sōtatsu|Hon'ami Kōetsu,Japanese|Japanese,1540 |1558,1640 |1637,dated 1606,1606,1606,"Poem card (shikishi) mounted as a hanging scroll; ink, gold, and silver on paper",Overall: 7 15/16 x 7in. (20.2 x 17.8cm) Overall with mounting: 53 x 14 3/4 in. (134.6 x 37.5 cm) Overall with knobs: 53 x 16 1/2 in. (134.6 x 41.9 cm),"The Harry G. C. Packard Collection of Asian Art, Gift of Harry G. C. Packard, and Purchase, Fletcher, Rogers, Harris Brisbane Dick, and Louis V. Bell Funds, Joseph Pulitzer Bequest, and The Annenberg Fund Inc. Gift, 1975",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/44861,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3497a–uu,false,true,55727,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist|Artist,,Toyohara Kunichika|Utagawa Kunisada,"Japanese, 1835–1900|Japanese, 1786–1865",(tt and uu),Toyohara Kunichika|Utagawa Kunisada,Japanese|Japanese,1835 |1786,1900 |1865,,1769,1900,Polychrome woodblock print; ink and color on paper,a–t: 14 x 9 1/4 in. (35.6 x 23.5 cm) (each) tt –uu: 6 3/4 x 9 1/4 in. (17.1 x 23.5 cm) (each),"Gift of Dr. and Mrs. Harold B. Bilsky, 1975",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55727,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2752,false,true,57018,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist|Artist,,Utagawa Kunisada|Ichiransai Kunitsuna,"Japanese, 1786–1865|Japanese, mid-nineteenth century",,Utagawa Kunisada|Ichiransai Kunitsuna,Japanese|Japanese,1786 |1800,1865 |1899,,1786,1864,Preliminary sketch for drawing intended as design for woodblock print; ink on thin paper,14 1/4 x 30 1/2 in. (36.2 x 77.5 cm),"Fletcher Fund, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/57018,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2754,false,true,57020,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist|Artist,,Utagawa Kunisada|Ichiransai Kunitsuna,"Japanese, 1786–1865|Japanese, mid-nineteenth century",,Utagawa Kunisada|Ichiransai Kunitsuna,Japanese|Japanese,1786 |1800,1865 |1899,,1786,1864,Ink on thin paper,14 1/4 x 30 1/2 in. (36.2 x 77.5 cm),"Fletcher Fund, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/57020,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3439,false,true,55643,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist|Artist,or,Utagawa Hiroshige II|Utagawa Hiroshige,"Japanese, 1829–1869|Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa Hiroshige II|Utagawa, Hiroshige",Japanese|Japanese,1829 |1797,1869 |1858,,1615,1868,Polychrome woodblock print; ink and color on paper,9 x 14 in. (22.9 x 35.6 cm),"Gift of Cole J. Younger, 1975",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55643,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.36,false,true,55354,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist|Artist,Attributed to,Matsumura Keibun|Matsumura Keibun,"Japanese, 1779–1843|Japanese, 1779–1843",,Matsumura Keibun|Matsumura Keibun,Japanese|Japanese,1779 |1779,1843 |1843,,1779,1843,Hanging scroll; ink and color on paper,40 1/8 x 11 7/8 in. (101.9 x 30.2 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/55354,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.37,false,true,55364,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist|Artist,Attributed to,Matsumura Keibun|Matsumura Keibun,"Japanese, 1779–1843|Japanese, 1779–1843",,Matsumura Keibun|Matsumura Keibun,Japanese|Japanese,1779 |1779,1843 |1843,,1779,1843,Hanging scroll; color on paper,31 5/8 x 12 5/8 in. (80.3 x 32.1 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/55364,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.477,false,true,54974,Asian Art,Hanging scroll,,Japan,Meiji period (1868–1912),,,,Artist|Artist,Attributed to|In the Style of,Kano Tambi|Li Gonglin,"Japanese, 1840–1893|Chinese, ca. 1041–1106",,Kano Tambi|Li Gonglin,Japanese|Chinese,1840 |1041,1893 |1106,,1840,1893,Hanging scroll; ink on paper,31 x 15 in. (78.7 x 38.1 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/54974,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.905,false,true,58829,Asian Art,Inrō,,Japan,Edo period (1615–1868),,,,Artist|Artist,,Hosoya Hansai|Tomioka Tessai,"Japanese|Japanese, 1836–1924",,Hosoya Hansai|Tomioka Tessai,Japanese|Japanese,1836,1924,19th century,1800,1899,"Lacquer, roiro, black hiramakie, incised; Interior: roiro and hirame",2 9/16 x 1 7/8 x 7/8 in. (6.5 x 4.7 x 2.3 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Inrō,,http://www.metmuseum.org/art/collection/search/58829,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -13.67.44,false,true,58585,Asian Art,Inrō,原羊遊斎作・狩野晴川院筆 満月に鵞鳥図印籠|Inrō with Goose Flying across the Full Moon,Japan,Edo period (1615–1868),,,,Artist|Artist,After,Kano Seisen’in|Hara Yōyūsai,"1775–1828|Japanese, 1772–1845",,Kano Seisen’in|Hara Yōyūsai,Japanese|Japanese,1775 |1772,1828 |1845,19th century,1800,1899,"Lacquer, kinji, gold, silver, and black hiramaki-e, takamaki-e, and togidashi",Overall (inro): H. 3 11/16 in. (9.4 cm); W. 1 7/8 in. (4.8 cm); D. 1 1/16 in. (2.7 cm),"Rogers Fund, 1913",,,,,,,,,,,,Inrō,,http://www.metmuseum.org/art/collection/search/58585,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.245,false,true,58931,Asian Art,Inrō,原羊遊斎作・酒井抱一下絵 梅木蒔絵印籠|Inrō with Design of Blossoming Plum Tree,Japan,Edo period (1615–1868),,,,Artist|Artist,After a design by,Hara Yōyūsai|Sakai Hōitsu,"Japanese, 1772–1845|Japanese, 1761–1828",,Hara Yōyūsai|Sakai Hōitsu,Japanese|Japanese,1772 |1761,1845 |1828,19th century,1800,1899,"Sprinkled gold lacquer with gold, silver, and red makie, takamakie, and coral inlay Ojime: bead; tortoiseshell Netsuke: box with decoration of violets; gold makie lacquer with gold and silver makie",Overall (inro): H. 3 11/16 in. (9.3 cm); W. 2 1/4 in. (5.7 cm); D. 7/8 in. (2.2 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Inrō,,http://www.metmuseum.org/art/collection/search/58931,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.249,false,true,45456,Asian Art,Inrō,柴田是真作 波文印籠|Inrō with Stylized Waves,Japan,Edo period (1615–1868),,,,Artist|Artist,After a design by,Shibata Zeshin|Ogata Kōrin,"Japanese, 1807–1891|Japanese, 1658–1716",,Shibata Zeshin|Kōrin,Japanese|Japanese,1807 |1658,1891 |1716,19th century,1790,1899,Gold lacquer with pewter inlay; Ojime: bronze and gold jar; Netsuke: carved tortoiseshell turtle,Overall (inro): H. 1 15/16 in. (5 cm); W. 1 3/4 in. (4.4 cm); D. 11/16 in. (1.7 cm) Overall (netsuke): H. 11/16 in. (1.7 cm); W. 1 3/16 in. (3 cm); L. 1 9/16 in. (4 cm) Overall (ojime): H. 3/8 in. (1 cm); W. 3/16 in. (0.5 cm); D. 3/16 in. (0.5 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Inrō,,http://www.metmuseum.org/art/collection/search/45456,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.251,false,true,45446,Asian Art,Inrō,,Japan,Meiji period (1868–1912),,,,Artist|Artist,Attributed to|After an inro by,Shibata Zeshin|Ogawa Haritsu (Ritsuō),"Japanese, 1807–1891|Japanese, 1663–1747",,Shibata Zeshin|Ritsuō,Japanese|Japanese,1807 |1663,1891 |1747,19th century,1790,1899,Roiro ('waxen') lacquer with black hiramakie sprinkled and polished lacquer and takamakie sprinkled and polished lacquer relief; Interior: roiro; Netsuke: nut carved with Chinese sages and attendants; Ojime: carved peach stone with landscape and figures,2 7/16 x 2 7/16 x 9/16 in. (6.2 x 6.2 x 1.5 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Inrō,,http://www.metmuseum.org/art/collection/search/45446,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP210,false,true,36687,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist|Artist,Fourteen prints by|Sixteen prints by,Utagawa Kuniyoshi|Utagawa Kunisada,"Japanese, 1797–1861|Japanese, 1786–1865",,Utagawa Kuniyoshi|Utagawa Kunisada,Japanese|Japanese,1797 |1786,1861 |1865,19th century,1800,1865,Album of 30 polychrome woodblock prints; ink and color on paper,14 1/4 × 9 5/8 × 1/2 in. (36.2 × 24.4 × 1.3 cm),"Gift of the Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36687,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.63,false,true,49100,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist|Artist,In the Style of,Kano Osanobu|Yoden,"1796–1846|Chinese, active early Yuan period (1279–1368)",,Kano Osanobu|Yoden,Japanese|Chinese,1796 |1279,1846 |1368,19th century,1800,1846,Hanging scroll; ink on silk,37 1/2 x 17 1/4 in. (95.3 x 43.8 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49100,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -13.67.33,false,true,58575,Asian Art,Inrō,原羊遊斎作・酒井抱一下絵 南天に雀印籠|Inrō with Sparrows in Snow-covered Nandina,Japan,Edo period (1615–1868),,,,Artist|Artist,After a design by,Hara Yōyūsai|Sakai Hōitsu,"Japanese, 1772–1845|Japanese, 1761–1828",,Hara Yōyūsai|Sakai Hōitsu,Japanese|Japanese,1772 |1761,1845 |1828,early 19th century,1800,1833,"Lacquer with gold and silver makie, coral, and applied stained ivory Ojime: bead with stylized medallions; green stone with gold makie lacquer Netsuke: round box with iris design",Overall (inro): H. 3 11/16 in. (9.3 cm); W. 2 3/16 in. (5.6 cm); D. 13/16 in. (2.1 cm),"Rogers Fund, 1913",,,,,,,,,,,,Inrō,,http://www.metmuseum.org/art/collection/search/58575,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.300,false,true,72471,Asian Art,Hanging scroll,해강 김규진 난초|海岡 金圭鎭 墨蘭圖|Orchids in hanging basket,Korea,,,,,Artist,,Kim Gyujin,"Korean, 1868–1933",,Kim Gyujin,,1868,1933,early 20th century,1900,1933,Hanging scroll; ink on paper,Image: 55 1/4 × 15 3/4 in. (140.3 × 40 cm) Overall with mounting: 81 1/8 × 21 5/8 in. (206 × 55 cm) Overall with knobs: 81 1/8 × 23 7/8 in. (206 × 60.7 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/72471,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1988.103,false,true,38027,Asian Art,Folio,,"India (Rajasthan, Mewar)",,,,,Artist,,Sahibdin,active ca. 1628–55,,Sahibdin,,1618,1665,ca. 1665,1655,1675,Ink and opaque watercolor on paper,H. 9 5/8 in (24.4 cm); W. 7 3/4 in. (19.7 cm),"Gift of Ernest Erickson Foundation, 1988",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/38027,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -19.24.1,false,true,37947,Asian Art,Folio,,"India (Guler, Himachal Pradesh)",,,,,Artist,Attributed to,Manaku,active ca. 1725–60,,Manaku,,1715,1770,ca. 1725,1715,1735,"Opaque watercolor, ink and gold on paper",Page: 23 1/2 x 32 3/4 in. (59.7 x 83.2 cm) Image: 22 1/4 x 31 1/4 in. (56.5 x 79.4 cm),"Rogers Fund, 1919",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/37947,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -19.24.2,false,true,37948,Asian Art,Folio,,"India (Punjab Hills, Guler)",,,,,Artist,Attributed to,Manaku,active ca. 1725–60,,Manaku,,1715,1770,ca. 1725,1715,1735,Ink and opaque watercolor on paper,22 5/16 x 33 in. (56.7 x 83.8 cm),"Rogers Fund, 1919",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/37948,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -19.24.3,false,true,37949,Asian Art,Drawing,,"India (Himachal Pradesh, Guler)",,,,,Artist,Attributed to,Manaku,active ca. 1725–60,,Manaku,,1715,1770,ca. 1725,1715,1735,Ink on paper,24 1/2 x 32 5/8 in. (62.2 x 82.9 cm),"Rogers Fund, 1919",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/37949,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -19.24.4,false,true,37950,Asian Art,Drawing,,"India (Guler, Himachal Pradesh)",,,,,Artist,Attributed to,Manaku,active ca. 1725–60,,Manaku,,1715,1770,ca. 1725,1715,1735,Ink on paper,Page: 23 1/2 x 33 in. (59.7 x 83.8 cm) Image: 22 3/8 x 31 1/2 in. (56.8 x 80 cm),"Rogers Fund, 1919",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/37950,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1985.398.12,false,true,37906,Asian Art,Folio,,"India (Rajasthan, Mewar)",,,,,Artist,Style of,Manohar,active ca. 1582–1624,,Manohar,,1577,1624,ca. 1655–60,1655,1660,Ink and opaque watercolor on paper,Image: 9 1/8 x 7 1/4 in. (23.2 x 18.4 cm); Page: 10 1/4 x 8 3/8 in. (26 x 21.3 cm),"Gift of Cythian Hazen Polsky, 1985",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/37906,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1996.100.6,false,true,38043,Asian Art,Painting,,"India (Jaipur, Rajasthan)",,,,,Artist,Generation of,Bagta,active ca. 1761–1814,,Bagta,,1751,1824,ca. 1810–18,1800,1818,Opaque watercolor and ink on paper,Overall: 29 x 40 5/8 in. (73.7 x 103.2 cm) Framed: 39 3/4 x 50 3/4 in. (101 x 128.9 cm),"Fletcher Fund, 1996",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/38043,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1978.540.2,false,true,37863,Asian Art,Folio,,"India (Bikaner, Rajasthan)",,,,,Artist,,Ruknuddin,active late 17th century,,Ruknuddin,,1667,1695,ca. 1690–95,1680,1705,Opaque watercolor and ink on paper,Image: 6 in. × 4 11/16 in. (15.2 × 11.9 cm) Sheet: 10 1/8 × 7 1/4 in. (25.7 × 18.4 cm),"Gift of Mr. and Mrs. Peter Findlay, 1978",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/37863,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1980.526.2,false,true,37870,Asian Art,Folio,,"India (Rajasthan, Bikaner)",,,,,Artist,,Mohamed,active early 18th century,,Mohamed,,1700,1733,1714,1714,1714,Ink and opaque watercolor on paper,6 x 4 3/4 in. (15.2 x 12.1 cm),"Gift of John and Evelyn Kossak, The Kronos Collections, 1980",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/37870,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2001.344,true,true,61429,Asian Art,Painting,,"Western India, Rajasthan, Mewar",,,,,Artist,,Tara,"Indian, active 1836–1870",,Tara,,1836,1870,1845–46,1845,1846,"Opaque watercolor, ink, and gold on paper",Image (painting): 16 3/4 x 22 3/4 in. (42.5 x 57.8 cm) Sheet: 19 x 24 7/8 in. (48.3 x 63.2 cm),"Cynthia Hazen Polsky and Leon B. Polsky Fund, 2001",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/61429,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1994.310,false,true,38010,Asian Art,Folio,,"India (Bahu, Jammu)",,,,,Artist,,Bahu Masters,active ca. 1680–ca. 1720,,Bahu Masters,,1670,1730,ca. 1690–1710,1690,1710,Opaque watercolor and ink on paper,Page: 8 5/8 x 12 1/2 in. (21.9 x 31.8 cm) Image: 7 3/4 x 11 5/8 in. (19.7 x 29.5 cm),"Purchase, The Dillon Fund, Evelyn Kranes Kossak, and Anonymous Gifts, 1994",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/38010,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -57.185.2,true,true,37942,Asian Art,Folio,,"India (Basohli, Jammu)",,,,,Artist,,Devidasa of Nurpur,active ca. 1680–ca. 1720,,Devidasa of Nurpur,,1670,1730,dated 1694–95,1694,1695,"Opaque watercolor, ink, silver, and gold on paper",Image: 6 1/2 x 10 7/8 in. (16.5 x 27.6 cm) Sheet: 8 x 12 1/4 in. (20.3 x 31.1 cm) Framed: 15 5/8 x 20 1/2 in. (39.7 x 52.1 cm),"Gift of Dr. J. C. Burnett, 1957",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/37942,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -57.51.14,false,true,37941,Asian Art,Folio,,"India (Punjab Hills, Basohli)",,,,,Artist,,Devidasa of Nurpur,active ca. 1680–ca. 1720,,Devidasa of Nurpur,,1670,1730,dated 1694–95,1694,1695,"Opaque watercolor, ink, silver, and gold on paper",Overall: 8 5/8 x 12 3/4in. (21.9 x 32.4cm) Painting within rules: 6 3/4 x 11 1/4 in. (17.2 x 28.6 cm),"Bequest of Cora Timken Burnett, 1956",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/37941,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -18.85.2,false,true,37877,Asian Art,Painting,,"India (Rajasthan, Jaipur)",,,,,Artist,Attributed to,Sahib Ram,"active reign of Maharaja Sawai Pratap Singh, 1778–1803",,SAHIB RAM,,1778,1803,ca. 1800,1790,1810,Ink and opaque watercolor on paper,H. 27 1/4 in. (69.2 cm); W. 18 1/2 in. (47 cm),"Rogers Fund, 1918",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/37877,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -18.85.4,false,true,38465,Asian Art,Drawing,,"India (Rajasthan, Jaipur)",,,,,Artist,Attributed to,Sahib Ram,"active reign of Maharaja Sawai Pratap Singh, 1778–1803",,SAHIB RAM,,1778,1803,ca. 1800,1790,1810,Ink on paper,26 x 18 1/4 in. (66 x 46.4 cm),"Rogers Fund, 1918",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/38465,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.299,false,true,50486,Asian Art,Hanging scroll,"탄은 이정 대나무 조선|灘隱 李霆 墨竹圖 朝鮮|Bamboo in the wind",Korea,Joseon dynasty (1392–1910),,,,Artist,,Yi Jeong,"Korean, 1541–1626",,Yi Jeong,,1541,1626,early 17th century,1600,1633,Hanging scroll; ink on silk with gold on colophon,Image: 45 1/2 x 21 in. (115.6 x 53.3 cm) Overall with mounting: 87 13/16 × 26 5/16 in. (223 × 66.8 cm) Overall with knobs: 87 13/16 × 28 3/4 in. (223 × 73 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/50486,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1995.419,true,true,38648,Asian Art,Torso,,"Pakistan (ancient region of Gandhara, mondern Peshawar region)",,,,,Artist,Probably,Sahri-Bahlol Workshop,,,Sahri-Bahlol Workshop,,0350,0550,ca. 5th century,350,550,Schist,H. 64 1/2 in. (163.8 cm),"Purchase, Lila Acheson Wallace Gift, 1995",,,,,,,,,,,,Sculpture,,http://www.metmuseum.org/art/collection/search/38648,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2008.359.4,false,true,74649,Asian Art,Drawing,,"India (Pahari Hills, Guler)",,,,,Artist|Artist,Attributed to the|possibly,Seu Family|Manaku,active ca. 1725–60,,Seu Family|Manaku,,1715,1770,mid-18th century,1736,1770,Ink and wash on paper,Image (sight): 8 1/2 x 12 3/8 in. (21.6 x 31.4 cm),"Gift of Subhash Kapoor, in memory of his parents, Smt Shashi Kanta and Shree Parshotam Ram Kapoor, 2008",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/74649,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2008.359.5,false,true,74650,Asian Art,Painting,,"India (Pahari Hills, Guler)",,,,,Artist|Artist,Attributed to the|possibly,Seu Family|Manaku,active ca. 1725–60,,Seu Family|Manaku,,1715,1770,mid-18th century,1736,1770,Ink and wash on paper,Image (sight): 8 1/2 x 12 1/8 in. (21.6 x 30.8 cm),"Gift of Subhash Kapoor, in memory of his parents, Smt Shashi Kanta and Shree Parshotam Ram Kapoor, 2008",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/74650,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2008.359.6,false,true,74651,Asian Art,Painting,,"India (Pahari Hills, Guler)",,,,,Artist|Artist,Attributed to the|possibly,Seu Family|Manaku,active ca. 1725–60,,Seu Family|Manaku,,1715,1770,mid-18th century,1736,1770,Ink and wash on paper,Image (sight): 8 3/4 x 13 in. (22.2 x 33 cm),"Gift of Subhash Kapoor, in memory of his parents, Smt Shashi Kanta and Shree Parshotam Ram Kapoor, 2008",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/74651,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -13.100.90,false,true,51383,Asian Art,Folding fan mounted as an album leaf,,China,Ming dynasty (1368–1644),,,,Calligrapher,,Gong Dingzi,"Chinese, 1615–1673",,Gong Dingzi,,1615,1673,,1368,1644,Folding fan mounted as an album leaf; ink on gold paper,6 1/2 x 20 1/2 in. (16.5 x 52.1 cm),"John Stewart Kennedy Fund, 1913",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/51383,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1995.271,false,true,39626,Asian Art,Incense holder,,China,Ming dynasty (1368–1644),,,,Artist,,Zhu Sansong,active ca. 1573–1619,,Zhu Sansong,,1573,1619,late 16th–early 17th century,1567,1633,Bamboo,Overall (including new wooden ends) H. 7 in. (17.8 cm),"Purchase, Friends of Asian Art Gifts, 1995",,,,,,,,,,,,Bamboo,,http://www.metmuseum.org/art/collection/search/39626,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.784.9,false,true,65630,Asian Art,Hanging scroll,清 鄭簠 隷書軸|Poetic Maxim,China,Qing dynasty (1644–1911),,,,Artist,,Zheng Fu,"Chinese, 1622–1693",,Zheng Fu,,1622,1693,dated 1691,1691,1691,Hanging scroll; ink on paper,Image: 45 1/2 × 17 3/4 in. (115.6 × 45.1 cm) Overall with mounting: 97 3/4 × 25 1/2 in. (248.3 × 64.8 cm) Overall with knobs: 97 3/4 × 29 1/4 in. (248.3 × 74.3 cm),"Gift of Julia and John Curtis, in memory of Marie-Hélène and Guy Weill, 2015",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/65630,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1971.74a–h,false,true,64086,Asian Art,Screen,清晚期 盧葵生款 剔紅郭子儀賀壽圖屏風|Screen with birthday celebration for General Guo Ziyi,China,Qing dynasty (1644–1911),,,,Artist,,Lu Guisheng,"Chinese, active 1821–50",,"Lu, Guisheng",,1821,1850,mid-19th century,1834,1850,Carved red and black lacquer,Open flat: 84 1/8 in. × 12 ft. 4 in. (213.7 × 375.9 cm) Open curved: 84 1/8 in. × 11 ft. 3 13/16 in. × 35 7/16 in. (213.7 × 345 × 90 cm),"Gift of Mrs. Henry-George J. McNeary, 1971",,,,,,,,,,,,Lacquer,,http://www.metmuseum.org/art/collection/search/64086,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.29,false,true,40512,Asian Art,Handscroll,元 鮮于樞 草書石鼓歌 卷|Song of the Stone Drums,China,Yuan dynasty (1271–1368),,,,Artist,,Xianyu Shu,"Chinese, 1246–1302",,Xianyu Shu,,1246,1302,dated 1301,1301,1301,Handscroll; ink on paper,Image: 17 11/16 in. x 15 ft. 1 1/16 in. (44.9 x 459.9 cm) Overall with mounting: 18 in. x 38 ft. 11 3/16 in. (45.7 x 1186.7 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/40512,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -34.13,false,true,51042,Asian Art,Figure,,China,Qing dynasty (1644–1911),,,,Artist,,Su Xuejin,1869–1919,,Su Xuejin,,1869,1919,late 19th–early 20th century,1867,1933,"Porcelain with clear glaze (Dehua ware, Fujian Province)",H. 15 1/2 in. (39.4 cm); W. 5 in. (12.7 cm); D. 4 in. (10.2 cm),"Purchase, Anita M. Linzee Bequest, 1934",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/51042,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1995.181,false,true,36458,Asian Art,Table screen,"大理石插屏|Table Screen Inscribed with the Poem ""Lisao,"" now converted to a wall panel",China,Ming dynasty (1368–1644),,,,Artist,,Li Mi,active early 17th century,,Li Mi,,1600,1699,dated 1624,1624,1624,Veined marble with wood frame,H. 16 5/8 (42.2 cm); W. 14 3/8in. (36.5cm),"Purchase, Eileen W. Bamberger Bequest, in memory of her husband, Max Bamberger, 1995",,,,,,,,,,,,Furniture,,http://www.metmuseum.org/art/collection/search/36458,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.500.6.22,false,true,39879,Asian Art,Incense burner,,China,Ming dynasty (1368–1644),,,,Artist,Attributed to,Hu Wenming,"Chinese, active late 16th–early 17th century",,Hu Wenming,,1550,1650,late 16th–17th century,1567,1699,Copper with gilding,H. 6 1/4 in. (15.9 cm); W. 2 7/8 in. (7.3 cm),"Gift of Florence and Herbert Irving, 2015",,,,,,,,,,,,Metalwork,,http://www.metmuseum.org/art/collection/search/39879,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.500.6.24,false,true,39878,Asian Art,Vase,,China,Ming dynasty (1368–1644),,,,Artist,Attributed to,Hu Wenming,"Chinese, active late 16th–early 17th century",,Hu Wenming,,1550,1650,16th–17th century,1500,1799,Bronze with gilding,H. 3 7/8 in. (9.8 cm),"Gift of Florence and Herbert Irving, 2015",,,,,,,,,,,,Metalwork,,http://www.metmuseum.org/art/collection/search/39878,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.500.6.23a–c,false,true,39877,Asian Art,Incense box,,China,Ming dynasty (1368–1644),,,,Artist,Attributed to,Hu Wenming,"Chinese, active late 16th–early 17th century",,Hu Wenming,,1550,1650,16th–17th century,1500,1799,Bronze with gilding,H. 1 1/4 in.; Diam. 2 7/8 in.,"Gift of Florence and Herbert Irving, 2015",,,,,,,,,,,,Metalwork,,http://www.metmuseum.org/art/collection/search/39877,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.273,false,true,684605,Asian Art,Vase,十七/十八世紀 銅錯銀絲題竇常詩文饕餮耳方壺|Vase,China,Qing dynasty (1644–1911),,,,Artist,Attributed to,Shisou,"Chinese, active first half the 17th century",,Shisou,,1600,1649,17th–18th century,1600,1799,Bronze,H. 5 1/4 in. (13.3 cm); W. 2 1/2 in. (6.4 cm); D. 2 5/16 in. (5.9 cm),"Purchase, Seymour Fund, Barbara and Sorrell Mathes Gift, and various donors, 2015",,,,,,,,,,,,Metalwork,,http://www.metmuseum.org/art/collection/search/684605,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.784.5,false,true,65624,Asian Art,Album leaf,"明/清 惲向 欲雪圖 冊頁|Snowscape, from Album for Zhou Lianggong",China,,,,,Artist,,Yun Xiang,"Chinese, 1586–1655",,Yun Xiang,,1586,1655,Undated,1586,1655,Double album leaf from a collective album of twelve paintings and facing pages of calligraphy; ink and color on paper,9 3/4 x 13 in. (24.8 x 33 cm),"Gift of Julia and John Curtis, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/65624,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.784.3,false,true,73650,Asian Art,Handscroll,明/清 張宏 桃源勝概圖 卷|Peach Blossom Spring,China,Ming dynasty (1368–1644),,,,Artist,,Zhang Hong,"Chinese, 1577–after 1652",,Zhang Hong,,1577,1652,dated 1638,1638,1638,Handscroll; ink and color on paper,Image: 9 1/2 × 74 5/8 in. (24.1 × 189.5 cm) Overall with mounting: 11 in. × 21 ft. 1 5/8 in. (28 × 644.2 cm),"Gift of Julia and John Curtis, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/73650,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -69.242.1–.6,false,true,72262,Asian Art,Album,明 盛茂曄 唐詩意山水圖 冊|Landscapes after Tang Poems,China,Ming dynasty (1368–1644),,,,Artist,,Sheng Maoye,"Chinese, active ca. 1615–ca. 1640",,Sheng Maoye,,1615,1640,mid 17th century,1634,1666,Album of six paintings; ink and color on silk,.1: 11 1/4 x 12 in. (28.6 x 30.5 cm) .2: 11 1/4 x 12 in. (28.6 x 30.5 cm) .3: 11 5/8 x 12 in. (29.5 x 30.5 cm) .4: 11 1/4 x 12 in. (28.6 x 30.5 cm) .5: 11 7/8 x 12 in. (30.2 x 30.5 cm) .6: 11 5/8 x 12 in. (29.5 x 30.5 cm),"The Sackler Fund, 1969",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/72262,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.363.128,false,true,49125,Asian Art,Hanging scroll,,China,Qing dynasty (1644–1911),,,,Artist,,Li Yu,1611–1680,,Li Yu,,1611,1680,dated 1648,1648,1648,Hanging scroll; ink on satin,Image: 33 5/8 x 11 1/4 in. (85.4 x 28.6 cm) Overall with mounting: 85 x 17 1/8 in. (215.9 x 43.5 cm) Overall with knobs: 85 x 20 5/8 in. (215.9 x 52.4 cm),"Bequest of John M. Crawford Jr., 1988",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49125,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1988.324.5,false,true,44569,Asian Art,Album leaf,清 顧澐 冊頁|Clouds and Spring Trees at Dusk,China,Qing dynasty (1644–1911),,,,Artist,,Gu Yun,1835–1896,,Gu Yun,,1835,1896,dated 1888,1888,1888,Album leaf; ink on paper,9 3/4 x 13 1/2 in. (24.8 x 34.3 cm),"Gift of Robert Hatfield Ellsworth, in memory of La Ferne Hatfield Ellsworth, 1988",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/44569,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.267.57,false,true,36173,Asian Art,Folding fan mounted as an album leaf,清 顧澐 懷素蕉林午睡圖 扇面|Huai Su in the Banana Grove,China,Qing dynasty (1644–1911),,,,Artist,,Gu Yun,1835–1896,,Gu Yun,,1835,1896,dated 1869,1869,1869,Folding fan mounted as an album leaf; ink and color on alum paper,6 3/4 x 20 1/8 in. (17.1 x 51.1 cm),"Gift of Robert Hatfield Ellsworth, in memory of La Ferne Hatfield Ellsworth, 1986",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/36173,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.784.4,false,true,73652,Asian Art,Handscroll,明/清 黃向堅 萬里尋親圖 卷|Searching for My Parents,China,Qing dynasty (1644–1911),,,,Artist,,Huang Xiangjian,"Chinese, 1609–1673",,Huang Xiangjian,,1609,1673,dated 1656,1656,1656,Handscroll; ink and color on silk,Image: 14 3/8 in. × 18 ft. 2 in. (36.5 × 553.7 cm) Overall with mounting: 14 3/4 in. × 27 ft. 1 in. (37.5 × 825.5 cm),"Gift of Julia and John Curtis, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/73652,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.278.7,false,true,36101,Asian Art,Hanging scroll,清 楊晉 滄洲牧牛圖 軸|Landscape with Figures,China,Qing dynasty (1644–1911),,,,Artist,,Yang Jin,"Chinese, 1644–1728",,Yang Jin,,1644,1728,Dated 1726,1726,1726,Hanging scroll; ink and color on paper,Image: 47 x 21 1/2 in. (119.4 x 54.6 cm) Overall with mounting: 92 1/8 x 28 1/8 in. (234 x 71.4 cm) Overall with knobs: 92 1/8 x 31 7/8 in. (234 x 81 cm),"Gift of Mr. and Mrs. Earl Morse, 1972",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/36101,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.784.10,false,true,646992,Asian Art,Album,清 汪鋆 阮元遺事十景圖 冊 紙本|Ten Sites Associated with Ruan Yuan,China,Qing dynasty (1644–1911),,,,Artist,,Wang Jun,"Chinese, 1816–after 1883",,Wang Jun,,1816,1883,dated 1883,1883,1883,Album of ten paintings; ink and color on paper,Image (each): 11 × 13 1/4 in. (27.9 × 33.7 cm),"Gift of Julia and John Curtis, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/646992,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.784.6,false,true,65625,Asian Art,Album leaf,"清 葉欣 聼雪圖 冊頁|Snowscape, from Album for Zhou Lianggong",China,Qing dynasty (1644–1911),,,,Artist,,Ye Xin,"Chinese, active ca. 1640–1673",,Ye Xin,,1640,1640,undated,1640,1673,Double album leaf from a collective album of twelve paintings and facing pages of calligraphy; ink and color on paper,9 3/4 x 13 in. (24.8 x 33 cm),"Gift of Julia and John Curtis, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/65625,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -02.18.623e,false,true,51234,Asian Art,Handscroll,敬芝軒第貳圖|Second View of the Studio for Respecting the Fungus of Longevity,China,Qing dynasty (1644–1911),,,,Artist,,Yang Tianbi,"Chinese, active early 19th century",,Yang Tianbi,,1800,1833,1825,1825,1825,Handscroll; ink and color on silk,8 3/8 x 31 in. (21.3 x 78.7 cm),"Gift of Heber R. Bishop, 1902",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/51234,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2008.673,false,true,39548,Asian Art,Hanging scroll,元 張遜 石上松花圖 軸|Rocky Landscape with Pines,China,Yuan dynasty (1271–1368),,,,Artist,,Zhang Xun,"Chinese, ca. 1295–after 1349",,ZHANG XUNG,,1295,1349,before 1346,1295,1345,Hanging scroll; ink on silk,Image: 35 3/4 × 16 3/4 in. (90.8 × 42.5 cm) Overall with mounting: 76 5/8 × 22 3/4 in. (194.6 × 57.8 cm) Overall with knobs: 76 5/8 × 26 1/8 in. (194.6 × 66.4 cm),"Ex coll.: C. C. Wang Family, Gift of Oscar L. Tang Family, 2008",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/39548,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1982.2.2,false,true,42328,Asian Art,Hanging scroll,元 陸廣 丹臺春曉圖 軸|Spring Dawn Over the Elixir Terrace,China,early Ming dynasty (1368–1644),,,,Artist,,Lu Guang,"Chinese, ca. 1300–after 1371",,Lu Guang,,1300,1371,ca. 1369,1359,1379,Hanging scroll; ink on paper,Image: 24 1/4 x 10 1/4 in. (61.6 x 26 cm) Overall with mounting: 87 1/2 x 17 5/8 in. (222.3 x 44.8 cm) Overall with knobs: 87 1/2 x 20 5/8 in. (222.3 x 52.4 cm),"Ex coll.: C. C. Wang Family, Edward Elliott Family Collection, Purchase, The Dillon Fund Gift, 1982",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/42328,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.500.8.3,false,true,78125,Asian Art,Handscroll,,China,Ming dynasty (1368–1644),,,,Artist,,Zhan Jingfeng,"Chinese, 1520–1602",,Zhan Jingfeng,,1520,1602,,1520,1602,Handscroll; ink on paper,Image: 12 1/4 x 22 in. (31.1 x 55.9 cm),"Gift of Florence and Herbert Irving, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/78125,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2015.500.8.2a, b",false,true,78085,Asian Art,Two leaves from an album,,China,Ming dynasty (1368–1644),,,,Artist,,Cheng Jiasui,"Chinese, 1565–1644",,Cheng Jiasui,,1565,1644,,1368,1644,Two leaves from an album; ink and color on paper,Each: 14 x 14 in. (35.6 x 35.6 cm),"Gift of Florence and Herbert Irving, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/78085,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1981.285.10,false,true,45669,Asian Art,Hanging scroll,明 周文靖 漁隱圖 軸|Rustic Retreat among Fishermen,China,Ming dynasty (1368–1644),,,,Artist,,Zhou Wenjing,"Chinese, active ca. 1430–after 1463",,Zhou Wenjing,,1430,1463,,1430,1530,Hanging scroll; ink and color on silk,Image: 35 15/16 x 16 1/2 in. (91.3 x 41.9 cm) Overall with mounting: 79 1/2 x 21 5/16 in. (201.9 x 54.1 cm) Overall with knobs: 79 1/2 x 23 1/8 in. (201.9 x 58.7 cm),"Edward Elliott Family Collection, Purchase, The Dillon Fund Gift, 1981",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45669,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -13.220.94,false,true,51491,Asian Art,Hanging scroll,,China,Qing dynasty (1644–1911),,,,Artist,,Zheng Zhong,"Chinese, active ca. 1612–48",,Zheng Zhong,,1612,1648,,1612,1648,Hanging scroll; ink and color on silk,Image: 34 × 12 3/8 in. (86.4 × 31.4 cm) Overall with mounting: 49 3/4 × 17 3/4 in. (126.4 × 45.1 cm),"John Stewart Kennedy Fund, 1913",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/51491,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -47.18.6,false,true,51638,Asian Art,Handscroll,,China,Ming dynasty (?) (1368–1644),,,,Artist,Attributed to,Tai Wan,"Chinese, active 1111–25",,Tai Wan,,1111,1125,,1368,1644,Handscroll; ink and color on silk,Image: 11 in. × 21 ft. 1 1/8 in. (27.9 × 642.9 cm),"From the Collection of A. W. Bahr, Purchase, Fletcher Fund, 1947",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/51638,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1991.14,false,true,44630,Asian Art,Handscroll,明 鄭重 搜山圖 卷|Searching the Mountains for Demons,China,late Ming dynasty (1368–1644),,,,Artist,,Zheng Zhong,"Chinese, active ca. 1612–48",,Zheng Zhong,,1612,1648,,1612,1644,Handscroll; ink and color on paper,10 5/8 in. x 27 ft. 9 1/2 in. (27 x 847.1 cm),"Purchase, Bequest of Dorothy Graham Bennett, 1991",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/44630,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -52.177.9,false,true,51784,Asian Art,Hanging scroll,,China,Qing dynasty (1644–1911),,,,Artist,,Empress Dowager Cixi,"Chinese, 1835–1908",,Cixi Empress Dowager,,1835,1908,18th–19th century,1700,1899,Hanging scroll; ink on gold-flecked paper,45 x 24 1/2 in. (114.3 x 62.2 cm),"Bequest of Katherine S. Dreier, 1952",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/51784,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.784.8,false,true,73330,Asian Art,Handscroll,明 二家法書合卷|Joint Calligraphy,China,Ming dynasty (1368–1644),,,,Artist|Artist,,Ni Yuanlu|Huang Daozhou,"Chinese, 1593–1644|Chinese, 1585–1646",,Ni Yuanlu|Huang Daozhou,,1593 |1585,1644 |1646,dated 1632,1632,1632,Handscroll; ink on satin,Image: 10 11/16 x 57 3/4 in. (27.1 x 146.7 cm) Overall with mounting: 10 13/16 x 357 7/16 in. (27.5 x 907.9 cm),"Gift of Julia and John Curtis, 2015",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/73330,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -13.100.115,false,true,51398,Asian Art,Fan mounted as an album leaf,,China,Song dynasty (?) (960–1279),,,,Artist|Artist,Formerly Attributed to,Huang Jucai|Unidentified Artist,"Chinese, Song dynasty, 933–after 993",,Huang Jucai|Unidentified Artist,,0933,1000,ca. 1000,990,1010,Fan mounted as an album leaf; ink and color on silk,7 7/8 x 8 7/8 in. (20.0 x 22.5 cm),"John Stewart Kennedy Fund, 1913",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/51398,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -13.100.104,false,true,40071,Asian Art,Fan mounted as an album leaf,南宋 佚名 “三元得祿”圖扇頁|Gibbons Raiding an Egret's Nest,China,Southern Song dynasty (1127–1279),,,,Artist|Artist,Formerly Attributed to,Yi Yuanji|Unidentified Artist,"Chinese, died 1066",Chinese,Yi Yuanji|Unidentified Artist,,0966,1066,late 12th century,1167,1199,Fan mounted as an album leaf; ink and color on silk,Image: 9 1/2 in. × 9 in. (24.1 × 22.9 cm),"John Stewart Kennedy Fund, 1913",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/40071,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1981.285.1,false,true,48967,Asian Art,Hanging scroll,明 倣米萬鍾 行書開襟揮手聯句 軸|Poem,China,Ming dynasty (1368–1644),,,,Artist|Artist,After,Unidentified Artist|Mi Wanzhong,"Chinese, 1570–1628",,Unidentified Artist|Mi Wanzhong,,1570,1628,,1368,1644,Hanging scroll; ink on paper,Image: 134 3/4 x 39 1/8 in. (342.3 x 99.4 cm) Overall: 186 3/4 x 48 in. (474.3 x 121.9 cm) Overall with knobs: 186 3/4 x 53 3/4 in. (474.3 x 136.5 cm),"Edward Elliott Family Collection, Purchase, The Dillon Fund Gift, 1981",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/48967,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -47.18.92,false,true,51704,Asian Art,Handscroll,明 佚名(仿)吳育 新安汪氏譜牒 卷|Portrait of a Member and Record of the Wang Family,China,Ming dynasty (1368-1644),,,,Artist|Artist,after,Unidentified Artist|Wu Yu,"Chinese, 1004–1058",,"Unidentified Artist|Wu, Yu",,1004,1058,,1368,1644,"Handscroll; calligraphy, ink on paper; portrait, ink and color on paper",13 1/8 x 9 3/4 in. (33.3 x 24.8 cm),"From the Collection of A. W. Bahr, Purchase, Fletcher Fund, 1947",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/51704,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -13.220.99d,false,true,51497,Asian Art,Album leaf,,China,Ming (1368–1644)–Qing (1644–1911) dynasty,,,,Artist|Artist,After,Guan Daosheng|Unidentified Artist,1262–1319,,Guan Daosheng|Unidentified Artist,,1262,1319,,1368,1911,Album leaf; ink on silk,10 3/4 x 10 3/4 in. (27.3 x 27.3 cm),"John Stewart Kennedy Fund, 1913",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/51497,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -13.220.5,false,true,51419,Asian Art,Handscroll,,China,Ming (1368–1644) or Qing dynasty (1644–1911),,,,Artist|Artist,In the Style of,Guo Zhongshu|Unidentified Artist,"Chinese, died 977",,Guo Zhongshu|Unidentified Artist,,0877,0977,,1571,1599,Handscroll; ink and color on silk,12 1/2 in. × 16 ft. 1 in. (31.8 × 490.2 cm),"John Stewart Kennedy Fund, 1913",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/51419,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2015.300.81.1, .2",false,true,671030,Asian Art,Screens,『伊勢物語』|Forty-nine scenes from the Tales of Ise (Ise monogatari),Japan,Edo period (1615–1868),,,,Calligrapher,Attributed to,Satomura Genchin,"Japanese, 1591–1665",,Satomura Genchin,,1591,1665,mid-17th century,1634,1666,"Pair of six-panel folding screens, with ninety-eight paintings and poem cards (shikishi) applied to gold leaf on paper; paintings: ink and red ink on paper, text: ink on paper",Image (each): 42 1/4 in. × 8 ft. 9 7/8 in. (107.3 × 268.9 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Screens,,http://www.metmuseum.org/art/collection/search/671030,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.247,false,true,671039,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Calligrapher,,Ike Taiga,"Japanese, 1723–1776",,Ike Taiga,,1723,1776,1759,1759,1759,Hanging scroll; ink on paper,Image: 9 5/16 × 13 7/16 in. (23.7 × 34.1 cm) Overall with mounting: 41 1/4 × 18 3/8 in. (104.7 × 46.7 cm) Overall with knobs: 41 1/4 × 20 5/16 in. (104.7 × 51.6 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/671039,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.248,false,true,671043,Asian Art,Album,「大雅堂画弁題詩」|“Paintings by Taigadō with Colophons”,Japan,Edo period (1615–1868),,,,Calligrapher,,Ike Taiga,"Japanese, 1723–1776",", colophons by eight calligraphers",Ike Taiga,,1723,1776,late 18th–early 19th century,1776,1833,Album,Image: 11 7/8 × 16 1/4 in. (30.2 × 41.2 cm) Album: 11 7/8 × 15 7/8 × 1 3/16 in. (30.2 × 40.4 × 3 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/671043,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.237,false,true,687621,Asian Art,Hanging scroll,『古今和歌集』断簡|Three poems from the Collection of Poems Ancient and Modern (Kokin wakashū),Japan,Kamakura period (1185–1333),,,,Calligrapher,Traditionally attributed to,Fujiwara no Tameyori,"Japanese, 939?–998",,Fujiwara no Tameyori,,0939,0998,13th century,1200,1299,Page from a book mounted as a hanging scroll; ink on paper,Image: 9 3/16 × 5 9/16 in. (23.4 × 14.1 cm) Overall with mounting: 54 5/16 × 11 in. (138 × 28 cm) Overall with knobs: 54 5/16 × 13 3/8 in. (138 × 34 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/687621,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.240,false,true,670905,Asian Art,Book,"詠歌大概|Manuscript Version of “Fundamentals of Poetic Composition” (Eiga taigai), compiled by Fujiwara no Teika (1162–1241)",Japan,Muromachi period (1392–1573),,,,Calligrapher,,Konoe Taneie,"Japanese, 1503–1566",,Konoe Taneie,,1503,1566,1531,1531,1531,Book of 102 waka by various poets; ink on paper,Image (closed): 10 1/4 × 7 1/16 in. (26 × 18 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/670905,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2015.300.164a, b",false,true,53455,Asian Art,Album leaves mounted as hanging scrolls,,Japan,Edo period (1615–1868),,,,Artist|Calligrapher,,Ike Taiga|Minagawa Kien,"Japanese, 1723–1776|Japanese, 1734–1807",,Ike Taiga|Minagawa Kien,,1723 |1734,1776 |1807,late 18th–early 19th century,1767,1807,Album leaves mounted as hanging scrolls; ink on paper,Image (a): 9 in. × 14 7/16 in. (22.8 × 36.6 cm) Overall with mounting (a): 39 3/8 × 19 7/16 in. (100 × 49.4 cm) Overall with knobs (a): 39 3/8 × 21 7/16 in. (100 × 54.5 cm) Image (b): 9 5/16 × 14 15/16 in. (23.6 × 38 cm) Overall with mounting (b): 39 3/8 × 19 1/2 in. (100 × 49.6 cm) Overall with knobs (b): 39 3/8 × 21 9/16 in. (100 × 54.7 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/53455,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2015.300.167a, b",false,true,670542,Asian Art,Album leaves mounted as hanging scrolls,,Japan,Edo period (1615–1868),,,,Calligrapher|Artist,,Rokunyo|Ike Taiga,"Japanese, 1737–1801|Japanese, 1723–1776",,Rokunyo|Ike Taiga,,1737 |1723,1801 |1776,late 18th–early 19th century,1767,1833,Album leaves mounted as hanging scrolls; ink on paper,Image (each): 9 × 14 5/8 in. (22.9 × 37.1 cm) Overall with mounting (each): 39 1/4 × 21 1/2 in. (99.7 × 54.6 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/670542,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.500.9.19,false,true,78056,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Calligrapher|Artist,,Kameda Bōsai|Sakai Hōitsu,"Japanese, 1752–1826|Japanese, 1761–1828",,Kameda Bōsai|Sakai Hōitsu,,1752 |1761,1826 |1828,ca. 1821,1821,1821,Hanging scroll; ink on paper,Image: 44 3/8 × 14 3/4 in. (112.7 × 37.5 cm) Overall with mounting: 68 1/4 × 14 3/4 in. (173.4 × 37.5 cm) Overall with knobs: 16 7/8 in. (42.9 cm),"Gift of Florence and Herbert Irving, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/78056,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.80,false,true,670935,Asian Art,Handscroll,,Japan,Edo period (1615–1868),,,,Artist|Artist|Artist,Calligraphy by|Calligraphy by,Kano Tōun|Ichiki Konzan|Saskai Genryō,"Japanese, 1625–1694|Japanese|Japanese, 1650–1723",,Kano Tōun|Ichiki Konzan|Saskai Genryō,,1625 |1650,1694 |1723,1675,1675,1675,Handscroll; ink and color on paper,Image: 10 5/8 in. × 16 ft. 10 1/16 in. (27 × 513.3 cm) Overall with knobs: 11 7/16 in. × 16 ft. 10 1/16 in. (29 × 513.3 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/670935,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.141,false,true,53447,Asian Art,Hanging scroll,柳下美人図|Courtesan and her Attendants under a Willow Tree,Japan,Edo period (1615–1868),,,,Artist|Artist|Artist,Inscribed by|Inscribed by,Unchō|Kyokutei Bakin|Kitao Masanobu (Santō Kyōden),"Japanese, active late 18th century|1767–1848|Japanese, 1761–1816",,Unchō|Kyokutei Bakin|Kitao Masanobu (Santō Kyōden),,1740 |1767 |1761,1820 |1848 |1816,1796,1600,1870,"Hanging scroll; ink, color, and gold on silk",36 1/2 x 13 3/8 in. (92.7 x 34 cm) Overall with mounting: 72 13/16 × 18 7/8 in. (185 × 48 cm) Overall with knobs: 72 13/16 × 20 5/8 in. (185 × 52.4 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/53447,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.500.9.8a–c,false,true,78167,Asian Art,Set of three hanging scrolls,,Japan,Edo period (1615–1868),,,,Calligrapher|Calligrapher|Calligrapher,a)|c),Hiin Tsūyō|Sokuhi Nyoitsu (Jifei Ruyi)|Mokuan Shōtō,"Chinese/ Japanese, 1593–1661|Chinese/ Japanese, 1616–1671|Chinese, 1611–1684",,Hiin Tsūyō|Sokuhi Nyoitsu|Mokuan Shōtō,,1593 |1616 |1611,1661 |1671 |1684,17th century,1600,1699,Set of three hanging scrolls; ink on paper,Image (a (right)): 48 7/8 in. × 11 in. (124.1 × 27.9 cm) Overall with mounting (a (right)): 79 3/4 × 15 3/8 in. (202.6 × 39.1 cm) Overall with knobs (a (right)): 79 3/4 × 17 1/4 in. (202.6 × 43.8 cm) Image (b (center)): 48 7/8 × 14 1/2 in. (124.1 × 36.8 cm) Overall with mounting (b (center)): 79 5/8 × 15 1/2 in. (202.2 × 39.4 cm) Overall with knobs (b (center)): 79 5/8 × 17 3/8 in. (202.2 × 44.1 cm) Image (c (left)): 48 7/8 in. × 11 in. (124.1 × 27.9 cm) Overall with mounting (c (left)): 79 3/4 × 15 3/8 in. (202.6 × 39.1 cm) Overall with knobs (c (left)): 79 3/4 × 17 1/4 in. (202.6 × 43.8 cm),"Gift of Florence and Herbert Irving, 2015",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/78167,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.244,false,true,671036,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Calligrapher,,Ike Taiga,"Japanese, 1723–1776",,Ike Taiga,,1723,1776,18th century,1723,1776,Hanging scroll; ink on paper,Image: 10 1/16 × 9 1/8 in. (25.5 × 23.2 cm) Overall with mounting: 41 9/16 × 14 3/4 in. (105.5 × 37.4 cm) Overall with knobs: 41 9/16 × 16 5/8 in. (105.5 × 42.3 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/671036,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.245,false,true,671037,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Calligrapher,,Ike Taiga,"Japanese, 1723–1776",,Ike Taiga,,1723,1776,18th century,1723,1776,Hanging scroll; ink on paper,Image: 9 5/16 × 8 1/8 in. (23.6 × 20.6 cm) Overall with mounting: 42 15/16 × 13 11/16 in. (109 × 34.8 cm) Overall with knobs: 42 15/16 × 15 3/4 in. (109 × 40 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/671037,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.246,false,true,671038,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Calligrapher,,Ike Taiga,"Japanese, 1723–1776",,Ike Taiga,,1723,1776,18th century,1723,1776,Hanging scroll; ink on paper,Image: 9 5/16 × 13 1/8 in. (23.7 × 33.4 cm) Overall with mounting: 40 1/4 × 17 13/16 in. (102.3 × 45.3 cm) Overall with knobs: 40 1/4 × 19 3/4 in. (102.3 × 50.2 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/671038,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.195,false,true,670944,Asian Art,Album of fifty-four sketches,,Japan,Edo (1615–1868)–Meiji period (1868–1912),,,,Artist|Artist|Artist,,Tsubaki Chinzan|Watanabe Kazan|Takagi Goan,"Japanese, 1801–1854|Japanese, 1793–1841|Japanese, active 19th century",,Tsubaki Chinzan|Watanabe Kazan|Takagi Goan,,1801 |1793 |1800,1854 |1841 |1899,19th century,1800,1899,Album of fifty-four sketches; ink and color on paper,Album: 11 7/16 × 12 3/8 × 3 13/16 in. (29 × 31.4 × 9.7 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/670944,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2015.300.166a, b",false,true,670541,Asian Art,Two album leaves mounted as two hanging scrolls,,Japan,Edo period (1615–1868),,,,Calligrapher|Artist,,Kameda Bōsai|Ike Taiga,"Japanese, 1752–1826|Japanese, 1723–1776",,Kameda Bōsai|Ike Taiga,,1752 |1723,1826 |1776,18th–19th century,1723,1826,Album leaves mounted as hanging scrolls; ink on paper,Image (a): 8 7/8 × 14 3/4 in. (22.6 × 37.5 cm) Overall with mounting (a): 39 3/8 × 19 1/2 in. (100 × 49.5 cm) Overall with knobs (a): 39 3/8 × 21 1/2 in. (100 × 54.6 cm) Image (b): 9 3/16 × 14 1/2 in. (23.4 × 36.9 cm) Overall with mounting (b): 39 3/8 × 19 9/16 in. (100 × 49.7 cm) Overall with knobs (b): 39 3/8 × 21 7/8 in. (100 × 55.5 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/670541,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.165a–c,false,true,670540,Asian Art,Three album leaves mounted as three hanging scrolls,,Japan,Edo period (1615–1868),,,,Calligrapher|Artist|Calligrapher,,Shinozaki Shōchiku|Ike Taiga|Ōkubo Shibutsu,"Japanese, 1781–1851|Japanese, 1723–1776|Japanese, 1766–1837",,Shinozaki Shōchiku|Ike Taiga|Ōkubo Shibutsu,,1781 |1723 |1766,1851 |1776 |1837,18th–19th century,1723,1851,Album leaves mounted as hanging scrolls; ink on paper,Image (a): 8 3/4 × 14 7/16 in. (22.2 × 36.7 cm) Overall with mounting (a): 39 3/16 × 19 1/2 in. (99.5 × 49.5 cm) Overall with knobs (a): 39 3/16 × 21 7/16 in. (99.5 × 54.5 cm) Image (b): 9 1/8 × 14 7/16 in. (23.1 × 36.7 cm) Overall with mounting (b): 39 5/16 × 19 7/16 in. (99.8 × 49.3 cm) Overall with knobs (b): 39 5/16 × 21 7/16 in. (99.8 × 54.5 cm) Image (c): 8 11/16 × 14 7/16 in. (22 × 36.7 cm) Overall with mounting (c): 39 3/8 × 19 7/16 in. (100 × 49.4 cm) Overall with knobs (c): 39 3/8 × 21 1/2 in. (100 × 54.6 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/670540,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -13.67.73,false,true,58906,Asian Art,Inrō,,Japan,Edo period (1615–1868),,,,Artist,In the Style of,Fang Shi Mopu,ca. 1588,,Fang Shi Mopu,,1588,1588,1721,1721,1721,"Lacquer, roiro, black hiramakie, takamakie; Interior: roiro and gold leaf",3 x 2 15/16 x 13/16 in. (7.6 x 7.5 x 2 cm),"Rogers Fund, 1913",,,,,,,,,,,,Inrō,,http://www.metmuseum.org/art/collection/search/58906,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.903,false,true,58827,Asian Art,Inrō,古墨形印籠 (文章司命)|Inrō with Chinese Scholars and Characters,Japan,Edo period (1615–1868),,,,Artist,based on design by,Kitajima Setsuzan,1636–1697,,Kitajima Setsuzan,,1636,1697,early to mid-18th century,1733,1766,"Three cases; lacquered wood with black and brown takamaki-e, togidashimaki-e Netsuke: manjū type, ivory; writing implements and books Ojime: malachite bead",3 9/16 x 1 11/16 x 5/8 in. (9.1 x 4.3 x 1.6 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Inrō,,http://www.metmuseum.org/art/collection/search/58827,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2015.300.226a, b",false,true,671019,Asian Art,Illustrated books,,Japan,Edo period (1615–1868),,,,Artist,,Nakagawa Tenju,"Japanese, died 1795",,Nakagawa Tenju,,,1795,1803,1803,1803,Two woodblock printed books; ink on paper,Each book 10 1/2 × 7 1/4 in. (26.7 × 18.4 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/671019,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2013.745a, b",false,true,78647,Asian Art,Illustrated books,,Japan,Edo period (1615–1868),,,,Artist,,Wang Gai,"Chinese, 1645–1710",,Wang Gai,,1645,1710,ca. 1748,1743,1753,Set of two woodblock printed books; ink and color on paper,11 1/8 × 7 1/16 in. (28.3 × 18 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78647,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.746a–e,false,true,78648,Asian Art,Illustrated books,,Japan,Edo period (1615–1868),,,,Artist,,Wang Gai,"Chinese, 1645–1710",,Wang Gai,,1645,1710,1753,1753,1753,Set of five woodblock printed books; ink and color on paper,each: 11 1/8 × 7 1/8 in. (28.3 × 18.1 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78648,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2015.300.74.1, .2",false,true,671028,Asian Art,Screens,金山西湖図屏風|Jinshan Island and West Lake,Japan,Edo period (1615–1868),,,,Artist,,Kano Sanraku,"Japanese, 1559–1635",,Kano Sanraku,,1559,1635,1630,1630,1630,"Pair of six-panel folding screens; ink, color, and gold on paper",Image: 60 1/16 in. × 11 ft. 9 in. (152.5 × 358.2 cm) Overall with mounting: 67 5/16 in. × 12 ft. 4 1/4 in. (170.9 × 376.6 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Screens,,http://www.metmuseum.org/art/collection/search/671028,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.500.9.2,false,true,78058,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Ōbaku Ingen,"Japanese, 1594–1673",,Ōbaku Ingen,,1594,1673,1615–1868,1615,1868,Hanging scroll; ink on paper,Image: 47 × 11 in. (119.4 × 27.9 cm) Overall with mounting: 80 1/2 × 15 1/2 in. (204.5 × 39.4 cm) Overall with knobs: 80 1/2 × 17 5/8 in. (204.5 × 44.8 cm),"Gift of Florence and Herbert Irving, 2015",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/78058,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2015.300.77.1, .2",false,true,53009,Asian Art,Screens,四季山水図屏風|Landscapes of the Four Seasons,Japan,Edo period (1615–1868),,,,Artist,,Kano Tan'yū,"Japanese, 1602–1674",,Kano Tan'yū,,1602,1674,1630s,1600,1700,Pair of six-panel folding screens; ink and color on paper,Image (each): 60 3/8 in. × 11 ft. 6 7/8 in. (153.4 × 352.7 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Screens,,http://www.metmuseum.org/art/collection/search/53009,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.242a,false,true,54779,Asian Art,Hanging scroll,桜下絵和歌色紙 紀 貫之|Poem by Ki no Tsurayuki (ca. 872–945) on Decorated Paper with Cherry Blossoms,Japan,Edo period (1615–1868),,,,Artist,,Ogata Sōken,"Japanese, 1621–1687",,Ogata Sōken,,1621,1687,mid- to late 17th century,1621,1687,Poem card (shikishi) mounted as a hanging scroll; ink and gold on paper,Image: 8 3/8 × 7 11/16 in. (21.2 × 19.5 cm) Overall with mounting (a): 41 1/4 × 12 1/2 in. (104.8 × 31.7 cm) Overall with knobs (a): 41 1/4 × 14 1/4 in. (104.8 × 36.2 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/54779,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.242b,false,true,727181,Asian Art,Hanging scroll,萩下絵和歌色紙 藤原家隆|Poem by Fujiwara no Ietaka (1158–1237) on Decorated Paper with Bush Clover,Japan,Edo period (1615–1868),,,,Artist,,Ogata Sōken,"Japanese, 1621–1687",,Ogata Sōken,,1621,1687,mid- to late 17th century,1621,1687,Poem card (shikishi) mounted as a hanging scroll; ink and gold on paper,Image: 8 3/8 × 7 5/8 in. (21.2 × 19.4 cm) Overall with mounting (b): 38 1/4 × 11 15/16 in. (97.2 × 30.4 cm) Overall with knobs (b): 38 1/4 × 13 7/8 in. (97.2 × 35.3 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/727181,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.83,false,true,53401,Asian Art,Screen,,Japan,Edo period (1615–1868),,,,Artist,,Hanabusa Itchō,"Japanese, 1652–1724",,Hanabusa Itchō,,1652,1724,after 1709,1700,1850,Six-panel folding screen; ink and color on paper,47 3/4 x 124 1/2 in. (121.3 x 316.2 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Screens,,http://www.metmuseum.org/art/collection/search/53401,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2004.366,false,true,72416,Asian Art,Screen,,Japan,Edo period (1615–1868),,,,Artist,,Watanabe Shikō,"Japanese, 1683–1755",,Watanabe Shikō,,1683,1755,first half of the 18th century,1700,1750,Two-panel folding screen; ink on paper,Image: 59 7/16 x 66 9/16 in. (151 x 169.1 cm),"Purchase, Friends of Asian Art Gifts, 2004",,,,,,,,,,,,Screens,,http://www.metmuseum.org/art/collection/search/72416,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.224a–c,false,true,670927,Asian Art,Illustrated books,,Japan,Edo period (1615–1868),,,,Artist,,Hanabusa Ippō,"Japanese, 1691–1760",,Hanabusa Ippō,,1691,1760,May 1751,1751,1751,Three woodblock printed books; ink on paper,Each book: 10 1/8 × 7 5/16 in. (25.7 × 18.5 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/670927,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.116,false,true,671018,Asian Art,Screen,白拍子・遊女図|Shirabyōshi Dancer and Female Servant; Courtesan and Girl Attendant,Japan,Edo period (1615–1868),,,,Artist,,Tsukioka Settei,"Japanese, 1710–1786",,Tsukioka Settei,,1710,1786,mid-18th century,1734,1766,"Hanging scroll paintings, remounted as a two‑panel folding screen; ink, color, and gold on silk",Image (each): 45 in. × 16 1/2 in. (114.3 × 41.9 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Screens,,http://www.metmuseum.org/art/collection/search/671018,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2015.300.157.1, .2",false,true,671023,Asian Art,Screens,山野行旅図屏風|Travels through Mountains and Fields,Japan,Edo period (1615–1868),,,,Artist,,Yosa Buson,"Japanese, 1716–1783",,Yosa Buson,,1716,1783,ca. 1765,1755,1775,Pair of six-panel folding screens; ink and color on silk,Image (each): 62 1/2 in. × 11 ft. 9 3/4 in. (158.8 × 360 cm) Overall (each): 63 7/8 in. × 11 ft. 11 1/16 in. (162.2 × 363.4 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Screens,,http://www.metmuseum.org/art/collection/search/671023,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.601,false,true,690313,Asian Art,Hanging scroll,至誠心|Profound Sincerity,Japan,Edo period (1615–1868),,,,Artist,,Jiun Sonja,"Japanese, 1718–1804",,Sonja Jiun,,1718,1804,ca.1780–90,1775,1800,Hanging scroll; ink and color on paper,Image: 44 × 17 7/8 in. (111.8 × 45.4 cm) Overall with mounting: 72 1/2 × 23 1/2 in. (184.2 × 59.7 cm) Overall with knobs: 26 1/2 in. (67.3 cm),"Gift of Joan B. Mirviss, in memory of T. Richard Fishbein, 2015",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/690313,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2008.66,false,true,74363,Asian Art,Screen,"楓橋夜泊|Calligraphy of a Tang-dynasty poem, ""Maple Bridge Night Mooring""",Japan,Edo period (1615–1868),,,,Artist,,Ike Taiga,"Japanese, 1723–1776",,Ike Taiga,,1723,1776,ca. 1770,1760,1780,Two-panel folding screen; ink on paper,Image (each panel): 53 1/4 x 22 1/8 in. (135.3 x 56.2 cm) Overall: 68 3/4 x 72 3/4 in. (174.6 x 184.8 cm),"Purchase, Friends of Asian Art Gifts, 2008",,,,,,,,,,,,Screens,,http://www.metmuseum.org/art/collection/search/74363,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"1987.81a, b",false,true,44865,Asian Art,Screen,,Japan,Edo period (1615–1868),,,,Artist,After,Ike Taiga,"Japanese, 1723–1776",,Ike Taiga,,1723,1776,probably 19th century,1800,1899,"Two-panel screen, originally sliding-door panels; ink on paper",65 1/2 x 70 1/2 in. (166.4 x 179 cm),"Purchase, The Charles Engelhard Foundation Gift, 1987",,,,,,,,,,,,Screens,,http://www.metmuseum.org/art/collection/search/44865,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2015.300.163.1, .2",false,true,53456,Asian Art,Folding screens,蘭亭曲水図屏風. 秋社図屏風|Orchid Pavilion Gathering; Autumn Landscape,Japan,Edo period (1615–1868),,,,Artist,,Ike Taiga,"Japanese, 1723–1776",,Ike Taiga,,1723,1776,ca. 1763,1600,1870,Pair of six-panel folding screens; ink and color on paper,Image (each): 63 1/4 in. × 11 ft. 8 3/16 in. (160.7 × 356 cm) Overall with mounting: 69 9/16 in. × 12 ft. 2 7/16 in. (176.7 × 372 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Screens,,http://www.metmuseum.org/art/collection/search/53456,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.672,false,true,78593,Asian Art,Illustrated book,絵本松のしらべ|Picture Book on the Music of the Pine Trees (Ehon matsu no shirabe),Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,,1726,1792,1795,1795,1795,Woodblock printed book; ink and color on paper,9 5/16 × 6 11/16 in. (23.7 × 17 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78593,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2015.300.197.1, .2",false,true,671024,Asian Art,Screens,芦雁図屏風; 柳に水上月図屏風|Goose and Reeds; Willows and Moon,Japan,Edo period (1615–1868),,,,Artist,,Maruyama Ōkyo,"Japanese, 1733–1795",,Maruyama Ōkyo,,1733,1795,right screen: 1774; left screen: 1793,1774,1793,"Pair of six-panel folding screens; ink, color and gold on paper",Each: Image: 60 9/16 in. × 11 ft. 7 1/2 in. (153.9 × 354.4 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Screens,,http://www.metmuseum.org/art/collection/search/671024,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2015.300.206.1, .2",false,true,53406,Asian Art,Screens,山樵漁夫図屏風|Woodcutters and Fishermen,Japan,Edo period (1615–1868),,,,Artist,,Matsumura Goshun,"Japanese, 1752–1811",,Matsumura Goshun,,1752,1811,ca. 1790–95,1600,1800,Pair of six-panel folding screens; ink and color on paper,Image (each): 65 15/16 in. × 12 ft. 2 7/16 in. (167.5 × 372 cm) Overall with mounting: 67 1/2 in. × 12 ft. 4 in. (171.5 × 376 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Screens,,http://www.metmuseum.org/art/collection/search/53406,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.889,false,true,78779,Asian Art,Illustrated book,,Japan,Edo period (1615–1868),,,,Artist,,Kitao Masanobu (Santō Kyōden),"Japanese, 1761–1816",,Kitao Masanobu (Santō Kyōden),,1761,1816,1786,1786,1786,Woodblock printed book; ink and color on paper,10 7/16 × 7 1/16 in. (26.5 × 18 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78779,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2015.300.93.1, .2",false,true,53423,Asian Art,Screens,,Japan,Edo period (1615–1868),,,,Artist,,Sakai Hōitsu,"Japanese, 1761–1828",,Sakai Hōitsu,,1761,1828,ca. 1805,1720,1920,"Pair of six-panel folding screens; ink, color, and gold on gilded paper",Image: 38 x 82 3/16 in. (96.5 x 208.8 cm) Overall with mounting: 39 9/16 × 83 3/4 in. (100.5 × 212.8 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Screens,,http://www.metmuseum.org/art/collection/search/53423,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.225a–h,false,true,670895,Asian Art,Illustrated books,,Japan,Edo period (1615–1868),,,,Artist,,Tani Bunchō,"Japanese, 1763–1840",,Tani Bunchō,,1763,1840,1809,1809,1809,Eight woodblock printed books; ink and color on paper,Each book: 9 1/4 × 6 1/4 in. (23.5 × 15.9 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/670895,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.667,false,true,78588,Asian Art,Illustrated book,融齋畫譜|Yūsai Picture Album (Yūsai gafu),Japan,Edo period (1615–1868),,,,Artist,,Nakabayashi Chikutō,"Japanese, 1776–1853",,Nakabayashi Chikutō,,1776,1853,1831,1831,1831,"Woodblock printed book (orihon, accordion-style); ink and color on paper",10 15/16 × 6 5/16 in. (27.8 × 16 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78588,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2016.254.1, .2",false,true,640049,Asian Art,Folding screens,四季琵琶湖図屏風|Lake Biwa in Four Seasons,Japan,Edo period (1615–1868),,,,Artist,,Nukina Kaioku,"Japanese, 1778–1863",,Nukina Kaioku,,1778,1863,1834,1834,1834,Pair of six-panel folding screens; ink and color on paper,Image: 24 9/16 × 60 5/8 in. (62.4 × 154 cm) Overall with mounting: 67 1/2 × 25 3/8 in. (171.4 × 64.5 cm),"Gift of Robert and Betsy Feinberg, 2016",,,,,,,,,,,,Screens,,http://www.metmuseum.org/art/collection/search/640049,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.892a–c,false,true,78782,Asian Art,Illustrated book,天の浮橋|Floating Bridge of Heaven (Ama no ukihashi),Japan,Edo period (1615–1868),,,,Artist,,Yanagawa Shigenobu,"Japanese, 1787–1832",,Yanagawa Shigenobu,,1787,1832,ca. 1830s,1830,1839,Set of three woodblock printed books; ink and color on paper,each: 9 13/16 × 7 1/16 in. (25 × 18 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78782,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2013.763a, b",false,true,78665,Asian Art,Illustrated books,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kuniyoshi,"Japanese, 1797–1861",,Utagawa Kuniyoshi,,1797,1861,ca. 1850,1845,1855,Set of two woodblock printed books; ink and color on paper,each: 7 1/16 × 4 3/4 in. (18 × 12 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78665,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -91.1.717,false,true,58488,Asian Art,Inrō,,Japan,Edo period (1615–1868),,,,Artist,Attributed to,Matsuda Sukenaga,"Japanese, 1800–1871",,"Sukenaga, Matsuda",,1800,1871,early–mid 19th century,1823,1866,"Lacquer, carved wood imitating leather, gold metal clasp; Interior: plain",H. 3 1/16 in. (7.7 cm); W. 1 15/16 in. (5 cm); D. 1 1/8 in. (2.8 cm),"Edward C. Moore Collection, Bequest of Edward C. Moore, 1891",,,,,,,,,,,,Inrō,,http://www.metmuseum.org/art/collection/search/58488,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"JIB98a, b",false,true,57785,Asian Art,Illustrated book,池田孤邨画 『抱一上人真蹟鏡』|Mirror of Genuine Work of Monk Hōitsu (Hōitsu shōnin shinseki kagami),Japan,Edo period (1615–1868),,,,Artist,,Ikeda Koson,"Japanese, 1803–1868",,Ikeda Koson,,1803,1868,1817,1817,1817,Set of two woodblock printed books; ink and color on paper,Image (a): 10 1/16 x 7 3/16 x 5/16 in. (25.5 x 18.2 x 0.8 cm) Image (b): 10 1/16 x 7 3/16 x 5/16 in. (25.6 x 18.3 x 0.8 cm) Overall: 13 3/4 in. (35 cm) (open for both volumes),"The Howard Mansfield Collection, Gift of Howard Mansfield, 1936",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/57785,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JIB71,false,true,57669,Asian Art,Illustrated book,,Japan,Edo period (1615–1868),,,,Artist,,Ōishi Shuga,"Japanese, born 1752 (?)",,Ōishi Shuga,,1752,1852,Spring 1822,1822,1822,Ink and color on paper,6 7/8 × 9 1/4 × 5/8 in. (17.5 × 23.5 × 1.6 cm),"Rogers Fund, 1919",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/57669,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JIB61,false,true,57661,Asian Art,Illustrated book,,Japan,Edo period (1615–1868),,,,Artist,,Kokan,late 17th–early 18th century,,Kokan,,1650,1750,1724,1724,1724,Ink on paper,10 5/8 × 7 1/8 × 3/8 in. (27 × 18.1 × 1 cm),"Rogers Fund, 1918",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/57661,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.222,false,true,53008,Asian Art,Paintings mounted on folding screen,,Japan,Edo period (1615–1868),,,,Artist,,Soga Nichokuan,"Japanese, active mid-17th century",,Soga Nichokuan,,1600,1700,mid-17th century,1600,1700,Pair of fan-shaped paintings mounted on two-panel folding screen; ink on paper,73 1/2 x 67 3/4 in. (186.7 x 172.1 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Screens,,http://www.metmuseum.org/art/collection/search/53008,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"1999.204.1, .2",false,true,50843,Asian Art,Screen,,Japan,Edo period (1615–1868),,,,Artist,,Kano Sanboku,"Japanese, active late 17th–early 18th century",,Kano Sanboku,,1671,1725,late 17th century,1667,1699,"Pair of six-panel folding screens; ink, color, and gold on paper",Image: 59 in. x 12 ft. 1/2 in. (149.9 x 367 cm),"Purchase, Friends of Asian Art Gifts, 1999",,,,,,,,,,,,Screens,,http://www.metmuseum.org/art/collection/search/50843,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2016.745,false,true,78458,Asian Art,Handscroll,"白居易作「醉吟先生傳」断簡|Excerpts from Bai Juyi's ""Biography of a Master of Drunken Poetry"" (Suigin sensei den)",Japan,Heian period (794–1185),,,,Artist,,Fujiwara no Yukinari (Kōzei),"Japanese, 972–1027",,Fujiwara no Yukinari,,0972,1027,early 11th century,1000,1027,Detached section of a handscroll mounted as a hanging scroll; ink on paper,Image: 10 13/16 × 3 3/8 in. (27.5 × 8.6 cm) Overall with mounting: 53 1/8 × 11 1/16 in. (134.9 × 28.1 cm) Overall with knobs: 53 1/8 × 12 7/8 in. (134.9 × 32.7 cm),"Gift of Raymond and Priscilla Vickers, 2016",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/78458,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.232,false,true,670938,Asian Art,"Page from book, mounted as hanging scroll",,Japan,Heian period (794–1185),,,,Artist,Traditionally attributed to,Fujiwara no Sadayori,"Japanese, 995–1045",,Fujiwara no Sadayori,,0995,1045,early 12th century,1100,1133,"Page from book, mounted as hanging scroll; ink on paper",Image: 8 1/8 in. × 5 in. (20.6 × 12.7 cm) Overall with mounting: 48 7/16 × 14 3/16 in. (123 × 36 cm) Overall with knobs: 48 7/16 × 15 11/16 in. (123 × 39.8 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/670938,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.235,false,true,671049,Asian Art,"Page from book, mounted as hanging scroll","『三宝絵詞』断簡 (東大寺切)|Page from Illustrations and Explanations of the Three Jewels (Sanbō e-kotoba), one of the “Tōdaiji Fragments” (Tōdaiji-gire)",Japan,Heian period (794–1185),,,,Artist,Calligraphy attributed to,Minamoto no Toshiyori,"Japanese, 1055–1129",,Minamoto no Toshiyori,,1055,1129,1120,1120,1120,Page from a book; ink on decorated paper,Image: 9 1/4 × 5 7/8 in. (23.5 × 15 cm) Overall with mounting: 49 3/16 × 13 3/8 in. (125 × 34 cm) Overall with knobs: 49 3/16 × 15 3/8 in. (125 × 39 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/671049,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.500.9.6,false,true,78166,Asian Art,Hanging scroll,"伝源俊頼 『三宝絵詞』 (東大寺切)|Page from the Illustrations and Explanations of the Three Jewels (Sanbō ekotoba), known as the Tōdaiji Fragment (Tōdaiji-gire)",Japan,Heian period (794–1185),,,,Artist,Calligraphy attributed to,Minamoto no Toshiyori,"Japanese, 1055–1129",,Minamoto no Toshiyori,,1055,1129,1120,1120,1120,Detached page from a book mounted as a hanging scroll; ink on mica paper,Image: 9 1/2 x 5 3/8 in. (24.1 x 13.7 cm),"Gift of Florence and Herbert Irving, 2015",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/78166,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.233,false,true,671014,Asian Art,"Page from book, mounted as hanging scroll",,Japan,Heian period (794–1185),,,,Artist,Traditionally attributed to,Monk Saigyō,"Japanese, 1118–1190",,,,1118,1190,late 12th century,1167,1185,"Page from book, mounted as hanging scroll; ink on paper",Image: 6 7/8 × 5 11/16 in. (17.4 × 14.5 cm) Overall with mounting: 51 5/16 × 14 1/2 in. (130.3 × 36.8 cm) Overall with knobs: 51 5/16 × 16 1/4 in. (130.3 × 41.2 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/671014,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.500.2.53a–g,false,true,40491,Asian Art,Box,,Japan,Meiji period (1868–1912),,,,Artist,,Shibata Zeshin,"Japanese, 1807–1891",,Shibata Zeshin,,1807,1891,second half of the 19th century,1850,1899,"Gold maki-e on black and brown lacquer, with mother-of-pearl inlay and pewter",L. 9 5/8 in. (24.4 cm); W. 9 3/8 in. (23.8 cm); H. 16 1/2 in. (41.9 cm),"Gift of Florence and Herbert Irving, 2015",,,,,,,,,,,,Lacquer,,http://www.metmuseum.org/art/collection/search/40491,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.500.2.54a–o,false,true,40498,Asian Art,Writing box; table,2代由木尾雪雄作|Writing Box (Suzuribako) and Writing Table (Bundai) with Pines at Takasago and Sumiyoshi,Japan,Meiji period (1868–1912),,,,Artist,,Yukio Yukio II,"Japanese, 1860–1929",,Yukio Yukio II,,1860,1929,early 20th century,1900,1912,"Lacquered wood with gold, silver takamaki-e, hiramaki-e, cut-out gold foil on nashiji ground, silver inlay, silver fittings",Writing box: H. 2 in.; W. 9 in.; L. 9 3/4 in. Table: H. 4 7/8 in.; W.14 1/2 in.; L. 24 in.,"Gift of Florence and Herbert Irving, 2015",,,,,,,,,,,,Lacquer,,http://www.metmuseum.org/art/collection/search/40498,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.228,false,true,670953,Asian Art,illustrated book,,Japan,Meiji period (1868–1912),,,,Artist,,Utagawa (Baidō) Kokunimasa,"Japanese, 1874–1944",,Utagawa (Baidō) Kokunimasa,,1874,1944,1879,1879,1879,Woodblock printed book; ink and color on paper,Image: 8 1/8 × 5 1/2 in. (20.6 × 14 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/670953,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.585,false,true,688515,Asian Art,Folding screen,,Japan,Taishō period (1912–26),,,,Artist,,Imazu Tatsuyuki,"Japanese, active early 20th century",,Imazu Tatsuyuki,,1900,1926,ca. 1925,1915,1926,Two-panel folding screen; mineral colors and metallic powders on paper,Image: 80 1/8 × 72 13/16 in. (203.5 × 185 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2015",,,,,,,,,,,,Screens,,http://www.metmuseum.org/art/collection/search/688515,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.236,false,true,670885,Asian Art,Hanging scroll,"『続古今和歌集』断簡|Two Poems from the Collection of Poems Ancient and Modern, Continued (Zoku kokin wakashū)",Japan,Kamakura period (1185–1333),,,,Artist,,Nun Abutsu,"Japanese, died 1283",,Abutsu,,,1283,13th century,1200,1299,Page from a book; ink on paper,Image: 9 1/4 × 5 1/2 in. (23.5 × 14 cm) Overall with mounting: 54 5/16 × 11 in. (138 × 27.9 cm) Overall with knobs: 54 5/16 × 13 1/2 in. (138 × 34.3 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/670885,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2014.719.3,false,true,60437,Asian Art,Hanging scroll,明恵房高弁筆 『夢の記』|Section of the Dream Diary (Yume no ki) with a Sketch of Mountains,Japan,Kamakura period (1185–1333),,,,Artist,,Myōe Kōben,"Japanese, 1173–1232",,Myōe Kōben,,1173,1232,ca. 1203–10,1203,1210,Hanging scroll; ink on paper,Image: 12 x 19 in. (30.5 x 48.3 cm) Overall with mounting: 44 5/8 x 19 5/8 in. (113.3 x 49.8 cm) Overall with knobs: 44 5/8 x 21 1/2 in. (113.3 x 54.6 cm),"Gift of Sylvan Barnet and William Burto, in honor of Saretta and Howard Barnet, 2014",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/60437,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2014.719.4,false,true,60435,Asian Art,Hanging scroll,明恵房高弁筆 『夢の記』 断簡|Section of the Dream Diary (Yume no ki),Japan,Kamakura period (1185–1333),,,,Artist,,Myōe Kōben,"Japanese, 1173–1232",,Myōe Kōben,,1173,1232,dated 1225,1225,1225,Hanging scroll; ink on paper,Image: 13 1/4 x 21 5/8 in. (33.7 x 54.9 cm) Overall with mounting: 48 x 27 in. (121.9 x 68.6 cm) Overall with knobs: 48 x 29 1/8 in. (121.9 x 74 cm),"Gift of Sylvan Barnet and William Burto, in honor of Saretta and Howard Barnet, 2014",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/60435,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2014.719.5,false,true,60451,Asian Art,Hanging scroll,,Japan,Kamakura period (1185–1333),,,,Artist,,Myōe Kōben,"Japanese, 1173–1232",,Myōe Kōben,,1173,1232,ca. 1221,1221,1221,Hanging scroll; ink on paper,Image: 8 1/4 x 17 in. (21 x 43.2 cm) Overall with mounting: 39 7/8 x 21 1/16 in. (101.3 x 53.5 cm) Overall with knobs: 39 7/8 x 22 7/8 in. (101.3 x 58.1 cm),"Gift of Sylvan Barnet and William Burto, in honor of Saretta and Howard Barnet, 2014",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/60451,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2015.300.73.1, .2",false,true,53240,Asian Art,Screens,,Japan,Momoyama period (1573–1615),,,,Artist,,Unkoku Tōgan,"Japanese, 1547–1618",,Unkoku Tōgan,,1547,1618,late 16th–early 17th century,1573,1615,"Pair of six-panel folding screens; ink, color, and gold dust on paper",Image (each): 61 15/16 in. × 11 ft. 8 1/4 in. (157.3 × 356.2 cm) Overall with mounting: 68 15/16 in. × 12 ft. 3 7/8 in. (175.1 × 375.6 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Screens,,http://www.metmuseum.org/art/collection/search/53240,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2004.261,false,true,72569,Asian Art,Calligraphy,,Japan,Momoyama period (1573–1615),,,,Artist,,Konoe Nobutada,"Japanese, 1565–1614",,Konoe Nobutada,,1565,1614,early 17th century,1600,1633,Hanging scroll; ink on paper,Image: 16 x 12 in. (40.6 x 30.5 cm),"Gift of Tomohiko and Kyoko Horie, 2004",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/72569,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2015.300.70.1, .2",false,true,72585,Asian Art,Screens,,Japan,Momoyama period (1573–1615),,,,Artist,Circle of,Kano Mitsunobu,"Japanese, ca. 1561–1608",,Kano Mitsunobu,,1561,1608,late 16th century,1567,1599,"Pair of six-panel folding screens; ink, color, and gold leaf on paper",Each: 59 15/16 in. × 11 ft. 7 7/16 in. (152.3 × 354.2 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Screens,,http://www.metmuseum.org/art/collection/search/72585,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2014.718,false,true,60472,Asian Art,Hanging scroll,,Japan,Muromachi period (1392–1573),,,,Artist,,Zekkai Chūshin,"Japanese, 1336–1405",,Zekkai Chūshin,,1336,1405,14th century,1392,1399,Hanging scroll; ink on paper,Image: 12 1/4 × 16 5/16 in. (31.1 × 41.4 cm) Overall with mounting: 45 3/8 × 21 1/2 in. (115.3 × 54.6 cm) Overall with knobs: 45 3/8 × 23 3/4 in. (115.3 × 60.3 cm),"Gift of Sylvan Barnet and William Burto, 2014",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/60472,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2014.719.10,false,true,60469,Asian Art,Hanging scroll,策彦周良筆 墨跡|Account of the Three Springs of Jiangsu Province in China,Japan,Muromachi period (1392–1573),,,,Artist,,Sakugen Shūryō,"Japanese, 1501–1579",,Sakugen Shūryō,,1501,1579,late 16th century,1567,1573,Hanging scroll; ink on paper,Image: 11 1/2 x 16 3/4 in. (29.2 x 42.5 cm) Overall with mounting: 44 1/4 x 21 7/8 in. (112.4 x 55.6 cm) Overall with knobs: 44 1/4 x 23 3/4 in. (112.4 x 60.3 cm),"Gift of Sylvan Barnet and William Burto, 2014",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/60469,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.241,false,true,671054,Asian Art,Book mounted as handscroll,"柳江書 連歌集『老葉』より「旅」|Manuscript Version of the “Travel” Section of the Linked Verse (Renga) Collection “Aged Leaves” (Wakuraba), compiled by Sōgi (1421–1502)",Japan,Muromachi period (1392–1573),,,,Artist,Calligraphy by,Ryūkō,"Japanese, active 16th century",,Ryūkō,,1500,1573,1533,1533,1533,Book mounted as handscroll; ink on paper,Overall: 9 1/16 in. × 10 ft. 15/16 in. (23 × 307.2 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/671054,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2015.300.51.1, .2",false,true,53223,Asian Art,Screens,山水図屏風|Landscape after Xia Gui,Japan,Muromachi period (1392–1573),,,,Artist,Traditionally attributed to,Tenshō Shūbun,"Japanese, active 1414–before 1463",,Tenshō Shūbun,,1414,1463,early–mid-15th century,1414,1463,Two six-panel folding screens; ink and color on paper,60 5/8 in. × 9 ft. 6 3/16 in. (154 × 290 cm) Overall with mounting: 68 in. × 9 ft. 11 11/16 in. (172.7 × 304 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Screens,,http://www.metmuseum.org/art/collection/search/53223,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2014.719.7,false,true,60470,Asian Art,Hanging scroll,墨蹟|Poem on the Theme of a Monk’s Life,Japan,Nanbokuchō period (1336–92),,,,Artist,,Sesson Yūbai,"Japanese, 1290–1346",,Sesson Yūbai,,1290,1346,14th century,1336,1392,Hanging scroll; ink on paper,Image: 16 x 23 3/8 in. (40.6 x 59.4 cm) Overall with mounting: 51 x 29 1/8 in. (129.5 x 74 cm) Overall with knobs: 51 x 31 3/16 in. (129.5 x 79.2 cm),"Gift of Sylvan Barnet and William Burto, in honor of Miyeko Murase, 2014",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/60470,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2014.719.8,false,true,60473,Asian Art,Hanging scroll,絶海中津筆 「山空松子落」|“The Mountain is Empty; A Pinecone Falls”,Japan,Nanbokuchō period (1336–92),,,,Artist,,Zekkai Chūshin,"Japanese, 1336–1405",,Zekkai Chūshin,,1336,1405,late 14th century,1367,1405,Hanging scroll; ink on paper,Image: 34 1/2 x 8 9/16 in. (87.6 x 21.7 cm) Overall with mounting: 65 7/8 x 9 7/16 in. (167.3 x 24 cm) Overall with knobs: 65 7/8 x 11 3/8 in. (167.3 x 28.9 cm),"Gift of Sylvan Barnet and William Burto, in honor of Elizabeth ten Grotenhuis and Merton Flemings, 2014",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/60473,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.289a–g,false,true,53416,Asian Art,Box,里芋菊蒔絵重箱|Stacked Food Box (Jūbako) with Taro Plants and Chrysanthemums,Japan,late Edo (1615–1868)–early Meiji (1868–1912) period,,,,Artist,,Shibata Zeshin,"Japanese, 1807–1891",,Shibata Zeshin,,1807,1891,mid-19th century,1834,1866,"Lacquered wood, gold and silver hiramaki-e, takamaki-e, and colored togidashimaki-e",H. 16 1/2 in. (41.9 cm); W. 9 in. (22.9 cm); D. 9 5/8 in. (24.4 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Lacquer,,http://www.metmuseum.org/art/collection/search/53416,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3284,false,true,53345,Asian Art,Woodblock print,Hakurai taizo no zu|View of the Large Imported Elephant,Japan,Edo period (1615–1868),,,,Artist,,Taguchi (Utagawa) Yoshimori,1830–1884,,Taguchi (Utagawa) Yoshimori,,1830,1884,"1863 (Bunkyo 3, 4th month)",1863,1863,Polychrome woodblock print; ink and color on paper,Image: 14 3/4 × 10 3/16 in. (37.5 × 25.9 cm),"Gift of Lincoln Kirstein, 1959",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/53345,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1036,false,true,54321,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shuntei,"Japanese, 1770–1820",,Katsukawa Shuntei,,1770,1820,ca. 1815,1795,1825,Polychrome woodblock print (surimono); ink and color on paper,7 5/8 x 8 7/8 in. (19.4 x 22.5 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54321,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1933,false,true,54473,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shuntei,"Japanese, 1770–1820",,Katsukawa Shuntei,,1770,1820,ca. 1816,1806,1826,Polychrome woodblock print (surimono); ink and color on paper,7 3/4 x 7 in. (19.7 x 17.8 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54473,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2060,false,true,54840,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shuntei,"Japanese, 1770–1820",,Katsukawa Shuntei,,1770,1820,ca. 1815,1805,1825,Polychrome woodblock print (surimono); ink and color on paper,7 3/4 x 7 1/4 in. (19.7 x 18.4 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54840,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2336,false,true,54120,Asian Art,Print,花魁と梅盆栽|Courtesan and her Child Attendant with a Potted Plum Tree,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shuntei,"Japanese, 1770–1820",,Katsukawa Shuntei,,1770,1820,ca. 1815,1805,1825,Part of an album of woodblock prints (surimono); ink and color on paper,8 x 7 1/4 in. (20.3 x 18.4 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54120,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2910,false,true,56025,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shuntei,"Japanese, 1770–1820",,Katsukawa Shuntei,,1770,1820,1770–1820,1770,1820,Polychrome woodblock print; ink and color on paper,14 7/8 x 9 7/8 in. (37.8 x 25.1 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56025,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2167,false,true,55076,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Taisosai Hokushu,"Japanese, 18th–19th century",,Taisosai Hokushu,,1700,1899,probably 1819,1819,1819,Polychrome woodblock print (surimono); ink and color on paper,8 1/8 x 7 1/4 in. (20.6 x 18.4 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55076,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3324,false,true,53702,Asian Art,Print,外国人之図|Views of Foreigners (Gaikokujin no zu),Japan,Edo period (1615–1868),,,,Artist,,Utagawa Yoshitomi,"Japanese, active mid-19th century",,Utagawa Yoshitomi,,1836,1870,1861,1861,1861,Polychrome woodblock print; ink and color on paper,H. 14 1/2 in. (36.8 cm); W. 10 in. (25.4 cm),"Gift of Lincoln Kirstein, 1959",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/53702,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3329,false,true,55477,Asian Art,Print,Ikiutsushi Americajin no zu|生写亜墨利加人之図|An American Drawn from Life,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Yoshitomi,"Japanese, active mid-19th century",,Utagawa Yoshitomi,,1836,1870,1861 (2nd month),1861,1861,Polychrome woodblock print; ink and color on paper,14 1/4 x 10 in. (36.2 x 25.4 cm),"Gift of Lincoln Kirstein, 1959",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55477,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.235,false,true,73526,Asian Art,Woodblock print,Oroshia|Russian Horseman,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Yoshitomi,"Japanese, active mid-19th century",,Utagawa Yoshitomi,,1836,1870,"10th month, 1860",1860,1860,Polychrome woodblock print; ink and color on paper,Image: 14 3/4 x 10 1/2 in. (37.5 x 26.7 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73526,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.236,false,true,73527,Asian Art,Print,Orandasen|Dutch Ship,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Yoshitomi,"Japanese, active mid-19th century",,Utagawa Yoshitomi,,1836,1870,"2nd month, 1861",1861,1861,Polychrome woodblock print; ink and color on paper,Image: 14 x 9 1/2 in. (35.6 x 24.1 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73527,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.227a–f,false,true,53449,Asian Art,Prints,『諸國六玉川』|Six Tamagawa Rivers from Various Provinces (Shokoku Mu Tamagawa),Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",,1797,1858,1857,1750,1900,Six polychrome woodblock prints; ink and color on paper,Image (each): 14 1/4 × 9 5/8 in. (36.2 × 24.4 cm) Mat (each): 22 13/16 × 15 9/16 in. (58 × 39.6 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/53449,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -93.3.197,false,true,45917,Asian Art,Vase,,Japan,,,,,Artist,,Eiraku Hozen,1795–1854,(Zengoro Hozen),Eiraku Hozen,,1795,1854,1820,1820,1820,White porcelain decorated with gold on an iron red ground,H. 3 5/8 in. (9.2 cm),"Gift of Charles Stewart Smith, 1893",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/45917,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -93.3.297,false,true,47986,Asian Art,Bowl,,Japan,,,,,Artist,,Eiraku Hozen,1795–1854,,Eiraku Hozen,,1795,1854,1830,1830,1830,"White porcelain; reproduction of Chinese Song, Dingyao bowl (Kairakuen ware)",H. 1 3/8 in. (3.5 cm); Diam. of rim 3 7/8 in. (9.8 cm); Diam. of base 1 1/4 in. (3.2 cm),"Gift of Charles Stewart Smith, 1893",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/47986,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -93.3.427,false,true,46697,Asian Art,Washer,,Japan,,,,,Artist,,Eiraku Hozen,1795–1854,,Eiraku Hozen,,1795,1854,1800,1800,1800,Clay covered with colored and transparent glazes over relief (Kyoto ware),H. 5 7/8 in. (14.9 cm); Diam. of rim 7 1/4 in. (18.4 cm); Diam. of base 3 7/8 in. (9.8 cm),"Gift of Charles Stewart Smith, 1893",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/46697,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"93.3.191a, b",false,true,44450,Asian Art,Jar with lid,,Japan,,,,,Artist,,Eiraku Hozen,1795–1854,,Eiraku Hozen,,1795,1854,1820,1820,1820,White porcelain decorated with gold and iron red (Kyoto ware),H. 2 3/4 in. (7 cm); Diam. 2 3/4 in. (7 cm); Diam. of rim 1 1/8 in. (2.9 cm); Diam. of base 1 1/4 in. (3.2 cm),"Gift of Charles Stewart Smith, 1893",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/44450,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"93.3.203a, b",false,true,46506,Asian Art,Wine kettle,,Japan,,,,,Artist,,Eiraku Hozen,1795–1854,(Zengoro Hozen),Eiraku Hozen,,1795,1854,1810,1810,1810,White porcelain decorated with iron red and gold (Kyoto ware),H. 5 1/4 in. (13.3 cm); W. at spout 6 3/8 in. (16.2 cm); Diam. 5 in. (12.7 cm); Diam. of base 3 in. (7.6 cm),"Gift of Charles Stewart Smith, 1893",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/46506,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"93.3.381a, b",false,true,46497,Asian Art,Incense box,,Japan,,,,,Artist,,Eiraku Hozen,1795–1854,,Eiraku Hozen,,1795,1854,1800,1800,1800,Clay covered inside with a transparent crackled glaze and outside with polychrome glazes (Kyoto ware),H. 2 1/2 in. (6.4 cm); Diam. 1 3/4 in. (4.4 cm),"Gift of Charles Stewart Smith, 1893",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/46497,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -17.118.63,false,true,62883,Asian Art,Teabowl,,Japan,Edo period (1615–1868),,,,Artist,,Ichigen,died 1722,,Ichigen,,1622,1722,ca. 1720,1710,1730,"Clay covered with a pitted, black glaze (Raku ware)",H. 3 3/8 in. (8.6 cm); Diam. 4 1/4 in. (10.8 cm),"Rogers Fund, 1917",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/62883,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -93.3.216,false,true,62794,Asian Art,Teabowl,,Japan,Edo period (1615–1868),,,,Artist,,Eiraku Hozen,1795–1854,,Eiraku Hozen,,1795,1854,1790,1790,1790,"Clay covered with a fine crackled glaze; decorated with white slip, colored enamels and gold and silver (Kyoto ware)",H. 3 1/4 in. (8.3 cm); Diam. of rim 5 in. (12.7 cm); Diam. of base 1 7/8 in. (4.8 cm),"Gift of Charles Stewart Smith, 1893",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/62794,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -23.225.251,false,true,63023,Asian Art,Bowl,,Japan,Edo period (1615–1868),,,,Artist,,Eiraku Hozen,1795–1854,,Eiraku Hozen,,1795,1854,1800,1800,1800,Porcelain decorated with enamel and gold (Kyoto ware),H. 2 in. (5.1 cm); W. 6 3/8 in. (16.2 cm),"Gift of Mrs. V. Everit Macy, 1923",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/63023,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -23.225.253,false,true,63025,Asian Art,Bowl,,Japan,Edo period (1615–1868),,,,Artist,,Eiraku Hozen,1795–1854,,Eiraku Hozen,,1795,1854,1800,1800,1800,Porcelain decorated with enamel and gold (Kyoto ware),H. 3 1/4 in. (8.3 cm); W. sq. 5 3/4 in. (14.6 cm),"Gift of Mrs. V. Everit Macy, 1923",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/63025,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -23.225.259,false,true,63027,Asian Art,Bowl,,Japan,Edo period (1615–1868),,,,Artist,,Eiraku Hozen,1795–1854,,Eiraku Hozen,,1795,1854,1800,1800,1800,Porcelain glazed with recessed design in enamels (Kyoto ware),H. 3 5/8 in. (9.2 cm); Diam. 6 3/4 in. (17.1 cm),"Gift of Mrs. V. Everit Macy, 1923",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/63027,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -23.225.261,false,true,63028,Asian Art,Bowl,,Japan,Edo period (1615–1868),,,,Artist,,Eiraku Hozen,1795–1854,,Eiraku Hozen,,1795,1854,1800,1800,1800,Porcelain decorated in enamels and blue under the glaze,H. 3 1/8 in. (7.9 cm); Diam. 5 1/8 in. (13 cm),"Gift of Mrs. V. Everit Macy, 1923",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/63028,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -23.225.265,false,true,63029,Asian Art,Bowl,,Japan,Edo period (1615–1868),,,,Artist,,Eiraku Hozen,1795–1854,,Eiraku Hozen,,1795,1854,1800,1800,1800,Porcelain decorated with glaze and gold (Kyoto ware),H. 4 1/8 in. (10.5 cm); Diam. 7 in. (17.8 cm),"Gift of Mrs. V. Everit Macy, 1923",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/63029,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -23.225.266,false,true,63030,Asian Art,Cup,,Japan,Edo period (1615–1868),,,,Artist,,Eiraku Hozen,1795–1854,,Eiraku Hozen,,1795,1854,1800,1800,1800,Stoneware with lustrous glaze; the decoration in enamels (Kyoto ware),H. 3 1/2 in. (8.9 cm); Diam. 3 1/2 in. (8.9 cm),"Gift of Mrs. V. Everit Macy, 1923",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/63030,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -23.225.270,false,true,47332,Asian Art,Vase,,Japan,Edo period (1615–1868),,,,Artist,,Eiraku Hozen,1795–1854,,Eiraku Hozen,,1795,1854,1800,1800,1800,Porcelain; white glaze covered with design in high relief (Kyoto ware),H. 3 3/16 in. (8.1 cm),"Gift of Mrs. V. Everit Macy, 1923",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/47332,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -23.225.271,false,true,63031,Asian Art,Bowl,,Japan,Edo period (1615–1868),,,,Artist,,Eiraku Hozen,1795–1854,,Eiraku Hozen,,1795,1854,1800,1800,1800,Porcelain decorated with red under the glaze; bottom unglazed (Kyoto ware),H. 2 3/8 in. (6 cm); Diam. 4 1/4 in. (10.8 cm),"Gift of Mrs. V. Everit Macy, 1923",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/63031,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -23.225.272,false,true,48577,Asian Art,Vase,,Japan,Edo period (1615–1868),,,,Artist,,Eiraku Hozen,1795–1854,,Eiraku Hozen,,1795,1854,1800,1800,1800,"Faience with glaze, the design incised and gilt (Kyoto ware)",H. 14 1/4 in. (36.2 cm),"Ex coll.: V. Everit Macy, Gift of Mrs. Everit Macy",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/48577,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -23.225.380,false,true,47195,Asian Art,Bowl,,Japan,Edo period (1615–1868),,,,Artist,,Eiraku Hozen,1795–1854,,Eiraku Hozen,,1795,1854,1830,1830,1830,"Faience(?) covered with rich green glaze, craquelé (Kyoto ware)",H. 3 1/16 in. (7.8 cm); Diam. 5 3/4 in. (14.6 cm),"Gift of Mrs. V. Everit Macy, 1923",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/47195,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.635,false,true,45354,Asian Art,Teabowl,,Japan,Edo period (1615–1868),,,,Artist,,Eiraku Hozen,1795–1854,,Eiraku Hozen,,1795,1854,ca. 1825,1815,1835,Clay decorated with crackled glaze and enamels and gold (Kyo ware),H. 2 3/4 in. (7 cm); Diam. of rim 4 7/8 in. (12.4 cm); Diam. of base 1 7/8 in. (4.8 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/45354,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.120.503,false,true,63157,Asian Art,Teabowl,,Japan,Edo period (1615–1868),,,,Artist,,Eiraku Hozen,1795–1854,,Eiraku Hozen,,1795,1854,ca. 1830,1820,1840,Clay with creamy white glaze (Eiraku pottery),H. 3 3/8 in. (8.6 cm); Diam. 4 7/8 in. (12.4 cm),"The Howard Mansfield Collection, Gift of Howard Mansfield, 1936",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/63157,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"25.60.31a, b",false,true,63087,Asian Art,Tea jar,,Japan,Edo period (1615–1868),,,,Artist,,Eiraku Hozen,1795–1854,,Eiraku Hozen,,1795,1854,ca. 1820,1810,1830,Pottery with design in relief and colored enamels; pewter cover (Kyoto ware),H. 6 in. (15.2 cm); Diam. 5 in. (12.7 cm),"Rogers Fund, 1925",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/63087,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"93.3.159a, b",false,true,47074,Asian Art,Bottle,,Japan,Edo period (1615–1868),,,,Artist,,Eiraku Hozen,1795–1854,,Eiraku Hozen,,1795,1854,1790,1790,1790,Paste decorated with polychrome and transparent enamels (Kyoto ware),H. 5 3/4 in. (14.6 cm); Diam. 4 in. (10.2 cm); Diam. of rim 1 1/4 in. (3.2 cm); Diam. of base 2 1/2 in. (6.4 cm),"Gift of Charles Stewart Smith, 1893",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/47074,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"93.3.199a, b",false,true,47421,Asian Art,Bowl,,Japan,Edo period (1615–1868),,,,Artist,,Eiraku Hozen,1795–1854,(Zenichiro),Eiraku Hozen,,1795,1854,1840,1840,1840,"White porcelain decorated with blue under the glaze, polychrome enamels (Kyoto ware)",H. 6 1/2 in. (16.5 cm),"Gift of Charles Stewart Smith, 1893",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/47421,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"23.225.252a, b",false,true,63024,Asian Art,Covered box,,Japan,Edo period (1615–1868),,,,Artist,,Eiraku Hozen,1795–1854,,Eiraku Hozen,,1795,1854,1800,1800,1800,Porcelain decorated with enamel and gold (Kyoto ware),H. 2 3/8 in. (6 cm),"Gift of Mrs. V. Everit Macy, 1923",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/63024,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"23.225.275a, b",false,true,47362,Asian Art,Censer,,Japan,Edo period (1615–1868),,,,Artist,,Eiraku Hozen,1795–1854,,Eiraku Hozen,,1795,1854,1820,1820,1820,Faience with enamels (Kyoto ware),H. 5 1/8 in. (13 cm); Diam. 4 1/4 in. (10.8 cm),"Gift of Mrs. V. Everit Macy, 1923",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/47362,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -17.118.78,false,true,62902,Asian Art,Teabowl,,Japan,Edo period (1615–1868),,,,Artist,,Ichinyu,"Japanese, died 1682",,Ichinyu,,1582,1682,ca. 1675,1665,1685,Clay covered with a black glaze dappled with red (Raku ware),H. 3 in. (7.6 cm); Diam. 4 1/2 in. (11.4 cm),"Rogers Fund, 1893",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/62902,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -17.118.79,false,true,62903,Asian Art,Teabowl,,Japan,Edo period (1615–1868),,,,Artist,,Ichinyu,"Japanese, died 1682",,Ichinyu,,1582,1682,ca. 1675,1665,1685,Clay covered with a dull black glaze (Raku ware),H. 3 1/4 in. (8.3 cm); Diam. 4 1/4 in. (10.8 cm),"Rogers Fund, 1917",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/62903,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.269,false,true,77863,Asian Art,Dish,,Japan,Edo period (1615–1868),,,,Artist,,Ogata Kenzan,"Japanese, 1663–1743",,Ogata Kenzan,,1663,1743,early 18th century,1700,1733,Glazed stoneware with enamels,H. 2 1/16 in. (5.2 cm); W. 14 7/16 in. (36.7 cm); L. 12 7/16 in. (31.6 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/77863,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2015.500.9.28a, b",false,true,667248,Asian Art,Incense container,,Japan,Edo period (1615–1868),,,,Artist,,Miyagawa Chozo,"Japanese, 1797–1860",,Miyagawa Chozo,,1797,1860,1797–1860,1797,1860,Stoneware with polychrome enamels,H. 1 13/16 in. (4.6 cm); Diam. 2 13/16 in. (7.2 cm),"Gift of Florence and Herbert Irving, 2015",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/667248,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.500.9.9,false,true,78156,Asian Art,Hanging scroll,,Japan,Momoyama (1573–1615),,,,Artist,,Unkoku Tōgan,"Japanese, 1547–1618",,Unkoku Tōgan,,1547,1618,early 17th century,1600,1618,Hanging scroll; ink on paper,Image: 39 1/2 x 14 1/8 in. (100.3 x 35.9 cm),"Gift of Florence and Herbert Irving, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/78156,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2015.300.68a, b",false,true,53237,Asian Art,Hanging scrolls,四季花鳥図|Pheasants among Trees: Flowers of the Four Seasons,Japan,Muromachi (1392–1573),,,,Artist,,Kano Shōei,"Japanese, 1519–1592",,Kano Shōei,,1519,1592,probably 1560s,1560,1569,Pair of hanging scrolls; ink and color on paper,Image (a): 37 3/8 × 18 3/8 in. (95 × 46.6 cm) Overall with mounting (a): 84 3/4 × 26 5/8 in. (215.2 × 67.6 cm) Overall with knobs (a): 84 3/4 × 28 15/16 in. (215.2 × 73.5 cm) Image (b): 37 3/8 × 18 3/8 in. (95 × 46.6 cm) Overall with mounting (b): 84 15/16 × 26 5/8 in. (215.8 × 67.7 cm) Overall with knobs (b): 84 15/16 × 28 15/16 in. (215.8 × 73.5 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/53237,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.268.92,false,true,48988,Asian Art,Hanging scroll,雪竹図|Bamboo and Rock in Snow,Japan,Edo period (1615–1868),,,,Artist,,Sakaki Hyakusen,1697–1752,,Sakaki Hyakusen,,1697,1752,spring 1750,1750,1750,Hanging scroll; ink on paper,Image: 53 1/8 x 16in. (134.9 x 40.6cm) Overall with mounting: 80 7/8 x 19 5/8 in. (205.4 x 49.8 cm) Overall with rollers: 80 7/8 x 21 7/8 in. (205.4 x 55.6 cm),"The Harry G. C. Packard Collection of Asian Art, Gift of Harry G. C. Packard, and Purchase, Fletcher, Rogers, Harris Brisbane Dick, and Louis V. Bell Funds, Joseph Pulitzer Bequest, and The Annenberg Fund Inc. Gift, 1975",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/48988,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.155,false,true,53451,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Sakaki Hyakusen,1697–1752,,Sakaki Hyakusen,,1697,1752,1744,1620,1880,Hanging scroll; ink and color on paper,47 3/4 × 19 3/4 in. (121.3 × 50.2 cm) 76 9/16 × 27 3/16 in. (194.5 × 69 cm) Overall with knobs: 76 9/16 × 27 3/8 in. (194.5 × 69.5 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/53451,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.180,false,true,53461,Asian Art,Folding fan mounted,,Japan,Edo period (1615–1868),,,,Artist,,Aoki Mokubei,1767–1833,,Aoki Mokubei,,1767,1833,1825,1700,1899,Folding fan mounted as a hanging scroll; ink and color on paper,Image: 8 1/4 × 19 7/16 in. (21 × 49.4 cm) Overall with mounting: 39 3/8 × 25 1/16 in. (100 × 63.7 cm) Overall with knobs: 39 3/8 × 27 7/16 in. (100 × 69.7 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/53461,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.95,false,true,45229,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Eiraku Hozen,1795–1854,,Eiraku Hozen,,1795,1854,after 1848,1849,1854,Hanging scroll; ink on paper,Image: 38 1/2 x 11 1/4 in. (97.8 x 28.6 cm) Overall with mounting: 68 1/2 x 11 3/4 in. (174 x 29.8 cm) Overall with knobs: 68 1/2 x 14 in. (174 x 35.6 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45229,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.524,false,true,48974,Asian Art,Hanging scroll,芥子図|Poppies,Japan,Edo period (1615–1868),,,,Artist,Attributed to,Kitagawa Sōsetsu,active 1639–50,,Kitagawa Sōsetsu,,1639,1650,mid-17th century,1634,1666,Hanging scroll; color and gold on paper,34 1/4 x 14 5/8 in. (87 x 37.1 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/48974,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2015.300.34a, b",false,true,53892,Asian Art,Albums,,Japan,Edo period (1615–1868),,,,Artist,,Tosa Mitsunori,"Japanese, 1583–1638",,Tosa Mitsunori,,1583,1638,early 17th century,1600,1700,"Two albums, thirty leaves in each; ink, red pigment, and gold on paper",Image (each leaf): 5 5/16 × 5 1/8 in. (13.5 × 13 cm) Album: 7 1/8 × 6 1/8 × 1 3/16 in. (18.1 × 15.6 × 3 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/53892,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.221,false,true,671056,Asian Art,Hanging scroll,周茂叔愛蓮図|Zhou Maoshu Admiring Lotuses,Japan,Edo period (1615–1868),,,,Artist,,Kaihō Yūsetsu,"Japanese, 1598–1677",,Kaihō Yūsetsu,,1598,1677,mid-17th century,1634,1666,Hanging scroll; ink on silk,Image: 12 3/4 × 19 5/8 in. (32.4 × 49.8 cm) Overall with mounting: 47 3/16 × 24 7/16 in. (119.8 × 62 cm) Overall with knobs: 47 3/16 × 26 1/4 in. (119.8 × 66.7 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/671056,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.78,false,true,53010,Asian Art,Hanging scroll,笛吹地蔵図|Jizō Bosatsu Playing a Flute,Japan,Edo period (1615–1868),,,,Artist,,Kano Tan'yū,"Japanese, 1602–1674",,Kano Tan'yū,,1602,1674,mid- 17th century,1634,1666,Hanging scroll; ink and color on paper,Image: 38 3/4 in. × 15 in. (98.5 × 38.1 cm) Overall with mounting: 80 1/2 × 21 7/16 in. (204.5 × 54.5 cm) Overall with knobs: 80 1/2 × 23 5/8 in. (204.5 × 60 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/53010,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.24,false,true,670915,Asian Art,Album of thirty-six paintings and thirty-six poems,,Japan,Edo period (1615–1868),,,,Artist,,Sumiyoshi Gukei,"Japanese, 1631–1705",,Sumiyoshi Gukei,,1631,1705,1674–92,1674,1692,"Album of thirty-six paintings and thirty-six poems; ink, color and gold on silk and paper",Image (each leaf): 6 7/8 × 6 5/16 in. (17.4 × 16 cm) Album: 8 3/4 × 7 11/16 × 2 5/8 in. (22.3 × 19.5 × 6.7 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/670915,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.82a–c,false,true,54774,Asian Art,Hanging scrolls,"滝見業平図|Nunobiki Waterfall, Mount Yoshino, and Tatsuta River",Japan,Edo period (1615–1868),,,,Artist,,Kano Tsunenobu,"Japanese, 1636–1713",,Kano Tsunenobu,,1636,1713,after 1709,1709,1709,Triptych of hanging scrolls; ink and color on silk,Image (a): 58 9/16 × 31 1/2 in. (148.7 × 80 cm) Overall with mounting (a): 9 ft. 8 9/16 in. × 40 9/16 in. (296 × 103 cm) Overall with knobs (a): 9 ft. 8 9/16 in. × 43 1/2 in. (296 × 110.5 cm) Image (b): 58 9/16 × 31 1/2 in. (148.7 × 80 cm) Overall with mounting (b): 9 ft. 7 15/16 in. × 40 9/16 in. (294.5 × 103 cm) Overall with knobs (b): 9 ft. 7 15/16 in. × 43 9/16 in. (294.5 × 110.7 cm) Image (c): 58 3/4 × 31 1/2 in. (149.3 × 80 cm) Overall with mounting (c): 9 ft. 8 9/16 in. × 40 9/16 in. (296 × 103 cm) Overall with knobs (c): 9 ft. 8 9/16 in. × 43 5/8 in. (296 × 110.8 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/54774,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.89,false,true,53422,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Ogata Kōrin,"Japanese, 1658–1716",,Kōrin,,1658,1716,after 1704,1650,1850,Hanging scroll; ink on paper,Image: 11 1/4 × 14 1/2 in. (28.5 × 36.8 cm) Overall with mounting: 42 1/2 × 26 9/16 in. (108 × 67.4 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/53422,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2015.300.90a, b",false,true,53421,Asian Art,Panels,,Japan,Edo period (1615–1868),,,,Artist,,Ogata Kōrin,"Japanese, 1658–1716",,Kōrin,,1658,1716,shortly after 1701,1650,1850,Pair of panels; ink and color on cryptomeria wood,Image: 54 in. × 7 7/8 in. (137.2 × 20 cm) Overall with mounting: 81 × 13 in. (205.7 × 33 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/53421,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.212,false,true,670916,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Ogawa Haritsu (Ritsuō),"Japanese, 1663–1747",,Ritsuō,,1663,1747,1741,1741,1741,Hanging scroll; ink and color on silk,Image: 15 1/4 × 21 5/16 in. (38.7 × 54.2 cm) Overall with mounting: 51 11/16 × 26 7/8 in. (131.3 × 68.3 cm) Overall with knobs: 51 11/16 × 29 1/16 in. (131.3 × 73.8 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/670916,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.500.9.49,false,true,667348,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Nishikawa Sukenobu,"Japanese, 1671–1750",,Nishikawa Sukenobu,,1671,1750,ca. 1716–1736,1716,1736,Hanging scroll; ink and color on silk,Image: 23 in. × 32 3/4 in. (58.4 × 83.2 cm) Overall with mounting: 60 1/2 × 38 1/2 in. (153.7 × 97.8 cm) Overall with knobs: 60 1/2 × 41 5/8 in. (153.7 × 105.7 cm),,,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/667348,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.154,false,true,53450,Asian Art,Hanging scroll,「竹窗雨日」図|“Window onto Bamboo on a Rainy Day”,Japan,Edo period (1615–1868),,,,Artist,,Gion Nankai,"Japanese, 1677–1751",,Gion Nankai,,1677,1751,first half of the 18th century,1700,1749,Hanging scroll; ink on paper,Image: 52 5/8 × 22 13/16 in. (133.7 × 58 cm) Overall with mounting: 84 1/16 × 29 7/16 in. (213.5 × 74.8 cm) Overall with knobs: 84 1/16 × 31 13/16 in. (213.5 × 80.8 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/53450,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.500.9.3,false,true,78145,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Hakuin Ekaku,"Japanese, 1685–1768",,Hakuin Ekaku,,1685,1768,1685–1769,1685,1769,Hanging scroll; ink on paper,Image: 46 1/4 × 21 1/4 in. (117.5 × 54 cm) Overall with mounting: 74 × 27 1/2 in. (188 × 69.9 cm) Overall with knobs: 74 × 30 in. (188 × 76.2 cm),"Gift of Florence and Herbert Irving, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/78145,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.132,false,true,670926,Asian Art,Hanging scroll,島原の節分図|Shimabara Courtesans Exorcizing Demons,Japan,Edo period (1615–1868),,,,Artist,,Miyagawa Isshō,"Japanese, 1689–1779",,Miyagawa Isshō,,1689,1779,second half of the 18th century,1750,1799,Hanging scroll; ink and color on paper,Image: 34 1/4 × 10 1/2 in. (87 × 26.6 cm) Overall with mounting: 72 5/8 × 15 13/16 in. (184.5 × 40.2 cm) Overall with knobs: 72 5/8 × 18 1/8 in. (184.5 × 46 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/670926,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.500.9.20,false,true,78060,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Miyagawa Isshō,"Japanese, 1689–1779",,Miyagawa Isshō,,1689,1779,late 1730s–early 1740s,1736,1743,"Hanging scroll; ink, color and gold on silk",Image: 35 7/16 x 14 9/16 in. (90 x 37 cm),"Gift of Florence and Herbert Irving, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/78060,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.91,false,true,77799,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Fukae Roshū,"Japanese, 1699–1757",,Fukae Roshū,,1699,1757,early 18th century,1700,1733,Fan mounted as a hanging scroll,Image: 8 13/16 × 18 1/8 in. (22.4 × 46 cm) Overall with mounting: 43 11/16 × 25 9/16 in. (111 × 65 cm) Overall with knobs: 43 11/16 × 27 5/8 in. (111 × 70.2 cm),Property of Mary Griggs Burke,,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/77799,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.156,false,true,670942,Asian Art,Hanging scroll,山水図|Landscape,Japan,Edo period (1615–1868),,,,Artist,,Yanagisawa Kien,"Japanese, 1704–1758",,Yanagisawa Kien,,1704,1758,first half of the 18th century,1700,1749,Hanging scroll; ink and color on paper,Image: 53 7/8 × 12 1/2 in. (136.8 × 31.8 cm) Overall with mounting: 82 × 18 1/4 in. (208.3 × 46.4 cm) Overall with knobs: 82 × 21 5/16 in. (208.3 × 54.2 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/670942,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.134,false,true,671034,Asian Art,Hanging scroll,文を読む遊女図|Courtesan Reading a Letter,Japan,Edo period (1615–1868),,,,Artist,,Ishikawa Toyonobu,"Japanese, 1711–1785",,Ishikawa Toyonobu,,1711,1785,mid-18th century,1734,1766,Hanging scroll; ink and color on paper,Image: 32 7/8 × 8 9/16 in. (83.5 × 21.7 cm) Overall with mounting: 67 1/2 × 12 11/16 in. (171.5 × 32.2 cm) Overall with knobs: 67 1/2 × 14 5/16 in. (171.5 × 36.4 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/671034,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.135a–c,false,true,53445,Asian Art,Hanging scrolls,"「翁」図|The Auspicious Noh Dance ""Okina""",Japan,Edo period (1615–1868),,,,Artist,,Toriyama Sekien,"Japanese, 1712–1788",,Toriyama Sekien,,1712,1788,ca. 1790–95,1790,1795,"Triptych of hanging scrolls; ink, color, and gold on paper",Image (a): 34 5/16 × 10 3/4 in. (87.2 × 27.3 cm) Overall with mounting (a): 67 5/16 × 15 in. (171 × 38.1 cm) Overall with knobs (a): 67 5/16 × 16 3/4 in. (171 × 42.5 cm) Image (b): 34 1/2 × 10 15/16 in. (87.6 × 27.8 cm) Overall with mounting (b): 67 1/16 × 15 1/16 in. (170.3 × 38.3 cm) Overall with knobs (b): 67 1/16 × 16 5/8 in. (170.3 × 42.2 cm) Image (c): 34 1/2 × 10 11/16 in. (87.6 × 27.2 cm) Overall with mounting (c): 67 5/16 × 15 in. (171 × 38.1 cm) Overall with knobs (c): 67 5/16 × 16 5/8 in. (171 × 42.3 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/53445,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.159,false,true,53453,Asian Art,Folding fan mounted as a hanging scroll,奥の細道図扇面|Scene from The Narrow Road to the Deep North (Oku no hosomichi),Japan,Edo period (1615–1868),,,,Artist,,Yosa Buson,"Japanese, 1716–1783",,Yosa Buson,,1716,1783,ca. 1780,1600,1870,Folding fan mounted as a hanging scroll; ink and color on paper,Image: 8 7/8 × 11 3/16 in. (22.6 × 28.4 cm) Overall with mounting: 40 in. × 20 15/16 in. (101.6 × 53.2 cm) Overall with knobs: 40 × 23 3/8 in. (101.6 × 59.3 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/53453,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.500.9.18,false,true,78134,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Yosa Buson,"Japanese, 1716–1783",,Yosa Buson,,1716,1783,1760s,1760,1769,Hanging scroll; ink and color on paper,Overall with mounting: 73 3/4 × 15 1/2 in. (187.3 × 39.4 cm) Image: 84 1/4 in. × 11 in. (214 × 27.9 cm) Overall with knobs: 17 5/8 in. (44.8 cm),"Gift of Florence and Herbert Irving, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/78134,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.168,false,true,671035,Asian Art,Hanging scroll,蘇鉄図|Cycad,Japan,Edo period (1615–1868),,,,Artist,,Ike Taiga,"Japanese, 1723–1776",,Ike Taiga,,1723,1776,mid-18th century,1734,1766,Hanging scroll; ink on paper,Image: 11 in. × 12 5/8 in. (28 × 32.1 cm) Overall with mounting: 41 9/16 × 18 1/16 in. (105.5 × 45.8 cm) Overall with knobs: 41 9/16 × 19 15/16 in. (105.5 × 50.7 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/671035,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.243,false,true,53454,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Ike Taiga,"Japanese, 1723–1776",,Ike Taiga,,1723,1776,1734,1600,1870,Hanging scroll; ink on paper,Image: 10 1/2 × 13 1/8 in. (26.7 × 33.3 cm) Overall with mounting: 42 15/16 × 17 7/8 in. (109 × 45.4 cm) Overall with knobs: 42 15/16 × 20 1/2 in. (109 × 52 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/53454,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.139,false,true,53446,Asian Art,Hanging scroll,立姿美人図|Woman in a Black Kimono,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,,1726,1792,1783–89,1600,1870,"Hanging scroll; ink, color, and gold on silk",Image: 33 1/2 × 11 1/4 in. (85.1 × 28.6 cm) Overall with mounting: 67 11/16 × 15 7/8 in. (172 × 40.4 cm) Overall with knobs: 67 11/16 × 17 13/16 in. (172 × 45.2 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/53446,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.500.9.4,false,true,78146,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Soga Shōhaku,"Japanese, 1730–1781",,Soga Shōhaku,,1730,1781,ca. 1770s,1770,1779,Hanging scroll; ink on paper,Image: 52 1/2 x 21 in. (133.4 x 53.3 cm),"Gift of Florence and Herbert Irving, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/78146,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2015.300.198a, b",false,true,53404,Asian Art,Pair of hanging scrolls,鮎図|Sweetfish in Summer and Autumn,Japan,Edo period (1615–1868),,,,Artist,,Maruyama Ōkyo,"Japanese, 1733–1795",,Maruyama Ōkyo,,1733,1795,1785,1600,1850,"Pair of hanging scrolls; ink, gold, and color on silk",Image (a): 40 15/16 × 14 9/16 in. (104 × 37 cm) Overall with mounting (a): 75 3/8 × 20 1/4 in. (191.5 × 51.5 cm) Overall with knobs (a): 75 3/8 × 22 5/8 in. (191.5 × 57.4 cm) Image (b): 40 15/16 × 14 1/2 in. (104 × 36.8 cm) Overall with mounting (b): 75 3/16 × 20 1/4 in. (191 × 51.5 cm) Overall with knobs (b): 75 3/16 × 22 9/16 in. (191 × 57.3 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/53404,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.217,false,true,73359,Asian Art,Hanging scroll,羅漢図|Ten Rakan Examining a Painting of White-Robed Kannon,Japan,Edo period (1615–1868),,,,Artist,,Katō Nobukiyo,"Japanese, 1734–1810",,Katō Nobukiyo,,1734,1810,1792,1792,1792,Hanging scroll; ink and color on paper,Image: 55 1/4 × 22 3/4 in. (140.3 × 57.8 cm) Overall with mounting: 91 1/8 × 32 5/8 in. (231.5 × 82.9 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/73359,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.176,false,true,53458,Asian Art,Folding fan mounted,「残雨半村」図|“Lingering Rain over Half the Village”,Japan,Edo period (1615–1868),,,,Artist,,Uragami Gyokudō,"Japanese, 1745–1820",,Uragami Gyokudō,,1745,1820,ca. 1815–20,1700,1899,Folding fan mounted as a hanging scroll; ink on paper,Image: 8 9/16 × 18 7/8 in. (21.7 × 48 cm) Overall with mounting: 41 3/4 × 23 1/2 in. (106 × 59.7 cm) Overall with knobs: 41 3/4 × 25 11/16 in. (106 × 65.2 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/53458,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.177,false,true,53460,Asian Art,Hanging scroll,"「野橋抱琴図」|“On an Earthen Bridge, Carrying a Zither” (Yakyō hōkin zu)",Japan,Edo period (1615–1868),,,,Artist,,Uragami Gyokudō,"Japanese, 1745–1820",,Uragami Gyokudō,,1745,1820,1814,1600,1880,Hanging scroll; ink on paper,Image: 50 1/4 × 21 5/16 in. (127.7 × 54.2 cm) Overall with mounting: 73 1/4 × 27 7/16 in. (186 × 69.7 cm) Overall with knobs: 73 1/4 × 32 5/16 in. (186 × 82 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/53460,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.201,false,true,64872,Asian Art,Handscroll,華洛四季遊楽図巻|Scenes of the Four Seasons in Kyoto,Japan,Edo period (1615–1868),,,,Artist,,Genki (Komai Ki),"Japanese, 1747–1797",,Genki,,1747,1797,1778,1778,1778,Handscroll; ink and color on silk,Image: 12 5/16 in. × 16 ft. 7 15/16 in. (31.3 × 507.8 cm) Overall with mounting: 12 13/16 in. × 16 ft. 7 15/16 in. (32.5 × 507.8 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/64872,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2015.300.202a, b",false,true,53405,Asian Art,Pair of hanging scrolls,燕姞・楊貴妃図|Yanji with Orchids and Yang Guifei with Peonies,Japan,Edo period (1615–1868),,,,Artist,,Genki (Komai Ki),"Japanese, 1747–1797",,Genki,,1747,1797,1785,1600,1800,Pair of hanging scrolls; ink and color on silk,Image (a): 43 3/16 × 21 15/16 in. (109.7 × 55.8 cm) Overall with mounting (a): 78 7/8 × 27 1/16 in. (200.3 × 68.8 cm) Overall with knobs (a): 78 7/8 × 29 5/16 in. (200.3 × 74.4 cm) Image (b): 43 3/16 × 21 15/16 in. (109.7 × 55.7 cm) Overall with mounting (b): 78 7/8 × 27 1/8 in. (200.3 × 68.9 cm) Overall with knobs (b): 78 7/8 × 29 5/16 in. (200.3 × 74.5 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/53405,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.173,false,true,670940,Asian Art,Handscroll section,,Japan,Edo period (1615–1868),,,,Artist,,Noro Kaiseki,"Japanese, 1747–1828",,Noro Kaiseki,,1747,1828,1826,1826,1826,Handscroll section mounted as a hanging scroll; ink and color on silk,Image: 13 7/8 × 36 7/16 in. (35.2 × 92.6 cm) Overall with mounting: 48 13/16 × 42 1/4 in. (124 × 107.3 cm) Overall with knobs: 48 13/16 × 46 5/8 in. (124 × 118.5 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/670940,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2015.300.175a, b",false,true,670892,Asian Art,Albums,,Japan,Edo period (1615–1868),,,,Artist,,Totoki Baigai,"Japanese, 1749–1804",,Totoki Baigai,,1749,1804,1800,1800,1800,"Two albums, each with ten leaves; ink and color on paper",Image (each leaf): 7 3/8 × 7 5/8 in. (18.8 × 19.4 cm) Each album: 9 5/16 × 8 11/16 × 15/16 in. (23.6 × 22 × 2.4 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/670892,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.191,false,true,53465,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Kameda Bōsai,"Japanese, 1752–1826",,Kameda Bōsai,,1752,1826,ca. 1817,1600,1899,Hanging scroll; ink and color on silk,Image: 41 7/8 × 19 1/8 in. (106.4 × 48.6 cm) Overall with mounting: 71 5/8 × 23 3/4 in. (182 × 60.4 cm) Overall with knobs: 71 5/8 × 26 in. (182 × 66 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/53465,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.500.9.16,false,true,78143,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Chōbunsai Eishi,"Japanese, 1756–1829",,Chōbunsai Eishi,,1756,1829,1756–1815,1756,1815,Hanging scroll; ink and color on silk,Image: 31 5/8 x 13 1/16 in. (80.4 x 33.2 cm),"Gift of Florence and Herbert Irving, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/78143,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.94,false,true,670923,Asian Art,Handscroll,三十六歌仙図|The Thirty-Six Poetic Immortals (Sanjūrokkasen),Japan,Edo period (1615–1868),,,,Artist,,Sakai Hōitsu,"Japanese, 1761–1828",,Sakai Hōitsu,,1761,1828,1824,1824,1824,Handscroll; ink and color on paper,Image: 11 5/8 in. × 27 ft. 1 13/16 in. (29.5 × 827.6 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/670923,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.500.9.13,false,true,78135,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Sakai Hōitsu,"Japanese, 1761–1828",,Sakai Hōitsu,,1761,1828,1761–1828,1761,1828,Hanging scroll; ink and color on silk,Image: 46 3/4 x 21 1/2 in. (118.7 x 54.6 cm),"Gift of Florence and Herbert Irving, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/78135,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.500.9.15,false,true,78139,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Sakai Hōitsu,"Japanese, 1761–1828",,Sakai Hōitsu,,1761,1828,1761–1828,1761,1828,"Hanging scroll; ink, color and gold on silk",Image: 72 1/4 x 18 5/16 in. (183.5 x 46.5 cm),"Gift of Florence and Herbert Irving, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/78139,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.149,false,true,671045,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Toyohiro,"Japanese, 1763–1828",,Utagawa Toyohiro,,1763,1828,late 18th–early 19th century,1763,1828,Hanging scroll; ink and color on silk,Image: 34 5/8 in. × 11 in. (88 × 28 cm) Overall with mounting: 66 3/4 × 15 7/8 in. (169.5 × 40.3 cm) Overall with knobs: 66 3/4 × 18 1/16 in. (169.5 × 45.9 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/671045,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.192,false,true,53466,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Tani Bunchō,"Japanese, 1763–1840",,Tani Bunchō,,1763,1840,1828,1600,1899,Hanging scroll; ink on silk,Image: 49 13/16 × 23 1/4 in. (126.5 × 59.1 cm) Overall with mounting: 88 3/16 × 29 1/8 in. (224 × 74 cm) Overall with knobs: 88 3/16 × 31 13/16 in. (224 × 80.8 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/53466,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.193,false,true,670897,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Tani Bunchō,"Japanese, 1763–1840",,Tani Bunchō,,1763,1840,1828,1828,1828,Hanging scroll; ink and color on silk,Image: 52 3/8 × 27 13/16 in. (133 × 70.7 cm) Overall with mounting: 81 1/8 × 32 15/16 in. (206 × 83.7 cm) Overall with knobs: 81 1/8 × 35 7/8 in. (206 × 91.2 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/670897,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.147,false,true,671044,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Toyokuni I,"Japanese, 1769–1825",,Utagawa Toyokuni I,,1769,1825,late 18th–early 19th century,1769,1825,Hanging scroll; ink and color on paper,Image: 23 9/16 × 10 3/8 in. (59.8 × 26.4 cm) Overall with mounting: 55 11/16 × 14 13/16 in. (141.5 × 37.7 cm) Overall with knobs: 55 11/16 × 17 1/8 in. (141.5 × 43.5 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/671044,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.71,false,true,45792,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shuntei,"Japanese, 1770–1820",,Katsukawa Shuntei,,1770,1820,dated 1795,1795,1795,Hanging scroll; ink and color on silk,35 5/8 x 10 3/4 in. (90.5 x 27.3 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45792,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.140,false,true,671006,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shuntei,"Japanese, 1770–1820",,Katsukawa Shuntei,,1770,1820,late 18th–early 19th century,1770,1820,"Hanging scroll; ink, color and gold on paper",Image: 50 9/16 × 23 7/16 in. (128.4 × 59.6 cm) Overall with mounting: 82 7/8 × 28 1/16 in. (210.5 × 71.3 cm) Overall with knobs: 82 7/8 × 30 5/8 in. (210.5 × 77.8 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/671006,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.152,false,true,670924,Asian Art,Hanging scroll,雨宿り図|Taking Shelter from the Rain,Japan,Edo period (1615–1868),,,,Artist,,Teisai Hokuba,"Japanese, 1771–1844",,Teisai Hokuba,,1771,1844,early to mid-19th century,1800,1844,Hanging scroll; ink and color on silk,Image: 15 1/2 × 27 5/16 in. (39.4 × 69.3 cm) Overall with mounting: 51 15/16 × 32 5/16 in. (132 × 82 cm) Overall with knobs: 51 15/16 × 34 3/4 in. (132 × 88.3 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/670924,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.500.9.12,false,true,667347,Asian Art,Hanging scroll,五美人図|Five Beauties,Japan,Edo period (1615–1868),,,,Artist,,Teisai Hokuba,"Japanese, 1771–1844",,Teisai Hokuba,,1771,1844,1840,1840,1840,Hanging scroll; ink and color on silk,Image: 15 3/8 × 20 3/4 in. (39.1 × 52.7 cm) Overall with mounting: 47 3/4 × 25 3/4 in. (121.3 × 65.4 cm) Overall with knobs: 47 3/4 × 28 in. (121.3 × 71.1 cm),"Gift of Florence and Herbert Irving, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/667347,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.185,false,true,670900,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Nakabayashi Chikutō,"Japanese, 1776–1853",,Nakabayashi Chikutō,,1776,1853,1840,1840,1840,Hanging scroll; ink and color on paper,Image: 42 13/16 × 17 3/8 in. (108.8 × 44.1 cm) Overall with mounting: 71 13/16 × 18 5/16 in. (182.4 × 46.5 cm) Overall with knobs: 71 13/16 × 22 5/8 in. (182.4 × 57.5 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/670900,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.181,false,true,53462,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Tanomura Chikuden,"Japanese, 1777–1835",,Tanomura Chikuden,,1777,1835,late 18th–early 19th century,1777,1835,Hanging scroll; ink and color on paper,Image: 52 1/8 x 16 5/8 in. (132.4 x 42.2 cm) Overall with mounting: 85 13/16 × 23 3/8 in. (218 × 59.3 cm) Overall with knobs: 85 13/16 × 25 7/8 in. (218 × 65.8 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/53462,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.186,false,true,670943,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Nukina Kaioku,"Japanese, 1778–1863",,Nukina Kaioku,,1778,1863,1833,1833,1833,Hanging scroll; ink and color on silk,Image: 11 7/8 in. × 7 in. (30.2 × 17.8 cm) Overall with mounting: 39 7/8 × 12 5/8 in. (101.3 × 32 cm) Overall with knobs: 39 7/8 × 14 7/8 in. (101.3 × 37.8 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/670943,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.187,false,true,670947,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Nukina Kaioku,"Japanese, 1778–1863",,Nukina Kaioku,,1778,1863,1844,1844,1844,Hanging scroll; ink and color on paper,Image: 53 in. × 20 5/8 in. (134.6 × 52.4 cm) Overall with mounting: 79 1/8 × 27 1/16 in. (201 × 68.8 cm) Overall with knobs: 79 1/8 × 29 5/16 in. (201 × 74.4 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/670947,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.84,false,true,670939,Asian Art,Handscroll,,Japan,Edo period (1615–1868),,,,Artist,,Nonoyama Kōzan,"Japanese, 1780–1847",,Nonoyama Kōzan,,1780,1847,1822,1822,1822,"Handscroll; ink, color and gold on paper",Image: 13 5/8 × 54 15/16 in. (34.6 × 139.5 cm) Overall with mounting: 14 15/16 × 93 1/2 in. (38 × 237.5 cm) Overall with knobs: 16 5/16 × 93 1/2 in. (41.5 × 237.5 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/670939,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.178,false,true,73358,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Okada Hankō,"Japanese, 1782–1846",,Okada Hankō,,1782,1846,1843,1843,1843,Hanging scroll; ink and color on paper,Image: 50 3/8 × 23 1/8 in. (128 × 58.8 cm) Overall with mounting: 87 3/16 × 29 5/16 in. (221.5 × 74.5 cm) Overall with knobs: 87 3/16 × 31 13/16 in. (221.5 × 80.8 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/73358,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.179,false,true,670921,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Okada Hankō,"Japanese, 1782–1846",,Okada Hankō,,1782,1846,1833,1833,1833,Hanging scroll; ink and color on paper,Image: 11 13/16 × 25 1/16 in. (30 × 63.7 cm) Overall with mounting: 50 3/16 × 31 3/4 in. (127.5 × 80.6 cm) Overall with knobs: 50 3/16 × 34 3/16 in. (127.5 × 86.8 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/670921,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.760,false,true,77196,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Yamamoto Baiitsu,"Japanese, 1783–1856",,Yamamoto Baiitsu,,1783,1783,dated 10th month of 1852,1852,1852,Hanging scroll; ink and color on silk,Image: 50 × 21 13/16 in. (127 × 55.4 cm) Overall with mounting: 84 × 27 9/16 in. (213.4 × 70 cm) Overall with knobs: 84 × 30 in. (213.4 × 76.2 cm),"Fishbein-Bender Collection, Gift of T. Richard Fishbein and Estelle P. Bender, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/77196,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.188,false,true,670891,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Yamamoto Baiitsu,"Japanese, 1783–1856",,Yamamoto Baiitsu,,1783,1783,1843,1843,1843,Hanging scroll; ink and color on silk,Image: 45 9/16 × 16 1/8 in. (115.8 × 40.9 cm) Overall with mounting: 78 1/16 × 22 5/8 in. (198.2 × 57.5 cm) Overall with knobs: 78 1/16 × 25 1/16 in. (198.2 × 63.7 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/670891,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.189,false,true,670893,Asian Art,Folding fan,,Japan,Edo period (1615–1868),,,,Artist,,Yamamoto Baiitsu,"Japanese, 1783–1856",,Yamamoto Baiitsu,,1783,1783,1832,1832,1832,Folding fan; ink and color on paper,Image: 6 11/16 × 18 11/16 in. (17 × 47.5 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/670893,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.500.9.47,false,true,78149,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Yamamoto Baiitsu,"Japanese, 1783–1856",,Yamamoto Baiitsu,,1783,1783,dated 1851,1851,1851,Hanging scroll; ink on silk,Image: 45 1/4 x 15 3/4 in. (114.9 x 40 cm),"Gift of Florence and Herbert Irving, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/78149,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.190a–d,false,true,53463,Asian Art,Four hanging scrolls,四季山水図|Landscapes of the Four Seasons,Japan,Edo period (1615–1868),,,,Artist,,Yamamoto Baiitsu,"Japanese, 1783–1856",,Yamamoto Baiitsu,,1783,1783,1848,1700,1899,Set of four hanging scrolls; ink and color on silk,Image (a): 40 3/8 × 13 7/8 in. (102.6 × 35.2 cm) Overall with mounting (a): 75 1/16 × 19 3/4 in. (190.7 × 50.1 cm) Overall with knobs (a): 75 1/16 × 21 5/8 in. (190.7 × 55 cm) Image (b): 40 3/8 × 13 7/8 in. (102.6 × 35.3 cm) Overall with mounting (b): 75 1/4 × 19 11/16 in. (191.2 × 50 cm) Overall with knobs (b): 75 1/4 × 21 11/16 in. (191.2 × 55.1 cm) Image (c): 40 7/16 × 13 7/8 in. (102.7 × 35.3 cm) Overall with mounting (c): 75 1/16 × 19 3/4 in. (190.6 × 50.1 cm) Overall with knobs (c): 75 1/16 × 21 3/4 in. (190.6 × 55.3 cm) Image (d): 40 1/2 × 13 15/16 in. (102.8 × 35.4 cm) Overall with mounting (d): 75 3/8 × 19 11/16 in. (191.5 × 50 cm) Overall with knobs (d): 75 3/8 × 21 11/16 in. (191.5 × 55.1 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/53463,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.194,false,true,670882,Asian Art,Album,,Japan,Edo period (1615–1868),,,,Artist,,Takaku Aigai,"Japanese, 1796–1843",,Takaku Aigai,,1796,1843,1833,1833,1833,Album with twelve leaves; ink and color on paper,Album: 13 × 7 1/2 × 1 in. (33 × 19 × 2.5 cm) Image (each leaf): 11 7/16 × 13 1/8 in. (29 × 33.3 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/670882,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.95,false,true,53424,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Suzuki Kiitsu,"Japanese, 1796–1858",,Suzuki Kiitsu,,1796,1858,ca. 1805,1720,1920,Hanging scroll; ink and color on silk,Image: 39 7/8 × 12 15/16 in. (101.3 × 32.8 cm) Overall with mounting: 77 3/16 × 18 3/8 in. (196 × 46.6 cm) Overall with knobs: 77 3/16 × 20 9/16 in. (196 × 52.2 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/53424,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.98,false,true,670946,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Suzuki Kiitsu,"Japanese, 1796–1858",,Suzuki Kiitsu,,1796,1858,1857,1857,1857,Hanging scroll; ink and color on paper,Image: 39 1/16 × 11 5/16 in. (99.2 × 28.7 cm) Overall with mounting: 74 5/16 × 12 1/2 in. (188.8 × 31.8 cm) Overall with knobs: 74 5/16 × 14 5/8 in. (188.8 × 37.2 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/670946,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.100a–f,false,true,53427,Asian Art,Handscrolls,,Japan,Edo period (1615–1868),,,,Artist,,Sakai Ōho,"Japanese, 1808–1841",,Sakai Ōho,,1808,1841,ca. 1839,1800,1920,"Six handscrolls; ink, color, and gold on silk",Overall (a): 3 9/16 x 46 7/16 in. (9 x 118 cm) Overall (b): 3 9/16 x 46 15/16 in. (9 x 119.2 cm) Overall (c): 3 1/2 x 46 7/16 in. (8.9 x 118 cm) Overall (d): 3 9/16 x 48 13/16 in. (9 x 124 cm) Overall (e): 3 5/8 x 46 1/2 in. (9.2 x 118.1 cm) Overall (f): 3 1/2 x 46 7/8 in. (8.9 x 119.1 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/53427,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.196,false,true,671048,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Hine Taizan,"Japanese, 1813–1869",,Hine Taizan,,1813,1869,1859,1859,1859,Hanging scroll; ink and color on silk,Image: 53 15/16 × 20 3/16 in. (137 × 51.3 cm) Overall with mounting: 88 × 26 11/16 in. (223.5 × 67.8 cm) Overall with knobs: 88 × 30 1/2 in. (223.5 × 77.5 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/671048,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.126,false,true,671003,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Ryūkadō,"Japanese, active 1740s",,Ryūkadō,,1740,1749,1740s,1740,1749,Hanging scroll; ink and color on paper,Image: 28 11/16 in. × 13 in. (72.8 × 33 cm) Overall with mounting: 60 13/16 × 14 3/4 in. (154.5 × 37.4 cm) Overall with knobs: 60 13/16 × 17 1/16 in. (154.5 × 43.3 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/671003,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.182,false,true,671012,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Takahashi Sōhei,"Japanese, 1804?–?1835",,Takahashi Sōhei,,1804,1835,1824,1824,1824,Hanging scroll; ink and color on paper,Image: 53 15/16 × 19 5/16 in. (137 × 49 cm) Overall with mounting: 77 1/16 × 21 5/8 in. (195.8 × 55 cm) Overall with knobs: 77 1/16 × 23 11/16 in. (195.8 × 60.2 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/671012,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.183,false,true,671017,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Takahashi Sōhei,"Japanese, 1804?–?1835",,Takahashi Sōhei,,1804,1835,1831,1831,1831,Hanging scroll; ink and color on paper,Image: 39 1/2 × 11 13/16 in. (100.3 × 30 cm) Overall with mounting: 63 3/4 × 16 7/16 in. (162 × 41.7 cm) Overall with knobs: 63 3/4 × 18 11/16 in. (162 × 47.5 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/671017,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.184,false,true,671011,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Takahashi Sōhei,"Japanese, 1804?–?1835",,Takahashi Sōhei,,1804,1835,1832,1832,1832,Hanging scroll; ink and color on paper,Image: 40 1/16 × 16 1/2 in. (101.8 × 41.9 cm) Overall with mounting: 69 11/16 × 22 1/16 in. (177 × 56 cm) Overall with knobs: 69 11/16 × 24 7/16 in. (177 × 62 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/671011,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.169,false,true,53457,Asian Art,Hanging scroll,牡丹に竹図|Peony and Bamboo by a Rock,Japan,Edo period (1615–1868),,,,Artist,,Tokuyama Gyokuran,"Japanse, ca. 1728–1784",,Tokuyama Gyokuran,,1728,1784,ca. 1768,1600,1870,Hanging scroll; ink and color on paper,Image: 36 5/8 × 16 7/16 in. (93 × 41.7 cm) Overall with mounting: 68 7/8 × 22 3/16 in. (175 × 56.4 cm) Overall with knobs: 68 7/8 × 24 7/16 in. (175 × 62 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/53457,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.75,false,true,671042,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Ono Otsū,"Japanese, 1568–ca. 1631",,Ono Otsū,,1568,1631,1624,1624,1624,Hanging scroll; ink on paper,Image: 24 5/8 × 16 1/16 in. (62.6 × 40.8 cm) Overall with mounting: 68 1/2 × 21 9/16 in. (174 × 54.7 cm) Overall with knobs: 68 1/2 × 23 11/16 in. (174 × 60.2 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/671042,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.136,false,true,670933,Asian Art,Hanging scroll,花魁と禿の初詣図|Courtesan and Two Attendants on New Year's Day,Japan,Edo period (1615–1868),,,,Artist,,Isoda Koryūsai,"Japanese, 1735–ca. 1790",,Isoda Koryūsai,,1735,1790,ca. 1780s,1780,1789,"Hanging scroll; ink, color and gold on paper",Image: 33 11/16 × 13 11/16 in. (85.6 × 34.7 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/670933,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.137,false,true,670932,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Isoda Koryūsai,"Japanese, 1735–ca. 1790",,Isoda Koryūsai,,1735,1790,1764–88,1764,1788,Hanging scroll; ink and color on silk,Image: 32 in. × 13 1/2 in. (81.3 × 34.3 cm) Overall with mounting: 66 15/16 × 18 13/16 in. (170 × 47.8 cm) Overall with knobs: 66 15/16 × 20 11/16 in. (170 × 52.6 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/670932,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.120,false,true,670997,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Tōsendō Rifū,"Japanese, active ca. 1730",,Tōsendō Rifū,,1720,1740,ca. 1730,1720,1740,Hanging scroll; ink and color on silk,Image: 26 7/8 × 12 1/8 in. (68.3 × 30.8 cm) Overall with mounting: 60 1/4 × 16 15/16 in. (153 × 43 cm) Overall with knobs: 60 1/4 × 18 7/8 in. (153 × 48 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/670997,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.118,false,true,53443,Asian Art,Hanging scroll mounted as panel,立姿遊女図|Standing Courtesan,Japan,Edo period (1615–1868),,,,Artist,,Kaigetsudō Ando,"Japanese, ca. 1671–1743",,Kaigetsudō Ando,,1671,1743,early 18th century,1700,1733,"Hanging scroll, mounted as panel; ink and color on paper",Image: 40 15/16 × 16 5/8 in. (104 × 42.2 cm) Overall with mounting: 44 7/8 × 20 1/2 in. (114 × 52 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/53443,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.125,false,true,670954,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Kakondō,"Japanese, active 1716–36",,Kakondō,,1716,1736,early 18th century,1716,1736,"Hanging scroll; ink, color and gold on paper",Image: 31 1/8 × 15 13/16 in. (79 × 40.2 cm) Overall with mounting: 51 3/16 × 19 5/16 in. (130 × 49 cm) Overall with knobs: 51 3/16 × 21 1/4 in. (130 × 54 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/670954,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.127,false,true,671021,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Takizawa Shigenobu,"Japanese, active 1720–40",,Takizawa Shigenobu,,1700,1799,ca. 1730,1720,1740,Hanging scroll; ink and color on silk,Image: 39 15/16 × 18 3/8 in. (101.5 × 46.6 cm) Overall with mounting: 71 7/16 × 22 11/16 in. (181.5 × 57.6 cm) Overall with knobs: 71 7/16 × 24 7/8 in. (181.5 × 63.2 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/671021,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.133,false,true,671020,Asian Art,Hanging scroll,見立松風図|The Brine Maiden Matsukaze,Japan,Edo period (1615–1868),,,,Artist,,Nishimura Shigenobu,"Japanese, active 1729–39",,Nishimura Shigenobu,,1729,1739,early 18th century,1700,1733,Hanging scroll; ink and color on paper,Image: 31 9/16 × 11 3/16 in. (80.2 × 28.4 cm) Overall with mounting: 63 3/8 × 15 5/8 in. (161 × 39.7 cm) Overall with knobs: 63 3/8 × 17 15/16 in. (161 × 45.5 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/671020,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.138,false,true,671005,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Miyagawa (Katsukawa) Shunsui,"Japanese, active 1744–64",,Miyagawa (Katsukawa) Shunsui,,1744,1764,mid-18th century,1734,1766,"Hanging scroll; ink, color and gold on silk",Image: 30 7/8 × 13 7/8 in. (78.5 × 35.2 cm) Overall with mounting: 67 5/16 × 15 1/16 in. (171 × 38.3 cm) Overall with knobs: 67 5/16 × 17 1/4 in. (171 × 43.8 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/671005,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.119,false,true,53444,Asian Art,Hanging scroll,文を書く遊女図|Courtesan Writing a Letter,Japan,Edo period (1615–1868),,,,Artist,,Kaigetsudō Doshin,"Japanese, active 1711–1736",,Kaigetsudō Doshin,,1711,1736,ca. 1715,1705,1725,Hanging scroll; ink and color on paper,Image: 19 1/2 × 23 5/8 in. (49.5 × 60 cm) Overall with mounting: 53 × 28 13/16 in. (134.6 × 73.2 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/53444,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.131,false,true,670931,Asian Art,Hanging scroll,朝比奈義秀図|The Warrior Asahina Yoshihide Lifting a Puppet of a Courtesan on a Go Board,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyoshige,"Japanese, active ca. 1716–1759",,Torii Kiyoshige,,1716,1759,mid-18th century,1734,1766,"Hanging scroll; ink, color and gold on paper",Image: 30 in. × 7 15/16 in. (76.2 × 20.1 cm) Overall with mounting: 65 3/16 × 12 11/16 in. (165.5 × 32.2 cm) Overall with knobs: 65 3/16 × 14 7/8 in. (165.5 × 37.8 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/670931,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.500.9.7,false,true,78155,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Kita Genki,"Japanese, active late 17th century",,Kita Genki,,1667,1699,dated 1674,1674,1674,Hanging scroll; ink and color on silk,Image: 43 1/8 × 16 1/2 in. (109.5 × 41.9 cm) Overall with mounting: 79 5/8 × 23 3/8 in. (202.2 × 59.4 cm),"Gift of Florence and Herbert Irving, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/78155,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.43,false,true,671053,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Hishikawa Waō,"Japanese, active early 18th century",,Hishikawa Waō,,1700,1733,early 18th century,1700,1733,"Hanging scroll; ink, color and gold on silk",Image: 14 5/8 × 17 15/16 in. (37.1 × 45.6 cm) Overall with mounting: 49 1/2 × 22 7/8 in. (125.8 × 58.1 cm) Overall with knobs: 49 1/2 × 24 15/16 in. (125.8 × 63.3 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/671053,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.123,false,true,670907,Asian Art,Hanging scroll,立姿遊女図|Standing Courtesan,Japan,Edo period (1615–1868),,,,Artist,,Baiōken Eishun,"Japanese, active early 18th century",,Baiōken Eishun,,1710,1730,probably 1720s,1720,1729,Hanging scroll; ink and color on silk,Image: 39 7/16 × 16 1/8 in. (100.2 × 41 cm) Overall with mounting: 77 9/16 × 22 3/8 in. (197 × 56.8 cm) Overall with knobs: 77 9/16 × 24 5/16 in. (197 × 61.7 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/670907,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.143,false,true,671050,Asian Art,Hanging scroll,見立寒山拾得図|Courtesans Parodying Kanzan and Jittoku,Japan,Edo period (1615–1868),,,,Artist,,Kinpūsha Toyomaro,"Japanese, active early 19th century",,Kinpūsha Toyomaro,,1800,1833,late 18th–early 19th century,1767,1833,"Hanging scroll; ink, color and gold on paper",Image: 48 1/4 × 22 1/4 in. (122.5 × 56.5 cm) Overall with mounting: 85 1/4 × 27 13/16 in. (216.5 × 70.7 cm) Overall with knobs: 85 1/4 × 30 5/16 in. (216.5 × 77 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/671050,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.122,false,true,671031,Asian Art,Hanging scroll,蚊帳美人図|Woman Reading under a Mosquito Net,Japan,Edo period (1615–1868),,,,Artist,,Fuhiken Tokikaze,active first half of the 18th century,,Fuhiken Tokikaze,,1700,1749,ca. 1720,1710,1730,Hanging scroll; ink and color on silk,Image: 27 1/2 × 14 5/8 in. (69.8 × 37.1 cm) Overall with mounting: 60 3/16 × 22 1/8 in. (152.8 × 56.2 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/671031,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2015.300.150a, b",false,true,670920,Asian Art,Diptych of hanging scrolls,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",,1797,1858,1848–54,1848,1854,Diptych of hanging scrolls; ink and color on silk,Image (a): 36 7/16 in. × 13 in. (92.5 × 33 cm) Overall with mounting (a): 67 11/16 × 17 11/16 in. (172 × 44.9 cm) Overall with knobs (a): 67 11/16 × 19 1/2 in. (172 × 49.6 cm) Image (b): 36 7/16 × 12 15/16 in. (92.5 × 32.9 cm) Overall with mounting (b): 67 5/16 × 17 11/16 in. (171 × 44.9 cm) Overall with knobs: 67 5/16 × 19 5/8 in. (171 × 49.8 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/670920,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2015.300.172a, b",false,true,670883,Asian Art,Hanging scrolls,,Japan,Edo period (1615–1868),,,,Artist,,Aiseki,"Japanese, active first half of the 19th century",,Aiseki,,1800,1849,first half of the 19th century,1800,1849,Pair of hanging scrolls; ink and color on paper,Image (a): 51 3/4 × 17 15/16 in. (131.5 × 45.5 cm) Overall with mounting (a): 81 1/2 × 22 15/16 in. (207 × 58.2 cm) Overall with knobs (a): 81 1/2 × 25 1/16 in. (207 × 63.6 cm) Image (b): 51 13/16 × 17 7/8 in. (131.6 × 45.4 cm) Overall with mounting (b): 81 1/2 × 22 15/16 in. (207 × 58.2 cm) Overall with knobs (b): 81 1/2 × 25 1/16 in. (207 × 63.7 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/670883,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.113,false,true,670973,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Furuyama Moroshige,"Japanese, active second half of the 17th century",,Furuyama Moroshige,,1650,1699,second half of the 17th century,1650,1699,Hanging scroll; ink and color on silk,Image: 11 7/16 × 18 3/16 in. (29 × 46.2 cm) Overall with mounting: 38 × 20 1/4 in. (96.5 × 51.5 cm) Overall with knobs: 38 × 22 5/8 in. (96.5 × 57.5 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/670973,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.268.8,false,true,45618,Asian Art,Hanging scroll,"月光菩薩像 「金胎仏画帖」断簡|Gakkō Bosatsu, from “Album of Buddhist Deities from the Diamond World and Womb World Mandalas” (“Kontai butsugajō”)",Japan,Heian period (794–1185),,,,Artist,Attributed to,Takuma Tametō,"Japanese, active ca. 1132–74",,Takuma Tametō,,1132,1174,mid-12th century,1134,1166,"Page from a book mounted as a hanging scroll; ink, color, and gold on paper",10 in. × 5 3/8 in. (25.4 × 13.7 cm) Overall with mounting: 49 1/4 × 15 1/2 in. (125.1 × 39.4 cm) Overall with knobs: 49 1/4 × 17 3/4 in. (125.1 × 45.1 cm),"The Harry G. C. Packard Collection of Asian Art, Gift of Harry G. C. Packard, and Purchase, Fletcher, Rogers, Harris Brisbane Dick, and Louis V. Bell Funds, Joseph Pulitzer Bequest, and The Annenberg Fund Inc. Gift, 1975",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45618,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.4,false,true,53164,Asian Art,Hanging scroll,"大精進菩薩  「金胎仏画帖」断簡|Daishōjin Bosatsu, from “Album of Buddhist Deities from the Diamond World and Womb World Mandalas” (“Kontai butsugajō”)",Japan,Heian period (794–1185),,,,Artist,Attributed to,Takuma Tametō,"Japanese, active ca. 1132–74",,Takuma Tametō,,1132,1174,12th century,1100,1185,"Hanging scroll; ink, color, and gold on paper",Image: 9 3/4 in. × 5 in. (24.7 × 12.7 cm) Overall with mounting: 46 1/16 × 14 3/16 in. (117 × 36 cm) Overall with knobs: 46 1/16 × 15 7/8 in. (117 × 40.3 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/53164,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.31,false,true,670972,Asian Art,Album leaf remounted as a hanging scroll,"源氏物語図色紙 「柏木」|Scene from “The Oak Tree” (“Kashiwagi”), from The Tale of Genji (Genji monogatari)",Japan,Momoyama period (1573–1615),,,,Artist,,Tosa Mitsuyoshi,"Japanese, 1539–1613",,TOSA MITSUYOSHI,,1539,1613,late 16th–early 17th century,1567,1613,"Album leaf remounted as a hanging scroll; ink, color and gold on paper",Image: 9 3/4 × 8 3/16 in. (24.7 × 20.8 cm) Overall with mounting: 54 5/16 × 15 1/2 in. (138 × 39.4 cm) Overall with knobs: 54 5/16 × 17 1/4 in. (138 × 43.8 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/670972,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2015.300.33a, b",false,true,53245,Asian Art,Album leaves mounted as a pair of hanging scrolls,"玉鬘図 (『源氏物語』画帖の内)|“The Jeweled Chaplet” (“Tamakazura”), from The Tale of Genji (Genji monogatari)",Japan,Momoyama period (1573–1615),,,,Artist,Circle of,Tosa Mitsuyoshi,"Japanese, 1539–1613",,TOSA MITSUYOSHI,,1539,1613,early 17th century,1600,1615,"Album leaves mounted as a pair of hanging scrolls; ink, gold, silver, and color on paper",Image (a): 9 5/8 × 8 3/8 in. (24.4 × 21.3 cm) Overall with mounting (a): 53 1/4 × 15 11/16 in. (135.3 × 39.8 cm) Overall with knobs (a): 53 1/4 × 17 3/8 in. (135.3 × 44.2 cm) Image (b): 9 7/16 × 8 3/8 in. (24 × 21.2 cm) Overall with mounting (b): 53 1/8 × 15 5/8 in. (135 × 39.7 cm) Overall with knobs (b): 53 1/8 × 17 3/8 in. (135 × 44.1 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/53245,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.72,false,true,670979,Asian Art,Hanging scroll,,Japan,Momoyama period (1573–1615),,,,Artist,,Konoe Nobutada,"Japanese, 1565–1614",,Konoe Nobutada,,1565,1614,late 16th century,1573,1599,Hanging scroll; ink on paper,Image: 38 7/16 × 16 13/16 in. (97.7 × 42.7 cm) Overall with mounting: 72 1/16 × 17 13/16 in. (183 × 45.3 cm) Overall with knobs: 72 1/16 × 19 13/16 in. (183 × 50.4 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/670979,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.130,false,true,670930,Asian Art,Hanging scroll,羽根突き美人図|Woman with Battledore and Shuttlecock,Japan,Momoyama period (1573–1615),,,,Artist,,Torii Kiyotomo,"Japanese, active early 19th century",,Torii Kiyotomo,,1815,1820,1815–20,1815,1820,"Hanging scroll; ink, color and gold on paper",Image: 45 3/16 × 20 11/16 in. (114.8 × 52.5 cm) Overall with mounting: 71 1/4 × 24 1/8 in. (181 × 61.2 cm) Overall with knobs: 71 1/4 × 26 3/8 in. (181 × 67 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/670930,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.50,false,true,53231,Asian Art,Hanging scroll,竹林七聖図|Seven Sages of the Bamboo Grove,Japan,Muromachi period (1392–1573),,,,Artist,,Sesson Shūkei,ca. 1504–ca. 1589,,Sesson Shūkei,,1504,1589,1550s,1550,1559,Hanging scroll; ink and color on paper,Image: 40 5/16 × 20 3/8 in. (102.4 × 51.7 cm) Overall with mounting: 79 3/4 × 26 7/16 in. (202.5 × 67.2 cm) Overall with knobs: 79 3/4 × 28 9/16 in. (202.5 × 72.6 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/53231,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.49,false,true,53212,Asian Art,Hanging scroll,蘇軾騎驢図|Su Shi Riding a Donkey,Japan,Muromachi period (1392–1573),,,,Artist,,Bokudō Sojun,"Japanese, 1373–1459",,Bokudō Sojun,,1373,1459,early 15th century,1400,1433,Hanging scroll; ink and gold on paper,Image: 22 1/2 × 10 1/4 in. (57.2 × 26 cm) Overall with mounting: 57 11/16 × 14 7/8 in. (146.5 × 37.8 cm) Overall with knobs: 57 11/16 × 16 11/16 in. (146.5 × 42.4 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/53212,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.67,false,true,53233,Asian Art,Hanging scroll,伯牙鍾子期図|Bo Ya Plays the Qin as Zhong Ziqi Listens,Japan,Muromachi period (1392–1573),,,,Artist,Circle of,Kano Motonobu,"Japan, ca. 1476–1559",,Kano Motonobu,,1476,1559,1530s,1530,1539,Hanging scroll; ink and color on paper,Image: 65 1/16 × 34 1/4 in. (165.2 × 87 cm) Overall with mounting: 8 ft. 10 7/8 in. × 40 13/16 in. (271.5 × 103.7 cm) Overall with knobs: 8 ft. 10 7/8 in. × 43 3/16 in. (271.5 × 109.7 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/53233,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.268.40,false,true,45638,Asian Art,Hanging scroll,粟に燕図|Millet and Sparrows,Japan,Muromachi period (1392–1573),,,,Artist,,Geiai,active mid-16th century,,Geiai,,1534,1566,mid-16th century,1534,1566,Hanging scroll; ink on paper,Image: 39 9/16 × 17 5/8 in. (100.5 × 44.8 cm) Overall with knobs: 75 3/4 × 24 7/8 in. (192.4 × 63.2 cm),"The Harry G. C. Packard Collection of Asian Art, Gift of Harry G. C. Packard, and Purchase, Fletcher, Rogers, Harris Brisbane Dick, and Louis V. Bell Funds, Joseph Pulitzer Bequest, and The Annenberg Fund Inc. Gift, 1975",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45638,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2015.300.46a, b",false,true,65394,Asian Art,Hanging scrolls,"豊干寒山拾得図|Fenggan, Hanshan, and Shide",Japan,Muromachi period (1392–1573),,,,Artist,,Reisai,"Japanese, active ca. 1430–50",,Reisai,,1430,1450,first half of the 15th century,1400,1449,Pair of hanging scrolls; ink and color on paper,Image (a): 37 7/8 × 13 5/8 in. (96.2 × 34.6 cm) Overall with mounting (a): 72 15/16 × 18 11/16 in. (185.3 × 47.5 cm) Overall with knobs (a): 72 15/16 × 20 5/16 in. (185.3 × 51.6 cm) Image (b): 37 15/16 × 13 9/16 in. (96.3 × 34.5 cm) Overall with mounting (b): 72 5/8 × 18 11/16 in. (184.5 × 47.4 cm) Overall with knobs (b): 72 5/8 × 20 1/4 in. (184.5 × 51.5 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/65394,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.58,false,true,53219,Asian Art,Hanging scroll,葡萄蝉図|Cicada on a Grapevine,Japan,Muromachi period (1392–1573),,,,Artist,,Bokurin Guan,"Japanese, active late 14th century",,Bokurin Guan,,1367,1399,late 14th century,1392,1399,Hanging scroll; ink on paper,Image: 25 1/4 × 12 1/8 in. (64.2 × 30.8 cm) Overall with mounting: 57 5/16 × 15 1/2 in. (145.5 × 39.3 cm) Overall with knobs: 57 5/16 × 17 5/16 in. (145.5 × 44 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/53219,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.45,false,true,670971,Asian Art,Hanging scroll,騎獅文殊図|Monju on a Lion,Japan,Muromachi period (1392–1573),,,,Artist,,Shūsei,"Japanese, active late 15th century",(reading unsure),Shūsei,,1467,1499,late 15th century,1467,1499,Hanging scroll; ink on paper,Image: 32 1/16 in. × 13 in. (81.5 × 33 cm) Overall with mounting: 65 9/16 × 19 1/8 in. (166.5 × 48.6 cm) Overall with knobs: 65 9/16 × 20 7/8 in. (166.5 × 53 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/670971,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2015.300.64a, b",false,true,53221,Asian Art,Hanging scrolls,夏秋花鳥図|Birds and Flowers of Summer and Autumn,Japan,Muromachi period (1392–1573),,,,Artist,,Shikibu Terutada,"Japanese, active mid–16th century",,Shikibu Terutada,,1534,1566,mid-16th century,1534,1566,Pair of hanging scrolls; ink and color on paper,Image (a): 37 11/16 × 17 5/8 in. (95.8 × 44.8 cm) Overall with mounting (a): 74 7/16 × 23 3/8 in. (189 × 59.3 cm) Overall with knobs (a): 74 7/16 × 25 3/16 in. (189 × 64 cm) Image (b): 37 11/16 × 17 5/8 in. (95.8 × 44.8 cm) Overall with mounting (b): 74 5/8 × 23 3/8 in. (189.5 × 59.3 cm) Overall with knobs (b): 74 5/8 × 25 3/16 in. (189.5 × 64 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/53221,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -53.194.2,false,true,57338,Asian Art,Painted panel,,Japan,Muromachi period (1392–1573),,,,Artist,After,Kenkō Shokei,"Japanese, active ca. 1470–after 1523",,Kenkō Shokei,,1460,1523,late 15th century,1467,1499,Paint on paper,33 x 12 1/8 in. (83.8 x 30.8 cm),"Gift of Nathan V. Hammer, 1953",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/57338,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -53.194.3,false,true,57350,Asian Art,Painted panel,,Japan,Muromachi period (1392–1573),,,,Artist,After,Kenkō Shokei,"Japanese, active ca. 1470–after 1523",,Kenkō Shokei,,1460,1523,late 15th century,1467,1499,Paint on paper,33 x 12 in. (83.3 x 30.5 cm),"Gift of Nathan V. Hammer, 1953",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/57350,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.63,false,true,670904,Asian Art,Hanging scroll,瓜図|Melons,Japan,Muromachi period (1392–1573),,,,Artist,,Yamada Dōan,"Japanese, second half of the 16th century",,Yamada Dōan,,1550,1599,late 16th century,1567,1599,Hanging scroll; ink on paper,Image: 13 1/4 × 18 1/8 in. (33.6 × 46 cm) Overall with mounting: 47 1/2 × 23 7/8 in. (120.7 × 60.6 cm) Overall with knobs: 47 1/2 × 25 13/16 in. (120.7 × 65.5 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/670904,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2015.300.52a, b",false,true,53226,Asian Art,Hanging scrolls,,Japan,Muromachi period (1392–1573),,,,Artist,,Kantei,"Japanese, active second half of 15th century",,Kantei,,1400,1499,early 16th century,1500,1533,Pair of hanging scrolls; ink and color on paper,Image (a): 18 1/16 × 11 3/4 in. (45.8 × 29.9 cm) Overall with mounting (a): 51 15/16 × 16 3/8 in. (132 × 41.6 cm) Overall with knobs (a): 51 15/16 × 18 1/8 in. (132 × 46 cm) Image (b): 18 1/8 × 11 3/4 in. (46 × 29.9 cm) Overall with mounting (b): 52 1/16 × 16 5/16 in. (132.2 × 41.5 cm) Overall with knobs (b): 52 1/16 × 18 1/8 in. (132.2 × 46 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/53226,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.66,false,true,53222,Asian Art,Hanging scroll,麝香猫図|Musk Cat,Japan,Muromachi period (1392–1573),,,,Artist,,Uto Gyoshi,"Japanese, active second half of 16th century",,Uto Gyoshi,,1550,1599,second half of the 16th century,1550,1599,Hanging scroll; ink and color on paper,Image: 29 15/16 × 18 5/16 in. (76 × 46.5 cm) Overall with mounting: 66 1/8 × 24 1/8 in. (168 × 61.3 cm) Overall with knobs: 66 1/8 × 25 7/8 in. (168 × 65.8 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/53222,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.47,false,true,670981,Asian Art,Hanging scroll,牧牛図|Oxherding,Japan,Muromachi period (1392–1573),,,,Artist,,Sekkyakushi,"Japanese, active first half of the 15th century",,Sekkyakushi,,1400,1499,first half of the 15th century,1400,1449,Hanging scroll; ink on paper,Image: 21 1/16 × 11 9/16 in. (53.5 × 29.4 cm) Overall with mounting: 54 3/4 × 15 5/8 in. (139 × 39.7 cm) Overall with knobs: 54 3/4 × 17 5/8 in. (139 × 44.7 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/670981,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.55,false,true,53228,Asian Art,Hanging scroll,破墨山水図|Splashed-Ink Landscape,Japan,Muromachi period (1392–1573),,,,Artist,,Bokushō Shūshō,"Japanese, active late 15th–early 16th century",,Bokushō Shūshō,,1450,1600,early 16th century,1500,1533,Hanging scroll; ink on paper,Image: 31 1/2 × 13 3/8 in. (80 × 33.9 cm) Overall with mounting: 59 13/16 × 14 3/16 in. (152 × 36 cm) Overall with knobs: 59 13/16 × 16 1/16 in. (152 × 40.8 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/53228,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2015.300.53a, b",false,true,65392,Asian Art,Hanging scrolls,四季山水図|Landscapes of the Four Seasons,Japan,Muromachi period (1392–1573),,,,Artist,,Keison,"Japanese, active late 15th– early 16th century",,Keison,,1467,1533,late 15th–early 16th century,1467,1533,Pair of hanging scrolls; ink on paper,Image (a): 38 5/16 × 19 9/16 in. (97.3 × 49.7 cm) Overall with mounting (a): 75 13/16 × 25 11/16 in. (192.5 × 65.3 cm) Overall with knobs (a): 75 13/16 × 27 1/2 in. (192.5 × 69.8 cm) Image (b): 38 3/8 × 19 5/8 in. (97.4 × 49.8 cm) Overall with mounting (b): 75 9/16 × 25 11/16 in. (192 × 65.3 cm) Overall with knobs (b): 75 9/16 × 28 1/8 in. (192 × 71.4 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/65392,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.61,false,true,53199,Asian Art,Hanging scroll,"蘭竹図|Orchids, Bamboo, Briars, and Rocks",Japan,Nanbokuchō period (1336–92),,,,Artist,,Tesshū Tokusai,"Japanese, died 1366",,Tesshū Tokusai,,1342,1366,mid-14th century,1300,1400,Hanging scroll; ink on paper,Image: 28 3/8 × 14 1/2 in. (72 × 36.8 cm) Overall with mounting: 60 1/16 × 19 5/16 in. (152.5 × 49 cm) Overall with knobs: 60 1/16 × 21 in. (152.5 × 53.4 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/53199,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.56,false,true,53198,Asian Art,Hanging scroll,岩に鶺鴒図|Wagtail on a Rock,Japan,Nanbokuchō period (1336–92),,,,Artist,Attributed to,Taikyo Genju,"Japanese, active mid-14th century",,Taikyo Genju,,1300,1366,mid-14th century,1334,1366,Hanging scroll; ink on silk,Image: 32 3/4 × 13 3/4 in. (83.2 × 34.9 cm) Overall with mounting: 63 3/4 × 18 7/16 in. (161.9 × 46.8 cm) Overall with knobs: 63 3/4 × 20 1/4 in. (162 × 51.5 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/53198,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.234,false,true,670928,Asian Art,"Page from book, mounted as a hanging scroll",,Japan,late Heian period (794–1185),,,,Artist,Calligraphy by,Fujiwara no Norinaga,"Japanese, 1109–1180",,Fujiwara no Norinaga,,1109,1180,mid-to late 12th century,1134,1199,"Page from book, mounted as hanging scroll; ink on paper",Image: 9 15/16 × 6 1/4 in. (25.3 × 15.9 cm) Overall with mounting: 51 3/16 × 14 3/16 in. (130 × 36 cm) Overall with knobs: 51 3/16 × 16 1/8 in. (130 × 41 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/670928,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.230,false,true,53172,Asian Art,Album leaf mounted as a hanging scroll,,Japan,late Heian period (ca. 900–1185),,,,Artist,Calligraphy traditionally attributed to,Fujiwara no Yukinari (Kōzei),"Japanese, 972–1027",,Fujiwara no Yukinari,,0972,1027,2nd half of the 11th century,900,1200,Album leaf mounted as a hanging scroll; ink on paper,Image: 8 in. × 5 3/8 in. (20.3 × 13.7 cm) Overall with mounting: 49 3/4 × 14 3/16 in. (126.3 × 36 cm) Overall with knobs: 49 3/4 × 15 7/8 in. (126.3 × 40.3 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/53172,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.500.9.5,false,true,78147,Asian Art,Hanging scroll,,Japan,Momoyama (1573–1615)–Edo (1615–1868) period,,,,Artist,,Fūgai Ekun,"Japanese, 1568–1654",,Fūgai Ekun,,1568,1654,1568–1654,1568,1654,Hanging scroll; ink on paper,Image: 30 1/2 x 12 1/8 in. (77.5 x 30.8 cm),"Gift of Florence and Herbert Irving, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/78147,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2016.363.1,false,true,711870,Asian Art,Sculpture,不動明王像|Fudō Myōō,Japan,Edo period (1615–1868),,,,Artist,,Mokujiki Shōnin 木喰上人,"Japanese, 1718–1810",,Mokujiki Shōnin 木喰上人,,1718,1810,1805,1805,1805,Chisel-carved (natabori) wood,H. 35 7/16 in. (90 cm); W. 14 9/16 in. (37 cm); D. 9 13/16 in. (25 cm),"Purchase, Friends of Asian Art Gifts, 2016",,,,,,,,,,,,Sculpture,,http://www.metmuseum.org/art/collection/search/711870,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2015.300.250a, b",false,true,53175,Asian Art,Sculpture,地蔵菩薩立像|Jizō Bosatsu,Japan,Kamakura period (1185–1333),,,,Artist,,Kaikei,"Japanese, active 1183–1223",,Kaikei,,1183,1223,ca. 1202,1050,1400,"Lacquered Japanese cypress, color, gold, cut gold leaf, and inlaid crystal eyes",H. of figure incl. base 22 in. (55.9 cm); H. to top of spear 22 7/8 in. (58.1 cm); W. 6 3/4 in. (17.1 cm); D. 6 3/4 in. (17.1 cm); Diam. of base 6 3/4 in. (17.1 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Sculpture,,http://www.metmuseum.org/art/collection/search/53175,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2015.300.252a, b",false,true,53176,Asian Art,Figure,不動明王坐像|Fudō Myōō,Japan,Kamakura period (1185–1333),,,,Artist,,Kaikei,"Japanese, active 1183–1223",,Kaikei,,1183,1223,early 13th century,1100,1400,"Lacquered Japanese cypress, color, gold, cut gold (kirikane), and inlaid crystal eyes",H. 21 in. (53.3 cm); H. to top of sword 21 1/2 in. (54.6 cm); W. 16 3/4 in. (42.5 cm); D. 15 in. (38.1 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Sculpture,,http://www.metmuseum.org/art/collection/search/53176,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.500.2.46,false,true,39671,Asian Art,Tray,,Japan,,,,,Artist,Attributed to,Shibata Zeshin,"Japanese, 1807–1891",,Shibata Zeshin,,1807,1891,,1807,1891,"Wood, maki-e",L. 10 11/16 in. (27.1 cm); W. 10 5/8 in. (27 cm); H. 1 3/4 in. (4.4 cm),"Gift of Florence and Herbert Irving, 2015",,,,,,,,,,,,Lacquer,,http://www.metmuseum.org/art/collection/search/39671,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.97,false,true,53426,Asian Art,Screens,,Japan,Edo period (1615–1868),,,,Artist,,Ikeda Koson,"Japanese, 1803–1868",,Ikeda Koson,,1803,1868,,1720,1920,Two-panel folding screen; ink on paper,Image: 59 5/16 x 63 1/16 in. (150.6 x 160.2 cm) Overall with mounting: 67 1/2 x 70 1/2 in. (171.5 x 179.1 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Screens,,http://www.metmuseum.org/art/collection/search/53426,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.96,false,true,45648,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,Attributed to,Wagyoku Yogetsu,active 1521–1530,,Wagyoku Yogetsu,,1521,1530,,1521,1530,Hanging scroll; ink on paper,36 1/2 x 14 1/4 in. (92.7 x 36.2 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45648,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.44,false,true,53012,Asian Art,Handscroll,,Japan,Edo period (1615–1868),,,,Artist,,Ishiyama Moroka,"Japanese, 1669–1734",,Ishiyama Moroka,,1669,1734,,1600,1800,"Handscroll; ink, color, and gold on silk",Image: 13 in. × 25 ft. (33 × 762 cm) Overall with mounting: 14 in. (35.6 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/53012,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.129,false,true,670901,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Miyagawa Chōshun,"Japanese, 1683–1753",,Miyagawa Chōshun,,1683,1753,,1615,1868,Hanging scroll; ink and color on paper,Image: 49 3/4 × 20 7/8 in. (126.4 × 53 cm) Overall with mounting: 86 1/4 × 26 1/8 in. (219 × 66.4 cm) Overall with knobs: 86 1/4 × 28 5/8 in. (219 × 72.7 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/670901,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.158,false,true,670888,Asian Art,Hanging scrol,,Japan,Edo period (1615–1868),,,,Artist,,Yosa Buson,"Japanese, 1716–1783",,Yosa Buson,,1716,1783,,1615,1868,Hanging scroll; ink and color on paper,Image: 12 3/16 × 18 3/4 in. (31 × 47.7 cm) Overall with mounting: 44 5/16 × 21 3/16 in. (112.5 × 53.8 cm) Overall with knobs: 44 5/16 × 24 13/16 in. (112.5 × 63 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/670888,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.233,false,true,75347,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Ike Taiga,"Japanese, 1723–1776",,Ike Taiga,,1723,1776,,1723,1776,Hanging scroll; ink on paper,Image: 42 3/8 x 10 7/8 in. (107.6 x 27.6 cm),"Purchase, Friends of Asian Art Gifts, 2009",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/75347,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.96,false,true,670950,Asian Art,Folding fan mounted as an album leaf,,Japan,Edo period (1615–1868),,,,Artist,,Suzuki Kiitsu,"Japanese, 1796–1858",,Suzuki Kiitsu,,1796,1858,,1615,1868,"Folding fan mounted as an album leaf; ink and color on paper, framed",Image: 9 7/16 × 20 5/8 in. (24 × 52.4 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/670950,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.208,false,true,670894,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Shiokawa Bunrin,"Japanese, 1808–1877",,Shiokawa Bunrin,,1808,1877,,1615,1868,"Hanging scroll; ink, color and gold on silk",Image: 37 3/16 × 13 11/16 in. (94.4 × 34.8 cm) Overall with mounting: 74 13/16 × 19 7/16 in. (190 × 49.3 cm) Overall with knobs: 74 13/16 × 21 5/16 in. (190 × 54.2 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/670894,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.144,false,true,73355,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Kaseki,"Japanese, active 18th century",,Kaseki,,1700,1799,,1615,1868,Hanging scroll; ink and color on silk,Image: 14 1/2 in. × 21 in. (36.8 × 53.3 cm) Overall with mounting: 50 × 24 5/8 in. (127 × 62.5 cm) Overall with knobs: 50 × 27 3/16 in. (127 × 69 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/73355,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.124,false,true,670956,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Kengetsudō,"Japanese, active 18th century",,Kengetsudō,,1700,1799,,1615,1868,Hanging scroll; ink and color on paper,Image: 41 7/8 × 18 3/8 in. (106.4 × 46.7 cm) Overall with mounting: 65 7/8 × 23 3/16 in. (167.3 × 58.9 cm) Overall with knobs: 65 7/8 × 25 3/8 in. (167.3 × 64.5 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/670956,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.209,false,true,671057,Asian Art,Folding fan,,Japan,Meiji period (1868–1912),,,,Artist,,Shibata Zeshin,"Japanese, 1807–1891",,Shibata Zeshin,,1807,1891,,1868,1912,Folding fan; lacquer on paper,Image: 12 3/16 × 19 7/16 in. (31 × 49.3 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/671057,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.30,false,true,670968,Asian Art,Album leaf mounted as a hanging scroll,"源氏物語図色紙 「藤袴」|Scene from “Purple Trousers” (“Fujibakama”), from The Tale of Genji (Genji monogatari)",Japan,Momoyama period (1573–1615),,,,Artist,,Tosa Mitsuyoshi,"Japanese, 1539–1613",,TOSA MITSUYOSHI,,1539,1613,,1573,1615,"Album leaf mounted as a hanging scroll; ink, color and gold on paper",Image: 10 1/8 × 8 3/8 in. (25.7 × 21.2 cm) Overall with mounting: 54 5/8 × 15 1/4 in. (138.7 × 38.7 cm) Overall with knobs: 54 5/8 × 17 3/16 in. (138.7 × 43.6 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/670968,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -53.194.4,false,true,57351,Asian Art,Painted panel,,Japan,Muromachi period (1392–1573),,,,Artist,After,Kenkō Shokei,"Japanese, active ca. 1470–after 1523",,Kenkō Shokei,,1460,1523,,1467,1533,Paint on paper,33 1/8 x 12 1/4 in. (84.1 x 31.1 cm),"Gift of Nathan V. Hammer, 1953",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/57351,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.239,false,true,670887,Asian Art,Hanging scroll,「離離原上草一歳一枯榮」 (白居易『草』より)|Couplet from the Chinese Poem “Grasses” by Bai Juyi,Japan,Muromachi period (1392–1573),,,,Artist,,Motsurin Jōtō (Bokusai),"Japanese, died 1491",,Motsurin Jōtō,,,1491,15th century,1400,1499,Hanging scroll; ink on paper,Image: 46 3/4 × 10 3/8 in. (118.8 × 26.4 cm) Overall with mounting: 74 11/16 × 10 15/16 in. (189.7 × 27.8 cm) Overall with knobs: 74 11/16 × 12 1/2 in. (189.7 × 31.8 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/670887,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2014.719.9,false,true,60465,Asian Art,Hanging scroll,愚極礼才書 「極重悪人無他方便・唯稱弥陀得生極楽」|Buddhist Maxim on the Saving Power of Amida,Japan,Nanbokuchō period (1336–92),,,,Artist,,Gukyoku Reisai,"Japanese, 1369–1452",,Gukyoku Reisai,,1369,1452,15th century,1400,1452,Pair of hanging scrolls; ink on paper,Image (each scroll): 36 3/4 x 8 3/4 in. (93.4 x 22.3 cm) Overall with mounting (a): 67 1/8 x 9 1/2 in. (170.5 x 24.1 cm) Overall with knobs (a): 67 1/8 x 11 1/4 in. (170.5 x 28.6 cm) Overall with mounting (b): 67 x 9 1/2 in. (170.2 x 24.1 cm) Overall with knobs (b): 67 x 11 3/16 in. (170.2 x 28.4 cm),"Gift of Sylvan Barnet and William Burto, in memory of John M. Rosenfield, 2014",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/60465,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.56,false,true,45245,Asian Art,Hanging scroll,,Japan,Muromachi period (1392–1573),,,,Artist,Attributed to,Shinno Noami,1397–1471,,Shinno Noami,,1397,1471,15th century,1400,1471,Hanging scroll; ink on paper,31 1/4 x 12 1/2 in. (79.4 x 31.8 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45245,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.62,false,true,670896,Asian Art,Hanging scroll,,Japan,Muromachi period (1392–1573),,,,Artist,,Motsurin Jōtō (Bokusai),"Japanese, died 1491",,Motsurin Jōtō,,,1491,15th century,1400,1491,Hanging scroll; ink on paper,Image: 10 3/4 × 16 11/16 in. (27.3 × 42.4 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/670896,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.2,false,true,670899,Asian Art,Hanging scroll,"不動明王四童子種字像|Fudō Myōō with Four Attendants, Outlined in Seed Syllables",Japan,Muromachi period (1392–1573),,,,Artist,,Chikai,"Japanese, ca. 1422–ca. 1503",,Chikai,,1422,1503,15th century,1400,1499,Hanging scroll; ink and color on paper,Image: 39 15/16 × 17 1/16 in. (101.5 × 43.4 cm) Overall with mounting: 67 11/16 × 22 15/16 in. (172 × 58.3 cm) Overall with knobs: 67 11/16 × 25 3/16 in. (172 × 64 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/670899,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.54,false,true,53230,Asian Art,Hanging scroll,,Japan,Muromachi period (1392–1573),,,,Artist,,Sesson Shūkei,ca. 1504–ca. 1589,,Sesson Shūkei,,1504,1589,16th century,1504,1589,Hanging scroll; ink and color on paper,Image: 11 7/8 × 18 3/8 in. (30.2 × 46.7 cm) Overall with mounting: 44 in. × 22 5/8 in. (111.8 × 57.4 cm) Overall with knobs: 44 × 24 1/2 in. (111.8 × 62.3 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/53230,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.39,false,true,42343,Asian Art,Hanging scroll,禅機(鳥窠道林・白居昜)|Zen Encounter (Niaoke Daolin and Bai Juyi),Japan,Muromachi period (1392–1573),,,,Artist,Attributed to,Kenkō Shokei,"Japanese, active ca. 1470–after 1523",,Kenkō Shokei,,1460,1523,16th century,1467,1533,Hanging scroll; ink on paper,12 x 18 3/4 in. (30.5 x 47.6 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/42343,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.211a–g,false,true,670969,Asian Art,Fourteen figures on seven folded sheets,,Japan,Edo period (1615–1868),,,,Artist,Attributed to,Iwasa Matabei,"Japanese, 1578–1650",,Iwasa Matabei,,1578,1650,17th century,1615,1650,Fourteen figures on seven folded sheets; ink on paper,Image (a): 8 3/4 × 15 11/16 in. (22.3 × 39.9 cm) Image (b): 8 3/4 × 11 1/2 in. (22.3 × 29.2 cm) Image (c): 8 3/4 × 11 9/16 in. (22.3 × 29.3 cm) Image (d): 8 3/4 × 11 3/8 in. (22.3 × 28.9 cm) Image (e): 8 3/4 × 11 9/16 in. (22.3 × 29.4 cm) Image (f): 8 3/4 × 11 3/16 in. (22.3 × 28.4 cm) Image (g): 8 3/4 × 12 3/16 in. (22.3 × 30.9 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/670969,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.39a–x,false,true,670914,Asian Art,Twenty-four volumes of printed text and illustrations,,Japan,Edo period (1615–1868),,,,Artist,,Yamamoto Shunshō,"Japanese, 1610–1682",,Yamamoto Shunshō,,1610,1682,17th century,1610,1682,Twenty-four volumes of printed text and illustrations; black ink on paper,Each book: 10 1/2 × 7 3/8 in. (26.7 × 18.7 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/670914,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.79,false,true,670911,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Kano Kōya,"Japanese, died 1673",,Kano Kōya,,,1673,17th century,1615,1673,Hanging scroll; ink on paper,Image: 35 15/16 × 16 1/16 in. (91.3 × 40.8 cm) Overall with mounting: 67 13/16 × 19 13/16 in. (172.2 × 50.4 cm) Overall with knobs: 67 13/16 × 22 3/16 in. (172.2 × 56.3 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/670911,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2015.300.38a, b",false,true,76463,Asian Art,Handscrolls,,Japan,Edo period (1615–1868),,,,Artist,Attributed to,Kaihō Yūsetsu,"Japanese, 1598–1677",,Kaihō Yūsetsu,,1598,1677,17th century,1600,1699,Set of two handscrolls; ink and color on paper,Image (each scroll): 9 7/16 in. × 63 ft. 8 9/16 in. (24 × 1942 cm) Overall with knobs: 11 7/16 in. × 63 ft. 8 9/16 in. (29 × 1942 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/76463,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.220,false,true,671033,Asian Art,Handscroll,,Japan,Edo period (1615–1868),,,,Artist,,Unkoku Tōban,"Japanese, 1633–1724",,Unkoku Tōban,,1633,1724,17th century,1615,1699,Handscroll; ink on silk,Image: 12 3/4 × 19 5/8 in. (32.4 × 49.8 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/671033,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -91.1.679,false,true,58458,Asian Art,Inrō,,Japan,Edo period (1615–1868),,,,Artist,,Gion Nankai,"Japanese, 1677–1751",,Gion Nankai,,1677,1751,18th century,1700,1799,"Lacquer sprinkled with gold and silver makie, and foil; Ojima: floral scrolls in openwork; silver and silver wire; Netsuke: chrysanthemum medallion; ivory",H. 3 1/8 in. (7.9 cm); W. 2 5/8 in. (6.7 cm); D. 13/16 in. (2.1 cm),"Edward C. Moore Collection, Bequest of Edward C. Moore, 1891",,,,,,,,,,,,Inrō,,http://www.metmuseum.org/art/collection/search/58458,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -10.211.2098,false,true,59281,Asian Art,Netsuke,,Japan,Edo period (1615–1868),,,,Artist,,Matsuda Sukenaga,"Japanese, 1800–1871",,"Sukenaga, Matsuda",,1800,1871,18th century,1700,1799,Wood,H. 1 3/4 in. (4.4 cm),"Gift of Mrs. Russell Sage, 1910",,,,,,,,,,,,Netsuke,,http://www.metmuseum.org/art/collection/search/59281,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.27,false,true,48979,Asian Art,Folding screen,俵屋宗理筆 朝顔図屏風|Morning Glories,Japan,Edo period (1615–1868),,,,Artist,,Tawaraya Sōri,active late 18th century,,Tawaraya Sori,,1764,1780,18th century,1700,1799,Two-panel folding screen; ink and color on paper,19 15/16 x 65 3/16 in. (50.6 x 165.6 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Screens,,http://www.metmuseum.org/art/collection/search/48979,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.42,false,true,671032,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Kawamata Tsunemasa,active 1716–48,,Kawamata Tsunemasa,,1706,1758,18th century,1700,1799,Hanging scroll; ink and color on paper,Image: 13 1/8 × 21 9/16 in. (33.4 × 54.7 cm) Overall with mounting: 46 7/16 × 25 3/16 in. (118 × 64 cm) Overall with knobs: 46 7/16 × 27 5/16 in. (118 × 69.3 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/671032,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.115,false,true,671008,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Nishikawa Sukenobu,"Japanese, 1671–1750",,Nishikawa Sukenobu,,1671,1750,18th century,1700,1750,Hanging scroll; ink and color on paper,Image: 15 1/16 × 22 7/16 in. (38.3 × 57 cm) Overall with mounting: 49 13/16 × 27 5/16 in. (126.5 × 69.4 cm) Overall with knobs: 49 13/16 × 29 7/16 in. (126.5 × 74.7 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/671008,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.8,false,true,45767,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,In the Style of,Miyagawa Chōshun,"Japanese, 1683–1753",,Miyagawa Chōshun,,1683,1753,18th century,1700,1753,Hanging scroll; ink and color on silk,24 7/8 x 10 11/16 in. (63.2 x 27.1 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45767,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.128,false,true,670967,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Okumura Masanobu,"Japanese, 1686–1764",,Masanobu Okumura,,1686,1764,18th century,1700,1799,Hanging scroll; ink and color on silk,Image: 12 5/16 × 19 5/8 in. (31.3 × 49.9 cm) Overall with mounting: 49 1/8 × 25 1/8 in. (124.8 × 63.8 cm) Overall with knobs: 49 1/8 × 27 5/16 in. (124.8 × 69.3 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/670967,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.219,false,true,670917,Asian Art,Handscroll,,Japan,Edo period (1615–1868),,,,Artist,,Sumiyoshi Hiromori,"Japanese, 1705–1777",,Sumiyoshi Hiromori,,1705,1777,18th century,1705,1777,"Handscroll; ink, color and gold on paper",Image: 13 3/8 in. × 11 ft. 9 7/16 in. (33.9 × 359.3 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/670917,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.40,false,true,671015,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Tsukioka Settei,"Japanese, 1710–1786",,Tsukioka Settei,,1710,1786,18th century,1700,1799,Hanging scroll; ink and color on silk,Image: 34 13/16 × 12 7/16 in. (88.5 × 31.6 cm) Overall with mounting: 70 1/4 × 17 13/16 in. (178.5 × 45.2 cm) Overall with knobs: 70 1/4 × 19 3/4 in. (178.5 × 50.2 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/671015,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -64.55.1,false,true,48990,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Ike Taiga,"Japanese, 1723–1776",,Ike Taiga,,1723,1776,18th century,1723,1776,Hanging scroll; ink on silk,38 3/4 x 18 3/16 in. (98.4 x 46.2 cm),"Fletcher Fund, 1964",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/48990,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -64.55.2,false,true,48991,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Ike Taiga,"Japanese, 1723–1776",,Ike Taiga,,1723,1776,18th century,1723,1776,Hanging scroll; ink on silk,38 3/4 x 18 in. (98.4 x 45.7 cm),"Fletcher Fund, 1964",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/48991,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.268.93,false,true,48989,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Ike Taiga,"Japanese, 1723–1776",,Ike Taiga,,1723,1776,18th century,1723,1776,Hanging scroll; ink and color on paper,Image: 37 13/16 x 10 11/16 in. (96 x 27.2 cm) Overall with mounting: 67 3/8 x 15 3/4 in. (171.1 x 40 cm) Overall with knobs: 67 3/8 x 17 7/8 in. (171.1 x 45.4 cm),"The Harry G. C. Packard Collection of Asian Art, Gift of Harry G. C. Packard, and Purchase, Fletcher, Rogers, Harris Brisbane Dick, and Louis V. Bell Funds, Joseph Pulitzer Bequest, and The Annenberg Fund Inc. Gift, 1975",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/48989,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.268.94,false,true,48993,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Ike Taiga,"Japanese, 1723–1776",,Ike Taiga,,1723,1776,18th century,1723,1776,Hanging scroll; ink on paper,38 1/16 x 9 15/16 in. (96.7 x 25.2 cm),"The Harry G. C. Packard Collection of Asian Art, Gift of Harry G. C. Packard, and Purchase, Fletcher, Rogers, Harris Brisbane Dick, and Louis V. Bell Funds, Joseph Pulitzer Bequest, and The Annenberg Fund Inc. Gift, 1975",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/48993,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.162,false,true,671046,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Ike Taiga,"Japanese, 1723–1776",,Ike Taiga,,1723,1776,18th century,1723,1776,Hanging scroll; ink and color on paper,Image: 39 in. × 12 1/2 in. (99 × 31.7 cm) Overall with mounting: 75 × 17 15/16 in. (190.5 × 45.5 cm) Overall with knobs: 75 × 20 in. (190.5 × 50.8 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/671046,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.199,false,true,670983,Asian Art,Handscroll,,Japan,Edo period (1615–1868),,,,Artist,,Maruyama Ōkyo,"Japanese, 1733–1795",,Maruyama Ōkyo,,1733,1795,18th century,1733,1799,Handscroll; ink on paper,Image: 15 1/2 in. × 18 ft. 2 7/16 in. (39.4 × 554.8 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/670983,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.174,false,true,670890,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Totoki Baigai,"Japanese, 1749–1804",,Totoki Baigai,,1749,1804,18th century,1700,1799,Hanging scroll; ink and color on paper,Image: 10 11/16 × 22 5/16 in. (27.1 × 56.7 cm) Overall with mounting: 46 1/4 × 23 15/16 in. (117.5 × 60.8 cm) Overall with knobs: 46 1/4 × 26 in. (117.5 × 66 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/670890,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.92,false,true,670922,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Sakai Hōitsu,"Japanese, 1761–1828",,Sakai Hōitsu,,1761,1828,18th century,1700,1799,Fan mounted as a hanging scroll; ink and color on paper,Image: 8 11/16 × 17 11/16 in. (22 × 45 cm) Overall with mounting: 46 1/8 × 20 13/16 in. (117.2 × 52.8 cm) Overall with knobs: 46 1/8 × 22 11/16 in. (117.2 × 57.6 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/670922,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.117,false,true,671013,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Tsukioka Sessai,"Japanese, 1761–1839",,Tsukioka Sessai,,1761,1839,18th century,1761,1799,Hanging scroll; ink on silk,Image: 32 × 12 in. (81.3 × 30.5 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/671013,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.223,false,true,670902,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Chōgō,"Japanese, active 18th century",,Chōgō,,1700,1799,18th century,1700,1799,Hanging scroll; ink and color on paper,Image: 18 7/8 × 21 5/8 in. (48 × 55 cm) Overall with mounting: 54 1/2 × 24 3/4 in. (138.5 × 62.8 cm) Overall with knobs: 54 1/2 × 26 15/16 in. (138.5 × 68.5 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/670902,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.41,false,true,670898,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,Attributed to,Matsuno Chikanobu,"Japanese, active early 18th century",,Matsuno Chikanobu,,1700,1733,18th century,1700,1799,Hanging scroll; ink and color on paper,Image: 31 7/16 × 11 5/8 in. (79.9 × 29.6 cm) Overall with mounting: 64 9/16 × 13 7/16 in. (164 × 34.2 cm) Overall with knobs: 64 9/16 × 15 3/4 in. (164 × 40 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/670898,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.751,false,true,58663,Asian Art,Inrō,,Japan,,,,,Artist,,Kano Terunobu,1717–63,", Yusei",Kano Terunobu,,1717,1763,19th century,1800,1899,"Lacquer, roiro, white lacquer, gold and coloured hiramakie; Interior: nashiji and fundame",3 1/4 x 1 5/8 x 13/16 in. (8.3 x 4.1 x 2.1 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Inrō,,http://www.metmuseum.org/art/collection/search/58663,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.894,false,true,78784,Asian Art,Illustrated book,小磯前雪窓先生画帖 完|Album [of works] by the master Koiso Zensetsusō (complete) (Koiso Zensentsusō sensei gajō–kan),Japan,,,,,Artist,,Koiso Zensetsusō,"Japanese, 1832–1902",,"Koiso, Zensetsusō",,1832,1902,19th century,1832,1902,Accordion album; ink and color on paper,11 × 6 1/2 in. (28 × 16.5 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78784,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.736,false,true,58649,Asian Art,Inrō,,Japan,Edo period (1615–1868),,,,Artist,,Louisine W. Havemeyer,,,"Havemeyer, Louisine W.",,1855,1929,19th century,1800,1899,Aogai shell and gold foil inlay on black lacquer,3 1/2 x 2 7/16 x 1 in. (8.9 x 6.2 x 2.5 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Inrō,,http://www.metmuseum.org/art/collection/search/58649,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.762,false,true,58673,Asian Art,Inrō,,Japan,Edo period (1615–1868),,,,Artist,,Louisine W. Havemeyer,,,"Havemeyer, Louisine W.",,1855,1929,19th century,1800,1899,"Lacquer, gold, hirame, gold and coloured hiramakie, takamakie, nashiji; Interior: nashiji and fundame",3 5/16 x 1 15/16 x 1 in. (8.4 x 5 x 2.6 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Inrō,,http://www.metmuseum.org/art/collection/search/58673,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.771,false,true,58681,Asian Art,Inrō,,Japan,Edo period (1615–1868),,,,Artist,,Louisine W. Havemeyer,,,"Havemeyer, Louisine W.",,1855,1929,19th century,1800,1899,"Lacquer, roiro, hirame, gold and coloured hiramakie, nashiji, various inlay; Interior: fundame",3 3/16 x 2 x 7/8 in. (8.1 x 5.1 x 2.2 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Inrō,,http://www.metmuseum.org/art/collection/search/58681,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.840,false,true,58767,Asian Art,Inrō,,Japan,Edo period (1615–1868),,,,Artist,,Hogen Dohaku,died 1851,,Hogen Dohaku,,1851,1851,19th century,1800,1899,"Lacquer, fundame, sumie togidashi, applied metals; Interior: nashiji and fundame",3 3/16 x 2 5/16 x 3/4 in. (8.1 x 5.8 x 1.9 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Inrō,,http://www.metmuseum.org/art/collection/search/58767,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JIB76,false,true,57674,Asian Art,Illustrated book,,Japan,Edo period (1615–1868),,,,Artist,,Reizen Saburo Tametaka,died 1864,,Reizen Saburo Tametaka,,1864,1864,19th century,1800,1900,Ink and color on paper,11 × 7 3/4 × 1 3/8 in. (27.9 × 19.7 × 3.5 cm),"Gift of Yamanaka Co., 1926",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/57674,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.246,false,true,58932,Asian Art,Inrō,,Japan,Edo period (1615–1868),,,,Artist,In the Style of,Tanzan,1655–1729?,,Tanzan,,1655,1729,19th century,1800,1899,"Sprinkled gold and silver lacquer, makie, and takamakie Ojime: bead; tortoiseshell Netuske: box with decoration of violets; gold makie lacquer with gold and silver makie",3 1/2 x 2 3/16 x 1 in. (8.9 x 5.5 x 2.5 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Inrō,,http://www.metmuseum.org/art/collection/search/58932,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.778,false,true,58688,Asian Art,Inrō,,Japan,Meiji period (1868–1912),,,,Artist,,Fang Shi Mopu,ca. 1588,,Fang Shi Mopu,,1588,1588,19th century,1800,1899,"Black hiramaki-e, takamaki-e, ceramic and mother-of-pearl inlay; Interior: Roiro and fundame",3 1/16 x 1 7/8 x 3/4 in. (7.8 x 4.8 x 1.9 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Inrō,,http://www.metmuseum.org/art/collection/search/58688,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.500.2.56a–g,false,true,40489,Asian Art,Writing box,,Japan,Meiji period (1868–1912),,,,Artist,Style of,Ogawa Haritsu (Ritsuō),"Japanese, 1663–1747",,Ritsuō,,1663,1747,19th century,1700,1900,Gold maki-e on black lacquer,L. 8 3/4 in. (22.2 cm); W. 9 in. (22.9 cm); H. 2 in. (5.1 cm),"Gift of Florence and Herbert Irving, 2015",,,,,,,,,,,,Lacquer,,http://www.metmuseum.org/art/collection/search/40489,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -79.2.954,false,true,47053,Asian Art,Vase,,Japan,,,,,Artist,,Eiraku Hozen,1795–1854,,Eiraku Hozen,,1795,1854,19th century,1800,1899,Clay covered with polychrome glazes on ornaments outlined in relief (Kairakuen ware),H. 6 7/8 in. (17.5 cm); Diam. 4 3/8 in. (11.1 cm); Diam. of rim 3 1/8 in. (7.9 cm); Diam. of base 2 1/2 in. (6.4 cm),"Purchase by subscription, 1879",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/47053,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -79.2.978,false,true,46687,Asian Art,Vase,,Japan,,,,,Artist,,Eiraku Hozen,1795–1854,,Eiraku Hozen,,1795,1854,19th century,1800,1899,Clay covered with glazes (Kairakuen ware),H. 5 3/4 in. (14.6 cm); Diam. 3 3/8 in. (8.6 cm); Diam. of rim 2 1/8 in. (5.4 cm); Diam. of base 2 1/4 in. (5.7 cm),"Purchase by subscription, 1879",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/46687,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -93.1.168,false,true,47519,Asian Art,Flower pot,,Japan,,,,,Artist,,Eiraku Hozen,1795–1854,,Eiraku Hozen,,1795,1854,19th century,1800,1899,Paste covered with a transparent crackled glaze (Kyoto ware),H. 7 1/8 in. (18.1 cm); Diam. 7 5/8 in. (19.4 cm),"Gift of Mr. and Mrs. Samuel Colman, 1893",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/47519,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"79.2.1338a, b",false,true,62536,Asian Art,Covered bowl,,Japan,,,,,Artist,in the style of,Eiraku Hozen,1795–1854,,Eiraku Hozen,,1795,1854,19th century,1800,1899,Porcelain with polychrome glaze (Kairakuen ware),H. 3/1/4 in. (8.3 cm); W. 5 in. (12.7 cm),"Purchase by subscription, 1879",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/62536,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -93.3.490,false,true,45869,Asian Art,Vase,,Japan,,,,,Artist,,Tanzan,1655–1729?,,Tanzan,,1655,1729,19th century,1800,1899,Clay decorated with slip under a transparent glaze andcolored enamels (Nabeshima ware),H. 18 1/8 in. (46 cm),"Gift of Charles Stewart Smith, 1893",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/45869,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -93.3.491,false,true,45870,Asian Art,Vase,,Japan,,,,,Artist,,Tanzan,1655–1729?,,Tanzan,,1655,1729,19th century,1800,1899,Clay decorated with slip under a transparent glaze andcolored enamels (Nabeshima ware),H. 18 1/8 in. (46 cm),"Gift of Charles Stewart Smith, 1893",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/45870,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.500.9.43,false,true,667265,Asian Art,Bowl,,Japan,Edo (1615–1868),,,,Artist,,Nin'ami Dōhachi (Dōhachi II),"Japanese, 1783–1855",,Dōhachi II,,1783,1855,19th century,1800,1868,Stoneware with polychrome enamels,H. 4 in. (10.2 cm); Diam. 7 3/16 in. (18.2 cm),"Gift of Florence and Herbert Irving, 2015",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/667265,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.500.9.41,false,true,667263,Asian Art,Bowl,,Japan,Edo period (1615–1868),,,,Artist,,Nin'ami Dōhachi (Dōhachi II),"Japanese, 1783–1855",,Dōhachi II,,1783,1855,19th century,1800,1855,Stoneware with light blue glaze (Kyoto ware),W. 8 1/4 in. (21 cm),"Gift of Florence and Herbert Irving, 2015",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/667263,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.500.9.44,false,true,667266,Asian Art,Bowl,,Japan,Edo period (1615–1868),,,,Artist,,Nin'ami Dōhachi (Dōhachi II),"Japanese, 1783–1855",,Dōhachi II,,1783,1855,19th century,1800,1899,Stoneware with polychrome enamels,Diam. 6 1/2 in. (16.5 cm),"Gift of Florence and Herbert Irving, 2015",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/667266,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.84,false,true,49052,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,Attributed to,Okamoto Toyohiko,1773–1845,,Okamoto Toyohiko,,1773,1845,19th century,1800,1845,Hanging scroll; ink and color on silk,15 3/4 x 27 3/4 in. (40 x 70.5 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49052,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.85,false,true,40010,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Okamoto Toyohiko,1773–1845,,Okamoto Toyohiko,,1773,1845,19th century,1800,1845,Hanging scroll; ink and color on silk,43 x 16 1/2 in. (109.2 x 41.9 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/40010,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -18.77.2,false,true,49086,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Eiraku Hozen,1795–1854,,Eiraku Hozen,,1795,1854,19th century,1800,1854,Hanging scroll; ink and color on paper,Overall: 47 1/2 x 16 7/8 in. (120.7 x 42.9 cm) Overall with mounting: 67 3/8 x 21 3/4 in. (171.1 x 55.2 cm) Overall with knobs: 67 3/8 x 23 3/4 in. (171.1 x 60.3 cm),"Rogers Fund, 1918",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49086,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -18.77.3,false,true,49087,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Eiraku Hozen,1795–1854,,Eiraku Hozen,,1795,1854,19th century,1800,1854,Hanging scroll; ink and color on paper,Overall: 47 1/4 x 16 7/8 in. (120 x 42.9 cm) Overall with mounting: 67 x 21 3/4 in. (170.2 x 55.2 cm) Overall with knobs: 67 x 23 3/4 in. (170.2 x 60.3 cm),"Rogers Fund, 1918",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49087,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -18.77.4,false,true,49088,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Eiraku Hozen,1795–1854,,Eiraku Hozen,,1795,1854,19th century,1800,1854,Hanging scroll; ink and color on paper,Overall: 47 1/2 x 16 7/8 in. (120.7 x 42.9 cm) Overall with mounting: 67 x 21 3/4 in. (170.2 x 55.2 cm) Overall with knobs: 67 x 23 3/4 in. (170.2 x 60.3 cm),"Rogers Fund, 1918",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49088,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -18.77.5,false,true,49089,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Eiraku Hozen,1795–1854,,Eiraku Hozen,,1795,1854,19th century,1800,1854,Hanging scroll; ink and color on paper,Overall: 47 1/2 x 16 7/8 in. (120.7 x 42.9 cm) Overall with mounting: 67 1/4 x 21 7/8 in. (170.8 x 55.6 cm) Overall with knobs: 67 1/4 x 23 3/4 in. (170.8 x 60.3 cm),"Rogers Fund, 1918",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49089,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -18.77.6,false,true,49090,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Eiraku Hozen,1795–1854,,Eiraku Hozen,,1795,1854,19th century,1800,1854,Hanging scroll; ink and color on paper,Overall: 47 1/2 x 16 7/8 in. (120.7 x 42.9 cm) Overall with mounting: 67 1/4 x 21 7/8 in. (170.8 x 55.6 cm) Overall with knobs: 67 1/4 x 23 3/4 in. (170.8 x 60.3 cm),"Rogers Fund, 1918",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49090,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -18.77.7,false,true,49091,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Eiraku Hozen,1795–1854,,Eiraku Hozen,,1795,1854,19th century,1800,1854,Hanging scroll; ink and color on paper,Overall: 47 1/2 x 16 3/4 in. (120.7 x 42.5 cm) Overall with mounting: 67 1/4 x 21 7/8 in. (170.8 x 55.6 cm) Overall with knobs: 67 1/4 x 23 3/4 in. (170.8 x 60.3 cm),"Rogers Fund, 1918",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49091,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.83,false,true,48999,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,Attributed to,Senkaku Toshu,1804–1871,,Senkaku Toshu,,1804,1871,19th century,1804,1871,Hanging scroll; ink and color on silk,37 x 14 in. (94.0 x 35.6 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/48999,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2015.300.218a, b",false,true,671051,Asian Art,Two folding fans mounted on panels,,Japan,Edo period (1615–1868),,,,Artist,Attributed to,Okada Tamechika,1823–1864,,Okada Tamechika,,1823,1864,19th century,1823,1868,"Two folding fans mounted on panels; ink, color and gold on paper",Image (a): 9 5/16 × 19 1/8 in. (23.7 × 48.6 cm) Frame (a): 16 1/8 × 29 1/8 in. (41 × 74 cm) Image (b): 9 1/4 × 19 3/16 in. (23.5 × 48.7 cm) Frame (b): 16 1/8 × 29 1/8 in. (41 × 74 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/671051,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.160,false,true,670934,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Yokoi Kinkoku,"Japanese, 1761–1832",,Yokoi Kinkoku,,1761,1832,19th century,1800,1899,Hanging scroll; ink and color on paper,Image: 31 1/2 × 59 1/8 in. (80 × 150.2 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/670934,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.161,false,true,670945,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Yokoi Kinkoku,"Japanese, 1761–1832",,Yokoi Kinkoku,,1761,1832,19th century,1800,1868,Hanging scroll; ink and light color on paper,Image: 42 3/4 × 17 5/8 in. (108.6 × 44.8 cm) Overall with mounting: 72 5/8 × 24 7/16 in. (184.5 × 62 cm) Overall with knobs: 72 5/8 × 26 7/8 in. (184.5 × 68.3 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/670945,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.151,false,true,670925,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Teisai Hokuba,"Japanese, 1771–1844",,Teisai Hokuba,,1771,1844,19th century,1800,1844,Hanging scroll; ink and color on paper,Image: 41 1/8 in. × 11 in. (104.5 × 28 cm) Overall with mounting: 76 × 11 1/4 in. (193 × 28.5 cm) Overall with knobs: 76 × 17 1/4 in. (193 × 43.8 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/670925,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.148,false,true,671047,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Toyokuni II,"Japanese, 1777–1835",,Utagawa Toyokuni II,,1777,1835,19th century,1800,1868,Hanging scroll; ink and color on silk,Image: 15 7/8 × 21 7/16 in. (40.3 × 54.4 cm) Overall with mounting: 44 11/16 × 25 5/16 in. (113.5 × 64.3 cm) Overall with knobs: 44 11/16 × 27 3/8 in. (113.5 × 69.5 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/671047,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2015.300.99a, b",false,true,54773,Asian Art,Hanging scrolls,,Japan,Edo period (1615–1868),,,,Artist,,Ikeda Koson,"Japanese, 1803–1868",,Ikeda Koson,,1803,1868,19th century,1801,1866,"Pair of hanging scrolls; ink, color, and gold on silk",Image (a): 42 3/8 × 14 3/16 in. (107.7 × 36 cm) Overall with mounting (a): 76 3/16 × 18 11/16 in. (193.5 × 47.5 cm) Overall with knobs: 76 3/16 × 20 3/4 in. (193.5 × 52.7 cm) Image (b): 42 5/8 × 14 1/4 in. (108.3 × 36.2 cm) Overall with mounting (b): 76 3/8 × 18 3/4 in. (194 × 47.6 cm) Overall with knobs (b): 76 3/8 × 20 13/16 in. (194 × 52.8 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/54773,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.101,false,true,670980,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Sakai Ōho,"Japanese, 1808–1841",,Sakai Ōho,,1808,1841,19th century,1808,1841,Hanging scroll; ink and color on silk,Image: 40 1/2 in. × 14 in. (102.9 × 35.6 cm) Overall with mounting: 76 1/8 × 19 7/16 in. (193.3 × 49.3 cm) Overall with knobs: 76 1/8 × 21 5/16 in. (193.3 × 54.1 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/670980,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.207,false,true,670951,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Nishiyama Kan'ei,"Japanese, 1834–1897",,Nishiyama Kan'ei,,1834,1897,19th century,1834,1897,Hanging scroll; ink and color on silk,Image: 13 1/8 × 32 1/8 in. (33.4 × 81.6 cm) Overall with mounting: 51 × 33 1/8 in. (129.5 × 84.2 cm) Overall with knobs: 51 × 35 5/16 in. (129.5 × 89.7 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/670951,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.85,false,true,670919,Asian Art,Hanging scroll,,Japan,Edo (1615–1868)–Meiji period (1868–1912),,,,Artist,,Kano Hōgai,"Japanese, 1828–1888",,Kano Hōgai,,1828,1888,19th century,1868,1899,Hanging scroll; ink on paper,Image: 50 7/8 × 12 11/16 in. (129.2 × 32.2 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/670919,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2015.300.203.1, .2",false,true,671026,Asian Art,Sliding panels,雪狗子図襖|Puppies in the Snow,Japan,Edo period (1615–1868),,,,Artist,,Nagasawa Rosetsu,"Japanese, 1754–1799",,Nagasawa Rosetsu,,1754,1799,late 18th century,1767,1799,Set of four sliding panels hinged together as a pair of two-panel screens; ink and color on paper,Image: 66 7/16 × 72 1/16 in. (168.7 × 183 cm) Overall with mounting: 68 × 74 5/8 in. (172.7 × 189.6 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Screens,,http://www.metmuseum.org/art/collection/search/671026,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1508,false,true,53342,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Suzuki Harushige,1747–1818,,Suzuki Harushige,,1747,1818,late 18th century,1767,1799,Polychrome woodblock print; ink and color on paper,H. 11 1/4 in. (28.6 cm); 8 1/4 in. (21 cm),"Fletcher Fund, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/53342,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.114,false,true,671007,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Nishikawa Sukenobu,"Japanese, 1671–1750",,Nishikawa Sukenobu,,1671,1750,late 18th century,1767,1799,Hanging scroll; ink and color on paper,Image: 33 3/4 in. × 13 in. (85.8 × 33 cm) Overall with mounting: 65 9/16 × 14 7/16 in. (166.5 × 36.7 cm) Overall with knobs: 65 9/16 × 17 1/16 in. (166.5 × 43.3 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/671007,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.602,false,true,679649,Asian Art,Hanging scroll,百年無事人|For a hundred years [I have been] a person with no attachments,Japan,Edo period (1615–1868),,,,Artist,,Jiun Sonja,"Japanese, 1718–1804",,Sonja Jiun,,1718,1804,late 18th century,1766,1799,Hanging scroll; ink on paper,Image: 46 1/2 × 10 7/8 in. (118.1 × 27.6 cm) Overall with mounting: 74 × 11 5/8 in. (188 × 29.5 cm) Overall with knobs: 13 5/8 in. (34.6 cm),"Gift of Morton Berman, in honor of Sylvan Barnet and William Burto, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/679649,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.170,false,true,670910,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Kuwayama Gyokushū,"Japanese, 1746–1799",,Kuwayama Gyokushū,,1746,1799,late 18th century,1767,1799,Hanging scroll; ink on silk,Image: 39 11/16 × 14 9/16 in. (100.8 × 37 cm) Overall with mounting: 79 5/16 × 22 3/16 in. (201.5 × 56.3 cm) Overall with knobs: 79 5/16 × 24 7/16 in. (201.5 × 62 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/670910,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.204,false,true,670999,Asian Art,Hanging scroll,飲中八仙図|Drinking Festival of the Eight Immortals,Japan,Edo period (1615–1868),,,,Artist,,Nagasawa Rosetsu,"Japanese, 1754–1799",,Nagasawa Rosetsu,,1754,1799,late 18th century,1767,1799,Hanging scroll; ink and color on paper,Image: 51 5/8 × 23 11/16 in. (131.1 × 60.1 cm) Overall with mounting: 88 3/4 × 29 11/16 in. (225.5 × 75.4 cm) Overall with knobs: 88 3/4 × 32 1/16 in. (225.5 × 81.5 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/670999,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.205,false,true,671000,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Nagasawa Rosetsu,"Japanese, 1754–1799",,Nagasawa Rosetsu,,1754,1799,late 18th century,1767,1799,Hanging scroll; ink and color on silk,Image: 39 3/4 × 13 15/16 in. (101 × 35.4 cm) Overall with mounting: 74 3/16 × 19 5/16 in. (188.5 × 49 cm) Overall with knobs: 74 3/16 × 21 1/4 in. (188.5 × 54 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/671000,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.500.9.14,false,true,78136,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Miyagawa (Katsukawa) Shunsui,"Japanese, active 1744–64",,Miyagawa (Katsukawa) Shunsui,,1744,1764,late 18th century,1744,1764,Hanging scroll; ink and color on silk,Image: 50 3/8 x 24 7/16 in. (128 x 62 cm),"Gift of Florence and Herbert Irving, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/78136,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2015.500.2.43a, b",false,true,40493,Asian Art,Smoking set,刻み煙草入れ|Portable Smoking Set,Japan,Edo period (1615–1868),,,,Artist,,Shibata Zeshin,"Japanese, 1807–1891",,Shibata Zeshin,,1807,1891,early 19th century,1800,1849,"Pipe: iron, gold, silver on wood; Pipe case: gold, silver hiramaki-e on black; Tobacco case: dyed cotton with metal fitting of a snail; Netsuke: carved staghorn with paulownia pattern",Pipe case: W. 3/4 in.; D. 5/8 in.; L. 11 1/4 in.; Pipe: L.7 3/4 in.; Tobacco case: H. 2 3/4 in.; W. 4 1/8 in.; D. 1 1/2 in.; Netsuke: Diam. 1 1/2 in.,"Gift of Florence and Herbert Irving, 2015",,,,,,,,,,,,Lacquer,,http://www.metmuseum.org/art/collection/search/40493,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.146,false,true,671041,Asian Art,Hanging scroll,桜下遊女と禿|Courtesan and her Attendant under a Cherry Tree,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Toyoharu,"Japanese, 1735–1814",,Utagawa Toyoharu,,1735,1814,early 19th century,1800,1833,Hanging scroll; ink and color on silk,Image: 35 5/16 in. × 14 in. (89.7 × 35.5 cm) Overall with mounting: 67 1/2 × 18 3/8 in. (171.5 × 46.7 cm) Overall with knobs: 67 1/2 × 20 1/2 in. (171.5 × 52.1 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/671041,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.500.9.17,false,true,78055,Asian Art,Hanging scroll,"「天満宮」 渡唐天神図|“Tenmangū,” Sugawara no Michizane as Tenjin Traveling to China",Japan,Edo period (1615–1868),,,,Artist,,Sengai Gibon,"Japanese, 1750–1837",,Sengai Gibon,,1750,1837,early 19th century,1800,1833,Hanging scroll; ink on paper,Image: 150 13/16 x 17 11/16 in. (383 x 45 cm),"Gift of Florence and Herbert Irving, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/78055,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.145,false,true,670908,Asian Art,Handscroll,三幅神吉原通い図巻 「全盛季春遊戯」|Three Gods of Good Fortune Visit the Yoshiwara; or “Scenes of Pleasure at the Height of Spring”,Japan,Edo period (1615–1868),,,,Artist,,Chōbunsai Eishi,"Japanese, 1756–1829",,Chōbunsai Eishi,,1756,1829,early 19th century,1800,1833,Handscroll; ink and color on silk,Image: 13 1/8 in. × 29 ft. 2 9/16 in. (33.3 × 890.4 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/670908,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.142a–c,false,true,53448,Asian Art,Hanging scrols,"蜀山人(大田 南畝)賛 雪・月・花図 |Snow, Moon, and Cherry Blossoms (Yoshiwara in Three Seasons)",Japan,Edo period (1615–1868),,,,Artist,,Chōbunsai Eishi,"Japanese, 1756–1829",,Chōbunsai Eishi,,1756,1829,early 19th century,1804,1815,"Triptych of hanging scrolls; ink, color, and gold on silk",Image (a): 32 3/8 × 11 13/16 in. (82.3 × 30 cm) Overall with mounting (a): 65 1/2 × 16 3/8 in. (166.3 × 41.6 cm) Overall with knobs (a): 65 1/2 × 18 9/16 in. (166.3 × 47.2 cm) Image (b): 32 3/8 × 11 13/16 in. (82.2 × 30 cm) Overall with mounting (b): 65 1/2 × 16 7/16 in. (166.3 × 41.7 cm) Overall with knobs (b): 65 1/2 × 18 9/16 in. (166.3 × 47.2 cm) Image (c): 32 3/8 × 11 3/4 in. (82.2 × 29.9 cm) Overall with mounting (c): 65 3/8 × 16 7/16 in. (166 × 41.7 cm) Overall with knobs (c): 65 3/8 × 18 9/16 in. (166 × 47.2 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/53448,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.500.9.45,false,true,78065,Asian Art,Hanging Scroll,,Japan,Edo period (1615–1868),,,,Artist,Attributed to,Kawahara Keiga,"Japanese, 1786–1860",,"Keiga, Kawahara",,1786,1860,early 19th century,1800,1833,"Hanging scroll; ink and color on silk, negoro lacquer roller knobs",Image: 19 3/4 x 9 7/8 in. (50.2 x 25.1 cm),"Gift of Florence and Herbert Irving, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/78065,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.200,false,true,670982,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Maruyama Ōshin,"Japanese, 1790–1838",,Maruyama Ōshin,,1790,1838,early 19th century,1800,1838,Hanging scroll; ink and color on silk,Image: 39 1/4 × 14 5/16 in. (99.7 × 36.3 cm) Overall with mounting: 77 3/8 × 19 13/16 in. (196.5 × 50.4 cm) Overall with knobs: 77 3/8 × 22 3/16 in. (196.5 × 56.3 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/670982,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.755,false,true,58666,Asian Art,Inrō,,Japan,Edo period (1615–1868),,,,Artist,,Fang Shi Mopu,ca. 1588,,Fang Shi Mopu,,1588,1588,18th–19th century,1700,1899,"Lacquer, roiro, black, gold, red hiramakie, takamakie, ceramic inlay; Interior: roiro and fundame",2 15/16 x 1 3/4 x 11/16 in. (7.5 x 4.4 x 1.7 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Inrō,,http://www.metmuseum.org/art/collection/search/58666,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.46,false,true,48898,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Suga Mitsusada,1738–1806,,Suga Mitsusada,,1738,1806,18th–19th century,1738,1806,Hanging scroll; ink and color on silk,40 x 12 3/16 in. (101.6 x 30.9 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/48898,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.47,false,true,48899,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Suga Mitsusada,1738–1806,,Suga Mitsusada,,1738,1806,18th–19th century,1738,1806,Hanging scroll; ink and color on silk,40 x 12 3/16 in. (101.6 x 30.9 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/48899,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.48,false,true,48900,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Suga Mitsusada,1738–1806,,Suga Mitsusada,,1738,1806,18th–19th century,1738,1806,Hanging scroll; ink and color on silk,40 x 12 3/16 in. (101.6 x 30.9 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/48900,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.171,false,true,670949,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Minagawa Kien,"Japanese, 1734–1807",,Minagawa Kien,,1734,1807,18th–19th century,1734,1807,Hanging scroll; ink on paper,Image: 41 1/4 × 12 1/2 in. (104.7 × 31.7 cm) Overall with mounting: 68 7/16 × 18 1/4 in. (173.8 × 46.4 cm) Overall with knobs: 68 7/16 × 20 3/8 in. (173.8 × 51.8 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/670949,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.268.135,false,true,49022,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Unpō,"Japanese, 1765–1848",,Unpō,,1765,1848,18th–19th century,1749,1848,Hanging scroll; ink on paper,49 1/2 x 11 9/16 in. (125.7 x 29.3 cm),"The Harry G. C. Packard Collection of Asian Art, Gift of Harry G. C. Packard, and Purchase, Fletcher, Rogers, Harris Brisbane Dick, and Louis V. Bell Funds, Joseph Pulitzer Bequest, and The Annenberg Fund Inc. Gift, 1975",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49022,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.500.9.46,false,true,78165,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist|Artist,Calligraphy by|Underpainting attributed to,Hon'ami Kōetsu|Tawaraya Sōtatsu,"Japanese, 1558–1637|Japanese, died ca. 1640",,Hon'ami Kōetsu|Tawaraya Sōtatsu,,1558 |1540,1637 |1640,mid-1620s,1624,1626,"Section of a handscroll, mounted as a hanging scroll",Image: 12 5/8 x 20 3/4 in. (32.1 x 52.7 cm),"Gift of Florence and Herbert Irving, 2015",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/78165,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.216,false,true,53410,Asian Art,Hanging scroll,天台山石橋図|Lions at the Stone Bridge of Mount Tiantai,Japan,Edo period (1615–1868),,,,Artist|Artist,Inscribed by,Soga Shōhaku|Gazan Yō Nansō,"Japanese, 1730–1781|Japanese, 1727–1797",,Soga Shōhaku|Gazan Yō Nansō,,1730 |1727,1781 |1797,1779,1600,1850,Hanging scroll; ink on silk,Image: 44 7/8 in. × 20 in. (114 × 50.8 cm) Overall with mounting: 79 1/8 × 25 3/16 in. (201 × 64 cm) Overall with knobs: 79 1/8 × 27 1/2 in. (201 × 69.8 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/53410,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.87,false,true,52987,Asian Art,Handscroll,木版下絵和歌巻断簡|Twelve Poems from the New Collection of Poems Ancient and Modern (Shin kokin wakashū),Japan,Edo period (1615–1868),,,,Artist|Artist,Calligraphy by|Printed designs by a follower of,Hon'ami Kōetsu|Tawaraya Sōtatsu,"Japanese, 1558–1637|Japanese, died ca. 1640",,Hon'ami Kōetsu|Tawaraya Sōtatsu,,1558 |1540,1637 |1640,ca. 1620,1550,1700,Handscroll; ink and gold on silk,Image: 13 3/8 in. × 16 ft. 1/8 in. (34 × 488 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/52987,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.121,false,true,671001,Asian Art,Hanging scroll,立美人図|Standing Courtesan,Japan,Edo period (1615–1868),,,,Artist|Artist,Inscribed by,Kamo no Suketame|Tōsendō Rifū,"Japanese, 1740–1801|Japanese, active ca. 1730",,Kamo no Suketame|Tōsendō Rifū,,1740 |1720,1801 |1740,ca. 1720,1710,1730,Hanging scroll; ink and color on silk,Image: 28 1/8 × 13 1/8 in. (71.4 × 33.4 cm) Overall with mounting: 59 1/16 in. (150 cm) Overall with knobs: 59 1/16 × 17 7/16 in. (150 × 44.3 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/671001,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2015.300.69a, b",false,true,53238,Asian Art,Hanging scrolls,政黄牛・郁山主図|Zheng Huangniu and Yushanzhu,Japan,Momoyama period (1573–1615),,,,Artist|Artist,Inscribed by,Takuan Sōhō|Kano Naizen,"1573–1645|Japanese, 1570–1616",,Takuan Sōhō|Kano Naizen,,1573 |1570,1645 |1616,early 17th century,1600,1633,Pair of hanging scrolls; ink on paper,Image (a): 44 in. × 18 11/16 in. (111.7 × 47.4 cm) Overall with mounting (a): 78 11/16 × 24 5/8 in. (199.8 × 62.6 cm) Overall with knobs (a): 78 11/16 × 26 9/16 in. (199.8 × 67.5 cm) Image (b): 44 1/16 × 18 11/16 in. (111.9 × 47.4 cm) Overall with mounting (b): 78 15/16 × 24 1/2 in. (200.5 × 62.3 cm) Overall with knobs (b): 78 15/16 × 26 9/16 in. (200.5 × 67.4 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/53238,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.71,false,true,671040,Asian Art,Hanging scroll,達磨図|Bodhidharma,Japan,Momoyama period (1573–1615),,,,Artist|Artist,Inscribed by,Unkoku Tōgan|Gyokuho Jōsō,"Japanese, 1547–1618|Japanese, 1546–1613",,Unkoku Tōgan|Gyokuho Jōsō,,1547 |1546,1618 |1613,late 16th–early 17th century,1567,1633,Hanging scroll; ink on paper,Image: 35 1/4 × 13 1/8 in. (89.6 × 33.4 cm) Overall with mounting: 70 7/8 × 18 9/16 in. (180 × 47.2 cm) Overall with knobs: 70 7/8 × 20 9/16 in. (180 × 52.3 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/671040,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.59,false,true,671052,Asian Art,Hanging scroll,枝に小禽図|Bird on a Branch,Japan,Muromachi period (1392–1573),,,,Artist|Artist,Inscribed by,Daiko Shōkaku|Unkei Eii,"Japanese, died 1535|Japanese, active first half of the 16th century",,Daiko Shōkaku|Unkei Eii,,1500,1535 |1549,early 16th century,1500,1535,Hanging scroll; ink on paper,Image: 9 5/16 × 10 13/16 in. (23.7 × 27.5 cm) Overall with mounting: 45 7/16 × 16 1/8 in. (115.4 × 41 cm) Overall with knobs: 45 7/16 × 17 15/16 in. (115.4 × 45.5 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/671052,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.60,false,true,671055,Asian Art,Hanging scroll,藻鯉図|Carp and Waterweeds,Japan,Muromachi period (1392–1573),,,,Artist|Artist,Inscribed by,Yōgetsu|Mokumoku Dōjin,"Japanese, active late 15th century|Japanese, active late 15th century",,Yōgetsu|Mokumoku Dōjin,,1467 |1467,1499 |1499,late 15th century,1467,1499,Hanging scroll; ink on silk,Image: 33 7/16 × 13 7/8 in. (85 × 35.2 cm) Overall with mounting: 65 3/4 × 19 1/16 in. (167 × 48.4 cm) Overall with knobs: 65 3/4 × 20 7/8 in. (167 × 53 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/671055,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.500.2.44,false,true,40494,Asian Art,Inrō,,Japan,Meiji period (1868–1912),,,,Artist|Artist,copied from a design by,Hon'ami Kōetsu|Shibata Zeshin,"Japanese, 1558–1637|Japanese, 1807–1891",,Hon'ami Kōetsu|Shibata Zeshin,,1558 |1807,1637 |1891,19th century,1800,1899,Gold maki-e with mother-of-pearl inlay on black lacquer; netsuke of hardwood with cloisonne,H.1 7/8 in. (4.8 cm); W. 1 1/2 in. (3.8 cm),"Gift of Florence and Herbert Irving, 2015",,,,,,,,,,,,Lacquer,,http://www.metmuseum.org/art/collection/search/40494,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.300.215,false,true,670929,Asian Art,Hanging scroll,伊藤若冲筆 伝池大雅賛 寒山拾得図|Hanshan and Shide (Japanese: Kanzan and Jittoku),Japan,Edo period (1615–1868),,,,Artist|Artist,Calligraphy attributed to,Itō Jakuchū|Ike Taiga,"Japanese, 1716–1800|Japanese, 1723–1776",,Itō Jakuchū|Ike Taiga,,1716 |1723,1800 |1776,late 18th century,1767,1799,Hanging scroll; ink on paper,Image: 39 1/2 × 11 15/16 in. (100.4 × 30.3 cm) Overall with mounting: 69 11/16 × 15 3/8 in. (177 × 39 cm) Overall with knobs: 69 11/16 × 17 5/16 in. (177 × 44 cm),"Mary Griggs Burke Collection, Gift of the Mary and Jackson Burke Foundation, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/670929,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2002.447.120a, b",false,true,49420,Asian Art,Teapot,,Japan,Edo period (1615–1868),,,,Artist,Design attributed to,Olfert Dapper,"Dutch, 1635–1689",,"Dapper, Olfert",Dutch,1639,1689,early 18th century,1700,1733,Porcelain painted with cobalt blue under transparent glaze (Jingdezhen ware),H. 6 1/2 in. (16.5 cm); W. 9 3/4 in. (24.8 cm),"Dr. and Mrs. Roger G. Gerry Collection, Bequest of Dr. and Mrs. Roger G. Gerry, 2000",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/49420,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2002.447.121,false,true,49421,Asian Art,Plate,,Japan,Edo period (1615–1868),,,,Artist,Design attributed to,Cornelis Pronk,"Dutch, Amsterdam 1691–1759 Amsterdam",,"Pronk, Cornelis",Dutch,1691,1759,ca. 1734–37,1734,1737,Porcelain painted with cobalt blue under and colored enamels over transparent glaze (Hizen ware; Imari type),H. 1 1/4 in. (3.2 cm); Diam. 10 1/2 in. (26.7 cm),"Dr. and Mrs. Roger G. Gerry Collection, Bequest of Dr. and Mrs. Roger G. Gerry, 2000",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/49421,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2002.447.122,false,true,49422,Asian Art,Dish,,Japan,Edo period (1615–1868),,,,Artist,Design attributed to,Cornelis Pronk,"Dutch, Amsterdam 1691–1759 Amsterdam",,"Pronk, Cornelis",Dutch,1691,1759,ca. 1734–37,1734,1737,Porcelain painted with cobalt blue under and colored enamels over transparent glaze (Hizen ware; Imari type),H. 1 in. (2.5 cm); Diam. 9 3/8 in. (23.9 cm),"Dr. and Mrs. Roger G. Gerry Collection, Bequest of Dr. and Mrs. Roger G. Gerry, 2000",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/49422,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2002.447.123,false,true,49423,Asian Art,Plate,,Japan,Edo period (1615–1868),,,,Artist,Design attributed to,Cornelis Pronk,"Dutch, Amsterdam 1691–1759 Amsterdam",,"Pronk, Cornelis",Dutch,1691,1759,ca. 1734–37,1734,1737,Porcelain painted with cobalt blue under and colored enamels over transparent glaze (Hizen ware; Imari type),Diam. 10 1/2 in. (26.7 cm),"Dr. and Mrs. Roger G. Gerry Collection, Bequest of Dr. and Mrs. Roger G. Gerry, 2000",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/49423,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2002.447.72,false,true,49324,Asian Art,Barber's bowl,,Japan,Edo period (1615–1868),,,,Artist,Design by,Cornelis Pronk,"Dutch, Amsterdam 1691–1759 Amsterdam",,"Pronk, Cornelis",Dutch,1691,1759,18th century,1700,1799,"Porcelain with underglaze blue (Hizen ware, Ko Imari type)",H. 3 7/16 in. (8.7 cm); Diam. 12 1/16 in. (30.6 cm),"Dr. and Mrs. Roger G. Gerry Collection, Bequest of Dr. and Mrs. Roger G. Gerry, 2000",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/49324,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1991.134,false,true,44632,Asian Art,Handscroll,"清 郎世寧 百駿圖白描稿 卷|One Hundred Horses",China,Qing dynasty (1644–1911),,,,Artist,,Giuseppe Castiglione,"Italian, Milan 1688–1766 Beijing",,"Castiglione, Giuseppe",Italian,1688,1766,datable to 1723–25,1723,1725,Handscroll; ink on paper,Image: 37 in. x 25 ft. 10 3/4 in. (94 x 789.3 cm) Overall with mounting: H. 38 1/2 in. (97.8 cm),"Purchase, Friends of Asian Art Gifts, 1991",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/44632,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -30.84,false,true,51582,Asian Art,Album leaf,,China,Qing dynasty (1644–1911),,,,Artist|Artist,In the style of,Unidentified Artist|Giuseppe Castiglione,"Italian, Milan 1688–1766 Beijing",,"Unidentified Artist|Castiglione, Giuseppe",Italian,1688,1766,,1644,1911,Album leaf; color on silk,12 3/4 x 11 1/4 in. (32.4 x 28.6 cm),"Rogers Fund, 1930",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/51582,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -13.220.121,false,true,51513,Asian Art,Hanging scroll,,China,Qing dynasty (1644–1911),,,,Artist|Artist,Formerly Attributed to,Unidentified Artist|Giuseppe Castiglione,"Italian, Milan 1688–1766 Beijing",,"Unidentified Artist|Castiglione, Giuseppe",Italian,1688,1766,,1644,1911,Hanging scroll,Overall: 24 3/4 x 54 in. (62.9 x 137.2 cm),"John Stewart Kennedy Fund, 1913",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/51513,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.513,false,true,48907,Asian Art,Painting,,Japan,Edo period (1615–1868),,,,Artist|Artist,Attributed to|In the Style of,Tsukioka Sessai|Giuseppe Castiglione,"Japanese, 1761–1839|Italian, Milan 1688–1766 Beijing",,"Tsukioka Sessai|Castiglione, Giuseppe",Italian,1761 |1688,1839 |1766,19th century,1800,1839,Framed painting; ink and color on silk,35 3/4 x 27 in. (90.8 x 68.6 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/48907,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.468,false,true,51563,Asian Art,Painting,,China,Ming dynasty (?) (1368–1644),,,,Artist|Artist,Formerly Attributed to,Unidentified Artist|Gessen,1721–1809,,Unidentified Artist|Gessen,Japanese,1721,1809,,1368,1644,Painting; ink and color on silk,Image: 10 × 23 in. (25.4 × 58.4 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/51563,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JIB164,false,true,57855,Asian Art,Illustrated book,Hyakunin joro shinasadame|百人女郎品定|One Hundred Women Classified According to Their Rank (Hyakunin joro shinasadame),Japan,Edo period (1615–1868),,,,Artist|Calligrapher,Preface by,Nishikawa Sukenobu|Jisho,"Japanese, 1671–1750",,Nishikawa Sukenobu|Jisho,Japanese,1671,1750,1723,1723,1723,"Vol. I: 9 double and 2 single page illustrations; vol. II: 15 double, 1 single page illustrations; ink on paper",Overall: 10 x 7 1/2in. (25.4 x 19.1cm),"Gift of Mrs. Henry J. Bernheim, 1945",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/57855,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.797,false,true,78813,Asian Art,Illustrated book,,Japan,Momoyama period (1573–1615),,,,Artist|Calligrapher|Calligrapher,in the style of,Unidentified Artist|Unidentified Artist|Hon'ami Kōetsu,"Japanese, 1558–1637",Japanese|Japanese,Unidentified Artist|Unidentified Artist|Hon'ami Kōetsu,Japanese,1558,1637,possibly ca. 1610,1600,1620,Woodblock printed book; ink on paper,13 3/4 × 10 1/8 in. (35 × 25.7 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78813,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1550,false,true,55714,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Calligrapher|Artist,Engraved by,Yokogawa Horitake|Utagawa Kuniyoshi,"Japanese, 1797–1861",,Yokogawa Horitake|Utagawa Kuniyoshi,Japanese,1797,1861,1852,1852,1852,Polychrome woodblock print; ink and color on paper,H. 14 5/8 in. (37.1 cm); W. 9 7/8 in. (25.1 cm),"Fletcher Fund, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55714,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1551,false,true,55715,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Calligrapher|Artist,Engraved by,Yokogawa Horitake|Utagawa Kuniyoshi,"Japanese, 1797–1861",,Yokogawa Horitake|Utagawa Kuniyoshi,Japanese,1797,1861,1852,1852,1852,Polychrome woodblock print; ink and color on paper,H. 14 5/8 in. (37.1 cm); W. 9 7/8 in. (25.1 cm),"Fletcher Fund, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55715,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1553,false,true,55716,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Calligrapher|Artist,Engraved by,Yokogawa Horitake|Utagawa Kuniyoshi,"Japanese, 1797–1861",,Yokogawa Horitake|Utagawa Kuniyoshi,Japanese,1797,1861,1852,1852,1852,Polychrome woodblock print; ink and color on paper,H. 14 5/8 in. (37.1 cm); W. 9 7/8 in. (25.1 cm),"Fletcher Fund, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55716,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1554,false,true,55717,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Calligrapher|Artist,Engraved by,Yokogawa Horitake|Utagawa Kuniyoshi,"Japanese, 1797–1861",,Yokogawa Horitake|Utagawa Kuniyoshi,Japanese,1797,1861,1852,1852,1852,Polychrome woodblock print; ink and color on paper,H. 14 5/8 in. (37.1 cm); W. 9 7/8 in. (25.1 cm),"Fletcher Fund, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55717,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1555,false,true,55718,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Calligrapher|Artist,Engraved by,Yokogawa Horitake|Utagawa Kuniyoshi,"Japanese, 1797–1861",,Yokogawa Horitake|Utagawa Kuniyoshi,Japanese,1797,1861,1852,1852,1852,Polychrome woodblock print; ink and color on paper,H. 14 5/8 in. (37.1 cm); W. 9 7/8 in. (25.1 cm),"Fletcher Fund, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55718,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1556,false,true,55719,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Calligrapher|Artist,Engraved by,Yokogawa Horitake|Utagawa Kuniyoshi,"Japanese, 1797–1861",,Yokogawa Horitake|Utagawa Kuniyoshi,Japanese,1797,1861,1852,1852,1852,Polychrome woodblock print; ink and color on paper,H. 14 5/8 in. (37.1 cm); W. 9 7/8 in. (25.1 cm),"Fletcher Fund, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55719,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1557,false,true,44999,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Calligrapher|Artist,Engraved by,Yokogawa Horitake|Utagawa Kuniyoshi,"Japanese, 1797–1861",,Yokogawa Horitake|Utagawa Kuniyoshi,Japanese,1797,1861,1852–53,1852,1853,Polychrome woodblock print; ink and color on paper,H. 14 5/8 in. (37.1 cm); W. 9 7/8 in. (25.1 cm),"Fletcher Fund, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/44999,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1558,false,true,55722,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Calligrapher|Artist,Engraved by,Yokogawa Horitake|Utagawa Kuniyoshi,"Japanese, 1797–1861",,Yokogawa Horitake|Utagawa Kuniyoshi,Japanese,1797,1861,1852,1852,1852,Polychrome woodblock print; ink and color on paper,H. 14 5/8 in. (37.1 cm); W. 9 7/8 in. (25.1 cm),"Fletcher Fund, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55722,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1559,false,true,55724,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Calligrapher|Artist,Engraved by,Yokogawa Horitake|Utagawa Kuniyoshi,"Japanese, 1797–1861",,Yokogawa Horitake|Utagawa Kuniyoshi,Japanese,1797,1861,1852,1852,1852,Polychrome woodblock print; ink and color on paper,H. 14 5/8 in. (37.1 cm); W. 9 7/8 in. (25.1 cm),"Fletcher Fund, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55724,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1560,false,true,55725,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Calligrapher|Artist,Engraved by,Yokogawa Horitake|Utagawa Kuniyoshi,"Japanese, 1797–1861",,Yokogawa Horitake|Utagawa Kuniyoshi,Japanese,1797,1861,1852,1852,1852,Polychrome woodblock print; ink and color on paper,H. 14 5/8 in. (37.1 cm); W. 9 5/8 in. (24.4 cm),"Fletcher Fund, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55725,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1561,false,true,55728,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Calligrapher|Artist,Engraved by,Yokogawa Horitake|Utagawa Kuniyoshi,"Japanese, 1797–1861",,Yokogawa Horitake|Utagawa Kuniyoshi,Japanese,1797,1861,1852,1852,1852,Polychrome woodblock print; ink and color on paper,H. 14 5/8 in. (37.1 cm); W. 9 7/8 in. (25.1 cm),"Fletcher Fund, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55728,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.268.34,false,true,44908,Asian Art,Hanging scroll,,Japan,Kamakura period (1185–1333),,,,Calligrapher,Calligraphy attributed to,Fujiwara no Nobuzane,"Japanese, 1176–1265",,Fujiwara no Nobuzane,Japanese,1176,1265,13th century,1200,1265,Section of a handscroll mounted as a hanging scroll; ink on paper,Image: 11 3/4 x 7 1/2 in. (29.8 x 19.1 cm) Overall: 57 1/2 x 17 7/8in. (146.1 x 45.4 cm),"The Harry G. C. Packard Collection of Asian Art, Gift of Harry G. C. Packard, and Purchase, Fletcher, Rogers, Harris Brisbane Dick, and Louis V. Bell Funds, Joseph Pulitzer Bequest, and The Annenberg Fund Inc. Gift, 1975",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/44908,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.162,false,true,49081,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Calligrapher,,Kobori Enshū,1579–1647,,Kobori Enshū,Japanese,1579,1579,17th century,1600,1647,Hanging scroll; ink on paper,8 3/4 x 11 3/4 in. (22.3 x 29.9 cm),"Gift of Mr. and Mrs. H. Jack Lang, 1975",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49081,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -94.18.1a–xx,false,true,45427,Asian Art,Album,源氏物語画帖|The Tale of Genji (Genji Monogatari),Japan,Edo period (1615–1868),,,,Calligrapher,,Shōren'in Sonjun Shinnō,"Japanese, 1581–1653",,Shōren'in Sonjun Shinnō,Japanese,1581,1653,17th century,1738,1806,"Set of twenty-four album leaves; ink, gold and color on paper",10 3/8 x 9 1/4 in. (26.3 x 23.5 cm),"Gift of Mary L. Cassilly, 1894",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45427,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2075,false,true,54906,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist|Calligrapher,,Teisai Hokuba|Chou,"Japanese, 1771–1844",,Teisai Hokuba|Chou,Japanese,1771,1844,early 19th century,1800,1833,Polychrome woodblock print (surimono); ink and color on paper,5 9/16 x 7 1/2 in. (14.1 x 19.1 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54906,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -10.211.2319,false,true,59380,Asian Art,Netsuke,,Japan,,,,,Artist,,Sanshō,1871–1936,,Sanshō,Japanese,1871,1936,early 20th century,1900,1933,Wood,H. 4 1/4 in. (10.8 cm); W. 1 1/4 in. (3.2 cm); D. 1 in. (2.5 cm),"Gift of Mrs. Russell Sage, 1910",,,,,,,,,,,,Netsuke,,http://www.metmuseum.org/art/collection/search/59380,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -10.211.2353,false,true,59404,Asian Art,Netsuke,,Japan,,,,,Artist,,Sanshō,1871–1936,,Sanshō,Japanese,1871,1936,late 19th century,1867,1899,Wood,H. 3 1/4 in. (8.3 cm),"Gift of Mrs. Russell Sage, 1910",,,,,,,,,,,,Netsuke,,http://www.metmuseum.org/art/collection/search/59404,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.120.687,false,true,60230,Asian Art,Kyogen mask,,Japan,,,,,Artist,,Tenkaichi Taiko,"Japanese, died 1616",,Tenkaichi Taiko,Japanese,1516,1616,early 17th century,1600,1633,Lacquered wood,H. 7 5/8 in. (19.4 cm); W. 5 3/4 in. (14.6 cm),"The Howard Mansfield Collection, Gift of Howard Mansfield, 1936",,,,,,,,,,,,Masks,,http://www.metmuseum.org/art/collection/search/60230,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -38.25.107a–e,false,true,58280,Asian Art,Writing box,,Japan,,,,,Artist,In the Style of,Ogawa Haritsu (Ritsuō),"Japanese, 1663–1747",,Ritsuō,Japanese,1663,1747,late 19th century,1871,1899,"Colored lacquer, gold maki-e, and inlaid pewter",H. 2 3/8 in. (6 cm); W. 10 1/2 in. (26.7 cm); L. 12 in. (30.5 cm),"Gift of Mrs. George A. Crocker (Elizabeth Masten), 1937",,,,,,,,,,,,Lacquer,,http://www.metmuseum.org/art/collection/search/58280,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JIB182,false,true,57912,Asian Art,Illustrated book,,Japan,,,,,Artist,,Utagawa Toyohiro,"Japanese, 1763–1828",,Utagawa Toyohiro,Japanese,1763,1828,1793,1793,1793,Ink on paper,13 13/16 x 9 1/16 in. (35.1 x 23 cm),"The Harry G. G. Packard Collection of Asian Art, Gift of Harry G. G. Packard and Purchase, Fletcher, Rogers, Harris Brisbane Dick and Louis V. Bell Funds, Joseph Pulitzer Bequest and The Annenberg Fund, Inc. Gift, 1975",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/57912,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1985.399.24,false,true,59676,Asian Art,Netsuke,,Japan,,,,,Artist,,Minkō,"Japanese, ca. 1735–1816",,Minkō,Japanese,1735,1816,late 18th–early 19th century,1767,1833,"Wood, horn",H. 1 1/16 in. (2.7 cm); W. 1 7/16 in. (3.7 cm),"Gift of Alvin H. Schechter, 1985",,,,,,,,,,,,Netsuke,,http://www.metmuseum.org/art/collection/search/59676,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -13.67.83,false,true,45585,Asian Art,Inrō,,Japan,Edo period (1615–1868),,,,Artist,,Yamada Jōkasai (1681–1704),,,Yamada Jōkasai,Japanese,1681,1704,second half of the 19th century,1850,1899,Gold and colored lacquer with inlaid mother-of-pearl on ro-iro black lacquer; ojime: metal bead inlaid with silver; netsuke: carved ebony inlaid with ivory design of Daruma,3 7/8 x 1 15/16 x 1 1/4 in. (9.8 x 4.9 x 3.2 cm),"Rogers Fund, 1913",,,,,,,,,,,,Inrō,,http://www.metmuseum.org/art/collection/search/45585,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JIB97,false,true,57784,Asian Art,Illustrated book,職人盡歌合|Poetry Contest by Various Artisans (Shokunin zukushi uta-awase),Japan,Edo period (1615–1868),,,,Artist,After,Tosa Mitsunobu,1434–1525,,Tosa Mitsunobu,Japanese,1434,1525,ca. 1744,1734,1754,Woodblock printed book; ink on paper,Overall: 10 1/8 × 7 3/16 × 5/8 in. (25.7 × 18.3 × 1.6 cm),"The Howard Mansfield Collection, Gift of Howard Mansfield, 1936",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/57784,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.784,false,true,78686,Asian Art,Illustrated book,,Japan,Edo period (1615–1868),,,,Artist,Attributed to,Tosa Mitsunari,1648–1710,,Tosa Mitsunari,Japanese,1648,1710,late 17th century,1675,1699,Accordion album; ink and color on silk,9 9/16 × 8 7/8 in. (24.3 × 22.5 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78686,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.817a–g,false,true,78707,Asian Art,Illustrated books,,Japan,Edo period (1615–1868),,,,Artist,,Ōoka Shunboku,1680–1763,,Ōoka Shunboku,Japanese,1680,1763,1750,1750,1750,Set of six woodblock printed books bound as one with additional volume; ink on paper,10 3/16 × 7 3/16 in. (25.8 × 18.2 cm) 10 3/16 × 7 1/16 in. (25.8 × 18 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78707,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.818a–c,false,true,78708,Asian Art,Illustrated books,,Japan,Edo period (1615–1868),,,,Artist,,Ōoka Shunboku,1680–1763,,Ōoka Shunboku,Japanese,1680,1763,ca. 1812,1807,1817,Set of three woodblock printed books; ink and color on paper,each: 10 13/16 × 7 in. (27.5 × 17.8 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78708,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.268.89,false,true,57224,Asian Art,Handscroll,,Japan,Edo period (1615–1868),,,,Artist,,Rai San'yō,1780–1832,,Rai San'yō,Japanese,1780,1832,dated 1824,1824,1824,Handscroll; ink on paper,11 7/8 x 116 3/4 in. (30.2 x 296.5 cm),"The Harry G. C. Packard Collection of Asian Art, Gift of Harry G. C. Packard, and Purchase, Fletcher, Rogers, Harris Brisbane Dick, and Louis V. Bell Funds, Joseph Pulitzer Bequest, and The Annenberg Fund Inc. Gift, 1975",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/57224,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JIB113,false,true,57806,Asian Art,Illustrated book,,Japan,Edo period (1615–1868),,,,Artist,,Ōnishi Chinnen,1792–1851,,Ōnishi Chinnen,Japanese,1792,1851,1832,1832,1832,Ink on paper,10 1/2 × 7 3/16 × 3/8 in. (26.7 × 18.3 × 1 cm),"The Howard Mansfield Collection, Gift of Howard Mansfield, 1936",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/57806,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.669,false,true,78590,Asian Art,Illustrated book,楚南画譜|Sōnan (Chinnen) Picture Album (Sōnan gafu),Japan,Edo period (1615–1868),,,,Artist,,Ōnishi Chinnen,1792–1851,,Ōnishi Chinnen,Japanese,1792,1851,1834,1834,1834,Woodblock printed book; ink and color on paper,10 13/16 × 7 5/16 in. (27.5 × 18.5 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78590,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.670,false,true,78591,Asian Art,Illustrated book,あづまの手ぶり|Customs of the Eastern Capital (Edo) (Azuma no teburi),Japan,Edo period (1615–1868),,,,Artist,,Ōnishi Chinnen,1792–1851,,Ōnishi Chinnen,Japanese,1792,1851,1829,1829,1829,Woodblock printed book; ink and color with hand-coloring (?) on paper,10 15/16 × 7 3/8 in. (27.8 × 18.8 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78591,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JIB64,false,true,57664,Asian Art,Illustrated book,,Japan,Edo period (1615–1868),,,,Artist,,Hishikawa Moronobu,"Japanese, died 1694",,Hishikawa Moronobu,Japanese,,1694,"1695, fourth month",1695,1695,Woodblock printed book; ink on paper,8 1/4 × 6 1/8 × 3/8 in. (21 × 15.6 × 1 cm),"Rogers Fund, 1918",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/57664,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JIB65,false,true,45061,Asian Art,Illustrated book,,Japan,Edo period (1615–1868),,,,Artist,,Hishikawa Moronobu,"Japanese, died 1694",,Hishikawa Moronobu,Japanese,,1694,ca. 1694,1684,1704,Woodblock printed book; ink on paper,10 5/8 × 7 1/4 × 1/8 in. (27 × 18.4 × 0.3 cm),"Rogers Fund, 1918",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/45061,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JIB66,false,true,57665,Asian Art,Illustrated book,,Japan,Edo period (1615–1868),,,,Artist,,Hishikawa Moronobu,"Japanese, died 1694",,Hishikawa Moronobu,Japanese,,1694,"1683, fifth month",1683,1683,Woodblock printed book; ink and color on paper,10 1/4 × 7 × 3/8 in. (26 × 17.8 × 1 cm),"Rogers Fund, 1918",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/57665,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JIB74,false,true,57672,Asian Art,Illustrated book,,Japan,Edo period (1615–1868),,,,Artist,,Hishikawa Moronobu,"Japanese, died 1694",,Hishikawa Moronobu,Japanese,,1694,"1685, second month",1685,1685,Woodblock printed book; ink and color on paper,10 3/8 × 7 3/8 × 3/16 in. (26.4 × 18.7 × 0.5 cm),"Rogers Fund, 1923",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/57672,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JIB75,false,true,57673,Asian Art,Illustrated book,美人絵づくし|Illustrations of Beautiful Women (Bijin e-zukushi),Japan,Edo period (1615–1868),,,,Artist,,Hishikawa Moronobu,"Japanese, died 1694",,Hishikawa Moronobu,Japanese,,1694,"1683, fifth month",1683,1683,Woodblock printed book; ink and color on paper,10 5/8 × 7 1/4 × 3/8 in. (27 × 18.4 × 1 cm),"Rogers Fund, 1923",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/57673,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JIB84,false,true,57743,Asian Art,Illustrated book,大和絵づくし|Compendium of Yamato-e Painting Themes (Yamato-e zukushi),Japan,Edo period (1615–1868),,,,Artist,,Hishikawa Moronobu,"Japanese, died 1694",,Hishikawa Moronobu,Japanese,,1694,"1686, ninth month",1686,1686,Woodblock printed book; ink on paper,10 3/4 x 7 3/8 in. (27.3 x 18.7 cm),"The Howard Mansfield Collection, Gift of Howard Mansfield, 1936",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/57743,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JIB86,false,true,57745,Asian Art,Illustrated book,築山図庭画畫 余慶作り庭の図|A Compendium of Model Gardens (Tsukiyama no zu niwa zukushi; Yokei tsukuri niwa no zu),Japan,Edo period (1615–1868),,,,Artist,,Hishikawa Moronobu,"Japanese, died 1694",,Hishikawa Moronobu,Japanese,,1694,"1691, fifth month",1691,1691,Woodblock printed book; ink on paper,10 1/2 x 7 1/2 in. (26.7 x 19.1 cm),"The Howard Mansfield Collection, Gift of Howard Mansfield, 1936",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/57745,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.887,false,true,78777,Asian Art,Illustrated book,『當世雛形』|Contemporary Kimono Patterns (Tōsei hiinagata),Japan,Edo period (1615–1868),,,,Artist,,Hishikawa Moronobu,"Japanese, died 1694",,Hishikawa Moronobu,Japanese,,1694,1677,1677,1677,Woodblock printed book; ink on paper,10 11/16 × 7 1/2 in. (27.2 × 19 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78777,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"JIB122a, b",false,true,57814,Asian Art,Illustrated book,ぶんしやう物語|The Tale of Bunshō (Bunshō monogatari),Japan,Edo period (1615–1868),,,,Artist,,Hishikawa Moronobu,"Japanese, died 1694",,Hishikawa Moronobu,Japanese,,1694,ca. 1685,1675,1695,Set of two woodblock printed books; ink on paper,Each: 8 3/4 × 6 1/8 × 1/8 in. (22.2 × 15.6 × 0.3 cm),"The Howard Mansfield Collection, Gift of Howard Mansfield, 1936",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/57814,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JIB26a–c,false,true,57559,Asian Art,Illustrated book,姿絵百人一首|Portraits for One Hundred Poems about One Hundred Poets (Sugata-e hyakunin isshu),Japan,Edo period (1615–1868),,,,Artist,,Hishikawa Moronobu,"Japanese, died 1694",,Hishikawa Moronobu,Japanese,,1694,"1695, fourth month",1695,1695,Set of three woodblock printed books; ink on paper,Each: 8 3/4 × 6 3/8 × 1/4 in. (22.2 × 16.2 × 0.6 cm),"Rogers Fund, 1918",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/57559,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JIB67a–c,false,true,57666,Asian Art,Illustrated book,美人絵づくし|Illustrations of Beautiful Women (Bijin e-zukushi),Japan,Edo period (1615–1868),,,,Artist,,Hishikawa Moronobu,"Japanese, died 1694",,Hishikawa Moronobu,Japanese,,1694,"1683, fifth month",1683,1683,Set of three woodblock printed books; ink and color on paper,Each: 10 3/8 × 7 5/16 × 1/4 in. (26.4 × 18.6 × 0.6 cm),"Rogers Fund, 1918",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/57666,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JIB85a–c,false,true,57744,Asian Art,Illustrated book,伊勢物語頭書抄|Tales of Ise with Annotations (Ise Monogatari tōsho shō),Japan,Edo period (1615–1868),,,,Artist,,Hishikawa Moronobu,"Japanese, died 1694",,Hishikawa Moronobu,Japanese,,1694,"1679, third month",1679,1679,Set of 3 woodblock printed books; ink on paper,Each: 10 1/2 × 7 3/8 × 3/8 in. (26.7 × 18.7 × 1 cm),"The Howard Mansfield Collection, Gift of Howard Mansfield, 1936",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/57744,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.786a–c,false,true,78688,Asian Art,Illustrated books,『和国諸職絵尽 諸織 絵本鏡』|A Picture Book Mirror of Various Occupations (Wakoku shoshoku ezukushi),Japan,Edo period (1615–1868),,,,Artist,,Hishikawa Moronobu,"Japanese, died 1694",,Hishikawa Moronobu,Japanese,,1694,1685,1685,1685,Set of three woodblock printed books; ink on paper,each: 10 5/8 × 7 5/16 in. (27 × 18.5 cm),"Purchase, Mary and James G. Wallach Family Foundation Gift, in honor of John T. Carpenter, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78688,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.82.3,false,true,45574,Asian Art,Inrō,,Japan,Edo period (1615–1868),,,,Artist,,Koma Kōryū,"Japanese, died 1796",,Koma Kōryū,Japanese,,1796,late 18th–19th century,1771,1899,"Roiro (waxen) lacquer with decoration in togisashi sprinkled and polished lacquer; Ojime: gold lacquer bead; Netsuke: ivory ""Dream of a Clam""",H. 3 3/16 in. (8.1 cm); W. 1 7/8 in. (4.8 cm); D. 15/16 in. ( 2.4 cm),"Gift of Wilton Lloyd-Smith and his wife, Marjorie Fleming Lloyd-Smith, 1929",,,,,,,,,,,,Inrō,,http://www.metmuseum.org/art/collection/search/45574,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.824,false,true,78714,Asian Art,Illustrated book,,Japan,Edo period (1615–1868),,,,Artist,Attributed to,Shimokōbe Shūsui,"Japanese, died 1797",,Shimokōbe Shūsui,Japanese,,1797,1797,1797,1797,Woodblock printed book; ink on paper,6 1/4 × 4 5/16 in. (15.8 × 11 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78714,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2013.697a, b",false,true,78618,Asian Art,Album,『光琳画譜』|Kōrin Picture Album (Kōrin gafu),Japan,Edo period (1615–1868),,,,Artist,,Nakamura Hōchū,"Japanese, died 1819",,Nakamura Hōchū,Japanese,,1819,after 1826 (reprinted posthumously),1827,1868,"Woodblock-printed book in two volumes (orihon, accordion-style); ink and color on paper",each: 10 5/8 × 7 11/16 in. (27 × 19.5 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78618,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2013.890a, b",false,true,78780,Asian Art,Illustrated book,,Japan,Edo period (1615–1868),,,,Artist,,Ki Chikudō,"Japanese, died 1825",,Ki Chikudō,Japanese,,1825,1815,1815,1815,Set of two woodblock printed books; ink and color on paper,10 13/16 × 6 15/16 in. (27.5 × 17.7 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78780,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.689,false,true,45562,Asian Art,Writing box,伝本阿弥光悦 橅夫蒔絵硯箱|Writing Box (Suzuribako) with Woodcutter,Japan,Edo period (1615–1868),,,,Artist,In the style of,Hon'ami Kōetsu,"Japanese, 1558–1637",,Hon'ami Kōetsu,Japanese,1558,1637,18th or early 19th century,1700,1833,Gold maki-e on black lacquer with mother-of-pearl inlay,H. 3 3/4 in. (9.5 cm); W. 9 in. (22.9 cm); L. 9 3/8 in. (23.8 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Lacquer,,http://www.metmuseum.org/art/collection/search/45562,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"1975.268.177a, b",false,true,45481,Asian Art,Basket,,Japan,Edo period (1615–1868),,,,Artist,Style of,Hon'ami Kōetsu,"Japanese, 1558–1637",,Hon'ami Kōetsu,Japanese,1558,1637,late 17th century,1667,1699,"Rattan body, gold, silver hiramaki-e",H. 4 1/8 in. (10.5 cm); W. 8 7/8 in. (22.5 cm); L. 9 7/8 in. (25.1 cm),"The Harry G. C. Packard Collection of Asian Art, Gift of Harry G. C. Packard, and Purchase, Fletcher, Rogers, Harris Brisbane Dick, and Louis V. Bell Funds, Joseph Pulitzer Bequest, and The Annenberg Fund Inc. Gift, 1975",,,,,,,,,,,,Lacquer,,http://www.metmuseum.org/art/collection/search/45481,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.753,false,true,58664,Asian Art,Inrō,馬蒔絵印籠 自適斎筆|Inrō with Two Standing Horses; Seal and Inscription (reverse),Japan,Edo period (1615–1868),,,,Artist,After,Kano Naonobu,"Japanese, 1607–1650",,Kano Naonobu,Japanese,1607,1650,mid- 18th century,1734,1766,"Four cases; lacquered wood with gold hiramaki-e, gold foil application, and mother-of-pearl inlay on black ground Netsuke: horse; ivory Ojime: iron bead with cricket and flower in gold overlay",3 1/8 x 1 7/16 x 1 in. (7.9 x 3.7 x 2.5 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Inrō,,http://www.metmuseum.org/art/collection/search/58664,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"1975.268.57, .58",false,true,45264,Asian Art,Folding screens,舞楽図屏風 ・唐獅子図屏風|Bugaku Dances (front); Chinese Lions (reverse),Japan,Edo period (1615–1868),,,,Artist,,Hanabusa Itchō,"Japanese, 1652–1724",,Hanabusa Itchō,Japanese,1652,1724,early 18th century,1700,1733,"Pair of six-panel screens; ink, color, and gold leaf on paper",Image (each screen): 72 1/8 in. x 14 ft. 9 5/8 in. (183.2 x 451.2 cm),"The Harry G. C. Packard Collection of Asian Art, Gift of Harry G. C. Packard, and Purchase, Fletcher, Rogers, Harris Brisbane Dick, and Louis V. Bell Funds, Joseph Pulitzer Bequest, and The Annenberg Fund Inc. Gift, 1975",,,,,,,,,,,,Screens,,http://www.metmuseum.org/art/collection/search/45264,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JIB100a–c,false,true,36460,Asian Art,Illustrated book,一蝶画譜|Itchō Picture Album (Itchō gafu),Japan,Edo period (1615–1868),,,,Artist,,Hanabusa Itchō,"Japanese, 1652–1724",,Hanabusa Itchō,Japanese,1652,1724,"1770, first month",1770,1770,Set of three woodblock printed books; ink on paper,Overall (vols. 1–3 each): 10 1/16 × 6 7/8 in. (25.6 × 17.5 cm),"The Howard Mansfield Collection, Gift of Howard Mansfield, 1936",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/36460,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.812,false,true,58718,Asian Art,Inrō,寒山拾得蒔絵印籠|Inrō with Rinpa Style Kanzan and Jittoku,Japan,Edo period (1615–1868),,,,Artist,School of,Ogata Kōrin,"Japanese, 1658–1716",,Kōrin,Japanese,1658,1716,first half of the 19th century,1800,1849,Three cases; lacquered wood with mother-of-pearl and pewter inlay on gold lacquer ground; Pouch: printed cotton with sarasa pattern; Ojime: metal bead,H. 2 3/16 in. (5.5 cm); W. 1 15/16 in. (4.9 cm); D. 3/4 in. (1.9 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Inrō,,http://www.metmuseum.org/art/collection/search/58718,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -26.117,true,true,44918,Asian Art,Folding screen,波濤図屏風|Rough Waves,Japan,Edo period (1615–1868),,,,Artist,,Ogata Kōrin,"Japanese, 1658–1716",,Kōrin,Japanese,1658,1716,ca. 1704–9,1694,1719,"Two-panel folding screen; ink, color, and gold leaf on paper",Image: 57 11/16 x 65 1/8 in. (146.5 x 165.4 cm) Overall: 59 1/4 x 66 1/2 in. (150.5 x 168.9 cm),"Fletcher Fund, 1926",,,,,,,,,,,,Screens,,http://www.metmuseum.org/art/collection/search/44918,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"53.7.1, .2",false,true,39664,Asian Art,Folding screen,八橋図屏風 |Irises at Yatsuhashi (Eight Bridges),Japan,Edo period (1615–1868),,,,Artist,,Ogata Kōrin,"Japanese, 1658–1716",,Kōrin,Japanese,1658,1716,after 1709,1710,1716,Pair of six-panel folding screens; ink and color on gold leaf on paper,Image (each screen): 64 7/16 in. x 11ft. 6 3/4 in. (163.7 x 352.4 cm) Overall (each screen): 70 1/2 in. x 12 ft. 2 1/4 in. (179.1 x 371.5 cm),"Purchase, Louisa Eldridge McBurney Gift, 1953",,,,,,,,,,,,Screens,,http://www.metmuseum.org/art/collection/search/39664,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"1975.268.62, .63",false,true,44896,Asian Art,Folding screen,"尾形光琳筆 松竹に鶴図屏風|Cranes, Pines, and Bamboo",Japan,Edo period (1615–1868),,,,Artist,,Ogata Kōrin,"Japanese, 1658–1716",,Kōrin,Japanese,1658,1716,late 17th century,1671,1699,Pair of folding screens; ink and light color on paper,Right screen (4-panel): 65 3/4 x 101 1/4 in. (167 x 257.2 cm) Left screen (6-panel): 65 3/4 x 151 1/4 in. (167 x 384.2 cm),"The Harry G. C. Packard Collection of Asian Art, Gift of Harry G. C. Packard, and Purchase, Fletcher, Rogers, Harris Brisbane Dick, and Louis V. Bell Funds, Joseph Pulitzer Bequest, and The Annenberg Fund Inc. Gift, 1975",,,,,,,,,,,,Screens,,http://www.metmuseum.org/art/collection/search/44896,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"JIB124a, b",false,true,57816,Asian Art,Illustrated books,池田孤邨画 『光琳新選百図』|One Hundred Newly Selected Designs by Kōrin (Kōrin shinsen hyakuzu),Japan,Edo period (1615–1868),,,,Artist,,Ogata Kōrin,"Japanese, 1658–1716",,Kōrin,Japanese,1658,1716,1864,1864,1864,Two volumes of Woodblock printed books; ink on paper,Overall (each): 10 1/16 x 7 3/8 x 3/8 in. (25.6 x 18.7 x 1 cm),"The Howard Mansfield Collection, Gift of Howard Mansfield, 1936",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/57816,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -66.149,false,true,45729,Asian Art,Screen,,Japan,Edo period (1615–1868),,,,Artist,,Kano Chikanobu,"Japanese, 1660–1728",,Kano Chikanobu,Japanese,1660,1728,17th–18th century,1600,1799,Six-panel folding screen; ink and color on silk,Image: 61 1/8 x 139 3/4 in. (155.3 x 355 cm),"Gift of Richard W. Courts, in memory of Mr. and Mrs. Thomas J. Watson, 1966",,,,,,,,,,,,Screens,,http://www.metmuseum.org/art/collection/search/45729,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.498,false,true,45217,Asian Art,Screen,,Japan,Edo period (1615–1868),,,,Artist,,Kano Chikanobu,"Japanese, 1660–1728",,Kano Chikanobu,Japanese,1660,1728,17th–18th century,1600,1799,"One of a pair of six-panel folding screens; ink, color, and gilt on paper; Reverse side: ink, color, and gold on paper",69 1/4 x 153 in. (175.9 x 388.6 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Screens,,http://www.metmuseum.org/art/collection/search/45217,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.702,false,true,58238,Asian Art,Writing box,,Japan,Edo period (1615–1868),,,,Artist,,Ogawa Haritsu (Ritsuō),"Japanese, 1663–1747",,Ritsuō,Japanese,1663,1747,17th–18th century,1600,1799,Lacquer with design in pottery and pewter,H. 1 5/8 in. (4.1 cm); W. 8 3/4 in. (22.2 cm); L. 9 1/4 in. (23.5 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Lacquer,,http://www.metmuseum.org/art/collection/search/58238,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JIB34,false,true,45356,Asian Art,Illustrated book,Onna Ichidai Fūzoku Ehon Masukagami|絵本十寸鑑|True Reflections on the Life and Manners of a Woman,Japan,Edo period (1615–1868),,,,Artist,,Nishikawa Sukenobu,"Japanese, 1671–1750",,Nishikawa Sukenobu,Japanese,1671,1750,January 1748,1748,1748,Monochrome woodblock printed book; ink on paper,Overall: 8 3/4 × 6 1/4 in. (22.2 × 15.9 cm),"Rogers Fund, 1918",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/45356,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JIB35,false,true,53815,Asian Art,Illustrated book,Ehon Asakayama|絵本浅香山|Picture Book: Mount Asaka (Ehon Asakayama),Japan,Edo period (1615–1868),,,,Artist,,Nishikawa Sukenobu,"Japanese, 1671–1750",,Nishikawa Sukenobu,Japanese,1671,1750,January 1739,1739,1739,Bound book of monochrome woodblock prints; ink on paper,Overall: 10 3/8 × 7 in. (26.4 × 17.8 cm),"Rogers Fund, 1918",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/53815,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JIB90,false,true,57756,Asian Art,Illustrated book,,Japan,Edo period (1615–1868),,,,Artist,,Nishikawa Sukenobu,"Japanese, 1671–1750",,Nishikawa Sukenobu,Japanese,1671,1750,1740,1740,1740,Woodblock print; ink on paper,Overall: 9 x 6 1/4 in. (22.9 x 15.9 cm),"The Howard Mansfield Collection, Gift of Howard Mansfield, 1936",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/57756,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JIB99,false,true,45062,Asian Art,Illustrated book,"絵本末摘花|Picture Book: Flowers Yet to be Picked, Vol. 1",Japan,Edo period (1615–1868),,,,Artist,,Nishikawa Sukenobu,"Japanese, 1671–1750",,Nishikawa Sukenobu,Japanese,1671,1750,1756,1615,1868,Woodblock printed book; ink and color on paper,Overall: 9 × 6 in. (22.9 × 15.2 cm),"The Howard Mansfield Collection, Gift of Howard Mansfield, 1936",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/45062,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JIB151,false,true,57842,Asian Art,Illustrated book,Ehon Ogurayama|絵本小倉山|Picture Book: Ogura Hill (Ehon Ogurayama),Japan,Edo period (1615–1868),,,,Artist,,Nishikawa Sukenobu,"Japanese, 1671–1750",,Nishikawa Sukenobu,Japanese,1671,1750,1749,1749,1749,Thirteen double-page monochrome illustrations; ink on paper,Overall: 8 7/8 × 6 1/4 in. (22.5 × 15.9 cm),"Bequest of W. Gedney Beatty, 1941",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/57842,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JIB152,false,true,57843,Asian Art,Illustrated book,Ehon Kai kasen|絵本貝歌仙|Illustrated Poems,Japan,Edo period (1615–1868),,,,Artist,,Nishikawa Sukenobu,"Japanese, 1671–1750",,Nishikawa Sukenobu,Japanese,1671,1750,1747,1747,1747,Nineteen pages of illustrations and poems; ink on paper,Overall: 8 5/8 × 6 3/8 in. (21.9 × 16.2 cm),"Bequest of W. Gedney Beatty, 1941",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/57843,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JIB153,false,true,57844,Asian Art,Illustrated book,,Japan,Edo period (1615–1868),,,,Artist,,Nishikawa Sukenobu,"Japanese, 1671–1750",,Nishikawa Sukenobu,Japanese,1671,1750,1741,1741,1741,Ink on paper,Overall: 10 3/4 x 7 1/2in. (27.3 x 19.1cm),"Bequest of W. Gedney Beatty, 1941",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/57844,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JIB163,false,true,57854,Asian Art,Illustrated book,,Japan,Edo period (1615–1868),,,,Artist,,Nishikawa Sukenobu,"Japanese, 1671–1750",,Nishikawa Sukenobu,Japanese,1671,1750,1731,1731,1731,"One volume, black and white, twenty-five double-page sheets; ink on paper",Overall: 10 1/4 x 6 1/2in. (26 x 16.5cm),"Gift of Mrs. Henry J. Bernheim, 1945",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/57854,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JIB167,false,true,57858,Asian Art,Illustrated book,Ehon Himetsubaki|繪本女貞木|Picture Book: Camellia (Ehon Himetsubaki),Japan,Edo period (1615–1868),,,,Artist,,Nishikawa Sukenobu,"Japanese, 1671–1750",,Nishikawa Sukenobu,Japanese,1671,1750,1745(?),1745,1745,Bound book of monochrome woodblock prints; ink on paper,Overall: 10 1/2 x 7in. (26.7 x 17.8cm),"Gift of Mrs. Morris Manges, in memory of her husband, Dr. Morris Manges, 1947",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/57858,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.833,false,true,78723,Asian Art,Illustrated book,『繪本常盤草』|Picture Book of the Evergreens (Ehon tokiwagusa),Japan,Edo period (1615–1868),,,,Artist,,Nishikawa Sukenobu,"Japanese, 1671–1750",,Nishikawa Sukenobu,Japanese,1671,1750,1731,1731,1731,Woodblock printed book; ink and hand-coloring on paper,10 7/8 × 7 9/16 in. (27.7 × 19.2 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78723,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.834,false,true,78724,Asian Art,Illustrated book,,Japan,Edo period (1615–1868),,,,Artist,,Nishikawa Sukenobu,"Japanese, 1671–1750",,Nishikawa Sukenobu,Japanese,1671,1750,1739,1739,1739,Woodblock printed book; ink on paper,10 1/2 × 7 3/16 in. (26.7 × 18.3 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78724,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.835,false,true,78725,Asian Art,Illustrated books,,Japan,Edo period (1615–1868),,,,Artist,,Nishikawa Sukenobu,"Japanese, 1671–1750",,Nishikawa Sukenobu,Japanese,1671,1750,1740,1740,1740,Set of three woodblock printed books bound as one volume; ink on paper,10 5/8 × 7 1/2 in. (27 × 19 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78725,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.836,false,true,78726,Asian Art,Illustrated book,,Japan,Edo period (1615–1868),,,,Artist,,Nishikawa Sukenobu,"Japanese, 1671–1750",,Nishikawa Sukenobu,Japanese,1671,1750,1746,1746,1746,Woodblock printed book; ink on paper,9 1/8 × 6 7/16 in. (23.2 × 16.3 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78726,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.838,false,true,78728,Asian Art,Illustrated books,『繪本小倉山』|Picture Book of Ogura Hill (Ehon ogurayama),Japan,Edo period (1615–1868),,,,Artist,,Nishikawa Sukenobu,"Japanese, 1671–1750",,Nishikawa Sukenobu,Japanese,1671,1750,1749,1749,1749,Set of three woodblock-printed books bound as one volume; ink on paper,each: 9 1/16 × 6 1/2 in. (23 × 16.5 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78728,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.839,false,true,78729,Asian Art,Illustrated book,『繪本小松原』|Picture Book of Komatsubara (Ehon komatsubara),Japan,Edo period (1615–1868),,,,Artist,,Nishikawa Sukenobu,"Japanese, 1671–1750",,Nishikawa Sukenobu,Japanese,1671,1750,1761,1761,1761,Woodblock-printed book; ink on paper,10 5/8 × 7 1/2 in. (27 × 19 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78729,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.832a–c,false,true,78722,Asian Art,Illustrated book,絵本常盤草 上・中・下|Picture Book of the Evergreens (Ehon tokiwagusa),Japan,Edo period (1615–1868),,,,Artist,,Nishikawa Sukenobu,"Japanese, 1671–1750",,Nishikawa Sukenobu,Japanese,1671,1750,1731,1731,1731,Woodblock-printed book; ink and hand-coloring on paper,10 3/4 × 7 1/2 in. (27.3 × 19 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78722,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.837a–c,false,true,78727,Asian Art,Illustrated books,,Japan,Edo period (1615–1868),,,,Artist,,Nishikawa Sukenobu,"Japanese, 1671–1750",,Nishikawa Sukenobu,Japanese,1671,1750,1748,1748,1748,Set of three woodblock printed books; ink on paper,each: 8 7/8 × 6 5/16 in. (22.5 × 16 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78727,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.268.88,false,true,44864,Asian Art,Overrobe,白繻子地墨竹図打掛 祇園南海筆|Overrobe (Uchikake) with Bamboo,Japan,Edo period (1615–1868),,,,Artist,,Gion Nankai,"Japanese, 1677–1751",,Gion Nankai,Japanese,1677,1751,first half of the 18th century,1700,1749,Ink and gold powder on silk satin,Overall: 64 3/4 x 48 7/8 in. (164.5 x 124.2 cm) Sleeve length: 37 3/4 in. (95.9 cm); sleeve width: 12 3/4 in. (32.4 cm),"The Harry G. C. Packard Collection of Asian Art, Gift of Harry G. C. Packard, and Purchase, Fletcher, Rogers, Harris Brisbane Dick, and Louis V. Bell Funds, Joseph Pulitzer Bequest, and The Annenberg Fund Inc. Gift, 1975",,,,,,,,,,,,Costumes,,http://www.metmuseum.org/art/collection/search/44864,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"JIB121a, b",false,true,57813,Asian Art,Illustrated book,唐土訓蒙図彙|Illustrated Encyclopedia of China (Morokoshi kinmō zui),Japan,Edo period (1615–1868),,,,Artist,,Tachibana Morikuni,"Japanese, 1679–1748",,Tachibana Morikuni,Japanese,1679,1748,1719; preface dated 1718,1718,1719,Set of two woodblock printed books; ink on paper,Overall (vol. 1): 11 1/16 × 10 3/16 × 9/16 in. (28.1 × 25.8 × 1.5 cm) Overall (vol. 2): 11 1/16 × 10 1/8 × 13/16 in. (28.1 × 25.7 × 2 cm),"The Howard Mansfield Collection, Gift of Howard Mansfield, 1936",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/57813,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.785a–c,false,true,78687,Asian Art,Illustrated books,,Japan,Edo period (1615–1868),,,,Artist,,Tachibana Morikuni,"Japanese, 1679–1748",,Tachibana Morikuni,Japanese,1679,1748,1749,1749,1749,Set of three woodblock printed books; ink on paper,each: 10 1/16 × 7 1/16 in. (25.5 × 18 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78687,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2014.768.2,false,true,77197,Asian Art,Hanging scroll,大字「悳」|Virtue,Japan,Edo period (1615–1868),,,,Artist,,Hakuin Ekaku,"Japanese, 1685–1768",,Hakuin Ekaku,Japanese,1685,1768,mid-18th century,1734,1766,Hanging scroll; ink on paper,Image: 41 3/4 × 20 1/2 in. (106 × 52 cm) Overall with mounting: 73 1/8 × 25 3/16 in. (185.8 × 64 cm) Overall with knobs: 73 1/8 × 27 3/8 in. (185.8 × 69.5 cm),"Fishbein-Bender Collection, Gift of T. Richard Fishbein and Estelle P. Bender, 2014",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/77197,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.883a–d,false,true,78773,Asian Art,Illustrated books,,Japan,Edo period (1615–1868),,,,Artist,,Tsukioka Settei,"Japanese, 1710–1786",,Tsukioka Settei,Japanese,1710,1786,1764,1764,1764,Set of four woodblock printed books; ink and color (vol. 5 only) on paper,10 5/8 × 7 1/2 in. (27 × 19 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78773,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.803,false,true,78693,Asian Art,Illustrated books,,Japan,Edo period (1615–1868),,,,Artist,,Toriyama Sekien,"Japanese, 1712–1788",,Toriyama Sekien,Japanese,1712,1788,1781,1781,1781,Set of three woodblock printed books bound as one volume; ink on paper,8 7/8 × 6 5/16 in. (22.5 × 16 cm),"Purchase, Mary and James G. Wallach Family Foundation Gift, in honor of John T. Carpenter, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78693,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.814a–c,false,true,78704,Asian Art,Illustrated books,,Japan,Edo period (1615–1868),,,,Artist,,Sō Shiseki,"Japanese, 1715–1786",,Sō Shiseki,Japanese,1715,1786,1764,1764,1764,"Set of three woodblock printed books; ink, color, and hand-coloring (vol. 2) on paper",each: 10 1/2 × 6 3/4 in. (26.7 × 17.2 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78704,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.665,false,true,78586,Asian Art,Illustrated book,俳諧三十六歌僊|The Thirty-six Immortals of Haikai Verse (Haikai sanjūrokkasen),Japan,Edo period (1615–1868),,,,Artist,,Yosa Buson,"Japanese, 1716–1783",,Yosa Buson,Japanese,1716,1783,1799,1799,1799,Woodblock printed book; ink with hand-coloring on paper,10 5/8 × 7 1/2 in. (27 × 19 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78586,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.666,false,true,78587,Asian Art,Illustrated book,三十六歌仙|The Thirty-six Immortals of Poetry (Sanjūrokkasen),Japan,Edo period (1615–1868),,,,Artist,,Yosa Buson,"Japanese, 1716–1783",,Yosa Buson,Japanese,1716,1783,1799,1799,1799,Woodblock printed book; ink on paper,10 15/16 × 7 1/2 in. (27.8 × 19 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78587,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.751,false,true,78653,Asian Art,Illustrated books,,Japan,Edo period (1615–1868),,,,Artist,,Kanyōsai,"Japanese, 1719–1774",,Kanyōsai,Japanese,1719,1774,1762,1762,1762,Set of three woodblock printed books; ink on paper,each: 10 13/16 × 6 15/16 in. (27.5 × 17.7 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78653,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.752a–e,false,true,78654,Asian Art,Illustrated books,,Japan,Edo period (1615–1868),,,,Artist,,Kanyōsai,"Japanese, 1719–1774",,Kanyōsai,Japanese,1719,1774,1762,1762,1762,Set of five woodblock printed books; ink on paper,each: 10 3/8 × 7 1/16 in. (26.3 × 18 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78654,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.884,false,true,78774,Asian Art,Illustrated book,,Japan,Edo period (1615–1868),,,,Artist,,Suzuki Harunobu,"Japanese, 1725–1770",,Suzuki Harunobu,Japanese,1725,1770,ca. 1767,1761,1771,Woodblock printed book; ink and color on paper,6 1/4 × 8 5/16 in. (15.9 × 21.1 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78774,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JIB7a–c,false,true,57542,Asian Art,Illustrated book,,Japan,Edo period (1615–1868),,,,Artist,,Suzuki Harunobu,"Japanese, 1725–1770",,Suzuki Harunobu,Japanese,1725,1770,1788,1788,1788,Three volumes; ink on paper,Each: 9 × 6 1/4 × 1/16 in. (22.9 × 15.9 × 0.2 cm),"Rogers Fund, 1918",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/57542,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.685a–c,false,true,78606,Asian Art,Illustrated books,,Japan,Edo period (1615–1868),,,,Artist,,Suzuki Harunobu,"Japanese, 1725–1770",,Suzuki Harunobu,Japanese,1725,1770,1763,1763,1763,Set of three woodblock printed books; ink on paper,each: 8 7/8 × 6 5/16 in. (22.5 × 16 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78606,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.687a–c,false,true,78608,Asian Art,Illustrated books,,Japan,Edo period (1615–1868),,,,Artist,,Suzuki Harunobu,"Japanese, 1725–1770",,Suzuki Harunobu,Japanese,1725,1770,ca. 1767,1761,1771,Set of three woodblock printed books; ink on paper,each: 12 3/16 × 8 1/4 in. (31 × 21 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78608,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.821,false,true,78711,Asian Art,Illustrated book,『錦百人一首東織』|Eastern Brocade of One Hundred Poems by One Hundred Poets (Nishiki hyakunin isshu azuma-ori),Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,1775,1775,1775,Woodblock printed book; ink and color on paper,11 5/8 × 7 11/16 in. (29.5 × 19.5 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78711,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.823,false,true,78713,Asian Art,illustrated book,"『役者夏の富士』|Actors [Out of Costume] Like Mount Fuji [Without Snow] in Summer (Yakusha natsu no Fuji), by Ichiba Tsūshō",Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,ca. 1830,1825,1835,Woodblock printed book; ink on paper,8 9/16 × 6 1/8 in. (21.8 × 15.5 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78713,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1994.566,false,true,40022,Asian Art,Screen,近江八景|Eight Views of Ōmi (Ōmi hakkei),Japan,Edo period (1615–1868),,,,Artist,After,Soga Shōhaku,"Japanese, 1730–1781",,Soga Shōhaku,Japanese,1730,1781,Probably late 18th or early 19th century,1700,1833,Six-panel folding screen; ink and paper,Image: 46 in. x 13 ft. (116.8 x 396.2 cm),"Gift of William and Marjorie Normand, 1994",,,,,,,,,,,,Screens,,http://www.metmuseum.org/art/collection/search/40022,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -57.156.1,false,true,45422,Asian Art,Screen,,Japan,Edo period (1615–1868),,,,Artist,,Maruyama Ōkyo,"Japanese, 1733–1795",,Maruyama Ōkyo,Japanese,1733,1795,dated 1773,1773,1773,Two-panel folding screen; ink and color on paper,Image: 22 x 53 3/8 in. (55.9 x 135.6 cm) Overall: 29 1/4 x 74 in. (74.3 x 188 cm),"Seymour Fund, 1957",,,,,,,,,,,,Screens,,http://www.metmuseum.org/art/collection/search/45422,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.792,false,true,78808,Asian Art,Illustrated book,,Japan,Edo period (1615–1868),,,,Artist,After,Maruyama Ōkyo,"Japanese, 1733–1795",,Maruyama Ōkyo,Japanese,1733,1795,1850,1850,1850,"Woodblock printed book (orihon, accordion-style); ink and color on paper",11 3/16 × 7 9/16 in. (28.4 × 19.2 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78808,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2013.791a, b",false,true,78807,Asian Art,Illustrated books,,Japan,Edo period (1615–1868),,,,Artist,After,Maruyama Ōkyo,"Japanese, 1733–1795",,Maruyama Ōkyo,Japanese,1733,1795,1837,1837,1837,Set of two woodblock printed books; ink and color on paper,each: 10 1/2 × 7 5/16 in. (26.7 × 18.5 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78807,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.654,false,true,78575,Asian Art,Illustrated book,九老画譜|Kyūrō (Baitei) Picture Album (Kyūrō gafu),Japan,Edo period (1615–1868),,,,Artist,,Ki Baitei,"Japanese, 1734–1810",,Ki Baitei,Japanese,1734,1810,preface and postscript dated 1797,1797,1797,Woodblock printed book; ink on paper,10 3/16 × 7 1/16 in. (25.8 × 18 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78575,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.807,false,true,78697,Asian Art,Illustrated book,閨暦大雑書玉門大成|Erotica; Compendium Guide to the Brothels of Osaka (Keiryaku ōzassho gyokumon taisei),Japan,Edo period (1615–1868),,,,Artist,,Kitao Shigemasa,"Japanese, 1739–1820",,Kitao Shigemasa,Japanese,1739,1820,ca. 1770,1765,1775,Woodblock printed book; ink and color on paper,10 9/16 × 7 5/16 in. (26.8 × 18.5 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78697,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.808,false,true,78698,Asian Art,Illustrated book,,Japan,Edo period (1615–1868),,,,Artist,,Kitao Shigemasa,"Japanese, 1739–1820",,Kitao Shigemasa,Japanese,1739,1820,1787,1787,1787,Woodblock printed book; ink and color on paper,10 7/16 × 7 1/16 in. (26.5 × 18 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78698,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.809a–c,false,true,78699,Asian Art,Illustrated books,,Japan,Edo period (1615–1868),,,,Artist,,Kitao Shigemasa,"Japanese, 1739–1820",,Kitao Shigemasa,Japanese,1739,1820,1802,1802,1802,Set of three woodblock printed books; ink and color on paper,each: 8 7/8 × 6 1/8 in. (22.5 × 15.5 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78699,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.810a–c,false,true,78700,Asian Art,Illustrated books,,Japan,Edo period (1615–1868),,,,Artist,,Kitao Shigemasa,"Japanese, 1739–1820",", et al",Kitao Shigemasa,Japanese,1739,1820,1805,1805,1805,Set of three woodblock printed books; ink on paper,each: 8 7/8 × 6 1/4 in. (22.5 × 15.8 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78700,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.820a–d,false,true,78710,Asian Art,Illustrated books,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunkō,"Japanese, 1743–1812",,Katsukawa Shunkō,Japanese,1743,1812,1847,1847,1847,Set of four woodblock printed books; ink and color on paper,9 1/16 × 6 5/16 in. (23 × 16 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78710,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.340a–f,false,true,73593,Asian Art,Illustrated book,,Japan,Edo period (1615–1868),,,,Artist,,Shiba Kōkan,"Japanese, 1747–1818",,Shiba Kōkan,Japanese,1747,1818,1803,1803,1803,Five volumes of woodblock printed books; ink on paper,Overall (each volume): 10 1/8 x 7 1/8 x 1/4 in. (25.7 x 18.1 x 0.6 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/73593,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.679,false,true,78600,Asian Art,Illustrated book,,Japan,Edo period (1615–1868),,,,Artist,,Suzuki Fuyō,"Japanese, 1749–1816",,Suzuki Fuyō,Japanese,1749,1816,1809,1809,1809,Set of three woodblock printed books bound as a single volume; ink on paper,10 13/16 × 7 5/16 in. (27.5 × 18.5 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78600,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JIB6,false,true,57541,Asian Art,Illustrated book,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyonaga,"Japanese, 1752–1815",,Torii Kiyonaga,Japanese,1752,1815,ca. 1790,1780,1800,Ink and color on paper,10 × 7 1/2 × 3/8 in. (25.4 × 19.1 × 1 cm),"Rogers Fund, 1918",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/57541,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"JIB22a, b",false,true,45271,Asian Art,Illustrated book,Ehon monomi ga oka|Watchtower Hill,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyonaga,"Japanese, 1752–1815",,Torii Kiyonaga,Japanese,1752,1815,1785,1600,1868,Two volumes; polychrome woodblock printed book; ink on paper,Each: 8 3/4 × 6 3/8 × 1/4 in. (22.2 × 16.2 × 0.6 cm),"Rogers Fund, 1918",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/45271,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.656,false,true,78577,Asian Art,Illustrated book,胸中山|Mountains of the Heart (Kyōchūzan),Japan,Edo period (1615–1868),,,,Artist,,Kameda Bōsai,"Japanese, 1752–1826",,Kameda Bōsai,Japanese,1752,1826,1816,1816,1816,Woodblock printed book; ink and color on paper,10 15/16 × 7 3/8 in. (27.8 × 18.7 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78577,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.657,false,true,78578,Asian Art,Illustrated book,胸中山|Mountains of the Heart (Kyōchūzan),Japan,Edo period (1615–1868),,,,Artist,,Kameda Bōsai,"Japanese, 1752–1826",,Kameda Bōsai,Japanese,1752,1826,1816,1816,1816,Woodblock printed book; ink and color on paper,10 1/4 × 6 13/16 in. (26 × 17.3 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78578,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"1975.268.75, .76",false,true,39658,Asian Art,Screen,山水唐人物図屏風|Landscapes with the Chinese Literati Su Shi and Tao Qian,Japan,Edo period (1615–1868),,,,Artist,,Nagasawa Rosetsu,"Japanese, 1754–1799",,Nagasawa Rosetsu,Japanese,1754,1799,1795–99,1795,1799,Pair of six-panel folding screens; ink on gold leaf on paper,Image (each screen): 67 3/8 in. x 12 ft. 2 3/4 in. (171.1 x 372.7 cm),"The Harry G. C. Packard Collection of Asian Art, Gift of Harry G. C. Packard, and Purchase, Fletcher, Rogers, Harris Brisbane Dick, and Louis V. Bell Funds, Joseph Pulitzer Bequest, and The Annenberg Fund Inc. Gift, 1975",,,,,,,,,,,,Screens,,http://www.metmuseum.org/art/collection/search/39658,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.828,false,true,78718,Asian Art,Illustrated book,,Japan,Edo period (1615–1868),,,,Artist,,Yamaguchi Soken,"Japanese, 1759–1818",,Yamaguchi Soken,Japanese,1759,1818,1807,1807,1807,Woodblock printed book; ink on paper,Other: 10 3/8 × 7 3/8 in. (26.3 × 18.8 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78718,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2013.829a, b",false,true,78719,Asian Art,Illustrated books,,Japan,Edo period (1615–1868),,,,Artist,,Yamaguchi Soken,"Japanese, 1759–1818",,Yamaguchi Soken,Japanese,1759,1818,1818,1818,1818,Set of two woodblock printed books; ink and hand-coloring (vol. 2) on paper,each: 10 1/16 × 7 3/16 in. (25.5 × 18.2 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78719,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JIB91,false,true,45278,Asian Art,Illustrated book,「吉原傾城」新美人合自筆鏡|Yoshiwara Courtesans: A New Mirror Comparing the Calligraphy of Beauties (Yoshiwara keisei: Shin bijin awase jihitsu kagami),Japan,Edo period (1615–1868),,,,Artist,,Kitao Masanobu (Santō Kyōden),"Japanese, 1761–1816",,Kitao Masanobu (Santō Kyōden),Japanese,1761,1816,"1784, first month",1784,1784,Polychrome woodblock printed book; ink and color on paper,Overall: 14 7/8 × 10 3/16 in. (37.8 × 25.8 cm),"The Howard Mansfield Collection, Gift of Howard Mansfield, 1936",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/45278,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JIB101,false,true,57786,Asian Art,Illustrated book,,Japan,Edo period (1615–1868),,,,Artist,,Kitao Masanobu (Santō Kyōden),"Japanese, 1761–1816",,Kitao Masanobu (Santō Kyōden),Japanese,1761,1816,1844,1844,1844,Two volumes; ink on paper,Each: 11 1/8 × 7 3/4 × 3/8 in. (28.3 × 19.7 × 1 cm),"The Howard Mansfield Collection, Gift of Howard Mansfield, 1936",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/57786,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.768,false,true,78670,Asian Art,Illustrated book,『吉原傾城新美人自筆鏡』|New Mirror Comparing the Handwriting of the Courtesans of the Yoshiwara (Yoshiwara keisei shin bijin jihitsu kagami),Japan,Edo period (1615–1868),,,,Artist,,Kitao Masanobu (Santō Kyōden),"Japanese, 1761–1816",,Kitao Masanobu (Santō Kyōden),Japanese,1761,1816,1784,1784,1784,Woodblock printed book; ink and color on paper,14 15/16 × 10 1/4 in. (38 × 26 cm),"Purchase, Mary and James G. Wallach Family Foundation Gift, in honor of John T. Carpenter, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78670,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -57.156.3,false,true,45392,Asian Art,Folding screen,酒井抱一筆 柿図屏風|The Persimmon Tree,Japan,Edo period (1615–1868),,,,Artist,,Sakai Hōitsu,"Japanese, 1761–1828",,Sakai Hōitsu,Japanese,1761,1828,1816,1816,1816,Two-panel folding screen; ink and color on paper,Image: 56 9/16 x 56 5/8 in. (143.7 x 143.8 cm) Overall: 65 1/4 x 64 in. (165.7 x 162.6 cm),"Rogers Fund, 1957",,,,,,,,,,,,Screens,,http://www.metmuseum.org/art/collection/search/45392,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JIB57,false,true,57657,Asian Art,Illustrated book,酒井抱一画 『乾山遺墨』|Ink Traces of Kenzan (Kenzan iboku),Japan,Edo period (1615–1868),,,,Artist,,Sakai Hōitsu,"Japanese, 1761–1828",,Sakai Hōitsu,Japanese,1761,1828,1823,1823,1823,Woodblock printed book; ink and color on paper,Image: 9 3/4 x 6 15/16 x 1/4 in. (24.8 x 17.7 x 0.7 cm) Overall (open): 9 3/4 x 13 in. (24.8 x 33 cm),"Rogers Fund, 1918",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/57657,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JIB58,false,true,57658,Asian Art,Illustrated book,酒井抱一 画 『鶯邨画譜』|Ōson (Hōitsu) Picture Album (Ōson gafu),Japan,Edo period (1615–1868),,,,Artist,,Sakai Hōitsu,"Japanese, 1761–1828",,Sakai Hōitsu,Japanese,1761,1828,1817,1817,1817,Woodblock printed book; ink and color on paper,Image: 10 7/8 x 7 3/8 x 1/2 in. (27.7 x 18.8 x 1.2 cm) Overall: 14 in. (35.5 cm) (open),"Rogers Fund, 1918",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/57658,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JIB95,false,true,57776,Asian Art,Illustrated book,乾山遺墨|Ink Traces of Kenzan (Kenzan iboku),Japan,Edo period (1615–1868),,,,Artist,,Sakai Hōitsu,"Japanese, 1761–1828",,Sakai Hōitsu,Japanese,1761,1828,1823,1823,1823,Woodblock printed book; ink and color on paper,Image: 9 5/8 x 6 7/8 x 1/4 in. (24.4 x 17.5 x 0.7 cm) Overall (Open): 9 5/8 x 13 in. (24.4 x 33 cm),"The Howard Mansfield Collection, Gift of Howard Mansfield, 1936",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/57776,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JIB116,false,true,57809,Asian Art,Illustrated book,池田孤邨画 『抱一上人真蹟鏡』|Ōson (Hōitsu) Picture Album (Ōson gafu),Japan,Edo period (1615–1868),,,,Artist,,Sakai Hōitsu,"Japanese, 1761–1828",,Sakai Hōitsu,Japanese,1761,1828,1817,1817,1817,Woodblock printed book; ink and color on paper,Image: 10 15/16 x 7 3/8 x 1/16 in. (27.8 x 18.8 x 0.1 cm) Overall (Open): 10 15/16 x 13 7/8 in. (27.8 x 35.3 cm),"The Howard Mansfield Collection, Gift of Howard Mansfield, 1936",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/57809,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JIB37,false,true,45272,Asian Art,Illustrated book,Edo hakkei|Eight Views of Edo,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Toyohiro,"Japanese, 1763–1828",,Utagawa Toyohiro,Japanese,1763,1828,ca. 1771,1761,1781,Bound book of polychrome woodblock prints; ink and color on paper,Overall: 9 3/4 x 14 7/8 in. (24.8 x 37.8 cm),"Rogers Fund, 1918",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/45272,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.664,false,true,78585,Asian Art,Illustrated book,『寫山樓畫本』|Shazanrō (Bunchō) Picture Book (Shazanrō ehon),Japan,Edo period (1615–1868),,,,Artist,,Tani Bunchō,"Japanese, 1763–1840",,Tani Bunchō,Japanese,1763,1840,1817,1817,1817,Woodblock-printed book; ink and color on paper,10 13/16 × 7 3/8 in. (27.5 × 18.8 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78585,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.776a–d,false,true,78678,Asian Art,Illustrated books,,Japan,Edo period (1615–1868),,,,Artist,,Kuwagata Keisai,"Japanese, 1764–1824",,Kuwagata Keisai,Japanese,1764,1824,ca. 1800,1790,1810,Four woodblocks for printed books,framed each: 13 3/4 × 9 1/16 in. (35 × 23 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Woodblocks,,http://www.metmuseum.org/art/collection/search/78678,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JIB21,false,true,57554,Asian Art,Illustrated book,略画式 (人物)|Abbreviated Drawing Styles (Ryakuga shiki)(Figures),Japan,Edo period (1615–1868),,,,Artist,,Kuwagata Keisai,"Japanese, 1764–1824",,Kuwagata Keisai,Japanese,1764,1824,"1795, twelfth month",1795,1795,Woodblock printed book; ink and color on paper,Overall: 9 3/4 × 6 15/16 in. (24.7 × 17.6 cm),"Rogers Fund, 1923",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/57554,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JIB55,false,true,57655,Asian Art,Illustrated book,鳥獣略画式|Abbreviated Drawing Styles for Birds and Animals (Chōjū ryakuga shiki),Japan,Edo period (1615–1868),,,,Artist,,Kuwagata Keisai,"Japanese, 1764–1824",,Kuwagata Keisai,Japanese,1764,1824,1797,1797,1797,Woodblock printed book; ink and color on paper,Overall: 10 1/8 × 7 in. (25.7 × 17.8 cm),"Rogers Fund, 1918",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/57655,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JIB56,false,true,57656,Asian Art,Illustrated book,人物略画式|Abbreviated Drawing Styles for Figures (Jinbutsu ryakuga shiki),Japan,Edo period (1615–1868),,,,Artist,,Kuwagata Keisai,"Japanese, 1764–1824",,Kuwagata Keisai,Japanese,1764,1824,1795,1795,1795,Woodblock printed book; ink and color on paper,Overall: 11 × 7 3/8 in. (28 × 18.8 cm),"Rogers Fund, 1918",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/57656,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.770,false,true,78672,Asian Art,Illustrated book,,Japan,Edo period (1615–1868),,,,Artist,,Kuwagata Keisai,"Japanese, 1764–1824",,Kuwagata Keisai,Japanese,1764,1824,1795,1795,1795,Woodblock printed book; ink and color on paper,10 9/16 × 7 5/16 in. (26.8 × 18.5 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78672,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.771,false,true,78673,Asian Art,Illustrated book,,Japan,Edo period (1615–1868),,,,Artist,,Kuwagata Keisai,"Japanese, 1764–1824",,Kuwagata Keisai,Japanese,1764,1824,1797,1797,1797,Woodblock printed book; ink and color on paper,10 7/16 × 7 3/16 in. (26.5 × 18.2 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78673,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.772,false,true,78674,Asian Art,Illustrated books,,Japan,Edo period (1615–1868),,,,Artist,,Kuwagata Keisai,"Japanese, 1764–1824",,Kuwagata Keisai,Japanese,1764,1824,1799,1799,1799,Set of three woodblock printed books; ink and color on paper,10 1/2 × 7 3/16 in. (26.7 × 18.3 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78674,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.773,false,true,78675,Asian Art,Illustrated book,,Japan,Edo period (1615–1868),,,,Artist,,Kuwagata Keisai,"Japanese, 1764–1824",,Kuwagata Keisai,Japanese,1764,1824,dated 1812,1812,1812,Woodblock printed book; ink and color on paper,9 3/16 × 6 9/16 in. (23.3 × 16.6 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78675,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.774,false,true,78676,Asian Art,Illustrated book,,Japan,Edo period (1615–1868),,,,Artist,,Kuwagata Keisai,"Japanese, 1764–1824",,Kuwagata Keisai,Japanese,1764,1824,1800,1800,1800,Woodblock printed book; ink and color on paper,10 3/8 × 7 5/16 in. (26.4 × 18.5 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78676,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.775,false,true,78677,Asian Art,Illustrated book,,Japan,Edo period (1615–1868),,,,Artist,,Kuwagata Keisai,"Japanese, 1764–1824",,Kuwagata Keisai,Japanese,1764,1824,1813,1813,1813,Woodblock printed book; ink and color on paper,10 5/8 × 7 1/16 in. (27 × 18 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78677,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.777,false,true,78679,Asian Art,Illustrated book,,Japan,Edo period (1615–1868),,,,Artist,,Kuwagata Keisai,"Japanese, 1764–1824",,Kuwagata Keisai,Japanese,1764,1824,1802,1802,1802,"Woodblock printed book; ink, color and mica on paper",10 7/16 × 7 1/16 in. (26.5 × 18 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78679,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.779,false,true,78681,Asian Art,Illustrated book,,Japan,Edo period (1615–1868),,,,Artist,,Kuwagata Keisai,"Japanese, 1764–1824",,Kuwagata Keisai,Japanese,1764,1824,1823,1823,1823,Woodblock printed book; ink and color on paper,10 11/16 × 7 3/16 in. (27.2 × 18.3 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78681,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.880,false,true,78770,Asian Art,Illustrated book,,Japan,Edo period (1615–1868),,,,Artist,,Kuwagata Keisai,"Japanese, 1764–1824",,Kuwagata Keisai,Japanese,1764,1824,ca. 1815–42,1810,1847,Woodblock printed book; ink and color on paper,9 × 6 5/16 in. (22.9 × 16 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78770,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.812a–c,false,true,78702,Asian Art,Illustrated books,,Japan,Edo period (1615–1868),,,,Artist,,Shakuyakutei Nagane,"Japanese, 1767–1845",,Shakuyakutei Nagane,Japanese,1767,1845,1834,1834,1834,Set of three woodblock printed books; ink and color on paper,each: 9 × 6 3/8 in. (22.8 × 16.2 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78702,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JIB38,false,true,45228,Asian Art,Illustrated book,"『俳優三階興』|Amusements of Kabuki Actors of the “Third Floor” [Dressing Room] (Yakusha sangaikyō), by Shikitei Sanba",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Toyokuni I,"Japanese, 1769–1825",,Utagawa Toyokuni I,Japanese,1769,1825,1801,1801,1801,Set of polychrome woodblock-printed books; ink and color on paper,8 1/2 × 6 in. (21.6 × 15.2 cm),"Rogers Fund, 1918",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/45228,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.848,false,true,78738,Asian Art,Illustrated book,,Japan,Edo period (1615–1868),,,,Artist,Attributed to,Utagawa Toyokuni I,"Japanese, 1769–1825",,Utagawa Toyokuni I,Japanese,1769,1825,1798,1798,1798,Woodblock printed book; ink and color on paper,5 × 6 7/8 in. (12.7 × 17.5 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78738,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.877,false,true,78767,Asian Art,Illustrated book,"『俳優三階興』|Amusements of Kabuki Actors of the “Third Floor” [Dressing Room] (Yakusha sangaikyō), by Shikitei Sanba",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Toyokuni I,"Japanese, 1769–1825",,Utagawa Toyokuni I,Japanese,1769,1825,1801,1801,1801,First volume of a two-volume set of woodblock-printed books; ink and color on paper,8 7/16 × 6 1/8 in. (21.5 × 15.5 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78767,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"JIB39a, b",false,true,57566,Asian Art,Illustrated book,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Toyokuni I,"Japanese, 1769–1825",,Utagawa Toyokuni I,Japanese,1769,1825,1802,1802,1802,Two volumes; polychrome woodblock printed book; ink and color on paper,Each: 8 1/2 × 6 1/16 × 1/2 in. (21.6 × 15.4 × 1.3 cm),"Rogers Fund, 1918",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/57566,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2013.849a, b",false,true,78739,Asian Art,Illustrated books,『絵本時世粧』|Picture Book of Modern Figures of Fashion (Ehon imayō sugata),Japan,Edo period (1615–1868),,,,Artist,,Utagawa Toyokuni I,"Japanese, 1769–1825",,Utagawa Toyokuni I,Japanese,1769,1825,1802,1802,1802,Set of two woodblock-printed books with hand-written names in volume two; ink and color on paper,each: 8 9/16 × 6 1/8 in. (21.8 × 15.5 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78739,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2013.850a, b",false,true,78740,Asian Art,illustrated books,『役者相貌鏡』|Mirror Images of Kabuki Actors (Yakusha awase kagami),Japan,Edo period (1615–1868),,,,Artist,,Utagawa Toyokuni I,"Japanese, 1769–1825",,Utagawa Toyokuni I,Japanese,1769,1825,1804,1804,1804,Set of two woodblock printed books; ink and color on paper,each: 10 7/16 × 7 1/16 in. (26.5 × 18 cm),"Purchase, Mary and James G. Wallach Family Foundation Gift, in honor of John T. Carpenter, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78740,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.709,false,true,78630,Asian Art,Illustrated book,,Japan,Edo period (1615–1868),,,,Artist,,Teisai Hokuba,"Japanese, 1771–1844",,Teisai Hokuba,Japanese,1771,1844,1815,1815,1815,Woodblock printed book; ink on paper,8 11/16 × 6 1/8 in. (22 × 15.6 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78630,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.682,false,true,78603,Asian Art,Illustrated book,,Japan,Edo period (1615–1868),,,,Artist,,Chō Gesshō,"Japanese, 1772–1832",,Chō Gesshō,Japanese,1772,1832,1817,1817,1817,Woodblock printed book; ink and color on paper,10 5/16 × 7 5/16 in. (26.2 × 18.5 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78603,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.788a–d,false,true,78690,Asian Art,Illustrated books,,Japan,Edo period (1615–1868),,,,Artist,,Nishimura Nantei,"Japanese, 1775–1834",,Nishimura Nantei,Japanese,1775,1834,1823,1823,1823,"Set of three woodblock printed books; ink on paper and ink and color on paper (vol. ""kan"")","vol. ""plum"" each: 10 1/16 × 7 5/16 in. (25.5 × 18.5 cm) vol. ""kan"": 10 5/16 × 7 3/16 in. (26.2 × 18.2 cm)","Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78690,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2013.750a, b",false,true,78652,Asian Art,Illustrated book,,Japan,Edo period (1615–1868),,,,Artist,,Okada Kanrin,"Japanese, 1775–1849",,Okada Kanrin,Japanese,1775,1849,1845,1845,1845,Set of two woodblock printed books; ink and color on paper,each: 10 7/8 × 7 3/8 in. (27.7 × 18.7 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78652,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.668,false,true,78589,Asian Art,Illustrated books,"竹洞四君子|The Four Worthies (Plum, Bamboo, Orchid, Chrysanthemum) (Shikunshi)",Japan,Edo period (1615–1868),,,,Artist,,Nakabayashi Chikutō,"Japanese, 1776–1853",,Nakabayashi Chikutō,Japanese,1776,1853,1853,1853,1853,Set of two woodblock printed books bound as one; ink on paper,10 9/16 × 6 7/8 in. (26.8 × 17.5 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78589,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.755,false,true,78657,Asian Art,Illustrated book,,Japan,Edo period (1615–1868),,,,Artist,,Kawamura Kihō,"Japanese, 1778–1852",,Kawamura Kihō,Japanese,1778,1852,after 1825,1825,1850,Woodblock printed book; ink and color on paper,10 3/16 × 7 1/8 in. (25.8 × 18.1 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78657,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.756,false,true,78658,Asian Art,Illustrated book,,Japan,Edo period (1615–1868),,,,Artist,,Kawamura Kihō,"Japanese, 1778–1852",,Kawamura Kihō,Japanese,1778,1852,1827,1827,1827,Woodblock printed book; ink and color on paper,10 3/8 × 7 1/16 in. (26.3 × 18 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78658,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.660,false,true,78581,Asian Art,Illustrated book,金波園画譜|Kinpaen (Bunpō) Picture Album (Kinpaen gafu),Japan,Edo period (1615–1868),,,,Artist,,Kawamura Bunpō,"Japanese, 1779–1821",,Kawamura Bunpō,Japanese,1779,1821,1820,1820,1820,Woodblock printed book; ink and color on paper,10 7/16 × 4 1/2 in. (26.5 × 11.5 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78581,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.661,false,true,78582,Asian Art,Illustrated book,金波園画譜|Kinpaen (Bunpō) Picture Album (Kinpaen gafu),Japan,Edo period (1615–1868),,,,Artist,,Kawamura Bunpō,"Japanese, 1779–1821",,Kawamura Bunpō,Japanese,1779,1821,1820,1820,1820,Woodblock printed book; ink and color on paper,10 7/16 × 6 11/16 in. (26.5 × 17 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78582,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.662,false,true,78583,Asian Art,Illustrated book,文鳳山水画譜|Bunpō Landscape Picture Album (Bunpō sansui gafu),Japan,Edo period (1615–1868),,,,Artist,,Kawamura Bunpō,"Japanese, 1779–1821",,Kawamura Bunpō,Japanese,1779,1821,1824,1824,1824,Woodblock printed book; ink and color on paper,10 3/16 × 6 13/16 in. (25.8 × 17.3 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78583,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.658a–c,false,true,78579,Asian Art,Illustrated books,"文鳳画譜|Bunpō Picture Album (Bunpō gafu), First Series",Japan,Edo period (1615–1868),,,,Artist,,Kawamura Bunpō,"Japanese, 1779–1821",,Kawamura Bunpō,Japanese,1779,1821,1807,1807,1807,Set of three woodblock printed books; ink and color on paper,each: 10 5/16 × 7 1/16 in. (26.2 × 18 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78579,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.663a–c,false,true,78584,Asian Art,Illustrated book,"漢画指南二編|Guide to Chinese Painting (Kanga shinan nihen), Second Series",Japan,Edo period (1615–1868),,,,Artist,,Kawamura Bunpō,"Japanese, 1779–1821",,Kawamura Bunpō,Japanese,1779,1821,1811,1811,1811,Set of three woodblock printed books; ink and color on paper,each: 10 7/16 × 7 in. (26.5 × 17.8 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78584,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.830,false,true,78720,Asian Art,Illustrated book,,Japan,Edo period (1615–1868),,,,Artist,,Komatsubara Suikei,"Japanese, 1780–1833",,Komatsubara Suikei,Japanese,1780,1833,ca. 1831,1826,1836,"Woodblock printed book; ink, color, and metallic pigments on paper",8 7/8 × 6 5/16 in. (22.5 × 16 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78720,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JIB9,false,true,57543,Asian Art,Illustrated book,,Japan,Edo period (1615–1868),,,,Artist,,Totoya Hokkei,"Japanese, 1780–1850",,Totoya Hokkei,Japanese,1780,1850,1833,1833,1833,Ink and color on paper,10 × 6 7/8 × 1/2 in. (25.4 × 17.5 × 1.3 cm),"Rogers Fund, 1918",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/57543,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.702,false,true,78623,Asian Art,Illustrated book,,Japan,Edo period (1615–1868),,,,Artist,,Totoya Hokkei,"Japanese, 1780–1850",,Totoya Hokkei,Japanese,1780,1850,1824,1824,1824,Woodblock printed book; ink and color on paper,8 15/16 × 6 5/16 in. (22.7 × 16 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78623,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.703,false,true,78624,Asian Art,Illustrated book,,Japan,Edo period (1615–1868),,,,Artist,,Totoya Hokkei,"Japanese, 1780–1850",(et al),Totoya Hokkei,Japanese,1780,1850,1826,1826,1826,Woodblock printed book; ink and color on paper,9 × 6 5/8 in. (22.8 × 16.8 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78624,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.704,false,true,78625,Asian Art,Illustrated book,,Japan,Edo period (1615–1868),,,,Artist,,Totoya Hokkei,"Japanese, 1780–1850",,Totoya Hokkei,Japanese,1780,1850,1829,1829,1829,Woodblock printed book; ink and color on paper,9 1/16 × 6 13/16 in. (23 × 17.3 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78625,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.705,false,true,78626,Asian Art,Illustrated book,,Japan,Edo period (1615–1868),,,,Artist,,Totoya Hokkei,"Japanese, 1780–1850",,Totoya Hokkei,Japanese,1780,1850,1830,1830,1830,Woodblock printed book; ink and color on paper,9 × 6 5/16 in. (22.8 × 16 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78626,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.706,false,true,78627,Asian Art,Illustrated book,,Japan,Edo period (1615–1868),,,,Artist,,Totoya Hokkei,"Japanese, 1780–1850",,Totoya Hokkei,Japanese,1780,1850,1830s,1830,1839,Woodblock printed book; ink and color on paper,9 × 6 1/2 in. (22.8 × 16.5 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78627,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.708,false,true,78629,Asian Art,Illustrated book,,Japan,Edo period (1615–1868),,,,Artist,,Totoya Hokkei,"Japanese, 1780–1850",,Totoya Hokkei,Japanese,1780,1850,1845,1845,1845,Woodblock printed book; ink and color on paper,8 1/4 × 6 in. (21 × 15.3 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78629,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JIB8a–c,false,true,58813,Asian Art,Illustrated book,,Japan,Edo period (1615–1868),,,,Artist,,Totoya Hokkei,"Japanese, 1780–1850",,Totoya Hokkei,Japanese,1780,1850,1828–30,1828,1830,Three volumes; ink and color on paper,Each: 8 7/8 × 6 1/4 × 3/8 in. (22.5 × 15.9 × 1 cm),"Rogers Fund, 1918",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/58813,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JIB1,false,true,57536,Asian Art,Illustrated book,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,1830,1830,1830,Ink and color on paper,9 × 12 1/4 × 1/2 in. (22.9 × 31.1 × 1.3 cm),"Rogers Fund, 1918",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/57536,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.759,false,true,78661,Asian Art,Illustrated book,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,1837,1837,1837,Woodblock printed book; ink and color on paper,9 15/16 × 6 7/8 in. (25.2 × 17.5 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78661,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.760,false,true,78662,Asian Art,Illustrated book,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,1840s,1840,1849,Woodblock printed book; ink and color on paper,9 13/16 × 6 3/4 in. (25 × 17.2 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78662,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.758a–c,false,true,78660,Asian Art,Illustrated books,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,1830s,1830,1839,Set of three woodblock printed books; ink and color on paper,each: 10 1/16 × 7 5/16 in. (25.5 × 18.5 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78660,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.762a–c,false,true,78664,Asian Art,Illustrated books,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,ca. 1850,1845,1855,Set of three woodblock printed books; ink and color on paper,each: 9 1/8 × 6 5/16 in. (23.2 × 16 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78664,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.813,false,true,78703,Asian Art,Illustrated books,,Japan,Edo period (1615–1868),,,,Artist,,Yanagawa Shigenobu,"Japanese, 1787–1832",,Yanagawa Shigenobu,Japanese,1787,1832,1839,1839,1839,Set of three woodblock printed books; ink and color on paper,each: 9 1/16 × 6 5/16 in. (23 × 16 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78703,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2013.811a, b",false,true,78701,Asian Art,Illustrated books,,Japan,Edo period (1615–1868),,,,Artist,,Yanagawa Shigenobu,"Japanese, 1787–1832",,Yanagawa Shigenobu,Japanese,1787,1832,1823,1823,1823,Set of two woodblock printed books; ink and color on paper,each: 8 7/8 × 6 1/4 in. (22.6 × 15.8 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78701,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.802,false,true,78818,Asian Art,Illustrated book,,Japan,Edo period (1615–1868),,,,Artist,,Katsura Seiyō,"Japanese, 1787–1860",,Katsura Seiyō,Japanese,1787,1860,1831,1831,1831,Woodblock printed book; ink and color on paper,Other: 9 × 6 1/2 in. (22.8 × 16.5 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78818,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.676,false,true,78597,Asian Art,Illustrated book,艶本 婦慈のゆき|An Erotic Picture Book of Snow on Fuji (Enpon fuji no yuki),Japan,Edo period (1615–1868),,,,Artist,,Keisai Eisen,"Japanese, 1790–1848",,Keisai Eisen,Japanese,1790,1848,1824,1824,1824,Woodblock printed book; ink and color on paper,8 3/4 × 6 1/4 in. (22.2 × 15.9 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78597,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.675a–c,false,true,78596,Asian Art,Illustrated books,浮世画譜|Picture Album of the Floating World (Ukiyo efu),Japan,Edo period (1615–1868),,,,Artist,,Keisai Eisen,"Japanese, 1790–1848",,Keisai Eisen,Japanese,1790,1848,ca. 1820s,1820,1829,Set of three woodblock printed books; ink and color on paper,each: 9 1/16 × 6 1/4 in. (23 × 15.9 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78596,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.677a–c,false,true,78598,Asian Art,Illustrated books,,Japan,Edo period (1615–1868),,,,Artist,,Keisai Eisen,"Japanese, 1790–1848",,Keisai Eisen,Japanese,1790,1848,1822,1822,1822,Set of three woodblock printed books; ink and color on paper,each: 8 7/8 × 6 5/16 in. (22.5 × 16.1 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78598,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.780,false,true,78682,Asian Art,Illustrated book,,Japan,Edo period (1615–1868),,,,Artist,,Ōishi Matora,"Japanese, 1793–1833",,Ōishi Matora,Japanese,1793,1833,1829,1829,1829,Woodblock printed book; ink and color on paper,9 × 6 3/16 in. (22.8 × 15.7 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78682,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2013.650a, b",false,true,78571,Asian Art,Illustrated books,持子鼠花山姥|Stories of a Fortunate Rat (Komochi nezumi hana no yamauba),Japan,Edo period (1615–1868),,,,Artist,,Akatsuki no Kanenari,"Japanese, 1793–1861",,Akatsuki no Kanenari,Japanese,1793,1861,1827,1827,1827,Set of two woodblock printed books; ink and color on paper,each: 8 3/4 × 6 in. (22.2 × 15.2 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78571,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.671,false,true,78592,Asian Art,Illustrated book,椿山翁畫譜|Chinzan Picture Album (Chinzan-ō gafu),Japan,Edo period (1615–1868),,,,Artist,,Tsubaki Chinzan,"Japanese, 1801–1854",,Tsubaki Chinzan,Japanese,1801,1854,1851,1851,1851,Accordion album; ink and color on paper,11 1/4 × 7 1/16 in. (28.5 × 18 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78592,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JIB117,false,true,45227,Asian Art,Illustrated book,,Japan,Edo period (1615–1868),,,,Artist,,Shibata Zeshin,"Japanese, 1807–1891",", and others",Shibata Zeshin,Japanese,1807,1891,1867,1615,1868,Polychrome Woodblock printed book,,"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/45227,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.866,false,true,78756,Asian Art,Illustrated book,,Japan,Edo period (1615–1868),,,,Artist,,Shibata Zeshin,"Japanese, 1807–1891",", et al",Shibata Zeshin,Japanese,1807,1891,1867,1867,1867,Woodblock printed book; ink and color on paper,9 3/4 × 7 3/16 in. (24.8 × 18.2 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78756,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -91.1.975,false,true,59689,Asian Art,Netsuke,,Japan,Edo period (1615–1868),,,,Artist,,Mitsuhiro Ōhara,"Japanese, 1810–1875",,Mitsuhiro Ōhara,Japanese,1810,1875,mid-19th century,1825,1875,Ivory,H. 1 in. (2.5 cm); W. 1 in. (2.5 cm); D. 1 3/4 in. (4.4 cm),"Edward C. Moore Collection, Bequest of Edward C. Moore, 1891",,,,,,,,,,,,Netsuke,,http://www.metmuseum.org/art/collection/search/59689,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.845,false,true,78735,Asian Art,Illustrated book,,Japan,Edo period (1615–1868),,,,Artist,,Hine Taizan,"Japanese, 1813–1869",,Hine Taizan,Japanese,1813,1869,ca. 1850,1845,1855,Accordion album; ink on paper,8 11/16 × 6 11/16 in. (22 × 17 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78735,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.862a–c,false,true,78752,Asian Art,Illustrated books,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Yoshimune,"Japanese, 1817–1880",,Utagawa Yoshimune,Japanese,1817,1880,ca. 1860,1855,1865,Three books (in hanshita-e form); ink and light colors on paper,each: 6 3/4 × 4 3/4 in. (17.2 × 12 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78752,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.696,false,true,78617,Asian Art,Illustrated book,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige II,"Japanese, 1829–1869",,Utagawa Hiroshige II,Japanese,1829,1869,1862,1862,1862,Woodblock printed book; ink and color on paper,6 15/16 × 4 5/16 in. (17.7 × 11 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78617,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JIB143,false,true,57834,Asian Art,Illustrated book,,Japan,Edo period (1615–1868),,,,Artist,,Kawanabe Kyōsai,"Japanese, 1831–1889",,Kawanabe Kyōsai,Japanese,1831,1889,1881,1881,1881,Woodblock printed book; ink and color on paper,8 1/2 x 5 3/4 in. (21.6 x 14.6 cm),"Bequest of W. Gedney Beatty, 1941",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/57834,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"JIB104a, b",false,true,57789,Asian Art,Illustrated book,,Japan,Edo period (1615–1868),,,,Artist,,Kawanabe Kyōsai,"Japanese, 1831–1889",and many others,Kawanabe Kyōsai,Japanese,1831,1889,1814,1814,1814,Ink and color on paper,Overall: 11 x 7 1/2 x 1/4 in. (27.9 x 19.1 x 0.7 cm) Overall (open): 11 x 13 3/4 in. (27.9 x 35 cm),"The Howard Mansfield Collection, Gift of Howard Mansfield, 1936",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/57789,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JIB41,false,true,45273,Asian Art,Illustrated book,絵本吾妻遊|Ehon Azuma asobi|Picture Book of Amusements of the Eastern Capital (Ehon Azuma asobi),Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,"1790, first month",1790,1790,Woodblock printed book; ink on paper,Overall: 8 11/16 × 6 1/4 in. (22 × 15.8 cm),"Rogers Fund, 1918",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/45273,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JIB43,false,true,57568,Asian Art,Illustrated book,Ehon Momochidori Kyōka-awase|百千鳥狂歌合|Myriad Birds: Picture Book of Playful Verse (Momo chidori kyōka-awase),Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,1791,1791,1791,Woodblock printed books (vols. 1 and 2); ink and color on paper,Overall: 10 x 14 7/8 x (25.4 x 37.8 cm); L. (open) 29 3/4 in. (75.6 cm),"Rogers Fund, 1918",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/57568,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JIB45,false,true,57605,Asian Art,Illustrated book,絵本和歌夷 「龢謌夷」|Picture Book with Playful Poems for the Young God Ebisu,Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,1792,1792,1792,Polychrome woodblock printed book; ink and color on paper,Overall: 10 1/16 × 7 1/2 in. (25.5 × 19 cm),"Rogers Fund, 1918",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/57605,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JIB46,false,true,45323,Asian Art,Illustrated book,"狂月坊|The Moon-Mad Monk, or Crazy Gazing at the Moon (Kyōgetsubō)",Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,"1789, eighth month",1789,1789,Polychrome woodblock printed book; ink and color on paper,Overall: 9 15/16 × 7 1/2 in. (25.2 × 19 cm),"Rogers Fund, 1918",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/45323,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JIB47,false,true,57648,Asian Art,Illustrated book,潮干のつと|Gifts from the Ebb Tide (The Shell Book) (Shiohi no tsuto),Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,1789,1789,1789,Polychrome woodblock printed book; ink and color on paper,10 1/4 x 7 1/2 in. (26 x 19.1 cm),"Rogers Fund, 1918",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/57648,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JIB48,false,true,45324,Asian Art,Illustrated book,銀世界|The Silver World (Gin sekai),Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,"1790, first month",1790,1790,Polychrome woodblock printed book; ink and color on paper,Overall: 10 1/8 × 7 1/2 in. (25.7 × 19 cm),"Rogers Fund, 1918",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/45324,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JIB49,false,true,57649,Asian Art,Illustrated book,普賢像|Statue of the Bodhisattva Fugen (Fugenzō),Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,"1790, third month",1790,1790,Polychrome woodblock printed book; ink and color on paper,Overall: 10 1/16 × 7 5/16 in. (25.6 × 18.5 cm),"Rogers Fund, 1918",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/57649,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JIB89,false,true,57755,Asian Art,Illustrated book,銀世界|The Silver World (Gin sekai),Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,preface dated 1790,1790,1790,Polychrome woodblock printed book; ink and color on paper,10 1/16 x 7 1/2 in. (25.5 x 19 cm),"The Howard Mansfield Collection, Gift of Howard Mansfield, 1936",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/57755,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JIB44a,false,true,57569,Asian Art,Illustrated book,画本虫撰|Picture Book of Crawling Creatures (The Insect Book) (Ehon mushi erami),Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,probably 1823 (later edition),1823,1823,One from a set of two polychrome woodblock printed books; ink and color on paper,Overall: 9 15/16 × 7 3/16 in. (25.2 × 18.3 cm),"Rogers Fund, 1918",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/57569,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JIB44b,false,true,57570,Asian Art,Illustrated book,画本虫撰|Picture Book of Crawling Creatures (The Insect Book) (Ehon mushi erami),Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,probably 1823 (later edition),1823,1823,One from a set of two polychrome woodblock printed books; ink and color on paper,Overall: 9 5/8 × 7 1/16 in. (24.5 × 18 cm),"Rogers Fund, 1918",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/57570,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.853,false,true,78743,Asian Art,Illustrated book,,Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,1789,1789,1789,"Woodblock printed book; ink, color, and brass dust on paper",11 5/16 × 7 3/8 in. (28.7 × 18.7 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78743,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.858,false,true,78748,Asian Art,Illustrated book,,Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,preface dated 1794,1794,1795,"Woodblock printed book; ink, color, and metallic pigments on paper",10 3/16 × 7 1/2 in. (25.8 × 19 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78748,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.859,false,true,78749,Asian Art,Illustrated book,,Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,1789,1789,1789,"Woodblock printed book; ink, color, and brass dust on paper",10 1/16 × 7 1/2 in. (25.5 × 19 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78749,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.897,false,true,78787,Asian Art,Illustrated book,,Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,probably 1789,1789,1789,"Woodblock printed book (orihon, accordion-style); ink, color, mica, and gold-leaf on paper",10 11/16 × 7 9/16 in. (27.2 × 19.2 cm),"Purchase, Mary and James G. Wallach Family Foundation Gift, in honor of John T. Carpenter, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78787,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"JIB42a, b",false,true,45322,Asian Art,Illustrated book,繪本四季花|Picture Book of Flowers of the Four Seasons (Ehon shiki no hana),Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,"1801, first month",1801,1801,Set of two polychrome woodblock printed books; ink and color on paper,Overall (each volume): 8 1/4 × 5 13/16 in. (20.9 × 14.8 cm),"Rogers Fund, 1918",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/45322,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"JIB77a, b",false,true,57675,Asian Art,Illustrated book,"『百千鳥狂歌合』|Myriad Birds:A Playful Poetry Contest (Momo chidori kyōka-awase), 2 vols.",Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,1790,1790,1790,Two volumes; woodblock printed books; ink and color on paper,Each: 10 × 7 1/2 × 1/4 in. (25.4 × 19.1 × 0.6 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/57675,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2013.852a, b",false,true,78742,Asian Art,Illustrated books,,Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,1788,1788,1788,"Set of two woodblock printed books; ink, color, and mica (vol. 2) on paper",each: 9 13/16 × 7 3/16 in. (25 × 18.3 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78742,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2013.854a, b",false,true,78744,Asian Art,Illustrated books,,Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,1801,1801,1801,Set of two woodblock printed books; ink and color on paper,each: 8 1/2 × 6 1/16 in. (21.6 × 15.4 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78744,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2013.856a, b",false,true,78746,Asian Art,Illustrated books,『青楼繪本年中行事』|Yoshiwara Picture Book of New Year’s Festivities (Seirō ehon nenjū gyōji),Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,1804,1804,1804,Set of two woodblock printed books; ink and color on paper,each: 9 × 6 5/16 in. (22.8 × 16 cm),"Purchase, Mary and James G. Wallach Family Foundation Gift, in honor of John T. Carpenter, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78746,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2013.857a, b",false,true,78747,Asian Art,Illustrated books,,Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,1804,1804,1804,Set of two woodblock printed books; ink on paper,each: 9 × 6 5/16 in. (22.8 × 16 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78747,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.855a–c,false,true,78745,Asian Art,Illustrated book,,Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,ca. 1803,1798,1808,Set of three woodblock printed books; ink on paper,each: 8 7/16 × 6 in. (21.5 × 15.3 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78745,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JIB3,false,true,57538,Asian Art,Illustrated book,,Japan,Edo period (1615–1868),,,,Artist,,Yashima Gakutei,"Japanese, 1786?–1868",,Yashima Gakutei,Japanese,1786,1868,1823,1823,1823,Ink and color on paper,9 × 6 1/8 × 3/8 in. (22.9 × 15.6 × 1 cm),"Rogers Fund, 1918",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/57538,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.680,false,true,78601,Asian Art,Illustrated book,一老画譜|Ichirō Picture Album (Ichirō gafu),Japan,Edo period (1615–1868),,,,Artist,,Yashima Gakutei,"Japanese, 1786?–1868",,Yashima Gakutei,Japanese,1786,1868,1823,1823,1823,Woodblock printed book; ink and color on paper,9 × 6 5/16 in. (22.8 × 16 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78601,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.686,false,true,78607,Asian Art,Illustrated book,,Japan,Edo period (1615–1868),,,,Artist,,Yashima Gakutei,"Japanese, 1786?–1868",,Yashima Gakutei,Japanese,1786,1868,ca. 1820,1815,1825,Woodblock printed book; ink and color on paper,9 × 6 3/8 in. (22.8 × 16.2 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78607,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.681a–c,false,true,78602,Asian Art,Illustrated books,,Japan,Edo period (1615–1868),,,,Artist,,Yashima Gakutei,"Japanese, 1786?–1868",,Yashima Gakutei,Japanese,1786,1868,ca. 1830,1825,1835,Set of three woodblock printed books; ink and color on paper,8 11/16 × 6 3/16 in. (22 × 15.7 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78602,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2013.742a, b",false,true,78644,Asian Art,Illustrated books,,Japan,Edo period (1615–1868),,,,Artist,,Nichōsai,"Japanese, active 1780s",,Nichōsai,Japanese,1780,1789,1803,1803,1803,"Set of two woodblock printed books; one volume ink and color on paper, other volume ink on paper",10 3/16 × 7 1/16 in. (25.8 × 17.9 cm) 9 13/16 × 7 in. (25 × 17.8 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78644,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2013.863a, b",false,true,78753,Asian Art,Illustrated books,,Japan,Edo period (1615–1868),,,,Artist,,Yoshishige,"Japanese, active 1840s",,Yoshishige,Japanese,1840,1849,1848,1848,1848,Set of two woodblock printed books; ink and color on paper,each: 9 13/16 × 6 13/16 in. (25 × 17.3 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78753,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -49.35.3,false,true,48921,Asian Art,Folding screen,波に舟図屏風|Boats upon Waves,Japan,Edo period (1615–1868),,,,Artist,Studio of,Tawaraya Sōtatsu,"Japanese, died ca. 1640",,Tawaraya Sōtatsu,Japanese,1540,1640,mid- to late 17th century,1634,1699,"Six-panel folding screen; ink, color, and gold on paper",61 1/8 x 141 3/4 in. (155.2 x 360 cm),"H. O. Havemeyer Collection, Gift of Horace Havemeyer, 1949",,,,,,,,,,,,Screens,,http://www.metmuseum.org/art/collection/search/48921,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"55.94.3, .4",false,true,45388,Asian Art,Folding screen,"俵屋宗達工房 大原御幸図屏風|Royal Visit to Ōhara, from The Tale of the Heike",Japan,Edo period (1615–1868),,,,Artist,Studio of,Tawaraya Sōtatsu,"Japanese, died ca. 1640",,Tawaraya Sōtatsu,Japanese,1540,1640,first half of the 17th century,1600,1650,Pair of six-panel folding screens; ink and color on paper,Image (each screen): 63 7/16 x 143 5/16 in. (161.1 x 364 cm),"Fletcher Fund, 1955",,,,,,,,,,,,Screens,,http://www.metmuseum.org/art/collection/search/45388,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"1975.268.60, .61",false,true,48923,Asian Art,Folding screen,月に秋草図屏風|Moon and Autumn Grasses,Japan,Edo period (1615–1868),,,,Artist,Studio of,Tawaraya Sōtatsu,"Japanese, died ca. 1640",,Tawaraya Sōtatsu,Japanese,1540,1640,mid- to late 17th century,1634,1699,"Pair of six-panel folding screens; ink, color, silver, and gold flecks on paper",Overall (each screen): 59 7/16 in. x 11 ft. 10 3/8 in. (151 x 361.6 cm),"The Harry G. C. Packard Collection of Asian Art, Gift of Harry G. C. Packard, and Purchase, Fletcher, Rogers, Harris Brisbane Dick, and Louis V. Bell Funds, Joseph Pulitzer Bequest, and The Annenberg Fund Inc. Gift, 1975",,,,,,,,,,,,Screens,,http://www.metmuseum.org/art/collection/search/48923,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.335,false,true,73588,Asian Art,Illustrated book,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa (Gountei) Sadahide,"Japanese, 1807–1878/79",,Utagawa (Goutei) Sadahide,Japanese,1807,1879,"February–March, 1860",1860,1860,Polychrome woodblock prints; ink and color on paper,Image: 9 1/4 x 6 1/2 x 3/8 in. (23.5 x 16.5 x 1 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/73588,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.341a–f,false,true,73594,Asian Art,Illustrated book,,Japan,Edo period (1615–1868),,,,Artist,Illustrations attributed to,Utagawa (Gountei) Sadahide,"Japanese, 1807–1878/79",,Utagawa (Goutei) Sadahide,Japanese,1807,1879,1853,1853,1853,Five volumes of woodblock printed books; ink on paper,Overall (each volume): 10 x 7 1/8 x 1/4 in. (25.4 x 18.1 x 0.6 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/73594,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.82.8,false,true,58638,Asian Art,Inrō,,Japan,Edo period (1615–1868),,,,Artist,,Shiomi Masanari,"Japanese, 1647–ca. 1722",,Shiomi Masanari,Japanese,1647,1732,early 18th century,1700,1733,"Case: gold on black lacquer with mother-of-pearl inlay; Fastener (ojime): gold with design of crabs, reed, and rock; Toggle (netsuke): ivory carved in the shape of a reclining bull (signed: Ran’ichi)",H. 2 13/16 in. (7.2 cm); W. 2 3/8 in. (6 cm); D. 7/8 in. (2.2 cm),"Gift of Wilton Lloyd-Smith and his wife, Marjorie Fleming Lloyd-Smith, 1929",,,,,,,,,,,,Inrō,,http://www.metmuseum.org/art/collection/search/58638,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -13.67.99,false,true,58622,Asian Art,Inrō,,Japan,Edo period (1615–1868),,,,Artist,,Shiomi Masanari,"Japanese, 1647–ca. 1722",,Shiomi Masanari,Japanese,1647,1732,early 18th century,1700,1733,"Case: gold, silver, and blue lacquer on black lacquer; Fastener (ojime): pierced gold with floral design; Toggle (netsuke): crystal carved in the shape of a bucket",H. 3 1/16 in. (7.8 cm); W. 2 5/16 in. (5.9 cm); D. 1 1/16 in. (2.7 cm),"Rogers Fund, 1913",,,,,,,,,,,,Inrō,,http://www.metmuseum.org/art/collection/search/58622,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JIB92,false,true,57766,Asian Art,Illustrated book,Kotori Ruishō|A Compendium of Small Birds,Japan,Edo period (1615–1868),,,,Artist,,Nantō,active early 19th century,,Nantō,Japanese,1800,1835,1836,1836,1836,Polychrome woodblock printed book,10 1/2 × 7 3/4 × 1/4 in. (26.7 × 19.7 × 0.6 cm),"The Howard Mansfield Collection, Gift of Howard Mansfield, 1936",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/57766,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.796,false,true,78812,Asian Art,Illustrated book,,Japan,Edo period (1615–1868),,,,Artist,,Sadatoshi,"Japanese, active 1716–36",,Sadatoshi,Japanese,1716,1736,1751,1751,1751,Woodblock printed book; ink and hand-coloring (tanroku bon) on paper,10 1/4 × 7 1/16 in. (26 × 18 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78812,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.874,false,true,78764,Asian Art,Illustrated book,,Japan,Edo period (1615–1868),,,,Artist,,Mori Shunkei,"Japanese, active 1800–20",,Mori Shunkei,Japanese,1800,1820,1820,1820,1820,"Woodblock printed book (orihon, accordion-style); ink and color on paper",8 1/4 × 5 11/16 in. (21 × 14.5 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78764,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JIB60,false,true,57660,Asian Art,Illustrated book,水石画譜|Suiseki Picture Album (Suiseki gafu),Japan,Edo period (1615–1868),,,,Artist,,Satō Suiseki,"Japanese, active 1806–40",,Satō Suiseki,Japanese,1806,1840,"1814, sixth month; preface dated 1814, fourth month",1814,1814,Woodblock printed book; ink and color on paper,Overall: 10 × 6 7/8 in. (25.4 × 17.4 cm),"Rogers Fund, 1918",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/57660,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.831,false,true,78721,Asian Art,Illustrated book,,Japan,Edo period (1615–1868),,,,Artist,,Satō Suiseki,"Japanese, active 1806–40",,Satō Suiseki,Japanese,1806,1840,1820,1820,1820,Woodblock printed book; ink and color on paper,10 3/16 × 7 in. (25.8 × 17.8 cm),"Purchase, Mary and James G. Wallach Family Foundation Gift, in honor of John T. Carpenter, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78721,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.865,false,true,78755,Asian Art,Illustrated book,,Japan,Edo period (1615–1868),,,,Artist,,Yamada Yoshitsuna,"Japanese, active 1848–68",,Yamada Yoshitsuna,Japanese,1848,1868,1828,1828,1828,"Woodblock printed book (orihon, accordion-style but bound); ink and color on paper",7 3/16 × 9 1/4 in. (18.3 × 23.5 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78755,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -13.67.31,false,true,58573,Asian Art,Inrō,,Japan,Edo period (1615–1868),,,,Artist,,Kyūho,"Japanese, active 1789–1801",,Kyūho,Japanese,1789,1801,ca. 1800,1790,1810,Gold maki-e with black lacquer Ojime: metal or lacquered bead Netsuke: ivory carved with design of chrysanthemum and waves,H. 3 3/16 in. (8.1 cm); W. 2 1/16 in. (5.3 cm); D. 3/4 in. (1.9 cm),"Rogers Fund, 1913",,,,,,,,,,,,Inrō,,http://www.metmuseum.org/art/collection/search/58573,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.782,false,true,78684,Asian Art,Illustrated book,,Japan,Edo period (1615–1868),,,,Artist,,Aikawa Minwa,"Japanese, active 1806–1821",,Aikawa Minwa,Japanese,1806,1821,1814,1814,1814,Woodblock printed book; ink and color on paper,10 3/8 × 7 1/16 in. (26.3 × 18 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78684,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.783,false,true,78685,Asian Art,Illustrated book,,Japan,Edo period (1615–1868),,,,Artist,,Aikawa Minwa,"Japanese, active 1806–1821",,Aikawa Minwa,Japanese,1806,1821,1818,1818,1818,Woodblock printed book; ink and color on paper,10 1/4 × 7 5/16 in. (26 × 18.5 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78685,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"JIB59a, b",false,true,57659,Asian Art,Illustrated book,合川珉和画『光琳画式』|Kōrin's Painting Style (Kōrin gashiki),Japan,Edo period (1615–1868),,,,Artist,,Aikawa Minwa,"Japanese, active 1806–1821",,Aikawa Minwa,Japanese,1806,1821,1818,1818,1818,Set of two Woodblock printed books; ink and color on paper,Image: 10 1/16 x 7 1/16 x 3/16 in. (25.5 x 18 x 0.5 cm) Overall: 13 1/8 in. (33.3 cm),"Rogers Fund, 1918",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/57659,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.653,false,true,78574,Asian Art,Illustrated book,"勝景眺望山水画譜|Prospects and Views, Picture Album of Landscapes (Shōkei chōbō, Sansui gafu)",Japan,Edo period (1615–1868),,,,Artist,,Kōkunsai Bairin,"Japanese, early 19th century",,Kōkunsai Bairin,Japanese,1800,1833,preface dated 1826,1826,1826,Woodblock printed book; ink and color on paper,7 3/16 × 4 13/16 in. (18.3 × 12.2 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78574,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JIB27,false,true,45447,Asian Art,Illustrated book,Kyoka Hyaku Monogatari|Poems on One Hundred Ghost Stories,Japan,Edo period (1615–1868),,,,Artist,,Masazumi Ryusai,"Japanese, active 19th century",,Masazumi Ryusai,Japanese,1800,1899,1853,1853,1853,Polychrome woodblock printed book; ink and color on paper,9 × 6 3/8 × 1 1/2 in. (22.9 × 16.2 × 3.8 cm),"Rogers Fund, 1918",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/45447,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JIB30,false,true,57562,Asian Art,Illustrated book,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunchō,"Japanese, active ca. 1783–95",,Katsukawa Shunchō,Japanese,1775,1795,ca. 1790,1780,1800,Ink on paper,8 1/2 × 6 × 3/8 in. (21.6 × 15.2 × 1 cm),"Rogers Fund, 1918",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/57562,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"JIB29a, b",false,true,57560,Asian Art,Illustrated book,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunchō,"Japanese, active ca. 1783–95",,Katsukawa Shunchō,Japanese,1775,1795,1790,1790,1790,Two volumes; ink and color on paper,Each: 8 1/2 × 6 × 1/4 in. (21.6 × 15.2 × 0.6 cm),"Rogers Fund, 1918",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/57560,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.864,false,true,78754,Asian Art,Illustrated book,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Yoshitora,"Japanese, active ca. 1850–80",,Utagawa Yoshitora,Japanese,1845,1880,1858,1858,1858,Woodblock printed book; ink and color on paper,8 5/8 × 6 1/8 in. (21.9 × 15.5 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78754,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JIB23,false,true,57555,Asian Art,Illustrated book,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyotsune,"Japanese, active ca. 1757–1779",,Torii Kiyotsune,Japanese,1757,1779,1774,1774,1774,Ink and color on paper,8 3/4 × 6 × 3/8 in. (22.2 × 15.2 × 1 cm),"Rogers Fund, 1918",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/57555,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.893,false,true,78783,Asian Art,Illustrated book,,Japan,Edo period (1615–1868),,,,Artist,,Hasegawa Myōtei,"Japanese, active late 17th century",,Hasegawa Myōtei,Japanese,1667,1699,1714,1714,1714,Woodblock printed book; ink on paper,10 11/16 × 7 1/2 in. (27.2 × 19 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78783,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.861,false,true,78751,Asian Art,Illustrated book,,Japan,Edo period (1615–1868),,,,Artist,,Umemaru Yūzen,"Japanese, active late 18th century",,Umemaru Yūzen,Japanese,1767,1799,ca. 1820s,1820,1829,"Woodblock printed book; ink, color, and mica on paper",4 15/16 × 7 5/16 in. (12.5 × 18.5 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78751,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.868,false,true,78758,Asian Art,Illustrated book,(榎木新右衛門)画 『雛形伊勢乃海』|Book of Kosode Patterns (Hiinagata Ise no umi),Japan,Edo period (1615–1868),,,,Artist,,Enoki Hironobu,"Japanese, active mid- 18th century",,Enoki Hironobu,Japanese,1734,1766,1751,1751,1751,Three woodblock-printed books bound as one; ink on paper,10 3/4 × 7 3/8 in. (27.3 × 18.7 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78758,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.795a–c,false,true,78811,Asian Art,Illustrated books,,Japan,Edo period (1615–1868),,,,Artist,Attributed to,Takagi Sadatake,"Japanese, active early 18th century",,Takagi Sadatake,Japanese,1700,1733,1734,1734,1734,Set of two woodblock printed books; ink on paper,each: 10 5/8 × 7 5/8 in. (27 × 19.3 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78811,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.683,false,true,78604,Asian Art,Illustrated book,,Japan,Edo period (1615–1868),,,,Artist,,Fukuchi Hakuei,"Japanese, active early 19th century",,Fukuchi Hakuei,Japanese,1800,1833,ca. 1814,1804,1824,Woodblock printed book; ink and color on paper,9 15/16 × 6 1/2 in. (25.3 × 16.5 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78604,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -10.211.107,false,true,59824,Asian Art,Netsuke,文房具牙彫根付|Writing Utensils,Japan,Edo period (1615–1868),,,,Artist,,Ryūsen,"Japanese, active mid–19th century",,Ryūsen,Japanese,1834,1866,mid-19th century,1825,1875,Ivory,H. 1 1/2 in. (3.8 cm); W. 1 3/8 in. (3.5 cm),"Gift of Mrs. Russell Sage, 1910",,,,,,,,,,,,Netsuke,,http://www.metmuseum.org/art/collection/search/59824,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JIB12,false,true,57546,Asian Art,Illustrated book,,Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,1802,1802,1802,Ink and color on paper,7 1/2 × 5 1/4 × 3/8 in. (19.1 × 13.3 × 1 cm),"Rogers Fund, 1918",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/57546,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JIB13,false,true,57547,Asian Art,Illustrated book,,Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1845,1835,1855,Polychrome woodblock printed book; ink and color on paper,4 7/8 × 7 3/4 × 5/8 in. (12.4 × 19.7 × 1.6 cm),"Rogers Fund, 1918",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/57547,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JIB14,false,true,57548,Asian Art,Illustrated book,,Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1840,1830,1850,Two volumes; ink and color on paper,Each: 9 × 6 1/4 × 3/8 in. (22.9 × 15.9 × 1 cm),"Rogers Fund, 1918",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/57548,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JIB16,false,true,45445,Asian Art,Illustrated book,Onna Imagawa|Precepts for Women,Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1820s,1820,1829,Book of polychrome woodblock prints; ink and color on paper,9 × 6 1/4 × 1/2 in. (22.9 × 15.9 × 1.3 cm),"Rogers Fund, 1918",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/45445,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JIB17,false,true,57550,Asian Art,Illustrated book,,Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1814,1804,1824,Polychrome woodblock prints in a book; ink and color on paper,10 × 13 1/4 × 1/4 in. (25.4 × 33.7 × 0.6 cm),"Rogers Fund, 1918",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/57550,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JIB18,false,true,57551,Asian Art,Illustrated book,,Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,1834,1834,1834,Woodblock print; ink on paper,9 × 6 1/4 × 3/8 in. (22.9 × 15.9 × 1 cm),"Rogers Fund, 1918",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/57551,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JIB20,false,true,57553,Asian Art,Illustrated book,,Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1847,1837,1857,Woodblock print; ink on paper,9 × 6 1/4 × 3/8 in. (22.9 × 15.9 × 1 cm),"Rogers Fund, 1918",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/57553,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JIB69,false,true,57667,Asian Art,Illustrated book,,Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,1811,1811,1811,Woodblock print; ink on paper,8 3/4 × 6 1/16 × 1 in. (22.2 × 15.4 × 2.5 cm),"Gift of Julius Mahn, 1919",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/57667,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JIB80,false,true,57677,Asian Art,Illustrated book,,Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1804,1794,1814,Pentaptych of polychrome woodblock prints,14 1/2 x 48 1/2 in. (36.8 x 123.2 cm),"Rogers Fund, 1918",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/57677,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JIB82,false,true,57678,Asian Art,Illustrated book,,Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,1816,1816,1816,Woodblock printed book; ink and color on paper,Overall: 8 7/8 × 6 1/4 × 1/2 in. (22.5 × 15.9 × 1.3 cm),"Rogers Fund, 1931",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/57678,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JIB106,false,true,57791,Asian Art,Illustrated books,,Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,1833,1833,1833,Five volumes; ink on paper,Each: 9 × 6 3/16 × 1/4 in. (22.9 × 15.7 × 0.6 cm),"The Howard Mansfield Collection, Gift of Howard Mansfield, 1936",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/57791,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JIB107,false,true,45444,Asian Art,Illustrated book,Ehon Musashi no Abumi|A Picture Book of Japanese Warriors,Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,1836,1700,1868,Polychrome Woodblock printed book,8 7/8 × 6 1/8 × 3/8 in. (22.5 × 15.6 × 1 cm),"The Howard Mansfield Collection, Gift of Howard Mansfield, 1936",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/45444,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JIB108,false,true,57793,Asian Art,Illustrated book,富岳百景|Mount Fuji of the Mists (Vol. 1); Mount Fuji of the Ascending Dragon (Vol. 2),Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,1834–35,1834,1835,Woodblock print (first and second volumes with 100 pages of illustrations); ink and color on paper,9 x 6 1/4 in. (22.9 x 15.9 cm),"The Howard Mansfield Collection, Gift of Howard Mansfield, 1936",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/57793,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JIB109,false,true,57794,Asian Art,Illustrated book,富岳百景|Fugaku Hyakkei,Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,1834–35,1834,1835,Woodblock print; ink and color on paper,9 × 6 1/4 × 3/8 in. (22.9 × 15.9 × 1 cm),"The Howard Mansfield Collection, Gift of Howard Mansfield, 1936",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/57794,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JIB110,false,true,57796,Asian Art,Illustrated book,,Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,1850,1850,1850,Ink on paper,8 7/8 × 6 1/4 × 3/8 in. (22.5 × 15.9 × 1 cm),"The Howard Mansfield Collection, Gift of Howard Mansfield, 1936",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/57796,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JIB112,false,true,57805,Asian Art,Illustrated book,,Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,1834,1834,1834,Woodblock printed book; ink and color on paper,Overall: 9 × 6 1/4 × 1/2 in. (22.9 × 15.9 × 1.3 cm),"The Howard Mansfield Collection, Gift of Howard Mansfield, 1936",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/57805,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JIB120,false,true,57812,Asian Art,Illustrated book,,Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,1818,1818,1818,Ink on paper,10 1/2 × 7 1/16 × 3/8 in. (26.7 × 17.9 × 1 cm),"The Howard Mansfield Collection, Gift of Howard Mansfield, 1936",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/57812,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JIB81.1,false,true,57679,Asian Art,Illustrated book,,Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,after 1828,1828,1868,Woodblock printed book; ink and color on paper,Overall: 9 × 6 1/4 × 3/8 in. (22.9 × 15.9 × 1 cm),"Rogers Fund, 1931",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/57679,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JIB81.2,false,true,57680,Asian Art,Illustrated book,,Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,after 1814,1815,1868,Woodblock printed book; ink and color on paper,9 x 6 1/4 x 1/2 in. (22.9 x 15.9 x 1.3 cm),"Rogers Fund, 1931",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/57680,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JIB81.4,false,true,57682,Asian Art,Illustrated book,,Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,1819,1819,1819,Woodblock printed book; ink and color on paper,Overall: 9 × 6 × 3/8 in. (22.9 × 15.2 × 1 cm),"Rogers Fund, 1931",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/57682,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JIB81.7,false,true,57684,Asian Art,Illustrated book,,Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,1819,1819,1819,Woodblock printed book; ink and color on paper,Overall: 9 × 6 × 3/8 in. (22.9 × 15.2 × 1 cm),"Rogers Fund, 1931",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/57684,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JIB81.8,false,true,57685,Asian Art,Illustrated book,,Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,1817,1817,1817,Woodblock printed book; ink and color on paper,Overall: 9 × 6 × 3/8 in. (22.9 × 15.2 × 1 cm),"Rogers Fund, 1931",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/57685,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JIB81.9,false,true,57686,Asian Art,Illustrated book,,Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,1819,1819,1819,Woodblock printed book; ink and color on paper,Overall: 9 × 6 1/4 × 3/8 in. (22.9 × 15.9 × 1 cm),"Rogers Fund, 1931",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/57686,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.711,false,true,78632,Asian Art,Illustrated book,,Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,1799,1799,1799,Woodblock printed book; ink on paper,10 7/16 × 7 1/16 in. (26.5 × 18 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78632,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.713,false,true,78634,Asian Art,Illustrated book,,Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,1802,1802,1802,Woodblock printed book; ink and color on paper,7 5/8 × 5 1/4 in. (19.3 × 13.4 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78634,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.714,false,true,78635,Asian Art,Illustrated book,『画本狂歌山満多山』|Picture Book of Kyōka Poems: Mountains upon Mountains (Ehon kyōka yama mata yama),Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,1804,1804,1804,Woodblock printed book; ink and color on paper,10 7/16 × 6 7/8 in. (26.5 × 17.5 cm),"Purchase, Mary and James G. Wallach Family Foundation Gift, in honor of John T. Carpenter, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78635,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.719,false,true,78790,Asian Art,Illustrated book,,Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,1814,1814,1814,"Woodblock printed book (orihon, accordion-style); ink and color on paper",10 1/4 × 6 13/16 in. (26 × 17.3 cm),"Purchase, Mary and James G. Wallach Family Foundation Gift, in honor of John T. Carpenter, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78790,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.721,false,true,78792,Asian Art,Illustrated book,,Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,1815,1815,1815,Woodblock printed book; ink and color on paper,10 1/4 × 6 7/8 in. (26 × 17.5 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78792,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.726,false,true,78797,Asian Art,Illustrated book,,Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,1820,1820,1820,Woodblock printed book; ink and color on paper,10 3/8 × 6 3/4 in. (26.4 × 17.2 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78797,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.728,false,true,78799,Asian Art,Illustrated book,,Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,1823,1823,1823,Woodblock printed book; ink and color on paper,9 × 6 1/4 in. (22.8 × 15.8 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78799,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.730,false,true,78801,Asian Art,Illustrated book,,Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,1829,1829,1829,Woodblock printed book; ink and color on paper,8 15/16 × 6 1/4 in. (22.7 × 15.8 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78801,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.733,false,true,78804,Asian Art,Illustrated book,,Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,1836,1836,1836,Woodblock printed book; ink on paper,8 13/16 × 6 1/8 in. (22.4 × 15.6 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78804,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.734,false,true,78805,Asian Art,Illustrated book,,Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1840s,1840,1849,Woodblock printed book; ink on paper,8 7/8 × 6 5/16 in. (22.5 × 16 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78805,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.737,false,true,78639,Asian Art,Illustrated book,,Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,1843,1843,1843,Woodblock printed book; ink and color on paper,8 3/4 × 6 1/8 in. (22.3 × 15.6 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78639,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.875,false,true,78765,Asian Art,Illustrated book,,Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,1843,1843,1843,Woodblock printed book; ink on paper,8 15/16 × 6 1/8 in. (22.7 × 15.6 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78765,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.878,false,true,78768,Asian Art,Illustrated book,,Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,1815,1815,1815,Woodblock printed book; ink on paper,8 7/8 × 6 3/8 in. (22.5 × 16.2 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78768,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.881,false,true,78771,Asian Art,Illustrated book,"繪本彩色通 初編|Picture Book on the Use of Coloring, first volume (Ehon saishikitsū shohen)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,1848,1848,1848,Woodblock printed book; ink on paper and color scribbles,7 3/16 × 5 1/16 in. (18.3 × 12.9 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78771,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.882,false,true,78772,Asian Art,Illustrated book,『絵本和漢誉』|Picture Book on Heroes of China and Japan (Ehon wakan no homare),Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,1850 (designed ca. 1836; published posthumously),1836,1850,Woodblock printed book; ink on paper,9 1/16 × 6 3/8 in. (23 × 16.2 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78772,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JIB81.10,false,true,57687,Asian Art,Illustrated book,,Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,1819,1819,1819,Woodblock printed book; ink and color on paper,Overall: 9 × 6 × 3/8 in. (22.9 × 15.2 × 1 cm),"Rogers Fund, 1931",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/57687,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JIB81.12,false,true,57689,Asian Art,Illustrated book,,Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,1834,1834,1834,Woodblock printed book; ink and color on paper,Overall: 9 × 6 1/4 × 3/8 in. (22.9 × 15.9 × 1 cm),"Rogers Fund, 1931",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/57689,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JIB81.13,false,true,57741,Asian Art,Illustrated book,,Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1849,1839,1859,Woodblock printed book; ink and color on paper,Overall (JIB81.13 and .14 combined): 9 × 6 1/4 × 3/4 in. (22.9 × 15.9 × 1.9 cm),"Rogers Fund, 1931",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/57741,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JIB81.14,false,true,57742,Asian Art,Illustrated book,,Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1875–78,1875,1878,Woodblock printed book; ink and color on paper,See JIB81.13,"Rogers Fund, 1931",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/57742,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"JIB19a, b",false,true,57552,Asian Art,Illustrated book,,Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,1835,1835,1835,Two volumes; woodblock print; ink on paper,Each: 9 × 6 1/4 × 1/2 in. (22.9 × 15.9 × 1.3 cm),"Rogers Fund, 1918",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/57552,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"JIB79a, b",false,true,45274,Asian Art,Illustrated book,Edo meisho|Famous Sites of Edo,Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,1800,1700,1868,Two volumes; polychrome woodblock print; ink and color on paper,Each: 10 1/16 × 6 5/8 × 1/4 in. (25.6 × 16.8 × 0.6 cm),"Gift of Mary L. Cassilly, 1894",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/45274,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JIB11a–c,false,true,57545,Asian Art,Illustrated books,,Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1828,1818,1838,Three volumes; ink on paper,9 × 6 1/4 × 1/2 in. (22.9 × 15.9 × 1.3 cm),"Rogers Fund, 1918",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/57545,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JIB15a–c,false,true,57549,Asian Art,Illustrated book,,Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1803,1793,1813,Three volumes; ink and color on paper,Each: 10 × 6 3/4 × 1/4 in. (25.4 × 17.1 × 0.6 cm),"Rogers Fund, 1918",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/57549,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2013.710a, b",false,true,78631,Asian Art,Illustrated books,,Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1802,1797,1807,Set of two woodblock printed books; ink and color on paper,each: 10 1/2 × 6 7/8 in. (26.6 × 17.5 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78631,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2013.712a, b",false,true,78633,Asian Art,Illustrated books,『東都名所一覧』|Fine Views of the Eastern Capital at a Glance (Tōto meisho ichiran),Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,1800,1800,1800,Set of two woodblock printed books; ink and color on paper,each: 10 3/16 × 6 7/8 in. (25.8 × 17.5 cm),"Purchase, Mary and James G. Wallach Family Foundation Gift, in honor of John T. Carpenter, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78633,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2013.722a, b",false,true,78793,Asian Art,Illustrated books,,Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,"part I, 1817 and II, 1819",1817,1819,Woodblock printed books; ink on paper,"vol. ""kan"": 7 3/16 × 4 15/16 in. (18.2 × 12.6 cm) vol. ""zen"": 8 3/8 × 5 7/8 in. (21.2 × 14.9 cm)","Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78793,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2013.738a, b",false,true,78640,Asian Art,Illustrated books,,Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,1850,1850,1850,Set of two woodblock printed books; ink on paper,each: 9 × 6 3/16 in. (22.8 × 15.7 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78640,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.715a–d,false,true,78636,Asian Art,Illustrated books,,Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,1805–38,1805,1838,Set of four woodblock printed books; ink on paper,each: 9 13/16 × 7 1/16 in. (25 × 18 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78636,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.716a–c,false,true,78637,Asian Art,Illustrated books,,Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1806,1801,1811,Set of three woodblock printed books; ink and color on paper,each: 9 1/4 × 6 5/8 in. (23.5 × 16.9 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78637,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.717a–e,false,true,78788,Asian Art,Illustrated books,,Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,1809,1809,1809,Set of five woodblock printed books; ink on paper,each: 8 15/16 × 6 5/16 in. (22.7 × 16 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78788,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.718a–f,false,true,78789,Asian Art,Illustrated books,,Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,1810,1810,1810,Set of six woodblock printed books; ink on paper,Other (each): 9 × 6 1/4 in. (22.8 × 15.8 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78789,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.720a–r,false,true,78791,Asian Art,Illustrated book,,Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,1814–78,1814,1878,Set of nineteen woodblock printed books; ink and color on paper,each approximately: 9 × 6 1/4 in. (22.8 × 15.8 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78791,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.729a–c,false,true,78800,Asian Art,Illustrated books,,Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,"part I, 1828, part II, 1830s, and part III, ca. 1848",1828,1848,Set of three woodblock printed books; ink on paper,each approximately: 8 7/8 × 6 1/16 in. (22.5 × 15.4 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78800,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.732a–c,false,true,78803,Asian Art,Illustrated books,,Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,1834; 1835; ca. 1849,1834,1849,Set of three woodblock printed books; ink on paper,each: 8 15/16 × 6 1/4 in. (22.7 × 15.8 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78803,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.735a–e,false,true,78806,Asian Art,Illustrated books,,Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,1833,1833,1833,Set of five woodblock printed books; ink on paper,each: 9 × 6 1/8 in. (22.8 × 15.6 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78806,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.689,false,true,78610,Asian Art,Illustrated books,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,1839,1839,1839,"Woodblock printed book; ink, color, and white paint on paper",8 15/16 × 6 5/16 in. (22.7 × 16 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78610,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.690,false,true,78611,Asian Art,Illustrated books,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,1847,1847,1847,Woodblock printed book; ink and color on paper,7 1/16 × 4 13/16 in. (18 × 12.2 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78611,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.691,false,true,78612,Asian Art,Illustrated book,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,1849,1849,1849,Woodblock printed book; ink and color on paper,each: 7 1/16 × 4 13/16 in. (18 × 12.2 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78612,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.693,false,true,78614,Asian Art,Illustrated book,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,1857,1857,1857,Woodblock printed book; ink and color on paper,10 3/8 × 7 1/16 in. (26.3 × 18 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78614,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.694,false,true,78615,Asian Art,Illustrated book,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,1855,1855,1855,Woodblock printed book; ink and color on paper,9 1/16 × 6 5/16 in. (23 × 16 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78615,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.695,false,true,78616,Asian Art,Illustrated books,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,1859,1859,1859,Woodblock printed book; ink and color on paper,8 9/16 × 6 1/8 in. (21.7 × 15.5 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78616,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.692a–j,false,true,78613,Asian Art,Illustrated books,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,1850–67,1850,1867,Set of ten woodblock printed books; ink and color on paper,each: 7 1/16 × 4 13/16 in. (18 × 12.2 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78613,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -38.25.128,false,true,58950,Asian Art,Inrō,兎月秋草蒔絵鞘印籠|Inrō with Rabbit in the Moon and Autumn Grasses,Japan,Edo period (1615–1868),,,,Artist,,Kyūkoku,"Japanese, active first half of the 19th century",,Kyūkoku,Japanese,1800,1849,first half of the 19th century,1800,1849,"Two-part (sheath type); lacquered wood with gold, silver, and pewter hiramaki-e, togidashimaki-e, on red lacquer ground Netsuke: ivory; rabbit Ojime: pierced metal with floral design",3 3/8 x 1 7/8 x 1 1/8 in. (8.5 x 4.7 x 2.8 cm),"Gift of Mrs. George A. Crocker (Elizabeth Masten), 1937",,,,,,,,,,,,Inrō,,http://www.metmuseum.org/art/collection/search/58950,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"14.40.878a, b",false,true,58628,Asian Art,Inrō,七夕蒔絵印籠|Inrō with Tanabata Story of the Weaver and the Herdboy,Japan,Edo period (1615–1868),,,,Artist,,Nomura Kyūkoku,"Japanese, active first half of the 19th century",,Nomura Kyūkoku,Japanese,1800,1849,active first half of the19th century,1800,1849,Three cases; lacquered wood with gold hiramaki-e and ivory inlay on mother-of-pearl ground; Netsuke: carved ivory; flowers and grasses with silver butterflies; Ojime: silver and gold quail in autumn grasses,Overall (inro): H. 3 7/8 in. (9.8 cm); W. 2 11/16 in. (6.9 cm); D. 13/16 in. (2 cm) Overall (netsuke): H. 11/16 in. (1.7 cm); Diam. 1 11/16 in. (4.3 cm) Overall (ojime): H. 9/16 in. (1.5 cm); W. 9/16 in. (1.4 cm),"Bequest of Benjamin Altman, 1913",,,,,,,,,,,,Inrō,,http://www.metmuseum.org/art/collection/search/58628,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -81.1.582,false,true,45437,Asian Art,Sake cup,,Japan,Edo period (1615–1868),,,,Artist,,Shomosai,"Japanese, active late 18th–early 19th century",,Shomosai,Japanese,0018,0019,mid-19th century,1834,1866,Gold lacquer on red lacquer ground,H. 1 1/16 in. (2.7 cm); W. 4 3/4 in. (12.1 cm),"Bequest of Stephen Whitney Phoenix, 1881",,,,,,,,,,,,Lacquer,,http://www.metmuseum.org/art/collection/search/45437,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -81.1.587,false,true,45439,Asian Art,Sake cup,,Japan,Edo period (1615–1868),,,,Artist,,Shomosai,"Japanese, active late 18th–early 19th century",,Shomosai,Japanese,0018,0019,mid-19th century,1834,1866,Gold lacquer on red lacquer ground,H. 1 in. (2.5 cm); W. 4 1/8 in. (10.5 cm),"Bequest of Stephen Whitney Phoenix, 1881",,,,,,,,,,,,Lacquer,,http://www.metmuseum.org/art/collection/search/45439,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -81.1.40,false,true,59162,Asian Art,Netsuke,牛牙彫根付|Ox,Japan,Edo period (1615–1868),,,,Artist,,Tomotada,"Japanese, active late 18th–early 19th century",,Tomotada,Japanese,1777,1833,late 18th–early 19th century,1777,1833,Ivory,H. 1 in. (2.5 cm); W. 2 1/4 in. (5.7 cm); D. 1 1/8 in. (2.9 cm),"Bequest of Stephen Whitney Phoenix, 1881",,,,,,,,,,,,Netsuke,,http://www.metmuseum.org/art/collection/search/59162,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -81.1.91,false,true,59180,Asian Art,Netsuke,猪牙彫根付|Boar,Japan,Edo period (1615–1868),,,,Artist,,Tomotada,"Japanese, active late 18th–early 19th century",,Tomotada,Japanese,1777,1833,late 18th–early 19th century,1767,1833,Ivory,H. 1 in. (2.5 cm); W. 2 1/8 in. (5.4 cm); D. 1 1/4 in. (3.2 cm),"Bequest of Stephen Whitney Phoenix, 1881",,,,,,,,,,,,Netsuke,,http://www.metmuseum.org/art/collection/search/59180,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.918,false,true,59612,Asian Art,Netsuke,狼牙彫根付|Wolf,Japan,Edo period (1615–1868),,,,Artist,,Tomotada,"Japanese, active late 18th–early 19th century",,Tomotada,Japanese,1777,1833,late 18th–early 19th century,1767,1833,Carved ivory,H. 1 1/4 in. (3.2 cm); W. 7/8 in. (2.2 cm); D. 2 1/8 in. (5.4 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Netsuke,,http://www.metmuseum.org/art/collection/search/59612,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -10.211.1106,false,true,60290,Asian Art,Netsuke,犬牙彫根付|Dog,Japan,Edo period (1615–1868),,,,Artist,,Tomotada,"Japanese, active late 18th–early 19th century",,Tomotada,Japanese,1777,1833,late 18th century–early 19th century,1771,1835,Ivory,H. 2 1/2 in. (6.4 cm); W. 1 1/4 in. (3.2 cm); D. 1/2 in. (1.3 cm),"Gift of Mrs. Russell Sage, 1910",,,,,,,,,,,,Netsuke,,http://www.metmuseum.org/art/collection/search/60290,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -10.211.1154,false,true,60305,Asian Art,Netsuke,枝豆牙彫根付|Edamame (Soy Beans),Japan,Edo period (1615–1868),,,,Artist,,Okatomo,"Japanese, active late 18th–early 19th century",,Okatomo,Japanese,1767,1833,late 18th–early 19th century,1771,1835,Ivory,H. 3/4 in. (1.9 cm); W. 2 1/4 in. (5.7 cm); D. 1 1/2 in. (3.8 cm),"Gift of Mrs. Russell Sage, 1910",,,,,,,,,,,,Netsuke,,http://www.metmuseum.org/art/collection/search/60305,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -10.211.1441,false,true,60386,Asian Art,Netsuke,東方朔仙人牙彫根付|Daoist Immortal Tōbōsaku Sennin,Japan,Edo period (1615–1868),,,,Artist,,Ryūminsai,"Japanese, active late 18th–early 19th century",,Ryūminsai,Japanese,1767,1833,late 18th–early 19th century,1767,1833,Ivory,H. 3 1/4 in. (8.3 cm); W. 1 1/2 in. (3.8 cm); D. 1 in. (2.5 cm),"Gift of Mrs. Russell Sage, 1910",,,,,,,,,,,,Netsuke,,http://www.metmuseum.org/art/collection/search/60386,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -10.211.2362,false,true,59409,Asian Art,Netsuke,鉄拐仙人木彫根付|Daoist Immortal Tekkai,Japan,Edo period (1615–1868),,,,Artist,,Chikusai,"Japanese, active late 18th–early 19th century",,Chikusai,Japanese,1767,1833,late 18th–early 19th century,1767,1833,Wood,H. 2 3/4 in. (7 cm); W. 1 in. (2.5 cm); D. 1 1/4 in. (3.2 cm),"Gift of Mrs. Russell Sage, 1910",,,,,,,,,,,,Netsuke,,http://www.metmuseum.org/art/collection/search/59409,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -13.67.23,false,true,58569,Asian Art,Inrō,蜻蛉蒔絵印籠|Inrō with Dragonflies,Japan,Edo period (1615–1868),,,,Artist,,Kōami Nagataka,"Japanese, active second half of the 18th century",,Kōami Nagataka,Japanese,1750,1799,second half of the 18th century,1750,1799,"Three cases; lacquered wood with gold, red lacquer takamaki-e, hiramaki-e with mother-of-pearl inlay on black lacquer ground; Netsuke: carved ivory; dog; Ojime: carved ivory; persimmon",H. 2 11/16 in. (6.8 cm); W. 2 3/4 in. (7 cm); D. 7/8 in. (2.2 cm),"Rogers Fund, 1913",,,,,,,,,,,,Inrō,,http://www.metmuseum.org/art/collection/search/58569,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -13.67.92,false,true,58617,Asian Art,Inrō,掛軸文字散蒔絵印籠|Inrō with Carp Hanging Scroll and Characters,Japan,Edo period (1615–1868),,,,Artist,,Kōami Chōkō,"Japanese, active second half of the 18th century",,Kōami Chōkō,Japanese,1750,1799,second half of the 18th century,1750,1799,"Three cases; lacquered wood with gold and silver takamaki-e, hiramaki-e, togidashimaki-e, gold foil cut-outs, and mother-of-pearl inlay on black ground Netsuke: kagamibuta with Shōjō design Ojime: agate bead",3 7/8 x 3 5/16 x 15/16 in. (9.8 x 8.4 x 2.4 cm),"Rogers Fund, 1913",,,,,,,,,,,,Inrō,,http://www.metmuseum.org/art/collection/search/58617,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.40.877,false,true,45573,Asian Art,Inrō,祭蒔絵印籠|Inrō with Street Festival (obverse); People Watching a Puppet Show (reverse),Japan,Edo period (1615–1868),,,,Artist,,Tatsuke Takamasu,"Japanese, active second half of the 18th century",,Tatsuke Takamasu,Japanese,1750,1799,second half of the 18th century,1750,1799,"Three cases; lacquered wood with gold, silver, and color (iroko) togidashimaki-e on black lacquer ground Netsuke: carved teakwood; peach with a monkey inside (signed: Kagetoshi) Ojime: carved teakwood with floral design",2 13/16 x 2 15/16 x 1 1/8 in. (7.1 x 7.5 x 2.9 cm),"Bequest of Benjamin Altman, 1913",,,,,,,,,,,,Inrō,,http://www.metmuseum.org/art/collection/search/45573,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.883,false,true,58803,Asian Art,Inrō,葡萄蒔絵印籠 銘「樗平」|Inrō with Grapevine,Japan,Edo period (1615–1868),,,,Artist,,Nomura Choheisai,"Japanese, active second half of the 18th century",,Nomura Choheisai,Japanese,1750,1799,second half of the 18th century,1750,1799,"One case; lacquered wood with gold hiramaki-e, gold foil application with green stained ivory, mother-of-pearl, amber, and horn inlays on black lacquer ground Netsuke: ivory; kagamibuta with inlaid design of gourd and vine Ojime: ivory bead with inlaid design of branch with fruits",H. 3 1/16 in. (7.7 cm); W. 3 13/16 in. (9.7 cm); D. 1 3/16 in. (3 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Inrō,,http://www.metmuseum.org/art/collection/search/58803,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -10.211.1436,false,true,60382,Asian Art,Netsuke,狸牙彫根付|Tanuki (Japanese Racoon Dog) with a Sake Bottle and Bills for Sake,Japan,Edo period (1615–1868),,,,Artist,,Garaku,"Japanese, active second half of the 18th century",,Garaku,Japanese,1750,1799,second half of the 18th century,1750,1799,Ivory,H. 2 3/4 in. (7 cm); W. 1 5/8 in. (4.1 cm); D. 1 in. (2.5 cm),"Gift of Mrs. Russell Sage, 1910",,,,,,,,,,,,Netsuke,,http://www.metmuseum.org/art/collection/search/60382,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.846,false,true,78736,Asian Art,Illustrated book,,Japan,Edo period (1615–1868),,,,Artist,,Ogura Tōkei,"Japanese, active second half of the 18th century",,Ogura Tōkei,Japanese,1750,1799,1809,1809,1809,Woodblock printed book; ink on paper,9 13/16 × 6 7/8 in. (25 × 17.5 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78736,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.847,false,true,78737,Asian Art,Illustrated book,,Japan,Edo period (1615–1868),,,,Artist,,Shōfusai Tōsen,"Japanese, active second half of the 19th century",,Shōfusai Tōsen,Japanese,1850,1899,mid-19th century,1825,1875,Woodblock printed book; ink and color on paper,11 1/4 × 7 7/8 in. (28.5 × 20 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78737,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"JIB105a, b",false,true,57790,Asian Art,Illustrated book,中野期明画 『尾形流百図』|One Hundred Paintings of the Ogata Lineage (Ogata ryu hyakuzu),Japan,Meiji period (1868-1912),,,,Artist,,Nakano Kimei,"Japanese, 1834–1892",,Nakano Kimei,Japanese,1834,1892,1892,1892,1892,Set of two Woodblock printed books; ink on paper,Image (a): 10 x 7 3/8 x 1/2 in. (25.4 x 18.8 x 1.2 cm) Overall: 13 7/16 in. (34.2 cm) (open) Image (b): 10 x 7 3/8 x 7/16 in. (25.4 x 18.8 x 1.1 cm) Overall: 13 9/16 in. (34.5 cm) (open),"The Howard Mansfield Collection, Gift of Howard Mansfield, 1936",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/57790,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.343a–c,false,true,73596,Asian Art,Illustrated book,橫濱開港見聞誌|Observations on the Opening of Yokohama (Yokohama kaiko kanbunshi),Japan,Shōwa period (1926–89),,,,Artist,,Utagawa (Gountei) Sadahide,"Japanese, 1807–1878/79",,Utagawa (Goutei) Sadahide,Japanese,1807,1879,1967,1967,1967,Book; ink on paper,Image (a): 8 1/2 x 6 1/8 x 7/8 in. (21.6 x 15.6 x 2.2 cm) Image (b): 8 1/4 x 5 7/8 x 1/16 in. (21 x 14.9 x 0.2 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/73596,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -91.1.642,false,true,57779,Asian Art,Box,,Japan,Meiji period (1868–1912),,,,Artist,Style of,Ogata Kōrin,"Japanese, 1658–1716",,Kōrin,Japanese,1658,1716,second half of the 19th century,1850,1899,"Gold inlaid with mother-of-pearl and tin"" to ""Gold hiramaki-e, takamaki-e, tin and mother-of-pearl inlay on gold ground",H. 1 7/8 in. (4.8 cm); W. 3 9/16 in. (9 cm); D. 2 1/16 in. (5.2 cm),"Edward C. Moore Collection, Bequest of Edward C. Moore, 1891",,,,,,,,,,,,Lacquer,,http://www.metmuseum.org/art/collection/search/57779,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"36.100.162a, b",false,true,53643,Asian Art,Box,,Japan,Meiji period (1868–1912),,,,Artist,Style of,Ogawa Haritsu (Ritsuō),"Japanese, 1663–1747",,Ritsuō,Japanese,1663,1747,second half of 19th century,1850,1899,"Bottle gourd; gold, red takamaki-e, mother-of-pearl, tin, ceramic inlay",H. 4 in. (10.2 cm); Diam. at top 3 in. (7.6 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Lacquer,,http://www.metmuseum.org/art/collection/search/53643,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.754,false,true,78656,Asian Art,Illustrated book,,Japan,Meiji period (1868–1912),,,,Artist,After,Seizei Kigyoku,"Japanese, 1732–1756",,Seizei Kigyoku,Japanese,1732,1756,1901,1901,1901,Woodblock printed book; ink and color on paper,11 1/8 × 7 1/2 in. (28.2 × 19 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78656,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.805,false,true,78695,Asian Art,Illustrated book,,Japan,Meiji period (1868–1912),,,,Artist,,Sengai Gibon,"Japanese, 1750–1837",,Sengai Gibon,Japanese,1750,1837,1894,1894,1894,Woodblock printed book; ink on paper,Other: 9 1/16 × 5 7/8 in. (23 × 15 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78695,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.268.181,false,true,53644,Asian Art,Chest,,Japan,Meiji period (1868–1912),,,,Artist,Attributed to,Shibata Zeshin,"Japanese, 1807–1891",,Shibata Zeshin,Japanese,1807,1891,ca. 1878,1868,1888,Wood with black and gold lacquer,H. 25 1/2 in. (64.8 cm); W. 28 3/4 in. (73 cm); D. 10 1/8 in. (25.7 cm),"The Harry G. C. Packard Collection of Asian Art, Gift of Harry G. C. Packard, and Purchase, Fletcher, Rogers, Harris Brisbane Dick, and Louis V. Bell Funds, Joseph Pulitzer Bequest, and The Annenberg Fund Inc. Gift, 1975",,,,,,,,,,,,Lacquer,,http://www.metmuseum.org/art/collection/search/53644,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2010.143a–g,false,true,75746,Asian Art,Tiered box,明治時代 柴田是真 果蔬蒔絵重箱|Tiered Food Box with Summer and Autumn Fruits,Japan,Meiji period (1868–1912),,,,Artist,,Shibata Zeshin,"Japanese, 1807–1891",,Shibata Zeshin,Japanese,1807,1891,ca. 1868–90,1850,1900,"Brown lacquer with gold, silver, and colored lacquer maki-e",H. 16 1/8 in. (41 cm); W. 9 in. (22.9 cm); D. 9 5/8 in. (24.4 cm),"Purchase, The Vincent Astor Foundation Gift and Parnassus Foundation/Jane and Raphael Bernstein Gift, 2010",,,,,,,,,,,,Lacquer,,http://www.metmuseum.org/art/collection/search/75746,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.142a–j,false,true,44868,Asian Art,Writing box,明治時代 柴田是真派 蒲公英酒瓢蒔絵 硯箱|Writing Box with Gourd,Japan,Meiji period (1868–1912),,,,Artist,School of,Shibata Zeshin,"Japanese, 1807–1891",,Shibata Zeshin,Japanese,1807,1891,1886,1886,1886,"Black lacquer with gold and silver hiramaki-e, colored lacquer application",H. 1 1/2 in. (3.8 cm); W. 7 5/8 in. (19.4 cm); L. 8 7/8 in. (22.5 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Lacquer,,http://www.metmuseum.org/art/collection/search/44868,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -54.56,false,true,57347,Asian Art,Screen,花鳥図風炉先屏風|Folding Screen for Tea Ceremony with Six Bird-and-Flower Paintings,Japan,Meiji period (1868–1912),,,,Artist,In the style of,Shibata Zeshin,"Japanese, 1807–1891",,Shibata Zeshin,Japanese,1807,1891,late 19th century,1867,1899,"Two-panel folding screen; lacquer, color, and silver on paper",Overall (each panel): 14 1/2 x 31 in. (36.8 x 78.7 cm),"Gift of Mr. and Mrs. Nathan V. Hammer, 1954",,,,,,,,,,,,Screens,,http://www.metmuseum.org/art/collection/search/57347,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -53.119,false,true,57345,Asian Art,Folding screen,,Japan,Meiji period (1868–1912),,,,Artist,Style of,Shibata Zeshin,"Japanese, 1807–1891",,Shibata Zeshin,Japanese,1807,1891,1868–1912,1868,1912,Two-panel folding screen; color on silk,26 3/4 x 69 in. (67.9 x 175.3 cm),"Rogers Fund, 1953",,,,,,,,,,,,Screens,,http://www.metmuseum.org/art/collection/search/57345,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2011.572.3,false,true,77169,Asian Art,Screen,柴田是真筆 烏鷺図屏風|Three Crows in Flight and Two Egrets at Rest,Japan,Meiji period (1868–1912),,,,Artist,,Shibata Zeshin,"Japanese, 1807–1891",,Shibata Zeshin,Japanese,1807,1891,late 19th century,1867,1899,Two-panel folding screen; colored lacquer and white pigment on gilt paper,Image (each panel): 53 1/2 x 36 in. (135.9 x 91.4 cm) Overall (each panel): 60 7/8 x 39 1/4 in. (154.6 x 99.7 cm),"Fishbein-Bender Collection, Gift of T. Richard Fishbein and Estelle P. Bender, 2011",,,,,,,,,,,,Screens,,http://www.metmuseum.org/art/collection/search/77169,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.268.137,false,true,45080,Asian Art,Folding screen,月に秋草図屏風|Autumn Grasses in Moonlight,Japan,Meiji period (1868–1912),,,,Artist,,Shibata Zeshin,"Japanese, 1807–1891",,Shibata Zeshin,Japanese,1807,1891,second half of the 19th century,1850,1899,"Two-panel folding screen; ink, lacquer, silver, and silver leaf on paper","Image (each panel): 18 in. × 33 1/4 in. (45.7 × 84.5 cm) Each panel, with frame: 26 1/8 × 34 3/8 in. (66.4 × 87.3 cm) Overall with frame (both panels): 26 1/8 in. × 69 in. (66.4 × 175.3 cm)","The Harry G. C. Packard Collection of Asian Art, Gift of Harry G. C. Packard, and Purchase, Fletcher, Rogers, Harris Brisbane Dick, and Louis V. Bell Funds, Joseph Pulitzer Bequest, and The Annenberg Fund Inc. Gift, 1975",,,,,,,,,,,,Screens,,http://www.metmuseum.org/art/collection/search/45080,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.186,false,true,45443,Asian Art,Inrō,,Japan,Meiji period (1868–1912),,,,Artist,,Nakayama Komin,"Japanese, 1808–1870",,Nakayama Komin,Japanese,1808,1870,late 19th century,1871,1899,"Gold lacquer with gold hiramkie sprinkled and polished lacquer, nashiji (pear skin) lacquer, and mother-of-pearl, ivory, and wood inlay; Interior: nashiji and fundame; Netsuke: wood-framed ivory plaque with bird and flower inlay; Ojime: lacquer Daikoku's hammer",3 9/16 x 2 13/16 x 1 1/8 in. (9.1 x 7.1 x 2.9 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Inrō,,http://www.metmuseum.org/art/collection/search/45443,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"1998.189.1, .2",false,true,40006,Asian Art,Folding screens,,Japan,Meiji period (1868–1912),,,,Artist,,Shiokawa Bunrin,"Japanese, 1808–1877",,Shiokawa Bunrin,Japanese,1808,1877,1875,1875,1875,Pair of six-panel folding screens; ink and gold on paper,Image (each): 59 5/8 in. x 11 ft. 6 5/8 in. (151.4 x 352.1 cm),"Purchase, Friends of Asian Art Gifts, 1998",,,,,,,,,,,,Screens,,http://www.metmuseum.org/art/collection/search/40006,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.148,false,true,45581,Asian Art,Box,果蔬蒔絵菓子箱|Two-Tiered Box with Design of Autumn Fruits,Japan,Meiji period (1868–1912),,,,Artist,,Ikeda Taishin,"Japanese, 1825–1903",,Ikeda Taishin,Japanese,1825,1903,second half of the 19th century,1850,1899,"Lacquered wood with gold, silver, black, and red takamaki-e, hiramaki-e, and e-nashiji on black lacquer ground",H. 4 1/4 in. (10.8 cm); W. 4 1/4 in. (10.8 cm); L. 6 1/8 in. (15.6 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Lacquer,,http://www.metmuseum.org/art/collection/search/45581,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.767,false,true,78669,Asian Art,Illustrated book,『暁斎百鬼画談』|Kyōsai’s Pictures of One Hundred Demons (Kyōsai hyakki gadan),Japan,Meiji period (1868–1912),,,,Artist,,Kawanabe Kyōsai,"Japanese, 1831–1889",,Kawanabe Kyōsai,Japanese,1831,1889,1890 (first edition published posthumously in 1889),1890,1890,"Woodblock printed book (orihon, accordion-style); ink and color on paper",7 13/16 × 4 3/4 in. (19.8 × 12 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78669,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.764a–d,false,true,78666,Asian Art,illustrated books,『暁斎画談』|Kyōsai’s Treatise on Painting (Kyōsai gadan),Japan,Meiji period (1868–1912),,,,Artist,,Kawanabe Kyōsai,"Japanese, 1831–1889",,Kawanabe Kyōsai,Japanese,1831,1889,1887,1887,1887,Set of four woodblock printed books; ink and color on paper,each: 9 15/16 × 6 7/8 in. (25.3 × 17.5 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78666,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.748,false,true,78650,Asian Art,Illustrated book,,Japan,Meiji period (1868–1912),,,,Artist,,Nishiyama Ken,"Japanese, 1833–1897",,Nishiyama Ken,Japanese,1833,1897,1886,1886,1886,"Woodblock printed book (orihon, accordion-style); ink and color on paper",10 5/8 × 5 1/2 in. (27 × 14 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78650,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JIB4a–c,false,true,57539,Asian Art,Illustrated book,梅嶺百鳥畫譜|Bairei Picture Album of One Hundred Birds (Bairei hyakuchō gafu),Japan,Meiji period (1868–1912),,,,Artist,,Kōno Bairei,"Japanese, 1844–1895",,Kōno Bairei,Japanese,1844,1895,1881–84,1881,1884,Set of three polychrome woodblock printed books; ink and color on paper,Overall (vol. 1): 9 5/8 × 6 3/8 in. (24.5 × 16.2 cm) Overall (vol. 2): 9 3/4 × 6 9/16 in. (24.8 × 16.7 cm) Overall (vol. 3): 9 3/4 × 6 7/16 in. (24.8 × 16.3 cm),"Rogers Fund, 1918",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/57539,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.652a–c,false,true,78573,Asian Art,Illustrated books,楳嶺百鳥畫譜|Bairei Picture Album of One Hundred Birds (Bairei hyakuchō gafu),Japan,Meiji period (1868–1912),,,,Artist,,Kōno Bairei,"Japanese, 1844–1895",,Kōno Bairei,Japanese,1844,1895,1881–84,1881,1884,Set of three woodblock printed books; ink and color on paper,each: 9 1/2 × 6 13/16 in. (24.2 × 17.3 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78573,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2003.317.1, .2",false,true,65603,Asian Art,Screen,,Japan,Meiji period (1868–1912),,,,Artist,,Suzuki Shōnen,"Japanese, 1849–1918",,Suzuki Shōnen,Japanese,1849,1918,late 19th century,1867,1899,Pair of six-panel folding screens; ink on gold-leaf,Image (each): 68 1/16 in. x 12 ft. 1 3/16 in. (172.9 x 368.8 cm),"Purchase, The B. D. G. Leviton Foundation Gift, 2003",,,,,,,,,,,,Screens,,http://www.metmuseum.org/art/collection/search/65603,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.800,false,true,78816,Asian Art,Illustrated book,,Japan,Meiji period (1868–1912),,,,Artist,,Watanabe Seitei,"Japanese, 1851–1918",,Watanabe Seitei,Japanese,1851,1918,1891,1891,1891,Woodblock printed book; ink and color on paper,9 13/16 × 6 3/4 in. (25 × 17.2 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78816,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.320.2,false,true,60501,Asian Art,Vase,菊紋楓枝文七宝瓶 (一対)|Imperial Presentation Vase with Maple Branches and Imperial Chrysanthemum Crest (one of a pair),Japan,Meiji period (1868–1912),,,,Artist,,Kawade Shibatarō,"Japanese, 1861–1921",,Kawade Shibatarō,Japanese,1861,1921,ca. 1906,1896,1916,Standard and repoussé cloisonné enamel; silver wires and rims; signed: combined marks of Andō Cloisonné Company and Kawade Shibatarō,H. 17 1/8 in. (43.5 cm); W. 6 3/4 in. (17.1 cm); D. 4 1/2 in. (11.4 cm),"Gift of Barbara S. McKenna, 1976",,,,,,,,,,,,Cloisonné,,http://www.metmuseum.org/art/collection/search/60501,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.799,false,true,78815,Asian Art,Illustrated book,,Japan,Meiji period (1868–1912),,,,Artist,,Takeuchi Seihō,"Japanese, 1864–1942",,Takeuchi Seihō,Japanese,1864,1942,ca. 1905–6,1905,1906,"Woodblock printed book (orihon, accordion-style); ink, color and metallic pigments on paper",14 1/4 × 9 13/16 in. (36.2 × 25 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78815,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JIB202,false,true,65710,Asian Art,Illustrated book,,Japan,Meiji period (1868–1912),,,,Artist,,Tsukioka Kōgyo,"Japanese, 1869–1927",,Tsukioka Kōgyo,Japanese,1869,1927,1898–1899,1898,1899,Polychrome woodblock prints; ink and color on paper,Overall: 9 1/2 x 14 1/4 in. (24.1 x 36.2 cm),"Gift of Lincoln Kirstein, 1985",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/65710,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2013.789a, b",false,true,78691,Asian Art,Illustrated books,,Japan,Meiji period (1868–1912),,,,Artist,,Ogino Issui,"Japanese, active 1900–10",,Ogino Issui,Japanese,1900,1910,1903,1903,1903,Set of two woodblock printed books; ink and color on paper,each: 10 1/16 × 7 1/16 in. (25.5 × 18 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78691,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JIB83,false,true,57390,Asian Art,Illustrated book,,Japan,Meiji period (1868–1912),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,1878,1878,1878,Woodblock printed book; ink and color on paper,Overall: 9 1/8 × 6 × 3/8 in. (23.2 × 15.2 × 1 cm),"Rogers Fund, 1932",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/57390,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.365,false,true,44603,Asian Art,Calligraphy,Sono gi o aege|Embrace Righteousness,Japan,Taishō period (1912–26),,,,Artist,,Tomioka Tessai,"Japanese, 1836–1924",,Tomioka Tessai,Japanese,1836,1924,20th century,1912,1926,Framed calligraphy; ink on paper,Image: 12 3/8 × 36 3/16 in. (31.4 × 91.9 cm) Framed: 17 3/4 × 49 1/2 in. (45.1 × 125.7 cm),"Gift of Dr. Yukikazu Iwasa, in honor of Mrs. Shizuko Iwasa, 1989",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/44603,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -91.1.2068,false,true,62167,Asian Art,Basket,掛花籃|Large Flower Basket,Japan,Meiji period (1868–1912),,,,Artist,,Hayakawa Shōkōsai I,"Japanese, 1815–1897",,Hayakawa Shōkōsai I,Japanese,1815,1897,second half of the 19th century,1850,1899,Bamboo (madake) with rattan accents,H. 19 3/4 in. (50.2 cm); Diam. 16 in. (40.6 cm),"Edward C. Moore Collection, Bequest of Edward C. Moore, 1891",,,,,,,,,,,,Basketry,,http://www.metmuseum.org/art/collection/search/62167,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"55.94.1, .2",false,true,39665,Asian Art,Screen,"源氏物語図屏風「御幸」・「浮船」・「関谷」|Scenes from The Tale of Genji: “The Royal Outing,” “Ukifune,” and “The Gatehouse”",Japan,Momoyama period (1573–1615),,,,Artist,,Tosa Mitsuyoshi,"Japanese, 1539–1613",,TOSA MITSUYOSHI,Japanese,1539,1613,mid-16th–early 17th century,1550,1633,"Pair of four-panel folding screens; ink, color, and gold leaf on paper",Image (each screen): 65 1/2 in. × 11 ft. 8 in. (166.4 × 355.6 cm),"Fletcher Fund, 1955",,,,,,,,,,,,Screens,,http://www.metmuseum.org/art/collection/search/39665,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2006.42.1, .2",false,true,73167,Asian Art,Folding screens,,Japan,Momoyama period (1573–1615),,,,Artist,Attributed to,Kano Takanobu,"Japanese, 1571–1618",,Kano Takanobu,Japanese,1571,1618,ca. 1600,1590,1610,"Pair of six-panel folding screens; ink, color, and gold on gilded paper",Image (each screen): 66 7/16 x 149 1/2 in. (168.8 x 379.7 cm),"Purchase, Gift of Mrs. Russell Sage, Bequest of Stephen Whitney Phoenix, and other gifts, bequests and funds from various donors, by exchange, Joseph Pulitzer Bequest, and Rogers, Fletcher, Harris Brisbane Dick, and Louis V. Bell Funds, 2006",,,,,,,,,,,,Screens,,http://www.metmuseum.org/art/collection/search/73167,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"1975.268.46, .47",false,true,45180,Asian Art,Screen,商山四皓・蘇東坡風水洞|The Return to Court of the Four Graybeards of Mount Shang (left); Su Shi’s Visit to the Wind and Water Cave (right,Japan,Momoyama period (1573–1615),,,,Artist,In the Style of,Kano Mitsunobu,"Japanese, ca. 1561–1608",,Kano Mitsunobu,Japanese,1561,1608,late 16th century,1571,1599,"Pair of six-panel folding screens; ink, color, gold, and gold leaf on paper",Image (each screen): 68 3/4 in. x 12 ft. 4 7/8 in. (174.6 x 378.1 cm),"The Harry G. C. Packard Collection of Asian Art, Gift of Harry G. C. Packard, and Purchase, Fletcher, Rogers, Harris Brisbane Dick, and Louis V. Bell Funds, Joseph Pulitzer Bequest, and The Annenberg Fund Inc. Gift, 1975",,,,,,,,,,,,Screens,,http://www.metmuseum.org/art/collection/search/45180,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.736a–f,false,true,78638,Asian Art,Illustrated books,,Japan,Edo period (1615–1868) 1845.,,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,1845,1845,1845,Set of six woodblock printed books; ink on paper,10 1/16 × 6 15/16 in. (25.6 × 17.7 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78638,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"1975.268.44, .45",false,true,45258,Asian Art,Screen,四季竹図屏風|Bamboo in the Four Seasons,Japan,Muromachi period (1392–1573),,,,Artist,Attributed to,Tosa Mitsunobu,1434–1525,,Tosa Mitsunobu,Japanese,1434,1525,late 15th–early 16th century,1480,1525,"Pair of six-panel screens; ink, color, and gold leaf on paper",Image: 61 13/16 x 9 ft. 9 3/4 in. (157 x 360 cm),"The Harry G. C. Packard Collection of Asian Art, Gift of Harry G. C. Packard, and Purchase, Fletcher, Rogers, Harris Brisbane Dick, and Louis V. Bell Funds, Joseph Pulitzer Bequest, and The Annenberg Fund Inc. Gift, 1975",,,,,,,,,,,,Screens,,http://www.metmuseum.org/art/collection/search/45258,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"41.59.1, .2",false,true,42344,Asian Art,Painting,四季山水画 (瀟湘八景)|Landscape of the Four Seasons (Eight Views of the Xiao and Xiang Rivers),Japan,Muromachi period (1392–1573),,,,Artist,,Sōami,"Japanese, died 1525",,Sōami,Japanese,,1525,early 16th century,1500,1533,Pair of six-panel folding screens; ink on paper,Each: 68 1/4 × 146 in. (173.4 × 370.8 cm),"Gift of John D. Rockefeller Jr., 1941",,,,,,,,,,,,Screens,,http://www.metmuseum.org/art/collection/search/42344,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"1992.8.1, .2",true,true,44696,Asian Art,Screen,猿猴捉月図屏風|Gibbons in a Landscape,Japan,Muromachi period (1392–1573),,,,Artist,,Sesson Shūkei,ca. 1504–ca. 1589,,Sesson Shūkei,Japanese,1504,1589,ca. 1570,1560,1580,Pair of six-panel screens; ink on paper,Image (each screen): 62 in. x 11 ft. 5 in. (157.5 x 348 cm),"Purchase, Rogers Fund and The Vincent Astor Foundation, Mary Livingston Griggs and Mary Griggs Burke Foundation, and Florence and Herbert Irving Gifts, 1992",,,,,,,,,,,,Screens,,http://www.metmuseum.org/art/collection/search/44696,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"1991.480.1, .2",false,true,44673,Asian Art,Screen,琴棋書画図屏風|The Four Accomplishments,Japan,Muromachi period (1392–1573),,,,Artist,,Kano Motonobu,"Japan, ca. 1476–1559",,Kano Motonobu,Japanese,1476,1559,mid-16th century,1534,1566,Pair of six-panel folding screens; ink and color on paper,Image (each screen): 67 x 150 in. (170.2 x 381 cm),"Dr. and Mrs. Roger G. Gerry Collection, Gift of Dr. and Mrs. Roger G. Gerry, 1991",,,,,,,,,,,,Screens,,http://www.metmuseum.org/art/collection/search/44673,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2011.534,false,true,60467,Asian Art,Hanging scroll,墨蹟 「雪」|Poem on the Theme of Snow,Japan,Nanbokuchō period (1336–92),,,,Artist,,Musō Soseki,"Japanese, 1275–1351",,Musō Soseki,Japanese,1275,1351,14th century,1336,1392,Hanging scroll; ink on paper,Image: 11 3/4 x 32 1/2 in. (29.8 x 82.6 cm) Overall with mounting: 48 7/8 x 38 in. (124.1 x 96.5 cm) Overall with knobs: 48 7/8 x 40 1/4 in. (124.1 x 102.2 cm),"Gift of Sylvan Barnet and William Burto, in honor of Maxwell K. Hearn, 2011",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/60467,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2014.719.12,false,true,60468,Asian Art,Hanging scroll,"夢窓疎石筆 消息|Letter to Suwa Daishin, Officer of the Shogun",Japan,Nanbokuchō period (1336–92),,,,Artist,,Musō Soseki,"Japanese, 1275–1351",,Musō Soseki,Japanese,1275,1351,ca. 1339–51,1329,1361,Hanging scroll; ink on paper,Image: 11 5/16 × 14 1/16 in. (28.7 × 35.7 cm) Overall with mounting: 44 1/2 × 18 1/2 in. (113 × 47 cm) Overall with knobs: 44 1/2 × 20 1/4 in. (113 × 51.4 cm),"Gift of Sylvan Barnet and William Burto, in honor of John T. Carpenter, 2014",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/60468,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2014.719.6,false,true,42693,Asian Art,Hanging scroll,墨跡「糖」|Poem in Chinese about Sugar,Japan,Nanbokuchō period (1336–92),,,,Artist,,Kokan Shiren,"Japanese, 1278–1346",,Kokan Shiren,Japanese,1278,1346,14th century,1336,1392,Hanging scroll; ink on paper,Image: 12 1/4 x 18 5/8 in. (31.1 x 47.3 cm) Overall with mounting: 47 x 24 in. (119.4 x 61 cm) Overall with knobs: 47 x 25 13/16 in. (119.4 x 65.6 cm),"Gift of Sylvan Barnet and William Burto, in honor of Elizabeth and Neil Swinton, 2014",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/42693,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.840,false,true,78730,Asian Art,Illustrated book,,Japan,Edo period (1615–1868) Kihei.,,,,Artist,,Nishikawa Sukenobu,"Japanese, 1671–1750",,Nishikawa Sukenobu,Japanese,1671,1750,1768,1768,1768,Woodblock printed book; ink and color on paper,9 1/16 × 6 5/16 in. (23 × 16 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78730,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.765,false,true,78667,Asian Art,illustrated book,『暁斎漫画』|Kyōsai Sketchbook (Kyōsai manga),Japan,Edo period (1615–1868Meiji period (1868–1912),,,,Artist,,Kawanabe Kyōsai,"Japanese, 1831–1889",,Kawanabe Kyōsai,Japanese,1831,1889,1881,1881,1881,Woodblock printed book; ink and color on paper,8 3/4 × 5 7/8 in. (22.3 × 15 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78667,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2013.766a, b",false,true,78668,Asian Art,illustrated books,『暁斎楽画』|Kyōsai’s Drawings for Pleasure (Kyōsai rakuga),Japan,Edo period (1615–1868Meiji period (1868–1912),,,,Artist,,Kawanabe Kyōsai,"Japanese, 1831–1889",,Kawanabe Kyōsai,Japanese,1831,1889,1881,1881,1881,"Set of two woodblock-printed books (one volume orihon, accordion-style); ink and color on paper",each: 8 7/8 × 5 7/8 in. (22.5 × 15 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78668,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -62.36.1,false,true,44914,Asian Art,Screen,芥子図屏風|Red and White Poppies,Japan,Momoyama (1573–1615)– Edo (1615–1868) period,,,,Artist,Traditionally attributed to,Tosa Mitsumochi,active 1525–ca. 1559,,Tosa Mitsumochi,Japanese,1525,1559,early 17th century,1600,1633,"Six-panel folding screen; ink, color, and gold leaf on paper",65 3/4 x 147 1/2 in. (167.0 x 374.7 cm),"H. O. Havemeyer Collection, Gift of Mrs. Dunbar W. Bostwick, John C. Wilmerding, J. Watson Webb Jr., Harry H. Webb, and Samuel B. Webb, 1962",,,,,,,,,,,,Screens,,http://www.metmuseum.org/art/collection/search/44914,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"51.89.1, .2",false,true,44941,Asian Art,Screen,見立琴碁書画屏風|Parody of the Four Accomplishments,Japan,late Edo (1615–1868) or Meiji (1868–1912) period,,,,Artist,,Shibata Zeshin,"Japanese, 1807–1891",,Shibata Zeshin,Japanese,1807,1891,second half of the 19th century,1850,1899,Pair of six-panel folding screens; ink and color on gold leaf on paper,Overall (each screen): 47 1/2 x 112 1/2 in. (120.7 x 285.8 cm),"Fletcher Fund, 1951",,,,,,,,,,,,Screens,,http://www.metmuseum.org/art/collection/search/44941,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.790,false,true,78692,Asian Art,Illustrated book,,Japan,Edo period (1615–1868) or Meiji period (1868–1912),,,,Artist,Attributed to,Rinsai Ōkubo,"Japanese, 19th century",,Rinsai Ōkubo,Japanese,1800,1899,1810 or 1870,1810,1870,Woodblock printed book; ink and color on paper,11 1/8 × 7 1/16 in. (28.3 × 18 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78692,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2013.684a, b",false,true,78605,Asian Art,Illustrated books,,Japan,Edo period (1615–1868) Osaka. 1837. Publishers: Eirakuya Tōshirō,,,,Artist,,Hanzan (Matsukawa),"Japanese, 1820–1882",,Hanzan,Japanese,1820,1882,1837,1837,1837,Set of two woodblock printed books; ink on paper,each: 8 11/16 × 6 1/8 in. (22 × 15.5 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78605,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -31.101.2,false,true,55007,Asian Art,DUPLICATE: this is JP207,,Japan,,,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,1769,1769,1769,Polychrome woodblock print; ink and color on paper,12 x 5 3/4 in. (30.5 x 14.6 cm),"Gift of Louis V. Ledoux, 1931",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55007,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2271,false,true,54044,Asian Art,Woodblock print,,Japan,,,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",,Kubo Shunman,Japanese,1757,1820,probably 1815,1815,1815,Part of an album of woodblock prints (surimono); ink and color on paper,7 15/16 x 7 1/8 in. (20.2 x 18.1 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54044,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2272,false,true,54045,Asian Art,Print,,Japan,,,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",(?),Kubo Shunman,Japanese,1757,1820,probably 1815,1815,1815,Part of an album of woodblock prints (surimono); ink and color on paper,7 15/16 x 7 1/4 in. (20.2 x 18.4 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54045,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2273,false,true,54046,Asian Art,Print,,Japan,,,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",(?),Kubo Shunman,Japanese,1757,1820,probably 1815,1815,1815,Part of an album of woodblock prints (surimono); ink and color on paper,7 15/16 x 7 in. (20.2 x 17.8 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54046,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2274,false,true,54047,Asian Art,Print,,Japan,,,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",(?),Kubo Shunman,Japanese,1757,1820,probably 1815,1815,1815,Part of an album of woodblock prints (surimono); ink and color on paper,7 15/16 x 7 1/8 in. (20.2 x 18.1 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54047,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2277,false,true,54050,Asian Art,Print,,Japan,,,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",(?),Kubo Shunman,Japanese,1757,1820,probably 1815,1815,1815,Part of an album of woodblock prints (surimono); ink and color on paper,7 15/16 x 7 1/16 in. (20.2 x 17.9 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54050,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2279,false,true,54052,Asian Art,Print,,Japan,,,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",(?),Kubo Shunman,Japanese,1757,1820,probably 1815,1815,1815,Part of an album of woodblock prints (surimono); ink and color on paper,8 x 7 1/8 in. (20.3 x 18.1 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54052,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2306,false,true,54090,Asian Art,Print,,Japan,,,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",(?),Kubo Shunman,Japanese,1757,1820,probably 1814,1814,1814,Part of an album of woodblock prints (surimono); ink and color on paper,5 1/8 x 7 in. (13 x 17.8 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54090,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3685,false,true,55916,Asian Art,Print,,Japan,,,,,Artist,,Utagawa Toyokuni I,"Japanese, 1769–1825",,Utagawa Toyokuni I,Japanese,1769,1825,ca. 1797–1800,1797,1800,Polychrome woodblock print; ink and color on paper,16 1/4 x 11 1/8 in. (41.3 x 28.3 cm),"Gift of Lincoln Kirstein, 1985",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55916,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3686,false,true,55917,Asian Art,Print,,Japan,,,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,1854,1854,1854,One sheet of a triptych of polychrome woodblock prints; ink and color on paper,14 5/8 x 10 in. (37.1 x 25.4 cm),"Gift of Lincoln Kirstein, 1985",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55917,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"JP3683a, b",false,true,55914,Asian Art,Print,,Japan,,,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,1858,1858,1858,Diptych of polychrome woodblock prints; ink and color on paper,Each 14 1/4 x 9 3/4 in. (36.2 x 24.8 cm),"Gift of Lincoln Kirstein, 1985",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55914,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3684a–c,false,true,55915,Asian Art,Print,,Japan,,,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,1858,1858,1858,Triptych of polychrome woodblock prints; ink and color on paper,Each: 14 1/4 x 9 7/8 in. (36.2 x 25.1 cm),"Gift of Lincoln Kirstein, 1985",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55915,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2636,false,true,54203,Asian Art,Woodblock print,,Japan,,,,,Artist,,Watanabe Kazan,"Japanese, 1793–1841",,Watanabe Kazan,Japanese,1793,1841,ca. 1840,1830,1850,Polychrome woodblock print (surimono); ink and color on paper,7 3/4 x 7 1/8 in. (19.7 x 18.1 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54203,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3170,false,true,56723,Asian Art,Print,,Japan,,,,,Artist,,Shibata Zeshin,"Japanese, 1807–1891",,Shibata Zeshin,Japanese,1807,1891,ca. 1860,1850,1870,Polychrome woodblock print; ink and color on paper,Image: 8 1/2 × 11 5/8 in. (21.6 × 29.5 cm) Mat: 15 1/4 × 22 3/4 in. (38.7 × 57.8 cm),"Purchase, Gifts, Bequests, and Funds from various donors, by exchange, 1955",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56723,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3171,false,true,56724,Asian Art,Print,,Japan,,,,,Artist,,Shibata Zeshin,"Japanese, 1807–1891",,Shibata Zeshin,Japanese,1807,1891,ca. 1860,1850,1870,Polychrome woodblock print; ink and color on paper,Image: 7 1/8 × 9 1/2 in. (18.1 × 24.1 cm) Mat: 15 1/4 × 22 3/4 in. (38.7 × 57.8 cm),"Purchase, Gifts, Bequests, and Funds from various donors, by exchange, 1955",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56724,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3608a–c,false,true,55913,Asian Art,Print,横浜 岩亀見込の図|The Interior of the Gankiro Tea House in Yokohama,Japan,,,,,Artist,,Suzuki Hiroshige II,"Japanese, 1826–1869",,Suzuki Hiroshige II,Japanese,1826,1869,1861 (April),1861,1861,Triptych of polychrome woodblock prints; ink and color on paper,Oban triptych: 14 /14 x 28 7/8 in. (35.6 x 73.3 cm),"The Howard Mansfield Collection, Rogers Fund, by exchange, 1982",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55913,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3308,false,true,55412,Asian Art,Print,,Japan,,,,,Artist,,Ichiryūsai Yoshitoyo,"Japanese, 1830–1866",,Ichiryūsai Yoshitoyo,Japanese,1830,1866,ca. 1863,1853,1873,Polychrome woodblock print; ink and color on paper,14 1/2 x 9 7/8 in. (36.8 x 25.1 cm),"Gift of Lincoln Kirstein, 1959",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55412,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3315,false,true,55455,Asian Art,Print,浅草観音境内ニ於イテ興行仕候 佛蘭西曲馬|French Equestrian Circus on the grounds of Asakusa Kannon temple (Asakusa kannon keidai ni oite kōgyō tsukawashi sōrō-Furansu kyokuba),Japan,,,,,Artist,,Utagawa Kuniteru,"Japanese, 1830–1874",,Utagawa Kuniteru,Japanese,1830,1874,1871,1871,1871,Polychrome woodblock print; ink and color on paper,14 1/8 x 28 13/16 in. (35.9 x 73.2 cm),"Gift of Lincoln Kirstein, 1959",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55455,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3316,false,true,55457,Asian Art,Print,"港崎横浜一覧|A Glance at Miyosaki, Yokohama",Japan,,,,,Artist,,Utagawa Yoshimori,"Japanese, 1830–1884",,Utagawa Yoshimori,Japanese,1830,1884,ca. 1860,1850,1870,Diptych of polychrome woodblock prints; ink and color on paper,Oban; 13 7/8 x 18 7/8 in. (35.2 x 47.9 cm),"Gift of Lincoln Kirstein, 1959",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55457,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3200,false,true,55184,Asian Art,Print,,Japan,,,,,Artist,Attributed to,Yōshū (Hashimoto) Chikanobu,"Japanese, 1838–1912",,Yōshū (Hashimoto) Chikanobu,Japanese,1838,1912,ca. 1880,1870,1890,Polychrome woodblock print; ink and color on paper,14 1/2 x 9 3/8 in. (36.8 x 23.8 cm),"Gift of Lincoln Kirstein, 1959",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55184,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3702,false,true,55950,Asian Art,Print,"Keinen kachō gafu 景年花鳥畫譜|Two Birds and Crysanthemums, from Keinen kachō gafu (Keinen’s Flower-and-Bird Painting Manual)",Japan,,,,,Artist,,Imao Keinen,"Japanese, 1845–1924",,Imao Keinen,Japanese,1845,1924,1891,1891,1891,Polychrome woodblock print; ink and color on paper,14 1/2 x 10 1/16 in. (36.8 x 25.6 cm),"Bequest of Grace M. Pugh, 1985",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55950,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3703,false,true,55951,Asian Art,Print,"Keinen kachō gafu 景年花鳥畫譜|Cormorant and Kerria Rose (Yamabuki), from Keinen kachō gafu (Keinen’s Flower-and-Bird Painting Manual)",Japan,,,,,Artist,,Imao Keinen,"Japanese, 1845–1924",,Imao Keinen,Japanese,1845,1924,1891,1891,1891,Polychrome woodblock print; ink and color on paper,14 1/2 x 10 1/16 in. (36.8 x 25.6 cm),"Bequest of Grace M. Pugh, 1985",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55951,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3203,false,true,55187,Asian Art,Print,,Japan,,,,,Artist,,Mizuno Toshikata,"Japanese, 1866–1908",,Mizuno Toshikata,Japanese,1866,1908,ca. 1903,1893,1913,Polychrome woodblock print; ink and color on paper,8 1/2 x 11 3/8 in. (21.6 x 28.9 cm),"Gift of Lincoln Kirstein, 1959",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55187,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3205,false,true,55189,Asian Art,Print,Sakamoto Otasuku Kankyo ni tachite tekijo o nozomu zu|Sakamoto Otasuku,Japan,,,,,Artist,Attributed to,Mizuno Toshikata,"Japanese, 1866–1908",,Mizuno Toshikata,Japanese,1866,1908,ca. 1894,1884,1904,Polychrome woodblock print; ink and color on paper,7 x 9 1/4 in. (17.8 x 23.5 cm),"Gift of Lincoln Kirstein, 1959",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55189,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2353,false,true,54138,Asian Art,Print,"詩人と富士山『春雨集』 摺物帖|A Poet and Mount FujiFrom the Spring Rain Collection (Harusame shū), vol. 3",Japan,,,,,Artist,,Yashima Gakutei,"Japanese, 1786?–1868",,Yashima Gakutei,Japanese,1786,1868,ca. 1820s,1820,1829,Part of an album of woodblock prints (surimono); ink and color on paper,4 7/8 x 11 1/8 in. (12.4 x 28.3 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54138,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2380,false,true,54164,Asian Art,Woodblock print,,Japan,,,,,Artist,,Yashima Gakutei,"Japanese, 1786?–1868",,Yashima Gakutei,Japanese,1786,1868,probably 1817,1817,1817,Part of an album of woodblock prints (surimono); ink and color on paper,8 1/16 x 7 1/8 in. (20.5 x 18.1 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54164,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3208,false,true,55192,Asian Art,Print,,Japan,,,,,Artist,,Shōsai Ikkei,"Japanese, active ca. 1870",,Shōsai Ikkei,Japanese,1870,1870,ca. 1875,1865,1885,Polychrome woodblock print; ink and color on paper,14 x 9 1/2 in. (35.6 x 24.1 cm),"Gift of Lincoln Kirstein, 1959",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55192,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3227,false,true,55219,Asian Art,Print,,Japan,,,,,Artist,,Shōsai Ikkei,"Japanese, active ca. 1870",,Shōsai Ikkei,Japanese,1870,1870,ca. 1870,1860,1880,One sheet of a triptych of polychrome woodblock prints; ink and color on paper,Triptych 14 1/16 x 28 1/8 in. (35.7 x 71.4 cm),"Gift of Lincoln Kirstein, 1959",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55219,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3228,false,true,55220,Asian Art,Print,,Japan,,,,,Artist,,Shōsai Ikkei,"Japanese, active ca. 1870",,Shōsai Ikkei,Japanese,1870,1870,ca. 1870,1860,1880,One sheet of a triptych of polychrome woodblock prints; ink and color on paper,Triptych 14 1/16 x 28 1/8 in. (35.7 x 71.4 cm),"Gift of Lincoln Kirstein, 1959",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55220,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3229,false,true,55221,Asian Art,Print,,Japan,,,,,Artist,,Shōsai Ikkei,"Japanese, active ca. 1870",,Shōsai Ikkei,Japanese,1870,1870,ca. 1870,1860,1880,One sheet of a triptych of polychrome woodblock prints; ink and color on paper,Triptych 14 1/16 x 28 1/8 in. (35.7 x 71.4 cm),"Gift of Lincoln Kirstein, 1959",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55221,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2351,false,true,54136,Asian Art,Woodblock print,"胴乱印籠と懐中時計根付『春雨集』 摺物帖|Dōran (Square Leather Box Used as an Inrō) with a Watch as a NetsukeFrom the Spring Rain Collection (Harusame shū), vol. 3",Japan,,,,,Artist,,Hokusen Taigaku,"Japanese, active 1805–1825",,Hokusen Taigaku,Japanese,1805,1825,probably 1817,1817,1817,Part of an album of woodblock prints (surimono); ink and color on paper,5 9/16 x 7 1/4 in. (14.1 x 18.4 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54136,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3383,false,true,55549,Asian Art,Print,"Shunshoku, Onkyoku no Shirabe|Spring Scenery; Melody of a Musical Performance",Japan,,,,,Artist,,Utagawa Fusatane,"Japanese, active ca. 1849–80",,Utagawa Fusatane,Japanese,1849,1880,1877? (ink stain renders date partly illegible),1877,1877,Triptych of polychrome woodblock prints; ink and color on paper,14 1/2 x 28 5/8 in. (36.8 x 72.7 cm),"Gift of Lincoln Kirstein, 1960",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55549,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3380,false,true,55547,Asian Art,Print,亜米利加国|American Balloon Ascension (Amerikakoku),Japan,,,,,Artist,,Utagawa Yoshitora,"Japanese, active ca. 1850–80",,Utagawa Yoshitora,Japanese,1845,1880,"6th month, 1867",1867,1867,Polychrome woodblock print; ink and color on paper,14 x 28 1/2 in. (35.6 x 72.4 cm),"Gift of Lincoln Kirstein, 1960",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55547,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3074,false,true,56554,Asian Art,Print,,Japan,,,,,Artist,Attributed to,Kondo Kiyoharu,"Japanese, active ca. 1704–1720",,Kondo Kiyoharu,Japanese,1704,1720,ca. 1715,1705,1725,Polychrome woodblock print (hand colored); ink and color on paper,12 1/4 x 5 3/4 in. (31.1 x 14.6 cm),"Harris Brisbane Dick Fund and Rogers Fund, 1949",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56554,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2315,false,true,54099,Asian Art,Print,,Japan,,,,,Artist,,Ryūryūkyo Shinsai,"Japanese, active ca. 1799–1823",,Ryūryūkyo Shinsai,Japanese,1799,1823,probably 1816,1816,1816,Part of an album of woodblock prints (surimono); ink and color on paper,5 7/16 x 3 5/16 in. (13.8 x 8.4 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54099,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3312,false,true,55423,Asian Art,Print,Igirisujin|英吉利人|Englishmen,Japan,,,,,Artist,,Utagawa Yoshikazu,"Japanese, active ca. 1850–1870",,Utagawa Yoshikazu,Japanese,1845,1870,ca. 1862,1852,1872,Polychrome woodblock print; ink and color on paper,14 x 9 3/4 in. (35.6 x 24.8 cm),"Gift of Lincoln Kirstein, 1959",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55423,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3313,false,true,55437,Asian Art,Print,亜墨利加人|Amerikajin,Japan,,,,,Artist,,Utagawa Yoshikazu,"Japanese, active ca. 1850–1870",,Utagawa Yoshikazu,Japanese,1845,1870,1862,1862,1862,Polychrome woodblock print; ink and color on paper,14 1/2 x 9 3/4 in. (36.8 x 24.8 cm),"Gift of Lincoln Kirstein, 1959",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55437,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3321,false,true,55465,Asian Art,Print,魯西亜|Russians Reading and Writing,Japan,,,,,Artist,,Utagawa Yoshikazu,"Japanese, active ca. 1850–1870",,Utagawa Yoshikazu,Japanese,1845,1870,"1861 (Bunkyu 1st year, 2nd month)",1861,1861,Polychrome woodblock print; ink and color on paper,14 3/4 x 10 1/8 in. (37.5 x 25.7 cm),"Gift of Lincoln Kirstein, 1959",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55465,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3441,false,true,55648,Asian Art,Print,,Japan,,,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,early 20th century,1900,1933,Polychrome woodblock print; ink and color on paper,7 x 9 in. (17.8 x 22.9 cm),"Gift of Cole J. Younger, 1975",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55648,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3442,false,true,55649,Asian Art,Print,,Japan,,,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,early 20th century,1900,1933,Polychrome woodblock print; ink and color on paper,7 x 9 in. (17.8 x 22.9 cm),"Gift of Cole J. Younger, 1975",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55649,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3443,false,true,55650,Asian Art,Print,,Japan,,,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,early 20th century,1900,1933,Polychrome woodblock print; ink and color on paper,7 x 9 in. (17.8 x 22.9 cm),"Gift of Cole J. Younger, 1975",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55650,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3444,false,true,55651,Asian Art,Print,,Japan,,,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,early 20th century,1900,1933,Polychrome woodblock print; ink and color on paper,7 x 9 in. (17.8 x 22.9 cm),"Gift of Cole J. Younger, 1975",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55651,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3445,false,true,55652,Asian Art,Print,,Japan,,,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,early 20th century,1900,1933,Polychrome woodblock print; ink and color on paper,7 x 9 in. (17.8 x 22.9 cm),"Gift of Cole J. Younger, 1975",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55652,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3446,false,true,55653,Asian Art,Print,,Japan,,,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,early 20th century,1900,1933,Polychrome woodblock print; ink and color on paper,7 x 9 in. (17.8 x 22.9 cm),"Gift of Cole J. Younger, 1975",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55653,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3447,false,true,55654,Asian Art,Print,,Japan,,,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,early 20th century,1900,1933,Polychrome woodblock print; ink and color on paper,7 x 9 in. (17.8 x 22.9 cm),"Gift of Cole J. Younger, 1975",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55654,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3448,false,true,55655,Asian Art,Print,,Japan,,,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,early 20th century,1900,1933,Polychrome woodblock print; ink and color on paper,7 x 9 in. (17.8 x 22.9 cm),"Gift of Cole J. Younger, 1975",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55655,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3449,false,true,55656,Asian Art,Print,,Japan,,,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,early 20th century,1900,1933,Polychrome woodblock print; ink and color on paper,7 x 9 in. (17.8 x 22.9 cm),"Gift of Cole J. Younger, 1975",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55656,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3450,false,true,55657,Asian Art,Print,,Japan,,,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,early 20th century,1900,1933,Polychrome woodblock print; ink and color on paper,7 x 9 in. (17.8 x 22.9 cm),"Gift of Cole J. Younger, 1975",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55657,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3451,false,true,55658,Asian Art,Print,,Japan,,,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,early 20th century,1900,1933,Polychrome woodblock print; ink and color on paper,7 x 9 in. (17.8 x 22.9 cm),"Gift of Cole J. Younger, 1975",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55658,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3452,false,true,55659,Asian Art,Print,,Japan,,,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,early 20th century,1900,1933,Polychrome woodblock print; ink and color on paper,7 x 9 in. (17.8 x 22.9 cm),"Gift of Cole J. Younger, 1975",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55659,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3453,false,true,55660,Asian Art,Print,,Japan,,,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,early 20th century,1900,1933,Polychrome woodblock print; ink and color on paper,7 x 9 in. (17.8 x 22.9 cm),"Gift of Cole J. Younger, 1975",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55660,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3454,false,true,55661,Asian Art,Print,,Japan,,,,,Artist,Formerly Attributed to,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,early 20th century,1900,1933,Polychrome woodblock print; ink and color on paper,7 x 9 in. (17.8 x 22.9 cm),"Gift of Cole J. Younger, 1975",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55661,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3455,false,true,55662,Asian Art,Print,,Japan,,,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,early 20th century,1900,1933,Polychrome woodblock print; ink and color on paper,7 x 9 in. (17.8 x 22.9 cm),"Gift of Cole J. Younger, 1975",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55662,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3456,false,true,55663,Asian Art,Print,,Japan,,,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,early 20th century,1900,1933,Polychrome woodblock print; ink and color on paper,7 x 9 in. (17.8 x 22.9 cm),"Gift of Cole J. Younger, 1975",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55663,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3457,false,true,55664,Asian Art,Print,,Japan,,,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,early 20th century,1900,1933,Polychrome woodblock print; ink and color on paper,7 x 9 in. (17.8 x 22.9 cm),"Gift of Cole J. Younger, 1975",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55664,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3458,false,true,55665,Asian Art,Print,,Japan,,,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,early 20th century,1900,1933,Polychrome woodblock print; ink and color on paper,7 x 9 in. (17.8 x 22.9 cm),"Gift of Cole J. Younger, 1975",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55665,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3459,false,true,55666,Asian Art,Print,,Japan,,,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,early 20th century,1900,1933,Polychrome woodblock print; ink and color on paper,7 x 9 in. (17.8 x 22.9 cm),"Gift of Cole J. Younger, 1975",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55666,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3460,false,true,55667,Asian Art,Print,,Japan,,,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,early 20th century,1900,1933,Polychrome woodblock print; ink and color on paper,7 x 9 in. (17.8 x 22.9 cm),"Gift of Cole J. Younger, 1975",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55667,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3461,false,true,55668,Asian Art,Print,,Japan,,,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,early 20th century,1900,1933,Polychrome woodblock print; ink and color on paper,7 x 9 in. (17.8 x 22.9 cm),"Gift of Cole J. Younger, 1975",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55668,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3462,false,true,55669,Asian Art,Print,,Japan,,,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,early 20th century,1900,1933,Polychrome woodblock print; ink and color on paper,7 x 9 in. (17.8 x 22.9 cm),"Gift of Cole J. Younger, 1975",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55669,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3463,false,true,55670,Asian Art,Print,,Japan,,,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,early 20th century,1900,1933,Polychrome woodblock print; ink and color on paper,7 x 9 in. (17.8 x 22.9 cm),"Gift of Cole J. Younger, 1975",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55670,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3464,false,true,55671,Asian Art,Print,,Japan,,,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,early 20th century,1900,1933,Polychrome woodblock print; ink and color on paper,7 x 9 in. (17.8 x 22.9 cm),"Gift of Cole J. Younger, 1975",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55671,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3465,false,true,55672,Asian Art,Print,,Japan,,,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,early 20th century,1900,1933,Polychrome woodblock print; ink and color on paper,7 x 9 in. (17.8 x 22.9 cm),"Gift of Cole J. Younger, 1975",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55672,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3466,false,true,55673,Asian Art,Print,,Japan,,,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,early 20th century,1900,1933,Polychrome woodblock print; ink and color on paper,7 x 9 in. (17.8 x 22.9 cm),"Gift of Cole J. Younger, 1975",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55673,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3467,false,true,55674,Asian Art,Print,,Japan,,,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,early 20th century,1900,1933,Polychrome woodblock print; ink and color on paper,7 x 9 in. (17.8 x 22.9 cm),"Gift of Cole J. Younger, 1975",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55674,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3468,false,true,55675,Asian Art,Print,,Japan,,,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,early 20th century,1900,1933,Polychrome woodblock print; ink and color on paper,7 x 9 in. (17.8 x 22.9 cm),"Gift of Cole J. Younger, 1975",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55675,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3469,false,true,55676,Asian Art,Print,,Japan,,,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,early 20th century,1900,1933,Polychrome woodblock print; ink and color on paper,7 x 9 in. (17.8 x 22.9 cm),"Gift of Cole J. Younger, 1975",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55676,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3470,false,true,55677,Asian Art,Print,,Japan,,,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,early 20th century,1900,1933,Polychrome woodblock print; ink and color on paper,7 x 9 in. (17.8 x 22.9 cm),"Gift of Cole J. Younger, 1975",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55677,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3471,false,true,55678,Asian Art,Print,,Japan,,,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,early 20th century,1900,1933,Polychrome woodblock print; ink and color on paper,7 x 9 in. (17.8 x 22.9 cm),"Gift of Cole J. Younger, 1975",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55678,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3472,false,true,55679,Asian Art,Print,"東海道五十三次 袋井|Fukuroi, from the series The Fifty-three Stations of the Tōkaidō Road",Japan,,,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,early 20th century,1900,1933,Polychrome woodblock print; ink and color on paper,7 x 9 in. (17.8 x 22.9 cm),"Gift of Cole J. Younger, 1975",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55679,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3473,false,true,55680,Asian Art,Print,"東海道五十三次 白須賀|Shirasuka, from the series The Fifty-three Stations of the Tōkaidō Road",Japan,,,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,early 20th century,1900,1933,Polychrome woodblock print; ink and color on paper,7 x 9 in. (17.8 x 22.9 cm),"Gift of Cole J. Younger, 1975",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55680,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3474,false,true,55681,Asian Art,Print,"東海道五十三次 二川|Futakawa, from the series The Fifty-three Stations of the Tōkaidō Road",Japan,,,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,early 20th century,1900,1933,Polychrome woodblock print; ink and color on paper,7 x 9 in. (17.8 x 22.9 cm),"Gift of Cole J. Younger, 1975",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55681,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3475,false,true,55682,Asian Art,Print,"東海道五十三次 吉田|Yoshida, from the series The Fifty-three Stations of the Tōkaidō Road",Japan,,,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,early 20th century,1900,1933,Polychrome woodblock print; ink and color on paper,7 x 9 in. (17.8 x 22.9 cm),"Gift of Cole J. Younger, 1975",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55682,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3476,false,true,55683,Asian Art,Print,"東海道五十三次 御油|Goyu, from the series The Fifty-three Stations of the Tōkaidō Road",Japan,,,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,early 20th century,1900,1933,Polychrome woodblock print; ink and color on paper,7 x 9 in. (17.8 x 22.9 cm),"Gift of Cole J. Younger, 1975",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55683,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3477,false,true,55684,Asian Art,Print,"東海道五十三次 赤坂|Akasaka, from the series The Fifty-three Stations of the Tōkaidō Road",Japan,,,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,early 20th century,1900,1933,Polychrome woodblock print; ink and color on paper,7 x 9 in. (17.8 x 22.9 cm),"Gift of Cole J. Younger, 1975",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55684,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3478,false,true,55685,Asian Art,Print,"東海道五十三次 藤川|Fujikawa, from the series The Fifty-three Stations of the Tōkaidō Road",Japan,,,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,early 20th century,1900,1933,Polychrome woodblock print; ink and color on paper,7 x 9 in. (17.8 x 22.9 cm),"Gift of Cole J. Younger, 1975",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55685,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3479,false,true,55686,Asian Art,Print,"東海道五十三次 岡崎|Okazaki, from the series The Fifty-three Stations of the Tōkaidō Road",Japan,,,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,early 20th century,1900,1933,Polychrome woodblock print; ink and color on paper,7 x 9 in. (17.8 x 22.9 cm),"Gift of Cole J. Younger, 1975",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55686,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3480,false,true,55687,Asian Art,Print,"東海道五十三次 池鯉鮒|Chiryūshuku, from the series The Fifty-three Stations of the Tōkaidō Road",Japan,,,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,early 20th century,1900,1933,Polychrome woodblock print; ink and color on paper,7 x 9 in. (17.8 x 22.9 cm),"Gift of Cole J. Younger, 1975",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55687,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3481,false,true,55688,Asian Art,Print,"東海道五十三次 鳴海|Narumi, from the series The Fifty-three Stations of the Tōkaidō Road",Japan,,,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,early 20th century,1900,1933,Polychrome woodblock print; ink and color on paper,7 x 9 in. (17.8 x 22.9 cm),"Gift of Cole J. Younger, 1975",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55688,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3482,false,true,55689,Asian Art,Print,"東海道五十三次 宮|Miya, from the series The Fifty-three Stations of the Tōkaidō Road",Japan,,,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,early 20th century,1900,1933,Polychrome woodblock print; ink and color on paper,7 x 9 in. (17.8 x 22.9 cm),"Gift of Cole J. Younger, 1975",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55689,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3483,false,true,55690,Asian Art,Print,"東海道五十三次 桑名|Kuwana, from the series The Fifty-three Stations of the Tōkaidō Road",Japan,,,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,early 20th century,1900,1933,Polychrome woodblock print; ink and color on paper,7 x 9 in. (17.8 x 22.9 cm),"Gift of Cole J. Younger, 1975",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55690,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3484,false,true,55691,Asian Art,Print,"東海道五十三次 四日市|Yokkaichi, from the series The Fifty-three Stations of the Tōkaidō Road",Japan,,,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,early 20th century,1900,1933,Polychrome woodblock print; ink and color on paper,7 x 9 in. (17.8 x 22.9 cm),"Gift of Cole J. Younger, 1975",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55691,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3485,false,true,55692,Asian Art,Print,"東海道五十三次 石薬師|Ishiyakushi, from the series The Fifty-three Stations of the Tōkaidō Road",Japan,,,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,early 20th century,1900,1933,Polychrome woodblock print; ink and color on paper,7 x 9 in. (17.8 x 22.9 cm),"Gift of Cole J. Younger, 1975",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55692,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3486,false,true,55693,Asian Art,Print,"東海道五十三次 庄野|Shōno, from the series The Fifty-three Stations of the Tōkaidō Road",Japan,,,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,early 20th century,1900,1933,Polychrome woodblock print; ink and color on paper,7 x 9 in. (17.8 x 22.9 cm),"Gift of Cole J. Younger, 1975",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55693,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3487,false,true,55694,Asian Art,Print,"東海道五十三次 亀山|Kameyama, from the series The Fifty-three Stations of the Tōkaidō Road",Japan,,,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,early 20th century,1900,1933,Polychrome woodblock print; ink and color on paper,7 x 9 in. (17.8 x 22.9 cm),"Gift of Cole J. Younger, 1975",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55694,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3488,false,true,55695,Asian Art,Print,"東海道五十三次 関|Seki, from the series The Fifty-three Stations of the Tōkaidō Road",Japan,,,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,early 20th century,1900,1933,Polychrome woodblock print; ink and color on paper,7 x 9 in. (17.8 x 22.9 cm),"Gift of Cole J. Younger, 1975",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55695,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3489,false,true,55696,Asian Art,Print,"東海道五十三次 阪之下|Sakanoshita, from the series The Fifty-three Stations of the Tōkaidō Road",Japan,,,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,early 20th century,1900,1933,Polychrome woodblock print; ink and color on paper,7 x 9 in. (17.8 x 22.9 cm),"Gift of Cole J. Younger, 1975",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55696,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3490,false,true,55697,Asian Art,Print,"東海道五十三次 土山|Tsuchiyama, from the series The Fifty-three Stations of the Tōkaidō Road",Japan,,,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,early 20th century,1900,1933,Polychrome woodblock print; ink and color on paper,7 x 9 in. (17.8 x 22.9 cm),"Gift of Cole J. Younger, 1975",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55697,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3491,false,true,55698,Asian Art,Print,"東海道五十三次 水口|Mizukuchi, from the series The Fifty-three Stations of the Tōkaidō Road",Japan,,,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,early 20th century,1900,1933,Polychrome woodblock print; ink and color on paper,7 x 9 in. (17.8 x 22.9 cm),"Gift of Cole J. Younger, 1975",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55698,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3492,false,true,55699,Asian Art,Print,"東海道五十三次 石部|Ishibe, from the series The Fifty-three Stations of the Tōkaidō Road",Japan,,,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,early 20th century,1900,1933,Polychrome woodblock print; ink and color on paper,7 x 9 in. (17.8 x 22.9 cm),"Gift of Cole J. Younger, 1975",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55699,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3440a–c,false,true,55647,Asian Art,Woodblock print,木曾路之山川|Mountains and Rivers Along the Kisokaidō,Japan,,,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1930s,1930,1939,Triptych of polychrome woodblock prints; ink and color on paper,14 3/4 x 10 in. (37.5 x 25.4 cm),"Gift of Cole J. Younger, 1975",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55647,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3364,false,true,55532,Asian Art,Print,,Japan,Edo (1615–1868),,,,Artist,,Utagawa Yoshimori,"Japanese, 1830–1885",,Utagawa Yoshimori,Japanese,1830,1885,ca. 1865,1855,1875,Polychrome woodblock print; ink and color on paper,9 5/8 x 14 5/16 in. (24.4 x 36.4 cm),"Gift of Lincoln Kirstein, 1960",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55532,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP669,false,true,37116,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Shigenaga,1697–1756,,Shigenaga,Japanese,1697,1756,ca. 1765,1755,1775,Polychrome woodblock print; ink and color on paper,27 7/32 x 6 15/32 in. (69.2 x 16.4 cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37116,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP819,false,true,37263,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Shigenaga,1697–1756,,Shigenaga,Japanese,1697,1756,ca. 1722,1712,1732,Polychrome woodblock print; ink and color on paper (Urushi-e),Overall: 11 3/4 x 6in. (29.8 x 15.2cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37263,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2657,false,true,56836,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Shigenaga,1697–1756,,Shigenaga,Japanese,1697,1756,ca. 1738,1728,1748,Polychrome woodblock print; ink and color on paper,13 1/4 x 6 1/8 in. (33.7 x 15.6 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56836,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3102,false,true,45056,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kaigetsudō Dohan,active 1710–16,,Kaigetsudō Dohan,Japanese,1710,1716,ca. 1714,1704,1724,Polychrome woodblock print (sumizuri-e); ink and color on paper,22 1/4 x 12 in. (56.5 x 30.5 cm),"Harris Brisbane Dick Fund and Rogers Fund, 1949",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45056,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3103,false,true,45057,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kaigetsudō Dohan,active 1710–16,,Kaigetsudō Dohan,Japanese,1710,1716,ca. 1714,1704,1724,Polychrome woodblock print (sumizuri-e); ink and color on paper,24 1/8 x 12 1/2 in. (61.3 x 31.8 cm),"Harris Brisbane Dick Fund and Rogers Fund, 1949",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45057,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3104,false,true,45058,Asian Art,Print,短冊持立美人図|Courtesan with Poetry Card (Tanzaku) at New Year,Japan,Edo period (1615–1868),,,,Artist,,Kaigetsudō Dohan,active 1710–16,,Kaigetsudō Dohan,Japanese,1710,1716,ca. 1714,1704,1724,Polychrome woodblock print (sumizuri-e); ink and color on paper,23 1/2 x 12 1/2 in. (59.7 x 31.8 cm),"Harris Brisbane Dick Fund and Rogers Fund, 1949",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45058,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1477,false,true,52008,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Chōkōsai Eishō,"Japanese, 1793–99",,Chōkōsai Eishō,Japanese,1793,1799,ca. 1800,1790,1810,Polychrome woodblock print; ink and color on paper,14 15/16 x 10 in. (37.9 x 25.4cm),"Fletcher Fund, 1925",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/52008,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1571,false,true,55748,Asian Art,Print,「扇屋昼見世畧」|Interior of the House called Ōgiya,Japan,Edo period (1615–1868),,,,Artist,,Chōkōsai Eishō,"Japanese, 1793–99",,Chōkōsai Eishō,Japanese,1793,1799,ca. 1800,1790,1810,Triptych of polychrome woodblock prints; ink and color on paper,Each H. 15 5/16 in. (38.9 cm); W. 9 5/8 in. (24.4 cm),"Fletcher Fund, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55748,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2425a–c,false,true,56676,Asian Art,Woodblock print,「丁子屋畧見世」|The Chōjiya Pleasure House by Day (Chōjiya hiru-mise),Japan,Edo period (1615–1868),,,,Artist,,Chōkōsai Eishō,"Japanese, 1793–99",,Chōkōsai Eishō,Japanese,1793,1799,ca. 1798,1788,1808,Triptych of polychrome woodblock prints; ink and color on paper,Each H. 15 in. (38.1 cm); W. 9 3/4 in. (24.8 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56676,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP229,false,true,36701,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Hishikawa Moronobu,"Japanese, died 1694",,Hishikawa Moronobu,Japanese,,1694,late 17th century,1667,1699,Polychrome woodblock print; ink and color on paper,10 x 15 in. (25.4 x 38.1 cm),"Rogers Fund, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36701,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP647,false,true,37097,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Hishikawa Moronobu,"Japanese, died 1694",,Hishikawa Moronobu,Japanese,,1694,ca. 1675–80,1673,1682,Polychrome woodblock print; ink and color on paper (sumi-e (ink print),11 1/4 x 20 2/3 in. (28.6 x 52.5 cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37097,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP676,false,true,37123,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Hishikawa Moronobu,"Japanese, died 1694",,Hishikawa Moronobu,Japanese,,1694,ca. 1675–80,1665,1690,Polychrome woodblock print; ink and color on paper,9 x 13 1/4 in. (22.9 x 33.7 cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37123,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP677,false,true,37124,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Hishikawa Moronobu,"Japanese, died 1694",,Hishikawa Moronobu,Japanese,,1694,ca. 1690,1680,1700,Polychrome woodblock print; ink and color on paper,10 x 13 27/32 in. (25.4 x 35.2 cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37124,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP807,false,true,37251,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Hishikawa Moronobu,"Japanese, died 1694",,Hishikawa Moronobu,Japanese,,1694,ca. 1680,1670,1690,Polychrome woodblock print; ink and color on paper,10 1/8 x 15 1/5 in. (25.7 x 38.6 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37251,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP808,false,true,37252,Asian Art,Print,"よしわらの躰 揚屋町入り口|The Entrance to Ageya-machi, from the series Scenes in the Yoshiwara (Yoshiwara no tei)",Japan,Edo period (1615–1868),,,,Artist,,Hishikawa Moronobu,"Japanese, died 1694",,Hishikawa Moronobu,Japanese,,1694,1681–84,1681,1684,Woodblock print (sumizuri-e); ink on paper,10 1/8 x 15 1/5 in. (25.7 x 38.6 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37252,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP809,false,true,37253,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Hishikawa Moronobu,"Japanese, died 1694",,Hishikawa Moronobu,Japanese,,1694,ca. 1680,1670,1690,Monochrome woodblock print; ink on paper,10 1/8 x 15 1/5 in. (25.7 x 38.6 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37253,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP810,false,true,37254,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Hishikawa Moronobu,"Japanese, died 1694",,Hishikawa Moronobu,Japanese,,1694,ca. 1680,1670,1690,Polychrome woodblock print; ink and color on paper,10 1/8 x 15 1/5 in. (25.7 x 38.6 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37254,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP811,false,true,37255,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Hishikawa Moronobu,"Japanese, died 1694",,Hishikawa Moronobu,Japanese,,1694,ca. 1680,1670,1680,Monochrome woodblock print; ink and color on paper,10 1/8 x 15 1/5 in. (25.7 x 38.6 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37255,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP812,false,true,37256,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Hishikawa Moronobu,"Japanese, died 1694",,Hishikawa Moronobu,Japanese,,1694,ca. 1680,1670,1690,Woodblock print; ink on paper,10 1/8 x 15 1/5 in. (25.7 x 38.6 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37256,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP813,false,true,37257,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Hishikawa Moronobu,"Japanese, died 1694",,Hishikawa Moronobu,Japanese,,1694,ca. 1680,1670,1690,Polychrome woodblock print; ink and color on paper,11 1/4 x 17 31/32 in. (28.6 x 45.7 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37257,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP814,false,true,37258,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Hishikawa Moronobu,"Japanese, died 1694",,Hishikawa Moronobu,Japanese,,1694,ca. 1685,1675,1695,Polychrome woodblock print; ink and color on paper,8 5/8 x 12 7/8 in. (21.9 x 32.7 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37258,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1590,false,true,45045,Asian Art,Print,Wakoku Hyakujo|Leaf from One Hundred Japanese Women,Japan,Edo period (1615–1868),,,,Artist,,Hishikawa Moronobu,"Japanese, died 1694",,Hishikawa Moronobu,Japanese,,1694,ca. 1695,1685,1705,Monochrome woodblock print; ink on paper,H. 6 1/2 in. (16.5 cm); W. 6 3/4 in. (17.1 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45045,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1594,false,true,55758,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Hishikawa Moronobu,"Japanese, died 1694",,Hishikawa Moronobu,Japanese,,1694,late 17th century,1667,1699,Monochrome woodblock print; ink on paper,H. 10 1/16 in. (25.6 cm); W. 15 3/16 in. (38.6 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55758,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2643,false,true,56825,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Hishikawa Moronobu,"Japanese, died 1694",,Hishikawa Moronobu,Japanese,,1694,ca. 1685,1675,1695,Monochrome woodblock print (sumie); ink on paper,11 x 16 1/4 in. (27.9 x 41.3 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56825,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3069,false,true,56550,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Hishikawa Moronobu,"Japanese, died 1694",,Hishikawa Moronobu,Japanese,,1694,1680s,1680,1689,Woodblock print; ink on paper,9 1/4 × 13 1/4 in. (23.5 × 33.7 cm),"Harris Brisbane Dick Fund and Rogers Fund, 1949",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56550,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3402,false,true,55584,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Hishikawa Moronobu,"Japanese, died 1694",,Hishikawa Moronobu,Japanese,,1694,late 17th century,1667,1694,Monochrome woodblock print; ink on paper,10 3/4 x 15 1/4 in. (27.3 x 38.7 cm),"Bequest of Julia H. Manges, 1960",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55584,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP123,false,true,36602,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunjō,"Japanese, died 1787",,Katsukawa Shunjō,Japanese,1700,1787,ca. 1780,1770,1790,Polychrome woodblock print; ink and color on paper,11 3/8 x 5 1/2 in. (28.9 x 14.0 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36602,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP386,false,true,36850,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunjō,"Japanese, died 1787",,Katsukawa Shunjō,Japanese,1700,1787,"2nd month, 1782",1782,1782,Polychrome woodblock print; ink and color on paper,11 7/8 x 5 5/8 in. (30.2 x 14.3 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36850,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP388,false,true,36851,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunjō,"Japanese, died 1787",,Katsukawa Shunjō,Japanese,1700,1787,1779,1779,1779,Diptych of polychrome woodblock prints; ink and color on paper,a: H. 12 13/16 in (32.5 cm); W. 5 3/4 in. (14.6 cm) b: H. 12 3/4 in. (32.4 cm); W. 5 11/16 in. (14.4 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36851,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1303,false,true,55242,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunjō,"Japanese, died 1787",,Katsukawa Shunjō,Japanese,1700,1787,ca. 1780,1770,1790,Polychrome woodblock print; ink and color on paper,H. 12 1/4 in. (31.1 cm); W. 5 3/4 in. (14.6 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55242,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1402,false,true,55410,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunjō,"Japanese, died 1787",,Katsukawa Shunjō,Japanese,1700,1787,ca. 1780,1770,1790,Polychrome woodblock print; ink and color on paper,H. 12 5/8 in. (32.1 cm); W. 6 1/4 in. (15.9 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55410,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1541,false,true,55706,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunjō,"Japanese, died 1787",,Katsukawa Shunjō,Japanese,1700,1787,ca. 1785,1775,1795,Polychrome woodblock print; ink and color on paper,H. 13 in. (33 cm); W. 5 3/4 in. (14.6 cm),"Fletcher Fund, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55706,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2678,false,true,56862,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunjō,"Japanese, died 1787",,Katsukawa Shunjō,Japanese,1700,1787,ca. 1790,1780,1810,Polychrome woodblock print; ink and color on paper,13 x 5 1/2 in. (33 x 14 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56862,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2679,false,true,56863,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunjō,"Japanese, died 1787",,Katsukawa Shunjō,Japanese,1700,1787,ca. 1785,1775,1795,Polychrome woodblock print; ink and color on paper,12 1/2 x 5 5/8 in. (31.8 x 14.3 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56863,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2905,false,true,56020,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunjō,"Japanese, died 1787",,Katsukawa Shunjō,Japanese,1700,1787,1700–1787,1700,1787,Polychrome woodblock print; ink and color on paper,12 1/8 x 5 1/2 in. (30.8 x 14 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56020,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP191,false,true,36668,Asian Art,Woodblock print,"『浅草観音奉掛額之 図』 「扇屋内滝川 富川、粂川、玉川、津川、歌川、清川 め浪、お浪」|A Votive Picture to Be Donated to the Kannon of Asakusa (Asakusa Kannon hō kakegaku no zu), by Takigawa of the Ōgiya, Kamuro Menami and Onami, with Tomikawa, Kumegawa, Tamagawa, Tsugawa, Utagawa, and Kiyokawa",Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Kikumaro,"Japanese, died 1830",,Kitagawa Kikumaro,Japanese,,1830,ca. 1800,1790,1810,Triptych of polychrome woodblock prints; ink and color on paper,Overall: H. 14 3/4 in. (37.5 cm); W. 23 3/4 in. (60. 3 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36668,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1091,false,true,55028,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Kikumaro,"Japanese, died 1830",,Kitagawa Kikumaro,Japanese,,1830,ca. 1815,1805,1825,Polychrome woodblock print; ink and color on paper,Aiban; H. 13 3/4 in. (34.9 cm); W. 9 1/16 in. (23 cm),The Metropolitan Museum of Art,,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55028,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1968,false,true,54552,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Kikumaro,"Japanese, died 1830",,Kitagawa Kikumaro,Japanese,,1830,probably 1815,1815,1815,Polychrome woodblock print (surimono); ink and color on paper,8 1/4 x 7 1/4 in. (21 x 18.4 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54552,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2011.135,false,true,76565,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Shunbaisai Hokuei,"Japanese, died 1837",,Shunbaisai Hokuei,Japanese,,1837,1832,1832,1832,Polychrome woodblock print,Image (ôban tate-e): 14 7/8 x 10 1/8 in. (37.8 x 25.7 cm),"Purchase, Friends of Asian Art Gifts, in honor of James C. Y. Watt, 2011",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/76565,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2011.138,false,true,76568,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Shunbaisai Hokuei,"Japanese, died 1837",,Shunbaisai Hokuei,Japanese,,1837,1835,1835,1835,Polychrome woodblock print,Image (ôban tate-e): 14 5/8 x 10 1/8 in. (37.1 x 25.7 cm),"Purchase, Friends of Asian Art Gifts, in honor of James C. Y. Watt, 2011",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/76568,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2011.139,false,true,76569,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Shunbaisai Hokuei,"Japanese, died 1837",,Shunbaisai Hokuei,Japanese,,1837,1837,1837,1837,Polychrome woodblock print,Image (ôban tate-e): 15 1/4 x 10 in. (38.7 x 25.4 cm),"Purchase, Friends of Asian Art Gifts, in honor of James C. Y. Watt, 2011",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/76569,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2011.137a–d,false,true,76567,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Shunbaisai Hokuei,"Japanese, died 1837",,Shunbaisai Hokuei,Japanese,,1837,1835,1835,1835,Tetraptych of polychrome woodblock prints,Each sheet (ôban tate-e tetraptych): 14 5/8 x 10 1/8 in. (37.1 x 25.7 cm),"Purchase, Friends of Asian Art Gifts, in honor of James C. Y. Watt, 2011",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/76567,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1000,false,true,54877,Asian Art,Print,"風流六玉川 紀伊 高野の玉川|The Kōya no Tamagawa, Province of Kii",Japan,Edo period (1615–1868),,,,Artist,,Utamaro II,Japanese (died 1831?),,Utamaro II,Japanese,1750,1850,ca. 1806,1796,1816,Polychrome woodblock print; ink and color on paper,H. 13 7/8 in. (35.2 cm); W. 8 7/8 in. (22.5 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54877,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1001,false,true,54878,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Utamaro II,Japanese (died 1831?),,Utamaro II,Japanese,1750,1850,ca. 1806,1796,1816,Polychrome woodblock print; ink and color on paper,H. 15 in. (38.1 cm); W. 9 5/8 in. (24.4 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54878,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1002,false,true,54879,Asian Art,Print,遊君出そめ初衣裳 扇屋内花扇|The Oiran Hanaogi of Ogiya attended by Two Shinzo and Her Kamuro Yoshino,Japan,Edo period (1615–1868),,,,Artist,,Utamaro II,Japanese (died 1831?),,Utamaro II,Japanese,1750,1850,ca. 1806,1796,1816,Polychrome woodblock print; ink and color on paper,H. 14 5/8 in. (37.1 cm); W. 9 1/2 in. (24.1 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54879,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1991.341,false,true,54231,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Utamaro II,Japanese (died 1831?),,Utamaro II,Japanese,1750,1850,ca. 1807,1797,1817,Polychrome woodblock print (surimono); ink and color on paper,15 3/4 x 21 5/8 in. (40 x 54.9 cm) (unfolded),"Gift of Joan B. Mirviss and Robert J. Levine, in memory of Carolyn D. Solomon, 1991",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54231,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP648,false,true,37098,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyonobu,"Japanese, 1664–1729",,Torii Kiyonobu,Japanese,1664,1729,ca. 1701–06,1699,1708,Polychrome woodblock print; ink and color on paper,20 31/32 x 11 7/8 in. (53.3 x 30.2 cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37098,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP649,false,true,37099,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyonobu,"Japanese, 1664–1729",,Torii Kiyonobu,Japanese,1664,1729,ca. 1700–05,1695,1715,Polychrome woodblock print; ink and color on paper,22 x 12 3/4 in. (55.9 x 32.4 cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37099,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP650,false,true,37100,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyonobu,"Japanese, 1664–1729",,Torii Kiyonobu,Japanese,1664,1729,ca. 1725,1715,1735,Polychrome woodblock print; ink and color on paper,27 15/32 x 6 1/8 in. (69.8 x 15.6 cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37100,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP680,false,true,37127,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyonobu,"Japanese, 1664–1729",,Torii Kiyonobu,Japanese,1664,1729,ca. 1705,1695,1715,Polychrome woodblock print; ink and color on paper,11 7/8 x 5 3/4 in. (30.2 x 14.6 cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37127,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP681,false,true,37128,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyonobu,"Japanese, 1664–1729",,Torii Kiyonobu,Japanese,1664,1729,ca. 1705,1695,1715,Polychrome woodblock print; ink and color on paper,11 3/5 x 5 31/32 in. (29.5 x 15.2 cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37128,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP828,false,true,54482,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyonobu,"Japanese, 1664–1729",,Torii Kiyonobu,Japanese,1664,1729,1748,1748,1748,Polychrome woodblock print; ink and color on paper,H. 12 1/2 in. (31.8 cm); W. 5 7/8 in. (14.9 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54482,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP829,false,true,54483,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyonobu,"Japanese, 1664–1729",,Torii Kiyonobu,Japanese,1664,1729,ca. 1744,1734,1754,Polychrome woodblock print; ink and color on paper,H. 12 1/8 in. (30.8 cm); 5 5/8 in. (14.3 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54483,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP830,false,true,54484,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyonobu,"Japanese, 1664–1729",,Torii Kiyonobu,Japanese,1664,1729,ca. 1745,1735,1755,Polychrome woodblock print; ink and color on paper,H. 12 1/4 in. (31.1 cm); W. 6 in. (15.2 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54484,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP831,false,true,54485,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyonobu,"Japanese, 1664–1729",,Torii Kiyonobu,Japanese,1664,1729,ca. 1745,1735,1755,Polychrome woodblock print; ink and color on paper,H. 12 3/8 in. (31.4 cm); W. 6 in. (15.2 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54485,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1448,false,true,55494,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyonobu,"Japanese, 1664–1729",,Torii Kiyonobu,Japanese,1664,1729,ca. 1750,1740,1760,Polychrome woodblock print; ink and color on paper,H. 11 5/8 in. (29.5 cm); W. 5 3/4 in. (14.6 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55494,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1464,false,true,37326,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyonobu,"Japanese, 1664–1729",,Torii Kiyonobu,Japanese,1664,1729,ca. 1742,1732,1752,Polychrome woodblock print; ink and color on paper,H. 10 7/8 in. (27.6 cm); W. 5 3/4 in. (14.6 cm),"Rogers Fund, 1925",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37326,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2623,false,true,56766,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyonobu,"Japanese, 1664–1729",,Torii Kiyonobu,Japanese,1664,1729,ca. 1720–25,1720,1725,Polychrome woodblock print; ink and color on paper,13 1/4 x 6 1/4 in. (33.7 x 15.9 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56766,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3071,false,true,56552,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyonobu,"Japanese, 1664–1729",,Torii Kiyonobu,Japanese,1664,1729,1703,1703,1703,Polychrome woodblock print (hand-colored); ink and color on paper,10 3/4 x 14 1/2 in. (27.3 x 36.8 cm),"Harris Brisbane Dick Fund and Rogers Fund, 1949",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56552,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3097,false,true,45060,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyonobu,"Japanese, 1664–1729",,Torii Kiyonobu,Japanese,1664,1729,1698,1688,1708,Polychrome woodblock print (sumizuri-e); ink and color on paper,23 1/4 x 12 1/2 in. (59.1 x 31.8 cm),"Harris Brisbane Dick Fund and Rogers Fund, 1949",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45060,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3098,false,true,56610,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,Attributed to,Torii Kiyonobu,"Japanese, 1664–1729",,Torii Kiyonobu,Japanese,1664,1729,ca. 1708,1698,1718,Polychrome woodblock print; ink and color on paper,21 3/4 x 11 1/2 in. (55.2 x 29.2 cm),"Harris Brisbane Dick Fund and Rogers Fund, 1949",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56610,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP672,false,true,37119,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Nishikawa Sukenobu,"Japanese, 1671–1750",,Nishikawa Sukenobu,Japanese,1671,1750,1731,1731,1731,Polychrome woodblock print; ink and color on paper,10 3/4 x 13 1/10 in. (27.3 x 33.3 cm),"Gift of Mrs. Henry J. Bernheim, 1945",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37119,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2717,false,true,56985,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Nishikawa Sukenobu,"Japanese, 1671–1750",,Nishikawa Sukenobu,Japanese,1671,1750,ca. 1730,1720,1740,Monochrome woodblock print; ink on paper,10 3/8 x 16 1/2 in. (26.4 x 41.9 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56985,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2755,false,true,57021,Asian Art,Book illustration,,Japan,Edo period (1615–1868),,,,Artist,,Nishikawa Sukenobu,"Japanese, 1671–1750",,Nishikawa Sukenobu,Japanese,1671,1750,1671–1751,1671,1751,Monochrome woodblock print; ink on paper,14 1/2 x 10 1/4 in. (36.8 x 26 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/57021,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP188,false,true,36658,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Okumura Masanobu,"Japanese, 1686–1764",,Masanobu Okumura,Japanese,1686,1764,ca. 1738,1728,1748,"Polychrome woodblock print; ink, color, and hand-coloring on paper",11 1/4 x 16 1/2 in. (28.6 x 41.9 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36658,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP189,false,true,36666,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Okumura Masanobu,"Japanese, 1686–1764",,Masanobu Okumura,Japanese,1686,1764,ca. 1748,1738,1758,Polychrome woodblock print; ink and color on paper,27 5/8 x 9 7/8 in. (70.2 x 25.1 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36666,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP539,false,true,36990,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Okumura Masanobu,"Japanese, 1686–1764",,Masanobu Okumura,Japanese,1686,1764,ca. 1709,1699,1719,Polychrome woodblock print; ink and color on paper,10 3/8 x 14 3/4 in. (26.4 x 37.5 cm),"Rogers Fund, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36990,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP540,false,true,36991,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Okumura Masanobu,"Japanese, 1686–1764",,Masanobu Okumura,Japanese,1686,1764,probably 1709,1707,1711,Polychrome woodblock print; ink and color on paper,10 3/8 x 14 3/4 in. (26.4 x 37.5 cm),"Rogers Fund, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36991,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP541,false,true,36992,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Okumura Masanobu,"Japanese, 1686–1764",,Masanobu Okumura,Japanese,1686,1764,probably 1709,1707,1711,Polychrome woodblock print; ink and color on paper,10 3/8 x 14 3/4 in. (26.4 x 37.5 cm),"Rogers Fund, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36992,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP542,false,true,36993,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Okumura Masanobu,"Japanese, 1686–1764",,Masanobu Okumura,Japanese,1686,1764,1710–13,1710,1713,Polychrome woodblock print; ink and color on paper,10 3/8 x 14 3/4 in. (26.4 x 37.5 cm),"Rogers Fund, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36993,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP543,false,true,36994,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Okumura Masanobu,"Japanese, 1686–1764",,Masanobu Okumura,Japanese,1686,1764,probably 1709,1707,1711,Polychrome woodblock print; ink and color on paper,10 7/32 x 14 3/4 in. (26.0 x 37.5 cm),"Rogers Fund, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36994,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP544,false,true,36995,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Okumura Masanobu,"Japanese, 1686–1764",,Masanobu Okumura,Japanese,1686,1764,probably 1709,1707,1711,Polychrome woodblock print; ink and color on paper,10 3/8 x 14 3/4 in. (26.4 x 37.5 cm),"Rogers Fund, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36995,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP545,false,true,36996,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Okumura Masanobu,"Japanese, 1686–1764",,Masanobu Okumura,Japanese,1686,1764,probably 1709,1707,1711,Polychrome woodblock print; ink and color on paper,10 3/8 x 14 3/4 in. (26.4 x 37.5 cm),"Rogers Fund, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36996,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP546,false,true,36997,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Okumura Masanobu,"Japanese, 1686–1764",,Masanobu Okumura,Japanese,1686,1764,1710–13,1710,1713,Polychrome woodblock print; ink and color on paper,10 3/8 x 14 3/4 in. (26.4 x 37.5 cm),"Rogers Fund, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36997,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP547,false,true,36998,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Okumura Masanobu,"Japanese, 1686–1764",,Masanobu Okumura,Japanese,1686,1764,ca. 1710,1690,1700,Polychrome woodblock print; ink and color on paper,10 3/8 x 14 3/4 in. (26.4 x 37.5 cm),"Rogers Fund, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36998,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP548,false,true,36999,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Okumura Masanobu,"Japanese, 1686–1764",,Masanobu Okumura,Japanese,1686,1764,probably 1709,1707,1711,Polychrome woodblock print; ink and color on paper,10 3/8 x 14 3/4 in. (26.4 x 37.5 cm),"Rogers Fund, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36999,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP549,false,true,37000,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Okumura Masanobu,"Japanese, 1686–1764",,Masanobu Okumura,Japanese,1686,1764,probably 1709,1707,1711,Polychrome woodblock print; ink and color on paper,10 3/8 x 14 3/4 in. (26.4 x 37.5 cm),"Rogers Fund, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37000,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP550,false,true,37001,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Okumura Masanobu,"Japanese, 1686–1764",,Masanobu Okumura,Japanese,1686,1764,1710–13,1710,1713,Polychrome woodblock print; ink and color on paper,10 3/8 x 13 27/32 in. (26.4 x 35.2 cm),"Rogers Fund, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37001,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP666,false,true,37113,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Okumura Masanobu,"Japanese, 1686–1764",,Masanobu Okumura,Japanese,1686,1764,ca. 1763,1753,1773,Polychrome woodblock print; ink and color on paper,28 23/32 x 5 7/32 in. (73.0 x 13.3 cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37113,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP678,false,true,37125,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Okumura Masanobu,"Japanese, 1686–1764",,Masanobu Okumura,Japanese,1686,1764,ca. 1700,1690,1710,Polychrome woodblock print; ink and color on paper,10 3/8 x 14 3/4 in. (26.4 x 37.5 cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37125,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP679,false,true,37126,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Okumura Masanobu,"Japanese, 1686–1764",,Masanobu Okumura,Japanese,1686,1764,ca. 1700–1703,1690,1713,Polychrome woodblock print; ink and color on paper,10 1/8 x 13 23/32 in. (25.7 x 34.9 cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37126,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP682,false,true,37129,Asian Art,Print,"見立紫式部図|Parody of Murasaki Shikibu, Author of The Tale of Genji",Japan,Edo period (1615–1868),,,,Artist,,Okumura Masanobu,"Japanese, 1686–1764",,Masanobu Okumura,Japanese,1686,1764,early 18th century,1700,1733,Monochrome woodblock print; ink on paper,10 3/8 x 14 1/8 in. (26.4 x 35.9 cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37129,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP683,false,true,37130,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Okumura Masanobu,"Japanese, 1686–1764",,Masanobu Okumura,Japanese,1686,1764,early 18th century,1700,1733,Monochrome woodblock print; ink and color on paper,10 3/8 x 14 in. (26.4 x 35.6 cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37130,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP684,false,true,37131,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Okumura Masanobu,"Japanese, 1686–1764",,Masanobu Okumura,Japanese,1686,1764,ca. 1705–7,1695,1717,Polychrome woodblock print; ink and color on paper,10 x 14 15/32 in. (25.4 x 36.8 cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37131,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP685,false,true,37132,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Okumura Masanobu,"Japanese, 1686–1764",,Masanobu Okumura,Japanese,1686,1764,ca. 1705–7,1695,1717,Polychrome woodblock print; ink and color on paper,9 15/32 x 13 1/4 in. (24.1 x 33.7 cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37132,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP686,false,true,37133,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Okumura Masanobu,"Japanese, 1686–1764",,Masanobu Okumura,Japanese,1686,1764,ca. 1730,1720,1740,Polychrome woodblock print; ink and color on paper,12 7/32 x 5 31/32 in. (31.1 x 15.2 cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37133,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP687,false,true,37134,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Okumura Masanobu,"Japanese, 1686–1764",,Masanobu Okumura,Japanese,1686,1764,ca. 1749,1739,1759,Polychrome woodblock print; ink and color on paper,4 1/4 x 6 7/8 in. (10.8 x 17.5 cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37134,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP816,false,true,37260,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Okumura Masanobu,"Japanese, 1686–1764",,Masanobu Okumura,Japanese,1686,1764,early 18th century,1700,1735,Monochrome woodblock print; ink and color on paper,9 7/8 x 14 3/4 in. (25.1 x 37.5 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37260,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1308,false,true,37318,Asian Art,Lacquer print,,Japan,Edo period (1615–1868),,,,Artist,,Okumura Masanobu,"Japanese, 1686–1764",,Masanobu Okumura,Japanese,1686,1764,1740,1740,1740,Polychrome lacquer print (urushi-e),12 7/8 x 6 1/4 in. (32.7 x 15.9 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37318,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1499,false,true,55597,Asian Art,Print,"『閨の雛形』 正月|Plate from the Erotic Book Mounds of Dyed Colors: A Pattern Book for the Boudoir (Someiro no yama neya no hinagata), First Month",Japan,Edo period (1615–1868),,,,Artist,,Okumura Masanobu,"Japanese, 1686–1764",,Masanobu Okumura,Japanese,1686,1764,ca. 1736–44,1736,1744,Hand-colored woodblock illustration; ink and color on paper,H. 9 9/16 in. (24.3 cm); W. 14 1/4 in. (36.2 cm),"Fletcher Fund, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55597,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2637,false,true,56780,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Okumura Masanobu,"Japanese, 1686–1764",,Masanobu Okumura,Japanese,1686,1764,ca. 1715,1705,1725,Monochrome woodblock print (sumie); ink on paper,11 3/8 x 16 1/4 in. (28.9 x 41.3 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56780,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2638,false,true,56782,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Okumura Masanobu,"Japanese, 1686–1764",,Masanobu Okumura,Japanese,1686,1764,1750,1750,1750,Polychrome woodblock print; ink and color on paper,16 x 11 1/4 in. (40.6 x 28.6 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56782,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2639,false,true,56781,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Okumura Masanobu,"Japanese, 1686–1764",,Masanobu Okumura,Japanese,1686,1764,ca. 1715,1705,1725,Monochrome woodblock print (sumie); ink on paper,10 x 14 in. (25.4 x 35.6 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56781,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2640,false,true,45050,Asian Art,Print,Sanpuku Tsui|Moon in Musashi Province,Japan,Edo period (1615–1868),,,,Artist,,Okumura Masanobu,"Japanese, 1686–1764",,Masanobu Okumura,Japanese,1686,1764,ca. 1740,1730,1750,"Center sheet of a triptych of polychrome woodblock prints; ink and applied color (""tan-e"") on paper",12 1/2 x 6 1/8 in. (31.8 x 15.6 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45050,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2641,false,true,56821,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Okumura Masanobu,"Japanese, 1686–1764",,Masanobu Okumura,Japanese,1686,1764,ca. 1744,1734,1754,Polychrome woodblock print; ink and color on paper,12 1/8 x 5 1/2 in. (30.8 x 14 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56821,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3025,false,true,56423,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Okumura Masanobu,"Japanese, 1686–1764",,Masanobu Okumura,Japanese,1686,1764,ca. 1739,1729,1749,Polychrome woodblock print; ink and color on paper (hand colored),10 3/4 x 15 5/8 in. (27.3 x 39.7 cm),"Anonymous Gift, 1945",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56423,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3026,false,true,56424,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Okumura Masanobu,"Japanese, 1686–1764",,Masanobu Okumura,Japanese,1686,1764,ca. 1739,1729,1749,Polychrome woodblock print; ink and color on paper (hand colored),10 3/4 x 15 in. (27.3 x 38.1 cm),"Anonymous Gift, 1945",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56424,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3027,false,true,56425,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Okumura Masanobu,"Japanese, 1686–1764",,Masanobu Okumura,Japanese,1686,1764,ca. 1739,1729,1749,Polychrome woodblock print; ink and color on paper (hand colored),10 3/4 x 15 1/4 in. (27.3 x 38.7 cm),"Anonymous Gift, 1945",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56425,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3028,false,true,56426,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Okumura Masanobu,"Japanese, 1686–1764",,Masanobu Okumura,Japanese,1686,1764,ca. 1739,1729,1749,Polychrome woodblock print; ink and color on paper (hand colored),10 3/4 x 15 1/8 in. (27.3 x 38.4 cm),"Anonymous Gift, 1945",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56426,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3029,false,true,56427,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Okumura Masanobu,"Japanese, 1686–1764",,Masanobu Okumura,Japanese,1686,1764,ca. 1739,1729,1749,Polychrome woodblock print; ink and color on paper (hand colored),10 3/4 x 15 1/8 in. (27.3 x 38.4 cm),"Anonymous Gift, 1945",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56427,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3030,false,true,56428,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Okumura Masanobu,"Japanese, 1686–1764",,Masanobu Okumura,Japanese,1686,1764,ca. 1739,1729,1749,Polychrome woodblock print; ink and color on paper (hand colored),10 3/4 x 15 1/8 in. (27.3 x 38.4 cm),"Anonymous Gift, 1945",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56428,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3031,false,true,56429,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Okumura Masanobu,"Japanese, 1686–1764",,Masanobu Okumura,Japanese,1686,1764,ca. 1739,1729,1749,Polychrome woodblock print; ink and color on paper (hand colored),10 3/4 x 15 1/8 in. (27.3 x 38.4 cm),"Anonymous Gift, 1945",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56429,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3032,false,true,56490,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Okumura Masanobu,"Japanese, 1686–1764",,Masanobu Okumura,Japanese,1686,1764,ca. 1739,1729,1749,Polychrome woodblock print; ink and color on paper (hand colored),10 3/4 x 15 1/8 in. (27.3 x 38.4 cm),"Anonymous Gift, 1946",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56490,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3033,false,true,56491,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Okumura Masanobu,"Japanese, 1686–1764",,Masanobu Okumura,Japanese,1686,1764,ca. 1739,1729,1749,Polychrome woodblock print; ink and color on paper (hand colored),10 3/4 x 15 1/8 in. (27.3 x 38.4 cm),"Anonymous Gift, 1945",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56491,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3034,false,true,56492,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Okumura Masanobu,"Japanese, 1686–1764",,Masanobu Okumura,Japanese,1686,1764,ca. 1739,1729,1749,Polychrome woodblock print; ink and color on paper (hand colored),10 3/4 x 15 1/8 in. (27.3 x 38.4 cm),"Anonymous Gift, 1945",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56492,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3035,false,true,56493,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Okumura Masanobu,"Japanese, 1686–1764",,Masanobu Okumura,Japanese,1686,1764,ca. 1739,1729,1749,Polychrome woodblock print; ink and color on paper (hand colored),10 3/4 x 15 3/8 in. (27.3 x 39.1 cm),"Anonymous Gift, 1945",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56493,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3036,false,true,56494,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Okumura Masanobu,"Japanese, 1686–1764",,Masanobu Okumura,Japanese,1686,1764,ca. 1739,1729,1749,Polychrome woodblock print; ink and color on paper (hand colored),10 3/4 x 15 1/8 in. (27.3 x 38.4 cm),"Anonymous Gift, 1945",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56494,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3077,false,true,45239,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Okumura Masanobu,"Japanese, 1686–1764",,Masanobu Okumura,Japanese,1686,1764,ca. 1730,1720,1740,Polychrome woodblock print (hand colored); ink and color on paper,12 3/4 x 6 1/4 in. (32.4 x 15.9 cm),"Harris Brisbane Dick Fund and Rogers Fund, 1949",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45239,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3078,false,true,56588,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Okumura Masanobu,"Japanese, 1686–1764",,Masanobu Okumura,Japanese,1686,1764,ca. 1755,1745,1765,Polychrome woodblock print; ink and color on paper,12 1/8 x 5 1/2 in. (30.8 x 14 cm),"Harris Brisbane Dick Fund and Rogers Fund, 1949",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56588,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3079,false,true,51993,Asian Art,Print,見立『平家物語』 紅葉焚図|Parody of Palace Servants Heating Sake over a Fire of Maple Leaves,Japan,Edo period (1615–1868),,,,Artist,,Okumura Masanobu,"Japanese, 1686–1764",,Masanobu Okumura,Japanese,1686,1764,ca. 1750,1740,1760,Red-colored woodblock print (benizuri-e); ink and color on paper,Image: 16 5/16 × 11 3/4 in. (41.4 × 29.8 cm),"Harris Brisbane Dick Fund and Rogers Fund, 1949",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/51993,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3107,false,true,56613,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Okumura Masanobu,"Japanese, 1686–1764",,Masanobu Okumura,Japanese,1686,1764,ca. 1745,1735,1755,Polychrome woodblock print; ink and color on paper,28 1/2 x 6 1/4 in. (72.4 x 15.9 cm),"Harris Brisbane Dick Fund and Rogers Fund, 1949",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56613,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3169,false,true,56722,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Okumura Masanobu,"Japanese, 1686–1764",,Masanobu Okumura,Japanese,1686,1764,early 18th century,1700,1733,Polychrome woodblock print; ink and color on paper (Beni-e),H. 27 in. (68.6 cm); W. 10 in. (25.4 cm),"Bequest of Katherine S. Dreier, 1952",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56722,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1307,false,true,55265,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyomasu II,"Japanese, 1706–1763",,Torii Kiyomasu II,Japanese,1706,1763,ca. 1748,1738,1758,Polychrome lacquer print (urushi-e),12 1/2 x 5 7/8 in. (31.8 x 14.9 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55265,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1447,false,true,51088,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyomasu II,"Japanese, 1706–1763",,Torii Kiyomasu II,Japanese,1706,1763,early 18th century,1700,1733,"Polychrome woodblock print; (beni-e); black, red and green on paper",12 1/2 x 6 1/8 in. (31.8 x 15.6 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/51088,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2758,false,true,54861,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,Attributed to,Torii Kiyomasu II,"Japanese, 1706–1763",,Torii Kiyomasu II,Japanese,1706,1763,ca. 1730–40,1720,1750,Tan-e (hand-colored print); ink and color on paper,H. 11 3/8 in. (28.9 cm); W. 6 in. (15.2 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54861,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3081,false,true,56594,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,Attributed to,Torii Kiyomasu II,"Japanese, 1706–1763",,Torii Kiyomasu II,Japanese,1706,1763,before 1763,1663,1763,Polychrome woodblock print; ink and color on paper,12 3/4 x 5 3/4 in. (32.4 x 14.6 cm),"Harris Brisbane Dick Fund and Rogers Fund, 1949",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56594,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP656,false,true,37106,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Ishikawa Toyonobu,"Japanese, 1711–1785",,Ishikawa Toyonobu,Japanese,1711,1785,ca. 1750,1740,1760,Polychrome woodblock print; ink and color on paper,17 7/32 x 12 7/32 in. (43.8 x 31.1 cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37106,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP691,false,true,37138,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Ishikawa Toyonobu,"Japanese, 1711–1785",,Ishikawa Toyonobu,Japanese,1711,1785,ca. 1748,1738,1758,Polychrome woodblock print; ink and color on paper,12 7/32 x 5 5/8 in. (31.1 x 14.3 cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37138,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP844,false,true,45240,Asian Art,Print,Go Gatsu|The Fifth Month,Japan,Edo period (1615–1868),,,,Artist,,Ishikawa Toyonobu,"Japanese, 1711–1785",,Ishikawa Toyonobu,Japanese,1711,1785,ca. 1748,1738,1758,Polychrome woodblock print; ink and color on paper,H. 12 1/4 in. (31.1 cm); W. 2 15/16 in. (7.5 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45240,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP845,false,true,45288,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Ishikawa Toyonobu,"Japanese, 1711–1785",,Ishikawa Toyonobu,Japanese,1711,1785,ca. 1750,1740,1760,Polychrome woodblock print; ink and color on paper,H. 7 1/4 in. (18.4 cm); W. 11 3/8 in. (28.9 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45288,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP846,false,true,54499,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Ishikawa Toyonobu,"Japanese, 1711–1785",,Ishikawa Toyonobu,Japanese,1711,1785,ca. 1750,1740,1760,Polychrome woodblock print; ink and color on paper,H. 7 1/4 in. (18.4 cm); W. 11 3/8 in. (28.9 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54499,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP847,false,true,54501,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Ishikawa Toyonobu,"Japanese, 1711–1785",,Ishikawa Toyonobu,Japanese,1711,1785,ca. 1752,1742,1762,Polychrome woodblock print; ink and color on paper,H. 15 1/2 in. (39.4 cm); W. 11 3/16 in. (28.4 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54501,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP848,false,true,54505,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Ishikawa Toyonobu,"Japanese, 1711–1785",,Ishikawa Toyonobu,Japanese,1711,1785,ca. 1758,1748,1768,Polychrome woodblock print; ink and color on paper,H. 11 1/2 in. (29.2 cm); W. 17 1/8 in. (43.5 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54505,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1616,false,true,55781,Asian Art,Print,初代瀬川菊之丞の傾城図|The Kabuki Actor Segawa Kikunojo in the Role of a Courtesan Reading a Letter,Japan,Edo period (1615–1868),,,,Artist,,Ishikawa Toyonobu,"Japanese, 1711–1785",,Ishikawa Toyonobu,Japanese,1711,1785,late 1740s,1745,1749,Polychrome woodblock print; ink and color on paper,H. 26 3/4 in. (67.9 cm); W. 9 5/8 in. (24.4 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55781,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2727,false,true,56999,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Ishikawa Toyonobu,"Japanese, 1711–1785",,Ishikawa Toyonobu,Japanese,1711,1785,probably January 1749,1749,1749,Polychrome woodblock print; ink and color on paper,11 1/2 x 5 3/8 in. (29.2 x 13.7 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56999,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2728,false,true,57001,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Ishikawa Toyonobu,"Japanese, 1711–1785",,Ishikawa Toyonobu,Japanese,1711,1785,1748–1750,1748,1750,Polychrome woodblock print; ink and color on paper,16 3/4 x 12 in. (42.5 x 30.5 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/57001,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2729,false,true,57002,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Ishikawa Toyonobu,"Japanese, 1711–1785",,Ishikawa Toyonobu,Japanese,1711,1785,1744,1744,1744,Polychrome woodblock print; ink and color on paper,15 x 11 1/4 in. (38.1 x 28.6 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/57002,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2759,false,true,57023,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Ishikawa Toyonobu,"Japanese, 1711–1785",,Ishikawa Toyonobu,Japanese,1711,1785,1711–1785,1711,1785,Polychrome woodblock print; ink and color on paper,15 5/8 x 6 7/8 in. (39.7 x 17.5 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/57023,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2760,false,true,57024,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Ishikawa Toyonobu,"Japanese, 1711–1785",,Ishikawa Toyonobu,Japanese,1711,1785,1711–1785,1711,1785,Polychrome woodblock print; ink and color on paper,12 1/8 x 5 3/4 in. (30.8 x 14.6 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/57024,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3085,false,true,56598,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,Attributed to,Ishikawa Toyonobu,"Japanese, 1711–1785",,Ishikawa Toyonobu,Japanese,1711,1785,ca. 1728,1718,1738,Polychrome woodblock print (hand-colored); ink and color on paper,10 1/2 x 15 in. (26.7 x 38.1 cm),"Harris Brisbane Dick Fund and Rogers Fund, 1949",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56598,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3086,false,true,56599,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Ishikawa Toyonobu,"Japanese, 1711–1785",,Ishikawa Toyonobu,Japanese,1711,1785,1758,1758,1758,Polychrome woodblock print; ink and color on paper,15 7/8 x 6 7/8 in. (40.3 x 17.5 cm),"Harris Brisbane Dick Fund and Rogers Fund, 1949",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56599,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3087,false,true,56600,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Ishikawa Toyonobu,"Japanese, 1711–1785",,Ishikawa Toyonobu,Japanese,1711,1785,1761,1761,1761,Polychrome woodblock print; ink and color on paper,15 3/8 x 6 1/2 in. (39.1 x 16.5 cm),"Harris Brisbane Dick Fund and Rogers Fund, 1949",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56600,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3088,false,true,56601,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Ishikawa Toyonobu,"Japanese, 1711–1785",,Ishikawa Toyonobu,Japanese,1711,1785,1750 or 1751,1750,1751,Triptych of polychrome woodblock prints; ink and color on paper,12 x 17 1/2 in. (30.5 x 44.5 cm),"Harris Brisbane Dick Fund and Rogers Fund, 1949",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56601,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3108,false,true,56614,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Ishikawa Toyonobu,"Japanese, 1711–1785",,Ishikawa Toyonobu,Japanese,1711,1785,ca. 1745,1735,1755,Polychrome woodblock print (hand colored); ink and color on paper,25 1/2 x 6 in. (64.8 x 15.2 cm),"Harris Brisbane Dick Fund and Rogers Fund, 1949",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56614,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3109,false,true,56615,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Ishikawa Toyonobu,"Japanese, 1711–1785",,Ishikawa Toyonobu,Japanese,1711,1785,ca. 1748,1738,1758,Polychrome woodblock print (hand colored); ink and color on paper,19 3/4 x 9 in. (50.2 x 22.9 cm),"Harris Brisbane Dick Fund and Rogers Fund, 1949",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56615,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3110,false,true,56617,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Ishikawa Toyonobu,"Japanese, 1711–1785",,Ishikawa Toyonobu,Japanese,1711,1785,ca. 1743,1733,1753,Polychrome woodblock print (hand colored); ink and color on paper,24 x 9 3/4 in. (61 x 24.8 cm),"Harris Brisbane Dick Fund and Rogers Fund, 1949",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56617,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP655,false,true,37105,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Furuyama Moromasa,"Japanese, 1712–1772",,Furuyama Moromasa,Japanese,1712,1772,ca. 1740,1730,1750,Polychrome woodblock print; ink and color on paper,Oban: 12 31/32 x 18 1/2 in. (33.0 x 47.0 cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37105,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1399,false,true,55407,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Toriyama Sekien,"Japanese, 1712–1788",,Toriyama Sekien,Japanese,1712,1788,ca. 1775,1765,1785,Polychrome woodblock print; ink and color on paper,H. 12 7/8 in. (32.7 cm); W. 9 7/16 in. (24 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55407,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP154,false,true,36633,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Suzuki Harunobu,"Japanese, 1725–1770",,Suzuki Harunobu,Japanese,1725,1770,ca.1766–70,1766,1770,Polychrome woodblock print; ink and color on paper,11 1/4 x 8 7/16 in. (28.6 x 21.4 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36633,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP155,false,true,36634,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Suzuki Harunobu,"Japanese, 1725–1770",,Suzuki Harunobu,Japanese,1725,1770,ca. 1766,1756,1776,Polychrome woodblock print; ink and color on paper,11 3/8 x 8 17/32 in. (28.9 x 21.7 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36634,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP156,false,true,36635,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Suzuki Harunobu,"Japanese, 1725–1770",,Suzuki Harunobu,Japanese,1725,1770,probably 1799,1797,1801,Polychrome woodblock print; ink and color on paper,11 1/4 x 8 1/2 in. (28.6 x 21.6 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36635,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP157,false,true,36636,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Suzuki Harunobu,"Japanese, 1725–1770",,Suzuki Harunobu,Japanese,1725,1770,ca. 1768,1758,1778,Polychrome woodblock print; ink and color on paper,25 1/2 x 4 3/4 in. (64.8 x 12.1 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36636,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP158,false,true,36637,Asian Art,Print,風俗六玉川|A Young Komuso,Japan,Edo period (1615–1868),,,,Artist,,Suzuki Harunobu,"Japanese, 1725–1770",,Suzuki Harunobu,Japanese,1725,1770,ca. 1768,1758,1778,Polychrome woodblock print; ink and color on paper,26 3/5 x 4 3/4 in. (67.6 x 12.1 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36637,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP226,false,true,45090,Asian Art,Print,"青楼美人合|The Courtesans, from the Series, ""Seiro Bijin Awase Carver End Shigoro"" (sic.)",Japan,Edo period (1615–1868),,,,Artist,,Suzuki Harunobu,"Japanese, 1725–1770",,Suzuki Harunobu,Japanese,1725,1770,ca. 1765,1755,1775,Polychrome woodblock print; ink and color on paper,H. 8 1/2 in. (21.6 cm); W. 5 3/4 in. (14.6 cm),"Rogers Fund, 1917",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45090,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP227,false,true,48892,Asian Art,Print,"青楼美人合|The Courtesans, from the series, ""Seiro Bijin Awase Carver End Shigoro"" (sic)",Japan,Edo period (1615–1868),,,,Artist,,Suzuki Harunobu,"Japanese, 1725–1770",,Suzuki Harunobu,Japanese,1725,1770,ca. 1765,1755,1775,Polychrome woodblock print; ink and color on paper,8 1/2 x 5 3/4 in. (21.6 x 14.6 cm),"Rogers Fund, 1917",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/48892,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP565,false,true,37016,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Suzuki Harunobu,"Japanese, 1725–1770",,Suzuki Harunobu,Japanese,1725,1770,ca. 1769,1759,1789,Polychrome woodblock print; ink and color on paper,10 3/4 x 7 15/16 in. (27.3 x 20.2 cm) medium-size print (chu-ban),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37016,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP566,false,true,37017,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Suzuki Harunobu,"Japanese, 1725–1770",,Suzuki Harunobu,Japanese,1725,1770,1769 or 1770,1769,1770,Polychrome woodblock print; ink and color on paper,10 1/2 x 5 5/8 in. (26.7 x 14.3 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37017,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP668,false,true,37115,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Suzuki Harunobu,"Japanese, 1725–1770",,Suzuki Harunobu,Japanese,1725,1770,ca. 1763,1753,1773,Polychrome woodblock print; ink and color on paper,27 11/32 x 4 in. (69.5 x 10.2 cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37115,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP670,false,true,37117,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Suzuki Harunobu,"Japanese, 1725–1770",,Suzuki Harunobu,Japanese,1725,1770,ca. 1768,1758,1778,Polychrome woodblock print; ink and color on paper,25 1/8 x 4 15/32 in. (63.8 x 11.4 cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37117,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP671,false,true,37118,Asian Art,Print,風流七小町|Visiting,Japan,Edo period (1615–1868),,,,Artist,,Suzuki Harunobu,"Japanese, 1725–1770",,Suzuki Harunobu,Japanese,1725,1770,ca. 1769,1759,1779,Polychrome woodblock print; ink and color on paper,25 31/32 x 4 1/4 in. (66.0 x 10.8 cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37118,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP697,false,true,37144,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Suzuki Harunobu,"Japanese, 1725–1770",,Suzuki Harunobu,Japanese,1725,1770,ca. 1764,1754,1774,Polychrome woodblock print; ink and color on paper,7 3/4 x 12 1/8 in. (19.7 x 30.8 cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37144,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP698,true,true,37145,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Suzuki Harunobu,"Japanese, 1725–1770",,Suzuki Harunobu,Japanese,1725,1770,ca. 1766,1756,1776,"Polychrome woodblock print (first edition); ink and colors on paper, medium-sized print (chuban)",11 1/4 x 8 in. (28.6 x 20.3 cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37145,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP699,false,true,37146,Asian Art,Print,風流七小町 しみず|Shimizu Temple,Japan,Edo period (1615–1868),,,,Artist,,Suzuki Harunobu,"Japanese, 1725–1770",,Suzuki Harunobu,Japanese,1725,1770,ca. 1765,1755,1775,Polychrome woodblock print; ink and color on paper; Benizuri-e; small print (hosoban),12 7/32 x 5 1/2 in. (31.1 x 14.0 cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37146,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP861,false,true,54540,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Suzuki Harunobu,"Japanese, 1725–1770",,Suzuki Harunobu,Japanese,1725,1770,ca. 1765,1755,1775,Polychrome woodblock print; ink and color on paper,H. 11 in. (27.9 cm); W. 8 1/8 in. (20.6 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54540,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP862,false,true,54541,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Suzuki Harunobu,"Japanese, 1725–1770",,Suzuki Harunobu,Japanese,1725,1770,ca. 1766,1756,1776,Polychrome woodblock print; ink and color on paper,H. 10 3/4 in. (27.3 cm); W. 8 1/8 in. (20.6 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54541,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP863,false,true,54542,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Suzuki Harunobu,"Japanese, 1725–1770",,Suzuki Harunobu,Japanese,1725,1770,probably 1765,1760,1770,Polychrome woodblock print; ink and color on paper,H. 11 1/8 in. (28.3 cm); W. 8 3/8 in. (21.3 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54542,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP864,false,true,54543,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Suzuki Harunobu,"Japanese, 1725–1770",,Suzuki Harunobu,Japanese,1725,1770,ca. 1766,1756,1777,Polychrome woodblock print; ink and color on paper,H. 8 3/16 in. (20.8 cm); W. 2 3/4 in. (7 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54543,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP865,false,true,54546,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Suzuki Harunobu,"Japanese, 1725–1770",,Suzuki Harunobu,Japanese,1725,1770,probably 1766,1761,1771,Polychrome woodblock print; ink and color on paper,H. 10 5/8 in. (27 cm); W. 8 1/8 in. (20.6 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54546,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP866,false,true,54550,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Suzuki Harunobu,"Japanese, 1725–1770",,Suzuki Harunobu,Japanese,1725,1770,ca. 1765,1755,1777,Polychrome woodblock print; ink and color on paper,H. 11 3/16 in. (28.4 cm); W. 8 3/8 in. (21.3 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54550,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP867,false,true,54551,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Suzuki Harunobu,"Japanese, 1725–1770",,Suzuki Harunobu,Japanese,1725,1770,late 1760s,1765,1769,Polychrome woodblock print; ink and color on paper,H. 7 11/16 in. (19.5 cm); W. 9 9/16 in. (24.3 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54551,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP868,false,true,37268,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Suzuki Harunobu,"Japanese, 1725–1770",,Suzuki Harunobu,Japanese,1725,1770,ca. 1767,1757,1777,Polychrome woodblock print; ink and color on paper,10 1/4 x 7 1/4 in. (26 x 18.4 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37268,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP869,false,true,54554,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Suzuki Harunobu,"Japanese, 1725–1770",,Suzuki Harunobu,Japanese,1725,1770,ca. 1767,1757,1777,Polychrome woodblock print; ink and color on paper,H. 10 11/16 in. (27.1 cm); W. 8 1/4 in. (21 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54554,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP870,false,true,45078,Asian Art,Print,百人一首 天智天皇|Sympathy,Japan,Edo period (1615–1868),,,,Artist,,Suzuki Harunobu,"Japanese, 1725–1770",,Suzuki Harunobu,Japanese,1725,1770,ca. 1768,1758,1778,Polychrome woodblock print; ink and color on paper,H. 10 in. (25.4 cm); W. 7 5/ 16 in. (18.6 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45078,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP871,false,true,45079,Asian Art,Print,"百人一首 小式の内持|Koshikibu no Naishi (999–1025), from ""Hyakunin Isshu"" (One Hundred Poems by One Hundred Poets)",Japan,Edo period (1615–1868),,,,Artist,,Suzuki Harunobu,"Japanese, 1725–1770",,Suzuki Harunobu,Japanese,1725,1770,ca. 1768,1758,1778,Polychrome woodblock print; ink and color on paper,H. 11 in. (27.9 cm); W. 8 1/8 in. (20.6 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45079,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP872,false,true,45075,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Suzuki Harunobu,"Japanese, 1725–1770",,Suzuki Harunobu,Japanese,1725,1770,ca. 1768,1758,1778,Polychrome woodblock print; ink and color on paper,H. 11 in. (27.9 cm); W. 8 in. (20.3 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45075,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP873,false,true,45069,Asian Art,Print,百人一首 藤原元真|Poem by Fujiwara no Motozane (ca. 860) from the Series Thirty-Six Poets,Japan,Edo period (1615–1868),,,,Artist,,Suzuki Harunobu,"Japanese, 1725–1770",,Suzuki Harunobu,Japanese,1725,1770,ca. 1768,1758,1778,Polychrome woodblock print; ink and color on paper with embossing (karazuri),Image: 10 7/8 x 8 1/8 in. (27.6 x 20.6 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45069,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP875,false,true,51994,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Suzuki Harunobu,"Japanese, 1725–1770",,Suzuki Harunobu,Japanese,1725,1770,probably 1768,1768,1768,Polychrome woodblock print; ink and color on paper,H. 8 1/8 in. (20.6 cm); W. 11 1/4 in. (28.6 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/51994,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP876,false,true,54569,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Suzuki Harunobu,"Japanese, 1725–1770",,Suzuki Harunobu,Japanese,1725,1770,1764–72,1764,1772,Polychrome woodblock print; ink and color on paper,H. 11 1/8 in. (28.3 cm); W. 8 1/4 in. (21 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54569,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP877,false,true,54570,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Suzuki Harunobu,"Japanese, 1725–1770",,Suzuki Harunobu,Japanese,1725,1770,ca. 1769,1759,1779,Polychrome woodblock print; ink and color on paper,H. 20 1/8 in. (51.5 cm); W. 4 1/2 in. (11.4 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54570,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP878,false,true,54571,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Suzuki Harunobu,"Japanese, 1725–1770",,Suzuki Harunobu,Japanese,1725,1770,ca. 1769,1759,1779,Polychrome woodblock print; ink and color on paper,H. 25 7/8 in. (65.7 cm); W. 4 1/2 in. (11.4 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54571,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP879,false,true,54572,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,School of,Suzuki Harunobu,"Japanese, 1725–1770",,Suzuki Harunobu,Japanese,1725,1770,ca. 1765,1755,1775,Polychrome woodblock print; ink and color on paper,H. 11 1/4 in. (28.6 cm); W. 8 3/8 in. (21.3 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54572,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1010,false,true,54886,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Suzuki Harunobu,"Japanese, 1725–1770",,Suzuki Harunobu,Japanese,1725,1770,ca. 1765,1755,1775,Polychrome woodblock print; ink and color on paper,H. 11 5/8 in. (29.5 cm); W. 8 3/8 in. (21.3 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54886,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1219,false,true,55142,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Suzuki Harunobu,"Japanese, 1725–1770",,Suzuki Harunobu,Japanese,1725,1770,1770,1770,1770,Polychrome woodblock print; ink and color on paper,H. 11 1/8 in. (28.3 cm); W. 8 1/8 in. (20.6 cm),"Rogers Fund, 1921",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55142,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1220,false,true,55143,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Suzuki Harunobu,"Japanese, 1725–1770",,Suzuki Harunobu,Japanese,1725,1770,1770,1770,1770,Polychrome woodblock print; ink and color on paper,H. 10 1/4 in. (26 cm); W. 8 1/8 in. (20.6 cm),"Rogers Fund, 1921",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55143,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1223,false,true,55145,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Suzuki Harunobu,"Japanese, 1725–1770",,Suzuki Harunobu,Japanese,1725,1770,1770,1770,1770,Polychrome woodblock print; ink and color on paper,H. 11 in. (27.9 cm); W. 8 5/16 in. (21.1 cm),"Rogers Fund, 1921",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55145,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1273,false,true,55194,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Suzuki Harunobu,"Japanese, 1725–1770",,Suzuki Harunobu,Japanese,1725,1770,ca. 1766,1756,1776,Polychrome woodblock print; ink and color on paper,10 5/8 x 8 1/16 in. (27 x 20.5 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55194,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1274,false,true,45073,Asian Art,Print,Aki|風俗四季歌仙 立秋|First Day of Autumn (Risshu),Japan,Edo period (1615–1868),,,,Artist,,Suzuki Harunobu,"Japanese, 1725–1770",,Suzuki Harunobu,Japanese,1725,1770,ca. 1865,1855,1875,Polychrome woodblock print; ink and color on paper,Image: 11 × 8 in. (27.9 × 20.3 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45073,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1275,false,true,55204,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Suzuki Harunobu,"Japanese, 1725–1770",,Suzuki Harunobu,Japanese,1725,1770,ca. 1760,1750,1770,Polychrome woodblock print; ink and color on paper,H. 10 1/4 in. (26 cm); W. 8 1/8 in. (20.6 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55204,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1276,false,true,45089,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Suzuki Harunobu,"Japanese, 1725–1770",,Suzuki Harunobu,Japanese,1725,1770,ca. 1769,1759,1779,Polychrome woodblock print; pillar print (hashira-e); ink and color on paper,H. 25 7/8 in. (65.7 cm); W. 4 7/8 in. (12.4 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45089,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1375,false,true,55356,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Suzuki Harunobu,"Japanese, 1725–1770",,Suzuki Harunobu,Japanese,1725,1770,ca. 1770,1760,1780,Polychrome woodblock print; ink and color on paper,H. 27 1/2 in. (69.9 cm); W. 4 3/4 in. (12.1 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55356,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1376,false,true,55358,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Suzuki Harunobu,"Japanese, 1725–1770",,Suzuki Harunobu,Japanese,1725,1770,ca. 1770,1760,1780,Polychrome woodblock print (pillar print); ink and color on paper,H. 26 3/4 in. (67.9 cm); W. 5 in. (12.7 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55358,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1384,false,true,55371,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Suzuki Harunobu,"Japanese, 1725–1770",,Suzuki Harunobu,Japanese,1725,1770,ca. 1769,1759,1779,Polychrome woodblock print; ink and color on paper,H. 8 in. (20.3 cm); W. 10 1/4 in. (26 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55371,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1435,false,true,55468,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Suzuki Harunobu,"Japanese, 1725–1770",,Suzuki Harunobu,Japanese,1725,1770,ca. 1770,1760,1780,Polychrome woodblock print; ink and color on paper,H. 27 5/8 in. (70.2 cm); W. 4 3/4 in. (12.1 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55468,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1504,false,true,53895,Asian Art,Print,回文歌 京 大阪 江戸|Palindromic Poems (Kaibunka): Kyo,Japan,Edo period (1615–1868),,,,Artist,,Suzuki Harunobu,"Japanese, 1725–1770",,Suzuki Harunobu,Japanese,1725,1770,ca. 1768,1758,1778,Right-hand sheet of a triptych of polychrome woodblock prints; ink and color on paper,12 3/8 x 5 3/8 in. (31.4 x 13.7 cm),"Fletcher Fund, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/53895,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1505,false,true,55608,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Suzuki Harunobu,"Japanese, 1725–1770",,Suzuki Harunobu,Japanese,1725,1770,ca. 1768,1758,1778,Polychrome woodblock print; ink and color on paper,H. 11 in. (27.9 cm); W. 8 1/8 in. (20.6 cm),"Fletcher Fund, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55608,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1625,false,true,54867,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Suzuki Harunobu,"Japanese, 1725–1770",,Suzuki Harunobu,Japanese,1725,1770,ca. 1767,1757,1777,Polychrome woodblock print; ink and color on paper,H. 11 1/4 in. (28.6 cm); W. 8 1/8 in. (20.6 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54867,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1627,false,true,45086,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Suzuki Harunobu,"Japanese, 1725–1770",,Suzuki Harunobu,Japanese,1725,1770,ca. 1769,1759,1779,Polychrome woodblock print; ink and color on paper,H. 10 1/8 in. (25.7 cm); W. 7 1/2 in. (19.1 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45086,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1632,false,true,55796,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Suzuki Harunobu,"Japanese, 1725–1770",,Suzuki Harunobu,Japanese,1725,1770,ca. 1768,1758,1778,Polychrome woodblock print; ink and color on paper,H. 11 in. (27.9 cm); W. 8 1/2 in. (21.6 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55796,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1633,false,true,55797,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Suzuki Harunobu,"Japanese, 1725–1770",,Suzuki Harunobu,Japanese,1725,1770,1764–72,1764,1772,Polychrome woodblock print; ink and color on paper,H. 10 3/4 in. (27.3 cm); W. 8 1/4 in. (21 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55797,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1634,false,true,45068,Asian Art,Print,Uzuki|風俗四季歌仙 卯月|The Fourth Month (April),Japan,Edo period (1615–1868),,,,Artist,,Suzuki Harunobu,"Japanese, 1725–1770",,Suzuki Harunobu,Japanese,1725,1770,ca. 1768,1758,1778,Polychrome woodblock print; ink and color on paper,H. 11 in. (27.9 cm); W. 8 3/16 in. (20.8 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45068,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1636,false,true,45083,Asian Art,Print,百人一首 僧正遍昭|Poem by Henjō Sojō,Japan,Edo period (1615–1868),,,,Artist,,Suzuki Harunobu,"Japanese, 1725–1770",,Suzuki Harunobu,Japanese,1725,1770,ca. 1766,1756,1776,Polychrome woodblock print; ink and color on paper,H. 10 9/16 in. (26.8 cm); W. 8 3/16 in. (20.8 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45083,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1637,false,true,45084,Asian Art,Print,百人一首 陽成院|Yozei no In,Japan,Edo period (1615–1868),,,,Artist,,Suzuki Harunobu,"Japanese, 1725–1770",,Suzuki Harunobu,Japanese,1725,1770,ca. 1766,1756,1776,Polychrome woodblock print; ink and color on paper,10 7/8 x 8 in. (27.6 x 20.3 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45084,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1638,false,true,45065,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Suzuki Harunobu,"Japanese, 1725–1770",,Suzuki Harunobu,Japanese,1725,1770,1768–70,1700,1868,Polychrome woodblock print; ink and color on paper,10 5/16 x 7 9/1 6in. (26.2 x 19.2 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45065,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1640,false,true,54863,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Suzuki Harunobu,"Japanese, 1725–1770",,Suzuki Harunobu,Japanese,1725,1770,1764–72,1764,1772,Polychrome woodblock print; ink and color on paper,H. 11 1/8 in. (28.3 cm); W. 8 1/8 in. (20.6 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54863,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1643,false,true,55801,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Suzuki Harunobu,"Japanese, 1725–1770",,Suzuki Harunobu,Japanese,1725,1770,ca. late 18th century,1767,1799,Polychrome woodblock print; ink and color on paper,H. 10 11/16 in. (27.1 cm); W. 8 in. (20.3 cm) medium-size print (chu-ban),"Fletcher Fund, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55801,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1644,false,true,55802,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Suzuki Harunobu,"Japanese, 1725–1770",,Suzuki Harunobu,Japanese,1725,1770,ca. 1765,1755,1775,Polychrome woodblock print; ink and color on paper,H. 11 1/4 in. (28.6 cm); W. 8 1/8 in. (20.6 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55802,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1646,false,true,55804,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Suzuki Harunobu,"Japanese, 1725–1770",,Suzuki Harunobu,Japanese,1725,1770,ca. 1765–70,1755,1780,Polychrome woodblock print; chuban yoko-e; ink and color on paper,H. 8 in. (20.3 cm); W. 12 5/16 in. (31.3 cm) medium-size print (chu-ban),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55804,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1649,false,true,53608,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Suzuki Harunobu,"Japanese, 1725–1770",,Suzuki Harunobu,Japanese,1725,1770,1764–70,1764,1770,Polychrome woodblock print; ink and color on paper,H. 10 3/4 in. (27.3 cm); W. 7 13/16 in. (19.8 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/53608,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1654,false,true,55814,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Suzuki Harunobu,"Japanese, 1725–1770",,Suzuki Harunobu,Japanese,1725,1770,1764–72,1764,1772,Polychrome woodblock print; ink and color on paper,H. 26 1/2 in. (67.3 cm); W. 4 7/16 in. (11.3 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55814,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1655,false,true,55815,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Suzuki Harunobu,"Japanese, 1725–1770",,Suzuki Harunobu,Japanese,1725,1770,ca. 1765–70,1755,1780,Polychrome woodblock print; pillar print (hashira-e); ink and color on paper,H. 27 in. (68.6 cm); W. 4 13/16 in. (12.2 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55815,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2434,false,true,42563,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Suzuki Harunobu,"Japanese, 1725–1770",,Suzuki Harunobu,Japanese,1725,1770,ca. 1766,1756,1776,Polychrome woodblock print; ink and color on paper,H. 11 in. (27.9 cm); W. 10 1/2 in. (26.7 cm) medium-size block (chu-ban),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/42563,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2435,false,true,45268,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Suzuki Harunobu,"Japanese, 1725–1770",,Suzuki Harunobu,Japanese,1725,1770,ca. 1765,1755,1775,Polychrome woodblock print; ink and color on paper,10 3/8 x 7 1/2 in. (26.4 x 19.1 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45268,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2436,false,true,44943,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Suzuki Harunobu,"Japanese, 1725–1770",,Suzuki Harunobu,Japanese,1725,1770,ca. 1766,1756,1776,Polychrome woodblock print; ink and color on paper,"H. 7 15/16 in. (20.2 cm); W. 11 1/8 in. (28.3 cm) Medium-size block (""chuban"")","The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/44943,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2437,false,true,54181,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Suzuki Harunobu,"Japanese, 1725–1770",,Suzuki Harunobu,Japanese,1725,1770,1765,1765,1765,Polychrome woodblock print; ink and color on paper,10 1/2 x 7 7/8 in. (26.7 x 20 cm) medium-size print (chu-ban),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54181,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2438,false,true,56874,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Suzuki Harunobu,"Japanese, 1725–1770",,Suzuki Harunobu,Japanese,1725,1770,1765,1765,1765,Polychrome woodblock print; ink and color on paper,11 1/8 x 8 1/8 in. (28.3 x 20.6 cm) medium-size print (chu-ban),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56874,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2439,false,true,56789,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Suzuki Harunobu,"Japanese, 1725–1770",,Suzuki Harunobu,Japanese,1725,1770,ca. 1765,1755,1775,Polychrome woodblock print; ink and color on paper,H. 11 1/8 in. (28.3 cm); W. 8 3/8 in. (21.3 cm) medium-size print (chuban),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56789,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2440,false,true,56791,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Suzuki Harunobu,"Japanese, 1725–1770",,Suzuki Harunobu,Japanese,1725,1770,1764–72,1764,1772,Polychrome woodblock print; ink and color on paper,H. 10 3/8 in. (26.4 cm); W. 8 1/8 in. (20.6 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56791,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2441,false,true,45066,Asian Art,Print,百人一首 素性法師|Poem by the Monk Sosei (act. 850-97),Japan,Edo period (1615–1868),,,,Artist,,Suzuki Harunobu,"Japanese, 1725–1770",,Suzuki Harunobu,Japanese,1725,1770,ca. 1767–68,1762,1775,"Polychrome woodblock print; ink and color on paper, with embossing (karazuri)",10 7/8 x 8 in. (27.6 x 20.3 cm) medium-size print (chu-ban),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45066,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2442,false,true,51995,Asian Art,Print,萩|The Bush Clover (Hagi),Japan,Edo period (1615–1868),,,,Artist,,Suzuki Harunobu,"Japanese, 1725–1770",,Suzuki Harunobu,Japanese,1725,1770,ca. 1766,1756,1776,Polychrome woodblock print; ink and color on paper,10 7/8 x 7 7/8 in. (27.6 x 20 cm) medium-size print (chu-ban),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/51995,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2443,false,true,56875,Asian Art,Print,"井手の玉川|The Tama River at Ide, Yamashiro Province",Japan,Edo period (1615–1868),,,,Artist,,Suzuki Harunobu,"Japanese, 1725–1770",,Suzuki Harunobu,Japanese,1725,1770,probably 1766,1766,1766,Polychrome woodblock print; ink and color on paper,10 1/4 x 7 3/4 in. (26 x 19.7 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56875,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2444,false,true,56876,Asian Art,Print,"六玉川 「千鳥の玉川 陸奥名所」|“The Jewel River of Plovers, a Famous Place in Mutsu Province,” from the series Six Jewel Rivers (Mu Tamagawa: Chidori no Tamagawa, Mutsu meisho)",Japan,Edo period (1615–1868),,,,Artist,,Suzuki Harunobu,"Japanese, 1725–1770",,Suzuki Harunobu,Japanese,1725,1770,probably 1766,1766,1766,Polychrome woodblock print; ink and color on paper,10 7/8 x 8 in. (27.6 x 20.3 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56876,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2445,false,true,40991,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Suzuki Harunobu,"Japanese, 1725–1770",,Suzuki Harunobu,Japanese,1725,1770,ca. 1770,1760,1780,Polychrome woodblock print; ink and color on paper,10 3/4 x 7 7/8 in. (27.3 x 20 cm) medium-size print (chu-ban),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/40991,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2446,false,true,56877,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Suzuki Harunobu,"Japanese, 1725–1770",,Suzuki Harunobu,Japanese,1725,1770,ca. 1762,1752,1772,Polychrome woodblock print; ink and color on paper,10 3/4 x 7 7/8 in. (27.3 x 20 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56877,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2447,false,true,56792,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Suzuki Harunobu,"Japanese, 1725–1770",,Suzuki Harunobu,Japanese,1725,1770,ca. 1766,1756,1776,Polychrome woodblock print; chuban; ink and color on paper,H. 11 1/8 in. (28.3 cm); W. 7 1/16 in. (17.9 cm) medium-size print (chu-ban),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56792,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2448,false,true,56878,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Suzuki Harunobu,"Japanese, 1725–1770",,Suzuki Harunobu,Japanese,1725,1770,probably 1770,1770,1770,Polychrome woodblock print; ink and color on paper,Hashirae: 25 x 5 in. (63.5 x 12.7 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56878,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2450,false,true,56880,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Suzuki Harunobu,"Japanese, 1725–1770",,Suzuki Harunobu,Japanese,1725,1770,ca. 1766,1756,1776,Polychrome woodblock print; ink and color on paper,11 1/4 x 8 in. (28.6 x 8 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56880,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2451,false,true,56881,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Suzuki Harunobu,"Japanese, 1725–1770",,Suzuki Harunobu,Japanese,1725,1770,ca. 1768,1758,1778,Polychrome woodblock print; ink and color on paper,10 3/4 x 7 7/8 in. (27.3 x 20 cm) medium-size print (chu-ban),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56881,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2452,false,true,56882,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Suzuki Harunobu,"Japanese, 1725–1770",,Suzuki Harunobu,Japanese,1725,1770,ca. 1768,1758,1778,Polychrome woodblock print; ink and color on paper,11 1/2 x 8 3/8 in. (29.2 x 21.3 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56882,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2453,false,true,45085,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Suzuki Harunobu,"Japanese, 1725–1770",,Suzuki Harunobu,Japanese,1725,1770,1764–72,1764,1772,"Polychrome woodblock print; ink and color on paper, with embossing (karazuri)",H. 11 1/4 in. (28.6 cm); W. 8 1/8 in. (20.6 cm) medium-size print (chu-ban),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45085,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2454,false,true,56883,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Suzuki Harunobu,"Japanese, 1725–1770",,Suzuki Harunobu,Japanese,1725,1770,probably 1770,1770,1770,Polychrome woodblock print; ink and color on paper,Hashire: 27 1/2 x 5 in. (69.9 x 12.7 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56883,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2455,false,true,56884,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Suzuki Harunobu,"Japanese, 1725–1770",,Suzuki Harunobu,Japanese,1725,1770,probably 1770,1770,1770,Polychrome woodblock print; ink and color on paper,Hashirae: 28 1/8 x 5 in. (71.4 x 12.7 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56884,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2767,false,true,45074,Asian Art,Print,未月|The Seventh Month (Fumizuki),Japan,Edo period (1615–1868),,,,Artist,,Suzuki Harunobu,"Japanese, 1725–1770",,Suzuki Harunobu,Japanese,1725,1770,ca. 1865,1855,1875,Polychrome woodblock print; ink and color on paper,14 x 3 in. (35.6 x 7.6 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45074,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2768,false,true,45091,Asian Art,Print,青楼美人合|The Courtesan Kasugano Writing a Letter,Japan,Edo period (1615–1868),,,,Artist,,Suzuki Harunobu,"Japanese, 1725–1770",,Suzuki Harunobu,Japanese,1725,1770,ca. 1765,1755,1775,Polychrome woodblock print (Yonkyokuban); ink and color on paper,8 3/8 x 5 3/4 in. (21.3 x 14.6 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45091,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2769,false,true,45092,Asian Art,Print,青楼美人合|The Courtesan Itsuhata with Her Pipe,Japan,Edo period (1615–1868),,,,Artist,,Suzuki Harunobu,"Japanese, 1725–1770",,Suzuki Harunobu,Japanese,1725,1770,ca. 1765,1755,1775,Polychrome woodblock print (Yonkyokuban); ink and color on paper,8 3/8 x 5 3/4 in. (21.3 x 14.6 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45092,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2771,false,true,45067,Asian Art,Print,Sakura-gari|桜狩|Cherry Blossom Viewing,Japan,Edo period (1615–1868),,,,Artist,,Suzuki Harunobu,"Japanese, 1725–1770",,Suzuki Harunobu,Japanese,1725,1770,1764–70,1764,1770,Polychrome woodblock print; ink and color on paper,H. 11 3/8 in. (28.9 cm); W. 8 3/8 in. (21.3 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45067,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2772,false,true,45077,Asian Art,Print,百人一首 西行法師|Poem by the Monk Saigyō (1118-1190),Japan,Edo period (1615–1868),,,,Artist,,Suzuki Harunobu,"Japanese, 1725–1770",,Suzuki Harunobu,Japanese,1725,1770,ca. 1767–68,1767,1768,Polychrome woodblock print; ink and color on paper,11 1/6 x 8 1/4 in. (28.4 x 21 cm) medium-size block (chu-ban),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45077,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2773,false,true,42564,Asian Art,Print,百人一首 中納言兼輔|Man and Woman Playing Shogi,Japan,Edo period (1615–1868),,,,Artist,,Suzuki Harunobu,"Japanese, 1725–1770",,Suzuki Harunobu,Japanese,1725,1770,mid-18th century,1734,1766,Polychrome woodblock print; ink and color on paper,11 1/8 x 8 1/8 in. (28.3 x 20.6 cm),"Henry J. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/42564,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2774,false,true,57031,Asian Art,Print,Yuki|雪月花 雪|Snow,Japan,Edo period (1615–1868),,,,Artist,,Suzuki Harunobu,"Japanese, 1725–1770",,Suzuki Harunobu,Japanese,1725,1770,ca. 1769,1759,1779,Polychrome woodblock print; ink and color on paper,11 1/8 x 8 3/8 in. (28.3 x 21.3 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/57031,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2775,false,true,57032,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Suzuki Harunobu,"Japanese, 1725–1770",,Suzuki Harunobu,Japanese,1725,1770,1725–1770,1725,1770,Polychrome woodblock print; ink and color on paper,11 1/4 x 8 5/8 in. (28.6 x 21.9 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/57032,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2776,false,true,45064,Asian Art,Print,風俗四季歌仙 立春|The First Day of Spring (Risshun),Japan,Edo period (1615–1868),,,,Artist,,Suzuki Harunobu,"Japanese, 1725–1770",,Suzuki Harunobu,Japanese,1725,1770,ca. 1768,1778,1798,Polychrome woodblock print; ink and color on paper,11 x 8 1/4 in. (27.9 x 21 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45064,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2777,false,true,57033,Asian Art,Woodblock print,遊女と新造|Courtesan and Shinzō,Japan,Edo period (1615–1868),,,,Artist,,Suzuki Harunobu,"Japanese, 1725–1770",,Suzuki Harunobu,Japanese,1725,1770,1725–1770,1725,1770,Polychrome woodblock print; ink and color on paper,11 1/4 x 8 1/2 in. (28.6 x 21.6 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/57033,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2778,false,true,57034,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Suzuki Harunobu,"Japanese, 1725–1770",,Suzuki Harunobu,Japanese,1725,1770,ca. 1768–69,1768,1769,Polychrome woodblock print; ink and color on paper,11 1/4 x 8 5/8 in. (28.6 x 21.9 cm) medium-size print (chu-ban),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/57034,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2779,false,true,57035,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Suzuki Harunobu,"Japanese, 1725–1770",,Suzuki Harunobu,Japanese,1725,1770,1725–1770,1725,1770,Polychrome woodblock print; ink and color on paper,11 1/8 x 8 1/2 in. (28.3 x 21.6 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/57035,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2780,false,true,57036,Asian Art,Print,Sumidagawa no rakugan|風俗江戸八景 隅田川落雁|Wild Geese Flying Down the Sumida River,Japan,Edo period (1615–1868),,,,Artist,,Suzuki Harunobu,"Japanese, 1725–1770",,Suzuki Harunobu,Japanese,1725,1770,ca. 1769,1759,1779,Polychrome woodblock print; ink and color on paper,11 1/4 x 8 3/8 in. (28.6 x 21.3 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/57036,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3019,false,true,56417,Asian Art,Print,井手の玉川|Jewel River at Ide (Ide no Tamagawa),Japan,Edo period (1615–1868),,,,Artist,,Suzuki Harunobu,"Japanese, 1725–1770",,Suzuki Harunobu,Japanese,1725,1770,ca. 1768,1758,1778,Polychrome woodblock print; ink and color on paper,10 3/4 x 7 7/16 in. (27.3 x 18.9 cm) medium-size print (chu-ban),"Harris Brisbane Dick Fund, 1946",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56417,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3021,false,true,56419,Asian Art,Woodblock print,Kōromō Uchi Tamagawa|Fulling Cloth at the Jewel River (Kinuta no Tamagawa),Japan,Edo period (1615–1868),,,,Artist,,Suzuki Harunobu,"Japanese, 1725–1770",,Suzuki Harunobu,Japanese,1725,1770,ca. 1768,1758,1778,Polychrome woodblock print; ink and color on paper,10 3/4 x 7 7/8 in. (27.3 x 20 cm) medium-size print (chu-ban),"Harris Brisbane Dick Fund, 1946",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56419,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3022,false,true,56420,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Suzuki Harunobu,"Japanese, 1725–1770",,Suzuki Harunobu,Japanese,1725,1770,ca. 1768,1758,1778,Polychrome woodblock print; ink and color on paper,10 5/8 x 7 3/4 in. (27 x 19.7 cm) medium-size print (chu-ban),"Harris Brisbane Dick Fund, 1946",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56420,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3043,false,true,45041,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Suzuki Harunobu,"Japanese, 1725–1770",,Suzuki Harunobu,Japanese,1725,1770,ca. 1760,1750,1770,Polychrome woodblock print; ink and color on paper,13 3/4 x 5 3/4 in. (34.9 x 14.6 cm),"Gift of Mrs. Morris Manges, in memory of her husband, Dr. Morris Manges, 1947",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45041,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3052,false,true,56507,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Suzuki Harunobu,"Japanese, 1725–1770",,Suzuki Harunobu,Japanese,1725,1770,ca. 1768,1758,1778,Polychrome woodblock print; chuban; ink and color on paper,10 x 15 1/2 in. (25.4 x 39.4 cm) medium-size print (chu-ban),"Gift of Mrs. Morris Manges, in memory of her husband, Dr. Morris Manges, 1947",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56507,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3406,false,true,55593,Asian Art,Print,"風流江戸八景 真乳山の暮雪|Evening Snow on Matsuchi Hilll, from the series Eight Fashionable Views of Edo (Furyu Edo hakkei)",Japan,Edo period (1615–1868),,,,Artist,,Suzuki Harunobu,"Japanese, 1725–1770",,Suzuki Harunobu,Japanese,1725,1770,ca. 1765–70,1755,1780,Polychrome woodblock print; ink and color on paper,11 x 8 1/8 in. (27.9 x 20.6 cm) medium-size print (chu-ban),"Bequest of Julia H. Manges, 1960",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55593,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"JP2770a, b",false,true,57030,Asian Art,Print,青楼美人合|Two Girls Play the Finger Game of Kitsume Ken,Japan,Edo period (1615–1868),,,,Artist,,Suzuki Harunobu,"Japanese, 1725–1770",,Suzuki Harunobu,Japanese,1725,1770,1725–1770,1725,1770,Diptych of polychrome woodblock prints; ink and color on paper,a Left sheet: 8 3/8 x 5 3/4 in. (21.3 x 14.6 cm); b Right sheet: 8 3/8 x 5 3/4 in. (21.3 x 14.6 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/57030,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP111,false,true,36590,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,probably 1785,1785,1785,Polychrome woodblock print; ink and color on paper,14 1/3 x 9 7/16 in. (36.4 x 24.0 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36590,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP112,false,true,36591,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,ca. 1779,1769,1789,Polychrome woodblock print; ink and color on paper,11 7/8 x 5 5/8 in. (30.2 x 14.3 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36591,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP113,false,true,36592,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,"9th month, 1774",1774,1774,Polychrome woodblock print; ink and color on paper,12 1/8 x 5 2/3 in. (30.8 x 14.4 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36592,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP114,false,true,36593,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,1782,1782,1782,Middle sheet of a triptych of polychrome woodblock prints; ink and color on paper,12 3/4 x 5 7/8 in. (32.4 x 14.9 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36593,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP115,false,true,36594,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,dated October or November 1778,1778,1778,Polychrome woodblock print; ink and color on paper,12 5/8 x 5 5/8 in. (32.1 x 14.3 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36594,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP116,false,true,36595,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,ca. 1773,1763,1783,Polychrome woodblock print; ink and color on paper,12 x 5 3/8 in. (30.5 x 13.7 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36595,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP117,false,true,36596,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,ca. 1778,1768,1788,Polychrome woodblock print; ink and color on paper,12 5/16 x 5 13/16 in. (31.3 x 14.8 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36596,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP118,false,true,36597,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,1773 or 1774,1773,1774,Polychrome woodblock print; ink and color on paper,11 3/8 x 5 1/5 in. (28.9 x 13.2 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36597,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP119,false,true,36598,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,ca. 1782,1772,1792,Polychrome woodblock print; ink and color on paper,12 4/5 x 5 3/4 in. (32.5 x 14.6 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36598,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP120,false,true,36599,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,ca. 1770,1760,1780,Polychrome woodblock print; ink and color on paper,12 x 5 5/8 in. (30.5 x 14.3 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36599,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP121,false,true,36600,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,1773 or 1774,1773,1774,Polychrome woodblock print; ink and color on paper,12 x 5 1/2 in. (30.5 x 14.0 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36600,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP122,false,true,36601,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,1773 or 1774,1773,1774,Polychrome woodblock print; ink and color on paper,11 7/8 x 5 3/4 in. (30.2 x 14.6 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36601,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP124,false,true,36603,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,1781,1781,1781,Polychrome woodblock print; ink and color on paper,11 7/8 x 5 31/32 in. (30.2 x 15.2 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36603,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP125,false,true,36604,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,ca. 1778,1768,1788,Polychrome woodblock print; ink and color on paper,11 1/2 x 5 1/2 in. (29.2 x 14.0 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36604,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP126,false,true,36605,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,"2nd month, 1785",1785,1785,Polychrome woodblock print; ink and color on paper,10 31/32 x 5 1/2 in. (27.9 x 14 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36605,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP127,false,true,36606,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,ca. 1772,1762,1782,Polychrome woodblock print; ink and color on paper,11 1/2 x 5 1/5 in. (29.2 x 13.2 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36606,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP207,false,true,36684,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,dated 1769,1769,1769,Polychrome woodblock print; ink and color on paper,Overall: 12 1/16 x 5 13/16 in. (30.6 x 14.8 cm),"Gift of Louis V. Ledoux, 1931",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36684,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP342,false,true,36807,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,ca. 1780,1778,1782,Polychrome woodblock print; ink and color on paper,12 7/32 x 5 3/4 in. (31.1 x 14.6 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36807,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP343,false,true,36808,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,"6th month, 1783",1783,1783,Polychrome woodblock print; ink and color on paper,12 11/32 x 5 17/32 in. (31.4 x 14.1 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36808,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP344,false,true,36809,Asian Art,Woodblock print,五代目市川団十郎|Kabuki Actor Ichikawa Danjūrō V as Sakata Kintoki in the Play Raikō’s Four Intrepid Retainers in the Costume of the Night Watch (Shitennō tonoi no kisewata),Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,"11th month, 1781",1781,1781,Polychrome woodblock print; ink and color on paper,12 5/16 x 5 1/2 in. (31.3 x 14 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36809,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP345,false,true,36810,Asian Art,Woodblock print,五代目市川団十郎|Kabuki Actor Ichikawa Danjūrō V in a Shibaraku (Stop Right There!) Role,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,"11th month, 1779",1779,1779,Polychrome woodblock print; ink and color on paper,Hosoban 12 1/2 x 5 7/8 in. (31.8 x 14.9 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36810,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP346,false,true,36811,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,ca. 1785?,1783,1787,Polychrome woodblock print; ink and color on paper,12 11/32 x 5 1/2 in. (31.4 x 14.0 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36811,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP347,false,true,36812,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,"2nd month, 1785",1785,1785,Polychrome woodblock print; ink and color on paper,Hosoban 12 7/8 x 5 3/4 in. (32.7 x 14.6 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36812,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP348,false,true,36813,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,ca. 1778,1768,1788,Polychrome woodblock print; ink and color on paper,12 9/16 x 5 17/32 in. (31.9 x 14.1 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36813,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP349,false,true,36814,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,ca. 1778,1776,1780,Polychrome woodblock print; ink and color on paper,12 11/32 x 5 3/4 in. (31.4 x 14.6 cm),"Purchase, Joseph Pultizer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36814,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP350,false,true,36815,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,ca. 1783–84,1781,1785,Polychrome woodblock print; ink and color on paper,12 7/16 x 5 7/8 in. (31.6 x 14.9 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36815,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP351,false,true,36816,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,"10th month, 1777",1777,1777,Polychrome woodblock print; ink and color on paper,11 3/10 x 5 3/4 in. (28.7 x 14.6 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36816,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP352,false,true,48895,Asian Art,Print,"二代目市川八百藏・二代目中島三甫右衛門・三代目市川海老藏・九代目市村羽左衛門|Kabuki Actors Ichikawa Yaozō II, Nakajima Mihoemon II, Ichikawa Ebizō III, and Ichimura Uzaemon IX in the Play Sugawara’s Secrets of Calligraphy (Sugawara denju tenarai kagami)",Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,"7th month, 1776",1776,1776,One sheet of a triptych of polychrome woodblock prints; ink and color on paper,Overall: 12 5/8 x 17 7/16 in. (32.1 x 44.3 cm); Image: 12 3/4 x 5 7/8 in. (32.4 x 14.9 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/48895,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP353,false,true,36817,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,ca. 1787,1777,1797,Polychrome woodblock print; ink and color on paper,11 7/8 x 5 2/3 in. (30.2 x 14.4 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36817,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP354,false,true,36818,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,"12th month, 1780",1780,1780,Left-hand sheet of a diptych of polychrome woodblock prints; ink and color on paper,12 1/2 x 5 7/8 in. (31.8 x 14.9 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36818,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP355,false,true,36819,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,1780,1780,1780,Polychrome woodblock print; ink and color on paper,12 4/5 x 5 7/8 in. (32.5 x 14.9 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36819,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP356,false,true,36820,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,1787 or 1788,1787,1788,Polychrome woodblock print; ink and color on paper,12 4/5 x 5 13/16 in. (32.5 x 14.8 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36820,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP357,false,true,36821,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,ca. 1778,1768,1788,Polychrome woodblock print; ink and color on paper,11 23/32 x 5 3/4 in. (29.8 x 14.6 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36821,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP358,false,true,36822,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,ca. 1779–83,1777,1785,Polychrome woodblock print; ink and color on paper,12 1/2 x 5 13/16 in. (31.8 x 14.8 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36822,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP359,false,true,36823,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,1778,1778,1778,One sheet of a triptych of polychrome woodblock prints; ink and color on paper,12 9/16 x 5 7/8 in. (31.9 x 14.9 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36823,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP360,false,true,36824,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,1776,1776,1776,Polychrome woodblock print; ink and color on paper,12 7/32 x 5 5/8 in. (31.1 x 14.3 cm),"Purchase, Joseph Pultizer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36824,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP361,false,true,36825,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,ca. 1779,1769,1789,Polychrome woodblock print; ink and color on paper,12 9/16 x 5 13/16 in. (31.9 x 14.8 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36825,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP362,false,true,36826,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,ca. 1788,1778,1798,Polychrome woodblock print; ink and color on paper,12 5/8 x 5 3/4 in. (32.1 x 14.6 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36826,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP363,false,true,36827,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,probably 1782,1780,1784,Polychrome woodblock print; ink and color on paper,12 5/8 x 5 2/3 in. (32.1 x 14.4 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36827,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP364,false,true,36828,Asian Art,Print,三代目大谷廣右衛門|The Third Otani Hiroemon as an Outlaw Standing Near a Willow Tree,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,"3rd month, 1777",1777,1777,Polychrome woodblock print; ink and color on paper,11 15/16 x 5 5/8 in. (30.3 x 14.3 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36828,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP365,false,true,36829,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,ca. 1785,1775,1795,Polychrome woodblock print; ink and color on paper,12 5/16 x 5 2/3 in. (31.3 x 14.4 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36829,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP366,false,true,36830,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,ca. 1783,1773,1793,Polychrome woodblock print; ink and color on paper,12 5/8 x 5 3/4 in. (32.1 x 14.6 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36830,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP367,false,true,36831,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,ca. 1782,1772,1792,Polychrome woodblock print; ink and color on paper,12 4/5 x 5 7/8 in. (32.5 x 14.9 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36831,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP368,false,true,36832,Asian Art,Woodblock print,五代目市川団十郎|Kabuki Actor Ichikawa Danjūrō V in a Shibaraku (Stop Right There!) Role as Hannya no Gorō,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,11th month 1776,1776,1776,Polychrome woodblock print; ink and color on paper,Hosoban 12 9/16 x 5 7/8 in. (31.9 x 14.9 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36832,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP369,false,true,36833,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,ca. 1778,1768,1788,Polychrome woodblock print; ink and color on paper,12 3/4 x 5 7/8 in. (32.4 x 14.9 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36833,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP370,false,true,36834,Asian Art,Print,初代中村仲蔵|The First Nakamura Nakazō in the Role of Shimada no Hachizō,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,1783,1783,1783,Polychrome woodblock print; ink and color on paper,12 7/8 x 5 13/16 in. (32.7 x 14.8 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36834,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP371,false,true,36835,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,probably 1775,1773,1777,Polychrome woodblock print; ink and color on paper,12 7/8 x 5 7/8 in. (32.7 x 14.9 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36835,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP372,false,true,36836,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,ca. 1777,1767,1787,Polychrome woodblock print; ink and color on paper,12 1/32 x 5 3/4 in. (30.6 x 14.6 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36836,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP373,false,true,36837,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,"12th month, 1786",1786,1786,Polychrome woodblock print; ink and color on paper,12 7/32 x 5 1/2 in. (31.1 x 14.0 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36837,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP374,false,true,36838,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,ca. 1781,1771,1791,Polychrome woodblock print; ink and color on paper,12 7/16 x 5 5/8 in. (31.6 x 14.3 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36838,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP375,false,true,36839,Asian Art,Print,二代目中村助五郎|Kabuki Actor Nakamura Sukegorō II as Kaminari Shōkurō,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,"2nd month, 1780",1780,1780,One sheet of a pentaptych; polychrome woodblock print; ink and color on paper,12 11/32 x 5 5/8 in. (31.4 x 14.3 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36839,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP376,false,true,36840,Asian Art,Print,三代目瀬川菊之丞|Kabuki Actor Segawa Kikunojō III in a Female Role (Shizuka Gozen),Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,ca. 1777,1767,1787,Polychrome woodblock print; ink and color on paper,12 11/32 x 5 17/32 in. (31.4 x 14.1 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36840,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP377,false,true,36841,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,ca. 1777,1767,1787,Polychrome woodblock print; ink and color on paper,12 1/2 x 5 3/4 in. (31.8 x 14.6 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36841,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP378,false,true,36842,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,ca. 1779,1769,1789,Polychrome woodblock print; ink and color on paper,12 1/2 x 5 5/8 in. (31.8 x 14.3 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36842,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP379,false,true,36843,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,1784,1784,1784,Polychrome woodblock print; ink and color on paper,12 11/16 x 5 5/8 in. (32.2 x 14.3 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36843,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP380,false,true,36844,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,ca. 1787–90,1785,1792,Polychrome woodblock print; ink and color on paper,12 x 5 5/8 in. (30.5 x 14.3 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36844,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP381,false,true,36845,Asian Art,Print,初代中村富十郎|Kabuki Actor Nakamura Tomijūrō I in a Female Dance Role,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,ca. 1777,1767,1787,Polychrome woodblock print; ink and color on paper,Hosoban 11 7/8 x 5 17/32 in. (30.2 x 14.1 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36845,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP382,false,true,36846,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,"2nd month, 1773",1773,1773,Polychrome woodblock print; ink and color on paper,Overall: 13 x 5 3/4in. (33 x 14.6cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36846,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP383,false,true,36847,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,1780,1780,1780,Polychrome woodblock print; ink and color on paper,12 11/32 x 5 1/2 in. (31.4 x 14.0 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36847,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP384,false,true,36848,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,ca. 1786–87,1784,1789,Polychrome woodblock print; ink and color on paper,12 1/2 x 5 3/4 in. (31.8 x 14.6 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36848,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP385,false,true,36849,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,"summer, 1772",1772,1772,Polychrome woodblock print; ink and color on paper,12 1/8 x 5 3/4 in. (30.8 x 14.6 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36849,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP392,false,true,36852,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,ca. 1785,1775,1795,Polychrome woodblock print; ink and color on paper,12 7/32 x 5 3/4 in. (31.1 x 14.6 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36852,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP395,false,true,36853,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,probably 1778,1776,1780,Diptych (probably two sheets of a triptych) of polychrome woodblock prints; ink and color on paper,A: H. 12 11/16 in. (32.2 cm); W. 5 11/16 in. (14.4 cm) B: H. 12 9/16 in. (31.9 cm); W. 5 3/4 in. (14.6 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36853,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP399,false,true,36854,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,1774,1774,1774,Diptych of polychrome woodblock prints; ink and color on paper,A: H. 12 11/16 in. (32.2 cm); W. 5 5/8 in. (14.3 cm) B: H. 12 13/16 in (32.5 cm); W. 5 9/16 in. (14.1 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36854,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP402,false,true,36855,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,1773,1773,1773,Polychrome woodblock print; ink and color on paper,12 11/32 x 5 1/2 in. (31.4 x 14.0 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36855,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP403,false,true,36856,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,1773,1773,1773,Polychrome woodblock print; ink and color on paper,12 3/4 x 5 13/16 in. (32.4 x 14.8 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36856,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP404,false,true,36857,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,"12th month, 1775",1775,1775,Polychrome woodblock print; ink and color on paper,12 5/16 x 5 1/2 in. (31.3 x 14.0 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36857,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP405,false,true,36858,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,1774 or 1775,1774,1775,Polychrome woodblock print; ink and color on paper,12 11/32 x 5 3/8 in. (31.4 x 13.7 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36858,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP406,false,true,36859,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,ca. 1766,1756,1776,Polychrome woodblock print; ink and color on paper,12 3/4 x 5 13/16 in. (32.4 x 14.8 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36859,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP407,false,true,36860,Asian Art,Print,四代目市川団十郎|Kabuki Actor Ichikawa Ebizō III (Ichikawa Danjūrō IV),Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,"2nd month, 1774",1774,1774,Polychrome woodblock print; ink and color on paper,12 3/4 x 5 3/4 in. (32.4 x 14.6 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36860,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP408,false,true,36861,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,probably 1773,1771,1775,Polychrome woodblock print; ink and color on paper,11 23/32 x 5 5/8 in. (29.8 x 14.3 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36861,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP409,false,true,36862,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,ca. 1774,1764,1784,Polychrome woodblock print; ink and color on paper,12 11/16 x 5 7/8 in. (32.2 x 14.9 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36862,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP410,false,true,36863,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,1773 or 1774,1773,1774,Polychrome woodblock print; ink and color on paper,12 4/5 x 5 7/8 in. (32.5 x 14.9 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36863,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP411,false,true,36864,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,1774 or 1775,1774,1775,Polychrome woodblock print; ink and color on paper,12 1/2 x 5 3/4 in. (31.8 x 14.6 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36864,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP412,false,true,36865,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,ca. 1771,1761,1781,Polychrome woodblock print; ink and color on paper,12 5/8 x 5 13/16 in. (32.1 x 14.8 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36865,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP413,false,true,36866,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,ca. 1775,1765,1785,One sheet of a diptych or triptych of polychrome woodblock prints; ink and color on paper,12 x 5 7/8 in. (30.5 x 14.9 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36866,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP414,false,true,36867,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,1773 ?,1771,1775,Polychrome woodblock print; ink and color on paper,12 5/16 x 5 13/16 in. (31.3 x 14.8 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36867,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP415,false,true,36868,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,ca. 1771,1761,1781,Polychrome woodblock print; ink and color on paper,12 1/8 x 5 7/8 in. (30.8 x 14.9 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36868,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP416,false,true,36869,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,ca. 1775,1765,1785,Polychrome woodblock print; ink and color on paper,12 1/2 x 5 13/16 in. (31.8 x 14.8 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36869,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP417,false,true,36870,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,1773 or 1774,1773,1774,Polychrome woodblock print; ink and color on paper,12 11/16 x 5 13/16 in. (32.2 x 14.8 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36870,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP418,false,true,36871,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,"2nd month, 1771",1771,1771,Polychrome woodblock print; ink and color on paper,12 7/8 x 5 15/16 in. (32.7 x 15.1 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36871,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP419,false,true,36872,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,"12th month, 1774",1774,1774,Polychrome woodblock print; ink and color on paper,12 11/32 x 5 1/2 in. (31.4 x 14.0 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36872,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP420,false,true,36873,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,1772 or 1773,1772,1773,Polychrome woodblock print; ink and color on paper,11 11/16 x 5 2/3 in. (29.7 x 14.4 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36873,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP422,false,true,36875,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,probably 1773,1771,1775,Polychrome woodblock print; ink and color on paper,Hosoban; 12 5/16 x 5 9/16 in. (31.3 x 14.1 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36875,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP423,false,true,36876,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,1774 or 1775,1774,1775,Polychrome woodblock print; ink and color on paper,12 11/16 x 5 3/4 in. (32.2 x 14.6 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36876,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP424,false,true,36877,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,1773 or 1774,1773,1774,Polychrome woodblock print; ink and color on paper,12 5/8 x 5 3/4 in. (32.1 x 14.6 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36877,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP425,false,true,36878,Asian Art,Print,鳥高斎栄昌画 「丁子屋畧見世」|The First Nakamura Tomijuro as an Oiran Standing in a Room,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,1771,1771,1771,Polychrome woodblock print; ink and color on paper,12 1/5 x 5 13/16 in. (31.0 x 14.8 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36878,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP426,false,true,36879,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,ca. 1774,1764,1784,Polychrome woodblock print; ink and color on paper,12 5/8 x 5 7/8 in. (32.1 x 14.9 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36879,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP427,false,true,36880,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,probably 1774,1772,1776,Left-hand sheet of a triptych of polychrome woodblock prints; ink and color on paper,12 11/32 x 5 13/16 in. (31.4 x 14.8 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36880,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP428,false,true,36881,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,1774 or 1775,1774,1775,One sheet of a triptych of polychrome woodblock prints; ink and color on paper,12 11/32 x 5 13/16 in. (31.4 x 14.8 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36881,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP429,false,true,36882,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,ca. 1775,1765,1785,One sheet of a triptych of polychrome woodblock prints; ink and color on paper,11 7/8 x 5 5/8 in. (30.2 x 14.3 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36882,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP430,false,true,36883,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,ca. 1775,1765,1785,Polychrome woodblock print; ink and color on paper,12 1/2 x 5 5/8 in. (31.8 x 14.3 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36883,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP431,false,true,36884,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,probably 1774,1772,1776,Polychrome woodblock print; ink and color on paper,12 11/32 x 5 17/32 in. (31.4 x 14.1 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36884,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP432,false,true,36885,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,"2nd month, 1771",1771,1771,Polychrome woodblock print; ink and color on paper,12 x 5 5/8 in. (30.5 x 14.3 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36885,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP433,false,true,36886,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,probably 1775,1773,1777,Polychrome woodblock print; ink and color on paper,12 7/32 x 5 13/16 in. (31.1 x 14.8 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36886,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP434,false,true,36887,Asian Art,Print,四代目市川団十郎|Kabuki Actor Ichikawa Danjūrō IV,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,"12th month, 1771",1771,1771,Polychrome woodblock print; ink and color on paper,12 31/32 x 5 13/16 in. (33.0 x 14.8 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36887,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP435,false,true,36888,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,"2nd month, 1771",1771,1771,Polychrome woodblock print; ink and color on paper,12 4/5 x 5 7/8 in. (32.5 x 14.9 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36888,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP436,false,true,36889,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,"2nd month, 1770",1770,1770,Polychrome woodblock print; ink and color on paper,12 5/8 x 5 5/8 in. (32.1 x 14.3 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36889,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP437,false,true,36890,Asian Art,Woodblock print,九代目市村羽左衛門・三代目大谷廣次|Kabuki Actors Ichimura Uzaemon IX as Ko-kakeyama and Ōtani Hiroji III as Kōga Saburō,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,ca. 1771,1761,1781,Polychrome woodblock print; ink and color on paper,12 x 5 5/8 in. (30.5 x 14.3 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36890,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP438,false,true,36891,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,ca. 1771,1761,1781,Polychrome woodblock print; ink and color on paper,12 11/32 x 5 17/32 in. (31.4 x 14.1 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36891,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP439,false,true,36892,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,"12th month, 1768",1768,1768,Polychrome woodblock print; ink and color on paper,12 x 5 1/2 in. (30.5 x 14.0 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36892,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP440,false,true,36893,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,1768 or 1769,1768,1769,Polychrome woodblock print; ink and color on paper,12 3/4 x 5 2/3 in. (32.4 x 14.4 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36893,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP441,false,true,36894,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,ca. 1773,1763,1783,Polychrome woodblock print; ink and color on paper,12 5/8 x 5 7/8 in. (32.1 x 14.9 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36894,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP442,false,true,36895,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,1770,1770,1770,Polychrome woodblock print; ink and color on paper,12 9/16 x 5 7/8 in. (31.9 x 14.9 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36895,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP443,false,true,36896,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,1769 or 1770,1769,1770,Polychrome woodblock print; ink and color on paper,12 5/8 x 5 13/16 in. (32.1 x 14.8 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36896,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP444,false,true,36897,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,1769 Autumn,1769,1769,Polychrome woodblock print; ink and color on paper,12 11/32 x 5 17/32 in. (31.4 x 14.1 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36897,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP445,false,true,36898,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,probably 1770,1768,1772,Polychrome woodblock print; ink and color on paper,12 11/32 x 5 1/2 in. (31.4 x 14.0 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36898,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP446,false,true,36899,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,"12th month, 1769",1769,1769,Polychrome woodblock print; ink and color on paper,12 11/32 x 5 7/16 in. (31.4 x 13.8 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36899,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP447,false,true,36900,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,1770,1770,1770,Polychrome woodblock print; ink and color on paper,12 3/4 x 5 7/8 in. (32.4 x 14.9 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36900,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP448,false,true,36901,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,1770 or 1771,1770,1771,Polychrome woodblock print; ink and color on paper,12 1/2 x 5 13/16 in. (31.8 x 14.8 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36901,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP449,false,true,36902,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,1769 or 1770,1769,1770,Polychrome woodblock print; ink and color on paper,12 1/2 x 5 3/4 in. (31.8 x 14.6 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36902,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP450,false,true,36903,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,1769 or 1770,1769,1770,Polychrome woodblock print; ink and color on paper,12 3/4 x 5 7/8 in. (32.4 x 14.9 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36903,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP451,false,true,36904,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,"2nd month, 1770",1770,1770,Polychrome woodblock print; ink and color on paper,12 1/8 x 5 5/8 in. (30.8 x 14.3 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36904,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP452,false,true,36905,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,"summer, 1768",1768,1768,Polychrome woodblock print; ink and color on paper,12 1/8 x 5 17/32 in. (30.8 x 14.1 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36905,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP453,false,true,36906,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,1769,1769,1769,Polychrome woodblock print; ink and color on paper,12 1/2 x 5 3/4 in. (31.8 x 14.6 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36906,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP454,false,true,36907,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,1768 or 1769,1768,1769,Polychrome woodblock print; ink and color on paper,12 x 5 1/2 in. (30.5 x 14.0 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36907,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP455,false,true,36908,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,"12th month, 1770",1770,1770,Polychrome woodblock print; ink and color on paper,12 7/16 x 5 7/32 in. (31.6 x 13.3 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36908,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP456,false,true,36909,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,ca. 1770,1760,1780,Polychrome woodblock print; ink and color on paper,12 7/16 x 5 1/2 in. (31.6 x 14.0 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36909,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP458,false,true,36911,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,"12th month, 1768",1768,1768,Polychrome woodblock print; ink and color on paper,12 11/32 x 5 1/2 in. (31.4 x 14.0 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36911,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP460,false,true,36913,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,1768 or 1769,1768,1769,Polychrome woodblock print; ink and color on paper,12 7/16 x 5 1/2 in. (31.6 x 14 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36913,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP461,false,true,36914,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,1768 or 1769,1768,1769,Polychrome woodblock print; ink and color on paper,12 11/32 x 5 2/3 in. (31.4 x 14.4 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36914,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP462,false,true,36915,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,1791,1791,1791,Polychrome woodblock print; ink and color on paper,12 5/8 x 5 3/4 in. (32.1 x 14.6 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36915,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP463,false,true,36916,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,ca. 1775,1765,1785,Polychrome woodblock print; ink and color on paper,11 3/5 x 5 3/4 in. (29.5 x 14.6 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36916,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP464,false,true,48894,Asian Art,Print,"二代目市川八百藏・二代目中島三甫右衛門・三代目市川海老藏・九代目市村羽左衛門|Kabuki Actors Ichikawa Yaozō II, Nakajima Mihoemon II, Ichikawa Ebizō III, and Ichimura Uzaemon IX in the Play Sugawara’s Secrets of Calligraphy (Sugawara denju tenarai kagami)",Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,"7th month, 1776",1776,1776,One sheet of a triptych of polychrome woodblock prints; ink and color on paper,Overall: 12 5/8 x 17 7/16 in. (32.1 x 44.3 cm); Image: 12 11/16 x 5 15/16 in. (32.2 x 15.1 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/48894,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP465,false,true,36917,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,ca. 1779,1759,1779,Polychrome woodblock print; ink and color on paper,11 7/8 x 5 3/4 in. (30.2 x 14.6 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36917,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP551,false,true,37002,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,1769 spring,1769,1769,Polychrome woodblock print; ink and color on paper,12 5/8 x 5 3/4 in. (32.1 x 14.6 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37002,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP552,false,true,37003,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,ca. 1775 or 1776,1173,1778,Polychrome woodblock print; ink and color on paper,11 15/16 x 5 3/8 in. (30.3 x 13.7 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37003,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP553,false,true,37004,Asian Art,Print,"二代目市川八百藏・二代目中島三甫右衛門・三代目市川海老藏・九代目市村羽左衛門|Kabuki Actors Ichikawa Yaozō II, Nakajima Mihoemon II, Ichikawa Ebizō III, and Ichimura Uzaemon IX in the Play Sugawara’s Secrets of Calligraphy (Sugawara denju tenarai kagami)",Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,"7th month, 1776",1776,1776,One sheet of a triptych of polychrome woodblock prints; ink and color on paper,Overall: 12 5/8 x 17 7/16 in. (32.1 x 44.3 cm); Image: 12 9/16 x 5 3/4 in. (31.9 x 14.6 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37004,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP703,false,true,37150,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,ca. 1765,1755,1775,Polychrome woodblock print; ink and color on paper,12 x 5 1/2 in. (30.5 x 14.0 cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37150,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP704,false,true,37152,Asian Art,Print,"「風流六く歌仙紀友則 十」|“Two Young Women on a Verandah Watching Plovers,” from the series Stylish Six Poetic Immortals (Fūryū rokkasen: Ki no Tomonori, jū)",Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,ca. 1770,1760,1780,Polychrome woodblock print; ink and color on paper,9 7/8 x 7 1/8 in. (25.1 x 18.1 cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37152,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP705,false,true,37153,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,1772,1772,1772,Polychrome woodblock print; ink and color on paper,11 1/2 x 5 7/32 in. (29.2 x 13.3 cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37153,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP706,false,true,37154,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,ca. 1777,1767,1787,Polychrome woodblock print; ink and color on paper,12 7/32 x 5 5/8 in. (31.1 x 14.3 cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37154,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP707,false,true,37155,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,ca. 1778,1768,1788,Polychrome woodblock print; ink and color on paper,12 31/32 x 5 7/8 in. (33.0 x 14.9 cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37155,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP708,false,true,37156,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,ca. 1778,1768,1788,Polychrome woodblock print; ink and color on paper,12 7/32 x 17 1/5 in. (31.1 x 43.7 cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37156,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP709,false,true,37157,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,ca. 1778,1768,1788,Polychrome woodblock print; ink and color on paper,12 3/4 x 8 27/32 in. (32.4 x 22.5 cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37157,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP710,false,true,37158,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,ca. 1778,1768,1788,Polychrome woodblock print; ink and color on paper,12 7/32 x 8 1/10 in. (31.1 x 20.6 cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37158,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP711,false,true,37159,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,ca. 1779,1769,1789,Polychrome woodblock print; ink and color on paper,12 7/32 x 5 5/8 in. (31.1 x 14.3 cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37159,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP893,false,true,54587,Asian Art,Print,"花形見風折烏帽子瀬川富三郎の娘道成寺|Segawa Tomisaburo in the Role of Musume Dojoji in ""Hanagatami Kazaori Eboshi""",Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,February 1774,1769,1779,Polychrome woodblock print; ink and color on paper,H. 12 1/4 in. (31.1 cm); W. 5 3/4 in. (14.6 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54587,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP894,false,true,54588,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,ca. 1773,1763,1783,Polychrome woodblock print; ink and color on paper,H. 11 3/8 in. (28.9 cm); W. 5 7/8 in. (14.9 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54588,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP895,false,true,54589,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,ca. 1774,1764,1784,Polychrome woodblock print; ink and color on paper,H. 12 1/2 in. (31.8 cm); W. 5 7/8 in. (14.9 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54589,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP896,false,true,54590,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,1774,1774,1774,Polychrome woodblock print; ink and color on paper,H. 12 1/8 in. (30.8 cm); W. 5 7/8 in. (14.9 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54590,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP897,false,true,54591,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,1776,1776,1776,Polychrome woodblock print; ink and color on paper,H. 8 1/2 in. (21.6 cm); W. 11 3/4 in. (29.8 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54591,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP898,false,true,54592,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,ca. 1776,1766,1786,Polychrome woodblock print; ink and color on paper,H. 12 1/2 in. (31.8 cm); W. 8 1/4 in. (21 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54592,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP899,false,true,54595,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,ca. 1776,1766,1786,Polychrome woodblock print; ink and color on paper,H. 11 3/4 in. (29.8 cm); W. 8 1/4 in. (21 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54595,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP900,false,true,54596,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,ca. 1777,1767,1787,Polychrome woodblock print; ink and color on paper,H. 12 1/4 in. (31.1 cm); W. 5 3/4 in. (14.6 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54596,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP901,false,true,54597,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,ca. 1778,1768,1788,Polychrome woodblock print; ink and color on paper,H. 12 1/4 in. (31.1 cm); W. 5 5/8 in. (14.3 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54597,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP902,false,true,54599,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,ca. 1778,1768,1788,Polychrome woodblock print; ink and color on paper,H. 12 1/2 in. (31.8 cm); W. 5 3/4 in. (14.6 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54599,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP903,false,true,54600,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,ca. 1778,1768,1788,Diptych of polychrome woodblock prints; ink and color on paper,H. 14 1/4 in. (36.2 cm); W. 6 11/16 in. (17 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54600,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP904,false,true,54601,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,ca. 1778,1768,1788,Polychrome woodblock print; ink and color on paper,H. 12 1/4 in. (31.1 cm); W. 5 7/8 in. (14.9 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54601,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP905,false,true,54602,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,ca. 1779,1769,1789,Polychrome woodblock print; ink and color on paper,H. 12 1/4 in. (31.1 cm); W. 5 1/2 in. (14 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54602,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP906,false,true,54647,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,ca. 1779,1769,1789,Polychrome woodblock print; ink and color on paper,H. 12 3/4 in. (32.4 cm); W. 6 9/16 in. (16.7 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54647,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1270,false,true,55167,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,ca. 1778,1768,1788,Diptych of polychrome woodblock prints; ink and color on paper,H. 14 1/4 in. (36.2 cm); W. 6 11/16 in. (17 cm),"Rogers Fund, 1921",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55167,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1298,false,true,55239,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,ca. 1781,1771,1791,Polychrome woodblock print; ink and color on paper,H. 12 1/4 in. (31.1 cm); W. 5 7/8 in. (14.9 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55239,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1355,false,true,55331,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,ca. 1771,1761,1781,Polychrome woodblock print; ink and color on paper,H. 12 13/16 in. (32.5 cm); W. 5 9/16 in. (14.1 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55331,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1356,false,true,55333,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,1768,1768,1768,Polychrome woodblock print; ink and color on paper,H. 12 3/4 in. (32.4 cm); W. 5 11/16 in. (14.4 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55333,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1357,false,true,55334,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,1769,1769,1769,Polychrome woodblock print; ink and color on paper,H. 12 7/8 in. (32.7 cm); W. 5 7/8 in. (14.9 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55334,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1358,false,true,55335,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,1774,1774,1774,Polychrome woodblock print; ink and color on paper,H. 12 3/4 in. (32.4 cm); W. 5 7/8 in. (14.9 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55335,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1359,false,true,55336,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,ca. 1775,1765,1785,Polychrome woodblock print; ink and color on paper,H. 12 1/2 in. (31.8 cm); W. 5 7/8 in. (14.9 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55336,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1360,false,true,55337,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,ca. 1775,1765,1785,Polychrome woodblock print; ink and color on paper,H. 12 1/2 in. (31.8 cm); W. 5 11/16 in. (14.4 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55337,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1366,false,true,55317,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,ca. 1776,1766,1786,Polychrome woodblock print; ink and color on paper,Uncut double hosoban 12 3/4 x 11 15/16 in. (32.4 x 30.3 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55317,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1445,false,true,55489,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,1770,1770,1770,Polychrome woodblock print; ink and color on paper,H. 5 7/16 in. (13.8 cm); W. 6 3/16 in. (15.7 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55489,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1450,false,true,55497,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,ca. 1781,1771,1791,Polychrome woodblock print; ink and color on paper,H. 12 3/4 in. (32.4 cm); W. 5 3/4 in. (14.6 cm),"Rogers Fund, 1925",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55497,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1451,false,true,55498,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,ca. 1781,1771,1791,Polychrome woodblock print; ink and color on paper,H. 12 13/16 in. (32.5 cm); W. 5 11/16 in. (14.4 cm),"Rogers Fund, 1925",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55498,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1458,false,true,39719,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,ca. 1785,1775,1795,Polychrome woodblock print; ink and color on paper,14 3/8 x 19 1/4 in. (36.5 x 48.9 cm),"Rogers Fund, 1925",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/39719,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1489,false,true,39720,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,ca. 1775,1765,1785,Polychrome woodblock print; ink and color on paper,17 7/8 x W. Top 12 13/16 in. (45.4 x 32.5 cm) 17 7/8 x W. Bot 12 7/8 in (45.4 x 32.7 cm),"Rogers Fund, 1927",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/39720,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1532,false,true,45036,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,ca. 1780,1770,1790,Polychrome woodblock print; ink and color on paper,12 13/16 x 5 3/4 in. (32.5 x 14.6 cm),"Fletcher Fund, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45036,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1533,false,true,55645,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,ca. 1780,1770,1790,Polychrome woodblock print; ink and color on paper,H. 12 in. (30.5 cm); W. 6 in. (15.2 cm),"Fletcher Fund, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55645,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1534,false,true,55646,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,1771–72,1771,1772,Polychrome woodblock print; ink and color on paper,H. 12 3/4 in. (32.4 cm); W. 5 15/16 in. (15.1 cm),"Fletcher Fund, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55646,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1535,false,true,55700,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,1771–72,1771,1772,Polychrome woodblock print; ink and color on paper,H. 12 13/16 in. (32.5 cm); W. 5 13/16 in. (14.8 cm),"Fletcher Fund, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55700,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1757,false,true,56063,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,1771,1771,1771,Polychrome woodblock print; ink and color on paper,H. 12 3/8 in. (31.4 cm); W. 5 5/8 in. (14.3 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56063,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2405,false,true,56808,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,1770,1770,1770,Polychrome woodblock print; ink and color on paper,11 1/4 x 5 1/4 in. (28.6 x 13.3 cm),"Gift of Louis V. Ledoux, 1931",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56808,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2406,false,true,56809,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,ca. 1780,1770,1790,Polychrome woodblock print; ink and color on paper,12 3/8 x 6 in. (31.4 x 15.2 cm),"Gift of Louis V. Ledoux, 1931",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56809,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2680,false,true,56864,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,1768 or 1769,1768,1769,Polychrome woodblock print; ink and color on paper,12 3/8 x 5 5/8 in. (31.4 x 14.3 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56864,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2682,false,true,56866,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,ca. 1786,1776,1796,Polychrome woodblock print; ink and color on paper,12 3/4 x 5 3/4 in. (32.4 x 14.6 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56866,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2683,false,true,56867,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,ca. 1778,1768,1788,Polychrome woodblock print; ink and color on paper,12 3/4 x 5 7/8 in. (32.4 x 14.9 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56867,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2684,false,true,56868,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,"2nd month, 1772",1772,1772,Polychrome woodblock print; ink and color on paper,12 1/8 x 5 5/8 in. (30.8 x 14.3 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56868,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2685,false,true,56869,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,1786 (?),1786,1786,Polychrome woodblock print; ink and color on paper,12 7/8 x 5 3/4 in. (32.7 x 14.6 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56869,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2686,false,true,56870,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,ca. 1772,1762,1782,Polychrome woodblock print; ink and color on paper,12 3/4 x 5 3/4 in. (32.4 x 14.6 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56870,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2687,false,true,56871,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,ca. 1772,1762,1782,Polychrome woodblock print; ink and color on paper,12 3/4 x 5 3/4 in. (32.4 x 14.6 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56871,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2688,false,true,56872,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,"12th month, 1780",1780,1780,Polychrome woodblock print; ink and color on paper,12 3/4 x 6 in. (32.4 x 15.2 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56872,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2689,false,true,56947,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,ca. 1780,1770,1790,Polychrome woodblock print; ink and color on paper,12 3/4 x 5 3/4 in. (32.4 x 14.6 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56947,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2690,false,true,56948,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,ca. 1780,1770,1790,Polychrome woodblock print; ink and color on paper,12 1/4 x 5 7/8 in. (31.1 x 14.9 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56948,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2691,false,true,56952,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,ca. 1780,1770,1790,Polychrome woodblock print; ink and color on paper,12 1/4 x 5 3/4 in. (31.1 x 14.6 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56952,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2692,false,true,56953,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,1782,1782,1782,Polychrome woodblock print; ink and color on paper,12 1/4 x 5 1/2 in. (31.1 x 14 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56953,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2693,false,true,56954,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,"5th month, 1781",1781,1781,Polychrome woodblock print; ink and color on paper,12 1/8 x 8 3/4 in. (30.8 x 22.2 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56954,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2695,false,true,56957,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,ca. 1772,1762,1782,Polychrome woodblock print; ink and color on paper,12 1/2 x 5 3/4 in. (31.8 x 14.6 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56957,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2696,false,true,56959,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,1774,1774,1774,Polychrome woodblock print; ink and color on paper,12 5/8 x 5 5/8 in. (32.1 x 14.3 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56959,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2697,false,true,56960,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,ca. 1780,1770,1790,Polychrome woodblock print; ink and color on paper,11 3/4 x 5 1/2 in. (29.8 x 14 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56960,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2698,false,true,56962,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,ca. 1780,1770,1790,Polychrome woodblock print; ink and color on paper,12 1/4 x 5 5/8 in. (31.1 x 14.3 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56962,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2699,false,true,56963,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,ca. 1778,1768,1788,Polychrome woodblock print; ink and color on paper,12 1/2 x 5 3/8 in. (31.8 x 13.7 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56963,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2700,false,true,56964,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,ca. 1775,1765,1785,Polychrome woodblock print; ink and color on paper,12 3/8 x 5 5/8 in. (31.4 x 14.3 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56964,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2701,false,true,56966,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,ca. 1780,1770,1790,Polychrome woodblock print; ink and color on paper,12 x 5 3/4 in. (30.5 x 14.6 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56966,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2702,false,true,56968,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,ca. 1779,1769,1789,Polychrome woodblock print; ink and color on paper,12 x 5 in. (30.5 x 12.7 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56968,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2703,false,true,56969,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,1772,1772,1772,Polychrome woodblock print; ink and color on paper,12 1/2 x 6 in. (31.8 x 15.2 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56969,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2704,false,true,44995,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,ca. 1785,1775,1795,Polychrome woodblock print; ink and color on paper,12 1/8 x 5 3/4 in. (30.8 x 14.6 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/44995,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2705,false,true,56970,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,ca. 1772 (?),1762,1782,Polychrome woodblock print; ink and color on paper,12 x 5 3/4 in. (30.5 x 14.6 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56970,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2706,false,true,56971,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",", New York, NY (1936; sold to MMA).",Katsukawa Shunshō,Japanese,1726,1792,ca. 1790,1780,1810,Polychrome woodblock print; ink and color on paper,13 x 6 in. (33 x 15.2 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56971,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2783,false,true,57037,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,1726–1792,1726,1792,Polychrome woodblock print; ink and color on paper,12 1/2 x 5 5/8 in. (31.8 x 14.3 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/57037,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2784,false,true,57105,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,1726–1792,1726,1792,Polychrome woodblock print; ink and color on paper,14 5/8 x 6 5/8 in. (37.1 x 16.8 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/57105,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2886,false,true,56001,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,1726–1792,1726,1792,Polychrome woodblock print; ink and color on paper,11 1/2 x 5 1/2 in. (29.2 x 14 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56001,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2887,false,true,56002,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,1726–1792,1726,1792,Polychrome woodblock print; ink and color on paper,11 3/4 x 5 1/2 in. (29.8 x 14 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56002,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2888,false,true,56003,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,1726–1792,1726,1792,Polychrome woodblock print; ink and color on paper,12 3/4 x 5 3/4 in. (32.4 x 14.6 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56003,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2890,false,true,56005,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,1726–1792,1726,1792,Polychrome woodblock print; ink and color on paper,12 1/2 x 5 3/4 in. (31.8 x 14.6 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56005,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2891,false,true,56006,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,1726–1792,1726,1792,Polychrome woodblock print; ink and color on paper,11 7/8 x 5 3/4 in. (30.2 x 14.6 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56006,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2892,false,true,56007,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,1726–1792,1726,1792,Polychrome woodblock print; ink and color on paper,12 1/8 x 5 7/8 in. (30.8 x 14.9 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56007,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2893,false,true,56008,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,1726–1792,1726,1792,Polychrome woodblock print; ink and color on paper,12 3/4 x 6 in. (32.4 x 15.2 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56008,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP703.5,false,true,37151,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,1771,1771,1771,Polychrome woodblock print; ink and color on paper,12 7/8 x 6 1/8 in. (32.7 x 15.6 cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37151,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"JP387a, b",false,true,36473,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,ca. 1778,1768,1788,Diptych of polychrome woodblock prints; ink and color on paper,H. 12 1/8 in. (30.8 cm); W. 11 3/8 in. (28.9 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36473,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"JP390a, b",false,true,36471,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,1776,1776,1776,Diptych of polychrome woodblock prints; ink and color on paper,H. 12 1/4 in. (31.1 cm); W. 11 1/2 in. (29.2 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36471,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"JP391a, b",false,true,36470,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,ca. 1780,1770,1790,Diptych of polychrome woodblock prints; ink and color on paper,A. H. 12 5/16 in. (31.3 cm); W. 5 1/2 in. (14 cm) B. H. 12 1/16 in. (30.6 cm); W. 5 11/16 in. (14.4 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36470,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"JP393a, b",false,true,36469,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,ca. 1791,1781,1801,Polychrome woodblock print; ink and color on paper,Hosoban diptych 12 1/4 x 11 1/4 in. (31.1 x 28.6 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36469,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"JP394a, b",false,true,36468,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,ca. 1775,1765,1785,Diptych of polychrome woodblock prints; ink and color on paper,H. 12 5/16 in. (31.3 cm); W. 5 1/2 in. (14 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36468,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"JP400a, b",false,true,36464,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,ca. 1778,1768,1788,Diptych of polychrome woodblock prints; ink and color on paper,A: H. 12 in. (30 cm); W. 5 5/8 in. (14.3 cm) B: H. 11 15/16 in. (30.3 cm); W. 5 5/8 in. (14.3 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36464,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"JP401a, b",false,true,36463,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,ca. 1773,1763,1783,Diptych of polychrome woodblock prints; ink and color on paper,Overall: H. 12 5/16 in. (31.3 cm); W. 11 in. (27.9 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36463,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"JP466a, b",false,true,36462,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,1780,1780,1780,Diptych of polychrome woodblock prints; ink and color on paper,A: 12 5/8 x 5 3/4 in. (32.1 x 14.6 cm) B: 12 5/8 x 5 3/4 in. (32.1 x 14.6 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36462,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"JP2681a, b",false,true,56865,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,ca. 1780,1770,1790,Diptych of polychrome woodblock prints; ink and color on paper,Image (left): 10 7/8 × 5 1/2 in. (27.6 × 14 cm) Image (right): 10 7/8 × 5 3/4 in. (27.6 × 14.6 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56865,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP389a–c,false,true,36472,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,1775,1778,1782,Triptych of polychrome woodblock prints; ink and color on paper,Overall: 12 5/8 x 5 1/2 in. (32.1 x 14 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36472,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP396a–e,false,true,36467,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,"2nd month, 1780",1780,1780,Pentaptych of polychrome woodblock prints; ink and color on paper,Overall: H. 12 5/16 (31.3 cm); W. 27 1/2 in. (69.9 cm),"Purchase, Joseph Pulitzer Bequest, 1918 (JP396a-d) Rogers Fund, 1922 (JP396e)",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36467,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP398a–d,false,true,36465,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,1780,1780,1780,Tetraptych of polychrome woodblock prints; ink and color on paper,Overall: H. 13 in. (33 cm); W. 22 3/4 in. (57.8 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36465,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP459a–d,false,true,36912,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,Designed by,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,1768,1768,1768,Polychrome woodblock prints; ink and color on paper,Four of a set of five hosoban 12 1/2 x 22 in. (31.8 x 55.9 cm) B.12 5/16 x 5 7/16 in.(32.4 x 14 cm) C.12 1/8 x 5 1/2 in. (30.8 x 14 cm) D.12 5/6 x 5 7/16 in. (32.4 x 14 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36912,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1342a–c,false,true,55307,Asian Art,Print,"三代目市川八百藏・初代目尾上松助・三代目澤村宗十郎|Kabuki Actors Ichikawa Yaozō III, Onoe Matsusuke I, and Sawamura Sōjūrō III",Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,"11th month, 1786",1786,1786,Triptych of polychrome woodblock prints; ink and color on paper,a: 12 5/8 in. (32.1 cm); W. 5 3/4 in. (14.6 cm) b: 12 5/8 in. (32.1 cm); W. 5 13/16 in. (14.8 cm) c: 12 11/16 in. (32.2 cm); W. 5 13/16 in. (14.8 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55307,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2889a–c,false,true,56004,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,third month of 1780,1780,1780,Triptych of polychrome woodblock prints; ink and color on paper,A: 12 7/8 x 5 5/8 in. (32.7 x 14.3 cm) B: 12 3/4 x 5 3/4 in. (32.4 x 14.6 cm) C: 12 3/4 x 5 7/8 in. (32.4 x 14.9 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56004,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP199,false,true,36676,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyomitsu,"Japanese, 1735–1785",,Torii Kiyomitsu,Japanese,1735,1785,probably 1767,1765,1769,Polychrome woodblock print; ink and color on paper,12 7/32 x 5 7/8 in. (31.1 x 14.9 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36676,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP468,false,true,36919,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyomitsu,"Japanese, 1735–1785",,Torii Kiyomitsu,Japanese,1735,1785,ca. 1760?,1750,1760,Polychrome woodblock print; ink and color on paper,12 1/4 x 5 7/16 in. (31.1 x 13.8 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36919,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP469,false,true,36920,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyomitsu,"Japanese, 1735–1785",,Torii Kiyomitsu,Japanese,1735,1785,"2nd month, 1771",1771,1771,Polychrome woodblock print; ink and color on paper,12 7/32 x 5 1/8 in. (31.1 x 13.0 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36920,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP667,false,true,37114,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyomitsu,"Japanese, 1735–1785",,Torii Kiyomitsu,Japanese,1735,1785,ca. 1764,1754,1774,Polychrome woodblock print; ink and color on paper,27 11/32 x 4 in. (69.5 x 10.2 cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37114,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP692,false,true,37139,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyomitsu,"Japanese, 1735–1785",,Torii Kiyomitsu,Japanese,1735,1785,ca. 1755,1745,1765,Polychrome woodblock print; ink and color on paper,12 7/32 x 5 5/8 in. (31.1 x 14.3 cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37139,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP693,false,true,37140,Asian Art,Print,「岩井半四郎図」|The Actor Iwai Hanshiro as a Courtesan Reading a Love Letter while Mounted on a Black Ox,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyomitsu,"Japanese, 1735–1785",,Torii Kiyomitsu,Japanese,1735,1785,ca. 1763,1753,1773,Polychrome woodblock print; ink and color on paper,11 1/2 x 5 1/2 in. (29.2 x 14.0 cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37140,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP694,false,true,37141,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyomitsu,"Japanese, 1735–1785",,Torii Kiyomitsu,Japanese,1735,1785,ca. 1763,1753,1773,Polychrome woodblock print; ink and color on paper,12 7/32 x 17 in. (31.1 x 43.2 cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37141,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP695,false,true,37142,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyomitsu,"Japanese, 1735–1785",,Torii Kiyomitsu,Japanese,1735,1785,ca. 1763,1753,1773,Polychrome woodblock print; ink and color on paper,12 1/8 x 16 1/2 in. (30.8 x 41.9 cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37142,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP835,false,true,54490,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyomitsu,"Japanese, 1735–1785",,Torii Kiyomitsu,Japanese,1735,1785,ca. 1757,1747,1767,Polychrome woodblock print; ink and color on paper,H. 12 in. (30.5 cm); W. 5 1/2 in. (14 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54490,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP836,false,true,54491,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyomitsu,"Japanese, 1735–1785",,Torii Kiyomitsu,Japanese,1735,1785,ca. 1761,1751,1771,Polychrome woodblock print; ink and color on paper,H. 11 7/8 in. (30.2 cm); W. 5 5/8 in. (14.3 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54491,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP837,false,true,54492,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyomitsu,"Japanese, 1735–1785",,Torii Kiyomitsu,Japanese,1735,1785,ca. 1761,1751,1771,Polychrome woodblock print; ink and color on paper,H. 12 1/4 in. (31.1 cm); 5 7/8 in. (14.9 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54492,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP838,false,true,54493,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyomitsu,"Japanese, 1735–1785",,Torii Kiyomitsu,Japanese,1735,1785,ca. 1762,1752,1772,Polychrome woodblock print; ink and color on paper,H. 23 1/2 in. (59.7 cm); W. 4 1/8 in. (10.5 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54493,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP839,false,true,54494,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyomitsu,"Japanese, 1735–1785",,Torii Kiyomitsu,Japanese,1735,1785,probably 1765,1760,1770,Polychrome woodblock print; ink and color on paper,H. 11 7/8 in. (30.2 cm); W. 5 1/2 in. (14 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54494,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1378,false,true,55362,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyomitsu,"Japanese, 1735–1785",,Torii Kiyomitsu,Japanese,1735,1785,ca. 1760,1750,1770,Polychrome woodblock print; ink and color on paper,H. 11 15/16 in. (30.3 cm); W. 5 1/2 in. (14 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55362,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1385,false,true,55383,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyomitsu,"Japanese, 1735–1785",,Torii Kiyomitsu,Japanese,1735,1785,ca. 1760,1750,1770,Polychrome woodblock print; ink and color on paper,H. 12 7/16 in. (31.6 cm); W. 5 3/4 in. (14.6 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55383,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1500,false,true,55602,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyomitsu,"Japanese, 1735–1785",,Torii Kiyomitsu,Japanese,1735,1785,ca. 1767,1757,1777,Polychrome woodblock print; ink and color on paper,H. 11 7/8 in. (30.2 cm); W. 5 3/8 in. (13.7 cm),"Rogers Fund, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55602,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1501,false,true,55606,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyomitsu,"Japanese, 1735–1785",,Torii Kiyomitsu,Japanese,1735,1785,ca. 1767,1767,1777,Polychrome woodblock print; ink and color on paper,H. 12 1/4 in. (31.1 cm); W. 5 1/2 in. (14 cm),"Fletcher Fund, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55606,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1502,false,true,55607,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyomitsu,"Japanese, 1735–1785",,Torii Kiyomitsu,Japanese,1735,1785,ca. 1768,1758,1778,One sheet of a triptych of polychrome woodblock prints; ink and color on paper,H. 12 3/8 in. (31.4 cm); W. 5 3/8 in. (13.7 cm),"Fletcher Fund, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55607,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1507,false,true,51090,Asian Art,Print,Amagoi Komachi|Komachi Praying for Rain,Japan,Edo period (1615–1868),,,,Artist,Attributed to,Torii Kiyomitsu,"Japanese, 1735–1785",,Torii Kiyomitsu,Japanese,1735,1785,ca. 1765,1755,1775,Polychrome woodblock print; ink and color on paper,H. 11 5/16 in. (28.7 cm); W. 8 1/2 in. (21.6 cm),"Fletcher Fund, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/51090,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1568,false,true,55745,Asian Art,Print,「二代目瀬川菊之丞図」|The Kabuki Actor Segawa Kikunojō II as a Woman Reading a Letter,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyomitsu,"Japanese, 1735–1785",,Torii Kiyomitsu,Japanese,1735,1785,ca. 1760s,1750,1770,Polychrome woodblock print; ink and color on paper,H. 28 1/8 in. (71.4 cm); W. 4 3/8 in. (11.1 cm),"Fletcher Fund, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55745,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2588,false,true,56734,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyomitsu,"Japanese, 1735–1785",,Torii Kiyomitsu,Japanese,1735,1785,1757 or 1758,1757,1758,Polychrome woodblock print; ink and color on paper,12 5/8 x 5 3/4 in. (32.1 x 14.6 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56734,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2590,false,true,56736,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyomitsu,"Japanese, 1735–1785",,Torii Kiyomitsu,Japanese,1735,1785,ca. 1759,1749,1769,Polychrome woodblock print; ink and color on paper,14 3/8 x 6 5/8 in. (36.5 x 16.8 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56736,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2591,false,true,56737,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyomitsu,"Japanese, 1735–1785",,Torii Kiyomitsu,Japanese,1735,1785,ca. 1764,1754,1774,Polychrome woodblock print; ink and color on paper,21 3/8 x 4 1/8 in. (54.3 x 10.5 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56737,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2592,false,true,56738,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyomitsu,"Japanese, 1735–1785",,Torii Kiyomitsu,Japanese,1735,1785,ca. 1763,1753,1773,Polychrome woodblock print; ink and color on paper,27 7/8 x 4 in. (70.8 x 10.2 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56738,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2763,false,true,57027,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyomitsu,"Japanese, 1735–1785",,Torii Kiyomitsu,Japanese,1735,1785,1735–1785,1735,1785,Polychrome woodblock print; ink and color on paper,12 1/8 x 5 1/2 in. (30.8 x 14 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/57027,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2764,false,true,57028,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyomitsu,"Japanese, 1735–1785",,Torii Kiyomitsu,Japanese,1735,1785,1735–1785,1735,1785,Polychrome woodblock print; ink and color on paper,12 1/8 x 5 3/8 in. (30.8 x 13.7 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/57028,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2765,false,true,57029,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyomitsu,"Japanese, 1735–1785",,Torii Kiyomitsu,Japanese,1735,1785,1761,1761,1761,Polychrome woodblock print; ink and color on paper,11 7/8 x 5 1/2 in. (30.2 x 14 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/57029,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3042,false,true,56498,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyomitsu,"Japanese, 1735–1785",,Torii Kiyomitsu,Japanese,1735,1785,ca. 1756,1746,1766,Polychrome woodblock print; ink and color on paper,12 x 5 3/4 in. (30.5 x 14.6 cm),"Gift of Mrs. Morris Manges, in memory of her husband, Dr. Morris Manges, 1947",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56498,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3093,false,true,56606,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyomitsu,"Japanese, 1735–1785",,Torii Kiyomitsu,Japanese,1735,1785,1768,1768,1768,Polychrome woodblock print; ink and color on paper,12 1/4 x 5 1/2 in. (31.1 x 14 cm),"Harris Brisbane Dick Fund and Rogers Fund, 1949",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56606,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3094,false,true,56607,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyomitsu,"Japanese, 1735–1785",,Torii Kiyomitsu,Japanese,1735,1785,1756,1756,1756,Polychrome woodblock print; ink and color on paper,12 1/4 x 5 5/8 in. (31.1 x 14.3 cm),"Harris Brisbane Dick Fund and Rogers Fund, 1949",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56607,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3095,false,true,56608,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyomitsu,"Japanese, 1735–1785",,Torii Kiyomitsu,Japanese,1735,1785,1766,1766,1766,Polychrome woodblock print; ink and color on paper,15 x 7 in. (38.1 x 17.8 cm),"Harris Brisbane Dick Fund and Rogers Fund, 1949",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56608,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP192,false,true,36669,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Toyoharu,"Japanese, 1735–1814",,Utagawa Toyoharu,Japanese,1735,1814,ca. 1780,1770,1790,Polychrome woodblock print; ink and color on paper,10 x 14 3/4 in. (25.4 x 37.5 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36669,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP224,false,true,36698,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Toyoharu,"Japanese, 1735–1814",,Utagawa Toyoharu,Japanese,1735,1814,ca. 1780,1770,1790,Polychrome woodblock print; ink and color on paper,9 7/16 x 14 3/4 in. (24.0 x 37.5 cm),"Rogers Fund, 1917",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36698,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP675,false,true,37122,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Toyoharu,"Japanese, 1735–1814",,Utagawa Toyoharu,Japanese,1735,1814,ca. 1771,1761,1781,Polychrome woodblock print (pillar print); ink and color on paper,26 1/4 x 4 1/4 in. (66.7 x 10.8 cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37122,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP740,false,true,37188,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Toyoharu,"Japanese, 1735–1814",,Utagawa Toyoharu,Japanese,1735,1814,ca. 1770,1760,1780,Polychrome woodblock print; ink and color on paper,15 1/8 x 5 1/8 in. (38.4 x 13.0 cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37188,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1417,false,true,55445,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Toyoharu,"Japanese, 1735–1814",,Utagawa Toyoharu,Japanese,1735,1814,ca. 1805,1795,1815,Polychrome woodblock print; ink and color on paper,H. 9 3/8 in. (23.8 cm); W. 14 1/6 in. (36 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55445,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1420,false,true,55448,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Toyoharu,"Japanese, 1735–1814",,Utagawa Toyoharu,Japanese,1735,1814,ca. 1780,1770,1790,Polychrome woodblock print; ink and color on paper,H. 9 13/16 in. (24.9 cm); W. 14 3/4 in. (37.5 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55448,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1566,false,true,55744,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Toyoharu,"Japanese, 1735–1814",,Utagawa Toyoharu,Japanese,1735,1814,ca. 1790,1780,1800,Triptych of polychrome woodblock prints (surimono enriched with gold); ink and color on paper,Aiban; H. 13 7/8 in. (35.2 cm); W. 9 7/8 in. (25.1 cm),"Fletcher Fund, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55744,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1605,false,true,55769,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Toyoharu,"Japanese, 1735–1814",,Utagawa Toyoharu,Japanese,1735,1814,ca. 1780,1770,1790,Polychrome woodblock print; ink and color on paper,H. 8 3/4 in. (22.2 cm); W. 14 3/4 in. (37.5 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55769,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2855,false,true,45259,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Toyoharu,"Japanese, 1735–1814",,Utagawa Toyoharu,Japanese,1735,1814,ca. 1780,1770,1790,Polychrome woodblock print; ink and color on paper,10 1/2 x 15 1/8 in. (26.7 x 38.4 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45259,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP194,false,true,36671,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kitao Shigemasa,"Japanese, 1739–1820",,Kitao Shigemasa,Japanese,1739,1820,ca. 1782,1772,1792,Polychrome woodblock print; ink and color on paper,14 11/16 x 10 in. (37.3 x 25.4 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36671,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP195,false,true,36672,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kitao Shigemasa,"Japanese, 1739–1820",,Kitao Shigemasa,Japanese,1739,1820,ca. 1782,1772,1792,Polychrome woodblock print; ink and color on paper,14 3/5 x 9 7/8 in. (37.1 x 25.1 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36672,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP662,false,true,37109,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kitao Shigemasa,"Japanese, 1739–1820",,Kitao Shigemasa,Japanese,1739,1820,ca. 1800,1790,1810,Polychrome woodblock print (surimono); ink and color on paper,15 x 20 in. (38.1 x 50.8 cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37109,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP696,false,true,37143,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Kitao Shigemasa,"Japanese, 1739–1820",,Kitao Shigemasa,Japanese,1739,1820,1767,1757,1757,Polychrome woodblock print; ink and color on paper,11 1/8 x 5 1/2 in. (28.3 x 14.0 cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37143,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP932,false,true,54802,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kitao Shigemasa,"Japanese, 1739–1820",,Kitao Shigemasa,Japanese,1739,1820,ca. 1759,1749,1769,Polychrome woodblock print; ink and color on paper,H. 11 5/8 in. (29.5 cm); W. 5 1/8 in. (13 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54802,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP933,false,true,54803,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kitao Shigemasa,"Japanese, 1739–1820",,Kitao Shigemasa,Japanese,1739,1820,ca. 1772,1762,1782,Polychrome woodblock print; ink and color on paper,H. 9 7/16 in. (24 cm); W. 13 7/8 in. (35.2 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54803,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP934,false,true,45237,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kitao Shigemasa,"Japanese, 1739–1820",,Kitao Shigemasa,Japanese,1739,1820,ca. 1780,1770,1790,Polychrome woodblock print; ink and color on paper,H. 14 9/16 in. (37 cm); W. 9 13 /16 in. (24.9 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45237,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP936,false,true,54806,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kitao Shigemasa,"Japanese, 1739–1820",,Kitao Shigemasa,Japanese,1739,1820,ca. 1790,1780,1800,Polychrome woodblock print; ink and color on paper,H. 8 2/4 in. (21.6 cm); W. 12 3/8 in. (31.4 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54806,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1269,false,true,55166,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kitao Shigemasa,"Japanese, 1739–1820",,Kitao Shigemasa,Japanese,1739,1820,ca. 1764,1754,1774,Polychrome woodblock print; ink and color on paper,H. 11 1/8 in. (28.3 cm); W. 5 1/8 in. (13 cm),"Rogers Fund, 1921",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55166,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2123,false,true,54977,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kitao Shigemasa,"Japanese, 1739–1820",,Kitao Shigemasa,Japanese,1739,1820,1813,1813,1813,Polychrome woodblock print (surimono); ink and color on paper,8 5/16 x 3 5/16 in. (21.1 x 8.4 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54977,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2126,false,true,54981,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kitao Shigemasa,"Japanese, 1739–1820",,Kitao Shigemasa,Japanese,1739,1820,1813,1813,1813,Polychrome woodblock print (surimono); ink and color on paper,8 3/16 x 3 7/16 in. (20.8 x 8.7 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54981,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2338,false,true,54123,Asian Art,Print,判子と赤肉箱|Seals and a Carved Lacquer Container for Seal Ink,Japan,Edo period (1615–1868),,,,Artist,,Kitao Shigemasa,"Japanese, 1739–1820",,Kitao Shigemasa,Japanese,1739,1820,1817,1817,1817,Part of an album of woodblock prints (surimono); ink and color on paper,5 9/16 x 7 5/16 in. (14.1 x 18.6 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54123,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2651,false,true,56829,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kitao Shigemasa,"Japanese, 1739–1820",,Kitao Shigemasa,Japanese,1739,1820,ca. 1766,1756,1776,Polychrome woodblock print; ink and color on paper,12 1/4 x 5 1/2 in. (31.1 x 14 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56829,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2652,false,true,56831,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kitao Shigemasa,"Japanese, 1739–1820",,Kitao Shigemasa,Japanese,1739,1820,1769 (early),1769,1769,Polychrome woodblock print; ink and color on paper,12 1/8 x 5 1/4 in. (30.8 x 13.3 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56831,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2653,false,true,45248,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kitao Shigemasa,"Japanese, 1739–1820",,Kitao Shigemasa,Japanese,1739,1820,ca. 1770,1760,1780,Polychrome woodblock print; ink and color on paper,10 5/8 x 7 3/4 in. (27 x 19.7 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45248,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2654,false,true,56833,Asian Art,Woodblock print,Daibutsu no bansho|Vesper Bell of the Temple of Great Buddha,Japan,Edo period (1615–1868),,,,Artist,,Kitao Shigemasa,"Japanese, 1739–1820",,Kitao Shigemasa,Japanese,1739,1820,ca. 1778,1768,1788,Polychrome woodblock print; ink and color on paper,15 x 9 7/8 in. (38.1 x 25.1 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56833,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2655,false,true,56834,Asian Art,Woodblock print,Tesage andon|The Hand Lantern,Japan,Edo period (1615–1868),,,,Artist,,Kitao Shigemasa,"Japanese, 1739–1820",,Kitao Shigemasa,Japanese,1739,1820,ca. 1790,1780,1810,Polychrome woodblock print; ink and color on paper,14 5/8 x 9 3/4 in. (37.1 x 24.8 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56834,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2656,false,true,56835,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kitao Shigemasa,"Japanese, 1739–1820",,Kitao Shigemasa,Japanese,1739,1820,ca. 1790,1780,1810,Polychrome woodblock print; ink and color on paper,15 x 10 1/8 in. (38.1 x 25.7 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56835,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2794,false,true,57095,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kitao Shigemasa,"Japanese, 1739–1820",,Kitao Shigemasa,Japanese,1739,1820,1739–1820,1739,1820,Polychrome woodblock print; ink and color on paper,14 3/4 x 10 1/8 in. (37.5 x 25.7 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/57095,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP159,false,true,36638,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunkō,"Japanese, 1743–1812",,Katsukawa Shunkō,Japanese,1743,1812,ca. 1789,1779,1799,Polychrome woodblock print; ink and color on paper,12 3/4 x 5 3/4 in. (32.4 x 14.6 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36638,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP160,false,true,36639,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunkō,"Japanese, 1743–1812",,Katsukawa Shunkō,Japanese,1743,1812,probably 1777,1767,1787,Polychrome woodblock print; ink and color on paper,12 31/32 x 5 15/16 in. (33.0 x 15.1 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36639,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP161,false,true,36640,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunkō,"Japanese, 1743–1812",,Katsukawa Shunkō,Japanese,1743,1812,ca. 1789,1779,1799,Polychrome woodblock print; ink and color on paper,12 1/2 x 5 2/3 in. (31.8 x 14.4 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36640,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP162,false,true,36641,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunkō,"Japanese, 1743–1812",,Katsukawa Shunkō,Japanese,1743,1812,ca. 1784–88,1784,1788,Polychrome woodblock print; ink and color on paper,12 7/32 x 5 3/8 in. (31.1 x 13.7 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36641,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP163,false,true,36642,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunkō,"Japanese, 1743–1812",,Katsukawa Shunkō,Japanese,1743,1812,ca. 1780,1770,1790,Polychrome woodblock print; ink and color on paper,11 3/5 x 5 3/8 in. (29.5 x 13.7 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36642,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP164,false,true,36643,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunkō,"Japanese, 1743–1812",,Katsukawa Shunkō,Japanese,1743,1812,ca. 1780,1770,1790,Polychrome woodblock print; ink and color on paper,12 x 5 3/8 in. (30.5 x 13.7 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36643,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP165,false,true,36644,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunkō,"Japanese, 1743–1812",,Katsukawa Shunkō,Japanese,1743,1812,June 1786,1786,1786,Polychrome woodblock print; ink and color on paper,11 15/32 x 5 in. (29.1 x 12.7 cm) (trimmed),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36644,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP166,false,true,36645,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunkō,"Japanese, 1743–1812",,Katsukawa Shunkō,Japanese,1743,1812,April 1783,1783,1783,Polychrome woodblock print; ink and color on paper,12 4/5 x 5 15/16 in. (32.5 x 15.1 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36645,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP167,false,true,36646,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunkō,"Japanese, 1743–1812",,Katsukawa Shunkō,Japanese,1743,1812,dated 1788,1788,1788,Polychrome woodblock print; ink and color on paper,12 x 5 3/4 in. (30.5 x 14.6 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36646,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP168,false,true,36647,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunkō,"Japanese, 1743–1812",,Katsukawa Shunkō,Japanese,1743,1812,"ca. 12th month, 1779",1769,1789,Polychrome woodblock print; ink and color on paper,11 1/2 x 5 7/32 in. (29.2 x 13.3 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36647,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP169,false,true,36648,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunkō,"Japanese, 1743–1812",,Katsukawa Shunkō,Japanese,1743,1812,dated 1787,1787,1787,Middle sheet of a triptych of polychrome woodblock prints; ink and color on paper,12 3/4 x 5 3/4 in. (32.4 x 14.6 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36648,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP170,false,true,36649,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunkō,"Japanese, 1743–1812",,Katsukawa Shunkō,Japanese,1743,1812,ca. 1790,1780,1800,Polychrome woodblock print; ink and color on paper,11 1/2 x 5 1/2 in. (29.2 x 14 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36649,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP171,false,true,36650,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunkō,"Japanese, 1743–1812",,Katsukawa Shunkō,Japanese,1743,1812,December 1785,1785,1785,Polychrome woodblock print; ink and color on paper,14 9/16 x 9 11/16 in. (37.0 x 24.6 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36650,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP274,false,true,36746,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunkō,"Japanese, 1743–1812",,Katsukawa Shunkō,Japanese,1743,1812,1784,1784,1784,Polychrome woodblock print; ink and color on paper,11 7/8 x 5 3/4 in. (30.2 x 14.6 cm) (trimmed),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36746,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP275,false,true,36747,Asian Art,Print,六代目中山小十郎|Kabuki Actor Nakayama Kojūrō VI,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunkō,"Japanese, 1743–1812",,Katsukawa Shunkō,Japanese,1743,1812,1786,1786,1786,Polychrome woodblock print; ink and color on paper,12 5/8 x 5 3/4 in. (32.1 x 14.6 cm),"Purchase, Joseph Pulitzer Beqeust, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36747,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP276,false,true,36748,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunkō,"Japanese, 1743–1812",,Katsukawa Shunkō,Japanese,1743,1812,ca. 1784–88,1782,1790,Polychrome woodblock print; ink and color on paper,12 3/4 x 5 3/4 in. (32.4 x 14.6 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36748,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP277,false,true,36749,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunkō,"Japanese, 1743–1812",,Katsukawa Shunkō,Japanese,1743,1812,"2nd month, 1788",1788,1788,Polychrome woodblock print; ink and color on paper,12 1/32 x 5 3/8 in. (30.6 x 13.7 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36749,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP278,false,true,36750,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunkō,"Japanese, 1743–1812",,Katsukawa Shunkō,Japanese,1743,1812,ca. 1790,1780,1800,Polychrome woodblock print; ink and color on paper,12 7/32 x 5 2/3 in. (31.1 x 14.4 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36750,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP279,false,true,36751,Asian Art,Woodblock print,五代目市川団十郎|Scene from the Play Yoshitsune and the Thousand Cherry Trees (Yoshitsune senbon zakura),Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunkō,"Japanese, 1743–1812",,Katsukawa Shunkō,Japanese,1743,1812,"8th or 9th month, 1784",1784,1784,Polychrome woodblock print; ink and color on paper,12 7/16 x 5 3/4 in. (31.6 x 14.6 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36751,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP280,false,true,36752,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunkō,"Japanese, 1743–1812",,Katsukawa Shunkō,Japanese,1743,1812,ca. 1788?,1778,1798,Polychrome woodblock print; ink and color on paper,11 13/16 x 5 5/8 in. (30.0 x 14.3 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36752,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP281,false,true,36753,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunkō,"Japanese, 1743–1812",,Katsukawa Shunkō,Japanese,1743,1812,ca. 1790,1780,1800,Polychrome woodblock print; ink and color on paper,12 3/4 x 5 1/2 in. (32.4 x 14.0 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36753,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP282,false,true,36754,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunkō,"Japanese, 1743–1812",,Katsukawa Shunkō,Japanese,1743,1812,1787,1787,1787,Polychrome woodblock print; ink and color on paper,12 1/2 x 5 3/4 in. (31.8 x 14.6 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36754,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP283,false,true,36755,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunkō,"Japanese, 1743–1812",,Katsukawa Shunkō,Japanese,1743,1812,ca. 1779?,1769,1789,Polychrome woodblock print; ink and color on paper,12 7/8 x 5 7/8 in. (32.7 x 14.9 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36755,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP284,false,true,36756,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunkō,"Japanese, 1743–1812",,Katsukawa Shunkō,Japanese,1743,1812,Spring of 1785,1785,1785,Polychrome woodblock print; ink and color on paper,12 11/32 x 5 7/8 in. (31.4 x 14.9 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36756,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP285,false,true,36757,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunkō,"Japanese, 1743–1812",,Katsukawa Shunkō,Japanese,1743,1812,ca. 1790,1780,1800,Polychrome woodblock print; ink and color on paper,11 3/5 x 5 5/8 in. (29.5 x 14.3 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36757,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP286,false,true,36758,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunkō,"Japanese, 1743–1812",,Katsukawa Shunkō,Japanese,1743,1812,ca. 1780,1780,1790,Polychrome woodblock print; ink and color on paper,Hosoban 12 3/8 x 5 15/16 in. (31.4 x 15.1 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36758,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP287,false,true,36759,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunkō,"Japanese, 1743–1812",,Katsukawa Shunkō,Japanese,1743,1812,ca. 1785–90,1775,1800,Polychrome woodblock print; ink and color on paper,Overall: 12 1/4 x 5 9/16in. (31.1 x 14.1cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36759,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP288,false,true,36760,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunkō,"Japanese, 1743–1812",,Katsukawa Shunkō,Japanese,1743,1812,ca. 1774,1764,1784,Polychrome woodblock print; ink and color on paper,12 x 5 3/4 in. (30.5 x 14.6 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36760,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP289,false,true,36761,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunkō,"Japanese, 1743–1812",,Katsukawa Shunkō,Japanese,1743,1812,ca. 1790?,1780,1800,Polychrome woodblock print; ink and color on paper,12 7/32 x 5 5/8 in. (31.1 x 14.3 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36761,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP290,false,true,36762,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunkō,"Japanese, 1743–1812",,Katsukawa Shunkō,Japanese,1743,1812,late 1777 or early in 1778,1777,1778,Polychrome woodblock print; ink and color on paper,11 23/32 x 5 3/4 in. (29.8 x 14.6 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36762,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP295,false,true,36763,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunkō,"Japanese, 1743–1812",,Katsukawa Shunkō,Japanese,1743,1812,ca. 1778,1768,1788,Polychrome woodblock print; ink and color on paper,11 23/32 x 5 1/2 in. (29.8 x 14.0 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36763,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP296,false,true,36764,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunkō,"Japanese, 1743–1812",,Katsukawa Shunkō,Japanese,1743,1812,"4th month, 1783",1783,1783,Polychrome woodblock print; ink and color on paper,12 11/16 x 5 3/4 in. (32.2 x 14.6 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36764,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP297,false,true,36765,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunkō,"Japanese, 1743–1812",,Katsukawa Shunkō,Japanese,1743,1812,ca. 1780–85,1770,1795,Polychrome woodblock print; ink and color on paper,12 7/8 x 5 3/4 in. (32.7 x 14.6 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36765,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP298,false,true,36766,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunkō,"Japanese, 1743–1812",,Katsukawa Shunkō,Japanese,1743,1812,ca. 1785?,1775,1795,Polychrome woodblock print; ink and color on paper,12 3/4 x 5 3/4 in. (32.4 x 14.6 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36766,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP299,false,true,36767,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunkō,"Japanese, 1743–1812",,Katsukawa Shunkō,Japanese,1743,1812,ca. 1788,1778,1798,Polychrome woodblock print; ink and color on paper,12 5/8 x 5 5/8 in. (32.1 x 14.3 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36767,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP341,false,true,36806,Asian Art,Print,二代目嵐龍藏・三代目瀬川菊之丞|Kabuki Actors Arashi Ryūzō II and Segawa Kikunojō III,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunkō,"Japanese, 1743–1812",,Katsukawa Shunkō,Japanese,1743,1812,ca. 1790,1788,1792,Polychrome woodblock print; ink and color on paper,11 3/8 x 5 5/16 in. (28.9 x 13.5 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36806,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP712,false,true,37160,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunkō,"Japanese, 1743–1812",,Katsukawa Shunkō,Japanese,1743,1812,ca. 1791,1781,1801,Polychrome woodblock print; ink and color on paper,12 7/32 x 5 3/4 in. (31.1 x 14.6 cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37160,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP713,false,true,37161,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunkō,"Japanese, 1743–1812",,Katsukawa Shunkō,Japanese,1743,1812,ca. 1778,1768,1788,Polychrome woodblock print; ink and color on paper,12 7/32 x 5 5/8 in. (31.1 x 14.3 cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37161,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP714,false,true,37162,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunkō,"Japanese, 1743–1812",,Katsukawa Shunkō,Japanese,1743,1812,ca. 1786,1776,1796,Polychrome woodblock print; ink and color on paper,14 1/3 x 10 in. (36.4 x 25.4 cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37162,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP908,false,true,54648,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunkō,"Japanese, 1743–1812",,Katsukawa Shunkō,Japanese,1743,1812,1776,1776,1776,Polychrome woodblock print; ink and color on paper,H. 12 1/2 in. (31.8 cm); W. 5 3/4 in. (14.6 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54648,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP909,false,true,54649,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunkō,"Japanese, 1743–1812",,Katsukawa Shunkō,Japanese,1743,1812,ca. 1772,1762,1782,Polychrome woodblock print; ink and color on paper,H. 12 7/8 in. (32.7 cm); W. 6 in. (15.2 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54649,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP910,false,true,54650,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunkō,"Japanese, 1743–1812",,Katsukawa Shunkō,Japanese,1743,1812,ca. 1773,1763,1783,Polychrome woodblock print; ink and color on paper,H. 12 1/2 in. (31.8 cm); W. 5 7/8 in. (14.9 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54650,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP911,false,true,54651,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunkō,"Japanese, 1743–1812",,Katsukawa Shunkō,Japanese,1743,1812,ca. 1777,1767,1787,Polychrome woodblock print; ink and color on paper,H. 12 7/8 in. (32.7 cm); W. 5 7/8 in. (14.9 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54651,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1304,false,true,55243,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunkō,"Japanese, 1743–1812",,Katsukawa Shunkō,Japanese,1743,1812,ca. 1779,1769,1789,Polychrome woodblock print; ink and color on paper,H. 11 15/16 (30.3 cm); W. 5 3/8 in. (13.7 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55243,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1343,false,true,55308,Asian Art,Print,二代目市川門之助|Kabuki Actor Ichikawa Monnosuke II as Shinozuka in a Shibaraku (Stop Right There!) Scene,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunkō,"Japanese, 1743–1812",,Katsukawa Shunkō,Japanese,1743,1812,"11th month, 1790",1790,1790,Polychrome woodblock print; ink and color on paper,Hosoban 12 7/8 x 5 3/4 in. (32.7 x 14.6 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55308,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1344,false,true,55309,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunkō,"Japanese, 1743–1812",,Katsukawa Shunkō,Japanese,1743,1812,1786,1786,1786,Polychrome woodblock print; ink and color on paper,H. 12 5/8 in. (32.1 cm); W. 5 3/4 in. (14.6 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55309,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1345,false,true,55311,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunkō,"Japanese, 1743–1812",,Katsukawa Shunkō,Japanese,1743,1812,1795,1795,1795,Polychrome woodblock print; ink and color on paper,H. 12 13/16 in. (32.5 cm); W. 5 5/8 in. (14.3 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55311,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1346,false,true,55312,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunkō,"Japanese, 1743–1812",,Katsukawa Shunkō,Japanese,1743,1812,ca. 1790,1780,1800,Polychrome woodblock print; ink and color on paper,H. 12 9/16 in. (31.9 cm); W. 5 13/16 in. (14.8 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55312,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1352,false,true,55328,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunkō,"Japanese, 1743–1812",,Katsukawa Shunkō,Japanese,1743,1812,ca. 1790,1780,1800,Polychrome woodblock print; ink and color on paper,H. 13 in. (33 cm); W. 5 15/16 in. (15.1 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55328,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1364,false,true,55344,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunkō,"Japanese, 1743–1812",,Katsukawa Shunkō,Japanese,1743,1812,ca. 1790,1780,1800,Polychrome woodblock print; ink and color on paper,H. 12 9/16 in. (31.9 cm); W. 5 7/8 in. (14.9 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55344,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1479,false,true,45221,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunkō,"Japanese, 1743–1812",,Katsukawa Shunkō,Japanese,1743,1812,ca. 1789,1779,1899,Polychrome woodblock print; ink and color on paper,H. 12 11/16 in. (32.2 cm); W. 8 3/4 in. (22.2 cm),"Fletcher Fund, 1925",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45221,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1480,false,true,55566,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunkō,"Japanese, 1743–1812",,Katsukawa Shunkō,Japanese,1743,1812,ca. 1789,1779,1799,Polychrome woodblock print; ink and color on paper,H. 12 7/8 in. (32.7 cm); W. 8 5/8 in. (21.9 cm),"Fletcher Fund, 1925",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55566,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1494,false,true,55589,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunkō,"Japanese, 1743–1812",,Katsukawa Shunkō,Japanese,1743,1812,1795,1795,1795,Polychrome woodblock print; ink and color on paper,H. 12 7/8 in. (32.7 cm); W. 5 5/8 in. (14.3 cm),"Fletcher Fund, 1928",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55589,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1542,false,true,55707,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunkō,"Japanese, 1743–1812",,Katsukawa Shunkō,Japanese,1743,1812,ca. 1780,1770,1790,Polychrome woodblock print; ink and color on paper,H. 12 7/8 in. (32.7 cm); W. 5 3/4 in. (14.6 cm),"Fletcher Fund, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55707,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1543,false,true,55708,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunkō,"Japanese, 1743–1812",,Katsukawa Shunkō,Japanese,1743,1812,ca. 1780,1770,1790,Polychrome woodblock print; ink and color on paper,H. 12 1/4 in. (31.1 cm); W. 5 3/4 in. (14.6 cm),"Fletcher Fund, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55708,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2666,false,true,56844,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunkō,"Japanese, 1743–1812",,Katsukawa Shunkō,Japanese,1743,1812,ca. 1775,1765,1785,Polychrome woodblock print; ink and color on paper,12 1/4 x 5 3/4 in. (31.1 x 14.6 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56844,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2667,false,true,56845,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunkō,"Japanese, 1743–1812",,Katsukawa Shunkō,Japanese,1743,1812,ca. 1790,1780,1810,Polychrome woodblock print; ink and color on paper,13 x 6 in. (33 x 15.2 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56845,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2668,false,true,56846,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunkō,"Japanese, 1743–1812",,Katsukawa Shunkō,Japanese,1743,1812,ca. 1790,1780,1810,Polychrome woodblock print; ink and color on paper,13 x 5 3/4 in. (33 x 14.6 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56846,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2669,false,true,56847,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunkō,"Japanese, 1743–1812",,Katsukawa Shunkō,Japanese,1743,1812,ca. 1790,1780,1810,Polychrome woodblock print; ink and color on paper,12 3/4 x 5 3/4 in. (32.4 x 14.6 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56847,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2671,false,true,56849,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunkō,"Japanese, 1743–1812",,Katsukawa Shunkō,Japanese,1743,1812,ca. 1785,1775,1795,Polychrome woodblock print; ink and color on paper,12 3/4 x 5 3/4 in. (32.4 x 14.6 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56849,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2672,false,true,56850,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunkō,"Japanese, 1743–1812",,Katsukawa Shunkō,Japanese,1743,1812,ca. 1780,1770,1790,Polychrome woodblock print; ink and color on paper,11 7/8 x 5 7/8 in. (30.2 x 14.9 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56850,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2673,false,true,56851,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunkō,"Japanese, 1743–1812",,Katsukawa Shunkō,Japanese,1743,1812,1784,1784,1784,Polychrome woodblock print; ink and color on paper,12 1/2 x 5 5/8 in. (31.8 x 14.3 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56851,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2674,false,true,56852,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunkō,"Japanese, 1743–1812",,Katsukawa Shunkō,Japanese,1743,1812,ca. 1786,1776,1796,Polychrome woodblock print; ink and color on paper,12 3/8 x 5 5/8 in. (31.4 x 14.3 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56852,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2675,false,true,56853,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunkō,"Japanese, 1743–1812",,Katsukawa Shunkō,Japanese,1743,1812,ca. 1780,1770,1790,Polychrome woodblock print; ink and color on paper,12 1/4 x 5 1/2 in. (31.1 x 14 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56853,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2830,false,true,57070,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunkō,"Japanese, 1743–1812",,Katsukawa Shunkō,Japanese,1743,1812,1743–1812,1743,1812,Polychrome woodblock print; ink and color on paper,15 3/16 x 10 1/4 in. (38.6 x 26 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/57070,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2907,false,true,56022,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunkō,"Japanese, 1743–1812",,Katsukawa Shunkō,Japanese,1743,1812,1743–1812,1743,1812,Polychrome woodblock print; ink and color on paper,13 7/8 x 6 in. (35.2 x 15.2 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56022,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2908,false,true,56023,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunkō,"Japanese, 1743–1812",,Katsukawa Shunkō,Japanese,1743,1812,1743–1812,1743,1812,Polychrome woodblock print; ink and color on paper,12 1/4 x 5 1/2 in. (31.1 x 14 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56023,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2909,false,true,56024,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunkō,"Japanese, 1743–1812",,Katsukawa Shunkō,Japanese,1743,1812,1743–1812,1743,1812,Polychrome woodblock print; ink and color on paper,12 3/4 x 8 5/8 in. (32.4 x 21.9 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56024,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"JP293a, b",false,true,36478,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunkō,"Japanese, 1743–1812",,Katsukawa Shunkō,Japanese,1743,1812,ca. 1787,1777,1797,Diptych of polychrome woodblock prints; ink and color on paper,H. 11 7/8 in. (30.2 cm); W. 11 3/8 in. (28.9 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36478,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"JP294a, b",false,true,36477,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunkō,"Japanese, 1743–1812",,Katsukawa Shunkō,Japanese,1743,1812,ca. 1789,1779,1799,Diptych of polychrome woodblock prints; ink and color on paper,H. 12 9/16 in. (31.9 cm); W. 11 11/16 in. (29.7 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36477,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"JP2670a, b",false,true,56848,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunkō,"Japanese, 1743–1812",,Katsukawa Shunkō,Japanese,1743,1812,ca. 1780,1770,1790,Diptych of polychrome woodblock prints; ink and color on paper,L. 12 3/8 x 5 3/4 in. (31.4 x 14.6 cm) R. 12 1/4 x 5 7/8 in. (31.1 x 14.9 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56848,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP291a–c,false,true,36480,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunkō,"Japanese, 1743–1812",,Katsukawa Shunkō,Japanese,1743,1812,ca. 1788,1778,1798,Triptych of polychrome woodblock prints; ink and color on paper,Mat measurements: H. 22 3/4 in. (57.8 cm); W. 27 1/2 in. (69.9 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36480,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP292a–c,false,true,36479,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunkō,"Japanese, 1743–1812",,Katsukawa Shunkō,Japanese,1743,1812,ca. 1785,1775,1795,Triptych of polychrome woodblock prints; ink and color on paper,H. 12 1/4 (31.1 cm); W. 17 3/16 in. (43.7 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36479,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP128,false,true,36607,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyonaga,"Japanese, 1752–1815",,Torii Kiyonaga,Japanese,1752,1815,ca. 1783,1773,1793,Diptych of polychrome woodblock prints; ink and color on paper,15 x 20 3/8 in. (38.1 x 51.8 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36607,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP129,false,true,36608,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyonaga,"Japanese, 1752–1815",,Torii Kiyonaga,Japanese,1752,1815,ca. 1784,1774,1794,Polychrome woodblock print; ink and color on paper,14 11/16 x 10 1/8 in. (37.3 x 25.7 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36608,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP130,false,true,36609,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyonaga,"Japanese, 1752–1815",,Torii Kiyonaga,Japanese,1752,1815,ca. 1779,1769,1789,Polychrome woodblock print; ink and color on paper,9 3/5 x 7 1/5 in. (24.4 x 18.3 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36609,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP131,false,true,36610,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyonaga,"Japanese, 1752–1815",,Torii Kiyonaga,Japanese,1752,1815,ca. 1780,1770,1790,Polychrome woodblock print; ink and color on paper,10 x 7 1/8 in. (25.4 x 18.1 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36610,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP132,false,true,36611,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyonaga,"Japanese, 1752–1815",,Torii Kiyonaga,Japanese,1752,1815,ca. 1785,1775,1795,Polychrome woodblock print; ink and color on paper,25 31/32 x 4 15/32 in. (66.0 x 11.4 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36611,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP133,false,true,36612,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyonaga,"Japanese, 1752–1815",,Torii Kiyonaga,Japanese,1752,1815,ca. 1785,1775,1795,Polychrome woodblock print; ink and color on paper,26 7/8 x 4 3/4 in. (68.3 x 12.1 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36612,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP134,false,true,36613,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyonaga,"Japanese, 1752–1815",,Torii Kiyonaga,Japanese,1752,1815,ca. 1785,1775,1795,Polychrome woodblock print; ink and color on paper,26 3/5 x 4 3/5 in. (67.6 x 11.7 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36613,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP135,false,true,36614,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyonaga,"Japanese, 1752–1815",,Torii Kiyonaga,Japanese,1752,1815,ca. 1788–90,1786,1792,Triptych of polychrome woodblock prints; ink and color on paper,14 3/4 x 10 in. (37.5 x 25.4 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36614,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP564,false,true,37015,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyonaga,"Japanese, 1752–1815",,Torii Kiyonaga,Japanese,1752,1815,ca. 1790,1780,1800,Polychrome woodblock print; ink and color on paper,14 3/5 x 9 15/32 in. (37.1 x 24.1 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37015,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP717,false,true,37165,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyonaga,"Japanese, 1752–1815",,Torii Kiyonaga,Japanese,1752,1815,ca. 1763,1753,1775,Polychrome woodblock print; ink and color on paper,12 x 5 7/32 in. (30.5 x 13.3 cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37165,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP718,false,true,37166,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyonaga,"Japanese, 1752–1815",,Torii Kiyonaga,Japanese,1752,1815,1773,1773,1773,Polychrome woodblock print; ink and color on paper,11 3/8 x 5 3/8 in. (28.9 x 13.7 cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37166,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP719,false,true,37167,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyonaga,"Japanese, 1752–1815",,Torii Kiyonaga,Japanese,1752,1815,ca. 1770,1760,1780,Polychrome woodblock print; ink and color on paper,12 11/32 x 5 17/32 in. (31.4 x 14.1 cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37167,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP720,false,true,37168,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyonaga,"Japanese, 1752–1815",,Torii Kiyonaga,Japanese,1752,1815,ca. 1779,1769,1789,Polychrome woodblock print; ink and color on paper,10 x 7 11/32 in. (25.4 x 18.7 cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37168,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP721,false,true,37169,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyonaga,"Japanese, 1752–1815",,Torii Kiyonaga,Japanese,1752,1815,ca. 1782,1772,1792,Polychrome woodblock print; ink and color on paper,15 x 9 1/16 in. (38.1 x 23.0 cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37169,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP722,false,true,37170,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyonaga,"Japanese, 1752–1815",,Torii Kiyonaga,Japanese,1752,1815,ca. 1782,1772,1792,Right-hand sheet of a diptych of polychrome woodblock prints; ink and color on paper,14 15/32 x 9 3/4 in. (36.8 x 24.8 cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37170,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP723,false,true,37171,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyonaga,"Japanese, 1752–1815",,Torii Kiyonaga,Japanese,1752,1815,ca. 1783,1773,1793,Polychrome woodblock print; ink and color on paper,15 3/8 x 10 7/32 in. (39.1 x 26 cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37171,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP724,false,true,37172,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyonaga,"Japanese, 1752–1815",,Torii Kiyonaga,Japanese,1752,1815,ca. 1781–89,1781,1789,Polychrome woodblock print; ink and color on paper,Aiban; 13 1/4 x 9 7/8 in. (33.7 x 25.1 cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37172,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP725,false,true,37173,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyonaga,"Japanese, 1752–1815",,Torii Kiyonaga,Japanese,1752,1815,ca. 1788,1778,1798,Polychrome woodblock print; ink and color on paper,15 1/8 x 10 1/8 in. (38.4 x 25.7 cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37173,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP726,false,true,37174,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyonaga,"Japanese, 1752–1815",,Torii Kiyonaga,Japanese,1752,1815,ca. 1788,1778,1798,Polychrome woodblock print; ink and color on paper,14 15/32 x 10 1/8 in. (36.8 x 25.7 cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37174,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP727,false,true,37175,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyonaga,"Japanese, 1752–1815",,Torii Kiyonaga,Japanese,1752,1815,ca. 1784,1774,1794,Polychrome woodblock print; ink and color on paper,12 31/32 x 5 3/4 in. (33.0 x 14.6 cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37175,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP728,false,true,37176,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyonaga,"Japanese, 1752–1815",,Torii Kiyonaga,Japanese,1752,1815,ca. 1790,1780,1800,Polychrome woodblock print; ink and color on paper,15 1/8 x 10 1/8 in. (38.4 x 25.7 cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37176,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP729,false,true,37177,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyonaga,"Japanese, 1752–1815",,Torii Kiyonaga,Japanese,1752,1815,ca. 1790,1780,1800,One sheet of a triptych of polychrome woodblock prints; ink and color on paper,15 x 10 1/8 in. (38.1 x 25.7 cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37177,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP730,false,true,37178,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyonaga,"Japanese, 1752–1815",,Torii Kiyonaga,Japanese,1752,1815,ca. 1790,1780,1800,Middle sheet of a triptych of polychrome woodblock prints; ink and color on paper,14 1/3 x 10 in. (36.4 x 25.4 cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37178,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP806,false,true,37250,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyonaga,"Japanese, 1752–1815",,Torii Kiyonaga,Japanese,1752,1815,ca. 1789,1779,1799,Polychrome woodblock print; ink and color on paper,9 3/4 x 7 1/2 in. (24.8 x 19.1 cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37250,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP918,false,true,54658,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyonaga,"Japanese, 1752–1815",,Torii Kiyonaga,Japanese,1752,1815,ca. 1779,1769,1789,Polychrome woodblock print; ink and color on paper,H. 8 1/16 in. (20.5 cm); W. 8 1/16 in. (20.5 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54658,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP919,false,true,54659,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyonaga,"Japanese, 1752–1815",,Torii Kiyonaga,Japanese,1752,1815,ca. 1779,1769,1789,Polychrome woodblock print; ink and color on paper,H. 10 1/2 in. (26.7 cm); W. 7 3/4 in. (19.7 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54659,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP920,false,true,54660,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyonaga,"Japanese, 1752–1815",,Torii Kiyonaga,Japanese,1752,1815,ca. 1781,1771,1791,Polychrome woodblock print; ink and color on paper,H. 10 1/4 in. (26 cm); W. 7 1/2 in. (19.1 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54660,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP921,false,true,54661,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyonaga,"Japanese, 1752–1815",,Torii Kiyonaga,Japanese,1752,1815,ca. 1781,1776,1786,Polychrome woodblock print; ink and color on paper,H. 9 13/16 in. (24.9 cm); W. 7 1/4 in. (18.4 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54661,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP922,false,true,54662,Asian Art,Print,玉花子栄茂図|Gyoku-kashi Eimo Preparing Calligraphy Offerings,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyonaga,"Japanese, 1752–1815",,Torii Kiyonaga,Japanese,1752,1815,ca. 1782,1772,1792,Polychrome woodblock print; ink and color on paper,H. 15 1/8 in. (38.4 cm); W. 10 1/4 in. (26 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54662,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP923,false,true,54663,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyonaga,"Japanese, 1752–1815",,Torii Kiyonaga,Japanese,1752,1815,ca. 1784,1774,1794,Polychrome woodblock print; ink and color on paper,H. 14 1/4 in. (36.2 cm); W. 9 5/16 in. (23.7 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54663,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP924,false,true,54664,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyonaga,"Japanese, 1752–1815",,Torii Kiyonaga,Japanese,1752,1815,ca. 1782,1772,1792,Polychrome woodblock print; ink and color on paper,14 1/8 x 9 9/16in. (35.9 x 24.3cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54664,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP925,false,true,54665,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyonaga,"Japanese, 1752–1815",,Torii Kiyonaga,Japanese,1752,1815,ca. 1784,1774,1794,Polychrome woodblock print; ink and color on paper,H. 13 1/8 in. (33.3 cm); W. 10 1/8 in. (25.7 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54665,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP926,false,true,54666,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyonaga,"Japanese, 1752–1815",,Torii Kiyonaga,Japanese,1752,1815,ca. 1785,1775,1795,Polychrome woodblock print; ink and color on paper,H. 14 1/8 in. (35.9 cm); W. 9 5/8 in. (24.4 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54666,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP927,false,true,45241,Asian Art,Print,"Kodomo Mando, Kojimachi 1,2, 3-chome, Sanno Go-sairei|Childrens' Lantern Float, Kojimachi 1,2, 3-chome Block Association, Sanno Festival",Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyonaga,"Japanese, 1752–1815",,Torii Kiyonaga,Japanese,1752,1815,ca. 1780,1771,1790,Polychrome woodblock print; ink and color on paper,H. 10 1/4 in. (26 cm); W. 7 1/2 in. (19.1 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45241,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP928,false,true,54667,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyonaga,"Japanese, 1752–1815",,Torii Kiyonaga,Japanese,1752,1815,ca. 1790,1780,1800,Polychrome woodblock print; ink and color on paper,H. 14 7/8 in. (37.8 cm); W. 9 1/2 in. (24.1 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54667,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP929,false,true,54668,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyonaga,"Japanese, 1752–1815",,Torii Kiyonaga,Japanese,1752,1815,ca. 1791,1781,1801,Polychrome woodblock print; ink and color on paper,H. 10 3/8 in. (26.4 cm); W. 7 1/2 in. (19.1 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54668,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP930,false,true,54669,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyonaga,"Japanese, 1752–1815",,Torii Kiyonaga,Japanese,1752,1815,ca. 1793,1783,1803,Polychrome woodblock print; ink and color on paper,H. 9 5/8 in. (24.4 cm); W. 7 1/8 in. (18.1 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54669,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP931,false,true,54670,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyonaga,"Japanese, 1752–1815",,Torii Kiyonaga,Japanese,1752,1815,ca. 1792,1782,1802,Polychrome woodblock print; ink and color on paper,H. 14 7/8 in. (37.8 cm); W. 10 in. (25.4 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54670,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1211,false,true,55134,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyonaga,"Japanese, 1752–1815",,Torii Kiyonaga,Japanese,1752,1815,ca. 1780,1770,1790,Polychrome woodblock print; ink and color on paper,H. 27 7/16 in. (69.7 cm); W. 4 7/8 in. (12.4 cm),"Rogers Fund, 1920",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55134,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1372,false,true,55352,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyonaga,"Japanese, 1752–1815",,Torii Kiyonaga,Japanese,1752,1815,ca. 1788,1778,1798,Polychrome woodblock print; ink and color on paper,H. 15 1/4 in. (38.7 cm); W. 9 3/4 in. (24.8 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55352,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1373,false,true,45038,Asian Art,Print,Shiokumi|The Dance of the Beach Maidens from the series Brocade of the East,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyonaga,"Japanese, 1752–1815",,Torii Kiyonaga,Japanese,1752,1815,ca. 1770,1760,1780,Polychrome woodblock print; ink and color on paper,Image: 15 3/16 x 9 13/16 in. (38.6 x 24.9 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45038,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1377,false,true,55360,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyonaga,"Japanese, 1752–1815",,Torii Kiyonaga,Japanese,1752,1815,1780,1780,1780,Polychrome woodblock print; ink and color on paper,H. 27 3/8 in. (69.5 cm); W. 4 5/8 in. (11.7 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55360,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1463,false,true,55510,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyonaga,"Japanese, 1752–1815",,Torii Kiyonaga,Japanese,1752,1815,ca. 1779,1769,1789,Polychrome woodblock print; ink and color on paper,H. 10 3/8 in. (26.4 cm); W. 7 1/2 in. (19.1 cm),"Rogers Fund, 1925",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55510,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1509,false,true,45031,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyonaga,"Japanese, 1752–1815",,Torii Kiyonaga,Japanese,1752,1815,ca. 1800,1790,1810,Polychrome woodblock print; ink and color on paper,H. 14 1/8 in. (35.9 cm); W. 19 1/4 in. (48.9 cm),"Fletcher Fund, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45031,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1510,false,true,55610,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyonaga,"Japanese, 1752–1815",,Torii Kiyonaga,Japanese,1752,1815,ca. 1785,1775,1795,Polychrome woodblock print; ink and color on paper,H. 15 1/8 in. (38.4 cm); W. 10 in. (25.4 cm),"Fletcher Fund, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55610,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1511,false,true,55613,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyonaga,"Japanese, 1752–1815",,Torii Kiyonaga,Japanese,1752,1815,ca. 1785,1775,1795,Polychrome woodblock print; ink and color on paper,H. 15 5/16 in. (38.9 cm); W. 10 3/8 in. (26.4 cm),"Fletcher Fund, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55613,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1512,false,true,51998,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyonaga,"Japanese, 1752–1815",,Torii Kiyonaga,Japanese,1752,1815,ca. 1790–91,1660,1900,Polychrome woodblock print; ink and color on paper,Image: 14 3/4 x 9 15/16 in. (37.5 x 25.2 cm),"Fletcher Fund, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/51998,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1513,false,true,55615,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyonaga,"Japanese, 1752–1815",,Torii Kiyonaga,Japanese,1752,1815,ca. 1784,1774,1794,Polychrome woodblock print; ink and color on paper,H. 14 1/2 in. (36.8 cm); W. 9 1/2 in. (24.1 cm),"Fletcher Fund, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55615,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1514,false,true,55616,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyonaga,"Japanese, 1752–1815",,Torii Kiyonaga,Japanese,1752,1815,ca. 1788,1778,1798,Polychrome woodblock print; ink and color on paper,H. 14 15/16 in. (37.9 cm); W. 9 3/4 in. (24.8 cm),"Fletcher Fund, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55616,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1713,false,true,55966,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyonaga,"Japanese, 1752–1815",,Torii Kiyonaga,Japanese,1752,1815,1782,1779,1789,Polychrome woodblock print; ink and color on paper,14 5/8 x 10 in. (37.1 x 25.4cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55966,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1724,false,true,56035,Asian Art,Print,"「美南見十二候」 五月 |The Fifth Month, from the series Twelve Months in the Southern Pleasure District (Minami jūni kō)",Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyonaga,"Japanese, 1752–1815",,Torii Kiyonaga,Japanese,1752,1815,1784,1784,1784,Left sheet of a diptych of polychrome woodblock prints; ink and color on paper,14 7/8 x 9 15/16in. (37.8 x 25.2cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56035,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1734,false,true,45256,Asian Art,Print,Okawabata yu-suzumi|Enjoying the Evening Cool on the Banks of the Sumida River,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyonaga,"Japanese, 1752–1815",,Torii Kiyonaga,Japanese,1752,1815,ca. 1784,1774,1794,Diptych of polychrome woodblock prints; ink and color on paper,A: H. 15 in. (38.1 cm); W. 10 in. (25.4 cm) B: H. 14 5/8 in. (37.1 cm); W. 9 15/16 in. (25.2 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45256,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2593,false,true,56739,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyonaga,"Japanese, 1752–1815",,Torii Kiyonaga,Japanese,1752,1815,ca. 1787,1777,1797,Polychrome woodblock print; ink and color on paper,10 1/4 x 7 5/8 in. (26 x 19.4 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56739,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2594,false,true,56740,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyonaga,"Japanese, 1752–1815",,Torii Kiyonaga,Japanese,1752,1815,ca. 1779,1769,1789,Polychrome woodblock print; ink and color on paper,10 1/2 x 7 3/4 in. (26.7 x 19.7 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56740,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2595,false,true,56741,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyonaga,"Japanese, 1752–1815",,Torii Kiyonaga,Japanese,1752,1815,ca. 1778,1768,1788,Polychrome woodblock print; ink and color on paper,10 5/16 x 7 3/4 in. (26.2 x 19.7 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56741,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2596,false,true,56742,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyonaga,"Japanese, 1752–1815",,Torii Kiyonaga,Japanese,1752,1815,ca. 1778,1768,1788,Polychrome woodblock print; ink and color on paper,10 1/4 x 7 3/8 in. (26 x 18.7 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56742,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2597,false,true,56743,Asian Art,Print,Shiba: Atago|Atago Hill at Shiba,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyonaga,"Japanese, 1752–1815",,Torii Kiyonaga,Japanese,1752,1815,ca. 1783,1773,1793,Polychrome woodblock print; ink and color on paper,10 7/16 x 7 5/8 in. (26.5 x 19.4 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56743,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2598,false,true,56744,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyonaga,"Japanese, 1752–1815",,Torii Kiyonaga,Japanese,1752,1815,ca. 1788,1778,1798,Polychrome woodblock print; ink and color on paper,12 5/8 x 8 3/4 in. (32.1 x 22.2 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56744,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2599,false,true,56745,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyonaga,"Japanese, 1752–1815",,Torii Kiyonaga,Japanese,1752,1815,ca. 1783,1773,1793,Polychrome woodblock print; ink and color on paper,12 3/4 x 8 3/4 in. (32.4 x 22.2 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56745,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2600,false,true,56746,Asian Art,Print,二階座敷に三人の女|Three Women on a Balcony,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyonaga,"Japanese, 1752–1815",,Torii Kiyonaga,Japanese,1752,1815,ca. 1786,1776,1796,Upper sheet of a vertical diptych of polychrome woodblock prints; ink and color on paper,14 7/8 x 10 1/16 in. (37.8 x 25.6 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56746,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2602,false,true,56748,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyonaga,"Japanese, 1752–1815",,Torii Kiyonaga,Japanese,1752,1815,ca. 1785,1775,1795,Polychrome woodblock print; ink and color on paper,11 3/4 x 5 7/8 in. (29.8 x 14.9 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56748,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2603,false,true,56749,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyonaga,"Japanese, 1752–1815",,Torii Kiyonaga,Japanese,1752,1815,1784,1784,1784,Polychrome woodblock print; ink and color on paper,15 1/4 x 10 1/4 in. (38.7 x 26 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56749,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2604,false,true,45246,Asian Art,Print,"「風俗東之錦」 姫君と侍女四人|High-Ranking Samurai Girl with Four Attendants, from the series A Brocade of Eastern Manners (Fūzoku Azuma no nishiki)",Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyonaga,"Japanese, 1752–1815",,Torii Kiyonaga,Japanese,1752,1815,ca. 1784,1774,1794,Polychrome woodblock print; ink and color on paper,15 x 10 in. (38.1 x 25.4 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45246,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2605,false,true,56750,Asian Art,Print,Kitchugi|Geisha of the Tachibana Street,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyonaga,"Japanese, 1752–1815",,Torii Kiyonaga,Japanese,1752,1815,ca. 1786,1776,1796,Polychrome woodblock print; ink and color on paper,14 7/8 x 10 1/4 in. (37.8 x 26 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56750,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2607,false,true,56751,Asian Art,Print,"「風俗東之錦」  武家の息女と侍女と若党 |A Lady from a Samurai Household with Three Attendants, from the series A Brocade of Eastern Manners (Fūzoku Azuma no nishiki)",Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyonaga,"Japanese, 1752–1815",,Torii Kiyonaga,Japanese,1752,1815,ca. 1820,1810,1830,Polychrome woodblock print; ink and color on paper,15 1/4 x 10 3/8 in. (38.7 x 26.4 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56751,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2608,false,true,56752,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyonaga,"Japanese, 1752–1815",,Torii Kiyonaga,Japanese,1752,1815,1782,1782,1782,Polychrome woodblock print; ink and color on paper,15 1/4 x 10 in. (38.7 x 25.4 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56752,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2609,false,true,56753,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyonaga,"Japanese, 1752–1815",,Torii Kiyonaga,Japanese,1752,1815,1784–85,1784,1785,Polychrome woodblock print; ink and color on paper,15 1/8 x 10 1/4 in. (38.4 x 26 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56753,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2610,false,true,56754,Asian Art,Print,四代目松本幸四郎とその家庭|The Kabuki Actor Matsumoto Kōshirō IV,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyonaga,"Japanese, 1752–1815",,Torii Kiyonaga,Japanese,1752,1815,ca. 1783,1773,1793,Polychrome woodblock print; ink and color on paper,15 1/4 x 10 1/2 in. (38.7 x 26.7 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56754,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2611,false,true,56755,Asian Art,Woodblock print,三代目沢村宗十郎と遊女|The Kabuki Actor Sawamura Sōjūrō III and Courtesans,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyonaga,"Japanese, 1752–1815",,Torii Kiyonaga,Japanese,1752,1815,ca. 1783–84,1783,1784,Polychrome woodblock print; ink and color on paper,14 7/8 x 10 in. (37.8 x 25.4 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56755,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2612,false,true,56756,Asian Art,Print,Nakasu no suzumi|Enjoying the Evening Cool at Nakasu,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyonaga,"Japanese, 1752–1815",,Torii Kiyonaga,Japanese,1752,1815,ca. 1782–83,1782,1783,Polychrome woodblock print; ink and color on paper,14 3/4 x 10 1/2 in. (37.5 x 26.7 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56756,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2613,false,true,56757,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyonaga,"Japanese, 1752–1815",,Torii Kiyonaga,Japanese,1752,1815,ca. 1781,1771,1791,Polychrome woodblock print; ink and color on paper,15 x 10 1/8 in. (38.1 x 25.7 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56757,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2614,false,true,56758,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyonaga,"Japanese, 1752–1815",,Torii Kiyonaga,Japanese,1752,1815,ca. 1786,1776,1796,Polychrome woodblock print; ink and color on paper,15 1/8 x 10 1/8 in. (38.4 x 25.7 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56758,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2615,false,true,52003,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyonaga,"Japanese, 1752–1815",,Torii Kiyonaga,Japanese,1752,1815,ca. 1790,1790,1810,Polychrome woodblock print; ink and color on paper,15 x 10 in. (38.1 x 25.4 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/52003,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2616,false,true,56760,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyonaga,"Japanese, 1752–1815",,Torii Kiyonaga,Japanese,1752,1815,ca. 1788,1778,1798,Polychrome woodblock print; ink and color on paper,15 x 10 1/8 in. (38.1 x 25.7 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56760,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2617,false,true,56761,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyonaga,"Japanese, 1752–1815",,Torii Kiyonaga,Japanese,1752,1815,ca. 1784,1774,1794,Diptych of polychrome woodblock prints; ink and color on paper,Each sheet: 15 x 10 in. (38.1 x 25.4 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56761,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2618,false,true,56762,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyonaga,"Japanese, 1752–1815",,Torii Kiyonaga,Japanese,1752,1815,ca. 1785,1775,1795,Polychrome woodblock print; ink and color on paper,26 1/4 x 4 3/4 in. (66.7 x 12.1 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56762,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2797,false,true,57092,Asian Art,Print,Kitchugi|Dancers of Tachibana Street,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyonaga,"Japanese, 1752–1815",,Torii Kiyonaga,Japanese,1752,1815,1742–1815,1742,1815,Polychrome woodblock print; ink and color on paper,15 1/8 x 10 1/8 in. (38.4 x 25.7 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/57092,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2798,false,true,57091,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyonaga,"Japanese, 1752–1815",,Torii Kiyonaga,Japanese,1752,1815,1783,1783,1783,Polychrome woodblock print; ink and color on paper,15 x 10 in. (38.1 x 25.4 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/57091,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2799,false,true,53904,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyonaga,"Japanese, 1752–1815",,Torii Kiyonaga,Japanese,1752,1815,1783,1750,1868,Polychrome woodblock print; ink and color on paper,15 1/4 x 10 1/8 in. (38.7 x 25.7 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/53904,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2800,false,true,57090,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyonaga,"Japanese, 1752–1815",,Torii Kiyonaga,Japanese,1752,1815,1784,1784,1784,Polychrome woodblock print; ink and color on paper,14 15/16 x 9 3/4 in. (37.9 x 24.8 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/57090,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2801,false,true,57089,Asian Art,Print,Sako no Suzumi|Cooling Off at Nakazu,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyonaga,"Japanese, 1752–1815",,Torii Kiyonaga,Japanese,1752,1815,1782,1782,1782,Left-hand sheet of a diptych of polychrome woodblock prints; ink and color on paper,14 1/2 x 9 3/4 in. (36.8 x 24.8 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/57089,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2802,false,true,57088,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyonaga,"Japanese, 1752–1815",,Torii Kiyonaga,Japanese,1752,1815,1785,1785,1785,Polychrome woodblock print; ink and color on paper,15 3/8 x 10 1/8 in. (39.1 x 25.7 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/57088,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"JP1268a, b",false,true,55165,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyonaga,"Japanese, 1752–1815",,Torii Kiyonaga,Japanese,1752,1815,ca. 1783,1773,1793,Diptych of polychrome woodblock prints; ink with color reprinted and revamped on paper,Oban diptych 15 1/2 x 20 in. (39.4 x 50.8 cm),"Gift of Frank L. Wright, 1921",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55165,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"JP2622a, b",false,true,56765,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyonaga,"Japanese, 1752–1815",,Torii Kiyonaga,Japanese,1752,1815,ca. 1785,1785,1795,Diptych of polychrome woodblock prints; ink and color on paper,Each sheet: 15 1/4 x 10 1/4 in. (38.7 x 26 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56765,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2619a–c,false,true,56763,Asian Art,Print,仲の町の牡丹|The Peony Show,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyonaga,"Japanese, 1752–1815",,Torii Kiyonaga,Japanese,1752,1815,ca. 1790,1780,1810,Triptych of polychrome woodblock prints; ink and color on paper,Each sheet: 15 x 10 in. (38.1 x 25.4 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56763,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2620a–c,false,true,45257,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyonaga,"Japanese, 1752–1815",,Torii Kiyonaga,Japanese,1752,1815,ca. 1785,1775,1795,Triptych of polychrome woodblock prints; ink and color on paper,Each sheet: 15 1/4 x 10 1/4 in. (38.7 x 26 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45257,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2621a–c,false,true,56764,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyonaga,"Japanese, 1752–1815",,Torii Kiyonaga,Japanese,1752,1815,ca. 1785,1775,1795,Triptych of polychrome woodblock prints; ink and color on paper,Each sheet: 15 x 10 in. (38.1 x 25.4 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56764,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3393a–c,false,true,56670,Asian Art,Print,三俳優隅田川船遊び|Sumida River Holiday,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyonaga,"Japanese, 1752–1815",,Torii Kiyonaga,Japanese,1752,1815,1788–90,1788,1790,Triptych of polychrome woodblock prints; ink and color on paper,Triptych; each H. 14 1/2 in. (36.8 cm); W. 10 1/8 in. (25.7 cm),"Gift of Mr. and Mrs. Arthur J. Steel, 1960",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56670,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP178,false,true,36656,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Chōbunsai Eishi,"Japanese, 1756–1829",,Chōbunsai Eishi,Japanese,1756,1829,ca. 1780,1770,1790,Polychrome woodblock print; ink and color on paper,25 7/8 x 4 7/8 in. (65.7 x 12.4 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36656,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP179,false,true,36657,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Chōbunsai Eishi,"Japanese, 1756–1829",,Chōbunsai Eishi,Japanese,1756,1829,ca. 1780,1770,1790,Polychrome woodblock print; ink and color on paper,25 7/32 x 4 3/5 in. (64.1 x 11.7 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36657,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP180,false,true,36481,Asian Art,Print,新大橋橋下の涼み船|Pleasure Boats on the Sumida River beneath Shin-Ōhashi Bridge,Japan,Edo period (1615–1868),,,,Artist,,Chōbunsai Eishi,"Japanese, 1756–1829",,Chōbunsai Eishi,Japanese,1756,1829,ca. 1792,1782,1802,Pentaptych of polychrome woodblock prints; ink and color on paper,15 3/8 x 49 7/8 in. (39.1 x 126.7 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36481,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP181,false,true,36659,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Chōbunsai Eishi,"Japanese, 1756–1829",,Chōbunsai Eishi,Japanese,1756,1829,ca. 1790,1780,1800,Two sheets from a triptych of polychrome woodblock prints; ink and color on paper,15 1/8 x 20 1/2 in. (38.4 x 52.1 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36659,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP182,false,true,36660,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Chōbunsai Eishi,"Japanese, 1756–1829",,Chōbunsai Eishi,Japanese,1756,1829,ca. 1794,1784,1804,Triptych of polychrome woodblock prints; ink and color on paper,15 7/32 x 29 15/32 in. (38.7 x 74.9 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36660,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP183,false,true,36661,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Chōbunsai Eishi,"Japanese, 1756–1829",,Chōbunsai Eishi,Japanese,1756,1829,ca. 1793,1783,1803,Triptych of polychrome woodblock prints; ink and color on paper,14 15/32 x 29 1/4 in. (36.8 x 74.3 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36661,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP184,false,true,36662,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Chōbunsai Eishi,"Japanese, 1756–1829",,Chōbunsai Eishi,Japanese,1756,1829,ca. 1794,1784,1804,Triptych of polychrome woodblock prints (trimmed); ink and color on paper,14 3/8 x 28 23/32 in. (36.5 x 73.0 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36662,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP185,false,true,36663,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Chōbunsai Eishi,"Japanese, 1756–1829",,Chōbunsai Eishi,Japanese,1756,1829,ca. 1797,1787,1807,Triptych of polychrome woodblock prints; ink and color on paper,15 7/32 x 30 in. (38.7 x 76.2 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36663,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP944,false,true,54830,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Chōbunsai Eishi,"Japanese, 1756–1829",,Chōbunsai Eishi,Japanese,1756,1829,ca. 1788,1778,1798,Polychrome woodblock print; ink and color on paper,H. 9 5/8 in. (24.4 cm); W. 7 1/16 in. (17.9 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54830,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP945,false,true,54833,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Chōbunsai Eishi,"Japanese, 1756–1829",,Chōbunsai Eishi,Japanese,1756,1829,ca. 1788,1778,1798,Polychrome woodblock print; ink and color on paper,H. 15 in. (38.1 cm); W. 9 7/8 in. (25.1 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54833,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP946,false,true,54834,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Chōbunsai Eishi,"Japanese, 1756–1829",,Chōbunsai Eishi,Japanese,1756,1829,ca. 1788,1778,1798,Polychrome woodblock print; ink and color on paper,H. 10 1/16 in. (25.6 cm); W. 7 3/16 in. (18.3 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54834,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP947,false,true,54837,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Hosoda Eishi,"Japanese, 1756–1829",,Hosoda Eishi,Japanese,1756,1829,ca. 1788,1778,1798,Polychrome woodblock print; ink and color on paper,15 1/2 x 10 1/8 in. (39.4 x 25.7 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54837,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP948,false,true,54839,Asian Art,Print,和歌三神図|Honoring the Three Gods of Poetry: Women Composing Poems,Japan,Edo period (1615–1868),,,,Artist,,Chōbunsai Eishi,"Japanese, 1756–1829",,Chōbunsai Eishi,Japanese,1756,1829,ca. 1792,1782,1802,Triptych of polychrome woodblock prints; ink and color on paper,H. 14 1/2 in. (36.8 cm); W. 30 in. (76.2 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54839,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP949,false,true,54841,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Chōbunsai Eishi,"Japanese, 1756–1829",,Chōbunsai Eishi,Japanese,1756,1829,ca. 1794,1784,1804,Polychrome woodblock print; ink and color on paper,H. 14 5/8 in. (37.1 cm); 9 7/8 in. (25.1 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54841,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP950,false,true,54844,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Chōbunsai Eishi,"Japanese, 1756–1829",,Chōbunsai Eishi,Japanese,1756,1829,ca. 1794,1789,1799,Polychrome woodblock print; ink and color on paper,H. 15 1/2 in. (39.4 cm); W. 10 1/4 in. (26 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54844,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP951,false,true,54845,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Chōbunsai Eishi,"Japanese, 1756–1829",,Chōbunsai Eishi,Japanese,1756,1829,ca. 1795,1785,1805,Triptych of polychrome woodblock prints; ink and color on paper,H. 14 3/8 in. (36.5 cm); W. 30 in. (76.2 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54845,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP952,false,true,54846,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Chōbunsai Eishi,"Japanese, 1756–1829",,Chōbunsai Eishi,Japanese,1756,1829,ca. 1797,1787,1807,Triptych of polychrome woodblock prints; ink and color on paper,14 3/8 x 30 in. (36.5 x 76.2cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54846,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1124,false,true,55044,Asian Art,Print,"『青楼美人六花仙』「扇屋花扇」|“Hanaōgi of the Ōgiya,” from the series Beauties of the Yoshiwara as Six Floral Immortals (Seirō bijin rokkasen)",Japan,Edo period (1615–1868),,,,Artist,,Chōbunsai Eishi,"Japanese, 1756–1829",,Chōbunsai Eishi,Japanese,1756,1829,ca. 1794,1784,1804,Polychrome woodblock print; ink and color on paper,H. 14 9/16 in. (37 cm); W. 9 7/8 in. (25.1 cm),"Rogers Fund, 1919",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55044,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1442,false,true,55486,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Chōbunsai Eishi,"Japanese, 1756–1829",,Chōbunsai Eishi,Japanese,1756,1829,ca. 1790,1780,1800,Polychrome woodblock print; ink and color on paper,H. 14 5/8 in. (37.1 cm); W. 9 13/16 in. (24.9 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55486,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1524,false,true,53657,Asian Art,Print,"「青楼美撰合 初売座敷之図 扇屋 滝川」|Takigawa of the Ōgiya House, from the series A Comparison of Selected Beauties of the Pleasure Quarters (Seirō bisen awase)",Japan,Edo period (1615–1868),,,,Artist,,Chōbunsai Eishi,"Japanese, 1756–1829",,Chōbunsai Eishi,Japanese,1756,1829,ca. 1794–95,1794,1795,Polychrome woodblock print; ink and color on paper,H. 14 5/8 in. (37.1 cm); W. 10 in. (25.4 cm),"Fletcher Fund, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/53657,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1569,false,true,55746,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Chōbunsai Eishi,"Japanese, 1756–1829",,Chōbunsai Eishi,Japanese,1756,1829,ca. 1790,1780,1800,Triptych of polychrome woodblock prints; ink and color on paper,a: H. 14 7/8 in. (37.8 cm); W. 9 7/8 in. (25.1 cm); b: H. 14 7/8 in. (37.8 cm); W. 9 3/4 in. (24.8 cm); c: H. 15 in. (38.1 cm); W. 10 in. (25.4 cm),"Fletcher Fund, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55746,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1786,false,true,45215,Asian Art,Print,"「畧六花撰」|Matching Shells (Kaiawase), from the series Six Immortal Poets in Modern Guise (Yatsushi Rokkasen)",Japan,Edo period (1615–1868),,,,Artist,,Chōbunsai Eishi,"Japanese, 1756–1829",,Chōbunsai Eishi,Japanese,1756,1829,ca. 1796–98,1786,1808,Polychrome woodblock print; ink and color on paper,14 15/16 x 9 3/4 in. (37.9 x 24.8 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45215,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2420,false,true,51091,Asian Art,Print,Amagoi|Ono no Komachi Praying for Rain,Japan,Edo period (1615–1868),,,,Artist,,Chōbunsai Eishi,"Japanese, 1756–1829",,Chōbunsai Eishi,Japanese,1756,1829,ca. 1791,1781,1801,Monochrome woodblock print; ink on paper,H. 15 1/4 in. (38.7 cm); W. 10 1/4 in. (26 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/51091,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2421,false,true,56824,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Chōbunsai Eishi,"Japanese, 1756–1829",,Chōbunsai Eishi,Japanese,1756,1829,ca. 1792,1782,1802,One sheet of a triptych of polychrome woodblock prints; ink and color on paper,14 1/2 x 9 5/8 in. (36.8 x 24.4 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56824,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2422,false,true,56828,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Chōbunsai Eishi,"Japanese, 1756–1829",,Chōbunsai Eishi,Japanese,1756,1829,ca. 1790,1780,1800,Polychrome woodblock print; ink and color on paper,15 1/4 x 10 1/4 in. (38.7 x 26 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56828,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2423,false,true,52000,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Chōbunsai Eishi,"Japanese, 1756–1829",,Chōbunsai Eishi,Japanese,1756,1829,ca. 1790,1780,1800,One sheet of a triptych of polychrome woodblock prints; ink and color on paper,15 1/8 x 10 1/8 in. (38.4 x 25.7 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/52000,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2424,false,true,56827,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Chōbunsai Eishi,"Japanese, 1756–1829",,Chōbunsai Eishi,Japanese,1756,1829,ca. 1795,1785,1805,Polychrome woodblock print; ink and color on paper,15 1/4 x 10 1/4 in. (38.7 x 26 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56827,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2815,false,true,53655,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Chōbunsai Eishi,"Japanese, 1756–1829",,Chōbunsai Eishi,Japanese,1756,1829,1756–1829,1756,1829,Polychrome woodblock print; ink and color on paper,14 5/8 x 9 5/8 in. (37.1 x 24.4 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/53655,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2816,false,true,57079,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Chōbunsai Eishi,"Japanese, 1756–1829",,Chōbunsai Eishi,Japanese,1756,1829,ca. 1795,1785,1805,Polychrome woodblock print; ink and color on paper,13 7/8 x 9 5/8 in. (35.2 x 24.4 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/57079,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2817,false,true,57078,Asian Art,Print,Seiryu Edo|Courtesan District of Edo,Japan,Edo period (1615–1868),,,,Artist,,Chōbunsai Eishi,"Japanese, 1756–1829",,Chōbunsai Eishi,Japanese,1756,1829,1756–1829,1756,1829,Polychrome woodblock print; ink and color on paper,15 x 10 1/8 in. (38.1 x 25.7 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/57078,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2819,false,true,57076,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Chōbunsai Eishi,"Japanese, 1756–1829",,Chōbunsai Eishi,Japanese,1756,1829,1756–1829,1756,1829,Polychrome woodblock print; ink and color on paper,14 7/8 x 9 9/16 in. (37.8 x 24.3 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/57076,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2820,false,true,57075,Asian Art,Print,Onono Komachi|Parrot Komachi,Japan,Edo period (1615–1868),,,,Artist,,Chōbunsai Eishi,"Japanese, 1756–1829",,Chōbunsai Eishi,Japanese,1756,1829,ca. 1788,1778,1798,Polychrome woodblock print; ink and color on paper,14 5/8 x 9 7/8 in. (37.1 x 25.1 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/57075,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3132,false,true,56680,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Chōbunsai Eishi,"Japanese, 1756–1829",,Chōbunsai Eishi,Japanese,1756,1829,ca. 1781–1800,1781,1800,Triptych of polychrome woodblock prints; ink and color on paper,Triptych; Overall: 14 1/4 x 30 1/8 in. (36.2 x 76.5 cm),"Gift of Francis M. Weld, 1948",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56680,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1096c,false,true,639381,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Chōbunsai Eishi,"Japanese, 1756–1829",,Chōbunsai Eishi,Japanese,1756,1829,ca. 1796 (Kansei 8),1796,1796,Polychrome woodblock print; ink and color on paper,Image: 14 3/4 × 9 1/2 in. (37.5 × 24.1 cm),The Metropolitan Museum of Art,,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/639381,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2818a–c,false,true,57077,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Chōbunsai Eishi,"Japanese, 1756–1829",,Chōbunsai Eishi,Japanese,1756,1829,ca. 1794,1784,1804,Triptych of polychrome woodblock prints; ink and color on paper,A. (left): 14 5/8 x 9 7/8 in. (37.1 x 25.1 cm) B. (center): 14 5/8 x 10 in. (37.1 x 25.4 cm) C. (right): 14 5/8 x 10 in. (37.1 x 25.4 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/57077,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP177,false,true,36655,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",,Kubo Shunman,Japanese,1757,1820,ca. 1790,1770,1790,Polychrome woodblock print; ink and color on paper,15 x 9 3/5 in. (38.1 x 24.4 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36655,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP731,false,true,37179,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",,Kubo Shunman,Japanese,1757,1820,ca. 1790,1780,1800,Polychrome woodblock print; ink and color on paper,14 7/8 x 9 1/8 in. (37.8 x 23.2 cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37179,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP958,false,true,54852,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",,Kubo Shunman,Japanese,1757,1820,ca. 1788,1778,1798,Diptych of polychrome woodblock prints; ink and color on paper,H. 15 in. (38.1 cm); W. 20 1/2 in. (52.1 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54852,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP959,false,true,54853,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",,Kubo Shunman,Japanese,1757,1820,ca. 1790,1780,1800,Polychrome woodblock print; ink and color on paper,H. 8 7/16 in. (21.4 cm); W. 14 7 /16 in. (36.7 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54853,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP960,false,true,54314,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",,Kubo Shunman,Japanese,1757,1820,ca. 1800,1790,1810,Polychrome woodblock print (surimono); ink and color on paper,7 7/8 x 7 1/16 in. (20 x 17.9 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54314,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1106,false,true,54335,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",,Kubo Shunman,Japanese,1757,1820,ca. 1800,1790,1810,Polychrome woodblock print (surimono); ink and color on paper,7 15/16 x 7 3/16 in. (20.2 x 18.3 cm),"Rogers Fund, 1919",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54335,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1254,false,true,54386,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,possibly,Kubo Shunman,"Japanese, 1757–1820",,Kubo Shunman,Japanese,1757,1820,ca. 1800,1790,1810,Polychrome woodblock print (surimono); ink and color on paper,7 7/8 x 7 1/8 in. (20 x 18.1 cm),"Rogers Fund, 1921",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54386,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1907,false,true,54446,Asian Art,Print,"窪俊満画 煙草入れ袋と煙管『春雨集』 摺物帖|Pipe and Tobacco PouchFrom the Spring Rain Collection (Harusame shū), vol. 1",Japan,Edo period (1615–1868),,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",,Kubo Shunman,Japanese,1757,1820,ca. 1810s,1810,1819,Polychrome woodblock print (surimono); ink and color on paper,5 9/16 x 7 1/8 in. (14.1 x 18.1 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54446,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1912,false,true,54451,Asian Art,Print,"「松風台七番之内柄」|“Hilt of a Sword,” from the series of Seven Prints for the Shōfudai Poetry Circle",Japan,Edo period (1615–1868),,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",,Kubo Shunman,Japanese,1757,1820,1810s,1810,1819,Polychrome woodblock print (surimono); ink and color on paper,5 3/8 x 7 5/16 in. (13.7 x 18.6 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54451,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1927,false,true,54467,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",,Kubo Shunman,Japanese,1757,1820,probably 1816,1816,1816,Polychrome woodblock print (surimono); ink and color on paper,5 1/2 x 7 5/8 in. (14 x 19.4 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54467,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1931,false,true,54471,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",,Kubo Shunman,Japanese,1757,1820,1816,1816,1816,Polychrome woodblock print (surimono); ink and color on paper,5 9/16 x 5 1/2 in. (14.1 x 14 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54471,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1932,false,true,54472,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",,Kubo Shunman,Japanese,1757,1820,1815,1815,1815,Polychrome woodblock print (surimono); ink and color on paper,5 1/2 x 6 5/8 in. (14 x 16.8 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54472,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1951,false,true,54519,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",,Kubo Shunman,Japanese,1757,1820,probably 1820,1820,1820,Polychrome woodblock print (surimono); ink and color on paper,7 7/8 x 7 in. (20 x 17.8 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54519,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1953,false,true,54521,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",,Kubo Shunman,Japanese,1757,1820,probably 1810,1810,1810,Polychrome woodblock print (surimono); ink and color on paper,8 1/4 x 7 1/4 in. (21 x 18.4 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54521,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1954,false,true,54523,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",,Kubo Shunman,Japanese,1757,1820,1813,1813,1813,Polychrome woodblock print (surimono); ink and color on paper,7 7/8 x 7 1/8 in. (20 x 18.1 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54523,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1971,false,true,54556,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",(?),Kubo Shunman,Japanese,1757,1820,1814,1814,1814,Polychrome woodblock print (surimono); ink and color on paper,8 1/4 x 7 1/4 in. (21 x 18.4 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54556,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2035,false,true,54801,Asian Art,Print,"『春雨集』 摺物帖窪俊満画 『鎌倉志』 「影向石」|Spring Rain Collection (Harusame shū), vol. 1: “Offering Incense to the Deity of the Stone” (Yōgōishi), from the series History of Kamakura (Kamakura shi)",Japan,Edo period (1615–1868),,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",,Kubo Shunman,Japanese,1757,1820,early to mid-1810s,1810,1816,Privately published woodblock prints (surimono) mounted in an album; ink and color on paper,7 7/8 x 7 1/8 in. (20 x 18.1 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54801,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2038,false,true,54809,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",,Kubo Shunman,Japanese,1757,1820,early to mid-1810s,1810,1816,Privately published woodblock prints (surimono) mounted in an album; ink and color on paper,7 11/16 x 7 3/16 in. (19.5 x 18.3 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54809,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2050,false,true,54823,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",,Kubo Shunman,Japanese,1757,1820,1814,1814,1814,Polychrome woodblock print (surimono); ink and color on paper,8 5/16 x 11 3/16 in. (21.1 x 28.4 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54823,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2052,false,true,54826,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",,Kubo Shunman,Japanese,1757,1820,probably 1816,1816,1816,Polychrome woodblock print (surimono); ink and color on paper,5 7/16 x 7 3/16 in. (13.8 x 18.3 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54826,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2113,false,true,54965,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",,Kubo Shunman,Japanese,1757,1820,1808,1808,1808,Polychrome woodblock print (surimono); ink and color on paper,5 1/2 x 7 1/2 in. (14 x 19.1 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54965,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2139,false,true,54994,Asian Art,Print,"『浅草側いせ暦』 節分の悪霊ばらい『春雨集』 摺物帖 |“Beans for Tossing During Setsubun Exorcism Ceremony,” from the series Ise Calendars for the Asakusa Group (Asakusa-gawa Ise goyomi)From the Spring Rain Collection (Harusame shū), vol. 2",Japan,Edo period (1615–1868),,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",(?),Kubo Shunman,Japanese,1757,1820,1810s,1810,1819,Polychrome woodblock print (surimono); ink and color on paper,8 5/16 x 7 3/8 in. (21.1 x 18.7 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54994,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2140,false,true,54995,Asian Art,Print,"『浅草側いせ暦』 文房具と梅熨斗『春雨集』 摺物帖 |“Desk with Writing Set and Plum Flowers,” from the series Ise Calendars for the Asakusa Group (Asakusa-gawa Ise goyomi)From the Spring Rain Collection (Harusame shū), vol. 2",Japan,Edo period (1615–1868),,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",(?),Kubo Shunman,Japanese,1757,1820,probably 1814 (Year of the Dog),1814,1814,Polychrome woodblock print (surimono); ink and color on paper,8 1/4 x 7 3/8 in. (21 x 18.7 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54995,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2141,false,true,54996,Asian Art,Print,"『名物革仝印籠仝根付』 人形手金唐革 菖蒲革『春雨集』 摺物帖|“Gold-decorated Leather with Figure of a Chinese Boy” and “Patterned Leather,” from the series Famous Leathers, Inrō, and Netsuke Meibutsu kawa, inrō, netsuke)From the Spring Rain Collection (Harusame shū), vol. 2",Japan,Edo period (1615–1868),,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",,Kubo Shunman,Japanese,1757,1820,1810s,1810,1819,Polychrome woodblock print (surimono); ink and color on paper,5 1/2 x 7 1/8 in. (14 x 18.1 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54996,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2142,false,true,54997,Asian Art,Print,"『名物革仝印籠仝根付』 印籠根付『春雨集』 摺物帖|“Inrō and Netsuke,” from the series Famous Leathers, Inrō, and Netsuke (Meibutsu kawa, inrō, netsuke)From the Spring Rain Collection (Harusame shū), vol. 2",Japan,Edo period (1615–1868),,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",,Kubo Shunman,Japanese,1757,1820,1810s,1810,1819,Polychrome woodblock print (surimono); ink and color on paper,5 7/16 x 7 1/4 in. (13.8 x 18.4 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54997,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2143,false,true,54998,Asian Art,Print,"『浅草側いせ暦』 弓道具『春雨集』 摺物帖|“Bow, Arrows, Target, and Other Outfits for Archery,” from the series Ise Calendars for the Asakusa Group (Asakusa-gawa Ise goyomi)From the Spring Rain Collection (Harusame shū), vol. 2",Japan,Edo period (1615–1868),,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",(?),Kubo Shunman,Japanese,1757,1820,ca. 1814,1804,1824,Polychrome woodblock print (surimono); ink and color on paper,8 1/4 x 7 5/16 in. (21 x 18.6 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54998,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2152,false,true,55056,Asian Art,Print,"『春雨集』 摺物帖窪俊満画 胡蝶舞の衣装|Spring Rain Collection (Harusame shū), vol. 2: Costume for the Butterfly Dance (Kochō no mai)",Japan,Edo period (1615–1868),,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",,Kubo Shunman,Japanese,1757,1820,1810s,1810,1819,Privately published woodblock prints (surimono) mounted in an album; ink and color on paper,8 5/16 x 7 1/4 in. (21.1 x 18.4 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55056,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2155,false,true,55062,Asian Art,Print,"『春雨集』 摺物帖窪俊満画 クサボタン、萩、シャ ガ、ツバキ、モチツバキ|Spring Rain Collection (Harusame shū), vol. 2: Cut Flowers: Clematis, Bush Clover, Iris, Camellia, and Azalea",Japan,Edo period (1615–1868),,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",,Kubo Shunman,Japanese,1757,1820,1815 (Year of the Ox),1815,1815,Privately published woodblock prints (surimono) mounted in an album; ink and color on paper,8 5/16 x 11 1/16 in. (21.1 x 28.1 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55062,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2179,false,true,55093,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",,Kubo Shunman,Japanese,1757,1820,probably 1816,1816,1816,Polychrome woodblock print (surimono); ink and color on paper,8 1/4 x 5 3/8 in. (21 x 13.7 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55093,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2197,false,true,55114,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",,Kubo Shunman,Japanese,1757,1820,1813,1813,1813,Polychrome woodblock print (surimono); ink and color on paper,7 7/8 x 10 13/16 in. (20 x 27.5 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55114,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2214,false,true,53980,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",(?),Kubo Shunman,Japanese,1757,1820,1815,1815,1815,Polychrome woodblock print (surimono); ink and color on paper,8 7/16 x 11 1/4 in. (21.4 x 28.6 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/53980,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2291,false,true,54064,Asian Art,Print,"石橋物|Two Dancers Performing a “Shakkyōmono” Kabuki Dance, from Spring Rain Surimono Album (Harusame surimono-jō), vol. 3",Japan,Edo period (1615–1868),,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",,Kubo Shunman,Japanese,1757,1820,ca. 1805–10,1795,1820,Privately published polychrome woodblock prints (surimono) mounted in an album; ink and color on paper,8 5/16 x 5 1/2 in. (21.1 x 14 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54064,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2292,false,true,54065,Asian Art,Print,"『鳥合』 桜草に雲雀|Skylarks and Primroses,” from the Series An Array of Birds (Tori awase), from Spring Rain Surimono Album (Harusame surimono-jō, vol. 3)",Japan,Edo period (1615–1868),,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",,Kubo Shunman,Japanese,1757,1820,ca. 1805–10,1800,1820,Privately published polychrome woodblock prints (surimono) mounted in an album; ink and color on paper,8 1/4 x 5 3/8 in. (21 x 13.7 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54065,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2293,false,true,54066,Asian Art,Woodblock print,"『鳥合』 桃花に目白|Japanese White-eyes on a Branch of Peach Tree,” from the Series An Array of Birds (Tori awase), from Spring Rain Surimono Album (Harusame surimono-jō, vol. 3)",Japan,Edo period (1615–1868),,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",,Kubo Shunman,Japanese,1757,1820,ca. 1805–10,1800,1820,Privately published polychrome woodblock prints (surimono) mounted in an album; ink and color on paper,8 3/16 x 5 3/8 in. (20.8 x 13.7 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54066,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2294,false,true,54067,Asian Art,Woodblock print,"梅と柳に目白|Japanese White-eyes with Plum Tree and Willow, from Spring Rain Surimono Album (Harusame surimono-jō, vol. 3)",Japan,Edo period (1615–1868),,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",(?),Kubo Shunman,Japanese,1757,1820,ca. 1810,1800,1820,Privately published polychrome woodblock prints (surimono) mounted in an album; ink and color on paper,8 1/4 x 7 5/16 in. (21 x 18.6 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54067,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2318,false,true,54102,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",,Kubo Shunman,Japanese,1757,1820,"1816, year of the rat",1816,1816,Part of an album of woodblock prints (surimono); ink and color on paper,5 1/2 x 8 1/4 in. (14 x 21 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54102,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2340,false,true,54125,Asian Art,Print,舞楽|Courtier Playing a Flute to Accompany a Bugaku Dance,Japan,Edo period (1615–1868),,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",,Kubo Shunman,Japanese,1757,1820,probably 1810,1810,1810,Part of an album of woodblock prints (surimono); ink and color on paper,5 1/2 x 8 3/16 in. (14 x 20.8 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54125,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2012.248.1,false,true,77872,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",,Kubo Shunman,Japanese,1757,1820,1789,1789,1789,Polychrome woodblock print (surimono); ink and color on paper,Image: 4 1/8 x 6 1/2 in. (10.5 x 16.5 cm),"Purchase, Marjorie H. Holden Gift, 2012",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/77872,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2012.248.2,false,true,77873,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",,Kubo Shunman,Japanese,1757,1820,1793,1793,1793,Polychrome woodblock print (surimono); ink and color on paper,Image: 6 5/8 x 10 in. (16.8 x 25.4 cm),"Purchase, Marjorie H. Holden Gift, 2012",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/77873,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2012.248.3,false,true,77874,Asian Art,Print,書初め図|Young Woman Writing Calligraphy,Japan,Edo period (1615–1868),,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",,Kubo Shunman,Japanese,1757,1820,1793 (Year of the Goat),1793,1793,Polychrome woodblock print (surimono); ink and color on paper,Image: 4 1/4 x 6 5/8 in. (10.8 x 16.8 cm),"Purchase, Marjorie H. Holden Gift, 2012",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/77874,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2012.248.4,false,true,77875,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",,Kubo Shunman,Japanese,1757,1820,"1794, year of the tiger",1794,1794,Polychrome woodblock print (surimono); ink and color on paper,Image: 5 3/8 x 6 1/2 in. (13.7 x 16.5 cm),"Purchase, Marjorie H. Holden Gift, 2012",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/77875,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2012.248.5,false,true,77876,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",,Kubo Shunman,Japanese,1757,1820,"1795, year of the rabbit",1795,1795,Polychrome woodblock print (surimono); ink and color on paper,Image: 5 3/8 x 11 in. (13.7 x 27.9 cm),"Purchase, Marjorie H. Holden Gift, 2012",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/77876,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2012.248.6,false,true,77877,Asian Art,Print,女官図|Court Woman at her Desk with Poem Cards,Japan,Edo period (1615–1868),,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",,Kubo Shunman,Japanese,1757,1820,1795 (Year of the Rabbit),1795,1795,Polychrome woodblock print (surimono); ink and color on paper,Image: 5 3/8 x 7 1/4 in. (13.7 x 18.4 cm),"Purchase, Marjorie H. Holden Gift, 2012",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/77877,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2012.248.9,false,true,77880,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",,Kubo Shunman,Japanese,1757,1820,"1795, year of the rabbit",1795,1795,Polychrome woodblock print (surimono); ink and color on paper,Image: 4 x 5 3/4 in. (10.2 x 14.6 cm),"Purchase, Marjorie H. Holden Gift, 2012",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/77880,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"JP2821a, b",false,true,57074,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",,Kubo Shunman,Japanese,1757,1820,1757–1820,1757,1820,Polychrome woodblock print; ink and color on paper,A. 15 5/16 x 10 6/16 in. (38.9 x 26.4 cm); B. 15 1/2 x 10 5/16 in. (38.9 x 26.4 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/57074,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2012.248.10,false,true,77881,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",,Kubo Shunman,Japanese,1757,1820,"1795, year of the rabbit",1795,1795,Polychrome woodblock print (surimono); ink and color on paper,Image: 4 1/8 x 7 1/8 in. (10.5 x 18.1 cm),"Purchase, Marjorie H. Holden Gift, 2012",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/77881,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2012.248.32,false,true,77903,Asian Art,Print,遊女と詩人|Courtesan with Client before a Tokonoma Alcove,Japan,Edo period (1615–1868),,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",,Kubo Shunman,Japanese,1757,1820,1798,1798,1798,Polychrome woodblock print (surimono); ink and color on paper,Image: 5 1/2 x 11 in. (14 x 27.9 cm),"Purchase, Marjorie H. Holden Gift, 2012",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/77903,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP937,false,true,54808,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Kitao Masanobu (Santō Kyōden),"Japanese, 1761–1816",,Kitao Masanobu (Santō Kyōden),Japanese,1761,1816,ca. 1783,1773,1793,Polychrome woodblock print; ink and color on paper,H. 10 1/8 in. (25.7 cm); W. 7 1/4 in. (18.4 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54808,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2796,false,true,57093,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kitao Masanobu (Santō Kyōden),"Japanese, 1761–1816",,Kitao Masanobu (Santō Kyōden),Japanese,1761,1816,1761–1816,1761,1816,Polychrome woodblock print; ink and color on paper,10 3/8 x 8 3/4 in. (26.4 x 22.2 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/57093,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP174,false,true,36652,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shun'ei,"Japanese, 1762–1819",,Katsukawa Shun'ei,Japanese,1762,1819,ca. 1796,1786,1806,Polychrome woodblock print; ink and color on paper,12 11/32 x 5 3/8 in. (31.4 x 13.7 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36652,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP301,false,true,36769,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shun'ei,"Japanese, 1762–1819",,Katsukawa Shun'ei,Japanese,1762,1819,ca. 1792?,1782,1802,Polychrome woodblock print; ink and color on paper,12 3/4 x 5 5/8 in. (32.4 x 14.3 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36769,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP302,false,true,36770,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shun'ei,"Japanese, 1762–1819",,Katsukawa Shun'ei,Japanese,1762,1819,ca. 1787,1777,1797,Polychrome woodblock print; ink and color on paper,11 15/16 x 5 5/16 in. (30.3 x 13.5 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36770,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP303,false,true,36771,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shun'ei,"Japanese, 1762–1819",,Katsukawa Shun'ei,Japanese,1762,1819,"2nd month, 1792",1792,1792,Polychrome woodblock print; ink and color on paper,12 11/32 x 5 5/8 in. (31.4 x 14.3 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36771,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP304,false,true,36772,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shun'ei,"Japanese, 1762–1819",,Katsukawa Shun'ei,Japanese,1762,1819,ca. 1793?,1783,1883,Polychrome woodblock print; ink and color on paper,12 7/32 x 5 1/2 in. (31.1 x 14.0 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36772,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP305,false,true,36773,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shun'ei,"Japanese, 1762–1819",,Katsukawa Shun'ei,Japanese,1762,1819,ca. 1792–93,1782,1803,Polychrome woodblock print; ink and color on paper,12 5/8 x 5 2/3 in. (32.1 x 14.4 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36773,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP306,false,true,36774,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shun'ei,"Japanese, 1762–1819",,Katsukawa Shun'ei,Japanese,1762,1819,ca. 1793?,1783,1803,Polychrome woodblock print; ink and color on paper,11 1/2 x 5 5/16 in. (29.2 x 13.5 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36774,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP307,false,true,36775,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shun'ei,"Japanese, 1762–1819",,Katsukawa Shun'ei,Japanese,1762,1819,ca. 1791,1781,1801,Polychrome woodblock print; ink and color on paper,12 7/16 x 5 17/32 in. (31.6 x 14.1 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36775,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP308,false,true,36776,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shun'ei,"Japanese, 1762–1819",,Katsukawa Shun'ei,Japanese,1762,1819,ca. 1795,1785,1805,Polychrome woodblock print; ink and color on paper,12 3/4 x 5 5/8 in. (32.4 x 14.3 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36776,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP309,false,true,36777,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shun'ei,"Japanese, 1762–1819",,Katsukawa Shun'ei,Japanese,1762,1819,ca. 1798,1788,1808,Polychrome woodblock print; ink and color on paper,12 x 5 1/2 in. (30.5 x 14.0 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36777,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP310,false,true,36778,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shun'ei,"Japanese, 1762–1819",,Katsukawa Shun'ei,Japanese,1762,1819,1787,1787,1787,Polychrome woodblock print; ink and color on paper,12 x 5 1/2 in. (30.5 x 14.0 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36778,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP311,false,true,36779,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shun'ei,"Japanese, 1762–1819",,Katsukawa Shun'ei,Japanese,1762,1819,ca. 1792?,1782,1804,Polychrome woodblock print; ink and color on paper,12 7/32 x 5 1/2 in. (31.1 x 14.0 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36779,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP312,false,true,36780,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shun'ei,"Japanese, 1762–1819",,Katsukawa Shun'ei,Japanese,1762,1819,ca. 1794?,1784,1804,Polychrome woodblock print; ink and color on paper,12 5/16 x 5 1/2 in. (31.3 x 14.0 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36780,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP313,false,true,36781,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shun'ei,"Japanese, 1762–1819",,Katsukawa Shun'ei,Japanese,1762,1819,"2nd month, 1789",1789,1789,Polychrome woodblock print; ink and color on paper,12 5/8 x 5 7/8 in. (32.1 x 14.9 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36781,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP314,false,true,36782,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shun'ei,"Japanese, 1762–1819",,Katsukawa Shun'ei,Japanese,1762,1819,ca. 1781,1771,1791,One sheet of a triptych of polychrome woodblock prints; ink and color on paper,12 1/2 x 5 2/3 in. (31.8 x 14.4 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36782,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP315,false,true,36783,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shun'ei,"Japanese, 1762–1819",,Katsukawa Shun'ei,Japanese,1762,1819,ca. 1793,1783,1803,One sheet of a triptych of polychrome woodblock prints; ink and color on paper,12 3/4 x 5 3/4 in. (32.4 x 14.6 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36783,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP316,false,true,36784,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shun'ei,"Japanese, 1762–1819",,Katsukawa Shun'ei,Japanese,1762,1819,ca. 1795,1785,1805,Polychrome woodblock print; ink and color on paper,12 3/4 x 5 5/8 in. (32.4 x 14.3 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36784,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP317,false,true,36785,Asian Art,Print,三代目大谷鬼次|Kabuki Actor Ōtani Oniji III as a Samurai,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shun'ei,"Japanese, 1762–1819",,Katsukawa Shun'ei,Japanese,1762,1819,ca. 1792,1782,1802,Polychrome woodblock print; ink and color on paper,12 15/16 x 5 2/3 in. (32.9 x 14.4 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36785,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP318,false,true,36786,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shun'ei,"Japanese, 1762–1819",,Katsukawa Shun'ei,Japanese,1762,1819,ca. 1793,1783,1803,Polychrome woodblock print; ink and color on paper,12 1/8 x 5 5/8 in. (30.8 x 14.3 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36786,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP319,false,true,36787,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shun'ei,"Japanese, 1762–1819",,Katsukawa Shun'ei,Japanese,1762,1819,ca. 1793,1783,1803,Polychrome woodblock print; ink and color on paper,12 7/8 x 5 3/4 in. (32.7 x 14.6 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36787,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP320,false,true,36788,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shun'ei,"Japanese, 1762–1819",,Katsukawa Shun'ei,Japanese,1762,1819,ca. 1795,1785,1805,Polychrome woodblock print; ink and color on paper,12 3/4 x 5 3/4 in. (32.4 x 14.6 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36788,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP321,false,true,36789,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shun'ei,"Japanese, 1762–1819",,Katsukawa Shun'ei,Japanese,1762,1819,December 1790,1790,1790,Polychrome woodblock print; ink and color on paper,11 9/16 x 5 5/8 in. (29.4 x 14.3 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36789,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP322,false,true,36790,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shun'ei,"Japanese, 1762–1819",,Katsukawa Shun'ei,Japanese,1762,1819,"12th month, 1788",1788,1788,Polychrome woodblock print; ink and color on paper,12 7/8 x 5 17/32 in. (32.7 x 14.1 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36790,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP323,false,true,36791,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shun'ei,"Japanese, 1762–1819",,Katsukawa Shun'ei,Japanese,1762,1819,ca. 1795,1785,1805,Polychrome woodblock print; ink and color on paper,12 3/4 x 5 3/4 in. (32.4 x 14.6 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36791,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP324,false,true,36792,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shun'ei,"Japanese, 1762–1819",,Katsukawa Shun'ei,Japanese,1762,1819,ca. 1793,1783,1803,Polychrome woodblock print; ink and color on paper,12 x 5 3/8 in. (30.5 x 13.7 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36792,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP325,false,true,36793,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shun'ei,"Japanese, 1762–1819",,Katsukawa Shun'ei,Japanese,1762,1819,ca. 1793?,1783,1803,Polychrome woodblock print; ink and color on paper,12 3/4 x 5 5/8 in. (32.4 x 14.3 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36793,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP326,false,true,36794,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shun'ei,"Japanese, 1762–1819",,Katsukawa Shun'ei,Japanese,1762,1819,ca. 1788,1778,1798,One sheet of a triptych of polychrome woodblock prints; ink and color on paper,12 5/8 x 5 3/4 in. (32.1 x 14.6 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36794,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP327,false,true,36795,Asian Art,Print,三代目坂田半五郎|Kabuki Actor Sakata Hangorō III as an Outlaw,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shun'ei,"Japanese, 1762–1819",,Katsukawa Shun'ei,Japanese,1762,1819,ca. 1791,1781,1801,Polychrome woodblock print; ink and color on paper,12 4/5 x 5 2/3 in. (32.5 x 14.4 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36795,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP328,false,true,36796,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shun'ei,"Japanese, 1762–1819",,Katsukawa Shun'ei,Japanese,1762,1819,"5th month, 1795",1795,1795,Polychrome woodblock print; ink and color on paper,12 5/8 x 5 7/16 in. (32.1 x 13.8 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36796,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP329,false,true,36797,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shun'ei,"Japanese, 1762–1819",,Katsukawa Shun'ei,Japanese,1762,1819,ca. 1793,1783,1803,Polychrome woodblock print; ink and color on paper,12 3/4 x 5 3/4 in. (32.4 x 14.6 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36797,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP330,false,true,36798,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shun'ei,"Japanese, 1762–1819",,Katsukawa Shun'ei,Japanese,1762,1819,ca. 1796,1786,1806,One sheet of a triptych of polychrome woodblock prints; ink and color on paper,12 5/8 x 5 5/8 in. (32.1 x 14.3 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36798,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP331,false,true,36799,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shun'ei,"Japanese, 1762–1819",,Katsukawa Shun'ei,Japanese,1762,1819,ca. 1795?,1785,1805,Polychrome woodblock print; ink and color on paper,12 5/16 x 5 5/8 in. (31.3 x 14.3 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36799,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP333,false,true,36801,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shun'ei,"Japanese, 1762–1819",,Katsukawa Shun'ei,Japanese,1762,1819,ca. 1790?,1780,1800,Polychrome woodblock print; ink and color on paper,12 3/4 x 5 17/32 in. (32.4 x 14.1 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36801,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP334,false,true,36802,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shun'ei,"Japanese, 1762–1819",,Katsukawa Shun'ei,Japanese,1762,1819,ca. 1789,1779,1799,Polychrome woodblock print; ink and color on paper,12 11/32 x 5 17/32 in. (31.4 x 14.1 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36802,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP335,false,true,36803,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shun'ei,"Japanese, 1762–1819",,Katsukawa Shun'ei,Japanese,1762,1819,ca. 1791?,1781,1801,One sheet of a triptych of polychrome woodblock prints; ink and color on paper,12 3/4 x 5 5/8 in. (32.4 x 14.3 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36803,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP336,false,true,36804,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shun'ei,"Japanese, 1762–1819",,Katsukawa Shun'ei,Japanese,1762,1819,ca. 1797,1787,1807,One sheet of a triptych of polychrome woodblock prints; ink and color on paper,12 1/2 x 5 2/3 in. (31.8 x 14.4 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36804,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP337,false,true,36805,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shun'ei,"Japanese, 1762–1819",,Katsukawa Shun'ei,Japanese,1762,1819,"2nd month, 1794",1794,1794,Polychrome woodblock print; ink and color on paper,11 7/8 x 5 7/16 in. (30.2 x 13.8 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36805,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP715,false,true,37163,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shun'ei,"Japanese, 1762–1819",,Katsukawa Shun'ei,Japanese,1762,1819,ca. 1788,1778,1798,Polychrome woodblock print; ink and color on paper,12 1/8 x 5 3/4 in. (30.8 x 14.6 cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37163,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP716,false,true,37164,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shun'ei,"Japanese, 1762–1819",,Katsukawa Shun'ei,Japanese,1762,1819,ca. 1799,1789,1809,Polychrome woodblock print; ink and color on paper,14 1/8 x 9 7/8 in. (35.9 x 25.1 cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37164,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP912,false,true,54652,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shun'ei,"Japanese, 1762–1819",,Katsukawa Shun'ei,Japanese,1762,1819,ca. 1790,1780,1800,Polychrome woodblock print; ink and color on paper,H. 10 5/16 in. (26.2 cm); W. 7 9/16 in. (19.2 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54652,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP913,false,true,54653,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shun'ei,"Japanese, 1762–1819",,Katsukawa Shun'ei,Japanese,1762,1819,1796,1796,1796,Polychrome woodblock print; ink and color on paper,H. 12 3/4 in. (32.4 cm); W. 5 3/4 in. (14.6 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54653,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP914,false,true,54654,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shun'ei,"Japanese, 1762–1819",,Katsukawa Shun'ei,Japanese,1762,1819,ca. 1703,1693,1713,Polychrome woodblock print; ink and color on paper,H. 12 in. (30.5 cm); W. 5 1/2 in. (14 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54654,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP915,false,true,54655,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shun'ei,"Japanese, 1762–1819",,Katsukawa Shun'ei,Japanese,1762,1819,ca. 1793,1783,1803,Polychrome woodblock print; ink and color on paper,H. 12 7/8 in. (32.7 cm); W. 5 3/4 in. (14.6 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54655,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP916,false,true,54656,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shun'ei,"Japanese, 1762–1819",,Katsukawa Shun'ei,Japanese,1762,1819,ca. 1794,1784,1804,Polychrome woodblock print; ink and color on paper,H. 14 1/2 in. (36.8 cm); W. 9 3/4 in. (24.8 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54656,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1221,false,true,55144,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shun'ei,"Japanese, 1762–1819",,Katsukawa Shun'ei,Japanese,1762,1819,ca. 1795,1785,1805,Polychrome woodblock print; ink and color on paper,H. 12 1/2 in. (31.8 cm); W. 5 3/4 in. (14.6 cm),"Rogers Fund, 1921",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55144,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1302,false,true,55241,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shun'ei,"Japanese, 1762–1819",,Katsukawa Shun'ei,Japanese,1762,1819,1794 (Kansei 6),1794,1794,Polychrome woodblock print; ink and color on paper,H. 12 1/8 in. (30.8 cm); W. 5 9/16 in. (14.1 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55241,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1347,false,true,55313,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shun'ei,"Japanese, 1762–1819",,Katsukawa Shun'ei,Japanese,1762,1819,1785,1785,1785,Polychrome woodblock print; ink and color on paper,Hosoban 12 5/16 x 5 1/2 in. (31.3 x 14 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55313,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1348,false,true,55315,Asian Art,Print,三代目瀬川菊之丞|Kabuki Actor Segawa Kikunojō III in a Female Role,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shun'ei,"Japanese, 1762–1819",,Katsukawa Shun'ei,Japanese,1762,1819,1788,1788,1788,Polychrome woodblock print; ink and color on paper,Hosoban 12 5/16 x 5 1/2 in. (31.3 x 14 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55315,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1349,false,true,55321,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shun'ei,"Japanese, 1762–1819",,Katsukawa Shun'ei,Japanese,1762,1819,1794,1794,1794,Polychrome woodblock print; ink and color on paper,12 13/16 x 5 13/16 in. (32.5 x 14.8 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55321,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1350,false,true,55322,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shun'ei,"Japanese, 1762–1819",,Katsukawa Shun'ei,Japanese,1762,1819,1789,1789,1789,Polychrome woodblock print; ink and color on paper,H. 12 1/4 in. (31.1 cm); W. 5 11/16 in. (14.4 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55322,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1354,false,true,55330,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shun'ei,"Japanese, 1762–1819",,Katsukawa Shun'ei,Japanese,1762,1819,1782,1782,1782,Polychrome woodblock print; ink and color on paper,H. 12 in. (30.5 cm); W. 5 1/4 in. (13.3 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55330,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1386,false,true,55387,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shun'ei,"Japanese, 1762–1819",,Katsukawa Shun'ei,Japanese,1762,1819,1794,1794,1794,Polychrome woodblock print; ink and color on paper,H. 15 in. (38.1 cm); W. 9 7/8 in. (25.1 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55387,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1401,false,true,45242,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shun'ei,"Japanese, 1762–1819",,Katsukawa Shun'ei,Japanese,1762,1819,ca. 1788,1778,1798,Polychrome woodblock print; uchiwa fan format; ink and color on paper,W. 10 1/4 in. (26 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45242,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1493,false,true,55588,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shun'ei,"Japanese, 1762–1819",,Katsukawa Shun'ei,Japanese,1762,1819,ca. 1780,1770,1790,Polychrome woodblock print; ink and color on paper,H. 12 in. (30.5 cm); W. 5 in. (12.7 cm),"Fletcher Fund, 1928",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55588,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1536,false,true,55701,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shun'ei,"Japanese, 1762–1819",,Katsukawa Shun'ei,Japanese,1762,1819,ca. 1793,1783,1803,Polychrome woodblock print; ink and color on paper,H. 8 in. (20.3 cm); W. 9 9/16 in. (24.3 cm),"Fletcher Fund, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55701,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1537,false,true,55702,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shun'ei,"Japanese, 1762–1819",,Katsukawa Shun'ei,Japanese,1762,1819,ca. 1792,1782,1802,Polychrome woodblock print; ink and color on paper,H. 12 1/2 in. (31.8 cm); W. 5 3/4 in. (14.6 cm),"Fletcher Fund, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55702,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1538,false,true,55703,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shun'ei,"Japanese, 1762–1819",,Katsukawa Shun'ei,Japanese,1762,1819,ca. 1790,1780,1800,Polychrome woodblock print; ink and color on paper,H. 12 9/16 in. (31.9 cm); W. 5 5/8 in. (14.3 cm),"Fletcher Fund, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55703,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1539,false,true,55704,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shun'ei,"Japanese, 1762–1819",,Katsukawa Shun'ei,Japanese,1762,1819,ca. 1793,1783,1803,Polychrome woodblock print; ink and color on paper,H. 12 7/8 in. (32.7 cm); W. 6 in. (15.2 cm),"Fletcher Fund, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55704,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1540,false,true,55705,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shun'ei,"Japanese, 1762–1819",,Katsukawa Shun'ei,Japanese,1762,1819,ca. 1795,1785,1805,Polychrome woodblock print; ink and color on paper,H. 13 1/8 in. (33.3 cm); W. 5 3/4 in. (14.6 cm),"Fletcher Fund, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55705,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2404,false,true,56807,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shun'ei,"Japanese, 1762–1819",,Katsukawa Shun'ei,Japanese,1762,1819,1789,1789,1789,Polychrome woodblock print; ink and color on paper,12 3/4 x 5 3/4 in. (32.4 x 14.6 cm),"Gift of Louis V. Ledoux, 1931",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56807,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2707,false,true,56972,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shun'ei,"Japanese, 1762–1819",,Katsukawa Shun'ei,Japanese,1762,1819,ca. 1790,1780,1810,Polychrome woodblock print; ink and color on paper,12 x 6 in. (30.5 x 15.2 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56972,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2708,false,true,56974,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shun'ei,"Japanese, 1762–1819",,Katsukawa Shun'ei,Japanese,1762,1819,ca. 1791,1781,1811,Polychrome woodblock print; ink and color on paper,12 5/6 x 5 5/8 in. (32.6 x 14.3 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56974,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2709,false,true,56975,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shun'ei,"Japanese, 1762–1819",,Katsukawa Shun'ei,Japanese,1762,1819,ca. 1790–1797,1790,1797,Polychrome woodblock print; ink and color on paper,11 3/4 x 5 3/8 in. (29.8 x 13.7 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56975,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2710,false,true,56976,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shun'ei,"Japanese, 1762–1819",,Katsukawa Shun'ei,Japanese,1762,1819,ca. 1789,1779,1799,Polychrome woodblock print; ink and color on paper,12 3/8 x 5 9/16 in. (31.4 x 14.1 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56976,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2711,false,true,56978,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shun'ei,"Japanese, 1762–1819",,Katsukawa Shun'ei,Japanese,1762,1819,ca. 1790,1780,1810,Polychrome woodblock print; ink and color on paper,12 1/8 x 5 1/2 in. (30.8 x 14 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56978,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2712,false,true,56980,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shun'ei,"Japanese, 1762–1819",,Katsukawa Shun'ei,Japanese,1762,1819,ca. 1790,1780,1810,Polychrome woodblock print; ink and color on paper,12 3/4 x 5 3/4 in. (32.4 x 14.6 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56980,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2713,false,true,56981,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shun'ei,"Japanese, 1762–1819",,Katsukawa Shun'ei,Japanese,1762,1819,ca. 1790,1780,1810,Polychrome woodblock print; ink and color on paper,12 3/8 x 5 1/2 in. (31.4 x 14 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56981,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2714,false,true,56982,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shun'ei,"Japanese, 1762–1819",,Katsukawa Shun'ei,Japanese,1762,1819,ca. 1792,1782,1802,Probably the middle sheet of a triptych or the right-hand sheet of a diptych of polychrome woodblock prints; ink and color on paper,12 3/4 x 5 5/8 in. (32.4 x 14.3 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56982,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2715,false,true,56983,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shun'ei,"Japanese, 1762–1819",,Katsukawa Shun'ei,Japanese,1762,1819,1793,1793,1793,Polychrome woodblock print; ink and color on paper,12 1/2 x 5 5/8 in. (31.8 x 14.3 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56983,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2824,false,true,45037,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shun'ei,"Japanese, 1762–1819",,Katsukawa Shun'ei,Japanese,1762,1819,ca. 1800,1790,1810,Polychrome woodblock print; ink and color on paper,15 1/4 x 10 1/4 in. (38.7 x 26 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45037,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2894,false,true,56009,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shun'ei,"Japanese, 1762–1819",,Katsukawa Shun'ei,Japanese,1762,1819,1762–1819,1762,1819,Polychrome woodblock print; ink and color on paper,12 1/2 x 5 5/8 in. (31.8 x 14.3 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56009,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2895,false,true,56010,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shun'ei,"Japanese, 1762–1819",,Katsukawa Shun'ei,Japanese,1762,1819,1762–1819,1762,1819,Polychrome woodblock print; ink and color on paper,11 7/8 x 5 1/4 in. (30.2 x 13.3 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56010,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2897,false,true,56012,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shun'ei,"Japanese, 1762–1819",,Katsukawa Shun'ei,Japanese,1762,1819,1762–1819,1762,1819,Polychrome woodblock print; ink and color on paper,12 3/4 x 6 in. (32.4 x 15.2 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56012,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2898,false,true,56013,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shun'ei,"Japanese, 1762–1819",,Katsukawa Shun'ei,Japanese,1762,1819,1762–1819,1762,1819,Polychrome woodblock print; ink and color on paper,12 x 5 1/4 in. (30.5 x 13.3 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56013,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2899,false,true,56014,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shun'ei,"Japanese, 1762–1819",,Katsukawa Shun'ei,Japanese,1762,1819,1762–1819,1762,1819,Polychrome woodblock print; ink and color on paper,12 x 5 3/8 in. (30.5 x 13.7 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56014,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"JP338a, b",false,true,36476,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shun'ei,"Japanese, 1762–1819",,Katsukawa Shun'ei,Japanese,1762,1819,ca. 1792,1782,1802,Diptych of polychrome woodblock prints; ink and color on paper,A: H. 12 13/16 (32.5 cm); W. 5 11/16 in. (14.4 cm) B: H. 13 in. (33 cm); W. 5 3/4 in. (14.6 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36476,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"JP340a, b",false,true,36474,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shun'ei,"Japanese, 1762–1819",,Katsukawa Shun'ei,Japanese,1762,1819,ca. 1793,1783,1803,Diptych of polychrome woodblock prints; ink and color on paper,Overall: H. 12 9/16 in. (31.9 cm); W. 11 1/2 in. (29.2 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36474,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP339a–c,false,true,36475,Asian Art,Woodblock prints,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shun'ei,"Japanese, 1762–1819",,Katsukawa Shun'ei,Japanese,1762,1819,ca. 1793,1783,1803,Triptych of polychrome woodblock prints; ink and color on paper,A: H. 12 3/8 in. (31.4 cm); W. 5 1/2 in. (14 cm) B: H. 12 5/8 in. (32.1 cm); W. 5 5/8 in. (14.3 cm) C: H. 13 in. (33 cm); W. 5 7/8 in. (14.9 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36475,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2896a–c,false,true,56011,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shun'ei,"Japanese, 1762–1819",,Katsukawa Shun'ei,Japanese,1762,1819,1762–1819,1762,1819,Triptych of polychrome woodblock prints; ink and color on paper,A: 12 3/4 x 5 5/8 in. (32.4 x 14.3 cm) B: 12 3/4 x 5 3/4 in. (32.4 x 14.6 cm) C: 12 3/4 x 5 3/4 in. (32.4 x 14.6 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56011,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1005,false,true,54881,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Toyohiro,"Japanese, 1763–1828",,Utagawa Toyohiro,Japanese,1763,1828,ca. 1804,1794,1814,Two sheets of a pentaptych of polychrome woodblock prints; ink and color on paper,Aiban; H. 13 3/8 in. (34 cm); W. 19 1/2 in. (49.5 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54881,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1006,false,true,54882,Asian Art,Print,"Horinouchi Myōhōji Eho Mairi no Zu|Pilgrimage to Myōhōji in Horinouchi, Edo",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Toyohiro,"Japanese, 1763–1828",,Utagawa Toyohiro,Japanese,1763,1828,ca. 1804,1794,1814,Two sheets of a pentaptych of polychrome woodblock prints; ink and color on paper,H. 14 3/8 in. (36.5 cm); W. 19 in. (48.3 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54882,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1007,false,true,54883,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Toyohiro,"Japanese, 1763–1828",,Utagawa Toyohiro,Japanese,1763,1828,ca. 1805,1795,1815,One sheet of a triptych(?)of polychrome woodblock prints; ink and color on paper,H. 15 in. (38.1 cm); W. 10 1/4 in. (26 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54883,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1009,false,true,54885,Asian Art,Print,Tsurigitsune|Trapping the Fox,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Toyohiro,"Japanese, 1763–1828",,Utagawa Toyohiro,Japanese,1763,1828,1808,1808,1808,Polychrome woodblock print; ink and color on paper,H. 14 in. (35.6 cm); W. 9 1/4 in. (23.5 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54885,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2718,false,true,56986,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Toyohiro,"Japanese, 1763–1828",,Utagawa Toyohiro,Japanese,1763,1828,ca. 1802,1792,1812,Probably one sheet of a triptych of polychrome woodblock prints; ink and color on paper,12 3/8 x 8 3/8 in. (31.4 x 21.3 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56986,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP193,false,true,36670,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kuwagata Keisai,"Japanese, 1764–1824",,Kuwagata Keisai,Japanese,1764,1824,ca. 1790,1780,1800,Polychrome woodblock print; ink and color on paper,10 x 7 7/32 in. (25.4 x 18.4 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36670,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP527,false,true,36978,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kuwagata Keisai,"Japanese, 1764–1824",,Kuwagata Keisai,Japanese,1764,1824,1789,1789,1789,Polychrome woodblock print; ink and color on paper,10 x 14 7/8 in. (25.4 x 37.8 cm),"Rogers Fund, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36978,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP528,false,true,36979,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Kuwagata Keisai,"Japanese, 1764–1824",,Kuwagata Keisai,Japanese,1764,1824,1789,1789,1789,Polychrome woodblock print; ink and color on paper,10 x 14 7/8 in. (25.4 x 37.8 cm),"Rogers Fund, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36979,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP529,false,true,36980,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kuwagata Keisai,"Japanese, 1764–1824",,Kuwagata Keisai,Japanese,1764,1824,1789,1789,1789,Polychrome woodblock print; ink and color on paper,10 x 14 7/8 in. (25.4 x 37.8 cm),"Rogers Fund, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36980,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP530,false,true,36981,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kuwagata Keisai,"Japanese, 1764–1824",,Kuwagata Keisai,Japanese,1764,1824,1789,1789,1789,Polychrome woodblock print; ink and color on paper,10 x 14 7/8 in. (25.4 x 37.8 cm),"Rogers Fund, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36981,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP531,false,true,36982,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kuwagata Keisai,"Japanese, 1764–1824",,Kuwagata Keisai,Japanese,1764,1824,1789,1789,1789,Polychrome woodblock print; ink and color on paper,10 x 14 7/8 in. (25.4 x 37.8 cm),"Rogers Fund, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36982,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP532,false,true,36983,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kuwagata Keisai,"Japanese, 1764–1824",,Kuwagata Keisai,Japanese,1764,1824,1789,1789,1789,Polychrome woodblock print; ink and color on paper,10 x 14 7/8 in. (25.4 x 37.8 cm),"Rogers Fund, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36983,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP533,false,true,36984,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kuwagata Keisai,"Japanese, 1764–1824",,Kuwagata Keisai,Japanese,1764,1824,1789,1789,1789,Polychrome woodblock print; ink and color on paper,10 x 14 7/8 in. (25.4 x 37.8 cm),"Rogers Fund, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36984,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP534,false,true,36985,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kuwagata Keisai,"Japanese, 1764–1824",,Kuwagata Keisai,Japanese,1764,1824,1789,1789,1789,Polychrome woodblock print; ink and color on paper,10 x 14 7/8 in. (25.4 x 37.8 cm),"Rogers Fund, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36985,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP535,false,true,36986,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kuwagata Keisai,"Japanese, 1764–1824",,Kuwagata Keisai,Japanese,1764,1824,1789,1789,1789,Polychrome woodblock print; ink and color on paper,10 x 14 7/8 in. (25.4 x 37.8 cm),"Rogers Fund, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36986,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP536,false,true,36987,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kuwagata Keisai,"Japanese, 1764–1824",,Kuwagata Keisai,Japanese,1764,1824,1789,1789,1789,Polychrome woodblock print; ink and color on paper,10 x 14 7/8 in. (25.4 x 37.8 cm),"Rogers Fund, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36987,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP537,false,true,36988,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kuwagata Keisai,"Japanese, 1764–1824",,Kuwagata Keisai,Japanese,1764,1824,1789,1789,1789,Polychrome woodblock print; ink and color on paper,10 x 14 7/8 in. (25.4 x 37.8 cm),"Rogers Fund, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36988,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP538,false,true,36989,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kuwagata Keisai,"Japanese, 1764–1824",,Kuwagata Keisai,Japanese,1764,1824,1789,1789,1789,Polychrome woodblock print; ink and color on paper,10 x 14 7/8 in. (25.4 x 37.8 cm),"Rogers Fund, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36989,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP938,false,true,45243,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kuwagata Keisai,"Japanese, 1764–1824",,Kuwagata Keisai,Japanese,1764,1824,ca. 1790,1780,1800,Polychrome woodblock print; ink and color on paper,H. 8 5/8 in. (21.9 cm); W. 6 3/8 in. (16.2 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45243,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3053,false,true,56508,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kuwagata Keisai,"Japanese, 1764–1824",,Kuwagata Keisai,Japanese,1764,1824,ca. 1780,1770,1790,Polychrome woodblock print; ink and color on paper,11 5/8 x 16 5/8 in. (29.5 x 42.2 cm),"Gift of Mrs. Morris Manges, in memory of her husband, Dr. Morris Manges, 1947",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56508,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP200,false,true,36677,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Toyokuni I,"Japanese, 1769–1825",,Utagawa Toyokuni I,Japanese,1769,1825,ca. 1797,1787,1807,Pentaptych of polychrome woodblock prints; ink and color on paper,15 7/32 x 50 1/2 in. (38.7 x 128.3 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36677,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP201,false,true,36678,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Toyokuni I,"Japanese, 1769–1825",,Utagawa Toyokuni I,Japanese,1769,1825,ca. 1791,1781,1801,Triptych of polychrome woodblock prints; ink and color on paper,15 3/8 x 29 3/4 in. (39.1 x 75.6 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36678,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP203,false,true,36680,Asian Art,Print,雪こかし|Courtesans and Attendants Playing in the Snow,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Toyokuni I,"Japanese, 1769–1825",,Utagawa Toyokuni I,Japanese,1769,1825,ca. 1796,1786,1806,Triptych of polychrome woodblock prints; ink and color on paper,14 15/32 x 29 15/32 in. (36.8 x 74.9 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36680,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP204,false,true,36681,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Toyokuni I,"Japanese, 1769–1825",,Utagawa Toyokuni I,Japanese,1769,1825,ca. 1791,1781,1801,Triptych of polychrome woodblock prints; ink and color on paper,15 5/8 x 30 5/8 in. (39.7 x 77.8 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36681,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP235,false,true,36707,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Toyokuni I,"Japanese, 1769–1825",,Utagawa Toyokuni I,Japanese,1769,1825,ca. 1789,1779,1799,Polychrome woodblock print; ink and color on paper,10 1/8 x 7 5/8 in. (25.7 x 19.4 cm),"Rogers Fund, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36707,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP664,false,true,37111,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Toyokuni I,"Japanese, 1769–1825",,Utagawa Toyokuni I,Japanese,1769,1825,ca. 1801,1791,1821,Two sheets of a triptych of polychrome woodblock prints; ink and color on paper,14 1/8 x 19 15/32 in. (35.9 x 49.5 cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37111,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP665,false,true,37112,Asian Art,Woodblock prints,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Toyokuni I,"Japanese, 1769–1825",,Utagawa Toyokuni I,Japanese,1769,1825,ca. 1800,1790,1810,Triptych of polychrome woodblocks print; ink and color on paper,15 1/2 x 30 1/8 in. (39.4 x 76.5 cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37112,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1003,false,true,54880,Asian Art,Print,吉原仲の町花魁道中|Courtesans Promenading on the Nakanochō in Yoshiwara,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Toyokuni I,"Japanese, 1769–1825",,Utagawa Toyokuni I,Japanese,1769,1825,ca. 1795,1785,1805,Triptych of polychrome woodblock prints; ink and color on paper,H. 15 3/8 in. (39.1 cm); W. 50 in. (127 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54880,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1004,false,true,45226,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Toyokuni I,"Japanese, 1769–1825",,Utagawa Toyokuni I,Japanese,1769,1825,1796,1615,1868,Polychrome woodblock print; ink and color on paper,H. 15 in. (38.1 cm); W. 10 1/8 in. (25.7 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45226,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1119,false,true,55041,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Toyokuni I,"Japanese, 1769–1825",,Utagawa Toyokuni I,Japanese,1769,1825,1798–99,1798,1799,Polychrome woodblock print; ink and color on paper,H. 15 in. (38.1 cm); W. 9 5/8 in. (24.4 cm),"Rogers Fund, 1919",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55041,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1371,false,true,55350,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Toyokuni I,"Japanese, 1769–1825",,Utagawa Toyokuni I,Japanese,1769,1825,ca. 1798,1788,1808,Polychrome woodblock print; ink and color on paper,H. 14 5/8 in. (37.1 cm); W. 9 7/8 in. (25.1 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55350,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1387,false,true,55389,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Toyokuni I,"Japanese, 1769–1825",,Utagawa Toyokuni I,Japanese,1769,1825,ca. 1798,1788,1808,Polychrome woodblock print; ink and color on paper,H. 14 1/4 in. (36.2 cm); W. 10 1/4 in. (26 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55389,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1388,false,true,55394,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Toyokuni I,"Japanese, 1769–1825",,Utagawa Toyokuni I,Japanese,1769,1825,ca. 1798,1788,1808,Polychrome woodblock print; ink and color on paper,H. 15 1/4 in. (38.7 cm); W. 10 1/4 in. (26 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55394,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1389,false,true,55395,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Toyokuni I,"Japanese, 1769–1825",,Utagawa Toyokuni I,Japanese,1769,1825,1798,1798,1798,Polychrome woodblock print; ink and color on paper,H. 12 1/2 in. (31.8 cm); W. 5 5/8 in. (14.3 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55395,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1390,false,true,55397,Asian Art,Print,"『祇園神輿洗 ねり物姿』「いろは歌の売」|“The Geisha To’e as a Vendor of Poems,” from the series Gion Festival Costume Parade (Gion mikoshi arai nerimono sugata)",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Toyokuni I,"Japanese, 1769–1825",,Utagawa Toyokuni I,Japanese,1769,1825,ca. 1795,1785,1805,Polychrome woodblock print; ink and color on paper,H. 13 1/4 in. (33.7 cm); W. 6 1/4 in. (15.9 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55397,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1391,false,true,55398,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Toyokuni I,"Japanese, 1769–1825",,Utagawa Toyokuni I,Japanese,1769,1825,ca. 1810,1800,1820,Polychrome woodblock print; ink and color on paper,H. 14 1/4 in. (36.2 cm); W. 9 3/4 in. (24.8 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55398,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1407,false,true,55420,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Toyokuni I,"Japanese, 1769–1825",,Utagawa Toyokuni I,Japanese,1769,1825,1804,1804,1804,Polychrome woodblock print; ink and color on paper,H. 15 1/8 in. (38.4 cm); W. 10 3/16 in. (25.9 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55420,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1408,false,true,55421,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Toyokuni I,"Japanese, 1769–1825",,Utagawa Toyokuni I,Japanese,1769,1825,ca. 1804,1794,1814,Polychrome woodblock print; ink and color on paper,H. 15 1/4 in. (38.7 cm); W. 9 7/8 in. (25.1 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55421,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1492,false,true,45290,Asian Art,Print,"Tokaido Yotsuya Kaidan|Onoe Matsusuke as the Ghost of the Murdered Wife Oiwa, in ""A Tale of Horror from the Yotsuya Station on the Tokaido Road""",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Toyokuni I,"Japanese, 1769–1825",,Utagawa Toyokuni I,Japanese,1769,1825,1812,1615,1868,Polychrome woodblock print; ink and color on paper,H. 14 3/4 in. (37.5 cm); W. 10 1/8 in. (25.7 cm),"Gift of Louis V. Ledoux, 1927",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45290,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1529,false,true,55640,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Toyokuni I,"Japanese, 1769–1825",,Utagawa Toyokuni I,Japanese,1769,1825,1798 (Kansei 9),1798,1798,Polychrome woodblock print; ink and color on paper,H. 14 1/4 in. (36.2 cm); W. 9 1/2 in. (24.1 cm),"Fletcher Fund, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55640,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1530,false,true,55641,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Toyokuni I,"Japanese, 1769–1825",,Utagawa Toyokuni I,Japanese,1769,1825,ca. 1795,1785,1805,Polychrome woodblock print; ink and color on paper; mica background,14 3/4 x 10 in. (37.5 x 25.4cm),"Fletcher Fund, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55641,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1531,false,true,55644,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Toyokuni I,"Japanese, 1769–1825",,Utagawa Toyokuni I,Japanese,1769,1825,ca. 1794,1784,1804,Polychrome woodblock print; ink and color on paper,15 3/8 x 10 1/8 in. (39.1 x 25.7cm),"Fletcher Fund, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55644,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1570,false,true,55747,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Toyokuni I,"Japanese, 1769–1825",,Utagawa Toyokuni I,Japanese,1769,1825,ca. 1797,1787,1807,Triptych of polychrome woodblock prints; ink and color on paper,H. 14 1/2 in. (36.8 cm); W. 9 7/8 in. (25.1 cm),"Fletcher Fund, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55747,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1743,false,true,56050,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Toyokuni I,"Japanese, 1769–1825",,Utagawa Toyokuni I,Japanese,1769,1825,ca. 1795,1785,1805,Polychrome woodblock print; ink and color on paper,H. 15 1/4 in. (38.7 cm); W. 10 1/8 in. (25.7 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56050,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1747,false,true,56054,Asian Art,Print,南四季 夏景|The Four Seasons in Southern Edo: A Summer Scene (Minami shiki; Natsu [no] kei),Japan,Edo period (1615–1868),,,,Artist,,Utagawa Toyokuni I,"Japanese, 1769–1825",,Utagawa Toyokuni I,Japanese,1769,1825,late 1780s,1787,1789,Right and center sheet of a triptych of polychrome woodblock prints; ink and color on paper,Image (a): 14 3/8 x 10 1/8 in. (36.5 x 25.7 cm) Image (b): 14 1/2 x 9 15/16 in. (36.8 x 25.2 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56054,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1749,false,true,56056,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Toyokuni I,"Japanese, 1769–1825",,Utagawa Toyokuni I,Japanese,1769,1825,ca. 1800,1790,1810,Triptych of polychrome woodblock prints; ink and color on paper,A: H. 14 13/16 in. (37.6 cm); W. 10 1/16 in. (25.6 cm) B: H. 14 3/4 in. (37.5 cm); W. 10 in. (25.4 cm) C: H. 14 3/16 in. (36 cm); W. 9 15/16 in. (25.2 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56056,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1910,false,true,54449,Asian Art,Print,娘道成寺図の錦絵|Print of a Kabuki Dancer from the Maiden of the Dōjōji Temple (Musume Dōjōji),Japan,Edo period (1615–1868),,,,Artist,,Utagawa Toyokuni I,"Japanese, 1769–1825",,Utagawa Toyokuni I,Japanese,1769,1825,ca. 1810s,1810,1819,Polychrome woodblock print (surimono); ink and color on paper,5 3/8 x 7 1/8 in. (13.7 x 18.1 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54449,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2719,false,true,56987,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Toyokuni I,"Japanese, 1769–1825",,Utagawa Toyokuni I,Japanese,1769,1825,ca. 1796,1786,1806,Polychrome woodblock print; ink and color on paper,15 x 10 in. (38.1 x 25.4 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56987,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2720,false,true,56989,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Toyokuni I,"Japanese, 1769–1825",,Utagawa Toyokuni I,Japanese,1769,1825,ca. 1795,1785,1805,Polychrome woodblock print; ink and color on paper,Image: 14 1/8 x 9 1/2 in. (35.9 x 24.1 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56989,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2721,false,true,56991,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Toyokuni I,"Japanese, 1769–1825",,Utagawa Toyokuni I,Japanese,1769,1825,ca. 1795,1785,1805,Polychrome woodblock print; ink and color on paper,14 7/8 x 10 in. (37.8 x 25.4 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56991,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2722,false,true,56992,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Toyokuni I,"Japanese, 1769–1825",,Utagawa Toyokuni I,Japanese,1769,1825,1774,1794,1794,Polychrome woodblock print; ink and color on paper,14 7/8 x 9 5/8 in. (37.8 x 24.4 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56992,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2723,false,true,56993,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Toyokuni I,"Japanese, 1769–1825",,Utagawa Toyokuni I,Japanese,1769,1825,1796,1796,1796,Polychrome woodblock print; ink and color on paper,14 1/2 x 9 7/8 in. (36.8 x 25.1 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56993,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2725,false,true,51136,Asian Art,Print,御影堂扇屋図|The Mieidō Fan Shop,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Toyokuni I,"Japanese, 1769–1825",,Utagawa Toyokuni I,Japanese,1769,1825,ca. 1785–93,1785,1793,Triptych of polychrome woodblock prints; ink and color on paper,Each H. 15 1/8 in. (38.4 cm); W. 10 1/8 in. (25.7 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/51136,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2726,false,true,56997,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Toyokuni I,"Japanese, 1769–1825",,Utagawa Toyokuni I,Japanese,1769,1825,ca. 1800,1790,1810,Diptych of polychrome woodblock prints; ink and color on paper,Each sheet: 15 x 10 in. (38.1 x 25.4 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56997,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2825,false,true,57073,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Toyokuni I,"Japanese, 1769–1825",,Utagawa Toyokuni I,Japanese,1769,1825,ca. 1790s,1785,1805,Polychrome woodblock print; ink and color on paper,15 13/16 x 9 3/8 in. (40.2 x 23.8 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/57073,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2826,false,true,57072,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Toyokuni I,"Japanese, 1769–1825",,Utagawa Toyokuni I,Japanese,1769,1825,1769–1825,1769,1825,Polychrome woodblock print; ink and color on paper,14 7/8 x 9 7/8 in. (37.8 x 25.1 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/57072,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2828,false,true,44997,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Toyokuni I,"Japanese, 1769–1825",,Utagawa Toyokuni I,Japanese,1769,1825,ca. 1795,1785,1805,Polychrome woodblock print; ink and color on paper,H. 14 7/8 in. (37.8 cm); W. 9 3/4 in. (24.8 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/44997,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2829,false,true,57071,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Toyokuni I,"Japanese, 1769–1825",,Utagawa Toyokuni I,Japanese,1769,1825,1769–1825,1769,1825,Polychrome woodblock print; ink and color on paper,14 11/16 x 10 in. (37.3 x 25.4 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/57071,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2900,false,true,56015,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Toyokuni I,"Japanese, 1769–1825",,Utagawa Toyokuni I,Japanese,1769,1825,1769–1825,1769,1825,Polychrome woodblock print; ink and color on paper,12 1/8 x 5 1/2 in. (30.8 x 14 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56015,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2901,false,true,56016,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Toyokuni I,"Japanese, 1769–1825",,Utagawa Toyokuni I,Japanese,1769,1825,1769–1825,1769,1825,Polychrome woodblock print; ink and color on paper,13 x 6 in. (33 x 15.2 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56016,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2902,false,true,56017,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Toyokuni I,"Japanese, 1769–1825",,Utagawa Toyokuni I,Japanese,1769,1825,1769–1825,1769,1825,Polychrome woodblock print; ink and color on paper,12 3/8 x 5 1/4 in. (31.4 x 13.3 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56017,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3391,false,true,55567,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Toyokuni I,"Japanese, 1769–1825",,Utagawa Toyokuni I,Japanese,1769,1825,ca. 1800,1790,1810,Polychrome woodblock print; ink and color on paper,15 1/8 x 10 1/4 in. (38.4 x 26 cm),"Gift of Mr. and Mrs. Arthur J. Steel, 1960",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55567,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2011.125,false,true,76555,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Toyokuni I,"Japanese, 1769–1825",,Utagawa Toyokuni I,Japanese,1769,1825,1812,1812,1812,Polychrome woodblock print with metallic pigment and lacquer details,Image (ôban tate-e): 14 7/8 x 10 1/8 in. (37.8 x 25.7 cm),"Purchase, Friends of Asian Art Gifts, in honor of James C. Y. Watt, 2011",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/76555,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2724a–c,false,true,56995,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Toyokuni I,"Japanese, 1769–1825",,Utagawa Toyokuni I,Japanese,1769,1825,ca. 1799,1789,1809,Triptych of polychrome woodblock prints; ink and color on paper,Each sheet: 15 x 10 in. (38.1 x 25.4 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56995,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2026,false,true,54786,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Teisai Hokuba,"Japanese, 1771–1844",,Teisai Hokuba,Japanese,1771,1844,probably 1808,1808,1808,Polychrome woodblock print (surimono); ink and color on paper,5 1/8 x 7 3/16 in. (13 x 18.3 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54786,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2362,false,true,54146,Asian Art,Print,"摺物帖 『春雨集』 『花鳥六番之内 下野宇都宮』 牡丹に燕|Spring Rain Collection (Harusame shū), vol. 3: Swallows and Peonies",Japan,Edo period (1615–1868),,,,Artist,,Teisai Hokuba,"Japanese, 1771–1844",,Teisai Hokuba,Japanese,1771,1844,ca. 1820,1810,1830,Privately published woodblock prints (surimono) mounted in an album; ink and color on paper,8 1/4 x 7 1/16 in. (21 x 17.9 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54146,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2363,false,true,54147,Asian Art,Print,"摺物帖 『春雨集』 『花鳥六番之内 下野宇都宮』 海棠に山雀|Spring Rain Collection (Harusame shū), vol. 3: Marsh-tits and Crab Apple Flowers",Japan,Edo period (1615–1868),,,,Artist,,Teisai Hokuba,"Japanese, 1771–1844",,Teisai Hokuba,Japanese,1771,1844,ca. 1820,1810,1830,Privately published woodblock prints (surimono) mounted in an album; ink and color on paper,8 x 7 3/8 in. (20.3 x 18.7 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54147,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2364,false,true,54148,Asian Art,Print,"摺物帖 『春雨集』 『花鳥六番之内 下野宇都宮』 タンポポに雀|Spring Rain Collection (Harusame shū), vol. 3: Sparrows and Dandelions",Japan,Edo period (1615–1868),,,,Artist,,Teisai Hokuba,"Japanese, 1771–1844",,Teisai Hokuba,Japanese,1771,1844,ca. 1820,1810,1830,Privately published woodblock prints (surimono) mounted in an album; ink and color on paper,8 3/8 x 7 1/2 in. (21.3 x 19.1 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54148,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2365,false,true,54149,Asian Art,Woodblock print,"摺物帖 『春雨集』 『花鳥六番之内 下野宇都宮』 桃に山鳩|Spring Rain Collection (Harusame shū), vol. 3: Mountain Dove and Peach Flowers",Japan,Edo period (1615–1868),,,,Artist,,Teisai Hokuba,"Japanese, 1771–1844",,Teisai Hokuba,Japanese,1771,1844,ca. 1820,1810,1830,Privately published woodblock prints (surimono) mounted in an album; ink and color on paper,8 1/4 x 7 7/16 in. (21 x 18.9 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54149,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2635,false,true,45225,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunimasa,"Japanese, 1773–1810",,Utagawa Kunimasa,Japanese,1773,1810,late 1790s,1796,1799,Polychrome woodblock print; ink and color on paper,14 1/2 x 9 3/4 in. (36.8 x 24.8 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45225,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1416,false,true,51086,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Toyokuni II,"Japanese, 1777–1835",,Utagawa Toyokuni II,Japanese,1777,1835,ca. 1830,1820,1840,Polychrome woodblock print; ink and color on paper,H. 9 1/2 in. (24.1 cm); W. 14 5/16 in. (36.4 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/51086,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1750,false,true,54396,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Toyokuni II,"Japanese, 1777–1835",,Utagawa Toyokuni II,Japanese,1777,1835,ca. 1834,1824,1844,Polychrome woodblock print (surimono); ink and color on paper,7 x 6 3/4 in. (17.8 x 17.1 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54396,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1153,false,true,54359,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Takashima Chiharu,"Japanese, 1777–1859",,Takashima Chiharu,Japanese,1777,1859,ca. 1820,1810,1830,Polychrome woodblock print (surimono); ink and color on paper,8 1/16 x 7 1/16 in. (20.5 x 17.9 cm),"Gift of T. Ito, Chicago, Ill, 1919",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54359,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2064,false,true,54892,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Takashima Chiharu,"Japanese, 1777–1859",,Takashima Chiharu,Japanese,1777,1859,probably 1815,1815,1815,Polychrome woodblock print (surimono); ink and color on paper,5 9/16 x 7 7/16 in. (14.1 x 18.9 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54892,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP749,false,true,54307,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Totoya Hokkei,"Japanese, 1780–1850",,Totoya Hokkei,Japanese,1780,1850,ca. 1830,1820,1840,Diptych of woodblock prints (surimono); ink and color on paper,8 1/8 x 14 7/16 in. (20.6 x 36.7 cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54307,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1032,false,true,54317,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Totoya Hokkei,"Japanese, 1780–1850",,Totoya Hokkei,Japanese,1780,1850,1814,1814,1814,Polychrome woodblock print (surimono); ink and color on paper,8 1/16 x 7 in. (20.5 x 17.8 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54317,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1033,false,true,54318,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Totoya Hokkei,"Japanese, 1780–1850",,Totoya Hokkei,Japanese,1780,1850,ca. 1825,1825,1825,Polychrome woodblock print (surimono); ink and color on paper,8 1/2 x 7 3/16 in. (21.6 x 18.3 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54318,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1035,false,true,54320,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Totoya Hokkei,"Japanese, 1780–1850",,Totoya Hokkei,Japanese,1780,1850,ca. 1825,1815,1835,Polychrome woodblock print (surimono); ink and color on paper,8 5/8 x 7 5/8 in. (21.9 x 19.4 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54320,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1098,false,true,54327,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Totoya Hokkei,"Japanese, 1780–1850",,Totoya Hokkei,Japanese,1780,1850,ca. 1820,1810,1830,Polychrome woodblock print (surimono); ink and color on paper,7 3/4 x 6 3/4 in. (19.7 x 17.1 cm),"Rogers Fund, 1919",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54327,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1100,false,true,54329,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Totoya Hokkei,"Japanese, 1780–1850",,Totoya Hokkei,Japanese,1780,1850,ca. 1820,1810,1830,Polychrome woodblock print (surimono); ink and color on paper,16 3/16 x 7 1/4 in. (41.1 x 18.4 cm),"Rogers Fund, 1919",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54329,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1144,false,true,54351,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Totoya Hokkei,"Japanese, 1780–1850",(?),Totoya Hokkei,Japanese,1780,1850,ca. 1820,1810,1830,Polychrome woodblock print (surimono); ink and color on paper,8 1/4 x 7 5/8 in. (21 x 19.4 cm),"Rogers Fund, 1919",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54351,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1145,false,true,54352,Asian Art,Print,Sumidagawa|Sumida River,Japan,Edo period (1615–1868),,,,Artist,,Totoya Hokkei,"Japanese, 1780–1850",,Totoya Hokkei,Japanese,1780,1850,"Year of the Dragon, probably 1832",1832,1832,Polychrome woodblock print (surimono); ink and color on paper,7 7/8 x 7 3/8 in. (20 x 18.7 cm),"Rogers Fund, 1919",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54352,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1146,false,true,44978,Asian Art,Print,Tosa no umi|Inland Sea near Tosa,Japan,Edo period (1615–1868),,,,Artist,,Totoya Hokkei,"Japanese, 1780–1850",,Totoya Hokkei,Japanese,1780,1850,ca. 1820,1810,1830,Polychrome woodblock print (surimono); ink and color on paper,8 1/16 x 7 1/4 in. (20.5 x 18.4 cm),"Rogers Fund, 1919",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/44978,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1150,false,true,54356,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Totoya Hokkei,"Japanese, 1780–1850",,Totoya Hokkei,Japanese,1780,1850,ca. 1800,1790,1810,Polychrome woodblock print (surimono); ink and color on paper,7 3/4 x 7 1/4 in. (19.7 x 18.4 cm),"Rogers Fund, 1919",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54356,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1154,false,true,54360,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Totoya Hokkei,"Japanese, 1780–1850",,Totoya Hokkei,Japanese,1780,1850,ca. 1820,1810,1830,Polychrome woodblock print (surimono); ink and color on paper,7 3/4 x 6 7/8 in. (19.7 x 17.5 cm),"Gift of T. Ito, Chicago, Ill, 1919",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54360,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1240,false,true,54371,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Totoya Hokkei,"Japanese, 1780–1850",,Totoya Hokkei,Japanese,1780,1850,1830,1830,1830,Polychrome woodblock print (surimono); ink and color on paper,8 1/4 x 7 1/4 in. (21 x 18.4 cm),"Rogers Fund, 1921",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54371,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1241,false,true,54372,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Totoya Hokkei,"Japanese, 1780–1850",,Totoya Hokkei,Japanese,1780,1850,1830,1830,1830,Polychrome woodblock print (surimono); ink and color on paper,8 1/16 x 7 1/4 in. (20.5 x 18.4 cm),"Rogers Fund, 1921",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54372,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1242,false,true,54373,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Totoya Hokkei,"Japanese, 1780–1850",,Totoya Hokkei,Japanese,1780,1850,1830,1830,1830,Polychrome woodblock print (surimono); ink and color on paper,8 x 7 1/8 in. (20.3 x 18.1 cm),"Rogers Fund, 1921",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54373,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1253,false,true,54385,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Totoya Hokkei,"Japanese, 1780–1850",,Totoya Hokkei,Japanese,1780,1850,ca. 1800,1790,1810,Polychrome woodblock print (surimono); ink and color on paper,8 1/16 x 11 in. (20.5 x 27.9 cm),"Rogers Fund, 1921",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54385,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1297,false,true,54387,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Totoya Hokkei,"Japanese, 1780–1850",,Totoya Hokkei,Japanese,1780,1850,ca. 1820,1810,1830,Polychrome woodblock print (surimono); ink and color on paper,8 3/8 x 7 1/8 in. (21.3 x 18.1 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54387,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1434,false,true,54392,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Totoya Hokkei,"Japanese, 1780–1850",,Totoya Hokkei,Japanese,1780,1850,ca. 1825,1815,1835,Polychrome woodblock print (surimono); ink and color on paper,8 1/8 x 7 in. (20.6 x 17.8 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54392,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1811,false,true,54399,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Totoya Hokkei,"Japanese, 1780–1850",,Totoya Hokkei,Japanese,1780,1850,ca. 1815–20,1815,1820,Polychrome woodblock print (surimono); ink and color on paper,8 1/8 x 7 1/4 in. (20.6 x 18.4 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54399,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1909,false,true,54448,Asian Art,Print,"松・滝に孔雀図と緋毛氈|Painting of Peacocks, Pines, a Waterfall, and a Roll of Red Fabric",Japan,Edo period (1615–1868),,,,Artist,,Totoya Hokkei,"Japanese, 1780–1850",,Totoya Hokkei,Japanese,1780,1850,ca. 1818,1808,1828,Polychrome woodblock print (surimono); ink and color on paper,8 1/2 x 7 1/4 in. (21.6 x 18.4 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54448,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1914,false,true,54453,Asian Art,Woodblock print,鮭頭|Head of a Salmon,Japan,Edo period (1615–1868),,,,Artist,,Totoya Hokkei,"Japanese, 1780–1850",,Totoya Hokkei,Japanese,1780,1850,ca. 1820,1810,1830,Polychrome woodblock print (surimono); ink and color on paper,8 5/16 x 7 3/8 in. (21.1 x 18.7 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54453,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1917,false,true,54456,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Totoya Hokkei,"Japanese, 1780–1850",(?),Totoya Hokkei,Japanese,1780,1850,probably 1814,1814,1814,Polychrome woodblock print (surimono); ink and color on paper,7 15/16 x 7 1/8 in. (20.2 x 18.1 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54456,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1929,false,true,54469,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Totoya Hokkei,"Japanese, 1780–1850",,Totoya Hokkei,Japanese,1780,1850,probably 1816,1816,1816,Polychrome woodblock print (surimono); ink and color on paper,8 3/8 x 7 1/4 in. (21.3 x 18.4 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54469,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1945,false,true,54513,Asian Art,Print,"印鑑と朱に孔雀羽根|Seal-stone and Seal-ink with Peacock Feathers, from Spring Rain Surimono Album (Harusame surimono-jō), vol. 1",Japan,Edo period (1615–1868),,,,Artist,,Totoya Hokkei,"Japanese, 1780–1850",,Totoya Hokkei,Japanese,1780,1850,probably 1817,1817,1817,Privately published polychrome woodblock prints (surimono) mounted in an album; ink and color on paper,7 9/16 x 5 1/8 in. (19.2 x 13 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54513,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1948,false,true,54516,Asian Art,Print,"元日仕度|Preparations for the New Year, from Spring Rain Surimono Album (Harusame surimono-jō, vol. 1)",Japan,Edo period (1615–1868),,,,Artist,,Totoya Hokkei,"Japanese, 1780–1850",,Totoya Hokkei,Japanese,1780,1850,1817,1817,1817,Privately published polychrome woodblock prints (surimono) mounted in an album; ink and color on paper,8 1/4 x 7 1/4 in. (21 x 18.4 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54516,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1977,false,true,54562,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Totoya Hokkei,"Japanese, 1780–1850",,Totoya Hokkei,Japanese,1780,1850,1815,1815,1815,Polychrome woodblock print (surimono); ink and color on paper,8 9/16 x 7 3/8 in. (21.7 x 18.7 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54562,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2011,false,true,54731,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Totoya Hokkei,"Japanese, 1780–1850",,Totoya Hokkei,Japanese,1780,1850,1811,1811,1811,Polychrome woodblock print (surimono); ink and color on paper,5 11/16 x 7 1/2 in. (14.4 x 19.1 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54731,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2033,false,true,54799,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Totoya Hokkei,"Japanese, 1780–1850",,Totoya Hokkei,Japanese,1780,1850,1816,1816,1816,Polychrome woodblock print (surimono); ink and color on paper,5 3/8 x 7 5/16 in. (13.7 x 18.6 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54799,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2046,false,true,54817,Asian Art,Print,"「文齊側五行火 花街細見合」|Courtesan by a Lantern, “Fire,” from the series Five Elements for the Bunsai Poetry Group, a Guide to the Yoshiwara Pleasure Quarters",Japan,Edo period (1615–1868),,,,Artist,,Totoya Hokkei,"Japanese, 1780–1850",,Totoya Hokkei,Japanese,1780,1850,ca. 1820,1810,1830,Polychrome woodblock print (surimono); ink and color on paper,7 7/8 x 7 3/16 in. (20 x 18.3 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54817,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2107,false,true,54959,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Totoya Hokkei,"Japanese, 1780–1850",,Totoya Hokkei,Japanese,1780,1850,1816,1816,1816,Polychrome woodblock print (surimono); ink and color on paper,5 1/4 x 7 3/16 in. (13.3 x 18.3 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54959,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2288,false,true,54061,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Totoya Hokkei,"Japanese, 1780–1850",,Totoya Hokkei,Japanese,1780,1850,1816,1816,1816,Part of an album of woodblock prints (surimono); ink and color on paper,5 11/16 x 7 1/2 in. (14.4 x 19.1 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54061,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2309,false,true,54093,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Totoya Hokkei,"Japanese, 1780–1850",,Totoya Hokkei,Japanese,1780,1850,ca. 1800,1790,1810,Part of an album of woodblock prints (surimono); ink and color on paper,7 1/8 x 6 5/8 in. (18.1 x 16.8 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54093,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2350,false,true,54135,Asian Art,Woodblock print,"印籠と牛根付『春雨集』 摺物帖|Lacquer Inrō with Waterbirds and Ox-shaped Netsuke in a BoxFrom the Spring Rain Collection (Harusame shū), vol. 3",Japan,Edo period (1615–1868),,,,Artist,,Totoya Hokkei,"Japanese, 1780–1850",,Totoya Hokkei,Japanese,1780,1850,probably 1817,1817,1817,Part of an album of woodblock prints (surimono); ink and color on paper,5 1/4 x 7 3/8 in. (13.3 x 18.7 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54135,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2371,false,true,54155,Asian Art,Print,"「三ひらの内」松・牡丹に孔雀|Peacock on Pine Tree and Peonies, from the series Three Sheets (Mihira no uchi)",Japan,Edo period (1615–1868),,,,Artist,,Totoya Hokkei,"Japanese, 1780–1850",,Totoya Hokkei,Japanese,1780,1850,mid- 1810s,1814,1816,Part of an album of woodblock prints (surimono); ink and color on paper,8 3/8 x 7 3/8 in. (21.3 x 18.7 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54155,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2372,false,true,54156,Asian Art,Album leaf,"「三ひらの内」汀五羽の鶴|Five Cranes by the Water’s Edge, from the series Three Sheets (Mihira no uchi)",Japan,Edo period (1615–1868),,,,Artist,,Totoya Hokkei,"Japanese, 1780–1850",,Totoya Hokkei,Japanese,1780,1850,mid- 1810s,1814,1816,Part of an album of woodblock prints (surimono); ink and color on paper,8 3/8 x 7 5/16 in. (21.3 x 18.6 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54156,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2373,false,true,54157,Asian Art,Print,"「三ひらの内」日輪に烏|Three Crows against the Rising Sun, from the series Three Sheets (Mihira no uchi)",Japan,Edo period (1615–1868),,,,Artist,,Totoya Hokkei,"Japanese, 1780–1850",,Totoya Hokkei,Japanese,1780,1850,mid- 1810s,1814,1816,Part of an album of woodblock prints (surimono); ink and color on paper,8 1/4 x 7 1/4 in. (21 x 18.4 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54157,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2544,false,true,45040,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Totoya Hokkei,"Japanese, 1780–1850",,Totoya Hokkei,Japanese,1780,1850,ca. 1825,1815,1835,Polychrome woodblock print (surimono); ink and color on paper,8 1/2 x 7 1/4 in. (21.6 x 18.4 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45040,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2545,false,true,54182,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Totoya Hokkei,"Japanese, 1780–1850",,Totoya Hokkei,Japanese,1780,1850,ca. 1825,1815,1835,Polychrome woodblock print (surimono); ink and color on paper,10 x 7 in. (25.4 x 17.8 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54182,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3005,false,true,54221,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Totoya Hokkei,"Japanese, 1780–1850",,Totoya Hokkei,Japanese,1780,1850,1780–1850,1780,1850,Polychrome woodblock print (surimono); ink and color on paper,8 1/4 x 7 1/4 in. (21 x 18.4 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54221,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3006,false,true,54222,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Totoya Hokkei,"Japanese, 1780–1850",,Totoya Hokkei,Japanese,1780,1850,1780–1850,1780,1850,Polychrome woodblock print (surimono); ink and color on paper,7 7/8 x 6 11/16 in. (20 x 17 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54222,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3007,false,true,54224,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Totoya Hokkei,"Japanese, 1780–1850",,Totoya Hokkei,Japanese,1780,1850,1780–1850,1780,1850,Polychrome woodblock print (surimono); ink and color on paper,8 1/8 x 7 3/16 in. (20.6 x 18.3 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54224,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3009,false,true,54226,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Totoya Hokkei,"Japanese, 1780–1850",,Totoya Hokkei,Japanese,1780,1850,1831,1831,1831,Polychrome woodblock print (surimono); ink and color on paper,16 5/8 x 7 in. (42.2 x 17.8 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54226,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.1126.3,false,true,633305,Asian Art,woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Totoya Hokkei,"Japanese, 1780–1850",,Totoya Hokkei,Japanese,1780,1850,1835,1835,1835,Polychrome woodblock print (shikishiban surimono); ink and color on paper,Image: 7 1/4 × 6 3/4 in. (18.4 × 17.1 cm),"Gift of Dorothy Tapper Goldman, 2013",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/633305,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP225,false,true,36699,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,ca. 1840,1830,1850,Polychrome woodblock print; ink and color on paper,9 15/32 x 14 3/8 in. (24.1 x 36.5 cm),"Rogers Fund, 1917",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36699,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1147,false,true,54353,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,ca. 1819,1809,1829,Polychrome woodblock print (surimono); ink and color on paper,8 3/16 x 7 1/4 in. (20.8 x 18.4 cm),"Rogers Fund, 1919",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54353,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1216,false,true,55139,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,ca. 1850,1840,1860,Polychrome woodblock print; ink and color on paper,H. 14 5/16 in. (36.4 cm); W. 10 in. (25.4 cm),"Rogers Fund, 1920",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55139,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1251,false,true,54383,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,ca. 1824,1814,1834,Polychrome woodblock print (surimono); ink and color on paper,8 7/16 x 7 3/8 in. (21.4 x 18.7 cm),"Rogers Fund, 1921",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54383,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1267,false,true,55164,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,1833–47,1833,1847,Polychrome woodblock print; ink and color on paper,14 1/8 x 10 1/8 in. (35.9 x 25.7 cm),"Gift of Albert Gallatin, 1921",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55164,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1454,false,true,54395,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,ca. 1840,1830,1850,Polychrome woodblock print (surimono); ink and color on paper,8 7/16 x 7 3/8 in. (21.4 x 18.7 cm),"Rogers Fund, 1923",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54395,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1497,false,true,44979,Asian Art,Print,"Futami-ga-ura akebono-no kuni|Futami-ga-ura Rocks at Ise, Land of Dawn",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,ca. 1835,1825,1845,Polychrome woodblock print; ink and color on paper,H. 9 1/2 in. (24.1 cm); W. 14 in. (35.6 cm),"Fletcher Fund, 1928",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/44979,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2051,false,true,54824,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,probably 1815,1815,1815,Polychrome woodblock print (surimono); ink and color on paper,7 7/8 x 5 5/16 in. (20 x 13.5 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54824,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2396,false,true,54176,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,1819,1819,1819,Polychrome woodblock print (surimono); ink and color on paper,8 3/16 x 7 7/16 in. (20.8 x 18.9 cm),"H. O. Havemeyer Collection, Gift of Mrs. J. Watson Webb, 1930",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54176,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2631,false,true,56679,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,mid-19th century,1834,1866,Polychrome woodblock print; ink and color on paper,H. 9 3/4 in. (24.8 cm); W. 14 1/2 in. (36.8 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56679,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2863,false,true,56611,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,mid-19th century,1834,1866,Polychrome woodblock print; ink and color on paper,H. 9 3/4 in. (24.8 cm); W. 14 1/2 in. (36.8 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56611,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2911,false,true,56026,Asian Art,Print,O Ateri Kyogen Uchi|Wild Words - a Play,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,1786–1864,1786,1864,Polychrome woodblock print; ink and color on paper,14 7/8 x 9 7/8 in. (37.8 x 25.1 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56026,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3358,false,true,55526,Asian Art,Print,"「湯灌場子僧吉三 市川竹之丞」(五代目) 「近世水滸伝」|Ichimura Takenojō V as Yukanba Kozō Kichiza, from A Modern Water Margin (Kinsei suikoden)",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,1862,1862,1862,Polychrome woodblock print; ink and color on paper,13 5/18 x 9 9/16 in. (33.7 x 24.3 cm),"Gift of Lincoln Kirstein, 1960",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55526,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3359,false,true,55527,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,"1854 (year of the Horse, 7th month)",1854,1854,Polychrome woodblock print; ink and color on paper,14 1/4 x 9 3/4 in. (36.2 x 24.8 cm),"Gift of Lincoln Kirstein, 1960",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55527,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3360,false,true,55528,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,"1854 (year of the Horse, 7th month)",1854,1854,Polychrome woodblock print; ink and color on paper,14 1/8 x 9 3/4 in. (35.9 x 24.8 cm),"Gift of Lincoln Kirstein, 1960",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55528,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3361,false,true,55529,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,"1854 (year of the Horse, 7th month)",1854,1854,Polychrome woodblock print; ink and color on paper,14 1/8 x 9 3/4 in. (35.9 x 24.8 cm),"Gift of Lincoln Kirstein, 1960",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55529,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3376,false,true,55543,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,1863 (5th month),1863,1863,Polychrome woodblock print; ink and color on paper,14 1/8 x 28 1/2 in. (35.9 x 72.4 cm),"Gift of Lincoln Kirstein, 1960",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55543,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP212.15,false,true,58000,Asian Art,Print,「籠細工 浪花細工人一田庄七郎」|Basketry Work: By the Craftsman Ichida Shōshichirō of Naniwa (Kagosaiku Naniwa saikujin Ichida Shōshichirō),Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,1819,1819,1819,Polychrome woodblock print; ink and color on paper,Image: 13 1/2 × 9 1/2 in. (34.3 × 24.1 cm) Mat: 15 1/2 × 22 3/4 in. (39.4 × 57.8 cm),"Gift of the Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/58000,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP212.16,false,true,58001,Asian Art,Print,「籠細工 浪花細工人一田庄七郎」|Basketry Work: By the Craftsman Ichida Shōshichirō of Naniwa (Kagosaiku Naniwa saikujin Ichida Shōshichirō),Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,1819,1819,1819,Polychrome woodblock print; ink and color on paper,Image: 13 1/2 × 9 1/2 in. (34.3 × 24.1 cm) Mat: 15 1/2 × 22 3/4 in. (39.4 × 57.8 cm),"Gift of the Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/58001,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP212.17,false,true,58002,Asian Art,Print,「籠細工 浪花細工人一田庄七郎」|Basketry Work: By the Craftsman Ichida Shōshichirō of Naniwa (Kagosaiku Naniwa saikujin Ichida Shōshichirō),Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,1819,1819,1819,Polychrome woodblock print; ink and color on paper,Image: 13 1/2 × 9 1/2 in. (34.3 × 24.1 cm) Mat: 15 1/2 × 22 3/4 in. (39.4 × 57.8 cm),"Gift of the Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/58002,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP212.18,false,true,58003,Asian Art,Print,「籠細工 浪花細工人一田庄七郎」|Basketry Work: By the Craftsman Ichida Shōshichirō of Naniwa (Kagosaiku Naniwa saikujin Ichida Shōshichirō),Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,1819,1819,1819,Polychrome woodblock print; ink and color on paper,Image: 13 1/2 × 9 1/2 in. (34.3 × 24.1 cm) Mat: 15 1/2 × 22 3/4 in. (39.4 × 57.8 cm),"Gift of the Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/58003,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP212.58,false,true,58042,Asian Art,Print,"三代目関三十郎の大寺正兵衛 小袖曽我薊色縫|Seki Sanjūrō III as Ōdera Shōbei from ""Kosode Soga azami no ironui""",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,1859,1859,1859,Right panel of a tryptich of polychrome woodblock prints; ink and color on paper,Image: 13 5/8 × 9 5/8 in. (34.6 × 24.4 cm),"Gift of the Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/58042,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1093.11,false,true,58263,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,1830s (Tenpô era),1830,1839,Polychrome woodblock print; ink and color on paper,Image: 14 3/4 × 10 1/8 in. (37.5 × 25.7 cm),The Metropolitan Museum of Art,,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/58263,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1093.12,false,true,58264,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,mid-19th century,1834,1866,Polychrome woodblock print; ink and color on paper,Image: 14 3/8 × 10 1/2 in. (36.5 × 26.7 cm),The Metropolitan Museum of Art,,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/58264,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1999.457.1,false,true,53710,Asian Art,Print,"重陽後の月宴 十二月ノ内|Banquet of the Next Full Moon at the Chrysanthemum Festival, from the series The Twelve Months (Chōyō nochi no tsuki no en, Jūni tsuki no uchi)",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,1854,1854,1854,Triptych of polychrome woodblock prints; ink and color on paper,Overall: 14 5/8 x 30 1/8in. (37.1 x 76.5cm),"Gift of Eliot C. Nolen, 1999",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/53710,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2001.715.3,false,true,63357,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,ca. 1820,1810,1830,"Polychrome woodblock print (surimono); ink, silver, and color on paper",8 1/2 x 7 1/2 in. (21.6 x 19.1 cm),"Purchase, Jack Greene Gift, 2001",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/63357,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2001.715.13,false,true,63384,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,ca. 1840,1830,1850,Polychrome woodblock print; ink and color on paper,"Oban tate-e, 14 3/4 x 10 1/4 in. (37.5 x 26 cm)","Purchase, Arnold Weinstein Gift, 2001",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/63384,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.271,false,true,73611,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,1864,1864,1864,Polychrome woodblock print; ink and color on paper,Image: 14 3/8 x 10 in. (36.5 x 25.4 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73611,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.1126.1,false,true,633299,Asian Art,woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,probably 1829,1829,1829,Polychrome woodblock print (shikishiban surimono); ink and color on paper,Image: 8 1/4 × 7 1/4 in. (21 × 18.4 cm),"Gift of Dorothy Tapper Goldman, 2013",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/633299,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.1126.2,false,true,633302,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,ca. 1830,1820,1840,"Polychrome woodblock print (shikishiban surimono); ink, color and metallic pigments on paper",Image: 8 1/4 × 7 1/4 in. (21 × 18.4 cm),"Gift of Dorothy Tapper Goldman, 2013",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/633302,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2001.715.12a–c,false,true,63381,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,1811,1801,1821,Triptych of polychrome woodblock prints; ink and color on paper,"Oban tate-e; triptych, each: 15 1/8 x 10 1/8 in. (38.4 x 25.7 cm)","Purchase, Arnold Weinstein Gift, 2001",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/63381,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1217,false,true,55140,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Yanagawa Shigenobu,"Japanese, 1787–1832",,Yanagawa Shigenobu,Japanese,1787,1832,ca. 1825,1815,1835,Polychrome woodblock print; ink and color on paper,H. 15 in. (38.1 cm); W. 10 in. (25.4 cm),"Rogers Fund, 1921",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55140,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1218,false,true,55141,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Yanagawa Shigenobu,"Japanese, 1787–1832",,Yanagawa Shigenobu,Japanese,1787,1832,ca. 1825,1815,1835,Polychrome woodblock print; ink and color on paper,H. 14 7/8 in. (37.8 cm); W. 9 3/4 in. (24.8 cm),"Rogers Fund, 1921",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55141,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1244,false,true,54375,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Yanagawa Shigenobu,"Japanese, 1787–1832",,Yanagawa Shigenobu,Japanese,1787,1832,1828,1828,1828,Polychrome woodblock print (surimono); ink and color on paper,8 1/4 x 7 5/16 in. (21 x 18.6 cm),"Rogers Fund, 1921",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54375,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1245,false,true,54376,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Yanagawa Shigenobu,"Japanese, 1787–1832",,Yanagawa Shigenobu,Japanese,1787,1832,1830,1830,1830,Polychrome woodblock print (surimono); ink and color on paper,8 5/16 x 7 5/16 in. (21.1 x 18.6 cm),"Rogers Fund, 1921",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54376,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1281,false,true,55215,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Yanagawa Shigenobu,"Japanese, 1787–1832",,Yanagawa Shigenobu,Japanese,1787,1832,ca. 1825,1815,1835,Polychrome woodblock print; ink and color on paper,H. 14 3/4 in. (37.5 cm); W. 9 7/8 in. (25.1 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55215,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1944,false,true,54512,Asian Art,Print,"鶏図衝立を見る鶏|Cock Eyeing a Free-standing Screen Painted with Cock, Hen, and Chicks, from Spring Rain Surimono Album (Harusame surimono-jō), vol. 1",Japan,Edo period (1615–1868),,,,Artist,,Yanagawa Shigenobu,"Japanese, 1787–1832",,Yanagawa Shigenobu,Japanese,1787,1832,probably 1813,1813,1813,Privately published polychrome woodblock prints (surimono) mounted in an album; ink and color on paper,7 11/16 x 10 7/8 in. (19.5 x 27.6 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54512,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2659,false,true,56838,Asian Art,Print,Shōkei|Celebrated Waterfall,Japan,Edo period (1615–1868),,,,Artist,,Yanagawa Shigenobu,"Japanese, 1787–1832",,Yanagawa Shigenobu,Japanese,1787,1832,1820–1830,1820,1830,Polychrome woodblock print; ink and color on paper,13 x 6 3/4 in. (33 x 17.1 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56838,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1017,false,true,54926,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kikugawa Eizan,"Japanese, 1787–1867",,Kikugawa Eizan,Japanese,1787,1867,ca. 1805,1795,1815,Triptych of polychrome woodblock prints; ink and color on paper,H. 15 1/8 in. (38.4 cm); W. 30 in. (76.2 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54926,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1962,false,true,54537,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kikugawa Eizan,"Japanese, 1787–1867",,Kikugawa Eizan,Japanese,1787,1867,probably 1815,1815,1815,Polychrome woodblock print (surimono); ink and color on paper,5 x 7 1/16 in. (12.7 x 17.9 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54537,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1018,false,true,54927,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyomine,"Japanese, 1787–1868",,Torii Kiyomine,Japanese,1787,1868,ca. 1820,1810,1830,Triptych of monochrome woodblock prints; ink on paper,H. 15 in. (38.1 cm); W. 30 in. (76.2 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54927,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1019,false,true,54316,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyomine,"Japanese, 1787–1868",,Torii Kiyomine,Japanese,1787,1868,ca. 1820,1810,1830,Polychrome woodblock print (surimono); ink and color on paper,8 7/16 x 7 3/16 in. (21.4 x 18.3 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54316,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1801,false,true,56103,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyomine,"Japanese, 1787–1868",,Torii Kiyomine,Japanese,1787,1868,ca. 1808,1798,1818,Polychrome woodblock print; ink and color on paper,14 15/16 x 9 7/8 in. (37.9 x 25.1cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56103,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1802,false,true,56104,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyomine,"Japanese, 1787–1868",,Torii Kiyomine,Japanese,1787,1868,ca. 1804,1794,1814,Polychrome woodblock print; ink and color on paper,19 1/16 x 9 1/16in. (48.4 x 23cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56104,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP222,false,true,36696,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Keisai Eisen,"Japanese, 1790–1848",,Keisai Eisen,Japanese,1790,1848,ca. 1842,1832,1852,Polychrome woodblock print; ink and color on paper,9 3/4 x 14 in. (24.8 x 35.6 cm),"Rogers Fund, 1917",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36696,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1031,false,true,54951,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Keisai Eisen,"Japanese, 1790–1848",,Keisai Eisen,Japanese,1790,1848,ca. 1838,1833,1843,Polychrome woodblock print; ink and color on paper,H. 8 5/8 in. (21.9 cm); W. 13 1/2 in. (34.3 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54951,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1065,false,true,55012,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Keisai Eisen,"Japanese, 1790–1848",,Keisai Eisen,Japanese,1790,1848,1824,1824,1824,Polychrome woodblock print; ink and color on paper,H. 7 in. (17.8 cm); W. 5 in. (12.7 cm),"Gift of Estate of Samuel Isham, 1915",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55012,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1066,false,true,55013,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Keisai Eisen,"Japanese, 1790–1848",,Keisai Eisen,Japanese,1790,1848,1824,1824,1824,Polychrome woodblock print; ink and color on paper,H. 7 in. (17.8 cm); W. 5 in. (12.7 cm),"Gift of Estate of Samuel Isham, 1915",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55013,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1067,false,true,55014,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Keisai Eisen,"Japanese, 1790–1848",,Keisai Eisen,Japanese,1790,1848,1824,1824,1824,Polychrome woodblock print; ink and color on paper,H. 7in. (17.8 cm); W. 5 in. (12.7 cm),"Gift of Estate of Samuel Isham, 1915",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55014,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1068,false,true,55015,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Keisai Eisen,"Japanese, 1790–1848",,Keisai Eisen,Japanese,1790,1848,ca. 1825,1815,1835,Polychrome woodblock print; ink and color on paper,H. 7 in. (17.8 cm); W. 5 in. (12.7 cm),"Gift of Estate of Samuel Isham, 1915",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55015,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1069,false,true,55016,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Keisai Eisen,"Japanese, 1790–1848",,Keisai Eisen,Japanese,1790,1848,1824,1824,1824,Polychrome woodblock print; ink and color on paper,H. 7 in. (17.8 cm); W. 5 in. (12.7 cm),"Gift of Estate of Samuel Isham, 1915",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55016,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1070,false,true,55017,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Keisai Eisen,"Japanese, 1790–1848",,Keisai Eisen,Japanese,1790,1848,ca. 1825,1815,1835,Polychrome woodblock print; ink and color on paper,H. 7 in. (17.8 cm); W. 5 in. (12.7 cm),"Gift of Estate of Samuel Isham, 1915",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55017,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1071,false,true,55018,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Keisai Eisen,"Japanese, 1790–1848",,Keisai Eisen,Japanese,1790,1848,ca. 1825,1815,1835,Polychrome woodblock print; ink and color on paper,H. 7 in. (17.8 cm); W. 5 in. (12.7 cm),"Gift of Estate of Samuel Isham, 1915",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55018,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1148,false,true,54354,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Keisai Eisen,"Japanese, 1790–1848",,Keisai Eisen,Japanese,1790,1848,ca. 1830,1820,1840,Polychrome woodblock print (surimono); ink and color on paper,8 1/4 x 7 7/8 in. (21 x 20 cm),"Rogers Fund, 1919",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54354,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1201,false,true,55119,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Keisai Eisen,"Japanese, 1790–1848",,Keisai Eisen,Japanese,1790,1848,late 1830s,1830,1840,Polychrome woodblock print; ink and color on paper,Oban 9 5/8 x 14 7/16 in. (24.4 x 36.7 cm),"Rogers Fund, 1919",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55119,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1282,false,true,55218,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Keisai Eisen,"Japanese, 1790–1848",,Keisai Eisen,Japanese,1790,1848,ca. 1845,1835,1855,Polychrome woodblock print; ink and color on paper,H. 6 5/8 in. (16.8 cm); W. 12 1/4 in. (31.1 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55218,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1283,false,true,55222,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Keisai Eisen,"Japanese, 1790–1848",,Keisai Eisen,Japanese,1790,1848,mid-19th century,1834,1866,Polychrome woodblock print; ink and color on paper,H. 9 in. (22.9 cm); W. 14 in. (35.6 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55222,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1419,false,true,55447,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Keisai Eisen,"Japanese, 1790–1848",,Keisai Eisen,Japanese,1790,1848,ca. 1835,1825,1845,Polychrome woodblock print; ink and color on paper,H. 10 in. (25.4 cm); W. 14 3/4 in. (37.5 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55447,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2220,false,true,53986,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Keisai Eisen,"Japanese, 1790–1848",,Keisai Eisen,Japanese,1790,1848,probably 1812,1812,1812,Polychrome woodblock print (surimono); ink and color on paper,7 3/4 x 6 9/16 in. (19.7 x 16.7 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/53986,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2742,false,true,54204,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Keisai Eisen,"Japanese, 1790–1848",,Keisai Eisen,Japanese,1790,1848,ca. 1840,1830,1850,Polychrome woodblock print (surimono); ink and color on paper,8 3/8 x 7 1/4 in. (21.3 x 18.4 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54204,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2870,false,true,57041,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Keisai Eisen,"Japanese, 1790–1848",,Keisai Eisen,Japanese,1790,1848,1790–1848,1790,1848,Polychrome woodblock print; ink and color on paper,8 5/8 x 13 1/2 in. (21.9 x 34.3 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/57041,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2874,false,true,57012,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Keisai Eisen,"Japanese, 1790–1848",,Keisai Eisen,Japanese,1790,1848,1790–1848,1790,1848,Polychrome woodblock print; ink and color on paper,9 1/2 x 14 1/2 in. (24.1 x 36.8 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/57012,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2001.715.14,false,true,63397,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Keisai Eisen,"Japanese, 1790–1848",,Keisai Eisen,Japanese,1790,1848,ca. 1825,1815,1835,Polychrome woodblock print; ink and color on paper,"Oban yoko-e, 10 1/4 x 15 1/8 in. (26 x 38.4 cm)","Purchase, Arnold Weinstein Gift, 2001",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/63397,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2231,false,true,53997,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Ōishi Matora,"Japanese, 1793–1833",,Ōishi Matora,Japanese,1793,1833,1827,1827,1827,Polychrome woodblock print (surimono); ink and color on paper,9 7/8 x 5 3/4 in. (25.1 x 14.6 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/53997,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1936,false,true,54498,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kuninao,"Japanese, 1793–1854",,Utagawa Kuninao,Japanese,1793,1854,1814,1814,1814,Polychrome woodblock print (surimono); ink and color on paper,8 1/4 x 3 1/4 in. (21 x 8.3 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54498,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1959,false,true,54532,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kuninao,"Japanese, 1793–1854",,Utagawa Kuninao,Japanese,1793,1854,1814,1814,1814,Polychrome woodblock print (surimono); ink and color on paper,8 5/16 x 7 1/4 in. (21.1 x 18.4 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54532,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2037,false,true,54807,Asian Art,Print,"『春雨集』 摺物帖歌川国直画 元禄美人|Spring Rain Collection (Harusame shū), vol. 1: Genroku-style Courtesan",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kuninao,"Japanese, 1793–1854",,Utagawa Kuninao,Japanese,1793,1854,probably 1810s,1810,1819,Privately published woodblock prints (surimono) mounted in an album; ink and color on paper,8 7/16 x 3 7/16 in. (21.4 x 8.7 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54807,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2001.715.4a–c,false,true,63358,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kuniyasu,"Japanese, 1794–1834",,Utagawa Kuniyasu,Japanese,1794,1834,ca. 1823,1813,1833,Triptych of polychrome woodblock prints (surimono); ink and color on paper,"Triptych, each: 7 1/4 x 8 3/8 in. (18.4 x 21.3 cm)","Purchase, Jack Greene Gift, 2001",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/63358,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP221,false,true,36695,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kuniyoshi,"Japanese, 1797–1861",,Utagawa Kuniyoshi,Japanese,1797,1861,ca. 1840,1830,1850,Polychrome woodblock print; ink and color on paper,10 1/2 x 15 1/8 in. (26.7 x 38.4 cm),"Rogers Fund, 1917",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36695,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP237,false,true,36709,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kuniyoshi,"Japanese, 1797–1861",,Utagawa Kuniyoshi,Japanese,1797,1861,ca. 1830,1820,1840,Polychrome woodblock print; ink and color on paper,8 7/10 x 13 5/8 in. (22.1 x 34.6 cm),"Rogers Fund, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36709,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP750,false,true,37194,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kuniyoshi,"Japanese, 1797–1861",,Utagawa Kuniyoshi,Japanese,1797,1861,ca. 1840,1830,1850,Polychrome woodblock print; ink and color on paper,10 1/8 x 14 15/32 in. (25.7 x 36.8 cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37194,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1118,false,true,55040,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kuniyoshi,"Japanese, 1797–1861",,Utagawa Kuniyoshi,Japanese,1797,1861,1840,1840,1840,Polychrome woodblock print; ink and color on paper,H. 8 5/8 in. (21.9 cm); W. 13 9/16 in. (34.4 cm),"Rogers Fund, 1919",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55040,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1120,false,true,55042,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kuniyoshi,"Japanese, 1797–1861",,Utagawa Kuniyoshi,Japanese,1797,1861,1845,1845,1845,Polychrome woodblock print; ink and color on paper,H. 13 1/8 in. (33.3 cm); W. 8 15/16 in. (22.7 cm),"Rogers Fund, 1919",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55042,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1149,false,true,54355,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kuniyoshi,"Japanese, 1797–1861",,Utagawa Kuniyoshi,Japanese,1797,1861,ca. 1830,1820,1840,Polychrome woodblock print (surimono); ink and color on paper,8 3/8 x 7 3/8 in. (21.3 x 18.7 cm),"Rogers Fund, 1919",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54355,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1252,false,true,54384,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kuniyoshi,"Japanese, 1797–1861",,Utagawa Kuniyoshi,Japanese,1797,1861,1840,1840,1840,Polychrome woodblock print (surimono); ink and color on paper,7 15/16 x 7 1/8 in. (20.2 x 18.1 cm),"Rogers Fund, 1921",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54384,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1421,false,true,55449,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kuniyoshi,"Japanese, 1797–1861",,Utagawa Kuniyoshi,Japanese,1797,1861,1840,1840,1840,Polychrome woodblock print; ink and color on paper,H. 10 1/8 in. (25.7 cm); W. 15 1/16 in. (38.3 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55449,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1422,false,true,55451,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kuniyoshi,"Japanese, 1797–1861",,Utagawa Kuniyoshi,Japanese,1797,1861,ca. 1830,1820,1840,Polychrome woodblock print; ink and color on paper,Oban: H. 9 3/4 in. (24.8 cm); W. 14 11/16 in. (37.3 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55451,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1423,false,true,55452,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kuniyoshi,"Japanese, 1797–1861",,Utagawa Kuniyoshi,Japanese,1797,1861,ca. 1850,1840,1860,Polychrome woodblock print; ink and color on paper,H. 9 5/8 in. (24.4 cm); W. 14 1/16 in. (35.7 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55452,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1424,false,true,55453,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kuniyoshi,"Japanese, 1797–1861",,Utagawa Kuniyoshi,Japanese,1797,1861,ca. 1850,1840,1860,Polychrome woodblock print; ink and color on paper,H. 10 1/8 in. (25.7 cm); W. 14 in. (35.6 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55453,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1465,false,true,55514,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kuniyoshi,"Japanese, 1797–1861",,Utagawa Kuniyoshi,Japanese,1797,1861,ca. 1845,1835,1855,Polychrome woodblock print; ink and color on paper,H. 14 11/16 in. (37.3 cm); W. 10 1/16 in. (25.6 cm),"Rogers Fund, 1925",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55514,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1563,false,true,45282,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kuniyoshi,"Japanese, 1797–1861",,Utagawa Kuniyoshi,Japanese,1797,1861,ca. 1850,1840,1860,Diptych of polychrome woodblock prints; ink and color on paper,A: H. 14 3/8 in. (36.5 cm); W. 10 in. (25.4 cm) B: H. 14 3/8 in. (36.5 cm); W. 10 in. (25.4 cm),"Fletcher Fund, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45282,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1564,false,true,45281,Asian Art,Print,Ryugu Tamatori Hime no su|Recovering the Stolen Jewel from the Palace of the Dragon King,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kuniyoshi,"Japanese, 1797–1861",,Utagawa Kuniyoshi,Japanese,1797,1861,1853,1615,1868,Triptych of polychrome woodblock prints; ink and color on paper,A: H. 14 3/8 in. (36.5 cm); W. 9 3/4 in. (24.8 cm) B: H. 14 3/8 in. (36.5 cm); W. 9 7/8 in. (25.1 cm) C: H. 14 3/8 in. (36.5 cm); W. 9 3/4 in. (24.8 cm),"Fletcher Fund, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45281,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1565,false,true,55743,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kuniyoshi,"Japanese, 1797–1861",,Utagawa Kuniyoshi,Japanese,1797,1861,1843–47,1843,1847,Triptych of polychrome woodblock prints; ink and color on paper,Oban triptych: Each H. 14 3/4 in. (37.5 cm); W. 9 7/8 in. (25.1 cm),"Fletcher Fund, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55743,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1805,false,true,51085,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kuniyoshi,"Japanese, 1797–1861",,Utagawa Kuniyoshi,Japanese,1797,1861,1830–44,1830,1844,Polychrome woodblock print; ink and color on paper,H. 10 1/8 in. (25.7 cm); W. 14 9/16 in. (37 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/51085,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2633,false,true,51084,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kuniyoshi,"Japanese, 1797–1861",,Utagawa Kuniyoshi,Japanese,1797,1861,mid-19th century,1834,1866,Polychrome woodblock print; ink and color on paper,H. 10 1/4 in. (26 cm); W. 13 3/4 in. (34.9 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/51084,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2634,false,true,56779,Asian Art,Print,"Sashu Tsukahara setchu|Nichiren in Snow at Tsukahara, Sodo Province",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kuniyoshi,"Japanese, 1797–1861",,Utagawa Kuniyoshi,Japanese,1797,1861,ca. 1840,1830,1850,Polychrome woodblock print; ink and color on paper,"8 7/8 x 13 3/5 in. (22.5 x 34.5 cm), excluding margins","The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56779,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2749,false,true,56790,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kuniyoshi,"Japanese, 1797–1861",,Utagawa Kuniyoshi,Japanese,1797,1861,mid-19th century,1834,1866,Polychrome woodblock print; ink on thin paper,H. 8 1/2 in. (21.6 cm); W. 13 3/4 in. (34.9 cm),"Fletcher Fund, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56790,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2856,false,true,57049,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kuniyoshi,"Japanese, 1797–1861",,Utagawa Kuniyoshi,Japanese,1797,1861,1835–36,1835,1836,Polychrome woodblock print; ink and color on paper,Oban: 9 3/4 x 14 3/4 in. (24.8 x 37.5 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/57049,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2857,false,true,57048,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kuniyoshi,"Japanese, 1797–1861",,Utagawa Kuniyoshi,Japanese,1797,1861,1797–1861,1797,1861,Polychrome woodblock print; ink and color on paper,10 3/4 x 14 7/8 in. (27.3 x 37.8 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/57048,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2858,false,true,57047,Asian Art,Print,Toto Mitsumata no zu|Picture of Mitsumata,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kuniyoshi,"Japanese, 1797–1861",,Utagawa Kuniyoshi,Japanese,1797,1861,1797–1861,1797,1861,Polychrome woodblock print; ink and color on paper,10 x 14 1/2 in. (25.4 x 36.8 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/57047,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2859,false,true,45286,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kuniyoshi,"Japanese, 1797–1861",,Utagawa Kuniyoshi,Japanese,1797,1861,ca. 1835,1825,1845,Polychrome woodblock print; ink and color on paper,8 3/4 x 13 5/8 in. (22.2 x 34.6 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45286,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2860,false,true,45287,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kuniyoshi,"Japanese, 1797–1861",,Utagawa Kuniyoshi,Japanese,1797,1861,ca. 1835,1825,1845,Polychrome woodblock print; ink and color on paper,8 7/8 x 13 3/4 in. (22.5 x 34.9 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45287,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2861,false,true,45284,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kuniyoshi,"Japanese, 1797–1861",,Utagawa Kuniyoshi,Japanese,1797,1861,ca. 1835,1825,1845,Polychrome woodblock print; ink and color on paper,8 7/8 x 13 3/4 in. (22.5 x 34.9 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45284,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2862,false,true,57046,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kuniyoshi,"Japanese, 1797–1861",,Utagawa Kuniyoshi,Japanese,1797,1861,1797–1861,1797,1861,Polychrome woodblock print; ink and color on paper,14 3/4 x 10 in. (37.5 x 25.4 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/57046,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3008,false,true,54225,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kuniyoshi,"Japanese, 1797–1861",,Utagawa Kuniyoshi,Japanese,1797,1861,1797–1861,1797,1861,Polychrome woodblock print (surimono); ink and color on paper,8 1/2 x 7 1/2 in. (21.6 x 19.1 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54225,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3136,false,true,56684,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kuniyoshi,"Japanese, 1797–1861",,Utagawa Kuniyoshi,Japanese,1797,1861,ca. 1844–46,1834,1856,"Triptych of polychrome woodblock prints; ink, silver, and color on paper",9 3/4 x 22 1/8 in. (24.8 x 56.2 cm),"Gift of Francis M. Weld, 1948",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56684,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2001.612.1,false,true,62028,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kuniyoshi,"Japanese, 1797–1861",,Utagawa Kuniyoshi,Japanese,1797,1861,ca. 1845–46,1835,1856,Polychrome woodblock print; ink and color on paper,13 1/2 x 9 in. (34.3 x 22.9 cm),"Gift of Judith Underwood Stewart, in memory of Martha Davenport Heard, 2001",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/62028,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2001.612.2,false,true,62029,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kuniyoshi,"Japanese, 1797–1861",,Utagawa Kuniyoshi,Japanese,1797,1861,ca. 1845–46,1835,1856,Polychrome woodblock print; ink and color on paper,13 1/2 x 9 in. (34.3 x 22.9 cm),"Gift of Judith Underwood Stewart, in memory of Martha Davenport Heard, 2001",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/62029,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2001.612.3,false,true,62030,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kuniyoshi,"Japanese, 1797–1861",,Utagawa Kuniyoshi,Japanese,1797,1861,ca. 1845–46,1835,1856,Polychrome woodblock print; ink and color on paper,13 1/2 x 9 in. (34.3 x 22.9 cm),"Gift of Judith Underwood Stewart, in memory of Martha Davenport Heard, 2001",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/62030,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2001.612.4,false,true,62031,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kuniyoshi,"Japanese, 1797–1861",,Utagawa Kuniyoshi,Japanese,1797,1861,ca. 1845–46,1835,1856,Polychrome woodblock print; ink and color on paper,13 1/2 x 9 in. (34.3 x 22.9 cm),"Gift of Judith Underwood Stewart, in memory of Martha Davenport Heard, 2001",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/62031,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2001.612.5,false,true,62032,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kuniyoshi,"Japanese, 1797–1861",,Utagawa Kuniyoshi,Japanese,1797,1861,ca. 1845–46,1835,1856,Polychrome woodblock print; ink and color on paper,13 1/2 x 9 in. (34.3 x 22.9 cm),"Gift of Judith Underwood Stewart, in memory of Martha Davenport Heard, 2001",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/62032,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2001.715.6,false,true,63361,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kuniyoshi,"Japanese, 1797–1861",,Utagawa Kuniyoshi,Japanese,1797,1861,ca. 1848,1838,1858,Polychrome woodblock print; ink and color on paper,"Oban tate-e, 14 7/8 x 10 in. (37.8 x 25.4 cm)","Purchase, Arnold Weinstein Gift, 2001",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/63361,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2001.715.7,false,true,63364,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kuniyoshi,"Japanese, 1797–1861",,Utagawa Kuniyoshi,Japanese,1797,1861,ca. 1848,1838,1858,Polychrome woodblock print; ink and color on paper,"Oban tate-e, 14 3/4 x 10 1/8 in. (37.5 x 25.7 cm)","Purchase, Arnold Weinstein Gift, 2001",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/63364,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2001.715.8,false,true,63371,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kuniyoshi,"Japanese, 1797–1861",,Utagawa Kuniyoshi,Japanese,1797,1861,ca. 1830,1820,1840,Polychrome woodblock print; ink and color on paper,"Oban tate-e, 17 7/8 x 10 1/8 in. (45.4 x 25.7 cm)","Purchase, Arnold Weinstein Gift, 2001",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/63371,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2001.715.9,false,true,63374,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kuniyoshi,"Japanese, 1797–1861",,Utagawa Kuniyoshi,Japanese,1797,1861,"1856, 2nd month",1856,1856,Polychrome woodblock print; ink and color on paper,"Oban tate-e, 14 5/8 x 10 in. (37.1 x 25.4 cm)","Purchase, Arnold Weinstein Gift, 2001",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/63374,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.277,false,true,73560,Asian Art,Woodblock print,"二十四孝童子鑑  楊香|Yang Xiang (Yō Kō), from the series A Child’s Mirror of the Twenty-four Paragons of Filial Piety (Nijūshi kō dōji kagami)",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kuniyoshi,"Japanese, 1797–1861",,Utagawa Kuniyoshi,Japanese,1797,1861,ca. 1843,1843,1843,Polychrome woodblock print; ink and color on paper,Image: 8 5/8 x 13 7/8 in. (21.9 x 35.2 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73560,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1130a–c,false,true,55050,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kuniyoshi,"Japanese, 1797–1861",,Utagawa Kuniyoshi,Japanese,1797,1861,ca. 1850,1840,1860,"Triptych of polychrome woodblock prints; ink, silver, and color on paper",a) H. 14 7/8 in. (37.8 cm); W. 10 1/8 in. (25.7 cm) b): H. 14 3/4 in. (37.5 cm); W. 10 3/8 in. (26.4 cm) c): H. 14 13/16 in. (37.6 cm); W. 10 1/16 in. (25.6 cm),"Rogers Fund, 1919",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55050,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3518a–j,false,true,45005,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kuniyoshi,"Japanese, 1797–1861",,Utagawa Kuniyoshi,Japanese,1797,1861,mid-19th century,1834,1866,Polychrome woodblock print; ink and color on paper,H. 5 7/8 in. (14.9 cm); W. 4 1/8 in. (10.5 cm),"Gift of Lincoln Kirstein, 1966",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45005,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2011.133a, b",false,true,76563,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Gyokuryūtei Shigeharu,"Japanese, 1803–1853",,Gyokuryūtei Shigeharu,Japanese,1803,1853,1830,1830,1830,Diptych of polychrome woodblock prints,Each sheet ( ôban tate-e diptych): 14 1/2 x 9 3/4 in. (36.8 x 24.8 cm),"Purchase, Friends of Asian Art Gifts, in honor of James C. Y. Watt, 2011",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/76563,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2743,false,true,45265,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Shibata Zeshin,"Japanese, 1807–1891",,Shibata Zeshin,Japanese,1807,1891,1860,1700,1868,Polychrome woodblock print; ink and color on paper,9 1/8 x 6 3/8 in. (23.2 x 16.2 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45265,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2746,false,true,54205,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Shibata Zeshin,"Japanese, 1807–1891",,Shibata Zeshin,Japanese,1807,1891,1862 (Dog Year),1862,1862,Polychrome woodblock print (surimono); ink and color on paper,7 7/8 x 10 3/4 in. (20 x 27.3 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54205,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3172,false,true,73598,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Shibata Zeshin,"Japanese, 1807–1891",,Shibata Zeshin,Japanese,1807,1891,ca. 1860,1860,1860,Polychrome woodblock print; ink and color on paper,Image: 7 3/4 × 10 7/8 in. (19.7 × 27.6 cm) Mat: 15 1/4 × 22 3/4 in. (38.7 × 57.8 cm),"Purchase, Gifts, Bequests, and Funds from various donors, by exchange, 1955",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73598,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3367,false,true,55534,Asian Art,Print,Fusen zu|写真鏡・風船図|Picture of a Balloon,Japan,Edo period (1615–1868),,,,Artist,,Miyagi Gengyo,"Japanese, 1817–1880",,Gengyo,Japanese,1817,1880,1860,1860,1860,Polychrome woodblock print; ink and color on paper,14 x 9 3/8 in. (35.6 x 23.8 cm),"Gift of Lincoln Kirstein, 1960",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55534,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1093.3,false,true,58255,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Yoshitsuya,"Japanese, 1822–1866",,Utagawa Yoshitsuya,Japanese,1822,1866,mid-19th century,1822,1866,Polychrome woodblock print; ink and color on paper,Image: 14 1/4 × 9 1/2 in. (36.2 × 24.1 cm),The Metropolitan Museum of Art,,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/58255,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.255,false,true,73546,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Yoshitsuya,"Japanese, 1822–1866",,Utagawa Yoshitsuya,Japanese,1822,1866,"5th month, 1863",1863,1863,Polychrome woodblock print; ink and color on paper,Image: 13 7/8 x 9 3/4 in. (35.2 x 24.8 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73546,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2632,false,true,54197,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada II,"Japanese, 1823–1880",,Utagawa Kunisada II,Japanese,1823,1880,1852–64,1852,1864,Polychrome woodblock print (surimono); ink and color on paper,8 1/8 x 7 1/8 in. (20.6 x 18.1 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54197,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP212.59,false,true,58043,Asian Art,Print,"岩井紫若(二代目)の道具屋娘おかめ|Iwai Shijaku II as Okame, the Daughter of a Furniture Store",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada II,"Japanese, 1823–1880",,Utagawa Kunisada II,Japanese,1823,1880,1864,1864,1864,Right panel of a triptych of polychrome woodblock prints; ink and color on paper,Image: 13 5/8 × 9 5/8 in. (34.6 × 24.4 cm),"Gift of the Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/58043,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.434.5,false,true,75302,Asian Art,Print,大坂下り早竹虎吉|Hayatake Torakichi from Osaka: Performance in Ryōgoku,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada II,"Japanese, 1823–1880",,Utagawa Kunisada II,Japanese,1823,1880,1857,1857,1857,Polychrome woodblock print; ink and color on paper,14 1/2 x 10 in. (36.8 x 25.4 cm),"Gift of Takemitsu Oba, 2009",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/75302,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3320,false,true,55464,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Yoshifuji,"Japanese, 1828–1887",,Utagawa Yoshifuji,Japanese,1828,1887,1861,1861,1861,Polychrome woodblock print; ink and color on paper,14 x 9 in. (35.6 x 22.9 cm),"Gift of Lincoln Kirstein, 1959",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55464,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3322,false,true,55467,Asian Art,Print,Worosiiazin yuko|魯西亜人遊行|Russians Strolling,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Yoshifuji,"Japanese, 1828–1887",,Utagawa Yoshifuji,Japanese,1828,1887,February 1861,1861,1861,Polychrome woodblock print; ink and color on paper,14 x 9 1/2 in. (35.6 x 24.1 cm),"Gift of Lincoln Kirstein, 1959",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55467,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3424,false,true,37390,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Yoshifuji,"Japanese, 1828–1887",,Utagawa Yoshifuji,Japanese,1828,1887,1867,1867,1867,Triptych of polychrome woodblock prints; ink and color on paper,Oban; 14 1/8 x 28 3/4 in. (35.9 x 73 cm),"Gift of Lincoln Kirstein, 1962",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37390,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.251,false,true,73542,Asian Art,Print,Amerikajin|An American Family,Japan,Edo period (1615–1868),,,,Artist,,Ippōsai Yoshifuji,"Japanese, 1828–1887",,Ippōsai Yoshifuji,Japanese,1828,1887,"2nd, 1861",1861,1861,Polychrome woodblock print; ink and color on paper,Image: 14 1/2 x 10 in. (36.8 x 25.4 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73542,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.252,false,true,73543,Asian Art,Print,Oroshiajin|Russian Soldier with His Family,Japan,Edo period (1615–1868),,,,Artist,,Ippōsai Yoshifuji,"Japanese, 1828–1887",,Ippōsai Yoshifuji,Japanese,1828,1887,"2nd month, 1861",1861,1861,Polychrome woodblock print; ink and color on paper,Image: 14 1/8 x 9 3/4 in. (35.9 x 24.8 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73543,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.253,false,true,73544,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Ippōsai Yoshifuji,"Japanese, 1828–1887",,Ippōsai Yoshifuji,Japanese,1828,1887,"2nd month, 1861",1861,1861,Polychrome woodblock print; ink and color on paper,Image: 14 1/2 x 10 in. (36.8 x 25.4 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73544,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.254,false,true,73545,Asian Art,Woodblock print,Gojin jūshin no hataraku|Five People Working Like Ten,Japan,Edo period (1615–1868),,,,Artist,,Ippōsai Yoshifuji,"Japanese, 1828–1887",,Ippōsai Yoshifuji,Japanese,1828,1887,"3rd month, 1861",1861,1861,Polychrome woodblock print; ink and color on paper,Image: 14 1/4 x 10 in. (36.2 x 25.4 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73545,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.434.1,false,true,75298,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Yoshiharu,"Japanese, 1828–1888",,Utagawa Yoshiharu,Japanese,1828,1888,1857,1857,1857,Polychrome woodblock print; ink and color on paper,14 1/4 x 10 in. (36.2 x 25.4 cm),"Gift of Takemitsu Oba, 2009",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/75298,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.434.2,false,true,75299,Asian Art,Print,大坂下り早竹虎吉|The Plum Blossom that Flew on Lightning from Chikushino,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Yoshiharu,"Japanese, 1828–1888",,Utagawa Yoshiharu,Japanese,1828,1888,1857,1857,1857,Polychrome woodblock print; ink and color on paper,14 1/4 x 9 5/8 in. (36.2 x 24.4 cm),"Gift of Takemitsu Oba, 2009",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/75299,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.434.3,false,true,75300,Asian Art,Print,大坂下り早竹虎吉|Hayatake Torakichi from Osaka,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Yoshiharu,"Japanese, 1828–1888",,Utagawa Yoshiharu,Japanese,1828,1888,1857,1857,1857,Polychrome woodblock print; ink and color on paper,14 1/4 x 10 in. (36.2 x 25.4 cm),"Gift of Takemitsu Oba, 2009",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/75300,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.434.4,false,true,75301,Asian Art,Print,大坂下り早竹虎吉|Hayatake Torakichi from Osaka: Spinning Tops in Ryogoku,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Yoshiharu,"Japanese, 1828–1888",,Utagawa Yoshiharu,Japanese,1828,1888,1857,1857,1857,Polychrome woodblock print; ink and color on paper,14 1/4 x 10 1/8 in. (36.2 x 25.7 cm),"Gift of Takemitsu Oba, 2009",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/75301,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP236,false,true,36708,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige II,"Japanese, 1829–1869",,Utagawa Hiroshige II,Japanese,1829,1869,1859,1859,1859,Polychrome woodblock print; ink and color on paper,H. 14 1/8 in. (35.9 cm); W. 9 1/2 in. (24.1 cm),"Rogers Fund, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36708,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1195,false,true,45263,Asian Art,Print,Kyoto Shijo yu-suzumi|Cooling Off at the Kamo River near Shijo in Kyoto,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige II,"Japanese, 1829–1869",,Utagawa Hiroshige II,Japanese,1829,1869,ca. 1860,1850,1870,Polychrome woodblock print; ink and color on paper,H. 14 5/8 in. (37.1 cm); W. 9 7/8 in. (25.1 cm),"Rogers Fund, 1919",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45263,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1196,false,true,55113,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige II,"Japanese, 1829–1869",,Utagawa Hiroshige II,Japanese,1829,1869,1859–61,1859,1861,Polychrome woodblock print; ink and color on paper,H. 14 7/16 in. (36.7 cm); W. 9 7/8 in. (25.1 cm),"Rogers Fund, 1919",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55113,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1485,false,true,55579,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige II,"Japanese, 1829–1869",,Utagawa Hiroshige II,Japanese,1829,1869,1862,1862,1862,Polychrome woodblock print; ink and color on paper,H. 9 13/16 in. (24.9 cm); W. 14 5/8 in. (37.1 cm),"Rogers Fund, 1926",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55579,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1549,false,true,55713,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige II,"Japanese, 1829–1869",,Utagawa Hiroshige II,Japanese,1829,1869,ca. 1859,1849,1899,Polychrome woodblock print; ink and color on paper,H. 14 1/8 in. (35.9 cm); W. 9 1/2 in. (24.1 cm),"Fletcher Fund, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55713,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2524,false,true,56937,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige II,"Japanese, 1829–1869",,Utagawa Hiroshige II,Japanese,1829,1869,1859,1859,1859,Polychrome woodblock print; ink and color on paper,14 3/4 x 10 in. (37.5 x 25.4 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56937,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1093.7,false,true,58259,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige II,"Japanese, 1829–1869",,Utagawa Hiroshige II,Japanese,1829,1869,mid-19th century,1834,1866,Polychrome woodblock print; ink and color on paper,Image: 14 3/4 × 9 7/8 in. (37.5 × 25.1 cm),The Metropolitan Museum of Art,,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/58259,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.136,false,true,73422,Asian Art,Print,諸国名所百景 肥前長崎唐船の津|Dutch and Chinese Ships in the Harbor at Nagasaki in Hizen Province,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige II,"Japanese, 1829–1869",,Utagawa Hiroshige II,Japanese,1829,1869,"3rd month, 1859",1859,1859,Polychrome woodblock print; ink and color on paper,Overall: 14 1/4 x 9 3/4 in. (36.2 x 24.8 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73422,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.137,false,true,73423,Asian Art,Print,諸国名所百景 對州海岸|Dutch Ship at Anchor off the Coast of Tsushima,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige II,"Japanese, 1829–1869",,Utagawa Hiroshige II,Japanese,1829,1869,"3rd month, 1859",1859,1859,Polychrome woodblock print; ink and color on paper,Image: 14 1/4 x 9 1/2 in. (36.2 x 24.1 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73423,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.138,false,true,73424,Asian Art,Print,"Bushu Yokohama Gankirō|諸国名所百景 武州横浜岩亀楼|Entrance to the Gankirō Tea House in the Miyozaki District, Yokohama, Bushu",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige II,"Japanese, 1829–1869",,Utagawa Hiroshige II,Japanese,1829,1869,"3rd month, 1859",1859,1859,Polychrome woodblock print; ink and color on paper,Image: 14 1/4 x 9 3/4 in. (36.2 x 24.8 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73424,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.139,false,true,73425,Asian Art,Woodblock print,"横浜賣物図会の内 唐犬|Copper Plate Engraving of a Woman Riding a Horse, a Goat and a Dog",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige II,"Japanese, 1829–1869",,Utagawa Hiroshige II,Japanese,1829,1869,"3rd month, 1860",1860,1860,Polychrome woodblock print; ink and color on paper,Image: 14 3/4 x 10 in. (37.5 x 25.4 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73425,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.142,false,true,73428,Asian Art,Woodblock print,「亞墨利加」|America: A Woman on Horseback in the Snow,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige II,"Japanese, 1829–1869",,Utagawa Hiroshige II,Japanese,1829,1869,"10th month, 1860",1860,1860,Polychrome woodblock print; ink and color on paper,Image: 14 x 9 7/8 in. (35.6 x 25.1 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73428,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.143,false,true,73429,Asian Art,Print,"Furansu|ふらんす|French Woman, Her Child and Pet Dog",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige II,"Japanese, 1829–1869",,Utagawa Hiroshige II,Japanese,1829,1869,"10th month, 1860",1860,1860,Polychrome woodblock print; ink and color on paper,Image: 14 3/4 x 10 1/4 in. (37.5 x 26 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73429,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.144,false,true,73430,Asian Art,Print,南京 於魯西亜|Russians and a Chinese Inscribing a Fan,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige II,"Japanese, 1829–1869",,Utagawa Hiroshige II,Japanese,1829,1869,"12th month, 1860",1860,1860,Polychrome woodblock print; ink and color on paper,Image: 14 1/2 x 9 3/4 in. (36.8 x 24.8 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73430,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.145,false,true,73431,Asian Art,Print,"亜墨利加 横浜本村本牧道|American Woman Riding Side-Saddle on the Road at Hommoku, Motomura, Yokohama",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige II,"Japanese, 1829–1869",,Utagawa Hiroshige II,Japanese,1829,1869,"2nd month, 1861",1861,1861,Polychrome woodblock print; ink and color on paper,Image: 14 3/4 x 10 in. (37.5 x 25.4 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73431,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.147,false,true,73433,Asian Art,Print,Tōtō Takanawa Kaigen|Foreigners Riding Along the Coast at Takanawa in the Eastern Capital,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige II,"Japanese, 1829–1869",,Utagawa Hiroshige II,Japanese,1829,1869,"9th month, 1861",1861,1861,Polychrome woodblock print; ink and color on paper,Image: 14 1/4 x 10 5/8 in. (36.2 x 27 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73433,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.148,false,true,73434,Asian Art,Print,Tōtō Takanawa Kaigen|Foreigners Riding Along the Coast at Takanawa in the Eastern Capital,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige II,"Japanese, 1829–1869",,Utagawa Hiroshige II,Japanese,1829,1869,"9th month, 1861",1861,1861,Polychrome woodblock print; ink and color on paper,Image: 14 3/8 x 10 in. (36.5 x 25.4 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73434,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.140a–c,false,true,73426,Asian Art,Print,Yokohama Gankirō no zu|View of the Interior of the Gankirō Tea House in Yokohama,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige II,"Japanese, 1829–1869",,Utagawa Hiroshige II,Japanese,1829,1869,"4th month, 1860",1860,1860,Triptych of polychrome woodblock prints; ink and color on paper,Image (a): 14 3/4 x 10 in. (37.5 x 25.4 cm) Image (b): 14 5/8 x 10 in. (37.1 x 25.4 cm) Image (c): 14 1/2 x 10 in. (36.8 x 25.4 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73426,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.141a–c,false,true,73427,Asian Art,Print,Yokohama Gankirō age|横浜岩亀楼上|Upper Floor of the Gankirō Tea House in Yokohama,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige II,"Japanese, 1829–1869",,Utagawa Hiroshige II,Japanese,1829,1869,"4th month, 1860",1860,1860,Triptych of polychrome woodblock prints; ink and color on paper,Image (a): 14 3/4 x 10 in. (37.5 x 25.4 cm) Image (b): 14 3/4 x 10 in. (37.5 x 25.4 cm) Image (c): 14 3/4 x 10 in. (37.5 x 25.4 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73427,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.149a–c,false,true,73435,Asian Art,Print,Yokohama ijin kyaku no zu|Foreigner's Residence in Yokohama,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige II,"Japanese, 1829–1869",,Utagawa Hiroshige II,Japanese,1829,1869,"10th month, 1861",1861,1861,Triptych of polychrome woodblock prints; ink and color on paper,Image (a): 14 x 9 1/2 in. (35.6 x 24.1 cm) Image (b): 14 x 9 1/2 in. (35.6 x 24.1 cm) Image (c): 14 x 10 in. (35.6 x 25.4 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73435,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3305,false,true,55406,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Ichiryūsai Yoshitoyo,"Japanese, 1830–1866",,Ichiryūsai Yoshitoyo,Japanese,1830,1866,1863 (3rd month),1863,1863,Diptych of polychrome woodblock prints; ink and color on paper,,"Gift of Lincoln Kirstein, 1959",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55406,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3334,false,true,55482,Asian Art,Print,Furansu-jin Yukyo|フランス人遊興|French Pastimes,Japan,Edo period (1615–1868),,,,Artist,,Ichiryūsai Yoshitoyo,"Japanese, 1830–1866",,Ichiryūsai Yoshitoyo,Japanese,1830,1866,"1860 (Man–en, 1st year)",1860,1860,Polychrome woodblock print; ink and color on paper,14 1/2 x 10 in. (36.8 x 25.4 cm),"Gift of Lincoln Kirstein, 1959",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55482,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.244,false,true,73535,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Ichiryūsai Yoshitoyo,"Japanese, 1830–1866",,Ichiryūsai Yoshitoyo,Japanese,1830,1866,"7th month, 1860",1860,1860,Polychrome woodblock print; ink and color on paper,Image: 14 x 9 1/2 in. (35.6 x 24.1 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73535,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.245,false,true,73536,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Ichiryūsai Yoshitoyo,"Japanese, 1830–1866",,Ichiryūsai Yoshitoyo,Japanese,1830,1866,"7th month, 1860",1860,1860,Polychrome woodblock print; ink and color on paper,Image: 14 1/2 x 10 in. (36.8 x 25.4 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73536,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.246,false,true,73537,Asian Art,Print,Igirisujin ryōkō no zu|Englishman Walking for Pleasure,Japan,Edo period (1615–1868),,,,Artist,,Ichiryūsai Yoshitoyo,"Japanese, 1830–1866",,Ichiryūsai Yoshitoyo,Japanese,1830,1866,"12th month, 1860",1860,1860,Polychrome woodblock print; ink and color on paper,Image: 13 7/8 x 10 in. (35.2 x 25.4 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73537,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.247,false,true,73538,Asian Art,Print,英吉利人之図|Illustration of English People (Igirisujin no zu),Japan,Edo period (1615–1868),,,,Artist,,Ichiryūsai Yoshitoyo,"Japanese, 1830–1866",,Ichiryūsai Yoshitoyo,Japanese,1830,1866,"1st month, 1861",1861,1861,Polychrome woodblock print; ink and color on paper,Image: 13 7/8 x 9 5/8 in. (35.2 x 24.4 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73538,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.248,false,true,73539,Asian Art,Print,Orandajin no zu|Dutchman with Black Servant,Japan,Edo period (1615–1868),,,,Artist,,Ichiryūsai Yoshitoyo,"Japanese, 1830–1866",,Ichiryūsai Yoshitoyo,Japanese,1830,1866,"1st month, 1861",1861,1861,Polychrome woodblock print; ink and color on paper,Image: 14 3/8 x 9 3/4 in. (36.5 x 24.8 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73539,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.250,false,true,73541,Asian Art,Woodblock print,Chū tenjiku Maruka koku shūsshō daizō zu|Picture of an Elephant Born in Maruka in Central India,Japan,Edo period (1615–1868),,,,Artist,,Ichiryūsai Yoshitoyo,"Japanese, 1830–1866",,Ichiryūsai Yoshitoyo,Japanese,1830,1866,"2nd month, 1863",1863,1863,Polychrome woodblock print; ink and color on paper,Image: 14 1/8 x 9 1/2 in. (35.9 x 24.1 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73541,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2007.49.249a, b",false,true,73540,Asian Art,Woodblock print,Shintō hakurai no daizō|Newly Imported Great Elephant,Japan,Edo period (1615–1868),,,,Artist,,Ichiryūsai Yoshitoyo,"Japanese, 1830–1866",,Ichiryūsai Yoshitoyo,Japanese,1830,1866,"2nd month, 1863",1863,1863,Diptych of polychrome woodblock prints; ink and color on paper,"Image (a): 14 1/4 x 9 1/2 in. (36.2 cm, 24130 g) Image (b): 14 1/4 x 9 1/2 in. (36.2 x 24.1 cm)","Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73540,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1093.8,false,true,58260,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kuniteru,"Japanese, 1830–1874",,Utagawa Kuniteru,Japanese,1830,1874,1853,1853,1853,Polychrome woodblock print; ink and color on paper,Image: 14 1/2 × 9 3/4 in. (36.8 × 24.8 cm),The Metropolitan Museum of Art,,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/58260,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1093.9,false,true,58261,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kuniteru,"Japanese, 1830–1874",,Utagawa Kuniteru,Japanese,1830,1874,1853,1853,1853,Polychrome woodblock print; ink and color on paper,Image: 14 3/4 × 9 7/8 in. (37.5 × 25.1 cm),The Metropolitan Museum of Art,,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/58261,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.237,false,true,73528,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Yoshimori,"Japanese, 1830–1884",,Utagawa Yoshimori,Japanese,1830,1884,"4th month, 1860",1860,1860,Polychrome woodblock print; ink and color on paper,Image: 14 3/4 x 9 3/4 in. (37.5 x 24.8 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73528,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.238,false,true,73529,Asian Art,Woodblock print,"歌川芳盛画 「鳥獣図會」豹と阿蘭陀婦人|“Dutchwoman with Leopard,” from the series Pictures of Birds and Animals (Chōjū zue)",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Yoshimori,"Japanese, 1830–1884",,Utagawa Yoshimori,Japanese,1830,1884,"7th month, 1860",1860,1860,Polychrome woodblock print; ink and color on paper,Image: 14 1/4 x 9 1/2 in. (36.2 x 24.1 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73529,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.239,false,true,73530,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Yoshimori,"Japanese, 1830–1884",,Utagawa Yoshimori,Japanese,1830,1884,"11th month, 1860",1860,1860,Polychrome woodblock print; ink and color on paper,Image: 14 3/4 x 9 7/8 in. (37.5 x 25.1 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73530,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.240,false,true,73531,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Yoshimori,"Japanese, 1830–1884",,Utagawa Yoshimori,Japanese,1830,1884,"11th month, 1860",1860,1860,Polychrome woodblock print; ink and color on paper,Image: 14 1/2 x 9 3/4 in. (36.8 x 24.8 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73531,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.241,false,true,73532,Asian Art,Print,Amerikajin|Mounted American Woman,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Yoshimori,"Japanese, 1830–1884",,Utagawa Yoshimori,Japanese,1830,1884,"12th month, 1860",1860,1860,Polychrome woodblock print; ink and color on paper,Image: 14 5/8 x 9 7/8 in. (37.1 x 25.1 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73532,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.262,false,true,73553,Asian Art,Woodblock print,"「今昔未見 生物猛虎之真図」|Never Seen Before: True Picture of a Live Wild Tiger (Konjaku miken, Ikimono mōko no shinzu)",Japan,Edo period (1615–1868),,,,Artist,,Kawanabe Kyōsai,"Japanese, 1831–1889",,Kawanabe Kyōsai,Japanese,1831,1889,sixth month 1860,1860,1860,Polychrome woodblock print; ink and color on paper,Image: 14 3/8 x 9 7/8 in. (36.5 x 25.1 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73553,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3335,false,true,55483,Asian Art,Print,Roshiajin|Russians,Japan,Edo period (1615–1868),,,,Artist,,Kunihisa,"Japanese, 1832–1891",,Kunihisa,Japanese,1832,1891,1861 (10th month),1861,1861,Polychrome woodblock print; ink and color on paper,14 3/4 x 10 in. (37.5 x 25.4 cm),"Gift of Lincoln Kirstein, 1959",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55483,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3181,false,true,55129,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Yoshiiku,"Japanese, 1833–1904",,Utagawa Yoshiiku,Japanese,1833,1904,1860,1860,1860,Polychrome woodblock print; ink and color on paper,14 1/2 x 9 3/4 in. (36.8 x 24.8 cm),"GIft of Lincoln Kirstein, 1959",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55129,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3184,false,true,53243,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Yoshiiku,"Japanese, 1833–1904",,Utagawa Yoshiiku,Japanese,1833,1904,1861,1861,1861,Polychrome woodblock print; ink and color on paper,Oban 13 3/4 x 9 3/4 in. (34.9 x 24.8 cm),"GIft of Lincoln Kirstein, 1959",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/53243,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3197,false,true,55181,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Yoshiiku,"Japanese, 1833–1904",,Utagawa Yoshiiku,Japanese,1833,1904,1865,1865,1865,Polychrome woodblock print; ink and color on paper,9 5/16 x 14 in. (23.6 x 35.5 cm),"Gift of Lincoln Kirstein, 1959",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55181,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3682,false,true,45280,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Yoshiiku,"Japanese, 1833–1904",,Utagawa Yoshiiku,Japanese,1833,1904,1864,1615,1868,Polychrome woodblock print; ink and color on paper,14 1/2 x 10 in. (36.8 x 25.4 cm),"Gift of Lincoln Kirstein, 1985",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45280,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3314a,false,true,55439,Asian Art,Woodblock print,亜米利加・南京|An American on Horseback and a Chinese with a Furled Umbrella,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Yoshiiku,"Japanese, 1833–1904",,Utagawa Yoshiiku,Japanese,1833,1904,"12th month, 1860",1860,1860,Polychrome woodblock print; ink and color on paper,14 x 9 in. (35.6 x 22.9 cm),"Gift of Lincoln Kirstein, 1959",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55439,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3314b,false,true,55440,Asian Art,Print,魯西亞 ・英吉利|An English Man and a Russian Woman,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Yoshiiku,"Japanese, 1833–1904",,Utagawa Yoshiiku,Japanese,1833,1904,"12th month, 1860",1860,1860,Polychrome woodblock print; ink and color on paper,14 1/2 x 9 1/2 in. (36.8 x 24.1 cm),"Gift of Lincoln Kirstein, 1959",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55440,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.225,false,true,73516,Asian Art,Print,Sumo no homane|Sumo Wrestler Tossing a Foreigner,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Yoshiiku,"Japanese, 1833–1904",,Utagawa Yoshiiku,Japanese,1833,1904,"1st month, 1861",1861,1861,Polychrome woodblock print; ink and color on paper,Image: 14 1/4 x 9 3/8 in. (36.2 x 23.8 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73516,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.226,false,true,73517,Asian Art,Print,Bijin zu|Beauties,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Yoshiiku,"Japanese, 1833–1904",,Utagawa Yoshiiku,Japanese,1833,1904,"1st month, 1861",1861,1861,Polychrome woodblock print; ink and color on paper,Image: 14 5/8 x 9 3/8 in. (37.1 x 23.8 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73517,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.227,false,true,73518,Asian Art,Print,Oroshia|Russian Couple,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Yoshiiku,"Japanese, 1833–1904",,Utagawa Yoshiiku,Japanese,1833,1904,"1st month, 1861",1861,1861,Polychrome woodblock print; ink and color on paper,Image: 14 1/4 x 9 7/8 in. (36.2 x 25.1 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73518,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.228,false,true,73519,Asian Art,Print,Oroshia|Chinese Servant and Frenchman,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Yoshiiku,"Japanese, 1833–1904",,Utagawa Yoshiiku,Japanese,1833,1904,"2nd month, 1861",1861,1861,Polychrome woodblock print; ink and color on paper,Image: 15 3/4 x 11 1/4 in. (40 x 28.6 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73519,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.231,false,true,73522,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Yoshiiku,"Japanese, 1833–1904",,Utagawa Yoshiiku,Japanese,1833,1904,"10th month, 1861",1861,1861,Polychrome woodblock print; ink and color on paper,Image: 14 1/4 x 9 7/8 in. (36.2 x 25.1 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73522,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.232,false,true,73523,Asian Art,Print,Igirisu to korombojin|English Woman with Black Man,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Yoshiiku,"Japanese, 1833–1904",,Utagawa Yoshiiku,Japanese,1833,1904,"11th month, 1861",1861,1861,Polychrome woodblock print; ink and color on paper,Image: 14 1/2 x 9 3/4 in. (36.8 x 24.8 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73523,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.233,false,true,73524,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Yoshiiku,"Japanese, 1833–1904",,Utagawa Yoshiiku,Japanese,1833,1904,"7th month, 1863",1863,1863,Polychrome woodblock print; ink and color on paper,Image: 13 x 8 5/8 in. (33 x 21.9 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73524,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.224a–c,false,true,73515,Asian Art,Print,Gok'koku o Gankirō sakamori no zu|The Five Nations Enjoying a Drunken Revel at the Gankirō Tea House,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Yoshiiku,"Japanese, 1833–1904",,Utagawa Yoshiiku,Japanese,1833,1904,"12th month, 1860",1860,1860,Triptych of polychrome woodblock prints; ink and color on paper,Image (a): 14 3/4 x 10 in. (37.5 x 25.4 cm) Image (b): 14 3/4 x 10 in. (37.5 x 25.4 cm) Image (c): 14 3/4 x 10 in. (37.5 x 25.4 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73515,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.229a–c,false,true,73520,Asian Art,Print,万国男女人物図絵|Picture of Men and Women from all Nations (Bankoku danjo jinbutsu zue),Japan,Edo period (1615–1868),,,,Artist,,Utagawa Yoshiiku,"Japanese, 1833–1904",,Utagawa Yoshiiku,Japanese,1833,1904,"4th month, 1861",1861,1861,Triptych of polychrome woodblock prints; ink and color on paper,Image (a): 14 x 9 3/4 in. (35.6 x 24.8 cm) Image (b): 14 x 9 3/4 in. (35.6 x 24.8 cm) Image (c): 14 x 9 3/4 in. (35.6 x 24.8 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73520,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.230a–c,false,true,73521,Asian Art,Print,Bankoku danjō jimbutsu zue|Picture of Men and Women from all nations (Bankoku danjo jinbutsu zue),Japan,Edo period (1615–1868),,,,Artist,,Utagawa Yoshiiku,"Japanese, 1833–1904",,Utagawa Yoshiiku,Japanese,1833,1904,"4th month, 1861",1861,1861,Triptych of polychrome woodblock prints; ink and color on paper,Image (a): 14 3/4 x 10 in. (37.5 x 25.4 cm) Image (b): 14 3/4 x 10 in. (37.5 x 25.4 cm) Image (c): 14 3/4 x 10 in. (37.5 x 25.4 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73521,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1094a,false,true,55029,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kuniaki II,"Japanese, 1835–1888",,Utagawa Kuniaki II,Japanese,1835,1888,"1862 (Bunkyū 2), 6th month",1862,1862,Polychrome woodblock print; ink and color on paper,Image: 14 1/4 × 9 1/4 in. (36.2 × 23.5 cm),The Metropolitan Museum of Art,,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55029,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1094b,false,true,638607,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kuniaki II,"Japanese, 1835–1888",,Utagawa Kuniaki II,Japanese,1835,1888,"1862 (Bunkyū 2), 6th month",1862,1862,Polychrome woodblock print; ink and color on paper,Image: 14 in. × 9 1/2 in. (35.6 × 24.1 cm),The Metropolitan Museum of Art,,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/638607,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1094c,false,true,638609,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kuniaki II,"Japanese, 1835–1888",,Utagawa Kuniaki II,Japanese,1835,1888,"1862 (Bunkyū 2), 6th month",1862,1862,Polychrome woodblock print; ink and color on paper,Image: 14 in. × 9 5/8 in. (35.6 × 24.4 cm),The Metropolitan Museum of Art,,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/638609,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1094d,false,true,638610,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kuniaki II,"Japanese, 1835–1888",,Utagawa Kuniaki II,Japanese,1835,1888,"1862 (Bunkyū 2), 6th month",1862,1862,Polychrome woodblock print; ink and color on paper,Image: 14 1/4 × 9 5/8 in. (36.2 × 24.4 cm),The Metropolitan Museum of Art,,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/638610,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1094e,false,true,638611,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kuniaki II,"Japanese, 1835–1888",,Utagawa Kuniaki II,Japanese,1835,1888,"1862 (Bunkyū 2), 6th month",1862,1862,Polychrome woodblock print; ink and color on paper,Image: 13 3/4 × 9 5/8 in. (34.9 × 24.4 cm),The Metropolitan Museum of Art,,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/638611,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1094f,false,true,638612,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kuniaki II,"Japanese, 1835–1888",,Utagawa Kuniaki II,Japanese,1835,1888,"1862 (Bunkyū 2), 6th month",1862,1862,Polychrome woodblock print; ink and color on paper,Image: 14 1/16 × 9 5/8 in. (35.7 × 24.4 cm),The Metropolitan Museum of Art,,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/638612,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1094g,false,true,638613,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kuniaki II,"Japanese, 1835–1888",,Utagawa Kuniaki II,Japanese,1835,1888,"1862 (Bunkyū 2), 6th month",1862,1862,Polychrome woodblock print; ink and color on paper,Image: 14 1/8 × 9 5/8 in. (35.9 × 24.4 cm),The Metropolitan Museum of Art,,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/638613,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3178,false,true,55125,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Tsukioka Yoshitoshi,"Japanese, 1839–1892",,Tsukioka Yoshitoshi,Japanese,1839,1892,ca. 1865,1855,1875,Polychrome woodblock print; ink and color on paper,9 7/16 x 13 7/16 in. (23.9 x 34.2 cm),"Gift of Lincoln Kirstein, 1959",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55125,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.257,false,true,73548,Asian Art,Print,仏蘭西英吉利三兵大調錬之図|Maneuvers by Three Categories of French and English Soldiers (Furansu Igirisu sanhei ōchōren no zu),Japan,Edo period (1615–1868),,,,Artist,,Tsukioka Yoshitoshi,"Japanese, 1839–1892",,Tsukioka Yoshitoshi,Japanese,1839,1892,"8th month, 1867",1867,1867,Triptych of polychrome woodblock prints; ink and color on paper,Image: 14 1/2 x 28 3/16 in. (36.8 x 71.6 cm) Overall (mat): 19 x 32 in. (48.3 x 81.3 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73548,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.259,false,true,73550,Asian Art,Print,"騎兵体歩兵体大調練之図|Illustration of Cavalry, Infantry and Soldiers Retreating (Kiheitai, hoheitai, daichōren no zu)",Japan,Edo period (1615–1868),,,,Artist,,Tsukioka Yoshitoshi,"Japanese, 1839–1892",,Tsukioka Yoshitoshi,Japanese,1839,1892,"8th month, 1867",1867,1867,Triptych of polychrome woodblock prints; ink and color on paper,Image: 14 1/4 x 28 3/4 in. (36.2 x 73 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73550,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.348a–c,false,true,72826,Asian Art,Print,"武勇雪月花之内 ゑびらの梅|Plum Blossoms in the Forrest of Ikuta, from the series Bravery-Beauty of the four seasons (Buyū setsugekka no uchi-Ikuta no mori, Ebira no ume)",Japan,Edo period (1615–1868),,,,Artist,,Tsukioka Yoshitoshi,"Japanese, 1839–1892",,Tsukioka Yoshitoshi,Japanese,1839,1892,1867,1867,1867,Triptych of polychrome woodblock prints; ink and color on paper,Overall (a): 14 3/4 x 10 in. (37.5 x 25.4 cm) Overall (b): 14 9/16 x 10 in. (37 x 25.4 cm) Overall (c): 14 9/16 x 10 in. (37 x 25.4 cm),"Purchase, Friends of Asian Art Gifts, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/72826,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.351a–c,false,true,72829,Asian Art,Print,"太平記正清難戦之図|Masakiyo's Challenging Battle, from the series Taiheiki (Taiheiki, Masakiyo nansen no zu)",Japan,Edo period (1615–1868),,,,Artist,,Tsukioka Yoshitoshi,"Japanese, 1839–1892",,Tsukioka Yoshitoshi,Japanese,1839,1892,1866,1866,1866,Triptych of polychrome woodblock prints; ink and color on paper,Overall (a): 14 3/8 x 10 in. (36.5 x 25.4 cm) Overall (b): 14 3/8 x 9 7/8 in. (36.5 x 25.1 cm) Overall (c): 14 9/16 x 10 in. (37 x 25.4 cm),"Purchase, Friends of Asian Art Gifts, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/72829,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.256a–c,false,true,73547,Asian Art,Print,仏蘭西大湊諸国交易図|Illustration of a Large French Port Trading with Many Nations (Furansukoku oominato shokoku kōeki zu),Japan,Edo period (1615–1868),,,,Artist,,Tsukioka Yoshitoshi,"Japanese, 1839–1892",,Tsukioka Yoshitoshi,Japanese,1839,1892,"4th month, 1866",1866,1866,Triptych of polychrome woodblock prints; ink and color on paper,Image (a): 14 x 9 1/2 in. (35.6 x 24.1 cm) Image (b): 14 x 9 1/4 in. (35.6 x 23.5 cm) Image (c): 14 x 9 1/2 in. (35.6 x 24.1 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73547,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.258a–c,false,true,73549,Asian Art,Print,仏蘭西英吉利三兵大調錬之図|Maneuvers by Three Categories of French and English Soldiers (Furansu Igirisu sanhei ōchōren no zu),Japan,Edo period (1615–1868),,,,Artist,,Tsukioka Yoshitoshi,"Japanese, 1839–1892",,Tsukioka Yoshitoshi,Japanese,1839,1892,"8th month, 1867",1867,1867,Triptych of polychrome woodblock prints; ink and color on paper,Image (a): 14 1/2 x 9 1/4 in. (36.8 x 23.5 cm) Image (b): 14 3/8 x 9 1/4 in. (36.5 x 23.5 cm) Image (c): 14 3/8 x 9 1/2 in. (36.5 x 24.1 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73549,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3101,false,true,45055,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kaigetsudo Anchi,"Japanese, active 1714",,Kaigetsudo Anchi,Japanese,1714,1714,ca. 1714,1704,1724,Polychrome woodblock print (sumizuri-e); ink and color on paper,22 3/4 x 12 3/4 in. (57.8 x 32.4 cm),"Harris Brisbane Dick Fund and Rogers Fund, 1949",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45055,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3106,false,true,56612,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kaigetsudo Anchi,"Japanese, active 1714",,Kaigetsudo Anchi,Japanese,1714,1714,ca. 1714,1704,1724,Polychrome woodblock print; ink and color on paper,22 3/4 x 12 3/4 in. (57.8 x 32.4 cm),"Harris Brisbane Dick Fund and Rogers Fund, 1949",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56612,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP142,false,true,36621,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,ca. 1796,1786,1806,Polychrome woodblock print; ink and color on paper,H. 15 in. (38.1 cm); W. 10 in. (25.4 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36621,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP143,false,true,36622,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,ca. 1800,1790,1810,Polychrome woodblock print; ink and color on paper,20 x 8 1/2 in. (50.8 x 21.6 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36622,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP144,false,true,36623,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,1790s,1790,1799,Polychrome woodblock print; ink and color on paper,H. 14 5/8 in. (37.1 cm); W. 9 5/8 in. (24.4 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36623,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP145,false,true,36624,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,ca. 1795,1785,1805,Polychrome woodblock print; ink and color on paper,13 3/4 x 9 7/8 in. (34.9 x 25.1 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36624,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP146,false,true,36625,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,ca. 1796,1786,1806,One sheet of a hexaptych of polychrome woodblock prints; ink and color on paper,15 3/32 x 10 1/5 in. (38.3 x 25.9 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36625,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP147,false,true,36626,Asian Art,Print,歌撰恋之部 夜毎に逢恋|A Young Woman Reading A Letter,Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,1790s,1790,1799,Polychrome woodblock print; ink and color on paper,14 1/2 x 10 1/8 in. (36.8 x 25.7 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36626,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP148,false,true,36627,Asian Art,Woodblock print,女織蚕手業草 十二終|Women Weaving Silk Cloth,Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,ca. 1800,1790,1810,Polychrome woodblock print; ink and color on paper,14 7/8 x 9 3/4 in. (37.8 x 24.8 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36627,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP149,false,true,36628,Asian Art,Print,三囲神社の御開帳|Display of Treasures at Mimeguri Shrine (Mimeguri jinja no onkaichō),Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,1799,1799,1799,Triptych of polychrome woodblock prints; ink and color on paper,15 3/8 x 9 7/8 x 10 in. (39.1 x 25.1 x 25.4 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36628,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP150,false,true,36629,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,"Rat year, i.e. 1804",1804,1804,Triptych of polychrome woodblock prints; ink and color on paper,15 7/32 x 30 in. (38.7 x 76.2 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36629,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP151,false,true,36630,Asian Art,Print,蛍狩|Catching fireflies (Hotaru gari),Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,ca. 1796–97,1796,1797,Triptych of polychrome woodblock prints; ink and color on paper,Image (triptych): 13 7/8 x 28 5/8 in. (35.2 x 72.7 cm) Image (each sheet): 13 7/8 x 9 1/2 in. (35.2 x 24.1 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36630,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP152,false,true,36631,Asian Art,Woodblock print,"婦人手業操鏡|A Woman Weaving, Seated at a Hand Loom",Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,ca. 1796,1786,1806,Polychrome woodblock print; ink and color on paper,14 3/4 x 9 7/8 in. (37.5 x 25.1 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36631,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP153,false,true,36632,Asian Art,Print,実競色乃美名家見 三浦屋小紫 白井権八|The Lovers Miura-ya Komurasaki and Shirai Gonpachi.,Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,probably 1797,1795,1799,Polychrome woodblock print; ink and color on paper,15 x 9 15/32 in. (38.1 x 24.1 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36632,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP555,false,true,37006,Asian Art,Print,江戸の花 娘浄瑠璃|A Woman Playing with a Young Boy,Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,ca. 1804,1794,1814,Polychrome woodblock print; ink and color on paper,14 3/5 x 9 29/32 in. (37.1 x 25.2 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37006,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP556,false,true,37007,Asian Art,Print,名君 閨中の粧ひ|The Oiran Yoso-oi Seated at Her Toilet,Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,ca. 1799,1789,1809,Polychrome woodblock print; ink and color on paper,14 3/5 x 9 29/32 in. (37.1 x 25.2 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37007,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP663,false,true,37110,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,ca. 1800,1790,1810,Two sheets of a hexaptych of polychrome woodblock prints; ink and color on paper,14 1/4 x 20 1/8 in. (36.2 x 51.1 cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37110,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP735,false,true,37183,Asian Art,Print,新吉原仮宅両国之図|Picture of the Temporary Lodgings of the New Yoshiwara Pleasure Quarter at Ryōgoku (Shin Yoshiwara Karitaku Ryogoku no zu),Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,1784,1784,1784,Polychrome woodblock print; ink and color on paper,12 1/8 x 17 in. (30.8 x 43.2 cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37183,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP736,false,true,37184,Asian Art,Print,扇屋内春日野わかな こてう|The Oiran Kasugano of Ōgiya on Parade,Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,ca. 1788,1778,1798,Polychrome woodblock print; ink and color on paper,12 1/8 x 5 1/8 in. (30.8 x 13.0 cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37184,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP737,false,true,37185,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,1790s,1790,1799,Left-hand of a triptych of polychrome woodblock prints; ink and color on paper,H. 15 in. (38.1 cm); W. 10 1/4 in. (26 cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37185,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP738,false,true,37186,Asian Art,Print,丁子屋内雛鶴|The Oiran Hinazuru of Chojiya Standing upon a Pile of Futon,Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,ca. 1794,1784,1804,Polychrome woodblock print; ink and color on paper,12 1/8 x 8 1/2 in. (30.8 x 21.6 cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37186,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP961,false,true,37269,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,1789,1789,1789,Polychrome woodblock print; ink and color on paper,H. 8 1/4 in. (21 cm); W. 14 1/4 in. (36.2 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37269,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP962,false,true,37270,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,1789,1789,1789,Polychrome woodblock print; ink and color on paper,H. 9 in. (22.9 cm); W. 14 5/8 in. (37.1 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37270,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP963,false,true,54854,Asian Art,Print,扇屋内春日野|The Oiran Kasugano of Ogiya on Parade under Blossoming Cherry Trees,Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,ca. 1788,1778,1798,Polychrome woodblock print; ink and color on paper,H. 15 in. (38.1 cm); W. 10 in. (25.4 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54854,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP964,false,true,45250,Asian Art,Print,Mando|子供遊に和賀 万度|Lantern Float,Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,early 1800s,1800,1833,Polychrome woodblock print; ink and color on paper,H. 9 1/2 in. (24.1 cm); W. 7 in. (17.8 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45250,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP966,false,true,37272,Asian Art,Print,狂月坊|A Party of Merrymakers in a House in the Yoshiwara on a Moonlight Night,Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,1789,1789,1789,Polychrome woodblock print; ink and color on paper,9 1/8 x 14 3/5 in. (23.2 x 37.1 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37272,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP967,false,true,37273,Asian Art,Print,狂月坊|The Palace in the Moon,Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,"8th month, 1789",1789,1789,Polychrome woodblock print; ink and color on paper,9 3/8 x 14 11/16 in. (23.8 x 37.3 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37273,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP968,false,true,37274,Asian Art,Print,狂月坊|The Full Moon at the Time of the Imo Harvest,Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,"8th month, 1789",1789,1789,Polychrome woodblock print; ink and color on paper,9 1/4 x 14 3/4 in. (23.5 x 37.5 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37274,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP969,false,true,37275,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,1789,1789,1789,Polychrome woodblock print; ink and color on paper,9 1/4 x 14 3/5 in. (23.5 x 37.1 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37275,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP970,false,true,37276,Asian Art,Woodblock print,"喜多川歌麿画 『男踏歌』 鶯の餌すり|“Preparing Food for the Warbler,” from the album Men’s Stamping Dance (Otoko dōka, uguisu no esa suri)",Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,1798,1798,1798,Page from a woodblock printed book; ink and color on paper,8 1/2 x 14 in. (21.6 x 35.6 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37276,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP971,false,true,45473,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,ca. 1788,1778,1798,Polychrome woodblock print; ink and color on paper,H. 14 7/8 in. (37.8 cm); W. 9 7/8 in. (25.1 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45473,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP973,false,true,54855,Asian Art,Print,"音曲比翼の番組 小波 力弥|Rikiya and Konami, from the series A Program with Music about Loving Couples (Ongyoku hiyoku no bangumi)",Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,1798,1798,1798,Polychrome woodblock print; ink and color on paper,Aiban; H. 13 3/16 in. (33.5 cm); W. 9 in. (22.9 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54855,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP974,false,true,45009,Asian Art,Print,"『五色染六歌仙』 僧正遍昭|“The Poet Sōjō Henjō (816–890) Slipping a Letter into a Woman’s Sleeve,” from the series Five Colors of Love for the Six Poetic Immortals (Goshiki-zome rokkasen)",Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,ca. 1798,1788,1808,Polychrome woodblock print; ink and color on paper,H. 12 5/8 in. (32.1 cm); W. 8 1/2 in. (21.6 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45009,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP975,false,true,45012,Asian Art,Print,"『五色染六歌仙』 在原業平と小野小町|“The Poet Ariwara no Narihira (825–880) and Ono no Komachi,” from the series Five Colors of Love for the Six Poetic Immortals (Goshiki-zome rokkasen)",Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,ca. 1798,1788,1808,Polychrome woodblock print; ink and color on paper,H. 12 5/8 in. (32.1 cm); W. 8 1/2 in. (21.6 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45012,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP977,false,true,45013,Asian Art,Print,Ushi no Koku|青楼十二時 続 丑の刻|The Hour of the Ox (1 A.M.–3 A.M.),Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,ca. 1794,1784,1804,Polychrome woodblock print; ink and color on paper,H. 14 5/8 in. (37.1 cm); W. 9 5/8 in. (24.4 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45013,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP978,false,true,54857,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,1790s,1790,1799,Polychrome woodblock print; ink and color on paper,H. 15 1/2 in. (39.4 cm); W. 10 in. (25.4 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54857,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP979,false,true,54858,Asian Art,Print,山姥と金太郎|Yamauba Combing Her Hair and Kintoki,Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,ca. 1801,1791,1811,Polychrome woodblock print; ink and color on paper,H. 14 5/8 in. (37.1 cm); W. 10 in. (25.4 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54858,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP980,false,true,45100,Asian Art,Print,山姥と金太郎|Yamauba and Kintoki,Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,ca. 1795,1785,1805,Polychrome woodblock print; ink and color on paper,H. 15 1/8 in. (38.4 cm); W. 8 7/8 in. (22.5 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45100,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP981,false,true,54859,Asian Art,Print,山姥と金太郎|Yamauba Playing with the Young Kintoki,Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,ca. 1795,1795,1805,Polychrome woodblock print; ink and color on paper,H. 14 1/4 in. (36.2 cm); W. 9 in. (22.9 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54859,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP982,false,true,54860,Asian Art,Print,山姥と金太郎|Yamauba and Kintarō,Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,ca. 1795,1785,1805,Polychrome woodblock print; ink and color on paper,H. 15 1/8 in. (38.4 cm); W. 9 3/8 in. (23.8 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54860,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP983,false,true,54869,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,1790s,1790,1799,Polychrome woodblock print; ink and color on paper,H. 14 7/8 in. (37.8 cm); W. 9 7/8 in. (25.1 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54869,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP984,false,true,37277,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,ca. 1797,1787,1807,Triptych of polychrome woodblock prints; ink and color on paper,Overall: 15 1/8 x 30 in. (38.4 x 76.2 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37277,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP985,false,true,54870,Asian Art,Print,絵兄弟|A Woman Dressing a Girl for a Kabuki Dance (E-kyodai),Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,1790s,1790,1800,Polychrome woodblock print; ink and color on paper,H. 15 1/4 in. (38.7 cm); W. 10 1/8 in. (25.7 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54870,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP986,false,true,54871,Asian Art,Print,二葉草七小町 清水小町|Kiyomizu Komachi,Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,1790s,1790,1800,Polychrome woodblock print; ink and color on paper,H. 14 1/2 in. (36.8 cm); W. 9 1/2 in. (24.1 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54871,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP987,false,true,54872,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,ca. 1798,1788,1808,Polychrome woodblock print; ink and color on paper,H. 13 in. (33 cm); W. 8 7/8 in. (22.5 cm); Diam. of circle 8 3/8 in. (21.3 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54872,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP988,false,true,45474,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,ca. 1793,1783,1803,Polychrome woodblock print; ink and color on paper,H. 14 5/8 in. (37.1 cm); W. 10 in. (25.4 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45474,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP989,false,true,54873,Asian Art,Print,吉原時計 夜の七つ|The Seventh Hour of the Night,Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,ca. 1800,1790,1810,Polychrome woodblock print; ink and color on paper,H. 9 1/4 in. (23.5 cm); W. 6 3/4 in. (17.1 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54873,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP990,false,true,45292,Asian Art,Print,江戸仕入大津土産|Young Woman with an Otsue Demon Dressed as an Itinerant Priest,Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,ca. 1804,1794,1814,Polychrome woodblock print; ink and color on paper,14 1/2 x 9 1/2 in. (36.8 x 24.1 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45292,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP991,false,true,45291,Asian Art,Print,"江戸仕入大津土産 槍持奴 鷹匠|Souvenir Paintings from Ōtsu, Stocked in Edo (Edo shi-ire Ōtsu miyage) Foot-soldier with a Spear and Hawk-handler (Yari mochi yakko to taka shō)",Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,ca. 1802–3,1802,1803,Polychrome woodblock print; ink and color on paper,H. 14 1/2 in. (36.8 cm); W. 9 1/2 in. (24.1 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45291,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP992,false,true,37278,Asian Art,Print,太閤五妻洛東遊観之図|A View of the Pleasures of the Taiko and His Five Wives at Rakutō,Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,1804,1804,1804,Triptych of polychrome woodblock prints; ink and color on paper,15 x 30 in. (38.1 x 76.2 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37278,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP993,false,true,37279,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,ca. 1805,1795,1815,Triptych of polychrome woodblock prints; ink and color on paper,14 3/8 x 29 in. (36.5 x 73.7 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37279,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP994,false,true,37280,Asian Art,Print,柿もぎ|Picking Persimmons ((Kaki mogi)),Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,ca. 1803–4,1793,1814,Triptych of polychrome woodblock prints; ink and color on paper,15 x 30 in. (38.1 x 76.2 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37280,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP996,false,true,37281,Asian Art,Print,忠臣蔵三段目|Chushingura Act III,Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,ca. 1800,1790,1810,Polychrome woodblock print; ink and color on paper,H. 15 1/8 in. (38.4 cm); W. 10 1/2 in. (26.7 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37281,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1043,false,true,37283,Asian Art,Woodblock print,"『画本虫撰』 「蜂」「毛虫」|Paper Wasp (Hachi); Hairy Caterpillar (Kemushi), from the Picture Book of Crawling Creatures (Ehon mushi erami)",Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,1788,1788,1788,Page from woodblock printed book; ink and color on paper,10 1/2 x 7 7/32 in. (26.7 x 18.4 cm),"Rogers Fund, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37283,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1044,false,true,37284,Asian Art,Woodblock print,"『画本虫撰』 「馬追虫」「むかて」|Katydid (Umaoi-mushi); Centipede, (Mukade), from the Picture Book of Crawling Creatures (Ehon mushi erami)",Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,1788,1788,1788,Page from woodblock printed book; ink and color on paper,10 1/2 x 7 7/32 in. (26.7 x 18.4 cm),"Rogers Fund, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37284,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1045,false,true,37285,Asian Art,Woodblock print,"『画本虫撰』 「けら」「はさみむし」|Mole Cricket (Kera); Earwig, (Hasami-mushi), from the Picture Book of Crawling Creatures (Ehon mushi erami)",Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,1788,1788,1788,Page from woodblock printed book; ink and color on paper,10 1/2 x 7 7/32 in. (26.7 x 18.4 cm),"Rogers Fund, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37285,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1046,false,true,37286,Asian Art,Woodblock print,"『画本虫撰』 「蝶」「蜻蛉」|Butterfly (Chō); Dragonfly (Kagerō or Tonbo), from the Picture Book of Crawling Creatures (Ehon mushi erami)",Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,1788,1788,1788,Page from woodblock printed book; ink and color on paper,10 1/2 x 7 1/4 in. (26.7 x 18.4 cm),"Rogers Fund, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37286,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1047,false,true,37287,Asian Art,Woodblock print,"『画本虫撰』 「虻」「芋虫」|Horsefly (abu); Green Caterpillar, imomushi, from the Picture Book of Crawling Creatures (Ehon mushi erami)",Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,1788,1788,1788,Page from woodblock printed book; ink and color on paper,10 1/2 x 7 7/32 in. (26.7 x 18.4 cm),"Rogers Fund, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37287,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1048,false,true,37288,Asian Art,Woodblock print,"『画本虫撰』 「松虫」「虫蛍」|Tree cricket (Matsumushi); Firefly (Hotaru), from the Picture Book of Crawling Creatures (Ehon mushi erami)",Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,1788,1788,1788,Page from woodblock printed book; ink and color on paper,10 1/2 x 7 7/32 in. (26.7 x 18.4 cm),"Rogers Fund, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37288,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1049,false,true,37289,Asian Art,Woodblock print,"画本虫撰 バッタと蟷螂|Cone-headed Grasshopper or Locust, (batta); Praying Mantis (Tōrō or Kamakiri), from the Picture Book of Crawling Creatures (Ehon mushi erami)",Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,1788,1788,1788,Page from woodblock printed book; ink and color on paper,10 1/2 x 7 7/32 in. (26.7 x 18.4 cm),"Rogers Fund, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37289,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1050,false,true,37290,Asian Art,Print,"『画本虫撰』 「ひくらし」「くも」|Evening Cicada, Higurashi; Spider, Kumo, from the Picture Book of Crawling Creatures (Ehon mushi erami)",Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,1788,1788,1788,Page from woodblock printed book; ink and color on paper,10 1/2 x 7 7/32 in. (26.7 x 18.4 cm),"Rogers Fund, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37290,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1051,false,true,37291,Asian Art,Woodblock print,"『画本虫撰』「赤蜻蛉」「いなこ」|Red Dragonfly (Akatonbo); Locust (Inago), from the Picture Book of Crawling Creatures (Ehon mushi erami)",Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,1788,1788,1788,Page from woodblock-printed book; ink and color on paper,Overall: 10 1/2 x 7 1/4 in. (26.7 x 18.4 cm),"Rogers Fund, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37291,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1052,false,true,37292,Asian Art,Woodblock print,"『画本虫撰』「虵」「とかけ」|Rat Snake (Hebi); Lizard or Skink (Tokage), from the Picture Book of Crawling Creatures (Ehon mushi erami)",Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,1788,1788,1788,Page from woodblock-printed book; ink and color on paper,Overall: 10 1/2 x 7 1/4in. (26.7 x 18.4cm),"Rogers Fund, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37292,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1053,false,true,37293,Asian Art,Woodblock print,"『画本虫撰』「蓑虫」「兜虫」|Bagworm (Minomushi); Horned Scarab Beetle (Kabutomushi), from the Picture Book of Crawling Creatures (Ehon mushi erami)",Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,1788,1788,1788,Page from woodblock-printed book; ink and color on paper,10 1/2 x 7 7/32 in. (26.7 x 18.4 cm),"Rogers Fund, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37293,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1054,false,true,37294,Asian Art,Woodblock print,"『画本虫撰』「蝸牛」「轡虫」|Land Snail (Katatsumuri); Giant Katydid (Kutsuwamushi), from the Picture Book of Crawling Creatures (Ehon mushi erami)",Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,1788,1788,1788,Page from woodblock-printed book; ink and color on paper,10 1/2 x 7 7/32 in. (26.7 x 18.4 cm),"Rogers Fund, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37294,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1055,false,true,37295,Asian Art,Woodblock print,"『画本虫撰』「きりきりす」「蝉」|Grasshopper (Kirigirisu); Cicada (Semi), from the Picture Book of Crawling Creatures (Ehon mushi erami)",Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,1788,1788,1788,Page from woodblock-printed book; ink and color on paper,10 1/2 x 7 1/4 in. (26.7 x 18.4 cm),"Rogers Fund, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37295,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1056,false,true,37296,Asian Art,Woodblock print,"『画本虫撰』「蚓」「こうろき」|Earthworm (Mimizu); Cricket (Kōrogi), from the Picture Book of Crawling Creatures (Ehon mushi erami)",Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,1788,1788,1788,Page from woodblock-printed book; ink and color on paper,10 1/2 x 7 7/32 in. (26.7 x 18.4 cm),"Rogers Fund, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37296,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1058,false,true,37298,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,ca. 1790,1780,1800,Polychrome woodblock print; ink and color on paper,14 1/2 x 9 in. (36.8 x 22.9 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37298,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1059,false,true,37299,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,1790,1790,1790,Polychrome woodblock print; ink and color on paper,9 1/8 x 14 7/8 in. (23.2 x 37.8 cm),"Gift of Estate of Samuel Isham, 1915",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37299,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1060,false,true,37300,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,1790,1790,1790,Polychrome woodblock print; ink and color on paper,9 1/8 x 14 15/32 in. (23.2 x 36.8 cm),"Gift of Estate of Samuel Isham, 1915",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37300,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1061,false,true,37301,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,1790,1790,1790,Polychrome woodblock print; ink and color on paper,9 x 14 3/4 in. (22.9 x 37.5 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37301,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1062,false,true,37302,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,1790,1790,1790,Polychrome woodblock print; ink and color on paper,9 1/8 x 14 1/2 in. (23.2 x 36.8 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37302,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1063,false,true,37303,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,1790,1790,1790,Polychrome woodblock print; ink and color on paper,9 1/8 x 14 7/8 in. (23.2 x 37.8 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37303,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1064,false,true,37304,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,1790,1790,1790,Polychrome woodblock print; ink and color on paper,9 1/8 x 14 3/5 in. (23.2 x 37.1 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37304,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1072,false,true,37305,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,1801,1801,1801,Polychrome woodblock print; ink and color on paper,6 13/16 x 10 5/16 in. (17.3 x 26.2 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37305,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1073,false,true,37306,Asian Art,Woodblock print,"四季の花|The Coming Thunderstorm, from the illustrated book Flowers of the Four Seasons",Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,1801,1801,1801,Polychrome woodblock print; ink and color on paper,6 7/8 x 9 7/8 in. (17.5 x 25.1 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37306,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1074,false,true,37307,Asian Art,Woodblock print,"四季の花|Women on a Bridge, from the illustrated book Flowers of the Four Seasons",Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,1801,1801,1801,Polychrome woodblock print; ink and color on paper,6 3/4 × 9 7/8 in. (17.1 × 25.1 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37307,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1075,false,true,37308,Asian Art,Woodblock print,"四季の花|A Child Lighting Fireworks, from the illustrated book Flowers of the Four Seasons",Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,1801,1801,1801,Polychrome woodblock print; ink and color on paper,6 3/4 × 9 7/8 in. (17.1 × 25.1 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37308,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1076,false,true,37309,Asian Art,Woodblock print,"四季の花|Girls Getting on Board a Boat, from the illustrated book Flowers of the Four Seasons",Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,1801,1801,1801,Polychrome woodblock print; ink and color on paper,6 3/4 x 9 7/8 in. (17.1 x 25.1 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37309,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1077,false,true,37310,Asian Art,Woodblock print,"四季の花|Girls Picking Green Leaves, from the illustrated book Flowers of the Four Seasons",Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,1801,1801,1801,Polychrome woodblock print; ink and color on paper,6 13/16 x 9 7/8 in. (17.3 x 25.1 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37310,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1078,false,true,37311,Asian Art,Woodblock print,"四季の花|New Year's Games, from the printed book Flowers of the Four Seasons (Shiki no hana)",Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,1801,1801,1801,Polychrome woodblock print; ink and color on paper,6 7/8 x 9 7/8 in. (17.5 x 25.1 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37311,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1079,false,true,37312,Asian Art,Woodblock print,"四季の花|Girls Entertained by Performers, from the illustrated book Flowers of the Four Seasons",Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,1801,1801,1801,Polychrome woodblock print; ink and color on paper,6 7/8 x 9 7/8 in. (17.5 x 25.1 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37312,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1080,false,true,37313,Asian Art,Woodblock print,"四季の花|Poetry, from the illustrated book Flowers of the Four Seasons",Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,1801,1801,1801,Polychrome woodblock print; ink and color on paper,6 13/16 x 9 4/5 in. (17.3 x 24.9 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37313,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1111,false,true,37314,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,ca. 1800,1790,1810,Polychrome woodblock print; ink and color on paper,H. 14 3/4 in. (37.5 cm); W. 10 in. (25.4 cm),"Rogers Fund, 1919",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37314,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1112,false,true,52009,Asian Art,Print,吾妻美人ゑらみ|A Tea-house Waitress,Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,ca. 1795,1785,1805,Album of eighty-nine prints; ink and color on paper,H. 13 in. (33 cm); W. 14 in. (35.6 cm),"Rogers Fund, 1919",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/52009,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1113,false,true,45096,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,ca. 1795,1785,1805,Polychrome woodblock print; ink and color on paper,H. 15 in. (38.1 cm); W. 9 1/2 in. (24.1 cm),"Rogers Fund, 1919",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45096,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1121,false,true,55043,Asian Art,Print,当世風俗通 女房風|Mother and Child,Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,ca. 1790,1780,1810,Polychrome woodblock print; ink and color on paper,H. 14 7/8 in. (37.8 cm); W. 10 in. (25.4 cm),"Rogers Fund, 1919",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55043,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1122,false,true,37315,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,1806–20,1806,1820,Polychrome woodblock print; ink and color on paper,15 x 10 in. (38.1 x 25.4 cm),"Rogers Fund, 1919",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37315,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1123,false,true,39647,Asian Art,Print,三勝と半七|Sankatsu and Hanshichi,Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,ca. 1800,1790,1810,Polychrome woodblock print; ink and color on paper,H. 24 1/2 in. (62.2 cm); W. 5 7/8 in. (14.9 cm),"Rogers Fund, 1919",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/39647,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1214,false,true,55137,Asian Art,Print,逢身八契 権八小紫の床の通気|Gonpachi ni Komurasaki no Toko no Tsuki,Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,ca. 1795,1785,1805,Polychrome woodblock print; ink and color on paper,H. 15 1/8 in. (38.4 cm); W. 9 7/8 in. (25.1 cm),"Rogers Fund, 1920",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55137,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1215,false,true,55138,Asian Art,Print,"『青楼七小町』 「玉屋内花紫」|“Hanamurasaki of the Tamaya,” from the series Seven Komachi of the Pleasure Quarters (Seirō Nana Komachi)",Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,ca. 1790,1780,1800,Polychrome woodblock print; ink and color on paper,H. 14 1/2 in. (36.8 cm); W. 9 7/8 in. (25.1 cm),"Rogers Fund, 1920",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55138,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1272,false,true,55193,Asian Art,Print,青楼仁和嘉女芸者之部 扇売 団扇売 麦つき|The Niwaka Performers,Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,ca. 1795,1785,1805,Polychrome woodblock print; ink and color on paper,H. 15 3/16 in. (38.6 cm); W. 10 1/8 in. (25.7 cm),"Rogers Fund, 1921",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55193,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1277,false,true,55208,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,1795,1795,1795,Polychrome woodblock print; ink and color on paper,H. 14 3/4 in. (37.5 cm); W. 10 in. (25.4 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55208,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1278,false,true,51137,Asian Art,Print,風俗美人時計 子ノ刻 妾|Midnight: Mother and Sleepy Child,Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,1790,1790,1790,Polychrome woodblock print; ink and color on paper,14 3/8 x 9 5/8 in. (36.5 x 24.4cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/51137,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1367,false,true,53689,Asian Art,Print,南国美人合|Courtesan Holding a Fan,Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,ca. 1793,1783,1803,Polychrome woodblock print; ink and color on paper 02_18_60---(Mica ground),14 1/2 x 9 5/16 in. (36.8 x 23.7 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/53689,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1368,false,true,55318,Asian Art,Print,"『北国五色墨』「おいらん」|“High-Ranking Courtesan” (Oiran), from the series Five Shades of Ink in the Northern Quarter (Hokkoku goshiki-zumi),",Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,1794–95,1794,1795,Polychrome woodblock print; ink and color on paper,Oban 14 3/4 x 9 3/4 in. (37.5 x 24.8 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55318,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1392,false,true,55399,Asian Art,Print,富本豊ひな|The Lady Tomimoto Toyohina Reading a Letter,Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,1790,1790,1790,Polychrome woodblock print; ink and color on paper,H. 14 5/8 in. (37.1 cm); W. 9 3/8 in. (23.8 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55399,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1438,false,true,45099,Asian Art,Print,山姥と金太郎|Yamauba and Kintoki,Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,ca. 1795,1785,1805,Polychrome woodblock print; ink and color on paper,H. 15 1/16 in. (38.3 cm); W. 9 7/8 in. (25.1 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45099,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1439,false,true,45015,Asian Art,Print,当世恋歌八契 お七と吉三郎|The Lovers Oshichi and Kichisaburo,Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,ca. 1800,1790,1810,Polychrome woodblock print; ink and color on paper,H. 15 1/4 in. (38.7 cm); W. 9 3/4 in. (24.8 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45015,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1440,false,true,45016,Asian Art,Print,Gompachi Komurasaki no Toko no Tsuki|逢身八契 権八小紫の床の通気|Shared Feelings in the Bedchamber of Komurasaki and Gompachi,Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,ca. 1795,1785,1805,Polychrome woodblock print; ink and color on paper,H. 14 15/16 in. (37.9 cm); W. 10 3/16 in. (25.9 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45016,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1441,false,true,55485,Asian Art,Print,八百屋お七 寺小姓吉三郎|O Shichi and Kichisaburo,Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,ca. 1780,1770,1790,Polychrome woodblock print; ink and color on paper,H. 24 3/4 in. (62.9 cm); W. 5 5/16 in. (13.5 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55485,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1461,false,true,54862,Asian Art,Print,"名取酒六家選 兵庫屋華妻 坂上の剣菱|""Hanazuma of Hyōgoya, Kenbishi of Sakagami” from the series The Peers of Saké Likened to Select Denizens of Six Houses (Natori zake rokkasen: Hyōgoya Hanazuma, Sakagami no Kenbishi)",Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,ca. 1794,1784,1804,Polychrome woodblock print; ink and color on paper,H. 14 3/4 in. (37.5 cm); W. 9 1/2 in. (24.1 cm),"Rogers Fund, 1925",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54862,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1462,false,true,45098,Asian Art,Print,江戸の園花合 東屋の花|Azumaya no Hana,Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,ca. 1790,1780,1800,Polychrome woodblock print; ink and color on paper,H. 13 1/16 in. (33.2 cm); W. 8 7/8 in. (22.5 cm),"Rogers Fund, 1925",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45098,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1478,false,true,55565,Asian Art,Print,娘日時計 申ノ刻|Seru no Koku,Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,ca. 1795,1785,1805,Polychrome woodblock print; ink and color on paper,H. 14 15/16 in. (37.9 cm); W. 10 1/4 in. (26 cm),"Fletcher Fund, 1925",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55565,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1525,false,true,51093,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,1790s,1790,1800,Polychrome woodblock print; ink and color on paper,H. 14 3/4 in. (37.5 cm); W. 9 5/8 in. (24.4 cm),"Fletcher Fund, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/51093,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1526,false,true,54865,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,ca. 1793,1783,1803,Polychrome woodblock print; ink and color on paper,H. 14 7/8 in. (37.8 cm); W. 9 7/8 in. (25.1 cm),"Fletcher Fund, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54865,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1527,false,true,55636,Asian Art,Print,"「扇屋内花扇図」|The Courtesan Hanaōgi of the Ōgiya Brothel in Yoshiwara (Ōgiya uchi Hanaōgi, Yoshino, Tatsuta)",Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,ca. 1793–94,1793,1794,Polychrome woodblock print; ink and color on paper,H. 14 5/8 in. (37.1 cm); W. 10 13/16 in. (27.5 cm),"Fletcher Fund, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55636,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1528,false,true,55639,Asian Art,Print,三婦艶|Three Beauties of the Kwansei Period,Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,ca. 1791,1781,1801,Polychrome woodblock print; ink and color on paper,H. 15 1/4 in. (38.7 cm); W. 10 1/8 in. (25.7 cm),"Fletcher Fund, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55639,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1657,false,true,55818,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,ca. 1805,1795,1815,Polychrome woodblock print; ink and color on paper,H. 15 in. (38.1 cm); W. 9 13/16 in. (24.9 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55818,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1658,false,true,55819,Asian Art,Print,藤棚下の遊女たち|Courtesans Beneath a Wisteria Arbor (Fuji dana shita no yūjo tachi),Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,ca. 1795,1785,1805,Polychrome woodblock print; ink and color on paper,H. 15 1/8 in. (38.4 cm); W. 10 7/8 in. (27.6 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55819,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1659,false,true,55434,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,1790s,1790,1800,Polychrome woodblock print; ink and color on paper,H. 15 in. (38.1 cm); W. 10 in. (25.4 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55434,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1660,false,true,44990,Asian Art,Print,実競色乃美名家見 紙屋次兵衛 紀ノ国屋小春|Jihei of Kamiya Eloping with Koharu of Kinokuniya,Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,early 1800s,1800,1833,Polychrome woodblock print; ink and color on paper,Image: 13 3/4 x 10 in. (34.9 x 25.4 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/44990,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1661,false,true,45477,Asian Art,Print,行水|Bathtime (Gyōzui),Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,ca. 1801,1791,1811,Polychrome woodblock print; ink and color on paper,14 11/16 x 9 7/8 in. (37.3 x 25.1 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45477,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1662,false,true,45017,Asian Art,Print,婦女人相十品 相観歌麿考画|Woman with a Glass Noisemaker (Popen),Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,early 1790s,1790,1794,Polychrome woodblock print; ink and color on paper,15 5/16 x 10 13/16 in. (38.9 x 27.5 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45017,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1664,false,true,37335,Asian Art,Print,当世美人三遊 芸妓|“Geisha” from the series Three Amusements of Contemporary Beauties (Tōsei bijin san’yū: Geigi),Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,ca. 1800,1790,1810,Polychrome woodblock print; ink and color on paper,20 7/8 x 9 9/16 in. (53.0 x 24.3 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37335,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1665,false,true,55862,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,ca. 1797,1787,1807,Polychrome woodblock print; ink and color on paper,H. 14 1/8 in. (35.9 cm); W. 9 1/4 in. (23.5 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55862,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1666,false,true,37336,Asian Art,Print,雪の桟橋|Landing-stage in the Snow (Yuki no sanbashi),Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,1800,1800,1800,Polychrome woodblock print; ink and color on paper,H. 20 1/2 in. (52.1 cm); W. 7 7/16 in. (18.9 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37336,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1667,false,true,55863,Asian Art,Print,青楼仁和嘉女芸者之部 唐人 獅子 角力|Seiro Niwaka Onna Geisha no Bu Tojin Shishi Sumo,Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,ca. 1791,1781,1801,Polychrome woodblock print; ink and color on paper,H. 10 1/16 in. (25.6 cm); W. 15 1/8 in. (38.4 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55863,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1668,false,true,55867,Asian Art,Print,難波屋おきた|Okita of the Naniwa-ya Tea-house,Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,1790s,1790,1799,Polychrome woodblock print; ink and color on paper,H. 14 5/16 in. (36.4 cm); W. 9 1/2 in. (24.1 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55867,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1669,false,true,37337,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,1790,1790,1790,Polychrome woodblock print; ink and color on paper,Image: 8 7/8 in. × 14 in. (22.5 × 35.6 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37337,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1672,false,true,53653,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,ca. 1793–94,1793,1794,One sheet of a triptych of polychrome woodblock prints; ink and color on paper,H. 15 1/8 in. (38.4 cm); W. 10 3/16 in. (25.9 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/53653,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1673,false,true,54864,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,1790s,1790,1799,Polychrome woodblock print; ink and color on paper,H. 18 5/8 in. (47.3 cm); W. 7 5/8 in. (19.4 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54864,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1674,false,true,45479,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,ca. 1793,1783,1803,Polychrome woodblock print; ink and color on paper,20 3/4 x 7 5/8 in. (52.7 x 19.4 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45479,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1675,false,true,37338,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,ca. 1794–95,1794,1795,Right-hand sheet of a diptych of polychrome woodblock prints; ink and color on paper,Image: 15 × 10 in. (38.1 × 25.4 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37338,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1676,false,true,53654,Asian Art,Print,"美人気量競 五明楼 花扇|“Hanaōgi of the Gomeirō,” from the series Comparing the Charms of Beauties (Bijin kiryō kurabe: Gomeirō Hanaōgi)",Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,1794–95,1794,1795,Polychrome woodblock print; ink and color on paper,H. 14 15/16 in. (37.9 cm); W. 9 11/16 in. (24.6 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/53654,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1677,false,true,37339,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,1791,1791,1791,Triptych of polychrome woodblock prints; ink and color on paper,7 7/16 x 14 3/5 in. (18.9 x 37.1 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37339,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1679,false,true,45018,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,ca. 1795,1785,1805,Polychrome woodblock print; ink and color on paper,H. 24 7/8 in. (63.2 cm); W. 5 5/8 in. (14.3 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45018,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1685,false,true,53897,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,ca. 1800,1790,1810,Three sheets of a hexaptych of polychrome woodblock prints; ink and color on paper,15 x 29 3/4in. (38.1 x 75.6cm) Framed: 38 1/8 × 38 1/8 in. (96.8 × 96.8 cm) shares frame with JP1686,"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/53897,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1686,false,true,37344,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,ca. 1800,1790,1810,Three sheets of a hexaptych of polychrome woodblock prints; ink and color on paper,15 x 29 3/4in. (38.1 x 75.6cm) Framed: 38 1/8 × 38 1/8 in. (96.8 × 96.8 cm) shares frame with JP1685,"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37344,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1687,false,true,55922,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,1790s,1790,1800,Triptych of polychrome woodblock prints; ink and color on paper,a: H. 14 7/8 in. (37.8 cm); W. 9 7/8 in. (25.1 cm) b: H. 15 in. (38.1 cm); W. 9 3/4 in. (24.8 cm) c: H. 14 5/16 in. (36.4 cm); W. 9 15/16 in. (25.2 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55922,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2393,false,true,56798,Asian Art,Print,忠臣蔵四段目|A Woman and a Man Arranging Flowers for the Tsukimi (Moon Festival),Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,1802,1802,1802,Polychrome woodblock print; ink and color on paper,15 1/8 x 10 1/8 in. (38.4 x 25.7 cm),"H. O. Havemeyer Collection, Gift of Mrs. J. Watson Webb, 1930",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56798,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2395,false,true,56800,Asian Art,Print,玉屋内 志津賀|A Courtesan,Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,1807,1807,1807,Polychrome woodblock print; ink and color on paper,14 3/16 x 9 7/8 in. (36 x 25.1 cm),"H. O. Havemeyer Collection, Gift of Mrs. J. Watson Webb, 1930",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56800,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2730,false,true,55319,Asian Art,Print,婦女人相十品 相観|“Woman Holding Up a Parasol” from the series Ten Classes of Women’s Physiognomy (Fujo ninsō juppen: Higasa o sasu onna),Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,ca. 1792–93,1792,1793,Polychrome woodblock print; ink and color on paper,15 x 10 1/8 in. (38.1 x 25.7 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55319,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2731,false,true,52001,Asian Art,Print,女織蚕手業草 十|The Making of Silk Floss,Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,ca. 1790,1780,1800,Polychrome woodblock print; ink and color on paper,15 x 10 in. (38.1 x 25.4 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/52001,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2732,false,true,37354,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,1795,1795,1795,Right-hand sheet of a triptych of polychrome woodblock prints; ink and color on paper,15 x 10 in. (38.1 x 25.4 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37354,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2734,false,true,45019,Asian Art,Print,Naniwaya Okita|Teahouse Waitress,Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,ca. 1793,1783,1803,Polychrome woodblock print; ink and color on paper,14 1/8 x 9 7/8 in. (35.9 x 25.1 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45019,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2735,false,true,52002,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,ca. 1790,1780,1800,Polychrome woodblock print; ink and color on paper,15 3/8 x 10 1/8 in. (39.1 x 25.7 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/52002,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2736,false,true,53656,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,ca. 1795,1785,1805,Polychrome woodblock print; ink and color on paper,15 x 9 3/4 in. (38.1 x 24.8 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/53656,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2737,false,true,37355,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,ca. 1790,1780,1800,Right-hand sheet of a triptych of polychrome woodblock prints; ink and color on paper,14 7/8 x 9 3/4 in. (37.8 x 24.8 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37355,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2738,false,true,57011,Asian Art,Print,実競色乃美名家見 紙屋次兵衛 紀ノ国屋小春|Jihei of Kamiya Eloping with the Geisha Koharu of Kinokuniya,Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,ca. 1798,1788,1808,Polychrome woodblock print; ink and color on paper,15 1/8 x 9 5/8 in. (38.4 x 24.4 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/57011,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2739,false,true,37356,Asian Art,Print,青楼仁和嘉女芸者之部 茶せん売 黒木売 さいもん|Three Niwaka Performers,Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,ca. 1794,1784,1804,Polychrome woodblock print; ink and color on paper,15 x 9 3/4 in. (38.1 x 24.8 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37356,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2740,false,true,37357,Asian Art,Print,契情三人酔 三幅之内 腹立上戸 泣上戸 笑上戸|Three Intoxicated Courtesans,Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,1790s,1790,1799,Triptych of polychrome woodblock prints; ink and color on paper,Each H. 12 7/8 in. (32.7 cm); W. 8 1/2 in. (21.6 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37357,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2741,false,true,57013,Asian Art,Print,六玉川 扇屋内 花扇|The Oiran Hanaogi of Ogiya,Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,ca. 1796,1786,1806,Polychrome woodblock print; ink and color on paper,15 1/8 x 10 in. (38.4 x 25.4 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/57013,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2809,false,true,57081,Asian Art,Print,Uguisu|風流小鳥合 鶯|Japanese Bush Warbler,Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,1754–1806,1754,1806,Polychrome woodblock print; ink and color on paper,8 3/4 x 6 1/4 in. (22.2 x 15.9 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/57081,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2810,false,true,57080,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,1615–1806,1615,1806,Polychrome woodblock print; ink and color on paper,14 5/8 x 10 1/8 in. (37.1 x 25.7 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/57080,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2811,false,true,54866,Asian Art,Print,児戯意乃三笑 恵恩芳子|Mother and Child,Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,ca. 1800,1790,1810,Polychrome woodblock print; ink and color on paper,H. 14 5/16 in. (36.4 cm); W. 9 9/16 in. (24.3 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54866,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2812,false,true,45020,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,ca. 1794–95,1750,1850,Polychrome woodblock print; ink and color on paper,14 7/8 x 10 in. (37.8 x 25.4 cm),"Henry L. Phillips Collection; Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45020,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2813,false,true,56793,Asian Art,Print,"喜多川歌麿画 『美人花合』 「兵庫屋内 花妻図」|“The Courtesan Hanazuma Reading a Letter,” from the series Beauties Compared to Flowers (Bijin hana awase)",Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,1790s,1790,1799,Polychrome woodblock print; ink and color on paper,H. 15 1/4 in. (38.7 cm); W. 10 1/4 in. (26 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56793,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2814,false,true,45097,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,ca. 1798,1788,1808,Polychrome woodblock print; ink and color on paper,14 7/16 x 9 3/4 in. (36.7 x 24.8 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45097,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2853,false,true,37360,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,1754–1806,1754,1806,Polychrome woodblock print; ink and color on paper,9 7/8 x 14 7/8 in. (25.1 x 37.8 cm) (including margins),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37360,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2854,false,true,37361,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,1754–1806,1754,1806,Polychrome woodblock print; ink and color on paper,9 7/8 x 14 7/8 in. (25.1 x 37.8 cm) (including margins),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37361,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3018,false,true,51135,Asian Art,Print,姿見七人化粧|Naniwa Okita Admiring Herself in a Mirror,Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,ca. 1790–95,1790,1795,"Polychrome woodblock print; ink and color on paper, mica ground",14 1/2 x 9 7/8 in. (36.8 x 25.1 cm),"Harris Brisbane Dick Fund, 1946",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/51135,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3060,false,true,37364,Asian Art,Print,仮宅の後朝|Scene in the Yoshiwara,Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,1790,1790,1790,Triptych of polychrome woodblock prints; ink and color on paper,Triptych; each H. 14 3/4 in. (37.5 cm); W. 11 5/8 in. (29.5 cm),"Gift of Mrs. Morris Manges, in memory of her husband, Dr. Morris Manges, 1947",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37364,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1996.85,false,true,39615,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,ca. 1800,1790,1810,Polychrome woodblock print; mineral pigments and ink on paper,14 x 9 3/4 in. (35.6 x 24.8 cm); oban size 14 3/8 x 9 1/2 in. (36.5 x 24.1 cm),"Gift of Mr. and Mrs. Horace H. Wilson, 1996",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/39615,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1996.463,false,true,40244,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,ca. 1795,1785,1805,Ink on mulberry paper,Oban: 14 3/4 x 9 3/4 in. (37.5 x 24.8 cm),"Gift of John and Lili Bussel, 1996",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/40244,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"JP1369a, b",false,true,37325,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,ca. 1794–95,1784,1805,Diptych of polychrome woodblock prints; ink and color on paper,Image (each): 14 5/8 × 9 7/8 in. (37.1 × 25.1 cm) Image (diptych): 14 5/8 × 19 3/4 in. (37.1 × 50.2 cm) Framed: 24 1/4 × 30 3/4 in. (61.6 × 78.1 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37325,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP965a–c,false,true,37271,Asian Art,Print,琴棋書画図|The Four Elegant Accomplishments (Kin ki sho ga),Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,ca. 1788,1778,1798,Triptych of polychrome woodblock prints; ink and color on paper,15 x 30 in. (38.1 x 76.2 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37271,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1057a–c,false,true,37297,Asian Art,Woodblock print,"『画本虫撰』「蛙」「こかねむし」|Frog (Kaeru); Gold Beetle (Kogane mushi), from the Picture Book of Crawling Creatures (Ehon mushi erami)",Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,1788,1788,1788,Page from woodblock-printed book; ink and color on paper,10 1/2 x 7 7/32 in. (26.7 x 18.4 cm),"Rogers Fund, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37297,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1037,false,true,54322,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Yashima Gakutei,"Japanese, 1786?–1868",,Yashima Gakutei,Japanese,1786,1868,1835,1835,1835,Polychrome woodblock print (surimono); ink and color on paper,8 1/4 x 7 1/4 in. (21 x 18.4 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54322,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1038,false,true,54323,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,Formerly attributed to,Yashima Gakutei,"Japanese, 1786?–1868",,Yashima Gakutei,Japanese,1786,1868,1835,1835,1835,Polychrome woodblock print (surimono); ink and color on paper,7 7/8 x 8 3/4 in. (20 x 22.2 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54323,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1039,false,true,54324,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Yashima Gakutei,"Japanese, 1786?–1868",,Yashima Gakutei,Japanese,1786,1868,1835?,1835,1835,Polychrome woodblock print (surimono); ink and color on paper,7 7/8 x 7 in. (20 x 17.8 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54324,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1101,false,true,54330,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Yashima Gakutei,"Japanese, 1786?–1868",,Yashima Gakutei,Japanese,1786,1868,ca. 1820,1810,1830,Diptych of Polychrome woodblock print (surimono); ink and color on paper,"8 3/16 x 7 1/8 in. (20.8 x 18.1 cm); dyptych, vertical","Rogers Fund, 1919",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54330,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1107,false,true,54336,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Yashima Gakutei,"Japanese, 1786?–1868",,Yashima Gakutei,Japanese,1786,1868,ca. 1825,1815,1835,Polychrome woodblock print (surimono); ink and color on paper,8 x 6 15/16 in. (20.3 x 17.6 cm),"Rogers Fund, 1919",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54336,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1131,false,true,54338,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Yashima Gakutei,"Japanese, 1786?–1868",,Yashima Gakutei,Japanese,1786,1868,ca. 1820,1810,1830,Polychrome woodblock print (surimono); ink and color on paper,8 3/16 x 7 3/8 in. (20.8 x 18.7 cm),"Rogers Fund, 1919",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54338,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1132,false,true,54339,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Yashima Gakutei,"Japanese, 1786?–1868",,Yashima Gakutei,Japanese,1786,1868,ca. 1820,1810,1830,Polychrome woodblock print (surimono); ink and color on paper,8 3/16 x 7 5/16 in. (20.8 x 18.6 cm),"Rogers Fund, 1919",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54339,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1133,false,true,54340,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Yashima Gakutei,"Japanese, 1786?–1868",,Yashima Gakutei,Japanese,1786,1868,ca. 1830,1820,1840,Polychrome woodblock print (surimono); ink and color on paper,8 1/16 x 7 3/16 in. (20.5 x 18.3 cm),"Rogers Fund, 1919",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54340,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1134,false,true,54341,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Yashima Gakutei,"Japanese, 1786?–1868",,Yashima Gakutei,Japanese,1786,1868,ca. 1820,1810,1830,Polychrome woodblock print (surimono); ink and color on paper,8 5/16 x 7 5/16 in. (21.1 x 18.6 cm),"Rogers Fund, 1919",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54341,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1135,false,true,54342,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Yashima Gakutei,"Japanese, 1786?–1868",,Yashima Gakutei,Japanese,1786,1868,ca. 1825,1815,1835,Polychrome woodblock print (surimono); ink and color on paper,8 5/16 x 7 5/16 in. (21.1 x 18.6 cm),"Rogers Fund, 1919",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54342,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1136,false,true,54343,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Yashima Gakutei,"Japanese, 1786?–1868",,Yashima Gakutei,Japanese,1786,1868,ca. 1820,1810,1830,Polychrome woodblock print (surimono); ink and color on paper,8 5/16 x 7 7/16 in. (21.1 x 18.9 cm),"Rogers Fund, 1919",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54343,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1137,false,true,54344,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Yashima Gakutei,"Japanese, 1786?–1868",,Yashima Gakutei,Japanese,1786,1868,ca. 1830,1820,1840,Polychrome woodblock print (surimono); ink and color on paper,8 5/16 x 7 7/16 in. (21.1 x 18.9 cm),"Rogers Fund, 1919",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54344,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1138,false,true,54345,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Yashima Gakutei,"Japanese, 1786?–1868",,Yashima Gakutei,Japanese,1786,1868,ca. 1830,1820,1840,Polychrome woodblock print (surimono); ink and color on paper,8 x 7 5/16 in. (20.3 x 18.6 cm),"Rogers Fund, 1919",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54345,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1139,false,true,54346,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Yashima Gakutei,"Japanese, 1786?–1868",,Yashima Gakutei,Japanese,1786,1868,ca. 1830,1820,1840,Polychrome woodblock print (surimono); ink and color on paper,8 5/8 x 7 1/4 in. (21.9 x 18.4 cm),"Rogers Fund, 1919",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54346,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1231,false,true,54361,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Yashima Gakutei,"Japanese, 1786?–1868",,Yashima Gakutei,Japanese,1786,1868,ca. 1830,1820,1840,Polychrome woodblock print (surimono); ink and color on paper,8 1/4 x 7 1/2 in. (21 x 19.1 cm),"Rogers Fund, 1921",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54361,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1232,false,true,54362,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Yashima Gakutei,"Japanese, 1786?–1868",,Yashima Gakutei,Japanese,1786,1868,ca. 1830,1820,1840,Polychrome woodblock print (surimono); ink and color on paper,8 1/4 x 6 3/4 in. (21 x 17.1 cm),"Rogers Fund, 1921",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54362,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1233,false,true,54363,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Yashima Gakutei,"Japanese, 1786?–1868",,Yashima Gakutei,Japanese,1786,1868,ca. 1820,1810,1830,Polychrome woodblock print (surimono); ink and color on paper,8 1/4 x 7 1/4 in. (21 x 18.4 cm),"Rogers Fund, 1921",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54363,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1234,false,true,54364,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Yashima Gakutei,"Japanese, 1786?–1868",,Yashima Gakutei,Japanese,1786,1868,ca. 1810,1800,1820,Polychrome woodblock print (surimono); ink and color on paper,8 1/4 x 7 3/16 in. (21 x 18.3 cm),"Rogers Fund, 1921",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54364,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1235,false,true,54365,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Yashima Gakutei,"Japanese, 1786?–1868",,Yashima Gakutei,Japanese,1786,1868,ca. 1820,1810,1830,Polychrome woodblock print (surimono); ink and color on paper,8 3/8 x 7 5/16 in. (21.3 x 18.6 cm),"Rogers Fund, 1921",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54365,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1236,false,true,54366,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Yashima Gakutei,"Japanese, 1786?–1868",,Yashima Gakutei,Japanese,1786,1868,ca. 1830,1820,1840,Polychrome woodblock print (surimono); ink and color on paper,7 3/4 x 7 1/8 in. (19.7 x 18.1 cm),"Rogers Fund, 1921",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54366,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1237,false,true,54367,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Yashima Gakutei,"Japanese, 1786?–1868",,Yashima Gakutei,Japanese,1786,1868,ca. 1820,1810,1830,Polychrome woodblock print (surimono); ink and color on paper,8 3/16 x 7 3/16 in. (20.8 x 18.3 cm),"Rogers Fund, 1921",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54367,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1238,false,true,54369,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Yashima Gakutei,"Japanese, 1786?–1868",,Yashima Gakutei,Japanese,1786,1868,ca. 1830,1820,1840,Polychrome woodblock print (surimono); ink and color on paper,8 1/4 x 7 3/8 in. (21 x 18.7 cm),"Rogers Fund, 1921",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54369,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1239,false,true,54370,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Yashima Gakutei,"Japanese, 1786?–1868",,Yashima Gakutei,Japanese,1786,1868,ca. 1820,1810,1830,Polychrome woodblock print (surimono); ink and color on paper,5 1/2 x 7 1/2 in. (14 x 19.1 cm),"Rogers Fund, 1921",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54370,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1250,false,true,54382,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Yashima Gakutei,"Japanese, 1786?–1868",,Yashima Gakutei,Japanese,1786,1868,1840,1840,1840,Polychrome woodblock print (surimono); ink and color on paper,7 1/8 x 6 13/16 in. (18.1 x 17.3 cm),"Rogers Fund, 1921",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54382,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1300,false,true,54388,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Yashima Gakutei,"Japanese, 1786?–1868",,Yashima Gakutei,Japanese,1786,1868,ca. 1820,1810,1830,Polychrome woodblock print (surimono); ink and color on paper,8 9/16 x 7 3/8 in. (21.7 x 18.7 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54388,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1411,false,true,55441,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Yashima Gakutei,"Japanese, 1786?–1868",,Yashima Gakutei,Japanese,1786,1868,1838,1838,1838,Polychrome woodblock print; ink and color on paper,H. 10 1/16 in. (25.6 cm); W. 14 15/16 in. (37.9 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55441,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1412,false,true,55442,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Yashima Gakutei,"Japanese, 1786?–1868",,Yashima Gakutei,Japanese,1786,1868,1838,1838,1838,Polychrome woodblock print; ink and color on paper,H. 10 1/8 in. (25.7 cm); W. 14 15/16 in. (37.9 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55442,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1413,false,true,55443,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Yashima Gakutei,"Japanese, 1786?–1868",,Yashima Gakutei,Japanese,1786,1868,1838,1838,1838,Polychrome woodblock print; ink and color on paper,H. 10 1/16 in. (25.6 cm); W. 14 13/16 in. (37.6 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55443,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1414,false,true,55444,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Yashima Gakutei,"Japanese, 1786?–1868",,Yashima Gakutei,Japanese,1786,1868,1838,1838,1838,Polychrome woodblock print; ink and color on paper,H. 10 1/16 in. (25.6 cm); W. 14 7/8 in. (37.8 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55444,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1415,false,true,45266,Asian Art,Print,Osaka Tenmangu sairei no zu|The Tenmangu Festival at Osaka,Japan,Edo period (1615–1868),,,,Artist,,Yashima Gakutei,"Japanese, 1786?–1868",,Yashima Gakutei,Japanese,1786,1868,1834,1700,1868,Polychrome woodblock print; ink and color on paper,H. 10 1/8 in. (25.7 cm); W. 15 in. (38.1 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45266,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1452,false,true,54393,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Yashima Gakutei,"Japanese, 1786?–1868",,Yashima Gakutei,Japanese,1786,1868,ca. 1830,1820,1840,Polychrome woodblock print (surimono); ink and color on paper,8 3/16 x 7 1/4 in. (20.8 x 18.4 cm),"Rogers Fund, 1923",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54393,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1903,false,true,54442,Asian Art,Print,"『和歌三神』柿本人麻呂『春雨集』 摺物帖|Kakinomoto no Hitomaro (ca. 662–710), One of the Three Gods of PoetryFrom the Spring Rain Collection (Harusame shū), vol. 1",Japan,Edo period (1615–1868),,,,Artist,,Yashima Gakutei,"Japanese, 1786?–1868",,Yashima Gakutei,Japanese,1786,1868,ca. 1820s,1820,1829,Polychrome woodblock print (surimono); ink and color on paper,8 x 5 1/4 in. (20.3 x 13.3 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54442,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1904,false,true,54443,Asian Art,Print,"『和歌三神』衣通姫『春雨集』 摺物帖|Sotoori-hime (early 5th century), One of the Three Gods of PoetryFrom the Spring Rain Collection (Harusame shū), vol. 1",Japan,Edo period (1615–1868),,,,Artist,,Yashima Gakutei,"Japanese, 1786?–1868",,Yashima Gakutei,Japanese,1786,1868,ca. 1820s,1820,1829,Polychrome woodblock print (surimono); ink and color on paper,8 x 5 3/8 in. (20.3 x 13.7 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54443,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1905,false,true,54444,Asian Art,Print,"『和歌三神』山部赤人『春雨集』 摺物帖|Yamabe no Akahito (active 724–736), One of the Three Gods of PoetryFrom the Spring Rain Collection (Harusame shū), vol. 1",Japan,Edo period (1615–1868),,,,Artist,,Yashima Gakutei,"Japanese, 1786?–1868",,Yashima Gakutei,Japanese,1786,1868,ca. 1820s,1820,1829,Polychrome woodblock print (surimono); ink and color on paper,8 x 5 1/4 in. (20.3 x 13.3 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54444,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1940,false,true,54504,Asian Art,Print,"「一陽連文房四友 硯 伯英」『春雨集』 摺物帖|The Chinese Calligrapher Boying (Japanese: Hakuei; also known as the “Sage of Cursive Script”); “Inkstone” (Suzuri), from Four Friends of the Writing Table for the Ichiyō Poetry Circle (Ichiyō-ren Bunbō shiyū) From the Spring Rain Collection (Harusame shū), vol. 1",Japan,Edo period (1615–1868),,,,Artist,,Yashima Gakutei,"Japanese, 1786?–1868",,Yashima Gakutei,Japanese,1786,1868,ca. 1827,1817,1837,Polychrome woodblock print (surimono); ink and color on paper,8 x 7 1/8 in. (20.3 x 18.1 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54504,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1941,false,true,54508,Asian Art,Print,"「一陽連文房四友 筆 道風」『春雨集』 摺物帖|The Heian Court Calligrapher Ono no Tōfū (894–966); “Calligraphy Brush” (Fude), from Four Friends of the Writing Table for the Ichiyō Poetry Circle (Ichiyō-ren Bunbō shiyū)From the Spring Rain Collection (Harusame shū), vol. 1",Japan,Edo period (1615–1868),,,,Artist,,Yashima Gakutei,"Japanese, 1786?–1868",,Yashima Gakutei,Japanese,1786,1868,ca. 1827,1817,1837,Polychrome woodblock print (surimono); ink and color on paper,8 x 7 1/16 in. (20.3 x 17.9 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54508,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1942,false,true,54509,Asian Art,Print,"「一陽連文房四友 紙 女凢」『春雨集』 摺物帖 |Nuji (Japanese: Joki; female attendant who compiled writings by Daoist sages); “Paper” (Kami), from Four Friends of the Writing Table for the Ichiyō Poetry Circle (Ichiyō-ren Bunbō shiyū)From the Spring Rain Collection (Harusame shū), vol. 1",Japan,Edo period (1615–1868),,,,Artist,,Yashima Gakutei,"Japanese, 1786?–1868",,Yashima Gakutei,Japanese,1786,1868,ca. 1827,1817,1837,Polychrome woodblock print (surimono); ink and color on paper,8 x 7 1/16 in. (20.3 x 17.9 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54509,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1943,false,true,54510,Asian Art,Print,"「一陽連文房四友 墨 玄宗皇帝」『春雨集』 摺物帖 |Emperor Xuanzong (Japanese: Gensō) and Daoist Magician Lo Gongyuan Arising from an Inkstone; “Ink” (Sumi), from Four Friends of the Writing Table for the Ichiyō Poetry Circle (Ichiyō-ren Bunbō shiyū)From the Spring Rain Collection (Harusame shū), vol. 1",Japan,Edo period (1615–1868),,,,Artist,,Yashima Gakutei,"Japanese, 1786?–1868",,Yashima Gakutei,Japanese,1786,1868,ca. 1827,1817,1837,Polychrome woodblock print (surimono); ink and color on paper,8 x 7 1/16 in. (20.3 x 17.9 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54510,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1952,false,true,54520,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Yashima Gakutei,"Japanese, 1786?–1868",,Yashima Gakutei,Japanese,1786,1868,probably 1819,1819,1819,Polychrome woodblock print (surimono); ink and color on paper,7 1/8 x 6 1/2 in. (18.1 x 16.5 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54520,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2029,false,true,54790,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Yashima Gakutei,"Japanese, 1786?–1868",,Yashima Gakutei,Japanese,1786,1868,1819,1819,1819,Polychrome woodblock print (surimono); ink and color on paper,8 1/16 x 7 1/8 in. (20.5 x 18.1 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54790,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2047,false,true,54818,Asian Art,Print,"孫悟空|The Monkey King Songokū, from the Chinese novel Journey to the West",Japan,Edo period (1615–1868),,,,Artist,,Yashima Gakutei,"Japanese, 1786?–1868",,Yashima Gakutei,Japanese,1786,1868,probably 1824,1824,1824,Polychrome woodblock print (surimono); ink and color on paper,8 1/8 x 7 3/16 in. (20.6 x 18.3 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54818,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2049,false,true,54822,Asian Art,Woodblock print,玉藻前と三浦介|The Warrior Miura-no-suke Confronting the Court Lady Tamamo-no-mae as She Turns into an Evil Fox with Nine Tails,Japan,Edo period (1615–1868),,,,Artist,,Yashima Gakutei,"Japanese, 1786?–1868",,Yashima Gakutei,Japanese,1786,1868,late 1820s,1826,1829,Polychrome woodblock print (surimono); ink and color on paper,8 5/16 x 7 3/8 in. (21.1 x 18.7 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54822,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2232,false,true,53998,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Yashima Gakutei,"Japanese, 1786?–1868",,Yashima Gakutei,Japanese,1786,1868,probably 1828,1828,1828,Polychrome woodblock print (surimono); ink and color on paper,8 1/4 x 7 5/16 in. (21 x 18.6 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/53998,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2339,false,true,54124,Asian Art,Print,三味線の調弦|Woman Tuning a Shamisen and a Cat Looking at its Own Reflection,Japan,Edo period (1615–1868),,,,Artist,,Yashima Gakutei,"Japanese, 1786?–1868",,Yashima Gakutei,Japanese,1786,1868,mid- 1820s,1824,1826,Part of an album of woodblock prints (surimono); ink and color on paper,8 3/16 x 7 5/16 in. (20.8 x 18.6 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54124,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2352,false,true,54137,Asian Art,Print,"墨をする官女『春雨集』 摺物帖|Court Lady at Her Writing TableFrom the Spring Rain Collection (Harusame shū), vol. 3",Japan,Edo period (1615–1868),,,,Artist,,Yashima Gakutei,"Japanese, 1786?–1868",,Yashima Gakutei,Japanese,1786,1868,ca. 1820s,1820,1829,Part of an album of woodblock prints (surimono); ink and color on paper,4 15/16 x 11 1/8 in. (12.5 x 28.3 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54137,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2374,false,true,54158,Asian Art,Woodblock print,鯉の滝登り|Red Carp Ascending a Waterfall,Japan,Edo period (1615–1868),,,,Artist,,Yashima Gakutei,"Japanese, 1786?–1868",,Yashima Gakutei,Japanese,1786,1868,late 1820s,1826,1829,Part of an album of woodblock prints (surimono); ink and color on paper,7 1/4 x 6 9/16 in. (18.4 x 16.7 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54158,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2426,false,true,54178,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Yashima Gakutei,"Japanese, 1786?–1868",,Yashima Gakutei,Japanese,1786,1868,ca. 1840,1830,1850,Polychrome woodblock print (surimono); ink and color on paper,8 1/4 x 7 1/4 in. (21 x 18.4 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54178,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2427,false,true,54179,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Yashima Gakutei,"Japanese, 1786?–1868",,Yashima Gakutei,Japanese,1786,1868,ca. 1840,1830,1850,Polychrome woodblock print (surimono); ink and color on paper,8 1/4 x 7 1/4 in. (21 x 18.4 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54179,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2428,false,true,54180,Asian Art,Woodblock print,"Kamakura no Koshi|The Filial Son at Kamakura, From the Book: Sasekishu",Japan,Edo period (1615–1868),,,,Artist,,Yashima Gakutei,"Japanese, 1786?–1868",,Yashima Gakutei,Japanese,1786,1868,ca. 1835,1825,1845,Polychrome woodblock print (surimono); ink and color on paper,8 1/4 x 7 3/8 in. (21 x 18.7 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54180,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2747,false,true,54206,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Yashima Gakutei,"Japanese, 1786?–1868",,Yashima Gakutei,Japanese,1786,1868,first half of the 19th century,1800,1849,"Triptych of polychrome woodblock prints(surimono); gold, copper and silver on paper",Each print: 8 3/8 x 7 1/2 in. (21.3 x 19.1 cm),"Gift of Louis V. Ledoux, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54206,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2999,false,true,54215,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Yashima Gakutei,"Japanese, 1786?–1868",,Yashima Gakutei,Japanese,1786,1868,1786–1868,1786,1868,Polychrome woodblock print (surimono); ink and color on paper,8 5/16 x 7 7/16 in. (21.1 x 18.9 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54215,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2054,false,true,54829,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Ryūgetsusai Shinkō,"Japanese, active 1810s",,Ryūgetsusai Shinkō,Japanese,1810,1819,probably 1814,1814,1814,Polychrome woodblock print (surimono); ink and color on paper,5 3/8 x 7 7/16 in. (13.7 x 18.9 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54829,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2156,false,true,55063,Asian Art,Print,"『春雨集』 摺物帖柳月斎辰光 少女に鳥かご|Spring Rain Collection (Harusame shū), vol. 2: Young Woman with a Birdcage",Japan,Edo period (1615–1868),,,,Artist,,Ryūgetsusai Shinkō,"Japanese, active 1810s",,Ryūgetsusai Shinkō,Japanese,1810,1819,1810s,1810,1819,Privately published woodblock prints (surimono) mounted in an album; ink and color on paper,8 3/8 x 4 1/4 in. (21.3 x 10.8 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55063,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2011.132,false,true,76562,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Juyōdō Minekuni,"Japanese, active 1820s",,Juyōdō Minekuni,Japanese,1820,1829,1826,1826,1826,Polychrome woodblock print,Image (ôban tate-e): 14 5/8 x 10 1/8 in. (37.1 x 25.7 cm),"Purchase, Friends of Asian Art Gifts, in honor of James C. Y. Watt, 2011",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/76562,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.263,false,true,73554,Asian Art,Woodblock print,"「広影写生 両国の虎」|“The Tiger of Ryōkoku,” from the series True Scenes by Hirokage",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hirokage,"Japanese, active 1860s",,Utagawa Hirokage,Japanese,1860,1869,"8th month, 1860",1860,1860,Polychrome woodblock print; ink and color on paper,Image: 14 1/4 x 9 3/4 in. (36.2 x 24.8 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73554,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP817,false,true,37261,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Okumura Toshinobu,active ca. 1725–1750,,Okumura Toshinobu,Japanese,1725,1750,ca. 1730,1720,1740,Monochrome woodblock print; ink and color on paper,9 x 12 in. (22.9 x 30.5 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37261,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP818,false,true,37262,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Okumura Toshinobu,active ca. 1725–1750,,Okumura Toshinobu,Japanese,1725,1750,ca. 1728,1718,1738,Polychrome woodblock print; ink and color on paper (Urushi-e),Overall: 13 x 6in. (33 x 15.2cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37262,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1310,false,true,45044,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Okumura Toshinobu,active ca. 1725–1750,,Okumura Toshinobu,Japanese,1725,1750,ca. 1728,1718,1738,Polychrome woodblock print (urushi-e); ink and color on paper,H. 12 5/16 in. (31.3 cm); W. 5 7/8 in. (14.9 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45044,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1606,false,true,45046,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Okumura Toshinobu,active ca. 1725–1750,,Okumura Toshinobu,Japanese,1725,1750,ca. 1730,1720,1740,Polychrome woodblock print (urushi-e); ink and color on paper,H. 12 in. (30.5 cm); W. 5 1/2 in. (14 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45046,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1607,false,true,45047,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Okumura Toshinobu,active ca. 1725–1750,,Okumura Toshinobu,Japanese,1725,1750,ca. 1730,1720,1740,Polychrome woodblock print (urushi-e); ink and color on paper,H. 12 3/8 in. (31.4 cm); W. 5 3/4 in. (14.6 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45047,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1608,false,true,45048,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Okumura Toshinobu,active ca. 1725–1750,,Okumura Toshinobu,Japanese,1725,1750,ca. 1730,1720,1740,Polychrome woodblock print (urushi-e); ink and color on paper,H. 12 1/2 in. (31.8 cm); W. 6 1/16 in. (15.4 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45048,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1609,false,true,45049,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Okumura Toshinobu,active ca. 1725–1750,,Okumura Toshinobu,Japanese,1725,1750,ca. 1730,1720,1740,Polychrome woodblock print (urushi-e); ink and color on paper,H. 12 7/16 in. (31.6 cm); W. 6 in. (15.2 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45049,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2757,false,true,45051,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Okumura Toshinobu,active ca. 1725–1750,,Okumura Toshinobu,Japanese,1725,1750,ca. 1793,1783,1803,Tan-e (hand-colored print); ink and color on paper,H. 10 3/4 in. (27.3 cm); W. 6 in. (15.2 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45051,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3080,false,true,56593,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,Attributed to,Okumura Toshinobu,active ca. 1725–1750,,Okumura Toshinobu,Japanese,1725,1750,ca. 1730,1720,1740,Polychrome woodblock print (hand colored); ink and color on paper,13 1/4 x 6 1/2 in. (33.7 x 16.5 cm),"Harris Brisbane Dick Fund and Rogers Fund, 1949",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56593,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3002,false,true,54218,Asian Art,Print,"うし和歌春|Two Women, from the series Spring Poems on Ushiwaka for the Year of the Ox (Ushiwaka haru)",Japan,Edo period (1615–1868),,,,Artist,,Hishikawa Sōri,active ca. 1797–1813,,Hishikawa Sōri,Japanese,1797,1813,1805,1805,1805,Polychrome woodblock print (surimono); ink and color on paper,5 5/16 x 6 11/16 in. (13.5 x 17 cm) (trimmed),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54218,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP172,false,true,36651,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunsen,"Japanese, 1762–ca.1830",,Katsukawa Shunsen,Japanese,1762,1830,ca. 1785,1775,1795,Polychrome woodblock print; ink and color on paper,12 1/2 x 5 5/8 in. (31.8 x 14.3 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36651,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1351,false,true,55326,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunsen,"Japanese, 1762–ca.1830",,Katsukawa Shunsen,Japanese,1762,1830,1786,1786,1786,Polychrome woodblock print; ink and color on paper,H. 12 1/4 in. (31.1 cm); W. 5 1/2 in. (14 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55326,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP173a–c,false,true,36482,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunsen,"Japanese, 1762–ca.1830",,Katsukawa Shunsen,Japanese,1762,1830,ca. 1785,1775,1795,Triptych of polychrome woodblock prints; ink and color on paper,Overall H. 12 1/4 in. (31.1 cm); W. 16 5/8 in. (42.2 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36482,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3179,false,true,55127,Asian Art,Print,『横浜異 人商館座敷之図』|Drawing Room of a Foreign Business Establishment in Yokohama,Japan,Edo period (1615–1868),,,,Artist,,Utagawa (Gountei) Sadahide,"Japanese, 1807–1878/79",,Utagawa (Goutei) Sadahide,Japanese,1807,1879,"9th month, 1861",1861,1861,Center and right sheets of a triptych of polychrome woodblock prints; ink and color on paper,Image: 13 7/8 × 18 1/4 in. (35.2 × 46.4 cm) Mat: 18 3/4 × 23 1/4 in. (47.6 × 59.1 cm),"Gift of Lincoln Kirstein, 1959",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55127,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3245,false,true,55257,Asian Art,Print,Yokohama Shukan Shin no zu|A True View of a Trading House of a Yokohama Merchant,Japan,Edo period (1615–1868),,,,Artist,,Utagawa (Gountei) Sadahide,"Japanese, 1807–1878/79",,Utagawa (Goutei) Sadahide,Japanese,1807,1879,"1st month, 1861",1861,1861,Triptych of polychrome woodblock prints; ink and color on paper,14 3/8 x 29 1/4 in. (36.5 x 74.3 cm),"Gift of Lincoln Kirstein, 1959",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55257,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3246,false,true,55258,Asian Art,Print,Uchoren no zu|Great Military Drill,Japan,Edo period (1615–1868),,,,Artist,,Utagawa (Gountei) Sadahide,"Japanese, 1807–1878/79",,Utagawa (Goutei) Sadahide,Japanese,1807,1879,"1866 (Keio 2, 2nd month)",1866,1866,Triptych of polychrome woodblock prints; ink and color on paper,13 5/8 x 27 7/16 in. (34.6 x 69.7 cm),"Gift of Lincoln Kirstein, 1959",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55258,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3247,false,true,55259,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa (Gountei) Sadahide,"Japanese, 1807–1878/79",,Utagawa (Goutei) Sadahide,Japanese,1807,1879,1861,1861,1861,Triptych of polychrome woodblock prints; ink and color on paper,Oban,"Gift of Lincoln Kirstein, 1959",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55259,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3265,false,true,37389,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa (Gountei) Sadahide,"Japanese, 1807–1878/79",,Utagawa (Goutei) Sadahide,Japanese,1807,1879,1861,1861,1861,Triptych of polychrome woodblock prints; ink and color on paper,Oban 14 1/4 x 28 5/8 in. (36.2 x 72.7 cm),"Gift of Lincoln Kirstein, 1959",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37389,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3268,false,true,55332,Asian Art,Print,Amerika Karuhorunia Ko shuppan no zu|Sailing from a California Port,Japan,Edo period (1615–1868),,,,Artist,,Utagawa (Gountei) Sadahide,"Japanese, 1807–1878/79",,Utagawa (Goutei) Sadahide,Japanese,1807,1879,1862,1862,1862,Triptych of polychrome woodblock prints; ink and color on paper,Image: 14 1/4 x 28 15/16 in. (36.2 x 73.5 cm),"Gift of Lincoln Kirstein, 1959",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55332,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3298,false,true,55396,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa (Gountei) Sadahide,"Japanese, 1807–1878/79",,Utagawa (Goutei) Sadahide,Japanese,1807,1879,November 1860,1860,1860,Polychrome woodblock print; ink and color on paper,14 x 10 in. (35.6 x 25.4 cm),"Gift of Lincoln Kirstein, 1959",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55396,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3331,false,true,55479,Asian Art,Print,Yokohama torai Amerika shonin ryoko no zu|横浜渡來亜墨利加商人旅行之図|American Merchant Strolling in Yokohama,Japan,Edo period (1615–1868),,,,Artist,,Utagawa (Gountei) Sadahide,"Japanese, 1807–1878/79",,Utagawa (Goutei) Sadahide,Japanese,1807,1879,1861 (1st month),1861,1861,Polychrome woodblock print; ink and color on paper,14 x 10 in. (35.6 x 25.4 cm),"Gift of Lincoln Kirstein, 1959",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55479,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3419,false,true,55614,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa (Gountei) Sadahide,"Japanese, 1807–1878/79",,Utagawa (Goutei) Sadahide,Japanese,1807,1879,"1861 (Bunkyu 1, 1st month)",1861,1861,Polychrome woodblock print; ink and color on paper,14 x 9 7/8 in. (35.6 x 25.1 cm),"Gift of Lincoln Kirstein, 1962",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55614,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.101,false,true,73391,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa (Gountei) Sadahide,"Japanese, 1807–1878/79",,Utagawa (Goutei) Sadahide,Japanese,1807,1879,"2nd month, 1860",1860,1860,Polychrome woodblock print; ink and color on paper,Image: 9 1/2 x 12 3/4 in. (24.1 x 32.4 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73391,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.102,false,true,73602,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa (Gountei) Sadahide,"Japanese, 1807–1878/79",,Utagawa (Goutei) Sadahide,Japanese,1807,1879,"2nd month, 1860",1860,1860,Polychrome woodblock print; ink and color on paper,Image: 9 1/2 x 12 5/8 in. (24.1 x 32.1 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73602,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.103,false,true,73603,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa (Gountei) Sadahide,"Japanese, 1807–1878/79",,Utagawa (Goutei) Sadahide,Japanese,1807,1879,"2nd month, 1860",1860,1860,Polychrome woodblock print; ink and color on paper,Image: 9 1/2 x 12 3/4 in. (24.1 x 32.4 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73603,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.104,false,true,73604,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa (Gountei) Sadahide,"Japanese, 1807–1878/79",,Utagawa (Goutei) Sadahide,Japanese,1807,1879,"2nd month, 1860",1860,1860,Polychrome woodblock print; ink and color on paper,Image: 9 1/2 x 12 5/8 in. (24.1 x 32.1 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73604,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.105,false,true,73605,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa (Gountei) Sadahide,"Japanese, 1807–1878/79",,Utagawa (Goutei) Sadahide,Japanese,1807,1879,"2nd month, 1860",1860,1860,Polychrome woodblock print; ink and color on paper,Image: 10 x 14 5/8 in. (25.4 x 37.1 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73605,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.106,false,true,73606,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa (Gountei) Sadahide,"Japanese, 1807–1878/79",,Utagawa (Goutei) Sadahide,Japanese,1807,1879,"2nd month, 1860",1860,1860,Polychrome woodblock print; ink and color on paper,Image: 9 1/2 x 12 7/8 in. (24.1 x 32.7 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73606,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.107,false,true,73392,Asian Art,Print,「神名川横浜新開港圖」|“The Newly Opened Port of Yokohama in Kanagawa Prefecture”,Japan,Edo period (1615–1868),,,,Artist,,Utagawa (Gountei) Sadahide,"Japanese, 1807–1878/79",,Utagawa (Goutei) Sadahide,Japanese,1807,1879,"2nd month, 1860",1860,1860,Triptych of polychrome woodblock prints; ink and color on paper,Image: 14 1/2 x 29 in. (36.8 x 73.7 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73392,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.109,false,true,73394,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa (Gountei) Sadahide,"Japanese, 1807–1878/79",,Utagawa (Goutei) Sadahide,Japanese,1807,1879,"3rd month, 1860",1860,1860,Polychrome woodblock print; ink and color on paper,Image: 14 1/2 x 9 1/2 in. (36.8 x 24.1 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73394,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.112,false,true,73397,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa (Gountei) Sadahide,"Japanese, 1807–1878/79",,Utagawa (Goutei) Sadahide,Japanese,1807,1879,"3rd month, 1860",1860,1860,Polychrome woodblock print; ink and color on paper,Image: 14 7/8 x 10 in. (37.8 x 25.4 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73397,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.113,false,true,73398,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa (Gountei) Sadahide,"Japanese, 1807–1878/79",,Utagawa (Goutei) Sadahide,Japanese,1807,1879,"3rd month,1860",1860,1860,Polychrome woodblock print; ink and color on paper,Image: 10 1/4 x 14 3/4 in. (26 x 37.5 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73398,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.114,false,true,73399,Asian Art,Print,Doban-e Jōgyō shiki|Color Print of a Copperplate Picture of a Toy Shop,Japan,Edo period (1615–1868),,,,Artist,,Utagawa (Gountei) Sadahide,"Japanese, 1807–1878/79",,Utagawa (Goutei) Sadahide,Japanese,1807,1879,1860,1860,1860,Polychrome woodblock print; ink and color on paper,Image: 9 1/2 x 13 3/4 in. (24.1 x 34.9 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73399,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.116,false,true,73401,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa (Gountei) Sadahide,"Japanese, 1807–1878/79",,Utagawa (Goutei) Sadahide,Japanese,1807,1879,"4th month, 1860",1860,1860,Polychrome woodblock print; ink and color on paper,Image: 14 1/2 x 10 1/8 in. (36.8 x 25.7 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73401,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.118,false,true,73403,Asian Art,Print,"「生寫異國人物」|American Woman Playing a Concertina, from the series Life Drawings of People from Foreign Nations",Japan,Edo period (1615–1868),,,,Artist,,Utagawa (Gountei) Sadahide,"Japanese, 1807–1878/79",,Utagawa (Goutei) Sadahide,Japanese,1807,1879,1860,1860,1860,Polychrome woodblock print; ink and color on paper,Image: 14 5/8 x 10 in. (37.1 x 25.4 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73403,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.119,false,true,73405,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa (Gountei) Sadahide,"Japanese, 1807–1878/79",,Utagawa (Goutei) Sadahide,Japanese,1807,1879,"11th month, 1860",1860,1860,Polychrome woodblock print; ink and color on paper,Image: 14 3/8 x 10 in. (36.5 x 25.4 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73405,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.120,false,true,73406,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa (Gountei) Sadahide,"Japanese, 1807–1878/79",,Utagawa (Goutei) Sadahide,Japanese,1807,1879,1860,1860,1860,Polychrome woodblock print; ink and color on pape22,Image: 14 3/4 x 10 in. (37.5 x 25.4 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73406,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.121,false,true,73407,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa (Gountei) Sadahide,"Japanese, 1807–1878/79",,Utagawa (Goutei) Sadahide,Japanese,1807,1879,"11th month, 1860",1860,1860,Polychrome woodblock print; ink and color on paper,Image: 14 1/2 x 10 in. (36.8 x 25.4 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73407,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.122,false,true,73408,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa (Gountei) Sadahide,"Japanese, 1807–1878/79",,Utagawa (Goutei) Sadahide,Japanese,1807,1879,1861,1861,1861,Polychrome woodblock print; ink and color on paper,Image: 14 1/2 x 9 3/4 in. (36.8 x 24.8 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73408,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.123,false,true,73409,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa (Gountei) Sadahide,"Japanese, 1807–1878/79",,Utagawa (Goutei) Sadahide,Japanese,1807,1879,1861,1861,1861,Polychrome woodblock print; ink and color on paper,Image: 14 1/2 x 9 7/8 in. (36.8 x 25.1 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73409,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.125,false,true,73411,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa (Gountei) Sadahide,"Japanese, 1807–1878/79",,Utagawa (Goutei) Sadahide,Japanese,1807,1879,"2nd month, 1861",1861,1861,Polychrome woodblock print; ink and color on paper,Image: 14 1/4 x 9 3/4 in. (36.2 x 24.8 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73411,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.128,false,true,73413,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa (Gountei) Sadahide,"Japanese, 1807–1878/79",,Utagawa (Goutei) Sadahide,Japanese,1807,1879,"7th month, 1861",1861,1861,Polychrome woodblock print; ink and color on paper,Image: 14 1/2 x 9 3/4 in. (36.8 x 24.8 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73413,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.132,false,true,73417,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa (Gountei) Sadahide,"Japanese, 1807–1878/79",,Utagawa (Goutei) Sadahide,Japanese,1807,1879,"9th month, 1861",1861,1861,Triptych of polychrome woodblock prints; ink and color on paper,Image: 14 1/8 x 29 1/16 in. (35.9 x 73.8 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73417,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3389a–e,false,true,55564,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa (Gountei) Sadahide,"Japanese, 1807–1878/79",,Utagawa (Goutei) Sadahide,Japanese,1807,1879,April 1861,1861,1861,Oban pentaptych of polychrome woodblock prints; ink and color on paper,Image: 14 1/8 x 49 13/16 in. (35.9 x 126.5 cm),"Gift of Lincoln Kirstein, 1960",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55564,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.108a–c,false,true,73393,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa (Gountei) Sadahide,"Japanese, 1807–1878/79",,Utagawa (Goutei) Sadahide,Japanese,1807,1879,"2nd month, 1860",1860,1860,Triptych of polychrome woodblock prints; ink and color on paper,Image (a): 14 3/4 x 9 7/8 in. (37.5 x 25.1 cm) Image (b): 14 3/4 x 9 7/8 in. (37.5 x 25.1 cm) Image (c): 14 3/4 x 9 7/8 in. (37.5 x 25.1 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73393,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.110a–c,false,true,73395,Asian Art,Print,「横浜買物圖繒 唐物店之圖」|Curio Shop in Yokohama,Japan,Edo period (1615–1868),,,,Artist,,Utagawa (Gountei) Sadahide,"Japanese, 1807–1878/79",,Utagawa (Goutei) Sadahide,Japanese,1807,1879,"3rd month, 1860",1860,1860,Triptych of polychrome woodblock prints; ink and color on paper,Image (a): 14 1/4 x 9 5/8 in. (36.2 x 24.4 cm) Image (b): 14 1/4 x 9 7/8 in. (36.2 x 25.1 cm) Image (c): 14 1/4 x 9 5/8 in. (36.2 x 24.4 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73395,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.111a–c,false,true,73396,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa (Gountei) Sadahide,"Japanese, 1807–1878/79",,Utagawa (Goutei) Sadahide,Japanese,1807,1879,"3rd month, 1860",1860,1860,Triptych of polychrome woodblock prints; ink and color on paper,Image (a): 14 1/8 x 9 5/8 in. (35.9 x 24.4 cm) Image (b): 14 1/8 x 9 5/8 in. (35.9 x 24.4 cm) Image (c): 14 1/8 x 9 5/8 in. (35.9 x 24.4 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73396,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.115a–c,false,true,73400,Asian Art,Print,Yokohama Hon-chō...ni Miyozaki...kenkin zu|Detailed Print of Yokohama Hon-chō and the Miyozaki Pleasure Quarter,Japan,Edo period (1615–1868),,,,Artist,,Utagawa (Gountei) Sadahide,"Japanese, 1807–1878/79",,Utagawa (Goutei) Sadahide,Japanese,1807,1879,"4th month, 1860",1860,1860,Triptych of polychrome woodblock prints; ink and color on paper,Image (a): 14 3/8 x 9 5/8 in. (36.5 x 24.4 cm) Image (b): 14 1/4 x 9 3/4 in. (36.2 x 24.8 cm) Image (c): 14 1/4 x 9 3/8 in. (36.2 x 23.8 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73400,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.117a–c,false,true,73402,Asian Art,Print,Kanagwa Yokohama minato...zue|Pictorial Guide to Yokohama Harbor,Japan,Edo period (1615–1868),,,,Artist,,Utagawa (Gountei) Sadahide,"Japanese, 1807–1878/79",,Utagawa (Goutei) Sadahide,Japanese,1807,1879,"7th month, 1860",1860,1860,Triptych of polychrome woodblock prints; ink and color on paper,Image (a): 14 1/4 x 9 7/8 in. (36.2 x 25.1 cm) Image (b): 14 1/4 x 9 5/8 in. (36.2 x 24.4 cm) Image (c): 14 1/4 x 9 3/8 in. (36.2 x 23.8 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73402,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.124a–f,false,true,73410,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa (Gountei) Sadahide,"Japanese, 1807–1878/79",,Utagawa (Goutei) Sadahide,Japanese,1807,1879,"2nd month, 1861",1861,1861,Hexaptych of polychrome woodblock prints; ink and color on paper,Image (a): 14 1/2 x 10 in. (36.8 x 25.4 cm) Image (b): 14 1/2 x 9 7/8 in. (36.8 x 25.1 cm) Image (c): 14 5/8 x 10 in. (37.1 x 25.4 cm) Image (d): 14 1/2 x 9 7/8 in. (36.8 x 25.1 cm) Image (e): 14 5/8 x 9 7/8 in. (37.1 x 25.1 cm) Image (f): 14 1/2 x 9 7/8 in. (36.8 x 25.1 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73410,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.126a–c,false,true,73572,Asian Art,Print,Gok'koku jimbutsu gyo...no zu|Picture of a Parade of the Five Nations,Japan,Edo period (1615–1868),,,,Artist,,Utagawa (Gountei) Sadahide,"Japanese, 1807–1878/79",,Utagawa (Goutei) Sadahide,Japanese,1807,1879,"3rd month, 1861",1861,1861,Triptych of polychrome woodblock prints; ink and color on paper,Image (a): 14 1/4 x 9 3/4 in. (36.2 x 24.8 cm) Image (b): 14 1/4 x 9 3/4 in. (36.2 x 24.8 cm) Image (c): 14 1/4 x 9 3/4 in. (36.2 x 24.8 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73572,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.127a–e,false,true,73412,Asian Art,Woodblock print,「横浜交易西洋人荷物運送之圖」|“Yokohama Trade: Westerners Loading Cargo”,Japan,Edo period (1615–1868),,,,Artist,,Utagawa (Gountei) Sadahide,"Japanese, 1807–1878/79",,Utagawa (Goutei) Sadahide,Japanese,1807,1879,"4th month, 1861",1861,1861,Pentaptych of polychrome woodblock prints; ink and color on paper,Image (a): 14 1/2 x 9 1/2 in. (36.8 x 24.1 cm) Image (b): 14 1/2 x 9 7/8 in. (36.8 x 25.1 cm) Image (c): 14 1/2 x 9 3/4 in. (36.8 x 24.8 cm) Image (d): 14 1/2 x 9 7/8 in. (36.8 x 25.1 cm) Image (e): 14 1/2 x 9 7/8 in. (36.8 x 25.1 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73412,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.129a–c,false,true,73414,Asian Art,Print,『横浜異 人商館写真之図』|Foreign Business Establishment in Yokohama,Japan,Edo period (1615–1868),,,,Artist,,Utagawa (Gountei) Sadahide,"Japanese, 1807–1878/79",,Utagawa (Goutei) Sadahide,Japanese,1807,1879,1861,1861,1861,Triptych of polychrome woodblock prints; ink and color on paper,Image (a): 13 3/4 x 9 1/4 in. (34.9 x 23.5 cm) Image (b): 14 x 9 7/8 in. (35.6 x 25.1 cm) Image (c): 14 x 9 1/4 in. (35.6 x 23.5 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73414,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.130a–c,false,true,73415,Asian Art,Print,「横浜異人商館買場之圖」|Foreign Business Establishment in Yokohama,Japan,Edo period (1615–1868),,,,Artist,,Utagawa (Gountei) Sadahide,"Japanese, 1807–1878/79",,Utagawa (Goutei) Sadahide,Japanese,1807,1879,"9th month, 1861",1861,1861,Triptych of polychrome woodblock prints; ink and color on paper,Image (a): 14 1/2 x 10 in. (36.8 x 25.4 cm) Image (b): 14 1/2 x 10 in. (36.8 x 25.4 cm) Image (c): 14 1/2 x 10 in. (36.8 x 25.4 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73415,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.131a–c,false,true,73416,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa (Gountei) Sadahide,"Japanese, 1807–1878/79",,Utagawa (Goutei) Sadahide,Japanese,1807,1879,"9th month, 1861",1861,1861,Triptych of polychrome woodblock prints; ink and color on paper,Image (a): 14 x 9 3/4 in. (35.6 x 24.8 cm) Image (b): 14 x 9 3/8 in. (35.6 x 23.8 cm) Image (c): 14 x 10 7/8 in. (35.6 x 27.6 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73416,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.133a–c,false,true,73419,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa (Gountei) Sadahide,"Japanese, 1807–1878/79",,Utagawa (Goutei) Sadahide,Japanese,1807,1879,"3rd month, 1862",1862,1862,Triptych of polychrome woodblock prints; ink and color on paper,Image (a): 14 1/2 x 9 7/8 in. (36.8 x 25.1 cm) Image (b): 14 1/2 x 10 in. (36.8 x 25.4 cm) Image (c): 14 1/2 x 10 in. (36.8 x 25.4 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73419,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.290a–c,false,true,73567,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa (Gountei) Sadahide,"Japanese, 1807–1878/79",,Utagawa (Goutei) Sadahide,Japanese,1807,1879,ca. 1862–63,1862,1863,Triptych of polychrome woodblock prints; ink and color on paper,Image (a): 14 1/2 x 9 1/2 in. (36.8 x 24.1 cm) Image (b): 14 1/2 x 9 3/8 in. (36.8 x 23.8 cm) Image (c): 14 3/8 x 9 1/2 in. (36.5 x 24.1 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73567,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1490,false,true,55586,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Ryūkōsai,active late 18th century,,Ryūkōsai,Japanese,1771,1799,ca. 1790,1780,1800,Polychrome woodblock print; ink and color on paper,12 13/16 x 5 11/16 in. (32.5 x 14.4 cm),"Gift of S. C. Bosch-Reitz, 1927",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55586,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP136,false,true,36615,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Isoda Koryūsai,"Japanese, 1735–ca. 1790",,Isoda Koryūsai,Japanese,1735,1790,ca. 1770,1760,1780,Polychrome woodblock print; ink and color on paper,26 23/32 x 4 3/4 in. (67.9 x 12.1 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36615,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP137,false,true,36616,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Isoda Koryūsai,"Japanese, 1735–ca. 1790",,Isoda Koryūsai,Japanese,1735,1790,ca. 1770,1760,1780,Polychrome woodblock print; ink and color on paper,26 1/8 x 4 3/4 in. (66.4 x 12.1 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36616,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP138,false,true,36617,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Isoda Koryūsai,"Japanese, 1735–ca. 1790",,Isoda Koryūsai,Japanese,1735,1790,ca. 1770,1760,1780,Polychrome woodblock print; ink and color on paper,26 3/8 x 4 3/5 in. (67.0 x 11.7 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36617,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP139,false,true,36618,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Isoda Koryūsai,"Japanese, 1735–ca. 1790",,Isoda Koryūsai,Japanese,1735,1790,ca. 1770,1760,1780,Polychrome woodblock print; ink and color on paper,27 15/32 x 4 3/8 in. (69.8 x 11.1 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36618,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP140,false,true,36619,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Isoda Koryūsai,"Japanese, 1735–ca. 1790",,Isoda Koryūsai,Japanese,1735,1790,ca. 1775,1765,1785,Polychrome woodblock print; ink and color on paper,14 3/5 x 9 7/8 in. (37.1 x 25.1 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36619,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP141,false,true,36620,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Isoda Koryūsai,"Japanese, 1735–ca. 1790",,Isoda Koryūsai,Japanese,1735,1790,ca. 1770,1760,1780,Polychrome woodblock print; ink and color on paper,10 3/8 x 7 5/8 in. (26.4 x 19.4 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36620,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP220,false,true,36694,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Isoda Koryūsai,"Japanese, 1735–ca. 1790",,Isoda Koryūsai,Japanese,1735,1790,ca. 1770,1760,1780,Polychrome woodblock print; ink and color on paper,28 1/2 x 4 3/5 in. (72.4 x 11.7 cm),"Rogers Fund, 1917",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36694,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP558,false,true,37009,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Isoda Koryūsai,"Japanese, 1735–ca. 1790",,Isoda Koryūsai,Japanese,1735,1790,ca. 1768,1758,1778,Polychrome woodblock print; ink and color on paper,10 x 7 11/32 in. (25.4 x 18.7 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37009,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP559,false,true,37010,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Isoda Koryūsai,"Japanese, 1735–ca. 1790",,Isoda Koryūsai,Japanese,1735,1790,ca. 1768,1758,1778,Polychrome woodblock print; ink and color on paper,10 x 7 7/32 in. (25.4 x 18.4 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37010,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP560,false,true,37011,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Isoda Koryūsai,"Japanese, 1735–ca. 1790",,Isoda Koryūsai,Japanese,1735,1790,ca. 1770,1760,1780,Polychrome woodblock print; ink and color on paper,10 x 7 1/8 in. (25.4 x 18.1 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37011,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP673,false,true,37120,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Isoda Koryūsai,"Japanese, 1735–ca. 1790",,Isoda Koryūsai,Japanese,1735,1790,ca. 1769,1759,1779,Polychrome woodblock print; ink and color on paper,27 x 4 1/8 in. (68.6 x 10.5 cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37120,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP674,false,true,37121,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Isoda Koryūsai,"Japanese, 1735–ca. 1790",,Isoda Koryūsai,Japanese,1735,1790,ca. 1770,1760,1790,Polychrome woodblock print; ink and color on paper,27 3/4 x 4 5/32 in. (70.5 x 10.6 cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37121,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP700,false,true,37147,Asian Art,Print,"『雛形若菜の初模様 つるや内 すがたみ』|The Courtesan Sugatami of the Tsuruya Brothel, from the series “A Pat-tern Book of the Year’s First Designs, Fresh as Spring Herbs” (“Hinagata wakana no hatsu moyō”)",Japan,Edo period (1615–1868),,,,Artist,,Isoda Koryūsai,"Japanese, 1735–ca. 1790",,Isoda Koryūsai,Japanese,1735,1790,1777–78,1777,1778,Polychrome woodblock print; ink and color on paper,15 7/32 x 10 1/8 in. (38.7 x 25.7 cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37147,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP881,false,true,54574,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Isoda Koryūsai,"Japanese, 1735–ca. 1790",,Isoda Koryūsai,Japanese,1735,1790,ca. 1769,1759,1779,Polychrome woodblock print; ink and color on paper,H. 9 7/8 in. (25.1 cm); W. 7 1/2 in. (19.1 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54574,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP882,false,true,54575,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Isoda Koryūsai,"Japanese, 1735–ca. 1790",,Isoda Koryūsai,Japanese,1735,1790,ca. 1760,1750,1770,Polychrome woodblock print; ink and color on paper,Image: 8 5/8 × 8 1/4 in. (21.9 × 21 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54575,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP883,false,true,54577,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Isoda Koryūsai,"Japanese, 1735–ca. 1790",,Isoda Koryūsai,Japanese,1735,1790,ca. 1769,1759,1779,Polychrome woodblock print; ink and color on paper,H. 10 1/4 in. (26 cm); W. 7 5/8 in. (19.4 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54577,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP884,false,true,54578,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Isoda Koryūsai,"Japanese, 1735–ca. 1790",,Isoda Koryūsai,Japanese,1735,1790,ca. 1769,1759,1779,Polychrome woodblock print; ink and color on paper,H. 27 1/5 in. (69.1 cm); W. 3 7/8 in. (9.8 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54578,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP885,false,true,54579,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Isoda Koryūsai,"Japanese, 1735–ca. 1790",,Isoda Koryūsai,Japanese,1735,1790,ca 1773–75,1763,1785,Polychrome woodblock print; ink and color on paper,H. 10 3/8 in. (26.4 cm); W. 7 5/8 in. (19.4 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54579,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP886,false,true,54580,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Isoda Koryūsai,"Japanese, 1735–ca. 1790",,Isoda Koryūsai,Japanese,1735,1790,ca. 1770,1780,1780,Polychrome woodblock print; ink and color on paper,H. 10 in. (25.4 cm); W. 6 7/8 in. (22.5 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54580,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP887,false,true,54581,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Isoda Koryūsai,"Japanese, 1735–ca. 1790",,Isoda Koryūsai,Japanese,1735,1790,ca. 1770,1760,1780,Polychrome woodblock print; ink and color on paper,H. 10 3/8 in. (26.4 cm); W. 7 5/8 in. (19.4 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54581,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP888,false,true,54582,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Isoda Koryūsai,"Japanese, 1735–ca. 1790",,Isoda Koryūsai,Japanese,1735,1790,ca. 1771,1761,1781,Polychrome woodblock print; ink and color on paper,H. 10 5/8 in. (27 cm); W. 7 5/8 in. (19.4 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54582,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP889,false,true,54583,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Isoda Koryūsai,"Japanese, 1735–ca. 1790",,Isoda Koryūsai,Japanese,1735,1790,ca. 1773,1763,1783,Polychrome woodblock print; ink and color on paper,H. 10 1/4 in. (26 cm); W. 7 5/8 in. (19.4 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54583,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP890,false,true,54584,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Isoda Koryūsai,"Japanese, 1735–ca. 1790",,Isoda Koryūsai,Japanese,1735,1790,ca. 1773,1763,1783,Polychrome woodblock print; ink and color on paper,H. 10 1/8 in. (26.7 cm); W. 7 1/2 in. (19.1 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54584,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP892,false,true,54586,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Isoda Koryūsai,"Japanese, 1735–ca. 1790",,Isoda Koryūsai,Japanese,1735,1790,ca. 1773,1763,1783,Polychrome woodblock print; ink and color on paper,H. 8 3/8 in. (21.3 cm); W. 14 3/4 in. (37.5 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54586,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1125,false,true,55045,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Isoda Koryūsai,"Japanese, 1735–ca. 1790",,Isoda Koryūsai,Japanese,1735,1790,ca. 1770,1760,1780,Polychrome woodblock print; ink and color on paper,27 3/8 x 4 7/8in. (69.5 x 12.4cm),"Rogers Fund, 1919",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55045,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1208,false,true,55132,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Isoda Koryūsai,"Japanese, 1735–ca. 1790",,Isoda Koryūsai,Japanese,1735,1790,ca. 1770,1760,1780,Polychrome woodblock print; ink and color on paper,H. 25 9/16 in. (64.9 cm); W. 4 3/4 in. (12.1 cm),"Rogers Fund, 1920",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55132,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1209,false,true,55133,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Isoda Koryūsai,"Japanese, 1735–ca. 1790",,Isoda Koryūsai,Japanese,1735,1790,ca. 1770,1760,1780,Polychrome woodblock print; ink and color on paper,H. 27 1/4 in. (69.2 cm); W. 4 5/8 in. (11.7 cm),"Rogers Fund, 1920",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55133,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1230,false,true,55151,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Isoda Koryūsai,"Japanese, 1735–ca. 1790",,Isoda Koryūsai,Japanese,1735,1790,ca. 1765,1755,1775,Polychrome woodblock print; ink and color on paper,H. 10 3/16 in. (25.9 cm); W. 7 9/16 in. (19.2 cm),"Rogers Fund, 1921",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55151,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1271,false,true,55168,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Isoda Koryūsai,"Japanese, 1735–ca. 1790",,Isoda Koryūsai,Japanese,1735,1790,ca. 1770,1760,1780,Polychrome woodblock print; ink and color on paper,H. 28 1/8 in. (71.4 cm); W. 5 in. (12.7 cm),"Rogers Fund, 1921",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55168,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1291,false,true,55233,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Isoda Koryūsai,"Japanese, 1735–ca. 1790",,Isoda Koryūsai,Japanese,1735,1790,1770,1770,1770,Polychrome woodblock print; ink and color on paper,H. 27 3/4 in. (70.5 cm); W. 4 3/8 in. (11.1 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55233,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1459,false,true,55506,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Isoda Koryūsai,"Japanese, 1735–ca. 1790",,Isoda Koryūsai,Japanese,1735,1790,probably 18th century,1700,1799,Hand-colored print (ishizuri-e); ink and color on paper,29 15/16 x 10 1/4 in. (76 x 26 cm),"Rogers Fund, 1923",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55506,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1703,false,true,55944,Asian Art,Print,"『雛形若菜初模様 玉や内 しら玉』|The Courtesan Shiratama of the Tamaya Brothel, from the series “A Pat-tern Book of the Year’s First Designs, Fresh as Spring Herbs” (“Hinagata wakana hatsu moyō”)",Japan,Edo period (1615–1868),,,,Artist,,Isoda Koryūsai,"Japanese, 1735–ca. 1790",,Isoda Koryūsai,Japanese,1735,1790,1777–78,1777,1778,Polychrome woodblock print; ink and color on paper,H. 15 1/4 in. (38.7 cm); W. 10 1/2 in. (26.7 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55944,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2626,false,true,56775,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Isoda Koryūsai,"Japanese, 1735–ca. 1790",,Isoda Koryūsai,Japanese,1735,1790,ca. 1770,1760,1780,Polychrome woodblock print; ink and color on paper,10 1/2 x 7 1/4 in. (26.7 x 18.4 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56775,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2627,false,true,51996,Asian Art,Print,"『雛形若菜初模様 四ツ目屋内 にしき木』|The Courtesan Nishikigi of the Yotsumeya Brothel, from the series “A Pattern Book of the Year’s First Designs, Fresh as Spring Herbs” (“Hinagata wakana hatsu moyō”)",Japan,Edo period (1615–1868),,,,Artist,,Isoda Koryūsai,"Japanese, 1735–ca. 1790",,Isoda Koryūsai,Japanese,1735,1790,1776,1776,1776,Polychrome woodblock print; ink and color on paper,14 7/8 x 10 1/8 in. (37.8 x 25.7 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/51996,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2628,false,true,56776,Asian Art,Print,"『雛形若菜初模様 四ツ目屋内 さよぎぬ』|The Courtesan Sayoginu of the Yotsumeya Brothel, from the series “A Pattern Book of the Year’s First Designs, Fresh as Spring Herbs” (“Hinagata wakana hatsu moyō”)",Japan,Edo period (1615–1868),,,,Artist,,Isoda Koryūsai,"Japanese, 1735–ca. 1790",,Isoda Koryūsai,Japanese,1735,1790,1776,1776,1776,Polychrome woodblock print; ink and color on paper,15 1/4 x 10 3/8 in. (38.7 x 26.4 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56776,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2629,false,true,56777,Asian Art,Print,"『雛形若菜の初模様 つたや内 人まち』|The Courtesan Hitomachi of the Tsutaya Brothel, from the series “A Pat-tern Book of the Year’s First Designs, Fresh as Spring Herbs” (“Hinagata wakana no hatsu moyō”)",Japan,Edo period (1615–1868),,,,Artist,,Isoda Koryūsai,"Japanese, 1735–ca. 1790",,Isoda Koryūsai,Japanese,1735,1790,1777–78,1777,1778,Polychrome woodblock print; ink and color on paper,15 1/2 x 10 3/8 in. (39.4 x 26.4 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56777,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2630,false,true,56778,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Isoda Koryūsai,"Japanese, 1735–ca. 1790",,Isoda Koryūsai,Japanese,1735,1790,ca. 1770,1760,1780,Polychrome woodblock print; ink and color on paper,10 3/8 x 7 5/8 in. (26.4 x 19.4 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56778,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2786,false,true,57103,Asian Art,Print,Umemi Tsuki|Plum-Seeing Month: Second Month,Japan,Edo period (1615–1868),,,,Artist,,Isoda Koryūsai,"Japanese, 1735–ca. 1790",,Isoda Koryūsai,Japanese,1735,1790,1735–1790,1735,1790,Polychrome woodblock print; ink and color on paper,10 3/8 x 7 3/4 in. (26.4 x 19.7 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/57103,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2787,false,true,57102,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Isoda Koryūsai,"Japanese, 1735–ca. 1790",,Isoda Koryūsai,Japanese,1735,1790,1735–1790,1735,1790,Polychrome woodblock print; ink and color on paper,10 1/4 x 7 5/8 in. (26 x 19.4 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/57102,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2788,false,true,57101,Asian Art,Woodblock print,"『雛形若菜の初模様 扇屋内 七越』|The Courtesan Nanakoshi of the Ōgiya Brothel, from the series “A Pat-tern Book of the Year’s First Designs, Fresh as Spring Herbs” (“Hinagata wakana no hatsu moyō”)",Japan,Edo period (1615–1868),,,,Artist,,Isoda Koryūsai,"Japanese, 1735–ca. 1790",,Isoda Koryūsai,Japanese,1735,1790,1777–78,1777,1778,Polychrome woodblock print; ink and color on paper,15 1/4 x 10 1/2 in. (38.7 x 26.7 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/57101,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3408,false,true,55596,Asian Art,Print,"『雛形若菜の初模様 扇屋内 からうた』|The Courtesan Karauta of the Ōgiya Brothel, from the series “A Pattern Book of the Year’s First Designs, Fresh as Spring Herbs” (“Hinagata wakana no hatsu moyō”)",Japan,Edo period (1615–1868),,,,Artist,,Isoda Koryūsai,"Japanese, 1735–ca. 1790",,Isoda Koryūsai,Japanese,1735,1790,1777–78,1777,1778,Polychrome woodblock print; ink and color on paper,15 x 10 1/4 in. (38.1 x 26 cm),"Bequest of Julia H. Manges, 1960",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55596,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1436,false,true,55470,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Suzuki Haruji,"Japanese, active ca. 1770",,Suzuki Haruji,Japanese,1770,1770,ca. 1770,1760,1780,Polychrome woodblock print; ink and color on paper,H. 27 13/16 in. (70.6 cm); W. 4 5/8 in. (11.7 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55470,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2456,false,true,56885,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Suzuki Haruji,"Japanese, active ca. 1770",,Suzuki Haruji,Japanese,1770,1770,ca. 1770,1760,1780,Polychrome woodblock print; ink and color on paper,26 3/4 x 4 5/8 in. (67.9 x 11.7 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56885,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2624,false,true,56768,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyonobu II,"Japanese, ca. 1702–1752",,Torii Kiyonobu II,Japanese,1702,1752,1735 or 1736,1735,1736,Polychrome woodblock print; ink and color on paper,12 1/8 x 5 3/4 in. (30.8 x 14.6 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56768,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2625,false,true,56769,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyonobu II,"Japanese, ca. 1702–1752",,Torii Kiyonobu II,Japanese,1702,1752,1747,1747,1747,Polychrome woodblock print; ink and color on paper,11 1/2 x 5 9/16 in. (29.2 x 14.1 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56769,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1486,false,true,55580,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kashosai Shunsen,"Japanese, died after 1830",,Kashosai Shunsen,Japanese,1830,1830,ca. 1810,1800,1820,Polychrome woodblock print; ink and color on paper,H. 7 13/16 in. (19.8 cm); W. 15 3/16 in. (38.6 cm),"Rogers Fund, 1926",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55580,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP972,false,true,45475,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Okimura Toshinobu,"Japanese, active 1725–50",,Okimura Toshinobu,Japanese,1725,1750,ca. 1793,1783,1803,Tan-e (hand-colored print); ink and color on paper,Aiban; H. 13 3/16 in. (33.5 cm); W. 9 in. (22.9 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45475,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2658,false,true,56837,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Nishimura Shigenobu,"Japanese, active 1729–39",,Nishimura Shigenobu,Japanese,1729,1739,ca. 1738,1728,1748,Polychrome woodblock print; ink and color on paper,13 1/4 x 6 1/4 in. (33.7 x 15.9 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56837,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2756,false,true,57022,Asian Art,Print,Odari Kafu|Style of the Dancer,Japan,Edo period (1615–1868),,,,Artist,,Nishimura Shigenobu,"Japanese, active 1729–39",,Nishimura Shigenobu,Japanese,1729,1739,first half of 18th century,1700,1749,Tan-e print (hand-colored); ink and color on paper,11 7/8 x 6 in. (30.2 x 15.2 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/57022,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2766,false,true,56671,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Yamamoto Yoshinobu,"Japanese, active 1748–63",,Yamamoto Yoshinobu,Japanese,1748,1763,ca. 1750,1740,1760,Beni-e (rouge) woodblock print; ink and color on paper,H. 12 1/8 in. (30.8 cm); W. 5 1/2 in. (14 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56671,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP196,false,true,36673,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Tōshūsai Sharaku,"Japanese, active 1794–95",,Tōshūsai Sharaku,Japanese,1794,1795,1794,1792,1796,"Polychrome woodblock print; ink, color, white mica on paper",14 7/8 x 9 7/8 in. (37.8 x 25.1 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36673,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP197,false,true,36674,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Tōshūsai Sharaku,"Japanese, active 1794–95",,Tōshūsai Sharaku,Japanese,1794,1795,ca. 1794,1784,1804,Polychrome woodblock print; ink and color on paper,15 1/8 x 9 7/8 in. (38.4 x 25.1 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36674,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP732,false,true,37180,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Tōshūsai Sharaku,"Japanese, active 1794–95",,Tōshūsai Sharaku,Japanese,1794,1795,1794–75,1794,1795,Middle sheet of a triptych of polychrome woodblock prints; ink and color on paper,Image: 12 1/2 x 6 in. (31.8 x 15.2 cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37180,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP733,false,true,37181,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,After,Tōshūsai Sharaku,"Japanese, active 1794–95",,Tōshūsai Sharaku,Japanese,1794,1795,Probably late 1880s or early 1890s,1880,1899,Polychrome woodblock print; ink and color on paper,14 3/4 x 10 1/8 in. (37.5 x 25.7 cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37181,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP734,false,true,37182,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,After,Tōshūsai Sharaku,"Japanese, active 1794–95",,Tōshūsai Sharaku,Japanese,1794,1795,Probably late 1880s or early 1890s,1880,1899,Polychrome woodblock print; ink and color on paper with mica ground,15 x 10 1/5 in. (38.1 x 25.9 cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37182,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1495,false,true,37327,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Tōshūsai Sharaku,"Japanese, active 1794–95",,Tōshūsai Sharaku,Japanese,1794,1795,1794,1794,1794,"Polychrome woodblock print; ink, color, white mica on paper",H. 14 1/2 in. (36.8 cm); W. 9 1/2 in. (24.1 cm),"Fletcher Fund, 1928",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37327,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1519,false,true,37328,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Tōshūsai Sharaku,"Japanese, active 1794–95",,Tōshūsai Sharaku,Japanese,1794,1795,1794–95,1794,1795,Possibly one sheet of a triptych of polychrome woodblock prints; ink and color on paper,12 1/2 x 5 7/8 in. (31.8 x 14.9 cm),"Fletcher Fund, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37328,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1520,false,true,37329,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Tōshūsai Sharaku,"Japanese, active 1794–95",,Tōshūsai Sharaku,Japanese,1794,1795,1794–95,1794,1795,Polychrome woodblock print; ink and color on paper,Image: 12 3/4 x 6 in. (32.4 x 15.2 cm),"Fletcher Fund, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37329,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1521,false,true,37330,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Tōshūsai Sharaku,"Japanese, active 1794–95",,Tōshūsai Sharaku,Japanese,1794,1795,1794,1794,1794,"Polychrome woodblock print; ink, color, white mica on paper",H. 14 1/2 in. (36.8 cm); W. 9 11/16 in. (24.6 cm),"Fletcher Fund, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37330,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1522,false,true,37331,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Tōshūsai Sharaku,"Japanese, active 1794–95",,Tōshūsai Sharaku,Japanese,1794,1795,1794,1794,1794,Polychrome woodblock print; ink and color on paper with mica ground,15 x 10 in. (38.1 x 25.4 cm),"Fletcher Fund, 1912",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37331,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1523,false,true,37332,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Tōshūsai Sharaku,"Japanese, active 1794–95",,Tōshūsai Sharaku,Japanese,1794,1795,1794,1794,1794,"Polychrome woodblock print; ink, color, white mica on paper",14 3/5 x 9 3/5 in. (37.1 x 24.4 cm),"Fletcher Fund, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37332,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1737,false,true,37347,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Tōshūsai Sharaku,"Japanese, active 1794–95",,Tōshūsai Sharaku,Japanese,1794,1795,1794,1794,1794,"Polychrome woodblock print; ink, color, white mica on paper",14 1/8 x 9 7/16 in. (35.9 x 24.0 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37347,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2645,false,true,37348,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Tōshūsai Sharaku,"Japanese, active 1794–95",,Tōshūsai Sharaku,Japanese,1794,1795,1794–95,1794,1795,Polychrome woodblock print; ink and color on paper,12 7/8 x 5 31/32 in. (32.7 x 15.2 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37348,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2646,false,true,37349,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Tōshūsai Sharaku,"Japanese, active 1794–95",,Tōshūsai Sharaku,Japanese,1794,1795,1794–75,1794,1975,Polychrome woodblock print; ink and color on paper,12 1/2 x 5 5/8 in. (31.8 x 14.3 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37349,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2647,false,true,37350,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Tōshūsai Sharaku,"Japanese, active 1794–95",,Tōshūsai Sharaku,Japanese,1794,1795,ca. 1794,1784,1804,"Polychrome woodblock print; ink and color on paper (hoso-e, Yellow ground)",12 31/32 x 6 11/32 in. (33 x 16.1 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37350,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2648,false,true,37351,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Tōshūsai Sharaku,"Japanese, active 1794–95",,Tōshūsai Sharaku,Japanese,1794,1795,1794,1794,1794,Left-hand sheet of a triptych of polychrome woodblock prints; ink and color on paper,12 3/4 x 5 31/32 in. (32.4 x 15.2 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37351,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2649,false,true,37352,Asian Art,Print,三代目市川高麗蔵の志賀大七|Kabuki Actor Ichikawa Komazō III as Shiga Daishichi in the Play A Medley of Tales of Revenge (Katakiuchi noriaibanashi),Japan,Edo period (1615–1868),,,,Artist,,Tōshūsai Sharaku,"Japanese, active 1794–95",,Tōshūsai Sharaku,Japanese,1794,1795,"5th month, 1794",1794,1794,"Polychrome woodblock print; ink, color, white mica on paper",14 3/8 x 9 7/8 in. (36.5 x 25.1 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37352,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2650,false,true,37353,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Tōshūsai Sharaku,"Japanese, active 1794–95",,Tōshūsai Sharaku,Japanese,1794,1795,1794,1794,1794,"Polychrome woodblock print; ink, color, white mica on paper",14 7/16 x 9 1/3 in. (36.7 x 23.7 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37353,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2822,false,true,37358,Asian Art,Print,三代目大谷鬼次の奴江戸兵衛|Kabuki Actor Ōtani Oniji III as Yakko Edobei in the Play The Colored Reins of a Loving Wife (Koi nyōbō somewake tazuna),Japan,Edo period (1615–1868),,,,Artist,,Tōshūsai Sharaku,"Japanese, active 1794–95",,Tōshūsai Sharaku,Japanese,1794,1795,"6th month, 1794",1794,1794,"Polychrome woodblock print; ink, color, white mica on paper",15 x 9 7/8 in. (38.1 x 25.1 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37358,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2823,false,true,37359,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Tōshūsai Sharaku,"Japanese, active 1794–95",,Tōshūsai Sharaku,Japanese,1794,1795,1794,1794,1794,"Polychrome woodblock print; ink, color, white mica on paper",H. 15 in. (38.1 cm); W. 9 7/8 in. (25.1 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37359,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3017,false,true,37363,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Tōshūsai Sharaku,"Japanese, active 1794–95",,Tōshūsai Sharaku,Japanese,1794,1795,1794–95,1794,1795,Polychrome woodblock print; ink and color on paper; white mica ground,14 3/5 x 9 3/4 in. (37.1 x 24.8 cm) Oban,"Harris Brisbane Dick Fund, 1946",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37363,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3111,false,true,37365,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Tōshūsai Sharaku,"Japanese, active 1794–95",,Tōshūsai Sharaku,Japanese,1794,1795,1794,1794,1794,"Polychrome woodblock print; ink, color, white mica on paper",10 x 14 3/8 in. (25.4 x 36.5 cm),"Harris Brisbane Dick Fund and Rogers Fund, 1949",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37365,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3112,false,true,37366,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Tōshūsai Sharaku,"Japanese, active 1794–95",,Tōshūsai Sharaku,Japanese,1794,1795,1794,1794,1794,"Polychrome woodblock print; ink, color, white mica on paper",H. 14 5/8 in. (37.1 cm); W. 9 7/8 in. (25.1 cm),"Harris Brisbane Dick Fund and Rogers Fund, 1949",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37366,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3113,false,true,37367,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Tōshūsai Sharaku,"Japanese, active 1794–95",,Tōshūsai Sharaku,Japanese,1794,1795,1794,1794,1794,"Polychrome woodblock print; ink, color, white mica on paper",H. 15 in. (38.1 cm); W. 9 15/16 in. (25.2 cm),"Harris Brisbane Dick Fund and Rogers Fund, 1949",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37367,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3114,false,true,37368,Asian Art,Print,市川鰕蔵の竹村定之進|Kabuki Actor Ichikawa Ebizō (Ichikawa Danjūrō V) in the play The Colored Reins of a Loving Wife (Koi nyōbō somewake tazuna),Japan,Edo period (1615–1868),,,,Artist,,Tōshūsai Sharaku,"Japanese, active 1794–95",,Tōshūsai Sharaku,Japanese,1794,1795,"5th month, 1794",1794,1794,Polychrome woodblock print; ink and color on paper with mica ground,14 15/32 x 9 3/5 in. (36.8 x 24.4 cm),"Harris Brisbane Dick Fund and Rogers Fund, 1949",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37368,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3115,false,true,37369,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Tōshūsai Sharaku,"Japanese, active 1794–95",,Tōshūsai Sharaku,Japanese,1794,1795,1794,1794,1794,"Polychrome woodblock print; ink, color, white mica on paper",H. 14 (35.6 cm); W. 9 in. (22.9 cm),"Harris Brisbane Dick Fund and Rogers Fund, 1949",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37369,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3117,false,true,37371,Asian Art,Print,八代目守田勘弥の鴬の次郎作|Kabuki Actor Morita Kan’ya VIII as the Palanquin-Bearer in the Play A Medley of Tales of Revenge (Katakiuchi noriaibanashi),Japan,Edo period (1615–1868),,,,Artist,,Tōshūsai Sharaku,"Japanese, active 1794–95",,Tōshūsai Sharaku,Japanese,1794,1795,"5th month, 1794",1794,1794,"Polychrome woodblock print; ink, color, white mica on paper",15 x 10 in. (38.1 x 25.4 cm),"Harris Brisbane Dick Fund and Rogers Fund, 1949",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37371,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3118,false,true,37372,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Tōshūsai Sharaku,"Japanese, active 1794–95",,Tōshūsai Sharaku,Japanese,1794,1795,1794,1794,1794,"Polychrome woodblock print; ink, color, white mica on paper",15 1/8 x 10 in. (38.4 x 25.4 cm),"Harris Brisbane Dick Fund and Rogers Fund, 1949",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37372,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3119,false,true,37373,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Tōshūsai Sharaku,"Japanese, active 1794–95",,Tōshūsai Sharaku,Japanese,1794,1795,1794–95,1794,1795,Polychrome woodblock print; ink and color on paper; white mica ground,Image: 14 1/2 x 9 7/8 in. (36.8 x 25.1 cm),"Harris Brisbane Dick Fund and Rogers Fund, 1949",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37373,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3120,false,true,37374,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Tōshūsai Sharaku,"Japanese, active 1794–95",,Tōshūsai Sharaku,Japanese,1794,1795,1794–95,1794,1795,Polychrome woodblock print; ink and color on paper,12 3/4 x 5 31/32 in. (32.4 x 15.2 cm),"Harris Brisbane Dick Fund and Rogers Fund, 1949",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37374,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3121,false,true,37375,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Tōshūsai Sharaku,"Japanese, active 1794–95",,Tōshūsai Sharaku,Japanese,1794,1795,1794–95,1794,1795,Polychrome woodblock print; ink and color on paper,12 31/32 x 5 31/32 in. (33 x 15.2 cm),"Harris Brisbane Dick Fund and Rogers Fund, 1949",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37375,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3122,false,true,37376,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Tōshūsai Sharaku,"Japanese, active 1794–95",,Tōshūsai Sharaku,Japanese,1794,1795,1794,1794,1794,Polychrome woodblock print; white mica ground; ink and color on paper,14 3/4 x 9 3/4 in. (37.5 x 24.8 cm),"Harris Brisbane Dick Fund and Rogers Fund, 1949",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37376,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3123,false,true,37377,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Tōshūsai Sharaku,"Japanese, active 1794–95",,Tōshūsai Sharaku,Japanese,1794,1795,1794–95,1794,1795,Polychrome woodblock print; ink and color on paper,12 11/32 x 5 5/8 in. (31.4 x 14.3 cm),"Harris Brisbane Dick Fund and Rogers Fund, 1949",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37377,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3124,false,true,37378,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Tōshūsai Sharaku,"Japanese, active 1794–95",,Tōshūsai Sharaku,Japanese,1794,1795,1794–95,1794,1795,Polychrome woodblock print; ink and color on paper,Image: 12 3/4 × 5 15/16 in. (32.4 × 15.1 cm) Mat: 22 3/4 × 15 1/2 in. (57.8 × 39.4 cm),"Harris Brisbane Dick Fund and Rogers Fund, 1949",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37378,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3125,false,true,37379,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Tōshūsai Sharaku,"Japanese, active 1794–95",,Tōshūsai Sharaku,Japanese,1794,1795,1794–95,1794,1795,Polychrome woodblock print; ink and color on paper,12 7/32 x 5 3/4 in. (31.1 x 14.6 cm),"Harris Brisbane Dick Fund and Rogers Fund, 1949",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37379,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3126,false,true,37380,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Tōshūsai Sharaku,"Japanese, active 1794–95",,Tōshūsai Sharaku,Japanese,1794,1795,1794–95,1794,1795,Polychrome woodblock print; ink and color on paper,Image: 12 1/2 x 6 in. (31.8 x 15.2 cm),"Harris Brisbane Dick Fund and Rogers Fund, 1949",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37380,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3127,false,true,37381,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Tōshūsai Sharaku,"Japanese, active 1794–95",,Tōshūsai Sharaku,Japanese,1794,1795,1794–95,1794,1795,Polychrome woodblock print; ink and color on paper,14 11/16 x 5 3/4 in. (37.3 x 14.6 cm),"Harris Brisbane Dick Fund and Rogers Fund, 1949",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37381,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3129,false,true,37383,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Tōshūsai Sharaku,"Japanese, active 1794–95",,Tōshūsai Sharaku,Japanese,1794,1795,1794,1794,1794,"Polychrome woodblock print; ink, color, white mica on paper",12 x 8 1/2 in. (30.5 x 21.6 cm),"Harris Brisbane Dick Fund and Rogers Fund, 1949",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37383,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3130,false,true,37384,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Tōshūsai Sharaku,"Japanese, active 1794–95",,Tōshūsai Sharaku,Japanese,1794,1795,1794–95,1794,1795,One sheet of a pentaptych of polychrome woodblock prints; ink and color on paper,12 7/8 x 5 3/4 in. (32.7 x 14.6 cm),"Harris Brisbane Dick Fund and Rogers Fund, 1949",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37384,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3131,false,true,37385,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Tōshūsai Sharaku,"Japanese, active 1794–95",,Tōshūsai Sharaku,Japanese,1794,1795,1794–95,1794,1795,Polychrome woodblock print; ink and color on paper with mica ground,12 11/32 x 5 3/4 in. (31.4 x 14.6 cm),"Harris Brisbane Dick Fund and Rogers Fund, 1949",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37385,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"JP3128a, b",false,true,37382,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Tōshūsai Sharaku,"Japanese, active 1794–95",,Tōshūsai Sharaku,Japanese,1794,1795,1794–95,1794,1795,Diptych of polychrome woodblock prints; ink and color on paper,12 1/2 x 5 31/32 in. (31.8 x 15.2 cm),"Harris Brisbane Dick Fund and Rogers Fund, 1949",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37382,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2011.127,false,true,76557,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Shunkōsai Hokushū,"Japanese, active 1808–32",,Shunkōsai Hokushū,Japanese,1808,1832,1822,1822,1822,Polychrome woodblock print,Image (ôban tate-e): 14 3/4 x 10 1/8 in. (37.5 x 25.7 cm),"Purchase, Friends of Asian Art Gifts, in honor of James C. Y. Watt, 2011",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/76557,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2011.128,false,true,76558,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Shunkōsai Hokushū,"Japanese, active 1808–32",,Shunkōsai Hokushū,Japanese,1808,1832,1821,1821,1821,Polychrome woodblock print,Image (ôban tate-e): 15 x 10 in. (38.1 x 25.4 cm),"Purchase, Friends of Asian Art Gifts, in honor of James C. Y. Watt, 2011",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/76558,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2011.130,false,true,76560,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Shunkōsai Hokushū,"Japanese, active 1808–32",,Shunkōsai Hokushū,Japanese,1808,1832,1825,1825,1825,Polychrome woodblock print,Image (ôban tate-e): 15 x 10 1/8 in. (38.1 x 25.7 cm),"Purchase, Friends of Asian Art Gifts, in honor of James C. Y. Watt, 2011",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/76560,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2011.126a, b",false,true,76556,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Shunkōsai Hokushū,"Japanese, active 1808–32",,Shunkōsai Hokushū,Japanese,1808,1832,1822,1822,1822,Diptych of polychrome woodblock prints,Each sheet (ôban tate-e diptych): 15 x 10 in. (38.1 x 25.4 cm),"Purchase, Friends of Asian Art Gifts, in honor of James C. Y. Watt, 2011",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/76556,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2011.143,false,true,76573,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Sadahiro,"Japanese, active 1825–75",,Utagawa Sadahiro,Japanese,1825,1875,1841,1841,1841,Polychrome woodblock print,Image (chûban tate-e): 10 x 7 1/4 in. (25.4 x 18.4 cm),"Purchase, Friends of Asian Art Gifts, in honor of James C. Y. Watt, 2011",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/76573,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2011.147,false,true,76577,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hirosada,"Japanese, active 1825–75",,Utagawa Hirosada,Japanese,1825,1875,ca. 1852,1842,1862,Polychrome woodblock print,Image (chûban tate-e): 9 1/2 x 7 1/8 in. (24.1 x 18.1 cm),"Purchase, Friends of Asian Art Gifts, in honor of James C. Y. Watt, 2011",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/76577,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2011.146a–e,false,true,76576,Asian Art,Woodblock prints,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hirosada,"Japanese, active 1825–75",,Utagawa Hirosada,Japanese,1825,1875,ca. 1852,1842,1862,Nine polychrome woodblock prints,Each (chûban tate-e): 10 x 7 1/4 in. (25.4 x 18.4 cm),"Purchase, Friends of Asian Art Gifts, in honor of James C. Y. Watt, 2011",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/76576,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2011.144,false,true,76574,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunimasu,"Japanese, active 1830–52",,Utagawa Kunimasu,Japanese,1830,1852,ca. 1849,1839,1859,Polychrome woodblock print,Image (ôban tate-e): 15 1/4 x 10 in. (38.7 x 25.4 cm),"Purchase, Friends of Asian Art Gifts, in honor of James C. Y. Watt, 2011",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/76574,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2011.145,false,true,76575,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunimasu,"Japanese, active 1830–52",,Utagawa Kunimasu,Japanese,1830,1852,1850,1850,1850,Polychrome woodblock print,Image (chûban tate-e): 9 7/8 x 7 in. (25.1 x 17.8 cm),"Purchase, Friends of Asian Art Gifts, in honor of James C. Y. Watt, 2011",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/76575,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP661,false,true,37108,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyotada,"Japanese, fl. ca. 1720–50",,Torii Kiyotada,Japanese,1720,1750,ca. early 1740s,1740,1745,Polychrome woodblock print; ink and color on paper,Oban: 17 1/8 x 25 3/8 in. (43.5 x 64.5 cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37108,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP833,false,true,54487,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyotada,"Japanese, fl. ca. 1720–50",,Torii Kiyotada,Japanese,1720,1750,ca. 1735,1725,1745,Polychrome woodblock print; ink and color on paper (Urushi-e),H. 11 in. (27.9 cm); W. 4 7/8 in. (12.4 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54487,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP834,false,true,54488,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyotada,"Japanese, fl. ca. 1720–50",,Torii Kiyotada,Japanese,1720,1750,ca. 1735,1725,1745,Polychrome woodblock print; ink and color on paper (Urushi-e),H. 12 1/4 in. (31.1 cm); 6 3/8 in. (16.2 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54488,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1567,false,true,45035,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyotada,"Japanese, fl. ca. 1720–50",,Torii Kiyotada,Japanese,1720,1750,ca. 1738,1728,1748,Polychrome woodblock print; ink and color on paper,H. 16 3/4 in. (42.5 cm); W. 25 in. (63.5 cm),"Fletcher Fund, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45035,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3075,false,true,56555,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyotada,"Japanese, fl. ca. 1720–50",,Torii Kiyotada,Japanese,1720,1750,ca. 1715,1705,1725,Woodblock print; ink and hand-painted color (tan-e) on paper,11 1/4 x 6 in. (28.6 x 15.2 cm),"Harris Brisbane Dick Fund and Rogers Fund, 1949",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56555,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP198,false,true,36675,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyomasu I,"Japanese, active 1696–1716",,Torii Kiyomasu I,Japanese,1696,1716,"5th month, 1736",1736,1736,Polychrome woodblock print; ink and color on paper,11 1/4 x 5 13/16 in. (28.6 x 14.8 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36675,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP230,false,true,36702,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyomasu I,"Japanese, active 1696–1716",,Torii Kiyomasu I,Japanese,1696,1716,ca. 1748,1738,1758,Polychrome woodblock print; ink and color on paper,10 1/2 x 5 17/32 in. (26.7 x 14.1 cm),"Rogers Fund, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36702,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP231,false,true,36703,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyomasu I,"Japanese, active 1696–1716",,Torii Kiyomasu I,Japanese,1696,1716,ca. 1748,1738,1758,Polychrome woodblock print; ink and color on paper,12 1/32 x 5 5/8 in. (30.6 x 14.3 cm),"Rogers Fund, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36703,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP232,false,true,36704,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyomasu I,"Japanese, active 1696–1716",,Torii Kiyomasu I,Japanese,1696,1716,dated 1749,1749,1749,Polychrome woodblock print; ink and color on paper,10 17/32 x 5 1/2 in. (26.8 x 14.0 cm),"Rogers Fund, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36704,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP651,false,true,37101,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyomasu I,"Japanese, active 1696–1716",,Torii Kiyomasu I,Japanese,1696,1716,ca. 1705,1695,1715,Polychrome woodblock print; ink and color on paper,12 x 20 3/8 in. (30.5 x 51.8 cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37101,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP652,false,true,37102,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyomasu I,"Japanese, active 1696–1716",,Torii Kiyomasu I,Japanese,1696,1716,ca. 1705,1695,1715,Polychrome woodblock print; ink and color on paper,21 7/8 x 12 3/4 in. (55.6 x 32.4 cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37102,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP653,false,true,37103,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyomasu I,"Japanese, active 1696–1716",,Torii Kiyomasu I,Japanese,1696,1716,ca. 1713,1703,1723,Polychrome woodblock print; ink and color on paper,20 31/32 x 12 5/8 in. (53.3 x 32.1 cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37103,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP654,false,true,37104,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyomasu I,"Japanese, active 1696–1716",,Torii Kiyomasu I,Japanese,1696,1716,ca. 1710,1700,1720,Polychrome woodblock print; ink and color on paper,20 3/4 x 12 5/8 in. (52.7 x 32.1 cm),"The Francis Lathorp Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37104,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP688,false,true,37135,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyomasu I,"Japanese, active 1696–1716",,Torii Kiyomasu I,Japanese,1696,1716,ca. 1730,1720,1740,Polychrome woodblock print; ink and color on paper,12 1/8 x 5 3/4 in. (30.8 x 14.6 cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37135,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP689,false,true,37136,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyomasu I,"Japanese, active 1696–1716",,Torii Kiyomasu I,Japanese,1696,1716,ca. 1750,1740,1760,Polychrome woodblock print; ink and color on paper,12 1/2 x 5 7/8 in. (31.8 x 14.9 cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37136,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP690,false,true,37137,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyomasu I,"Japanese, active 1696–1716",,Torii Kiyomasu I,Japanese,1696,1716,ca. 1755,1745,1765,Polychrome woodblock print; ink and color on paper,11 3/8 x 5 7/32 in. (28.9 x 13.3 cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37137,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP821,false,true,37265,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyomasu I,"Japanese, active 1696–1716",,Torii Kiyomasu I,Japanese,1696,1716,ca. 1730,1720,1740,Polychrome woodblock print; ink and color on paper (Urushi-e),13 1/10 x 6 1/8 in. (33.3 x 15.6 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37265,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP822,false,true,37266,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyomasu I,"Japanese, active 1696–1716",,Torii Kiyomasu I,Japanese,1696,1716,ca. 1735,1725,1745,Polychrome woodblock print; ink and color on paper (Urushi-e),Overall: 12 1/4 x 5 7/8in. (31.1 x 14.9cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37266,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP823,false,true,37267,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyomasu I,"Japanese, active 1696–1716",,Torii Kiyomasu I,Japanese,1696,1716,ca. 1745,1735,1755,Polychrome woodblock print; ink and color on paper,12 11/32 x 5 7/8 in. (31.4 x 14.9 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37267,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP824,false,true,54478,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyomasu I,"Japanese, active 1696–1716",,Torii Kiyomasu I,Japanese,1696,1716,ca. 1745,1735,1755,Polychrome woodblock print; ink and color on paper,H. 12 3/8 in. (31.4 cm); W. 5 3/4 in. (14.6 cm),"Gift of Estate of Samuel Ishma, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54478,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP825,false,true,54479,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyomasu I,"Japanese, active 1696–1716",,Torii Kiyomasu I,Japanese,1696,1716,ca. 1748,1738,1758,Polychrome woodblock print; ink and color on paper,H. 12 3/8 in. (31.4 cm); W. 5 7/8 in. (14.9 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54479,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP826,false,true,54480,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyomasu I,"Japanese, active 1696–1716",,Torii Kiyomasu I,Japanese,1696,1716,ca. 1748,1738,1758,Polychrome woodblock print; ink and color on paper,H. 12 3/8 in. (31.4 cm); W. 5 3/4 in. (14.6 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54480,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP827,false,true,54481,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyomasu I,"Japanese, active 1696–1716",,Torii Kiyomasu I,Japanese,1696,1716,ca. 1752,1742,1762,Polychrome woodblock print; ink and color on paper,H. 12 1/2 in. (31.8 cm); W. 5 7/8 in. (14.9 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54481,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1110,false,true,55035,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyomasu I,"Japanese, active 1696–1716",,Torii Kiyomasu I,Japanese,1696,1716,ca. 1745,1735,1755,Polychrome woodblock print; ink and color on paper,H. 12 1/2 in. (31.8 cm); W. 5 1/2 in. (14 cm),"Rogers Fund, 1919",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55035,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2583,false,true,56728,Asian Art,Lacquer print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyomasu I,"Japanese, active 1696–1716",,Torii Kiyomasu I,Japanese,1696,1716,"12th month, 1743 or 1st month, 1744",1743,1744,Urushi-e (lacquer) print,12 1/2 x 5 3/4 in. (31.8 x 14.6 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56728,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2584,false,true,56729,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyomasu I,"Japanese, active 1696–1716",,Torii Kiyomasu I,Japanese,1696,1716,ca. 1734,1724,1744,Urushi-e (lacquer) print,11 1/2 x 5 3/4 in. (29.2 x 14.6 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56729,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2585,false,true,56730,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyomasu I,"Japanese, active 1696–1716",,Torii Kiyomasu I,Japanese,1696,1716,ca. 1741,1731,1751,Polychrome woodblock print; ink and color on paper,12 3/4 x 5 3/4 in. (32.4 x 14.6 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56730,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3072,false,true,45247,Asian Art,Print,"Kairaishi|The Actor Ichimura Takenojo VIII in the Role of a Puppeteer, showing Puppets to a Courtesan",Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyomasu I,"Japanese, active 1696–1716",,Torii Kiyomasu I,Japanese,1696,1716,ca. 1715,1705,1725,Monochrome woodblock print; ink on paper,11 x 16 in. (27.9 x 40.6 cm),"Harris Brisbane Dick Fund and Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45247,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3073,false,true,45052,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyomasu I,"Japanese, active 1696–1716",,Torii Kiyomasu I,Japanese,1696,1716,ca. 1712,1702,1722,"Polychrome ""tan-e"" woodblock print; ink and color on paper",12 1/4 x 6 in. (31.1 x 15.2 cm),"Harris Brisbane Dick Fund and Rogers Fund, 1949",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45052,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3099,false,true,45053,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyomasu I,"Japanese, active 1696–1716",,Torii Kiyomasu I,Japanese,1696,1716,ca. 1712,1702,1722,Monochrome woodblock (tan-e) print; ink on paper,22 1/2 x 12 1/4 in. (57.2 x 31.1 cm),"Harris Brisbane Dick Fund and Rogers Fund, 1949",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45053,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3100,false,true,45054,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyomasu I,"Japanese, active 1696–1716",,Torii Kiyomasu I,Japanese,1696,1716,ca. 1716,1706,1726,Polychrome woodblock print (tan-e); ink and color on paper,23 1/4 x 12 1/4 in. (59.1 x 31.1 cm),"Harris Brisbane Dick Fund and Rogers Fund, 1949",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45054,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3105,false,true,45059,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kaigetsudō Doshin,"Japanese, active 1711–1736",,Kaigetsudō Doshin,Japanese,1711,1736,ca. 1714,1704,1724,Polychrome woodblock print (sumizuri-e); ink and color on paper,23 1/2 x 12 1/2 in. (59.7 x 31.8 cm),"Harris Brisbane Dick Fund and Rogers Fund, 1949",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45059,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1311,false,true,55270,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kondo Katsunobu,"Japanese, active 1716–1736",,Kondo Katsunobu,Japanese,1716,1736,ca. 1730,1720,1740,Polychrome woodblock print (urushi-e); ink and color on paper,H. 11 3/4 in. (29.8 cm); W. 5 7/8 in. (14.9 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55270,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3082,false,true,56595,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kiyonobu II,"Japanese, active 1720–1750",,Kiyonobu II,Japanese,1720,1750,1739,1739,1739,Polychrome woodblock print (hand-colored); ink and color on paper,12 x 6 in. (30.5 x 15.2 cm),"Harris Brisbane Dick Fund and Rogers Fund, 1949",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56595,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP240,false,true,36712,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Ippitsusai Bunchō,"Japanese, active 1760–1794",,Ippitsusai Bunchō,Japanese,1760,1794,ca. 1770,1760,1780,Polychrome woodblock print; ink and color on paper,12 1/32 x 5 5/8 in. (30.6 x 14.3 cm),"Rogers Fund, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36712,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP241,false,true,36713,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Ippitsusai Bunchō,"Japanese, active 1760–1794",,Ippitsusai Bunchō,Japanese,1760,1794,ca. 1770,1760,1780,Polychrome woodblock print; ink and color on paper,12 7/32 x 5 17/32 in. (31.1 x 14.1 cm),"Rogers Fund, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36713,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP242,false,true,36714,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Ippitsusai Bunchō,"Japanese, active 1760–1794",,Ippitsusai Bunchō,Japanese,1760,1794,probably 1770,1768,1772,Polychrome woodblock print; ink and color on paper,12 5/8 x 5 7/8 in. (32.1 x 14.9 cm),"Rogers Fund, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36714,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP470,false,true,36921,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Ippitsusai Bunchō,"Japanese, active 1760–1794",,Ippitsusai Bunchō,Japanese,1760,1794,ca. 1769,1759,1779,Polychrome woodblock print; ink and color on paper,11 3/5 x 5 1/2 in. (29.5 x 14 cm),"Gift of Frank Lloyd Wright, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36921,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP561,false,true,37012,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Ippitsusai Bunchō,"Japanese, active 1760–1794",,Ippitsusai Bunchō,Japanese,1760,1794,1770,1770,1770,Polychrome woodblock print; ink and color on paper,12 7/32 x 5 5/8 in. (31.1 x 14.3 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37012,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP562,false,true,37013,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Ippitsusai Bunchō,"Japanese, active 1760–1794",,Ippitsusai Bunchō,Japanese,1760,1794,"12th month, 1768",1768,1768,Polychrome woodblock print; ink and color on paper,12 5/8 x 5 7/8 in. (32.1 x 14.9 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37013,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP563,false,true,37014,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Ippitsusai Bunchō,"Japanese, active 1760–1794",,Ippitsusai Bunchō,Japanese,1760,1794,1770 or 1771,1770,1771,Polychrome woodblock print; ink and color on paper,12 5/16 x 5 13/16 in. (31.3 x 14.8 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37014,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP701,false,true,37148,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Ippitsusai Bunchō,"Japanese, active 1760–1794",,Ippitsusai Bunchō,Japanese,1760,1794,ca. 1770,1760,1780,Polychrome woodblock print; ink and color on paper,12 7/32 x 5 3/4 in. (31.1 x 14.6 cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37148,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP702,false,true,37149,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Ippitsusai Bunchō,"Japanese, active 1760–1794",,Ippitsusai Bunchō,Japanese,1760,1794,ca. 1771,1761,1781,Polychrome woodblock print; ink and color on paper,11 23/32 x 5 1/2 in. (29.8 x 14.0 cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37149,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP907,false,true,51997,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Ippitsusai Bunchō,"Japanese, active 1760–1794",,Ippitsusai Bunchō,Japanese,1760,1794,ca. 1770,1760,1780,Polychrome woodblock print; ink and color on paper,H. 12 1/8 in. (30.8 cm); W. 5 1/2 in. (14 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/51997,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1116,false,true,55038,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Ippitsusai Bunchō,"Japanese, active 1760–1794",,Ippitsusai Bunchō,Japanese,1760,1794,ca. 1770,1760,1780,Polychrome woodblock print; ink and color on paper,H. 10 1/2 in. (26.7 cm); W. 5 1/2 in. (14 cm),"Rogers Fund, 1919",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55038,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1226,false,true,55148,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Ippitsusai Bunchō,"Japanese, active 1760–1794",,Ippitsusai Bunchō,Japanese,1760,1794,ca. 1770,1760,1780,Polychrome woodblock print; ink and color on paper,H. 10 1/16 in. (25.6 cm); W. 7 3/16 in. (18.3 cm),"Rogers Fund, 1921",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55148,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1227,false,true,55149,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Ippitsusai Bunchō,"Japanese, active 1760–1794",,Ippitsusai Bunchō,Japanese,1760,1794,ca. 1770,1760,1780,Polychrome woodblock print; ink and color on paper,H. 11 1/4 in. (28.6 cm); W. 5 5/16 in. (13.5 cm),"Rogers Fund, 1921",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55149,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1279,false,true,55210,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Ippitsusai Bunchō,"Japanese, active 1760–1794",,Ippitsusai Bunchō,Japanese,1760,1794,1769,1769,1769,Polychrome woodblock print; ink and color on paper,H. 12 1/4 in. (31.1 cm); W. 5 5/8 in. (14.3 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55210,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1361,false,true,55339,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Ippitsusai Bunchō,"Japanese, active 1760–1794",,Ippitsusai Bunchō,Japanese,1760,1794,ca. 1770,1760,1780,Polychrome woodblock print; ink and color on paper,H. 12 3/8 in. (31.4 cm); W. 5 7/8 in. (14.9 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55339,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1362,false,true,55341,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Ippitsusai Bunchō,"Japanese, active 1760–1794",,Ippitsusai Bunchō,Japanese,1760,1794,ca. 1770,1760,1780,Polychrome woodblock print; ink and color on paper,H. 11 7/16 in. (29.1 cm); W. 5 3/4 in. (14.6 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55341,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1363,false,true,55342,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Ippitsusai Bunchō,"Japanese, active 1760–1794",,Ippitsusai Bunchō,Japanese,1760,1794,1764–71,1764,1771,Polychrome woodblock print; ink and color on paper,H. 12 3/4 in. (32.4 cm); W. 5 7/8 in. (14.9 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55342,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1365,false,true,55346,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Ippitsusai Bunchō,"Japanese, active 1760–1794",,Ippitsusai Bunchō,Japanese,1760,1794,ca. 1769,1759,1779,Polychrome woodblock print; ink and color on paper,H. 12 1/2 in. (31.8 cm); W. 5 3/4 in. (14.6 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55346,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1460,false,true,55507,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Ippitsusai Bunchō,"Japanese, active 1760–1794",,Ippitsusai Bunchō,Japanese,1760,1794,ca. 1772,1762,1782,Polychrome woodblock print; ink and color on paper,H. 11 3/4 in. (29.8 cm); W. 5 1/2 in. (14 cm),"Rogers Fund, 1925",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55507,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1544,false,true,55709,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Ippitsusai Bunchō,"Japanese, active 1760–1794",,Ippitsusai Bunchō,Japanese,1760,1794,ca. 1769,1759,1779,Polychrome woodblock print; ink and color on paper,H. 12 1/2 in. (31.8 cm); W. 5 5/8 in. (14.3 cm),"Fletcher Fund, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55709,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1545,false,true,55710,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Ippitsusai Bunchō,"Japanese, active 1760–1794",,Ippitsusai Bunchō,Japanese,1760,1794,ca. 1770,1760,1780,Polychrome woodblock print; ink and color on paper,H. 12 1/4 in. (31.1 cm); W. 5 5/8 in. (14.3 cm),"Fletcher Fund, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55710,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2407,false,true,56810,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Ippitsusai Bunchō,"Japanese, active 1760–1794",,Ippitsusai Bunchō,Japanese,1760,1794,ca. 1770,1760,1780,Polychrome woodblock print; ink and color on paper,12 1/4 x 5 3/4 in. (31.1 x 14.6 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56810,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2408,false,true,56811,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Ippitsusai Bunchō,"Japanese, active 1760–1794",,Ippitsusai Bunchō,Japanese,1760,1794,ca. 1769,1759,1779,Polychrome woodblock print; ink and color on paper,Hosoe; 12 x 5 3/4 in. (30.5 x 14.6 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56811,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2409,false,true,56812,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Ippitsusai Bunchō,"Japanese, active 1760–1794",,Ippitsusai Bunchō,Japanese,1760,1794,ca. 1769,1759,1779,Polychrome woodblock print; ink and color on paper,Hosoe: 12 1/8 x 5 3/4 in. (30.8 x 14.6 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56812,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2410,false,true,56813,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Ippitsusai Bunchō,"Japanese, active 1760–1794",,Ippitsusai Bunchō,Japanese,1760,1794,ca. 1772,1762,1782,Polychrome woodblock print; ink and color on paper,Hosoe: 12 3/8 x 5 3/4 in. (31.4 x 14.6 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56813,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2411,false,true,56814,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Ippitsusai Bunchō,"Japanese, active 1760–1794",,Ippitsusai Bunchō,Japanese,1760,1794,ca. 1771,1761,1781,Polychrome woodblock print; ink and color on paper,Hosoe: 12 3/4 x 5 7/8 in. (32.4 x 14.9 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56814,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2412,false,true,56815,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Ippitsusai Bunchō,"Japanese, active 1760–1794",,Ippitsusai Bunchō,Japanese,1760,1794,ca. 1769,1759,1779,Polychrome woodblock print; ink and color on paper,Hosoe: 12 3/4 x 6 in. (32.4 x 15.2 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56815,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2413,false,true,56816,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Ippitsusai Bunchō,"Japanese, active 1760–1794",,Ippitsusai Bunchō,Japanese,1760,1794,ca. 1769,1759,1779,Polychrome woodblock print; ink and color on paper,12 1/2 x 5 5/8 in. (31.8 x 14.3 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56816,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2414,false,true,56817,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Ippitsusai Bunchō,"Japanese, active 1760–1794",,Ippitsusai Bunchō,Japanese,1760,1794,ca. 1790,1780,1800,Polychrome woodblock print; ink and color on paper,Hosoe: 11 3/4 x 5 5/8 in. (29.8 x 14.3 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56817,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2415,false,true,56818,Asian Art,Print,"初世尾上菊五郎の戸無瀬|Onoe Kikugorō as Tonase, from Kanadehon Chūshingura (Kanadehon Chūshingura, Shosei Onoe Kikugorō no Tonase)",Japan,Edo period (1615–1868),,,,Artist,,Ippitsusai Bunchō,"Japanese, active 1760–1794",,Ippitsusai Bunchō,Japanese,1760,1794,1773,1773,1773,Polychrome woodblock print; ink and color on paper,Hosoe: 11 3/4 x 5 3/4 in. (29.8 x 14.6 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56818,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2416,false,true,56819,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Ippitsusai Bunchō,"Japanese, active 1760–1794",,Ippitsusai Bunchō,Japanese,1760,1794,ca. 1772,1762,1802,Polychrome woodblock print; ink and color on paper,Hosoe: 12 7/16 x 5 7/8 in. (31.6 x 14.9 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56819,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2789,false,true,57100,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Ippitsusai Bunchō,"Japanese, active 1760–1794",,Ippitsusai Bunchō,Japanese,1760,1794,1770,1770,1770,Polychrome woodblock print; ink and color on paper,12 5/8 x 5 3/4 in. (32.1 x 14.6 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/57100,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2790,false,true,57099,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Ippitsusai Bunchō,"Japanese, active 1760–1794",,Ippitsusai Bunchō,Japanese,1760,1794,1723–1792,1723,1792,Polychrome woodblock print; ink and color on paper,12 1/2 x 5 7/8 in. (31.8 x 14.9 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/57099,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2791,false,true,57098,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Ippitsusai Bunchō,"Japanese, active 1760–1794",,Ippitsusai Bunchō,Japanese,1760,1794,1723–1792,1723,1792,Polychrome woodblock print; ink and color on paper,10 3/8 x 7 1/2 in. (26.4 x 19.1 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/57098,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2792,false,true,57097,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Ippitsusai Bunchō,"Japanese, active 1760–1794",,Ippitsusai Bunchō,Japanese,1760,1794,1723–1792,1723,1792,Polychrome woodblock print; ink and color on paper,12 3/4 x 6 1/8 in. (32.4 x 15.6 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/57097,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2793,false,true,57096,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Ippitsusai Bunchō,"Japanese, active 1760–1794",,Ippitsusai Bunchō,Japanese,1760,1794,1723–1792,1723,1792,Polychrome woodblock print; ink and color on paper,12 3/4 x 5 7/8 in. (32.4 x 14.9 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/57096,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP849,false,true,54506,Asian Art,Print,Shō gatsu|The First Month,Japan,Edo period (1615–1868),,,,Artist,,Ishikawa Toyomasa,"Japanese, active 1770–1790",,Ishikawa Toyomasa,Japanese,1770,1790,ca. 1767,1757,1777,Polychrome woodblock print; ink and color on paper,H. 10 in. (25.4 cm); W. 7 3/8 in. (18.7 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54506,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP850,false,true,54507,Asian Art,Print,Inari-ko|The Second Month,Japan,Edo period (1615–1868),,,,Artist,,Ishikawa Toyomasa,"Japanese, active 1770–1790",,Ishikawa Toyomasa,Japanese,1770,1790,ca. 1767,1757,1777,Polychrome woodblock print; ink and color on paper,H. 10 in. (25.4 cm); W. 7 3/8 in. (18.7 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54507,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP851,false,true,54511,Asian Art,Print,Hina no Sekku|The Festival of Dolls (Third Month),Japan,Edo period (1615–1868),,,,Artist,,Ishikawa Toyomasa,"Japanese, active 1770–1790",,Ishikawa Toyomasa,Japanese,1770,1790,ca. 1767,1757,1777,Polychrome woodblock print; ink and color on paper,H. 10 in. (25.4 cm); W. 7 3/8 in. (18.7 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54511,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP852,false,true,54522,Asian Art,Print,Shi Gatsu|The Fourth Month,Japan,Edo period (1615–1868),,,,Artist,,Ishikawa Toyomasa,"Japanese, active 1770–1790",,Ishikawa Toyomasa,Japanese,1770,1790,ca. 1767,1757,1777,Polychrome woodblock print; ink and color on paper,H. 10 in. (25.4 cm); W. 7 3/8 in. (18.7 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54522,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP853,false,true,54524,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Ishikawa Toyomasa,"Japanese, active 1770–1790",,Ishikawa Toyomasa,Japanese,1770,1790,ca. 1767,1757,1777,Polychrome woodblock print; ink and color on paper,H. 10 in. (25.4 cm); W. 7 3/8 in. (18.7 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54524,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP854,false,true,54526,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Ishikawa Toyomasa,"Japanese, active 1770–1790",,Ishikawa Toyomasa,Japanese,1770,1790,ca. 1767,1757,1777,Polychrome woodblock print; ink and color on paper,H. 10 in. (25.4 cm); W. 7 3/8 in. (18.7 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54526,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP855,false,true,54528,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Ishikawa Toyomasa,"Japanese, active 1770–1790",,Ishikawa Toyomasa,Japanese,1770,1790,ca. 1767,1757,1777,Polychrome woodblock print; ink and color on paper,H. 10 in. (25.4 cm); W. 7 3/8 in. (18.7 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54528,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP856,false,true,54531,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Ishikawa Toyomasa,"Japanese, active 1770–1790",,Ishikawa Toyomasa,Japanese,1770,1790,ca. 1767,1757,1777,Polychrome woodblock print; ink and color on paper,H. 10 in. (25.4 cm); W. 7 3/8 in. (18.7 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54531,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP857,false,true,54534,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Ishikawa Toyomasa,"Japanese, active 1770–1790",,Ishikawa Toyomasa,Japanese,1770,1790,ca. 1767,1757,1777,Polychrome woodblock print; ink and color on paper,H. 10 in. (25.4 cm); W. 7 3/8 in. (18.7 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54534,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP858,false,true,54536,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Ishikawa Toyomasa,"Japanese, active 1770–1790",,Ishikawa Toyomasa,Japanese,1770,1790,ca. 1767,1757,1777,Polychrome woodblock print; ink and color on paper,H. 10 in. (25.4 cm); W. 7 3/8 in. (18.7 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54536,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP859,false,true,54538,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Ishikawa Toyomasa,"Japanese, active 1770–1790",,Ishikawa Toyomasa,Japanese,1770,1790,ca. 1767,1757,1777,Polychrome woodblock print; ink and color on paper,H. 10 in. (25.4 cm); W. 7 3/8 in. (18.7 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54538,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP860,false,true,54539,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Ishikawa Toyomasa,"Japanese, active 1770–1790",,Ishikawa Toyomasa,Japanese,1770,1790,ca. 1767,1757,1777,Polychrome woodblock print; ink and color on paper,H. 10 in. (25.4 cm); W. 7 3/8 in. (18.7 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54539,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2716,false,true,56984,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunzan,"Japanese, active 1782–1798",,Katsukawa Shunzan,Japanese,1782,1798,ca. 1789,1779,1799,Polychrome woodblock print; ink and color on paper,15 x 9 3/4 in. (38.1 x 24.8 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56984,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1292,false,true,55234,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Shotei Hokuju,"Japanese, active 1790–1820",,Shotei Hokuju,Japanese,1790,1820,ca. 1825,1815,1835,Polychrome woodblock print; ink and color on paper,Oban 9 5/8 x 14 1/4 in. (24.4 x 36.2 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55234,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1394,false,true,55400,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Shotei Hokuju,"Japanese, active 1790–1820",,Shotei Hokuju,Japanese,1790,1820,ca. 1830,1820,1830,Polychrome woodblock print; ink and color on paper,H. 10 5/16 in. (26.2 cm); W. 15 1/4 in. (38.7 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55400,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1409,false,true,55424,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Shotei Hokuju,"Japanese, active 1790–1820",,Shotei Hokuju,Japanese,1790,1820,ca. 1830,1820,1840,Polychrome woodblock print; ink and color on paper,H. 10 1/4 in. (26 cm); W. 14 1/2 in. (36.8 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55424,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2582,false,true,54196,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Ishikawa Kazan,"Japanese, active 1810–1823",,Ishikawa Kazan,Japanese,1810,1823,ca. 1820,1810,1830,Polychrome woodblock print (surimono); ink and color on paper,7 1/4 x 6 5/8 in. (18.4 x 16.8 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54196,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2011.142,false,true,76572,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Hasegawa Sadamasu,"Japanese, active 1830s–40s",,Hasegawa Sadamasu,Japanese,1830,1849,1841,1841,1841,Polychrome woodblock print,Image (chûban tate-e): 10 x 7 1/4 in. (25.4 x 18.4 cm),"Purchase, Friends of Asian Art Gifts, in honor of James C. Y. Watt, 2011",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/76572,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2011.136a–d,false,true,76566,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Hasegawa Sadamasu,"Japanese, active 1830s–40s",,Hasegawa Sadamasu,Japanese,1830,1849,1834,1834,1834,Tetraptych of polychrome woodblock prints,Each sheet (ôban tate-e tetraptych): 14 1/8 x 10 in. (35.9 x 25.4 cm),"Purchase, Friends of Asian Art Gifts, in honor of James C. Y. Watt, 2011",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/76566,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2001.715.11a–c,false,true,63378,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Yoshifusa,"Japanese, active 1837–1860",,Utagawa Yoshifusa,Japanese,1837,1860,"1856, 2nd month",1856,1856,Triptych of polychrome woodblock prints; ink and color on paper,Oban tate-e; triptych (right): 14 1/2 x 9 3/4 in. (36.8 x 24.8 cm) (middle): 10 3/8 x 9 3/4 in. (26.4 x 24.8 cm) (left): 14 1/2 x 9 3/4 in. (36.8 x 24.8 cm),"Purchase, Arnold Weinstein Gift, 2001",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/63378,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP223,false,true,36697,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Toyoshige,"Japanese, active 1802?–?1835",,Utagawa Toyoshige,Japanese,1802,1835,ca. 1830,1820,1850,Polychrome woodblock print; ink and color on paper,9 4/5 x 14 15/32 in. (24.9 x 36.8 cm),"Rogers Fund, 1917",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36697,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1370,false,true,55349,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Toyoshige,"Japanese, active 1802?–?1835",,Utagawa Toyoshige,Japanese,1802,1835,ca. 1828,1818,1838,Polychrome woodblock print; ink and color on paper,H. 10 1/4 in. (26 cm); W. 13 7/8 in. (35.2 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55349,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2001.715.5,false,true,63359,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Toyoshige,"Japanese, active 1802?–?1835",,Utagawa Toyoshige,Japanese,1802,1835,ca. 1825,1815,1835,Polychrome woodblock print; ink and color on paper,"Oban tate-e, 15 1/2 x 10 3/4 in. (39.4 x 27.3 cm)","Purchase, Jack Greene Gift, 2001",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/63359,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2048,false,true,54820,Asian Art,Print,騎龍弁財天|Benzaiten (Goddess of Music and Good Fortune) Seated on a White Dragon,Japan,Edo period (1615–1868),,,,Artist,,Aoigaoka Keisei,"Japanese, active 1820s–1830s",,Aoigaoka Keisei,Japanese,1810,1840,1832,1832,1832,Polychrome woodblock print (surimono); ink and color on paper,8 1/2 x 7 1/4 in. (21.6 x 18.4 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54820,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP176,false,true,36654,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunchō,"Japanese, active ca. 1783–95",,Katsukawa Shunchō,Japanese,1775,1795,ca. 1789,1779,1799,Middle sheet of a triptych of polychrome woodblock prints; ink and color on paper,14 9/16 x 10 in. (37 x 25.4 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36654,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP940,false,true,54819,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunchō,"Japanese, active ca. 1783–95",,Katsukawa Shunchō,Japanese,1775,1795,ca. 1778,1768,1788,Polychrome woodblock print; ink and color on paper,13 × 9 in. (33 × 22.9 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54819,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP941,false,true,54821,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunchō,"Japanese, active ca. 1783–95",,Katsukawa Shunchō,Japanese,1775,1795,ca. 1778,1768,1788,Polychrome woodblock print; ink and color on paper,13 × 9 in. (33 × 22.9 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54821,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP942,false,true,54825,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunchō,"Japanese, active ca. 1783–95",,Katsukawa Shunchō,Japanese,1775,1795,ca. 1788,1778,1798,Polychrome woodblock print; ink and color on paper,9 11/16 × 7 3/4 in. (24.6 × 19.7 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54825,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP943,false,true,54827,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunchō,"Japanese, active ca. 1783–95",,Katsukawa Shunchō,Japanese,1775,1795,ca. 1788,1778,1798,Polychrome woodblock print; ink and color on paper,14 11/16 × 9 7/8 in. (37.3 × 25.1 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54827,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1042,false,true,54952,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunchō,"Japanese, active ca. 1783–95",,Katsukawa Shunchō,Japanese,1775,1795,ca. 1787,1777,1797,Triptych of polychrome woodblock prints; ink and color on paper,12 3/4 × 25 1/2 in. (32.4 × 64.8 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54952,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1515,false,true,55618,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunchō,"Japanese, active ca. 1783–95",,Katsukawa Shunchō,Japanese,1775,1795,ca. 1788,1778,1798,Polychrome woodblock print; ink and color on paper,15 3/8 × 10 3/16 in. (39.1 × 25.9 cm),"Fletcher Fund, 1925",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55618,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1516,false,true,55622,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunchō,"Japanese, active ca. 1783–95",,Katsukawa Shunchō,Japanese,1775,1795,ca. 1790,1780,1800,Polychrome woodblock print; ink and color on paper,15 1/8 × 10 1/16 in. (38.4 × 25.6 cm),"Fletcher Fund, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55622,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1517,false,true,55625,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunchō,"Japanese, active ca. 1783–95",,Katsukawa Shunchō,Japanese,1775,1795,ca. 1790,1790,1800,Polychrome woodblock print; ink and color on paper,15 1/8 × 10 1/8 in. (38.4 × 25.7 cm),"Fletcher Fund, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55625,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1518,false,true,55626,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunchō,"Japanese, active ca. 1783–95",,Katsukawa Shunchō,Japanese,1775,1795,ca. 1785,1775,1795,Polychrome woodblock print; ink and color on paper,14 1/2 × 9 3/4 in. (36.8 × 24.8 cm),"Fletcher Fund, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55626,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1834,false,true,56119,Asian Art,Print,あやめ燈籠図|Three Women Enjoying Literary Pursuits,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunchō,"Japanese, active ca. 1783–95",,Katsukawa Shunchō,Japanese,1775,1795,ca. late 1780s,1785,1789,One sheet of a triptych of polychrome woodblock prints; ink and color on paper,14 3/4 × 9 5/8 in. (37.5 × 24.4 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56119,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2660,false,true,56839,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunchō,"Japanese, active ca. 1783–95",,Katsukawa Shunchō,Japanese,1775,1795,ca. 1789,1779,1799,Polychrome woodblock print; ink and color on paper,14 3/8 × 10 in. (36.5 × 25.4 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56839,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2661,false,true,56840,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunchō,"Japanese, active ca. 1783–95",,Katsukawa Shunchō,Japanese,1775,1795,ca. 1789,1779,1799,One sheet of a triptych of polychrome woodblock prints; ink and color on paper,15 x 10 in. (38.1 x 25.4 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56840,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2662,false,true,56841,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunchō,"Japanese, active ca. 1783–95",,Katsukawa Shunchō,Japanese,1775,1795,1787,1787,1787,Polychrome woodblock print; ink and color on paper,15 x 10 in. (38.1 x 25.4 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56841,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2665,false,true,56843,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunchō,"Japanese, active ca. 1783–95",,Katsukawa Shunchō,Japanese,1775,1795,ca. 1786,1776,1796,Polychrome woodblock print; ink and color on paper,15 x 30 in. (38.1 x 76.2 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56843,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2804,false,true,57086,Asian Art,Print,Ukiyoe Ga|The Beauty of the Floating World,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunchō,"Japanese, active ca. 1783–95",,Katsukawa Shunchō,Japanese,1775,1795,1780–1795,1780,1795,Polychrome woodblock print; ink and color on paper,15 x 10 in. (38.1 x 25.4 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/57086,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2805,false,true,57085,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunchō,"Japanese, active ca. 1783–95",,Katsukawa Shunchō,Japanese,1775,1795,1780–1795,1780,1795,Polychrome woodblock print; ink and color on paper,14 3/4 x 9 7/8 in. (37.5 x 25.1 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/57085,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2806,false,true,57084,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunchō,"Japanese, active ca. 1783–95",,Katsukawa Shunchō,Japanese,1775,1795,1780–1795,1780,1795,Polychrome woodblock print; ink and color on paper,10 5/16 x 7 9/16 in. (26.2 x 19.2 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/57084,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2807,false,true,57083,Asian Art,Print,Junigatsu|The Twelfth Month: December,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunchō,"Japanese, active ca. 1783–95",,Katsukawa Shunchō,Japanese,1775,1795,1780–1795,1780,1795,Polychrome woodblock print; ink and color on paper,9 3/4 x 6 7/8 in. (24.8 x 17.5 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/57083,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2808,false,true,57082,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunchō,"Japanese, active ca. 1783–95",,Katsukawa Shunchō,Japanese,1775,1795,1780–1795,1780,1795,Polychrome woodblock print; ink and color on paper,15 1/2 x 10 3/8 in. (39.4 x 26.4 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/57082,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"JP2664a, b",false,true,56842,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunchō,"Japanese, active ca. 1783–95",,Katsukawa Shunchō,Japanese,1775,1795,ca. 1789,1779,1799,Diptych of polychrome woodblock prints; ink and color on paper,Image (each): 14 7/8 × 10 in. (37.8 × 25.4 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56842,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"JP2803a, b",false,true,57087,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunchō,"Japanese, active ca. 1783–95",,Katsukawa Shunchō,Japanese,1775,1795,ca. 1780–95,1780,1795,Diptych of polychrome woodblock prints; ink and color on paper,Image (each): 15 3/8 × 10 3/8 in. (39.1 × 26.4 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/57087,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.261a–c,false,true,73552,Asian Art,Woodblock print,Daizō shasei|Big Elephants Being Attacked,Japan,Edo period (1615–1868),,,,Artist,,Isshinsai Yoshikata,"Japanese, active ca. 1841–64",,Isshinsai Yoshikata,Japanese,1841,1864,"2nd month, 1863",1863,1863,Triptych of polychrome woodblock prints; ink and color on paper,Image (a): 14 1/2 x 10 in. (36.8 x 25.4 cm) Image (b): 14 1/2 x 9 5/8 in. (36.8 x 24.4 cm) Image (c): 14 5/8 x 9 7/8 in. (37.1 x 25.1 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73552,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3183,false,true,55169,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Yoshitora,"Japanese, active ca. 1850–80",,Utagawa Yoshitora,Japanese,1845,1880,ca. 1861,1851,1871,Polychrome woodblock print; ink and color on paper,Oban 14 x 9 1/2 in. (35.6 x 24.1 cm),"GIft of Lincoln Kirstein, 1959",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55169,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3185,false,true,55170,Asian Art,Print,Amerika jin Yuko Sakamori|An American Carousing,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Yoshitora,"Japanese, active ca. 1850–80",,Utagawa Yoshitora,Japanese,1845,1880,"1861 (Bunkyu 1, first month)",1861,1861,Polychrome woodblock print; ink and color on paper,14 1/4 x 9 3/4 in. (36.2 x 24.8 cm),"GIft of Lincoln Kirstein, 1959",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55170,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3186,false,true,55171,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Yoshitora,"Japanese, active ca. 1850–80",,Utagawa Yoshitora,Japanese,1845,1880,1860,1860,1860,Polychrome woodblock print; ink and color on paper,Image: 14 x 9 1/2 in. (35.6 x 24.1 cm),"GIft of Lincoln Kirstein, 1959",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55171,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3243,false,true,55256,Asian Art,Print,"東都芝浦之風景|View of Shibaura, from the series Eastern Capital (Tōto, Shibaura no fūkei)",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Yoshitora,"Japanese, active ca. 1850–80",,Utagawa Yoshitora,Japanese,1845,1880,"6th month, 1863",1863,1863,Sheet from a triptych of polychrome woodblock prints; ink and color on paper,13 x 28 1/8 in. (33 x 71.4 cm),"Gift of Lincoln Kirstein, 1959",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55256,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3248,false,true,55260,Asian Art,Print,Goko Kokujinmotsu-Don Taku no zu|View of Eating and Drinking by People of Five Countries,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Yoshitora,"Japanese, active ca. 1850–80",,Utagawa Yoshitora,Japanese,1845,1880,"1861, 12th month (?)",1861,1861,Triptych of polychrome woodblock prints; ink and color on paper,14 x 28 1/2 in. (35.6 x 72.4 cm),"Gift of Lincoln Kirstein, 1959",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55260,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3311,false,true,55422,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Yoshitora,"Japanese, active ca. 1850–80",,Utagawa Yoshitora,Japanese,1845,1880,January 1861,1861,1861,Polychrome woodblock print; ink and color on paper,14 x 9 1/2 in. (35.6 x 24.1 cm),"Gift of Lincoln Kirstein, 1959",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55422,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3330,false,true,55478,Asian Art,Print,五箇国人物呑託之図|People of the Five Nations,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Yoshitora,"Japanese, active ca. 1850–80",,Utagawa Yoshitora,Japanese,1845,1880,1861,1861,1861,Triptych of polychrome woodblock prints; ink and color on paper,Image: 14 1/4 x 29 1/2 in. (36.2 x 74.9 cm),"Gift of Lincoln Kirstein, 1959",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55478,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3339,false,true,55493,Asian Art,Print,英吉利国倫敦図|Illustration of London in England (Igirisukoku rondon zu),Japan,Edo period (1615–1868),,,,Artist,,Utagawa Yoshitora,"Japanese, active ca. 1850–80",,Utagawa Yoshitora,Japanese,1845,1880,1866,1866,1866,Triptych of polychrome woodblock prints; ink and color on paper,Oban; 29 5/8 x 14 9/16 in. (75.2 x 37 cm),"Gift of Lincoln Kirstein, 1959",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55493,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3362,false,true,55530,Asian Art,Print,Orandajin|Dutchmen,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Yoshitora,"Japanese, active ca. 1850–80",,Utagawa Yoshitora,Japanese,1845,1880,1860,1860,1860,Polychrome woodblock print; ink and color on paper,14 3/4 x 9 3/4 in. (37.5 x 24.8 cm),"Gift of Lincoln Kirstein, 1960",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55530,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3368,false,true,55535,Asian Art,Print,Orandajin|A Dutch Couple,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Yoshitora,"Japanese, active ca. 1850–80",,Utagawa Yoshitora,Japanese,1845,1880,1863 (Bunkyu 3),1863,1863,Polychrome woodblock print; ink and color on paper,14 3/8 x 9 15/16 in. (36.5 x 25.2 cm),"Gift of Lincoln Kirstein, 1960",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55535,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3369,false,true,55536,Asian Art,Print,英吉利人|Ingirisu-jin,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Yoshitora,"Japanese, active ca. 1850–80",,Utagawa Yoshitora,Japanese,1845,1880,"1863, 7th month (Bunkyu 3)",1863,1863,Polychrome woodblock print; ink and color on paper,14 1/8 x 10 in. (35.9 x 25.4 cm),"Gift of Lincoln Kirstein, 1960",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55536,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3378,false,true,55545,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Yoshitora,"Japanese, active ca. 1850–80",,Utagawa Yoshitora,Japanese,1845,1880,1870,1870,1870,Triptych of polychrome woodblock prints; ink and color on paper,13 3/4 x 27 1/2 in. (34.9 x 69.9 cm),"Gift of Lincoln Kirstein, 1960",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55545,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP212.25,false,true,57985,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Yoshitora,"Japanese, active ca. 1850–80",,Utagawa Yoshitora,Japanese,1845,1880,ca. 1858,1848,1868,Polychrome woodblock print; ink and color on paper,Image: 13 1/2 × 9 1/4 in. (34.3 × 23.5 cm) Mat: 15 1/2 × 22 3/4 in. (39.4 × 57.8 cm),"Gift of the Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/57985,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2001.715.10,false,true,63376,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Yoshitora,"Japanese, active ca. 1850–80",,Utagawa Yoshitora,Japanese,1845,1880,"1859, 2nd month",1859,1859,Polychrome woodblock print; ink and color on paper,"Obant tate-e, 14 5/8 x 10 1/4 in. (37.1 x 26 cm)","Purchase, Arnold Weinstein Gift, 2001",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/63376,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.180,false,true,73467,Asian Art,Print,Porosiajin|A Prussian Couple,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Yoshitora,"Japanese, active ca. 1850–80",,Utagawa Yoshitora,Japanese,1845,1880,"12th month, 1860",1860,1860,Polychrome woodblock print; ink and color on paper,Image: 14 1/4 x 9 1/2 in. (36.2 x 24.1 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73467,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.181,false,true,73468,Asian Art,Print,Karajin|Two Chinese Women,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Yoshitora,"Japanese, active ca. 1850–80",,Utagawa Yoshitora,Japanese,1845,1880,"12th month, 1860",1860,1860,Polychrome woodblock print; ink and color on paper,Image: 14 1/2 x 10 in. (36.8 x 25.4 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73468,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.182,false,true,73469,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Yoshitora,"Japanese, active ca. 1850–80",,Utagawa Yoshitora,Japanese,1845,1880,"12th month, 1860",1860,1860,Polychrome woodblock print; ink and color on paper,Image: 14 1/4 x 9 3/4 in. (36.2 x 24.8 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73469,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.183,false,true,73470,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Yoshitora,"Japanese, active ca. 1850–80",,Utagawa Yoshitora,Japanese,1845,1880,"12th month, 1860",1860,1860,Polychrome woodblock print; ink and color on paper,Image: 14 3/8 x 9 7/8 in. (36.5 x 25.1 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73470,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.184,false,true,73471,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Yoshitora,"Japanese, active ca. 1850–80",,Utagawa Yoshitora,Japanese,1845,1880,"1st month, 1861",1861,1861,Polychrome woodblock print; ink and color on paper,Image: 14 1/4 x 9 7/8 in. (36.2 x 25.1 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73471,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.185,false,true,73472,Asian Art,Print,Nōgei no seiran|Returning Sails at Nōgei [American couple riding over the Nōgei Bridge],Japan,Edo period (1615–1868),,,,Artist,,Utagawa Yoshitora,"Japanese, active ca. 1850–80",,Utagawa Yoshitora,Japanese,1845,1880,"1st month, 1861",1861,1861,Polychrome woodblock print; ink and color on paper,Image: 14 1/8 x 9 3/4 in. (35.9 x 24.8 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73472,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.186,false,true,73473,Asian Art,Woodblock print,Michiyuki no embō|Evening Glow on a Traveling Drama [Chinese watching a Kabuki play],Japan,Edo period (1615–1868),,,,Artist,,Utagawa Yoshitora,"Japanese, active ca. 1850–80",,Utagawa Yoshitora,Japanese,1845,1880,"1st month, 1861",1861,1861,Polychrome woodblock print; ink and color on paper,Image: 14 1/2 x 9 7/8 in. (36.8 x 25.1 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73473,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.187,false,true,73474,Asian Art,Print,Asa ichi no yuki|Snow at an Early Morning Market [Chinese shopping for vegetables],Japan,Edo period (1615–1868),,,,Artist,,Utagawa Yoshitora,"Japanese, active ca. 1850–80",,Utagawa Yoshitora,Japanese,1845,1880,"1st month, 1861",1861,1861,Polychrome woodblock print; ink and color on paper,Image: 14 5/8 x 9 3/8 in. (37.1 x 23.8 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73474,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.188,false,true,73475,Asian Art,Print,Motomura no yūdachi|Evening Glow at Motomura [Two Englishmen looking at the sunset],Japan,Edo period (1615–1868),,,,Artist,,Utagawa Yoshitora,"Japanese, active ca. 1850–80",,Utagawa Yoshitora,Japanese,1845,1880,"1st month, 1861",1861,1861,Polychrome woodblock print; ink and color on paper,Image: 14 1/8 x 9 3/4 in. (35.9 x 24.8 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73475,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.189,false,true,73476,Asian Art,Print,Miyozaki no shūgetsu|Autumn Moon at Miyozaki,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Yoshitora,"Japanese, active ca. 1850–80",,Utagawa Yoshitora,Japanese,1845,1880,"1st month, 1861",1861,1861,Polychrome woodblock print; ink and color on paper,Image: 14 3/4 x 10 in. (37.5 x 25.4 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73476,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.190,false,true,73477,Asian Art,Print,Gankirō yoru no ame|Night Rain at Gankirō,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Yoshitora,"Japanese, active ca. 1850–80",,Utagawa Yoshitora,Japanese,1845,1880,"1st month, 1861",1861,1861,Polychrome woodblock print; ink and color on paper,Image: 14 3/8 x 10 in. (36.5 x 25.4 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73477,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.191,false,true,73478,Asian Art,Print,Hatoba no kihan|Returning Sails at the Wharves [American couple],Japan,Edo period (1615–1868),,,,Artist,,Utagawa Yoshitora,"Japanese, active ca. 1850–80",,Utagawa Yoshitora,Japanese,1845,1880,"1st month, 1861",1861,1861,Polychrome woodblock print; ink and color on paper,Image: 14 7/8 x 9 15/16 in. (37.8 x 25.2 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73478,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.192,false,true,73480,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Yoshitora,"Japanese, active ca. 1850–80",,Utagawa Yoshitora,Japanese,1845,1880,"1st month, 1861",1861,1861,Polychrome woodblock print; ink and color on paper,Image: 10 1/16 x 14 7/16 in. (25.6 x 36.7 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73480,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.193,false,true,73481,Asian Art,Woodblock print,Amerika|American Horseman,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Yoshitora,"Japanese, active ca. 1850–80",,Utagawa Yoshitora,Japanese,1845,1880,"1st month, 1861",1861,1861,Polychrome woodblock print; ink and color on paper,Image: 14 5/8 x 9 7/8 in. (37.1 x 25.1 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73481,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.194,false,true,73482,Asian Art,Woodblock print,Amerika nyōjin|American Horsewoman,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Yoshitora,"Japanese, active ca. 1850–80",,Utagawa Yoshitora,Japanese,1845,1880,"1st month, 1861",1861,1861,Polychrome woodblock print; ink and color on paper,Image: 14 3/8 x 9 7/8 in. (36.5 x 25.1 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73482,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.195,false,true,73483,Asian Art,Print,Oranda|Dutch Couple,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Yoshitora,"Japanese, active ca. 1850–80",,Utagawa Yoshitora,Japanese,1845,1880,"1st month, 1861",1861,1861,Polychrome woodblock print; ink and color on paper,Image: 14 3/8 x 9 5/8 in. (36.5 x 24.4 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73483,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.196,false,true,73484,Asian Art,Print,Igirisu|Englishmen Dining,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Yoshitora,"Japanese, active ca. 1850–80",,Utagawa Yoshitora,Japanese,1845,1880,"1st month, 1861",1861,1861,Polychrome woodblock print; ink and color on paper,Image: 14 1/2 x 9 7/8 in. (36.8 x 25.1 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73484,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.197,false,true,73485,Asian Art,Woodblock print,Igirisu nyōjin|Englishmen Woman on Horseback,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Yoshitora,"Japanese, active ca. 1850–80",,Utagawa Yoshitora,Japanese,1845,1880,"1st month, 1861",1861,1861,Polychrome woodblock print; ink and color on paper,Image: 14 1/2 x 10 1/16 in. (36.8 x 25.6 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73485,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.198,false,true,73486,Asian Art,Print,Amerika nyōjin|American Woman with Her Child on Stilts,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Yoshitora,"Japanese, active ca. 1850–80",,Utagawa Yoshitora,Japanese,1845,1880,"1st month, 1861",1861,1861,Polychrome woodblock print; ink and color on paper,Image: 14 5/8 x 9 7/8 in. (37.1 x 25.1 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73486,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.199,false,true,73487,Asian Art,Print,Furansu nyōjin|French Housewife and Her Husband,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Yoshitora,"Japanese, active ca. 1850–80",,Utagawa Yoshitora,Japanese,1845,1880,"1st month, 1861",1861,1861,Polychrome woodblock print; ink and color on paper,Image: 14 3/4 x 10 in. (37.5 x 25.4 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73487,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.203,false,true,73491,Asian Art,Print,Oranda|Dutch Printers,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Yoshitora,"Japanese, active ca. 1850–80",,Utagawa Yoshitora,Japanese,1845,1880,"2nd month, 1861",1861,1861,Polychrome woodblock print; ink and color on paper,Image: 14 3/4 x 10 1/8 in. (37.5 x 25.7 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73491,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.205,false,true,73493,Asian Art,Print,Orosia|Mounted Russian,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Yoshitora,"Japanese, active ca. 1850–80",,Utagawa Yoshitora,Japanese,1845,1880,"5th month, 1861",1861,1861,Polychrome woodblock print; ink and color on paper,Image: 14 × 10 in. (35.6 × 25.4 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73493,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.209,false,true,73499,Asian Art,Print,Tōsen no zu|Chinese Junk,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Yoshitora,"Japanese, active ca. 1850–80",,Utagawa Yoshitora,Japanese,1845,1880,"2nd month, 1862",1862,1862,Polychrome woodblock print; ink and color on paper,Image: 14 5/8 x 10 1/4 in. (37.1 x 26 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73499,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.210,false,true,73500,Asian Art,Print,Igirisu fune|English Ship,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Yoshitora,"Japanese, active ca. 1850–80",,Utagawa Yoshitora,Japanese,1845,1880,"2nd month, 1862",1862,1862,Polychrome woodblock print; ink and color on paper,Image: 14 x 9 3/4 in. (35.6 x 24.8 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73500,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.211,false,true,73501,Asian Art,Print,Kita Amerka fune no zu|North American Ship,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Yoshitora,"Japanese, active ca. 1850–80",,Utagawa Yoshitora,Japanese,1845,1880,"4th month, 1862",1862,1862,Polychrome woodblock print; ink and color on paper,Image: 14 3/4 x 10 1/8 in. (37.5 x 25.7 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73501,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.214,false,true,73504,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Yoshitora,"Japanese, active ca. 1850–80",,Utagawa Yoshitora,Japanese,1845,1880,"3rd month, 1864",1864,1864,Polychrome woodblock print; ink and color on paper,Image: 14 1/4 x 9 3/8 in. (36.2 x 23.8 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73504,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.215,false,true,73505,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Yoshitora,"Japanese, active ca. 1850–80",,Utagawa Yoshitora,Japanese,1845,1880,"3rd month, 1864",1864,1864,Polychrome woodblock print; ink and color on paper,Image: 14 1/4 x 9 5/8 in. (36.2 x 24.4 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73505,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.216,false,true,73506,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Yoshitora,"Japanese, active ca. 1850–80",,Utagawa Yoshitora,Japanese,1845,1880,"3rd month, 1864",1864,1864,Polychrome woodblock print; ink and color on paper,Image: 14 1/4 x 9 1/2 in. (36.2 x 24.1 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73506,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.217,false,true,73507,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Yoshitora,"Japanese, active ca. 1850–80",,Utagawa Yoshitora,Japanese,1845,1880,"3rd month, 1864",1864,1864,Polychrome woodblock print; ink and color on paper,Image: 14 1/4 x 9 5/8 in. (36.2 x 24.4 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73507,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.222,false,true,73512,Asian Art,Print,「亜墨利加國」|“America”: Enjoying Hot Air Balloons,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Yoshitora,"Japanese, active ca. 1850–80",,Utagawa Yoshitora,Japanese,1845,1880,1867,1867,1867,Triptych of polychrome woodblock prints; ink and color on paper,Image: 14 3/4 x 28 3/4 in. (37.5 x 73 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73512,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3275a–c,false,true,55357,Asian Art,Print,亜米利加国|American Balloon Ascension (Amerikakoku),Japan,Edo period (1615–1868),,,,Artist,,Utagawa Yoshitora,"Japanese, active ca. 1850–80",,Utagawa Yoshitora,Japanese,1845,1880,1867,1867,1867,Triptych of polychrome woodblock prints; ink and color on paper,Image (a): 14 5/8 × 9 5/8 in. (37.1 × 24.4 cm) Image (b): 14 3/4 in. × 10 in. (37.5 × 25.4 cm) Image (c): 14 3/4 in. × 10 in. (37.5 × 25.4 cm) Mat: 22 7/8 in. × 37 in. (58.1 × 94 cm),"Gift of Lincoln Kirstein, 1959",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55357,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2007.49.200a, b",false,true,73488,Asian Art,Print,Gaikokujin yūkyō no zu|Foreigners Enjoying a Party,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Yoshitora,"Japanese, active ca. 1850–80",,Utagawa Yoshitora,Japanese,1845,1880,"1st month, 1861",1861,1861,Polychrome woodblock print; ink and color on paper,Image (a): 14 3/8 x 10 in. (36.5 x 25.4 cm) Image (b): 14 1/4 x 19 1/4 in. (36.2 x 48.9 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73488,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2007.49.204a, b",false,true,73492,Asian Art,Print,Yokohama hatoba keshiki|A View of the Wharves in Yokohama,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Yoshitora,"Japanese, active ca. 1850–80",,Utagawa Yoshitora,Japanese,1845,1880,"2nd month, 1861",1861,1861,Diptych of polychrome woodblock prints; ink and color on paper,Image (a): 14 3/8 x 9 3/4 in. (36.5 x 24.8 cm) Image (b): 14 5/8 x 9 3/4 in. (37.1 x 24.8 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73492,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.177a–c,false,true,73465,Asian Art,Print,"神奈川横浜港崎町遊女屋光景|View of the Miyozaki Brothel District in Yokohama, Kanagawa (Kanagwa Yokohama Miyozaki machi yūjoya kōkei)",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Yoshitora,"Japanese, active ca. 1850–80",,Utagawa Yoshitora,Japanese,1845,1880,"4th month, 1864",1864,1864,Triptych of polychrome woodblock prints; ink and color on paper,Image (a): 14 3/8 x 9 3/4 in. (36.5 x 24.8 cm) Image (b): 14 1/2 x 9 7/8 in. (36.8 x 25.1 cm) Image (c): 14 1/2 x 9 3/4 in. (36.8 x 24.8 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73465,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.178a–c,false,true,73466,Asian Art,Print,Kanagawa yōri Yokohama...ken no zu|A Distant View of Yokohama from Kanagawa,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Yoshitora,"Japanese, active ca. 1850–80",,Utagawa Yoshitora,Japanese,1845,1880,"5th month, 1860",1860,1860,Triptych of polychrome woodblock prints; ink and color on paper,Image (a): 14 3/8 x 9 5/8 in. (36.5 x 24.4 cm) Image (b): 14 5/8 x 9 3/4 in. (37.1 x 24.8 cm) Image (c): 14 1/4 x 10 1/8 in. (36.2 x 25.7 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73466,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.179a–c,false,true,73691,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Yoshitora,"Japanese, active ca. 1850–80",,Utagawa Yoshitora,Japanese,1845,1880,"5th month, 1860",1860,1860,Triptych of polychrome woodblock prints; ink and color on paper,Image (d): 14 1/4 x 9 1/2 in. (36.2 x 24.1 cm) Image (e): 14 1/4 x 9 3/4 in. (36.2 x 24.8 cm) Image (f): 14 1/8 x 9 7/8 in. (35.9 x 25.1 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73691,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.201a–c,false,true,73489,Asian Art,Print,"Bushu Yokohama gaikokujin yūkyō no zu|A View of the Amusements of the Foreigners in Yokohama, Bushu",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Yoshitora,"Japanese, active ca. 1850–80",,Utagawa Yoshitora,Japanese,1845,1880,"1st month, 1861",1861,1861,Triptych of polychrome woodblock prints; ink and color on paper,Image (a): 14 1/2 x 9 3/4 in. (36.8 x 24.8 cm) Image (b): 14 1/2 x 9 3/4 in. (36.8 x 24.8 cm) Image (c): 14 1/2 x 9 3/4 in. (36.8 x 24.8 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73489,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.202a–c,false,true,73490,Asian Art,Print,"Bushu Yokohama gaikokujin yūkyō no zu|A View of the Amusements of the Foreigners in Yokohama, Bushu",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Yoshitora,"Japanese, active ca. 1850–80",,Utagawa Yoshitora,Japanese,1845,1880,"1st month, 1861",1861,1861,Triptych of polychrome woodblock prints; ink and color on paper,Image (a): 14 1/2 x 9 3/4 in. (36.8 x 24.8 cm) Image (b): 14 1/2 x 9 3/4 in. (36.8 x 24.8 cm) Image (c): 14 1/2 x 9 3/4 in. (36.8 x 24.8 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73490,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.206a–c,false,true,73494,Asian Art,Print,Amerika Washinton fu|City of Washington in America,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Yoshitora,"Japanese, active ca. 1850–80",,Utagawa Yoshitora,Japanese,1845,1880,"1st month, 1862",1862,1862,Triptych of polychrome woodblock prints; ink and color on paper,Image (a): 14 1/4 x 9 5/8 in. (36.2 x 24.4 cm) Image (b): 14 1/4 x 9 5/8 in. (36.2 x 24.4 cm) Image (c): 14 1/4 x 9 5/8 in. (36.2 x 24.4 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73494,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.207a–c,false,true,73495,Asian Art,Print,Igirisu Rondon no kaiko|The Port of London England,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Yoshitora,"Japanese, active ca. 1850–80",,Utagawa Yoshitora,Japanese,1845,1880,"2nd month, 1862",1862,1862,Triptych of polychrome woodblock prints; ink and color on paper,Image (a): 14 1/2 x 9 3/4 in. (36.8 x 24.8 cm) Image (b): 14 1/2 x 9 3/4 in. (36.8 x 24.8 cm) Image (c): 14 1/2 x 9 3/4 in. (36.8 x 24.8 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73495,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.208a–c,false,true,73497,Asian Art,Print,Daishin Nankin fu no shiō|Nankin in China,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Yoshitora,"Japanese, active ca. 1850–80",,Utagawa Yoshitora,Japanese,1845,1880,"2nd month, 1862",1862,1862,Triptych of polychrome woodblock prints; ink and color on paper,Image (a): 14 5/8 x 10 in. (37.1 x 25.4 cm) Image (b): 14 5/8 x 10 in. (37.1 x 25.4 cm) Image (c): 14 3/4 x 9 5/8 in. (37.5 x 24.4 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73497,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.212a–c,false,true,73502,Asian Art,Print,"Furansu Paris no fu|Paris, France",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Yoshitora,"Japanese, active ca. 1850–80",,Utagawa Yoshitora,Japanese,1845,1880,"6th month, 1862",1862,1862,Triptych of polychrome woodblock prints; ink and color on paper,Image (a): 14 1/4 x 10 in. (36.2 x 25.4 cm) Image (b): 14 1/4 x 10 in. (36.2 x 25.4 cm) Image (c): 14 1/4 x 10 in. (36.2 x 25.4 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73502,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.213a–c,false,true,73503,Asian Art,Woodblock print,Tenjiku maru no zu|A View of Indian Elephants,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Yoshitora,"Japanese, active ca. 1850–80",,Utagawa Yoshitora,Japanese,1845,1880,"6th month, 1863",1863,1863,Triptych of polychrome woodblock prints; ink and color on paper,Image (a): 14 1/4 x 9 5/8 in. (36.2 x 24.4 cm) Image (b): 14 3/8 x 9 5/8 in. (36.5 x 24.4 cm) Image (c): 14 1/4 x 9 5/8 in. (36.2 x 24.4 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73503,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.218a–c,false,true,73508,Asian Art,Print,"万国名勝尽競之内魯西亜本都伯徳|Heidoru (St. Petersberg), Capital of Russia, from the series Famous Places from All Nations (Bankoku meisho zukushi - Oroshiya miyako heidoru)",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Yoshitora,"Japanese, active ca. 1850–80",,Utagawa Yoshitora,Japanese,1845,1880,"2nd month, 1865",1865,1865,Triptych of polychrome woodblock prints; ink and color on paper,Image (a): 14 5/8 x 10 in. (37.1 x 25.4 cm) Image (b): 14 5/8 x 9 7/8 in. (37.1 x 25.1 cm) Image (c): 14 1/2 x 10 in. (36.8 x 25.4 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73508,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.219a–c,false,true,73509,Asian Art,Print,仏蘭西国|France (furansukoku),Japan,Edo period (1615–1868),,,,Artist,,Utagawa Yoshitora,"Japanese, active ca. 1850–80",,Utagawa Yoshitora,Japanese,1845,1880,"2nd month, 1865",1865,1865,Triptych of polychrome woodblock prints; ink and color on paper,Image (a): 14 3/8 x 9 5/8 in. (36.5 x 24.4 cm) Image (b): 14 1/2 x 9 1/2 in. (36.8 x 24.1 cm) Image (c): 14 3/8 x 9 7/8 in. (36.5 x 25.1 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73509,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.220a–c,false,true,73510,Asian Art,Print,英吉利国|England (Igirisukoku),Japan,Edo period (1615–1868),,,,Artist,,Utagawa Yoshitora,"Japanese, active ca. 1850–80",,Utagawa Yoshitora,Japanese,1845,1880,"2nd month, 1865",1865,1865,Triptych of polychrome woodblock prints; ink and color on paper,Image (a): 13 3/4 x 9 3/4 in. (34.9 x 24.8 cm) Image (b): 13 1/2 x 9 1/2 in. (34.3 x 24.1 cm) Image (c): 13 3/4 x 9 3/4 in. (34.9 x 24.8 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73510,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.221a–c,false,true,73511,Asian Art,Print,"英吉利国倫敦図|Illustration of London, England (Igirisukoku rondon zu)",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Yoshitora,"Japanese, active ca. 1850–80",,Utagawa Yoshitora,Japanese,1845,1880,"3rd month, 1866",1866,1866,Triptych of polychrome woodblock prints; ink and color on paper,Image (a): 14 1/4 x 9 3/4 in. (36.2 x 24.8 cm) Image (b): 14 1/4 x 9 3/4 in. (36.2 x 24.8 cm) Image (c): 14 1/4 x 9 3/4 in. (36.2 x 24.8 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73511,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3070,false,true,56551,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Sugimura Jihei Masataka,"Japanese, active ca. 1680–1698",,Sugimura Jihei Masataka,Japanese,1680,1698,ca. 1685,1675,1695,Monochrome woodblock print; ink on paper,10 3/4 x 15 1/2 in. (27.3 x 39.4 cm),"Harris Brisbane Dick Fund and Rogers Fund, 1949",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56551,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2642,false,true,56823,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Hishikawa Morofusa,"Japanese, active ca. 1685–1715",(?),Hishikawa Morofusa,Japanese,1685,1715,early 18th century,1700,1733,Monochrome woodblock print (sumie); ink on paper,9 1/2 x 13 3/4 in. (24.1 x 34.9 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56823,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP467,false,true,36918,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyoshige,"Japanese, active ca. 1716–1759",,Torii Kiyoshige,Japanese,1716,1759,"2nd month, 1763",1763,1763,Polychrome woodblock print; ink and color on paper,11 7/8 x 5 7/16 in. (30.2 x 13.8 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36918,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2761,false,true,57025,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyoshige,"Japanese, active ca. 1716–1759",,Torii Kiyoshige,Japanese,1716,1759,1716–1759,1716,1759,Polychrome woodblock print; ink and color on paper,14 7/8 x 6 7/8 in. (37.8 x 17.5 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/57025,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3083,false,true,56596,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Hasegawa Mitsunobu,"Japanese, active ca. 1724–1754",,Hasegawa Mitsunobu,Japanese,1724,1754,ca. 1730,1720,1740,Monochrome woodblock print; ink on paper,10 1/2 x 15 in. (26.7 x 38.1 cm),"Harris Brisbane Dick Fund and Rogers Fund, 1949",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56596,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3084,false,true,56597,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Hasegawa Mitsunobu,"Japanese, active ca. 1724–1754",,Hasegawa Mitsunobu,Japanese,1724,1754,ca. 1730,1720,1740,Monochrome woodblock print; ink on paper,10 1/2 x 15 in. (26.7 x 38.1 cm),"Harris Brisbane Dick Fund and Rogers Fund, 1949",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56597,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1309,false,true,45043,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Tamura Sadanobu,"Japanese, active ca. 1725–1740",,Tamura Sadanobu,Japanese,1725,1740,ca. 1730,1720,1740,Polychrome lacquer print (urushi-e),H. 12 1/16 in. (30.6 cm); W. 6 in. (15.2 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45043,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP233,false,true,36705,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyohiro,"Japanese, active ca. 1737–1766",,Torii Kiyohiro,Japanese,1737,1766,"2nd month, 1746",1746,1746,Polychrome woodblock print; ink and color on paper,11 15/32 x 5 7/16 in. (29.1 x 13.8 cm),"Rogers Fund, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36705,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP840,false,true,54495,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyohiro,"Japanese, active ca. 1737–1766",,Torii Kiyohiro,Japanese,1737,1766,ca. 1757,1747,1767,Polychrome woodblock print; ink and color on paper,H. 11 7/8 in. (30.2 cm); W. 5 9/16 in. (14.1 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54495,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP841,false,true,54496,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyohiro,"Japanese, active ca. 1737–1766",,Torii Kiyohiro,Japanese,1737,1766,ca. 1756,1746,1766,Polychrome woodblock print; ink and color on paper,H. 12 1/8 in. (30.8 cm); W. 5 1/2 in. (14 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54496,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP842,false,true,51089,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyohiro,"Japanese, active ca. 1737–1766",,Torii Kiyohiro,Japanese,1737,1766,ca. 1756,1746,1766,Polychrome woodblock print (beni-e); ink and color on paper,H. 12 1/8 in. (30.8 cm); W. 5 3/4 in. (14.6 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/51089,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP843,false,true,54497,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyohiro,"Japanese, active ca. 1737–1766",,Torii Kiyohiro,Japanese,1737,1766,ca. 1756,1746,1766,Polychrome woodblock print; ink and color on paper,H. 12 1/8 in. (30.8 cm); W. 5 3/4 in. (14.6 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54497,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1109,false,true,55033,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyohiro,"Japanese, active ca. 1737–1766",,Torii Kiyohiro,Japanese,1737,1766,ca. 1754,1744,1764,Polychrome woodblock print; ink and color on paper,H. 12 in. (30.5 cm); W. 5 3/4 in. (14.6 cm),"Rogers Fund, 1919",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55033,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1449,false,true,55496,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyohiro,"Japanese, active ca. 1737–1766",,Torii Kiyohiro,Japanese,1737,1766,ca. 1755,1745,1765,Polychrome woodblock print; ink and color on paper,H. 16 1/4 in. (41.3 cm); W. 11 1/4 in. (28.6 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55496,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2586,false,true,56732,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyohiro,"Japanese, active ca. 1737–1766",,Torii Kiyohiro,Japanese,1737,1766,"12th month, 1754 or 1st month, 1755",1754,1755,Polychrome woodblock print; ink and color on paper,12 1/2 x 5 3/4 in. (31.8 x 14.6 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56732,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2587,false,true,56733,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyohiro,"Japanese, active ca. 1737–1766",,Torii Kiyohiro,Japanese,1737,1766,ca. 1754,1734,1754,Polychrome woodblock print; ink and color on paper,Overall: 11 7/8 x 5 5/8 in. (30.2 x 14.3 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56733,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2762,false,true,57026,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyohiro,"Japanese, active ca. 1737–1766",,Torii Kiyohiro,Japanese,1737,1766,1737–1766,1737,1766,Polychrome woodblock print; ink and color on paper,15 1/8 x 6 3/4 in. (38.4 x 17.1 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/57026,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3089,false,true,56602,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyohiro,"Japanese, active ca. 1737–1766",,Torii Kiyohiro,Japanese,1737,1766,ca. 1755,1745,1765,Polychrome woodblock print; ink and color on paper,17 x 11 in. (43.2 x 27.9 cm),"Harris Brisbane Dick Fund and Rogers Fund, 1949",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56602,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3090,false,true,56603,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyohiro,"Japanese, active ca. 1737–1766",,Torii Kiyohiro,Japanese,1737,1766,1756,1756,1756,Polychrome woodblock print; ink and color on paper,12 1/4 x 5 3/4 in. (31.1 x 14.6 cm),"Harris Brisbane Dick Fund and Rogers Fund, 1949",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56603,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3091,false,true,56604,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyohiro,"Japanese, active ca. 1737–1766",,Torii Kiyohiro,Japanese,1737,1766,1754,1754,1754,Polychrome woodblock print; ink and color on paper,12 x 5 1/2 in. (30.5 x 14 cm),"Harris Brisbane Dick Fund and Rogers Fund, 1949",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56604,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3092,false,true,56605,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyohiro,"Japanese, active ca. 1737–1766",,Torii Kiyohiro,Japanese,1737,1766,1757,1757,1757,Polychrome woodblock print; ink and color on paper,15 x 7 in. (38.1 x 17.8 cm),"Harris Brisbane Dick Fund and Rogers Fund, 1949",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56605,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1562,false,true,55729,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Masafusa,"Japanese, active ca. 1750–1770",,Masafusa,Japanese,1750,1770,ca. 1750–70,1750,1770,Polychrome woodblock print; ink and color on paper,H. 11 1/2 in. (29.2 cm); W. 6 in. (15.2 cm),"Fletcher Fund, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55729,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1503,false,true,53894,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyotsune,"Japanese, active ca. 1757–1779",,Torii Kiyotsune,Japanese,1757,1779,ca. 1768,1758,1778,Left sheet of a triptych of polychrome woodblock prints; ink and color on paper,12 3/8 x 5 3/8 in. (31.4 x 13.7 cm),"Fletcher Fund, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/53894,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3096,false,true,56609,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyotsune,"Japanese, active ca. 1757–1779",,Torii Kiyotsune,Japanese,1757,1779,1767,1767,1767,Polychrome woodblock print; ink and color on paper,12 x 5 1/2 in. (30.5 x 14 cm),"Harris Brisbane Dick Fund and Rogers Fund, 1949",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56609,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2785,false,true,57104,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Haruhiro,"Japanese, active ca. 1765–1784",,Haruhiro,Japanese,1765,1784,1765–1784,1765,1784,Polychrome woodblock print; ink and color on paper,11 3/8 x 8 3/16 in. (28.9 x 20.8 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/57104,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP187,false,true,36665,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Rekisentei Eiri,"Japanese, active ca. 1789–1801",,Rekisentei Eiri,Japanese,1789,1801,ca. 1797,1787,1807,Pentaptych of polychrome woodblock prints; ink and color on paper,15 5/8 x 9 5/8 in. (39.7 x 24.46 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36665,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP739,false,true,37187,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Rekisentei Eiri,"Japanese, active ca. 1789–1801",,Rekisentei Eiri,Japanese,1789,1801,ca. 1791,1781,1801,Polychrome woodblock print; ink and color on paper,12 3/4 x 8 5/8 in. (32.4 x 21.9 cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37187,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP953,false,true,54847,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Rekisentei Eiri,"Japanese, active ca. 1789–1801",,Rekisentei Eiri,Japanese,1789,1801,ca. 1791,1781,1801,Polychrome woodblock print; ink and color on paper,H. 9 3/8 in. (23.8 cm); W. 7 1/4 in. (18.4 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54847,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP954,false,true,54848,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Rekisentei Eiri,"Japanese, active ca. 1789–1801",,Rekisentei Eiri,Japanese,1789,1801,ca. 1791,1781,1801,Polychrome woodblock print; ink and color on paper,H. 9 3/16 in. (23.3 cm); W. 7 1/16 in. (17.9 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54848,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP955,false,true,54849,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Rekisentei Eiri,"Japanese, active ca. 1789–1801",,Rekisentei Eiri,Japanese,1789,1801,ca. 1795,1785,1805,Polychrome woodblock print; ink and color on paper,H. 12 3/4 in. (32.4 cm); W. 8 3/8 in. (21.3 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54849,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1212,false,true,55135,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Rekisentei Eiri,"Japanese, active ca. 1789–1801",,Rekisentei Eiri,Japanese,1789,1801,ca. 1795,1785,1805,Polychrome woodblock print; ink and color on paper,H. 12 5/8 in. (32.1 cm); W. 8 5/8 in. (21.9 cm),"Rogers Fund, 1920",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55135,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1305,false,true,55244,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunkaku,"Japanese, active ca. 1789–1801",,Katsukawa,Japanese,1789,1801,ca. 1795,1785,1805,Polychrome woodblock print; ink and color on paper,H. 12 3/8 in. (31.4 cm); W. 5 9/16 in. (14.1 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55244,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1496,false,true,45224,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Rekisentei Eiri,"Japanese, active ca. 1789–1801",,Rekisentei Eiri,Japanese,1789,1801,ca. 1795,1785,1805,Polychrome woodblock print; ink and color on paper,H. 15 1/2 in. (39.4 cm); W. 10 1/4 in. (26 cm),"Fletcher Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45224,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1788,false,true,45216,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Rekisentei Eiri,"Japanese, active ca. 1789–1801",,Rekisentei Eiri,Japanese,1789,1801,ca. 1795,1785,1805,Polychrome woodblock print; ink and color on paper,H. 14 3/4 in. (37.5 cm); W. 9 1/2 in. (24.1 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45216,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2419,false,true,45223,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Rekisentei Eiri,"Japanese, active ca. 1789–1801",,Rekisentei Eiri,Japanese,1789,1801,ca. 1795,1785,1805,Polychrome woodblock print; ink and color on paper,15 3/8 x 10 1/4 in. (39.1 x 26 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45223,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP557,false,true,37008,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Ichirakutei Eisui,"Japanese, active ca. 1793–1801",,Ichirakutei Eisui,Japanese,1793,1801,ca. 1797,1787,1807,Polychrome woodblock print; ink and color on paper,14 1/4 x 9 1/8 in. (36.2 x 23.2 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37008,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1794,false,true,56096,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Ichirakutei Eisui,"Japanese, active ca. 1793–1801",,Ichirakutei Eisui,Japanese,1793,1801,1790s,1790,1790,Polychrome woodblock print; ink and color on paper,H. 14 5/8 in. (37.1 cm); W. 9 15/16 in. (25.2 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56096,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1280,false,true,55212,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Momokawa Shiko II,"Japanese, active ca. 1797–1810",,Momokawa Shiko II,Japanese,1797,1810,ca. 1800,1790,1810,Polychrome woodblock print; ink and color on paper,H. 14 7/8 in. (37.8 cm); W. 9 7/8 in. (25.1 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55212,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1481,false,true,55568,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Momokawa Shiko II,"Japanese, active ca. 1797–1810",,Momokawa Shiko II,Japanese,1797,1810,ca. 1800,1790,1810,Polychrome woodblock print; ink and color on paper,H. 5 9/16 in. (14.1 cm); W. 12 3/8 in. (31.4 cm),"Rogers Fund, 1926",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55568,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1140,false,true,54347,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Ryūryūkyo Shinsai,"Japanese, active ca. 1799–1823",,Ryūryūkyo Shinsai,Japanese,1799,1823,ca. 1820,1810,1830,Polychrome woodblock print (surimono); ink and color on paper,8 1/2 x 7 1/2 in. (21.6 x 19.1 cm),"Rogers Fund, 1919",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54347,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1141,false,true,54348,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Ryūryūkyo Shinsai,"Japanese, active ca. 1799–1823",,Ryūryūkyo Shinsai,Japanese,1799,1823,ca. 1800,1790,1810,Polychrome woodblock print (surimono); ink and color on paper,5 9/16 x 7 1/8 in. (14.1 x 18.1 cm),"Rogers Fund, 1919",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54348,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1248,false,true,54379,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Ryūryūkyo Shinsai,"Japanese, active ca. 1799–1823",,Ryūryūkyo Shinsai,Japanese,1799,1823,ca. 1830,1820,1840,Polychrome woodblock print (surimono); ink and color on paper,8 5/8 x 7 5/8 in. (21.9 x 19.4 cm),"Rogers Fund, 1921",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54379,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1410,false,true,55427,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Ryūryūkyo Shinsai,"Japanese, active ca. 1799–1823",,Ryūryūkyo Shinsai,Japanese,1799,1823,ca. 1820,1810,1830,Polychrome woodblock print; ink and color on paper,9 15/16 x 14 1/2 in. (25.2 x 36.8 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55427,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1906,false,true,54445,Asian Art,Print,"舞楽衣装『春雨集』 摺物帖|Costume for Bugaku Court DanceFrom the Spring Rain Collection (Harusame shū), vol. 1",Japan,Edo period (1615–1868),,,,Artist,,Ryūryūkyo Shinsai,"Japanese, active ca. 1799–1823",,Ryūryūkyo Shinsai,Japanese,1799,1823,probably 1818 (Year of the Tiger),1818,1818,Polychrome woodblock print (surimono); ink and color on paper,7 5/8 x 6 7/16 in. (19.4 x 16.4 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54445,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1908,false,true,54447,Asian Art,Print,"柳々居辰斎画 子春 嫁入の具『春雨集』 摺物帖|Accoutrements for a BrideFrom the Spring Rain Collection (Harusame shū), vol. 1",Japan,Edo period (1615–1868),,,,Artist,,Ryūryūkyo Shinsai,"Japanese, active ca. 1799–1823",,Ryūryūkyo Shinsai,Japanese,1799,1823,probably 1816 (Year of the Rat),1816,1816,Polychrome woodblock print (surimono); ink and color on paper,5 1/2 x 7 7/16 in. (14 x 18.9 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54447,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1913,false,true,54452,Asian Art,Print,"「楽器其三」|Biwa with Brocade Cover, from the series Musical Instruments",Japan,Edo period (1615–1868),,,,Artist,,Ryūryūkyo Shinsai,"Japanese, active ca. 1799–1823",,Ryūryūkyo Shinsai,Japanese,1799,1823,probably 1808,1808,1808,Polychrome woodblock print (surimono); ink and color on paper,5 1/2 x 7 3/8 in. (14 x 18.7 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54452,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1946,false,true,54514,Asian Art,Print,"文具一式|Writing Set and Poem Card Box (Shikishi-bako), from Spring Rain Surimono Album (Harusame surimono-jō), vol. 1",Japan,Edo period (1615–1868),,,,Artist,,Ryūryūkyo Shinsai,"Japanese, active ca. 1799–1823",,Ryūryūkyo Shinsai,Japanese,1799,1823,ca. 1805–10,1800,1820,Privately published polychrome woodblock prints (surimono) mounted in an album; ink and color on paper,4 15/16 x 6 13/16 in. (12.5 x 17.3 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54514,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1947,false,true,54515,Asian Art,Print,"衝立、虎図、文具一式|Desk Screen, Writing Set, Painting of Tiger, and Mounting Paraphernalia, from Spring Rain Surimono Album (Harusame surimono-jō, vol. 1)",Japan,Edo period (1615–1868),,,,Artist,,Ryūryūkyo Shinsai,"Japanese, active ca. 1799–1823",,Ryūryūkyo Shinsai,Japanese,1799,1823,probably 1818,1818,1818,Privately published polychrome woodblock prints (surimono) mounted in an album; ink and color on paper,5 1/2 x 7 3/8 in. (14 x 18.7 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54515,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1956,false,true,54527,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Ryūryūkyo Shinsai,"Japanese, active ca. 1799–1823",,Ryūryūkyo Shinsai,Japanese,1799,1823,probably 1813,1813,1813,Polychrome woodblock print (surimono); ink and color on paper,5 3/8 x 7 1/4 in. (13.7 x 18.4 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54527,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2023,false,true,54772,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Ryūryūkyo Shinsai,"Japanese, active ca. 1799–1823",,Ryūryūkyo Shinsai,Japanese,1799,1823,probably 1816,1816,1816,Polychrome woodblock print (surimono); ink and color on paper,5 1/2 x 7 3/8 in. (14 x 18.7 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54772,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2024,false,true,54784,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Ryūryūkyo Shinsai,"Japanese, active ca. 1799–1823",,Ryūryūkyo Shinsai,Japanese,1799,1823,1808,1808,1808,Polychrome woodblock print (surimono); ink and color on paper,5 9/16 x 7 7/16 in. (14.1 x 18.9 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54784,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2036,false,true,54804,Asian Art,Print,"『春雨集』 摺物帖柳々居辰斎画 紅梅|Spring Rain Collection (Harusame shū), vol. 1: Plum Tree in Bloom",Japan,Edo period (1615–1868),,,,Artist,,Ryūryūkyo Shinsai,"Japanese, active ca. 1799–1823",,Ryūryūkyo Shinsai,Japanese,1799,1823,ca. 1805–10,1805,1810,Privately published woodblock prints (surimono) mounted in an album; ink and color on paper,8 1/4 x 5 1/2 in. (21 x 14 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54804,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2039,false,true,54810,Asian Art,Print,"『春雨集』 摺物帖柳々居辰斎画 蟹と蓮華|Spring Rain Collection (Harusame shū), vol. 1: Crabs and Lotus Blossoms",Japan,Edo period (1615–1868),,,,Artist,,Ryūryūkyo Shinsai,"Japanese, active ca. 1799–1823",,Ryūryūkyo Shinsai,Japanese,1799,1823,ca. 1805–10,1805,1810,Privately published woodblock prints (surimono) mounted in an album; ink and color on paper,5 1/2 x 7 7/16 in. (14 x 18.9 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54810,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2040,false,true,54811,Asian Art,Woodblock print,"摺物帖 『春雨集』 鴨と葱|Spring Rain Collection (Harusame shū), vol. 1: Duck and Scallions",Japan,Edo period (1615–1868),,,,Artist,,Ryūryūkyo Shinsai,"Japanese, active ca. 1799–1823",,Ryūryūkyo Shinsai,Japanese,1799,1823,1810s,1810,1819,Privately published woodblock prints (surimono) mounted in an album; ink and color on paper,5 5/8 x 7 9/16 in. (14.3 x 19.2 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54811,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2043,false,true,54814,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Ryūryūkyo Shinsai,"Japanese, active ca. 1799–1823",(?),Ryūryūkyo Shinsai,Japanese,1799,1823,probably 1816,1816,1816,Polychrome woodblock print (surimono); ink and color on paper,5 1/2 x 7 3/8 in. (14 x 18.7 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54814,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2068,false,true,54898,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Ryūryūkyo Shinsai,"Japanese, active ca. 1799–1823",,Ryūryūkyo Shinsai,Japanese,1799,1823,probably 1819,1819,1819,Polychrome woodblock print (surimono); ink and color on paper,8 1/4 x 7 5/16 in. (21 x 18.6 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54898,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2108,false,true,54960,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Ryūryūkyo Shinsai,"Japanese, active ca. 1799–1823",,Ryūryūkyo Shinsai,Japanese,1799,1823,probably 1819,1819,1819,Polychrome woodblock print (surimono); ink and color on paper,7 15/16 x 7 1/16 in. (20.2 x 17.9 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54960,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2110,false,true,54962,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Ryūryūkyo Shinsai,"Japanese, active ca. 1799–1823",,Ryūryūkyo Shinsai,Japanese,1799,1823,probably 1815,1815,1815,Polychrome woodblock print (surimono); ink and color on paper,5 5/8 x 7 5/16 in. (14.3 x 18.6 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54962,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2135,false,true,54990,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Ryūryūkyo Shinsai,"Japanese, active ca. 1799–1823",,Ryūryūkyo Shinsai,Japanese,1799,1823,probably 1816,1816,1816,Polychrome woodblock print (surimono); ink and color on paper,5 5/8 x 7 7/16 in. (14.3 x 18.9 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54990,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2136,false,true,54991,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Ryūryūkyo Shinsai,"Japanese, active ca. 1799–1823",,Ryūryūkyo Shinsai,Japanese,1799,1823,probably 1816,1816,1816,Polychrome woodblock print (surimono); ink and color on paper,5 5/8 x 7 1/2 in. (14.3 x 19.1 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54991,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2153,false,true,55058,Asian Art,Print,"『春雨集』 摺物帖柳々居辰斎画 鎌倉の鶴岡八幡宮に鶴|Spring Rain Collection (Harusame shū), vol. 2: Cranes at Tsurugaoka Hachimangō Shrine in Kamakura",Japan,Edo period (1615–1868),,,,Artist,,Ryūryūkyo Shinsai,"Japanese, active ca. 1799–1823",,Ryūryūkyo Shinsai,Japanese,1799,1823,1810s,1810,1819,Privately published woodblock prints (surimono) mounted in an album; ink and color on paper,5 1/2 x 7 1/4 in. (14 x 18.4 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55058,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2196,false,true,55112,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Ryūryūkyo Shinsai,"Japanese, active ca. 1799–1823",,Ryūryūkyo Shinsai,Japanese,1799,1823,1810,1810,1810,Polychrome woodblock print (surimono); ink and color on paper,4 1/8 x 5 11/16 in. (10.5 x 14.4 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55112,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2256,false,true,54025,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Ryūryūkyo Shinsai,"Japanese, active ca. 1799–1823",,Ryūryūkyo Shinsai,Japanese,1799,1823,probably 1816,1800,1900,Part of an album of woodblock prints (surimono); ink and color on paper,5 5/8 x 7 9/16 in. (14.3 x 19.2 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54025,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2257,false,true,54026,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Ryūryūkyo Shinsai,"Japanese, active ca. 1799–1823",,Ryūryūkyo Shinsai,Japanese,1799,1823,probably 1815,1815,1815,Part of an album of woodblock prints (surimono); ink and color on paper,5 5/8 x 7 9/16 in. (14.3 x 19.2 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54026,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2295,false,true,54068,Asian Art,Print,"鉢梅、懐中日時計、羅針盤|Bonsai Plum, Compass, and Pocket Sundial with Design of Calendar, from Spring Rain Surimono Album (Harusame surimono-jō, vol. 3)",Japan,Edo period (1615–1868),,,,Artist,,Ryūryūkyo Shinsai,"Japanese, active ca. 1799–1823",,Ryūryūkyo Shinsai,Japanese,1799,1823,1806,1806,1806,Privately published polychrome woodblock prints (surimono) mounted in an album; ink and color on paper,5 1/2 x 7 1/4 in. (14 x 18.4 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54068,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2296,false,true,54069,Asian Art,Woodblock print,"『蝶揃・寵愛』帯と簪|Roll of Cloth for an Obi and Tortoise-shell Hair Ornaments (“Presents for One’s Beloved”), from the Butterfly Series, from Spring Rain Surimono Album (Harusame surimono-jō, vol. 3)",Japan,Edo period (1615–1868),,,,Artist,,Ryūryūkyo Shinsai,"Japanese, active ca. 1799–1823",,Ryūryūkyo Shinsai,Japanese,1799,1823,ca. 1805–10,1800,1820,Privately published polychrome woodblock prints (surimono) mounted in an album; ink and color on paper,5 7/8 x 7 9/16 in. (14.9 x 19.2 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54069,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2337,false,true,54122,Asian Art,Woodblock print,「牛和歌十二段矢矧長者」|Dance Robe and Koto (Zither) Representing the Wealthy Man of Yahagi from the Jōruri Play Ushiwaka (Minamoto no Yoshitsune),Japan,Edo period (1615–1868),,,,Artist,,Ryūryūkyo Shinsai,"Japanese, active ca. 1799–1823",,Ryūryūkyo Shinsai,Japanese,1799,1823,probably 1810,1810,1810,Part of an album of woodblock prints (surimono); ink and color on paper,5 9/16 x 7 3/8 in. (14.1 x 18.7 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54122,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2341,false,true,54126,Asian Art,Print,"「職人合香聞 」|Utensils for the Incense Ceremony, “Incense Master” (Kōgiki), from the series An Array of Artisans",Japan,Edo period (1615–1868),,,,Artist,,Ryūryūkyo Shinsai,"Japanese, active ca. 1799–1823",,Ryūryūkyo Shinsai,Japanese,1799,1823,ca. 1810s,1805,1820,Part of an album of woodblock prints (surimono); ink and color on paper,5 11/16 x 7 9/16 in. (14.4 x 19.2 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54126,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2344,false,true,54129,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Ryūryūkyo Shinsai,"Japanese, active ca. 1799–1823",,Ryūryūkyo Shinsai,Japanese,1799,1823,1811,1811,1811,Part of an album of woodblock prints (surimono); ink and color on paper,4 1/6 x 7 7/16 in. (10.6 x 18.9 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54129,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3000,false,true,54216,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Ryūryūkyo Shinsai,"Japanese, active ca. 1799–1823",,Ryūryūkyo Shinsai,Japanese,1799,1823,1750–1835,1750,1835,Polychrome woodblock print (surimono); ink and color on paper,5 1/2 x 7 7/16 in. (14 x 18.9 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54216,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3004,false,true,54220,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Ryūryūkyo Shinsai,"Japanese, active ca. 1799–1823",,Ryūryūkyo Shinsai,Japanese,1799,1823,1750–1835,1750,1835,Polychrome woodblock print (surimono); ink and color on paper,8 3/16 x 7 7/16 in. (20.8 x 18.9 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54220,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1093.5,false,true,58257,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Toyohara Sadatora,"Japanese, active ca. 1818–1844",,Toyohara Sadatora,Japanese,1818,1844,first half of the 19th century,1818,1844,Polychrome woodblock print; ink and color on paper,Image: 15 in. × 10 1/8 in. (38.1 × 25.7 cm),The Metropolitan Museum of Art,,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/58257,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3264,false,true,37388,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Yoshikazu,"Japanese, active ca. 1850–1870",,Utagawa Yoshikazu,Japanese,1845,1870,ca. 1861,1851,1871,Triptych of polychrome woodblock prints; ink and color on paper,Image: 14 1/2 in. × 30 in. (36.8 × 76.2 cm) Mat: 20 3/4 in. × 37 in. (52.7 × 94 cm),"Gift of Lincoln Kirstein, 1959",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37388,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3303,false,true,53701,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Yoshikazu,"Japanese, active ca. 1850–1870",,Utagawa Yoshikazu,Japanese,1845,1870,1861,1861,1861,Polychrome woodblock print; ink and color on paper,H. 14 in. 35.6 cm); W. 9 1/2 in. ( 24.1 cm),"Gift of Lincoln Kirstein, 1959",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/53701,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3327,false,true,55475,Asian Art,Print,Furansukoku|仏蘭西国|France,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Yoshikazu,"Japanese, active ca. 1850–1870",,Utagawa Yoshikazu,Japanese,1845,1870,1861 (10th month),1861,1861,Polychrome woodblock print; ink and color on paper,13 3/4 x 9 5/8 in. (34.9 x 24.4 cm),"Gift of Lincoln Kirstein, 1959",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55475,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3333,false,true,55481,Asian Art,Print,Gaikokujin kodomo choai no zu|A Foreigner Enjoying Her Children,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Yoshikazu,"Japanese, active ca. 1850–1870",,Utagawa Yoshikazu,Japanese,1845,1870,1860 (10th month),1860,1860,Polychrome woodblock print; ink and color on paper,13 1/2 x 9 in. (34.3 x 22.9 cm),"Gift of Lincoln Kirstein, 1959",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55481,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3344,false,true,55508,Asian Art,Print,Amerika Koku Jokisen naka no zu|View Inside an American Steamship,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Yoshikazu,"Japanese, active ca. 1850–1870",,Utagawa Yoshikazu,Japanese,1845,1870,"1861 (Bunkyu, 1st year, 4th month)",1861,1861,Triptych of polychrome woodblock prints; ink and color on paper,Oban; 10 1/4 x 27 1/4 in. (26 x 69.2 cm),"Gift of Lincoln Kirstein, 1959",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55508,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3363,false,true,55531,Asian Art,Woodblock print,Gaikokujin ifuku shitate no zu|Picture of a Foreigner Making Clothes,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Yoshikazu,"Japanese, active ca. 1850–1870",,Utagawa Yoshikazu,Japanese,1845,1870,1860,1860,1860,Polychrome woodblock print; ink and color on paper,Image: 13 1/2 x 9 in. (34.3 x 22.9 cm),"Gift of Lincoln Kirstein, 1970",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55531,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3365,false,true,53596,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Yoshikazu,"Japanese, active ca. 1850–1870",,Utagawa Yoshikazu,Japanese,1845,1870,dated 1861,1800,1899,Polychrome woodblock print; ink and color on paper,14 x 9 1/4 in. (35.6 x 23.5 cm),"Gift of Lincoln Kirstein, 1970",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/53596,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3366,false,true,55533,Asian Art,Print,Igirisujin|Englishmen,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Yoshikazu,"Japanese, active ca. 1850–1870",,Utagawa Yoshikazu,Japanese,1845,1870,1861,1861,1861,Polychrome woodblock print; ink and color on paper,14 3/8 x 9 7/8 in. (36.5 x 25.1 cm),"Gift of Lincoln Kirstein, 1960",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55533,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3323a,false,true,55469,Asian Art,Print,亜墨利加|American Family with a Dancing Daughter,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Yoshikazu,"Japanese, active ca. 1850–1870",,Utagawa Yoshikazu,Japanese,1845,1870,"1861 (Bunkyu 1, 2nd month)",1861,1861,Polychrome woodblock print; ink and color on paper,Oban tate-e,"Gift of Lincoln Kirstein, 1959",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55469,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3323b,false,true,55471,Asian Art,Print,仏蘭西|French Photographer with His Wife,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Yoshikazu,"Japanese, active ca. 1850–1870",,Utagawa Yoshikazu,Japanese,1845,1870,"1861 (Bunkyu 1, 2nd month)",1861,1861,Polychrome woodblock print; ink and color on paper,Oban tate-e,"Gift of Lincoln Kirstein, 1959",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55471,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.152,false,true,73438,Asian Art,Print,"Gaikokujin Sake no zu|A Foreigner's Wine Party (Gaikokujin shuen no zu), from an untitled series of foreigners at home",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Yoshikazu,"Japanese, active ca. 1850–1870",,Utagawa Yoshikazu,Japanese,1845,1870,"10th month, 1860",1860,1860,Polychrome woodblock print; ink and color on paper,Image: 13 3/8 x 9 in. (34 x 22.9 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73438,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.153,false,true,73439,Asian Art,Print,Ijiin Yashiki ryōri no zu|Inside a Foreign Restaurant,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Yoshikazu,"Japanese, active ca. 1850–1870",,Utagawa Yoshikazu,Japanese,1845,1870,"10th month, 1860",1860,1860,Polychrome woodblock print; ink and color on paper,Image: 13 1/2 x 9 in. (34.3 x 22.9 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73439,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.154,false,true,73440,Asian Art,Print,Gaikokujin yoru benkyo no zu|Foreigners Studying at Night,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Yoshikazu,"Japanese, active ca. 1850–1870",,Utagawa Yoshikazu,Japanese,1845,1870,1861,1861,1861,Polychrome woodblock print; ink and color on paper,Image: 13 1/2 x 9 in. (34.3 x 22.9 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73440,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.155,false,true,73441,Asian Art,Print,Gaikoku shashin kagami no zu|Foreigners Employing a Camera,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Yoshikazu,"Japanese, active ca. 1850–1870",,Utagawa Yoshikazu,Japanese,1845,1870,"11th month, 1860",1860,1860,Polychrome woodblock print; ink and color on paper,Image: 13 1/4 x 8 7/8 in. (33.7 x 22.5 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73441,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.156,false,true,73442,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Yoshikazu,"Japanese, active ca. 1850–1870",,Utagawa Yoshikazu,Japanese,1845,1870,1860,1860,1860,Polychrome woodblock print; ink and color on paper,Image: 13 1/4 x 8 7/8 in. (33.7 x 22.5 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73442,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.157,false,true,73443,Asian Art,Print,"Yokohama kenbutsu zue|Picture of Sights in Yokohama: Woman with a Ringer, Lamp Post, a Steamboat at Full Sail and a Woman with a Sewing Machine",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Yoshikazu,"Japanese, active ca. 1850–1870",,Utagawa Yoshikazu,Japanese,1845,1870,"11th month, 1860",1860,1860,Polychrome woodblock print; ink and color on paper,Image: 14 3/4 x 10 in. (37.5 x 25.4 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73443,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.159,false,true,73445,Asian Art,Print,「横浜異人屋敷之圖」|A Foreign Residence in Yokohama,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Yoshikazu,"Japanese, active ca. 1850–1870",,Utagawa Yoshikazu,Japanese,1845,1870,"1st month, 1861",1861,1861,Triptych of polychrome woodblock prints; ink and color on paper,Image: 29 1/2 x 14 3/4 in. (74.9 x 37.5 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73445,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.161,false,true,73447,Asian Art,Print,Yokohama Gankirō gaikokujin gyōretsu no zu|Picture of a Procession of Foreigners at Yokohama,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Yoshikazu,"Japanese, active ca. 1850–1870",,Utagawa Yoshikazu,Japanese,1845,1870,"2nd month, 1861",1861,1861,Triptych of polychrome woodblock prints; ink and color on paper,Image: 14 1/8 x 29 1/16 in. (35.9 x 73.8 cm) Overall (Mat): 20 x 35 in. (50.8 x 88.9 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73447,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.165,false,true,73451,Asian Art,Print,Furansu|French Photographer,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Yoshikazu,"Japanese, active ca. 1850–1870",,Utagawa Yoshikazu,Japanese,1845,1870,1861,1861,1861,Polychrome woodblock print; ink and color on paper,Image: 13 7/8 x 9 1/2 in. (35.2 x 24.1 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73451,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.166,false,true,73453,Asian Art,Print,Orosia|Russian Printers,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Yoshikazu,"Japanese, active ca. 1850–1870",,Utagawa Yoshikazu,Japanese,1845,1870,"2nd month, 1861",1861,1861,Polychrome woodblock print; ink and color on paper,Image: 14 1/8 x 9 7/8 in. (35.9 x 25.1 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73453,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.167,false,true,73454,Asian Art,Woodblock print,Oranda|A Dutch Group,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Yoshikazu,"Japanese, active ca. 1850–1870",,Utagawa Yoshikazu,Japanese,1845,1870,"2nd month, 1861",1861,1861,Polychrome woodblock print; ink and color on paper,Image: 14 1/4 x 9 3/4 in. (36.2 x 24.8 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73454,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.169,false,true,73457,Asian Art,Print,Amerika koku jōkisen naka no zu|Interior of an American Steamship,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Yoshikazu,"Japanese, active ca. 1850–1870",,Utagawa Yoshikazu,Japanese,1845,1870,"4th month, 1861",1861,1861,Triptych of polychrome woodblock prints; ink and color on paper,Overall: 14 x 28 3/4 in. (35.6 x 73 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73457,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.171,false,true,73459,Asian Art,Print,Furansujin|French Couple,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Yoshikazu,"Japanese, active ca. 1850–1870",,Utagawa Yoshikazu,Japanese,1845,1870,"4th month, 1861",1861,1861,Polychrome woodblock print; ink and color on paper,Image: 14 1/2 x 10 in. (36.8 x 25.4 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73459,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.172,false,true,73460,Asian Art,Print,"Igirisujin|An English Woman with a Chinese Servant in the Foreign District, from the series Famous Places in Yokohama",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Yoshikazu,"Japanese, active ca. 1850–1870",,Utagawa Yoshikazu,Japanese,1845,1870,1861,1861,1861,Polychrome woodblock print; ink and color on paper,Image: 14 3/8 x 9 3/4 in. (36.5 x 24.8 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73460,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.175,false,true,73463,Asian Art,Print,Orandajin|Dutch Couple,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Yoshikazu,"Japanese, active ca. 1850–1870",,Utagawa Yoshikazu,Japanese,1845,1870,"10th month, 1862",1862,1862,Polychrome woodblock print; ink and color on paper,Image: 14 x 9 1/2 in. (35.6 x 24.1 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73463,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.158a–c,false,true,73444,Asian Art,Print,Yokohama Miyozaki-kaku Gankirō ijin yūkyō no zu|Foreigners Enjoying a Party at the Gankirō Tea House,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Yoshikazu,"Japanese, active ca. 1850–1870",,Utagawa Yoshikazu,Japanese,1845,1870,"1st month, 1861",1861,1861,Triptych of polychrome woodblock prints; ink and color on paper,Image (a): 14 3/8 x 9 7/8 in. (36.5 x 25.1 cm) Image (b): 14 1/4 x 10 in. (36.2 x 25.4 cm) Image (c): 14 1/8 x 9 1/2 in. (35.9 x 24.1 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73444,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.160a–c,false,true,73446,Asian Art,Print,Yokohama Gankirō kodomo te odori no zu|Foreigners Enjoying Children's Kabuki at the Gankirō Tea House,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Yoshikazu,"Japanese, active ca. 1850–1870",,Utagawa Yoshikazu,Japanese,1845,1870,"1st month, 1861",1861,1861,Triptych of polychrome woodblock prints; ink and color on paper,Image (a): 14 3/8 x 9 7/8 in. (36.5 x 25.1 cm) Image (b): 14 3/8 x 9 7/8 in. (36.5 x 25.1 cm) Image (c): 14 3/8 x 9 7/8 in. (36.5 x 25.1 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73446,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.162a–c,false,true,73448,Asian Art,Print,Yokohama Gankirō gaikokujin gyōretsu no zu|Picture of a Procession of Foreigners at Yokohama,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Yoshikazu,"Japanese, active ca. 1850–1870",,Utagawa Yoshikazu,Japanese,1845,1870,"2nd month, 1861",1861,1861,Triptych of polychrome woodblock prints; ink and color on paper,Image (a): 14 1/4 x 9 3/4 in. (36.2 x 24.8 cm) Image (b): 14 1/4 x 9 3/4 in. (36.2 x 24.8 cm) Image (c): 14 1/4 x 9 3/4 in. (36.2 x 24.8 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73448,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.163a–c,false,true,73449,Asian Art,Print,Kanagawa Gongenyama Gaikokujin yūran|Foreigners Visiting the Famous Site of Mt. Gongen in Kanagawa,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Yoshikazu,"Japanese, active ca. 1850–1870",,Utagawa Yoshikazu,Japanese,1845,1870,"2nd month, 1861",1861,1861,Triptych of polychrome woodblock prints; ink and color on paper,Image (a): 14 1/4 x 9 3/4 in. (36.2 x 24.8 cm) Image (b): 14 1/4 x 9 7/8 in. (36.2 x 25.1 cm) Image (c): 14 1/4 x 9 5/8 in. (36.2 x 24.4 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73449,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.164a–c,false,true,73450,Asian Art,Print,Kanagawa Gongenyama Gaikokujin yūran|Foreigners Visiting the Famous Site of Mt. Gongen in Kanagawa,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Yoshikazu,"Japanese, active ca. 1850–1870",,Utagawa Yoshikazu,Japanese,1845,1870,"2nd month, 1861",1861,1861,Triptych of polychrome woodblock prints; ink and color on paper,Image (a): 14 1/4 x 9 3/4 in. (36.2 x 24.8 cm) Image (b): 14 1/4 x 9 3/4 in. (36.2 x 24.8 cm) Image (c): 14 1/4 x 9 5/8 in. (36.2 x 24.4 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73450,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.168a–c,false,true,73455,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Yoshikazu,"Japanese, active ca. 1850–1870",,Utagawa Yoshikazu,Japanese,1845,1870,"3rd month, 1861",1861,1861,Triptych of polychrome woodblock prints; ink and color on paper,Image (a): H. 14 1/4 in. (36.2 cm); W. 9 5/8 in. (24.4 cm) Image (b): H. 14 1/4 in. (36.2 cm); W. 9 5/8 in. (24.4 cm) Image (c): 14 1/4 x 9 1/2 in. (36.2 x 24.1 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73455,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.170a–c,false,true,73458,Asian Art,Print,Amerika koku jōkisen naka no zu|Interior of an American Steamship,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Yoshikazu,"Japanese, active ca. 1850–1870",,Utagawa Yoshikazu,Japanese,1845,1870,"4th month, 1861",1861,1861,Triptych of polychrome woodblock prints; ink and color on paper,Image (a): 14 1/4 x 9 7/8 in. (36.2 x 25.1 cm) Image (b): 14 1/4 x 9 5/8 in. (36.2 x 24.4 cm) Image (c): 14 1/2 x 9 7/8 in. (36.8 x 25.1 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73458,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.173a–c,false,true,73461,Asian Art,Print,「亞墨利加國蒸氣車往来」|“America”: A Steamship in Transit,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Yoshikazu,"Japanese, active ca. 1850–1870",,Utagawa Yoshikazu,Japanese,1845,1870,1861,1861,1861,Triptych of polychrome woodblock prints; ink and color on paper,Image (a): 14 1/2 x 9 3/4 in. (36.8 x 24.8 cm) Image (b): 14 3/8 x 9 3/4 in. (36.5 x 24.8 cm) Image (c): 14 1/4 x 9 3/4 in. (36.2 x 24.8 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73461,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.174a–c,false,true,73462,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Yoshikazu,"Japanese, active ca. 1850–1870",,Utagawa Yoshikazu,Japanese,1845,1870,"2nd month, 1862",1862,1862,Triptych of polychrome woodblock prints; ink and color on paper,Image (a): 14 x 9 5/8 in. (35.6 x 24.4 cm) Image (b): 17 5/8 x 9 3/4 in. (44.8 x 24.8 cm) Image (c): 13 7/8 x 9 5/8 in. (35.2 x 24.4 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73462,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.176a–c,false,true,73464,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Yoshikazu,"Japanese, active ca. 1850–1870",,Utagawa Yoshikazu,Japanese,1845,1870,"4th month, 1864",1864,1864,Triptych of polychrome woodblock prints; ink and color on paper,Image (a): 14 x 9 7/8 in. (35.6 x 25.1 cm) Image (b): 14 x 9 7/8 in. (35.6 x 25.1 cm) Image (c): 14 x 9 3/4 in. (35.6 x 24.8 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73464,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1911,false,true,54450,Asian Art,Woodblock print,蒔絵櫛|Box with a Lacquer Comb,Japan,Edo period (1615–1868),,,,Artist,,Uematsu Tōshū,"Japanese, active late 1810s–20s",,Uematsu Tōshū,Japanese,1810,1830,1812,1812,1812,Polychrome woodblock print (surimono); ink and color on paper,5 3/8 x 6 7/8 in. (13.7 x 17.5 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54450,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1432,false,true,54423,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Sadakage,"Japanese, active mid-19th century",,Utagawa Sadakage,Japanese,1800,1899,ca. 1840,1830,1850,Polychrome woodblock print (surimono); ink and color on paper,8 1/16 x 7 1/8 in. (20.5 x 18.1 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54423,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1103,false,true,54332,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Kurokawa Michita,"Japanese, active early 19th century",,Kurokawa Michita,Japanese,1800,1899,probably 1820,1820,1820,Polychrome woodblock print (surimono); ink and color on paper,8 1/16 x 7 1/16 in. (20.5 x 17.9 cm),"Rogers Fund, 1919",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54332,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1151,false,true,54357,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kosetsu,"Japanese, active early 19th century",(?),Kosetsu,Japanese,0019,0019,ca. 1820,1810,1830,Polychrome woodblock print (surimono); ink and color on paper,8 1/4 x 7 3/16 in. (21 x 18.3 cm),"Gift of T. Ito, Chicago, Ill, 1919",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54357,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1152,false,true,54358,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kosetsu,"Japanese, active early 19th century",,Kosetsu,Japanese,0019,0019,ca. 1820,1810,1830,Polychrome woodblock print (surimono); ink and color on paper,8 3/16 x 7 1/16 in. (20.8 x 17.9 cm),"Gift of T. Ito, Chicago, Ill, 1919",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54358,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1824,false,true,56112,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyotomo,"Japanese, active early 19th century",,Torii Kiyotomo,Japanese,1815,1820,ca. 1721,1711,1731,Polychrome woodblock print; ink and color on paper,H. 12 7/16 in. (31.6 cm); W. 6 1/2 in. (16.5 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56112,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3076,false,true,56556,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,Attributed to,Torii Kiyotomo,"Japanese, active early 19th century",,Torii Kiyotomo,Japanese,1815,1820,ca. 1720,1710,1730,Polychrome woodblock print (hand colored); ink and color on paper,13 1/4 x 6 1/4 in. (33.7 x 15.9 cm),"Harris Brisbane Dick Fund and Rogers Fund, 1949",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56556,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1243,false,true,54374,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Yanagawa Shigenobu II,"Japanese, active ca. 1820s–late 1850s",,Yanagawa Shigenobu II,Japanese,1820,1859,1830,1830,1830,Polychrome woodblock print (surimono); ink and color on paper,8 5/16 x 7 1/4 in. (21.1 x 18.4 cm),"Rogers Fund, 1921",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54374,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2011.131,false,true,76561,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Gigadō Ashiyuki,"Japanese, active first half of 19th century",,Gigadō Ashiyuki,Japanese,1800,1849,1826,1826,1826,Polychrome woodblock print,Image (ôban tate-e): 14 5/8 x 10 1/4 in. (37.1 x 26 cm),"Purchase, Friends of Asian Art Gifts, in honor of James C. Y. Watt, 2011",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/76561,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2011.134,false,true,76564,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Gigadō Ashiyuki,"Japanese, active first half of 19th century",,Gigadō Ashiyuki,Japanese,1800,1849,1832,1832,1832,Polychrome woodblock print,Image (ôban tate-e): 14 3/4 x 10 1/4 in. (37.5 x 26 cm),"Purchase, Friends of Asian Art Gifts, in honor of James C. Y. Watt, 2011",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/76564,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1,false,true,37391,Asian Art,Print,鞠子|Mariko,Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1805,1795,1815,Polychrome woodblock print; ink and color on paper,4 15/16 x 14 3/16 in. (12.5 x 36 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37391,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2,false,true,36484,Asian Art,Woodblock print,"諸國瀧廻リ 相州大山ろうべんの瀧|Rōben Waterfall at Ōyama in Sagami Province (Sōshū Ōyama Rōben no taki), from the series A Tour of Waterfalls in Various Provinces (Shokoku taki meguri)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1827,1817,1837,Polychrome woodblock print; ink and color on paper,14 3/4 x 10 1/4 in. (37.5 x 26 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36484,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3,false,true,36485,Asian Art,Woodblock print,"百人一首 うはかゑとき 持統天皇|Poem by Jitō Tenno (Empress Jitō), from the series One Hundred Poems Explained by the Nurse (Hyakunin isshu uba ga etoki)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,1839,1839,1839,Polychrome woodblock print; ink and color on paper,10 1/8 x 14 3/4 in. (25.7 x 37.5 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36485,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP4,false,true,36486,Asian Art,Woodblock print,"百人一首 乳母かゑとき 柿本人麿|Poem by Kakinomoto Hitomaro, from the series One Hundred Poems Explained by the Nurse (Hyakunin isshu uba ga etoki)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,1839,1839,1839,Polychrome woodblock print; ink and color on paper,9 7/8 x 14 in. (25.1 x 35.6 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36486,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP5,false,true,36487,Asian Art,Woodblock print,"百人一首 うはかゑとき 菅家|Poem by Kanke (Sugawara Michizane), from the series One Hundred Poems Explained by the Nurse (Hyakunin isshu uba ga etoki)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1839,1829,1849,Polychrome woodblock print; ink and color on paper,10 x 14 3/5 in. (25.4 x 37.1 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36487,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP6,false,true,53920,Asian Art,Woodblock print,"百人一首 うはかゑとき 源宗于朝臣|Poem by Minamoto no Muneyuki Ason, from the series One Hundred Poems Explained by the Nurse (Hyakunin isshu uba ga etoki)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,1839,1839,1839,Polychrome woodblock print; ink and color on paper,H. 9 7/8 in. (25.1 cm); W. 14 5/8 in. (37.1 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/53920,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP7,false,true,36488,Asian Art,Woodblock print,"百人一首 姥か恵と起 大中臣能宣朝臣|Poem by Ōnakatomi no Yoshinobu Ason, from the series One Hundred Poems Explained by the Nurse (Hyakunin isshu uba ga etoki)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,1839,1839,1839,Polychrome woodblock print; ink and color on paper,10 x 14 1/8 in. (25.4 x 35.9 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36488,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP8,false,true,36489,Asian Art,Print,"諸國名橋奇覧 すほうの国きんたいはし|Kintai Bridge in Suō Province (Suō no kuni Kintaibashi), from the series Remarkable Views of Bridges in Various Provinces (Shokoku meikyō kiran)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,1827–30,1827,1830,Polychrome woodblock print; ink and color on paper,10 7/32 x 15 7/32 in. (26.0 x 38.7 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36489,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP9,false,true,36490,Asian Art,Woodblock print,"「富嶽三十六景 凱風快晴」|South Wind, Clear Sky (Gaifū kaisei), also known as Red Fuji, from the series Thirty-six Views of Mount Fuji (Fugaku sanjūrokkei)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1830–32,1820,1842,Polychrome woodblock print; ink and color on paper,9 5/8 x 14 in. (24.4 x 35.6 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36490,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP10,false,true,36491,Asian Art,Woodblock print,"冨嶽三十六景 神奈川沖浪裏|Under the Wave off Kanagawa (Kanagawa oki nami ura), also known as The Great Wave, from the series Thirty-six Views of Mount Fuji (Fugaku sanjūrokkei)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1830–32,1820,1842,Polychrome woodblock print; ink and color on paper,9 5/8 x 14 1/16 in. (24.4 x 35.7 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36491,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP11,false,true,36492,Asian Art,Woodblock print,"冨嶽三十六景 山下白雨|Storm below Mount Fuji (Sanka no haku u), from the series Thirty-six Views of Mount Fuji (Fugaku sanjūrokkei)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1830–32,1820,1842,Polychrome woodblock print; ink and color on paper,10 1/8 x 15 1/8 in. (25.7 x 38.4 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36492,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP12,false,true,36493,Asian Art,Woodblock print,"冨嶽三十六景 駿州江尻|Ejiri in Suruga Province (Sunshū Ejiri), from the series Thirty-six Views of Mount Fuji (Fugaku sanjūrokkei)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1830–32,1820,1842,Polychrome woodblock print; ink and color on paper,10 x 14 3/5 in. (25.4 x 37.1 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36493,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP13,false,true,36494,Asian Art,Woodblock print,"冨嶽三十六景 身延川裏不二|View from the Other Side of Fuji from the Minobu River (Minobugawa ura Fuji), from the series Thirty-six Views of Mount Fuji (Fugaku sanjūrokkei)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1830–32,1820,1842,Polychrome woodblock print; ink and color on paper,9 7/8 x 14 3/4 in. (25.1 x 37.5 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36494,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP14,false,true,36495,Asian Art,Woodblock print,"冨嶽三十六景 甲州三坂水面|Reflection in Lake at Misaka in Kai Province (Kōshū Misaka suimen), from the series Thirty-six Views of Mount Fuji (Fugaku sanjūrokkei)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1830–32,1820,1842,Polychrome woodblock print; ink and color on paper,9 4/5 x 14 9/16 in. (24.9 x 37.0 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36495,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP15,false,true,36496,Asian Art,Woodblock print,"冨嶽三十六景 相州七里浜|Shichirigahama in Sagami Province (Sōshū Shichirigahama), from the series Thirty-six Views of Mount Fuji (Fugaku sanjūrokkei)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1830–32,1820,1842,Polychrome woodblock print; ink and color on paper,10 1/8 x 15 in. (25.7 x 38.1 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36496,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP16,false,true,36497,Asian Art,Woodblock print,"冨嶽三十六景 武州玉川|Tama River in Musashi Province (Bushū Tamagawa), from the series Thirty-six Views of Mount Fuji (Fugaku sanjūrokkei)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1830–32,1820,1842,Polychrome woodblock print; ink and color on paper,10 1/8 x 14 3/4 in. (25.7 x 37.5 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36497,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP17,false,true,36498,Asian Art,Woodblock print,"冨嶽三十六景 相州箱根湖水|The Lake at Hakone in Sagami Province (Sōshū Hakone kosui), from the series Thirty-six Views of Mount Fuji (Fugaku sanjūrokkei)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1830–32,1820,1842,Polychrome woodblock print; ink and color on paper,10 1/8 x 15 in. (25.7 x 38.1 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36498,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP18,false,true,36499,Asian Art,Woodblock print,"冨嶽三十六景 甲州三島越|Mishima Pass in Kai Province (Kōshū Mishima goe), from the series Thirty-six Views of Mount Fuji (Fugaku sanjūrokkei)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1830–32,1820,1832,Polychrome woodblock print; ink and color on paper,9 3/5 x 14 3/8 in. (24.4 x 36.5 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36499,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP19,false,true,36500,Asian Art,Woodblock print,"冨嶽三十六景 尾州不二見原|Fujimigahara in Owari Province (Bishū Fujimigahara), from the series Thirty-six Views of Mount Fuji (Fugaku sanjūrokkei)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1830–32,1820,1842,Polychrome woodblock print; ink and color on paper,9 15/32 x 15 5/16 in. (24.1 x 38.9 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36500,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP20,false,true,36501,Asian Art,Woodblock print,"「冨嶽三十六景 相州梅沢左」|“Umezawa Manor in Sagami Province,” from the series Thirty-six Views of Mount Fuji (Fugaku sanjūrokkei, Sōshū Umezawa zai)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1830–32,1820,1842,Polychrome woodblock print; ink and color on paper,10 1/8 x 15 1/8 in. (25.7 x 38.4 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36501,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP21,false,true,36502,Asian Art,Woodblock print,"冨嶽三十六景 上総の海路|At Sea off Kazusa (Kazusa no kairo), from the series Thirty-six Views of Mount Fuji (Fugaku sanjūrokkei)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1830–32,1820,1842,Polychrome woodblock print; ink and color on paper,10 3/32 x 14 3/4 in. (25.6 x 37.5 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36502,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP22,false,true,36503,Asian Art,Woodblock print,"冨嶽三十六景 相州江の島|Enoshima in Sagami Province (Sōshū Enoshima), from the series Thirty-six Views of Mount Fuji (Fugaku sanjūrokkei)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1830–32,1820,1842,Polychrome woodblock print; ink and color on paper,10 1/8 x 14 7/8 in. (25.7 x 37.8 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36503,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP23,false,true,36504,Asian Art,Woodblock print,"冨嶽三十六景 武陽佃島|Tsukudajima in Musashi Province (Buyō Tsukudajima), from the series Thirty-six Views of Mount Fuji (Fugaku sanjūrokkei)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1830–32,1820,1842,Polychrome woodblock print; ink and color on paper,10 1/5 x 15 1/8 in. (25.9 x 38.4 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36504,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP24,false,true,36505,Asian Art,Woodblock print,"冨嶽三十六景 遠江山中|In the Mountains of Tōtomi Province (Tōtomi sanchū), from the series Thirty-six Views of Mount Fuji (Fugaku sanjūrokkei)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1830–32,1820,1842,Polychrome woodblock print; ink and color on paper,9 15/32 x 14 3/5 in. (24.1 x 37.1 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36505,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP25,false,true,36506,Asian Art,Woodblock print,"冨嶽三十六景 遠江山中|In the Mountains of Tōtomi Province (Tōtomi sanchū), from the series Thirty-six Views of Mount Fuji (Fugaku sanjūrokkei)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1830–32,1830,1832,Polychrome woodblock print; ink and color on paper,9 3/5 x 14 29/32 in. (24.4 x 37.9 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36506,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP26,false,true,36507,Asian Art,Woodblock print,"冨嶽三十六景 武陽佃島|Tsukudajima in Musashi Province (Buyō Tsukudajima), from the series Thirty-six Views of Mount Fuji (Fugaku sanjūrokkei)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1830–32,1820,1842,Polychrome woodblock print; ink and color on paper,9 15/32 x 14 3/8 in. (24.1 x 36.5 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36507,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP27,false,true,36508,Asian Art,Woodblock print,"冨嶽三十六景 常州牛掘|Ushibori in Hitachi Province (Jōshū Ushibori), from the series Thirty-six Views of Mount Fuji (Fugaku sanjūrokkei)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1830–32,1820,1842,Polychrome woodblock print; ink and color on paper,9 11/16 x 14 7/16 in. (24.6 x 36.7 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36508,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP28,false,true,36509,Asian Art,Woodblock print,"冨嶽三十六景 青山円座松|Cushion Pine at Aoyama (Aoyama enza no matsu), from the series Thirty-six Views of Mount Fuji (Fugaku sanjūrokkei)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1830–32,1820,1842,Polychrome woodblock print; ink and color on paper,10 x 15 in. (25.4 x 38.1 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36509,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP657,false,true,54301,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1801,1791,1811,Polychrome woodblock print (surimono); ink and color on paper,7 1/8 x 20 1/4 in. (18.1 x 51.4 cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54301,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP658,false,true,54302,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1801,1791,1811,Polychrome woodblock print (surimono); ink and color on paper,8 1/4 x 22 1/8 in. (21 x 56.2 cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54302,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP659,false,true,54303,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1799–1810,1799,1810,Polychrome woodblock print (surimono); ink and color on paper,8 1/4 x 22 1/8 in. (21 x 56.2 cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54303,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP660,false,true,37107,Asian Art,Woodblock print,雪松に鶴|Cranes on Branch of Snow-covered Pine,Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,late 1820s,1825,1829,Polychrome woodblock print; ink and color on paper,20 3/8 x 9 1/8 in. (51.8 x 23.2 cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911 Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37107,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP741,false,true,37189,Asian Art,Print,高根山与一右ェ門 千田川吉五郎|The Sumo Wrestlers Takaneyama Yoichiemon and Sendagawa Kichigorō,Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1790–93,1790,1793,Polychrome woodblock print; ink and color on paper,H. 12 1/3 ( 30.6 cm; W. 5 3/32 in. (12.9 cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37189,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP742,false,true,37190,Asian Art,Print,和田原甚四郎 花項山五郎吉|The Sumo Wrestlers Wadagahara Jinshirō and Kachōzan Gorokichi,Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1783,1773,1793,Polychrome woodblock print; ink and color on paper,12 1/32 x 5 3/32 in. (30.6 x 12.9 cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37190,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP743,false,true,37191,Asian Art,Print,唐子書画図|Chinese Boys Learning to Write and Paint,Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1785,1775,1795,Polychrome woodblock print; ink and color on paper,15 3/8 x 10 1/8 in. (39.1 x 25.7 cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37191,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP744,false,true,54304,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1800,1790,1810,Polychrome woodblock print (surimono); ink and color on paper,7 3/8 x 13 1/8 in. (18.7 x 33.3 cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54304,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP745,false,true,54305,Asian Art,Print,"露草に鶏と雛|Rooster, Hen and Chicken with Spiderwort",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1830–33,1830,1833,Polychrome woodblock print; ink and color on paper,Image: 9 x 11 1/2 in. (22.9 x 29.2 cm) Overall with paper mount: 12 7/8 x 18 1/2 in. (32.7 x 47 cm) Overall with matt: 15 1/2 x 22 3/4 in. (39.4 x 57.8 cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54305,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP746,false,true,54306,Asian Art,Woodblock print,葛飾北斎画 桔梗に蜻蛉|Dragonfly and Bellflower,Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,late 1820s,1820,1830,Polychrome woodblock print (surimono); ink and color on paper,9 3/4 x 14 3/16 in. (24.8 x 36 cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54306,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP747,false,true,37192,Asian Art,Print,葛飾北斎画 燕子花|Grasshopper and Iris,Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,late 1820s,1824,1829,Polychrome woodblock print; ink and color on paper,Overall: 9 3/4 x 14 3/16in. (24.8 x 36 cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37192,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP748,false,true,37193,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1835,1825,1845,Polychrome woodblock print; ink and color on paper,7 31/32 x 12 1/2 in. (20.3 x 31.8 cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37193,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1011,false,true,54315,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1800,1790,1810,Polychrome woodblock print (surimono); ink and color on paper,7 11/16 x 20 3/8 in. (19.5 x 51.8 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54315,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1012,false,true,54887,Asian Art,Print,Fuji Tohō|The Top of Mount Fuji,Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1800,1790,1810,Polychrome woodblock print; ink and color on paper,H. 10 1/8 in. (25.7 cm); W. 15 1/4 in. (38.7 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54887,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1013,false,true,54888,Asian Art,Print,今戸川|Imadogawa,Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1801–4,1801,1804,Polychrome woodblock print; ink and color on paper,H. 10 3/16 in. (25.9 cm); W. 15 1/4 in. (38.3 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54888,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1014,false,true,54889,Asian Art,Woodblock print,"百人一首 乳母かえ説 元良親王|Poem by Motoyoshi Shinnō, from the series One Hundred Poems Explained by the Nurse (Hyakunin isshu uba ga etoki)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1839,1829,1849,Polychrome woodblock print; ink and color on paper,H. 10 1/8 in. (25.7 cm); W. 14 1/2 in. (36.8 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54889,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1016,false,true,54925,Asian Art,Print,葛飾北斎画 菊に雀|Sparrows and Chrysanthemums,Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1825,1815,1835,Polychrome woodblock print; ink and color on paper,H. 8 1/2 in. (21.6 cm); W. 10 13/16 in. (27.5 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54925,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1081,false,true,55019,Asian Art,Woodblock print,"諸國瀧廻リ 下野黒髪山 きりふりの滝|Kirifuri Waterfall at Kurokami Mountain in Shimotsuke (Shimotsuke Kurokamiyama Kirifuri no taki), from the series A Tour of Waterfalls in Various Provinces (Shokoku taki meguri)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1832,1822,1842,Polychrome woodblock print; ink and color on paper,Image: 14 3/4 × 10 1/4 in. (37.5 × 26 cm),The Metropolitan Museum of Art,,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55019,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1082,false,true,55020,Asian Art,Woodblock print,"諸國瀧廻リ 木曾海道小野ノ瀑布|Ono Waterfall on the Kisokaidō (Kisokaidō Ono no bakufu), from the series A Tour of Waterfalls in Various Provinces (Shokoku taki meguri)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1832,1822,1842,Polychrome woodblock print; ink and color on paper,14 7/8 x 10 5/16 in. (37.8 x 26.2 cm),The Metropolitan Museum of Art,,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55020,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1083,false,true,55021,Asian Art,Woodblock print,"諸國瀧廻リ 東海道坂ノ下 清瀧くわんおん|Kiyotaki Kannon Waterfall at Sakanoshita on the Tōkaidō (Tōkaidō Sakanoshita Kiyotaki kannon), from the series A Tour of Waterfalls in Various Provinces (Shokoku taki meguri)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1832,1822,1842,Polychrome woodblock print; ink and color on paper,H. 14 5/8 in. (37.1 cm); W. 10 3/16 in. (25.9 cm),The Metropolitan Museum of Art,,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55021,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1084,false,true,45027,Asian Art,Woodblock print,"諸國瀧廻リ 和州吉野義経馬洗滝|The Waterfall Where Yoshitsune Washed His Horse at Yoshino in Yamato Province (Washū Yoshino Yoshitsune uma arai no taki), from the series A Tour of Waterfalls in Various Provinces (Shokoku taki meguri)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1832,1822,1842,Polychrome woodblock print; ink and color on paper,H. 14 5/8 in. (37.1 cm); W. 10 in. (25.4 cm),"Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45027,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1085,false,true,55022,Asian Art,Woodblock print,"諸國瀧廻リ 木曽路ノ奥 阿彌陀ヶ瀧|The Amida Falls in the Far Reaches of the Kisokaidō Road (Kisoji no oku Amida-ga-taki), from the series A Tour of Waterfalls in Various Provinces (Shokoku taki meguri)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1827,1817,1837,Polychrome woodblock print; ink and color on paper,Image: 14 1/2 x 10 in. (36.8 x 25.4 cm),The Metropolitan Museum of Art,,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55022,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1086,false,true,55023,Asian Art,Print,"諸國瀧廻 東都葵ヶ岡の瀧|Fall of Aoiga Oka, Yedo",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1827,1817,1837,Polychrome woodblock print; ink and color on paper,H. 14 3/4 in. (37.5 cm); W. 10 3/16 in. (25.9 cm),The Metropolitan Museum of Art,,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55023,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1087,false,true,55024,Asian Art,Woodblock print,"諸國瀧廻リ 相州大山ろうべんの瀧|Rōben Waterfall at Ōyama in Sagami Province (Sōshū Ōyama Rōben no taki), from the series A Tour of Waterfalls in Various Provinces (Shokoku taki meguri)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1827,1817,1837,Polychrome woodblock print; ink and color on paper,H. 14 3/8 in. (36.5 cm); W. 10 1/8 in. (25.7 cm),The Metropolitan Museum of Art,,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55024,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1088,false,true,55025,Asian Art,Woodblock print,"諸國瀧廻リ 美濃ノ国養老の滝|Yōrō Waterfall in Mino Province (Mino no Yōrō no taki), from the series A Tour of Waterfalls in Various Provinces (Shokoku taki meguri)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1827,1817,1837,Polychrome woodblock print; ink and color on paper,H. 14 7/16 in. (36.7 cm); W. 10 3/16 in. (25.9 cm),The Metropolitan Museum of Art,,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55025,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1117,false,true,55039,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1825,1815,1835,Polychrome woodblock print; ink and color on paper,H. 13 7/8 in. (35.2 cm); W. 4 9/16 in. (11.6 cm),"Rogers Fund, 1919",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55039,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1142,false,true,54349,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1810,1800,1820,Polychrome woodblock print (surimono); ink and color on paper,8 3/8 x 7 1/4 in. (21.3 x 18.4 cm),"Rogers Fund, 1919",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54349,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1246,false,true,54377,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1814,1804,1824,Polychrome woodblock print (surimono); ink and color on paper,8 5/16 x 5 9/16 in. (21.1 x 14.1 cm),"Rogers Fund, 1921",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54377,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1247,false,true,54378,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,1820,1820,1820,Polychrome woodblock print (surimono); ink and color on paper,8 x 10 3/8 in. (20.3 x 26.4 cm),"Rogers Fund, 1921",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54378,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1284,false,true,55223,Asian Art,Woodblock print,"冨嶽三十六景 東海道品川御殿山の不二|Fuji from Gotenyama on the Tōkaidō at Shinagawa (Tōkaidō Shinagawa Gotenyama no Fuji), from the series Thirty-six Views of Mount Fuji (Fugaku sanjūrokkei)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1830–32,1820,1842,Polychrome woodblock print; ink and color on paper,H. 9 3/4 in. (24.8 cm); W. 14 7/16 in. (36.7 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55223,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1285,false,true,55225,Asian Art,Woodblock print,"冨嶽三十六景 本所立川|Tatekawa in Honjō (Honjō Tatekawa), from the series Thirty-six Views of Mount Fuji (Fugaku sanjūrokkei)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1830–32,1820,1842,Polychrome woodblock print; ink and color on paper,9 3/4 x 14 1/8 in. (24.8 x 35.9cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55225,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1286,false,true,55226,Asian Art,Woodblock print,"冨嶽三十六景 下目黒|Lower Meguro (Shimo Meguro), from the series Thirty-six Views of Mount Fuji (Fugaku sanjūrokkei)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1830–32,1820,1842,Polychrome woodblock print; ink and color on paper,H. 9 11/16 in. (24.6 cm); W. 14 3/8 in. (36.5 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55226,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1287,false,true,55227,Asian Art,Woodblock print,"冨嶽三十六景 東都駿台|Surugadai in Edo (Tōto Sundai), from the series Thirty-six Views of Mount Fuji (Fugaku sanjūrokkei)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1830–32,1820,1842,Polychrome woodblock print; ink and color on paper,H. 9 3/4 in. (24.8 cm); W. 14 3/8 in. (36.5 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55227,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1288,false,true,55228,Asian Art,Woodblock print,"冨嶽三十六景 東海道江尻田子の浦略図|Tago Bay near Ejiri on the Tōkaidō (Tōkaidō Ejiri Tago no ura ryaku zu), from the series Thirty-six Views of Mount Fuji (Fugaku sanjūrokkei)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1830–32,1820,1842,Polychrome woodblock print; ink and color on paper,9 3/4 x 14 3/8 in. (24.8 x 36.5 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55228,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1289,false,true,55231,Asian Art,Woodblock print,"冨嶽三十六景 武州千住|Senju in Musashi Province (Bushū Senju), from the series Thirty-six Views of Mount Fuji (Fugaku sanjūrokkei)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1830–32,1820,1842,Polychrome woodblock print; ink and color on paper,H. 9 11/16 in. (24.6 cm); W. 14 3/8 in. (36.5 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55231,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1290,false,true,45218,Asian Art,Print,"風流無くてななくせ|Squeaking a Ground Cherry, from the series Seven Fashionable Useless Habits (Furyu nakute nana kuse)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1798,1788,1808,Polychrome woodblock print; ink and color on paper,14 5/16 x 9 3/4 in. (36.4 x 24.8cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45218,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1294,false,true,55236,Asian Art,Woodblock print,"冨嶽三十六景 東海道金谷の不二|Fuji Seen from Kanaya on the Tōkaidō (Tōkaidō Kanaya no Fuji), from the series Thirty-six Views of Mount Fuji (Fugaku sanjūrokkei)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1830–32,1820,1842,Polychrome woodblock print; ink and color on paper,H. 10 in. (25.4 cm); W. 14 1/2 in. (36.8 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55236,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1295,false,true,55237,Asian Art,Woodblock print,"冨嶽三十六景 江戸日本橋|Nihonbashi in Edo (Edo Nihonbashi), from the series Thirty-six Views of Mount Fuji (Fugaku sanjūrokkei)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1830–32,1820,1842,Polychrome woodblock print; ink and color on paper,H. 10 in. (25.4 cm); W. 14 1/2 in. (36.8 cm),"Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55237,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1296,false,true,55238,Asian Art,Woodblock print,"冨嶽三十六景 江都駿河町三井見世略図|Mitsui Shop at Surugachō in Edo (Edo Surugachō Mitsui mise ryaku zu), from the series Thirty-six Views of Mount Fuji (Fugaku sanjūrokkei)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1830–32,1820,1842,Polychrome woodblock print; ink and color on paper,H. 10 1/4 in. (26 cm); W. 15 1/8 in. (38.4 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55238,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1322,false,true,55281,Asian Art,Woodblock print,"冨嶽三十六景 諸人登山|Groups of Mountain Climbers (Shojin tozan), from the series Thirty-six Views of Mount Fuji (Fugaku sanjūrokkei)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1830–32,1820,1842,Polychrome woodblock print; ink and color on paper,H. 10 in. (25.4 cm); W. 14 3/4 in. (37.5 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55281,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1323,false,true,55282,Asian Art,Woodblock print,"冨嶽三十六景 東都浅草本願寺|Honganji at Asakusa in Edo (Tōto Asakusa Honganji), from the series Thirty-six Views of Mount Fuji (Fugaku sanjūrokkei)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1830–32,1820,1842,Polychrome woodblock print; ink and color on paper,H. 10 1/8 in. (25.7 cm); W. 15 1/8 in. (38.4 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55282,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1324,false,true,55283,Asian Art,Woodblock print,"冨嶽三十六景 東海道吉田|Yoshida on the Tōkaidō (Tōkaidō Yoshida), from the series Thirty-six Views of Mount Fuji (Fugaku sanjūrokkei)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1830–32,1830,1832,Polychrome woodblock print; ink and color on paper,H. 10 1/8 in. (25.7 cm); W. 15 1/4 in. (38.7 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55283,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1325,false,true,55284,Asian Art,Woodblock print,"冨嶽三十六景 相州仲原|Nakahara in Sagami Province (Sōshū Nakahara), from the series Thirty-six Views of Mount Fuji (Fugaku sanjūrokkei)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1830–32,1820,1842,Polychrome woodblock print; ink and color on paper,H. 10 in. (25.4 cm); W. 14 3/4 in. (37.5 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55284,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1326,false,true,55285,Asian Art,Woodblock print,"冨嶽三十六景 従千住花街眺望の不二|Fuji Seen in the Distance from Senju Pleasure Quarter (Senju kagai yori chōbō no Fuji), from the series Thirty-six Views of Mount Fuji (Fugaku sanjūrokkei)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1830–32,1820,1842,Polychrome woodblock print; ink and color on paper,H. 9 7/8 in. (25.1 cm); W. 14 3/4 in. (37.5 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55285,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1327,false,true,39656,Asian Art,Woodblock print,"冨嶽三十六景 甲州石班沢|Kajikazawa in Kai Province (Kōshū Kajikazawa), from the series Thirty-six Views of Mount Fuji (Fugaku sanjūrokkei)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1830–32,1820,1842,Polychrome woodblock print; ink and color on paper,10 x 15 in. (25.4 x 38.1 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/39656,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1328,false,true,55286,Asian Art,Woodblock print,"冨嶽三十六景 甲州伊沢暁|Dawn at Isawa in Kai Province (Kōshū Isawa no akatsuki), from the series Thirty-six Views of Mount Fuji (Fugaku sanjūrokkei)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1830–32,1820,1842,Polychrome woodblock print; ink and color on paper,H. 10 5/16 in. (26.2 cm); W. 15 in. (38.1 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55286,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1329,false,true,55287,Asian Art,Woodblock print,"冨嶽三十六景 江都駿河町三井見世略図|Mitsui Shop at Surugachō in Edo (Edo Surugachō Mitsui mise ryaku zu), from the series Thirty-six Views of Mount Fuji (Fugaku sanjūrokkei)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1830–32,1820,1842,Polychrome woodblock print; ink and color on paper,Oban 10 x 15 in. (25.4 x 38.1 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55287,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1330,false,true,55288,Asian Art,Woodblock print,"冨嶽三十六景 礫川雪の旦|Morning after the Snow at Koishikawa in Edo (Koishikawa yuki no ashita), from the series Thirty-six Views of Mount Fuji (Fugaku sanjūrokkei)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1830–32,1820,1842,Polychrome woodblock print; ink and color on paper,H. 10 1/8 in. (25.7 cm); W. 15 1/16 in. (38.3 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55288,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1331,false,true,37319,Asian Art,Woodblock print,"冨嶽三十六景 御厩川岸より両国橋夕陽見|Viewing the Sunset over Ryōgoku Bridge from the Onmaya Embankment (Onmayagashi yori Ryōgokubashi sekiyō o miru), from the series Thirty-six Views of Mount Fuji (Fugaku sanjūrokkei)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1830–32,1823,1843,Polychrome woodblock print; ink and color on paper,9 15/16 x 14 11/16 in. (25.2 x 37.3 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37319,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1332,false,true,55289,Asian Art,Woodblock print,"冨嶽三十六景 深川万年橋下|Under the Mannen Bridge at Fukagawa (Fukagawa Mannenbashi shita), from the series Thirty-six Views of Mount Fuji (Fugaku sanjūrokkei)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1830–32,1820,1842,Polychrome woodblock print; ink and color on paper,10 1/8 x 15 3/16 in. (25.7 x 38.6 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55289,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1333,false,true,55290,Asian Art,Woodblock print,"冨嶽三十六景 駿州片倉茶園の不二|Fuji from the Katakura Tea Fields in Suruga (Sunshū Katakura chaen no Fuji), from the series Thirty-six Views of Mount Fuji (Fugaku sanjūrokkei)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1830–32,1820,1842,Polychrome woodblock print; ink and color on paper,H. 10 1/4 in. (26 cm); W. 15 in. (38.1 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55290,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1334,false,true,37320,Asian Art,Woodblock print,"冨嶽三十六景 駿州片倉茶園の不二|Fuji from the Katakura Tea Fields in Suruga (Sunshū Katakura chaen no Fuji), from the series Thirty-six Views of Mount Fuji (Fugaku sanjūrokkei)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1830–32,1820,1842,Polychrome woodblock print; ink and color on paper,H. 9 13/16 in. (24.9 cm); W. 14 5/8 in. (37.1 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37320,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1335,false,true,37321,Asian Art,Woodblock print,"冨嶽三十六景 五百らかん寺さざゐどう|Sazai Hall at the Temple of the Five Hundred Arhats (Gohyaku Rakanji Sazaidō), from the series Thirty-six Views of Mount Fuji (Fugaku sanjūrokkei)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1830–32,1820,1842,Polychrome woodblock print; ink and color on paper,H. 10 1/16 in. (25.6 cm); W. 14 5/8 in. (37.1 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37321,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1336,false,true,37322,Asian Art,Woodblock print,"冨嶽三十六景 隠田の水車|The Waterwheel at Onden (Onden no suisha), from the series Thirty-six Views of Mount Fuji (Fugaku sanjūrokkei)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1830–32,1820,1842,Polychrome woodblock print; ink and color on paper,H. 10 in. (25.4 cm); W. 14 7/8 in. (37.8 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37322,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1337,false,true,39655,Asian Art,Woodblock print,"冨嶽三十六景 登戸浦|Noboto Bay (Noboto no ura), from the series Thirty-six Views of Mount Fuji (Fugaku sanjūrokkei)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1830–32,1820,1842,Polychrome woodblock print; ink and color on paper,10 1/4 x 15 3/16 in. (26.0 x 38.6 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/39655,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1338,false,true,55291,Asian Art,Woodblock print,"冨嶽三十六景 隅田川関屋の里|Sekiya Village on the Sumida River (Sumidagawa Sekiya no sato), from the series Thirty-six Views of Mount Fuji (Fugaku sanjūrokkei)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1830–32,1820,1842,Polychrome woodblock print; ink and color on paper,H. 10 in. (25.4 cm); W. 15 1/4 in. (38.7 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55291,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1339,false,true,37323,Asian Art,Woodblock print,"百人一首 宇波か縁説 藤原道信朝臣|Poem by Fujiwara no Michinobu Ason, from the series One Hundred Poems Explained by the Nurse (Hyakunin isshu uba ga etoki)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,1839,1839,1839,Polychrome woodblock print; ink and color on paper,H. 9 15/16 in. (25.2 cm); W. 14 3/8 in. (36.5 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37323,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1340,false,true,37324,Asian Art,Woodblock print,"百人一首 うばがゑとき 伊勢|Poem by Ise, from the series One Hundred Poems Explained by the Nurse (Hyakunin isshu uba ga etoki)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,1839,1839,1839,Polychrome woodblock print; ink and color on paper,H. 9 15/16 in. (25.2 cm); W. 14 3/8 in. (36.5 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37324,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1341,false,true,55306,Asian Art,Woodblock print,"百人一首 うはかゑとき 天智天皇|Poem by Tenchi Tennō, from the series One Hundred Poems Explained by the Nurse (Hyakunin isshu uba ga etoki)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,1839,1839,1839,Polychrome woodblock print; ink and color on paper,H. 9 15/16 in. (25.2 cm); W. 14 5/16 in. (36.4 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55306,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1380,false,true,50916,Asian Art,Woodblock print,"諸國名橋奇覧 東海道岡崎矢はぎのはし|Yahagi Bridge at Okazaki on the Tōkaidō (Tōkaidō Okazaki Yahagi no hashi), from the series Remarkable Views of Bridges in Various Provinces (Shokoku meikyō kiran)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,1827–30,1827,1830,Polychrome woodblock print; ink and color on paper,H. 9 3/4 in. (24.8 cm); W. 14 1/2 in. (36.8 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/50916,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1381,false,true,53699,Asian Art,Woodblock print,"諸國名橋奇覧 かうつけ佐野ふなはしの古づ|Old View of the Boat-bridge at Sano in Kōzuke Province (Kōzuke Sano funabashi no kozu), from the series Remarkable Views of Bridges in Various Provinces (Shokoku meikyō kiran)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,1827–30,1827,1830,Polychrome woodblock print; ink and color on paper,H. 9 3/4 in. (24.8 cm); W. 14 9/16 in. (37 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/53699,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1382,false,true,50924,Asian Art,Woodblock print,"諸國名橋奇覧 ゑちぜんふくゐの橋|Fukui Bridge in Echizen Province (Echizen Fukui no hashi), from the series Remarkable Views of Bridges in Various Provinces (Shokoku meikyō kiran)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,1827–30,1827,1830,Polychrome woodblock print; ink and color on paper,H. 9 3/4 in. (24.8 cm); W. 14 1/2 in. ( 36.8 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/50924,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1383,false,true,55369,Asian Art,Woodblock print,"冨嶽三十六景 東海道保土ケ谷|Hodogaya on the Tōkaidō (Tōkaidō Hodogaya), from the series Thirty-six Views of Mount Fuji (Fugaku sanjūrokkei)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1830–32,1830,1832,Polychrome woodblock print; ink and color on paper,H. 9 3/4 in. (24.8 cm); W. 14 9/16 in. (37 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55369,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1393,false,true,54389,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,1822,1822,1822,Polychrome woodblock print (surimono); ink and color on paper,8 1/4 x 7 3/8 in. (21 x 18.7 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54389,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1395,false,true,55402,Asian Art,Woodblock print,"雪月花 吉野|Cherry Blossoms at Yoshino (Yoshino), from the series Snow, Moon, and Flowers (Setsugekka)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1833,1823,1843,Polychrome woodblock print; ink and color on paper,H. 10 1/4 in. (26 cm); W. 14 15/16 in. (37.9 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55402,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1396,false,true,53788,Asian Art,Woodblock print,"諸國名橋奇覧 足利行道山くものかけはし|The Hanging-cloud Bridge at Mount Gyōdō near Ashikaga (Ashikaga Gyōdōzan kumo no kakehashi), from the series Remarkable Views of Bridges in Various Provinces (Shokoku meikyō kiran)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1830,1820,1840,Polychrome woodblock print; ink and color on paper,H. 9 1/2 in. (24.1 cm); W. 14 1/16 in. (35.7 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/53788,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1397,false,true,53789,Asian Art,Woodblock print,"諸國名橋奇覧 飛越の堺つりはし|The Suspension Bridge on the Border of Hida and Etchū Provinces (Hietsu no sakai tsuribashi), from the series Remarkable Views of Bridges in Various Provinces (Shokoku meikyō kiran)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1830,1820,1840,Polychrome woodblock print; ink and color on paper,H. 10 1/4 in. (26 cm); W. 15 1/8 in. (38.4 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/53789,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1398,false,true,45026,Asian Art,Woodblock print,"諸國名橋奇覧 三河の八ツ橋の古図|Ancient View of Yatsuhashi in Mikawa Province (Mikawa no Yatsuhashi no kozu), from the series Remarkable Views of Bridges in Various Provinces (Shokoku meikyō kiran)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1834,1824,1844,Polychrome woodblock print; ink and color on paper,9 1/8 x 14 5/8 in. (23.2 x 37.1 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45026,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1425,false,true,55454,Asian Art,Woodblock print,"百人一首 乳母か縁説 在原業平|Poem by Ariwara no Narihira, from the series One Hundred Poems Explained by the Nurse (Hyakunin isshu uba ga etoki)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,1839,1839,1839,Polychrome woodblock print; ink and color on paper,H. 10 1/8 in. (25.7 cm); W. 15 in. (38.1 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55454,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1426,false,true,55456,Asian Art,Woodblock print,"冨嶽三十六景 甲州犬目峠|The Inume Pass in Kai Province (Kōshū Inume tōge), from the series Thirty-six Views of Mount Fuji (Fugaku sanjūrokkei)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1830–32,1820,1842,Polychrome woodblock print; ink and color on paper,H. 9 11/16 in. (24.6 cm); W. 14 1/4 in. (36.2 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55456,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1427,false,true,55458,Asian Art,Woodblock print,"冨嶽三十六景 東海道保土ケ谷|Hodogaya on the Tōkaidō (Tōkaidō Hodogaya), from the series Thirty-six Views of Mount Fuji (Fugaku sanjūrokkei)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1830–32,1820,1842,Polychrome woodblock print; ink and color on paper,H. 9 15/16 in. (25.2 cm); W. 14 3/4 in. (37.5 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55458,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1428,false,true,55461,Asian Art,Woodblock print,"諸國瀧廻リ 東海道坂ノ下 清瀧くわんおん|Kiyotaki Kannon Waterfall at Sakanoshita on the Tōkaidō (Tōkaidō Sakanoshita Kiyotaki kannon), from the series A Tour of Waterfalls in Various Provinces (Shokoku taki meguri)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1832,1822,1842,Polychrome woodblock print; ink and color on paper,H. 14 3/4 in. (37.5 cm); W. 10 in. (25.4 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55461,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1429,false,true,55463,Asian Art,Woodblock print,"雪月花 淀川|Moonlight on the Yodo River (Yodogawa), from the series Snow, Moon, and Flowers (Setsugekka)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1833,1823,1843,Polychrome woodblock print; ink and color on paper,H. 9 5/8 in. (24.4 cm); W. 14 9/16 in. (37 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55463,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1430,false,true,55466,Asian Art,Woodblock print,"琉球八景 城嶽霊泉|The Sacred Spring at Jōgaku (Jōgaku reisen), from the series Eight Views of the Ryūkyū Islands (Ryūkyū hakkei)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,1832,1822,1842,Polychrome woodblock print; ink and color on paper,H. 10 1/16 in. (25.6 cm); 14 5/8 in. (37.1 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55466,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1456,false,true,55503,Asian Art,Woodblock print,"冨嶽三十六景 駿州大野新田|The New Fields at Ōno in Suruga Province (Sunshū Ōno shinden), from the series Thirty-six Views of Mount Fuji (Fugaku sanjūrokkei)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1830–32,1820,1842,Polychrome woodblock print; ink and color on paper,9 7/8 x 14 7/8in. (25.1 x 37.8cm),"Rogers Fund, 1923",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55503,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1457,false,true,55505,Asian Art,Woodblock print,"諸國名橋奇覧 摂洲阿治川口天保山|Tenpōzan at the Mouth of the Aji River in Settsu Province (Sesshū Ajikawaguchi Tenpōzan), from the series Remarkable Views of Bridges in Various Provinces (Shokoku meikyō kiran)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,1827–30,1827,1830,Polychrome woodblock print; ink and color on paper,H. 10 in. (25.4 cm); W. 14 5/8 in. (37.1 cm),"Rogers Fund, 1923",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55505,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1482,false,true,55571,Asian Art,Woodblock print,"冨嶽三十六景 上総の海路|At Sea off Kazusa (Kazusa no kairo), from the series Thirty-six Views of Mount Fuji (Fugaku sanjūrokkei)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1830–32,1830,1832,Polychrome woodblock print; ink and color on paper,H. 9 7/8 in. (25.1 cm); W. 15 1/16 in. (38.3 cm),"Rogers Fund, 1926",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55571,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1843,false,true,56128,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,1830s,1830,1839,Monochrome woodblock print; ink on paper,H. 10 1/2 in. (26.7 cm); W. 14 3/4 in. (37.5 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56128,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1846,false,true,56131,Asian Art,Woodblock print,"冨嶽三十六景 隠田の水車|The Waterwheel at Onden (Onden no suisha), from the series Thirty-six Views of Mount Fuji (Fugaku sanjūrokkei)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1830–32,1820,1842,Polychrome woodblock print; ink and color on paper,H. 9 3/8 in. (23.8 cm); W. 14 in. (35.6 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56131,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1847,false,true,45434,Asian Art,Woodblock print,"「富嶽三十六景 神奈川沖浪裏」|Under the Wave off Kanagawa (Kanagawa oki nami ura), also known as The Great Wave, from the series Thirty-six Views of Mount Fuji (Fugaku sanjūrokkei)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1830–32,1820,1842,Polychrome woodblock print; ink and color on paper,10 1/8 x 14 15/16 in. (25.7 x 37.9 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45434,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1848,false,true,56132,Asian Art,Woodblock print,"冨嶽三十六景 常州牛掘|Ushibori in Hitachi Province (Jōshū Ushibori), from the series Thirty-six Views of Mount Fuji (Fugaku sanjūrokkei)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1830–32,1820,1842,Polychrome woodblock print; ink and color on paper,H. 9 13/16 in. (24.9 cm); W. 13 7/8 in. (35.2 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56132,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1849,false,true,56133,Asian Art,Woodblock print,"冨嶽三十六景 信州諏訪湖|Lake Suwa in Shinano Province (Shinshū Suwako), from the series Thirty-six Views of Mount Fuji (Fugaku sanjūrokkei)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1830–32,1820,1842,Polychrome woodblock print; ink and color on paper,H. 10 1/8 in. (25.7 cm); W. 14 13/16 in. (37.6 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56133,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1850,false,true,56135,Asian Art,Woodblock print,"冨嶽三十六景 東海道品川御殿山の不二|Fuji from Gotenyama at Shinagawa on the Tōkaidō (Tōkaidō Shinagawa Gotenyama no Fuji), from the series Thirty-six Views of Mount Fuji (Fugaku sanjūrokkei)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1830–32,1830,1842,Polychrome woodblock print; ink and color on paper,H. 9 1/2 in. (24.1 cm); W. 14 1/8 in. (35.9 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56135,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1851,false,true,53700,Asian Art,Woodblock print,"「諸國名橋寄覧 東海道岡崎 矢はぎのはし」|Yahagi Bridge at Okazaki on the Tōkaidō (Tōkaidō Okazaki Yahagi no hashi), from the series Remarkable Views of Bridges in Various Provinces (Shokoku meikyō kiran)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1834,1824,1844,Polychrome woodblock print; ink and color on paper,H. 10 3/16 in. (25.9 cm); W. 15 1/16 in. (38.3 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/53700,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1852,false,true,56136,Asian Art,Woodblock print,"百人一首 乳母かゑとき 猿丸太夫|Poem by Sarumaru Dayū, from the series One Hundred Poems Explained by the Nurse (Hyakunin isshu uba ga etoki)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,1839,1839,1839,Polychrome woodblock print; ink and color on paper,H. 10 in. (25.4 cm); W. 14 3/8 in. (36.5 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56136,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1854,false,true,56172,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1800,1790,1810,Polychrome woodblock print; ink and color on paper,H. 9 9/16 in. (24.3 cm); W. 14 7/16 in. (36.7 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56172,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1859,false,true,56178,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1825,1815,1835,Sketch for a woodblock print; ink and color on paper,Image: 7 1/16 x 11 7/8 in. (17.9 x 30.2 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56178,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1860,false,true,56183,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,1820–34,1820,1834,Polychrome woodblock print; ink and color on paper,H. 9 3/4 in. (24.8 cm); W. 14 1/4 in. (36.2 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56183,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1865,false,true,54430,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,1800–1815,1800,1815,Polychrome woodblock print (surimono); ink and color on paper,7 5/16 x 19 7/8 in. (18.6 x 50.5 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54430,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1868,false,true,54433,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,1820–33,1820,1833,Polychrome woodblock print (surimono); ink and color on paper,8 9/16 x 7 1/16 in. (21.7 x 17.9 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54433,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1870,false,true,54435,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1804–13,1804,1813,Polychrome woodblock print (surimono); ink and color on paper,8 1/8 x 10 11/16 in. (20.6 x 27.1 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54435,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1873,false,true,54438,Asian Art,Print,"元禄歌仙貝合|Ashi Clam, from the series ""Genroku Kasen Kai-awase""",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,1821,1821,1821,Polychrome woodblock print; ink and color on paper,7 15/16 x 7 in. (20.2 x 17.8 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54438,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1900,false,true,54439,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,1808–27,1808,1827,Polychrome woodblock print (surimono); ink and color on paper,8 1/4 x 7 1/2 in. (21 x 19.1 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54439,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2019,false,true,54768,Asian Art,Print,初詣|Young Women Visiting a Shinto Shrine,Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,1814,1814,1814,Polychrome woodblock print (surimono); ink and color on paper,8 3/8 x 5 7/16 in. (21.3 x 13.8 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54768,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2224,false,true,53990,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,probably 1807,1807,1807,Polychrome woodblock print (surimono); ink and color on paper,5 7/16 x 11 1/8 in. (13.8 x 28.3 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/53990,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2330,false,true,54114,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,1816,1816,1816,Part of an album of woodblock prints (surimono); ink and color on paper,8 7/16 x 7 1/2 in. (21.4 x 19.1 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54114,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2403,false,true,56806,Asian Art,Woodblock print,"百人一首 乳母かゑとき 猿丸太夫|Poem by Sarumaru Dayū, from the series One Hundred Poems Explained by the Nurse (Hyakunin isshu uba ga etoki)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,1839,1839,1839,Polychrome woodblock print; ink and color on paper,10 x 14 3/8 in. (25.4 x 36.5 cm),"Gift of Louis V. Ledoux, 1931",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56806,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2546,false,true,56961,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1795,1785,1805,Polychrome woodblock print; ink and color on paper,10 x 15 1/2 in. (25.4 x 39.4 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56961,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2547,false,true,53787,Asian Art,Woodblock print,"諸國名橋奇覧 かうつけ佐野ふなはしの古づ|Old View of the Boat-bridge at Sano in Kōzuke Province (Kōzuke Sano funabashi no kozu), from the series Remarkable Views of Bridges in Various Provinces (Shokoku meikyō kiran)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1830,1820,1840,Polychrome woodblock print; ink and color on paper,10 x 14 7/8 in. (25.4 x 37.8 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/53787,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2548,false,true,56965,Asian Art,Woodblock print,"百人一首 うばがゑとき 伊勢|Poem by Ise, from the series One Hundred Poems Explained by the Nurse (Hyakunin isshu uba ga etoki)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1835,1825,1845,Polychrome woodblock print; ink and color on paper,10 1/2 x 14 3/4 in. (26.7 x 37.5 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56965,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2549,false,true,56973,Asian Art,Woodblock print,"百人一首 うばがゑとき 大納言経信|Poem by Dainagon Tsunenobu (Minamoto no Tsunenobu, Katsura no Dainagon), from the series One Hundred Poems Explained by the Nurse (Hyakunin isshu uba ga etoki)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,1839,1839,1839,Polychrome woodblock print; ink and color on paper,Image: 9 15/16 × 14 3/8 in. (25.2 × 36.5 cm) Mat: 22 3/4 × 15 1/2 in. (57.8 × 39.4 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56973,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2550,false,true,56967,Asian Art,Woodblock print,"百人一首 うはかゑとき 持統天皇|Poem by Jitō Tenno (Empress Jitō), from the series One Hundred Poems Explained by the Nurse (Hyakunin isshu uba ga etoki)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1839,1829,1849,Polychrome woodblock print; ink and color on paper,10 x 14 5/8 in. (25.4 x 37.1 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56967,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2552,false,true,56979,Asian Art,Woodblock print,"百人一首 うはかゑとき 源宗于朝臣|Poem by Minamoto no Muneyuki Ason, from the series One Hundred Poems Explained by the Nurse (Hyakunin isshu uba ga etoki)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1835,1825,1845,Polychrome woodblock print; ink and color on paper,Overall: 9 7/8 x 14 1/2 in. (25.1 x 36.8 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56979,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2553,false,true,56988,Asian Art,Woodblock print,"冨嶽三十六景 駿州江尻|Ejiri in Suruga Province (Sunshū Ejiri), from the series Thirty-six Views of Mount Fuji (Fugaku sanjūrokkei)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1830–32,1820,1842,Polychrome woodblock print; ink and color on paper,9 5/8 x 14 3/4 in. (24.4 x 37.5 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56988,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2554,false,true,53845,Asian Art,Woodblock print,"冨嶽三十六景 御厩川岸より両国橋夕陽見|Viewing the Sunset over Ryōgoku Bridge from the Onmayagashi Embankment (Onmayagashi yori Ryōgokubashi sekiyō o miru), from the series Thirty-six Views of Mount Fuji (Fugaku sanjūrokkei)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1830–32,1820,1842,Woodblock print; ink and color on paper,H. 10 1/8 in. (25.7 cm); W. 14 7/8 in. (37.8 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/53845,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2555,false,true,56990,Asian Art,Woodblock print,"冨嶽三十六景 礫川雪の旦|Morning after the Snow at Koishikawa in Edo (Koishikawa yuki no ashita), from the series Thirty-six Views of Mount Fuji (Fugaku sanjūrokkei)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1830–32,1820,1842,Polychrome woodblock print; ink and color on paper,10 x 15 in. (25.4 x 38.1 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56990,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2556,false,true,56786,Asian Art,Woodblock print,"冨嶽三十六景 甲州三島越|Mishima Pass in Kai Province (Kōshū Mishima goe), from the series Thirty-six Views of Mount Fuji (Fugaku sanjūrokkei)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1830–32,1820,1842,Polychrome woodblock print; ink and color on paper,9 3/4 × 14 3/4 in. (24.8 × 37.5 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56786,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2557,false,true,56787,Asian Art,Woodblock print,"冨嶽三十六景 尾州不二見原|Fujimigahara in Owari Province (Bishū Fujimigahara), from the series Thirty-six Views of Mount Fuji (Fugaku sanjūrokkei)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1830–32,1820,1842,Polychrome woodblock print; ink and color on paper,H. 10 in. (25.4 cm); W. 15 in. (38.1 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56787,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2558,false,true,56994,Asian Art,Woodblock print,"冨嶽三十六景 甲州犬目峠|The Inume Pass in Kai Province (Kōshū Inume tōge), from the series Thirty-six Views of Mount Fuji (Fugaku sanjūrokkei)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1830–32,1820,1842,Polychrome woodblock print; ink and color on paper,10 x 15 in. (25.4 x 38.1 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56994,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2559,false,true,56996,Asian Art,Woodblock print,"冨嶽三十六景 相州江の島|Enoshima in Sagami Province (Sōshū Enoshima), from the series Thirty-six Views of Mount Fuji (Fugaku sanjūrokkei)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1830–32,1820,1842,Polychrome woodblock print; ink and color on paper,10 1/8 x 14 3/4 in. (25.7 x 37.5 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56996,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2560,false,true,56998,Asian Art,Woodblock print,"冨嶽三十六景 武州玉川|Tama River in Musashi Province (Bushū Tamagawa), from the series Thirty-six Views of Mount Fuji (Fugaku sanjūrokkei)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1830–32,1820,1842,Polychrome woodblock print; ink and color on paper,10 x 15 in. (25.4 x 38.1 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56998,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2561,false,true,57000,Asian Art,Woodblock print,"冨嶽三十六景 東海道江尻田子の浦略図|Tago Bay near Ejiri on the Tōkaidō (Tōkaidō Ejiri Tago no ura ryaku zu), from the series Thirty-six Views of Mount Fuji (Fugaku sanjūrokkei)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1830–32,1820,1842,Polychrome woodblock print; ink and color on paper,9 7/8 x 14 5/8 in. (25.1 x 37.1 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/57000,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2562,false,true,57003,Asian Art,Woodblock print,"冨嶽三十六景 東海道保土ケ谷|Hodogaya on the Tōkaidō (Tōkaidō Hodogaya), from the series Thirty-six Views of Mount Fuji (Fugaku sanjūrokkei)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1830–32,1820,1842,Polychrome woodblock print; ink and color on paper,10 1/8 x 15 in. (25.7 x 38.1 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/57003,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2563,false,true,57004,Asian Art,Woodblock print,"冨嶽三十六景 武陽佃島|Tsukudajima in Musashi Province (Buyō Tsukudajima), from the series Thirty-six Views of Mount Fuji (Fugaku sanjūrokkei)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1830–32,1820,1842,Polychrome woodblock print; ink and color on paper,10 x 15 1/4 in. (25.4 x 38.7 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/57004,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2564,false,true,39798,Asian Art,Woodblock print,"冨嶽三十六景 信州諏訪湖|Lake Suwa in Shinano Province (Shinshū Suwako), from the series Thirty-six Views of Mount Fuji (Fugaku sanjūrokkei)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1830–32,1820,1842,Polychrome woodblock print; ink and color on paper,9 3/4 x 14 7/8 in. (24.8 x 37.8 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/39798,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2565,false,true,56785,Asian Art,Woodblock print,"冨嶽三十六景 常州牛掘|Ushibori in Hitachi Province (Jōshū Ushibori), from the series Thirty-six Views of Mount Fuji (Fugaku sanjūrokkei)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1830–32,1820,1842,Polychrome woodblock print; ink and color on paper,H. 10 in. (25.4 cm); W. 15 in. (38.1 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56785,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2566,false,true,57005,Asian Art,Woodblock print,"冨嶽三十六景 本所立川|Tatekawa in Honjō (Honjō Tatekawa), from the series Thirty-six Views of Mount Fuji (Fugaku sanjūrokkei)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1830–32,1820,1842,Polychrome woodblock print; ink and color on paper,10 x 15 in. (25.4 x 38.1 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/57005,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2567,false,true,57006,Asian Art,Woodblock print,"冨嶽三十六景 山下白雨|Storm below Mount Fuji (Sanka no haku u), from the series Thirty-six Views of Mount Fuji (Fugaku sanjūrokkei)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1830–32,1820,1842,Polychrome woodblock print; ink and color on paper,9 3/4 x 14 3/4 in. (24.8 x 37.5 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/57006,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2568,false,true,57007,Asian Art,Woodblock print,"冨嶽三十六景 凱風快晴|South Wind, Clear Sky (Gaifū kaisei), also known as Red Fuji, from the series Thirty-six Views of Mount Fuji (Fugaku sanjūrokkei)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1830–32,1820,1842,Polychrome woodblock print; ink and color on paper,Oban nishiki-e triptych: 10 x 15 in. (25.4 x 38.1 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/57007,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2569,false,true,39799,Asian Art,Woodblock print,"「富嶽三十六景 神奈川沖浪裏」|Under the Wave off Kanagawa (Kanagawa oki nami ura), or The Great Wave, from the series Thirty-six Views of Mount Fuji (Fugaku sanjūrokkei)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1830–32,1820,1842,Polychrome woodblock print; ink and color on paper,10 x 15 in. (25.4 x 38.1 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/39799,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2570,false,true,45494,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",", New York, NY (1936; sold to MMA).",Katsushika Hokusai,Japanese,1760,1849,ca. 1796,1786,1806,Polychrome woodblock print; ink and color on paper,8 1/2 x 6 in. (21.6 x 15.2 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45494,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2571,false,true,57008,Asian Art,Woodblock print,松本幸四郎|Matsumoto Koshiro IV as Tsurifune no Sabu,Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1796,1786,1806,Polychrome woodblock print; ink and color on paper,10 3/4 x 5 3/4 in. (27.3 x 14.6 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/57008,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2572,false,true,57009,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1800,1790,1810,Polychrome woodblock print; ink and color on paper,7 7/8 x 12 7/8 in. (20 x 32.7 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/57009,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2573,false,true,54188,Asian Art,Print,Horimono-shi|職人三十六番|The Metal Carver,Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,1802,1802,1802,Polychrome woodblock print (surimono); ink and color on paper,5 1/2 x 7 1/2 in. (14 x 19.1 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54188,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2574,false,true,54189,Asian Art,Print,Tachi-shi|職人三十六番|The Swordsmith,Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,1802,1802,1802,Polychrome woodblock print (surimono); ink and color on paper,5 3/8 x 7 3/8 in. (13.7 x 18.7 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54189,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2575,false,true,54191,Asian Art,Print,Hata-ori|職人三十六歌仙|The Weaving Factory,Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1802,1792,1812,Polychrome woodblock print (surimono); ink and color on paper,5 3/8 x 7 3/8 in. (13.7 x 18.7 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54191,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2576,false,true,54192,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,1841,1841,1841,Polychrome woodblock print (surimono); ink and color on paper,5 3/8 x 7 1/4 in. (13.7 x 18.4 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54192,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2577,false,true,54193,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1800,1790,1810,Polychrome woodblock print (surimono); ink and color on paper,5 1/2 x 10 1/2 in. (14 x 26.7 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54193,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2578,false,true,54194,Asian Art,Print,"Sakura-gai|元禄歌仙貝合|Cherry Shell, from the series Genroku Poetry Shell Games",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,probably 1821,1821,1821,Polychrome woodblock print (surimono); ink and color on paper,7 3/4 x 6 7/8 in. (19.7 x 17.5 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54194,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2579,false,true,54195,Asian Art,Print,Miyako-gai|元禄歌仙貝合|Miyako Shell,Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,probably 1821,1821,1821,Polychrome woodblock print (surimono); ink and color on paper,7 7/8 x 7 in. (20 x 17.8 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54195,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2580,false,true,57010,Asian Art,Print,新柳橋の白雨|Shower at the New Yanagi Bridge,Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,1806,1806,1806,Woodblock print ; ink and color on paper,7 7/8 x 11 3/4 in. (20 x 29.8 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/57010,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2581,false,true,56727,Asian Art,Print,"冨嶽三十六景 甲州石班沢|Kajikazawa in Kai Province (Kōshū Kajikazawa), from the series Thirty-six Views of Mount Fuji (Fugaku sanjūrokkei)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1830–32,1830,1832,Polychrome woodblock print; ink and color on paper,10 x 15 1/8 in. (25.4 x 38.4 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56727,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2831,false,true,57069,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,1760–1849,1760,1849,Polychrome woodblock print; ink and color on paper,14 x 6 3/8 in. (35.6 x 16.2 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/57069,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2903,false,true,56018,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,1781–1801,1781,1801,Polychrome woodblock print; ink and color on paper,11 3/4 x 5 1/4 in. (29.8 x 13.3 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56018,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2904,false,true,56019,Asian Art,Print,"二代中村野塩|The Actor Nakamura Noshio II, in Female Role, Holding a Shakuhachi (Bamboo Flute)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,"1796 (Kansei, 6th year)",1796,1796,Polychrome woodblock print; ink and color on paper,11 3/4 x 5 1/4 in. (29.8 x 13.3 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56019,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2912,false,true,49934,Asian Art,Woodblock print,"琉球八景 泉崎夜月|Evening Moon at Izumizaki (Izaumizaki yagetsu), from the series Eight Views of the Ryūkyū Islands (Ryūkyū hakkei)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1832,1822,1842,Polychrome woodblock print; ink and color on paper,9 7/8 x 14 5/8 in. (25.1 x 37.1 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/49934,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2913,false,true,49935,Asian Art,Woodblock print,"琉球八景 中島蕉園|Banana Garden at Nakashima (Nakashima shōen), from the series Eight Views of the Ryūkyū Islands (Ryūkyū hakkei)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1832,1822,1842,Polychrome woodblock print; ink and color on paper,9 7/8 x 14 5/8 in. (25.1 x 37.1 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/49935,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2914,false,true,49936,Asian Art,Woodblock print,"琉球八景 粂村竹籬|Bamboo Hedge at Kumemura (Kumemura chikuri), from the series Eight Views of the Ryūkyū Islands (Ryūkyū hakkei)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1832,1822,1842,Polychrome woodblock print; ink and color on paper,9 7/8 x 14 5/8 in. (25.1 x 37.1 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/49936,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2915,false,true,49937,Asian Art,Woodblock print,"琉球八景 城嶽霊泉|The Sacred Spring at Jōgaku (Jōgaku reisen), from the series Eight Views of the Ryūkyū Islands (Ryūkyū hakkei)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1832,1822,1842,Polychrome woodblock print; ink and color on paper,9 7/8 x 14 5/8 in. (25.1 x 37.1 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/49937,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2916,false,true,49938,Asian Art,Woodblock print,"琉球八景 臨海潮(湖)聲|Sound of the Lake at Rinkai (Rinkai kosei), from the series Eight Views of the Ryūkyū Islands (Ryūkyū hakkei)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1832,1822,1842,Polychrome woodblock print; ink and color on paper,Oban 9 3/4 x 14 1/2 in. (24.8 x 36.8 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/49938,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2917,false,true,49939,Asian Art,Woodblock print,"琉球八景 筍崖夕照|Evening Glow at Jungai (Jungai sekishō), from the series Eight Views of the Ryūkyū Islands (Ryūkyū hakkei)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1832,1822,1842,Polychrome woodblock print; ink and color on paper,9 7/8 x 14 1/2 in. (25.1 x 36.8 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/49939,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2918,false,true,49940,Asian Art,Woodblock print,"琉球八景 長虹秋霽|Autumn Sky at Chōkō (Chōkō shūsei), from the series Eight Views of the Ryūkyū Islands (Ryūkyū hakkei)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1832,1822,1842,Polychrome woodblock print; ink and color on paper,9 7/8 x 14 5/8 in. (25.1 x 37.1 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/49940,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2919,false,true,49941,Asian Art,Woodblock print,"琉球八景 龍洞松濤|Pines and Waves at Ryūtō (Ryūtō shōtō), from the series Eight Views of the Ryūkyū Islands (Ryūkyū hakkei)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1832,1822,1842,Polychrome woodblock print; ink and color on paper,9 7/8 x 14 5/8 in. (25.1 x 37.1 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/49941,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2920,false,true,55739,Asian Art,Print,"千絵の海 下総登戸|Noboto at Shimōsa (Shimōsa Noboto), from the series One Thousand Pictures of the Sea (Chie no umi)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,1832–33,1832,1833,Polychrome woodblock print; ink and color on paper,H. 7 1/4 in. (18.4 cm); W. 10 in. (25.4 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55739,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2921,false,true,44981,Asian Art,Woodblock print,"雪月花 隅田|Snow on the Sumida River (Sumida), from the series, Snow, Moon, and Flowers (Setsugekka)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1833,1823,1843,Polychrome woodblock print; ink and color on paper,9 3/4 x 14 1/2 in. (24.8 x 36.8 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/44981,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2922,false,true,56137,Asian Art,Woodblock print,"雪月花 淀川|Moonlight on the Yodo River (Yodogawa), from the series Snow, Moon, and Flowers (Setsugekka)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1833,1823,1843,Polychrome woodblock print; ink and color on paper,9 7/8 x 14 7/8 in. (25.1 x 37.8 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56137,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2923,false,true,56138,Asian Art,Woodblock print,"雪月花 吉野|Cherry Blossoms at Yoshino (Yoshino), from the series Snow, Moon, and Flowers (Setsugekka)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1833,1823,1843,Polychrome woodblock print; ink and color on paper,9 7/8 x 14 5/8 in. (25.1 x 37.1 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56138,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2924,false,true,56139,Asian Art,Woodblock print,"諸國瀧廻リ 下野黒髪山 きりふりの滝|Kirifuri Waterfall at Kurokami Mountain in Shimotsuke (Shimotsuke Kurokamiyama Kirifuri no taki), from the series A Tour of Waterfalls in Various Provinces (Shokoku taki meguri)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1832,1827,1837,Polychrome woodblock print; ink and color on paper,14 5/8 x 10 5/16 in. (37.1 x 26.2 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56139,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2925,false,true,56140,Asian Art,Woodblock print,"諸國瀧廻リ 木曾海道小野ノ瀑布|Ono Waterfall on the Kisokaidō (Kisokaidō Ono no bakufu), from the series A Tour of Waterfalls in Various Provinces (Shokoku taki meguri)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1833,1823,1843,Polychrome woodblock print; ink and color on paper,14 3/4 x 10 1/4 in. (37.5 x 26 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56140,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2926,false,true,56141,Asian Art,Woodblock print,"諸國瀧廻リ 東海道坂ノ下 清瀧くわんおん|Kiyotaki Kannon Waterfall at Sakanoshita on the Tōkaidō (Tōkaidō Sakanoshita Kiyotaki kannon), from the series A Tour of Waterfalls in Various Provinces (Shokoku taki meguri)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1832,1822,1842,Polychrome woodblock print; ink and color on paper,14 5/8 x 10 1/4 in. (37.1 x 26 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56141,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2927,false,true,56142,Asian Art,Woodblock print,"諸國瀧廻リ 和州吉野義経馬洗滝|The Waterfall Where Yoshitsune Washed His Horse at Yoshino in Yamato Province (Washū Yoshino Yoshitsune uma arai no taki), from the series A Tour of Waterfalls in Various Provinces (Shokoku taki meguri)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1832,1822,1842,Polychrome woodblock print; ink and color on paper,14 5/8 x 10 1/4 in. (37.1 x 26 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56142,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2928,false,true,56143,Asian Art,Woodblock print,"諸國瀧廻リ 木曽路ノ奥 阿彌陀ヶ瀧|The Amida Falls in the Far Reaches of the Kisokaidō Road (Kisoji no oku Amida-ga-taki), from the series A Tour of Waterfalls in Various Provinces (Shokoku taki meguri)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1832,1830,1833,Polychrome woodblock print; ink and color on paper,Image: 14 3/4 x 10 1/4 in. (37.5 x 26 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56143,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2930,false,true,56145,Asian Art,Woodblock print,"諸國瀧廻リ 相州大山ろうべんの瀧|Rōben Waterfall at Ōyama in Sagami Province (Sōshū Ōyama Rōben no taki), from the series A Tour of Waterfalls in Various Provinces (Shokoku taki meguri)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,1760–1849,1760,1849,Polychrome woodblock print; ink and color on paper,14 1/2 x 10 in. (36.8 x 25.4 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56145,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2931,false,true,56146,Asian Art,Woodblock print,"諸國瀧廻リ 美濃ノ国養老の滝|Yōrō Waterfall in Mino Province (Mino no Yōrō no taki), from the series A Tour of Waterfalls in Various Provinces (Shokoku taki meguri)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1832,1822,1842,Polychrome woodblock print; ink and color on paper,14 5/8 x 10 1/4 in. (37.1 x 26 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56146,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2932,false,true,56147,Asian Art,Woodblock print,"百人一首 宇波か縁説 参儀等|Poem by Sangi Hitoshi (Minamoto no Hitoshi), from the series One Hundred Poems Explained by the Nurse (Hyakunin isshu uba ga etoki)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,1760–1849,1760,1849,Polychrome woodblock print; ink and color on paper,9 7/8 x 14 1/4 in. (25.1 x 36.2 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56147,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2933,false,true,56148,Asian Art,Woodblock print,"百人一首 乳母かゑとき 柿の本人麿|Poem by Kakinomoto Hitomaro, from the series One Hundred Poems Explained by the Nurse (Hyakunin isshu uba ga etoki)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,1760–1849,1760,1849,Polychrome woodblock print; ink and color on paper,10 1/8 x 14 5/8 in. (25.7 x 37.1 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56148,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2934,false,true,56149,Asian Art,Woodblock print,"百人一首 乳母か縁説 中納言家持|Poem by Chūnagon Yakamochi (Ōtomo no Yakamochi), from the series One Hundred Poems Explained by the Nurse (Hyakunin isshu uba ga etoki)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,1760–1849,1760,1849,Polychrome woodblock print; ink and color on paper,9 3/4 x 14 3/8 in. (24.8 x 36.5 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56149,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2935,false,true,56150,Asian Art,Woodblock print,"百人一首 うはかゑとき 源宗于朝臣|Poem by Minamoto no Muneyuki Ason, from the series One Hundred poems Explained by the Nurse (Hyakunin isshu uba ga etoki)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1835,1825,1845,Polychrome woodblock print; ink and color on paper,9 5/8 x 14 3/8 in. (24.4 x 36.5 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56150,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2936,false,true,56151,Asian Art,Woodblock print,"百人一首 宇波か縁説 藤原道信朝臣|Poem by Fujiwara no Michinobu Ason, from the series One Hundred Poems Explained by the Nurse (Hyakunin isshu uba ga etoki)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,1760–1845,1760,1845,Polychrome woodblock print; ink and color on paper,10 3/8 x 14 7/8 in. (26.4 x 37.8 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56151,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2937,false,true,56157,Asian Art,Woodblock print,"百人一首 宇波か縁説 権中納言定家|Poem by Gon-Chūnagon Sadaie, from the series One Hundred Poems Explained by the Nurse (Hyakunin isshu uba ga etoki)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,1760–1849,1760,1849,Polychrome woodblock print; ink and color on paper,10 1/4 x 15 in. (26 x 38.1 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56157,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2938,false,true,56171,Asian Art,Woodblock print,"百人一首 うはかゑとき 文屋朝康|Poem by Funya no Asayasu, from the series One Hundred Poems Explained by a Nurse (Hyakunin isshu ubaga etoki)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,1760–1849,1760,1849,Polychrome woodblock print; ink and color on paper,9 3/4 x 14 3/8 in. (24.8 x 36.5 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56171,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2939,false,true,45261,Asian Art,Woodblock print,"百人一首 うはか縁説 清原深養父|Poem by Kiyohara no Fukayabu, from the series One Hundred Poems Explained by the Nurse (Hyakunin isshu uba ga etoki)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1835,1825,1845,Polychrome woodblock print; ink and color on paper,10 5/8 x 14 1/2 in. (27 x 36.8 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45261,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2940,false,true,56175,Asian Art,Woodblock print,"百人一首 宇波かゑとき 安部仲麿|Poem by Abe no Nakamaro, from the series One Hundred Poems Explained by the Nurse (Hyakunin isshu uba ga etoki)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,1760–1849,1760,1849,Polychrome woodblock print; ink and color on paper,10 3/8 x 14 7/8 in. (26.4 x 37.8 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56175,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2941,false,true,55734,Asian Art,Woodblock print,"百人一首 乳母か絵とき 参議篁|Poem by Sangi no Takamura (Ono no Takamura), from the series One Hundred Poems Explained by the Nurse (Hyakunin isshu uba ga etoki)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1835,1825,1845,Polychrome woodblock print; ink and color on paper,Oban 10 1/4 x 14 3/4 in. (26 x 37.5 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55734,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2942,false,true,53191,Asian Art,Woodblock print,"諸國名橋奇覧 足利行道山くものかけはし|The Hanging-cloud Bridge at Mount Gyōdō near Ashikaga (Ashikaga Gyōdōzan kumo no kakehashi), from the series Remarkable Views of Bridges in Various Provinces (Shokoku meikyō kiran)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,1760–1849,1760,1849,Polychrome woodblock print; ink and color on paper,H. 10 1/8 in. (25.7 cm); W. 15 1/8 in. (38.4 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/53191,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2943,false,true,53698,Asian Art,Woodblock print,"諸國名橋奇覧 かうつけ佐野ふなはしの古づ|Old View of the Boat-bridge at Sano in Kōzuke Province (Kōzuke Sano funabashi no kozu), from the series Remarkable Views of Bridges in Various Provinces (Shokoku meikyō kiran)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1830,1820,1840,Polychrome woodblock print; ink and color on paper,10 1/8 x 15 1/2 in. (25.7 x 39.4 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/53698,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2944,false,true,53192,Asian Art,Woodblock print,"諸國名橋奇覧 飛越の堺つりはし|The Suspension Bridge on the Border of Hida and Etchū Provinces (Hietsu no sakai tsuribashi), from the series Remarkable Views of Bridges in Various Provinces (Shokoku meikyō kiran)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1830,1820,1840,Polychrome woodblock print; ink and color on paper,H. 10 1/8 in. (25.7 cm); W. 15 1/8 in. (38.4 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/53192,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2945,false,true,53791,Asian Art,Woodblock print,"諸國名橋奇覧 東海道岡崎矢はぎのはし|Yahagi Bridge at Okazaki on the Tōkaidō (Tōkaidō Okazaki Yahagi no hashi), from the series Remarkable Views of Bridges in Various Provinces (Shokoku meikyō kiran)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1830,1820,1840,Polychrome woodblock print; ink and color on paper,10 1/8 x 15 1/8 in. (25.7 x 38.4 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/53791,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2946,false,true,53790,Asian Art,Woodblock print,"諸國名橋奇覧 すほうの国きんたいはし|Kintai Bridge in Suō Province (Suō no kuni Kintaibashi), from the series Remarkable Views of Bridges in Various Provinces (Shokoku meikyō kiran)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1830,1820,1840,Polychrome woodblock print; ink and color on paper,10 1/4 x 15 1/8 in. (26 x 38.4 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/53790,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2947,false,true,53792,Asian Art,Woodblock print,"諸國名橋奇覧 かめゐど天神たいこはし|The Arched Bridge at Kameido Tenjin Shrine (Kameido Tenjin Taikobashi), from the series Remarkable Views of Bridges in Various Provinces (Shokoku meikyō kiran)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1830,1820,1840,Polychrome woodblock print; ink and color on paper,10 1/8 x 15 1/8 in. (25.7 x 38.4 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/53792,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2948,false,true,53786,Asian Art,Woodblock print,"諸國名橋奇覧 山城あらし山吐月橋 |Togetsu Bridge at Arashiyama in Yamashiro, from the series Remarkable Views of Bridges in Various Provinces (Shokoku meikyō kiran)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1830,1820,1840,Polychrome woodblock print; ink and color on paper,10 3/8 x 15 1/4 in. (26.4 x 38.7 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/53786,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2949,false,true,53193,Asian Art,Woodblock print,"諸國名橋奇覧 摂洲天満橋|Tenman Bridge at Settsu Province (Sesshū Tenmanbashi), from the series Remarkable Views of Bridges in Various Provinces (Shokoku meikyō kiran)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,1760–1849,1760,1849,Polychrome woodblock print; ink and color on paper,H. 10 1/8 in. (25.7 cm); W. 15 1/8 in. (38.4 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/53193,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2950,false,true,56202,Asian Art,Woodblock print,"諸國名橋奇覧 摂洲阿治川口天保山|Tenpōzan at the Mouth of the Aji River in Settsu Province (Sesshū Ajikawaguchi Tenpōzan), from the series Remarkable Views of Bridges in Various Provinces (Shokoku meikyō kiran)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,1760–1849,1760,1849,Polychrome woodblock print; ink and color on paper,10 1/4 x 15 1/8 in. (26 x 38.4 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56202,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2951,false,true,53793,Asian Art,Woodblock print,"諸國名橋奇覧 三河の八ツ橋の古図|Ancient View of Yatsuhashi in Mikawa Province (Mikawa no Yatsuhashi no kozu), from the series Remarkable Views of Bridges in Various Provinces (Shokoku meikyō kiran)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1830,1820,1840,Polychrome woodblock print; ink and color on paper,9 7/8 x 14 7/8 in. (25.1 x 37.8 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/53793,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2952,false,true,56210,Asian Art,Woodblock print,"諸國名橋奇覧 ゑちぜんふくゐの橋|Fukui Bridge in Echizen Province (Echizen Fukui no hashi), from the series Remarkable Views of Bridges in Various Provinces (Shokoku meikyō kiran)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1830,1820,1840,Polychrome woodblock print; ink and color on paper,10 1/8 x 15 1/8 in. (25.7 x 38.4 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56210,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2953,false,true,55735,Asian Art,Woodblock print,"冨嶽三十六景 駿州江尻|Ejiri in Suruga Province (Sunshū Ejiri), from the series Thirty-six Views of Mount Fuji (Fugaku sanjūrokkei)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1830–32,1830,1832,Polychrome woodblock print; ink and color on paper,Oban 9 7/8 x 14 3/4 in. (25.1 x 37.5 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55735,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2954,false,true,56212,Asian Art,Woodblock print,"冨嶽三十六景 駿州大野新田|The New Fields at Ōno in Suruga Province (Sunshū Ōno shinden), from the series Thirty-six Views of Mount Fuji (Fugaku sanjūrokkei)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1830–32,1820,1842,Polychrome woodblock print; ink and color on paper,10 x 14 3/4 in. (25.4 x 37.5 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56212,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2955,false,true,56213,Asian Art,Woodblock print,"冨嶽三十六景 駿州片倉茶園の不二|Fuji from the Katakura Tea Fields in Suruga (Sunshū Katakura chaen no Fuji), from the series Thirty-six Views of Mount Fuji (Fugaku sanjūrokkei)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1830–32,1820,1842,Polychrome woodblock print; ink and color on paper,9 5/8 x 14 3/4 in. (24.4 x 37.5 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56213,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2956,false,true,56214,Asian Art,Woodblock print,"冨嶽三十六景 尾州不二見原|Fujimigahara in Owari Province (Bishū Fujimigahara), from the series Thirty-six Views of Mount Fuji (Fugaku sanjūrokkei)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1830–32,1830,1832,Polychrome woodblock print; ink and color on paper,10 1/16 x 14 7/8 in. (25.6 x 37.8 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56214,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2957,false,true,56215,Asian Art,Woodblock print,"冨嶽三十六景 礫川雪の旦|Morning after the Snow at Koishikawa in Edo (Koishikawa yuki no ashita), from the series Thirty-six Views of Mount Fuji (Fugaku sanjūrokkei)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1830–32,1820,1842,Polychrome woodblock print; ink and color on paper,10 1/8 x 15 in. (25.7 x 38.1 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56215,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2958,false,true,56216,Asian Art,Woodblock print,"冨嶽三十六景 登戸浦|Noboto Bay (Noboto no ura), from the series Thirty-six Views of Mount Fuji (Fugaku sanjūrokkei)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1830–32,1820,1842,Polychrome woodblock print; ink and color on paper,9 3/4 x 14 3/8 in. (24.8 x 36.5 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56216,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2959,false,true,56217,Asian Art,Woodblock print,"冨嶽三十六景 身延川裏不二|View from the Other Side of Fuji from the Minobu River (Minobugawa ura Fuji), from the series Thirty-six Views of Mount Fuji (Fugaku sanjūrokkei)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1830–32,1820,1842,Polychrome woodblock print; ink and color on paper,10 x 14 5/8 in. (25.4 x 37.1 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56217,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2960,false,true,55736,Asian Art,Woodblock print,"冨嶽三十六景 凱風快晴|South Wind, Clear Sky (Gaifū kaisei), also known as Red Fuji, from the series Thirty-six Views of Mount Fuji (Fugaku sanjūrokkei)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1830–32,1830,1832,Polychrome woodblock print; ink and color on paper,Oban 10 x 14 7/8 in. (25.4 x 37.8 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55736,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2961,false,true,56229,Asian Art,Print,"冨嶽三十六景 山下白雨|Storm below Mount Fuji (Sanka no haku u), from the series Thirty-six Views of Mount Fuji (Fugaku sanjūrokkei)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1830–32,1830,1832,Polychrome woodblock print; ink and color on paper,10 x 14 3/4 in. (25.4 x 37.5 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56229,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2962,false,true,56235,Asian Art,Woodblock print,"冨嶽三十六景 諸人登山|Groups of Mountain Climbers (Shojin tozan), from the series Thirty-six Views of Mount Fuji (Fugaku sanjūrokkei)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1830–32,1830,1832,Polychrome woodblock print; ink and color on paper,9 1/2 x 14 5/8 in. (24.1 x 37.1 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56235,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2963,false,true,56238,Asian Art,Woodblock print,"冨嶽三十六景 上総の海路|At Sea off Kazusa (Kazusa no kairo), from the series Thirty-six Views of Mount Fuji (Fugaku sanjūrokkei)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1830–32,1820,1842,Polychrome woodblock print; ink and color on paper,9 1/2 x 14 7/8 in. (24.1 x 37.8 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56238,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2964,false,true,56239,Asian Art,Woodblock print,"冨嶽三十六景 常州牛掘|Ushibori in Hitachi Province (Jōshū Ushibori), from the series Thirty-six Views of Mount Fuji (Fugaku sanjūrokkei)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1830–32,1820,1842,Polychrome woodblock print; ink and color on paper,10 1/4 x 14 7/8 in. (26 x 37.8 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56239,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2965,false,true,56240,Asian Art,Woodblock print,"冨嶽三十六景 信州諏訪湖|Lake Suwa in Shinano Province (Shinshū Suwako), from the series Thirty-six Views of Mount Fuji (Fugaku sanjūrokkei)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1830–32,1820,1842,Polychrome woodblock print; ink and color on paper,10 1/4 x 15 1/8 in. (26 x 38.4 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56240,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2966,false,true,56241,Asian Art,Woodblock print,"冨嶽三十六景 遠江山中|In the Mountains of Tōtomi Province (Tōtomi sanchū), from the series Thirty-six Views of Mount Fuji (Fugaku sanjūrokkei)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1830–32,1820,1842,Polychrome woodblock print; ink and color on paper,10 3/8 x 15 1/4 in. (26.4 x 38.7 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56241,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2967,false,true,56242,Asian Art,Woodblock print,"冨嶽三十六景 隠田の水車|The Waterwheel at Onden (Onden no suisha), from the series Thirty-six Views of Mount Fuji (Fugaku sanjūrokkei)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1830–32,1820,1842,Polychrome woodblock print; ink and color on paper,10 1/4 x 15 1/4 in. (26 x 38.7 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56242,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2968,false,true,45030,Asian Art,Woodblock print,"冨嶽三十六景 甲州犬目峠|The Inume Pass in Kai Province (Kōshū Inume tōge), from the series Thirty-six Views of Mount Fuji (Fugaku sanjūrokkei)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1831–32,1820,1842,Polychrome woodblock print; ink and color on paper,9 7/8 x 14 7/8 in. (25.1 x 37.8 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45030,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2969,false,true,54868,Asian Art,Woodblock print,"冨嶽三十六景 甲州三坂水面|Reflection in Lake at Misaka in Kai Province (Kōshū Misaka suimen), from the series Thirty-six Views of Mount Fuji (Fugaku sanjūrokkei",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1830–32,1820,1842,Polychrome woodblock print; ink and color on paper,9 5/8 x 14 3/4 in. (24.4 x 37.5 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54868,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2970,false,true,56346,Asian Art,Woodblock print,"冨嶽三十六景 甲州三島越|Mishima Pass in Kai Province (Kōshū Mishima goe), from the series Thirty-six Views of Mount Fuji (Fugaku sanjūrokkei",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1830–32,1830,1830,Polychrome woodblock print; ink and color on paper,9 5/8 x 14 7/8 in. (24.4 x 37.8 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56346,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2971,false,true,56349,Asian Art,Woodblock print,"冨嶽三十六景 甲州伊沢暁|Dawn at Isawa in Kai Province (Kōshū Isawa no akatsuki), from the series Thirty-six Views of Mount Fuji (Fugaku sanjūrokkei)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1830–32,1820,1842,Polychrome woodblock print; ink and color on paper,9 7/8 x 14 5/8 in. (25.1 x 37.1 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56349,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2972,false,true,56353,Asian Art,Woodblock print,"冨嶽三十六景 神奈川沖浪裏|Under the Wave off Kanagawa (Kanagawa oki nami ura), also known as The Great Wave, from the series Thirty-six Views of Mount Fuji (Fugaku sanjūrokkei)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1830–32,1820,1842,Polychrome woodblock print; ink and color on paper,9 7/8 x 14 7/8 in. (25.1 x 37.8 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56353,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2973,false,true,56357,Asian Art,Woodblock print,"冨嶽三十六景 東海道保土ケ谷|Hodogaya on the Tōkaidō (Tōkaidō Hodogaya), from the series Thirty-six Views of Mount Fuji (Fugaku sanjūrokkei)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1830–32,1820,1842,Polychrome woodblock print; ink and color on paper,9 5/8 x 14 7/8 in. (24.4 x 37.8 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56357,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2974,false,true,56360,Asian Art,Woodblock print,"冨嶽三十六景 東海道吉田|Yoshida on the Tōkaidō (Tōkaidō Yoshida), from the series Thirty-six Views of Mount Fuji (Fugaku sanjūrokkei)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1830–32,1820,1842,Polychrome woodblock print; ink and color on paper,10 3/8 x 15 1/8 in. (26.4 x 38.4 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56360,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2975,false,true,56365,Asian Art,Woodblock print,"冨嶽三十六景 東海道金谷の不二|Fuji Seen from Kanaya on the Tōkaidō (Tōkaidō Kanaya no Fuji), from the series Thirty-six Views of Mount Fuji (Fugaku sanjūrokkei",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1830–32,1830,1832,Polychrome woodblock print; ink and color on paper,10 1/4 x 15 1/4 in. (26 x 38.7 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56365,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2976,false,true,56373,Asian Art,Woodblock print,"冨嶽三十六景 東海道江尻田子の浦略図|Tago Bay near Ejiri on the Tōkaidō (Tōkaidō Ejiri Tago no ura ryaku zu), from the series Thirty-six Views of Mount Fuji (Fugaku sanjūrokkei)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1830–32,1830,1832,Polychrome woodblock print; ink and color on paper,9 3/4 x 14 3/4 in. (24.8 x 37.5 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56373,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2977,false,true,56376,Asian Art,Woodblock print,"冨嶽三十六景 相州江の島|Enoshima in Sagami Province (Sōshū Enoshima), from the series Thirty-six Views of Mount Fuji (Fugaku sanjūrokkei)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1830–32,1820,1842,Polychrome woodblock print; ink and color on paper,9 7/8 x 14 3/4 in. (25.1 x 37.5 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56376,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2978,false,true,56384,Asian Art,Woodblock print,"冨嶽三十六景 相州仲原|Nakahara in Sagami Province (Sōshū Nakahara), from the series Thirty-six Views of Mount Fuji (Fugaku sanjūrokkei)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1830–32,1830,1832,Polychrome woodblock print; ink and color on paper,10 x 15 in. (25.4 x 38.1 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56384,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2979,false,true,55737,Asian Art,Woodblock print,"冨嶽三十六景 相州七里浜|Shichirigahama in Sagami Province (Sōshū Shichirigahama), from the series Thirty-six Views of Mount Fuji (Fugaku sanjūrokkei)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1830–32,1830,1832,Polychrome woodblock print; ink and color on paper,Oban 10 x 14 3/4 in. (25.4 x 37.5 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55737,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2980,false,true,56385,Asian Art,Woodblock print,"冨嶽三十六景 相州箱根湖水|The Lake at Hakone in Sagami Province (Sōshū Hakone kosui), from the series Thirty-six Views of Mount Fuji (Fugaku sanjūrokkei)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1830–32,1820,1842,Polychrome woodblock print; ink and color on paper,9 3/4 x 14 7/8 in. (24.8 x 37.8 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56385,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2981,false,true,55738,Asian Art,Woodblock print,"冨嶽三十六景 相州梅沢左|Umezawa Manor in Sagami Province (Sōshū Umezawa zai), from the series Thirty-six Views of Mount Fuji (Fugaku sanjūrokkei)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1830–32,1830,1832,Polychrome woodblock print; ink and color on paper,Oban 10 1/8 x 15 in. (25.7 x 38.1 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55738,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2982,false,true,56386,Asian Art,Woodblock print,"冨嶽三十六景 本所立川|Tatekawa in Honjō (Honjō Tatekawa), from the series Thirty-six Views of Mount Fuji (Fugaku sanjūrokkei)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1830–32,1830,1832,Polychrome woodblock print; ink and color on paper,10 1/16 x 15 in. (25.6 x 38.1 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56386,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2983,false,true,56387,Asian Art,Woodblock print,"冨嶽三十六景 深川万年橋下|Under the Mannen Bridge at Fukagawa (Fukagawa Mannenbashi shita), from the series Thirty-six Views of Mount Fuji (Fugaku sanjūrokkei)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1830–32,1830,1832,Polychrome woodblock print; ink and color on paper,10 x 14 3/8 in. (25.4 x 36.5 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56387,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2984,false,true,55740,Asian Art,Woodblock print,"冨嶽三十六景 五百らかん寺さざゐどう|Sazai Hall at the Temple of the Five Hundred Arhats (Gohyaku Rakanji Sazaidō), from the series Thirty-six Views of Mount Fuji (Fugaku sanjūrokkei)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1830–32,1830,1832,Polychrome woodblock print; ink and color on paper,Oban 10 1/4 x 15 1/4 in. (26 x 38.7 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55740,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2985,false,true,56388,Asian Art,Woodblock print,"冨嶽三十六景 青山円座松|Cushion Pine at Aoyama (Aoyama enza no matsu), from the series Thirty-six Views of Mount Fuji (Fugaku sanjūrokkei)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1830–32,1820,1842,Polychrome woodblock print; ink and color on paper,9 5/8 x 14 3/4 in. (24.4 x 37.5 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56388,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2986,false,true,39800,Asian Art,Woodblock print,"冨嶽三十六景 甲州石班沢|Kajikazawa in Kai Province (Kōshū Kajikazawa), from the series Thirty-six Views of Mount Fuji (Fugaku sanjūrokkei)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1830–32,1820,1842,Polychrome woodblock print; ink and color on paper,10 1/4 x 15 1/8 in. (26 x 38.4 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/39800,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2987,false,true,56389,Asian Art,Woodblock print,"冨嶽三十六景 下目黒|Lower Meguro (Shimo Meguro), from the series Thirty-six Views of Mount Fuji (Fugaku sanjūrokkei)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1830–32,1820,1842,Polychrome woodblock print; ink and color on paper,10 1/4 x 15 1/4 in. (26 x 38.7 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56389,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2988,false,true,56390,Asian Art,Woodblock print,"冨嶽三十六景 武州千住|Senju in Musashi Province (Bushū Senju), from the series Thirty-six Views of Mount Fuji (Fugaku sanjūrokkei)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1830–32,1820,1842,Polychrome woodblock print; ink and color on paper,9 5/8 x 15 in. (24.4 x 38.1 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56390,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2989,false,true,56391,Asian Art,Woodblock print,"冨嶽三十六景 従千住花街眺望の不二|Fuji Seen in the Distance from Senju Pleasure Quarter (Senju kagai yori chōbō no Fuji), from the series Thirty-six Views of Mount Fuji (Fugaku sanjūrokkei)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1830–32,1820,1842,Polychrome woodblock print; ink and color on paper,10 1/4 x 15 1/4 in. (26 x 38.7 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56391,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2990,false,true,55741,Asian Art,Woodblock print,"冨嶽三十六景 武陽佃島|Tsukudajima in Musashi Province (Buyō Tsukudajima), from the series Thirty-six Views of Mount Fuji (Fugaku sanjūrokkei)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1830–32,1830,1832,Polychrome woodblock print; ink and color on paper,Oban 10 1/8 x 15 in. (25.7 x 38.1 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55741,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2991,false,true,56392,Asian Art,Woodblock print,"冨嶽三十六景 武州玉川|Tama River in Musashi Province (Bushū Tamagawa), from the series Thirty-six Views of Mount Fuji (Fugaku sanjūrokkei)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1830–32,1820,1842,Polychrome woodblock print; ink and color on paper,9 7/8 x 14 7/8 in. (25.1 x 37.8 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56392,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2992,false,true,56393,Asian Art,Woodblock print,"冨嶽三十六景 東海道品川御殿山の不二|Fuji from Gotenyama at Shinagawa on the Tōkaidō (Tōkaidō Shinagawa Gotenyama no Fuji), from the series Thirty-six Views of Mount Fuji (Fugaku sanjūrokkei)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1830–32,1820,1842,Polychrome woodblock print; ink and color on paper,9 7/8 x 14 7/8 in. (25.1 x 37.8 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56393,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2993,false,true,56394,Asian Art,Woodblock print,"冨嶽三十六景 江戸日本橋|Nihonbashi in Edo (Edo Nihonbashi), from the series Thirty-six Views of Mount Fuji (Fugaku sanjūrokkei)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1830–32,1820,1842,Polychrome woodblock print; ink and color on paper,9 7/8 x 14 3/4 in. (25.1 x 37.5 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56394,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2994,false,true,53692,Asian Art,Woodblock print,"冨嶽三十六景 江都駿河町三井見世略図|Mitsui Shop at Surugachō in Edo (Edo Surugachō Mitsui mise ryaku zu), from the series Thirty-six Views of Mount Fuji (Fugaku sanjūrokkei)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1830–32,1820,1842,Polychrome woodblock print; ink and color on paper,10 1/8 x 14 7/8 in. (25.7 x 37.8 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/53692,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2995,false,true,56395,Asian Art,Woodblock print,"冨嶽三十六景 東都駿台|Surugadai in Edo (Tōto Sundai), from the series Thirty-six Views of Mount Fuji (Fugaku sanjūrokkei)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1830–32,1820,1842,Polychrome woodblock print; ink and color on paper,9 3/4 x 14 3/4 in. (24.8 x 37.5 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56395,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2996,false,true,56396,Asian Art,Woodblock print,"冨嶽三十六景 東都浅草本願寺|Honganji at Asakusa in Edo (Tōto Asakusa Honganji), from the series Thirty-six Views of Mount Fuji (Fugaku sanjūrokkei)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1830–32,1820,1842,Polychrome woodblock print; ink and color on paper,10 x 15 in. (25.4 x 38.1 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56396,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2997,false,true,37362,Asian Art,Woodblock print,"冨嶽三十六景 御厩川岸より両国橋夕陽見|Viewing the Sunset over Ryōgoku Bridge from the Onmaya Embankment (Onmayagashi yori Ryōgokubashi sekiyō o miru), from the series Thirty-six Views of Mount Fuji (Fugaku sanjūrokkei)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1830–31,1820,1842,Polychrome woodblock print; ink and color on paper,9 5/8 x 14 3/4 in. (24.4 x 37.5 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37362,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2998,false,true,55742,Asian Art,Woodblock print,"冨嶽三十六景 隅田川関屋の里|Sekiya Village on the Sumida River (Sumidagawa Sekiya no sato), from the series Thirty-six Views of Mount Fuji (Fugaku sanjūrokkei)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1830–32,1830,1832,Polychrome woodblock print; ink and color on paper,Oban 10 1/4 x 15 1/8 in. (26 x 38.4 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55742,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3001,false,true,54217,Asian Art,Print,衣食住|Attire,Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,1760–1849,1760,1849,Polychrome woodblock print (surimono); ink and color on paper,5 5/8 x 7 1/2 in. (14.3 x 19.1 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54217,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3003,false,true,54219,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,1760–1849,1760,1849,Polychrome woodblock print (surimono); ink and color on paper,5 1/8 x 6 13/16 in. (13 x 17.3 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54219,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3138,false,true,56686,Asian Art,Print,"冨嶽三十六景 武州玉川|Fuji—The Tama River, Musashi Province, from the series Thirty-six Views of Mount Fuji (Fugaku sanjūrokkei)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1830–32,1820,1842,Polychrome woodblock print; ink and color on paper,10 1/8 x 14 3/4 in. (25.7 x 37.5 cm),"Gift of Francis M. Weld, 1948",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56686,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3516,false,true,44900,Asian Art,Print,詩歌写真鏡 李白|Ri Haku from the series Mirrors of Japanese and Chinese Poems (Shiika shashin kyō),Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1832,1822,1842,Polychrome woodblock print; ink and color on paper,20 3/8 x 9 in. (51.8 x 22.9 cm),"The Harry G. C. Packard Collection of Asian Art, Gift of Harry G. C. Packard, and Purchase, Fletcher, Rogers, Harris Brisbane Dick, and Louis V. Bell Funds, Joseph Pulitzer Bequest, and The Annenberg Fund Inc. Gift, 1975",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/44900,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP29a–e,false,true,36483,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1804,1794,1814,Pentaptych of polychrome woodblock prints; ink and color on paper,"Oban, pentaptych: 14 15/32 x 48 1/2 in. (36.8 x 123.2 cm)","Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36483,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP30,false,true,36510,Asian Art,Print,東都名所 御殿山之夕桜|Evening Cherries on Gotem Yama,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,1830,1830,1830,Polychrome woodblock print; ink and color on paper,9 x 13 15/16 in. (22.9 x 35.4 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36510,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP31,false,true,36511,Asian Art,Print,金沢八景 乙艫帰帆|Otomo Kihan,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1836,1826,1846,Polychrome woodblock print; ink and color on paper,9 1/4 x 14 15/32 in. (23.5 x 36.8 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36511,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP32,false,true,36512,Asian Art,Print,江戸近郊八景之内 羽根田落雁|Haneda Rakugan,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1835,1825,1845,Polychrome woodblock print; ink and color on paper,9 15/32 x 13 27/32 in. (24.1 x 35.2 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36512,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP33,false,true,36513,Asian Art,Print,木曽海道六拾九次之内 大井|Ōi Station,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1835,1825,1845,Polychrome woodblock print; ink and color on paper,9 1/4 x 14 15/32 in. (23.5 x 36.8 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36513,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP35,false,true,36515,Asian Art,Print,"京都名所之内 あらし山満花|Cherry Blossoms at Arashiyama, from the series Famous Places of Kyōto",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1834,1824,1844,Polychrome woodblock print; ink and color on paper,9 3/8 x 14 15/32 in. (23.8 x 36.8 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36515,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP36,false,true,36516,Asian Art,Print,京都名所之内 八瀬之里|Yase no Sato,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1834,1824,1844,Polychrome woodblock print; ink and color on paper,10 7/32 x 15 1/8 in. (26.0 x 38.4 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36516,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP37,false,true,36517,Asian Art,Print,京都名所之内 淀川|Yodogawa,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1834,1824,1844,Polychrome woodblock print; ink and color on paper,9 29/32 x 14 7/8 in. (25.2 x 37.8 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36517,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP38,false,true,36518,Asian Art,Print,東海道五十三次 見附 天竜川|Mitsukei Tenryugawa,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1834,1824,1844,Polychrome woodblock print; ink and color on paper,9 3/4 x 14 29/32 in. (24.8 x 37.9 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36518,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP40,false,true,36520,Asian Art,Print,東海道五十三次 三島 朝霧|Morning Mist at Mishima,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1833,1823,1843,Polychrome woodblock print; ink and color on paper,H. 9 1/2 in. (24.1 cm); W. 14 7/8 in. (37.8 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36520,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP41,false,true,36521,Asian Art,Print,"東海道五十三次・庄野 白雨|Sudden Shower at Shōno, from the series Fifty-three Stations of the Tōkaidō",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,1834–35,1834,1835,Polychrome woodblock print; ink and color on paper,Image: 9 3/4 x 14 1/4 in. (24.8 x 36.2 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36521,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP42,false,true,36522,Asian Art,Print,木曽海道六拾九次之内 和田|Wada Station,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1835,1825,1845,Polychrome woodblock print; ink and color on paper,9 15/32 x 14 1/4 in. (24.1 x 36.2 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36522,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP43,false,true,36523,Asian Art,Print,木曽海道六拾九次之内 下諏訪|Shimono Suwa Station,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1835,1825,1845,Polychrome woodblock print; ink and color on paper,9 9/16 x 14 3/8 in. (24.3 x 36.5 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36523,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP44,false,true,36524,Asian Art,Print,吾妻の森|View of Azuma Wood,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1858,1848,1868,Polychrome woodblock print (surimono); ink and color on paper,20 3/8 x 7 7/32 in. (51.8 x 18.4 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36524,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP45,false,true,45321,Asian Art,Print,Asakusa Kinryuzan shita Azumabashi uchu nozomi|東都名所 浅草金龍山下東橋雨中望|View of the Asakusa Kinryuzan Temple from the Azuma Bridge in the Rain,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1840,1830,1850,"Polychrome woodblock print; ink and color on paper, tanzaku format",14 9/16 x 5 in. (37.0 x 12.7 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45321,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP46,false,true,36525,Asian Art,Print,東都名所 佃島海辺朧月|Tsukudajima Kaihin Rōgetsu,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1836,1826,1846,Polychrome woodblock print; ink and color on paper,13 15/16 x 4 3/4 in. (35.4 x 12.1 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36525,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP47,false,true,36526,Asian Art,Print,東都名所 新吉原衣紋阪秋月|Shin Yoshiwara Emonzaka Aki no Tsuki,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1836,1826,1846,Polychrome woodblock print; ink and color on paper,14 3/4 x 4 7/8 in. (37.5 x 12.4 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36526,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP48,false,true,36527,Asian Art,Print,近江八景之内 石山秋月|The Autumn Moon at Ishiyama on Lake Biwa,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1835,1825,1845,Polychrome woodblock print; ink and color on paper,9 7/8 x 14 15/32 in. (25.1 x 36.8 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36527,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP49,false,true,36528,Asian Art,Print,"近江八景之内 瀬田夕照|Seta no Sekisho. Sunset, Seta. Lake Biwa",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1835,1825,1845,Polychrome woodblock print; ink and color on paper,8 23/32 x 13 5/8 in. (22.2 x 34.6 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36528,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP50,false,true,36529,Asian Art,Print,"近江八景之内 粟津晴嵐|Clearing Weather at Awazu, Lake Biwa",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1835,1825,1845,Polychrome woodblock print; ink and color on paper,8 23/32 x 13 3/8 in. (22.2 x 34.0 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36529,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP51,false,true,36530,Asian Art,Print,"近江八景之内 堅田落雁|Geese Alighting at Katada, Lake Biwa",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1835,1825,1845,Polychrome woodblock print; ink and color on paper,8 13/16 x 13 5/8 in. (22.4 x 34.6 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36530,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP52,false,true,36531,Asian Art,Print,"近江八景之内 唐崎夜雨|Night Rain at Karasaki, from the series Eight Views of Ō-mi",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1835,1825,1845,Polychrome woodblock print; ink and color on paper,Image: 8 3/4 × 13 5/8 in. (22.2 × 34.6 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36531,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP53,false,true,36532,Asian Art,Woodblock print,"近江八景之内 三井晩鐘|Vesper Bell at Mii Temple, Lake Biwa",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1835,1825,1845,Polychrome woodblock print; ink and color on paper,8 23/32 x 13 1/2 in. (22.2 x 34.3 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36532,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP54,false,true,36533,Asian Art,Print,"近江八景之内 比良暮雪|Evening Snow on Hira, Lake Biwa",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1835,1825,1845,Polychrome woodblock print; ink and color on paper,8 27/32 x 13 23/32 in. (22.5 x 34.9 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36533,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP55,false,true,36534,Asian Art,Woodblock print,"近江八景之内 矢橋帰帆|Sailing Boats Returning to Yabase, Lake Biwa",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1835,1825,1845,Polychrome woodblock print; ink and color on paper,9 9/16 x 14 in. (24.3 x 35.6 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36534,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP56,false,true,36535,Asian Art,Print,江戸名所 御茶之水|Ochanomizu,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,"dated 11th month, Ox year, 1853",11,1853,Polychrome woodblock print; ink and color on paper,Oban 9 1/8 x 14 in. (23.2 x 35.6 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36535,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP57,false,true,36536,Asian Art,Print,"富士三十六景 東都両ごく|Toto, Ryogoku, from the series Thirty-six Views of Mount Fuji (Fugaku sanjūrokkei)",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,"4th month, Horse year 1858",1858,1858,Polychrome woodblock print; ink and color on paper,14 15/32 x 9 15/32 in. (36.8 x 24.1 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36536,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP58,false,true,36537,Asian Art,Print,"富士三十六景 武蔵越かや在|View of Mount Fuji from Koshigaya, Province of Musashi (Musashi, Koshigaya Zai), from the series Thirty-six Views of Mount Fuji (Fugaku sanjūrokkei)",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,"4th month, Horse year 1858",1858,1858,Polychrome woodblock print; ink and color on paper,13 27/32 x 9 1/4 in. (35.2 x 23.5 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36537,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP59,false,true,36538,Asian Art,Print,"富士三十六景 相模七里ケ浜|View of Mount Fuji from Seven-ri Beach, Province of Sagami (Sōshū: Shichi-ri ga hama), from the series Thirty-six Views of Mount Fuji (Fugaku sanjūrokkei)",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,"dated 4th month, Horse year 1858",1858,1858,Polychrome woodblock print; ink and color on paper,13 27/32 x 9 1/4 in. (35.2 x 23.5 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36538,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP60,false,true,36539,Asian Art,Print,"名所江戸百景 浅草田圃酉の町詣|Revelers Returned from the Tori no Machi Festival at Asakusa, from the series One Hundred Famous Views of Edo",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,1857,1857,1857,Polychrome woodblock print; ink and color on paper,13 1/16 x 8 11/16 in. (33.2 x 22.1 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36539,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP61,false,true,36540,Asian Art,Print,"Ukeji, Akiba no Keidai|名所江戸百景 請地秋葉の境内|Inside the Akiba Shrine at Ukeji",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,"8th month, Snake year 1857",1857,1857,Polychrome woodblock print; ink and color on paper,13 1/4 x 8 23/32 in. (33.7 x 22.2 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36540,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP62,false,true,36541,Asian Art,Print,"名所江戸百景 市中繁栄七夕祭|The Tanabata Festival, from the series One Hundred Famous Views of Edo",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,1857,1857,1857,Polychrome woodblock print; ink and color on paper,14 x 9 3/5 in. (35.6 x 24.4 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36541,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP63,false,true,36542,Asian Art,Woodblock print,"「名所江戸百景 蓑輪 金杉 三河しま」|“Minowa, Kanasugi at Mikawashima,” from the series One Hundred Famous Views of Edo (Meisho Edo hyakkei, Minowa Kanasugi, Mikawashima)",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,snake year 1857,1857,1857,Polychrome woodblock print; ink and color on paper,13 1/4 x 8 17/32 in. (33.7 x 21.7 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36542,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP64,false,true,36543,Asian Art,Print,"名所江戸百景 真崎辺より水神の森内川関屋の里を見る|The Suijin Temple Grove, Uchikawa, and the Village of Sekiya",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,8th month 1857,1857,1857,Polychrome woodblock print; ink and color on paper,H. 13 1/8 in. (33.3 cm); W. 8 11/16 in. (22.1 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36543,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP65,false,true,36544,Asian Art,Print,名所江戸百景 廓中東雲|Kakuchu Shinonome,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,"4th month, Snake year 1857",1857,1857,Polychrome woodblock print; ink and color on paper,14 3/5 x 9 7/8 in. (37.1 x 25.1 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36544,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP66,false,true,36545,Asian Art,Woodblock print,Kisojo no San Sen|木曽路之山川|The Kiso Mountains in Snow,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,"dated 8th month of the Snake year, 1857",1857,1857,Triptych of woodblock prints; ink and color on paper,Each H. 14 1/4 in. (36.2 cm); W. 9 3/4 in. (24.8 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36545,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP67,false,true,36546,Asian Art,Print,東海道五十三次 品川|Shinagawa Station,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1838,1828,1848,Polychrome woodblock print; ink and color on paper,8 1/2 x 6 1/2 in. (21.6 x 16.5 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36546,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP68,false,true,36547,Asian Art,Print,東海道五十三次 川崎|Kawasaki,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1838,1828,1848,Polychrome woodblock print; ink and color on paper,8 1/2 x 6 1/2 in. (21.6 x 16.5 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36547,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP69,false,true,36548,Asian Art,Print,東海道五十三次 神奈川|Kanagawa,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1838,1828,1848,Polychrome woodblock print; ink and color on paper,8 1/2 x 6 1/2 in. (21.6 x 16.5 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36548,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP70,false,true,36549,Asian Art,Print,東海道五十三次 保土ヶ谷|Hodogaya,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1838,1828,1848,Polychrome woodblock print; ink and color on paper,8 1/2 x 6 1/2 in. (21.6 x 16.5 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36549,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP71,false,true,36550,Asian Art,Print,東海道五十三次 戸塚|Totsuka,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1838,1828,1848,Polychrome woodblock print; ink and color on paper,8 1/2 x 6 1/2 in. (21.6 x 16.5 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36550,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP72,false,true,36551,Asian Art,Print,東海道五十三次 藤沢|Fujisawa,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1838,1828,1848,Polychrome woodblock print; ink and color on paper,8 1/2 x 6 1/2 in. (21.6 x 16.5 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36551,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP73,false,true,36552,Asian Art,Print,東海道五十三次 平塚|Hiratsuka,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1838,1828,1848,Polychrome woodblock print; ink and color on paper,8 1/2 x 6 1/2 in. (21.6 x 16.5 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36552,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP74,false,true,36553,Asian Art,Print,東海道五十三次 大磯|Ōiso,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1838,1828,1848,Polychrome woodblock print; ink and color on paper,8 1/2 x 6 1/2 in. (21.6 x 16.5 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36553,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP75,false,true,36554,Asian Art,Print,東海道五十三次 小田原|Odawara,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1838,1828,1848,Polychrome woodblock print; ink and color on paper,8 1/2 x 6 1/2 in. (21.6 x 16.5 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36554,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP76,false,true,36555,Asian Art,Print,東海道五十三次 箱根|Hakone,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1838,1828,1848,Polychrome woodblock print; ink and color on paper,8 1/2 x 6 1/2 in. (21.6 x 16.5 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36555,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP77,false,true,36556,Asian Art,Print,"東海道五十三次 三島|Mishima, from the series Fifty-three Stations of the Tōkaidō Road (Tōkaidō gojūsan tsugi, Mishima), also known as the Kyōka (Witty Verse) Tōkaidō",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1838,1828,1848,Polychrome woodblock print; ink and color on paper,8 1/2 x 6 1/2 in. (21.6 x 16.5 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36556,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP78,false,true,36557,Asian Art,Print,東海道五十三次 沼津|Numazu,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1838,1828,1848,Polychrome woodblock print; ink and color on paper,8 1/2 x 6 1/2 in. (21.6 x 16.5 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36557,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP79,false,true,36558,Asian Art,Print,東海道五十三次 原|Hara,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1838,1828,1848,Polychrome woodblock print; ink and color on paper,8 1/2 x 6 1/2 in. (21.6 x 16.5 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36558,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP80,false,true,36559,Asian Art,Woodblock print,東海道五十三次 蒲原|Kambara,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1838,1828,1848,Polychrome woodblock print; ink and color on paper,8 1/2 x 6 1/2 in. (21.6 x 16.5 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36559,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP81,false,true,36560,Asian Art,Print,東海道五十三次 興津|Okitsu,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1838,1828,1848,Polychrome woodblock print; ink and color on paper,8 1/2 x 6 1/2 in. (21.6 x 16.5 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36560,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP82,false,true,36561,Asian Art,Print,東海道五十三次 江尻|Ejiri,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1838,1828,1848,Polychrome woodblock print; ink and color on paper,8 1/2 x 6 1/2 in. (21.6 x 16.5 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36561,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP83,false,true,36562,Asian Art,Print,東海道五十三次 府中|Fuchu,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1838,1828,1848,Polychrome woodblock print; ink and color on paper,8 1/2 x 6 1/2 in. (21.6 x 16.5 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36562,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP84,false,true,36563,Asian Art,Print,東海道五十三次 鞠子|Mariko,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1838,1828,1848,Polychrome woodblock print; ink and color on paper,8 1/2 x 6 1/2 in. (21.6 x 16.5 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36563,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP85,false,true,36564,Asian Art,Print,東海道五十三次 岡部|Okabe,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1838,1828,1848,Polychrome woodblock print; ink and color on paper,8 1/2 x 6 1/2 in. (21.6 x 16.5 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36564,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP86,false,true,36565,Asian Art,Print,東海道五十三次 藤枝|Fujieda,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1838,1828,1848,Polychrome woodblock print; ink and color on paper,8 1/2 x 6 1/2 in. (21.6 x 16.5 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36565,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP87,false,true,36566,Asian Art,Print,東海道五十三次 金谷|Kanaya,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1838,1828,1848,Polychrome woodblock print; ink and color on paper,8 1/2 x 6 1/2 in. (21.6 x 16.5 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36566,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP88,false,true,36567,Asian Art,Print,東海道五十三次 袋井|Fukuroi,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1838,1828,1848,Polychrome woodblock print; ink and color on paper,8 1/2 x 6 1/2 in. (21.6 x 16.5 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36567,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP89,false,true,36568,Asian Art,Print,東海道五十三次 見附|Mitsuki,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1838,1828,1848,Polychrome woodblock print; ink and color on paper,8 1/2 x 6 1/2 in. (21.6 x 16.5 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36568,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP90,false,true,36569,Asian Art,Print,東海道五十三次 舞阪|Maizaka Station,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1838,1828,1848,Polychrome woodblock print; ink and color on paper,8 1/2 x 6 1/2 in. (21.6 x 16.5 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36569,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP91,false,true,36570,Asian Art,Print,東海道五十三次 荒井|Arai,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1838,1828,1848,Polychrome woodblock print; ink and color on paper,8 1/2 x 6 1/2 in. (21.6 x 16.5 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36570,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP92,false,true,36571,Asian Art,Print,東海道五十三次 白須賀|Shirasuka,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1838,1828,1848,Polychrome woodblock print; ink and color on paper,8 1/2 x 6 1/2 in. (21.6 x 16.5 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36571,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP93,false,true,36572,Asian Art,Print,東海道五十三次 赤坂|Akasaka,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1838,1828,1848,Polychrome woodblock print; ink and color on paper,8 1/2 x 6 1/2 in. (21.6 x 16.5 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36572,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP94,false,true,36573,Asian Art,Print,東海道五十三次 岡崎|Okazaki,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1838,1828,1848,Polychrome woodblock print; ink and color on paper,8 1/2 x 6 1/2 in. (21.6 x 16.5 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36573,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP95,false,true,36574,Asian Art,Print,東海道五十三次 池鯉鮒|Chiryu,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1838,1828,1848,Polychrome woodblock print; ink and color on paper,8 1/2 x 6 1/2 in. (21.6 x 16.5 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36574,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP96,false,true,36575,Asian Art,Print,東海道五十三次 鳴海|Narumi,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1838,1828,1848,Polychrome woodblock print; ink and color on paper,8 1/2 x 6 1/2 in. (21.6 x 16.5 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36575,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP97,false,true,36576,Asian Art,Print,東海道五十三次 宮|Miya,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1838,1828,1848,Polychrome woodblock print; ink and color on paper,8 1/2 x 6 1/2 in. (21.6 x 16.5 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36576,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP98,false,true,36577,Asian Art,Print,東海道五十三次 桑名|Kuwana,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1838,1828,1848,Polychrome woodblock print; ink and color on paper,8 1/2 x 6 1/2 in. (21.6 x 16.5 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36577,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP99,false,true,36578,Asian Art,Print,東海道五十三次 四日市|Yokkaichi,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1838,1828,1848,Polychrome woodblock print; ink and color on paper,8 1/2 x 6 1/2 in. (21.6 x 16.5 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36578,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP100,false,true,36579,Asian Art,Print,東海道五十三次 石薬師|Ishiyakushi,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1838,1828,1848,Polychrome woodblock print; ink and color on paper,8 1/2 x 6 1/2 in. (21.6 x 16.5 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36579,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP101,false,true,36580,Asian Art,Print,東海道五十三次 庄野|Shono,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1838,1828,1848,Polychrome woodblock print; ink and color on paper,8 1/2 x 6 1/2 in. (21.6 x 16.5 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36580,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP102,false,true,36581,Asian Art,Print,東海道五十三次 亀山|Kameyama,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1838,1828,1848,Polychrome woodblock print; ink and color on paper,8 1/2 x 6 1/2 in. (21.6 x 16.5 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36581,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP103,false,true,36582,Asian Art,Print,東海道五十三次 関|Seki,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1838,1828,1848,Polychrome woodblock print; ink and color on paper,8 1/2 x 6 1/2 in. (21.6 x 16.5 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36582,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP104,false,true,36583,Asian Art,Print,東海道五十三次 坂下|Saka-no-shita,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1838,1828,1848,Polychrome woodblock print; ink and color on paper,8 1/2 x 6 1/2 in. (21.6 x 16.5 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36583,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP105,false,true,36584,Asian Art,Print,東海道五十三次 土山 鈴鹿山の図|Tsuchiyama: Suzuka-yama no zu.,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1838,1828,1848,Polychrome woodblock print; ink and color on paper,8 1/2 x 6 1/2 in. (21.6 x 16.5 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36584,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP106,false,true,36585,Asian Art,Print,東海道五十三次 水口|Mizukuchi,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1838,1828,1848,Polychrome woodblock print; ink and color on paper,8 1/2 x 6 1/2 in. (21.6 x 16.5 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36585,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP107,false,true,36586,Asian Art,Print,東海道五十三次 草津|Kusatsu,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1838,1828,1848,Polychrome woodblock print; ink and color on paper,8 1/2 x 6 1/2 in. (21.6 x 16.5 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36586,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP108,false,true,36587,Asian Art,Print,東海道五十三次 大津|Otsu,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1838,1828,1848,Polychrome woodblock print; ink and color on paper,8 1/2 x 6 1/2 in. (21.6 x 16.5 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36587,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP109,false,true,36588,Asian Art,Print,東海道五十三次 京 三条大橋図|Kyoto: Sanju Ohashi no zu.,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1838,1828,1848,Polychrome woodblock print; ink and color on paper,8 1/2 x 6 1/2 in. (21.6 x 16.5 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36588,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP110,false,true,36589,Asian Art,Print,東海道五十三 京 内裏|Kyoto: Dairi,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1838,1828,1848,Polychrome woodblock print; ink and color on paper,8 1/2 x 6 1/2 in. (21.6 x 16.5 cm),"Rogers Fund, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36589,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP217,false,true,36693,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,1797–1858,1797,1858,Polychrome woodblock print; ink and color on paper,Image: 14 3/8 in. × 10 in. (36.5 × 25.4 cm) Mat: 12 5/8 × 18 1/2 in. (32.1 × 47 cm),"Gift of Mrs. Russell Sage, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36693,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP238,false,true,36710,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,early 1830s,1830,1835,Polychrome woodblock print; ink and color on paper,13 1/2 x 4 15/32 in. (34.3 x 11.4 cm),"Rogers Fund, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36710,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP239,false,true,36711,Asian Art,Print,歌川広重画 罌栗に鶉|Quails and Poppies,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1835,1825,1845,Polychrome woodblock print; ink and color on paper,14 x 5 in. (35.6 x 12.7 cm),"Rogers Fund, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36711,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP243,false,true,36715,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1836,1826,1846,Polychrome woodblock print; ink and color on paper,Overall: 14 7/8 x 5 1/16 in. (37.8 x 12.9 cm),"Rogers Fund, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36715,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP244,false,true,36716,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1833,1823,1843,Polychrome woodblock print; ink and color on paper,10 x 4 7/8 in. (25.4 x 12.4 cm),"Rogers Fund, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36716,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP245,false,true,36717,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1833,1823,1843,Polychrome woodblock print; ink and color on paper,9 7/8 x 4 11/16 in. (25.1 x 11.9 cm),"Rogers Fund, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36717,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP246,false,true,36718,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1840,1830,1850,Polychrome woodblock print; ink and color on paper,8 1/2 x 6 3/8 in. (21.6 x 16.2 cm),"Rogers Fund, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36718,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP247,false,true,36719,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1833,1823,1843,Polychrome woodblock print; ink and color on paper,8 27/32 x 6 3/8 in. (22.5 x 16.2 cm),"Rogers Fund, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36719,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP248,false,true,36720,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1837,1827,1847,Polychrome woodblock print,10 1/8 x 7 1/2 in. (25.7 x 19.1 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36720,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP249,false,true,36721,Asian Art,Woodblock print,紅蜀葵に燕と川蝉図|Swallows and Kingfisher with Rose Mallows,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1838,1828,1848,Polychrome woodblock print,10 7/16 x 7 11/32 in. (26.5 x 18.7 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36721,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP250,false,true,36722,Asian Art,Woodblock print,椿に目白と四十雀図|Japanese White-eye and Titmouse on a Camellia Branch,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1840,1830,1850,Polychrome woodblock print,10 7/32 x 7 7/32 in. (26.0 x 18.4 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36722,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP251,false,true,36723,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1833,1823,1843,Polychrome woodblock print; ink and color on paper,8 15/16 x 6 11/16 in. (22.7 x 17.0 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36723,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP252,false,true,36724,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1832,1822,1842,Polychrome woodblock print; ink and color on paper,8 15/16 x 6 3/5 in. (22.7 x 16.8 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36724,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP253,false,true,36725,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1838,1828,1848,Polychrome woodblock print; ink and color on paper,8 27/32 x 6 9/16 in. (22.5 x 16.7 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36725,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP254,false,true,36726,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,mid-1840s,1843,1846,Polychrome woodblock print; ink and color on paper,8 27/32 x 6 3/5 in. (22.5 x 16.8 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36726,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP255,false,true,36727,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1842,1832,1852,Polychrome woodblock print; ink and color on paper,8 23/32 x 6 9/16 in. (22.2 x 16.7 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36727,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP256,false,true,36728,Asian Art,Print,歌川広重画 芙蓉に高麗鶯|Black-naped Oriole Perched on a Stem of Rose Mallow,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1834,1824,1844,Polychrome woodblock print; ink and color on paper,14 11/16 x 4 29/32 in. (37.3 x 12.5 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36728,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP257,false,true,36729,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1834,1824,1844,Polychrome woodblock print,14 15/32 x 5 in. (36.8 x 12.7 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36729,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP258,false,true,36730,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1840,1830,1850,Polychrome woodblock print; ink and color on paper,12 31/32 x 4 1/4 in. (33.0 x 10.8 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36730,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP259,false,true,36731,Asian Art,Print,歌川広重画 菊に百舌鳥|Shrike and Chrysanthemums,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1830,1820,1840,Polychrome woodblock print (hosoban); ink and color on paper,14 7/8 x 5 in. (37.8 x 12.7 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36731,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP260,false,true,36732,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1833,1823,1843,Polychrome woodblock print; ink and color on paper,13 3/32 x 4 7/16 in. (33.2 x 11.3 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36732,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP261,false,true,36733,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1832,1822,1842,Polychrome woodblock print; ink and color on paper,15 x 5 in. (38.1 x 12.7 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36733,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP262,false,true,36734,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1840,1830,1850,Polychrome woodblock print; ink and color on paper,13 1/10 x 4 3/8 in. (33.3 x 11.1 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36734,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP263,false,true,36735,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1842,1832,1852,Polychrome woodblock print; ink and color on paper,13 3/32 x 4 15/32 in. (33.2 x 11.4 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36735,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP264,false,true,36736,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1830,1820,1840,Polychrome woodblock print; ink and color on paper,14 3/5 x 4 29/32 in. (37.1 x 12.5 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36736,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP265,false,true,36737,Asian Art,Print,歌川広重画|Morning Glories with Poem by Gyōkō,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1843,1833,1853,Polychrome woodblock print (hosoban); ink and color on paper,12 15/16 x 4 3/8 in. (32.9 x 11.1 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36737,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP266,false,true,36738,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1843,1841,1845,Polychrome woodblock print; ink and color on paper,13 1/4 x 4 15/32 in. (33.7 x 11.4 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36738,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP267,false,true,36739,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1833,1831,1835,Polychrome woodblock print; ink and color on paper,14 15/32 x 6 11/16 in. (36.8 x 17.0 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36739,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP268,false,true,36740,Asian Art,Print,波に丹頂鶴|Crane and Surf,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1833,1823,1843,Polychrome woodblock print; ink and color on paper,14 1/8 x 6 3/8 in. (35.9 x 16.2 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36740,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP269,false,true,36741,Asian Art,Print,歌川広重画 楓に孔雀|Peacock Perched on a Maple Tree,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1833,1823,1843,Polychrome woodblock print (hosoban); ink and color on paper,14 13/16 x 5 in. (37.6 x 12.7 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36741,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP270,false,true,36742,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1833,1823,1843,Polychrome woodblock print; ink and color on paper,15 x 4 29/32 in. (38.1 x 12.5 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36742,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP271,false,true,36743,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1835,1825,1845,Polychrome woodblock print; ink and color on paper,14 29/32 x 4 7/8 in. (37.9 x 12.4 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36743,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP272,false,true,36744,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1835,1825,1845,Polychrome woodblock print; ink and color on paper,10 x 4 3/4 in. (25.4 x 12.1 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36744,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP273,false,true,36745,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1835,1825,1845,Polychrome woodblock print; ink and color on paper,10 1/5 x 4 29/32 in. (25.9 x 12.5 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36745,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP471,false,true,36922,Asian Art,Print,東海道五十三次之内 日本橋 朝之景|Stations One: Morning View of Nihonbashi,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1833–34,1833,1834,Polychrome woodblock print; ink and color on paper,H. 9 1/2 in. (24.1 cm); W. 13 7/8 in. (35.2 cm),"Rogers Fund, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36922,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP472,false,true,36923,Asian Art,Print,東海道五十三次之内 品川 日之出|Daybreak at Shinagawa,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1834,1824,1844,Polychrome woodblock print; ink and color on paper,9 15/32 x 14 3/5 in. (24.1 x 37.1 cm),"Rogers Fund, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36923,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP473,false,true,36924,Asian Art,Print,東海道五十三次之内 川崎 六郷渡舟|Ferry Boat Crossing the Rokugo River,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1834,1824,1844,Polychrome woodblock print; ink and color on paper,9 15/32 x 14 3/4 in. (24.1 x 37.5 cm),"Rogers Fund, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36924,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP474,false,true,36925,Asian Art,Print,東海道五十三次之内 神奈川 宿台之景|View of the Kanagawa station at sunset,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1833–34,1833,1834,Polychrome woodblock print; ink and color on paper,9 15/32 x 14 in. (24.1 x 35.6 cm),"Rogers Fund, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36925,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP475,false,true,36926,Asian Art,Print,東海道五十三次之内 保土ヶ谷 新町橋|Hodogaya Station and Shinkame Bridge,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1834,1824,1844,Polychrome woodblock print; ink and color on paper,9 15/32 x 14 1/8 in. (24.1 x 35.9 cm),"Rogers Fund, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36926,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP476,false,true,36927,Asian Art,Print,東海道五十三次之内 戸塚 元町別道|Totsuka; Moto Machi Betsudo,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1834,1824,1844,Polychrome woodblock print; ink and color on paper,9 15/32 x 14 in. (24.1 x 35.6 cm),"Rogers Fund, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36927,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP477,false,true,36928,Asian Art,Print,東海道五十三次之内 藤澤 遊行寺|Fujiwara; Yugyoji,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1834,1824,1844,Polychrome woodblock print; ink and color on paper,9 15/32 x 14 in. (24.1 x 35.6 cm),"Rogers Fund, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36928,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP478,false,true,36929,Asian Art,Print,東海道五十三次之内 平塚 縄手道|Hiratsuka; Nawate Do,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1834,1824,1844,Polychrome woodblock print; ink and color on paper,9 15/32 x 14 1/8 in. (24.1 x 35.9 cm),"Rogers Fund, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36929,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP479,false,true,36930,Asian Art,Woodblock print,東海道五十三次之内 大磯 虎ケ雨|Tiger Rain at Ōiso Station,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1834,1824,1844,Polychrome woodblock print; ink and color on paper,9 15/32 x 14 in. (24.1 x 35.6 cm),"Rogers Fund, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36930,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP480,false,true,36931,Asian Art,Print,東海道五十三次之内 小田原 酒匂川|Odawara; Sakogawa,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1833–34,1833,1834,Polychrome woodblock print; ink and color on paper,9 15/32 x 14 1/8 in. (24.1 x 35.9 cm),"Rogers Fund, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36931,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP481,false,true,36932,Asian Art,Print,東海道五十三次之内 箱根 湖水図|Hakone; Kosui,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1833–34,1833,1834,Polychrome woodblock print; ink and color on paper,9 15/32 x 14 1/8 in. (24.1 x 35.9 cm),"Rogers Fund, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36932,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP482,false,true,36933,Asian Art,Print,"東海道五十三次之内 三島 朝霧|Mishima, Asa Kiri",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1833–34,1833,1834,Polychrome woodblock print; ink and color on paper,9 15/32 x 14 7/8 in. (24.1 x 37.8 cm),"Rogers Fund, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36933,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP483,false,true,36934,Asian Art,Print,東海道五十三次之内 沼津 黄昏図|Numazu Ki Kure,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1834,1824,1844,Polychrome woodblock print; ink and color on paper,9 15/32 x 14 in. (24.1 x 35.6 cm),"Rogers Fund, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36934,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP484,false,true,36935,Asian Art,Print,東海道五十三次之内 原 朝の富士|Hara; Asa no Fuji,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1833–34,1833,1834,Polychrome woodblock print; ink and color on paper,9 15/32 x 14 1/8 in. (24.1 x 35.9 cm),"Rogers Fund, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36935,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP485,false,true,36936,Asian Art,Print,"東海道五十三次之内 吉原 左富士|Yoshiwara, Hidari Fuji",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1834,1824,1844,Polychrome woodblock print; ink and color on paper,9 15/32 x 14 in. (24.1 x 35.6 cm),"Rogers Fund, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36936,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP486,false,true,36937,Asian Art,Woodblock print,東海道五十三次之内 蒲原 夜の雪|A Snowy Evening at Kambara Station,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1833–34,1833,1834,Polychrome woodblock print; ink and color on paper,Image: 9 1/2 × 14 1/8 in. (24.1 × 35.9 cm),"Rogers Fund, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36937,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP487,false,true,36938,Asian Art,Print,東海道五十三次之内 由井 薩埵嶺|Satta Peak at Yui,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1834,1824,1844,Polychrome woodblock print; ink and color on paper,9 15/32 x 14 1/8 in. (24.1 x 35.9 cm),"Rogers Fund, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36938,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP488,false,true,36939,Asian Art,Print,"東海道五十三次之内 奥津 興津川|Okitsu, Okitsugawa",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1834,1824,1844,Polychrome woodblock print; ink and color on paper,9 15/32 x 14 1/8 in. (24.1 x 35.9 cm),"Rogers Fund, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36939,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP489,false,true,36940,Asian Art,Print,東海道五十三次之内 江尻 三保遠望|Distant View of Miho Beach from Ejiri,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1834,1824,1844,Polychrome woodblock print; ink and color on paper,9 15/32 x 14 in. (24.1 x 35.6 cm),"Rogers Fund, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36940,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP490,false,true,36941,Asian Art,Print,東海道五十三次之内 府中 安部川|Travellers Fording the Abe River at Fuchu,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1833–34,1833,1834,Polychrome woodblock print; ink and color on paper,9 15/32 x 14 in. (24.1 x 35.6 cm),"Rogers Fund, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36941,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP491,false,true,36942,Asian Art,Print,東海道五十三次之内 鞠子 名物茶店|Mariko; Meibutsu Chaya,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1834,1824,1844,Polychrome woodblock print; ink and color on paper,9 15/32 x 14 1/16 in. (24.1 x 35.7 cm),"Rogers Fund, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36942,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP492,false,true,36943,Asian Art,Print,東海道五十三次之内 岡部 宇津の山|Okabe; Utsu no Yama,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1834,1824,1844,Polychrome woodblock print; ink and color on paper,9 15/32 x 14 1/8 in. (24.1 x 35.9 cm),"Rogers Fund, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36943,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP493,false,true,36944,Asian Art,Print,東海道五十三次之内 藤枝 人馬継立|Fujieda; Hito Uma Keitatsu,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1834,1824,1844,Polychrome woodblock print; ink and color on paper,9 15/32 x 14 1/16 in. (24.1 x 35.7 cm),"Rogers Fund, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36944,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP494,false,true,36945,Asian Art,Print,"東海道五十三次之内 嶋田 大井川駿岸|Shimada, Oigawa Shun Gan",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1834,1824,1844,Polychrome woodblock print; ink and color on paper,9 15/32 x 14 1/8 in. (24.1 x 35.9 cm),"Rogers Fund, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36945,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP495,false,true,36946,Asian Art,Print,"東海道五十三次之内 金谷 大井川遠岸|Kanaya, Oigawa Em Gan",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1834,1824,1844,Polychrome woodblock print; ink and color on paper,9 15/32 x 14 1/8 in. (24.1 x 35.9 cm),"Rogers Fund, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36946,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP496,false,true,36947,Asian Art,Print,"東海道五十三次之内 日坂 佐夜の中山|Nissaka, Sayo Nakayama",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1834,1824,1844,Polychrome woodblock print; ink and color on paper,9 15/32 x 14 1/8 in. (24.1 x 35.9 cm),"Rogers Fund, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36947,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP497,false,true,36948,Asian Art,Print,"東海道五十三次之内 掛川 秋葉山遠望|Kakegawa, Akihasan Empo",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1834,1824,1844,Polychrome woodblock print; ink and color on paper,9 3/5 x 14 1/8 in. (24.4 x 35.9 cm),"Rogers Fund, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36948,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP498,false,true,36949,Asian Art,Print,東海道五十三次之内 袋井 出茶屋の図|Fukuroi; De Chaya,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1834,1824,1844,Polychrome woodblock print; ink and color on paper,9 15/32 x 14 1/8 in. (24.1 x 35.9 cm),"Rogers Fund, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36949,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP500,false,true,36951,Asian Art,Print,"東海道五十三次之内 濱松 冬枯の図|Hamamatsu, Toko no Zu",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1834,1824,1844,Polychrome woodblock print; ink and color on paper,9 15/32 x 14 1/16 in. (24.1 x 35.7 cm),"Rogers Fund, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36951,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP501,false,true,36952,Asian Art,Print,東海道五十三次之内 舞坂 今切真景|View of Imaki Point from Maizaka,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1834,1824,1844,Polychrome woodblock print; ink and color on paper,9 15/32 x 14 1/16 in. (24.1 x 35.7 cm),"Rogers Fund, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36952,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP502,false,true,36953,Asian Art,Print,"東海道五十三次之内 荒井 渡舟の図|Arai, Tosen",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1834,1824,1844,Polychrome woodblock print; ink and color on paper,9 15/32 x 14 1/16 in. (24.1 x 35.7 cm),"Rogers Fund, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36953,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP503,false,true,36954,Asian Art,Print,"東海道五十三次之内 白須賀 汐見阪図|Shirasuka, Shio-mi Zaka",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1834,1824,1844,Polychrome woodblock print; ink and color on paper,9 15/32 x 14 1/16 in. (24.1 x 35.7 cm),"Rogers Fund, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36954,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP504,false,true,36955,Asian Art,Print,"東海道五十三次之内 二川 猿ヶ馬場|Futagawa, Saru ga Baba",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1834,1824,1844,Polychrome woodblock print; ink and color on paper,9 15/32 x 14 1/16 in. (24.1 x 35.7 cm),"Rogers Fund, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36955,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP505,false,true,36956,Asian Art,Print,"東海道五十三次之内 吉田 豊川橋|Yoshida, Toyokawa Hashi",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1834,1824,1844,Polychrome woodblock print; ink and color on paper,9 15/32 x 14 1/16 in. (24.1 x 35.7 cm),"Rogers Fund, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36956,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP506,false,true,36957,Asian Art,Print,"東海道五十三次之内 御油 旅人留女|Goyu, Tabibito Ryujo",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1833–34,1833,1834,Polychrome woodblock print; ink and color on paper,9 15/32 x 14 in. (24.1 x 35.6 cm),"Rogers Fund, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36957,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP507,false,true,36958,Asian Art,Print,"東海道五十三次之内 赤阪 旅舎招婦の図|Akasaka, Ryosha Sho-fu",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1834,1824,1844,Polychrome woodblock print; ink and color on paper,9 15/32 x 14 1/16 in. (24.1 x 35.7 cm),"Rogers Fund, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36958,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP508,false,true,36959,Asian Art,Print,"東海道五十三次之内 藤川 棒鼻の図|Fujikawa, Bo Bana",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1834,1824,1844,Polychrome woodblock print; ink and color on paper,9 15/32 x 14 1/16 in. (24.1 x 35.7 cm),"Rogers Fund, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36959,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP509,false,true,36960,Asian Art,Print,"東海道五十三次之内 岡崎 矢矧の橋|Okazaki, Tenshin no Hashi",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1834,1824,1844,Polychrome woodblock print; ink and color on paper,9 15/32 x 14 in. (24.1 x 35.6 cm),"Rogers Fund, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36960,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP510,false,true,36961,Asian Art,Print,"東海道五十三次之内 池鯉鮒 首夏馬市|Chiryu, Shuka Uma Ichi",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1834,1824,1844,Polychrome woodblock print; ink and color on paper,9 15/32 x 14 in. (24.1 x 35.6 cm),"Rogers Fund, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36961,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP511,false,true,36962,Asian Art,Print,"東海道五十三次之内 鳴海 名物有松絞|Narumi, Meibutsu Arimatsu Shibori",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1834,1824,1844,Polychrome woodblock print; ink and color on paper,9 15/32 x 14 in. (24.1 x 35.6 cm),"Rogers Fund, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36962,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP512,false,true,36963,Asian Art,Print,"東海道五十三次之内 宮 熱田神事|Miya, Atsuta Shin Ji",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1834,1824,1844,Polychrome woodblock print; ink and color on paper,9 15/32 x 14 1/16 in. (24.1 x 35.7 cm),"Rogers Fund, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36963,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP513,false,true,36964,Asian Art,Print,"東海道五十三次之内 桑名 七里渡口|Kuwana, Shichi-Ri Watashi Guchi",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1834,1824,1844,Polychrome woodblock print; ink and color on paper,9 15/32 x 14 1/16 in. (24.1 x 35.7 cm),"Rogers Fund, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36964,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP514,false,true,36965,Asian Art,Print,"東海道五十三次之内 四日市 三重川|Yokkaichi, Sanchokawa",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1834,1824,1844,Polychrome woodblock print; ink and color on paper,9 15/32 x 14 1/16 in. (24.1 x 35.7 cm),"Rogers Fund, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36965,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP515,false,true,36966,Asian Art,Print,"東海道五十三次之内 石薬師 石薬師寺|Ishiyakushi, Ishiyakushi Ji",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1834,1824,1844,Polychrome woodblock print; ink and color on paper,9 15/32 x 14 in. (24.1 x 35.6 cm),"Rogers Fund, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36966,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP516,false,true,36967,Asian Art,Print,東海道五十三次之内 庄野 白雨|Sudden Shower in Shōno,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1833–34,1832,1836,Polychrome woodblock print; ink and color on paper,9 15/32 x 14 in. (24.1 x 35.6 cm),"Rogers Fund, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36967,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP517,false,true,36968,Asian Art,Print,"東海道五十三次之内 亀山 雪晴|Kameyama, Yuki Hare",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1834,1824,1844,Polychrome woodblock print; ink and color on paper,9 15/32 x 14 in. (24.1 x 35.6 cm),"Rogers Fund, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36968,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP518,false,true,36969,Asian Art,Print,"東海道五十三次之内 関 本陣早立|Seki, Honjin Sotatsu",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1834,1824,1844,Polychrome woodblock print; ink and color on paper,9 15/32 x 14 1/16 in. (24.1 x 35.7 cm),"Rogers Fund, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36969,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP519,false,true,36970,Asian Art,Print,"東海道五十三次之内 阪之下 筆捨嶺|Saka-no-shita, Fude-sute Mine",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1834,1824,1844,Polychrome woodblock print; ink and color on paper,Image: 9 1/2 × 14 1/8 in. (24.1 × 35.9 cm),"Rogers Fund, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36970,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP520,false,true,36971,Asian Art,Print,"東海道五十三次 土山 春の雨|Spring Rain at Tsuchiyama, from the series Fifty-three Stations of the Tōkaidō",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,1834–35,1834,1835,Polychrome woodblock print; ink and color on paper,H. 9 1/2 in. (24.1 cm); W. 14 in. (35.6 cm),"Rogers Fund, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36971,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP521,false,true,36972,Asian Art,Print,"東海道五十三次之内 水口 名物干瓢|Mizukuchi, Meibutsu Kampyo",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1834,1824,1844,Polychrome woodblock print; ink and color on paper,9 15/32 x 14 in. (24.1 x 35.6 cm),"Rogers Fund, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36972,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP522,false,true,36973,Asian Art,Print,"東海道五十三次之内 石部 目川の里|Ishibe, Megawa Sato",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1834,1824,1844,Polychrome woodblock print; ink and color on paper,9 15/32 x 14 1/16 in. (24.1 x 35.7 cm),"Rogers Fund, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36973,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP523,false,true,36974,Asian Art,Print,"東海道五十三次之内 草津 名物立場|Kusatsu: Famous Post House (Kusatsu, Meibutsu tateba), from the series Fifty-three Stations of the Tōkaidō Road (Tōkaidō gojūsan tsugi no uchi)",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1833–34,1833,1834,Polychrome woodblock print; ink and color on paper,9 15/32 x 14 in. (24.1 x 35.6 cm),"Rogers Fund, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36974,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP524,false,true,36975,Asian Art,Print,"東海道五十三次之内 大津 走井茶屋|Otsu, Soii Chaya",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1834,1824,1844,Polychrome woodblock print; ink and color on paper,9 15/32 x 14 in. (24.1 x 35.6 cm),"Rogers Fund, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36975,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP525,false,true,36976,Asian Art,Print,"東海道五十三次之内 大尾 京師 三条大橋|Kyoto, Sanjo Ohashi",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1833–34,1833,1834,Polychrome woodblock print; ink and color on paper,9 3/5 x 14 1/16 in. (24.4 x 35.7 cm),"Rogers Fund, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36976,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP526,false,true,36977,Asian Art,Print,東都名所 日本橋の白雨|Sunshower at Nihonbashi,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,1833–34,1833,1834,Polychrome woodblock print; ink and color on paper,10 1/8 x 15 in. (25.7 x 38.1 cm),"Rogers Fund, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36977,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP567,false,true,37018,Asian Art,Woodblock print,忠臣蔵 十一段目 夜打 押寄|The Loyal Ronin Crossing the Long Bridge to Embark for the Night Attack upon Moronao,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1840,1830,1850,Polychrome woodblock print; ink and color on paper,9 1/8 x 14 in. (23.2 x 35.6 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37018,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP568,false,true,37019,Asian Art,Print,東海道五十三次之内 荒井 海上壹リ半舟渡之図|Arai,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1842,1832,1852,Polychrome woodblock print; ink and color on paper,9 x 13 5/8 in. (22.9 x 34.6 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37019,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP569,false,true,37020,Asian Art,Print,東海道五十三次之内 吉原|Yoshiwara,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1842,1832,1852,Polychrome woodblock print; ink and color on paper,9 x 13 5/8 in. (22.9 x 34.6 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37020,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP570,false,true,37021,Asian Art,Print,東海道五十三次之内 川崎 六郷の渡し舟|Kawasaki,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1842,1832,1852,Polychrome woodblock print; ink and color on paper,Overall: 9 x 13 3/4in. (22.9 x 34.9cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37021,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP571,false,true,37022,Asian Art,Print,東海道五十三次之内 亀山|Kameyama,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1842,1832,1852,Polychrome woodblock print; ink and color on paper,9 1/16 x 13 7/10 in. (23.0 x 34.8 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37022,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP572,false,true,37023,Asian Art,Print,東海道五十三次之内 赤阪|Akasaka Station,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1842,1832,1852,Polychrome woodblock print; ink and color on paper,8 27/32 x 13 1/2 in. (22.5 x 34.3 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37023,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP573,false,true,37024,Asian Art,Print,東海道五十三次之内 石薬師|Gyosho Tokaido: Ishikushi,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1842,1832,1852,Polychrome woodblock print; ink and color on paper,8 13/16 x 13 17/32 in. (22.4 x 34.4 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37024,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP574,false,true,37025,Asian Art,Print,東海道五十三次之内 蒲原 岩淵よりふじ川を見る圖|Kambura Station,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1842,1832,1852,Polychrome woodblock print; ink and color on paper,8 27/32 x 13 1/2 in. (22.5 x 34.3 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37025,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP575,false,true,37026,Asian Art,Print,東海道五十三次之内 品川 鮫洲朝之景|Shinagawa Samesu Asa no Kei,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1842,1832,1852,Polychrome woodblock print; ink and color on paper,8 27/32 x 13 1/2 in. (22.5 x 34.3 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37026,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP576,false,true,37027,Asian Art,Print,東海道五十三次之内 岡部 宇津の山之図|Okabe,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1842,1832,1852,Polychrome woodblock print; ink and color on paper,9 1/8 x 13 23/32 in. (23.2 x 34.9 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37027,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP577,false,true,37028,Asian Art,Print,東海道五十三次之内 平塚 馬入川舟渡しの図|Hiratsuka; Banyugawa Funa Watashi no Zu,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1842,1832,1852,Polychrome woodblock print; ink and color on paper,8 27/32 x 13 1/2 in. (22.5 x 34.3 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37028,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP578,false,true,37029,Asian Art,Print,東海道五十三次之内 嶋田 大井川駿岸|Shimada,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1842,1832,1852,Polychrome woodblock print; ink and color on paper,9 x 13 5/8 in. (22.9 x 34.6 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37029,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP579,false,true,37030,Asian Art,Print,東海道五十三次之内 草津|Kusatsu Station,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1842,1832,1852,Polychrome woodblock print; ink and color on paper,8 27/32 x 13 1/2 in. (22.5 x 34.3 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37030,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP580,false,true,37031,Asian Art,Print,東海道五十三次之内 大津|Otsu Station,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1842,1832,1852,Polychrome woodblock print; ink and color on paper,8 27/32 x 13 1/2 in. (22.5 x 34.3 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37031,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP581,false,true,37032,Asian Art,Print,東海道五十三次之内 神奈川 浅間下より台を見る図|Kanazawa,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1842,1832,1852,Polychrome woodblock print; ink and color on paper,9 1/8 x 13 23/32 in. (23.2 x 34.9 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37032,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP582,false,true,37033,Asian Art,Print,東海道五十三次之内 江尻 清水之湊遠望|Ejiri,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1842,1832,1852,Polychrome woodblock print; ink and color on paper,8 27/32 x 13 1/2 in. (22.5 x 34.3 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37033,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP583,false,true,37034,Asian Art,Print,東海道五十三次之内 袋井 出茶屋の図|Fukuroi; De Chaya,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1834,1824,1844,Polychrome woodblock print; ink and color on paper,9 3/4 x 14 1/3 in. (24.8 x 36.4 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37034,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP584,false,true,37035,Asian Art,Print,東海道五十三次之内 品川 諸侯出立|Shinagawa; Shoko Detachi,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1834,1824,1844,Polychrome woodblock print; ink and color on paper,9 1/4 x 14 1/4 in. (23.5 x 36.2 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37035,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP585,false,true,37036,Asian Art,Print,木曽海道六拾九次之内 望月|Mochizuki Station,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1835,1825,1845,Polychrome woodblock print; ink and color on paper,9 3/4 x 14 7/8 in. (24.8 x 37.8 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37036,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP586,false,true,37037,Asian Art,Print,木曽海道六拾九次之内 芦田|Ashida Station,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1835,1825,1845,Polychrome woodblock print; ink and color on paper,Oban 10 x 14 29/32 in. (25.4 x 37.9 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37037,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP587,false,true,37038,Asian Art,Print,"本朝名所 相州七里ヶ浜|Soshu, Shichi-ri ga Hama",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1830,1820,1840,Polychrome woodblock print; ink and color on paper,9 x 14 1/3 in. (22.9 x 36.4 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37038,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP588,false,true,37039,Asian Art,Print,"本朝名所 相州江ノ嶋岩屋之図|Soshu, Enoshima Iwaya no Zu",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1830,1820,1840,Polychrome woodblock print; ink and color on paper,9 7/8 x 14 3/5 in. (25.1 x 37.1 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37039,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP589,false,true,37040,Asian Art,Print,"六十余州名所図会 隠岐 焚火の社|The Takihi Shrine, Oki Province, from the series Views of Famous Places in the Sixty-Odd Provinces",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1853,1843,1863,Polychrome woodblock print; ink and color on paper,14 x 9 7/16 in. (35.6 x 24 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37040,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP590,false,true,37041,Asian Art,Print,"六十余州名所図会 大和 立田山 龍田川|Yamato, Tatsutayama, Tatsutagawa",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,7th month ox year 1853,1853,1853,Polychrome woodblock print; ink and color on paper,14 1/8 x 9 3/4 in. (35.9 x 24.8 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37041,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP591,false,true,37042,Asian Art,Print,"「六十余州名所図絵 伊予 西条」|“Iyo Province, Saijō ,” from the series Views of Famous Places in the Sixty-odd Provinces (Rokujū yoshū meisho zu-e, Iyo, Saijō)",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1853,1843,1863,Polychrome woodblock print; ink and color on paper,14 1/4 x 9 15/32 in. (36.2 x 24.1 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37042,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP592,false,true,37043,Asian Art,Woodblock print,六十余州名所図会 対馬 海岸夕晴|Tsushima Kaigan Yubare,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,3rd month dragon year 1856,1856,1856,Polychrome woodblock print; ink and color on paper,14 x 9 3/5 in. (35.6 x 24.4 cm),"Purchase, Joseph Pultizer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37043,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP593,false,true,37044,Asian Art,Print,"六十余州名所図会 因幡 か路小山|View of Kajikoyama, Inaba Province, from the series Views of Famous Places in the Sixty-Odd Provinces",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1853,1843,1863,Polychrome woodblock print; ink and color on paper,Aiban; 13 15/16 x 9 1/4 in. (35.4 x 23.5 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37044,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP594,false,true,37045,Asian Art,Print,"六十余州名所図会 武蔵 隅田川 雪の朝|Morning after a Snowfall, the Sumida River, Musashi Province, from the series Views of Famous Places in the Sixty-Odd Provinces",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1853,1843,1863,Polychrome woodblock print; ink and color on paper,14 1/8 x 9 1/3 in. (35.9 x 23.7 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37045,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP595,false,true,37046,Asian Art,Print,"六十余州名所図会 播磨 舞子の浜|Harima, Maiko no Hama",Japan,Edo period (1615–1868),,,,Artist,Designed by,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,1853–56,1853,1856,Polychrome woodblock print; ink and color on paper,Oban tate-e 14 x 9 7/16 in. (35.6 x 24 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37046,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP596,false,true,37047,Asian Art,Print,"Kazusa Yazashi-ga-ura tsumei|六十余州名所図会 上総 矢さしか浦 通名九十九里|Yasashi Beach, known as Kujūkuri, Kazusa Province, from the series Views of Famous Places in the Sixty-Odd Provinces",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1853,1843,1863,Polychrome woodblock print; ink and color on paper,Oban tate-e 14 3/5 x 10 1/8 in. (37.1 x 25.7 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37047,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP597,false,true,37048,Asian Art,Print,"東都名所 二丁町芝居の図|View of the Kabuki Theaters at Sakai-cho on Opening Day of the New Season (Sakai-cho Shibai no Zu), from the series, ""Toto Meisho""",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1838,1828,1848,Polychrome woodblock print; ink and color on paper,10 1/8 x 14 7/8 in. (25.7 x 37.8 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37048,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP598,false,true,37049,Asian Art,Print,東都名所 二丁町芝居の図|Sakai Cho Shibai no Zu,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1838,1828,1848,Polychrome woodblock print; ink and color on paper,10 x 14 29/32 in. (25.4 x 37.9 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37049,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP599,false,true,37050,Asian Art,Print,江都名所 新橋の図|Shimbashi no Zu,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1837,1827,1847,Polychrome woodblock print; ink and color on paper,10 1/8 x 15 in. (25.7 x 38.1 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37050,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP600,false,true,37051,Asian Art,Print,東都名所 芝増上寺山内の図|Shiba Zōjōji Sannai no Zu,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1833–34,1831,1836,Polychrome woodblock print; ink and color on paper,10 1/8 x 14 7/8 in. (25.7 x 37.8 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37051,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP601,false,true,37052,Asian Art,Print,東都名所 駿河町の図|Suruga Street,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1836,1826,1846,Polychrome woodblock print; ink and color on paper,9 9/16 x 14 1/2 in. (24.3 x 36.8 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37052,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP602,false,true,37053,Asian Art,Print,東都名所 亀戸天満宮境内雪|Kameido Tenmangu Keidai no Yuki,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1833–34,1831,1836,Polychrome woodblock print; ink and color on paper,10 x 14 5/8 in. (25.4 x 37.1 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37053,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP603,false,true,37054,Asian Art,Print,東都名所 亀戸天満宮境内雪|Kameido Tenmangu Keidai no Yuki,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1833–34,1831,1836,Polychrome woodblock print; ink and color on paper,9 3/8 x 13 11/16 in. (23.8 x 34.8 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37054,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP604,false,true,37055,Asian Art,Print,"東都名所 芝赤羽根之雪|Shiba, Akabane no Yuki",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1837,1827,1847,Polychrome woodblock print; ink and color on paper,10 1/8 x 14 15/16 in. (25.7 x 37.9 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37055,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP605,false,true,37056,Asian Art,Print,東都名所 高輪の夕景|Takanawa no Yukei,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1833,1823,1843,Polychrome woodblock print; ink and color on paper,9 1/2 x 14 1/2 in. (24.1 x 36.8 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37056,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP606,false,true,37057,Asian Art,Print,東都名所 芝愛宕山上の図|Shiba Atago Sanjo no Zu,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1833–34,1831,1836,Polychrome woodblock print; ink and color on paper,9 13/16 x 14 11/16 in. (24.9 x 37.3 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37057,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP607,false,true,37058,Asian Art,Print,東都名所 浅草金龍山年の市|Year End Fair at Kinryuzan Temple,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1836,1826,1846,Polychrome woodblock print; ink and color on paper,9 15/16 x 14 5/8 in. (25.2 x 37.1 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37058,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP608,false,true,37059,Asian Art,Print,東都名所 吉原夜桜の図|Yoshiwara Yo Zakura no Zu,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1841,1831,1851,Polychrome woodblock print; ink and color on paper,9 13/16 x 14 3/8 in. (24.9 x 36.5 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37059,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP609,false,true,37060,Asian Art,Print,東都名所 浅草金龍山年の市|Asakusa Kinryusan Toshi no Ichi,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1838,1828,1848,Polychrome woodblock print; ink and color on paper,9 7/16 x 14 7/16 in. (24 x 36.6 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37060,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP610,false,true,37061,Asian Art,Print,江都名所 飛鳥山はな見|Asakayama Hanami,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1834,1824,1844,Polychrome woodblock print; ink and color on paper,9 1/4 x 14 3/4 in. (23.5 x 37.5 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37061,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP611,false,true,37062,Asian Art,Print,東都名所 両国花火の図|Ryogoku Hanabi no Zu,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1841,1831,1851,Polychrome woodblock print; ink and color on paper,10 3/32 x 14 3/4 in. (25.6 x 37.5 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37062,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP612,false,true,37063,Asian Art,Print,新撰江戸名所 高輪廿六夜之図|Takanawa Ni-ju-roku Ya,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1840,1838,1842,Polychrome woodblock print; ink and color on paper,8 1/2 x 13 5/8 in. (21.6 x 34.6 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37063,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP613,false,true,37064,Asian Art,Print,東都名所 高輪之図|Takanawa no Zu,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1841,1831,1851,Polychrome woodblock print; ink and color on paper,10 5/32 x 14 11/16 in. (25.8 x 37.3 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37064,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP614,false,true,37065,Asian Art,Print,東都名所 新吉原日本堤衣紋坂曙|Shin Yoshiwara Nihon Tsutsumi Emonzaka Akatsuki,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1835,1825,1845,Polychrome woodblock print; ink and color on paper,10 1/5 x 14 7/16 in. (25.9 x 36.7 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37065,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP616,false,true,37067,Asian Art,Print,東都名所 新吉原|Shin Yoshiwara,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1840,1830,1850,Polychrome woodblock print; ink and color on paper,10 5/16 x 14 7/8 in. (26.2 x 37.8 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37067,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP617,false,true,37068,Asian Art,Print,東都名所 佃島初郭公|Tsukudajima Hatsu Hototogisu,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,probably 1830,1828,1832,Polychrome woodblock print; ink and color on paper,8 23/32 x 14 1/16 in. (22.2 x 35.7 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37068,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP618,false,true,37069,Asian Art,Print,"東都名所 外桜田弁慶桜の井|Soto Sakurada, Benkei Bori, Sakura-no-i",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1842,1832,1852,Polychrome woodblock print; ink and color on paper,10 1/8 x 14 3/4 in. (25.7 x 37.5 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37069,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP620,false,true,37071,Asian Art,Print,日本湊尽 相州浦賀|Uraga Harbor,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1837,1827,1847,Polychrome woodblock print; ink and color on paper,8 5/8 x 13 5/8 in. (21.9 x 34.6 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37071,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP621,false,true,37072,Asian Art,Print,"日本湊尽 東都品川|Toto, Shinagawa",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1838,1828,1848,Polychrome woodblock print; ink and color on paper,9 3/4 x 14 15/32 in. (24.8 x 36.8 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37072,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP622,false,true,37073,Asian Art,Print,"江戸名所 上野不忍の池|Ueno, Shinobazu no Ike",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1835,1833,1837,Polychrome woodblock print; ink and color on paper,8 5/8 x 13 1/2 in. (21.9 x 34.3 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37073,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP623,false,true,37074,Asian Art,Print,"諸国六玉河 陸奥 野田の玉川|Mutsu, Noda no Tamagawa",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1833,1823,1843,Polychrome woodblock print; ink and color on paper,9 3/4 x 14 3/5 in. (24.8 x 37.1 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37074,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP624,false,true,37075,Asian Art,Print,諸国六玉河 武蔵 調布の玉川|Musashi: Chōfu no Tamagawa,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1833,1823,1843,Polychrome woodblock print; ink and color on paper,9 3/4 x 14 3/4 in. (24.8 x 37.5 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37075,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP625,false,true,37076,Asian Art,Print,"山海見立相撲 摂津安治川口|The Harbor of Ajikawa, Settsu Province",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,1858,1858,1858,Polychrome woodblock print; ink and color on paper,9 11/16 x 14 1/4 in. (24.6 x 36.2 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37076,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP626,false,true,37077,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,6th month ox year 1853,1853,1853,Polychrome woodblock print; ink and color on paper,9 7/16 x 14 11/16 in. (24.0 x 37.3 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37077,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP627,false,true,37078,Asian Art,Print,東都名所 神田明神境内雪晴之図|Kanda Myojin Kyodai Yuki Hare no Zu,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1840,1830,1852,Polychrome woodblock print; ink and color on paper,9 15/32 x 14 3/5 in. (24.1 x 37.1 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37078,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP628,false,true,37079,Asian Art,Print,東都名所 永代橋佃沖漁舟|Eitai Bashi Tsukudajima Ryosen,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1835,1825,1845,Polychrome woodblock print; ink and color on paper,9 7/16 x 14 1/16 in. (24.0 x 35.7 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37079,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP629,false,true,37080,Asian Art,Print,東都名所 上野東叡山ノ圖|Ueno Toezan no Zu,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1835,1825,1845,Polychrome woodblock print; ink and color on paper,9 7/16 x 13 27/32 in. (24.0 x 35.2 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37080,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP630,false,true,37081,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1842,1832,1852,Polychrome woodblock print; ink and color on paper,9 11/16 x 14 1/4 in. (24.6 x 36.2 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37081,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP631,false,true,37082,Asian Art,Print,東都名所 飛鳥山花盛|Asukayama Hana Zakari,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1844,1834,1854,Polychrome woodblock print; ink and color on paper,9 4/5 x 14 7/8 in. (24.9 x 37.8 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37082,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP632,false,true,37083,Asian Art,Print,江戸名所 外桜田弁慶堀|Soto Sakurada Benkei Bori,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1843,1833,1853,Polychrome woodblock print; ink and color on paper,9 7/8 x 14 3/5 in. (25.1 x 37.1 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37083,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP633,false,true,37084,Asian Art,Print,江戸名所 高輪月の景|Takanawa Tsuki no Kei,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,7th month tiger year 1854,1854,1854,Polychrome woodblock print; ink and color on paper,9 3/5 x 14 3/8 in. (24.4 x 36.5 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37084,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP635,false,true,37086,Asian Art,Print,近江八景之内 石山秋月|The Autumn Moon at Ishiyama on Lake Biwa.,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1835,1825,1845,Polychrome woodblock print; ink and color on paper,10 x 14 9/16 in. (25.4 x 37.0 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37086,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP636,false,true,37087,Asian Art,Print,五十三次名所図会 藤川 山中の里別名宮路山|Fujikawa; Sanchu Yamanaka no Sato Miyajiyama,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,7th month Hare year 1855,1855,1855,Polychrome woodblock print; ink and color on paper,14 x 8 27/32 in. (35.6 x 22.5 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37087,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP637,false,true,37088,Asian Art,Print,"五十三次名所図会 鳴海 名產有松しぼり店|Narumi, Meisan Arimatsu Shibori Mise",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,7th month Hare year 1855,1855,1855,Polychrome woodblock print; ink and color on paper,Aiban; 13 23/32 x 9 1/4 in. (34.9 x 23.5 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37088,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP638,false,true,37089,Asian Art,Print,"富士三十六景 甲斐大月の原|Kai, Otsuki no Hara",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,4th month horse year 1858,1858,1858,Polychrome woodblock print; ink and color on paper,14 x 9 15/32 in. (35.6 x 24.1 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37089,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP639,false,true,37090,Asian Art,Print,"富士三十六景 武蔵越かや在|Musashi, Koshigaya Zai",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,4th month horse year 1858,1858,1858,Polychrome woodblock print; ink and color on paper,14 1/16 x 9 1/3 in. (35.7 x 23.7 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37090,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP640,false,true,37091,Asian Art,Print,東都三十六景 佃しま漁舟|Tsukudajima Gyoshoi,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,1868,1868,1868,Polychrome woodblock print; ink and color on paper,14 15/32 x 9 7/8 in. (36.8 x 25.1 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37091,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP641,false,true,37092,Asian Art,Print,"東都三十六景 隅田川三囲り堤|Sumidagawa, Mimeguri",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,3rd month dragon year 1868,1868,1868,Polychrome woodblock print; ink and color on paper,14 15/32 x 9 4/5 in. (36.8 x 24.9 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37092,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP642,false,true,37093,Asian Art,Print,"名所江戶百景 両国花火|Fireworks at Ryōgoku Bridge, from the series One Hundred Famous Views of Edo",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,1858,1858,1858,Polychrome woodblock print; ink and color on paper,Image: 13 1/4 × 8 3/4 in. (33.7 × 22.2 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37093,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP643,false,true,36461,Asian Art,Print,"Ōhashi Atake no yūdachi|名所江戶百景 大はしあたけの夕立|Sudden Shower over Shin-Ōhashi Bridge and Atake (Ōhashi Atake no yūdachi), from the series One Hundred Famous Views of Edo (Meisho Edo hyakkei)",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,1857,1857,1857,Polychrome woodblock print; ink and color on paper,13 3/8 x 9 1/2 in. (34 x 24.1 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36461,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP644,false,true,37094,Asian Art,Woodblock print,"名所江戶百景 大はしあたけの夕立|Sudden Shower over Shin-Ōhashi Bridge and Atake (Ōhashi Atake no yūdachi), from the series One Hundred Famous Views of Edo (Meisho Edo hyakkei)",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,1857,1857,1857,Polychrome woodblock print; ink and color on paper,14 3/8 x 9 9/16 in. (36.5 x 24.3 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37094,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP645,false,true,37095,Asian Art,Print,"名所江戶百景 目黒太鼓橋夕ひの岡|Taiko Bridge, Meguro, on a Snowy Evening",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,4th month snake year 1857,1857,1857,Polychrome woodblock print; ink and color on paper,14 1/4 x 9 1/4 in. (36.2 x 23.5 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37095,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP646,false,true,37096,Asian Art,Print,"名所江戸百景 真間の紅葉手古那の社継はし|Maples at Mama, from the series One Hundred Famous Views of Edo",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1857,1847,1867,Polychrome woodblock print; ink and color on paper,Oban 14 1/8 x 9 1/16 in. (35.9 x 23.0 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37096,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP751,false,true,37195,Asian Art,Print,東海道五十三次 日本橋|Nihon bashi,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1848–49,1848,1849,Polychrome woodblock print; ink and color on paper,Overall: 8 3/4 x 13 3/4in. (22.2 x 34.9cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37195,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP752,false,true,37196,Asian Art,Print,東海道五十三次 品川 鮫州の茶や|Shinagawa,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1840,1830,1850,Polychrome woodblock print; ink and color on paper,Overall: 8 3/4 x 13 3/4in. (22.2 x 34.9cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37196,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP753,false,true,37197,Asian Art,Print,東海道五十三次 川崎 六郷のわたし|Kawasaki,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1840,1830,1850,Polychrome woodblock print; ink and color on paper,8 23/32 x 13 23/32 in. (22.2 x 34.9 cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37197,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP754,false,true,37198,Asian Art,Print,東海道五十三次 神奈川 台の茶や|Kanagawa,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1840,1830,1850,Polychrome woodblock print; ink and color on paper,Overall: 8 3/4 x 13 3/4in. (22.2 x 34.9cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37198,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP755,false,true,37199,Asian Art,Print,東海道五十三次 程ヶ谷 かたびら橋 かたびら川|Hodogaya,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1840,1830,1850,Polychrome woodblock print; ink and color on paper,8 23/32 x 13 23/32 in. (22.2 x 34.9 cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37199,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP756,false,true,37200,Asian Art,Print,東海道五十三次 戸塚|Totsuka,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1840,1830,1850,Polychrome woodblock print; ink and color on paper,Overall: 8 3/4 x 13 3/4in. (22.2 x 34.9cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37200,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP757,false,true,37201,Asian Art,Print,東海道五十三次 藤澤|Fujisawa,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1840,1830,1850,Polychrome woodblock print; ink and color on paper,Overall: 8 3/4 x 13 3/4in. (22.2 x 34.9cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37201,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP758,false,true,37202,Asian Art,Print,東海道五十三次 平塚|Hiratsuka,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1840,1830,1850,Polychrome woodblock print; ink and color on paper,Overall: 8 3/4 x 13 3/4in. (22.2 x 34.9cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37202,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP759,false,true,37203,Asian Art,Print,東海道五十三次 大磯 鴫立沢西行庵|Ōiso,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1840,1830,1850,Polychrome woodblock print; ink and color on paper,8 23/32 x 13 23/32 in. (22.2 x 34.9 cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37203,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP760,false,true,37204,Asian Art,Print,東海道五十三次 小田原 酒匂川|Odawara,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1840,1830,1850,Polychrome woodblock print; ink and color on paper,Overall: 8 3/4 x 13 3/4in. (22.2 x 34.9cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37204,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP761,false,true,37205,Asian Art,Print,東海道五十三次 箱根 夜中松明とり|Hakone,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1848–49,1848,1849,Polychrome woodblock print; ink and color on paper,Overall: 8 3/4 x 13 3/4in. (22.2 x 34.9cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37205,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP762,false,true,37206,Asian Art,Print,東海道五十三次 三島|Mishima,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1840,1830,1850,Polychrome woodblock print; ink and color on paper,Overall: 8 3/4 x 13 3/4in. (22.2 x 34.9cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37206,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP763,false,true,37207,Asian Art,Print,東海道五十三次 沼津|Numazu,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1840,1830,1850,Polychrome woodblock print; ink and color on paper,Overall: 8 3/4 x 13 3/4in. (22.2 x 34.9cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37207,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP764,false,true,37208,Asian Art,Print,東海道五十三次 原|Hara,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1848–49,1848,1849,Polychrome woodblock print; ink and color on paper,Overall: 8 3/4 x 13 3/4in. (22.2 x 34.9cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37208,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP765,false,true,37209,Asian Art,Print,東海道五十三次 吉原 名所左り不二|Yoshiwara,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1840,1830,1850,Polychrome woodblock print; ink and color on paper,Overall: 8 3/4 x 13 3/4in. (22.2 x 34.9cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37209,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP766,false,true,37210,Asian Art,Woodblock print,東海道五十三次 蒲原 富士川渡舟|Kambara,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1840,1830,1850,Polychrome woodblock print; ink and color on paper,Overall: 8 3/4 x 13 3/4in. (22.2 x 34.9cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37210,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP767,false,true,37211,Asian Art,Print,東海道五十三次 由井|Yui,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1840,1830,1850,Polychrome woodblock print; ink and color on paper,Overall: 8 3/4 x 13 3/4in. (22.2 x 34.9cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37211,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP768,false,true,37212,Asian Art,Print,東海道五十三次 奥津 清見かせき 清見寺|Okitsu,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1840,1830,1850,Polychrome woodblock print; ink and color on paper,Overall: 8 3/4 x 13 3/4in. (22.2 x 34.9cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37212,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP769,false,true,37213,Asian Art,Print,東海道五十三次 江尻|Ejiri,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1840,1830,1850,Polychrome woodblock print; ink and color on paper,Overall: 8 3/4 x 13 3/4in. (22.2 x 34.9cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37213,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP770,false,true,37214,Asian Art,Print,東海道五十三次 府中|Fuchu,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1840,1830,1850,Polychrome woodblock print; ink and color on paper,Overall: 8 3/4 x 13 3/4in. (22.2 x 34.9cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37214,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP771,false,true,37215,Asian Art,Print,"東海道五十三次 鞠子|Mariko, from the series Tokaidō (popularly known as the Reisho Tokaidō)",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1840,1830,1850,Polychrome woodblock print; ink and color on paper,Overall: 8 3/4 x 13 3/4in. (22.2 x 34.9cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37215,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP772,false,true,37216,Asian Art,Print,東海道五十三次 岡部 宇津の山|Okabe,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1840,1830,1850,Polychrome woodblock print; ink and color on paper,Overall: 8 3/4 x 13 3/4in. (22.2 x 34.9cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37216,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP773,false,true,37217,Asian Art,Print,東海道五十三次 藤枝|Fujieda,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1840,1830,1850,Polychrome woodblock print; ink and color on paper,Overall: 8 3/4 x 13 3/4in. (22.2 x 34.9cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37217,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP774,false,true,37218,Asian Art,Print,東海道五十三次 島田 大井川|Shimada,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1840,1830,1850,Polychrome woodblock print; ink and color on paper,Overall: 8 3/4 x 13 3/4in. (22.2 x 34.9cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37218,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP775,false,true,37219,Asian Art,Print,東海道五十三次 金谷 かなや駅 大井川|Kanaya,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1840,1830,1850,Polychrome woodblock print; ink and color on paper,Overall: 8 3/4 x 13 3/4in. (22.2 x 34.9cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37219,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP776,false,true,37220,Asian Art,Print,東海道五十三次 日阪 夜啼石 無間山 小夜の中山|Nissaka,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1848–49,1848,1849,Polychrome woodblock print; ink and color on paper,Overall: 8 3/4 x 13 3/4in. (22.2 x 34.9cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37220,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP777,false,true,37221,Asian Art,Print,東海道五十三次 懸川 秋葉山別道|Kakegawa,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1840,1830,1850,Polychrome woodblock print; ink and color on paper,Overall: 8 3/4 x 13 3/4in. (22.2 x 34.9cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37221,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP778,false,true,37222,Asian Art,Print,東海道五十三次 袋井 名物遠川だこ|Fukoroi,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1840,1830,1850,Polychrome woodblock print; ink and color on paper,Overall: 8 3/4 x 13 3/4in. (22.2 x 34.9cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37222,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP779,false,true,37223,Asian Art,Print,東海道五十三次 見附 天竜川渡舟|Mitsuke,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1840,1830,1850,Polychrome woodblock print; ink and color on paper,Overall: 8 3/4 x 13 3/4in. (22.2 x 34.9cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37223,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP780,false,true,37224,Asian Art,Print,東海道五十三次 はま松|Hamamatsu,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1840,1830,1850,Polychrome woodblock print; ink and color on paper,Overall: 8 3/4 x 13 3/4in. (22.2 x 34.9cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37224,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP781,false,true,37225,Asian Art,Print,東海道五十三次 舞坂|Maizaka,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1840,1830,1850,Polychrome woodblock print; ink and color on paper,Overall: 8 3/4 x 13 3/4in. (22.2 x 34.9cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37225,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP782,false,true,37226,Asian Art,Print,東海道五十三次 荒井|Arai,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1840,1830,1850,Polychrome woodblock print; ink and color on paper,Overall: 8 3/4 x 13 3/4in. (22.2 x 34.9cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37226,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP783,false,true,37227,Asian Art,Print,東海道五十三次 白須賀 汐見坂|Shirasuke,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1840,1850,1850,Polychrome woodblock print; ink and color on paper,Overall: 8 3/4 x 13 3/4in. (22.2 x 34.9cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37227,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP784,false,true,37228,Asian Art,Print,東海道五十三次 二川  猿ヶ馬場|Futagawa,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1840,1830,1850,Polychrome woodblock print; ink and color on paper,Overall: 8 3/4 x 13 3/4in. (22.2 x 34.9cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37228,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP785,false,true,37229,Asian Art,Print,東海道五十三次 吉田 六月十五日天王祭|Yoshida,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1840,1830,1850,Polychrome woodblock print; ink and color on paper,Overall: 8 3/4 x 13 3/4in. (22.2 x 34.9cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37229,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP786,false,true,37230,Asian Art,Print,東海道五十三次 御油 古街道本野ヶ原|Goyu,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1840,1830,1850,Polychrome woodblock print; ink and color on paper,Overall: 8 3/4 x 13 3/4 in. (22.2 x 34.9 cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37230,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP787,false,true,37231,Asian Art,Print,東海道五十三次 赤阪|Akasaka,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1840,1830,1850,Polychrome woodblock print; ink and color on paper,Overall: 8 3/4 x 13 3/4in. (22.2 x 34.9cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37231,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP788,false,true,37232,Asian Art,Print,東海道五十三次 藤川|Fujikawa,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1840,1830,1850,Polychrome woodblock print; ink and color on paper,Overall: 8 3/4 x 13 3/4in. (22.2 x 34.9cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37232,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP789,false,true,37233,Asian Art,Print,東海道五十三次 岡崎 矢はぎ川|Okazaki,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1840,1830,1850,Polychrome woodblock print; ink and color on paper,Overall: 8 3/4 x 13 3/4in. (22.2 x 34.9cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37233,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP790,false,true,37234,Asian Art,Print,東海道五十三次 池鯉鮒|Chiryu,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1840,1830,1850,Polychrome woodblock print; ink and color on paper,Overall: 8 3/4 x 13 3/4in. (22.2 x 34.9cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37234,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP791,false,true,37235,Asian Art,Print,東海道五十三次 鳴海 名産絞り店|Narumi,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1840,1830,1850,Polychrome woodblock print; ink and color on paper,Overall: 8 3/4 x 13 3/4in. (22.2 x 34.9cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37235,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP792,false,true,37236,Asian Art,Print,東海道五十三次 宮 七里の渡し 熱田鳥居 寝覚の里|Miya,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1840,1830,1850,Polychrome woodblock print; ink and color on paper,Overall: 8 3/4 x 13 3/4in. (22.2 x 34.9cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37236,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP793,false,true,37237,Asian Art,Print,東海道五十三次 桑名 七里の渡舟|Kuwana,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1840,1830,1850,Polychrome woodblock print; ink and color on paper,Overall: 8 3/4 x 13 3/4in. (22.2 x 34.9cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37237,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP794,false,true,37238,Asian Art,Print,東海道五十三次 四日市 日永村追分 参宮道|Yokkaichi,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1840,1830,1850,Polychrome woodblock print; ink and color on paper,Overall: 8 3/4 x 13 3/4in. (22.2 x 34.9cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37238,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP795,false,true,37239,Asian Art,Print,東海道五十三次 石薬師|Ishiyakushi,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1840,1830,1850,Polychrome woodblock print; ink and color on paper,Overall: 8 3/4 x 13 3/4in. (22.2 x 34.9cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37239,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP796,false,true,37240,Asian Art,Print,東海道五十三次 庄野|Shono,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1840,1830,1850,Polychrome woodblock print; ink and color on paper,Overall: 8 3/4 x 13 3/4in. (22.2 x 34.9cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37240,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP797,false,true,37241,Asian Art,Print,東海道五十三次 亀山|Kameyama,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1840,1830,1850,Polychrome woodblock print; ink and color on paper,Overall: 8 3/4 x 13 3/4in. (22.2 x 34.9cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37241,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP798,false,true,37242,Asian Art,Print,東海道五十三次 関|Seki,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1848–49,1848,1849,Polychrome woodblock print; ink and color on paper,Overall: 8 3/4 x 13 3/4in. (22.2 x 34.9cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37242,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP799,false,true,37243,Asian Art,Print,東海道五十三次 坂の下|Saka no Shita,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1840,1830,1850,Polychrome woodblock print; ink and color on paper,Overall: 8 3/4 x 13 3/4in. (22.2 x 34.9cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37243,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP800,false,true,37244,Asian Art,Print,東海道五十三次 土山|Tsuchiyama,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1840,1830,1850,Polychrome woodblock print; ink and color on paper,Overall: 8 3/4 x 13 3/4in. (22.2 x 34.9cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37244,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP801,false,true,37245,Asian Art,Print,東海道五十三次 水口 平松山美松|Minaguchi,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1840,1830,1850,Polychrome woodblock print; ink and color on paper,Overall: 8 3/4 x 13 3/4in. (22.2 x 34.9cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37245,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP802,false,true,37246,Asian Art,Print,東海道五十三次 石部|Ishibe,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1840,1830,1850,Polychrome woodblock print; ink and color on paper,Overall: 8 3/4 x 13 3/4in. (22.2 x 34.9cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37246,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP803,false,true,37247,Asian Art,Print,東海道五十三次 草津 矢ばせの渡口 琵琶湖風景|Kusatsu,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1840,1830,1850,Polychrome woodblock print; ink and color on paper,Overall: 8 3/4 x 13 3/4in. (22.2 x 34.9cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37247,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP804,false,true,37248,Asian Art,Print,東海道五十三次 大津|Otsu,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1840,1830,1850,Polychrome woodblock print; ink and color on paper,Overall: 8 3/4 x 13 3/4in. (22.2 x 34.9cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37248,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP805,false,true,37249,Asian Art,Print,東海道五十三次 京 三条大はし|Kyoto,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1848–49,1848,1849,Polychrome woodblock print; ink and color on paper,Overall: 8 3/4 x 13 3/4 in. (22.2 x 34.9 cm),"The Francis Lathrop Collection, Purchase, Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37249,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1021,false,true,53697,Asian Art,Print,六十余州名所図会 壱岐 志作|Winter View of Shimasaku in the Province of Iki,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,1856,1856,1856,Polychrome woodblock print; ink and color on paper,H. 13 1/2 in. (34.3 cm); W. 8 13/16 in. (22.4 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/53697,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1022,false,true,54930,Asian Art,Woodblock print,"富士三十六景 伊豆の山中|View of Fuji san from the Mountains in the Province of Izu (Izu no Sanchu), from the series Thirty-six Views of Mount Fuji (Fugaku sanjūrokkei)",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,1858,1858,1858,Polychrome woodblock print; ink and color on paper,H. 12 5/8 in. (32.1 cm); W. 8 1/4 in. (21 cm) (trimmed),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54930,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1024,false,true,54931,Asian Art,Print,名所江戸百景・廓中東雲|The Entrance to the Yoshiwara at Dawn,Japan,Edo period (1615–1868),,,,Artist,Designed by,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,1857,1857,1857,Polychrome woodblock print; ink and color on paper,H. 13 3/8 in. (34 cm); W. 8 3/4 in. (22.2 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54931,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1025,false,true,53680,Asian Art,Print,名所江戸百景 浅草 金龍山|The Kinryusan Temple at Asakusa,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,1856,1856,1856,Polychrome woodblock print; ink and color on paper,H. 13 3/8 in. (34 cm); W. 8 3/4 in. (22.2 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/53680,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1026,false,true,54935,Asian Art,Print,"名所江戸百景 真崎辺より水神の森内川関屋の里を見る|Susaki Hen-yori Suijin no Mori, Uchikawa",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,1857,1857,1857,Polychrome woodblock print; ink and color on paper,H. 13 3/8 in. (34 cm); W. 8 3/4 in. (22.2 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54935,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1027,false,true,54938,Asian Art,Woodblock print,道中膝栗毛|The Practical Jokers Yajirobei and Kitahachi,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1840,1830,1850,Monochrome woodblock print; ink on paper,H. 9 in. (22.9 cm); W. 13 7/8 in. (35.2 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54938,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1028,false,true,54940,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1840,1830,1850,Monochrome woodblock print; ink on paper,H. 8 3/4 in. (22.2 cm); W. 13 3/4 in. (34.9 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54940,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1029,false,true,54942,Asian Art,Woodblock print,道中膝栗毛 四日市追分|The Branch Road at Yokkaichi,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1840,1830,1850,Polychrome woodblock print; ink and color on paper,H. 9 in. (22.9 cm); W. 13 7/8 in. (35.2 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54942,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1030,false,true,54944,Asian Art,Print,道中膝栗毛 京都の町|Street in Kyoto,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1840,1830,1850,Polychrome woodblock print; ink and color on paper,H. 9 in. (22.9 cm); W. 13 7/8 in. (35.2 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54944,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1129,false,true,55049,Asian Art,Print,"東海道五十三次之内 平塚 縄手道|Hiratsuka, Nawate Do",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1834,1824,1844,Polychrome woodblock print; ink and color on paper,H. 9 15/16 in. (25.2 cm); W. 14 13/16 in. (37.6 cm),"Rogers Fund, 1919",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55049,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1155,false,true,45303,Asian Art,Print,江戸高名会亭尽 両国|The Aoyagi in Ryogoku,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1835–42,1825,1852,Polychrome woodblock print (surimono); ink and color on paper,H. 9 15/16 in. (25.2 cm); W. 14 7/16 in. (36.7 cm),"Rogers Fund, 1919",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45303,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1156,false,true,45304,Asian Art,Print,江戸高名会亭尽 浅草雷門前 かめや|Asakusa Kaminarimon Mae (Kameya),Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1835–42,1825,1852,Polychrome woodblock print; ink and color on paper,H. 9 7/8 in. (25.1 cm); W. 14 3/4 in. (37.5 cm),"Rogers Fund, 1919",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45304,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1157,false,true,45305,Asian Art,Print,江戸高名会亭尽 両国柳橋 大のし|The Ono at Ryogoku Yanagibashi,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1835–42,1825,1852,Polychrome woodblock print; ink and color on paper,H. 9 7/8 in. (25.1 cm); W. 14 9/16 in. (37 cm),"Rogers Fund, 1919",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45305,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1158,false,true,45306,Asian Art,Print,江戸高名会亭尽 柳ばし夜景 万八|The Manpachi at Evening in Yanagibashi,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1835–42,1825,1852,Polychrome woodblock print; ink and color on paper,H. 9 13/16 in. (24.9 cm); W. 14 9/16 in. (37 cm),"Rogers Fund, 1919",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45306,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1159,false,true,45307,Asian Art,Print,江戸高名会亭尽 柳島の図 橋本|Yanagishima no Zu,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1835–42,1825,1852,Polychrome woodblock print; ink and color on paper,H. 9 5/16 in. (23.7 cm); W. 14 3/16 in. (36 cm),"Rogers Fund, 1919",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45307,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1160,false,true,45308,Asian Art,Print,Ryogoku Yanagibashi (Umegawa)|江戸高名会亭尽 柳ばし|The Umegawa at Ryogoku Yanagibashi,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1835–42,1825,1852,Polychrome woodblock print; ink and color on paper,H. 9 7/16 in. (24 cm); W. 14 3/16 in. (36 cm),"Rogers Fund, 1919",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45308,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1161,false,true,45309,Asian Art,Print,Mukōjima (Daikokuya)|江戸高名会亭尽 向島 大七|The Daikokuya at Mukojima,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1835–42,1825,1852,Polychrome woodblock print; ink and color on paper,H. 9 5/16 in. (23.7 cm); W. 14 3/16 in. (36 cm),"Rogers Fund, 1919",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45309,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1162,false,true,45310,Asian Art,Print,江戸高名会亭尽 大をんし前 田川屋|Daisenji Mae (Tagawaya),Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1835–42,1825,1852,Polychrome woodblock print; ink and color on paper,H. 9 7/8 in. (25.1 cm); W. 14 9/16 in. (37 cm),"Rogers Fund, 1919",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45310,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1163,false,true,45311,Asian Art,Print,江戸高名会亭尽 隅田川橋場渡之図 柳屋|Sumidagawa Hashiba Watashi Zu (Yanagiya),Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1835–42,1825,1852,Polychrome woodblock print; ink and color on paper,H. 9 in. (22.9 cm); W. 13 7/8 in. (35.2 cm),"Rogers Fund, 1919",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45311,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1164,false,true,45312,Asian Art,Print,Ryogoku Yanagibashi (Kawachiya)|江戸高名会亭尽 両国柳橋 河内屋|Tea-house at the Willow Bridge,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1835–42,1825,1852,Polychrome woodblock print; ink and color on paper,H. 9 5/16 in. (23.7 cm); W. 14 3/16 in. (36 cm),"Rogers Fund, 1919",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45312,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1165,false,true,45313,Asian Art,Print,Fukagawa Hachiman Keidai (Niken Jyaya)|江戸高名会亭尽 深川八幡境内 二軒茶屋|Tea-house inside Hachiman Shrine,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1835–42,1825,1852,Polychrome woodblock print; ink and color on paper,H. 9 5/8 in. (24.4 cm); W. 14 5/8 in. (37.1 cm),"Rogers Fund, 1919",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45313,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1166,false,true,45314,Asian Art,Print,Shitaya Hirokōji (Oike)|江戸高名会亭尽 下谷広小路 河内楼|Teahouse at Hirokōji,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1835–42,1615,1868,Polychrome woodblock print; ink and color on paper,H. 9 7/8 in. (25.1 cm); W. 14 1/2 in. (36.8 cm),"Rogers Fund, 1919",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45314,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1167,false,true,45315,Asian Art,Print,Shinyoshiwara Emonzaka Nihonzutsumi (Harimaya)|江戸高名会亭尽 新吉原衣紋坂日本堤 播磨屋|The Harimaya at Shinyoshiwara Emonzaka Nihonzutsumi,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1835–42,1825,1852,Polychrome woodblock print; ink and color on paper,H. 9 5/8 in. (24.4 cm); W. 14 3/4 in. (37.5 cm),"Rogers Fund, 1919",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45315,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1168,false,true,45316,Asian Art,Print,Hakusan Keiseiga Kubo (Daisen)|江戸高名会亭尽 白山傾城ヶ窪|Tea house in Hakusen district,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1835–42,1825,1852,Polychrome woodblock print; ink and color on paper,H. 9 5/8 in. (24.4 cm); W. 14 3/8 in. (36.5 cm),"Rogers Fund, 1919",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45316,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1169,false,true,45317,Asian Art,Print,Mokuboji Yukimi (Uekiya)|江戸高名会亭尽 木母寺雪見 植木屋|Uekiya Restaurant at Mokuboji,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1835–42,1825,1852,Polychrome woodblock print; ink and color on paper,H. 9 3/8 in. (23.8 cm); W. 14 3/16 in. (36 cm),"Rogers Fund, 1919",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45317,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1170,false,true,45318,Asian Art,Print,江戸高名会亭尽 王子 扇屋|The Ōgiya at Ōji,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1835–42,1615,1868,Polychrome woodblock print; ink and color on paper,H. 9 3/8 in. (23.8 cm); W. 14 3/16 in. (36.1 cm),"Rogers Fund, 1919",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45318,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1171,false,true,55051,Asian Art,Print,江戸高名会亭尽 芝神明社内 車轍楼|Shiba Shinmeisha Uchi (Shatetsu-ro),Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1840,1830,1850,Polychrome woodblock print; ink and color on paper,H. 9 3/4 in. (24.8 cm); W. 14 1/2 in. (36.8 cm),"Rogers Fund, 1919",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55051,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1172,false,true,55052,Asian Art,Woodblock print,江戸高名会亭尽 亀戸裏門 玉屋|Kameido Uramon (Tama-ya),Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1840,1830,1850,Polychrome woodblock print; ink and color on paper,H. 9 3/8 in. (23.8 cm); W. 14 3/16 in. (36 cm),"Rogers Fund, 1919",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55052,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1173,false,true,55053,Asian Art,Print,江戸高名会亭尽 牛嶋 武蔵屋|Ushijima (Musashi-ya),Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1840,1830,1850,Polychrome woodblock print; ink and color on paper,H. 9 3/8 in. (23.8 cm); W. 14 3/16 in. (36 cm),"Rogers Fund, 1919",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55053,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1174,false,true,55054,Asian Art,Print,江戸高名会亭尽 向島の図 平岩|Mukojima no Zu (Hira-Iwa),Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1840,1830,1850,Polychrome woodblock print; ink and color on paper,H. 8 13/16 in. (22.4 cm); W. 13 3/4 in. (34.9 cm),"Rogers Fund, 1919",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55054,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1175,false,true,55057,Asian Art,Print,江戸高名会亭尽 本所小梅 小倉庵|Honjo Komme (Ogura-an),Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1840,1830,1850,Polychrome woodblock print; ink and color on paper,H. 9 1/2 in. (24.1 cm); W. 14 7/16 in. (36.7 cm),"Rogers Fund, 1919",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55057,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1176,false,true,55059,Asian Art,Print,江戸高名会亭尽 湯島 松琴亭|Yushima (Matsu Kane-ya),Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1840,1830,1850,Polychrome woodblock print; ink and color on paper,H. 9 15/16 in. (25.2 cm); W. 14 7/16 in. (36.7 cm),"Rogers Fund, 1919",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55059,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1177,false,true,55061,Asian Art,Print,江戸高名会亭尽 池之端 蓬莱亭 青楼花見の休み|Ikeno Mata (Horai-ya),Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1840,1830,1850,Polychrome woodblock print; ink and color on paper,H. 9 7/8 in. (25.1 cm); W. 14 5/8 in. (37.1 cm),"Rogers Fund, 1919",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55061,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1178,false,true,55075,Asian Art,Print,江戸高名会亭尽 今戸橋之図 金波楼|Imadobashi no Zu (Tama-Sho),Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1840,1830,1850,Polychrome woodblock print; ink and color on paper,H. 9 7/16 in. (24 cm); W. 14 3/16 in. (36 cm),"Rogers Fund, 1919",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55075,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1179,false,true,55077,Asian Art,Print,江戸高名会亭尽 三囲之景 出羽屋|Mimeguri no Kei (Toyoha-ya),Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1840,1830,1850,Polychrome woodblock print; ink and color on paper,H. 9 3/8 in. (23.8 cm); W. 14 3/16 in. (36 cm),"Rogers Fund, 1919",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55077,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1180,false,true,55078,Asian Art,Print,江戸高名会亭尽 雑司ヶ谷の図 茗荷屋|Zoshigaya no Zu (Myoga-ya),Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1840,1830,1850,Polychrome woodblock print; ink and color on paper,H. 9 7/16 in. (24 cm); W. 14 3/16 in. (36 cm),"Rogers Fund, 1919",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55078,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1181,false,true,55079,Asian Art,Woodblock print,江戸高名会亭尽 山谷 八百善|San-ya (Yaozen),Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1840,1830,1850,Polychrome woodblock print; ink and color on paper,H. 9 3/8 in. (23.8 cm); W. 14 3/16 in. (36 cm),"Rogers Fund, 1919",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55079,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1182,false,true,55081,Asian Art,Print,名所江戸百景 千駄木団子坂花屋敷|Sendagi Dangozaka,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,1856,1856,1856,Polychrome woodblock print; ink and color on paper,Aiban; H. 13 7/8 in. (35.2 cm); W. 9 3/4 in. (24.8 cm),"Rogers Fund, 1919",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55081,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1183,false,true,55084,Asian Art,Woodblock print,"名所江戸百景 馬喰町初音の馬場|The First Race Course, Horse-Dealer's Street",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,1857,1857,1857,Polychrome woodblock print; ink and color on paper,H. 14 1/16 in. (35.7 cm); W. 9 1/4 in. (23.5 cm),"Rogers Fund, 1919",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55084,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1184,false,true,37316,Asian Art,Print,"名所江戸百景 深川萬年橋|Mannen Bridge, Fukagawa",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,1858,1858,1858,Polychrome woodblock print; ink and color on paper,H. 14 1/16 in. (35.7 cm); W. 9 7/16 in. (24 cm),"Rogers Fund, 1919",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37316,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1185,false,true,55095,Asian Art,Print,名所江戸百景 月の岬|Moon Viewing Point,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,1857,1857,1857,Polychrome woodblock print; ink and color on paper,Oban 13 7/8 x 9 5/8 in. (35.2 x 24.4 cm),"Rogers Fund, 1919",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55095,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1186,false,true,55096,Asian Art,Print,"富士三十六景 相州三浦の海上|Fuji from Miura, Sagami (Soshu Miura no Kaijo), from the series Thirty-six Views of Mount Fuji (Fugaku sanjūrokkei)",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,1858,1858,1858,Polychrome woodblock print; ink and color on paper,H. 14 1/4 in. (36.2 cm); W. 9 7/16 in. (24 cm),"Rogers Fund, 1919",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55096,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1187,false,true,49926,Asian Art,Print,近江八景 辛崎夜雨|Evening Rain at Karasaki Pine Tree,Japan,Edo period (1615–1868),,,,Artist,Designed by,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1857,1847,1867,Polychrome woodblock print; ink and color on paper,H. 6 1/2 in. (16.5 cm); W. 9 1/16 in. (23 cm),"Rogers Fund, 1919",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/49926,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1188,false,true,49927,Asian Art,Print,近江八景 石山秋月|Autumn Moon at Ishiyama,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,1857,1857,1857,Polychrome woodblock print; ink and color on paper,H. 6 5/8 in. (16.8 cm); W. 9 in. (22.9 cm),"Rogers Fund, 1919",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/49927,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1189,false,true,49928,Asian Art,Print,近江八景 比良暮雪|Evening Snow at Mt. Hira,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1857,1847,1867,Polychrome woodblock print; ink and color on paper,H. 6 5/8 in. (16.8 cm); W. 8 3/4 in. (22.2 cm),"Rogers Fund, 1919",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/49928,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1190,false,true,49929,Asian Art,Print,近江八景 堅田落雁|Returning Geese at Katata,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,1857,1857,1857,Polychrome woodblock print; ink and color on paper,H. 6 13/16 in. (17.3 cm); W. 9 in. (22.9 cm),"Rogers Fund, 1919",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/49929,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1191,false,true,49930,Asian Art,Print,近江八景 瀬田夕照|Sunset at Seta,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1857,1847,1867,Polychrome woodblock print; ink and color on paper,H. 6 5/8 in. (16.8 cm); W. 9 in. (22.9 cm),"Rogers Fund, 1919",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/49930,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1192,false,true,49931,Asian Art,Woodblock print,近江八景 三井晩鍾|Vesper Bells at Mii Temple,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1857,1847,1867,Polychrome woodblock print; ink and color on paper,H. 6 11/16 in. (17 cm); W. 9 1/8 in. (23.2 cm),"Rogers Fund, 1919",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/49931,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1193,false,true,49932,Asian Art,Woodblock print,近江八景 矢橋帰帆|Fishing Boats Sailing Back to Yabase,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1857,1847,1867,Polychrome woodblock print; ink and color on paper,H. 6 11/16 in. (17 cm); W. 9 1/16 in. (23 cm),"Rogers Fund, 1919",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/49932,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1194,false,true,49933,Asian Art,Print,近江八景 粟津晴嵐|Clearing Weather at Awazu,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,1857,1857,1857,Polychrome woodblock print; ink and color on paper,H. 6 13/16 in. (17.3 cm); W. 9 1/8 in. (23.2 cm),"Rogers Fund, 1919",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/49933,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1197,false,true,53781,Asian Art,Print,"六十余州名所図会 肥後 五ヶの庄|Goka no Shō, Higo Province, from the series Views of Famous Places in the Sixty-Odd Provinces",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1853,1843,1863,Polychrome woodblock print; ink and color on paper,H. 13 5/8 in. (34.6 cm); W. 9 1/4 in. (23.5 cm),"Rogers Fund, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/53781,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1198,false,true,53783,Asian Art,Print,"六十余州名所図会 阿波 鳴門の風波|Naruto Whirlpool, Awa Province, from the series Views of Famous Places in the Sixty-Odd Provinces",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1853,1843,1863,Polychrome woodblock print; ink and color on paper,Oban tate-e 14 x 9 5/8 in. (35.6 x 24.4 cm),"Rogers Fund, 1919",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/53783,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1199,false,true,53784,Asian Art,Print,"六十余州名所図会 備後 阿武門観音堂|Kannondo, Abuto, Bingo Province, from the series Views of Famous Places in the Sixty-Odd Provinces",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1853,1843,1863,Polychrome woodblock print; ink and color on paper,H. 14 5/8 in. (37.1 cm); W. 10 1/15 in. (25.6 cm),"Rogers Fund, 1919",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/53784,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1200,false,true,55116,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,1833–43,1833,1843,Polychrome woodblock print; ink and color on paper,H. 8 5/8 in. (21.9 cm); W. 13 1/2 in. (34.3 cm),"Rogers Fund, 1919",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55116,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1202,false,true,55120,Asian Art,Print,浪花名所図会 順慶町夜見世の図|Junkei machi Yomise no Zu,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1828,1818,1838,Polychrome woodblock print; ink and color on paper,H. 9 1/4 in. (23.5 cm); W. 14 3/16 in. (36 cm),"Rogers Fund, 1919",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55120,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1203,false,true,55122,Asian Art,Print,近江八景 石山秋月|The Autumn Moon on Ishiyama,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,1857,1857,1857,Polychrome woodblock print; ink and color on paper,H. 14 5/8 in. (37.1 cm); W. 9 15/16 in. (25.2 cm),"Rogers Fund, 1919",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55122,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1204,false,true,55123,Asian Art,Print,五十三次名所図会 藤川 山中の里別名宮路山|Fujikawa; Sanchu Yamanaka no Sato Miyajiyama,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,1855,1855,1855,Polychrome woodblock print; ink and color on paper,H. 14 7/16 in. (36.7 cm); W. 9 3/4 in. (24.8 cm),"Rogers Fund, 1919",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55123,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1205,false,true,55126,Asian Art,Print,五十三次名所図会 御油 本野ヶ原本坂ごへ|Goyu,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,1855,1855,1855,Polychrome woodblock print; ink and color on paper,H. 14 1/16 in. (35.7 cm); W. 9 1/4 in. (23.5 cm),"Rogers Fund, 1919",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55126,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1206,false,true,55130,Asian Art,Print,五十三次名所図会 見付 天竜川舟渡し|Mitsuke,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,1855,1855,1855,Polychrome woodblock print; ink and color on paper,H. 14 in. (35.6 cm); W. 9 1/4 in. (23.5 cm),"Rogers Fund, 1919",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55130,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1207,false,true,55131,Asian Art,Print,五十三次名所図会 水口|Mizukuchi,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,1855,1855,1855,Polychrome woodblock print; ink and color on paper,H. 14 9/16 in. (37 cm); W. 9 13/16 in. (24.9 cm),"Rogers Fund, 1919",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55131,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1255,false,true,55152,Asian Art,Print,東海道五十三次 日本橋|Nihon Bashi,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1838,1828,1848,Polychrome woodblock print; ink and color on paper,H. 9 1/8 in. (23.2 cm); W. 6 5/8 in. (16.8 cm),"Rogers Fund, 1921",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55152,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1256,false,true,55153,Asian Art,Print,東海道五十三次 吉原 左リ富士ノ縄手|Yoshiwara,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1838,1828,1848,Polychrome woodblock print; ink and color on paper,H. 8 11/16 in. (22.1 cm); W. 6 5/16 in. (16 cm),"Rogers Fund, 1921",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55153,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1257,false,true,55154,Asian Art,Print,東海道五十三次 由井 由井川|Yui,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1838,1828,1848,Polychrome woodblock print; ink and color on paper,H. 8 1/2 in. (21.6 cm); W. 6 1/2 in. (16.5 cm),"Rogers Fund, 1921",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55154,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1258,false,true,55155,Asian Art,Print,"東海道五十三次 嶋田 大井川|Shimada; Oigawa Shun-Gan, Banks of the Oi River",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1838,1828,1848,Polychrome woodblock print; ink and color on paper,H. 8 7/8 in. (22.5 cm); W. 6 1/2 in. (16.5 cm),"Rogers Fund, 1921",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55155,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1259,false,true,55156,Asian Art,Woodblock print,"東海道五十三次 日阪 倭園琴桜|Nissaka; Sayo no Naka Yama, pass in the Bayo Mountains",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1838,1828,1848,Polychrome woodblock print; ink and color on paper,H. 8 5/8 in. (21.9 cm); W. 6 3/8 in. (16.2 cm),"Rogers Fund, 1921",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55156,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1260,false,true,55157,Asian Art,Print,東海道五十三次 掛川|Kakegawa; Akiba-san Embo,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1838,1828,1848,Polychrome woodblock print; ink and color on paper,H. 9 1/8 in. (23.2 cm); W. 6 5/8 in. (16.8 cm),"Rogers Fund, 1921",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55157,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1261,false,true,55158,Asian Art,Print,東海道五十三次 浜松|Hamamatsu,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1838,1828,1848,Polychrome woodblock print; ink and color on paper,H. 8 15/16 in. (22.7 cm); W. 6 11/16 in. (17 cm),"Rogers Fund, 1921",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55158,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1262,false,true,55159,Asian Art,Print,東海道五十三次 二川|Futagawa,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1838,1828,1848,Polychrome woodblock print; ink and color on paper,H. 8 11/16 in. (22.1 cm); W. 6 7/16 in. (16.4 cm),"Rogers Fund, 1912",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55159,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1263,false,true,55160,Asian Art,Print,"東海道五十三次 吉田|Yoshida; Toyokawa-Bashi, Toyokawa Bridge",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1838,1828,1848,Polychrome woodblock print; ink and color on paper,H. 8 1/2 in. (21.6 cm); W. 6 7/16 in. (16.4 cm),"Rogers Fund, 1921",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55160,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1264,false,true,55161,Asian Art,Print,東海道五十三次 御油|Goyu,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1838,1828,1848,Polychrome woodblock print; ink and color on paper,H. 8 7/8 in. (22.5 cm); W. 6 1/2 in. (16.5 cm),"Rogers Fund, 1921",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55161,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1265,false,true,55162,Asian Art,Print,東海道五十三次 藤川|Fujikawa,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1838,1828,1848,Polychrome woodblock print; ink and color on paper,H. 9 1/8 in. (23.2 cm); W. 6 11/16 in. (17 cm),"Rogers Fund, 1921",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55162,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1266,false,true,55163,Asian Art,Print,東海道五十三次 石部|Ishibe,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1838,1828,1848,Polychrome woodblock print; ink and color on paper,H. 8 5/8 in. (21.9 cm); W. 6 1/2 in. (16.5 cm),"Rogers Fund, 1921",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55163,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1293,false,true,55235,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1840,1830,1850,Polychrome woodblock print; ink and color on paper,H. 4 3/8 (11.1 cm); W. 6 1/2 in. (16.5 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55235,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1313,false,true,45293,Asian Art,Print,Takanawa no Meigetsu|東都名所 高輪之明月|Full Moon at Takanawa,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1831,1821,1841,Polychrome woodblock print; ink and color on paper,H. 9 5/8 in. (24.4 cm); W. 15 1/8 in. (38.4 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45293,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1317,false,true,55275,Asian Art,Print,木曽海道六拾九次之内 三渡野|Santono Station,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1835,1825,1845,Polychrome woodblock print; ink and color on paper,H. 10 in. (25.4 cm); W. 15 in. (38.1 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55275,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1319,false,true,55278,Asian Art,Print,京都名所之内 四条河原夕涼|Cooling off in the Evening at Shijogawara,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1834,1824,1844,Polychrome woodblock print; ink and color on paper,Image: 8 5/8 x 13 7/8 in. (21.9 x 35.2 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55278,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1468,false,true,55551,Asian Art,Print,忠臣蔵 五段目|Sadakuro Threatening to Kill Yoichibei,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1840,1830,1850,Polychrome woodblock print; ink and color on paper,H. 9 1/8 in. (23.2 cm); W. 13 3/4 in. (34.9 cm),"Rogers Fund, 1925",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55551,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1469,false,true,55552,Asian Art,Print,名所江戸百景 馬喰町初音の馬場|Hatsune no Baba; Bakurocho,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1857,1847,1867,Polychrome woodblock print; ink and color on paper,H. 13 1/2 in. (34.3 cm); W. 8 13/16 in. (22.4 cm),"Rogers Fund, 1925",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55552,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1470,false,true,55553,Asian Art,Print,"名所江戸百景 王子装束ゑの木大晦日の狐火|New Year's Eve Foxfires at the Changing Tree, Ōji",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1857,1847,1867,Polychrome woodblock print; ink and color on paper,Image: 12 13/16 × 8 5/8 in. (32.5 × 21.9 cm),"Rogers Fund, 1925",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55553,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1473,false,true,55557,Asian Art,Print,東海道五十三次之内 神奈川 台之景|View of Kangawa at Sunset,Japan,Edo period (1615–1868),,,,Artist,Designed by,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1834,1824,1844,Polychrome woodblock print; ink and color on paper,H. 9 3/4 in. (24.8 cm); W. 14 in. (35.6 cm),"Gift of Mrs. Henry J. Bernheim, 1945",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55557,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1474,false,true,55558,Asian Art,Print,"東海道五十三次之内 御油 旅人留女|Goyu, Tabibito Ryujo",Japan,Edo period (1615–1868),,,,Artist,Designed by,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1834,1824,1844,Polychrome woodblock print; ink and color on paper,H. 13 7/8 in. (35.2 cm); W. 10 3/4 in. (27.3 cm),"Gift of Mrs. Henry J. Bernheim, 1945",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55558,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1475,false,true,55560,Asian Art,Print,"東海道五十三次之内 鳴海 名物有松絞|Narumi, Meibutsu Arimatsu Shibori",Japan,Edo period (1615–1868),,,,Artist,Designed by,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1734,1724,1744,Polychrome woodblock print; ink and color on paper,H. 9 3/4 in. (24.8 cm); W. 14 1/2 in. (36.8 cm),"Gift of Mrs. Henry J. Bernheim, 1945",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55560,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1476,false,true,55563,Asian Art,Print,五十三次名所図会 草津|Kusatsu,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,1855,1855,1855,Polychrome woodblock print; ink and color on paper,H. 14 1/2 in. (36.8 cm); W. 9 3/4 in. (24.8 cm),"Gift of Mrs. Henry J. Bernheim, 1945",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55563,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1483,false,true,55574,Asian Art,Print,江戸高名会亭尽 深川八幡境内 二軒茶屋|Fukagawa Hachiman Keidai (Niken Jya-ya),Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1840,1830,1850,Polychrome woodblock print; ink and color on paper,H. 9 5/8 in. (24.4 cm); W. 14 5/8 in. (37.1 cm),"Rogers Fund, 1926",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55574,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1484,false,true,55577,Asian Art,Print,浪花名所図会 今宮 十日恵比寿|Imamiya Toka Ebisu,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1828,1818,1838,Polychrome woodblock print; ink and color on paper,H. 9 11/16 in. (24.6 cm); W. 14 11/16 in. (37.3 cm),"Rogers Fund, 1926",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55577,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1487,false,true,55583,Asian Art,Print,歌川広重画 桜花に都鳥|Hooded Gulls and Cherry Blossoms,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,probably 1850–62,1850,1862,Polychrome woodblock print; ink and color on paper,H. 7 7/8 in. (20 cm); W. (top) 9 in. (22.9 cm); (bottom) 7 3/4 in. (19.7 cm),"Rogers Fund, 1926",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55583,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1488,false,true,55585,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,1840–50,1840,1850,Polychrome woodblock print; ink and color on paper,H. 12 7/8 in. (32.7 cm); W. 4 13/16 in. (12.2 cm),"Rogers Fund, 1926",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55585,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1491,false,true,55587,Asian Art,Print,"東海道五十三次之内 桑名 七里渡口|Station Forty-Three: Kuwana, Seven-Ri Ferry at the Port, from the Fifty-Three Stations of the Tokaido",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1833–34,1833,1834,Polychrome woodblock print; ink and color on paper,H. 9 3/8 in. (23.8 cm); W. 14 7/8 in. (37.8 cm),"Gift of Louis V. Ledoux, 1927",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55587,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1498,false,true,55594,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1835,1825,1845,Polychrome woodblock print; ink and color on paper,H. 8 1/2 in. (21.6 cm); W. 11 1/2 in. (29.2 cm),"Fletcher Fund, 1928",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55594,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1546,false,true,55711,Asian Art,Print,木曽海道六拾九次之内 洗馬|Senba Station,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",(?),"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1835–48,1835,1848,Polychrome woodblock print; ink and color on paper,H. 9 5/8 in. (24.4 cm); W. 14 7/16 in. (36.7 cm),"Fletcher Fund, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55711,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1547,false,true,55712,Asian Art,Print,"名所江戸百景 御厩河岸|Ommayagashi, Sumida River",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1857,1847,1867,Polychrome woodblock print; ink and color on paper,H. 14 3/4 in. (37.5 cm); W. 10 1/8 in. (25.7 cm),"Fletcher Fund, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55712,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1548,false,true,53682,Asian Art,Print,"名所江戸百景 京橋竹がし|Full Moon Over Canal, with Bridge and Huge Stacks of Bamboo along the Bank",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1857,1847,1867,Polychrome woodblock print; ink and color on paper,Aiban; H. 13 7/8 in. (35.2 cm); W. 9 1/4 in. (23.5 cm),"Fletcher Fund, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/53682,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1876,false,true,56196,Asian Art,Print,"東海道五十三次 池鯉鮒 首夏馬市|Chiriu, Station No. 40",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1834,1824,1844,Polychrome woodblock print; ink and color on paper,H. 9 in. (22.9 cm); W. 13 7/8 in. (35.2 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56196,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1877,false,true,56258,Asian Art,Print,"東海道五十三次 関 本陣早立|Seki, Stations No. 48",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1834,1824,1844,Polychrome woodblock print; ink and color on paper,9 3/4 x 14 7/16 in. (24.8 x 36.7cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56258,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1880,false,true,44980,Asian Art,Print,Shinshu-sarashina tagoto no tsuki|本朝名所 信州更科田毎之月|Reflections of the Moon in the Rice Fields of Sarashina in Shinshu,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1832,1822,1842,Polychrome woodblock print; ink and color on paper,Image: 9 1/4 × 14 1/8 in. (23.5 × 35.9 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/44980,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1887,false,true,56519,Asian Art,Print,江戸近郊八景之内 玉川秋月|Autumn Moon on the Tama River,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1838,1828,1848,Polychrome woodblock print; ink and color on paper,H. 9 3/8 in. (23.8 cm); W. 14 5/16 in. (36.4 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56519,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1889,false,true,44623,Asian Art,Print,四季江都名所 冬 隅田川之雪|Snow on the Sumida River,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1835,1825,1845,Polychrome woodblock print; ink and color on paper,14 5/8 x 5 in. (37.1 x 12.7 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/44623,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1892,false,true,56590,Asian Art,Print,雪月花 阿波鳴門之風景|Rapids at Naruto,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,1857,1857,1857,Triptych of polychrome woodblock prints; ink and color on paper,A: H. 14 1/2 in. (36.8 cm); W. 9 11/16 in. (24.6 cm) B: H. 14 1/2 in. (36.8 cm); W. 9 11/16 in. (24.6 cm) C: H. 14 1/2 in. (36.8 cm); W. 9 11/16 in. (24.6 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56590,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1893,false,true,56591,Asian Art,Woodblock print,雪月花 武陽金沢八勝夜景|Panorama of the Eight Views of Kanasawa under a Full Moon,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,1857,1857,1857,Triptych of polychrome woodblock prints; ink and color on paper,H. 14 5/8 in. (37.1 cm); W. 29 7/8 in. (75.9 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56591,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1894,false,true,53688,Asian Art,Woodblock print,雪中芦に鴨|A Wild Duck near a Snow-laden Shore,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1843,1833,1853,Polychrome woodblock print; ink and color on paper,H. 14 7/8 in. (37.8 cm); W. 6 3/4 in. (17.1 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/53688,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1895,false,true,45023,Asian Art,Woodblock print,歌川広重画 雪中芦に鴨|Mallard Ducks and Snow-covered Reeds,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1843,1833,1853,Polychrome woodblock print; ink and color on paper,H. 14 7/8 in. (37.8 cm); W. 5 1/16 in. (12.9 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45023,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1898,false,true,56796,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1834,1824,1844,Polychrome woodblock print; ink and color on paper,14 11/16 x 4 15/16 in. (37.3 x 12.5 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56796,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1899,false,true,53687,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,1840s,1840,1849,Polychrome woodblock print; ink and color on paper,H. 8 15/16 in. (22.7 cm); W. 11 3/16 in. (28.4 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/53687,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2349,false,true,54134,Asian Art,Woodblock print,"西洋風の懐中時計『春雨集』 摺物帖|Western Pocket WatchFrom the Spring Rain Collection (Harusame shū), vol. 3",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,dated 1823,1823,1823,Part of an album of woodblock prints (surimono); ink and color on paper,7 11/16 x 6 1/4 in. (19.5 x 15.9 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54134,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2457,false,true,45298,Asian Art,Print,Gotenyama no yu-zakura|東都名所 御殿山之夕桜|Evening Cherry Blossoms at Gotenyama,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,1831,1831,1831,Polychrome woodblock print; ink and color on paper,13 3/4 x 8 1/2 in. (34.9 x 21.6 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45298,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2458,false,true,45300,Asian Art,Print,"Sumidagawa, hazakura no kei|東都名所 隅田川葉桜之景|A View of Cherry Trees in Leaf along the Sumida River",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,1831,1831,1831,Polychrome woodblock print; ink and color on paper,13 3/4 x 8 1/2 in. (34.9 x 21.6 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45300,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2459,false,true,45299,Asian Art,Print,"Masaki, boshun no kei|東都名所 真崎暮春之景|A View of Late Spring at Masaki",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,1831,1831,1831,Polychrome woodblock print; ink and color on paper,13 3/4 x 8 1/2 in. (34.9 x 21.6 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45299,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2461,false,true,45301,Asian Art,Print,Tsukudajima hatsuhotogizu|東都名所 佃島初郭公|The Year's First Song of the Cuckoo at Tsukudajima,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,1831,1831,1831,Polychrome woodblock print; ink and color on paper,13 3/4 x 8 1/2 in. (34.9 x 21.6 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45301,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2462,false,true,56887,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1834,1824,1844,Polychrome woodblock print; ink and color on paper,13 1/2 x 4 1/2 in. (34.3 x 11.4 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56887,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2463,false,true,45295,Asian Art,Print,Susaki yuki no hatsuhi|東都名所 洲崎雪之初日|New Year's Sunrise after Snow at Susaki,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1831,1821,1841,Polychrome woodblock print; ink and color on paper,8 1/2 x 14 in. (21.6 x 35.6 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45295,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2464,false,true,56888,Asian Art,Print,京都名所之内 八瀬之里|Village of Yase,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1832,1822,1842,Polychrome woodblock print; ink and color on paper,8 3/4 x 14 in. (22.2 x 35.6 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56888,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2465,false,true,56889,Asian Art,Print,京都名所之内 祇園社雪中|The Gion Shrine in Snow,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1832,1822,1842,Polychrome woodblock print; ink and color on paper,8 3/4 x 14 in. (22.2 x 35.6 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56889,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2466,false,true,45302,Asian Art,Print,Kameido Tenmangu keidai no yuki|東都名所 亀戸天満宮境内雪|Tenmangū Shrine at Kameido in Snow,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1833–43,1615,1868,Polychrome woodblock print; ink and color on paper,8 3/4 x 13 3/4 in. (22.2 x 34.9 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45302,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2467,false,true,56890,Asian Art,Print,金沢八景 内川暮雪|Evening Snow at Uchikawa,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1836,1826,1846,Polychrome woodblock print; ink and color on paper,9 x 13 7/8 in. (22.9 x 35.2 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56890,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2468,false,true,56891,Asian Art,Woodblock print,"山海見立相撲 備前偸賀山|Mount Yuga in Bizen Province (Bizen Yugasan), from the series Wrestling Matches between Mountains and Seas (Sankai mitate zumō)",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,"8th month, 1858",1858,1858,Polychrome woodblock print; ink and color on paper,8 3/4 x 13 in. (22.2 x 33 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56891,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2469,false,true,56892,Asian Art,Woodblock print,近江八景之内 三井晩鐘|Vesper Bells at Mii Temple,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1832,1822,1842,Polychrome woodblock print; ink and color on paper,9 x 13 3/4 in. (22.9 x 34.9 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56892,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2470,false,true,56893,Asian Art,Print,近江八景之内 堅田落雁|Returning Geese at Katada,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1832,1822,1842,Polychrome woodblock print; ink and color on paper,8 7/8 x 13 3/4 in. (22.5 x 34.9 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56893,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2471,false,true,56894,Asian Art,Print,近江八景之内 粟津晴嵐|Clearing Weather at Awazu,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1832,1822,1842,Polychrome woodblock print; ink and color on paper,8 7/8 x 13 3/4 in. (22.5 x 34.9 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56894,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2472,false,true,56895,Asian Art,Woodblock print,近江八景之内 矢橋帰帆|Fishing Boats Returning to Yabase,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1832,1822,1842,Polychrome woodblock print; ink and color on paper,8 7/8 x 13 3/4 in. (22.5 x 34.9 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56895,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2473,false,true,56896,Asian Art,Print,近江八景之内 瀬田夕照|Sunset at Seta,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1832,1822,1842,Polychrome woodblock print; ink and color on paper,8 7/8 x 13 3/4 in. (22.5 x 34.9 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56896,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2474,false,true,56897,Asian Art,Print,近江八景之内 比良暮雪|Evening Snow on Mount Hira,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1832,1822,1842,Polychrome woodblock print; ink and color on paper,8 7/8 x 15 15/16 in. (22.5 x 40.5 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56897,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2475,false,true,56898,Asian Art,Print,近江八景之内 石山秋月|The Autumn Full Moon at Ishiyama,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1832,1822,1842,Polychrome woodblock print; ink and color on paper,8 7/8 x 13 11/16 in. (22.5 x 34.8 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56898,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2476,false,true,56899,Asian Art,Print,近江八景之内 唐崎夜雨|Evening Rain at Karasaki-Pine Tree,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1832,1822,1842,Polychrome woodblock print; ink and color on paper,8 7/8 x 13 3/4 in. (22.5 x 34.9 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56899,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2477,false,true,56900,Asian Art,Print,江戸近郊八景之内 芝浦晴嵐|Clearing Weather at Shibaura,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1838,1828,1848,Polychrome woodblock print; ink and color on paper,10 x 15 in. (25.4 x 38.1 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56900,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2478,false,true,56901,Asian Art,Print,江戸近郊八景之内 行徳帰帆|Boats Returning to Gyotoku,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1838,1828,1848,Polychrome woodblock print; ink and color on paper,8 3/4 x 13 3/4 in. (22.2 x 34.9 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56901,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2479,false,true,56902,Asian Art,Print,江戸近郊八景之内 飛鳥山暮雪|Asukayama in Evening Snow,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1838,1828,1848,Polychrome woodblock print; ink and color on paper,8 3/4 x 13 3/4 in. (22.2 x 34.9 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56902,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2480,false,true,56903,Asian Art,Print,江戸近郊八景之内 玉川秋月|Autumn Moon on the Tama River,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1838,1828,1848,Polychrome woodblock print; ink and color on paper,8 3/4 x 13 3/4 in. (22.2 x 34.9 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56903,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2481,false,true,56904,Asian Art,Woodblock print,東海道五十三次之内 江尻 三保遠望|Panorama of Miwo Pine Wood from Ejiri,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,1834,1834,1834,Polychrome woodblock print; ink and color on paper,8 7/8 x 13 3/4 in. (22.5 x 34.9 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56904,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2482,false,true,56905,Asian Art,Print,東海道五十三次之内 石薬師 石薬師寺|The Ishiyakushi Temple at Ishiyakushi,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,1834,1834,1834,Polychrome woodblock print; ink and color on paper,8 7/8 x 13 5/8 in. (22.5 x 34.6 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56905,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2483,false,true,56906,Asian Art,Print,"東海道五十三次之内 藤川 棒鼻の図|Station Thirty-Eight: Fujikawa, Scene at the Border, from the Fifty-Three Stations of the Tokaido",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1833–34,1833,1834,Polychrome woodblock print; ink and color on paper,9 x 14 in. (22.9 x 35.6 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56906,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2484,false,true,56907,Asian Art,Print,東海道五十三次之内 四日市 三重川|Mie River at Yokkaichi,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,1834,1834,1834,Polychrome woodblock print; ink and color on paper,8 7/8 x 13 3/4 in. (22.5 x 34.9 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56907,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2485,false,true,56908,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1832,1822,1842,Polychrome woodblock print; ink and color on paper,14 7/8 x 5 1/8 in. (37.8 x 13 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56908,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2487,false,true,56910,Asian Art,Woodblock print,"東海道五十三次之内 関 本陣早立|Station Forty-Eight: Seki, Early Departure from the Headquarters Inn, from the Fifty-Three Stations of the Tokaido",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1833–34,1833,1834,Polychrome woodblock print; ink and color on paper,8 7/8 13 5/8 in. (22.5 x 34.6 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56910,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2489,false,true,56912,Asian Art,Print,東海道五十三次之内 岡部 宇津の山|Utsu Hill at Okabe,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,1834,1834,1834,Polychrome woodblock print; ink and color on paper,9 x 14 in. (22.9 x 35.6 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56912,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2490,false,true,56913,Asian Art,Print,東海道五十三次之内 金谷 大井川遠岸|The Far Bank of the Ōi River at Kanaya,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,1834,1834,1834,Polychrome woodblock print; ink and color on paper,9 x 14 in. (22.9 x 35.6 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56913,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2492,false,true,56915,Asian Art,Woodblock print,"東海道五十三次之内 蒲原 夜の雪|Evening Snow at Kanbara, from the series ""Fifty-three Stations of the Tōkaidō""",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1833–34,1823,1844,Polychrome woodblock print; ink and color on paper,8 7/8 x 13 3/4 in. (22.5 x 34.9 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56915,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2493,false,true,56916,Asian Art,Print,東海道五十三次之内 庄野 白雨|Shower at Shōno,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,1834,1834,1834,Polychrome woodblock print; ink and color on paper,8 7/8 x 13 5/8 in. (22.5 x 34.6 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56916,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2494,false,true,56917,Asian Art,Print,東海道五十三次 はま松|Hamamatsu Station,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1842,1832,1852,Polychrome woodblock print; ink and color on paper,8 9/16 x 13 5/8 in. (21.7 x 34.6 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56917,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2495,false,true,56918,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1833,1823,1843,Uncut triptych of polychrome woodblock prints; ink and color on paper,9 1/4 x 14 1/2 in. (23.5 x 36.8 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56918,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2496,false,true,56919,Asian Art,Print,木曽海道六拾九次之内 あし田|Ashida Station,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1836,1826,1846,Polychrome woodblock print; ink and color on paper,8 3/4 x 13 3/4 in. (22.2 x 34.9 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56919,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2497,false,true,56920,Asian Art,Woodblock print,歌川広重画 雪中芦に鴨|Mallard Duck and Snow-covered Reeds,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1832,1822,1842,Polychrome woodblock print; ink and color on paper,15 x 6 7/8 in. (38.1 x 17.5 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56920,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2498,false,true,56921,Asian Art,Print,木曽海道六拾九次之内 長久保|Nagakubo Station,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1836,1826,1846,Polychrome woodblock print; ink and color on paper,8 3/4 x 13 3/4 in. (22.2 x 34.9 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56921,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2499,false,true,56922,Asian Art,Print,木曽海道六拾九次之内 福しま|Fukushima Station,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1837,1827,1847,Polychrome woodblock print; ink and color on paper,8 3/4 x 13 3/4 in. (22.2 x 34.9 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56922,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2500,false,true,56923,Asian Art,Print,木曽海道六拾九次之内 宮ノ越|Miyanokoshi Station,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1836,1826,1846,Polychrome woodblock print; ink and color on paper,8 7/8 x 13 3/8 in. (22.5 x 34 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56923,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2501,false,true,56924,Asian Art,Print,木曽海道六拾九次之内 大井|Ōi Station,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1837,1827,1847,Polychrome woodblock print; ink and color on paper,8 7/8 x 13 3/8 in. (22.5 x 34 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56924,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2502,false,true,56925,Asian Art,Print,"歌川広重画 朝顔に鶏と傘|Rooster, Umbrella, and Morning Glories",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1830,1820,1840,Polychrome woodblock print; ink and color on paper,14 3/4 x 6 1/2 in. (37.5 x 16.5 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56925,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2503,false,true,45320,Asian Art,Print,"Sumidagawa no yuki|Sumida River in the Snow, from the series ""Famous Places in Edo in the Four Seasons""",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,1834,1834,1834,Polychrome woodblock print (tanzaku size); ink and color on paper,Tanzaku 14 3/4 x 5 in. (37.5 x 12.7 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45320,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2504,false,true,56926,Asian Art,Print,月二拾八景之内 弓張月|Bow Moon,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,1832,1832,1832,Polychrome woodblock print; ink and color on paper,14 3/4 x 6 7/8 in. (37.5 x 17.5 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56926,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2506,false,true,56928,Asian Art,Print,"富士三十六景 房州保田の海岸|Seashore at Hoda, Province of Awa",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,1858–59,1858,1859,Polychrome woodblock print; ink and color on paper,13 1/4 x 8 3/4 in. (33.7 x 22.2 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56928,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2507,false,true,56929,Asian Art,Woodblock print,富士三十六景 駿河三保の松原|Pine Groves of Miho in Suruga Province,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,1858,1858,1858,Polychrome woodblock print; ink and color on paper,13 3/8 x 8 3/4 in. (34 x 22.2 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56929,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2508,false,true,53785,Asian Art,Woodblock print,"六十余州名所図会  対馬 海岸 夕晴|Evening Glow, Tsushima Province, from the series Views of Famous Places in the Sixty-Odd Provinces",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1853,1843,1863,Polychrome woodblock print; ink and color on paper,13 3/8 x 9 in. (34 x 22.9 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/53785,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2509,false,true,53780,Asian Art,Print,"六十余州名所図会 播磨 舞子の浜|Maiko Beach, Harima Province, from the series Views of Famous Places in the Sixty-Odd Provinces",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1853,1843,1863,Polychrome woodblock print; ink and color on paper,Oban tate-e 12 3/8 x 9 in. (31.4 x 22.9 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/53780,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2510,false,true,53681,Asian Art,Print,Kamata no Baien|名所江戸百景 蒲田の梅園|Plum Garden at Kamata,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,1857,1857,1857,Polychrome woodblock print; ink and color on paper,13 1/4 x 8 5/8 in. (33.7 x 21.9 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/53681,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2511,false,true,56930,Asian Art,Print,歌川広重画 鉄線花に鳥|Clematis and Bird,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,1852,1852,1852,Polychrome woodblock print; ink and color on paper,6 7/8 x 9 in. (17.5 x 22.9 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56930,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2512,false,true,53679,Asian Art,Print,"名所江戸百景 昌平橋 聖堂 神田川|Shohei Bridge, Seido Temple and Kanda River",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,1857,1857,1857,Polychrome woodblock print; ink and color on paper,13 1/4 x 8 3/4 in. (33.7 x 22.2 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/53679,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2513,false,true,56692,Asian Art,Print,"名所江戸百景 神田紺屋町|Dye House at Konya-cho, Kanda",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,1857,1857,1857,Polychrome woodblock print; ink and color on paper,H. 13 in. (33 cm); W. 8 1/2 in. (21.6 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56692,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2514,false,true,56931,Asian Art,Print,"「名所江戸百景 よし原 日本堤」|“Nihon Embankment at Yoshiwara,” from the series One Hundred Famous Views of Edo (Meisho Edo hyakkei, Yoshiwara, Nihonzutsumi)",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,1857,1857,1857,Polychrome woodblock print; ink and color on paper,14 1/4 x 9 1/2 in. (36.2 x 24.1 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56931,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2515,false,true,56932,Asian Art,Print,名所江戸百景 深川木場|The Lumber Yard at Fukagawa,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,1856,1856,1856,Polychrome woodblock print; ink and color on paper,14 1/4 x 9 3/8 in. (36.2 x 23.8 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56932,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2516,false,true,56933,Asian Art,Print,名所江戸百景 目黒太鼓橋夕日の岡|The Taiko (Drum) Bridge and the Yuhi Mound at Meguro,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,1857,1857,1857,Polychrome woodblock print; ink and color on paper,Oban: 14 1/8 x 9 1/8 in. (35.9 x 23.2 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56933,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2517,false,true,53659,Asian Art,Print,名所江戸百景 亀戸天神境内|In the Kameido Tenjin Shrine Compound,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,1856,1856,1856,Polychrome woodblock print; ink and color on paper,14 1/4 x 9 3/4 in. (36.2 x 24.8 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/53659,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2518,false,true,56934,Asian Art,Print,名所江戸百景 請地秋葉の境内|Inside Akiba Shrine at Ukeji,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,1857,1857,1857,Polychrome woodblock print; ink and color on paper,13 1/4 x 8 3/4 in. (33.7 x 22.2 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56934,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2519,false,true,56689,Asian Art,Print,"名所江戸百景 浅草金龍山|Kinryūsan Temple at Asakusa, from the series ""One Hundred Famous Views of Edo""",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,1856,1856,1856,Polychrome woodblock print; ink and color on paper,H. 14 1/16 in. (35.7 cm); W. 9 1/2 in. (24.1 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56689,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2520,false,true,55733,Asian Art,Print,"「名所江戸百景 深川洲崎十万坪」|“Jūmantsubo Plain at Fukagawa Susaki,” from the series One Hundred Famous Views of Edo (Meisho Edo hyakkei, Fukagawa Susaki Jūmantsubo)",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,1856,1856,1856,Polychrome woodblock print; ink and color on paper,Oban 14 1/16 x 9 1/2 in. (35.7 x 24.1 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55733,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2521,false,true,56935,Asian Art,Print,名所江戸百景 京橋竹がし|Bamboo Market at Capital Bridge,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,1857,1857,1857,Polychrome woodblock print; ink and color on paper,14 1/16 x 9 1/2 in. (35.7 x 24.1 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56935,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2522,false,true,55433,Asian Art,Print,"Ōhashi Atake no yūdachi|名所江戸百景 大はしあたけの夕立|Sudden Shower over Shin-Ōhashi Bridge and Atake (Ōhashi Atake no yūdachi), from the series One Hundred Famous Views of Edo (Meisho Edo hyakkei)",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,1857,1857,1857,Polychrome woodblock print; ink and color on paper,Oban 13 3/8 x 9 1/2 in. (34 x 24.1 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55433,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2523,false,true,56936,Asian Art,Print,名所江戸百景 王子装束ゑの木大晦日の狐火|Shozokuenoki Tree at Oji: Fox–fires on New Years Eve,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,1857,1857,1857,Polychrome woodblock print; ink and color on paper,14 1/16 x 9 1/2 in. (35.7 x 24.1 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56936,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2525,false,true,56938,Asian Art,Woodblock print,"五十三次名所図会 藤川 山中の里別名宮路山|Fujikawa, a Village in the Mountains Formerly Called Miyajiyama",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,1855,1855,1855,Polychrome woodblock print; ink and color on paper,13 1/2 x 8 7/8 in. (34.3 x 22.5 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56938,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2526,false,true,56939,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,1854,1854,1854,Polychrome woodblock print; ink and color on paper,13 1/8 x 4 3/8 in. (33.3 x 11.1 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56939,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2527,false,true,56940,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1847,1837,1857,Polychrome woodblock print; ink and color on paper,13 5/8 x 4 1/2 in. (34.6 x 11.4 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56940,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2528,false,true,56941,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1835,1825,1845,Polychrome woodblock print; ink and color on paper,14 1/2 x 5 in. (36.8 x 12.7 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56941,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2529,false,true,56942,Asian Art,Woodblock print,歌川広重画 梅に鶯|Warbler on a Plum Branch,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1835,1825,1845,Polychrome woodblock print; ink and color on paper,14 1/2 x 5 1/8 in. (36.8 x 13 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56942,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2530,false,true,56943,Asian Art,Woodblock print,歌川広重画 燕子花に川蝉|Kingfisher and Iris,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,1832–34,1832,1834,Polychrome woodblock print; ink and color on paper,15 x 5 1/8 in. (38.1 x 13 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56943,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2531,false,true,56944,Asian Art,Woodblock print,歌川広重画|Five Swallows above a Branch,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1835,1845,1855,Polychrome woodblock print; ink and color on paper,15 1/8 x 5 1/8 in. (38.4 x 13 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56944,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2532,false,true,56945,Asian Art,Print,歌川広重画 罌栗に瑠璃鳥|Bluebird and Flowering Poppies,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,1832–34,1832,1834,Polychrome woodblock print; ink and color on paper,13 1/4 x 4 1/4 in. (33.7 x 10.8 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56945,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2533,false,true,56946,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,Designed by,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1835,1825,1845,Polychrome woodblock print; ink and color on paper,14 1/4 x 5 in. (36.2 x 12.7 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56946,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2534,false,true,39648,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1847,1837,1857,Polychrome woodblock print; ink and color on paper,13 x 4 3/8 in. (33 x 11.1 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/39648,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2535,false,true,56949,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1845,1835,1855,Polychrome woodblock print; ink and color on paper,13 3/4 x 4 1/2 in. (34.9 x 11.4 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56949,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2536,false,true,45024,Asian Art,Print,歌川広重画 蔦に柄長鳥|Long-tailed Tit on Autumn Ivy,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1835,1825,1845,Polychrome woodblock print (hosoban); ink and color on paper,14 3/4 x 4 3/4 in. (37.5 x 12.1 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45024,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2537,false,true,56950,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1835,1825,1845,Polychrome woodblock print; ink and color on paper,8 3/8 x 13 3/8 in. (21.3 x 34 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56950,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2538,false,true,56951,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1840,1830,1850,Polychrome woodblock print; ink and color on paper,10 3/8 x 7 1/4 in. (26.4 x 18.4 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56951,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2539,false,true,44894,Asian Art,Print,歌川広重画 雪中小松に錦雉|Golden Pheasant and Pine Shoots in Snow,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1835,1825,1845,Polychrome woodblock print; ink and color on paper,14 1/2 x 6 1/2 in. (36.8 x 16.5 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/44894,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2542,false,true,56956,Asian Art,Print,木曽海道六拾九次之内 洗馬|Senba Station,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1836,1826,1846,Polychrome woodblock print; ink and color on paper,8 3/4 x 13 3/4 in. (22.2 x 34.9 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56956,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2543,false,true,56958,Asian Art,Print,歌川広重画 芦に鷺|White Heron Standing among Reeds,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1835,1825,1845,Polychrome woodblock print; ink and color on paper,15 x 5 1/8 in. (38.1 x 13 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56958,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2836,false,true,57066,Asian Art,Print,Zumihari Zuki|月二拾八景之内 弓張月|Bow Moon,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,1797–1858,1797,1858,Polychrome woodblock print; ink and color on paper,14 7/8 x 6 15/16 in. (37.8 x 17.6 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/57066,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2837,false,true,57065,Asian Art,Print,Azuma Mori Yau|江戸近郊八景之内 吾嬬杜夜雨|Evening Rain in Azuma Wood,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1838,1828,1848,Polychrome woodblock print; ink and color on paper,9 5/8 x 14 3/4 in. (24.4 x 37.5 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/57065,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2838,false,true,57064,Asian Art,Woodblock print,Ikegami Bansho|江戸近郊八景之内 池上晩鐘|Vesper Bells at Ikegami,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,1797–1858,1797,1858,Polychrome woodblock print; ink and color on paper,10 x 14 5/8 in. (25.4 x 37.1 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/57064,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2839,false,true,57063,Asian Art,Print,江戸近郊八景之内 小金井橋夕照|Evening Glow at Koganei Border,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,1797–1858,1797,1858,Polychrome woodblock print; ink and color on paper,9 7/8 x 14 1/4 in. (25.1 x 36.2 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/57063,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2840,false,true,57062,Asian Art,Print,Gyotoku Kihan|江戸近郊八景之内 行徳帰帆|Boats Returning to Gyotoku,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,1797–1858,1797,1858,Polychrome woodblock print; ink and color on paper,8 7/8 x 13 1/8 in. (22.5 x 33.3 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/57062,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2841,false,true,57061,Asian Art,Print,Asukayama Bosetsu|江戸近郊八景之内 飛鳥山暮雪|Asukayama in the Snow at Evening,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,1797–1858,1797,1858,Polychrome woodblock print; ink and color on paper,9 1/4 x 14 1/4 in. (23.5 x 36.2 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/57061,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2842,false,true,57060,Asian Art,Print,Shibaura Seiran|江戸近郊八景之内 芝浦晴嵐|Clearing Weather at Shibaura,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,1797–1858,1797,1858,Polychrome woodblock print; ink and color on paper,9 1/4 x 14 1/4 in. (23.5 x 36.2 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/57060,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2843,false,true,57059,Asian Art,Print,Haneda Rakugan|江戸近郊八景之内 羽根田落雁|Wild Geese at Haneda,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,1797–1858,1797,1858,Polychrome woodblock print; ink and color on paper,14 5/8 x 10 in. (37.1 x 25.4 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/57059,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2844,false,true,57058,Asian Art,Print,Tamagawa Shugetsu|江戸近郊八景之内 玉川秋月|Autumn Moon on the Tama River,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1838,1828,1848,Polychrome woodblock print; ink and color on paper,9 3/8 x 14 3/8 in. (23.8 x 36.5 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/57058,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2845,false,true,57057,Asian Art,Print,Awazu no Seiran|近江八景之内 粟津晴嵐|Clearing Weather at Awazu,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,1797–1858,1797,1858,Polychrome woodblock print; ink and color on paper,9 7/8 x 14 7/8 in. (25.1 x 37.8 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/57057,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2846,false,true,57056,Asian Art,Woodblock print,Yabase no Kihan|近江八景之内 矢橋帰帆|Fishing Boats Returning to Yabase,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,1797–1858,1797,1858,Polychrome woodblock print; ink and color on paper,9 3/4 x 14 7/8 in. (24.8 x 37.8 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/57056,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2847,false,true,57055,Asian Art,Print,Ishiyama no Shūgetsu|近江八景之内 石山秋月|Autumn Full Moon at Ishiyama,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,1797–1858,1797,1858,Polychrome woodblock print; ink and color on paper,Image: 9 3/4 × 14 3/4 in. (24.8 × 37.5 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/57055,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2848,false,true,57054,Asian Art,Print,Hira no Bosetsu|近江八景の内 比良暮雪|Evening Snow on Mount Hira,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,1797–1858,1797,1858,Polychrome woodblock print; ink and color on paper,9 3/4 x 14 3/4 in. (24.8 x 37.5 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/57054,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2849,false,true,57053,Asian Art,Print,"Karasaki no Yau|近江八景の内 唐崎夜雨|Evening Rain at Karasaki, Pine Tree",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,1797–1858,1797,1858,Polychrome woodblock print; ink and color on paper,9 3/4 x 14 7/8 in. (24.8 x 37.8 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/57053,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2850,false,true,57052,Asian Art,Woodblock print,Mii no Bansho|近江八景の内 三井晩鍾|Vesper Bells at Mii Temple,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,1797–1858,1797,1858,Polychrome woodblock print; ink and color on paper,9 3/4 x 14 7/8 in. (24.8 x 37.8 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/57052,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2851,false,true,57051,Asian Art,Print,Katada no Rakugan|近江八景之内 堅田落雁|Returning Geese at Katada,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,1797–1858,1797,1858,Polychrome woodblock print; ink and color on paper,9 3/4 x 15 in. (24.8 x 38.1 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/57051,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2852,false,true,57050,Asian Art,Print,Seta no Yusho|近江八景の内 瀬田夕照|Sunset at Seta,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,1797–1858,1797,1858,Polychrome woodblock print; ink and color on paper,9 5/8 x 14 5/8 in. (24.4 x 37.1 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/57050,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2865,false,true,57045,Asian Art,Woodblock print,東海道五十三次之内 亀山 雪晴|Clear Weather after Snow,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,1797–1861,1797,1861,Polychrome woodblock print; ink and color on paper,9 3/8 x 14 1/8 in. (23.8 x 35.9 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/57045,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2866,false,true,57044,Asian Art,Woodblock print,東海道五十三次之内 蒲原 夜の雪|Evening Snow,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,1797–1861,1797,1861,Polychrome woodblock print; ink and color on paper,9 5/8 x 14 5/8 in. (24.4 x 37.1 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/57044,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2867,false,true,57043,Asian Art,Print,Shōno Hakuu|東海道五十三次之内 庄野 白雨|White Rain at Shōno,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,1797–1861,1797,1861,Polychrome woodblock print; ink and color on paper,9 1/2 x 14 1/2 in. (24.1 x 36.8 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/57043,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2868,false,true,56678,Asian Art,Print,"木曽海道六拾九次之内 須原|Suhara, from The Sixty-nine Stations of the Kisokaidō",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1838,1828,1848,Polychrome woodblock print; ink and color on paper,H. 9 5/8 in. (24.4 cm); W. 14 1/2 in. (36.8 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56678,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2869,false,true,57042,Asian Art,Print,木曽海道六拾九次之内 長久保|Nagakubo,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,1797–1858,1797,1858,Polychrome woodblock print; ink and color on paper,9 7/8 x 14 7/8 in. (25.1 x 37.8 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/57042,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2871,false,true,56677,Asian Art,Print,"木曽海道六拾九次之内 宮ノ越|Moonlit Night at Miyanokoshi, from The Sixty-nine Stations of the Kisokaidō",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1838,1828,1848,Polychrome woodblock print; ink and color on paper,H. 9 5/8 in. (24.4 cm); W. 14 3/4 in. (37.5 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56677,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2872,false,true,57040,Asian Art,Print,木曽海道六拾九次之内 大井|Ōi,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,1797–1858,1797,1858,Polychrome woodblock print; ink and color on paper,10 x 14 7/8 in. (25.4 x 37.8 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/57040,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2875,false,true,55989,Asian Art,Print,東海道五十三次 沼津|Numazu,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,1797–1858,1797,1858,Polychrome woodblock print; ink and color on paper,9 7/8 x 14 3/4 in. (25.1 x 37.5 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55989,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2876,false,true,55991,Asian Art,Print,東海道五十三次 はま松|Hamamatsu,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,1797–1858,1797,1858,Polychrome woodblock print; ink and color on paper,9 5/8 x 14 1/4 in. (24.4 x 36.2 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55991,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2877,false,true,55992,Asian Art,Print,Miya Atsuta Shinji|東海道五十三次之内 宮 熱田神事|Festival at Atsuta Temple,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,1797–1858,1797,1858,Polychrome woodblock print; ink and color on paper,9 5/8 x 14 1/4 in. (24.4 x 36.2 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55992,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2878,false,true,55993,Asian Art,Woodblock print,"Nissaka-sayo no Naka Yama|東海道五十三次之内 日坂 佐夜の中山|Station Twenty-six: Nissaka, Sayo no Nakayama, from the Fifty-three Stations of the Tokaido",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1833–34,1833,1834,Polychrome woodblock print; ink and color on paper,9 3/8 x 14 3/8 in. (23.8 x 36.5 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55993,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2879,false,true,55994,Asian Art,Print,Mishima Asa-Giri|東海道五十三次之内 三島 朝霧|Morning Mist,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,1797–1858,1797,1858,Polychrome woodblock print; ink and color on paper,9 1/2 x 14 1/4 in. (24.1 x 36.2 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55994,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2880,false,true,55995,Asian Art,Print,Tsuchiyama Haru no Ame|東海道五十三次之内 土山 春の雨|Spring Rain at Tsuchiyama (50th Station of the Tōkaidō),Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,1797–1858,1797,1858,Polychrome woodblock print; ink and color on paper,9 3/8 x 14 1/4 in. (23.8 x 36.2 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55995,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2881,false,true,55996,Asian Art,Print,Arashiyama|京都名所之内 あらし山満花|Full Blossom at Arashiyama on the Oi River,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,1797–1858,1797,1858,Polychrome woodblock print; ink and color on paper,9 7/8 x 14 5/8 in. (25.1 x 37.1 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55996,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2882,false,true,55997,Asian Art,Print,Yodogawa|京都名所之内 淀川|On the Yodo River,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,1797–1858,1797,1858,Polychrome woodblock print; ink and color on paper,10 1/4 x 15 in. (26.0 x 38.1 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55997,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2883,false,true,55998,Asian Art,Print,京都名所之内 祇園社雪中|Gion Shrine in Snow,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,1797–1858,1797,1858,Polychrome woodblock print; ink and color on paper,9 3/4 x 15 1/8 in. (24.8 x 38.4 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55998,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2884,false,true,55999,Asian Art,Woodblock print,京都名所之内 嶋原出口之柳|Gate of the Shimbara,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,1797–1858,1797,1858,Polychrome woodblock print; ink and color on paper,10 1/4 x 15 3/8 in. (26 x 39.1 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55999,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2885,false,true,56000,Asian Art,Woodblock print,歌川広重画 雪中芦に鴨|Mallard and Snow-covered Reeds,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1843,1833,1853,Polychrome woodblock print; ink and color on paper,9 7/8 x 14 5/8 in. (25.1 x 37.1 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56000,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3010,false,true,56411,Asian Art,Print,Karasaki ya'u|近江八景 唐崎夜雨|Pine Tree at Karasaki,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,1857,1857,1857,Polychrome woodblock print; ink and color on paper,14 3/4 x 10 in. (37.5 x 25.4 cm),"Gift of Mrs. Henry J. Bernheim, 1945",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56411,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3037,false,true,53782,Asian Art,Print,"六十余州名所図会 壱岐 志作|Snowfall at Shimasaku, Iki Province, from the series Views of Famous Places in the Sixty-Odd Provinces",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1853,1843,1863,Polychrome woodblock print; ink and color on paper,14 3/8 x 9 1/4 in. (36.5 x 23.5 cm),"Gift of Mrs. Morris Manges, in memory of her husband, Dr. Morris Manges, 1947",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/53782,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3038,false,true,56495,Asian Art,Print,Atagoshita Yabu-Kōji|東都名所 芝赤羽根之雪|Winter Landscape,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,1846,1846,1846,Polychrome woodblock print; ink and color on paper,9 3/4 x 14 1/2 in. (24.8 x 36.8 cm),"Gift of Mrs. Morris Manges, in memory of her husband, Dr. Morris Manges, 1947",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56495,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3039,false,true,56496,Asian Art,Print,東都司馬八景 高輪帰帆|River View at Takanawa,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1856,1846,1866,Polychrome woodblock print; ink and color on paper,9 x 14 in. (22.9 x 35.6 cm),"Gift of Mrs. Morris Manges, in memory of her husband, Dr. Morris Manges, 1947",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56496,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3040,false,true,45319,Asian Art,Woodblock print,Toto meisho Sumidagawa zenzu settchukei|東都名所 隅田川全図雪中景|Celebrated Places in the Eastern Capital: Panoramic View of the Sumida River in Snow,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1840,1830,1850,Triptych of polychrome woodblock prints; ink and color on paper,14 1/2 x 10 in. (36.8 x 25.4 cm),"Gift of Mrs. Morris Manges, in memory of her husband, Dr. Morris Manges, 1947",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45319,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3148,false,true,56699,Asian Art,Print,歌川広重画 菊に雉|Pheasant with Chrysanthemums,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1835,1825,1845,Polychrome woodblock print (hosoban); ink and color on paper,14 3/4 x 6 3/4 in. (37.5 x 17.1 cm),"Bequest of Ellis G. Seymour, 1949",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56699,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3174,false,true,37386,Asian Art,Print,"Ōhashi Atake no yūdachi|名所江戸百景 大はしあたけの夕立|Sudden Shower over Shin-Ōhashi Bridge and Atake (Ōhashi Atake no yūdachi), from the series One Hundred Famous Views of Edo (Meisho Edo hyakkei)",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,1857,1857,1857,Polychrome woodblock print; ink and color on paper,Image: 14 3/4 × 9 5/8 in. (37.5 × 24.4 cm) Mat: 22 3/4 × 15 1/2 in. (57.8 × 39.4 cm),"Gift of Mr. and Mrs. A. I. Sherr, 1956",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37386,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3413,false,true,55603,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,1850,1850,1850,Polychrome woodblock print; ink and color on paper,9 1/4 x 7 in. (23.5 x 17.8 cm),"Bequest of Julia H. Manges, 1960",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55603,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3414,false,true,55604,Asian Art,Print,江戸名所四十八景 神田明神|Kanda Temple Snow,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,1861,1861,1861,Polychrome woodblock print; ink and color on paper,7 1/4 x 9 1/4 in. (18.4 x 23.5 cm),"Bequest of Julia H. Manges, 1960",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55604,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3435,false,true,55635,Asian Art,Print,名所江戸百景 目黒太鼓橋夕ひの岡|Ochanomizu,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,probably late 19th century,1867,1899,Polychrome woodblock print; ink and color on paper,9 1/2 x 14 1/2 in. (24.1 x 36.8 cm),"Gift of Cole J. Younger, 1975",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55635,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3510,false,true,55803,Asian Art,Print,「甲陽猿橋之図」|The Monkey Bridge in Kai Province (Kōyō Saruhashi no zu),Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1841–42,1841,1842,Vertical ōban diptych mounted as a hanging scroll; ink and color on paper,Overall: 38 3/8 x 13 3/8 in. (97.5 x 34 cm); painting: 28 3/4 x 9 5/8 in. (73 x 24.4 cm),"The Harry G. C. Packard Collection of Asian Art, Gift of Harry G. C. Packard, and Purchase, Fletcher, Rogers, Harris Brisbane Dick, and Louis V. Bell Funds, Joseph Pulitzer Bequest, and The Annenberg Fund Inc. Gift, 1975",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55803,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3512,false,true,53691,Asian Art,Print,名所江戸百景 王子装束えの木大晦日の狐火|Foxes Meeting at Oji,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,1857,1857,1857,Polychrome woodblock print; ink and color on paper,H. 14 5/8 in. (37.1 cm); W. 10 in. (25.4 cm),"The Harry G. C. Packard Collection of Asian Art, Gift of Harry G. C. Packard, and Purchase, Fletcher, Rogers, Harris Brisbane Dick, and Louis V. Bell Funds, Joseph Pulitzer Bequest, and The Annenberg Fund Inc. Gift, 1975",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/53691,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3592,false,true,55897,Asian Art,Woodblock print,"魚づくし こちに茄子|Kochi Fish with Eggplant, from the series Uozukushi (Every Variety of Fish)",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,1830s,1830,1839,Polychrome woodblock print; ink and color on paper,11 1/4 x 14 9/16 in. (28.6 x 37 cm),"Gift of Mr. and Mrs. Bryan Holme, 1980",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55897,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3593,false,true,55898,Asian Art,Woodblock print,"魚づくし ぼらにうど|Bora Fish with Camellia, from the series Uozukushi (Every Variety of Fish)",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,1830s,1830,1839,Polychrome woodblock print; ink and color on paper,9 7/8 x 14 9/16 in. (25.1 x 37 cm),"Gift of Mr. and Mrs. Bryan Holme, 1980",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55898,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3594,false,true,55900,Asian Art,Woodblock print,"魚づくし 鰹に桜|Katsuo Fish with Cherry Buds, from the series Uozukushi (Every Variety of Fish)",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,1830s,1830,1839,Polychrome woodblock print; ink and color on paper,10 1/16 x 14 9/16 in. (25.6 x 37 cm),"Gift of Mr. and Mrs. Bryan Holme, 1980",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55900,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3595,false,true,55901,Asian Art,Woodblock print,"魚づくし 車海老 鯵にたで|Aji Fish and Kuruma-ebi, from the series Uozukushi (Every Variety of Fish)",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,1830s,1830,1839,Polychrome woodblock print; ink and color on paper,10 1/8 x 14 1/2 in. (25.7 x 36.9 cm),"Gift of Mr. and Mrs. Bryan Holme, 1980",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55901,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3596,false,true,55902,Asian Art,Woodblock print,"魚づくし 黒鯛 小鯛に山椒|Kurodai and Kodai Fish with Bamboo Shoots and Berries, from the series Uozukushi (Every Variety of Fish)",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,1830s,1830,1839,Polychrome woodblock print; ink and color on paper,10 1/8 x 14 1/2 in. (25.7 x 36.9 cm),"Gift of Mr. and Mrs. Bryan Holme, 1980",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55902,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3597,false,true,55903,Asian Art,Woodblock print,"魚づくし 伊勢海老 芝蝦|Ise-ebi and Shiba-ebi, from the series Uozukushi (Every Variety of Fish)",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,1830s,1830,1839,Polychrome woodblock print; ink and color on paper,9 15/16 x 14 3/16 in. (25.3 x 36.1 cm),"Gift of Mr. and Mrs. Bryan Holme, 1980",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55903,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3598,false,true,55904,Asian Art,Woodblock print,"魚づくし かれい かながしらに笹|Kanagashira and Karei Fish, from the series Uozukushi (Every Variety of Fish)",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,1830s,1830,1839,Polychrome woodblock print; ink and color on paper,10 1/8 x 14 1/2 in. (25.7 x 36.9 cm),"Gift of Mr. and Mrs. Bryan Holme, 1980",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55904,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3599,false,true,55905,Asian Art,Woodblock print,"魚づくし かさご いさきに生姜|Isaki and Kasago Fish, from the series Uozukushi (Every Variety of Fish)",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,1830s,1830,1839,Polychrome woodblock print; ink and color on paper,10 1/4 x 14 7/8 in. (26 x 37.8 cm),"Gift of Mr. and Mrs. Bryan Holme, 1980",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55905,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3600,false,true,55906,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,1840s,1840,1849,Polychrome woodblock print; ink and color on paper,10 1/8 x 14 5/8 in. (25.7 x 37.2 cm),"Gift of Mr. and Mrs. Bryan Holme, 1980",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55906,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3601,false,true,55907,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,1840s,1840,1849,Polychrome woodblock print; ink and color on paper,10 1/8 x 14 9/16 in. (25.7 x 37 cm),"Gift of Mr. and Mrs. Bryan Holme, 1980",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55907,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3602,false,true,55908,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,1840s,1840,1849,Polychrome woodblock print; ink and color on paper,10 1/16 x 14 15/16 in. (25.5 x 38 cm),"Gift of Mr. and Mrs. Bryan Holme, 1980",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55908,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3603,false,true,55909,Asian Art,Woodblock print,"魚づくし|Medetai Fush and Sasaki Bamboo, from the series Uozukushi (Every Variety of Fish)",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,1840s,1840,1849,Polychrome woodblock print; ink and color on paper,10 x 14 5/8 in. (25.4 x 37.2 cm),"Gift of Mr. and Mrs. Bryan Holme, 1980",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55909,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3604,false,true,55911,Asian Art,Woodblock print,魚づくし いなだ ふぐに梅|Suzuki and Kinmedai Fish from the series Uozukushi (Every Variety of Fish),Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,1840s,1840,1849,Polychrome woodblock print; ink and color on paper,10 1/8 x 14 9/16 in. (25.7 x 37 cm),"Gift of Mr. and Mrs. Bryan Holme, 1980",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55911,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3605,false,true,55912,Asian Art,Woodblock print,"魚づくし ひらめ めばるに桜|Hirame and Mebaru Fish with Cherry Blossoms, from the series Uozukushi (Every Variety of Fish)",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,1840s,1840,1849,Polychrome woodblock print; ink and color on paper,10 1/8 x 14 9/16 in. (25.7 x 37 cm),"Gift of Mr. and Mrs. Bryan Holme, 1980",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55912,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3701,false,true,55946,Asian Art,Print,名所江戸百景 川口のわたし善光寺|Kawaguchi,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,1857,1857,1857,Polychrome woodblock print; ink and color on paper,14 x 9 1/2 in. (35.6 x 24.1 cm),"Bequest of Grace M. Pugh, 1985",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55946,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1093.10,false,true,58262,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,1854,1854,1854,Polychrome woodblock print; ink and color on paper,Image: 14 1/2 in. × 10 in. (36.8 × 25.4 cm),The Metropolitan Museum of Art,,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/58262,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2540a–c,false,true,45283,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,ca. 1845,1835,1855,Triptych of polychrome woodblock prints; ink and color on paper,15 x 29 in. (38.1 x 73.7 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45283,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2833a–c,false,true,57068,Asian Art,Print,"Buyō Kanazawa Hasshō Yakei|雪月花 武陽金沢八勝夜景|Full Moon at Kanazawa, Province of Musashi",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,1857,1857,1857,Triptych of polychrome woodblock prints; ink and color on paper,Each page: 14 7/8 x 10 1/8 in. (37.8 x 25.7 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/57068,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2834a–c,false,true,45025,Asian Art,Print,Awa no Naruto|雪月花 阿波鳴門之風景|The Whirlpools of Awa,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,1857,1857,1857,Triptych of polychrome woodblock prints; ink and color on paper,Image (triptych): 14 1/2 × 29 1/2 in. (36.8 × 74.9 cm) Image (each): 14 1/2 in. × 10 in. (36.8 × 25.4 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45025,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2835a–c,false,true,57067,Asian Art,Woodblock print,Kisoji no Sansen|雪月花 木曽路之山川|Kisō Mountains in Snow,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,1857,1857,1857,Triptych of polychrome woodblock prints; ink and color on paper,A and B (each): 14 5/8 x 10 in. (37.8 x 25.7 cm) C: 14 5/8 x 9 7/8 in. (37.8 x 25.1 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/57067,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1040,false,true,54325,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Keiri,"Japanese, active first half of the 19th century",,Keiri,Japanese,1800,1899,1835?,1835,1835,Polychrome woodblock print (surimono); ink and color on paper,8 1/2 x 7 5/16 in. (21.6 x 18.6 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54325,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP815,false,true,37259,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Furuyama Moroshige,"Japanese, active second half of the 17th century",,Furuyama Moroshige,Japanese,1650,1699,ca. 1690,1680,1700,Polychrome woodblock print; ink and color on paper,10 1/2 x 13 1/2 in. (26.7 x 34.3 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37259,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1443,false,true,55487,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Yanagawa Shunsui,"Japanese, active last quarter of the 18th century",,Yanagawa Shunsui,Japanese,1700,1799,ca. 1780,1770,1790,Polychrome woodblock print; ink and color on paper,H. 8 1/8 in. (20.6 cm); W. 6 in. (15.2 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55487,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1961,false,true,54535,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusen,"Japanese, active first half of the nineteenth century",,Katsushika Hokusen,Japanese,1800,1850,probably 1813,1813,1813,Polychrome woodblock print (surimono); ink and color on paper,5 1/2 x 7 3/8 in. (14 x 18.7 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54535,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3214,false,true,55200,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Munakata,,,Munakata,Japanese,1868,1912,ca. 1906,1896,1916,Polychrome woodblock print; ink and color on paper,12 1/2 x 8 7/8 in. (31.8 x 22.5 cm),"Gift of Lincoln Kirstein, 1959",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55200,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3218,false,true,55205,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Hashimoto Naoyoshi,,,Hashimoto Naoyoshi,Japanese,1838,1912,dated 1882,1882,1882,Two panels of a triptych of polychrome woodblock prints; ink and color on paper,14 x 9 1/8 in. (35.6 x 23.2 cm),"Gift of Lincoln Kirstein, 1959",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55205,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3219,false,true,55206,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Hashimoto Naoyoshi,,,Hashimoto Naoyoshi,Japanese,1838,1912,dated 1882,1882,1882,Two panels of a triptych of polychrome woodblock prints; ink and color on paper,14 x 9 1/8 in. (35.6 x 23.2 cm),"Gift of Lincoln Kirstein, 1959",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55206,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -MJP3,false,true,57114,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,After,Okumura Masanobu,"Japanese, 1686–1764",,Masanobu Okumura,Japanese,1686,1764,ca. 1910,1900,1920,Polychrome woodblock print; ink and color on paper,14 1/2 x 19 15/16 in. (36.8 x 50.6 cm),"Gift of Teiji Ito, 1919",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/57114,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3717,false,true,55981,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,After,Genki (Komai Ki),"Japanese, 1747–1797",,Genki,Japanese,1747,1797,1895,1895,1895,Polychrome woodblock print; ink and color on paper,Album: 8 1/2 x 5 5/16 in. (21.6 x 13.5 cm),"Gift of Donald Keene, in honor of Julia Meech-Pekarik, 1986",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55981,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2744,false,true,57014,Asian Art,Woodblock print,,Japan,Meiji period (1868–1912),,,,Artist,,Shibata Zeshin,"Japanese, 1807–1891",,Shibata Zeshin,Japanese,1807,1891,ca. 1875,1865,1885,Polychrome woodblock print; ink and color on paper,6 7/8 x 10 1/8 in. (17.5 x 25.7 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/57014,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2745,false,true,57015,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Shibata Zeshin,"Japanese, 1807–1891",,Shibata Zeshin,Japanese,1807,1891,ca. 1875,1865,1885,Polychrome woodblock print; ink and color on paper,7 3/4 x 10 in. (19.7 x 25.4 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/57015,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3215,false,true,55201,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Utagawa Kunisada II,"Japanese, 1823–1880",,Utagawa Kunisada II,Japanese,1823,1880,Feb. 1889 (Meiji 22),1889,1889,One sheet of a triptych of polychrome woodblock prints; ink and color on paper,Triptych 13 3/4 x 27 3/4 in. (35 x 70.5 cm),"Gift of Lincoln Kirstein, 1959",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55201,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3270,false,true,55340,Asian Art,Print,東京高輪鉄道蒸気車走行之図|Illustration of a Steam Locomotive Running on the Takanawa Railroad in Tokyo (Tōkyō takanawa tetsudō jōkisha sōkō no zu),Japan,Meiji period (1868–1912),,,,Artist,,Utagawa Kuniteru,"Japanese, 1830–1874",,Utagawa Kuniteru,Japanese,1830,1874,ca. 1873,1863,1883,Triptych of polychrome woodblock prints; ink and color on paper,14.2 x 29.1 in. (36.1 x 73.9 cm),"Gift of Lincoln Kirstein, 1959",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55340,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3347,false,true,55512,Asian Art,Print,東京汐留鉄道蒸気車通行図|Illustration of a Steam Locomotive Passing Shiodome in Tokyo (Tōkyō Shiodome testudō jōkisha tsūkō zu),Japan,Meiji period (1868–1912),,,,Artist,,Utagawa Kuniteru,"Japanese, 1830–1874",,Utagawa Kuniteru,Japanese,1830,1874,1872,1872,1872,Triptych of polychrome woodblock prints; ink and color on paper,Oban; 28 1/2 x 14 1/2 in. (72.4 x 36.8 cm),"Gift of Lincoln Kirstein, 1959",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55512,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3348,false,true,55513,Asian Art,Print,東京高輪鉄道蒸気車走行之全図|Tokyo /Takanawa Steam Railway,Japan,Meiji period (1868–1912),,,,Artist,,Utagawa Kuniteru,"Japanese, 1830–1874",,Utagawa Kuniteru,Japanese,1830,1874,"1879 (Meiji 3, 2nd month)",1879,1879,Triptych of polychrome woodblock prints; ink and color on paper,Oban; 14 3/16 x 30 in. (36 x 76.2 cm),"Gift of Lincoln Kirstein, 1959",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55513,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.243,false,true,73534,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Utagawa Yoshimori,"Japanese, 1830–1884",,Utagawa Yoshimori,Japanese,1830,1884,"October, 1872",1872,1872,Polychrome woodblock print; ink and color on paper,Image: 14 1/4 x 9 1/2 in. (36.2 x 24.1 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73534,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.242a–c,false,true,73533,Asian Art,Print,"Kak'koku han e sukushi Igirisu Rondon|""View in London,"" the Prosperity of Countries: London, England",Japan,Meiji period (1868–1912),,,,Artist,,Utagawa Yoshimori,"Japanese, 1830–1884",,Utagawa Yoshimori,Japanese,1830,1884,September 1872,1872,1872,Polychrome woodblock print; ink and color on paper,Image (a): 14 1/4 x 9 3/8 in. (36.2 x 23.8 cm) Image (b): 14 1/4 x 9 3/8 in. (36.2 x 23.8 cm) Image (c): 14 1/4 x 9 1/2 in. (36.2 x 24.1 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73533,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3249,false,true,45001,Asian Art,Print,Yokohama Igirisu Shokan hanei no zu|Picture of a Prosperous English Trading Firm in Yokohama,Japan,Meiji period (1868–1912),,,,Artist,,Utagawa Yoshiiku,"Japanese, 1833–1904",,Utagawa Yoshiiku,Japanese,1833,1904,January 1871,1871,1871,Triptych of polychrome woodblock prints; ink and color on paper,14 1/4 x 29 3/4 in. (36.2 x 75.6 cm),"Gift of Lincoln Kirstein, 1959",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45001,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.234a–c,false,true,73525,Asian Art,Print,「横浜英吉利西商館繁栄圖」|“The Flourishing of an English Trading Firm in Yokohama”,Japan,Meiji period (1868–1912),,,,Artist,,Utagawa Yoshiiku,"Japanese, 1833–1904",,Utagawa Yoshiiku,Japanese,1833,1904,"9th month, 1870",1870,1870,Triptych of polychrome woodblock prints; ink and color on paper,Image (a): 14 1/8 x 9 5/8 in. (35.9 x 24.4 cm) Image (b): 14 x 9 5/8 in. (35.6 x 24.4 cm) Image (c): 14 1/4 x 9 5/8 in. (36.2 x 24.4 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73525,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3198,false,true,55182,Asian Art,Print,"『東風俗福づくし 大 礼ふく』|“Ceremonial Attire” from the series An Array of Auspicious Customs of Eastern Japan (Azuma fūzoku, fukuzukushi: Tairei fuku)",Japan,Meiji period (1868–1912),,,,Artist,,Yōshū (Hashimoto) Chikanobu,"Japanese, 1838–1912",,Yōshū (Hashimoto) Chikanobu,Japanese,1838,1912,ca. 1889,1879,1899,Triptych of polychrome woodblock prints; ink and color on paper,Oban 14 5/8 x 9 11/16 in. (37.1 x 24.6 cm),"Gift of Lincoln Kirstein, 1959",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55182,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3201,false,true,55185,Asian Art,Woodblock print,"『東風俗福づくし 洋 ふく』|Western Clothing from the series An Array of Auspicious Customs of Eastern Japan (Azuma fūzoku, fukuzukushi-Yōfuku)",Japan,Meiji period (1868–1912),,,,Artist,,Yōshū (Hashimoto) Chikanobu,"Japanese, 1838–1912",,Yōshū (Hashimoto) Chikanobu,Japanese,1838,1912,1889,1889,1889,Triptych of polychrome woodblock prints; ink and color on paper,Oban 12 15/16 x 8 15/16 in. (32.9 x 22.7 cm),"Gift of Lincoln Kirstein, 1959",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55185,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3221,false,true,55209,Asian Art,Print,"勲功之将天杯賜之図|Illustration of the Honored Commanders, Receiving the Emperor's Gift Cup (Kunkō no shō tenpai o tamau no zu)",Japan,Meiji period (1868–1912),,,,Artist,,Yōshū (Hashimoto) Chikanobu,"Japanese, 1838–1912",,Yōshū (Hashimoto) Chikanobu,Japanese,1838,1912,"September, 1877",1877,1877,One sheet of a triptych of polychrome woodblock prints; ink and color on paper,Image (each): 13 7/8 × 9 1/4 in. (35.2 × 23.5 cm) Mat: 18 3/4 × 23 1/4 in. (47.6 × 59.1 cm),"Gift of Lincoln Kirstein, 1959",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55209,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3222,false,true,55211,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Yōshū (Hashimoto) Chikanobu,"Japanese, 1838–1912",,Yōshū (Hashimoto) Chikanobu,Japanese,1838,1912,"Sept. 5, 1877 (Meiji 10)",1877,1877,One sheet of a triptych of polychrome woodblock prints; ink and color on paper,Image (each): 13 7/8 × 9 1/4 in. (35.2 × 23.5 cm) Mat: 23 × 37 in. (58.4 × 94 cm),"Gift of Lincoln Kirstein, 1959",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55211,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3223,false,true,55213,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Yōshū (Hashimoto) Chikanobu,"Japanese, 1838–1912",,Yōshū (Hashimoto) Chikanobu,Japanese,1838,1912,"Sept. 5, 1877 (Meiji 10)",1877,1877,One sheet of a triptych of polychrome woodblock prints; ink and color on paper,Image: 14 in. × 9 1/4 in. (35.6 × 23.5 cm),"Gift of Lincoln Kirstein, 1959",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55213,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3236,false,true,55249,Asian Art,Print,大山綱良糾問の図|Illustration of the Inquisition of Ōyama Tsunayoshi (Ōyama Tsunayoshi kyūmon no zu),Japan,Meiji period (1868–1912),,,,Artist,,Yōshū (Hashimoto) Chikanobu,"Japanese, 1838–1912",,Yōshū (Hashimoto) Chikanobu,Japanese,1838,1912,"August, 1877",1877,1877,One sheet of a triptych of polychrome woodblock prints; ink and color on paper,Triptych 13 15/16 x 28 1/8 in. (35.4 x 71.4 cm),"Gift of Lincoln Kirstein, 1959",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55249,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3237,false,true,55250,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Yōshū (Hashimoto) Chikanobu,"Japanese, 1838–1912",,Yōshū (Hashimoto) Chikanobu,Japanese,1838,1912,"August 27, 1877 (Meiji 10)",1877,1877,One sheet of a triptych of polychrome woodblock prints; ink and color on paper,Triptych 13 15/16 x 28 1/8 in. (35.4 x 71.4 cm),"Gift of Lincoln Kirstein, 1959",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55250,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3238,false,true,55251,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Yōshū (Hashimoto) Chikanobu,"Japanese, 1838–1912",,Yōshū (Hashimoto) Chikanobu,Japanese,1838,1912,"August 27, 1877 (Meiji 10)",1877,1877,One sheet of a triptych of polychrome woodblock prints; ink and color on paper,Triptych 13 15/16 x 28 1/8 in. (35.4 x 71.4 cm),"Gift of Lincoln Kirstein, 1959",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55251,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3239,false,true,55252,Asian Art,Print,チャリネ大曲馬御遊覧ノ図|Illustration of the Imperial Excursion to see the Charini's Circus (Charine daikyokuba goyūran no zu),Japan,Meiji period (1868–1912),,,,Artist,,Yōshū (Hashimoto) Chikanobu,"Japanese, 1838–1912",,Yōshū (Hashimoto) Chikanobu,Japanese,1838,1912,"November, 1886",1886,1886,Polychrome woodblock print; ink and color on paper,14 3/4 x 27 3/4 in. (37.5 x 70.5 cm),"Gift of Lincoln Kirstein, 1959",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55252,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3240,false,true,55253,Asian Art,Print,憲法発布式之図|Illustration of the Ceremony Issuing the Constitution (Kenpō happu shiki no zu),Japan,Meiji period (1868–1912),,,,Artist,,Yōshū (Hashimoto) Chikanobu,"Japanese, 1838–1912",,Yōshū (Hashimoto) Chikanobu,Japanese,1838,1912,1889,1889,1889,Polychrome woodblock print; ink and color on paper,Oban 14 1/2 x 29 5/8 in. (36.8 x 75.2 cm),"Gift of Lincoln Kirstein, 1959",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55253,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3250,false,true,55261,Asian Art,Print,『鹿児島賊徒鎮静依諸将 天杯頂戴之図』|Leaders of the Pacification of the Kagoshima Rebels Celebrating with Cups of Wine from the Emperor (Kagoshima zokuto chinsei ni yotte shoshō tenhai chōdai no zu),Japan,Meiji period (1868–1912),,,,Artist,,Yōshū (Hashimoto) Chikanobu,"Japanese, 1838–1912",,Yōshū (Hashimoto) Chikanobu,Japanese,1838,1912,"September 20th, 1877",1877,1877,Triptych of polychrome woodblock prints; ink and color on paper,Image (each): 14 1/2 × 9 3/4 in. (36.8 × 24.8 cm),"Gift of Lincoln Kirstein, 1959",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55261,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3263,false,true,55323,Asian Art,Woodblock print,"西国鎮静撫諸将天杯賜之図|Illustration of the Commanders who Pacified Western Japan, Receiving the Emperor's Gift Cups (Saigoku chinbu shoshō tenpai o tamau no zu)",Japan,Meiji period (1868–1912),,,,Artist,,Yōshū (Hashimoto) Chikanobu,"Japanese, 1838–1912",,Yōshū (Hashimoto) Chikanobu,Japanese,1838,1912,"July, 1877",1877,1877,Polychrome woodblock print; ink and color on paper,Oban 14 1/4 x 28 3/4 in. (36.2 x 73 cm),"Gift of Lincoln Kirstein, 1959",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55323,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3269,false,true,55338,Asian Art,Print,世上各国写画帝王鏡|Mirror of Portraits of All Sovereigns in the World (Sejō kakkoku shaga teiō kagami),Japan,Meiji period (1868–1912),,,,Artist,,Yōshū (Hashimoto) Chikanobu,"Japanese, 1838–1912",,Yōshū (Hashimoto) Chikanobu,Japanese,1838,1912,"April, 1879",1879,1879,Polychrome woodblock print; ink and color on paper,14 5/8 x 29 in. (37.1 x 73.7 cm),"Gift of Lincoln Kirstein, 1959",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55338,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3273,false,true,55347,Asian Art,Woodblock print,『上野不忍競馬図』|View of the Horse Track at Shinobazu in Ueno Park (Ueno shinobazu keiba zu),Japan,Meiji period (1868–1912),,,,Artist,,Yōshū (Hashimoto) Chikanobu,"Japanese, 1838–1912",,Yōshū (Hashimoto) Chikanobu,Japanese,1838,1912,1885,1885,1885,Triptych of polychrome woodblock prints; ink and color on paper,Image: 14 1/2 × 29 5/8 in. (36.8 × 75.2 cm),"Gift of Lincoln Kirstein, 1959",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55347,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3276,false,true,53314,Asian Art,Print,『欧州管絃楽合奏之図』|Concert of European Music (Ōshū kangengaku gassō no zu),Japan,Meiji period (1868–1912),,,,Artist,,Yōshū (Hashimoto) Chikanobu,"Japanese, 1838–1912",,Yōshū (Hashimoto) Chikanobu,Japanese,1838,1912,1889,1889,1889,Triptych of polychrome woodblock prints; ink and color on paper,Image: 14 1/2 x 29 in. (36.8 x 73.7 cm),"Gift of Lincoln Kirstein, 1959",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/53314,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3306,false,true,55408,Asian Art,Print,『踏舞会 上野桜花観 遊ノ図』|A Dance Party: Enjoying Cherry Blossom Viewing at Ueno (Tōbukai Ueno ōka yūran no zu),Japan,Meiji period (1868–1912),,,,Artist,,Yōshū (Hashimoto) Chikanobu,"Japanese, 1838–1912",,Yōshū (Hashimoto) Chikanobu,Japanese,1838,1912,"March, 1887",1887,1887,Triptych of polychrome woodblock prints; ink and color on paper,Image: 14 in. × 28 1/4 in. (35.6 × 71.8 cm),"Gift of Lincoln Kirstein, 1959",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55408,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3338,false,true,55492,Asian Art,Print,『高貴納涼ノ図』|Nobility in the Evening Cool (Koki nōryō no zu),Japan,Meiji period (1868–1912),,,,Artist,,Yōshū (Hashimoto) Chikanobu,"Japanese, 1838–1912",,Yōshū (Hashimoto) Chikanobu,Japanese,1838,1912,1887,1887,1887,Triptych of polychrome woodblock prints; ink and color on paper,Image: 13 3/4 x 27 1/2 in. (34.9 x 69.9 cm),"Gift of Lincoln Kirstein, 1959",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55492,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3340,false,true,55499,Asian Art,Woodblock prints,『女官洋服裁縫之図』|Court Ladies Sewing Western Clothing (Jokan yōfuku saihō no zu),Japan,Meiji period (1868–1912),,,,Artist,,Yōshū (Hashimoto) Chikanobu,"Japanese, 1838–1912",,Yōshū (Hashimoto) Chikanobu,Japanese,1838,1912,"August 23rd, 1887",1887,1887,Triptych of polychrome woodblock prints; ink and color on paper,Image: 14 5/16 x 29 11/16 in. (36.4 x 75.4 cm),"Gift of Lincoln Kirstein, 1959",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55499,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3341,false,true,55500,Asian Art,Woodblock print,『雪中梅荘群児遊戯 図』|Children Playing in the Snow under Plum Trees in Bloom (Secchū baisō gunji yūgi zu),Japan,Meiji period (1868–1912),,,,Artist,,Yōshū (Hashimoto) Chikanobu,"Japanese, 1838–1912",,Yōshū (Hashimoto) Chikanobu,Japanese,1838,1912,November 1887,1887,1887,Triptych of polychrome woodblock prints; ink and color on paper,Image: 14 x 28 3/4 in. (35.6 x 73 cm),"Gift of Lincoln Kirstein, 1959",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55500,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3342,false,true,55502,Asian Art,Print,『雨過洗庭之図』|A Garden Refreshed by the Passing Rain (Ukasentei no zu),Japan,Meiji period (1868–1912),,,,Artist,,Yōshū (Hashimoto) Chikanobu,"Japanese, 1838–1912",,Yōshū (Hashimoto) Chikanobu,Japanese,1838,1912,July 1888,1888,1888,Polychrome woodblock print; ink and color on paper,Image: 14 3/4 x 28 1/4 in. (37.5 x 71.8 cm),"Gift of Lincoln Kirstein, 1959",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55502,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3343,false,true,55504,Asian Art,Print,『開花貴婦人競』|A Contest of Elegant Ladies among the Cherry Blossoms (Kaika kifujin kisoi),Japan,Meiji period (1868–1912),,,,Artist,,Yōshū (Hashimoto) Chikanobu,"Japanese, 1838–1912",,Yōshū (Hashimoto) Chikanobu,Japanese,1838,1912,September 1887,1887,1887,Triptych of polychrome woodblock prints; ink and color on paper,Image: 14 5/8 x 29 1/8 in. (37.1 x 74 cm),"Gift of Lincoln Kirstein, 1959",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55504,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3386,false,true,55559,Asian Art,Print,"西国諸将鎮静天杯賜ル之図|Illustration of the Commanders who Pacified Western Japan, Receiving the Emperor's Gift Cups (Saigoku chinsei shoshō tenpai o tamawaru no zu)",Japan,Meiji period (1868–1912),,,,Artist,,Yōshū (Hashimoto) Chikanobu,"Japanese, 1838–1912",,Yōshū (Hashimoto) Chikanobu,Japanese,1838,1912,"September, 1887",1887,1887,Triptych of polychrome woodblock prints; ink and color on paper,14 1/4 x 29 1/4 in. (36.2 x 74.3 cm),"Gift of Lincoln Kirstein, 1960",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55559,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3422,false,true,55623,Asian Art,Print,帝国議会貴族院之図|Illustration of The Imperial Assembly of the House of Peers (Teikoku gikai kizokuin no zu),Japan,Meiji period (1868–1912),,,,Artist,,Yōshū (Hashimoto) Chikanobu,"Japanese, 1838–1912",,Yōshū (Hashimoto) Chikanobu,Japanese,1838,1912,1890,1890,1890,Triptych of polychrome woodblock prints; ink and color on paper,Oban; 14 x 29 3/4 in. (35.6 x 75.6 cm),"Gift of Lincoln Kirstein, 1962",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55623,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3519,false,true,55820,Asian Art,Print,千代田の大奥|The Inner Palace of Chiyoda (Chiyoda no Ōoku),Japan,Meiji period (1868–1912),,,,Artist,,Yōshū (Hashimoto) Chikanobu,"Japanese, 1838–1912",,Yōshū (Hashimoto) Chikanobu,Japanese,1838,1912,1895,1895,1895,Diptych of polychrome woodblock prints; ink and color on paper,L. (page) 13 7/8 in. (35.2 cm); W. (page) 9 1/4 in. (23.4 cm),"Gift of Mrs. W. Walton Butterworth, 1979",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55820,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3520,false,true,55821,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Yōshū (Hashimoto) Chikanobu,"Japanese, 1838–1912",,Yōshū (Hashimoto) Chikanobu,Japanese,1838,1912,1895,1895,1895,Triptych of polychrome woodblock prints; ink and color on paper,L. (page) 13 7/8 in. (35.2 cm); W. (page) 9 1/4 in. (23.4 cm),"Gift of Mrs. W. Walton Butterworth, 1979",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55821,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3521,false,true,55822,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Yōshū (Hashimoto) Chikanobu,"Japanese, 1838–1912",,Yōshū (Hashimoto) Chikanobu,Japanese,1838,1912,1895,1895,1895,Triptych of polychrome woodblock prints; ink and color on paper,L. (page) 13 7/8 in. (35.2 cm); W. (page) 9 1/4 in. (23.4 cm),"Gift of Mrs. W. Walton Butterworth, 1979",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55822,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3522,false,true,55823,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Yōshū (Hashimoto) Chikanobu,"Japanese, 1838–1912",,Yōshū (Hashimoto) Chikanobu,Japanese,1838,1912,1895,1895,1895,Triptych of polychrome woodblock prints; ink and color on paper,L. (page) 13 7/8 in. (35.2 cm); W. (page) 9 1/4 in. (23.4 cm),"Gift of Mrs. W. Walton Butterworth, 1979",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55823,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3523,false,true,55824,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Yōshū (Hashimoto) Chikanobu,"Japanese, 1838–1912",,Yōshū (Hashimoto) Chikanobu,Japanese,1838,1912,1895,1895,1895,Triptych of polychrome woodblock prints; ink and color on paper,L. (page) 13 7/8 in. (35.2 cm); W. (page) 9 1/4 in. (23.4 cm),"Gift of Mrs. W. Walton Butterworth, 1979",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55824,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3524,false,true,55825,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Yōshū (Hashimoto) Chikanobu,"Japanese, 1838–1912",,Yōshū (Hashimoto) Chikanobu,Japanese,1838,1912,1895,1895,1895,Triptych of polychrome woodblock prints; ink and color on paper,L. (page) 13 7/8 in. (35.2 cm); W. (page) 9 1/4 in. (23.4 cm),"Gift of Mrs. W. Walton Butterworth, 1979",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55825,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3525,false,true,55826,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Yōshū (Hashimoto) Chikanobu,"Japanese, 1838–1912",,Yōshū (Hashimoto) Chikanobu,Japanese,1838,1912,1895,1895,1895,Triptych of polychrome woodblock prints; ink and color on paper,L. (page) 13 7/8 in. (35.2 cm); W. (page) 9 1/4 in. (23.4 cm),"Gift of Mrs. W. Walton Butterworth, 1979",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55826,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3526,false,true,55827,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Yōshū (Hashimoto) Chikanobu,"Japanese, 1838–1912",,Yōshū (Hashimoto) Chikanobu,Japanese,1838,1912,1895,1895,1895,Triptych of polychrome woodblock prints; ink and color on paper,L. (page) 13 7/8 in. (35.2 cm); W. (page) 9 1/4 in. (23.4 cm),"Gift of Mrs. W. Walton Butterworth, 1979",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55827,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3527,false,true,55828,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Yōshū (Hashimoto) Chikanobu,"Japanese, 1838–1912",,Yōshū (Hashimoto) Chikanobu,Japanese,1838,1912,1895,1895,1895,Triptych of polychrome woodblock prints; ink and color on paper,L. (page) 13 7/8 in. (35.2 cm); W. (page) 9 1/4 in. (23.4 cm),"Gift of Mrs. W. Walton Butterworth, 1979",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55828,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3528,false,true,55829,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Yōshū (Hashimoto) Chikanobu,"Japanese, 1838–1912",,Yōshū (Hashimoto) Chikanobu,Japanese,1838,1912,1895,1895,1895,Triptych of polychrome woodblock prints; ink and color on paper,L. (page) 13 7/8 in. (35.2 cm); W. (page) 9 1/4 in. (23.4 cm),"Gift of Mrs. W. Walton Butterworth, 1979",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55829,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3529,false,true,55830,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Yōshū (Hashimoto) Chikanobu,"Japanese, 1838–1912",,Yōshū (Hashimoto) Chikanobu,Japanese,1838,1912,1895,1895,1895,Triptych of polychrome woodblock prints; ink and color on paper,L. (page) 13 7/8 in. (35.2 cm); W. (page) 9 1/4 in. (23.4 cm),"Gift of Mrs. W. Walton Butterworth, 1979",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55830,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3530,false,true,55831,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Yōshū (Hashimoto) Chikanobu,"Japanese, 1838–1912",,Yōshū (Hashimoto) Chikanobu,Japanese,1838,1912,1895,1895,1895,Triptych of polychrome woodblock prints; ink and color on paper,L. (page) 13 7/8 in. (35.2 cm); W. (page) 9 1/4 in. (23.4 cm),"Gift of Mrs. W. Walton Butterworth, 1979",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55831,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3531,false,true,55832,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Yōshū (Hashimoto) Chikanobu,"Japanese, 1838–1912",,Yōshū (Hashimoto) Chikanobu,Japanese,1838,1912,1895,1895,1895,Triptych of polychrome woodblock prints; ink and color on paper,L. (page) 13 7/8 in. (35.2 cm); W. (page) 9 1/4 in. (23.4 cm),"Gift of Mrs. W. Walton Butterworth, 1979",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55832,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3532,false,true,55833,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Yōshū (Hashimoto) Chikanobu,"Japanese, 1838–1912",,Yōshū (Hashimoto) Chikanobu,Japanese,1838,1912,1895,1895,1895,Triptych of polychrome woodblock prints; ink and color on paper,L. (page) 13 7/8 in. (35.2 cm); W. (page) 9 1/4 in. (23.4 cm),"Gift of Mrs. W. Walton Butterworth, 1979",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55833,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3533,false,true,55834,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Yōshū (Hashimoto) Chikanobu,"Japanese, 1838–1912",,Yōshū (Hashimoto) Chikanobu,Japanese,1838,1912,1895,1895,1895,Triptych of polychrome woodblock prints; ink and color on paper,L. (page) 13 7/8 in. (35.2 cm); W. (page) 9 1/4 in. (23.4 cm),"Gift of Mrs. W. Walton Butterworth, 1979",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55834,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3534,false,true,55835,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Yōshū (Hashimoto) Chikanobu,"Japanese, 1838–1912",,Yōshū (Hashimoto) Chikanobu,Japanese,1838,1912,1895,1895,1895,Triptych of polychrome woodblock prints; ink and color on paper,L. (page) 13 7/8 in. (35.2 cm); W. (page) 9 1/4 in. (23.4 cm),"Gift of Mrs. W. Walton Butterworth, 1979",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55835,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3535,false,true,55836,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Yōshū (Hashimoto) Chikanobu,"Japanese, 1838–1912",,Yōshū (Hashimoto) Chikanobu,Japanese,1838,1912,1895,1895,1895,Triptych of polychrome woodblock prints; ink and color on paper,L. (page) 13 7/8 in. (35.2 cm); W. (page) 9 1/4 in. (23.4 cm),"Gift of Mrs. W. Walton Butterworth, 1979",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55836,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3536,false,true,55837,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Yōshū (Hashimoto) Chikanobu,"Japanese, 1838–1912",,Yōshū (Hashimoto) Chikanobu,Japanese,1838,1912,1895,1895,1895,Triptych of polychrome woodblock prints; ink and color on paper,L. (page) 13 7/8 in. (35.2 cm); W. (page) 9 1/4 in. (23.4 cm),"Gift of Mrs. W. Walton Butterworth, 1979",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55837,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3537,false,true,55838,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Yōshū (Hashimoto) Chikanobu,"Japanese, 1838–1912",,Yōshū (Hashimoto) Chikanobu,Japanese,1838,1912,1895,1895,1895,Triptych of polychrome woodblock prints; ink and color on paper,L. (page) 13 7/8 in. (35.2 cm); W. (page) 9 1/4 in. (23.4 cm),"Gift of Mrs. W. Walton Butterworth, 1979",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55838,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3538,false,true,55839,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Yōshū (Hashimoto) Chikanobu,"Japanese, 1838–1912",,Yōshū (Hashimoto) Chikanobu,Japanese,1838,1912,1895,1895,1895,Triptych of polychrome woodblock prints; ink and color on paper,L. (page) 13 7/8 in. (35.2 cm); W. (page) 9 1/4 in. (23.4 cm),"Gift of Mrs. W. Walton Butterworth, 1979",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55839,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3540,false,true,55841,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Yōshū (Hashimoto) Chikanobu,"Japanese, 1838–1912",,Yōshū (Hashimoto) Chikanobu,Japanese,1838,1912,1895,1895,1895,Polychrome woodblock prints; ink and color on paper,L. (page) 13 7/8 in. (35.2 cm); W. (page) 9 1/4 in. (23.4 cm),"Gift of Mrs. W. Walton Butterworth, 1979",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55841,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3541,false,true,55842,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Yōshū (Hashimoto) Chikanobu,"Japanese, 1838–1912",,Yōshū (Hashimoto) Chikanobu,Japanese,1838,1912,1895,1895,1895,Quintiptych of polychrome woodblock prints; ink and color on paper,L. (page) 13 7/8 in. (35.2 cm); W. (page) 9 1/4 in. (23.4 cm),"Gift of Mrs. W. Walton Butterworth, 1979",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55842,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3542,false,true,55843,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Yōshū (Hashimoto) Chikanobu,"Japanese, 1838–1912",,Yōshū (Hashimoto) Chikanobu,Japanese,1838,1912,1895,1895,1895,Triptych of polychrome woodblock prints; ink and color on paper,L. (page) 13 7/8 in. (35.2 cm); W. (page) 9 1/4 in. (23.4 cm),"Gift of Mrs. W. Walton Butterworth, 1979",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55843,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3543,false,true,55844,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Yōshū (Hashimoto) Chikanobu,"Japanese, 1838–1912",,Yōshū (Hashimoto) Chikanobu,Japanese,1838,1912,1895,1895,1895,Triptych of polychrome woodblock prints; ink and color on paper,L. (page) 13 7/8 in. (35.2 cm); W. (page) 9 1/4 in. (23.4 cm),"Gift of Mrs. W. Walton Butterworth, 1979",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55844,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3544,false,true,55845,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Yōshū (Hashimoto) Chikanobu,"Japanese, 1838–1912",,Yōshū (Hashimoto) Chikanobu,Japanese,1838,1912,1895,1895,1895,Triptych of polychrome woodblock prints; ink and color on paper,L. (page) 13 7/8 in. (35.2 cm); W. (page) 9 1/4 in. (23.4 cm),"Gift of Mrs. W. Walton Butterworth, 1979",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55845,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3545,false,true,55846,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Yōshū (Hashimoto) Chikanobu,"Japanese, 1838–1912",,Yōshū (Hashimoto) Chikanobu,Japanese,1838,1912,1895,1895,1895,Triptych of polychrome woodblock prints; ink and color on paper,L. (page) 13 7/8 in. (35.2 cm); W. (page) 9 1/4 in. (23.4 cm),"Gift of Mrs. W. Walton Butterworth, 1979",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55846,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3546,false,true,55847,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Yōshū (Hashimoto) Chikanobu,"Japanese, 1838–1912",,Yōshū (Hashimoto) Chikanobu,Japanese,1838,1912,1895,1895,1895,Triptych of polychrome woodblock prints; ink and color on paper,L. (page) 13 7/8 in. (35.2 cm); W. (page) 9 1/4 in. (23.4 cm),"Gift of Mrs. W. Walton Butterworth, 1979",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55847,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3547,false,true,55848,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Yōshū (Hashimoto) Chikanobu,"Japanese, 1838–1912",,Yōshū (Hashimoto) Chikanobu,Japanese,1838,1912,1895,1895,1895,Triptych of polychrome woodblock prints; ink and color on paper,L. (page) 13 7/8 in. (35.2 cm); W. (page) 9 1/4 in. (23.4 cm),"Gift of Mrs. W. Walton Butterworth, 1979",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55848,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3548,false,true,55849,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Yōshū (Hashimoto) Chikanobu,"Japanese, 1838–1912",,Yōshū (Hashimoto) Chikanobu,Japanese,1838,1912,1895,1895,1895,Triptych of polychrome woodblock prints; ink and color on paper,L. (page) 13 7/8 in. (35.2 cm); W. (page) 9 1/4 in. (23.4 cm),"Gift of Mrs. W. Walton Butterworth, 1979",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55849,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3549,false,true,55850,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Yōshū (Hashimoto) Chikanobu,"Japanese, 1838–1912",,Yōshū (Hashimoto) Chikanobu,Japanese,1838,1912,1895,1895,1895,Triptych of polychrome woodblock prints; ink and color on paper,L. (page) 13 7/8 in. (35.2 cm); W. (page) 9 1/4 in. (23.4 cm),"Gift of Mrs. W. Walton Butterworth, 1979",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55850,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3550,false,true,55851,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Yōshū (Hashimoto) Chikanobu,"Japanese, 1838–1912",,Yōshū (Hashimoto) Chikanobu,Japanese,1838,1912,1895,1895,1895,Triptych of polychrome woodblock prints; ink and color on paper,L. (page) 13 7/8 in. (35.2 cm); W. (page) 9 1/4 in. (23.4 cm),"Gift of Mrs. W. Walton Butterworth, 1979",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55851,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3551,false,true,55852,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Yōshū (Hashimoto) Chikanobu,"Japanese, 1838–1912",,Yōshū (Hashimoto) Chikanobu,Japanese,1838,1912,1895,1895,1895,Triptych of polychrome woodblock prints; ink and color on paper,L. (page) 13 7/8 in. (35.2 cm); W. (page) 9 1/4 in. (23.4 cm),"Gift of Mrs. W. Walton Butterworth, 1979",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55852,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3552,false,true,55853,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Yōshū (Hashimoto) Chikanobu,"Japanese, 1838–1912",,Yōshū (Hashimoto) Chikanobu,Japanese,1838,1912,1895,1895,1895,Triptych of polychrome woodblock prints; ink and color on paper,L. (page) 13 7/8 in. (35.2 cm); W. (page) 9 1/4 in. (23.4 cm),"Gift of Mrs. W. Walton Butterworth, 1979",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55853,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3553,false,true,55854,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Yōshū (Hashimoto) Chikanobu,"Japanese, 1838–1912",,Yōshū (Hashimoto) Chikanobu,Japanese,1838,1912,1895,1895,1895,Triptych of polychrome woodblock prints; ink and color on paper,L. (page) 13 7/8 in. (35.2 cm); W. (page) 9 1/4 in. (23.4 cm),"Gift of Mrs. W. Walton Butterworth, 1979",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55854,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3554,false,true,55855,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Yōshū (Hashimoto) Chikanobu,"Japanese, 1838–1912",,Yōshū (Hashimoto) Chikanobu,Japanese,1838,1912,1895,1895,1895,Triptych of polychrome woodblock prints; ink and color on paper,L. (page) 13 7/8 in. (35.2 cm); W. (page) 9 1/4 in. (23.4 cm),"Gift of Mrs. W. Walton Butterworth, 1979",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55855,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3555,false,true,55856,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Yōshū (Hashimoto) Chikanobu,"Japanese, 1838–1912",,Yōshū (Hashimoto) Chikanobu,Japanese,1838,1912,1895,1895,1895,Triptych from an album of polychrome woodblock prints; ink and color on paper,13 7/8 x 9 1/4 in. (35.2 x 23.5 cm),"Gift of Mrs. W. Walton Butterworth, 1979",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55856,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3556,false,true,55857,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Yōshū (Hashimoto) Chikanobu,"Japanese, 1838–1912",,Yōshū (Hashimoto) Chikanobu,Japanese,1838,1912,1895,1895,1895,Triptych from an album of polychrome woodblock prints; ink and color on paper,9 1/4 x 13 7/8 in. (23.5 x 35.2 cm),"Gift of Mrs. W. Walton Butterworth, 1979",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55857,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3557,false,true,55858,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Yōshū (Hashimoto) Chikanobu,"Japanese, 1838–1912",,Yōshū (Hashimoto) Chikanobu,Japanese,1838,1912,1895,1895,1895,Triptych of polychrome woodblock prints; ink and color on paper,L. (page) 13 7/8 in. (35.2 cm); W. (page) 9 1/4 in. (23.4 cm),"Gift of Mrs. W. Walton Butterworth, 1979",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55858,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3558,false,true,55859,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Yōshū (Hashimoto) Chikanobu,"Japanese, 1838–1912",,Yōshū (Hashimoto) Chikanobu,Japanese,1838,1912,1895,1895,1895,Triptych of polychrome woodblock prints; ink and color on paper,L. (page) 13 7/8 in. (35.2 cm); W. (page) 9 1/4 in. (23.4 cm),"Gift of Mrs. W. Walton Butterworth, 1979",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55859,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3559,false,true,55860,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Yōshū (Hashimoto) Chikanobu,"Japanese, 1838–1912",,Yōshū (Hashimoto) Chikanobu,Japanese,1838,1912,1895,1895,1895,Pentaptych of polychrome woodblock prints; ink and color on paper,L. (page) 13 7/8 in. (35.2 cm); W. (page) 9 1/4 in. (23.4 cm),"Gift of Mrs. W. Walton Butterworth, 1979",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55860,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3560,false,true,55861,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Yōshū (Hashimoto) Chikanobu,"Japanese, 1838–1912",,Yōshū (Hashimoto) Chikanobu,Japanese,1838,1912,1897,1897,1897,Diptych of polychrome woodblock prints; ink and color on paper,L. (page) 13 7/8 in. (35.2 cm); W. (page) 9 1/4 in. (23.4 cm); thickness of album 1 9/16 in. (4 cm),"Gift of Mrs. W. Walton Butterworth, 1979",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55861,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3561,false,true,55864,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Yōshū (Hashimoto) Chikanobu,"Japanese, 1838–1912",,Yōshū (Hashimoto) Chikanobu,Japanese,1838,1912,1897,1897,1897,Hexaptych of polychrome woodblock prints; ink and color on paper,L. (page) 13 7/8 in. (35.2 cm); W. (page) 9 1/4 in. (23.4 cm); thickness of album 1 9/16 in. (4 cm),"Gift of Mrs. W. Walton Butterworth, 1979",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55864,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3562,false,true,55865,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Yōshū (Hashimoto) Chikanobu,"Japanese, 1838–1912",,Yōshū (Hashimoto) Chikanobu,Japanese,1838,1912,1897,1897,1897,Triptych of polychrome woodblock prints; ink and color on paper,L. (page) 13 7/8 in. (35.2 cm); W. (page) 9 1/4 in. (23.4 cm); thickness of album 1 9/16 in. (4 cm),"Gift of Mrs. W. Walton Butterworth, 1979",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55865,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3563,false,true,55866,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Yōshū (Hashimoto) Chikanobu,"Japanese, 1838–1912",,Yōshū (Hashimoto) Chikanobu,Japanese,1838,1912,1897,1897,1897,Triptych of polychrome woodblock prints; ink and color on paper,L. (page) 13 7/8 in. (35.2 cm); W. (page) 9 1/4 in. (23.4 cm); thickness of album 1 9/16 in. (4 cm),"Gift of Mrs. W. Walton Butterworth, 1979",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55866,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3564,false,true,55868,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Yōshū (Hashimoto) Chikanobu,"Japanese, 1838–1912",,Yōshū (Hashimoto) Chikanobu,Japanese,1838,1912,1897,1897,1897,Triptych of polychrome woodblock prints; ink and color on paper,L. (page) 13 7/8 in. (35.2 cm); W. (page) 9 1/4 in. (23.4 cm); thickness of album 1 9/16 in. (4 cm),"Gift of Mrs. W. Walton Butterworth, 1979",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55868,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3565,false,true,55869,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Yōshū (Hashimoto) Chikanobu,"Japanese, 1838–1912",,Yōshū (Hashimoto) Chikanobu,Japanese,1838,1912,1897,1897,1897,Triptych of polychrome woodblock prints; ink and color on paper,L. (page) 13 7/8 in. (35.2 cm); W. (page) 9 1/4 in. (23.4 cm); thickness of album 1 9/16 in. (4 cm),"Gift of Mrs. W. Walton Butterworth, 1979",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55869,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3566,false,true,55870,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Yōshū (Hashimoto) Chikanobu,"Japanese, 1838–1912",,Yōshū (Hashimoto) Chikanobu,Japanese,1838,1912,1897,1897,1897,Triptych of polychrome woodblock prints; ink and color on paper,L. (page) 13 7/8 in. (35.2 cm); W. (page) 9 1/4 in. (23.4 cm); thickness of album 1 9/16 in. (4 cm),"Gift of Mrs. W. Walton Butterworth, 1979",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55870,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3567,false,true,55871,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Yōshū (Hashimoto) Chikanobu,"Japanese, 1838–1912",,Yōshū (Hashimoto) Chikanobu,Japanese,1838,1912,1897,1897,1897,Triptych of polychrome woodblock prints; ink and color on paper,L. (page) 13 7/8 in. (35.2 cm); W. (page) 9 1/4 in. (23.4 cm); thickness of album 1 9/16 in. (4 cm),"Gift of Mrs. W. Walton Butterworth, 1979",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55871,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3568,false,true,55872,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Yōshū (Hashimoto) Chikanobu,"Japanese, 1838–1912",,Yōshū (Hashimoto) Chikanobu,Japanese,1838,1912,1897,1897,1897,Triptych of polychrome woodblock prints; ink and color on paper,L. (page) 13 7/8 in. (35.2 cm); W. (page) 9 1/4 in. (23.4 cm); thickness of album 1 9/16 in. (4 cm),"Gift of Mrs. W. Walton Butterworth, 1979",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55872,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3569,false,true,55873,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Yōshū (Hashimoto) Chikanobu,"Japanese, 1838–1912",,Yōshū (Hashimoto) Chikanobu,Japanese,1838,1912,1897,1897,1897,Triptych of polychrome woodblock prints; ink and color on paper,L. (page) 13 7/8 in. (35.2 cm); W. (page) 9 1/4 in. (23.4 cm); thickness of album 1 9/16 in. (4 cm),"Gift of Mrs. W. Walton Butterworth, 1979",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55873,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3570,false,true,55874,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Yōshū (Hashimoto) Chikanobu,"Japanese, 1838–1912",,Yōshū (Hashimoto) Chikanobu,Japanese,1838,1912,1897,1897,1897,Triptych of polychrome woodblock prints; ink and color on paper,L. (page) 13 7/8 in. (35.2 cm); W. (page) 9 1/4 in. (23.4 cm); thickness of album 1 9/16 in. (4 cm),"Gift of Mrs. W. Walton Butterworth, 1979",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55874,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3571,false,true,55875,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Yōshū (Hashimoto) Chikanobu,"Japanese, 1838–1912",,Yōshū (Hashimoto) Chikanobu,Japanese,1838,1912,1897,1897,1897,Triptych of polychrome woodblock prints; ink and color on paper,L. (page) 13 7/8 in. (35.2 cm); W. (page) 9 1/4 in. (23.4 cm); thickness of album 1 9/16 in. (4 cm),"Gift of Mrs. W. Walton Butterworth, 1979",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55875,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3572,false,true,55876,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Yōshū (Hashimoto) Chikanobu,"Japanese, 1838–1912",,Yōshū (Hashimoto) Chikanobu,Japanese,1838,1912,1897,1897,1897,Triptych of polychrome woodblock prints; ink and color on paper,L. (page) 13 7/8 in. (35.2 cm); W. (page) 9 1/4 in. (23.4 cm); thickness of album 1 9/16 in. (4 cm),"Gift of Mrs. W. Walton Butterworth, 1979",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55876,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3573,false,true,55877,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Yōshū (Hashimoto) Chikanobu,"Japanese, 1838–1912",,Yōshū (Hashimoto) Chikanobu,Japanese,1838,1912,1897,1897,1897,Triptych of polychrome woodblock prints; ink and color on paper,L. (page) 13 7/8 in. (35.2 cm); W. (page) 9 1/4 in. (23.4 cm); thickness of album 1 9/16 in. (4 cm),"Gift of Mrs. W. Walton Butterworth, 1979",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55877,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3574,false,true,55878,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Yōshū (Hashimoto) Chikanobu,"Japanese, 1838–1912",,Yōshū (Hashimoto) Chikanobu,Japanese,1838,1912,1897,1897,1897,Triptych of polychrome woodblock prints; ink and color on paper,L. (page) 13 7/8 in. (35.2 cm); W. (page) 9 1/4 in. (23.4 cm); thickness of album 1 9/16 in. (4 cm),"Gift of Mrs. W. Walton Butterworth, 1979",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55878,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3576,false,true,55880,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Yōshū (Hashimoto) Chikanobu,"Japanese, 1838–1912",,Yōshū (Hashimoto) Chikanobu,Japanese,1838,1912,1897,1897,1897,Triptych of polychrome woodblock prints; ink and color on paper,L. (page) 13 7/8 in. (35.2 cm); W. (page) 9 1/4 in. (23.4 cm); thickness of album 1 9/16 in. (4 cm),"Gift of Mrs. W. Walton Butterworth, 1979",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55880,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3577,false,true,55881,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Yōshū (Hashimoto) Chikanobu,"Japanese, 1838–1912",,Yōshū (Hashimoto) Chikanobu,Japanese,1838,1912,1897,1897,1897,Triptych of polychrome woodblock prints; ink and color on paper,L. (page) 13 7/8 in. (35.2 cm); W. (page) 9 1/4 in. (23.4 cm); thickness of album 1 9/16 in. (4 cm),"Gift of Mrs. W. Walton Butterworth, 1979",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55881,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3578,false,true,55882,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Yōshū (Hashimoto) Chikanobu,"Japanese, 1838–1912",,Yōshū (Hashimoto) Chikanobu,Japanese,1838,1912,1897,1897,1897,Triptych of polychrome woodblock prints; ink and color on paper,L. (page) 13 7/8 in. (35.2 cm); W. (page) 9 1/4 in. (23.4 cm); thickness of album 1 9/16 in. (4 cm),"Gift of Mrs. W. Walton Butterworth, 1979",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55882,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3579,false,true,55883,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Yōshū (Hashimoto) Chikanobu,"Japanese, 1838–1912",,Yōshū (Hashimoto) Chikanobu,Japanese,1838,1912,1897,1897,1897,Triptych of polychrome woodblock prints; ink and color on paper,L. (page) 13 7/8 in. (35.2 cm); W. (page) 9 1/4 in. (23.4 cm); thickness of album 1 9/16 in. (4 cm),"Gift of Mrs. W. Walton Butterworth, 1979",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55883,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3580,false,true,55884,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Yōshū (Hashimoto) Chikanobu,"Japanese, 1838–1912",,Yōshū (Hashimoto) Chikanobu,Japanese,1838,1912,1897,1897,1897,Triptych of polychrome woodblock prints; ink and color on paper,L. (page) 13 7/8 in. (35.2 cm); W. (page) 9 1/4 in. (23.4 cm); thickness of album 1 9/16 in. (4 cm),"Gift of Mrs. W. Walton Butterworth, 1979",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55884,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3581,false,true,55885,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Yōshū (Hashimoto) Chikanobu,"Japanese, 1838–1912",,Yōshū (Hashimoto) Chikanobu,Japanese,1838,1912,1897,1897,1897,Triptych of polychrome woodblock prints; ink and color on paper,L. (page) 13 7/8 in. (35.2 cm); W. (page) 9 1/4 in. (23.4 cm); thickness of album 1 9/16 in. (4 cm),"Gift of Mrs. W. Walton Butterworth, 1979",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55885,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3582,false,true,55886,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Yōshū (Hashimoto) Chikanobu,"Japanese, 1838–1912",,Yōshū (Hashimoto) Chikanobu,Japanese,1838,1912,1897,1897,1897,Triptych of polychrome woodblock prints; ink and color on paper,L. (page) 13 7/8 in. (35.2 cm); W. (page) 9 1/4 in. (23.4 cm); thickness of album 1 9/16 in. (4 cm),"Gift of Mrs. W. Walton Butterworth, 1979",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55886,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3583,false,true,55887,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Yōshū (Hashimoto) Chikanobu,"Japanese, 1838–1912",,Yōshū (Hashimoto) Chikanobu,Japanese,1838,1912,1897,1897,1897,Triptych of polychrome woodblock prints; ink and color on paper,L. (page) 13 7/8 in. (35.2 cm); W. (page) 9 1/4 in. (23.4 cm); thickness of album 1 9/16 in. (4 cm),"Gift of Mrs. W. Walton Butterworth, 1979",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55887,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3584,false,true,55888,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Yōshū (Hashimoto) Chikanobu,"Japanese, 1838–1912",,Yōshū (Hashimoto) Chikanobu,Japanese,1838,1912,1897,1897,1897,Triptych of polychrome woodblock prints; ink and color on paper,L. (page) 13 7/8 in. (35.2 cm); W. (page) 9 1/4 in. (23.4 cm); thickness of album 1 9/16 in. (4 cm),"Gift of Mrs. W. Walton Butterworth, 1979",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55888,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3585,false,true,55889,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Yōshū (Hashimoto) Chikanobu,"Japanese, 1838–1912",,Yōshū (Hashimoto) Chikanobu,Japanese,1838,1912,1897,1897,1897,Triptych of polychrome woodblock prints; ink and color on paper,L. (page) 13 7/8 in. (35.2 cm); W. (page) 9 1/4 in. (23.4 cm); thickness of album 1 9/16 in. (4 cm),"Gift of Mrs. W. Walton Butterworth, 1979",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55889,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3586,false,true,55890,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Yōshū (Hashimoto) Chikanobu,"Japanese, 1838–1912",,Yōshū (Hashimoto) Chikanobu,Japanese,1838,1912,1897,1897,1897,Triptych of polychrome woodblock prints; ink and color on paper,L. (page) 13 7/8 in. (35.2 cm); W. (page) 9 1/4 in. (23.4 cm); thickness of album 1 9/16 in. (4 cm),"Gift of Mrs. W. Walton Butterworth, 1979",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55890,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3587,false,true,55891,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Yōshū (Hashimoto) Chikanobu,"Japanese, 1838–1912",,Yōshū (Hashimoto) Chikanobu,Japanese,1838,1912,1897,1897,1897,Triptych of polychrome woodblock prints; ink and color on paper,L. (page) 13 7/8 in. (35.2 cm); W. (page) 9 1/4 in. (23.4 cm); thickness of album 1 9/16 in. (4 cm),"Gift of Mrs. W. Walton Butterworth, 1979",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55891,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3588,false,true,55892,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Yōshū (Hashimoto) Chikanobu,"Japanese, 1838–1912",,Yōshū (Hashimoto) Chikanobu,Japanese,1838,1912,1897,1897,1897,Triptych of polychrome woodblock prints; ink and color on paper,L. (page) 13 7/8 in. (35.2 cm); W. (page) 9 1/4 in. (23.4 cm); thickness of album 1 9/16 in. (4 cm),"Gift of Mrs. W. Walton Butterworth, 1979",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55892,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3589,false,true,55893,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Yōshū (Hashimoto) Chikanobu,"Japanese, 1838–1912",,Yōshū (Hashimoto) Chikanobu,Japanese,1838,1912,1897,1897,1897,Triptych of polychrome woodblock prints; ink and color on paper,L. (page) 13 7/8 in. (35.2 cm); W. (page) 9 1/4 in. (23.4 cm); thickness of album 1 9/16 in. (4 cm),"Gift of Mrs. W. Walton Butterworth, 1979",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55893,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3590,false,true,55894,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Yōshū (Hashimoto) Chikanobu,"Japanese, 1838–1912",,Yōshū (Hashimoto) Chikanobu,Japanese,1838,1912,1897,1897,1897,Triptych of polychrome woodblock prints; ink and color on paper,L. (page) 13 7/8 in. (35.2 cm); W. (page) 9 1/4 in. (23.4 cm); thickness of album 1 9/16 in. (4 cm),"Gift of Mrs. W. Walton Butterworth, 1979",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55894,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3591,false,true,55895,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Yōshū (Hashimoto) Chikanobu,"Japanese, 1838–1912",,Yōshū (Hashimoto) Chikanobu,Japanese,1838,1912,1897,1897,1897,Triptych of polychrome woodblock prints; ink and color on paper,L. (page) 13 7/8 in. (35.2 cm); W. (page) 9 1/4 in. (23.4 cm); thickness of album 1 9/16 in. (4 cm),"Gift of Mrs. W. Walton Butterworth, 1979",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55895,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3241a–c,false,true,55254,Asian Art,Print,"『扶桑高貴鑑』|A Mirror of Japan’s Nobility: The Emperor Meiji, His Wife, and Prince Haru (Fūsō kōki kagami)",Japan,Meiji period (1868–1912),,,,Artist,,Yōshū (Hashimoto) Chikanobu,"Japanese, 1838–1912",,Yōshū (Hashimoto) Chikanobu,Japanese,1838,1912,"August 8th, 1887",1887,1887,Triptych of polychrome woodblock prints; ink and color on paper,14 3/4 x 29 5/8 in. (37.5 x 75.2 cm),"Gift of Lincoln Kirstein, 1959",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55254,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3253a–c,false,true,55264,Asian Art,Print,『上野第三回内国勧業 博覧会御幸之図』|Visit of the Empress to the Third National Industrial Promotional Exhibition at Ueno Park (Ueno dai sankai naikoku kangyō hakuran kai gyokō no zu),Japan,Meiji period (1868–1912),,,,Artist,,Yōshū (Hashimoto) Chikanobu,"Japanese, 1838–1912",,Yōshū (Hashimoto) Chikanobu,Japanese,1838,1912,1889,1889,1889,Triptych of polychrome woodblock prints; ink and color on paper,Image (each): 14 1/2 × 9 1/2 in. (36.8 × 24.1 cm),"Gift of Lincoln Kirstein, 1959",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55264,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3688a–c,false,true,55920,Asian Art,Print,"二品親王女三宮|The Third Princess and Kashiwagi, from Chapter 34, “New Herbs I (Wakana I)” (Nihon shinnō onna sannomiya)",Japan,Meiji period (1868–1912),,,,Artist,,Yōshū (Hashimoto) Chikanobu,"Japanese, 1838–1912",,Yōshū (Hashimoto) Chikanobu,Japanese,1838,1912,1890,1890,1890,Triptych of polychrome woodblock prints; ink and color on paper,Each 14 1/4 x 9 1/4 in. (36.2 x 23.5 cm),"Gift of Lincoln Kirstein, 1985",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55920,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"JP3539.1, .2",false,true,55840,Asian Art,Print,千代田の大奥 茶の湯辺り花|Chiyoda Inner Palace: No.20 Flower Arranging in Turn (Chiyoda no Ōoku: Chanoyu mawaribana),Japan,Meiji period (1868–1912),,,,Artist,,Hashimoto Chikanobu,"Japanese, 1838–1912",,Hashimoto Chikanobu,Japanese,1838,1912,August 1895,1895,1895,Triptych of polychrome woodblock prints; ink and color on paper,L. (page) 13 7/8 in. (35.2 cm); W. (page) 9 1/4 in. (23.4 cm),"Gift of Mrs. W. Walton Butterworth, 1979",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55840,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"JP3575.1, .2",false,true,55879,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Yōshū (Hashimoto) Chikanobu,"Japanese, 1838–1912",,Yōshū (Hashimoto) Chikanobu,Japanese,1838,1912,1897,1897,1897,Triptych of polychrome woodblock prints; ink and color on paper,L. (page) 13 7/8 in. (35.2 cm); W. (page) 9 1/4 in. (23.4 cm); thickness of album 1 9/16 in. (4 cm),"Gift of Mrs. W. Walton Butterworth, 1979",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55879,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.352a–c,false,true,72830,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Yōshū (Hashimoto) Chikanobu,"Japanese, 1838–1912",,Yōshū (Hashimoto) Chikanobu,Japanese,1838,1912,1898,1898,1898,Triptych of polychrome woodblock prints; color on paper,Overall (a): 14 5/8 x 9 15/16 in. (37.1 x 25.2 cm) Overall (b): 14 7/8 x 9 7/8 in. (37.8 x 25.1 cm) Overall (c): 14 5/8 x 9 15/16 in. (37.1 x 25.2 cm),"Purchase, Friends of Asian Art Gifts, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/72830,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2007.49.331a, b",false,true,73583,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Yōshū (Hashimoto) Chikanobu,"Japanese, 1838–1912",,Yōshū (Hashimoto) Chikanobu,Japanese,1838,1912,late 19th century,1867,1899,Polychrome woodblock prints; ink and color on paper,Image: 13 1/8 x 9 in. (33.3 x 22.9 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73583,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3277,false,true,55359,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Tsukioka Yoshitoshi,"Japanese, 1839–1892",,Tsukioka Yoshitoshi,Japanese,1839,1892,September 1877,1877,1877,Triptych of polychrome woodblock prints; ink and color on paper,14 3/16 x 29 1/4 in. (36 x 74.3 cm),"Gift of Lincoln Kirstein, 1959",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55359,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3387,false,true,55561,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Tsukioka Yoshitoshi,"Japanese, 1839–1892",,Tsukioka Yoshitoshi,Japanese,1839,1892,September 1877,1877,1877,Polychrome woodblock print; ink and color on paper,13 1/2 x 28 1/2 in. (34.3 x 72.4 cm),"Gift of Lincoln Kirstein, 1960",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55561,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.308,false,true,73575,Asian Art,Print,"東錦浮世稿談-幡随院長兵衛|Banzuiin Chōbei, from the series Story of Brocades of the East in the Floating World (Azuma no hana ukiyo kōdan - Banzuiin Chōbei)",Japan,Meiji period (1868–1912),,,,Artist,,Tsukioka Yoshitoshi,"Japanese, 1839–1892",,Tsukioka Yoshitoshi,Japanese,1839,1892,"10th month, 1867",1867,1867,Polychrome woodblock print; ink and color on paper,Image: 14 1/2 x 9 1/2 in. (36.8 x 24.1 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73575,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.310,false,true,73623,Asian Art,Print,月百姿 - 煙中月|Moon in the Flame from the Series One Hundred Images of the Moon (Tsuki hyaku sugata-enchūgetsu),Japan,Meiji period (1868–1912),,,,Artist,,Tsukioka Yoshitoshi,"Japanese, 1839–1892",,Tsukioka Yoshitoshi,Japanese,1839,1892,"February, 1886",1886,1886,Polychrome woodblock print; ink and color on paper,Image: 14 x 9 3/8 in. (35.6 x 23.8 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73623,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.311,false,true,73624,Asian Art,Print,"郵便報知新聞 645号|Postal Hōchi Newspaper no. 645, Englishman raping a wine shopkeeper's daughter (Yūbin Hōchi shinbun, roppyaku yonjū gogō)",Japan,Meiji period (1868–1912),,,,Artist,,Tsukioka Yoshitoshi,"Japanese, 1839–1892",,Tsukioka Yoshitoshi,Japanese,1839,1892,"August, 1875",1875,1875,Polychrome woodblock print; ink and color on paper,Image: 14 1/8 x 9 3/8 in. (35.9 x 23.8 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73624,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.312,false,true,73625,Asian Art,Print,皇国一新見聞誌|Chronicle of the Imperial Restoration (Kōkoku isshin kenbunshi),Japan,Meiji period (1868–1912),,,,Artist,,Tsukioka Yoshitoshi,"Japanese, 1839–1892",,Tsukioka Yoshitoshi,Japanese,1839,1892,"June, 1876",1876,1876,Polychrome woodblock print; ink and color on paper,Image: 14 1/2 x 9 3/8 in. (36.8 x 23.8 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73625,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.313,false,true,73626,Asian Art,Print,"新柳二十四時 午前十二時|Twenty-Four Hours at Shinbashi/Yanagibashi: 12 Noon. (Shinyanagi nijūyo-ji, gozen jūni-ji)",Japan,Meiji period (1868–1912),,,,Artist,,Tsukioka Yoshitoshi,"Japanese, 1839–1892",,Tsukioka Yoshitoshi,Japanese,1839,1892,1880,1880,1880,Polychrome woodblock print; ink and color on paper,Image: 10 x 14 5/8 in. (25.4 x 37.1 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73626,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.315,false,true,73628,Asian Art,Print,"見立多以尽 - 洋行がしたい|Collection of Desires, Wish for Foreign Travel (Mitate Tai zukushi-yōkō ga shitai)",Japan,Meiji period (1868–1912),,,,Artist,,Tsukioka Yoshitoshi,"Japanese, 1839–1892",,Tsukioka Yoshitoshi,Japanese,1839,1892,"January, 1878",1878,1878,Polychrome woodblock print; ink and color on paper,Image: 14 1/2 x 9 7/8 in. (36.8 x 25.1 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73628,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.316,false,true,73622,Asian Art,Print,"新柳二十四時 午前十二時|Twenty-Four Hours at Shinbashi/Yanagibashi: 12 Noon. (Shinyanagi nijūyo-ji, gozen jūni-ji)",Japan,Meiji period (1868–1912),,,,Artist,,Tsukioka Yoshitoshi,"Japanese, 1839–1892",,Tsukioka Yoshitoshi,Japanese,1839,1892,1880,1800,1899,Polychrome woodblock print; ink and color on paper,Image: 17 x 11 in. (43.2 x 27.9 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73622,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3252a–c,false,true,55263,Asian Art,Print,官女ステーション着車図|Illustration of Ladies-in-waiting boarding at a station (Kanjo sutēshon chakusha zu),Japan,Meiji period (1868–1912),,,,Artist,,Tsukioka Yoshitoshi,"Japanese, 1839–1892",,Tsukioka Yoshitoshi,Japanese,1839,1892,1879,1879,1879,Polychrome woodblock print; ink and color on paper,13 5/8 x 27 3/4 in. (34.6 x 70.5 cm),"Gift of Lincoln Kirstein, 1959",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55263,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.350a–c,false,true,72828,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Tsukioka Yoshitoshi,"Japanese, 1839–1892",,Tsukioka Yoshitoshi,Japanese,1839,1892,1885,1885,1885,Triptych of polychrome woodblock prints; ink and color on paper,Overall (a): 14 3/4 x 10 in. (37.5 x 25.4 cm) Overall (b): 14 5/8 x 10 in. (37.1 x 25.4 cm) Overall (c): 14 15/16 x 10 1/16 in. (37.9 x 25.6 cm),"Purchase, Friends of Asian Art Gifts, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/72828,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.260a–c,false,true,73551,Asian Art,Print,"東京名勝高輪 蒸気車鉄道之全図|Illustration of Steam Locomotive Tracks at Takanawa, from the series Famous Places in Tokyo (Tōkyō meishō Takanawa-jōki kikansha no zen zu)",Japan,Meiji period (1868–1912),,,,Artist,,Tsukioka Yoshitoshi,"Japanese, 1839–1892",,Tsukioka Yoshitoshi,Japanese,1839,1892,1872,1872,1872,Triptych of polychrome woodblock prints; ink and color on paper,Image (a): 14 x 9 1/2 in. (35.6 x 24.1 cm) Image (b): 14 1/4 x 9 5/8 in. (36.2 x 24.4 cm) Image (c): 14 1/8 x 9 3/4 in. (35.9 x 24.8 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73551,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.307a–c,false,true,73574,Asian Art,Print,清盛入道布引滝遊覧悪源太義平霊討難波次郎|Kiyomori and the History of Nunobiki Waterfall: The spirit of Akugenta Yoshihira strikes Nanba Jirō. (Kiyomori nyūdō nunobiki no taki yūran akugenta yoshihira no rei nanba jirō o utsu),Japan,Meiji period (1868–1912),,,,Artist,,Tsukioka Yoshitoshi,"Japanese, 1839–1892",,Tsukioka Yoshitoshi,Japanese,1839,1892,1868,1868,1868,Triptych of polychrome woodblock prints; ink and color on paper,Image (a): 9 1/2 x 14 in. (24.1 x 35.6 cm) Image (b): 9 1/2 x 14 3/4 in. (24.1 x 37.5 cm) Image (c): 9 1/2 x 14 in. (24.1 x 35.6 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73574,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3259,false,true,55274,Asian Art,Print,"東京名所従 上野公園不忍池中嶋弁天之景|View of Benten Shrine on Nakanoshima Island in Shinobazu Pond, Ueno Park, from the series Famous Views of Tokyo (Tōkyō Tokyo meisho yori Ueno kōen Shinobazu no ike Nakanoshima Benten no kei)",Japan,Meiji period (1868–1912),,,,Artist,,Utagawa Hiroshige III,"Japanese, 1843–1894",,Utagawa Hiroshige III,Japanese,1843,1894,"May, 1881",1881,1881,Polychrome woodblock print; ink and color on paper,Image (each): 14 3/4 × 9 1/2 in. (37.5 × 24.1 cm) 14 3/4 × 29 1/8 in. (37.5 × 74 cm),"Gift of Lincoln Kirstein, 1959",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55274,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3267,false,true,55327,Asian Art,Print,"東京名所 上野公園内国勧業第二博覧会美術館図|Illustration of the Museum at the Second National Industrial Exhibition in Ueno, from the series Famous Places in Tokyo (Tokyo meisho-Ueno kōen naikoku kangyō daini hakurankai bijutsukan zu)",Japan,Meiji period (1868–1912),,,,Artist,,Utagawa Hiroshige III,"Japanese, 1843–1894",,Utagawa Hiroshige III,Japanese,1843,1894,1881,1881,1881,Triptych of polychrome woodblock prints; ink and color on paper,13 3/4 x 28 1/8 in. (34.9 x 71.4 cm),"Gift of Lincoln Kirstein, 1959",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55327,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3349,false,true,55515,Asian Art,Print,横浜商館天主堂ノ図|Illustration of Foreign Residences and the Catholic Church in Yokohama (Yokohama shōkan tenshudō no zu),Japan,Meiji period (1868–1912),,,,Artist,,Utagawa Hiroshige III,"Japanese, 1843–1894",,Utagawa Hiroshige III,Japanese,1843,1894,"10th month, 1870",1870,1870,Triptych of polychrome woodblock prints; ink and color on paper,28 3/8 x 14 1/2 in. (72.1 x 36.8 cm),"Gift of Lincoln Kirstein, 1959",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55515,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3385,false,true,55556,Asian Art,Woodblock print,"諸国名所図会内 西京東山一覧|A Glimpse of Higayashiyama the Western Capital, from the series Famous Places in the Nation (Shokoku meisho zukai no uchi-Saikyō Higashiyama ichiran)",Japan,Meiji period (1868–1912),,,,Artist,,Utagawa Hiroshige III,"Japanese, 1843–1894",,Utagawa Hiroshige III,Japanese,1843,1894,"May, 1880",1880,1880,Triptych of polychrome woodblock prints; ink and color on paper,14 1/2 x 29 1/8 in. (36.8 x 74 cm),"Gift of Lincoln Kirstein, 1960",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55556,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3388,false,true,55562,Asian Art,Woodblock print,不二詣諸品下山之図|View of the Descent from a Mountain by Many from Pilgrimage to Mt. Fuji (Fuji mōde shoshina gesan no zu),Japan,Meiji period (1868–1912),,,,Artist,,Utagawa Hiroshige III,"Japanese, 1843–1894",,Utagawa Hiroshige III,Japanese,1843,1894,"November, 1883",1883,1883,Triptych of polychrome woodblock prints; ink and color on paper,14 1/4 x 28 1/4 in. (36.2 x 71.8 cm),"Gift of Lincoln Kirstein, 1960",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55562,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.280,false,true,73563,Asian Art,Print,横浜海岸通り之風景|View of the Seafront in Yokohama (Yokohama Kagandori no fūkei),Japan,Meiji period (1868–1912),,,,Artist,,Utagawa Hiroshige III,"Japanese, 1843–1894",,Utagawa Hiroshige III,Japanese,1843,1894,"5th month, 1870",1870,1870,Triptych of polychrome woodblock prints; ink and color on paper,Image: 14 1/2 x 28 5/8 in. (36.8 x 72.7 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73563,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.150a–c,false,true,73436,Asian Art,Print,東都築地ホテル館庭前の図|Illustration of the Front Garden of the Tsukiji Hotel in the Eastern Capital (Tōto Tsukiji hoteru kan niwa mae no zu),Japan,Meiji period (1868–1912),,,,Artist,,Utagawa Hiroshige III,"Japanese, 1843–1894",,Utagawa Hiroshige III,Japanese,1843,1894,ca. 1868–72,1868,1872,Triptych of polychrome woodblock prints; ink and color on paper,Image (a): 14 3/8 x 10 in. (36.5 x 25.4 cm) Image (b): 14 1/2 x 10 in. (36.8 x 25.4 cm) Image (c): 14 1/2 x 10 in. (36.8 x 25.4 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73436,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.151a–c,false,true,73437,Asian Art,Print,東京築地ホテル館|The Tsukiji Hotel in Tokyo (Tokyo Tsukiji hoteru kan),Japan,Meiji period (1868–1912),,,,Artist,,Utagawa Hiroshige III,"Japanese, 1843–1894",,Utagawa Hiroshige III,Japanese,1843,1894,"5th month, 1870",1870,1870,Triptych of polychrome woodblock prints; ink and color on paper,Image (a): 14 1/2 x 9 5/8 in. (36.8 x 24.4 cm) Image (b): 14 1/2 x 9 5/8 in. (36.8 x 24.4 cm) Image (c): 14 1/2 x 9 5/8 in. (36.8 x 24.4 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73437,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3384,false,true,55550,Asian Art,Print,Kempo happu shiki no zu|View of the Issuance of the Constitution,Japan,Meiji period (1868–1912),,,,Artist,,Baiju Kunitoshi,"Japanese, 1847–1899",,Baiju Kunitoshi,Japanese,1847,1899,March 1889,1889,1889,Triptych of polychrome woodblock prints; ink and color on paper,Oban; 14 1/2 x 28 5/8 in. (36.8 x 72.7 cm),"Gift of Lincoln Kirstein, 1960",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55550,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP228,false,true,36700,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Kobayashi Kiyochika,"Japanese, 1847–1915",,Kobayashi Kiyochika,Japanese,1847,1915,ca. 1878,1868,1888,Polychrome woodblock print; ink and color on paper,9 5/32 x 13 5/8 in. (23.3 x 34.6 cm),"Rogers Fund, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36700,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3209,false,true,55195,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Kobayashi Kiyochika,"Japanese, 1847–1915",,Kobayashi Kiyochika,Japanese,1847,1915,September 1904,1904,1904,Polychrome woodblock print; ink and color on paper,Oban 14 1/4 x 9 7/8 in. (36.2 x 25.1 cm),"Gift of Lincoln Kirstein, 1959",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55195,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3210,false,true,55196,Asian Art,Print,Kitai taihora|The Spiraling (Effect) of the Fundamental Law on the Fearful Party (Russians),Japan,Meiji period (1868–1912),,,,Artist,,Kobayashi Kiyochika,"Japanese, 1847–1915",,Kobayashi Kiyochika,Japanese,1847,1915,September 1904,1904,1904,Polychrome woodblock print; ink and color on paper,14 1/2 x 9 7/8 in. (36.8 x 25.1 cm),"Gift of Lincoln Kirstein, 1959",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55196,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3211,false,true,55197,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Kobayashi Kiyochika,"Japanese, 1847–1915",,Kobayashi Kiyochika,Japanese,1847,1915,October 1904,1904,1904,Polychrome woodblock print; ink and color on paper,14 1/2 x 9 7/8 in. (36.8 x 25.1 cm),"Gift of Lincoln Kirstein, 1959",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55197,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3212,false,true,55198,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Kobayashi Kiyochika,"Japanese, 1847–1915",,Kobayashi Kiyochika,Japanese,1847,1915,"1895 (Meiji 28, 7th month)",1895,1895,Polychrome woodblock print; ink and color on paper,14 x 9 5/16 in. (35.6 x 23.7 cm),"Gift of Lincoln Kirstein, 1959",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55198,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3258,false,true,55272,Asian Art,Print,凱旋新橋ステーション御着之図|Illustration of the Arrival of the Emperor at Shinbashi Station Following a Victory (Gaisen Shinbashi stēshon gochaku no zu),Japan,Meiji period (1868–1912),,,,Artist,,Kobayashi Kiyochika,"Japanese, 1847–1915",,Kobayashi Kiyochika,Japanese,1847,1915,1895,1895,1895,Triptych of polychrome woodblock prints; ink and color on paper,Each panel: 14 1/4 x 9 1/2 in. (36.2 x 24.1 cm),"Gift of Lincoln Kirstein, 1959",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55272,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3266,false,true,55325,Asian Art,Print,参謀本部行啓之図|Illustration of the Empress Visiting the General Staff Headquarters [to present a tray of bandages] (Sanbō honbu gyōkei no zu),Japan,Meiji period (1868–1912),,,,Artist,,Kobayashi Kiyochika,"Japanese, 1847–1915",,Kobayashi Kiyochika,Japanese,1847,1915,1895,1895,1895,Triptych of polychrome woodblock prints; ink and color on paper,Oban 14 x 27 3/4 in. (35.6 x 70.5 cm),"Gift of Lincoln Kirstein, 1959",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55325,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3274,false,true,55348,Asian Art,Print,野戦病院行幸之図|Illustration of the Empress Visiting a Field Hospital [in Hiroshima] (Yasen byōin gyōkō no zu),Japan,Meiji period (1868–1912),,,,Artist,,Kobayashi Kiyochika,"Japanese, 1847–1915",,Kobayashi Kiyochika,Japanese,1847,1915,1895,1895,1895,Triptych of polychrome woodblock prints; ink and color on paper,14 3/4 x 30 in. (37.5 x 76.2cm),"Gift of Lincoln Kirstein, 1959",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55348,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3310,false,true,55419,Asian Art,Print,朝鮮大戦争之図|Illustration of the Great Korean War (Chōsen dai sensō no zu),Japan,Meiji period (1868–1912),,,,Artist,,Kobayashi Kiyochika,"Japanese, 1847–1915",,Kobayashi Kiyochika,Japanese,1847,1915,"August, 1882",1882,1882,Right-hand sheet of a triptych of polychrome woodblock prints; ink and color on paper,14 1/8 x 9 in. (35.9 x 22.9 cm),"Gift of Lincoln Kirstein, 1959",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55419,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3336,false,true,55484,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Kobayashi Kiyochika,"Japanese, 1847–1915",,Kobayashi Kiyochika,Japanese,1847,1915,October 1878,1878,1878,Triptych of polychrome woodblock prints; ink and color on paper,Oban; 14 1/8 x 28 1/8 in. (35.9 x 71.4 cm),"Gift of Lincoln Kirstein, 1959",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55484,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3420,false,true,55617,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Kobayashi Kiyochika,"Japanese, 1847–1915",,Kobayashi Kiyochika,Japanese,1847,1915,October 1885,1885,1885,Polychrome woodblock print; ink and color on paper,Oban tat-e; 14 x 9 1/4 in. (35.6 x 23.5 cm),"Gift of Lincoln Kirstein, 1962",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55617,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1993.310,false,true,55988,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Kobayashi Kiyochika,"Japanese, 1847–1915",,Kobayashi Kiyochika,Japanese,1847,1915,dated 1874,1874,1874,Polychrome woodblock print; ink and color on paper,14 x 8 5/16 in. (35.6 x 21.1 cm),"Bequest of Gustave von Groschwitz, 1993",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55988,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.328,false,true,73581,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Kobayashi Kiyochika,"Japanese, 1847–1915",,Kobayashi Kiyochika,Japanese,1847,1915,late 19th century,1867,1899,Triptych of polychrome woodblock prints; ink and color on paper,Image: 14 3/4 x 28 1/4 in. (37.5 x 71.8 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73581,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.322a–c,false,true,73632,Asian Art,Print,『万国衣装鑑』|Mirror of National Costumes of All Nations (Bankoku ishō kagami),Japan,Meiji period (1868–1912),,,,Artist,,Kobayashi Kiyochika,"Japanese, 1847–1915",,Kobayashi Kiyochika,Japanese,1847,1915,1882,1882,1882,Triptych of polychrome woodblock prints; ink and color on paper,Image (a): 14 x 9 1/4 in. (35.6 x 23.5 cm) Image (b): 13 7/8 x 9 in. (35.2 x 22.9 cm) Image (c): 14 x 9 1/2 in. (35.6 x 24.1 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73632,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3287,false,true,55375,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Watanabe Seitei,"Japanese, 1851–1918",(?),Watanabe Seitei,Japanese,1851,1918,ca. 1906,1896,1916,Polychrome woodblock print; ink and color on paper,8 7/8 x 11 9/16 in. (22.5 x 29.4 cm),"Gift of Lincoln Kirstein, 1959",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55375,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3283,false,true,55370,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Mishima Shōsō,"Japanese, 1856–1928",,Mishima Shōsō,Japanese,1856,1928,ca. 1906,1896,1916,Polychrome woodblock print; ink and color on paper,8 3/4 x 11 1/8 in. (22.2 x 28.3 cm),"Gift of Lincoln Kirstein, 1959",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55370,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3289,false,true,55382,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Suzuki Kason,"Japanese, 1860–1919",,Suzuki Kason,Japanese,1860,1919,ca. 1906,1896,1916,Polychrome woodblock print; ink and color on paper,8 15/16 x 10 3/4 in. (22.7 x 27.3 cm),"Gift of Lincoln Kirstein, 1959",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55382,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3296,false,true,55393,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Suzuki Kason,"Japanese, 1860–1919",,Suzuki Kason,Japanese,1860,1919,1904,1904,1904,Frontispiece; polychrome woodblock print; ink and color on paper,Image: 8 3/4 x 12 1/4 in. (22.2 x 31.1 cm),"Gift of Lincoln Kirstein, 1959",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55393,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3190,false,true,55175,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Takeuchi Keishū,"Japanese, 1861–1943",,Takeuchi Keishū,Japanese,1861,1943,ca. 1906,1896,1916,Polychrome woodblock print; ink and color on paper,8 3/4 x 12 1/16 in. (22.2 x 30.6 cm),"GIft of Lincoln Kirstein, 1959",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55175,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3193,false,true,55177,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Takeuchi Keishū,"Japanese, 1861–1943",,Takeuchi Keishū,Japanese,1861,1943,ca. 1905,1895,1915,Polychrome woodblock print; ink and color on paper,12 x 8 1/4 in. (30.5 x 21 cm),"Gift of Lincoln Kirstein, 1959",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55177,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3279,false,true,45279,Asian Art,Print,"(Frontispiece to) ""Fukuro Monogatari"", (by) Izumi Kyoka|Frontispiece to ""An Owl's Story"", by Izumi Kyoka",Japan,Meiji period (1868–1912),,,,Artist,,Takeuchi Keishū,"Japanese, 1861–1943",,Takeuchi Keishū,Japanese,1861,1943,ca. 1900,1890,1910,Polychrome woodblock print; ink and color on paper,Overall: 8 3/4 x 11 1/4 in. (22.2 x 28.6 cm),"Gift of Lincoln Kirstein, 1959",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45279,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3286,false,true,55373,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Takeuchi Keishū,"Japanese, 1861–1943",(?),Takeuchi Keishū,Japanese,1861,1943,ca. 1906,1896,1916,Polychrome woodblock print; ink and color on paper,8 1/2 x 11 1/4 in. (21.6 x 28.6 cm),"Gift of Lincoln Kirstein, 1959",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55373,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3319,false,true,55462,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Takeuchi Keishū,"Japanese, 1861–1943",,Takeuchi Keishū,Japanese,1861,1943,ca. 1906,1896,1916,Polychrome woodblock print; ink and color on paper,11 3/4 x 8 1/2 in. (29.8 x 21.6 cm),"Gift of Lincoln Kirstein, 1959",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55462,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3242,false,true,55255,Asian Art,Woodblock print,青上練兵場観兵式之図|Illustration of Emperor's Military Review of a Parade Ground at Aoyama (Aoyama renpeijō kanpeishiki no zu),Japan,Meiji period (1868–1912),,,,Artist,,Inoue Yasuji,"Japanese, 1864–1889",,INOUE YASUJI,Japanese,1864,1889,"June, 1888",1888,1888,Triptych of polychrome woodblock prints; ink and color on paper,Oban 14 1/2 x 28 3/4 in. (36.8 x 73 cm),"Gift of Lincoln Kirstein, 1959",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55255,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3244,false,true,37387,Asian Art,Print,東京名所之内吾妻橋新築之図|Illustration of the Opening of Azuma Bridge in Tokyo (Tokyo meisho no uchi azuma bashi shinchiku no zu),Japan,Meiji period (1868–1912),,,,Artist,,Inoue Yasuji,"Japanese, 1864–1889",,INOUE YASUJI,Japanese,1864,1889,1887,1887,1887,Triptych of polychrome woodblock prints; ink and color on paper,14 1/8 x 28 1/2 in. (35.9 x 72.4 cm),"Gift of Lincoln Kirstein, 1959",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37387,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3337,false,true,55490,Asian Art,Print,共楽泰平貴顕図|Illustration of the Emperor Enjoying a Moment of Peace with his Family (Kyōraku taihei kiken zu),Japan,Meiji period (1868–1912),,,,Artist,,Inoue Yasuji,"Japanese, 1864–1889",,INOUE YASUJI,Japanese,1864,1889,1887,1887,1887,Polychrome woodblock print; ink and color on paper,Image: 14 3/8 x 29 3/8 in. (36.5 x 74.6 cm),"Gift of Lincoln Kirstein, 1959",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55490,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3196,false,true,55180,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Tomioka Eisen,"Japanese, 1864–1905",,Tomioka Eisen,Japanese,1864,1905,ca. 1906,1896,1916,Polychrome woodblock print; ink and color on paper,8 5/8 x 12 3/8 in. (21.9 x 31.4 cm),"Gift of Lincoln Kirstein, 1959",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55180,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3282,false,true,55368,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Tomioka Eisen,"Japanese, 1864–1905",,Tomioka Eisen,Japanese,1864,1905,early 20th century,1900,1933,Polychrome woodblock print; ink and color on paper,8 1/2 x 10 3/8 in. (21.6 x 26.4 cm),"Gift of Lincoln Kirstein, 1959",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55368,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3294,false,true,55391,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Tomioka Eisen,"Japanese, 1864–1905",,Tomioka Eisen,Japanese,1864,1905,ca. 1906,1896,1916,Polychrome woodblock print; ink and color on paper,8 5/8 x 12 1/4 in. (21.9 x 31.1 cm),"Gift of Lincoln Kirstein, 1959",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55391,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3202,false,true,55186,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Kobori Tomoto,"Japanese, 1864–1931",,Kobori Tomoto,Japanese,1864,1931,ca. 1906,1896,1916,Polychrome woodblock print; ink and color on paper,11 5/8 x 8 1/2 in. (29.5 x 21.6 cm),"Gift of Lincoln Kirstein, 1959",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55186,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3206,false,true,55190,Asian Art,Print,"Ito Chujo tekidan o mite shi warau suru zu|Vice Admiral Ito Mocks, Points and Looks at the Enemy Bullets",Japan,Meiji period (1868–1912),,,,Artist,,Mizuno Toshikata,"Japanese, 1866–1908",,Mizuno Toshikata,Japanese,1866,1908,ca. 1894,1884,1904,Polychrome woodblock print; ink and color on paper,6 11/16 x 9 1/8 in. (17 x 23.2 cm),"Gift of Lincoln Kirstein, 1959",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55190,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3207,false,true,55191,Asian Art,Print,Kabayama Chujo furu yumo shin no zu|Vice Admiral Kabayama Advancing Bravely and Heartily,Japan,Meiji period (1868–1912),,,,Artist,,Mizuno Toshikata,"Japanese, 1866–1908",,Mizuno Toshikata,Japanese,1866,1908,December 1894,1894,1894,Polychrome woodblock print; ink and color on paper,7 x 9 1/8 in. (17.8 x 23.2 cm),"Gift of Lincoln Kirstein, 1959",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55191,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3260,false,true,55276,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Mizuno Toshikata,"Japanese, 1866–1908",,Mizuno Toshikata,Japanese,1866,1908,1894,1894,1894,Triptych of polychrome woodblock prints; ink and color on paper,Oban 13 7/8 x 27 3/4 in. (35.2 x 70.5 cm),"Gift of Lincoln Kirstein, 1959",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55276,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3261,false,true,55316,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Mizuno Toshikata,"Japanese, 1866–1908",,Mizuno Toshikata,Japanese,1866,1908,1894 (Meiji 27),1894,1894,Triptych of polychrome woodblock prints; ink and color on paper,Oban 14 x 27 3/4 in. (35.6 x 70.5 cm),"Gift of Lincoln Kirstein, 1959",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55316,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3281,false,true,55366,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Mizuno Toshikata,"Japanese, 1866–1908",,Mizuno Toshikata,Japanese,1866,1908,ca. 1906,1896,1916,Polychrome woodblock print; ink and color on paper,8 11/16 x 11 13/16 in. (22.1 x 30 cm),"Gift of Lincoln Kirstein, 1959",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55366,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3288,false,true,55381,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Mizuno Toshikata,"Japanese, 1866–1908",,Mizuno Toshikata,Japanese,1866,1908,ca. 1905,1895,1915,Polychrome woodblock print; ink and color on paper,8 3/4 x 11 3/4 in. (22.2 x 29.8 cm),"Gift of Lincoln Kirstein, 1959",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55381,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3292,false,true,55388,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Mizuno Toshikata,"Japanese, 1866–1908",,Mizuno Toshikata,Japanese,1866,1908,ca. 1906,1896,1916,Frontispiece; polychrome woodblock print; ink and color on paper,Image: 8 1/2 x 11 1/4 in. (21.6 x 28.6 cm),"Gift of Lincoln Kirstein, 1959",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55388,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1984.210.1–.72,false,true,57107,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Mizuno Toshikata,"Japanese, 1866–1908",,Mizuno Toshikata,Japanese,1866,1908,1891–93,1891,1893,Album of 72 polychrome woodblock prints; ink and color on paper,Overall (each): 14 x 9 1/2 in. (35.6 x 24.1 cm),"Gift of Mr. and Mrs. Malcolm P. Aldrich, 1984",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/57107,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3297,false,true,45252,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Terazaki,"Japanese, 1866–1919",,Terazaki,Japanese,1866,1919,1906,1868,1912,Polychrome woodblock print; ink and color on paper,12 1/2 x 17 3/4 in. (31.8 x 45.1 cm),"Gift of Lincoln Kirstein, 1959",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45252,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -MJP2,false,true,57113,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Tsukioka Kōgyo,"Japanese, 1869–1927",,Tsukioka Kōgyo,Japanese,1869,1927,ca. 1910,1900,1920,Polychrome woodblock print; ink and color on paper,14 1/2 x 20 in. (36.8 x 50.8 cm),"Gift of Teiji Ito, 1919",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/57113,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -MJP4,false,true,45495,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Tsukioka Kōgyo,"Japanese, 1869–1927",,Tsukioka Kōgyo,Japanese,1869,1927,ca. 1910,1900,1920,Polychrome woodblock print; ink and color on paper,14 1/2 x 19 7/8 in. (36.8 x 50.5 cm),"Gift of Teiji Ito, 1919",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45495,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -MJP5,false,true,57115,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Tsukioka Kōgyo,"Japanese, 1869–1927",,Tsukioka Kōgyo,Japanese,1869,1927,ca. 1910,1900,1920,Polychrome woodblock print; ink and color on paper,14 1/2 x 20 in. (36.8 x 50.8 cm),"Gift of Teiji Ito, 1919",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/57115,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -MJP6,false,true,57116,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Tsukioka Kōgyo,"Japanese, 1869–1927",,Tsukioka Kōgyo,Japanese,1869,1927,ca. 1910,1900,1920,Polychrome woodblock print; ink and color on paper,14 9/16 x 19 15/16 in. (37 x 50.6 cm),"Gift of Teiji Ito, 1919",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/57116,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -MJP7,false,true,53816,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Tsukioka Kōgyo,"Japanese, 1869–1927",,Tsukioka Kōgyo,Japanese,1869,1927,ca. 1910,1900,1920,Polychrome woodblock print; ink and color on paper,14 9/16 x 20 in. (37 x 50.8 cm),"Gift of Teiji Ito, 1919",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/53816,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -MJP8,false,true,45277,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Tsukioka Kōgyo,"Japanese, 1869–1927",,Tsukioka Kōgyo,Japanese,1869,1927,ca. 1910,1900,1920,Polychrome woodblock print; ink and color on paper,14 9/16 x 19 15/16 in. (37 x 50.6 cm),"Gift of Teiji Ito, 1919",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45277,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -MJP9,false,true,57117,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Tsukioka Kōgyo,"Japanese, 1869–1927",,Tsukioka Kōgyo,Japanese,1869,1927,ca. 1910,1900,1920,Polychrome woodblock print; ink and color on paper,14 5/8 x 20 in. (37.1 x 50.8 cm),"Gift of Teiji Ito, 1919",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/57117,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -MJP10,false,true,57118,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Tsukioka Kōgyo,"Japanese, 1869–1927",,Tsukioka Kōgyo,Japanese,1869,1927,ca. 1910,1900,1920,Polychrome woodblock print; ink and color on paper,14 9/16 x 20 in. (37 x 50.8 cm),"Gift of Teiji Ito, 1919",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/57118,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -MJP17,false,true,45496,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Tsukioka Kōgyo,"Japanese, 1869–1927",,Tsukioka Kōgyo,Japanese,1869,1927,ca. 1910,1900,1920,Polychrome woodblock print; ink and color on paper,14 1/2 x 20 in. (36.8 x 50.8 cm),"Gift of Frederick E Church, 1928",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45496,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -MJP18,false,true,57125,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Tsukioka Kōgyo,"Japanese, 1869–1927",,Tsukioka Kōgyo,Japanese,1869,1927,ca. 1910,1900,1920,Polychrome woodblock print; ink and color on paper,14 1/2 x 20 in. (36.8 x 50.8 cm),"Gift of Frederick E Church, 1928",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/57125,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -MJP20,false,true,57127,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Tsukioka Kōgyo,"Japanese, 1869–1927",,Tsukioka Kōgyo,Japanese,1869,1927,ca. 1910,1900,1920,Polychrome woodblock print; ink and color on paper,14 1/2 x 20 in. (36.8 x 50.8 cm),"Gift of Frederick E Church, 1928",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/57127,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -MJP21,false,true,57128,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Tsukioka Kōgyo,"Japanese, 1869–1927",,Tsukioka Kōgyo,Japanese,1869,1927,ca. 1910,1900,1920,Polychrome woodblock print; ink and color on paper,14 1/2 x 19 15/16 in. (36.8 x 50.6 cm),"Gift of Frederick E Church, 1928",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/57128,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -MJP22,false,true,57129,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Tsukioka Kōgyo,"Japanese, 1869–1927",,Tsukioka Kōgyo,Japanese,1869,1927,ca. 1910,1900,1920,Polychrome woodblock print; ink and color on paper,14 5/8 x 19 13/16 in. (37.1 x 50.3 cm),"Gift of Frederick E Church, 1928",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/57129,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -MJP23,false,true,57130,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Tsukioka Kōgyo,"Japanese, 1869–1927",,Tsukioka Kōgyo,Japanese,1869,1927,ca. 1910,1900,1920,Polychrome woodblock print; ink and color on paper,14 1/2 x 20 1/16 in. (36.8 x 51 cm),"Gift of Frederick E Church, 1928",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/57130,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -MJP24,false,true,57131,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Tsukioka Kōgyo,"Japanese, 1869–1927",,Tsukioka Kōgyo,Japanese,1869,1927,ca. 1910,1900,1920,Polychrome woodblock print; ink and color on paper,14 5/8 x 19 13/16 in. (37.1 x 50.3 cm),"Gift of Frederick E Church, 1928",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/57131,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -MJP25,false,true,57132,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Tsukioka Kōgyo,"Japanese, 1869–1927",,Tsukioka Kōgyo,Japanese,1869,1927,ca. 1910,1900,1920,Polychrome woodblock print; ink and color on paper,14 13/16 x 19 in. (37.6 x 48.3 cm),"Gift of Frederick E Church, 1928",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/57132,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -MJP26,false,true,57133,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Tsukioka Kōgyo,"Japanese, 1869–1927",,Tsukioka Kōgyo,Japanese,1869,1927,ca. 1910,1900,1920,Polychrome woodblock print; ink and color on paper,14 9/16 x 20 1/16 in. (37 x 51 cm),"Gift of Frederick E Church, 1928",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/57133,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -MJP27,false,true,57134,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Tsukioka Kōgyo,"Japanese, 1869–1927",,Tsukioka Kōgyo,Japanese,1869,1927,ca. 1910,1900,1920,Polychrome woodblock print; ink and color on paper,14 13/16 x 19 in. (37.6 x 48.3 cm),"Gift of Frederick E Church, 1928",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/57134,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3187,false,true,55172,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Kajita Hanko,"Japanese, 1870–1917",,Kajita Hanko,Japanese,1870,1917,ca. 1900,1890,1910,Polychrome woodblock print; ink and color on paper,8 15/16 x 11 5/6 in. (22.7 x 30.1 cm),"GIft of Lincoln Kirstein, 1959",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55172,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3204,false,true,55188,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Kajita Hanko,"Japanese, 1870–1917",,Kajita Hanko,Japanese,1870,1917,ca. 1906,1896,1916,Polychrome woodblock print; ink and color on paper,8 5/8 x 8 7/8 in. (21.9 x 22.5 cm),"Gift of Lincoln Kirstein, 1959",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55188,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3290,false,true,55386,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Kajita Hanko,"Japanese, 1870–1917",,Kajita Hanko,Japanese,1870,1917,November 1901,1901,1901,Polychrome woodblock print; ink and color on paper,8 3/4 x 11 7/8 in. (22.2 x 30.2 cm),"Gift of Lincoln Kirstein, 1959",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55386,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3295,false,true,55392,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Kajita Hanko,"Japanese, 1870–1917",,Kajita Hanko,Japanese,1870,1917,January 1906,1906,1906,Polychrome woodblock print; ink and color on paper,8 3/4 x 11 3/4 in. (22.2 x 29.8 cm),"Gift of Lincoln Kirstein, 1959",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55392,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3318,false,true,55460,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Kajita Hanko,"Japanese, 1870–1917",,Kajita Hanko,Japanese,1870,1917,1902,1902,1902,Frontispiece; polychrome woodblock print; ink and color on paper,Image: 8 5/8 x 12 1/16 in. (21.9 x 30.6 cm),"Gift of Lincoln Kirstein, 1959",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55460,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3278,false,true,55361,Asian Art,Print,御世の栄東の粧|Imperial Prosperity: Ceremony in the Eastern Capital (Miyo no sakae azuma no kewai),Japan,Meiji period (1868–1912),,,,Artist,,Yōsai Nobukazu 楊斎延一,"Japanese, 1872–1944",,Yōsai Nobukazu,Japanese,1872,1944,ca. 1900,1890,1910,Triptych of polychrome woodblock prints; ink and color on paper,Image: 14 5/8 x 29 in. (37.1 x 73.7 cm),"Gift of Lincoln Kirstein, 1959",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55361,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3271a–c,false,true,55343,Asian Art,Print,東京名所帝国議事堂|Famous Places in Tokyo: The Imperial Diet Building (Tōkyō Meisho: Teikoku Kokkai Gijidō),Japan,Meiji period (1868–1912),,,,Artist,,Yōsai Nobukazu 楊斎延一,"Japanese, 1872–1944",,Yōsai Nobukazu,Japanese,1872,1944,1899,1899,1899,Triptych of polychrome woodblock prints; ink and color on paper,14.3 x 28.7 in. (36.3 x 72.9 cm),"Gift of Lincoln Kirstein, 1959",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55343,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.134a–f,false,true,73420,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Utagawa (Gountei) Sadahide,"Japanese, 1807–1878/79",,Utagawa (Goutei) Sadahide,Japanese,1807,1879,ca. 1869,1859,1879,Hexaptych of polychrome woodblock prints; ink and color on paper,Image: 14 1/8 x 58 1/2 in. (35.9 x 148.6 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73420,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.135a–f,false,true,73421,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Utagawa (Gountei) Sadahide,"Japanese, 1807–1878/79",,Utagawa (Goutei) Sadahide,Japanese,1807,1879,"7th–10th month, 1870",1870,1870,Hexaptych of polychrome woodblock prints; ink and color on paper,Image (a): 9 7/8 x 14 1/2 in. (25.1 x 36.8 cm) Image (b): 9 7/8 x 14 1/2 in. (25.1 x 36.8 cm) Image (c): 9 7/8 x 14 1/2 in. (25.1 x 36.8 cm) Image (d): 9 5/8 x 14 1/8 in. (24.4 x 35.9 cm) Image (e): 9 5/8 x 14 1/8 in. (24.4 x 35.9 cm) Image (f): 9 1/2 x 14 1/8 in. (24.1 x 35.9 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73421,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3421a–c,false,true,55619,Asian Art,Print,Doitsukokukan naibu kikai|The Interior Works of an Armed Japanese Battleship,Japan,Meiji period (1868–1912),,,,Artist,,Unsen,"Japanese, active ca. 1875",,Unsen,Japanese,1875,1875,1874 (Meiji 7),1874,1874,Triptych of polychrome woodblock prints; ink and color on paper,Oban; 14 1/8 x 28 3/8 in. (35.9 x 72.1 cm),"Gift of Lincoln Kirstein, 1962",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55619,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3233,false,true,55232,Asian Art,Print,新皇居於テ正殿憲法発布式之図|Illustration of the Issuing of the State Constitution in the State Chamber of the New Imperial Palace (Shin kōkyo ni oite seiden kenpō happushiki no zu),Japan,Meiji period (1868–1912),,,,Artist,,Adachi (Shōsai) Ginkō,"Japanese, active 1874–97",,Adachi Ginkō,Japanese,1874,1897,"March 14, 1889",1889,1889,One sheet of a triptych of polychrome woodblock prints; ink and color on paper,Image: 14 5/8 in. × 10 in. (37.1 × 25.4 cm),"Gift of Lincoln Kirstein, 1959",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55232,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3234,false,true,55246,Asian Art,Print,Shinkokyo Oite Seiden Kempo Happu no zu|View of the Issuance of the State Constitution in the State Chamber of the New Imperial Palace,Japan,Meiji period (1868–1912),,,,Artist,,Adachi (Shōsai) Ginkō,"Japanese, active 1874–97",,Adachi Ginkō,Japanese,1874,1897,"March 2, 1889 (Meiji 22)",1889,1889,One sheet of a triptych of polychrome woodblock prints; ink and color on paper,Image: 14 3/4 × 9 1/2 in. (37.5 × 24.1 cm),"Gift of Lincoln Kirstein, 1959",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55246,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3235,false,true,55247,Asian Art,Print,Shinkokyo Oite Seiden Kempo Happu no zu|View of the Issuance of the State Constitution in the State Chamber of the New Imperial Palace,Japan,Meiji period (1868–1912),,,,Artist,,Adachi (Shōsai) Ginkō,"Japanese, active 1874–97",,Adachi Ginkō,Japanese,1874,1897,"March 2, 1889 (Meiji 22)",1889,1889,One sheet of a triptych of polychrome woodblock prints; ink and color on paper,Image: 14 3/4 × 9 1/2 in. (37.5 × 24.1 cm),"Gift of Lincoln Kirstein, 1959",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55247,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3272,false,true,55345,Asian Art,Print,『貴女裁縫之図』|Ladies Sewing (Kijo saihō no zu),Japan,Meiji period (1868–1912),,,,Artist,,Adachi (Shōsai) Ginkō,"Japanese, active 1874–97",,Adachi Ginkō,Japanese,1874,1897,"September 3rd, 1887",1887,1887,Triptych of polychrome woodblock prints; ink and color on paper,Image: 14 in. × 28 1/2 in. (35.6 × 72.4 cm),"Gift of Lincoln Kirstein, 1959",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55345,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.323,false,true,73585,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Adachi (Shōsai) Ginkō,"Japanese, active 1874–97",,Adachi Ginkō,Japanese,1874,1897,late 19th century,1871,1899,Polychrome woodblock print; ink and color on paper,Image: 13 7/8 x 9 in. (35.2 x 22.9 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73585,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.324,false,true,73390,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Adachi (Shōsai) Ginkō,"Japanese, active 1874–97",,Adachi Ginkō,Japanese,1874,1897,late 19th century,1867,1899,Polychrome woodblock print; ink and color on paper,14 1/4 x 9 1/2 in. (36.2 x 24.1 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73390,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3346,false,true,55511,Asian Art,Print,『東京築地舶来ぜんま い大仕かけきぬ糸をとる図』|Imported Silk Reeling Machine at Tsukiji in Tokyo,Japan,Meiji period (1868–1912),,,,Artist,,Utagawa Yoshitora,"Japanese, active ca. 1850–80",,Utagawa Yoshitora,Japanese,1845,1880,"4th month, 1872",1872,1872,Triptych of polychrome woodblock prints; ink and color on paper,Image: 14 1/2 x 28 3/4 in. (36.8 x 73 cm),"Gift of Lincoln Kirstein, 1959",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55511,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.223a–c,false,true,73514,Asian Art,Print,風船昇遥図|Illustration of a Balloon Ascending (Fūsen shōyō no zu),Japan,Meiji period (1868–1912),,,,Artist,,Utagawa Yoshitora,"Japanese, active ca. 1850–80",,Utagawa Yoshitora,Japanese,1845,1880,"November, 1872",1872,1872,Triptych of polychrome woodblock prints; ink and color on paper,Image (a): 14 1/2 x 9 7/8 in. (36.8 x 25.1 cm) Image (b): 14 5/8 x 9 7/8 in. (37.1 x 25.1 cm) Image (c): 14 3/8 x 9 3/4 in. (36.5 x 24.8 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73514,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.330a–c,false,true,73582,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Utagawa Masanobu,"Japanese, active ca. 1882–87",,Utagawa Masanobu,Japanese,1882,1887,"September 4, 1886",1886,1886,Three single polychrome woodblock prints; ink and color on paper,Image (a): 14 1/2 x 9 3/4 in. (36.8 x 24.8 cm) Image (b): 14 1/2 x 10 in. (36.8 x 25.4 cm) Image (c): 14 1/2 x 10 in. (36.8 x 25.4 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73582,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3224,false,true,55214,Asian Art,Woodblock print,Saigo Ryusei Kubi jitsu ken|Presentation of the Head of Saigo to the Prince Arisogawa,Japan,Meiji period (1868–1912),,,,Artist,,Yamazaki Toshinobu,"Japanese, active ca. 1857–1886",,Yamazaki Toshinobu,Japanese,1857,1886,"Oct. 16, 1877 (Meiji 10)",1877,1877,One sheet of a triptych of polychrome woodblock prints; ink and color on paper,14 in. × 9 1/2 in. (35.6 × 24.1 cm),"Gift of Lincoln Kirstein, 1959",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55214,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3225,false,true,55216,Asian Art,Woodblock print,Saigo Ryusei Kubi jitsu ken|Presentation of the Head of Saigo to the Prince Arisogawa,Japan,Meiji period (1868–1912),,,,Artist,,Yamazaki Toshinobu,"Japanese, active ca. 1857–1886",,Yamazaki Toshinobu,Japanese,1857,1886,"Oct. 16, 1877 (Meiji 10)",1877,1877,One sheet of a triptych of polychrome woodblock prints; ink and color on paper,Triptych 14 x 27 11/16 in. (35.5 x 70.4 cm),"Gift of Lincoln Kirstein, 1959",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55216,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3226,false,true,55217,Asian Art,Woodblock print,Saigo Ryusei Kubi jitsu ken|Presentation of the Head of Saigo to the Prince Arisogawa,Japan,Meiji period (1868–1912),,,,Artist,,Yamazaki Toshinobu,"Japanese, active ca. 1857–1886",,Yamazaki Toshinobu,Japanese,1857,1886,"Oct. 16, 1877 (Meiji 10)",1877,1877,One sheet of a triptych of polychrome woodblock prints; ink and color on paper,Triptych 14 x 27 11/16 in. (35.5 x 70.4 cm),"Gift of Lincoln Kirstein, 1959",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55217,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3251,false,true,55262,Asian Art,Woodblock prints,Taiseikan Shosho Shiten|Commanders Receiving the Emperor's Drinking Cups,Japan,Meiji period (1868–1912),,,,Artist,,Yamazaki Toshinobu,"Japanese, active ca. 1857–1886",,Yamazaki Toshinobu,Japanese,1857,1886,1886 (Meiji 19),1886,1886,Triptych of polychrome woodblock prints; ink and color on paper,13 1/8 x 29 1/8 in. (33.3 x 74 cm),"Gift of Lincoln Kirstein, 1959",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55262,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3372,false,true,55539,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Hamada Josen,"Japanese, active turn of 20th century",,Hamada Josen,Japanese,1880,1930,ca. 1906,1896,1916,Polychrome woodblock print; ink and color on paper,8 1/16 x 11 5/8 in. (20.5 x 29.5 cm),"Gift of Lincoln Kirstein, 1960",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55539,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3230,false,true,55224,Asian Art,Print,御鳳輦之図|Illustration of the Imperial Carriage (Gohōren no zu),Japan,Meiji period (1868–1912),,,,Artist,,Utagawa Kunitoshi,"Japanese, active 2nd half of 19th century",,Utagawa Kunitoshi,Japanese,1850,1899,1889,1889,1889,One sheet of a triptych of polychrome woodblock prints; ink and color on paper,Triptych 14 9/16 x 29 in. (37 x 73.6 cm),"Gift of Lincoln Kirstein, 1959",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55224,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3231,false,true,55229,Asian Art,Print,Go Horen no zu|View of the Imperial Carriage,Japan,Meiji period (1868–1912),,,,Artist,,Utagawa Kunitoshi,"Japanese, active 2nd half of 19th century",,Utagawa Kunitoshi,Japanese,1850,1899,1889 (Meiji 22),1889,1889,One sheet of a triptych of polychrome woodblock prints; ink and color on paper,Triptych 14 9/16 x 29 in. (37 x 73.6 cm),"Gift of Lincoln Kirstein, 1959",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55229,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3232,false,true,55230,Asian Art,Print,Go Horen no zu|View of the Imperial Carriage,Japan,Meiji period (1868–1912),,,,Artist,,Utagawa Kunitoshi,"Japanese, active 2nd half of 19th century",,Utagawa Kunitoshi,Japanese,1850,1899,1889 (Meiji 22),1889,1889,One sheet of a triptych of polychrome woodblock prints; ink and color on paper,Triptych 14 9/16 x 29 in. (37 x 73.6 cm),"Gift of Lincoln Kirstein, 1959",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55230,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -MJP46,false,true,57155,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Natori Shunsen,"Japanese, 1886–1960, born in Kushigata machi, Yamanishi Prefecture",,Natori Shunsen,Japanese,1886,1960,1929,1929,1929,Polychrome woodblock print; ink and color on paper,14 3/4 x 9 7/8 (37.5 x 25.1 cm),"Gift of H. J. Isaacson, 1948",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/57155,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2431,false,true,44586,Asian Art,Print,長襦袢の女|Woman Dressing,Japan,Taishō period (1912–26),,,,Artist,,Hashiguchi Goyō,"Japanese, 1881–1921",,Hashiguchi Goyō,Japanese,1881,1921,1920,1920,1920,Polychrome woodblock print; ink and color on paper,Image: 17 3/4 × 5 3/4 in. (45.1 × 14.6 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/44586,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2432,false,true,44587,Asian Art,Print,夏衣の女|Woman in Summer Clothing,Japan,Taishō period (1912–26),,,,Artist,,Hashiguchi Goyō,"Japanese, 1881–1921",,Hashiguchi Goyō,Japanese,1881,1921,1920,1920,1920,Polychrome woodblock print; ink and color on paper,Image: 17 3/4 × 11 1/2 in. (45.1 × 29.2 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/44587,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2433,false,true,56873,Asian Art,Woodblock print,,Japan,Taishō period (1912–26),,,,Artist,,Hashiguchi Goyō,"Japanese, 1881–1921",,Hashiguchi Goyō,Japanese,1881,1921,1920,1920,1920,Polychrome woodblock print; ink and color on paper,9 x 15 1/2 in. (22.9 x 39.4 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56873,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3415,false,true,55605,Asian Art,Woodblock print,"百人一首 うはかゑとき 権中納言匡房|Poem by Gon-chūnagon Masafusa (Ōe no Masafusa), from the series One Hundred Poems Explained by the Nurse (Hyakunin isshu uba ga etoki)",Japan,Taishō period (1912–26),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,1921,1921,1921,Polychrome woodblock print; ink and color on paper,9 3/4 x 14 5/8 in. (24.8 x 37.1 cm),"Gift of Mrs. Carll Tucker, 1962",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55605,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3416,false,true,55609,Asian Art,Woodblock print,"百人一首 うはかゑとき 赤染衛門|Poem by Akazome Emon, from the series One Hundred Poems Explained by the Nurse (Hyakunin isshu uba ga etoki)",Japan,Taishō period (1912–26),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,1921,1921,1921,Polychrome woodblock print; ink and color on paper,9 5/8 x 14 3/8 in. (24.4 x 36.5 cm),"Gift of Mrs. Carll Tucker, 1962",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55609,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3417,false,true,55611,Asian Art,Woodblock print,"百人一首 乳かゑとき 中納言敦忠|Poem by Chūnagon Atsutada (Fujiwara no Asatada), from the series One Hundred Poems Explained by the Nurse (Hyakunin isshu uba ga etoki)",Japan,Taishō period (1912–26),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,1921,1921,1921,Polychrome woodblock print; ink and color on paper,Image: 10 1/4 × 15 1/4 in. (26 × 38.7 cm) Mat: 15 5/8 × 22 7/8 in. (39.7 × 58.1 cm),"Gift of Mrs. Carll Tucker, 1962",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55611,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3418,false,true,55612,Asian Art,Woodblock print,"百人一首 乳母か縁説 素胜法師|Poem by Sōsei Hōshi, from the series One Hundred Poems Explained by the Nurse (Hyakunin isshu uba ga etoki)",Japan,Taishō period (1912–26),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,1921,1921,1921,Polychrome woodblock print; ink and color on paper,9 3/4 x 14 3/4 in. (24.8 x 37.5 cm),"Gift of Mrs. Carll Tucker, 1962",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55612,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3494,false,true,55721,Asian Art,Woodblock print,,Japan,Meiji (1868–1912)–Taishō (1912–26) period,,,,Artist,,Kahō,"Japanese, early 20th century",,Kahō,Japanese,1900,1930,early 20th century,1900,1912,Polychrome woodblock print; ink and color on paper,7 x 9 in. (17.8 x 22.9 cm),"Gift of Cole J. Younger, 1975",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55721,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3438,false,true,55642,Asian Art,Print,江戸名所 御茶の水|Meguro,Japan,Meiji (1868–1912) or Taishō (1912–26) period,,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,early 20th century,1900,1926,Polychrome woodblock print; ink and color on paper,10 x 14 1/2 in. (25.4 x 36.8 cm),"Gift of Cole J. Younger, 1975",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55642,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -17.118.74,false,true,62898,Asian Art,Teabowl,,Japan,,,,,Artist,,Chōjirō,(1516–?1592),,Chōjirō,Japanese,1516,1592,ca. 1575,1565,1585,Clay covered with a dull black glaze (Raku ware),H. 3 5/8 in. (9.2 cm); Diam. 3 3/4 in. (9.5 cm),"Rogers Fund, 1917",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/62898,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -17.118.76,false,true,62900,Asian Art,Teabowl,,Japan,,,,,Artist,,Raku Donyu,"Japanese, died 1656",,"Raku, Donyu",Japanese,1556,1656,ca. 1650,1640,1660,Clay covered with a shiny black glaze and frothy edge of glaze (Raku ware),H. 3 in. (7.6 cm); Diam. 5 in. (12.7 cm),"Rogers Fund, 1917",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/62900,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"93.3.305a, b",false,true,47110,Asian Art,Bowl,,Japan,,,,,Artist,,Minpei,active 19th century,,Minpei,Japanese,0019,0019,1840,1840,1840,Porcelaneous ware covered with a finely crackled glaze over which is a black enameled glaze (Awaji ware),H. 3 3/4 in. (9.5 cm); Diam. 5 1/2 in. (14 cm),"Gift of Charles Stewart Smith, 1893",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/47110,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -16.13.1,false,true,62879,Asian Art,Teabowl,,Japan,,,,,Artist,,Hon'ami Kōetsu,"Japanese, 1558–1637",,Hon'ami Kōetsu,Japanese,1558,1637,ca. 1600,1590,1610,"Clay covered with glaze, except on lower part where it is left bare",H. 4 1/2 in. (11.4 cm); Diam. 4 5/8 in. (11.7 cm),"Rogers Fund, 1916",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/62879,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"93.3.217a, b",false,true,47328,Asian Art,Teapot,,Japan,,,,,Artist,,Shuhei,"Japanese, 1788–1839",,Shuhei,Japanese,1788,1839,1800,1800,1899,"Clay covered with a partly crackled glaze and decorated with enamels on a gold ground (Kyoto ware, Satsuma style)",H. 3 1/4 in. (8.3 cm),"Gift of Charles Stewart Smith, 1893",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/47328,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"93.1.143a, b",false,true,62678,Asian Art,Teabowl,,Japan,,,,,Artist,,Eiraku Wazen,"Japanese, 1821–1896",,Eiraku Wazen,Japanese,1821,1896,1850,1850,1850,"Clay with speckled glaze (Kyoto ware, Bizen type)",H. 3 1/8 in. (7.9 cm); Diam. 4 3/8 in. (11.1 cm),"Gift of Mr. and Mrs. Samuel Colman, 1893",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/62678,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -93.1.26,false,true,62620,Asian Art,Cup,,Japan,,,,,Artist,,Kenya,"Japanese, 1825–1889",,Miura Kenya,Japanese,1825,1889,ca. 1840,1830,1850,Crackled porcelain covered with glaze and decoration in enamels,H. 2 3/4 in. (7 cm),"Gift of Mr. and Mrs. Samuel Colman, 1893",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/62620,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"93.3.15a, b",false,true,47814,Asian Art,Pot,,Japan,,,,,Artist,,Makuzu Kōzan I (Miyagawa Toranosuke),"Japanese, 1842–1916",,Makuzu Kōzan,Japanese,1842,1916,1890,1890,1890,"White porcelain decorated in red under the glaze (Kyoto ware, Makuzu type)",H. inc. lid 8 1/2 in. (21.6 cm); H. w/o lid 7 3/4 in. (19.7 cm); Diam. 8 1/4 in. (21 cm),"Gift of Charles Stewart Smith, 1893",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/47814,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"93.3.424a, b",false,true,62836,Asian Art,Censer,,Japan,,,,,Artist,,Takemoto,"Japanese, 1845–1892",,Takemoto,Japanese,1845,1892,1892,1892,1892,"White porcelain covered with a blue glaze, run like Zhun",H. 3 3/4 in. (9.5 cm); Diam. 4 7/8 in. (12.4 cm),"Gift of Charles Stewart Smith, 1893",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/62836,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1984.392.2,false,true,63938,Asian Art,Teabowl,,Japan,,,,,Artist,,Ōhi Chōzaemon,"Japanese, 1850–1927",,Ōhi Chōzaemon,Japanese,1850,1927,20th century,1850,1927,Pottery (brown Raku ware),H. 3 1/4 in. (8.3 cm); Diam. 4 1/2 in. (11.4 cm),"Gift of Toshiro Ohi, 1984",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/63938,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -46.39.11,false,true,63332,Asian Art,Bowl,,Japan,,,,,Artist,,Yabu Meizan,"Japanese, 1853–1934",,Yabu Meizan,Japanese,1853,1934,late 19th century,1871,1899,"Porcelain; exterior, spiral millefleurs bands in a variety of enamels and gold; geometric bands in black and rust enamels and gold on lower part of bowl; interior, closely covered with tiny butterflies in red, black, yellow and pale blue enamels, with gold; gold lip rim (Satsuma ware)",H. 1 5/8 in. (4.1 cm); Diam. 3 in. (7.6 cm),"Gift of Philip A. Rollins, 1946",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/63332,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"93.3.221a, b",false,true,62795,Asian Art,Incense box,,Japan,,,,,Artist,,Nonomura Ninsei,"Japanese, active ca. 1646–94",,Ninsei Nonomura,Japanese,1646,1694,1670,1670,1670,"Paste covered with a transparent crackled glaze and decorated with colored enamels and gold (Kyoto ware, Satsuma type)",H. 1 1/4 in. (3.2 cm); Diam. 3 1/8 in. (7.9 cm),"Gift of Charles Stewart Smith, 1893",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/62795,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -93.1.46,false,true,62006,Asian Art,Bowl,,Japan,Edo period (1615–1868),,,,Artist,In the style of,Shunzan,,,Shunzan,Japanese,0019,0019,ca. 1770,1760,1780,"Seto ware, Oribe Revival type; glazed stoneware",H. 3 1/2 in. (8.9 cm); L. 10 3/4 in. (27.3 cm),"Gift of Mr. and Mrs. Samuel Colman, 1893",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/62006,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -17.118.70,false,true,62889,Asian Art,Teabowl,,Japan,Edo period (1615–1868),,,,Artist,,Ryōnyu,died 1835,,Ryōnyu,Japanese,1735,1835,ca. 1800,1790,1810,Clay covered with a shiny black glaze (Raku ware),H. 3 3/4 in. (9.5 cm); Diam. 4 in. (10.2 cm),"Rogers Fund, 1917",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/62889,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.120.545,false,true,63233,Asian Art,Teabowl,,Japan,Edo period (1615–1868),,,,Artist,,Ryōnyu,died 1835,,Ryōnyu,Japanese,1735,1835,ca. 1790,1780,1800,"Rounded body, well defined foot; brown clay, yellow underglaze, black, red and green overglaze (Raku ware)",H. 2 3/4 in. (7 cm); Diam. 4 1/2 in. (11.4 cm),"The Howard Mansfield Collection, Gift of Howard Mansfield, 1936",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/63233,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.120.537,false,true,63197,Asian Art,Teabowl,,Japan,Edo period (1615–1868),,,,Artist,,Keinyu,died 1893,,Keinyu,Japanese,1793,1893,ca. 1852,1842,1862,"Cylindrical with small foot; horizontal lines incised below uneven lip; brown clay with creamy glaze and green overglaze; brown latticed strokes; inside, three flying birds suggested (Raku ware)",H. 3 in. (7.6 cm); Diam. 4 1/4 in. (10.8 cm),"The Howard Mansfield Collection, Gift of Howard Mansfield, 1936",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/63197,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -23.225.248,false,true,63021,Asian Art,Bowl,,Japan,Edo period (1615–1868),,,,Artist,,Aoki Mokubei,1767–1833,,Aoki Mokubei,Japanese,1767,1833,ca. 1800,1790,1810,Porcelain decorated in enamels and gold (Kyoto ware),H. 4 in. (10.2 cm); Diam. 9 in. (22.9 cm),"Gift of Mrs. V. Everit Macy, 1923",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/63021,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -23.225.257,false,true,47204,Asian Art,Fire box,,Japan,Edo period (1615–1868),,,,Artist,,Aoki Mokubei,1767–1833,,Aoki Mokubei,Japanese,1767,1833,1800,1800,1800,Modeled and unglazed (Kyoto ware),H. 9 in. (22.9 cm),"Gift of Mrs. V. Everit Macy, 1923",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/47204,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"23.225.258a, b",false,true,47249,Asian Art,Teapot,,Japan,Edo period (1615–1868),,,,Artist,,Aoki Mokubei,1767–1833,(?),Aoki Mokubei,Japanese,1767,1833,1800,1800,1800,Faience decorated with design in relief and glaze (Kyoto ware),H. 4 5/8 in. (11.7 cm); L. 5 3/4 in. (14.6 cm),"Gift of Mrs. V. Everit Macy, 1923",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/47249,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"23.225.267a, b",false,true,47250,Asian Art,Teapot,,Japan,Edo period (1615–1868),,,,Artist,,Aoki Mokubei,1767–1833,,Aoki Mokubei,Japanese,1767,1833,1800,1800,1800,"Faience decorated in relief, celadon glaze (Kyoto ware)",H. (with cover) 4 5/8 in. (11.7 cm); L. 6 1/8 in. (15.6 cm),"Gift of Mrs. V. Everit Macy, 1923",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/47250,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"93.3.182a, b",false,true,47329,Asian Art,Teapot,,Japan,Edo period (1615–1868),,,,Artist,,Bizan,died 1838 (?),,Bizan,Japanese,1738,1838,ca. 1850,1840,1860,"Claycovered with a finely crackled, smooth glaze and decorated in enamels and gold (Kyoto ware)",H. 3 3/4 in. (9.5 cm),"Gift of Charles Stewart Smith, 1893",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/47329,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -17.118.77,false,true,62901,Asian Art,Teabowl,,Japan,Edo period (1615–1868),,,,Artist,,Raku Donyu,"Japanese, died 1656",,"Raku, Donyu",Japanese,1556,1656,ca. 1650,1640,1660,Clay covered with a dull red glaze (Raku ware),H. 3 1/4 in. (8.3 cm); Diam. 4 1/2 in. (11.4 cm),"Rogers Fund, 1917",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/62901,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"25.60.18a, b",false,true,47113,Asian Art,Covered incense box,,Japan,Edo period (1615–1868),,,,Artist,,Raku Donyu,"Japanese, died 1656",,"Raku, Donyu",Japanese,1556,1656,ca. 1650,1640,1660,"Pottery incised in scroll pattern, in imitation of Chinese carved lacquer; brownish white glaze (Raku ware)",H. 1 1/2 in. (3.8 cm); W. sq. 2 1/2 in. (6.4 cm),"Rogers Fund, 1925",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/47113,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -17.118.73,false,true,62897,Asian Art,Teabowl,,Japan,Edo period (1615–1868),,,,Artist,,Sonyu,"Japanese, died 1725",,Sonyu,Japanese,1625,1725,ca. 1710,1700,1720,Clay covered with glaze (Raku ware),H. 4 in. (10.2 cm); Diam. 4 1/8 in. (10.5 cm),"Rogers Fund, 1917",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/62897,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.120.523,false,true,63177,Asian Art,Teabowl,,Japan,Edo period (1615–1868),,,,Artist,,Sonyu,"Japanese, died 1725",,Sonyu,Japanese,1625,1725,ca. 1710,1700,1720,Clay with lustrous black glaze (Raku ware),H. 3 1/8 in. (7.9 cm); Diam. 4 3/8 in. (11.1 cm),"The Howard Mansfield Collection, Gift of Howard Mansfield, 1936",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/63177,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.120.491,false,true,63147,Asian Art,Teabowl,,Japan,Edo period (1615–1868),,,,Artist,,Minpei,active 19th century,,Minpei,Japanese,0019,0019,ca. 1830,1820,1840,White glaze which stops short of foot; elaborate decoration in enamel colors (Agano ware),H. 3 in. (7.6 cm); Diam. 3 7/8 in. (9.8 cm),"The Howard Mansfield Collection, Gift of Howard Mansfield, 1936",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/63147,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"29.100.615a, b",false,true,63109,Asian Art,Jar,,Japan,Edo period (1615–1868),,,,Artist,,Hon'ami Kōetsu,"Japanese, 1558–1637",,Hon'ami Kōetsu,Japanese,1558,1637,ca. 1620,1610,1630,"Clay, red and green glaze (Kyoto ware)",H. 5 1/2 in. (14 cm); Diam. 7 in. (17.8 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/63109,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -91.1.361,false,true,56158,Asian Art,Teabowl,,Japan,Edo period (1615–1868),,,,Artist,Style of,Ogata Kenzan,"Japanese, 1663–1743",,Ogata Kenzan,Japanese,1663,1743,mid- to late18th century,1734,1799,Pottery covered with glaze and decorated with designs in slip (Kyoto ware),H. 3 in. (7.6 cm); Diam. 4 3/4 in. (12.1 cm); Diam. of foot 2 1/2 in. (6.4 cm),"Edward C. Moore Collection, Bequest of Edward C. Moore, 1891",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/56158,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -17.118.89,false,true,44929,Asian Art,Dish,乾山様式 色絵竹文皿|Kenzan-style Dish with Bamboo Leaves,Japan,Edo period (1615–1868),,,,Artist,In the style of,Ogata Kenzan,"Japanese, 1663–1743",(?),Ogata Kenzan,Japanese,1663,1743,17th–18th century,1600,1799,Buff stoneware decorated with white and iron-brown slip and underglaze cobalt blue; gold lacquer repair,H. 1 3/8 in. (3.5 cm); Diam. 6 7/8 in. (17.5 cm),"Rogers Fund, 1917",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/44929,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -17.118.90,false,true,45514,Asian Art,Dish,乾山様式 色絵竹文皿|Kenzan-style Dish with Bamboo Leaves,Japan,Edo period (1615–1868),,,,Artist,In the style of,Ogata Kenzan,"Japanese, 1663–1743",(?),Ogata Kenzan,Japanese,1663,1743,17th–18th century,1600,1799,Buff stoneware decorated with white and iron-brown slip and underglaze cobalt blue; gold lacquer repair,H. 1 3/8 in. (3.5 cm); Diam. 6 7/8 in. (17.5 cm),"Rogers Fund, 1917",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/45514,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.120.635,false,true,63314,Asian Art,Tray,,Japan,Edo period (1615–1868),,,,Artist,,Ogata Kenzan,"Japanese, 1663–1743",,Ogata Kenzan,Japanese,1663,1743,ca. 1720,1710,1730,"Oblong with upturned rim; gray clay; crackled cream glaze; inside, chrysanthemum sprays and poem, in brown; outer rim, conventional floral diaper in blue (Tokyo ware)",H. 1 1/2 in. (3.8 cm); W. 8 7/8 in. (22.5 cm); L. 9 3/4 in. (24.8 cm),"The Howard Mansfield Collection, Gift of Howard Mansfield, 1936",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/63314,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.120.655,false,true,63318,Asian Art,Reading screen,,Japan,Edo period (1615–1868),,,,Artist,,Ogata Kenzan,"Japanese, 1663–1743",,Ogata Kenzan,Japanese,1663,1743,ca. 1740,1730,1750,"Oblong; dark brown clay; moulded frame and shaped supports glazed black; both faces of screen with ivory glaze, decorated with landscapes in black (Tokyo ware)",H. 10 1/2 in. (26.7 cm); L. 15 3/4 in. (40 cm),"The Howard Mansfield Collection, Gift of Howard Mansfield, 1936",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/63318,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"29.100.614a, b",false,true,52514,Asian Art,Jar,伝尾形乾山 松文水差|Water Jar (Mizusashi) with Pine Trees,Japan,Edo period (1615–1868),,,,Artist,Attributed to,Ogata Kenzan,"Japanese, 1663–1743",,Ogata Kenzan,Japanese,1663,1743,ca. 1720,1710,1730,Stoneware with underglaze iron oxide; lacquer cover,H. 4 1/2 in. (11.4 cm); Diam. 6 1/2 in. (16.5 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/52514,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"36.120.634a, b",false,true,63313,Asian Art,Water jar,,Japan,Edo period (1615–1868),,,,Artist,,Ogata Kenzan,"Japanese, 1663–1743",,Ogata Kenzan,Japanese,1663,1743,ca. 1705,1695,1715,"Cylindrical, the flat cover inset; hard, light clay; bluish-gray glaze, streaked showing white underglaze; snowy landscape with figures in boat modeled in white and brown slip, in low relief; meander borders in white and blue (Tokyo ware)",H. 5 3/4 in. (14.6 cm); Diam. 5 3/4 in. (14.6 cm),"The Howard Mansfield Collection, Gift of Howard Mansfield, 1936",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/63313,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.120.517,false,true,63171,Asian Art,Teabowl,,Japan,Edo period (1615–1868),,,,Artist,,Raku Sanyū,"Japanese, 1685–1739",,Sanyū,Japanese,1685,1739,ca. 1730,1720,1740,"Clay pitted; glaze on outside, mottled within; small unglazed space on foot (Raku ware)",H. 2 7/8 in. (7.3 cm); Diam. 3 3/8 in. (8.6 cm),"The Howard Mansfield Collection, Gift of Howard Mansfield, 1936",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/63171,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -17.118.68,false,true,62887,Asian Art,Teabowl,,Japan,Edo period (1615–1868),,,,Artist,,Raku Chōnyū,"Japanese, 1714–1770",,"Raku, Chōnyū",Japanese,1714,1770,ca. 1750,1740,1760,"Clay partly covered with a black, pitted glaze (Raku ware)",H. 3 1/2 in. (8.9 cm); Diam. 5 1/4 in. (13.3 cm),"Rogers Fund, 1917",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/62887,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"23.225.277a, b",false,true,45946,Asian Art,Jar with cover,,Japan,Edo period (1615–1868),,,,Artist,,Kiyomizu Rokubei I,"Japanese, 1737–1799",,Kiyomizu Rokubei I,Japanese,1737,1799,1820,1820,1820,Faience boldly craquelé with design in colored enamels (Kiyomizu ware),H. 7 7/8 in. (20 cm); Diam. 8 1/8 in. (20.6 cm),"Gift of Mrs. V. Everit Macy, 1923",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/45946,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.120.518a–e,false,true,58854,Asian Art,Teabowl,,Japan,Edo period (1615–1868),,,,Artist,,Kiyomizu Rokubei I,"Japanese, 1737–1799",,Kiyomizu Rokubei I,Japanese,1737,1799,mid- to late 18th century,1750,1799,Stoneware with inlaid design (Kyoto ware),H. 3 3/4 in. (9.5 cm); Diam. of rim 3 7/8 in. (9.8 cm); Diam. of foot 2 1/4 in. (5.7 cm),"The Howard Mansfield Collection, Gift of Howard Mansfield, 1936",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/58854,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -93.3.210,false,true,58850,Asian Art,Cup,,Japan,Edo period (1615–1868),,,,Artist,,Nin'ami Dōhachi (Dōhachi II),"Japanese, 1783–1855",,Dōhachi II,Japanese,1783,1855,1770,1700,1799,Earthenware with colored enamels and gold (Kyoto ware),H. 3 in. (7.6 cm); Diam. 4 3/4 in. (12.1 cm),"Gift of Charles Stewart Smith, 1893",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/58850,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.634,false,true,63115,Asian Art,Bowl,,Japan,Edo period (1615–1868),,,,Artist,,Nin'ami Dōhachi (Dōhachi II),"Japanese, 1783–1855",,Dōhachi II,Japanese,1783,1855,ca. 1850,1840,1860,"Clay, glaze decorated with in overglaze enamels (Kiyomizu ware)",H. 3 1/8 in. (7.9 cm); Diam. 4 3/4 in. (12.1 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/63115,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.120.546,false,true,63234,Asian Art,Teabowl,,Japan,Edo period (1615–1868),,,,Artist,,Nin'ami Dōhachi (Dōhachi II),"Japanese, 1783–1855",,Dōhachi II,Japanese,1783,1855,ca. 1850,1840,1860,"Distinct wheelmarks; unglazed foot; fawn-colored clay, very thin; pinkish glaze (Kiyomizu ware)",H. 3 in. (7.6 cm); Diam. 4 1/4 in. (10.8 cm),"The Howard Mansfield Collection, Gift of Howard Mansfield, 1936",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/63234,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.120.547,false,true,63235,Asian Art,Teabowl,,Japan,Edo period (1615–1868),,,,Artist,,Nin'ami Dōhachi (Dōhachi II),"Japanese, 1783–1855",,Dōhachi II,Japanese,1783,1855,ca. 1850,1840,1860,"Slightly curved outline, small unglazed foot; lightweight fawn-colored clay; thin lustrous black glaze with pine boughs and poem in white (Kiyomizu ware)",H. 3 1/8 in. (7.9 cm); Diam. 4 3/8 in. (11.1 cm),"The Howard Mansfield Collection, Gift of Howard Mansfield, 1936",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/63235,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -17.118.80,false,true,62904,Asian Art,Teabowl,,Japan,Edo period (1615–1868),,,,Artist,,Raku Tannyu,"Japanese, 1795–1854",,"Raku, Tannyu",Japanese,1795,1854,ca. 1840,1830,1850,Clay partly covered with a shiny black glaze,H. 3 in. (7.6 cm); Diam. 4 1/2 in. (11.4 cm),"Rogers Fund, 1917",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/62904,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.120.500,false,true,63154,Asian Art,Teabowl,,Japan,Edo period (1615–1868),,,,Artist,,Raku Tannyu,"Japanese, 1795–1854",,"Raku, Tannyu",Japanese,1795,1854,ca. 1810,1800,1820,"Clay completely covered with uniform glaze, over a gray glaze (Raku ware)",H. 3 3/8 in. (8.6 cm); Diam. 4 3/8 in. (11.1 cm),"The Howard Mansfield Collection, Gift of Howard Mansfield, 1936",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/63154,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.120.522,false,true,63176,Asian Art,Teabowl,,Japan,Edo period (1615–1868),,,,Artist,,Raku Tannyu,"Japanese, 1795–1854",,"Raku, Tannyu",Japanese,1795,1854,ca. 1810,1800,1820,"Clay covered, except for spots on foot, by glaze and overglaze which has turned black (Raku ware)",H. 2 3/4 in. (7 cm); Diam. 4 5/8 in. (11.7 cm),"The Howard Mansfield Collection, Gift of Howard Mansfield, 1936",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/63176,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -93.1.87,false,true,45971,Asian Art,Cup,,Japan,Edo period (1615–1868),,,,Artist,,Shuntai,"Japanese, 1799–1878",,Shuntai,Japanese,1799,1799,1825,1825,1825,"Clay covered with a transparent crackled glaze with streaks (Mino ware, Ofuke type)",H. 5 in. (12.7 cm),"Gift of Mr. and Mrs. Samuel Colman, 1893",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/45971,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.120.516,false,true,63170,Asian Art,Teabowl,,Japan,Edo period (1615–1868),,,,Artist,,Eiraku Wazen,"Japanese, 1821–1896",,Eiraku Wazen,Japanese,1821,1896,ca. 1850,1840,1860,"Clay; thick glaze, sufflé with touches of black inside and out (Eiraku ware)",H. 3 in. (7.6 cm); Diam. 4 1/4 in. (10.8 cm),"The Howard Mansfield Collection, Gift of Howard Mansfield, 1936",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/63170,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.120.531,false,true,63190,Asian Art,Teabowl,,Japan,Edo period (1615–1868),,,,Artist,,Makuzu Kōzan I (Miyagawa Toranosuke),"Japanese, 1842–1916",,Makuzu Kōzan,Japanese,1842,1916,ca. 1850,1840,1860,Clay with flecks of crackle inside and out (Awata ware),H. 3 in. (7.6 cm); Diam. 4 5/8 in. (11.7 cm),"The Howard Mansfield Collection, Gift of Howard Mansfield, 1936",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/63190,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"93.3.195a, b",false,true,47267,Asian Art,Teapot,,Japan,Edo period (1615–1868),,,,Artist,,Takahashi Sōhei,"Japanese, 1804?–?1835",,Takahashi Sōhei,Japanese,1804,1835,1840,1840,1840,"Clay covered with a finely crackled transparent glaze and decorated in colored enamels (Kyoto ware, after Ninsei)",H. 2 1/2 in. (6.4 cm),"Gift of Charles Stewart Smith, 1893",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/47267,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -25.60.34,false,true,63089,Asian Art,Teabowl,,Japan,Edo period (1615–1868),,,,Artist,,Tokunyu,"Japanese, died ca. 1775",,Tokunyu,Japanese,1675,1775,ca. 1770,1760,1780,Pottery with black glaze (Raku ware),Diam. 4 3/8 in. (11.1 cm),"Rogers Fund, 1925",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/63089,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -17.118.69,false,true,62888,Asian Art,Teabowl,,Japan,Edo period (1615–1868),,,,Artist,,Tokunyu,"Japanese, died ca. 1775",,Tokunyu,Japanese,1675,1775,ca. 1770,1760,1780,Clay partly covered with a dull black glaze (Raku ware),H. 3 1/4 in. (8.3 cm); Diam. 5 in. (12.7 cm),"Rogers Fund, 1917",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/62888,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -93.3.204,false,true,45962,Asian Art,Jar,,Japan,Edo period (1615–1868),,,,Artist,,Nonomura Ninsei,"Japanese, active ca. 1646–94",,Ninsei Nonomura,Japanese,1646,1694,1730,1730,1730,"Clay covered with a transparent crackled glaze; decorated colored enamels and gold (Kyoto ware, Takamatsu type)",H. 8 1/2 in. (21.6 cm),"Gift of Charles Stewart Smith, 1893",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/45962,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.666,false,true,63141,Asian Art,Tea jar,,Japan,Edo period (1615–1868),,,,Artist,,Nonomura Ninsei,"Japanese, active ca. 1646–94",,Ninsei Nonomura,Japanese,1646,1694,ca. 1660,1650,1670,Clay; black glaze with two bands of crackled white glaze; (Awata ware),H. 3 1/2 in. (8.9 cm); Diam. 2 1/8 in. (5.4 cm); Diam. of rim 1 in. (2.5 cm); Diam. of base 1 1/8 in. (2.9 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/63141,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.668,false,true,63143,Asian Art,Incense burner,野々村仁清工房 四季花文香炉|Ninsei-style Incense Burner with Flowers of the Four Seasons,Japan,Edo period (1615–1868),,,,Artist,Workshop of,Nonomura Ninsei,"Japanese, active ca. 1646–94",,Ninsei Nonomura,Japanese,1646,1694,mid-17th century,1634,1666,Stoneware with overglaze enamels,H. 6 3/4 in. (17.1 cm); W. 7 1/4 in. (18.4 cm); D. 7 1/4 in. (18.4 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/63143,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"23.225.236a, b",false,true,44928,Asian Art,Incense box,,Japan,Edo period (1615–1868),,,,Artist,Attributed to,Nonomura Ninsei,"Japanese, active ca. 1646–94",,Ninsei Nonomura,Japanese,1646,1694,ca. 1648–1649,1648,1649,"Glazed stoneware, colored enamels and gold",H. 1 1/4 in. (3.2 cm); Diam. 3 1/4 in. (8.3 cm),"Gift of Mrs. V. Everit Macy, 1923",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/44928,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.120.559a–f,false,true,53832,Asian Art,Tea caddy,,Japan,Edo period (1615–1868),,,,Artist,,Nonomura Ninsei,"Japanese, active ca. 1646–94",,Ninsei Nonomura,Japanese,1646,1694,ca. 1650,1640,1660,"Stoneware with red, brown, and black glazes",H. (with lid) 6 in. (15.2 cm); Diam. 1 7/8 in. (4.8 cm); Diam. of rim 1 1/8 in. (2.9 cm); Diam. of base 1 1/4 in. (3.2 cm),"The Howard Mansfield Collection, Gift of Howard Mansfield, 1936",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/53832,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -17.118.71,false,true,62890,Asian Art,Teabowl,,Japan,Meiji period (1868–1912),,,,Artist,,Keinyu,died 1893,,Keinyu,Japanese,1793,1893,ca. 1870,1860,1880,Clay covered with a black glaze; on the lower part with a transparent brown glaze (Raku ware),H. 4 in. (10.2 cm); Diam. 4 3/8 in. (11.1 cm),"Rogers Fund, 1917",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/62890,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.103,false,true,57186,Asian Art,Album leaf,,Japan,Meiji period (1868–1912),,,,Artist,,Shibata Zeshin,"Japanese, 1807–1891",,Shibata Zeshin,Japanese,1807,1891,1882,1882,1882,Lacquer on paper,7 1/2 x 6 1/2 in. (19.1 x 16.5 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/57186,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -93.3.111,false,true,46691,Asian Art,Flower vase,紫馬麒麟卍紗綾形紋瓶 (一対)|Vase with Horse and Kirin on Geometric Sayagata (key fret) Pattern (one of a pair),Japan,Meiji period (1868–1912),,,,Artist,Style of,Makuzu Kōzan I (Miyagawa Toranosuke),"Japanese, 1842–1916",,Makuzu Kōzan,Japanese,1842,1916,late 19th century,1867,1899,Porcelain with incised design and underglaze red,H. 4 3/4 in. (12.1 cm); Diam. of rim 1 1/4 in. (3.2 cm); Diam. 4 3/4 in. (12.1 cm); Diam. of base 2 1/4 in. (5.7 cm),"Gift of Charles Stewart Smith, 1893",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/46691,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -93.3.112,false,true,46692,Asian Art,Flower vase,紫馬麒麟卍紗綾形紋瓶 (一対)|Vase with Horse and Kirin on Geometric Sayagata (key fret) Pattern (one of a pair),Japan,Meiji period (1868–1912),,,,Artist,Style of,Makuzu Kōzan I (Miyagawa Toranosuke),"Japanese, 1842–1916",,Makuzu Kōzan,Japanese,1842,1916,late 19th century,1867,1899,Porcelain with incised design and underglaze red,H. 4 3/4 in. (12.1 cm); Diam. of rim 1 1/4 in. (3.2 cm); Diam. 4 3/4 in. (12.1 cm); Diam. of base 2 1/4 in. (5.7 cm),"Gift of Charles Stewart Smith, 1893",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/46692,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1984.408.1,false,true,63939,Asian Art,Vase,波に鯉文瓶|Vase with Carps in Waves,Japan,Meiji period (1868–1912),,,,Artist,,Makuzu Kōzan I (Miyagawa Toranosuke),"Japanese, 1842–1916",,Makuzu Kōzan,Japanese,1842,1916,late 19th–early 20th century,1867,1933,Porcelain with metal fittings,H. 10 in. (25.4 cm); Diam. 3 1/4 in. (8.3 cm) Diam. of rim: 3 3/4 in. (9.5 cm) Diam. of foot: 4 7/8 in. (12.4 cm),"Gift of Stanley J. Love, 1984",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/63939,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"91.1.367a, b",false,true,53613,Asian Art,Water jar,バッタ行列文水指|Water Jar with Procession of Grasshoppers,Japan,Meiji period (1868–1912),,,,Artist,,Makuzu Kōzan I (Miyagawa Toranosuke),"Japanese, 1842–1916",,Makuzu Kōzan,Japanese,1842,1916,late 19th century,1868,1899,Stoneware with polychrome overglaze enamels and gold with a wood lid and ivory knob (Makuzu ware),H. 5 3/4 in. (14.6 cm),"Edward C. Moore Collection, Bequest of Edward C. Moore, 1891",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/53613,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"93.3.364a, b",false,true,45997,Asian Art,Wine pot,染付龍文水注|Ewer with Dragon,Japan,Meiji period (1868–1912),,,,Artist,Style of,Makuzu Kōzan I (Miyagawa Toranosuke),"Japanese, 1842–1916",,Makuzu Kōzan,Japanese,1842,1916,late 19th century,1867,1899,Porcelain with underglaze blue,H. 9 1/4 in. (23.5 cm),"Gift of Charles Stewart Smith, 1893",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/45997,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -93.3.446,false,true,46627,Asian Art,Vase,蝶文瓶|Vase with Butterflies,Japan,Meiji period (1868–1912),,,,Artist,,Katō Tomotarō,"Japanese, 1851–1916",,Katō Tomotarō,Japanese,1851,1916,late 19th century,1867,1899,"Porcelain with underglaze blue, overglaze pink and white slip",H. 6 1/4 in. (15.9 cm); Diam. 4 in. (10.2 cm),"Gift of Charles Stewart Smith, 1893",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/46627,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"93.3.35a, b",false,true,62722,Asian Art,Covered pot,青磁月に梅樹文水指|Water Jar with Plum Tree,Japan,Meiji period (1868–1912),,,,Artist,,Katō Tomotarō,"Japanese, 1851–1916",,Katō Tomotarō,Japanese,1851,1916,ca. 1890,1880,1900,Porcelain with blue-and-white slip under celadon glaze,H. 7 3/4 in. (19.7 cm),"Gift of Charles Stewart Smith, 1893",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/62722,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"25.60.28a, b",false,true,63085,Asian Art,Tea jar,,Japan,Kamakura period (1185–1333),,,,Artist,Attributed to,Tôshiro,,,Tôshiro,Japanese,1200,1230,ca. 1245,1235,1255,Crackled brown glaze on a clay that has burned very dark; specimen of class known as Manako Tubutsu (Seto ware),H. 3 in. (7.6 cm),"Rogers Fund, 1925",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/63085,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"93.3.147a, b",false,true,62763,Asian Art,Tea jar,,Japan,Kamakura period (1185–1333),,,,Artist,,Tôshiro,,,Tôshiro,Japanese,1200,1230,ca. 1320 (?),1310,1330,Clay covered with a mottled glaze (Seto ware),H. 4 1/4 in. (10.8 cm),"Gift of Charles Stewart Smith, 1893",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/62763,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -17.118.64,false,true,53833,Asian Art,Teabowl,,Japan,Momoyama period (1573–1615),,,,Artist,,Hon'ami Kōetsu,"Japanese, 1558–1637",,Hon'ami Kōetsu,Japanese,1558,1637,early 17th century,1600,1633,Earthenware covered with light red glaze (Raku ware),H. 3 7/8 in. (9.8 cm); Diam. 5 in. (12.7 cm),"Rogers Fund, 1917",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/53833,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.120.492a–c,false,true,63148,Asian Art,Teabowl,,Japan,Momoyama period (1573–1615),,,,Artist,Attributed to,Hon'ami Kōetsu,"Japanese, 1558–1637",,Hon'ami Kōetsu,Japanese,1558,1637,ca. 1600,1590,1610,"Cclay, pitted, covered with glaze having patches; vertical incisions near lip, under glaze (Raku ware)",H. 3 3/4 in. (9.5 cm); Diam. 5 1/8 in. (13 cm),"The Howard Mansfield Collection, Gift of Howard Mansfield, 1936",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/63148,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.68a–c,false,true,60505,Asian Art,Cabinet,,Japan,,,,,Artist,,Ogawa Haritsu (Ritsuō),"Japanese, 1663–1747",,Ritsuō,Japanese,1663,1747,1663–1747,1663,1747,Lacquered wood,Cabinet and stools: H. 66 3/4 in. (169.5 cm); W. 41 1/4 in. (104.8 cm); D. 19 1/2 in. (49.5 cm) Cabinet: H. 36 1/4 in. (92.1 cm); W. 41 1/4 in. (104.8 cm); D. 9 1/2 in. (49.5 cm) Each stool: H. 30 1/2 in. (77.5 cm); W. 18 1/2 in. (47 cm); D. 8 1/2 in. (47 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Furniture,,http://www.metmuseum.org/art/collection/search/60505,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1984.408.2,false,true,60138,Asian Art,Water jug,,Japan,Meiji period (1868–1912),,,,Artist,,Tō Kai Ko,"Japanese, active late 19th century",,Tō Kai Ko,Japanese,1871,1899,late 19th century,1871,1899,Silver,H. 6 1/2 in. (16.5 cm); W. 5 1/2 in. (14 cm); W. (at base) 3 in. (7.6 cm),"Gift of Stanley J. Love, 1984",,,,,,,,,,,,Metalwork,,http://www.metmuseum.org/art/collection/search/60138,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -52.177.22,false,true,57332,Asian Art,Hanging scroll,,Japan,,,,,Artist,"Anonymous, in the style of",Mori Sosen,"Japanese, 1747–1821",,Mori Sosen,Japanese,1747,1821,style of 18th century,1700,1799,Hanging scroll; ink and color on silk,40 1/2 x 13 3/4 in. (102.9 x 34.9 cm),"Bequest of Katherine S. Dreier, 1952",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/57332,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.38,false,true,55367,Asian Art,Hanging scroll,,Japan,,,,,Artist,Attributed to,Imao Keinen,"Japanese, 1845–1924",,Imao Keinen,Japanese,1845,1924,19th–20th century,1845,1924,Hanging scroll; ink and color on silk,45 3/4 x 10 1/2 in. (116.2 x 26.7 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/55367,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -52.160.4,false,true,57330,Asian Art,Painting,,Japan,,,,,Artist,,Hashimoto Kansetsu,"Japanese, 1883–1945",,Hashimoto Kansetsu,Japanese,1883,1945,20th century,1900,1945,Painting; watercolor on paper,19 x 22 in. (48.3 x 55.9 cm),"Purchase, Gifts, Bequests, and Funds from various donors, by exchange, 1952",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/57330,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.19,false,true,49053,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Gyokusen,died 1852,,Gyokusen,Japanese,1752,1852,1812,1615,1868,Hanging scroll; ink on paper,34 x 15 1/2 in. (86.4 x 39.4 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49053,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.268.110,false,true,49072,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Hidaka Tetsuo,1791–1875,,Hidaka Tetsuo,Japanese,1791,1875,1862,1862,1862,Hanging scroll; ink on satin,45 1/2 x 20 5/8 in. (115.5 x 52.4 cm),"The Harry G. C. Packard Collection of Asian Art, Gift of Harry G. C. Packard, and Purchase, Fletcher, Rogers, Harris Brisbane Dick, and Louis V. Bell Funds, Joseph Pulitzer Bequest, and The Annenberg Fund Inc. Gift, 1975",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49072,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.99,false,true,45736,Asian Art,Hanging scroll,西王母図|Queen Mother of the West,Japan,Edo period (1615–1868),,,,Artist,,Kano Osanobu,1796–1846,,Kano Osanobu,Japanese,1796,1846,first half of the 19th century,1800,1846,Hanging scroll; ink and color on silk,39 3/4 x 14 7/8 in. (101 x 37.8 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45736,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.268.78,false,true,45378,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Kita Genki,active 1664–98,,Kita Genki,Japanese,1664,1698,1666,1666,1666,Hanging scroll; ink and color on silk,Image: 43 1/8 × 17 5/16 in. (109.5 × 44 cm) Overall with mounting: 79 1/4 × 22 1/4 in. (201.3 × 56.5 cm) Overall with knobs: 79 1/4 × 24 3/8 in. (201.3 × 61.9 cm),"The Harry G. C. Packard Collection of Asian Art, Gift of Harry G. C. Packard, and Purchase, Fletcher, Rogers, Harris Brisbane Dick, and Louis V. Bell Funds, Joseph Pulitzer Bequest, and The Annenberg Fund Inc. Gift, 1975",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45378,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -57.138.1,false,true,45380,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,Attributed to,Kaigetsudō Dohan,active 1710–16,,Kaigetsudō Dohan,Japanese,1710,1716,ca. 1715,1710,1716,Hanging scroll; ink and color on paper,51 x 17 3/4 in. (129.5 x 45.1 cm),"Seymour Fund, 1957",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45380,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.87,false,true,45770,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Kawamata Tsunemasa,active 1716–48,,Kawamata Tsunemasa,Japanese,1706,1758,first half of the 18th century,1716,1749,"Hanging scroll; ink, color, and gold on silk",33 3/4 x 10 5/8 in. (85.7 x 27 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45770,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -63.174.1–.3,false,true,45389,Asian Art,Triptych of hanging scrolls,,Japan,Edo period (1615–1868),,,,Artist,Attributed to,Kano Kōi,"Japanese, died 1636",,Kano Kōi,Japanese,,1636,early 17th century,1600,1636,Triptych of hanging scrolls; ink and color on paper,46 1/2 x 19 in. (118.1 x 48.3 cm),"Gift of Mr. and Mrs. Benjamin J. Levy, 1963",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45389,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.64,false,true,57175,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,After,Sesson Shūkei,ca. 1504–ca. 1589,,Sesson Shūkei,Japanese,1504,1589,probably 19th century,1800,1868,Hanging scroll; ink on paper,28 1/2 x 16 in. (72.4 x 40.6 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/57175,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.453,false,true,40353,Asian Art,Painting,,Japan,Edo period (1615–1868),,,,Artist,After,Sesson Shūkei,ca. 1504–ca. 1589,,Sesson Shūkei,Japanese,1504,1589,probably 19th century,1800,1868,Matted painting; ink on paper,Image: 16 1/2 x 20 in. (41.9 x 50.8 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/40353,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.1133.2,false,true,77188,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Kano Shōei,"Japanese, 1519–1592",,Kano Shōei,Japanese,1519,1592,late 16th century,1567,1599,Hanging scroll; ink and color on paper,Image: 25 1/2 × 12 15/16 in. (64.7 × 32.9 cm) Overall with knobs: 59 1/8 × 19 3/4 in. (150.1 × 50.1 cm) Overall with mounting: 59 1/8 × 17 13/16 in. (150.1 × 45.3 cm),"Fishbein-Bender Collection, Gift of T. Richard Fishbein and Estelle P. Bender, 2013",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/77188,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.1133.1,false,true,77179,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Fūgai Ekun,"Japanese, 1568–1654",,Fūgai Ekun,Japanese,1568,1654,datable to 1650,1650,1650,Hanging scroll; ink on paper,Image: 12 15/16 × 17 3/16 in. (32.9 × 43.7 cm) Overall with knobs: 45 9/16 × 20 1/16 in. (115.8 × 50.9 cm) Overall with mounting: 45 9/16 × 18 1/16 in. (115.8 × 45.9 cm),"Fishbein-Bender Collection, Gift of T. Richard Fishbein and Estelle P. Bender, 2013",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/77179,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.268.124,false,true,45340,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Iwasa Matabei,"Japanese, 1578–1650",,Iwasa Matabei,Japanese,1578,1650,early 17th century,1600,1633,"Hanging scroll; ink, color, gold and silver on paper",Image: 11 1/4 × 12 3/4 in. (28.6 × 32.4 cm); Overall with mounting: 47 3/4 × 16 5/8 in. (121.3 × 42.2 cm); Overall with knobs: 47 3/4 × 18 3/4 in. (121.3 × 47.6 cm),"The Harry G. C. Packard Collection of Asian Art, Gift of Harry G. C. Packard, and Purchase, Fletcher, Rogers, Harris Brisbane Dick, and Louis V. Bell Funds, Joseph Pulitzer Bequest, and The Annenberg Fund Inc. Gift, 1975",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45340,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.142,false,true,44761,Asian Art,Handscroll,烏丸光広筆 十牛図歌賛図巻|Ten Oxherding Songs,Japan,Edo period (1615–1868),,,,Artist,,Karasumaru Mitsuhiro,"Japanese, 1579–1638",,Karasumaru Mitsuhiro,Japanese,1579,1638,ca. 1634,1624,1638,Handscroll; ink on dyed paper with stenciled decoration in gold and silver,11 3/4 x 107 in. (29.9 x 271.8 cm),"Purchase, Friends of Asian Art Gifts, 1986",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/44761,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.268.48a–d,true,true,44858,Asian Art,Door panels,老梅図襖|Old Plum,Japan,Edo period (1615–1868),,,,Artist,,Kano Sansetsu,"Japanese, 1590–1651",,Kano Sansetsu,Japanese,1590,1651,1646,1646,1646,"Four sliding-door panels (fusuma); ink, color, gold, and gold leaf on paper",Overall (of all four panels): 68 3/4 x 191 1/8 in. (174.6 x 485.5 cm) Overall (a): 68 3/8 x 47 5/8 in. (173.7 x 121 cm) Overall (b): 68 3/8 x 48 3/4 in. (173.7 x 123.8 cm) Overall (c): 68 1/2 x 47 3/4 in. (174 x 121.3 cm) Overall (d): 68 1/2 x 47 3/4 in. (174 x 121.3 cm),"The Harry G. C. Packard Collection of Asian Art, Gift of Harry G. C. Packard, and Purchase, Fletcher, Rogers, Harris Brisbane Dick, and Louis V. Bell Funds, Joseph Pulitzer Bequest, and The Annenberg Fund Inc. Gift, 1975",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/44858,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.26,false,true,45698,Asian Art,Hanging scroll,神農・夏冬山水図|Winter Landscape,Japan,Edo period (1615–1868),,,,Artist,,Kano Tan'yū,"Japanese, 1602–1674",,Kano Tan'yū,Japanese,1602,1674,1662,1662,1662,One from a set of three hanging scrolls; ink and color on silk,44 1/2 x 15 1/2 in. (113 x 39.4 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45698,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.27,false,true,45699,Asian Art,Hanging scroll,神農・夏冬山水図|Portrait of Emperor Shennong,Japan,Edo period (1615–1868),,,,Artist,,Kano Tan'yū,"Japanese, 1602–1674",,Kano Tan'yū,Japanese,1602,1674,1665,1665,1665,One from a set of three hanging scrolls; ink and color on silk,41 1/2 x 18 3/16 in. (105.4 x 46.2 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45699,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.28,false,true,45700,Asian Art,Hanging scroll,神農・夏冬山水図|Summer Landscape,Japan,Edo period (1615–1868),,,,Artist,,Kano Tan'yū,"Japanese, 1602–1674",,Kano Tan'yū,Japanese,1602,1674,1662,1662,1662,One from a set of three hanging scrolls; ink and color on silk,44 1/2 x 15 1/2 in. (113 x 39.4 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45700,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2006.174,false,true,73193,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Kano Tan'yū,"Japanese, 1602–1674",,Kano Tan'yū,Japanese,1602,1674,1635–45,1635,1645,Hanging scroll; ink on paper,Image: 40 x 9 1/2 in. (101.6 x 24.1 cm) Overall with mounting: 70 1/2 x 10 in. (179.1 x 25.4 cm) Overall with rollers: W. 12 in. (30.5 cm),"The Miriam and Ira D. Wallach Foundation Fund, 2006",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/73193,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1996.464.1,false,true,44297,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,Kano artist After,Kano Tan'yū,"Japanese, 1602–1674",,Kano Tan'yū,Japanese,1602,1674,dated 1666,1666,1666,Hanging scroll mounted as a panel; ink and color on paper,Image: 33 1/8 x 16 1/4 in. (84.1 x 41.3 cm) Overall with mounting: 36 1/2 x 18 7/8 in. (92.7 x 47.9 cm) Framed: 37 3/4 x 19 7/8 in. (95.9 x 50.5 cm),"Gift of John and Lili Bussel, 1996",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/44297,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1996.464.2,false,true,56098,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,Kano artist after,Kano Tan'yū,"Japanese, 1602–1674",,Kano Tan'yū,Japanese,1602,1674,dated 1666,1666,1666,Hanging scroll mounted as a panel; ink and color on paper,Image: 33 1/8 x 16 in. (84.1 x 40.6 cm) Overall with mounting: 36 1/2 x 18 5/8 in. (92.7 x 47.3 cm) Framed: 37 7/16 x 19 7/8 in. (95.1 x 50.5 cm),"Gift of John and Lili Bussel, 1996",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/56098,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.61.61,false,true,54704,Asian Art,Album leaf,,Japan,Edo period (1615–1868),,,,Artist,,Kano Tan'yū,"Japanese, 1602–1674",,Kano Tan'yū,Japanese,1602,1674,ca. 1650,1640,1650,Album leaf; ink on paper,8 3/8 x 10 3/4 in. (21.3 x 27.3 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/54704,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.61.64,false,true,54707,Asian Art,Album leaf,,Japan,Edo period (1615–1868),,,,Artist,,Kano Tan'yū,"Japanese, 1602–1674",,Kano Tan'yū,Japanese,1602,1674,ca. 1650,1640,1660,Album leaf; ink and color on silk,10 x 14 in. (25.4 x 35.6 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/54707,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.268.49,false,true,45418,Asian Art,Hanging scroll,月夜山水図|Landscape in Moonlight,Japan,Edo period (1615–1868),,,,Artist,,Kano Tan'yū,"Japanese, 1602–1674",,Kano Tan'yū,Japanese,1602,1674,after 1662,1663,1674,One of a triptych of hanging scrolls; ink on silk,Image: 39 5/8 x 16 3/4 in. (100.6 x 42.5 cm) Overall: 75 3/8 x 23 1/2 in. (191.5 x 59.7 cm),"The Harry G. C. Packard Collection of Asian Art, Gift of Harry G. C. Packard, and Purchase, Fletcher, Rogers, Harris Brisbane Dick, and Louis V. Bell Funds, Joseph Pulitzer Bequest, and The Annenberg Fund Inc. Gift, 1975",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45418,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.268.50,false,true,45419,Asian Art,Hanging scroll,月夜山水図|Landscape in Moonlight,Japan,Edo period (1615–1868),,,,Artist,,Kano Tan'yū,"Japanese, 1602–1674",,Kano Tan'yū,Japanese,1602,1674,after 1662,1663,1674,One of a triptych of hanging scrolls; ink on silk,Image: 39 5/8 x 16 3/4 in. (100.6 x 42.5 cm) Overall: 75 1/4 x 23 1/2 in. (191.1 x 59.7 cm),"The Harry G. C. Packard Collection of Asian Art, Gift of Harry G. C. Packard, and Purchase, Fletcher, Rogers, Harris Brisbane Dick, and Louis V. Bell Funds, Joseph Pulitzer Bequest, and The Annenberg Fund Inc. Gift, 1975",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45419,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.268.51,false,true,45420,Asian Art,Hanging scroll,月夜山水図|Landscape in Moonlight,Japan,Edo period (1615–1868),,,,Artist,,Kano Tan'yū,"Japanese, 1602–1674",,Kano Tan'yū,Japanese,1602,1674,after 1662,1663,1674,One of a triptych of hanging scrolls; ink on silk,Image: 39 5/8 x 16 3/4 in. (100.6 x 42.5 cm) Overall: 75 1/4 x 23 1/2 in. (191.1 x 59.7 cm),"The Harry G. C. Packard Collection of Asian Art, Gift of Harry G. C. Packard, and Purchase, Fletcher, Rogers, Harris Brisbane Dick, and Louis V. Bell Funds, Joseph Pulitzer Bequest, and The Annenberg Fund Inc. Gift, 1975",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45420,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.268.52,false,true,49098,Asian Art,Handscroll,狩野探幽筆 『画苑』|Famous Themes for Painting Study Known as “The Garden of Painting” (Gaen),Japan,Edo period (1615–1868),,,,Artist,,Kano Tan'yū,"Japanese, 1602–1674",,Kano Tan'yū,Japanese,1602,1674,1670,1600,1670,One of a pair of handscrolls; ink on paper,10 7/8 in. x 10 ft. 7 5/8 in. (27.6 x 324.2 cm),"The Harry G. C. Packard Collection of Asian Art, Gift of Harry G. C. Packard, and Purchase, Fletcher, Rogers, Harris Brisbane Dick, and Louis V. Bell Funds, Joseph Pulitzer Bequest, and The Annenberg Fund Inc. Gift, 1975",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49098,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.268.53,false,true,49099,Asian Art,Handscroll,狩野探幽筆 『画苑』|Famous Themes for Painting Study Known as “The Garden of Painting” (Gaen),Japan,Edo period (1615–1868),,,,Artist,,Kano Tan'yū,"Japanese, 1602–1674",,Kano Tan'yū,Japanese,1602,1674,1670,1670,1670,Handscroll; ink on paper,10 7/8 in. x 11 ft. 7 7/16 in. (27.6 x 354.2 cm),"The Harry G. C. Packard Collection of Asian Art, Gift of Harry G. C. Packard, and Purchase, Fletcher, Rogers, Harris Brisbane Dick, and Louis V. Bell Funds, Joseph Pulitzer Bequest, and The Annenberg Fund Inc. Gift, 1975",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49099,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2014.768.1,false,true,77184,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Tosa Mitsuoki,"Japanese, 1617–1691",,Tosa Mitsuoki,Japanese,1617,1691,mid- to late 17th century,1634,1699,Hanging scroll; color on silk,Image: 46 5/8 × 22 3/16 in. (118.5 × 56.3 cm) Overall with knobs: 69 × 28 1/16 in. (175.2 × 71.2 cm) Overall with mounting: 69 × 26 in. (175.2 × 66.1 cm),"Fishbein-Bender Collection, Gift of T. Richard Fishbein and Estelle P. Bender, 2014",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/77184,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -61.63,false,true,57239,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Kano Tsunenobu,"Japanese, 1636–1713",,Kano Tsunenobu,Japanese,1636,1713,late 17th–early 18th century,1667,1713,One of a triptych of hanging scrolls; ink and color on paper,31 x 11 3/8 in. (78.7 x 28.9 cm),"Purchase, Gifts, Bequests, and Funds from various donors, by exchange, 1961",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/57239,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -26.146,false,true,44897,Asian Art,Handscroll,,Japan,Edo period (1615–1868),,,,Artist,,Kano Tsunenobu,"Japanese, 1636–1713",,Kano Tsunenobu,Japanese,1636,1713,17th–18th century,1636,1713,Handscroll; ink and color on paper,12 3/4 in. × 16 ft. 1 1/4 in. (32.4 × 490.9 cm),"Purchase, Fletcher Fund and J. Pierpont Morgan Gift, 1926",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/44897,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.88,false,true,45721,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,Attributed to,Kano Tsunenobu,"Japanese, 1636–1713",,Kano Tsunenobu,Japanese,1636,1713,17th–18th century,1636,1713,Hanging scroll; ink and color on silk,35 3/4 x 13 in. (90.8 x 33 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45721,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.89,false,true,45722,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,Attributed to,Kano Tsunenobu,"Japanese, 1636–1713",,Kano Tsunenobu,Japanese,1636,1713,17th–18th century,1636,1713,Hanging scroll; ink and color on silk,16 3/4 x 28 1/2 in. (42.5 x 72.4 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45722,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.268.55,false,true,45042,Asian Art,Hanging scroll,四愛図|Four Admirers,Japan,Edo period (1615–1868),,,,Artist,,Kano Tsunenobu,"Japanese, 1636–1713",,Kano Tsunenobu,Japanese,1636,1713,late 17th–early 18th century,1667,1713,Hanging scroll; ink and color on silk,Image: 21 5/16 x 46 in. (54.1 x 116.8 cm) Overall with mounting: 67 5/8 x 55 1/2 in. (171.8 x 141 cm),"The Harry G. C. Packard Collection of Asian Art, Gift of Harry G. C. Packard, and Purchase, Fletcher, Rogers, Harris Brisbane Dick, and Louis V. Bell Funds, Joseph Pulitzer Bequest, and The Annenberg Fund Inc. Gift, 1975",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45042,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.1398a–j,false,true,45723,Asian Art,Album leaves,十鷹書画冊|Album of Hawks and Calligraphy,Japan,Edo period (1615–1868),,,,Artist,,Kano Tsunenobu,"Japanese, 1636–1713",,Kano Tsunenobu,Japanese,1636,1713,17th–18th century,1636,1713,Album of ten paintings; ink and color on silk,Each leaf: 10 3/4 x 9 7/16 in. (27.3 x 23.9 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45723,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.45,false,true,45734,Asian Art,Hanging scroll,粟に鶉図|Quail and Millet,Japan,Edo period (1615–1868),,,,Artist,,Kiyohara Yukinobu,"Japanese, 1643–1682",,Kiyohara Yukinobu,Japanese,1643,1682,late 17th century,1667,1682,Hanging scroll; ink and color on silk,46 5/8 x 18 3/4 in. (118.4 x 47.6 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45734,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2012.522.2,false,true,77204,Asian Art,Hanging scroll,"小川に連雀桜竹図|Waxwings, Cherry Blossoms, and Bamboo by a Stream",Japan,Edo period (1615–1868),,,,Artist,,Kiyohara Yukinobu,"Japanese, 1643–1682",,Kiyohara Yukinobu,Japanese,1643,1682,late 17th century,1667,1682,Hanging scroll; ink and color on silk,Image: 39 1/8 x 16 3/8 in. (99.4 x 41.6 cm) Overall with knobs: 23 1/2 in. (59.7 cm) Overall with mounting: 71 7/8 x 21 9/16 in. (182.5 x 54.7 cm),"Fishbein-Bender Collection, Gift of T. Richard Fishbein and Estelle P. Bender, 2012",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/77204,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.33,false,true,45733,Asian Art,Hanging scroll,地蔵菩薩像|Jizō Bosatsu,Japan,Edo period (1615–1868),,,,Artist,,Hanabusa Itchō,"Japanese, 1652–1724",,Hanabusa Itchō,Japanese,1652,1724,1667–98,1667,1698,"Hanging scroll; ink, color, and gold on paper",24 3/4 x 10 1/2 in. (62.9 x 26.7 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45733,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.23,false,true,45724,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Tanshin Morimasa,"Japanese, 1653–1718",,Tanshin Morimasa,Japanese,1653,1718,late 17th century,1667,1699,One of a triptych of hanging scrolls; ink and color on silk,60 x 30 1/4 in. (152.4 x 76.8 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45724,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.24,false,true,45725,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Tanshin Morimasa,"Japanese, 1653–1718",,Tanshin Morimasa,Japanese,1653,1718,late 17th century,1667,1699,One of a triptych of hanging scrolls; ink and color on silk,59 1/4 x 30 1/4 in. (150.5 x 76.8 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45725,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.25,false,true,45726,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Tanshin Morimasa,"Japanese, 1653–1718",,Tanshin Morimasa,Japanese,1653,1718,17th–18th century,1653,1718,One of a triptych of hanging scrolls; ink and color on silk,58 3/4 x 30 1/4 in. (149.2 x 76.8 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45726,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.78,false,true,45727,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,Attributed to,Tanshin Morimasa,"Japanese, 1653–1718",,Tanshin Morimasa,Japanese,1653,1718,17th–18th century,1653,1718,Hanging scroll; ink and color on silk,37 13/16 x 15 13/16 in. (96 x 40.1 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45727,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.526,false,true,40347,Asian Art,Folding fan mounting as a hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,Attributed to,Ogata Kōrin,"Japanese, 1658–1716",,Kōrin,Japanese,1658,1716,17th–18th century,1658,1716,"Folding fan mounting as a hanging scroll; ink, color, and gold on paper",6 3/4 x 20 3/4 in. (17.2 x 52.7 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/40347,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.6,false,true,45728,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Kano Chikanobu,"Japanese, 1660–1728",,Kano Chikanobu,Japanese,1660,1728,17th–18th century,1660,1728,Hanging scroll; ink and color on silk,40 1/4 x 17 5/8 in. (102.2 x 44.8 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45728,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.7,false,true,45730,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Kano Chikanobu,"Japanese, 1660–1728",,Kano Chikanobu,Japanese,1660,1728,17th–18th century,1660,1728,Hanging scroll; ink and color on silk,Image: 50 1/4 × 21 1/4 in. (127.6 × 54 cm) Overall with mounting: 85 1/8 × 26 1/8 in. (216.2 × 66.4 cm) Overall with knobs: 85 1/8 × 28 1/2 in. (216.2 × 72.4 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45730,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -53.118.1,false,true,48908,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Kano Chikanobu,"Japanese, 1660–1728",,Kano Chikanobu,Japanese,1660,1728,17th–18th century,1660,1728,Hanging scroll; ink and color on paper Reverse side: ink and color on silk,40 x 13 9/16 in. (101.6 x 34.4 cm),"Rogers Fund, 1953",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/48908,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.512,false,true,40354,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,In the Style of,Ogata Kenzan,"Japanese, 1663–1743",,Ogata Kenzan,Japanese,1663,1743,dated 1741,1741,1741,Hanging scroll; ink and color on paper,12 x 17 3/4 in. (30.5 x 45.1 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/40354,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.268.65,false,true,45275,Asian Art,Hanging scroll,定家詠十二ヶ月和歌花鳥図『拾遺愚草』より四月|“Fourth Month” from Fujiwara no Teika’s “Birds and Flowers of the Twelve Months”,Japan,Edo period (1615–1868),,,,Artist,,Ogata Kenzan,"Japanese, 1663–1743",,Ogata Kenzan,Japanese,1663,1743,1743,1743,1743,Hanging scroll; ink and color on paper,Image: 6 5/16 x 8 15/16 in. (16 x 22.7 cm) Overall with mounting: 43 1/4 x 19 in. (109.9 x 48.3 cm) Overall with knobs: 43 1/4 x 20 5/8 in. (109.9 x 52.4 cm),"The Harry G. C. Packard Collection of Asian Art, Gift of Harry G. C. Packard, and Purchase, Fletcher, Rogers, Harris Brisbane Dick, and Louis V. Bell Funds, Joseph Pulitzer Bequest, and The Annenberg Fund Inc. Gift, 1975",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45275,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.268.66,false,true,45276,Asian Art,Hanging scroll,尾形乾山筆 定家詠十二ヶ月和歌花鳥図「拾遺愚草』より六月|“Sixth Month” from Fujiwara no Teika’s “Birds and Flowers of the Twelve Months”,Japan,Edo period (1615–1868),,,,Artist,,Ogata Kenzan,"Japanese, 1663–1743",,Ogata Kenzan,Japanese,1663,1743,1743,1743,1743,Hanging scroll; ink and color on paper,Image: 6 1/4 x 9 1/8 in. (15.9 x 23.2 cm) Overall with mounting: 43 1/4 x 19 in. (109.9 x 48.3 cm) Overall with knobs: 43 1/4 x 20 5/8 in. (109.9 x 52.4 cm),"The Harry G. C. Packard Collection of Asian Art, Gift of Harry G. C. Packard, and Purchase, Fletcher, Rogers, Harris Brisbane Dick, and Louis V. Bell Funds, Joseph Pulitzer Bequest, and The Annenberg Fund Inc. Gift, 1975",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45276,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.268.67,false,true,45076,Asian Art,Hanging scroll,蔦紅葉図|Autumn Ivy,Japan,Edo period (1615–1868),,,,Artist,,Ogata Kenzan,"Japanese, 1663–1743",,Ogata Kenzan,Japanese,1663,1743,after 1732,1733,1743,"Album leaf mounted as a hanging scroll; ink, color, and gold on paper",Image: 8 3/8 x 10 7/8 in. (21.3 x 27.6 cm) Overall: 44 7/8 x 22 1/4 in. (114 x 56.5 cm),"The Harry G. C. Packard Collection of Asian Art, Gift of Harry G. C. Packard, and Purchase, Fletcher, Rogers, Harris Brisbane Dick, and Louis V. Bell Funds, Joseph Pulitzer Bequest, and The Annenberg Fund Inc. Gift, 1975",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45076,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.58,false,true,49085,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Ogawa Haritsu (Ritsuō),"Japanese, 1663–1747",,Ritsuō,Japanese,1663,1747,1740,1700,1800,Hanging scroll; ink and color on silk,37 1/4 x 12 1/4 in. (94.6 x 31.1 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49085,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.73,false,true,49084,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Yokoya Sōmin,"Japanese, 1669–1733",,Yokoya Sōmin,Japanese,1669,1733,early 18th century,1700,1733,Hanging scroll; ink on paper,Image: 27 1/8 x 8 1/8 in. (68.9 x 20.6 cm) Overall with mounting: 60 1/2 x 12 15/16 in. (153.7 x 32.9 cm) Overall with knobs: 60 1/2 x 14 13/16 in. (153.7 x 37.6 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49084,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.24,false,true,45376,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Kano Furunobu,"Japanese, 1696–1731",,Kano Furunobu,Japanese,1696,1731,early 18th century,1700,1731,Hanging scroll; ink and color on silk,19 1/2 x 41 1/8 in. (49.5 x 104.5 cm),"Gift of August Belmont, 1976",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45376,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.268.97,false,true,45352,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Yosa Buson,"Japanese, 1716–1783",,Yosa Buson,Japanese,1716,1783,ca. 1780,1770,1783,"Hanging scroll, color on satin",14 3/16 x 9 15/16 in. (36.1 x 25.2 cm),"The Harry G. C. Packard Collection of Asian Art, Gift of Harry G. C. Packard, and Purchase, Fletcher, Rogers, Harris Brisbane Dick, and Louis V. Bell Funds, Joseph Pulitzer Bequest, and The Annenberg Fund Inc. Gift, 1975",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45352,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2011.572.1,false,true,73348,Asian Art,Hanging scroll,曽我蕭白筆 寿老人図|The God of Good Fortune Jurōjin,Japan,Edo period (1615–1868),,,,Artist,,Soga Shōhaku,"Japanese, 1730–1781",,Soga Shōhaku,Japanese,1730,1781,mid- to late 18th century,1734,1799,Hanging scroll; ink and color on paper,Image: 52 3/4 x 22 5/16 in. (134 x 56.7 cm) Overall with mounting: 81 1/2 x 28 1/2 in. (207 x 72.4 cm) Overall with knobs: 81 1/2 x 30 3/4 in. (207 x 78.1 cm),"Fishbein-Bender Collection, Gift of T. Richard Fishbein and Estelle P. Bender, 2011",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/73348,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.268.71,false,true,49031,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Maruyama Ōkyo,"Japanese, 1733–1795",,Maruyama Ōkyo,Japanese,1733,1795,1769,1769,1769,Hanging scroll; ink on paper,14 x 22 5/16 in. (35.5 x 56.6 cm),"The Harry G. C. Packard Collection of Asian Art, Gift of Harry G. C. Packard, and Purchase, Fletcher, Rogers, Harris Brisbane Dick, and Louis V. Bell Funds, Joseph Pulitzer Bequest, and The Annenberg Fund Inc. Gift, 1975",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49031,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2000.245,false,true,54038,Asian Art,Hanging scroll,宋紫山筆 嵐に鶏図|Rooster in a Storm,Japan,Edo period (1615–1868),,,,Artist,,Sō Shizan,"Japanese, 1733–1805",,Sō Shizan,Japanese,1733,1805,1783,1783,1783,"Hanging scroll; ink, color, and gold on silk",38 7/8 x 17 1/4 in. (98.7 x 43.8 cm),"Friends of Asian Art, Purchase, The Dillon Fund Gift, in honor of Wen C. Fong, 2000",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/54038,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.268.103,false,true,45426,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Okada Beisanjin,"Japanese, 1744–1820",,Okada Beisanjin,Japanese,1744,1820,1817,1817,1817,Hanging scroll; ink and color on paper,Image: 70 1/8 x 35 5/8 in. (178.1 x 90.5 cm) Overall: 91 x 43 1/4in. (231.1 x 109.9 cm),"The Harry G. C. Packard Collection of Asian Art, Gift of Harry G. C. Packard, and Purchase, Fletcher, Rogers, Harris Brisbane Dick, and Louis V. Bell Funds, Joseph Pulitzer Bequest, and The Annenberg Fund Inc. Gift, 1975",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45426,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.268.99,false,true,45393,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Noro Kaiseki,"Japanese, 1747–1828",,Noro Kaiseki,Japanese,1747,1828,1822,1822,1822,Hanging scroll; ink and color on silk,Image: 53 3/4 x 26 7/8 in. (136.5 x 68.3 cm) Overall: 86 3/8 x 36 7/8in. (219.4 x 93.7 cm),"The Harry G. C. Packard Collection of Asian Art, Gift of Harry G. C. Packard, and Purchase, Fletcher, Rogers, Harris Brisbane Dick, and Louis V. Bell Funds, Joseph Pulitzer Bequest, and The Annenberg Fund Inc. Gift, 1975",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45393,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.268.134,false,true,49083,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Shokusanjin (Ōta Nanpo),"Japanese, 1749–1823",,,Japanese,1749,1823,1820,1820,1820,Hanging scroll; ink on silk,38 1/8 x 13 5/16 in. (96.8 x 33.8 cm),"The Harry G. C. Packard Collection of Asian Art, Gift of Harry G. C. Packard, and Purchase, Fletcher, Rogers, Harris Brisbane Dick, and Louis V. Bell Funds, Joseph Pulitzer Bequest, and The Annenberg Fund Inc. Gift, 1975",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49083,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.21,false,true,49061,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Ganku,"Japanese, 1749–1838",,Ganku,Japanese,1749,1838,dated 1790,1615,1868,Hanging scroll; ink and color on silk,38 3/4 x 13 3/4 in. (98.4 x 34.9 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49061,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2011.572.2a, b",false,true,77168,Asian Art,Hanging scrolls,鶴図|Cranes,Japan,Edo period (1615–1868),,,,Artist,,Nagasawa Rosetsu,"Japanese, 1754–1799",,Nagasawa Rosetsu,Japanese,1754,1799,1780s,1780,1789,Pair of hanging scrolls; ink and color on paper,Image (each scroll): 61 7/16 x 35 7/8 in. (156 x 91.2 cm) Overall with mounting (each scroll): 84 x 36 3/4 in. (213.4 x 93.3 cm) Overall with knobs (each scroll): 84 x 39 1/4 in. (213.4 x 99.7 cm),"Fishbein-Bender Collection, Gift of T. Richard Fishbein and Estelle P. Bender, 2011",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/77168,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.9,false,true,45398,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,Attributed to,Chōbunsai Eishi,"Japanese, 1756–1829",,Chōbunsai Eishi,Japanese,1756,1829,late 18th–19th century,1767,1829,Hanging scroll; ink and color on silk,12 5/8 x 27 5/8 in. (32.1 x 70.2 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45398,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.49,false,true,45800,Asian Art,Painting,,Japan,Edo period (1615–1868),,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",,Kubo Shunman,Japanese,1757,1820,dated 1780,1615,1868,Ink and color on silk,29 7/8 x 11 11/16 in. (75.9 x 29.7 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45800,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2014.727,false,true,675695,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",,Kubo Shunman,Japanese,1757,1820,ca. 1810,1810,1810,Hanging scroll; ink and color on silk,36 × 12 3/4 in. (91.4 × 32.4 cm),"Gift of Sebastian Izzard and Masaharu Nagano, in memory of T. Richard Fishbein, 2014",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/675695,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.268.68,false,true,48981,Asian Art,Handscroll,,Japan,Edo period (1615–1868),,,,Artist,After,Sakai Hōitsu,"Japanese, 1761–1828",,Sakai Hōitsu,Japanese,1761,1828,1815,1815,1815,Handscroll; ink and color on paper,13 3/8 in. x 30 ft. 4 3/16 in. (33.9 x 925 cm),"The Harry G. C. Packard Collection of Asian Art, Gift of Harry G. C. Packard, and Purchase, Fletcher, Rogers, Harris Brisbane Dick, and Louis V. Bell Funds, Joseph Pulitzer Bequest, and The Annenberg Fund Inc. Gift, 1975",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/48981,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.51,false,true,49003,Asian Art,Hanging scroll,牡丹に孔雀図|Peacocks and Peonies,Japan,Edo period (1615–1868),,,,Artist,,Tani Bunchō,"Japanese, 1763–1840",,Tani Bunchō,Japanese,1763,1840,1820,1810,1830,Hanging scroll; ink and color on silk,Image: 60 1/4 × 34 3/4 in. (153 × 88.2 cm) Overall with mounting: 79 15/16 × 41 3/4 in. (203 × 106 cm) Overall with knobs: 79 15/16 × 46 3/4 in. (203 × 118.8 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49003,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.268.113,false,true,45395,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Tani Bunchō,"Japanese, 1763–1840",,Tani Bunchō,Japanese,1763,1840,1791,1791,1791,Hanging scroll; ink and color on paper,Image: 52 15/16 in. × 11 in. (134.5 × 27.9 cm) Overall with mounting: 81 7/8 × 16 3/16 in. (208 × 41.1 cm) Overall with knobs: 81 7/8 × 18 9/16 in. (208 × 47.1 cm),"The Harry G. C. Packard Collection of Asian Art, Gift of Harry G. C. Packard, and Purchase, Fletcher, Rogers, Harris Brisbane Dick, and Louis V. Bell Funds, Joseph Pulitzer Bequest, and The Annenberg Fund Inc. Gift, 1975",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45395,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.86,false,true,39629,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,Attributed to,Utagawa Toyokuni I,"Japanese, 1769–1825",,Utagawa Toyokuni I,Japanese,1769,1825,ca. 1795,1695,1895,Hanging scroll; ink and color on silk,34 3/4 x 10 1/2 in. (88.3 x 26.7 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/39629,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.268.107,false,true,49024,Asian Art,Hanging scroll,観瀑山水図|Landscape with Waterfall,Japan,Edo period (1615–1868),,,,Artist,,Nakabayashi Chikutō,"Japanese, 1776–1853",,Nakabayashi Chikutō,Japanese,1776,1853,1841,1841,1841,Hanging scroll; ink on paper,Image: 61 15/16 x 34 3/16 in. (157.3 x 86.8 cm) Overall with mounting: 111 1/4 x 41 15/16 in. (282.5 x 106.5 cm) Overall with knobs: 111 1/4 x 45 1/16 in. (282.5 x 114.5 cm),"The Harry G. C. Packard Collection of Asian Art, Gift of Harry G. C. Packard, and Purchase, Fletcher, Rogers, Harris Brisbane Dick, and Louis V. Bell Funds, Joseph Pulitzer Bequest, and The Annenberg Fund Inc. Gift, 1975",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49024,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.268.108,false,true,49026,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Nakabayashi Chikutō,"Japanese, 1776–1853",,Nakabayashi Chikutō,Japanese,1776,1853,ca. 1840,1830,1850,Hanging scroll; ink on paper,51 3/4 x 23 11/16 in. (131.5 x 60.2 cm),"The Harry G. C. Packard Collection of Asian Art, Gift of Harry G. C. Packard, and Purchase, Fletcher, Rogers, Harris Brisbane Dick, and Louis V. Bell Funds, Joseph Pulitzer Bequest, and The Annenberg Fund Inc. Gift, 1975",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49026,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.268.105,false,true,48998,Asian Art,Handscroll,,Japan,Edo period (1615–1868),,,,Artist,,Okada Hankō,"Japanese, 1782–1846",,Okada Hankō,Japanese,1782,1846,1831,1831,1831,Handscroll; ink and color on paper,6 3/4 in. × 12 ft. 7 5/16 in. (17.2 × 384.3 cm),"The Harry G. C. Packard Collection of Asian Art, Gift of Harry G. C. Packard, and Purchase, Fletcher, Rogers, Harris Brisbane Dick, and Louis V. Bell Funds, Joseph Pulitzer Bequest, and The Annenberg Fund Inc. Gift, 1975",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/48998,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1995.247,false,true,44285,Asian Art,Handscroll,,Japan,Edo period (1615–1868),,,,Artist,,Yamamoto Baiitsu,"Japanese, 1783–1856",,Yamamoto Baiitsu,Japanese,1783,1783,dated 1847,1847,1847,Handscroll; ink and color on silk,Image: 8 1/4 x 95 in. (21 x 241.3 cm),"Purchase, Friends of Asian Art Gifts, 1995",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/44285,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1984.346,false,true,45430,Asian Art,Hanging scroll,三樹坡曉色图|View of the Kamo River from Sanbogi at Dawn,Japan,Edo period (1615–1868),,,,Artist,,Oda Kaisen,"Japanese, 1785–1862",,Oda Kaisen,Japanese,1785,1862,1829,1829,1829,Hanging scroll; ink and color on paper,Image: 26 x 9 1/8 in. (66 x 23.2 cm) Overall with mounting: 43 5/8 x 28 3/4 in. (110.8 x 73 cm) Overall with rollers: 32 1/2 in. (82.6 cm),"Purchase, Bequest of John L. Cadwalader, Gift of Mrs. Russell Sage, and Charles Sewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, by exchange, 1984",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45430,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.23,false,true,75265,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,1836,1836,1836,Hanging scroll: ink and color on silk,Image: 35 1/4 x 13 5/8 in. (89.5 x 34.6 cm) Overall with mounting: 67 3/4 x 17 1/4 in. (172.1 x 43.8 cm) Overall with knobs: 19 1/4 x 67 3/4 in. (48.9 x 172.1 cm),"Purchase, Friends of Asian Art Gifts, 2009",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/75265,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.98,false,true,55295,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,Attributed to,Yosai,"Japanese, 1788–1878",,Yosai,Japanese,1788,1878,dated 1867,1867,1867,Hanging scroll; ink and color on silk,46 5/8 x 15 1/2 in. (118.4 x 39.4 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/55295,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2001.611,false,true,61278,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Ōtagaki Rengetsu,"Japanese, 1791–1871",,Ōtagaki Rengetsu,Japanese,1791,1871,1868,1868,1868,Hanging scroll; ink and color on paper,11 3/8 x 49 1/8 in. (28.9 x 124.8 cm),"Gift of Professor Donald Keene, 2001",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/61278,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -57.156.7,false,true,44854,Asian Art,Handscroll,,Japan,Edo period (1615–1868),,,,Artist,,Ukita Ikkei,"Japanese, 1795–1859",,Ukita Ikkei,Japanese,1795,1859,ca. 1858,1848,1859,Handscroll; ink and color on paper,11 3/4 in. x 25 ft. 6 in. (29.8 x 777.2 cm),"Rogers Fund, 1957",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/44854,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.268.123,false,true,45332,Asian Art,Folding fan,墨竹扇面図|Bamboo and Rocks by a Stream,Japan,Edo period (1615–1868),,,,Artist,,Takaku Aigai,"Japanese, 1796–1843",,Takaku Aigai,Japanese,1796,1843,1832,1832,1832,"Folding fan; ink on paper, wood ribs",Overall: 11 5/16 x 18 in. (28.7 x 45.7 cm),"The Harry G. C. Packard Collection of Asian Art, Gift of Harry G. C. Packard, and Purchase, Fletcher, Rogers, Harris Brisbane Dick, and Louis V. Bell Funds, Joseph Pulitzer Bequest, and The Annenberg Fund Inc. Gift, 1975",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45332,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2012.522.3,false,true,77202,Asian Art,Hanging scroll,芥子図|Poppies,Japan,Edo period (1615–1868),,,,Artist,,Suzuki Kiitsu,"Japanese, 1796–1858",,Suzuki Kiitsu,Japanese,1796,1858,mid-19th century,1834,1866,Hanging scroll; ink and color on silk,Image: 38 9/16 x 13 1/8 in. (98 x 33.3 cm) Overall with knobs: 40 13/16 x 21 in. (103.7 x 53.3 cm) Overall with mounting: 40 13/16 x 18 11/16 in. (103.7 x 47.5 cm),"Fishbein-Bender Collection, Gift of T. Richard Fishbein and Estelle P. Bender, 2012",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/77202,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.268.117,false,true,49010,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Tsubaki Chinzan,"Japanese, 1801–1854",,Tsubaki Chinzan,Japanese,1801,1854,dated Fall 1850,1850,1850,Hanging scroll; ink and color on paper,Image: 43 3/8 x 17 1/2 in. (110.2 x 44.4 cm) Overall: 75 3/8 x 30 3/8in. (191.5 x 77.2 cm),"The Harry G. C. Packard Collection of Asian Art, Gift of Harry G. C. Packard, and Purchase, Fletcher, Rogers, Harris Brisbane Dick, and Louis V. Bell Funds, Joseph Pulitzer Bequest, and The Annenberg Fund Inc. Gift, 1975",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49010,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.268.120,false,true,49014,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Tsubaki Chinzan,"Japanese, 1801–1854",,Tsubaki Chinzan,Japanese,1801,1854,1854,1854,1854,Hanging scroll; ink and color on paper,51 1/8 x 11 5/16 in. (129.9 x 28.8 cm),"The Harry G. C. Packard Collection of Asian Art, Gift of Harry G. C. Packard, and Purchase, Fletcher, Rogers, Harris Brisbane Dick, and Louis V. Bell Funds, Joseph Pulitzer Bequest, and The Annenberg Fund Inc. Gift, 1975",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49014,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.268.121,false,true,49016,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Tsubaki Chinzan,"Japanese, 1801–1854",,Tsubaki Chinzan,Japanese,1801,1854,1854,1854,1854,Hanging scroll; ink and color on paper,51 x 11 5/16 in. (129.5 x 28.8 cm),"The Harry G. C. Packard Collection of Asian Art, Gift of Harry G. C. Packard, and Purchase, Fletcher, Rogers, Harris Brisbane Dick, and Louis V. Bell Funds, Joseph Pulitzer Bequest, and The Annenberg Fund Inc. Gift, 1975",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49016,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.112,false,true,57197,Asian Art,Album leaf,,Japan,Edo period (1615–1868),,,,Artist,,Shibata Zeshin,"Japanese, 1807–1891",,Shibata Zeshin,Japanese,1807,1891,1882,1882,1882,Album leaf; lacquer on gold paper,4 3/4 x 3 1/2 in. (12.1 x 8.9 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/57197,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.113,false,true,57198,Asian Art,Album leaf,,Japan,Edo period (1615–1868),,,,Artist,,Shibata Zeshin,"Japanese, 1807–1891",,Shibata Zeshin,Japanese,1807,1891,1882,1882,1882,Album leaf; lacquer on silver paper,4 3/4 x 3 1/2 in. (12.1 x 8.9 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/57198,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.70b,false,true,45803,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,ca. 1801–4,1791,1814,Hanging scroll; ink and color on silk,Image: 40 3/4 x 12 1/2 in. (103.5 x 31.8 cm) Overall with mounting: 69 11/16 x 19 1/2 in. (177 x 49.5 cm) with ivory rollers (dia. 15/16 in.),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45803,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.172,false,true,72722,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Takahashi Sōhei,"Japanese, 1804?–?1835",,Takahashi Sōhei,Japanese,1804,1835,1827,1827,1827,Hanging scroll; ink on paper,Image: 53 3/8 x 16 3/8 in. (135.5 x 41.6 cm) Overall with mounting: 83 15/16 x 21 9/16 in. (213.2 x 54.8 cm) Overall with rollers: 83 15/16 x 25 in. (213.2 x 63.5 cm),"Gift of Gitter-Yelen Collection, 2005",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/72722,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.51,false,true,49000,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,Attributed to,Takahashi Sōhei,"Japanese, 1804?–?1835",,Takahashi Sōhei,Japanese,1804,1835,1832,1700,1900,Hanging scroll; ink and color on paper,48 1/8 x 11 5/8 in. (122.2 x 29.5 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49000,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2006.570,false,true,65586,Asian Art,Hanging scroll,"伝俵屋宗達筆 源氏物語図「宿木」|Scene from ""The Ivy"" (Yadorigi), chapter 49 of the Tale of Genji",Japan,Edo period (1615–1868),,,,Artist,Studio of,Tawaraya Sōtatsu,"Japanese, died ca. 1640",,Tawaraya Sōtatsu,Japanese,1540,1640,early 17th century,1615,1633,"Hanging scroll; ink, color, and gold on paper",10 x 21 3/4 in. (25.4 x 55.2 cm),"Gift of Chizuko and Frank Korn, in honor of Miyeko Murase, 2006",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/65586,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.32,false,true,45396,Asian Art,Hanging scroll,雪兎図|Painting the Eyes on a Snow Rabbit,Japan,Edo period (1615–1868),,,,Artist,,Isoda Koryūsai,"Japanese, 1735–ca. 1790",,Isoda Koryūsai,Japanese,1735,1790,ca. 1780,1770,1790,Hanging scroll; ink and color on silk,Image: 23 3/4 in. × 16 in. (60.3 × 40.6 cm) Overall with mounting: 62 1/4 in. × 23 in. (158.1 × 58.4 cm) Overall with knobs: 62 1/4 × 24 3/4 in. (158.1 × 62.9 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45396,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2011.525,false,true,76811,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Shunkōsai Hokushū,"Japanese, active 1808–32",,Shunkōsai Hokushū,Japanese,1808,1832,ca. 1812,1802,1822,Hanging scroll; ink and color on silk,Image: 26 x 13 1/2 in. (66 x 34.3 cm) Overall with mounting: 58 1/2 x 17 1/8 in. (148.6 x 43.5 cm),"Gift of Miki and Sebastian Izzard, in honor of James C. Y. Watt, 2011",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/76811,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2012.522.1,false,true,77198,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Soga Nichokuan,"Japanese, active mid-17th century",,Soga Nichokuan,Japanese,1600,1700,early 17th century,1615,1633,Hanging scroll; ink on paper,Image: 45 3/16 x 20 3/16 in. (114.8 x 51.2 cm) Overall with mounting: 81 5/16 x 21 1/8 in. (206.5 x 53.7 cm) Overall with knobs: 22 13/16 in. (58 cm),"Fishbein-Bender Collection, Gift of T. Richard Fishbein and Estelle P. Bender, 2012",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/77198,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -57.156.2,false,true,40450,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,Attributed to,Unkoku Tōetsu,"Japanese, active second half 17th century",,Unkoku Tōetsu,Japanese,1650,1699,late 17th century,1667,1699,Hanging scroll; ink on paper,50 3/4 x 18 3/4 in. (128.9 x 47.6 cm),"Seymour Fund, 1957",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/40450,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.30,false,true,45822,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,dated 1790,1790,1790,Hanging scroll; ink and color on silk,Image: 33 3/4 × 12 5/16 in. (85.8 × 31.3 cm) Overall with mounting: 67 5/16 × 12 5/16 in. (171 × 31.3 cm) Overall with knobs: 67 5/16 × 19 1/8 in. (171 × 48.5 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45822,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.34,false,true,48878,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,dated 1780,1780,1780,Hanging scroll; ink and color on silk,Image: 49 3/16 × 19 5/8 in. (125 × 49.8 cm) Overall with mounting: 90 3/16 × 28 1/16 in. (229 cm) Overall with knobs: 90 3/16 × 30 3/16 in. (229 × 76.7 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/48878,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.36,false,true,45811,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,dated 1847,1847,1847,Hanging scroll; ink and color on silk,Image: 31 3/4 × 12 1/2 in. (80.7 × 31.7 cm) Overall with mounting: 64 7/16 × 17 9/16 in. (163.7 × 44.6 cm) Overall with knobs: 64 7/16 × 19 5/16 in. (163.7 × 49 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45811,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.37,false,true,45818,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,dated 1847,1847,1847,Hanging scroll; ink and color on silk,Image: 23 1/4 × 11 7/8 in. (59.1 × 30.2 cm) Overall with mounting: 61 × 19 1/2 in. (154.9 × 49.6 cm) Overall with knobs: 61 in. × 21 5/16 in. (154.9 × 54.1 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45818,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.44,false,true,48885,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,dated 1790,1615,1868,Hanging scroll; ink and color on silk,12 3/4 x 22 1/2 in. (32.4 x 57.2 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/48885,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.56,false,true,45816,Asian Art,Hanging scroll,軍鶏図|Gamecocks,Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,dated 1838,1838,1838,Hanging scroll; ink and color on silk,Image: 21 3/4 × 33 7/16 in. (55.3 × 85 cm) Overall with mounting: 59 7/16 × 40 3/16 in. (151 × 102 cm) Overall with knobs: 59 7/16 × 63 3/8 in. (151 × 160.9 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45816,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.57,false,true,45819,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,1849,1615,1868,Hanging scroll; ink on paper,Image: 42 1/4 × 14 3/16 in. (107.3 × 36 cm) Overall with mounting: 72 5/8 × 18 1/2 in. (184.5 × 47 cm) Overall with knobs: 72 5/8 × 20 1/2 in. (184.5 × 52 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45819,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1991.151,false,true,44634,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1810,1800,1820,Fan mounted as hanging scroll; ink and color on paper,Image: 9 7/16 × 20 3/16 in. (24 × 51.3 cm) Overall with mounting: 54 3/4 × 28 3/4 in. (139 × 73 cm) Overall with knobs: 54 3/4 × 31 in. (139 × 78.7 cm),"Purchase, Friends of Asian Art Gifts, 1991",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/44634,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.1403,false,true,48880,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,1839,1839,1839,Hanging scroll; ink and color on paper,Image: 11 3/16 × 27 9/16 in. (28.4 × 70 cm) Overall with mounting: 45 3/8 × 30 7/16 in. (115.3 × 77.3 cm) Overall with knobs: 45 3/8 × 30 13/16 in. (115.3 × 78.2 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/48880,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.58.1–.25,false,true,50915,Asian Art,Album leaves,『画本葛飾振』|Picture Book in the Katsushika Style (Ehon Katsushika-buri),Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,ca. 1836,1826,1846,Album of twenty-five preparatory drawings (hanshita-e) for book illustrations; ink on paper,10 x 15 1/2 in. (25.4 x 39.4 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/50915,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.517,false,true,54978,Asian Art,Hanging scroll,地獄太夫図|The Hell Courtesan,Japan,Edo period (1615–1868),,,,Artist,,Seikei,"Japanese, active second half of the 19th century",,Seikei,Japanese,1850,1899,late 19th century,1868,1899,Hanging scroll; ink and color on silk,Image: 39 1/8 × 20 1/8 in. (99.4 × 51.1 cm) Overall with mounting: 81 1/4 × 26 1/4 in. (206.4 × 66.7 cm) Overall with knobs: 26 1/4 × 29 in. (66.7 × 73.7 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/54978,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1990.116,false,true,44614,Asian Art,Hanging scroll,法華経断簡|Segment of the Lotus Sutra (Hokekyō),Japan,Heian period (794–1185),,,,Artist,Attributed to,Kujō Kanezane,"Japanese, 1149–1207",,Kujo Kanezane,Japanese,1149,1207,12th century,1149,1185,"Hanging scroll; ink on colored paper decorated with cut gold (kirikane), sprinkled gold (sunago), and silver leaf",10 x 3 9/16 in. (25.4 x 9.1 cm),"Purchase, Friends of Asian Art Gifts, 1990",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/44614,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.268.4,false,true,45616,Asian Art,Handscroll,宗観筆 九曜秘暦|The Secrets of the Nine Luminaries (Kuyō hiryaku),Japan,Heian period (794–1185),,,,Artist,,Sōkan,"Japanese, active late 11th–early 12th century",,Sōkan,Japanese,1050,1150,1125,1125,1125,Handscroll; ink and color on paper,11 1/4 in. x 26 ft. 6 1/4 in. (28.5 x 808.3 cm),"The Harry G. C. Packard Collection of Asian Art, Gift of Harry G. C. Packard, and Purchase, Fletcher, Rogers, Harris Brisbane Dick, and Louis V. Bell Funds, Joseph Pulitzer Bequest, and The Annenberg Fund Inc. Gift, 1975",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45616,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -52.160.6a,false,true,56616,Asian Art,Fan mounted as an album leaf,,Japan,Meiji period (1868–1912),,,,Artist,,Shibata Zeshin,"Japanese, 1807–1891",,Shibata Zeshin,Japanese,1807,1891,late 19th century,1867,1891,Fan painting mounted as album leaf; tempera on paper,Overall: 13 x 23 5/8 in. (33 x 60 cm) Image: 6 3/8 x 19 1/2 in. (16.2 x 49.5 cm),"Purchase, Gifts, Bequests, and Funds from various donors, by exchange, 1952",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/56616,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -52.160.6b,false,true,75156,Asian Art,Fan mounted as an album leaf,,Japan,Meiji period (1868–1912),,,,Artist,,Shibata Zeshin,"Japanese, 1807–1891",,Shibata Zeshin,Japanese,1807,1891,late 19th century,1867,1891,Fan painting mounted as album leaf; tempera on paper,Overall: 13 x 23 5/8 in. (33 x 60 cm) Image: 7 7/16 x 20 5/8 in. (18.9 x 52.4 cm),"Purchase, Gifts, Bequests, and Funds from various donors, by exchange, 1952",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/75156,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -52.160.6c,false,true,75157,Asian Art,Fan mounted as an album leaf,,Japan,Meiji period (1868–1912),,,,Artist,,Shibata Zeshin,"Japanese, 1807–1891",,Shibata Zeshin,Japanese,1807,1891,late 19th century,1867,1891,Fan painting mounted as album leaf; tempera on paper,Overall: 13 x 23 5/8 in. (33 x 60 cm) Image: 7 1/2 x 20 5/8 in. (19.1 x 52.4 cm),"Purchase, Gifts, Bequests, and Funds from various donors, by exchange, 1952",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/75157,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -52.160.6d,false,true,75158,Asian Art,Fan mounted as an album leaf,,Japan,Meiji period (1868–1912),,,,Artist,,Shibata Zeshin,"Japanese, 1807–1891",,Shibata Zeshin,Japanese,1807,1891,late 19th century,1867,1891,Fan painting mounted as album leaf; tempera on paper,Overall: 13 x 23 5/8 in. (33 x 60 cm) Image: 7 x 20 7/8 in. (17.8 x 53 cm),"Purchase, Gifts, Bequests, and Funds from various donors, by exchange, 1952",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/75158,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -52.160.6e,false,true,75159,Asian Art,Fan mounted as an album leaf,,Japan,Meiji period (1868–1912),,,,Artist,,Shibata Zeshin,"Japanese, 1807–1891",,Shibata Zeshin,Japanese,1807,1891,late 19th century,1867,1891,Fan painting mounted as album leaf; tempera on paper,Overall: 13 x 23 5/8 in. (33 x 60 cm) Image: 7 1/2 x 20 5/8 in. (19.1 x 52.4 cm),"Purchase, Gifts, Bequests, and Funds from various donors, by exchange, 1952",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/75159,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -52.160.6f,false,true,75160,Asian Art,Fan mounted as an album leaf,,Japan,Meiji period (1868–1912),,,,Artist,,Shibata Zeshin,"Japanese, 1807–1891",,Shibata Zeshin,Japanese,1807,1891,late 19th century,1867,1891,Fan painting mounted as album leaf; tempera on paper,Overall: 13 x 23 5/8 in. (33 x 60 cm) Image: 7 9/16 x 20 3/4 in. (19.2 x 52.7 cm),"Purchase, Gifts, Bequests, and Funds from various donors, by exchange, 1952",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/75160,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -52.160.6g,false,true,75161,Asian Art,Fan mounted as an album leaf,,Japan,Meiji period (1868–1912),,,,Artist,,Shibata Zeshin,"Japanese, 1807–1891",,Shibata Zeshin,Japanese,1807,1891,late 19th century,1867,1891,Fan painting mounted as album leaf; tempera on paper,Overall: 13 x 23 5/8 in. (33 x 60 cm) Image: 7 7/16 x 20 3/4 in. (18.9 x 52.7 cm),"Purchase, Gifts, Bequests, and Funds from various donors, by exchange, 1952",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/75161,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -52.160.6h,false,true,75162,Asian Art,Fan mounted as an album leaf,,Japan,Meiji period (1868–1912),,,,Artist,,Shibata Zeshin,"Japanese, 1807–1891",,Shibata Zeshin,Japanese,1807,1891,late 19th century,1867,1891,Fan painting mounted as album leaf; tempera on paper,Overall: 13 x 23 5/8 in. (33 x 60 cm) Image: 6 7/8 x 20 1/8 in. (17.5 x 51.1 cm),"Purchase, Gifts, Bequests, and Funds from various donors, by exchange, 1952",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/75162,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -52.160.6i,false,true,75163,Asian Art,Fan mounted as an album leaf,,Japan,Meiji period (1868–1912),,,,Artist,,Shibata Zeshin,"Japanese, 1807–1891",,Shibata Zeshin,Japanese,1807,1891,late 19th century,1867,1891,Fan painting mounted as album leaf; tempera on paper,Overall: 13 x 23 5/8 in. (33 x 60 cm) Image: 7 1/4 x 20 1/8 in. (18.4 x 51.1 cm),"Purchase, Gifts, Bequests, and Funds from various donors, by exchange, 1952",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/75163,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -52.160.6j,false,true,75164,Asian Art,Fan mounted as an album leaf,,Japan,Meiji period (1868–1912),,,,Artist,,Shibata Zeshin,"Japanese, 1807–1891",,Shibata Zeshin,Japanese,1807,1891,late 19th century,1867,1891,Fan painting mounted as album leaf; tempera on paper,Overall: 13 x 23 5/8 in. (33 x 60 cm) Image: 7 1/2 x 20 7/8 in. (19.1 x 53 cm),"Purchase, Gifts, Bequests, and Funds from various donors, by exchange, 1952",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/75164,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -52.160.6k,false,true,75165,Asian Art,Fan mounted as an album leaf,,Japan,Meiji period (1868–1912),,,,Artist,,Shibata Zeshin,"Japanese, 1807–1891",,Shibata Zeshin,Japanese,1807,1891,late 19th century,1867,1891,Fan painting mounted as album leaf; tempera on paper,Overall: 13 x 23 5/8 in. (33 x 60 cm) Image: 7 1/2 x 20 1/2 in. (19.1 x 52.1 cm),"Purchase, Gifts, Bequests, and Funds from various donors, by exchange, 1952",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/75165,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -52.160.6l,false,true,75166,Asian Art,Fan mounted as an album leaf,,Japan,Meiji period (1868–1912),,,,Artist,,Shibata Zeshin,"Japanese, 1807–1891",,Shibata Zeshin,Japanese,1807,1891,late 19th century,1867,1891,Fan painting mounted as album leaf; tempera on paper,Overall: 13 x 23 5/8 in. (33 x 60 cm) Image: 7 x 20 1/4 in. (17.8 x 51.4 cm),"Purchase, Gifts, Bequests, and Funds from various donors, by exchange, 1952",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/75166,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.100,false,true,57195,Asian Art,Hanging scroll,旭日図|Rising Sun,Japan,Meiji period (1868–1912),,,,Artist,,Shibata Zeshin,"Japanese, 1807–1891",,Shibata Zeshin,Japanese,1807,1891,second half of the 19th century,1850,1891,Hanging scroll; ink and color on silk,31 1/2 x 9 1/4 in. (80 x 23.5 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/57195,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.101,false,true,57196,Asian Art,Hanging scroll,,Japan,Meiji period (1868–1912),,,,Artist,,Shibata Zeshin,"Japanese, 1807–1891",,Shibata Zeshin,Japanese,1807,1891,dated 1890,1890,1890,Mounted and hanging scroll; ink on paper,14 1/8 x 18 5/8 in. (35.9 x 57.3 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/57196,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.102,false,true,57185,Asian Art,Painting,,Japan,Meiji period (1868–1912),,,,Artist,,Shibata Zeshin,"Japanese, 1807–1891",,Shibata Zeshin,Japanese,1807,1891,1882,1882,1882,Lacquer on paper,7 1/2 x 6 1/2 in. (19.1 x 16.5 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/57185,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.104,false,true,57187,Asian Art,Painting,,Japan,Meiji period (1868–1912),,,,Artist,,Shibata Zeshin,"Japanese, 1807–1891",,Shibata Zeshin,Japanese,1807,1891,1882,1882,1882,Lacquer on paper,7 1/2 x 6 1/2 in. (19.1 x 16.5 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/57187,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.105,false,true,57188,Asian Art,Painting,,Japan,Meiji period (1868–1912),,,,Artist,,Shibata Zeshin,"Japanese, 1807–1891",,Shibata Zeshin,Japanese,1807,1891,1882,1882,1882,Lacquer on paper,7 1/2 x 6 1/2 in. (19.1 x 16.5 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/57188,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.106,false,true,57189,Asian Art,Album leaf,,Japan,Meiji period (1868–1912),,,,Artist,,Shibata Zeshin,"Japanese, 1807–1891",,Shibata Zeshin,Japanese,1807,1891,1882,1882,1882,"Album leaf; ink, color, and lacquer on paper",7 1/2 x 6 1/2 in. (19.1 x 16.5 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/57189,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.107,false,true,57190,Asian Art,Painting,,Japan,Meiji period (1868–1912),,,,Artist,,Shibata Zeshin,"Japanese, 1807–1891",,Shibata Zeshin,Japanese,1807,1891,1882,1882,1882,Lacquer on paper,7 1/2 x 6 1/2 in. (19.1 x 16.5 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/57190,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.108,false,true,57191,Asian Art,Painting,,Japan,Meiji period (1868–1912),,,,Artist,,Shibata Zeshin,"Japanese, 1807–1891",,Shibata Zeshin,Japanese,1807,1891,1882,1882,1882,Lacquer on paper,7 1/2 x 6 1/2 in. (19.1 x 16.5 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/57191,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.109,false,true,57192,Asian Art,Painting,,Japan,Meiji period (1868–1912),,,,Artist,,Shibata Zeshin,"Japanese, 1807–1891",,Shibata Zeshin,Japanese,1807,1891,1882,1882,1882,Lacquer on paper,7 1/2 x 6 1/2 in. (19.1 x 16.5 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/57192,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.110,false,true,57193,Asian Art,Painting,,Japan,Meiji period (1868–1912),,,,Artist,,Shibata Zeshin,"Japanese, 1807–1891",,Shibata Zeshin,Japanese,1807,1891,1882,1882,1882,Lacquer and gold on paper,7 1/2 x 6 1/2 in. (19.1 x 16.5 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/57193,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.111,false,true,57194,Asian Art,Painting,,Japan,Meiji period (1868–1912),,,,Artist,,Shibata Zeshin,"Japanese, 1807–1891",,Shibata Zeshin,Japanese,1807,1891,dated 1881,1881,1881,Lacquer and mother-of-pearl fragments,7 1/2 x 6 1/2 in. (19.1 x 16.5 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/57194,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.268.136,false,true,57161,Asian Art,Hanging scroll,,Japan,Meiji period (1868–1912),,,,Artist,,Shibata Zeshin,"Japanese, 1807–1891",,Shibata Zeshin,Japanese,1807,1891,dated 1879,1879,1879,"Hanging scroll; colored lacquer with mother-of-pearl, gold and ink on paper",11 3/8 x 16 1/8 in. (28.9 x 41 cm),"The Harry G. C. Packard Collection of Asian Art, Gift of Harry G. C. Packard, and Purchase, Fletcher, Rogers, Harris Brisbane Dick, and Louis V. Bell Funds, Joseph Pulitzer Bequest, and The Annenberg Fund Inc. Gift, 1975",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/57161,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.77,false,true,57180,Asian Art,Hanging scroll,,Japan,Meiji period (1868–1912),,,,Artist,Attributed to,Tazaki Soun,"Japanese, 1815–1898",,Tazaki Soun,Japanese,1815,1898,dated 1827,1827,1827,Hanging scroll; ink and color on silk,38 3/8 x 13 in. (97.5 x 33 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/57180,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1981.26,false,true,57168,Asian Art,Hanging scroll,,Japan,Meiji period (1868–1912),,,,Artist,,Taki Katei,"Japanese, 1830–1901",,Taki Katei,Japanese,1830,1901,dated January 1896,1896,1896,Hanging scroll; ink and color on silk,43 1/16 x 16 3/8 in. (109.4 x 41.6 cm),"Gift of Daniel Slott, 1981",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/57168,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.138,false,true,57238,Asian Art,Painting,,Japan,Meiji period (1868–1912),,,,Artist,Attributed to,Kawanabe Kyōsai,"Japanese, 1831–1889",,Kawanabe Kyōsai,Japanese,1831,1889,late 19th century,1867,1889,Ink on paper,8 x 5 in. (20.3 x 12.7 cm),"Gift of F. Tikotin, 1937",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/57238,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -57.70.4,false,true,50771,Asian Art,Hanging scroll,,Japan,Meiji period (1868–1912),,,,Artist,,Kawanabe Kyōsai,"Japanese, 1831–1889",,Kawanabe Kyōsai,Japanese,1831,1889,after 1888,1888,1912,Hanging scroll; ink and color on silk,38 7/8 x 14 1/4 in. (98.7 x 36.2 cm),"Gift of Edward M. Bratter, 1957",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/50771,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.119.2,false,true,57229,Asian Art,Handscroll,,Japan,Meiji period (1868–1912),,,,Artist,,Kawanabe Kyōsai,"Japanese, 1831–1889",,Kawanabe Kyōsai,Japanese,1831,1889,"late 19th century, before 1870",1868,1869,Handscroll; ink on paper,17 ft 3 1/2 in. x 10 3/4 in. (516.9 x 27.3 cm),"Fletcher Fund, 1937",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/57229,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.119.5,false,true,57231,Asian Art,Wash drawing,,Japan,Meiji period (1868–1912),,,,Artist,,Kawanabe Kyōsai,"Japanese, 1831–1889",,Kawanabe Kyōsai,Japanese,1831,1889,late 19th century,1867,1889,On paper,9 1/8 x 13 1/4 in. (23.2 x 33.7 cm),"Fletcher Fund, 1937",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/57231,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.119.6,false,true,57233,Asian Art,Wash drawing,,Japan,Meiji period (1868–1912),,,,Artist,,Kawanabe Kyōsai,"Japanese, 1831–1889",,Kawanabe Kyōsai,Japanese,1831,1889,late 19th century,1867,1889,Ink on paper,6 x 10 1/4 in. (15.2 x 26 cm),"Fletcher Fund, 1937",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/57233,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.64a,false,true,54604,Asian Art,Hanging scroll,猿を襲う鷲図|Eagle Attacking a Monkey,Japan,Meiji period (1868–1912),,,,Artist,,Kawanabe Kyōsai,"Japanese, 1831–1889",,Kawanabe Kyōsai,Japanese,1831,1889,1885,1885,1885,Hanging scroll; ink and color on paper,Image: 65 1/2 x 33 in. (166.4 x 83.8 cm) Overall with mounting: 111 1/2 x 43 1/2 in. (283.2 x 110.5 cm) Overall with knobs: 111 1/2 x 47 1/2 in. (283.2 x 120.7 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/54604,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.64b,false,true,54605,Asian Art,Hanging scroll,,Japan,Meiji period (1868–1912),,,,Artist,,Kawanabe Kyōsai,"Japanese, 1831–1889",,Kawanabe Kyōsai,Japanese,1831,1889,1885,1885,1885,Hanging scroll; ink and color on paper,Image: 65 1/2 x 33 in. (166.4 x 83.8 cm) Overall with mounting: 111 1/2 x 43 1/2 in. (283.2 x 110.5 cm) Overall with knobs: 111 1/2 x 47 1/2 in. (283.2 x 120.7 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/54605,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.64c,false,true,54606,Asian Art,Hanging scroll,,Japan,Meiji period (1868–1912),,,,Artist,,Kawanabe Kyōsai,"Japanese, 1831–1889",,Kawanabe Kyōsai,Japanese,1831,1889,1885,1885,1885,Hanging scroll; ink and color on paper,Image: 65 1/2 x 33 in. (166.4 x 83.8 cm) Overall with mounting: 111 1/2 x 43 1/2 in. (283.2 x 110.5 cm) Overall with knobs: 111 1/2 x 43 1/2 in. (283.2 x 110.5 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/54606,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.64d,false,true,54607,Asian Art,Hanging scroll,兎を追う鷲図|Eagle Pursuing Rabbit,Japan,Meiji period (1868–1912),,,,Artist,,Kawanabe Kyōsai,"Japanese, 1831–1889",,Kawanabe Kyōsai,Japanese,1831,1889,1885,1885,1885,Hanging scroll; ink and color on paper,Image: 65 1/2 x 33 in. (166.4 x 83.8 cm) Overall with mounting: 111 1/2 x 43 1/2 in. (283.2 x 110.5 cm) Overall with knobs: 111 1/2 x 47 1/2 in. (283.2 x 120.7 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/54607,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.44,false,true,55378,Asian Art,Hanging scroll,,Japan,Meiji period (1868–1912),,,,Artist,,Kawanabe Kyōsai,"Japanese, 1831–1889",,Kawanabe Kyōsai,Japanese,1831,1889,late 19th century,1868,1889,Hanging scroll; ink on silk,49 3/8 x 19 1/2 in. (125.4 x 49.5 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/55378,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.61.1,false,true,54608,Asian Art,Album leaf,白衣観音図|White-Robed Kannon,Japan,Meiji period (1868–1912),,,,Artist,,Kawanabe Kyōsai,"Japanese, 1831–1889",,Kawanabe Kyōsai,Japanese,1831,1889,ca. 1887,1877,1897,Album leaf; ink and color on silk,14 3/8 x 11 in. (36.5 x 27.9 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/54608,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.61.2,false,true,54609,Asian Art,Album leaf,松に鴉図|Two Crows on a Pine Branch,Japan,Meiji period (1868–1912),,,,Artist,,Kawanabe Kyōsai,"Japanese, 1831–1889",,Kawanabe Kyōsai,Japanese,1831,1889,ca. 1887,1877,1897,Album leaf; ink and color on silk,14 5/16 x 10 3/4 in. (36.4 x 27.3 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/54609,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.61.3,false,true,54610,Asian Art,Album leaf,富士図|Mount Fuji,Japan,Meiji period (1868–1912),,,,Artist,,Kawanabe Kyōsai,"Japanese, 1831–1889",,Kawanabe Kyōsai,Japanese,1831,1889,ca. 1887,1877,1897,Album leaf; ink and color on silk,14 1/8 x 10 3/4 in. (35.9 x 27.3 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/54610,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.61.4,false,true,54611,Asian Art,Album leaf,旭に群鴉図|Flock of Crows at Dawn,Japan,Meiji period (1868–1912),,,,Artist,,Kawanabe Kyōsai,"Japanese, 1831–1889",,Kawanabe Kyōsai,Japanese,1831,1889,ca. 1887,1877,1897,Album leaf; ink and color on silk,14 3/8 x 10 1/4 in. (36.5 x 26.0 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/54611,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.61.5,false,true,54612,Asian Art,Album leaf,,Japan,Meiji period (1868–1912),,,,Artist,,Kawanabe Kyōsai,"Japanese, 1831–1889",,Kawanabe Kyōsai,Japanese,1831,1889,ca. 1887,1877,1897,Album leaf; ink and color on silk,14 1/4 x 10 3/4 in. (36.2 x 27.3 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/54612,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.61.6,false,true,54613,Asian Art,Album leaf,滝に燕図|Swallows by a Waterfall,Japan,Meiji period (1868–1912),,,,Artist,,Kawanabe Kyōsai,"Japanese, 1831–1889",,Kawanabe Kyōsai,Japanese,1831,1889,ca. 1887,1877,1897,Album leaf; ink and color on silk,14 5/16 x 10 1/2 in. (36.4 x 26.7 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/54613,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.61.7,false,true,54614,Asian Art,Album leaf,水辺に鴉図|Crow and Reeds by a Stream,Japan,Meiji period (1868–1912),,,,Artist,,Kawanabe Kyōsai,"Japanese, 1831–1889",,Kawanabe Kyōsai,Japanese,1831,1889,ca. 1887,1877,1897,Album leaf; ink and color on silk,14 1/4 x 10 3/4 in. (36.2 x 27.3 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/54614,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.61.8,false,true,54615,Asian Art,Album leaf,,Japan,Meiji period (1868–1912),,,,Artist,,Kawanabe Kyōsai,"Japanese, 1831–1889",,Kawanabe Kyōsai,Japanese,1831,1889,ca. 1887,1877,1897,Album leaf; ink and color on silk,14 1/4 x 10 5/8 in. (36.2 x 27 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/54615,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.61.9,false,true,54616,Asian Art,Album leaf,,Japan,Meiji period (1868–1912),,,,Artist,,Kawanabe Kyōsai,"Japanese, 1831–1889",,Kawanabe Kyōsai,Japanese,1831,1889,March 1888,1888,1888,Album leaf; ink and color on silk,14 1/4 x 10 7/8 in. (36.2 x 27.6 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/54616,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.61.10,false,true,54617,Asian Art,Album leaf,,Japan,Meiji period (1868–1912),,,,Artist,,Kawanabe Kyōsai,"Japanese, 1831–1889",,Kawanabe Kyōsai,Japanese,1831,1889,ca. 1887,1877,1897,Album leaf; ink and color on silk,14 1/8 x 10 3/4 in. (35.9 x 27.3 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/54617,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.61.11,false,true,54618,Asian Art,Album leaf,竹に鴉図|Crow on a Bamboo Branch,Japan,Meiji period (1868–1912),,,,Artist,,Kawanabe Kyōsai,"Japanese, 1831–1889",,Kawanabe Kyōsai,Japanese,1831,1889,ca. 1887,1877,1897,Album leaf; ink and color on silk,14 1/4 x 10 1/2 in. (36.2 x 26.7 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/54618,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.61.12,false,true,54619,Asian Art,Album leaf,,Japan,Meiji period (1868–1912),,,,Artist,,Kawanabe Kyōsai,"Japanese, 1831–1889",,Kawanabe Kyōsai,Japanese,1831,1889,ca. 1887,1877,1897,Album leaf; ink and color on silk,14 1/4 x 10 3/4 in. (36.2 x 27.3 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/54619,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.61.13,false,true,54620,Asian Art,Album leaf,,Japan,Meiji period (1868–1912),,,,Artist,,Kawanabe Kyōsai,"Japanese, 1831–1889",,Kawanabe Kyōsai,Japanese,1831,1889,ca. 1887,1877,1897,Album leaf; ink and color on silk,14 1/4 x 10 3/4 in. (36.2 x 27.3 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/54620,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.61.14,false,true,54621,Asian Art,Album leaf,木に鴉図|Crow on a Branch,Japan,Meiji period (1868–1912),,,,Artist,,Kawanabe Kyōsai,"Japanese, 1831–1889",,Kawanabe Kyōsai,Japanese,1831,1889,ca. 1887,1877,1897,Album leaf; ink and color on silk,14 1/4 x 10 3/4 in. (36.2 x 27.3 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/54621,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.61.15,false,true,54622,Asian Art,Album leaf,,Japan,Meiji period (1868–1912),,,,Artist,,Kawanabe Kyōsai,"Japanese, 1831–1889",,Kawanabe Kyōsai,Japanese,1831,1889,ca. 1887,1877,1897,Album leaf; ink and color on silk,14 1/4 x 11 1/4 in. (36.2 x 28.6 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/54622,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.61.16,false,true,45526,Asian Art,Album leaf,,Japan,Meiji period (1868–1912),,,,Artist,,Kawanabe Kyōsai,"Japanese, 1831–1889",,Kawanabe Kyōsai,Japanese,1831,1889,ca. 1887,1877,1897,Album leaf; ink and color on silk,14 1/4 x 10 1/2 in. (36.2 x 26.7 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45526,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.61.17,false,true,54623,Asian Art,Album leaf,,Japan,Meiji period (1868–1912),,,,Artist,,Kawanabe Kyōsai,"Japanese, 1831–1889",,Kawanabe Kyōsai,Japanese,1831,1889,ca. 1887,1877,1897,Album leaf; ink and color on silk,14 1/4 x 10 7/8 in. (36.2 x 27.6 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/54623,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.61.18,false,true,54624,Asian Art,Album leaf,,Japan,Meiji period (1868–1912),,,,Artist,,Kawanabe Kyōsai,"Japanese, 1831–1889",,Kawanabe Kyōsai,Japanese,1831,1889,ca. 1887,1877,1897,Album leaf; ink and color on silk,14 1/4 x 10 3/8 in. (36.2 x 26.4 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/54624,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.61.19,false,true,54625,Asian Art,Album leaf,,Japan,Meiji period (1868–1912),,,,Artist,,Kawanabe Kyōsai,"Japanese, 1831–1889",,Kawanabe Kyōsai,Japanese,1831,1889,ca. 1887,1877,1897,Album leaf; ink and color on silk,14 1/4 x 10 7/8 in. (36.2 x 27.6 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/54625,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.61.20,false,true,54626,Asian Art,Album leaf,,Japan,Meiji period (1868–1912),,,,Artist,,Kawanabe Kyōsai,"Japanese, 1831–1889",,Kawanabe Kyōsai,Japanese,1831,1889,ca. 1887,1877,1897,Album leaf; ink and color on silk,14 1/8 x 10 5/8 in. (35.9 x 27 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/54626,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.61.21,false,true,54627,Asian Art,Album leaf,岩に鴉図|Crow on a Rock,Japan,Meiji period (1868–1912),,,,Artist,,Kawanabe Kyōsai,"Japanese, 1831–1889",,Kawanabe Kyōsai,Japanese,1831,1889,ca. 1887,1877,1897,Album leaf; ink and color on silk,14 1/4 x 10 7/8 in. (36.2 x 27.6 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/54627,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.61.22,false,true,54628,Asian Art,Album leaf,,Japan,Meiji period (1868–1912),,,,Artist,,Kawanabe Kyōsai,"Japanese, 1831–1889",,Kawanabe Kyōsai,Japanese,1831,1889,ca. 1887,1877,1897,Album leaf; ink and color on silk,14 1/4 x 10 7/8 in. (36.2 x 27.6 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/54628,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.61.23,false,true,54629,Asian Art,Album leaf,瓜に鼠図|Mice in a Melon,Japan,Meiji period (1868–1912),,,,Artist,,Kawanabe Kyōsai,"Japanese, 1831–1889",,Kawanabe Kyōsai,Japanese,1831,1889,ca. 1887,1877,1897,Album leaf; ink and color on silk,14 1/4 x 10 7/8 in. (36.2 x 27.6 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/54629,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.61.24,false,true,54630,Asian Art,Album leaf,,Japan,Meiji period (1868–1912),,,,Artist,,Kawanabe Kyōsai,"Japanese, 1831–1889",,Kawanabe Kyōsai,Japanese,1831,1889,ca. 1887,1877,1897,Album leaf; ink and color on silk,14 1/4 x 10 1/2 in. (36.2 x 26.7 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/54630,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.61.25,false,true,54631,Asian Art,Album leaf,月に鴉図|Crow and the Moon,Japan,Meiji period (1868–1912),,,,Artist,,Kawanabe Kyōsai,"Japanese, 1831–1889",,Kawanabe Kyōsai,Japanese,1831,1889,ca. 1887,1877,1897,Album leaf; ink and color on silk,14 1/4 x 10 7/8 in. (36.2 x 27.6 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/54631,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.61.26,false,true,54632,Asian Art,Album leaf,木に鴉図|Crow on a Branch,Japan,Meiji period (1868–1912),,,,Artist,,Kawanabe Kyōsai,"Japanese, 1831–1889",,Kawanabe Kyōsai,Japanese,1831,1889,ca. 1887,1877,1897,Album leaf; ink and color on silk,14 1/4 x 10 3/4 in. (36.2 x 27.3 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/54632,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.61.27,false,true,54633,Asian Art,Album leaf,,Japan,Meiji period (1868–1912),,,,Artist,,Kawanabe Kyōsai,"Japanese, 1831–1889",,Kawanabe Kyōsai,Japanese,1831,1889,ca. 1887,1877,1897,Album leaf; ink and gold on silk,14 1/4 x 10 3/4 in. (36.2 x 27.3 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/54633,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.61.28,false,true,54634,Asian Art,Album leaf,,Japan,Meiji period (1868–1912),,,,Artist,,Kawanabe Kyōsai,"Japanese, 1831–1889",,Kawanabe Kyōsai,Japanese,1831,1889,ca. 1887,1877,1897,Album leaf; ink and red on silk,14 1/8 x 10 3/4 in. (35.9 x 27.3 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/54634,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.61.29,false,true,54635,Asian Art,Album leaf,ムクドリ図|Starlings on a Branch,Japan,Meiji period (1868–1912),,,,Artist,,Kawanabe Kyōsai,"Japanese, 1831–1889",,Kawanabe Kyōsai,Japanese,1831,1889,ca. 1887,1877,1897,Album leaf; ink and color on silk,14 1/4 x 10 1/2 in. (36.2 x 26.7 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/54635,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.61.30,false,true,54636,Asian Art,Album leaf,,Japan,Meiji period (1868–1912),,,,Artist,,Kawanabe Kyōsai,"Japanese, 1831–1889",,Kawanabe Kyōsai,Japanese,1831,1889,ca. 1887,1877,1897,Album leaf; ink and color on silk,14 1/4 x 10 1/2 in. (36.2 x 26.7 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/54636,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.61.31,false,true,54637,Asian Art,Album leaf,,Japan,Meiji period (1868–1912),,,,Artist,,Kawanabe Kyōsai,"Japanese, 1831–1889",,Kawanabe Kyōsai,Japanese,1831,1889,ca. 1887,1877,1897,Album leaf; ink and color on silk,14 1/2 x 10 1/2 in. (36.8 x 26.7 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/54637,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.61.32,false,true,54638,Asian Art,Album leaf,,Japan,Meiji period (1868–1912),,,,Artist,,Kawanabe Kyōsai,"Japanese, 1831–1889",,Kawanabe Kyōsai,Japanese,1831,1889,ca. 1887,1877,1897,Album leaf; ink and color on silk,14 1/4 x 10 3/4 in. (36.2 x 27.3 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/54638,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.61.33,false,true,54639,Asian Art,Album leaf,,Japan,Meiji period (1868–1912),,,,Artist,,Kawanabe Kyōsai,"Japanese, 1831–1889",,Kawanabe Kyōsai,Japanese,1831,1889,ca. 1887,1877,1897,Album leaf; ink and color on silk,14 1/4 x 10 3/4 in. (36.2 x 27.3 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/54639,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.61.34,false,true,54640,Asian Art,Album leaf,雪中鴉図|Crow Flying in the Snow,Japan,Meiji period (1868–1912),,,,Artist,,Kawanabe Kyōsai,"Japanese, 1831–1889",,Kawanabe Kyōsai,Japanese,1831,1889,ca. 1887,1877,1897,Album leaf; ink and color on silk,14 1/4 x 10 3/4 in. (36.2 x 27.3 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/54640,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.61.35,false,true,54641,Asian Art,Album leaf,,Japan,Meiji period (1868–1912),,,,Artist,,Kawanabe Kyōsai,"Japanese, 1831–1889",,Kawanabe Kyōsai,Japanese,1831,1889,ca. 1887,1877,1897,Album leaf; ink and color on silk,14 1/4 x 10 1/2 in. (36.2 x 26.7 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/54641,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.61.36,false,true,54642,Asian Art,Album leaf,柳に鴉図|Crow and Willow Tree,Japan,Meiji period (1868–1912),,,,Artist,,Kawanabe Kyōsai,"Japanese, 1831–1889",,Kawanabe Kyōsai,Japanese,1831,1889,November 1887,1887,1887,Album leaf; ink and color on silk,14 1/4 x 10 1/2 in. (36.2 x 26.7 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/54642,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.61.40,false,true,54646,Asian Art,Album leaf,,Japan,Meiji period (1868–1912),,,,Artist,,Hashimoto Gahō,"Japanese, 1835–1908",,Hashimoto Gahō,Japanese,1835,1908,ca. 1885–89,1885,1889,Album leaf; ink and color on silk,Image: 14 1/4 × 10 1/2 in. (36.2 × 26.7 cm) Mat: 22 7/8 × 15 1/2 in. (58.1 × 39.4 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/54646,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.61.47,false,true,54677,Asian Art,Album leaf,,Japan,Meiji period (1868–1912),,,,Artist,,Hashimoto Gahō,"Japanese, 1835–1908",,Hashimoto Gahō,Japanese,1835,1908,ca. 1885–89,1885,1889,Album leaf; ink and color on silk,Image: 14 1/4 × 10 3/8 in. (36.2 × 26.4 cm) Mat: 22 7/8 × 15 1/2 in. (58.1 × 39.4 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/54677,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.61.55,false,true,54696,Asian Art,Album leaf,,Japan,Meiji period (1868–1912),,,,Artist,,Hashimoto Gahō,"Japanese, 1835–1908",,Hashimoto Gahō,Japanese,1835,1908,ca. 1885–89,1885,1889,Album leaf; ink and color on silk,14 x 10 in. (35.6 x 25.4 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/54696,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.61.59,false,true,54701,Asian Art,Album leaf,,Japan,Meiji period (1868–1912),,,,Artist,,Hashimoto Gahō,"Japanese, 1835–1908",,Hashimoto Gahō,Japanese,1835,1908,ca. 1885–89,1885,1889,Album leaf; ink and color on silk,Image: 14 in. × 10 1/2 in. (35.6 × 26.7 cm) Mat: 22 7/8 × 15 1/2 in. (58.1 × 39.4 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/54701,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.61.73,false,true,50826,Asian Art,Album leaf,猫に蜘蛛図|Cat Watching a Spider,Japan,Meiji period (1868–1912),,,,Artist,,Ōide Tōkō,"Japanese, 1841–1905",,Ōide Tōkō,Japanese,1841,1905,ca. 1888–92,1888,1892,Album leaf; ink and color on silk,14 3/4 x 11 in. (37.5 x 27.9 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/50826,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -08.148.10,false,true,54476,Asian Art,Panel,,Japan,Meiji period (1868–1912),,,,Artist,,Kawabata Gyokushō,"Japanese, 1842–1913",,Kawabata Gyokushō,Japanese,1842,1913,20th century,1900,1912,Ink and color on silk,51 7/8 x 21 5/8 in. (131.8 x 54.9 cm),"Gift of Francis Lathrop, 1908",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/54476,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -08.148.11,false,true,54477,Asian Art,Panel,,Japan,Meiji period (1868–1912),,,,Artist,,Kawabata Gyokushō,"Japanese, 1842–1913",,Kawabata Gyokushō,Japanese,1842,1913,20th century,1900,1912,Ink and color on silk,50 7/16 x 19 11/16 in. (128.1 x 50 cm),"Gift of Francis Lathrop, 1908",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/54477,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.61.65,false,true,54708,Asian Art,Album leaf,,Japan,Meiji period (1868–1912),,,,Artist,,Kawabata Gyokushō,"Japanese, 1842–1913",,Kawabata Gyokushō,Japanese,1842,1913,1800,1800,1800,Album leaf; ink and color on silk,14 1/2 x 11 1/8 in. (36.8 x 28.3 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/54708,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.61.66,false,true,54709,Asian Art,Album leaf,,Japan,Meiji period (1868–1912),,,,Artist,,Kawabata Gyokushō,"Japanese, 1842–1913",,Kawabata Gyokushō,Japanese,1842,1913,1800,1800,1800,Album leaf; ink and color on silk,14 1/2 x 11 in. (36.8 x 27.9 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/54709,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.61.67,false,true,54710,Asian Art,Album leaf,,Japan,Meiji period (1868–1912),,,,Artist,,Kawabata Gyokushō,"Japanese, 1842–1913",,Kawabata Gyokushō,Japanese,1842,1913,1800,1800,1800,Album leaf; ink and color on silk,14 1/2 x 11 1/2 in. (36.8 x 29.2 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/54710,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.61.68,false,true,54711,Asian Art,Album leaf,,Japan,Meiji period (1868–1912),,,,Artist,,Kawabata Gyokushō,"Japanese, 1842–1913",,Kawabata Gyokushō,Japanese,1842,1913,1868,1868,1868,Album leaf; ink and color on silk,14 1/2 x 11 in. (36.8 x 27.9 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/54711,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.61.85,false,true,54745,Asian Art,Album leaf,,Japan,Meiji period (1868–1912),,,,Artist,,Kawabata Gyokushō,"Japanese, 1842–1913",,Kawabata Gyokushō,Japanese,1842,1913,1887–92,1887,1892,Album leaf; ink on silk,13 5/8 x 10 7/8 in. (34.6 x 27.6 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/54745,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.61.86,false,true,54746,Asian Art,Album leaf,,Japan,Meiji period (1868–1912),,,,Artist,,Kawabata Gyokushō,"Japanese, 1842–1913",,Kawabata Gyokushō,Japanese,1842,1913,1887–92,1887,1892,Album leaf; silk,13 1/2 x 11 in. (34.3 x 27.9 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/54746,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.61.87,false,true,54747,Asian Art,Album leaf,猫図|Cat Seen from Behind,Japan,Meiji period (1868–1912),,,,Artist,,Kawabata Gyokushō,"Japanese, 1842–1913",,Kawabata Gyokushō,Japanese,1842,1913,1868,1868,1868,Album leaf; ink and color on silk,13 1/2 x 10 7/8 in. (34.3 x 27.6 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/54747,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.61.88,false,true,54748,Asian Art,Album leaf,,Japan,Meiji period (1868–1912),,,,Artist,,Kawabata Gyokushō,"Japanese, 1842–1913",,Kawabata Gyokushō,Japanese,1842,1913,1887–92,1887,1892,Album leaf; silk,13 1/2 x 10 7/8 in. (34.3 x 27.6 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/54748,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.61.89,false,true,54749,Asian Art,Album leaf,狗児図|A Pair of Puppies,Japan,Meiji period (1868–1912),,,,Artist,,Kawabata Gyokushō,"Japanese, 1842–1913",,Kawabata Gyokushō,Japanese,1842,1913,1868,1868,1868,Album leaf; ink on silk,13 1/2 x 10 7/8 in. (34.3 x 27.6 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/54749,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.61.90,false,true,54750,Asian Art,Album leaf,,Japan,Meiji period (1868–1912),,,,Artist,,Kawabata Gyokushō,"Japanese, 1842–1913",,Kawabata Gyokushō,Japanese,1842,1913,1887–92,1887,1892,Album leaf; ink on silk,13 1/2 x 10 7/8 in. (34.3 x 27.6 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/54750,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.61.91,false,true,54751,Asian Art,Album leaf,,Japan,Meiji period (1868–1912),,,,Artist,,Kawabata Gyokushō,"Japanese, 1842–1913",,Kawabata Gyokushō,Japanese,1842,1913,1887–92,1887,1892,Album leaf; silk,13 1/2 x 10 7/8 in. (34.3 x 27.6 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/54751,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.61.92,false,true,54752,Asian Art,Album leaf,,Japan,Meiji period (1868–1912),,,,Artist,,Kawabata Gyokushō,"Japanese, 1842–1913",,Kawabata Gyokushō,Japanese,1842,1913,1887–92,1887,1892,Album leaf; silk,13 1/2 x 10 7/8 in. (34.3 x 27.6 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/54752,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.61.37,false,true,54643,Asian Art,Album leaf,,Japan,Meiji period (1868–1912),,,,Artist,,Watanabe Seitei,"Japanese, 1851–1918",,Watanabe Seitei,Japanese,1851,1918,ca. 1887,1877,1897,Album leaf; ink and color on silk,14 1/4 x 10 7/8 in. (36.2 x 27.6 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/54643,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.61.38,false,true,54644,Asian Art,Album leaf,桜に小禽図|Birds on a Flowering Branch,Japan,Meiji period (1868–1912),,,,Artist,,Watanabe Seitei,"Japanese, 1851–1918",,Watanabe Seitei,Japanese,1851,1918,ca. 1887,1877,1897,Album leaf; ink and color on silk,14 1/4 x 10 3/4 in. (36.2 x 27.3 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/54644,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.61.39,false,true,54645,Asian Art,Album leaf,,Japan,Meiji period (1868–1912),,,,Artist,,Watanabe Seitei,"Japanese, 1851–1918",,Watanabe Seitei,Japanese,1851,1918,ca. 1887,1877,1897,Album leaf; ink and color on silk,14 1/4 x 10 3/4 in. (36.2 x 27.3 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/54645,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.61.41,false,true,54671,Asian Art,Album leaf,,Japan,Meiji period (1868–1912),,,,Artist,,Watanabe Seitei,"Japanese, 1851–1918",,Watanabe Seitei,Japanese,1851,1918,ca. 1887,1877,1897,Album leaf; ink and color on silk,14 3/16 x 10 7/8 in. (36.0 x 27.6 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/54671,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.61.42,false,true,54672,Asian Art,Album leaf,,Japan,Meiji period (1868–1912),,,,Artist,,Watanabe Seitei,"Japanese, 1851–1918",,Watanabe Seitei,Japanese,1851,1918,ca. 1887,1877,1897,Album leaf; ink and color on silk,14 1/4 x 10 3/4 in. (36.2 x 27.3 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/54672,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.61.43,false,true,54673,Asian Art,Album leaf,,Japan,Meiji period (1868–1912),,,,Artist,,Watanabe Seitei,"Japanese, 1851–1918",,Watanabe Seitei,Japanese,1851,1918,ca. 1887,1877,1897,Album leaf; ink and color on silk,14 1/4 x 10 3/8 in. (36.2 x 26.4 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/54673,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.61.44,false,true,54674,Asian Art,Album leaf,,Japan,Meiji period (1868–1912),,,,Artist,,Watanabe Seitei,"Japanese, 1851–1918",,Watanabe Seitei,Japanese,1851,1918,ca. 1887,1877,1897,Album leaf; ink and color on silk,14 1/8 x 10 3/4 in. (35.9 x 27.3 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/54674,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.61.45,false,true,54675,Asian Art,Album leaf,,Japan,Meiji period (1868–1912),,,,Artist,,Watanabe Seitei,"Japanese, 1851–1918",,Watanabe Seitei,Japanese,1851,1918,ca. 1887,1877,1897,Album leaf; ink and color on silk,14 1/8 x 10 3/4 in. (35.9 x 27.3 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/54675,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.61.46,false,true,54676,Asian Art,Album leaf,,Japan,Meiji period (1868–1912),,,,Artist,,Watanabe Seitei,"Japanese, 1851–1918",,Watanabe Seitei,Japanese,1851,1918,ca. 1887,1877,1897,Album leaf; ink and color on silk,14 1/8 x 10 3/4 in. (35.9 x 27.3 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/54676,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.61.48,false,true,54684,Asian Art,Album leaf,,Japan,Meiji period (1868–1912),,,,Artist,,Watanabe Seitei,"Japanese, 1851–1918",,Watanabe Seitei,Japanese,1851,1918,ca. 1887,1877,1897,Album leaf; ink and color on silk,14 1/4 x 10 3/4 in. (36.2 x 27.3 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/54684,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.61.52,false,true,54692,Asian Art,Album leaf,,Japan,Meiji period (1868–1912),,,,Artist,,Watanabe Seitei,"Japanese, 1851–1918",,Watanabe Seitei,Japanese,1851,1918,ca. 1887,1877,1897,Album leaf; ink and color on silk,14 x 10 3/4 in. (35.6 x 27.3 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/54692,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.61.53,false,true,54694,Asian Art,Album leaf,,Japan,Meiji period (1868–1912),,,,Artist,,Watanabe Seitei,"Japanese, 1851–1918",,Watanabe Seitei,Japanese,1851,1918,ca. 1887,1877,1897,Album leaf; ink and color on silk,14 x 10 1/2 in. (35.6 x 26.7 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/54694,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.61.54,false,true,54695,Asian Art,Album leaf,,Japan,Meiji period (1868–1912),,,,Artist,,Watanabe Seitei,"Japanese, 1851–1918",,Watanabe Seitei,Japanese,1851,1918,ca. 1887,1877,1897,Album leaf; ink and color on silk,14 x 10 1/2 in. (35.6 x 26.7 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/54695,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.61.56,false,true,54698,Asian Art,Album leaf,,Japan,Meiji period (1868–1912),,,,Artist,,Watanabe Seitei,"Japanese, 1851–1918",,Watanabe Seitei,Japanese,1851,1918,ca. 1887,1877,1897,Album leaf; ink and color on silk,14 x 10 3/4 in. (35.6 x 27.3 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/54698,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.61.57,false,true,54699,Asian Art,Album leaf,,Japan,Meiji period (1868–1912),,,,Artist,,Watanabe Seitei,"Japanese, 1851–1918",,Watanabe Seitei,Japanese,1851,1918,ca. 1887,1877,1897,Album leaf; ink and color on silk,14 x 10 3/4 in. (35.6 x 27.3 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/54699,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.61.58,false,true,54700,Asian Art,Album leaf,,Japan,Meiji period (1868–1912),,,,Artist,,Watanabe Seitei,"Japanese, 1851–1918",,Watanabe Seitei,Japanese,1851,1918,ca. 1887,1877,1897,Album leaf; ink and color on silk,14 x 10 3/4 in. (35.6 x 27.3 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/54700,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.61.60,false,true,54703,Asian Art,Album leaf,,Japan,Meiji period (1868–1912),,,,Artist,,Watanabe Seitei,"Japanese, 1851–1918",,Watanabe Seitei,Japanese,1851,1918,ca. 1887,1877,1897,Album leaf; ink and color on silk,14 x 10 3/4 in. (35.6 x 27.3 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/54703,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.61.75,false,true,54735,Asian Art,Album leaf,,Japan,Meiji period (1868–1912),,,,Artist,,Watanabe Seitei,"Japanese, 1851–1918",,Watanabe Seitei,Japanese,1851,1918,ca. 1887,1877,1897,Album leaf; ink and color on silk,13 7/8 x 10 1/2 in. (35.2 x 26.7 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/54735,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.61.76,false,true,54736,Asian Art,Album leaf,,Japan,Meiji period (1868–1912),,,,Artist,,Watanabe Seitei,"Japanese, 1851–1918",,Watanabe Seitei,Japanese,1851,1918,ca. 1887,1877,1897,Album leaf; ink and color on silk,13 7/8 x 10 1/2 in. (35.2 x 26.7 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/54736,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.61.79,false,true,54739,Asian Art,Album leaf,,Japan,Meiji period (1868–1912),,,,Artist,,Watanabe Seitei,"Japanese, 1851–1918",,Watanabe Seitei,Japanese,1851,1918,ca. 1887,1877,1897,Album leaf; ink and color on silk,13 7/8 x 10 1/2 in. (35.2 x 26.7 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/54739,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.61.80,false,true,54740,Asian Art,Album leaf,,Japan,Meiji period (1868–1912),,,,Artist,,Watanabe Seitei,"Japanese, 1851–1918",,Watanabe Seitei,Japanese,1851,1918,ca. 1887,1877,1897,Album leaf; ink and color on silk,13 7/8 x 10 3/8 in. (35.2 x 26.4 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/54740,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.61.70,false,true,54714,Asian Art,Album leaf,,Japan,Meiji period (1868–1912),,,,Artist,,Seki Shūkō,"Japanese, 1858–1915",,Seki Shūkō,Japanese,1858,1915,"Summer, 1891",1891,1891,Album leaf; ink and color on silk,14 3/4 x 11 1/4 in. (37.5 x 28.6 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/54714,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.61.74,false,true,54734,Asian Art,Album leaf,,Japan,Meiji period (1868–1912),,,,Artist,,Seki Shūkō,"Japanese, 1858–1915",,Seki Shūkō,Japanese,1858,1915,ca. 1890–92,1890,1892,Album leaf; ink and color on silk,14 3/4 x 11 in. (37.5 x 27.9 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/54734,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.61.77,false,true,54737,Asian Art,Album leaf,,Japan,Meiji period (1868–1912),,,,Artist,,Seki Shūkō,"Japanese, 1858–1915",,Seki Shūkō,Japanese,1858,1915,1892,1892,1892,Album leaf; ink and color on silk,13 7/8 x 10 1/2 in. (35.2 x 26.7 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/54737,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.61.78,false,true,54738,Asian Art,Album leaf,,Japan,Meiji period (1868–1912),,,,Artist,,Seki Shūkō,"Japanese, 1858–1915",,Seki Shūkō,Japanese,1858,1915,1892,1892,1892,Album leaf; silk,13 7/8 x 10 1/2 in. (35.2 x 26.7 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/54738,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.61.81,false,true,54741,Asian Art,Album leaf,,Japan,Meiji period (1868–1912),,,,Artist,,Seki Shūkō,"Japanese, 1858–1915",,Seki Shūkō,Japanese,1858,1915,ca. 1890–92,1890,1892,Album leaf; silk,14 1/4 x 10 5/8 in. (36.2 x 27.0 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/54741,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.61.82,false,true,54742,Asian Art,Album leaf,,Japan,Meiji period (1868–1912),,,,Artist,,Seki Shūkō,"Japanese, 1858–1915",,Seki Shūkō,Japanese,1858,1915,ca. 1890–92,1890,1892,Album leaf; ink and color silk,14 1/4 x 10 1/2 in. (36.2 x 26.7 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/54742,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.61.83,false,true,54743,Asian Art,Album leaf,,Japan,Meiji period (1868–1912),,,,Artist,,Seki Shūkō,"Japanese, 1858–1915",,Seki Shūkō,Japanese,1858,1915,ca. 1890–92,1890,1892,Album leaf; ink and color silk,14 1/4 x 10 5/8 in. (36.2 x 27.0 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/54743,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.61.84,false,true,54744,Asian Art,Album leaf,,Japan,Meiji period (1868–1912),,,,Artist,,Seki Shūkō,"Japanese, 1858–1915",,Seki Shūkō,Japanese,1858,1915,ca. 1890–92,1890,1892,Album leaf; ink and color silk,14 1/4 x 10 5/8 in. (36.2 x 27.0 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/54744,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.61.93,false,true,54753,Asian Art,Album leaf,,Japan,Meiji period (1868–1912),,,,Artist,,Seki Shūkō,"Japanese, 1858–1915",,Seki Shūkō,Japanese,1858,1915,ca. 1890–92,1890,1892,Album leaf; silk,13 5/8 x 10 3/4 in. (34.6 x 27.3 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/54753,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.61.94,false,true,54754,Asian Art,Album leaf,,Japan,Meiji period (1868–1912),,,,Artist,,Seki Shūkō,"Japanese, 1858–1915",,Seki Shūkō,Japanese,1858,1915,ca. 1890–92,1890,1892,Album leaf; silk,13 1/2 x 10 7/8 in. (34.3 x 27.6 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/54754,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.61.95,false,true,54755,Asian Art,Album leaf,,Japan,Meiji period (1868–1912),,,,Artist,,Seki Shūkō,"Japanese, 1858–1915",,Seki Shūkō,Japanese,1858,1915,ca. 1890–92,1890,1892,Album leaf; silk,13 1/2 x 10 7/8 in. (34.3 x 27.6 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/54755,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.61.96,false,true,54756,Asian Art,Album leaf,,Japan,Meiji period (1868–1912),,,,Artist,,Seki Shūkō,"Japanese, 1858–1915",,Seki Shūkō,Japanese,1858,1915,ca. 1890–92,1890,1892,Album leaf; silk,13 5/8 x 10 7/8 in. (34.6 x 27.6 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/54756,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.61.97,false,true,54757,Asian Art,Album leaf,,Japan,Meiji period (1868–1912),,,,Artist,,Seki Shūkō,"Japanese, 1858–1915",,Seki Shūkō,Japanese,1858,1915,ca. 1890–92,1890,1892,Album leaf; silk,13 1/2 x 10 7/8 in. (34.3 x 27.6 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/54757,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.61.98,false,true,54758,Asian Art,Album leaf,,Japan,Meiji period (1868–1912),,,,Artist,,Seki Shūkō,"Japanese, 1858–1915",,Seki Shūkō,Japanese,1858,1915,ca. 1890–92,1890,1892,Album leaf; silk,13 1/2 x 10 3/4 in. (34.3 x 27.3 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/54758,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.61.99,false,true,54759,Asian Art,Album leaf,,Japan,Meiji period (1868–1912),,,,Artist,,Seki Shūkō,"Japanese, 1858–1915",,Seki Shūkō,Japanese,1858,1915,ca. 1890–92,1890,1892,Album leaf; silk,13 1/2 x 10 7/8 in. (34.3 x 27.6 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/54759,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.61.100,false,true,54760,Asian Art,Album leaf,,Japan,Meiji period (1868–1912),,,,Artist,,Seki Shūkō,"Japanese, 1858–1915",,Seki Shūkō,Japanese,1858,1915,ca. 1890–92,1890,1892,Album leaf; silk,13 1/2 x 10 7/8 in. (34.3 x 27.6 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/54760,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.61.69,false,true,54712,Asian Art,Album leaf,,Japan,Meiji period (1868–1912),,,,Artist,,Okada Baison,"Japanese, 1864–1913",,Okada Baison,Japanese,1864,1913,ca. 1891–92,1891,1892,Album leaf; ink and color on silk,14 3/4 x 11 1/2 in. (37.5 x 29.2 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/54712,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.61.71,false,true,54732,Asian Art,Album leaf,,Japan,Meiji period (1868–1912),,,,Artist,,Okada Baison,"Japanese, 1864–1913",,Okada Baison,Japanese,1864,1913,ca. 1891–92,1891,1892,Album leaf; ink and color on silk,14 5/8 x 11 in. (37.1 x 27.9 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/54732,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.61.72,false,true,54733,Asian Art,Album leaf,,Japan,Meiji period (1868–1912),,,,Artist,,Okada Baison,"Japanese, 1864–1913",,Okada Baison,Japanese,1864,1913,ca. 1891–92,1891,1892,Album leaf; ink and color on silk,14 5/8 x 11 in. (37.1 x 27.9 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/54733,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.510,false,true,75612,Asian Art,Hanging scroll,,Japan,Taishō period (1912–26),,,,Artist,,Fukuda Kodōjin,"Japanese, 1865–1944",,Fukuda Kodōjin,Japanese,1865,1944,1922,1922,1922,Hanging scroll; ink on paper,Image: 57 3/4 x 12 1/4 in. (146.7 x 31.1 cm) Overall with mounting: 81 x 17 5/16 in. (205.7 x 44 cm) Overall with knobs: 81 x 19 1/4 in. (205.7 x 48.9 cm),"Gift of the Gitter-Yelen Collection, 2009",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/75612,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -53.7.3,false,true,44849,Asian Art,Handscroll,"『妙法蓮華経』「観世音菩薩普門品」|“Universal Gateway,” Chapter 25 of the Lotus Sutra",Japan,Kamakura period (1185–1333),,,,Artist,Calligrapher:,Sugawara Mitsushige,"Japanese, active mid- 13th century",,Sugawara Mitsushige,Japanese,1234,1266,dated 1257,1257,1257,"Handscroll; ink, color, and gold on paper",Overall with mounting: 9 11/16 in. × 30 ft. 8 1/16 in. (24.6 × 934.9 cm),"Purchase, Louisa Eldridge McBurney Gift, 1953",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/44849,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.160.29,false,true,45372,Asian Art,Hanging scroll,玄奘三蔵像|Portrait of Xuanzang (Genjō) with Attendant,Japan,Kamakura period (1185–1333),,,,Artist,In the Style of,Kasuga Motomitsu,"Japanese, active early 11th century",,Kasuga Motomitsu,Japanese,1000,1099,14th century,1300,1333,Hanging scroll; ink and color on silk,Image: 48 3/4 x 29 1/4 in. (123.8 x 74.3 cm) Overall with mounting: 87 1/2 x 37 1/2 in. (222.3 x 95.3 cm) Overall with knobs: 87 1/2 x 39 1/2 in. (222.3 x 100.3 cm),"H. O. Havemeyer Collection, Gift of Horace Havemeyer, 1929",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45372,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.29,false,true,73645,Asian Art,Hanging scroll,芦葉達磨図|Bodhidharma Crossing the Yangzi River on a Reed,Japan,Momoyama period (1573–1615),,,,Artist,,Kano Sōshū,"Japanese, 1551–1601",,Kano Sōshū,Japanese,1551,1601,late 16th century,1567,1599,Hanging scroll; ink on paper,Image: 31 3/4 x 12 3/4 in. (80.6 x 32.4 cm) Overall with mounting: 64 x 17 1/2 in. (162.6 x 44.5 cm) Overall with knobs: 64 x 19 1/4 in. (162.6 x 48.9 cm),"Purchase, Friends of Asian Art Gifts, 2007",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/73645,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.11,false,true,54576,Asian Art,Hanging scroll,,Japan,Muromachi period (1392–1573),,,,Artist,Attributed to,Sesshū Tōyō,"Japanese, 1420–1506",,Sesshū Tōyō,Japanese,1420,1506,1480,1334,1573,Hanging scroll; ink on paper,39 1/4 x 16 in. (99.7 x 40.6 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/54576,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2002.3,false,true,63956,Asian Art,Hanging scroll,張騫図|The Chinese Explorer Zhang Qian on a Raft,Japan,Muromachi period (1392–1573),,,,Artist,,Maejima Sōyū,active mid-16th century,,Maejima Sōyū,Japanese,1536,1570,mid-16th century,1534,1566,Hanging scroll; ink on paper,20 5/16 x 13 11/16 in. (51.6 x 34.7cm),"Purchase, Mary Livingston Griggs and Mary Griggs Burke Foundation Gift, 2002",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/63956,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -53.123.1,false,true,45643,Asian Art,Hanging scroll,,Japan,Muromachi period (1392–1573),,,,Artist,Attributed to,Shubun,"Japanese, active ca. 1414",,Shubun,Japanese,1414,1414,first half of the 15th century,1400,1424,Hanging scroll; ink and color on paper,37 1/8 x 14 7/16 in. (94.3 x 36.7 cm),"Purchase, Gifts, Bequests, and Funds from various donors, by exchange, 1953",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45643,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -53.123.2,false,true,45644,Asian Art,Hanging scroll,,Japan,Muromachi period (1392–1573),,,,Artist,Attributed to,Shubun,"Japanese, active ca. 1414",,Shubun,Japanese,1414,1414,first half of the 15th century,1400,1424,Hanging scroll; ink and color on paper,36 3/8 x 14 7/16 in. (92.4 x 36.7 cm),"Purchase, Gifts, Bequests, and Funds from various donors, by exchange, 1953",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45644,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2001.385,false,true,60786,Asian Art,Hanging scroll,,Japan,Muromachi period (1392–1573),,,,Artist,,Kano Yukinobu,"Japanese, ca. 1513–1575",,Kano Yukinobu,Japanese,1513,1575,mid-16th century,1534,1566,Hanging scroll; ink and color on paper,18 3/8 x 15 3/4 in. (46.7 x 40 cm) Overall with mounting: 52 5/8 x 20 3/8 in. (133.7 x 51.8 cm),"Purchase, Friends of Asian Art Gifts, in honor of Douglas Dillon, 2001",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/60786,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.268.38,false,true,44857,Asian Art,Hanging scroll,蘭蕙同芳図|Orchids and Rock,Japan,Muromachi period (1392–1573),,,,Artist,,Gyokuen Bonpō,"Japanese, ca. 1348–after 1420",,Gyokuen Bonpo,Japanese,1348,1420,late 14th–early 15th century,1367,1433,Hanging scroll; ink on paper,39 9/16 x 13 1/8 in. (100.5 x 33.4 cm),"The Harry G. C. Packard Collection of Asian Art, Gift of Harry G. C. Packard, and Purchase, Fletcher, Rogers, Harris Brisbane Dick, and Louis V. Bell Funds, Joseph Pulitzer Bequest, and The Annenberg Fund Inc. Gift, 1975",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/44857,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.514,false,true,45326,Asian Art,Hanging scroll,ガマズミに山鵲図|Magpie on Viburnum Branch,Japan,Muromachi period (1392–1573),,,,Artist,,Genga,"Japanese, active early 16th century",", in Song tradition",Genga,Japanese,1500,1550,early 16th century,1500,1533,Hanging scroll; ink and color on paper,Image: 18 1/4 in. × 14 in. (46.3 × 35.5 cm) Overall with mounting: 53 1/2 × 19 1/2 in. (135.9 × 49.5 cm) Overall with knobs: 53 1/2 × 21 1/2 in. (135.9 × 54.6 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45326,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1977.172,false,true,44856,Asian Art,Hanging scroll,芦雁図|Reeds and Geese,Japan,Nanbokuchō period (1336–92),,,,Artist,,Tesshū Tokusai,"Japanese, died 1366",,Tesshū Tokusai,Japanese,1342,1366,"dated 11th month, 1343",1343,1343,One of a pair of hanging scrolls; ink on silk,Image: 43 7/16 x 17 5/16 in. (110.4 x 44 cm),"Purchase, Mrs. Jackson Burke Gift, 1977",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/44856,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.268.37,false,true,44855,Asian Art,Hanging scroll,芦雁図|Reeds and Geese,Japan,Nanbokuchō period (1336–92),,,,Artist,,Tesshū Tokusai,"Japanese, died 1366",,Tesshū Tokusai,Japanese,1342,1366,"dated 11th month, 1343",1343,1343,One of a pair of hanging scrolls; ink on silk,Image: 43 1/2 x 17 3/8 in. (110.5 x 44.1 cm) Overall: 71 3/4 x 20 1/4 in. (182.2 x 51.4 cm),"The Harry G. C. Packard Collection of Asian Art, Gift of Harry G. C. Packard, and Purchase, Fletcher, Rogers, Harris Brisbane Dick, and Louis V. Bell Funds, Joseph Pulitzer Bequest, and The Annenberg Fund Inc. Gift, 1975",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/44855,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.508,false,true,45630,Asian Art,Hanging scroll,,Japan,Nanbokuchō period (1336–92),,,,Artist,In the Style of,Toba Sōjō,"Japanese, 1053–1140",,Toba Sōjō,Japanese,1053,1140,14th century,1336,1392,"Hanging scroll; ink, color, and gold on silk",Image: 32 1/2 × 20 9/16 in. (82.6 × 52.2 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45630,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.268.25,false,true,45606,Asian Art,Hanging scroll,衿羯羅童子像 |Kongara-doji,Japan,Nanbokuchō period (1336–92),,,,Artist,,Ryūshū Shūtaku (Myōtaku),"Japanese, 1307–1388",,Ryūshū Shūtaku,Japanese,1307,1388,1387,1387,1387,Hanging scroll; ink and color on silk,43 x 15 3/4 in. (109.2 x 40 cm),"The Harry G. C. Packard Collection of Asian Art, Gift of Harry G. C. Packard, and Purchase, Fletcher, Rogers, Harris Brisbane Dick, and Louis V. Bell Funds, Joseph Pulitzer Bequest, and The Annenberg Fund Inc. Gift, 1975",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45606,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1993.476,false,true,45230,Asian Art,Hanging scroll,,Japan,Meiji (1868–1912)–Taishō (1912–26) period,,,,Artist,,Tomioka Tessai,"Japanese, 1836–1924",,Tomioka Tessai,Japanese,1836,1924,19th–20th century,1868,1924,Hanging scroll; ink on paper,20 1/4 x 13 1/4 in. (51.4 x 33.7 cm),"Gift of Dr. Yukikazu Iwasa, in honor of Shizuko Iwasa, 1993",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45230,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -52.160.1,false,true,57327,Asian Art,Hanging scroll,,Japan,Late Edo (1615–1868) or Meiji (1868–1912) period,,,,Artist,,Shibata Zeshin,"Japanese, 1807–1891",,Shibata Zeshin,Japanese,1807,1891,second half of the 19th century,1850,1891,Hanging scroll; ink and color on silk,Image: 35 1/2 x 13 in. (90.2 x 33 cm) Overall with knobs: 70 1/4 x 19 3/4 in. (178.4 x 50.2 cm),"Purchase, Gifts, Bequests, and Funds from various donors, by exchange, 1952",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/57327,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -27.69,false,true,40009,Asian Art,Figure,羅漢像|Rakan,Japan,Edo period (1615–1868),,,,Artist,,Shōun Genkei,"Japanese, 1648–1710",,Shōun Genkei,Japanese,1648,1710,1688–95,1688,1695,"One of a set of five hundred; wood with lacquer, gold leaf, and paint",H. 33 1/2 in. (85.1 cm); W. 28 3/4 in. (73 cm); D. 26 1/4 in. (66.7 cm),"Fletcher Fund, 1927",,,,,,,,,,,,Sculpture,,http://www.metmuseum.org/art/collection/search/40009,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"57.157.1, .2",false,true,45693,Asian Art,Pair of folding screens,伝狩野山楽筆 粟に小禽図屏風|Autumn Millet and Small Birds,Japan,Edo period (1615–1868),,,,Artist,Attributed to,Kano Sanraku,"Japanese, 1559–1635",,Kano Sanraku,Japanese,1559,1635,,1559,1635,"Pair of eight-panel foldingscreens; ink, color, and gold on gilt paper",Image (each screen): 33 1/2 x 134 1/2 in. (85.1 x 341.6 cm),"Purchase, Joseph Pulitzer Bequest, 1957",,,,,,,,,,,,Screens,,http://www.metmuseum.org/art/collection/search/45693,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JIB149,false,true,57840,Asian Art,Illustrated book,,Japan,Edo period (1615–1868),,,,Artist,After,Kano Tan'yū,"Japanese, 1602–1674",,Kano Tan'yū,Japanese,1602,1674,,1615,1868,Monochrome woodblock print; ink on paper,10 x 7 1/2 in. (25.4 x 19.1 cm),"Bequest of W. Gedney Beatty, 1941",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/57840,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2000.453,false,true,55450,Asian Art,Screen,,Japan,Edo period (1615–1868),,,,Artist,,Kano Yasunobu,"Japanese, 1613–1685",,Kano Yasunobu,Japanese,1613,1685,,1615,1868,"Six-panel screen; ink, color, and gold on gilded paper",Image: 69 3/8 x 146 3/8 in. (176.2 x 371.8 cm),"Gift of James L. Greenfield, in memory of Margaret Greenfield, 2000",,,,,,,,,,,,Screens,,http://www.metmuseum.org/art/collection/search/55450,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -49.35.1,false,true,57344,Asian Art,Screen,,Japan,Edo period (1615–1868),,,,Artist,School of,Ogata Kōrin,"Japanese, 1658–1716",,Kōrin,Japanese,1658,1716,,1615,1868,Six-panel folding screen; color on paper,67 x 12 ft. 7 in. (170.2 x 383.5 cm),"H. O. Havemeyer Collection, Gift of Horace Havemeyer, 1949",,,,,,,,,,,,Screens,,http://www.metmuseum.org/art/collection/search/57344,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.784,false,true,58693,Asian Art,Inrō,,Japan,Edo period (1615–1868),,,,Artist,,Ogawa Haritsu (Ritsuō),"Japanese, 1663–1747",,Ritsuō,Japanese,1663,1747,,1615,1868,"Ceramic, mother-of-pearl, pewter on brown lacquer with sprinkled gold Ojime: bead; agate Netsuke: ivory and wood",2 15/16 x 2 1/8 x 7/8 in. (7.5 x 5.4 x 2.3 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Inrō,,http://www.metmuseum.org/art/collection/search/58693,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.671,false,true,47413,Asian Art,Figure,,Japan,Edo period (1615–1868),,,,Artist,,Ogawa Haritsu (Ritsuō),"Japanese, 1663–1747",,Ritsuō,Japanese,1663,1747,,1615,1868,Lacquered pottery,H. 8 3/4 in. (22.2 cm); W. 9 1/2 in. (24.1 cm); D. 6 1/2 in. (16.5 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Lacquer,,http://www.metmuseum.org/art/collection/search/47413,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.672,false,true,47405,Asian Art,Figure,,Japan,Edo period (1615–1868),,,,Artist,Attributed to,Ogawa Haritsu (Ritsuō),"Japanese, 1663–1747",,Ritsuō,Japanese,1663,1747,,1615,1868,"Ceramic body covered in lacquer, gold foil and covered in lacquer again",H. 9 in. (22.9 cm); W. 9 in. (22.9 cm); D. 7 in. (17.8 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Lacquer,,http://www.metmuseum.org/art/collection/search/47405,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"36.100.161a, b",false,true,56165,Asian Art,Tea caddy,,Japan,Edo period (1615–1868),,,,Artist,Attributed to,Ogawa Haritsu (Ritsuō),"Japanese, 1663–1747",,Ritsuō,Japanese,1663,1747,,1663,1747,Lacquer,H. 3 1/8 in. (7.9 cm); Diam. 2 5/8 in. (6.7 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Lacquer,,http://www.metmuseum.org/art/collection/search/56165,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1996.104,false,true,39581,Asian Art,Screen,,Japan,Edo period (1615–1868),,,,Artist,,Soga Shōhaku,"Japanese, 1730–1781",,Soga Shōhaku,Japanese,1730,1781,,1615,1868,Two-panel folding screen; ink and gold paint on paper,Image: 61 3/4 x 68 3/8 in. (156.8 x 173.7 cm),"Purchase, Barbara and William Karatz Gift and Rogers Fund, 1996",,,,,,,,,,,,Screens,,http://www.metmuseum.org/art/collection/search/39581,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.778,false,true,78680,Asian Art,Illustrated book,,Japan,Edo period (1615–1868),,,,Artist,,Kuwagata Keisai,"Japanese, 1764–1824",,Kuwagata Keisai,Japanese,1764,1824,,1764,1824,Woodblock printed book; ink and color on paper,9 3/4 × 6 7/8 in. (24.7 × 17.5 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78680,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2009.511.1, .2",false,true,75552,Asian Art,Screen,,Japan,Edo period (1615–1868),,,,Artist,,Maruyama Ōshin,"Japanese, 1790–1838",,Maruyama Ōshin,Japanese,1790,1838,,1790,1838,"Pair of six-panel folding screens; ink, color, and gold on paper",Image (each screen): 32 5/16 x 103 3/16 in. (82 x 262.1 cm),"Gift of the Gitter-Yelen Foundation, 2009",,,,,,,,,,,,Screens,,http://www.metmuseum.org/art/collection/search/75552,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JIB81.3,false,true,57681,Asian Art,Illustrated book,,Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,,1615,1868,Woodblock printed book; ink and color on paper,Overall: 9 × 6 1/4 × 3/8 in. (22.9 × 15.9 × 1 cm),"Rogers Fund, 1931",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/57681,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JIB81.6,false,true,57683,Asian Art,Illustrated book,,Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,,1615,1868,Woodblock printed book; ink and color on paper,Overall: 9 × 6 1/4 × 3/8 in. (22.9 × 15.9 × 1 cm),"Rogers Fund, 1931",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/57683,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JIB81.11,false,true,57688,Asian Art,Illustrated book,,Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,,1615,1868,Woodblock printed book; ink and color on paper,Overall: 9 × 6 1/4 × 3/8 in. (22.9 × 15.9 × 1 cm),"Rogers Fund, 1931",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/57688,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JIB111a–k,false,true,57804,Asian Art,Illustrated book,,Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,,1615,1868,Eleven volumes of Woodblock printed books; ink and color on paper,Overall (each volume): 8 15/16 x 6 1/4 x 1/2 in. (22.7 x 15.8 x 1.3 cm) Image: 7 x 4 7/8 in. (17.8 x 12.4 cm),"The Howard Mansfield Collection, Gift of Howard Mansfield, 1936",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/57804,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JIB52,false,true,57652,Asian Art,Illustrated book,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,,1615,1868,Ink on paper,4 1/2 × 6 1/2 × 2 3/8 in. (11.4 × 16.5 × 6 cm),"Rogers Fund, 1918",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/57652,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.69,false,true,57975,Asian Art,Panel,,Japan,late Edo period (1615–1868)–early Meiji period (1868–1912) ?,,,,Artist,Style of,Ogawa Haritsu (Ritsuō),"Japanese, 1663–1747",,Ritsuō,Japanese,1663,1747,,1615,1912,Lacquer,L. 67 in. (170.2 cm); W. 8 1/2 in. (21.6 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Lacquer,,http://www.metmuseum.org/art/collection/search/57975,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.70,false,true,50225,Asian Art,Panel,,Japan,late Edo period (1615–1868)–early Meiji period (1868–1912) ?,,,,Artist,Style of,Ogawa Haritsu (Ritsuō),"Japanese, 1663–1747",,Ritsuō,Japanese,1663,1747,,1615,1912,Lacquer,W. 5 3/8 in. (13.7 cm); L. 64 1/2 in. (163.9 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Lacquer,,http://www.metmuseum.org/art/collection/search/50225,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3156,false,true,56709,Asian Art,Print,,Japan,,,,,Artist,,Torii Kiyonobu,"Japanese, 1664–1729",,Torii Kiyonobu,Japanese,1664,1729,,1664,1729,Polychrome woodblock print; ink and color on paper,5 3/4 x 12 1/2 in. (14.6 x 31.8 cm),"Gift of Mrs. Francis Ormond, 1949",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56709,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2346,false,true,54131,Asian Art,Print,,Japan,,,,,Artist,,Teisai Hokuba,"Japanese, 1771–1844",,Teisai Hokuba,Japanese,1771,1844,,1700,1899,Part of an album of woodblock prints (surimono); ink and color on paper,8 7/16 x 5 7/16 in. (21.4 x 13.8 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54131,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3285,false,true,55372,Asian Art,Print,,Japan,,,,,Artist,,Keisai Eisen,"Japanese, 1790–1848",,Keisai Eisen,Japanese,1790,1848,,1790,1848,Polychrome woodblock print; ink and color on paper,Overall: 8 11/16 x 11 15/16 in. (22.1 x 30.3 cm),"Gift of Lincoln Kirstein, 1959",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55372,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3176,false,true,56726,Asian Art,Print,,Japan,,,,,Artist,,Shibata Zeshin,"Japanese, 1807–1891",,Shibata Zeshin,Japanese,1807,1891,,1807,1891,Polychrome woodblock print; ink and color on paper,Image: 7 1/2 × 10 1/8 in. (19.1 × 25.7 cm) Mat: 15 1/4 × 22 3/4 in. (38.7 × 57.8 cm),"Gift of Roland Koscherak, 1957",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56726,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3153,false,true,56706,Asian Art,Print,,Japan,,,,,Artist,,Kawanabe Kyōsai,"Japanese, 1831–1889",,Kawanabe Kyōsai,Japanese,1831,1889,,1831,1899,Polychrome woodblock print; ink and color on paper,9 7/8 x 14 1/8 in. (25.1 x 35.9 cm),"Bequest of Ellis G. Seymour, 1949",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56706,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3133,false,true,56681,Asian Art,Woodblock print,,Japan,,,,,Artist,,Tsukioka Yoshitoshi,"Japanese, 1839–1892",,Tsukioka Yoshitoshi,Japanese,1839,1892,,1831,1889,Triptych of polychrome woodblock prints; ink and color on paper,14 1/4 x 29 5/8 in. (36.2 x 75.2 cm),"Gift of Francis M. Weld, 1948",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56681,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2297,false,true,54070,Asian Art,Print,,Japan,,,,,Artist,,Ryūgetsusai Shinkō,"Japanese, active 1810s",,Ryūgetsusai Shinkō,Japanese,1810,1819,,1700,1899,Part of an album of woodblock prints (surimono); ink and color on paper,5 3/16 x 7 1/8 in. (13.2 x 18.1 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54070,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3397,false,true,55575,Asian Art,Print,,Japan,,,,,Artist,Attributed to,Yumiaki Toriyama,"Japanese, active ca. 1800",,Yumiaki Toriyama,Japanese,1790,1810,,1790,1810,Polychrome woodblock print; ink and color on paper,26 3/4 x 4 3/4 in. (67.9 x 12.1 cm),"Bequest of Julia H. Manges, 1960",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55575,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2367,false,true,54151,Asian Art,Print,,Japan,,,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,,1760,1849,Part of an album of woodblock prints (surimono); ink and color on paper,7 1/2 x 10 1/4 in. (19.1 x 26 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54151,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2929,false,true,56144,Asian Art,Woodblock print,"諸國瀧廻 東都葵ヶ岡の瀧|The Falls at Aoigaoka in the Eastern Capital (Tōto Aoigaoka no taki), from the series A Tour of Waterfalls in Various Provinces (Shokoku taki meguri)",Japan,,,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,,1760,1849,Polychrome woodblock print; ink and color on paper,14 5/8 x 10 1/4 in. (37.1 x 26 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56144,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3146,false,true,56697,Asian Art,Print,冨嶽三十六景 甲州犬目峠|Fuji from Inume (?) Pass,Japan,,,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,,1760,1849,Polychrome woodblock print; ink and color on paper,14 1/4 x 9 1/4 in. (36.2 x 23.5 cm),"Bequest of Ellis G. Seymour, 1949",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56697,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3175,false,true,54227,Asian Art,Print,,Japan,,,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,,1760,1849,Polychrome woodblock print (surimono); ink and color on paper,Image: 7 5/8 × 21 1/16 in. (19.4 × 53.5 cm) Mat: 12 1/2 in. × 37 in. (31.8 × 94 cm),"Gift of Mrs. Henry L. Phillips, in memory of her husband, Henry L. Phillips, 1957",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54227,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1620,false,true,55786,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Shigenaga,1697–1756,,Shigenaga,Japanese,1697,1756,,1697,1756,Polychrome woodblock print; ink and color on paper,H. 11 1/4 in. (28.6 cm); W. 5 7/8 in. (14.9 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55786,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1374,false,true,55355,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Komatsuken,1710–1792,,Komatsuken,Japanese,1710,1792,,1710,1792,Polychrome woodblock print; ink and color on paper,H. 10 9/16 in. (26.8 cm); W. 8 7/16 in. (21.4 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55355,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1790,false,true,56092,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Chōkōsai Eishō,"Japanese, 1793–99",,Chōkōsai Eishō,Japanese,1793,1799,,1792,1801,Polychrome woodblock print; ink and color on paper,H. 14 1/2 in. (36.8 cm); W. 10 1/8 in. (25.7 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56092,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1791,false,true,56093,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Chōkōsai Eishō,"Japanese, 1793–99",,Chōkōsai Eishō,Japanese,1793,1799,,1792,1801,Monochrome woodblock print; ink on paper,H. 10 5/8 in. (27 cm); W. 7 1/2 in. (19.1 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56093,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3064,false,true,56541,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Chōkōsai Eishō,"Japanese, 1793–99",,Chōkōsai Eishō,Japanese,1793,1799,,1615,1868,Polychrome woodblock print; ink and color on paper,13 x 8 3/4 in. (33 x 22.2 cm),"Gift of Mrs. Morris Manges, in memory of her husband, Dr. Morris Manges, 1947",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56541,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1591,false,true,55755,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Hishikawa Moronobu,"Japanese, died 1694",,Hishikawa Moronobu,Japanese,,1694,,1625,1694,Monochrome woodblock print; ink on paper,H. 6 1/2 in. (16.5 cm); W. 6 7/16 in. (16.4 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55755,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1592,false,true,55756,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Hishikawa Moronobu,"Japanese, died 1694",,Hishikawa Moronobu,Japanese,,1694,,1625,1694,Monochrome woodblock print; ink on paper,H. 6 5/16 in. (16 cm); W. 6 7/16 in. (16.4 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55756,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1593,false,true,55757,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Hishikawa Moronobu,"Japanese, died 1694",,Hishikawa Moronobu,Japanese,,1694,,1625,1694,Monochrome woodblock print; ink on paper,H. 5 1/8 in. (13 cm); 8 13/16 in. (22.4 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55757,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3048,false,true,56503,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Hishikawa Moronobu,"Japanese, died 1694",,Hishikawa Moronobu,Japanese,,1694,,1615,1868,Monochrome woodblock print; ink on paper,9 3/4 x 13 1/4 in. (24.8 x 33.7 cm),"Gift of Mrs. Morris Manges, in memory of her husband, Dr. Morris Manges, 1947",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56503,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3049,false,true,56504,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Hishikawa Moronobu,"Japanese, died 1694",,Hishikawa Moronobu,Japanese,,1694,,1615,1868,Monochrome woodblock print; ink on paper,9 5/8 x 13 in. (24.4 x 33 cm),"Gift of Mrs. Morris Manges, in memory of her husband, Dr. Morris Manges, 1947",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56504,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3066,false,true,56546,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Hishikawa Moronobu,"Japanese, died 1694",,Hishikawa Moronobu,Japanese,,1694,,1615,1868,Monochrome woodblock print; ink on paper,10 1/4 x 16 3/4 in. (26 x 42.5 cm),"Harris Brisbane Dick Fund and Rogers Fund, 1949",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56546,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3067,false,true,56548,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Hishikawa Moronobu,"Japanese, died 1694",,Hishikawa Moronobu,Japanese,,1694,,1615,1868,Monochrome woodblock print; ink on paper,10 1/2 x 16 1/4 in. (26.7 x 41.3 cm),"Harris Brisbane Dick Fund and Rogers Fund, 1949",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56548,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3068,false,true,56549,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Hishikawa Moronobu,"Japanese, died 1694",,Hishikawa Moronobu,Japanese,,1694,,1615,1868,Monochrome woodblock print; ink on paper,10 1/2 x 16 in. (26.7 x 40.6 cm),"Harris Brisbane Dick Fund and Rogers Fund, 1949",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56549,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1691,false,true,55926,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Kikumaro,"Japanese, died 1830",,Kitagawa Kikumaro,Japanese,,1830,,1789,1829,Polychrome woodblock print; ink and color on paper,H. 13 1/4 in. (33.7 cm); W. 8 3/4 in. (22.2 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55926,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1692,false,true,55927,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Kikumaro,"Japanese, died 1830",,Kitagawa Kikumaro,Japanese,,1830,,1789,1829,Polychrome woodblock print; ink and color on paper,H. 23 1/8 in. (58.7 cm); W. 4 1/4 in. (10.8 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55927,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1089,false,true,55026,Asian Art,Print,青楼美人 六花撰 岡本屋内 重岡|A Courtesan with Morning-glories on the Background,Japan,Edo period (1615–1868),,,,Artist,,Utamaro II,Japanese (died 1831?),,Utamaro II,Japanese,1750,1850,,1615,1831,Polychrome woodblock print; ink and color on paper,H. 14 1/4 in. (36.2 cm); W. 9 3/8 in. (23.8),The Metropolitan Museum of Art,,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55026,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1090,false,true,55027,Asian Art,Print,青楼美人 六花撰 扇屋内花扇|A Courtesan with Wisteria on the Background,Japan,Edo period (1615–1868),,,,Artist,,Utamaro II,Japanese (died 1831?),,Utamaro II,Japanese,1750,1850,,1615,1831,Polychrome woodblock print; ink and color on paper,H. 14 1/8 in. (35.9 cm); W. 9 1/4 in. (23.5 cm),The Metropolitan Museum of Art,,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55027,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3394,false,true,55570,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utamaro II,Japanese (died 1831?),,Utamaro II,Japanese,1750,1850,,1804,1817,Triptych of polychrome woodblock prints; ink and color on paper,15 x 30 in. (38.1 x 76.2 cm),"Bequest of Julia H. Manges, 1960",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55570,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1828,false,true,56114,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Ogata Kōrin,"Japanese, 1658–1716",,Kōrin,Japanese,1658,1716,,1658,1716,Polychrome woodblock print (leaf from an album); ink and color on paper,H. 10 in. (25.4 cm); W. 14 3/16 in. (36 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56114,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1829,false,true,56115,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Ogata Kōrin,"Japanese, 1658–1716",,Kōrin,Japanese,1658,1716,,1658,1716,Polychrome woodblock print (album leaf); ink and color on paper,H. 10 in. (25.4 cm); W. 14 7/16 in. (36.7 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56115,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1597,false,true,55761,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyonobu,"Japanese, 1664–1729",,Torii Kiyonobu,Japanese,1664,1729,,1664,1729,Polychrome woodblock print; ink and color on paper,H. 12 1/2 in. (31.8 cm); W. 6 in. (15.2 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55761,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1598,false,true,55762,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyonobu,"Japanese, 1664–1729",,Torii Kiyonobu,Japanese,1664,1729,,1664,1729,Polychrome woodblock print; ink and color on paper,H. 11 3/4 in. (29.8 cm); W. 5 3/4 in. (14.6 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55762,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1584,false,true,55749,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Nishikawa Sukenobu,"Japanese, 1671–1750",,Nishikawa Sukenobu,Japanese,1671,1750,,1671,1751,Monochrome woodblock print; ink on paper,H. 10 3/16 in. (25.9 cm); W. 14 1/2 in. (36.8 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55749,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1585,false,true,55750,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Nishikawa Sukenobu,"Japanese, 1671–1750",,Nishikawa Sukenobu,Japanese,1671,1750,,1671,1751,Monochrome woodblock print; ink on paper,H. 10 3/16 in. (25.9 cm); W. 14 1/2 in. (36.8 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55750,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3050,false,true,56505,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Nishikawa Sukenobu,"Japanese, 1671–1750",,Nishikawa Sukenobu,Japanese,1671,1750,,1615,1868,Monochrome woodblock print; ink on paper,9 1/4 x 12 3/4 in. (23.5 x 32.4 cm),"Gift of Mrs. Morris Manges, in memory of her husband, Dr. Morris Manges, 1947",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56505,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3051,false,true,56506,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Nishikawa Sukenobu,"Japanese, 1671–1750",,Nishikawa Sukenobu,Japanese,1671,1750,,1615,1868,Monochrome woodblock print; ink on paper,9 1/2 x 12 3/4 in. (24.1 x 32.4 cm),"Gift of Mrs. Morris Manges, in memory of her husband, Dr. Morris Manges, 1947",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56506,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3065,false,true,56544,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Nishikawa Sukenobu,"Japanese, 1671–1750",,Nishikawa Sukenobu,Japanese,1671,1750,,1615,1868,Polychrome woodblock print (hand-colored); ink and color on paper,8 3/4 x 6 1/2 in. (22.2 x 16.5 cm),"Gift of Mrs. Morris Manges, in memory of her husband, Dr. Morris Manges, 1947",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56544,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3405,false,true,55592,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Nishikawa Sukenobu,"Japanese, 1671–1750",,Nishikawa Sukenobu,Japanese,1671,1750,,1671,1751,Monochrome woodblock print; ink on paper,6 1/2 x 5 in. (16.5 x 12.7 cm),"Bequest of Julia H. Manges, 1960",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55592,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1586,false,true,55751,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Okumura Masanobu,"Japanese, 1686–1764",,Masanobu Okumura,Japanese,1686,1764,,1691,1768,Polychrome woodblock print; ink and color on paper,H. 12 1/8 in. (30.8 cm); W. 17 in. (43.2 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55751,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1587,false,true,55752,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Okumura Masanobu,"Japanese, 1686–1764",,Masanobu Okumura,Japanese,1686,1764,,1691,1768,Undivided triptych of polychrome woodblock prints; ink and color on paper,H. 11 3/4 in. (29.8 cm); W. 16 15/16 in. (43 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55752,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1588,false,true,55753,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Okumura Masanobu,"Japanese, 1686–1764",,Masanobu Okumura,Japanese,1686,1764,,1691,1768,Undivided triptych of polychrome woodblock prints; ink and color on paper,H. 11 7/8 in. (30.2 cm); W. 17 3/8 in. (44.1 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55753,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1595,false,true,55759,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Okumura Masanobu,"Japanese, 1686–1764",,Masanobu Okumura,Japanese,1686,1764,,1691,1768,Monochrome woodblock print; ink on paper,H. 11 1/2 in. (29.2 cm); W. 15 3/4 in. (40 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55759,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1596,false,true,55760,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Okumura Masanobu,"Japanese, 1686–1764",,Masanobu Okumura,Japanese,1686,1764,,1691,1768,Monochrome woodblock print; ink on paper,H. 11 1/2 in. (29.2 cm); W. 15 3/4 in. (40 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55760,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3059,false,true,56534,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Okumura Masanobu,"Japanese, 1686–1764",,Masanobu Okumura,Japanese,1686,1764,,1615,1868,Polychrome woodblock print; ink and color on paper,12 x 7 in. (30.5 x 17.8 cm),"Gift of Mrs. Morris Manges, in memory of her husband, Dr. Morris Manges, 1947",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56534,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3411,false,true,55600,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Okumura Masanobu,"Japanese, 1686–1764",,Masanobu Okumura,Japanese,1686,1764,,1686,1764,Monochrome woodblock print; ink on paper,11 x 12 1/4 in. (27.9 x 31.1 cm),"Bequest of Julia H. Manges, 1960",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55600,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1600,false,true,55764,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyomasu II,"Japanese, 1706–1763",,Torii Kiyomasu II,Japanese,1706,1763,,1706,1763,Polychrome woodblock print; ink and color on paper,H. 12 in. (30.5 cm); W. 5 3/4 in. (14.6 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55764,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3396,false,true,55573,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyomasu II,"Japanese, 1706–1763",,Torii Kiyomasu II,Japanese,1706,1763,,1706,1763,Monochrome woodblock print (probably hand colored); ink and color on paper,12 x 6 1/4 in. (30.5 x 15.9 cm),"Bequest of Julia H. Manges, 1960",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55573,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1225,false,true,55147,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Ishikawa Toyonobu,"Japanese, 1711–1785",,Ishikawa Toyonobu,Japanese,1711,1785,,1711,1785,Polychrome woodblock print; ink and color on paper,H. 14 7/8 in. (37.8 cm); W. 10 5/16 in. (26.2 cm),"Rogers Fund, 1921",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55147,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1611,false,true,55771,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Ishikawa Toyonobu,"Japanese, 1711–1785",,Ishikawa Toyonobu,Japanese,1711,1785,,1711,1785,Polychrome woodblock print; ink and color on paper,H. 11 3/8 (28.9 cm); W. 5 5/8 in. (14.3 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55771,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1612,false,true,55773,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Ishikawa Toyonobu,"Japanese, 1711–1785",,Ishikawa Toyonobu,Japanese,1711,1785,,1711,1785,Polychrome woodblock print; ink and color on paper,H. 11 3/8 in. (28.9 cm); W. 5 1/4 in. (13.3 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55773,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1613,false,true,55776,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Ishikawa Toyonobu,"Japanese, 1711–1785",,Ishikawa Toyonobu,Japanese,1711,1785,,1711,1785,Polychrome woodblock print; ink and color on paper,H. 14 1/4 in. (36.2 cm); W. 6 in. (15.2 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55776,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1614,false,true,55778,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Ishikawa Toyonobu,"Japanese, 1711–1785",,Ishikawa Toyonobu,Japanese,1711,1785,,1711,1785,Polychrome woodblock print; ink and color on paper,H. 27 3/4 in. (70.5 cm); W. 4 3/16 in. (10.6 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55778,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1615,false,true,55779,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Ishikawa Toyonobu,"Japanese, 1711–1785",,Ishikawa Toyonobu,Japanese,1711,1785,,1711,1785,Polychrome woodblock print; ink and color on paper,H. 25 9/16 in. (64.9 cm); W. 4 1/16 in. (10.3 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55779,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3142,false,true,56691,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Ishikawa Toyonobu,"Japanese, 1711–1785",,Ishikawa Toyonobu,Japanese,1711,1785,,1711,1785,Polychrome woodblock print; ink and color on paper,11 x 5 1/4 in. (27.9 x 13.3 cm),"Gift of Francis M. Weld, 1948",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56691,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP874,false,true,54568,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Suzuki Harunobu,"Japanese, 1725–1770",,Suzuki Harunobu,Japanese,1725,1770,,1725,1770,Polychrome woodblock print; ink and color on paper,H. 7 5/16 in. (18.6 cm); W. 10 3/16 in. (25.9 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54568,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1224,false,true,55146,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Suzuki Harunobu,"Japanese, 1725–1770",,Suzuki Harunobu,Japanese,1725,1770,,1725,1770,Polychrome woodblock print; ink and color on paper,H. 9 3/4 in. (24.8 cm); W. 7 7/16 in. (18.9 cm),"Rogers Fund, 1921",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55146,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1506,false,true,41057,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Suzuki Harunobu,"Japanese, 1725–1770",,Suzuki Harunobu,Japanese,1725,1770,,1725,1770,Polychrome woodblock print with embossing (karazuri); ink and color on paper,Image: 12 3/4 x 8 1/4 in. (32.4 x 21 cm),"Fletcher Fund, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/41057,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1623,false,true,55790,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Suzuki Harunobu,"Japanese, 1725–1770",,Suzuki Harunobu,Japanese,1725,1770,,1725,1770,Polychrome woodblock print; ink and color on paper,H. 17 7/16 in. (44.3 cm); W. 8 1/8 in. (20.6 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55790,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1624,false,true,55791,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Suzuki Harunobu,"Japanese, 1725–1770",,Suzuki Harunobu,Japanese,1725,1770,,1725,1770,Polychrome woodblock print; ink and color on paper,H. 10 13/16 in. (27.5 cm); W. 8 1/8 in. (20.6 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55791,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1626,false,true,55792,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Suzuki Harunobu,"Japanese, 1725–1770",,Suzuki Harunobu,Japanese,1725,1770,,1725,1770,Polychrome woodblock print; ink and color on paper,H. 11 in. (27.9 cm); W. 8 3/8 in. (21.3 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55792,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1629,false,true,42562,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Suzuki Harunobu,"Japanese, 1725–1770",,Suzuki Harunobu,Japanese,1725,1770,,1725,1770,Polychrome woodblock print; ink and color on paper,H. 11 in. (27.9 cm); W. 8 1/8 in. (20.6 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/42562,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1630,false,true,55794,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Suzuki Harunobu,"Japanese, 1725–1770",,Suzuki Harunobu,Japanese,1725,1770,,1725,1770,Polychrome woodblock print; ink and color on paper,H. 10 1/4 in. (26 cm); W. 7 13/16 in. (19.8 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55794,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1631,false,true,55795,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Suzuki Harunobu,"Japanese, 1725–1770",,Suzuki Harunobu,Japanese,1725,1770,,1725,1770,Polychrome woodblock print; ink and color on paper,H. 9 13/16 in. (24.9 cm); W. 7 5/16 in. (18.6 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55795,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1635,false,true,45071,Asian Art,Print,すだれ貝|The Curtain Clam,Japan,Edo period (1615–1868),,,,Artist,,Suzuki Harunobu,"Japanese, 1725–1770",,Suzuki Harunobu,Japanese,1725,1770,,1725,1770,Polychrome woodblock print; ink and color on paper,H. 11 1/4 in. (28.6 cm); W. 8 9/16 in. (21.7 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45071,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1639,false,true,55798,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Suzuki Harunobu,"Japanese, 1725–1770",,Suzuki Harunobu,Japanese,1725,1770,,1725,1770,Polychrome woodblock print; ink and color on paper,H. 10 in. (25.4 cm); W. 7 5/8 in. (19.4 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55798,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1641,false,true,55799,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Suzuki Harunobu,"Japanese, 1725–1770",,Suzuki Harunobu,Japanese,1725,1770,,1725,1770,Polychrome woodblock print; ink and color on paper,H. 10 in. (25.4 cm); W. 8 in. (20.3 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55799,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1642,false,true,55800,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Suzuki Harunobu,"Japanese, 1725–1770",,Suzuki Harunobu,Japanese,1725,1770,,1725,1770,Polychrome woodblock print; ink and color on paper,H. 10 11/16 in. (27.1 cm); W. 8 1/4 in. (21 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55800,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1645,false,true,45087,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Suzuki Harunobu,"Japanese, 1725–1770",,Suzuki Harunobu,Japanese,1725,1770,,1725,1770,Polychrome woodblock print; ink and color on paper,"H. 7 1/4 in. (18.4 cm); W. 12 3/8 in. (31.4 cm) Medium-size block (""chuban"")","H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45087,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1647,false,true,55805,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Suzuki Harunobu,"Japanese, 1725–1770",,Suzuki Harunobu,Japanese,1725,1770,,1725,1770,Polychrome woodblock print; ink and color on paper,H. 10 7/16 in. (26.5 cm); W. 7 15/16 in. (20.2 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55805,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1648,false,true,55806,Asian Art,Print,風俗江戸八景 浅草晴嵐|Asakusa Seiran,Japan,Edo period (1615–1868),,,,Artist,,Suzuki Harunobu,"Japanese, 1725–1770",,Suzuki Harunobu,Japanese,1725,1770,,1725,1770,Polychrome woodblock print; ink and color on paper,H. 10 3/4 in. (27.3 cm); W. 7 13/16 in. (19.8 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55806,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1650,false,true,55807,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Suzuki Harunobu,"Japanese, 1725–1770",,Suzuki Harunobu,Japanese,1725,1770,,1725,1770,Polychrome woodblock print; ink and color on paper,H. 10 3/4 in. (27.3 cm); W. 8 7/16 in. (21.4 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55807,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1652,false,true,55811,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Suzuki Harunobu,"Japanese, 1725–1770",,Suzuki Harunobu,Japanese,1725,1770,,1725,1770,Polychrome woodblock print; ink and color on paper,H. 11 in. (27.9 cm); W. 8 1/4 in. (21 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55811,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1653,false,true,55813,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Suzuki Harunobu,"Japanese, 1725–1770",,Suzuki Harunobu,Japanese,1725,1770,,1725,1770,Polychrome woodblock print; ink and color on paper,H. 10 15/16 in. (27.8 cm); W. 8 1/16 in. (20.5 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55813,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3020,false,true,56418,Asian Art,Print,"六玉川 「千鳥の玉川 陸奥名所」|“The Jewel River of Plovers, a Famous Place in Mutsu Province,” from the series Six Jewel Rivers (Mu Tamagawa: Chidori no Tamagawa, Mutsu meisho)",Japan,Edo period (1615–1868),,,,Artist,,Suzuki Harunobu,"Japanese, 1725–1770",,Suzuki Harunobu,Japanese,1725,1770,,1725,1770,Polychrome woodblock print; ink and color on paper,10 13/16 x 7 13/16 in. (27.5 x 19.8 cm),"Harris Brisbane Dick Fund, 1946",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56418,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3023,false,true,56421,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Suzuki Harunobu,"Japanese, 1725–1770",,Suzuki Harunobu,Japanese,1725,1770,,1725,1770,Polychrome woodblock print; ink and color on paper,10 3/4 x 7 7/8 in. (27.3 x 20 cm),"Harris Brisbane Dick Fund, 1946",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56421,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3024,false,true,56422,Asian Art,Print,"Koya no Tamagawa|Boy, Girl and Viewing Glass",Japan,Edo period (1615–1868),,,,Artist,,Suzuki Harunobu,"Japanese, 1725–1770",,Suzuki Harunobu,Japanese,1725,1770,,1725,1770,Polychrome woodblock print; ink and color on paper,10 3/4 x 7 3/4 in. (27.3 x 19.7 cm),"Harris Brisbane Dick Fund, 1946",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56422,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3139,false,true,56687,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Suzuki Harunobu,"Japanese, 1725–1770",,Suzuki Harunobu,Japanese,1725,1770,,1725,1770,Polychrome woodblock print; ink and color on paper,11 x 8 in. (27.9 x 20.3 cm),"Gift of Francis M. Weld, 1948",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56687,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3403,false,true,55590,Asian Art,Print,梅|Blowing Soap Bubbles Under the Plum Blossom,Japan,Edo period (1615–1868),,,,Artist,,Suzuki Harunobu,"Japanese, 1725–1770",,Suzuki Harunobu,Japanese,1725,1770,,1725,1770,Polychrome woodblock print; ink and color on paper,11 x 8 in. (27.9 x 20.3 cm),"Bequest of Julia H. Manges, 1960",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55590,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP457,false,true,36910,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,,1726,1792,Polychrome woodblock print; ink and color on paper,12 3/4 x 5 3/4 in. (32.4 x 14.6 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36910,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1353,false,true,55329,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,,1726,1792,Polychrome woodblock print; ink and color on paper,H. 12 11/16 in. (32.2 cm); W. 5 15/16 in. (15.1 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55329,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1444,false,true,55488,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,,1726,1792,Polychrome woodblock print; ink and color on paper,H. 5 1/2 in. (14 cm); W. 6 3/16 in. (15.7 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55488,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1446,false,true,55491,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,,1726,1792,Polychrome woodblock print; ink and color on paper,H. 12 1/2 in. (31.8 cm); W. 5 13/16 in. (14.8 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55491,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1455,false,true,55501,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,,1726,1792,Polychrome woodblock print; ink and color on paper,H. 12 7/16 in. (31.6 cm); 5 7/8 in. (14.9 cm),"Rogers Fund, 1923",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55501,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1751,false,true,56057,Asian Art,Design for a fan,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,,1726,1792,Monochrome woodblock print; ink on paper,H. 9 3/4 in. (24.8 cm); W. 13 1/2 in. (34.3 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56057,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1752,false,true,56058,Asian Art,Design for a fan,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,,1726,1792,Monochrome woodblock print; ink on paper,H. 9 3/4 in. (24.8 cm); W. 13 1/2 in. (34.3 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56058,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1753,false,true,56059,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,,1726,1792,Polychrome woodblock print; ink and color on paper,H. 12 15/16 in. (32.9 cm); W. 5 15/16 in. (15.1 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56059,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1754,false,true,56060,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,,1726,1792,Polychrome woodblock print; ink and color on paper,H. 12 3/4 in. (32.4 cm); W. 6 in. (15.2 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56060,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1755,false,true,56061,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,,1726,1792,Polychrome woodblock print; ink and color on paper,H. 11 in. (27.9 cm); W. 8 3/8 in. (21.3 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56061,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1756,false,true,56062,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,,1726,1792,Polychrome woodblock print; ink and color on paper,H. 12 3/4 in. (32.4 cm); W. 8 15/16 in. (22.7 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56062,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1758,false,true,56064,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,,1726,1792,Left-hand sheet of a diptych of polychrome woodblock prints; ink and color on paper,H. 8 7/8 in. (22.5 cm); W. 6 7/16 in. (16.4 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56064,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3057,false,true,56512,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,,1615,1868,Polychrome woodblock print; ink and color on paper,12 5/8 x 5 3/4 in. (32.1 x 14.6 cm),"Gift of Mrs. Morris Manges, in memory of her husband, Dr. Morris Manges, 1947",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56512,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3058,false,true,56513,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,,1615,1868,Polychrome woodblock print; ink and color on paper,12 x 5 3/8 in. (30.5 x 13.7 cm),"Gift of Mrs. Morris Manges, in memory of her husband, Dr. Morris Manges, 1947",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56513,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3061,false,true,56536,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,,1615,1868,Diptych of polychrome woodblock prints; ink and color on paper,10 1/8 x 7 3/8 in. (25.7 x 18.7 cm),"Gift of Mrs. Morris Manges, in memory of her husband, Dr. Morris Manges, 1947",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56536,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"JP397a, b",false,true,36466,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,,1726,1792,Diptych of polychrome woodblock prints; ink and color on paper,H. 12 1/4 in. (31.1 cm); W. 11 1/4 in. (28.6 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36466,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1601,false,true,55765,Asian Art,Print,新板浮絵忍ヶ岡之圖|Perspective Print: Shinobazu Pond,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Toyoharu,"Japanese, 1735–1814",,Utagawa Toyoharu,Japanese,1735,1814,,1734,1815,Polychrome woodblock print; ink and color on paper,H. 9 3/16 in. (23.3 cm); W. 13 1/2 in. (34.3 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55765,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1602,false,true,55766,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Toyoharu,"Japanese, 1735–1814",,Utagawa Toyoharu,Japanese,1735,1814,,1735,1814,Polychrome woodblock print; ink and color on paper,H. 4 1/2 in. (11.4 cm); W. 12 3/8 in. (31.4 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55766,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1603,false,true,55767,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Toyoharu,"Japanese, 1735–1814",,Utagawa Toyoharu,Japanese,1735,1814,,1735,1814,Polychrome woodblock print; ink and color on paper,H. 9 3/16 in. (23.3 cm); W. 14 7/8 in. (37.8 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55767,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1604,false,true,55768,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Toyoharu,"Japanese, 1735–1814",,Utagawa Toyoharu,Japanese,1735,1814,,1735,1814,Polychrome woodblock print; ink and color on paper,H. 4 1/2 in. (11.4 cm); W. 12 3/8 in. (31.4 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55768,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP935,false,true,54805,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kitao Shigemasa,"Japanese, 1739–1820",,Kitao Shigemasa,Japanese,1739,1820,,1739,1820,Polychrome woodblock print; ink and color on paper,H. 9 in. (22.9 cm); W. 14 in. (35.6 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54805,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1622,false,true,55789,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kitao Shigemasa,"Japanese, 1739–1820",,Kitao Shigemasa,Japanese,1739,1820,,1739,1820,Polychrome woodblock print; ink and color on paper,H. 8 3/4 in. (22.2 cm); W. 14 13/16 in. (37.6 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55789,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1726,false,true,56037,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kitao Shigemasa,"Japanese, 1739–1820",,Kitao Shigemasa,Japanese,1739,1820,,1739,1820,Polychrome woodblock print; ink and color on paper,H. 14 9/16 in. (37 cm); W. 9 7/8 in. (25.1 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56037,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1835,false,true,56120,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Kitao Shigemasa,"Japanese, 1739–1820",,Kitao Shigemasa,Japanese,1739,1820,,1739,1820,Monochrome woodblock print; ink on paper,H. 10 13/16 in. (27.5 cm); W. 7 9/16 in. (19.2 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56120,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2178,false,true,55092,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kitao Shigemasa,"Japanese, 1739–1820",,Kitao Shigemasa,Japanese,1739,1820,,1739,1820,Polychrome woodblock print (surimono); ink and color on paper,8 x 3 9/16 in. (20.3 x 9 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55092,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2505,false,true,56927,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kitao Shigemasa,"Japanese, 1739–1820",,Kitao Shigemasa,Japanese,1739,1820,,1739,1820,Polychrome woodblock print; ink and color on paper,Image: 20 1/8 × 14 1/4 in. (51.1 × 36.2 cm) Mat: 22 3/4 × 27 1/2 in. (57.8 × 69.9 cm),"The Howard Mansfield Collection, Gift of Howard Mansfield, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56927,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1127,false,true,55047,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunkō,"Japanese, 1743–1812",,Katsukawa Shunkō,Japanese,1743,1812,,1743,1812,Polychrome woodblock print; ink and color on paper,H. 12 5/8 in. (32.1 cm); W. 5 5/8 in. (14.3 cm),"Rogers Fund, 1919",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55047,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1761,false,true,56066,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunkō,"Japanese, 1743–1812",,Katsukawa Shunkō,Japanese,1743,1812,,1743,1812,Polychrome woodblock print; ink and color on paper,H. 11 7/8 in. (30.2 cm); W. 5 7/16 in. (13.8 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56066,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1762,false,true,56067,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunkō,"Japanese, 1743–1812",,Katsukawa Shunkō,Japanese,1743,1812,,1743,1812,Polychrome woodblock print; ink and color on paper,H. 11 1/2 in. (29.2 cm); W. 5 1/2 in. (14 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56067,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1763,false,true,56068,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunkō,"Japanese, 1743–1812",,Katsukawa Shunkō,Japanese,1743,1812,,1743,1812,Polychrome woodblock print; ink and color on paper,H. 12 7/8 in. (32.7 cm); W. 5 5/8 in. (14.3 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56068,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1764,false,true,56069,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunkō,"Japanese, 1743–1812",,Katsukawa Shunkō,Japanese,1743,1812,,1743,1812,Polychrome woodblock print; ink and color on paper,H. 8 in. (20.3 cm); W. 13 1/4 in. (33.7 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56069,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3063,false,true,56540,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunkō,"Japanese, 1743–1812",,Katsukawa Shunkō,Japanese,1743,1812,,1615,1868,Polychrome woodblock print; ink and color on paper,15 1/4 x 10 1/8 in. (38.7 x 25.7 cm),"Gift of Mrs. Morris Manges, in memory of her husband, Dr. Morris Manges, 1947",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56540,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1437,false,true,55472,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyonaga,"Japanese, 1752–1815",,Torii Kiyonaga,Japanese,1752,1815,,1742,1815,Polychrome woodblock print; ink and color on paper,H. 28 5/16 in. (71.9 cm); W. 4 7/8 in. (12.4 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55472,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1712,false,true,55964,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyonaga,"Japanese, 1752–1815",,Torii Kiyonaga,Japanese,1752,1815,,1742,1815,Polychrome woodblock print; ink and color on paper,H. 15 1/16 in. (38.3 cm); W. 9 7/8 in. (25.1 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55964,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1714,false,true,55968,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyonaga,"Japanese, 1752–1815",,Torii Kiyonaga,Japanese,1752,1815,,1742,1815,Polychrome woodblock print; ink and color on paper,H. 9 11/16 in. (24.6 cm); W. 7 1/4 in. (18.4 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55968,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1716,false,true,56027,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyonaga,"Japanese, 1752–1815",,Torii Kiyonaga,Japanese,1752,1815,,1742,1815,Polychrome woodblock print; ink and color on paper,H. 7 1/16 in. (17.9 cm); W. 9 1/2 in. (24.1 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56027,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1717,false,true,56028,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyonaga,"Japanese, 1752–1815",,Torii Kiyonaga,Japanese,1752,1815,,1742,1815,Polychrome woodblock print; ink and color on paper,H. 10 5/16 in. (26.2 cm); W. 7 9/16 in. (19.2 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56028,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1718,false,true,56029,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyonaga,"Japanese, 1752–1815",,Torii Kiyonaga,Japanese,1752,1815,,1742,1815,Polychrome woodblock print; ink and color on paper,H. 10 1/16 in. (25.6 cm); W. 7 9/16 in. (19.2 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56029,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1719,false,true,56030,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyonaga,"Japanese, 1752–1815",,Torii Kiyonaga,Japanese,1752,1815,,1742,1815,Polychrome woodblock print; ink and color on paper,H. 15 3/8 in. (39.1 cm); W. 10 15/16 in. (27.8 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56030,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1720,false,true,56031,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyonaga,"Japanese, 1752–1815",,Torii Kiyonaga,Japanese,1752,1815,,1742,1815,Diptych of polychrome woodblock prints; ink and color on paper,H. 15 1/8 in. (38.4 cm); W. 10 1/8 in. (25.7 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56031,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1721,false,true,56032,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyonaga,"Japanese, 1752–1815",,Torii Kiyonaga,Japanese,1752,1815,,1742,1815,Polychrome woodblock print; ink and color on paper,H. 14 7/8 in. (37.8 cm); w. 9 7/8 in. (25.1 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56032,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1722,false,true,56033,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyonaga,"Japanese, 1752–1815",,Torii Kiyonaga,Japanese,1752,1815,,1742,1815,Polychrome woodblock print; ink and color on paper,H. 14 13/16 in. (37.6 cm); W. 10 1/16 in. (25.6 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56033,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1723,false,true,56034,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyonaga,"Japanese, 1752–1815",,Torii Kiyonaga,Japanese,1752,1815,,1742,1815,Polychrome woodblock print; ink and color on paper,H. 15 1/4 in. (38.7 cm); W. 10 5/16 in. (26.2 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56034,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1725,false,true,56036,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyonaga,"Japanese, 1752–1815",,Torii Kiyonaga,Japanese,1752,1815,,1742,1815,Polychrome woodblock print; ink and color on paper,H. 14 13/16 in. (37.6 cm); W. 10 in. (25.4 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56036,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1727,false,true,56038,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyonaga,"Japanese, 1752–1815",,Torii Kiyonaga,Japanese,1752,1815,,1742,1815,Polychrome woodblock print; ink and color on paper,H. 27 in. (68.6 cm); W. 4 9/16 in. (11.6 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56038,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1728,false,true,56039,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyonaga,"Japanese, 1752–1815",,Torii Kiyonaga,Japanese,1752,1815,,1742,1815,Polychrome woodblock print; ink and color on paper,H. 27 1/8 in. (68.9 cm); W. 4 1/4 in. (10.8 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56039,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1730,false,true,56041,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyonaga,"Japanese, 1752–1815",,Torii Kiyonaga,Japanese,1752,1815,,1742,1815,Polychrome woodblock print; ink and color on paper,H. 27 1/8 in. (68.9 cm); W. 4 13/16 in. (12.1 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56041,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1731,false,true,45270,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyonaga,"Japanese, 1752–1815",,Torii Kiyonaga,Japanese,1752,1815,,1742,1815,Polychrome woodblock print (hashira-e); ink and color on paper,H. 27 3/8 in. (69.5 cm); W. 4 13/16 in. (12.2 cm),"H. O. Havemeyer Collection; Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45270,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1732,false,true,56042,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyonaga,"Japanese, 1752–1815",,Torii Kiyonaga,Japanese,1752,1815,,1742,1815,Polychrome woodblock print; ink and color on paper,Overall: 28 1/4 x 4 13/16 in. (71.8 x 12.2 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56042,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1733,false,true,56043,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyonaga,"Japanese, 1752–1815",,Torii Kiyonaga,Japanese,1752,1815,,1742,1815,Polychrome woodblock print; ink and color on paper,H. 25 5/8 in. (65.1 cm); W. 4 5/8 in. (11.7 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56043,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1735,false,true,56044,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyonaga,"Japanese, 1752–1815",,Torii Kiyonaga,Japanese,1752,1815,,1742,1815,Triptych of polychrome woodblock prints; ink and color on paper,a: H. 14 5/8 in. (37.1 cm); W. 9 11/16 in. (24.6 cm) b: H. 14 5/8 in. (37.1 cm); W. 9 11/16 in. (24.6 cm) c: H. 14 5/8 in. (37.1 cm); W. 9 15/16 in. (25.2 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56044,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2399,false,true,56802,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyonaga,"Japanese, 1752–1815",,Torii Kiyonaga,Japanese,1752,1815,,1742,1815,Polychrome woodblock print; ink and color on paper,15 x 9 13/16 in. (38.1 x 24.9 cm),"H. O. Havemeyer Collection, Gift of Mrs. J. Watson Webb, 1930",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56802,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3404,false,true,55591,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyonaga,"Japanese, 1752–1815",,Torii Kiyonaga,Japanese,1752,1815,,1742,1815,Polychrome woodblock print; ink and color on paper,10 1/4 x 7 5/8 in. (26 x 19.4 cm),"Bequest of Julia H. Manges, 1960",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55591,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2180,false,true,55094,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Yomo no Utagaki Magao,"Japanese, 1753–1829",,Yomo no Utagaki Magao,Japanese,1753,1829,,1753,1829,Polychrome woodblock print (surimono); ink and color on paper,7 9/16 x 2 3/8 in. (19.2 x 6 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55094,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1222,false,true,37317,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Chōbunsai Eishi,"Japanese, 1756–1829",,Chōbunsai Eishi,Japanese,1756,1829,,1756,1829,Diptych of polychrome woodblock prints; ink and color on paper,H. 14 3/8 in. (36.5 cm); W. 9 1/2 in. (24.1 cm),"Rogers Fund, 1921",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37317,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1781,false,true,56085,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Chōbunsai Eishi,"Japanese, 1756–1829",,Chōbunsai Eishi,Japanese,1756,1829,,1756,1829,Monochrome woodblock print; ink on paper,H. 10 5/8 in. (27 cm); W. 15 3/16 in. (38.6 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56085,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1782,false,true,56086,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Chōbunsai Eishi,"Japanese, 1756–1829",,Chōbunsai Eishi,Japanese,1756,1829,,1756,1829,Monochrome woodblock print; ink on paper,H. 13 5/8 in. (34.6 cm); W. 8 15/16 in. (22.7 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56086,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1783,false,true,56087,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Chōbunsai Eishi,"Japanese, 1756–1829",,Chōbunsai Eishi,Japanese,1756,1829,,1756,1829,Monochrome woodblock print; ink on paper,H. 14 in. (35.6 cm); W. 10 in. (25.4 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56087,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1784,false,true,56088,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Chōbunsai Eishi,"Japanese, 1756–1829",,Chōbunsai Eishi,Japanese,1756,1829,,1756,1829,Polychrome woodblock print; ink and color on paper,H. 14 5/8 in. (37.1 cm); W. 9 1/2 in. (24.1 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56088,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1785,false,true,56089,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Chōbunsai Eishi,"Japanese, 1756–1829",,Chōbunsai Eishi,Japanese,1756,1829,,1756,1829,Monochrome woodblock print; ink on paper,H. 14 3/4 in. (37.5 cm); W. 10 in. (25.4 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56089,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1787,false,true,56090,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Chōbunsai Eishi,"Japanese, 1756–1829",,Chōbunsai Eishi,Japanese,1756,1829,,1756,1829,Polychrome woodblock print; ink and color on paper,24 1/4 x 4 7/16 in. (61.6 x 11.3cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56090,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2186,false,true,55102,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Chōbunsai Eishi,"Japanese, 1756–1829",,Chōbunsai Eishi,Japanese,1756,1829,,1756,1829,Polychrome woodblock print (surimono); ink and color on paper,4 3/16 x 6 1/4 in. (10.6 x 15.9 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55102,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2398,false,true,56801,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Chōbunsai Eishi,"Japanese, 1756–1829",,Chōbunsai Eishi,Japanese,1756,1829,,1756,1829,Polychrome woodblock print; ink and color on paper,14 13/16 x 9 5/8 in. (37.6 x 24.4 cm),"H. O. Havemeyer Collection, Gift of Mrs. J. Watson Webb, 1930",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56801,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1096a,false,true,55031,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Chōbunsai Eishi,"Japanese, 1756–1829",,Chōbunsai Eishi,Japanese,1756,1829,,1756,1829,Polychrome woodblock print; ink and color on paper,Image: 14 3/4 × 9 7/8 in. (37.5 × 25.1 cm),The Metropolitan Museum of Art,,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55031,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1096b,false,true,639380,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Chōbunsai Eishi,"Japanese, 1756–1829",,Chōbunsai Eishi,Japanese,1756,1829,,1756,1829,Polychrome woodblock print; ink and color on paper,Image: 14 3/4 × 9 3/4 in. (37.5 × 24.8 cm),The Metropolitan Museum of Art,,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/639380,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1818,false,true,54406,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",,Kubo Shunman,Japanese,1757,1820,,1757,1820,Polychrome woodblock print (surimono); ink and color on paper,7 13/16 x 7 1/16 in. (19.8 x 17.9 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54406,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1902,false,true,54441,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",(?),Kubo Shunman,Japanese,1757,1820,,1757,1820,Polychrome woodblock print (surimono); ink and color on paper,5 1/8 x 7 3/16 in. (13 x 18.3 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54441,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1922,false,true,54461,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",,Kubo Shunman,Japanese,1757,1820,,1757,1820,Polychrome woodblock print (surimono); ink and color on paper,8 1/8 x 7 3/16 in. (20.6 x 18.3 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54461,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2032,false,true,54798,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",(?),Kubo Shunman,Japanese,1757,1820,,1757,1820,Polychrome woodblock print (surimono); ink and color on paper,8 1/16 x 7 1/4 in. (20.5 x 18.4 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54798,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2181,false,true,55097,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",,Kubo Shunman,Japanese,1757,1820,,1780,1850,Polychrome woodblock print (surimono); ink and color on paper,7 5/8 x 6 3/4 in. (19.4 x 17.1 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55097,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2184,false,true,55100,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",,Kubo Shunman,Japanese,1757,1820,,1615,1868,Polychrome woodblock print (surimono); ink and color on paper,5 9/16 x 7 1/8 in. (14.1 x 18.1 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55100,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2198,false,true,55115,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",,Kubo Shunman,Japanese,1757,1820,,1757,1820,Polychrome woodblock print (surimono); ink and color on paper,8 5/16 x 5 1/2 in. (21.1 x 14 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55115,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1780,false,true,56084,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shun'ei,"Japanese, 1762–1819",,Katsukawa Shun'ei,Japanese,1762,1819,,1762,1819,Polychrome woodblock print; ink and color on paper,H. 14 3/4 in. (37.5 cm); W. 9 7/16 in. (24 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56084,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3041,false,true,56497,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shun'ei,"Japanese, 1762–1819",,Katsukawa Shun'ei,Japanese,1762,1819,,1762,1819,Polychrome woodblock print; ink and color on paper,14 1/2 x 91/2 in. (36.8 x 24.1 cm),"Gift of Mrs. Morris Manges, in memory of her husband, Dr. Morris Manges, 1947",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56497,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3137,false,true,56685,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shun'ei,"Japanese, 1762–1819",,Katsukawa Shun'ei,Japanese,1762,1819,,1762,1819,Polychrome woodblock print; ink and color on paper,13 3/4 x 9 in. (34.9 x 22.9 cm),"Gift of Francis M. Weld, 1948",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56685,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1008,false,true,54884,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Toyohiro,"Japanese, 1763–1828",,Utagawa Toyohiro,Japanese,1763,1828,,1763,1828,Polychrome woodblock print; ink and color on paper,H. 14 3/8 in. (36.5 cm); W. 10 in. (25.4 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54884,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1819,false,true,56109,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Toyohiro,"Japanese, 1763–1828",,Utagawa Toyohiro,Japanese,1763,1828,,1763,1828,Polychrome woodblock print; ink and color on paper,H. 14 5/8 in. (37.1 cm); W. 9 11/16 in. (24.6 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56109,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1820,false,true,56110,Asian Art,Print,Yahashi Kiho|Sails Returning to Yahashi,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Toyohiro,"Japanese, 1763–1828",,Utagawa Toyohiro,Japanese,1763,1828,,1763,1828,Polychrome woodblock print; ink and color on paper,H. 8 13/16 in. (22.4 cm); W. 5 15/16 in. (15.1 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56110,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1821,false,true,54407,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Toyohiro,"Japanese, 1763–1828",,Utagawa Toyohiro,Japanese,1763,1828,,1763,1828,Polychrome woodblock print (surimono); ink and color on paper,6 13/16 x 8 1/8 in. (17.3 x 20.6 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54407,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2228,false,true,53994,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Toyohiro,"Japanese, 1763–1828",,Utagawa Toyohiro,Japanese,1763,1828,,1763,1828,Polychrome woodblock print (surimono); ink and color on paper,8 9/16 x 7 9/16 in. (21.7 x 19.2 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/53994,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1741,false,true,56048,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Toyokuni I,"Japanese, 1769–1825",,Utagawa Toyokuni I,Japanese,1769,1825,,1769,1825,Middle sheet of a triptych of polychrome woodblock prints; ink and color on paper,H. 15 1/2 in. (39.4 cm); W. 10 1/8 in. (25.7 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56048,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1742,false,true,56049,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Toyokuni I,"Japanese, 1769–1825",,Utagawa Toyokuni I,Japanese,1769,1825,,1769,1825,Polychrome woodblock print; ink and color on paper,H. 14 1/8 in. (35.9 cm); W. 10 in. (25.4 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56049,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1744,false,true,56051,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Toyokuni I,"Japanese, 1769–1825",,Utagawa Toyokuni I,Japanese,1769,1825,,1769,1825,Polychrome woodblock print; ink and color on paper,H. 15 1/8 in. (38.4 cm); W. 10 1/4 in. (26 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56051,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1745,false,true,56052,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Toyokuni I,"Japanese, 1769–1825",,Utagawa Toyokuni I,Japanese,1769,1825,,1769,1825,Polychrome woodblock print; ink and color on paper,H. 14 5/8 in. (37.1 cm); W. 10 1/8 in. (25.7 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56052,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1748,false,true,56055,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Toyokuni I,"Japanese, 1769–1825",,Utagawa Toyokuni I,Japanese,1769,1825,,1769,1825,Triptych of polychrome woodblock prints; ink and color on paper,A: H. 15 1/16 in. (38.3 cm); W. 9 3/4 in. (24.8 cm) B: H. 15 1/16 in. (38.3 cm); W. 9 13/16 in. (24.9 cm) C: H. 15 1/16 in. (38.3 cm); W. 9 15/16 in. (25.2 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56055,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1831,false,true,56116,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Toyokuni I,"Japanese, 1769–1825",,Utagawa Toyokuni I,Japanese,1769,1825,,1769,1825,Monochrome woodblock print; ink on paper,H. 10 1/2 in. (26.7 cm); W. 7 7/16 in. (18.9 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56116,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1832,false,true,56117,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Toyokuni I,"Japanese, 1769–1825",,Utagawa Toyokuni I,Japanese,1769,1825,,1769,1825,Monochrome woodblock print; ink on paper,H. 10 1/2 in. (26.7 cm); W. 7 1/8 in. (18.1 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56117,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2211,false,true,53977,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Toyokuni I,"Japanese, 1769–1825",,Utagawa Toyokuni I,Japanese,1769,1825,,1769,1825,Polychrome woodblock print (surimono); ink and color on paper,5 1/2 x 7 3/8 in. (14 x 18.7 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/53977,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2401,false,true,56804,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Toyokuni I,"Japanese, 1769–1825",,Utagawa Toyokuni I,Japanese,1769,1825,,1769,1825,Diptych of polychrome woodblock prints; ink and color on paper,Diptych; each 15 x 10 1/16 in. (38.1 x 25.6 cm),"H. O. Havemeyer Collection, Gift of Mrs. J. Watson Webb, 1930",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56804,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3062,false,true,56538,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Toyokuni I,"Japanese, 1769–1825",,Utagawa Toyokuni I,Japanese,1769,1825,,1615,1868,Polychrome woodblock print; ink and color on paper,14 3/4 x 9 3/4 in. (37.5 x 24.8 cm),"Gift of Mrs. Morris Manges, in memory of her husband, Dr. Morris Manges, 1947",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56538,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3154,false,true,56707,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Toyokuni I,"Japanese, 1769–1825",,Utagawa Toyokuni I,Japanese,1769,1825,,1769,1825,Polychrome woodblock print; ink and color on paper,10 1/4 x 14 5/8 in. (26 x 37.1 cm),"Gift of Mrs. Francis Ormond, 1949",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56707,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3155,false,true,56708,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Toyokuni I,"Japanese, 1769–1825",,Utagawa Toyokuni I,Japanese,1769,1825,,1769,1825,Polychrome woodblock print; ink and color on paper,10 x 14 in. (25.4 x 35.6 cm),"Gift of Mrs. Francis Ormond, 1949",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56708,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3392,false,true,55569,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,Woodblock for a print designed by,Utagawa Toyokuni I,"Japanese, 1769–1825",,Utagawa Toyokuni I,Japanese,1769,1825,,1615,1868,Polychrome woodblock print; ink and color on paper,14 x 8 7/8 in. (35.6 x 22.5 cm),"Gift of Mr. and Mrs. Arthur J. Steel, 1960",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55569,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1830,false,true,54427,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Teisai Hokuba,"Japanese, 1771–1844",,Teisai Hokuba,Japanese,1771,1844,,1771,1844,Polychrome woodblock print (surimono); ink and color on paper,5 1/2 x 11 1/8 in. (14 x 28.3 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54427,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2194,false,true,55110,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Teisai Hokuba,"Japanese, 1771–1844",,Teisai Hokuba,Japanese,1771,1844,,1771,1844,Polychrome woodblock print (surimono); ink and color on paper,8 1/8 x 7 5/16 in. (20.6 x 18.6 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55110,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2216,false,true,53982,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Teisai Hokuba,"Japanese, 1771–1844",,Teisai Hokuba,Japanese,1771,1844,,1771,1844,Polychrome woodblock print (surimono); ink and color on paper,8 7/16 x 7 5/8 in. (21.4 x 19.4 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/53982,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2161,false,true,55068,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Harukawa Goshichi,"Japanese, 1776–1831",,Harukawa Goshichi,Japanese,1776,1831,,1776,1831,Polychrome woodblock print (surimono); ink and color on paper,8 9/16 x 7 7/16 in. (21.7 x 18.9 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55068,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1099,false,true,54328,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kita Busei,"Japanese, 1776–1856",,Kita Busei,Japanese,1776,1856,,1776,1856,Polychrome woodblock print (surimono); ink and color on paper,7 3/16 x 10 3/4 in. (18.3 x 27.3 cm),"Rogers Fund, 1919",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54328,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3054a–e,false,true,56509,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Toyokuni II,"Japanese, 1777–1835",,Utagawa Toyokuni II,Japanese,1777,1835,,1615,1868,Pentaptych of polychrome woodblock prints; ink and color on paper,a: 15 1/2 x 10 1/4 in. (39.4 x 26 cm); b: 15 1/4 x 10 in. (38.7 x 25.4 cm); c: 15 3/8 x 10 1/2 in. (39.1 x 26.7 cm); d: 15 3/8 x 10 1/8 in. (39.1 x 25.7 cm); e: 15 1/4 x 10 1/2 in. (38.7 x 26.7 cm),"Gift of Mrs. Morris Manges, in memory of her husband, Dr. Morris Manges, 1947",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56509,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1034,false,true,54319,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Totoya Hokkei,"Japanese, 1780–1850",,Totoya Hokkei,Japanese,1780,1850,,1780,1850,Polychrome woodblock print (surimono); ink and color on paper,8 1/4 x 7 1/8 in. (21 x 18.1 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54319,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1466,false,true,55516,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Totoya Hokkei,"Japanese, 1780–1850",,Totoya Hokkei,Japanese,1780,1850,,1780,1850,Polychrome woodblock print; ink and color on paper,H. 10 1/2 in. (26.7 cm); W. 7 1/2 in. (19.1 cm),"Rogers Fund, 1925",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55516,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1467,false,true,55517,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Totoya Hokkei,"Japanese, 1780–1850",,Totoya Hokkei,Japanese,1780,1850,,1780,1850,Polychrome woodblock print; ink and color on paper,H. 10 1/2 in. (26.7 cm); W. 8 in. (20.3 cm),"Rogers Fund, 1925",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55517,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1810,false,true,54398,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Totoya Hokkei,"Japanese, 1780–1850",,Totoya Hokkei,Japanese,1780,1850,,1780,1850,Diptych of polychrome woodblock prints (surimono); ink and color on paper,Each print: 8 1/4 x 7 1/8 in. (21 x 18.1 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54398,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1812,false,true,54400,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Totoya Hokkei,"Japanese, 1780–1850",,Totoya Hokkei,Japanese,1780,1850,,1780,1850,Polychrome woodblock print (surimono); ink and color on paper,8 5/16 x 7 3/16 in. (21.1 x 18.3 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54400,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1813,false,true,54401,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Totoya Hokkei,"Japanese, 1780–1850",,Totoya Hokkei,Japanese,1780,1850,,1780,1850,Polychrome woodblock print (surimono); ink and color on paper,8 1/8 x 6 13/16 in. (20.6 x 17.3 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54401,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1871,false,true,54436,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Totoya Hokkei,"Japanese, 1780–1850",,Totoya Hokkei,Japanese,1780,1850,,1780,1850,Polychrome woodblock print (surimono); ink and color on paper,8 1/4 x 7 1/8 in. (21 x 18.1 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54436,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1915,false,true,54454,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Totoya Hokkei,"Japanese, 1780–1850",,Totoya Hokkei,Japanese,1780,1850,,1780,1850,Polychrome woodblock print (surimono); ink and color on paper,5 5/8 x 7 1/2 in. (14.3 x 19.1 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54454,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1937,false,true,54500,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Totoya Hokkei,"Japanese, 1780–1850",,Totoya Hokkei,Japanese,1780,1850,,1780,1850,Polychrome woodblock print (surimono); ink and color on paper,8 x 7 5/16 in. (20.3 x 18.6 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54500,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2185,false,true,55101,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Totoya Hokkei,"Japanese, 1780–1850",,Totoya Hokkei,Japanese,1780,1850,,1780,1850,Polychrome woodblock print (surimono); ink and color on paper,5 5/8 x 7 11/16 in. (14.3 x 19.5 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55101,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2219,false,true,53985,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Totoya Hokkei,"Japanese, 1780–1850",,Totoya Hokkei,Japanese,1780,1850,,1780,1850,Polychrome woodblock print (surimono); ink and color on paper,8 3/16 x 7 1/4 in. (20.8 x 18.4 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/53985,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2222,false,true,53988,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Totoya Hokkei,"Japanese, 1780–1850",,Totoya Hokkei,Japanese,1780,1850,,1780,1850,Polychrome woodblock print (surimono); ink and color on paper,5 1/2 x 7 1/4 in. (14 x 18.4 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/53988,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1092.1,false,true,58198,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,,1786,1854,Polychrome woodblock print; ink and color on paper,Image: 14 5/8 × 10 1/8 in. (37.1 × 25.7 cm),The Metropolitan Museum of Art,,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/58198,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1092.2,false,true,58199,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,,1786,1854,Polychrome woodblock print; ink and color on paper,Image: 14 in. × 10 1/16 in. (35.6 × 25.6 cm),The Metropolitan Museum of Art,,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/58199,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1092.3,false,true,58200,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,,1786,1854,Polychrome woodblock print; ink and color on paper,Image: 14 1/8 × 10 1/8 in. (35.9 × 25.7 cm),The Metropolitan Museum of Art,,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/58200,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1092.4,false,true,58201,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,,1786,1854,Polychrome woodblock print; ink and color on paper,Image: 14 5/8 × 9 7/8 in. (37.1 × 25.1 cm),The Metropolitan Museum of Art,,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/58201,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1092.5,false,true,58202,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,,1786,1854,Polychrome woodblock print; ink and color on paper,Image: 14 5/8 in. × 10 in. (37.1 × 25.4 cm),The Metropolitan Museum of Art,,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/58202,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1092.6,false,true,58203,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,,1786,1854,Polychrome woodblock print; ink and color on paper,Image: 14 1/8 × 9 7/8 in. (35.9 × 25.1 cm),The Metropolitan Museum of Art,,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/58203,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1092.7,false,true,58204,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,,1786,1854,Polychrome woodblock print; ink and color on paper,Image: 14 1/16 × 9 7/8 in. (35.7 × 25.1 cm),The Metropolitan Museum of Art,,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/58204,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1092.8,false,true,58205,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,,1786,1854,Polychrome woodblock print; ink and color on paper,Image: 14 5/8 in. × 10 in. (37.1 × 25.4 cm),The Metropolitan Museum of Art,,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/58205,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1092.9,false,true,58206,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,,1786,1854,Polychrome woodblock print; ink and color on paper,Image: 14 1/8 × 9 7/8 in. (35.9 × 25.1 cm),The Metropolitan Museum of Art,,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/58206,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1092.10,false,true,58207,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,,1786,1854,Polychrome woodblock print; ink and color on paper,Image: 14 3/8 × 9 7/8 in. (36.5 × 25.1 cm),The Metropolitan Museum of Art,,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/58207,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1092.11,false,true,58208,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,,1786,1854,Polychrome woodblock print; ink and color on paper,Image: 14 1/4 × 9 7/8 in. (36.2 × 25.1 cm),The Metropolitan Museum of Art,,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/58208,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1092.12,false,true,58209,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,,1786,1854,Polychrome woodblock print; ink and color on paper,Image: 14 5/8 × 10 1/16 in. (37.1 × 25.6 cm),The Metropolitan Museum of Art,,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/58209,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1092.13,false,true,58210,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,,1786,1854,Polychrome woodblock print; ink and color on paper,Image: 14 3/8 × 9 7/8 in. (36.5 × 25.1 cm),The Metropolitan Museum of Art,,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/58210,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1092.14,false,true,58211,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,,1786,1854,Polychrome woodblock print; ink and color on paper,Image: 14 1/4 in. × 10 in. (36.2 × 25.4 cm),The Metropolitan Museum of Art,,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/58211,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1092.15,false,true,58212,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,,1786,1854,Polychrome woodblock print; ink and color on paper,Image: 14 1/16 × 9 3/4 in. (35.7 × 24.8 cm),The Metropolitan Museum of Art,,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/58212,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1092.16,false,true,58213,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,,1786,1854,Polychrome woodblock print; ink and color on paper,Image: 14 1/2 × 9 3/4 in. (36.8 × 24.8 cm),The Metropolitan Museum of Art,,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/58213,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1092.17,false,true,58214,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,,1786,1854,Polychrome woodblock print; ink and color on paper,Image: 14 7/8 in. × 10 in. (37.8 × 25.4 cm),The Metropolitan Museum of Art,,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/58214,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1092.18,false,true,58215,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,,1786,1854,Polychrome woodblock print; ink and color on paper,Image: 13 15/16 × 9 3/8 in. (35.4 × 23.8 cm),The Metropolitan Museum of Art,,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/58215,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1092.19,false,true,58216,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,,1786,1854,Polychrome woodblock print; ink and color on paper,Image: 14 1/4 × 9 7/8 in. (36.2 × 25.1 cm),The Metropolitan Museum of Art,,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/58216,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1092.20,false,true,58217,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,,1786,1854,Polychrome woodblock print; ink and color on paper,Image: 14 1/4 × 9 7/8 in. (36.2 × 25.1 cm),The Metropolitan Museum of Art,,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/58217,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1092.21,false,true,58218,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,,1786,1854,Polychrome woodblock print; ink and color on paper,Image: 14 3/16 × 9 3/4 in. (36 × 24.8 cm),The Metropolitan Museum of Art,,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/58218,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1092.22,false,true,58219,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,,1786,1854,Polychrome woodblock print; ink and color on paper,Image: 14 3/16 in. × 10 in. (36 × 25.4 cm),The Metropolitan Museum of Art,,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/58219,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1092.23,false,true,58220,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,,1786,1854,Polychrome woodblock print; ink and color on paper,Image: 14 1/4 × 10 1/8 in. (36.2 × 25.7 cm),The Metropolitan Museum of Art,,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/58220,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1092.24,false,true,58221,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,,1786,1854,Polychrome woodblock print; ink and color on paper,Image: 14 3/4 × 10 1/8 in. (37.5 × 25.7 cm),The Metropolitan Museum of Art,,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/58221,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1092.25,false,true,58222,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,,1786,1854,Polychrome woodblock print; ink and color on paper,Image: 14 3/4 × 10 1/8 in. (37.5 × 25.7 cm),The Metropolitan Museum of Art,,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/58222,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1092.26,false,true,58223,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,,1786,1854,Polychrome woodblock print; ink and color on paper,Image: 14 7/8 × 10 1/8 in. (37.8 × 25.7 cm),The Metropolitan Museum of Art,,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/58223,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1092.27,false,true,58224,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,,1786,1854,Polychrome woodblock print; ink and color on paper,Image: 14 1/8 × 9 7/8 in. (35.9 × 25.1 cm),The Metropolitan Museum of Art,,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/58224,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1092.28,false,true,58225,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,,1786,1854,Polychrome woodblock print; ink and color on paper,Image: 14 1/4 in. × 10 in. (36.2 × 25.4 cm),The Metropolitan Museum of Art,,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/58225,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1092.29,false,true,58226,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,,1786,1854,Polychrome woodblock print; ink and color on paper,Image: 14 1/4 × 9 3/4 in. (36.2 × 24.8 cm),The Metropolitan Museum of Art,,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/58226,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1092.30,false,true,58227,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,,1786,1854,Polychrome woodblock print; ink and color on paper,Image: 14 1/8 × 9 7/8 in. (35.9 × 25.1 cm),The Metropolitan Museum of Art,,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/58227,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1092.31,false,true,58228,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,,1786,1854,Polychrome woodblock print; ink and color on paper,Image: 14 in. × 10 1/16 in. (35.6 × 25.6 cm),The Metropolitan Museum of Art,,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/58228,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1092.32,false,true,58229,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,,1786,1854,Polychrome woodblock print; ink and color on paper,Image: 14 5/8 in. × 10 in. (37.1 × 25.4 cm),The Metropolitan Museum of Art,,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/58229,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1092.33,false,true,58230,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,,1786,1854,Polychrome woodblock print; ink and color on paper,Image: 14 1/16 × 9 7/8 in. (35.7 × 25.1 cm),The Metropolitan Museum of Art,,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/58230,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1092.34,false,true,58231,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,,1786,1854,Polychrome woodblock print; ink and color on paper,Image: 13 5/8 × 9 7/8 in. (34.6 × 25.1 cm),The Metropolitan Museum of Art,,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/58231,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1092.35,false,true,58232,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,,1786,1854,Polychrome woodblock print; ink and color on paper,Image: 14 in. × 10 1/16 in. (35.6 × 25.6 cm),The Metropolitan Museum of Art,,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/58232,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1092.36,false,true,58233,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,,1786,1854,Polychrome woodblock print; ink and color on paper,Image: 14 3/4 in. × 10 in. (37.5 × 25.4 cm),The Metropolitan Museum of Art,,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/58233,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1092.37,false,true,58234,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,,1786,1854,Polychrome woodblock print; ink and color on paper,Image: 14 1/4 × 9 1/2 in. (36.2 × 24.1 cm),The Metropolitan Museum of Art,,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/58234,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1092.38,false,true,58235,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,,1786,1854,Polychrome woodblock print; ink and color on paper,Image: 14 3/8 × 9 7/8 in. (36.5 × 25.1 cm),The Metropolitan Museum of Art,,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/58235,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1092.39,false,true,58237,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,,1786,1854,Polychrome woodblock print; ink and color on paper,Image: 14 1/8 in. × 10 in. (35.9 × 25.4 cm),The Metropolitan Museum of Art,,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/58237,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1092.40,false,true,58239,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,,1786,1854,Polychrome woodblock print; ink and color on paper,Image: 14 3/8 in. × 10 in. (36.5 × 25.4 cm),The Metropolitan Museum of Art,,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/58239,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1795,false,true,56097,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kikugawa Eizan,"Japanese, 1787–1867",,Kikugawa Eizan,Japanese,1787,1867,,1787,1867,Polychrome woodblock print; ink and color on paper,H. 14 15/16 in. (37.9 cm); W. 10 3/4 in. (27.3 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56097,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1796,false,true,56099,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kikugawa Eizan,"Japanese, 1787–1867",,Kikugawa Eizan,Japanese,1787,1867,,1787,1867,Polychrome woodblock print; ink and color on paper,H. 24 1/16 in. (61.1 cm); W. 4 1/4 in. (10.8 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56099,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1797,false,true,56100,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kikugawa Eizan,"Japanese, 1787–1867",,Kikugawa Eizan,Japanese,1787,1867,,1787,1867,Polychrome woodblock print; ink and color on paper,24 x 4 1/16 in. (61 x 10.3cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56100,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1798,false,true,56101,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kikugawa Eizan,"Japanese, 1787–1867",,Kikugawa Eizan,Japanese,1787,1867,,1787,1867,Polychrome woodblock print; ink and color on paper,H. 23 9/16 in. (59.8 cm); W. 3 15/16 in. 910 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56101,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1799,false,true,56102,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kikugawa Eizan,"Japanese, 1787–1867",,Kikugawa Eizan,Japanese,1787,1867,,1787,1867,Triptych of polychrome woodblock prints; ink and color on paper,H. 14 5/8 in. (37.1 cm); W. 9 7/8 in (25.1 cm) H. 14 5/8 in. (37.1 cm); W. 9 13/16 in. (24.9 cm) H. 14 5/8 in. (37.1 cm); W. 10 1/8 in. (25.7 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56102,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3055,false,true,56510,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kikugawa Eizan,"Japanese, 1787–1867",,Kikugawa Eizan,Japanese,1787,1867,,1615,1868,Triptych of polychrome woodblock prints; ink and color on paper,11 5/8 x 16 5/8 in. (29.5 x 42.2 cm),"Gift of Mrs. Morris Manges, in memory of her husband, Dr. Morris Manges, 1947",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56510,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP554,false,true,37005,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Keisai Eisen,"Japanese, 1790–1848",,Keisai Eisen,Japanese,1790,1848,,1790,1848,Polychrome woodblock print; ink and color on paper,10 x 15 in. (25.4 x 38.1 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37005,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JIB170,false,true,57861,Asian Art,Prints,"「傾城道中双六 見立よしはら五十三つい」|Album of prints from the series A Tōkaidō Board Game of Courtesans, Fifty-three Pairings in the Yoshiwara (Keisei dōchū sugoroku, Mitate Yoshiwara gojūsan tsui)",Japan,Edo period (1615–1868),,,,Artist,,Keisai Eisen,"Japanese, 1790–1848",,Keisai Eisen,Japanese,1790,1848,,1790,1848,Fifty-six polychrome woodblock prints mounted as an album; ink and color on paper,11 in. × 14 3/4 in. (27.9 × 37.5 cm) Image (each): 9 3/8 × 13 1/4 in. (23.8 × 33.7 cm),"Gift of Suizan Miki, 1952",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/57861,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1229,false,true,55150,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Keisai Eisen,"Japanese, 1790–1848",,Keisai Eisen,Japanese,1790,1848,,1790,1848,Polychrome woodblock print; ink and color on paper,H. 13 1/2 in. (34.3 cm); W. 8 11/16 in. (22.1 cm),"Rogers Fund, 1921",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55150,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1822,false,true,56111,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Keisai Eisen,"Japanese, 1790–1848",,Keisai Eisen,Japanese,1790,1848,,1790,1848,Polychrome woodblock print; ink and color on paper,H. 14 3/16 in. (36 cm); W. 8 13/16 in. (22.4 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56111,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1823,false,true,54424,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Keisai Eisen,"Japanese, 1790–1848",,Keisai Eisen,Japanese,1790,1848,,1790,1848,Polychrome woodblock print (surimono); ink and color on paper,8 1/16 x 7 3/16 in. (20.5 x 18.3 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54424,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1301,false,true,42651,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Ichikawa Danjuro VII,"Japanese, 1791–1859",,Danjuro VII,Japanese,1791,1859,,1790,1860,Polychrome woodblock print (surimono); ink and color on paper,8 x 7 in. (20.3 x 17.8 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/42651,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1833,false,true,56118,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kuniyasu,"Japanese, 1794–1834",,Utagawa Kuniyasu,Japanese,1794,1834,,1794,1834,Monochrome woodblock print; ink on paper,H. 10 5/8 in. (27 cm); W. 7 7/16 in. (18.9 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56118,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1431,false,true,54390,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Suzuki Kiitsu,"Japanese, 1796–1858",,Suzuki Kiitsu,Japanese,1796,1858,,1798,1810,Polychrome woodblock print (surimono); ink and color on paper,8 1/8 x 7 3/16 in. (20.6 x 18.3 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54390,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1804,false,true,54397,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kuniyoshi,"Japanese, 1797–1861",,Utagawa Kuniyoshi,Japanese,1797,1861,,1797,1861,Polychrome woodblock print (surimono); ink and color on paper,8 1/16 x 7 1/8 in. (20.5 x 18.1 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54397,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP634,false,true,37085,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige II,"Japanese, 1829–1869",,Utagawa Hiroshige II,Japanese,1829,1869,,1829,1869,Polychrome woodblock print; ink and color on paper,8 7/16 x 13 1/2 in. (21.4 x 34.3 cm),"Purchase, Joseph Pulitzer Bequest, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37085,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1128,false,true,55048,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige II,"Japanese, 1829–1869",,Utagawa Hiroshige II,Japanese,1829,1869,,1829,1869,Polychrome woodblock print; ink and color on paper,H. 14 3/8 in. (36.5 cm); W. 9 1/2 in. (24.1 cm),"Rogers Fund, 1919",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55048,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP976,false,true,54856,Asian Art,Print,青楼歌舞妓やつし画尽 十番続|The Oiran Yoyogiku of Matsubaya Standing under a Cherry Tree,Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,,1754,1806,Polychrome woodblock print; ink and color on paper,H. 15 1/8 in. (38.4 cm); W. 9 7/8 in. (25.1 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54856,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP995,false,true,54874,Asian Art,Print,忠臣蔵二段目|A Young Man at the Side of a House,Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,,1754,1806,Polychrome woodblock print; ink and color on paper,H. 15 in. (38.1 cm); W. 10 3/8 in. (26.4 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54874,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP997,false,true,54875,Asian Art,Print,忠臣蔵五段目|A Woman Snatching a Bag of Sweetmeats from Her MotHer,Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,,1754,1806,Polychrome woodblock print; ink and color on paper,H. 14 1/8 in. (35.9 cm); W. 9 7/8 in. (25.1 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54875,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP998,false,true,54876,Asian Art,Print,"忠臣蔵八段目|Two Tori-oi, or Itinerant Women Musicians of the Eta Class",Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,,1754,1806,Polychrome woodblock print; ink and color on paper,H. 15 in. (38.1 cm); W. 10 1/2 in. (26.7 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54876,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP999,false,true,45476,Asian Art,Print,"忠臣蔵九段目|A Woman at Her Toilet Seated before a Mirror, Having Her Hair combed by a Kameyui (Woman Hairdresser)",Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,,1754,1806,Polychrome woodblock print; ink and color on paper,H. 15 1/8 in. (38.4 cm); W. 10 1/2 in. (26.7 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45476,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1663,false,true,40601,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,,1754,1806,Polychrome woodblock print; ink and color on paper,H. 14 3/4 in. (37.5 cm); W. 9 3/4 in. (24.8 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/40601,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1670,false,true,55896,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,,1754,1806,Polychrome woodblock print; ink and color on paper,H. 12 3/4 in. (32.4 cm); W. 7 3/4 in. (19.7 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55896,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1671,false,true,45478,Asian Art,Print,風流七小町 通ひ|Young MotHer Nursing Her Baby,Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,,1754,1806,Polychrome woodblock print; ink and color on paper,H. 10 in. (25.4 cm); W. 14 11/16 in. (37.3 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45478,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1678,false,true,55910,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,,1754,1806,Polychrome woodblock print; ink and color on paper,H. 23 1/2 in. (59.7 cm); W. 4 3/8 in. (11.1 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55910,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1680,false,true,37340,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,,1754,1806,Triptych of polychrome woodblock prints; ink and color on paper,14 1/8 x 28 23/32 in. (35.9 x 73.0 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37340,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1681,false,true,37341,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,,1754,1806,Triptych of polychrome woodblock prints; ink and color on paper,14 15/32 x 29 7/16 in. (36.8 x 74.8 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37341,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1682,false,true,37342,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,,1754,1806,Triptych of polychrome woodblock prints; ink and color on paper,15 1/8 x 28 13/16 in. (38.4 x 73.2 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37342,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1688,false,true,37345,Asian Art,Print,風流六玉川 武蔵 紀伊 陸奥|Women and a Man in the Country; Some pageant(?),Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,,1754,1806,Triptych of polychrome woodblock prints; ink and color on paper,A: H. 14 5/8 in. (37.21 cm); W. 9 3/8 in. (23.8 cm) B: H. 14 11/16 in. (37.31 cm); W. 9 9/16 in. (24. 31 cm) C: H. 14 11/16 in. (37.31 cm); W. 9 7/16 in. (24 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37345,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1689,false,true,37346,Asian Art,Print,風流六玉川 山城 近江 摂津|Women and Children on the Banks of a Stream,Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,,1754,1806,Triptych of polychrome woodblock prints; ink and color on paper,14 5/8 x 9 7/16 in. (37.21 x 23.9 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37346,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1690,false,true,55925,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,,1754,1806,Polychrome woodblock print; ink and color on paper,H. 29 1/2 in. (74.9 cm); W. 9 5/8 in. (24.4 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55925,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2394,false,true,56799,Asian Art,Print,名取酒六家選 大もんぢや内浅じふ 木綿屋七ッ梅|A Courtesan,Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,,1754,1806,Polychrome woodblock print; ink and color on paper,14 3/8 x 9 11/16 in. (36.5 x 24.6 cm),"H. O. Havemeyer Collection, Gift of Mrs. J. Watson Webb, 1930",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56799,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3157,false,true,56710,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,,1753,1806,Polychrome woodblock print; ink and color on paper,Image: 10 in. × 15 1/8 in. (25.4 × 38.4 cm) Mat: 15 1/2 × 22 3/4 in. (39.4 × 57.8 cm),"Anonymous Gift, 1951",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56710,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3158,false,true,56711,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,,1753,1806,Polychrome woodblock print; ink and color on paper,Image: 10 in. × 15 1/8 in. (25.4 × 38.4 cm) Image: 15 1/2 × 22 3/4 in. (39.4 × 57.8 cm),"Anonymous Gift, 1951",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56711,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3159,false,true,56712,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,,1753,1806,Polychrome woodblock print; ink and color on paper,Image: 10 in. × 15 1/8 in. (25.4 × 38.4 cm) Mat: 15 1/2 × 22 3/4 in. (39.4 × 57.8 cm),"Anonymous Gift, 1951",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56712,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3160,false,true,56713,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,,1753,1806,Polychrome woodblock print; ink and color on paper,Image: 10 in. × 15 1/8 in. (25.4 × 38.4 cm) Mat: 15 1/2 × 22 3/4 in. (39.4 × 57.8 cm),"Anonymous Gift, 1951",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56713,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3161,false,true,56714,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,,1753,1806,Polychrome woodblock print; ink and color on paper,Image: 10 in. × 15 1/8 in. (25.4 × 38.4 cm) Mat: 15 1/2 × 22 3/4 in. (39.4 × 57.8 cm),"Anonymous Gift, 1952",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56714,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3162,false,true,56715,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,,1753,1806,Polychrome woodblock print; ink and color on paper,Image: 10 in. × 15 1/8 in. (25.4 × 38.4 cm) Mat: 15 1/2 × 22 3/4 in. (39.4 × 57.8 cm),"Anonymous Gift, 1952",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56715,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3163,false,true,56716,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,,1753,1806,Polychrome woodblock print; ink and color on paper,Image: 10 in. × 15 1/8 in. (25.4 × 38.4 cm) Mat: 15 1/2 × 22 3/4 in. (39.4 × 57.8 cm),"Anonymous Gift, 1952",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56716,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3164,false,true,56717,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,,1753,1806,Polychrome woodblock print; ink and color on paper,Image: 10 in. × 15 1/8 in. (25.4 × 38.4 cm) Mat: 15 1/2 × 22 3/4 in. (39.4 × 57.8 cm),"Anonymous Gift, 1952",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56717,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3165,false,true,56718,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,,1753,1806,Polychrome woodblock print; ink and color on paper,Image: 10 in. × 15 1/8 in. (25.4 × 38.4 cm) Mat: 15 1/2 × 22 3/4 in. (39.4 × 57.8 cm),"Anonymous Gift, 1949",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56718,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3166,false,true,56719,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,,1753,1806,Polychrome woodblock print; ink and color on paper,Image: 10 in. × 15 1/8 in. (25.4 × 38.4 cm) Mat: 15 1/2 × 22 3/4 in. (39.4 × 57.8 cm),"Anonymous Gift, 1949",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56719,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3167,false,true,56720,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,,1753,1806,Polychrome woodblock print; ink and color on paper,Image: 10 in. × 15 1/8 in. (25.4 × 38.4 cm) Mat: 15 1/2 × 22 3/4 in. (39.4 × 57.8 cm),"Anonymous Gift, 1949",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56720,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3168,false,true,56721,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,,1753,1806,Polychrome woodblock print; ink and color on paper,Image: 10 in. × 15 1/8 in. (25.4 × 38.4 cm) Mat: 15 1/2 × 22 3/4 in. (39.4 × 57.8 cm),"Anonymous Gift, 1949",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56721,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1433,false,true,54391,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Yashima Gakutei,"Japanese, 1786?–1868",,Yashima Gakutei,Japanese,1786,1868,,1786,1868,Polychrome woodblock print (surimono); ink and color on paper,8 11/16 x 7 1/2 in. (22.1 x 19.1 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54391,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1825,false,true,54425,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Yashima Gakutei,"Japanese, 1786?–1868",,Yashima Gakutei,Japanese,1786,1868,,1786,1868,Polychrome woodblock print (surimono); ink and color on paper,7 7/8 x 6 7/8 in. (20 x 17.5 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54425,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1826,false,true,54426,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Yashima Gakutei,"Japanese, 1786?–1868",,Yashima Gakutei,Japanese,1786,1868,,1786,1868,Polychrome woodblock print (surimono); ink and color on paper,8 1/8 x 7 1/4 in. (20.6 x 18.4 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54426,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3606,false,true,54229,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Yashima Gakutei,"Japanese, 1786?–1868",,Yashima Gakutei,Japanese,1786,1868,,1786,1868,Polychrome woodblock print (surimono); ink and color on paper,7 7/8 x 6 1/3 in. (20 x 16.1 cm),"Seymour Fund, 1981",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54229,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3607,false,true,54230,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Yashima Gakutei,"Japanese, 1786?–1868",,Yashima Gakutei,Japanese,1786,1868,,1776,1878,Polychrome woodblock print (surimono); ink and color on paper,7 7/16 x 7 3/16 in. (18.9 x 18.3 cm),"Seymour Fund, 1981",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54230,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1926,false,true,54466,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Ryūgetsusai Shinkō,"Japanese, active 1810s",,Ryūgetsusai Shinkō,Japanese,1810,1819,,1810,1819,Polychrome woodblock print (surimono); ink and color on paper,8 5/16 x 10 9/16 in. (21.1 x 26.8 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54466,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1610,false,true,55770,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Okumura Toshinobu,active ca. 1725–1750,,Okumura Toshinobu,Japanese,1725,1750,,1715,1760,Polychrome woodblock print; ink and color on paper,H. 12 5/16 in. (31.3 cm); W. 5 5/8 in. (14.3 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55770,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3409,false,true,55598,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Okumura Toshinobu,active ca. 1725–1750,,Okumura Toshinobu,Japanese,1725,1750,,1725,1750,Polychrome woodblock print; ink and color on paper,12 1/4 x 5 3/4 in. (31.1 x 14.6 cm),"Bequest of Julia H. Manges, 1960",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55598,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3328,false,true,55476,Asian Art,Print,生写異国人物 清朝南京人感賞皇州扇之図|Two Chinese Men,Japan,Edo period (1615–1868),,,,Artist,,Utagawa (Gountei) Sadahide,"Japanese, 1807–1878/79",,Utagawa (Goutei) Sadahide,Japanese,1807,1879,,1807,1879,Polychrome woodblock print; ink and color on paper,14 1/4 x 10 in. (36.2 x 25.4 cm),"Gift of Lincoln Kirstein, 1959",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55476,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1306,false,true,55245,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Ryūkōsai,active late 18th century,,Ryūkōsai,Japanese,1771,1799,,1771,1799,Polychrome woodblock print; ink and color on paper,H. 12 1/16 in. (30.6 cm); W. 5 9/16 in. (14.1 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55245,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP880,false,true,54573,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Isoda Koryūsai,"Japanese, 1735–ca. 1790",,Isoda Koryūsai,Japanese,1735,1790,,1735,1790,Polychrome woodblock print; ink and color on paper,H. 9 7/8 in. (25.1 cm); W. 7 in. (17.8 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54573,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP891,false,true,54585,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Isoda Koryūsai,"Japanese, 1735–ca. 1790",,Isoda Koryūsai,Japanese,1735,1790,,1735,1790,Polychrome woodblock print; ink and color on paper,H. 10 1/8 in. (25.7 cm); W. 7 1/2 in. (19.1 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54585,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1693,false,true,55928,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Isoda Koryūsai,"Japanese, 1735–ca. 1790",,Isoda Koryūsai,Japanese,1735,1790,,1735,1790,Polychrome woodblock print; ink and color on paper,H. 10 1/8 in. (25.7 cm); W. 7 9/16 in. (19.2 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55928,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1694,false,true,55929,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Isoda Koryūsai,"Japanese, 1735–ca. 1790",,Isoda Koryūsai,Japanese,1735,1790,,1770,1790,Polychrome woodblock print; ink and color on paper,H. 10 1/8 in. (25.7 cm); W. 7 9/16 in. (19.2 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55929,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1695,false,true,55930,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Isoda Koryūsai,"Japanese, 1735–ca. 1790",,Isoda Koryūsai,Japanese,1735,1790,,1770,1790,Polychrome woodblock print; ink and color on paper,H. 10 1/4 in. (26 cm); W. 7 3/4 in. (19.7 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55930,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1696,false,true,55932,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Isoda Koryūsai,"Japanese, 1735–ca. 1790",,Isoda Koryūsai,Japanese,1735,1790,,1770,1790,Polychrome woodblock print; ink and color on paper,H. 8 1/8 in. (20.6 cm); W. 5 7/8 in. (14.9 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55932,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1697,false,true,55934,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Isoda Koryūsai,"Japanese, 1735–ca. 1790",,Isoda Koryūsai,Japanese,1735,1790,,1770,1790,Polychrome woodblock print; ink and color on paper,H. 9 1/2 in. (24.1 cm); W. 7 7/16 in. (18.9 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55934,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1698,false,true,55936,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Isoda Koryūsai,"Japanese, 1735–ca. 1790",,Isoda Koryūsai,Japanese,1735,1790,,1770,1790,Polychrome woodblock print; ink and color on paper,10 × 7 1/2 in. (25.4 × 19.1 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55936,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1699,false,true,55939,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Isoda Koryūsai,"Japanese, 1735–ca. 1790",,Isoda Koryūsai,Japanese,1735,1790,,1770,1790,Polychrome woodblock print; ink and color on paper,H. 10 in. (25.4 cm); W. 7 1/8 in. (18.1 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55939,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1700,false,true,55941,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Isoda Koryūsai,"Japanese, 1735–ca. 1790",,Isoda Koryūsai,Japanese,1735,1790,,1770,1790,Polychrome woodblock print; ink and color on paper,H. 10 1/4 in. (26 cm); W. 7 5/8 in. (19.4 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55941,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1701,false,true,55942,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Isoda Koryūsai,"Japanese, 1735–ca. 1790",,Isoda Koryūsai,Japanese,1735,1790,,1770,1790,Polychrome woodblock print; ink and color on paper,H. 10 1/4 in. (26 cm); W. 7 11/16 in. (19.5 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55942,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1702,false,true,55943,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Isoda Koryūsai,"Japanese, 1735–ca. 1790",,Isoda Koryūsai,Japanese,1735,1790,,1770,1790,Polychrome woodblock print; ink and color on paper,H. 10 5/16 in. (26.2 cm); W. 7 3/4 in. (19.7 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55943,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1705,false,true,55947,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Isoda Koryūsai,"Japanese, 1735–ca. 1790",,Isoda Koryūsai,Japanese,1735,1790,,1770,1790,Polychrome woodblock print; ink and color on paper,H. 27 3/4 in. (70.5 cm); W. 4 5/8 in. (11.7 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55947,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1706,false,true,55948,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Isoda Koryūsai,"Japanese, 1735–ca. 1790",,Isoda Koryūsai,Japanese,1735,1790,,1770,1790,Polychrome woodblock print; ink and color on paper,H. 26 3/8 in. (67 cm); W. 4 5/8 in. (11.7 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55948,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1707,false,true,55949,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Isoda Koryūsai,"Japanese, 1735–ca. 1790",,Isoda Koryūsai,Japanese,1735,1790,,1770,1790,Polychrome woodblock print; ink and color on paper,H. 26 1/8 in. (66.4 cm); W. 4 7/16 in. (11.3 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55949,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1708,false,true,55953,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Isoda Koryūsai,"Japanese, 1735–ca. 1790",,Isoda Koryūsai,Japanese,1735,1790,,1770,1790,Polychrome woodblock print; ink and color on paper,H. 26 1/4 in. (66.7 cm); W. 4 3/4 in. (12.1 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55953,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1709,false,true,55955,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Isoda Koryūsai,"Japanese, 1735–ca. 1790",,Isoda Koryūsai,Japanese,1735,1790,,1770,1790,Polychrome woodblock print; ink and color on paper,H. 27 9/16 in. (70 cm); W. 4 7/8 in. (12.4 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55955,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1710,false,true,55957,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Isoda Koryūsai,"Japanese, 1735–ca. 1790",,Isoda Koryūsai,Japanese,1735,1790,,1770,1790,Polychrome woodblock print; ink and color on paper,H. 27 5/8 in. (70.2 cm); W. 4 7/16 in. (11.3 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55957,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3140,false,true,56688,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Isoda Koryūsai,"Japanese, 1735–ca. 1790",,Isoda Koryūsai,Japanese,1735,1790,,1735,1790,Polychrome woodblock print; ink and color on paper,12 5/8 x 8 3/8 in. (32.1 x 21.3 cm),"Gift of Francis M. Weld, 1948",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56688,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3412,false,true,55601,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Isoda Koryūsai,"Japanese, 1735–ca. 1790",,Isoda Koryūsai,Japanese,1735,1790,,1735,1790,Polychrome woodblock print; ink and color on paper,10 1/2 x 7 1/2 in. (26.7 x 19.1 cm),"Bequest of Julia H. Manges, 1960",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55601,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2095,false,true,54941,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Shūchōdō Monoyana,"Japanese, 1761–ca. 1830",,Shūchōdō Monoyana,Japanese,1761,1830,,1761,1830,Polychrome woodblock print (surimono); ink and color on paper,5 7/8 x 6 15/16 in. (14.9 x 17.6 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54941,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1379,false,true,55365,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Suikōdō Sakei,"Japanese, active ca. 1764",,Suikōdō Sakei,Japanese,1754,1774,,1754,1774,Polychrome woodblock print; ink and color on paper,Image: 10 x 7 5/8 in. (25.4 x 19.4 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55365,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3398,false,true,55576,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Tosen,"Japanese, active ca. 1770",,Tosen,Japanese,1770,1770,,1760,1780,Polychrome woodblock print; ink and color on paper,27 x 5 1/4 in. (68.6 x 13.3 cm),"Bequest of Julia H. Manges, 1960",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55576,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3395,false,true,55572,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Hanekawa Chinchō,"Japanese, ca. 1679–1754",,Hanekawa Chinchō,Japanese,1679,1754,,1669,1754,Monochrome woodblock print; ink on paper,12 1/4 x 21 3/4 in. (31.1 x 55.2 cm),"Bequest of Julia H. Manges, 1960",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55572,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1808,false,true,56107,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Nishimura Shigenobu,"Japanese, active 1729–39",,Nishimura Shigenobu,Japanese,1729,1739,,1711,1785,Polychrome woodblock print; ink and color on paper,12 9/16 x 5 7/8 in. (31.9 x 14.9cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56107,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3056,false,true,56511,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Nishimura Shigenobu,"Japanese, active 1729–39",,Nishimura Shigenobu,Japanese,1729,1739,,1615,1868,Polychrome woodblock print; ink and color on paper,12 1/2 in. x 6 in. (31.8 x 15.2 cm),"Gift of Mrs. Morris Manges, in memory of her husband, Dr. Morris Manges, 1947",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56511,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1736,false,true,56045,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Tōshūsai Sharaku,"Japanese, active 1794–95",,Tōshūsai Sharaku,Japanese,1794,1795,,1749,1795,Monochrome woodblock print; ink on paper,H. 15 3/8 in. (39.1 cm); W. 10 7/8 in. (27.6 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56045,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1738,false,true,56046,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Tōshūsai Sharaku,"Japanese, active 1794–95",,Tōshūsai Sharaku,Japanese,1794,1795,,1749,1795,Polychrome woodblock print; ink and color on paper,H. 14 7/8 in. (37.8 cm); W. 9 3/4 in. (24.8 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56046,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1599,false,true,55763,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyomasu I,"Japanese, active 1696–1716",,Torii Kiyomasu I,Japanese,1696,1716,,1696,1716,Polychrome woodblock print; ink and color on paper,H. 12 1/8 in. (30.8 cm); W. 5 3/4 in. (14.6 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55763,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1774,false,true,56078,Asian Art,Design for a fan,,Japan,Edo period (1615–1868),,,,Artist,,Ippitsusai Bunchō,"Japanese, active 1760–1794",,Ippitsusai Bunchō,Japanese,1760,1794,,1723,1792,Monochrome woodblock print; ink on paper,H. 9 3/4 in. (24.8 cm); W. 13 7/16 in. (34. 1 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56078,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1775,false,true,56079,Asian Art,Design for a fan,,Japan,Edo period (1615–1868),,,,Artist,,Ippitsusai Bunchō,"Japanese, active 1760–1794",,Ippitsusai Bunchō,Japanese,1760,1794,,1723,1792,Monochrome woodblock print; ink on paper,H. 9 3/4 in. (24.8 cm); W. 13 1/2 in. (34.3 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56079,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1776,false,true,56080,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Ippitsusai Bunchō,"Japanese, active 1760–1794",,Ippitsusai Bunchō,Japanese,1760,1794,,1723,1792,Monochrome woodblock print; ink on paper,H. 9 14/16 in. (25.1 cm); W. 13 9/16 in. (34.4 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56080,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1777,false,true,56081,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Ippitsusai Bunchō,"Japanese, active 1760–1794",,Ippitsusai Bunchō,Japanese,1760,1794,,1723,1792,Polychrome woodblock print; ink and color on paper,H. 11 5/8 in. (29.5 cm); W. 5 5/8 in. (14.3 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56081,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1778,false,true,56082,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Ippitsusai Bunchō,"Japanese, active 1760–1794",,Ippitsusai Bunchō,Japanese,1760,1794,,1723,1792,Polychrome woodblock print; ink and color on paper,H. 11 1/2 in. (29.2 cm); W. 5 3/8 in. (13.7 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56082,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1779,false,true,56083,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Ippitsusai Bunchō,"Japanese, active 1760–1794",,Ippitsusai Bunchō,Japanese,1760,1794,,1723,1792,Polychrome woodblock print; ink and color on paper,H. 5 7/8 in. (14.9 cm); W. 11 1/2 in. (29.2 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56083,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3044,false,true,56499,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Ippitsusai Bunchō,"Japanese, active 1760–1794",,Ippitsusai Bunchō,Japanese,1760,1794,,1615,1868,Polychrome woodblock print; ink and color on paper,11 3/4 x 5 5/8 in. (29.8 x 14.3 cm),"Gift of Mrs. Morris Manges, in memory of her husband, Dr. Morris Manges, 1947",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56499,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3410,false,true,55599,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunzan,"Japanese, active 1782–1798",,Katsukawa Shunzan,Japanese,1782,1798,,1782,1798,Polychrome woodblock print; ink and color on paper,15 x 9 7/8 in. (38.1 x 25.1 cm),"Bequest of Julia H. Manges, 1960",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55599,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1809,false,true,56108,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Shotei Hokuju,"Japanese, active 1790–1820",,Shotei Hokuju,Japanese,1790,1820,,1790,1820,Polychrome woodblock print; ink and color on paper,10 x 14 15/16 in. (25.4 x 37.9 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56108,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3399,false,true,55578,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,Attributed to,Yuko,"Japanese, early 18th century",,Yuko,Japanese,1700,1750,,1700,1735,Polychrome woodblock print; ink and color on paper,12 x 5 3/8 in. (30.5 x 13.7 cm),"Bequest of Julia H. Manges, 1960",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55578,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1935,false,true,54475,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Shungensai,"Japanese, 18th–19th century",,Shungensai,Japanese,1700,1899,,1768,1868,Polychrome woodblock print (surimono); ink and color on paper,8 x 10 7/8 in. (20.3 x 27.6 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54475,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2091,false,true,54934,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Yukeisha,"Japanese, 18th–19th century",,Yukeisha,Japanese,1700,1899,,1768,1868,Polychrome woodblock print (surimono); ink and color on paper,5 3/8 x 7 1/16 in. (13.7 x 17.9 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54934,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1925,false,true,54465,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Toyoshige,"Japanese, active 1802?–?1835",,Utagawa Toyoshige,Japanese,1802,1835,,1802,1835,Polychrome woodblock print (surimono); ink and color on paper,8 1/8 x 5 1/2 in. (20.6 x 14 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54465,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1765,false,true,56070,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunchō,"Japanese, active ca. 1783–95",,Katsukawa Shunchō,Japanese,1775,1795,,1780,1795,Polychrome woodblock print; ink and color on paper,14 × 9 11/16 in. (35.6 × 24.6 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56070,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1766,false,true,56071,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunchō,"Japanese, active ca. 1783–95",,Katsukawa Shunchō,Japanese,1775,1795,,1780,1795,Middle sheet of a triptych of polychrome woodblock prints; ink and color on paper,15 1/8 × 9 7/8 in. (38.4 × 25.1 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56071,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1767,false,true,56072,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunchō,"Japanese, active ca. 1783–95",,Katsukawa Shunchō,Japanese,1775,1795,,1780,1795,Polychrome woodblock print; ink and color on paper,12 1/2 × 8 7/8 in. (31.8 × 22.5 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56072,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1768,false,true,56073,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunchō,"Japanese, active ca. 1783–95",,Katsukawa Shunchō,Japanese,1775,1795,,1780,1795,Polychrome woodblock print; ink and color on paper,25 3/8 × 4 1/2 in. (64.5 × 11.4 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56073,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1769,false,true,56074,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunchō,"Japanese, active ca. 1783–95",,Katsukawa Shunchō,Japanese,1775,1795,,1780,1795,Polychrome woodblock print; ink and color on paper,25 7/8 × 4 9/16 in. (65.7 × 11.6 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56074,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1771,false,true,56075,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunchō,"Japanese, active ca. 1783–95",,Katsukawa Shunchō,Japanese,1775,1795,,1780,1795,Polychrome woodblock print; ink and color on paper,27 3/4 × 4 13/16 in. (70.5 × 12.2 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56075,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1772,false,true,56076,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunchō,"Japanese, active ca. 1783–95",,Katsukawa Shunchō,Japanese,1775,1795,,1780,1795,Polychrome woodblock print; ink and color on paper,27 1/8 × 4 9/16 in. (68.9 × 11.6 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56076,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1773,false,true,56077,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunchō,"Japanese, active ca. 1783–95",,Katsukawa Shunchō,Japanese,1775,1795,,1780,1795,Diptych of polychrome woodblock prints; ink and color on paper,(a): 15 3/16 × 10 in. (38.6 × 25.4 cm) (b): 14 7/8 × 9 7/8 in. (37.8 × 25.1 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56077,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2402,false,true,56805,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunchō,"Japanese, active ca. 1783–95",,Katsukawa Shunchō,Japanese,1775,1795,,1780,1795,Diptych of polychrome woodblock prints; ink and color on paper,Image (each): 14 7/16 × 9 3/4 in. (36.7 × 24.8 cm),"H. O. Havemeyer Collection, Gift of Mrs. J. Watson Webb, 1930",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56805,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3182,false,true,53244,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Yoshitora,"Japanese, active ca. 1850–80",,Utagawa Yoshitora,Japanese,1845,1880,,1615,1868,Polychrome woodblock print; ink and color on paper,14 3/4 x 9 3/4 in. (37.5 x 24.8 cm),"GIft of Lincoln Kirstein, 1959",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/53244,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3325,false,true,55473,Asian Art,Print,"外国人物図畫仏蘭西|France, from the series Pictures of People from Foreign Lands (Gaikoku jinbutsu zuga)",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Yoshitora,"Japanese, active ca. 1850–80",,Utagawa Yoshitora,Japanese,1845,1880,,1850,1880,Polychrome woodblock print; ink and color on paper,14 x 9 5/8 in. (35.6 x 24.4 cm),"Gift of Lincoln Kirstein, 1959",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55473,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1807,false,true,56106,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Tsunegawa Shigenobu,"Japanese, active ca. 1724–1735",,Tsunegawa Shigenobu,Japanese,1724,1735,,1724,1735,Polychrome woodblock print; ink and color on paper,12 9/16 x 6 3/16 in. (31.9 x 15.7cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56106,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1213,false,true,55136,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Eiju,"Japanese, active ca. 1789–1801",,Eiju,Japanese,1789,1801,,1789,1801,Polychrome woodblock print; ink and color on paper,H. 15 7/16 in. (39.2 cm); W. 10 1/8 in. (25.7 cm),"Rogers Fund, 1920",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55136,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1792,false,true,56094,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Ichirakutei Eisui,"Japanese, active ca. 1793–1801",,Ichirakutei Eisui,Japanese,1793,1801,,1793,1801,Polychrome woodblock print; ink and color on paper,H. 13 3/16 in. (33.5 cm); W. 9 3/16 in. (23.3 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56094,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1793,false,true,56095,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Ichirakutei Eisui,"Japanese, active ca. 1793–1801",,Ichirakutei Eisui,Japanese,1793,1801,,1793,1801,Polychrome woodblock print; ink and color on paper,Image: 14 15/16 x 9 11/16 in. (37.9 x 24.6 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56095,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1105,false,true,54334,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Ryūryūkyo Shinsai,"Japanese, active ca. 1799–1823",,Ryūryūkyo Shinsai,Japanese,1799,1823,,1801,1813,Polychrome woodblock print (surimono); ink and color on paper,5 7/16 x 7 7/16 in. (13.8 x 18.9 cm),"Rogers Fund, 1919",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54334,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1249,false,true,54380,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Ryūryūkyo Shinsai,"Japanese, active ca. 1799–1823",,Ryūryūkyo Shinsai,Japanese,1799,1823,,1750,1835,Polychrome woodblock print (surimono); ink and color on paper,8 3/8 x 7 1/16 in. (21.3 x 17.9 cm),"Rogers Fund, 1921",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54380,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1814,false,true,54402,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Ryūryūkyo Shinsai,"Japanese, active ca. 1799–1823",,Ryūryūkyo Shinsai,Japanese,1799,1823,,1750,1835,Polychrome woodblock print (surimono); ink and color on paper,7 5/16 x 10 7/16 in. (18.6 x 26.5 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54402,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1815,false,true,54403,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Ryūryūkyo Shinsai,"Japanese, active ca. 1799–1823",,Ryūryūkyo Shinsai,Japanese,1799,1823,,1750,1835,Polychrome woodblock print (surimono); ink and color on paper,5 7/16 x 7 3/8 in. (13.8 x 18.7 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54403,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1816,false,true,54404,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Ryūryūkyo Shinsai,"Japanese, active ca. 1799–1823",,Ryūryūkyo Shinsai,Japanese,1799,1823,,1750,1835,Polychrome woodblock print (surimono) in shape of a twofold screen; ink and color on paper,8 1/4 x 7 3/8 in. (21 x 18.7 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54404,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1817,false,true,54405,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Ryūryūkyo Shinsai,"Japanese, active ca. 1799–1823",,Ryūryūkyo Shinsai,Japanese,1799,1823,,1750,1835,Polychrome woodblock print (surimono); ink and color on paper,5 3/8 x 7 3/8 in. (13.7 x 18.7 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54405,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1916,false,true,54455,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Ryūryūkyo Shinsai,"Japanese, active ca. 1799–1823",,Ryūryūkyo Shinsai,Japanese,1799,1823,,1750,1835,Polychrome woodblock print (surimono); ink and color on paper,5 1/4 x 7 1/4 in. (13.3 x 18.4 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54455,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1918,false,true,54457,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Ryūryūkyo Shinsai,"Japanese, active ca. 1799–1823",,Ryūryūkyo Shinsai,Japanese,1799,1823,,1750,1835,Polychrome woodblock print (surimono); ink and color on paper,8 1/4 x 7 5/16 in. (21 x 18.6 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54457,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1919,false,true,54458,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Ryūryūkyo Shinsai,"Japanese, active ca. 1799–1823",,Ryūryūkyo Shinsai,Japanese,1799,1823,,1750,1835,Polychrome woodblock print (surimono); ink and color on paper,8 3/8 x 7 5/16 in. (21.3 x 18.6 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54458,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1923,false,true,54462,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Ryūryūkyo Shinsai,"Japanese, active ca. 1799–1823",,Ryūryūkyo Shinsai,Japanese,1799,1823,,1799,1823,Polychrome woodblock print (surimono); ink and color on paper,8 1/4 x 3 5/8 in. (21 x 9.2 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54462,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1928,false,true,54468,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Ryūryūkyo Shinsai,"Japanese, active ca. 1799–1823",,Ryūryūkyo Shinsai,Japanese,1799,1823,,1799,1823,Polychrome woodblock print (surimono); ink and color on paper,6 x 7 3/8 in. (15.2 x 18.7 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54468,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1938,false,true,54502,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Ryūryūkyo Shinsai,"Japanese, active ca. 1799–1823",,Ryūryūkyo Shinsai,Japanese,1799,1823,,1750,1835,Polychrome woodblock print (surimono); ink and color on paper,5 11/16 x 7 1/2 in. (14.4 x 19.1 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54502,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1939,false,true,54503,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Ryūryūkyo Shinsai,"Japanese, active ca. 1799–1823",,Ryūryūkyo Shinsai,Japanese,1799,1823,,1750,1835,Polychrome woodblock print (surimono); ink and color on paper,5 13/16 x 7 3/4 in. (14.8 x 19.7 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54503,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2053,false,true,54828,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Ryūryūkyo Shinsai,"Japanese, active ca. 1799–1823",,Ryūryūkyo Shinsai,Japanese,1799,1823,,1750,1835,Polychrome woodblock print (surimono); ink and color on paper,5 5/8 x 3 7/8 in. (14.3 x 9.8 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54828,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2183,false,true,55099,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Ryūryūkyo Shinsai,"Japanese, active ca. 1799–1823",,Ryūryūkyo Shinsai,Japanese,1799,1823,,1750,1835,Polychrome woodblock print (surimono); ink and color on paper,8 3/16 x 11 in. (20.8 x 27.9 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55099,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2188,false,true,55104,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Ryūryūkyo Shinsai,"Japanese, active ca. 1799–1823",,Ryūryūkyo Shinsai,Japanese,1799,1823,,1750,1835,Polychrome woodblock print (surimono); ink and color on paper,5 3/8 x 7 1/4 in. (13.7 x 18.4 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55104,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2191,false,true,55107,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Ryūryūkyo Shinsai,"Japanese, active ca. 1799–1823",,Ryūryūkyo Shinsai,Japanese,1799,1823,,1750,1835,Polychrome woodblock print (surimono); ink and color on paper,8 1/16 x 7 1/4 in. (20.5 x 18.4 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55107,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2193,false,true,55109,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Ryūryūkyo Shinsai,"Japanese, active ca. 1799–1823",,Ryūryūkyo Shinsai,Japanese,1799,1823,,1750,1835,Polychrome woodblock print (surimono); ink and color on paper,5 3/4 x 7 1/2 in. (14.6 x 19.1 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55109,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1803,false,true,56105,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Hakusanjin Hokui,"Japanese, active ca. 1830–1840",,Hakusanjin Hokui,Japanese,1830,1840,,1830,1840,Polychrome woodblock print; ink and color on paper,9 x 12 1/2 in. (22.9 x 31.8cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56105,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3326,false,true,55474,Asian Art,Print,Furansukoku|仏蘭西国|France,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Yoshikazu,"Japanese, active ca. 1850–1870",,Utagawa Yoshikazu,Japanese,1845,1870,,1840,1860,Polychrome woodblock print; ink and color on paper,13 x 9 1/4 in. (33 x 23.5 cm),"Gift of Lincoln Kirstein, 1959",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55474,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2130,false,true,54985,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Uematsu Tōshū,"Japanese, active late 1810s–20s",,Uematsu Tōshū,Japanese,1810,1830,,1615,1868,Polychrome woodblock print (surimono); ink and color on paper,5 x 7 1/8 in. (12.7 x 18.1 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54985,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2131,false,true,54986,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Uematsu Tōshū,"Japanese, active late 1810s–20s",,Uematsu Tōshū,Japanese,1810,1830,,1615,1868,Polychrome woodblock print (surimono); ink and color on paper,5 1/8 x 7 in. (13 x 17.8 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54986,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2170,false,true,55083,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Uematsu Tōshū,"Japanese, active late 1810s–20s",,Uematsu Tōshū,Japanese,1810,1830,,1615,1868,Polychrome woodblock print (surimono); ink and color on paper,8 x 7 3/16 in. (20.3 x 18.3 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55083,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2223,false,true,53989,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Uematsu Tōshū,"Japanese, active late 1810s–20s",,Uematsu Tōshū,Japanese,1810,1830,,1760,1849,Polychrome woodblock print (surimono); ink and color on paper,5 1/8 x 10 3/16 in. (13 x 25.9 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/53989,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1020,false,true,54928,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Ippyotei Yoshikuni,"Japanese, active mid-19th century",,Ippyotei Yoshikuni,Japanese,1800,1899,,1836,1870,Polychrome woodblock print; ink and color on paper,H. 14 5/8 in. (37.1 cm); W. 10 1/4 in. (26 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54928,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1836,false,true,56121,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Ranshu,"Japanese, latter half of the 18th century",,Ranshu,Japanese,0018,0018,,1750,1799,Monochrome woodblock print; ink on paper,H. 9 3/4 in. (24.8 cm); W. 13 1/2 in. (34.3 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56121,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1015,false,true,54890,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,,1760,1849,Polychrome woodblock print; ink and color on paper,H. 9 3/4 in. (24.8 cm); W. 14 1/2 in. (36.8 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54890,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1108,false,true,54337,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,,1760,1850,Polychrome woodblock print (surimono); ink and color on paper,8 1/2 x 13 5/16 in. (21.6 x 33.8 cm),"Rogers Fund, 1919",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54337,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1844,false,true,56129,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,,1760,1849,Monochrome woodblock print; ink on paper,H. 14 15/16 in. (37.9 cm); W. 10 5/16 in. (26.2 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56129,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1845,false,true,56130,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,,1760,1849,Monochrome woodblock print; ink on paper,H. 15 in. (38.1 cm); W. 10 3/8 in. (26.4 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56130,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1853,false,true,54428,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,,1760,1849,Polychrome woodblock print (surimono); ink and color on paper,6 11/16 x 18 3/16 in. (17 x 46.2 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54428,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1857,false,true,45034,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,,1760,1849,Polychrome woodblock print; ink and color on paper,H. 7 7/8 in. (20 cm); W. 21 7/8 in. (55.6 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45034,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1858,false,true,56177,Asian Art,Print,市川団十郎|The Actor Ichikawa Danjuro I 1660–1704,Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,,1760,1849,Polychrome woodblock print; ink and color on paper,H. 12 in. (30.5 cm); W. 5 3/4 in. (14.6 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56177,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1862,false,true,56185,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,,1760,1849,Polychrome woodblock print; ink and color on paper,H. 13 1/16 in. (33.2 cm); W. 9 3/8 in. (23.8 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56185,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1863,false,true,56186,Asian Art,Print,七日夜|Wave,Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,,1760,1849,Monochrome woodblock print; ink on paper,H. 14 11/16 in. (37.3 cm); W. 10 11/16 in. (27.1 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56186,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1864,false,true,54429,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,,1760,1849,Polychrome woodblock print (surimono); ink and color on paper,7 3/4 x 27 7/16 in. (19.7 x 69.7 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54429,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1866,false,true,54431,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,,1760,1849,Polychrome woodblock print (surimono); ink and color on paper,5 1/8 x 7 5/16 in. (13 x 18.6 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54431,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1867,false,true,54432,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,,1760,1849,Polychrome woodblock print (surimono); ink and color on paper,7 7/8 x 7 1/4 in. (20 x 18.4 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54432,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1869,false,true,54434,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,,1760,1849,Polychrome woodblock print (surimono); ink and color on paper,6 5/16 x 12 7/16 in. (16 x 31.6 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54434,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1872,false,true,54437,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,,1760,1849,Polychrome woodblock print (surimono); ink and color on paper,8 1/16 x 7 1/4 in. (20.5 x 18.4 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54437,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2150,false,true,55011,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,,1760,1849,Polychrome woodblock print (surimono); ink and color on paper,7 1/2 x 10 3/8 in. (19.1 x 26.4 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55011,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2177,false,true,55091,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,,1760,1849,Polychrome woodblock print (surimono); ink and color on paper,7 1/2 x 21 1/4 in. (19.1 x 54 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55091,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2392,false,true,56797,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,,1760,1849,Polychrome woodblock print; ink and color on paper,9 13/16 x 13 1/16 in. (24.9 x 33.2 cm),"H. O. Havemeyer Collection, Gift of Mrs. J. Watson Webb, 1930",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56797,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2551,false,true,56977,Asian Art,Woodblock print,"百人一首 うばがゑとき 三条院|Poem by Sanjō-in, from the series One Hundred Poems Explained by the Nurse (Hyakunin isshu uba ga etoki)",Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,,1760,1849,Polychrome woodblock print; ink and color on paper,Overall: 10 1/4 x 14 3/4 in. (26 x 37.5 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56977,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1312,false,true,45294,Asian Art,Print,Ryogoku no yoizuki|東都名所 両国之宵月|Twilight Moon at Ryōgoku Bridge,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,,1797,1858,Polychrome woodblock print; ink and color on paper,H. 9 5/8 in. (24.4 cm); W. 15 3/16 in. (38.6 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45294,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1314,false,true,55271,Asian Art,Print,東都名所 新吉原朝桜之図|Morning Cherries at Yoshiwara,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,,1797,1858,Polychrome woodblock print; ink and color on paper,H. 9 7/16 in. (24 cm); W. 15 1/8 in. (38.4 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55271,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1315,false,true,45296,Asian Art,Print,"Shibaura, shiohi-gari no zu|東都名所 芝浦汐干之図|Shell Gathering at Shibaura",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,,1797,1858,Polychrome woodblock print; ink and color on paper,H. 9 7/8 in. (25.1 cm); W. 15 in. (38.1 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45296,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1316,false,true,55273,Asian Art,Print,江都名所 飛鳥山はな見|Asukayama Hanami,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,,1797,1858,Polychrome woodblock print; ink and color on paper,H. 9 13/16 in. (24.9 cm); W. 14 5/8 in. (37.1 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55273,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1318,false,true,55277,Asian Art,Print,京都名所之内 清水|Kiyomizu,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,,1797,1858,Polychrome woodblock print; ink and color on paper,H. 9 1/2 in. (24.1 cm); W. 14 11/16 in. (37.3 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55277,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1320,false,true,55279,Asian Art,Print,東都名所 目黒行人阪之図|Meguro Gionin Zaka,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,,1797,1858,Polychrome woodblock print; ink and color on paper,H. 9 1/2 in. (24.1 cm); W. 14 1/4 in. (36.2 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55279,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1321,false,true,55280,Asian Art,Print,東都名所 品川大井 八景坂鎧掛松|Shinagawa Hakkei Zaka,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,,1797,1858,Polychrome woodblock print; ink and color on paper,H. 9 7/16 in. (24 cm); W. 14 1/4 in. (36.2 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55280,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1861,false,true,56184,Asian Art,Print,東都名所 芝増上寺雪中ノ図|Zojoji Temple at Shiba in Snow,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,,1797,1858,Polychrome woodblock print; ink and color on paper,H. 10 in. (25.4 cm); W. 14 3/4 in. (37.5 cm),"Gift of Mrs. Henry J. Bernheim, 1945",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56184,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1874,false,true,56188,Asian Art,Print,近江八景之内 唐崎夜雨|Evening Rain on the Karasaki Pine,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,,1797,1858,Polychrome woodblock print; ink and color on paper,H. 10 1/4 in. (26 cm); W. 15 in. (38.1 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56188,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1875,false,true,56191,Asian Art,Print,近江八景之内 瀬田夕照|Sunset at Seta,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,,1797,1858,Polychrome woodblock print; ink and color on paper,H. 8 15/16 in. (22.7 cm); W. 13 7/8 in. (35.2 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56191,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1878,false,true,56259,Asian Art,Print,"東海道五十三次 見附 天竜川図|Mitsuke; Tenryugawa Ferry, Station No. 29",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,,1797,1858,Polychrome woodblock print; ink and color on paper,8 7/8 x 13 11/16 in. (22.5 x 34.8cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56259,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1879,false,true,56293,Asian Art,Print,"木曽海道六拾九次之内 長久保|Nagakubo, Station No. 28",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,,1797,1858,Polychrome woodblock print; ink and color on paper,H. 8 5/8 in. (21.9 cm); W. 13 1/8 in. (33.3 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56293,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1881,false,true,56294,Asian Art,Print,"本朝名所 相州江ノ嶋岩屋之図|Sōshū, Enoshima Iwaya no Zu",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,,1797,1858,Polychrome woodblock print; ink and color on paper,H. 8 7/8 in. (22.5 cm); W. 14 in. (35.6 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56294,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1882,false,true,56514,Asian Art,Print,"本朝名所 相州七里ヶ浜|Seven-ri Beach, Province of Soshu",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,,1797,1858,Polychrome woodblock print; ink and color on paper,H. 9 1/8 in. (23.2 cm); W. 14 1/16 in. (35.7 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56514,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1883,false,true,56515,Asian Art,Print,東都名所 猿若町芝居の図|Picture of the Theatres in Sakai Cho,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,,1797,1858,Polychrome woodblock print; ink and color on paper,H. 8 15/16 in. (22.7 cm); W. 13 5/8 in. (34.6 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56515,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1884,false,true,56516,Asian Art,Print,東都名所 真崎雪晴ノ図|Clearing Weather after Snow at Massaki,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,,1797,1858,Polychrome woodblock print; ink and color on paper,H. 9 7/8 in. (25.1 cm); W. 13 7/16 in. (34.1 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56516,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1885,false,true,56517,Asian Art,Print,京都名所之内 糺川原之夕立|Tea-houses on the Bank of the Tadasu River in a Shower,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,,1797,1858,Polychrome woodblock print; ink and color on paper,H. 8 1/2 in. (21.6 cm); W. 13 11/16 in. (34.8 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56517,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1886,false,true,56518,Asian Art,Print,京都名所之内 あらし山満花|Arashiyama Manka,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,,1797,1858,Polychrome woodblock print; ink and color on paper,H. 8 3/4 in. (22.2 cm); W. 14 in. (35.6 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56518,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1888,false,true,56520,Asian Art,Print,東都名所 佃島海辺朧月|Tsukudajima no Oborozuki,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,,1797,1858,Polychrome woodblock print; ink and color on paper,H. 14 3/8 in. (36.5 cm); W. 4 15/16 in. (12.5 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56520,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1890,false,true,56521,Asian Art,Print,江戸十二景 隅田川|Gotenyama-no Hana,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,,1797,1858,Polychrome woodblock print; ink and color on paper,H. 4 7/16 in. (11.3 cm); W. 6 5/8 in. (16.8 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56521,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1891,false,true,56589,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,,1797,1858,Polychrome woodblock print; ink and color on paper,H. 4 7/16 in. (11.3 cm); W. 6 5/8 in. (16.8 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56589,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1896,false,true,56592,Asian Art,Print,小松にきじ|Pheasant and Pine-trees on Snowy Hillside,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,,1797,1858,Polychrome woodblock print; ink and color on paper,H. 14 7/8 in. (37.8 cm); W. 6 3/4 in. (17.1 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56592,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1897,false,true,56795,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,,1797,1858,Polychrome woodblock print; ink and color on paper,13 1/16 x 4 5/16 in. (33.2 x 11 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56795,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2400,false,true,56803,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,,1797,1858,Polychrome woodblock print; ink and color on paper,9 1/16 x 4 1/16 in. (23 x 10.3 cm) Probably harimazé (mixed print),"H. O. Havemeyer Collection, Gift of Mrs. J. Watson Webb, 1930",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56803,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2460,false,true,56886,Asian Art,Print,東都名所 両国之宵月|Twilight Moon at Ryōgoku Bridge,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,,1797,1858,Polychrome woodblock print; ink and color on paper,13 3/4 x 8 1/2 in. (34.9 x 21.6 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56886,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2873,false,true,57039,Asian Art,Print,木曽海道六拾九次之内 望月|Mochizuki,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,,1797,1858,Polychrome woodblock print; ink and color on paper,9 1/4 x 14 3/4 in. (23.5 x 37.5 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/57039,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3134,false,true,56682,Asian Art,Print,雪月花 木曽路之山川|Kiso Gorge in the Snow,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,,1797,1858,Triptych of polychrome woodblock prints; ink and color on paper,14 x 29 5/8 in. (35.6 x 75.2 cm),"Gift of Francis M. Weld, 1948",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56682,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3135,false,true,56683,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,,1615,1868,Polychrome woodblock print; ink and color on paper,28 3/4 x 9 1/2 in. (73 x 24.1 cm),"Gift of Francis M. Weld, 1948",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56683,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3141,false,true,56690,Asian Art,Print,月二拾八景之内 弓張月|Bow Moon,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,,1797,1858,Polychrome woodblock print; ink and color on paper,14 7/8 x 6 1/4 in. (37.8 x 15.9 cm),"Gift of Francis M. Weld, 1948",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56690,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3144,false,true,56695,Asian Art,Print,近江八景之内 堅田落雁|Geese Flying Down to Katada,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,,1797,1858,Polychrome woodblock print; ink and color on paper,13 3/8 x 8 5/8 in. (34 x 21.9 cm),"Bequest of Ellis G. Seymour, 1949",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56695,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3145,false,true,56696,Asian Art,Print,近江八景之内 瀬田夕照|Long Bridge of Seta,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,,1797,1858,Polychrome woodblock print; ink and color on paper,13 3/4 x 8 3/4 in. (34.9 x 22.2 cm),"Bequest of Ellis G. Seymour, 1949",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56696,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3147,false,true,56698,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,,1797,1858,Polychrome woodblock print; ink and color on paper,14 5/8 x 5 in. (37.1 x 12.7 cm),"Bequest of Ellis G. Seymour, 1949",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56698,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3149,false,true,56700,Asian Art,Print,梅に三光鳥|Long Tailed Bird,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,,1797,1858,Polychrome woodblock print; ink and color on paper,14 1/2 x 6 3/4 in. (36.8 x 17.1 cm),"Bequest of Ellis G. Seymour, 1949",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56700,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3150,false,true,56702,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,,1797,1858,Polychrome woodblock print; ink and color on paper,12 3/4 x 2 3/8 in. (32.4 x 6 cm),"Bequest of Ellis G. Seymour, 1949",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56702,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3151,false,true,56703,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,,1615,1868,Polychrome woodblock print; ink and color on paper,13 1/8 x 4 5/16 in. (33.3 x 11 cm),"Bequest of Ellis G. Seymour, 1949",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56703,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3152,false,true,56705,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,,1797,1858,Polychrome woodblock print; ink and color on paper,8 7/8 x 6 1/2 in. (22.5 x 16.5 cm),"Bequest of Ellis G. Seymour, 1949",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56705,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3426,false,true,55628,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,,1797,1858,Polychrome woodblock print; ink and color on paper,27 3/4 x 9 3/8 in. (70.5 x 23.8 cm),"Bequest of Gertrude Abbot Phillips, 1965",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55628,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3436,false,true,55637,Asian Art,Print,江戸名所 芝愛宕山|Shiba Atogayama,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,,1615,1868,Polychrome woodblock print; ink and color on paper,10 x 14 1/2 in. (25.4 x 36.8 cm),"Gift of Cole J. Younger, 1975",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55637,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3437,false,true,55638,Asian Art,Print,江都名所 飛鳥山はな見|Asukayama Hanami,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,,1615,1868,Polychrome woodblock print; ink and color on paper,9 1/2 x 14 1/2 in. (24.1 x 36.8 cm),"Gift of Cole J. Younger, 1975",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55638,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3511,false,true,45297,Asian Art,Print,"Shin-Yoshiwara, asazukura no zu|東都名所 新吉原朝桜之図|Morning Cherry Blossoms at Shin-Yoshiwara",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,,1797,1858,Polychrome woodblock print; ink and color on paper,10 1/4 x 15 1/8 in. (26 x 38.4 cm),"The Harry G. C. Packard Collection of Asian Art, Gift of Harry G. C. Packard, and Purchase, Fletcher, Rogers, Harris Brisbane Dick, and Louis V. Bell Funds, Joseph Pulitzer Bequest, and The Annenberg Fund Inc. Gift, 1975",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45297,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3513,false,true,55808,Asian Art,Woodblock print,梅に三光鳥|Bird on a Plum Branch,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,,1615,1868,Polychrome woodblock print; ink and color on paper,15 1/4 x 6 3/4 in. (38.7 x 17.1 cm),"The Harry G. C. Packard Collection of Asian Art, Gift of Harry G. C. Packard, and Purchase, Fletcher, Rogers, Harris Brisbane Dick, and Louis V. Bell Funds, Joseph Pulitzer Bequest, and The Annenberg Fund Inc. Gift, 1975",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55808,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3514,false,true,55809,Asian Art,Print,花菖蒲に白鷺|White Heron and Iris,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,,1800,1858,Polychrome woodblock print; ink and color on paper,15 3/8 x 6 1/2 in. (39.1 x 16.5 cm),"The Harry G. C. Packard Collection of Asian Art, Gift of Harry G. C. Packard, and Purchase, Fletcher, Rogers, Harris Brisbane Dick, and Louis V. Bell Funds, Joseph Pulitzer Bequest, and The Annenberg Fund Inc. Gift, 1975",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55809,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3515,false,true,55812,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,,1615,1868,Polychrome woodblock print; ink and color on paper,14 1/2 x 4 7/8 in. (36.8 x 12.4 cm),"The Harry G. C. Packard Collection of Asian Art, Gift of Harry G. C. Packard, and Purchase, Fletcher, Rogers, Harris Brisbane Dick, and Louis V. Bell Funds, Joseph Pulitzer Bequest, and The Annenberg Fund Inc. Gift, 1975",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55812,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3509a–c,false,true,55787,Asian Art,Print,"雪月花 武陽金沢八勝夜景|Full Moon at Kanazawa, Province of Musashi",Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,,1797,1858,Triptych of polychrome woodblock prints; ink and color on paper,L. sheet: 9 15/16 x 14 5/8 in. (25.2 x 37.1 cm); C. sheet: 14 21/32 x 9 29/32 in. (37.2 x 25.2 cm); R. sheet: 14 21/32 x 9 15/16 in. (37.2 x 25.2 cm),"The Harry G. C. Packard Collection of Asian Art, Gift of Harry G. C. Packard, and Purchase, Fletcher, Rogers, Harris Brisbane Dick, and Louis V. Bell Funds, Joseph Pulitzer Bequest, and The Annenberg Fund Inc. Gift, 1975",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55787,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -MJP28,false,true,57135,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,After,Okumura Masanobu,"Japanese, 1686–1764",,Masanobu Okumura,Japanese,1686,1764,,1868,1912,Monochrome woodblock print; ink on paper,10 1/4 x 14 11/16 in. (26 x 37.3 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/57135,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -MJP29,false,true,57136,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,After,Okumura Masanobu,"Japanese, 1686–1764",,Masanobu Okumura,Japanese,1686,1764,,1868,1912,Monochrome woodblock print; ink on paper,10 1/4 x 14 11/16 in. (26 x 37.3 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/57136,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -MJP30,false,true,57137,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,After,Okumura Masanobu,"Japanese, 1686–1764",,Masanobu Okumura,Japanese,1686,1764,,1868,1912,Monochrome woodblock print; ink on paper,10 1/4 x 14 11/16 in. (26 x 37.3 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/57137,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -MJP31,false,true,57140,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,After,Okumura Masanobu,"Japanese, 1686–1764",,Masanobu Okumura,Japanese,1686,1764,,1868,1912,Monochrome woodblock print; ink on paper,10 1/4 x 14 11/16 in. (26 x 37.3 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/57140,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -MJP32,false,true,57141,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,After,Okumura Masanobu,"Japanese, 1686–1764",,Masanobu Okumura,Japanese,1686,1764,,1868,1912,Monochrome woodblock print; ink on paper,10 1/4 x 14 11/16 in. (26 x 37.3 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/57141,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -MJP33,false,true,57142,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,After,Okumura Masanobu,"Japanese, 1686–1764",,Masanobu Okumura,Japanese,1686,1764,,1868,1912,Monochrome woodblock print; ink on paper,10 1/4 x 14 11/16 in. (26 x 37.3 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/57142,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -MJP34,false,true,57143,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,After,Okumura Masanobu,"Japanese, 1686–1764",,Masanobu Okumura,Japanese,1686,1764,,1868,1912,Monochrome woodblock print; ink on paper,10 1/4 x 14 11/16 in. (26 x 37.3 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/57143,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -MJP35,false,true,57144,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,After,Okumura Masanobu,"Japanese, 1686–1764",,Masanobu Okumura,Japanese,1686,1764,,1868,1912,Monochrome woodblock print; ink on paper,10 1/4 x 14 11/16 in. (26 x 37.3 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/57144,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -MJP36,false,true,57145,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,After,Okumura Masanobu,"Japanese, 1686–1764",,Masanobu Okumura,Japanese,1686,1764,,1868,1912,Monochrome woodblock print; ink on paper,10 1/4 x 14 11/16 in. (26 x 37.3 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/57145,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -MJP37,false,true,57146,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,After,Okumura Masanobu,"Japanese, 1686–1764",,Masanobu Okumura,Japanese,1686,1764,,1868,1912,Monochrome woodblock print; ink on paper,10 1/4 x 14 11/16 in. (26 x 37.3 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/57146,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -MJP38,false,true,57147,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,After,Okumura Masanobu,"Japanese, 1686–1764",,Masanobu Okumura,Japanese,1686,1764,,1868,1912,Monochrome woodblock print; ink on paper,10 1/4 x 14 11/16 in. (26 x 37.3 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/57147,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -MJP39,false,true,57148,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,After,Okumura Masanobu,"Japanese, 1686–1764",,Masanobu Okumura,Japanese,1686,1764,,1868,1912,Monochrome woodblock print; ink on paper,10 1/4 x 14 11/16 in. (26 x 37.3 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/57148,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3712,false,true,55976,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Watanabe Seitei,"Japanese, 1851–1918",,Watanabe Seitei,Japanese,1851,1918,,1851,1918,Frontispiece; polychrome woodblock print; ink and color on paper,Album: 8 1/2 x 5 5/16 in. (21.6 x 13.5 cm),"Gift of Donald Keene, in honor of Julia Meech-Pekarik, 1986",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55976,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3709,false,true,55967,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Mishima Shōsō,"Japanese, 1856–1928",,Mishima Shōsō,Japanese,1856,1928,,1856,1928,Frontispiece; polychrome woodblock print; ink and color on paper,Album: 8 1/2 x 5 5/16 in. (21.6 x 13.5 cm),"Gift of Donald Keene, in honor of Julia Meech-Pekarik, 1986",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55967,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3710,false,true,55970,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Mishima Shōsō,"Japanese, 1856–1928",,Mishima Shōsō,Japanese,1856,1928,,1868,1912,Frontispiece; polychrome woodblock print; ink and color on paper,Album: 8 1/2 x 5 5/16 in. (21.6 x 13.5 cm),"Gift of Donald Keene, in honor of Julia Meech-Pekarik, 1986",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55970,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3714,false,true,55978,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Suzuki Kason,"Japanese, 1860–1919",,Suzuki Kason,Japanese,1860,1919,,1868,1912,Polychrome woodblock print; ink and color on paper,Album: 8 1/2 x 5 5/16 in. (21.6 x 13.5 cm),"Gift of Donald Keene, in honor of Julia Meech-Pekarik, 1986",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55978,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3715,false,true,55979,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Suzuki Kason,"Japanese, 1860–1919",,Suzuki Kason,Japanese,1860,1919,,1860,1919,Frontispiece; polychrome woodblock print; ink and color on paper,Album: 8 1/2 x 5 5/16 in. (21.6 x 13.5 cm),"Gift of Donald Keene, in honor of Julia Meech-Pekarik, 1986",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55979,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3718,false,true,55982,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Takeuchi Keishū,"Japanese, 1861–1943",,Takeuchi Keishū,Japanese,1861,1943,,1868,1912,Polychrome woodblock print; ink and color on paper,Album: 8 1/2 x 5 5/16 in. (21.6 x 13.5 cm),"Gift of Donald Keene, in honor of Julia Meech-Pekarik, 1986",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55982,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3719,false,true,55983,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Takeuchi Keishū,"Japanese, 1861–1943",,Takeuchi Keishū,Japanese,1861,1943,,1868,1912,Polychrome woodblock print; ink and color on paper,Album: 8 1/2 x 5 5/16 in. (21.6 x 13.5 cm),"Gift of Donald Keene, in honor of Julia Meech-Pekarik, 1986",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55983,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3708,false,true,55962,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Tomioka Eisen,"Japanese, 1864–1905",,Tomioka Eisen,Japanese,1864,1905,,1864,1905,Frontispiece; polychrome woodblock print; ink and color on paper,Album: 8 1/2 x 5 5/16 in. (21.6 x 13.5 cm),"Gift of Donald Keene, in honor of Julia Meech-Pekarik, 1986",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55962,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3711,false,true,55975,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Tomioka Eisen,"Japanese, 1864–1905",,Tomioka Eisen,Japanese,1864,1905,,1864,1905,Polychrome woodblock print; ink and color on paper,Album: 8 1/2 x 5 5/16 in. (21.6 x 13.5 cm),"Gift of Donald Keene, in honor of Julia Meech-Pekarik, 1986",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55975,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3721,false,true,55985,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Tomioka Eisen,"Japanese, 1864–1905",,Tomioka Eisen,Japanese,1864,1905,,1864,1905,Polychrome woodblock print; ink and color on paper,Album: 8 1/2 x 5 5/16 in. (21.6 x 13.5 cm),"Gift of Donald Keene, in honor of Julia Meech-Pekarik, 1986",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55985,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3713,false,true,55977,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Mizuno Toshikata,"Japanese, 1866–1908",,Mizuno Toshikata,Japanese,1866,1908,,1866,1908,Frontispiece; polychrome woodblock print; ink and color on paper,Album: 8 1/2 x 5 5/16 in. (21.6 x 13.5 cm),"Gift of Donald Keene, in honor of Julia Meech-Pekarik, 1986",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55977,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3716,false,true,55980,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Kajita Hanko,"Japanese, 1870–1917",,Kajita Hanko,Japanese,1870,1917,,1870,1917,Polychrome woodblock print; ink and color on paper,Album: 8 1/2 x 5 5/16 in. (21.6 x 13.5 cm),"Gift of Donald Keene, in honor of Julia Meech-Pekarik, 1986",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55980,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3720,false,true,55984,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Kajita Hanko,"Japanese, 1870–1917",,Kajita Hanko,Japanese,1870,1917,,1868,1912,Polychrome woodblock print; ink and color on paper,Album: 8 1/2 x 5 5/16 in. (21.6 x 13.5 cm),"Gift of Donald Keene, in honor of Julia Meech-Pekarik, 1986",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55984,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3173,false,true,56725,Asian Art,Print,,Japan,Taishō period (1912–26),,,,Artist,,Hashiguchi Goyō,"Japanese, 1881–1921",,Hashiguchi Goyō,Japanese,1881,1921,,1880,1921,Polychrome woodblock print; ink and color on paper,Image: 16 1/4 × 20 1/2 in. (41.3 × 52.1 cm) Mat: 22 3/4 × 27 5/8 in. (57.8 × 70.2 cm),"Gift of Mr. and Mrs. A. I. Sherr, 1956",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56725,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3370,false,true,55537,Asian Art,Print,Furansukoku|France,Japan,Edo (1615–1868)–Meiji period (1868–1912),,,,Artist,,Utagawa Yoshikazu,"Japanese, active ca. 1850–1870",,Utagawa Yoshikazu,Japanese,1845,1870,,1850,1870,Polychrome woodblock print; ink and color on paper,13 1/2 x 9 1/4 in. (34.3 x 23.5 cm),"Gift of Lincoln Kirstein, 1960",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55537,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.669,false,true,45511,Asian Art,Writer's box,,Japan,Edo period (1615–1868),,,,Artist,In the style of,Ogata Kenzan,"Japanese, 1663–1743",,Ogata Kenzan,Japanese,1663,1743,,1615,1868,"Clay with crackled glaze, decorated on outside and inside (Kyoto ware, Kenzan style)",H. 3 1/2 in. (8.9 cm); W. 8 in. (20.3 cm); L. 9 in. (22.9 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/45511,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.670,false,true,45512,Asian Art,Writer's box,,Japan,Edo period (1615–1868),,,,Artist,In the style of,Ogata Kenzan,"Japanese, 1663–1743",,Ogata Kenzan,Japanese,1663,1743,,1615,1868,"Crackled glaze; design modelled in relief (Kyoto ware, Kenzan style)",H. 3 1/4 in. (8.3 cm); W. 7 1/4 in. (18.4 cm); L. 9 1/4 in. (23.5 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/45512,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.268.603,false,true,52308,Asian Art,Teabowl,,Japan,late Edo period (1615–1868),,,,Artist,,Eiraku Wazen,"Japanese, 1821–1896",,Eiraku Wazen,Japanese,1821,1896,,1615,1868,Stoneware with gilt and polychrome enamels (Kyoto ware),H. 2 3/4 in. (7 cm); Diam. 4 7/8 in. (12.4 cm),"The Harry G. C. Packard Collection of Asian Art, Gift of Harry G. C. Packard, and Purchase, Fletcher, Rogers, Harris Brisbane Dick, and Louis V. Bell Funds, Joseph Pulitzer Bequest, and The Annenberg Fund Inc. Gift, 1975",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/52308,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -67.254,false,true,57244,Asian Art,Hanging scroll,,Japan,,,,,Artist,,Shibata Zeshin,"Japanese, 1807–1891",,Shibata Zeshin,Japanese,1807,1891,,1807,1891,Hanging scroll; ink on silk,32 1/8 x 9 3/8 in. (81.6 x 23.8 cm),"Gift of Mr. and Mrs. Harold G. Henderson, 1967",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/57244,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -39.92.1,false,true,57310,Asian Art,Fan,,Japan,,,,,Artist,,Kano Motonobu,"Japan, ca. 1476–1559",,Kano Motonobu,Japanese,1476,1559,,1466,1569,Color on paper,7 3/4 x 19 1/8 in. (19.7 x 48.6 cm),"Purchase, Harris Brisbane Dick Fund, 1938",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/57310,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -39.92.2,false,true,57311,Asian Art,Fan,,Japan,,,,,Artist,,Kano Motonobu,"Japan, ca. 1476–1559",,Kano Motonobu,Japanese,1476,1559,,1466,1569,Color on paper,8 1/4 x 19 3/4 in. (21 x 50.2 cm),"Purchase, Harris Brisbane Dick Fund, 1938",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/57311,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -39.92.3,false,true,57312,Asian Art,Fan,,Japan,,,,,Artist,,Kano Motonobu,"Japan, ca. 1476–1559",,Kano Motonobu,Japanese,1476,1559,,1466,1569,Color on paper,7 11/16 x 19 in. (19.5 x 48.3 cm),"Purchase, Harris Brisbane Dick Fund, 1938",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/57312,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -39.92.4,false,true,57313,Asian Art,Fan,,Japan,,,,,Artist,,Kano Motonobu,"Japan, ca. 1476–1559",,Kano Motonobu,Japanese,1476,1559,,1466,1569,Color on paper,8 x 19 1/4 in. (20.3 x 48.9 cm),"Purchase, Dick Fund, 1939",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/57313,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1991.116,false,true,44625,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Gessen,1721–1809,,Gessen,Japanese,1721,1809,,1721,1809,Hanging scroll; ink and color on silk,41 9/16 x 14 1/4 in. (105.5 x 36.2 cm),"Gift of Akiko Kobayashi Bowers, 1991",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/44625,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.446,false,true,54897,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,School of,Hishikawa Moronobu,"Japanese, died 1694",,Hishikawa Moronobu,Japanese,,1694,,1618,1694,Hanging scroll; color on paper,Painting only: 30 3/4 x 16 1/2 in. (78.1 x 41.9 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/54897,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.454,false,true,40349,Asian Art,Painting,,Japan,Edo period (1615–1868),,,,Artist,,Hishikawa Moronobu,"Japanese, died 1694",,Hishikawa Moronobu,Japanese,,1694,,1618,1694,Matted painting; ink and color on paper,Overall: 25 5/8 x 12 13/16 in. (65.1 x 32.5 cm) Image: 23 7/16 x 11 3/8 in. (59.5 x 28.9 cm) Mat: 29 3/8 x 17 1/4 in. (74.6 x 43.8 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/40349,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.61,false,true,57173,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,Attributed to,Kano Sansetsu,"Japanese, 1590–1651",,Kano Sansetsu,Japanese,1590,1651,,1615,1651,Hanging scroll; ink on paper,29 1/8 x 9 3/8 in. (74 x 23.8 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/57173,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.53,false,true,55380,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,Attributed to,Kano Naonobu,"Japanese, 1607–1650",,Kano Naonobu,Japanese,1607,1650,,1607,1650,Hanging scroll; ink on paper,40 3/4 x 15 1/2 in. (103.5 x 39.4 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/55380,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.54,false,true,55384,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,Attributed to,Kano Naonobu,"Japanese, 1607–1650",,Kano Naonobu,Japanese,1607,1650,,1607,1650,Hanging scroll; ink on paper,41 x 15 1/2 in. (104.1 x 39.4 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/55384,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.61.63,false,true,54706,Asian Art,Album leaf,,Japan,Edo period (1615–1868),,,,Artist,,Kano Naonobu,"Japanese, 1607–1650",,Kano Naonobu,Japanese,1607,1650,,1607,1650,Album leaf; ink and color on paper,10 x 13 5/8 in. (25.4 x 34.6 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/54706,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.93,false,true,55292,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Kano Yasunobu,"Japanese, 1613–1685",,Kano Yasunobu,Japanese,1613,1685,,1613,1685,Hanging scroll; ink on silk,32 1/4 x 16 in. (81.9 x 40.6 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/55292,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.94,false,true,55293,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,Attributed to,Kano Yasunobu,"Japanese, 1613–1685",,Kano Yasunobu,Japanese,1613,1685,,1613,1685,Hanging scroll; ink and color on silk,47 1/8 x 18 3/4 in. (119.7 x 47.6 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/55293,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.52,false,true,45327,Asian Art,Hanging scroll,秋の花に鶉図|Quail Under Autumn Flowers,Japan,Edo period (1615–1868),,,,Artist,,Tosa Mitsuoki,"Japanese, 1617–1691",,Tosa Mitsuoki,Japanese,1617,1691,,1617,1691,Hanging scroll; ink and color on silk,38 1/2 x 16 3/8 in. (97.8 x 41.6 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45327,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -60.86.1,false,true,57232,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Kano Tsunenobu,"Japanese, 1636–1713",,Kano Tsunenobu,Japanese,1636,1713,,1636,1713,One of a triptych of hanging scrolls; ink and color on paper,35 1/4 x 11 3/8 in. (89.5 x 28.9 cm),"Rogers Fund, 1960",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/57232,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -60.86.2,false,true,72771,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Kano Tsunenobu,"Japanese, 1636–1713",,Kano Tsunenobu,Japanese,1636,1713,,1636,1713,One of a triptych of hanging scrolls; ink and color on paper,35 1/4 x 11 3/8 in. (89.5 x 28.9 cm),"Rogers Fund, 1960",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/72771,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.48,false,true,55379,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,Attributed to,Ogata Kōrin,"Japanese, 1658–1716",,Kōrin,Japanese,1658,1716,,1658,1716,Hanging scroll; color on paper,38 3/4 x 14 in. (98.4 x 35.6 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/55379,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -17.118.117,false,true,48984,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,Attributed to,Ogata Kōrin,"Japanese, 1658–1716",,Kōrin,Japanese,1658,1716,,1658,1716,Hanging scroll; ink and color on silk,11 3/4 x 16 1/2 in. (29.8 x 41.9 cm),"Rogers Fund, 1917",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/48984,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.268.64,false,true,45329,Asian Art,Folding fan mounted as a hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,Follower of,Ogata Kōrin,"Japanese, 1658–1716",,Kōrin,Japanese,1658,1716,,1658,1716,Folding fan remounted as a hanging scroll; ink and color on paper,Image: 15 1/4 × 22 3/8 in. (38.7 × 56.8 cm) Overall with mounting: 51 1/8 × 27 3/4 in. (129.9 × 70.5 cm) Overall with knobs: 51 1/8 × 29 3/4 in. (129.9 × 75.6 cm),"The Harry G. C. Packard Collection of Asian Art, Gift of Harry G. C. Packard, and Purchase, Fletcher, Rogers, Harris Brisbane Dick, and Louis V. Bell Funds, Joseph Pulitzer Bequest, and The Annenberg Fund Inc. Gift, 1975",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45329,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.3,false,true,55297,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,Attributed to,Katō Bunrei,"Japanese, 1706–1782",,Katō Bunrei,Japanese,1706,1782,,1706,1782,Hanging scroll; ink and color on silk,38 1/4 x 14 3/8 in. (97.2 x 36.5 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/55297,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.268.84,false,true,49073,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Sō Shiseki,"Japanese, 1715–1786",,Sō Shiseki,Japanese,1715,1786,,1715,1786,Hanging scroll; ink and color on silk,Image: 39 1/16 x 11 3/16 in. (99.2 x 28.4 cm) Overall: 69 x 18 7/8in. (175.3 x 47.9 cm),"The Harry G. C. Packard Collection of Asian Art, Gift of Harry G. C. Packard, and Purchase, Fletcher, Rogers, Harris Brisbane Dick, and Louis V. Bell Funds, Joseph Pulitzer Bequest, and The Annenberg Fund Inc. Gift, 1975",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49073,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.268.96,false,true,48995,Asian Art,Hanging scroll,与謝蕪村筆 柳緑桃紅図|Birds in Willows and Blossoming Peach Tree,Japan,Edo period (1615–1868),,,,Artist,,Yosa Buson,"Japanese, 1716–1783",,Yosa Buson,Japanese,1716,1783,,1716,1783,Hanging scroll; ink and color on silk,51 1/2 x 24 9/16 in. (130.8 x 62.4 cm),"The Harry G. C. Packard Collection of Asian Art, Gift of Harry G. C. Packard, and Purchase, Fletcher, Rogers, Harris Brisbane Dick, and Louis V. Bell Funds, Joseph Pulitzer Bequest, and The Annenberg Fund Inc. Gift, 1975",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/48995,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.268.102,false,true,45394,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Okada Beisanjin,"Japanese, 1744–1820",,Okada Beisanjin,Japanese,1744,1820,,1744,1820,Hanging scroll; ink and color on paper,Image: 53 11/16 x 11 9/16 in. (136.4 x 29.4 cm) Overall: 72 3/4 x 18 3/8in. (184.8 x 46.7cm),"The Harry G. C. Packard Collection of Asian Art, Gift of Harry G. C. Packard, and Purchase, Fletcher, Rogers, Harris Brisbane Dick, and Louis V. Bell Funds, Joseph Pulitzer Bequest, and The Annenberg Fund Inc. Gift, 1975",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45394,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.74,false,true,57178,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,Attributed to,Mori Sosen,"Japanese, 1747–1821",,Mori Sosen,Japanese,1747,1821,,1747,1821,Hanging scroll; ink on silk,40 3/8 x 12 1/2 in. (102.6 x 31.8 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/57178,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.75,false,true,57179,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,Attributed to,Mori Sosen,"Japanese, 1747–1821",,Mori Sosen,Japanese,1747,1821,,1747,1821,Hanging scroll; ink on silk,40 3/8 x 12 1/2 in. (102.6 x 31.8 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/57179,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.18,false,true,45222,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,Attributed to,Matsumura Goshun,"Japanese, 1752–1811",,Matsumura Goshun,Japanese,1752,1811,,1752,1811,Hanging scroll ;ink on paper,14 15/16 x 21 7/16 in. (38 x 54.5 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45222,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.20,false,true,55304,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,Attributed to,Matsumura Goshun,"Japanese, 1752–1811",,Matsumura Goshun,Japanese,1752,1811,,1752,1811,Hanging scroll; ink on silk,16 1/8 x 10 3/8 in. (41.0 x 26.4 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/55304,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.59,false,true,55385,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,Attributed to,Nagasawa Rosetsu,"Japanese, 1754–1799",,Nagasawa Rosetsu,Japanese,1754,1799,,1754,1799,Hanging scroll; color on paper,37 5/8 x 11 in. (95.6 x 27.9 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/55385,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.23,false,true,55305,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,Attributed to,Sakai Hōitsu,"Japanese, 1761–1828",,Sakai Hōitsu,Japanese,1761,1828,,1761,1828,Hanging scroll; ink and color on paper,34 1/4 x 13 in. (87x 33 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/55305,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.24,false,true,55310,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,Attributed to,Sakai Hōitsu,"Japanese, 1761–1828",,Sakai Hōitsu,Japanese,1761,1828,,1761,1828,Hanging scroll; ink and color on silk,44 1/4 x 16 in. (112.4 x 40.6 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/55310,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.25,false,true,55314,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,Attributed to,Sakai Hōitsu,"Japanese, 1761–1828",,Sakai Hōitsu,Japanese,1761,1828,,1761,1828,Hanging scroll; ink and color on paper,44 5/8 x 20 1/4 in. (113.3 x 51.4 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/55314,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.1,false,true,55300,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,Attributed to,Tani Bunchō,"Japanese, 1763–1840",,Tani Bunchō,Japanese,1763,1840,,1763,1840,Hanging scroll; ink on silk,Image: 35 13/16 × 8 7/16 in. (91 × 21.5 cm) Overall with mounting: 67 11/16 × 16 7/8 in. (172 × 42.9 cm) Overall with knobs: 67 11/16 × 18 3/4 in. (172 × 47.7 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/55300,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.2,false,true,55301,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,Attributed to,Tani Bunchō,"Japanese, 1763–1840",,Tani Bunchō,Japanese,1763,1840,,1763,1840,Hanging scroll; ink on silk,Image: 35 13/16 × 8 1/2 in. (91 × 21.6 cm) Overall with mounting: 68 1/8 × 16 7/8 in. (173 × 42.9 cm) Overall with knobs: 68 1/8 × 18 3/4 in. (173 × 47.7 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/55301,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2000.214,false,true,53707,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Nagasawa Roshu,"Japanese, 1767–1847",,Nagasawa Roshu,Japanese,1767,1847,,1767,1847,Hanging scroll; ink and color on silk,34 7/8 x 14 1/8 in. (88.6 x 35.9 cm),"Anonymous Gift, 2000",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/53707,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.268.109,false,true,49027,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Yamamoto Baiitsu,"Japanese, 1783–1856",,Yamamoto Baiitsu,Japanese,1783,1783,,1783,1856,Hanging scroll; color on silk,Image: 57 1/2 x 28 3/16 in. (146.1 x 71.6 cm) Overall: 88 3/4 x 37 1/4 in. (225.4 x 94.6 cm),"The Harry G. C. Packard Collection of Asian Art, Gift of Harry G. C. Packard, and Purchase, Fletcher, Rogers, Harris Brisbane Dick, and Louis V. Bell Funds, Joseph Pulitzer Bequest, and The Annenberg Fund Inc. Gift, 1975",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49027,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.97,false,true,55294,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Yosai,"Japanese, 1788–1878",,Yosai,Japanese,1788,1878,,1788,1868,Hanging scroll; ink and color on silk,43 1/4 x 15 3/4 in. (109.9 x 40 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/55294,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2000.661.1,false,true,53824,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Ōtagaki Rengetsu,"Japanese, 1791–1871",,Ōtagaki Rengetsu,Japanese,1791,1871,,1791,1871,Hanging scroll; ink and color on paper,44 1/16 x 10 3/4 in. (111.9 x 27.3 cm),"Gift of Donald Keene, 2000",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/53824,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2000.661.2,false,true,59004,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Ōtagaki Rengetsu,"Japanese, 1791–1871",,Ōtagaki Rengetsu,Japanese,1791,1871,,1791,1871,Hanging scroll; ink and color on paper,44 1/16 x 10 3/4 in. (111.9 x 27.3 cm),"Gift of Donald Keene, 2000",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/59004,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2000.661.3,false,true,59005,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Ōtagaki Rengetsu,"Japanese, 1791–1871",,Ōtagaki Rengetsu,Japanese,1791,1871,,1791,1871,Hanging scroll; ink and color on paper,44 1/16 x 10 3/4 in. (111.9 x 27.3 cm),"Gift of Donald Keene, 2000",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/59005,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.268.115,false,true,49007,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Tsubaki Chinzan,"Japanese, 1801–1854",,Tsubaki Chinzan,Japanese,1801,1854,,1801,1854,Hanging scroll; color on paper,Image: 55 1/2 x 14 5/8 in. (141 x 37.1 cm) Overall with mounting: 85 3/4 x 24 1/4 in. (217.8 x 61.6 cm),"The Harry G. C. Packard Collection of Asian Art, Gift of Harry G. C. Packard, and Purchase, Fletcher, Rogers, Harris Brisbane Dick, and Louis V. Bell Funds, Joseph Pulitzer Bequest, and The Annenberg Fund Inc. Gift, 1975",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49007,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.268.116,false,true,49008,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Tsubaki Chinzan,"Japanese, 1801–1854",,Tsubaki Chinzan,Japanese,1801,1854,,1801,1854,Hanging scroll; color on paper,Image: 56 7/8 x 14 13/16 in. (144.5 x 37.6 cm) Overall with mounting: 85 1/4 x 24 3/16 in. (216.5 x 61.4 cm),"The Harry G. C. Packard Collection of Asian Art, Gift of Harry G. C. Packard, and Purchase, Fletcher, Rogers, Harris Brisbane Dick, and Louis V. Bell Funds, Joseph Pulitzer Bequest, and The Annenberg Fund Inc. Gift, 1975",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49008,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.114,false,true,57199,Asian Art,Album leaf,,Japan,Edo period (1615–1868),,,,Artist,,Shibata Zeshin,"Japanese, 1807–1891",,Shibata Zeshin,Japanese,1807,1891,,1807,1868,Album leaf; lacquer on gold paper,4 3/4 x 3 1/2 in. (12.1 x 8.9 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/57199,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.115,false,true,57200,Asian Art,Album leaf,,Japan,Edo period (1615–1868),,,,Artist,,Shibata Zeshin,"Japanese, 1807–1891",,Shibata Zeshin,Japanese,1807,1891,,1807,1868,Album leaf; brown and gold lacquer on silver paper,3 1/2 x 4 3/4 in. (8.9 x 12.1 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/57200,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.116,false,true,57201,Asian Art,Album leaf,,Japan,Edo period (1615–1868),,,,Artist,,Shibata Zeshin,"Japanese, 1807–1891",,Shibata Zeshin,Japanese,1807,1891,,1807,1868,Album leaf; lacquer on gold paper,4 3/4 x 3 1/2 in. (12.1 x 8.9 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/57201,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.117,false,true,57202,Asian Art,Album leaf,,Japan,Edo period (1615–1868),,,,Artist,,Shibata Zeshin,"Japanese, 1807–1891",,Shibata Zeshin,Japanese,1807,1891,,1807,1868,Album leaf; lacquer on silver paper,4 3/4 x 3 1/2 in. (12.1 x 8.9 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/57202,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.118,false,true,57203,Asian Art,Album leaf,,Japan,Edo period (1615–1868),,,,Artist,,Shibata Zeshin,"Japanese, 1807–1891",,Shibata Zeshin,Japanese,1807,1891,,1807,1868,Album leaf; lacquer on gold paper,4 3/4 x 3 1/2 in. (12.1 x 8.9 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/57203,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.119,false,true,57204,Asian Art,Album leaf,,Japan,Edo period (1615–1868),,,,Artist,,Shibata Zeshin,"Japanese, 1807–1891",,Shibata Zeshin,Japanese,1807,1891,,1807,1868,Album leaf; lacquer on silver paper,4 3/4 x 3 1/2 in. (12.1 x 8.9 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/57204,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.120,false,true,57205,Asian Art,Album leaf,,Japan,Edo period (1615–1868),,,,Artist,,Shibata Zeshin,"Japanese, 1807–1891",,Shibata Zeshin,Japanese,1807,1891,,1807,1868,Album leaf; lacquer on gold paper,4 3/4 x 3 1/2 in. (12.1 x 8.9 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/57205,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.121,false,true,57206,Asian Art,Album leaf,,Japan,Edo period (1615–1868),,,,Artist,,Shibata Zeshin,"Japanese, 1807–1891",,Shibata Zeshin,Japanese,1807,1891,,1807,1868,Album leaf; lacquer on silver paper,4 3/4 x 3 1/2 in. (12.1 x 8.9 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/57206,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.122,false,true,57207,Asian Art,Album leaf,,Japan,Edo period (1615–1868),,,,Artist,,Shibata Zeshin,"Japanese, 1807–1891",,Shibata Zeshin,Japanese,1807,1891,,1807,1868,Album leaf; lacquer on gold paper,4 3/4 x 3 1/2 in. (12.1 x 8.9 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/57207,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.123,false,true,57208,Asian Art,Album leaf,,Japan,Edo period (1615–1868),,,,Artist,,Shibata Zeshin,"Japanese, 1807–1891",,Shibata Zeshin,Japanese,1807,1891,,1807,1868,Album leaf; lacquer on silver paper,4 3/4 x 3 1/2 in. (12.1 x 8.9 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/57208,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.124,false,true,57209,Asian Art,Album leaf,,Japan,Edo period (1615–1868),,,,Artist,,Shibata Zeshin,"Japanese, 1807–1891",,Shibata Zeshin,Japanese,1807,1891,,1807,1868,Album leaf; lacquer on paper,4 1/2 x 3 1/2 in. (11.4 x 8.9 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/57209,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.125,false,true,57210,Asian Art,Album leaf,,Japan,Edo period (1615–1868),,,,Artist,,Shibata Zeshin,"Japanese, 1807–1891",,Shibata Zeshin,Japanese,1807,1891,,1807,1868,Album leaf; lacquer on paper,4 1/2 x 3 1/2 in. (11.4 x 8.9 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/57210,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.126,false,true,57211,Asian Art,Album leaf,,Japan,Edo period (1615–1868),,,,Artist,,Shibata Zeshin,"Japanese, 1807–1891",,Shibata Zeshin,Japanese,1807,1891,,1807,1868,Album leaf; lacquer on paper,4 1/2 x 3 1/2 in. (11.4 x 8.9 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/57211,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.127,false,true,57212,Asian Art,Album leaf,,Japan,Edo period (1615–1868),,,,Artist,,Shibata Zeshin,"Japanese, 1807–1891",,Shibata Zeshin,Japanese,1807,1891,,1807,1868,Album leaf; lacquer on paper,4 1/2 x 3 1/2 in. (11.4 x 8.9 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/57212,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.128,false,true,57213,Asian Art,Album leaf,,Japan,Edo period (1615–1868),,,,Artist,,Shibata Zeshin,"Japanese, 1807–1891",,Shibata Zeshin,Japanese,1807,1891,,1807,1868,Album leaf; lacquer on paper,4 1/2 x 3 1/2 in. (11.4 x 8.9 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/57213,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.129,false,true,57214,Asian Art,Album leaf,,Japan,Edo period (1615–1868),,,,Artist,,Shibata Zeshin,"Japanese, 1807–1891",,Shibata Zeshin,Japanese,1807,1891,,1807,1868,Album leaf; lacquer on paper,4 1/2 x 3 1/2 in. (11.4 x 8.9 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/57214,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.130,false,true,57215,Asian Art,Album leaf,,Japan,Edo period (1615–1868),,,,Artist,,Shibata Zeshin,"Japanese, 1807–1891",,Shibata Zeshin,Japanese,1807,1891,,1807,1868,Album leaf; lacquer on paper,4 1/2 x 3 1/2 in. (11.4 x 8.9 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/57215,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.131,false,true,57216,Asian Art,Album leaf,,Japan,Edo period (1615–1868),,,,Artist,,Shibata Zeshin,"Japanese, 1807–1891",,Shibata Zeshin,Japanese,1807,1891,,1807,1868,Album leaf; lacquer on paper,4 1/2 x 3 1/2 in. (11.4 x 8.9 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/57216,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.132,false,true,57217,Asian Art,Album leaf,,Japan,Edo period (1615–1868),,,,Artist,,Shibata Zeshin,"Japanese, 1807–1891",,Shibata Zeshin,Japanese,1807,1891,,1807,1868,Album leaf; lacquer on paper,4 1/2 x 3 1/2 in. (11.4 x 8.9 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/57217,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.133,false,true,57218,Asian Art,Album leaf,,Japan,Edo period (1615–1868),,,,Artist,,Shibata Zeshin,"Japanese, 1807–1891",,Shibata Zeshin,Japanese,1807,1891,,1807,1868,Album leaf; lacquer on paper,4 1/2 x 3 1/2 in. (11.4 x 8.9 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/57218,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.134,false,true,57219,Asian Art,Album leaf,,Japan,Edo period (1615–1868),,,,Artist,,Shibata Zeshin,"Japanese, 1807–1891",,Shibata Zeshin,Japanese,1807,1891,,1807,1868,Album leaf; lacquer on paper,4 1/2 x 3 1/2 in. (11.4 x 8.9 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/57219,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.135,false,true,57220,Asian Art,Album leaf,,Japan,Edo period (1615–1868),,,,Artist,,Shibata Zeshin,"Japanese, 1807–1891",,Shibata Zeshin,Japanese,1807,1891,,1807,1868,Album leaf; lacquer on paper,4 1/2 x 3 1/2 in. (11.4 x 8.9 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/57220,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.136,false,true,57221,Asian Art,Album leaf,,Japan,Edo period (1615–1868),,,,Artist,,Shibata Zeshin,"Japanese, 1807–1891",,Shibata Zeshin,Japanese,1807,1891,,1807,1868,Album leaf; lacquer on paper,4 1/2 x 3 1/2 in. (11.4 x 8.9 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/57221,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.137,false,true,57222,Asian Art,Album leaf,,Japan,Edo period (1615–1868),,,,Artist,,Shibata Zeshin,"Japanese, 1807–1891",,Shibata Zeshin,Japanese,1807,1891,,1807,1868,Album leaf; lacquer on paper,4 1/2 x 3 1/2 in. (11.4 x 8.9 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/57222,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.138,false,true,57223,Asian Art,Album leaf,,Japan,Edo period (1615–1868),,,,Artist,,Shibata Zeshin,"Japanese, 1807–1891",,Shibata Zeshin,Japanese,1807,1891,,1807,1868,Album leaf; lacquer on paper,4 1/2 x 3 1/2 in. (11.4 x 8.9 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/57223,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.4,false,true,55298,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,Attributed to,Shiokawa Bunrin,"Japanese, 1808–1877",,Shiokawa Bunrin,Japanese,1808,1877,,1808,1877,Hanging scroll; ink and color on silk,12 x 16 1/4 in. (30.5 x 41.3 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/55298,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2000.210,false,true,54037,Asian Art,Folding fan,,Japan,Edo period (1615–1868),,,,Artist,,Katsushika Isai,"Japanese, 1821–1880",,Katsushika Isai,Japanese,1821,1880,,1821,1868,"Folding fan; ink and color on paper, with mounting in ivory",H. 16 1/8 in. (41 cm); W. 29 1/2 in. (75 cm),"Purchase, Friends of Asian Art Gifts, in honor of Wen C. Fong, 2000",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/54037,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2001.561.1,false,true,61942,Asian Art,Folding fan mounted as hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Katsushika Isai,"Japanese, 1821–1880",,Katsushika Isai,Japanese,1821,1880,,1821,1868,Folding fan remounted as a hanging scroll; ink and color on silk,7 3/4 x 27 in. (19.7 x 68.6 cm),"Gift of Rosemarie and Leighton Longhi, 2001",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/61942,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2001.561.2,false,true,64505,Asian Art,Folding fan mounted as hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Katsushika Isai,"Japanese, 1821–1880",,Katsushika Isai,Japanese,1821,1880,,1821,1868,"Folding fan, remounted as a hanging scroll; ink and color on silk",7 3/4 x 27 in. (19.7 x 68.6 cm),"Gift of Rosemarie and Leighton Longhi, 2001",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/64505,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.268.122,false,true,45333,Asian Art,Folding fan,,Japan,Edo period (1615–1868),,,,Artist,,Tsubaki Kakoku,"Japanese, 1823–1848",,Tsubaki Kakoku,Japanese,1823,1848,,1823,1848,Folding fan; ink and color on paper,10 1/4 x 17 3/8 in. (26 x 44.1 cm),"The Harry G. C. Packard Collection of Asian Art, Gift of Harry G. C. Packard, and Purchase, Fletcher, Rogers, Harris Brisbane Dick, and Louis V. Bell Funds, Joseph Pulitzer Bequest, and The Annenberg Fund Inc. Gift, 1975",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45333,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.67,false,true,57177,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,Attributed to,Shōkadō Shōjō,"Japanese, 1584?–1639",,Shōkadō Shōjō,Japanese,1584,1639,,1615,1639,Hanging scroll; ink on paper,10 1/4 x 23 1/8 in. (26 x 58.7 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/57177,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.516,false,true,54967,Asian Art,Painting,,Japan,Edo period (1615–1868),,,,Artist,Attributed to,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,,1754,1806,Painting; color on silk,34 3/4 x 14 1/4 in. (88.3 x 36.2 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/54967,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.268.95,false,true,48994,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Ike Gyokuran,"Japanese, 1727/8–1784",,Ike Gyokuran,Japanese,1727,1784,,1728,1784,Fan mounted as a hanging scroll; ink and color on paper,Image: 7 1/2 x 20 9/16 in. (19.1 x 52.3 cm) Overall with mounting: 52 1/4 x 30 3/4 in. (132.7 x 78.1 cm) Overall with knobs: 52 1/4 x 33 1/8 in. (132.7 x 84.1 cm),"The Harry G. C. Packard Collection of Asian Art, Gift of Harry G. C. Packard, and Purchase, Fletcher, Rogers, Harris Brisbane Dick, and Louis V. Bell Funds, Joseph Pulitzer Bequest, and The Annenberg Fund Inc. Gift, 1975",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/48994,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.511,false,true,54953,Asian Art,Painting,,Japan,Edo period (1615–1868),,,,Artist,Attributed to,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,,1760,1849,Painting; color on paper,23 7/8 x 15 3/4 in. (60.6 x 40 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/54953,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2011.526.1a–d,false,true,76806,Asian Art,Door panels,神坂雪佳筆 竹波図襖|Bamboo and Waves,Japan,Shōwa period (1926–89),,,,Artist,,Kamisaka Sekka,"Japanese, 1866–1942",,Kamisaka Sekka,Japanese,1866,1942,,1926,1942,Set of four sliding-door panels (fusuma); ink and gold on paper,Overall (for four panels): 68 1/2 x 190 3/8 in. (174 x 483.6 cm),"Gift of Gitter-Yelen Foundation, in honor of Maxwell K. Hearn, 2011",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/76806,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2011.526.2,false,true,76924,Asian Art,Hanging scroll,神坂雪佳筆 寿老人図|Jurōjin,Japan,Meiji period (1866–1912),,,,Artist,,Kamisaka Sekka,"Japanese, 1866–1942",,Kamisaka Sekka,Japanese,1866,1942,,1866,1912,Hanging scroll; ink and color on silk,Image: 48 1/2 x 16 7/16 in. (123.2 x 41.8 cm) Overall with mounting: 84 x 22 in. (213.4 x 55.9 cm) Overall with knobs: 85 1/4 x 24 1/8 in. (216.5 x 61.3 cm),"Gift of Gitter-Yelen Foundation, in honor of John T. Carpenter, 2011",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/76924,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.61.49,false,true,54686,Asian Art,Album leaf,,Japan,Meiji period (1868–1912),,,,Artist,,Hashimoto Gahō,"Japanese, 1835–1908",,Hashimoto Gahō,Japanese,1835,1908,,1868,1912,Album leaf; ink and color on silk,14 1/8 x 10 3/8 in. (35.9 x 26.4 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/54686,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.61.50,false,true,54687,Asian Art,Album leaf,,Japan,Meiji period (1868–1912),,,,Artist,,Hashimoto Gahō,"Japanese, 1835–1908",,Hashimoto Gahō,Japanese,1835,1908,,1868,1912,Album leaf; ink and color on silk,14 1/8 x 10 3/8 in. (35.9 x 26.4 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/54687,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1977.50,false,true,57165,Asian Art,Hanging scroll,,Japan,Meiji period (1868–1912),,,,Artist,,Tomioka Tessai,"Japanese, 1836–1924",,Tomioka Tessai,Japanese,1836,1924,,1836,1924,Hanging scroll; ink and color on paper,11 1/2 x 26 3/8 in. (29.2 x 67 cm),"Gift of Dr. and Mrs. Joseph Kurstin, 1977",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/57165,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1979.72,false,true,57166,Asian Art,Hanging scroll,,Japan,Meiji period (1868–1912),,,,Artist,,Suzuki Shōnen,"Japanese, 1849–1918",,Suzuki Shōnen,Japanese,1849,1918,,1868,1912,"Hanging scroll; ink, color, and gold on silk",Image: 48 3/4 x 19 1/2 in. (123.8 x 49.5 cm) Overall with mounting: 79 1/2 x 25 3/4 in. (201.9 x 65.4 cm) Overall with knobs: 79 1/4 x 28 in. (201.3 x 71.1 cm),"Purchase, Gift of Mrs. Russell Sage, by exchange, 1979",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/57166,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.62,false,true,57174,Asian Art,Hanging scroll,,Japan,Meiji period (1868–1912),,,,Artist,,Takeuchi Seihō,"Japanese, 1864–1942",,Takeuchi Seihō,Japanese,1864,1942,,1868,1912,Hanging scroll; ink and color on paper,Overall: 40 3/4 x 13 1/2in. (103.5 x 34.3cm) Overall with mounting: 71 x 14 3/4 in. (180.3 x 37.5 cm) Overall with knobs: 71 x 17 1/8 in. (180.3 x 43.5 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/57174,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.460,false,true,36040,Asian Art,Hanging scroll,,Japan,Momoyama period (1573–1615),,,,Artist,In the Style of,Hasegawa Tōhaku,"Japanese, 1539–1610",,Hasegawa Tōhaku,Japanese,1539,1610,,1573,1615,Hanging scroll; ink and color on silk,28 1/2 x 22 31/32 in. (72.4 x 58.4 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/36040,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.31,false,true,55324,Asian Art,Hanging scroll,,Japan,Muromachi period (1392–1573),,,,Artist,In the Style of,Ikkyu Sojun,"Japanese, 1394–1481",,Ikkyu Sojun,Japanese,1394,1481,,1392,1573,Hanging scroll; ink on paper,34 1/4 x 10 1/4 in. (87 x 26 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/55324,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.8,false,true,54567,Asian Art,Painting,,Japan,Muromachi period (1392–1573),,,,Artist,Attributed to,Sesshū Tōyō,"Japanese, 1420–1506",,Sesshū Tōyō,Japanese,1420,1506,,1420,1506,India ink on paper,Image: 10 1/2 × 15 3/4 in. (26.7 × 40 cm) Overall with mounting: 44 7/8 × 20 1/2 in. (114 × 52.1 cm) Overall with knobs: 44 7/8 × 22 3/8 in. (114 × 56.8 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/54567,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.10,false,true,45642,Asian Art,Hanging scroll,,Japan,Muromachi period (1392–1573),,,,Artist,Attributed to,Gakuo Zokyu,"Japanese, active ca. 1500",,Gakuo Zokyu,Japanese,1500,1500,,1490,1510,Hanging scroll; ink on paper,27 1/2 x 12 1/2 in. (69.9 x 31.8 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45642,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.65,false,true,57176,Asian Art,Hanging scroll,,Japan,Muromachi period (1392–1573),,,,Artist,Attributed to,Shūkō,"Japanese, active 1504–20",,Shūkō,Japanese,1504,1520,,1504,1520,Hanging scroll; ink on paper,40 1/2 x 12 in. (102.9 x 30.5 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/57176,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.450,false,true,45696,Asian Art,Painting,,Japan,Muromachi period (1392–1573),,,,Artist,,Isei,"Japanese, mid-16th century",,Isei,Japanese,1536,1570,,1534,1566,Framed painting; ink and color on paper,18 1/2 x 12 1/2 in. (47 x 31.8 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45696,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.451,false,true,45697,Asian Art,Painting,,Japan,Muromachi period (1392–1573),,,,Artist,,Isei,"Japanese, mid-16th century",,Isei,Japanese,1536,1570,,1534,1573,Framed painting; ink and color on paper,18 1/2 x 12 1/2 in. (47 x 31.8 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45697,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.268.26,false,true,45202,Asian Art,Hanging scroll,不動明王二童子像|Fudō Myōō and Two Attendants,Japan,Nanbokuchō period (1336–92),,,,Artist,After,Ryūshū Shūtaku (Myōtaku),"Japanese, 1307–1388",,Ryūshū Shūtaku,Japanese,1307,1388,,1336,1388,One of a triptych of hanging scrolls; hand-colored woodblock print on paper,Image: 40 5/16 x 14 in. (102.4 x 35.6 cm) Overall: 71 1/8 x 22 1/2 in. (180.7 x 57.2 cm),"The Harry G. C. Packard Collection of Asian Art, Gift of Harry G. C. Packard, and Purchase, Fletcher, Rogers, Harris Brisbane Dick, and Louis V. Bell Funds, Joseph Pulitzer Bequest, and The Annenberg Fund Inc. Gift, 1975",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45202,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.268.27,false,true,45203,Asian Art,Hanging scroll,不動明王二童子像|Fudō Myōō and Two Attendants,Japan,Nanbokuchō period (1336–92),,,,Artist,After,Ryūshū Shūtaku (Myōtaku),"Japanese, 1307–1388",,Ryūshū Shūtaku,Japanese,1307,1388,,1336,1388,One of a triptych of hanging scrolls; hand-colored woodblock print on paper,Image: 40 1/4 x 14 in. (102.3 x 35.6 cm) Overall: 71 x 22 1/2 in. (180.3 x 57.2 cm),"The Harry G. C. Packard Collection of Asian Art, Gift of Harry G. C. Packard, and Purchase, Fletcher, Rogers, Harris Brisbane Dick, and Louis V. Bell Funds, Joseph Pulitzer Bequest, and The Annenberg Fund Inc. Gift, 1975",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45203,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.268.28,false,true,45204,Asian Art,Hanging scroll,不動明王二童子像|Fudō Myōō and Two Attendants,Japan,Nanbokuchō period (1336–92),,,,Artist,After,Ryūshū Shūtaku (Myōtaku),"Japanese, 1307–1388",,Ryūshū Shūtaku,Japanese,1307,1388,,1336,1388,One of a triptych of hanging scrolls; hand-colored woodblock print on paper,Image: 40 3/8 x 14 in. (102.6 x 35.6 cm) Overall: 71 3/8 x 22 1/2 in. (181.3 x 57.2 cm),"The Harry G. C. Packard Collection of Asian Art, Gift of Harry G. C. Packard, and Purchase, Fletcher, Rogers, Harris Brisbane Dick, and Louis V. Bell Funds, Joseph Pulitzer Bequest, and The Annenberg Fund Inc. Gift, 1975",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45204,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.35,false,true,55353,Asian Art,Hanging scroll,,Japan,Nanbokuchō (1336–92)–Muromachi (1392–1573) period,,,,Artist,Attributed to,Kao,,(Ryozen?),Kao,Japanese,,1345,,1336,1573,Hanging scroll; ink on silk,33 x 14 1/4 in. (83.8 x 36.2 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/55353,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JIB53,false,true,57653,Asian Art,Illustrated book,,Japan,Meiji period (1868–1912),,,,Artist,,Kawanabe Kyōsai,"Japanese, 1831–1889",,Kawanabe Kyōsai,Japanese,1831,1889,1888,1888,1888,Album of 29 leaves; ink and color on paper,8 7/8 x 5 7/8 in. (22.5 x 14.9 cm),"Gift of Albert Gallatin, 1922",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/57653,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3199,false,true,55183,Asian Art,Print,『上野公園開花図』|Blossoming Cherry Trees in Ueno Park (Ueno kōen kaika zu),Japan,Meiji period (1868–1912),,,,Artist,,Yōshū (Hashimoto) Chikanobu,"Japanese, 1838–1912",,Yōshū (Hashimoto) Chikanobu,Japanese,1838,1912,1888,1888,1888,Triptych of polychrome woodblock prints; ink and color on paper,Oban 14 3/4 x 9 7/8 in. (37.5 x 25.1 cm),"Gift of Lincoln Kirstein, 1959",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55183,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3687a,false,true,55918,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Tsukioka Yoshitoshi,"Japanese, 1839–1892",,Tsukioka Yoshitoshi,Japanese,1839,1892,1888,1888,1888,Polychrome woodblock print; ink and color on paper,14 x 9 3/8 in. (35.6 x 23.8 cm),"Gift of Lincoln Kirstein, 1985",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55918,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3687b,false,true,55919,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Tsukioka Yoshitoshi,"Japanese, 1839–1892",,Tsukioka Yoshitoshi,Japanese,1839,1892,1888,1888,1888,Polychrome woodblock print; ink and color on paper,14 9/16 x 10 in. (37 x 25.4 cm),"Gift of Lincoln Kirstein, 1985",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55919,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.349,false,true,72827,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Tsukioka Yoshitoshi,"Japanese, 1839–1892",,Tsukioka Yoshitoshi,Japanese,1839,1892,1888,1888,1888,Polychrome woodblock print; ink and color on paper,14 5/8 x 10 in. (37.1 x 25.4 cm),"Purchase, Friends of Asian Art Gifts, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/72827,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3307a–c,false,true,55411,Asian Art,Print,九段坂上靖国神社庭内真図|True View of the Courtyard of Yasukuni Shrine at Kudan Sakaue (Kudan Sakaue Yasukuni jinsha teinai shin zu),Japan,Meiji period (1868–1912),,,,Artist,,Inoue Yasuji,"Japanese, 1864–1889",,INOUE YASUJI,Japanese,1864,1889,1888,1888,1888,Triptych of polychrome woodblock prints; ink and color on paper,Image: 14 x 29 1/2 in. (35.6 x 74.9 cm),"Gift of Lincoln Kirstein, 1959",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55411,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.61.51,false,true,54688,Asian Art,Album leaf,,Japan,Meiji period (1868–1912),,,,Artist,,Kawanabe Kyōsai,"Japanese, 1831–1889",,Kawanabe Kyōsai,Japanese,1831,1889,ca. 1888,1878,1898,Album leaf mounted as a hanging scroll; ink and color on silk,14 1/2 x 11 in. (36.8 x 27.9 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/54688,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.13,false,true,45692,Asian Art,Hanging scroll,,Japan,Muromachi period (1392–1573),,,,Artist,Attributed to,Kano Motonobu,"Japan, ca. 1476–1559",,Kano Motonobu,Japanese,1476,1559,15th century,1400,1499,Hanging scroll; ink on paper,25 1/8 x 12 1/2 in. (63.8 x 31.8 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45692,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1982.244.16,false,true,45072,Asian Art,Screen,,Japan,Muromachi period (1392–1573),,,,Artist,,Sesson Shūkei,ca. 1504–ca. 1589,,Sesson Shūkei,Japanese,1504,1589,16th century,1500,1599,Six-panel folding screen; ink on paper,60 1/2 in. x 11 ft. 8 1/4 in. (153.7 x 356.2 cm),"Bequest of Hope Skillman Schary, 1981",,,,,,,,,,,,Screens,,http://www.metmuseum.org/art/collection/search/45072,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"29.100.495g, h",false,true,45219,Asian Art,Hanging scroll,,Japan,Momoyama period (1573–1615),,,,Artist,In the Style of,Kano Eitoku,"Japanese, 1534–1590",,Kano Eitoku,Japanese,1543,1590,16th century,1534,1590,"Hanging scroll; ink, color, and gold on gilded paper",61 1/8 x 45 1/2in. (155.3 x 115.6cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45219,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -51.95,false,true,45070,Asian Art,Painting,,Japan,Muromachi period (1392–1573),,,,Artist,Attributed to,Kano Motonobu,"Japan, ca. 1476–1559",,Kano Motonobu,Japanese,1476,1559,16th century,1500,1573,Framed painting; ink on paper,23 1/4 x 42 in. (59.1 x 106.7 cm),"Fletcher Fund,1951",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45070,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.268.41,false,true,42342,Asian Art,Hanging scroll,山水図|Landscape,Japan,Muromachi period (1392–1573),,,,Artist,,Maejima Sōyū,active mid-16th century,,Maejima Sōyū,Japanese,1536,1570,16th century,1500,1573,Hanging scroll; ink and color on paper,Image: 20 1/4 × 13 5/8 in. (51.4 × 34.6 cm) Overall with mounting: 55 3/4 × 18 3/4 in. (141.6 × 47.6 cm) Overall with knobs: 55 3/4 × 20 3/4 in. (141.6 × 52.7 cm),"The Harry G. C. Packard Collection of Asian Art, Gift of Harry G. C. Packard, and Purchase, Fletcher, Rogers, Harris Brisbane Dick, and Louis V. Bell Funds, Joseph Pulitzer Bequest, and The Annenberg Fund Inc. Gift, 1975",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/42342,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.10,false,true,45374,Asian Art,Fan mounted as a hanging scroll,伝狩野之信 列子御風図扇面|Resshi Flying on a Cloud,Japan,Muromachi period (1392–1573),,,,Artist,,Kano Yukinobu,"Japanese, ca. 1513–1575",,Kano Yukinobu,Japanese,1513,1575,16th century,1513,1575,Fan mounted as hanging scroll; ink on paper,8 1/4 x 20 1/2 in. (21 x 52 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45374,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1997.140,false,true,39487,Asian Art,Fan mounted as hanging scroll,,Japan,Muromachi period (1392–1573),,,,Artist,Attributed to,Kano Yukinobu,"Japanese, ca. 1513–1575",,Kano Yukinobu,Japanese,1513,1575,16th century,1500,1573,"Originally a fan mounted as a hanging scroll; ink, color, and silver on gilded paper",9 3/4 x 19 1/2 in. (24.8 x 49.5 cm),"Purchase, Friends of Asian Art Gifts, 1997",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/39487,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.91,false,true,45206,Asian Art,Hanging scroll,,Japan,Muromachi period (1392–1573),,,,Artist,Attributed to,Kano Yukinobu,"Japanese, ca. 1513–1575",,Kano Yukinobu,Japanese,1513,1575,16th century,1513,1575,Hanging scroll; ink on paper,39 1/8 x 16 3/4 in. (99.4 x 42.5 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45206,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.92,false,true,45207,Asian Art,Hanging scroll,,Japan,Muromachi period (1392–1573),,,,Artist,,Kano Yukinobu,"Japanese, ca. 1513–1575",,Kano Yukinobu,Japanese,1513,1575,16th century,1513,1575,Hanging scroll; ink on paper,39 1/8 x 16 3/4 in. (99.4 x 42.5 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45207,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"53.156.1, .2",false,true,57346,Asian Art,Screen,,Japan,,,,,Artist,,Kano Tan'yū,"Japanese, 1602–1674",,Kano Tan'yū,Japanese,1602,1674,17th century,1600,1699,Pair of six-panel folding screens; paint and gilt on paper,Overall (each screen): 70 1/4 x 77 1/4 in. (178.4 x 196.2 cm),"Gift of Major General R. B. Woodruff, 1953",,,,,,,,,,,,Screens,,http://www.metmuseum.org/art/collection/search/57346,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"17.118.114, .115",false,true,45423,Asian Art,Screen,,Japan,Edo period (1615–1868),,,,Artist,,Unkoku Tōeki,1591–1644,,Unkoku Tōeki,Japanese,1591,1644,17th century,1600,1699,Pair of six-panel screens; ink and gold on paper,Overall (each screen): 67 1/2 x 147 in. (171.5 x 373.4 cm),"Rogers Fund, 1917",,,,,,,,,,,,Screens,,http://www.metmuseum.org/art/collection/search/45423,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"1972.179.1, .2",false,true,45082,Asian Art,Screen,,Japan,Edo period (1615–1868),,,,Artist,,Kusumi Morikage,ca. 1620–1690,,Kusumi Morikage,Japanese,1610,1700,17th century,1600,1699,Pair of six-panel folding screens ; ink and light color on paper,Overall (.1): 68 3/4 x 146 3/8 in. (174.6 x 371.8 cm) Overall (.2): 69 x 146 3/8 in. (175.3 x 371.8 cm),"Purchase, Joseph Pulitzer Bequest, 1972",,,,,,,,,,,,Screens,,http://www.metmuseum.org/art/collection/search/45082,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.496,false,true,45651,Asian Art,Screen,,Japan,Edo period (1615–1868),,,,Artist,Attributed to,Sesson Shūkei,ca. 1504–ca. 1589,,Sesson Shūkei,Japanese,1504,1589,17th century,1600,1699,Two-panel folding screen; ink on paper,59 1/4 x 66 7/16 in. (150.5 x 168.8 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Screens,,http://www.metmuseum.org/art/collection/search/45651,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -25.215.53,false,true,45654,Asian Art,Screen,,Japan,Edo period (1615–1868),,,,Artist,,Kano Eitoku,"Japanese, 1534–1590",,Kano Eitoku,Japanese,1543,1590,17th century,1600,1699,"Two-panel folding screen; ink, color, and gold on paper (obverse); ink and color on paper (reverse side)",22 x 73 1/2 in. (55.9 x 186.7 cm),"Fletcher Fund, 1925",,,,,,,,,,,,Screens,,http://www.metmuseum.org/art/collection/search/45654,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.495a–f,false,true,45695,Asian Art,Screen,,Japan,Edo period (1615–1868),,,,Artist,In the Style of,Kano Eitoku,"Japanese, 1534–1590",,Kano Eitoku,Japanese,1543,1590,17th century,1600,1699,Six-panel folding screen; ink and color on gilt paper,65 1/4 x 146 1/4in. (165.8 x 371.5 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Screens,,http://www.metmuseum.org/art/collection/search/45695,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"29.100.491, .492",false,true,45341,Asian Art,Folding screens,伝三谷等宿筆 松と椿に鷹・柳と椿に小禽図屏風|Pine and Camellia with Hawks and Willow and Camellia with Small Birds,Japan,Edo period (1615–1868),,,,Artist,Attributed to,Mitani Tōshuku,"Japanese, 1577–1654",,Mitani Tōshuku,Japanese,1577,1654,17th century,1600,1699,Pair of six-panel folding screens; ink and color on paper,Image (each): 58 7/16 in. x 10 ft. 8 13/16 in. (148.5 x 327.2 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Screens,,http://www.metmuseum.org/art/collection/search/45341,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2014.719.11,false,true,60485,Asian Art,Hanging scroll,清巌宗渭 「地獄」|Hell,Japan,Edo period (1615–1868),,,,Artist,,Seigan Sōi,"Japanese, 1588–1661",,Seigan Sōi,Japanese,1588,1661,17th century,1600,1699,Hanging scroll; ink on paper,Image: 12 x 35 1/2 in. (30.5 x 90.2 cm),"Gift of Sylvan Barnet and William Burto, in honor of Setsu Isao, 2014",,,,,,,,,,,,Calligraphy,,http://www.metmuseum.org/art/collection/search/60485,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2009.260.1, .2",false,true,75372,Asian Art,Screen,,Japan,Edo period (1615–1868),,,,Artist,,Kano Einō,"Japanese, 1631–1697",,Kano Einō,Japanese,1631,1697,17th century,1600,1699,"Pair of six-panel folding screens; ink, color, and gold on paper",Image (each screen): 44 7/8 x 111 in. (114 x 282 cm),"Purchase, Lila Acheson Wallace Gift, Mary and James G. Wallach Foundation Gift, Gift of Dr. Mortimer D. Sackler, Theresa Sackler and Family, and Dodge Fund, 2009",,,,,,,,,,,,Screens,,http://www.metmuseum.org/art/collection/search/75372,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.89,false,true,45373,Asian Art,Screen,,Japan,Momoyama period (1573–1615),,,,Artist,,Hasegawa Tōchō,"Japanese, active 1624–43",,Hasegawa Tōcho,Japanese,1624,1643,17th century,1600,1699,Six-panel screen; ink on paper,58 1/4 x 121 3/4 in. (147.9 x 309.2 cm),"Fletcher Fund, 1933",,,,,,,,,,,,Screens,,http://www.metmuseum.org/art/collection/search/45373,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"36.120.643a, b",false,true,63316,Asian Art,Incense burner,,Japan,,,,,Artist,,Hon'ami Kōetsu,"Japanese, 1558–1637",,Hon'ami Kōetsu,Japanese,1558,1637,17th century,1600,1699,"Square with sunken panels on all sides and bottom; four moulded feet; light brown clay; yellow glaze; decoration of flowers, grasses and butterflies in green and dark blue; perforated iron cover (Kyoto ware)",H. 4 1/4 in. (10.8 cm); W. sq. 5 in. (12.7 cm),"The Howard Mansfield Collection, Gift of Howard Mansfield, 1936",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/63316,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"66.155.6a, b",false,true,53214,Asian Art,Incense burner with lid,,Japan,Edo period (1615–1868),,,,Artist,,Jokei,"Japanese, died 1636",,Jokei,Japanese,1536,1636,17th century,1600,1700,Earthenware with glaze; lid of network brass grille (Raku ware),H. 4 1/4 in. (10.8 cm); Gr. Diam. 4 1/4 in. (10.8 cm),"Gift of Harold G. Henderson, 1966",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/53214,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.628,false,true,63111,Asian Art,Teabowl,,Japan,Edo period (1615–1868),,,,Artist,Attributed to,Hon'ami Kōetsu,"Japanese, 1558–1637",,Hon'ami Kōetsu,Japanese,1558,1637,17th century,1600,1699,Clay; crackled glaze with markings; (Kyoto ware),H. 3 3/4 in. (9.5 cm); Diam. 4 7/8 in. (12.4 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/63111,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -91.1.320,false,true,39576,Asian Art,Water jar,,Japan,Edo period (1615–1868),,,,Artist,,Nonomura Ninsei,"Japanese, active ca. 1646–94",,Ninsei Nonomura,Japanese,1646,1694,17th century,1600,1699,Pottery covered with glaze; handles at sides (Kyoto ware),H. 6 1/3 in. (16.1 cm),"Edward C. Moore Collection, Bequest of Edward C. Moore, 1891",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/39576,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.29,false,true,54593,Asian Art,Hanging scroll,,Japan,,,,,Artist,In the Style of,Kano Sanraku,"Japanese, 1559–1635",,Kano Sanraku,Japanese,1559,1635,17th century,1600,1699,Hanging scroll; monochrome on silk,40 1/2 x 21 in. (102.9 x 53.3 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/54593,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -60.47,false,true,57228,Asian Art,Hanging scroll,,Japan,,,,,Artist,,Kano Tan'yū,"Japanese, 1602–1674",,Kano Tan'yū,Japanese,1602,1674,17th century,1602,1674,Hanging scroll; ink on paper,5 1/4 x 19 3/8 in. (13.3 x 49.2 cm),"Rogers Fund, 1960",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/57228,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.35,false,true,54594,Asian Art,Hanging scroll,,Japan,,,,,Artist,Attributed to,Kano Tan'yū,"Japanese, 1602–1674",,Kano Tan'yū,Japanese,1602,1674,17th century,1600,1699,Hanging scroll; india ink on paper,48 x 21 1/8 in. (121.9 x 53.7 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/54594,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -53.118.2,false,true,57336,Asian Art,Hanging scroll,,Japan,,,,,Artist,Attributed to,Kano Naonobu,"Japanese, 1607–1650",,Kano Naonobu,Japanese,1607,1650,17th century,1607,1650,Hanging scroll; ink on paper,38 3/8 x 11 3/16 in. (97.5 x 28.4 cm),"Rogers Fund, 1953",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/57336,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.140.5,false,true,45702,Asian Art,Painting,,Japan,Edo period (1615–1868),,,,Artist,,Kano Shōun,1637–1702,,Kano Shōun,Japanese,1637,1702,17th century,1600,1799,Unmounted shikisi leaf; ink and color on silk,7 1/4 × 6 1/2 in. (18.4 × 16.5 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45702,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.140.6,false,true,45703,Asian Art,Painting,,Japan,Edo period (1615–1868),,,,Artist,,Kano Shōun,1637–1702,,Kano Shōun,Japanese,1637,1702,17th century,1600,1699,Unmounted shikisi leaf; ink and color on silk,7 1/4 × 6 1/2 in. (18.4 × 16.5 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45703,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.140.7,false,true,45704,Asian Art,Painting,,Japan,Edo period (1615–1868),,,,Artist,,Kano Shōun,1637–1702,,Kano Shōun,Japanese,1637,1702,17th century,1600,1699,Unmounted shikisi leaf; ink and color on silk,7 1/4 × 6 1/2 in. (18.4 × 16.5 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45704,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.140.8,false,true,45705,Asian Art,Painting,,Japan,Edo period (1615–1868),,,,Artist,,Kano Shōun,1637–1702,,Kano Shōun,Japanese,1637,1702,17th century,1600,1699,Unmounted shikisi leaf; ink and color on silk,7 1/4 × 6 1/2 in. (18.4 × 16.5 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45705,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.140.9,false,true,45706,Asian Art,Painting,,Japan,Edo period (1615–1868),,,,Artist,,Kano Shōun,1637–1702,,Kano Shōun,Japanese,1637,1702,17th century,1600,1699,Unmounted shikisi leaf; ink and color on silk,7 1/4 × 6 1/2 in. (18.4 × 16.5 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45706,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.140.10,false,true,658681,Asian Art,Painting,,Japan,Edo period (1615–1868),,,,Artist,,Kano Shōun,1637–1702,,Kano Shōun,Japanese,1637,1702,17th century,1600,1799,Unmounted shikisi leaf; ink and color on silk,7 1/4 × 6 1/2 in. (18.4 × 16.5 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/658681,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.140.11,false,true,45708,Asian Art,Painting,,Japan,Edo period (1615–1868),,,,Artist,,Kano Shōun,1637–1702,,Kano Shōun,Japanese,1637,1702,17th century,1600,1699,Unmounted shikisi leaf; ink and color on silk,7 1/4 × 6 1/2 in. (18.4 × 16.5 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45708,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.140.12,false,true,45709,Asian Art,Painting,,Japan,Edo period (1615–1868),,,,Artist,,Kano Shōun,1637–1702,,Kano Shōun,Japanese,1637,1702,17th century,1600,1699,Unmounted shikisi leaf; ink and color on silk,7 1/4 × 6 1/2 in. (18.4 × 16.5 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45709,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.140.13,false,true,45710,Asian Art,Painting,,Japan,Edo period (1615–1868),,,,Artist,,Kano Shōun,1637–1702,,Kano Shōun,Japanese,1637,1702,17th century,1600,1699,Unmounted shikisi leaf; ink and color on silk,7 1/4 × 6 1/2 in. (18.4 × 16.5 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45710,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.140.14,false,true,45711,Asian Art,Painting,,Japan,Edo period (1615–1868),,,,Artist,,Kano Shōun,1637–1702,,Kano Shōun,Japanese,1637,1702,17th century,1600,1699,Unmounted shikisi leaf; ink and color on silk,7 1/4 × 6 1/2 in. (18.4 × 16.5 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45711,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.140.15,false,true,45712,Asian Art,Painting,,Japan,Edo period (1615–1868),,,,Artist,,Kano Shōun,1637–1702,,Kano Shōun,Japanese,1637,1702,17th century,1600,1699,Unmounted shikisi leaf; ink and color on silk,7 1/4 × 6 1/2 in. (18.4 × 16.5 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45712,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.140.16,false,true,45713,Asian Art,Painting,,Japan,Edo period (1615–1868),,,,Artist,,Kano Shōun,1637–1702,,Kano Shōun,Japanese,1637,1702,17th century,1600,1699,Unmounted shikisi leaf; ink and color on silk,7 1/4 × 6 1/2 in. (18.4 × 16.5 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45713,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.140.17,false,true,45714,Asian Art,Painting,,Japan,Edo period (1615–1868),,,,Artist,,Kano Shōun,1637–1702,,Kano Shōun,Japanese,1637,1702,17th century,1600,1699,Unmounted shikisi leaf; ink and color on silk,7 1/4 × 6 1/2 in. (18.4 × 16.5 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45714,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.140.18,false,true,45715,Asian Art,Painting,,Japan,Edo period (1615–1868),,,,Artist,,Kano Shōun,1637–1702,,Kano Shōun,Japanese,1637,1702,17th century,1600,1699,Unmounted shikisi leaf; ink and color on silk,7 1/4 × 6 1/2 in. (18.4 × 16.5 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45715,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.140.19,false,true,45716,Asian Art,Painting,,Japan,Edo period (1615–1868),,,,Artist,,Kano Shōun,1637–1702,,Kano Shōun,Japanese,1637,1702,17th century,1600,1699,Unmounted shikisi leaf; ink and color on silk,7 1/4 × 6 1/2 in. (18.4 × 16.5 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45716,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.140.20,false,true,45717,Asian Art,Painting,,Japan,Edo period (1615–1868),,,,Artist,,Kano Shōun,1637–1702,,Kano Shōun,Japanese,1637,1702,17th century,1600,1699,Unmounted shikisi leaf; ink and color on silk,7 1/4 × 6 1/2 in. (18.4 × 16.5 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45717,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.140.21,false,true,45718,Asian Art,Painting,,Japan,Edo period (1615–1868),,,,Artist,,Kano Shōun,1637–1702,,Kano Shōun,Japanese,1637,1702,17th century,1600,1699,Unmounted shikisi leaf; ink and color on silk,7 1/4 × 6 1/2 in. (18.4 × 16.5 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45718,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.140.22,false,true,45719,Asian Art,Painting,,Japan,Edo period (1615–1868),,,,Artist,,Kano Shōun,1637–1702,,Kano Shōun,Japanese,1637,1702,17th century,1600,1699,Unmounted shikisi leaf; ink and color on silk,7 1/4 × 6 1/2 in. (18.4 × 16.5 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45719,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.268.56,false,true,45731,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Kusumi Morikage,ca. 1620–1690,,Kusumi Morikage,Japanese,1610,1700,17th century,1620,1690,Hanging scroll; ink on paper,48 9/16 x 20 7/8 in. (123.4 x 53.1 cm),"The Harry G. C. Packard Collection of Asian Art, Gift of Harry G. C. Packard, and Purchase, Fletcher, Rogers, Harris Brisbane Dick, and Louis V. Bell Funds, Joseph Pulitzer Bequest, and The Annenberg Fund Inc. Gift, 1975",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45731,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -59.80,false,true,45646,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,Formerly attributed to,Sōami,"Japanese, died 1525",,Sōami,Japanese,,1525,17th century,1600,1699,Hanging scroll; ink on paper,17 3/4 x 10 1/2 in. (45.1 x 26.7 cm),"Gift of Mrs. John D. Rockefeller 3rd, 1959",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45646,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.72,false,true,45645,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,Attributed to,Sōami,"Japanese, died 1525",,Sōami,Japanese,,1525,17th century,1600,1699,Hanging scroll; ink on paper,13 1/4 x 27 3/8 in. (33.7 x 69.5 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45645,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.529,false,true,45649,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,In the Style of,Sesshū Tōyō,"Japanese, 1420–1506",,Sesshū Tōyō,Japanese,1420,1506,17th century,1615,1699,Hanging scroll; ink on paper,33 1/4 x 11 5/8 in. (84.5 x 29.5 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45649,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1974.224.1–.31,false,true,74462,Asian Art,Album leaf,,Japan,Edo period (1615–1868),,,,Artist,,Kano Tsunenobu,"Japanese, 1636–1713",,Kano Tsunenobu,Japanese,1636,1713,17th century,1636,1699,Album leaf; ink and color on silk,11 7/16 x 16 3/4 in. (29 x 42.5 cm),"Gift of Mr. and Mrs. Harry Rubin, 1974",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/74462,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -54.22.1,false,true,57339,Asian Art,Painting,,Japan,Edo period (1615–1868),,,,Artist,,Matsuo Basho,"Japanese, 1644–1694",,Matsuo Basho,Japanese,1644,1694,17th century,1644,1694,Ink wash and color on paper,43 x 11 1/2 in. (109.2 x 29.2 cm),"Gift of Dr. Robert Pollak, 1954",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/57339,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.440,false,true,48971,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,In the Style of,Tawaraya Sōtatsu,"Japanese, died ca. 1640",,Tawaraya Sōtatsu,Japanese,1540,1640,17th century,1600,1640,Hanging scroll; ink and color on paper,33 7/8 x 17 1/8 in. (86 x 43.5 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/48971,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.464,false,true,48972,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,School of,Tawaraya Sōtatsu,"Japanese, died ca. 1640",,Tawaraya Sōtatsu,Japanese,1540,1640,17th century,1600,1650,Hanging scroll; ink and color on paper,49 3/4 x 18 1/4 in. (126.4 x 46.4 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/48972,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.68,false,true,45641,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,Attributed to,Shubun,"Japanese, active ca. 1414",,Shubun,Japanese,1414,1414,17th century,1600,1699,Hanging scroll; ink and color on paper,25 3/4 x 12 1/8 in. (65.4 x 30.8 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45641,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.268.79,false,true,49067,Asian Art,Hanging scroll,即非如一筆 墨蹟|Reading a Sutra by Moonlight,Japan,Edo period (1615–1868),,,,Artist,,Sokuhi Nyoitsu (Jifei Ruyi),"Chinese/ Japanese, 1616–1671",,Sokuhi Nyoitsu,Japanese,1616,1671,17th century,1616,1671,Hanging scroll; ink on paper,Image: 10 13/16 x 23 7/8 in. (27.5 x 60.6 cm) Overall with mounting: 44 1/4 × 24 5/8 in. (112.4 × 62.5 cm) Overall with knobs: 44 1/4 × 26 3/4 in. (112.4 × 68 cm),"The Harry G. C. Packard Collection of Asian Art, Gift of Harry G. C. Packard, and Purchase, Fletcher, Rogers, Harris Brisbane Dick, and Louis V. Bell Funds, Joseph Pulitzer Bequest, and The Annenberg Fund Inc. Gift, 1975",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49067,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"51.132.1a, b",false,true,58328,Asian Art,Coal container,,Japan,,,,,Artist,School of,Ogata Kōrin,"Japanese, 1658–1716",,Kōrin,Japanese,1658,1716,18th century,1700,1799,"Wood, metal, mother-of-pearl, lacquer",H. (with cover) 13 3/4 in. (34.9 cm); Diam. 16 1/4 in. (41.3 cm),"Gift of Mrs. David Randall-MacIver and Mrs. Natalie Tuttle Martin in memory of her husband Dr. George Montgomery Tuttle, 1951",,,,,,,,,,,,Woodwork,,http://www.metmuseum.org/art/collection/search/58328,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -51.132.2a–c,false,true,56533,Asian Art,Coal container,,Japan,,,,,Artist,,Ogawa Haritsu (Ritsuō),"Japanese, 1663–1747",,Ritsuō,Japanese,1663,1747,18th century,1700,1799,"Wood, metal, mother-of-pearl",H. (w/cover and base) 14 in. (35.6 cm); Diam. (grt.) 13 in. (33 cm),"Gift of Mrs. David Randall-MacIver and Mrs. Natalie Tuttle Martin in memory of her husband, Dr. George Montgomery Tuttle, 1951",,,,,,,,,,,,Woodwork,,http://www.metmuseum.org/art/collection/search/56533,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -27.117.1,false,true,57108,Asian Art,Printer's woodblock,,Japan,,,,,Artist,Original illustration designed by,Nishikawa Sukenobu,"Japanese, 1671–1750",,Nishikawa Sukenobu,Japanese,1671,1750,18th century,1700,1799,Wood,10 5/8 x 9 11/16 in. (27 x 24.6 cm),"Rogers Fund, 1927",,,,,,,,,,,,Woodblocks,,http://www.metmuseum.org/art/collection/search/57108,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -10.211.192,false,true,59880,Asian Art,Netsuke,,Japan,,,,,Artist,Formerly Attributed to,Garaku,"Japanese, active second half of the 18th century",,Garaku,Japanese,1750,1799,18th century,1700,1799,Ivory,H. 13/16 in. (2.1 cm); W. 1 1/2 in. (3.8 cm); L. 1 13/16 in. (4.6 cm),"Gift of Mrs. Russell Sage, 1910",,,,,,,,,,,,Netsuke,,http://www.metmuseum.org/art/collection/search/59880,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.226,false,true,58914,Asian Art,Inrō,古墨形鞘印籠 (宝露臺)|Inrō Imitating an Old Chinese Ink Cake,Japan,Edo period (1615–1868,,,,Artist,,Ogawa Haritsu (Ritsuō),"Japanese, 1663–1747",,Ritsuō,Japanese,1663,1747,18th century,1700,1799,"Two-part (sheath-type); lacquered wood, metal, leather, with black and brown togidashimaki-e, takamaki-e on black ground Netsuke: manjū type with maki-e Daruma Ojime: ceramic bead with incense and ash design",3 1/16 x 2 1/16 x 11/16 in. (7.7 x 5.3 x 1.7 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Inrō,,http://www.metmuseum.org/art/collection/search/58914,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.528,false,true,49082,Asian Art,Screen,,Japan,Edo period (1615–1868),,,,Artist,,Miwa Zaiei,died 1789,,Miwa Zaiei,Japanese,,1789,18th century,1700,1799,"Two-panel folding screen; ink, color, and gold on paper",40 x 49 1/4 in. (101.6 x 125.1 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Screens,,http://www.metmuseum.org/art/collection/search/49082,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -10.211.2325,false,true,59383,Asian Art,Netsuke,,Japan,Edo period (1615–1868),,,,Artist,,Sanshō,1871–1936,,Sanshō,Japanese,1871,1936,18th century,1700,1799,Wood with ivory insert,H. 2 3/4 in. (7 cm),"Gift of Mrs. Russell Sage, 1910",,,,,,,,,,,,Netsuke,,http://www.metmuseum.org/art/collection/search/59383,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.206,false,true,53936,Asian Art,Inrō,古満休伯作 群鶴蒔絵印籠|Inrō with Cranes and Pines,Japan,Edo period (1615–1868),,,,Artist,,Koma Kyūhaku V,"Japanese, died 1794",,Koma Kyūhaku V,Japanese,,1794,18th century,1701,1800,"Gold togidashi lacquer ground with gold and silver takamaki-e and hiramaki-e, and black and red lacquer Netsuke: hat and mask; lacquer on wood Ojime: jade bead",H. 3 1/16 in. (7.7 cm); W. 2 3/16 in. ( 5.5 cm); D. 7/8 in. (2.3 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Inrō,,http://www.metmuseum.org/art/collection/search/53936,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -13.67.72,false,true,45462,Asian Art,Inrō,,Japan,Edo period (1615–1868),,,,Artist,After,Ogata Kōrin,"Japanese, 1658–1716",,Kōrin,Japanese,1658,1716,18th century,1700,1799,Gold lacquer with mother-of-pearl and pewter inlay Ojime: carnelian bead Netsuke: carved wood deer,2 1/16 x 1 15/16 x 3/4 in. (5.3 x 4.9 x 1.9 cm),"Rogers Fund, 1913",,,,,,,,,,,,Inrō,,http://www.metmuseum.org/art/collection/search/45462,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.229,false,true,45457,Asian Art,Inrō,,Japan,Edo period (1615–1868),,,,Artist,After,Ogata Kōrin,"Japanese, 1658–1716",,Kōrin,Japanese,1658,1716,18th century,1700,1799,Gold maki-e and mother-of-pearl inlay Ojime: wood-and-gilt bead in shape of sake bottle Netsuke: ivory with lacquer design of toys,H. 2 3/8 in. (6.1 cm); W. 2 3/16 in. (5.5 cm); D. 13/16 in. (2.1 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Inrō,,http://www.metmuseum.org/art/collection/search/45457,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.139a–d,false,true,57925,Asian Art,Writing box,,Japan,Edo period (1615–1868),,,,Artist,Formerly Attributed to,Ogata Kōrin,"Japanese, 1658–1716",,Kōrin,Japanese,1658,1716,18th century,1700,1799,Lacquer with pewter and gold,H. 1 5/8 in. (4.1 cm); W. 8 1/4 in. (21 cm); L. 9 in. (22.9 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Lacquer,,http://www.metmuseum.org/art/collection/search/57925,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.703,false,true,44867,Asian Art,Writing box,鼠扇蒔絵象嵌硯箱|Writing Box (suzuribako) with Mice and Fan,Japan,Edo period (1615–1868),,,,Artist,School of,Ogawa Haritsu (Ritsuō),"Japanese, 1663–1747",,Ritsuō,Japanese,1663,1747,18th century,1700,1799,"Lacquered wood with gold, silver hiramaki-e, ceramic, mother-of-pearl, and pewter inlays on wood ground",H. 1 3/4 in. (4.4 cm); W. 8 1/8 in. (20.6 cm); L. 10 1/8 in. (25.7 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Lacquer,,http://www.metmuseum.org/art/collection/search/44867,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.151,false,true,58335,Asian Art,Panel,,Japan,Edo period (1615–1868),,,,Artist,Attributed to,Ogawa Haritsu (Ritsuō),"Japanese, 1663–1747",,Ritsuō,Japanese,1663,1747,18th century,1700,1799,Lacquered wood,L. 10 3/4 in. (27.3 cm); W. 14 3/4 in. (37.5 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Lacquer,,http://www.metmuseum.org/art/collection/search/58335,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"36.100.146a, b",false,true,57937,Asian Art,Box,,Japan,Edo period (1615–1868),,,,Artist,,Ogawa Haritsu (Ritsuō),"Japanese, 1663–1747",,Ritsuō,Japanese,1663,1747,18th century,1700,1799,Lacquer,H. 4 5/8 in. (11.7 cm); L. 9 3/8 in. (23.8 cm); W. 8 3/4 in. (22.2 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Lacquer,,http://www.metmuseum.org/art/collection/search/57937,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.141a–e,false,true,44940,Asian Art,Writing box,,Japan,Edo period (1615–1868),,,,Artist,Style of,Ogawa Haritsu (Ritsuō),"Japanese, 1663–1747",,Ritsuō,Japanese,1663,1747,18th century,1700,1799,Gold and glazed pottery on colored lacquer inlaid with mother-of-pearl,H. 1 1/2 in. (3.8 cm); W. 7 in. (17.8 cm); L 9 1/2 in. (24.1 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Lacquer,,http://www.metmuseum.org/art/collection/search/44940,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.769a–c,false,true,78671,Asian Art,Illustrated books,,Japan,Edo period (1615–1868),,,,Artist,,Okumura Masanobu,"Japanese, 1686–1764",,Masanobu Okumura,Japanese,1686,1764,18th century,1700,1764,Set of three woodblock printed books; ink and color on paper,each: 5 9/16 × 8 1/16 in. (14.2 × 20.5 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78671,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.188,false,true,44866,Asian Art,Inrō,,Japan,Edo period (1615–1868),,,,Artist,,Mochizuki Hanzan,"Japanese, 1743–?1790",,Mochizuki Hanzan,Japanese,1743,1790,18th century,1700,1799,"Case: powdered gold (maki-e) on lacquer with ceramic, lead, and mother-of-pearl inlays; ceramic seal on base; Fastener (ojime): alloy of silver and copper with design of mice in rice cakes; Toggle (netsuke): lacquer with design of lotus leaf and caterpillar in lead and ivory inlays",H. 3 in. (7.6 cm); W. 2 3/16 in. (5.5 cm); D. 13/16 in. (2 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Inrō,,http://www.metmuseum.org/art/collection/search/44866,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"36.100.145a, b",false,true,57929,Asian Art,Sutra chest,,Japan,Edo period (1615–1868),,,,Artist,,Mochizuki Hanzan,"Japanese, 1743–?1790",,Mochizuki Hanzan,Japanese,1743,1790,18th century,1700,1799,"Colored lacquer, gold, and ceramic on natural wood",H. 10 3/4 in. (27.3 cm); L. 17 3/4 in. (45.1 cm); W. 10 1/4 in. (26 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Lacquer,,http://www.metmuseum.org/art/collection/search/57929,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.147a–c,false,true,57939,Asian Art,Document box,,Japan,Edo period (1615–1868),,,,Artist,,Nagata Yūji,"Japanese, acive 1711–36",,Nagata Yūji,Japanese,1711,1736,18th century,1700,1799,Lacquer with gold and pewter,H. 6 in. (15.2 cm); W. 8 1/4 in. (21 cm); L. 10 3/4 in. (27.3 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Lacquer,,http://www.metmuseum.org/art/collection/search/57939,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -10.211.1981,false,true,59141,Asian Art,Netsuke,虎木彫根付|Tiger,Japan,Edo period (1615–1868),,,,Artist,,Minkō,"Japanese, ca. 1735–1816",,Minkō,Japanese,1735,1816,18th century,1700,1799,Wood,H. 1 1/8 in. (2.9 cm); W. 1 5/8 in. (4.1 cm),"Gift of Mrs. Russell Sage, 1910",,,,,,,,,,,,Netsuke,,http://www.metmuseum.org/art/collection/search/59141,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -10.211.2278,false,true,59357,Asian Art,Netsuke,獏木彫根付|Crouching Baku (Mythical Creature),Japan,Edo period (1615–1868),,,,Artist,,Sadatake,"Japanese, active 18th century",,Sadatake,Japanese,1700,1799,18th century,1700,1799,Wood,H. 2 in. (5.1 cm); W. 2 1/4 in. (5.7 cm); D. 1 1/2 in. (3.8 cm),"Gift of Mrs. Russell Sage, 1910",,,,,,,,,,,,Netsuke,,http://www.metmuseum.org/art/collection/search/59357,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -10.211.2383,false,true,59420,Asian Art,Netsuke,ガルダ木彫根付|Garuda,Japan,Edo period (1615–1868),,,,Artist,,Tori,"Japanese, active 18th century",,Tori,Japanese,1700,1799,18th century,1700,1799,Wood; dark brown,H. 1 7/8 in. (4.8 cm); W. 1 5/8 in. (4.1 cm),"Gift of Mrs. Russell Sage, 1910",,,,,,,,,,,,Netsuke,,http://www.metmuseum.org/art/collection/search/59420,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -13.67.64,false,true,45586,Asian Art,Inrō,,Japan,Edo period (1615–1868),,,,Artist,,Tōyō,"Japanese, active ca. 1764–71",,Tōyō,Japanese,1764,1771,18th century,1700,1799,Aventurine lacquer with gold sprinkled and polished lacquer; Netsuke: Box with flowers; Ojime: coral bead,2 3/8 x 2 5/16 x 15/16 in. (6 x 5.8 x 2.4 cm),"Rogers Fund, 1913",,,,,,,,,,,,Inrō,,http://www.metmuseum.org/art/collection/search/45586,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1993.341.2,false,true,45493,Asian Art,Noh mask,,Japan,Edo period (1615–1868),,,,Artist,,Genkyu Michinaga,"Japanese, active second half of the 17th century",,Genkyu Michinaga,Japanese,1650,1699,18th century,1700,1799,Painted wood,W. 5 1/2 in. (14 cm); L. 8 1/2 in. (21.6 cm),"Purchase, Lila Acheson Wallace Gift, 1993",,,,,,,,,,,,Masks,,http://www.metmuseum.org/art/collection/search/45493,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1617,false,true,37333,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Shigenaga,1697–1756,,Shigenaga,Japanese,1697,1756,18th century,1700,1756,Polychrome woodblock print; ink and color on paper,H. 5 3/4 in. (14.6 cm); W. 12 11/16 in. (32.2 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37333,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1618,false,true,37334,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Shigenaga,1697–1756,,Shigenaga,Japanese,1697,1756,18th century,1700,1756,Polychrome woodblock print; ink and color on paper,H. 5 1/4 in. (13.3 cm); W. 12 11/16 in. (32.2 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37334,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1589,false,true,55754,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Okumura Masanobu,"Japanese, 1686–1764",,Masanobu Okumura,Japanese,1686,1764,18th century,1700,1764,"""White–line"" woodblock print; ink and color on paper",H. 11 7/16 in. (29.1 cm); W. 5 1/2 in. (14 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55754,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1628,false,true,55793,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Suzuki Harunobu,"Japanese, 1725–1770",,Suzuki Harunobu,Japanese,1725,1770,18th century,1764,1772,Polychrome woodblock print; ink and color on paper,H. 11 1/2 in. (29.2 cm); W. 8 1/4 in. (21 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55793,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1651,false,true,55810,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Suzuki Harunobu,"Japanese, 1725–1770",,Suzuki Harunobu,Japanese,1725,1770,18th century,1764,1772,Polychrome woodblock print; ink and color on paper,H. 10 7/8 in. (27.6 cm); W. 8 1/8 in. (20.6 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55810,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1656,false,true,55817,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Suzuki Harunobu,"Japanese, 1725–1770",,Suzuki Harunobu,Japanese,1725,1770,18th century,1764,1772,Polychrome woodblock print (hashira-e); ink and color on paper,H. 27 1/2 in. (69.9 cm); W. 4 13/16 in. (12.2 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55817,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.265,false,true,73556,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Suzuki Harunobu,"Japanese, 1725–1770",,Suzuki Harunobu,Japanese,1725,1770,18th century,1725,1770,Polychrome woodblock print; ink and color on paper,Image: 7 5/8 x 11 in. (19.4 x 27.9 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73556,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2694,false,true,39722,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,18th century,1700,1799,Polychrome woodblock print; ink and color on paper,12 3/8 x 8 3/4 in. (31.4 x 22.2 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/39722,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.266,false,true,73557,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Rekisentei Eiri,"Japanese, active ca. 1789–1801",,Rekisentei Eiri,Japanese,1789,1801,18th century,1789,1801,Polychrome woodblock print; ink and color on paper,Image: 9 7/8 x 14 3/4 in. (25.1 x 37.5 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73557,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -25.60.7,false,true,63067,Asian Art,Bowl,,Japan,Edo period (1615–1868),,,,Artist,,Seifu Yohei,1803–1861,,Seifu Yohei,Japanese,1803,1861,18th century,1700,1799,"White porcelain decorated with blue under the glaze, the inside partly unglazed (Kyoto ware)",H. 2 3/4 in. (7 cm); Diam. 5 1/8 in. (13 cm),"Rogers Fund, 1925",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/63067,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.120.534,false,true,63193,Asian Art,Teabowl,,Japan,Edo period (1615–1868),,,,Artist,,Sonyu,"Japanese, died 1725",,Sonyu,Japanese,1625,1725,18th century,1700,1799,"Clay, thick black glaze, patch of reddish underglaze (Raku ware)",H. 5 1/4 in. (13.3 cm); Diam. 4 1/4 in. (10.8 cm),"The Howard Mansfield Collection, Gift of Howard Mansfield, 1936",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/63193,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -93.1.27,false,true,62621,Asian Art,Cup,,Japan,Edo period (1615–1868),,,,Artist,,Ogata Kenzan,"Japanese, 1663–1743",,Ogata Kenzan,Japanese,1663,1743,18th century,1700,1799,"Porcelain decorated in blue under the glaze, polychrome enamels over the glaze (Kenzan ware)",H. 2 1/4 in. (5.7 cm),"Gift of Mr. and Mrs. Samuel Colman, 1893",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/62621,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -93.1.180,false,true,62690,Asian Art,Water pot,,Japan,Edo period (1615–1868),,,,Artist,,Ogata Kenzan,"Japanese, 1663–1743",,Ogata Kenzan,Japanese,1663,1743,18th century,1700,1799,Clay covered with white and black glazes and decorated on the white parts under the glaze (Kenzan style),H. 3 1/2 in. (8.9 cm); Diam. 7 in. (17.8 cm),"Gift of Mr. and Mrs. Samuel Colman, 1893",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/62690,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -93.1.115,false,true,62670,Asian Art,Teabowl,,Japan,Edo period (1615–1868),,,,Artist,,Kiyomizu Rokubei I,"Japanese, 1737–1799",,Kiyomizu Rokubei I,Japanese,1737,1799,18th century,1700,1799,"Clay covered with glaze and overglaze (Kiyomizu ware, Awata type)",H. 3 5/8 in. (9.2 cm),"Gift of Mr. and Mrs. Samuel Colman, 1893",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/62670,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1979.455,false,true,63796,Asian Art,Stem cup,,Japan,Edo period (1615–1868),,,,Artist,,Okuda Eisen,"Japanese, 1753–1811",,Okuda Eisen,Japanese,1753,1811,18th century,1700,1799,Porcelain painted with red and green enamels,H. 4 5/8 in. (11.7 cm); Diam. 6 5/8 in. (16.8 cm),"Gift of Mr. and Mrs. John R. Menke, 1979",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/63796,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2002.447.115,false,true,49416,Asian Art,Octagonal bowl,,Japan,Edo period (1615–1868),,,,Artist,Attributed to,Shibuemon,"Japanese, active 17th century",,Shibuemon,Japanese,1600,1699,18th century,1700,1799,Porcelain with underglaze cobalt and enamels,Diam. 6 3/4 in. (17.2 cm),"Dr. and Mrs. Roger G. Gerry Collection, Bequest of Dr. and Mrs. Roger G. Gerry, 2000",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/49416,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -93.1.139,false,true,62674,Asian Art,Teabowl,,Japan,Edo period (1615–1868),,,,Artist,,Nonomura Ninsei,"Japanese, active ca. 1646–94",,Ninsei Nonomura,Japanese,1646,1694,18th century,1700,1799,Clay covered with a warm Seto glaze and a border of white enamel (Awata ware),H. 1 7/8 in. (4.8 cm); Diam. 5 in. (12.7 cm); Diam. of foot 2 in. (5.1 cm),"Gift of Mr. and Mrs. Samuel Colman, 1893",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/62674,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.90,false,true,44938,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Kawamata Tsuneyuki,1676 (?)–1741,,Kawamata Tsuneyuki,Japanese,1676,1741,18th century,1700,1741,Hanging scroll; ink and color on paper,14 3/8 x 18 5/8 in. (36.5 x 47.3 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/44938,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.268.125,false,true,45381,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Kaigetsudō Dohan,active 1710–16,,Kaigetsudō Dohan,Japanese,1710,1716,18th century,1710,1716,Hanging scroll; ink and color on paper,Image: 32 3/16 x 13 3/16 in. (81.8 x 33.5 cm) Overall with mounting: 64 3/8 x 20 1/8 in. (163.5 x 51.1 cm),"The Harry G. C. Packard Collection of Asian Art, Gift of Harry G. C. Packard, and Purchase, Fletcher, Rogers, Harris Brisbane Dick, and Louis V. Bell Funds, Joseph Pulitzer Bequest, and The Annenberg Fund Inc. Gift, 1975",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45381,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.34,false,true,49076,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,Attributed to,Kakutei,"Japanese, died 1785",,Kakutei,Japanese,,1785,18th century,1700,1785,Hanging scroll; ink and color on silk,29 1/4 x 14 1/2 in. (74.3 x 36.8 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49076,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.49,false,true,48975,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,Attributed to,Ogata Kōrin,"Japanese, 1658–1716",,Kōrin,Japanese,1658,1716,18th century,1700,1716,Hanging scroll; ink and color on silk,43 5/16 x 16 1/4 in. (110 x 41.3 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/48975,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.527,false,true,40351,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Ogata Kenzan,"Japanese, 1663–1743",,Ogata Kenzan,Japanese,1663,1743,18th century,1700,1743,Hanging scroll; ink and color on paper,10 1/4 x 13 3/8 in. (26 x 34 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/40351,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.444,false,true,49104,Asian Art,Painting,,Japan,Edo period (1615–1868),,,,Artist,In the Style of,Ogawa Haritsu (Ritsuō),"Japanese, 1663–1747",,Ritsuō,Japanese,1663,1747,18th century,1700,1747,"Matted painting; color on papier mache in relief, against a paper background",Diam. 10 3/4 in. (27.3 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49104,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.445,false,true,49105,Asian Art,Painting,,Japan,Edo period (1615–1868),,,,Artist,In the Style of,Ogawa Haritsu (Ritsuō),"Japanese, 1663–1747",,Ritsuō,Japanese,1663,1747,18th century,1700,1747,"Matted painting; color on papier-mâché in relief, against a paper background",Diam. 10 3/4 in. (27.3 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49105,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.945,false,true,36043,Asian Art,Painting,,Japan,Edo period (1615–1868),,,,Artist,In the Style of,Ogawa Haritsu (Ritsuō),"Japanese, 1663–1747",,Ritsuō,Japanese,1663,1747,18th century,1700,1747,Watercolor on paper,7 1/32 x 10 7/32 in. (17.9 x 26 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/36043,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.87.1,false,true,45769,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Nishikawa Sukenobu,"Japanese, 1671–1750",,Nishikawa Sukenobu,Japanese,1671,1750,18th century,1700,1750,Hanging scroll; ink and color on silk,Image: 11 1/4 × 14 3/4 in. (28.6 × 37.5 cm) Overall with mounting: 44 3/4 × 19 3/4 in. (113.7 × 50.2 cm) Overall with knobs: 44 3/4 × 21 5/8 in. (113.7 × 54.9 cm),"Fletcher Fund, 1933",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45769,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.42,false,true,45768,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Nishikawa Sukenobu,"Japanese, 1671–1750",,Nishikawa Sukenobu,Japanese,1671,1750,18th century,1700,1750,Hanging scroll; ink and color on paper,Image: 14 1/4 in. × 21 in. (36.2 × 53.3 cm) Overall with mounting: 51 1/8 × 26 7/8 in. (129.9 × 68.3 cm) Overall with knobs: 51 1/8 × 29 1/4 in. (129.9 × 74.3 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45768,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.268.126,false,true,44863,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Okumura Masanobu,"Japanese, 1686–1764",,Masanobu Okumura,Japanese,1686,1764,18th century,1700,1764,Hanging scroll; ink and color on silk,Image: 33 1/8 x 12 7/8 in. (84.1 x 32.7 cm) Overall with mounting: 66 3/4 x 17 3/4 in. (169.5 x 45.1 cm) Overall with knobs: 66 3/4 x 20 1/4 in. (169.5 x 51.4 cm),"The Harry G. C. Packard Collection of Asian Art, Gift of Harry G. C. Packard, and Purchase, Fletcher, Rogers, Harris Brisbane Dick, and Louis V. Bell Funds, Joseph Pulitzer Bequest, and The Annenberg Fund Inc. Gift, 1975",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/44863,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.31,false,true,45762,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Furuyama Moromasa,"Japanese, 1712–1772",,Furuyama Moromasa,Japanese,1712,1772,18th century,1712,1772,Hanging scroll; ink and color on silk,Image: 16 3/4 × 23 3/8 in. (42.5 × 59.4 cm) Overall with mounting: 55 × 29 3/4 in. (139.7 × 75.6 cm) Overall with knobs: 55 × 31 3/4 in. (139.7 × 80.6 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45762,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1985.243,false,true,49075,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Sō Shiseki,"Japanese, 1715–1786",,Sō Shiseki,Japanese,1715,1786,18th century,1715,1786,Hanging scroll; ink and color on silk,40 7/8 x 13 3/4 in. (103.8 x 34.9 cm),"Purchase, Bequest of Stephen Whitney Phoenix, by exchange, 1985",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49075,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -54.22.3,false,true,48996,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,In the Style of,Yosa Buson,"Japanese, 1716–1783",,Yosa Buson,Japanese,1716,1783,18th century,1716,1783,Hanging scroll; ink and color on paper,40 1/4 x 20 5/16 in. (102.2 x 51.6 cm),"Gift of Robert Pollak, 1954",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/48996,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.38,false,true,45231,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,18th century,1726,1792,Hanging scroll; ink and color on silk,28 1/2 x 12 1/8 in. (72.4 x 30.8 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45231,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.39,false,true,45232,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,18th century,1726,1792,Hanging scroll; ink and color on silk,28 1/2 x 12 1/2 in. (72.4 x 31.8 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45232,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.40,false,true,45233,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,18th century,1726,1792,Hanging scroll; ink and color on silk,28 1/2 x 12 1/8 in. (72.4 x 30.8 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45233,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.41,false,true,45788,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,18th century,1726,1792,Hanging scroll; ink and color on silk,38 3/4 x 8 1/4 in. (98.4 x 21 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45788,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.70c,false,true,45787,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,18th century,1726,1792,Hanging scroll; ink and color on silk,40 3/4 x 12 1/2 in. (103.5 x 31.8 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45787,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.70d,false,true,45789,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,18th century,1726,1792,Hanging scroll; ink and color on silk,40 3/4 x 12 1/2 in. (103.5 x 31.8 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45789,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.70,false,true,39630,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,Attributed to,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,18th century,1726,1792,Hanging scroll; ink and color on silk,Image: 44 x 12 in. (111.8 x 30.5 cm) Overall with mounting: 74 x 17 11/16 in. (188 x 44.9 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/39630,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.66,false,true,49079,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Soga Shōhaku,"Japanese, 1730–1781",,Soga Shōhaku,Japanese,1730,1781,18th century,1730,1781,Hanging scroll; ink and color on silk,Image: 18 x 25 in. (45.7 x 63.5 cm) Overall with mounting: 54 1/2 x 29 5/8 in. (138.4 x 75.2 cm) Overall with knobs: 54 1/2 x 32 in. (138.4 x 81.3 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49079,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.268.85,false,true,42634,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Sō Shizan,"Japanese, 1733–1805",,Sō Shizan,Japanese,1733,1805,18th century,1733,1799,Hanging scroll; ink and color on silk,Image: 36 1/4 x 13 9/16 in. (92.1 x 34.4 cm),"The Harry G. C. Packard Collection of Asian Art, Gift of Harry G. C. Packard, and Purchase, Fletcher, Rogers, Harris Brisbane Dick, and Louis V. Bell Funds, Joseph Pulitzer Bequest, and The Annenberg Fund Inc. Gift, 1975",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/42634,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.70f,false,true,45805,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Toyoharu,"Japanese, 1735–1814",,Utagawa Toyoharu,Japanese,1735,1814,18th century,1735,1799,Hanging scroll; ink and color on silk,40 3/4 x 12 1/2 in. (103.5 x 31.8 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45805,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1971.190,false,true,49050,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Matsumura Goshun,"Japanese, 1752–1811",,Matsumura Goshun,Japanese,1752,1811,18th century,1752,1799,Hanging scroll; ink and color on paper,39 13/16 x 11 5/8 in. (101.2 x 29.5 cm),"Rogers Fund, 1971",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49050,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.268.77,false,true,49049,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Matsumura Goshun,"Japanese, 1752–1811",,Matsumura Goshun,Japanese,1752,1811,18th century,1752,1799,Hanging scroll; ink and color on paper,69 x 36 7/8 in. (175.2 x 93.6 cm),"The Harry G. C. Packard Collection of Asian Art, Gift of Harry G. C. Packard, and Purchase, Fletcher, Rogers, Harris Brisbane Dick, and Louis V. Bell Funds, Joseph Pulitzer Bequest, and The Annenberg Fund Inc. Gift, 1975",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49049,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.70e,false,true,45796,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyonaga,"Japanese, 1752–1815",,Torii Kiyonaga,Japanese,1752,1815,18th century,1752,1799,Hanging scroll; ink and color on silk,40 3/4 x 12 1/2 in. (103.5 x 31.8 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45796,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.47,false,true,45794,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyonaga,"Japanese, 1752–1815",,Torii Kiyonaga,Japanese,1752,1815,18th century,1752,1799,Hanging scroll; ink and color on silk,39 7/8 x 14 1/2 in. (101.3 x 36.8 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45794,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.268.127,false,true,45799,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",,Kubo Shunman,Japanese,1757,1820,18th century,1757,1799,Hanging scroll; ink and color on paper,Image: 32 x 11 15/16 in. (81.3 x 30.3 cm) Overall: 63 7/8 x 18 3/4in. (162.2 x 47.6 cm),"The Harry G. C. Packard Collection of Asian Art, Gift of Harry G. C. Packard, and Purchase, Fletcher, Rogers, Harris Brisbane Dick, and Louis V. Bell Funds, Joseph Pulitzer Bequest, and The Annenberg Fund Inc. Gift, 1975",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45799,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.268.101,false,true,57160,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Kushiro Unsen,"Japanese, 1759–1811",,Kushiro Unsen,Japanese,1759,1811,18th century,1759,1799,Hanging scroll; ink and color on paper,42 5/8 x 11 1/8 in. (108.3 x 28.3 cm),"The Harry G. C. Packard Collection of Asian Art, Gift of Harry G. C. Packard, and Purchase, Fletcher, Rogers, Harris Brisbane Dick, and Louis V. Bell Funds, Joseph Pulitzer Bequest, and The Annenberg Fund Inc. Gift, 1975",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/57160,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.55,false,true,48891,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Teisai Hokuba,"Japanese, 1771–1844",,Teisai Hokuba,Japanese,1771,1844,18th century,1771,1799,"Hanging scroll; ink and color, and gold on silk",29 x 10 3/4 in. (73.7 x 27.3 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/48891,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.70a,false,true,45785,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Isoda Koryūsai,"Japanese, 1735–ca. 1790",,Isoda Koryūsai,Japanese,1735,1790,18th century,1735,1790,Hanging scroll; ink and color on silk,40 3/4 x 12 1/2 in. (103.5 x 31.8 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45785,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.404,false,true,45771,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Kondo Katsunobu,"Japanese, active 1716–1736",,Kondo Katsunobu,Japanese,1716,1736,18th century,1716,1736,Hanging scroll; ink and color on paper,38 3/16 x 16 5/8 in. (97 x 42.3 cm),"Gift of Francis T. Henderson Jr., in memory of Harold G. Henderson, 1976",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45771,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.69,false,true,45791,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,Attributed to,Katsukawa Shunchō,"Japanese, active ca. 1783–95",,Katsukawa Shunchō,Japanese,1775,1795,18th century,1783,1795,Hanging scroll; ink and color on silk,35 7/8 x 12 1/8 in. (91.1 x 30.8 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45791,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -13.67.74,false,true,58908,Asian Art,Inrō,,Japan,,,,,Artist,,Gyokuzan,1737–1812,,Gyokuzan,Japanese,1737,1812,19th century,1800,1899,"Lacquer, roiro, gold and colored hiramakie, takamakie, kirigane, nashiji; Interior: nashiji and fundame",3 3/4 x 2 x 1 1/8 in. (9.5 x 5.1 x 2.9 cm),"Rogers Fund, 1913",,,,,,,,,,,,Inrō,,http://www.metmuseum.org/art/collection/search/58908,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.214,false,true,58895,Asian Art,Inrō,,Japan,,,,,Artist,,Koma Kōryū,"Japanese, died 1796",,Koma Kōryū,Japanese,,1796,19th century,1800,1899,"Lacquer, silver ground, gold, black and red togidashi, ivory lid; Interior: nashiji and fundame",2 1/2 x 2 3/16 x 1 1/16 (6.3 x 5.6 x 2.7 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Inrō,,http://www.metmuseum.org/art/collection/search/58895,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1985.399.8,false,true,59650,Asian Art,Netsuke,,Japan,,,,,Artist,,Masakazu,"Japanese, died 1886",,Masakazu,Japanese,,1886,19th century,1800,1899,Wood,H. 1 1/8 in. (2.9 cm); W. 1 1/8 in. (2.9 cm),"Gift of Alvin H. Schechter, 1985",,,,,,,,,,,,Netsuke,,http://www.metmuseum.org/art/collection/search/59650,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.82.7,false,true,58637,Asian Art,Inrō,,Japan,,,,,Artist,In the Style of,Hanabusa Itchō,"Japanese, 1652–1724",,Hanabusa Itchō,Japanese,1652,1724,19th century,1800,1899,"Metal and lacquer, dark silver metal, incised, roiro, gold hiramakie, raden, aogai; Interior: fundame, four boxes",4 1/8 x 1 9/16 x 7/8 in. (10.4 x 4 x 2.2 cm),"Gift of Wilton Lloyd-Smith and his wife, Marjorie Fleming Lloyd-Smith, 1929",,,,,,,,,,,,Inrō,,http://www.metmuseum.org/art/collection/search/58637,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.879,false,true,78769,Asian Art,Woodblock,,Japan,,,,,Artist,After,Kuwagata Keisai,"Japanese, 1764–1824",,Kuwagata Keisai,Japanese,1764,1824,19th century,1800,1824,Woodblock,8 9/16 × 6 3/4 in. (21.7 × 17.2 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Woodblocks,,http://www.metmuseum.org/art/collection/search/78769,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -10.211.1894,false,true,59113,Asian Art,Netsuke,,Japan,,,,,Artist,,Toyomasa,"Japanese, 1773–1856",,Toyomasa,Japanese,1773,1856,19th century,1800,1899,Wood,H. 2 in. (5.1 cm),"Gift of Mrs. Russell Sage, 1910",,,,,,,,,,,,Netsuke,,http://www.metmuseum.org/art/collection/search/59113,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.830,false,true,58735,Asian Art,Inrō,,Japan,,,,,Artist,,Shibata Zeshin,"Japanese, 1807–1891",,Shibata Zeshin,Japanese,1807,1891,19th century,1800,1899,"Bamboo, black, silver and gold hiramakie, takamakie, wood lid; Interior: plain",2 1/4 x 2 5/8 x 1 5/8 in. (5.7 x 6.7 x 4.2 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Inrō,,http://www.metmuseum.org/art/collection/search/58735,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.248,false,true,58934,Asian Art,Inrō,,Japan,,,,,Artist,,Shibata Zeshin,"Japanese, 1807–1891",,Shibata Zeshin,Japanese,1807,1891,19th century,1800,1899,"Lacquer, dark brown, imitating leather, relief, light brown rim; Interior: dark brown and nashiji",2 15/16 x 2 5/16 x 11/16 in. (7.4 x 5.9 x 1.7 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Inrō,,http://www.metmuseum.org/art/collection/search/58934,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -51.137.5,false,true,58329,Asian Art,Box,,Japan,,,,,Artist,,Shibata Zeshin,"Japanese, 1807–1891",,Shibata Zeshin,Japanese,1807,1891,19th century,1800,1899,Lacquer,H. 1 3/8 in. (3.5 cm); W. 2 1/4 in. (5.7 cm); L. 3 1/4 in. (8.3 cm),"Purchase, Gifts, Bequests, and Funds from various donors, by exchange, 1951",,,,,,,,,,,,Lacquer,,http://www.metmuseum.org/art/collection/search/58329,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.153,false,true,58336,Asian Art,Tray,,Japan,,,,,Artist,Lacquered by,Shibata Zeshin,"Japanese, 1807–1891",,Shibata Zeshin,Japanese,1807,1891,19th century,1800,1899,Lacquer with gold,H. 1 1/2 in. (3.8 cm); W. 4 5/8 in. (11.7 cm); L. 8 1/2 in. (21.6 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Lacquer,,http://www.metmuseum.org/art/collection/search/58336,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.154,false,true,56529,Asian Art,Tray,,Japan,,,,,Artist,Lacquered by,Shibata Zeshin,"Japanese, 1807–1891",,Shibata Zeshin,Japanese,1807,1891,19th century,1800,1899,Lacquer,W. 10 in. (25.4 cm); L. 20 3/4 in. (52.7 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Lacquer,,http://www.metmuseum.org/art/collection/search/56529,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.170,false,true,56166,Asian Art,Tea caddy,,Japan,,,,,Artist,Lacquered by,Shibata Zeshin,"Japanese, 1807–1891",,Shibata Zeshin,Japanese,1807,1891,19th century,1800,1899,Lacquer,H. 4 1/8 in. (10.5 cm); Diam. 3 in. (7.6 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Lacquer,,http://www.metmuseum.org/art/collection/search/56166,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.175,false,true,58353,Asian Art,Box,,Japan,,,,,Artist,Attributed to,Shibata Zeshin,"Japanese, 1807–1891",,Shibata Zeshin,Japanese,1807,1891,19th century,1800,1899,Lacquer,H. 3/8 in. (1 cm); W. 1 1/2 in. (3.8 cm); L. 7 1/8 in. (18.1 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Lacquer,,http://www.metmuseum.org/art/collection/search/58353,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"36.100.168a, b",false,true,58345,Asian Art,Box,,Japan,,,,,Artist,,Shibata Zeshin,"Japanese, 1807–1891",,Shibata Zeshin,Japanese,1807,1891,19th century,1800,1899,Lacquer,H. 1 in. (2.5 cm); W. 3 7/8 in. (9.8 cm); D. 3 7/8 in. (9.8 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Lacquer,,http://www.metmuseum.org/art/collection/search/58345,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -66.155.1,false,true,57348,Asian Art,Screen,,Japan,,,,,Artist,,Shibata Zeshin,"Japanese, 1807–1891",,Shibata Zeshin,Japanese,1807,1891,19th century,1800,1899,Two-panel folding screen; ink on paper,41 1/2 x 55 in. (105.4 x 139.7 cm),"Gift of Harold G. Henderson, 1966",,,,,,,,,,,,Screens,,http://www.metmuseum.org/art/collection/search/57348,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.749,false,true,78651,Asian Art,Illustrated book,,Japan,,,,,Artist,,Nishiyama Ken,"Japanese, 1833–1897",,Nishiyama Ken,Japanese,1833,1897,19th century,1833,1897,Accordion album; ink and color on paper,10 5/8 × 5 1/2 in. (27 × 14 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78651,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -67.55.82,false,true,59630,Asian Art,Netsuke,,Japan,,,,,Artist,,Koichi,"Japanese, 19th century",,Koichi,Japanese,1800,1899,19th century,1800,1899,"Wood, horn",H. 1 3/4 in. (4.4 cm); W. 1 1/8 in. (2.9 cm); D. 1 1/4 in. (3.2 cm),"Bequest of Susan Dwight Bliss, 1966",,,,,,,,,,,,Netsuke,,http://www.metmuseum.org/art/collection/search/59630,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1979.459.1,false,true,70647,Asian Art,Pressing board,,Japan,,,,,Artist,,Tabata Sadahiko,"Japanese, 19th century",,Tabata Sadahiko,Japanese,0019,0019,19th century,1800,1899,,9 x 18 1/2 in. (22.86 x 46.99 cm),"Gift of Takami Sugiura and Sadahiko Tabata, 1979",,,,,,,,,,,,Textiles-Methods and Materials,,http://www.metmuseum.org/art/collection/search/70647,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.221,false,true,58902,Asian Art,Inrō,,Japan,,,,,Artist,,Tōyō,"Japanese, active ca. 1764–71",,Tōyō,Japanese,1764,1771,19th century,1800,1899,"Lacquer, roiro, gyobu, gold, black, silver and brown hiramakie, takamakie; Interior: nashiji and fundame",3 15/16 x 1 7/8 x 1 1/8 in. (10 x 4.8 x 2.9 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Inrō,,http://www.metmuseum.org/art/collection/search/58902,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -10.211.1001,false,true,60266,Asian Art,Netsuke,,Japan,,,,,Artist,,Ryūsa,"Japanese, active late 18th century",,Ryūsa,Japanese,1767,1799,19th century,1800,1899,Ivory,H. 1 3/16 in. (3 cm); W. 9/16 in. (1.4 cm); L. 1 3/8 in. (3.5 cm),"Gift of Mrs. Russell Sage, 1910",,,,,,,,,,,,Netsuke,,http://www.metmuseum.org/art/collection/search/60266,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -10.211.1262,false,true,60327,Asian Art,Netsuke,,Japan,,,,,Artist,,Ryūsa,"Japanese, active late 18th century",,Ryūsa,Japanese,1767,1799,19th century,1800,1899,Ivory,H. 3/4 in. (1.9 cm); Diam. 1 7/8 in. (4.8 cm),"Gift of Mrs. Russell Sage, 1910",,,,,,,,,,,,Netsuke,,http://www.metmuseum.org/art/collection/search/60327,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -55.153,false,true,57110,Asian Art,Printer's woodblock,,Japan,,,,,Artist,Original print designed by,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,19th century,1800,1899,Wood,13 3/4 x 9 1/2 in. (34.9 x 24.1 cm),"Gift of Harry E. Goldman, 1955",,,,,,,,,,,,Woodblocks,,http://www.metmuseum.org/art/collection/search/57110,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -13.67.84,false,true,45569,Asian Art,Inrō,,Japan,Edo period (1615–1868),,,,Artist,,Yamada Jōkasai (1681–1704),,,Yamada Jōkasai,Japanese,1681,1704,19th century,1800,1899,"Lacquer decorated with sprinkled hiramakie lacquer, sprinkled and polished takamakie lacquer relief, and nashiji (pear skin) lacquer; Ojime: ivory and gold lacquer bead; Netsuke: carved wood bird (signed Shuko)",3 5/16 x 2 5/16 x 1 1/16 in. (8.4 x 5.8 x 2.7 cm),"Rogers Fund, 1913",,,,,,,,,,,,Inrō,,http://www.metmuseum.org/art/collection/search/45569,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.889,false,true,58809,Asian Art,Inrō,,Japan,Edo period (1615–1868),,,,Artist,,Toyōsai (1772–1845),,,Toyōsai,Japanese,1772,1845,19th century,1800,1899,Black lacquer with sprinkled gold and silver makie and mother-of-pearl Ojime: bead with openwork design of waves; silver Netsuke: fish laid on bamboo branch; stained ivory,2 7/8 x 3 1/16 x 11/16 in. (7.3 x 7.7 x 1.8 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Inrō,,http://www.metmuseum.org/art/collection/search/58809,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.198,false,true,45503,Asian Art,Inrō,,Japan,Edo period (1615–1868),,,,Artist,,Yamada Jōkasai (1681–1704),,,Yamada Jōkasai,Japanese,1681,1704,19th century,1800,1899,"Sprinkled gold lacquer with mother-of-pearl, stone, and metal Ojime: ovoid gold bead decorated with grasses Netuske: black wood carved as a piece of firewood",3 3/4 x 2 11/16 x 3/4 in. (9.5 x 6.8 x 1.9 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Inrō,,http://www.metmuseum.org/art/collection/search/45503,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"36.100.157a, b",false,true,45364,Asian Art,Incense box,,Japan,Edo period (1615–1868),,,,Artist,,Yamada Jōkasai (1681–1704),,,Yamada Jōkasai,Japanese,1681,1704,19th century,1800,1899,Gold-speckled aventurine (nashiji) lacquer with sprinkled and polished design (hiramakie),H. 1 1/16 in. (2.7 cm); W. 1 1/8 in. (2.9 cm); L. 3 5/8 in. (9.2 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Lacquer,,http://www.metmuseum.org/art/collection/search/45364,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"36.100.159a, b",false,true,45497,Asian Art,Round box,,Japan,Edo period (1615–1868),,,,Artist,,Yamada Jōkasai (1681–1704),,,Yamada Jōkasai,Japanese,1681,1704,19th century,1800,1899,"Lacquer on wood with gold, mother-of-pearl inlay, and colored lacquer",H. 1 3/8 in. (3.5 cm); Diam. 3 in. (7.6 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Lacquer,,http://www.metmuseum.org/art/collection/search/45497,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.40.885a–e,false,true,57953,Asian Art,Writing box,,Japan,Edo period (1615–1868),,,,Artist,,Tatsuke Takanori,1757–1833,,Tatsuke Takanori,Japanese,1757,1833,19th century,1800,1899,Gold and silver maki-e with colored lacquer on black lacquer,H. 2 1/16 in. (5.2 cm); W. 9 in. (22.9 cm); D. 9 1/2 in. (24.2 cm),"Bequest of Benjamin Altman, 1913",,,,,,,,,,,,Lacquer,,http://www.metmuseum.org/art/collection/search/57953,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.195,false,true,58881,Asian Art,Inrō,,Japan,Edo period (1615–1868),,,,Artist,,Kano Seisen’in,1775–1828,,Kano Seisen’in,Japanese,1775,1828,19th century,1800,1899,"Lacquer with sprinkled gold, silver, and red makie and takamakie Ojime: bead; tortoiseshell Netsuke: basket; woven reeds",3 7/16 x 2 3/8 x 15/16 in. (8.8 x 6 x 2.4 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Inrō,,http://www.metmuseum.org/art/collection/search/58881,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.199,false,true,58883,Asian Art,Inrō,山田常嘉斎作・狩野晴川院筆 満月に鵞鳥図印籠|Inrō with Goose Flying across the Full Moon,Japan,Edo period (1615–1868),,,,Artist,After,Kano Seisen’in,1775–1828,,Kano Seisen’in,Japanese,1775,1828,19th century,1800,1899,"Lacquer, kinji, gold, silver, black and red hiramakie, togidashi, aogai inlay; Interior: nashiji and fundame",Overall (inro): H. 3 1/16 in. (7.7 cm); W. 2 1/16 in. (5.2 cm); D. 13/16 in. (2 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Inrō,,http://www.metmuseum.org/art/collection/search/58883,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.745,false,true,58657,Asian Art,Inrō,古満巨柳作 鷹蒔絵印籠|Inrō with Hawks on Perches,Japan,Edo period (1615–1868),,,,Artist,,Koma Kōryū,"Japanese, died 1796",,Koma Kōryū,Japanese,,1796,19th century,1800,1899,"Black lacquer ground with gold and silver togidashi, takamaki-e and hiramaki-e, red lacquer, and applied gold and silver foil Netsuke: gourd; guri lacquer, silver ring and stopper in chrysanthemum shape Ojime: butterfly and flower; cloisonné bead",2 15/16 x 2 5/8 x 3/4 in. (7.4 x 6.7 x 1.9 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Inrō,,http://www.metmuseum.org/art/collection/search/58657,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.213,false,true,45575,Asian Art,Inrō,,Japan,Edo period (1615–1868),,,,Artist,,Koma Kōryū,"Japanese, died 1796",,Koma Kōryū,Japanese,,1796,19th century,1800,1899,Roiro (waxen) lacquer with decoration in togidashi sprinkled and polished lacquer and nashiji (pear-skin) lacquer; Interior: nashiji and fundame; Ojime: metal (zogan) inlay with spider; Netsuke: carved wood with stone inlay of snail on broken roof tile,3 x 1 7/8 x 1 1/8 in. (7.6 x 4.8 x 2.8 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Inrō,,http://www.metmuseum.org/art/collection/search/45575,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.690,false,true,45563,Asian Art,Writing box,橅夫蒔絵硯箱|Writing Box (Suzuribako) with Woodcutter,Japan,Edo period (1615–1868),,,,Artist,In the Style of,Ogata Kōrin,"Japanese, 1658–1716",,Kōrin,Japanese,1658,1716,19th century,1800,1899,Black and gold lacquer on wood with mother-of-pearl and pewter inlay,H. 2 in. (5.1 cm); W. 9 7/8 in. (25.1 cm); L. 9 1/4 in. (23.5 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Lacquer,,http://www.metmuseum.org/art/collection/search/45563,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.691,false,true,45564,Asian Art,Writing box,,Japan,Edo period (1615–1868),,,,Artist,In the Style of,Ogata Kōrin,"Japanese, 1658–1716",,Kōrin,Japanese,1658,1716,19th century,1800,1899,Black and gold lacquer on wood with mother-of-pearl and pewter inlay,H. 1 7/8 in. (4.8 cm); W. 8 3/4 in. (22.2 cm); L. 9 1/2 in. (24.1 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Lacquer,,http://www.metmuseum.org/art/collection/search/45564,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.697,false,true,58168,Asian Art,Box,,Japan,Edo period (1615–1868),,,,Artist,Style of,Ogata Kōrin,"Japanese, 1658–1716",,Kōrin,Japanese,1658,1716,19th century,1800,1899,Gold inlaid with mother-of-pearl and tin,H. 4 3/4 in. (12.1 cm); W. 4 1/2 in. (11.4 cm); L. 7 3/4 in. (19.7 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Lacquer,,http://www.metmuseum.org/art/collection/search/58168,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.719,false,true,56523,Asian Art,Incense box,江戸時代 琳派 梅蒔絵螺鈿香合|Incense Box with Flowering Plum Tree,Japan,Edo period (1615–1868),,,,Artist,School of,Ogata Kōrin,"Japanese, 1658–1716",,Kōrin,Japanese,1658,1716,19th century,1800,1899,"Gold lacquer with gold hiramaki-e, black lacquer, and mother-of-pearl inlay",H. 1 1/2 in. (3.8 cm); W. 2 1/4 in. (5.7 cm); L. 2 1/4 in. (5.7 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Lacquer,,http://www.metmuseum.org/art/collection/search/56523,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"58.169a, b",false,true,58334,Asian Art,Writing box,橅夫蒔絵硯箱|Writing Box (Suzuribako) with Woodcutter,Japan,Edo period (1615–1868),,,,Artist,Attributed to,Ogata Kōrin,"Japanese, 1658–1716",,Kōrin,Japanese,1658,1716,19th century,1800,1899,Black and gold lacquer on wood with gold maki-e and mother-of-pearl inlay,(a): H. 8 1/4 in. (21 cm); W. 7 3/8 in. (18.7 cm); D. 1 3/8 in. (3.5 cm) (b): H. 8 9/16 in. (21.7 cm); W. 7 3/4 in. (19.7 cm); D. 15/16 in. (2.4 cm),"Gift of Nathan Hammer, 1958",,,,,,,,,,,,Lacquer,,http://www.metmuseum.org/art/collection/search/58334,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.832,false,true,58737,Asian Art,Inrō,,Japan,Edo period (1615–1868),,,,Artist,In the Style of,Ogawa Haritsu (Ritsuō),"Japanese, 1663–1747",,Ritsuō,Japanese,1663,1747,19th century,1800,1899,Colored togidashi and gold hiramaki-e on black lacquer,3 5/16 x 2 1/2 x 1 1/16 in. (8.4 x 6.4 x 2.7 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Inrō,,http://www.metmuseum.org/art/collection/search/58737,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.679,false,true,58096,Asian Art,Document box,,Japan,Edo period (1615–1868),,,,Artist,Style of,Ogawa Haritsu (Ritsuō),"Japanese, 1663–1747",,Ritsuō,Japanese,1663,1747,19th century,1800,1899,"Colored lacquer, gold and silver foil, mother-of-pearl, ivory, tortoiseshell, and ceramic on black lacquer",H. 2 3/4 in. (7 cm); W. 10 in. (25.4 cm); L. 11 in. (27.9 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Lacquer,,http://www.metmuseum.org/art/collection/search/58096,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.727,false,true,58642,Asian Art,Inrō,,Japan,Edo period (1615–1868),,,,Artist,,Sō Shiseki,"Japanese, 1715–1786",,Sō Shiseki,Japanese,1715,1786,19th century,1800,1899,"Lacquer, roiro, gold foil, aogai inlay; Interior: roiro",3 1/16 x 3 1/16 x 15/16 in. (7.8 x 7.8 x 2.4 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Inrō,,http://www.metmuseum.org/art/collection/search/58642,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.811,false,true,58717,Asian Art,Inrō,,Japan,Edo period (1615–1868),,,,Artist,,Mori Sosen,"Japanese, 1747–1821",,Mori Sosen,Japanese,1747,1821,19th century,1800,1899,"Metal, brass metal, incised, various applied metals; Interior: silver metal",2 5/8 x 1 15/16 x 13/16 in. (6.7 x 5 x 2 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Inrō,,http://www.metmuseum.org/art/collection/search/58717,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.845,false,true,58771,Asian Art,Inrō,,Japan,Edo period (1615–1868),,,,Artist,,Mori Sosen,"Japanese, 1747–1821",,Mori Sosen,Japanese,1747,1821,19th century,1800,1899,"Lacquer, gold and silver hirame, gold, red and coloured hiramakie, takamakie; Interior: red lacquer, fundame and decoration; the interior risers decorated in gold and silver togidashi with various brocade pattern, key-fret, waves, etc.",3 1/8 x 3 1/8 x 13/16 in. (7.9 x 8 . 2.1 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Inrō,,http://www.metmuseum.org/art/collection/search/58771,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.250,false,true,45576,Asian Art,Inrō,,Japan,Edo period (1615–1868),,,,Artist,,Shibata Zeshin,"Japanese, 1807–1891",,Shibata Zeshin,Japanese,1807,1891,19th century,1800,1899,Roiro (waxen) lacquer with sprinkled hiramkie lacquer and togidashi sprinkled and polished lacquer; Interior: nashiji and fundame; Ojime: silver shibuichi lacquer bead decorated with design of castle grounds; Netsuke: carved wooden sleeping goose,2 11/16 x 2 1/16 x 15/16 in. (6.8 x 5.3 x 2.4 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Inrō,,http://www.metmuseum.org/art/collection/search/45576,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.210,false,true,54240,Asian Art,Inrō,,Japan,Edo period (1615–1868),,,,Artist,,Nakayama Komin,"Japanese, 1808–1870",,Nakayama Komin,Japanese,1808,1870,19th century,1800,1899,"Hiramaki-e with nashiji on black lacquer, roiro, nashiji, gold and silver hiramakie, gold and silver foil; Interior: nashiji and fundame; Ojime: ivory bead with vines and grasses in gold lacquer; Netsuke: woven basket with shell and gold lacquer)",3 1/16 x 2 1/16 x 13/16 in. (7.8 x 5.2 x 2 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Inrō,,http://www.metmuseum.org/art/collection/search/54240,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.40.879a–n,false,true,44916,Asian Art,Writing box,中山胡民作 伊勢物語硯箱|Writing Box (Suzuribako) with Episodes from the Tales of Ise (Ise monogatari),Japan,Edo period (1615–1868),,,,Artist,,Nakayama Komin,"Japanese, 1808–1870",,Nakayama Komin,Japanese,1808,1870,19th century,1800,1899,Gold maki-e with inlaid silver on lacquer,H. 1 1/2 in. (3.8 cm); W. 7 in. (17.8 cm); D. 7 3/4 in. (19.7 cm),"Bequest of Benjamin Altman, 1913",,,,,,,,,,,,Lacquer,,http://www.metmuseum.org/art/collection/search/44916,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -13.67.34,false,true,58576,Asian Art,Inrō,山田常嘉斎作 木蓮オウム蒔絵螺鈿印籠|Inrō with Cockatoo and Magnolia,Japan,Edo period (1615–1868),,,,Artist,,Yamada Jōkasai,"Japanese, 1811–1879",,Yamada Jōkasai,Japanese,1811,1879,19th century,1800,1899,"Red lacquer ground with gold maki-e, carved red lacquer, and mother-of-pearl inlay Netsuke: poppy; ivory Ojime: roundels; red and green lacquer in wood",3 5/8 x 1 13/16 x 1 3/16 in. (9.2 x 4.6 x 3 cm),"Rogers Fund, 1913",,,,,,,,,,,,Inrō,,http://www.metmuseum.org/art/collection/search/58576,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.673,false,true,78594,Asian Art,Illustrated book,奇観幀|Album of Twelve Nanga-style Landscapes (Kikanchō),Japan,Edo period (1615–1868),,,,Artist,,Tanomura Chokunyū,"Japanese, 1814–1907",,Tanomura Chokunyū,Japanese,1814,1907,19th century,1800,1868,Accordion album; ink and color on paper,8 7/16 × 5 13/16 in. (21.5 × 14.8 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78594,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.722,false,true,58289,Asian Art,Pipe-case with pipe,,Japan,Edo period (1615–1868),,,,Artist,,Tamakaji Zōkoku,"Japanese, 1803?–1866",,Tamakaji Zōkoku,Japanese,1803,1866,19th century,1800,1899,"Lacquer, silver and shakudo",W. 1 1/2 in. (3.8 cm); L. 11 in. (27.9 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Lacquer,,http://www.metmuseum.org/art/collection/search/58289,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.338a–d,false,true,73591,Asian Art,Illustrated book,橫濱開港見聞誌|Observations on the Opening of Yokohama (Yokohama kaikō kenbunshi),Japan,Edo period (1615–1868),,,,Artist,,Utagawa (Gountei) Sadahide,"Japanese, 1807–1878/79",,Utagawa (Goutei) Sadahide,Japanese,1807,1879,19th century,1807,1879,Three volumes of woodblock printed books; ink on paper,Image (a-c): 9 1/2 x 6 3/4 x 3/4 in. (24.1 x 17.1 x 1.9 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/73591,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.339a–c,false,true,73592,Asian Art,Illustrated book,橫濱開港見聞誌|Observations on the Opening of Yokohama (Yokohama kaiko kenbunshi),Japan,Edo period (1615–1868),,,,Artist,,Utagawa (Gountei) Sadahide,"Japanese, 1807–1878/79",,Utagawa (Goutei) Sadahide,Japanese,1807,1879,19th century,1807,1879,Three volumes of woodblock printed books; ink on paper,Image (a-c): 9 5/8 x 6 7/8 x 1/2 in. (24.4 x 17.5 x 1.3 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/73592,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.183,false,true,58871,Asian Art,Inrō,梶川文竜斎作 宝船蒔絵印籠|Inrō with Treasure Boat,Japan,Edo period (1615–1868),,,,Artist,,Kajikawa Bunryūsai,"Japanese, ca. 1751–1817",,Kajikawa Bunryūsai,Japanese,1741,1827,19th century,1800,1899,"Gold and black lacquer and nashiji ground with gold and silver hiramaki-e, red lacquer, and mother-of-pearl inlay Netsuke: kingfisher; carved ivory Ojime: Chinese children; porcelain (Kutani ware",3 7/16 x 2 1/16 x 13/16 in. (8.7 x 5.3 x 2 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Inrō,,http://www.metmuseum.org/art/collection/search/58871,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"JIB141a, b",false,true,48887,Asian Art,Two booklets,,Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,19th century,1750,1900,Two volumes of drawings; ink and color on paper,each: 7 3/4 × 5 1/4 in. (19.7 × 13.3 cm),"Fletcher Fund, 1941",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/48887,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -81.1.569,false,true,45436,Asian Art,Sake cup,,Japan,Edo period (1615–1868),,,,Artist,,Shomosai,"Japanese, active late 18th–early 19th century",,Shomosai,Japanese,0018,0019,19th century,1800,1899,Gold lacquer on red lacquer ground,H. 1 in. (2.5 cm); W. 4 3/8 in. (11.1 cm),"Bequest of Stephen Whitney Phoenix, 1881",,,,,,,,,,,,Lacquer,,http://www.metmuseum.org/art/collection/search/45436,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.82.2,false,true,58633,Asian Art,Inrō,光琳様式 梅鶴蒔絵螺鈿印籠|Inrō with Crane and Plum Tree,Japan,Meiji period (1868–1912),,,,Artist,Style of,Ogata Kōrin,"Japanese, 1658–1716",,Kōrin,Japanese,1658,1716,19th century,1800,1899,Gold lacquer ground with mother-of-pearl and pewter inlay Netsuke: fish on wheels; carved wood Ojime: vajra (thunderbolt); metal,2 7/16 x 2 1/4 x 13/16 in. (6.2 x 5.7 x 2.1 cm),"Gift of Wilton Lloyd-Smith and his wife, Marjorie Fleming Lloyd-Smith, 1929",,,,,,,,,,,,Inrō,,http://www.metmuseum.org/art/collection/search/58633,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -57.46,false,true,36077,Asian Art,Inrō,,Japan,Meiji period (1868–1912),,,,Artist,,Shibata Zeshin,"Japanese, 1807–1891",,Shibata Zeshin,Japanese,1807,1891,19th century,1800,1899,Gold and colored lacquer on wood Signed: Zeshin Ojime: carved wooden dragon (signed: Ichiryūsai Furuta) Netsuke: ceramic figure of Kaduzōsu (signed: Eiraku),2 1/4 x 2 3/4 x 1 in. (5.72 x 6.99 x 2.54 cm),"Purchase, Gifts, Bequests, and Funds from various donors, by exchange, 1957",,,,,,,,,,,,Inrō,,http://www.metmuseum.org/art/collection/search/36077,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -57.53,false,true,36078,Asian Art,Inrō,,Japan,Meiji period (1868–1912),,,,Artist,In the style of,Shibata Zeshin,"Japanese, 1807–1891",,Shibata Zeshin,Japanese,1807,1891,19th century,1800,1899,"Colored lacquer and gold maki-e on wood Netsuke: ivory in form of fox reclining on large leaf; Ojima: gold bead; connecting cord, dark green silk Inro with netsuke and ojime",3 1/16 x 2 1/8 x 13/16 in. (7.7 x 5.4 x 2 cm),"Rogers Fund, 1957",,,,,,,,,,,,Inrō,,http://www.metmuseum.org/art/collection/search/36078,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -13.67.13,false,true,58563,Asian Art,Inrō,,Japan,Meiji period (1868–1912),,,,Artist,In the style of,Shibata Zeshin,"Japanese, 1807–1891",,Shibata Zeshin,Japanese,1807,1891,19th century,1800,1899,"Gold, silver, red, and black hiramaki-e, takamaki-e, mother-of-pearl inlay on bamboo; interior: plain wood, drawers",2 13/16 x 1 9/16 x 11/16 in. (7.2 x 4 x 1.8 cm),"Rogers Fund, 1913",,,,,,,,,,,,Inrō,,http://www.metmuseum.org/art/collection/search/58563,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -38.25.148,false,true,59011,Asian Art,Inrō,,Japan,Meiji period (1868–1912),,,,Artist,,Shibata Zeshin,"Japanese, 1807–1891",,Shibata Zeshin,Japanese,1807,1891,19th century,1800,1899,"Black lacquer with wood, shell, and glass inlays; Fastener (ojime): metal; Toggle (netsuke): carved ivory in the shape of a demon",H. 3 1/16 in. (9.4 cm); W. 1 7/8 in. (4.8 cm); D. 1 3/16 in. (3 cm),"Gift of Mrs. George A. Crocker (Elizabeth Masten), 1937",,,,,,,,,,,,Inrō,,http://www.metmuseum.org/art/collection/search/59011,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.247,false,true,58933,Asian Art,Inrō,,Japan,Meiji period (1868–1912),,,,Artist,,Shibata Zeshin,"Japanese, 1807–1891",,Shibata Zeshin,Japanese,1807,1891,19th century,1800,1899,"Lacquer, roiro, incised, gold, silver and brown hiramakie, takamakie, aogai; Interior: gyobu nashiji and fundame",2 7/8 x 2 x 13/16 in. (7.3 x 5.1 x 2 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Inrō,,http://www.metmuseum.org/art/collection/search/58933,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.252,false,true,58937,Asian Art,Inrō,,Japan,Meiji period (1868–1912),,,,Artist,,Shibata Zeshin,"Japanese, 1807–1891",,Shibata Zeshin,Japanese,1807,1891,19th century,1800,1899,Gold and silver maki-e with colored lacquer on black lacquer Ojime: gilded metal with fox-monk Netuske: Lacquer oval box with diamond floral design (signed Kiyoharu),3 11/16 x 2 1/16 x 1 1/8 in. (9.4 x 5.2 x 2.9 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Inrō,,http://www.metmuseum.org/art/collection/search/58937,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.253,false,true,45448,Asian Art,Inrō,,Japan,Meiji period (1868–1912),,,,Artist,,Shibata Zeshin,"Japanese, 1807–1891",,Shibata Zeshin,Japanese,1807,1891,19th century,1800,1899,Roiro (waxen) lacquer with black hiramakie sprinkled and polished lacquer and takamakie sprinkled and polished lacquer relief; Interior: roiro; Netsuke: lacquer in shape of a worn ink stick; Ojime: black lacquer bead,2 5/16 x 1 1/2 x 11/16 in. (5.9 x 3.8 x 1.7 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Inrō,,http://www.metmuseum.org/art/collection/search/45448,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.257,false,true,58940,Asian Art,Inrō,,Japan,Meiji period (1868–1912),,,,Artist,,Shibata Zeshin,"Japanese, 1807–1891",,Shibata Zeshin,Japanese,1807,1891,19th century,1800,1899,"Lacquer, roiro, gold black, brown hiramakie, aogai; Interior: nashiji and fundame",2 7/8 x 1 15/16 x 3/4 in. (7.3 x 4.9 x 1.9 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Inrō,,http://www.metmuseum.org/art/collection/search/58940,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -54.77,false,true,56577,Asian Art,Tray,,Japan,Meiji period (1868–1912),,,,Artist,Style of,Shibata Zeshin,"Japanese, 1807–1891",,Shibata Zeshin,Japanese,1807,1891,19th century,1800,1899,"Hiramaki-e, cut-out silver foil, gold, silver application, mother-of-pearl application",H. 1/2 in. (1.3 cm); W. 3 5/16 in. (8.4 cm); L. 6 7/16 in. (16.4 cm),"Gift of Joseph U. Seo, 1954",,,,,,,,,,,,Lacquer,,http://www.metmuseum.org/art/collection/search/56577,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"36.100.169a, b",false,true,58346,Asian Art,Tea caddy,,Japan,Meiji period (1868–1912),,,,Artist,In the style of,Shibata Zeshin,"Japanese, 1807–1891",,Shibata Zeshin,Japanese,1807,1891,19th century,1800,1899,Colored lacquer and gold maki-e on wood,H. 1 7/8 in. (4.8 cm); Diam. 2 3/4 in. (7 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Lacquer,,http://www.metmuseum.org/art/collection/search/58346,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.155,false,true,58337,Asian Art,Tray,,Japan,Meiji period (1868–1912),,,,Artist,,Ikeda Taishin,"Japanese, 1825–1903",,Ikeda Taishin,Japanese,1825,1903,19th century,1800,1899,"Silver, gold takamaki-e, hiramaki-e, kirikane, black lacquer",W. 5 in. (12.7 cm); L. 8 in. (20.3 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Lacquer,,http://www.metmuseum.org/art/collection/search/58337,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -91.1.684,false,true,58463,Asian Art,Inrō,,Japan,Meiji period (1868–1912),,,,Artist,Signed,Tōyō,"Japanese, active ca. 1764–71",,Tōyō,Japanese,1764,1771,19th century,1800,1899,"Gold hiramaki-e, takamaki-e, cut-out gold foil application and mother-of-pearl inlay on red lacquer ground; Inside: Nashiji and fundame",H. 2 13/16 in. (7.2 cm); W. 2 3/8 in. (6 cm); D. 13/16 in. (2.1 cm),"Edward C. Moore Collection, Bequest of Edward C. Moore, 1891",,,,,,,,,,,,Inrō,,http://www.metmuseum.org/art/collection/search/58463,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"36.100.172a, b",false,true,58350,Asian Art,Box,花熨斗蒔絵短冊箱|Poem Card (Tanzaku) Box with Flower Bouquet,Japan,Meiji period (1869–1912),,,,Artist,Style of,Shibata Zeshin,"Japanese, 1807–1891",,Shibata Zeshin,Japanese,1807,1891,19th century,1800,1899,"Lacquered wood with gold, silver, color, takamaki-e, hiramaki-e, and togidashimaki-e",H. 7/8 in. (2.2 cm); W. 2 3/8 in. (6 cm); L. 14 1/2 in. (36.8 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Lacquer,,http://www.metmuseum.org/art/collection/search/58350,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1960,false,true,54533,Asian Art,Woodblock print,,Japan,,,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",,Kubo Shunman,Japanese,1757,1820,19th century,1800,1899,Polychrome woodblock print (surimono); ink and color on paper,8 1/8 x 7 3/8 in. (20.6 x 18.7 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54533,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2148,false,true,55006,Asian Art,Print,,Japan,,,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",,Kubo Shunman,Japanese,1757,1820,19th century,1800,1899,Polychrome woodblock print (surimono); ink and color on paper,5 3/8 x 7 3/4 in. (13.7 x 19.7 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55006,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2157,false,true,55064,Asian Art,Woodblock print,,Japan,,,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",,Kubo Shunman,Japanese,1757,1820,19th century,1800,1899,Polychrome woodblock print (surimono); ink and color on paper,8 3/16 x 7 1/4 in. (20.8 x 18.4 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55064,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2210,false,true,53976,Asian Art,Print,,Japan,,,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",(?),Kubo Shunman,Japanese,1757,1820,19th century,1800,1899,Polychrome woodblock print (surimono); ink and color on paper,8 3/8 x 7 3/8 in. (21.3 x 18.7 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/53976,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2225,false,true,53991,Asian Art,Woodblock print,,Japan,,,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",,Kubo Shunman,Japanese,1757,1820,19th century,1800,1899,Polychrome woodblock print (surimono); ink and color on paper,8 15/16 x 4 5/16 in. (22.7 x 11 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/53991,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2270,false,true,54043,Asian Art,Print,,Japan,,,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",(?),Kubo Shunman,Japanese,1757,1820,19th century,1800,1899,Part of an album of woodblock prints (surimono); ink and color on paper,5 1/2 x 5 1/4 in. (14 x 13.3 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54043,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2284,false,true,54057,Asian Art,Woodblock print,,Japan,,,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",,Kubo Shunman,Japanese,1757,1820,19th century,1800,1899,Part of an album of woodblock prints (surimono); ink and color on paper,8 x 10 3/16 in. (20.3 x 25.9 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54057,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2285,false,true,54058,Asian Art,Woodblock print,,Japan,,,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",,Kubo Shunman,Japanese,1757,1820,19th century,1800,1899,Part of an album of woodblock prints (surimono); ink and color on paper,8 3/8 x 5 3/8 in. (21.3 x 13.7 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54058,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2286,false,true,54059,Asian Art,Print,,Japan,,,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",(?),Kubo Shunman,Japanese,1757,1820,19th century,1800,1899,Part of an album of woodblock prints (surimono); ink and color on paper,8 1/4 x 7 3/8 in. (21 x 18.7 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54059,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2289,false,true,54062,Asian Art,Print,,Japan,,,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",,Kubo Shunman,Japanese,1757,1820,19th century,1800,1899,Part of an album of woodblock prints (surimono); ink and color on paper,8 3/8 x 7 1/4 in. (21.3 x 18.4 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54062,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2290,false,true,54063,Asian Art,Print,,Japan,,,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",,Kubo Shunman,Japanese,1757,1820,19th century,1800,1899,Part of an album of woodblock prints (surimono); ink and color on paper,8 1/4 x 7 1/4 in. (21 x 18.4 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54063,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2299,false,true,54072,Asian Art,Print,,Japan,,,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",(?),Kubo Shunman,Japanese,1757,1820,19th century,1800,1899,Part of an album of woodblock prints (surimono); ink and color on paper,8 1/8 x 7 3/8 in. (20.6 x 18.7 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54072,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2300,false,true,54073,Asian Art,Print,,Japan,,,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",(?),Kubo Shunman,Japanese,1757,1820,19th century,1800,1899,Part of an album of woodblock prints (surimono); ink and color on paper,5 1/4 x 7 1/8 in. (13.3 x 18.1 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54073,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2302,false,true,54086,Asian Art,Print,,Japan,,,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",(?),Kubo Shunman,Japanese,1757,1820,19th century,1800,1899,Part of an album of woodblock prints (surimono); ink and color on paper,8 x 7 1/4 in. (20.3 x 18.4 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54086,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2303,false,true,54087,Asian Art,Woodblock print,,Japan,,,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",,Kubo Shunman,Japanese,1757,1820,19th century,1800,1899,Part of an album of woodblock prints (surimono); ink and color on paper,8 1/4 x 7 3/8 in. (21 x 18.7 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54087,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2304,false,true,54088,Asian Art,Woodblock print,,Japan,,,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",,Kubo Shunman,Japanese,1757,1820,19th century,1800,1899,Part of an album of woodblock prints (surimono); ink and color on paper,8 3/8 x 7 3/8 in. (21.3 x 18.7 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54088,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2305,false,true,54089,Asian Art,Print,,Japan,,,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",,Kubo Shunman,Japanese,1757,1820,19th century,1800,1899,Part of an album of woodblock prints (surimono); ink and color on paper,8 3/8 x 7 3/8 in. (21.3 x 18.7 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54089,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2308,false,true,54092,Asian Art,Print,,Japan,,,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",,Kubo Shunman,Japanese,1757,1820,19th century,1800,1899,Part of an album of woodblock prints (surimono); ink and color on paper,8 1/4 x 7 3/8 in. (21 x 18.7 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54092,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2310,false,true,54094,Asian Art,Print,,Japan,,,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",,Kubo Shunman,Japanese,1757,1820,19th century,1800,1899,Part of an album of woodblock prints (surimono); ink and color on paper,8 5/16 x 7 3/8 in. (21.1 x 18.7 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54094,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2311,false,true,54095,Asian Art,Print,,Japan,,,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",,Kubo Shunman,Japanese,1757,1820,19th century,1800,1899,Part of an album of woodblock prints (surimono); ink and color on paper,8 1/4 x 7 1/4 in. (21 x 18.4 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54095,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2312,false,true,54096,Asian Art,Print,,Japan,,,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",,Kubo Shunman,Japanese,1757,1820,19th century,1800,1899,Part of an album of woodblock prints (surimono); ink and color on paper,8 1/4 x 7 1/4 in. (21 x 18.4 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54096,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2313,false,true,54097,Asian Art,Print,,Japan,,,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",,Kubo Shunman,Japanese,1757,1820,19th century,1800,1899,Part of an album of woodblock prints (surimono); ink and color on paper,8 1/4 x 7 1/4 in. (21 x 18.4 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54097,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2316,false,true,54100,Asian Art,Print,,Japan,,,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",,Kubo Shunman,Japanese,1757,1820,19th century,1800,1899,Part of an album of woodblock prints (surimono); ink and color on paper,8 1/4 x 7 1/4 in. (21 x 18.4 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54100,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2325,false,true,54109,Asian Art,Print,,Japan,,,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",,Kubo Shunman,Japanese,1757,1820,19th century,1800,1899,Part of an album of woodblock prints (surimono); ink and color on paper,8 1/4 x 7 5/16 in. (21 x 18.6 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54109,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2329,false,true,54113,Asian Art,Print,,Japan,,,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",,Kubo Shunman,Japanese,1757,1820,19th century,1800,1899,Part of an album of woodblock prints (surimono); ink and color on paper,8 1/4 x 7 1/4 in. (21 x 18.4 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54113,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2334,false,true,54118,Asian Art,Print,,Japan,,,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",,Kubo Shunman,Japanese,1757,1820,19th century,1800,1899,Part of an album of woodblock prints (surimono); ink and color on paper,8 1/8 x 6 5/8 in. (20.6 x 16.8 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54118,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2347,false,true,54132,Asian Art,Woodblock print,,Japan,,,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",,Kubo Shunman,Japanese,1757,1820,19th century,1800,1899,Part of an album of woodblock prints (surimono); ink and color on paper,6 1/8 x 11 in. (15.6 x 27.9 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54132,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2348,false,true,54133,Asian Art,Print,,Japan,,,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",,Kubo Shunman,Japanese,1757,1820,19th century,1800,1899,Part of an album of woodblock prints (surimono); ink and color on paper,7 1/4 x 4 7/8 in. (18.4 x 12.4 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54133,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2354,false,true,54139,Asian Art,Print,,Japan,,,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",,Kubo Shunman,Japanese,1757,1820,19th century,1800,1899,Part of an album of woodblock prints (surimono); ink and color on paper,7 15/16 x 7 1/8 in. (20.2 x 18.1 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54139,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2355,false,true,53813,Asian Art,Print,,Japan,,,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",,Kubo Shunman,Japanese,1757,1820,19th century,1800,1899,Polychrome woodblock print (surimono); ink and color on paper,7 7/8 x 7 1/8 in. (20 x 18.1 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/53813,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2356,false,true,54140,Asian Art,Print,,Japan,,,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",,Kubo Shunman,Japanese,1757,1820,19th century,1800,1899,Part of an album of woodblock prints (surimono); ink and color on paper,7 3/4 x 7 3/16 in. (19.7 x 18.3 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54140,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2357,false,true,54141,Asian Art,Print,,Japan,,,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",,Kubo Shunman,Japanese,1757,1820,19th century,1800,1899,Part of an album of woodblock prints (surimono); ink and color on paper,8 x 7 1/4 in. (20.3 x 18.4 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54141,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2358,false,true,54142,Asian Art,Print,,Japan,,,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",,Kubo Shunman,Japanese,1757,1820,19th century,1800,1899,Part of an album of woodblock prints (surimono); ink and color on paper,7 13/16 x 7 1/8 in. (19.8 x 18.1 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54142,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2359,false,true,54143,Asian Art,Print,,Japan,,,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",,Kubo Shunman,Japanese,1757,1820,19th century,1800,1899,Part of an album of woodblock prints (surimono); ink and color on paper,7 13/16 x 7 13/16 in. (19.8 x 19.8 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54143,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2360,false,true,54144,Asian Art,Print,,Japan,,,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",,Kubo Shunman,Japanese,1757,1820,19th century,1800,1899,Part of an album of woodblock prints (surimono); ink and color on paper,7 15/16 x 7 1/8 in. (20.2 x 18.1 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54144,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2332,false,true,54116,Asian Art,Print,,Japan,,,,,Artist,,Utagawa Toyokuni I,"Japanese, 1769–1825",(?),Utagawa Toyokuni I,Japanese,1769,1825,19th century,1800,1899,Part of an album of woodblock prints (surimono); ink and color on paper,8 1/4 x 7 5/16 in. (21 x 18.6 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54116,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2361,false,true,54145,Asian Art,Print,,Japan,,,,,Artist,,Teisai Hokuba,"Japanese, 1771–1844",,Teisai Hokuba,Japanese,1771,1844,19th century,1800,1899,Part of an album of woodblock prints (surimono); ink and color on paper,8 5/16 x 7 5/16 in. (21.1 x 18.6 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54145,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2227,false,true,53993,Asian Art,Print,,Japan,,,,,Artist,,Takashima Chiharu,"Japanese, 1777–1859",(?),Takashima Chiharu,Japanese,1777,1859,19th century,1800,1899,Polychrome woodblock print (surimono); ink and color on paper,8 13/16 x 3 15/16 in. (22.4 x 10 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/53993,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2368,false,true,54152,Asian Art,Woodblock print,,Japan,,,,,Artist,,Takashima Chiharu,"Japanese, 1777–1859",,Takashima Chiharu,Japanese,1777,1859,19th century,1800,1899,Part of an album of woodblock prints (surimono); ink and color on paper,8 7/8 x 3 3/4 in. (22.5 x 9.5 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54152,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2327,false,true,54111,Asian Art,Woodblock print,,Japan,,,,,Artist,,Totoya Hokkei,"Japanese, 1780–1850",,Totoya Hokkei,Japanese,1780,1850,19th century,1800,1899,Part of an album of woodblock prints (surimono); ink and color on paper,5 9/16 x 7 3/8 in. (14.1 x 18.7 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54111,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2345,false,true,54130,Asian Art,Print,,Japan,,,,,Artist,,Totoya Hokkei,"Japanese, 1780–1850",,Totoya Hokkei,Japanese,1780,1850,19th century,1800,1899,Part of an album of woodblock prints (surimono); ink and color on paper,8 x 10 1/4 in. (20.3 x 26 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54130,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2369,false,true,54153,Asian Art,Print,,Japan,,,,,Artist,,Totoya Hokkei,"Japanese, 1780–1850",,Totoya Hokkei,Japanese,1780,1850,19th century,1800,1899,Part of an album of woodblock prints (surimono); ink and color on paper,4 1/2 x 10 1/2 in. (11.4 x 26.7 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54153,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2390,false,true,54174,Asian Art,Woodblock print,,Japan,,,,,Artist,,Totoya Hokkei,"Japanese, 1780–1850",,Totoya Hokkei,Japanese,1780,1850,19th century,1800,1899,Part of an album of woodblock prints (surimono); ink and color on paper,5 3/8 x 7 3/8 in. (13.7 x 18.7 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54174,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2278,false,true,54051,Asian Art,Print,,Japan,,,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,19th century,1800,1899,Part of an album of woodblock prints (surimono); ink and color on paper,8 1/4 x 7 1/6 in. (21 x 18.2 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54051,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2319,false,true,54103,Asian Art,Print,,Japan,,,,,Artist,,Keisai Eisen,"Japanese, 1790–1848",,Keisai Eisen,Japanese,1790,1848,19th century,1800,1899,Part of an album of woodblock prints (surimono); ink and color on paper,7 3/4 x 6 9/16 in. (19.7 x 16.7 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54103,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2333,false,true,54117,Asian Art,Woodblock print,,Japan,,,,,Artist,,Keisai Eisen,"Japanese, 1790–1848",,Keisai Eisen,Japanese,1790,1848,19th century,1800,1899,Part of an album of woodblock prints (surimono); ink and color on paper,7 11/16 x 6 5/8 in. (19.5 x 16.8 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54117,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2162,false,true,55069,Asian Art,Print,,Japan,,,,,Artist,,Yashima Gakutei,"Japanese, 1786?–1868",,Yashima Gakutei,Japanese,1786,1868,19th century,1800,1899,Polychrome woodblock print (surimono); ink and color on paper,8 3/16 x 7 1/8 in. (20.8 x 18.1 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55069,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2163,false,true,55070,Asian Art,Print,,Japan,,,,,Artist,,Yashima Gakutei,"Japanese, 1786?–1868",,Yashima Gakutei,Japanese,1786,1868,19th century,1800,1899,Polychrome woodblock print (surimono); ink and color on paper,8 3/16 x 7 1/8 in. (20.8 x 18.1 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55070,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2322,false,true,54106,Asian Art,Print,,Japan,,,,,Artist,,Yashima Gakutei,"Japanese, 1786?–1868",,Yashima Gakutei,Japanese,1786,1868,19th century,1800,1899,Part of an album of woodblock prints (surimono); ink and color on paper,5 3/8 x 7 3/8 in. (13.7 x 18.7 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54106,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2324,false,true,54108,Asian Art,Print,,Japan,,,,,Artist,,Yashima Gakutei,"Japanese, 1786?–1868",,Yashima Gakutei,Japanese,1786,1868,19th century,1800,1899,Part of an album of woodblock prints (surimono); ink and color on paper,8 3/8 x 7 3/8 in. (21.3 x 18.7 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54108,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2343,false,true,54128,Asian Art,Print,,Japan,,,,,Artist,,Yashima Gakutei,"Japanese, 1786?–1868",,Yashima Gakutei,Japanese,1786,1868,19th century,1800,1899,Part of an album of woodblock prints (surimono); ink and color on paper,8 5/16 x 7 1/4 in. (21.1 x 18.4 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54128,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2326,false,true,54110,Asian Art,Print,,Japan,,,,,Artist,,Ishikawa Kazan,"Japanese, active 1810–1823",,Ishikawa Kazan,Japanese,1810,1823,19th century,1800,1899,Polychrome woodblock print (surimono); ink and color on paper,8 1/4 x 7 5/16 in. (21 x 18.6 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54110,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2158,false,true,55065,Asian Art,Woodblock print,,Japan,,,,,Artist,,Ryūryūkyo Shinsai,"Japanese, active ca. 1799–1823",,Ryūryūkyo Shinsai,Japanese,1799,1823,19th century,1750,1835,Polychrome woodblock print (surimono); ink and color on paper,5 5/8 x 7 1/2 in. (14.3 x 19.1 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55065,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2171,false,true,55085,Asian Art,Print,,Japan,,,,,Artist,,Ryūryūkyo Shinsai,"Japanese, active ca. 1799–1823",,Ryūryūkyo Shinsai,Japanese,1799,1823,19th century,1800,1899,Polychrome woodblock print (surimono); ink and color on paper,8 1/4 x 7 3/8 in. (21 x 18.7 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55085,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2207,false,true,53973,Asian Art,Woodblock print,,Japan,,,,,Artist,,Ryūryūkyo Shinsai,"Japanese, active ca. 1799–1823",,Ryūryūkyo Shinsai,Japanese,1799,1823,19th century,1800,1899,Polychrome woodblock print (surimono); ink and color on paper,5 1/2 x 7 3/8 in. (14 x 18.7 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/53973,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2208,false,true,53974,Asian Art,Woodblock print,,Japan,,,,,Artist,,Ryūryūkyo Shinsai,"Japanese, active ca. 1799–1823",,Ryūryūkyo Shinsai,Japanese,1799,1823,19th century,1800,1899,Polychrome woodblock print (surimono); ink and color on paper,8 x 7 1/4 in. (20.3 x 18.4 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/53974,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2212,false,true,53978,Asian Art,Print,,Japan,,,,,Artist,,Ryūryūkyo Shinsai,"Japanese, active ca. 1799–1823",,Ryūryūkyo Shinsai,Japanese,1799,1823,19th century,1800,1899,Polychrome woodblock print (surimono); ink and color on paper,5 1/2 x 7 3/8 in. (14 x 18.7 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/53978,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2226,false,true,53992,Asian Art,Woodblock print,,Japan,,,,,Artist,,Ryūryūkyo Shinsai,"Japanese, active ca. 1799–1823",,Ryūryūkyo Shinsai,Japanese,1799,1823,19th century,1800,1899,Polychrome woodblock print (surimono); ink and color on paper,8 1/4 x 11 1/6 in. (21 x 28.4 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/53992,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2229,false,true,53995,Asian Art,Print,,Japan,,,,,Artist,,Ryūryūkyo Shinsai,"Japanese, active ca. 1799–1823",,Ryūryūkyo Shinsai,Japanese,1799,1823,19th century,1800,1899,Polychrome woodblock print (surimono); ink and color on paper,5 x 6 13/16 in. (12.7 x 17.3 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/53995,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2230,false,true,53996,Asian Art,Print,,Japan,,,,,Artist,,Ryūryūkyo Shinsai,"Japanese, active ca. 1799–1823",,Ryūryūkyo Shinsai,Japanese,1799,1823,19th century,1800,1899,Polychrome woodblock print (surimono); ink and color on paper,5 x 7 1/16 in. (12.7 x 17.9 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/53996,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2234,false,true,54002,Asian Art,Woodblock print,,Japan,,,,,Artist,,Ryūryūkyo Shinsai,"Japanese, active ca. 1799–1823",,Ryūryūkyo Shinsai,Japanese,1799,1823,19th century,1800,1899,Part of an album of woodblock prints (surimono); ink and color on paper,8 1/4 x 10 15/16 in. (21 x 27.8 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54002,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2269,false,true,54042,Asian Art,Woodblock print,,Japan,,,,,Artist,,Ryūryūkyo Shinsai,"Japanese, active ca. 1799–1823",,Ryūryūkyo Shinsai,Japanese,1799,1823,19th century,1800,1899,Part of an album of woodblock prints (surimono); ink and color on paper,6 3/4 x 8 3/4 in. (17.1 x 22.2 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54042,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2275,false,true,54048,Asian Art,Print,,Japan,,,,,Artist,,Ryūryūkyo Shinsai,"Japanese, active ca. 1799–1823",,Ryūryūkyo Shinsai,Japanese,1799,1823,19th century,1800,1899,Part of an album of woodblock prints (surimono); ink and color on paper,5 7/16 x 7 7/16 in. (13.8 x 18.9 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54048,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2276,false,true,54049,Asian Art,Print,,Japan,,,,,Artist,,Ryūryūkyo Shinsai,"Japanese, active ca. 1799–1823",,Ryūryūkyo Shinsai,Japanese,1799,1823,19th century,1800,1899,Part of an album of woodblock prints (surimono); ink and color on paper,5 7/16 x 7 1/2 in. (13.8 x 19.1 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54049,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2280,false,true,54053,Asian Art,Print,,Japan,,,,,Artist,,Ryūryūkyo Shinsai,"Japanese, active ca. 1799–1823",,Ryūryūkyo Shinsai,Japanese,1799,1823,19th century,1800,1899,Part of an album of woodblock prints (surimono); ink and color on paper,8 x 7 1/4 in. (20.3 x 18.4 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54053,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2281,false,true,54054,Asian Art,Print,,Japan,,,,,Artist,,Ryūryūkyo Shinsai,"Japanese, active ca. 1799–1823",,Ryūryūkyo Shinsai,Japanese,1799,1823,19th century,1800,1899,Part of an album of woodblock prints (surimono); ink and color on paper,5 1/2 x 7 7/16 in. (14 x 18.9 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54054,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2282,false,true,54055,Asian Art,Print,,Japan,,,,,Artist,,Ryūryūkyo Shinsai,"Japanese, active ca. 1799–1823",,Ryūryūkyo Shinsai,Japanese,1799,1823,19th century,1800,1899,Part of an album of woodblock prints (surimono); ink and color on paper,5 1/2 x 7 7/16 in. (14 x 18.9 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54055,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2283,false,true,54056,Asian Art,Woodblock print,,Japan,,,,,Artist,,Ryūryūkyo Shinsai,"Japanese, active ca. 1799–1823",,Ryūryūkyo Shinsai,Japanese,1799,1823,19th century,1800,1899,Part of an album of woodblock prints (surimono); ink and color on paper,4 1/8 x 5 11/16 in. (10.5 x 14.4 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54056,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2287,false,true,54060,Asian Art,Woodblock print,,Japan,,,,,Artist,,Ryūryūkyo Shinsai,"Japanese, active ca. 1799–1823",,Ryūryūkyo Shinsai,Japanese,1799,1823,19th century,1800,1899,Part of an album of woodblock prints (surimono); ink and color on paper,5 7/16 x 7 1/4 in. (13.8 x 18.4 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54060,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2301,false,true,54085,Asian Art,Woodblock print,,Japan,,,,,Artist,,Ryūryūkyo Shinsai,"Japanese, active ca. 1799–1823",,Ryūryūkyo Shinsai,Japanese,1799,1823,19th century,1800,1899,Part of an album of woodblock prints (surimono); ink and color on paper,5 3/8 x 7 1/4 in. (13.7 x 18.4 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54085,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2314,false,true,54098,Asian Art,Print,,Japan,,,,,Artist,,Ryūryūkyo Shinsai,"Japanese, active ca. 1799–1823",,Ryūryūkyo Shinsai,Japanese,1799,1823,19th century,1800,1899,Part of an album of woodblock prints (surimono); ink and color on paper,5 1/2 x 7 3/8 in. (14 x 18.7 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54098,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2317,false,true,54101,Asian Art,Print,,Japan,,,,,Artist,,Ryūryūkyo Shinsai,"Japanese, active ca. 1799–1823",,Ryūryūkyo Shinsai,Japanese,1799,1823,19th century,1800,1899,Part of an album of woodblock prints (surimono); ink and color on paper,5 5/16 x 7 3/16 in. (13.5 x 18.3 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54101,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2323,false,true,54107,Asian Art,Print,,Japan,,,,,Artist,,Ryūryūkyo Shinsai,"Japanese, active ca. 1799–1823",,Ryūryūkyo Shinsai,Japanese,1799,1823,19th century,1800,1899,Part of an album of woodblock prints (surimono); ink and color on paper,5 1/2 x 7 3/8 in. (14 x 18.7 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54107,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2328,false,true,54112,Asian Art,Woodblock print,,Japan,,,,,Artist,,Ryūryūkyo Shinsai,"Japanese, active ca. 1799–1823",,Ryūryūkyo Shinsai,Japanese,1799,1823,19th century,1800,1899,Part of an album of woodblock prints (surimono); ink and color on paper,5 5/8 x 7 9/16 in. (14.3 x 19.2 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54112,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2375,false,true,54159,Asian Art,Print,,Japan,,,,,Artist,,Ryūryūkyo Shinsai,"Japanese, active ca. 1799–1823",,Ryūryūkyo Shinsai,Japanese,1799,1823,19th century,1800,1899,Part of an album of woodblock prints (surimono); ink and color on paper,7 11/16 x 6 9/16 in. (19.5 x 16.7 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54159,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2388,false,true,54172,Asian Art,Print,,Japan,,,,,Artist,,Ryūryūkyo Shinsai,"Japanese, active ca. 1799–1823",,Ryūryūkyo Shinsai,Japanese,1799,1823,19th century,1800,1899,Part of an album of woodblock prints (surimono); ink and color on paper,8 1/2 x 7 1/2 in. (21.6 x 19.1 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54172,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2389,false,true,54173,Asian Art,Print,,Japan,,,,,Artist,,Ryūryūkyo Shinsai,"Japanese, active ca. 1799–1823",,Ryūryūkyo Shinsai,Japanese,1799,1823,19th century,1800,1899,Part of an album of woodblock prints (surimono); ink and color on paper,8 1/4 x 7 3/8 in. (21 x 18.7 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54173,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2391,false,true,54175,Asian Art,Print,,Japan,,,,,Artist,,Ryūryūkyo Shinsai,"Japanese, active ca. 1799–1823",,Ryūryūkyo Shinsai,Japanese,1799,1823,19th century,1800,1899,Part of an album of woodblock prints (surimono); ink and color on paper,5 7/8 x 7 1/2 in. (14.9 x 19.1 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54175,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2307,false,true,54091,Asian Art,Print,,Japan,,,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,19th century,1800,1899,Part of an album of woodblock prints (surimono); ink and color on paper,5 1/8 x 7 3/16 in. (13 x 18.3 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54091,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1930,false,true,54470,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",,Kubo Shunman,Japanese,1757,1820,19th century,1757,1820,Polychrome woodblock print (surimono); ink and color on paper,5 3/8 x 10 11/16 in. (13.7 x 27.1 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54470,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1949,false,true,54517,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",,Kubo Shunman,Japanese,1757,1820,19th century,1800,1899,Polychrome woodblock print (surimono); ink and color on paper,5 3/8 x 7 1/4 in. (13.7 x 18.4 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54517,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1969,false,true,54553,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",,Kubo Shunman,Japanese,1757,1820,19th century,1800,1820,Polychrome woodblock print (surimono); ink and color on paper,5 5/8 x 7 1/4 in. (14.3 x 18.4 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54553,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1974,false,true,54559,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",(?),Kubo Shunman,Japanese,1757,1820,19th century,1800,1820,Polychrome woodblock print (surimono); ink and color on paper,5 7/16 x 7 1/4 in. (13.8 x 18.4 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54559,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1980,false,true,54565,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",,Kubo Shunman,Japanese,1757,1820,19th century,1800,1899,Polychrome woodblock print (surimono); ink and color on paper,8 1/4 x 7 3/8 in. (21 x 18.7 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54565,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1981,false,true,54679,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",,Kubo Shunman,Japanese,1757,1820,19th century,1800,1899,Polychrome woodblock print (surimono); ink and color on paper,5 1/4 x 7 1/16 in. (13.3 x 17.9 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54679,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1982,false,true,54680,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",,Kubo Shunman,Japanese,1757,1820,19th century,1800,1899,Polychrome woodblock print (surimono); ink and color on paper,5 3/8 x 7 3/16 in. (13.7 x 18.3 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54680,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1983,false,true,54681,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",,Kubo Shunman,Japanese,1757,1820,19th century,1800,1899,Polychrome woodblock print (surimono); ink and color on paper,8 x 7 3/16 in. (20.3 x 18.3 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54681,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1984,false,true,54682,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",,Kubo Shunman,Japanese,1757,1820,19th century,1800,1899,Polychrome woodblock print (surimono); ink and color on paper,8 1/4 x 7 1/4 in. (21 x 18.4 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54682,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1985,false,true,54683,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",,Kubo Shunman,Japanese,1757,1820,19th century,1800,1899,Polychrome woodblock print (surimono); ink and color on paper,8 3/16 x 7 1/8 in. (20.8 x 18.1 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54683,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1986,false,true,54685,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",,Kubo Shunman,Japanese,1757,1820,19th century,1800,1899,Polychrome woodblock print (surimono); ink and color on paper,8 5/16 x 7 5/16 in. (21.1 x 18.6 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54685,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1987,false,true,54689,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",,Kubo Shunman,Japanese,1757,1820,19th century,1800,1899,Polychrome woodblock print (surimono); ink and color on paper,5 9/16 x 7 1/4 in. (14.1 x 18.4 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54689,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1988,false,true,54690,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",,Kubo Shunman,Japanese,1757,1820,19th century,1800,1899,Polychrome woodblock print (surimono); ink and color on paper,5 3/8 x 7 1/4 in. (13.7 x 18.4 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54690,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1989,false,true,54691,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",,Kubo Shunman,Japanese,1757,1820,19th century,1800,1899,Polychrome woodblock print (surimono); ink and color on paper,7 15/16 x 7 3/16 in. (20.2 x 18.3 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54691,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1991,false,true,54697,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",,Kubo Shunman,Japanese,1757,1820,19th century,1800,1899,Polychrome woodblock print (surimono); ink and color on paper,5 1/2 x 7 3/8 in. (14 x 18.7 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54697,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1992,false,true,54702,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",,Kubo Shunman,Japanese,1757,1820,19th century,1800,1899,Polychrome woodblock print (surimono); ink and color on paper,8 x 7 1/8 in. (20.3 x 18.1 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54702,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1993,false,true,54705,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",,Kubo Shunman,Japanese,1757,1820,19th century,1800,1899,Polychrome woodblock print (surimono); ink and color on paper,8 5/16 x 7 3/8 in. (21.1 x 18.7 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54705,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1994,false,true,54713,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",,Kubo Shunman,Japanese,1757,1820,19th century,1800,1899,Polychrome woodblock print (surimono); ink and color on paper,8 1/4 x 7 1/4 in. (21 x 18.4 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54713,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1995,false,true,54715,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",,Kubo Shunman,Japanese,1757,1820,19th century,1800,1899,Polychrome woodblock print (surimono); ink and color on paper,8 1/4 x 7 5/16 in. (21 x 18.6 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54715,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1996,false,true,54716,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",,Kubo Shunman,Japanese,1757,1820,19th century,1800,1899,Polychrome woodblock print (surimono); ink and color on paper,8 5/16 x 7 1/4 in. (21.1 x 18.4 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54716,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1999,false,true,54719,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",,Kubo Shunman,Japanese,1757,1820,19th century,1800,1899,Polychrome woodblock print (surimono); ink and color on paper,8 5/16 x 7 1/4 in. (21.1 x 18.4 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54719,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2000,false,true,54720,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",,Kubo Shunman,Japanese,1757,1820,19th century,1800,1899,Polychrome woodblock print (surimono); ink and color on paper,8 3/8 x 7 1/4 in. (21.3 x 18.4 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54720,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2003,false,true,54723,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",,Kubo Shunman,Japanese,1757,1820,19th century,1800,1899,Polychrome woodblock print (surimono); ink and color on paper,8 1/16 x 7 1/4 in. (20.5 x 18.4 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54723,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2004,false,true,54724,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",,Kubo Shunman,Japanese,1757,1820,19th century,1800,1899,Polychrome woodblock print (surimono); ink and color on paper,8 1/16 x 7 1/4 in. (20.5 x 18.4 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54724,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2005,false,true,54725,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",,Kubo Shunman,Japanese,1757,1820,19th century,1800,1899,Polychrome woodblock print (surimono); ink and color on paper,7 15/16 x 7 3/16 in. (20.2 x 18.3 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54725,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2006,false,true,54726,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",,Kubo Shunman,Japanese,1757,1820,19th century,1800,1899,Polychrome woodblock print (surimono); ink and color on paper,7 15/16 x 7 3/16 in. (20.2 x 18.3 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54726,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2007,false,true,54727,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",,Kubo Shunman,Japanese,1757,1820,19th century,1800,1899,Polychrome woodblock print (surimono); ink and color on paper,8 x 7 1/16 in. (20.3 x 17.9 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54727,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2009,false,true,54729,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",,Kubo Shunman,Japanese,1757,1820,19th century,1800,1899,Polychrome woodblock print (surimono); ink and color on paper,5 7/16 x 7 5/16 in. (13.8 x 18.6 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54729,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2010,false,true,54730,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",,Kubo Shunman,Japanese,1757,1820,19th century,1800,1899,Polychrome woodblock print (surimono); ink and color on paper,8 1/16 x 7 3/16 in. (20.5 x 18.3 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54730,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2013,false,true,54762,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",,Kubo Shunman,Japanese,1757,1820,19th century,1800,1899,Polychrome woodblock print (surimono); ink and color on paper,7 15/16 x 7 1/4 in. (20.2 x 18.4 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54762,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2014,false,true,54763,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",,Kubo Shunman,Japanese,1757,1820,19th century,1800,1899,Polychrome woodblock print (surimono); ink and color on paper,8 x 7 1/4 in. (20.3 x 18.4 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54763,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2016,false,true,54765,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",,Kubo Shunman,Japanese,1757,1820,19th century,1800,1820,Polychrome woodblock print (surimono); ink and color on paper,5 1/4 x 10 13/16 in. (13.3 x 27.5 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54765,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2017,false,true,54766,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",,Kubo Shunman,Japanese,1757,1820,19th century,1800,1899,Polychrome woodblock print (surimono); ink and color on paper,6 x 12 5/8 in. (15.2 x 32.1 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54766,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2020,false,true,54769,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",,Kubo Shunman,Japanese,1757,1820,19th century,1800,1899,Polychrome woodblock print (surimono); ink and color on paper,8 x 7 1/4 in. (20.3 x 18.4 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54769,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2021,false,true,54770,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",,Kubo Shunman,Japanese,1757,1820,19th century,1800,1899,Polychrome woodblock print (surimono); ink and color on paper,8 1/4 x 7 1/4 in. (21 x 18.4 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54770,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2027,false,true,54787,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",,Kubo Shunman,Japanese,1757,1820,19th century,1800,1899,Polychrome woodblock print (surimono); ink and color on paper,8 1/4 x 7 1/4 in. (21 x 18.4 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54787,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2030,false,true,54793,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",(?),Kubo Shunman,Japanese,1757,1820,19th century,1800,1899,Polychrome woodblock print (surimono); ink and color on paper,8 1/4 x 7 1/8 in. (21 x 18.1 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54793,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2031,false,true,54797,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",(?),Kubo Shunman,Japanese,1757,1820,19th century,1800,1899,Polychrome woodblock print (surimono); ink and color on paper,8 1/4 x 7 3/16 in. (21 x 18.3 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54797,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2044,false,true,54815,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",(?),Kubo Shunman,Japanese,1757,1820,19th century,1800,1899,Polychrome woodblock print (surimono); ink and color on paper,5 1/2 x 7 3/8 in. (14 x 18.7 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54815,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2063,false,true,54891,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",(?),Kubo Shunman,Japanese,1757,1820,19th century,1800,1899,Polychrome woodblock print (surimono); ink and color on paper,7 15/16 x 7 1/8 in. (20.2 x 18.1 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54891,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2066,false,true,54895,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",(?),Kubo Shunman,Japanese,1757,1820,19th century,1800,1899,Polychrome woodblock print (surimono); ink and color on paper,8 5/16 x 7 3/8 in. (21.1 x 18.7 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54895,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2067,false,true,54896,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",(?),Kubo Shunman,Japanese,1757,1820,19th century,1800,1899,Polychrome woodblock print (surimono); ink and color on paper,8 1/16 x 7 3/16 in. (20.5 x 18.3 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54896,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2069,false,true,54899,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",(?),Kubo Shunman,Japanese,1757,1820,19th century,1800,1899,Polychrome woodblock print (surimono); ink and color on paper,8 x 7 1/4 in. (20.3 x 18.4 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54899,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2072,false,true,54903,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",(?),Kubo Shunman,Japanese,1757,1820,19th century,1800,1899,Polychrome woodblock print (surimono); ink and color on paper,8 1/8 x 7 1/4 in. (20.6 x 18.4 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54903,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2073,false,true,54904,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",(?),Kubo Shunman,Japanese,1757,1820,19th century,1800,1899,Polychrome woodblock print (surimono); ink and color on paper,8 x 7 1/16 in. (20.3 x 17.9 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54904,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2076,false,true,54907,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",(?),Kubo Shunman,Japanese,1757,1820,19th century,1800,1899,Polychrome woodblock print (surimono); ink and color on paper,8 5/16 x 7 1/4 in. (21.1 x 18.4 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54907,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2077,false,true,54908,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",(?),Kubo Shunman,Japanese,1757,1820,19th century,1800,1899,Polychrome woodblock print (surimono); ink and color on paper,8 9/16 x 7 3/8 in. (21.7 x 18.7 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54908,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2078,false,true,54909,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",(?),Kubo Shunman,Japanese,1757,1820,19th century,1800,1899,Polychrome woodblock print (surimono); ink and color on paper,8 x 7 3/16 in. (20.3 x 18.3 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54909,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2079,false,true,54910,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",(?),Kubo Shunman,Japanese,1757,1820,19th century,1800,1899,Polychrome woodblock print (surimono); ink and color on paper,7 15/16 x 7 1/16 in. (20.2 x 17.9 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54910,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2080,false,true,54916,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",(?),Kubo Shunman,Japanese,1757,1820,19th century,1800,1899,Polychrome woodblock print (surimono); ink and color on paper,8 x 7 1/8 in. (20.3 x 18.1 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54916,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2083,false,true,54920,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",(?),Kubo Shunman,Japanese,1757,1820,19th century,1800,1899,Polychrome woodblock print (surimono); ink and color on paper,8 x 7 3/16 in. (20.3 x 18.3 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54920,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2086,false,true,54923,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",(?),Kubo Shunman,Japanese,1757,1820,19th century,1800,1899,Polychrome woodblock print (surimono); ink and color on paper,8 x 7 3/16 in. (20.3 x 18.3 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54923,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2087,false,true,54924,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",(?),Kubo Shunman,Japanese,1757,1820,19th century,1800,1899,Polychrome woodblock print (surimono); ink and color on paper,7 15/16 x 7 1/4 in. (20.2 x 18.4 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54924,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2092,false,true,54936,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",,Kubo Shunman,Japanese,1757,1820,19th century,1800,1820,Polychrome woodblock print (surimono); ink and color on paper,5 1/2 x 7 7/16 in. (14 x 18.9 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54936,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2096,false,true,54943,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",(?),Kubo Shunman,Japanese,1757,1820,19th century,1800,1820,Polychrome woodblock print (surimono); ink and color on paper,7 7/8 x 7 3/16 in. (20 x 18.3 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54943,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2120,false,true,54973,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",,Kubo Shunman,Japanese,1757,1820,19th century,1800,1899,Polychrome woodblock print (surimono); ink and color on paper,8 1/8 x 7 1/16 in. (20.6 x 17.9 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54973,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2124,false,true,54979,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",(?),Kubo Shunman,Japanese,1757,1820,19th century,1800,1899,Polychrome woodblock print (surimono); ink and color on paper,8 x 4 1/8 in. (20.3 x 10.5 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54979,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2125,false,true,54980,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",(?),Kubo Shunman,Japanese,1757,1820,19th century,1800,1899,Polychrome woodblock print (surimono); ink and color on paper,8 1/8 x 4 3/16 in. (20.6 x 10.6 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54980,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2128,false,true,54983,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",(?),Kubo Shunman,Japanese,1757,1820,19th century,1800,1899,Polychrome woodblock print (surimono); ink and color on paper,7 7/8 x 7 3/16 in. (20 x 18.3 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54983,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2129,false,true,54984,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",(?),Kubo Shunman,Japanese,1757,1820,19th century,1800,1899,Polychrome woodblock print (surimono); ink and color on paper,8 1/4 x 7 1/4 in. (21 x 18.4 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54984,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2134,false,true,54989,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",(?),Kubo Shunman,Japanese,1757,1820,19th century,1800,1899,Polychrome woodblock print (surimono); ink and color on paper,8 3/16 x 7 5/16 in. (20.8 x 18.6 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54989,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2137,false,true,54992,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",(?),Kubo Shunman,Japanese,1757,1820,19th century,1800,1899,Polychrome woodblock print (surimono); ink and color on paper,7 7/8 x 7 1/4 in. (20 x 18.4 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54992,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2138,false,true,54993,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",(?),Kubo Shunman,Japanese,1757,1820,19th century,1800,1899,Polychrome woodblock print (surimono); ink and color on paper,8 5/16 x 7 3/8 in. (21.1 x 18.7 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54993,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2144,false,true,54999,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",(?),Kubo Shunman,Japanese,1757,1820,19th century,1800,1899,Polychrome woodblock print (surimono); ink and color on paper,7 7/8 x 7 in. (20 x 17.8 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54999,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2145,false,true,55003,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",(?),Kubo Shunman,Japanese,1757,1820,19th century,1800,1899,Polychrome woodblock print (surimono); ink and color on paper,5 7/16 x 7 1/8 in. (13.8 x 18.1 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55003,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2149,false,true,55010,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",,Kubo Shunman,Japanese,1757,1820,19th century,1800,1899,Polychrome woodblock print (surimono); ink and color on paper,5 9/16 x 7 7/16 in. (14.1 x 18.9 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55010,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2166,false,true,55073,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",(?),Kubo Shunman,Japanese,1757,1820,19th century,1800,1899,Polychrome woodblock print (surimono); ink and color on paper,8 1/4 x 7 3/8 in. (21 x 18.7 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55073,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2192,false,true,55108,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",(?),Kubo Shunman,Japanese,1757,1820,19th century,1800,1899,Polychrome woodblock print (surimono); ink and color on paper,5 7/16 x 8 1/4 in. (13.8 x 21 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55108,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2200,false,true,53812,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",,Kubo Shunman,Japanese,1757,1820,19th century,1800,1899,Polychrome woodblock print (surimono); ink and color on paper,6 3/16 x 7 3/16 in. (15.7 x 18.3 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/53812,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2236,false,true,54004,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",,Kubo Shunman,Japanese,1757,1820,19th century,1800,1899,Part of an album of woodblock prints (surimono); ink and color on paper,8 3/16 x 7 5/16 in. (20.8 x 18.6 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54004,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2238,false,true,54007,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",(?),Kubo Shunman,Japanese,1757,1820,19th century,1800,1899,Part of an album of woodblock prints (surimono); ink and color on paper,8 1/8 x 5 5/16 in. (20.6 x 13.5 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54007,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2240,false,true,54009,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",,Kubo Shunman,Japanese,1757,1820,19th century,1800,1820,Part of an album of woodblock prints (surimono); ink and color on paper,8 1/8 x 7 3/8 in. (20.6 x 18.7 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54009,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2241,false,true,54010,Asian Art,Album,,Japan,Edo period (1615–1868),,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",(?),Kubo Shunman,Japanese,1757,1820,19th century,1800,1820,Part of an album of woodblock prints (surimono); ink and color on paper,8 1/8 x 5 7/16 in. (20.6 x 13.8 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54010,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2244,false,true,54013,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",(?),Kubo Shunman,Japanese,1757,1820,19th century,1800,1820,Part of an album of woodblock prints (surimono); ink and color on paper,8 3/16 x 5 1/2 in. (20.8 x 14 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54013,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2247,false,true,54016,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",(?),Kubo Shunman,Japanese,1757,1820,19th century,1800,1820,Part of an album of woodblock prints (surimono); ink and color on paper,8 1/4 x 5 7/16 in. (21 x 13.8 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54016,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2250,false,true,54019,Asian Art,Print,Asazuma-bune|Courtesan in Ancient Costume Seated in a Boat,Japan,Edo period (1615–1868),,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",(?),Kubo Shunman,Japanese,1757,1820,19th century,1800,1899,Part of an album of woodblock prints (surimono); ink and color on paper,8 3/16 x 5 5/16 in. (20.8 x 13.5 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54019,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2251,false,true,54020,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",(?),Kubo Shunman,Japanese,1757,1820,19th century,1800,1899,Part of an album of woodblock prints (surimono); ink and color on paper,8 1/8 x 10 9/16 in. (20.6 x 26.8 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54020,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2254,false,true,54023,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",(?),Kubo Shunman,Japanese,1757,1820,19th century,1800,1899,Part of an album of woodblock prints (surimono); ink and color on paper,8 1/8 x 5 5/16 in. (20.6 x 13.5 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54023,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2255,false,true,54024,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",,Kubo Shunman,Japanese,1757,1820,19th century,1800,1899,Part of an album of woodblock prints (surimono); ink and color on paper,7 5/8 x 5 3/16 in. (19.4 x 13.2 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54024,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2376,false,true,54160,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",,Kubo Shunman,Japanese,1757,1820,19th century,1800,1899,Part of an album of woodblock prints (surimono); ink and color on paper,8 1/16 x 7 1/8 in. (20.5 x 18.1 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54160,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2377,false,true,54161,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",,Kubo Shunman,Japanese,1757,1820,19th century,1800,1899,Part of an album of woodblock prints (surimono); ink and color on paper,8 1/16 x 6 15/16 in. (20.5 x 17.6 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54161,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2378,false,true,54162,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",,Kubo Shunman,Japanese,1757,1820,19th century,1800,1899,Part of an album of woodblock prints (surimono); ink and color on paper,8 x 7 1/8 in. (20.3 x 18.1 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54162,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2379,false,true,54163,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",,Kubo Shunman,Japanese,1757,1820,19th century,1800,1899,Part of an album of woodblock prints (surimono); ink and color on paper,8 1/16 x 7 3/16 in. (20.5 x 18.3 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54163,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2217,false,true,53983,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Nagayama Koin (Hirotora),"Japanese, 1765–1849",,Nagayama Koin (Hirotora),Japanese,1765,1849,19th century,1800,1899,Polychrome woodblock print (surimono); ink and color on paper,8 1/2 x 7 3/16 in. (21.6 x 18.3 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/53983,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2215,false,true,53981,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Toyokuni I,"Japanese, 1769–1825",,Utagawa Toyokuni I,Japanese,1769,1825,19th century,1800,1899,Polychrome woodblock print (surimono); ink and color on paper,8 1/8 x 4 1/2 in. (20.6 x 11.4 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/53981,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2397,false,true,54177,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Teisai Hokuba,"Japanese, 1771–1844",,Teisai Hokuba,Japanese,1771,1844,19th century,1800,1899,Polychrome woodblock print (surimono); ink and color on paper,7 3/8 x 10 1/4 in. (18.7 x 26 cm),"H. O. Havemeyer Collection, Gift of Mrs. J. Watson Webb, 1930",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54177,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1955,false,true,54525,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Totoya Hokkei,"Japanese, 1780–1850",,Totoya Hokkei,Japanese,1780,1850,19th century,1800,1899,Polychrome woodblock print (surimono); ink and color on paper,5 7/16 x 5 5/8 in. (13.8 x 14.3 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54525,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2012,false,true,54761,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Totoya Hokkei,"Japanese, 1780–1850",,Totoya Hokkei,Japanese,1780,1850,19th century,1800,1899,Polychrome woodblock print (surimono); ink and color on paper,5 9/16 x 7 1/8 in. (14.1 x 18.1 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54761,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2022,false,true,54771,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Totoya Hokkei,"Japanese, 1780–1850",,Totoya Hokkei,Japanese,1780,1850,19th century,1800,1899,Polychrome woodblock print (surimono); ink and color on paper,8 3/16 x 7 3/8 in. (20.8 x 18.7 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54771,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2041,false,true,54812,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Totoya Hokkei,"Japanese, 1780–1850",(?),Totoya Hokkei,Japanese,1780,1850,19th century,1800,1899,Polychrome woodblock print (surimono); ink and color on paper,8 x 7 in. (20.3 x 17.8 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54812,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2045,false,true,54816,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Totoya Hokkei,"Japanese, 1780–1850",,Totoya Hokkei,Japanese,1780,1850,19th century,1800,1899,Polychrome woodblock print (surimono); ink and color on paper,7 15/16 x 7 1/4 in. (20.2 x 18.4 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54816,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2081,false,true,54918,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Totoya Hokkei,"Japanese, 1780–1850",,Totoya Hokkei,Japanese,1780,1850,19th century,1800,1899,Polychrome woodblock print (surimono); ink and color on paper,5 3/4 x 7 7/16 in. (14.6 x 18.9 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54918,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2093,false,true,54937,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Totoya Hokkei,"Japanese, 1780–1850",,Totoya Hokkei,Japanese,1780,1850,19th century,1800,1850,Polychrome woodblock print (surimono); ink and color on paper,7 1/16 x 6 5/8 in. (17.9 x 16.8 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54937,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2112,false,true,54964,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Totoya Hokkei,"Japanese, 1780–1850",,Totoya Hokkei,Japanese,1780,1850,19th century,1800,1899,Polychrome woodblock print (surimono); ink and color on paper,7 3/4 x 6 9/16 in. (19.7 x 16.7 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54964,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2117,false,true,54970,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Totoya Hokkei,"Japanese, 1780–1850",,Totoya Hokkei,Japanese,1780,1850,19th century,1800,1899,Polychrome woodblock print (surimono); ink and color on paper,8 1/8 x 7 1/8 in. (20.6 x 18.1 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54970,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2121,false,true,54975,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Totoya Hokkei,"Japanese, 1780–1850",,Totoya Hokkei,Japanese,1780,1850,19th century,1800,1899,Polychrome woodblock print (surimono); ink and color on paper,8 1/8 x 7 1/16 in. (20.6 x 17.9 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54975,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2160,false,true,55067,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Totoya Hokkei,"Japanese, 1780–1850",,Totoya Hokkei,Japanese,1780,1850,19th century,1800,1899,Polychrome woodblock print (surimono); ink and color on paper,7 7/8 x 6 7/8 in. (20 x 17.5 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55067,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2201,false,true,53967,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Totoya Hokkei,"Japanese, 1780–1850",,Totoya Hokkei,Japanese,1780,1850,19th century,1800,1899,Polychrome woodblock print (surimono); ink and color on paper,3 1/2 x 11 in. (8.9 x 27.9 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/53967,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2213,false,true,53979,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Totoya Hokkei,"Japanese, 1780–1850",,Totoya Hokkei,Japanese,1780,1850,19th century,1800,1899,Polychrome woodblock print (surimono); ink and color on paper,2 3/4 x 9 1/2 in. (7.0 x 24.1 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/53979,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2235,false,true,54003,Asian Art,Print,Ushi-no-toki mairi|Woman in the Rain at Midnight Driving a Nail into a Tree to Invoke Evil on Her Unfaithful Lover,Japan,Edo period (1615–1868),,,,Artist,,Totoya Hokkei,"Japanese, 1780–1850",,Totoya Hokkei,Japanese,1780,1850,19th century,1800,1899,Part of an album of woodblock prints (surimono); ink and color on paper,8 3/16 x 5 3/8 in. (20.8 x 13.7 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54003,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2248,false,true,54017,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Totoya Hokkei,"Japanese, 1780–1850",,Totoya Hokkei,Japanese,1780,1850,19th century,1800,1850,Part of an album of woodblock prints (surimono); ink and color on paper,7 15/16 x 7 1/4 in. (20.2 x 18.4 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54017,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2259,false,true,54029,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Totoya Hokkei,"Japanese, 1780–1850",,Totoya Hokkei,Japanese,1780,1850,19th century,1800,1899,Part of an album of woodblock prints (surimono); ink and color on paper,7 9/16 x 10 5/16 in. (19.2 x 26.2 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54029,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2381,false,true,54165,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Totoya Hokkei,"Japanese, 1780–1850",,Totoya Hokkei,Japanese,1780,1850,19th century,1800,1899,Part of an album of woodblock prints (surimono); ink and color on paper,7 1/8 x 6 11/16 in. (18.1 x 17 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54165,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2382,false,true,54166,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Hokucho Joren,"Japanese, 1780–1850",,Hokucho Joren,Japanese,1780,1850,19th century,1800,1899,Part of an album of woodblock prints (surimono); ink and color on paper,7 15/16 x 7 1/16 in. (20.2 x 17.9 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54166,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2383,false,true,54167,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Totoya Hokkei,"Japanese, 1780–1850",,Totoya Hokkei,Japanese,1780,1850,19th century,1800,1899,Part of an album of woodblock prints (surimono); ink and color on paper,8 1/8 x 7 1/4 in. (20.6 x 18.4 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54167,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2384,false,true,54168,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Totoya Hokkei,"Japanese, 1780–1850",,Totoya Hokkei,Japanese,1780,1850,19th century,1800,1899,Part of an album of woodblock prints (surimono); ink and color on paper,8 1/8 x 7 1/4 in. (20.6 x 18.4 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54168,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2385,false,true,54169,Asian Art,Album,,Japan,Edo period (1615–1868),,,,Artist,,Totoya Hokkei,"Japanese, 1780–1850",,Totoya Hokkei,Japanese,1780,1850,19th century,1800,1899,Part of an album of woodblock prints (surimono); ink and color on paper,8 1/8 x 7 1/8 in. (20.6 x 18.1 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54169,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2386,false,true,54170,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Totoya Hokkei,"Japanese, 1780–1850",,Totoya Hokkei,Japanese,1780,1850,19th century,1800,1899,Part of an album of woodblock prints (surimono); ink and color on paper,7 15/16 x 7 1/8 in. (20.2 x 18.1 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54170,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2387,false,true,54171,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Totoya Hokkei,"Japanese, 1780–1850",,Totoya Hokkei,Japanese,1780,1850,19th century,1800,1899,Part of an album of woodblock prints (surimono); ink and color on paper,8 x 7 3/16 in. (20.3 x 18.3 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54171,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP209,false,true,36686,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,19th century,1800,1865,Album of 48 polychrome woodblock prints; ink and color on paper,14 3/4 × 10 1/4 × 1/2 in. (37.5 × 26 × 1.3 cm),"Gift of the Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/36686,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1114,false,true,55036,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,19th century,1800,1865,Album of 18 polychrome woodblock prints; ink and color on paper,91 in. × 10 3/8 in. × 3/8 in. (231.1 × 26.4 × 1 cm),"Gift of Harold de Raasloff, 1918",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55036,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2864,false,true,51087,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,19th century,1800,1899,Polychrome woodblock print; ink and color on paper,H. 9 3/8 in. (23.8 cm); W. 14 3/8 in. (36.5 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/51087,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP212.2,false,true,57987,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,19th century,1800,1865,Polychrome woodblock print; ink and color on paper,Image: 13 5/8 × 9 1/2 in. (34.6 × 24.1 cm),"Gift of the Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/57987,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP212.3,false,true,57988,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,19th century,1800,1865,Polychrome woodblock print; ink and color on paper,Image: 13 5/8 × 9 5/8 in. (34.6 × 24.4 cm),"Gift of the Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/57988,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP212.4,false,true,57989,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,19th century,1800,1865,Polychrome woodblock print; ink and color on paper,Image: 13 5/8 × 9 5/8 in. (34.6 × 24.4 cm),"Gift of the Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/57989,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP212.6,false,true,57991,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,19th century,1800,1865,Polychrome woodblock print; ink and color on paper,Image: 13 5/8 × 9 5/8 in. (34.6 × 24.4 cm),"Gift of the Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/57991,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP212.7,false,true,57992,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,19th century,1800,1865,Polychrome woodblock print; ink and color on paper,Image: 13 5/8 × 9 5/8 in. (34.6 × 24.4 cm),"Gift of the Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/57992,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP212.8,false,true,57993,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,19th century,1800,1865,Polychrome woodblock print; ink and color on paper,Image: 13 5/8 × 9 5/8 in. (34.6 × 24.4 cm),"Gift of the Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/57993,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP212.9,false,true,57994,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,19th century,1800,1865,Polychrome woodblock print; ink and color on paper,Image: 13 5/8 × 9 5/8 in. (34.6 × 24.4 cm),"Gift of the Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/57994,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP212.10,false,true,57995,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,19th century,1800,1865,Polychrome woodblock print; ink and color on paper,Image: 13 5/8 × 9 5/8 in. (34.6 × 24.4 cm),"Gift of the Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/57995,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP212.11,false,true,57996,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,19th century,1800,1865,Polychrome woodblock print; ink and color on paper,Image: 13 5/8 × 9 1/2 in. (34.6 × 24.1 cm),"Gift of the Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/57996,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP212.12,false,true,57997,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,19th century,1800,1865,Polychrome woodblock print; ink and color on paper,Image: 13 5/8 × 9 5/8 in. (34.6 × 24.4 cm),"Gift of the Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/57997,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP212.13,false,true,57998,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,19th century,1800,1865,Polychrome woodblock print; ink and color on paper,Image: 13 5/8 × 9 1/8 in. (34.6 × 23.2 cm),"Gift of the Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/57998,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP212.14,false,true,57999,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,19th century,1800,1865,Polychrome woodblock print; ink and color on paper,Image: 13 5/8 × 9 5/8 in. (34.6 × 24.4 cm),"Gift of the Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/57999,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP212.19,false,true,58004,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,19th century,1800,1865,Polychrome woodblock print; ink and color on paper,Image: 13 5/8 × 9 5/8 in. (34.6 × 24.4 cm),"Gift of the Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/58004,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP212.20,false,true,58005,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,19th century,1800,1865,Polychrome woodblock print; ink and color on paper,Image: 13 5/8 × 9 5/8 in. (34.6 × 24.4 cm),"Gift of the Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/58005,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP212.21,false,true,58006,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,19th century,1800,1865,Polychrome woodblock print; ink and color on paper,Image: 13 5/8 × 9 5/8 in. (34.6 × 24.4 cm),"Gift of the Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/58006,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP212.24,false,true,58009,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,19th century,1800,1865,Polychrome woodblock print; ink and color on paper,Image: 13 5/8 × 9 5/8 in. (34.6 × 24.4 cm),"Gift of the Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/58009,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP212.26,false,true,58010,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,19th century,1800,1865,Polychrome woodblock print; ink and color on paper,Image: 13 3/8 × 8 3/4 in. (34 × 22.2 cm),"Gift of the Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/58010,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP212.27,false,true,58011,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,19th century,1800,1865,Polychrome woodblock print; ink and color on paper,Image: 13 1/2 × 9 5/8 in. (34.3 × 24.4 cm),"Gift of the Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/58011,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP212.28,false,true,58012,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,19th century,1800,1865,Polychrome woodblock print; ink and color on paper,Image: 13 1/2 × 9 5/8 in. (34.3 × 24.4 cm),"Gift of the Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/58012,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP212.29,false,true,58013,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,19th century,1800,1865,Polychrome woodblock print; ink and color on paper,Image: 13 1/2 × 9 1/2 in. (34.3 × 24.1 cm),"Gift of the Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/58013,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP212.31,false,true,58015,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,19th century,1800,1865,Polychrome woodblock print; ink and color on paper,Image: 13 5/8 × 9 5/8 in. (34.6 × 24.4 cm),"Gift of the Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/58015,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP212.32,false,true,58016,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,19th century,1800,1865,Polychrome woodblock print; ink and color on paper,Image: 13 5/8 × 9 5/8 in. (34.6 × 24.4 cm),"Gift of the Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/58016,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP212.33,false,true,58017,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,19th century,1800,1865,Polychrome woodblock print; ink and color on paper,Image: 13 5/8 × 9 1/2 in. (34.6 × 24.1 cm),"Gift of the Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/58017,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP212.34,false,true,58018,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,19th century,1800,1865,Polychrome woodblock print; ink and color on paper,Image: 13 5/8 × 9 1/2 in. (34.6 × 24.1 cm),"Gift of the Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/58018,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP212.35,false,true,58019,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,19th century,1800,1865,Polychrome woodblock print; ink and color on paper,Image: 13 5/8 × 9 1/2 in. (34.6 × 24.1 cm),"Gift of the Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/58019,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP212.36,false,true,58020,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,19th century,1800,1865,Polychrome woodblock print; ink and color on paper,Image: 13 5/8 × 9 1/2 in. (34.6 × 24.1 cm),"Gift of the Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/58020,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP212.37,false,true,58021,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,19th century,1800,1865,Polychrome woodblock print; ink and color on paper,Image: 13 5/8 × 9 5/8 in. (34.6 × 24.4 cm),"Gift of the Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/58021,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP212.39,false,true,58023,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,19th century,1800,1865,Polychrome woodblock print; ink and color on paper,Image: 13 5/8 × 9 1/2 in. (34.6 × 24.1 cm),"Gift of the Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/58023,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP212.41,false,true,58025,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,19th century,1800,1865,Polychrome woodblock print; ink and color on paper,Image: 13 5/8 × 9 5/8 in. (34.6 × 24.4 cm),"Gift of the Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/58025,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP212.43,false,true,58027,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,19th century,1800,1865,Polychrome woodblock print; ink and color on paper,Image: 13 5/8 × 9 5/8 in. (34.6 × 24.4 cm),"Gift of the Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/58027,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP212.44,false,true,58028,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,19th century,1800,1865,Polychrome woodblock print; ink and color on paper,Image: 13 5/8 × 9 5/8 in. (34.6 × 24.4 cm),"Gift of the Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/58028,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP212.47,false,true,58031,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,19th century,1800,1865,Polychrome woodblock print; ink and color on paper,Image: 13 5/8 × 9 5/8 in. (34.6 × 24.4 cm),"Gift of the Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/58031,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP212.48,false,true,58032,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,19th century,1800,1865,Polychrome woodblock print; ink and color on paper,Image: 13 5/8 × 9 5/8 in. (34.6 × 24.4 cm),"Gift of the Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/58032,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP212.50,false,true,58034,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,19th century,1800,1865,Polychrome woodblock print; ink and color on paper,Image: 13 5/8 × 9 1/2 in. (34.6 × 24.1 cm),"Gift of the Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/58034,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP212.55,false,true,58039,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,19th century,1800,1865,Polychrome woodblock print; ink and color on paper,Image: 13 5/8 × 9 5/8 in. (34.6 × 24.4 cm),"Gift of the Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/58039,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP212.56,false,true,58040,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,19th century,1800,1865,Polychrome woodblock print; ink and color on paper,Image: 13 5/8 × 9 5/8 in. (34.6 × 24.4 cm),"Gift of the Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/58040,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP212.57,false,true,58041,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,19th century,1800,1865,Polychrome woodblock print; ink and color on paper,Image: 13 5/8 × 9 5/8 in. (34.6 × 24.4 cm),"Gift of the Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/58041,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP212.60,false,true,58044,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,19th century,1800,1865,Polychrome woodblock print; ink and color on paper,Image: 13 5/8 × 9 5/8 in. (34.6 × 24.4 cm),"Gift of the Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/58044,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP212.61,false,true,58045,Asian Art,Print,相模屋亭主|Actor as Master of Sagamiya (Sagamiya teishu),Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,19th century,1800,1865,Panel from a triptych of polychrome woodblock prints; ink and color on paper,Image: 13 5/8 × 9 1/2 in. (34.6 × 24.1 cm),"Gift of the Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/58045,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP212.62,false,true,58046,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,19th century,1800,1865,Polychrome woodblock print; ink and color on paper,Image: 13 5/8 × 9 5/8 in. (34.6 × 24.4 cm),"Gift of the Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/58046,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP212.63,false,true,58047,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,19th century,1800,1865,Polychrome woodblock print; ink and color on paper,Image: 13 5/8 × 9 5/8 in. (34.6 × 24.4 cm),"Gift of the Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/58047,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP212.64,false,true,58048,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,19th century,1800,1865,Polychrome woodblock print; ink and color on paper,Image: 13 5/8 × 9 5/8 in. (34.6 × 24.4 cm),"Gift of the Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/58048,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP212.65,false,true,58049,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,19th century,1800,1865,Polychrome woodblock print; ink and color on paper,Image: 13 5/8 × 9 5/8 in. (34.6 × 24.4 cm),"Gift of the Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/58049,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP212.66,false,true,58050,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,19th century,1800,1865,Polychrome woodblock print; ink and color on paper,Image: 13 5/8 × 9 5/8 in. (34.6 × 24.4 cm),"Gift of the Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/58050,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP212.67,false,true,58051,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,19th century,1800,1865,Polychrome woodblock print; ink and color on paper,Image: 13 5/8 × 9 5/8 in. (34.6 × 24.4 cm),"Gift of the Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/58051,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP212.68,false,true,58052,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,19th century,1800,1865,Polychrome woodblock print; ink and color on paper,Image: 13 5/8 × 9 5/8 in. (34.6 × 24.4 cm),"Gift of the Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/58052,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP212.69,false,true,58053,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,19th century,1800,1865,Polychrome woodblock print; ink and color on paper,Image: 13 3/4 × 9 1/2 in. (34.9 × 24.1 cm),"Gift of the Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/58053,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP212.70,false,true,58054,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,19th century,1800,1865,Polychrome woodblock print; ink and color on paper,Image: 13 3/4 × 9 5/8 in. (34.9 × 24.4 cm),"Gift of the Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/58054,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP212.71,false,true,58058,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,19th century,1800,1865,Polychrome woodblock print; ink and color on paper,Image: 13 1/2 × 9 1/2 in. (34.3 × 24.1 cm),"Gift of the Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/58058,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP212.72,false,true,58059,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,19th century,1800,1865,Polychrome woodblock print; ink and color on paper,Image: 13 5/8 × 9 1/2 in. (34.6 × 24.1 cm),"Gift of the Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/58059,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP212.73,false,true,58060,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,19th century,1800,1865,Polychrome woodblock print; ink and color on paper,Image: 13 5/8 × 9 5/8 in. (34.6 × 24.4 cm),"Gift of the Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/58060,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP212.74,false,true,58061,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,19th century,1800,1865,Polychrome woodblock print; ink and color on paper,Image: 13 5/8 × 9 5/8 in. (34.6 × 24.4 cm),"Gift of the Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/58061,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP212.75,false,true,58062,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,19th century,1800,1865,Polychrome woodblock print; ink and color on paper,Image: 13 3/4 × 9 5/8 in. (34.9 × 24.4 cm),"Gift of the Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/58062,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP212.76,false,true,58063,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,19th century,1800,1865,Polychrome woodblock print; ink and color on paper,Image: 13 3/4 × 9 3/4 in. (34.9 × 24.8 cm),"Gift of the Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/58063,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP212.77,false,true,58064,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,19th century,1800,1865,Polychrome woodblock print; ink and color on paper,Image: 13 5/8 × 9 5/8 in. (34.6 × 24.4 cm),"Gift of the Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/58064,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP212.78,false,true,58065,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,19th century,1800,1865,Polychrome woodblock print; ink and color on paper,Image: 13 3/4 × 9 5/8 in. (34.9 × 24.4 cm),"Gift of the Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/58065,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP212.79,false,true,58066,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,19th century,1800,1865,Polychrome woodblock print; ink and color on paper,Image: 13 5/8 × 9 5/8 in. (34.6 × 24.4 cm),"Gift of the Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/58066,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP212.80,false,true,58067,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,19th century,1800,1865,Polychrome woodblock print; ink and color on paper,Image: 13 5/8 × 9 5/8 in. (34.6 × 24.4 cm),"Gift of the Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/58067,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP212.81,false,true,58068,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,19th century,1800,1865,Polychrome woodblock print; ink and color on paper,Image: 13 5/8 × 9 5/8 in. (34.6 × 24.4 cm),"Gift of the Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/58068,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP212.82,false,true,58069,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,19th century,1800,1865,Polychrome woodblock print; ink and color on paper,Image: 13 5/8 × 9 5/8 in. (34.6 × 24.4 cm),"Gift of the Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/58069,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP212.83,false,true,58070,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,19th century,1800,1865,Polychrome woodblock print; ink and color on paper,Image: 13 3/4 × 9 5/8 in. (34.9 × 24.4 cm),"Gift of the Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/58070,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP212.84,false,true,58071,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,19th century,1800,1865,Polychrome woodblock print; ink and color on paper,Image: 13 3/4 × 9 5/8 in. (34.9 × 24.4 cm),"Gift of the Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/58071,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP212.86,false,true,58073,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,19th century,1800,1865,Polychrome woodblock print; ink and color on paper,Image: 13 3/4 × 9 1/2 in. (34.9 × 24.1 cm),"Gift of the Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/58073,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP212.88,false,true,58076,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,19th century,1800,1865,Polychrome woodblock print; ink and color on paper,Image: 13 3/4 × 9 5/8 in. (34.9 × 24.4 cm),"Gift of the Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/58076,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP212.89,false,true,58077,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,19th century,1800,1865,Polychrome woodblock print; ink and color on paper,Image: 13 3/4 × 9 1/2 in. (34.9 × 24.1 cm),"Gift of the Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/58077,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.267,false,true,73558,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,19th century,1786,1865,Polychrome woodblock print; ink and color on paper,Image: 11 3/8 x 8 3/4 in. (28.9 x 22.2 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73558,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.268,false,true,73608,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,19th century,1786,1865,Polychrome woodblock print; ink and color on paper,Image: 11 7/8 x 9 3/8 in. (30.2 x 23.8 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73608,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.269,false,true,73609,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,19th century,1786,1865,Polychrome woodblock print; ink and color on paper,Image: 14 x 9 3/4 in. (35.6 x 24.8 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73609,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.270,false,true,73610,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,19th century,1800,1865,Polychrome woodblock print; ink and color on paper,Image: 14 5/8 x 9 7/8 in. (37.1 x 25.1 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73610,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.272,false,true,73612,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,19th century,1800,1865,Polychrome woodblock print; ink and color on paper,Image: 14 3/16 x 9 3/4 in. (36 x 24.8 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73612,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.273,false,true,73613,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,19th century,1800,1865,Polychrome woodblock print; ink and color on paper,Image: 14 1/8 x 9 1/2 in. (35.9 x 24.1 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73613,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.274,false,true,73614,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,19th century,1800,1865,Polychrome woodblock print; ink and color on paper,Image: 14 3/8 x 9 7/8 in. (36.5 x 25.1 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73614,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.275,false,true,73615,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,19th century,1800,1865,Polychrome woodblock print; ink and color on paper,Image: 14 1/8 x 9 1/2 in. (35.9 x 24.1 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73615,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3495a–c,false,true,55723,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kunisada,"Japanese, 1786–1865",,Utagawa Kunisada,Japanese,1786,1865,19th century,1800,1865,Triptych of polychrome woodblock prints; ink and color on paper,Each 10 x 14 1/2 in. (25.4 x 36.8 cm),"Gift of Cole J. Younger, 1975",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55723,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2202,false,true,53968,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Yanagawa Shigenobu,"Japanese, 1787–1832",,Yanagawa Shigenobu,Japanese,1787,1832,19th century,1800,1899,Polychrome woodblock print (surimono); ink and color on paper,8 1/4 x 11 1/16 in. (21 x 28.1 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/53968,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2085,false,true,54922,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Kikugawa Eizan,"Japanese, 1787–1867",,Kikugawa Eizan,Japanese,1787,1867,19th century,1800,1899,Polychrome woodblock print (surimono); ink and color on paper,5 5/8 x 7 7/16 in. (14.3 x 18.9 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54922,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2133,false,true,54988,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Yosai,"Japanese, 1788–1878",,Yosai,Japanese,1788,1878,19th century,1800,1899,Polychrome woodblock print (surimono); ink and color on paper,5 3/4 x 7 5/8 in. (14.6 x 19.4 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54988,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1963,false,true,54544,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Keisai Eisen,"Japanese, 1790–1848",,Keisai Eisen,Japanese,1790,1848,19th century,1800,1899,Polychrome woodblock print (surimono); ink and color on paper,7 11/16 x 7 1/8 in. (19.5 x 18.1 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54544,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3434,false,true,55634,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Keisai Eisen,"Japanese, 1790–1848",,Keisai Eisen,Japanese,1790,1848,19th century,1800,1899,Polychrome woodblock print; ink and color on paper,14 x 10 in. (35.6 x 25.4 cm),"Gift of Cole J. Younger, 1975",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55634,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1041,false,true,54326,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Watanabe Kazan,"Japanese, 1793–1841",,Watanabe Kazan,Japanese,1793,1841,19th century,1800,1841,Polychrome woodblock print (surimono); ink and color on paper,8 1/4 x 7 1/8 in. (21 x 18.1 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54326,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2750,false,true,57016,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kuniyoshi,"Japanese, 1797–1861",,Utagawa Kuniyoshi,Japanese,1797,1861,19th century,1800,1899,Woodblock print; ink on thin paper (some corrections have been pasted over the drawing),13 3/4 x 8 1/2 in. (34.9 x 21.6 cm),"Fletcher Fund, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/57016,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3706,false,true,55956,Asian Art,Print,木曽街道六十九次之内・下諏訪 八重垣姫|Princess Yaegaki,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kuniyoshi,"Japanese, 1797–1861",,Utagawa Kuniyoshi,Japanese,1797,1861,19th century,1800,1899,Polychrome woodblock print; ink and color on paper,14 1/2 x 10 in. (36.8 x 25.4 cm),"Bequest of Grace M. Pugh, 1985",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55956,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP212.22,false,true,58007,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kuniyoshi,"Japanese, 1797–1861",,Utagawa Kuniyoshi,Japanese,1797,1861,19th century,1800,1861,Polychrome woodblock print; ink and color on paper,Image: 13 5/8 × 9 5/8 in. (34.6 × 24.4 cm),"Gift of the Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/58007,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP212.23,false,true,58008,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kuniyoshi,"Japanese, 1797–1861",,Utagawa Kuniyoshi,Japanese,1797,1861,19th century,1800,1861,Polychrome woodblock print; ink and color on paper,Image: 13 5/8 × 9 5/8 in. (34.6 × 24.4 cm),"Gift of the Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/58008,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP212.30,false,true,58014,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kuniyoshi,"Japanese, 1797–1861",,Utagawa Kuniyoshi,Japanese,1797,1861,19th century,1800,1861,Polychrome woodblock print; ink and color on paper,Image: 13 1/2 × 9 1/2 in. (34.3 × 24.1 cm),"Gift of the Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/58014,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP212.38,false,true,58022,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kuniyoshi,"Japanese, 1797–1861",,Utagawa Kuniyoshi,Japanese,1797,1861,19th century,1800,1861,Polychrome woodblock print; ink and color on paper,Image: 13 5/8 × 9 5/8 in. (34.6 × 24.4 cm),"Gift of the Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/58022,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP212.40,false,true,58024,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kuniyoshi,"Japanese, 1797–1861",,Utagawa Kuniyoshi,Japanese,1797,1861,19th century,1800,1865,Polychrome woodblock print; ink and color on paper,Image: 13 5/8 × 9 1/2 in. (34.6 × 24.1 cm),"Gift of the Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/58024,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP212.42,false,true,58026,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kuniyoshi,"Japanese, 1797–1861",,Utagawa Kuniyoshi,Japanese,1797,1861,19th century,1800,1861,Polychrome woodblock print; ink and color on paper,Image: 13 5/8 × 9 5/8 in. (34.6 × 24.4 cm),"Gift of the Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/58026,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP212.51,false,true,58035,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kuniyoshi,"Japanese, 1797–1861",,Utagawa Kuniyoshi,Japanese,1797,1861,19th century,1800,1861,Polychrome woodblock print; ink and color on paper,Image: 13 5/8 × 9 1/2 in. (34.6 × 24.1 cm),"Gift of the Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/58035,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP212.85,false,true,58072,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kuniyoshi,"Japanese, 1797–1861",,Utagawa Kuniyoshi,Japanese,1797,1861,19th century,1800,1861,Polychrome woodblock print; ink and color on paper,Image: 13 3/4 × 9 1/2 in. (34.9 × 24.1 cm),"Gift of the Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/58072,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP212.87,false,true,58074,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kuniyoshi,"Japanese, 1797–1861",,Utagawa Kuniyoshi,Japanese,1797,1861,19th century,1800,1861,Polychrome woodblock print; ink and color on paper,Image: 13 3/4 × 9 5/8 in. (34.9 × 24.4 cm),"Gift of the Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/58074,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1806a–e,false,true,45235,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kuniyoshi,"Japanese, 1797–1861",,Utagawa Kuniyoshi,Japanese,1797,1861,19th century,1800,1868,Pentaptych of polychrome woodblock prints; ink and color on paper,14 5/8 x 50 in. (37.1 x 127 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45235,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1993.309,false,true,55987,Asian Art,Print,江戸名所 芝神明|Famous Places of Edo: Shiba Shinmei,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige II,"Japanese, 1829–1869",,Utagawa Hiroshige II,Japanese,1829,1869,19th century,1829,1868,Polychrome woodblock print; ink and color on paper,8 1/2 x 13 3/8 in. (21.6 x 34 cm),"Bequest of Gustave von Groschwitz, 1993",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55987,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.279,false,true,73562,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige II,"Japanese, 1829–1869",,Utagawa Hiroshige II,Japanese,1829,1869,19th century,1829,1869,Polychrome woodblock print; ink and color on paper,Image: 8 3/8 x 11 1/4 in. (21.3 x 28.6 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73562,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.320a–c,false,true,73578,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Yoshiiku,"Japanese, 1833–1904",,Utagawa Yoshiiku,Japanese,1833,1904,19th century,1833,1868,Triptych of polychrome woodblock prints; ink and color on paper,Image (a): 14 1/4 x 9 3/4 in. (36.2 x 24.8 cm) Image (b): 14 1/2 x 9 1/2 in. (36.8 x 24.1 cm) Image (c): 14 1/4 x 9 7/8 in. (36.2 x 25.1 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73578,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1453,false,true,54394,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Yashima Gakutei,"Japanese, 1786?–1868",,Yashima Gakutei,Japanese,1786,1868,19th century,1800,1899,Polychrome woodblock print (surimono); ink and color on paper,8 1/4 x 7 1/2 in. (21 x 19.1 cm),"Rogers Fund, 1923",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54394,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1934,false,true,54474,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Yashima Gakutei,"Japanese, 1786?–1868",,Yashima Gakutei,Japanese,1786,1868,19th century,1800,1868,Polychrome woodblock print (surimono); ink and color on paper,8 3/16 x 7 3/8 in. (20.8 x 18.7 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54474,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1964,false,true,54545,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Yashima Gakutei,"Japanese, 1786?–1868",,Yashima Gakutei,Japanese,1786,1868,19th century,1800,1899,Polychrome woodblock print (surimono); ink and color on paper,8 1/4 x 7 3/8 in. (21 x 18.7 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54545,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1965,false,true,54547,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Yashima Gakutei,"Japanese, 1786?–1868",,Yashima Gakutei,Japanese,1786,1868,19th century,1800,1899,Polychrome woodblock print (surimono); ink and color on paper,8 5/16 x 7 3/8 in. (21.1 x 18.7 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54547,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1966,false,true,54548,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Yashima Gakutei,"Japanese, 1786?–1868",,Yashima Gakutei,Japanese,1786,1868,19th century,1800,1899,Polychrome woodblock print (surimono); ink and color on paper,8 5/16 x 7 3/8 in. (21.1 x 18.7 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54548,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1967,false,true,54549,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Yashima Gakutei,"Japanese, 1786?–1868",,Yashima Gakutei,Japanese,1786,1868,19th century,1800,1899,Polychrome woodblock print (surimono); ink and color on paper,8 5/16 x 7 3/8 in. (21.1 x 18.7 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54549,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1972,false,true,54557,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Yashima Gakutei,"Japanese, 1786?–1868",,Yashima Gakutei,Japanese,1786,1868,19th century,1800,1899,Polychrome woodblock print (surimono); ink and color on paper,7 3/4 x 6 1/2 in. (19.7 x 16.5 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54557,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1978,false,true,54563,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Yashima Gakutei,"Japanese, 1786?–1868",,Yashima Gakutei,Japanese,1786,1868,19th century,1800,1868,Polychrome woodblock print (surimono); ink and color on paper,8 1/8 x 7 3/16 in. (20.6 x 18.3 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54563,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1979,false,true,54564,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Yashima Gakutei,"Japanese, 1786?–1868",,Yashima Gakutei,Japanese,1786,1868,19th century,1800,1868,Polychrome woodblock print (surimono); ink and color on paper,8 3/16 x 7 1/4 in. (20.8 x 18.4 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54564,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2042,false,true,54813,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Yashima Gakutei,"Japanese, 1786?–1868",,Yashima Gakutei,Japanese,1786,1868,19th century,1800,1899,Polychrome woodblock print (surimono); ink and color on paper,7 3/16 x 6 1/2 in. (18.3 x 16.5 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54813,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2061,false,true,54842,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Yashima Gakutei,"Japanese, 1786?–1868",,Yashima Gakutei,Japanese,1786,1868,19th century,1800,1899,Polychrome woodblock print (surimono); ink and color on paper,7 7/8 x 6 13/16 in. (20 x 17.3 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54842,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2062,false,true,54843,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Yashima Gakutei,"Japanese, 1786?–1868",,Yashima Gakutei,Japanese,1786,1868,19th century,1800,1899,Polychrome woodblock print (surimono); ink and color on paper,7 15/16 x 6 3/4 in. (20.2 x 17.1 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54843,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2088,false,true,54929,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Yashima Gakutei,"Japanese, 1786?–1868",,Yashima Gakutei,Japanese,1786,1868,19th century,1800,1899,Polychrome woodblock print (surimono); ink and color on paper,8 3/16 x 7 3/8 in. (20.8 x 18.7 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54929,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2089,false,true,54932,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Yashima Gakutei,"Japanese, 1786?–1868",,Yashima Gakutei,Japanese,1786,1868,19th century,1800,1899,Polychrome woodblock print (surimono); ink and color on paper,8 3/8 x 7 7/16 in. (21.3 x 18.9 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54932,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2090,false,true,54933,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Yashima Gakutei,"Japanese, 1786?–1868",,Yashima Gakutei,Japanese,1786,1868,19th century,1800,1899,Polychrome woodblock print (surimono); ink and color on paper,8 7/16 x 7 3/8 in. (21.4 x 18.7 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54933,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2098,false,true,54947,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Yashima Gakutei,"Japanese, 1786?–1868",,Yashima Gakutei,Japanese,1786,1868,19th century,1800,1868,Polychrome woodblock print (surimono); ink and color on paper,8 3/16 x 7 1/4 in. (20.8 x 18.4 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54947,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2099,false,true,54948,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Yashima Gakutei,"Japanese, 1786?–1868",,Yashima Gakutei,Japanese,1786,1868,19th century,1800,1868,Polychrome woodblock print (surimono); ink and color on paper,8 3/16 x 7 1/4 in. (20.8 x 18.4 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54948,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2100,false,true,54949,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Yashima Gakutei,"Japanese, 1786?–1868",,Yashima Gakutei,Japanese,1786,1868,19th century,1800,1899,Polychrome woodblock print (surimono); ink and color on paper,8 1/8 x 7 5/16 in. (20.6 x 18.6 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54949,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2101,false,true,54950,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Yashima Gakutei,"Japanese, 1786?–1868",,Yashima Gakutei,Japanese,1786,1868,19th century,1800,1899,Polychrome woodblock print (surimono); ink and color on paper,8 1/4 x 7 5/16 in. (21 x 18.6 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54950,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2102,false,true,54954,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Yashima Gakutei,"Japanese, 1786?–1868",,Yashima Gakutei,Japanese,1786,1868,19th century,1800,1899,Polychrome woodblock print (surimono); ink and color on paper,8 3/16 x 7 5/16 in. (20.8 x 18.6 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54954,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2103,false,true,54955,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Yashima Gakutei,"Japanese, 1786?–1868",,Yashima Gakutei,Japanese,1786,1868,19th century,1800,1899,Polychrome woodblock print (surimono); ink and color on paper,8 3/16 x 7 5/16 in. (20.8 x 18.6 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54955,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2104,false,true,54956,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Yashima Gakutei,"Japanese, 1786?–1868",,Yashima Gakutei,Japanese,1786,1868,19th century,1800,1899,Polychrome woodblock print (surimono); ink and color on paper,8 3/16 x 7 5/16 in. (20.8 x 18.6 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54956,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2105,false,true,54957,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Yashima Gakutei,"Japanese, 1786?–1868",,Yashima Gakutei,Japanese,1786,1868,19th century,1800,1899,Polychrome woodblock print (surimono); ink and color on paper,8 3/16 x 7 5/16 in. (20.8 x 18.6 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54957,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2115,false,true,54968,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Yashima Gakutei,"Japanese, 1786?–1868",,Yashima Gakutei,Japanese,1786,1868,19th century,1800,1899,Polychrome woodblock print (surimono); ink and color on paper,8 1/16 x 7 3/16 in. (20.5 x 18.3 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54968,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2122,false,true,54976,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Yashima Gakutei,"Japanese, 1786?–1868",,Yashima Gakutei,Japanese,1786,1868,19th century,1800,1899,Polychrome woodblock print (surimono); ink and color on paper,8 1/16 x 7 1/4 in. (20.5 x 18.4 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54976,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2127,false,true,54982,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Yashima Gakutei,"Japanese, 1786?–1868",,Yashima Gakutei,Japanese,1786,1868,19th century,1800,1899,Polychrome woodblock print (surimono); ink and color on paper,8 1/16 x 7 5/16 in. (20.5 x 18.6 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54982,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2164,false,true,55071,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Yashima Gakutei,"Japanese, 1786?–1868",,Yashima Gakutei,Japanese,1786,1868,19th century,1800,1899,Polychrome woodblock print (surimono); ink and color on paper,8 7/16 x 7 3/8 in. (21.4 x 18.7 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55071,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2165,false,true,55072,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Yashima Gakutei,"Japanese, 1786?–1868",,Yashima Gakutei,Japanese,1786,1868,19th century,1800,1899,Polychrome woodblock print (surimono); ink and color on paper,8 1/4 x 7 5/16 in. (21 x 18.6 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55072,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2262,false,true,54032,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Yashima Gakutei,"Japanese, 1786?–1868",,Yashima Gakutei,Japanese,1786,1868,19th century,1800,1899,Part of an album of woodblock prints (surimono); ink and color on paper,7 11/16 x 6 9/16 in. (19.5 x 16.7 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54032,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.281,false,true,73564,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa (Gountei) Sadahide,"Japanese, 1807–1878/79",,Utagawa (Goutei) Sadahide,Japanese,1807,1879,19th century,1807,1879,Proof line-block print; ink on paper,Image: 11 3/4 x 9 1/2 in. (29.8 x 24.1 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73564,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.282,false,true,73637,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa (Gountei) Sadahide,"Japanese, 1807–1878/79",,Utagawa (Goutei) Sadahide,Japanese,1807,1879,19th century,1807,1879,Proof line-block print for fan; ink on paper,Image: 11 7/8 x 9 1/2 in. (30.2 x 24.1 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73637,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.283,false,true,73638,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa (Gountei) Sadahide,"Japanese, 1807–1878/79",,Utagawa (Goutei) Sadahide,Japanese,1807,1879,19th century,1807,1879,Proof line-block print for fan; ink on paper,Image: 12 x 9 1/2 in. (30.5 x 24.1 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73638,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.284,false,true,73639,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa (Gountei) Sadahide,"Japanese, 1807–1878/79",,Utagawa (Goutei) Sadahide,Japanese,1807,1879,19th century,1807,1879,Proof line-block print for fan; ink on paper,Image: 12 x 9 1/2 in. (30.5 x 24.1 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73639,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.285,false,true,73640,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa (Gountei) Sadahide,"Japanese, 1807–1878/79",,Utagawa (Goutei) Sadahide,Japanese,1807,1879,19th century,1807,1879,Proof line-block print for fan; ink on paper,Image: 12 x 9 1/2 in. (30.5 x 24.1 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73640,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.286,false,true,73641,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa (Gountei) Sadahide,"Japanese, 1807–1878/79",,Utagawa (Goutei) Sadahide,Japanese,1807,1879,19th century,1807,1879,Proof line-block print for fan; ink on paper,Image: 11 3/4 x 9 1/2 in. (29.8 x 24.1 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73641,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.287,false,true,73642,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa (Gountei) Sadahide,"Japanese, 1807–1878/79",,Utagawa (Goutei) Sadahide,Japanese,1807,1879,19th century,1807,1879,Proof line-block print for fan; ink on paper,Image: 12 x 9 1/2 in. (30.5 x 24.1 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73642,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.289,false,true,73566,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa (Gountei) Sadahide,"Japanese, 1807–1878/79",,Utagawa (Goutei) Sadahide,Japanese,1807,1879,19th century,1807,1879,Preparatory drawing for triptych of woodblock prints; ink on paper,Image: 14 1/2 x 32 7/8 in. (36.8 x 83.5 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73566,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.297,false,true,73643,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa (Gountei) Sadahide,"Japanese, 1807–1878/79",,Utagawa (Goutei) Sadahide,Japanese,1807,1879,19th century,1807,1879,Triptych of polychrome woodblock prints; ink and color on paper,Image: 13 7/8 x 28 5/8 in. (35.2 x 72.7 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73643,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.298,false,true,73644,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa (Gountei) Sadahide,"Japanese, 1807–1878/79",,Utagawa (Goutei) Sadahide,Japanese,1807,1879,19th century,1807,1879,Polychrome woodblock print; ink and color on paper,Image: 9 3/8 x 14 3/8 in. (23.8 x 36.5 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73644,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.291a–c,false,true,73647,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa (Gountei) Sadahide,"Japanese, 1807–1878/79",,Utagawa (Goutei) Sadahide,Japanese,1807,1879,19th century,1807,1879,Triptych of polychrome woodblock prints; ink and color on paper,Image (d): 14 3/8 x 9 3/4 in. (36.5 x 24.8 cm) Image (e): 14 1/2 x 9 5/8 in. (36.8 x 24.4 cm) Image (f): 14 3/8 x 9 1/2 in. (36.5 x 24.1 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73647,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.292a–c,false,true,73648,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa (Gountei) Sadahide,"Japanese, 1807–1878/79",,Utagawa (Goutei) Sadahide,Japanese,1807,1879,19th century,1807,1879,Triptych of polychrome woodblock prints; ink and color on paper,Image (g): 14 1/2 x 9 1/2 in. (36.8 x 24.1 cm) Image (h): 14 1/2 x 9 1/2 in. (36.8 x 24.1 cm) Image (i): 14 1/8 x 9 1/2 in. (35.9 x 24.1 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73648,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.293a–c,false,true,73649,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa (Gountei) Sadahide,"Japanese, 1807–1878/79",,Utagawa (Goutei) Sadahide,Japanese,1807,1879,19th century,1807,1879,Triptych of polychrome woodblock prints; ink and color on paper,Image (j): 14 1/8 x 9 5/8 in. (35.9 x 24.4 cm) Image (k): 14 1/8 x 9 1/2 in. (35.9 x 24.1 cm) Image (l): 14 1/4 x 9 1/2 in. (36.2 x 24.1 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73649,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.294a–c,false,true,73568,Asian Art,Woodblock print,Tōtō Ryōgokubashi natsu keshiki|Panoramic View of Ryōgoku Bridge in the Summer,Japan,Edo period (1615–1868),,,,Artist,,Utagawa (Gountei) Sadahide,"Japanese, 1807–1878/79",,Utagawa (Goutei) Sadahide,Japanese,1807,1879,19th century,1807,1879,Triptych of polychrome woodblock prints; ink and color on paper,Image (a): 13 3/4 x 9 1/2 in. (34.9 x 24.1 cm) Image (b): 13 3/4 x 9 1/2 in. (34.9 x 24.1 cm) Image (c): 14 x 9 1/2 in. (35.6 x 24.1 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73568,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.295a–c,false,true,73569,Asian Art,Woodblock prints,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa (Gountei) Sadahide,"Japanese, 1807–1878/79",,Utagawa (Goutei) Sadahide,Japanese,1807,1879,19th century,1807,1879,Triptych of polychrome woodblock prints; ink and color on paper,Image (a): 13 7/8 x 9 1/2 in. (35.2 x 24.1 cm) Image (b): 13 7/8 x 9 1/4 in. (35.2 x 23.5 cm) Image (c): 13 3/4 x 9 1/2 in. (34.9 x 24.1 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73569,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.296a–f,false,true,73570,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa (Gountei) Sadahide,"Japanese, 1807–1878/79",,Utagawa (Goutei) Sadahide,Japanese,1807,1879,19th century,1807,1879,Hexaptych of polychrome woodblock prints; ink and color on paper,Image (a): 14 1/4 x 9 3/4 in. (36.2 x 24.8 cm) Image (b): 14 1/4 x 9 3/4 in. (36.2 x 24.8 cm) Image (c): 14 1/4 x 9 5/8 in. (36.2 x 24.4 cm) Image (d): 14 1/4 x 9 3/4 in. (36.2 x 24.8 cm) Image (e): 14 1/4 x 9 7/8 in. (36.2 x 25.1 cm) Image (f): 14 1/8 x 9 7/8 in. (35.9 x 25.1 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73570,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.299a–c,false,true,73571,Asian Art,Print,Jōkisen zenzu|Complete Picture of a Steamship off Kanazawa,Japan,Edo period (1615–1868),,,,Artist,,Utagawa (Gountei) Sadahide,"Japanese, 1807–1878/79",,Utagawa (Goutei) Sadahide,Japanese,1807,1879,19th century,1807,1879,Triptych of polychrome woodblock prints; ink and color on paper,Image (a): 14 1/8 x 9 3/8 in. (35.9 x 23.8 cm) Image (b): 14 1/8 x 9 1/4 in. (35.9 x 23.5 cm) Image (c): 14 1/8 x 9 1/4 in. (35.9 x 23.5 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73571,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2097,false,true,54945,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Ishikawa Kazan,"Japanese, active 1810–1823",,Ishikawa Kazan,Japanese,1810,1823,19th century,1810,1823,Polychrome woodblock print (surimono); ink and color on paper,8 1/8 x 7 1/4 in. (20.6 x 18.4 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54945,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.317,false,true,73576,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Yoshitora,"Japanese, active ca. 1850–80",,Utagawa Yoshitora,Japanese,1845,1880,19th century,1850,1880,Woodblock print; ink on paper,Image: 13 1/2 x 9 7/8 in. (34.3 x 25.1 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73576,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.319,false,true,73631,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Yoshitora,"Japanese, active ca. 1850–80",,Utagawa Yoshitora,Japanese,1845,1880,19th century,1850,1880,Polychrome woodblock print; ink and color on paper,Image: 14 1/2 x 9 3/4 in. (36.8 x 24.8 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73631,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.318a–c,false,true,73577,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Yoshitora,"Japanese, active ca. 1850–80",,Utagawa Yoshitora,Japanese,1845,1880,19th century,1850,1880,Triptych of polychrome woodblock prints; ink and color on paper,Image (a): 14 1/4 x 9 3/4 in. (36.2 x 24.8 cm) Image (b): 14 1/4 x 9 3/4 in. (36.2 x 24.8 cm) Image (c): 14 1/4 x 9 1/2 in. (36.2 x 24.1 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73577,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1950,false,true,54518,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Ryūryūkyo Shinsai,"Japanese, active ca. 1799–1823",,Ryūryūkyo Shinsai,Japanese,1799,1823,19th century,1800,1899,Polychrome woodblock print (surimono); ink and color on paper,5 1/2 x 7 1/8 in. (14 x 18.1 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54518,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1958,false,true,54530,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Ryūryūkyo Shinsai,"Japanese, active ca. 1799–1823",,Ryūryūkyo Shinsai,Japanese,1799,1823,19th century,1800,1823,Polychrome woodblock print (surimono); ink and color on paper,8 1/4 x 11 3/8 in. (21 x 28.9 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54530,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1970,false,true,54555,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Ryūryūkyo Shinsai,"Japanese, active ca. 1799–1823",,Ryūryūkyo Shinsai,Japanese,1799,1823,19th century,1800,1899,Polychrome woodblock print (surimono); ink and color on paper,5 1/2 x 7 9/16 in. (14 x 19.2 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54555,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1973,false,true,54558,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Ryūryūkyo Shinsai,"Japanese, active ca. 1799–1823",,Ryūryūkyo Shinsai,Japanese,1799,1823,19th century,1800,1899,Polychrome woodblock print (surimono); ink and color on paper,8 1/4 x 7 3/8 in. (21 x 18.7 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54558,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1975,false,true,54560,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Ryūryūkyo Shinsai,"Japanese, active ca. 1799–1823",,Ryūryūkyo Shinsai,Japanese,1799,1823,19th century,1800,1823,Polychrome woodblock print (surimono); ink and color on paper,5 3/4 x 6 7/8 in. (14.6 x 17.5 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54560,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1976,false,true,54561,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Ryūryūkyo Shinsai,"Japanese, active ca. 1799–1823",,Ryūryūkyo Shinsai,Japanese,1799,1823,19th century,1800,1823,Polychrome woodblock print (surimono); ink and color on paper,7 3/4 x 6 1/2 in. (19.7 x 16.5 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54561,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1990,false,true,54693,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Ryūryūkyo Shinsai,"Japanese, active ca. 1799–1823",,Ryūryūkyo Shinsai,Japanese,1799,1823,19th century,1800,1899,Polychrome woodblock print (surimono); ink and color on paper,5 5/8 x 7 7/16 in. (14.3 x 18.9 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54693,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1997,false,true,54717,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Ryūryūkyo Shinsai,"Japanese, active ca. 1799–1823",,Ryūryūkyo Shinsai,Japanese,1799,1823,19th century,1800,1899,Polychrome woodblock print (surimono); ink and color on paper,5 1/8 x 7 in. (13 x 17.8 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54717,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1998,false,true,54718,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Ryūryūkyo Shinsai,"Japanese, active ca. 1799–1823",,Ryūryūkyo Shinsai,Japanese,1799,1823,19th century,1800,1899,Polychrome woodblock print (surimono); ink and color on paper,5 5/8 x 7 7/16 in. (14.3 x 18.9 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54718,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2001,false,true,54721,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Ryūryūkyo Shinsai,"Japanese, active ca. 1799–1823",,Ryūryūkyo Shinsai,Japanese,1799,1823,19th century,1800,1899,Polychrome woodblock print (surimono); ink and color on paper,5 5/8 x 7 1/2 in. (14.3 x 19.1 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54721,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2002,false,true,54722,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Ryūryūkyo Shinsai,"Japanese, active ca. 1799–1823",,Ryūryūkyo Shinsai,Japanese,1799,1823,19th century,1800,1899,Polychrome woodblock print (surimono); ink and color on paper,5 5/8 x 7 1/2 in. (14.3 x 19.1 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54722,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2008,false,true,54728,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Ryūryūkyo Shinsai,"Japanese, active ca. 1799–1823",,Ryūryūkyo Shinsai,Japanese,1799,1823,19th century,1800,1899,Polychrome woodblock print (surimono); ink and color on paper,5 7/16 x 7 5/16 in. (13.8 x 18.6 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54728,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2018,false,true,54767,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Ryūryūkyo Shinsai,"Japanese, active ca. 1799–1823",,Ryūryūkyo Shinsai,Japanese,1799,1823,19th century,1800,1899,Polychrome woodblock print (surimono); ink and color on paper,8 5/8 x 11 1/4 in. (21.9 x 28.6 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54767,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2025,false,true,54785,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Ryūryūkyo Shinsai,"Japanese, active ca. 1799–1823",,Ryūryūkyo Shinsai,Japanese,1799,1823,19th century,1800,1899,Polychrome woodblock print (surimono); ink and color on paper,5 5/16 x 7 5/16 in. (13.5 x 18.6 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54785,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2028,false,true,54788,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Ryūryūkyo Shinsai,"Japanese, active ca. 1799–1823",,Ryūryūkyo Shinsai,Japanese,1799,1823,19th century,1800,1899,Polychrome woodblock print (surimono); ink and color on paper,8 1/16 x 7 1/8 in. (20.5 x 18.1 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54788,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2034,false,true,54800,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Ryūryūkyo Shinsai,"Japanese, active ca. 1799–1823",,Ryūryūkyo Shinsai,Japanese,1799,1823,19th century,1800,1899,Polychrome woodblock print (surimono); ink and color on paper,5 11/16 x 7 9/16 in. (14.4 x 19.2 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54800,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2056,false,true,54832,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Ryūryūkyo Shinsai,"Japanese, active ca. 1799–1823",,Ryūryūkyo Shinsai,Japanese,1799,1823,19th century,1800,1899,Polychrome woodblock print (surimono); ink and color on paper,8 1/4 x 10 3/4 in. (21 x 27.3 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54832,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2058,false,true,54836,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Ryūryūkyo Shinsai,"Japanese, active ca. 1799–1823",,Ryūryūkyo Shinsai,Japanese,1799,1823,19th century,1800,1899,Polychrome woodblock print (surimono); ink and color on paper,5 11/16 x 7 3/8 in. (14.4 x 18.7 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54836,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2059,false,true,54838,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Ryūryūkyo Shinsai,"Japanese, active ca. 1799–1823",,Ryūryūkyo Shinsai,Japanese,1799,1823,19th century,1800,1899,Polychrome woodblock print (surimono); ink and color on paper,5 3/8 x 7 1/4 in. (13.7 x 18.4 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54838,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2065,false,true,54894,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Ryūryūkyo Shinsai,"Japanese, active ca. 1799–1823",,Ryūryūkyo Shinsai,Japanese,1799,1823,19th century,1800,1899,Polychrome woodblock print (surimono); ink and color on paper,4 7/8 x 10 3/4 in. (12.4 x 27.3 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54894,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2070,false,true,54901,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Ryūryūkyo Shinsai,"Japanese, active ca. 1799–1823",,Ryūryūkyo Shinsai,Japanese,1799,1823,19th century,1800,1899,Polychrome woodblock print (surimono); ink and color on paper,5 7/16 x 7 3/8 in. (13.8 x 18.7 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54901,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2071,false,true,54902,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Ryūryūkyo Shinsai,"Japanese, active ca. 1799–1823",,Ryūryūkyo Shinsai,Japanese,1799,1823,19th century,1800,1899,Polychrome woodblock print (surimono); ink and color on paper,5 11/16 x 7 1/2 in. (14.4 x 19.1 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54902,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2074,false,true,54905,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Ryūryūkyo Shinsai,"Japanese, active ca. 1799–1823",,Ryūryūkyo Shinsai,Japanese,1799,1823,19th century,1800,1899,Polychrome woodblock print (surimono); ink and color on paper,5 7/16 x 7 3/8 in. (13.8 x 18.7 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54905,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2082,false,true,54919,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Ryūryūkyo Shinsai,"Japanese, active ca. 1799–1823",,Ryūryūkyo Shinsai,Japanese,1799,1823,19th century,1800,1899,Polychrome woodblock print (surimono); ink and color on paper,5 7/16 x 7 5/16 in. (13.8 x 18.6 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54919,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2084,false,true,54921,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Ryūryūkyo Shinsai,"Japanese, active ca. 1799–1823",,Ryūryūkyo Shinsai,Japanese,1799,1823,19th century,1800,1899,Polychrome woodblock print (surimono); ink and color on paper,5 9/16 x 7 1/2 in. (14.1 x 19.1 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54921,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2094,false,true,54939,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Ryūryūkyo Shinsai,"Japanese, active ca. 1799–1823",,Ryūryūkyo Shinsai,Japanese,1799,1823,19th century,1800,1823,Polychrome woodblock print (surimono); ink and color on paper,5 11/16 x 7 9/16 in. (14.4 x 19.2 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54939,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2109,false,true,54961,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Ryūryūkyo Shinsai,"Japanese, active ca. 1799–1823",,Ryūryūkyo Shinsai,Japanese,1799,1823,19th century,1800,1899,Polychrome woodblock print (surimono); ink and color on paper,7 1/16 x 6 11/16 in. (17.9 x 17 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54961,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2111,false,true,54963,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Ryūryūkyo Shinsai,"Japanese, active ca. 1799–1823",,Ryūryūkyo Shinsai,Japanese,1799,1823,19th century,1800,1899,Polychrome woodblock print (surimono); ink and color on paper,5 9/16 x 7 7/16 in. (14.1 x 18.9 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54963,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2116,false,true,54969,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Ryūryūkyo Shinsai,"Japanese, active ca. 1799–1823",,Ryūryūkyo Shinsai,Japanese,1799,1823,19th century,1800,1899,Polychrome woodblock print (surimono); ink and color on paper,7 3/16 x 6 9/16 in. (18.3 x 16.7 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54969,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2118,false,true,54971,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Ryūryūkyo Shinsai,"Japanese, active ca. 1799–1823",,Ryūryūkyo Shinsai,Japanese,1799,1823,19th century,1800,1899,Polychrome woodblock print (surimono); ink and color on paper,5 1/2 x 7 7/16 in. (14 x 18.9 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54971,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2119,false,true,54972,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Ryūryūkyo Shinsai,"Japanese, active ca. 1799–1823",,Ryūryūkyo Shinsai,Japanese,1799,1823,19th century,1800,1899,Polychrome woodblock print (surimono); ink and color on paper,5 7/16 x 7 3/8 in. (13.8 x 18.7 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54972,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2154,false,true,55060,Asian Art,Print,"『春雨集』 摺物帖柳々居辰斎画 若松|Spring Rain Collection (Harusame shū), vol. 2: Pine Shoots and Accoutrements for New Year’s Celebrations",Japan,Edo period (1615–1868),,,,Artist,,Ryūryūkyo Shinsai,"Japanese, active ca. 1799–1823",,Ryūryūkyo Shinsai,Japanese,1799,1823,19th century,1800,1899,Privately published woodblock prints (surimono) mounted in an album; ink and color on paper,5 7/16 x 7 3/8 in. (13.8 x 18.7 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55060,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2159,false,true,55066,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Ryūryūkyo Shinsai,"Japanese, active ca. 1799–1823",,Ryūryūkyo Shinsai,Japanese,1799,1823,19th century,1800,1899,Polychrome woodblock print (surimono); ink and color on paper,5 3/16 x 7 1/16 in. (13.2 x 17.9 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55066,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2172,false,true,55086,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Ryūryūkyo Shinsai,"Japanese, active ca. 1799–1823",,Ryūryūkyo Shinsai,Japanese,1799,1823,19th century,1800,1899,Polychrome woodblock print (surimono); ink and color on paper,7 13/16 x 7 5/16 in. (19.8 x 18.6 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55086,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2173,false,true,55087,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Ryūryūkyo Shinsai,"Japanese, active ca. 1799–1823",,Ryūryūkyo Shinsai,Japanese,1799,1823,19th century,1800,1899,Polychrome woodblock print (surimono); ink and color on paper,5 5/16 x 7 3/8 in. (13.5 x 18.7 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55087,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2237,false,true,54005,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Ryūryūkyo Shinsai,"Japanese, active ca. 1799–1823",,Ryūryūkyo Shinsai,Japanese,1799,1823,19th century,1800,1899,Part of an album of woodblock prints (surimono); ink and color on paper,3 1/2 x 7 1/8 in. (8.9 x 18.1 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54005,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2245,false,true,54014,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Ryūryūkyo Shinsai,"Japanese, active ca. 1799–1823",,Ryūryūkyo Shinsai,Japanese,1799,1823,19th century,1800,1823,Part of an album of woodblock prints (surimono); ink and color on paper,3 1/2 x 7 1/8 in. (8.9 x 18.1 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54014,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2246,false,true,54015,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Ryūryūkyo Shinsai,"Japanese, active ca. 1799–1823",,Ryūryūkyo Shinsai,Japanese,1799,1823,19th century,1800,1823,Part of an album of woodblock prints (surimono); ink and color on paper,7 5/8 x 10 7/8 in. (19.4 x 27.6 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54015,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2252,false,true,54021,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Ryūryūkyo Shinsai,"Japanese, active ca. 1799–1823",,Ryūryūkyo Shinsai,Japanese,1799,1823,19th century,1800,1899,Part of an album of woodblock prints (surimono); ink and color on paper,5 1/2 x 7 5/16 in. (14 x 18.6 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54021,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2253,false,true,54022,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Ryūryūkyo Shinsai,"Japanese, active ca. 1799–1823",,Ryūryūkyo Shinsai,Japanese,1799,1823,19th century,1800,1899,Part of an album of woodblock prints (surimono); ink and color on paper,5 13/16 x 7 7/16 in. (14.8 x 18.9 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54022,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2263,false,true,54033,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Ryūryūkyo Shinsai,"Japanese, active ca. 1799–1823",,Ryūryūkyo Shinsai,Japanese,1799,1823,19th century,1800,1899,Part of an album of woodblock prints (surimono); ink and color on paper,5 1/2 x 7 7/16 in. (14 x 18.9 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54033,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2264,false,true,54034,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Ryūryūkyo Shinsai,"Japanese, active ca. 1799–1823",,Ryūryūkyo Shinsai,Japanese,1799,1823,19th century,1800,1899,Part of an album of woodblock prints (surimono); ink and color on paper,5 1/2 x 7 1/2 in. (14 x 19.1 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54034,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2265,false,true,54035,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Ryūryūkyo Shinsai,"Japanese, active ca. 1799–1823",,Ryūryūkyo Shinsai,Japanese,1799,1823,19th century,1800,1899,Part of an album of woodblock prints (surimono); ink and color on paper,7 3/4 x 6 9/16 in. (19.7 x 16.7 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54035,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2266,false,true,54039,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Ryūryūkyo Shinsai,"Japanese, active ca. 1799–1823",,Ryūryūkyo Shinsai,Japanese,1799,1823,19th century,1800,1823,Part of an album of woodblock prints (surimono); ink and color on paper,5 1/2 x 7 7/16 in. (14 x 18.9 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54039,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2267,false,true,54040,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Ryūryūkyo Shinsai,"Japanese, active ca. 1799–1823",,Ryūryūkyo Shinsai,Japanese,1799,1823,19th century,1800,1823,Part of an album of woodblock prints (surimono); ink and color on paper,5 1/2 x 7 1/2 in. (14 x 19.1 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54040,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2370,false,true,54154,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Ryūryūkyo Shinsai,"Japanese, active ca. 1799–1823",,Ryūryūkyo Shinsai,Japanese,1799,1823,19th century,1800,1899,Part of an album of woodblock prints (surimono); ink and color on paper,6 3/8 x 11 5/8 in. (16.2 x 29.5 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54154,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP212.45,false,true,58029,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Yoshikazu,"Japanese, active ca. 1850–1870",,Utagawa Yoshikazu,Japanese,1845,1870,19th century,1850,1870,Polychrome woodblock print; ink and color on paper,Image: 13 5/8 × 9 1/2 in. (34.6 × 24.1 cm),"Gift of the Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/58029,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP212.46,false,true,58030,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Yoshikazu,"Japanese, active ca. 1850–1870",,Utagawa Yoshikazu,Japanese,1845,1870,19th century,1850,1870,Polychrome woodblock print; ink and color on paper,Image: 13 5/8 × 9 1/2 in. (34.6 × 24.1 cm),"Gift of the Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/58030,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP212.52,false,true,58036,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Sadakage,"Japanese, active mid-19th century",,Utagawa Sadakage,Japanese,1800,1899,19th century,1834,1866,Polychrome woodblock print; ink and color on paper,Image: 13 5/8 × 9 1/2 in. (34.6 × 24.1 cm),"Gift of the Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/58036,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP212.53,false,true,58037,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Sadakage,"Japanese, active mid-19th century",,Utagawa Sadakage,Japanese,1800,1899,19th century,1834,1866,Polychrome woodblock print; ink and color on paper,Image: 13 5/8 × 9 1/2 in. (34.6 × 24.1 cm),"Gift of the Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/58037,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP212.54,false,true,58038,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Sadakage,"Japanese, active mid-19th century",,Utagawa Sadakage,Japanese,1800,1899,19th century,1834,1866,Polychrome woodblock print; ink and color on paper,Image: 13 5/8 × 9 1/2 in. (34.6 × 24.1 cm),"Gift of the Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/58038,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP212.1,false,true,57986,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,19th century,1800,1858,Polychrome woodblock print; ink and color on paper,Image: 13 1/2 × 9 1/2 in. (34.3 × 24.1 cm),"Gift of the Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/57986,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.278,false,true,73561,Asian Art,Print,六十余州名所図会 薩摩 坊ノ浦 雙剣石|Upright Landscape,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,19th century,1800,1858,Polychrome woodblock print; ink and color on paper,Image: 14 3/16 x 9 3/4 in. (36 x 24.8 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73561,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2748.3,false,true,58267,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Utagawa Kuniteru,"Japanese, 1830–1874",,Utagawa Kuniteru,Japanese,1830,1874,19th century,1800,1899,Polychrome woodblock print; ink and color on paper,Approx. 14 1/2 x 9 1/2 in. (36.8 x 24.1 cm),"Bequest of Mary Martin, 1938",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/58267,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2748.1,false,true,58265,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Utagawa Yoshiiku,"Japanese, 1833–1904",,Utagawa Yoshiiku,Japanese,1833,1904,19th century,1800,1899,Polychrome woodblock print; ink and color on paper,Approx. 14 1/2 x 9 1/2 in. (36.8 x 24.1 cm),"Bequest of Mary Martin, 1938",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/58265,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2748.2,false,true,58266,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Utagawa Yoshiiku,"Japanese, 1833–1904",,Utagawa Yoshiiku,Japanese,1833,1904,19th century,1800,1899,Polychrome woodblock print; ink and color on paper,Approx. 14 1/2 x 9 1/2 in. (36.8 x 24.1 cm),"Bequest of Mary Martin, 1938",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/58266,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2748.4,false,true,58268,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Utagawa Yoshiiku,"Japanese, 1833–1904",,Utagawa Yoshiiku,Japanese,1833,1904,19th century,1800,1899,Polychrome woodblock print; ink and color on paper,Approx. 14 1/2 x 9 1/2 in. (36.8 x 24.1 cm),"Bequest of Mary Martin, 1938",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/58268,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2748.5,false,true,58269,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Utagawa Yoshiiku,"Japanese, 1833–1904",,Utagawa Yoshiiku,Japanese,1833,1904,19th century,1800,1899,Polychrome woodblock print; ink and color on paper,Approx. 14 1/2 x 9 1/2 in. (36.8 x 24.1 cm),"Bequest of Mary Martin, 1938",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/58269,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2748.6,false,true,58270,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Utagawa Yoshiiku,"Japanese, 1833–1904",,Utagawa Yoshiiku,Japanese,1833,1904,19th century,1800,1899,Polychrome woodblock print; ink and color on paper,Approx. 14 1/2 x 9 1/2 in. (36.8 x 24.1 cm),"Bequest of Mary Martin, 1938",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/58270,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.309,false,true,73630,Asian Art,Print,真柴秀吉公名護屋陣先手諸将繰出之図|Hideyoshi and His Troops Leaving Nagoya Camp (Mashiba Hideyoshi kō nagoya jin saki te no shoshō kuridashi no zu),Japan,Meiji period (1868–1912),,,,Artist,,Tsukioka Yoshitoshi,"Japanese, 1839–1892",,Tsukioka Yoshitoshi,Japanese,1839,1892,19th century,1800,1899,Triptych of polychrome woodblock prints; ink and color on paper,Image (a): 13 7/8 x 9 3/4 in. (35.2 x 24.8 cm) Image (b): 13 7/8 x 9 7/8 in. (35.2 x 25.1 cm) Image (c): 13 7/8 x 9 7/8 in. (35.2 x 25.1 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73630,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.314,false,true,73627,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Tsukioka Yoshitoshi,"Japanese, 1839–1892",,Tsukioka Yoshitoshi,Japanese,1839,1892,19th century,1800,1899,Polychrome woodblock print; ink and color on paper,Image: 14 9/16 x 9 7/8 in. (37 x 25.1 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73627,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.321a–c,false,true,73579,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Kobayashi Kiyochika,"Japanese, 1847–1915",,Kobayashi Kiyochika,Japanese,1847,1915,19th century,1800,1899,Triptych of polychrome woodblock prints; ink and color on paper,Image (a): 14 x 9 1/2 in. (35.6 x 24.1 cm) Image (b): 9 3/8 x 14 in. (23.8 x 35.6 cm) Image (c): 14 x 9 1/2 in. (35.6 x 24.1 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73579,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.325,false,true,73580,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Unsen,"Japanese, active ca. 1875",,Unsen,Japanese,1875,1875,19th century,1800,1899,Triptych of preparatory drawings; ink on paper,Image: 14 1/8 x 27 3/4 in. (35.9 x 70.5 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73580,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.326,false,true,73633,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Unsen,"Japanese, active ca. 1875",,Unsen,Japanese,1875,1875,19th century,1800,1899,Triptych of proof prints; ink on paper,Image: 15 1/8 x 30 1/8 in. (38.4 x 76.5 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73633,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.327,false,true,73634,Asian Art,Print,,Japan,Meiji period (1868–1912),,,,Artist,,Unsen,"Japanese, active ca. 1875",,Unsen,Japanese,1875,1875,19th century,1800,1899,Triptych of polychrome woodblock prints; ink and color on paper,Image: 14 1/4 x 28 7/8 in. (36.2 x 73.3 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/73634,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -93.3.367,false,true,62816,Asian Art,Bowl,,Japan,,,,,Artist,,Seifu Yohei,1803–1861,,Seifu Yohei,Japanese,1803,1861,19th century,1800,1899,"White porcelain decorated with blue under the glaze, polychrome enamels and gold (Kyoto ware)",H. 2 1/8 in. (5.4 cm); Diam. 5 1/8 in. (13 cm),"Gift of Charles Stewart Smith, 1893",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/62816,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -93.3.368,false,true,46485,Asian Art,Bowl,,Japan,,,,,Artist,,Seifu Yohei,1803–1861,,Seifu Yohei,Japanese,1803,1861,19th century,1800,1899,"White porcelain decorated with blue under the glaze, polychrome enamels (Kyoto ware)",H. 2 3/8 in. (6 cm); Diam. 5 1/2 in. (14 cm),"Gift of Charles Stewart Smith, 1893",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/46485,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -93.1.151,false,true,62679,Asian Art,Bowl,,Japan,,,,,Artist,,Nin'ami Dōhachi (Dōhachi II),"Japanese, 1783–1855",,Dōhachi II,Japanese,1783,1855,19th century,1800,1899,"Clay partly covered with glaze; reserves forming patterns having slip dots in the center, covered with a transparent glaze (Kyoto ware)",H. 3 1/4 in. (8.3 cm); Diam. 6 3/8 in. (16.2 cm),"Gift of Mr. and Mrs. Samuel Colman, 1893",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/62679,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -25.215.55,false,true,53585,Asian Art,Tile,,Japan,,,,,Artist,,Raku Tannyu,"Japanese, 1795–1854",,"Raku, Tannyu",Japanese,1795,1854,19th century,1800,1899,Earthenware (Raku ware),H. 4 1/2 (11.4 cm); W. 2 3/4 in. (7 cm),"Fletcher Fund, 1925",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/53585,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -07.13,false,true,62871,Asian Art,Wine bottle,,Japan,,,,,Artist,,Shuntai,"Japanese, 1799–1878",,Shuntai,Japanese,1799,1799,19th century,1800,1899,Clay covered with buff crackled glaze and decoration in overglaze (Shino Oribe type),H. 9 1/8 in. (23.2 cm),"Rogers Fund, 1907",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/62871,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -93.1.3,false,true,62608,Asian Art,Deep bowl,,Japan,,,,,Artist,,Shuntai,"Japanese, 1799–1878",,Shuntai,Japanese,1799,1799,19th century,1800,1899,"Clay covered with a transparent crackled glaze over incised decoration (Mino ware, Ofuke type)",H. 4 in. (10.2 cm); Diam. 5 1/2 in. (14 cm),"Gift of Mr. and Mrs. Samuel Colman, 1893",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/62608,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1984.392.1,false,true,63937,Asian Art,Teabowl,,Japan,,,,,Artist,,Mitsutada Ohi,"Japanese, 1835–1896",,Mitsutada Ohi,Japanese,1835,1896,19th century,1800,1899,Pottery (brown raku ware),H. 4 1/4 in. (10.8 cm); Diam. 4 1/4 in. (10.8 cm),"Gift of Toshiro Ohi, 1984",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/63937,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -93.3.271,false,true,47808,Asian Art,Vase,,Japan,,,,,Artist,,Makuzu Kōzan I (Miyagawa Toranosuke),"Japanese, 1842–1916",,Makuzu Kōzan,Japanese,1842,1916,19th century,1800,1899,Clay covered with high-fired glazes,H. 6 3/4 in. (17.1 cm),"Gift of Charles Stewart Smith, 1893",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/47808,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -93.3.423,false,true,47983,Asian Art,Vase,,Japan,,,,,Artist,,Makuzu Kōzan I (Miyagawa Toranosuke),"Japanese, 1842–1916",,Makuzu Kōzan,Japanese,1842,1916,19th century,1800,1899,Porcelain covered with a dark green glaze and design in white enamel (Kyoto ware),H. 5 3/4 in. (14.6 cm); Diam. 5 1/2 in. (14 cm),"Gift of Charles Stewart Smith, 1893",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/47983,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -93.3.480,false,true,47970,Asian Art,Vase,,Japan,,,,,Artist,,Makuzu Kōzan I (Miyagawa Toranosuke),"Japanese, 1842–1916",,Makuzu Kōzan,Japanese,1842,1916,19th century,1800,1899,White porcelain covered with a mazarine blue glaze (Kyoto ware),H. 11 3/8 in. (28.9 cm); Diam. 11 in. (27.9 cm),"Gift of Charles Stewart Smith, 1893",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/47970,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -93.3.321,false,true,47982,Asian Art,Vase,,Japan,,,,,Artist,,Takemoto,"Japanese, 1845–1892",,Takemoto,Japanese,1845,1892,19th century,1800,1899,White porcelain with a dappled red and white glaze,H. 4 1/4 in. (10.8 cm); Diam. 9 5/8 in. (24.4 cm),"Gift of Charles Stewart Smith, 1893",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/47982,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -93.3.382,false,true,48015,Asian Art,Flower vase,,Japan,,,,,Artist,,Takemoto,"Japanese, 1845–1892",,Takemoto,Japanese,1845,1892,19th century,1800,1899,"White porcelain, faintly green, with a flambé collar",H. 8 1/4 in. (21 cm); Diam. 5 in. (12.7 cm),"Gift of Charles Stewart Smith, 1893",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/48015,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -93.1.91,false,true,62646,Asian Art,Teabowl,,Japan,,,,,Artist,,Kiyomizu Rokubei III,"Japanese, active 1820–1880",,Kiyomizu Rokubei III,Japanese,1820,1880,19th century,1800,1899,Clay covered with finely crackled glaze over a design (Kiyomizu ware),H. 2 1/4 in. (5.7 cm),"Gift of Mr. and Mrs. Samuel Colman, 1893",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/62646,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -93.1.135,false,true,47357,Asian Art,Bowl,,Japan,,,,,Artist,,Nonomura Ninsei,"Japanese, active ca. 1646–94",,Ninsei Nonomura,Japanese,1646,1694,19th century,1800,1899,"Clay covered with a transparent crackled glaze and decorated with polychrome enamel (Kyoto ware, Banko style)",H. 3 in. (7.6 cm); Diam. 4 3/4 in. (12.1 cm); Diam. of foot 1 7/8 in. (4.8 cm),"Gift of Mr. and Mrs. Samuel Colman, 1893",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/47357,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.268.697,false,true,52317,Asian Art,Wine cup,,Japan,Edo period (1615–1868),,,,Artist,Design attributed to,Aoki Mokubei,1767–1833,,Aoki Mokubei,Japanese,1767,1833,19th century,1800,1899,Porcelain with green and red enamels (Kyoto ware),H. 1 1/2 in. (3.8 cm); Diam. 4 1/4 in. (10.8 cm),"The Harry G. C. Packard Collection of Asian Art, Gift of Harry G. C. Packard, and Purchase, Fletcher, Rogers, Harris Brisbane Dick, and Louis V. Bell Funds, Joseph Pulitzer Bequest, and The Annenberg Fund Inc. Gift, 1975",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/52317,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.268.698,false,true,52318,Asian Art,Wine cup,,Japan,Edo period (1615–1868),,,,Artist,Design attributed to,Aoki Mokubei,1767–1833,,Aoki Mokubei,Japanese,1767,1833,19th century,1800,1899,Porcelain with green and red enamels (Kyoto ware),H. 1 1/2 in. (3.8 cm); Diam. 3 1/4 in. (8.3 cm),"The Harry G. C. Packard Collection of Asian Art, Gift of Harry G. C. Packard, and Purchase, Fletcher, Rogers, Harris Brisbane Dick, and Louis V. Bell Funds, Joseph Pulitzer Bequest, and The Annenberg Fund Inc. Gift, 1975",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/52318,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -X.127,false,true,58286,Asian Art,Dish,,Japan,Edo period (1615–1868),,,,Artist,Style of,Ogata Kenzan,"Japanese, 1663–1743",,Ogata Kenzan,Japanese,1663,1743,19th century,1800,1899,Stoneware with colored enamels (Kyoto ware),H. 1 1/2 in. (3.8 cm); W. 6 7/8 in. (17.5 cm),Museum Accession,,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/58286,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -93.1.34,false,true,62625,Asian Art,Dish,,Japan,Edo period (1615–1868),,,,Artist,,Shuntai,"Japanese, 1799–1878",,Shuntai,Japanese,1799,1799,19th century,1800,1899,"Glazed stoneware (Seto ware, Oribe Revival type)",H. 2 1/4 in. (5.7 cm); W. 9 in. square (22.9 cm square),"Gift of Mr. and Mrs. Samuel Colman, 1893",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/62625,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"23.225.5a, b",false,true,60782,Asian Art,Incense box,,Japan,Edo (1615–1868) or Meiji period (1868–1912),,,,Artist,,Minpei,active 19th century,,Minpei,Japanese,0019,0019,19th century,1800,1899,"Pottery decorated with colors, black and gold (Awaji ware)",H. 2 1/2 in. (6.4 cm),"Gift of Mrs. V. Everit Macy, 1923",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/60782,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -23.225.336,false,true,47856,Asian Art,Figure,,Japan,Edo (1615–1868) or Meiji period (1868–1912),,,,Artist,,Kawamoto Hansuke IV,"Japanese, active first half of the 19th century",,Kawamoto Hansuke,Japanese,1800,1849,19th century,1800,1899,Stoneware and white porcelain (Seto ware),H. 4 1/2 in. (11.4 cm); L. 9 3/8 in. (23.8 cm),"Gift of Mrs. V. Everit Macy, 1923",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/47856,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -23.225.337,false,true,48557,Asian Art,Figure,,Japan,Edo (1615–1868) or Meiji period (1868–1912),,,,Artist,,Kawamoto Hansuke IV,"Japanese, active first half of the 19th century",,Kawamoto Hansuke,Japanese,1800,1849,19th century,1800,1899,Stoneware and porcelain (Seto ware),H. 6 1/4 in. (15.9 cm); L. 10 1/8 in. (25.7 cm),"Gift of Mrs. V. Everit Macy, 1923",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/48557,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.13,false,true,49065,Asian Art,Hanging scroll,,Japan,,,,,Artist,Attributed to,Kishi Ganryo,1798–1852,,Kishi Ganryo,Japanese,1798,1852,19th century,1800,1899,Hanging scroll; ink and color on silk,38 1/2 x 13 1/2 in. (97.8 x 34.3 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49065,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.14.2,false,true,57227,Asian Art,Hanging scroll,,Japan,,,,,Artist,,Yusen,"Japanese, 1778–1850",,Yusen,Japanese,1778,1850,19th century,1800,1850,Hanging scroll; ink and color on silk,39 3/8 x 12 1/8 in. (100 x 30.8 cm),"Fletcher Fund, 1937",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/57227,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -57.70.1,false,true,57246,Asian Art,Hanging scroll,,Japan,,,,,Artist,Attributed to,Shibata Zeshin,"Japanese, 1807–1891",,Shibata Zeshin,Japanese,1807,1891,19th century,1807,1891,Hanging scroll; ink on silk,41 1/4 x 11 1/2 in. (104.8 x 29.2 cm),"Gift of Edward M. Bratter, 1957",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/57246,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -57.70.3,false,true,57248,Asian Art,Hanging scroll,,Japan,,,,,Artist,Attributed to,Shibata Zeshin,"Japanese, 1807–1891",,Shibata Zeshin,Japanese,1807,1891,19th century,1807,1891,Hanging scroll; ink on silk,5 1/16 x 4 3/4 in. (12.9 x 12.1 cm),"Gift of Edward M. Bratter, 1957",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/57248,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -64.31.1,false,true,57242,Asian Art,Painting,,Japan,,,,,Artist,,Shibata Zeshin,"Japanese, 1807–1891",,Shibata Zeshin,Japanese,1807,1891,19th century,1807,1891,Painting; colored lacquers on paper,8 x 6 3/4 in. (20.3 x 17.1 cm),"Rogers Fund, 1964",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/57242,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -51.137.1,false,true,57315,Asian Art,Hanging scroll,,Japan,,,,,Artist,,Shibata Zeshin,"Japanese, 1807–1891",,Shibata Zeshin,Japanese,1807,1891,19th century,1807,1891,Hanging scroll; ink on silk,42 7/8 x 16 3/8 in. (108.9 x 41.6 cm),"Purchase, Gifts, Bequests, and Funds from various donors, by exchange, 1951",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/57315,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -56.124.1,false,true,57294,Asian Art,Painting,,Japan,,,,,Artist,,Shibata Zeshin,"Japanese, 1807–1891",,Shibata Zeshin,Japanese,1807,1891,19th century,1807,1891,Painting; ink and color on paper (tanzaku),14 3/4 x 2 1/4 in. (37.5 x 5.7 cm),"Funds from various donors, by exchange, 1956",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/57294,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -56.124.2,false,true,57295,Asian Art,Painting,,Japan,,,,,Artist,,Shibata Zeshin,"Japanese, 1807–1891",,Shibata Zeshin,Japanese,1807,1891,19th century,1807,1891,Painting; ink and color on paper (tanzaku),14 5/8 x 2 7/16 in. (37.1 x 6.2 cm),"Funds from various donors, by exchange, 1956",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/57295,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -56.124.3,false,true,57296,Asian Art,Painting,,Japan,,,,,Artist,,Shibata Zeshin,"Japanese, 1807–1891",,Shibata Zeshin,Japanese,1807,1891,19th century,1807,1891,Painting; ink and color on paper (tanzaku),14 x 2 5/8 in. (35.6 x 6.7 cm),"Funds from various donors, by exchange, 1956",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/57296,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -56.124.4,false,true,57297,Asian Art,Painting,,Japan,,,,,Artist,,Shibata Zeshin,"Japanese, 1807–1891",,Shibata Zeshin,Japanese,1807,1891,19th century,1807,1891,Painting; ink and color on paper (tanzaku),14 3/4 x 2 3/8 in. (37.5 x 6 cm),"Funds from various donors, by exchange, 1956",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/57297,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -56.124.5,false,true,57298,Asian Art,Painting,,Japan,,,,,Artist,,Shibata Zeshin,"Japanese, 1807–1891",,Shibata Zeshin,Japanese,1807,1891,19th century,1807,1891,Painting; ink and color on paper (tanzaku),14 x 2 1/2 in. (35.6 x 6.4 cm),"Funds from various donors, by exchange, 1956",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/57298,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.5,false,true,55299,Asian Art,Hanging scroll,,Japan,,,,,Artist,Attributed to,Shiokawa Bunrin,"Japanese, 1808–1877",,Shiokawa Bunrin,Japanese,1808,1877,19th century,1800,1899,Hanging scroll; ink and color on silk,39 1/4 x 13 7/8 in. (99.7 x 35.2 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/55299,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.282.2,false,true,57164,Asian Art,Hanging scroll,,Japan,,,,,Artist,,Tsurana,"Japanese, 1809–1892",,Tsurana,Japanese,1809,1892,19th century,1809,1892,Hanging scroll; ink and color on silk,15 1/2 x 49 1/2 in. (39.4 x 125.7 cm),"Gift of Dr. and Mrs. Harold B. Bilsky, 1975",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/57164,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.16,false,true,55302,Asian Art,Hanging scroll,,Japan,,,,,Artist,Attributed to,Hasegawa Gyokuho,"Japanese, 1822–1879",,Hasegawa Gyokuho,Japanese,1822,1879,19th century,1822,1879,Hanging scroll; ink and color on silk,Overall: 41 x 16 1/8in. (104.1 x 41cm) Overall with mounting: 41 x 21 3/4 in. (104.1 x 55.2 cm) Overall with knobs: 41 x 24 in. (104.1 x 61 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/55302,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -44.35.2,false,true,57314,Asian Art,Painting,,Japan,,,,,Artist,,Toyosei Kimigi,"Japanese, active 19th century",(?),Toyosei Kimigi,Japanese,1800,1899,19th century,1800,1899,Painting; color on silk,24 5/8 x 34 5/8 in. (62.5 x 87.9 cm),"Gift of Susan Dwight Bliss, 1944",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/57314,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.14,false,true,45737,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Kano Tansui,active 19th century,,Kano Tansui,Japanese,1800,1899,19th century,1800,1868,Hanging scroll; ink and color on silk,37 1/2 x 13 1/2 in. (95.3 x 34.3 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45737,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.15,false,true,44876,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Kano Tansui,active 19th century,,Kano Tansui,Japanese,1800,1899,19th century,1800,1868,Hanging scroll; ink and color on silk,37 1/2 x 13 1/2 in. (95.3 x 34.3 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/44876,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.16,false,true,45738,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Kano Tansui,active 19th century,,Kano Tansui,Japanese,1800,1899,19th century,1800,1868,Hanging scroll; ink and color on silk,37 1/2 x 13 1/2 in. (95.3 x 34.3 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45738,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -08.148.1,false,true,48985,Asian Art,Painting,,Japan,Edo period (1615–1868),,,,Artist,,Ogata Kōrin,"Japanese, 1658–1716",,Kōrin,Japanese,1658,1716,19th century,1800,1899,Matted painting; ink and color on paper,39 7/16 x 14 3/16 in. (100.2 x 36 cm),"Gift of Francis Lathrop, 1908",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/48985,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -08.148.2,false,true,48986,Asian Art,Painting,,Japan,Edo period (1615–1868),,,,Artist,,Ogata Kōrin,"Japanese, 1658–1716",,Kōrin,Japanese,1658,1716,19th century,1800,1899,Matted painting; ink and color on paper,39 7/16 x 14 3/16 in. (100.2 x 36 cm),"Gift of Francis Lathrop, 1908",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/48986,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -08.148.3,false,true,48987,Asian Art,Painting,,Japan,Edo period (1615–1868),,,,Artist,,Ogata Kōrin,"Japanese, 1658–1716",,Kōrin,Japanese,1658,1716,19th century,1800,1899,Matted painting; ink and color on paper,39 7/16 x 14 3/16 in. (100.2 x 36 cm),"Gift of Francis Lathrop, 1908",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/48987,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -52.160.5,false,true,48911,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Nishikawa Sukenobu,"Japanese, 1671–1750",,Nishikawa Sukenobu,Japanese,1671,1750,19th century,1800,1899,Hanging scroll; ink and color on silk,21 1/2 x 25 in. (54.6 x 63.5 cm),"Purchase, Gifts, Bequests, and Funds from various donors, by exchange, 1952",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/48911,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.11,false,true,49063,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Ganku,"Japanese, 1749–1838",,Ganku,Japanese,1749,1838,19th century,1800,1838,Hanging scroll; ink and color on silk,42 x 16 1/8 in. (106.7 x 41 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49063,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2003.265,false,true,65574,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Teisai Hokuba,"Japanese, 1771–1844",,Teisai Hokuba,Japanese,1771,1844,19th century,1800,1899,"Hanging scroll; ink, color, and gold on silk",37 5/16 x 12 7/16 in. (94.7 x 31.6 cm),"Purchase, Friends of Asian Art Gifts, 2003",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/65574,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.268.106,false,true,49023,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Nakabayashi Chikutō,"Japanese, 1776–1853",,Nakabayashi Chikutō,Japanese,1776,1853,19th century,1800,1853,Hanging scroll; ink on paper,Image: 69 1/2 x 34 in. (176.5 x 86.4 cm) Overall: 92 1/4 x 46 1/4 in. (234.3 x 117.5 cm),"The Harry G. C. Packard Collection of Asian Art, Gift of Harry G. C. Packard, and Purchase, Fletcher, Rogers, Harris Brisbane Dick, and Louis V. Bell Funds, Joseph Pulitzer Bequest, and The Annenberg Fund Inc. Gift, 1975",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49023,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.268.104,false,true,48997,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Okada Hankō,"Japanese, 1782–1846",,Okada Hankō,Japanese,1782,1846,19th century,1800,1846,Hanging scroll; ink and color on paper,Image: 68 1/2 x 18 7/8 in. (174 x 47.9 cm) Overall: 92 1/4 x 28 3/8in. (234.3 x 72.1cm),"The Harry G. C. Packard Collection of Asian Art, Gift of Harry G. C. Packard, and Purchase, Fletcher, Rogers, Harris Brisbane Dick, and Louis V. Bell Funds, Joseph Pulitzer Bequest, and The Annenberg Fund Inc. Gift, 1975",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/48997,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.268.54,false,true,45739,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Kano Tanshin Moromichi,"Japanese, 1785–1835",,Tanshin Moromichi Kano,Japanese,1785,1835,19th century,1800,1835,Hanging scroll; ink on silk,36 11/16 x 15 in. (93.2 x 38.1 cm),"The Harry G. C. Packard Collection of Asian Art, Gift of Harry G. C. Packard, and Purchase, Fletcher, Rogers, Harris Brisbane Dick, and Louis V. Bell Funds, Joseph Pulitzer Bequest, and The Annenberg Fund Inc. Gift, 1975",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45739,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1998.483.2,false,true,49821,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Ōtagaki Rengetsu,"Japanese, 1791–1871",,Ōtagaki Rengetsu,Japanese,1791,1871,19th century,1800,1868,"Hanging scroll; ink, color, and silver on paper",38 1/4 x 11 7/8 in. (97.2 x 30.2 cm),"Gift of Donald Keene, 1998",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49821,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.268.114,false,true,45379,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Watanabe Kazan,"Japanese, 1793–1841",,Watanabe Kazan,Japanese,1793,1841,19th century,1800,1841,Hanging scroll; ink and color on paper,29 1/8 x 17 in. (74 x 43.2 cm),"The Harry G. C. Packard Collection of Asian Art, Gift of Harry G. C. Packard, and Purchase, Fletcher, Rogers, Harris Brisbane Dick, and Louis V. Bell Funds, Joseph Pulitzer Bequest, and The Annenberg Fund Inc. Gift, 1975",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45379,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -59.137.1,false,true,48983,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Suzuki Kiitsu,"Japanese, 1796–1858",,Suzuki Kiitsu,Japanese,1796,1858,19th century,1800,1858,Hanging scroll; ink and color on silk,48 1/2 x 13 5/16 in. (123.2 x 33.8 cm),"Rogers Fund, 1959",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/48983,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.537,false,true,40352,Asian Art,Painting,,Japan,Edo period (1615–1868),,,,Artist,Attributed to,Utagawa Kuniyoshi,"Japanese, 1797–1861",,Utagawa Kuniyoshi,Japanese,1797,1861,19th century,1800,1861,Matted; ink and color on silk,16 1/2 x 23 1/2 in. (41.9 x 59.7 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/40352,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.268.118,false,true,49011,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Tsubaki Chinzan,"Japanese, 1801–1854",,Tsubaki Chinzan,Japanese,1801,1854,19th century,1800,1854,Hanging scroll; ink and color on silk,43 15/16 x 18 7/8 in. (111.6 x 48 cm),"The Harry G. C. Packard Collection of Asian Art, Gift of Harry G. C. Packard, and Purchase, Fletcher, Rogers, Harris Brisbane Dick, and Louis V. Bell Funds, Joseph Pulitzer Bequest, and The Annenberg Fund Inc. Gift, 1975",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49011,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.268.119,false,true,49012,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Tsubaki Chinzan,"Japanese, 1801–1854",,Tsubaki Chinzan,Japanese,1801,1854,19th century,1800,1854,Hanging scroll; ink and color on paper,65 7/8 x 33 1/4 in. (167.3 x 84.4 cm),"The Harry G. C. Packard Collection of Asian Art, Gift of Harry G. C. Packard, and Purchase, Fletcher, Rogers, Harris Brisbane Dick, and Louis V. Bell Funds, Joseph Pulitzer Bequest, and The Annenberg Fund Inc. Gift, 1975",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49012,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.14,false,true,49066,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,Attributed to,Gantoku,"Japanese, 1805–1859",,Gantoku,Japanese,1805,1859,19th century,1805,1859,Hanging scroll; ink on paper,38 1/2 x 12 in. (97.8 x 30.5 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49066,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.30,false,true,49054,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Nishiyama Hōen,"Japanese, 1807–1867",,Nishiyama Hōen,Japanese,1807,1867,19th century,1807,1867,Hanging scroll; ink and color on silk,39 5/16 x 16 1/8 in. (99.8 x 41 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49054,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -51.137.2,false,true,57317,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Shibata Zeshin,"Japanese, 1807–1891",,Shibata Zeshin,Japanese,1807,1891,19th century,1807,1868,Hanging scroll; ink on silk,39 1/8 x 14 in. (99.4 x 35.6 cm),"Purchase, Gifts, Bequests, and Funds from various donors, by exchange, 1951",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/57317,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -51.137.3,false,true,57318,Asian Art,Hanging scroll,三羽黒鳥図|Three Crows in Flight,Japan,Edo period (1615–1868),,,,Artist,,Shibata Zeshin,"Japanese, 1807–1891",,Shibata Zeshin,Japanese,1807,1891,19th century,1807,1891,Hanging scroll; ink on silk,Image: 28 7/8 × 6 3/4 in. (73.3 × 17.1 cm),"Purchase, Gifts, Bequests, and Funds from various donors, by exchange, 1951",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/57318,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -51.137.4,false,true,57319,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Shibata Zeshin,"Japanese, 1807–1891",,Shibata Zeshin,Japanese,1807,1891,19th century,1807,1891,Hanging scroll; ink on paper,Image: 13 5/8 x 21 3/8 in. (34.6 x 54.3 cm) Overall with knobs: 50 1/4 x 25 in. (127.6 x 63.5 cm),"Purchase, Gifts, Bequests, and Funds from various donors, by exchange, 1951",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/57319,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.282.3,false,true,49028,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Nakabayashi Chikkei,"Japanese, 1816–1867",,Nakabayashi Chikkei,Japanese,1816,1867,19th century,1816,1867,Hanging scroll; ink and color on silk,47 1/2 x 16 1/2 in. (120.7 x 41.9 cm),"Gift of Dr. and Mrs. Harold B. Bilsky, 1975",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49028,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.21,false,true,45188,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,Attributed to,Hanzan (Matsukawa),"Japanese, 1820–1882",,Hanzan,Japanese,1820,1882,19th century,1800,1882,Hanging scroll; ink and color on paper,12 1/2 x 16 13/16 in. (31.8 x 42.7 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45188,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2014.513,false,true,650682,Asian Art,Hanging scroll,神宮皇后|Empress Jingū,Japan,Edo period (1615–1868),,,,Artist,,Kōsai Hokushin,"Japanese, 1824–1876",,Kōsai Hokushin,Japanese,1824,1876,19th century,1800,1899,"Hanging scroll; ink, color, and gold on silk",Image: 28 7/8 × 12 15/16 in. (73.3 × 32.9 cm) Overall with mounting: 63 1/8 × 16 5/16 in. (160.3 × 41.4 cm) Overall with knobs: 63 1/8 × 18 in. (160.3 × 45.7 cm),"Purchase, Friends of Asian Art Gifts, 2014",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/650682,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -60.129,false,true,45809,Asian Art,Painting,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Kuniteru,"Japanese, 1830–1874",,Utagawa Kuniteru,Japanese,1830,1874,19th century,1800,1868,Matted painting; ink on paper,14 3/8 x 29 5/16 in. (36.5 x 74.5 cm),"Gift of Lincoln Kirstein, 1960",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45809,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.268.111,false,true,49002,Asian Art,Folding fan,,Japan,Edo period (1615–1868),,,,Artist,,Takahashi Sōhei,"Japanese, 1804?–?1835",,Takahashi Sōhei,Japanese,1804,1835,19th century,1804,1835,Folding fan; ink on paper,10 3/16 x 16 15/16 in. (25.8 x 43.1 cm),"The Harry G. C. Packard Collection of Asian Art, Gift of Harry G. C. Packard, and Purchase, Fletcher, Rogers, Harris Brisbane Dick, and Louis V. Bell Funds, Joseph Pulitzer Bequest, and The Annenberg Fund Inc. Gift, 1975",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49002,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.49.288,false,true,73565,Asian Art,Sketch,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa (Gountei) Sadahide,"Japanese, 1807–1878/79",,Utagawa (Goutei) Sadahide,Japanese,1807,1879,19th century,1807,1879,Preparatory drawing; ink on paper,Image: 9 1/2 x 12 in. (24.1 x 30.5 cm),"Bequest of William S. Lieberman, 2005",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/73565,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.45,false,true,48881,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Hokuga,"Japanese, active early 19th century",,Hokuga,Japanese,1800,1835,19th century,1800,1868,Hanging scroll; ink and color on silk,Image: 12 13/16 × 22 5/8 in. (32.5 × 57.5 cm) Overall with mounting: 50 1/8 × 28 3/8 in. (127.3 × 72 cm) Overall with knobs: 50 1/8 × 30 9/16 in. (127.3 × 77.6 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/48881,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -59.37,false,true,45813,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,19th century,1800,1849,Hanging scroll; ink and color on silk,Image: 33 11/16 × 13 9/16 in. (85.5 × 34.5 cm) Overall with mounting: 59 1/2 × 16 15/16 in. (151.2 × 43 cm) Overall with knobs: 59 1/2 × 19 3/16 in. (151.2 × 48.7 cm),"Seymour Fund, 1959",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45813,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.119.3,false,true,48889,Asian Art,Painting,,Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,19th century,1800,1849,Matted painting; ink on paper (wash drawing?),10 3/8 x 14 in. (26.4 x 35.6 cm),"Fletcher Fund, 1937",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/48889,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.28,false,true,45817,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,Attributed to,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,19th century,1800,1849,Hanging scroll; ink and color on silk,Image: 27 3/8 × 10 15/16 in. (69.5 × 27.8 cm) Overall with mounting: 62 5/8 × 16 15/16 in. (159 × 43 cm) Overall with knobs: 62 5/8 × 19 in. (159 × 48.2 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45817,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.29,false,true,48883,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,Attributed to,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,19th century,1800,1849,Hanging scroll; ink and color on paper,Image: 44 5/16 × 9 3/16 in. (112.5 × 23.4 cm) Overall with mounting: 80 9/16 × 10 11/16 in. (204.7 × 27.2 cm) Overall with knobs: 80 9/16 × 12 1/2 in. (204.7 × 31.8 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/48883,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.507,false,true,45820,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,19th century,1800,1849,Hanging scroll; ink and color on paper,Image: 30 7/8 × 9 5/8 in. (78.5 × 24.5 cm) Overall with mounting: 60 3/4 × 13 3/16 in. (154.3 × 33.5 cm) Overall with knobs: 60 3/4 × 15 1/8 in. (154.3 × 38.4 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45820,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.59.1–.102,false,true,40011,Asian Art,Album leaves,葛飾北斎筆 鶏と木材鶏図|Album of Sketches by Katsushika Hokusai and His Disciples,Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,19th century,1800,1868,Album of ninety-seven leaves; ink and color on paper,Each leaf: 15 1/2 x 10 1/2 in. (39.4 x 26.7 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/40011,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.60.1–.109,false,true,48886,Asian Art,Album leaves,,Japan,Edo period (1615–1868),,,,Artist,,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",and others,Katsushika Hokusai,Japanese,1760,1849,19th century,1800,1868,"Album of one hundred and nine leaves; ink on paper, ink and color on paper",15 3/8 x 10 11/16 in. (39.1 x 27.1 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/48886,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -08.148.4,false,true,40336,Asian Art,Painting,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,19th century,1797,1858,Matted painting; ink and color on silk (Ukiyo-e),13 15/16 x 21 7/16 in. (35.4 x 54.5 cm),"Gift of Francis Lathrop, 1908",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/40336,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -08.148.5,false,true,40337,Asian Art,Painting,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,19th century,1797,1858,Matted painting; ink and color on silk,13 15/16 x 21 7/16 in. (35.4 x 54.4 cm),"Gift of Francis Lathrop, 1908",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/40337,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -08.148.6,false,true,40338,Asian Art,Painting,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,19th century,1797,1858,Matted painting; ink and color on silk,13 15/16 x 21 7/16 in. (35.4 x 54.4 cm),"Gift of Francis Lathrop, 1908",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/40338,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -08.148.7,false,true,40339,Asian Art,Painting,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,19th century,1797,1858,Matted painting; ink and color on silk,13 15/16 x 21 7/16 in. (35.4 x 54.4 cm),"Gift of Francis Lathrop, 1908",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/40339,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -08.148.8,false,true,40340,Asian Art,Painting,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,19th century,1797,1858,Matted painting; ink and color on silk,8 1/4 x 12 3/16 in. (21.0 x 30.9 cm),"Gift of Francis Lathrop, 1908",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/40340,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -08.148.9,false,true,40341,Asian Art,Painting,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,19th century,1797,1858,Matted painting; ink and color on silk,8 1/4 x 12 3/16 in. (20.9 x 31 cm),"Gift of Francis Lathrop, 1908",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/40341,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.268.130,false,true,45425,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Hiroshige,"Japanese, Tokyo (Edo) 1797–1858 Tokyo (Edo)",,"Utagawa, Hiroshige",Japanese,1797,1858,19th century,1800,1858,Hanging scroll; ink and color on silk,16 3/8 x 22 5/8 in. (41.6 x 57.5 cm),"The Harry G. C. Packard Collection of Asian Art, Gift of Harry G. C. Packard, and Purchase, Fletcher, Rogers, Harris Brisbane Dick, and Louis V. Bell Funds, Joseph Pulitzer Bequest, and The Annenberg Fund Inc. Gift, 1975",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45425,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -52.160.2,false,true,57328,Asian Art,Hanging scroll,,Japan,Meiji period (1868–1912),,,,Artist,,Shibata Zeshin,"Japanese, 1807–1891",,Shibata Zeshin,Japanese,1807,1891,19th century,1807,1891,Hanging scroll; color on silk,13 3/4 x 5 in. (34.9 x 12.7 cm),"Purchase, Gifts, Bequests, and Funds from various donors, by exchange, 1952",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/57328,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -53.194.1,false,true,57337,Asian Art,Hanging scroll,,Japan,Meiji period (1868–1912),,,,Artist,,Shibata Zeshin,"Japanese, 1807–1891",,Shibata Zeshin,Japanese,1807,1891,19th century,1807,1891,Hanging scroll; black paint on silk,37 3/8 x 11 7/8 in. (30.2 x 94.9 cm),"Gift of Nathan V. Hammer, 1953",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/57337,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -55.161.2,false,true,57342,Asian Art,Hanging scroll,,Japan,Meiji period (1868–1912),,,,Artist,In the Style of,Shibata Zeshin,"Japanese, 1807–1891",,Shibata Zeshin,Japanese,1807,1891,19th century,1807,1891,Hanging scroll; ink and color on silk,14 3/8 x 10 3/4 in. (36.5 x 27.3 cm),"Purchase, Gifts, Bequests, and Funds from various donors, by exchange, 1955",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/57342,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -52.160.3a–q,false,true,57329,Asian Art,Album leaves,,Japan,Meiji period (1868–1912),,,,Artist,,Shibata Zeshin,"Japanese, 1807–1891",,Shibata Zeshin,Japanese,1807,1891,19th century,1807,1891,Album of seventeen sketches; watercolor on paper; mounted on natural silk,Image: 11 x 14 in. (27.9 x 35.6 cm),"Purchase, Gifts, Bequests, and Funds from various donors, by exchange, 1952",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/57329,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.282.1a–m,false,true,57163,Asian Art,Album,桃に鶴図|Flowers and Birds,Japan,Meiji period (1868–1912),,,,Artist,,Taki Katei,"Japanese, 1830–1901",,Taki Katei,Japanese,1830,1901,19th century,1800,1899,Album of twelve leaves; ink and color on silk,Each painting: 10 x 12 in. (25.4 x 30.5 cm),"Gift of Dr. and Mrs. Harold B. Bilsky, 1975",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/57163,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.119.1,false,true,50829,Asian Art,Handscroll,,Japan,Meiji period (1868–1912),,,,Artist,,Kawanabe Kyōsai,"Japanese, 1831–1889",,Kawanabe Kyōsai,Japanese,1831,1889,19th century,1831,1889,Handscroll; wash drawing,20 ft. 8 in. x 10 7/8 in. (629.9 x 27.6 cm),"Fletcher Fund, 1937",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/50829,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -13.67.26,false,true,58571,Asian Art,Inrō,印籠扇面蒔絵印籠|Inrō with Inrō and Fan,Japan,Edo period (1615–1868),,,,Artist,,Koma Kyūhaku V,"Japanese, died 1794",,Koma Kyūhaku V,Japanese,,1794,late 18th century,1767,1799,"Four cases; lacquered wood with gold, black, red lacquer takamaki-e, hiramaki-e, tgidashimaki-e on red lacquer ground; Netsuke: carved ivory; beans; Ojime: metal bead with insects",H. 2 15/16 in. (7.5 cm); W. 2 1/2in. (6.3 cm); D. 1 in. (2.5 cm),"Rogers Fund, 1913",,,,,,,,,,,,Inrō,,http://www.metmuseum.org/art/collection/search/58571,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -13.67.68,false,true,45568,Asian Art,Inrō,,Japan,Edo period (1615–1868),,,,Artist,,Tōyō,"Japanese, active ca. 1764–71",,Tōyō,Japanese,1764,1771,late 18th century,1767,1799,Silver shibuichi lacquer decorated with roiro (waxen) lacquer and gold sprinkled and polished hiramakie lacquer; Ojime: coral bead; Netsuke: metal zogan inlay pine tree,3 7/16 x 2 9/16 x 1 1/8 in. (8.7 x 6.5 x 2.9 cm),"Rogers Fund, 1913",,,,,,,,,,,,Inrō,,http://www.metmuseum.org/art/collection/search/45568,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -10.211.1271,false,true,60329,Asian Art,Netsuke,草花螳螂牙彫根付|Flowers and Grasses with a Praying-Mantis,Japan,Edo period (1615–1868),,,,Artist,Attributed to,Ryūsa,"Japanese, active late 18th century",,Ryūsa,Japanese,1767,1799,late 18th century,1767,1799,Ivory,H. 7/8 in. (2.2 cm); Diam. 2 1/8 in. (5.4 cm),"Gift of Mrs. Russell Sage, 1910",,,,,,,,,,,,Netsuke,,http://www.metmuseum.org/art/collection/search/60329,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2449,false,true,56879,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Suzuki Harunobu,"Japanese, 1725–1770",,Suzuki Harunobu,Japanese,1725,1770,late 18th century,1767,1799,"Polychrome woodblock print with embossing (karazuri), ink and color on paper",11 1/8 x 8 1/8 in. (28.3 x 20.6 cm) medium-size print (chu-ban),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56879,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1759,false,true,56065,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,late 18th century,1767,1799,Polychrome woodblock pillar print; ink and color on paper,Image: 28 x 5 3/16 in. (71.1 x 13.2 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56065,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1760,false,true,39721,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,late 18th century,1767,1799,Polychrome woodblock print; ink and color on paper,27 7/8 x 6 1/2 in. (70.8 x 16.5 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/39721,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1770,false,true,51092,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,late 18th century,1767,1799,Polychrome woodblock print (pillar print); ink and color on paper,H. 27 in. (68.6 cm); W. 4 5/8 in. (11.7 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/51092,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2781,false,true,39723,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,late 18th century,1767,1799,Polychrome woodblock print; ink and color on paper,10 1/2 x 7 1/2 in. (26.7 x 19.1 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/39723,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2782,false,true,39724,Asian Art,Print,Tatohe uta|Analogy,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunshō,"Japanese, 1726–1792",,Katsukawa Shunshō,Japanese,1726,1792,late 18th century,1767,1799,Polychrome woodblock print; ink and color on paper,10 1/8 x 7 3/4 in. (25.7 x 19.7 cm),"Henry L.Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/39724,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1621,false,true,55788,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kitao Shigemasa,"Japanese, 1739–1820",,Kitao Shigemasa,Japanese,1739,1820,late 18th century,1767,1799,Polychrome woodblock print; ink and color on paper,H. 8 7/8 in. (22.5 cm); W. 12 3/8 in. (31.4 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55788,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1126,false,true,55046,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyonaga,"Japanese, 1752–1815",,Torii Kiyonaga,Japanese,1752,1815,late 18th century,1767,1799,Polychrome woodblock pillar print; ink and color on paper,Image: 26 3/4 x 4 9/16 in. (67.9 x 11.6 cm),"Rogers Fund, 1919",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55046,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1210,false,true,55118,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyonaga,"Japanese, 1752–1815",,Torii Kiyonaga,Japanese,1752,1815,late 18th century,1767,1799,Polychrome woodblock print; ink and color on paper,H. 26 5/16 in. (66.8 cm); W. 4 3/4 in. (12.1 cm),"Rogers Fund, 1920",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55118,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1228,false,true,45039,Asian Art,Print,Shiokumi|Dance of the Beach Maidens,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyonaga,"Japanese, 1752–1815",,Torii Kiyonaga,Japanese,1752,1815,late 18th century,1767,1799,Polychrome woodblock print; ink and color on paper,Image: 25 3/4 x 4 9/16 in. (65.4 x 11.6 cm),"Rogers Fund, 1922",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45039,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1729,false,true,56040,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Torii Kiyonaga,"Japanese, 1752–1815",,Torii Kiyonaga,Japanese,1752,1815,late 18th century,1767,1799,Polychrome woodblock print; ink and color on paper,H. 27 7/16 in. (69.7 cm); W. 4 5/8 in. (11.7 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56040,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP939,false,true,44989,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kuwagata Keisai,"Japanese, 1764–1824",,Kuwagata Keisai,Japanese,1764,1824,late 18th century,1767,1799,Polychrome woodblock print; ink and color on paper,H. 14 1/2 in. (36.8 cm); W. 9 8/12 in. (24.6 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/44989,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1684,false,true,42697,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,late 18th century,1767,1799,Diptych of polychrome woodblock prints; ink and color on paper,H. 13 7/16 in. (34.1 cm); W. 17 5/8 in. (44.8 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/42697,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1095b,false,true,639384,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,late 18th century,1754,1806,Polychrome woodblock print; ink and color on paper,Image: 14 in. × 9 1/2 in. (35.6 × 24.1 cm),The Metropolitan Museum of Art,,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/639384,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1095c,false,true,639385,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,late 18th century,1754,1806,Polychrome woodblock print; ink and color on paper,Image: 14 1/2 × 9 3/8 in. (36.8 × 23.8 cm),The Metropolitan Museum of Art,,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/639385,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1095d,false,true,639386,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,late 18th century,1754,1806,Polychrome woodblock print; ink and color on paper,Image: 15 1/8 × 10 1/8 in. (38.4 × 25.7 cm),The Metropolitan Museum of Art,,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/639386,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1704,false,true,55945,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Isoda Koryūsai,"Japanese, 1735–ca. 1790",,Isoda Koryūsai,Japanese,1735,1790,late 18th century,1767,1799,Polychrome woodblock print; ink and color on paper,H. 27 3/16 in. (69.1 cm); W. 5 in. (12.7 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55945,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1711,false,true,45269,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Isoda Koryūsai,"Japanese, 1735–ca. 1790",,Isoda Koryūsai,Japanese,1735,1790,late 18th century,1767,1799,Polychrome woodblock print (hashira-e); ink and color on paper,H. 26 1/8 in. (66.4 cm); W. 4 1/2 in. (11.4 cm),"H. O. Havemeyer Collection; Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45269,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1800,false,true,45260,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunzan,"Japanese, active 1782–1798",,Katsukawa Shunzan,Japanese,1782,1798,late 18th century,1767,1799,Triptych of polychrome woodblock prints; ink and color on paper,A: H. 15 in. (38.1 cm); W. 10 1/4 in. (26 cm) B: H. 14 15/16 in. (37.9 cm); W. 10 1/4 in. (26 cm) C: H. 14 7/8 in. (37.9 cm); W. 10 1/4 in. (26 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45260,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2663,false,true,51999,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Katsukawa Shunchō,"Japanese, active ca. 1783–95",,Katsukawa Shunchō,Japanese,1775,1795,late 18th century,1767,1799,Triptych of polychrome woodblock prints; ink and color on paper,Image (each): 15 × 10 in. (38.1 × 25.4 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/51999,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1789,false,true,56091,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Rekisentei Eiri,"Japanese, active ca. 1789–1801",,Rekisentei Eiri,Japanese,1789,1801,late 18th century,1767,1799,Triptych of polychrome woodblock prints; ink and color on paper,Image (each): 14 9/16 x 9 3/4 in. (37 x 24.8 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56091,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1095a,false,true,55030,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Juka Sekijō,"Japanese, active ca. 1789–1817",,Juka Sekijō,Japanese,1789,1817,late 18th century,1754,1806,Polychrome woodblock print; ink and color on paper,Image: 13 7/8 in. × 9 in. (35.2 × 22.9 cm),The Metropolitan Museum of Art,,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55030,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.268.98a–p,false,true,45331,Asian Art,Album,,Japan,Edo period (1615–1868),,,,Artist,,Kuwayama Gyokushū,"Japanese, 1746–1799",,Kuwayama Gyokushū,Japanese,1746,1799,late 18th century,1767,1799,Album of fourteen paintings and one calligraphy; Ink and color on paper,Image (each): 8 7/8 x 5 7/16 in. (22.5 x 13.8 cm) Overall (album opened): 10 1/2 x 13 5/8 in. (26.7 x 34.6 cm),"The Harry G. C. Packard Collection of Asian Art, Gift of Harry G. C. Packard, and Purchase, Fletcher, Rogers, Harris Brisbane Dick, and Louis V. Bell Funds, Joseph Pulitzer Bequest, and The Annenberg Fund Inc. Gift, 1975",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45331,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1985.399.25,false,true,59677,Asian Art,Netsuke,,Japan,,,,,Artist,,Minkō,"Japanese, ca. 1735–1816",,Minkō,Japanese,1735,1816,early 19th century,1800,1833,Wood; brass and horn inlay,H. 1 3/16 in. (3 cm); W. 1 7/8 in. (4.8 cm),"Gift of Alvin H. Schechter, 1985",,,,,,,,,,,,Netsuke,,http://www.metmuseum.org/art/collection/search/59677,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.877,false,true,45504,Asian Art,Inrō,秋蔦蒔絵印籠|Inrō with Autumn Ivy,Japan,Edo period (1615–1868),,,,Artist,Attributed to,Koma Kyūhaku VI,"Japanese, died 1816",,Koma Kyūhaku VI,Japanese,,1816,early 19th century,1800,1833,"Four cases; lacquered wood with gold, silver, and color (iroko) togidashimaki-e on black lacquer ground Netsuke: ivory; folded letter decorated with paulownia and Genji incense symbols Ojime: oblong bead; gilt bronze with openwork design of autumn flowers",3 11/16 x 2 3/8 x 3/4 in. (9.3 x 6 x 1.9 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Inrō,,http://www.metmuseum.org/art/collection/search/45504,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2001.768.3, .4",false,true,64397,Asian Art,Folding screen,,Japan,Edo period (1615–1868),,,,Artist,,Mori Shūhō,"Japanese, 1738–1823",,Mori Shūhō,Japanese,1738,1823,early 19th century,1800,1833,"Pair of six-panel folding screens; ink, color, and gold flecks on gilded paper",Image (each screen): 5 ft. 2 3/16 in. x 11 ft. 8 15/16 in. (1.58 x 3.58 m),"Gift of Rosemarie and Leighton Longhi, 2001",,,,,,,,,,,,Screens,,http://www.metmuseum.org/art/collection/search/64397,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -13.67.22,false,true,53935,Asian Art,Inrō,,Japan,Edo period (1615–1868),,,,Artist,,Hara Yōyūsai,"Japanese, 1772–1845",,Hara Yōyūsai,Japanese,1772,1845,early 19th century,1800,1833,Case: powdered gold (maki-e) and colored lacquer on black lacquer with mother-of-pearl and gold inlays; Fastener (ojime): ivory carved with abstract design; Toggle (netsuke): ivory carved in the shape of a crab,H. 3 1/4 (8.3 cm); W. 2 in. ( 5.1 cm),"Rogers Fund, 1913",,,,,,,,,,,,Inrō,,http://www.metmuseum.org/art/collection/search/53935,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"54.69.1, .2",false,true,48982,Asian Art,Folding screen,朝顔図屏風|Morning Glories,Japan,Edo period (1615–1868),,,,Artist,,Suzuki Kiitsu,"Japanese, 1796–1858",,Suzuki Kiitsu,Japanese,1796,1858,early 19th century,1800,1833,"Pair of six-panel folding screens; ink, color, and gold leaf on paper",Image (each screen): 70 3/16 x 12 ft. 5 1/2 in. (178.3 x 379.7 cm),"Seymour Fund, 1954",,,,,,,,,,,,Screens,,http://www.metmuseum.org/art/collection/search/48982,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1715,false,true,45251,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Kitao Shigemasa,"Japanese, 1739–1820",,Kitao Shigemasa,Japanese,1739,1820,early 19th century,1800,1833,Polychrome woodblock print; ink and color on paper,H. 8 3/8 in. (21.3 cm); W. 14 3/4 in. (37.5 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/45251,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1746,false,true,56053,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Toyokuni I,"Japanese, 1769–1825",,Utagawa Toyokuni I,Japanese,1769,1825,early 19th century,1800,1833,Polychrome woodblock print; ink and color on paper,H. 14 11/16 in. (37.3 cm); W. 9 7/8 in. (25.1 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56053,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1921,false,true,54460,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Totoya Hokkei,"Japanese, 1780–1850",,Totoya Hokkei,Japanese,1780,1850,early 19th century,1800,1833,Polychrome woodblock print (surimono); ink and color on paper,7 3/4 x 6 5/8 in. (19.7 x 16.8 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54460,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1957,false,true,54529,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Totoya Hokkei,"Japanese, 1780–1850",,Totoya Hokkei,Japanese,1780,1850,early 19th century,1800,1833,Polychrome woodblock print (surimono); ink and color on paper,8 1/4 x 5 1/8 in. (21 x 13 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54529,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2055,false,true,54831,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Totoya Hokkei,"Japanese, 1780–1850",,Totoya Hokkei,Japanese,1780,1850,early 19th century,1800,1833,Polychrome woodblock print (surimono); ink and color on paper,5 7/16 x 11 1/8 in. (13.8 x 28.3 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54831,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2132,false,true,54987,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist,,Yosai,"Japanese, 1788–1878",,Yosai,Japanese,1788,1878,early 19th century,1800,1833,Polychrome woodblock print (surimono); ink and color on paper,5 9/16 x 7 7/16 in. (14.1 x 18.9 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54987,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1683,false,true,37343,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,early 19th century,1800,1833,Triptych of polychrome woodblock prints; ink and color on paper,Overall: 15 1/4 x 29 1/2 in. (38.7 x 74.9 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/37343,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2733,false,true,56794,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Kitagawa Utamaro,"Japanese, 1753?–1806",,Kitagawa Utamaro,Japanese,1753,1806,early 19th century,1800,1833,Right sheet of a triptych of polychrome woodblock prints; ink and color on paper,Image (oban triptych): 15 1/8 x 10 1/8 in. (38.4 x 25.7 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56794,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1102,false,true,54331,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Yashima Gakutei,"Japanese, 1786?–1868",,Yashima Gakutei,Japanese,1786,1868,early 19th century,1800,1833,Diptych of polychrome woodblock prints (surimono); ink and color on paper,a) 8 7/16 x 7 3/8 in. (21.4 x 18.7 cm) b): 8 5/16 x 7 3/8 in. (21.1 x 18.7 cm),"Rogers Fund, 1919",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54331,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1920,false,true,54459,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Yashima Gakutei,"Japanese, 1786?–1868",,Yashima Gakutei,Japanese,1786,1868,early 19th century,1800,1833,Polychrome woodblock print (surimono); ink and color on paper,7 11/16 x 6 1/2 in. (19.5 x 16.5 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54459,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP3493,false,true,55720,Asian Art,Print,Seishu Futami ga Ura|View of Futami Beach at Ise,Japan,Edo period (1615–1868),,,,Artist,,Shotei Hokuju,"Japanese, active 1790–1820",,Shotei Hokuju,Japanese,1790,1820,early 19th century,1800,1833,Polychrome woodblock print; ink and color on paper,Oban 10 x 15 in. (25.4 x 38.1 cm),"Gift of Cole J. Younger, 1975",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55720,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP1827,false,true,56113,Asian Art,Print,,Japan,Edo period (1615–1868),,,,Artist,,Koikawa Harumasa,"Japanese, active 1800–1820",,Koikawa Harumasa,Japanese,1800,1820,early 19th century,1800,1833,Polychrome woodblock print; ink and color on paper,H. 23 1/2 in. (59.7 cm); W. 4 9/16 in. (11.6 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/56113,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -93.1.20,false,true,62617,Asian Art,Teabowl,,Japan,,,,,Artist,,Kenzan III,"Japanese, 1767–1810",,Kenzan III,Japanese,1767,1810,early 19th century,1800,1833,"Clay, ribbed and covered a mottled glaze (Kenzan ware)",H. 3 in. (7.6 cm),"Gift of Mr. and Mrs. Samuel Colman, 1893",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/62617,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -17.118.67,false,true,62886,Asian Art,Teabowl,,Japan,Edo period (1615–1868),,,,Artist,,Raku Ryōnyū,"Japanese, 1756–1834",,Raku Ryonyu,Japanese,1756,1834,early 19th century,1800,1833,Clay covered with a black glaze (Raku ware),H. 3 3/8 in. (8.6 cm); Diam. 4 in. (10.2 cm),"Rogers Fund, 1917",,,,,,,,,,,,Ceramics,,http://www.metmuseum.org/art/collection/search/62886,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.46,false,true,39725,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,Attributed to,Torii Kiyonaga,"Japanese, 1752–1815",,Torii Kiyonaga,Japanese,1752,1815,early 19th century,1800,1815,Hanging scroll; ink and color on silk,Image: 15 3/4 × 23 1/4 in. (40 × 59.1 cm) Overall with mounting: 51 1/8 × 28 1/4 in. (129.9 × 71.8 cm) Overall with knobs: 51 1/8 × 30 3/8 in. (129.9 × 77.2 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/39725,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.43,false,true,45806,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Toyohiro,"Japanese, 1763–1828",,Utagawa Toyohiro,Japanese,1763,1828,early 19th century,1800,1828,Hanging scroll; ink and color on silk,31 5/16 x 10 1/4 in. (79.6 x 26 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45806,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.268.129,false,true,45808,Asian Art,Hanging scroll,夕顔棚下納涼図|Enjoying the Evening Cool under a Gourd Trellis,Japan,Edo period (1615–1868),,,,Artist,,Utagawa Toyohiro,"Japanese, 1763–1828",,Utagawa Toyohiro,Japanese,1763,1828,early 19th century,1800,1828,Hanging scroll; ink and color on paper,Image: 33 1/4 x 11 in. (84.5 x 27.9 cm) Overall with knobs: 65 1/8 x 16 1/2 in. (165.4 x 41.9 cm),"The Harry G. C. Packard Collection of Asian Art, Gift of Harry G. C. Packard, and Purchase, Fletcher, Rogers, Harris Brisbane Dick, and Louis V. Bell Funds, Joseph Pulitzer Bequest, and The Annenberg Fund Inc. Gift, 1975",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45808,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1977.445,false,true,49005,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Tani Bunchō,"Japanese, 1763–1840",,Tani Bunchō,Japanese,1763,1840,early 19th century,1800,1833,Hanging scroll; ink and color on silk,64 15/16 x 44 3/4 in. (165 x 113.6 cm),"Gift of Mr. and Mrs. Theodore R. Conant, 1977",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49005,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.26,false,true,42314,Asian Art,Hanging scroll,三味線持つ美人図|Female Entertainer with Shamisen,Japan,Edo period (1615–1868),,,,Artist,Attributed to,Teisai Hokuba,"Japanese, 1771–1844",,Teisai Hokuba,Japanese,1771,1844,early 19th century,1800,1828,Hanging scroll; ink and color on paper,31 1/8 x 11 1/4 in. (79.1 x 28.6 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/42314,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.43,false,true,72603,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Tōshū,"Japanese, active ca. 1800",,Tōshū,Japanese,1790,1810,early 19th century,1800,1833,Hanging scroll; ink and color on silk,Image: 39 7/8 x 11 1/8 in. (101.3 x 28.3 cm) Overall with mounting: 62 1/2 x 15 1/2 in. (158.8 x 39.4 cm) Overall with rollers: 62 1/2 x 18 1/8 in. (158.8 x 46 cm),"Gift of Jack Jacoby, 2005",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/72603,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.197,false,true,58882,Asian Art,Inrō,,Japan,,,,,Artist,,Kano Seisen’in,1775–1828,,Kano Seisen’in,Japanese,1775,1828,18th–19th century,1700,1899,"Lacquer, roiro, yamimakie, black hiramakie, takamakie; Interior: gyobu nashiji and fundame",3 11/16 x 1 7/8 x 1 1/16 in. (9.4 x 4.8 x 2.7 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Inrō,,http://www.metmuseum.org/art/collection/search/58882,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.795,false,true,58704,Asian Art,Inrō,,Japan,,,,,Artist,,Hanabusa Itchō,"Japanese, 1652–1724",,Hanabusa Itchō,Japanese,1652,1724,18th–19th century,1700,1899,"Lacquer, roiro, gold, silver, brown and red hiramakie, various inlay; Interior: nashiji and fundame",4 1/8 x 1 3/4 x 15/16 in. (10.4 x 4.4 x 2.4 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Inrō,,http://www.metmuseum.org/art/collection/search/58704,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.228,false,true,58916,Asian Art,Inrō,,Japan,,,,,Artist,Attributed to,Ogawa Haritsu (Ritsuō),"Japanese, 1663–1747",,Ritsuō,Japanese,1663,1747,18th–19th century,1700,1899,"Lacquer, roiro, gold and silver hiramakie, togidashi, kimpun, ivory, horn inlay; Interior: fundame",3 3/8 x 2 3/16 x 7/8 in. (8.5 x 5.5 x 2.3 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Inrō,,http://www.metmuseum.org/art/collection/search/58916,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.191,false,true,58878,Asian Art,Inrō,,Japan,,,,,Artist,,Maruyama Ōkyo,"Japanese, 1733–1795",,Maruyama Ōkyo,Japanese,1733,1795,18th–19th century,1700,1899,"Lacquer, roiro, gold and silver togidashi, mura nashiji; Interior: roiro and fundame",2 15/16 x 2 7/16 x 1 in. (7.4 x 6.2 x 2.6 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Inrō,,http://www.metmuseum.org/art/collection/search/58878,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -49.46.1,false,true,53814,Asian Art,Printer's woodblock,,Japan,,,,,Artist,Original print designed by,Utagawa Toyokuni I,"Japanese, 1769–1825",,Utagawa Toyokuni I,Japanese,1769,1825,18th–19th century,1700,1899,,15 1/2 x 10 1/2 in. (39.4 x 26.7 cm),"Gift of Mrs. Howard Mansfield, 1949",,,,,,,,,,,,Woodblocks,,http://www.metmuseum.org/art/collection/search/53814,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.222,false,true,58910,Asian Art,Inrō,,Japan,,,,,Artist,,Tōyō,"Japanese, active ca. 1764–71",,Tōyō,Japanese,1764,1771,18th–19th century,1700,1899,"Lacquer, silver brown ground, incised; Interior: gyobu nashiji and fundame",4 3/16 x 1 15/16 x 1 5/16 in. (10.7 x 4.9 x 3.4 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Inrō,,http://www.metmuseum.org/art/collection/search/58910,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -91.1.701,false,true,58478,Asian Art,Inrō,,Japan,Edo period (1615–1868),,,,Artist,In the style of,Ogata Kōrin,"Japanese, 1658–1716",,Kōrin,Japanese,1658,1716,18th–19th century,1700,1899,"Gold lacquer with dark gray ishime, gold, red, black, and silver makie, pewter, and mother-of-pearl; Ojime: bead with autumn wild flowers; Netsuke: rat eating peach; boxwood",H. 2 3/8 in. (6 cm); W. 2 1/2 in. (6.4 cm); D. 3/4 in. (1.9 cm),"Edward C. Moore Collection, Bequest of Edward C. Moore, 1891",,,,,,,,,,,,Inrō,,http://www.metmuseum.org/art/collection/search/58478,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.187,false,true,58874,Asian Art,Inrō,,Japan,Edo period (1615–1868),,,,Artist,,Mochizuki Hanzan,"Japanese, 1743–?1790",,Mochizuki Hanzan,Japanese,1743,1790,18th–19th century,1700,1899,"Wood, brushed wood ground, gold, red and green hiramakie, takamakie, raden; Interior: nashiji and fundame",3 x 2 5/8 x 7/8 in. (7.6 x 6.7 x 2.3 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Inrō,,http://www.metmuseum.org/art/collection/search/58874,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2204,false,true,53970,Asian Art,Print,,Japan,,,,,Artist,,Haikairyō Henpuku,"Japanese, 1744–1830",,Haikairyō Henpuku,Japanese,1744,1830,18th–19th century,1700,1899,Polychrome woodblock print (surimono); ink and color on paper,3 7/8 x 5 7/16 in. (9.8 x 13.8 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/53970,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2175,false,true,55089,Asian Art,Woodblock print,,Japan,,,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",,Kubo Shunman,Japanese,1757,1820,18th–19th century,1700,1899,Polychrome woodblock print (surimono); ink and color on paper,8 x 7 3/16 in. (20.3 x 18.3 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55089,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2221,false,true,53987,Asian Art,Print,,Japan,,,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",,Kubo Shunman,Japanese,1757,1820,18th–19th century,1700,1900,Polychrome woodblock print (surimono); ink and color on paper,5 1/4 x 7 7/8 in. (13.3 x 20 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/53987,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2205,false,true,53971,Asian Art,Print,,Japan,,,,,Artist,,Yanagawa Shigenobu,"Japanese, 1787–1832",,Yanagawa Shigenobu,Japanese,1787,1832,18th–19th century,1700,1899,Polychrome woodblock print (surimono); ink and color on paper,8 9/16 x 7 5/16 in. (21.7 x 18.6 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/53971,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2176,false,true,55090,Asian Art,Print,,Japan,,,,,Artist,,Yanagawa Shigemasa,"Japanese, 18th–19th century",,Yanagawa Shigemasa,Japanese,1700,1899,18th–19th century,1700,1899,Polychrome woodblock print (surimono); ink and color on paper,8 1/16 x 3 5/8 in. (20.5 x 9.2 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55090,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2187,false,true,55103,Asian Art,Woodblock print,,Japan,,,,,Artist,,Reisai,"Japanese, 18th–19th century",,Reisai,Japanese,1700,1899,18th–19th century,1700,1899,Polychrome woodblock print (surimono); ink and color on paper,7 x 6 1/2 in. (17.8 x 16.5 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/55103,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP2827,false,true,39584,Asian Art,Print,,Japan,Edo (1615–1868),,,,Artist,,Utagawa Toyokuni I,"Japanese, 1769–1825",,Utagawa Toyokuni I,Japanese,1769,1825,18th–19th century,1700,1899,Polychrome woodblock print; ink and color on paper,14-1/8 x 9-9/16 in. (35.9 x 24.3 cm),"Henry L. Phillips Collection, Bequest of Henry L. Phillips, 1939",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/39584,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -57.70.2,false,true,57247,Asian Art,Hanging scroll,,Japan,,,,,Artist,Attributed to,Chōbunsai Eishi,"Japanese, 1756–1829",,Chōbunsai Eishi,Japanese,1756,1829,18th–19th century,1756,1829,Hanging scroll; ink and color on paper,36 1/2 x 15 3/4 in. (92.7 x 40 cm),"Gift of Edward M. Bratter, 1957",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/57247,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.32,false,true,45735,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,Attributed to,Kano Seisen’in,1775–1828,,Kano Seisen’in,Japanese,1775,1828,18th–19th century,1775,1828,Hanging scroll; ink and color on silk,38 x 14 1/4 in. (96.5 x 36.2 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45735,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.17,false,true,49058,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Mori Sosen,"Japanese, 1747–1821",,Mori Sosen,Japanese,1747,1821,18th–19th century,1747,1821,Hanging scroll; ink and color on silk,26 3/4 x 6 1/4 in. (67.9 x 15.9 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49058,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.18,false,true,49059,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Mori Sosen,"Japanese, 1747–1821",,Mori Sosen,Japanese,1747,1821,18th–19th century,1747,1821,Hanging scroll; ink and color on silk,26 3/4 x 6 1/4 in. (67.9 x 15.9 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/49059,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.268.128,false,true,45798,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Kubo Shunman,"Japanese, 1757–1820",,Kubo Shunman,Japanese,1757,1820,18th–19th century,1757,1820,Hanging scroll; ink and color on paper,36 3/8 x 13 1/16 in. (92.4 x 33.2 cm),"The Harry G. C. Packard Collection of Asian Art, Gift of Harry G. C. Packard, and Purchase, Fletcher, Rogers, Harris Brisbane Dick, and Louis V. Bell Funds, Joseph Pulitzer Bequest, and The Annenberg Fund Inc. Gift, 1975",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45798,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.268.100,false,true,44895,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Kushiro Unsen,"Japanese, 1759–1811",,Kushiro Unsen,Japanese,1759,1811,18th–19th century,1759,1811,Hanging scroll; ink and color on satin,Image: 58 7/16 x 13 1/4 in. (148.4 x 33.7 cm) Overall: 82 1/2 x 20 3/4in. (209.6 x 52.7cm),"The Harry G. C. Packard Collection of Asian Art, Gift of Harry G. C. Packard, and Purchase, Fletcher, Rogers, Harris Brisbane Dick, and Louis V. Bell Funds, Joseph Pulitzer Bequest, and The Annenberg Fund Inc. Gift, 1975",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/44895,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.76.33,false,true,45801,Asian Art,Hanging scroll,,Japan,Edo period (1615–1868),,,,Artist,,Yanagi Buncho II,active ca. 1764–1801,,Yanagi Buncho II,Japanese,1764,1801,18th–19th century,1764,1801,Hanging scroll; ink and color on silk,17 5/8 x 20 1/2 in. (44.8 x 52.1 cm),"Charles Stewart Smith Collection, Gift of Mrs. Charles Stewart Smith, Charles Stewart Smith Jr., and Howard Caswell Smith, in memory of Charles Stewart Smith, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/45801,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -56.121.1,false,true,48890,Asian Art,Painting,,Japan,Edo period (1615–1868),,,,Artist,Attributed to,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,18th–19th century,1760,1849,Unmounted painting; ink on paper,10 3/4 x 6 7/8 in. (27.3 x 17.5 cm),"Gift of Annette Young, in memory of her brother, Innis Young, 1956",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/48890,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -56.121.2,false,true,57252,Asian Art,Drawing,,Japan,Edo period (1615–1868),,,,Artist,Attributed to,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,18th–19th century,1760,1849,Ink on paper,9 7/16 x 12 3/8 in. (24 x 31.4 cm),"Gift of Annette Young, in memory of her brother, Innis Young, 1956",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/57252,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -56.121.3,false,true,57253,Asian Art,Drawing,,Japan,Edo period (1615–1868),,,,Artist,Attributed to,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,18th–19th century,1760,1849,Ink on paper,6 7/8 x 9 3/8 in. (17.5 x 23.8 cm),"Gift of Annette Young, in memory of her brother, Innis Young, 1956",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/57253,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -56.121.4,false,true,57254,Asian Art,Drawing,,Japan,Edo period (1615–1868),,,,Artist,Attributed to,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,18th–19th century,1760,1849,Ink on paper,9 1/2 x 12 7/8 in. (24.1 x 32.7 cm),"Gift of Annette Young, in memory of her brother, Innis Young, 1956",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/57254,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -56.121.5,false,true,57255,Asian Art,Drawing,,Japan,Edo period (1615–1868),,,,Artist,Attributed to,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,18th–19th century,1760,1849,Ink and color on paper,9 3/8 x 13 15/16 in. (23.8 x 35.4 cm),"Gift of Annette Young, in memory of her brother, Innis Young, 1956",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/57255,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -56.121.6,false,true,57256,Asian Art,Drawing,,Japan,Edo period (1615–1868),,,,Artist,Attributed to,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,18th–19th century,1760,1849,Ink on paper,10 7/8 x 7 9/16 in. (27.6 x 19.2 cm),"Gift of Annette Young, in memory of her brother, Innis Young, 1956",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/57256,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -56.121.7,false,true,57257,Asian Art,Drawing,,Japan,Edo period (1615–1868),,,,Artist,Attributed to,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,18th–19th century,1760,1849,Ink on paper,13 1/16 x 9 3/4 in. (33.2 x 24.8 cm),"Gift of Annette Young, in memory of her brother, Innis Young, 1956",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/57257,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -56.121.8,false,true,57258,Asian Art,Drawing,,Japan,Edo period (1615–1868),,,,Artist,Attributed to,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,18th–19th century,1760,1849,Ink and color on paper,9 5/8 x 12 3/8 in. (24.4 x 31.4 cm),"Gift of Annette Young, in memory of her brother, Innis Young, 1956",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/57258,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -56.121.9,false,true,57259,Asian Art,Drawing,,Japan,Edo period (1615–1868),,,,Artist,Attributed to,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,18th–19th century,1760,1849,Ink and color on paper,13 x 9 1/4 in. (33 x 23.5 cm),"Gift of Annette Young, in memory of her brother, Innis Young, 1956",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/57259,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -56.121.10,false,true,57260,Asian Art,Drawing,,Japan,Edo period (1615–1868),,,,Artist,Attributed to,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,18th–19th century,1760,1849,Ink on paper,18 1/2 x 12 3/16 in. (47 x 31 cm),"Gift of Annette Young, in memory of her brother, Innis Young, 1956",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/57260,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -56.121.11,false,true,57261,Asian Art,Drawing,,Japan,Edo period (1615–1868),,,,Artist,Attributed to,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,18th–19th century,1760,1849,Ink on paper,9 7/8 x 7 7/16 in. (25.1 x 18.9 cm),"Gift of Annette Young, in memory of her brother, Innis Young, 1956",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/57261,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -56.121.12,false,true,57262,Asian Art,Drawing,,Japan,Edo period (1615–1868),,,,Artist,Attributed to,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,18th–19th century,1760,1849,Ink on paper,11 3/8 x 8 3/8 in. (28.9 x 21.3 cm),"Gift of Annette Young, in memory of her brother, Innis Young, 1956",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/57262,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -56.121.13,false,true,57263,Asian Art,Drawing,,Japan,Edo period (1615–1868),,,,Artist,Attributed to,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,18th–19th century,1760,1849,Ink on paper,8 1/4 x 10 7/8 in. (21 x 27.6 cm),"Gift of Annette Young, in memory of her brother, Innis Young, 1956",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/57263,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -56.121.14,false,true,57264,Asian Art,Drawings,,Japan,Edo period (1615–1868),,,,Artist,Attributed to,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,18th–19th century,1760,1849,Ink and color on paper,Turtle: 5 x 9 5/16 in. (12.7 x 23.7 cm) Other sketch. 5 3/8 x 7 7/8 in. (13.7 x 20 cm),"Gift of Annette Young, in memory of her brother, Innis Young, 1956",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/57264,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -56.121.15,false,true,57265,Asian Art,Drawing,,Japan,Edo period (1615–1868),,,,Artist,School of,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,18th–19th century,1760,1849,Ink on paper,9 5/8 x 13 1/8 in. (24.4 x 33.3 cm),"Gift of Annette Young, in memory of her brother, Innis Young, 1956",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/57265,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -56.121.16,false,true,57266,Asian Art,Drawing,,Japan,Edo period (1615–1868),,,,Artist,School of,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,18th–19th century,1760,1849,Ink and red color on paper,16 13/16 x 10 1/2 in. (42.7 x 26.7 cm),"Gift of Annette Young, in memory of her brother, Innis Young, 1956",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/57266,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -56.121.17,false,true,57267,Asian Art,Drawing,,Japan,Edo period (1615–1868),,,,Artist,School of,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,18th–19th century,1760,1849,Ink on paper,15 3/8 x 10 13/16 in. (39.1 x 27.5 cm),"Gift of Annette Young, in memory of her brother, Innis Young, 1956",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/57267,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -56.121.18,false,true,57268,Asian Art,Drawing,,Japan,Edo period (1615–1868),,,,Artist,School of,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,18th–19th century,1760,1849,Ink and color on brown prepared paper,11 x 16 in. (27.9 x 40.6 cm),"Gift of Annette Young, in memory of her brother, Innis Young, 1956",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/57268,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -56.121.19,false,true,57269,Asian Art,Drawing,,Japan,Edo period (1615–1868),,,,Artist,School of,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,18th–19th century,1760,1849,Ink on paper,10 11/16 x 7 1/8 in. (27.1 x 18.1 cm),"Gift of Annette Young, in memory of her brother, Innis Young, 1956",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/57269,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -56.121.20,false,true,57270,Asian Art,Drawing,,Japan,Edo period (1615–1868),,,,Artist,School of,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,18th–19th century,1760,1849,Ink on paper,12 15/16 x 9 1/2 in. (32.9 x 24.1 cm),"Gift of Annette Young, in memory of her brother, Innis Young, 1956",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/57270,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -56.121.21,false,true,57271,Asian Art,Drawing,,Japan,Edo period (1615–1868),,,,Artist,School of,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,18th–19th century,1760,1849,Ink on paper,12 7/8 x 9 1/2 in. (32.7 x 24.1 cm),"Gift of Annette Young, in memory of her brother, Innis Young, 1956",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/57271,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -56.121.22,false,true,57272,Asian Art,Drawing,,Japan,Edo period (1615–1868),,,,Artist,School of,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,18th–19th century,1700,1899,Ink on paper,9 3/4 x 13 5/8 in. (24.8 x 34.6 cm),"Gift of Annette Young, in memory of her brother, Innis Young, 1956",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/57272,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -56.121.23,false,true,57273,Asian Art,Drawing,,Japan,Edo period (1615–1868),,,,Artist,School of,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,18th–19th century,1700,1899,Ink and color on paper,11 x 16 1/2 in. (27.9 x 41.9 cm),"Gift of Annette Young, in memory of her brother, Innis Young, 1956",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/57273,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -56.121.24,false,true,57274,Asian Art,Drawing,,Japan,Edo period (1615–1868),,,,Artist,School of,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,18th–19th century,1700,1899,Ink on paper,11 3/8 x 5 7/8 in. (28.9 x 14.9 cm),"Gift of Annette Young, in memory of her brother, Innis Young, 1956",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/57274,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -56.121.25,false,true,57275,Asian Art,Drawing,,Japan,Edo period (1615–1868),,,,Artist,School of,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,18th–19th century,1700,1899,Ink and color on paper,9 7/16 x 13 1/2 in. (24 x 34.3 cm),"Gift of Annette Young, in memory of her brother, Innis Young, 1956",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/57275,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -56.121.26,false,true,57276,Asian Art,Drawing,,Japan,Edo period (1615–1868),,,,Artist,School of,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,18th–19th century,1700,1899,Ink on paper,12 5/16 x 9 3/16 in. (31.3 x 23.3 cm),"Gift of Annette Young, in memory of her brother, Innis Young, 1956",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/57276,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -56.121.27,false,true,57277,Asian Art,Drawing,,Japan,Edo period (1615–1868),,,,Artist,School of,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,18th–19th century,1700,1899,Ink and color on paper,11 3/16 x 8 3/4 in. (28.4 x 22.2 cm),"Gift of Annette Young, in memory of her brother, Innis Young, 1956",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/57277,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -56.121.28,false,true,57278,Asian Art,Drawing,,Japan,Edo period (1615–1868),,,,Artist,School of,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,18th–19th century,1700,1899,Ink and color on paper,11 x 16 1/2 in. (27.9 x 41.9 cm),"Gift of Annette Young, in memory of her brother, Innis Young, 1956",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/57278,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -56.121.29,false,true,57279,Asian Art,Drawing,,Japan,Edo period (1615–1868),,,,Artist,School of,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,18th–19th century,1700,1899,Ink on paper,16 1/4 x 10 1/4 in. (41.3 x 26 cm),"Gift of Annette Young, in memory of her brother, Innis Young, 1956",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/57279,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -56.121.30,false,true,57280,Asian Art,Drawing,,Japan,Edo period (1615–1868),,,,Artist,School of,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,18th–19th century,1700,1899,Ink on paper,16 1/4 x 10 3/8 in. (41.3 x 26.4 cm),"Gift of Annette Young, in memory of her brother, Innis Young, 1956",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/57280,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -56.121.31,false,true,57281,Asian Art,Drawing,,Japan,Edo period (1615–1868),,,,Artist,School of,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,18th–19th century,1700,1899,Ink on paper,9 9/16 x 5 1/2 in. (24.3 x 14 cm),"Gift of Annette Young, in memory of her brother, Innis Young, 1956",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/57281,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -56.121.32,false,true,57282,Asian Art,Drawing,,Japan,Edo period (1615–1868),,,,Artist,School of,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,18th–19th century,1700,1899,Ink and color on paper,16 1/8 x 9 5/16 in. (41 x 23.7 cm),"Gift of Annette Young, in memory of her brother, Innis Young, 1956",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/57282,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -56.121.33,false,true,57283,Asian Art,Drawing,,Japan,Edo period (1615–1868),,,,Artist,School of,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,18th–19th century,1700,1899,Ink and color on paper,10 11/16 x 7 in. (27.1 x 17.8 cm),"Gift of Annette Young, in memory of her brother, Innis Young, 1956",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/57283,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -56.121.34,false,true,57284,Asian Art,Drawing,,Japan,Edo period (1615–1868),,,,Artist,School of,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,18th–19th century,1700,1899,Ink on paper,9 3/4 x 7 3/8 in. (24.8 x 18.7 cm),"Gift of Annette Young, in memory of her brother, Innis Young, 1956",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/57284,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -56.121.35,false,true,57285,Asian Art,Drawing,,Japan,Edo period (1615–1868),,,,Artist,School of,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,18th–19th century,1700,1899,Ink on paper,12 7/8 x 9 7/8 in. (32.7 x 25.1 cm),"Gift of Annette Young, in memory of her brother, Innis Young, 1956",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/57285,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -56.121.36,false,true,57286,Asian Art,Painting,,Japan,Edo period (1615–1868),,,,Artist,School of,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,18th–19th century,1700,1899,Ink and color on paper,7 9/16 x 10 1/4 in. (19.2 x 26 cm),"Gift of Annette Young, in memory of her brother, Innis Young, 1956",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/57286,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -56.121.37,false,true,57287,Asian Art,Drawing,,Japan,Edo period (1615–1868),,,,Artist,School of,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,18th–19th century,1700,1899,Ink on paper,7 9/16 x 5 7/8 in. (19.2 x 14.9 cm),"Gift of Annette Young, in memory of her brother, Innis Young, 1956",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/57287,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -56.121.38,false,true,57288,Asian Art,Drawing,,Japan,Edo period (1615–1868),,,,Artist,School of,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,18th–19th century,1700,1899,Ink on paper,10 3/4 x 7 1/8 in. (27.3 x 18.1 cm),"Gift of Annette Young, in memory of her brother, Innis Young, 1956",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/57288,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -56.121.39,false,true,57289,Asian Art,Drawing,,Japan,Edo period (1615–1868),,,,Artist,School of,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,18th–19th century,1700,1899,Ink on paper,12 1/8 x 5 5/8 in. (30.8 x 14.3 cm),"Gift of Annette Young, in memory of her brother, Innis Young, 1956",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/57289,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -56.121.40,false,true,57290,Asian Art,Painting,,Japan,Edo period (1615–1868),,,,Artist,School of,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,18th–19th century,1700,1899,Ink and color on paper,10 5/8 x 15 3/8 in. (27 x 39.1 cm),"Gift of Annette Young, in memory of her brother, Innis Young, 1956",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/57290,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -56.121.41,false,true,57291,Asian Art,Painting,,Japan,Edo period (1615–1868),,,,Artist,School of,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,18th–19th century,1700,1899,Two paintings mounted together; ink on paper,Left painting: 7 11/16 x 5 1/2 in. (19.5 x 14 cm) Right painting: 7 3/4 x 5 1/2 in. (19.7 x 14 cm),"Gift of Annette Young, in memory of her brother, Innis Young, 1956",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/57291,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -56.121.42,false,true,57292,Asian Art,Painting,,Japan,Edo period (1615–1868),,,,Artist,School of,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,18th–19th century,1700,1899,Ink and color on paper,Mount: 8 5/16 x 9 7/8 in. (21.1 x 25.1 cm),"Gift of Annette Young, in memory of her brother, Innis Young, 1956",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/57292,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -56.121.43,false,true,57293,Asian Art,Painting,,Japan,Edo period (1615–1868),,,,Artist,School of,Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",,Katsushika Hokusai,Japanese,1760,1849,18th–19th century,1700,1899,Ink and color on paper,8 1/4 x 12 5/8 in. (21 x 32.1 cm),"Gift of Annette Young, in memory of her brother, Innis Young, 1956",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/57293,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.842,false,true,78732,Asian Art,Illustrated book,『大雅堂画譜』|Taigadō (Taiga Hall at Sōrinji Temple) Picture Album (Taigadō gafu),Japan,Edo period (1615–1868),,,,Artist|Artist,,Ike Taiga|Sō Geppō,"Japanese, 1723–1776|Japanese, 1760–1839",,Ike Taiga|Sō Geppō,Japanese,1723 |1760,1776 |1839,1804,1804,1804,"Woodblock printed book (orihon, accordion-style); ink and color on paper",11 1/4 × 7 1/2 in. (28.5 × 19 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78732,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JIB10a–c,false,true,57544,Asian Art,Illustrated book,,Japan,Edo period (1615–1868),,,,Artist|Artist,,Rokuzotei|Katsushika Hokusai,"Japanese, Tokyo (Edo) 1760–1849 Tokyo (Edo)",(assisting draftsman),Rokuzotei|Katsushika Hokusai,Japanese,1760,1849,1802,1802,1802,Three volumes; ink and color on paper,Each: 10 1/4 × 6 3/4 × 1/4 in. (26 × 17.1 × 0.6 cm),"Rogers Fund, 1918",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/57544,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.841,false,true,78731,Asian Art,Illustrated books,『山水畫譜』|Picture Album of Landscapes by Yi Fujiu and Ike no Taiga (I Fukyū Ike no Taiga sansui gafu),Japan,Edo period (1615–1868),,,,Artist|Artist,,Ike Taiga|Yi Fujiu,"Japanese, 1723–1776|Japanese, active 1726–50",,Ike Taiga|Yi Fujiu,Japanese,1723 |1726,1776 |1750,1803,1803,1803,Set of two woodblock-printed books bound as one volume; ink on paper,Other (each): 10 3/8 × 7 5/16 in. (26.3 × 18.5 cm),"Purchase, Mary and James G. Wallach Foundation Gift, 2013",,,,,,,,,,,,Illustrated Books,,http://www.metmuseum.org/art/collection/search/78731,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -13.67.76,false,true,45438,Asian Art,Inrō,,Japan,Meiji period (1868–1912),,,,Artist|Artist,After a painting by,Tokoku Fuzui|Hanabusa Itchō,"Japanese, 1652–1724",,Tokoku Fuzui|Hanabusa Itchō,Japanese,1652,1724,late 19th century,1867,1899,"Gold lacquer with ivory and wood inlay; Netsuke: ivory and lacquered wood figure, Ojime: gold bead with face of Daikoku, god of good fortune",3 1/4 x 2 3/8 x 13/16 in. (8.3 x 6.1 x 2 cm),"Rogers Fund, 1913",,,,,,,,,,,,Inrō,,http://www.metmuseum.org/art/collection/search/45438,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -JP832,false,true,54486,Asian Art,Woodblock print,,Japan,Edo period (1615–1868),,,,Artist|Artist,Made by,Estate of Samuel Isham|Torii Kiyonobu,"Japanese, 1664–1729",,Estate of Samuel Isham|Torii Kiyonobu,Japanese,1664,1729,ca. 1749,1739,1759,Polychrome woodblock print; ink and color on paper,H. 12 3/8 in. (31.4 cm); W. 5 7/8 in. (14.9 cm),"Gift of Estate of Samuel Isham, 1914",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/54486,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1985.7,false,true,42346,Asian Art,Hanging scroll,賢江祥啓筆山水図|Landscape with Pavilion,Japan,Muromachi period (1392–1573),,,,Artist|Artist,Inscribed by,Kenkō Shōkei|Tōgen Zuisen,"active ca. 1470–after 1523|Japanese, 1430–1480",,Kenkō Shōkei|Tōgen Zuisen,Japanese,1470 |1430,1550 |1480,1478–80,1478,1480,Hanging scroll; ink and color on paper,Image: 19 13/16 x 13 13/16 in. (50.3 x 35.1 cm) Entire scroll: 58 5/8 x 19 in. (148.9 x 48.3 cm) Width including rollers: 21 in. (53.3 cm),"Purchase, Bequest of Stephen Whitney Phoenix, by exchange, 1985",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/42346,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -13.67.25,false,true,45435,Asian Art,Inrō,,Japan,Edo period (1615–1868),,,,Artist|Artist,After a painting by,Tachibana Gyokuzan|Hanabusa Itchō,"Japanese, 1652–1724",,Tachibana Gyokuzan|Hanabusa Itchō,Japanese,1652,1724,19th century,1800,1899,"Fundami sprinkled lacquer, gold and colored hiramakie sprinkled and polished lacquer, takamakie sprinkled and polished lacquer relief, and foil decoration; Netsuke: lacquered wood figure of Juro-jin; Ojime: zogan metal",3 5/16 x 2 1/16 x 3/4 in. (8.4 x 5.3 x 1.9 cm),"Rogers Fund, 1913",,,,,,,,,,,,Inrō,,http://www.metmuseum.org/art/collection/search/45435,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.853,false,true,45441,Asian Art,Inrō,,Japan,Edo period (1615–1868),,,,Artist|Artist,In the Style of,Nikkōsai|Hanabusa Itchō,"Japanese, 1652–1724",,Nikkōsai|Hanabusa Itchō,Japanese,1652,1724,19th century,1800,1899,Gold lacquer with gold and colored hiramkie sprinkled and polished lacquer and ivory inlay; Netsuke: polished wood button; Ojime: red lacquer bead; Interior: nashiji and fundame,3 3/8 x 2 5/16 x 13/16 in. (8.6 x 5.9 x 2 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Inrō,,http://www.metmuseum.org/art/collection/search/45441,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.100.212,false,true,58894,Asian Art,Inrō,手長猿蒔絵印籠|Inrō with Gibbons in a Landscape,Japan,Edo period (1615–1868),,,,Artist|Artist,Maki-e by|Design by,Hasegawa Kyorinsai|Kano Sukekiyo,"Japanese, active early 19th century|1787–1840",,Hasegawa Kyorinsai|Kano Sukekiyo,Japanese,1800 |1787,1833 |1840,early 19th century,1800,1833,Four cases; lacquered wood with togidashimaki-e imitating ink painting (togikirimaki-e) on gold lacquer ground Netsuke: ivory; monkey on horseback Ojime: bronze; monkey,3 5/16 x 2 3/16 x 3/4 in. (8.4 x 5.5 x 1.9 cm),"The Howard Mansfield Collection, Purchase, Rogers Fund, 1936",,,,,,,,,,,,Inrō,,http://www.metmuseum.org/art/collection/search/58894,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.838,false,true,58765,Asian Art,Inrō,,Japan,Edo period (1615–1868),,,,Artist|Artist,,Hogen Eisen|Kano Hidenobu,"Japanese, 1588–1672",,Hogen Eisen|Kano Hidenobu,Japanese,1588,1672,18th–19th century,1700,1899,"Lacquer, red ground, gold and black togidashi; Interior: nashiji and fundame",2 13/16 x 2 13/16 x 13/16 in. (7.1 x 7.1 x 2 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Inrō,,http://www.metmuseum.org/art/collection/search/58765,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -63.210.3,false,true,456212,Islamic Art,Illustrated album leaf,Page of Calligraphy from a Mantiq al-tair (Language of the Birds),,,,,,Author,,Farid al-Din `Attar,ca. 1142–1220,,Farid al-Din `Attar,,1142,1220,dated A.H. 892/ A.D. 1486,1461,1511,Opaque watercolor and gold on paper,,"Fletcher Fund, 1963",,,,,,,,,,,,Codices,,http://www.metmuseum.org/art/collection/search/456212,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -13.228.23.2,false,true,446600,Islamic Art,Folio from an illustrated manuscript,"""A Religious Devotee Summoned to Pray for the King's Recovery"", Folio from a Bustan (Orchard) of Sa'di",,,,,,Author,,Sa'di,1213/19–92,,Sa'di,,1213,1292,17th century,1600,1699,"Ink, opaque watercolor, and gold on paper",,"Gift of Alexander Smith Cochran, 1913",,,,,,,,,,,,Codices,,http://www.metmuseum.org/art/collection/search/446600,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -13.228.23.3,false,true,454668,Islamic Art,Folio from an illustrated manuscript,"""A Fire-Worshipper Received at the Board of Abraham the Patriarch"", Folio from a Bustan (Orchard) of Sa'di",,,,,,Author,,Sa'di,1213/19–92,,Sa'di,,1213,1292,17th century,1600,1699,"Ink, opaque watercolor, and gold on paper",,"Gift of Alexander Smith Cochran, 1913",,,,,,,,,,,,Codices,,http://www.metmuseum.org/art/collection/search/454668,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -13.228.23.4,false,true,446601,Islamic Art,Folio from an illustrated manuscript,"""King Salih of Syria Entertaining Two Dervishes"", Folio from a Bustan (Orchard) of Sa'di",,,,,,Author,,Sa'di,1213/19–92,,Sa'di,,1213,1292,17th century,1600,1699,"Ink, opaque watercolor, and gold on paper",,"Gift of Alexander Smith Cochran, 1913",,,,,,,,,,,,Codices,,http://www.metmuseum.org/art/collection/search/446601,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.500.3 (1-44),false,true,286316,Photographs,Album,Voyage en Orient et en Espagne Vols 3 & 4 [bound together],,,,,,Artist|Lithographer|Printer,,Louis de Clercq|H. Jannin|J. Blondeau et Antonin,"French, 1837–1901|French",,"de Clercq, Louis|Jannin, H.|J. Blondeau et Antonin",French|French|French,1837,1901,1860 or later,1860,1860,Albumen silver prints from paper negatives,Album: 46.4 x 62.2 cm (18 1/4 x 24 1/2 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Albums,,http://www.metmuseum.org/art/collection/search/286316,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.18 (1-22),false,true,290386,Photographs,Photographically illustrated book,Photographic Facsimiles of the Remains of the Epistles of Clement of Rome. Made from the Unique Copy Preserved in the Codex Alexandrinus.,,,,,,Artist|Author,,Roger Fenton|Frederic Madden,"British, 1819–1869|British, 1801–1873",,"Fenton, Roger|Madden, Frederic",British|British,1819 |1801,1819 |1873,1856,1856,1856,Salted paper prints from glass negatives,Images: 34.3 x 29.8 cm (13 1/2 x 11 3/4 in.),"Purchase, Alfred Stieglitz Society Gifts, 2009",,,,,,,,,,,,Books,,http://www.metmuseum.org/art/collection/search/290386,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.37 (18),false,true,289207,Photographs,Letter; Correspondence,[Manuscript Letter from William-Fox Strangways to Antonio Bertoloni],,,,,,Correspondent|Author,,Antonio Bertoloni|William Thomas Horner Fox-Strangways,"Italian, 1775–1869|British, 1795–1865",,"Bertoloni, Antonio|Fox-Strangways, William Thomas Horner",Italian|British,1775 |1795-05-07,1869 |1865-01-10,1839,1839,1839,Ink on paper (manuscript),Sheet: 23.2 x 36.8 cm (9 1/8 x 14 1/2 in.),"Harris Brisbane Dick Fund, 1936",,,,,,,,,,,,Manuscript Materials,,http://www.metmuseum.org/art/collection/search/289207,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.505.1 (1-48),false,true,285940,Photographs,Album,[Chinese and Japanese Portraits],,,,,,Artist|Artist|Artist,,Raimund von Stillfried|Unknown|Suzuki Shin'ichi,"Austrian, 1839–1911|Japanese, 1835–1919",", et al","Stillfried, Raimond von|Unknown|Suzuki, Shin'ichi",Austrian|Japanese,1839 |1835,1911 |1919,1870s,1870,1879,Albumen silver prints from glass negatives,28 x 35.3 x 4.8 cm (11 x 13 7/8 x 1 7/8 in.),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Albums,,http://www.metmuseum.org/art/collection/search/285940,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.502.1 (1-50),false,true,283195,Photographs,Album,"Gardner's Photographic Sketchbook of the War, Volume 1",,,,,,Artist|Artist|Artist|Artist|Artist|Artist|Artist|Artist|Printer|Author,,Timothy H. O'Sullivan|Alexander Gardner|George N. Barnard|James Gardner|John Reekie|David Knox|William R. Pywell|David B. Woodbury|Alexander Gardner|Alexander Gardner,"American, born Ireland, 1840–1882|American, Glasgow, Scotland 1821–1882 Washington, D.C.|American, 1819–1902|American, born 1832|American, active 1860s|American|American, died 1866|American, Glasgow, Scotland 1821–1882 Washington, D.C.|American, Glasgow, Scotland 1821–1882 Washington, D.C.",,"O'Sullivan, Timothy H.|Gardner, Alexander|Barnard, George N.|Gardner, James|Reekie, John|Knox, David|Pywell, William R.|Woodbury, David B.|Gardner, Alexander|Gardner, Alexander","American, born Ireland|American, Scottish|American|American|American|American|American|American, Scottish|American, Scottish",1840 |1821 |1819 |1832 |1860 |1821 |1821,1882 |1882 |1902 |1869 |1866 |1882 |1882,1863,1863,1863,Albumen silver prints from glass negatives,17.8 x 22.7 cm (7 x 8 15/16 in.),"Gilman Collection, Purchase, Ann Tenenbaum and Thomas H. Lee Gift, 2005",,,,,,,,,,,,Albums,,http://www.metmuseum.org/art/collection/search/283195,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.502.2 (1-50),false,true,286128,Photographs,Album,"Gardner's Photographic Sketchbook of the War, Volume 2",,,,,,Artist|Artist|Artist|Artist|Artist|Artist|Artist|Artist|Printer|Author,,Timothy H. O'Sullivan|Alexander Gardner|James Gardner|George N. Barnard|John Reekie|David Knox|William R. Pywell|David B. Woodbury|Alexander Gardner|Alexander Gardner,"American, born Ireland, 1840–1882|American, Glasgow, Scotland 1821–1882 Washington, D.C.|American, born 1832|American, 1819–1902|American, active 1860s|American|American, died 1866|American, Glasgow, Scotland 1821–1882 Washington, D.C.|American, Glasgow, Scotland 1821–1882 Washington, D.C.",,"O'Sullivan, Timothy H.|Gardner, Alexander|Gardner, James|Barnard, George N.|Reekie, John|Knox, David|Pywell, William R.|Woodbury, David B.|Gardner, Alexander|Gardner, Alexander","American, born Ireland|American, Scottish|American|American|American|American|American|American, Scottish|American, Scottish",1840 |1821 |1832 |1819 |1860 |1821 |1821,1882 |1882 |1902 |1869 |1866 |1882 |1882,1863,1863,1863,Albumen silver prints from glass negatives,Images approx: 17.4 × 22.5 cm (6 7/8 × 8 7/8 in.),"Gilman Collection, Purchase, Ann Tenenbaum and Thomas H. Lee Gift, 2005",,,,,,,,,,,,Albums,,http://www.metmuseum.org/art/collection/search/286128,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1987.1100.282,false,true,265335,Photographs,Photographically illustrated book,Reichsparteitag der Arbeit,,,,,,Artist|Author|Author,,Heinrich Hoffmann|Pitter Gern|Doctor Otto Dietrich,"German, 1885–1957|German|German, 1897–1952",,"Hoffmann, Heinrich|Gern, Pitter|Dietrich, Doctor Otto",German|German|German,1885 |1897,1957 |1952,1930s,1930,1939,Gelatin silver prints,,"Ford Motor Company Collection, Gift of Ford Motor Company and John C. Waddell, 1987",,,,,,,,,,,,Books,,http://www.metmuseum.org/art/collection/search/265335,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2008.669 (1-40),false,true,290484,Photographs,Photographically illustrated book,Life and Landscape on the Norfolk Broads,,,,,,Artist|Author|Printer,,Peter Henry Emerson|Thomas Frederick Goodall|Valentine of Dundee,"British, born Cuba, 1856–1936|British, 1857–1944",,"Emerson, Peter Henry|Goodall, Thomas Frederick|Valentine of Dundee","British, born Cuba|British",1856 |1857,1936 |1944,1885–86,1885,1886,Platinum prints from glass negatives,Images: 12 x 18 cm (4 3/4 x 7 1/16 in.) to 23 x 30 cm (9 1/16 x 11 13/16 in.) Binding: 30.5 x 41.9 x 5.1 cm (12 x 16 1/2 x 2 in.),"Gift of Joyce F. Menschel, 2008",,,,,,,,,,,,Books,,http://www.metmuseum.org/art/collection/search/290484,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.37 (2),false,true,289191,Photographs,Letter; Correspondence,[Manuscript Letter from W. H. Fox Talbot to Antonio Bertoloni],,,,,,Correspondent|Author,,Antonio Bertoloni|William Henry Fox Talbot,"Italian, 1775–1869|British, Dorset 1800–1877 Lacock",,"Bertoloni, Antonio|Talbot, William Henry Fox",Italian|British,1775 |1800,1869 |1800,1839,1839,1839,Ink on paper (manuscript),Sheet: 30 x 37.4 cm (11 13/16 x 14 3/4 in.),"Harris Brisbane Dick Fund, 1936",,,,,,,,,,,,Manuscript Materials,,http://www.metmuseum.org/art/collection/search/289191,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.37 (12),false,true,289201,Photographs,Letter; Correspondence,[Manuscript Letter from W. H. Fox Talbot to Antonio Bertoloni],,,,,,Correspondent|Author,,Antonio Bertoloni|William Henry Fox Talbot,"Italian, 1775–1869|British, Dorset 1800–1877 Lacock",,"Bertoloni, Antonio|Talbot, William Henry Fox",Italian|British,1775 |1800,1869 |1800,1839,1839,1839,Ink on paper (manuscript),Sheet: 22.7 x 37.2 cm (8 15/16 x 14 5/8 in.),"Harris Brisbane Dick Fund, 1936",,,,,,,,,,,,Manuscript Materials,,http://www.metmuseum.org/art/collection/search/289201,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.37 (30),false,true,289218,Photographs,Letter; Correspondence,[Manuscript Letter from W. H. Fox Talbot to Antonio Bertoloni],,,,,,Correspondent|Author,,Antonio Bertoloni|William Henry Fox Talbot,"Italian, 1775–1869|British, Dorset 1800–1877 Lacock",,"Bertoloni, Antonio|Talbot, William Henry Fox",Italian|British,1775 |1800,1869 |1800,1840,1840,1840,Ink on paper (manuscript),Sheet: 22.6 x 37.6 cm (8 7/8 x 14 13/16 in.),"Harris Brisbane Dick Fund, 1936",,,,,,,,,,,,Manuscript Materials,,http://www.metmuseum.org/art/collection/search/289218,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.410 (1-9),false,true,286154,Photographs,Album,[Duc de Morny Album],,,,,,Artist|Artist|Patron,Painted and retouched by|Commissioned by,"Pierre-Louis Pierson|Marck|Charles-Auguste-Louis-Joseph de Morny, duc de Morny","French, 1822–1913|French (born Switzerland) 1811–1865 Paris",", et al","Pierson, Pierre-Louis|Marck|Morny, Charles-Auguste-Louis-Joseph de, duc de Morny","French|French, born Switzerland",1822 |1811,1913 |1865,before 1865,1855,1865,Albumen silver prints from glass negatives,38 x 31.5 x 8.5 cm (14 15/16 x 12 3/8 x 3 3/8 in.) Album 35 CDVs approximately 8.6 x 5.1 cm 6 photographs various sizes from 10.1 x 7.1 cm to 16.9 x 11.9 cm,"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Albums,,http://www.metmuseum.org/art/collection/search/286154,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.381.1.1–.10,false,true,285625,Photographs,Album,"Photographic Views in Madura, Part I",,,,,,Artist|Author,,Linnaeus Tripe|Martin Norman,"British, Devonport (Plymouth Dock) 1822–1902 Devonport|British",,"Tripe, Linnaeus|Norman, Martin",British|British,1822,1902,1858,1858,1858,Albumen silver prints from paper negatives,Various: approx. 34.6 x 28.3,"Gilman Collection, Purchase, Cynthia Hazen Polsky Gift, 2005",,,,,,,,,,,,Albums,,http://www.metmuseum.org/art/collection/search/285625,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.381.2.1–.15,false,true,285624,Photographs,Album,"Photographic Views in Madura, Part III",,,,,,Artist|Author,,Linnaeus Tripe|Martin Norman,"British, Devonport (Plymouth Dock) 1822–1902 Devonport|British",,"Tripe, Linnaeus|Norman, Martin",British|British,1822,1902,1858,1858,1858,Albumen silver prints from paper negatives,Various: approx. 34.6 x 28.3,"Gilman Collection, Purchase, Cynthia Hazen Polsky Gift, 2005",,,,,,,,,,,,Albums,,http://www.metmuseum.org/art/collection/search/285624,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1987.1183.38,false,true,265767,Photographs,Album,[Collection of British Calotypes and Wood-engravings],,,,,,Artist|Artist|Artist|Artist,,"William John Newton|Sir Thomas Maryon Wilson, 8th Baronet|Arthur James Melhuish|Unknown","British, 1785–1869|British, 1800–1869|British, 1829–1895|British",,"Newton, William John|Wilson, Thomas Maryon, Sir, 8th Baronet|Melhuish, Arthur James|Unknown",British|British|British,1785 |1800 |1829,1869 |1869 |1895,1850s,1850,1859,Salted paper prints and engravings,20.0 x 13.8 cm (7 7/8 x 5 7/16 in.),"Gift of Paul F. Walter, in memory of Christopher Hemphill, 1987",,,,,,,,,,,,Albums,,http://www.metmuseum.org/art/collection/search/265767,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.588.2.1–.151,false,true,286628,Photographs,Album,Demi-Monde I 56,,,,,,Artist|Person in Photograph|Person in Photograph,Person in photograph|Person in photograph,André-Adolphe-Eugène Disdéri|Cora Pearl|Emily Fowler,"French, Paris 1819–1889 Paris|British, 1835?–1886|British, 1849–1896",,"Disdéri, André-Adolphe-Eugène|Pearl, Cora|Fowler, Emily",French|British|British,1819 |1835 |1849,1889 |1886 |1896,1858–68,1858,1868,Albumen silver print from glass negative,Image (.2.54-64143): 18.4 x 24.8 cm (7 1/4 x 9 3/4 in.) Album page: 26.2 x 34.8 cm (10 5/16 x 13 11/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Albums,,http://www.metmuseum.org/art/collection/search/286628,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -53.627.1,false,true,269659,Photographs,Cartes-de-visite,[Carte-de-Visite Album of Prominent Personages],,,,,,Artist|Artist|Artist|Artist|Artist|Artist|Artist|Artist|Artist|Artist|Artist|Artist|Artist|Artist|Artist|Artist,,Mayer & Pierson|Neurdein Frères|Leon and Lévy|Boulton|Franck|Pierre-Louis Pierson|H. B. Randall|Sergei Luvovich Levitsky|William Downey|Horatio Nelson King|André-Adolphe-Eugène Disdéri|George Washington Wilson|Vernon Heath|Daniel Downey|Sergei Luvovich Levitsky|Robert Jefferson Bingham,"French|French, active Paris, 1870s–1900s|French|French, 1816–1906|French, 1822–1913|Russian, 1819–1898|British, born 1828|British, 1830–1905|French, Paris 1819–1889 Paris|British, Grampian (Baffshire), Scotland 1823–1893 Abedeen, Scotland|British, 1819–1895|British|Russian, 1819–1898|British, active France, 1825–70",,"Mayer & Pierson|Neurdein Frères|Leon and Lévy|Boulton|Franck|Pierson, Pierre-Louis|Randall, H. B.|Levitsky, Sergei Luvovich|Downey, William and Daniel|King, Horatio Nelson|Disdéri, André-Adolphe-Eugène|Wilson, George Washington|Heath, Vernon|Downey, Daniel |Levitsky, Sergei Luvovich|Bingham, Robert Jefferson","French|French|French|French|French|Russian|British|British|French|British, Scottish|British|British|Russian|British, active France",1863 |1816 |1822 |1819 |1828 |1830 |1819 |1823 |1819 |1819 |1825,1910 |1906 |1913 |1898 |1828 |1905 |1889 |1893 |1895 |1898 |1870,1860s–70s,1860,1879,Albumen silver prints,,"Gift of Susanna Myers, 1953",,,,,,,,,,,,Albums,,http://www.metmuseum.org/art/collection/search/269659,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1991.1073.97 (1–17),false,true,266381,Photographs,Photographically illustrated book,Pictorial Photographs. A Record of the Photographic Salon of 1897. In Seventeen Plates Reproduced in Photogravure,,,,,,Artist|Artist|Artist|Artist|Artist|Artist|Artist|Artist|Artist|Artist|Artist|Artist|Artist|Artist|Artist|Artist|Artist|Author,,W. Thomas|Paul Martin|Heinrich Kühn|Robert Demachy|Alfred Horsley Hinton|Alfred Stieglitz|William Crooke|Ernest R. Ashton|Ralph Winwood Robinson|Paul Bergon|J. B. B. Wellington|Reginald W. Craigie|Maitland|Rev. F. C. Lambert|Frederick H. Evans|Frank M. Sutcliffe|Lionel C. Bennett|Walter L. Colls,"British|Austrian (born Germany), Dresden 1866–1944 Birgitz|French, 1859–1936|British, 1863–1908|American, Hoboken, New Jersey 1864–1946 New York|British, born Scotland|British, 1867–1952|British, 1862–1942|French, 1863–1912|British, 1858–1939|British|British, London 1853–1943 London|British, 1853–1941|British|British",,"Thomas, W.|Martin, Paul|Kühn, Heinrich|Demachy, Robert|Hinton, Afred Horsley|Stieglitz, Alfred|Crooke, William|Ashton, Ernest R.|Robinson, Ralph W.|Bergon, Paul|Wellington, J. B. B.|Craigie, Reginald W.|Maitland, Viscount|Lambert, Rev. F. C.|Evans, Frederick Henry|Sutcliffe, Frank Meadow|Bennett, Lionel C.|Colls, Walter L.","British|Austrian, born Germany|French|British|American|British, Scottish|British|British|French|British|British|British|British|British|British",1866 |1859 |1863 |1864 |1867 |1862 |1863 |1858 |1853 |1853,1944 |1936 |1908 |1946 |1952 |1942 |1912 |1939 |1943 |1941,1897,1897,1897,Photogravures,Each sheet: 14 3/4 × 10 1/2 in. (37.5 × 26.7 cm) Plate dimesions vary,"Gift of Alfred Stieglitz, 1922, transferred from the Library",,,,,,,,,,,,Books,,http://www.metmuseum.org/art/collection/search/266381,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1977.683.1–.76,false,true,262199,Photographs,Album,The Wilkinson Album,,,,,,Artist|Artist|Artist|Artist|Artist,Attributed to,Unknown|Henri de Couliboeuf de Blocqueville|Frances Carlhian|Antonio Giannuzzi|Luigi Pesce,"French, active 1850s–60s|French, 1818–1870|Italian, 1818–1876|Italian, 1818–1891",,"Unknown|Couliboeuf de Blocqueville, Henri de|Carlhian, Frances|Giannuzzi, Antonio|Pesce, Luigi",French|French|Italian|Italian,1818 |1818 |1818,1870 |1876 |1891,1840s–60s,1840,1869,Albumen silver and salted paper prints,,"Gift of Charles K. and Irma B. Wilkinson, 1977",,,,,,,,,,,,Albums,,http://www.metmuseum.org/art/collection/search/262199,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.96,false,true,283200,Photographs,Broadside; Photographs,"[Broadside for the Capture of John Wilkes Booth, John Surratt, and David Herold]",,,,,,Maker|Artist|Photography Studio|Photography Studio,,"Unknown|Alexander Gardner|Silsbee, Case & Company|Unknown","American|American, Glasgow, Scotland 1821–1882 Washington, D.C.|American, active Boston",,"Unknown|Gardner, Alexander|Silsbee, Case & Company|Unknown","American, Scottish|American",1821,1882,"April 20, 1865",1865,1865,Ink on paper with three albumen silver prints from glass negatives,Sheet: 60.5 x 31.3 cm (23 13/16 x 12 5/16 in.) Each photograph: 8.6 x 5.4 cm (3 3/8 x 2 1/8 in.),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Collages,,http://www.metmuseum.org/art/collection/search/283200,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.585 (1-35),false,true,283216,Photographs,Album,"Wheeler Survey, Season of 1872",,,,,,Artist|Patron,Commissioned by,William Bell|Lieutenant George Montague Wheeler,"American (born England) Liverpool 1831–1910 Philadelphia, Pennsylvania|American, 1842–1905",,"Bell, William|Wheeler, Lieutenant George Montague","American, born Britain|American",1831 |1842,1910 |1905,1872,1872,1872,Albumen silver print from glass negative,"28.2 x 20.2 cm (11 1/8 x 7 15/16 in.), each (approx.)","Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Albums,,http://www.metmuseum.org/art/collection/search/283216,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.495,false,true,286333,Photographs,Panorama,Panorama of Niagara Falls,,,,,,Artist|Former Attribution,,Frederick Langenheim|William Langenheim,"American, born Germany, Schöningen 1809–1879|American, born Germany, Schöningen 1807–1874",,"Langenheim, Frederick|Langenheim, William","American, born Germany",1809 |1807,1879 |1874,July 1845,1845,1845,Daguerreotype,"Each plate, visible: 31/2 x 2 3/4; Unframed: 12 x 18; Framed: 13 x 193/8","Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Parts-Stone,,http://www.metmuseum.org/art/collection/search/286333,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.586.2,false,true,261590,Photographs,Photographically illustrated book,"The Far East: A Monthly Journal, Illustrated with Photographs",,,,,,Artist|Artist|Artist|Artist|Editor|Artist|Artist,,L.P. Fisler|Christopher T. Gardner|Thomas Child|Unknown|John Reddie Black|William Thomas Saunders|Kameya Tokujirō,"British, 1865–1886|British|British|Chinese|British, 1827–1880|British, 1832–1892|Japanese, 1825–1884",,"Fisler, L.P.|Gardner, Christopher T.|Child, Thomas|Unknown|Black, John Reddie|Saunders, William Thomas|Kameya, Tokujirō",British|British|British|British|British|Japanese,1865 |1827 |1832 |1825,1886 |1880 |1892 |1884,1870s,1870,1879,Albumen silver prints,,"Gift of John J. McKendry, 1975",,,,,,,,,,,,Periodicals,,http://www.metmuseum.org/art/collection/search/261590,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -52.640,false,true,269625,Photographs,Photographically illustrated book,A Photographic Tour Among the Abbeys of Yorkshire,,,,,,Artist|Artist|Author|Author,,Joseph Cundall|Philip Henry Delamotte|John Richard Walbran|William Jones,"British, Norwich, Norfolk 1818–1895 Wallington, Surrey|British, 1821–1889|British, 1817–1869|British, 1817–1885",,"Cundall, Joseph|Delamotte, Philip Henry|Walbran, John Richard|Jones, William",British|British|British|British,1818 |1821 |1817 |1817,1895 |1889 |1869 |1885,1850s,1850,1859,Albumen silver prints,"45.3 x 31.8 x 3.4 cm (17 13/16 x 12 1/2 x 1 5/16 in.), closed","David Hunter McAlpin Fund, 1952",,,,,,,,,,,,Books,,http://www.metmuseum.org/art/collection/search/269625,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1991.1073.98,false,true,266382,Photographs,Photographically illustrated book,Architecture in Dharwar and Mysore,,,,,,Artist|Artist|Artist|Author|Author,,Thomas Biggs|Andrew Charles Brisbane Neill|Doctor William Henry Pigou|Colonel Philip Meadows Taylor|James Fergusson,"British|British, active India, 1814–1891|British, active India, 1818–1858|British, 1808–1876|British, 1808–1886",,"Biggs, Thomas|Neill, Andrew Charles Brisbane|Pigou, Doctor William Henry|Taylor, Colonel Philip Meadows|Fergusson, James","British|British, active India|British, active India|British|British",1814 |1818 |1808 |1808,1891 |1858 |1876 |1886,1860s,1860,1869,Albumen silver prints,,"Rogers Fund, 1920, transferred from the Library",,,,,,,,,,,,Books,,http://www.metmuseum.org/art/collection/search/266382,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -60.554.11,false,true,270265,Photographs,Portfolio,Productions of the Leeds Photographic Society,,,,,,Artist|Artist|Artist|Artist|Artist|Artist|Artist|Artist|Artist|Artist|Artist|Artist|Artist|Editor,,Ramsden and Birch|W. Birch|T. Dixon|Ramsden and Hope|George Fowler Jones|Pumphrey & Fowler|William A. Pumphrey|William Gardam|E. Holliday|Thomas Henry Briggs|William Fieldhouse|John William Ramsden|J. A. Hope|Leeds Photographic Society,"British|British|British|British|British|British|British, 1817–1905|British|British|British|British|British, 1834–1894|British",,"Ramsden and Birch|Birch, W.|Diseon, & Gardam|Ramsden and Hope|Jones, George Fowler|Pumphrey & Fowler|Pumphrey, William A.|Gardam, William|Holliday, E.|Briggs, Thomas Henry|Fieldhouse, William|Ramsden, John William|Hope, J. A.|Leeds Photographic Society",British|British|British|British|British|British|British|British|British|British|British|British|British|British,1817 |1834 |1852,1905 |1894,1852,1852,1852,Salted paper prints,,"The Elisha Whittelsey Collection, The Elisha Whittelsey Fund, 1960",,,,,,,,,,,,Books,,http://www.metmuseum.org/art/collection/search/270265,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.1068,false,true,264745,Photographs,Photographically illustrated book,Paris-Théàtre,,,,,,Artist|Artist|Artist|Artist|Artist|Artist|Artist|Artist|Artist|Artist|Artist|Artist|Artist|Artist|Artist|Artist|Artist|Artist|Artist|Artist|Artist|Artist|Artist|Artist|Artist|Artist|Artist|Editor|Artist|Artist|Artist,,"Nadar|Paul, fils Bacard|Truchelut|Franck|Charles Reutlinger|E. Flamant|Frank|Eugenio Maunoury|Gaston et Mathieu|Alphonse J. Liébert|Louis Ghémar|Saglio|Emile Bondonneau|Joseph Lemercier|Étienne Carjat|Carrette|Buguet|J. M. Lopez|Ulric Grob|Alexandre Quinet|Dupont|J. Tourtin|Pierre Petit|Héribert Mayer|D. H. Mayer|Gougenheim et Forest|Alexander Courtin|Eugène Paz|Ferdinand Mulnier|Ferdinand Mulnier|Émile Tourtin","French, Paris 1820–1910 Paris|French|French, 1816–1906|German, Karlsruhe 1816–1881 Karlsruhe|Peruvian|French|French, 1827–1913|Belgian, 1819–1873|French|French, 1803–1887|French, Fareins 1828–1906 Paris|French|French|French, born 1836|French|French, Aups 1832–1909 Paris|French|German|French|French, active 1850s–70s|French, active 1850s–70s|French, active 1860s–70s",,"Nadar|Bacard, Paul|Truchelut|Franck|Reutlinger, Charles|Flamant, E.|Frank|Maunoury, Eugenio|Gaston et Mathieu|Liébert, Alphonse J.|Ghémar, Louis|Saglio|Bondonneau, Emile|Lemercier, Joseph|Carjat, Étienne|Carrette|Buguet|Lopez, J. M.|Grob, Ulric|Quinet, Alexandre|Dupont|Tourtin, J.|Petit, Pierre|Mayer, Héribert|Mayer, D. H.|Gougenheim et Forest|Courtin, Alexander|Paz, Eugène|Mulnier, Ferdinand|Mulnier, Ferdinand|Tourtin, Émile",French|French|French|German|Peruvian|French|French|Belgian|French|French|French|French|French|French|French|French|French|German|French|French|French|French,1820 |1816 |1816 |1827 |1819 |1803 |1828 |1836 |1832 |1850 |1850 |1860,1910 |1906 |1881 |1913 |1873 |1887 |1906 |1909 |1875 |1875 |1880,1870s,1870,1879,Woodburytypes,,"Purchase, Mary Martin Fund, 1986",,,,,,,,,,,,Periodicals,,http://www.metmuseum.org/art/collection/search/264745,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.490.1–.10,false,true,291692,Photographs,Books,"Galerie Contemporaine, Littéraire, Artistique, v. 1 - 5",,,,,,Artist|Artist|Artist|Artist|Artist|Artist|Artist|Artist|Artist|Artist|Artist|Artist|Artist|Artist|Artist|Publisher|Artist|Artist|Artist|Printer,,Unknown|Pierre Petit|Nadar|Lejeune|Valery|Ernest Eugène Appert|Delphin|Emile Courtin|Étienne Carjat|Charles-Albert Arnoux Bertall|Vallois|Franck|Antoine-Samuel Adam-Salomon|G. Fontaine|L. Baschet|Goupil et Cie|Émile Tourtin|Ferdinand Mulnier|Melandri|Goupil et Cie,"French|French, Aups 1832–1909 Paris|French, Paris 1820–1910 Paris|French|French, 1831–1891|French|French|French, Fareins 1828–1906 Paris|French, Paris 1820–1882 Paris|French|French, 1816–1906|French, La Ferté-sous-Jouarre 1811–1881 Paris|French|French|French, active 1850–84|French, active 1860s–70s|French, active 1850s–70s|French, active 1860s|French, active 1850–84",,"Unknown|Petit, Pierre|Nadar|Lejeune|Valery|Appert, Ernest Eugène|Delphin|Courtin, Emile|Carjat, Étienne|Bertall, Charles-Albert Arnoux|Vallois|Franck|Adam-Salomon, Antoine-Samuel|Fontaine, G.|Baschet, L.|Goupil et Cie|Tourtin, Émile|Mulnier, Ferdinand|Melandri|Goupil et Cie",French|French|French|French|French|French|French|French|French|French|French|French|French|French|French|French|French|French,1832 |1820 |1831 |1828 |1820 |1816 |1811 |1850 |1860 |1850 |1850,1909 |1910 |1891 |1906 |1882 |1906 |1881 |1884 |1880 |1875 |1884,1876–1881,1876,1881,Woodburytypes,,"Gift of Samuel P. Avery, transferred from the Library",,,,,,,,,,,,Periodicals,,http://www.metmuseum.org/art/collection/search/291692,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.382 (1-85),false,true,283076,Photographs,Album,[Emma Charlotte Dillwyn Llewelyn's Album],,,,,,Artist|Artist|Artist|Artist|Artist|Artist|Artist,,John Dillwyn Llewelyn|Thereza Dillwyn Llewelyn|Jane Martha St. John|James Knight|Miss Bush|P. W. Fry|M.D.,"British, Swansea, Wales 1810–1882 Swansea, Wales|British, 1803–1882|British|British, active 19th century|British, active 19th century",", et al","Llewelyn, John Dillwyn|Llewelyn, Thereza Dillwyn|St., John Jane Martha|Knight, James|Bush Miss|Fry, P. W.|M.D.","British, Welsh|British|British|British|British",1810 |1803,1882 |1882,1853–56,1853,1856,128 salted paper prints and albumen silver prints from paper and glass negatives,28.8 × 22.3 cm (11 5/16 × 8 3/4 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Albums,,http://www.metmuseum.org/art/collection/search/283076,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -38.170,false,true,268615,Photographs,Photographically illustrated book,"The Philadelphia Photographer, Vol. I & II, Nos. 1-24",,,,,,Artist|Artist|Artist|Artist|Artist|Artist|Artist|Editor,,"Wenderoth, Taylor & Brown|Henry P. Moore|John Coates Browne|John Carbutt|J. D. Sergeant|Leon S. Levy|Max Petsch|Edward Livingston Wilson","American, active Philadelphia, 1860s|American, 1833–1911|American, 1838–1918|American, 1832–1905|German, active 1860s|American, 1838–1903",,"Wenderoth, Taylor & Brown|Moore, Henry P.|Browne, John Coates|Carbutt, John|Sergeant, J. D.|Levy, Leon S. & Cornelius Cohen|Loescher & Petsch|Wilson, Edward Livingston",American|American|American|American|German|American,1833 |1838 |1832 |1860 |1838,1911 |1918 |1905 |1870 |1903,1864–65,1864,1865,Albumen silver prints,,"Harris Brisbane Dick Fund, 1938",,,,,,,,,,,,Periodicals,,http://www.metmuseum.org/art/collection/search/268615,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -69.604.5,false,true,271509,Photographs,Cartes-de-visite,[Carte-de-Visite Album of British and European Royalty],,,,,,Artist|Artist|Artist|Artist|Artist|Artist|Artist|Artist|Artist|Artist|Artist,,F. Joubert|Hermann Günther|Hills and Saunders|W. C. Lacy|Alfred Windsor|Cornelius Jabez Hughes|Camille Silvy|John Jabez Edwin Mayall|S. Mauer|Daniel Downey|Frères Ghemar,"British|German|British, active 1856–95|British|British, 1819–1884|French, 1835–1869|British, Oldham, Lancashire 1813–1901 West Sussex|British",,"Joubert, F.|Günther, Hermann|Hills and Saunders|Lacy, W. C.|Windsor, Alfred, Prince of England|Hughes, Jabez|Silvy, Camille|Mayall, John Jabez Edwin|Mauer, S.|Downey, Daniel |Ghemar, Frères",British|German|British|British|British|French|British|British|Belgian,1856 |1819 |1835 |1813,1895 |1884 |1869 |1901,1860s–70s,1860,1879,Albumen silver prints,,"The Elisha Whittelsey Collection, The Elisha Whittelsey Fund, 1969",,,,,,,,,,,,Albums,,http://www.metmuseum.org/art/collection/search/271509,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548,false,true,261267,Photographs,Album,La Colombe et Le Tigre. Ma collection de photographies de la Comtesse de Castiglione,,,,,,Artist|Artist|Artist|Artist|Artist|Artist|Artist|Person in Photograph,,Duroni et Murer|Adolphe Braun|Alphonse (Jean-Baptiste) Bernoud|Mayer & Pierson|Giovanni Morotti|Pierre-Louis Pierson|André-Adolphe-Eugène Disdéri|Countess Virginia Oldoini Verasis di Castiglione,"Italian, 1807–1870|French, Besançon 1811–1877 Dornach|French, 1820–1889|French|Italian|French, 1822–1913|French, Paris 1819–1889 Paris|1835–1899",,"Duroni et Murer|Braun, Adolphe|Bernoud, Alphonse|Mayer & Pierson|Morotti, Giovanni|Pierson, Pierre-Louis|Disdéri, André-Adolphe-Eugène|Castiglione, di, Virginia Oldoini Verasis Countess",Italian|French|French|French|Italian|French|French,1807 |1811 |1820 |1822 |1819 |1835,1870 |1877 |1889 |1913 |1889 |1899,1860s,1860,1869,Albumen silver prints,,"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Albums,,http://www.metmuseum.org/art/collection/search/261267,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -68.519 (1),false,true,675527,Photographs,Album; Artist's book,The Thames near Kelmscott Manor,,,,,,Correspondent|Artist|Artist|Artist|Artist|Author|Correspondent,Bookplate designed by|Bookplate designed by,Sir Sydney Cockerell|Frederick H. Evans|Unknown|Aubrey Vincent Beardsley|Frederick Colin Tilney|Ernest Radford|Theodore Watts-Dunton,"1910–1957|British, London 1853–1943 London|British|British, Brighton, Sussex 1872–1898 Menton|British, 1870–1951|British, active late 19th century|British, 1832–1914",,"Cockerell Sydney Sir|Evans, Frederick Henry|Unknown|Beardsley, Aubrey Vincent|Tilney, Frederick Colin|Radford, Ernest|Watts-Dunton, Theodore",British|British|British|British|British,1910 |1853 |1872 |1870 |1850 |1832,1957 |1943 |1898 |1951 |1950 |1914,1896,1896,1896,Platinum print,,"Purchase, David Hunter McAlpin Fund, 1968",,,,,,,,,,,,Albums,,http://www.metmuseum.org/art/collection/search/675527,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -68.519 (4),false,true,675530,Photographs,Album; Artist's book,"Entrance, On Right",,,,,,Correspondent|Artist|Artist|Artist|Artist|Author|Correspondent,Bookplate designed by|Bookplate designed by,Sir Sydney Cockerell|Frederick H. Evans|Unknown|Aubrey Vincent Beardsley|Frederick Colin Tilney|Ernest Radford|Theodore Watts-Dunton,"1910–1957|British, London 1853–1943 London|British|British, Brighton, Sussex 1872–1898 Menton|British, 1870–1951|British, active late 19th century|British, 1832–1914",,"Cockerell Sydney Sir|Evans, Frederick Henry|Unknown|Beardsley, Aubrey Vincent|Tilney, Frederick Colin|Radford, Ernest|Watts-Dunton, Theodore",British|British|British|British|British,1910 |1853 |1872 |1870 |1850 |1832,1957 |1943 |1898 |1951 |1950 |1914,1896,1896,1896,Platinum print,,"Purchase, David Hunter McAlpin Fund, 1968",,,,,,,,,,,,Albums,,http://www.metmuseum.org/art/collection/search/675530,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -68.519 (6),false,true,675532,Photographs,Album; Artist's book,Main Entrance,,,,,,Correspondent|Artist|Artist|Artist|Artist|Author|Correspondent,Bookplate designed by|Bookplate designed by,Sir Sydney Cockerell|Frederick H. Evans|Unknown|Aubrey Vincent Beardsley|Frederick Colin Tilney|Ernest Radford|Theodore Watts-Dunton,"1910–1957|British, London 1853–1943 London|British|British, Brighton, Sussex 1872–1898 Menton|British, 1870–1951|British, active late 19th century|British, 1832–1914",,"Cockerell Sydney Sir|Evans, Frederick Henry|Unknown|Beardsley, Aubrey Vincent|Tilney, Frederick Colin|Radford, Ernest|Watts-Dunton, Theodore",British|British|British|British|British,1910 |1853 |1872 |1870 |1850 |1832,1957 |1943 |1898 |1951 |1950 |1914,1896,1896,1896,Platinum print,,"Purchase, David Hunter McAlpin Fund, 1968",,,,,,,,,,,,Albums,,http://www.metmuseum.org/art/collection/search/675532,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -68.519 (21),false,true,675547,Photographs,Album; Artist's book,[Tapestry Room],,,,,,Correspondent|Artist|Artist|Artist|Artist|Author|Correspondent,Bookplate designed by|Bookplate designed by,Sir Sydney Cockerell|Frederick H. Evans|Unknown|Aubrey Vincent Beardsley|Frederick Colin Tilney|Ernest Radford|Theodore Watts-Dunton,"1910–1957|British, London 1853–1943 London|British|British, Brighton, Sussex 1872–1898 Menton|British, 1870–1951|British, active late 19th century|British, 1832–1914",,"Cockerell Sydney Sir|Evans, Frederick Henry|Unknown|Beardsley, Aubrey Vincent|Tilney, Frederick Colin|Radford, Ernest|Watts-Dunton, Theodore",British|British|British|British|British,1910 |1853 |1872 |1870 |1850 |1832,1957 |1943 |1898 |1951 |1950 |1914,1896,1896,1896,Platinum print,,"Purchase, David Hunter McAlpin Fund, 1968",,,,,,,,,,,,Albums,,http://www.metmuseum.org/art/collection/search/675547,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -68.519 (28),false,true,675554,Photographs,Album; Artist's book,"[Tithe Barn, Great Cokkeswell]",,,,,,Correspondent|Artist|Artist|Artist|Artist|Author|Correspondent,Bookplate designed by|Bookplate designed by,Sir Sydney Cockerell|Frederick H. Evans|Unknown|Aubrey Vincent Beardsley|Frederick Colin Tilney|Ernest Radford|Theodore Watts-Dunton,"1910–1957|British, London 1853–1943 London|British|British, Brighton, Sussex 1872–1898 Menton|British, 1870–1951|British, active late 19th century|British, 1832–1914",,"Cockerell Sydney Sir|Evans, Frederick Henry|Unknown|Beardsley, Aubrey Vincent|Tilney, Frederick Colin|Radford, Ernest|Watts-Dunton, Theodore",British|British|British|British|British,1910 |1853 |1872 |1870 |1850 |1832,1957 |1943 |1898 |1951 |1950 |1914,1896,1896,1896,Platinum print,,"Purchase, David Hunter McAlpin Fund, 1968",,,,,,,,,,,,Albums,,http://www.metmuseum.org/art/collection/search/675554,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -68.519 (27),false,true,675553,Photographs,Album; Artist's book,K. Church,,,,,,Correspondent|Artist|Artist|Artist|Author|Correspondent|Artist,Bookplate designed by|Bookplate designed by,Sir Sydney Cockerell|Frederick H. Evans|Unknown|Aubrey Vincent Beardsley|Ernest Radford|Theodore Watts-Dunton|Frederick Colin Tilney,"1910–1957|British, London 1853–1943 London|British|British, Brighton, Sussex 1872–1898 Menton|British, active late 19th century|British, 1832–1914|British, 1870–1951",,"Cockerell Sydney Sir|Evans, Frederick Henry|Unknown|Beardsley, Aubrey Vincent|Radford, Ernest|Watts-Dunton, Theodore|Tilney, Frederick Colin",British|British|British|British|British,1910 |1853 |1872 |1850 |1832 |1870,1957 |1943 |1898 |1950 |1914 |1951,1896,1896,1896,Platinum print,,"Purchase, David Hunter McAlpin Fund, 1968",,,,,,,,,,,,Albums,,http://www.metmuseum.org/art/collection/search/675553,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -68.519 (16),false,true,675542,Photographs,Album; Artist's book,Passage to Panelled Room,,,,,,Correspondent|Correspondent|Artist|Author|Artist|Artist|Artist,Bookplate designed by|Bookplate designed by,Theodore Watts-Dunton|Sir Sydney Cockerell|Frederick Colin Tilney|Ernest Radford|Frederick H. Evans|Unknown|Aubrey Vincent Beardsley,"British, 1832–1914|1910–1957|British, 1870–1951|British, active late 19th century|British, London 1853–1943 London|British|British, Brighton, Sussex 1872–1898 Menton",,"Watts-Dunton, Theodore|Cockerell Sydney Sir|Tilney, Frederick Colin|Radford, Ernest|Evans, Frederick Henry|Unknown|Beardsley, Aubrey Vincent",British|British|British|British|British,1832 |1910 |1870 |1850 |1853 |1872,1914 |1957 |1951 |1950 |1943 |1898,1896,1896,1896,Platinum print,,"Purchase, David Hunter McAlpin Fund, 1968",,,,,,,,,,,,Albums,,http://www.metmuseum.org/art/collection/search/675542,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -68.519 (24),false,true,675550,Photographs,Album; Artist's book,From the Tapestry Room,,,,,,Correspondent|Correspondent|Artist|Artist|Artist|Artist|Author,Bookplate designed by|Bookplate designed by,Theodore Watts-Dunton|Sir Sydney Cockerell|Frederick H. Evans|Unknown|Aubrey Vincent Beardsley|Frederick Colin Tilney|Ernest Radford,"British, 1832–1914|1910–1957|British, London 1853–1943 London|British|British, Brighton, Sussex 1872–1898 Menton|British, 1870–1951|British, active late 19th century",,"Watts-Dunton, Theodore|Cockerell Sydney Sir|Evans, Frederick Henry|Unknown|Beardsley, Aubrey Vincent|Tilney, Frederick Colin|Radford, Ernest",British|British|British|British|British,1832 |1910 |1853 |1872 |1870 |1850,1914 |1957 |1943 |1898 |1951 |1950,1896,1896,1896,Platinum print,,"Purchase, David Hunter McAlpin Fund, 1968",,,,,,,,,,,,Albums,,http://www.metmuseum.org/art/collection/search/675550,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -68.519 (26),false,true,675552,Photographs,Album; Artist's book,In the Attics,,,,,,Correspondent|Correspondent|Artist|Artist|Artist|Author|Artist,Bookplate designed by|Bookplate designed by,Theodore Watts-Dunton|Sir Sydney Cockerell|Unknown|Aubrey Vincent Beardsley|Frederick Colin Tilney|Ernest Radford|Frederick H. Evans,"British, 1832–1914|1910–1957|British|British, Brighton, Sussex 1872–1898 Menton|British, 1870–1951|British, active late 19th century|British, London 1853–1943 London",,"Watts-Dunton, Theodore|Cockerell Sydney Sir|Unknown|Beardsley, Aubrey Vincent|Tilney, Frederick Colin|Radford, Ernest|Evans, Frederick Henry",British|British|British|British|British,1832 |1910 |1872 |1870 |1850 |1853,1914 |1957 |1898 |1951 |1950 |1943,1896,1896,1896,Platinum print,Image: 15.4 x 21 cm (6 1/16 x 8 1/4 in.),"Purchase, David Hunter McAlpin Fund, 1968",,,,,,,,,,,,Albums,,http://www.metmuseum.org/art/collection/search/675552,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -68.519 (18),false,true,675544,Photographs,Album; Artist's book,The Green Room,,,,,,Artist|Author|Correspondent|Correspondent|Artist|Artist|Artist,Bookplate designed by|Bookplate designed by,Frederick Colin Tilney|Ernest Radford|Theodore Watts-Dunton|Sir Sydney Cockerell|Frederick H. Evans|Unknown|Aubrey Vincent Beardsley,"British, 1870–1951|British, active late 19th century|British, 1832–1914|1910–1957|British, London 1853–1943 London|British|British, Brighton, Sussex 1872–1898 Menton",,"Tilney, Frederick Colin|Radford, Ernest|Watts-Dunton, Theodore|Cockerell Sydney Sir|Evans, Frederick Henry|Unknown|Beardsley, Aubrey Vincent",British|British|British|British|British,1870 |1850 |1832 |1910 |1853 |1872,1951 |1950 |1914 |1957 |1943 |1898,1896,1896,1896,Platinum print,,"Purchase, David Hunter McAlpin Fund, 1968",,,,,,,,,,,,Albums,,http://www.metmuseum.org/art/collection/search/675544,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -68.519 (8),false,true,675534,Photographs,Album; Artist's book,[Main Entrance],,,,,,Artist|Artist|Artist|Correspondent|Artist|Author|Correspondent,Bookplate designed by|Bookplate designed by,Frederick H. Evans|Unknown|Aubrey Vincent Beardsley|Sir Sydney Cockerell|Frederick Colin Tilney|Ernest Radford|Theodore Watts-Dunton,"British, London 1853–1943 London|British|British, Brighton, Sussex 1872–1898 Menton|1910–1957|British, 1870–1951|British, active late 19th century|British, 1832–1914",,"Evans, Frederick Henry|Unknown|Beardsley, Aubrey Vincent|Cockerell Sydney Sir|Tilney, Frederick Colin|Radford, Ernest|Watts-Dunton, Theodore",British|British|British|British|British,1853 |1872 |1910 |1870 |1850 |1832,1943 |1898 |1957 |1951 |1950 |1914,1896,1896,1896,Platinum print,,"Purchase, David Hunter McAlpin Fund, 1968",,,,,,,,,,,,Albums,,http://www.metmuseum.org/art/collection/search/675534,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -68.519,false,true,271329,Photographs,Album; Artist's book,Kelmscott Manor Photographs,,,,,,Artist|Artist|Artist|Artist|Author|Correspondent|Correspondent,Bookplate designed by|Bookplate designed by,Frederick H. Evans|Unknown|Aubrey Vincent Beardsley|Frederick Colin Tilney|Ernest Radford|Theodore Watts-Dunton|Sir Sydney Cockerell,"British, London 1853–1943 London|British|British, Brighton, Sussex 1872–1898 Menton|British, 1870–1951|British, active late 19th century|British, 1832–1914|1910–1957",,"Evans, Frederick Henry|Unknown|Beardsley, Aubrey Vincent|Tilney, Frederick Colin|Radford, Ernest|Watts-Dunton, Theodore|Cockerell Sydney Sir",British|British|British|British|British,1853 |1872 |1870 |1850 |1832 |1910,1943 |1898 |1951 |1950 |1914 |1957,1896,1896,1896,Platinum prints; albumen silver print,,"Purchase, David Hunter McAlpin Fund, 1968",,,,,,,,,,,,Albums,,http://www.metmuseum.org/art/collection/search/271329,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -68.519 (2),false,true,675528,Photographs,Album; Artist's book,From the Meadows,,,,,,Artist|Artist|Artist|Artist|Author|Correspondent|Correspondent,Bookplate designed by|Bookplate designed by,Frederick H. Evans|Unknown|Aubrey Vincent Beardsley|Frederick Colin Tilney|Ernest Radford|Theodore Watts-Dunton|Sir Sydney Cockerell,"British, London 1853–1943 London|British|British, Brighton, Sussex 1872–1898 Menton|British, 1870–1951|British, active late 19th century|British, 1832–1914|1910–1957",,"Evans, Frederick Henry|Unknown|Beardsley, Aubrey Vincent|Tilney, Frederick Colin|Radford, Ernest|Watts-Dunton, Theodore|Cockerell Sydney Sir",British|British|British|British|British,1853 |1872 |1870 |1850 |1832 |1910,1943 |1898 |1951 |1950 |1914 |1957,1896,1896,1896,Platinum print,,"Purchase, David Hunter McAlpin Fund, 1968",,,,,,,,,,,,Albums,,http://www.metmuseum.org/art/collection/search/675528,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -68.519 (3),false,true,675529,Photographs,Album; Artist's book,From the Fields,,,,,,Artist|Artist|Artist|Artist|Author|Correspondent|Correspondent,Bookplate designed by|Bookplate designed by,Frederick H. Evans|Unknown|Aubrey Vincent Beardsley|Frederick Colin Tilney|Ernest Radford|Theodore Watts-Dunton|Sir Sydney Cockerell,"British, London 1853–1943 London|British|British, Brighton, Sussex 1872–1898 Menton|British, 1870–1951|British, active late 19th century|British, 1832–1914|1910–1957",,"Evans, Frederick Henry|Unknown|Beardsley, Aubrey Vincent|Tilney, Frederick Colin|Radford, Ernest|Watts-Dunton, Theodore|Cockerell Sydney Sir",British|British|British|British|British,1853 |1872 |1870 |1850 |1832 |1910,1943 |1898 |1951 |1950 |1914 |1957,1896,1896,1896,Platinum print,,"Purchase, David Hunter McAlpin Fund, 1968",,,,,,,,,,,,Albums,,http://www.metmuseum.org/art/collection/search/675529,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -68.519 (5),false,true,675531,Photographs,Album; Artist's book,Main Gate Entrance,,,,,,Artist|Artist|Artist|Artist|Author|Correspondent|Correspondent,Bookplate designed by|Bookplate designed by,Frederick H. Evans|Unknown|Aubrey Vincent Beardsley|Frederick Colin Tilney|Ernest Radford|Theodore Watts-Dunton|Sir Sydney Cockerell,"British, London 1853–1943 London|British|British, Brighton, Sussex 1872–1898 Menton|British, 1870–1951|British, active late 19th century|British, 1832–1914|1910–1957",,"Evans, Frederick Henry|Unknown|Beardsley, Aubrey Vincent|Tilney, Frederick Colin|Radford, Ernest|Watts-Dunton, Theodore|Cockerell Sydney Sir",British|British|British|British|British,1853 |1872 |1870 |1850 |1832 |1910,1943 |1898 |1951 |1950 |1914 |1957,1896,1896,1896,Platinum print,,"Purchase, David Hunter McAlpin Fund, 1968",,,,,,,,,,,,Albums,,http://www.metmuseum.org/art/collection/search/675531,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -68.519 (9),false,true,675535,Photographs,Album; Artist's book,[Main Entrance],,,,,,Artist|Artist|Artist|Artist|Author|Correspondent|Correspondent,Bookplate designed by|Bookplate designed by,Frederick H. Evans|Unknown|Aubrey Vincent Beardsley|Frederick Colin Tilney|Ernest Radford|Theodore Watts-Dunton|Sir Sydney Cockerell,"British, London 1853–1943 London|British|British, Brighton, Sussex 1872–1898 Menton|British, 1870–1951|British, active late 19th century|British, 1832–1914|1910–1957",,"Evans, Frederick Henry|Unknown|Beardsley, Aubrey Vincent|Tilney, Frederick Colin|Radford, Ernest|Watts-Dunton, Theodore|Cockerell Sydney Sir",British|British|British|British|British,1853 |1872 |1870 |1850 |1832 |1910,1943 |1898 |1951 |1950 |1914 |1957,1896,1896,1896,Platinum print,,"Purchase, David Hunter McAlpin Fund, 1968",,,,,,,,,,,,Albums,,http://www.metmuseum.org/art/collection/search/675535,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -68.519 (10),false,true,675536,Photographs,Album; Artist's book,"[Main Entrance, with Hedge]",,,,,,Artist|Artist|Artist|Artist|Author|Correspondent|Correspondent,Bookplate designed by|Bookplate designed by,Frederick H. Evans|Unknown|Aubrey Vincent Beardsley|Frederick Colin Tilney|Ernest Radford|Theodore Watts-Dunton|Sir Sydney Cockerell,"British, London 1853–1943 London|British|British, Brighton, Sussex 1872–1898 Menton|British, 1870–1951|British, active late 19th century|British, 1832–1914|1910–1957",,"Evans, Frederick Henry|Unknown|Beardsley, Aubrey Vincent|Tilney, Frederick Colin|Radford, Ernest|Watts-Dunton, Theodore|Cockerell Sydney Sir",British|British|British|British|British,1853 |1872 |1870 |1850 |1832 |1910,1943 |1898 |1951 |1950 |1914 |1957,1896,1896,1896,Platinum print,,"Purchase, David Hunter McAlpin Fund, 1968",,,,,,,,,,,,Albums,,http://www.metmuseum.org/art/collection/search/675536,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -68.519 (11),false,true,675537,Photographs,Album; Artist's book,"[Garden, Front]",,,,,,Artist|Artist|Artist|Artist|Author|Correspondent|Correspondent,Bookplate designed by|Bookplate designed by,Frederick H. Evans|Unknown|Aubrey Vincent Beardsley|Frederick Colin Tilney|Ernest Radford|Theodore Watts-Dunton|Sir Sydney Cockerell,"British, London 1853–1943 London|British|British, Brighton, Sussex 1872–1898 Menton|British, 1870–1951|British, active late 19th century|British, 1832–1914|1910–1957",,"Evans, Frederick Henry|Unknown|Beardsley, Aubrey Vincent|Tilney, Frederick Colin|Radford, Ernest|Watts-Dunton, Theodore|Cockerell Sydney Sir",British|British|British|British|British,1853 |1872 |1870 |1850 |1832 |1910,1943 |1898 |1951 |1950 |1914 |1957,1896,1896,1896,Platinum print,,"Purchase, David Hunter McAlpin Fund, 1968",,,,,,,,,,,,Albums,,http://www.metmuseum.org/art/collection/search/675537,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -68.519 (12),false,true,675538,Photographs,Album; Artist's book,"[Garden, Front]",,,,,,Artist|Artist|Artist|Artist|Author|Correspondent|Correspondent,Bookplate designed by|Bookplate designed by,Frederick H. Evans|Unknown|Aubrey Vincent Beardsley|Frederick Colin Tilney|Ernest Radford|Theodore Watts-Dunton|Sir Sydney Cockerell,"British, London 1853–1943 London|British|British, Brighton, Sussex 1872–1898 Menton|British, 1870–1951|British, active late 19th century|British, 1832–1914|1910–1957",,"Evans, Frederick Henry|Unknown|Beardsley, Aubrey Vincent|Tilney, Frederick Colin|Radford, Ernest|Watts-Dunton, Theodore|Cockerell Sydney Sir",British|British|British|British|British,1853 |1872 |1870 |1850 |1832 |1910,1943 |1898 |1951 |1950 |1914 |1957,1896,1896,1896,Platinum print,,"Purchase, David Hunter McAlpin Fund, 1968",,,,,,,,,,,,Albums,,http://www.metmuseum.org/art/collection/search/675538,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -68.519 (13),false,true,675539,Photographs,Album; Artist's book,In the Orchard,,,,,,Artist|Artist|Artist|Artist|Author|Correspondent|Correspondent,Bookplate designed by|Bookplate designed by,Frederick H. Evans|Unknown|Aubrey Vincent Beardsley|Frederick Colin Tilney|Ernest Radford|Theodore Watts-Dunton|Sir Sydney Cockerell,"British, London 1853–1943 London|British|British, Brighton, Sussex 1872–1898 Menton|British, 1870–1951|British, active late 19th century|British, 1832–1914|1910–1957",,"Evans, Frederick Henry|Unknown|Beardsley, Aubrey Vincent|Tilney, Frederick Colin|Radford, Ernest|Watts-Dunton, Theodore|Cockerell Sydney Sir",British|British|British|British|British,1853 |1872 |1870 |1850 |1832 |1910,1943 |1898 |1951 |1950 |1914 |1957,1896,1896,1896,Platinum print,,"Purchase, David Hunter McAlpin Fund, 1968",,,,,,,,,,,,Albums,,http://www.metmuseum.org/art/collection/search/675539,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -68.519 (14),false,true,675540,Photographs,Album; Artist's book,Bed Morris Was Born In,,,,,,Artist|Artist|Artist|Artist|Author|Correspondent|Correspondent,Bookplate designed by|Bookplate designed by,Frederick H. Evans|Unknown|Aubrey Vincent Beardsley|Frederick Colin Tilney|Ernest Radford|Theodore Watts-Dunton|Sir Sydney Cockerell,"British, London 1853–1943 London|British|British, Brighton, Sussex 1872–1898 Menton|British, 1870–1951|British, active late 19th century|British, 1832–1914|1910–1957",,"Evans, Frederick Henry|Unknown|Beardsley, Aubrey Vincent|Tilney, Frederick Colin|Radford, Ernest|Watts-Dunton, Theodore|Cockerell Sydney Sir",British|British|British|British|British,1853 |1872 |1870 |1850 |1832 |1910,1943 |1898 |1951 |1950 |1914 |1957,1896,1896,1896,Platinum print,,"Purchase, David Hunter McAlpin Fund, 1968",,,,,,,,,,,,Albums,,http://www.metmuseum.org/art/collection/search/675540,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -68.519 (15),false,true,675541,Photographs,Album; Artist's book,WM's Bedroom,,,,,,Artist|Artist|Artist|Artist|Author|Correspondent|Correspondent,Bookplate designed by|Bookplate designed by,Frederick H. Evans|Unknown|Aubrey Vincent Beardsley|Frederick Colin Tilney|Ernest Radford|Theodore Watts-Dunton|Sir Sydney Cockerell,"British, London 1853–1943 London|British|British, Brighton, Sussex 1872–1898 Menton|British, 1870–1951|British, active late 19th century|British, 1832–1914|1910–1957",,"Evans, Frederick Henry|Unknown|Beardsley, Aubrey Vincent|Tilney, Frederick Colin|Radford, Ernest|Watts-Dunton, Theodore|Cockerell Sydney Sir",British|British|British|British|British,1853 |1872 |1870 |1850 |1832 |1910,1943 |1898 |1951 |1950 |1914 |1957,1896,1896,1896,Platinum print,,"Purchase, David Hunter McAlpin Fund, 1968",,,,,,,,,,,,Albums,,http://www.metmuseum.org/art/collection/search/675541,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -68.519 (17),false,true,675543,Photographs,Album; Artist's book,The Panelled Room,,,,,,Artist|Artist|Artist|Artist|Author|Correspondent|Correspondent,Bookplate designed by|Bookplate designed by,Frederick H. Evans|Unknown|Aubrey Vincent Beardsley|Frederick Colin Tilney|Ernest Radford|Theodore Watts-Dunton|Sir Sydney Cockerell,"British, London 1853–1943 London|British|British, Brighton, Sussex 1872–1898 Menton|British, 1870–1951|British, active late 19th century|British, 1832–1914|1910–1957",,"Evans, Frederick Henry|Unknown|Beardsley, Aubrey Vincent|Tilney, Frederick Colin|Radford, Ernest|Watts-Dunton, Theodore|Cockerell Sydney Sir",British|British|British|British|British,1853 |1872 |1870 |1850 |1832 |1910,1943 |1898 |1951 |1950 |1914 |1957,1896,1896,1896,Platinum print,,"Purchase, David Hunter McAlpin Fund, 1968",,,,,,,,,,,,Albums,,http://www.metmuseum.org/art/collection/search/675543,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -68.519 (20),false,true,675546,Photographs,Album; Artist's book,[Tapestry Room],,,,,,Artist|Artist|Artist|Artist|Author|Correspondent|Correspondent,Bookplate designed by|Bookplate designed by,Frederick H. Evans|Unknown|Aubrey Vincent Beardsley|Frederick Colin Tilney|Ernest Radford|Theodore Watts-Dunton|Sir Sydney Cockerell,"British, London 1853–1943 London|British|British, Brighton, Sussex 1872–1898 Menton|British, 1870–1951|British, active late 19th century|British, 1832–1914|1910–1957",,"Evans, Frederick Henry|Unknown|Beardsley, Aubrey Vincent|Tilney, Frederick Colin|Radford, Ernest|Watts-Dunton, Theodore|Cockerell Sydney Sir",British|British|British|British|British,1853 |1872 |1870 |1850 |1832 |1910,1943 |1898 |1951 |1950 |1914 |1957,1896,1896,1896,Platinum print,,"Purchase, David Hunter McAlpin Fund, 1968",,,,,,,,,,,,Albums,,http://www.metmuseum.org/art/collection/search/675546,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -68.519 (22),false,true,675548,Photographs,Album; Artist's book,[Tapestry Room],,,,,,Artist|Artist|Artist|Artist|Author|Correspondent|Correspondent,Bookplate designed by|Bookplate designed by,Frederick H. Evans|Unknown|Aubrey Vincent Beardsley|Frederick Colin Tilney|Ernest Radford|Theodore Watts-Dunton|Sir Sydney Cockerell,"British, London 1853–1943 London|British|British, Brighton, Sussex 1872–1898 Menton|British, 1870–1951|British, active late 19th century|British, 1832–1914|1910–1957",,"Evans, Frederick Henry|Unknown|Beardsley, Aubrey Vincent|Tilney, Frederick Colin|Radford, Ernest|Watts-Dunton, Theodore|Cockerell Sydney Sir",British|British|British|British|British,1853 |1872 |1870 |1850 |1832 |1910,1943 |1898 |1951 |1950 |1914 |1957,1896,1896,1896,Platinum print,,"Purchase, David Hunter McAlpin Fund, 1968",,,,,,,,,,,,Albums,,http://www.metmuseum.org/art/collection/search/675548,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -68.519 (23),false,true,675549,Photographs,Album; Artist's book,The Tapestry Room,,,,,,Artist|Artist|Artist|Artist|Author|Correspondent|Correspondent,Bookplate designed by|Bookplate designed by,Frederick H. Evans|Unknown|Aubrey Vincent Beardsley|Frederick Colin Tilney|Ernest Radford|Theodore Watts-Dunton|Sir Sydney Cockerell,"British, London 1853–1943 London|British|British, Brighton, Sussex 1872–1898 Menton|British, 1870–1951|British, active late 19th century|British, 1832–1914|1910–1957",,"Evans, Frederick Henry|Unknown|Beardsley, Aubrey Vincent|Tilney, Frederick Colin|Radford, Ernest|Watts-Dunton, Theodore|Cockerell Sydney Sir",British|British|British|British|British,1853 |1872 |1870 |1850 |1832 |1910,1943 |1898 |1951 |1950 |1914 |1957,1896,1896,1896,Platinum print,,"Purchase, David Hunter McAlpin Fund, 1968",,,,,,,,,,,,Albums,,http://www.metmuseum.org/art/collection/search/675549,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -68.519 (25),false,true,675551,Photographs,Album; Artist's book,In the Attics,,,,,,Artist|Artist|Artist|Artist|Author|Correspondent|Correspondent,Bookplate designed by|Bookplate designed by,Frederick H. Evans|Unknown|Aubrey Vincent Beardsley|Frederick Colin Tilney|Ernest Radford|Theodore Watts-Dunton|Sir Sydney Cockerell,"British, London 1853–1943 London|British|British, Brighton, Sussex 1872–1898 Menton|British, 1870–1951|British, active late 19th century|British, 1832–1914|1910–1957",,"Evans, Frederick Henry|Unknown|Beardsley, Aubrey Vincent|Tilney, Frederick Colin|Radford, Ernest|Watts-Dunton, Theodore|Cockerell Sydney Sir",British|British|British|British|British,1853 |1872 |1870 |1850 |1832 |1910,1943 |1898 |1951 |1950 |1914 |1957,1896,1896,1896,Platinum print,,"Purchase, David Hunter McAlpin Fund, 1968",,,,,,,,,,,,Albums,,http://www.metmuseum.org/art/collection/search/675551,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -68.519 (29),false,true,675555,Photographs,Album; Artist's book,"[Tithe Barn, Great Cokkeswell]",,,,,,Artist|Artist|Artist|Artist|Author|Correspondent|Correspondent,Bookplate designed by|Bookplate designed by,Frederick H. Evans|Unknown|Aubrey Vincent Beardsley|Frederick Colin Tilney|Ernest Radford|Theodore Watts-Dunton|Sir Sydney Cockerell,"British, London 1853–1943 London|British|British, Brighton, Sussex 1872–1898 Menton|British, 1870–1951|British, active late 19th century|British, 1832–1914|1910–1957",,"Evans, Frederick Henry|Unknown|Beardsley, Aubrey Vincent|Tilney, Frederick Colin|Radford, Ernest|Watts-Dunton, Theodore|Cockerell Sydney Sir",British|British|British|British|British,1853 |1872 |1870 |1850 |1832 |1910,1943 |1898 |1951 |1950 |1914 |1957,1896,1896,1896,Platinum print,,"Purchase, David Hunter McAlpin Fund, 1968",,,,,,,,,,,,Albums,,http://www.metmuseum.org/art/collection/search/675555,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -68.519 (30),false,true,675556,Photographs,Album; Artist's book,"Tithe Barn, Great Cokkeswell, Interior",,,,,,Artist|Artist|Artist|Artist|Author|Correspondent|Correspondent,Bookplate designed by|Bookplate designed by,Frederick H. Evans|Unknown|Aubrey Vincent Beardsley|Frederick Colin Tilney|Ernest Radford|Theodore Watts-Dunton|Sir Sydney Cockerell,"British, London 1853–1943 London|British|British, Brighton, Sussex 1872–1898 Menton|British, 1870–1951|British, active late 19th century|British, 1832–1914|1910–1957",,"Evans, Frederick Henry|Unknown|Beardsley, Aubrey Vincent|Tilney, Frederick Colin|Radford, Ernest|Watts-Dunton, Theodore|Cockerell Sydney Sir",British|British|British|British|British,1853 |1872 |1870 |1850 |1832 |1910,1943 |1898 |1951 |1950 |1914 |1957,1896,1896,1896,Platinum print,,"Purchase, David Hunter McAlpin Fund, 1968",,,,,,,,,,,,Albums,,http://www.metmuseum.org/art/collection/search/675556,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -68.519 (31),false,true,675557,Photographs,Album; Artist's book,[Kelmscott Manor from the Garden],,,,,,Artist|Artist|Artist|Artist|Author|Correspondent|Correspondent,Bookplate designed by|Bookplate designed by,Frederick H. Evans|Unknown|Aubrey Vincent Beardsley|Frederick Colin Tilney|Ernest Radford|Theodore Watts-Dunton|Sir Sydney Cockerell,"British, London 1853–1943 London|British|British, Brighton, Sussex 1872–1898 Menton|British, 1870–1951|British, active late 19th century|British, 1832–1914|1910–1957",,"Evans, Frederick Henry|Unknown|Beardsley, Aubrey Vincent|Tilney, Frederick Colin|Radford, Ernest|Watts-Dunton, Theodore|Cockerell Sydney Sir",British|British|British|British|British,1853 |1872 |1870 |1850 |1832 |1910,1943 |1898 |1951 |1950 |1914 |1957,1896,1896,1896,Platinum print,,"Purchase, David Hunter McAlpin Fund, 1968",,,,,,,,,,,,Albums,,http://www.metmuseum.org/art/collection/search/675557,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -68.519 (7),false,true,675533,Photographs,Album; Artist's book,[Main Entrance],,,,,,Author|Correspondent|Correspondent|Artist|Artist|Artist|Artist,Bookplate designed by|Bookplate designed by,Ernest Radford|Theodore Watts-Dunton|Sir Sydney Cockerell|Frederick H. Evans|Unknown|Aubrey Vincent Beardsley|Frederick Colin Tilney,"British, active late 19th century|British, 1832–1914|1910–1957|British, London 1853–1943 London|British|British, Brighton, Sussex 1872–1898 Menton|British, 1870–1951",,"Radford, Ernest|Watts-Dunton, Theodore|Cockerell Sydney Sir|Evans, Frederick Henry|Unknown|Beardsley, Aubrey Vincent|Tilney, Frederick Colin",British|British|British|British|British,1850 |1832 |1910 |1853 |1872 |1870,1950 |1914 |1957 |1943 |1898 |1951,1896,1896,1896,Platinum print,,"Purchase, David Hunter McAlpin Fund, 1968",,,,,,,,,,,,Albums,,http://www.metmuseum.org/art/collection/search/675533,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -68.519 (19),false,true,675545,Photographs,Album; Artist's book,Tapestry Room,,,,,,Author|Correspondent|Correspondent|Artist|Artist|Artist|Artist,Bookplate designed by|Bookplate designed by,Ernest Radford|Theodore Watts-Dunton|Sir Sydney Cockerell|Frederick H. Evans|Unknown|Aubrey Vincent Beardsley|Frederick Colin Tilney,"British, active late 19th century|British, 1832–1914|1910–1957|British, London 1853–1943 London|British|British, Brighton, Sussex 1872–1898 Menton|British, 1870–1951",,"Radford, Ernest|Watts-Dunton, Theodore|Cockerell Sydney Sir|Evans, Frederick Henry|Unknown|Beardsley, Aubrey Vincent|Tilney, Frederick Colin",British|British|British|British|British,1850 |1832 |1910 |1853 |1872 |1870,1950 |1914 |1957 |1943 |1898 |1951,1896,1896,1896,Platinum print,,"Purchase, David Hunter McAlpin Fund, 1968",,,,,,,,,,,,Albums,,http://www.metmuseum.org/art/collection/search/675545,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1994.129,false,true,266993,Photographs,Album,[Album of 226 albumen silver prints of Japan],,,,,,Artist|Artist|Editor|Artist|Artist|Photography Studio|Publisher,,Raimund von Stillfried|Felice Beato|Stillfried and Andersen|Kusakabe Kimbei|Tamamura Kōzaburō|Yamamoto Studio|Stillfried and Andersen,"Austrian, 1839–1911|British (born Italy), Venice 1832–1909 Luxor, Egypt|active Japan, 1875–1885|Japanese, 1841–1934|Japanese, 1856–1923?|Japanese|active Japan, 1875–1885",,"Stillfried, Raimond von|Beato, Felice|Stillfried and Andersen|Kusakabe, Kimbei|Tamamura, Kōzaburō|Yamamoto Studio|Stillfried and Andersen","Austrian|British, born Italy|Japanese|Japanese|Japanese",1839 |1832 |1875 |1841 |1856 |1875,1911 |1909 |1885 |1934 |1923 |1885,1860s–90s,1860,1899,Albumen silver prints,,"Gift of H. de Rassloff, 1918, transferred from the Library",,,,,,,,,,,,Albums,,http://www.metmuseum.org/art/collection/search/266993,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -52.641,false,true,269626,Photographs,Photographically illustrated book,"La Photographie ses origines, ses progrès, ses transformations",,,,,,Artist|Artist|Artist|Artist|Artist|Artist|Artist|Artist|Artist|Artist|Artist|Artist|Artist,,Goupil et Cie|Édouard Baldus|B. Deloose|M. Marion|Henri Garnier|Zurcher|François-Auguste Renard|Joseph Nicéphore Niépce|Maxime Du Camp|Ernest Edwards|Louis-Désiré Blanquart-Évrard|Adolphe Braun|Frères Dujardin,"French, active 1850–84|French, born Prussia, 1813–1889|French|French|French, 1765–1833|French, 1822–1894|British, 1837–1903|French, 1802–1872|French, Besançon 1811–1877 Dornach|French",,"Goupil et Cie|Baldus, Édouard|Deloose, B.|Marion, M.|Garnier, Henri|Zurcher|Renard, François-Auguste|Niépce, Joseph Nicéphore|Du Camp, Maxime|Edwards, Ernest|Blanquart-Évrard, Louis-Désiré|Braun, Adolphe|Dujardin, Frères","French|French, born Prussia|French|French|French|French|British|French|French|French",1850 |1813 |1765 |1822 |1837 |1802 |1811,1884 |1889 |1833 |1894 |1903 |1872 |1877,1870s,1870,1879,Multiple photographic processes,,"David Hunter McAlpin Fund, 1952",,,,,,,,,,,,Books,,http://www.metmuseum.org/art/collection/search/269626,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1982.1182.1158–.1187,false,true,288304,Photographs,Stereographs,"[Group of 30 Stereograph Views of Colorado and Arizona, United States of America]",,,,,,Publisher|Artist|Publisher|Publisher|Artist|Artist|Publisher|Artist|Publisher|Publisher|Publisher|Artist|Publisher|Artist|Person in Photograph|Publisher,,Underwood & Underwood|Benneville Lloyd Singley|H. C. White Company|American Scenery|Strohmeyer & Wyman|Charles Weitfle|Barkalow Brothers|C. H. Graves|Universal Photo Art Co.|American Views|Montgomery Ward & Co.|William H. Rau|Continent Stereoscopic Company|Unknown|Grace Greenwood|Keystone View Company,"American|American, Union Township, Pennsylvania 1864–1938 Meadville, Pennsylvania|American|American|American|American, born Germany, 1836–after 1884|American|American|American|American, Chicago, Illinois|American, 1855–1920|New York|American|American, 1823–1904",,"Underwood & Underwood|Singley, Benneville Lloyd|H. C. White Company|American Scenery|Strohmeyer & Wyman|Weitfle, Charles|Barkalow Brothers|Graves, C. H.|Universal Photo Art Co.|American Views|Montgomery Ward & Co.|Rau, William H.|Continent Stereoscopic Company|Unknown|Greenwood, Grace|Keystone View Company",American|American|American|American|American|American|American|American|American|American|American|New York|American,1864 |1836 |1855 |1823,1938 |1884 |1920 |1904,1850s–1910s,1850,1919,Albumen silver prints,Mounts approximately: 8.8 x 17.3 cm (3 7/16 x 6 13/16 in.) to 10 x 17.8 cm (3 15/16 x 7 in.),"Gift of Weston J. Naef, in memory of Kathleen W. Naef and Weston J. Naef Sr., 1982",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288304,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1982.1182.256–.298,false,true,288121,Photographs,Stereographs,[Group of 43 Stereograph Views of Astronomy Related Scenes],,,,,,Patron|Publisher|Publisher|Artist|Artist|Publisher|Publisher|Publisher|Publisher|Publisher|Photography Studio|Publisher|Publisher|Publisher|Artist|Publisher|Artist|Artist|Publisher|Publisher|Artist|Publisher|Publisher|Artist|Artist|Publisher,Commissioned by,"Nautical Almanac Office|New H Series|Charles Bierstadt|Professor H. Draper|L. M. Rutherford|Popular Series|T. W. Ingersoll|Stereo Gems|Liberty Brand Stereo Views|Underwood & Underwood|Sun Sculpture Works and Studios|Strohmeyer & Wyman|Yerkes Observatory|Carnegie Intitute|Kilburn Brothers|Edward Kilburn|William N. Hobbs|G. R. Proctor|Edward Bierstadt|Whiple|Deloss Barnum|E. & H. T. Anthony|Smith, Beck & Beck|G. W. Thorne|Unknown|Keystone View Company","American|American, 1819–1903|American|American|American|American|American|American|American|American, active ca. 1865–1890|American, 1830–1884|American|American|American, born Germany, born 1824|American|American, 1825–1873 Cortland, New York|American|British|American",,"Nautical Almanac Office|New H Series|Bierstadt, Charles|Draper, H. Professor|Rutherford, L. M.|Popular Series|Ingersoll, T. W.|Stereo Gems|Liberty Brand Stereo Views|Underwood & Underwood|Sun Sculpture Works and Studios|Strohmeyer & Wyman|Yerkes Observatory|Carnegie Intitute|Kilburn Brothers|Kilburn, Edward|Hobbs, William N.|Proctor, G. R.|Bierstadt, Edward|Whiple|Barnum, Deloss|E. & H. T. Anthony|Smith, Beck & Beck|Thorne, G. W.|Unknown|Keystone View Company","American|American|American|American|American|American|American|American|American|American|American|American|American|American, born Germany|American|American|American|British|American",1819 |1863 |1830 |1824 |1825,1903 |1892 |1884 |1873,1850s–1910s,1850,1919,Albumen silver prints,Mounts approximately: 8.3 x 17.1 cm (3 1/4 x 6 3/4 in.) to 8.9 x 17.8 cm (3 1/2 x 7 in.),"Gift of Weston J. Naef, in memory of Kathleen W. Naef and Weston J. Naef Sr., 1982",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288121,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.500.3 (1),false,true,679863,Photographs,Photograph,Jérusalem,,,,,,Artist|Lithographer|Printer,,Louis de Clercq|H. Jannin|J. Blondeau et Antonin,"French, 1837–1901|French",,"de Clercq, Louis|Jannin, H.|J. Blondeau et Antonin",French|French|French,1837,1901,1860 or later,1860,1860,Albumen silver print from paper negative,Image: 8 1/8 × 31 1/2 in. (20.7 × 80 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/679863,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.500.3 (2),false,true,679864,Photographs,Photograph,"Jérusalem. État actuel du dôme, du St. Sépulcre et Minaret d'Omar",,,,,,Artist|Lithographer|Printer,,Louis de Clercq|H. Jannin|J. Blondeau et Antonin,"French, 1837–1901|French",,"de Clercq, Louis|Jannin, H.|J. Blondeau et Antonin",French|French|French,1837,1901,1860 or later,1860,1860,Albumen silver print from paper negative,Image: 8 3/16 × 10 7/8 in. (20.8 × 27.7 cm) Mount: 17 15/16 × 23 1/4 in. (45.5 × 59 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/679864,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.500.3 (3),false,true,679865,Photographs,Photograph,Jérusalem. Entrée de l'Église du St. Sépulcre,,,,,,Artist|Lithographer|Printer,,Louis de Clercq|H. Jannin|J. Blondeau et Antonin,"French, 1837–1901|French",,"de Clercq, Louis|Jannin, H.|J. Blondeau et Antonin",French|French|French,1837,1901,1860 or later,1860,1860,Albumen silver print from paper negative,Image: 10 13/16 × 8 5/16 in. (27.5 × 21.1 cm) Mount: 17 15/16 × 23 1/4 in. (45.5 × 59 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/679865,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.500.3 (4),false,true,679866,Photographs,Photograph,Jérusalem. Tour de David avec ses grandes assises salomoniennes,,,,,,Artist|Lithographer|Printer,,Louis de Clercq|H. Jannin|J. Blondeau et Antonin,"French, 1837–1901|French",,"de Clercq, Louis|Jannin, H.|J. Blondeau et Antonin",French|French|French,1837,1901,1860 or later,1860,1860,Albumen silver print from paper negative,Image: 8 1/8 × 10 7/8 in. (20.6 × 27.7 cm) Mount: 17 15/16 × 23 1/4 in. (45.5 × 59 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/679866,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.500.3 (5),false,true,679867,Photographs,Photograph,Jérusalem. Église Ste Anne appartenant à la France,,,,,,Artist|Lithographer|Printer,,Louis de Clercq|H. Jannin|J. Blondeau et Antonin,"French, 1837–1901|French",,"de Clercq, Louis|Jannin, H.|J. Blondeau et Antonin",French|French|French,1837,1901,1860 or later,1860,1860,Albumen silver print from paper negative,Image: 8 7/16 in. × 11 in. (21.4 × 28 cm) Mount: 17 15/16 × 23 1/4 in. (45.5 × 59 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/679867,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.500.3 (6),false,true,679868,Photographs,Photograph,Jérusalem. Façade de l'Église Ste. Anne.,,,,,,Artist|Lithographer|Printer,,Louis de Clercq|H. Jannin|J. Blondeau et Antonin,"French, 1837–1901|French",,"de Clercq, Louis|Jannin, H.|J. Blondeau et Antonin",French|French|French,1837,1901,1860 or later,1860,1860,Albumen silver print from paper negative,Image: 11 in. × 8 9/16 in. (28 × 21.7 cm) Mount: 17 15/16 × 23 1/4 in. (45.5 × 59 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/679868,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.500.3 (7),false,true,679869,Photographs,Photograph,Jérusalem. Intérieur de l'Église Ste Anne.,,,,,,Artist|Lithographer|Printer,,Louis de Clercq|H. Jannin|J. Blondeau et Antonin,"French, 1837–1901|French",,"de Clercq, Louis|Jannin, H.|J. Blondeau et Antonin",French|French|French,1837,1901,1860 or later,1860,1860,Albumen silver print from paper negative,Image: 10 7/8 × 8 1/16 in. (27.7 × 20.5 cm) Mount: 17 15/16 × 23 1/4 in. (45.5 × 59 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/679869,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.500.3 (8),false,true,679870,Photographs,Photograph,Jérusalem. Hospice autrichien et ancienne Église St Jean,,,,,,Artist|Lithographer|Printer,,Louis de Clercq|H. Jannin|J. Blondeau et Antonin,"French, 1837–1901|French",,"de Clercq, Louis|Jannin, H.|J. Blondeau et Antonin",French|French|French,1837,1901,1860 or later,1860,1860,Albumen silver print from paper negative,Image: 10 7/8 × 8 7/16 in. (27.7 × 21.4 cm) Mount: 17 15/16 × 23 1/4 in. (45.5 × 59 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/679870,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.500.3 (9),false,true,679871,Photographs,Photograph,Jérusalem. Tour Antonia et Environs,,,,,,Artist|Lithographer|Printer,,Louis de Clercq|H. Jannin|J. Blondeau et Antonin,"French, 1837–1901|French",,"de Clercq, Louis|Jannin, H.|J. Blondeau et Antonin",French|French|French,1837,1901,1860 or later,1860,1860,Albumen silver print from paper negative,Image: 8 1/8 × 11 1/4 in. (20.7 × 28.5 cm) Mount: 17 15/16 × 23 1/4 in. (45.5 × 59 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/679871,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.500.3 (10),false,true,679872,Photographs,Photograph,Jérusalem. Massif de la Tour Antonia,,,,,,Artist|Lithographer|Printer,,Louis de Clercq|H. Jannin|J. Blondeau et Antonin,"French, 1837–1901|French",,"de Clercq, Louis|Jannin, H.|J. Blondeau et Antonin",French|French|French,1837,1901,1860 or later,1860,1860,Albumen silver print from paper negative,Image: 10 13/16 × 8 1/16 in. (27.5 × 20.5 cm) Mount: 17 15/16 × 23 1/4 in. (45.5 × 59 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/679872,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.500.3 (11),false,true,679873,Photographs,Photograph,"Jérusalem. Mosquée d'Omar, construite sur l'emplacement su Temple de Salomon",,,,,,Artist|Lithographer|Printer,,Louis de Clercq|H. Jannin|J. Blondeau et Antonin,"French, 1837–1901|French",,"de Clercq, Louis|Jannin, H.|J. Blondeau et Antonin",French|French|French,1837,1901,1860 or later,1860,1860,Albumen silver print from paper negative,Image: 8 7/16 × 11 1/8 in. (21.5 × 28.2 cm) Mount: 17 15/16 × 23 1/4 in. (45.5 × 59 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/679873,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.500.3 (12),false,true,679874,Photographs,Photograph,Jérusalem. Mur oú pleurent les juifs. Grandes Assises du Temple de Salomon,,,,,,Artist|Lithographer|Printer,,Louis de Clercq|H. Jannin|J. Blondeau et Antonin,"French, 1837–1901|French",,"de Clercq, Louis|Jannin, H.|J. Blondeau et Antonin",French|French|French,1837,1901,1860 or later,1860,1860,Albumen silver print from paper negative,Image: 8 5/8 × 11 1/8 in. (21.9 × 28.3 cm) Mount: 17 15/16 × 23 1/4 in. (45.5 × 59 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/679874,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.500.3 (13),false,true,679875,Photographs,Photograph,Jérusalem. Une rue de Jérusalem et entrée du Grand Couvent,,,,,,Artist|Lithographer|Printer,,Louis de Clercq|H. Jannin|J. Blondeau et Antonin,"French, 1837–1901|French",,"de Clercq, Louis|Jannin, H.|J. Blondeau et Antonin",French|French|French,1837,1901,1860 or later,1860,1860,Albumen silver print from paper negative,Image: 11 1/8 × 8 3/8 in. (28.2 × 21.3 cm) Mount: 17 15/16 × 23 1/4 in. (45.5 × 59 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/679875,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.500.3 (14),false,true,679876,Photographs,Photograph,Jérusalem. Vue des Remparts.,,,,,,Artist|Lithographer|Printer,,Louis de Clercq|H. Jannin|J. Blondeau et Antonin,"French, 1837–1901|French",,"de Clercq, Louis|Jannin, H.|J. Blondeau et Antonin",French|French|French,1837,1901,1860 or later,1860,1860,Albumen silver print from paper negative,Image: 7 3/8 × 10 3/16 in. (18.8 × 25.8 cm) Mount: 17 15/16 × 23 1/4 in. (45.5 × 59 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/679876,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.500.3 (15),false,true,679877,Photographs,Photograph,Jérusalem. Chapelle protestante et environs,,,,,,Artist|Lithographer|Printer,,Louis de Clercq|H. Jannin|J. Blondeau et Antonin,"French, 1837–1901|French",,"de Clercq, Louis|Jannin, H.|J. Blondeau et Antonin",French|French|French,1837,1901,1860 or later,1860,1860,Albumen silver print from paper negative,Image: 8 in. × 10 1/4 in. (20.3 × 26 cm) Mount: 17 15/16 × 23 1/4 in. (45.5 × 59 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/679877,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.500.3 (16),false,true,679878,Photographs,Photograph,Jérusalem. Synagogue juive et environs,,,,,,Artist|Lithographer|Printer,,Louis de Clercq|H. Jannin|J. Blondeau et Antonin,"French, 1837–1901|French",,"de Clercq, Louis|Jannin, H.|J. Blondeau et Antonin",French|French|French,1837,1901,1860 or later,1860,1860,Albumen silver print from paper negative,Image: 8 in. × 10 1/4 in. (20.3 × 26 cm) Mount: 17 15/16 × 23 1/4 in. (45.5 × 59 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/679878,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.500.3 (17),false,true,679879,Photographs,Photograph,Jérusalem. Porte de Hebron et de Jaffa. (Bab-el-Khalil),,,,,,Artist|Lithographer|Printer,,Louis de Clercq|H. Jannin|J. Blondeau et Antonin,"French, 1837–1901|French",,"de Clercq, Louis|Jannin, H.|J. Blondeau et Antonin",French|French|French,1837,1901,1860 or later,1860,1860,Albumen silver print from paper negative,Image: 11 3/16 × 8 7/16 in. (28.4 × 21.4 cm) Mount: 17 15/16 × 23 1/4 in. (45.5 × 59 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/679879,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.500.3 (18),false,true,679880,Photographs,Photograph,Jérusalem. Porte de Damas ou des colonnes (Bab-el-Ahmoud),,,,,,Artist|Lithographer|Printer,,Louis de Clercq|H. Jannin|J. Blondeau et Antonin,"French, 1837–1901|French",,"de Clercq, Louis|Jannin, H.|J. Blondeau et Antonin",French|French|French,1837,1901,1860 or later,1860,1860,Albumen silver print from paper negative,Image: 8 7/16 × 11 1/8 in. (21.4 × 28.2 cm) Mount: 17 15/16 × 23 1/4 in. (45.5 × 59 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/679880,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.500.3 (19),false,true,679881,Photographs,Photograph,Jérusalem. Porte de Damas (Bab-el-Ahmoud),,,,,,Artist|Lithographer|Printer,,Louis de Clercq|H. Jannin|J. Blondeau et Antonin,"French, 1837–1901|French",,"de Clercq, Louis|Jannin, H.|J. Blondeau et Antonin",French|French|French,1837,1901,1860 or later,1860,1860,Albumen silver print from paper negative,Image: 11 in. × 8 9/16 in. (28 × 21.7 cm) Mount: 17 15/16 × 23 1/4 in. (45.5 × 59 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/679881,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.500.3 (21),false,true,679883,Photographs,Photograph,Jérusalem. (Environs) Montagne de Sion. Cenacle et Maison de Caiphe.,,,,,,Artist|Lithographer|Printer,,Louis de Clercq|H. Jannin|J. Blondeau et Antonin,"French, 1837–1901|French",,"de Clercq, Louis|Jannin, H.|J. Blondeau et Antonin",French|French|French,1837,1901,1860 or later,1860,1860,Albumen silver print from paper negative,Image: 11 in. × 8 1/2 in. (28 × 21.6 cm) Mount: 17 15/16 × 23 1/4 in. (45.5 × 59 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/679883,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.500.3 (22),false,true,679884,Photographs,Photograph,Jérusalem. (Environs) Jardin Gethsemani et Mont des Oliviers,,,,,,Artist|Lithographer|Printer,,Louis de Clercq|H. Jannin|J. Blondeau et Antonin,"French, 1837–1901|French",,"de Clercq, Louis|Jannin, H.|J. Blondeau et Antonin",French|French|French,1837,1901,1860 or later,1860,1860,Albumen silver print from paper negative,Image: 11 in. × 8 1/2 in. (28 × 21.6 cm) Mount: 17 15/16 × 23 1/4 in. (45.5 × 59 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/679884,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.500.3 (23),false,true,679885,Photographs,Photograph,Jérusalem. (Environs) Grotte de Jérémie,,,,,,Artist|Lithographer|Printer,,Louis de Clercq|H. Jannin|J. Blondeau et Antonin,"French, 1837–1901|French",,"de Clercq, Louis|Jannin, H.|J. Blondeau et Antonin",French|French|French,1837,1901,1860 or later,1860,1860,Albumen silver print from paper negative,Image: 11 in. × 8 1/4 in. (28 × 21 cm) Mount: 17 15/16 × 23 1/4 in. (45.5 × 59 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/679885,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.500.3 (24),false,true,679886,Photographs,Photograph,Jérusalem. (Environs) Tombeau des Rois,,,,,,Artist|Lithographer|Printer,,Louis de Clercq|H. Jannin|J. Blondeau et Antonin,"French, 1837–1901|French",,"de Clercq, Louis|Jannin, H.|J. Blondeau et Antonin",French|French|French,1837,1901,1860 or later,1860,1860,Albumen silver print from paper negative,Image: 10 13/16 × 8 3/8 in. (27.5 × 21.2 cm) Mount: 17 15/16 × 23 1/4 in. (45.5 × 59 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/679886,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.500.3 (25),false,true,679887,Photographs,Photograph,Jérusalem. (Environs) Tombeau de la vierge,,,,,,Artist|Lithographer|Printer,,Louis de Clercq|H. Jannin|J. Blondeau et Antonin,"French, 1837–1901|French",,"de Clercq, Louis|Jannin, H.|J. Blondeau et Antonin",French|French|French,1837,1901,1860 or later,1860,1860,Albumen silver print from paper negative,Image: 11 in. × 8 1/4 in. (28 × 21 cm) Mount: 17 15/16 × 23 1/4 in. (45.5 × 59 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/679887,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.500.3 (26),false,true,679888,Photographs,Photograph,Jérusalem. (Environs) Tombeau d'Absalon,,,,,,Artist|Lithographer|Printer,,Louis de Clercq|H. Jannin|J. Blondeau et Antonin,"French, 1837–1901|French",,"de Clercq, Louis|Jannin, H.|J. Blondeau et Antonin",French|French|French,1837,1901,1860 or later,1860,1860,Albumen silver print from paper negative,Image: 8 9/16 × 10 7/8 in. (21.8 × 27.7 cm) Mount: 17 15/16 × 23 1/4 in. (45.5 × 59 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/679888,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.500.3 (27),false,true,679889,Photographs,Photograph,Jérusalem. Tombeaux de St Jacques et de Zacharie,,,,,,Artist|Lithographer|Printer,,Louis de Clercq|H. Jannin|J. Blondeau et Antonin,"French, 1837–1901|French",,"de Clercq, Louis|Jannin, H.|J. Blondeau et Antonin",French|French|French,1837,1901,1860 or later,1860,1860,Albumen silver print from paper negative,Image: 8 11/16 × 11 1/4 in. (22 × 28.5 cm) Mount: 17 15/16 × 23 1/4 in. (45.5 × 59 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/679889,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.500.3 (28),false,true,679890,Photographs,Photograph,Jérusalem. (Environs) St Jean du Désert,,,,,,Artist|Lithographer|Printer,,Louis de Clercq|H. Jannin|J. Blondeau et Antonin,"French, 1837–1901|French",,"de Clercq, Louis|Jannin, H.|J. Blondeau et Antonin",French|French|French,1837,1901,1860 or later,1860,1860,Albumen silver print from paper negative,Image: 11 in. × 8 3/8 in. (28 × 21.3 cm) Mount: 17 15/16 × 23 1/4 in. (45.5 × 59 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/679890,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.500.3 (29),false,true,679891,Photographs,Photograph,Arcade de l'Ecce Homo. Ponce Pilate présente Jésus au Peuple,,,,,,Artist|Lithographer|Printer,,Louis de Clercq|H. Jannin|J. Blondeau et Antonin,"French, 1837–1901|French",,"de Clercq, Louis|Jannin, H.|J. Blondeau et Antonin",French|French|French,1837,1901,1860 or later,1860,1860,Albumen silver print from paper negative,Image: 10 13/16 × 8 3/16 in. (27.4 × 20.8 cm) Mount: 17 15/16 × 23 1/4 in. (45.5 × 59 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/679891,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.500.3 (30),false,true,679892,Photographs,Photograph,Entrée d'une caserne turque. C'est a cette porte que les pèlerins font les prières de la 1e Station. N'ayant pas la permission d'entrer dans la caserne,,,,,,Artist|Lithographer|Printer,,Louis de Clercq|H. Jannin|J. Blondeau et Antonin,"French, 1837–1901|French",,"de Clercq, Louis|Jannin, H.|J. Blondeau et Antonin",French|French|French,1837,1901,1860 or later,1860,1860,Albumen silver print from paper negative,Image: 10 9/16 in. × 8 in. (26.8 × 20.3 cm) Mount: 17 15/16 × 23 1/4 in. (45.5 × 59 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/679892,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.500.3 (31),false,true,679893,Photographs,Photograph,Ie Station. Jésus est condamné à mort. Une cour intérieure de la caserne turque où la tradition place l'endroit du prêtoire où Jésus fut jugé,,,,,,Artist|Lithographer|Printer,,Louis de Clercq|H. Jannin|J. Blondeau et Antonin,"French, 1837–1901|French",,"de Clercq, Louis|Jannin, H.|J. Blondeau et Antonin",French|French|French,1837,1901,1860 or later,1860,1860,Albumen silver print from paper negative,Image: 11 5/16 × 8 1/2 in. (28.7 × 21.6 cm) Mount: 17 15/16 × 23 1/4 in. (45.5 × 59 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/679893,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.500.3 (32),false,true,679894,Photographs,Photograph,"IIe Station. Jésus est chargé de sa croix. Cette Station est placée au bas de la scala santa qui a été entièrement transportée à Rome, il n'en reste que les première assises",,,,,,Artist|Lithographer|Printer,,Louis de Clercq|H. Jannin|J. Blondeau et Antonin,"French, 1837–1901|French",,"de Clercq, Louis|Jannin, H.|J. Blondeau et Antonin",French|French|French,1837,1901,1860 or later,1860,1860,Albumen silver print from paper negative,Image: 10 11/16 × 8 1/4 in. (27.1 × 20.9 cm) Mount: 17 15/16 × 23 1/4 in. (45.5 × 59 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/679894,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.500.3 (33),false,true,679895,Photographs,Photograph,IIIe Station. Jésus tombe pour la première fois. Une colonne brisée et entendue a terre indique la place de cette station au lieu où la voie douloureuse tourne brusquement a gauche,,,,,,Artist|Lithographer|Printer,,Louis de Clercq|H. Jannin|J. Blondeau et Antonin,"French, 1837–1901|French",,"de Clercq, Louis|Jannin, H.|J. Blondeau et Antonin",French|French|French,1837,1901,1860 or later,1860,1860,Albumen silver print from paper negative,Image: 11 in. × 8 1/4 in. (28 × 21 cm) Mount: 17 15/16 × 23 1/4 in. (45.5 × 59 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/679895,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.500.3 (34),false,true,679896,Photographs,Photograph,IVe Station. Jésus rencontre sa très Sainte Mère. Cette station est située a quelques pas a peine de la précédente. La tradition la place a l'arcade que l'on voit représentée ici,,,,,,Artist|Lithographer|Printer,,Louis de Clercq|H. Jannin|J. Blondeau et Antonin,"French, 1837–1901|French",,"de Clercq, Louis|Jannin, H.|J. Blondeau et Antonin",French|French|French,1837,1901,1860 or later,1860,1860,Albumen silver print from paper negative,Image: 10 11/16 × 8 1/4 in. (27.2 × 21 cm) Mount: 17 15/16 × 23 1/4 in. (45.5 × 59 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/679896,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.500.3 (35),false,true,679897,Photographs,Photograph,Ve Station. Jésus aidé par Simon de Cyrène. Une marque dans le mur indique seule cette station. La maison que l'on voit au fond est celle du mauvais riche,,,,,,Artist|Lithographer|Printer,,Louis de Clercq|H. Jannin|J. Blondeau et Antonin,"French, 1837–1901|French",,"de Clercq, Louis|Jannin, H.|J. Blondeau et Antonin",French|French|French,1837,1901,1860 or later,1860,1860,Albumen silver print from paper negative,Image: 11 3/16 × 8 1/4 in. (28.4 × 21 cm) Mount: 17 15/16 × 23 1/4 in. (45.5 × 59 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/679897,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.500.3 (36),false,true,679898,Photographs,Photograph,VIe Station. Ste Véronique essuie la face sanglante de Jésus. Aucune marque extérieure n'indique cette station. La tradition la place au pied du petit escalier,,,,,,Artist|Lithographer|Printer,,Louis de Clercq|H. Jannin|J. Blondeau et Antonin,"French, 1837–1901|French",,"de Clercq, Louis|Jannin, H.|J. Blondeau et Antonin",French|French|French,1837,1901,1860 or later,1860,1860,Albumen silver print from paper negative,Image: 11 in. × 8 7/16 in. (28 × 21.5 cm) Mount: 17 15/16 × 23 1/4 in. (45.5 × 59 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/679898,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.500.3 (37),false,true,679899,Photographs,Photograph,"VIIe Station. Jésus tombe pour la seconde fois. Ancienne porte judiciaire, il s'y trouvait une colonne ou l'on exposait les condamnés; Elle éxiste encore cachée dans l'intérieur de la maison a gauche",,,,,,Artist|Lithographer|Printer,,Louis de Clercq|H. Jannin|J. Blondeau et Antonin,"French, 1837–1901|French",,"de Clercq, Louis|Jannin, H.|J. Blondeau et Antonin",French|French|French,1837,1901,1860 or later,1860,1860,Albumen silver print from paper negative,Image: 11 in. × 8 1/8 in. (28 × 20.7 cm) Mount: 17 15/16 × 23 1/4 in. (45.5 × 59 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/679899,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.500.3 (38),false,true,679900,Photographs,Photograph,VIIIe Station. Jésus console les filles de Jérusalem. Ici encore une simple marque faite sur le fut d'une colonne encastrée dans le mur indique la station.,,,,,,Artist|Lithographer|Printer,,Louis de Clercq|H. Jannin|J. Blondeau et Antonin,"French, 1837–1901|French",,"de Clercq, Louis|Jannin, H.|J. Blondeau et Antonin",French|French|French,1837,1901,1860 or later,1860,1860,Albumen silver print from paper negative,Image: 10 13/16 × 8 7/16 in. (27.5 × 21.5 cm) Mount: 17 15/16 × 23 1/4 in. (45.5 × 59 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/679900,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.500.3 (39),false,true,679901,Photographs,Photograph,IXe Station. Jésus tombe pour la troisième fois. Le fut de colonne qui se trouve au pied du mur indique cette station.,,,,,,Artist|Lithographer|Printer,,Louis de Clercq|H. Jannin|J. Blondeau et Antonin,"French, 1837–1901|French",,"de Clercq, Louis|Jannin, H.|J. Blondeau et Antonin",French|French|French,1837,1901,1860 or later,1860,1860,Albumen silver print from paper negative,Image: 11 in. × 8 7/16 in. (28 × 21.5 cm) Mount: 17 15/16 × 23 1/4 in. (45.5 × 59 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/679901,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.500.3 (40),false,true,679902,Photographs,Photograph,Xe Station. Jésus est dépouillé de ses Vêtements. L'emplacement de cette station est indique par la mosaïque en marbre que l'on voit devant l'autel.,,,,,,Artist|Lithographer|Printer,,Louis de Clercq|H. Jannin|J. Blondeau et Antonin,"French, 1837–1901|French",,"de Clercq, Louis|Jannin, H.|J. Blondeau et Antonin",French|French|French,1837,1901,1860 or later,1860,1860,Albumen silver print from paper negative,Image: 10 1/16 × 7 13/16 in. (25.6 × 19.8 cm) Mount: 17 15/16 × 23 1/4 in. (45.5 × 59 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/679902,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.500.3 (41),false,true,679903,Photographs,Photograph,XIe Station. Jésus est cloué sur la croix. Cette station se trouve placée au pied même de l'autel.,,,,,,Artist|Lithographer|Printer,,Louis de Clercq|H. Jannin|J. Blondeau et Antonin,"French, 1837–1901|French",,"de Clercq, Louis|Jannin, H.|J. Blondeau et Antonin",French|French|French,1837,1901,1860 or later,1860,1860,Albumen silver print from paper negative,Image: 10 1/8 × 7 7/8 in. (25.7 × 20 cm) Mount: 17 15/16 × 23 1/4 in. (45.5 × 59 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/679903,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.500.3 (42),false,true,679904,Photographs,Photograph,XIIe Station. Jésus meurt sur la croix. Autel élevé sur le lieu même ou le Christ a été crucifié,,,,,,Artist|Lithographer|Printer,,Louis de Clercq|H. Jannin|J. Blondeau et Antonin,"French, 1837–1901|French",,"de Clercq, Louis|Jannin, H.|J. Blondeau et Antonin",French|French|French,1837,1901,1860 or later,1860,1860,Albumen silver print from paper negative,Image: 10 1/8 × 7 7/8 in. (25.7 × 20 cm) Mount: 17 15/16 × 23 1/4 in. (45.5 × 59 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/679904,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.500.3 (43),false,true,679905,Photographs,Photograph,XIIIe Station. Jésus est remis entre les mains de sa mère. Cet autel est construit sur le rocher où se tenait la vierge marie pendant le crucifiement de son fils.,,,,,,Artist|Lithographer|Printer,,Louis de Clercq|H. Jannin|J. Blondeau et Antonin,"French, 1837–1901|French",,"de Clercq, Louis|Jannin, H.|J. Blondeau et Antonin",French|French|French,1837,1901,1860 or later,1860,1860,Albumen silver print from paper negative,Image: 10 1/8 × 7 7/8 in. (25.7 × 20 cm) Mount: 17 15/16 × 23 1/4 in. (45.5 × 59 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/679905,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.500.3 (44),false,true,679906,Photographs,Photograph,XIVe Station. Le corps de Jésus est deposé dans le tombeau. Monument du St Sépulcre où le corps du Christ a été enseveli.,,,,,,Artist|Lithographer|Printer,,Louis de Clercq|H. Jannin|J. Blondeau et Antonin,"French, 1837–1901|French",,"de Clercq, Louis|Jannin, H.|J. Blondeau et Antonin",French|French|French,1837,1901,1860 or later,1860,1860,Albumen silver print from paper negative,Image: 9 5/8 × 7 9/16 in. (24.5 × 19.2 cm) Mount: 17 15/16 × 23 1/4 in. (45.5 × 59 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/679906,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.500.3 (20),false,true,679882,Photographs,Photograph,Jérusalem. Portes Dorées,,,,,,Lithographer|Printer|Artist,,H. Jannin|J. Blondeau et Antonin|Louis de Clercq,"French|French, 1837–1901",,"Jannin, H.|J. Blondeau et Antonin|de Clercq, Louis",French|French|French,1837,1901,1860 or later,1860,1860,Albumen silver print from paper negative,Image: 8 3/8 × 10 3/4 in. (21.3 × 27.3 cm) Mount: 17 15/16 × 23 1/4 in. (45.5 × 59 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/679882,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.491.1 (3),false,true,287593,Photographs,Photograph,"[The Earl Canning, K.G., K.S.I., G.C.B., Calcutta]",,,,,,Artist|Artist|Artist,,Bourne and Shephard|Samuel Bourne|Charles Shepherd,"British, 1834–1912|British",,"Bourne and Shepherd|Bourne, Samuel|Shepherd, Charles",British|British,1834,1912,1858–61,1858,1861,Albumen silver print from glass negative,Image: 26.5 x 21.7 cm (10 7/16 x 8 9/16 in.) Mount: 33 x 26.4 cm (13 x 10 3/8 in.),"Gilman Collection, Purchase, Cynthia Hazen Polsky Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287593,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.69,false,true,290464,Photographs,Photograph,Village de Murols,,,,,,Artist|Printer,,Édouard Baldus|Chicago Albumen Works,"French, born Prussia, 1813–1889",,"Baldus, Édouard|Chicago Albumen Works","French, born Prussia",1813,1889,"1854, printed 1979",1854,1854,Salted paper print from paper negative,Image: 34.2 x 44.3 cm (13 7/16 x 17 7/16 in.) Sheet: 35.3 x 47.2 cm (13 7/8 x 18 9/16 in.),"The Horace W. Goldsmith Foundation Fund, through Joyce and Robert Menschel, 2009",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/290464,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1988.1112.1,false,true,265967,Photographs,Photograph,[Rocks in the Auvergne],,,,,,Artist|Printer,,Édouard Baldus|Chicago Albumen Works,"French, born Prussia, 1813–1889",,"Baldus, Édouard|Chicago Albumen Works","French, born Prussia",1813,1889,"1854, printed ca. 1981",1854,1854,Salted paper print from paper negative,Image: 33.9 x 44.8 cm. (13 3/6 x 17 5/8 in.),"Museum Accession, 1988",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/265967,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1982.1182.492–.591,false,true,288149,Photographs,Stereographs,[Group of 100 Stereograph Views of California Nature and Landscapes With a Focus on Yosemite],,,,,,Author|Artist|Artist|Artist|Publisher|Publisher|Publisher|Artist|Publisher|Publisher|Artist|Publisher|Artist|Publisher|Publisher|Publisher|Artist|Publisher|Publisher|Author|Photography Studio|Publisher|Publisher|Publisher|Artist|Publisher|Publisher|Publisher|Publisher|Publisher|Publisher|Publisher|Publisher|Publisher,,"James Mason Hutchings|G. H. Aldrich & Company|Unknown|Unknown|C. W. Woodward|Strohmeyer & Wyman|Underwood & Underwood|H. C. White Company|American Scenery|Views in California|American Stereoscopic Company|Quaker Oats Company|T. W. Ingersoll|S. F. Sanderson|New H Series|S. W. Kelley|George W. Griffith|Griffith & Griffith, American|Popular Series|John Muir|Sun Sculpture Works and Studios|Dodge, Collier, & Perkins|J. Merrill & Son|Continent Stereoscopic Company|Andrew Price|American Series|H. Ropes & Co., American|Canvassers|J. F. Jarvis|Standard Series|A. Fuller|Lovejoy & Foster|American Views|Keystone View Company","American (born England), 1820–1902|American|American|American|American|American|American|American|American|American|American|American|American|American|American, born Scotland, 1838–1914|American|American|American|New York|American|American|American|American|American|American|American",,"Hutchings, James Mason|G. H. Aldrich & Company|Unknown|Unknown|Woodward, C. W.|Strohmeyer & Wyman|Underwood & Underwood|H. C. White Company|American Scenery|Views in California|American Stereoscopic Company|Quaker Oats Company|Ingersoll, T. W.|Sanderson, S. F.|New H Series|Kelley, S. W.|Griffith, George W.|Griffith & Griffith|Popular Series|Muir, John|Sun Sculpture Works and Studios|Dodge, Collier, & Perkins|J. Merrill & Son|Continent Stereoscopic Company|Price, Andrew|American Series|Ropes & Co., H.|Canvassers|Jarvis, J. F.|Standard Series|Fuller, A.|Lovejoy & Foster|American Views|Keystone View Company","American|American|American|American|American|American|American|American|American|American|American|American|American|American|American, born Scotland|American|American|American|New York|American|American|American|American|American|American|American|American",1820 |1838,1902 |1914,1860s–1910s,1860,1919,Albumen silver prints,Mounts approximately: 8.6 x 17.5 cm (3 3/8 x 6 7/8 in.) to 11.4 x 17.8 cm (4 1/2 x 7 in.),"Gift of Weston J. Naef, in memory of Kathleen W. Naef and Weston J. Naef Sr., 1982",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288149,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1977.683.22,false,true,285455,Photographs,Photograph,Naser al-Din Shah,,,,,,Artist|Person in Photograph,Person in photograph,Unknown|Naser od-Din Shah,"Iranian, Tehran 1831–1896 Tehran",,"Unknown|Din, Naser od- Shah",Iranian,1831,1896,ca. 1852–55,1852,1855,Salted paper print from glass negative,Image: 33.7 x 21.2 cm (13 1/4 x 8 3/8 in.),"Gift of Charles K. and Irma B. Wilkinson, 1977",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285455,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -38.82.1,false,true,268623,Photographs,Photograph,[Woman Opening Parasol],,,,,,Artist|Printer,,Eadweard Muybridge|The Photo-Gravure Company,"American, born Britain, 1830–1904",,"Muybridge, Eadweard|Photo-Gravure Company","American, born Britain",1830,1904,"1883–86, printed 1887",1883,1886,Collotype,,"Gift of the Philadelphia Commercial Museum, 1938",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268623,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -38.82.3,false,true,268638,Photographs,Photograph,[Boys Playing Leap Frog],,,,,,Artist|Printer,,Eadweard Muybridge|The Photo-Gravure Company,"American, born Britain, 1830–1904",,"Muybridge, Eadweard|Photo-Gravure Company","American, born Britain",1830,1904,"1883–86, printed 1887",1883,1886,Collotype,,"Gift of the Philadelphia Commercial Museum, 1938",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268638,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1991.1135.9.69,false,true,266441,Photographs,Photograph,[Horse and Rider Galloping],,,,,,Artist|Printer,,Eadweard Muybridge|The Photo-Gravure Company,"American, born Britain, 1830–1904",,"Muybridge, Eadweard|Photo-Gravure Company","American, born Britain",1830,1904,"1883–86, printed 1887",1883,1886,Collotype,,"Rogers Fund, transferred from the Library",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/266441,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -X.673,false,true,271667,Photographs,Photograph,Queen Victoria Presiding at the Reopening of the Reconstructed Crystal Palace at Sydenham,,,,,,Former Attribution|Artist,Attributed to,Philip Henry Delamotte|T. R. Williams,"British, 1821–1889|British, born 1825",,"Delamotte, Philip Henry|Williams, T. R.",British|British,1821 |1825,1889 |1825,1854,1854,1854,Albumen silver print from glass negative,,Museum Accession,,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/271667,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1982.1182.1–.14,false,true,288103,Photographs,Stereographs,[Group of 14 stereographs of Africa and Actors],,,,,,Artist|Publisher|Artist|Artist|Publisher|Publisher,,"Kilburn Brothers|Underwood & Underwood|Unknown|H. Ropes & Co., American|F. G. Weller|Keystone View Company","American, active ca. 1865–1890|American",,"Kilburn Brothers|Underwood & Underwood|Unknown|Ropes & Co., H.|Weller., F. G.|Keystone View Company",American|American|American,1863,1892,1850s–1910s,1850,1919,Albumen silver prints,Mounts approximately: 8.2 x 17.1 cm (3 1/4 x 6 3/4 in.) to 8.9 x 17.8 cm (3 1/2 x 7 in.),"Gift of Weston J. Naef, in memory of Kathleen W. Naef and Weston J. Naef Sr., 1982",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288103,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1983.1163,false,true,263729,Photographs,Photograph,Copacabana,,,,,,Artist|Printer,,Marc Ferrez|Alan B. Newman,"Brazilian, 1843–1923|American, born 1946",,"Ferrez, Marc|Newman, Alan B.",Brazilian|American,1843 |1946,1923,"1880, printed 1983",1880,1880,Platinum print from glass negative,Image: 17.3 x 32.9 cm (6 13/16 x 12 15/16 in.) Mount: 28.4 x 38 cm (11 3/16 x 14 15/16 in.),"Gift of H. L. Hoffenberg, 1983",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/263729,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -46.122.16,false,true,269064,Photographs,Photograph,Étude d'arbre,,,,,,Artist|Printer,,"Edward King Tenison|Imprimerie photographique de Blanquart-Évrard, à Lille","Irish, 1805–1878|French, active 1851–55",,"Tenison, Edward King|Imprimerie photographique de Blanquart-Évrard, à Lille",Irish,1805 |1851,1878 |1855,1850–53,1850,1853,Salted paper print (Blanquart-Évrard process) from paper negative,,"Harris Brisbane Dick Fund, 1946",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269064,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.410 (1),false,true,288141,Photographs,Photograph,[Duchesse de Morny],,,,,,Artist|Artist|Person in Photograph,Painted and retouched by|Person in photograph,"Pierre-Louis Pierson|Marck|Sophie Troubetzkoi, duchesse de Morny","French, 1822–1913|Russian, Moscow 1838–1896",", et al","Pierson, Pierre-Louis|Marck|Troubetzkoi, Sophie, duchesse de Morny",French|Russian,1822 |1838,1913 |1896,before 1865,1855,1865,Albumen silver print from glass negative,Image: 6 5/16 × 4 3/4 in. (16 × 12 cm) Mount: 14 3/16 in. × 11 in. (36 × 28 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288141,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.41,false,true,676536,Photographs,Photograph,"Zouave, 2nd Division",,,,,,Artist|Publisher|Person in Photograph,Person in photograph,"Roger Fenton|Thomas Agnew & Sons, Ltd.|Roger Fenton","British, 1819–1869|London|British, 1819–1869",,"Fenton, Roger|Agnew, Thomas & Sons, Ltd.|Fenton, Roger",British|British,1819 |1819,1819 |1819,1855,1855,1855,Salted paper print from collodion glass negative,Image: 7 5/8 × 6 7/16 in. (19.4 × 16.4 cm) Mount: 23 5/8 × 17 1/4 in. (60 × 43.8 cm),"Purchase, Alfred Stieglitz Society Gifts, 2015",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/676536,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1982.1182.315–.385,false,true,288138,Photographs,Stereographs,"[Group of 71 Stereograph Views of African-Americans and Early Black American Culture, including Colloquial Black Humor]",,,,,,Publisher|Publisher|Publisher|Publisher|Artist|Artist|Publisher|Publisher|Publisher|Artist|Publisher|Photography Studio|Artist|Artist|Artist|Artist|Artist|Publisher|Publisher|Publisher|Artist|Artist|Artist|Publisher|Artist|Publisher|Publisher|Publisher|Publisher|Artist|Publisher|Artist|Publisher,,"Francis Hendricks|European and American Views|Webster & Albee|American Stereoscopic Company|J. Mullen|E. F. Smith|American Scenery|Life Groups|America Illustrated|Wilson & Havens|Underwood & Underwood|Sun Sculpture Works and Studios|Strohmeyer & Wyman|George Barker|Kilburn Brothers|H. C. White Company|T. W. Ingersoll|Canvassers|Continent Stereoscopic Company|Littleton View Company|Alfred S. Campbell|James M. Davis|C. H. Graves|Florida Novelties and Views|Griffith & Griffith, American|Metropolitan Series|Ingersoll View Company|Popular Series|Universal View Co.|William H. Rau|M. H. Zahner|Unknown|Keystone View Company","American, Syracuse, New York|American|American|American|American|American|American|American|American|American|American|American|American, born Canada, 1844–1894|American, active ca. 1865–1890|American|New York|American|American|American|American|American|American|American|American, 1855–1920|American",,"Hendricks, Francis|European and American Views|Webster & Albee|American Stereoscopic Company|Mullen, J.|Smith, E. F.|American Scenery|Life Groups|America Illustrated|Wilson & Havens|Underwood & Underwood|Sun Sculpture Works and Studios|Strohmeyer & Wyman|Barker, George|Kilburn Brothers|H. C. White Company|Ingersoll, T. W.|Canvassers|Continent Stereoscopic Company|Littleton View Company|Campbell, Alfred S.|Davis, James M.|Graves, C. H.|Florida Novelties and Views|Griffith & Griffith|Metropolitan Series|Ingersoll View Company|Popular Series|Universal View Co.|Rau, William H.|Zahner, M. H.|Unknown|Keystone View Company",American|American|American|American|American|American|American|American|American|American|American|American|American|American|American|New York|American|American|American|American|American|American|American|American|American|American,1844 |1863 |1855,1894 |1892 |1920,1850s–1910s,1850,1919,Albumen silver prints,Mounts approximately: 8.5 x 17.5 cm (3 3/8 x 6 7/8 in.) to 10.7 x 17.8 cm (4 3/16 x 7 in.),"Gift of Weston J. Naef, in memory of Kathleen W. Naef and Weston J. Naef Sr., 1982",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288138,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.196,false,true,285607,Photographs,Photograph,[Portrait in a White Dress],,,,,,Artist|Artist|Person in Photograph,Painted and retouched by,Pierre-Louis Pierson|Aquilin Schad|Countess Virginia Oldoini Verasis di Castiglione,"French, 1822–1913|Austrian, 1817–1866|1835–1899",,"Pierson, Pierre-Louis|Schad, Aquilin|Castiglione, di, Virginia Oldoini Verasis Countess",French|Austrian,1822 |1817 |1835,1913 |1866 |1899,"1856–57, printed 1861–66",1856,1866,Salted paper print from glass negative,Image: 32.5 x 27 cm (12 13/16 x 10 5/8 in.) Mount: 39.1 x 29.7 cm (15 3/8 x 11 11/16 in.) Mat: 61 x 50.8 cm (24 x 20 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285607,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -48.77,false,true,269223,Photographs,Photograph,Auguste Rodin,,,,,,Artist|Person in Photograph,,Gertrude Käsebier|Auguste Rodin,"American, 1852–1934|French, Paris 1840–1917 Meudon",,"Käsebier, Gertrude|Rodin, Auguste",American|French,1852 |1840,1934 |1917,1907,1907,1907,Platinum print,,"Gift of Adele Rollins Clifton, 1948",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269223,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.185,false,true,306170,Photographs,Photograph,View of the Arruns Pass and Peak from the Pont de Soubé,,,,,,Artist|Printer,,"John Stewart|Imprimerie photographique de Blanquart-Évrard, à Lille","British, Scotland 1814–1887|French, active 1851–55",,"Stewart, John|Imprimerie photographique de Blanquart-Évrard, à Lille","British, Scottish",1814 |1851,1887 |1855,1852,1852,1852,"Salted paper print from paper negative, Blanquart Evrard process",Image: 22.2 × 29.3 cm (8 3/4 × 11 9/16 in.) Mount: 43.6 × 61.9 cm (17 3/16 × 24 3/8 in.),"Purchase, Susan and Thomas Dunn and Peter Bunnell Gifts, 2013",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306170,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1977.683.23,false,true,652117,Photographs,Photograph,[Naser al-Din Shah],,,,,,Person in Photograph|Artist,Person in photograph|Possibly by,Naser od-Din Shah|Luigi Pesce,"Iranian, Tehran 1831–1896 Tehran|Italian, 1818–1891",,"Din, Naser od- Shah|Pesce, Luigi",Iranian|Italian,1831 |1818,1896 |1891,1840s–60s,1840,1869,Salted paper print,,"Gift of Charles K. and Irma B. Wilkinson, 1977",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/652117,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1977.683.24,false,true,285456,Photographs,Photograph,Naser al-Din Shah,,,,,,Artist|Person in Photograph,Possibly by|Person in photograph,Luigi Pesce|Naser od-Din Shah,"Italian, 1818–1891|Iranian, Tehran 1831–1896 Tehran",,"Pesce, Luigi|Din, Naser od- Shah",Italian|Iranian,1818 |1831,1891 |1896,ca. 1855–58,1855,1858,Salted paper print from paper negative,Image: 19.5 x 13.5 cm (7 11/16 x 5 5/16 in.),"Gift of Charles K. and Irma B. Wilkinson, 1977",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285456,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1973.613.1,false,true,260977,Photographs,Photograph,LeRoy Beaulieu,,,,,,Artist|Maker,Mounted by,Gertrude Käsebier|Frederick H. Evans,"American, 1852–1934|British, London 1853–1943 London",,"Käsebier, Gertrude|Evans, Frederick Henry",American|British,1852 |1853,1934 |1943,ca. 1901,1899,1903,Platinum print,Image: 7 7/16 × 5 11/16 in. (18.9 × 14.5 cm) Sheet: 14 13/16 × 9 15/16 in. (37.6 × 25.2 cm),"The Elisha Whittelsey Collection, The Elisha Whittelsey Fund, 1973",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260977,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1973.613.2,false,true,260980,Photographs,Photograph,Luks: Painter,,,,,,Artist|Maker,,Gertrude Käsebier|Frederick H. Evans,"American, 1852–1934|British, London 1853–1943 London",,"Käsebier, Gertrude|Evans, Frederick Henry",American|British,1852 |1853,1934 |1943,ca. 1901,1899,1903,Platinum print,Image: 8 1/16 × 6 1/8 in. (20.4 × 15.6 cm) Sheet: 14 7/8 × 9 15/16 in. (37.8 × 25.2 cm),"The Elisha Whittelsey Collection, The Elisha Whittelsey Fund, 1973",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260980,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1973.613.3,false,true,260981,Photographs,Photograph,Josephine (Portrait of Miss B.),,,,,,Artist|Maker,,Gertrude Käsebier|Frederick H. Evans,"American, 1852–1934|British, London 1853–1943 London",,"Käsebier, Gertrude|Evans, Frederick Henry",American|British,1852 |1853,1934 |1943,1903,1903,1903,Platinum print,Image: 8 in. × 5 15/16 in. (20.3 × 15.1 cm) Sheet: 14 15/16 in. × 10 in. (37.9 × 25.4 cm),"The Elisha Whittelsey Collection, The Elisha Whittelsey Fund, 1973",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260981,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1973.613.4,false,true,260982,Photographs,Photograph,"""When the Sands are Running Low""",,,,,,Artist|Maker,,Gertrude Käsebier|Frederick H. Evans,"American, 1852–1934|British, London 1853–1943 London",,"Käsebier, Gertrude|Evans, Frederick Henry",American|British,1852 |1853,1934 |1943,ca. 1901,1899,1903,Platinum print,,"The Elisha Whittelsey Collection, The Elisha Whittelsey Fund, 1973",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260982,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1973.613.5,false,true,260983,Photographs,Photograph,Mrs. F. H. Evans,,,,,,Artist|Maker,Mounted by,Gertrude Käsebier|Frederick H. Evans,"American, 1852–1934|British, London 1853–1943 London",,"Käsebier, Gertrude|Evans, Frederick Henry",American|British,1852 |1853,1934 |1943,ca. 1901,1899,1903,Platinum print,Image: 7 13/16 × 5 7/8 in. (19.8 × 14.9 cm) Sheet: 14 15/16 × 10 1/16 in. (38 × 25.6 cm),"The Elisha Whittelsey Collection, The Elisha Whittelsey Fund, 1973",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260983,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1973.613.6,false,true,260984,Photographs,Photograph,Mrs. F. H. Evans,,,,,,Artist|Maker,Mounted by,Gertrude Käsebier|Frederick H. Evans,"American, 1852–1934|British, London 1853–1943 London",,"Käsebier, Gertrude|Evans, Frederick Henry",American|British,1852 |1853,1934 |1943,ca. 1901,1899,1903,Platinum print,Image: 6 3/4 × 5 15/16 in. (17.1 × 15.1 cm) Sheet: 14 5/8 in. × 10 in. (37.1 × 25.4 cm),"The Elisha Whittelsey Collection, The Elisha Whittelsey Fund, 1973",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260984,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1973.613.7,false,true,260985,Photographs,Photograph,Mrs. F. H. Evans,,,,,,Artist|Maker,Mounted by,Gertrude Käsebier|Frederick H. Evans,"American, 1852–1934|British, London 1853–1943 London",,"Käsebier, Gertrude|Evans, Frederick Henry",American|British,1852 |1853,1934 |1943,ca. 1901,1899,1903,Platinum print,Image: 7 1/2 × 5 3/4 in. (19 × 14.6 cm) Sheet: 14 7/8 × 10 1/8 in. (37.8 × 25.7 cm),"The Elisha Whittelsey Collection, The Elisha Whittelsey Fund, 1973",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260985,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1973.613.8,false,true,260986,Photographs,Photograph,Frederick H. Evans,,,,,,Artist|Maker,Mounted by,Gertrude Käsebier|Frederick H. Evans,"American, 1852–1934|British, London 1853–1943 London",,"Käsebier, Gertrude|Evans, Frederick Henry",American|British,1852 |1853,1934 |1943,ca. 1901,1899,1903,Platinum print,Image: 9 3/4 × 5 9/16 in. (24.8 × 14.1 cm) Sheet: 14 3/4 × 7 7/16 in. (37.5 × 18.9 cm),"The Elisha Whittelsey Collection, The Elisha Whittelsey Fund, 1973",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260986,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1973.613.9,false,true,260987,Photographs,Photograph,F. H. Evans,,,,,,Artist|Maker,Mounted by,Gertrude Käsebier|Frederick H. Evans,"American, 1852–1934|British, London 1853–1943 London",,"Käsebier, Gertrude|Evans, Frederick Henry",American|British,1852 |1853,1934 |1943,ca. 1901,1899,1903,Platinum print,Image: 8 in. × 5 13/16 in. (20.3 × 14.7 cm) Sheet: 14 13/16 × 10 1/16 in. (37.7 × 25.6 cm),"The Elisha Whittelsey Collection, The Elisha Whittelsey Fund, 1973",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260987,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1973.613.10,false,true,260978,Photographs,Photograph,F. H. Evans,,,,,,Artist|Maker,Mounted by,Gertrude Käsebier|Frederick H. Evans,"American, 1852–1934|British, London 1853–1943 London",,"Käsebier, Gertrude|Evans, Frederick Henry",American|British,1852 |1853,1934 |1943,ca. 1901,1899,1903,Platinum print,Image: 8 in. × 5 7/8 in. (20.3 × 15 cm) Sheet: 14 13/16 × 10 5/16 in. (37.7 × 26.2 cm),"The Elisha Whittelsey Collection, The Elisha Whittelsey Fund, 1973",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260978,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1973.613.11,false,true,260979,Photographs,Photograph,F. H. Evans,,,,,,Artist|Maker,Mounted by,Gertrude Käsebier|Frederick H. Evans,"American, 1852–1934|British, London 1853–1943 London",,"Käsebier, Gertrude|Evans, Frederick Henry",American|British,1852 |1853,1934 |1943,ca. 1901,1899,1903,Platinum print,Image: 7 11/16 in. × 6 in. (19.5 × 15.3 cm) Sheet: 14 7/8 × 10 3/8 in. (37.8 × 26.3 cm),"The Elisha Whittelsey Collection, The Elisha Whittelsey Fund, 1973",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260979,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1982.1010.3,false,true,263303,Photographs,Photograph,"View of Transept, Looking South",,,,,,Artist|Printer,,Hugh Owen|Nicolaas Henneman,"British, 1808–1897|Dutch, Heemskerk 1813–1898 London",,"Owen, Hugh|Henneman, Nicolaas",British|Dutch,1808 |1813,1897 |1898,1851,1851,1851,Salted paper print from paper negative,Image: 21.6 x 16.3 cm (8 1/2 x 6 7/16 in.) Mount: 35 x 21.2 cm (13 3/4 x 8 3/8 in.),"Purchase, Emanuel Gerard Gift, 1982",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/263303,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1982.1010.5,false,true,263305,Photographs,Photograph,Greek Slave,,,,,,Artist|Printer,,Hugh Owen|Nicolaas Henneman,"British, 1808–1897|Dutch, Heemskerk 1813–1898 London",,"Owen, Hugh|Henneman, Nicolaas",British|Dutch,1808 |1813,1897 |1898,1851,1851,1851,Salted paper print from paper negative,Image: 8 3/8 × 6 5/16 in. (21.3 × 16.1 cm) Mount: 13 3/4 × 9 13/16 in. (35 × 25 cm),"Purchase, Emanuel Gerard Gift, 1982",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/263305,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -66.724.2,false,true,271123,Photographs,Photograph,"Mirror Lake, Valley of the Yosemite",,,,,,Artist|Printer|Printer,,Eadweard Muybridge|Bradley and Rulofson|Henry W. Bradley,"American, born Britain, 1830–1904|American, 1813–1891",,"Muybridge, Eadweard|Bradley and Rulofson|Bradley, Henry W.","American, born Britain|American",1830 |1813,1904 |1891,1872,1872,1872,Albumen silver print from glass negative,42.8 x 54.3 cm (16 7/8 x 21 3/8 in. ),"David Hunter McAlpin Fund, 1966",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/271123,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -49.40.83,false,true,269270,Photographs,Photograph,"Notre Dame, Paris",,,,,,Artist|Artist|Photography Studio,,Louis-Auguste Bisson|Auguste-Rosalie Bisson|Bisson Frères,"French, 1814–1876|French, 1826–1900|French, active 1852–1863",,"Bisson, Louis-Auguste|Bisson, Auguste-Rosalie|Bisson Frères",French|French|French,1814 |1826 |1852,1876 |1900 |1863,1850s,1850,1859,Albumen silver print,,"The Elisha Whittelsey Collection, The Elisha Whittelsey Fund, 1949",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269270,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.410 (4),false,true,288159,Photographs,Photograph,[Empress Eugénie as an Odalisque],,,,,,Artist|Artist|Person in Photograph,Painted and retouched by|Person in photograph,Pierre-Louis Pierson|Marck|Empress Eugénie de Montijo,"French, 1822–1913|French (born Spain), Granada 1826–1920 Madrid",", et al","Pierson, Pierre-Louis|Marck|Montijo, Eugénie de, Empress","French|French, born Spain",1822 |1826,1913 |1920,1861–65,1861,1865,Albumen silver print,Image: 17 x 12 cm (6 11/16 x 4 3/4 in.) Mount: 38 x 28 cm (14 15/16 x 11 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288159,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.494.1,false,true,289273,Photographs,Photograph,Ru-Shan Monastery,,,,,,Former Attribution|Artist,Possibly by,John Thomson|Lai Fong,"British, Edinburgh, Scotland 1837–1921 London|Chinese, 1839–1890",,"Thomson, John|Lai, Fong","British, Scottish",1837 |1839,1921 |1890,ca. 1869,1869,1869,Albumen silver print from glass negative,Image: 8 15/16 × 11 1/8 in. (22.7 × 28.2 cm),"Gilman Collection, Purchase, Robert Rosenkranz Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/289273,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.494.27,false,true,289299,Photographs,Photograph,"Yuen-foo River, View from the Hill",,,,,,Former Attribution|Artist,Possibly by,John Thomson|Lai Fong,"British, Edinburgh, Scotland 1837–1921 London|Chinese, 1839–1890",,"Thomson, John|Lai, Fong","British, Scottish",1837 |1839,1921 |1890,ca. 1869,1869,1869,Albumen silver print from glass negative,Image: 8 1/16 × 11 7/16 in. (20.4 × 29 cm),"Gilman Collection, Purchase, Robert Rosenkranz Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/289299,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.494.30,false,true,289302,Photographs,Photograph,On the Road up to Yuen-foo Monastery,,,,,,Former Attribution|Artist,Possibly by,John Thomson|Lai Fong,"British, Edinburgh, Scotland 1837–1921 London|Chinese, 1839–1890",,"Thomson, John|Lai, Fong","British, Scottish",1837 |1839,1921 |1890,ca. 1869,1869,1869,Albumen silver print from glass negative,Image: 7 5/16 × 9 3/8 in. (18.6 × 23.8 cm),"Gilman Collection, Purchase, Robert Rosenkranz Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/289302,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.494.33,false,true,289305,Photographs,Photograph,Bankers Glen Yuen foo Monastery,,,,,,Former Attribution|Artist,Possibly by,John Thomson|Lai Fong,"British, Edinburgh, Scotland 1837–1921 London|Chinese, 1839–1890",,"Thomson, John|Lai, Fong","British, Scottish",1837 |1839,1921 |1890,ca. 1869,1869,1869,Albumen silver print from glass negative,Image: 9 1/16 × 11 3/8 in. (23 × 28.9 cm),"Gilman Collection, Purchase, Robert Rosenkranz Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/289305,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.494.43,false,true,289315,Photographs,Photograph,"Bowling Alley and Raquet Court, Foochow",,,,,,Former Attribution|Artist,Possibly by,John Thomson|Lai Fong,"British, Edinburgh, Scotland 1837–1921 London|Chinese, 1839–1890",,"Thomson, John|Lai, Fong","British, Scottish",1837 |1839,1921 |1890,ca. 1869,1869,1869,Albumen silver print from glass negative,Image: 8 15/16 × 11 5/16 in. (22.7 × 28.7 cm),"Gilman Collection, Purchase, Robert Rosenkranz Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/289315,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1982.1182.714–.736,false,true,288229,Photographs,Stereographs,[Group of 23 Early Stereograph Views of British Cathedrals],,,,,,Artist|Artist|Publisher|Artist|Publisher|Artist|Publisher|Artist,,Unknown|Budge|Edinburgh Stereographic Company|F. G. O. Stuart|John Browning|Mrs. Charles Lawrence|H. T. Ramsay|Thomas Heaviside,"British|British|British|British|British|British|British, 1828–1886",,"Unknown|Budge|Edinburgh Stereographic Company|Stuart, F. G. O.|Browning, John|Lawrence, Charles Mrs.|Ramsay, H. T.|Heaviside, Thomas","British|British, Scottish|British|British|British|British|British",1828,1886,1860s–80s,1860,1889,Albumen silver prints,Mounts approximately: 8.4 x 16.9 cm (3 5/16 x 6 5/8 in.) to 8.5 x 17.6 cm (3 3/8 x 6 15/16 in.),"Gift of Weston J. Naef, in memory of Kathleen W. Naef and Weston J. Naef Sr., 1982",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288229,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1982.1182.97–.204,false,true,288118,Photographs,Stereographs,[Group of 107 Stereograph Views of Animals],,,,,,Publisher|Publisher|Publisher|Publisher|Publisher|Artist|Photography Studio|Artist|Publisher|Artist|Artist|Artist|Publisher|Publisher|Artist|Artist|Publisher|Artist|Publisher|Publisher|Publisher|Artist|Publisher|Publisher|Publisher|Publisher|Artist|Publisher|Publisher|Publisher|Publisher|Artist|Publisher,,"Griffith & Griffith, American|William H. Rau|C. H. Graves|Universal Photo Art Co.|Underwood & Underwood|George Barker|Sun Sculpture Works and Studios|John L. Lovell|J. A. French|Merrimac Stereoscopic Company|E. M. Van Aken|H. Rikard, American|H. C. White Company|International View Company|C. L. Wasson|Allen|Stereoscopic Gems|H. Werner|Wilder & Williamson|Strohmeyer & Wyman|Union View Company|Benneville Lloyd Singley|Standard Series|J. F. Jarvis|Canvassers|Webster & Albee|T. W. Ingersoll|Metropolitan Series|Littleton View Company|The Whiting View Company|T. C. Fletcher & Company|Unknown|Keystone View Company","American, 1855–1920|American|American|American, born Canada, 1844–1894|American|American, 1825–1903|American|American|American|American|American|American|American|American|American|American, Union Township, Pennsylvania 1864–1938 Meadville, Pennsylvania|American|American|American|American|American|American|American",,"Griffith & Griffith|Rau, William H.|Graves, C. H.|Universal Photo Art Co.|Underwood & Underwood|Barker, George|Sun Sculpture Works and Studios|Lovell, John L.|French, J. A.|Merrimac Stereoscopic Company|Van Aken, E. M.|Rikard, H.|H. C. White Company|International View Company|Wasson, C. L.|Allen|Stereoscopic Gems|Werner, H.|Wilder & Williamson|Strohmeyer & Wyman|Union View Company|Singley, Benneville Lloyd|Standard Series|Jarvis, J. F.|Canvassers|Webster & Albee|Ingersoll, T. W.|Metropolitan Series|Littleton View Company|Whiting View Company|T. C. Fletcher & Company|Unknown|Keystone View Company",American|American|American|American|American|American|American|American|American|American|American|American|American|American|American|American|American|American|American|American|American|American|American|American|American,1855 |1844 |1825 |1864,1920 |1894 |1903 |1938,1850s–1910s,1850,1919,Albumen silver prints,Mounts approximately: 8.3 x 17.2 cm (3 1/4 x 6 3/4 in.) to 11.4 x 17.7 cm (4 1/2 x 6 15/16 in.),"Gift of Weston J. Naef, in memory of Kathleen W. Naef and Weston J. Naef Sr., 1982",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288118,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1982.1182.1940–.1950,false,true,288315,Photographs,Stereographs,"[Group of 11 Stereograph Views of the 1869 and 1872 World Peace Jubilees, Boston, Massachusetts, United States of America]",,,,,,Publisher|Artist|Subject|Publisher|Artist,Cited in manuscript,Charles Pollock|William G. Preston|Joseph H. Chadwick|John P. Soule|S. Towle,"American|American, 1842–1910|American|American, 1827–1904|American",,"Pollock, Charles|Preston, William G.|Chadwick, Joseph H.|Soule, John P.|Towle, S.",American|American|American|American|American,1842 |1827,1910 |1904,1850s–1910s,1850,1919,Albumen silver prints,Mounts approximately: 8.1 x 17.3 cm (3 3/16 x 6 13/16 in.) to 8.7 x 17.7 cm (3 7/16 x 6 15/16 in.),"Gift of Weston J. Naef, in memory of Kathleen W. Naef and Weston J. Naef Sr., 1982",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288315,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2011.206,false,true,296346,Photographs,Photograph,"[Distortograph: William Hale ""Big Bill"" Thompson, Mayor of Chicago]",,,,,,Artist|Person in Photograph,,Herbert George Ponting|William Hale Thompson,"British, Salisbury, Wiltshire 1870–1935 London|American, 1869–1944",,"Ponting, Herbert George|Thompson, William Hale",British|American,1870 |1869,1935 |1944,1927,1927,1927,Gelatin silver print,"Image: 9.8 x 7.3 cm (3 7/8 x 2 7/8 in.) Sheet: 16.3 x 12 cm (6 7/16 x 4 3/4 in.) Frame: 50.8 x 40.6 cm (20 x 16 in.) (Framed with 2011.207, .208, .209)","Twentieth-Century Photography Fund, 2011",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/296346,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2011.207,false,true,296347,Photographs,Photograph,"[Distortograph: William Hale ""Big Bill"" Thompson, Mayor of Chicago]",,,,,,Artist|Person in Photograph,,Herbert George Ponting|William Hale Thompson,"British, Salisbury, Wiltshire 1870–1935 London|American, 1869–1944",,"Ponting, Herbert George|Thompson, William Hale",British|American,1870 |1869,1935 |1944,1927,1927,1927,Gelatin silver print,"Image: 9.8 x 7.3 cm (3 7/8 x 2 7/8 in.) Sheet: 16.2 x 12.1 cm (6 3/8 x 4 3/4 in.) Frame: 50.8 x 40.6 cm (20 x 16 in.) (Framed with 2011.206, .208, .209)","Twentieth-Century Photography Fund, 2011",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/296347,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2011.208,false,true,296348,Photographs,Photograph,"[Distortograph: William Hale ""Big Bill"" Thompson, Mayor of Chicago]",,,,,,Artist|Person in Photograph,,Herbert George Ponting|William Hale Thompson,"British, Salisbury, Wiltshire 1870–1935 London|American, 1869–1944",,"Ponting, Herbert George|Thompson, William Hale",British|American,1870 |1869,1935 |1944,1927,1927,1927,Gelatin silver print,"Image: 9.8 x 7.3 cm (3 7/8 x 2 7/8 in.) Sheet: 16.3 x 11.9 cm (6 7/16 x 4 11/16 in.) Frame: 50.8 x 40.6 cm (20 x 16 in.) (Framed with 2011.206, .207, .209)","Twentieth-Century Photography Fund, 2011",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/296348,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2011.209,false,true,296349,Photographs,Photograph,"[Distortograph: William Hale ""Big Bill"" Thompson, Mayor of Chicago]",,,,,,Artist|Person in Photograph,,Herbert George Ponting|William Hale Thompson,"British, Salisbury, Wiltshire 1870–1935 London|American, 1869–1944",,"Ponting, Herbert George|Thompson, William Hale",British|American,1870 |1869,1935 |1944,1927,1927,1927,Gelatin silver print,"Image: 9.8 x 7.3 cm (3 7/8 x 2 7/8 in.) Sheet: 16.3 x 12 cm (6 7/16 x 4 3/4 in.) Frame: 50.8 x 40.6 cm (20 x 16 in.) (Framed with 2011.206, .207, .208)","Twentieth-Century Photography Fund, 2011",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/296349,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.159.4,false,true,306205,Photographs,Photograph,The Last Sitting,,,,,,Artist|Person in Photograph,Person in photograph,Lewis Carroll|Alice Pleasance Liddell,"British, Daresbury, Cheshire 1832–1898 Guildford|British, 1852–1934",,"Carroll, Lewis|Liddell, Alice",British|British,1832 |1852,1898 |1934,"June 25, 1870",1870,1870,Albumen silver print from glass negative,Sheet: 6 1/4 × 5 9/16 in. (15.9 × 14.1 cm) Image: 5 7/8 × 4 15/16 in. (15 × 12.6 cm),"Bequest of Maurice B. Sendak, 2012",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306205,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.20,false,true,283092,Photographs,Photograph,"Alice Liddell as ""The Beggar Maid""",,,,,,Artist|Subject,,Lewis Carroll|Alice Pleasance Liddell,"British, Daresbury, Cheshire 1832–1898 Guildford|British, 1852–1934",,"Carroll, Lewis|Liddell, Alice",British|British,1832 |1852,1898 |1934,1858,1858,1858,Albumen silver print from glass negative,Image: 16.3 x 10.9cm (6 7/16 x 4 5/16in.) Mount: 14 1/8 in. × 10 7/8 in. (35.8 × 27.6 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/283092,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.159.6,false,true,306207,Photographs,Photograph,Edith Liddell Seated Beside a Vase of Foxgloves,,,,,,Artist|Person in Photograph,Person in photograph,Lewis Carroll|Edith Mary Liddell,"British, Daresbury, Cheshire 1832–1898 Guildford|British, 1854–1876",,"Carroll, Lewis|Liddell, Edith Mary",British|British,1832 |1854,1898 |1876,1860,1860,1860,Albumen silver print from glass negative,Sheet: 6 1/4 × 5 3/4 in. (15.9 × 14.6 cm) Image: 5 1/4 × 3 3/4 in. (13.3 × 9.5 cm),"Bequest of Maurice B. Sendak, 2012",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306207,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.636,false,true,286007,Photographs,Photograph,"""The Prettiest Doll in the World""",,,,,,Artist|Subject,,"Lewis Carroll|Alexandra ""Xie"" Rhoda Kitchin","British, Daresbury, Cheshire 1832–1898 Guildford|British, 1864–1925",,"Carroll, Lewis|Kitchin, Alexandra ""Xie"" Rhoda",British|British,1832 |1864,1898 |1925,"July 5, 1870",1870,1870,Albumen silver print from glass negative,7 3/4 x 5 13/16,"Gilman Collection, Purchase, Mr. and Mrs. Henry R. Kravis Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286007,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.43.408,false,true,267824,Photographs,Photograph,Römische Villa,,,,,,Former Attribution|Artist,,Hugo Henneberg|Heinrich Kühn,"Austrian, 1863–1918|Austrian (born Germany), Dresden 1866–1944 Birgitz",,"Henneberg, Hugo|Kühn, Heinrich","Austrian|Austrian, born Germany",1863 |1866,1918 |1944,1898–1900,1898,1900,Gum bichromate print,55.8 x 74.5 cm. (22 x 29 5/16 in.),"Alfred Stieglitz Collection, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267824,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.43.353,false,true,267768,Photographs,Photograph,Robert Browning,,,,,,Artist|Printer,,Julia Margaret Cameron|The Autotype Company,"British (born India), Calcutta 1815–1879 Kalutara, Ceylon|British, London",,"Cameron, Julia Margaret|Autotype Company","British, born India|British",1815,1815,"1865, printed ca. 1902",1865,1865,Carbon print,21.3 x 22.1 cm (8 3/8 x 8 11/16 in.),"Alfred Stieglitz Collection, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267768,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -49.55.319,false,true,269428,Photographs,Photograph,"Alfred, Lord Tennyson",,,,,,Artist|Printer,,Julia Margaret Cameron|The Autotype Company,"British (born India), Calcutta 1815–1879 Kalutara, Ceylon|British, London",,"Cameron, Julia Margaret|Autotype Company","British, born India|British",1815,1815,"1869, printed 1905",1869,1869,Carbon print,35.8 x 26.2 cm. (14 1/16 x 10 5/16 in.),"Alfred Stieglitz Collection, 1949",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269428,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -49.55.320,false,true,269430,Photographs,Photograph,"Alfred, Lord Tennyson",,,,,,Artist|Printer,,Julia Margaret Cameron|The Autotype Company,"British (born India), Calcutta 1815–1879 Kalutara, Ceylon|British, London",,"Cameron, Julia Margaret|Autotype Company","British, born India|British",1815,1815,"1867, printed 1905",1867,1867,Carbon print,34.4 x 25.6 cm. (13 9/16 x 10 1/16 in.),"Alfred Stieglitz Collection, 1949",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269430,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -49.55.321,false,true,269431,Photographs,Photograph,George Frederick Watts,,,,,,Artist|Printer,,Julia Margaret Cameron|The Autotype Company,"British (born India), Calcutta 1815–1879 Kalutara, Ceylon|British, London",,"Cameron, Julia Margaret|Autotype Company","British, born India|British",1815,1815,"1864, printed ca. 1905",1864,1864,Carbon print,25.5 x 20.2 cm. (10 1/16 x 7 15/16 in.),"Alfred Stieglitz Collection, 1949",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269431,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -49.55.322,false,true,269432,Photographs,Photograph,Lord Justice James,,,,,,Artist|Printer,,Julia Margaret Cameron|The Autotype Company,"British (born India), Calcutta 1815–1879 Kalutara, Ceylon|British, London",,"Cameron, Julia Margaret|Autotype Company","British, born India|British",1815,1815,"ca. 1870, printed ca. 1905",1868,1872,Carbon print,33.3 x 26.6 cm. (13 1/8 x 10 1/2 in.),"Alfred Stieglitz Collection, 1949",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269432,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -49.55.323,false,true,269433,Photographs,Photograph,"Ellen Terry, at the age of sixteen",,,,,,Artist|Printer,,Julia Margaret Cameron|The Autotype Company,"British (born India), Calcutta 1815–1879 Kalutara, Ceylon|British, London",,"Cameron, Julia Margaret|Autotype Company","British, born India|British",1815,1815,"1864, printed ca. 1913",1864,1864,Carbon print,24 x 26.7 cm (9 7/16 x 10 1/2 in. ),"Alfred Stieglitz Collection, 1949",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269433,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -49.55.324,false,true,269434,Photographs,Photograph,Thomas Carlyle,,,,,,Artist|Printer,,Julia Margaret Cameron|The Autotype Company,"British (born India), Calcutta 1815–1879 Kalutara, Ceylon|British, London",,"Cameron, Julia Margaret|Autotype Company","British, born India|British",1815,1815,1867,1867,1867,Carbon print,35.0 x 28.1 cm. (13 3/4 x 11 1/16 in.),"Alfred Stieglitz Collection, 1949",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269434,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1982.1182.2035–.2037,false,true,288318,Photographs,Stereographs,"[Group of 3 Stereograph Views of the 1901 Pan American Exposition, Buffalo, New York]",,,,,,Subject|Artist|Person in Photograph|Person in Photograph,,James M. Davis|Kilburn Brothers|William McKinley|First Lady Ida Saxton McKinley,"American, active ca. 1865–1890|American, 1843–1901|American, 1847–1907",,"Davis, James M.|Kilburn Brothers|McKinley, William|McKinley, Ida Saxton First Lady",American|American|American,1863 |1843 |1847,1892 |1901 |1907,1850s–1910s,1850,1919,Albumen silver prints,Mounts: 8.9 x 17.8 cm (3 1/2 x 7 in.),"Gift of Weston J. Naef, in memory of Kathleen W. Naef and Weston J. Naef Sr., 1982",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288318,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.159.53,false,true,306337,Photographs,Photograph,Group of Gentlemen Conversing over a Glass of Wine,,,,,,Person in Photograph|Artist,Person in photograph,Antoine-François-Jean Claudet|William Henry Fox Talbot,"French, active Great Britain, 1797–1867|British, Dorset 1800–1877 Lacock",,"Claudet, Antione-François-Jean|Talbot, William Henry Fox","French, active Great Britain|British",1797 |1800,1867 |1800,"February 7, 1846",1846,1846,Salted paper print from paper negative,Mount: 12 3/16 × 9 11/16 in. (31 × 24.6 cm) Image: 5 1/2 × 7 13/16 in. (13.9 × 19.9 cm) Top corners trimmed,"Bequest of Maurice B. Sendak, 2012",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306337,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.245,false,true,268040,Photographs,Photograph,"The Crack Team of the 1st Division, 6th Corps near Hazel River, Virginia",,,,,,Former Attribution|Artist,Formerly attributed to,Mathew B. Brady|Timothy H. O'Sullivan,"American, born Ireland, 1823?–1896 New York|American, born Ireland, 1840–1882",,"Brady, Mathew B.|O'Sullivan, Timothy H.","American|American, born Ireland",1823 |1840,1896 |1882,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268040,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.266,false,true,268063,Photographs,Photograph,"[U.S. Gunboat]. Brady album, p. 161",,,,,,Former Attribution|Artist,Formerly attributed to,Mathew B. Brady|Timothy H. O'Sullivan,"American, born Ireland, 1823?–1896 New York|American, born Ireland, 1840–1882",,"Brady, Mathew B.|O'Sullivan, Timothy H.","American|American, born Ireland",1823 |1840,1896 |1882,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268063,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.3,false,true,268100,Photographs,Photograph,"Yellow House, Warren Station, Virginia",,,,,,Artist|Former Attribution,Formerly attributed to,Timothy H. O'Sullivan|Mathew B. Brady,"American, born Ireland, 1840–1882|American, born Ireland, 1823?–1896 New York",,"O'Sullivan, Timothy H.|Brady, Mathew B.","American, born Ireland",1840 |1823,1882 |1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268100,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.10,false,true,267880,Photographs,Photograph,"General Hospital, Point of Rocks, Appomattox River below Petersburg",,,,,,Artist|Former Attribution,Formerly attributed to,Timothy H. O'Sullivan|Mathew B. Brady,"American, born Ireland, 1840–1882|American, born Ireland, 1823?–1896 New York",,"O'Sullivan, Timothy H.|Brady, Mathew B.","American, born Ireland",1840 |1823,1882 |1896,1864,1864,1864,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267880,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.12,false,true,267902,Photographs,Photograph,"Poplar Grove Church, built by 50th New York Volunteers",,,,,,Artist|Former Attribution,Formerly attributed to,Timothy H. O'Sullivan|Mathew B. Brady,"American, born Ireland, 1840–1882|American, born Ireland, 1823?–1896 New York",,"O'Sullivan, Timothy H.|Brady, Mathew B.","American, born Ireland",1840 |1823,1882 |1896,1865,1865,1865,Albumen silver print from glass negative,16.5 x 20.2 cm (6 1/2 x 7 15/16 in. ),"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267902,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.13,false,true,267913,Photographs,Panorama,"Confederate Prisoners at Belle Plain, May 12",,,,,,Artist|Former Attribution,Formerly attributed to,Timothy H. O'Sullivan|Mathew B. Brady,"American, born Ireland, 1840–1882|American, born Ireland, 1823?–1896 New York",,"O'Sullivan, Timothy H.|Brady, Mathew B.","American, born Ireland",1840 |1823,1882 |1896,1863,1863,1863,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267913,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.19,false,true,267979,Photographs,Photograph,"White House Landing, Pamunkey River",,,,,,Artist|Former Attribution,Formerly attributed to,Timothy H. O'Sullivan|Mathew B. Brady,"American, born Ireland, 1840–1882|American, born Ireland, 1823?–1896 New York",,"O'Sullivan, Timothy H.|Brady, Mathew B.","American, born Ireland",1840 |1823,1882 |1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267979,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.26,false,true,268056,Photographs,Photograph,Bull Run. Bridge near Union Mills (destroyed seven times),,,,,,Artist|Former Attribution,Formerly attributed to,Timothy H. O'Sullivan|Mathew B. Brady,"American, born Ireland, 1840–1882|American, born Ireland, 1823?–1896 New York",,"O'Sullivan, Timothy H.|Brady, Mathew B.","American, born Ireland",1840 |1823,1882 |1896,ca. 1862,1860,1864,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268056,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.28,false,true,268078,Photographs,Photograph,"Colored Battery, Petersburg, June",,,,,,Artist|Former Attribution,Formerly attributed to,Timothy H. O'Sullivan|Mathew B. Brady,"American, born Ireland, 1840–1882|American, born Ireland, 1823?–1896 New York",,"O'Sullivan, Timothy H.|Brady, Mathew B.","American, born Ireland",1840 |1823,1882 |1896,1864,1864,1864,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268078,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.35,false,true,268156,Photographs,Photograph,"Commodore Perry, Pamunkey River",,,,,,Artist|Former Attribution,Formerly attributed to,Timothy H. O'Sullivan|Mathew B. Brady,"American, born Ireland, 1840–1882|American, born Ireland, 1823?–1896 New York",,"O'Sullivan, Timothy H.|Brady, Mathew B.","American, born Ireland",1840 |1823,1882 |1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268156,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.54,false,true,268246,Photographs,Photograph,"White House Landing, Pamunkey River",,,,,,Artist|Former Attribution,Formerly attributed to,Timothy H. O'Sullivan|Mathew B. Brady,"American, born Ireland, 1840–1882|American, born Ireland, 1823?–1896 New York",,"O'Sullivan, Timothy H.|Brady, Mathew B.","American, born Ireland",1840 |1823,1882 |1896,1861–65,1861,1865,Albumen silver print from glass negative,13.1 x 20.2 cm (5 3/16 x 7 15/16 in. ),"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268246,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.55,false,true,268247,Photographs,Photograph,"White House Landing, Pamunkey River",,,,,,Artist|Former Attribution,Formerly attributed to,Timothy H. O'Sullivan|Mathew B. Brady,"American, born Ireland, 1840–1882|American, born Ireland, 1823?–1896 New York",,"O'Sullivan, Timothy H.|Brady, Mathew B.","American, born Ireland",1840 |1823,1882 |1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268247,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.56,false,true,268248,Photographs,Photograph,"Bridge Across Pamunkey River, near White House",,,,,,Artist|Former Attribution,Formerly attributed to,Timothy H. O'Sullivan|Mathew B. Brady,"American, born Ireland, 1840–1882|American, born Ireland, 1823?–1896 New York",,"O'Sullivan, Timothy H.|Brady, Mathew B.","American, born Ireland",1840 |1823,1882 |1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268248,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.64,false,true,268257,Photographs,Photograph,"Blackburn's Ford / Rapidan River, The Wilderness",,,,,,Artist|Former Attribution,Formerly attributed to,Timothy H. O'Sullivan|Mathew B. Brady,"American, born Ireland, 1840–1882|American, born Ireland, 1823?–1896 New York",,"O'Sullivan, Timothy H.|Brady, Mathew B.","American, born Ireland",1840 |1823,1882 |1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268257,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.65,false,true,268258,Photographs,Photograph,"White House Landing, Pamunkey River",,,,,,Artist|Former Attribution,Formerly attributed to,Timothy H. O'Sullivan|Mathew B. Brady,"American, born Ireland, 1840–1882|American, born Ireland, 1823?–1896 New York",,"O'Sullivan, Timothy H.|Brady, Mathew B.","American, born Ireland",1840 |1823,1882 |1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268258,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.66,false,true,268259,Photographs,Photograph,"White House Landing, Pamunkey River",,,,,,Artist|Former Attribution,Formerly attributed to,Timothy H. O'Sullivan|Mathew B. Brady,"American, born Ireland, 1840–1882|American, born Ireland, 1823?–1896 New York",,"O'Sullivan, Timothy H.|Brady, Mathew B.","American, born Ireland",1840 |1823,1882 |1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268259,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.67,false,true,268260,Photographs,Photograph,"White House Landing, Pamunkey River",,,,,,Artist|Former Attribution,Formerly attributed to,Timothy H. O'Sullivan|Mathew B. Brady,"American, born Ireland, 1840–1882|American, born Ireland, 1823?–1896 New York",,"O'Sullivan, Timothy H.|Brady, Mathew B.","American, born Ireland",1840 |1823,1882 |1896,1861–65,1861,1865,Albumen silver print from glass negative,12.9 x 19.2 cm (5 1/16 x 7 9/16 in. ),"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268260,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.80,false,true,268275,Photographs,Photograph,"U.S. Gunboat ""Commodore Perry"" on Pamunkey River",,,,,,Artist|Former Attribution,Formerly attributed to,Timothy H. O'Sullivan|Mathew B. Brady,"American, born Ireland, 1840–1882|American, born Ireland, 1823?–1896 New York",,"O'Sullivan, Timothy H.|Brady, Mathew B.","American, born Ireland",1840 |1823,1882 |1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268275,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.105,false,true,267886,Photographs,Photograph,Bull Run. Blackburn's Ford,,,,,,Artist|Former Attribution,Formerly attributed to,Timothy H. O'Sullivan|Mathew B. Brady,"American, born Ireland, 1840–1882|American, born Ireland, 1823?–1896 New York",,"O'Sullivan, Timothy H.|Brady, Mathew B.","American, born Ireland",1840 |1823,1882 |1896,ca. 1862,1860,1864,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267886,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.111,false,true,267893,Photographs,Photograph,"Swamp near Broadway Landing, Appomattox River",,,,,,Artist|Former Attribution,Formerly attributed to,Timothy H. O'Sullivan|Mathew B. Brady,"American, born Ireland, 1840–1882|American, born Ireland, 1823?–1896 New York",,"O'Sullivan, Timothy H.|Brady, Mathew B.","American, born Ireland",1840 |1823,1882 |1896,1864,1864,1864,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267893,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.130,false,true,267914,Photographs,Photograph,"U.S. Gunboat ""Commodore Perry"" on Pamunkey River",,,,,,Artist|Former Attribution,Formerly attributed to,Timothy H. O'Sullivan|Mathew B. Brady,"American, born Ireland, 1840–1882|American, born Ireland, 1823?–1896 New York",,"O'Sullivan, Timothy H.|Brady, Mathew B.","American, born Ireland",1840 |1823,1882 |1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267914,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.140,false,true,267925,Photographs,Photograph,"Interior of Fort Steadman, front of Petersburg",,,,,,Artist|Former Attribution,Formerly attributed to,Timothy H. O'Sullivan|Mathew B. Brady,"American, born Ireland, 1840–1882|American, born Ireland, 1823?–1896 New York",,"O'Sullivan, Timothy H.|Brady, Mathew B.","American, born Ireland",1840 |1823,1882 |1896,1864,1864,1864,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267925,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.144,false,true,267929,Photographs,Photograph,"Outer Confederate Line, Petersburg, Captured June 15, 1864",,,,,,Artist|Former Attribution,Formerly attributed to,Timothy H. O'Sullivan|Mathew B. Brady,"American, born Ireland, 1840–1882|American, born Ireland, 1823?–1896 New York",,"O'Sullivan, Timothy H.|Brady, Mathew B.","American, born Ireland",1840 |1823,1882 |1896,1864,1864,1864,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267929,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.145,false,true,267930,Photographs,Photograph,"Fort Burnham, front of Petersburg",,,,,,Artist|Former Attribution,Formerly attributed to,Timothy H. O'Sullivan|Mathew B. Brady,"American, born Ireland, 1840–1882|American, born Ireland, 1823?–1896 New York",,"O'Sullivan, Timothy H.|Brady, Mathew B.","American, born Ireland",1840 |1823,1882 |1896,1864,1864,1864,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267930,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.146,false,true,267931,Photographs,Photograph,"The Mine, Petersburg",,,,,,Artist|Former Attribution,Formerly attributed to,Timothy H. O'Sullivan|Mathew B. Brady,"American, born Ireland, 1840–1882|American, born Ireland, 1823?–1896 New York",,"O'Sullivan, Timothy H.|Brady, Mathew B.","American, born Ireland",1840 |1823,1882 |1896,1864,1864,1864,Albumen silver print from glass negative,16 x 20.4 cm (6 5/16 x 8 1/16 in. ),"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267931,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.147,false,true,267932,Photographs,Photograph,"Interior of Fort Sedgwick, before Petersburg",,,,,,Artist|Former Attribution,Formerly attributed to,Timothy H. O'Sullivan|Mathew B. Brady,"American, born Ireland, 1840–1882|American, born Ireland, 1823?–1896 New York",,"O'Sullivan, Timothy H.|Brady, Mathew B.","American, born Ireland",1840 |1823,1882 |1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267932,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.149,false,true,267934,Photographs,Photograph,"The Crater, Petersburg",,,,,,Artist|Former Attribution,Formerly attributed to,Timothy H. O'Sullivan|Mathew B. Brady,"American, born Ireland, 1840–1882|American, born Ireland, 1823?–1896 New York",,"O'Sullivan, Timothy H.|Brady, Mathew B.","American, born Ireland",1840 |1823,1882 |1896,1864,1864,1864,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267934,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.154,false,true,267940,Photographs,Photograph,In front of Petersburg,,,,,,Artist|Former Attribution,Formerly attributed to,Timothy H. O'Sullivan|Mathew B. Brady,"American, born Ireland, 1840–1882|American, born Ireland, 1823?–1896 New York",,"O'Sullivan, Timothy H.|Brady, Mathew B.","American, born Ireland",1840 |1823,1882 |1896,1864,1864,1864,Albumen silver print from glass negative,16.3 x 21.3 cm (6 7/16 x 8 3/8 in. ),"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267940,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.156,false,true,267942,Photographs,Photograph,"Outer Confederate Line, Petersburg, Captured June 15, 1864",,,,,,Artist|Former Attribution,Formerly attributed to,Timothy H. O'Sullivan|Mathew B. Brady,"American, born Ireland, 1840–1882|American, born Ireland, 1823?–1896 New York",,"O'Sullivan, Timothy H.|Brady, Mathew B.","American, born Ireland",1840 |1823,1882 |1896,1864,1864,1864,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267942,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.157,false,true,267943,Photographs,Photograph,Front of Petersburg Lines,,,,,,Artist|Former Attribution,Formerly attributed to,Timothy H. O'Sullivan|Mathew B. Brady,"American, born Ireland, 1840–1882|American, born Ireland, 1823?–1896 New York",,"O'Sullivan, Timothy H.|Brady, Mathew B.","American, born Ireland",1840 |1823,1882 |1896,1864,1864,1864,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267943,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.160,false,true,267947,Photographs,Photograph,"Outer Confederate Line at Petersburg. Captured by 18th Corps, June 15, 1864",,,,,,Artist|Former Attribution,Formerly attributed to,Timothy H. O'Sullivan|Mathew B. Brady,"American, born Ireland, 1840–1882|American, born Ireland, 1823?–1896 New York",,"O'Sullivan, Timothy H.|Brady, Mathew B.","American, born Ireland",1840 |1823,1882 |1896,1864,1864,1864,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267947,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.162,false,true,267949,Photographs,Photograph,"Warren Station, near Petersburg (graves)",,,,,,Artist|Former Attribution,Formerly attributed to,Timothy H. O'Sullivan|Mathew B. Brady,"American, born Ireland, 1840–1882|American, born Ireland, 1823?–1896 New York",,"O'Sullivan, Timothy H.|Brady, Mathew B.","American, born Ireland",1840 |1823,1882 |1896,1864,1864,1864,Albumen silver print from glass negative,14.8 x 20.8 cm (5 13/16 x 8 3/16 in. ),"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267949,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.168,false,true,267955,Photographs,Photograph,Bull Run. Blackburn's Ford,,,,,,Artist|Former Attribution,Formerly attributed to,Timothy H. O'Sullivan|Mathew B. Brady,"American, born Ireland, 1840–1882|American, born Ireland, 1823?–1896 New York",,"O'Sullivan, Timothy H.|Brady, Mathew B.","American, born Ireland",1840 |1823,1882 |1896,ca. 1862,1860,1864,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267955,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.169,false,true,267956,Photographs,Photograph,Bull Run. Blackburn's Ford,,,,,,Artist|Former Attribution,Formerly attributed to,Timothy H. O'Sullivan|Mathew B. Brady,"American, born Ireland, 1840–1882|American, born Ireland, 1823?–1896 New York",,"O'Sullivan, Timothy H.|Brady, Mathew B.","American, born Ireland",1840 |1823,1882 |1896,ca. 1862,1860,1864,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267956,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.186,false,true,267975,Photographs,Photograph,Confederate Prisoners at Belle Plain,,,,,,Artist|Former Attribution,Formerly attributed to,Timothy H. O'Sullivan|Mathew B. Brady,"American, born Ireland, 1840–1882|American, born Ireland, 1823?–1896 New York",,"O'Sullivan, Timothy H.|Brady, Mathew B.","American, born Ireland",1840 |1823,1882 |1896,1863,1863,1863,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267975,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.201,false,true,267993,Photographs,Panorama,"Camp of Confederate Prisoners at Belle Plain, May 12, 1863",,,,,,Artist|Former Attribution,Formerly attributed to,Timothy H. O'Sullivan|Mathew B. Brady,"American, born Ireland, 1840–1882|American, born Ireland, 1823?–1896 New York",,"O'Sullivan, Timothy H.|Brady, Mathew B.","American, born Ireland",1840 |1823,1882 |1896,1863,1863,1863,Albumen silver print from glass negative,15.1 x 20.7 cm (5 15/16 x 8 1/8 in. ),"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267993,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.233,false,true,268027,Photographs,Photograph,"Quartermaster and Ambulance Camp, 6th Corps, Brandy Station, Virginia",,,,,,Artist|Former Attribution,Formerly attributed to,Timothy H. O'Sullivan|Mathew B. Brady,"American, born Ireland, 1840–1882|American, born Ireland, 1823?–1896 New York",,"O'Sullivan, Timothy H.|Brady, Mathew B.","American, born Ireland",1840 |1823,1882 |1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268027,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.241,false,true,268036,Photographs,Photograph,"Pennsylvania Light Artillery, Battery B, Petersburg, Virginia",,,,,,Artist|Former Attribution,Formerly attributed to,Timothy H. O'Sullivan|Mathew B. Brady,"American, born Ireland, 1840–1882|American, born Ireland, 1823?–1896 New York",,"O'Sullivan, Timothy H.|Brady, Mathew B.","American, born Ireland",1840 |1823,1882 |1896,1864,1864,1864,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268036,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.256,false,true,268052,Photographs,Photograph,"Quartermaster and Ambulance Camp, Brandy Station, Virginia",,,,,,Artist|Former Attribution,Formerly attributed to,Timothy H. O'Sullivan|Mathew B. Brady,"American, born Ireland, 1840–1882|American, born Ireland, 1823?–1896 New York",,"O'Sullivan, Timothy H.|Brady, Mathew B.","American, born Ireland",1840 |1823,1882 |1896,1861–65,1861,1865,Albumen silver print from glass negative,13.2 x 20.6 cm (5 3/16 x 8 1/8 in. ),"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268052,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.258,false,true,268054,Photographs,Photograph,"Quartermaster and Ambulance Camp, Brandy Station, Virginia",,,,,,Artist|Former Attribution,Formerly attributed to,Timothy H. O'Sullivan|Mathew B. Brady,"American, born Ireland, 1840–1882|American, born Ireland, 1823?–1896 New York",,"O'Sullivan, Timothy H.|Brady, Mathew B.","American, born Ireland",1840 |1823,1882 |1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268054,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.259,false,true,268055,Photographs,Photograph,"Gabions in Engineers' Camp, Petersburg",,,,,,Artist|Former Attribution,Formerly attributed to,Timothy H. O'Sullivan|Mathew B. Brady,"American, born Ireland, 1840–1882|American, born Ireland, 1823?–1896 New York",,"O'Sullivan, Timothy H.|Brady, Mathew B.","American, born Ireland",1840 |1823,1882 |1896,1864,1864,1864,Albumen silver print from glass negative,16.3 x 20.2 cm (6 7/16 x 7 15/16 in. ),"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268055,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.260,false,true,268057,Photographs,Photograph,Aqueduct near Petersburg,,,,,,Artist|Former Attribution,Formerly attributed to,Timothy H. O'Sullivan|Mathew B. Brady,"American, born Ireland, 1840–1882|American, born Ireland, 1823?–1896 New York",,"O'Sullivan, Timothy H.|Brady, Mathew B.","American, born Ireland",1840 |1823,1882 |1896,1864,1864,1864,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268057,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.261,false,true,268058,Photographs,Photograph,"Fort Sedgwick near Petersburg, interior",,,,,,Artist|Former Attribution,Formerly attributed to,Timothy H. O'Sullivan|Mathew B. Brady,"American, born Ireland, 1840–1882|American, born Ireland, 1823?–1896 New York",,"O'Sullivan, Timothy H.|Brady, Mathew B.","American, born Ireland",1840 |1823,1882 |1896,1864,1864,1864,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268058,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.273,false,true,268071,Photographs,Photograph,Bull Run. Bridge Near Union Mills,,,,,,Artist|Former Attribution,Formerly attributed to,Timothy H. O'Sullivan|Mathew B. Brady,"American, born Ireland, 1840–1882|American, born Ireland, 1823?–1896 New York",,"O'Sullivan, Timothy H.|Brady, Mathew B.","American, born Ireland",1840 |1823,1882 |1896,ca. 1862,1860,1864,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268071,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.292,false,true,268092,Photographs,Photograph,"Warren Station, Virginia",,,,,,Artist|Former Attribution,Formerly attributed to,Timothy H. O'Sullivan|Mathew B. Brady,"American, born Ireland, 1840–1882|American, born Ireland, 1823?–1896 New York",,"O'Sullivan, Timothy H.|Brady, Mathew B.","American, born Ireland",1840 |1823,1882 |1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268092,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.294,false,true,268094,Photographs,Photograph,"Quartermaster cargoes and transports, Pamunkey River",,,,,,Artist|Former Attribution,Formerly attributed to,Timothy H. O'Sullivan|Mathew B. Brady,"American, born Ireland, 1840–1882|American, born Ireland, 1823?–1896 New York",,"O'Sullivan, Timothy H.|Brady, Mathew B.","American, born Ireland",1840 |1823,1882 |1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268094,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.327,false,true,268131,Photographs,Photograph,Appomattox River,,,,,,Artist|Former Attribution,Formerly attributed to,Timothy H. O'Sullivan|Mathew B. Brady,"American, born Ireland, 1840–1882|American, born Ireland, 1823?–1896 New York",,"O'Sullivan, Timothy H.|Brady, Mathew B.","American, born Ireland",1840 |1823,1882 |1896,1864,1864,1864,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268131,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.329,false,true,268133,Photographs,Photograph,"Cobb's Hill, Lookout in Distance",,,,,,Artist|Former Attribution,Formerly attributed to,Timothy H. O'Sullivan|Mathew B. Brady,"American, born Ireland, 1840–1882|American, born Ireland, 1823?–1896 New York",,"O'Sullivan, Timothy H.|Brady, Mathew B.","American, born Ireland",1840 |1823,1882 |1896,1864,1864,1864,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268133,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.330,false,true,268135,Photographs,Photograph,Appomattox River,,,,,,Artist|Former Attribution,Formerly attributed to,Timothy H. O'Sullivan|Mathew B. Brady,"American, born Ireland, 1840–1882|American, born Ireland, 1823?–1896 New York",,"O'Sullivan, Timothy H.|Brady, Mathew B.","American, born Ireland",1840 |1823,1882 |1896,1864,1864,1864,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268135,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.332,false,true,268137,Photographs,Photograph,Warren Station near Petersburg,,,,,,Artist|Former Attribution,Formerly attributed to,Timothy H. O'Sullivan|Mathew B. Brady,"American, born Ireland, 1840–1882|American, born Ireland, 1823?–1896 New York",,"O'Sullivan, Timothy H.|Brady, Mathew B.","American, born Ireland",1840 |1823,1882 |1896,1864,1864,1864,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268137,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.335,false,true,268140,Photographs,Photograph,Fort Sedgwick,,,,,,Artist|Former Attribution,Formerly attributed to,Timothy H. O'Sullivan|Mathew B. Brady,"American, born Ireland, 1840–1882|American, born Ireland, 1823?–1896 New York",,"O'Sullivan, Timothy H.|Brady, Mathew B.","American, born Ireland",1840 |1823,1882 |1896,1864,1864,1864,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268140,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.338,false,true,268143,Photographs,Photograph,Fort Sedgwick in front of Petersburg,,,,,,Artist|Former Attribution,Formerly attributed to,Timothy H. O'Sullivan|Mathew B. Brady,"American, born Ireland, 1840–1882|American, born Ireland, 1823?–1896 New York",,"O'Sullivan, Timothy H.|Brady, Mathew B.","American, born Ireland",1840 |1823,1882 |1896,1864,1864,1864,Albumen silver print from glass negative,8.7 x 9.4 cm (3 7/16 x 3 11/16 in. ),"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268143,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.342,false,true,268148,Photographs,Photograph,Fort Sedgwick in front of Petersburg,,,,,,Artist|Former Attribution,Formerly attributed to,Timothy H. O'Sullivan|Mathew B. Brady,"American, born Ireland, 1840–1882|American, born Ireland, 1823?–1896 New York",,"O'Sullivan, Timothy H.|Brady, Mathew B.","American, born Ireland",1840 |1823,1882 |1896,1864,1864,1864,Albumen silver print from glass negative,8.6 x 9.9 cm (3 3/8 x 3 7/8 in. ),"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268148,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.346,false,true,268152,Photographs,Photograph,Fort Price,,,,,,Artist|Former Attribution,Formerly attributed to,Timothy H. O'Sullivan|Mathew B. Brady,"American, born Ireland, 1840–1882|American, born Ireland, 1823?–1896 New York",,"O'Sullivan, Timothy H.|Brady, Mathew B.","American, born Ireland",1840 |1823,1882 |1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268152,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.348,false,true,268154,Photographs,Photograph,"[Fort Sedgwick]/[Fort Price]. Brady album, p. 27",,,,,,Artist|Former Attribution,Formerly attributed to,Timothy H. O'Sullivan|Mathew B. Brady,"American, born Ireland, 1840–1882|American, born Ireland, 1823?–1896 New York",,"O'Sullivan, Timothy H.|Brady, Mathew B.","American, born Ireland",1840 |1823,1882 |1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268154,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.355,false,true,268162,Photographs,Photograph,"Signal Tower, Cobb's Hill, Appomattox River",,,,,,Artist|Former Attribution,Formerly attributed to,Timothy H. O'Sullivan|Mathew B. Brady,"American, born Ireland, 1840–1882|American, born Ireland, 1823?–1896 New York",,"O'Sullivan, Timothy H.|Brady, Mathew B.","American, born Ireland",1840 |1823,1882 |1896,1864,1864,1864,Albumen silver print from glass negative,10.1 x 9.9 cm (4 x 3 7/8 in. ),"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268162,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.374,false,true,268183,Photographs,Photograph,New York Herald Headquarters,,,,,,Artist|Former Attribution,Formerly attributed to,Timothy H. O'Sullivan|Mathew B. Brady,"American, born Ireland, 1840–1882|American, born Ireland, 1823?–1896 New York",,"O'Sullivan, Timothy H.|Brady, Mathew B.","American, born Ireland",1840 |1823,1882 |1896,1863,1863,1863,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268183,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.393,false,true,268204,Photographs,Panorama,Gettysburg from the West,,,,,,Artist|Former Attribution,Formerly attributed to,Timothy H. O'Sullivan|Mathew B. Brady,"American, born Ireland, 1840–1882|American, born Ireland, 1823?–1896 New York",,"O'Sullivan, Timothy H.|Brady, Mathew B.","American, born Ireland",1840 |1823,1882 |1896,1863,1863,1863,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268204,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.398,false,true,268209,Photographs,Photograph,"Signal Corps, Rappidan River/Signal Corps Reconnoitering at Fredericksburg, Virginia",,,,,,Artist|Former Attribution,Formerly attributed to,Timothy H. O'Sullivan|Mathew B. Brady,"American, born Ireland, 1840–1882|American, born Ireland, 1823?–1896 New York",,"O'Sullivan, Timothy H.|Brady, Mathew B.","American, born Ireland",1840 |1823,1882 |1896,1863,1863,1863,Albumen silver print from glass negative,13.4 x 20.2 cm (5 1/4 x 7 15/16 in. ),"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268209,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1982.1182.1131–.1151,false,true,288302,Photographs,Stereographs,[Group of 21 Stereograph Views of China],,,,,,Artist|Artist|Publisher|Publisher|Publisher|Photography Studio|Publisher|Artist|Artist|Artist|Publisher|Publisher|Editor|Artist,,"T. W. Ingersoll|Kilburn Brothers|James M. Davis|Kawin and Company|Underwood & Underwood|Sun Sculpture Works and Studios|Griffith & Griffith, American|George W. Griffith|Strohmeyer & Wyman|C. H. Graves|Universal Photo Art Co.|J. Good|B. K.|Unknown","American, active ca. 1865–1890|American|American|American|American|American|French",,"Ingersoll, T. W.|Kilburn Brothers|Davis, James M.|Kawin and Company|Underwood & Underwood|Sun Sculpture Works and Studios|Griffith & Griffith|Griffith, George W.|Strohmeyer & Wyman|Graves, C. H.|Universal Photo Art Co.|Good, J.|K., B.|Unknown",American|American|American|American|American|American|American|French,1863,1892,1850s–1910s,1850,1919,Albumen silver prints,Mounts approximately: 8.4 x 17.1 cm (3 5/16 x 6 3/4 in.) to 10 x 17.8 cm (3 15/16 x 7 in.),"Gift of Weston J. Naef, in memory of Kathleen W. Naef and Weston J. Naef Sr., 1982",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288302,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.1101,false,true,631959,Photographs,Photograph,La Tour Eiffel. – Détail du Campanile.,,,,,,Photography Studio|Artist|Artist,,Neurdein Frères|Louis-Antonin Neurdein|Etienne Neurdein,"French, active Paris, 1870s–1900s|French, 1846–after 1915|French, 1832–after 1915",,"Neurdein Frères|Neurdein, Louis-Antonin|Neurdein, Etienne",French|French|French,1863 |1846 |1832,1910 |1920 |1920,1889,1889,1889,Albumen silver print,Image: 8 5/8 × 10 11/16 in. (21.9 × 27.1 cm) Mount: 12 1/2 × 14 13/16 in. (31.7 × 37.6 cm),"Gift of Stéphane Samuel and Robert Melvin Rubin, 2013",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/631959,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1999.188.1,false,true,282759,Photographs,Photograph,"Boutique, Marché aux Halles, Paris",,,,,,Artist|Printer,,Eugène Atget|Berenice Abbott,"French, Libourne 1857–1927 Paris|American, Springfield, Ohio 1898–1991 Monson, Maine",,"Atget, Eugène|Abbott, Berenice",French|American,1857-02-02|1898,1927-08-04|1991,"1925, printed ca. 1929",1925,1925,Gelatin silver print,23.1 x 17 cm (9 1/8 x 6 11/16 in. ),"Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 1999",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/282759,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1999.188.2,false,true,282760,Photographs,Photograph,Fête du Trône,,,,,,Artist|Printer,,Eugène Atget|Berenice Abbott,"French, Libourne 1857–1927 Paris|American, Springfield, Ohio 1898–1991 Monson, Maine",,"Atget, Eugène|Abbott, Berenice",French|American,1857-02-02|1898,1927-08-04|1991,"1925, printed ca. 1929",1925,1925,,23.4 x 17 cm (9 3/16 x 6 11/16 in. ),"Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 1999",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/282760,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1982.1182.747–.763,false,true,288230,Photographs,Stereographs,[Group of 17 Early Stereograph Views of British Churches],,,,,,Publisher|Artist|Artist|Artist|Artist|Artist|Publisher|Publisher|Artist,,Sedgfield's English Scenery|E. W. Wyatt|Mrs. Charles Lawrence|Unknown|Unknown|Francis Bedford|Catherall and Prichard|London Stereoscopic Company|Taylor,"British|British|British|British|British, London 1816–1894 London|British|British|British",,"Sedgfield's English Scenery|Wyatt, E. W.|Lawrence, Charles Mrs.|Unknown|Unknown|Bedford, Francis|Catherall and Prichard|London Stereoscopic Company|Taylor, Mr.",British|British|British|British|British|British|British,1816,1894,1850s–1910s,1850,1919,Albumen silver prints,Mounts approximately: 8.5 x 16.9 cm (3 3/8 x 6 5/8 in.) to 8.7 x 17.7 cm (3 7/16 x 6 15/16 in.),"Gift of Weston J. Naef, in memory of Kathleen W. Naef and Weston J. Naef Sr., 1982",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288230,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.410 (9a-d),false,true,288168,Photographs,Cartes-de-visite,"[Duchesse d'Albe, Unknown Sitter, Duchesse de Morny, and Duc de Morny]",,,,,,Artist|Artist|Person in Photograph|Person in Photograph,Painted and retouched by|Person in photograph|Person in photograph,"Pierre-Louis Pierson|Marck|Sophie Troubetzkoi, duchesse de Morny|Charles-Auguste-Louis-Joseph de Morny, duc de Morny","French, 1822–1913|Russian, Moscow 1838–1896|French (born Switzerland) 1811–1865 Paris",", et al","Pierson, Pierre-Louis|Marck|Troubetzkoi, Sophie, duchesse de Morny|Morny, Charles-Auguste-Louis-Joseph de, duc de Morny","French|Russian|French, born Switzerland",1822 |1838 |1811,1913 |1896 |1865,before 1865,1855,1865,Albumen silver prints from glass negatives,Image: 3 3/8 in. × 2 in. (8.6 × 5.1 cm) (each),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288168,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.1202,false,true,285645,Photographs,Photograph,"Chief Officer and Clerks of the Ambulance Department, 9th Army Corps, in Front of Petersburg, Virginia",,,,,,Artist|Printer,,Timothy H. O'Sullivan|Alexander Gardner,"American, born Ireland, 1840–1882|American, Glasgow, Scotland 1821–1882 Washington, D.C.",,"O'Sullivan, Timothy H.|Gardner, Alexander","American, born Ireland|American, Scottish",1840 |1821,1882 |1882,August 1864,1864,1864,Albumen silver print from glass negative,Image: 6 7/8 × 8 7/8 in. (17.5 × 22.5 cm) Mount: 10 5/16 × 15 1/16 in. (26.2 × 38.2 cm),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285645,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.502.1 (37),false,true,286882,Photographs,Photograph,"Field Where General Reynolds Fell, Gettysburg",,,,,,Artist|Printer,,Timothy H. O'Sullivan|Alexander Gardner,"American, born Ireland, 1840–1882|American, Glasgow, Scotland 1821–1882 Washington, D.C.",,"O'Sullivan, Timothy H.|Gardner, Alexander","American, born Ireland|American, Scottish",1840 |1821,1882 |1882,1863,1863,1863,Albumen silver print from glass negative,17.8 x 22.7 cm (7 x 8 15/16 in.),"Gilman Collection, Purchase, Ann Tenenbaum and Thomas H. Lee Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286882,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1982.1182.950–.952,false,true,288285,Photographs,Stereographs,"[Group of 3 Stereograph Views of Fleet Street, London, England]",,,,,,Publisher|Artist|Artist|Artist|Publisher,,Underwood & Underwood|J. F. Jarvis|Benneville Lloyd Singley|Unknown|Keystone View Company,"American|American|American, Union Township, Pennsylvania 1864–1938 Meadville, Pennsylvania",,"Underwood & Underwood|Jarvis, J. F.|Singley, Benneville Lloyd|Unknown|Keystone View Company",American|American|American,1864,1938,1850s–1910s,1850,1919,Albumen silver prints,Mounts approximately: 8.9 x 17.8 cm (3 1/2 x 7 in.),"Gift of Weston J. Naef, in memory of Kathleen W. Naef and Weston J. Naef Sr., 1982",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288285,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.1133,false,true,286099,Photographs,Photograph,"Fortifications, Manassas",,,,,,Artist|Artist|Artist|Publisher,,Barnard & Gibson|George N. Barnard|James F. Gibson|Brady & Co.,"American, active 1860s|American, 1819–1902|American, born 1828|American, active 1840s–1880s",,"Barnard & Gibson|Barnard, George N.|Gibson, James F.|Brady & Co.",American|American|American|American,1860 |1819 |1828 |1840,1870 |1902 |1928 |1889,March 1862,1862,1862,Albumen silver print from glass negative,Image: 18.7 × 23.4 cm (7 3/8 × 9 3/16 in.) Mount: 27.1 × 34.6 cm (10 11/16 × 13 5/8 in.),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286099,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.22,false,true,283094,Photographs,Photograph,"Flora Rankin, Irene MacDonald, and Mary Josephine MacDonald at Elm Lodge",,,,,,Artist|Subject|Subject|Subject,,Lewis Carroll|Flora Rankin|Irene MacDonald|Mary Josephine MacDonald,"British, Daresbury, Cheshire 1832–1898 Guildford|British|British, born 1857|British, 1853–1878",,"Carroll, Lewis|Rankin, Flora|MacDonald, Irene|MacDonald, Mary Josephine",British|British|British|British,1832 |1857 |1853,1898 |1878,July 1863,1863,1863,Albumen silver print from glass negative,22.2 x 18 cm (8 3/4 x 7 1/16 in.),"Gilman Collection, Purchase, Joyce F. Menschel Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/283094,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.25,false,true,268045,Photographs,Photograph,"Belle Plain, Virginia",,,,,,Artist|Artist|Former Attribution,Formerly attributed to,James Gardner|Timothy H. O'Sullivan|Mathew B. Brady,"American, born 1832|American, born Ireland, 1840–1882|American, born Ireland, 1823?–1896 New York",,"Gardner, James|O'Sullivan, Timothy H.|Brady, Mathew B.","American|American, born Ireland",1832 |1840 |1823,1882 |1896,1864,1864,1864,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268045,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.139,false,true,267923,Photographs,Photograph,"Belle Plain, Virginia. Potomac River, Upper Wharf",,,,,,Artist|Artist|Former Attribution,Formerly attributed to,James Gardner|Timothy H. O'Sullivan|Mathew B. Brady,"American, born 1832|American, born Ireland, 1840–1882|American, born Ireland, 1823?–1896 New York",,"Gardner, James|O'Sullivan, Timothy H.|Brady, Mathew B.","American|American, born Ireland",1832 |1840 |1823,1882 |1896,1864,1864,1864,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267923,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.165,false,true,267952,Photographs,Photograph,"Belle Plain, Virginia",,,,,,Artist|Artist|Former Attribution,Formerly attributed to,James Gardner|Timothy H. O'Sullivan|Mathew B. Brady,"American, born 1832|American, born Ireland, 1840–1882|American, born Ireland, 1823?–1896 New York",,"Gardner, James|O'Sullivan, Timothy H.|Brady, Mathew B.","American|American, born Ireland",1832 |1840 |1823,1882 |1896,1864,1864,1864,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267952,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.200,false,true,267992,Photographs,Photograph,"Confederate Earthworks, Belle Plain, Virginia",,,,,,Artist|Artist|Former Attribution,Formerly attributed to,James Gardner|Timothy H. O'Sullivan|Mathew B. Brady,"American, born 1832|American, born Ireland, 1840–1882|American, born Ireland, 1823?–1896 New York",,"Gardner, James|O'Sullivan, Timothy H.|Brady, Mathew B.","American|American, born Ireland",1832 |1840 |1823,1882 |1896,1863,1863,1863,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267992,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.202,false,true,267994,Photographs,Photograph,"Belle Plain, Virginia",,,,,,Artist|Artist|Former Attribution,Formerly attributed to,James Gardner|Timothy H. O'Sullivan|Mathew B. Brady,"American, born 1832|American, born Ireland, 1840–1882|American, born Ireland, 1823?–1896 New York",,"Gardner, James|O'Sullivan, Timothy H.|Brady, Mathew B.","American|American, born Ireland",1832 |1840 |1823,1882 |1896,1864,1864,1864,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267994,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.255,false,true,268051,Photographs,Photograph,Camp near Brandy Station,,,,,,Artist|Artist|Former Attribution,Formerly attributed to,James Gardner|Timothy H. O'Sullivan|Mathew B. Brady,"American, born 1832|American, born Ireland, 1840–1882|American, born Ireland, 1823?–1896 New York",,"Gardner, James|O'Sullivan, Timothy H.|Brady, Mathew B.","American|American, born Ireland",1832 |1840 |1823,1882 |1896,1863–64,1863,1864,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268051,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.184,false,true,267973,Photographs,Photograph,"Belle Plain, Virginia",,,,,,Artist|Artist|Former Attribution,Formerly attributed to,Timothy H. O'Sullivan|James Gardner|Mathew B. Brady,"American, born Ireland, 1840–1882|American, born 1832|American, born Ireland, 1823?–1896 New York",,"O'Sullivan, Timothy H.|Gardner, James|Brady, Mathew B.","American, born Ireland|American",1840 |1832 |1823,1882 |1896,1864,1864,1864,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267973,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.203,false,true,267995,Photographs,Photograph,"Belle Plain, Virginia",,,,,,Artist|Artist|Former Attribution,Formerly attributed to,Timothy H. O'Sullivan|James Gardner|Mathew B. Brady,"American, born Ireland, 1840–1882|American, born 1832|American, born Ireland, 1823?–1896 New York",,"O'Sullivan, Timothy H.|Gardner, James|Brady, Mathew B.","American, born Ireland|American",1840 |1832 |1823,1882 |1896,1864,1864,1864,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267995,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.204,false,true,267996,Photographs,Photograph,Belle Plain. Distant View of Landing,,,,,,Artist|Artist|Former Attribution,Formerly attributed to,Timothy H. O'Sullivan|James Gardner|Mathew B. Brady,"American, born Ireland, 1840–1882|American, born 1832|American, born Ireland, 1823?–1896 New York",,"O'Sullivan, Timothy H.|Gardner, James|Brady, Mathew B.","American, born Ireland|American",1840 |1832 |1823,1882 |1896,1864,1864,1864,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267996,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.257,false,true,268053,Photographs,Photograph,"Brandy Station, Virginia",,,,,,Artist|Artist|Former Attribution,Formerly attributed to,Timothy H. O'Sullivan|James Gardner|Mathew B. Brady,"American, born Ireland, 1840–1882|American, born 1832|American, born Ireland, 1823?–1896 New York",,"O'Sullivan, Timothy H.|Gardner, James|Brady, Mathew B.","American, born Ireland|American",1840 |1832 |1823,1882 |1896,1863–64,1863,1864,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268053,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.313,false,true,268116,Photographs,Photograph,"Belle Plain, Virginia. Lower Wharf",,,,,,Artist|Artist|Former Attribution,Formerly attributed to,Timothy H. O'Sullivan|James Gardner|Mathew B. Brady,"American, born Ireland, 1840–1882|American, born 1832|American, born Ireland, 1823?–1896 New York",,"O'Sullivan, Timothy H.|Gardner, James|Brady, Mathew B.","American, born Ireland|American",1840 |1832 |1823,1882 |1896,1864,1864,1864,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268116,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1982.1182.1230–.1240,false,true,288308,Photographs,Stereographs,[Group of 11 Stereograph Views of Cowboys],,,,,,Publisher|Publisher|Artist|Photography Studio|Artist|Publisher,,European and American Views|Underwood & Underwood|Benneville Lloyd Singley|Sun Sculpture Works and Studios|Unknown|Keystone View Company,"American|American, Union Township, Pennsylvania 1864–1938 Meadville, Pennsylvania|American|American",,"European and American Views|Underwood & Underwood|Singley, Benneville Lloyd|Sun Sculpture Works and Studios|Unknown|Keystone View Company",American|American|American,1864,1938,1850s–1910s,1850,1919,Albumen silver prints,Mounts approximately: 3.9 x 17.8 cm (1 9/16 x 7 in.),"Gift of Weston J. Naef, in memory of Kathleen W. Naef and Weston J. Naef Sr., 1982",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288308,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.22,false,true,268013,Photographs,Photograph,"Ruins of Richmond & Petersburg Railroad Bridge, Richmond, Virginia",,,,,,Artist|Former Attribution,Formerly attributed to,Alexander Gardner|Mathew B. Brady,"American, Glasgow, Scotland 1821–1882 Washington, D.C.|American, born Ireland, 1823?–1896 New York",,"Gardner, Alexander|Brady, Mathew B.","American, Scottish",1821 |1823,1882 |1896,ca. 1865,1863,1867,Albumen silver print from glass negative,Image: 16.3 × 21.4 cm (6 7/16 × 8 7/16 in.),"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268013,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.57,false,true,268249,Photographs,Photograph,"St. Peter's Church near White House, Where Washington was Married. General E. V. Sumner and Staff",,,,,,Artist|Former Attribution,Formerly attributed to,Alexander Gardner|Mathew B. Brady,"American, Glasgow, Scotland 1821–1882 Washington, D.C.|American, born Ireland, 1823?–1896 New York",,"Gardner, Alexander|Brady, Mathew B.","American, Scottish",1821 |1823,1882 |1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268249,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.211,false,true,268004,Photographs,Photograph,"Custom House, Richmond, Virginia (after evacuation)",,,,,,Artist|Former Attribution,Formerly attributed to,Alexander Gardner|Mathew B. Brady,"American, Glasgow, Scotland 1821–1882 Washington, D.C.|American, born Ireland, 1823?–1896 New York",,"Gardner, Alexander|Brady, Mathew B.","American, Scottish",1821 |1823,1882 |1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268004,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.212,false,true,268005,Photographs,Photograph,"Sanitary Commission Headquarters, Richmond, Virginia",,,,,,Artist|Former Attribution,Formerly attributed to,Alexander Gardner|Mathew B. Brady,"American, Glasgow, Scotland 1821–1882 Washington, D.C.|American, born Ireland, 1823?–1896 New York",,"Gardner, Alexander|Brady, Mathew B.","American, Scottish",1821 |1823,1882 |1896,1865,1865,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268005,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.213,false,true,268006,Photographs,Photograph,"Richmond, Virginia",,,,,,Artist|Former Attribution,Formerly attributed to,Alexander Gardner|Mathew B. Brady,"American, Glasgow, Scotland 1821–1882 Washington, D.C.|American, born Ireland, 1823?–1896 New York",,"Gardner, Alexander|Brady, Mathew B.","American, Scottish",1821 |1823,1882 |1896,1865,1865,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268006,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.214,false,true,268007,Photographs,Photograph,"Penitentiary, Richmond",,,,,,Artist|Former Attribution,Formerly attributed to,Alexander Gardner|Mathew B. Brady,"American, Glasgow, Scotland 1821–1882 Washington, D.C.|American, born Ireland, 1823?–1896 New York",,"Gardner, Alexander|Brady, Mathew B.","American, Scottish",1821 |1823,1882 |1896,1865,1865,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268007,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.215,false,true,268008,Photographs,Photograph,"Castle Thunder, ex-tobacco factory, Petersburg",,,,,,Artist|Former Attribution,Formerly attributed to,Alexander Gardner|Mathew B. Brady,"American, Glasgow, Scotland 1821–1882 Washington, D.C.|American, born Ireland, 1823?–1896 New York",,"Gardner, Alexander|Brady, Mathew B.","American, Scottish",1821 |1823,1882 |1896,1864,1864,1864,Albumen silver print from glass negative,15.9 x 20.4 cm (6 1/4 x 8 1/16 in.),"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268008,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.216,false,true,268009,Photographs,Photograph,"Ruins near Canal Basin, Richmond",,,,,,Artist|Former Attribution,Formerly attributed to,Alexander Gardner|Mathew B. Brady,"American, Glasgow, Scotland 1821–1882 Washington, D.C.|American, born Ireland, 1823?–1896 New York",,"Gardner, Alexander|Brady, Mathew B.","American, Scottish",1821 |1823,1882 |1896,1865,1865,1865,Albumen silver print from glass negative,15.8 x 21 cm (6 1/4 x 8 1/4 in. ),"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268009,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.219,false,true,268012,Photographs,Photograph,"Smokestack of Confederate Ram Merrimac at Richmond/Remains of Ironclad Ram ""Virginia #2"", April, 1865",,,,,,Artist|Former Attribution,Formerly attributed to,Alexander Gardner|Mathew B. Brady,"American, Glasgow, Scotland 1821–1882 Washington, D.C.|American, born Ireland, 1823?–1896 New York",,"Gardner, Alexander|Brady, Mathew B.","American, Scottish",1821 |1823,1882 |1896,1865,1865,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268012,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.220,false,true,268014,Photographs,Photograph,Richmond after the Evacuation,,,,,,Artist|Former Attribution,Formerly attributed to,Alexander Gardner|Mathew B. Brady,"American, Glasgow, Scotland 1821–1882 Washington, D.C.|American, born Ireland, 1823?–1896 New York",,"Gardner, Alexander|Brady, Mathew B.","American, Scottish",1821 |1823,1882 |1896,1865,1865,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268014,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.221,false,true,268015,Photographs,Photograph,"Richmond, Virginia, after Evacuation",,,,,,Artist|Former Attribution,Formerly attributed to,Alexander Gardner|Mathew B. Brady,"American, Glasgow, Scotland 1821–1882 Washington, D.C.|American, born Ireland, 1823?–1896 New York",,"Gardner, Alexander|Brady, Mathew B.","American, Scottish",1821 |1823,1882 |1896,1865,1865,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268015,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.222,false,true,268016,Photographs,Photograph,"Wharves at Richmond, Virginia",,,,,,Artist|Former Attribution,Formerly attributed to,Alexander Gardner|Mathew B. Brady,"American, Glasgow, Scotland 1821–1882 Washington, D.C.|American, born Ireland, 1823?–1896 New York",,"Gardner, Alexander|Brady, Mathew B.","American, Scottish",1821 |1823,1882 |1896,1865,1865,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268016,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.223,false,true,268017,Photographs,Photograph,"Ruins of Arsenal, Richmond, Virginia",,,,,,Artist|Former Attribution,Formerly attributed to,Alexander Gardner|Mathew B. Brady,"American, Glasgow, Scotland 1821–1882 Washington, D.C.|American, born Ireland, 1823?–1896 New York",,"Gardner, Alexander|Brady, Mathew B.","American, Scottish",1821 |1823,1882 |1896,1865,1865,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268017,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.224,false,true,268018,Photographs,Photograph,"Ruins on North Bank of Canal, Richmond",,,,,,Artist|Former Attribution,Formerly attributed to,Alexander Gardner|Mathew B. Brady,"American, Glasgow, Scotland 1821–1882 Washington, D.C.|American, born Ireland, 1823?–1896 New York",,"Gardner, Alexander|Brady, Mathew B.","American, Scottish",1821 |1823,1882 |1896,1865,1865,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268018,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.225,false,true,268019,Photographs,Photograph,"Ruins of R & P Railroad Bridge, Richmond",,,,,,Artist|Former Attribution,Formerly attributed to,Alexander Gardner|Mathew B. Brady,"American, Glasgow, Scotland 1821–1882 Washington, D.C.|American, born Ireland, 1823?–1896 New York",,"Gardner, Alexander|Brady, Mathew B.","American, Scottish",1821 |1823,1882 |1896,ca. 1865,1863,1867,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268019,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.227,false,true,268020,Photographs,Photograph,"Ruins at end of Richmond and Petersburg Railroad Bridge, Richmond",,,,,,Artist|Former Attribution,Formerly attributed to,Alexander Gardner|Mathew B. Brady,"American, Glasgow, Scotland 1821–1882 Washington, D.C.|American, born Ireland, 1823?–1896 New York",,"Gardner, Alexander|Brady, Mathew B.","American, Scottish",1821 |1823,1882 |1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268020,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.229,false,true,268022,Photographs,Photograph,"Richmond, Virginia",,,,,,Artist|Former Attribution,Formerly attributed to,Alexander Gardner|Mathew B. Brady,"American, Glasgow, Scotland 1821–1882 Washington, D.C.|American, born Ireland, 1823?–1896 New York",,"Gardner, Alexander|Brady, Mathew B.","American, Scottish",1821 |1823,1882 |1896,1865,1865,1865,Albumen silver print from glass negative,17 x 21.1 cm (6 11/16 x 8 5/16 in. ),"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268022,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.230,false,true,268024,Photographs,Photograph,"Jeff. Davis House, Executive Mansion, C.S.A., Richmond",,,,,,Artist|Former Attribution,Formerly attributed to,Alexander Gardner|Mathew B. Brady,"American, Glasgow, Scotland 1821–1882 Washington, D.C.|American, born Ireland, 1823?–1896 New York",,"Gardner, Alexander|Brady, Mathew B.","American, Scottish",1821 |1823,1882 |1896,1865,1865,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268024,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.231,false,true,268025,Photographs,Photograph,"Richmond, Virginia. Looking toward Manchester",,,,,,Artist|Former Attribution,Formerly attributed to,Alexander Gardner|Mathew B. Brady,"American, Glasgow, Scotland 1821–1882 Washington, D.C.|American, born Ireland, 1823?–1896 New York",,"Gardner, Alexander|Brady, Mathew B.","American, Scottish",1821 |1823,1882 |1896,ca. 1865,1863,1867,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268025,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.287,false,true,268086,Photographs,Photograph,Headquarters of General Hooker,,,,,,Artist|Former Attribution,Formerly attributed to,Alexander Gardner|Mathew B. Brady,"American, Glasgow, Scotland 1821–1882 Washington, D.C.|American, born Ireland, 1823?–1896 New York",,"Gardner, Alexander|Brady, Mathew B.","American, Scottish",1821 |1823,1882 |1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268086,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.368,false,true,268176,Photographs,Photograph,Aquia Creek Landing,,,,,,Artist|Former Attribution,Formerly attributed to,Alexander Gardner|Mathew B. Brady,"American, Glasgow, Scotland 1821–1882 Washington, D.C.|American, born Ireland, 1823?–1896 New York",,"Gardner, Alexander|Brady, Mathew B.","American, Scottish",1821 |1823,1882 |1896,1863,1863,1863,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268176,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"33.65.11, .226",true,true,267891,Photographs,Panorama,"Ruins of Gallego Flour Mills, Richmond",,,,,,Artist|Former Attribution,Formerly attributed to,Alexander Gardner|Mathew B. Brady,"American, Glasgow, Scotland 1821–1882 Washington, D.C.|American, born Ireland, 1823?–1896 New York",,"Gardner, Alexander|Brady, Mathew B.","American, Scottish",1821 |1823,1882 |1896,1865,1865,1865,Albumen silver prints from glass negatives,16.3 x 36.9 cm (6 7/16 x 14 1/2 in.),"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267891,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.123,false,true,267906,Photographs,Photograph,Bull Run. Orange and Alexandria R.R. near Union Mills,,,,,,Artist|Artist|Former Attribution,Formerly attributed to,George N. Barnard|Timothy H. O'Sullivan|Mathew B. Brady,"American, 1819–1902|American, born Ireland, 1840–1882|American, born Ireland, 1823?–1896 New York",,"Barnard, George N.|O'Sullivan, Timothy H.|Brady, Mathew B.","American|American, born Ireland",1819 |1840 |1823,1902 |1882 |1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267906,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.272,false,true,268070,Photographs,Photograph,"Bull Run, Virginia",,,,,,Artist|Artist|Former Attribution,Formerly attributed to,George N. Barnard|Timothy H. O'Sullivan|Mathew B. Brady,"American, 1819–1902|American, born Ireland, 1840–1882|American, born Ireland, 1823?–1896 New York",,"Barnard, George N.|O'Sullivan, Timothy H.|Brady, Mathew B.","American|American, born Ireland",1819 |1840 |1823,1902 |1882 |1896,1861–62,1861,1862,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268070,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.159,false,true,267945,Photographs,Photograph,"Crow's Nest, Battery and Lookout",,,,,,Artist|Artist|Former Attribution,Formerly attributed to,Andrew Joseph Russell|Timothy H. O'Sullivan|Mathew B. Brady,"American, 1830–1902|American, born Ireland, 1840–1882|American, born Ireland, 1823?–1896 New York",,"Russell, Andrew Joseph|O'Sullivan, Timothy H.|Brady, Mathew B.","American|American, born Ireland",1830 |1840 |1823,1902 |1882 |1896,1864,1864,1864,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267945,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.107,false,true,267888,Photographs,Photograph,Bull Run. Orange and Alexandria R.R. near Union Mills,,,,,,Artist|Artist|Former Attribution,Formerly attributed to,Timothy H. O'Sullivan|George N. Barnard|Mathew B. Brady,"American, born Ireland, 1840–1882|American, 1819–1902|American, born Ireland, 1823?–1896 New York",,"O'Sullivan, Timothy H.|Barnard, George N.|Brady, Mathew B.","American, born Ireland|American",1840 |1819 |1823,1882 |1902 |1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267888,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.108,false,true,267889,Photographs,Photograph,Bull Run. The Stone Bridge,,,,,,Artist|Artist|Former Attribution,Formerly attributed to,Timothy H. O'Sullivan|George N. Barnard|Mathew B. Brady,"American, born Ireland, 1840–1882|American, 1819–1902|American, born Ireland, 1823?–1896 New York",,"O'Sullivan, Timothy H.|Barnard, George N.|Brady, Mathew B.","American, born Ireland|American",1840 |1819 |1823,1882 |1902 |1896,1861–62,1861,1862,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267889,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.134,false,true,267918,Photographs,Photograph,Bull Run. Pontoon Bridge near Blackburn's Ford,,,,,,Artist|Artist|Former Attribution,Formerly attributed to,Timothy H. O'Sullivan|George N. Barnard|Mathew B. Brady,"American, born Ireland, 1840–1882|American, 1819–1902|American, born Ireland, 1823?–1896 New York",,"O'Sullivan, Timothy H.|Barnard, George N.|Brady, Mathew B.","American, born Ireland|American",1840 |1819 |1823,1882 |1902 |1896,1862,1862,1862,Albumen silver print from glass negative,13.1 x 20.2 cm (5 3/16 x 7 15/16 in. ),"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267918,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.365,false,true,268173,Photographs,Photograph,"Confederate Fortifications, Petersburg",,,,,,Artist|Artist|Former Attribution,Formerly attributed to,Timothy H. O'Sullivan|Andrew Joseph Russell|Mathew B. Brady,"American, born Ireland, 1840–1882|American, 1830–1902|American, born Ireland, 1823?–1896 New York",,"O'Sullivan, Timothy H.|Russell, Andrew Joseph|Brady, Mathew B.","American, born Ireland|American",1840 |1830 |1823,1882 |1902 |1896,1864,1864,1864,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268173,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1982.1182.308–.311,false,true,288136,Photographs,Stereographs,[Group of 4 Stereograph Views of Berlin Beer Gardens],,,,,,Publisher|Publisher|Artist|Publisher|Artist|Artist|Publisher,,Strohmeyer & Wyman|Underwood & Underwood|Bert Underwood|Moser Senior|Benneville Lloyd Singley|Unknown|Keystone View Company,"American|American|American|German|American, Union Township, Pennsylvania 1864–1938 Meadville, Pennsylvania",,"Strohmeyer & Wyman|Underwood & Underwood|Underwood, Bert|Moser Senior|Singley, Benneville Lloyd|Unknown|Keystone View Company",American|American|American|German|American,1864,1938,1850s–1910s,1850,1919,Albumen silver prints,Mounts approximately: 8.9 x 17.8 cm (3 1/2 x 7 in.),"Gift of Weston J. Naef, in memory of Kathleen W. Naef and Weston J. Naef Sr., 1982",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288136,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1982.1182.401–.407,false,true,288140,Photographs,Stereographs,"[Group of 7 Stereograph Views of the Forth Bridge, Queensferry, Scotland]",,,,,,Publisher|Artist|Publisher|Artist|Photography Studio|Artist|Publisher,,International Stereoscopic View Company|Strohmeyer & Wyman|Underwood & Underwood|Benneville Lloyd Singley|Sun Sculpture Works and Studios|Unknown|Keystone View Company,"American|American|American|American, Union Township, Pennsylvania 1864–1938 Meadville, Pennsylvania|American",,"International Stereoscopic View Company|Strohmeyer & Wyman|Underwood & Underwood|Singley, Benneville Lloyd|Sun Sculpture Works and Studios|Unknown|Keystone View Company",American|American|American|American|American,1864,1938,1850s–1910s,1850,1919,Albumen silver prints,Mounts approximately: 8.9 x 17.8 cm (3 1/2 x 7 in.),"Gift of Weston J. Naef, in memory of Kathleen W. Naef and Weston J. Naef Sr., 1982",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288140,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1982.1182.917–.920,false,true,288272,Photographs,Stereographs,[Group of 4 Stereograph Views of London Bridges],,,,,,Publisher|Artist|Publisher|Publisher|Artist|Artist|Artist|Publisher,,"Griffith & Griffith, American|George W. Griffith|Strohmeyer & Wyman|Underwood & Underwood|Benneville Lloyd Singley|J. F. Jarvis|Unknown|Keystone View Company","American|American|American|American, Union Township, Pennsylvania 1864–1938 Meadville, Pennsylvania|American",,"Griffith & Griffith|Griffith, George W.|Strohmeyer & Wyman|Underwood & Underwood|Singley, Benneville Lloyd|Jarvis, J. F.|Unknown|Keystone View Company",American|American|American|American|American|American,1864,1938,1850s–1910s,1850,1919,Albumen silver prints,Mounts approximately: 8.9 x 17.8 cm (3 1/2 x 7 in.),"Gift of Weston J. Naef, in memory of Kathleen W. Naef and Weston J. Naef Sr., 1982",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288272,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.159.5,false,true,306206,Photographs,Photograph,"Edith, Ina and Alice Liddell on a Sofa",,,,,,Artist|Person in Photograph|Person in Photograph|Person in Photograph,Person in photograph|Person in photograph|Person in photograph,Lewis Carroll|Alice Pleasance Liddell|Edith Mary Liddell|Ina Liddell,"British, Daresbury, Cheshire 1832–1898 Guildford|British, 1852–1934|British, 1854–1876|British, 1849–1930",,"Carroll, Lewis|Liddell, Alice|Liddell, Edith Mary|Liddell, Ina",British|British|British|British,1832 |1852 |1854 |1849,1898 |1934 |1876 |1930,Summer 1858,1858,1858,Albumen silver print from glass negative,Mount: 4 3/16 in. × 6 7/16 in. (10.7 × 16.3 cm) Image: 4 1/16 × 6 1/16 in. (10.3 × 15.4 cm),"Bequest of Maurice B. Sendak, 2012",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306206,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.627.1,false,true,261925,Photographs,Photograph,Abraham Lincoln,,,,,,Artist|Printer|Person in Photograph,,Alexander Gardner|Rice|Abraham Lincoln,"American, Glasgow, Scotland 1821–1882 Washington, D.C.|American, Hardin County, Kentucky 1809–1865 Washington, D.C.",,"Gardner, Alexander|Rice|Lincoln, Abraham","American, Scottish|American",1821 |1809,1882 |1865,"1863, printed 1901",1863,1863,Gelatin silver print,Image: 45.7 x 38.1 cm (18 x 15 in.),"Warner Communications Inc. Purchase Fund, 1976",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261925,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -67.580.4.4,false,true,288741,Photographs,Carte-de-visite,[Napoleon III and Empress Eugenie],,,,,,Artist|Person in Photograph|Person in Photograph,,André-Adolphe-Eugène Disdéri|Charles-Louis-Napoleon Bonaparte|Empress Eugénie de Montijo,"French, Paris 1819–1889 Paris|French, Paris 1808–1873 Chislehurst, Kent|French (born Spain), Granada 1826–1920 Madrid",,"Disdéri, André-Adolphe-Eugène|Bonaparte, Charles-Louis-Napoleon|Montijo, Eugénie de, Empress","French|French|French, born Spain",1819 |1808 |1826,1889 |1873 |1920,ca. 1865,1865,1865,Albumen silver print,Image: 9.5 x 6 cm (3 3/4 x 2 3/8 in.),"Gift of A. Hyatt Mayor, 1967",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288741,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.228,false,true,268021,Photographs,Photograph,"Ruins in Carey Street, Richmond",,,,,,Artist|Artist|Former Attribution,Formerly attributed to,Thomas C. Roche|Alexander Gardner|Mathew B. Brady,"American, 1826–1895|American, Glasgow, Scotland 1821–1882 Washington, D.C.|American, born Ireland, 1823?–1896 New York",,"Roche, Thomas C.|Gardner, Alexander|Brady, Mathew B.","American|American, Scottish",1826 |1821 |1823,1895 |1882 |1896,1865,1865,1865,Albumen silver print from glass negative,Image: 18.6 x 23.8 cm (7 5/16 x 9 3/8 in.),"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268021,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.396,false,true,268207,Photographs,Photograph,"Falmouth, Virginia. Abandoned Camp",,,,,,Artist|Artist|Former Attribution,Formerly attributed to,Andrew Joseph Russell|Alexander Gardner|Mathew B. Brady,"American, 1830–1902|American, Glasgow, Scotland 1821–1882 Washington, D.C.|American, born Ireland, 1823?–1896 New York",,"Russell, Andrew Joseph|Gardner, Alexander|Brady, Mathew B.","American|American, Scottish",1830 |1821 |1823,1902 |1882 |1896,1862,1862,1862,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268207,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.167,false,true,267954,Photographs,Photograph,Bull Run,,,,,,Artist|Artist|Artist|Former Attribution,Formerly attributed to,George N. Barnard|Timothy H. O'Sullivan|Andrew Joseph Russell|Mathew B. Brady,"American, 1819–1902|American, born Ireland, 1840–1882|American, 1830–1902|American, born Ireland, 1823?–1896 New York",,"Barnard, George N.|O'Sullivan, Timothy H.|Russell, Andrew Joseph|Brady, Mathew B.","American|American, born Ireland|American",1819 |1840 |1830 |1823,1902 |1882 |1902 |1896,1861–62,1861,1862,Albumen silver print from glass negative,10.2 x 20 cm (4 x 7 7/8 in. ),"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267954,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.133,false,true,267917,Photographs,Photograph,Bull Run,,,,,,Artist|Artist|Artist|Former Attribution,Formerly attributed to,Andrew Joseph Russell|George N. Barnard|Timothy H. O'Sullivan|Mathew B. Brady,"American, 1830–1902|American, 1819–1902|American, born Ireland, 1840–1882|American, born Ireland, 1823?–1896 New York",,"Russell, Andrew Joseph|Barnard, George N.|O'Sullivan, Timothy H.|Brady, Mathew B.","American|American|American, born Ireland",1830 |1819 |1840 |1823,1902 |1902 |1882 |1896,1861–62,1861,1862,Albumen silver print from glass negative,16.1 x 20.7 cm (6 5/16 x 8 1/8 in. ),"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267917,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.109,false,true,267890,Photographs,Photograph,Bull Run,,,,,,Artist|Artist|Artist|Former Attribution,,Andrew Joseph Russell|Timothy H. O'Sullivan|George N. Barnard|Mathew B. Brady,"American, 1830–1902|American, born Ireland, 1840–1882|American, 1819–1902|American, born Ireland, 1823?–1896 New York",,"Russell, Andrew Joseph|O'Sullivan, Timothy H.|Barnard, George N.|Brady, Mathew B.","American|American, born Ireland|American",1830 |1840 |1819 |1823,1902 |1882 |1902 |1896,1861–62,1861,1862,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267890,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.20,false,true,267991,Photographs,Photograph,Wharf opposite Richmond,,,,,,Artist|Artist|Former Attribution,Formerly attributed to,John Reekie|Alexander Gardner|Mathew B. Brady,"American, active 1860s|American, Glasgow, Scotland 1821–1882 Washington, D.C.|American, born Ireland, 1823?–1896 New York",,"Reekie, John|Gardner, Alexander|Brady, Mathew B.","American|American, Scottish",1860 |1821 |1823,1869 |1882 |1896,ca. 1865,1863,1867,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267991,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1982.1182.15–.57,false,true,288104,Photographs,Stereographs,[Group of 42 Stereograph Views of Alaska Including the Gold Rush],,,,,,Publisher|Artist|Publisher|Artist|Artist|Publisher|Artist|Publisher|Publisher|Publisher|Publisher,,"George W. Griffith|Benneville Lloyd Singley|Berry, Kelley & Chadwick|Griffith & Griffith, American|Unknown|T. W. Ingersoll|William H. Rau|Universal View Co.|C. H. Graves|Universal Photo Art Co.|Keystone View Company","American|American, Union Township, Pennsylvania 1864–1938 Meadville, Pennsylvania|American|American, 1855–1920|American|American",,"Griffith, George W.|Singley, Benneville Lloyd|Berry, Kelley & Chadwick|Griffith & Griffith|Unknown|Ingersoll, T. W.|Rau, William H.|Universal View Co.|Graves, C. H.|Universal Photo Art Co.|Keystone View Company",American|American|American|American|American|American|American,1864 |1855,1938 |1920,1850s–1910s,1850,1919,Albumen silver prints,Mounts approximately: 8.9 x 17.8 cm (3 1/2 x 7 in.),"Gift of Weston J. Naef, in memory of Kathleen W. Naef and Weston J. Naef Sr., 1982",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288104,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1982.1182.1285–.1534,false,true,296355,Photographs,Stereographs,"[Group of 250 Stereograph Views From the London Stereoscopic Company, 1860-1870, Many Hand-Colored to Illustrate Books]",,,,,,Publisher|Publisher|Publisher|Artist|Artist|Publisher|Artist|Publisher|Publisher|Publisher|Publisher|Artist|Artist|Publisher|Publisher|Publisher|Artist|Artist|Publisher|Person in Photograph|Publisher|Publisher|Publisher|Printer|Publisher|Publisher|Publisher|Publisher|Author|Artist|Person in Photograph|Publisher|Publisher,Collaborated with,"New York Stereoscopic Company|L. H. Stockwell|London Stereoscopic Company|Unknown|Unknown|G. W. Thorne|Unknown|M. W. S. Jackson|J. L. Bates|Mrs. Charles Lawrence|F. W. & R. King|Benneville Lloyd Singley|Sir David Brewster|Littleton View Company|Underwood & Underwood|William Hall & Son|C. E. Goodman|J. Elliott|London Stereoscopic and Photographic Company|Henry IV, the Pius, Duke of Saxony|G. Hawgood|J. Eastlake|E. H. Chamberlain|Nachmann|E. Vimard|Gebhardt, Rottmann, & Co.|B. B. Savary|L. J. Cist|William Hepworth Dixon|M. Laroche|Charles-Louis-Napoleon Bonaparte|Keystone View Company|McAllister & Brother","American|American|British|British|American|American|American|American|British|American, Union Township, Pennsylvania 1864–1938 Meadville, Pennsylvania|British, Jedburgh, Scotland 1781–1868 Melrose|American|American|New York|British|German, 1473–1541|American|French|French|British|American|British, 1821–1879|French, Paris 1808–1873 Chislehurst, Kent|American, active 1860s–1870s",,"New York Stereoscopic Company|Stockwell, L. H.|London Stereoscopic Company|Unknown|Unknown|Thorne, G. W.|Unknown|Jackson, M. W. S.|Bates, J. L.|Lawrence, Charles Mrs.|F. W. & R. King|Singley, Benneville Lloyd|Brewster, David, Sir|Littleton View Company|Underwood & Underwood|William Hall & Son|Goodman, C. E.|Elliott, J.|London Stereoscopic and Photographic Company|Duke of Saxony Henry IV, the Pius,|Hawgood, G.|Eastlake, J.|Chamberlain, E. H.|Nachmann|Vimard, E.|Gebhardt, Rottmann, & Co.|Savary, B. B.|Cist, L. J.|Dixon, William Hepworth|Laroche, M.|Bonaparte, Charles-Louis-Napoleon|Keystone View Company|McAllister & Brother","American|American|British|American|American|American|British|American|British, Scottish|American|American|American|British|German|American|French|French|British|American|British|French|American",1864 |1781 |1473 |1821 |1808 |1850,1938 |1868 |1541 |1879 |1873 |1880,1860–70,1860,1870,Albumen silver prints,Mounts approximately: 8.6 x 17.5 cm (3 3/8 x 6 7/8 in.),"Gift of Weston J. Naef, in memory of Kathleen W. Naef and Weston J. Naef Sr., 1982",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/296355,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1982.1182.1242–.1283,false,true,288310,Photographs,Stereographs,"[Group of 42 Stereograph Views From the London Stereoscopic Company, 1860-1870, Many Hand-Colored to Illustrate Books]",,,,,,Publisher|Artist|Artist|Publisher|Artist|Publisher|Publisher|Publisher|Publisher|Artist|Artist|Publisher|Publisher|Publisher|Artist|Artist|Publisher|Person in Photograph|Publisher|Publisher|Publisher|Printer|Publisher|Publisher|Publisher|Publisher|Author|Artist|Publisher|Publisher|Person in Photograph|Publisher|Publisher,Collaborated with,"London Stereoscopic Company|Unknown|Unknown|G. W. Thorne|Unknown|M. W. S. Jackson|J. L. Bates|Mrs. Charles Lawrence|F. W. & R. King|Benneville Lloyd Singley|Sir David Brewster|Littleton View Company|Underwood & Underwood|William Hall & Son|C. E. Goodman|J. Elliott|London Stereoscopic and Photographic Company|Henry IV, the Pius, Duke of Saxony|G. Hawgood|J. Eastlake|E. H. Chamberlain|Nachmann|E. Vimard|Gebhardt, Rottmann, & Co.|B. B. Savary|L. J. Cist|William Hepworth Dixon|M. Laroche|New York Stereoscopic Company|L. H. Stockwell|Charles-Louis-Napoleon Bonaparte|Keystone View Company|McAllister & Brother","British|British|American|American|American|American|British|American, Union Township, Pennsylvania 1864–1938 Meadville, Pennsylvania|British, Jedburgh, Scotland 1781–1868 Melrose|American|American|New York|British|German, 1473–1541|American|French|French|British|American|British, 1821–1879|American|American|French, Paris 1808–1873 Chislehurst, Kent|American, active 1860s–1870s",,"London Stereoscopic Company|Unknown|Unknown|Thorne, G. W.|Unknown|Jackson, M. W. S.|Bates, J. L.|Lawrence, Charles Mrs.|F. W. & R. King|Singley, Benneville Lloyd|Brewster, David, Sir|Littleton View Company|Underwood & Underwood|William Hall & Son|Goodman, C. E.|Elliott, J.|London Stereoscopic and Photographic Company|Duke of Saxony Henry IV, the Pius,|Hawgood, G.|Eastlake, J.|Chamberlain, E. H.|Nachmann|Vimard, E.|Gebhardt, Rottmann, & Co.|Savary, B. B.|Cist, L. J.|Dixon, William Hepworth|Laroche, M.|New York Stereoscopic Company|Stockwell, L. H.|Bonaparte, Charles-Louis-Napoleon|Keystone View Company|McAllister & Brother","British|American|American|American|British|American|British, Scottish|American|American|American|British|German|American|French|French|British|American|British|American|American|French|American",1864 |1781 |1473 |1821 |1808 |1850,1938 |1868 |1541 |1879 |1873 |1880,1860–70,1860,1870,Albumen silver prints,Mounts approximately: 8.6 x 17.5 cm (3 3/8 x 6 7/8 in.),"Gift of Weston J. Naef, in memory of Kathleen W. Naef and Weston J. Naef Sr., 1982",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288310,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.1174.4,false,true,302667,Photographs,Carte-de-visite,Hamilton's Floating Battery Moored at the End of Sullivan's Island the Night Before They Opened Fire upon Fort Sumter,,,,,,Publisher|Artist|Artist,Attributed to|Attributed to,Edward Anthony|Alma A. Pelot|Jesse H. Bolles,"American, 1818–1888|American, active Charleston, South Carolina, 1850s–1860s|American, active Charleston, South Corlina, 1850s–1860s",,"Anthony, Edward|Pelot, Alma A.|Bolles, Jesse H.",American|American|American,1818 |1850 |1850,1888 |1869 |1869,April 1861,1861,1861,Albumen silver print from glass negative,Image: 1 15/16 × 3 1/8 in. (5 × 7.9 cm) Mount: 3 3/8 in. × 4 3/4 in. (8.5 × 12 cm),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/302667,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.1174.7,false,true,302670,Photographs,Carte-de-visite,"The Evacuation of Fort Sumter, April 1861",,,,,,Publisher|Artist|Artist,Attributed to|Attributed to,Edward Anthony|Alma A. Pelot|Jesse H. Bolles,"American, 1818–1888|American, active Charleston, South Carolina, 1850s–1860s|American, active Charleston, South Corlina, 1850s–1860s",,"Anthony, Edward|Pelot, Alma A.|Bolles, Jesse H.",American|American|American,1818 |1850 |1850,1888 |1869 |1869,April 1861,1861,1861,Albumen silver print from glass negative,Image: 1 15/16 × 2 11/16 in. (5 × 6.9 cm) Mount: 3 3/8 in. × 4 3/4 in. (8.5 × 12 cm),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/302670,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.1174.8,false,true,302671,Photographs,Carte-de-visite,"The Evacuation of Fort Sumter, April 1861",,,,,,Publisher|Artist|Artist,Attributed to|Attrobuted to,Edward Anthony|Alma A. Pelot|Jesse H. Bolles,"American, 1818–1888|American, active Charleston, South Carolina, 1850s–1860s|American, active Charleston, South Corlina, 1850s–1860s",,"Anthony, Edward|Pelot, Alma A.|Bolles, Jesse H.",American|American|American,1818 |1850 |1850,1888 |1869 |1869,April 1861,1861,1861,Albumen silver print from glass negative,Image: 1 15/16 × 3 1/8 in. (5 × 7.9 cm) Mount: 3 3/8 in. × 4 3/4 in. (8.5 × 12 cm),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/302671,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.1174.9,false,true,302672,Photographs,Carte-de-visite,"The Evacuation of Fort Sumter, April 1861",,,,,,Publisher|Artist|Artist,Attributed to|Attrobuted to,Edward Anthony|Alma A. Pelot|Jesse H. Bolles,"American, 1818–1888|American, active Charleston, South Carolina, 1850s–1860s|American, active Charleston, South Corlina, 1850s–1860s",,"Anthony, Edward|Pelot, Alma A.|Bolles, Jesse H.",American|American|American,1818 |1850 |1850,1888 |1869 |1869,April 1861,1861,1861,Albumen silver print from glass negative,Image: 1 15/16 × 3 1/8 in. (5 × 7.9 cm) Mount: 3 3/8 in. × 4 3/4 in. (8.5 × 12 cm),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/302672,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.1174.11,false,true,302674,Photographs,Carte-de-visite,"The Evacuation of Fort Sumter, April 1861",,,,,,Publisher|Artist|Artist,Attributed to|Attributed to,Edward Anthony|Alma A. Pelot|Jesse H. Bolles,"American, 1818–1888|American, active Charleston, South Carolina, 1850s–1860s|American, active Charleston, South Corlina, 1850s–1860s",,"Anthony, Edward|Pelot, Alma A.|Bolles, Jesse H.",American|American|American,1818 |1850 |1850,1888 |1869 |1869,April 1861,1861,1861,Albumen silver print from glass negative,Image: 1 15/16 × 3 1/8 in. (5 × 7.9 cm) Mount: 3 3/8 in. × 4 3/4 in. (8.5 × 12 cm),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/302674,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.1174.13,false,true,302676,Photographs,Carte-de-visite,"The Evacuation of Fort Sumter, April 1861",,,,,,Publisher|Artist|Artist,Attributed to|Attributed to,Edward Anthony|Alma A. Pelot|Jesse H. Bolles,"American, 1818–1888|American, active Charleston, South Carolina, 1850s–1860s|American, active Charleston, South Corlina, 1850s–1860s",,"Anthony, Edward|Pelot, Alma A.|Bolles, Jesse H.",American|American|American,1818 |1850 |1850,1888 |1869 |1869,April 1861,1861,1861,Albumen silver print from glass negative,Image: 1 15/16 × 3 1/8 in. (5 × 7.9 cm) Mount: 3 3/8 in. × 4 3/4 in. (8.5 × 12 cm),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/302676,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.151,false,true,267937,Photographs,Photograph,Petersburg,,,,,,Artist|Artist|Former Attribution,Formerly attributed to,Alexander Gardner|Timothy H. O'Sullivan|Mathew B. Brady,"American, Glasgow, Scotland 1821–1882 Washington, D.C.|American, born Ireland, 1840–1882|American, born Ireland, 1823?–1896 New York",,"Gardner, Alexander|O'Sullivan, Timothy H.|Brady, Mathew B.","American, Scottish|American, born Ireland",1821 |1840 |1823,1882 |1882 |1896,1864,1864,1864,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267937,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.150,false,true,267936,Photographs,Photograph,Petersburg,,,,,,Artist|Artist|Former Attribution,Formerly attributed to,Timothy H. O'Sullivan|Alexander Gardner|Mathew B. Brady,"American, born Ireland, 1840–1882|American, Glasgow, Scotland 1821–1882 Washington, D.C.|American, born Ireland, 1823?–1896 New York",,"O'Sullivan, Timothy H.|Gardner, Alexander|Brady, Mathew B.","American, born Ireland|American, Scottish",1840 |1821 |1823,1882 |1882 |1896,1864,1864,1864,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267936,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.152,false,true,267938,Photographs,Photograph,Petersburg,,,,,,Artist|Artist|Former Attribution,Formerly attributed to,Timothy H. O'Sullivan|Alexander Gardner|Mathew B. Brady,"American, born Ireland, 1840–1882|American, Glasgow, Scotland 1821–1882 Washington, D.C.|American, born Ireland, 1823?–1896 New York",,"O'Sullivan, Timothy H.|Gardner, Alexander|Brady, Mathew B.","American, born Ireland|American, Scottish",1840 |1821 |1823,1882 |1882 |1896,1864,1864,1864,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267938,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.153,false,true,267939,Photographs,Photograph,Petersburg,,,,,,Artist|Artist|Former Attribution,Formerly attributed to,Timothy H. O'Sullivan|Alexander Gardner|Mathew B. Brady,"American, born Ireland, 1840–1882|American, Glasgow, Scotland 1821–1882 Washington, D.C.|American, born Ireland, 1823?–1896 New York",,"O'Sullivan, Timothy H.|Gardner, Alexander|Brady, Mathew B.","American, born Ireland|American, Scottish",1840 |1821 |1823,1882 |1882 |1896,1864,1864,1864,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267939,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1982.1182.946–.949,false,true,288283,Photographs,Stereographs,"[Group of 4 Stereograph Views of the Coronation of Edward VII, London, England]",,,,,,Publisher|Artist|Publisher|Publisher|Photography Studio|Artist|Person in Photograph|Publisher,,"H. C. White Company|R. Y. Young|American Stereoscopic Company|Underwood & Underwood|Sun Sculpture Works and Studios|Benneville Lloyd Singley|Edward VII, King of Great Britain and Northern Ireland|Keystone View Company","American|American|American|American|American, Union Township, Pennsylvania 1864–1938 Meadville, Pennsylvania|British, London 1841–1910 London",,"H. C. White Company|Young, R. Y.|American Stereoscopic Company|Underwood & Underwood|Sun Sculpture Works and Studios|Singley, Benneville Lloyd|Edward VII, King of Great Britain and Northern Ireland|Keystone View Company",American|American|American|American|American|British,1864 |1841,1938 |1910,1850s–1910s,1850,1919,Albumen silver prints,Mounts: 8.9 x 17.8 cm (3 1/2 x 7 in.),"Gift of Weston J. Naef, in memory of Kathleen W. Naef and Weston J. Naef Sr., 1982",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288283,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1982.1182.1003–.1030,false,true,288296,Photographs,Stereographs,[Group of 28 Stereograph Views of Children],,,,,,Publisher|Artist|Publisher|Publisher|Publisher|Artist|Publisher|Publisher|Publisher|Publisher|Publisher|Publisher|Publisher|Artist|Artist,,Keystone View Company|Benneville Lloyd Singley|Merrimac Stereoscopic Company|A. Fuller|The Globe Photo Art Company|C. W. Woodward|Life Groups|Popular Series|International Stereoscopic View Company|F. G. Weller|Underwood & Underwood|Littleton View Company|Canvassers|Unknown|Unknown,"American, Union Township, Pennsylvania 1864–1938 Meadville, Pennsylvania|American|American|American|American|American|American|American|American",,"Keystone View Company|Singley, Benneville Lloyd|Merrimac Stereoscopic Company|Fuller, A.|Globe Photo Art Company|Woodward, C. W.|Life Groups|Popular Series|International Stereoscopic View Company|Weller., F. G.|Underwood & Underwood|Littleton View Company|Canvassers|Unknown|Unknown",American|American|American|American|American|American|American|American,1864,1938,1850s–1910s,1850,1919,Albumen silver prints,Mounts approximately: 8.9 x 17.8 cm (3 1/2 x 7 in.),"Gift of Weston J. Naef, in memory of Kathleen W. Naef and Weston J. Naef Sr., 1982",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288296,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1982.1182.1188–.1224,false,true,288305,Photographs,Stereographs,"[Group of 37 Stereograph Views of the Garden of the Gods and Other Colorado Scenery, United States of America]",,,,,,Publisher|Publisher|Photography Studio|Publisher|Publisher|Artist|Artist|Artist|Publisher|Publisher|Publisher|Artist|Publisher,,"H. C. White Company|Underwood & Underwood|Sun Sculpture Works and Studios|Berry, Kelley & Chadwick|American Colotype Company|J. F. Jarvis|Strohmeyer & Wyman|Benneville Lloyd Singley|Griffith & Griffith, American|Ingersoll View Company|Canvassers|Unknown|Keystone View Company","American|American|American|American|American|American|American, Union Township, Pennsylvania 1864–1938 Meadville, Pennsylvania|American|American",,"H. C. White Company|Underwood & Underwood|Sun Sculpture Works and Studios|Berry, Kelley & Chadwick|American Colotype Company|Jarvis, J. F.|Strohmeyer & Wyman|Singley, Benneville Lloyd|Griffith & Griffith|Ingersoll View Company|Canvassers|Unknown|Keystone View Company",American|American|American|American|American|American|American|American|American,1864,1938,1850s–1910s,1850,1919,Albumen silver prints,,"Gift of Weston J. Naef, in memory of Kathleen W. Naef and Weston J. Naef Sr., 1982",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288305,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1982.1182.465–.490,false,true,288147,Photographs,Stereographs,"[Group of 26 Stereograph Views of San Francisco, California]",,,,,,Publisher|Publisher|Publisher|Publisher|Publisher|Artist|Publisher|Publisher|Photography Studio|Publisher|Artist|Artist|Publisher,,Continent Stereoscopic Company|American Scenery|Popular Series|American Series|American Views|Benneville Lloyd Singley|Standard Series|Underwood & Underwood|Sun Sculpture Works and Studios|Stereoscopic Views|Unknown|Unknown|Keystone View Company,"New York|American|American|American|American, Union Township, Pennsylvania 1864–1938 Meadville, Pennsylvania|American|American|American|American",,"Continent Stereoscopic Company|American Scenery|Popular Series|American Series|American Views|Singley, Benneville Lloyd|Standard Series|Underwood & Underwood|Sun Sculpture Works and Studios|Stereoscopic Views|Unknown|Unknown|Keystone View Company",New York|American|American|American|American|American|American|American,1864,1938,1850s–1910s,1850,1919,Albumen silver prints,Mounts approximately: 8.9 x 17.6 cm (3 1/2 x 6 15/16 in.) to 10.5 x 17.8 cm (4 1/8 x 7 in.),"Gift of Weston J. Naef, in memory of Kathleen W. Naef and Weston J. Naef Sr., 1982",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288147,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.1201,false,true,285644,Photographs,Photograph,"A Harvest of Death, Gettysburg, Pennsylvania",,,,,,Artist|Printer|Publisher,,Timothy H. O'Sullivan|Alexander Gardner|Alexander Gardner,"American, born Ireland, 1840–1882|American, Glasgow, Scotland 1821–1882 Washington, D.C.|American, Glasgow, Scotland 1821–1882 Washington, D.C.",,"O'Sullivan, Timothy H.|Gardner, Alexander|Gardner, Alexander","American, born Ireland|American, Scottish|American, Scottish",1840 |1821 |1821,1882 |1882 |1882,July 1863,1863,1863,Albumen silver print from glass negative,Image: 17 13/16 × 22 1/2 in. (45.2 × 57.2 cm) Mount: 11 15/16 × 15 5/8 in. (30.4 × 39.7 cm),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285644,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -46.1.21,false,true,268962,Photographs,Photograph,"William Etty, R.A.",,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"David Hunter McAlpin Fund, 1946",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268962,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -43.10.37,false,true,268791,Photographs,Photograph,Sir William Allan,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Gift of Mrs. Pirie MacDonald and Mr. and Mrs. Everett Tutchings, 1943",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268791,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -43.10.38,false,true,268792,Photographs,Photograph,"Ogilvie Fairly, Capt. Hamilton, and Gilmore",,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Gift of Mrs. Pirie MacDonald and Mr. and Mrs. Everett Tutchings, 1943",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268792,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -43.10.40,false,true,268795,Photographs,Photograph,Mrs. Rigby,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Gift of Mrs. Pirie MacDonald and Mr. and Mrs. Everett Tutchings, 1943",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268795,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -43.10.41,false,true,268796,Photographs,Photograph,Mr. and Mrs. John Thomson,,,,,,Artist|Printer|Artist|Photography Studio,,David Octavius Hill|Ingals|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Ingals|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Gift of Mrs. Pirie MacDonald and Mr. and Mrs. Everett Tutchings, 1943",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268796,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -43.10.42,false,true,268797,Photographs,Photograph,Henning with Parthenon Frieze,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Gift of Mrs. Pirie MacDonald and Mr. and Mrs. Everett Tutchings, 1943",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268797,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -43.10.43,false,true,268798,Photographs,Photograph,"[Man, Full-length]",,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Gift of Mrs. Pirie MacDonald and Mr. and Mrs. Everett Tutchings, 1943",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268798,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -43.10.44,false,true,268799,Photographs,Photograph,Newhaven Fishwives,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Gift of Mrs. Pirie MacDonald and Mr. and Mrs. Everett Tutchings, 1943",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268799,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -43.10.46,false,true,268801,Photographs,Photograph,Dr. Jabez Bunting,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Gift of Mrs. Pirie MacDonald and Mr. and Mrs. Everett Tutchings, 1943",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268801,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -43.10.47,false,true,268802,Photographs,Photograph,Hugh Miller,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Gift of Mrs. Pirie MacDonald and Mr. and Mrs. Everett Tutchings, 1943",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268802,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -43.10.49,false,true,268803,Photographs,Photograph,Presbytery of Dumbarton,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Gift of Mrs. Pirie MacDonald and Mr. and Mrs. Everett Tutchings, 1943",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268803,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -43.10.50,false,true,268805,Photographs,Photograph,Rev. Dr. Thomas Chalmers (?),,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Gift of Mrs. Pirie MacDonald and Mr. and Mrs. Everett Tutchings, 1943",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268805,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -43.10.51,false,true,268806,Photographs,Photograph,[Man],,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Gift of Mrs. Pirie MacDonald and Mr. and Mrs. Everett Tutchings, 1943",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268806,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -43.10.53,false,true,268808,Photographs,Photograph,[Man],,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Gift of Mrs. Pirie MacDonald and Mr. and Mrs. Everett Tutchings, 1943",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268808,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -43.10.54,false,true,268809,Photographs,Photograph,"Prof. Fraser, Rev. Welsh, Rev. Hamilton, and Three Other Men",,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Gift of Mrs. Pirie MacDonald and Mr. and Mrs. Everett Tutchings, 1943",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268809,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -46.1.141,false,true,268914,Photographs,Photograph,Kenneth Macleay,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"David Hunter McAlpin Fund, 1946",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268914,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -46.1.142,false,true,268915,Photographs,Photograph,Sobieski Stuart,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"David Hunter McAlpin Fund, 1946",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268915,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -46.1.143,false,true,268916,Photographs,Photograph,David Roberts,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"David Hunter McAlpin Fund, 1946",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268916,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -46.1.153,false,true,268927,Photographs,Photograph,"Kenneth MaCleay, R.S.A.",,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"David Hunter McAlpin Fund, 1946",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268927,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -46.1.162,false,true,268937,Photographs,Photograph,James Glencairn Burns,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"David Hunter McAlpin Fund, 1946",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268937,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -46.1.163,false,true,268938,Photographs,Photograph,Finlay - The Deerstalker,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"David Hunter McAlpin Fund, 1946",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268938,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -46.1.165,false,true,268940,Photographs,Photograph,John Ban MacKenzie,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"David Hunter McAlpin Fund, 1946",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268940,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -46.1.166,false,true,268941,Photographs,Photograph,Alexander Thompson,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"David Hunter McAlpin Fund, 1946",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268941,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -46.1.169,false,true,268944,Photographs,Photograph,Sir John McNeill,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"David Hunter McAlpin Fund, 1946",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268944,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -46.1.171,false,true,268947,Photographs,Photograph,[Man],,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"David Hunter McAlpin Fund, 1946",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268947,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -46.1.172,false,true,268948,Photographs,Photograph,George Moon,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"David Hunter McAlpin Fund, 1946",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268948,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -46.1.173,false,true,268949,Photographs,Photograph,Sobieski Stuart,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"David Hunter McAlpin Fund, 1946",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268949,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -46.1.174,false,true,268950,Photographs,Photograph,"Dr. Inglis, Halifax",,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"David Hunter McAlpin Fund, 1946",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268950,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.1.1,false,true,268366,Photographs,Photograph,St. Andrews,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268366,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.1.2,false,true,268393,Photographs,Photograph,St. Andrews,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268393,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.1.3,false,true,268404,Photographs,Photograph,St. Andrews,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268404,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.1.4,false,true,268415,Photographs,Photograph,St. Andrews,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268415,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.1.5,false,true,268426,Photographs,Photograph,St. Andrews,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268426,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.1.6,false,true,268437,Photographs,Photograph,St. Andrews,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268437,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.1.7,false,true,268448,Photographs,Photograph,St. Andrews,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268448,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.1.8,false,true,268459,Photographs,Photograph,St. Andrews,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268459,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.1.9,false,true,268470,Photographs,Photograph,St. Andrews,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268470,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.2.1,false,true,268482,Photographs,Photograph,Rev. Dr. Thomas Chalmers and Thomas Chalmers Hanna,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268482,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.2.2,false,true,268525,Photographs,Photograph,"William Borthwick Johnstone, William Leighton Leitch and David Scott as ""The Monks of Kennaquhair"" from Sir Walter Scott's ""The Abbott""",,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268525,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.2.3,false,true,268536,Photographs,Photograph,Dunlop Esq. of Craigton,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268536,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.2.4,false,true,268547,Photographs,Photograph,"William Etty, R.A.",,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268547,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.2.5,false,true,268558,Photographs,Photograph,William Etty,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268558,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.2.6,false,true,268569,Photographs,Photograph,"Sir William Allan, P.R.S.A.",,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268569,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.2.7,false,true,268580,Photographs,Photograph,"Thomas Duncan, R.S.A.",,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268580,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.2.8,false,true,268591,Photographs,Photograph,Thomas Duncan,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268591,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.2.9,false,true,268602,Photographs,Photograph,Thomas Duncan and His Brother,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268602,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.1.10,false,true,268367,Photographs,Photograph,St. Andrews. The Harbor,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268367,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.1.11,false,true,268378,Photographs,Photograph,St. Andrews. College Church of St. Salvator,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268378,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.1.12,false,true,268385,Photographs,Photograph,St. Andrews. The College Church of St. Salvator,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268385,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.1.13,false,true,268386,Photographs,Photograph,St. Andrews. The Pends,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268386,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.1.14,false,true,268387,Photographs,Photograph,St. Andrews. The Pends,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268387,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.1.15,false,true,268388,Photographs,Photograph,St. Andrews. The West Port,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268388,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.1.16,false,true,268389,Photographs,Photograph,St. Andrews. The Abbey Wall,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268389,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.1.17,false,true,268390,Photographs,Photograph,St. Andrews. The Fore Tower of the Castle,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268390,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.1.18,false,true,268391,Photographs,Photograph,St. Andrews. The Fore Tower of the Castle,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268391,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.1.19,false,true,268392,Photographs,Photograph,St. Andrews. The Fore Tower of the Castle,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268392,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.1.20,false,true,268394,Photographs,Photograph,St. Andrews. The Spindle Rock,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268394,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.1.21,false,true,268395,Photographs,Photograph,St. Andrews. College Church of St. Salvator,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268395,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.1.22,false,true,268396,Photographs,Photograph,St. Andrews. Blackfriars' Chapel,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268396,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.1.23,false,true,268397,Photographs,Photograph,St. Andrews Cathedral,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268397,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.1.24,false,true,268398,Photographs,Photograph,St. Andrews (?). Ships in the Harbor,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268398,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.1.25,false,true,268399,Photographs,Photograph,St. Andrews. Madras College,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268399,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.1.26,false,true,268400,Photographs,Photograph,Edinburgh. The High Street with John Knox's House,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268400,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.1.27,false,true,268401,Photographs,Photograph,Edinburgh. Greyfriars' Churchyard,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268401,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.1.28,false,true,268402,Photographs,Photograph,Edinburgh. Greyfriars' Churchyard,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268402,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.1.29,false,true,268403,Photographs,Photograph,Edinburgh. The Orphan Hospital,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268403,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.1.30,false,true,268405,Photographs,Photograph,Edinburgh. The Scott Monument,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268405,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.1.31,false,true,268406,Photographs,Photograph,Edinburgh. The Scott Monument,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268406,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.1.32,false,true,268407,Photographs,Photograph,Edinburgh. The Royal High School,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268407,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.1.33,false,true,268408,Photographs,Photograph,"[Old Royal High School, Calton Hill, Edinburgh]",,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268408,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.1.34,false,true,268409,Photographs,Photograph,Edinburgh. Greyfriars' Churchyard,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268409,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.1.35,false,true,268410,Photographs,Photograph,Edinburgh. Greyfriars' Churchyard,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268410,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.1.36,false,true,268411,Photographs,Photograph,Edinburgh. Greyfriars' Churchyard,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268411,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.1.37,false,true,268412,Photographs,Photograph,Edinburgh. Greyfriars' Churchyard,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268412,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.1.38,false,true,268413,Photographs,Photograph,Edinburgh. Greyfriars' Churchyard,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268413,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.1.39,false,true,268414,Photographs,Photograph,Edinburgh. Greyfriars' Churchyard,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268414,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.1.40,false,true,268416,Photographs,Photograph,Edinburgh. Greyfriar's Churchyard,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268416,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.1.41,false,true,268417,Photographs,Photograph,Edinburgh. Greyfriars' Churchyard,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268417,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.1.42,false,true,268418,Photographs,Photograph,Edinburgh. Greyfriars' Churchyard,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268418,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.1.43,false,true,268419,Photographs,Photograph,Bonaly Towers,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268419,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.1.44,false,true,268420,Photographs,Photograph,Bonaly Towers. Home of Lord Cockburn,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268420,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.1.45,false,true,268421,Photographs,Photograph,Bonaly Towers. Home of Lord Cockburn,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268421,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.1.46,false,true,268422,Photographs,Photograph,"John Henning as Edie Ochiltree from Sir Walter Scott's ""The Antiquary""",,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268422,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.1.47,false,true,268423,Photographs,Photograph,"Burnside, Fife / Island in the Almond River",,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268423,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.1.48,false,true,268424,Photographs,Photograph,Tree at Colinton,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268424,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.1.49,false,true,268425,Photographs,Photograph,Tree at Colinton,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268425,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.1.50,false,true,268427,Photographs,Photograph,Lindlithgow Castle,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268427,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.1.51,false,true,268428,Photographs,Photograph,Newhaven,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268428,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.1.52,false,true,268429,Photographs,Photograph,"St. Andrews. North Street, Fishergate",,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268429,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.1.53,false,true,268430,Photographs,Photograph,Newhaven Fishwives,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268430,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.1.54,false,true,268431,Photographs,Photograph,Newhaven Children,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268431,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.1.55,false,true,268432,Photographs,Photograph,Newhaven Family,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268432,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.1.56,false,true,268433,Photographs,Photograph,Newhaven Group,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268433,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.1.57,false,true,268434,Photographs,Photograph,Newhaven Boys,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268434,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.1.58,false,true,268435,Photographs,Photograph,Newhaven Fishwife,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268435,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.1.59,false,true,268436,Photographs,Photograph,Newhaven Fishwives,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268436,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.1.60,false,true,268438,Photographs,Photograph,Newhaven Fishwives,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268438,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.1.61,false,true,268439,Photographs,Photograph,Newhaven Fishwives,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268439,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.1.63,false,true,268441,Photographs,Photograph,Newhaven Fishwife,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268441,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.1.64,false,true,268442,Photographs,Photograph,Fisher Lassies,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268442,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.1.65,false,true,268443,Photographs,Photograph,Newhaven Fishwife,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268443,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.1.66,false,true,268444,Photographs,Photograph,Newhaven Fishwife,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268444,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.1.67,false,true,268445,Photographs,Photograph,Newhaven Group,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268445,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.1.68,false,true,268446,Photographs,Photograph,Newhaven Fishwives,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268446,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.1.69,false,true,268447,Photographs,Photograph,Newhaven Fishwife,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268447,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.1.70,false,true,268449,Photographs,Photograph,Newhaven Fisherman with Two Boys,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268449,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.1.71,false,true,268450,Photographs,Photograph,Newhaven Fisherman,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268450,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.1.72,false,true,268451,Photographs,Photograph,Newhaven Fisherman,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268451,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.1.73,false,true,268452,Photographs,Photograph,Newhaven Boy,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268452,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.1.74,false,true,268453,Photographs,Photograph,Newhaven Group,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268453,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.1.76,false,true,268455,Photographs,Photograph,Newhaven Fisherman,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268455,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.1.77,false,true,268456,Photographs,Photograph,Newhaven Fishermen,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268456,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.1.78,false,true,268457,Photographs,Photograph,Newhaven Fishermen,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268457,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.1.79,false,true,268458,Photographs,Photograph,"The Porthole / Sergeant and Private of the 42nd Gordon Highlanders, Edinburgh Castle",,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268458,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.1.80,false,true,268460,Photographs,Photograph,"The 42nd Gordon Highlanders, Edinburgh Castle",,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268460,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.1.81,false,true,268461,Photographs,Photograph,"The 42nd Gordon Highlanders, Edinburgh Castle",,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268461,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.1.82,false,true,268462,Photographs,Photograph,Lane and Lewis / Lane and Redding,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268462,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.1.83,false,true,268463,Photographs,Photograph,Lane,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268463,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.1.84,false,true,268464,Photographs,Photograph,Rev. Peter Jones,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268464,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.1.85,false,true,268465,Photographs,Photograph,Miss Elizabeth Logan,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268465,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.1.86,false,true,268466,Photographs,Photograph,Jimmy Miller,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268466,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.1.87,false,true,268467,Photographs,Photograph,Jimmy Miller,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268467,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.1.88,false,true,268468,Photographs,Photograph,Jimmy Miller,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268468,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.1.90,false,true,268471,Photographs,Photograph,"John Henning as Edie Ochiltree from Sir Walter Scott's ""The Antiquary""",,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,Image: 20.6 x 15.8 cm (8 1/8 x 6 1/4 in.),"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268471,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.1.91,false,true,268472,Photographs,Photograph,Patrick Byrne,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268472,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.1.92,false,true,268473,Photographs,Photograph,Patrick Byrne,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268473,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.1.93,false,true,268474,Photographs,Photograph,Patrick Byrne,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268474,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.1.94,false,true,268475,Photographs,Photograph,The Misses McCandlish,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268475,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.1.95,false,true,268476,Photographs,Photograph,The Misses McCandlish,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268476,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.1.96,false,true,268477,Photographs,Photograph,Finlay Children,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268477,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.1.98,false,true,268479,Photographs,Photograph,Master Finlay,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268479,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.1.99,false,true,268480,Photographs,Photograph,"Lady, Standing",,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268480,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.2.10,false,true,268483,Photographs,Photograph,William Leighton Leitch,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268483,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.2.11,false,true,268494,Photographs,Photograph,William Leighton Leitch,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268494,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.2.12,false,true,268505,Photographs,Photograph,"Henning, Handyside Ritchie, & D.O. Hill, R.S.A.",,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268505,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.2.13,false,true,268516,Photographs,Photograph,Henning,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268516,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.2.14,false,true,268519,Photographs,Photograph,Sir John Robert Steell,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268519,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.2.15,false,true,268520,Photographs,Photograph,"H.B. Johnston, R.S.A.",,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268520,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.2.16,false,true,268521,Photographs,Photograph,D.O. Hill and W.B. Johnstone,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268521,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.2.17,false,true,268522,Photographs,Photograph,Kenneth Macleay,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268522,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.2.18,false,true,268523,Photographs,Photograph,Prof. John Wilson,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268523,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.2.19,false,true,268524,Photographs,Photograph,"""Edinburgh Ale"" James Ballentine, Dr. George Bell, D.O. Hill",,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268524,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.2.20,false,true,268526,Photographs,Photograph,Moir (?) and John Wilson,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268526,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.2.21,false,true,268527,Photographs,Photograph,John Wilson,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268527,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.2.22,false,true,268528,Photographs,Photograph,John Wilson,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268528,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.2.23,false,true,268529,Photographs,Photograph,Thomas Chalmers,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268529,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.2.24,false,true,268530,Photographs,Photograph,Thomas Chalmers,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268530,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.2.25,false,true,268531,Photographs,Photograph,Dr. Welsh (Retiring Moderator of Gel' Assembly 1843),,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268531,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.2.26,false,true,268532,Photographs,Photograph,Dr. Welsh,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268532,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.2.27,false,true,268533,Photographs,Photograph,Dr. Welsh,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268533,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.2.28,false,true,268534,Photographs,Photograph,"Cunningham, Beff, John Hamilton, Guthrie",,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268534,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.2.29,false,true,268535,Photographs,Photograph,Dr. Arnold,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268535,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.2.30,false,true,268537,Photographs,Photograph,Guthrie,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268537,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.2.31,false,true,268538,Photographs,Photograph,Lord Robertson,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268538,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.2.32,false,true,268539,Photographs,Photograph,Rev. Mr. Smith of Borgue,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268539,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.2.33,false,true,268540,Photographs,Photograph,Swinton,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268540,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.2.34,false,true,268541,Photographs,Photograph,Rev. Henshaw Jones,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268541,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.2.35,false,true,268542,Photographs,Photograph,Dr. Jabez Bunting,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268542,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.2.36,false,true,268543,Photographs,Photograph,Dr. Jabez Bunting,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268543,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.2.37,false,true,268544,Photographs,Photograph,Dr. Monro,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268544,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.2.38,false,true,268545,Photographs,Photograph,Campbell of Monzie,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268545,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.2.39,false,true,268546,Photographs,Photograph,Campbell of Monzie,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268546,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.2.40,false,true,268548,Photographs,Photograph,Rev. John Julius Wood,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268548,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.2.42,false,true,268550,Photographs,Photograph,Robert Dundas Cay,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–44,1843,1844,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268550,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.2.43,false,true,268551,Photographs,Photograph,George Gilfillan and Samuel Brown,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268551,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.2.44,false,true,268552,Photographs,Photograph,Annan Presbytery,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268552,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.2.45,false,true,268553,Photographs,Photograph,Sir James Young Simpson & Wainhouse (or Muirhouse),,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268553,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.2.46,false,true,268554,Photographs,Photograph,Sir John Jaffray and Dhanjiobai Nauroji,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268554,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.2.47,false,true,268555,Photographs,Photograph,Presbytery of Dundee,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268555,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.2.48,false,true,268556,Photographs,Photograph,"Sir David Brewster, Earle Monteith, Dr. Welsh & Two Others",,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268556,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.2.49,false,true,268557,Photographs,Photograph,"Alexander of Duntocher, McMillan of Cardross and Two Others",,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268557,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.2.50,false,true,268559,Photographs,Photograph,[Two Unidentified Men],,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268559,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.2.51,false,true,268560,Photographs,Photograph,"Symington, Paisley, and Glasgow",,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268560,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.2.52,false,true,268561,Photographs,Photograph,Rev. Miller and His Son Rev. Samuel Miller,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268561,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.2.53,false,true,268562,Photographs,Photograph,"James Gordon, Dr. Hanna, and Mr. Cowan",,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268562,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.2.55,false,true,268564,Photographs,Photograph,Rev. Dr. Keith,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268564,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.2.56,false,true,268565,Photographs,Photograph,Rev. Dr. Keith,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268565,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.2.57,false,true,268566,Photographs,Photograph,Dr. Capadore,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268566,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.2.58,false,true,268567,Photographs,Photograph,Davidson of Aberdeen,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268567,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.2.59,false,true,268568,Photographs,Photograph,Dr. Sampson of York,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268568,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.2.60,false,true,268570,Photographs,Photograph,"Rev. Stephen Hislop, Missionary",,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268570,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.2.61,false,true,268571,Photographs,Photograph,[Man Holding Umbrella],,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268571,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.2.62,false,true,268572,Photographs,Photograph,Rev. Mr. Elder of Watts,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268572,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.2.63,false,true,268573,Photographs,Photograph,"Dr. George Cook, St. Andrews",,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268573,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.2.64,false,true,268574,Photographs,Photograph,[Unidentified Man],,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268574,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.2.65,false,true,268575,Photographs,Photograph,Rev. Dr. William Hamilton Burns,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268575,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.2.66,false,true,268576,Photographs,Photograph,Dr. Foulis,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268576,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.2.67,false,true,268577,Photographs,Photograph,"""Cookie"" Miller",,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268577,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.2.68,false,true,268578,Photographs,Photograph,William Scott Moncrieff,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268578,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.2.69,false,true,268579,Photographs,Photograph,"Thomas Kitchenham Staveley, M.P. Ripon",,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268579,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.2.70,false,true,268581,Photographs,Photograph,"Rev. W. W. Duncan, Peebles (Sweet William)",,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268581,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.2.71,false,true,268582,Photographs,Photograph,John Murray (Publisher),,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268582,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.2.72,false,true,268583,Photographs,Photograph,[Man],,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268583,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.2.73,false,true,268584,Photographs,Photograph,Cookie Miller,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268584,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.2.74,false,true,268585,Photographs,Photograph,Rev. Charles John Brown,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268585,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.2.75,false,true,268586,Photographs,Photograph,[Man],,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268586,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.2.76,false,true,268587,Photographs,Photograph,Frederic Monod (Paris),,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268587,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.2.77,false,true,268588,Photographs,Photograph,"Rev. Thomas Jollie, Bowden",,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268588,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.2.78,false,true,268589,Photographs,Photograph,"Rev. Robert Aitken, Dundee",,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268589,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.2.79,false,true,268590,Photographs,Photograph,Archibald Butler of Faskally,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268590,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.2.80,false,true,268592,Photographs,Photograph,[Man],,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268592,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.2.81,false,true,268593,Photographs,Photograph,James Dymock,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268593,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.2.82,false,true,268594,Photographs,Photograph,Rev. James Scott,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268594,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.2.83,false,true,268595,Photographs,Photograph,Rev. James Scott,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268595,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.2.84,false,true,268596,Photographs,Photograph,"Thomas Bell, Leswalt",,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268596,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.2.85,false,true,268597,Photographs,Photograph,"David Maitland Makgill Crichton, Rankeillour",,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268597,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.2.86,false,true,268598,Photographs,Photograph,"Dr. Inglis, Halifax",,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268598,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.2.87,false,true,268599,Photographs,Photograph,James Aytoun,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268599,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.2.88,false,true,268600,Photographs,Photograph,"Rev. George Lewis, Dundee",,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268600,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.2.89,false,true,268601,Photographs,Photograph,[Man],,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268601,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.2.90,false,true,268603,Photographs,Photograph,[Man],,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268603,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.2.91,false,true,268604,Photographs,Photograph,Rev. Thomas Jolly of Bowden,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268604,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.2.92,false,true,268605,Photographs,Photograph,[Man],,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268605,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.2.93,false,true,268606,Photographs,Photograph,"Robert Paul, Commercial Bank",,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268606,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.2.94,false,true,268607,Photographs,Photograph,[Man],,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268607,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.2.95,false,true,268608,Photographs,Photograph,Jacob Abbott,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268608,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.2.96,false,true,268609,Photographs,Photograph,"General John Munro, Teanich",,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268609,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.2.97,false,true,268610,Photographs,Photograph,[Man],,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268610,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.2.98,false,true,268611,Photographs,Photograph,[Man],,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268611,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.2.99,false,true,268612,Photographs,Photograph,[Man],,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268612,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1997.382.14,false,true,282017,Photographs,Photograph,Rev. Dr. Thomas Chalmers,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,ca. 1843,1841,1845,Salted paper print from paper negative,16.1 x 11.9 cm (6 5/16 x 4 11/16 in.),"The Rubel Collection, Purchase, Harris Brisbane Dick Fund and Warner Communication Inc. Purchase Fund, by exchange, 1997",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/282017,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1997.382.18,false,true,282021,Photographs,Photograph,Lady Ruthven,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,ca. 1845,1843,1847,Salted paper print from paper negative,19.9 x 15 cm (7 13/16 x 5 7/8 in.),"The Rubel Collection, Purchase, Manfred Heiting and Lila Acheson Wallace Gifts, 1997",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/282021,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1997.382.19,true,true,282022,Photographs,Photograph,[Newhaven Fishwives],,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,ca. 1845,1843,1847,Salted paper print from paper negative,29.5 x 21.7 cm (11 5/8 x 8 9/16 in.),"The Rubel Collection, Purchase, Lila Acheson Wallace, Harriette and Noel Levine, and Alexandra R. Marshall Gifts, 1997",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/282022,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1997.382.25,false,true,282028,Photographs,Photograph,"[Officer of the 92nd Gordon Highlanders Reading to the Troops, Edinburgh Castle]",,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,"April 9, 1846",1846,1846,Salted paper print from paper negative,14.5 x 19.2 cm (5 11/16 x 7 9/16 in. ),"The Rubel Collection, Purchase, Lila Acheson Wallace, Ann Tenenbaum and Thomas H. Lee, and Harriette and Noel Levine Gifts, 1997",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/282028,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1997.382.29,false,true,282032,Photographs,Photograph,The Fairy Tree at Colinton,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1846,1846,1846,Salted paper print from paper negative,20.8 x 15 cm (8 3/16 x 5 7/8 in.),"The Rubel Collection, Purchase, Ann Tenenbaum and Thomas H. Lee and Lila Acheson Wallace Gifts, 1997",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/282032,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.1.100,false,true,268368,Photographs,Photograph,Mrs. Marian Murray,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268368,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.1.101,false,true,268369,Photographs,Photograph,Mrs. Jameson,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268369,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.1.102,false,true,268370,Photographs,Photograph,Lady Elizabeth Eastlake,,,,,,Artist|Artist|Photography Studio|Person in Photograph,Person in photograph,David Octavius Hill|Robert Adamson|Hill and Adamson|Lady Elizabeth Rigby Eastlake,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson|Eastlake, Elizabeth Rigby, Lady","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843 |1809,1870 |1848 |1848 |1893,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268370,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.1.103,false,true,268371,Photographs,Photograph,Lady Elizabeth Eastlake,,,,,,Artist|Artist|Photography Studio|Person in Photograph,Person in photograph,David Octavius Hill|Robert Adamson|Hill and Adamson|Lady Elizabeth Rigby Eastlake,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson|Eastlake, Elizabeth Rigby, Lady","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843 |1809,1870 |1848 |1848 |1893,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268371,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.1.104,false,true,268372,Photographs,Photograph,John Henning with Group of Ladies,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268372,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.1.105,false,true,268373,Photographs,Photograph,Miss Binney,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268373,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.1.106,false,true,268374,Photographs,Photograph,Unidentified Woman,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268374,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.1.107,false,true,268375,Photographs,Photograph,"Couple Seated, Woman Reading",,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268375,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.1.108,false,true,268376,Photographs,Photograph,Mrs. Watson,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268376,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.1.109,false,true,268377,Photographs,Photograph,"Mrs. Marian Murray, Lady Stair",,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268377,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.1.110,false,true,268379,Photographs,Photograph,Lady Ruthven,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268379,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.1.111,false,true,268380,Photographs,Photograph,Mrs. Shanker,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268380,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.1.112,false,true,268381,Photographs,Photograph,Mrs. Grierson,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268381,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.1.113,false,true,268382,Photographs,Photograph,Mrs. Rigby,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268382,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.1.114,false,true,268383,Photographs,Photograph,Miss Munro,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268383,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.1.115,false,true,268384,Photographs,Photograph,Miss Kemp as Ophelia,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268384,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.2.100,false,true,268484,Photographs,Photograph,Rev. Dr. Andrew Sutherland,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268484,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.2.101,false,true,268485,Photographs,Photograph,[George Gordon (?)],,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268485,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.2.102,false,true,268486,Photographs,Photograph,Dr. Cook of St. Andrews,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268486,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.2.103,false,true,268487,Photographs,Photograph,Dr. Cook,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268487,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.2.104,false,true,268488,Photographs,Photograph,Dr. Cook,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268488,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.2.105,false,true,268489,Photographs,Photograph,[Man],,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268489,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.2.106,false,true,268490,Photographs,Photograph,[Man],,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268490,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.2.107,false,true,268491,Photographs,Photograph,[Man],,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268491,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.2.108,false,true,268492,Photographs,Photograph,Hartcourt (Brother of Archbishop of York),,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268492,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.2.109,false,true,268493,Photographs,Photograph,Sir Charles Lyell - Geologist,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268493,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.2.110,false,true,268495,Photographs,Photograph,"Rev. Dr. John Purves, Jedburgh",,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268495,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.2.111,false,true,268496,Photographs,Photograph,Scott (of Peel),,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268496,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.2.112,false,true,268497,Photographs,Photograph,Rev D.T.K. Drummond,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268497,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.2.113,false,true,268498,Photographs,Photograph,[Man],,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268498,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.2.114,false,true,268499,Photographs,Photograph,[Man],,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268499,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.2.115,false,true,268500,Photographs,Photograph,[Man],,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268500,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.2.116,false,true,268501,Photographs,Photograph,Earl of Rosemore,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268501,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.2.117,false,true,268502,Photographs,Photograph,Dr. Latham - Editor of Dictionary,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268502,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.2.118,false,true,268503,Photographs,Photograph,Dr. MacCulloch of Kelso and Greenock,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268503,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.2.119,false,true,268504,Photographs,Photograph,Mr. McNab - Botanical Gardens,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268504,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.2.120,false,true,268506,Photographs,Photograph,[Man],,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268506,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.2.121,false,true,268507,Photographs,Photograph,Sir John Boilleau,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268507,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.2.122,false,true,268508,Photographs,Photograph,MacKenzie (Tongue),,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268508,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.2.123,false,true,268509,Photographs,Photograph,"Principal Haldane, St. Andrews",,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268509,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.2.124,false,true,268510,Photographs,Photograph,Rev. R. Brewster of Craig,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268510,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.2.125,false,true,268511,Photographs,Photograph,Dr. Smyttan,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268511,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.2.126,false,true,268512,Photographs,Photograph,James Nasmyth (Steam Hammer),,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268512,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.2.127,false,true,268513,Photographs,Photograph,Laird of Portmoak,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268513,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.2.128,false,true,268514,Photographs,Photograph,"Rev. Henry Grey, D.D., St. Mary's, Edinburgh",,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268514,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.2.129,false,true,268515,Photographs,Photograph,James Ballantine,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268515,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.2.130,false,true,268517,Photographs,Photograph,Sobieski Stuart,,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268517,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.98.2.131,false,true,268518,Photographs,Photograph,"Prof. Fraser, Rev. Welsh, Rev. Hamilton, and Three Other Men",,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268518,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.354,false,true,285726,Photographs,Photograph,"The Morning After ""He Greatly Daring Dined""",,,,,,Artist|Artist|Photography Studio,,David Octavius Hill|Robert Adamson|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843,1870 |1848 |1848,1843–47,1843,1847,Salted paper print from paper negative,Image: 19.7 x 14.6 cm (7 3/4 x 5 3/4 in.) Mount: 14 3/4 in. × 10 3/8 in. (37.4 × 26.3 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285726,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.159.17,false,true,306219,Photographs,Photograph,The Artist and the Grave Digger,,,,,,Artist|Artist|Photography Studio,,Robert Adamson|David Octavius Hill|Hill and Adamson,"British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, active 1843–1848",,"Adamson, Robert|Hill, David Octavius|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1821 |1802 |1843,1848 |1870 |1848,1843–44,1843,1844,Salted paper print from paper negative,Mount: 14 7/8 in. × 10 1/2 in. (37.8 × 26.7 cm) Image: 8 3/8 × 6 5/16 in. (21.2 × 16 cm),"Bequest of Maurice B. Sendak, 2012",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306219,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.159.23,false,true,306226,Photographs,Photograph,"David Young and Unknown Man, Newhaven",,,,,,Artist|Artist|Photography Studio,,Robert Adamson|David Octavius Hill|Hill and Adamson,"British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, active 1843–1848",,"Adamson, Robert|Hill, David Octavius|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish",1821 |1802 |1843,1848 |1870 |1848,1845,1845,1845,Salted paper print from paper negative,Image: 6 1/8 × 4 7/16 in. (15.6 × 11.3 cm),"Bequest of Maurice B. Sendak, 2012",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306226,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1982.1182.2038–.2084,false,true,288319,Photographs,Stereographs,[Group of 47 Stereograph Views of the 1904 St. Louis World's Fair and Louisiana Purchase Exposition],,,,,,Artist|Publisher|Publisher|Photography Studio|Artist|Artist|Publisher|Artist|Subject|Publisher|Artist|Person in Photograph|Publisher,,C. H. Graves|Universal View Co.|Underwood & Underwood|Sun Sculpture Works and Studios|Strohmeyer & Wyman|William H. Rau|Metropolitan Series|Kilburn Brothers|James M. Davis|H. C. White Company|Unknown|Geronimo (Goyaalé)|Keystone View Company,"American|American|American|American|American|American, 1855–1920|American|American, active ca. 1865–1890|American|American|American Indian (Apache), 1829–1909",,"Graves, C. H.|Universal View Co.|Underwood & Underwood|Sun Sculpture Works and Studios|Strohmeyer & Wyman|Rau, William H.|Metropolitan Series|Kilburn Brothers|Davis, James M.|H. C. White Company|Unknown|Geronimo (Goyaalé)|Keystone View Company","American|American|American|American|American|American|American|American|American|American Indian, Apache",1855 |1863 |1829,1920 |1892 |1909,1850s–1910s,1850,1919,Albumen silver prints,Mounts approximately: 8.9 x 17.8 cm (3 1/2 x 7 in.),"Gift of Weston J. Naef, in memory of Kathleen W. Naef and Weston J. Naef Sr., 1982",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288319,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1982.1182.411–.433,false,true,288143,Photographs,Stereographs,[Group of 23 Stereograph Views of Railroad Bridges],,,,,,Publisher|Publisher|Publisher|Publisher|Publisher|Publisher|Publisher|Publisher|Artist|Publisher|Artist|Publisher|Publisher,,E. P. Libby|American Scenery|American Series|American Views|Allen & Hovey|Mrs. M. E. Allen|E. C. Barnum|New H Series|Littleton View Company|Western View Company|Benneville Lloyd Singley|H. L. & G. E. Williams|Keystone View Company,"American|American|American|American|American|American|American|American|American|American, Union Township, Pennsylvania 1864–1938 Meadville, Pennsylvania|American",,"Libby, E. P.|American Scenery|American Series|American Views|Allen & Hovey|Allen, M. E. Mrs.|Barnum, E. C.|New H Series|Littleton View Company|Western View Company|Singley, Benneville Lloyd|H. L. & G. E. Williams|Keystone View Company",American|American|American|American|American|American|American|American|American|American|American,1864,1938,1850s–1910s,1850,1919,Albumen silver prints,Mounts approximately: 8.2 x 17.1 cm (3 1/4 x 6 3/4 in.) to 10 x 17.8 cm (3 15/16 x 7 in.),"Gift of Weston J. Naef, in memory of Kathleen W. Naef and Weston J. Naef Sr., 1982",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288143,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1982.1182.1044–.1074,false,true,288300,Photographs,Stereographs,[Group of 31 Stereograph Views of Children With Animals],,,,,,Publisher|Artist|Artist|Publisher|Photography Studio|Publisher|Artist|Artist|Publisher|Publisher|Publisher|Publisher|Publisher|Publisher|Publisher|Artist|Artist|Publisher,,Life Groups|Benneville Lloyd Singley|Strohmeyer & Wyman|Underwood & Underwood|Sun Sculpture Works and Studios|Universal View Co.|William H. Rau|C. H. Graves|Universal Photo Art Co.|Canvassers|Webster & Albee|Popular Series|European and American Views|Stereoscopic Gems|Comics and Groups|Unknown|Unknown|Keystone View Company,"American|American, Union Township, Pennsylvania 1864–1938 Meadville, Pennsylvania|American|American|American|American|American, 1855–1920|American|American|American",,"Life Groups|Singley, Benneville Lloyd|Strohmeyer & Wyman|Underwood & Underwood|Sun Sculpture Works and Studios|Universal View Co.|Rau, William H.|Graves, C. H.|Universal Photo Art Co.|Canvassers|Webster & Albee|Popular Series|European and American Views|Stereoscopic Gems|Comics and Groups|Unknown|Unknown|Keystone View Company",American|American|American|American|American|American|American|American|American,1864 |1855,1938 |1920,1850s–1910s,1850,1919,Albumen silver prints,Mounts approximately: 8.9 x 17.8 cm (3 1/2 x 7 in.),"Gift of Weston J. Naef, in memory of Kathleen W. Naef and Weston J. Naef Sr., 1982",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288300,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1982.1182.1152–.1157,false,true,288303,Photographs,Stereographs,[Group of 6 Stereograph Views of Christmas Scenes],,,,,,Publisher|Artist|Publisher|Artist|Artist|Publisher|Artist|Publisher|Photography Studio|Artist|Publisher|Artist|Publisher,,"Hegger|C. H. Graves|Universal Photo Art Co.|Benneville Lloyd Singley|Kilburn Brothers|James M. Davis|Strohmeyer & Wyman|Underwood & Underwood|Sun Sculpture Works and Studios|George W. Griffith|Griffith & Griffith, American|Unknown|Keystone View Company","American|American|American, Union Township, Pennsylvania 1864–1938 Meadville, Pennsylvania|American, active ca. 1865–1890|American|American|American|American|American",,"Hegger|Graves, C. H.|Universal Photo Art Co.|Singley, Benneville Lloyd|Kilburn Brothers|Davis, James M.|Strohmeyer & Wyman|Underwood & Underwood|Sun Sculpture Works and Studios|Griffith, George W.|Griffith & Griffith|Unknown|Keystone View Company",American|American|American|American|American|American|American|American|American,1864 |1863,1938 |1892,1850s–1910s,1850,1919,Albumen silver prints,Mounts approximately: 8.9 x 17.8 cm (3 1/2 x 7 in.),"Gift of Weston J. Naef, in memory of Kathleen W. Naef and Weston J. Naef Sr., 1982",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288303,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.14.25,false,true,268323,Photographs,Photograph,Horace Mann,,,,,,Artist|Photography Studio|Artist,,Josiah Johnson Hawes|Southworth and Hawes|Albert Sands Southworth,"American, Wayland, Massachusetts 1808–1901 Crawford Notch, New Hampshire|American, active 1843–1863|American, West Fairlee, Vermont 1811–1894 Charlestown, Massachusetts",,"Hawes, Josiah Johnson|Southworth and Hawes|Southworth, Albert Sands",American|American|American,1808 |1843 |1811,1901 |1863 |1894,ca. 1850,1848,1852,Daguerreotype,21.6 x 16.5 cm (8 1/2 x 6 1/2 in.),"Gift of I. N. Phelps Stokes, Edward S. Hawes, Alice Mary Hawes, and Marion Augusta Hawes, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268323,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -38.34,true,true,268621,Photographs,Photograph,Lemuel Shaw,,,,,,Artist|Artist|Photography Studio,,Albert Sands Southworth|Josiah Johnson Hawes|Southworth and Hawes,"American, West Fairlee, Vermont 1811–1894 Charlestown, Massachusetts|American, Wayland, Massachusetts 1808–1901 Crawford Notch, New Hampshire|American, active 1843–1863",,"Southworth, Albert Sands|Hawes, Josiah Johnson|Southworth and Hawes",American|American|American,1811 |1808 |1843,1894 |1901 |1863,ca. 1850,1848,1852,Daguerreotype,21.6 x 16.5 cm (8 1/2 x 6 1/2 in.),"Gift of Edward S. Hawes, Alice Mary Hawes, and Marion Augusta Hawes, 1938",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268621,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.14.2,false,true,268317,Photographs,Photograph,Daniel Webster,,,,,,Artist|Artist|Photography Studio,,Albert Sands Southworth|Josiah Johnson Hawes|Southworth and Hawes,"American, West Fairlee, Vermont 1811–1894 Charlestown, Massachusetts|American, Wayland, Massachusetts 1808–1901 Crawford Notch, New Hampshire|American, active 1843–1863",,"Southworth, Albert Sands|Hawes, Josiah Johnson|Southworth and Hawes",American|American|American,1811 |1808 |1843,1894 |1901 |1863,ca. 1850,1848,1852,Daguerreotype,21.5 x 16.6 cm (8 7/16 x 6 9/16 in.),"Gift of I. N. Phelps Stokes, Edward S. Hawes, Alice Mary Hawes, and Marion Augusta Hawes, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268317,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.14.3,false,true,268328,Photographs,Photograph,"[View Down Brattle Street from the Southworth & Hawes Studio at 5 1/2 Tremont Row, Boston]",,,,,,Artist|Artist|Photography Studio,,Albert Sands Southworth|Josiah Johnson Hawes|Southworth and Hawes,"American, West Fairlee, Vermont 1811–1894 Charlestown, Massachusetts|American, Wayland, Massachusetts 1808–1901 Crawford Notch, New Hampshire|American, active 1843–1863",,"Southworth, Albert Sands|Hawes, Josiah Johnson|Southworth and Hawes",American|American|American,1811 |1808 |1843,1894 |1901 |1863,1855,1855,1855,Daguerreotype,21.6 x 16.5 cm (8 1/2 x 6 1/2 in.),"Gift of I. N. Phelps Stokes, Edward S. Hawes, Alice Mary Hawes, and Marion Augusta Hawes, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268328,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.14.4,false,true,268339,Photographs,Photograph,[Woman in Black Taffeta Dress and Lace Shawl],,,,,,Artist|Artist|Photography Studio,,Albert Sands Southworth|Josiah Johnson Hawes|Southworth and Hawes,"American, West Fairlee, Vermont 1811–1894 Charlestown, Massachusetts|American, Wayland, Massachusetts 1808–1901 Crawford Notch, New Hampshire|American, active 1843–1863",,"Southworth, Albert Sands|Hawes, Josiah Johnson|Southworth and Hawes",American|American|American,1811 |1808 |1843,1894 |1901 |1863,ca. 1850,1848,1852,Daguerreotype,21.6 x 16.5 cm (8 1/2 x 6 1/2 in.),"Gift of I. N. Phelps Stokes, Edward S. Hawes, Alice Mary Hawes, and Marion Augusta Hawes, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268339,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.14.5,false,true,268350,Photographs,Photograph,[Unidentified Woman],,,,,,Artist|Artist|Photography Studio,,Albert Sands Southworth|Josiah Johnson Hawes|Southworth and Hawes,"American, West Fairlee, Vermont 1811–1894 Charlestown, Massachusetts|American, Wayland, Massachusetts 1808–1901 Crawford Notch, New Hampshire|American, active 1843–1863",,"Southworth, Albert Sands|Hawes, Josiah Johnson|Southworth and Hawes",American|American|American,1811 |1808 |1843,1894 |1901 |1863,ca. 1850,1848,1852,Daguerreotype,21.6 x 16.5 cm (8 1/2 x 6 1/2 in.),"Gift of I. N. Phelps Stokes, Edward S. Hawes, Alice Mary Hawes, and Marion Augusta Hawes, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268350,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.14.6,false,true,268359,Photographs,Photograph,[Boston Lawyers or Clergymen (?)],,,,,,Artist|Artist|Photography Studio,,Albert Sands Southworth|Josiah Johnson Hawes|Southworth and Hawes,"American, West Fairlee, Vermont 1811–1894 Charlestown, Massachusetts|American, Wayland, Massachusetts 1808–1901 Crawford Notch, New Hampshire|American, active 1843–1863",,"Southworth, Albert Sands|Hawes, Josiah Johnson|Southworth and Hawes",American|American|American,1811 |1808 |1843,1894 |1901 |1863,ca. 1850,1848,1852,Daguerreotype,16.5 x 21.6 cm (6 1/2 x 8 1/2 in.),"Gift of I. N. Phelps Stokes, Edward S. Hawes, Alice Mary Hawes, and Marion Augusta Hawes, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268359,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.14.7,false,true,268360,Photographs,Photograph,Niagara Falls from the Canadian Side,,,,,,Artist|Artist|Photography Studio,,Albert Sands Southworth|Josiah Johnson Hawes|Southworth and Hawes,"American, West Fairlee, Vermont 1811–1894 Charlestown, Massachusetts|American, Wayland, Massachusetts 1808–1901 Crawford Notch, New Hampshire|American, active 1843–1863",,"Southworth, Albert Sands|Hawes, Josiah Johnson|Southworth and Hawes",American|American|American,1811 |1808 |1843,1894 |1901 |1863,ca. 1850,1848,1852,Daguerreotype,21.6 x 16.5 cm (8 1/2 x 6 1/2 in.),"Gift of I. N. Phelps Stokes, Edward S. Hawes, Alice Mary Hawes, and Marion Augusta Hawes, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268360,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.14.9,false,true,268362,Photographs,Photograph,Francis Parkman,,,,,,Artist|Artist|Photography Studio,,Albert Sands Southworth|Josiah Johnson Hawes|Southworth and Hawes,"American, West Fairlee, Vermont 1811–1894 Charlestown, Massachusetts|American, Wayland, Massachusetts 1808–1901 Crawford Notch, New Hampshire|American, active 1843–1863",,"Southworth, Albert Sands|Hawes, Josiah Johnson|Southworth and Hawes",American|American|American,1811 |1808 |1843,1894 |1901 |1863,ca. 1850,1848,1852,Daguerreotype,10.8 x 8.3 cm (4 1/4 x 3 1/4 in.),"Gift of I. N. Phelps Stokes, Edward S. Hawes, Alice Mary Hawes, and Marion Augusta Hawes, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268362,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -39.22.2,false,true,268648,Photographs,Photograph,[Man in a Sheraton Chair],,,,,,Artist|Artist|Photography Studio,,Albert Sands Southworth|Josiah Johnson Hawes|Southworth and Hawes,"American, West Fairlee, Vermont 1811–1894 Charlestown, Massachusetts|American, Wayland, Massachusetts 1808–1901 Crawford Notch, New Hampshire|American, active 1843–1863",,"Southworth, Albert Sands|Hawes, Josiah Johnson|Southworth and Hawes",American|American|American,1811 |1808 |1843,1894 |1901 |1863,ca. 1850,1848,1852,Daguerreotype,21.6 x 16.5 cm (8 1/2 x 6 1/2 in.),"Gift Edward S. Hawes and Marion Augusta Hawes, 1939",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268648,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -39.22.4,false,true,268650,Photographs,Photograph,[Unidentified Woman in Nine Oval Views],,,,,,Artist|Artist|Photography Studio,,Albert Sands Southworth|Josiah Johnson Hawes|Southworth and Hawes,"American, West Fairlee, Vermont 1811–1894 Charlestown, Massachusetts|American, Wayland, Massachusetts 1808–1901 Crawford Notch, New Hampshire|American, active 1843–1863",,"Southworth, Albert Sands|Hawes, Josiah Johnson|Southworth and Hawes",American|American|American,1811 |1808 |1843,1894 |1901 |1863,ca. 1850,1848,1852,Daguerreotype,21.6 x 16.5 cm (8 1/2 x 6 1/2 in.),"Gift of Edward S. Hawes and Marion Augusta Hawes, 1939",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268650,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.14.10,false,true,268307,Photographs,Photograph,Margaret Fuller (Marchioness Ossoli),,,,,,Artist|Artist|Photography Studio,,Albert Sands Southworth|Josiah Johnson Hawes|Southworth and Hawes,"American, West Fairlee, Vermont 1811–1894 Charlestown, Massachusetts|American, Wayland, Massachusetts 1808–1901 Crawford Notch, New Hampshire|American, active 1843–1863",,"Southworth, Albert Sands|Hawes, Josiah Johnson|Southworth and Hawes",American|American|American,1811 |1808 |1843,1894 |1901 |1863,ca. 1850,1848,1852,Daguerreotype,10.8 x 8.3 cm (4 1/4 x 3 1/4 in.),"Gift of I. N. Phelps Stokes, Edward S. Hawes, Alice Mary Hawes, and Marion Augusta Hawes, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268307,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.14.11,false,true,268308,Photographs,Photograph,[Nancy Southworth Hawes],,,,,,Artist|Artist|Photography Studio,,Albert Sands Southworth|Josiah Johnson Hawes|Southworth and Hawes,"American, West Fairlee, Vermont 1811–1894 Charlestown, Massachusetts|American, Wayland, Massachusetts 1808–1901 Crawford Notch, New Hampshire|American, active 1843–1863",,"Southworth, Albert Sands|Hawes, Josiah Johnson|Southworth and Hawes",American|American|American,1811 |1808 |1843,1894 |1901 |1863,ca. 1850,1848,1852,Daguerreotype,8.3 x 7.0 cm (3 1/4 x 2 3/4 in.),"Gift of I. N. Phelps Stokes, Edward S. Hawes, Alice Mary Hawes, and Marion Augusta Hawes, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268308,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.14.12,false,true,268309,Photographs,Photograph,[Unidentified Boy in Dark Suit],,,,,,Artist|Artist|Photography Studio,,Albert Sands Southworth|Josiah Johnson Hawes|Southworth and Hawes,"American, West Fairlee, Vermont 1811–1894 Charlestown, Massachusetts|American, Wayland, Massachusetts 1808–1901 Crawford Notch, New Hampshire|American, active 1843–1863",,"Southworth, Albert Sands|Hawes, Josiah Johnson|Southworth and Hawes",American|American|American,1811 |1808 |1843,1894 |1901 |1863,ca. 1850,1848,1852,Daguerreotype,21.6 x 16.5 cm (8 1/2 x 6 1/2 in.),"Gift of I. N. Phelps Stokes, Edward S. Hawes, Alice Mary Hawes, and Marion Augusta Hawes, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268309,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.14.13,false,true,268310,Photographs,Photograph,[Unidentified Woman],,,,,,Artist|Artist|Photography Studio,,Albert Sands Southworth|Josiah Johnson Hawes|Southworth and Hawes,"American, West Fairlee, Vermont 1811–1894 Charlestown, Massachusetts|American, Wayland, Massachusetts 1808–1901 Crawford Notch, New Hampshire|American, active 1843–1863",,"Southworth, Albert Sands|Hawes, Josiah Johnson|Southworth and Hawes",American|American|American,1811 |1808 |1843,1894 |1901 |1863,ca. 1850,1848,1852,Daguerreotype,21.6 x 16.5 cm (8 1/2 x 6 1/2 in.),"Gift of I. N. Phelps Stokes, Edward S. Hawes, Alice Mary Hawes, and Marion Augusta Hawes, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268310,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.14.14,false,true,268311,Photographs,Photograph,Millard Fillmore,,,,,,Artist|Artist|Photography Studio,,Albert Sands Southworth|Josiah Johnson Hawes|Southworth and Hawes,"American, West Fairlee, Vermont 1811–1894 Charlestown, Massachusetts|American, Wayland, Massachusetts 1808–1901 Crawford Notch, New Hampshire|American, active 1843–1863",,"Southworth, Albert Sands|Hawes, Josiah Johnson|Southworth and Hawes",American|American|American,1811 |1808 |1843,1894 |1901 |1863,ca. 1850,1848,1852,Daguerreotype,21.6 x 16.5 cm (8 1/2 x 6 1/2 in.),"Gift of I. N. Phelps Stokes, Edward S. Hawes, Alice Mary Hawes, and Marion Augusta Hawes, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268311,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.14.15,false,true,268312,Photographs,Photograph,Henry Clay,,,,,,Artist|Artist|Photography Studio,,Albert Sands Southworth|Josiah Johnson Hawes|Southworth and Hawes,"American, West Fairlee, Vermont 1811–1894 Charlestown, Massachusetts|American, Wayland, Massachusetts 1808–1901 Crawford Notch, New Hampshire|American, active 1843–1863",,"Southworth, Albert Sands|Hawes, Josiah Johnson|Southworth and Hawes",American|American|American,1811 |1808 |1843,1894 |1901 |1863,ca. 1850,1848,1852,Daguerreotype,14.0 x 10.8 cm (5 1/2 x 4 1/4 in.),"Gift of I. N. Phelps Stokes, Edward S. Hawes, Alice Mary Hawes, and Marion Augusta Hawes, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268312,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.14.16,false,true,268313,Photographs,Photograph,Albert Sands Southworth,,,,,,Artist|Artist|Photography Studio,,Albert Sands Southworth|Josiah Johnson Hawes|Southworth and Hawes,"American, West Fairlee, Vermont 1811–1894 Charlestown, Massachusetts|American, Wayland, Massachusetts 1808–1901 Crawford Notch, New Hampshire|American, active 1843–1863",,"Southworth, Albert Sands|Hawes, Josiah Johnson|Southworth and Hawes",American|American|American,1811 |1808 |1843,1894 |1901 |1863,ca. 1845–50,1845,1850,Daguerreotype,8.3 x 7.0 cm (3 1/4 x 2 3/4 in.),"Gift of I. N. Phelps Stokes, Edward S. Hawes, Alice Mary Hawes, and Marion Augusta Hawes, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268313,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.14.17,false,true,268314,Photographs,Photograph,[Elderly Woman in Black Cape and Bonnet with Mourning Crape],,,,,,Artist|Artist|Photography Studio,,Albert Sands Southworth|Josiah Johnson Hawes|Southworth and Hawes,"American, West Fairlee, Vermont 1811–1894 Charlestown, Massachusetts|American, Wayland, Massachusetts 1808–1901 Crawford Notch, New Hampshire|American, active 1843–1863",,"Southworth, Albert Sands|Hawes, Josiah Johnson|Southworth and Hawes",American|American|American,1811 |1808 |1843,1894 |1901 |1863,ca. 1850,1848,1852,Daguerreotype,21.6 x 16.5 cm (8 1/2 x 6 1/2 in.),"Gift of I. N. Phelps Stokes, Edward S. Hawes, Alice Mary Hawes, and Marion Augusta Hawes, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268314,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.14.18,false,true,268315,Photographs,Photograph,[Unidentified Woman],,,,,,Artist|Artist|Photography Studio,,Albert Sands Southworth|Josiah Johnson Hawes|Southworth and Hawes,"American, West Fairlee, Vermont 1811–1894 Charlestown, Massachusetts|American, Wayland, Massachusetts 1808–1901 Crawford Notch, New Hampshire|American, active 1843–1863",,"Southworth, Albert Sands|Hawes, Josiah Johnson|Southworth and Hawes",American|American|American,1811 |1808 |1843,1894 |1901 |1863,ca. 1850,1848,1852,Daguerreotype,21.6 x 16.5 cm (8 1/2 x 6 1/2 in.),"Gift of I. N. Phelps Stokes, Edward S. Hawes, Alice Mary Hawes, and Marion Augusta Hawes, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268315,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.14.22,false,true,268320,Photographs,Photograph,Classroom in the Emerson School for Girls,,,,,,Artist|Artist|Photography Studio,,Albert Sands Southworth|Josiah Johnson Hawes|Southworth and Hawes,"American, West Fairlee, Vermont 1811–1894 Charlestown, Massachusetts|American, Wayland, Massachusetts 1808–1901 Crawford Notch, New Hampshire|American, active 1843–1863",,"Southworth, Albert Sands|Hawes, Josiah Johnson|Southworth and Hawes",American|American|American,1811 |1808 |1843,1894 |1901 |1863,ca. 1850,1848,1852,Daguerreotype,21.6 x 16.5 cm (8 1/2 x 6 1/2 in.),"Gift of I. N. Phelps Stokes, Edward S. Hawes, Alice Mary Hawes, and Marion Augusta Hawes, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268320,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.14.23,false,true,268321,Photographs,Photograph,Commodore Charles Morris,,,,,,Artist|Artist|Photography Studio,,Albert Sands Southworth|Josiah Johnson Hawes|Southworth and Hawes,"American, West Fairlee, Vermont 1811–1894 Charlestown, Massachusetts|American, Wayland, Massachusetts 1808–1901 Crawford Notch, New Hampshire|American, active 1843–1863",,"Southworth, Albert Sands|Hawes, Josiah Johnson|Southworth and Hawes",American|American|American,1811 |1808 |1843,1894 |1901 |1863,ca. 1850,1848,1852,Daguerreotype,14.0 x 10.8 cm (5 1/2 x 4 1/4 in.),"Gift of I. N. Phelps Stokes, Edward S. Hawes, Alice Mary Hawes, and Marion Augusta Hawes, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268321,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.14.24,false,true,268322,Photographs,Photograph,William Hickling Prescott,,,,,,Artist|Artist|Photography Studio,,Albert Sands Southworth|Josiah Johnson Hawes|Southworth and Hawes,"American, West Fairlee, Vermont 1811–1894 Charlestown, Massachusetts|American, Wayland, Massachusetts 1808–1901 Crawford Notch, New Hampshire|American, active 1843–1863",,"Southworth, Albert Sands|Hawes, Josiah Johnson|Southworth and Hawes",American|American|American,1811 |1808 |1843,1894 |1901 |1863,ca. 1850,1848,1852,Daguerreotype,14.0 x 10.8 cm (5 1/2 x 4 1/4 in.),"Gift of I. N. Phelps Stokes, Edward S. Hawes, Alice Mary Hawes, and Marion Augusta Hawes, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268322,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.14.27,false,true,268325,Photographs,Photograph,Mrs. James Thomas Fields (Annie Adams),,,,,,Artist|Artist|Photography Studio,,Albert Sands Southworth|Josiah Johnson Hawes|Southworth and Hawes,"American, West Fairlee, Vermont 1811–1894 Charlestown, Massachusetts|American, Wayland, Massachusetts 1808–1901 Crawford Notch, New Hampshire|American, active 1843–1863",,"Southworth, Albert Sands|Hawes, Josiah Johnson|Southworth and Hawes",American|American|American,1811 |1808 |1843,1894 |1901 |1863,1861,1861,1861,Daguerreotype,21.6 x 16.5 cm (8 1/2 x 6 1/2 in.),"Gift of I. N. Phelps Stokes, Edward S. Hawes, Alice Mary Hawes, and Marion Augusta Hawes, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268325,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.14.29,false,true,268327,Photographs,Photograph,[Unidentified Man Wearing Turban],,,,,,Artist|Artist|Photography Studio,,Albert Sands Southworth|Josiah Johnson Hawes|Southworth and Hawes,"American, West Fairlee, Vermont 1811–1894 Charlestown, Massachusetts|American, Wayland, Massachusetts 1808–1901 Crawford Notch, New Hampshire|American, active 1843–1863",,"Southworth, Albert Sands|Hawes, Josiah Johnson|Southworth and Hawes",American|American|American,1811 |1808 |1843,1894 |1901 |1863,1851–52,1851,1852,Daguerreotype,8.3 x 7.0 cm (3 1/4 x 2 3/4 in.),"Gift of I. N. Phelps Stokes, Edward S. Hawes, Alice Mary Hawes, and Marion Augusta Hawes, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268327,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.14.30,false,true,268329,Photographs,Photograph,George Peabody,,,,,,Artist|Artist|Photography Studio,,Albert Sands Southworth|Josiah Johnson Hawes|Southworth and Hawes,"American, West Fairlee, Vermont 1811–1894 Charlestown, Massachusetts|American, Wayland, Massachusetts 1808–1901 Crawford Notch, New Hampshire|American, active 1843–1863",,"Southworth, Albert Sands|Hawes, Josiah Johnson|Southworth and Hawes",American|American|American,1811 |1808 |1843,1894 |1901 |1863,ca. 1850,1848,1852,Daguerreotype,10.8 x 8.3 cm (4 1/4 x 3 1/4 in.),"Gift of I. N. Phelps Stokes, Edward S. Hawes, Alice Mary Hawes, and Marion Augusta Hawes, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268329,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.14.31,false,true,268330,Photographs,Photograph,Henry Wadsworth Longfellow,,,,,,Artist|Artist|Photography Studio,,Albert Sands Southworth|Josiah Johnson Hawes|Southworth and Hawes,"American, West Fairlee, Vermont 1811–1894 Charlestown, Massachusetts|American, Wayland, Massachusetts 1808–1901 Crawford Notch, New Hampshire|American, active 1843–1863",,"Southworth, Albert Sands|Hawes, Josiah Johnson|Southworth and Hawes",American|American|American,1811 |1808 |1843,1894 |1901 |1863,ca. 1850,1848,1852,Daguerreotype,10.8 x 8.3 cm (4 1/4 x 3 1/4 in.),"Gift of I. N. Phelps Stokes, Edward S. Hawes, Alice Mary Hawes, and Marion Augusta Hawes, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268330,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.14.32,false,true,268331,Photographs,Photograph,Zachary Taylor,,,,,,Artist|Artist|Photography Studio,,Albert Sands Southworth|Josiah Johnson Hawes|Southworth and Hawes,"American, West Fairlee, Vermont 1811–1894 Charlestown, Massachusetts|American, Wayland, Massachusetts 1808–1901 Crawford Notch, New Hampshire|American, active 1843–1863",,"Southworth, Albert Sands|Hawes, Josiah Johnson|Southworth and Hawes",American|American|American,1811 |1808 |1843,1894 |1901 |1863,ca. 1850,1840,1859,Daguerreotype,8.3 x 7.0 cm (3 1/4 x 2 3/4 in.),"Gift of I. N. Phelps Stokes, Edward S. Hawes, Alice Mary Hawes, and Marion Augusta Hawes, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268331,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.14.33,false,true,268332,Photographs,Photograph,John Howard Payne,,,,,,Artist|Artist|Photography Studio,,Albert Sands Southworth|Josiah Johnson Hawes|Southworth and Hawes,"American, West Fairlee, Vermont 1811–1894 Charlestown, Massachusetts|American, Wayland, Massachusetts 1808–1901 Crawford Notch, New Hampshire|American, active 1843–1863",,"Southworth, Albert Sands|Hawes, Josiah Johnson|Southworth and Hawes",American|American|American,1811 |1808 |1843,1894 |1901 |1863,ca. 1850,1848,1852,Daguerreotype,14.0 x 10.8 cm (5 1/2 x 4 1/4 in.),"Gift of I. N. Phelps Stokes, Edward S. Hawes, Alice Mary Hawes, and Marion Augusta Hawes, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268332,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.14.35,false,true,268334,Photographs,Photograph,[Woman in Profile with Lace Collar and Shawl],,,,,,Artist|Artist|Photography Studio,,Albert Sands Southworth|Josiah Johnson Hawes|Southworth and Hawes,"American, West Fairlee, Vermont 1811–1894 Charlestown, Massachusetts|American, Wayland, Massachusetts 1808–1901 Crawford Notch, New Hampshire|American, active 1843–1863",,"Southworth, Albert Sands|Hawes, Josiah Johnson|Southworth and Hawes",American|American|American,1811 |1808 |1843,1894 |1901 |1863,ca. 1850,1848,1852,Daguerreotype,21.6 x 16.5 cm (8 1/2 x 6 1/2 in.),"Gift of I. N. Phelps Stokes, Edward S. Hawes, Alice Mary Hawes, and Marion Augusta Hawes, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268334,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.14.36,false,true,268335,Photographs,Photograph,James Thomas Fields,,,,,,Artist|Artist|Photography Studio,,Albert Sands Southworth|Josiah Johnson Hawes|Southworth and Hawes,"American, West Fairlee, Vermont 1811–1894 Charlestown, Massachusetts|American, Wayland, Massachusetts 1808–1901 Crawford Notch, New Hampshire|American, active 1843–1863",,"Southworth, Albert Sands|Hawes, Josiah Johnson|Southworth and Hawes",American|American|American,1811 |1808 |1843,1894 |1901 |1863,1861,1861,1861,Daguerreotype,21.6 x 16.5 cm (8 1/2 x 6 1/2 in.),"Gift of I. N. Phelps Stokes, Edward S. Hawes, Alice Mary Hawes, and Marion Augusta Hawes, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268335,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.14.37,false,true,268336,Photographs,Photograph,William Lloyd Garrison,,,,,,Artist|Artist|Photography Studio,,Albert Sands Southworth|Josiah Johnson Hawes|Southworth and Hawes,"American, West Fairlee, Vermont 1811–1894 Charlestown, Massachusetts|American, Wayland, Massachusetts 1808–1901 Crawford Notch, New Hampshire|American, active 1843–1863",,"Southworth, Albert Sands|Hawes, Josiah Johnson|Southworth and Hawes",American|American|American,1811 |1808 |1843,1894 |1901 |1863,ca. 1850,1848,1852,Daguerreotype,14.0 x 10.8 cm (5 1/2 x 4 1/4 in.),"Gift of I. N. Phelps Stokes, Edward S. Hawes, Alice Mary Hawes, and Marion Augusta Hawes, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268336,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.14.38,false,true,268337,Photographs,Photograph,Josiah Johnson Hawes,,,,,,Artist|Artist|Photography Studio,,Albert Sands Southworth|Josiah Johnson Hawes|Southworth and Hawes,"American, West Fairlee, Vermont 1811–1894 Charlestown, Massachusetts|American, Wayland, Massachusetts 1808–1901 Crawford Notch, New Hampshire|American, active 1843–1863",,"Southworth, Albert Sands|Hawes, Josiah Johnson|Southworth and Hawes",American|American|American,1811 |1808 |1843,1894 |1901 |1863,ca. 1845–50,1845,1850,Daguerreotype,10.8 x 8.3 cm (4 1/4 x 3 1/4 in.),"Gift of I. N. Phelps Stokes, Edward S. Hawes, Alice Mary Hawes, and Marion Augusta Hawes, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268337,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.14.39,false,true,268338,Photographs,Photograph,[Boston Doctors],,,,,,Artist|Artist|Photography Studio,,Albert Sands Southworth|Josiah Johnson Hawes|Southworth and Hawes,"American, West Fairlee, Vermont 1811–1894 Charlestown, Massachusetts|American, Wayland, Massachusetts 1808–1901 Crawford Notch, New Hampshire|American, active 1843–1863",,"Southworth, Albert Sands|Hawes, Josiah Johnson|Southworth and Hawes",American|American|American,1811 |1808 |1843,1894 |1901 |1863,ca. 1850,1848,1852,Daguerreotype,16.5 x 21.6 cm (6 1/2 x 8 1/2 in.),"Gift of I. N. Phelps Stokes, Edward S. Hawes, Alice Mary Hawes, and Marion Augusta Hawes, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268338,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.14.40,false,true,268340,Photographs,Photograph,Harriet Beecher Stowe,,,,,,Artist|Artist|Photography Studio,,Albert Sands Southworth|Josiah Johnson Hawes|Southworth and Hawes,"American, West Fairlee, Vermont 1811–1894 Charlestown, Massachusetts|American, Wayland, Massachusetts 1808–1901 Crawford Notch, New Hampshire|American, active 1843–1863",,"Southworth, Albert Sands|Hawes, Josiah Johnson|Southworth and Hawes",American|American|American,1811 |1808 |1843,1894 |1901 |1863,1850s,1850,1850,Daguerreotype,10.8 x 8.3 cm. (4 1/4 x 3 1/4 in.),"Gift of I. N. Phelps Stokes, Edward S. Hawes, Alice Mary Hawes, and Marion Augusta Hawes, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268340,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.14.41,false,true,268341,Photographs,Photograph,Lola Montez,,,,,,Artist|Artist|Photography Studio,,Albert Sands Southworth|Josiah Johnson Hawes|Southworth and Hawes,"American, West Fairlee, Vermont 1811–1894 Charlestown, Massachusetts|American, Wayland, Massachusetts 1808–1901 Crawford Notch, New Hampshire|American, active 1843–1863",,"Southworth, Albert Sands|Hawes, Josiah Johnson|Southworth and Hawes",American|American|American,1811 |1808 |1843,1894 |1901 |1863,ca. 1850,1848,1852,Daguerreotype,21.6 x 16.5 cm (8 1/2 x 6 1/2 in.),"Gift of I. N. Phelps Stokes, Edward S. Hawes, Alice Mary Hawes, and Marion Augusta Hawes, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268341,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.14.43,false,true,268343,Photographs,Photograph,James Freeman Clarke,,,,,,Artist|Artist|Photography Studio,,Albert Sands Southworth|Josiah Johnson Hawes|Southworth and Hawes,"American, West Fairlee, Vermont 1811–1894 Charlestown, Massachusetts|American, Wayland, Massachusetts 1808–1901 Crawford Notch, New Hampshire|American, active 1843–1863",,"Southworth, Albert Sands|Hawes, Josiah Johnson|Southworth and Hawes",American|American|American,1811 |1808 |1843,1894 |1901 |1863,ca. 1850,1848,1852,Daguerreotype,14.0 x 10.8 cm (5 1/2 x 4 1/4 in.),"Gift of I. N. Phelps Stokes, Edward S. Hawes, Alice Mary Hawes, and Marion Augusta Hawes, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268343,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.14.48,false,true,268348,Photographs,Photograph,Rufus Choate,,,,,,Artist|Artist|Photography Studio,,Albert Sands Southworth|Josiah Johnson Hawes|Southworth and Hawes,"American, West Fairlee, Vermont 1811–1894 Charlestown, Massachusetts|American, Wayland, Massachusetts 1808–1901 Crawford Notch, New Hampshire|American, active 1843–1863",,"Southworth, Albert Sands|Hawes, Josiah Johnson|Southworth and Hawes",American|American|American,1811 |1808 |1843,1894 |1901 |1863,ca. 1850,1848,1852,Daguerreotype,20.4 x 15.3 cm (8 x 6 in.),"Gift of I. N. Phelps Stokes, Edward S. Hawes, Alice Mary Hawes, and Marion Augusta Hawes, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268348,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.14.50,false,true,268351,Photographs,Photograph,Charles Sprague,,,,,,Artist|Artist|Photography Studio,,Albert Sands Southworth|Josiah Johnson Hawes|Southworth and Hawes,"American, West Fairlee, Vermont 1811–1894 Charlestown, Massachusetts|American, Wayland, Massachusetts 1808–1901 Crawford Notch, New Hampshire|American, active 1843–1863",,"Southworth, Albert Sands|Hawes, Josiah Johnson|Southworth and Hawes",American|American|American,1811 |1808 |1843,1894 |1901 |1863,ca. 1850,1848,1852,Daguerreotype,42.2 x 32.4 cm (16 5/8 x 12 3/4 in.),"Gift of I. N. Phelps Stokes, Edward S. Hawes, Alice Mary Hawes, and Marion Augusta Hawes, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268351,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.14.51,false,true,268352,Photographs,Photograph,[Young Girl with Hand Raised to Shoulder],,,,,,Artist|Artist|Photography Studio,,Albert Sands Southworth|Josiah Johnson Hawes|Southworth and Hawes,"American, West Fairlee, Vermont 1811–1894 Charlestown, Massachusetts|American, Wayland, Massachusetts 1808–1901 Crawford Notch, New Hampshire|American, active 1843–1863",,"Southworth, Albert Sands|Hawes, Josiah Johnson|Southworth and Hawes",American|American|American,1811 |1808 |1843,1894 |1901 |1863,ca. 1850,1848,1852,Daguerreotype,21.6 x 16.5 cm (8 1/2 x 6 1/2 in.),"Gift of I. N. Phelps Stokes, Edward S. Hawes, Alice Mary Hawes, and Marion Augusta Hawes, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268352,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.14.52,false,true,268353,Photographs,Photograph,[Elderly Man; Full Face],,,,,,Artist|Artist|Photography Studio,,Albert Sands Southworth|Josiah Johnson Hawes|Southworth and Hawes,"American, West Fairlee, Vermont 1811–1894 Charlestown, Massachusetts|American, Wayland, Massachusetts 1808–1901 Crawford Notch, New Hampshire|American, active 1843–1863",,"Southworth, Albert Sands|Hawes, Josiah Johnson|Southworth and Hawes",American|American|American,1811 |1808 |1843,1894 |1901 |1863,ca. 1850,1848,1852,Daguerreotype,21.6 x 16.5 cm (8 1/2 x 6 1/2 in.),"Gift of I. N. Phelps Stokes, Edward S. Hawes, Alice Mary Hawes, and Marion Augusta Hawes, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268353,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.14.53,false,true,268354,Photographs,Photograph,[Girl with Portrait of George Washingtion],,,,,,Artist|Artist|Photography Studio,,Albert Sands Southworth|Josiah Johnson Hawes|Southworth and Hawes,"American, West Fairlee, Vermont 1811–1894 Charlestown, Massachusetts|American, Wayland, Massachusetts 1808–1901 Crawford Notch, New Hampshire|American, active 1843–1863",,"Southworth, Albert Sands|Hawes, Josiah Johnson|Southworth and Hawes",American|American|American,1811 |1808 |1843,1894 |1901 |1863,ca. 1850,1848,1852,Daguerreotype,21.6 x 16.5 cm (8 1/2 x 6 1/2 in.),"Gift of I. N. Phelps Stokes, Edward S. Hawes, Alice Mary Hawes, and Marion Augusta Hawes, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268354,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.14.54,false,true,268355,Photographs,Photograph,Dr. John Collins Warren,,,,,,Artist|Artist|Photography Studio,,Albert Sands Southworth|Josiah Johnson Hawes|Southworth and Hawes,"American, West Fairlee, Vermont 1811–1894 Charlestown, Massachusetts|American, Wayland, Massachusetts 1808–1901 Crawford Notch, New Hampshire|American, active 1843–1863",,"Southworth, Albert Sands|Hawes, Josiah Johnson|Southworth and Hawes",American|American|American,1811 |1808 |1843,1894 |1901 |1863,ca. 1850,1848,1852,Daguerreotype,21.6 x 16.5 cm (8 1/2 x 6 1/2 in.),"Gift of I. N. Phelps Stokes, Edward S. Hawes, Alice Mary Hawes, and Marion Augusta Hawes, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268355,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.14.55,false,true,268356,Photographs,Photograph,[Man in Judge's Robes; Seated],,,,,,Artist|Artist|Photography Studio,,Albert Sands Southworth|Josiah Johnson Hawes|Southworth and Hawes,"American, West Fairlee, Vermont 1811–1894 Charlestown, Massachusetts|American, Wayland, Massachusetts 1808–1901 Crawford Notch, New Hampshire|American, active 1843–1863",,"Southworth, Albert Sands|Hawes, Josiah Johnson|Southworth and Hawes",American|American|American,1811 |1808 |1843,1894 |1901 |1863,ca. 1850,1848,1852,Daguerreotype,21.6 x 16.5 cm (8 1/2 x 6 1/2 in.),"Gift of I. N. Phelps Stokes, Edward S. Hawes, Alice Mary Hawes, and Marion Augusta Hawes, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268356,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.14.56,false,true,268357,Photographs,Photograph,[Students from the Emerson School for Girls],,,,,,Artist|Artist|Photography Studio,,Albert Sands Southworth|Josiah Johnson Hawes|Southworth and Hawes,"American, West Fairlee, Vermont 1811–1894 Charlestown, Massachusetts|American, Wayland, Massachusetts 1808–1901 Crawford Notch, New Hampshire|American, active 1843–1863",,"Southworth, Albert Sands|Hawes, Josiah Johnson|Southworth and Hawes",American|American|American,1811 |1808 |1843,1894 |1901 |1863,ca. 1850,1848,1852,Daguerreotype,16.5 x 21.6 cm (6 1/2 x 8 1/2 in.),"Gift of I. N. Phelps Stokes, Edward S. Hawes, Alice Mary Hawes, and Marion Augusta Hawes, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268357,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.14.57,false,true,268358,Photographs,Photograph,John L. Tucker,,,,,,Artist|Artist|Photography Studio,,Albert Sands Southworth|Josiah Johnson Hawes|Southworth and Hawes,"American, West Fairlee, Vermont 1811–1894 Charlestown, Massachusetts|American, Wayland, Massachusetts 1808–1901 Crawford Notch, New Hampshire|American, active 1843–1863",,"Southworth, Albert Sands|Hawes, Josiah Johnson|Southworth and Hawes",American|American|American,1811 |1808 |1843,1894 |1901 |1863,ca. 1850,1848,1852,Daguerreotype,33.4 x 41.3 cm (13 1/8 x 16 1/4 in.),"Gift of I. N. Phelps Stokes, Edward S. Hawes, Alice Mary Hawes, and Marion Augusta Hawes, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268358,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1997.382.40,false,true,282045,Photographs,Photograph,"Sculpture Gallery, Boston Athenaeum",,,,,,Artist|Artist|Photography Studio,,Albert Sands Southworth|Josiah Johnson Hawes|Southworth and Hawes,"American, West Fairlee, Vermont 1811–1894 Charlestown, Massachusetts|American, Wayland, Massachusetts 1808–1901 Crawford Notch, New Hampshire|American, active 1843–1863",,"Southworth, Albert Sands|Hawes, Josiah Johnson|Southworth and Hawes",American|American|American,1811 |1808 |1843,1894 |1901 |1863,ca. 1855,1853,1857,Daguerreotype,visible: 18.6 x 13.7 cm (7 5/16 x 5 3/8 in.),"The Rubel Collection, Purchase, Ann Tenenbaum and Thomas H. Lee and Lila Acheson Wallace Gifts, 1997",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/282045,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.400.39,false,true,291792,Photographs,Daguerreotype,[Young Woman with Hair Styled in Two Buns],,,,,,Artist|Artist|Photography Studio,Attributed to|Attributed to,Albert Sands Southworth|Josiah Johnson Hawes|Southworth and Hawes,"American, West Fairlee, Vermont 1811–1894 Charlestown, Massachusetts|American, Wayland, Massachusetts 1808–1901 Crawford Notch, New Hampshire|American, active 1843–1863",,"Southworth, Albert Sands|Hawes, Josiah Johnson|Southworth and Hawes",American|American|American,1811 |1808 |1843,1894 |1901 |1863,1850s,1850,1859,Daguerreotype,Image: 12.5 x 9.3 cm (4 15/16 x 3 11/16 in.) Plate: 16.5 x 13.7 cm (6 1/2 x 5 3/8 in.) Case: 1.9 x 18.1 x 15.2 cm (3/4 x 7 1/8 x 6 in.),"Bequest of Herbert Mitchell, 2008",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/291792,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.14.20,false,true,268318,Photographs,Photograph,Miss Hodges of Salem,,,,,,Photography Studio|Artist|Artist,,Southworth and Hawes|Josiah Johnson Hawes|Albert Sands Southworth,"American, active 1843–1863|American, Wayland, Massachusetts 1808–1901 Crawford Notch, New Hampshire|American, West Fairlee, Vermont 1811–1894 Charlestown, Massachusetts",,"Southworth and Hawes|Hawes, Josiah Johnson|Southworth, Albert Sands",American|American|American,1843 |1808 |1811,1863 |1901 |1894,ca. 1850,1848,1852,Daguerreotype,21.6 x 16.5 cm (8 1/2 x 6 1/2 in.),"Gift of I. N. Phelps Stokes, Edward S. Hawes, Alice Mary Hawes, and Marion Augusta Hawes, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268318,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.14.1,false,true,268306,Photographs,Photograph,Donald McKay,,,,,,Photography Studio|Artist|Artist,,Southworth and Hawes|Albert Sands Southworth|Josiah Johnson Hawes,"American, active 1843–1863|American, West Fairlee, Vermont 1811–1894 Charlestown, Massachusetts|American, Wayland, Massachusetts 1808–1901 Crawford Notch, New Hampshire",,"Southworth and Hawes|Southworth, Albert Sands|Hawes, Josiah Johnson",American|American|American,1843 |1811 |1808,1863 |1894 |1901,ca. 1850–55,1850,1855,Daguerreotype,21.6 x 16.5cm (8 1/2 x 6 1/2in.),"Gift of I. N. Phelps Stokes, Edward S. Hawes, Alice Mary Hawes, and Marion Augusta Hawes, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268306,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.14.19,false,true,268316,Photographs,Photograph,Robert Charles Winthrop,,,,,,Photography Studio|Artist|Artist,,Southworth and Hawes|Albert Sands Southworth|Josiah Johnson Hawes,"American, active 1843–1863|American, West Fairlee, Vermont 1811–1894 Charlestown, Massachusetts|American, Wayland, Massachusetts 1808–1901 Crawford Notch, New Hampshire",,"Southworth and Hawes|Southworth, Albert Sands|Hawes, Josiah Johnson",American|American|American,1843 |1811 |1808,1863 |1894 |1901,ca. 1850,1848,1852,Daguerreotype,21.6 x 16.5 cm (8 1/2 x 6 1/2 in.),"Gift of I. N. Phelps Stokes, Edward S. Hawes, Alice Mary Hawes, and Marion Augusta Hawes, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268316,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.14.26,false,true,268324,Photographs,Photograph,Elias Howe,,,,,,Photography Studio|Artist|Artist,,Southworth and Hawes|Albert Sands Southworth|Josiah Johnson Hawes,"American, active 1843–1863|American, West Fairlee, Vermont 1811–1894 Charlestown, Massachusetts|American, Wayland, Massachusetts 1808–1901 Crawford Notch, New Hampshire",,"Southworth and Hawes|Southworth, Albert Sands|Hawes, Josiah Johnson",American|American|American,1843 |1811 |1808,1863 |1894 |1901,ca. 1850,1848,1852,Daguerreotype,14.0 x 10.8 cm (5 1/2 x 4 1/4 in.),"Gift of I. N. Phelps Stokes, Edward S. Hawes, Alice Mary Hawes, and Marion Augusta Hawes, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268324,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.14.28,false,true,268326,Photographs,Photograph,Charles Sumner,,,,,,Photography Studio|Artist|Artist,,Southworth and Hawes|Albert Sands Southworth|Josiah Johnson Hawes,"American, active 1843–1863|American, West Fairlee, Vermont 1811–1894 Charlestown, Massachusetts|American, Wayland, Massachusetts 1808–1901 Crawford Notch, New Hampshire",,"Southworth and Hawes|Southworth, Albert Sands|Hawes, Josiah Johnson",American|American|American,1843 |1811 |1808,1863 |1894 |1901,ca. 1850,1848,1852,Daguerreotype,21.6 x 16.5 cm (8 1/2 x 6 1/2 in.),"Gift of I. N. Phelps Stokes, Edward S. Hawes, Alice Mary Hawes, and Marion Augusta Hawes, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268326,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2011.556.1,false,true,301969,Photographs,Photograph,[Augusta Hawes at Four Years Old],,,,,,Photography Studio|Artist|Artist,,Southworth and Hawes|Albert Sands Southworth|Josiah Johnson Hawes,"American, active 1843–1863|American, West Fairlee, Vermont 1811–1894 Charlestown, Massachusetts|American, Wayland, Massachusetts 1808–1901 Crawford Notch, New Hampshire",,"Southworth and Hawes|Southworth, Albert Sands|Hawes, Josiah Johnson",American|American|American,1843 |1811 |1808,1863 |1894 |1901,1850s,1850,1859,Daguerreotype,Plate: 12.7 x 10.7 cm (5 x 4 3/16 in.),"Gift of Isaac Lagnado, in honor of Director Thomas P. Campbell, 2011",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/301969,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1982.1182.813–.820,false,true,288237,Photographs,Stereographs,"[Group of 8 Early Stereograph Views of British Monuments, Memorials, and Tombs]",,,,,,Artist|Artist|Artist|Person in Photograph|Publisher|Person in Photograph|Person in Photograph,,Lennie|Unknown|Taylor|Samuel Johnson|Stereoscopic Gems|Sir Walter Scott|Earl Richard Beauchamp,"British, born Scotland|British|British|British, Lichfield, Staffordshire 1709–1784 London|British, Edinburgh, Scotland 1771–1832 Abbotsford, Scotland|British, 1382–1439",,"Lennie|Unknown|Taylor, Mr.|Johnson, Samuel|Stereoscopic Gems|Scott, Walter, Sir|Beauchamp, Richard Earl","British, Scottish|British|British|British, Scottish|British",1709 |1771 |1382,1784 |1832 |1439,1850s–1910s,1850,1919,Albumen silver prints,Mounts approximately: 8.4 x 17.1 cm (3 5/16 x 6 3/4 in.) to 8.5 x 17.8 cm (3 3/8 x 7 in.),"Gift of Weston J. Naef, in memory of Kathleen W. Naef and Weston J. Naef Sr., 1982",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288237,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1982.1182.647–.658,false,true,288154,Photographs,Stereographs,"[Group of 12 Stereograph Views of Celebrities, Including Popes and Presidents]",,,,,,Publisher|Photography Studio|Person in Photograph|Publisher|Publisher|Person in Photograph|Person in Photograph|Person in Photograph|Person in Photograph|Publisher|Artist|Person in Photograph|Publisher|Publisher,,Underwood & Underwood|Sun Sculpture Works and Studios|Pope Pius X|C. H. Graves|Universal Photo Art Co.|William McKinley|President John Calvin Coolidge Jr.|First Lady Grace Anna Goodhue Coolidge|Major General Adna Romanza Chaffee|H. C. White Company|Alfred Hewitt|First Lady Ida Saxton McKinley|William Hibbert|Keystone View Company,"American|American|Italian, 1835–1914|American|American, 1843–1901|American, 1872–1933|American, 1879–1957|American, 1842–1941|American|American, 1847–1907|American",,"Underwood & Underwood|Sun Sculpture Works and Studios|Pius, X Pope|Graves, C. H.|Universal Photo Art Co.|McKinley, William|Coolidge, John Calvin Jr. President|Goodhue, Coolidge Grace Anna First Lady|Chaffee, Adna Romanza Major General|H. C. White Company|Hewitt, Alfred|McKinley, Ida Saxton First Lady|Hibbert, William|Keystone View Company",American|American|Italian|American|American|American|American|American|American|American|American,1835 |1843 |1872 |1879 |1842 |1847,1914 |1901 |1933 |1957 |1941 |1907,1850s–1910s,1850,1919,Albumen silver prints,Mounts approximately: 8.9 x 17.8 cm (3 1/2 x 7 in.),"Gift of Weston J. Naef, in memory of Kathleen W. Naef and Weston J. Naef Sr., 1982",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288154,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.14.34,false,true,268333,Photographs,Photograph,John Quincy Adams,,,,,,Artist|Artist|Photography Studio|Artist,After,Josiah Johnson Hawes|Albert Sands Southworth|Southworth and Hawes|Philip Haas,"American, Wayland, Massachusetts 1808–1901 Crawford Notch, New Hampshire|American, West Fairlee, Vermont 1811–1894 Charlestown, Massachusetts|American, active 1843–1863|American",,"Hawes, Josiah Johnson|Southworth, Albert Sands|Southworth and Hawes|Haas, Philip",American|American|American|American,1808 |1811 |1843,1901 |1894 |1863,ca. 1850,1848,1852,Daguerreotype,12.0 x 9.0 cm (4 3/4 x 3 9/16 in.),"Gift of I. N. Phelps Stokes, Edward S. Hawes, Alice Mary Hawes, and Marion Augusta Hawes, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268333,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1981.1229.13,false,true,263161,Photographs,Photograph,Afghans,,,,,,Artist|Artist|Photography Studio|Person in Photograph,Person in photograph,David Octavius Hill|Robert Adamson|Hill and Adamson|Edward William Lane,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, active 1843–1848|British, Hereford 1801–1876 Worthing",,"Hill, David Octavius|Adamson, Robert|Hill and Adamson|Lane, Edward William","British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1843 |1801,1870 |1848 |1848 |1876,1843,1843,1843,Salted paper print from paper negative,,"Robert O. Dougan Collection, Gift of Warner Communications Inc., 1981",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/263161,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1982.1182.1031–.1043,false,true,288297,Photographs,Stereographs,[Group of 13 Stereograph Views of Families and Children],,,,,,Publisher|Artist|Publisher|Publisher|Artist|Publisher|Artist|Artist|Publisher|Publisher|Publisher|Artist|Publisher|Artist|Artist,,Keystone View Company|Benneville Lloyd Singley|Group Series|Universal View Co.|William H. Rau|Charles Moody|C. L. Howe|Kilburn Brothers|James M. Davis|H. C. White Company|Littleton View Company|F. G. Weller|Underwood & Underwood|Unknown|Unknown,"American, Union Township, Pennsylvania 1864–1938 Meadville, Pennsylvania|American|American, 1855–1920|American|American|American, active ca. 1865–1890|American|American|American|American",,"Keystone View Company|Singley, Benneville Lloyd|Group Series|Universal View Co.|Rau, William H.|Moody, Charles|Howe, C. L.|Kilburn Brothers|Davis, James M.|H. C. White Company|Littleton View Company|Weller., F. G.|Underwood & Underwood|Unknown|Unknown",American|American|American|American|American|American|American|American|American,1864 |1855 |1863,1938 |1920 |1892,1850s–1910s,1850,1919,Albumen silver prints,Mounts approximately: 8.2 x 17.1 cm (3 1/4 x 6 3/4 in.) to 8.9 x 17.8 cm (3 1/2 x 7 in.),"Gift of Weston J. Naef, in memory of Kathleen W. Naef and Weston J. Naef Sr., 1982",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288297,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.14.44,false,true,268344,Photographs,Photograph,William Henry Harrison,,,,,,Artist|Artist|Photography Studio|Artist,,Albert Sands Southworth|Josiah Johnson Hawes|Southworth and Hawes|Albert Gallatin Hoit,"American, West Fairlee, Vermont 1811–1894 Charlestown, Massachusetts|American, Wayland, Massachusetts 1808–1901 Crawford Notch, New Hampshire|American, active 1843–1863|American, 1809–1856",,"Southworth, Albert Sands|Hawes, Josiah Johnson|Southworth and Hawes|Hoit, Albert Gallatin",American|American|American|American,1811 |1808 |1843 |1809,1894 |1901 |1863 |1856,ca. 1850,1848,1852,Daguerreotype,8.3 x 7.0 cm (3 1/4 x 2 3/4 in.),"Gift of I. N. Phelps Stokes, Edward S. Hawes, Alice Mary Hawes, and Marion Augusta Hawes, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268344,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1982.1182.1951–.2016,false,true,288316,Photographs,Stereographs,[Group of 66 Stereograph Views of the 1893 Chicago World's Fair and Columbian Exposition],,,,,,Artist|Publisher|Artist|Subject|Artist|Artist|Person in Photograph|Publisher|Publisher|Photography Studio|Publisher,,Strohmeyer & Wyman|Underwood & Underwood|Kilburn Brothers|James M. Davis|Charles Dudley Arnold|Benneville Lloyd Singley|Stephen Grover Cleveland|Gust. Holmquist|Webster & Albee|Sun Sculpture Works and Studios|Keystone View Company,"American|American|American, active ca. 1865–1890|American, 1844–1927|American, Union Township, Pennsylvania 1864–1938 Meadville, Pennsylvania|American, 1837–1908|American|American|American",,"Strohmeyer & Wyman|Underwood & Underwood|Kilburn Brothers|Davis, James M.|Arnold, Charles D.|Singley, Benneville Lloyd|Cleveland, Stephen Grover|Holmquist, Gust.|Webster & Albee|Sun Sculpture Works and Studios|Keystone View Company",American|American|American|American|American|American|American|American|American,1863 |1844 |1864 |1837,1892 |1927 |1938 |1908,1850s–1910s,1850,1919,Albumen silver prints,Mounts approximately: 8.7 x 17.5 cm (3 7/16 x 6 7/8 in.) to 10.6 x 17.8 cm (4 3/16 x 7 in.),"Gift of Weston J. Naef, in memory of Kathleen W. Naef and Weston J. Naef Sr., 1982",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288316,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1997.382.9,false,true,282012,Photographs,Photograph,Sir David Brewster,,,,,,Artist|Artist|Person in Photograph|Photography Studio,,David Octavius Hill|Robert Adamson|Sir David Brewster|Hill and Adamson,"British, Perth, Scotland 1802–1870 Edinburgh, Scotland|British, St. Andrews, Scotland 1821–1848 St. Andrews, Scotland|British, Jedburgh, Scotland 1781–1868 Melrose|British, active 1843–1848",,"Hill, David Octavius|Adamson, Robert|Brewster, David, Sir|Hill and Adamson","British, Scottish|British, Scottish|British, Scottish|British, Scottish",1802 |1821 |1781 |1843,1870 |1848 |1868 |1848,ca. 1844,1842,1846,Salted paper print from paper negative,20.2 x 15.1 cm (7 15/16 x 5 15/16 in. ),"The Rubel Collection, Purchase, Lila Acheson Wallace Gift, 1997",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/282012,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1982.1182.208–.255,false,true,288120,Photographs,Stereographs,[Group of 48 Stereograph Views of Arizona and the Surrounding Area],,,,,,Publisher|Publisher|Photography Studio|Artist|Publisher|Publisher|Publisher|Publisher|Artist|Publisher|Artist|Publisher|Artist|Publisher,,H. C. White Company|Underwood & Underwood|Sun Sculpture Works and Studios|Kilburn Brothers|Edward Kilburn|American Stereoscopic Company|Quaker Oats Company|Continent Stereoscopic Company|Benneville Lloyd Singley|New H Series|W. S. Conant|Standard Series|Unknown|Keystone View Company,"American|American|American|American, active ca. 1865–1890|American, 1830–1884|American|American|New York|American, Union Township, Pennsylvania 1864–1938 Meadville, Pennsylvania|American|American",,"H. C. White Company|Underwood & Underwood|Sun Sculpture Works and Studios|Kilburn Brothers|Kilburn, Edward|American Stereoscopic Company|Quaker Oats Company|Continent Stereoscopic Company|Singley, Benneville Lloyd|New H Series|Conant, W. S.|Standard Series|Unknown|Keystone View Company",American|American|American|American|American|American|American|New York|American|American|American,1863 |1830 |1864,1892 |1884 |1938,1880s–1910s,1880,1919,Albumen silver prints,Mounts approximately: 8.9 x 17.8 cm (3 1/2 x 7 in.) to 10.6 x 17.4 cm (4 3/16 x 6 7/8 in.),"Gift of Weston J. Naef, in memory of Kathleen W. Naef and Weston J. Naef Sr., 1982",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288120,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1982.1182.1075–.1129,false,true,288301,Photographs,Stereographs,[Group of 55 Stereograph Views of Groups of Children],,,,,,Artist|Publisher|Publisher|Publisher|Artist|Artist|Artist|Publisher|Publisher|Publisher|Artist|Artist|Publisher|Artist|Publisher|Artist|Publisher|Artist|Publisher|Publisher|Publisher|Publisher|Artist|Artist|Publisher,,T. W. Ingersoll|Comic Series|Popular Series|Comics and Groups|R. B. Lewis|W. E. Sparrow|F. G. Weller|Underwood & Underwood|Littleton View Company|Union View Company|Strohmeyer & Wyman|J. P. King|E. & H. T. Anthony|Benneville Lloyd Singley|Universal Photo Art Co.|C. H. Graves|Universal View Co.|William H. Rau|Hegger|Charles Moody|European and American Views|S. C. Northrop|Unknown|Unknown|Keystone View Company,"American|American|American|American|American|American|American|American|American, Union Township, Pennsylvania 1864–1938 Meadville, Pennsylvania|American|American|American, 1855–1920|American|American|American|American",,"Ingersoll, T. W.|Comic Series|Popular Series|Comics and Groups|Lewis, R. B.|Sparrow, W. E.|Weller., F. G.|Underwood & Underwood|Littleton View Company|Union View Company|Strohmeyer & Wyman|King, J. P.|E. & H. T. Anthony|Singley, Benneville Lloyd|Universal Photo Art Co.|Graves, C. H.|Universal View Co.|Rau, William H.|Hegger|Moody, Charles|European and American Views|Northrop, S. C.|Unknown|Unknown|Keystone View Company",American|American|American|American|American|American|American|American|American|American|American|American|American|American|American,1864 |1855,1938 |1920,1850s–1910s,1850,1919,Albumen silver prints,Mounts approximately: 8.7 x 17.4 cm (3 7/16 x 6 7/8 in.) to 9.9 x 17.8 cm (3 7/8 x 7 in.),"Gift of Weston J. Naef, in memory of Kathleen W. Naef and Weston J. Naef Sr., 1982",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288301,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.1221,false,true,286616,Photographs,Photograph,"[President Abraham Lincoln, Major General John A. McClernand (right), and E. J. Allen (Allan Pinkerton, left), Chief of the Secret Service of the United States, at Secret Service Department, Headquarters Army of the Potomac, near Antietam, Maryland]",,,,,,Artist|Person in Photograph|Person in Photograph|Person in Photograph,,Alexander Gardner|Abraham Lincoln|John Alexander McClernand|Allan Pinkerton,"American, Glasgow, Scotland 1821–1882 Washington, D.C.|American, Hardin County, Kentucky 1809–1865 Washington, D.C.|American, Breckinridge County, Kentucky 1812–1900 Springfield, Illinois|American, born Scotland, Glasgow 1819–1884 Chicago",,"Gardner, Alexander|Lincoln, Abraham|McClernand, John Alexander|Pinkerton, Allan","American, Scottish|American|American|American, born Scotland",1821 |1809 |1812-05-30|1819-08-25,1882 |1865 |1900-09-20|1884-07-01,"October 4, 1862",1862,1862,Albumen silver print from glass negative,Image: 22.4 x 18 cm (8 13/16 x 7 1/16 in.) Mount: 29.2 x 20.7 cm (11 1/2 x 8 1/8 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286616,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.1220,false,true,286615,Photographs,Photograph,"[President Abraham Lincoln, Major General John A. McClernand (right), and E. J. Allen (Allan Pinkerton, left), Chief of the Secret Service of the United States, at Secret Service Department, Headquarters Army of the Potomac, near Antietam, Maryland]",,,,,,Artist|Person in Photograph|Person in Photograph|Person in Photograph,,Alexander Gardner|Allan Pinkerton|Abraham Lincoln|John Alexander McClernand,"American, Glasgow, Scotland 1821–1882 Washington, D.C.|American, born Scotland, Glasgow 1819–1884 Chicago|American, Hardin County, Kentucky 1809–1865 Washington, D.C.|American, Breckinridge County, Kentucky 1812–1900 Springfield, Illinois",,"Gardner, Alexander|Pinkerton, Allan|Lincoln, Abraham|McClernand, John Alexander","American, Scottish|American, born Scotland|American|American",1821 |1819-08-25|1809 |1812-05-30,1882 |1884-07-01|1865 |1900-09-20,"October 3, 1862",1862,1862,Albumen silver print from glass negative,Image: 20 x 18.5 cm (7 7/8 x 7 5/16 in.) Mount: 22.8 x 21.3 cm (9 x 8 3/8 in.) Mount: 34.1 x 27 cm (13 7/16 x 10 5/8 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286615,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2010.443,false,true,294320,Photographs,Photograph,[Nine Portraits in Original Passe-Partout],,,,,,Artist|Person in Photograph|Person in Photograph|Person in Photograph,,James William Bailey|Charles-Louis-Napoleon Bonaparte|William Ewart Gladstone|Albert Edward Prince of Wales,"British|French, Paris 1808–1873 Chislehurst, Kent|British, 1841–1910",,"Bailey, James William|Bonaparte, Charles-Louis-Napoleon|Gladstone, William Ewart|Edward, Albert Prince of Wales",British|French|British,1808 |1809 |1841,1873 |1898 |1910,1880s,1880,1889,Albumen silver prints from glass negatives with applied color,Frame: 34.9 x 34.9 cm (13 3/4 x 13 3/4 in.) Image: 4 x 3.5 cm (1 9/16 x 1 3/8 in.) Image: 7.7 x 6 cm (3 1/16 x 2 3/8 in.),"Funds from various donors, 2010",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/294320,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1991.1073.105,false,true,266362,Photographs,Album,Views and Costumes of Japan,,,,,,Artist,,Raimund von Stillfried,"Austrian, 1839–1911",,"Stillfried, Raimond von",Austrian,1839,1911,1860s,1860,1869,Albumen silver prints,,"Rogers Fund, 1957, transferred from the Library",,,,,,,,,,,,Albums,,http://www.metmuseum.org/art/collection/search/266362,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2006.214 (1-32),false,true,287306,Photographs,Micrograph,Mikroskopisch-Photographischer Atlas der Harnsedimente,,,,,,Artist,,Robert Ultzmann,"Austrian, 1842–1889",,"Ultzmann, Robert",Austrian,1842,1889,1869,1869,1869,Albumen silver prints,Album: 30.5 x 47 x 6.4 cm (12 x 18 1/2 x 2 1/2 in.),"Joyce F. Menschel Photography Library Fund, 2006",,,,,,,,,,,,Albums,,http://www.metmuseum.org/art/collection/search/287306,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.1098.6,false,true,631030,Photographs,Paper Negative,"David Maitland Makgill Crichton, Rankeillour",,,,,,Artist,,Hill and Adamson,"British, active 1843–1848",,Hill and Adamson,"British, Scottish",1843,1848,1843–47,1843,1847,Waxed paper negative,Image: 8 3/8 × 6 3/16 in. (21.3 × 15.7 cm),"Gift of Joyce F. Menschel, 2013",,,,,,,,,,,,Negatives,,http://www.metmuseum.org/art/collection/search/631030,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -48.105.69,false,true,269197,Photographs,Photomechanical print,A Stiff Pull,,,,,,Artist,,Peter Henry Emerson,"British, born Cuba, 1856–1936",,"Emerson, Peter Henry","British, born Cuba",1856,1936,"1880s, printed 1887",1880,1889,Photogravure,20.8 x 28.9 cm. (8 3/16 x 11 3/8 in.),"David Hunter McAlpin Fund, 1948",,,,,,,,,,,,Photographs|Prints,,http://www.metmuseum.org/art/collection/search/269197,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.68,false,true,290463,Photographs,Paper negative,Village de Murols,,,,,,Artist,,Édouard Baldus,"French, born Prussia, 1813–1889",,"Baldus, Édouard","French, born Prussia",1813,1889,1854,1854,1854,Paper negative,Image: 33.9 x 43.9 cm (13 3/8 x 17 5/16 in.) Sheet: 34.4 x 44.5 cm (13 9/16 x 17 1/2 in.),"The Horace W. Goldsmith Foundation Fund, through Joyce and Robert Menschel, 2009",,,,,,,,,,,,Negatives,,http://www.metmuseum.org/art/collection/search/290463,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.372.1–.32,false,true,286010,Photographs,Album,[Album of photographs],,,,,,Artist,,Louis-Pierre-Théophile Dubois de Nehaut,"French, active Belgium, 1799–1872",,"Dubois de Nehaut, Louis-Pierre-Théophile",Belgian,1799,1872,1854–56,1854,1856,"32 salted paper prints, waxed salted paper prints, and albumen silver prints from paper and glass negatives",5 3/4 x 7 5/8 to 9 1/4 x 12,"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Albums,,http://www.metmuseum.org/art/collection/search/286010,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1987.1100.47,true,true,265543,Photographs,Photograph,Runner in the City,,,,,,Artist,,El Lissitzky,"Russian, Pochinok 1890–1941 Moscow",,"Lissitzky, El",Russian,1890,1941,ca. 1926,1924,1928,Gelatin silver print,13.1 x 12.8 cm (5 3/16 x 5 1/16 in.),"Ford Motor Company Collection, Gift of Ford Motor Company and John C. Waddell, 1987",,,,,,,,,,,,Photographs|Collages,,http://www.metmuseum.org/art/collection/search/265543,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.551.1,false,true,283188,Photographs,Album,"Rogues, a Study of Characters",,,,,,Artist,,Samuel G. Szabó,"Hungarian, active America ca. 1854–61",,"Szabó, Samuel G.",Hungarian,1854,0061,1857,1850,1860,Salted paper prints from glass negatives,From 8.8 x 6.6 cm (3 7/16 x 2 5/8 in.) to 11.5 x 8.8 cm (4 1/2 x 3 7/16 in.),"Gilman Collection, Purchase, Ann Tenenbaum and Thomas H. Lee Gift, 2005",,,,,,,,,,,,Albums,,http://www.metmuseum.org/art/collection/search/283188,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.3,false,true,283074,Photographs,Negative; Photograph,[Young Man],,,,,,Artist,,Calvert Richard Jones,"British, Swansea, Wales 1802–1877 Bath, England",,"Jones, Calvert Richard","British, Welsh",1802,1877,late 1840s,1846,1849,Paper negative,20.9 x 15.8 cm (8 1/4 x 6 1/4 in.),"Gilman Collection, Purchase, Joseph M. Cohen Gift, 2005",,,,,,,,,,,,Negatives,,http://www.metmuseum.org/art/collection/search/283074,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.43.243,false,true,267653,Photographs,Photomechanical print,The Church or the World,,,,,,Artist,,James Craig Annan,"British, Hamilton, South Lanarkshire, Scotland 1864–1946",,"Annan, James Craig","British, Scottish",1864,1946,1893,1893,1893,Photogravure,10.5 x 12.0 cm. (4 1/8 x 4 3/4 in.),"Alfred Stieglitz Collection, 1933",,,,,,,,,,,,Photographs|Prints,,http://www.metmuseum.org/art/collection/search/267653,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -49.55.268,false,true,269385,Photographs,Photomechanical print,Toledo,,,,,,Artist,,James Craig Annan,"British, Hamilton, South Lanarkshire, Scotland 1864–1946",,"Annan, James Craig","British, Scottish",1864,1946,1914,1914,1914,Photogravure,19.9 x 13.4 cm. (7 13/16 x 5 1/4 in.),"Alfred Stieglitz Collection, 1949",,,,,,,,,,,,Photographs|Prints,,http://www.metmuseum.org/art/collection/search/269385,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -49.55.274,false,true,269392,Photographs,Photomechanical print,"The Riva Schiavoni, Venice",,,,,,Artist,,James Craig Annan,"British, Hamilton, South Lanarkshire, Scotland 1864–1946",,"Annan, James Craig","British, Scottish",1864,1946,1894,1894,1894,Photogravure,14.3 x 19.9 cm. (5 5/8 x 7 13/16 in.),"Alfred Stieglitz Collection, 1949",,,,,,,,,,,,Photographs|Prints,,http://www.metmuseum.org/art/collection/search/269392,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -52.524.3.1–.12,false,true,269582,Photographs,Photographically illustrated book,"Alfred Tennyson's Idylls of the King, and other Poems",,,,,,Artist,,Julia Margaret Cameron,"British (born India), Calcutta 1815–1879 Kalutara, Ceylon",,"Cameron, Julia Margaret","British, born India",1815,1815,1874,1874,1874,Albumen silver prints,"45.4 x 35.5 x 2.4 cm (17 7/8 x 14 x 15/16 in.), closed","David Hunter McAlpin Fund, 1952",,,,,,,,,,,,Books,,http://www.metmuseum.org/art/collection/search/269582,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -57.634.16,false,true,269947,Photographs,Autochrome,[Pan-Pacific International Exposition],,,,,,Artist,,Arnold Genthe,"American (born Germany), Berlin 1869–1942 New Milford, Connecticut",,"Genthe, Arnold","American, born Germany",1869,1942,1915,1915,1915,Autochrome,12.7 x 17.9 cm (5 x 7 1/16 in.),"Gift of Mrs. Robert Aitken, 1957",,,,,,,,,,,,Transparencies,,http://www.metmuseum.org/art/collection/search/269947,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1988.1134.1,false,true,266025,Photographs,Waxed paper negative,"The Diwan-i Khas from the Mussaman Burj, Agra Palace",,,,,,Artist,,John Murray,"British, Blackhouse, Aberdeenshire, Scotland 1809–1898 Sheringham, Norfolk county, England",,"Murray, John","British, Scottish",1809,1898,1862–64,1862,1864,Waxed paper negative with applied media,37.1 x 46.4cm (14 5/8 x 18 1/4in.) Frame: 68.7 x 122.6 cm (27 1/16 x 48 1/4 in.) (Framed with 1988.1134.2),"Purchase, Cynthia Hazen Polsky Gift, 1988",,,,,,,,,,,,Negatives,,http://www.metmuseum.org/art/collection/search/266025,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1997.382.57,false,true,282068,Photographs,Waxed paper negative,Taj Mahal and Gardens,,,,,,Artist,,John Murray,"British, Blackhouse, Aberdeenshire, Scotland 1809–1898 Sheringham, Norfolk county, England",,"Murray, John","British, Scottish",1809,1898,ca. 1855,1853,1857,Waxed paper negative,38.5 x 47.2 cm (15 3/16 x 18 9/16 in.),"The Rubel Collection, Purchase, Anonymous Gift and Cynthia Hazen Polsky Gift, 1997",,,,,,,,,,,,Negatives,,http://www.metmuseum.org/art/collection/search/282068,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.946,false,true,287604,Photographs,Negative; Photograph,Suttee Ghat Cawnpore,,,,,,Artist,,John Murray,"British, Blackhouse, Aberdeenshire, Scotland 1809–1898 Sheringham, Norfolk county, England",,"Murray, John","British, Scottish",1809,1898,1858,1858,1858,Waxed paper negative,Image: 38 x 48 cm (14 15/16 x 18 7/8 in.),"Gilman Collection, Purchase, Cynthia Hazen Polsky Gift, 2005",,,,,,,,,,,,Negatives,,http://www.metmuseum.org/art/collection/search/287604,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.1224,false,true,305839,Photographs,Print,Planning the Capture of Booth and Harold,,,,,,Artist,After,Alexander Gardner,"American, Glasgow, Scotland 1821–1882 Washington, D.C.",,"Gardner, Alexander","American, Scottish",1821,1882,1865,1865,1865,Woodcut,,"Gilman Collection, Purchase, Ann Tenenbaum and Thomas H. Lee Gift, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/305839,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1973.594.55,false,true,260915,Photographs,Photograph,"Palais de Gézyret, Pavillon Exterieur",,,,,,Artist,,J. Pascal Sébah,Turkish,,"Sébah, J. Pascal",Turkish,,1890,1870s,1870,1879,Albumen silver print from glass negative,20.0 x 26.7 cm (7 7/8 x 10 1/2 in.),"The Elisha Whittelsey Collection, The Elisha Whittelsey Fund, 1973",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260915,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -52.524.12,false,true,269572,Photographs,Photograph,Chillon,,,,,,Artist,,John Joscelyn Coghill,"Irish, 1826–1905",,"Coghill, John Joscelyn",Irish,1826,1905,1855,1855,1855,Albumen silver print from glass negative,Approx. 15.2 x 20.3 cm (6 x 8 in.),"David Hunter McAlpin Fund, 1952",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269572,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -52.524.13,false,true,269573,Photographs,Photograph,Swiss Glacier,,,,,,Artist,,John Joscelyn Coghill,"Irish, 1826–1905",,"Coghill, John Joscelyn",Irish,1826,1905,1850s,1850,1859,Albumen silver print from glass negative,Approx. 20.3 x 15.2 cm (8 x 6 in.),"David Hunter McAlpin Fund, 1952",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269573,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -52.524.14,false,true,269574,Photographs,Photograph,View in Switzerland,,,,,,Artist,,John Joscelyn Coghill,"Irish, 1826–1905",,"Coghill, John Joscelyn",Irish,1826,1905,1850s,1850,1859,Albumen silver print from glass negative,Approx. 15.2 x 20.3 cm (6 x 8 in.),"David Hunter McAlpin Fund, 1952",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269574,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -52.524.15,false,true,269575,Photographs,Photograph,Heidelberg,,,,,,Artist,,John Joscelyn Coghill,"Irish, 1826–1905",,"Coghill, John Joscelyn",Irish,1826,1905,ca. 1855,1853,1857,Albumen silver print from glass negative,Approx. 15.2 x 20.3 cm (6 x 8 in.),"David Hunter McAlpin Fund, 1952",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269575,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -63.606.1.7,false,true,270859,Photographs,Photograph,The Castle of Chillon,,,,,,Artist,,John Joscelyn Coghill,"Irish, 1826–1905",,"Coghill, John Joscelyn",Irish,1826,1905,1855,1855,1855,Albumen silver print,,"The Elisha Whittelsey Collection, The Elisha Whittelsey Fund, 1963",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/270859,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.112,false,true,685454,Photographs,Carte-de-visite,[Alexander Calame],,,,,,Artist,,Vuagnat,"Swiss, active 1860s",,Vuagnat,Swiss,1859,1870,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/685454,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.1041,false,true,266108,Photographs,Photograph,"[Doorway of Predikheevenkerk, Gent, Belgium]",,,,,,Artist,,Charles D'Hoy,"Belgian, 1823–1895",,"D'Hoy, Charles",Belgian,1823,1895,ca. 1858,1856,1860,Albumen silver print from glass negative,23.0 x 27.9 cm. (9 1/16 x 11 in.),"Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 1989",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/266108,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.652,false,true,286263,Photographs,X-Ray,Le pied à travers la chaussure,,,,,,Artist,,Dr. Henri van Heurck,"Belgian, 1838–1909",,"Van Heurck, Dr. Henri",Belgian,1838,1909,1896,1896,1896,Gelatin silver print,Image: 16.2 × 12.1 cm (6 3/8 × 4 3/4 in.) Mount: 18.4 × 13.4 cm (7 1/4 × 5 1/4 in.),"Gilman Collection, Purchase, Denise and Andrew Saul Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286263,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.653,false,true,286772,Photographs,X-Ray,[X-Ray of the Mummy of a Raptor],,,,,,Artist,,Dr. Henri van Heurck,"Belgian, 1838–1909",,"Van Heurck, Dr. Henri",Belgian,1838,1909,1896,1896,1896,Gelatin silver print,Image: 6 3/4 × 4 3/4 in. (17.1 × 12.1 cm),"Gilman Collection, Purchase, Denise and Andrew Saul Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286772,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.654,false,true,287290,Photographs,X-Ray,[X-Ray of a Box Compasses],,,,,,Artist,,Dr. Henri van Heurck,"Belgian, 1838–1909",,"Van Heurck, Dr. Henri",Belgian,1838,1909,1896,1896,1896,Gelatin silver print,Image: 17.2 x 11.9 cm (6 3/4 x 4 11/16 in.) Mount: 18 x 13 cm (7 1/16 x 5 1/8 in.),"Gilman Collection, Purchase, Denise and Andrew Saul Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287290,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.494.17,false,true,289289,Photographs,Photograph,Piled Stone Mountain Near Sing Chang,,,,,,Artist,,Lai Fong,"Chinese, 1839–1890",,"Lai, Fong",Chinese,1839,1890,ca. 1869,1869,1869,Albumen silver print from glass negative,Image: 8 1/8 × 11 5/16 in. (20.7 × 28.7 cm),"Gilman Collection, Purchase, Robert Rosenkranz Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/289289,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.494.26,false,true,289298,Photographs,Photograph,Hisiu Peak,,,,,,Artist,,Lai Fong,"Chinese, 1839–1890",,"Lai, Fong",Chinese,1839,1890,ca. 1869,1869,1869,Albumen silver print from glass negative,Image: 9 in. × 11 7/16 in. (22.8 × 29 cm),"Gilman Collection, Purchase, Robert Rosenkranz Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/289298,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.494.37,false,true,289309,Photographs,Photograph,Duck Market,,,,,,Artist,,Lai Fong,"Chinese, 1839–1890",,"Lai, Fong",Chinese,1839,1890,ca. 1869,1869,1869,Albumen silver print from glass negative,Image: 8 1/8 × 11 5/16 in. (20.7 × 28.7 cm),"Gilman Collection, Purchase, Robert Rosenkranz Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/289309,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.494.39,false,true,289311,Photographs,Photograph,[Group of People Posing near River],,,,,,Artist,,Lai Fong,"Chinese, 1839–1890",,"Lai, Fong",Chinese,1839,1890,ca. 1869,1869,1869,Albumen silver print from glass negative,Image: 7 3/16 × 9 1/2 in. (18.2 × 24.1 cm),"Gilman Collection, Purchase, Robert Rosenkranz Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/289311,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -53.627.1.16,false,true,306151,Photographs,"Photograph, Carte-de-visite",Empress Eugénie,,,,,,Artist,,Sergei Luvovich Levitsky,"Russian, 1819–1898",,"Levitsky, Sergei Luvovich",Russian,1819,1898,ca. 1864,1859,1869,Albumen silver print,Sheet: 10.5 × 6 cm (4 1/8 × 2 3/8 in.),"Gift of Susanna Myers, 1953",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306151,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -53.627.1.17,false,true,306152,Photographs,"Photograph, Carte-de-visite",Empress Eugénie,,,,,,Artist,,Sergei Luvovich Levitsky,"Russian, 1819–1898",,"Levitsky, Sergei Luvovich",Russian,1819,1898,ca. 1864,1859,1869,Albumen silver print,Sheet: 10.5 × 6 cm (4 1/8 × 2 3/8 in.),"Gift of Susanna Myers, 1953",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306152,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.1152,false,true,285989,Photographs,Photographs,[Two Young Women at Window],,,,,,Artist,,Andrei Osipovich Karelin,"Russian, 1837–1906",,"Karelin, Andrei Osipovich",Russian,1837,1906,ca. 1870,1868,1872,Albumen silver print from glass negative,Image: 8 1/2 × 6 1/8 in. (21.6 × 15.6 cm) Mount: 16 7/16 × 12 5/8 in. (41.7 × 32 cm),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285989,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.14,false,true,283086,Photographs,Photograph,"The Hippopotamus at the Zoological Gardens, Regent's Park",,,,,,Artist,,"de Borbón, Juan","Spanish, 1822–1887",,"de Borbón, Juan",Spanish,1822,1887,1852,1852,1852,Salted paper print from glass negative,Image: 11.1 × 12 cm (4 3/8 × 4 3/4 in.) Mount: 43.8 × 30.5 cm (17 1/4 × 12 in.),"Gilman Collection, Purchase, Ann Tenenbaum and Thomas H. Lee Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/283086,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -48.83.1,false,true,269224,Photographs,Photograph,Gröfin Auersperg,,,,,,Artist,,Franz Antoine,"Austrian, 1814–1882",,"Antoine, Franz",Austrian,1814,1882,1850s–60s,1850,1869,Albumen silver print from glass negative,21.8 x 17.2 cm. (8 9/16 x 6 3/4 in.),"David Hunter McAlpin Fund, 1948",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269224,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -48.83.2,false,true,269235,Photographs,Photograph,Gröfin Auersperg,,,,,,Artist,,Franz Antoine,"Austrian, 1814–1882",,"Antoine, Franz",Austrian,1814,1882,1850s–60s,1850,1869,Albumen silver print from glass negative,24.7 x 19.6 cm. (9 3/4 x 7 3/4 in.),"David Hunter McAlpin Fund, 1948",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269235,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -48.83.3,false,true,269246,Photographs,Photograph,Gröfin Auersperg,,,,,,Artist,,Franz Antoine,"Austrian, 1814–1882",,"Antoine, Franz",Austrian,1814,1882,1850s–60s,1850,1869,Albumen silver print from glass negative,25.2 x 19.6 cm. (9 15/16 x 7 3/4 in.),"David Hunter McAlpin Fund, 1948",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269246,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -48.83.4,false,true,269257,Photographs,Photograph,[Seated Man with Cane and Hat],,,,,,Artist,,Franz Antoine,"Austrian, 1814–1882",,"Antoine, Franz",Austrian,1814,1882,1850s–60s,1850,1869,Albumen silver print from glass negative,38.2 x 28.7 cm. (15 1/16 x 11 5/16 in.),"David Hunter McAlpin Fund, 1948",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269257,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -48.83.5,false,true,269263,Photographs,Photograph,[Seated Man in White Vest and Dark Coat],,,,,,Artist,,Franz Antoine,"Austrian, 1814–1882",,"Antoine, Franz",Austrian,1814,1882,1850s–60s,1850,1869,Albumen silver print from glass negative,25.5 x 21.5 cm. (10 1/16 x 8 7/16 in.),"David Hunter McAlpin Fund, 1948",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269263,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -48.83.6,false,true,269264,Photographs,Photograph,[Seated Man in White Vest and Dark Coat],,,,,,Artist,,Franz Antoine,"Austrian, 1814–1882",,"Antoine, Franz",Austrian,1814,1882,1850s–60s,1850,1869,Albumen silver print from glass negative,24.9 x 21.8 cm. (9 13/16 x 8 9/16 in.),"David Hunter McAlpin Fund, 1948",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269264,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -48.83.7,false,true,269265,Photographs,Photograph,"Betty Held, vereh. Solön-Engelsberg",,,,,,Artist,,Franz Antoine,"Austrian, 1814–1882",,"Antoine, Franz",Austrian,1814,1882,1850s–60s,1850,1869,Albumen silver print from glass negative,25.1 x 19.4 cm. (9 7/8 x 7 5/8 in.),"David Hunter McAlpin Fund, 1948",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269265,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -48.83.8,false,true,269266,Photographs,Photograph,[Portrait of an Elderly Woman],,,,,,Artist,,Franz Antoine,"Austrian, 1814–1882",,"Antoine, Franz",Austrian,1814,1882,1850s–60s,1850,1869,Albumen silver print from glass negative,16.7 x 13.4 cm. (6 9/16 x 5 1/4 in.),"David Hunter McAlpin Fund, 1948",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269266,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -48.83.9,false,true,269267,Photographs,Photograph,"Marie Antoine, geb. Woes",,,,,,Artist,,Franz Antoine,"Austrian, 1814–1882",,"Antoine, Franz",Austrian,1814,1882,1850s–60s,1850,1869,Albumen silver print from glass negative,23.8 x 21.2 cm. (9 3/8 x 8 3/6 in.),"David Hunter McAlpin Fund, 1948",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269267,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -48.83.10,false,true,269225,Photographs,Photograph,[Man Seated in Armchair],,,,,,Artist,,Franz Antoine,"Austrian, 1814–1882",,"Antoine, Franz",Austrian,1814,1882,1850s–60s,1850,1869,Albumen silver print from glass negative,22.5 x 16.8 cm. (8 7/8 x 6 5/8 in.),"David Hunter McAlpin Fund, 1948",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269225,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -48.83.11,false,true,269226,Photographs,Photograph,[Young Woman in Dotted Dress],,,,,,Artist,,Franz Antoine,"Austrian, 1814–1882",,"Antoine, Franz",Austrian,1814,1882,1850s–60s,1850,1869,Albumen silver print from glass negative,22.0 x 17.3 cm. (8 11/16 x 6 13/16 in.),"David Hunter McAlpin Fund, 1948",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269226,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -48.83.12,false,true,269227,Photographs,Photograph,Elisabeth Höusermann,,,,,,Artist,,Franz Antoine,"Austrian, 1814–1882",,"Antoine, Franz",Austrian,1814,1882,1850s–60s,1850,1869,Albumen silver print from glass negative,25.4 x 19.3 cm. (10 x 7 5/8 in.),"David Hunter McAlpin Fund, 1948",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269227,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -48.83.14,false,true,269229,Photographs,Photograph,Mathias Höusermann,,,,,,Artist,,Franz Antoine,"Austrian, 1814–1882",,"Antoine, Franz",Austrian,1814,1882,1850s–60s,1850,1869,Albumen silver print from glass negative,18.7 x 14.8 cm. (7 3/8 x 5 13/16 in.),"David Hunter McAlpin Fund, 1948",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269229,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -48.83.15,false,true,269230,Photographs,Photograph,Mathias und Elise Höusermann,,,,,,Artist,,Franz Antoine,"Austrian, 1814–1882",,"Antoine, Franz",Austrian,1814,1882,1850s–60s,1850,1869,Albumen silver print from glass negative,28.5 x 24.3 cm. (11 1/4 x 9 9/16 in.),"David Hunter McAlpin Fund, 1948",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269230,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -48.83.17,false,true,269232,Photographs,Photograph,[Group Portrait of Five Adults and Two Children in a Garden],,,,,,Artist,,Franz Antoine,"Austrian, 1814–1882",,"Antoine, Franz",Austrian,1814,1882,1850s–60s,1850,1869,Albumen silver print from glass negative,28.3 x 34.3 cm. (11 1/8 x 13 1/2 in.),"David Hunter McAlpin Fund, 1948",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269232,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -48.83.18,false,true,269233,Photographs,Photograph,[Group Portrait of Six People],,,,,,Artist,,Franz Antoine,"Austrian, 1814–1882",,"Antoine, Franz",Austrian,1814,1882,1850s–60s,1850,1869,Albumen silver print from glass negative,,"David Hunter McAlpin Fund, 1948",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269233,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -48.83.19,false,true,269234,Photographs,Photograph,"[Group Portrait of Four Women, Two Men and Three Children in a Garden]",,,,,,Artist,,Franz Antoine,"Austrian, 1814–1882",,"Antoine, Franz",Austrian,1814,1882,1850s–60s,1850,1869,Albumen silver print from glass negative,2.3 x 37.8 cm. (0 15/16 x 14 7/8 in.),"David Hunter McAlpin Fund, 1948",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269234,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -48.83.20,false,true,269236,Photographs,Photograph,[Group portrait of the Antoine and Höusermann Families],,,,,,Artist,,Franz Antoine,"Austrian, 1814–1882",,"Antoine, Franz",Austrian,1814,1882,1850s–60s,1850,1869,Albumen silver print from glass negative,31.2 x 36.4 cm. (12 1/4 x 14 5/16 in.),"David Hunter McAlpin Fund, 1948",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269236,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -48.83.22,false,true,269238,Photographs,Photograph,[Mathias Höusermann seated with elbow on pedestal holding a vase of flowers],,,,,,Artist,,Franz Antoine,"Austrian, 1814–1882",,"Antoine, Franz",Austrian,1814,1882,1850s–60s,1850,1869,Albumen silver print from glass negative,37.3 x 30.5 cm. (14 11/16 x 12 in.),"David Hunter McAlpin Fund, 1948",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269238,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -48.83.23,false,true,269239,Photographs,Photograph,"Anna Wöss, Marie and Marie Antoine",,,,,,Artist,,Franz Antoine,"Austrian, 1814–1882",,"Antoine, Franz",Austrian,1814,1882,1850s–60s,1850,1869,Albumen silver print from glass negative,24.5 x 19.5 cm. (9 5/8 x 7 11/16 in.),"David Hunter McAlpin Fund, 1948",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269239,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -48.83.24,false,true,269240,Photographs,Photograph,[Group Portrait of the Antoine and Höusermann Families],,,,,,Artist,,Franz Antoine,"Austrian, 1814–1882",,"Antoine, Franz",Austrian,1814,1882,1850s–60s,1850,1869,Albumen silver print from glass negative,28.0 x 32.3 cm. (11 x 12 3/4 in.),"David Hunter McAlpin Fund, 1948",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269240,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -48.83.25,false,true,269241,Photographs,Photograph,[Group Portrait of Four Women and Three Children],,,,,,Artist,,Franz Antoine,"Austrian, 1814–1882",,"Antoine, Franz",Austrian,1814,1882,1850s–60s,1850,1869,Albumen silver print from glass negative,22.6 x 19.2 cm. (8 7/8 x 7 9/16 in.),"David Hunter McAlpin Fund, 1948",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269241,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -48.83.26,false,true,269242,Photographs,Photograph,[Seated Lady in Striped Dress with Four Little Girls],,,,,,Artist,,Franz Antoine,"Austrian, 1814–1882",,"Antoine, Franz",Austrian,1814,1882,1850s–60s,1850,1869,Coated salted paper print from glass negative,15.5 x 19.4 cm. (6 1/8 x 7 5/8 in.),"David Hunter McAlpin Fund, 1948",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269242,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -48.83.27,false,true,269243,Photographs,Photograph,"Elise Höusermann, Hermine, Marie and Marie Antoine",,,,,,Artist,,Franz Antoine,"Austrian, 1814–1882",,"Antoine, Franz",Austrian,1814,1882,1850s–60s,1850,1869,Albumen silver print from glass negative,19.0 x 21.9 cm. (7 1/2 x 8 5/8 in.),"David Hunter McAlpin Fund, 1948",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269243,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -48.83.28,false,true,269244,Photographs,Photograph,"[Three Women,Two Men, and a Child on a Picnic]",,,,,,Artist,,Franz Antoine,"Austrian, 1814–1882",,"Antoine, Franz",Austrian,1814,1882,1850s–60s,1850,1869,Albumen silver print from glass negative,16.1 x 21.1 cm. (6 5/16 x 8 5/16 in.),"David Hunter McAlpin Fund, 1948",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269244,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -48.83.29,false,true,269245,Photographs,Photograph,"[Hermine, Alfons and Eugen Antoine and Mathias Höusermann on a fallen tree]",,,,,,Artist,,Franz Antoine,"Austrian, 1814–1882",,"Antoine, Franz",Austrian,1814,1882,1850s–60s,1850,1869,Albumen silver print from glass negative,18.6 x 23.3 cm. (7 5/16 x 9 3/16 in.),"David Hunter McAlpin Fund, 1948",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269245,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -48.83.30,false,true,269247,Photographs,Photograph,Frau Hofrat Josefine Raymond,,,,,,Artist,,Franz Antoine,"Austrian, 1814–1882",,"Antoine, Franz",Austrian,1814,1882,1850s–60s,1850,1869,Albumen silver print from glass negative,28.5 x 21.5 cm. (11 1/4 x 8 7/16 in.),"David Hunter McAlpin Fund, 1948",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269247,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -48.83.31,false,true,269248,Photographs,Photograph,Hofrat Raymond,,,,,,Artist,,Franz Antoine,"Austrian, 1814–1882",,"Antoine, Franz",Austrian,1814,1882,1850s–60s,1850,1869,Albumen silver print from glass negative,34.9 x 25.0 cm. (13 3/4 x 9 13/16 in.),"David Hunter McAlpin Fund, 1948",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269248,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -48.83.33,false,true,269250,Photographs,Photograph,"[Mathias Höusermann, Marie Antoine, Elise Höusermann, and Pepe Wöss]",,,,,,Artist,,Franz Antoine,"Austrian, 1814–1882",,"Antoine, Franz",Austrian,1814,1882,1850s–60s,1850,1869,Albumen silver print from glass negative,,"David Hunter McAlpin Fund, 1948",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269250,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -48.83.34,false,true,269251,Photographs,Photograph,[Portrait of Three Women and Men in a Garden],,,,,,Artist,,Franz Antoine,"Austrian, 1814–1882",,"Antoine, Franz",Austrian,1814,1882,1850s–60s,1850,1869,Albumen silver print from glass negative,23.8 x 19.0 cm (9 3/8 x 7 1/2 in.),"David Hunter McAlpin Fund, 1948",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269251,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -48.83.35,false,true,269252,Photographs,Photograph,"Alfons, Eugen, Marie, and Hermine Antoine",,,,,,Artist,,Franz Antoine,"Austrian, 1814–1882",,"Antoine, Franz",Austrian,1814,1882,1850s–60s,1850,1869,Albumen silver print from glass negative,23.1 x 18.5 cm. (9 1/16 x 7 1/4 in.),"David Hunter McAlpin Fund, 1948",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269252,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -48.83.36,false,true,269253,Photographs,Photograph,"Hermine, Marie and Marie Antoine.",,,,,,Artist,,Franz Antoine,"Austrian, 1814–1882",,"Antoine, Franz",Austrian,1814,1882,1850s–60s,1850,1869,Albumen silver print from glass negative,15.0 x 11.1 cm. (5 15/16 x 4 3/8 in.),"David Hunter McAlpin Fund, 1948",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269253,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -48.83.37,false,true,269254,Photographs,Photograph,Hermine and Marie Antoine,,,,,,Artist,,Franz Antoine,"Austrian, 1814–1882",,"Antoine, Franz",Austrian,1814,1882,1850s–60s,1850,1869,Albumen silver print from glass negative,19.2 x 11.1 cm (7 9/16 x 4 3/8 in. ),"David Hunter McAlpin Fund, 1948",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269254,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -48.83.38,false,true,269255,Photographs,Photograph,Marie Antoine (Wöss),,,,,,Artist,,Franz Antoine,"Austrian, 1814–1882",,"Antoine, Franz",Austrian,1814,1882,1850s–60s,1850,1869,Albumen silver print from glass negative,11.3 x 8.8 cm. (4 7/16 x 3 7/16 in.),"David Hunter McAlpin Fund, 1948",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269255,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -48.83.39,false,true,269256,Photographs,Photograph,"[Female Portrait, Standing, Looking Left]",,,,,,Artist,,Franz Antoine,"Austrian, 1814–1882",,"Antoine, Franz",Austrian,1814,1882,1850s–60s,1850,1869,Albumen silver print from glass negative,22.4 x 14.4 cm. (8 13/16 x 5 11/16 in.),"David Hunter McAlpin Fund, 1948",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269256,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -48.83.40,false,true,269258,Photographs,Photograph,[Portrait of Two Girls],,,,,,Artist,,Franz Antoine,"Austrian, 1814–1882",,"Antoine, Franz",Austrian,1814,1882,1850s–60s,1850,1869,Albumen silver print from glass negative,20.4 x 14.5 cm. (8 x 5 11/16 in.),"David Hunter McAlpin Fund, 1948",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269258,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -48.83.41,false,true,269259,Photographs,Photograph,"[Portrait of a Seated Woman Surrounded by Five Girls, Seated and Standing]",,,,,,Artist,,Franz Antoine,"Austrian, 1814–1882",,"Antoine, Franz",Austrian,1814,1882,1850s–60s,1850,1869,Albumen silver print from glass negative,18.4 x 13.1 cm. (7 1/4 x 5 3/16 in.),"David Hunter McAlpin Fund, 1948",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269259,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -48.83.42,false,true,269260,Photographs,Photograph,[Ten members of the Antoine family],,,,,,Artist,,Franz Antoine,"Austrian, 1814–1882",,"Antoine, Franz",Austrian,1814,1882,1850s–60s,1850,1869,Albumen silver print from glass negative,19.2 x 26.1 cm. (7 9/16 x 10 1/4 in.),"David Hunter McAlpin Fund, 1948",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269260,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -48.83.44,false,true,269262,Photographs,Photograph,Mathias Höusermann,,,,,,Artist,,Franz Antoine,"Austrian, 1814–1882",,"Antoine, Franz",Austrian,1814,1882,1850s–60s,1850,1869,Albumen silver print from glass negative,36.9 x 32.3 cm. (14 1/2 x 12 3/4 in.),"David Hunter McAlpin Fund, 1948",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269262,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2012.108,false,true,301891,Photographs,Micrograph,[Frustules of Diatoms],,,,,,Artist,Attributed to,Julius Wiesner,"Austrian, 1838–1916",,"Wiesner, Julius",Austrian,1838,1916,ca. 1870,1865,1875,Cyanotype,9.8 x 7.9 cm (3 7/8 x 3 1/8 in.),"Purchase, Steven Ames Gift, 2012",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/301891,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.505.1 (1a),false,true,288477,Photographs,Photograph,[Chinese Gentleman],,,,,,Artist,,Raimund von Stillfried,"Austrian, 1839–1911",,"Stillfried, Raimond von",Austrian,1839,1911,1870s,1870,1879,Albumen silver print from glass negative,23.7 x 19.1 cm (9 5/16 x 7 1/2 in.),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288477,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.505.1 (2a),false,true,288481,Photographs,Photograph,Fille de Shanghai,,,,,,Artist,,Raimund von Stillfried,"Austrian, 1839–1911",,"Stillfried, Raimond von",Austrian,1839,1911,1870s,1870,1879,Albumen silver print from glass negative,23.7 x 19.2 cm (9 5/16 x 7 9/16 in.),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288481,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.505.1 (3a),false,true,283171,Photographs,Photograph,Interprete de la Legation for the Austro Hongroise,,,,,,Artist,,Raimund von Stillfried,"Austrian, 1839–1911",,"Stillfried, Raimond von",Austrian,1839,1911,1870s,1870,1879,Albumen silver print from glass negative,23.7 x 19.2 cm (9 5/16 x 7 9/16 in.),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/283171,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.505.1 (4a),false,true,288482,Photographs,Photograph,Fille de Shanghai,,,,,,Artist,,Raimund von Stillfried,"Austrian, 1839–1911",,"Stillfried, Raimond von",Austrian,1839,1911,1870s,1870,1879,Albumen silver print from glass negative,23.7 x 19.2 cm (9 5/16 x 7 9/16 in.),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288482,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.505.1 (5a),false,true,288012,Photographs,Photograph,Soldat de la ligne,,,,,,Artist,,Raimund von Stillfried,"Austrian, 1839–1911",,"Stillfried, Raimond von",Austrian,1839,1911,1870s,1870,1879,Albumen silver print from glass negative,23.7 x 19.2 cm (9 5/16 x 7 9/16 in.),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288012,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.505.1 (6a),false,true,288013,Photographs,Photograph,Fille de lanxchow,,,,,,Artist,,Raimund von Stillfried,"Austrian, 1839–1911",,"Stillfried, Raimond von",Austrian,1839,1911,1870s,1870,1879,Albumen silver print from glass negative,23.7 x 19.2 cm (9 5/16 x 7 9/16 in.),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288013,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.505.1 (7a),false,true,288485,Photographs,Photograph,[Young Chinese Gentleman],,,,,,Artist,,Raimund von Stillfried,"Austrian, 1839–1911",,"Stillfried, Raimond von",Austrian,1839,1911,1870s,1870,1879,Albumen silver print from glass negative,25.7 x 19.3 cm (10 1/8 x 7 5/8 in.),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288485,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.505.1 (8a),false,true,288487,Photographs,Photograph,Fille de Shanghai,,,,,,Artist,,Raimund von Stillfried,"Austrian, 1839–1911",,"Stillfried, Raimond von",Austrian,1839,1911,1870s,1870,1879,Albumen silver print from glass negative,23.7 x 19.3 cm (9 5/16 x 7 5/8 in.),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288487,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.505.1 (9a),false,true,288489,Photographs,Photograph,Vieux mendiant,,,,,,Artist,,Raimund von Stillfried,"Austrian, 1839–1911",,"Stillfried, Raimond von",Austrian,1839,1911,1870s,1870,1879,Albumen silver print from glass negative,23.7 x 19.2 cm (9 5/16 x 7 9/16 in.),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288489,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.505.1 (10a),false,true,288491,Photographs,Photograph,Vielle mendiant,,,,,,Artist,,Raimund von Stillfried,"Austrian, 1839–1911",,"Stillfried, Raimond von",Austrian,1839,1911,1870s,1870,1879,Albumen silver print from glass negative,23.7 x 19.2 cm (9 5/16 x 7 9/16 in.),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288491,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.505.1 (11a),false,true,288493,Photographs,Photograph,[Chinese Man],,,,,,Artist,,Raimund von Stillfried,"Austrian, 1839–1911",,"Stillfried, Raimond von",Austrian,1839,1911,1870s,1870,1879,Albumen silver print from glass negative,23.7 x 19.4 cm (9 5/16 x 7 5/8 in.),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288493,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.505.1 (12a),false,true,288495,Photographs,Photograph,Fille de Shanghai,,,,,,Artist,,Raimund von Stillfried,"Austrian, 1839–1911",,"Stillfried, Raimond von",Austrian,1839,1911,1870s,1870,1879,Albumen silver print from glass negative,23.7 x 19.3 cm (9 5/16 x 7 5/8 in.),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288495,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.505.1 (13a),false,true,288497,Photographs,Photograph,Vieux Chinoise de Canton,,,,,,Artist,,Raimund von Stillfried,"Austrian, 1839–1911",,"Stillfried, Raimond von",Austrian,1839,1911,1870s,1870,1879,Albumen silver print from glass negative,23.7 x 19.3 cm (9 5/16 x 7 5/8 in.),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288497,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.505.1 (14a),false,true,288499,Photographs,Photograph,[Woman from Canton],,,,,,Artist,,Raimund von Stillfried,"Austrian, 1839–1911",,"Stillfried, Raimond von",Austrian,1839,1911,1870s,1870,1879,Albumen silver print from glass negative,23.7 x 19.3 cm (9 5/16 x 7 5/8 in.),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288499,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.505.1 (15a),false,true,288501,Photographs,Photograph,Fille de Lanxchow,,,,,,Artist,,Raimund von Stillfried,"Austrian, 1839–1911",,"Stillfried, Raimond von",Austrian,1839,1911,1870s,1870,1879,Albumen silver print from glass negative,23.7 x 19.2 cm (9 5/16 x 7 9/16 in.),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288501,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.505.1 (16a),false,true,288503,Photographs,Photograph,Fille de Lanxchow,,,,,,Artist,,Raimund von Stillfried,"Austrian, 1839–1911",,"Stillfried, Raimond von",Austrian,1839,1911,1870s,1870,1879,Albumen silver print from glass negative,23.7 x 19.2 cm (9 5/16 x 7 9/16 in.),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288503,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.505.1 (17a),false,true,288506,Photographs,Photograph,Fille de Lanxchow,,,,,,Artist,,Raimund von Stillfried,"Austrian, 1839–1911",,"Stillfried, Raimond von",Austrian,1839,1911,1870s,1870,1879,Albumen silver print from glass negative,23.7 x 19.3 cm (9 5/16 x 7 5/8 in.),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288506,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.505.1 (18a),false,true,288508,Photographs,Photograph,Fille de Shanghai,,,,,,Artist,,Raimund von Stillfried,"Austrian, 1839–1911",,"Stillfried, Raimond von",Austrian,1839,1911,1870s,1870,1879,Albumen silver print from glass negative,23.7 x 19.2 cm (9 5/16 x 7 9/16 in.),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288508,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.505.1 (19a),false,true,288510,Photographs,Photograph,Fille de Shanghai,,,,,,Artist,,Raimund von Stillfried,"Austrian, 1839–1911",,"Stillfried, Raimond von",Austrian,1839,1911,1870s,1870,1879,Albumen silver print from glass negative,23.7 x 19.3 cm (9 5/16 x 7 5/8 in.),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288510,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.505.1 (20a),false,true,288512,Photographs,Photograph,Fille de Lanxchow,,,,,,Artist,,Raimund von Stillfried,"Austrian, 1839–1911",,"Stillfried, Raimond von",Austrian,1839,1911,1870s,1870,1879,Albumen silver print from glass negative,23.7 x 19.3 cm (9 5/16 x 7 5/8 in.),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288512,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.505.1 (21a),false,true,288014,Photographs,Photograph,Fille de Shanghai,,,,,,Artist,,Raimund von Stillfried,"Austrian, 1839–1911",,"Stillfried, Raimond von",Austrian,1839,1911,1870s,1870,1879,Albumen silver print from glass negative,23.7 x 19.2 cm (9 5/16 x 7 9/16 in.),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288014,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.505.1 (22a),false,true,288515,Photographs,Photograph,[Portrait of an Old Chinese Woman],,,,,,Artist,,Raimund von Stillfried,"Austrian, 1839–1911",,"Stillfried, Raimond von",Austrian,1839,1911,1870s,1870,1879,Albumen silver print from glass negative,23.7 x 19.3 cm (9 5/16 x 7 5/8 in.),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288515,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.505.1 (23a),false,true,288517,Photographs,Photograph,[Seated Chinese Woman with Fan],,,,,,Artist,,Raimund von Stillfried,"Austrian, 1839–1911",,"Stillfried, Raimond von",Austrian,1839,1911,1870s,1870,1879,Albumen silver print from glass negative,23.7 x 19.2 cm (9 5/16 x 7 9/16 in.),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288517,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.505.1 (24a),false,true,288519,Photographs,Photograph,Femme de Canton,,,,,,Artist,,Raimund von Stillfried,"Austrian, 1839–1911",,"Stillfried, Raimond von",Austrian,1839,1911,1870s,1870,1879,Albumen silver print from glass negative,23.7 x 19.3 cm (9 5/16 x 7 5/8 in.),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288519,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.505.1 (25a),false,true,288521,Photographs,Photograph,Négociant,,,,,,Artist,,Raimund von Stillfried,"Austrian, 1839–1911",,"Stillfried, Raimond von",Austrian,1839,1911,1870s,1870,1879,Albumen silver print from glass negative,23.7 x 19.3 cm (9 5/16 x 7 5/8 in.),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288521,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.505.1 (26a),false,true,288523,Photographs,Photograph,Fille de Shanghai,,,,,,Artist,,Raimund von Stillfried,"Austrian, 1839–1911",,"Stillfried, Raimond von",Austrian,1839,1911,1870s,1870,1879,Albumen silver print from glass negative,23.7 x 19.3 cm (9 5/16 x 7 5/8 in.),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288523,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.505.1 (27a),false,true,288525,Photographs,Photograph,[Two Chinese Men in Matching Traditional Dress],,,,,,Artist,,Raimund von Stillfried,"Austrian, 1839–1911",,"Stillfried, Raimond von",Austrian,1839,1911,1870s,1870,1879,Albumen silver print from glass negative,23.6 x 19.3 cm (9 5/16 x 7 5/8 in.),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288525,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.505.1 (28a),false,true,288015,Photographs,Photograph,Filles de Shanghai,,,,,,Artist,,Raimund von Stillfried,"Austrian, 1839–1911",,"Stillfried, Raimond von",Austrian,1839,1911,1870s,1870,1879,Albumen silver print from glass negative,23.7 x 19.2 cm (9 5/16 x 7 9/16 in.),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288015,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.505.1 (29a),false,true,288528,Photographs,Photograph,[Chinese Man Wearing Hat],,,,,,Artist,,Raimund von Stillfried,"Austrian, 1839–1911",,"Stillfried, Raimond von",Austrian,1839,1911,1870s,1870,1879,Albumen silver print from glass negative,23.7 x 19.2 cm (9 5/16 x 7 9/16 in.),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288528,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.505.1 (30a),false,true,288016,Photographs,Photograph,[Chinese Woman Sitting with Basket],,,,,,Artist,,Raimund von Stillfried,"Austrian, 1839–1911",,"Stillfried, Raimond von",Austrian,1839,1911,1870s,1870,1879,Albumen silver print from glass negative,23.7 x 19.2 cm (9 5/16 x 7 9/16 in.),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288016,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.505.1 (41a),false,true,288019,Photographs,Photograph,Brouette,,,,,,Artist,,Raimund von Stillfried,"Austrian, 1839–1911",,"Stillfried, Raimond von",Austrian,1839,1911,1870s,1870,1879,Albumen silver print from glass negative,23.7 x 19.2 cm (9 5/16 x 7 9/16 in.),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288019,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.43.405,false,true,267821,Photographs,Photograph,Sheep,,,,,,Artist,,Hans Watzek,"Austrian, 1848–1903",,"Watzek, Hans",Austrian,1848,1903,1901,1901,1901,Gum bichromate print,50.2 x 63.6 cm (19 3/4 x 25 1/16 in.),"Alfred Stieglitz Collection, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267821,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.43.411,false,true,267828,Photographs,Photograph,[Italian Villa in Autumn],,,,,,Artist,,Hugo Henneberg,"Austrian, 1863–1918",,"Henneberg, Hugo",Austrian,1863,1918,1898,1898,1898,Gum bichromate print,54.0 x 74.5 cm (21 1/4 x 29 5/16 in.),"Alfred Stieglitz Collection, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267828,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.43.412,false,true,267829,Photographs,Photograph,Motiv aus Pommern,,,,,,Artist,,Hugo Henneberg,"Austrian, 1863–1918",,"Henneberg, Hugo",Austrian,1863,1918,"1895–96, printed 1902",1895,1896,Gum bichromate print,77.7 x 55.5 cm (30 9/16 x 21 7/8 in.),"Alfred Stieglitz Collection, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267829,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.43.413,false,true,267830,Photographs,Photograph,Pflügen,,,,,,Artist,,Hugo Henneberg,"Austrian, 1863–1918",,"Henneberg, Hugo",Austrian,1863,1918,"1890–1901, printed 1903",1890,1901,Gum bichromate print,65.8 x 95.8 cm. (25 15/16 x 37 3/4 in.),"Alfred Stieglitz Collection, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267830,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.579,false,true,285639,Photographs,Photograph,Egon Schiele,,,,,,Artist,,Anton Joseph Trcka,"Austrian, 1893–1940",,"Trcka, Anton Joseph",Austrian,1893,1940,1914,1914,1914,Gelatin silver print,Image: 22.6 x 15.3 cm (8 7/8 x 6 in.),"Gilman Collection, Purchase, Ann Tenenbaum and Thomas H. Lee Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285639,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.72,false,true,685414,Photographs,Carte-de-visite,[George Henry Boughton],,,,,,Artist,,Oliver François Xavier Sarony,"Canadian, 1820–1879",,"Sarony, Oliver François Xavier",Canadian,1820,1879,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/685414,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.320,false,true,685661,Photographs,Carte-de-visite,[William Powell Frith],,,,,,Artist,,Oliver François Xavier Sarony,"Canadian, 1820–1879",,"Sarony, Oliver François Xavier",Canadian,1820,1879,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/685661,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2000.288.1,false,true,283777,Photographs,Photograph,"[Claudet Family Group, Chateau de la Roche, Amboise]",,,,,,Artist,,Francis George Claudet,"Canadian, 1837–1906",,"Claudet, Francis George",Canadian,1837,1906,1856,1856,1856,Salted paper print from glass negative,17.5 x 13.4 cm (6 7/8 x 5 1/4 in.) visible,"Gift of Georgina Claudet Gilchrist and Frances Claudet Johnson, 2000",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/283777,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.767,false,true,686106,Photographs,Carte-de-visite,[Sanford Thayer],,,,,,Artist,,G. J. Wood,"American?, active 1860s",,"Wood, G. J.",American?,1859,1870,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/686106,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.1098.5,false,true,631029,Photographs,Photograph,Miss Patricia Morris,,,,,,Artist,,Hill and Adamson,"British, active 1843–1848",,Hill and Adamson,"British, Scottish",1843,1848,1843–47,1843,1847,Salted paper print,Image: 8 3/8 × 6 1/8 in. (21.3 × 15.6 cm) Mount: 14 3/4 × 10 3/8 in. (37.5 × 26.4 cm),"Gift of Joyce F. Menschel, 2013",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/631029,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2011.599.1a,false,true,633310,Photographs,Photograph,The Great Elephant Saluting,,,,,,Artist,,Lala Deen Dayal,"Indian, Sardhana 1844–1905",,"Dayal, Lala Deen",Indian,1844,1905,1885–1900,1885,1900,Albumen silver print from glass negative,Image: 21.1 x 27.3 cm (8 5/16 x 10 3/4 in.) Mount: 30 x 37.7 cm (11 13/16 x 14 13/16 in.),"Gift of Cynthia Hazen Polsky, 2011",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/633310,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2011.599.1b,false,true,291060,Photographs,Photograph,The Great Elephant,,,,,,Artist,,Lala Deen Dayal,"Indian, Sardhana 1844–1905",,"Dayal, Lala Deen",Indian,1844,1905,1885–1900,1885,1900,Albumen silver print from glass negative,Image: 24.1 x 21.2 cm (9 1/2 x 8 3/8 in.) Mount: 30 x 37.7 cm (11 13/16 x 14 13/16 in.),"Gift of Cynthia Hazen Polsky, 2011",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/291060,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2011.599.2a,false,true,291679,Photographs,Photograph,Sookh-Vilas Palace Garden,,,,,,Artist,,Lala Deen Dayal,"Indian, Sardhana 1844–1905",,"Dayal, Lala Deen",Indian,1844,1905,1880–90,1880,1890,Albumen silver print from glass negative,Image: 20.3 x 27.5 cm (8 x 10 13/16 in.) Mount: 30 x 37.7 cm (11 13/16 x 14 13/16 in.),"Gift of Cynthia Hazen Polsky, 2011",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/291679,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2011.599.2b,false,true,633313,Photographs,Photograph,Sookh-Vilas Palace Garden,,,,,,Artist,,Lala Deen Dayal,"Indian, Sardhana 1844–1905",,"Dayal, Lala Deen",Indian,1844,1905,1880–90,1880,1890,Albumen silver print from glass negative,Image: 20.4 x 26.2 cm (8 1/16 x 10 5/16 in.) Mount: 30 x 37.7 cm (11 13/16 x 14 13/16 in.),"Gift of Cynthia Hazen Polsky, 2011",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/633313,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1995.368,false,true,267262,Photographs,Photograph,"Hunford Mill, Surrey",,,,,,Artist,,Henry White,"British, Scotland 1819–1903",,"White, Henry","British, Scottish",1819,1903,1855–57,1855,1857,Albumen silver print from glass negative,Image: 19.4 x 23.8 cm (7 5/8 x 9 3/8 in.) Mount: 43.4 x 59.9 cm (17 1/16 x 23 9/16 in.),"Purchase, Mrs. Harrison D. Horblit Gift, 1995",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267262,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.434,false,true,283227,Photographs,Photograph,Poling the Marsh Hay,,,,,,Artist,,Peter Henry Emerson,"British, born Cuba, 1856–1936",,"Emerson, Peter Henry","British, born Cuba",1856,1936,1886,1886,1886,Platinum print from glass negative,Image: 23.2 x 29 cm (9 1/8 x 11 7/16 in.) Mount: 28.6 x 40.9 cm (11 1/4 x 16 1/8 in.) Sheet ((Interleaving Plate Sheet)): 28 x 40.7 cm (11 x 16 in.),"Gilman Collection, Purchase, Mrs. Walter Annenberg and The Annenberg Foundation Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/283227,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.722,false,true,286469,Photographs,Photograph,Setting the Bownet,,,,,,Artist,,Peter Henry Emerson,"British, born Cuba, 1856–1936",,"Emerson, Peter Henry","British, born Cuba",1856,1936,1886,1886,1886,Platinum print from glass negative,Image: 16.4 x 28.9 cm (6 7/16 x 11 3/8 in.) Mount: 28.6 x 41.1 cm (11 1/4 x 16 3/16 in.),"Gilman Collection, Purchase, Mrs. Walter Annenberg and The Annenberg Foundation Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286469,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.726,false,true,286418,Photographs,Photograph,Rowing Home the Schoof-Stuff,,,,,,Artist,,Peter Henry Emerson,"British, born Cuba, 1856–1936",,"Emerson, Peter Henry","British, born Cuba",1856,1936,1886,1886,1886,Platinum print from glass negative,Image: 13.8 x 27.9 cm (5 7/16 x 11 in.) Mount: 28.6 x 41 cm (11 1/4 x 16 1/8 in.) Sheet (Interleaving Plate Sheet): 28.2 x 40.8 cm (11 1/8 x 16 1/16 in.),"Gilman Collection, Purchase, Mrs. Walter Annenberg and The Annenberg Foundation Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286418,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.741,false,true,286671,Photographs,Photograph,Gathering Water-Lilies,,,,,,Artist,,Peter Henry Emerson,"British, born Cuba, 1856–1936",,"Emerson, Peter Henry","British, born Cuba",1856,1936,1886,1886,1886,Platinum print from glass negative,Image: 19.7 x 29.2 cm (7 3/4 x 11 1/2 in.) Mount: 28.5 x 40.9 cm (11 1/4 x 16 1/8 in.) Sheet ((Interleaving Plate Sheet)): 28.1 x 40.6 cm (11 1/16 x 16 in.),"Gilman Collection, Purchase, Mrs. Walter Annenberg and The Annenberg Foundation Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286671,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.742,false,true,286673,Photographs,Photograph,Gunner Working Up To Fowl,,,,,,Artist,,Peter Henry Emerson,"British, born Cuba, 1856–1936",,"Emerson, Peter Henry","British, born Cuba",1856,1936,1886,1886,1886,Platinum print from glass negative,Image: 18.9 x 28.8 cm (7 7/16 x 11 5/16 in.) Mount: 28.6 x 40.9 cm (11 1/4 x 16 1/8 in.) Sheet ((Interleaving Plate Sheet)): 28.2 x 40.7 cm (11 1/8 x 16 in.),"Gilman Collection, Purchase, Mrs. Walter Annenberg and The Annenberg Foundation Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286673,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.743,false,true,286676,Photographs,Photograph,Cantley: Wherries Waiting for the Turn of the Tide,,,,,,Artist,,Peter Henry Emerson,"British, born Cuba, 1856–1936",,"Emerson, Peter Henry","British, born Cuba",1856,1936,1886,1886,1886,Platinum print from glass negative,Image: 18.9 x 28.5 cm (7 7/16 x 11 1/4 in.) Mount: 28.6 x 40.9 cm (11 1/4 x 16 1/8 in.) Sheet ((Interleaving Plate Sheet)): 28.2 x 41 cm (11 1/8 x 16 1/8 in.),"Gilman Collection, Purchase, Mrs. Walter Annenberg and The Annenberg Foundation Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286676,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.744,false,true,286677,Photographs,Photograph,Water-Lilies,,,,,,Artist,,Peter Henry Emerson,"British, born Cuba, 1856–1936",,"Emerson, Peter Henry","British, born Cuba",1856,1936,1886,1886,1886,Platinum print from glass negative,Image: 12.3 x 28 cm (4 13/16 x 11 in.) Mount: 28.7 x 41 cm (11 5/16 x 16 1/8 in.) Sheet (Interleaving Plate Sheet): 28 x 40.8 cm (11 x 16 1/16 in.),"Gilman Collection, Purchase, Mrs. Walter Annenberg and The Annenberg Foundation Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286677,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.400.10,false,true,291755,Photographs,Daguerreotype,[Elderly Man Holding Ivory-topped Walking Stick],,,,,,Artist,,John Plumbe Jr.,"American, born Wales, 1809–1857",,"Plumbe Jr., John","American, born Wales",1809,1857,1840s,1840,1849,Daguerreotype,Image: 6.7 x 5.5 cm (2 5/8 x 2 3/16 in.) Plate: 8.3 x 7 cm (3 1/4 x 2 3/4 in.) Case: 1.6 x 9.2 x 8.1 cm (5/8 x 3 5/8 x 3 3/16 in.),"Bequest of Herbert Mitchell, 2008",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/291755,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.400.32,false,true,291785,Photographs,Daguerreotype,[Man with Chinstrap Beard],,,,,,Artist,,John Plumbe Jr.,"American, born Wales, 1809–1857",,"Plumbe Jr., John","American, born Wales",1809,1857,1840s,1840,1849,Daguerreotype,Image: 7.6 x 6.6 cm (3 x 2 5/8 in.) Plate: 8.3 x 7 cm (3 1/4 x 2 3/4 in.) Case: 1.6 x 9.5 x 8.1 cm (5/8 x 3 3/4 x 3 3/16 in.),"Bequest of Herbert Mitchell, 2008",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/291785,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.400.60,false,true,291813,Photographs,Daguerreotype,"Elizabeth Page Bakewell and her Grandson, Frank B. James",,,,,,Artist,,John Plumbe Jr.,"American, born Wales, 1809–1857",,"Plumbe Jr., John","American, born Wales",1809,1857,ca. 1846,1845,1848,Daguerreotype,Image: 12 x 9.1 cm (4 3/4 x 3 9/16 in.) Frame: 34.6 x 31.1 cm (13 5/8 x 12 1/4 in.) Mat: 18.1 x 14.6 cm (7 1/8 x 5 3/4 in.),"Bequest of Herbert Mitchell, 2008",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/291813,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.400.61,false,true,291814,Photographs,Daguerreotype,"Elizabeth Bakewell James and her Son, Frank B. James",,,,,,Artist,Attributed to,John Plumbe Jr.,"American, born Wales, 1809–1857",,"Plumbe Jr., John","American, born Wales",1809,1857,ca. 1846,1845,1848,Daguerreotype,Image: 11.9 x 8.9 cm (4 11/16 x 3 1/2 in.) Mat: 18.4 x 14.9 cm (7 1/4 x 5 7/8 in.) Frame: 34.8 x 31.3 cm (13 11/16 x 12 5/16 in.),"Bequest of Herbert Mitchell, 2008",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/291814,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.804,false,true,286155,Photographs,Photograph,Dr. Edward Livingston,,,,,,Artist,Attributed to,John Plumbe Jr.,"American, born Wales, 1809–1857",,"Plumbe Jr., John","American, born Wales",1809,1857,ca. 1841,1839,1843,Daguerreotype,Overall: 4 11/16 × 3 11/16 in. (11.9 × 9.4 cm) Image: 3 9/16 × 2 5/8 in. (9.1 × 6.7 cm); visible,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286155,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -18.17.1.216,false,true,282222,Photographs,Photograph,[View of the rooftops and cathedral of Vienna],,,,,,Artist,,Alois Auer,"Austrian, Wels 1813–1869 Vienna",,"Auer, Alois",Austrian,1813,1869,ca. 1853,1851,1855,Albumen silver print,,"Rogers Fund, 1918",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/282222,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -18.17.1.217,false,true,259569,Photographs,Micrograph,[Microscopic view of an insect],,,,,,Artist,,Alois Auer,"Austrian, Wels 1813–1869 Vienna",,"Auer, Alois",Austrian,1813,1869,ca. 1853,1851,1855,Albumen silver print,,"Rogers Fund, 1918",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/259569,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -61.641.2.1,false,true,270438,Photographs,Photograph,[Jerusalem],,,,,,Artist,,John Anthony,"British, born France, 1823–1901",,"Anthony, John","British, born France",1823,1901,1860s,1860,1869,Albumen silver print from glass negative,Image: 16.3 x 21.8 cm (6 7/16 x 8 9/16 in.) Mount: 27.6 x 37.3 cm (10 7/8 x 14 11/16 in.),"Gift of A. Hyatt Mayor, 1961",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/270438,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -61.641.2.3,false,true,270441,Photographs,Photograph,[Garden of Gethsemane and View of Jerusalem],,,,,,Artist,,John Anthony,"British, born France, 1823–1901",,"Anthony, John","British, born France",1823,1901,1860s,1860,1869,Albumen silver print from glass negative,Image: 16.8 x 21.4 cm (6 5/8 x 8 7/16 in.) Mount: 27.8 x 37.2 cm (10 15/16 x 14 5/8 in.),"Gift of A. Hyatt Mayor, 1961",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/270441,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -61.641.2.4,false,true,270442,Photographs,Photograph,[Jerusalem],,,,,,Artist,,John Anthony,"British, born France, 1823–1901",,"Anthony, John","British, born France",1823,1901,1857,1857,1857,Albumen silver print from glass negative,Image: 13.6 x 17 cm (5 3/8 x 6 11/16 in.) Mount: 27.9 x 37.3 cm (11 x 14 11/16 in.),"Gift of A. Hyatt Mayor, 1961",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/270442,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -61.641.2.6,false,true,270443,Photographs,Photograph,"Jerusalem, Court of the Mosque of Omar",,,,,,Artist,,John Anthony,"British, born France, 1823–1901",,"Anthony, John","British, born France",1823,1901,1857,1857,1857,Albumen silver print from glass negative,Image: 16.9 x 21.4 cm (6 5/8 x 8 7/16 in.) Mount: 28 x 37.5 cm (11 x 14 3/4 in.),"Gift of A. Hyatt Mayor, 1961",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/270443,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -61.641.2.7,false,true,270444,Photographs,Photograph,"[Dome of the Holy Sepulchre, Jerusalem]",,,,,,Artist,,John Anthony,"British, born France, 1823–1901",,"Anthony, John","British, born France",1823,1901,1860s,1860,1869,Albumen silver print from glass negative,Image: 13.9 x 17 cm (5 1/2 x 6 11/16 in.) Mount: 27.7 x 37.3 cm (10 7/8 x 14 11/16 in.),"Gift of A. Hyatt Mayor, 1961",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/270444,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -61.641.2.9,false,true,270445,Photographs,Photograph,"[Garden of Gethsemane and the Tomb of the Virgin, Jerusalem]",,,,,,Artist,,John Anthony,"British, born France, 1823–1901",,"Anthony, John","British, born France",1823,1901,1860s,1860,1869,Albumen silver print from glass negative,Image: 12.8 x 16.2 cm (5 1/16 x 6 3/8 in.) Mount: 27.7 x 37.5 cm (10 7/8 x 14 3/4 in.),"Gift of A. Hyatt Mayor, 1961",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/270445,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -63.606.1.2,false,true,270834,Photographs,Photograph,"Jerusalem, Site of the Temple on Mount Moriah",,,,,,Artist,,John Anthony,"British, born France, 1823–1901",,"Anthony, John","British, born France",1823,1901,1857,1857,1857,Albumen silver print,,"The Elisha Whittelsey Collection, The Elisha Whittelsey Fund, 1963",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/270834,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -61.641.2.10,false,true,270439,Photographs,Photograph,"[Tomb of the Virgin, Jerusalem]",,,,,,Artist,,John Anthony,"British, born France, 1823–1901",,"Anthony, John","British, born France",1823,1901,1860s,1860,1869,Albumen silver print from glass negative,Image: 13.9 x 17.2 cm (5 1/2 x 6 3/4 in.) Mount: 27.7 x 37.3 cm (10 7/8 x 14 11/16 in.),"Gift of A. Hyatt Mayor, 1961",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/270439,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -61.641.2.11,false,true,270440,Photographs,Photograph,"[Tomb of Absalom, Zacharias, and St. James]",,,,,,Artist,,John Anthony,"British, born France, 1823–1901",,"Anthony, John","British, born France",1823,1901,1860s,1860,1869,Albumen silver print from glass negative,Image: 15.9 x 21.3 cm (6 1/4 x 8 3/8 in.) Mount: 27.7 x 37.3 cm (10 7/8 x 14 11/16 in.),"Gift of A. Hyatt Mayor, 1961",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/270440,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -46.1.45,false,true,268988,Photographs,Photograph,Cecily Hamilton,,,,,,Artist,,Oscar Gustav Rejlander,"British, born Sweden, 1813–1875",,"Rejlander, Oscar Gustav","British, born Sweden",1813,1875,1863–1867,1863,1867,Albumen silver print from glass negative,,"David Hunter McAlpin Fund, 1946",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268988,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.24,false,true,283096,Photographs,Photograph,Mr. and Miss Constable,,,,,,Artist,,Oscar Gustav Rejlander,"British, born Sweden, 1813–1875",,"Rejlander, Oscar Gustav","British, born Sweden",1813,1875,1866,1866,1866,Albumen silver print from glass negative,Image: 16.8 x 22.1 cm (6 5/8 x 8 11/16 in.) Mount: 21.2 x 28.9 cm (8 3/8 x 11 3/8 in.),"Gilman Collection, Purchase, Harriette and Noel Levine Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/283096,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.159.47,false,true,306330,Photographs,Photograph,His Country's Hope,,,,,,Artist,,Oscar Gustav Rejlander,"British, born Sweden, 1813–1875",,"Rejlander, Oscar Gustav","British, born Sweden",1813,1875,1850s,1850,1859,Albumen silver print from glass negative,Mount: 10 7/8 in. × 13 7/8 in. (27.6 × 35.2 cm) Image: 6 3/16 in. × 8 in. (15.7 × 20.3 cm),"Bequest of Maurice B. Sendak, 2012",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306330,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -63.606.1.33,false,true,270849,Photographs,Photograph,Study of a Head,,,,,,Artist,,Oscar Gustav Rejlander,"British, born Sweden, 1813–1875",,"Rejlander, Oscar Gustav","British, born Sweden",1813,1875,1857,1857,1857,Albumen silver print,,"The Elisha Whittelsey Collection, The Elisha Whittelsey Fund, 1963",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/270849,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.1106,false,true,285657,Photographs,Photograph,The Scholar's Mate,,,,,,Artist,,Oscar Gustav Rejlander,"British, born Sweden, 1813–1875",,"Rejlander, Oscar Gustav","British, born Sweden",1813,1875,1857–59,1857,1859,Albumen silver print,Mount: 16 in. × 12 7/8 in. (40.6 × 32.7 cm) Image: 7 15/16 × 6 9/16 in. (20.2 × 16.7 cm),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285657,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.1164,false,true,285658,Photographs,Photograph,Ariadne,,,,,,Artist,,Oscar Gustav Rejlander,"British, born Sweden, 1813–1875",,"Rejlander, Oscar Gustav","British, born Sweden",1813,1875,1857,1857,1857,Albumen silver print from glass negative,Mount: 16 1/16 in. × 13 1/16 in. (40.8 × 33.2 cm) Image: 8 1/4 × 6 1/2 in. (21 × 16.5 cm); oval,"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285658,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2000.3,false,true,283627,Photographs,Photograph,David D'Angers,,,,,,Artist,,Édouard Baldus,"French, born Prussia, 1813–1889",,"Baldus, Édouard","French, born Prussia",1813,1889,1853,1853,1853,Salted paper print from paper negative,18.9 x 15 cm (7 7/16 x 5 7/8 in. ),"Purchase, Jennifer and Joseph Duke and Harriette and Noel Levine Gifts, 2000",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/283627,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1997.52,false,true,271912,Photographs,Photograph,"The Floods of 1856, Avignon",,,,,,Artist,,Édouard Baldus,"French, born Prussia, 1813–1889",,"Baldus, Édouard","French, born Prussia",1813,1889,1856,1856,1856,Salted paper print from paper negative,30.4 x 43.8 cm (11 15/16 x 17 1/4 in. ),"Purchase, Robert Hurst, Paul F. Walter and Anonymous Gifts; Harris Brisbane Dick Fund, Rogers Fund and Gift of Mrs. Claire K. Feins, in memory of Daniel M. Feins and Linda S. Silverman, by exchange; and funds from various donors, 1997",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/271912,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.67,false,true,290462,Photographs,Photograph,Village de Murols,,,,,,Artist,,Édouard Baldus,"French, born Prussia, 1813–1889",,"Baldus, Édouard","French, born Prussia",1813,1889,1854,1854,1854,Salted paper print from paper negative,Image: 33 x 43.5 cm (13 x 17 1/8 in.) Mount: 47.6 x 62.4 cm (18 3/4 x 24 9/16 in.),"The Horace W. Goldsmith Foundation Fund, through Joyce and Robert Menschel, 2009",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/290462,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -44.55.1,false,true,268839,Photographs,Photograph,Panorama de la Cité,,,,,,Artist,,Édouard Baldus,"French, born Prussia, 1813–1889",,"Baldus, Édouard","French, born Prussia",1813,1889,1860s,1860,1869,Albumen silver print from glass negative,20.3 x 28.3 cm. (8 x 11 1/8 in.),"David Hunter McAlpin Fund, 1944",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268839,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -44.55.4,false,true,268863,Photographs,Photograph,Notre-Dame (Abside),,,,,,Artist,,Édouard Baldus,"French, born Prussia, 1813–1889",,"Baldus, Édouard","French, born Prussia",1813,1889,1860s,1860,1869,Albumen silver print from glass negative,21.7 x 28.6 cm. (8 9/16 x 11 1/4 in.),"David Hunter McAlpin Fund, 1944",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268863,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -44.55.6,false,true,268865,Photographs,Photograph,Arc de triomphe de l'Ètoile,,,,,,Artist,,Édouard Baldus,"French, born Prussia, 1813–1889",,"Baldus, Édouard","French, born Prussia",1813,1889,1860s,1860,1869,Albumen silver print from glass negative,21.1 x 27.0 cm. (8 5/16 x 10 5/8 in.),"David Hunter McAlpin Fund, 1944",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268865,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -44.55.7,false,true,268866,Photographs,Photograph,Notre-Dame (façade),,,,,,Artist,,Édouard Baldus,"French, born Prussia, 1813–1889",,"Baldus, Édouard","French, born Prussia",1813,1889,1860s,1860,1869,Albumen silver print from glass negative,27.6 x 21.1 cm. (10 7/8 x 8 5/16 in.),"David Hunter McAlpin Fund, 1944",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268866,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1994.137,false,true,267002,Photographs,Photograph,[Imperial Library of the Louvre],,,,,,Artist,,Édouard Baldus,"French, born Prussia, 1813–1889",,"Baldus, Édouard","French, born Prussia",1813,1889,1856–57,1856,1857,Salted paper print from glass negative,43.9 x 34.2 cm. (17 1/4 x 13 7/16 in.),"Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 1994",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267002,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1999.228,false,true,282780,Photographs,Photograph,"Château of Princess Mathilde, Enghien",,,,,,Artist,,Édouard Baldus,"French, born Prussia, 1813–1889",,"Baldus, Édouard","French, born Prussia",1813,1889,1854–55,1854,1855,Salted paper print from paper negative,31.7 x 44.4 cm (12 1/2 x 17 1/2 in. ),"Purchase, Louis V. Bell Fund, by exchange, and The Horace W. Goldsmith Foundation Gift through Joyce and Robert Menschel, 1999",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/282780,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2006.565,false,true,287985,Photographs,Photograph,Arc antique à Orange,,,,,,Artist,,Édouard Baldus,"French, born Prussia, 1813–1889",,"Baldus, Édouard","French, born Prussia",1813,1889,1853,1853,1853,Salted paper print from paper negative,Image: 32.4 x 42.4 cm (12 3/4 x 16 11/16 in.) Mount: 48.6 x 63.5 cm (19 1/8 x 25 in.),"Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2006",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287985,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.455,false,true,288843,Photographs,Photograph,"The Floods of 1856, Church of Saint-Pothin, Lyon",,,,,,Artist,,Édouard Baldus,"French, born Prussia, 1813–1889",,"Baldus, Édouard","French, born Prussia",1813,1889,June 1856,1856,1856,Salted paper print from paper negative,Image: 32.5 x 43.5 cm (12 13/16 x 17 1/8 in.),"Funds from various donors, 2007",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288843,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -44.55.23,false,true,268854,Photographs,Photograph,Panthéon,,,,,,Artist,,Édouard Baldus,"French, born Prussia, 1813–1889",,"Baldus, Édouard","French, born Prussia",1813,1889,1860s,1860,1869,Albumen silver print from glass negative,21.7 x 27.0 cm. (8 9/16 x 10 5/8 in.),"David Hunter McAlpin Fund, 1944",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268854,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -44.55.25,false,true,268856,Photographs,Photograph,Palais de l'Industrie,,,,,,Artist,,Édouard Baldus,"French, born Prussia, 1813–1889",,"Baldus, Édouard","French, born Prussia",1813,1889,1850s–60s,1850,1869,Albumen silver print,21.6 x 27.9 cm. (8 1/2 x 11 in.),"David Hunter McAlpin Fund, 1944",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268856,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1990.1012,false,true,266222,Photographs,Photograph,Eglise d'Auvers,,,,,,Artist,,Édouard Baldus,"French, born Prussia, 1813–1889",,"Baldus, Édouard","French, born Prussia",1813,1889,"1855, printed 1855–57",1855,1855,Salted paper print from paper negative,32.4 x 43.2 cm. (12 3/4 x 17 in.),"Purchase, Rogers Fund, Joyce and Robert Menschel Gift and Harriette and Noel Levine Gift, 1990",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/266222,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1990.1127,false,true,266282,Photographs,Photograph,[Roman Arch at Orange],,,,,,Artist,,Édouard Baldus,"French, born Prussia, 1813–1889",,"Baldus, Édouard","French, born Prussia",1813,1889,1851,1851,1851,Salted paper print from paper negative,35.3 x 26.2 cm (13 7/8 x 10 5/16 in.),"Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, and Edward Pearce Casey Fund, 1990",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/266282,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1991.1042,false,true,266331,Photographs,Photograph,"Pavillon de l'Horloge, Louvre",,,,,,Artist,,Édouard Baldus,"French, born Prussia, 1813–1889",,"Baldus, Édouard","French, born Prussia",1813,1889,1852–53,1852,1853,Salted paper print from paper negative,39.5 x 27.7 cm. (15 9/16 x 10 15/16 in.),"Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 1991",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/266331,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1992.5000,true,true,266644,Photographs,Photograph,[Entrance to the Port of Boulogne],,,,,,Artist,,Édouard Baldus,"French, born Prussia, 1813–1889",,"Baldus, Édouard","French, born Prussia",1813,1889,1855,1855,1855,Salted paper print from paper negative,28.8 x 43.5 cm (11 5/16 x 17 1/8 in.),"Purchase, Louis V. Bell Fund, 1992",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/266644,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1992.5002,false,true,266646,Photographs,Photograph,Gare d'Enghien,,,,,,Artist,,Édouard Baldus,"French, born Prussia, 1813–1889",,"Baldus, Édouard","French, born Prussia",1813,1889,1855,1855,1855,Salted paper print from paper negative,Image: 31.3 x 44.3 cm. (12 5/16 x 17 7/16 in.),"Louis V. Bell Fund, 1992",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/266646,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1992.5003,false,true,266647,Photographs,Photograph,Pont en Royans,,,,,,Artist,,Édouard Baldus,"French, born Prussia, 1813–1889",,"Baldus, Édouard","French, born Prussia",1813,1889,ca. 1859,1857,1861,Salted paper print from paper negative,Image: 43.2 x 33.9 cm. (17 x 13 3/6 in.),"Louis V. Bell Fund, 1992",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/266647,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1993.125.1,false,true,266882,Photographs,Photograph,Madeleine Bourquelot de Cervignieres,,,,,,Artist,,Édouard Baldus,"French, born Prussia, 1813–1889",,"Baldus, Édouard","French, born Prussia",1813,1889,1858,1858,1858,Albumen silver print from glass negative,Image: 16.0 x 13.3 cm. (6 5/16 x 5 1/4 in.),"Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 1993",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/266882,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1993.125.2,false,true,266883,Photographs,Photograph,Pierre Bourquelot de Cervignieres,,,,,,Artist,,Édouard Baldus,"French, born Prussia, 1813–1889",,"Baldus, Édouard","French, born Prussia",1813,1889,1858,1858,1858,Albumen silver print from glass negative,Image: 17.4 x 13.7 cm. (6 7/8 x 5 3/8 in.),"Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 1993",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/266883,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1994.179.1,false,true,267017,Photographs,Stereograph,"[Portal, Church of Saint-Trophime, Arles]",,,,,,Artist,,Édouard Baldus,"French, born Prussia, 1813–1889",,"Baldus, Édouard","French, born Prussia",1813,1889,ca. 1864,1862,1866,Albumen silver print from glass negative,7.0 x 14.2 cm. (2 3/4 x 5 9/16 in.),"Gift of Pierre-Marc Richard, 1994",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267017,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1994.179.2,false,true,267018,Photographs,Stereograph,"[Church of Saint-Honorat, Arles]",,,,,,Artist,,Édouard Baldus,"French, born Prussia, 1813–1889",,"Baldus, Édouard","French, born Prussia",1813,1889,ca. 1864,1862,1866,Albumen silver print from glass negative,7.0 x 14.2 cm. (2 3/4 x 5 9/16 in.),"Gift of Pierre-Marc Richard, 1994",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267018,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.50,false,true,283131,Photographs,Photograph,Groupe dans le parc du château de La Faloise,,,,,,Artist,,Édouard Baldus,"French, born Prussia, 1813–1889",,"Baldus, Édouard","French, born Prussia",1813,1889,1857,1857,1857,Salted paper print from glass negative,Mount: 41.6 × 55.3 cm (16 3/8 × 21 3/4 in.) Image: 27.8 × 38.2 cm (10 15/16 × 15 1/16 in.),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/283131,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.364.1,false,true,287310,Photographs,Photograph,Lyon,,,,,,Artist,,Édouard Baldus,"French, born Prussia, 1813–1889",,"Baldus, Édouard","French, born Prussia",1813,1889,ca. 1861,1860,1862,Albumen silver print from glass negative?,Image: 43.2 x 31.1 cm (17 x 12 1/4 in.) Mount: 46 x 60.5 cm (18 1/8 x 23 13/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287310,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.364.2,false,true,287311,Photographs,Photograph,"Lyon, Hôtel de Ville",,,,,,Artist,,Édouard Baldus,"French, born Prussia, 1813–1889",,"Baldus, Édouard","French, born Prussia",1813,1889,ca. 1861,1860,1862,Albumen silver print from glass negative,Image: 33 x 42.7 cm (13 x 16 13/16 in.) Mount: 46 x 60.5 cm (18 1/8 x 23 13/16 in.) Mat: 55.9 x 71.1 cm (22 x 28 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287311,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.364.3,false,true,287312,Photographs,Photograph,"Lyon, Gare de Perrache",,,,,,Artist,,Édouard Baldus,"French, born Prussia, 1813–1889",,"Baldus, Édouard","French, born Prussia",1813,1889,ca. 1861,1860,1862,Albumen silver print from glass negative,Image: 27.9 x 43.3 cm (11 x 17 1/16 in.) Mount: 46 x 60.5 cm (18 1/8 x 23 13/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287312,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.364.4,false,true,287313,Photographs,Photograph,Pont de la Mulatiere,,,,,,Artist,,Édouard Baldus,"French, born Prussia, 1813–1889",,"Baldus, Édouard","French, born Prussia",1813,1889,ca. 1861,1860,1862,Albumen silver print from glass negative,Image: 28 x 44.6 cm (11 x 17 9/16 in.) Mount: 46 x 60.5 cm (18 1/8 x 23 13/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287313,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.364.5,false,true,287314,Photographs,Photograph,Pont de la Mulatiere,,,,,,Artist,,Édouard Baldus,"French, born Prussia, 1813–1889",,"Baldus, Édouard","French, born Prussia",1813,1889,ca. 1861,1860,1862,Albumen silver print from glass negative,Image: 28.3 x 44.3 cm (11 1/8 x 17 7/16 in.) Mount: 46 x 60.5 cm (18 1/8 x 23 13/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287314,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.364.6,false,true,287315,Photographs,Photograph,"Lyon, Viaduc du Rhône",,,,,,Artist,,Édouard Baldus,"French, born Prussia, 1813–1889",,"Baldus, Édouard","French, born Prussia",1813,1889,ca. 1861,1860,1862,Albumen silver print from glass negative,Image: 24.3 x 42.5 cm (9 9/16 x 16 3/4 in.) Mount: 46 x 60.5 cm (18 1/8 x 23 13/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287315,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.364.7,false,true,287316,Photographs,Photograph,"Lyon, Viaduc du Rhône",,,,,,Artist,,Édouard Baldus,"French, born Prussia, 1813–1889",,"Baldus, Édouard","French, born Prussia",1813,1889,ca. 1861,1860,1862,Albumen silver print from glass negative,Image: 26 x 43.3 cm (10 1/4 x 17 1/16 in.) Mount: 46 x 60.5 cm (18 1/8 x 23 13/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287316,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.364.8,false,true,287317,Photographs,Photograph,"Givors, Viaduc",,,,,,Artist,,Édouard Baldus,"French, born Prussia, 1813–1889",,"Baldus, Édouard","French, born Prussia",1813,1889,ca. 1861,1860,1862,Albumen silver print from glass negative,Image: 27.1 x 43 cm (10 11/16 x 16 15/16 in.) Mount: 46 x 60.5 cm (18 1/8 x 23 13/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287317,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.364.9,false,true,287318,Photographs,Photograph,"Givors, Viaduc",,,,,,Artist,,Édouard Baldus,"French, born Prussia, 1813–1889",,"Baldus, Édouard","French, born Prussia",1813,1889,ca. 1861,1860,1862,Albumen silver print from glass negative,Image: 27.5 x 43.9 cm (10 13/16 x 17 5/16 in.) Mount: 46 x 60.5 cm (18 1/8 x 23 13/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287318,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.364.10,false,true,286796,Photographs,Photograph,"Vienne, Souterrain",,,,,,Artist,,Édouard Baldus,"French, born Prussia, 1813–1889",,"Baldus, Édouard","French, born Prussia",1813,1889,ca. 1861,1860,1862,Albumen silver print from glass negative,Image: 32.9 x 42.3 cm (12 15/16 x 16 5/8 in.) Mount: 46 x 60.5 cm (18 1/8 x 23 13/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286796,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.364.11,false,true,287319,Photographs,Photograph,"Vienne, St. Colombe",,,,,,Artist,,Édouard Baldus,"French, born Prussia, 1813–1889",,"Baldus, Édouard","French, born Prussia",1813,1889,ca. 1861,1860,1862,Albumen silver print from glass negative,Image: 32.5 x 42.8 cm (12 13/16 x 16 7/8 in.) Mount: 46 x 60.5 cm (18 1/8 x 23 13/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287319,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.364.12,false,true,287320,Photographs,Photograph,"Vienne, St. Jean",,,,,,Artist,,Édouard Baldus,"French, born Prussia, 1813–1889",,"Baldus, Édouard","French, born Prussia",1813,1889,ca. 1861,1860,1862,Albumen silver print from glass negative,Image: 30.8 x 43.2 cm (12 1/8 x 17 in.) Mount: 46 x 60.5 cm (18 1/8 x 23 13/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287320,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.364.13,false,true,287321,Photographs,Photograph,"Vienne, St. Maurice",,,,,,Artist,,Édouard Baldus,"French, born Prussia, 1813–1889",,"Baldus, Édouard","French, born Prussia",1813,1889,ca. 1861,1860,1862,Albumen silver print from glass negative,Image: 42.5 x 34.1 cm (16 3/4 x 13 7/16 in.) Mount: 46 x 60.5 cm (18 1/8 x 23 13/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287321,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.364.14,false,true,287322,Photographs,Photograph,"Vienne, Souterrain",,,,,,Artist,,Édouard Baldus,"French, born Prussia, 1813–1889",,"Baldus, Édouard","French, born Prussia",1813,1889,ca. 1861,1860,1862,Albumen silver print from glass negative,Image: 33 x 43 cm (13 x 16 15/16 in.) Mount: 46 x 60.5 cm (18 1/8 x 23 13/16 in.) Mat: 55.9 x 71.1 cm (22 x 28 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287322,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.364.15,false,true,287323,Photographs,Photograph,Viaduc de l'Iser,,,,,,Artist,,Édouard Baldus,"French, born Prussia, 1813–1889",,"Baldus, Édouard","French, born Prussia",1813,1889,ca. 1861,1860,1862,Albumen silver print from glass negative,Image: 26.6 x 43.7 cm (10 1/2 x 17 3/16 in.) Mount: 46 x 60.5 cm (18 1/8 x 23 13/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287323,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.364.17,false,true,287325,Photographs,Photograph,Viaduc de la Voulte,,,,,,Artist,,Édouard Baldus,"French, born Prussia, 1813–1889",,"Baldus, Édouard","French, born Prussia",1813,1889,1861 or after,1861,1862,Albumen silver print from glass negative,Image: 25.4 x 42.5 cm (10 x 16 3/4 in.) Mount: 46 x 60.5 cm (18 1/8 x 23 13/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287325,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.364.18,false,true,287326,Photographs,Photograph,Viaduc de la Voulte,,,,,,Artist,,Édouard Baldus,"French, born Prussia, 1813–1889",,"Baldus, Édouard","French, born Prussia",1813,1889,1861 or after,1861,1862,Albumen silver print from glass negative,Image: 30.5 x 43.2 cm (12 x 17 in.) Mount: 46 x 60.5 cm (18 1/8 x 23 13/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287326,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.364.19,false,true,287327,Photographs,Photograph,Viviers,,,,,,Artist,,Édouard Baldus,"French, born Prussia, 1813–1889",,"Baldus, Édouard","French, born Prussia",1813,1889,ca. 1861,1860,1862,Albumen silver print from glass negative,Image: 32.1 x 43.4 cm (12 5/8 x 17 1/16 in.) Mount: 46 x 60.5 cm (18 1/8 x 23 13/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287327,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.364.20,false,true,283135,Photographs,Photograph,Entrée du Robinet,,,,,,Artist,,Édouard Baldus,"French, born Prussia, 1813–1889",,"Baldus, Édouard","French, born Prussia",1813,1889,ca. 1861,1860,1862,Albumen silver print from glass negative,Image: 33.3 x 43.2 cm (13 1/8 x 17 in.) Mount: 46 x 60.5 cm (18 1/8 x 23 13/16 in.) Mat: 62.9 x 72.4 cm (24 3/4 x 28 1/2 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/283135,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.364.21,false,true,287328,Photographs,Photograph,"Orange, Arc Antique",,,,,,Artist,,Édouard Baldus,"French, born Prussia, 1813–1889",,"Baldus, Édouard","French, born Prussia",1813,1889,ca. 1864,1863,1865,Albumen silver print from glass negative,Image: 21.7 x 28.3 cm (8 9/16 x 11 1/8 in.) Mount: 46 x 60.5 cm (18 1/8 x 23 13/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287328,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.364.22,false,true,287329,Photographs,Photograph,"Orange, Théâtre Antique",,,,,,Artist,,Édouard Baldus,"French, born Prussia, 1813–1889",,"Baldus, Édouard","French, born Prussia",1813,1889,ca. 1861,1860,1862,Albumen silver print from glass negative,Image: 42.7 x 33.9 cm (16 13/16 x 13 3/8 in.) Mount: 60.5 x 46 cm (23 13/16 x 18 1/8 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287329,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.364.23,false,true,287330,Photographs,Photograph,"Orange, Théâtre Antique",,,,,,Artist,,Édouard Baldus,"French, born Prussia, 1813–1889",,"Baldus, Édouard","French, born Prussia",1813,1889,ca. 1861,1860,1862,Albumen silver print from glass negative,Image: 44.1 x 34.3 cm (17 3/8 x 13 1/2 in.) Mount: 60.5 x 46 cm (23 13/16 x 18 1/8 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287330,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.364.24,false,true,287331,Photographs,Photograph,Avignon,,,,,,Artist,,Édouard Baldus,"French, born Prussia, 1813–1889",,"Baldus, Édouard","French, born Prussia",1813,1889,ca. 1861,1859,1862,Albumen silver print from glass negative?,Image: 31.8 x 42.2 cm (12 1/2 x 16 5/8 in.) Mount: 46 x 60.5 cm (18 1/8 x 23 13/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287331,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.364.25,false,true,287332,Photographs,Photograph,"Avignon, Pont St. Bénezet",,,,,,Artist,,Édouard Baldus,"French, born Prussia, 1813–1889",,"Baldus, Édouard","French, born Prussia",1813,1889,ca. 1864,1863,1865,Albumen silver print from glass negative,Image: 21.1 x 28.5 cm (8 5/16 x 11 1/4 in.) Mount: 46 x 60.5 cm (18 1/8 x 23 13/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287332,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.364.26,false,true,287333,Photographs,Photograph,Avignon,,,,,,Artist,,Édouard Baldus,"French, born Prussia, 1813–1889",,"Baldus, Édouard","French, born Prussia",1813,1889,1859 or after,1859,1861,Albumen silver print from glass negative,Image: 25.6 x 41.7 cm (10 1/16 x 16 7/16 in.) Mount: 46 x 60.5 cm (18 1/8 x 23 13/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287333,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.364.27,false,true,287334,Photographs,Photograph,Villeneuve les Avignon,,,,,,Artist,,Édouard Baldus,"French, born Prussia, 1813–1889",,"Baldus, Édouard","French, born Prussia",1813,1889,ca. 1862,1861,1864,Albumen silver print from glass negative,Image: 21.6 x 28.6 cm (8 1/2 x 11 1/4 in.) Mount: 46 x 60.5 cm (18 1/8 x 23 13/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287334,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.364.28,false,true,287335,Photographs,Photograph,"Avignon, Palais des Papes",,,,,,Artist,,Édouard Baldus,"French, born Prussia, 1813–1889",,"Baldus, Édouard","French, born Prussia",1813,1889,1859 or after,1859,1861,Albumen silver print from glass negative,Image: 33.6 x 43.4 cm (13 1/4 x 17 1/16 in.) Mount: 46 x 60.5 cm (18 1/8 x 23 13/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287335,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.364.29,false,true,287336,Photographs,Photograph,Vaucluse,,,,,,Artist,,Édouard Baldus,"French, born Prussia, 1813–1889",,"Baldus, Édouard","French, born Prussia",1813,1889,ca. 1862,1861,1864,Albumen silver print from glass negative?,Image: 21.9 x 27.9 cm (8 5/8 x 11 in.) Mount: 46 x 60.5 cm (18 1/8 x 23 13/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287336,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.364.30,false,true,287337,Photographs,Photograph,Saint-Rémy,,,,,,Artist,,Édouard Baldus,"French, born Prussia, 1813–1889",,"Baldus, Édouard","French, born Prussia",1813,1889,ca. 1862,1861,1864,Albumen silver print from glass negative,Image: 21 x 27.4 cm (8 1/4 x 10 13/16 in.) Mount: 46 x 60.5 cm (18 1/8 x 23 13/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287337,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.364.31,false,true,287338,Photographs,Photograph,Saint-Rémy,,,,,,Artist,,Édouard Baldus,"French, born Prussia, 1813–1889",,"Baldus, Édouard","French, born Prussia",1813,1889,ca. 1862,1861,1864,Albumen silver print from glass negative?,Image: 28.6 x 21.7 cm (11 1/4 x 8 9/16 in.) Mount: 60.5 x 46 cm (23 13/16 x 18 1/8 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287338,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.364.32,false,true,287339,Photographs,Photograph,Pont du Gard,,,,,,Artist,,Édouard Baldus,"French, born Prussia, 1813–1889",,"Baldus, Édouard","French, born Prussia",1813,1889,ca. 1861,1860,1862,Albumen silver print from glass negative,Image: 27.1 x 43.2 cm (10 11/16 x 17 in.) Mount: 46 x 60.5 cm (18 1/8 x 23 13/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287339,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.364.33,false,true,287340,Photographs,Photograph,"Durance, Viaduc",,,,,,Artist,,Édouard Baldus,"French, born Prussia, 1813–1889",,"Baldus, Édouard","French, born Prussia",1813,1889,before 1859,1855,1859,Albumen silver print from paper negative,Image: 31.8 x 53 cm (12 1/2 x 20 7/8 in.) Mount: 46 x 60.5 cm (18 1/8 x 23 13/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287340,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.364.34,false,true,287341,Photographs,Photograph,"Tarascon, Viaduc",,,,,,Artist,,Édouard Baldus,"French, born Prussia, 1813–1889",,"Baldus, Édouard","French, born Prussia",1813,1889,ca. 1861,1860,1862,Albumen silver print from glass negative,Image: 27 x 44.2 cm (10 5/8 x 17 3/8 in.) Mount: 46 x 60.5 cm (18 1/8 x 23 13/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287341,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.364.35,false,true,287342,Photographs,Photograph,"Tarascon, Viaduc",,,,,,Artist,,Édouard Baldus,"French, born Prussia, 1813–1889",,"Baldus, Édouard","French, born Prussia",1813,1889,before 1859,1855,1859,Albumen silver print from paper negative,Image: 37.4 x 53.2 cm (14 3/4 x 20 15/16 in.) Mount: 46 x 60.5 cm (18 1/8 x 23 13/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287342,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.364.36,false,true,287343,Photographs,Photograph,"Tarascon, Château",,,,,,Artist,,Édouard Baldus,"French, born Prussia, 1813–1889",,"Baldus, Édouard","French, born Prussia",1813,1889,ca. 1862,1861,1864,Albumen silver print from glass negative,Image: 21.6 x 28.3 cm (8 1/2 x 11 1/8 in.) Mount: 46 x 60.5 cm (18 1/8 x 23 13/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287343,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.364.37,false,true,287344,Photographs,Photograph,Maison Carrée à Nîmes,,,,,,Artist,,Édouard Baldus,"French, born Prussia, 1813–1889",,"Baldus, Édouard","French, born Prussia",1813,1889,1853,1853,1853,Albumen silver print from paper negative,Image: 33 x 44 cm (13 x 17 5/16 in.) Mount: 46 x 60.5 cm (18 1/8 x 23 13/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287344,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.364.38,false,true,287345,Photographs,Photograph,"Nîmes, Amphithéâtre",,,,,,Artist,,Édouard Baldus,"French, born Prussia, 1813–1889",,"Baldus, Édouard","French, born Prussia",1813,1889,ca. 1861,1860,1862,Albumen silver print from glass negative,Image: 32 x 42.4 cm (12 5/8 x 16 11/16 in.) Mount: 46 x 60.5 cm (18 1/8 x 23 13/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287345,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.364.39,false,true,287346,Photographs,Photograph,"Nîmes, Fontaine",,,,,,Artist,,Édouard Baldus,"French, born Prussia, 1813–1889",,"Baldus, Édouard","French, born Prussia",1813,1889,ca. 1861,1860,1862,Albumen silver print from glass negative,Image: 43.2 x 34 cm (17 x 13 3/8 in.) Mount: 60.5 x 46 cm (23 13/16 x 18 1/8 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287346,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.364.40,false,true,287347,Photographs,Photograph,"Nîmes, Temple de Diane",,,,,,Artist,,Édouard Baldus,"French, born Prussia, 1813–1889",,"Baldus, Édouard","French, born Prussia",1813,1889,ca. 1862,1861,1864,Albumen silver print from paper negative,Image: 20.9 x 29.8 cm (8 1/4 x 11 3/4 in.) Mount: 46 x 60.5 cm (18 1/8 x 23 13/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287347,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.364.41,false,true,285452,Photographs,Photograph,"Nîmes, Tour Magne",,,,,,Artist,,Édouard Baldus,"French, born Prussia, 1813–1889",,"Baldus, Édouard","French, born Prussia",1813,1889,1853,1853,1853,Albumen silver print from paper negative,Image: 42.9 x 33 cm (16 7/8 x 13 in.) Mount: 60.5 x 46 cm (23 13/16 x 18 1/8 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285452,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.364.42,false,true,287348,Photographs,Photograph,"Nîmes, Porte d'Auguste",,,,,,Artist,,Édouard Baldus,"French, born Prussia, 1813–1889",,"Baldus, Édouard","French, born Prussia",1813,1889,ca. 1864,1863,1865,Albumen silver print from glass negative,Image: 21.6 x 28.1 cm (8 1/2 x 11 1/16 in.) Mount: 46 x 60.5 cm (18 1/8 x 23 13/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287348,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.364.43,false,true,287349,Photographs,Photograph,Aigues-Mortes,,,,,,Artist,,Édouard Baldus,"French, born Prussia, 1813–1889",,"Baldus, Édouard","French, born Prussia",1813,1889,ca. 1859,1856,1862,Albumen silver print from glass negative,Image: 29.6 x 43.2 cm (11 5/8 x 17 in.) Mount: 46 x 60.5 cm (18 1/8 x 23 13/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287349,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.364.44,false,true,287350,Photographs,Photograph,St. Gilles,,,,,,Artist,,Édouard Baldus,"French, born Prussia, 1813–1889",,"Baldus, Édouard","French, born Prussia",1813,1889,1853,1853,1853,Albumen silver print from paper negative,Image: 34.5 x 42.9 cm (13 9/16 x 16 7/8 in.) Mount: 46 x 60.5 cm (18 1/8 x 23 13/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287350,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.364.45,false,true,287351,Photographs,Photograph,"Arles, St. Trophime",,,,,,Artist,,Édouard Baldus,"French, born Prussia, 1813–1889",,"Baldus, Édouard","French, born Prussia",1813,1889,1861 or after,1861,1862,Albumen silver print from glass negative,Image: 42.6 x 33.8 cm (16 3/4 x 13 5/16 in.) Mount: 60.5 x 46 cm (23 13/16 x 18 1/8 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287351,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.364.46,false,true,283136,Photographs,Photograph,"Arles, Cloitre St. Trophime",,,,,,Artist,,Édouard Baldus,"French, born Prussia, 1813–1889",,"Baldus, Édouard","French, born Prussia",1813,1889,ca. 1861,1860,1862,Albumen silver print from glass negative,Image: 33.2 x 42.5 cm (13 1/16 x 16 3/4 in.) Mount: 46 x 60.5 cm (18 1/8 x 23 13/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/283136,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.364.47,false,true,287352,Photographs,Photograph,"Arles, Cloitre St. Trophime",,,,,,Artist,,Édouard Baldus,"French, born Prussia, 1813–1889",,"Baldus, Édouard","French, born Prussia",1813,1889,ca. 1861,1860,1862,Albumen silver print from glass negative,Image: 34 x 42.7 cm (13 3/8 x 16 13/16 in.) Mount: 46 x 60.5 cm (18 1/8 x 23 13/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287352,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.364.48,false,true,287353,Photographs,Photograph,"Arles, Amphithéâtre",,,,,,Artist,,Édouard Baldus,"French, born Prussia, 1813–1889",,"Baldus, Édouard","French, born Prussia",1813,1889,"before June 1, 1860",1858,1860,Albumen silver print from glass negative?,Image: 33.8 x 43.2 cm (13 5/16 x 17 in.) Mount: 46 x 60.5 cm (18 1/8 x 23 13/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287353,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.364.49,false,true,287354,Photographs,Photograph,"Arles, Amphithéâtre",,,,,,Artist,,Édouard Baldus,"French, born Prussia, 1813–1889",,"Baldus, Édouard","French, born Prussia",1813,1889,1860 or earlier,1857,1860,Albumen silver print from paper negative,Image: 32.3 x 42.3 cm (12 11/16 x 16 5/8 in.) Mount: 46 x 60.5 cm (18 1/8 x 23 13/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287354,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.364.50,false,true,287355,Photographs,Photograph,Théâtre Romain à Arles,,,,,,Artist,,Édouard Baldus,"French, born Prussia, 1813–1889",,"Baldus, Édouard","French, born Prussia",1813,1889,1860 or earlier,1857,1860,Albumen silver print from paper negative,Image: 33.5 x 42.4 cm (13 3/16 x 16 11/16 in.) Mount: 46 x 60.5 cm (18 1/8 x 23 13/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287355,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.364.51,false,true,287356,Photographs,Photograph,Montmajour,,,,,,Artist,,Édouard Baldus,"French, born Prussia, 1813–1889",,"Baldus, Édouard","French, born Prussia",1813,1889,ca. 1862,1861,1864,Albumen silver print from glass negative,Image: 19.6 x 28.1 cm (7 11/16 x 11 1/16 in.) Mount: 46 x 60.5 cm (18 1/8 x 23 13/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287356,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.364.52,false,true,287357,Photographs,Photograph,Viaduc de St. Chamas,,,,,,Artist,,Édouard Baldus,"French, born Prussia, 1813–1889",,"Baldus, Édouard","French, born Prussia",1813,1889,before 1859,1856,1859,Albumen silver print from paper negative,Image: 36 x 54.3 cm (14 3/16 x 21 3/8 in.) Mount: 46 x 60.5 cm (18 1/8 x 23 13/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287357,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.364.53,false,true,287358,Photographs,Photograph,Roquefavour,,,,,,Artist,,Édouard Baldus,"French, born Prussia, 1813–1889",,"Baldus, Édouard","French, born Prussia",1813,1889,ca. 1861,1860,1862,Albumen silver print from paper negative,Image: 33.6 x 42.7 cm (13 1/4 x 16 13/16 in.) Mount: 46 x 60.5 cm (18 1/8 x 23 13/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287358,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.364.54,false,true,287359,Photographs,Photograph,Souterrain de la Nerthe,,,,,,Artist,,Édouard Baldus,"French, born Prussia, 1813–1889",,"Baldus, Édouard","French, born Prussia",1813,1889,ca. 1861,1860,1862,Albumen silver print from paper negative,Image: 32.5 x 43.4 cm (12 13/16 x 17 1/16 in.) Mount: 46 x 60.5 cm (18 1/8 x 23 13/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287359,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.364.55,false,true,287360,Photographs,Photograph,Marseille,,,,,,Artist,,Édouard Baldus,"French, born Prussia, 1813–1889",,"Baldus, Édouard","French, born Prussia",1813,1889,late 1850s (?),1856,1860,Albumen silver print from paper negative,Image: 30.1 x 40.7 cm (11 7/8 x 16 in.) Mount: 46 x 60.5 cm (18 1/8 x 23 13/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287360,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.364.56,false,true,287361,Photographs,Photograph,Marseille,,,,,,Artist,,Édouard Baldus,"French, born Prussia, 1813–1889",,"Baldus, Édouard","French, born Prussia",1813,1889,late 1850s (?),1856,1860,Albumen silver print from paper negative,Image: 32.6 x 42.7 cm (12 13/16 x 16 13/16 in.) Mount: 46 x 60.5 cm (18 1/8 x 23 13/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287361,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.364.57,false,true,287362,Photographs,Photograph,Marseille,,,,,,Artist,,Édouard Baldus,"French, born Prussia, 1813–1889",,"Baldus, Édouard","French, born Prussia",1813,1889,ca. 1860,1859,1861,Albumen silver print from glass negative,Image: 27.6 x 42.9 cm (10 7/8 x 16 7/8 in.) Mount: 46 x 60.5 cm (18 1/8 x 23 13/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287362,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.364.58,false,true,287363,Photographs,Photograph,Marseille,,,,,,Artist,,Édouard Baldus,"French, born Prussia, 1813–1889",,"Baldus, Édouard","French, born Prussia",1813,1889,ca. 1861,1860,1862,Albumen silver print from glass negative,Image: 27.1 x 42.5 cm (10 11/16 x 16 3/4 in.) Mount: 46 x 60.5 cm (18 1/8 x 23 13/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287363,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.364.59,false,true,287364,Photographs,Photograph,Marseille,,,,,,Artist,,Édouard Baldus,"French, born Prussia, 1813–1889",,"Baldus, Édouard","French, born Prussia",1813,1889,ca. 1861,1860,1862,Albumen silver print from glass negative,Image: 26.7 x 40 cm (10 1/2 x 15 3/4 in.) Mount: 46 x 60.5 cm (18 1/8 x 23 13/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287364,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.364.60,false,true,287365,Photographs,Photograph,Marseille,,,,,,Artist,,Édouard Baldus,"French, born Prussia, 1813–1889",,"Baldus, Édouard","French, born Prussia",1813,1889,ca. 1861,1860,1862,Albumen silver print from glass negative,Image: 27.2 x 41.8 cm (10 11/16 x 16 7/16 in.) Mount: 46 x 60.5 cm (18 1/8 x 23 13/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287365,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.364.62,false,true,287367,Photographs,Photograph,La Ciotat,,,,,,Artist,,Édouard Baldus,"French, born Prussia, 1813–1889",,"Baldus, Édouard","French, born Prussia",1813,1889,ca. 1860,1859,1861,Albumen silver print from paper negative,Image: 32.4 x 43 cm (12 3/4 x 16 15/16 in.) Mount: 46 x 60.5 cm (18 1/8 x 23 13/16 in.) Mount: 55.9 x 71.1 cm (22 x 28 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287367,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.364.63,false,true,287368,Photographs,Photograph,"La Ciotat, Bec de l'Aigle",,,,,,Artist,,Édouard Baldus,"French, born Prussia, 1813–1889",,"Baldus, Édouard","French, born Prussia",1813,1889,ca. 1861,1860,1862,Albumen silver print from paper negative,Image: 32.1 x 42.3 cm (12 5/8 x 16 5/8 in.) Mount: 46 x 60.5 cm (18 1/8 x 23 13/16 in.) Mat: 55.9 x 71.1 cm (22 x 28 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287368,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.364.64,false,true,287283,Photographs,Photograph,Le Moine,,,,,,Artist,,Édouard Baldus,"French, born Prussia, 1813–1889",,"Baldus, Édouard","French, born Prussia",1813,1889,ca. 1861,1860,1862,Albumen silver print from paper negative,Image: 33.7 x 42.8 cm (13 1/4 x 16 7/8 in.) Mount: 46 x 60.5 cm (18 1/8 x 23 13/16 in.) Mat: 55.9 x 71.1 cm (22 x 28 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287283,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.364.65,false,true,287369,Photographs,Photograph,Bandol,,,,,,Artist,,Édouard Baldus,"French, born Prussia, 1813–1889",,"Baldus, Édouard","French, born Prussia",1813,1889,ca. 1860,1859,1861,Albumen silver print from paper negative?,Image: 33.1 x 42.9 cm (13 1/16 x 16 7/8 in.) Mount: 46 x 60.5 cm (18 1/8 x 23 13/16 in.) Mat: 55.9 x 71.1 cm (22 x 28 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287369,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.364.66,false,true,287370,Photographs,Photograph,Viaduc de Bandol,,,,,,Artist,,Édouard Baldus,"French, born Prussia, 1813–1889",,"Baldus, Édouard","French, born Prussia",1813,1889,1859 or after,1859,1862,Albumen silver print from paper negative,Image: 26.5 x 43 cm (10 7/16 x 16 15/16 in.) Mount: 46 x 60.5 cm (18 1/8 x 23 13/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287370,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.364.67,false,true,287371,Photographs,Photograph,St. Nazaire,,,,,,Artist,,Édouard Baldus,"French, born Prussia, 1813–1889",,"Baldus, Édouard","French, born Prussia",1813,1889,1859 or after,1859,1862,Albumen silver print from paper negative,Image: 32 x 43 cm (12 5/8 x 16 15/16 in.) Mount: 46 x 60.5 cm (18 1/8 x 23 13/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287371,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.364.68,false,true,287372,Photographs,Photograph,Gorges d'Ollioules,,,,,,Artist,,Édouard Baldus,"French, born Prussia, 1813–1889",,"Baldus, Édouard","French, born Prussia",1813,1889,ca. 1860,1859,1861,Albumen silver print from paper negative?,Image: 32.5 x 42 cm (12 13/16 x 16 9/16 in.) Mount: 46 x 60.5 cm (18 1/8 x 23 13/16 in.) Mat: 55.9 x 71.1 cm (22 x 28 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287372,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.364.69,false,true,285453,Photographs,Photograph,Toulon,,,,,,Artist,,Édouard Baldus,"French, born Prussia, 1813–1889",,"Baldus, Édouard","French, born Prussia",1813,1889,ca. 1861,1860,1862,Albumen silver print from glass negative,Image: 30.7 x 43.6 cm (12 1/16 x 17 3/16 in.) Mount: 46 x 60.5 cm (18 1/8 x 23 13/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285453,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.364.16.1-.2,false,true,287324,Photographs,Panorama,La Voulte,,,,,,Artist,,Édouard Baldus,"French, born Prussia, 1813–1889",,"Baldus, Édouard","French, born Prussia",1813,1889,1861,1861,1861,Albumen silver print from glass negative,Image: 26.3 x 41.7 cm (10 3/8 x 16 7/16 in.) left Image: 26.3 x 41.4 cm (10 3/8 x 16 5/16 in.) right Mount: 46 x 121 cm (18 1/8 x 47 5/8 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287324,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.364.61.1-.2,false,true,287366,Photographs,Panorama,Marseille,,,,,,Artist,,Édouard Baldus,"French, born Prussia, 1813–1889",,"Baldus, Édouard","French, born Prussia",1813,1889,ca. 1861,1860,1862,Albumen silver print from glass negative,Image: 29.3 x 40.8 cm (11 9/16 x 16 1/16 in.) left Image: 29.3 x 40.8 cm (11 9/16 x 16 1/16 in.) right Mount: 46 x 121 cm (18 1/8 x 47 5/8 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287366,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -52.658.1,false,true,269641,Photographs,Photograph,[Young Male Nude Seated on Leopard Skin],,,,,,Artist,,Guglielmo Plüshow,Italian (born Germany) 1852–1930,,"Plüshow, Guglielmo","Italian, born Germany",1852,1930,1890s–1900s,1890,1909,Albumen silver print from glass negative,22.2 x 16.2 cm. (8 3/4 x 6 3/8 in.),Museum Accession,,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269641,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2012.111,false,true,299472,Photographs,Photographs,"[Plaster Casts of Bodies, Pompeii]",,,,,,Artist,,Giorgio Sommer,"Italian, born Germany, 1834–1914",,"Sommer, Giorgio","Italian, born Germany",1834,1914,ca. 1875,1870,1880,Albumen print from glass negative,Image: 27.3 x 38.4 cm (10 3/4 x 15 1/8 in.) Sheet: 27.8 x 38.4 cm (10 15/16 x 15 1/8 in.) Mount: 47.8 x 55.8 cm (18 13/16 x 21 15/16 in.),"Purchase, Harriet Ames Charitable Trust Gift, 2012",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/299472,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -52.658.2,false,true,269642,Photographs,Photograph,[Reclining Male Nude Beside Vase],,,,,,Artist,,Wilhelm von Gloeden,"Italian, born Germany, 1886–1931",,"Gloeden, Wilhelm von","Italian, born Germany",1886,1931,1890s–1900s,1890,1909,Albumen silver print from glass negative,Image: 16.4 x 23.2cm (6 7/16 x 9 1/8in.) Mount: 27.9 x 35 cm (11 x 13 3/4 in.),Museum Accession,,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269642,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1983.1201.1,false,true,263857,Photographs,Photograph,"[Young Girl with Flowers, Sicily, Italy]",,,,,,Artist,,Wilhelm von Gloeden,"Italian, born Germany, 1886–1931",,"Gloeden, Wilhelm von","Italian, born Germany",1886,1931,1903,1903,1903,Albumen silver print from glass negative,21.9 x 16.9 cm. (8 5/8 x 6 5/8 in.),"Gift of Milton Radutzky, 1983",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/263857,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1983.1201.2,false,true,263859,Photographs,Photograph,"[Elderly Man and Young Boy at Garden Fountain, Sicily, Italy]",,,,,,Artist,,Wilhelm von Gloeden,"Italian, born Germany, 1886–1931",,"Gloeden, Wilhelm von","Italian, born Germany",1886,1931,1902,1902,1902,Albumen silver print from glass negative,16.9 x 22.6 cm. (6 5/8 x 8 7/8 in.),"Gift of Milton Radutzky, 1983",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/263859,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1983.1201.3,false,true,263860,Photographs,Photograph,"[Young Woman, Sicily, Italy]",,,,,,Artist,,Wilhelm von Gloeden,"Italian, born Germany, 1886–1931",,"Gloeden, Wilhelm von","Italian, born Germany",1886,1931,1890s–1900s,1890,1909,Albumen silver print from glass negative,22.1 x 16.6 cm. (8 11/16 x 6 9/16 in.),"Gift of Milton Radutzky, 1983",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/263860,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1983.1201.4,false,true,263861,Photographs,Photograph,"[Young Man in White Robe and Head Gear Holding Scabbard, Sicily, Italy]",,,,,,Artist,,Wilhelm von Gloeden,"Italian, born Germany, 1886–1931",,"Gloeden, Wilhelm von","Italian, born Germany",1886,1931,1890s–1900s,1890,1909,Albumen silver print from glass negative,22.3 x 16.5 cm. (8 3/4 x 6 1/2 in.),"Gift of Milton Radutzky, 1983",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/263861,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1983.1201.5,false,true,263862,Photographs,Photograph,"[Young Girl in Checked Dress with Roses, Sicily, Italy]",,,,,,Artist,,Wilhelm von Gloeden,"Italian, born Germany, 1886–1931",,"Gloeden, Wilhelm von","Italian, born Germany",1886,1931,1899,1899,1899,Albumen silver print from glass negative,22.5 x 17.0 cm. (8 7/8 x 6 11/16 in.),"Gift of Milton Radutzky, 1983",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/263862,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1983.1201.6,false,true,263863,Photographs,Photograph,"[Nude Young Child with Dog in Lap, Sicily, Italy]",,,,,,Artist,,Wilhelm von Gloeden,"Italian, born Germany, 1886–1931",,"Gloeden, Wilhelm von","Italian, born Germany",1886,1931,1890s–1900s,1890,1909,Albumen silver print from glass negative,22.5 x 16.5 cm. (8 7/8 x 6 1/2 in.),"Gift of Milton Radutzky, 1983",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/263863,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1983.1201.7,false,true,263864,Photographs,Photograph,"[Young Girl [?] with Cloak of Cloth Over Head, Sicily, Italy]",,,,,,Artist,,Wilhelm von Gloeden,"Italian, born Germany, 1886–1931",,"Gloeden, Wilhelm von","Italian, born Germany",1886,1931,1906,1906,1906,Albumen silver print from glass negative,21.8 x 16.5 cm. (8 9/16 x 6 1/2 in.),"Gift of Milton Radutzky, 1983",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/263864,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1983.1201.8,false,true,263865,Photographs,Photograph,"Mico Lo Giudice-Berbiredolu ""Il mago del mandolino""",,,,,,Artist,,Wilhelm von Gloeden,"Italian, born Germany, 1886–1931",,"Gloeden, Wilhelm von","Italian, born Germany",1886,1931,1890s–1900s,1890,1909,Albumen silver print from glass negative,22.3 x 16.9 cm. (8 3/4 x 6 5/8 in.),"Gift of Milton Radutzky, 1983",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/263865,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1983.1201.9,false,true,263866,Photographs,Photograph,"[Young Girl Wrapped in Cloth, Sicily, Italy]",,,,,,Artist,,Wilhelm von Gloeden,"Italian, born Germany, 1886–1931",,"Gloeden, Wilhelm von","Italian, born Germany",1886,1931,1890s–1900s,1890,1909,Albumen silver print from glass negative,21.7 x 16.3 cm. (8 9/16 x 6 7/16 in.),"Gift of Milton Radutzky, 1983",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/263866,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1983.1201.10,false,true,263858,Photographs,Photograph,"[Nude Study: Woman from Behind, Young Man, Sicily, Italy]",,,,,,Artist,,Wilhelm von Gloeden,"Italian, born Germany, 1886–1931",,"Gloeden, Wilhelm von","Italian, born Germany",1886,1931,1890s–1900s,1890,1909,Albumen silver print from glass negative,21.9 x 16.6 cm. (8 5/8 x 6 9/16 in.),"Gift of Milton Radutzky, 1983",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/263858,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.1138,false,true,286064,Photographs,Photograph,[Two Children],,,,,,Artist,,Wilhelm von Gloeden,"Italian, born Germany, 1886–1931",,"Gloeden, Wilhelm von","Italian, born Germany",1886,1931,ca. 1900,1898,1902,Gelatin silver print,Image: 14 5/16 × 10 11/16 in. (36.4 × 27.2 cm),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286064,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.1166,false,true,286608,Photographs,Photograph,[Man],,,,,,Artist,,Wilhelm von Gloeden,"Italian, born Germany, 1886–1931",,"Gloeden, Wilhelm von","Italian, born Germany",1886,1931,ca. 1900,1898,1902,Gelatin silver print,8 3/4 x 6 2/3,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286608,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.582,false,true,286232,Photographs,Photograph,"Tadeus Langier, Zakopane",,,,,,Artist,,Stanislaw Ignacy Witkiewicz,"Polish, Warsaw 1885–1939 Jeziory",,"Witkiewicz, Stanislaw Ignacy",Polish,1885,1939,1912–13,1912,1913,Gelatin silver print,Image: 12.6 x 17.6 cm (4 15/16 x 6 15/16 in.) Mount: 17.4 x 23.5 cm (6 7/8 x 9 1/4 in.),"Gilman Collection, Purchase, Denise and Andrew Saul Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286232,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.896,false,true,286234,Photographs,Photograph,"Jadwiga Janczewska, Zakopane",,,,,,Artist,,Stanislaw Ignacy Witkiewicz,"Polish, Warsaw 1885–1939 Jeziory",,"Witkiewicz, Stanislaw Ignacy",Polish,1885,1939,ca. 1913,1912,1914,Gelatin silver print,Image: 12.5 x 17.4 cm (4 15/16 x 6 7/8 in.),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286234,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.899,false,true,286054,Photographs,Photograph,"[Self-Portrait, ""Collapse, with Lamp"", Zakopane]",,,,,,Artist,,Stanislaw Ignacy Witkiewicz,"Polish, Warsaw 1885–1939 Jeziory",,"Witkiewicz, Stanislaw Ignacy",Polish,1885,1939,ca. 1913,1912,1914,Gelatin silver print,Image: 13 x 18.1 cm (5 1/8 x 7 1/8 in.),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286054,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.867,false,true,286118,Photographs,Photograph,Plan of the Socialist Offensive,,,,,,Artist,,Gustav Klutsis,"Russian, Latvia 1895–1938 Moscow",,"Klutsis, Gustav",Russian,1895,1938,1929–30,1929,1930,Gelatin silver print,Image: 7 3/8 × 5 1/2 in. (18.7 × 13.9 cm) Mount: 11 5/8 in. × 8 3/8 in. (29.6 × 21.2 cm),"Gilman Collection, Purchase, Denise and Andrew Saul Gift and Anonymous Gifts, by exchange, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286118,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2001.678,false,true,284734,Photographs,Photograph,"[Government Troops Firing on Demonstrators, Corner of Nevsky Prospect and Sadovaya Street, St. Petersburg, Russia]",,,,,,Artist,,Karl Karlovich Bulla,"Russian, born Germany, 1853–1929",,"Bulla, Karl Karlovich","Russian, born Germany",1853,1929,"July 4, 1917",1917,1917,Gelatin silver print,12.2 x 21.5cm (4 13/16 x 8 7/16 in.),"Purchase, Jennifer and Joseph Duke Gift, 2001",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/284734,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.82,false,true,283182,Photographs,Photograph,"Kno-Shr, Kansas Chief",,,,,,Artist,,John H. Fitzgibbon,"American, born Britain, 1816–1882",,"Fitzgibbon, John H.","American, born Britain",1816,1882,1853,1853,1853,Daguerreotype,Image: 17.9 x 14.8 cm (7 1/16 x 5 13/16 in.),"Gilman Collection, Purchase, Alfred Stieglitz Society Gifts, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/283182,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1991.1203,false,true,266499,Photographs,Photograph,"Sausalito from the N.P.C.R.R. Wharf, Looking South",,,,,,Artist,,Eadweard Muybridge,"American, born Britain, 1830–1904",,"Muybridge, Eadweard","American, born Britain",1830,1904,ca. 1868,1866,1870,Albumen silver print from glass negative,9.8 x 9.5cm (3 7/8 x 3 3/4in.) Mount: 22.8 x 17.6 cm (9 x 6 15/16 in.),"Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 1991",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/266499,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.780,false,true,286079,Photographs,Photograph,"Crater of Volcano, Quetzaltenango-Guatemala",,,,,,Artist,,Eadweard Muybridge,"American, born Britain, 1830–1904",,"Muybridge, Eadweard","American, born Britain",1830,1904,1875,1875,1875,Albumen silver print from glass negative,Image: 5 3/8 × 9 1/16 in. (13.7 × 23 cm),"Gilman Collection, Purchase, Sam Salz Foundation Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286079,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.781,false,true,286562,Photographs,Photograph,"Coffee Harvesting, Las Nubes-Guatemala",,,,,,Artist,,Eadweard Muybridge,"American, born Britain, 1830–1904",,"Muybridge, Eadweard","American, born Britain",1830,1904,1875,1875,1875,Albumen silver print from glass negative,Image: 5 3/8 × 9 1/8 in. (13.7 × 23.2 cm),"Gilman Collection, Purchase, Gift of Photography in the Fine Arts, by exchange, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286562,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.101,false,true,283211,Photographs,Photograph,"Mormon Emigrant Train, Echo Canyon",,,,,,Artist,,Charles William Carter,"American, born Britain, 1832–1918",,"Carter, Charles William","American, born Britain",1832,1918,ca. 1870,1868,1872,Albumen silver print from glass negative,Image: 6.1 × 10.3 cm (2 3/8 × 4 1/16 in.),"Gilman Collection, Purchase, Marlene Nathan Meyerson Family Foundation Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/283211,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -X.677,false,true,271673,Photographs,Cabinet card,[Man with Side Whiskers],,,,,,Artist,,Frederick Gutekunst,"American, born Germany, 1832–1917",,"Gutekunst, Frederick","American, born Germany",1832,1917,1870s–80s,1870,1889,Albumen silver print from glass negative,,Museum Accession,,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/271673,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1985.1080.3,false,true,264322,Photographs,Photograph,"[Girl with Ringlets, Half Length]",,,,,,Artist,,Frederick Gutekunst,"American, born Germany, 1832–1917",,"Gutekunst, Frederick","American, born Germany",1832,1917,1890s,1890,1899,Gelatin silver print,14.4 x 9.4 cm. (5 11/16 x 3 11/16 in.),"Museum Accession, 1985",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/264322,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1985.1080.5,false,true,264324,Photographs,Photograph,"[Girl with Ringlets, Seated, Three-Quarter Length]",,,,,,Artist,,Frederick Gutekunst,"American, born Germany, 1832–1917",,"Gutekunst, Frederick","American, born Germany",1832,1917,1890s,1890,1899,Gelatin silver print,14.2 x 9.7 cm. (5 9/16 x 3 13/16 in.),"Museum Accession, 1985",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/264324,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1985.1080.6,false,true,264325,Photographs,Photograph,[Girl in Walking Costume with Hat and Muff],,,,,,Artist,,Frederick Gutekunst,"American, born Germany, 1832–1917",,"Gutekunst, Frederick","American, born Germany",1832,1917,1890s,1890,1899,Gelatin silver print,14.1 x 8.8 cm. (5 9/16 x 3 7/16 in.),"Museum Accession, 1985",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/264325,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1985.1080.15,false,true,264320,Photographs,Photograph,[Girl with White Off-the-Shoulder Dress],,,,,,Artist,,Frederick Gutekunst,"American, born Germany, 1832–1917",,"Gutekunst, Frederick","American, born Germany",1832,1917,1890s,1890,1899,Gelatin silver print,14.5 x 9.5 cm. (5 11/16 x 3 3/4 in.),"Museum Accession, 1985",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/264320,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2005.100.753a, b",false,true,286587,Photographs,Photograph,Dr. Joseph Parrish and an Idiot,,,,,,Artist,,Frederick Gutekunst,"American, born Germany, 1832–1917",,"Gutekunst, Frederick","American, born Germany",1832,1917,ca. 1858,1856,1860,Albumen silver print from glass negative,Image: 7 5/16 × 4 3/4 in. (18.6 × 12 cm); (a) Image: 8 in. × 5 1/4 in. (20.3 × 13.3 cm); (b) Mount: 14 7/16 × 10 5/16 in. (36.6 × 26.2 cm),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286587,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.36,false,true,669901,Photographs,Photograph,Mouth of Wisconsin River,,,,,,Artist,,Henry P. Bosse,"American, born Germany, 1844–1893",,"Bosse, Henry P.","American, born Germany",1844,1893,1885,1885,1885,Cyanotype,"Image: 10 9/16 × 13 1/2 in. (26.8 × 34.3 cm), oval Sheet: 14 7/16 × 17 3/16 in. (36.7 × 43.7 cm)","Purchase, Acquisitions Fund and Mr. and Mrs. John A. Moran Gift, in memory of Louise Chisholm Moran, 2015",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/669901,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.38,false,true,669903,Photographs,Photograph,"Old Ponton Bridge at N. McGregor, Ia.",,,,,,Artist,,Henry P. Bosse,"American, born Germany, 1844–1893",,"Bosse, Henry P.","American, born Germany",1844,1893,1885,1885,1885,Cyanotype,"Image: 10 7/16 × 13 1/8 in. (26.5 × 33.4 cm), oval Sheet: 14 1/2 × 17 3/16 in. (36.8 × 43.7 cm)","Purchase, Acquisitions Fund and Mr. and Mrs. John A. Moran Gift, in memory of Louise Chisholm Moran, 2015",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/669903,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.39,false,true,669906,Photographs,Photograph,Pine Bend,,,,,,Artist,,Henry P. Bosse,"American, born Germany, 1844–1893",,"Bosse, Henry P.","American, born Germany",1844,1893,1885,1885,1885,Cyanotype,"Image: 10 9/16 × 13 1/2 in. (26.8 × 34.3 cm), oval Sheet: 14 7/16 × 17 3/16 in. (36.7 × 43.7 cm)","Purchase, Acquisitions Fund and Mr. and Mrs. John A. Moran Gift, in memory of Louise Chisholm Moran, 2015",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/669906,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2014.715.1,false,true,669900,Photographs,Photograph,"No. 34. From Bluffs at Merrimac, Minnesota Looking Down Stream",,,,,,Artist,,Henry P. Bosse,"American, born Germany, 1844–1893",,"Bosse, Henry P.","American, born Germany",1844,1893,1885,1885,1885,Cyanotype,Sheet: 14 1/2 × 17 3/16 in. (36.8 × 43.7 cm),"Gift of Charles Wehrenberg and Sally Larsen, 2014",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/669900,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2014.715.2,false,true,669904,Photographs,Photograph,"No. 6. From South Approach of Franklin Ave Bridge, Minneapolis, Minnesota Looking Up Stream (Low Water)",,,,,,Artist,,Henry P. Bosse,"American, born Germany, 1844–1893",,"Bosse, Henry P.","American, born Germany",1844,1893,1890,1890,1890,Cyanotype,Sheet: 14 1/2 × 17 3/16 in. (36.8 × 43.7 cm),"Gift of Charles Wehrenberg and Sally Larsen, 2014",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/669904,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2014.715.3,false,true,669905,Photographs,Photograph,No. 21. Rocks and Dam below Frenchmans Bar (Low Water),,,,,,Artist,,Henry P. Bosse,"American, born Germany, 1844–1893",,"Bosse, Henry P.","American, born Germany",1844,1893,1889,1889,1889,Cyanotype,Sheet: 14 1/2 × 17 3/16 in. (36.8 × 43.7 cm),"Gift of Charles Wehrenberg and Sally Larsen, 2014",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/669905,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2014.715.4,false,true,669907,Photographs,Photograph,"No. 69. Wabasha, Minnesota",,,,,,Artist,,Henry P. Bosse,"American, born Germany, 1844–1893",,"Bosse, Henry P.","American, born Germany",1844,1893,1889,1889,1889,Cyanotype,Sheet: 14 1/2 × 17 3/16 in. (36.8 × 43.7 cm),"Gift of Charles Wehrenberg and Sally Larsen, 2014",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/669907,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2014.715.5,false,true,669908,Photographs,Photograph,"No. 88. Wingdams below Winona, Minnesota",,,,,,Artist,,Henry P. Bosse,"American, born Germany, 1844–1893",,"Bosse, Henry P.","American, born Germany",1844,1893,1889,1889,1889,Cyanotype,Sheet: 14 1/2 × 17 3/16 in. (36.8 × 43.7 cm),"Gift of Charles Wehrenberg and Sally Larsen, 2014",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/669908,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2014.715.6,false,true,669909,Photographs,Photograph,"No. 90. From bluffs at Trempealueau, Wisconsin Looking Up Stream",,,,,,Artist,,Henry P. Bosse,"American, born Germany, 1844–1893",,"Bosse, Henry P.","American, born Germany",1844,1893,1885,1885,1885,Cyanotype,Sheet: 14 1/2 × 17 3/16 in. (36.8 × 43.7 cm),"Gift of Charles Wehrenberg and Sally Larsen, 2014",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/669909,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2014.715.7,false,true,669910,Photographs,Photograph,"No. 135. Iowa State Penitentiary - Fort Madison, Iowa",,,,,,Artist,,Henry P. Bosse,"American, born Germany, 1844–1893",,"Bosse, Henry P.","American, born Germany",1844,1893,1891,1891,1891,Cyanotype,Sheet: 14 1/2 × 17 3/16 in. (36.8 × 43.7 cm),"Gift of Charles Wehrenberg and Sally Larsen, 2014",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/669910,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2014.715.8,false,true,669911,Photographs,Photograph,"No. 139a. Head of Niota Chute with Closing Dam [near Fort Madison, Iowa]",,,,,,Artist,,Henry P. Bosse,"American, born Germany, 1844–1893",,"Bosse, Henry P.","American, born Germany",1844,1893,1885,1885,1885,Cyanotype,Sheet: 14 1/2 × 17 3/16 in. (36.8 × 43.7 cm),"Gift of Charles Wehrenberg and Sally Larsen, 2014",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/669911,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2014.715.9,false,true,669912,Photographs,Photograph,No. 155a. Lower Lock Des Moines Rapids Canal,,,,,,Artist,,Henry P. Bosse,"American, born Germany, 1844–1893",,"Bosse, Henry P.","American, born Germany",1844,1893,1891,1891,1891,Cyanotype,Sheet: 14 1/2 × 17 3/16 in. (36.8 × 43.7 cm),"Gift of Charles Wehrenberg and Sally Larsen, 2014",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/669912,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2014.715.10,false,true,669913,Photographs,Photograph,No. 167. Raftboat “David Bronson”,,,,,,Artist,,Henry P. Bosse,"American, born Germany, 1844–1893",,"Bosse, Henry P.","American, born Germany",1844,1893,1885,1885,1885,Cyanotype,Sheet: 14 1/2 × 17 3/16 in. (36.8 × 43.7 cm),"Gift of Charles Wehrenberg and Sally Larsen, 2014",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/669913,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2014.715.11,false,true,669914,Photographs,Photograph,"No. 181. Marshall Ave. Bridge, Minneapolis & St. Paul",,,,,,Artist,,Henry P. Bosse,"American, born Germany, 1844–1893",,"Bosse, Henry P.","American, born Germany",1844,1893,1889,1889,1889,Cyanotype,Sheet: 14 1/2 × 17 3/16 in. (36.8 × 43.7 cm),"Gift of Charles Wehrenberg and Sally Larsen, 2014",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/669914,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2014.715.12,false,true,669915,Photographs,Photograph,"No. 185. Chicago, Milwaukee & St. Paul Rail Road Bridge at Hasting, Minnesota",,,,,,Artist,,Henry P. Bosse,"American, born Germany, 1844–1893",,"Bosse, Henry P.","American, born Germany",1844,1893,1885,1885,1885,Cyanotype,Sheet: 14 1/2 × 17 3/16 in. (36.8 × 43.7 cm),"Gift of Charles Wehrenberg and Sally Larsen, 2014",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/669915,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2014.715.13,false,true,669916,Photographs,Photograph,"No. 186. Chicago, Burlington & Northern Rail Road Bridge Across Mouth of La Croix River",,,,,,Artist,,Henry P. Bosse,"American, born Germany, 1844–1893",,"Bosse, Henry P.","American, born Germany",1844,1893,1891,1891,1891,Cyanotype,Sheet: 14 1/2 × 17 3/16 in. (36.8 × 43.7 cm),"Gift of Charles Wehrenberg and Sally Larsen, 2014",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/669916,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2014.715.14,false,true,669917,Photographs,Photograph,"No. 193a. Old Ponton Bridge at Prairie du chien, Wisconsin",,,,,,Artist,,Henry P. Bosse,"American, born Germany, 1844–1893",,"Bosse, Henry P.","American, born Germany",1844,1893,1885,1885,1885,Cyanotype,Sheet: 14 1/2 × 17 3/16 in. (36.8 × 43.7 cm),"Gift of Charles Wehrenberg and Sally Larsen, 2014",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/669917,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2014.715.15,false,true,669918,Photographs,Photograph,"No. 199. Draw Span of Chicago & North Western Rail Road Bridge at Clinton, Iowa",,,,,,Artist,,Henry P. Bosse,"American, born Germany, 1844–1893",,"Bosse, Henry P.","American, born Germany",1844,1893,1885,1885,1885,Cyanotype,Sheet: 14 1/2 × 17 3/16 in. (36.8 × 43.7 cm),"Gift of Charles Wehrenberg and Sally Larsen, 2014",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/669918,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2014.715.17,false,true,669920,Photographs,Photograph,"No. 204. Iowa Central Railway Bridge at Keithsburg, Illinois",,,,,,Artist,,Henry P. Bosse,"American, born Germany, 1844–1893",,"Bosse, Henry P.","American, born Germany",1844,1893,1889,1889,1889,Cyanotype,Sheet: 14 1/2 × 17 3/16 in. (36.8 × 43.7 cm),"Gift of Charles Wehrenberg and Sally Larsen, 2014",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/669920,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2014.715.18,false,true,669921,Photographs,Photograph,"No. 207. Wabash Rail Road Bridge at Keokuk, Iowa",,,,,,Artist,,Henry P. Bosse,"American, born Germany, 1844–1893",,"Bosse, Henry P.","American, born Germany",1844,1893,1885,1885,1885,Cyanotype,Sheet: 14 1/2 × 17 3/16 in. (36.8 × 43.7 cm),"Gift of Charles Wehrenberg and Sally Larsen, 2014",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/669921,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.1027,false,true,264640,Photographs,Photograph,"General Grant's Council of War, Massaponax Church, Virginia",,,,,,Artist,,Timothy H. O'Sullivan,"American, born Ireland, 1840–1882",,"O'Sullivan, Timothy H.","American, born Ireland",1840,1882,"May 21, 1864",1864,1864,Albumen silver print from glass negative,8.7 x 10.8cm (3 7/16 x 4 1/4in.) Mount: 10.8 x 16.6cm (4 1/4 x 6 9/16in.),"The Elisha Whittelsey Collection, The Elisha Whittelsey Fund, 1986",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/264640,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.1054.19,true,true,264711,Photographs,Photograph,"Black Cañon, From Camp 8, Looking Above",,,,,,Artist,,Timothy H. O'Sullivan,"American, born Ireland, 1840–1882",,"O'Sullivan, Timothy H.","American, born Ireland",1840,1882,1871,1871,1871,Albumen silver print from glass negative,20 x 28.1 cm (7 7/8 x 11 1/16 in. ),"Purchase, Joseph Pulitzer Bequest and The Horace W. Goldsmith Foundation Gift through Joyce and Robert Menschel, 1986",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/264711,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.105,false,true,283217,Photographs,Photograph,"Fissure Vent at Steamboat Springs, Nevada",,,,,,Artist,,Timothy H. O'Sullivan,"American, born Ireland, 1840–1882",,"O'Sullivan, Timothy H.","American, born Ireland",1840,1882,1867,1867,1867,Albumen silver print from glass negative,Image: 22.3 x 29 cm (8 3/4 x 11 7/16 in.) Mat: 41.3 x 47.6 cm (16 1/4 x 18 3/4 in.),"Gilman Collection, Purchase, Marlene Nathan Meyerson Family Foundation Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/283217,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.1199,false,true,285642,Photographs,Photograph,"[Detachment of 50th N.Y. Volunteer Engineers, Pontoon Wagon and Saddle Boat]",,,,,,Artist,,Timothy H. O'Sullivan,"American, born Ireland, 1840–1882",,"O'Sullivan, Timothy H.","American, born Ireland",1840,1882,ca. 1864,1862,1866,Albumen silver print from glass negative,Image: 6 3/4 × 8 7/8 in. (17.1 × 22.5 cm) Mount: 12 in. × 17 13/16 in. (30.5 × 45.2 cm),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285642,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.1200,false,true,285643,Photographs,Photograph,Major General Pleasanton and General Custer,,,,,,Artist,,Timothy H. O'Sullivan,"American, born Ireland, 1840–1882",,"O'Sullivan, Timothy H.","American, born Ireland",1840,1882,1863,1863,1863,Albumen silver print from glass negative,Mount: 11 9/16 × 15 13/16 in. (29.4 × 40.1 cm) Image: 6 13/16 × 8 7/8 in. (17.3 × 22.5 cm),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285643,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.1203,false,true,285646,Photographs,Photograph,"Volcanic Islands in Mono Lake, California",,,,,,Artist,,Timothy H. O'Sullivan,"American, born Ireland, 1840–1882",,"O'Sullivan, Timothy H.","American, born Ireland",1840,1882,1868,1868,1868,Albumen silver print from glass negative,Image: 22.3 x 29.3 cm (8 3/4 x 11 9/16 in.) Mat: 40.6 x 50.8 cm (16 x 20 in.),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285646,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.1204,false,true,285647,Photographs,Photograph,"Desert Lake, near Ragtown, Nevada",,,,,,Artist,,Timothy H. O'Sullivan,"American, born Ireland, 1840–1882",,"O'Sullivan, Timothy H.","American, born Ireland",1840,1882,1867,1867,1867,Albumen silver print from glass negative,Image: 22.3 x 29.1 cm (8 3/4 x 11 7/16 in.) Mat: 40.6 x 50.8 cm (16 x 20 in.),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285647,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.1207,false,true,285785,Photographs,Photograph,"Tufa Rocks, Pyramid Lake, Nevada",,,,,,Artist,,Timothy H. O'Sullivan,"American, born Ireland, 1840–1882",,"O'Sullivan, Timothy H.","American, born Ireland",1840,1882,1867,1867,1867,Albumen silver print from glass negative,Image: 22.1 x 29.4 cm (8 11/16 x 11 9/16 in.) Mat: 47.9 x 61 cm (18 7/8 x 24 in.),"Gilman Collection, Purchase, Ann Tenenbaum and Thomas H. Lee Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285785,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.1222,false,true,285767,Photographs,Photograph,Return of Commander Selfridge and his Reconnaissance Party from an Expedition in the Interior of Darien,,,,,,Artist,,Timothy H. O'Sullivan,"American, born Ireland, 1840–1882",,"O'Sullivan, Timothy H.","American, born Ireland",1840,1882,1870,1870,1870,Albumen silver print from glass negative,9 x 11 3/8 (irregular and torn),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285767,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.372.1,false,true,286782,Photographs,Photograph,Des nouvelles occupations en Styrie,,,,,,Artist,,Louis-Pierre-Théophile Dubois de Nehaut,"French, active Belgium, 1799–1872",,"Dubois de Nehaut, Louis-Pierre-Théophile",Belgian,1799,1872,1854–56,1854,1856,Salted paper print from paper negative,Image: 20.8 x 15.9 cm (8 3/16 x 6 1/4 in.) Mount: 33.8 x 44.9 cm (13 5/16 x 17 11/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286782,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.372.2,false,true,286781,Photographs,Photograph,The Undersigned Photographer as He Was Before 1848,,,,,,Artist,,Louis-Pierre-Théophile Dubois de Nehaut,"French, active Belgium, 1799–1872",,"Dubois de Nehaut, Louis-Pierre-Théophile",Belgian,1799,1872,1854–56,1854,1856,Salted paper print from paper negative,Image: 9 13/16 × 7 15/16 in. (25 × 20.1 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286781,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.372.3,false,true,287239,Photographs,Photograph,La nièce de Mr. Tahon propriétaire de la maison n° 7,,,,,,Artist,,Louis-Pierre-Théophile Dubois de Nehaut,"French, active Belgium, 1799–1872",,"Dubois de Nehaut, Louis-Pierre-Théophile",Belgian,1799,1872,1854–56,1854,1856,Salted paper print from paper negative,Image: 9 3/4 × 7 5/16 in. (24.7 × 18.6 cm) Sheet: 13 3/8 × 18 1/8 in. (34 × 46 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287239,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.372.4,false,true,286780,Photographs,Photograph,"An ""Enfant terrible"" (No One Can Do the Impossible)",,,,,,Artist,,Louis-Pierre-Théophile Dubois de Nehaut,"French, active Belgium, 1799–1872",,"Dubois de Nehaut, Louis-Pierre-Théophile",Belgian,1799,1872,1854–56,1854,1856,Albumen silver print from paper negative,Image: 8 11/16 × 6 1/2 in. (22.1 × 16.5 cm) Sheet: 13 3/8 × 18 1/8 in. (34 × 46 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286780,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.372.5,false,true,287240,Photographs,Photograph,Mlle. Jeanne tellement tremblante que le photographe ne peut pas fixer les yeux,,,,,,Artist,,Louis-Pierre-Théophile Dubois de Nehaut,"French, active Belgium, 1799–1872",,"Dubois de Nehaut, Louis-Pierre-Théophile",Belgian,1799,1872,1854–56,1854,1856,Salted paper print from paper negative,Image: 8 9/16 × 6 7/8 in. (21.8 × 17.5 cm) Sheet: 13 3/8 × 18 1/8 in. (34 × 46 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287240,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.372.6,false,true,287241,Photographs,Photograph,Autre nièce du propriétaire du no. 7,,,,,,Artist,,Louis-Pierre-Théophile Dubois de Nehaut,"French, active Belgium, 1799–1872",,"Dubois de Nehaut, Louis-Pierre-Théophile",Belgian,1799,1872,1854–56,1854,1856,Salted paper print from paper negative,Image: 9 5/16 × 7 1/2 in. (23.7 × 19.1 cm) Sheet: 13 3/8 × 18 1/8 in. (34 × 46 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287241,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.372.7,false,true,287242,Photographs,Photograph,Visite d'un collègue de Bruxelles,,,,,,Artist,,Louis-Pierre-Théophile Dubois de Nehaut,"French, active Belgium, 1799–1872",,"Dubois de Nehaut, Louis-Pierre-Théophile",Belgian,1799,1872,1854–56,1854,1856,Salted paper print from paper negative,Image: 9 5/16 × 7 1/2 in. (23.7 × 19.1 cm) Sheet: 13 3/8 × 18 1/8 in. (34 × 46 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287242,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.372.8,false,true,287243,Photographs,Photograph,La fille de mon ami de Lille,,,,,,Artist,,Louis-Pierre-Théophile Dubois de Nehaut,"French, active Belgium, 1799–1872",,"Dubois de Nehaut, Louis-Pierre-Théophile",Belgian,1799,1872,1856,1856,1856,Salted paper print from paper negative,Image: 8 3/4 × 7 1/16 in. (22.2 × 17.9 cm) Sheet: 13 3/8 × 18 1/8 in. (34 × 46 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287243,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.372.9,false,true,287244,Photographs,Photograph,[Elderly Lady Sitting],,,,,,Artist,,Louis-Pierre-Théophile Dubois de Nehaut,"French, active Belgium, 1799–1872",,"Dubois de Nehaut, Louis-Pierre-Théophile",Belgian,1799,1872,1854–56,1854,1856,Salted paper print from paper negative,Image: 8 3/4 × 7 1/16 in. (22.2 × 18 cm) Sheet: 13 3/8 × 18 1/8 in. (34 × 46 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287244,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.372.10,false,true,287245,Photographs,Photograph,[Elderly Lady Sitting],,,,,,Artist,,Louis-Pierre-Théophile Dubois de Nehaut,"French, active Belgium, 1799–1872",,"Dubois de Nehaut, Louis-Pierre-Théophile",Belgian,1799,1872,1854–56,1854,1856,Salted paper print from paper negative,Image: 9 5/16 × 6 3/4 in. (23.7 × 17.2 cm) Sheet: 13 3/8 × 18 1/8 in. (34 × 46 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287245,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.372.11,false,true,287246,Photographs,Photograph,Madame Gihoul,,,,,,Artist,,Louis-Pierre-Théophile Dubois de Nehaut,"French, active Belgium, 1799–1872",,"Dubois de Nehaut, Louis-Pierre-Théophile",Belgian,1799,1872,1854–56,1854,1856,Albumen silver print from paper negative,Image: 7 5/16 in. × 6 in. (18.6 × 15.2 cm) Sheet: 13 3/8 × 18 1/8 in. (34 × 46 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287246,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.372.12,false,true,286784,Photographs,Photograph,[Portrait of a Woman],,,,,,Artist,,Louis-Pierre-Théophile Dubois de Nehaut,"French, active Belgium, 1799–1872",,"Dubois de Nehaut, Louis-Pierre-Théophile",Belgian,1799,1872,1854–56,1854,1856,Salted paper print from glass negative,Image: 7 1/4 × 9 1/8 in. (18.4 × 23.2 cm) Sheet: 13 3/8 × 18 1/8 in. (34 × 46 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286784,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.372.13,false,true,287247,Photographs,Photograph,En soirée - janvier 1856,,,,,,Artist,,Louis-Pierre-Théophile Dubois de Nehaut,"French, active Belgium, 1799–1872",,"Dubois de Nehaut, Louis-Pierre-Théophile",Belgian,1799,1872,1856,1856,1856,Salted paper print from paper negative,Image: 9 3/8 × 6 13/16 in. (23.8 × 17.3 cm) Sheet: 13 3/8 × 18 1/8 in. (34 × 46 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287247,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.372.14,false,true,287248,Photographs,Photograph,Madame Gihoul,,,,,,Artist,,Louis-Pierre-Théophile Dubois de Nehaut,"French, active Belgium, 1799–1872",,"Dubois de Nehaut, Louis-Pierre-Théophile",Belgian,1799,1872,1854–56,1854,1856,Salted paper print from paper negative,Image: 10 in. × 7 5/8 in. (25.4 × 19.3 cm) Sheet: 13 3/8 × 18 1/8 in. (34 × 46 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287248,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.372.15,false,true,286783,Photographs,Photograph,[Women Stacking Carrots],,,,,,Artist,,Louis-Pierre-Théophile Dubois de Nehaut,"French, active Belgium, 1799–1872",,"Dubois de Nehaut, Louis-Pierre-Théophile",Belgian,1799,1872,1854–56,1854,1856,Salted paper print from paper negative,Image: 7 1/4 × 9 1/8 in. (18.4 × 23.2 cm) Sheet: 13 3/8 × 18 1/2 in. (34 × 47 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286783,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.372.16,false,true,287249,Photographs,Photograph,"Il vient à Bruxelles, voit un appartement place du Cologne no. 7",,,,,,Artist,,Louis-Pierre-Théophile Dubois de Nehaut,"French, active Belgium, 1799–1872",,"Dubois de Nehaut, Louis-Pierre-Théophile",Belgian,1799,1872,1854–56,1854,1856,Salted paper print from paper negative,Image: 9 5/16 × 8 5/16 in. (23.6 × 21.1 cm) Sheet: 13 3/8 × 18 1/2 in. (34 × 47 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287249,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.372.17,false,true,283129,Photographs,Photograph,View of the Square in Melting Snow,,,,,,Artist,,Louis-Pierre-Théophile Dubois de Nehaut,"French, active Belgium, 1799–1872",,"Dubois de Nehaut, Louis-Pierre-Théophile",Belgian,1799,1872,1854–56,1854,1856,Salted paper print from glass negative,Image: 18 x 22.2 cm (7 1/16 x 8 3/4 in. ) Sheet: 13 3/8 × 18 1/8 in. (34 × 46 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/283129,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.372.18,false,true,287250,Photographs,Photograph,Vue de droite du balcon,,,,,,Artist,,Louis-Pierre-Théophile Dubois de Nehaut,"French, active Belgium, 1799–1872",,"Dubois de Nehaut, Louis-Pierre-Théophile",Belgian,1799,1872,1854–56,1854,1856,Salted paper print from paper negative,Image: 8 13/16 × 10 3/4 in. (22.4 × 27.3 cm) Sheet: 13 3/8 × 18 1/8 in. (34 × 46 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287250,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.372.19,false,true,287251,Photographs,Photograph,Vue de face du balcon avant l'entière construction de la place,,,,,,Artist,,Louis-Pierre-Théophile Dubois de Nehaut,"French, active Belgium, 1799–1872",,"Dubois de Nehaut, Louis-Pierre-Théophile",Belgian,1799,1872,1854–56,1854,1856,Albumen silver print from paper negative,Image: 9 3/8 × 12 13/16 in. (23.8 × 32.5 cm) Sheet: 13 3/8 × 18 1/8 in. (34 × 46 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287251,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.372.20,false,true,287252,Photographs,Photograph,La place pendant les fêtes de septembre,,,,,,Artist,,Louis-Pierre-Théophile Dubois de Nehaut,"French, active Belgium, 1799–1872",,"Dubois de Nehaut, Louis-Pierre-Théophile",Belgian,1799,1872,1854–56,1854,1856,Salted paper print from paper negative,Image: 10 5/8 × 9 3/16 in. (27 × 23.3 cm) Sheet: 13 3/8 × 18 1/8 in. (34 × 46 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287252,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.372.21,false,true,287253,Photographs,Photograph,Visite d'un ami de Lille,,,,,,Artist,,Louis-Pierre-Théophile Dubois de Nehaut,"French, active Belgium, 1799–1872",,"Dubois de Nehaut, Louis-Pierre-Théophile",Belgian,1799,1872,1854,1854,1854,Salted paper print from paper negative,Image: 6 1/16 × 7 1/16 in. (15.4 × 17.9 cm) Sheet: 13 3/8 × 18 1/8 in. (34 × 46 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287253,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.372.22,false,true,287254,Photographs,Photograph,"Station de Malines, Epreuve instantanée au passage d'un train au soleil couchant",,,,,,Artist,,Louis-Pierre-Théophile Dubois de Nehaut,"French, active Belgium, 1799–1872",,"Dubois de Nehaut, Louis-Pierre-Théophile",Belgian,1799,1872,1854–56,1854,1856,Salted paper print from paper negative,Image: 6 5/8 × 8 7/16 in. (16.8 × 21.4 cm) Sheet: 13 3/8 × 18 1/8 in. (34 × 46 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287254,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.372.24,false,true,287255,Photographs,Photograph,Jardin zoologique de Bruxelles,,,,,,Artist,,Louis-Pierre-Théophile Dubois de Nehaut,"French, active Belgium, 1799–1872",,"Dubois de Nehaut, Louis-Pierre-Théophile",Belgian,1799,1872,1854–56,1854,1856,Salted paper print from paper negative,Image: 8 11/16 × 10 13/16 in. (22.1 × 27.5 cm) Sheet: 13 3/8 × 18 1/8 in. (34 × 46 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287255,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.372.25,false,true,286899,Photographs,Photograph,Zoological Garden,,,,,,Artist,,Louis-Pierre-Théophile Dubois de Nehaut,"French, active Belgium, 1799–1872",,"Dubois de Nehaut, Louis-Pierre-Théophile",Belgian,1799,1872,1854–56,1854,1856,Salted paper print from paper negative,Image: 9 3/16 × 12 1/16 in. (23.4 × 30.6 cm) Sheet: 13 3/8 × 18 1/8 in. (34 × 46 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286899,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.372.26,false,true,287256,Photographs,Photograph,"[The Bear Enclave, Zoological Gardens, Brussels]",,,,,,Artist,,Louis-Pierre-Théophile Dubois de Nehaut,"French, active Belgium, 1799–1872",,"Dubois de Nehaut, Louis-Pierre-Théophile",Belgian,1799,1872,1854–56,1854,1856,Salted paper print from paper negative,Image: 5 13/16 × 7 3/16 in. (14.8 × 18.3 cm) Sheet: 13 3/8 × 18 1/8 in. (34 × 46 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287256,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.372.27,false,true,287257,Photographs,Photograph,"[The Kiosk, Zoological Gardens, Brussels]",,,,,,Artist,,Louis-Pierre-Théophile Dubois de Nehaut,"French, active Belgium, 1799–1872",,"Dubois de Nehaut, Louis-Pierre-Théophile",Belgian,1799,1872,1854–56,1854,1856,Salted paper print from paper negative,Image: 9 1/4 × 12 1/8 in. (23.5 × 30.8 cm) Sheet: 13 3/8 × 18 1/8 in. (34 × 46 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287257,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.372.28,false,true,286779,Photographs,Photograph,"The Zoological Garden, Brussels",,,,,,Artist,,Louis-Pierre-Théophile Dubois de Nehaut,"French, active Belgium, 1799–1872",,"Dubois de Nehaut, Louis-Pierre-Théophile",Belgian,1799,1872,1854,1854,1854,Salted paper print from paper negative,Image: 9 5/16 × 11 9/16 in. (23.7 × 29.4 cm) Sheet: 13 3/8 × 18 1/8 in. (34 × 46 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286779,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.372.29,false,true,287258,Photographs,Photograph,Jardin zoologique de Bruxelles; Les trois jumeaux fils de Mr. Lebens et toute la famille,,,,,,Artist,,Louis-Pierre-Théophile Dubois de Nehaut,"French, active Belgium, 1799–1872",,"Dubois de Nehaut, Louis-Pierre-Théophile",Belgian,1799,1872,1854–56,1854,1856,Salted paper print from paper negative,Image: 5 13/16 × 7 11/16 in. (14.7 × 19.5 cm) Sheet: 13 3/8 × 18 1/8 in. (34 × 46 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287258,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.372.30,false,true,287259,Photographs,Photograph,"[Heron Pond, Zoological Gardens, Brussels]",,,,,,Artist,,Louis-Pierre-Théophile Dubois de Nehaut,"French, active Belgium, 1799–1872",,"Dubois de Nehaut, Louis-Pierre-Théophile",Belgian,1799,1872,1854–56,1854,1856,Salted paper print from paper negative,Image: 18 × 21.2 cm (7 1/16 × 8 3/8 in.) Sheet: 13 3/8 × 18 1/8 in. (34 × 46 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287259,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.372.31,false,true,287260,Photographs,Photograph,"[The Pelicans and Greenhouses, Zoological Gardens, Brussels]",,,,,,Artist,,Louis-Pierre-Théophile Dubois de Nehaut,"French, active Belgium, 1799–1872",,"Dubois de Nehaut, Louis-Pierre-Théophile",Belgian,1799,1872,1854–56,1854,1856,Salted paper print from paper negative,Image: 14.6 × 19.5 cm (5 3/4 × 7 11/16 in.) Sheet: 13 3/8 × 18 1/8 in. (34 × 46 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287260,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.372.32,false,true,283127,Photographs,Photograph,Another Impossible Task,,,,,,Artist,,Louis-Pierre-Théophile Dubois de Nehaut,"French, active Belgium, 1799–1872",,"Dubois de Nehaut, Louis-Pierre-Théophile",Belgian,1799,1872,1854,1854,1854,Salted paper print from glass negative,Image: 16.3 x 21.2 cm (6 7/16 x 8 3/8 in.) Sheet: 13 3/8 × 18 1/8 in. (34 × 46 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/283127,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1987.1100.62,false,true,265589,Photographs,Multiple exposure; Photograph,In the Studio,,,,,,Artist,,El Lissitzky,"Russian, Pochinok 1890–1941 Moscow",,"Lissitzky, El",Russian,1890,1941,1923,1923,1923,Gelatin silver print,10.9 x 8.3 cm (4 5/16 x 3 1/4 in.),"Ford Motor Company Collection, Gift of Ford Motor Company and John C. Waddell, 1987",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/265589,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.151,false,true,283289,Photographs,Photograph,[Self-Portrait],,,,,,Artist,,El Lissitzky,"Russian, Pochinok 1890–1941 Moscow",,"Lissitzky, El",Russian,1890,1941,1924–25,1924,1925,Gelatin silver print,Image: 17.3 x 12.1 cm (6 13/16 x 4 3/4 in.),"Gilman Collection, Purchase, Denise and Andrew Saul Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/283289,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2012.110,false,true,299469,Photographs,Photographs,[Man in Chainmail Tunic Posing as a Dying Soldier],,,,,,Artist,,Adrien Constant de Rebecque,"Swiss, Lausanne 1806–1876 Lausanne",,"Constant de Rebecque, Adrien",Swiss,1806,1876,ca. 1863,1858,1868,Albumen print from collodion glass negative,Image: 17.9 x 24.2 cm (7 1/16 x 9 1/2 in.) Mount: 27.2 x 37 cm (10 11/16 x 14 9/16 in.),"Gilman Collection, Purchase, The Howard Gilman Foundation Gift, 2012",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/299469,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1985.1168.2,false,true,264528,Photographs,Photograph,"[A Gypsy Dancing-Girl, Kathiawar]",,,,,,Artist,,E. Taurines,"probably French, active ca. 1885–1901",,"Taurines, E.",French ?,1885,1901,ca. 1915,1915,1915,Albumen silver print from glass negative,23.5 x 18.2 cm (9 1/4 x 7 3/16 in.),"Gift of Matthew Dontzin, 1985",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/264528,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.400.67,false,true,291820,Photographs,Daguerreotype,[Middle-aged Man with Glasses Holding Pocket Watch],,,,,,Artist,,Antoine-François-Jean Claudet,"French, active Great Britain, 1797–1867",,"Claudet, Antione-François-Jean","French, active Great Britain",1797,1867,1844–1859,1844,1859,Daguerreotype,Image: 11.3 x 8.7 cm (4 7/16 x 3 7/16 in.) Plate: 14.6 x 12.2 cm (5 3/4 x 4 13/16 in.) Case: 2.2 x 17.8 x 15.2 cm (7/8 x 7 x 6 in.),"Bequest of Herbert Mitchell, 2008",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/291820,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2015.400.86a, b",false,true,291839,Photographs,Daguerreotype,"[Stereograph, Two Children Standing Between Furniture in a Studio Parlor Setting]",,,,,,Artist,,Antoine-François-Jean Claudet,"French, active Great Britain, 1797–1867",,"Claudet, Antione-François-Jean","French, active Great Britain",1797,1867,ca. 1855,1853,1857,Daguerreotype,"Image: 6.8 x 5.7 cm (2 11/16 x 2 1/4 in.), each Mount: 8.4 x 17.6 cm (3 5/16 x 6 15/16 in.)","Bequest of Herbert Mitchell, 2008",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/291839,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2008.528,false,true,289024,Photographs,Photograph,"Cloisters of St. Paul's, the Basilica, Outside the Walls of Rome",,,,,,Artist,,Robert Macpherson,"British, Tayside, Scotland 1811–1872 Rome",,"Macpherson, Robert","British, Scottish",1811,1811,1858 or earlier,1857,1858,Albumen silver print from glass negative,Image: 34.8 x 25.8 cm (13 11/16 x 10 3/16 in.) Mount (2nd): 52 x 36.8 cm (20 1/2 x 14 1/2 in.),"Purchase, Joyce F. Menschel Gift, 2008",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/289024,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.58,false,true,283141,Photographs,Photograph,Cloaca Maxima,,,,,,Artist,,Robert Macpherson,"British, Tayside, Scotland 1811–1872 Rome",,"Macpherson, Robert","British, Scottish",1811,1811,1858 or earlier,1854,1858,Albumen silver print from glass negative,Image: 31 x 37.2 cm (12 3/16 x 14 5/8 in.),"Gilman Collection, Purchase, William Talbott Hillman Foundation Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/283141,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.59,false,true,283142,Photographs,Photograph,"The Theater of Marcellus, from the Piazza Montanara",,,,,,Artist,,Robert Macpherson,"British, Tayside, Scotland 1811–1872 Rome",,"Macpherson, Robert","British, Scottish",1811,1811,1858 or earlier,1854,1858,Albumen silver print from glass negative,Image: 41.9 x 27.4 cm (16 1/2 x 10 13/16 in.),"Gilman Collection, Purchase, Alfred Stieglitz Society Gifts, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/283142,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.909,false,true,285821,Photographs,Photograph,"The Valley of the Anio, with the Upper and Lower Cascatelle, Mecenas's Villa, and Distant Campagna",,,,,,Artist,,Robert Macpherson,"British, Tayside, Scotland 1811–1872 Rome",,"Macpherson, Robert","British, Scottish",1811,1811,1858 or earlier,1858,1858,Albumen silver print from glass negative,"Image: 30.6 x 40.1 cm (12 1/16 x 15 13/16 in.), oval Mount: 49.4 x 64.7 cm (19 7/16 x 25 1/2 in.)","Gilman Collection, Purchase, W. Bruce and Delaney H. Lundberg Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285821,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.963,false,true,285822,Photographs,Photograph,Falls of Terni,,,,,,Artist,,Robert Macpherson,"British, Tayside, Scotland 1811–1872 Rome",,"Macpherson, Robert","British, Scottish",1811,1811,ca. 1860,1858,1862,Albumen silver print from glass negative,"Image: 27.6 x 37.7 cm (10 7/8 x 14 13/16 in.), oval Mount: 49.5 x 60.6 cm (19 1/2 x 23 7/8 in.)","Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285822,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.1098,false,true,285820,Photographs,Photograph,The Hermaphrodite,,,,,,Artist,,Robert Macpherson,"British, Tayside, Scotland 1811–1872 Rome",,"Macpherson, Robert","British, Scottish",1811,1811,ca. 1861,1858,1864,Albumen silver print from glass negative,Image: 6 3/8 in. × 13 in. (16.2 × 33 cm) Mount: 18 5/8 × 24 5/8 in. (47.3 × 62.5 cm),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285820,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1977.537,false,true,261983,Photographs,Photographs,George Frederick Watts,,,,,,Artist,,David Wilkie Wynfield,"British (born India), 1837–1887 London (?)",,"Wynfield, David Wilkie","British, born India",1837,1887,1860s,1860,1869,Albumen silver print,21.7 x 16.4 cm (8 9/16 x 6 7/16 in.),"Warner Communications Inc. Purchase Fund, 1977",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261983,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -49.55.310,false,true,269420,Photographs,Photograph,Portrait of D.O. Hill,,,,,,Artist,,Thomas Annan,"British, Dairsie, Fife, Scotland 1829–1887",,"Annan, Thomas","British, Scottish",1829,1887,"1867, printed ca. 1900s",1867,1867,Gelatin silver print,23.0 x 17.5 cm. (9 1/16 x 6 7/8 in.),"Alfred Stieglitz Collection, 1949",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269420,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.216,false,true,285739,Photographs,Photograph,Frederick Langenheim,,,,,,Artist,,William Langenheim,"American, born Germany, Schöningen 1807–1874",,"Langenheim, William","American, born Germany",1807,1874,ca. 1849–50,1847,1852,Daguerreotype,Image: 11.7 x 8.6 cm (4 5/8 x 3 3/8 in.) Case: 1.4 x 11.9 x 15.2 cm (9/16 x 4 11/16 x 6 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285739,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.217,false,true,285740,Photographs,Photograph,Frederick Langenheim,,,,,,Artist,,William Langenheim,"American, born Germany, Schöningen 1807–1874",,"Langenheim, William","American, born Germany",1807,1874,ca. 1851–53,1849,1855,Daguerreotype,Image: 8.9 x 7 cm (3 1/2 x 2 3/4 in.) Case: 1.6 x 11.9 x 9.4 cm (5/8 x 4 11/16 x 3 11/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285740,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.219,false,true,285769,Photographs,Photograph,William and Sophia Palmer Langenheim,,,,,,Artist,,William Langenheim,"American, born Germany, Schöningen 1807–1874",,"Langenheim, William","American, born Germany",1807,1874,ca. 1846–47,1844,1849,Daguerreotype,Image: 12.1 x 8.9 cm (4 3/4 x 3 1/2 in.) Case: 1.6 x 13.5 x 11.7 cm (5/8 x 5 5/16 x 4 5/8 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285769,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.221,false,true,286328,Photographs,Photograph,William Langenheim,,,,,,Artist,,William Langenheim,"American, born Germany, Schöningen 1807–1874",,"Langenheim, William","American, born Germany",1807,1874,ca. 1848–50,1846,1852,Daguerreotype,Image: 8.9 x 7 cm (3 1/2 x 2 3/4 in.) Case: 1.4 x 11.6 x 9.2 cm (9/16 x 4 9/16 x 3 5/8 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286328,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.222,false,true,286329,Photographs,Photograph,William Langenheim,,,,,,Artist,,William Langenheim,"American, born Germany, Schöningen 1807–1874",,"Langenheim, William","American, born Germany",1807,1874,1855–58,1855,1858,Daguerreotype,Image: 6.7 x 5.4 cm (2 5/8 x 2 1/8 in.) Case: 1.6 x 9.4 x 8.3 cm (5/8 x 3 11/16 x 3 1/4 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286329,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.224,false,true,286330,Photographs,Photograph,William Langenheim,,,,,,Artist,,William Langenheim,"American, born Germany, Schöningen 1807–1874",,"Langenheim, William","American, born Germany",1807,1874,ca. 1853–55,1851,1857,Daguerreotype,Image: 7.1 x 5.9 cm (2 13/16 x 2 5/16 in.) Frame: 8.4 x 7.1 cm (3 5/16 x 2 13/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286330,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.227,false,true,286326,Photographs,Photograph,William Langenheim,,,,,,Artist,,William Langenheim,"American, born Germany, Schöningen 1807–1874",,"Langenheim, William","American, born Germany",1807,1874,ca. 1849–51,1847,1853,Salted Paper Print From Paper Negative with Applied Color,Image: 16.5 x 12.1 cm (6 1/2 x 4 3/4 in.) Case: 1.3 x 19.4 x 14.4 cm (1/2 x 7 5/8 x 5 11/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286326,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.228,false,true,286273,Photographs,Photograph,Three Men Playing Cards,,,,,,Artist,,William Langenheim,"American, born Germany, Schöningen 1807–1874",,"Langenheim, William","American, born Germany",1807,1874,"March, 1842",1842,1842,Daguerreotype,Image: 7.3 x 5.7 cm (2 7/8 x 2 1/4 in.) Frame: 21.6 x 18.1 cm (8 1/2 x 7 1/8 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286273,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.230,false,true,286274,Photographs,Photograph,Frederick David Langenheim,,,,,,Artist,,William Langenheim,"American, born Germany, Schöningen 1807–1874",,"Langenheim, William","American, born Germany",1807,1874,ca. 1851–52,1849,1853,Daguerreotype,Image: 12.4 x 9.2 cm (4 7/8 x 3 5/8 in.) Case: 1.6 x 15.1 x 12.1 cm (5/8 x 5 15/16 x 4 3/4 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286274,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.237,false,true,285719,Photographs,Photograph,Professor Schneider's Children,,,,,,Artist,,William Langenheim,"American, born Germany, Schöningen 1807–1874",,"Langenheim, William","American, born Germany",1807,1874,ca. 1842,1840,1844,Daguerreotype,Image: 6.7 x 5.4 cm (2 5/8 x 2 1/8 in.) Case: 1.3 x 9.2 x 7.8 cm (1/2 x 3 5/8 x 3 1/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285719,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.870,false,true,285832,Photographs,Photograph,[Mrs. Thomas Ustick Walter and Her Deceased Child],,,,,,Artist,,William Langenheim,"American, born Germany, Schöningen 1807–1874",,"Langenheim, William","American, born Germany",1807,1874,ca. 1846,1844,1848,Daguerreotype,"Visible: 4 11/16 x 3 1/2, Case: 5 15/16 x 4 5/8 x 5/8","Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285832,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.215,false,true,285721,Photographs,Photograph,Frederick Langenheim,,,,,,Artist,,Frederick Langenheim,"American, born Germany, Schöningen 1809–1879",,"Langenheim, Frederick","American, born Germany",1809,1879,ca. 1850–51,1848,1853,Daguerreotype,Image: 11.4 x 8.4 cm (4 1/2 x 3 5/16 in.) Frame: 17.9 x 14.4 cm (7 1/16 x 5 11/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285721,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.494.4,false,true,289276,Photographs,Photograph,"Lovers Leap, Foochow",,,,,,Artist,Attributed to,John Thomson,"British, Edinburgh, Scotland 1837–1921 London",,"Thomson, John","British, Scottish",1837,1921,ca. 1869,1869,1869,Albumen silver print from glass negative,Image: 9 3/16 × 11 1/8 in. (23.3 × 28.3 cm),"Gilman Collection, Purchase, Robert Rosenkranz Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/289276,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.494.6,false,true,289278,Photographs,Photograph,"The Cemetery, Foochow",,,,,,Artist,Attributed to,John Thomson,"British, Edinburgh, Scotland 1837–1921 London",,"Thomson, John","British, Scottish",1837,1921,ca. 1869,1869,1869,Albumen silver print from glass negative,Image: 8 1/4 × 10 7/8 in. (21 × 27.6 cm); oval,"Gilman Collection, Purchase, Robert Rosenkranz Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/289278,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.494.7,false,true,289279,Photographs,Photograph,Yungfoo River,,,,,,Artist,Attributed to,John Thomson,"British, Edinburgh, Scotland 1837–1921 London",,"Thomson, John","British, Scottish",1837,1921,ca. 1869,1869,1869,Albumen silver print from glass negative,Image: 9 1/2 × 11 1/8 in. (24.1 × 28.2 cm),"Gilman Collection, Purchase, Robert Rosenkranz Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/289279,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.494.8,false,true,289280,Photographs,Photograph,"White Pagoda, Foochow",,,,,,Artist,Attributed to,John Thomson,"British, Edinburgh, Scotland 1837–1921 London",,"Thomson, John","British, Scottish",1837,1921,ca. 1869,1869,1869,Albumen silver print from glass negative,Image: 9 3/8 × 10 15/16 in. (23.8 × 27.8 cm),"Gilman Collection, Purchase, Robert Rosenkranz Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/289280,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.494.9,false,true,289281,Photographs,Photograph,Teahouse at Peking,,,,,,Artist,Attributed to,John Thomson,"British, Edinburgh, Scotland 1837–1921 London",,"Thomson, John","British, Scottish",1837,1921,ca. 1869,1869,1869,Albumen silver print from glass negative,Image: 9 7/16 × 11 5/16 in. (23.9 × 28.7 cm),"Gilman Collection, Purchase, Robert Rosenkranz Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/289281,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.494.10,false,true,289282,Photographs,Photograph,Pagoda Island,,,,,,Artist,Attributed to,John Thomson,"British, Edinburgh, Scotland 1837–1921 London",,"Thomson, John","British, Scottish",1837,1921,ca. 1869,1869,1869,Albumen silver print from glass negative,Image: 8 1/8 × 10 11/16 in. (20.7 × 27.2 cm),"Gilman Collection, Purchase, Robert Rosenkranz Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/289282,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.494.11,false,true,289283,Photographs,Photograph,Bowling Alley and Raquet Court at Foochow,,,,,,Artist,Attributed to,John Thomson,"British, Edinburgh, Scotland 1837–1921 London",,"Thomson, John","British, Scottish",1837,1921,ca. 1869,1869,1869,Albumen silver print from glass negative,Image: 8 9/16 × 11 7/16 in. (21.8 × 29 cm),"Gilman Collection, Purchase, Robert Rosenkranz Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/289283,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.494.12,false,true,289284,Photographs,Photograph,Cemetery at Foochow,,,,,,Artist,Attributed to,John Thomson,"British, Edinburgh, Scotland 1837–1921 London",,"Thomson, John","British, Scottish",1837,1921,ca. 1869,1869,1869,Albumen silver print from glass negative,Image: 8 3/16 × 11 3/8 in. (20.8 × 28.9 cm),"Gilman Collection, Purchase, Robert Rosenkranz Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/289284,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.494.14,false,true,289286,Photographs,Photograph,"Ming-Ming Customhouse, Rieng-Gang City",,,,,,Artist,Attributed to,John Thomson,"British, Edinburgh, Scotland 1837–1921 London",,"Thomson, John","British, Scottish",1837,1921,ca. 1869,1869,1869,Albumen silver print from glass negative,Image: 7 7/8 × 11 1/8 in. (20 × 28.3 cm),"Gilman Collection, Purchase, Robert Rosenkranz Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/289286,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.494.15,false,true,289287,Photographs,Photograph,"Missionary Houses, Foochow",,,,,,Artist,Attributed to,John Thomson,"British, Edinburgh, Scotland 1837–1921 London",,"Thomson, John","British, Scottish",1837,1921,ca. 1869,1869,1869,Albumen silver print from glass negative,Image: 8 1/8 × 11 3/8 in. (20.7 × 28.9 cm),"Gilman Collection, Purchase, Robert Rosenkranz Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/289287,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.494.16,false,true,289288,Photographs,Photograph,Min-ch'oi Temple & City Wall of Yen-ping,,,,,,Artist,Attributed to,John Thomson,"British, Edinburgh, Scotland 1837–1921 London",,"Thomson, John","British, Scottish",1837,1921,ca. 1869,1869,1869,Albumen silver print from glass negative,Image: 7 15/16 in. × 11 in. (20.1 × 27.9 cm),"Gilman Collection, Purchase, Robert Rosenkranz Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/289288,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.494.19,false,true,289291,Photographs,Photograph,Heaven Ascending Peak near Sing-Chang,,,,,,Artist,Attributed to,John Thomson,"British, Edinburgh, Scotland 1837–1921 London",,"Thomson, John","British, Scottish",1837,1921,ca. 1869,1869,1869,Albumen silver print from glass negative,Image: 8 5/16 × 11 3/16 in. (21.1 × 28.4 cm),"Gilman Collection, Purchase, Robert Rosenkranz Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/289291,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.494.20,false,true,289292,Photographs,Photograph,"Pure Spring Cave, near Sing Chang",,,,,,Artist,Attributed to,John Thomson,"British, Edinburgh, Scotland 1837–1921 London",,"Thomson, John","British, Scottish",1837,1921,ca. 1869,1869,1869,Albumen silver print from glass negative,Image: 10 1/16 × 8 3/8 in. (25.5 × 21.3 cm),"Gilman Collection, Purchase, Robert Rosenkranz Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/289292,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.494.21,false,true,289293,Photographs,Photograph,[untitled],,,,,,Artist,Attributed to,John Thomson,"British, Edinburgh, Scotland 1837–1921 London",,"Thomson, John","British, Scottish",1837,1921,ca. 1869,1869,1869,Albumen silver print from glass negative,Image: 8 1/8 × 10 15/16 in. (20.7 × 27.8 cm),"Gilman Collection, Purchase, Robert Rosenkranz Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/289293,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.494.22,false,true,289294,Photographs,Photograph,[untitled],,,,,,Artist,Attributed to,John Thomson,"British, Edinburgh, Scotland 1837–1921 London",,"Thomson, John","British, Scottish",1837,1921,ca. 1869,1869,1869,Albumen silver print from glass negative,Image: 8 1/16 × 11 5/16 in. (20.4 × 28.7 cm),"Gilman Collection, Purchase, Robert Rosenkranz Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/289294,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.494.23,false,true,289295,Photographs,Photograph,[untitled],,,,,,Artist,Attributed to,John Thomson,"British, Edinburgh, Scotland 1837–1921 London",,"Thomson, John","British, Scottish",1837,1921,ca. 1869,1869,1869,Albumen silver print from glass negative,Image: 8 in. × 11 5/16 in. (20.3 × 28.8 cm),"Gilman Collection, Purchase, Robert Rosenkranz Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/289295,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.494.24,false,true,289296,Photographs,Photograph,Flouring Mill at Yen-Ping,,,,,,Artist,Attributed to,John Thomson,"British, Edinburgh, Scotland 1837–1921 London",,"Thomson, John","British, Scottish",1837,1921,ca. 1869,1869,1869,Albumen silver print from glass negative,Image: 8 1/16 × 11 5/16 in. (20.4 × 28.7 cm),"Gilman Collection, Purchase, Robert Rosenkranz Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/289296,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.494.25,false,true,289297,Photographs,Photograph,Chui Nang opposite Kien-yang city,,,,,,Artist,Attributed to,John Thomson,"British, Edinburgh, Scotland 1837–1921 London",,"Thomson, John","British, Scottish",1837,1921,ca. 1869,1869,1869,Albumen silver print from glass negative,Image: 7 15/16 × 11 1/4 in. (20.2 × 28.5 cm),"Gilman Collection, Purchase, Robert Rosenkranz Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/289297,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.494.28,false,true,289300,Photographs,Photograph,"Factory and Silkworm Nursery, Foochow",,,,,,Artist,Attributed to,John Thomson,"British, Edinburgh, Scotland 1837–1921 London",,"Thomson, John","British, Scottish",1837,1921,ca. 1869,1869,1869,Albumen silver print from glass negative,Image: 6 15/16 × 9 7/16 in. (17.6 × 23.9 cm),"Gilman Collection, Purchase, Robert Rosenkranz Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/289300,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.494.32,false,true,289304,Photographs,Photograph,[Mountain and Rice Fields],,,,,,Artist,Attributed to,John Thomson,"British, Edinburgh, Scotland 1837–1921 London",,"Thomson, John","British, Scottish",1837,1921,ca. 1869,1869,1869,Albumen silver print from glass negative,Image: 8 3/16 × 11 1/4 in. (20.8 × 28.5 cm),"Gilman Collection, Purchase, Robert Rosenkranz Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/289304,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.494.34,false,true,289306,Photographs,Photograph,The Grand Stand Foochow,,,,,,Artist,Attributed to,John Thomson,"British, Edinburgh, Scotland 1837–1921 London",,"Thomson, John","British, Scottish",1837,1921,ca. 1869,1869,1869,Albumen silver print from glass negative,Image: 9 1/8 × 11 1/8 in. (23.1 × 28.3 cm),"Gilman Collection, Purchase, Robert Rosenkranz Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/289306,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.494.36,false,true,289308,Photographs,Photograph,[Untitled],,,,,,Artist,Attributed to,John Thomson,"British, Edinburgh, Scotland 1837–1921 London",,"Thomson, John","British, Scottish",1837,1921,ca. 1869,1869,1869,Albumen silver print from glass negative,Image: 8 1/8 × 11 7/16 in. (20.7 × 29 cm),"Gilman Collection, Purchase, Robert Rosenkranz Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/289308,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.494.40,false,true,289312,Photographs,Photograph,"[Village, River Min]",,,,,,Artist,Attributed to,John Thomson,"British, Edinburgh, Scotland 1837–1921 London",,"Thomson, John","British, Scottish",1837,1921,ca. 1869,1869,1869,Albumen silver print from glass negative,Image: 9 in. × 10 15/16 in. (22.8 × 27.8 cm),"Gilman Collection, Purchase, Robert Rosenkranz Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/289312,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.494.44,false,true,289316,Photographs,Photograph,"Tai-Laity Montain, North River, Canton",,,,,,Artist,Attributed to,John Thomson,"British, Edinburgh, Scotland 1837–1921 London",,"Thomson, John","British, Scottish",1837,1921,ca. 1869,1869,1869,Albumen silver print from glass negative,Image: 6 5/8 × 9 7/16 in. (16.9 × 24 cm),"Gilman Collection, Purchase, Robert Rosenkranz Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/289316,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.494.45,false,true,289317,Photographs,Photograph,View from Koong-Yan-Shang Temple,,,,,,Artist,Attributed to,John Thomson,"British, Edinburgh, Scotland 1837–1921 London",,"Thomson, John","British, Scottish",1837,1921,ca. 1869,1869,1869,Albumen silver print from glass negative,Image: 5 11/16 × 9 3/16 in. (14.4 × 23.3 cm),"Gilman Collection, Purchase, Robert Rosenkranz Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/289317,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.494.46,false,true,289318,Photographs,Photograph,Village Road North River,,,,,,Artist,Attributed to,John Thomson,"British, Edinburgh, Scotland 1837–1921 London",,"Thomson, John","British, Scottish",1837,1921,ca. 1869,1869,1869,Albumen silver print from glass negative,Image: 6 7/8 × 9 3/16 in. (17.5 × 23.4 cm),"Gilman Collection, Purchase, Robert Rosenkranz Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/289318,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.494.47,false,true,289319,Photographs,Photograph,[Panorama of Hong Kong],,,,,,Artist,Attributed to,John Thomson,"British, Edinburgh, Scotland 1837–1921 London",,"Thomson, John","British, Scottish",1837,1921,ca. 1869,1869,1869,Albumen silver print from glass negative,Image(a): 10 5/8 × 8 13/16 in. (27 × 22.4 cm) Image(b): 10 1/2 × 8 13/16 in. (26.6 × 22.4 cm),"Gilman Collection, Purchase, Robert Rosenkranz Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/289319,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.494.48,false,true,289320,Photographs,Photograph,View of Canton from the River,,,,,,Artist,Attributed to,John Thomson,"British, Edinburgh, Scotland 1837–1921 London",,"Thomson, John","British, Scottish",1837,1921,ca. 1869,1869,1869,Albumen silver print from glass negative,Image: 8 1/8 × 9 13/16 in. (20.6 × 24.9 cm),"Gilman Collection, Purchase, Robert Rosenkranz Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/289320,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.494.49,false,true,289321,Photographs,Photograph,View Opposite Canton,,,,,,Artist,Attributed to,John Thomson,"British, Edinburgh, Scotland 1837–1921 London",,"Thomson, John","British, Scottish",1837,1921,ca. 1869,1869,1869,Albumen silver print from glass negative,Image: 10 15/16 × 10 9/16 in. (27.8 × 26.8 cm),"Gilman Collection, Purchase, Robert Rosenkranz Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/289321,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.494.51,false,true,289323,Photographs,Photograph,"West Gate, Canton",,,,,,Artist,Attributed to,John Thomson,"British, Edinburgh, Scotland 1837–1921 London",,"Thomson, John","British, Scottish",1837,1921,ca. 1869,1869,1869,Albumen silver print from glass negative,Image: 8 3/8 × 10 3/4 in. (21.3 × 27.3 cm),"Gilman Collection, Purchase, Robert Rosenkranz Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/289323,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.494.52,false,true,289324,Photographs,Photograph,"Garden at the English Consulate, Canton",,,,,,Artist,Attributed to,John Thomson,"British, Edinburgh, Scotland 1837–1921 London",,"Thomson, John","British, Scottish",1837,1921,ca. 1869,1869,1869,Albumen silver print from glass negative,Image: 8 1/4 × 10 13/16 in. (21 × 27.5 cm),"Gilman Collection, Purchase, Robert Rosenkranz Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/289324,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.494.53,false,true,286867,Photographs,Photograph,"Beggars at the Gate of a Temple, Canton",,,,,,Artist,Attributed to,John Thomson,"British, Edinburgh, Scotland 1837–1921 London",,"Thomson, John","British, Scottish",1837,1921,ca. 1869,1869,1869,Albumen silver print from glass negative,Image: 27.3 x 21.3 cm (10 3/4 x 8 3/8 in.),"Gilman Collection, Purchase, Robert Rosenkranz Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286867,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.494.56,false,true,289327,Photographs,Photograph,"[Rapid, Yen-Ping]",,,,,,Artist,Attributed to,John Thomson,"British, Edinburgh, Scotland 1837–1921 London",,"Thomson, John","British, Scottish",1837,1921,ca. 1869,1869,1869,Albumen silver print from glass negative,Image: 7 15/16 × 11 1/8 in. (20.1 × 28.2 cm),"Gilman Collection, Purchase, Robert Rosenkranz Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/289327,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.494.58,false,true,289329,Photographs,Photograph,"Dwelling on the Water, Canton",,,,,,Artist,,John Thomson,"British, Edinburgh, Scotland 1837–1921 London",,"Thomson, John","British, Scottish",1837,1921,ca. 1869,1869,1869,Albumen silver print from glass negative,Image: 8 1/2 × 10 11/16 in. (21.6 × 27.2 cm),"Gilman Collection, Purchase, Robert Rosenkranz Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/289329,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.494.59,false,true,289330,Photographs,Photograph,A Creek in Canton,,,,,,Artist,Attributed to,John Thomson,"British, Edinburgh, Scotland 1837–1921 London",,"Thomson, John","British, Scottish",1837,1921,ca. 1869,1869,1869,Albumen silver print from glass negative,Image: 8 1/2 × 10 13/16 in. (21.6 × 27.5 cm),"Gilman Collection, Purchase, Robert Rosenkranz Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/289330,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.494.60,false,true,289331,Photographs,Photograph,A Creek in Canton,,,,,,Artist,Attributed to,John Thomson,"British, Edinburgh, Scotland 1837–1921 London",,"Thomson, John","British, Scottish",1837,1921,ca. 1869,1869,1869,Albumen silver print from glass negative,Image: 8 7/16 × 10 7/8 in. (21.5 × 27.6 cm),"Gilman Collection, Purchase, Robert Rosenkranz Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/289331,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.494.61,false,true,289332,Photographs,Photograph,Mercantile Junks at Canton,,,,,,Artist,,John Thomson,"British, Edinburgh, Scotland 1837–1921 London",,"Thomson, John","British, Scottish",1837,1921,ca. 1869,1869,1869,Albumen silver print from glass negative,Image: 8 7/16 × 10 9/16 in. (21.4 × 26.8 cm),"Gilman Collection, Purchase, Robert Rosenkranz Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/289332,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.494.62,false,true,289333,Photographs,Photograph,A Garden in Canton,,,,,,Artist,Attributed to,John Thomson,"British, Edinburgh, Scotland 1837–1921 London",,"Thomson, John","British, Scottish",1837,1921,ca. 1869,1869,1869,Albumen silver print from glass negative,Image: 8 7/16 × 10 7/8 in. (21.5 × 27.6 cm),"Gilman Collection, Purchase, Robert Rosenkranz Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/289333,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.494.63,false,true,289334,Photographs,Photograph,"A Tea Pavilion, Canton",,,,,,Artist,Attributed to,John Thomson,"British, Edinburgh, Scotland 1837–1921 London",,"Thomson, John","British, Scottish",1837,1921,ca. 1869,1869,1869,Albumen silver print from glass negative,Image: 8 1/2 × 10 5/8 in. (21.6 × 27 cm),"Gilman Collection, Purchase, Robert Rosenkranz Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/289334,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.494.64,false,true,289335,Photographs,Photograph,"Way to the Theater Pon-Jing-Quais Garden, Canton",,,,,,Artist,Attributed to,John Thomson,"British, Edinburgh, Scotland 1837–1921 London",,"Thomson, John","British, Scottish",1837,1921,ca. 1869,1869,1869,Albumen silver print from glass negative,Image: 8 7/16 × 10 5/8 in. (21.4 × 27 cm),"Gilman Collection, Purchase, Robert Rosenkranz Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/289335,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.494.65,false,true,289336,Photographs,Photograph,Mandarin Dwelling,,,,,,Artist,Attributed to,John Thomson,"British, Edinburgh, Scotland 1837–1921 London",,"Thomson, John","British, Scottish",1837,1921,ca. 1869,1869,1869,Albumen silver print from glass negative,Image: 8 3/8 × 10 5/8 in. (21.2 × 27 cm),"Gilman Collection, Purchase, Robert Rosenkranz Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/289336,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.494.66,false,true,289337,Photographs,Photograph,Madarin at Home,,,,,,Artist,Attributed to,John Thomson,"British, Edinburgh, Scotland 1837–1921 London",,"Thomson, John","British, Scottish",1837,1921,ca. 1869,1869,1869,Albumen silver print from glass negative,Image: 7 5/16 × 10 11/16 in. (18.6 × 27.1 cm),"Gilman Collection, Purchase, Robert Rosenkranz Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/289337,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.494.67,false,true,289338,Photographs,Photograph,Teh Hop-Ho (Canton) and his Son,,,,,,Artist,Attributed to,John Thomson,"British, Edinburgh, Scotland 1837–1921 London",,"Thomson, John","British, Scottish",1837,1921,ca. 1869,1869,1869,Albumen silver print from glass negative,Image: 10 3/8 × 8 3/8 in. (26.4 × 21.2 cm),"Gilman Collection, Purchase, Robert Rosenkranz Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/289338,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.494.68,false,true,289339,Photographs,Photograph,"A Tartar Soldier, Canton",,,,,,Artist,Attributed to,John Thomson,"British, Edinburgh, Scotland 1837–1921 London",,"Thomson, John","British, Scottish",1837,1921,ca. 1869,1869,1869,Albumen silver print from glass negative,Image: 10 11/16 × 8 3/8 in. (27.1 × 21.2 cm),"Gilman Collection, Purchase, Robert Rosenkranz Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/289339,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.494.69,false,true,289340,Photographs,Photograph,"Mandarin Dwelling, Canton",,,,,,Artist,Attributed to,John Thomson,"British, Edinburgh, Scotland 1837–1921 London",,"Thomson, John","British, Scottish",1837,1921,ca. 1869,1869,1869,Albumen silver print from glass negative,Image: 8 7/16 × 10 9/16 in. (21.5 × 26.9 cm),"Gilman Collection, Purchase, Robert Rosenkranz Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/289340,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.494.70,false,true,289341,Photographs,Photograph,"Garden, Canton",,,,,,Artist,Attributed to,John Thomson,"British, Edinburgh, Scotland 1837–1921 London",,"Thomson, John","British, Scottish",1837,1921,ca. 1869,1869,1869,Albumen silver print from glass negative,Image: 8 1/8 × 11 1/8 in. (20.7 × 28.2 cm),"Gilman Collection, Purchase, Robert Rosenkranz Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/289341,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.494.71,false,true,289342,Photographs,Photograph,Macao,,,,,,Artist,Attributed to,John Thomson,"British, Edinburgh, Scotland 1837–1921 London",,"Thomson, John","British, Scottish",1837,1921,ca. 1869,1869,1869,Albumen silver print from glass negative,Image: 7 9/16 × 11 3/16 in. (19.2 × 28.4 cm),"Gilman Collection, Purchase, Robert Rosenkranz Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/289342,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.494.72,false,true,289343,Photographs,Photograph,Fishing Boats going out Macao,,,,,,Artist,Attributed to,John Thomson,"British, Edinburgh, Scotland 1837–1921 London",,"Thomson, John","British, Scottish",1837,1921,ca. 1869,1869,1869,Albumen silver print from glass negative,Image: 7 5/16 × 10 11/16 in. (18.6 × 27.1 cm),"Gilman Collection, Purchase, Robert Rosenkranz Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/289343,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.494.73,false,true,289344,Photographs,Photograph,A Street in Macao,,,,,,Artist,Attributed to,John Thomson,"British, Edinburgh, Scotland 1837–1921 London",,"Thomson, John","British, Scottish",1837,1921,ca. 1869,1869,1869,Albumen silver print from glass negative,Image: 8 5/16 × 10 5/8 in. (21.1 × 27 cm),"Gilman Collection, Purchase, Robert Rosenkranz Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/289344,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.494.74,false,true,289345,Photographs,Photograph,"St. Pauls Cathedral, Macao",,,,,,Artist,Attributed to,John Thomson,"British, Edinburgh, Scotland 1837–1921 London",,"Thomson, John","British, Scottish",1837,1921,ca. 1869,1869,1869,Albumen silver print from glass negative,Image: 10 9/16 × 8 3/8 in. (26.8 × 21.2 cm),"Gilman Collection, Purchase, Robert Rosenkranz Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/289345,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.494.75,false,true,289346,Photographs,Photograph,"Tomb, Macao",,,,,,Artist,Attributed to,John Thomson,"British, Edinburgh, Scotland 1837–1921 London",,"Thomson, John","British, Scottish",1837,1921,ca. 1869,1869,1869,Albumen silver print from glass negative,Image: 10 9/16 × 8 1/8 in. (26.9 × 20.6 cm),"Gilman Collection, Purchase, Robert Rosenkranz Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/289346,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.494.76,false,true,289347,Photographs,Photograph,View in Camoens Garden Macao,,,,,,Artist,Attributed to,John Thomson,"British, Edinburgh, Scotland 1837–1921 London",,"Thomson, John","British, Scottish",1837,1921,ca. 1869,1869,1869,Albumen silver print from glass negative,Image: 10 3/8 × 8 1/4 in. (26.4 × 20.9 cm),"Gilman Collection, Purchase, Robert Rosenkranz Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/289347,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.494.77,false,true,289348,Photographs,Photograph,Swatow,,,,,,Artist,Attributed to,John Thomson,"British, Edinburgh, Scotland 1837–1921 London",,"Thomson, John","British, Scottish",1837,1921,ca. 1869,1869,1869,Albumen silver print from glass negative,Image: 7 11/16 × 10 9/16 in. (19.5 × 26.9 cm),"Gilman Collection, Purchase, Robert Rosenkranz Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/289348,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.494.78,false,true,289349,Photographs,Photograph,View on Rak-Chui opposite Swatow,,,,,,Artist,Attributed to,John Thomson,"British, Edinburgh, Scotland 1837–1921 London",,"Thomson, John","British, Scottish",1837,1921,ca. 1869,1869,1869,Albumen silver print from glass negative,Image: 6 1/4 × 10 9/16 in. (15.8 × 26.9 cm),"Gilman Collection, Purchase, Robert Rosenkranz Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/289349,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.494.79,false,true,289350,Photographs,Photograph,View on Rak-Chui opposite Swatow,,,,,,Artist,Attributed to,John Thomson,"British, Edinburgh, Scotland 1837–1921 London",,"Thomson, John","British, Scottish",1837,1921,ca. 1869,1869,1869,Albumen silver print from glass negative,Image: 6 1/4 × 10 9/16 in. (15.8 × 26.9 cm),"Gilman Collection, Purchase, Robert Rosenkranz Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/289350,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.494.80,false,true,289351,Photographs,Photograph,View on Rak-Chui opposite Swatow,,,,,,Artist,Attributed to,John Thomson,"British, Edinburgh, Scotland 1837–1921 London",,"Thomson, John","British, Scottish",1837,1921,ca. 1869,1869,1869,Albumen silver print from glass negative,Image: 7 3/8 × 10 15/16 in. (18.8 × 27.8 cm),"Gilman Collection, Purchase, Robert Rosenkranz Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/289351,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.494.81,false,true,289352,Photographs,Photograph,View of Swatow Harbour,,,,,,Artist,Attributed to,John Thomson,"British, Edinburgh, Scotland 1837–1921 London",,"Thomson, John","British, Scottish",1837,1921,ca. 1869,1869,1869,Albumen silver print from glass negative,Image: 7 11/16 × 10 11/16 in. (19.6 × 27.2 cm),"Gilman Collection, Purchase, Robert Rosenkranz Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/289352,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.494.82,false,true,289353,Photographs,Photograph,Entrance of Amoy Harbour,,,,,,Artist,Attributed to,John Thomson,"British, Edinburgh, Scotland 1837–1921 London",,"Thomson, John","British, Scottish",1837,1921,ca. 1869,1869,1869,Albumen silver print from glass negative,Image: 6 5/8 × 9 5/16 in. (16.8 × 23.7 cm),"Gilman Collection, Purchase, Robert Rosenkranz Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/289353,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.494.83,false,true,289354,Photographs,Photograph,Amoy Harbour,,,,,,Artist,Attributed to,John Thomson,"British, Edinburgh, Scotland 1837–1921 London",,"Thomson, John","British, Scottish",1837,1921,ca. 1869,1869,1869,Albumen silver print from glass negative,Image: 6 11/16 × 9 1/4 in. (17 × 23.5 cm),"Gilman Collection, Purchase, Robert Rosenkranz Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/289354,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.494.84,false,true,289355,Photographs,Photograph,"View on Koolangsoo Island, Amoy",,,,,,Artist,Attributed to,John Thomson,"British, Edinburgh, Scotland 1837–1921 London",,"Thomson, John","British, Scottish",1837,1921,ca. 1869,1869,1869,Albumen silver print from glass negative,Image: 6 5/8 × 9 1/4 in. (16.9 × 23.5 cm),"Gilman Collection, Purchase, Robert Rosenkranz Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/289355,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.494.85,false,true,289356,Photographs,Photograph,Amoy Harbour,,,,,,Artist,,John Thomson,"British, Edinburgh, Scotland 1837–1921 London",,"Thomson, John","British, Scottish",1837,1921,ca. 1869,1869,1869,Albumen silver print from glass negative,Image: 7 1/4 × 10 3/4 in. (18.4 × 27.3 cm),"Gilman Collection, Purchase, Robert Rosenkranz Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/289356,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.494.86,false,true,289357,Photographs,Photograph,View of Amoy,,,,,,Artist,Attributed to,John Thomson,"British, Edinburgh, Scotland 1837–1921 London",,"Thomson, John","British, Scottish",1837,1921,ca. 1869,1869,1869,Albumen silver print from glass negative,Image: 7 1/2 × 10 1/16 in. (19 × 25.5 cm),"Gilman Collection, Purchase, Robert Rosenkranz Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/289357,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.494.87,false,true,289358,Photographs,Photograph,View of Amoy,,,,,,Artist,Attributed to,John Thomson,"British, Edinburgh, Scotland 1837–1921 London",,"Thomson, John","British, Scottish",1837,1921,ca. 1869,1869,1869,Albumen silver print from glass negative,Image: 7 1/4 × 10 1/8 in. (18.4 × 25.7 cm),"Gilman Collection, Purchase, Robert Rosenkranz Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/289358,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.494.88,false,true,289359,Photographs,Photograph,"Lower Harbour, Amoy",,,,,,Artist,Attributed to,John Thomson,"British, Edinburgh, Scotland 1837–1921 London",,"Thomson, John","British, Scottish",1837,1921,ca. 1869,1869,1869,Albumen silver print from glass negative,Image: 7 3/16 × 10 1/8 in. (18.2 × 25.7 cm),"Gilman Collection, Purchase, Robert Rosenkranz Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/289359,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.494.89,false,true,289360,Photographs,Photograph,"The Grand Stand, Amoy 1871",,,,,,Artist,Attributed to,John Thomson,"British, Edinburgh, Scotland 1837–1921 London",,"Thomson, John","British, Scottish",1837,1921,1871,1871,1871,Albumen silver print from glass negative,Image: 6 3/4 × 10 1/16 in. (17.2 × 25.6 cm),"Gilman Collection, Purchase, Robert Rosenkranz Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/289360,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.494.90,false,true,289361,Photographs,Photograph,"Great Pagoda at Foochow, the Largest in China",,,,,,Artist,Attributed to,John Thomson,"British, Edinburgh, Scotland 1837–1921 London",,"Thomson, John","British, Scottish",1837,1921,ca. 1869,1869,1869,Albumen silver print from glass negative,Image: 9 3/4 × 13 1/4 in. (24.8 × 33.6 cm),"Gilman Collection, Purchase, Robert Rosenkranz Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/289361,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.494.91,false,true,289362,Photographs,Photograph,"Peculiar shaped Rocks on Roolangsoo Island, Amoy",,,,,,Artist,,John Thomson,"British, Edinburgh, Scotland 1837–1921 London",,"Thomson, John","British, Scottish",1837,1921,ca. 1869,1869,1869,Albumen silver print from glass negative,Image: 6 3/4 × 9 3/16 in. (17.1 × 23.3 cm),"Gilman Collection, Purchase, Robert Rosenkranz Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/289362,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.494.92,false,true,289363,Photographs,Photograph,Nine Arch Bridge outside Foochow,,,,,,Artist,Attributed to,John Thomson,"British, Edinburgh, Scotland 1837–1921 London",,"Thomson, John","British, Scottish",1837,1921,ca. 1869,1869,1869,Albumen silver print from glass negative,Image: 9 3/4 × 13 3/16 in. (24.7 × 33.5 cm),"Gilman Collection, Purchase, Robert Rosenkranz Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/289363,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.494.93,false,true,289364,Photographs,Photograph,Amoy Houses,,,,,,Artist,Attributed to,John Thomson,"British, Edinburgh, Scotland 1837–1921 London",,"Thomson, John","British, Scottish",1837,1921,ca. 1869,1869,1869,Albumen silver print from glass negative,Image: 6 11/16 × 9 7/16 in. (17 × 24 cm),"Gilman Collection, Purchase, Robert Rosenkranz Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/289364,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.494.94,false,true,289365,Photographs,Photograph,Twin Pagodas at Foochow,,,,,,Artist,Attributed to,John Thomson,"British, Edinburgh, Scotland 1837–1921 London",,"Thomson, John","British, Scottish",1837,1921,ca. 1869,1869,1869,Albumen silver print from glass negative,Image: 13 1/16 × 9 3/4 in. (33.2 × 24.8 cm),"Gilman Collection, Purchase, Robert Rosenkranz Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/289365,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.494.95,false,true,289366,Photographs,Photograph,Amoy Fishing Boats,,,,,,Artist,Attributed to,John Thomson,"British, Edinburgh, Scotland 1837–1921 London",,"Thomson, John","British, Scottish",1837,1921,ca. 1869,1869,1869,Albumen silver print from glass negative,Image: 6 3/4 × 9 3/16 in. (17.2 × 23.3 cm),"Gilman Collection, Purchase, Robert Rosenkranz Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/289366,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.494.96,false,true,289367,Photographs,Photograph,"Harbour of Hongkong; St Johns Cathedral Hongkong; Carriage; Tub Mending, North of China",,,,,,Artist,Attributed to,John Thomson,"British, Edinburgh, Scotland 1837–1921 London",,"Thomson, John","British, Scottish",1837,1921,ca. 1869,1869,1869,Albumen silver print from glass negative,"Image (a), (b): 3 5/8 × 6 7/8 in. (9.2 × 17.5 cm) Image (c), (d): 3 7/8 × 5 13/16 in. (9.9 × 14.8 cm)","Gilman Collection, Purchase, Robert Rosenkranz Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/289367,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.494.97,false,true,289368,Photographs,Photograph,"Le-Le- Cong Josshouse, Amoy",,,,,,Artist,,John Thomson,"British, Edinburgh, Scotland 1837–1921 London",,"Thomson, John","British, Scottish",1837,1921,ca. 1869,1869,1869,Albumen silver print from glass negative,Image: 6 11/16 × 9 3/16 in. (17 × 23.4 cm),"Gilman Collection, Purchase, Robert Rosenkranz Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/289368,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.494.98,false,true,289369,Photographs,Photograph,Ploughing; Carriage; Cotton Spinning; Selling Sweets,,,,,,Artist,Attributed to,John Thomson,"British, Edinburgh, Scotland 1837–1921 London",,"Thomson, John","British, Scottish",1837,1921,ca. 1869,1869,1869,Albumen silver print from glass negative,Image: 3 15/16 × 5 13/16 in. (10 × 14.8 cm); each,"Gilman Collection, Purchase, Robert Rosenkranz Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/289369,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.494.99,false,true,289370,Photographs,Photograph,View over Amoy from Pe-Le-Jong,,,,,,Artist,Attributed to,John Thomson,"British, Edinburgh, Scotland 1837–1921 London",,"Thomson, John","British, Scottish",1837,1921,ca. 1869,1869,1869,Albumen silver print from glass negative,Image: 8 7/16 × 10 13/16 in. (21.5 × 27.4 cm),"Gilman Collection, Purchase, Robert Rosenkranz Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/289370,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.494.100,false,true,289371,Photographs,Photograph,"Pagoda on the Execution Ground, Foochow",,,,,,Artist,Attributed to,John Thomson,"British, Edinburgh, Scotland 1837–1921 London",,"Thomson, John","British, Scottish",1837,1921,ca. 1869,1869,1869,Albumen silver print from glass negative,Image: 13 3/8 × 9 13/16 in. (33.9 × 24.9 cm),"Gilman Collection, Purchase, Robert Rosenkranz Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/289371,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.494.101,false,true,289372,Photographs,Photograph,"Temple, Amoy",,,,,,Artist,,John Thomson,"British, Edinburgh, Scotland 1837–1921 London",,"Thomson, John","British, Scottish",1837,1921,ca. 1869,1869,1869,Albumen silver print from glass negative,Image: 9 in. × 6 5/8 in. (22.8 × 16.9 cm),"Gilman Collection, Purchase, Robert Rosenkranz Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/289372,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.494.102,false,true,289373,Photographs,Photograph,Street in Kadhin; Foochow Creek Bridge; Country View; Shanghai,,,,,,Artist,Attributed to,John Thomson,"British, Edinburgh, Scotland 1837–1921 London",,"Thomson, John","British, Scottish",1837,1921,ca. 1869,1869,1869,Albumen silver print from glass negative,Image: 3 1/4 × 6 7/8 in. (8.2 × 17.5 cm); approx. each,"Gilman Collection, Purchase, Robert Rosenkranz Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/289373,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.494.103,false,true,289374,Photographs,Photograph,Shoemaker North of China; Wandering Restaurant North of China,,,,,,Artist,Attributed to,John Thomson,"British, Edinburgh, Scotland 1837–1921 London",,"Thomson, John","British, Scottish",1837,1921,ca. 1869,1869,1869,Albumen silver print from glass negative,Image: 4 in. × 5 3/4 in. (10.1 × 14.6 cm); each,"Gilman Collection, Purchase, Robert Rosenkranz Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/289374,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.494.104,false,true,289375,Photographs,Photograph,"View on the Bund; The Monument; The Club House; View from the Bund, Shanghai",,,,,,Artist,Attributed to,John Thomson,"British, Edinburgh, Scotland 1837–1921 London",,"Thomson, John","British, Scottish",1837,1921,ca. 1869,1869,1869,Albumen silver print from glass negative,Image: 3 11/16 × 6 15/16 in. (9.3 × 17.6 cm); each,"Gilman Collection, Purchase, Robert Rosenkranz Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/289375,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.494.105,false,true,289376,Photographs,Photograph,The Bund in Shanghai,,,,,,Artist,Attributed to,John Thomson,"British, Edinburgh, Scotland 1837–1921 London",,"Thomson, John","British, Scottish",1837,1921,ca. 1869,1869,1869,Albumen silver print from glass negative,Image: 9 9/16 × 13 1/16 in. (24.3 × 33.2 cm),"Gilman Collection, Purchase, Robert Rosenkranz Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/289376,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -46.1.61,false,true,269006,Photographs,Photograph,"[The Harbor at Valletta, Malta]",,,,,,Artist,Attributed to,Calvert Richard Jones,"British, Swansea, Wales 1802–1877 Bath, England",,"Jones, Calvert Richard","British, Welsh",1802,1877,1850s,1850,1859,Salted paper print from paper negative,,"David Hunter McAlpin Fund, 1946",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269006,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -46.1.73,false,true,269019,Photographs,Photograph,"[Temple of Concord, Rome]",,,,,,Artist,Attributed to,Calvert Richard Jones,"British, Swansea, Wales 1802–1877 Bath, England",,"Jones, Calvert Richard","British, Welsh",1802,1877,1850s,1850,1859,Salted paper print from paper negative,,"David Hunter McAlpin Fund, 1946",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269019,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -46.1.74,false,true,269020,Photographs,Photograph,"[Temple of Antonius and Faustina, San Lorenzo in Miranda, Rome]",,,,,,Artist,Attributed to,Calvert Richard Jones,"British, Swansea, Wales 1802–1877 Bath, England",,"Jones, Calvert Richard","British, Welsh",1802,1877,1850s,1850,1859,Salted paper print from paper negative,,"David Hunter McAlpin Fund, 1946",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269020,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -46.1.109,false,true,268882,Photographs,Photograph,"[Strada Levante, Valletta, Malta]",,,,,,Artist,Attributed to,Calvert Richard Jones,"British, Swansea, Wales 1802–1877 Bath, England",,"Jones, Calvert Richard","British, Welsh",1802,1877,1850s,1850,1859,Salted paper print from paper negative,Image: 21.7 x 17.4 cm (8 9/16 x 6 7/8 in.),"David Hunter McAlpin Fund, 1946",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268882,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1992.5167,false,true,266875,Photographs,Photograph,The Capitoline,,,,,,Artist,,Calvert Richard Jones,"British, Swansea, Wales 1802–1877 Bath, England",,"Jones, Calvert Richard","British, Welsh",1802,1877,1846,1846,1846,Salted paper print from a paper negative,16.2 x 21.2 cm. (6 3/8 x 8 3/6 in.),"Purchase, Hans P. Kraus Jr. Gift, and Mrs. Harrison D. Horblit and Joyce and Robert Menschel Gifts, 1992",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/266875,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.159.27,false,true,306247,Photographs,Photograph,"Saint Paul's Bay, Malta",,,,,,Artist,,Calvert Richard Jones,"British, Swansea, Wales 1802–1877 Bath, England",,"Jones, Calvert Richard","British, Welsh",1802,1877,Spring 1846,1846,1846,Salted paper print from paper negative,Image: 6 5/8 × 8 9/16 in. (16.9 × 21.7 cm) Sheet: 7 1/4 × 8 7/8 in. (18.4 × 22.6 cm),"Bequest of Maurice B. Sendak, 2012",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306247,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.159.28,false,true,306248,Photographs,Photograph,Seated Lad,,,,,,Artist,,Calvert Richard Jones,"British, Swansea, Wales 1802–1877 Bath, England",,"Jones, Calvert Richard","British, Welsh",1802,1877,1845–50,1845,1850,Salted paper print from paper negative,Image: 4 3/16 × 3 7/16 in. (10.6 × 8.7 cm),"Bequest of Maurice B. Sendak, 2012",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306248,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.159.29,false,true,306249,Photographs,Photograph,Family Group Portrait Posed in Doorway,,,,,,Artist,,Calvert Richard Jones,"British, Swansea, Wales 1802–1877 Bath, England",,"Jones, Calvert Richard","British, Welsh",1802,1877,late 1840s,1845,1849,Salted paper print from paper negative,Height: 3 3/8 in. (8.5 cm) Width: 4 1/4 in. (10.8 cm),"Bequest of Maurice B. Sendak, 2012",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306249,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.159.30,false,true,306250,Photographs,Photograph,"Portrait of the Gardener, possibly David Roderick",,,,,,Artist,,Calvert Richard Jones,"British, Swansea, Wales 1802–1877 Bath, England",,"Jones, Calvert Richard","British, Welsh",1802,1877,late 1840s,1845,1849,Salted paper print from paper negative,Height: 6 15/16 in. (17.7 cm) Width: 8 11/16 in. (22.1 cm),"Bequest of Maurice B. Sendak, 2012",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306250,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.159.31,false,true,306251,Photographs,"Photograph, contact sheet",[Contact sheet of four group portraits in a doorway],,,,,,Artist,,Calvert Richard Jones,"British, Swansea, Wales 1802–1877 Bath, England",,"Jones, Calvert Richard","British, Welsh",1802,1877,ca. 1850,1845,1855,Salted paper print from paper negative,Image: 7 3/16 in. × 9 in. (18.2 × 22.8 cm) Sheet: 7 3/8 × 9 1/8 in. (18.7 × 23.1 cm),"Bequest of Maurice B. Sendak, 2012",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306251,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.159.32,false,true,306252,Photographs,Photograph,Two Young Men Resting on a Pier,,,,,,Artist,,Calvert Richard Jones,"British, Swansea, Wales 1802–1877 Bath, England",,"Jones, Calvert Richard","British, Welsh",1802,1877,late 1840s,1845,1849,Salted paper print from paper negative,Image: 4 5/16 × 3 3/8 in. (11 × 8.6 cm) Sheet: 3 13/16 × 4 11/16 in. (9.7 × 11.9 cm),"Bequest of Maurice B. Sendak, 2012",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306252,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.159.33,false,true,306253,Photographs,Photograph,Duomo Milan,,,,,,Artist,,Calvert Richard Jones,"British, Swansea, Wales 1802–1877 Bath, England",,"Jones, Calvert Richard","British, Welsh",1802,1877,1846,1846,1846,Salted paper print from paper negative,Image: 8 1/2 × 6 5/8 in. (21.6 × 16.9 cm) Sheet: 9 7/16 × 7 11/16 in. (24 × 19.6 cm),"Bequest of Maurice B. Sendak, 2012",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306253,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.159.34,false,true,306254,Photographs,Photograph,"67. Colosseum, Rome, Second View",,,,,,Artist,,Calvert Richard Jones,"British, Swansea, Wales 1802–1877 Bath, England",,"Jones, Calvert Richard","British, Welsh",1802,1877,May 1846,1846,1846,Salted paper print from paper negative,Image: 6 7/8 × 8 9/16 in. (17.4 × 21.7 cm) Sheet: 7 5/16 × 8 7/8 in. (18.5 × 22.5 cm),"Bequest of Maurice B. Sendak, 2012",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306254,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.159.35,false,true,306255,Photographs,Photograph,Lady in Open Window with Bird Cage,,,,,,Artist,,Calvert Richard Jones,"British, Swansea, Wales 1802–1877 Bath, England",or his Circle,"Jones, Calvert Richard","British, Welsh",1802,1877,late 1840s,1845,1849,Salted paper print from paper negative,Sheet: 4 7/16 × 3 11/16 in. (11.2 × 9.4 cm) Image: 4 in. × 2 15/16 in. (10.2 × 7.5 cm),"Bequest of Maurice B. Sendak, 2012",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306255,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.851,false,true,285837,Photographs,Photograph,[Woman],,,,,,Artist,,Calvert Richard Jones,"British, Swansea, Wales 1802–1877 Bath, England",,"Jones, Calvert Richard","British, Welsh",1802,1877,1845–50,1845,1850,Salted paper print from paper negative,Image: 3 15/16 × 3 5/16 in. (10 × 8.4 cm),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285837,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.852,false,true,285838,Photographs,Photograph,[Soldier],,,,,,Artist,,Calvert Richard Jones,"British, Swansea, Wales 1802–1877 Bath, England",,"Jones, Calvert Richard","British, Welsh",1802,1877,1845–50,1845,1850,Salted paper print from paper negative,Image: 4 3/16 × 3 3/16 in. (10.6 × 8.1 cm),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285838,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2005.100.947a, b",false,true,285836,Photographs,Photograph,"Santa Lucia, Naples",,,,,,Artist,,Calvert Richard Jones,"British, Swansea, Wales 1802–1877 Bath, England",,"Jones, Calvert Richard","British, Welsh",1802,1877,1845–46,1845,1846,Salted paper prints from paper negatives,22.4 x 36.2 cm (8 13/16 x 14 1/4 in.) overall Image: 22.2 x 17 cm (8 3/4 x 6 11/16 in.) each,"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285836,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1981.1229.5,false,true,263190,Photographs,Photograph,[Thereza Dillwyn Llewelyn with Her Microscope],,,,,,Artist,,John Dillwyn Llewelyn,"British, Swansea, Wales 1810–1882 Swansea, Wales",,"Llewelyn, John Dillwyn","British, Welsh",1810,1882,ca. 1854,1852,1856,Salted paper print from glass negative,23.5 x 18.7 cm (9 1/4 x 7 3/8 in. ),"Robert O. Dougan Collection, Gift of Warner Communications Inc., 1981",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/263190,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -63.606.1.20,false,true,270835,Photographs,Photograph,"Piscator, No. 2",,,,,,Artist,,John Dillwyn Llewelyn,"British, Swansea, Wales 1810–1882 Swansea, Wales",,"Llewelyn, John Dillwyn","British, Welsh",1810,1882,1856,1856,1856,Albumen silver print,,"The Elisha Whittelsey Collection, The Elisha Whittelsey Fund, 1963",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/270835,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.573,false,true,285667,Photographs,Photograph,"The Wigwam, a Canadian Scene at Penllergare",,,,,,Artist,,John Dillwyn Llewelyn,"British, Swansea, Wales 1810–1882 Swansea, Wales",,"Llewelyn, John Dillwyn","British, Welsh",1810,1882,ca. 1855,1853,1857,Albumen silver print from glass negative,Image: 18.5 x 22.1 cm (7 5/16 x 8 11/16 in.) Mount: 22.1 x 36.9 cm (8 11/16 x 14 1/2 in.),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285667,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.382 (2),false,true,287892,Photographs,Photograph,Fishing for Shells,,,,,,Artist,,John Dillwyn Llewelyn,"British, Swansea, Wales 1810–1882 Swansea, Wales",,"Llewelyn, John Dillwyn","British, Welsh",1810,1882,1853–56,1853,1856,Salted paper print,Image: 15.6 × 20.5 cm (6 1/8 × 8 1/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287892,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.382 (3),false,true,287893,Photographs,Photograph,Feeding Poor Puss,,,,,,Artist,,John Dillwyn Llewelyn,"British, Swansea, Wales 1810–1882 Swansea, Wales",,"Llewelyn, John Dillwyn","British, Welsh",1810,1882,1853–56,1853,1856,Salted paper print,Image: 13.5 × 10.9 cm (5 5/16 × 4 5/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287893,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.382 (6),false,true,287896,Photographs,Photograph,Winds & Waves,,,,,,Artist,,John Dillwyn Llewelyn,"British, Swansea, Wales 1810–1882 Swansea, Wales",,"Llewelyn, John Dillwyn","British, Welsh",1810,1882,1853–56,1853,1856,Salted paper print,Image: 12 × 10.8 cm (4 3/4 × 4 1/4 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287896,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.382 (7),false,true,287897,Photographs,Photograph,Elinor and Lucy Llewelyn,,,,,,Artist,,John Dillwyn Llewelyn,"British, Swansea, Wales 1810–1882 Swansea, Wales",,"Llewelyn, John Dillwyn","British, Welsh",1810,1882,1853–56,1853,1856,Salted paper print,Image: 12.4 × 10.5 cm (4 7/8 × 4 1/8 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287897,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.382 (9),false,true,287899,Photographs,Photograph,Upper End of the Lake Penllergare,,,,,,Artist,,John Dillwyn Llewelyn,"British, Swansea, Wales 1810–1882 Swansea, Wales",,"Llewelyn, John Dillwyn","British, Welsh",1810,1882,1853–56,1853,1856,Salted paper print,Image: 16.9 × 21.9 cm (6 5/8 × 8 5/8 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287899,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.382 (11),false,true,287901,Photographs,Photograph,"[Two Women, One Kneeling and One Standing, Looking into Basket Filled with Vegetables]",,,,,,Artist,,John Dillwyn Llewelyn,"British, Swansea, Wales 1810–1882 Swansea, Wales",,"Llewelyn, John Dillwyn","British, Welsh",1810,1882,1853–56,1853,1856,Albumen silver print,Image: 19.2 × 14.9 cm (7 9/16 × 5 7/8 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287901,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.382 (13),false,true,287903,Photographs,Photograph,After the Storm,,,,,,Artist,,John Dillwyn Llewelyn,"British, Swansea, Wales 1810–1882 Swansea, Wales",,"Llewelyn, John Dillwyn","British, Welsh",1810,1882,1853–56,1853,1856,Salted paper print,Image: 19.5 × 15.3 cm (7 11/16 in. × 6 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287903,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.382 (23),false,true,287913,Photographs,Photograph,The Boating Party,,,,,,Artist,,John Dillwyn Llewelyn,"British, Swansea, Wales 1810–1882 Swansea, Wales",,"Llewelyn, John Dillwyn","British, Welsh",1810,1882,1853–56,1853,1856,Salted paper print,Image: 15.5 × 20 cm (6 1/8 × 7 7/8 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287913,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.382 (25),false,true,287915,Photographs,Photograph,Caswell,,,,,,Artist,,John Dillwyn Llewelyn,"British, Swansea, Wales 1810–1882 Swansea, Wales",,"Llewelyn, John Dillwyn","British, Welsh",1810,1882,1853–56,1853,1856,Salted paper print,Image: 15.1 × 20.4 cm (5 15/16 × 8 1/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287915,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.382 (26),false,true,287916,Photographs,Photograph,"Remember, remember the 5th of November!",,,,,,Artist,,John Dillwyn Llewelyn,"British, Swansea, Wales 1810–1882 Swansea, Wales",,"Llewelyn, John Dillwyn","British, Welsh",1810,1882,1853–56,1853,1856,Salted paper print,Image: 15.6 × 19.5 cm (6 1/8 × 7 11/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287916,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.382 (27),false,true,287917,Photographs,Photograph,Three Cliffs Bay,,,,,,Artist,,John Dillwyn Llewelyn,"British, Swansea, Wales 1810–1882 Swansea, Wales",,"Llewelyn, John Dillwyn","British, Welsh",1810,1882,1853–56,1853,1856,Salted paper print,Image: 17.8 × 15.8 cm (7 in. × 6 1/4 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287917,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.382 (31),false,true,287921,Photographs,Photograph,The Lonely Glen,,,,,,Artist,,John Dillwyn Llewelyn,"British, Swansea, Wales 1810–1882 Swansea, Wales",,"Llewelyn, John Dillwyn","British, Welsh",1810,1882,1853–56,1853,1856,Salted paper print,Image: 16 × 21 cm (6 5/16 × 8 1/4 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287921,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.382 (33),false,true,287923,Photographs,Photograph,The Sweet Water Fountain,,,,,,Artist,,John Dillwyn Llewelyn,"British, Swansea, Wales 1810–1882 Swansea, Wales",,"Llewelyn, John Dillwyn","British, Welsh",1810,1882,1853–56,1853,1856,Salted paper print,Image: 14.7 × 12 cm (14.7 × 12 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287923,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.382 (34),false,true,287924,Photographs,Photograph,The Great Torr and Crawley Rocks,,,,,,Artist,,John Dillwyn Llewelyn,"British, Swansea, Wales 1810–1882 Swansea, Wales",,"Llewelyn, John Dillwyn","British, Welsh",1810,1882,1853–56,1853,1856,Salted paper print,Image: 20.6 × 26 cm (8 1/8 × 10 1/4 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287924,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.382 (45),false,true,287935,Photographs,Photograph,Tenby Lifeboat,,,,,,Artist,,John Dillwyn Llewelyn,"British, Swansea, Wales 1810–1882 Swansea, Wales",,"Llewelyn, John Dillwyn","British, Welsh",1810,1882,1853–56,1853,1856,Salted paper print,Image: 15.9 × 20.2 cm (6 1/4 × 7 15/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287935,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.382 (82),false,true,287971,Photographs,Photograph,"The ""Juno"" in Tenby Harbour",,,,,,Artist,,John Dillwyn Llewelyn,"British, Swansea, Wales 1810–1882 Swansea, Wales",,"Llewelyn, John Dillwyn","British, Welsh",1810,1882,1853–56,1853,1856,Salted paper print,Image: 15.2 × 20.2 cm (6 in. × 7 15/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287971,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2005.100.382 (10a, b)",false,true,287900,Photographs,Photograph,Johnny and Drum; Dead Game,,,,,,Artist,,John Dillwyn Llewelyn,"British, Swansea, Wales 1810–1882 Swansea, Wales",,"Llewelyn, John Dillwyn","British, Welsh",1810,1882,1853–56,1853,1856,Salted paper print,Image: 12.7 × 10.9 cm (5 in. × 4 5/16 in.) (a) Image: 7.5 × 6.4 cm (2 15/16 × 2 1/2 in.) (b),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287900,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2005.100.382 (12a, b)",false,true,287902,Photographs,Photograph,Caswell Garden; Geneviève,,,,,,Artist,,John Dillwyn Llewelyn,"British, Swansea, Wales 1810–1882 Swansea, Wales",,"Llewelyn, John Dillwyn","British, Welsh",1810,1882,1853–56,1853,1856,Salted paper print,Image: 12.8 × 11 cm (5 1/16 × 4 5/16 in.) (a) Image: 11.5 × 8.8 cm (4 1/2 × 3 7/16 in.) (b),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287902,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2005.100.382 (16a, b)",false,true,287906,Photographs,Photograph,Water Lilies; The Photographer,,,,,,Artist,,John Dillwyn Llewelyn,"British, Swansea, Wales 1810–1882 Swansea, Wales",,"Llewelyn, John Dillwyn","British, Welsh",1810,1882,1853–56,1853,1856,Salted paper print,Image: 4.7 × 6.7 cm (1 7/8 × 2 5/8 in.) (a) Image: 13 × 10.7 cm (5 1/8 × 4 3/16 in.) (b),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287906,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2005.100.382 (61a, b)",false,true,287951,Photographs,Photograph,Lanelay; [Untitled],,,,,,Artist,,John Dillwyn Llewelyn,"British, Swansea, Wales 1810–1882 Swansea, Wales",,"Llewelyn, John Dillwyn","British, Welsh",1810,1882,1853–56,1853,1856,Salted paper print; albumen silver print,Image: 14.7 × 11.1 cm (5 13/16 × 4 3/8 in.) Image: 8.1 × 11.6 cm (3 3/16 × 4 9/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287951,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.120,false,true,283247,Photographs,Photograph,Oscar Wilde,,,,,,Artist,,Napoleon Sarony,"American (born Canada), Quebec 1821–1896 New York",,"Sarony, Napoleon","American, born Canada",1821,1896,1882,1882,1882,Albumen silver print,Image: 30.5 x 18.4 cm (12 x 7 1/4 in.),"Gilman Collection, Purchase, Ann Tenenbaum and Thomas H. Lee Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/283247,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.828,false,true,285691,Photographs,Photograph,Oscar Wilde,,,,,,Artist,,Napoleon Sarony,"American (born Canada), Quebec 1821–1896 New York",,"Sarony, Napoleon","American, born Canada",1821,1896,1882,1882,1882,Albumen silver print from glass negative,Image: 12 in. × 7 1/4 in. (30.5 × 18.4 cm) Mount: 12 15/16 × 7 3/8 in. (32.8 × 18.8 cm),"Gilman Collection, Purchase, Ann Tenenbaum and Thomas H. Lee Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285691,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.622,false,true,283242,Photographs,Photograph,Countess Greffulhe,,,,,,Artist,,Otto Wegener,"French (born Sweden), Helsingborg 1849–1922 Paris",,"Wegener, Otto","French, born Sweden",1849,1922,1899,1899,1899,Gelatin silver print,Image: 26 15/16 × 15 3/8 in. (68.4 × 39 cm) Mount: 27 3/16 × 15 11/16 in. (69 × 39.8 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/283242,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.538.1,false,true,291981,Photographs,Photograph,[Woman with Tea Set Playing the Koto],,,,,,Artist,,Felice Beato,"British (born Italy), Venice 1832–1909 Luxor, Egypt",,"Beato, Felice","British, born Italy",1832,1909,ca. 1860,1860,1860,Albumen silver print from glass negative,Image: 20.8 x 26.2 cm (8 3/16 x 10 5/16 in.) Mount: 34.3 x 48.3 cm (13 1/2 x 19 in.),"Gift of Isaac Lagnado, in honor of Paula J. Giardina, 2009",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/291981,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.538.2,false,true,291982,Photographs,Photograph,"""Shariki,"" or Cart-Pushing Coolies",,,,,,Artist,,Felice Beato,"British (born Italy), Venice 1832–1909 Luxor, Egypt",,"Beato, Felice","British, born Italy",1832,1909,ca. 1860,1860,1860,Albumen silver print from glass negative,Image: 20.8 x 25.4 cm (8 3/16 x 10 in.) Mount: 32.4 x 51.1 cm (12 3/4 x 20 1/8 in.),"Gift of Isaac Lagnado, in honor of Mary Stack, 2009",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/291982,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.72,false,true,283169,Photographs,Photograph,[After the Capture of the Taku Forts],,,,,,Artist,,Felice Beato,"British (born Italy), Venice 1832–1909 Luxor, Egypt",,"Beato, Felice","British, born Italy",1832,1909,1860,1860,1860,Albumen silver print from glass negative,Image: 26 x 29.9 cm (10 1/4 x 11 3/4 in.) Mount: 29.3 x 32.5 cm (11 9/16 x 12 13/16 in.),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/283169,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.566,false,true,285902,Photographs,Photograph,"Samurai, Yokohama",,,,,,Artist,,Felice Beato,"British (born Italy), Venice 1832–1909 Luxor, Egypt",,"Beato, Felice","British, born Italy",1832,1909,1864–65,1864,1865,Albumen silver print from glass negative,Image: 17.9 x 14.6 cm (7 1/16 x 5 3/4 in.),"Gilman Collection, Purchase, Robert Rosenkranz Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285902,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1994.263.20,false,true,282192,Photographs,Photograph,"[Man Standing in Front of Movie Theater, Omar, West Virginia]",,,,,,Artist,,Ben Shahn,"American (born Lithuania), Kaunas 1898–1969 New York",,"Shahn, Ben","American, born Lithuania",1898,1969,1935,1935,1935,Gelatin silver print,17.3 x 24.6 cm (6 13/16 x 9 11/16 in. ),"Walker Evans Archive, 1994",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/282192,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1994.263.21,false,true,282193,Photographs,Photograph,"[""Prepare to Meet God"" Arrow-Shaped Sign in Cornfield, Williamson, West Virginia]",,,,,,Artist,,Ben Shahn,"American (born Lithuania), Kaunas 1898–1969 New York",,"Shahn, Ben","American, born Lithuania",1898,1969,October 1935,1935,1935,Gelatin silver print,18.8 x 22.8 cm (7 3/8 x 9 in. ),"Walker Evans Archive, 1994",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/282193,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.488,false,true,286303,Photographs,Photograph,"Cotton Pickers, Pulaski County, Arkansas",,,,,,Artist,,Ben Shahn,"American (born Lithuania), Kaunas 1898–1969 New York",,"Shahn, Ben","American, born Lithuania",1898,1969,October 1935,1935,1935,Gelatin silver print,Image: 18.8 x 22.5 cm (7 3/8 x 8 7/8 in.) Mount: 35.6 x 27.8 cm (14 x 10 15/16 in.),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286303,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1987.1100.227,false,true,265274,Photographs,Photograph,[Smithland Bank],,,,,,Artist,,Ben Shahn,"American (born Lithuania), Kaunas 1898–1969 New York",,"Shahn, Ben","American, born Lithuania",1898,1969,1930s,1930,1939,Gelatin silver print,14.9 x 23.0 cm (5 7/8 x 9 1/16 in.),"Ford Motor Company Collection, Gift of Ford Motor Company and John C. Waddell, 1987",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/265274,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1987.1100.285,false,true,265337,Photographs,Photograph,"[Street Scene, Natchez, Mississippi: Two Women Walking along Sidewalk before Storefront]",,,,,,Artist,,Ben Shahn,"American (born Lithuania), Kaunas 1898–1969 New York",,"Shahn, Ben","American, born Lithuania",1898,1969,1935,1935,1935,Gelatin silver print,26.6 x 34.1 cm (10 1/2 x 13 7/16 in.),"Ford Motor Company Collection, Gift of Ford Motor Company and John C. Waddell, 1987",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/265337,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -56.517.3,false,true,269851,Photographs,Photograph,"Mrs. Greenhow and Daughter, Imprisoned in the Old Capitol, Washington",,,,,,Artist,,Alexander Gardner,"American, Glasgow, Scotland 1821–1882 Washington, D.C.",,"Gardner, Alexander","American, Scottish",1821,1882,1862,1862,1862,Albumen silver print,,"David Hunter McAlpin Fund, 1956",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269851,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.537.1,false,true,259595,Photographs,Photograph,"Burying the Dead on the Battlefield of Antietam, September 1862",,,,,,Artist,,Alexander Gardner,"American, Glasgow, Scotland 1821–1882 Washington, D.C.",,"Gardner, Alexander","American, Scottish",1821,1882,1862,1862,1862,Albumen silver print from glass negative,Image: 7.1 x 10 cm (2 13/16 x 3 15/16 in.) Mount: 7.3 x 10.6 cm (2 7/8 x 4 3/16 in.),"Purchase, Florance Waterbury Bequest, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/259595,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.537.2,false,true,259604,Photographs,Photograph,"View on the Battlefield of Antietam, September 1862",,,,,,Artist,,Alexander Gardner,"American, Glasgow, Scotland 1821–1882 Washington, D.C.",,"Gardner, Alexander","American, Scottish",1821,1882,1862,1862,1862,Albumen silver print from glass negative,Image: 7.1 x 10 cm (2 13/16 x 3 15/16 in.) Mount: 7.3 x 10.6 cm (2 7/8 x 4 3/16 in.),"Purchase, Florance Waterbury Bequest, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/259604,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.537.3,false,true,259605,Photographs,Photograph,"View in the Field, On the West Side of the Hagerstown Road, After the Battle of Antietam, Maryland, September 1862",,,,,,Artist,,Alexander Gardner,"American, Glasgow, Scotland 1821–1882 Washington, D.C.",,"Gardner, Alexander","American, Scottish",1821,1882,1862,1862,1862,Albumen silver print from glass negative,,"Purchase, Florance Waterbury Bequest, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/259605,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.537.5,false,true,259607,Photographs,Photograph,"Lutheran Church, Sharpsburgh, Maryland, September 1862",,,,,,Artist,,Alexander Gardner,"American, Glasgow, Scotland 1821–1882 Washington, D.C.",,"Gardner, Alexander","American, Scottish",1821,1882,1862,1862,1862,Albumen silver print from glass negative,,"Purchase, Florance Waterbury Bequest, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/259607,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.537.6,false,true,259608,Photographs,Photograph,"Is This Death - Antietam Battlefield, September 1862",,,,,,Artist,,Alexander Gardner,"American, Glasgow, Scotland 1821–1882 Washington, D.C.",,"Gardner, Alexander","American, Scottish",1821,1882,1862,1862,1862,Albumen silver print from glass negative,,"Purchase, Florance Waterbury Bequest, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/259608,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.537.7,false,true,259609,Photographs,Photograph,"Military Telegraphic Corps, Army of the Potomac, Berlin, October 1862",,,,,,Artist,,Alexander Gardner,"American, Glasgow, Scotland 1821–1882 Washington, D.C.",,"Gardner, Alexander","American, Scottish",1821,1882,1862,1862,1862,Albumen silver print from glass negative,Image: 6 11/16 in. × 9 in. (17 × 22.8 cm) Sheet: 10 1/16 in. × 12 in. (25.5 × 30.5 cm),"Purchase, Florance Waterbury Bequest, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/259609,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.537.8,false,true,259610,Photographs,Photograph,"Group at Secret Service Department, Headquarters, Army of the Potomac, Antietam, October 1862",,,,,,Artist,,Alexander Gardner,"American, Glasgow, Scotland 1821–1882 Washington, D.C.",,"Gardner, Alexander","American, Scottish",1821,1882,1862,1862,1862,Albumen silver print from glass negative,,"Purchase, Florance Waterbury Bequest, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/259610,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.537.9,false,true,259611,Photographs,Photograph,"Antietam Bridge, On the Sharpsburgh and Boonsboro Turnpike, No. 3, September 1862",,,,,,Artist,,Alexander Gardner,"American, Glasgow, Scotland 1821–1882 Washington, D.C.",,"Gardner, Alexander","American, Scottish",1821,1882,1862,1862,1862,Albumen silver print from glass negative,,"Purchase, Florance Waterbury Bequest, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/259611,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.537.10,false,true,259596,Photographs,Photograph,"Antietam Bridge, On the Sharpsburg and Boonsboro Turnpike, No. 1, September 1862",,,,,,Artist,,Alexander Gardner,"American, Glasgow, Scotland 1821–1882 Washington, D.C.",,"Gardner, Alexander","American, Scottish",1821,1882,1862,1862,1862,Albumen silver print from glass negative,,"Purchase, Florance Waterbury Bequest, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/259596,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.537.11,false,true,259597,Photographs,Photograph,"Antietam Bridge, On the Sharpsburg and Boonsboro Turnpike, No. 2, September 1862",,,,,,Artist,,Alexander Gardner,"American, Glasgow, Scotland 1821–1882 Washington, D.C.",,"Gardner, Alexander","American, Scottish",1821,1882,1862,1862,1862,Albumen silver print from glass negative,,"Purchase, Florance Waterbury Bequest, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/259597,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.537.12,false,true,259598,Photographs,Photograph,"Pontoon Bridge, Across the Potomac, at Berlin, Maryland, November 1862",,,,,,Artist,,Alexander Gardner,"American, Glasgow, Scotland 1821–1882 Washington, D.C.",,"Gardner, Alexander","American, Scottish",1821,1882,1862,1862,1862,Albumen silver print from glass negative,,"Purchase, Florance Waterbury Bequest, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/259598,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.537.13,false,true,259599,Photographs,Photograph,"Group at Headquarters of the Army of the Potomac, Antietam, October 1862",,,,,,Artist,,Alexander Gardner,"American, Glasgow, Scotland 1821–1882 Washington, D.C.",,"Gardner, Alexander","American, Scottish",1821,1882,1862,1862,1862,Albumen silver print from glass negative,,"Purchase, Florance Waterbury Bequest, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/259599,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.537.14,false,true,259600,Photographs,Photograph,"Burnside Bridge, Across the Antietam, near Sharpsburg, No. 1, September 1862",,,,,,Artist,,Alexander Gardner,"American, Glasgow, Scotland 1821–1882 Washington, D.C.",,"Gardner, Alexander","American, Scottish",1821,1882,1862,1862,1862,Albumen silver print from glass negative,,"Purchase, Florance Waterbury Bequest, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/259600,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.537.15,false,true,259601,Photographs,Photograph,"Group at Headquarters of the Army of the Potomac, Antietam, October 1862",,,,,,Artist,,Alexander Gardner,"American, Glasgow, Scotland 1821–1882 Washington, D.C.",,"Gardner, Alexander","American, Scottish",1821,1882,1862,1862,1862,Albumen silver print from glass negative,,"Purchase, Florance Waterbury Bequest, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/259601,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.537.16,false,true,259602,Photographs,Photograph,"Pontoon Bridge Across the Potomac, Berlin, October 1862",,,,,,Artist,,Alexander Gardner,"American, Glasgow, Scotland 1821–1882 Washington, D.C.",,"Gardner, Alexander","American, Scottish",1821,1882,1862,1862,1862,Albumen silver print from glass negative,,"Purchase, Florance Waterbury Bequest, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/259602,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1981.1158.2,false,true,263042,Photographs,Photograph,"Mrs. Greenhow and Daughter, Imprisoned in the Old Capitol, Washington",,,,,,Artist,,Alexander Gardner,"American, Glasgow, Scotland 1821–1882 Washington, D.C.",,"Gardner, Alexander","American, Scottish",1821,1882,1862,1862,1862,Albumen silver print from glass negative,,"Gift of Mrs. A. Hyatt Mayor, 1981",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/263042,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.1166.2,false,true,264880,Photographs,Photograph,"Brigadier General Gustavus A. DeRussy and Staff on Steps of Arlington House, Arlington, Virginia",,,,,,Artist,,Alexander Gardner,"American, Glasgow, Scotland 1821–1882 Washington, D.C.",,"Gardner, Alexander","American, Scottish",1821,1882,May 1864,1864,1864,Albumen silver print from glass negative,17.2 x 23cm (6 3/4 x 9 1/16in.) Mount: 27.7 x 31.8cm (10 7/8 x 12 1/2in.),"A. Hyatt Mayor Purchase Fund, Marjorie Phelps Starr Bequest, 1986",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/264880,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.90,false,true,283192,Photographs,Photograph,[Antietam Battlefield],,,,,,Artist,,Alexander Gardner,"American, Glasgow, Scotland 1821–1882 Washington, D.C.",,"Gardner, Alexander","American, Scottish",1821,1882,1862,1862,1862,Albumen silver print from glass negative,Image: 9.1 x 11.8cm (3 9/16 x 4 5/8in.) Mount: 4 3/16 in. × 4 15/16 in. (10.7 × 12.6 cm),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/283192,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.97,false,true,283201,Photographs,Photograph,Lewis Powell [alias Lewis Payne],,,,,,Artist,,Alexander Gardner,"American, Glasgow, Scotland 1821–1882 Washington, D.C.",,"Gardner, Alexander","American, Scottish",1821,1882,"April 27, 1865",1865,1865,Albumen silver print from glass negative,22.4 × 17.4 cm (8 13/16 × 6 7/8 in.),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/283201,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.243,false,true,286388,Photographs,Photographs,[Thaddeus Stevens Lying in State in the Rotunda of the Capitol at Washington],,,,,,Artist,,Alexander Gardner,"American, Glasgow, Scotland 1821–1882 Washington, D.C.",,"Gardner, Alexander","American, Scottish",1821,1882,1867,1867,1867,Albumen silver print from glass negative,Image: 16.8 x 20.5 cm (6 5/8 x 8 1/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286388,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.251,false,true,283202,Photographs,Photograph,Execution of the Conspirators,,,,,,Artist,,Alexander Gardner,"American, Glasgow, Scotland 1821–1882 Washington, D.C.",,"Gardner, Alexander","American, Scottish",1821,1882,"July 7, 1865",1865,1865,Albumen silver print from glass negative,Image: 16.8 x 24.2 cm (6 5/8 x 9 1/2 in.),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/283202,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.563,false,true,286442,Photographs,Photograph,[Four Officers],,,,,,Artist,,Alexander Gardner,"American, Glasgow, Scotland 1821–1882 Washington, D.C.",,"Gardner, Alexander","American, Scottish",1821,1882,ca. 1864,1862,1866,Albumen silver print from glass negative,Image: 17.8 x 22.8 cm (7 x 9 in.),"Gilman Collection, Purchase, Sam Salz Foundation Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286442,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.785,false,true,285881,Photographs,Photograph,Queen Emma of Hawaii and Her Entourage,,,,,,Artist,,Alexander Gardner,"American, Glasgow, Scotland 1821–1882 Washington, D.C.",,"Gardner, Alexander","American, Scottish",1821,1882,1865,1865,1865,Albumen silver print from glass negative,Image: 14 1/8 × 17 15/16 in. (35.9 × 45.6 cm),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285881,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.1118,false,true,286710,Photographs,Photographs,"Gardner's Gallery, 7th and D Streets, Washington, D.C.",,,,,,Artist,,Alexander Gardner,"American, Glasgow, Scotland 1821–1882 Washington, D.C.",,"Gardner, Alexander","American, Scottish",1821,1882,1864,1864,1864,Albumen silver print from glass negative,Image: 2 15/16 × 4 1/8 in. (7.5 × 10.5 cm),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286710,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.1191,false,true,285846,Photographs,Photograph,"[Grand Army Review, Washington, D.C.]",,,,,,Artist,,Alexander Gardner,"American, Glasgow, Scotland 1821–1882 Washington, D.C.",,"Gardner, Alexander","American, Scottish",1821,1882,May 1865,1865,1865,Albumen silver print from glass negative,Image: 3 3/4 × 4 1/2 in. (9.5 × 11.4 cm),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285846,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.1192,false,true,294721,Photographs,Photograph,"[Grand Army Review, Washington, D.C.]",,,,,,Artist,,Alexander Gardner,"American, Glasgow, Scotland 1821–1882 Washington, D.C.",,"Gardner, Alexander","American, Scottish",1821,1882,May 1865,1865,1865,Albumen silver print from glass negative,Image: 3 3/4 × 4 1/2 in. (9.5 × 11.4 cm),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/294721,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.1193,false,true,283204,Photographs,Photograph,"[Grand Army Review, Pennsylvania Avenue, Washington]",,,,,,Artist,,Alexander Gardner,"American, Glasgow, Scotland 1821–1882 Washington, D.C.",,"Gardner, Alexander","American, Scottish",1821,1882,"May 23 or 24, 1865",1865,1865,Albumen silver print from glass negative,"8.8 × 9.9 cm (3 7/16 × 3 7/8 in.), irregularly trimmed","Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/283204,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.1194,false,true,294722,Photographs,Photograph,"[Grand Army Review, Washington, D.C.]",,,,,,Artist,,Alexander Gardner,"American, Glasgow, Scotland 1821–1882 Washington, D.C.",,"Gardner, Alexander","American, Scottish",1821,1882,May 1865,1865,1865,Albumen silver print from glass negative,Image: 3 3/4 × 20 1/2 in. (9.5 × 52.1 cm),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/294722,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.1196,false,true,294725,Photographs,Photograph,"[Grand Army Review, Pennsylvania Avenue, Washington]",,,,,,Artist,,Alexander Gardner,"American, Glasgow, Scotland 1821–1882 Washington, D.C.",,"Gardner, Alexander","American, Scottish",1821,1882,"May 23 or 24, 1865",1865,1865,Albumen silver print from glass negative,"8.5 × 10.1 cm (3 3/8 × 4 in.), irregularly trimmed","Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/294725,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.1197,false,true,294726,Photographs,Photograph,"[Grand Army Review, Washington]",,,,,,Artist,,Alexander Gardner,"American, Glasgow, Scotland 1821–1882 Washington, D.C.",,"Gardner, Alexander","American, Scottish",1821,1882,"May 23 or 24, 1865",1865,1865,Albumen silver print from glass negative,"8.2 × 10 cm (3 1/4 × 3 15/16 in.), irregularly trimmed","Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/294726,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.1198,false,true,294724,Photographs,Photograph,"[Grand Army Review, Washington, D.C.]",,,,,,Artist,,Alexander Gardner,"American, Glasgow, Scotland 1821–1882 Washington, D.C.",,"Gardner, Alexander","American, Scottish",1821,1882,May 1865,1865,1865,Albumen silver print from glass negative,Image: 3 3/4 × 4 1/2 in. (9.6 × 11.4 cm),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/294724,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.1214,false,true,286583,Photographs,Photograph,[Bearded Man in Tweed Jacket],,,,,,Artist,Attributed to,Alexander Gardner,"American, Glasgow, Scotland 1821–1882 Washington, D.C.",,"Gardner, Alexander","American, Scottish",1821,1882,early 1860s,1860,1860,Albumen silver print from glass negative,Image: 11 3/16 × 8 3/4 in. (28.4 × 22.2 cm),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286583,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.1215,false,true,286584,Photographs,Photograph,Lincoln Inauguration,,,,,,Artist,,Alexander Gardner,"American, Glasgow, Scotland 1821–1882 Washington, D.C.",,"Gardner, Alexander","American, Scottish",1821,1882,"March 4 ,1865",1865,1865,Albumen silver print from glass negative,Image: 7 1/4 × 9 1/4 in. (18.4 × 23.5 cm),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286584,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.1216,false,true,286585,Photographs,Photograph,General McClellan and Staff,,,,,,Artist,Attributed to,Alexander Gardner,"American, Glasgow, Scotland 1821–1882 Washington, D.C.",,"Gardner, Alexander","American, Scottish",1821,1882,ca. 1863,1858,1868,Albumen silver print from glass negative,Image: 9 × 14 in. (22.9 × 35.6 cm),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286585,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.1219,false,true,286614,Photographs,Photographs,"Mill, Richmond, Virginia",,,,,,Artist,,Alexander Gardner,"American, Glasgow, Scotland 1821–1882 Washington, D.C.",,"Gardner, Alexander","American, Scottish",1821,1882,ca. 1865,1863,1867,Albumen silver print from glass negative,Image: 6 3/4 × 8 7/8 in. (17.1 × 22.5 cm),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286614,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.1223,false,true,286617,Photographs,Photograph,Planning the Capture of Booth,,,,,,Artist,,Alexander Gardner,"American, Glasgow, Scotland 1821–1882 Washington, D.C.",,"Gardner, Alexander","American, Scottish",1821,1882,1865,1865,1865,Albumen silver print from glass negative,Image: 27.1 × 24.5 cm (10 11/16 × 9 5/8 in.),"Gilman Collection, Purchase, Ann Tenenbaum and Thomas H. Lee Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286617,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.1225,false,true,286441,Photographs,Photograph,"Cannon, Fortress Monroe",,,,,,Artist,,Alexander Gardner,"American, Glasgow, Scotland 1821–1882 Washington, D.C.",,"Gardner, Alexander","American, Scottish",1821,1882,ca. 1864,1859,1869,Albumen silver print from glass negative,Image: 7 15/16 × 9 3/4 in. (20.2 × 24.8 cm),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286441,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.1230,false,true,285619,Photographs,Photograph,Naval Blockade,,,,,,Artist,,Alexander Gardner,"American, Glasgow, Scotland 1821–1882 Washington, D.C.",,"Gardner, Alexander","American, Scottish",1821,1882,1865,1865,1865,Albumen silver print from glass negative,Image: 7 in. × 9 5/8 in. (17.8 × 24.4 cm),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285619,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.1244,false,true,285756,Photographs,Photograph,[Black Soldier in Camp],,,,,,Artist,Possibly by,Alexander Gardner,"American, Glasgow, Scotland 1821–1882 Washington, D.C.",,"Gardner, Alexander","American, Scottish",1821,1882,ca. 1863,1861,1865,Albumen silver print from glass negative,Image: 6 in. × 8 7/16 in. (15.2 × 21.4 cm) Mount: 9 × 11 in. (22.9 × 27.9 cm),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285756,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -43.92,false,true,268827,Photographs,Photograph,Sir John Herschel,,,,,,Artist,,Julia Margaret Cameron,"British (born India), Calcutta 1815–1879 Kalutara, Ceylon",,"Cameron, Julia Margaret","British, born India",1815,1815,1867,1867,1867,Carbon Print,,"David Hunter McAlpin Fund, 1943",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268827,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -63.545,false,true,270819,Photographs,Photograph,Pomona,,,,,,Artist,,Julia Margaret Cameron,"British (born India), Calcutta 1815–1879 Kalutara, Ceylon",,"Cameron, Julia Margaret","British, born India",1815,1815,1872,1872,1872,Albumen silver print from glass negative,Image: 36.4 x 26.3 cm (14 5/16 x 10 3/8 in.) Mount: 49.7 x 37.4 cm (19 9/16 x 14 3/4 in.),"David Hunter McAlpin Fund, 1963",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/270819,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -35.85.1,false,true,268300,Photographs,Photograph,Henry Taylor,,,,,,Artist,,Julia Margaret Cameron,"British (born India), Calcutta 1815–1879 Kalutara, Ceylon",,"Cameron, Julia Margaret","British, born India",1815,1815,1867,1867,1867,Albumen silver print from glass negative,,"Gift of Lucy Chauncey, in memory of her father, Henry Chauncey, 1935",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268300,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -35.85.2,false,true,268301,Photographs,Photograph,"[The Lord Bishop of Winchester, Samuel Wilberforce]",,,,,,Artist,,Julia Margaret Cameron,"British (born India), Calcutta 1815–1879 Kalutara, Ceylon",,"Cameron, Julia Margaret","British, born India",1815,1815,1872,1872,1872,Albumen silver print,,"Gift of Lucy Chauncey, in memory of her father, Henry Chauncey, 1935",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268301,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -35.85.3,false,true,268302,Photographs,Photograph,Sir John Herschel,,,,,,Artist,,Julia Margaret Cameron,"British (born India), Calcutta 1815–1879 Kalutara, Ceylon",,"Cameron, Julia Margaret","British, born India",1815,1815,1867,1867,1867,Albumen silver print from glass negative,Image: 33.8 x 26.2cm (13 5/16 x 10 5/16in.) Mount: 58.3 x 46.2 cm (22 15/16 x 18 3/16 in.),"Gift of Lucy Chauncey, in memory of her father, Henry Chauncey, 1935",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268302,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -41.21.2,false,true,268702,Photographs,Photograph,La Madonna Riposata,,,,,,Artist,,Julia Margaret Cameron,"British (born India), Calcutta 1815–1879 Kalutara, Ceylon",,"Cameron, Julia Margaret","British, born India",1815,1815,1865,1865,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1941",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268702,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -41.21.3,false,true,268713,Photographs,Photograph,The Maid of Athens (May Prinsep),,,,,,Artist,,Julia Margaret Cameron,"British (born India), Calcutta 1815–1879 Kalutara, Ceylon",,"Cameron, Julia Margaret","British, born India",1815,1815,1866,1866,1866,Albumen silver print from glass negative,28.9 x 23.2 cm (11 3/8 x 9 1/8 in. ),"Harris Brisbane Dick Fund, 1941",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268713,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -41.21.4,false,true,268714,Photographs,Photograph,Daisy,,,,,,Artist,,Julia Margaret Cameron,"British (born India), Calcutta 1815–1879 Kalutara, Ceylon",,"Cameron, Julia Margaret","British, born India",1815,1815,1864,1864,1864,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1941",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268714,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -41.21.5,false,true,268715,Photographs,Photograph,Minnie Thackeray,,,,,,Artist,,Julia Margaret Cameron,"British (born India), Calcutta 1815–1879 Kalutara, Ceylon",,"Cameron, Julia Margaret","British, born India",1815,1815,1865,1865,1865,Albumen silver print from glass negative,24.8 x 19.6 cm. (9 3/4 x 7 3/4 in.),"Harris Brisbane Dick Fund, 1941",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268715,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -41.21.6,false,true,268716,Photographs,Photograph,Julia Herschel,,,,,,Artist,,Julia Margaret Cameron,"British (born India), Calcutta 1815–1879 Kalutara, Ceylon",,"Cameron, Julia Margaret","British, born India",1815,1815,1865,1865,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1941",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268716,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -41.21.7,false,true,268717,Photographs,Photograph,The Madonna Penserosa,,,,,,Artist,,Julia Margaret Cameron,"British (born India), Calcutta 1815–1879 Kalutara, Ceylon",,"Cameron, Julia Margaret","British, born India",1815,1815,1864,1864,1864,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1941",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268717,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -41.21.8,false,true,268718,Photographs,Photograph,"Alfred, Lord Tennyson",,,,,,Artist,,Julia Margaret Cameron,"British (born India), Calcutta 1815–1879 Kalutara, Ceylon",,"Cameron, Julia Margaret","British, born India",1815,1815,1865,1865,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1941",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268718,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -41.21.9,false,true,268719,Photographs,Photograph,"Charles Hay Cameron, Esq., in His Garden at Freshwater",,,,,,Artist,,Julia Margaret Cameron,"British (born India), Calcutta 1815–1879 Kalutara, Ceylon",,"Cameron, Julia Margaret","British, born India",1815,1815,1865–67,1865,1867,Albumen silver print from glass negative,33.4 x 26.7 cm (13 1/8 x 10 1/2 in. ),"Harris Brisbane Dick Fund, 1941",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268719,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.692,false,true,260359,Photographs,Photograph,[Ceylonese Group by a Tree],,,,,,Artist,,Julia Margaret Cameron,"British (born India), Calcutta 1815–1879 Kalutara, Ceylon",,"Cameron, Julia Margaret","British, born India",1815,1815,1878,1878,1878,Albumen silver print from glass negative,27 x 19.9 cm (10 5/8 x 7 13/16 in.),"The Elisha Whittelsey Collection, The Elisha Whittelsey Fund, 1972",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260359,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.17.17,false,true,268363,Photographs,Photograph,Thomas Carlyle,,,,,,Artist,,Julia Margaret Cameron,"British (born India), Calcutta 1815–1879 Kalutara, Ceylon",,"Cameron, Julia Margaret","British, born India",1815,1815,1867,1867,1867,Albumen silver print,,"Gift of Edith Root Grant, Edward W. Root and Eliho Root Jr., 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268363,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -41.21.11,false,true,268693,Photographs,Photograph,Contemplations,,,,,,Artist,,Julia Margaret Cameron,"British (born India), Calcutta 1815–1879 Kalutara, Ceylon",,"Cameron, Julia Margaret","British, born India",1815,1815,1864,1864,1864,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1941",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268693,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -41.21.12,false,true,268694,Photographs,Photograph,Daughters of Jerusalem,,,,,,Artist,,Julia Margaret Cameron,"British (born India), Calcutta 1815–1879 Kalutara, Ceylon",,"Cameron, Julia Margaret","British, born India",1815,1815,1865,1865,1865,Albumen silver print from glass negative,18.1 x 27.7 cm (7 1/8 x 10 7/8 in. ),"Harris Brisbane Dick Fund, 1941",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268694,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -41.21.13,false,true,268695,Photographs,Photograph,[Mary Hillier],,,,,,Artist,,Julia Margaret Cameron,"British (born India), Calcutta 1815–1879 Kalutara, Ceylon",,"Cameron, Julia Margaret","British, born India",1815,1815,ca. 1864–66,1864,1866,Albumen silver print from glass negative,19.8 x 14.8 cm (7 13/16 x 5 13/16 in. ),"Harris Brisbane Dick Fund, 1941",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268695,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -41.21.14,false,true,268696,Photographs,Photograph,Sappho,,,,,,Artist,,Julia Margaret Cameron,"British (born India), Calcutta 1815–1879 Kalutara, Ceylon",,"Cameron, Julia Margaret","British, born India",1815,1815,1865,1865,1865,Albumen silver print from glass negative,34.4 x 26.1 cm (13 9/16 x 10 1/4 in. ),"Harris Brisbane Dick Fund, 1941",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268696,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -41.21.15,false,true,268697,Photographs,Photograph,The Mountain Nymph Sweet Liberty,,,,,,Artist,,Julia Margaret Cameron,"British (born India), Calcutta 1815–1879 Kalutara, Ceylon",,"Cameron, Julia Margaret","British, born India",1815,1815,1866,1866,1866,Albumen silver print from glass negative,36.1 x 28.6 cm (14 3/16 x 11 1/4 in. ),"Harris Brisbane Dick Fund, 1941",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268697,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -41.21.17,false,true,268699,Photographs,Photograph,[James Rogers],,,,,,Artist,,Julia Margaret Cameron,"British (born India), Calcutta 1815–1879 Kalutara, Ceylon",,"Cameron, Julia Margaret","British, born India",1815,1815,1867,1867,1867,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1941",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268699,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -41.21.18,false,true,268700,Photographs,Photograph,May Prinsep,,,,,,Artist,,Julia Margaret Cameron,"British (born India), Calcutta 1815–1879 Kalutara, Ceylon",,"Cameron, Julia Margaret","British, born India",1815,1815,1868,1868,1868,Albumen silver print from glass negative,22.5 x 20.5 cm. (8 7/8 x 8 1/16 in.),"Harris Brisbane Dick Fund, 1941",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268700,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -41.21.19,false,true,268701,Photographs,Photograph,The South West Wind,,,,,,Artist,,Julia Margaret Cameron,"British (born India), Calcutta 1815–1879 Kalutara, Ceylon",,"Cameron, Julia Margaret","British, born India",1815,1815,1864,1864,1864,Albumen silver print from glass negative,25.4 x 21.6 cm (10 x 8 1/2 in. ),"Harris Brisbane Dick Fund, 1941",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268701,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -41.21.20,false,true,268703,Photographs,Photograph,The Vicar of Freshwater,,,,,,Artist,,Julia Margaret Cameron,"British (born India), Calcutta 1815–1879 Kalutara, Ceylon",,"Cameron, Julia Margaret","British, born India",1815,1815,1864,1864,1864,Albumen silver print from glass negative,26.4 x 20.8 cm (10 3/8 x 8 3/16 in. ),"Harris Brisbane Dick Fund, 1941",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268703,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -41.21.22,false,true,268705,Photographs,Photograph,Tennyson Reading,,,,,,Artist,,Julia Margaret Cameron,"British (born India), Calcutta 1815–1879 Kalutara, Ceylon",,"Cameron, Julia Margaret","British, born India",1815,1815,1865,1865,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1941",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268705,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -41.21.23,false,true,268706,Photographs,Photograph,"Henry Taylor. Author of ""Philip Van Artevelde""",,,,,,Artist,,Julia Margaret Cameron,"British (born India), Calcutta 1815–1879 Kalutara, Ceylon",,"Cameron, Julia Margaret","British, born India",1815,1815,1864,1864,1864,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1941",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268706,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -41.21.24,false,true,268707,Photographs,Photograph,Henry Taylor,,,,,,Artist,,Julia Margaret Cameron,"British (born India), Calcutta 1815–1879 Kalutara, Ceylon",,"Cameron, Julia Margaret","British, born India",1815,1815,1864,1864,1864,Albumen silver print from glass negative,25.5 x 20.1 cm (10 1/16 x 7 15/16 in. ),"Harris Brisbane Dick Fund, 1941",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268707,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -41.21.25,false,true,268708,Photographs,Photograph,Henry Taylor,,,,,,Artist,,Julia Margaret Cameron,"British (born India), Calcutta 1815–1879 Kalutara, Ceylon",,"Cameron, Julia Margaret","British, born India",1815,1815,1864,1864,1864,Albumen silver print from glass negative,26.1 x 20.8 cm (10 1/4 x 8 3/16 in. ),"Harris Brisbane Dick Fund, 1941",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268708,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -41.21.26,false,true,268709,Photographs,Photograph,Christabel,,,,,,Artist,,Julia Margaret Cameron,"British (born India), Calcutta 1815–1879 Kalutara, Ceylon",,"Cameron, Julia Margaret","British, born India",1815,1815,1866,1866,1866,Albumen silver print from glass negative,33.2 x 26.9 cm (13 1/16 x 10 9/16 in. ),"Harris Brisbane Dick Fund, 1941",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268709,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -41.21.27,false,true,268710,Photographs,Photograph,Herr Joachim,,,,,,Artist,,Julia Margaret Cameron,"British (born India), Calcutta 1815–1879 Kalutara, Ceylon",,"Cameron, Julia Margaret","British, born India",1815,1815,1868,1868,1868,Albumen silver print from glass negative,29.5 x 24.3 cm (11 5/8 x 9 9/16 in. ),"Harris Brisbane Dick Fund, 1941",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268710,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -41.21.28,false,true,268711,Photographs,Photograph,William Gifford Palgrave,,,,,,Artist,,Julia Margaret Cameron,"British (born India), Calcutta 1815–1879 Kalutara, Ceylon",,"Cameron, Julia Margaret","British, born India",1815,1815,1868,1868,1868,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1941",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268711,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -41.21.29,false,true,268712,Photographs,Photograph,"Lionel Tennyson, Freshwater",,,,,,Artist,,Julia Margaret Cameron,"British (born India), Calcutta 1815–1879 Kalutara, Ceylon",,"Cameron, Julia Margaret","British, born India",1815,1815,1869,1869,1869,Albumen silver print from glass negative,Image: 24.5 x 30.4 cm (9 5/8 x 11 15/16 in.),"Harris Brisbane Dick Fund, 1941",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268712,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -66.633.1,false,true,271077,Photographs,Photograph,"Alfred, Lord Tennyson",,,,,,Artist,,Julia Margaret Cameron,"British (born India), Calcutta 1815–1879 Kalutara, Ceylon",,"Cameron, Julia Margaret","British, born India",1815,1815,1865,1865,1865,Albumen silver print from glass negative,"Image: 22.9 x 18.4 cm (9 x 7 1/4 in.), rounded top Mount: 40.5 x 24.6 cm (15 15/16 x 9 11/16 in.), irregular","David Hunter McAlpin Fund, 1966",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/271077,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -66.633.2,false,true,271078,Photographs,Photograph,"Alfred, Lord Tennyson",,,,,,Artist,,Julia Margaret Cameron,"British (born India), Calcutta 1815–1879 Kalutara, Ceylon",,"Cameron, Julia Margaret","British, born India",1815,1815,ca. 1865,1863,1867,Albumen silver print from glass negative,,"David Hunter McAlpin Fund, 1966",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/271078,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -69.607.1,false,true,271510,Photographs,Photograph,[Unidentified Child],,,,,,Artist,,Julia Margaret Cameron,"British (born India), Calcutta 1815–1879 Kalutara, Ceylon",,"Cameron, Julia Margaret","British, born India",1815,1815,1873,1873,1873,Albumen silver print from glass negative,33.9 x 24.3 cm (13 3/8 x 9 9/16 in. ),"The Elisha Whittelsey Collection, The Elisha Whittelsey Fund, 1969",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/271510,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -69.607.2,false,true,271515,Photographs,Photograph,[Mary Ryan],,,,,,Artist,,Julia Margaret Cameron,"British (born India), Calcutta 1815–1879 Kalutara, Ceylon",,"Cameron, Julia Margaret","British, born India",1815,1815,1865–66,1865,1866,Albumen silver print from glass negative,33.1 x 24.7 cm (13 1/16 x 9 3/4 in. ),"The Elisha Whittelsey Collection, The Elisha Whittelsey Fund, 1969",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/271515,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -69.607.3,false,true,271516,Photographs,Photograph,Ceylonese Woman,,,,,,Artist,,Julia Margaret Cameron,"British (born India), Calcutta 1815–1879 Kalutara, Ceylon",,"Cameron, Julia Margaret","British, born India",1815,1815,1875–79,1875,1879,Albumen silver print from glass negative,,"The Elisha Whittelsey Collection, The Elisha Whittelsey Fund, 1969",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/271516,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -69.607.4,false,true,271517,Photographs,Photograph,"Mrs. Halford Vaugham, Freshwater",,,,,,Artist,,Julia Margaret Cameron,"British (born India), Calcutta 1815–1879 Kalutara, Ceylon",,"Cameron, Julia Margaret","British, born India",1815,1815,1873,1873,1873,Albumen silver print,33.5 x 27.3 cm (13 3/16 x 10 3/4 in. ),"The Elisha Whittelsey Collection, The Elisha Whittelsey Fund, 1969",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/271517,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -69.607.7,false,true,271520,Photographs,Photograph,Marie Spartali,,,,,,Artist,,Julia Margaret Cameron,"British (born India), Calcutta 1815–1879 Kalutara, Ceylon",,"Cameron, Julia Margaret","British, born India",1815,1815,1868,1868,1868,Albumen silver print,,"The Elisha Whittelsey Collection, The Elisha Whittelsey Fund, 1969",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/271520,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -69.607.8,false,true,271521,Photographs,Photograph,Aubrey de Vere,,,,,,Artist,,Julia Margaret Cameron,"British (born India), Calcutta 1815–1879 Kalutara, Ceylon",,"Cameron, Julia Margaret","British, born India",1815,1815,1866–68,1866,1868,Albumen silver print,32.5 x 26.8 cm (12 13/16 x 10 9/16 in. ),"The Elisha Whittelsey Collection, The Elisha Whittelsey Fund, 1969",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/271521,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -69.607.9,false,true,271522,Photographs,Photograph,Beatrice,,,,,,Artist,,Julia Margaret Cameron,"British (born India), Calcutta 1815–1879 Kalutara, Ceylon",,"Cameron, Julia Margaret","British, born India",1815,1815,1866,1866,1866,Albumen silver print from glass negative,36.8 x 29.0 cm (14 1/2 x 11 7/16 in. ),"The Elisha Whittelsey Collection, The Elisha Whittelsey Fund, 1969",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/271522,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1996.99.2,true,true,267426,Photographs,Photograph,Julia Jackson,,,,,,Artist,,Julia Margaret Cameron,"British (born India), Calcutta 1815–1879 Kalutara, Ceylon",,"Cameron, Julia Margaret","British, born India",1815,1815,1867,1867,1867,Albumen silver print from glass negative,27.4 x 20.6 cm (10 13/16 x 8 1/8 in.),"Purchase, Joseph Pulitzer Bequest, 1996",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267426,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -41.21.1.1,false,true,268690,Photographs,Photograph,Charles Hay Cameron,,,,,,Artist,,Julia Margaret Cameron,"British (born India), Calcutta 1815–1879 Kalutara, Ceylon",,"Cameron, Julia Margaret","British, born India",1815,1815,1864,1864,1864,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1941",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268690,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -41.21.1.2,false,true,268691,Photographs,Photograph,A Study,,,,,,Artist,,Julia Margaret Cameron,"British (born India), Calcutta 1815–1879 Kalutara, Ceylon",,"Cameron, Julia Margaret","British, born India",1815,1815,1864,1864,1864,Albumen silver print from glass negative,21.7 x 17.6 cm (8 9/16 x 6 15/16 in. ),"Harris Brisbane Dick Fund, 1941",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268691,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -69.607.10,false,true,271511,Photographs,Photograph,[Unidentified Woman in Profile],,,,,,Artist,,Julia Margaret Cameron,"British (born India), Calcutta 1815–1879 Kalutara, Ceylon",,"Cameron, Julia Margaret","British, born India",1815,1815,1866–68,1866,1868,Albumen silver print,33.9 x 25.8 cm (13 3/8 x 10 3/16 in. ),"The Elisha Whittelsey Collection, The Elisha Whittelsey Fund, 1969",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/271511,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -69.607.11,false,true,271512,Photographs,Photograph,[Woman in Robes Reading a Book],,,,,,Artist,,Julia Margaret Cameron,"British (born India), Calcutta 1815–1879 Kalutara, Ceylon",,"Cameron, Julia Margaret","British, born India",1815,1815,1870,1870,1870,Albumen silver print from glass negative,35.1 x 27.3 cm (13 13/16 x 10 3/4 in. ),"The Elisha Whittelsey Collection, The Elisha Whittelsey Fund, 1969",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/271512,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -69.607.12,false,true,271513,Photographs,Photograph,May. Freshwater,,,,,,Artist,,Julia Margaret Cameron,"British (born India), Calcutta 1815–1879 Kalutara, Ceylon",,"Cameron, Julia Margaret","British, born India",1815,1815,1870,1870,1870,Albumen silver print from glass negative,,"The Elisha Whittelsey Collection, The Elisha Whittelsey Fund, 1969",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/271513,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -69.607.13,false,true,271514,Photographs,Photograph,[Unidentified Child],,,,,,Artist,,Julia Margaret Cameron,"British (born India), Calcutta 1815–1879 Kalutara, Ceylon",,"Cameron, Julia Margaret","British, born India",1815,1815,1873,1873,1873,Albumen silver print,32.3 x 24.4 cm (12 11/16 x 9 5/8 in. ),"The Elisha Whittelsey Collection, The Elisha Whittelsey Fund, 1969",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/271514,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1973.502.2,false,true,260731,Photographs,Photograph,[Egeria],,,,,,Artist,,Julia Margaret Cameron,"British (born India), Calcutta 1815–1879 Kalutara, Ceylon",,"Cameron, Julia Margaret","British, born India",1815,1815,1874,1874,1874,Albumen silver print from glass negative,36.2 x 27.6 cm. (14 1/4 x 10 7/8 in.),"The Elisha Whittelsey Collection, The Elisha Whittelsey Fund, 1972",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260731,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.159.3,false,true,306204,Photographs,Photograph,King Lear Alotting His Kingdom to His Three Daughters,,,,,,Artist,,Julia Margaret Cameron,"British (born India), Calcutta 1815–1879 Kalutara, Ceylon",,"Cameron, Julia Margaret","British, born India",1815,1815,1872,1872,1872,Albumen silver print from glass negative,Mount: 18 1/2 × 14 7/8 in. (47 × 37.8 cm) Image: 13 1/8 × 11 1/16 in. (33.4 × 28.1 cm),"Bequest of Maurice B. Sendak, 2012",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306204,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -52.524.3.1,false,true,282128,Photographs,Photograph,Gareth and Lynette,,,,,,Artist,,Julia Margaret Cameron,"British (born India), Calcutta 1815–1879 Kalutara, Ceylon",,"Cameron, Julia Margaret","British, born India",1815,1815,1874,1874,1874,Albumen silver print from glass negative,33.9 x 28.2 cm (13 3/8 x 11 1/8 in. ),"David Hunter McAlpin Fund, 1952",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/282128,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -52.524.3.2,false,true,282119,Photographs,Photograph,Enid,,,,,,Artist,,Julia Margaret Cameron,"British (born India), Calcutta 1815–1879 Kalutara, Ceylon",,"Cameron, Julia Margaret","British, born India",1815,1815,September 1874,1874,1874,Albumen silver print from glass negative,34.2 x 26.7 cm (13 7/16 x 10 1/2 in. ),"David Hunter McAlpin Fund, 1952",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/282119,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -52.524.3.3,false,true,282120,Photographs,Photograph,And Enid Sang,,,,,,Artist,,Julia Margaret Cameron,"British (born India), Calcutta 1815–1879 Kalutara, Ceylon",,"Cameron, Julia Margaret","British, born India",1815,1815,September 1874,1874,1874,Albumen silver print from glass negative,35.4 x 28 cm (13 15/16 x 11 in. ),"David Hunter McAlpin Fund, 1952",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/282120,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -52.524.3.4,false,true,282121,Photographs,Photograph,Vivien and Merlin,,,,,,Artist,,Julia Margaret Cameron,"British (born India), Calcutta 1815–1879 Kalutara, Ceylon",,"Cameron, Julia Margaret","British, born India",1815,1815,September 1874,1874,1874,Albumen silver print from glass negative,31.9 x 28 cm (12 9/16 x 11 in. ),"David Hunter McAlpin Fund, 1952",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/282121,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -52.524.3.5,false,true,282118,Photographs,Photograph,Vivien and Merlin,,,,,,Artist,,Julia Margaret Cameron,"British (born India), Calcutta 1815–1879 Kalutara, Ceylon",,"Cameron, Julia Margaret","British, born India",1815,1815,1874,1874,1874,Albumen silver print from glass negative,30.4 x 25.3 cm (11 15/16 x 9 15/16 in. ),"David Hunter McAlpin Fund, 1952",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/282118,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -52.524.3.6,false,true,282122,Photographs,Photograph,Elaine the Lily - Maid of Astolat,,,,,,Artist,,Julia Margaret Cameron,"British (born India), Calcutta 1815–1879 Kalutara, Ceylon",,"Cameron, Julia Margaret","British, born India",1815,1815,1874,1874,1874,Albumen silver print from glass negative,34.3 x 28.4 cm (13 1/2 x 11 3/16 in. ),"David Hunter McAlpin Fund, 1952",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/282122,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -52.524.3.7,false,true,282129,Photographs,Photograph,Elaine,,,,,,Artist,,Julia Margaret Cameron,"British (born India), Calcutta 1815–1879 Kalutara, Ceylon",,"Cameron, Julia Margaret","British, born India",1815,1815,1874,1874,1874,Albumen silver print from glass negative,33.5 x 27.8 cm (13 3/16 x 10 15/16 in. ),"David Hunter McAlpin Fund, 1952",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/282129,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -52.524.3.8,false,true,282147,Photographs,Photograph,Sir Galahad and the Pale Nun,,,,,,Artist,,Julia Margaret Cameron,"British (born India), Calcutta 1815–1879 Kalutara, Ceylon",,"Cameron, Julia Margaret","British, born India",1815,1815,1874,1874,1874,Albumen silver print from glass negative,33.2 x 27.5 cm (13 1/16 x 10 13/16 in. ),"David Hunter McAlpin Fund, 1952",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/282147,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -52.524.3.9,false,true,282148,Photographs,Photograph,Queen Guinevere,,,,,,Artist,,Julia Margaret Cameron,"British (born India), Calcutta 1815–1879 Kalutara, Ceylon",,"Cameron, Julia Margaret","British, born India",1815,1815,1874,1874,1874,Albumen silver print from glass negative,34.1 x 25.5 cm (13 7/16 x 10 1/16 in. ),"David Hunter McAlpin Fund, 1952",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/282148,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1990.1074.1,false,true,266254,Photographs,Photograph,The Passing of Arthur,,,,,,Artist,,Julia Margaret Cameron,"British (born India), Calcutta 1815–1879 Kalutara, Ceylon",,"Cameron, Julia Margaret","British, born India",1815,1815,1874,1874,1874,Albumen silver print from glass negative,35.2 x 25.2 cm. (13 7/8 x 9 15/16 in.),"Bequest of James David Nelson, in memory of Samuel J. Wagstaff Jr., 1990",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/266254,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1990.1074.2,false,true,266255,Photographs,Photograph,Beatrice,,,,,,Artist,,Julia Margaret Cameron,"British (born India), Calcutta 1815–1879 Kalutara, Ceylon",,"Cameron, Julia Margaret","British, born India",1815,1815,1866,1866,1866,Albumen silver print from glass negative,34.6 x 26.3 cm. (13 5/8 x 10 3/6 in.),"Bequest of James David Nelson, in memory of Samuel J. Wagstaff Jr., 1990",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/266255,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1990.1074.3,false,true,266256,Photographs,Photograph,A Study,,,,,,Artist,,Julia Margaret Cameron,"British (born India), Calcutta 1815–1879 Kalutara, Ceylon",,"Cameron, Julia Margaret","British, born India",1815,1815,1865–66,1865,1866,Albumen silver print from glass negative,34.4 x 26.4 cm. (13 9/16 x 10 3/8 in.),"Bequest of James David Nelson, in memory of Samuel J. Wagstaff Jr., 1990",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/266256,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1990.1074.4,false,true,266257,Photographs,Photograph,Circe,,,,,,Artist,,Julia Margaret Cameron,"British (born India), Calcutta 1815–1879 Kalutara, Ceylon",,"Cameron, Julia Margaret","British, born India",1815,1815,1865,1865,1865,Albumen silver print from glass negative,25.3 x 20.1 cm. (9 15/16 x 7 15/16 in.),"Bequest of James David Nelson, in memory of Samuel J. Wagstaff Jr., 1990",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/266257,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1990.1074.5,false,true,266258,Photographs,Photograph,A Lovely Sketch,,,,,,Artist,,Julia Margaret Cameron,"British (born India), Calcutta 1815–1879 Kalutara, Ceylon",,"Cameron, Julia Margaret","British, born India",1815,1815,1873,1873,1873,Albumen silver print from glass negative,31.0 x 24.3 cm. (12 3/16 x 9 9/16 in.),"Bequest of James David Nelson, in memory of Samuel J. Wagstaff Jr., 1990",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/266258,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1990.1074.6,false,true,266259,Photographs,Photograph,English Blossoms,,,,,,Artist,,Julia Margaret Cameron,"British (born India), Calcutta 1815–1879 Kalutara, Ceylon",,"Cameron, Julia Margaret","British, born India",1815,1815,1873,1873,1873,Albumen silver print from glass negative,32.8 x 27.5 cm. (12 15/16 x 10 13/16 in.),"Bequest of James David Nelson, in memory of Samuel J. Wagstaff Jr., 1990",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/266259,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1990.1074.7,false,true,266260,Photographs,Carte-de-visite,Summer Days,,,,,,Artist,,Julia Margaret Cameron,"British (born India), Calcutta 1815–1879 Kalutara, Ceylon",,"Cameron, Julia Margaret","British, born India",1815,1815,1866–69,1866,1869,Albumen silver print from glass negative,8.4 x 5.7 cm. (3 5/16 x 2 1/4 in.),"Bequest of James David Nelson, in memory of Samuel J. Wagstaff Jr., 1990",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/266260,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1997.382.36,false,true,282041,Photographs,Photograph,"Alfred, Lord Tennyson",,,,,,Artist,,Julia Margaret Cameron,"British (born India), Calcutta 1815–1879 Kalutara, Ceylon",,"Cameron, Julia Margaret","British, born India",1815,1815,"July 4, 1866",1866,1866,Albumen silver print from glass negative,35 x 27 cm (13 3/4 x 10 5/8 in. ) irregular,"The Rubel Collection, Purchase, Lila Acheson Wallace, Michael and Jane Wilson, and Harry Kahn Gifts, 1997",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/282041,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1997.382.37,false,true,282042,Photographs,Photograph,Cassiopeia,,,,,,Artist,,Julia Margaret Cameron,"British (born India), Calcutta 1815–1879 Kalutara, Ceylon",,"Cameron, Julia Margaret","British, born India",1815,1815,1866,1866,1866,Albumen silver print from glass negative,34.9 x 27.4 cm (13 3/4 x 10 13/16 in. ),"The Rubel Collection, Purchase, Lila Acheson Wallace, Harry Kahn, and Ann Tenenbaum and Thomas H. Lee Gifts, 1997",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/282042,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1997.382.38,true,true,282043,Photographs,Photograph,"Zoe, Maid of Athens",,,,,,Artist,,Julia Margaret Cameron,"British (born India), Calcutta 1815–1879 Kalutara, Ceylon",,"Cameron, Julia Margaret","British, born India",1815,1815,1866,1866,1866,Albumen silver print from glass negative,30.1 x 24.5 cm (11 7/8 x 9 5/8 in.),"The Rubel Collection, Purchase, Lila Acheson Wallace, Ann Tenenbaum and Thomas H. Lee, and Muriel Kallis Newman Gifts, 1997",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/282043,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1997.382.39,false,true,282044,Photographs,Photograph,Sappho,,,,,,Artist,,Julia Margaret Cameron,"British (born India), Calcutta 1815–1879 Kalutara, Ceylon",,"Cameron, Julia Margaret","British, born India",1815,1815,1865,1865,1865,Albumen silver print from glass negative,35 x 27.3 cm (13 3/4 x 10 3/4 in. ),"The Rubel Collection, Purchase, Jennifer and Joseph Duke and Anonymous Gifts, 1997",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/282044,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.25,false,true,283097,Photographs,Photograph,Sir John Herschel,,,,,,Artist,,Julia Margaret Cameron,"British (born India), Calcutta 1815–1879 Kalutara, Ceylon",,"Cameron, Julia Margaret","British, born India",1815,1815,April 1867,1867,1867,Albumen silver print from glass negative,"Image: 31.8 x 24.9 cm (12 1/2 x 9 13/16 in.) Mount: 39.9 x 32.9 cm (15 11/16 x 12 15/16 in.), corners clipped","Gilman Collection, Purchase, Robert Rosenkranz Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/283097,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.26,false,true,283098,Photographs,Photograph,Mrs. Herbert Duckworth,,,,,,Artist,,Julia Margaret Cameron,"British (born India), Calcutta 1815–1879 Kalutara, Ceylon",,"Cameron, Julia Margaret","British, born India",1815,1815,1867,1867,1867,Albumen silver print from glass negative,32.8 x 23.7 cm (12 15/16 x 9 5/16 in.),"Gilman Collection, Purchase, Alfred Stieglitz Society Gifts, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/283098,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.27,true,true,283099,Photographs,Photograph,Philip Stanhope Worsley,,,,,,Artist,,Julia Margaret Cameron,"British (born India), Calcutta 1815–1879 Kalutara, Ceylon",,"Cameron, Julia Margaret","British, born India",1815,1815,1866,1866,1866,Albumen silver print from glass negative,"Image: 30.4 x 25 cm (11 15/16 x 9 13/16 in.) Mount: 40.9 x 30.6 cm (16 1/8 x 12 1/16 in.), irregular","Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/283099,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -52.524.3.10,false,true,269583,Photographs,Photograph,The Parting of Lancelot and Guinevere,,,,,,Artist,,Julia Margaret Cameron,"British (born India), Calcutta 1815–1879 Kalutara, Ceylon",,"Cameron, Julia Margaret","British, born India",1815,1815,1874,1874,1874,Albumen silver print from glass negative,Image: 33.2 x 28.8 cm (13 1/16 x 11 5/16 in.) Mount: 44 x 33.3cm (17 5/16 x 13 1/8in.),"David Hunter McAlpin Fund, 1952",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269583,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -52.524.3.11,false,true,282149,Photographs,Photograph,King Arthur,,,,,,Artist,,Julia Margaret Cameron,"British (born India), Calcutta 1815–1879 Kalutara, Ceylon",,"Cameron, Julia Margaret","British, born India",1815,1815,September 1874,1874,1874,Albumen silver print from glass negative,35.9 x 28 cm (14 1/8 x 11 in. ),"David Hunter McAlpin Fund, 1952",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/282149,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -52.524.3.12,false,true,282150,Photographs,Photograph,The Passing of King Arthur,,,,,,Artist,,Julia Margaret Cameron,"British (born India), Calcutta 1815–1879 Kalutara, Ceylon",,"Cameron, Julia Margaret","British, born India",1815,1815,1874,1874,1874,Albumen silver print from glass negative,35 x 27.3 cm (13 3/4 x 10 3/4 in. ),"David Hunter McAlpin Fund, 1952",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/282150,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1981.1229.18,false,true,263166,Photographs,Photograph,Sir John Herschel,,,,,,Artist,,Julia Margaret Cameron,"British (born India), Calcutta 1815–1879 Kalutara, Ceylon",,"Cameron, Julia Margaret","British, born India",1815,1815,1875,1875,1875,Carbon print,,"Robert O. Dougan Collection, Gift of Warner Communications Inc., 1981",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/263166,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.239,false,true,286351,Photographs,Photograph,"Déjatch Alámayou, King Theodore's Son",,,,,,Artist,,Julia Margaret Cameron,"British (born India), Calcutta 1815–1879 Kalutara, Ceylon",,"Cameron, Julia Margaret","British, born India",1815,1815,July 1868,1868,1868,Albumen silver print from glass negative,Image: 29.2 x 23.3 cm (11 1/2 x 9 3/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286351,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.265,false,true,286404,Photographs,Photograph,[Kate Keown],,,,,,Artist,,Julia Margaret Cameron,"British (born India), Calcutta 1815–1879 Kalutara, Ceylon",,"Cameron, Julia Margaret","British, born India",1815,1815,1866,1866,1866,Albumen silver print from glass negative,Image: 29 x 29 cm (11 7/16 x 11 7/16 in.) circle,"Gilman Collection, Purchase, Jennifer and Joseph Duke Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286404,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2004.275,false,true,285429,Photographs,Photograph,[Climbing the Mast],,,,,,Artist,,László Moholy-Nagy,"American (born Hungary), Borsod 1895–1946 Chicago, Illinois",,"Moholy-Nagy, László","American, born Hungary",1895,1946,1928,1928,1928,Gelatin silver print,Image: 35.4 x 28 cm (13 15/16 x 11 in.),"Purchase, several members of The Chairman's Council Gifts, 2004",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285429,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1981.1163,false,true,263068,Photographs,Photograph,[Lucia Moholy; Negative Print],,,,,,Artist,,László Moholy-Nagy,"American (born Hungary), Borsod 1895–1946 Chicago, Illinois",,"Moholy-Nagy, László","American, born Hungary",1895,1946,1924–28,1924,1928,Gelatin silver print,23.6 x 17.5 cm (9 5/16 x 6 7/8 in.),"Warner Communications Inc. Purchase Fund, 1981",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/263068,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.1183,false,true,264891,Photographs,Photograph,Fischernetze auf Isola Bella,,,,,,Artist,,László Moholy-Nagy,"American (born Hungary), Borsod 1895–1946 Chicago, Illinois",,"Moholy-Nagy, László","American, born Hungary",1895,1946,ca. 1930,1928,1932,Gelatin silver print,17.5 x 23.5 cm. (6 7/8 x 9 1/4 in.),"Gift of Sheri and Paul Siegel, 1986",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/264891,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1985.1150.4,false,true,264515,Photographs,Photograph,"[Cat, Seen From Above]",,,,,,Artist,,László Moholy-Nagy,"American (born Hungary), Borsod 1895–1946 Chicago, Illinois",,"Moholy-Nagy, László","American, born Hungary",1895,1946,ca. 1926,1924,1928,Gelatin silver print,23.4 x 17.5 cm. (9 3/16 x 6 7/8 in.),"Gift of Emanuel Gerard, 1985",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/264515,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1987.1100.23,false,true,265277,Photographs,Photograph,Behind the Back of the Gods,,,,,,Artist,,László Moholy-Nagy,"American (born Hungary), Borsod 1895–1946 Chicago, Illinois",,"Moholy-Nagy, László","American, born Hungary",1895,1946,1928,1928,1928,Gelatin silver print,36.0 x 27.4 cm (14 3/16 x 10 13/16 in.),"Ford Motor Company Collection, Gift of Ford Motor Company and John C. Waddell, 1987",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/265277,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.152,false,true,283290,Photographs,Photograph,"Decorating Work, Switzerland",,,,,,Artist,,László Moholy-Nagy,"American (born Hungary), Borsod 1895–1946 Chicago, Illinois",,"Moholy-Nagy, László","American, born Hungary",1895,1946,1925,1925,1925,Gelatin silver print,Image: 50.6 x 40.2 cm (19 15/16 x 15 13/16 in. ),"Gilman Collection, Purchase, Robert Rosenkranz Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/283290,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.298,false,true,285944,Photographs,Photograph,"Pont Transbordeur, Marseille",,,,,,Artist,,László Moholy-Nagy,"American (born Hungary), Borsod 1895–1946 Chicago, Illinois",,"Moholy-Nagy, László","American, born Hungary",1895,1946,1929,1929,1929,Gelatin silver print,Image: 23.7 x 17.9 cm (9 5/16 x 7 1/16 in.) Mount: 25.3 x 19.2 cm (9 15/16 x 7 9/16 in.),"Gilman Collection, Purchase, Alfred Stieglitz Society Gifts, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285944,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.437,false,true,283291,Photographs,Photograph,Dolls on the Balcony,,,,,,Artist,,László Moholy-Nagy,"American (born Hungary), Borsod 1895–1946 Chicago, Illinois",,"Moholy-Nagy, László","American, born Hungary",1895,1946,1926,1926,1926,Gelatin silver print,23.5 x 17.5 cm (9 1/4 x 6 7/8 in.),"Gilman Collection, Purchase, Denise and Andrew Saul Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/283291,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.444,false,true,285731,Photographs,Photograph,"From the Radio Tower, Berlin",,,,,,Artist,,László Moholy-Nagy,"American (born Hungary), Borsod 1895–1946 Chicago, Illinois",,"Moholy-Nagy, László","American, born Hungary",1895,1946,"1928, printed ca. 1940",1928,1940,Gelatin silver print,Image: 24.6 x 19.1 cm (9 11/16 x 7 1/2 in.) Mount: 38.5 x 26.8 cm (15 3/16 x 10 9/16 in.),"Gilman Collection, Purchase, Gift of Ford Motor Company and John C. Waddell, by exchange, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285731,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1987.1100.155,false,true,265194,Photographs,Photograph,7 A.M. (New Year's Morning),,,,,,Artist,,László Moholy-Nagy,"American (born Hungary), Borsod 1895–1946 Chicago, Illinois",,"Moholy-Nagy, László","American, born Hungary",1895,1946,ca. 1930,1928,1932,Gelatin silver print,27.8 x 21.3 cm (10 15/16 x 8 3/8 in.),"Ford Motor Company Collection, Gift of Ford Motor Company and John C. Waddell, 1987",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/265194,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1987.1100.158,false,true,265197,Photographs,Photogram,Fotogramm,,,,,,Artist,,László Moholy-Nagy,"American (born Hungary), Borsod 1895–1946 Chicago, Illinois",,"Moholy-Nagy, László","American, born Hungary",1895,1946,1926,1926,1926,Gelatin silver print,23.9 x 17.9 cm (9 7/16 x 7 1/16 in.),"Ford Motor Company Collection, Gift of Ford Motor Company and John C. Waddell, 1987",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/265197,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1987.1100.159,false,true,265198,Photographs,Photogram,Fotogramm,,,,,,Artist,,László Moholy-Nagy,"American (born Hungary), Borsod 1895–1946 Chicago, Illinois",,"Moholy-Nagy, László","American, born Hungary",1895,1946,1925–1928,1925,1928,Gelatin silver print,23.9 x 17.9 cm. (9 7/16 x 7 1/16 in.),"Ford Motor Company Collection, Gift of Ford Motor Company and John C. Waddell, 1987",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/265198,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1987.1100.160,false,true,265200,Photographs,Photogram,Fotogramm,,,,,,Artist,,László Moholy-Nagy,"American (born Hungary), Borsod 1895–1946 Chicago, Illinois",,"Moholy-Nagy, László","American, born Hungary",1895,1946,1925,1925,1925,Gelatin silver print,23.8 x 17.8 cm (9 3/8 x 7 in.),"Ford Motor Company Collection, Gift of Ford Motor Company and John C. Waddell, 1987",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/265200,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1987.1100.228,false,true,265275,Photographs,Photogram,Fotogramm,,,,,,Artist,,László Moholy-Nagy,"American (born Hungary), Borsod 1895–1946 Chicago, Illinois",,"Moholy-Nagy, László","American, born Hungary",1895,1946,1922,1922,1922,Gelatin silver print,17.8 x 23.7 cm (7 x 9 5/16 in.),"Ford Motor Company Collection, Gift of Ford Motor Company and John C. Waddell, 1987",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/265275,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1987.1100.230,false,true,265278,Photographs,Photograph,Target Practice (In the Name of the Law),,,,,,Artist,,László Moholy-Nagy,"American (born Hungary), Borsod 1895–1946 Chicago, Illinois",,"Moholy-Nagy, László","American, born Hungary",1895,1946,ca. 1927,1925,1929,Gelatin silver print,24.0 x 18.2 cm (9 7/16 x 7 3/16 in.),"Ford Motor Company Collection, Gift of Ford Motor Company and John C. Waddell, 1987",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/265278,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1987.1100.231,false,true,265279,Photographs,Photograph,Lucia,,,,,,Artist,,László Moholy-Nagy,"American (born Hungary), Borsod 1895–1946 Chicago, Illinois",,"Moholy-Nagy, László","American, born Hungary",1895,1946,1924–28,1924,1928,Gelatin silver print,8.2 x 5.4 cm (3 1/4 x 2 1/8 in.),"Ford Motor Company Collection, Gift of Ford Motor Company and John C. Waddell, 1987",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/265279,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1987.1100.463,false,true,265536,Photographs,Photograph,Lucia Moholy,,,,,,Artist,,László Moholy-Nagy,"American (born Hungary), Borsod 1895–1946 Chicago, Illinois",,"Moholy-Nagy, László","American, born Hungary",1895,1946,1920s,1920,1929,Gelatin silver print,10.0 x 7.2 cm. (3 15/16 x 2 13/16 in.),"Ford Motor Company Collection, Gift of Ford Motor Company and John C. Waddell, 1987",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/265536,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1987.1100.499,false,true,283698,Photographs,Photograph,Scandinavia,,,,,,Artist,,László Moholy-Nagy,"American (born Hungary), Borsod 1895–1946 Chicago, Illinois",,"Moholy-Nagy, László","American, born Hungary",1895,1946,1930,1930,1930,Gelatin silver print,23.5 x 17.1 cm (9 1/4 x 6 3/4 in.),"Ford Motor Company Collection, Gift of Ford Motor Company and John C. Waddell, 1987",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/283698,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.5,false,true,283077,Photographs,Photograph,[Trees],,,,,,Artist,,Thomas Keith,"British, Kincardine, Aberdeenshire, Scotland 1827–1895 London",,"Keith, Thomas","British, Scottish",1827,1895,1854–57,1854,1857,Salted paper print from paper negative,Image: 30.3 x 22.1 cm (11 15/16 x 8 11/16 in.) Mount: 54.8 x 38.3 cm (21 9/16 x 15 1/16 in.),"Gilman Collection, Purchase, Harriette and Noel Levine Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/283077,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.918,false,true,285847,Photographs,Photograph,"Tropical Scenery, Street, Chipigana",,,,,,Artist,,John Moran,"American (born England), Bolton, Lancashire 1821–1903 Pennsylvania",,"Moran, John","American, born England",1821,1903,1871,1871,1871,Albumen silver print from glass negative,Image: 20.3 x 27.9 cm (8 x 11 in.) Mount: 40.6 x 50.8 cm (16 x 20 in.),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285847,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.919,false,true,286179,Photographs,Photograph,"Tropical Scenery, Native Hut, Turbo",,,,,,Artist,,John Moran,"American (born England), Bolton, Lancashire 1821–1903 Pennsylvania",,"Moran, John","American, born England",1821,1903,1871,1871,1871,Albumen silver print from glass negative,Image: 20.3 x 27.9 cm (8 x 11 in.) Mount: 40.6 x 50.8 cm (16 x 20 in.),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286179,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.920,false,true,286180,Photographs,Photograph,"Tropical Scenery, Tropical Forest",,,,,,Artist,,John Moran,"American (born England), Bolton, Lancashire 1821–1903 Pennsylvania",,"Moran, John","American, born England",1821,1903,1871,1871,1871,Albumen silver print from glass negative,Image: 27.9 x 20.3 cm (11 x 8 in.) Mount: 50.8 x 40.6 cm (20 x 16 in.),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286180,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.921,false,true,286181,Photographs,Photograph,"Tropical Scenery, View Near Chipigana",,,,,,Artist,,John Moran,"American (born England), Bolton, Lancashire 1821–1903 Pennsylvania",,"Moran, John","American, born England",1821,1903,1871,1871,1871,Albumen silver print from glass negative,Image: 20.3 x 27.9 cm (8 x 11 in.) Mount: 40.6 x 50.8 cm (16 x 20 in.),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286181,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.922,false,true,286182,Photographs,Photograph,"Tropical Scenery, Turbo Village",,,,,,Artist,,John Moran,"American (born England), Bolton, Lancashire 1821–1903 Pennsylvania",,"Moran, John","American, born England",1821,1903,1871,1871,1871,Albumen silver print from glass negative,Image: 20.3 x 27.9 cm (8 x 11 in.) Mount: 40.6 x 50.8 cm (16 x 20 in.),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286182,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.923,false,true,286183,Photographs,Photograph,"Tropical Scenery, The Brook El Bano, Chipigana",,,,,,Artist,,John Moran,"American (born England), Bolton, Lancashire 1821–1903 Pennsylvania",,"Moran, John","American, born England",1821,1903,1871,1871,1871,Albumen silver print from glass negative,Image: 27.9 x 20.3 cm (11 x 8 in.) Mount: 50.8 x 40.6 cm (20 x 16 in.),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286183,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.926,false,true,286185,Photographs,Photograph,"Tropical Scenery, Landing, Chipigana",,,,,,Artist,,John Moran,"American (born England), Bolton, Lancashire 1821–1903 Pennsylvania",,"Moran, John","American, born England",1821,1903,1871,1871,1871,Albumen silver print from glass negative,Image: 20 x 27.6 cm (7 7/8 x 10 7/8 in.) Mount: 30.5 x 38.1 cm (12 x 15 in.),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286185,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.927,false,true,286186,Photographs,Photograph,"Tropical Scenery, Natural Arch, Cupica Bay",,,,,,Artist,,John Moran,"American (born England), Bolton, Lancashire 1821–1903 Pennsylvania",,"Moran, John","American, born England",1821,1903,1871,1871,1871,Albumen silver print from glass negative,Image: 27.6 x 20.2 cm (10 7/8 x 7 15/16 in.) Mount: 38.1 x 30.5 cm (15 x 12 in.),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286186,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.928,false,true,286187,Photographs,Photograph,"Tropical Scenery, Cascade, Limon River",,,,,,Artist,,John Moran,"American (born England), Bolton, Lancashire 1821–1903 Pennsylvania",,"Moran, John","American, born England",1821,1903,1871,1871,1871,Albumen silver print from glass negative,Image: 27.3 x 20 cm (10 3/4 x 7 7/8 in.) Mount: 38.1 x 30.5 cm (15 x 12 in.),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286187,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.929,false,true,286188,Photographs,Photograph,"Tropical Scenery, Native Hut, Turbo",,,,,,Artist,,John Moran,"American (born England), Bolton, Lancashire 1821–1903 Pennsylvania",,"Moran, John","American, born England",1821,1903,1871,1871,1871,Albumen silver print from glass negative,Image: 20 x 27.3 cm (7 7/8 x 10 3/4 in.) Mount: 30.5 x 38.1 cm (12 x 15 in.),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286188,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.930,false,true,286189,Photographs,Photograph,"Tropical Scenery, Tropical Forest",,,,,,Artist,,John Moran,"American (born England), Bolton, Lancashire 1821–1903 Pennsylvania",,"Moran, John","American, born England",1821,1903,1871,1871,1871,Albumen silver print from glass negative,Image: 27.6 x 20 cm (10 7/8 x 7 7/8 in.) Mount: 38.1 x 30.5 cm (15 x 12 in.),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286189,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.931,false,true,286190,Photographs,Photograph,"Tropical Scenery, Cathedral, Cartagena",,,,,,Artist,,John Moran,"American (born England), Bolton, Lancashire 1821–1903 Pennsylvania",,"Moran, John","American, born England",1821,1903,1871,1871,1871,Albumen silver print from glass negative,Image: 20 x 27.6 cm (7 7/8 x 10 7/8 in.) Mount: 30.5 x 38.1 cm (12 x 15 in.),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286190,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.932,false,true,286191,Photographs,Photograph,"Tropical Scenery, Darien Harbor, Chipigana",,,,,,Artist,,John Moran,"American (born England), Bolton, Lancashire 1821–1903 Pennsylvania",,"Moran, John","American, born England",1821,1903,1871,1871,1871,Albumen silver print from glass negative,Image: 20 x 27.3 cm (7 7/8 x 10 3/4 in.) Mount: 30.5 x 38.1 cm (12 x 15 in.),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286191,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.933,false,true,285991,Photographs,Photograph,"Tropical Scenery, Darien Harbor - Looking South",,,,,,Artist,,John Moran,"American (born England), Bolton, Lancashire 1821–1903 Pennsylvania",,"Moran, John","American, born England",1821,1903,1871,1871,1871,Albumen silver print from glass negative,Image: 20 x 27.6 cm (7 7/8 x 10 7/8 in.) Mount: 30.5 x 38.3 cm (12 x 15 1/16 in.),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285991,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.934,false,true,285992,Photographs,Photograph,"Tropical Scenery, The Terminus of the Proposed Canal, Limon Bay",,,,,,Artist,,John Moran,"American (born England), Bolton, Lancashire 1821–1903 Pennsylvania",,"Moran, John","American, born England",1821,1903,1871,1871,1871,Albumen silver print from glass negative,Image: 20 x 27.3 cm (7 7/8 x 10 3/4 in.) Mount: 30.5 x 38.2 cm (12 x 15 1/16 in.),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285992,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.935,false,true,285993,Photographs,Photograph,"Tropical Scenery, View of Limon Bay",,,,,,Artist,,John Moran,"American (born England), Bolton, Lancashire 1821–1903 Pennsylvania",,"Moran, John","American, born England",1821,1903,1871,1871,1871,Albumen silver print,Image: 20 x 27.3 cm (7 7/8 x 10 3/4 in.) Mount: 30.5 x 38.2 cm (12 x 15 1/16 in.),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285993,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.936,false,true,286040,Photographs,Photograph,"Tropical Scenery, Santa Maria del Real, Darien",,,,,,Artist,,John Moran,"American (born England), Bolton, Lancashire 1821–1903 Pennsylvania",,"Moran, John","American, born England",1821,1903,1871,1871,1871,Albumen silver print from glass negative,Image: 20 x 27.3 cm (7 7/8 x 10 3/4 in.) Mount: 30.5 x 38.2 cm (12 x 15 1/16 in.),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286040,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.937,false,true,286041,Photographs,Photograph,"Tropical Scenery, Cliff - Limon Bay",,,,,,Artist,,John Moran,"American (born England), Bolton, Lancashire 1821–1903 Pennsylvania",,"Moran, John","American, born England",1821,1903,1871,1871,1871,Albumen silver print from glass negative,Image: 20 x 27.3 cm (7 7/8 x 10 3/4 in.) Mount: 30.5 x 38.2 cm (12 x 15 1/16 in.),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286041,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.938,false,true,286042,Photographs,Photograph,"Tropical Scenery, Limon Bay - Low Tide",,,,,,Artist,,John Moran,"American (born England), Bolton, Lancashire 1821–1903 Pennsylvania",,"Moran, John","American, born England",1821,1903,1871,1871,1871,Albumen silver print from glass negative,Image: 20 x 27.6 cm (7 7/8 x 10 7/8 in.) Mount: 30.6 x 38.2 cm (12 1/16 x 15 1/16 in.),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286042,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.939,false,true,286560,Photographs,Photograph,"Tropical Scenery, Forest Near Turbo",,,,,,Artist,,John Moran,"American (born England), Bolton, Lancashire 1821–1903 Pennsylvania",,"Moran, John","American, born England",1821,1903,1871,1871,1871,Albumen silver print from glass negative,Image: 27.3 x 20 cm (10 3/4 x 7 7/8 in.) Mount: 38.2 x 30.6 cm (15 1/16 x 12 1/16 in.),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286560,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -53.680.6,false,true,269669,Photographs,Photograph,A Holiday Visit,,,,,,Artist,,Arnold Genthe,"American (born Germany), Berlin 1869–1942 New Milford, Connecticut",,"Genthe, Arnold","American, born Germany",1869,1942,1895–1908,1895,1908,Gelatin silver print,Image: 34 x 22.8 cm (13 3/8 x 9 in.),"Gift of Mrs. Eustace Seligman, 1953",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269669,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.43.223,false,true,267631,Photographs,Photograph,"After the Earthquake, San Francisco",,,,,,Artist,,Arnold Genthe,"American (born Germany), Berlin 1869–1942 New Milford, Connecticut",,"Genthe, Arnold","American, born Germany",1869,1942,1906,1906,1906,Gelatin silver print,13.3 x 23.5 cm (5 1/4 x 9 1/4 in.),"Alfred Stieglitz Collection, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267631,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.510.5,false,true,261240,Photographs,Photograph,"John D. Rockefeller, Jr.",,,,,,Artist,,Arnold Genthe,"American (born Germany), Berlin 1869–1942 New Milford, Connecticut",,"Genthe, Arnold","American, born Germany",1869,1942,ca. 1925,1923,1927,Gelatin silver print,Image: 19.2 x 23.8 cm (7 9/16 x 9 3/8 in.) Mount: 20.2 x 25 cm (7 15/16 x 9 13/16 in.),"The Elisha Whittelsey Collection, The Elisha Whittelsey Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261240,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.16,false,true,283088,Photographs,Photograph,[Stag in Cart],,,,,,Artist,,Horatio Ross,"British, Rossie Castle, near Montrose, Scotland 1801–1886 Scotland",,"Ross, Horatio","British, Scottish",1801,1886,ca. 1858,1856,1860,Albumen silver print from glass negative,Image: 26.7 x 32.6 cm (10 1/2 x 12 13/16 in.) Mount: 35.7 x 43.6 cm (14 1/16 x 17 3/16 in.),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/283088,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.17,false,true,283089,Photographs,Photograph,[Tree],,,,,,Artist,,Horatio Ross,"British, Rossie Castle, near Montrose, Scotland 1801–1886 Scotland",,"Ross, Horatio","British, Scottish",1801,1886,ca. 1858,1856,1860,Albumen silver print from glass negative,Mount: 14 1/16 in. × 17 3/4 in. (35.7 × 45.1 cm) Image: 9 3/4 × 12 1/16 in. (24.7 × 30.7 cm),"Gilman Collection, Purchase, Harriette and Noel Levine Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/283089,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.552 (1),false,true,288053,Photographs,Photograph,"[Miss Macrae of Inverinate, Wife of Horatio Ross]",,,,,,Artist,,Horatio Ross,"British, Rossie Castle, near Montrose, Scotland 1801–1886 Scotland",,"Ross, Horatio","British, Scottish",1801,1886,ca. 1858,1858,1858,Salted paper print,"19.8 x 15 cm (7 13/16 x 5 7/8 in.), oval","Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288053,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.552 (2),false,true,288054,Photographs,Photograph,"[Peel Ross, Son of Horatio Ross]",,,,,,Artist,,Horatio Ross,"British, Rossie Castle, near Montrose, Scotland 1801–1886 Scotland",,"Ross, Horatio","British, Scottish",1801,1886,ca. 1858,1858,1858,Salted paper print,"19.9 x 15.1 cm (7 13/16 x 5 15/16 in.), oval","Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288054,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.552 (3),false,true,288055,Photographs,Photograph,"[Edward Ross, Youngest Son of Horatio Ross]",,,,,,Artist,,Horatio Ross,"British, Rossie Castle, near Montrose, Scotland 1801–1886 Scotland",,"Ross, Horatio","British, Scottish",1801,1886,ca. 1858,1858,1858,Salted paper print,20.7 x 15.1 cm (8 1/8 x 5 15/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288055,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.552 (4),false,true,288056,Photographs,Photograph,[Ned and Colin Ross with Hunt Trophy],,,,,,Artist,,Horatio Ross,"British, Rossie Castle, near Montrose, Scotland 1801–1886 Scotland",,"Ross, Horatio","British, Scottish",1801,1886,ca. 1857,1857,1857,Salted paper print,14.9 x 19.8 cm (5 7/8 x 7 13/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288056,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.552 (5),false,true,288057,Photographs,Photograph,[Mrs. Kennedy],,,,,,Artist,,Horatio Ross,"British, Rossie Castle, near Montrose, Scotland 1801–1886 Scotland",,"Ross, Horatio","British, Scottish",1801,1886,ca. 1858,1858,1858,Salted paper print,18.4 x 14.9 cm (7 1/4 x 5 7/8 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288057,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.552 (6),false,true,288058,Photographs,Photograph,"[Macrae, Ross, and Warner Families Outdoors]",,,,,,Artist,,Horatio Ross,"British, Rossie Castle, near Montrose, Scotland 1801–1886 Scotland",,"Ross, Horatio","British, Scottish",1801,1886,ca. 1858,1858,1858,Salted paper print,15.3 x 18.8 cm (6 x 7 3/8 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288058,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.552 (7),false,true,288059,Photographs,Photograph,[Picnic Near a Stream],,,,,,Artist,,Horatio Ross,"British, Rossie Castle, near Montrose, Scotland 1801–1886 Scotland",,"Ross, Horatio","British, Scottish",1801,1886,ca. 1856,1856,1856,Salted paper print,12.9 x 18.1 cm (5 1/16 x 7 1/8 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288059,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.552 (8),false,true,288060,Photographs,Photograph,"[Stag Trophy Head, Killed by Ned Ross]",,,,,,Artist,,Horatio Ross,"British, Rossie Castle, near Montrose, Scotland 1801–1886 Scotland",,"Ross, Horatio","British, Scottish",1801,1886,ca. 1858,1858,1858,Albumen silver print,"17.9 x 14.9 cm (7 1/16 x 5 7/8 in.), corners trimmed diagonally","Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288060,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.552 (9),false,true,288061,Photographs,Photograph,"[Two Stags, One Shot by Mr. Ross and the Other by Mrs. Ross]",,,,,,Artist,,Horatio Ross,"British, Rossie Castle, near Montrose, Scotland 1801–1886 Scotland",,"Ross, Horatio","British, Scottish",1801,1886,ca. 1858,1858,1858,Salted paper print,"14.3 x 17.8 cm (5 5/8 x 7 in.), oval","Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288061,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.552 (10),false,true,288062,Photographs,Photograph,[Dead Roe Buck],,,,,,Artist,,Horatio Ross,"British, Rossie Castle, near Montrose, Scotland 1801–1886 Scotland",,"Ross, Horatio","British, Scottish",1801,1886,ca. 1856–59,1856,1859,Albumen silver print,14.6 x 18.5 cm (5 3/4 x 7 5/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288062,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.552 (11),false,true,288063,Photographs,Photograph,[Two Stags and Roe Buck],,,,,,Artist,,Horatio Ross,"British, Rossie Castle, near Montrose, Scotland 1801–1886 Scotland",,"Ross, Horatio","British, Scottish",1801,1886,ca. 1858,1858,1858,Salted paper print,15.9 x 20.6 cm (6 1/4 x 8 1/8 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288063,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.552 (12),false,true,288064,Photographs,Photograph,Jack Gralloching a Stag,,,,,,Artist,,Horatio Ross,"British, Rossie Castle, near Montrose, Scotland 1801–1886 Scotland",,"Ross, Horatio","British, Scottish",1801,1886,ca. 1856–58,1856,1858,Salted paper print,17 x 19.3 cm (6 11/16 x 7 5/8 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288064,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.552 (13),false,true,288065,Photographs,Photograph,[Dead Stag],,,,,,Artist,,Horatio Ross,"British, Rossie Castle, near Montrose, Scotland 1801–1886 Scotland",,"Ross, Horatio","British, Scottish",1801,1886,ca. 1856,1856,1856,Salted paper print,19.2 x 24.2 cm (7 9/16 x 9 1/2 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288065,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.552 (14),false,true,286900,Photographs,Photograph,[Dead Stag],,,,,,Artist,,Horatio Ross,"British, Rossie Castle, near Montrose, Scotland 1801–1886 Scotland",,"Ross, Horatio","British, Scottish",1801,1886,ca. 1857,1857,1857,Salted paper print,19.1 x 23.4 cm (7 1/2 x 9 3/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286900,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.552 (15),false,true,288066,Photographs,Photograph,[Dead Female Deer and Game Bird],,,,,,Artist,,Horatio Ross,"British, Rossie Castle, near Montrose, Scotland 1801–1886 Scotland",,"Ross, Horatio","British, Scottish",1801,1886,ca. 1856–59,1856,1859,Salted paper print,"17.8 x 23.8 cm (7 x 9 3/8 in.), oval","Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288066,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.552 (16),false,true,288067,Photographs,Photograph,[Stag Trophy Head Killed by Ned Ross],,,,,,Artist,,Horatio Ross,"British, Rossie Castle, near Montrose, Scotland 1801–1886 Scotland",,"Ross, Horatio","British, Scottish",1801,1886,ca. 1857,1857,1857,Albumen silver print,19.6 x 15.5 cm (7 11/16 x 6 1/8 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288067,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.552 (17),false,true,288068,Photographs,Photograph,[Stags Heads - Dibedale],,,,,,Artist,,Horatio Ross,"British, Rossie Castle, near Montrose, Scotland 1801–1886 Scotland",,"Ross, Horatio","British, Scottish",1801,1886,ca. 1856,1856,1856,Salted paper print,16 x 20.4 cm (6 5/16 x 8 1/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288068,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.552 (18),false,true,288069,Photographs,Photograph,[Portrait of a Seated Gentleman],,,,,,Artist,,Horatio Ross,"British, Rossie Castle, near Montrose, Scotland 1801–1886 Scotland",,"Ross, Horatio","British, Scottish",1801,1886,ca. 1856–59,1856,1859,Albumen silver print,18.1 x 14.3 cm (7 1/8 x 5 5/8 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288069,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.552 (19),false,true,288070,Photographs,Photograph,[Portrait of Major Anderson],,,,,,Artist,,Horatio Ross,"British, Rossie Castle, near Montrose, Scotland 1801–1886 Scotland",,"Ross, Horatio","British, Scottish",1801,1886,ca. 1856–59,1856,1859,Albumen silver print,18.9 x 15.6 cm (7 7/16 x 6 1/8 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288070,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.552 (20),false,true,288071,Photographs,Photograph,[Portrait of Man in Hunting Garb],,,,,,Artist,,Horatio Ross,"British, Rossie Castle, near Montrose, Scotland 1801–1886 Scotland",,"Ross, Horatio","British, Scottish",1801,1886,ca. 1856–59,1856,1859,Salted paper print,19.5 x 14.2 cm (7 11/16 x 5 9/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288071,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.552 (21),false,true,288072,Photographs,Photograph,[Old Tom],,,,,,Artist,,Horatio Ross,"British, Rossie Castle, near Montrose, Scotland 1801–1886 Scotland",,"Ross, Horatio","British, Scottish",1801,1886,ca. 1856–59,1856,1859,Salted paper print,20 x 15.1 cm (7 7/8 x 5 15/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288072,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.552 (22),false,true,288073,Photographs,Photograph,[Elegant Group Outdoors],,,,,,Artist,,Horatio Ross,"British, Rossie Castle, near Montrose, Scotland 1801–1886 Scotland",,"Ross, Horatio","British, Scottish",1801,1886,ca. 1856–59,1856,1859,Salted paper print,"14.8 x 19.7 cm (5 13/16 x 7 3/4 in.), oval","Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288073,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.552 (23),false,true,288074,Photographs,Photograph,[Peel Ross Fishing],,,,,,Artist,,Horatio Ross,"British, Rossie Castle, near Montrose, Scotland 1801–1886 Scotland",,"Ross, Horatio","British, Scottish",1801,1886,ca. 1856–59,1856,1859,Albumen silver print,"16.6 x 20.5 cm (6 9/16 x 8 1/16 in.), oval","Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288074,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.552 (24),false,true,288075,Photographs,Photograph,[Peel Ross with Hunting Trophies],,,,,,Artist,,Horatio Ross,"British, Rossie Castle, near Montrose, Scotland 1801–1886 Scotland",,"Ross, Horatio","British, Scottish",1801,1886,ca. 1856–59,1856,1859,Albumen silver print,18.7 x 22.2 cm (7 3/8 x 8 3/4 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288075,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.552 (25),false,true,288076,Photographs,Photograph,[Charlie and Peel Ross with Horse after a Hunt],,,,,,Artist,,Horatio Ross,"British, Rossie Castle, near Montrose, Scotland 1801–1886 Scotland",,"Ross, Horatio","British, Scottish",1801,1886,ca. 1856–59,1856,1859,Albumen silver print,"21.5 x 18.7 cm (8 7/16 x 7 3/8 in.), arched top","Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288076,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.552 (26),false,true,288077,Photographs,Photograph,[Colin and Horatio Ross Reading with Jessie Macrae],,,,,,Artist,,Horatio Ross,"British, Rossie Castle, near Montrose, Scotland 1801–1886 Scotland",,"Ross, Horatio","British, Scottish",1801,1886,ca. 1858,1858,1858,Albumen silver print,23.2 x 19.4 cm (9 1/8 x 7 5/8 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288077,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.552 (27),false,true,288078,Photographs,Photograph,[Man and Boys Fishing],,,,,,Artist,,Horatio Ross,"British, Rossie Castle, near Montrose, Scotland 1801–1886 Scotland",,"Ross, Horatio","British, Scottish",1801,1886,ca. 1856–59,1856,1859,Salted paper print,19.3 x 23.1 cm (7 5/8 x 9 1/8 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288078,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.552 (28),false,true,288079,Photographs,Photograph,[Stag Shot by Mrs. Ross],,,,,,Artist,,Horatio Ross,"British, Rossie Castle, near Montrose, Scotland 1801–1886 Scotland",,"Ross, Horatio","British, Scottish",1801,1886,ca. 1857,1857,1857,Salted paper print,13.5 x 23.5 cm (5 5/16 x 9 1/4 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288079,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.552 (29),false,true,288080,Photographs,Photograph,[View in the Gardens at Netherley],,,,,,Artist,,Horatio Ross,"British, Rossie Castle, near Montrose, Scotland 1801–1886 Scotland",,"Ross, Horatio","British, Scottish",1801,1886,ca. 1856–59,1856,1859,Albumen silver print,19.5 x 24.6 cm (7 11/16 x 9 11/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288080,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.552 (30),false,true,288081,Photographs,Photograph,[Colin's Royal Stag],,,,,,Artist,,Horatio Ross,"British, Rossie Castle, near Montrose, Scotland 1801–1886 Scotland",,"Ross, Horatio","British, Scottish",1801,1886,ca. 1856,1856,1856,Salted paper print,19.9 x 14.4 cm (7 13/16 x 5 11/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288081,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.552 (31),false,true,288082,Photographs,Photograph,[Spying in Glenfeshie],,,,,,Artist,,Horatio Ross,"British, Rossie Castle, near Montrose, Scotland 1801–1886 Scotland",,"Ross, Horatio","British, Scottish",1801,1886,ca. 1858,1858,1858,Salted paper print,18.7 x 23.4 cm (7 3/8 x 9 3/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288082,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.552 (32),false,true,288083,Photographs,Photograph,[Hunters Stalking a Deer],,,,,,Artist,,Horatio Ross,"British, Rossie Castle, near Montrose, Scotland 1801–1886 Scotland",,"Ross, Horatio","British, Scottish",1801,1886,ca. 1857,1857,1857,Salted paper print,13.6 x 18.9 cm (5 3/8 x 7 7/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288083,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.552 (33),false,true,288084,Photographs,Photograph,[Prize Cow and Calf],,,,,,Artist,,Horatio Ross,"British, Rossie Castle, near Montrose, Scotland 1801–1886 Scotland",,"Ross, Horatio","British, Scottish",1801,1886,ca. 1859,1859,1859,Salted paper print,15.6 x 19.7 cm (6 1/8 x 7 3/4 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288084,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.552 (34),false,true,288085,Photographs,Photograph,[Portrait of a Man in Military Regalia],,,,,,Artist,,Horatio Ross,"British, Rossie Castle, near Montrose, Scotland 1801–1886 Scotland",,"Ross, Horatio","British, Scottish",1801,1886,ca. 1859,1859,1859,Salted paper print,19.5 x 15.2 cm (7 11/16 x 6 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288085,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2011.424,false,true,298968,Photographs,Photographs,Aberdeen Portraits No. 1,,,,,,Artist,,George Washington Wilson,"British, Grampian (Baffshire), Scotland 1823–1893 Abedeen, Scotland",,"Wilson, George Washington","British, Scottish",1823,1893,1857,1857,1857,Albumen silver print from glass negative,Image: 21.3 x 17.2 cm (8 3/8 x 6 3/4 in.) Mount: 37.7 x 30 cm (14 13/16 x 11 13/16 in.) Frame: 43.2 x 35.6 cm (17 x 14 in.),"The Horace W. Goldsmith Foundation Fund, through Joyce and Robert Menschel, 2011",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/298968,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.1197,false,true,265002,Photographs,Photograph,"Excised Knee Joint. A Round Musket Ball in the Inner Condyle of the Right Femur [Gardiner Lewis, Company B, Nineteenth Indiana Volunteers]",,,,,,Artist,,William Bell,"American (born England) Liverpool 1831–1910 Philadelphia, Pennsylvania",,"Bell, William","American, born Britain",1831,1910,1866–67,1866,1867,Albumen silver print from glass negative,19 x 15.3cm (7 1/2 x 6in.) Mount: 35.4 x 27.6cm (13 15/16 x 10 7/8in.),"Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 1986",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/265002,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1988.1073,false,true,265934,Photographs,Photograph,"Grand Canyon, Colorado River, Near Paria Creek, Looking West",,,,,,Artist,,William Bell,"American (born England) Liverpool 1831–1910 Philadelphia, Pennsylvania",,"Bell, William","American, born Britain",1831,1910,1872,1872,1872,Albumen silver print from glass negative,27.3 x 20.1 cm. (10 3/4 x 7 15/16 in.),"Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 1988",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/265934,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1979.675.3,false,true,262679,Photographs,Photograph,"Limestone Walls, Kanab Wash, Colorado River",,,,,,Artist,,William Bell,"American (born England) Liverpool 1831–1910 Philadelphia, Pennsylvania",,"Bell, William","American, born Britain",1831,1910,1872,1872,1872,Albumen silver print from glass negative,,"Gift of Mr. and Mrs. Weston J. Naef, in memory of Edward Dawes Meanor, 1979",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/262679,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1979.675.4,false,true,262680,Photographs,Photograph,"Grand Cañon of the Colorado River, Mouth of Kanab Wash, looking West",,,,,,Artist,,William Bell,"American (born England) Liverpool 1831–1910 Philadelphia, Pennsylvania",,"Bell, William","American, born Britain",1831,1910,1872,1872,1872,Albumen silver print from glass negative,,"Gift of Mr. and Mrs. Weston J. Naef, in memory of Edward Dawes Meanor, 1979",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/262680,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.585 (10),false,true,287291,Photographs,Photograph,"Cañon of Kanab Wash, Looking South",,,,,,Artist,,William Bell,"American (born England) Liverpool 1831–1910 Philadelphia, Pennsylvania",,"Bell, William","American, born Britain",1831,1910,1872,1872,1872,Albumen silver print from glass negative,Image: 28.2 x 20.2 cm (11 1/8 x 7 15/16 in.),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287291,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1990.1106,false,true,266273,Photographs,Photograph,"Street in Fatehpur Sikri, India",,,,,,Artist,,John Murray,"British, Blackhouse, Aberdeenshire, Scotland 1809–1898 Sheringham, Norfolk county, England",,"Murray, John","British, Scottish",1809,1898,1858–62,1858,1862,Albumen silver print from paper negative,Image: 36.8 x 45 cm (14 1/2 x 17 11/16 in.) Mount: 53.4 x 73 cm (21 x 28 3/4 in.),"Purchase, The Horace W. Goldsmith Foundation Gift through Joyce and Robert Menschel and Cynthia Hazen Polsky Gift, 1990",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/266273,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1988.1134.2,false,true,266026,Photographs,Photograph,"The Diwan-i Khas from the Mussaman Burj, Agra Palace",,,,,,Artist,,John Murray,"British, Blackhouse, Aberdeenshire, Scotland 1809–1898 Sheringham, Norfolk county, England",,"Murray, John","British, Scottish",1809,1898,1862–64,1862,1864,Albumen silver print from paper negative,40.6 x 44.3cm (16 x 17 7/16in.) Frame: 68.7 x 122.6 cm (27 1/16 x 48 1/4 in.) (Framed with 1988.1134.1),"Purchase, Cynthia Hazen Polsky Gift, 1988",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/266026,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.70,false,true,283161,Photographs,Photograph,The Chowk,,,,,,Artist,,John Murray,"British, Blackhouse, Aberdeenshire, Scotland 1809–1898 Sheringham, Norfolk county, England",,"Murray, John","British, Scottish",1809,1898,1856–57,1856,1857,Salted paper print from paper negative,Image: 37.4 x 46.5 cm (14 3/4 x 18 5/16 in.) Mount: 45.4 x 53.9 cm (17 7/8 x 21 1/4 in.),"Gilman Collection, Purchase, Cynthia Hazen Polsky Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/283161,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.71,false,true,283162,Photographs,Photograph,[The Taj Mahal from the Banks of the Yamuna River],,,,,,Artist,,John Murray,"British, Blackhouse, Aberdeenshire, Scotland 1809–1898 Sheringham, Norfolk county, England",,"Murray, John","British, Scottish",1809,1898,1858–62,1858,1862,Albumen silver print from paper negative,Image: 39.9 x 44 cm (15 11/16 x 17 5/16 in.),"Gilman Collection, Purchase, Joseph M. Cohen Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/283162,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.945,false,true,286085,Photographs,Photograph,"Suttee Ghat, Cawnpore",,,,,,Artist,,John Murray,"British, Blackhouse, Aberdeenshire, Scotland 1809–1898 Sheringham, Norfolk county, England",,"Murray, John","British, Scottish",1809,1898,1858,1858,1858,Albumen silver print from paper negative,Image: 33 x 43.1 cm (13 x 16 15/16 in.) Mount: 40.3 x 51.6 cm (15 7/8 x 20 5/16 in.),"Gilman Collection, Purchase, Cynthia Hazen Polsky Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286085,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.1111,false,true,286082,Photographs,Photograph,"Fort Agra, The Delhi Gate",,,,,,Artist,,John Murray,"British, Blackhouse, Aberdeenshire, Scotland 1809–1898 Sheringham, Norfolk county, England",,"Murray, John","British, Scottish",1809,1898,1850s,1850,1859,Albumen silver print from paper negative,Image: 38.6 x 44.5 cm (15 3/16 x 17 1/2 in.),"Gilman Collection, Purchase, Cynthia Hazen Polsky Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286082,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.315a–c,false,true,286084,Photographs,Panorama,[The Taj Mahal from the Gateway],,,,,,Artist,,John Murray,"British, Blackhouse, Aberdeenshire, Scotland 1809–1898 Sheringham, Norfolk county, England",,"Murray, John","British, Scottish",1809,1898,January–March 1864,1864,1864,Albumen silver prints from waxed paper negatives,Image: 35 x 127 cm (13 3/4 x 50 in.) overall,"Gilman Collection, Purchase, Cynthia Hazen Polsky Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286084,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.37,false,true,669902,Photographs,Photograph,"Second Ave Rock Island, Ill. during high water",,,,,,Artist,,Henry P. Bosse,"American, born Germany, 1844–1893",,"Bosse, Henry P.","American, born Germany",1844,1893,1888,1888,1888,Cyanotype,"Image: 10 3/8 × 13 1/16 in. (26.4 × 33.2 cm), oval Sheet: 14 7/16 × 17 3/16 in. (36.7 × 43.7 cm)","Purchase, Acquisitions Fund and Mr. and Mrs. John A. Moran Gift, in memory of Louise Chisholm Moran, 2015",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/669902,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2014.715.16,false,true,669919,Photographs,Photograph,"No. 201. U.S. Government Bridge at Rock Island, Illinois (High Water)",,,,,,Artist,,Henry P. Bosse,"American, born Germany, 1844–1893",,"Bosse, Henry P.","American, born Germany",1844,1893,1888,1888,1888,Cyanotype,Sheet: 14 1/2 × 17 3/16 in. (36.8 × 43.7 cm),"Gift of Charles Wehrenberg and Sally Larsen, 2014",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/669919,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1991.1135.1,false,true,266429,Photographs,Photographically illustrated book,"Animal Locomotion. An Electro-Photographic Investigation of Consecutive Phases of Animal Movements. Commenced 1872 - Completed 1885. Volume I, Men (Nude)",,,,,,Artist,,Eadweard Muybridge,"American, born Britain, 1830–1904",,"Muybridge, Eadweard","American, born Britain",1830,1904,1880s,1880,1889,Photogravures,,"Rogers Fund, transferred from the Library",,,,,,,,,,,,Books,,http://www.metmuseum.org/art/collection/search/266429,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1991.1135.2,false,true,266432,Photographs,Photographically illustrated book,"Animal Locomotion. An Electro-Photographic Investigation of Consecutive Phases of Animal Movements. Commenced 1872 - Completed 1885. Volume II, Men (Nude)",,,,,,Artist,,Eadweard Muybridge,"American, born Britain, 1830–1904",,"Muybridge, Eadweard","American, born Britain",1830,1904,1880s,1880,1889,Photogravures,,"Rogers Fund, transferred from the Library",,,,,,,,,,,,Books,,http://www.metmuseum.org/art/collection/search/266432,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1991.1135.4,false,true,266434,Photographs,Photographically illustrated book,"Animal Locomotion. An Electro-Photographic Investigation of Consecutive Phases of Animal Movements. Commenced 1872 - Completed 1885. Volume IV, Women (Nude)",,,,,,Artist,,Eadweard Muybridge,"American, born Britain, 1830–1904",,"Muybridge, Eadweard","American, born Britain",1830,1904,1880s,1880,1889,Photogravures,,"Rogers Fund, transferred from the Library",,,,,,,,,,,,Books,,http://www.metmuseum.org/art/collection/search/266434,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1991.1135.5,false,true,266435,Photographs,Photographically illustrated book,"Animal Locomotion. An Electro-Photographic Investigation of Consecutive Phases of Animal Movements. Commenced 1872 - Completed 1885. Volume V, Man (Pelvis Cloth)",,,,,,Artist,,Eadweard Muybridge,"American, born Britain, 1830–1904",,"Muybridge, Eadweard","American, born Britain",1830,1904,1880s,1880,1889,Photogravures,,"Rogers Fund, transferred from the Library",,,,,,,,,,,,Books,,http://www.metmuseum.org/art/collection/search/266435,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1991.1135.6,false,true,266436,Photographs,Photographically illustrated book,"Animal Locomotion. An Electro-Photographic Investigation... of Animal Movements. Commenced 1872 - Completed 1885. Volume VI, Woman (Semi-Nude and Transparent Drapery) Children",,,,,,Artist,,Eadweard Muybridge,"American, born Britain, 1830–1904",,"Muybridge, Eadweard","American, born Britain",1830,1904,1880s,1880,1889,Photogravures,,"Rogers Fund, transferred from the Library",,,,,,,,,,,,Books,,http://www.metmuseum.org/art/collection/search/266436,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1991.1135.7,false,true,266437,Photographs,Photographically illustrated book,"Animal Locomotion. An Electro-Photographic Investigation... of Animal Movements. Commenced 1872 - Completed 1885. Volume VII, Men and Woman (Draped) Miscellaneous Subjects",,,,,,Artist,,Eadweard Muybridge,"American, born Britain, 1830–1904",,"Muybridge, Eadweard","American, born Britain",1830,1904,1880s,1880,1889,Photogravures,,"Rogers Fund, transferred from the Library",,,,,,,,,,,,Books,,http://www.metmuseum.org/art/collection/search/266437,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1991.1135.9,false,true,266439,Photographs,Photographically illustrated book,"Animal Locomotion. An Electro-Photographic Investigation of Consecutive Phases of Animal Movements. Commenced 1872 - Completed 1885. Volume IX, Horses",,,,,,Artist,,Eadweard Muybridge,"American, born Britain, 1830–1904",,"Muybridge, Eadweard","American, born Britain",1830,1904,1880s,1880,1889,Photogravures,,"Rogers Fund, transferred from the Library",,,,,,,,,,,,Books,,http://www.metmuseum.org/art/collection/search/266439,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1991.1135.11,false,true,266431,Photographs,Photographically illustrated book,"Animal Locomotion. An Electro-Photographic Investigation of Consecutive Phases of Animal Movements. Commenced 1872 - Completed 1885. Volume XI, Wild Animals and Birds",,,,,,Artist,,Eadweard Muybridge,"American, born Britain, 1830–1904",,"Muybridge, Eadweard","American, born Britain",1830,1904,1880s,1880,1889,Photogravures,,"Rogers Fund, transferred from the Library",,,,,,,,,,,,Books,,http://www.metmuseum.org/art/collection/search/266431,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2006.525.1,false,true,287979,Photographs,Photograph,[Studio Portrait of a Japanese Man in Western Clothing],,,,,,Artist,,Raimund von Stillfried,"Austrian, 1839–1911",,"Stillfried, Raimond von",Austrian,1839,1911,1880s,1880,1889,Albumen silver print,Image: 14.2 x 9.7 cm (5 9/16 x 3 13/16 in.) Mount: 16.6 x 10.9 cm (6 9/16 x 4 5/16 in.),"Gift of Sue Cassidy Clark, in honor of Dr. Barbara Brennen Ford, 2006",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287979,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.778,false,true,285692,Photographs,Photograph,[Advertisement for Sarony's Photographic Studies],,,,,,Artist,,Napoleon Sarony,"American (born Canada), Quebec 1821–1896 New York",,"Sarony, Napoleon","American, born Canada",1821,1896,1880s,1880,1889,Albumen silver print from glass negative; lithograph,7 3/4 x 9 5/16,"Gilman Collection, Joyce F. Menschel Photography Library Fund, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285692,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.662 (1–18),false,true,285805,Photographs,Album,[Jacob Christian Hansen Ellehammer's Experiments in Early Aviation (1905-1919)],,,,,,Artist|Artist,,Jacob Christian Hansen Ellehammer|Vilhelm Ellehammer,"Danish, 1871–1946|Danish",,"Ellehammer, Jacob Christian Hansen|Ellehammer, Vilhelm",Danish|Danish,1871,1946,1905–19,1905,1919,Gelatin silver prints from glass negatives,9 13/16 × 14 1/8 × 11/16 in. (25 × 35.8 × 1.7 cm),"Gilman Collection, Purchase, Alfred Stieglitz Society Gifts, 2005",,,,,,,,,,,,Albums,,http://www.metmuseum.org/art/collection/search/285805,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -52.639,false,true,269618,Photographs,Album,Progress of the Crystal Palace at Sydenham,,,,,,Artist|Artist,,Philip Henry Delamotte|Henry Angelo Ludovico Negretti,"British, 1821–1889|British, born Italy, 1818–1879",,"Delamotte, Philip Henry|Negretti, and Zambra","British|British, born Italy",1821 |1818,1889 |1879,1854,1854,1854,Albumen silver prints,,"David Hunter McAlpin Fund, 1952",,,,,,,,,,,,Albums,,http://www.metmuseum.org/art/collection/search/269618,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1994.128.1–.2,false,true,266992,Photographs,Photographically illustrated book,"Palais du Louvre et des Tuileries, motifs de décorations tirés des constructions éxécutées au nouveau Louvre et au palais des Tuileries ..., tomes I / II",,,,,,Artist|Artist,,Édouard Baldus|Hector Lefeul,"French, born Prussia, 1813–1889|French, 1810–1880",,"Baldus, Édouard|Lefeul, Hector","French, born Prussia|French",1813 |1810,1889 |1880,1850s–70s,1850,1879,Photogravures,,"Gift of George L. Morse, 1923, transferred from the Library",,,,,,,,,,,,Books,,http://www.metmuseum.org/art/collection/search/266992,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.37,false,true,268303,Photographs,Album,Album di disegni fotogenici,,,,,,Artist|Artist,Likely,Sebastiano Tassinari|William Henry Fox Talbot,"Italian, 1814–1888|British, Dorset 1800–1877 Lacock",,"Tassinari, Sebastiano|Talbot, William Henry Fox",Italian|British,1814 |1800,1888 |1800,1839–40,1839,1840,Photogenic drawings,28.5 x 22 cm (11 1/4 x 8 11/16 in. ),"Harris Brisbane Dick Fund, 1936",,,,,,,,,,,,Albums,,http://www.metmuseum.org/art/collection/search/268303,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2011.66.1–.15,false,true,296322,Photographs,Photogravure; X-Ray,Versuche über Photographie mittelst der Röntgen'schen Strahlen,,,,,,Artist|Artist,,Josef Maria Eder|Eduard Valenta,"Austrian, Krems an der Donau, 1855–1944 Kitzbühel|Austrian, 1857–1937",and,"Eder, Josef Maria|Valenta, Eduard",Austrian|Austrian,1855 |1857,1944 |1937,1896,1896,1896,Photogravures,50 x 36 cm (19 11/16 x 14 3/16 in.),"Purchase, Alfred Stieglitz Society Gifts, Joyce F. Menschel Photography Library Fund, and Maureen and Noel Testa Gift, 2011",,,,,,,,,,,,Portfolios,,http://www.metmuseum.org/art/collection/search/296322,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -54.550.1,false,true,269744,Photographs,Portfolio,Grotesques by Aubrey Beardsley. Facsimile Platinum Prints by Frederick H. Evans from the Twelve Original Drawings in His Collection with a Portrait Frontispiece,,,,,,Artist|Artist,After,Frederick H. Evans|Aubrey Vincent Beardsley,"British, London 1853–1943 London|British, Brighton, Sussex 1872–1898 Menton",,"Evans, Frederick Henry|Beardsley, Aubrey Vincent",British|British,1853 |1872,1943 |1898,1910s,1910,1919,Platinum prints,Sheet: 9 9/16 × 7 1/2 in. (24.3 × 19 cm) Plate: 4 × 3 1/16 in. (10.2 × 7.7 cm) Image: 1 11/16 × 1 5/16 in. (4.3 × 3.3 cm),"Gift of Gordon Conn, 1954",,,,,,,,,,,,Portfolios,,http://www.metmuseum.org/art/collection/search/269744,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.662 (17),false,true,660967,Photographs,Photograph,12 cyl. stjerneformet Motor 160HK. Forsog ikke aflluttede April 1919.,,,,,,Artist|Artist,,Jacob Christian Hansen Ellehammer|Vilhelm Ellehammer,"Danish, 1871–1946|Danish",,"Ellehammer, Jacob Christian Hansen|Ellehammer, Vilhelm",Danish|Danish,1871,1946,1905–19,1905,1919,Gelatin silver prints from glass negatives,Image: 4 3/16 × 3 1/8 in. (10.7 × 8 cm) Sheet: 9 5/8 × 12 13/16 in. (24.4 × 32.6 cm),"Gilman Collection, Purchase, Alfred Stieglitz Society Gifts, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/660967,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.662 (18),false,true,660968,Photographs,Photograph,3/10 26 Yl El Illk[?],,,,,,Artist|Artist,,Jacob Christian Hansen Ellehammer|Vilhelm Ellehammer,"Danish, 1871–1946|Danish",,"Ellehammer, Jacob Christian Hansen|Ellehammer, Vilhelm",Danish|Danish,1871,1946,1905–19,1905,1919,Gelatin silver prints from glass negatives,Image: 3 1/4 × 4 1/4 in. (8.2 × 10.8 cm) Sheet: 9 5/8 × 12 13/16 in. (24.4 × 32.6 cm),"Gilman Collection, Purchase, Alfred Stieglitz Society Gifts, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/660968,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.662 (1a),false,true,660934,Photographs,Photograph,Forsogs Stationar paa Oeu Lindholw 1905,,,,,,Artist|Artist,,Jacob Christian Hansen Ellehammer|Vilhelm Ellehammer,"Danish, 1871–1946|Danish",,"Ellehammer, Jacob Christian Hansen|Ellehammer, Vilhelm",Danish|Danish,1871,1946,1905–19,1905,1919,Gelatin silver prints from glass negatives,Image: 3 9/16 × 4 1/2 in. (9 × 11.4 cm) Sheet: 9 5/8 × 12 13/16 in. (24.4 × 32.6 cm),"Gilman Collection, Purchase, Alfred Stieglitz Society Gifts, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/660934,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.662 (1b),false,true,660936,Photographs,Photograph,Flyvernaskinin paa Banen,,,,,,Artist|Artist,,Jacob Christian Hansen Ellehammer|Vilhelm Ellehammer,"Danish, 1871–1946|Danish",,"Ellehammer, Jacob Christian Hansen|Ellehammer, Vilhelm",Danish|Danish,1871,1946,1905–19,1905,1919,Gelatin silver prints from glass negatives,Image: 3 9/16 × 4 1/2 in. (9.1 × 11.4 cm) Sheet: 9 5/8 × 12 13/16 in. (24.4 × 32.6 cm),"Gilman Collection, Purchase, Alfred Stieglitz Society Gifts, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/660936,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.662 (2a),false,true,660937,Photographs,Photograph,Pendiue Ophanguing of Motor og Flyver.,,,,,,Artist|Artist,,Jacob Christian Hansen Ellehammer|Vilhelm Ellehammer,"Danish, 1871–1946|Danish",,"Ellehammer, Jacob Christian Hansen|Ellehammer, Vilhelm",Danish|Danish,1871,1946,1905–19,1905,1919,Gelatin silver prints from glass negatives,Image: 3 9/16 × 4 7/16 in. (9 × 11.3 cm) Sheet: 9 5/8 × 12 13/16 in. (24.4 × 32.6 cm),"Gilman Collection, Purchase, Alfred Stieglitz Society Gifts, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/660937,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.662 (2b),false,true,660938,Photographs,Photograph,Banen paa Lindholm 1905.,,,,,,Artist|Artist,,Jacob Christian Hansen Ellehammer|Vilhelm Ellehammer,"Danish, 1871–1946|Danish",,"Ellehammer, Jacob Christian Hansen|Ellehammer, Vilhelm",Danish|Danish,1871,1946,1905–19,1905,1919,Gelatin silver prints from glass negatives,Image: 3 11/16 × 4 7/16 in. (9.4 × 11.3 cm) Sheet: 9 5/8 × 12 13/16 in. (24.4 × 32.6 cm),"Gilman Collection, Purchase, Alfred Stieglitz Society Gifts, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/660938,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.662 (3a),false,true,660939,Photographs,Photograph,Havari.,,,,,,Artist|Artist,,Jacob Christian Hansen Ellehammer|Vilhelm Ellehammer,"Danish, 1871–1946|Danish",,"Ellehammer, Jacob Christian Hansen|Ellehammer, Vilhelm",Danish|Danish,1871,1946,1905–19,1905,1919,Gelatin silver prints from glass negatives,Image: 4 1/8 in. × 3 in. (10.5 × 7.6 cm) Sheet: 9 5/8 × 12 13/16 in. (24.4 × 32.6 cm),"Gilman Collection, Purchase, Alfred Stieglitz Society Gifts, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/660939,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.662 (3b),false,true,660940,Photographs,Photograph,Foroget Bareflade.,,,,,,Artist|Artist,,Jacob Christian Hansen Ellehammer|Vilhelm Ellehammer,"Danish, 1871–1946|Danish",,"Ellehammer, Jacob Christian Hansen|Ellehammer, Vilhelm",Danish|Danish,1871,1946,1905–19,1905,1919,Gelatin silver prints from glass negatives,Image: 4 1/8 × 2 15/16 in. (10.5 × 7.5 cm) Sheet: 9 5/8 × 12 13/16 in. (24.4 × 32.6 cm),"Gilman Collection, Purchase, Alfred Stieglitz Society Gifts, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/660940,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.662 (4a),false,true,660941,Photographs,Photograph,"Forsog i 12m Vindhastighed, arbydende Motor.",,,,,,Artist|Artist,,Jacob Christian Hansen Ellehammer|Vilhelm Ellehammer,"Danish, 1871–1946|Danish",,"Ellehammer, Jacob Christian Hansen|Ellehammer, Vilhelm",Danish|Danish,1871,1946,1905–19,1905,1919,Gelatin silver prints from glass negatives,Image: 4 1/8 × 2 15/16 in. (10.5 × 7.4 cm) Sheet: 9 5/8 × 12 13/16 in. (24.4 × 32.6 cm),"Gilman Collection, Purchase, Alfred Stieglitz Society Gifts, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/660941,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.662 (4b),false,true,660942,Photographs,Photograph,Den 1' Flyvning i Europa Sen 12 September 1906.,,,,,,Artist|Artist,,Jacob Christian Hansen Ellehammer|Vilhelm Ellehammer,"Danish, 1871–1946|Danish",,"Ellehammer, Jacob Christian Hansen|Ellehammer, Vilhelm",Danish|Danish,1871,1946,1905–19,1905,1919,Gelatin silver prints from glass negatives,Image: 4 9/16 × 3 1/16 in. (11.6 × 7.7 cm) Sheet: 9 5/8 × 12 13/16 in. (24.4 × 32.6 cm),"Gilman Collection, Purchase, Alfred Stieglitz Society Gifts, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/660942,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.662 (5a),false,true,660943,Photographs,Photograph,"Treplau, Kollekolle. 1907.",,,,,,Artist|Artist,,Jacob Christian Hansen Ellehammer|Vilhelm Ellehammer,"Danish, 1871–1946|Danish",,"Ellehammer, Jacob Christian Hansen|Ellehammer, Vilhelm",Danish|Danish,1871,1946,1905–19,1905,1919,Gelatin silver prints from glass negatives,Image: 3 × 4 in. (7.6 × 10.2 cm) Sheet: 9 5/8 × 12 13/16 in. (24.4 × 32.6 cm),"Gilman Collection, Purchase, Alfred Stieglitz Society Gifts, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/660943,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.662 (5b),false,true,660944,Photographs,Photograph,"Treplan, Farum Su 1907.",,,,,,Artist|Artist,,Jacob Christian Hansen Ellehammer|Vilhelm Ellehammer,"Danish, 1871–1946|Danish",,"Ellehammer, Jacob Christian Hansen|Ellehammer, Vilhelm",Danish|Danish,1871,1946,1905–19,1905,1919,Gelatin silver prints from glass negatives,Image: 2 15/16 × 4 3/16 in. (7.4 × 10.6 cm) Sheet: 9 5/8 × 12 13/16 in. (24.4 × 32.6 cm),"Gilman Collection, Purchase, Alfred Stieglitz Society Gifts, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/660944,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.662 (6a),false,true,660945,Photographs,Photograph,Flyvring 1907.,,,,,,Artist|Artist,,Jacob Christian Hansen Ellehammer|Vilhelm Ellehammer,"Danish, 1871–1946|Danish",,"Ellehammer, Jacob Christian Hansen|Ellehammer, Vilhelm",Danish|Danish,1871,1946,1905–19,1905,1919,Gelatin silver prints from glass negatives,Image: 3 1/16 × 4 1/16 in. (7.7 × 10.3 cm) Sheet: 9 5/8 × 12 13/16 in. (24.4 × 32.6 cm),"Gilman Collection, Purchase, Alfred Stieglitz Society Gifts, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/660945,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.662 (6b),false,true,660946,Photographs,Photograph,"Todakker, med denne Maskine forekoges Seu 28 Tuni, Flyvring ved SX 1'officielle Flyve-Stame i Verdeu Kiel 1908. 1'Pris: 5000 Rm.",,,,,,Artist|Artist,,Jacob Christian Hansen Ellehammer|Vilhelm Ellehammer,"Danish, 1871–1946|Danish",,"Ellehammer, Jacob Christian Hansen|Ellehammer, Vilhelm",Danish|Danish,1871,1946,1905–19,1905,1919,Gelatin silver prints from glass negatives,Image: 3 1/8 × 4 1/8 in. (7.9 × 10.4 cm) Sheet: 9 5/8 × 12 13/16 in. (24.4 × 32.6 cm),"Gilman Collection, Purchase, Alfred Stieglitz Society Gifts, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/660946,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.662 (7a),false,true,660947,Photographs,Photograph,Samme Maskine Sammenfolder.,,,,,,Artist|Artist,,Jacob Christian Hansen Ellehammer|Vilhelm Ellehammer,"Danish, 1871–1946|Danish",,"Ellehammer, Jacob Christian Hansen|Ellehammer, Vilhelm",Danish|Danish,1871,1946,1905–19,1905,1919,Gelatin silver prints from glass negatives,Image: 3 1/16 × 4 1/16 in. (7.7 × 10.3 cm) Sheet: 9 5/8 × 12 13/16 in. (24.4 × 32.6 cm),"Gilman Collection, Purchase, Alfred Stieglitz Society Gifts, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/660947,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.662 (7b),false,true,660948,Photographs,Photograph,"Samme Maskine, Flyvring paa Eremitageu. 1908",,,,,,Artist|Artist,,Jacob Christian Hansen Ellehammer|Vilhelm Ellehammer,"Danish, 1871–1946|Danish",,"Ellehammer, Jacob Christian Hansen|Ellehammer, Vilhelm",Danish|Danish,1871,1946,1905–19,1905,1919,Gelatin silver prints from glass negatives,Image: 3 1/16 in. × 4 in. (7.7 × 10.2 cm) Sheet: 9 5/8 × 12 13/16 in. (24.4 × 32.6 cm),"Gilman Collection, Purchase, Alfred Stieglitz Society Gifts, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/660948,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.662 (8d),false,true,660950,Photographs,Photograph,"Flyvebaad, 1909-1910.",,,,,,Artist|Artist,,Jacob Christian Hansen Ellehammer|Vilhelm Ellehammer,"Danish, 1871–1946|Danish",,"Ellehammer, Jacob Christian Hansen|Ellehammer, Vilhelm",Danish|Danish,1871,1946,1905–19,1905,1919,Gelatin silver prints from glass negatives,Image: 2 15/16 in. × 4 in. (7.5 × 10.2 cm) Sheet: 9 5/8 × 12 13/16 in. (24.4 × 32.6 cm),"Gilman Collection, Purchase, Alfred Stieglitz Society Gifts, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/660950,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.662 (9a),false,true,660951,Photographs,Photograph,"Flyvebaad, 1909-1910.",,,,,,Artist|Artist,,Jacob Christian Hansen Ellehammer|Vilhelm Ellehammer,"Danish, 1871–1946|Danish",,"Ellehammer, Jacob Christian Hansen|Ellehammer, Vilhelm",Danish|Danish,1871,1946,1905–19,1905,1919,Gelatin silver prints from glass negatives,Image: 3 1/4 × 4 3/16 in. (8.2 × 10.7 cm) Sheet: 9 5/8 × 12 13/16 in. (24.4 × 32.6 cm),"Gilman Collection, Purchase, Alfred Stieglitz Society Gifts, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/660951,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.662 (9b),false,true,660952,Photographs,Photograph,Flyvebaad 1909-1910.,,,,,,Artist|Artist,,Jacob Christian Hansen Ellehammer|Vilhelm Ellehammer,"Danish, 1871–1946|Danish",,"Ellehammer, Jacob Christian Hansen|Ellehammer, Vilhelm",Danish|Danish,1871,1946,1905–19,1905,1919,Gelatin silver prints from glass negatives,Image: 4 1/16 × 5 5/8 in. (10.3 × 14.3 cm) Sheet: 9 5/8 × 12 13/16 in. (24.4 × 32.6 cm),"Gilman Collection, Purchase, Alfred Stieglitz Society Gifts, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/660952,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.662 (10a),false,true,660953,Photographs,Photograph,Samme Flyvebaad SammenfolSet.,,,,,,Artist|Artist,,Jacob Christian Hansen Ellehammer|Vilhelm Ellehammer,"Danish, 1871–1946|Danish",,"Ellehammer, Jacob Christian Hansen|Ellehammer, Vilhelm",Danish|Danish,1871,1946,1905–19,1905,1919,Gelatin silver prints from glass negatives,Image: 4 in. × 5 5/8 in. (10.1 × 14.3 cm) Sheet: 9 5/8 × 12 13/16 in. (24.4 × 32.6 cm),"Gilman Collection, Purchase, Alfred Stieglitz Society Gifts, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/660953,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.662 (10b),false,true,660954,Photographs,Photograph,Ieyl. skjerneformet Motor Standard Type. 30HK.,,,,,,Artist|Artist,,Jacob Christian Hansen Ellehammer|Vilhelm Ellehammer,"Danish, 1871–1946|Danish",,"Ellehammer, Jacob Christian Hansen|Ellehammer, Vilhelm",Danish|Danish,1871,1946,1905–19,1905,1919,Gelatin silver prints from glass negatives,Image: 3 1/4 × 4 7/16 in. (8.3 × 11.3 cm) Sheet: 9 5/8 × 12 13/16 in. (24.4 × 32.6 cm),"Gilman Collection, Purchase, Alfred Stieglitz Society Gifts, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/660954,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.662 (11a),false,true,660955,Photographs,Photograph,Ieyl. stjerneformet Motor Standard Type. 50HK.,,,,,,Artist|Artist,,Jacob Christian Hansen Ellehammer|Vilhelm Ellehammer,"Danish, 1871–1946|Danish",,"Ellehammer, Jacob Christian Hansen|Ellehammer, Vilhelm",Danish|Danish,1871,1946,1905–19,1905,1919,Gelatin silver prints from glass negatives,Image: 5 5/8 in. × 4 in. (14.3 × 10.1 cm) Sheet: 9 5/8 × 12 13/16 in. (24.4 × 32.6 cm),"Gilman Collection, Purchase, Alfred Stieglitz Society Gifts, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/660955,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.662 (11b),false,true,660956,Photographs,Photograph,Ellehammers roterende Aeroplan for lodret Start. 1911.,,,,,,Artist|Artist,,Jacob Christian Hansen Ellehammer|Vilhelm Ellehammer,"Danish, 1871–1946|Danish",,"Ellehammer, Jacob Christian Hansen|Ellehammer, Vilhelm",Danish|Danish,1871,1946,1905–19,1905,1919,Gelatin silver prints from glass negatives,Image: 3 1/16 in. × 4 in. (7.8 × 10.1 cm) Sheet: 9 5/8 × 12 13/16 in. (24.4 × 32.6 cm),"Gilman Collection, Purchase, Alfred Stieglitz Society Gifts, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/660956,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.662 (12a),false,true,660957,Photographs,Photograph,Samme Aeroplan.,,,,,,Artist|Artist,,Jacob Christian Hansen Ellehammer|Vilhelm Ellehammer,"Danish, 1871–1946|Danish",,"Ellehammer, Jacob Christian Hansen|Ellehammer, Vilhelm",Danish|Danish,1871,1946,1905–19,1905,1919,Gelatin silver prints from glass negatives,Image: 3 1/16 × 4 1/16 in. (7.7 × 10.3 cm) Sheet: 9 5/8 × 12 13/16 in. (24.4 × 32.6 cm),"Gilman Collection, Purchase, Alfred Stieglitz Society Gifts, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/660957,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.662 (12b),false,true,660958,Photographs,Photograph,Samme Aeroplan.,,,,,,Artist|Artist,,Jacob Christian Hansen Ellehammer|Vilhelm Ellehammer,"Danish, 1871–1946|Danish",,"Ellehammer, Jacob Christian Hansen|Ellehammer, Vilhelm",Danish|Danish,1871,1946,1905–19,1905,1919,Gelatin silver prints from glass negatives,Image: 3 1/8 × 4 1/8 in. (8 × 10.5 cm) Sheet: 9 5/8 × 12 13/16 in. (24.4 × 32.6 cm),"Gilman Collection, Purchase, Alfred Stieglitz Society Gifts, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/660958,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.662 (13a),false,true,660959,Photographs,Photograph,Samme Aeroplan svarende.,,,,,,Artist|Artist,,Jacob Christian Hansen Ellehammer|Vilhelm Ellehammer,"Danish, 1871–1946|Danish",,"Ellehammer, Jacob Christian Hansen|Ellehammer, Vilhelm",Danish|Danish,1871,1946,1905–19,1905,1919,Gelatin silver prints from glass negatives,Image: 3 5/16 × 4 7/16 in. (8.4 × 11.2 cm) Sheet: 9 5/8 × 12 13/16 in. (24.4 × 32.6 cm),"Gilman Collection, Purchase, Alfred Stieglitz Society Gifts, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/660959,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.662 (13b),false,true,660960,Photographs,Photograph,Samme Aeroplan svarende.,,,,,,Artist|Artist,,Jacob Christian Hansen Ellehammer|Vilhelm Ellehammer,"Danish, 1871–1946|Danish",,"Ellehammer, Jacob Christian Hansen|Ellehammer, Vilhelm",Danish|Danish,1871,1946,1905–19,1905,1919,Gelatin silver prints from glass negatives,Image: 3 7/16 × 4 3/8 in. (8.7 × 11.1 cm) Sheet: 9 5/8 × 12 13/16 in. (24.4 × 32.6 cm),"Gilman Collection, Purchase, Alfred Stieglitz Society Gifts, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/660960,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.662 (14a),false,true,660961,Photographs,Photograph,Samme Aeroplan svarende.,,,,,,Artist|Artist,,Jacob Christian Hansen Ellehammer|Vilhelm Ellehammer,"Danish, 1871–1946|Danish",,"Ellehammer, Jacob Christian Hansen|Ellehammer, Vilhelm",Danish|Danish,1871,1946,1905–19,1905,1919,Gelatin silver prints from glass negatives,Image: 3 1/4 × 4 1/16 in. (8.2 × 10.3 cm) Sheet: 9 5/8 × 12 13/16 in. (24.4 × 32.6 cm),"Gilman Collection, Purchase, Alfred Stieglitz Society Gifts, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/660961,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.662 (14b),false,true,660962,Photographs,Photograph,Standard Motor 80HK. Afbremsnings Prore'paa Orlogsvarftets Flyve Station 1916.,,,,,,Artist|Artist,,Jacob Christian Hansen Ellehammer|Vilhelm Ellehammer,"Danish, 1871–1946|Danish",,"Ellehammer, Jacob Christian Hansen|Ellehammer, Vilhelm",Danish|Danish,1871,1946,1905–19,1905,1919,Gelatin silver prints from glass negatives,Image: 3 5/16 × 4 3/8 in. (8.4 × 11.1 cm) Sheet: 9 5/8 × 12 13/16 in. (24.4 × 32.6 cm),"Gilman Collection, Purchase, Alfred Stieglitz Society Gifts, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/660962,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.662 (15a),false,true,660963,Photographs,Photograph,Samme Motor indbygget i en Marine Flyvebaad.,,,,,,Artist|Artist,,Jacob Christian Hansen Ellehammer|Vilhelm Ellehammer,"Danish, 1871–1946|Danish",,"Ellehammer, Jacob Christian Hansen|Ellehammer, Vilhelm",Danish|Danish,1871,1946,1905–19,1905,1919,Gelatin silver prints from glass negatives,Image: 3 3/8 × 4 5/16 in. (8.5 × 11 cm) Sheet: 9 5/8 × 12 13/16 in. (24.4 × 32.6 cm),"Gilman Collection, Purchase, Alfred Stieglitz Society Gifts, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/660963,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.662 (15b),false,true,660964,Photographs,Photograph,Samme Motor i Marine Flyvebaad.,,,,,,Artist|Artist,,Jacob Christian Hansen Ellehammer|Vilhelm Ellehammer,"Danish, 1871–1946|Danish",,"Ellehammer, Jacob Christian Hansen|Ellehammer, Vilhelm",Danish|Danish,1871,1946,1905–19,1905,1919,Gelatin silver prints from glass negatives,Image: 3 5/16 × 4 5/16 in. (8.4 × 10.9 cm) Sheet: 9 5/8 × 12 13/16 in. (24.4 × 32.6 cm),"Gilman Collection, Purchase, Alfred Stieglitz Society Gifts, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/660964,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.662 (16a),false,true,660965,Photographs,Photograph,Samme Motor i Marine Flyvebaad.,,,,,,Artist|Artist,,Jacob Christian Hansen Ellehammer|Vilhelm Ellehammer,"Danish, 1871–1946|Danish",,"Ellehammer, Jacob Christian Hansen|Ellehammer, Vilhelm",Danish|Danish,1871,1946,1905–19,1905,1919,Gelatin silver prints from glass negatives,Image: 3 3/8 × 4 5/16 in. (8.5 × 10.9 cm) Sheet: 9 5/8 × 12 13/16 in. (24.4 × 32.6 cm),"Gilman Collection, Purchase, Alfred Stieglitz Society Gifts, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/660965,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.662 (16b),false,true,660966,Photographs,Photograph,Samme Motor i Marine Flyvebaad.,,,,,,Artist|Artist,,Jacob Christian Hansen Ellehammer|Vilhelm Ellehammer,"Danish, 1871–1946|Danish",,"Ellehammer, Jacob Christian Hansen|Ellehammer, Vilhelm",Danish|Danish,1871,1946,1905–19,1905,1919,Gelatin silver prints from glass negatives,Image: 3 5/16 × 4 5/16 in. (8.4 × 10.9 cm) Sheet: 9 5/8 × 12 13/16 in. (24.4 × 32.6 cm),"Gilman Collection, Purchase, Alfred Stieglitz Society Gifts, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/660966,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.662 (8a–c),false,true,660949,Photographs,Photograph,"Standard Type, Monoplan 1909.",,,,,,Artist|Artist,,Jacob Christian Hansen Ellehammer|Vilhelm Ellehammer,"Danish, 1871–1946|Danish",,"Ellehammer, Jacob Christian Hansen|Ellehammer, Vilhelm",Danish|Danish,1871,1946,1905–19,1905,1919,Gelatin silver prints from glass negatives,Image (u.l.): 2 9/16 × 3 3/8 in. (6.5 × 8.6 cm) Image (u.r.): 2 9/16 × 3 7/16 in. (6.5 × 8.7 cm) Image (l.c.): 3 1/8 × 4 1/16 in. (7.9 × 10.3 cm) Sheet: 9 5/8 × 12 13/16 in. (24.4 × 32.6 cm),"Gilman Collection, Purchase, Alfred Stieglitz Society Gifts, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/660949,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.134a,false,true,305832,Photographs,Photomontage,Napoli Tarantella,,,,,,Artist|Artist,,Giorgio Sommer|Unknown,"Italian, born Germany, 1834–1914|Italian",,"Sommer, Giorgio|Unknown","Italian, born Germany",1834,1914,ca. 1870,1865,1875,Albumen silver print,Image: 19.7 × 25 cm (7 3/4 × 9 13/16 in.) Mount: 25.8 × 35 cm (10 3/16 × 13 3/4 in.),"Purchase, Greenwich ART Group Gift, 2013",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/305832,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2005.100.382 (1a, b)",false,true,287891,Photographs,Photograph,Papa & Mama; The Birthday Group,,,,,,Artist|Artist,,Thereza Dillwyn Llewelyn|John Dillwyn Llewelyn,"British, Swansea, Wales 1810–1882 Swansea, Wales",,"Llewelyn, Thereza Dillwyn|Llewelyn, John Dillwyn","British, Welsh",1810,1882,1853–56,1853,1856,Salted paper print,Image: 7.7 × 8.7 cm (3 1/16 × 3 7/16 in.) (a) Image: 12.6 × 11 cm (4 15/16 × 4 5/16 in.) (b),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287891,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2005.100.382 (15a, b)",false,true,287905,Photographs,Photograph,The Microscope; Thereza and Elinor,,,,,,Artist|Artist,,John Dillwyn Llewelyn|Thereza Dillwyn Llewelyn,"British, Swansea, Wales 1810–1882 Swansea, Wales",,"Llewelyn, John Dillwyn|Llewelyn, Thereza Dillwyn","British, Welsh",1810,1882,1853–56,1853,1856,Salted paper print,Image: 7.6 × 9 cm (3 in. × 3 9/16 in.) (a) Image: 13.8 × 10.5 cm (5 7/16 × 4 1/8 in.) (b),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287905,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.1236,false,true,286664,Photographs,Photograph,"Copying Maps, Photographic Headquarters, Petersburg, Virginia",,,,,,Artist|Artist,Attributed to,Alexander Gardner|Unknown,"American, Glasgow, Scotland 1821–1882 Washington, D.C.",,"Gardner, Alexander|Unknown","American, Scottish",1821,1882,March 1865,1865,1865,Albumen silver print from glass negative,Image: 19 x 24.6 cm (7 1/2 x 9 11/16 in.),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286664,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.944.23,false,true,306591,Photographs,Photograph,[The Wilderness Battlefield],,,,,,Artist|Artist,Possibly by,Unknown|Alexander Gardner,"American, Glasgow, Scotland 1821–1882 Washington, D.C.",,"Unknown|Gardner, Alexander","American, Scottish",1821,1882,1865–67,1865,1867,Albumen silver print from glass negative,Image: 4 15/16 × 3 1/4 in. (12.5 × 8.2 cm),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306591,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.944.24,false,true,306592,Photographs,Photograph,[The Wilderness Battlefield],,,,,,Artist|Artist,Possibly by,Unknown|Alexander Gardner,"American, Glasgow, Scotland 1821–1882 Washington, D.C.",,"Unknown|Gardner, Alexander","American, Scottish",1821,1882,1865–67,1865,1867,Albumen silver print from glass negative,Image: 5 in. × 3 5/16 in. (12.7 × 8.4 cm),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306592,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.382 (74a-c),false,true,287963,Photographs,Photograph,"Upper Lake, Penllengare; Rhododendrons; Brome, Morphine Tuff",,,,,,Artist|Artist,,James Knight|John Dillwyn Llewelyn,"British|British, Swansea, Wales 1810–1882 Swansea, Wales",,"Knight, James|Llewelyn, John Dillwyn","British|British, Welsh",1810,1882,1853–56,1853,1856,Albumen silver print; salted paper print,Image: 13.4 × 16.5 cm (5 1/4 × 6 1/2 in.) (a) Image: 7.4 × 6 cm (2 15/16 × 2 3/8 in.) (b) Image: 7.2 × 5.8 cm (2 13/16 × 2 5/16 in.) (c),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287963,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2005.100.382 (8a, b)",false,true,287898,Photographs,Photograph,Penllergare; Printing,,,,,,Artist|Artist,,James Knight|John Dillwyn Llewelyn,"British|British, Swansea, Wales 1810–1882 Swansea, Wales",,"Knight, James|Llewelyn, John Dillwyn","British|British, Welsh",1810,1882,1853–56,1853,1856,Salted paper print,"Image: 10.8 × 13.9 cm (4 1/4 × 5 1/2 in.) (a), oval Image: 12.6 × 10.3 cm (4 15/16 × 4 1/16 in.) (b)","Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287898,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.1252,false,true,286627,Photographs,Photograph,The Wilderness Battlefield,,,,,,Artist|Artist,Possibly by,Alexander Gardner|Unknown,"American, Glasgow, Scotland 1821–1882 Washington, D.C.|American",,"Gardner, Alexander|Unknown","American, Scottish",1821,1882,1864,1864,1864,Albumen silver print from glass negative,Image: 9 x 9.5 cm (3 9/16 x 3 3/4 in.),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286627,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.944.1,false,true,286347,Photographs,Photographs,The Wilderness Battlefield,,,,,,Artist|Artist,Possibly by,Alexander Gardner|Unknown,"American, Glasgow, Scotland 1821–1882 Washington, D.C.|American",,"Gardner, Alexander|Unknown","American, Scottish",1821,1882,1864,1864,1864,Albumen silver prints from glass negatives,From 12.5 x 7.9 cm (4 15/16 x 3 1/8 in.) to 12.5 x 9.1 cm (4 15/16 x 3 9/16 in.),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286347,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.944.2,false,true,306596,Photographs,Photographs,The Wilderness Battlefield,,,,,,Artist|Artist,Possibly by,Alexander Gardner|Unknown,"American, Glasgow, Scotland 1821–1882 Washington, D.C.|American",,"Gardner, Alexander|Unknown","American, Scottish",1821,1882,1864,1864,1864,Albumen silver print from glass negative,Image: 4 3/16 × 3 7/16 in. (10.7 × 8.7 cm),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306596,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.944.3,false,true,306597,Photographs,Photographs,The Wilderness Battlefield,,,,,,Artist|Artist,Possibly by,Alexander Gardner|Unknown,"American, Glasgow, Scotland 1821–1882 Washington, D.C.|American",,"Gardner, Alexander|Unknown","American, Scottish",1821,1882,1864,1864,1864,Albumen silver print from glass negative,Image: 4 5/8 × 3 7/16 in. (11.7 × 8.7 cm),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306597,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.944.4,false,true,306598,Photographs,Photographs,The Wilderness Battlefield,,,,,,Artist|Artist,Possibly by,Alexander Gardner|Unknown,"American, Glasgow, Scotland 1821–1882 Washington, D.C.|American",,"Gardner, Alexander|Unknown","American, Scottish",1821,1882,1864,1864,1864,Albumen silver print from glass negative,Image: 5 1/16 × 3 1/16 in. (12.8 × 7.8 cm),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306598,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.944.5,false,true,306599,Photographs,Photographs,The Wilderness Battlefield,,,,,,Artist|Artist,Possibly by,Alexander Gardner|Unknown,"American, Glasgow, Scotland 1821–1882 Washington, D.C.|American",,"Gardner, Alexander|Unknown","American, Scottish",1821,1882,1864,1864,1864,Albumen silver print from glass negative,Image: 4 7/8 × 3 11/16 in. (12.4 × 9.4 cm) Mount: 7 3/16 in. × 6 5/16 in. (18.3 × 16 cm),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306599,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.944.6,false,true,306600,Photographs,Photographs,The Wilderness Battlefield,,,,,,Artist|Artist,Possibly by,Alexander Gardner|Unknown,"American, Glasgow, Scotland 1821–1882 Washington, D.C.|American",,"Gardner, Alexander|Unknown","American, Scottish",1821,1882,1864,1864,1864,Albumen silver print from glass negative,Image: 3 1/4 × 4 15/16 in. (8.3 × 12.5 cm),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306600,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.944.7,false,true,306601,Photographs,Photographs,The Wilderness Battlefield,,,,,,Artist|Artist,Possibly by,Alexander Gardner|Unknown,"American, Glasgow, Scotland 1821–1882 Washington, D.C.|American",,"Gardner, Alexander|Unknown","American, Scottish",1821,1882,1864,1864,1864,Albumen silver print from glass negative,Image: 5 in. × 3 7/16 in. (12.7 × 8.8 cm),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306601,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.944.8,false,true,306602,Photographs,Photographs,The Wilderness Battlefield,,,,,,Artist|Artist,Possibly by,Alexander Gardner|Unknown,"American, Glasgow, Scotland 1821–1882 Washington, D.C.|American",,"Gardner, Alexander|Unknown","American, Scottish",1821,1882,1864,1864,1864,Albumen silver print from glass negative,Image: 5 1/16 × 3 11/16 in. (12.9 × 9.4 cm),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306602,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.944.9,false,true,306603,Photographs,Photographs,The Wilderness Battlefield,,,,,,Artist|Artist,Possibly by,Alexander Gardner|Unknown,"American, Glasgow, Scotland 1821–1882 Washington, D.C.|American",,"Gardner, Alexander|Unknown","American, Scottish",1821,1882,1864,1864,1864,Albumen silver print from glass negative,Image: 4 15/16 × 3 9/16 in. (12.5 × 9 cm),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306603,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.944.10,false,true,306604,Photographs,Photographs,The Wilderness Battlefield,,,,,,Artist|Artist,Possibly by,Alexander Gardner|Unknown,"American, Glasgow, Scotland 1821–1882 Washington, D.C.|American",,"Gardner, Alexander|Unknown","American, Scottish",1821,1882,1864,1864,1864,Albumen silver print from glass negative,Image: 5 in. × 3 1/4 in. (12.7 × 8.2 cm),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306604,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.944.11,false,true,306605,Photographs,Photographs,The Wilderness Battlefield,,,,,,Artist|Artist,Possibly by,Alexander Gardner|Unknown,"American, Glasgow, Scotland 1821–1882 Washington, D.C.|American",,"Gardner, Alexander|Unknown","American, Scottish",1821,1882,1864,1864,1864,Albumen silver print from glass negative,Image: 4 7/8 × 3 3/16 in. (12.4 × 8.1 cm),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306605,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.944.12,false,true,306606,Photographs,Photographs,The Wilderness Battlefield,,,,,,Artist|Artist,Possibly by,Alexander Gardner|Unknown,"American, Glasgow, Scotland 1821–1882 Washington, D.C.|American",,"Gardner, Alexander|Unknown","American, Scottish",1821,1882,1864,1864,1864,Albumen silver print from glass negative,Image: 4 15/16 × 3 5/16 in. (12.6 × 8.4 cm),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306606,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.944.13,false,true,306607,Photographs,Photographs,The Wilderness Battlefield,,,,,,Artist|Artist,Possibly by,Alexander Gardner|Unknown,"American, Glasgow, Scotland 1821–1882 Washington, D.C.|American",,"Gardner, Alexander|Unknown","American, Scottish",1821,1882,1864,1864,1864,Albumen silver print from glass negative,Image: 5 1/16 × 2 3/4 in. (12.8 × 7 cm),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306607,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.944.14,false,true,306608,Photographs,Photographs,The Wilderness Battlefield,,,,,,Artist|Artist,Possibly by,Alexander Gardner|Unknown,"American, Glasgow, Scotland 1821–1882 Washington, D.C.|American",,"Gardner, Alexander|Unknown","American, Scottish",1821,1882,1864,1864,1864,Albumen silver print from glass negative,Image: 5 in. × 3 3/16 in. (12.7 × 8.1 cm),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306608,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.944.15,false,true,306609,Photographs,Photographs,The Wilderness Battlefield,,,,,,Artist|Artist,Possibly by,Alexander Gardner|Unknown,"American, Glasgow, Scotland 1821–1882 Washington, D.C.|American",,"Gardner, Alexander|Unknown","American, Scottish",1821,1882,1864,1864,1864,Albumen silver print from glass negative,Image: 4 5/8 × 3 7/16 in. (11.8 × 8.7 cm),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306609,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.944.16,false,true,306610,Photographs,Photographs,The Wilderness Battlefield,,,,,,Artist|Artist,Possibly by,Alexander Gardner|Unknown,"American, Glasgow, Scotland 1821–1882 Washington, D.C.|American",,"Gardner, Alexander|Unknown","American, Scottish",1821,1882,1864,1864,1864,Albumen silver print from glass negative,Image: 4 13/16 × 3 5/16 in. (12.3 × 8.4 cm),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306610,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.944.17,false,true,306611,Photographs,Photographs,The Wilderness Battlefield,,,,,,Artist|Artist,Possibly by,Alexander Gardner|Unknown,"American, Glasgow, Scotland 1821–1882 Washington, D.C.|American",,"Gardner, Alexander|Unknown","American, Scottish",1821,1882,1864,1864,1864,Albumen silver print from glass negative,Image: 4 15/16 × 3 1/4 in. (12.6 × 8.2 cm),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306611,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.944.18,false,true,306612,Photographs,Photographs,"[The Wilderness Battlefield, near Spotsylvania, Virginia]",,,,,,Artist|Artist,Possibly by,Alexander Gardner|Unknown,"American, Glasgow, Scotland 1821–1882 Washington, D.C.|American",,"Gardner, Alexander|Unknown","American, Scottish",1821,1882,1865 (?),1865,1865,Albumen silver print from glass negative,,"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306612,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.944.19,false,true,306613,Photographs,Photographs,The Wilderness Battlefield,,,,,,Artist|Artist,Possibly by,Alexander Gardner|Unknown,"American, Glasgow, Scotland 1821–1882 Washington, D.C.|American",,"Gardner, Alexander|Unknown","American, Scottish",1821,1882,1864,1864,1864,Albumen silver print from glass negative,Image: 5 in. × 3 9/16 in. (12.7 × 9.1 cm),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306613,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.944.20,false,true,306614,Photographs,Photographs,The Wilderness Battlefield,,,,,,Artist|Artist,Possibly by,Alexander Gardner|Unknown,"American, Glasgow, Scotland 1821–1882 Washington, D.C.|American",,"Gardner, Alexander|Unknown","American, Scottish",1821,1882,1864,1864,1864,Albumen silver print from glass negative,Image: 4 3/4 × 3 1/8 in. (12.1 × 8 cm),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306614,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.944.21,false,true,306615,Photographs,Photographs,The Wilderness Battlefield,,,,,,Artist|Artist,Possibly by,Alexander Gardner|Unknown,"American, Glasgow, Scotland 1821–1882 Washington, D.C.|American",,"Gardner, Alexander|Unknown","American, Scottish",1821,1882,1864,1864,1864,Albumen silver print from glass negative,Image: 4 1/8 × 3 11/16 in. (10.4 × 9.3 cm),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306615,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.924,false,true,286184,Photographs,Photograph,"Infernal Rock, Chincha Islands",,,,,,Artist|Artist,Possibly by,Unknown|John Moran,"American (born England), Bolton, Lancashire 1821–1903 Pennsylvania",,"Unknown|Moran, John","American, born England",1821,1903,ca. 1870,1870,1870,Albumen silver print from glass negative,Image: 18.4 x 25.3 cm (7 1/4 x 9 15/16 in.),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286184,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1973.608.2.1,false,true,260973,Photographs,Photograph,[Pyramid at Dahshûr],,,,,,Artist|Artist,,Francis Frith|Francis Frith and Company,"British, Chesterfield, Derbyshire 1822–1898 Cannes, France|British",,"Frith, Francis|Francis Frith and Company",British|British,1822,1898,ca. 1857,1855,1859,Albumen silver print from glass negative,15.4 x 20.8 cm. (6 1/16 x 8 3/16 in.),"The Elisha Whittelsey Collection, The Elisha Whittelsey Fund, 1973",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260973,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1973.608.2.3,false,true,260975,Photographs,Photograph,"[Valley of the Kings, Thebes]",,,,,,Artist|Artist,,Francis Frith|Francis Frith and Company,"British, Chesterfield, Derbyshire 1822–1898 Cannes, France|British",,"Frith, Francis|Francis Frith and Company",British|British,1822,1898,"ca. 1857, printed 1870s",1855,1859,Albumen silver print from glass negative,,"The Elisha Whittelsey Collection, The Elisha Whittelsey Fund, 1973",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260975,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1973.608.1.26,false,true,260971,Photographs,Photograph,Banks of the Nile at Cairo,,,,,,Artist|Artist,,Francis Frith|Francis Frith and Company,"British, Chesterfield, Derbyshire 1822–1898 Cannes, France|British",,"Frith, Francis|Francis Frith and Company",British|British,1822,1898,"ca. 1857, printed 1870s",1855,1859,Albumen silver print from glass negative,,"The Elisha Whittelsey Collection, The Elisha Whittelsey Fund, 1973",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260971,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1999.138,false,true,282751,Photographs,Photograph,Zaragoza à Pamplona y Barcelona - Puente de Zuera,,,,,,Artist|Artist,,Juan Laurent|José Martinez Sánchez,"French, 1816–1892, active Spain, 1857–1880s|Spanish, 1808–1874",,"Laurent, Juan|Martinez, Sánchez José",French|Spanish,1816 |1808,1892 |1874,ca. 1867,1866,1868,Albumen silver print from glass negative,33.9 x 24.8 cm (13 3/8 x 9 3/4 in.),"Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, by exchange, 1999",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/282751,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2005.100.171.1, .2",false,true,283065,Photographs,Photograph,[The Reading Establishment],,,,,,Artist|Artist,Attributed to|Attributed to,Nicolaas Henneman|William Henry Fox Talbot,"Dutch, Heemskerk 1813–1898 London|British, Dorset 1800–1877 Lacock",,"Henneman, Nicolaas|Talbot, William Henry Fox",Dutch|British,1813 |1800,1898 |1800,1846,1846,1846,Salted paper prints from paper negatives,Left image: 18.6 x 22.4 cm (7 5/16 x 8 13/16 in.) Right image: 18.1 × 22 cm (7 1/8 × 8 11/16 in.) Overall sheet: 19.9 × 49.1 cm (7 13/16 × 19 5/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/283065,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1982.1168.1,false,true,263461,Photographs,Photograph,"[Mezzotint portrait of a Girl in Profile, from The St. Memin Collection of Portraits]",,,,,,Artist|Artist,After,Jeremiah Gurney|Charles B. J. F. de Saint-Mémin,"American, 1812–1895 Coxsackie, New York|French, Dijon 1770–1852 Dijon",,"Gurney, Jeremiah|Saint-Mémin, Charles de",American|French,1812 |1770,1895-04-21|1852,1862,1862,1862,Salted paper print,5.6 x 5.6 cm (2 3/16 x 2 3/16 in. ),"Gift of Mrs. James Anderson, 1982",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/263461,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1982.1168.2,false,true,263462,Photographs,Photograph,"[Mezzotint portrait of a Young Man in Profile, from The St. Memin Collection of Portraits]",,,,,,Artist|Artist,After,Jeremiah Gurney|Charles B. J. F. de Saint-Mémin,"American, 1812–1895 Coxsackie, New York|French, Dijon 1770–1852 Dijon",,"Gurney, Jeremiah|Saint-Mémin, Charles de",American|French,1812 |1770,1895-04-21|1852,1862,1862,1862,Salted paper print,5.6 x 5.6 cm (2 3/16 x 2 3/16 in. ),"Gift of Mrs. James Anderson, 1982",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/263462,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2011.66.1,false,true,660039,Photographs,Photogravure; X-Ray,Frauenhand,,,,,,Artist|Artist,,Josef Maria Eder|Eduard Valenta,"Austrian, Krems an der Donau, 1855–1944 Kitzbühel|Austrian, 1857–1937",and,"Eder, Josef Maria|Valenta, Eduard",Austrian|Austrian,1855 |1857,1944 |1937,1896,1896,1896,Photogravure,Image: 10 3/16 × 4 1/4 in. (25.9 × 10.8 cm) Plate: 11 7/16 × 5 5/16 in. (29 × 13.5 cm) Sheet: 19 5/16 in. × 14 in. (49 × 35.5 cm),"Purchase, Alfred Stieglitz Society Gifts, Joyce F. Menschel Photography Library Fund, and Maureen and Noel Testa Gift, 2011",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/660039,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2011.66.2,false,true,660040,Photographs,Photogravure; X-Ray,Hand eines 8 jährigen Mädchens,,,,,,Artist|Artist,,Josef Maria Eder|Eduard Valenta,"Austrian, Krems an der Donau, 1855–1944 Kitzbühel|Austrian, 1857–1937",and,"Eder, Josef Maria|Valenta, Eduard",Austrian|Austrian,1855 |1857,1944 |1937,1896,1896,1896,Photogravure,Image: 6 7/8 × 3 5/16 in. (17.4 × 8.4 cm) Plate: 8 1/16 × 4 9/16 in. (20.5 × 11.6 cm) Sheet: 19 1/2 × 13 3/4 in. (49.5 × 34.9 cm),"Purchase, Alfred Stieglitz Society Gifts, Joyce F. Menschel Photography Library Fund, and Maureen and Noel Testa Gift, 2011",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/660040,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2011.66.3,false,true,660041,Photographs,Photogravure; X-Ray,Hand eines 4 jährigen Kindes,,,,,,Artist|Artist,,Josef Maria Eder|Eduard Valenta,"Austrian, Krems an der Donau, 1855–1944 Kitzbühel|Austrian, 1857–1937",and,"Eder, Josef Maria|Valenta, Eduard",Austrian|Austrian,1855 |1857,1944 |1937,1896,1896,1896,Photogravure,Image: 7 13/16 × 3 1/4 in. (19.9 × 8.2 cm) Plate: 8 13/16 × 4 3/16 in. (22.4 × 10.7 cm) Sheet: 19 9/16 × 13 7/8 in. (49.7 × 35.2 cm),"Purchase, Alfred Stieglitz Society Gifts, Joyce F. Menschel Photography Library Fund, and Maureen and Noel Testa Gift, 2011",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/660041,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2011.66.4,false,true,660042,Photographs,Photogravure; X-Ray,Fuss eines 17 jährigen Jünglings,,,,,,Artist|Artist,,Josef Maria Eder|Eduard Valenta,"Austrian, Krems an der Donau, 1855–1944 Kitzbühel|Austrian, 1857–1937",and,"Eder, Josef Maria|Valenta, Eduard",Austrian|Austrian,1855 |1857,1944 |1937,1896,1896,1896,Photogravure,Image: 8 1/4 in. × 7 in. (21 × 17.8 cm) Plate: 11 1/4 × 8 1/16 in. (28.5 × 20.4 cm) Sheet: 19 5/8 × 13 3/4 in. (49.9 × 35 cm),"Purchase, Alfred Stieglitz Society Gifts, Joyce F. Menschel Photography Library Fund, and Maureen and Noel Testa Gift, 2011",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/660042,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2011.66.5,false,true,660043,Photographs,Photogravure; X-Ray,X-Ray of Samples of Various Materials,,,,,,Artist|Artist,,Josef Maria Eder|Eduard Valenta,"Austrian, Krems an der Donau, 1855–1944 Kitzbühel|Austrian, 1857–1937",and,"Eder, Josef Maria|Valenta, Eduard",Austrian|Austrian,1855 |1857,1944 |1937,1896,1896,1896,Photogravure,Image: 9 1/16 × 6 15/16 in. (23 × 17.7 cm) Plate: 10 5/16 × 8 1/4 in. (26.2 × 20.9 cm) Sheet: 17 5/16 × 13 15/16 in. (44 × 35.4 cm),"Purchase, Alfred Stieglitz Society Gifts, Joyce F. Menschel Photography Library Fund, and Maureen and Noel Testa Gift, 2011",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/660043,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2011.66.6,false,true,660044,Photographs,Photogravure; X-Ray,Cameen in Goldfassung,,,,,,Artist|Artist,,Josef Maria Eder|Eduard Valenta,"Austrian, Krems an der Donau, 1855–1944 Kitzbühel|Austrian, 1857–1937",and,"Eder, Josef Maria|Valenta, Eduard",Austrian|Austrian,1855 |1857,1944 |1937,1896,1896,1896,Photogravure,Image: 4 5/8 × 6 5/8 in. (11.7 × 16.8 cm) Plate: 5 1/2 × 7 in. (14 × 17.8 cm) Sheet: 14 1/8 × 19 5/8 in. (35.8 × 49.8 cm),"Purchase, Alfred Stieglitz Society Gifts, Joyce F. Menschel Photography Library Fund, and Maureen and Noel Testa Gift, 2011",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/660044,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2011.66.7,false,true,660045,Photographs,Photogravure; X-Ray,Grüne Eidechse,,,,,,Artist|Artist,,Josef Maria Eder|Eduard Valenta,"Austrian, Krems an der Donau, 1855–1944 Kitzbühel|Austrian, 1857–1937",and,"Eder, Josef Maria|Valenta, Eduard",Austrian|Austrian,1855 |1857,1944 |1937,1896,1896,1896,Photogravure,Image: 3 5/8 × 6 1/4 in. (9.2 × 15.9 cm) Plate: 4 15/16 × 7 7/16 in. (12.5 × 18.9 cm) Sheet: 13 7/8 × 19 5/8 in. (35.2 × 49.9 cm),"Purchase, Alfred Stieglitz Society Gifts, Joyce F. Menschel Photography Library Fund, and Maureen and Noel Testa Gift, 2011",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/660045,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2011.66.8,false,true,660046,Photographs,Photogravure; X-Ray,Chamäleon cristatus,,,,,,Artist|Artist,,Josef Maria Eder|Eduard Valenta,"Austrian, Krems an der Donau, 1855–1944 Kitzbühel|Austrian, 1857–1937",and,"Eder, Josef Maria|Valenta, Eduard",Austrian|Austrian,1855 |1857,1944 |1937,1896,1896,1896,Photogravure,Image: 4 15/16 × 7 7/8 in. (12.6 × 20 cm) Plate: 6 1/8 × 9 1/8 in. (15.5 × 23.2 cm) Sheet: 13 7/8 × 19 11/16 in. (35.3 × 50 cm),"Purchase, Alfred Stieglitz Society Gifts, Joyce F. Menschel Photography Library Fund, and Maureen and Noel Testa Gift, 2011",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/660046,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2011.66.9,false,true,660047,Photographs,Photogravure; X-Ray,Zanclus cornutus / Acanthurus nigros,,,,,,Artist|Artist,,Josef Maria Eder|Eduard Valenta,"Austrian, Krems an der Donau, 1855–1944 Kitzbühel|Austrian, 1857–1937",and,"Eder, Josef Maria|Valenta, Eduard",Austrian|Austrian,1855 |1857,1944 |1937,1896,1896,1896,Photogravure,Image: 9 1/2 × 7 13/16 in. (24.2 × 19.8 cm) Plate: 10 9/16 × 8 13/16 in. (26.8 × 22.4 cm) Sheet: 19 3/4 × 13 15/16 in. (50.2 × 35.4 cm),"Purchase, Alfred Stieglitz Society Gifts, Joyce F. Menschel Photography Library Fund, and Maureen and Noel Testa Gift, 2011",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/660047,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2011.66.10,false,true,660048,Photographs,Photogravure; X-Ray,Zwei Goldfische und ein Seefisch (Christiceps argentatus),,,,,,Artist|Artist,,Josef Maria Eder|Eduard Valenta,"Austrian, Krems an der Donau, 1855–1944 Kitzbühel|Austrian, 1857–1937",and,"Eder, Josef Maria|Valenta, Eduard",Austrian|Austrian,1855 |1857,1944 |1937,1896,1896,1896,Photogravure,Image: 4 15/16 × 6 9/16 in. (12.5 × 16.6 cm) Plate: 5 13/16 × 7 9/16 in. (14.7 × 19.2 cm) Sheet: 13 7/8 × 19 5/8 in. (35.3 × 49.9 cm),"Purchase, Alfred Stieglitz Society Gifts, Joyce F. Menschel Photography Library Fund, and Maureen and Noel Testa Gift, 2011",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/660048,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2011.66.11,false,true,660049,Photographs,Photogravure; X-Ray,Solfisch (Pleuronectes solea),,,,,,Artist|Artist,,Josef Maria Eder|Eduard Valenta,"Austrian, Krems an der Donau, 1855–1944 Kitzbühel|Austrian, 1857–1937",and,"Eder, Josef Maria|Valenta, Eduard",Austrian|Austrian,1855 |1857,1944 |1937,1896,1896,1896,Photogravure,Image: 6 3/4 × 13 5/8 in. (17.1 × 34.6 cm) Plate: 8 × 14 13/16 in. (20.3 × 37.6 cm) Sheet: 13 13/16 × 19 11/16 in. (35.1 × 50 cm),"Purchase, Alfred Stieglitz Society Gifts, Joyce F. Menschel Photography Library Fund, and Maureen and Noel Testa Gift, 2011",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/660049,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2011.66.12,false,true,660050,Photographs,Photogravure; X-Ray,Frösche in Bauch und Rückenlage,,,,,,Artist|Artist,,Josef Maria Eder|Eduard Valenta,"Austrian, Krems an der Donau, 1855–1944 Kitzbühel|Austrian, 1857–1937",and,"Eder, Josef Maria|Valenta, Eduard",Austrian|Austrian,1855 |1857,1944 |1937,1896,1896,1896,Photogravure,Image: 9 in. × 6 1/2 in. (22.8 × 16.5 cm) Plate: 9 15/16 × 7 1/2 in. (25.3 × 19 cm) Sheet: 19 9/16 × 13 7/8 in. (49.7 × 35.3 cm),"Purchase, Alfred Stieglitz Society Gifts, Joyce F. Menschel Photography Library Fund, and Maureen and Noel Testa Gift, 2011",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/660050,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2011.66.13,false,true,660051,Photographs,Photogravure; X-Ray,Ratte,,,,,,Artist|Artist,,Josef Maria Eder|Eduard Valenta,"Austrian, Krems an der Donau, 1855–1944 Kitzbühel|Austrian, 1857–1937",and,"Eder, Josef Maria|Valenta, Eduard",Austrian|Austrian,1855 |1857,1944 |1937,1896,1896,1896,Photogravure,Image: 5 7/16 × 9 3/4 in. (13.8 × 24.8 cm) Plate: 6 9/16 × 10 7/8 in. (16.6 × 27.6 cm) Sheet: 13 7/8 × 19 5/8 in. (35.3 × 49.9 cm),"Purchase, Alfred Stieglitz Society Gifts, Joyce F. Menschel Photography Library Fund, and Maureen and Noel Testa Gift, 2011",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/660051,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2011.66.14,false,true,660052,Photographs,Photogravure; X-Ray,Neugeborenes Kaninchen,,,,,,Artist|Artist,,Josef Maria Eder|Eduard Valenta,"Austrian, Krems an der Donau, 1855–1944 Kitzbühel|Austrian, 1857–1937",and,"Eder, Josef Maria|Valenta, Eduard",Austrian|Austrian,1855 |1857,1944 |1937,1896,1896,1896,Photogravure,Image: 2 11/16 × 5 7/16 in. (6.9 × 13.8 cm) Plate: 4 1/4 × 6 9/16 in. (10.8 × 16.7 cm) Sheet: 14 1/16 × 19 5/8 in. (35.7 × 49.9 cm),"Purchase, Alfred Stieglitz Society Gifts, Joyce F. Menschel Photography Library Fund, and Maureen and Noel Testa Gift, 2011",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/660052,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2011.66.15,false,true,660053,Photographs,Photogravure; X-Ray,Aesculap-Schlange,,,,,,Artist|Artist,,Josef Maria Eder|Eduard Valenta,"Austrian, Krems an der Donau, 1855–1944 Kitzbühel|Austrian, 1857–1937",and,"Eder, Josef Maria|Valenta, Eduard",Austrian|Austrian,1855 |1857,1944 |1937,1896,1896,1896,Photogravure,Image: 10 3/4 × 8 9/16 in. (27.3 × 21.7 cm) Plate: 11 15/16 × 9 3/4 in. (30.3 × 24.8 cm) Sheet: 19 5/8 × 13 7/8 in. (49.9 × 35.3 cm),"Purchase, Alfred Stieglitz Society Gifts, Joyce F. Menschel Photography Library Fund, and Maureen and Noel Testa Gift, 2011",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/660053,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.1098.8,false,true,631032,Photographs,Photograph,"[Dome of the Rock, Jerusalem]",,,,,,Artist|Artist,,Felice Beato|James Robertson,"British (born Italy), Venice 1832–1909 Luxor, Egypt|British, 1813–1881",and,"Beato, Felice|Robertson, James","British, born Italy|British",1832 |1813,1909 |1881,1856–57,1856,1857,Albumen silver print,Image: 9 in. × 11 1/4 in. (22.9 × 28.6 cm) Mount: 17 5/8 in. × 22 1/2 in. (44.8 × 57.2 cm),"Gift of Joyce F. Menschel, 2013",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/631032,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.1246,false,true,285890,Photographs,Photograph,"[Panorama of Camp Winfield Scott, Yorktown, Virginia]",,,,,,Artist|Artist,,Alexander Gardner|James F. Gibson,"American, Glasgow, Scotland 1821–1882 Washington, D.C.|American, born 1828",,"Gardner, Alexander|Gibson, James F.","American, Scottish|American",1821 |1828,1882 |1928,1863,1863,1863,Albumen silver prints from glass negatives,Image: 3 5/16 × 18 13/16 in. (8.4 × 47.8 cm) Mount: 5 3/8 × 20 5/8 in. (13.6 × 52.4 cm),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285890,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -52.640 (1),false,true,291374,Photographs,Photograph,Fountains Abbey. East Window and Tower,,,,,,Artist|Artist,,Joseph Cundall|Philip Henry Delamotte,"British, Norwich, Norfolk 1818–1895 Wallington, Surrey|British, 1821–1889",,"Cundall, Joseph|Delamotte, Philip Henry",British|British,1818 |1821,1895 |1889,1850s,1850,1859,Albumen silver print,Image: 28 x 23.5 cm (11 x 9 1/4 in.) Mount: 43.9 x 30 cm (17 5/16 x 11 13/16 in.),"David Hunter McAlpin Fund, 1952",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/291374,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -52.640 (2),false,true,291375,Photographs,Photograph,Fountains Abbey. General Western Front,,,,,,Artist|Artist,,Joseph Cundall|Philip Henry Delamotte,"British, Norwich, Norfolk 1818–1895 Wallington, Surrey|British, 1821–1889",,"Cundall, Joseph|Delamotte, Philip Henry",British|British,1818 |1821,1895 |1889,1850s,1850,1859,Albumen silver print,Image: 22.5 x 27.9 cm (8 7/8 x 11 in.) Mount: 43.9 x 30 cm (17 5/16 x 11 13/16 in.),"David Hunter McAlpin Fund, 1952",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/291375,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -52.640 (3),false,true,291376,Photographs,Photograph,"Fountains Abbey. The Church, Cloister and Hospitium",,,,,,Artist|Artist,,Joseph Cundall|Philip Henry Delamotte,"British, Norwich, Norfolk 1818–1895 Wallington, Surrey|British, 1821–1889",,"Cundall, Joseph|Delamotte, Philip Henry",British|British,1818 |1821,1895 |1889,1850s,1850,1859,Albumen silver print,Image: 28 x 23.4 cm (11 x 9 3/16 in.) Mount: 43.9 x 30 cm (17 5/16 x 11 13/16 in.),"David Hunter McAlpin Fund, 1952",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/291376,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -52.640 (4),false,true,291369,Photographs,Photograph,Fountains Abbey. The Refectory and Kitchen,,,,,,Artist|Artist,,Joseph Cundall|Philip Henry Delamotte,"British, Norwich, Norfolk 1818–1895 Wallington, Surrey|British, 1821–1889",,"Cundall, Joseph|Delamotte, Philip Henry",British|British,1818 |1821,1895 |1889,1850s,1850,1859,Albumen silver print,Image: 28.5 x 23.6 cm (11 1/4 x 9 5/16 in.) Mount: 43.9 x 30 cm (17 5/16 x 11 13/16 in.),"David Hunter McAlpin Fund, 1952",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/291369,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -52.640 (5),false,true,291377,Photographs,Photograph,Fountains Abbey. The Church and Chapter House,,,,,,Artist|Artist,,Joseph Cundall|Philip Henry Delamotte,"British, Norwich, Norfolk 1818–1895 Wallington, Surrey|British, 1821–1889",,"Cundall, Joseph|Delamotte, Philip Henry",British|British,1818 |1821,1895 |1889,1850s,1850,1859,Albumen silver print,Image: 27.4 x 21.7 cm (10 13/16 x 8 9/16 in.) Mount: 43.9 x 30 cm (17 5/16 x 11 13/16 in.),"David Hunter McAlpin Fund, 1952",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/291377,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -52.640 (6),false,true,291378,Photographs,Photograph,Fountains Abbey. Interior of Chapter House,,,,,,Artist|Artist,,Joseph Cundall|Philip Henry Delamotte,"British, Norwich, Norfolk 1818–1895 Wallington, Surrey|British, 1821–1889",,"Cundall, Joseph|Delamotte, Philip Henry",British|British,1818 |1821,1895 |1889,1850s,1850,1859,Albumen silver print,Image: 28.5 x 23.6 cm (11 1/4 x 9 5/16 in.) Mount: 43.9 x 30 cm (17 5/16 x 11 13/16 in.),"David Hunter McAlpin Fund, 1952",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/291378,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -52.640 (7),false,true,291379,Photographs,Photograph,"Fountains Abbey. The Chapel of the Nine Alters, Exterior",,,,,,Artist|Artist,,Joseph Cundall|Philip Henry Delamotte,"British, Norwich, Norfolk 1818–1895 Wallington, Surrey|British, 1821–1889",,"Cundall, Joseph|Delamotte, Philip Henry",British|British,1818 |1821,1895 |1889,1850s,1850,1859,Albumen silver print,Image: 23.5 x 28 cm (9 1/4 x 11 in.) Mount: 43.9 x 30 cm (17 5/16 x 11 13/16 in.),"David Hunter McAlpin Fund, 1952",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/291379,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -52.640 (8),false,true,291380,Photographs,Photograph,"Fountains Abbey. The Chapel of the Nine Alters, Interior",,,,,,Artist|Artist,,Joseph Cundall|Philip Henry Delamotte,"British, Norwich, Norfolk 1818–1895 Wallington, Surrey|British, 1821–1889",,"Cundall, Joseph|Delamotte, Philip Henry",British|British,1818 |1821,1895 |1889,1850s,1850,1859,Albumen silver print,Image: 28.6 x 23.6 cm (11 1/4 x 9 5/16 in.) Mount: 43.9 x 30 cm (17 5/16 x 11 13/16 in.),"David Hunter McAlpin Fund, 1952",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/291380,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -52.640 (9),false,true,291381,Photographs,Photograph,Fountains Abbey. Interior of the Choir,,,,,,Artist|Artist,,Joseph Cundall|Philip Henry Delamotte,"British, Norwich, Norfolk 1818–1895 Wallington, Surrey|British, 1821–1889",,"Cundall, Joseph|Delamotte, Philip Henry",British|British,1818 |1821,1895 |1889,1850s,1850,1859,Albumen silver print,Image: 28.2 x 22.8 cm (11 1/8 x 9 in.) Mount: 43.9 x 30 cm (17 5/16 x 11 13/16 in.),"David Hunter McAlpin Fund, 1952",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/291381,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -52.640 (10),false,true,291382,Photographs,Photograph,Fountains Abbey. The Echo Rock,,,,,,Artist|Artist,,Joseph Cundall|Philip Henry Delamotte,"British, Norwich, Norfolk 1818–1895 Wallington, Surrey|British, 1821–1889",,"Cundall, Joseph|Delamotte, Philip Henry",British|British,1818 |1821,1895 |1889,1850s,1850,1859,Albumen silver print,Image: 23.5 x 27.9 cm (9 1/4 x 11 in.) Mount: 43.9 x 30 cm (17 5/16 x 11 13/16 in.),"David Hunter McAlpin Fund, 1952",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/291382,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -52.640 (11),false,true,291383,Photographs,Photograph,Easby Abbey. From the East,,,,,,Artist|Artist,,Joseph Cundall|Philip Henry Delamotte,"British, Norwich, Norfolk 1818–1895 Wallington, Surrey|British, 1821–1889",,"Cundall, Joseph|Delamotte, Philip Henry",British|British,1818 |1821,1895 |1889,1850s,1850,1859,Albumen silver print,Image: 28.1 x 23.4 cm (11 1/16 x 9 3/16 in.) Mount: 43.9 x 30 cm (17 5/16 x 11 13/16 in.),"David Hunter McAlpin Fund, 1952",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/291383,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -52.640 (12),false,true,291384,Photographs,Photograph,Easby Abbey. The Refectory,,,,,,Artist|Artist,,Joseph Cundall|Philip Henry Delamotte,"British, Norwich, Norfolk 1818–1895 Wallington, Surrey|British, 1821–1889",,"Cundall, Joseph|Delamotte, Philip Henry",British|British,1818 |1821,1895 |1889,1850s,1850,1859,Albumen silver print,Image: 28 x 23.5 cm (11 x 9 1/4 in.) Mount: 43.9 x 30 cm (17 5/16 x 11 13/16 in.),"David Hunter McAlpin Fund, 1952",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/291384,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -52.640 (13),false,true,291385,Photographs,Photograph,Rivaulx Abbey. General View from the South,,,,,,Artist|Artist,,Joseph Cundall|Philip Henry Delamotte,"British, Norwich, Norfolk 1818–1895 Wallington, Surrey|British, 1821–1889",,"Cundall, Joseph|Delamotte, Philip Henry",British|British,1818 |1821,1895 |1889,1850s,1850,1859,Albumen silver print,Image: 28 x 23.5 cm (11 x 9 1/4 in.) Mount: 43.9 x 30 cm (17 5/16 x 11 13/16 in.),"David Hunter McAlpin Fund, 1952",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/291385,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -52.640 (14),false,true,291386,Photographs,Photograph,Rivaulx Abbey. Interior of the Choir,,,,,,Artist|Artist,,Joseph Cundall|Philip Henry Delamotte,"British, Norwich, Norfolk 1818–1895 Wallington, Surrey|British, 1821–1889",,"Cundall, Joseph|Delamotte, Philip Henry",British|British,1818 |1821,1895 |1889,1850s,1850,1859,Albumen silver print,Image: 28.6 x 23.6 cm (11 1/4 x 9 5/16 in.) Mount: 43.9 x 30 cm (17 5/16 x 11 13/16 in.),"David Hunter McAlpin Fund, 1952",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/291386,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -52.640 (15),false,true,291387,Photographs,Photograph,Rivaulx Abbey. Looking Across the Choir,,,,,,Artist|Artist,,Joseph Cundall|Philip Henry Delamotte,"British, Norwich, Norfolk 1818–1895 Wallington, Surrey|British, 1821–1889",,"Cundall, Joseph|Delamotte, Philip Henry",British|British,1818 |1821,1895 |1889,1850s,1850,1859,Albumen silver print,Image: 28.1 x 23.5 cm (11 1/16 x 9 1/4 in.) Mount: 43.9 x 30 cm (17 5/16 x 11 13/16 in.),"David Hunter McAlpin Fund, 1952",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/291387,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -52.640 (16),false,true,291388,Photographs,Photograph,Rivaulx Abbey. The Triforium Arches,,,,,,Artist|Artist,,Joseph Cundall|Philip Henry Delamotte,"British, Norwich, Norfolk 1818–1895 Wallington, Surrey|British, 1821–1889",,"Cundall, Joseph|Delamotte, Philip Henry",British|British,1818 |1821,1895 |1889,1850s,1850,1859,Albumen silver print,Image: 28.1 x 23.4 cm (11 1/16 x 9 3/16 in.) Mount: 43.9 x 30 cm (17 5/16 x 11 13/16 in.),"David Hunter McAlpin Fund, 1952",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/291388,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -52.640 (17),false,true,291389,Photographs,Photograph,Rivaulx Abbey. Doorway of the Refectory,,,,,,Artist|Artist,,Joseph Cundall|Philip Henry Delamotte,"British, Norwich, Norfolk 1818–1895 Wallington, Surrey|British, 1821–1889",,"Cundall, Joseph|Delamotte, Philip Henry",British|British,1818 |1821,1895 |1889,1850s,1850,1859,Albumen silver print,Image: 28.1 x 23.4 cm (11 1/16 x 9 3/16 in.) Mount: 43.9 x 30 cm (17 5/16 x 11 13/16 in.),"David Hunter McAlpin Fund, 1952",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/291389,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -52.640 (18),false,true,291390,Photographs,Photograph,Kirkstall Abbey. From the West,,,,,,Artist|Artist,,Joseph Cundall|Philip Henry Delamotte,"British, Norwich, Norfolk 1818–1895 Wallington, Surrey|British, 1821–1889",,"Cundall, Joseph|Delamotte, Philip Henry",British|British,1818 |1821,1895 |1889,1850s,1850,1859,Albumen silver print,Image: 26.3 x 23.5 cm (10 3/8 x 9 1/4 in.) Mount: 43.9 x 30 cm (17 5/16 x 11 13/16 in.),"David Hunter McAlpin Fund, 1952",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/291390,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -52.640 (19),false,true,291391,Photographs,Photograph,Kirkstall Abbey. Ruins on the South Side,,,,,,Artist|Artist,,Joseph Cundall|Philip Henry Delamotte,"British, Norwich, Norfolk 1818–1895 Wallington, Surrey|British, 1821–1889",,"Cundall, Joseph|Delamotte, Philip Henry",British|British,1818 |1821,1895 |1889,1850s,1850,1859,Albumen silver print,Image: 28.1 x 23.4 cm (11 1/16 x 9 3/16 in.) Mount: 43.9 x 30 cm (17 5/16 x 11 13/16 in.),"David Hunter McAlpin Fund, 1952",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/291391,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -52.640 (20),false,true,291392,Photographs,Photograph,Kirkstall Abbey. Doorway on the North Side,,,,,,Artist|Artist,,Joseph Cundall|Philip Henry Delamotte,"British, Norwich, Norfolk 1818–1895 Wallington, Surrey|British, 1821–1889",,"Cundall, Joseph|Delamotte, Philip Henry",British|British,1818 |1821,1895 |1889,1850s,1850,1859,Albumen silver print,Image: 28.1 x 23.4 cm (11 1/16 x 9 3/16 in.) Mount: 43.9 x 30 cm (17 5/16 x 11 13/16 in.),"David Hunter McAlpin Fund, 1952",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/291392,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -52.640 (21),false,true,291393,Photographs,Photograph,Bolton Priory. From the South,,,,,,Artist|Artist,,Joseph Cundall|Philip Henry Delamotte,"British, Norwich, Norfolk 1818–1895 Wallington, Surrey|British, 1821–1889",,"Cundall, Joseph|Delamotte, Philip Henry",British|British,1818 |1821,1895 |1889,1850s,1850,1859,Albumen silver print,Image: 28.4 x 21.6 cm (11 3/16 x 8 1/2 in.) Mount: 43.9 x 30 cm (17 5/16 x 11 13/16 in.),"David Hunter McAlpin Fund, 1952",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/291393,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -52.640 (22),false,true,291394,Photographs,Photograph,Bolton Priory. The Stepping Stones,,,,,,Artist|Artist,,Joseph Cundall|Philip Henry Delamotte,"British, Norwich, Norfolk 1818–1895 Wallington, Surrey|British, 1821–1889",,"Cundall, Joseph|Delamotte, Philip Henry",British|British,1818 |1821,1895 |1889,1850s,1850,1859,Albumen silver print,Image: 28.3 x 24.1 cm (11 1/8 x 9 1/2 in.) Mount: 43.9 x 30 cm (17 5/16 x 11 13/16 in.),"David Hunter McAlpin Fund, 1952",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/291394,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1973.616,false,true,260988,Photographs,Photograph,The Chess Players,,,,,,Artist|Artist,Likely by|Possibly by,Antoine-François-Jean Claudet|Nicolaas Henneman,"French, active Great Britain, 1797–1867|Dutch, Heemskerk 1813–1898 London",,"Claudet, Antione-François-Jean|Henneman, Nicolaas","French, active Great Britain",1797 |1813,1867 |1898,ca. 1845,1840,1850,Salted paper print from paper negative,19.5 x 14.4 cm. (7 11/16 x 5 11/16 in.),"The Elisha Whittelsey Collection, The Elisha Whittelsey Fund, 1973",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260988,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.159.40,false,true,306323,Photographs,Photograph,The Chess Players,,,,,,Artist|Artist,Likely by|Possibly by,Antoine-François-Jean Claudet|Nicolaas Henneman,"French, active Great Britain, 1797–1867|Dutch, Heemskerk 1813–1898 London",,"Claudet, Antione-François-Jean|Henneman, Nicolaas","French, active Great Britain|Dutch",1797 |1813,1867 |1898,ca. 1845,1840,1850,Salted paper print from paper negative,Sheet: 9 5/8 × 7 11/16 in. (24.5 × 19.6 cm) Image: 7 13/16 × 5 13/16 in. (19.8 × 14.7 cm),"Bequest of Maurice B. Sendak, 2012",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306323,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2005.100.382 (4a, b)",false,true,287894,Photographs,Photograph,The Observatory; Thereza Llewelyn,,,,,,Artist|Artist,,Miss Bush|John Dillwyn Llewelyn,"British, active 19th century|British, Swansea, Wales 1810–1882 Swansea, Wales",,"Bush Miss|Llewelyn, John Dillwyn","British|British, Welsh",1810,1882,1853–56,1853,1856,Salted paper print,Image: 9.8 × 15.6 cm (9.8 × 15.6 cm) (a) Image: 12.8 × 10.6 cm (5 1/16 × 4 3/16 in.) (b),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287894,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1981.1158.3,false,true,263051,Photographs,Photograph,"White House Landing, Pamunkey River",,,,,,Artist|Artist,,Timothy H. O'Sullivan|Mathew B. Brady,"American, born Ireland, 1840–1882|American, born Ireland, 1823?–1896 New York",,"O'Sullivan, Timothy H.|Brady, Mathew B.","American, born Ireland|American",1840 |1823,1882 |1896,1864,1864,1864,Albumen silver print,,"Gift of Mrs. A. Hyatt Mayor, 1981",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/263051,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.607,false,true,283067,Photographs,Photograph,The Fruit Sellers,,,,,,Artist|Artist,Possibly by|Possibly by,Calvert Richard Jones|William Henry Fox Talbot,"British, Swansea, Wales 1802–1877 Bath, England|British, Dorset 1800–1877 Lacock",,"Jones, Calvert Richard|Talbot, William Henry Fox","British, Welsh|British",1802 |1800,1877 |1800,ca. 1845,1843,1847,Salted paper print from paper negative,17.1 x 21.1 cm (6 3/4 x 8 5/16 in. ),"Gilman Collection, Purchase, Harriette and Noel Levine Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/283067,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1973.637,false,true,261000,Photographs,Photograph,"""God"" by Baroness Elsa von Freytag-Loringhoven and Morton Schamberg",,,,,,Artist|Artist,,Morton Schamberg|Elsa von Freytag-Loringhoven,"American, Philadelphia, Pennsylvania 1881–1918 Philadelphia, Pennsylvania|German, 1874–1927",,"Schamberg, Morton|Freytag-Loringhoven, Elsa von",American|German,1881 |1874,1918 |1927,1917,1917,1917,Gelatin silver print,24.1 x 19.2 cm (9 1/2 x 7 9/16 in.),"Elisha Whittelsey Collection, Elisha Whittelsey Fund, 1973",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261000,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.80,false,true,283179,Photographs,Photograph,Frederick Langenheim,,,,,,Artist|Artist,,William Langenheim|Frederick Langenheim,"American, born Germany, Schöningen 1807–1874|American, born Germany, Schöningen 1809–1879",,"Langenheim, William|Langenheim, Frederick","American, born Germany|American, born Germany",1807 |1809,1874 |1879,ca. 1848–50,1846,1852,Daguerreotype,Image: 9 5/9 x 7 5/8; Frame: 18 x 15 x 1 3/8,"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/283179,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.400.38,false,true,291791,Photographs,Daguerreotype,"[Middle-aged Man with Chinstrap Beard, Hand Tucked Inside Buttoned Jacket]",,,,,,Artist|Artist,,William Langenheim|Frederick Langenheim,"American, born Germany, Schöningen 1807–1874|American, born Germany, Schöningen 1809–1879",,"Langenheim, William|Langenheim, Frederick","American, born Germany|American, born Germany",1807 |1809,1874 |1879,1840s–50s,1840,1859,Daguerreotype,Image: 12.1 x 8.8 cm (4 3/4 x 3 7/16 in.) Plate: 14 x 10.8 cm (5 1/2 x 4 1/4 in.) Case: 1.6 x 14.9 x 11.9 cm (5/8 x 5 7/8 x 4 11/16 in.),"Bequest of Herbert Mitchell, 2008",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/291791,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.400.56,false,true,291809,Photographs,Daguerreotype,[Seated Man in Floral Vest],,,,,,Artist|Artist,,William Langenheim|Frederick Langenheim,"American, born Germany, Schöningen 1807–1874|American, born Germany, Schöningen 1809–1879",,"Langenheim, William|Langenheim, Frederick","American, born Germany|American, born Germany",1807 |1809,1874 |1879,1840s–50s,1840,1859,Daguerreotype,Image: 6.8 x 5.6 cm (2 11/16 x 2 3/16 in.) Plate: 8.3 x 7 cm (3 1/4 x 2 3/4 in.) Case: 1.7 x 9 x 7.9 cm (11/16 x 3 9/16 x 3 1/8 in.),"Bequest of Herbert Mitchell, 2008",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/291809,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.177,false,true,286315,Photographs,Photograph,Frederick Langenheim Looking at Talbotypes,,,,,,Artist|Artist,,William Langenheim|Frederick Langenheim,"American, born Germany, Schöningen 1807–1874|American, born Germany, Schöningen 1809–1879",,"Langenheim, William|Langenheim, Frederick","American, born Germany|American, born Germany",1807 |1809,1874 |1879,ca. 1849–51,1849,1851,Daguerreotype,Image: 12.1 × 8.9 cm (4 3/4 × 3 1/2 in.) Case: 1.6 × 15.2 × 11.9 cm (5/8 in. × 6 in. × 4 11/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286315,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.614a–g,false,true,283180,Photographs,Photograph,Eclipse of the Sun,,,,,,Artist|Artist,,William Langenheim|Frederick Langenheim,"American, born Germany, Schöningen 1807–1874|American, born Germany, Schöningen 1809–1879",,"Langenheim, William|Langenheim, Frederick","American, born Germany|American, born Germany",1807 |1809,1874 |1879,1854,1854,1854,Daguerreotype,From 3.2 x 2.5 cm (1 1/4 x 1 in.) to 7.2 x 5.9 cm (2 13/16 x 2 5/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/283180,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.537.4,false,true,259606,Photographs,Carte-de-visite,Confederate Soldier [on the Battlefield at Antietam],,,,,,Artist|Publisher,,Alexander Gardner|Brady & Co.,"American, Glasgow, Scotland 1821–1882 Washington, D.C.|American, active 1840s–1880s",,"Gardner, Alexander|Brady & Co.","American, Scottish|American",1821 |1840,1882 |1889,September 1862,1862,1862,Albumen silver print from glass negative,Image: 6.1 x 9.8 cm (2 3/8 x 3 7/8 in.),"Purchase, Florance Waterbury Bequest, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/259606,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.395,false,true,682875,Photographs,Photograph,La Frayeur,,,,,,Artist|Artist|Person in Photograph,Person in photograph,Aquilin Schad|Pierre-Louis Pierson|Countess Virginia Oldoini Verasis di Castiglione,"Austrian, 1817–1866|French, 1822–1913|1835–1899",,"Schad, Aquilin|Pierson, Pierre-Louis|Castiglione, di, Virginia Oldoini Verasis Countess",,1817 |1822 |1835,1866 |1913 |1899,1861–64,1861,1864,Salted paper print with applied color,Image: 22 7/16 × 17 5/16 in. (57 × 44 cm) Mat: 29 1/2 × 23 5/16 in. (75 × 59.2 cm),"Purchase, The Camille M. Lownds Fund, Joyce F. Menschel Gift, Louis V. Bell and 2012 Benefit Funds, and C. Jay Moorhead Foundation Gift, 2015",,,,,,,,,,,,Photographs|Paintings,,http://www.metmuseum.org/art/collection/search/682875,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -46.122.6,false,true,269075,Photographs,Photograph,Maison au toit de chaume,,,,,,Artist|Printer,,"Unknown|Imprimerie photographique de Blanquart-Évrard, à Lille","French|French, active 1851–55",,"Unknown|Imprimerie photographique de Blanquart-Évrard, à Lille",,1851,1855,1850–53,1850,1853,Salted paper print (Blanquart-Évrard process) from paper negative,,"Harris Brisbane Dick Fund, 1946",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269075,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -46.122.7,false,true,269076,Photographs,Photograph,Nature morte au lièvre,,,,,,Artist|Printer,,"Unknown|Imprimerie photographique de Blanquart-Évrard, à Lille","French|French, active 1851–55",,"Unknown|Imprimerie photographique de Blanquart-Évrard, à Lille",,1851,1855,1853,1853,1853,Salted paper print (Blanquart-Évrard process) from paper negative,,"Harris Brisbane Dick Fund, 1946",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269076,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -46.122.8,false,true,269077,Photographs,Photograph,"Vue prise dans la vallée de Changy, aux environs de Fontainebleau",,,,,,Artist|Printer,,"Unknown|Imprimerie photographique de Blanquart-Évrard, à Lille","French|French, active 1851–55",,"Unknown|Imprimerie photographique de Blanquart-Évrard, à Lille",,1851,1855,1850–53,1850,1853,Salted paper print (Blanquart-Évrard process) from paper negative,,"Harris Brisbane Dick Fund, 1946",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269077,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -46.122.10,false,true,269058,Photographs,Photograph,Trois jeunes enfants assis autour d'un panier,,,,,,Artist|Printer,,"Unknown|Imprimerie photographique de Blanquart-Évrard, à Lille","French|French, active 1851–55",,"Unknown|Imprimerie photographique de Blanquart-Évrard, à Lille",,1851,1855,1850–53,1850,1853,Salted paper print (Blanquart-Évrard process) from paper negative,,"Harris Brisbane Dick Fund, 1946",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269058,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -46.122.11,false,true,269059,Photographs,Photograph,Sous-bois en automne,,,,,,Artist|Printer,,"Unknown|Imprimerie photographique de Blanquart-Évrard, à Lille","French|French, active 1851–55",,"Unknown|Imprimerie photographique de Blanquart-Évrard, à Lille",,1851,1855,1850–53,1850,1853,Salted paper print (Blanquart-Évrard process) from paper negative,,"Harris Brisbane Dick Fund, 1946",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269059,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -46.122.13,false,true,269061,Photographs,Photograph,Ane attaché à une charette,,,,,,Artist|Printer,,"Unknown|Imprimerie photographique de Blanquart-Évrard, à Lille","French|French, active 1851–55",,"Unknown|Imprimerie photographique de Blanquart-Évrard, à Lille",,1851,1855,1850–53,1850,1853,Salted paper print (Blanquart-Évrard process) from paper negative,,"Harris Brisbane Dick Fund, 1946",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269061,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -46.122.15,false,true,269063,Photographs,Photograph,Charette devant l'entrée d'un abri au toit de chaume,,,,,,Artist|Printer,,"Unknown|Imprimerie photographique de Blanquart-Évrard, à Lille","French|French, active 1851–55",,"Unknown|Imprimerie photographique de Blanquart-Évrard, à Lille",,1851,1855,1850–53,1850,1853,Salted paper print (Blanquart-Évrard process) from paper negative,17.9 x 22.4 cm (7 1/16 x 8 13/16 in.),"Harris Brisbane Dick Fund, 1946",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269063,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -46.122.19,false,true,269067,Photographs,Photograph,Ferme au toit de chaume,,,,,,Artist|Printer,,"Unknown|Imprimerie photographique de Blanquart-Évrard, à Lille","French|French, active 1851–55",,"Unknown|Imprimerie photographique de Blanquart-Évrard, à Lille",,1851,1855,1850–53,1850,1853,Salted paper print (Blanquart-Évrard process) from paper negative,,"Harris Brisbane Dick Fund, 1946",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269067,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.572,false,true,685911,Photographs,Carte-de-visite,[Adolph Menzel],,,,,,Photography Studio|Person in Photograph,Person in photograph,Photographische Gesellschaft|Adolph Menzel,"German, Breslau 1815–1905 Berlin",,"Photographische Gesellschaft|Menzel, Adolph",,1910 |1815,1910 |1905,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/685911,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.777.1,false,true,705423,Photographs,Photograph,"The Memnonium or Rameseiom, Thebes",,,,,,Publisher|Artist,,J. Hogarth|Robert Murray,"British, Edinburgh 1822–1893 Plymouth",,"Hogarth, J.|Murray, Robert",,1822,1893,"1852–55, printed 1854–56",1852,1855,Albumen silver print from waxed paper negative,Image: 6 7/8 in. × 9 in. (17.5 × 22.9 cm) Mount: 12 in. × 15 3/8 in. (30.5 × 39.1 cm),"Gift of Charles Isaacs and Carol Nigro, 2015",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/705423,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1981.1229.49,false,true,263189,Photographs,Photograph,"Gate of Ptolemy Philomeder, B.C. 180, Karnac",,,,,,Publisher|Artist,Attributed to,J. Hogarth|Robert Murray,"British, Edinburgh 1822–1893 Plymouth",,"Hogarth, J.|Murray, Robert",,1822,1893,ca. 1856,1856,1856,Albumen silver print,Image: 9 1/8 × 7 3/8 in. (23.2 × 18.7 cm) Mount: 16 in. × 12 3/16 in. (40.6 × 31 cm),"Robert O. Dougan Collection, Gift of Warner Communications Inc., 1981",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/263189,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1981.1229.50,false,true,263191,Photographs,Photograph,"Nubian Sakkieh, or Water Wheel",,,,,,Publisher|Artist,Attributed to,J. Hogarth|Robert Murray,"British, Edinburgh 1822–1893 Plymouth",,"Hogarth, J.|Murray, Robert",,1822,1893,ca. 1856,1856,1856,Albumen silver print,Image: 6 3/4 × 8 3/16 in. (17.1 × 20.7 cm) Mount: 12 3/16 in. × 15 15/16 in. (31 × 40.5 cm),"Robert O. Dougan Collection, Gift of Warner Communications Inc., 1981",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/263191,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.778.34,false,true,700110,Photographs,Photograph,"Nubie, Grand Temple d'Isis, a Philœ, Galerie Orientale",,,,,,Publisher|Artist|Printer,,"Gide et Baudry|Maxime Du Camp|Imprimerie photographique de Blanquart-Évrard, à Lille","French, 1822–1894|French, active 1851–55",,"Gide et Baudry|Du Camp, Maxime|Imprimerie photographique de Blanquart-Évrard, à Lille",,1822 |1851,1894 |1855,"April 1850, printed 1852",1850,1850,Salted paper print from paper negative,Image: 6 9/16 × 8 9/16 in. (16.7 × 21.8 cm) Mount: 12 7/16 × 17 5/8 in. (31.6 × 44.7 cm),"Gift of Joyce F. Menschel, 2015",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/700110,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.158,false,true,267944,Photographs,Photograph,"Dead Confederate Soldier at Fort Mahone, Petersburg",,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",,1823,1896,1864,1864,1864,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267944,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.173,false,true,685515,Photographs,Carte-de-visite,[Konstantin Cretius],,,,,,Person in Photograph|Photographer,Person in photograph,Konstantin Johannes Franz Cretius|Ernst Milster,"German, Brieg 1814–1901 Berlin|German, born 1835",,"Cretius, Konstantin Johannes Franz|Milster, Ernst",,1814 |1835,1901,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/685515,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.395,false,true,268206,Photographs,Photograph,"Gettysburg, Pennsylvania",,,,,,Artist|Former Attribution,Formerly attributed to,Unknown|Mathew B. Brady,"American|American, born Ireland, 1823?–1896 New York",,"Unknown|Brady, Mathew B.",,1823,1896,1863,1863,1863,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268206,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.778.38,false,true,700114,Photographs,Photograph,"Head of Cañyon de Chelle, Looking Down",,,,,,Patron|Artist,Commissioned by,Lieutenant George Montague Wheeler|Timothy H. O'Sullivan,"American, 1842–1905|American, born Ireland, 1840–1882",,"Wheeler, Lieutenant George Montague|O'Sullivan, Timothy H.",,1842 |1840,1905 |1882,1873,1873,1873,Albumen silver print,Image: 8 1/16 × 10 13/16 in. (20.5 × 27.5 cm),"Gift of Joyce F. Menschel, 2015",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/700114,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.400.659–.687,false,true,692531,Photographs,,[29 Glass Stereographs],,,,,,Artist|Artist|Artist|Artist,,Bierstadt Brothers|Various|Franklin White|James McPherson,"American, active 1860s–80s|American, active 1850s–60s|American, active 1860s",,"Bierstadt Brothers|Various|White, Franklin|McPherson, James",,1850 |1850 |1860,1900 |1869 |1869,1850s–1900s,1850,1909,Glass stereographs,Each photograph approx. 3 1/2 in. × 7 in. (8.9 × 17.8 cm),"Bequest of Herbert Mitchell, 2008",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/692531,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.778.37,false,true,700113,Photographs,Photograph,"Perched Rock, Rocker Creek, Arizona",,,,,,Patron|Artist,Commissioned by,Lieutenant George Montague Wheeler|William Bell,"American, 1842–1905|American (born England) Liverpool 1831–1910 Philadelphia, Pennsylvania",,"Wheeler, Lieutenant George Montague|Bell, William",,1842 |1831,1905 |1910,1872,1872,1872,Albumen silver print from glass negative,Image: 10 7/8 × 8 1/16 in. (27.6 × 20.5 cm) Sheet: 21 7/16 in. × 16 in. (54.4 × 40.6 cm),"Gift of Joyce F. Menschel, 2015",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/700113,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.400.688–.897,false,true,692523,Photographs,,[210 Stereographs],,,,,,Publisher|Artist|Artist|Artist,,New York Stereoscopic Company|Frederick Langenheim|William Langenheim|C. G. Hill,"American|American, born Germany, Schöningen 1809–1879|American, born Germany, Schöningen 1807–1874|American, active 1860s–80s",,"New York Stereoscopic Company|Langenheim, Frederick|Langenheim, William|Hill, C. G.",,1809 |1807 |1860,1879 |1874 |1899,1850s–1890s,1850,1899,Albumen silver prints,Each photograph approx. 3 1/2 in. × 7 in. (8.9 × 17.8 cm),"Bequest of Herbert Mitchell, 2008",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/692523,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1996.438.2,false,true,294506,Photographs,Album,[Personal Travel Album Made by the Dowager Empress Maria Feoderovna Showing Events in the Daily Life of the Russian Imperial Family],,,,,,Artist,,Dowager Empress Maria Feodorovna,"Russian, born Denmark, Copenhagen 1847–1928 Copenhagen",,"Feodorovna, Maria, Dowager Empress",,1847,1928,1916,1916,1916,Gelatin silver prints; photomechanical prints,29.5 x 42.2 x 5.7cm (11 5/8 x 16 5/8 x 2 1/4in.) each,"Gift of Prince and Princess Alexander Romanoff, 1996",,,,,,,,,,,,Albums|Prints,,http://www.metmuseum.org/art/collection/search/294506,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.533,false,true,685873,Photographs,Carte-de-visite,[Ludwig von Löfftz],,,,,,Artist,,Franz Werner,active 1860s,,"Werner, Franz",,1859,1870,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/685873,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.678,false,true,686017,Photographs,Carte-de-visite,[Riefstahl ?],,,,,,Artist,,Carl Wigand,active 1860s,,"Wigand, Carl",,1859,1870,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/686017,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2016.91,false,true,702990,Photographs,Photograph,55. Athènes. Acropole. Ruines et 1ers plans (pour tableau),,,,,,Artist,,Joseph-Philibert Girault de Prangey,"French, 1804–1892",,"Girault, de Prangey Joseph-Philibert",,1804,1892,1842,1842,1842,Daguerreotype,Image: 3 3/4 × 9 7/16 in. (9.5 × 24 cm),"Purchase, Philippe de Montebello Fund, Mr. and Mrs. John A. Moran Gift, in memory of Louise Chisholm Moran, Joyce F. Menschel and Annette de la Renta Gifts, and funds from various donors, 2016",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/702990,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2016.92,false,true,702991,Photographs,Photograph,49. Athènes. Acropole. Côté O.,,,,,,Artist,,Joseph-Philibert Girault de Prangey,"French, 1804–1892",,"Girault, de Prangey Joseph-Philibert",,1804,1892,1842,1842,1842,Daguerreotype,Image: 3 3/4 × 9 7/16 in. (9.5 × 24 cm),"Purchase, Philippe de Montebello Fund, Mr. and Mrs. John A. Moran Gift, in memory of Louise Chisholm Moran, Joyce F. Menschel and Annette de la Renta Gifts, and funds from various donors, 2016",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/702991,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2016.93,false,true,702992,Photographs,Photograph,62. Athènes. Temple de Minerve Poliade.,,,,,,Artist,,Joseph-Philibert Girault de Prangey,"French, 1804–1892",,"Girault, de Prangey Joseph-Philibert",,1804,1892,1842,1842,1842,Daguerreotype,Image: 3 3/4 × 9 7/16 in. (9.5 × 24 cm),"Purchase, Philippe de Montebello Fund, Mr. and Mrs. John A. Moran Gift, in memory of Louise Chisholm Moran, Joyce F. Menschel and Annette de la Renta Gifts, and funds from various donors, 2016",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/702992,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2016.94,false,true,702993,Photographs,Photograph,56. Athènes. Caryatides. Ereckt,,,,,,Artist,,Joseph-Philibert Girault de Prangey,"French, 1804–1892",,"Girault, de Prangey Joseph-Philibert",,1804,1892,1842,1842,1842,Daguerreotype,Image: 9 7/16 × 3 3/4 in. (24 × 9.5 cm),"Purchase, Philippe de Montebello Fund, Mr. and Mrs. John A. Moran Gift, in memory of Louise Chisholm Moran, Joyce F. Menschel and Annette de la Renta Gifts, and funds from various donors, 2016",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/702993,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2016.95,false,true,702994,Photographs,Photograph,53. Athènes. Temple de Bacchus,,,,,,Artist,,Joseph-Philibert Girault de Prangey,"French, 1804–1892",,"Girault, de Prangey Joseph-Philibert",,1804,1892,1842,1842,1842,Daguerreotype,Image: 9 7/16 × 3 3/4 in. (24 × 9.5 cm),"Purchase, Philippe de Montebello Fund, Mr. and Mrs. John A. Moran Gift, in memory of Louise Chisholm Moran, Joyce F. Menschel and Annette de la Renta Gifts, and funds from various donors, 2016",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/702994,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2016.96,false,true,702995,Photographs,Photograph,73. Alexandrie. Grand Minaret,,,,,,Artist,,Joseph-Philibert Girault de Prangey,"French, 1804–1892",,"Girault, de Prangey Joseph-Philibert",,1804,1892,1842,1842,1842,Daguerreotype,Image: 9 7/16 × 3 3/4 in. (24 × 9.5 cm),"Purchase, Philippe de Montebello Fund, Mr. and Mrs. John A. Moran Gift, in memory of Louise Chisholm Moran, Joyce F. Menschel and Annette de la Renta Gifts, and funds from various donors, 2016",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/702995,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2016.97,false,true,702996,Photographs,Photograph,74. Près d'Alexandrie. Le désert.,,,,,,Artist,,Joseph-Philibert Girault de Prangey,"French, 1804–1892",,"Girault, de Prangey Joseph-Philibert",,1804,1892,1842,1842,1842,Daguerreotype,Image: 3 3/4 × 9 7/16 in. (9.5 × 24 cm),"Purchase, Philippe de Montebello Fund, Mr. and Mrs. John A. Moran Gift, in memory of Louise Chisholm Moran, Joyce F. Menschel and Annette de la Renta Gifts, and funds from various donors, 2016",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/702996,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2016.98,false,true,702997,Photographs,Photograph,68. Alexandrie. Colonne de Pompée,,,,,,Artist,,Joseph-Philibert Girault de Prangey,"French, 1804–1892",,"Girault, de Prangey Joseph-Philibert",,1804,1892,1842,1842,1842,Daguerreotype,Image: 9 7/16 × 3 3/4 in. (24 × 9.5 cm),"Purchase, Philippe de Montebello Fund, Mr. and Mrs. John A. Moran Gift, in memory of Louise Chisholm Moran, Joyce F. Menschel and Annette de la Renta Gifts, and funds from various donors, 2016",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/702997,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2016.600,false,true,726471,Photographs,Photograph,[Egypt],,,,,,Artist,,Joseph-Philibert Girault de Prangey,"French, 1804–1892",,"Girault, de Prangey Joseph-Philibert",,1804,1892,1842–44,1842,1844,Daguerreotype,Image: 3 3/4 × 9 7/16 in. (9.5 × 24 cm),"Purchase, Mr. and Mrs. John A. Moran Gift, in memory of Louise Chisholm Moran, Joyce F. Menschel Gift, Joseph Pulitzer Bequest, 2016 Benefit Fund, and Gift of Dr. Mortimer D. Sackler, Theresa Sackler and Family, 2016",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/726471,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2016.601,false,true,726472,Photographs,Photograph,"Phile, temple découvert.",,,,,,Artist,,Joseph-Philibert Girault de Prangey,"French, 1804–1892",,"Girault, de Prangey Joseph-Philibert",,1804,1892,1844,1844,1844,Daguerreotype,Image: 3 1/8 × 3 3/4 in. (8 × 9.5 cm),"Purchase, Mr. and Mrs. John A. Moran Gift, in memory of Louise Chisholm Moran, Joyce F. Menschel Gift, Joseph Pulitzer Bequest, 2016 Benefit Fund, and Gift of Dr. Mortimer D. Sackler, Theresa Sackler and Family, 2016",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/726472,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2016.602,false,true,726473,Photographs,Photograph,[Egypt],,,,,,Artist,,Joseph-Philibert Girault de Prangey,"French, 1804–1892",,"Girault, de Prangey Joseph-Philibert",,1804,1892,1842–44,1842,1844,Daguerreotype,Image: 3 3/4 × 4 3/4 in. (9.5 × 12 cm),"Purchase, Mr. and Mrs. John A. Moran Gift, in memory of Louise Chisholm Moran, Joyce F. Menschel Gift, Joseph Pulitzer Bequest, 2016 Benefit Fund, and Gift of Dr. Mortimer D. Sackler, Theresa Sackler and Family, 2016",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/726473,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2016.603,false,true,726474,Photographs,Photograph,"Kaire. Gama Soultan Ansoun, détails.",,,,,,Artist,,Joseph-Philibert Girault de Prangey,"French, 1804–1892",,"Girault, de Prangey Joseph-Philibert",,1804,1892,1842–44,1842,1844,Daguerreotype,Image: 9 7/16 × 3 3/4 in. (24 × 9.5 cm),"Purchase, Mr. and Mrs. John A. Moran Gift, in memory of Louise Chisholm Moran, Joyce F. Menschel Gift, Joseph Pulitzer Bequest, 2016 Benefit Fund, and Gift of Dr. Mortimer D. Sackler, Theresa Sackler and Family, 2016",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/726474,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2016.604,false,true,726475,Photographs,Photograph,Thebes Rhamseion,,,,,,Artist,,Joseph-Philibert Girault de Prangey,"French, 1804–1892",,"Girault, de Prangey Joseph-Philibert",,1804,1892,1844,1844,1844,Daguerreotype,Image: 7 1/2 × 9 7/16 in. (19 × 24 cm),"Purchase, Mr. and Mrs. John A. Moran Gift, in memory of Louise Chisholm Moran, Joyce F. Menschel Gift, Joseph Pulitzer Bequest, 2016 Benefit Fund, and Gift of Dr. Mortimer D. Sackler, Theresa Sackler and Family, 2016",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/726475,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2016.605,false,true,726476,Photographs,Photograph,Rome. Ponte Rotto,,,,,,Artist,,Joseph-Philibert Girault de Prangey,"French, 1804–1892",,"Girault, de Prangey Joseph-Philibert",,1804,1892,1842,1842,1842,Daguerreotype,Image: 3 3/4 × 9 7/16 in. (9.5 × 24 cm),"Purchase, Mr. and Mrs. John A. Moran Gift, in memory of Louise Chisholm Moran, Joyce F. Menschel Gift, Joseph Pulitzer Bequest, 2016 Benefit Fund, and Gift of Dr. Mortimer D. Sackler, Theresa Sackler and Family, 2016",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/726476,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2016.606,false,true,726477,Photographs,Photograph,Rome. Graecostato r fac. N. O.,,,,,,Artist,,Joseph-Philibert Girault de Prangey,"French, 1804–1892",,"Girault, de Prangey Joseph-Philibert",,1804,1892,1842,1842,1842,Daguerreotype,Image: 9 7/16 × 3 3/4 in. (24 × 9.5 cm),"Purchase, Mr. and Mrs. John A. Moran Gift, in memory of Louise Chisholm Moran, Joyce F. Menschel Gift, Joseph Pulitzer Bequest, 2016 Benefit Fund, and Gift of Dr. Mortimer D. Sackler, Theresa Sackler and Family, 2016",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/726477,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2016.607,false,true,726478,Photographs,Photograph,"Toscanella Eglise de S. Pietro, apside",,,,,,Artist,,Joseph-Philibert Girault de Prangey,"French, 1804–1892",,"Girault, de Prangey Joseph-Philibert",,1804,1892,1842,1842,1842,Daguerreotype,Image: 3 3/4 × 3 1/8 in. (9.5 × 8 cm),"Purchase, Mr. and Mrs. John A. Moran Gift, in memory of Louise Chisholm Moran, Joyce F. Menschel Gift, Joseph Pulitzer Bequest, 2016 Benefit Fund, and Gift of Dr. Mortimer D. Sackler, Theresa Sackler and Family, 2016",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/726478,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2016.608,false,true,726479,Photographs,Photograph,[Jerusalem],,,,,,Artist,,Joseph-Philibert Girault de Prangey,"French, 1804–1892",,"Girault, de Prangey Joseph-Philibert",,1804,1892,1842–44,1842,1844,Daguerreotype,Image: 3 3/4 × 9 7/16 in. (9.5 × 24 cm),"Purchase, Mr. and Mrs. John A. Moran Gift, in memory of Louise Chisholm Moran, Joyce F. Menschel Gift, Joseph Pulitzer Bequest, 2016 Benefit Fund, and Gift of Dr. Mortimer D. Sackler, Theresa Sackler and Family, 2016",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/726479,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2016.609,false,true,726480,Photographs,Photograph,"Jerusalem, près de la porte de Jaffa, chap.",,,,,,Artist,,Joseph-Philibert Girault de Prangey,"French, 1804–1892",,"Girault, de Prangey Joseph-Philibert",,1804,1892,1842–44,1842,1844,Daguerreotype,Image: 4 3/4 × 3 3/4 in. (12 × 9.5 cm),"Purchase, Mr. and Mrs. John A. Moran Gift, in memory of Louise Chisholm Moran, Joyce F. Menschel Gift, Joseph Pulitzer Bequest, 2016 Benefit Fund, and Gift of Dr. Mortimer D. Sackler, Theresa Sackler and Family, 2016",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/726480,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2016.610,false,true,726481,Photographs,Photograph,"Atlit Syrie, Chapelle [Damascus Gate, Jerusalem]",,,,,,Artist,,Joseph-Philibert Girault de Prangey,"French, 1804–1892",,"Girault, de Prangey Joseph-Philibert",,1804,1892,1842–44,1842,1844,Daguerreotype,Image: 9 7/16 × 7 1/2 in. (24 × 19 cm),"Purchase, Mr. and Mrs. John A. Moran Gift, in memory of Louise Chisholm Moran, Joyce F. Menschel Gift, Joseph Pulitzer Bequest, 2016 Benefit Fund, and Gift of Dr. Mortimer D. Sackler, Theresa Sackler and Family, 2016",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/726481,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2016.611,false,true,726482,Photographs,Photograph,"[Baalbek, Syria]",,,,,,Artist,,Joseph-Philibert Girault de Prangey,"French, 1804–1892",,"Girault, de Prangey Joseph-Philibert",,1804,1892,1842–44,1842,1844,Daguerreotype,Image: 3 3/4 × 9 7/16 in. (9.5 × 24 cm),"Purchase, Mr. and Mrs. John A. Moran Gift, in memory of Louise Chisholm Moran, Joyce F. Menschel Gift, Joseph Pulitzer Bequest, 2016 Benefit Fund, and Gift of Dr. Mortimer D. Sackler, Theresa Sackler and Family, 2016",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/726482,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2016.612,false,true,726483,Photographs,Photograph,Alep. Prise de Bab Antakieh (publiée),,,,,,Artist,,Joseph-Philibert Girault de Prangey,"French, 1804–1892",,"Girault, de Prangey Joseph-Philibert",,1804,1892,1842–44,1842,1844,Daguerreotype,Image: 7 1/2 × 9 7/16 in. (19 × 24 cm),"Purchase, Mr. and Mrs. John A. Moran Gift, in memory of Louise Chisholm Moran, Joyce F. Menschel Gift, Joseph Pulitzer Bequest, 2016 Benefit Fund, and Gift of Dr. Mortimer D. Sackler, Theresa Sackler and Family, 2016",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/726483,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2016.613,false,true,726484,Photographs,Photograph,Paris devant l’atelier de Daguerre,,,,,,Artist,,Joseph-Philibert Girault de Prangey,"French, 1804–1892",,"Girault, de Prangey Joseph-Philibert",,1804,1892,1841,1841,1841,Daguerreotype,Image: 3 3/4 × 4 3/4 in. (9.5 × 12 cm),"Purchase, Mr. and Mrs. John A. Moran Gift, in memory of Louise Chisholm Moran, Joyce F. Menschel Gift, Joseph Pulitzer Bequest, 2016 Benefit Fund, and Gift of Dr. Mortimer D. Sackler, Theresa Sackler and Family, 2016",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/726484,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2016.614,false,true,726485,Photographs,Photograph,"[Notre Dame Cathedral, Rose Window, North Transept]",,,,,,Artist,,Joseph-Philibert Girault de Prangey,"French, 1804–1892",,"Girault, de Prangey Joseph-Philibert",,1804,1892,1841,1841,1841,Daguerreotype,Image: 9 7/16 × 7 1/2 in. (24 × 19 cm),"Purchase, Mr. and Mrs. John A. Moran Gift, in memory of Louise Chisholm Moran, Joyce F. Menschel Gift, Joseph Pulitzer Bequest, 2016 Benefit Fund, and Gift of Dr. Mortimer D. Sackler, Theresa Sackler and Family, 2016",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/726485,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2016.615,false,true,726486,Photographs,Photograph,Sardes. T. de Cybèle,,,,,,Artist,,Joseph-Philibert Girault de Prangey,"French, 1804–1892",,"Girault, de Prangey Joseph-Philibert",,1804,1892,1843,1843,1843,Daguerreotype,Image: 7 1/2 × 9 7/16 in. (19 × 24 cm),"Purchase, Mr. and Mrs. John A. Moran Gift, in memory of Louise Chisholm Moran, Joyce F. Menschel Gift, Joseph Pulitzer Bequest, 2016 Benefit Fund, and Gift of Dr. Mortimer D. Sackler, Theresa Sackler and Family, 2016",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/726486,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2016.459,false,true,715974,Photographs,Photograph; Daguerreotype,[Profile of a woman with necrosis of the nose],,,,,,Artist,,Louis-Auguste Bisson,"French, 1814–1876",,"Bisson, Louis-Auguste",,1814,1876,1841–48,1841,1848,Daguerreotype,Image: 4 9/16 × 3 1/8 in. (11.6 × 7.9 cm) Plate: 7 3/16 × 5 7/8 in. (18.3 × 14.9 cm),"Funds from various donors, 2016",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/715974,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2012.352 (3),false,true,701977,Photographs,Photographs,"Assassinat des généraux Clément Tomas et Jules Lecomte, rue des Rosiers 6 à Montmartre deans la journée du 18 mars 1871",,,,,,Artist,,Ernest Eugène Appert,"French, 1831–1891",,"Appert, Ernest Eugène",,1831,1891,1870–71,1870,1871,Albumen silver print from glass negative,Sheet: 36 x 46 cm (14 3/16 x 18 1/8 in.),"Joyce F. Menschel Photography Library Fund, 2012",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/701977,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2012.352 (7),false,true,701997,Photographs,Photographs,"Massacre des dominicains d'Arcueil, route d'Italie no. 38, le 25 mai 1871, à 4 heures et demie",,,,,,Artist,,Ernest Eugène Appert,"French, 1831–1891",,"Appert, Ernest Eugène",,1831,1891,1870–71,1870,1871,Albumen silver print from glass negative,Sheet: 36 x 46 cm (14 3/16 x 18 1/8 in.),"Joyce F. Menschel Photography Library Fund, 2012",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/701997,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2012.352 (10),false,true,702000,Photographs,Photographs,"Exécution des otages, prison de la Roquette, le 24 mai 1871",,,,,,Artist,,Ernest Eugène Appert,"French, 1831–1891",,"Appert, Ernest Eugène",,1831,1891,1870–71,1870,1871,Albumen silver print from glass negative,Sheet: 36 x 46 cm (14 3/16 x 18 1/8 in.),"Joyce F. Menschel Photography Library Fund, 2012",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/702000,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2012.352 (11),false,true,702001,Photographs,Photographs,"Prison des Chantiers, le 15 août 1871, Versailles",,,,,,Artist,,Ernest Eugène Appert,"French, 1831–1891",,"Appert, Ernest Eugène",,1831,1891,1870–71,1870,1871,Albumen silver print from glass negative,Sheet: 36 x 46 cm (14 3/16 x 18 1/8 in.),"Joyce F. Menschel Photography Library Fund, 2012",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/702001,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2016.6,false,true,705511,Photographs,Photograph,"Notation of Scars, Schematic Drawings",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,ca. 1893,1888,1898,Albumen silver prints,Image (top): 6 in. × 4 1/8 in. (15.2 × 10.4 cm) Image (bottom): 2 3/16 × 4 1/8 in. (5.6 × 10.5 cm) Mount: 11 3/4 × 7 3/4 in. (29.8 × 19.7 cm),"The Horace W. Goldsmith Foundation Fund, Through Joyce and Robert Menschel, 2016",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/705511,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2016.7,false,true,705529,Photographs,Photograph,Measurement of Left Middle Finger,,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,ca. 1893,1888,1898,Albumen silver prints,Image (top): 5 7/8 × 4 7/16 in. (15 × 11.2 cm) Image (bottom): 2 7/8 × 4 3/8 in. (7.3 × 11.1 cm) Mount: 11 3/4 × 7 3/4 in. (29.8 × 19.7 cm),"The Horace W. Goldsmith Foundation Fund, Through Joyce and Robert Menschel, 2016",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/705529,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.1,false,true,306624,Photographs,Mugshot,"Adnet. Clotilde. 19 ans, née en décembre 74 à Argentant (Orne). Brodeuse. Anarchiste. Fichée le 7/1/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306624,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.2,false,true,306625,Photographs,Mugshot,"Adnet. Jeanne, Marie. Alphonsine (femme Quesnel). 22 ans, née à Argentan. Couturière. Anarchiste. 8/1/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306625,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.3,false,true,306626,Photographs,Mugshot,"Olguéni Gustave. 24 ans, né à Sala (Suède) le 24-5-69. Artiste-peintre. Anarchiste. 14-3-94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306626,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.4,false,true,306627,Photographs,Mugshot,"Alban. Jean-Louis. 35 ans, né à Paris. Plombier. Anarchiste. Fiché le 5/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306627,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.5,false,true,306628,Photographs,Mugshot,"Alicante. Philibert. 33 ans, né à Seire (Seine & Oise). Coupeur de talons. Anarchiste. 7/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306628,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.6,false,true,306629,Photographs,Mugshot,"Anacléto. Joseph, Jean-Baptiste. 36 ans, né le 14/7/57. Coiffeur. Anarchiste. 16/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306629,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.7,false,true,306630,Photographs,Mugshot,"Anceau. Aimé-Firmin. 20 ans, né le 18/2/74 à Paris XIIe. Sculpteur sur bois. Anarchiste. 17/7/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306630,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.8,false,true,306631,Photographs,Mugshot,"Arnaud. Eugène. 47 ans, né à Villeveyrac (Hérault). Ferblantier. Anarchiste. 20/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306631,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.9,false,true,306632,Photographs,Mugshot,"Augendre. Ernest. 37 ans, né à St-Pierre le Moutier (Nièvre). Maçon. Anarchiste. 1/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306632,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.10,false,true,306633,Photographs,Mugshot,"Aumaréchal. Auguste. 44 ans, né à Chateaumeillant (Cher). Ébéniste. Association de malfaiteurs. 8/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306633,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.11,false,true,306634,Photographs,Mugshot,"Auvin. Henri. 37 ans, né à St-Meme (Charente-Inférieure). Chaudronnier. Anarchiste. 1/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306634,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.12,false,true,306635,Photographs,Mugshot,"Baben. Hyppolyte, Antoine. 49 ans, né à St Sermain (Aveyron). Serrurier. Anarchiste. 9/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306635,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.13,false,true,306636,Photographs,Mugshot,"Baerisvuyl. Frédéric, Jean. 28 ans, né à Fribourg (Suisse). Ébéniste. Anarchiste. 8/1/93.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1893,1893,1893,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306636,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.14,false,true,306637,Photographs,Mugshot,"Baerisvuyl. Frédéric, Jean. 28 ans, né à Fribourg (Suisse). Ébéniste. Anarchiste. 8/1/93.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1893,1893,1893,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306637,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.15,false,true,306638,Photographs,Mugshot,"Barbichon. Jacques, Émile. 62 ans, né à Provins. Marchand de mouron. Anarchiste. 9/3/91.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1891,1891,1891,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306638,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.16,false,true,306639,Photographs,Mugshot,"Barbier. Émile, Alphonse. 36 ans, né à Paris. Peintre en bâtiment. Anarchiste. 26/2/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306639,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.17,false,true,306640,Photographs,Mugshot,"Barbier. Louis, Alexandre. 31 ans, né à Jussecourt (Marne). Comptable. Anarchiste. 27/2/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306640,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.18,false,true,306641,Photographs,Mugshot,"Barreyre. Alfred. 30 ans, né le 30/6/64 à Brassac (P. de Dôme). Gérant de restaurant. Anarchiste. 2/7/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306641,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.19,false,true,306642,Photographs,Mugshot,"Bassille. Maurice, Eugène. 19 ans, né à Paris Ille. Portefeuilliste. Anarchiste. 9/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306642,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.20,false,true,306643,Photographs,Mugshot,"Bastard. Élisée, Joseph, Michel. 22 ans, né à Birnel (Oise). Polisseur. Anarchiste. 20/8/93.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1893,1893,1893,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306643,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.21,false,true,306644,Photographs,Mugshot,"Bastard. Élisée, Joseph, Michel. 22 ans, né à Birnel (Oise). Polisseur. Anarchiste. 20/8/93.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1893,1893,1893,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306644,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.22,false,true,306645,Photographs,Mugshot,"Barla. Jean Michel. 46 ans, né à Sirié (Italie). Mécanicien. Anarchiste. 2/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306645,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.23,false,true,306646,Photographs,Mugshot,"Baudart. Joseph, Philippe. 42 ans, né à Reims le 25/3/51. Boucher. Anarchiste. 15/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306646,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.24,false,true,306647,Photographs,Mugshot,"Baumester. Augustin, Etienne. 49 ans, né le 16/1/45 à Paris VIe. Décorateur. Anarchiste. 2/7/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306647,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.25,false,true,306648,Photographs,Mugshot,"Baur. Pierre. 41 ans, né à St Leonard (Haute-Vienne). Cordonnier. Anarchiste. 1/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306648,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.26,false,true,306649,Photographs,Mugshot,"Bazin. Claudius dit César. 36 ans, né à Chatillon (Ain). Mécanicien. Anarchiste. 11/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306649,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.27,false,true,306650,Photographs,Mugshot,"Beaufort. Gilbert. 32 ans, né à Paris Xle. Ébéniste. Anarchiste. 5/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306650,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.28,false,true,306651,Photographs,Mugshot,"Beaulieu. Henri, Félix, Camille. 23 ans, né le 30/11/70 à Paris Ve. Comptable. Anarchiste. 23/5/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306651,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.29,false,true,306652,Photographs,Mugshot,"Beaulieu. Henri, Félix, Camille. 23 ans, né le 30/11/70 à Paris Ve. Comptable. Anarchiste. 23/5/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306652,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.30,false,true,306653,Photographs,Mugshot,"Becu. Lucien. 27 ans, né à la Conté d'Abigny (Pas-de-Calais). Garçon de café. Pas de motif. 22/2/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306653,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.31,false,true,306654,Photographs,Mugshot,"Bedei. Hercule. 21 ans, né à Sorli (Italie). Tailleur d'habits. Anarchiste. 1/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306654,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.32,false,true,306655,Photographs,Mugshot,"Bellemans. Eugène (ou Michel). 23 ans, né à Gand (Belgique). Tailleur d'habits. Anarchiste. 9/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306655,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.33,false,true,306656,Photographs,Mugshot,"Bellet. Eléonore. Alexandre. 29 ans, né le 23/9/65 à Salouel (Somme). Teinturier. Anarchiste. 3/7/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306656,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.34,false,true,306657,Photographs,Mugshot,"Bellon. Joseph, Alexandre. 54 ans, né à Granville (Meuse) le 19/6/39. Journalier. Anarchiste. 17/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306657,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.35,false,true,306658,Photographs,Mugshot,"Belloti. Louis. 28 ans, né à Turin. Camelot. Anarchiste. 18/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306658,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.36,false,true,306659,Photographs,Mugshot,"Benoit. Antoine. 29 ans, né à Paris Xle. Journalier. Anarchiste, vagabondage. 4/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306659,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.37,false,true,306660,Photographs,Mugshot,"Benoit. Joseph, Alexandre. 33 ans, né le 9/6/61 à Paris XIIIe. Potier d'étain. Anarchiste. 2/7/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306660,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.38,false,true,306661,Photographs,Mugshot,"Berard. Adolphe. 52 ans, né le 26/9/41 à Paris Ve. Ébéniste. Anarchiste. 16/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306661,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.39,false,true,306662,Photographs,Mugshot,"Bernaix. Louis. 29 ans, né à Clichy. Couvreur. Anarchiste. Fiché le 27/2/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306662,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.40,false,true,306663,Photographs,Mugshot,"Bernard. Paul, Auguste. 32 ans, né à Crest (Drôme). Employé. Excitation au meurtre, anarchiste. 11/2/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306663,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.41,false,true,306664,Photographs,Mugshot,"Bernard. Paul, Auguste. 32 ans, né à Crest (Drôme). Employé. Excitation au meurtre, anarchiste. 11/2/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306664,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.42,false,true,306665,Photographs,Mugshot,"Ber(h)nard. Victor. 43 ans, né à Paris. Coupeur. Anarchiste. Fiché le 28/2/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306665,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.43,false,true,306666,Photographs,Mugshot,"Berson. Samuel. 28 ans, né le 3/?/65 à Dinabourg (Russie). Tailleur d'habits. Anarchiste. 2/7/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306666,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.44,false,true,306667,Photographs,Mugshot,"Bertani. Orsini. 24 ou 25 ans, né à Florence (Italie). Sans profession. Anarchiste. 18/3/94",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306667,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.45,false,true,306668,Photographs,Mugshot,"Bertani. Orsini. 24 ou 25 ans, né à Florence (Italie). Sans profession. Anarchiste. 18/3/94",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306668,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.46,false,true,306669,Photographs,Mugshot,"Bertho. François, Élie. 26 ans, né le 30/9/67 à Jallais (Maine & Loire). Employé. Anarchiste. 9/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306669,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.47,false,true,306670,Photographs,Mugshot,"Bertout. Marie, Ismérie. 41 ans, née à Reims. Marchande de vins. Pas de motif. 26/2/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306670,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.48,false,true,306671,Photographs,Mugshot,"Biais. Fernand, Alphonse. 41 ans, né le 28/6/53 à Laval (Mayenne). Tourneur sur bois. Anarchiste. 2/7/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306671,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.49,false,true,306672,Photographs,Mugshot,"Billon. Gabriel, André, Adolphe. 20 ans, né à Boulogne /s/Seine. Typographe. Outrages. 14/8/93.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1893,1893,1893,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306672,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.50,false,true,306673,Photographs,Mugshot,"Billot. Eugène. 20 ans, né à La Charité (Nièvre). Tailleur d'habits. Anarchiste. 9/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306673,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.51,false,true,306674,Photographs,Mugshot,"Billot. Jean. 23 ans, né le 23/1/71 à Bourges (Cher). Tireur en barre. Anarchiste. 1/7/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306674,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.52,false,true,306675,Photographs,Mugshot,"Birilay. Henri, Marc, Julien. 46 ans, né à Chartres (Eure & Loir). Journalier. Anarchiste. 11/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306675,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.53,false,true,306676,Photographs,Mugshot,"Bissonier. Sébastien. 19 ans, né à St Bonnet (Allier). Journalier. Outrage à la Gendarmerie. 5/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306676,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.54,false,true,306677,Photographs,Mugshot,"Bligny. André, Eugène. 58 ans, né à Vincennes. Serrurier. Anarchiste. Fiché le 2/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306677,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.55,false,true,306678,Photographs,Mugshot,"Blay. François. 53 ans, né à St Gervais (Rhône). Tailleur. Anarchiste. 4/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306678,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.56,false,true,306679,Photographs,Mugshot,"Bocquet. Alexandre, Émile. 17 ans, né à Paris XVlle. Menuisier. Vol. Fiché le 14/4/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306679,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.57,false,true,306680,Photographs,Mugshot,"Bompeix. Eugène. 53 ans, né à St Martin d'Herbus (Haute-Vienne). Conducteur de machines. Anar. 3/7/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306680,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.58,false,true,306681,Photographs,Mugshot,"Borderie. Ferdinand, Jacques. 19 ans, né à Sarlat (Dordogne). Peintre sur métaux. Pas de motif. 1/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306681,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.59,false,true,306682,Photographs,Mugshot,"Borderie. Raoul. 18 ans, né à Castelsarazin (Tarn & Garonne). Peintre en bâtiment. Anarchiste. 1/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306682,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.60,false,true,306683,Photographs,Mugshot,"Bordes. Auguste. 15 ans, né à Paris XVIIIe. Garçon Marchand de vins. Anarchiste. 9/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306683,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.61,false,true,306684,Photographs,Mugshot,"Bordes. Guillaume, Auguste. 40 ans, né à Centrayes (Aveyron). Tailleur. Pas de motif. 29/2/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306684,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.62,false,true,306685,Photographs,Mugshot,"Borreman. Léontine, Eugénie. 23 ans, née à Paris le 25/12/70. Papetière. Anarchiste. 13/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306685,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.63,false,true,306686,Photographs,Mugshot,"Bossant. Edmond, Léon. 52 ans, né à Valenciennes (Nord). Sans profession. Anarchiste. 27/4/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306686,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.64,false,true,306687,Photographs,Mugshot,"Bossard. Célestin. 33 ans, né le 5/3/61 à Gonbretière (Vendée). Cordonnier. Anarchiste. 2/7/94",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306687,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.65,false,true,306688,Photographs,Mugshot,"Bouchenez. Adolphe. 36 ans, 27/2/94. (En rouge barrant la fiche: ""Transféré à Mazas. À faire extraire"").",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306688,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.66,false,true,306689,Photographs,Mugshot,"Bouchez. Louis. 19 ans, né le 29/8/75 à Paris XXe. Sculpteur. Anarchiste. 6/1/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306689,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.67,false,true,306690,Photographs,Mugshot,"Boulnois. Paul, Cyprien. 20 ans, né à Paris Ille. Employé de commerce. Anarchiste. 6/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306690,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.68,false,true,306691,Photographs,Mugshot,"Bourbasquet. François. 25 ans, né le 11/3/69 à St Avé (Morbihan). Garçon coiffeur. Anarchiste. 2/7/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306691,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.69,false,true,306692,Photographs,Mugshot,"Bourguoin. Hubert, Jules. 51 ans, né le 17/8/42 à Jelles (Seine & Marne). Maçon. Anarchiste. 2/7/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306692,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.70,false,true,306693,Photographs,Mugshot,"Bourlard. Joseph, Anselme. 45 ou 46 ans, né à Biemme (Belgique). Piqueur de grès. Anarchiste. 7/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306693,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.71,false,true,306694,Photographs,Mugshot,"Boutel. Joseph. Louis. 34 ans, né à Bonnay (Eure). Corroyeur. Anarchiste. 5/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306694,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.72,false,true,306695,Photographs,Mugshot,"Braun. Frédéric, Charles. 28 ans. Fiché le 22/2/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306695,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.73,false,true,306696,Photographs,Mugshot,"Breiner. Jean-Baptiste. 31 ans, né à Bar sur Aube (Aube). Mécanicien. Anarchiste. 5/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306696,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.74,false,true,306697,Photographs,Mugshot,"Bresson. Eugène, Marie. 30 ans, né le 4/7/63 à Chaumont (Haute-Marne). Avocat. Anarchiste. 20/1/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306697,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.75,false,true,306698,Photographs,Mugshot,"Breton. Ernest, Jean. 28 ans, 27/10/63. Anarchiste 24/1/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306698,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.76,false,true,306699,Photographs,Mugshot,"Briet. Albert, Louis. 44 ans, né à Lyon (Rhône). Boulanger. Anarchiste. 4/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306699,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.77,false,true,306700,Photographs,Mugshot,"Brosselin. Jean-Baptiste. 32 ans, né le 10/1/62 à Aulay (Côte d'Or). Menuisier. Anarchiste. 2/7/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306700,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.78,false,true,306701,Photographs,Mugshot,"Broggio. (Roche), Bernard. 39 ans, né en Italie. Journalier. Association de malfaiteurs. 22/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306701,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.79,false,true,306702,Photographs,Mugshot,"Bruchaesen. Etienne. 31 ans, né à Mag Levard (Hongrie). Tailleur d'habits. Anarchiste. 11/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306702,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.80,false,true,306703,Photographs,Mugshot,"Bruneau. Amédé, Jean Baptiste. 46 ans, né à Châteauroux (Indre). Cordonnier. Anarchiste. 2/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306703,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.81,false,true,306704,Photographs,Mugshot,"Brunel. Alexandre. 50 ans, né le 25/12/43 à Renaix (Belgique). Menuisier. Anarchiste. 3/7/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306704,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.82,false,true,306705,Photographs,Mugshot,"Brunet. Felix. 21 ans, né le 21/2/73 à Paris XVe. Peintre en voiture. Anarchiste. 7/7/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306705,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.83,false,true,306706,Photographs,Mugshot,"Brunet. Georges. 25 ans, né à Paris. Menuisier. Anarchiste. 4/2/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306706,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.84,false,true,306707,Photographs,Mugshot,"Buhr. Victor. 25 ans, né à Cologne (Allemagne). Peintre décorateur. Anarchiste. 2/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306707,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.85,false,true,306708,Photographs,Mugshot,"Cabuzac. Jean. 25 ans, né le 23/7/68 à Ivry la Bataille (Eure). Ciseleur. Anarchiste. 12/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306708,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.86,false,true,306709,Photographs,Mugshot,"Cana. Eugène, Louis. 22 ans, né à Paris Vllle. Monteur en bronze. Anarchiste. 2/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306709,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.87,false,true,306710,Photographs,Mugshot,"Cana. Eugène, Pierre. 47 ans, né à Paris XIe. Monteur en bronze. Anarchiste. 2/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306710,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.88,false,true,306711,Photographs,Mugshot,"Capette. Joseph, Désiré. 56 ans, né à Paris VI. Maroquinier. Anarchiste. 7/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306711,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.89,false,true,306712,Photographs,Mugshot,"Carraglia. Charles. 39 ans, né à Moceto (It). Homme de lettres. Anar, infraction à la loi du 21/6/73. 13/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306712,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.90,false,true,306713,Photographs,Mugshot,"Carteau. Auguste. 23 ans, né à St-Florent (Cher). Verrier. Anarchiste. 1/5/92.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1892,1892,1892,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306713,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.91,false,true,306714,Photographs,Mugshot,"Castallou. Charles. 53 ans, né le 4/10/41 à Paris IIe. Tapissier. Anarchiste. 16/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306714,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.92,false,true,306715,Photographs,Mugshot,"Catty. Nicolas, Pierre, François. 47 ans, né à Fressenneville (Somme). Mécanicien. Anarchiste. 10/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306715,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.93,false,true,306716,Photographs,Mugshot,"Cazal. Antoinette. 28 ans, née à Salgouz (Cantal). Couturière. Anarchiste. 28/2/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306716,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.94,false,true,306717,Photographs,Mugshot,"Ceaglio. Alexandre, Joseph. 42 ans, né à Turin (Italie). Employé de commerce. Anarchiste. 3/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306717,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.95,false,true,306718,Photographs,Mugshot,"Chambon. Raoul. 20 ans, né le 3/7/73 à Valréas (Vaucluse). Graveur. Anarchiste. 26/5/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306718,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.96,false,true,306719,Photographs,Mugshot,"Chapin. Armand, Louis. 30 ans, né à Épeigné (Indre & Loire). Charron. Anarchiste. 1/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306719,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.97,false,true,306720,Photographs,Mugshot,"Chapuis. Charles, Paul. 17 ans, né le 22/3/76 à Paris VIle. Tapissier. Anarchiste. 7/1/94",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306720,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.98,false,true,306721,Photographs,Mugshot,"Charlier. Ernile, Frédéric. 40 ans, né à Brest (Finistère). Peintre. Vol. 18/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306721,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.99,false,true,306722,Photographs,Mugshot,"Charrié. Cyprien. 26 ans, né le 7/10/67 à Paris XVIlle. Imprimeur. Anarchiste 2/7/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306722,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.100,false,true,306723,Photographs,Mugshot,"Charrié. Léon, Joseph. 27 ans, né à Paris XVllle. Garçon Plombier. Anarchiste. 1/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306723,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.101,false,true,306724,Photographs,Mugshot,"Chatel. Charles. 25 ans, né le 8/10/68 à Paris XVIIle. Anarchiste. 14/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306724,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.102,false,true,306725,Photographs,Mugshot,"Chatillon. Jean-Baptiste. 31 ans, né à Toiseron des Minard (Cher). Employé de commerce. Note du cabinet. 10/5/82",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1882,1882,1882,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306725,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.103,false,true,306726,Photographs,Mugshot,"Chauman. Nicolas. 38 ans, né à Paris XVe. Puisatier. Anarchiste. 6/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306726,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.104,false,true,306727,Photographs,Mugshot,"Chaumelin. Odilon. 22 ans, né à Paris VIe. Publiciste. Anarchiste. 24/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306727,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.105,false,true,306728,Photographs,Mugshot,"Chauvin. Émile. 18 ans, né à Paris IVe. Employé. Anarchiste. 1/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306728,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.106,false,true,306729,Photographs,Mugshot,"Chavanne. Gaston. 26 ans, né à Paris VIe. Graveur. Anarchiste. 28/2/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306729,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.107,false,true,306730,Photographs,Mugshot,"Chericotti. Paul. 35 ans, né à Milan (Italie). Marchand de volailles. Anarchiste/Assoc. de malfaiteurs. 25/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306730,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.108,false,true,306731,Photographs,Mugshot,"Chericotti. Paul. 35 ans, né à Milan (Italie). Marchand de volailles. Anarchiste/Assoc. de malfaiteurs. 25/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306731,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.109,false,true,306732,Photographs,Mugshot,"Chericotti. Paul. 35 ans, né à Milan (Italie). Marchand de volailles. Anarchiste/Assoc. de malfaiteurs. 25/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306732,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.110,false,true,306733,Photographs,Mugshot,"Chevalier. Étienne. 36 ans, né à Gémosac (Charente-Inférieure). Forgeron. Anarchiste. 11/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306733,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.111,false,true,306734,Photographs,Mugshot,"Chiroki. Eva (veuve Ortiz). 53 ans, née à Grosbitlech (Autriche). Cuisinière. Anarchiste. 21/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306734,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.112,false,true,306735,Photographs,Mugshot,"Chornat. Pierre. 50 ans, né le 20/6/44 à Letrat (Loire). Constructeur-mécanicien. Anarchiste. 2/7/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306735,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.113,false,true,306736,Photographs,Mugshot,"Clidière. François. 39 ans, né le 3/2/55 à Miales (Dordogne). Tailleur d'habits. Anarchiste. 9/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306736,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.114,false,true,306737,Photographs,Mugshot,"Cler. Henri. 31 ans, né à Paris XIe. Ébéniste. Anarchiste. 14/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306737,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.115,false,true,306738,Photographs,Mugshot,"Clouard. Paul, Jules. 35 ans, né le 20/6/58 à Peugans (Manche). Rétameur. Anarchiste. 9/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306738,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.116,false,true,306739,Photographs,Mugshot,"Cluzel. Louis. 30 ans, né le 31/8/63 à Bourg-Argental (Loire). Tailleur d'habits. Anarchiste. 2/7/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306739,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.117,false,true,306740,Photographs,Mugshot,"Collet. Edouard, Jean-Baptiste. 44 ans, né le 6/1/50 à Paris XVlle. Ciseleur. Anarchiste. 2/7/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306740,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.118,false,true,306741,Photographs,Mugshot,"Collot. Marie, Eugénie. 36 ans, né à Paris Xle. Tapissier. Anarchiste. 11/3/94",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306741,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.119,false,true,306742,Photographs,Mugshot,"Colombo. Joseph (on Jean, Octave). 19 ans, né à Paris Xlle. Monteur en bronze. Anarchiste. 10/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306742,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.120,false,true,306743,Photographs,Mugshot,"Colombet. Frédéric. 28 ans, né à Prigonnieux (Dordogne). Employé de commerce. Anarchiste. 2/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306743,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.121,false,true,306744,Photographs,Mugshot,"Couchot. Jean. 49 ans, né à Bidache (Basses-Pyrénées). Tailleurs d'habits. Anarchiste. 23/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306744,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.122,false,true,306745,Photographs,Mugshot,"Coudry. Hubert, Louis. 24 ans, né à Paris XVe. Corroyeur. Anarchiste. 7/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306745,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.123,false,true,306746,Photographs,Mugshot,"Court. Jean-Claude. 58 ans, né à Cherissey-le-M. (Haute-Savoie). Marchand de pains d'épices. Anarchiste. 17/3/94",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306746,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.124,false,true,306747,Photographs,Mugshot,"Cornu. Eugène. 25 ans, né à Paris XXe le 27/3/94. Cordonnier. Anarchiste. 2/7/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306747,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.125,false,true,306748,Photographs,Mugshot,"Cornuault. Joseph. 17 ans, né à Angers (Maine & Loire). Peintre eu bâtiment. Anarchiste. 7/1/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306748,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.126,false,true,306749,Photographs,Mugshot,"Cottée. Edouard, Eugène. 37 ans, né à Paris XVle. Artiste-peintre. Vol par complicité. 6/2/92.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1892,1892,1892,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306749,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.127,false,true,306750,Photographs,Mugshot,"Crespin. Joseph. 40 ans, né à Roquesteron (Alpes-Maritimes). Employé de banque. 25/3/93.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1893,1893,1893,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306750,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.128,false,true,306751,Photographs,Carte-de-visite; Mugshot,"Crespin. Joseph. 40 ans, né à Roquesteron (Alpes-Maritimes). Employé de banque. 25/3/93.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1893,1893,1893,Gelatin silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306751,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.129,false,true,306752,Photographs,Mugshot,"Cros. Jean. 19 ans, né à Négrin (Tarn). Tailleur d'habits. Pas de motif. 8/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306752,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.130,false,true,306753,Photographs,Mugshot,"Daguenet. Eugerne, Carolin. 39 ans, né à Granville (Manche). Ébéniste. Anarchiste. 26/2/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306753,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.131,false,true,306754,Photographs,Mugshot,"Damalix. Émile, Auguste. 37 ans, né à St-Claude (Doubs). Charpentier. Anarchiste. 19/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306754,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.132,false,true,306755,Photographs,Mugshot,"Daressy. Pierre. 39 ans, né à Lherme (Haute-Garonne). Cordonnier. Anarchiste. 28/2/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306755,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.133,false,true,306756,Photographs,Mugshot,"D'Auby. Henri. 48 (ou 49) ans, né à Montmédy (Meuse). Menuisier. Anarchiste. 28/2/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306756,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.134,false,true,306757,Photographs,Mugshot,"Dauriac. Henri, Georges. 36 ans, né à Memphis (USA). Agent d'affaires. Extortion de fonds. 22/12/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306757,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.135,false,true,306758,Photographs,Mugshot,"Dauriac. Henri, Georges. 36 ans, né à Memphis (USA). Agent d'affaires. Extortion de fonds. 22/12/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306758,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.136,false,true,306759,Photographs,Mugshot,"David. Armand, Auguste. 27 ans, né à Gien (Loiret). Faïencier. Anarchiste. 1/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306759,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.137,false,true,306760,Photographs,Mugshot,"Decker. Jacques. 43 ans, né à Grodeskersheim (Bas-Rhin). Tailleur d'habits. Anarchiste. 9/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306760,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.138,false,true,306761,Photographs,Mugshot,"Deforge. Henri, Walter. 19 ans, né à Bruxelles (Belgique). Porteur de journaux. Anarchiste. 6/1/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306761,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.139,false,true,306762,Photographs,Mugshot,"Defosse. Claude (dit Delfosse ou Lafosse). 29 ans, né à Arbeuf (Nièvre). Cocher. Anarchiste. 2/1/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306762,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.140,false,true,306763,Photographs,Mugshot,"Deguet. Victor, Adonis. 50 ans, né le 27/2/44 à Vuenpont (Aisne). Armurier. Anarchiste. 16/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306763,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.141,false,true,306764,Photographs,Mugshot,"Deherme. Marie, Adolphe. 26 ans, né à Paris XVIIe. Publiciste. Anarchiste. 9/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306764,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.142,false,true,306765,Photographs,Mugshot,"Dejernier (ou Degernier). Edouard. 45 ans, né à Gand (Belgique). Tailleur d'habits. Anarchiste. 8/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306765,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.143,false,true,306766,Photographs,Mugshot,"Dejoux. Jules. 46 ans, né le 16/6/48 à La Châtre (Indre). Maçon. Délit de presse. 2/7/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306766,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.144,false,true,306767,Photographs,Mugshot,"Delabie. Georges. 43 ans, né le 30/10/50 à Ganaches (Somme). Mécanicien. Anarchiste. 2/7/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306767,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.145,false,true,306768,Photographs,Mugshot,"De la Salle. Gabriel. 45 ans, né à Nantes (Loire-Inf.). Publiciste. Disposition du Préfet de Police. 16/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306768,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.146,false,true,306769,Photographs,Mugshot,"Delesderrier. Louis. 34 ans, né le 2/3/60 à Paris Ille. Ciseleur. Anarchiste. 16/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306769,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.147,false,true,306770,Photographs,Mugshot,"Deliège. Nicolas, François. 19 ans, né à Ixelles (Belgique). Tailleur d'habits. Anarchiste. 9/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306770,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.148,false,true,306771,Photographs,Mugshot,"Della Casa. 36 ans, né le 1/3/58 à Avoglion (Italie). Cordonnier. Anarchiste. 2/7/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306771,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.149,false,true,306772,Photographs,Mugshot,"Dery. Louis. 60 ans, né à Cobugny (Nièvre). Cordonnier. Anarchiste. 9/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306772,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.150,false,true,306773,Photographs,Mugshot,"Dodot. Émile (ou Jules). 55 ans, né à Paris Ier. Cordonnier. Anarchiste. 27/2/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306773,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.151,false,true,306774,Photographs,Mugshot,"Dufour. Louis. 37 ans, né à Port-Ste-Marie (Lot & Garonne). Bijoutier. Anarchiste.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306774,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.152,false,true,306775,Photographs,Mugshot,"Dumout. Henri, Victor. 29 ans, né à Issy (Seine). Mécanicien. Anarchiste. 8/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306775,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.153,false,true,306776,Photographs,Mugshot,"Dupit. Paul. 20 ans, né le 13/3/74 à Paris XVIIe. Garçon boucher. Anarchiste. 2/7/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306776,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.154,false,true,306777,Photographs,Mugshot,"Duprat. François, Louis. 34 ans, né à St-Martin (Gers). Marchand de vins. Anarchiste. 27/4/92.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1892,1892,1892,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306777,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.155,false,true,306778,Photographs,Mugshot,"Dupuis. Augustin. 53 ans, né le 24/6/41 à Dourdan (Seine & Oise). Charron, forgeron. Anarchiste. 3/7/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306778,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.156,false,true,306779,Photographs,Mugshot,"Dupuy. Edmond, Adolphe. 29 ans, né à Paris XIVe. Employé de commerce. Anarchiste. 1/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306779,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.157,false,true,306780,Photographs,Mugshot,"Durey. François, Louis. 43 ans, né le 25/2/51 à Lyon (Rhône). Architecte. Anarchiste. 2/7/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306780,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.158,false,true,306781,Photographs,Mugshot,"Durieux. Aléxis, Alberic. 20 ans, né le 28/2/74 à Stains (Seine). Verrier. Vol. 24/1/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306781,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.159,false,true,306782,Photographs,Mugshot,"Dutheil. Louis. 29 ans, né le 28/7/64 à Maisonnais (Ht Vienne). Tailleur d'habits. Anarchiste. 3/7/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306782,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.160,false,true,306783,Photographs,Mugshot,"Etiévant. Henri, Achille. 32 ans, né à Flammanville (Manche). Typographe. Anarchiste. 4/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306783,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.161,false,true,306784,Photographs,Mugshot,"Fauvel. Louis. 27 ans, né le 14/4/67 à Écouché (Orne). Tourneur. Anarchiste. 2/7/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306784,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.162,false,true,306785,Photographs,Mugshot,"Favre. Pierre, Maurice. 29 ans, né le 30/11/64. Ciseleur. Anarchiste. 15/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306785,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.163,false,true,306786,Photographs,Mugshot,"Favre. Sébastien. 36 ans, né à St Étienne (Loire). Négociant. Port d'arme prohibée, anarchiste. 20/2/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306786,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.164,false,true,302429,Photographs,Mugshot,Feneon. Felix. Clerk of the Galerie Berheim Jeune.,,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894–5,1894,1895,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/302429,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.165,false,true,306787,Photographs,Mugshot,"Ferter. Ernest, Charles. 31 ans, né le 23/10/62 à Melun (Seine & Marne). Fumiste. Anarchiste. 2/7/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306787,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.166,false,true,306788,Photographs,Mugshot,"Fétis. Julien. 26 ans, né à New York (USA). Couvreur. Anarchiste. 3/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306788,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.167,false,true,306789,Photographs,Mugshot,"Fischter. Joseph. 47 ans, né à Paris Ve. Imprimeur sur papiers-peints. Anarchiste. 4/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306789,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.168,false,true,306790,Photographs,Mugshot,"Forti. Alfred. 18 ans, né à Milan (Italie). Restaurateur. Anarchiste. 27/2/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306790,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.169,false,true,306791,Photographs,Mugshot,"Forti. Ernesta. 45 (ou 46) ans, née à Lodi (Italie). Laitière. Anarchiste. 27/2/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306791,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.170,false,true,306792,Photographs,Mugshot,"Fournier. Émile, Christophe. 26 ans, né à St-Martial (Creuse). Serrurier. Vol. 13/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306792,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.171,false,true,306793,Photographs,Mugshot,"Foussard. Eugène. 26 ans, né le 7/12/67 à Dangeul (Sarthe). Peintre en bâtiment. Anarchiste. 2/7/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306793,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.172,false,true,306794,Photographs,Mugshot,"Francier. Éloi. 41 ans, né le 28/10/53 à Resson-le-Long (Aisne). Ébéniste. Anarchiste. 22/5/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306794,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.173,false,true,306795,Photographs,Mugshot,"François dit Francis. 38 ans, né le 3/12/55 à Reims (Marne). Ébéniste. Anarchiste. 5/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306795,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.174,false,true,306796,Photographs,Mugshot,"Gaillard. Pierre, Auguste. 47 ans, né à Foulanges (Cantal). Employé de commerce. Anarchiste. 15/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306796,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.175,false,true,306797,Photographs,Mugshot,"Galau. Charles. 18 ans, né à Nogent s/Marne (Seine). Charron. Cris séditieux. Anarchiste. 21/2/91.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1891,1891,1891,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306797,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.176,false,true,306798,Photographs,Mugshot,"Galau (ou Gallot). Louis. 53 ans, né à Meriziès (Tarn). Charron. Anarchiste. 21/8/93.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1893,1893,1893,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306798,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.177,false,true,306799,Photographs,Mugshot,"Gama. Joseph. 41 ans, né le 5/3/42 à Paris IXe. Graveur. Anarchiste. 6/7/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306799,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.178,false,true,306800,Photographs,Mugshot,"Garnier. Anatole, Auguste. 18 ans, né à Montereau (Seine & Marne). Orfèvre. Anarchiste. 1/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306800,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.179,false,true,306801,Photographs,Mugshot,"Garnier. Auguste. 34 ans, né à Périgny (Côte-d'Or). Journalier. Anarchiste. 2/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306801,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.180,false,true,306802,Photographs,Mugshot,"Gatinet. Pierre, Adrien. 50 ans, né le 13/10/43 à Bourges (Cher). Charpentier. Anarchiste. 2/7/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306802,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.181,false,true,306803,Photographs,Mugshot,"Gauche. Henri. 24 ans, né le 7/2/60 à Paris. Rentier. Anarchiste.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1884,1884,1884,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306803,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.182,false,true,306804,Photographs,Mugshot,"Gelhausen. Jean. 55 ans, né à Grevennemache (Luxembourg). Cordonnier. Infraction à la loi du 18/12/93.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1893,1893,1893,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306804,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.183,false,true,306805,Photographs,Mugshot,"Giroux. Hippolyte. 34 ans, né à Montreuil les Mines (Saone & Loire). Mécanicien. Anarchiste. 28/2/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306805,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.184,false,true,306806,Photographs,Mugshot,"Godard. Armand, Alexandre. 18 ans, né le 11/3/75 à Paris XVIIe. Électricien. Cris séditieux. 6/1/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306806,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.185,false,true,306807,Photographs,Mugshot,"Gordon. Max. 39 ans, né le 20/8/54 à Vilna (Italie). Employé de commerce. Anarchiste. 2/7/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306807,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.186,false,true,306808,Photographs,Mugshot,"Grandidier. Louis, Auguste. 20 ans, né à St-Denis (Seine). Journalier. Anarchiste. 1/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306808,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.187,false,true,302430,Photographs,Mugshot,"Grave. Jean. 38 ans, né le 16/10/54 à Breuil (Puy de Dôme). Typographe. Anarchiste. 9/1/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/302430,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.188,false,true,306809,Photographs,Mugshot,"Grégoire. Aimé, Paul. 36 ans, né à Bruxelles (Belgique). Accordeur de piano. Anarchiste. 2/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306809,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.189,false,true,306810,Photographs,Mugshot,"Grégoire. Alphonse. 27 ans, né à La Montagne (Loire-Inférieure). Mécanicien. Anarchiste. 2/7/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306810,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.190,false,true,306811,Photographs,Mugshot,"Grugeau. Alfred, Alphonse. 26 ans, né à Tours (Indre & Loire). Cordonnier. Anarchiste. 9/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306811,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.191,false,true,306812,Photographs,Mugshot,"Guelle (ou Gueulle, dit St Denis). 72 ans, né à Beauvais (Oise). Matelassier. Anarchiste. 7/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306812,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.192,false,true,306813,Photographs,Mugshot,"Guénant. Louis, Désiré. 31 ans, né à Paris XVIIIe. Comptable. Anarchiste. 2/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306813,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.193,false,true,306814,Photographs,Mugshot,"Guerlinger. Pierre. 28 ans, né le 31/5/65 à St-Avold (Moselle). Journalier. Anarchiste. 14/4/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306814,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.194,false,true,306815,Photographs,Mugshot,"Guéry. Paul, Alphonse. 37 ans, né le 30/6/56 à Laversine (Aisne). Journalier. Anarchiste. 12/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306815,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.195,false,true,306816,Photographs,Mugshot,"Guignard. Georges, Auguste. 36 ans, né le 1/1/58 à Neuilly (Seine). Plombier. Anarchiste. 15/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306816,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.196,false,true,306817,Photographs,Mugshot,"Guillemard. Isidore, François. 46 ans, né à St-Michel des Andaines (Orne). Menuisier. Anarchiste 28/2/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306817,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.197,false,true,306818,Photographs,Mugshot,"Haesig. Léon. 18 ans, né à St-Denis. Chaudronnier. Disposition du Préfet de Police. 14/4/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306818,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.198,false,true,306819,Photographs,Mugshot,"Handrock. Frédéric(k). Vilhem. 34 ans, né à Lyeck (Allemagne). Doreur sur bois. Anarchiste. 2/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306819,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.199,false,true,306820,Photographs,Mugshot,"Hannedouche. François. 31 ans, né à Lilleris (Pas de Calais). Peintre en bâtiments. Anarchiste. 1/1/93.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1893,1893,1893,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306820,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.200,false,true,306821,Photographs,Mugshot,"Havard. Octave, Onésime. 25 ans, né à Hedouville (Calvados). Polisseur. Anarchiste. 1/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306821,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.201,false,true,306822,Photographs,Mugshot,"Hébert. Georges, Henri. 27 (ou 29) ans, né à Bayeux (Calvados). Menuisier. Anarchiste. 23/4/92.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1892,1892,1892,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306822,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.202,false,true,306823,Photographs,Mugshot,"Henon. François. 52 ans, né le 5/5/42 à Lyon (Rhône). Caissier. Anarchiste. 2/7/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306823,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.203,false,true,306824,Photographs,Mugshot,Henry. Émile. (auteur de l'attentat de l'Hotel St-Lazare),,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1890–94,1890,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306824,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.204,false,true,306825,Photographs,Mugshot,Henry. Émile. (auteur de l'attentat de l'Hotel St-Lazare),,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1890–94,1890,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306825,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.205,false,true,306826,Photographs,Mugshot,"Herman. Caroline. 33 ans, née à Paris Vllle. Couturière. Disposition du Préfet (Anarchie). 21/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306826,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.206,false,true,306827,Photographs,Mugshot,"Herouard. Henri. 17 ans, né à Paris XVIIe. Serrurier. Anarchiste. 6/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306827,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.207,false,true,306828,Photographs,Mugshot,"Hervy. Marcel, Noël. 19 ans, né à Paris XVIIIe. Raccommodeur de porcelaine. Anarchiste. 27/10/93.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1893,1893,1893,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306828,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.208,false,true,306829,Photographs,Mugshot,"Hettich (ou Hettig). Eugène. 17 ans, né le 6/1/77 à Paris XXe. Cocher. Anarchiste. 16/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306829,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.209,false,true,306830,Photographs,Mugshot,"Heurteaux. Auguste. 31 ans, né à Paris Xe. Polisseur. Anarchiste. 3/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306830,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.210,false,true,306831,Photographs,Mugshot,"Hivon. Pierre. 45 ans, né à Bourbon le Chambai (Allier). Lithographe. Anarchiste. 4/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306831,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.211,false,true,306832,Photographs,Mugshot,"Hostenbock. Joseph, Louis. 22 ans, né à Bruxelles (Belgique). Coiffeur. Anarchiste. 26/2/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306832,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.212,false,true,306833,Photographs,Mugshot,"Hourt. Jean. 34 (ou 35) ans, né à Reims (Marne). Menuisier. Anarchiste. 4/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306833,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.213,false,true,306834,Photographs,Mugshot,"Imhof. Louis, Alfred. 37 ans, né à Mex (Suisse). Journalier. Anarchiste. 1/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306834,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.214,false,true,306835,Photographs,Mugshot,"Iv(w)anowski. Casimir. 57 ans, né à Chalon-sur-Saône (Saône & Loire). Mécanicien. Anarchiste. 6/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306835,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.215,false,true,306836,Photographs,Mugshot,"Jacob. Georges, Gustave. 43 ans, né à Paris XVIIe. Journalier. Anarchiste. 27/2/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306836,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.216,false,true,306837,Photographs,Mugshot,"Jacot. Charles, Émile. 36 ans, né à Allenjoie (Doubs). Colporteur Anarchiste. 8/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306837,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.217,false,true,306838,Photographs,Mugshot,"Jacquet. Hippolyte, Edouard. 49 ans, né le 15/3/45 à Paris Ille. Sellier-maroquinier. Anarchiste. 3/7/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306838,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.218,false,true,306839,Photographs,Mugshot,"Jaffard. Julien, Ludovic. 37 ans, né le 31/5/57 à Lesterps (Charente). Journalier. Anarchiste. 2/7/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306839,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.219,false,true,306840,Photographs,Mugshot,"Jamard. Alphonse, Ernest. 51 ans, né à Paris. Distillateur. Anarchiste. 28/2/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306840,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.220,false,true,306841,Photographs,Mugshot,"Job. Eugène, François. 31 ans, né à Paris Xle. Chaisier. Anarchiste. 6/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306841,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.221,false,true,306842,Photographs,Mugshot,"Jordy. Baptiste. 61 ans, né le 8/72/32 à Labardes (Aude). Cordonnier. Anarchiste. 2/7/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306842,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.222,false,true,306843,Photographs,Mugshot,"Jourdan. Numa. 30 ans, né le 27/8/61 à Courbevoie (Seine). Teinturier. Anarchiste. 23/4/92.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1892,1892,1892,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306843,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.223,false,true,306844,Photographs,Mugshot,"Kahn. Rodolphe. 43 ans, né le 15/1/51 à Lyon (Rhône). Courtier de commerce. Anarchiste. 8/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306844,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.224,false,true,306845,Photographs,Mugshot,"Kaision. François. 39 ans, né à Reims. Mégissier. Anarchiste. 9/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306845,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.225,false,true,306846,Photographs,Mugshot,"Kern. Jacob, Hermann. 33 ans, né le 26/8/60 à Berlinger (Suisse). Comptable. Anarchiste. 10/7/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306846,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.226,false,true,306847,Photographs,Mugshot,"Kieffer. Nicolas. 35 ans, né le 8/4/59 à Haltuiller (Meurthe). Menuisier. Anarchiste. 2/7/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306847,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.227,false,true,306848,Photographs,Mugshot,"Kilchenstein. Dominique. 48 ans, né à Luneville (Meurthe & Moselle). Marchand au panier. Anarchiste. 23/4/92",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1892,1892,1892,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306848,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.228,false,true,306849,Photographs,Mugshot,"Klein. Louis. 25 ans, né le 8/8/67 à Colmar (Alsace). Employé de commerce. Anarchiste. 11/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306849,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.229,false,true,306850,Photographs,Mugshot,"Labeyrie. Romain. 19 ans, né le 13/11/74 à Cauna (Landes). Sculpteur. Anarchiste. 10/1/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306850,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.230,false,true,306851,Photographs,Mugshot,"Labrie. Oscar, Alexandre. 33 ans, né à Charenton (Seine). Marchand de vins. Anarchiste. 2/7/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306851,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.231,false,true,306852,Photographs,Mugshot,"Lagane (ou Lagasse). Lucien, Pierre. 35 ans. 22/2/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306852,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.232,false,true,306853,Photographs,Mugshot,"Lamure. Eugène, Clément. 20 ans, né le 5/11/73 à Paris Ier. Gérant de magasin. Anarchiste. 2/7/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306853,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.233,false,true,306854,Photographs,Mugshot,"Landoin. Antoine, Eugène. 33 ans, né le 16/11/60 à Quincie (Rhône). Comptable. Anarchiste. 16/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306854,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.234,false,true,306855,Photographs,Mugshot,"Landschoot. Edouard. 27 ans, né le 6/8/67 à Paris. Bijoutier. Anarchiste. 2/7/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306855,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.235,false,true,306856,Photographs,Mugshot,"Lapeyre. Louis, Pierre. 29 ans. né à Rodez (Aveyron). Employé. Anarchiste. 10/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306856,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.236,false,true,306857,Photographs,Mugshot,"Lapointe. Nicolas, Céleste. 45 ans, né à Marbach (Alsace-Lorraine). Cordonnier. Anarchiste. 2/7/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306857,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.237,false,true,306858,Photographs,Mugshot,"Large. Etienne, Louis. 20 ans, né le 1/1/74 à Lyon (Rhône). Tapissier. Anarchiste. 9/1/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306858,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.238,false,true,306859,Photographs,Mugshot,"Lassalas. Ernest, Auguste. 33 ans, né à Paris IVe. Ébéniste. Anarchiste. 2/1/94",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306859,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.239,false,true,306860,Photographs,Mugshot,"LaumesfeIt. Paul, Mathias. 35 ans, né le 29/3/59. à Paris VIe. Tailleur d'habits. Anarchiste. 2/7/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306860,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.240,false,true,306861,Photographs,Mugshot,"Leballeur. Jules, Léon. 29 (ou 30) ans, né à Rouissé Jassée (Sarthe). Cordonnier. Anarchiste. 1/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306861,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.241,false,true,306862,Photographs,Mugshot,"Leboucher. Edouard, Léon. 43 ans, né à Paris XIVe. Cordonnier. Anarchiste. 7/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306862,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.242,false,true,306863,Photographs,Mugshot,"Ledot. Julien. 41 ans, né à Bourges (Cher). Employé. Anarchiste. 1/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306863,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.243,false,true,306864,Photographs,Mugshot,"Lefebvre. Eugène, Anatole. 28 ans, né le 2/7/66 à St Pierre (Eure). Sculpteur sur bois. Anarchiste. 2/7/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306864,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.244,false,true,306865,Photographs,Mugshot,"Lefrançois. Charles, Albert. 27 ans, né le 10/11/67 à Paris XXe. Horloger. Anarchiste. 2/7/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306865,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.245,false,true,306866,Photographs,Mugshot,"Leger. Joseph. 16 ans, né à Marseille (Bouches-du-Rhône). Jardinier. Fabrication d'engins explosifs. 4/7/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306866,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.246,false,true,306867,Photographs,Mugshot,"Lelarge. Louis, Eugène. 46 ans, né le 5/4/48 à Paris Ille. Employé de commerce. Anarchiste. 2/7/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306867,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.247,false,true,306868,Photographs,Mugshot,"Leleu. Victor, Louis. 29 ans, né le 19/10/64 à Arras (Pas-de-Calais). Anarchiste. 9/1/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306868,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.248,false,true,306869,Photographs,Mugshot,"Lenfant. Émile, Jules. 26 ans, né à Choisy-le-Roi (Seine). Modeleur. Anarchiste. 2/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306869,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.249,false,true,306870,Photographs,Mugshot,"Léonard. Aimé. 30 ans, né à Chalonné/s/Loire (Maine & Loire). Mineur ou gazier. Anarchiste. 27/2/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306870,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.250,false,true,306871,Photographs,Mugshot,"Lepla. Henri, Florimond. 25 ans, né à Gand (Belgique). Anarchiste. 1/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306871,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.251,false,true,306872,Photographs,Mugshot,"Letellier. Louis, Auguste. 29 ans, né à Rouen (Seine-Inférieure). Employé. Anarchiste. 23/4/92.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1892,1892,1892,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306872,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.252,false,true,306873,Photographs,Mugshot,"Leveillé. Louis. 37 ans, né le 7/7/57 à Cliche (Seine). Forgeron. Anarchiste. 7/7/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306873,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.253,false,true,306874,Photographs,Mugshot,"Liégeois (ou Liegois), François. 30 ans, né à Vilette (Meurthe & Moselle). Cordonnier. Anarchiste. 26/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306874,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.254,false,true,306875,Photographs,Mugshot,"Livenais (ou Livenay), André. 39 ans, né à Renazé (Mayenne). Garçon de magasin. Anarchiste. 2/7/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306875,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.255,false,true,306876,Photographs,Mugshot,"Loth. Clotilde, Caroline (femme Bossant). 43 ans, née à Valenciennes. Sans profession. Anarchiste. 27/4/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306876,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.256,false,true,306877,Photographs,Mugshot,"Lothier. Gaston. 27 ans, né le 27/12/66 à St Thomas (Charente-Inférieure). Menuisier. Anarchiste. 1/1/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306877,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.257,false,true,306878,Photographs,Mugshot,"Loutrel. François. 37 ans, né à Paris XVlle. Journalier. Anarchiste. 1/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306878,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.258,false,true,302431,Photographs,Mugshot,"Luce. Maximilien. 36 ans, né le 13/3/58 à Paris VIIe. Artiste-peintre. Anarchiste. 6/7/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/302431,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.259,false,true,306879,Photographs,Mugshot,"Lustenberger. Louis, Joseph. 38 ans, le 16/3/56 à Paris XXe. Ciseleur. Anarchiste. 17/7/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306879,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.260,false,true,306880,Photographs,Mugshot,"Lutringer. Pierre, Léopold. 43 ans, né le 25/11/50 à Stenay (Meuse). Cordonnier. Anarchiste. 3/7/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306880,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.261,false,true,306881,Photographs,Mugshot,"Mahler. Jacob, Henri, Jean. 61 ans, né à Hanovre (Duché de Hesse). Couvreur. Anarchiste. 3/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306881,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.262,false,true,306882,Photographs,Mugshot,"Maillabuau. Auguste, Léon. 30 ans, né le 23/8/93 à Paris Vle. Anarchiste. 2/7/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306882,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.263,false,true,306883,Photographs,Mugshot,"Maillard. Louis. 30 ans, né à Rennes (Ille & Vilaine). Employé de commerce. Anarchiste. 27/2/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306883,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.264,false,true,306884,Photographs,Mugshot,"Maince. Émile. 19 ans, né à Levallois-Perret (Seine). Réparateur d'objets d'arts. Anarchiste. 6/1/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306884,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.265,false,true,306885,Photographs,Mugshot,"Mainfroy. Albert, Pierre. 41 ans, né le 7/4/52 à Courbevoie (Seine). Imprimeur. Anarchiste. 3/7/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306885,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.266,false,true,306886,Photographs,Mugshot,"Malpet. Jeanne (femme Pivier). 51 ans, née en mai 42. Couturière. Anarchiste. 3/7/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306886,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.268,false,true,306888,Photographs,Mugshot,"Mangin. Edmond, Émile. 33 ans, né le 19/3/61 à Senon (Meuse). Cimentier. Anarchiste. 8/7/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306888,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.269,false,true,306889,Photographs,Mugshot,"Margerand. Claude. 32 ans, né le 24/3/61 à Beaujeu (Rhône). Cordonnier. Anarchiste. 3/7/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306889,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.270,false,true,306890,Photographs,Mugshot,"Marie. Constant. 53 ans, né le 27/8/38 à Ste-Houvrince (Calvados). Garçon rnarçon. Anarchiste. 2/7/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306890,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.271,false,true,306891,Photographs,Mugshot,"Marie. Léon, Louis. 23 ans, né le 19/8/70 à Adouzeval (Calvados). Couvreur. Anarchiste. 16/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306891,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.272,false,true,306892,Photographs,Mugshot,"Martin. Constant. 53 ans, né le 5/4/39 à Santrevaux (Basses-Alpes). Crémier. Anarchiste. 27/4/92.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1892,1892,1892,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306892,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.273,false,true,306893,Photographs,Mugshot,"Martin. Pierre. 22 ans, né à St-Léger (Saône-et-Loire). Employé de commerce. Anarchiste. 27/2/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306893,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.274,false,true,306894,Photographs,Mugshot,"Martineau. Jules, Louis. 30 ans, né à Angers (Maine & Loire). Peintre en bâtiment. Anarchiste. 3/7/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306894,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.275,false,true,306895,Photographs,Mugshot,"Marty. Louis. 20 ans, né à Lacanne (Tarn). Tailleur d'habits. Anarchiste. 9/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306895,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.276,false,true,306896,Photographs,Mugshot,"Masini (dit Mazzini). Angelo, Henri. 25 ans, né le 4/9/69 à Milan (Italie). Ébéniste. Anarchiste. 1/9/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306896,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.277,false,true,306897,Photographs,Mugshot,Matha.,,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1880s–90s,1880,1899,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306897,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.278,false,true,306898,Photographs,Mugshot,Matha.,,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1880s–90s,1880,1899,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306898,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.279,false,true,306899,Photographs,Carte-de-visite; Mugshot,Mathieu. Gustave.,,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1880s–90s,1880,1899,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306899,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.280,false,true,306900,Photographs,Carte-de-visite; Mugshot,Mathieu. Gustave. (avec une barbe postiche),,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1880s–90s,1880,1899,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306900,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.281,false,true,306901,Photographs,Mugshot,"Mathon. Louis Marius. 30 ans, né à St Andiolle (Ardèche). Ferblantier. Anarchiste. 26/2/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306901,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.282,false,true,306902,Photographs,Mugshot,"Maurin. Émile, Auguste. 31 ans, né à Marseille (Bouche du Rhône). Ex photographe. Anarchiste. 2/7/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306902,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.283,false,true,306903,Photographs,Mugshot,"Mauroy. Alfred, Édouard. 34 ans, né à Paris VIIe. Dessinateur. Anarchiste. 26/2/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306903,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.284,false,true,306904,Photographs,Mugshot,"Mayence. Gustave, David. 33 ans, né le 29/5/60 à Paris XVllle. Tapissier. Anarchiste. 17/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306904,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.285,false,true,306905,Photographs,Mugshot,"Mazoldi. Frédéric, Jean-Baptiste. 54 ans, né à Bicroz (Autriche). Ferblantier. Anarchiste. 23/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306905,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.286,false,true,306906,Photographs,Mugshot,"Mentenich. François, Joseph. 22 ans, né le 3/10/71 à Paris XIIe. Ébéniste. Anarchiste. 2/7/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306906,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.287,false,true,306907,Photographs,Mugshot,"Mereaux. Émile-Louis. 33 ans, né à Laon (Aisne). Ébéniste. Anarchiste. 23/4/92.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1892,1892,1892,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306907,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.288,false,true,306908,Photographs,Mugshot,"Mérigeau. Jacques. 35 ans, né à St-Léger-les-Melles (Deux Sèvres). Ébéniste. 19/12/93.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1893,1893,1893,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306908,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.289,false,true,306909,Photographs,Mugshot,"Mermin. Camille. 33 ans, né à La Havane (Cn Espagnoles). Rep. de cou. (?). Anarchiste. 9/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306909,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.290,false,true,306910,Photographs,Mugshot,"Miaglia. Bernard. 41 ans, né à Giaglione (Italie). Cordonnier. Anarchiste. 9/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306910,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.291,false,true,306911,Photographs,Mugshot,"Miel. Eugène, Paul, Léon. 38 ans, né à Creil (Oise). Estampeur. Anarchiste. 2/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306911,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.292,false,true,306912,Photographs,Mugshot,"Millard. Victor. 53 ans, né le 5/6/40 à Moyon (Oise). Cordonnier. 16/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306912,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.293,false,true,306913,Photographs,Mugshot,"Mocquet. Georges, Gustave. 17 ans, né le 17/5/76 à Paris IXe. Tapissier. Anarchiste. 6/1/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306913,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.294,false,true,306914,Photographs,Mugshot,"Molmerret. Joseph, Camille. 28 ans, né le 20/11/65 à Lyon (Rhône). Graveur. Anarchiste. 26/5/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306914,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.295,false,true,306915,Photographs,Mugshot,"Morane. Antoine. 35 ans, né à Chalinargue (Cantal). Manœvre. Anarchiste. 6/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306915,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.296,false,true,306916,Photographs,Mugshot,"Moreau. François. 47 ans, né le 19/11/46 à Nevers (Nièvre). Menuisier: Anarchiste. 2/7/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306916,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.297,false,true,306917,Photographs,Mugshot,"Moreau. Louis. 40 ans, né le 22/10/53 à Villiers (Nièvre). Tailleur de pierre. Anarchiste. 2/7/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306917,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.298,false,true,306918,Photographs,Mugshot,"Morel. Benoit. 33 ans, né le 6/11/61 à St Laurent d'Orringt (Rhône). Ébéniste. Anarchiste. 8/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306918,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.299,false,true,306919,Photographs,Mugshot,"Morvan. Félicien. 45 ans, né le 8/6/49 à Kerity (Côte du Nord). Menuisier. Anarchiste. 2/7/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306919,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.300,false,true,306920,Photographs,Mugshot,"Moucheraud. Adrien, Eugène. 28 ans, né à Paris IVe. Imprimeur. Anarchiste. 4/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306920,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.301,false,true,306921,Photographs,Mugshot,"Moucheraud. Pierre, Yves. 27 ans, né à Paris IVe. Imprimeur. Anarchiste. 4/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306921,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.302,false,true,306922,Photographs,Mugshot,"Mouette. Charles. 32 ans, né à Paris Ile. Peintre en bâtiment. Association de malfaiteurs. 20/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306922,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.303,false,true,306923,Photographs,Mugshot,"Monzon. Lucien, Henri, Baptiste. 18 ans, né à Paris XXe. Couvreur. Anarchiste. 23/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306923,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.304,false,true,306924,Photographs,Mugshot,Mursch. Eugène. 24 ans à Schlestatt (Bas-Rhin). Ciseleur. Anarchiste. 18/3/94.,,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306924,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.305,false,true,306925,Photographs,Mugshot,"Naudet. Gervais. 40 ans, né à Echaleau (Côte-d'Or). Menuisier. Anarchiste. 10/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306925,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.306,false,true,306926,Photographs,Mugshot,"Nic. Celestin. 20 ans, né à Conflans-St-Honorine (Seine & Oise). Emballeur. 26/2/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306926,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.307,false,true,306927,Photographs,Mugshot,"Notelez. Charles, Émile. 29 ans, né à Paris XXe. Portefeuilliste. Anarchiste. 26/2/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306927,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.308,false,true,306928,Photographs,Mugshot,"308. Novi. Ernest, Théodore. 32 ans, né à Nice (Alpes-Maritimes). Architecte. Anarchiste. 27/2/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306928,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.309,false,true,306929,Photographs,Mugshot,"309. Ochart. Alphonse. 37 ans, né le 24/1/56 à Asbruch (Nord). Fabricant de chaussures. Anarchiste. 22/1/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306929,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.310,false,true,306930,Photographs,Mugshot,"310. Olivier. Philippe, Octave. 25 ans, né le 29/6/68 à Paris XVIIle. Plombier. Anarchiste. 16/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306930,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.311,false,true,306931,Photographs,Mugshot,"Ortiz. Léon. 25 ans, né à Paris. Commis d'architecte. Anarchiste. Voyage ordinairement en bicyclette.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306931,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.312,false,true,306932,Photographs,Mugshot,"Ortiz. Léon. 25 ans, né à Paris. Commis d'architecte. Anarchiste. Voyage ordinairement en bicyclette.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306932,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.313,false,true,306933,Photographs,Mugshot,"Ortiz. Léon. 25 ans, né à Paris. Commis d'architecte. Anarchiste. Voyage ordinairement en bicyclette.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306933,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.314,false,true,306934,Photographs,Mugshot,"Oudin. Clovis. 49 ans, né à Saint-Hilaire (Marne). Mécanicien. Anarchiste. 2/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306934,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.315,false,true,306935,Photographs,Mugshot,"Paget. Jean, Louis. 41 ans, né le 15/7/52 à Thonon (Haute-Savoie). Cordonnier. Anarchiste. 12/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306935,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.316,false,true,306936,Photographs,Mugshot,"Pallaz (ou Pellaz). Péronne. 28 ans, née le 11/8/66 à Aix-les-Bains (Savoie). Cuisière. Anarchiste. 8/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306936,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.317,false,true,306937,Photographs,Mugshot,"Para (ou Parra). Henri. 38 ans, né le 16/5/56 à Paris Ve. Camelot. Anarchiste. 4/9/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306937,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.318,false,true,306938,Photographs,Mugshot,"Parisis. Charles. 20 ans, né à Aubervilliers (Seine). Tailleur d'habits. Outrages anarchistes. 10/7/93.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1893,1893,1893,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306938,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.319,false,true,306939,Photographs,Mugshot,"Parisot. Louis. 37 ans, né à Saint-Avalet (Moselle). Employé au ""Petit Journal"". Anarchiste. 2/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306939,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.320,false,true,306940,Photographs,Mugshot,"Pausader. Jean, Ernest. 27 ans, né le 28/7/66 à Paris Xe. Publiciste. Anarchiste. 2/1/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306940,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.321,false,true,306941,Photographs,Mugshot,"Pelgrom. Elise (femme Schouppe). Deux photographies, dont une légendée: Reprod. faite 22/2/93.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1893,1893,1893,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306941,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.322,false,true,306942,Photographs,Mugshot,"Pelgrom. Elise (femme Schouppe). Deux photographies, dont une légendée: Reprod. faite 22/2/93.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1893,1893,1893,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306942,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.323,false,true,306943,Photographs,Mugshot,"Pemjean. Lucien, Pierre. 32 ans, né à Lyon (Rhône). Publiciste. Anarchiste. 2/1/93.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1893,1893,1893,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306943,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.324,false,true,306944,Photographs,Mugshot,"Pennelier. Casimir. Arthur. 36 ans, né à Billeuse (Somme). Clerc d'huissier. Anarchiste. 16/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306944,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.325,false,true,306945,Photographs,Mugshot,"Percheron. Auguste. 56 ans, né à Poitier (Nièvre). Écrivain public. Anarchiste. 21/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306945,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.326,false,true,306946,Photographs,Mugshot,"Pernin. François. 34 ans, né le 11/1/60 au Creuzot (Saône-et-Loire). Forgeron. Anarchiste. 2/7/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306946,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.327,false,true,306947,Photographs,Mugshot,"Perot. Gaston, Auguste. 22 ans, né à Paris XVllle. Journalier. Anarchiste. 4/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306947,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.328,false,true,306948,Photographs,Mugshot,"Perrare. Antoine. 53 ans, né à St Diaur (Rhône). Mécanicien. Anarchiste. 10/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306948,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.329,false,true,306949,Photographs,Mugshot,"Perrier (dit Theriez). Louis. 35 ans, né le 25/8/58 à Paris Vllle. Ébéniste. Anarchiste. 16/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306949,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.330,false,true,306950,Photographs,Mugshot,"Perron. Jules. 31 ans, né le 9/8/62 à Saint-Denis (Seine). Journalier. Anarchiste. 75/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306950,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.331,false,true,306951,Photographs,Mugshot,"Perrot. Jean. 33 ans, né le 18/11/61 à Tulle (Corrèze). Cordonnier. Anarchiste. 2/7/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306951,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.332,false,true,306952,Photographs,Mugshot,"Peticolin. Henri. 23 ans, né le 8/6/71 à Goersdorf (Bas-Rhin). Vernisseur. Anarchiste. 2/7/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306952,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.333,false,true,306953,Photographs,Mugshot,"Pierlay. Louis, Victor. 53 ans, né à Paris XVIle. Sculpteur. Anarchiste. 8/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306953,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.334,false,true,306954,Photographs,Mugshot,"Pierre. Joseph, Adrien. 42 ans, né à Rouen (Seine-Inférieure). Canneleur de chaises. Anarchiste. 12/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306954,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.335,false,true,306955,Photographs,Mugshot,"Pichon. Ernest. 41 ans, né le 2/11/52 à Villard-Rixoire (Jura). Terrassier. Anarchiste. 2/7/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306955,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.336,false,true,306956,Photographs,Mugshot,"Pidoux. Jean. Victor. 45 ans, né au Châtelet (Seine-et-Marne). Estampeur de métaux. Anarchiste 2/7/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306956,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.337,false,true,306957,Photographs,Mugshot,"Pioger. Louise (veuve Lefèvre). 45 ans, né à Mézières (Sarthe). Giletière. Disposition du préfet. 8/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306957,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.338,false,true,306958,Photographs,Mugshot,"Pivat. Georges, Léopold. 34 ans, né à Vauxcié (Aisne). Tailleur d'habits. Anarchiste. 9/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306958,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.339,false,true,306959,Photographs,Mugshot,"Pivier. Alexandre. 53 ans, né à Rochevan (Savoie). Tailleur d'habits. Anarchiste. 7/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306959,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.340,false,true,306960,Photographs,Mugshot,"Poirier. Jacques, Étienne. 30 ans, né à Gien (Loiret). Garçon de marchand de vins. Anarchiste. 30/4/92.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1892,1892,1892,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306960,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.341,false,true,306961,Photographs,Mugshot,"Poisson. Georges. 38 ans, né à Boulogne (Seine). Chaudronnier. Anarchiste. 6/3/94",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306961,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.342,false,true,306962,Photographs,Mugshot,"Ponchia. Charles, Albino. 32 ans, né le 1/3/62 à Montanaro (Italie). Menuisier. Anarchiste. 2/7/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306962,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.343,false,true,306963,Photographs,Mugshot,"Pouget. Émile, Jean, Joseph. 31 ans, né le 12/10/60 à Rodez (Aveyron). Publiciste. Anarchiste. 26/4/92.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1892,1892,1892,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306963,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.344,false,true,306964,Photographs,Mugshot,"Pourry. François, Nicolas. 58 ans, né à Ars-sur-Moselle (Alsace-Lorraine). Ajusteur. Anarchiste. 3/7/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306964,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.345,false,true,306965,Photographs,Mugshot,"Raboin. Émile, Pierre. 41 ans, né à Ouzoir-sur-Loire (Loiret). Distillateur. Assoc. de malfaiteurs. 28/2/94",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306965,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.346,false,true,306966,Photographs,Mugshot,"Raboin. Paul, Pierre, Augustin. 32 ans, né à Ouzoir-sur-Loire (Loiret). Journalier. Anarchiste. 23/4/92.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1892,1892,1892,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306966,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.347,false,true,306967,Photographs,Mugshot,Rampin. Pierre. 3/7/94,,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306967,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.348,false,true,306968,Photographs,Mugshot,"Ravachol. François Claudius Kœnigstein. 33 ans, né à St-Chamond (Loire). Condamné le 27/4/92.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1892,1892,1892,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306968,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.349,false,true,306969,Photographs,Mugshot,"Ravinet. Gaston. 34 ans, né à Paris XIXe. Couvreur. Anarchiste. 1/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306969,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.350,false,true,306970,Photographs,Mugshot,Reclus. Paul. Pas d'informations sur la fiche. 23/12/93,,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1893,1893,1893,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306970,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.351,false,true,306971,Photographs,Mugshot,"Recco. Grégoire. 35 ans, né à Formia (Italie). Tailleur d'habits. Anarchiste. 11/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306971,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.352,false,true,306972,Photographs,Mugshot,"Remond. Émile, Adolphe. 34 ans, né à Bagnolet (Seine). Carrier. Anarchiste. 26/2/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306972,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.353,false,true,306973,Photographs,Mugshot,"Renard. Pierre, Alfred. 46 ans, né à Flain (Haute-Saône) le 27/4/46.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1880s–90s,1880,1899,Gelatin silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306973,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.354,false,true,306974,Photographs,Mugshot,"Renaud. Jules. 41 ans, né à Anteuil (Doubs). Cordonnier. Anarchiste. 27/2/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306974,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.355,false,true,306975,Photographs,Mugshot,"Retté. Adolphe. 30 ans, né à Paris IXe. Homme de lettre. Cris séditieux. 21/1/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306975,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.356,false,true,306976,Photographs,Mugshot,"Rey. Claude. 23 ans, né le 24/5/70 au Creusot (Saone & Loire). Ébéniste. Anarchiste. 12/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306976,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.357,false,true,306977,Photographs,Mugshot,"Reytinat. Jacques, François. 48 ans, né à Mouy (Oise). Colporteur. Anarchiste. 9/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306977,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.358,false,true,306978,Photographs,Mugshot,"Ricois. Charles-Victor. 48 ans, né le 21/3/46 à Orléans. Employé au Journal Officiel. Anarchiste. 2/7/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306978,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.359,false,true,306979,Photographs,Mugshot,"Ridou. Paul, François. 27 (ou 28) ans. Ébéniste. Anarchiste. 8/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306979,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.360,false,true,306980,Photographs,Mugshot,"Rigollet. Alexandre. 41 ans, né dans le Loir-et-Cher. Terrassier. Anarchiste.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306980,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.361,false,true,306981,Photographs,Mugshot,"Ripert. Thomas. 33 ans, né à Marseille. Cocher. Anarchiste. 5/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306981,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.362,false,true,306982,Photographs,Mugshot,"Robert. Fritz, Malatesta. 24 ans, Suisse. Excitation à la haine des citoyens les uns contre les autres, expulsé.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1880s–90s,1880,1899,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306982,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.363,false,true,306983,Photographs,Mugshot,"Robillard. Guillaume, Joseph. 24 ans, né le 17/11/68 à Vaucresson. Fondeur en cuivre. Anarchiste. 2/7/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306983,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.364,false,true,306984,Photographs,Mugshot,"Robyns. Émile. 36 ans, né à Lumone (Belgique). Marchand de Pierres (?). Anarchiste. 28/2/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306984,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.365,false,true,306985,Photographs,Mugshot,"Rochet. Théophile. 24 ans, né à Rennes (Ille-et-Vilaine) le 7/6/69. Cordonnier. Anarchiste. 18/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306985,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.366,false,true,306986,Photographs,Mugshot,"Rodskidski. Eloi, Jean-Baptiste. 37 ans, né à Paris Xlle 13/12/56. Mécanicien. Anarchiste. 2/7/94",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306986,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.367,false,true,306987,Photographs,Mugshot,"Roobin. Joseph. 40 ans, né à Bourgneuf (Loire-Inférieure). Terrassier. Anarchiste. 2/3/94",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306987,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.368,false,true,306988,Photographs,Mugshot,"Rossi. Guillaume. 20 ans, né le 8/3/71 à Biel (Italie). Serrurier. Vagabondage. 15/1/95.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1895,1895,1895,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306988,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.369,false,true,306989,Photographs,Mugshot,Roubichon. Jean-Marie. né le 14/6/52 à Vannes (Morbihan). Maçon. Anarchiste. 2/7/94.,,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306989,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.370,false,true,306990,Photographs,Mugshot,"Rouif. Léon. 27 ans, né à Villethierry (Yonne). Garçon boucher. Anarchiste. 1/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306990,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.371,false,true,306991,Photographs,Mugshot,"Roussel. Henri-Louis. 28 ans, né à Paris le 2/10/65. Ardoiseur. Anarchiste. 2/7/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306991,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.372,false,true,306992,Photographs,Mugshot,"Ruaud. Jean-Baptiste. 35 ans, né à Limoges le 28/7/58. Cordonnier. Anarchiste. 4/2/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306992,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.373,false,true,306993,Photographs,Mugshot,"Sachet. Edmond. 27 ans, né à Mézières (Ardennes). Typographe. Anarchiste. 1/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306993,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.374,false,true,306994,Photographs,Mugshot,"Savard. Henri-Auguste. 29 ans, né le 7/5/65 à Paris XXe. Ciseleur. Anarchiste. 2/7/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306994,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.375,false,true,306995,Photographs,Mugshot,"Saulnier. Alphonse, Joseph. 31 ans, né à Paris XXe. Tourneur sur bois. Anarchiste. 14/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306995,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.376,false,true,306996,Photographs,Mugshot,"Schaeffer. Ignace. 42 ans, né à Berheim (Bas-Rhin) le 31/10/51. Ébéniste. Anarchiste. 2/7/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306996,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.377,false,true,306997,Photographs,Mugshot,"Schaffer. Charles. 28 ans, né à Paris Xle. Ébéniste. Association de malfaiteurs. 2/3/94",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306997,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.378,false,true,306998,Photographs,Carte-de-visite; Mugshot,"Schouppe. Placide. (dit Ricken, Franz).",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1889–94,1889,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306998,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.379,false,true,306999,Photographs,Mugshot,"Schouppe. Placide. (dit Ricken, Franz). 31 ans, né à Dickenvenne (Belgique). Mécanicien. Vol.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1889,1889,1889,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306999,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.380,false,true,307000,Photographs,Mugshot,"Schouppe. Placide. (dit Ricken, Franz). 35 ans, né à Dickenvenne (Belgique). Mécanicien. Vol.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1893,1893,1893,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/307000,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.381,false,true,307001,Photographs,Mugshot,"Schrader. Minna, Appoline. 19 ans, née à Paris XIe. Sculpteur. Association de malfaiteurs. 24/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/307001,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.382,false,true,307002,Photographs,Mugshot,"Schulé. Armand. 21 ans, né le 28/2/73 à Choisy-le-Roi. Comptable. Anarchiste. 2/7/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/307002,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.383,false,true,307003,Photographs,Mugshot,"Schwartz. Auguste. 31 ans, né à Paris Xlle le 23/1/63. Maroquinier. Anarchiste. 2/7/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/307003,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.384,false,true,307004,Photographs,Mugshot,"Segard. Émilien (dit Segard Fils). 18 ans, né à Saloüel (Somme). Peintre en voitures. Anarchiste. 2/7/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/307004,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.385,false,true,307005,Photographs,Mugshot,"Segard. Philogone. 44 ans (35 ans inscrit sur la photo), né à Salond (Somme). Journaliste. Anarchiste.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1891–95,1891,1895,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/307005,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.386,false,true,307006,Photographs,Mugshot,"Selle. Louis- Désiré-Honoré. 31 ans, né à Bougival. Cordonnier. Anarchiste. 26/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/307006,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.387,false,true,307007,Photographs,Mugshot,"Sentenac. Phillipe. 36 ans, né à Soulan (Ariège). Menuisier. Anarchiste. 7/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/307007,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.388,false,true,307008,Photographs,Mugshot,"Serre. Auguste. 37 ans, né à Anonnay (Ardêche) le 13/10/56. Mégissier. Anarchiste. 20/1/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/307008,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.389,false,true,307009,Photographs,Mugshot,"Sicard. André. 32 ans, né à Nîmes le 25/10/62. Bijoutier. Anarchiste. 2/7/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/307009,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.390,false,true,307010,Photographs,Mugshot,"Sigel. Jacques. 24 ans, né à Kuttalsheim (Bas Rhin). Bijoutier. Anarchiste. 26/2/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/307010,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.391,false,true,307011,Photographs,Mugshot,"Simonin. Joseph. 26 ans, né à Saint-Maurice (Seine). Gainier. Anarchiste. 6/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/307011,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.392,false,true,307012,Photographs,Mugshot,"Soubrié. François. 39 ans, né à Livignac-le-Haut (Aveyron). Brûleur de café. Anarchiste. 14/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/307012,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.393,false,true,307013,Photographs,Mugshot,"Soubrier. Annette (femme Chericotti). 28 ans, née à Paris Ille. Coutière. Anarchiste. 25/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/307013,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.394,false,true,307014,Photographs,Mugshot,"Solier. Auguste. 18 ans, né le 3/3/75 à Cemery-la-Ville. Dessinateur. Anarchiste. 12/1/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/307014,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.395,false,true,307015,Photographs,Mugshot,"Sost. Edmond. 30 ans, né à Serqueux (Seine-Inférieure) le 15/11/64. Ciseleur. Anarchiste. 1/7/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/307015,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.396,false,true,307016,Photographs,Mugshot,"Soulage. Alphonse, Charles. 30 ans, né à Lyon. Menuisier. Anarchiste. 1/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/307016,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.397,false,true,307017,Photographs,Mugshot,"Soulas. Honoré, Jules. 33 ans, né à Chatillon le 10/12/55. Peintre en bâtiment. Anarchiste. 27/5/89.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1889,1889,1889,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/307017,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.398,false,true,307018,Photographs,Mugshot,"Spanagel. Alfred, Vincent. 17 ans, né le 27/5/77 à Paris. Serrurier. Anarchiste. 7/7/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/307018,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.399,false,true,307019,Photographs,Mugshot,"Spanagel. Emile, Ignace. 20 ans, né le 28/2/74 à Paris XVlle. Serrurier. Anarchiste. 7/7/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/307019,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.400,false,true,307020,Photographs,Mugshot,"Springer. François. 21 ans, né le 17/9/72 à Duisburg (Allemagne). Menuisier. Anarchiste. 17/7/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/307020,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.401,false,true,307021,Photographs,Mugshot,"Surgand. Alphonse. 21 ans, né à Lyon. Réparateur de chaussures. Anarchiste. 4/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/307021,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.402,false,true,307022,Photographs,Mugshot,"Tardieu. Marius (ou Maurice). 26 ans, né le 15/7/68 à Piolène (Vaucluse). Ébéniste. Anarchiste. 2/7/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/307022,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.403,false,true,307023,Photographs,Mugshot,"Tennevin. Alexandre. 48 ans, né à Paris. Comptable. Anarchiste. 19/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/307023,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.404,false,true,307024,Photographs,Mugshot,"Terrier. Julien, François. 45 ans, né à Saint-Laurent (Mayenne). Menuisier. Anarchiste. 14/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/307024,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.405,false,true,307025,Photographs,Mugshot,"Theriez. Louis (ou Perriez). 35 ans, né le 29/8/58 à Paris. Ébéniste. Anarchiste. 16/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/307025,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.406,false,true,307026,Photographs,Mugshot,"Theuriet. Jean Baptiste. 30 ans, né à Lyon. Gérant-coiffeur. Anarchiste. 2/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/307026,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.407,false,true,307027,Photographs,Mugshot,"Thibivilliers. Eugène. 23 ans, né à Pinseux-le-Haut-Verger (Oise). Polisseur de métaux. Cris séditieux. 5/3/94",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/307027,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.408,false,true,307028,Photographs,Mugshot,"Thiebaut. Eugène. 35 (ou 36) ans, né à Château-Salin (Menthe & Moselle). Couvreur. Anarchiste. 3/3/94",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/307028,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.409,false,true,307029,Photographs,Mugshot,"Thirion. Louis, Joseph. 31 ans, né à Autrey (Vosges). Journaliste. Anarchiste. 4/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/307029,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.410,false,true,307030,Photographs,Mugshot,"Tiran. Arthur, Théodore. 29 ans, né à Briec (Finistère). Serrurier. Anarchiste. 26/2/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/307030,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.411,false,true,307031,Photographs,Mugshot,"Toesca. Calixte. 28 ans, Tour de Breuil (Alpes-Mar.). Étudiant en médecine. Association de malfaiteurs. 27/2/94",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/307031,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.412,false,true,307032,Photographs,Mugshot,"Toulet. Guy, Flavien. 41 ans, né à Boufler (Somme). Entrepreneur de peinture. Anarchiste. 23/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/307032,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.413,false,true,307033,Photographs,Mugshot,"Tournadre. Jacques (ou Eugène). 32 ans, né à Marchal (Cantal). Journaliste. Anarchiste. 3/3/94",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/307033,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.414,false,true,307034,Photographs,Mugshot,"Tournan. Pierre. 49 ans, né à Bouzouville (Moselle). Fabricant de couronnes. Anarchiste. 6/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/307034,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.415,false,true,307035,Photographs,Mugshot,"Tramcourt. Albert. 27 ans, né le 10/12/66 à Creil. Mécanicien. Anarchiste. 15/1/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/307035,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.416,false,true,307036,Photographs,Mugshot,"Tropini. Esprit, Antoine. 35 ans, né à San-Bueri (Italie). Tourneur. Anarchiste. 2/7/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/307036,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.417,false,true,307037,Photographs,Mugshot,"Trucano. Victorine (veuve Belloti). 54 ans, né à St Maurier (Italie). Chapelier. Vol anarchiste. 19/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/307037,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.418,false,true,307038,Photographs,Mugshot,"Vaury. Charles, Joseph. 43 (ou 44) ans, né le 31/3/59 à Sedan. Mécanicien. Anarchiste. 16/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/307038,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.419,false,true,307039,Photographs,Mugshot,"Vendel. Jules. 34 (ou 33) ans, né le 2/4/61 à Chevry (Ain). Garçon de cuisine. Anarchiste. 2/7/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/307039,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.420,false,true,307040,Photographs,Mugshot,"Véret. 0ctave-Jean. 19 ans, né à Paris XXe. Photographe. Anarchiste. 2/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/307040,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.421,false,true,307041,Photographs,Mugshot,"Veysseire. Michel. 25 ans, né à Montreuil. Journaliste. Anarchiste. 5/3/94",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/307041,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.422,false,true,307042,Photographs,Mugshot,"Vidal. Guillaume. 37 ans, né à Tellière (Puy de Dôme). Cocher. Anarchiste. 10/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/307042,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.423,false,true,307043,Photographs,Mugshot,"Vignaud. Antoine. 32 ans, né à Cussey (Allier). Cordonnier. Vol anarchiste. 21/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/307043,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.424,false,true,307044,Photographs,Mugshot,"Villa. Jean. 29 ans, né à Farini d'Olma (Italie). Manoeuvre. Anarchiste. 2/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/307044,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.425,false,true,307045,Photographs,Mugshot,"Villanneau. Henri, Fernand. 35 ans, né le 11/3/59 à Poitiers. Clerc de notaire. Anarchiste. 2/7/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/307045,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.426,false,true,307046,Photographs,Mugshot,"Villetard. Jules, Pierre. 42 ans, né à Ligny-le-Chastel (Yonne). Terrassier. Anarchiste. 5/3/94",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/307046,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.427,false,true,307047,Photographs,Mugshot,"Vuagniaux. Alfred. 41 ans, né à Vucheron (Suisse). Cordonnier. Anarchiste. 2/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/307047,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.428,false,true,307048,Photographs,Mugshot,"Wagner. Paul, Louis. 38 ans, né le 14/10/55. Ébéniste. Anarchiste. 2/7/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/307048,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.429,false,true,307049,Photographs,Mugshot,"Wallays. Charles. 29 ans, né à Lille. Tailleur d'habits. Anarchiste. 9/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/307049,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.430,false,true,307050,Photographs,Mugshot,"Widcoq. Alfred, François, Adolphe. 32 ans, né à Fressenneville (Somme). Mécanicien. Anarchiste. 10/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/307050,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.431,false,true,307051,Photographs,Mugshot,"Widcoq. Fulgence, Ignace. 36 ans, né à Fressenneville (Somme). Mécanicien. Anarchiste. 10/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/307051,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.432,false,true,307052,Photographs,Mugshot,"Willems. Charles, Louis. 52 ans, né à Houndchocte (Nord). Tailleur d'habits. Anarchiste. 18/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/307052,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.433,false,true,307053,Photographs,Mugshot,"Zanini. Marie (veuve Milanaccio). 28 ans, née à Turin (Italie). Cuisinière. Vol. 18/3/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/307053,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.434,false,true,307054,Photographs,Mugshot,"Zisly. Henri, Gabriel. 21 ans, né à Paris IVe. Employé de commerce. Anarchiste. 26/2/94.",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",,1853,1914,1894,1894,1894,Albumen silver print from glass negative,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/307054,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1981.1229.51.1,false,true,263192,Photographs,Photograph,Pekin. No. 923,,,,,,Artist,Attributed to,Lai Fong,"Chinese, 1839–1890",,"Lai, Fong",,1839,1890,1867,1867,1867,Albumen silver print from glass negative,,"Robert O. Dougan Collection, Gift of Warner Communications Inc., 1981",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/263192,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.494.13,false,true,289285,Photographs,Photograph,"A Portion of the Citywall, Foochow",,,,,,Artist,,Lai Fong,"Chinese, 1839–1890",,"Lai, Fong",,1839,1890,ca. 1869,1869,1869,Albumen silver print from glass negative,Image: 8 1/8 × 11 1/2 in. (20.7 × 29.2 cm),"Gilman Collection, Purchase, Robert Rosenkranz Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/289285,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.494.29,false,true,289301,Photographs,Photograph,Left Road up to Yuen foo Monastery,,,,,,Artist,,Lai Fong,"Chinese, 1839–1890",,"Lai, Fong",,1839,1890,ca. 1869,1869,1869,Albumen silver print from glass negative,Image: 8 7/8 × 11 5/16 in. (22.6 × 28.7 cm),"Gilman Collection, Purchase, Robert Rosenkranz Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/289301,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.494.31,false,true,289303,Photographs,Photograph,Temple of Tai-wang at Wu ü near Sing Chang Tea Mart,,,,,,Artist,,Lai Fong,"Chinese, 1839–1890",,"Lai, Fong",,1839,1890,ca. 1869,1869,1869,Albumen silver print from glass negative,Image: 8 1/16 × 11 1/4 in. (20.5 × 28.6 cm),"Gilman Collection, Purchase, Robert Rosenkranz Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/289303,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.494.35,false,true,289307,Photographs,Photograph,"The Grand Stand, Foochow",,,,,,Artist,,Lai Fong,"Chinese, 1839–1890",,"Lai, Fong",,1839,1890,ca. 1869,1869,1869,Albumen silver print from glass negative,Image: 7 15/16 × 11 5/16 in. (20.2 × 28.8 cm),"Gilman Collection, Purchase, Robert Rosenkranz Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/289307,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.494.38,false,true,289310,Photographs,Photograph,Culling Tea,,,,,,Artist,Attributed to,Lai Fong,"Chinese, 1839–1890",,"Lai, Fong",,1839,1890,ca. 1869,1869,1869,Albumen silver print from glass negative,Image: 6 15/16 × 9 3/8 in. (17.6 × 23.8 cm),"Gilman Collection, Purchase, Robert Rosenkranz Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/289310,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2010.423.1,false,true,294322,Photographs,Postcard,"Astapovo Train Station, On the Right is the House in Which Lev Nikolayevich (Tolstoy) Died",,,,,,Artist,,Aleksey Ivanovich Saveliev,"Russian, 1883–1923",,"Saveliev, Aleksey Ivanovich",,1883,1923,1910,1910,1910,Gelatin silver print,Image: 8.9 x 13.3 cm (3 1/2 x 5 1/4 in.),"Gift of Pierre Apraxine, 2010",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/294322,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2010.423.2,false,true,294323,Photographs,Postcard,The Lowering of the Coffin into the Grave with Kneeling Mourners,,,,,,Artist,,Aleksey Ivanovich Saveliev,"Russian, 1883–1923",,"Saveliev, Aleksey Ivanovich",,1883,1923,"November 9, 1910",1910,1910,Gelatin silver print,Image: 8.9 x 13.3 cm (3 1/2 x 5 1/4 in.),"Gift of Pierre Apraxine, 2010",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/294323,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2010.423.3,false,true,294324,Photographs,Postcard,Deputation of the Yasno-Polyanskyi Peasants,,,,,,Artist,,Aleksey Ivanovich Saveliev,"Russian, 1883–1923",,"Saveliev, Aleksey Ivanovich",,1883,1923,1910,1910,1910,Gelatin silver print,Image: 8.9 x 13.3 cm (3 1/2 x 5 1/4 in.),"Gift of Pierre Apraxine, 2010",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/294324,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2010.423.4,false,true,294325,Photographs,Postcard,"En Route to the House, Visible in the Distance is the Village of Yasnaya Polyana",,,,,,Artist,,Aleksey Ivanovich Saveliev,"Russian, 1883–1923",,"Saveliev, Aleksey Ivanovich",,1883,1923,1910,1910,1910,Gelatin silver print,Image: 8.9 x 13.3 cm (3 1/2 x 5 1/4 in.),"Gift of Pierre Apraxine, 2010",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/294325,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2010.423.5,false,true,294326,Photographs,Postcard,At the Prepared Grave,,,,,,Artist,,Aleksey Ivanovich Saveliev,"Russian, 1883–1923",,"Saveliev, Aleksey Ivanovich",,1883,1923,1910,1910,1910,Gelatin silver print,Image: 8.9 x 13.3 cm (3 1/2 x 5 1/4 in.),"Gift of Pierre Apraxine, 2010",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/294326,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2010.423.6,false,true,294327,Photographs,Postcard,Peasant Carts with Funeral Wreaths,,,,,,Artist,,Aleksey Ivanovich Saveliev,"Russian, 1883–1923",,"Saveliev, Aleksey Ivanovich",,1883,1923,1910,1910,1910,Gelatin silver print,Image: 8.9 x 13.3 cm (3 1/2 x 5 1/4 in.),"Gift of Pierre Apraxine, 2010",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/294327,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.1033,false,true,711640,Photographs,Photograph,[Civil War View],,,,,,Artist,,Thomas C. Roche,"American, 1826–1895",", et al","Roche, Thomas C.",,1826,1895,1860s,1860,1869,Albumen silver print from glass negative,,"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/711640,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.1034,false,true,711641,Photographs,Photograph,[Civil War View],,,,,,Artist,,Thomas C. Roche,"American, 1826–1895",", et al","Roche, Thomas C.",,1826,1895,1860s,1860,1869,Albumen silver print from glass negative,,"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/711641,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.1035,false,true,711642,Photographs,Photograph,[Civil War View],,,,,,Artist,,Thomas C. Roche,"American, 1826–1895",", et al","Roche, Thomas C.",,1826,1895,1860s,1860,1869,Albumen silver print from glass negative,,"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/711642,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.1036,false,true,711643,Photographs,Photograph,[Civil War View],,,,,,Artist,,Thomas C. Roche,"American, 1826–1895",", et al","Roche, Thomas C.",,1826,1895,1860s,1860,1869,Albumen silver print from glass negative,,"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/711643,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.1037,false,true,711644,Photographs,Photograph,[Civil War View],,,,,,Artist,,Thomas C. Roche,"American, 1826–1895",", et al","Roche, Thomas C.",,1826,1895,1860s,1860,1869,Albumen silver print from glass negative,,"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/711644,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.1038,false,true,711645,Photographs,Photograph,[Civil War View],,,,,,Artist,,Thomas C. Roche,"American, 1826–1895",", et al","Roche, Thomas C.",,1826,1895,1860s,1860,1869,Albumen silver print from glass negative,,"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/711645,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.1039,false,true,711646,Photographs,Photograph,[Civil War View],,,,,,Artist,,Thomas C. Roche,"American, 1826–1895",", et al","Roche, Thomas C.",,1826,1895,1860s,1860,1869,Albumen silver print from glass negative,,"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/711646,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.1040,false,true,711647,Photographs,Photograph,[Civil War View],,,,,,Artist,,Thomas C. Roche,"American, 1826–1895",", et al","Roche, Thomas C.",,1826,1895,1860s,1860,1869,Albumen silver print from glass negative,,"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/711647,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.1041,false,true,711648,Photographs,Photograph,[Civil War View],,,,,,Artist,,Thomas C. Roche,"American, 1826–1895",", et al","Roche, Thomas C.",,1826,1895,1860s,1860,1869,Albumen silver print from glass negative,,"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/711648,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.1042,false,true,711649,Photographs,Photograph,[Civil War View],,,,,,Artist,,Thomas C. Roche,"American, 1826–1895",", et al","Roche, Thomas C.",,1826,1895,1860s,1860,1869,Albumen silver print from glass negative,,"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/711649,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.1043,false,true,711650,Photographs,Photograph,[Civil War View],,,,,,Artist,,Thomas C. Roche,"American, 1826–1895",", et al","Roche, Thomas C.",,1826,1895,1860s,1860,1869,Albumen silver print from glass negative,,"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/711650,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.1044,false,true,711651,Photographs,Photograph,[Civil War View],,,,,,Artist,,Thomas C. Roche,"American, 1826–1895",", et al","Roche, Thomas C.",,1826,1895,1860s,1860,1869,Albumen silver print from glass negative,,"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/711651,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.1045,false,true,711652,Photographs,Photograph,[Civil War View],,,,,,Artist,,Thomas C. Roche,"American, 1826–1895",", et al","Roche, Thomas C.",,1826,1895,1860s,1860,1869,Albumen silver print from glass negative,,"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/711652,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.1046,false,true,711653,Photographs,Photograph,[Civil War View],,,,,,Artist,,Thomas C. Roche,"American, 1826–1895",", et al","Roche, Thomas C.",,1826,1895,1860s,1860,1869,Albumen silver print from glass negative,,"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/711653,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.1047,false,true,711654,Photographs,Photograph,[Civil War View],,,,,,Artist,,Thomas C. Roche,"American, 1826–1895",", et al","Roche, Thomas C.",,1826,1895,1860s,1860,1869,Albumen silver print from glass negative,,"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/711654,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.1048,false,true,711655,Photographs,Photograph,[Civil War View],,,,,,Artist,,Thomas C. Roche,"American, 1826–1895",", et al","Roche, Thomas C.",,1826,1895,1860s,1860,1869,Albumen silver print from glass negative,,"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/711655,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.1049,false,true,711656,Photographs,Photograph,[Civil War View],,,,,,Artist,,Thomas C. Roche,"American, 1826–1895",", et al","Roche, Thomas C.",,1826,1895,1860s,1860,1869,Albumen silver print from glass negative,,"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/711656,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.1050,false,true,711657,Photographs,Photograph,[Civil War View],,,,,,Artist,,Thomas C. Roche,"American, 1826–1895",", et al","Roche, Thomas C.",,1826,1895,1860s,1860,1869,Albumen silver print from glass negative,,"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/711657,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.1054,false,true,711661,Photographs,Photograph,[Civil War View],,,,,,Artist,,Thomas C. Roche,"American, 1826–1895",", et al","Roche, Thomas C.",,1826,1895,1860s,1860,1869,Albumen silver print from glass negative,,"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/711661,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.556(1),false,true,732748,Photographs,Photograph,Old Dominium and Uncle Tom's Tavern. Calaveras Grove,,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",,1829,1916,ca. 1878,1876,1880,Albumen silver print from glass negative,"Image: 12.5 x 12.5 cm (4 15/16 x 4 15/16 in.), circular Album page: 24 x 25.1 cm (9 7/16 x 9 7/8 in.)","Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/732748,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.556(2),false,true,732749,Photographs,Photograph,The Sentinels - Calaveras Grove,,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",,1829,1916,ca. 1878,1876,1880,Albumen silver print from glass negative,"Image: 12.5 x 12.5 cm (4 15/16 x 4 15/16 in.), circular Album page: 24 x 25.1 cm (9 7/16 x 9 7/8 in.)","Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/732749,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.556(3),false,true,732750,Photographs,Photograph,The Mother of the Forest - Calaveras Grove,,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",,1829,1916,ca. 1878,1876,1880,Albumen silver print from glass negative,"Image: 12.5 x 12.5 cm (4 15/16 x 4 15/16 in.), circular Album page: 24 x 25.1 cm (9 7/16 x 9 7/8 in.)","Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/732750,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.556(4),false,true,732751,Photographs,Photograph,The Miners Cabin - Calaveras Grove,,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",,1829,1916,ca. 1878,1876,1880,Albumen silver print from glass negative,"Image: 12.5 x 12.5 cm (4 15/16 x 4 15/16 in.), circular Album page: 24 x 25.1 cm (9 7/16 x 9 7/8 in.)","Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/732751,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.556(5),false,true,732752,Photographs,Photograph,The Mother of the Forest From the Father of the Forest - Calavaras Grove,,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",,1829,1916,ca. 1878,1876,1880,Albumen silver print from glass negative,"Image: 12.5 x 12.5 cm (4 15/16 x 4 15/16 in.), circular Album page: 24 x 25.1 cm (9 7/16 x 9 7/8 in.)","Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/732752,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.556(6),false,true,732753,Photographs,Photograph,The Mammoth Grove Hotel from the Grove - Calaveras,,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",,1829,1916,ca. 1878,1876,1880,Albumen silver print from glass negative,"Image: 12.5 x 12.5 cm (4 15/16 x 4 15/16 in.), circular Album page: 24 x 25.1 cm (9 7/16 x 9 7/8 in.)","Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/732753,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.556(7),false,true,732754,Photographs,Photograph,Looking up Among the Sugar Pines - Calaveras Grove,,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",,1829,1916,ca. 1878,1876,1880,Albumen silver print from glass negative,"Image: 12.5 x 12.5 cm (4 15/16 x 4 15/16 in.), circular Album page: 24 x 25.1 cm (9 7/16 x 9 7/8 in.)","Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/732754,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.556(8),false,true,732820,Photographs,Photograph,Pioneers Cabin - Calaveras Grove,,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",,1829,1916,ca. 1878,1876,1880,Albumen silver print from glass negative,"Image: 12.5 x 12.5 cm (4 15/16 x 4 15/16 in.), circular Album page: 24 x 25.1 cm (9 7/16 x 9 7/8 in.)","Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/732820,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.556(9),false,true,732819,Photographs,Photograph,The Three Graces Seen Through the Bryant and Seward Calaveras Grove,,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",,1829,1916,ca. 1878,1876,1880,Albumen silver print from glass negative,"Image: 12.5 x 12.5 cm (4 15/16 x 4 15/16 in.), circular Album page: 24 x 25.1 cm (9 7/16 x 9 7/8 in.)","Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/732819,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.556(10),false,true,732818,Photographs,Photograph,James King of William. Keyston State etc. Calaveras Grove,,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",,1829,1916,ca. 1878,1876,1880,Albumen silver print from glass negative,"Image: 12.5 x 12.5 cm (4 15/16 x 4 15/16 in.), circular Album page: 24 x 25.1 cm (9 7/16 x 9 7/8 in.)","Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/732818,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.556(11),false,true,732817,Photographs,Photograph,The Pride of the Forest - Calaveras Grove,,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",,1829,1916,ca. 1878,1876,1880,Albumen silver print from glass negative,"Image: 12.5 x 12.5 cm (4 15/16 x 4 15/16 in.), circular Album page: 24 x 25.1 cm (9 7/16 x 9 7/8 in.)","Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/732817,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.556(12),false,true,732816,Photographs,Photograph,Eagle's Wing - Calaveras Grove,,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",,1829,1916,ca. 1878,1876,1880,Albumen silver print from glass negative,"Image: 12.5 x 12.5 cm (4 15/16 x 4 15/16 in.), circular Album page: 24 x 25.1 cm (9 7/16 x 9 7/8 in.)","Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/732816,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.556(13),false,true,732815,Photographs,Photograph,The Father of the Forest - The Horse Back Side. Calaveras Grove,,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",,1829,1916,ca. 1878,1876,1880,Albumen silver print from glass negative,"Image: 12.5 x 12.5 cm (4 15/16 x 4 15/16 in.), circular Album page: 24 x 25.1 cm (9 7/16 x 9 7/8 in.)","Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/732815,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.556(14),false,true,732814,Photographs,Photograph,Mammoth Three Grove Hotel Calaveras Co Cal.,,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",,1829,1916,ca. 1878,1876,1880,Albumen silver print from glass negative,"Image: 12.5 x 12.5 cm (4 15/16 x 4 15/16 in.), circular Album page: 24 x 25.1 cm (9 7/16 x 9 7/8 in.)","Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/732814,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.556(15),false,true,732813,Photographs,Photograph,None,,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",,1829,1916,ca. 1878,1876,1880,Albumen silver print from glass negative,"Image: 12.5 x 12.5 cm (4 15/16 x 4 15/16 in.), circular Album page: 24 x 25.1 cm (9 7/16 x 9 7/8 in.)","Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/732813,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.556(16),false,true,732812,Photographs,Photograph,W. C. Bryant - Calaveras Grove,,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",,1829,1916,ca. 1878,1876,1880,Albumen silver print from glass negative,"Image: 12.5 x 12.5 cm (4 15/16 x 4 15/16 in.), circular Album page: 24 x 25.1 cm (9 7/16 x 9 7/8 in.)","Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/732812,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.556(17),false,true,732811,Photographs,Photograph,The Father of the Forest - C. Grove,,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",,1829,1916,ca. 1878,1876,1880,Albumen silver print from glass negative,"Image: 12.5 x 12.5 cm (4 15/16 x 4 15/16 in.), circular Album page: 24 x 25.1 cm (9 7/16 x 9 7/8 in.)","Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/732811,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.556(18),false,true,732810,Photographs,Photograph,The Empire State - C. Grove,,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",,1829,1916,ca. 1878,1876,1880,Albumen silver print from glass negative,"Image: 12.5 x 12.5 cm (4 15/16 x 4 15/16 in.), circular Album page: 24 x 25.1 cm (9 7/16 x 9 7/8 in.)","Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/732810,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.556(19),false,true,732809,Photographs,Photograph,Auld Reckie - C. Grove,,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",,1829,1916,ca. 1878,1876,1880,Albumen silver print from glass negative,"Image: 12.5 x 12.5 cm (4 15/16 x 4 15/16 in.), circular Album page: 24 x 25.1 cm (9 7/16 x 9 7/8 in.)","Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/732809,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.556(20),false,true,732808,Photographs,Photograph,Empire State. C. Grove,,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",,1829,1916,ca. 1878,1876,1880,Albumen silver print from glass negative,"Image: 12.5 x 12.5 cm (4 15/16 x 4 15/16 in.), circular Album page: 24 x 25.1 cm (9 7/16 x 9 7/8 in.)","Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/732808,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.556(21),false,true,732807,Photographs,Photograph,The Father of the Forest 450 ft C. Grove,,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",,1829,1916,ca. 1878,1876,1880,Albumen silver print from glass negative,"Image: 12.5 x 12.5 cm (4 15/16 x 4 15/16 in.), circular Album page: 24 x 25.1 cm (9 7/16 x 9 7/8 in.)","Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/732807,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.556(22),false,true,732806,Photographs,Photograph,Auld Reckie. C. Grove,,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",,1829,1916,ca. 1878,1876,1880,Albumen silver print from glass negative,"Image: 12.5 x 12.5 cm (4 15/16 x 4 15/16 in.), circular Album page: 24 x 25.1 cm (9 7/16 x 9 7/8 in.)","Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/732806,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.556(23),false,true,732805,Photographs,Photograph,"Interior of Pavilion Built on the Stump of the Tree, C. Grove",,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",,1829,1916,ca. 1878,1876,1880,Albumen silver print from glass negative,"Image: 12.5 x 12.5 cm (4 15/16 x 4 15/16 in.), circular Album page: 24 x 25.1 cm (9 7/16 x 9 7/8 in.)","Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/732805,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.556(24),false,true,732804,Photographs,Photograph,The Sentinels. C. Grove,,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",,1829,1916,ca. 1878,1876,1880,Albumen silver print from glass negative,"Image: 12.5 x 12.5 cm (4 15/16 x 4 15/16 in.), circular Album page: 24 x 25.1 cm (9 7/16 x 9 7/8 in.)","Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/732804,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.556(25),false,true,732803,Photographs,Photograph,Warm Springs Hotel Lake Tahoe,,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",,1829,1916,ca. 1878,1876,1880,Albumen silver print from glass negative,"Image: 12.5 x 12.5 cm (4 15/16 x 4 15/16 in.), circular Album page: 24 x 25.1 cm (9 7/16 x 9 7/8 in.)","Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/732803,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.556(26),false,true,732802,Photographs,Photograph,"The Father of the Forest 450 Ft Long, C. Grove",,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",,1829,1916,ca. 1878,1876,1880,Albumen silver print from glass negative,"Image: 12.5 x 12.5 cm (4 15/16 x 4 15/16 in.), circular Album page: 24 x 25.1 cm (9 7/16 x 9 7/8 in.)","Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/732802,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.556(27),false,true,732801,Photographs,Photograph,Donner Lake. C. P. R. R,,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",,1829,1916,ca. 1878,1876,1880,Albumen silver print from glass negative,"Image: 12.5 x 12.5 cm (4 15/16 x 4 15/16 in.), circular Album page: 24 x 25.1 cm (9 7/16 x 9 7/8 in.)","Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/732801,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.556(28),false,true,732800,Photographs,Photograph,In the Yosemite Valley,,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",,1829,1916,ca. 1878,1876,1880,Albumen silver print from glass negative,"Image: 12.5 x 12.5 cm (4 15/16 x 4 15/16 in.), circular Album page: 24 x 25.1 cm (9 7/16 x 9 7/8 in.)","Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/732800,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.556(29),false,true,732799,Photographs,Photograph,"[Yosemite National Park, California]",,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",,1829,1916,ca. 1878,1876,1880,Albumen silver print from glass negative,"Image: 12.5 x 12.5 cm (4 15/16 x 4 15/16 in.), circular Album page: 24 x 25.1 cm (9 7/16 x 9 7/8 in.)","Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/732799,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.556(30),false,true,732798,Photographs,Photograph,"[Yosemite National Park, California]",,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",,1829,1916,ca. 1878,1876,1880,Albumen silver print from glass negative,"Image: 12.5 x 12.5 cm (4 15/16 x 4 15/16 in.), circular Album page: 24 x 25.1 cm (9 7/16 x 9 7/8 in.)","Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/732798,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.556(31),false,true,732797,Photographs,Photograph,"[Yosemite National Park, California]",,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",,1829,1916,ca. 1878,1876,1880,Albumen silver print from glass negative,"Image: 12.5 x 12.5 cm (4 15/16 x 4 15/16 in.), circular Album page: 24 x 25.1 cm (9 7/16 x 9 7/8 in.)","Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/732797,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.556(32),false,true,732796,Photographs,Photograph,"[Yosemite National Park, California]",,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",,1829,1916,ca. 1878,1876,1880,Albumen silver print from glass negative,"Image: 12.5 x 12.5 cm (4 15/16 x 4 15/16 in.), circular Album page: 24 x 25.1 cm (9 7/16 x 9 7/8 in.)","Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/732796,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.556(33),false,true,732795,Photographs,Photograph,"[Yosemite National Park, California]",,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",,1829,1916,ca. 1878,1876,1880,Albumen silver print from glass negative,"Image: 12.5 x 12.5 cm (4 15/16 x 4 15/16 in.), circular Album page: 24 x 25.1 cm (9 7/16 x 9 7/8 in.)","Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/732795,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.556(34),false,true,732794,Photographs,Photograph,"[Yosemite National Park, California]",,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",,1829,1916,ca. 1878,1876,1880,Albumen silver print from glass negative,"Image: 12.5 x 12.5 cm (4 15/16 x 4 15/16 in.), circular Album page: 24 x 25.1 cm (9 7/16 x 9 7/8 in.)","Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/732794,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.556(35),false,true,732793,Photographs,Photograph,"[Yosemite National Park, California]",,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",,1829,1916,ca. 1878,1876,1880,Albumen silver print from glass negative,"Image: 12.5 x 12.5 cm (4 15/16 x 4 15/16 in.), circular Album page: 24 x 25.1 cm (9 7/16 x 9 7/8 in.)","Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/732793,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.556(36),false,true,732792,Photographs,Photograph,"[Yosemite National Park, California]",,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",,1829,1916,ca. 1878,1876,1880,Albumen silver print from glass negative,"Image: 12.5 x 12.5 cm (4 15/16 x 4 15/16 in.), circular Album page: 24 x 25.1 cm (9 7/16 x 9 7/8 in.)","Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/732792,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.556(37),false,true,732791,Photographs,Photograph,"[Yosemite National Park, California]",,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",,1829,1916,ca. 1878,1876,1880,Albumen silver print from glass negative,"Image: 12.5 x 12.5 cm (4 15/16 x 4 15/16 in.), circular Album page: 24 x 25.1 cm (9 7/16 x 9 7/8 in.)","Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/732791,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.556(38),false,true,732790,Photographs,Photograph,"[Yosemite National Park, California]",,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",,1829,1916,ca. 1878,1876,1880,Albumen silver print from glass negative,"Image: 12.5 x 12.5 cm (4 15/16 x 4 15/16 in.), circular Album page: 24 x 25.1 cm (9 7/16 x 9 7/8 in.)","Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/732790,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.556(39),false,true,732789,Photographs,Photograph,"[Yosemite National Park, California]",,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",,1829,1916,ca. 1878,1876,1880,Albumen silver print from glass negative,"Image: 12.5 x 12.5 cm (4 15/16 x 4 15/16 in.), circular Album page: 24 x 25.1 cm (9 7/16 x 9 7/8 in.)","Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/732789,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.556(40),false,true,732788,Photographs,Photograph,"[Yosemite National Park, California]",,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",,1829,1916,ca. 1878,1876,1880,Albumen silver print from glass negative,"Image: 12.5 x 12.5 cm (4 15/16 x 4 15/16 in.), circular Album page: 24 x 25.1 cm (9 7/16 x 9 7/8 in.)","Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/732788,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.556(41),false,true,732787,Photographs,Photograph,"[Yosemite National Park, California]",,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",,1829,1916,ca. 1878,1876,1880,Albumen silver print from glass negative,"Image: 12.5 x 12.5 cm (4 15/16 x 4 15/16 in.), circular Album page: 24 x 25.1 cm (9 7/16 x 9 7/8 in.)","Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/732787,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.556(42),false,true,732786,Photographs,Photograph,"[Yosemite National Park, California]",,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",,1829,1916,ca. 1878,1876,1880,Albumen silver print from glass negative,"Image: 12.5 x 12.5 cm (4 15/16 x 4 15/16 in.), circular Album page: 24 x 25.1 cm (9 7/16 x 9 7/8 in.)","Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/732786,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.556(43),false,true,732785,Photographs,Photograph,"[Yosemite National Park, California]",,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",,1829,1916,ca. 1878,1876,1880,Albumen silver print from glass negative,"Image: 12.5 x 12.5 cm (4 15/16 x 4 15/16 in.), circular Album page: 24 x 25.1 cm (9 7/16 x 9 7/8 in.)","Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/732785,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.556(44),false,true,732784,Photographs,Photograph,"[Yosemite National Park, California]",,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",,1829,1916,ca. 1878,1876,1880,Albumen silver print from glass negative,"Image: 12.5 x 12.5 cm (4 15/16 x 4 15/16 in.), circular Album page: 24 x 25.1 cm (9 7/16 x 9 7/8 in.)","Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/732784,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.556(45),false,true,732783,Photographs,Photograph,"[Yosemite National Park, California]",,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",,1829,1916,ca. 1878,1876,1880,Albumen silver print from glass negative,"Image: 12.5 x 12.5 cm (4 15/16 x 4 15/16 in.), circular Album page: 24 x 25.1 cm (9 7/16 x 9 7/8 in.)","Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/732783,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.556(46),false,true,732782,Photographs,Photograph,"[Yosemite National Park, California]",,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",,1829,1916,ca. 1878,1876,1880,Albumen silver print from glass negative,"Image: 12.5 x 12.5 cm (4 15/16 x 4 15/16 in.), circular Album page: 24 x 25.1 cm (9 7/16 x 9 7/8 in.)","Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/732782,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.556(47),false,true,732781,Photographs,Photograph,"[Yosemite National Park, California]",,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",,1829,1916,ca. 1878,1876,1880,Albumen silver print from glass negative,"Image: 12.5 x 12.5 cm (4 15/16 x 4 15/16 in.), circular Album page: 24 x 25.1 cm (9 7/16 x 9 7/8 in.)","Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/732781,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.556(48),false,true,732780,Photographs,Photograph,"[Yosemite National Park, California]",,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",,1829,1916,ca. 1878,1876,1880,Albumen silver print from glass negative,"Image: 12.5 x 12.5 cm (4 15/16 x 4 15/16 in.), circular Album page: 24 x 25.1 cm (9 7/16 x 9 7/8 in.)","Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/732780,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.556(49),false,true,732779,Photographs,Photograph,"[Yosemite National Park, California]",,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",,1829,1916,ca. 1878,1876,1880,Albumen silver print from glass negative,"Image: 12.5 x 12.5 cm (4 15/16 x 4 15/16 in.), circular Album page: 24 x 25.1 cm (9 7/16 x 9 7/8 in.)","Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/732779,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.556(50),false,true,732778,Photographs,Photograph,"[Yosemite National Park, California]",,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",,1829,1916,ca. 1878,1876,1880,Albumen silver print from glass negative,"Image: 12.5 x 12.5 cm (4 15/16 x 4 15/16 in.), circular Album page: 24 x 25.1 cm (9 7/16 x 9 7/8 in.)","Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/732778,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.556(51),false,true,732777,Photographs,Photograph,"[Yosemite National Park, California]",,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",,1829,1916,ca. 1878,1876,1880,Albumen silver print from glass negative,"Image: 12.5 x 12.5 cm (4 15/16 x 4 15/16 in.), circular Album page: 24 x 25.1 cm (9 7/16 x 9 7/8 in.)","Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/732777,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.556(52),false,true,732776,Photographs,Photograph,"[Yosemite National Park, California]",,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",,1829,1916,ca. 1878,1876,1880,Albumen silver print from glass negative,"Image: 12.5 x 12.5 cm (4 15/16 x 4 15/16 in.), circular Album page: 24 x 25.1 cm (9 7/16 x 9 7/8 in.)","Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/732776,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.556(53),false,true,732775,Photographs,Photograph,"[Yosemite National Park, California]",,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",,1829,1916,ca. 1878,1876,1880,Albumen silver print from glass negative,"Image: 12.5 x 12.5 cm (4 15/16 x 4 15/16 in.), circular Album page: 24 x 25.1 cm (9 7/16 x 9 7/8 in.)","Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/732775,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.556(54),false,true,732774,Photographs,Photograph,"[Yosemite National Park, California]",,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",,1829,1916,ca. 1878,1876,1880,Albumen silver print from glass negative,"Image: 12.5 x 12.5 cm (4 15/16 x 4 15/16 in.), circular Album page: 24 x 25.1 cm (9 7/16 x 9 7/8 in.)","Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/732774,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.556(55),false,true,732773,Photographs,Photograph,"[Yosemite National Park, California]",,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",,1829,1916,ca. 1878,1876,1880,Albumen silver print from glass negative,"Image: 12.5 x 12.5 cm (4 15/16 x 4 15/16 in.), circular Album page: 24 x 25.1 cm (9 7/16 x 9 7/8 in.)","Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/732773,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.556(56),false,true,732772,Photographs,Photograph,"[Yosemite National Park, California]",,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",,1829,1916,ca. 1878,1876,1880,Albumen silver print from glass negative,"Image: 12.5 x 12.5 cm (4 15/16 x 4 15/16 in.), circular Album page: 24 x 25.1 cm (9 7/16 x 9 7/8 in.)","Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/732772,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.556(57),false,true,732771,Photographs,Photograph,"[Yosemite National Park, California]",,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",,1829,1916,ca. 1878,1876,1880,Albumen silver print from glass negative,"Image: 12.5 x 12.5 cm (4 15/16 x 4 15/16 in.), circular Album page: 24 x 25.1 cm (9 7/16 x 9 7/8 in.)","Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/732771,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.556(58),false,true,732770,Photographs,Photograph,"[Yosemite National Park, California]",,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",,1829,1916,ca. 1878,1876,1880,Albumen silver print from glass negative,"Image: 12.5 x 12.5 cm (4 15/16 x 4 15/16 in.), circular Album page: 24 x 25.1 cm (9 7/16 x 9 7/8 in.)","Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/732770,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.556(59),false,true,732769,Photographs,Photograph,"[Yosemite National Park, California]",,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",,1829,1916,ca. 1878,1876,1880,Albumen silver print from glass negative,"Image: 12.5 x 12.5 cm (4 15/16 x 4 15/16 in.), circular Album page: 24 x 25.1 cm (9 7/16 x 9 7/8 in.)","Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/732769,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.556(60),false,true,732768,Photographs,Photograph,"[Yosemite National Park, California]",,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",,1829,1916,ca. 1878,1876,1880,Albumen silver print from glass negative,"Image: 12.5 x 12.5 cm (4 15/16 x 4 15/16 in.), circular Album page: 24 x 25.1 cm (9 7/16 x 9 7/8 in.)","Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/732768,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.556(61),false,true,732767,Photographs,Photograph,"[Yosemite National Park, California]",,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",,1829,1916,ca. 1878,1876,1880,Albumen silver print from glass negative,"Image: 12.5 x 12.5 cm (4 15/16 x 4 15/16 in.), circular Album page: 24 x 25.1 cm (9 7/16 x 9 7/8 in.)","Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/732767,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.556(62),false,true,732766,Photographs,Photograph,"[Yosemite National Park, California]",,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",,1829,1916,ca. 1878,1876,1880,Albumen silver print from glass negative,"Image: 12.5 x 12.5 cm (4 15/16 x 4 15/16 in.), circular Album page: 24 x 25.1 cm (9 7/16 x 9 7/8 in.)","Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/732766,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.556(63),false,true,732765,Photographs,Photograph,"[Yosemite National Park, California]",,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",,1829,1916,ca. 1878,1876,1880,Albumen silver print from glass negative,"Image: 12.5 x 12.5 cm (4 15/16 x 4 15/16 in.), circular Album page: 24 x 25.1 cm (9 7/16 x 9 7/8 in.)","Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/732765,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.556(64),false,true,732764,Photographs,Photograph,"[Yosemite National Park, California]",,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",,1829,1916,ca. 1878,1876,1880,Albumen silver print from glass negative,"Image: 12.5 x 12.5 cm (4 15/16 x 4 15/16 in.), circular Album page: 24 x 25.1 cm (9 7/16 x 9 7/8 in.)","Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/732764,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.556(65),false,true,732763,Photographs,Photograph,"[Yosemite National Park, California]",,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",,1829,1916,ca. 1878,1876,1880,Albumen silver print from glass negative,"Image: 12.5 x 12.5 cm (4 15/16 x 4 15/16 in.), circular Album page: 24 x 25.1 cm (9 7/16 x 9 7/8 in.)","Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/732763,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.556(66),false,true,732762,Photographs,Photograph,"[Yosemite National Park, California]",,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",,1829,1916,ca. 1878,1876,1880,Albumen silver print from glass negative,"Image: 12.5 x 12.5 cm (4 15/16 x 4 15/16 in.), circular Album page: 24 x 25.1 cm (9 7/16 x 9 7/8 in.)","Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/732762,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.556(67),false,true,732761,Photographs,Photograph,"[Yosemite National Park, California]",,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",,1829,1916,ca. 1878,1876,1880,Albumen silver print from glass negative,"Image: 12.5 x 12.5 cm (4 15/16 x 4 15/16 in.), circular Album page: 24 x 25.1 cm (9 7/16 x 9 7/8 in.)","Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/732761,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.556(68),false,true,732760,Photographs,Photograph,"[Yosemite National Park, California]",,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",,1829,1916,ca. 1878,1876,1880,Albumen silver print from glass negative,"Image: 12.5 x 12.5 cm (4 15/16 x 4 15/16 in.), circular Album page: 24 x 25.1 cm (9 7/16 x 9 7/8 in.)","Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/732760,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.556(69),false,true,732759,Photographs,Photograph,"[Yosemite National Park, California]",,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",,1829,1916,ca. 1878,1876,1880,Albumen silver print from glass negative,"Image: 12.5 x 12.5 cm (4 15/16 x 4 15/16 in.), circular Album page: 24 x 25.1 cm (9 7/16 x 9 7/8 in.)","Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/732759,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.556(70),false,true,732758,Photographs,Photograph,"[Yosemite National Park, California]",,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",,1829,1916,ca. 1878,1876,1880,Albumen silver print from glass negative,"Image: 12.5 x 12.5 cm (4 15/16 x 4 15/16 in.), circular Album page: 24 x 25.1 cm (9 7/16 x 9 7/8 in.)","Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/732758,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.556(71),false,true,732757,Photographs,Photograph,"[Yosemite National Park, California]",,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",,1829,1916,ca. 1878,1876,1880,Albumen silver print from glass negative,"Image: 12.5 x 12.5 cm (4 15/16 x 4 15/16 in.), circular Album page: 24 x 25.1 cm (9 7/16 x 9 7/8 in.)","Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/732757,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.556(72),false,true,732756,Photographs,Photograph,"[Yosemite National Park, California]",,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",,1829,1916,ca. 1878,1876,1880,Albumen silver print from glass negative,"Image: 12.5 x 12.5 cm (4 15/16 x 4 15/16 in.), circular Album page: 24 x 25.1 cm (9 7/16 x 9 7/8 in.)","Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/732756,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.556(73),false,true,732755,Photographs,Photograph,"[Yosemite National Park, California]",,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",,1829,1916,ca. 1878,1876,1880,Albumen silver print from glass negative,"Image: 12.5 x 12.5 cm (4 15/16 x 4 15/16 in.), circular Album page: 24 x 25.1 cm (9 7/16 x 9 7/8 in.)","Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/732755,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.261.2,false,true,680016,Photographs,Boudoir Card; Cabinet Card,"[Self-Portrait at Glacier Bay, Alaska]",,,,,,Artist,,Frank Jay Haynes,"American, 1853–1921",,"Haynes, F. Jay",,1853,1921,1889–91,1889,1891,Albumen silver print from glass negative,Image: 8 1/8 × 5 1/16 in. (20.7 × 12.8 cm) Mount: 8 7/16 × 5 1/4 in. (21.5 × 13.4 cm),"Gift of Paul M. Hertzmann Inc., 2015",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/680016,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.778.29,false,true,700105,Photographs,Photograph,Old Time Freight Brakeman – New York Central,,,,,,Artist,,Lewis Hine,"American, 1874–1940",,"Hine, Lewis",,1874,1940,1921,1921,1921,Gelatin silver print,Image: 6 11/16 × 4 11/16 in. (17 × 11.9 cm) Sheet: 6 15/16 in. × 5 in. (17.7 × 12.7 cm),"Gift of Joyce F. Menschel, 2015",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/700105,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.777.2,false,true,705430,Photographs,Photograph,"Thèbes, Temple de Ramasseum, Colosses brisés",,,,,,Artist,,Émile Béchard,"French, active 1860s–1880s",,"Béchard, Émile",,1859,1899,1870s,1870,1879,Albumen silver print,Image: 10 9/16 × 14 7/8 in. (26.8 × 37.8 cm) Mount: 13 7/8 × 18 1/16 in. (35.2 × 45.9 cm),"Gift of Charles Isaacs and Carol Nigro, 2015",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/705430,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.400.48,false,true,291801,Photographs,Daguerreotype,Elizabeth Michael Howell,,,,,,Artist,,Addis's Lancaster Gallery,"American, active 1840s–1860s",,Addis's Lancaster Gallery,,1840,1869,1855–59,1855,1859,Daguerreotype,Image: 8.9 x 6.5 cm (3 1/2 x 2 9/16 in.) Plate: 10.8 x 8.3 cm (4 1/4 x 3 1/4 in.) Case: 1.6 x 11.9 x 9.4 cm (5/8 x 4 11/16 x 3 11/16 in.),"Bequest of Herbert Mitchell, 2008",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/291801,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.588.1.114,false,true,704975,Photographs,Photograph; Carte-de-visite,Eugénie Schlosser et Coralli,,,,,,Artist,,André-Adolphe-Eugène Disdéri,"French, Paris 1819–1889 Paris",,"Disdéri, André-Adolphe-Eugène",,1819,1889,1863,1863,1863,Albumen silver print from glass negative,Image: 7 3/8 × 9 1/4 in. (18.8 × 23.5 cm) Album page: 10 3/8 × 13 3/4 in. (26.3 × 35 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/704975,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.588.1.115,false,true,704976,Photographs,Photograph; Carte-de-visite,Eugénie Schlosser et Coralli,,,,,,Artist,,André-Adolphe-Eugène Disdéri,"French, Paris 1819–1889 Paris",,"Disdéri, André-Adolphe-Eugène",,1819,1889,1863,1863,1863,Albumen silver print from glass negative,Image: 7 3/8 × 9 1/4 in. (18.8 × 23.5 cm) Album page: 10 3/8 × 13 3/4 in. (26.3 × 35 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/704976,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2005.100.588.1.116a, b",false,true,704977,Photographs,Photograph; Carte-de-visite,Eugénie Schlosser et Coralli,,,,,,Artist,,André-Adolphe-Eugène Disdéri,"French, Paris 1819–1889 Paris",,"Disdéri, André-Adolphe-Eugène",,1819,1889,1863,1863,1863,Albumen silver print from glass negative,Image: 7 3/8 × 9 1/4 in. (18.8 × 23.5 cm) Album page: 10 3/8 × 13 3/4 in. (26.3 × 35 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/704977,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.424,false,true,684347,Photographs,Photograph,[Spread from an Ornithological Book],,,,,,Artist,,Eugène Atget,"French, Libourne 1857–1927 Paris",,"Atget, Eugène",,1857-02-02,1927-08-04,ca. 1910,1905,1915,Matte albumen silver print from glass negative,"Image: 6 7/8 × 8 15/16 in. (17.4 × 22.7 cm) Sheet: 7 1/16 × 8 15/16 in. (17.9 × 22.7 cm), irregularly trimmed","The Horace W. Goldsmith Foundation Fund, through Joyce and Robert Menschel, 2015",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/684347,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.778.31,false,true,700107,Photographs,Photograph,"Hôtel des Ambassadeurs de Hollande, 47 rue Vieille du Temple",,,,,,Artist,,Eugène Atget,"French, Libourne 1857–1927 Paris",,"Atget, Eugène",,1857-02-02,1927-08-04,1898,1898,1898,Albumen silver print,Image: 8 1/2 in. × 7 in. (21.6 × 17.8 cm),"Gift of Joyce F. Menschel, 2015",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/700107,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.778.33,false,true,700109,Photographs,Photograph,Attitudes of Animals in Motion,,,,,,Artist,,Eadweard Muybridge,"American, born Britain, 1830–1904",,"Muybridge, Eadweard",,1830,1904,"1879, printed 1881",1879,1879,Albumen silver print,Image: 6 5/16 × 9 15/16 in. (16 × 25.3 cm) Mount: 8 13/16 × 12 5/8 in. (22.4 × 32 cm),"Gift of Joyce F. Menschel, 2015",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/700109,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.452,false,true,684344,Photographs,Photograph,[Portrait of Living Man beside Dead Man],,,,,,Artist,,Louis Dodero,"Italian, active France, 1840s–60s",,"Dodero, Louis",,1820,1880,ca. 1850,1845,1855,Daguerreotype,Image: 2 5/8 × 3 7/16 in. (6.7 × 8.7 cm),"The Horace W. Goldsmith Foundation Fund, through Joyce and Robert Menschel, 2015",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/684344,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.770,false,true,286100,Photographs,Photograph,The Old Gamekeeper,,,,,,Artist,Possibly by,David Kinnebrook,"British, Norwich 1819–1865 New Zealand",,"Kinnebrook, David",,1819,1865,ca. 1844,1842,1846,Salted paper print from paper negative,6 3/4 x 5 7/8,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286100,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2012.21,false,true,296345,Photographs,Photograph,"[Sutar ka Jhopda Cave Interior, Ellora Caves]",,,,,,Artist,,Alfred William Plâté,"German (active Sri Lanka), ca. 1859 –1931 Linz",,"Plâté, Alfred William",,1859,1931,1890–1900,1890,1900,Platinum print,Image: 27.8 x 22.8 cm (10 15/16 x 9 in.),"Purchase, Robert A. Taub Gift, 2012",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/296345,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.159.56,false,true,306342,Photographs,Photograph,34. Artistical Groups in Various Poses,,,,,,Artist,,Calvert Richard Jones,"British, Swansea, Wales 1802–1877 Bath, England",,"Jones, Calvert Richard",,1802,1877,ca. 1845,1840,1850,Salted paper print from paper negative,Sheet: 3 13/16 × 4 13/16 in. (9.7 × 12.2 cm) Image: 3 9/16 × 4 5/16 in. (9 × 11 cm),"Bequest of Maurice B. Sendak, 2012",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306342,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.382 (24),false,true,287914,Photographs,Photograph,[Tree and Brush in Creek Scene],,,,,,Artist,,John Dillwyn Llewelyn,"British, Swansea, Wales 1810–1882 Swansea, Wales",,"Llewelyn, John Dillwyn",,1810,1882,1853–56,1853,1856,Salted paper print,Image: 16 × 20.8 cm (6 5/16 × 8 3/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287914,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.382 (32),false,true,287922,Photographs,Photograph,Thereza,,,,,,Artist,,John Dillwyn Llewelyn,"British, Swansea, Wales 1810–1882 Swansea, Wales",,"Llewelyn, John Dillwyn",,1810,1882,1853–56,1853,1856,Salted paper print,Image: 23.2 × 19 cm (9 1/8 × 7 1/2 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287922,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.382 (35),false,true,287925,Photographs,Photograph,Yucca Gloriosa,,,,,,Artist,,John Dillwyn Llewelyn,"British, Swansea, Wales 1810–1882 Swansea, Wales",,"Llewelyn, John Dillwyn",,1810,1882,1853–56,1853,1856,Salted paper print,Image: 20.2 × 15.7 cm (7 15/16 × 6 3/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287925,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.382 (39),false,true,287929,Photographs,Photograph,The Upper Fall,,,,,,Artist,,John Dillwyn Llewelyn,"British, Swansea, Wales 1810–1882 Swansea, Wales",,"Llewelyn, John Dillwyn",,1810,1882,1853–56,1853,1856,Albumen silver print,Image: 24.7 × 17.8 cm (9 3/4 in. × 7 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287929,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.382 (46),false,true,287936,Photographs,Photograph,Tenby Sands,,,,,,Artist,,John Dillwyn Llewelyn,"British, Swansea, Wales 1810–1882 Swansea, Wales",,"Llewelyn, John Dillwyn",,1810,1882,1856,1856,1856,Salted paper print,Image: 15.9 × 21.1 cm (6 1/4 × 8 5/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287936,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.382 (47),false,true,287937,Photographs,Photograph,Upper Lake,,,,,,Artist,,John Dillwyn Llewelyn,"British, Swansea, Wales 1810–1882 Swansea, Wales",,"Llewelyn, John Dillwyn",,1810,1882,1853–56,1853,1856,Salted paper print,Image: 19.2 × 24.1 cm (7 9/16 × 9 1/2 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287937,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.382 (48),false,true,287938,Photographs,Photograph,Gipsies,,,,,,Artist,,John Dillwyn Llewelyn,"British, Swansea, Wales 1810–1882 Swansea, Wales",,"Llewelyn, John Dillwyn",,1810,1882,1853–56,1853,1856,Albumen silver print,Image: 15.8 × 21 cm (6 1/4 × 8 1/4 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287938,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.382 (49),false,true,287939,Photographs,Photograph,"[View of a House in the Woods, with a Waterlogged Road]",,,,,,Artist,,John Dillwyn Llewelyn,"British, Swansea, Wales 1810–1882 Swansea, Wales",,"Llewelyn, John Dillwyn",,1810,1882,1853–56,1853,1856,Albumen silver print,Image: 18.5 × 22.3 cm (7 5/16 × 8 3/4 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287939,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.382 (50),false,true,287940,Photographs,Photograph,The Lewitha,,,,,,Artist,,John Dillwyn Llewelyn,"British, Swansea, Wales 1810–1882 Swansea, Wales",,"Llewelyn, John Dillwyn",,1810,1882,1853–56,1853,1856,Albumen silver print,Image: 19.4 × 23.9 cm (7 5/8 × 9 7/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287940,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.382 (51),false,true,287941,Photographs,Photograph,Dunraven Cliffs - Low Tide,,,,,,Artist,,John Dillwyn Llewelyn,"British, Swansea, Wales 1810–1882 Swansea, Wales",,"Llewelyn, John Dillwyn",,1810,1882,1853–56,1853,1856,Salted paper print,Image: 15.8 × 20.5 cm (6 1/4 × 8 1/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287941,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.382 (52),false,true,287942,Photographs,Photograph,Birthday Group,,,,,,Artist,,John Dillwyn Llewelyn,"British, Swansea, Wales 1810–1882 Swansea, Wales",,"Llewelyn, John Dillwyn",,1810,1882,1856,1856,1856,Albumen silver print,Image: 15.7 × 20.6 cm (6 3/16 × 8 1/8 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287942,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.382 (53),false,true,287943,Photographs,Photograph,Sea Pool with Shells and Seaweeds,,,,,,Artist,,John Dillwyn Llewelyn,"British, Swansea, Wales 1810–1882 Swansea, Wales",,"Llewelyn, John Dillwyn",,1810,1882,1853–56,1853,1856,Salted paper print,"Image: 15.2 × 20.5 cm (6 in. × 8 1/16 in.), oval","Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287943,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.382 (54),false,true,287944,Photographs,Photograph,Penrice Garden,,,,,,Artist,,John Dillwyn Llewelyn,"British, Swansea, Wales 1810–1882 Swansea, Wales",,"Llewelyn, John Dillwyn",,1810,1882,1853–56,1853,1856,Albumen silver print,Image: 19.4 × 24.6 cm (7 5/8 × 9 11/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287944,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.382 (55),false,true,287945,Photographs,Photograph,Cureuleo Meadow,,,,,,Artist,,John Dillwyn Llewelyn,"British, Swansea, Wales 1810–1882 Swansea, Wales",,"Llewelyn, John Dillwyn",,1810,1882,1853–56,1853,1856,Albumen silver print,Image: 16.1 × 21.2 cm (6 5/16 × 8 3/8 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287945,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.382 (56),false,true,287946,Photographs,Photograph,Oakley Cottage with Mr. St. John and Peter and Polly,,,,,,Artist,,John Dillwyn Llewelyn,"British, Swansea, Wales 1810–1882 Swansea, Wales",,"Llewelyn, John Dillwyn",,1810,1882,1853–56,1853,1856,Salted paper print,Image: 16.4 × 21.2 cm (6 7/16 × 8 3/8 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287946,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.382 (57),false,true,287947,Photographs,Photograph,Oakley Cottage,,,,,,Artist,,John Dillwyn Llewelyn,"British, Swansea, Wales 1810–1882 Swansea, Wales",,"Llewelyn, John Dillwyn",,1810,1882,1853–56,1853,1856,Albumen silver print,Image: 16.1 × 20.8 cm (6 5/16 × 8 3/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287947,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.382 (58),false,true,287948,Photographs,Photograph,3 Cliffs Bay with a Wave,,,,,,Artist,,John Dillwyn Llewelyn,"British, Swansea, Wales 1810–1882 Swansea, Wales",,"Llewelyn, John Dillwyn",,1810,1882,1853–56,1853,1856,Salted paper print,Image: 15.6 × 20.5 cm (6 1/8 × 8 1/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287948,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.382 (78),false,true,287967,Photographs,Photograph,The Heron,,,,,,Artist,,John Dillwyn Llewelyn,"British, Swansea, Wales 1810–1882 Swansea, Wales",,"Llewelyn, John Dillwyn",,1810,1882,1853–56,1853,1856,Salted paper print,Image: 24.3 × 18.9 cm (9 9/16 × 7 7/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287967,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.382 (84),false,true,287973,Photographs,Photograph,"The Wharfe, Yorkshire",,,,,,Artist,,John Dillwyn Llewelyn,"British, Swansea, Wales 1810–1882 Swansea, Wales",,"Llewelyn, John Dillwyn",,1810,1882,1853–56,1853,1856,Salted paper print,"Image: 19.9 cm, 24 7/8 in. (7 13/16 × 24 7/8 in.)","Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287973,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.382 (85),false,true,287974,Photographs,Photograph,Guy Fawkes,,,,,,Artist,,John Dillwyn Llewelyn,"British, Swansea, Wales 1810–1882 Swansea, Wales",,"Llewelyn, John Dillwyn",,1810,1882,1853,1853,1853,Salted paper print,Image: 15 × 20.2 cm (5 7/8 × 7 15/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287974,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.382 (30a-e),false,true,287920,Photographs,Photograph,[Figurine of Young Boy Holding Apples; Cabinet Card of a Man; Figurine of a Young Child with a Hat; Sculpture of a Man with Child; Sculpture with Animal],,,,,,Artist,,John Dillwyn Llewelyn,"British, Swansea, Wales 1810–1882 Swansea, Wales",,"Llewelyn, John Dillwyn",,1810,1882,1853–56,1853,1856,Salted paper print,"Beginning at top, moving clockwise: Image: 5.6 × 5.2 cm (2 3/16 × 2 1/16 in.) (top) Image: 7.3 × 5.4 cm (2 7/8 × 2 1/8 in.) (right) Image: 8.2 × 6.1 cm (3 1/4 × 2 3/8 in.) (bottom) Image: 7.2 × 5.4 cm (2 13/16 × 2 1/8 in.) (left) Image: 4.3 × 4.3 cm (1 11/16 × 1 11/16 in.) (center)","Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287920,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.382 (43a-d),false,true,287933,Photographs,Photograph,Gipsies; Lewitha; Upper Lake; [Untitled],,,,,,Artist,,John Dillwyn Llewelyn,"British, Swansea, Wales 1810–1882 Swansea, Wales",,"Llewelyn, John Dillwyn",,1810,1882,1853–56,1853,1856,Salted paper print,"Image: 5.5 × 5.5 cm (2 3/16 × 2 3/16 in.) (a), circular Image: 5.5 × 5.7 cm (2 3/16 × 2 1/4 in.) (b), circular Image: 5.5 × 5.6 cm (2 3/16 × 2 3/16 in.) (c), circular Image: 14.8 × 10.7 cm (5 13/16 × 4 3/16 in.) (d), oval","Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287933,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.382 (75a-c),false,true,287964,Photographs,Photograph,"Saccolabium Guttatum; Lizzie, Emily, Alice, Mrs Stratton; Emily, Etty, Alice, Lizzie, Mrs Drake, Mrs Stratton",,,,,,Artist,,John Dillwyn Llewelyn,"British, Swansea, Wales 1810–1882 Swansea, Wales",,"Llewelyn, John Dillwyn",,1810,1882,1853–56,1853,1856,Salted paper print; albumen silver print,"Image: 14.9 × 9.2 cm (5 7/8 × 3 5/8 in.) (a) Image: 9.3 × 7.1 cm (3 11/16 × 2 13/16 in.) (b) Image: 7 11/16 in., 8.3 cm (7 11/16 × 3 1/4 in.) (c)","Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287964,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.382 (83a-h),false,true,287972,Photographs,Photograph,Miss Douglas; Mlle Isaline Motte; Miss Fanny Evans; Miss Catinka Smith; Mrs Leitch née Lloyd; Miss Martin; Capt. & Mrs Hibbert; Miss Cecilia Regnell,,,,,,Artist,,John Dillwyn Llewelyn,"British, Swansea, Wales 1810–1882 Swansea, Wales",,"Llewelyn, John Dillwyn",,1810,1882,1853–56,1853,1856,Albumen silver print,"Image: 8.9 × 5.5 cm (3 1/2 × 2 3/16 in.) (a) Image: 8 × 4.8 cm (3 1/8 × 1 7/8 in.) (b), oval Image: 8.7 × 5.5 cm (3 7/16 × 2 3/16 in.) (c) Image: 8.4 × 5.3 cm (3 5/16 × 2 1/16 in.) (d), diamond Image: 8.4 × 5.3 cm (3 5/16 × 2 1/16 in.) (e), diamond Image: 8.6 × 5.3 cm (3 3/8 × 2 1/16 in.) (f) Image: 8.1 × 5.5 cm (3 3/16 × 2 3/16 in.) (g), oval Image: 9.2 × 5.7 cm (3 5/8 × 2 1/4 in.) (h)","Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287972,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2005.100.382 (37a, b)",false,true,287927,Photographs,Photograph,"Granny, Thereza, Elinor; The Shanty",,,,,,Artist,,John Dillwyn Llewelyn,"British, Swansea, Wales 1810–1882 Swansea, Wales",,"Llewelyn, John Dillwyn",,1810,1882,1853–56,1853,1856,Salted paper print,"Image: 11.1 × 9.5 cm (4 3/8 × 3 3/4 in.) (a), oval Image: 10.4 × 13.8 cm (4 1/8 × 5 7/16 in.) (a), oval","Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287927,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2005.100.382 (41a, b)",false,true,287931,Photographs,Photograph,"Penllergare; Birthday Group, Sept. 23, 1853",,,,,,Artist,,John Dillwyn Llewelyn,"British, Swansea, Wales 1810–1882 Swansea, Wales",,"Llewelyn, John Dillwyn",,1810,1882,1853–56,1853,1856,Albumen silver print,Image: 15.3 × 16.1 cm (6 in. × 6 5/16 in.) Image: 7.6 × 10.6 cm (3 in. × 4 3/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287931,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2005.100.382 (77a, b)",false,true,287966,Photographs,Photograph,Penrice; [Untitled],,,,,,Artist,,John Dillwyn Llewelyn,"British, Swansea, Wales 1810–1882 Swansea, Wales",,"Llewelyn, John Dillwyn",,1810,1882,1853–56,1853,1856,Salted paper print; albumen silver print,Image: 11.8 × 13.9 cm (4 5/8 × 5 1/2 in.) (a) Image: 8.3 × 5.2 cm (3 1/4 × 2 1/16 in.) (b),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287966,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2005.100.382 (79a, b)",false,true,287968,Photographs,Photograph,Coln Church + Mr Kent; 3 Miss Wallingtons,,,,,,Artist,,John Dillwyn Llewelyn,"British, Swansea, Wales 1810–1882 Swansea, Wales",,"Llewelyn, John Dillwyn",,1810,1882,1853–56,1853,1856,Albumen silver print,Image: 15.5 × 14.5 cm (6 1/8 × 5 11/16 in.) (a) Image: 8.1 × 6.7 cm (3 3/16 × 2 5/8 in.) (b),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287968,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2005.100.382 (80a, b)",false,true,287969,Photographs,Photograph,Amy & Essie Dillwyn; TSM Meggie & Mary,,,,,,Artist,,John Dillwyn Llewelyn,"British, Swansea, Wales 1810–1882 Swansea, Wales",,"Llewelyn, John Dillwyn",,1810,1882,1861,1861,1861,Salted paper print; albumen silver print,Image: 13.9 × 10.4 cm (5 1/2 × 4 1/8 in.) (a) Image: 6.8 × 5.5 cm (2 11/16 × 2 3/16 in.) (b),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287969,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.1254,false,true,728494,Photographs,Photograph,The Wilderness Battlefield,,,,,,Artist|Artist,Possibly by,Alexander Gardner|Unknown,"American, Glasgow, Scotland 1821–1882 Washington, D.C.|American",,"Gardner, Alexander|Unknown",,1821,1882,1864,1864,1864,Albumen silver print from glass negative,Image: 12.8 × 8.7 cm (5 1/16 × 3 7/16 in.),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/728494,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.1253,false,true,728493,Photographs,Photograph,The Wilderness Battlefield,,,,,,Artist|Artist,Possibly by,Unknown|Alexander Gardner,"American|American, Glasgow, Scotland 1821–1882 Washington, D.C.",,"Unknown|Gardner, Alexander",,1821,1882,1864,1864,1864,Albumen silver print from glass negative,Image: 10.9 × 9.5 cm (4 5/16 × 3 3/4 in.),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/728493,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1996.158.3,false,true,267330,Photographs,Photograph,Marius Bourotte,,,,,,Artist|Publisher,,Unknown|Le Petit Parisien,"French|French, active 1876–1944",,Unknown|Le Petit Parisien,,1876,1944,1929,1929,1929,Gelatin silver print with applied color,11.6 x 16.2 cm. (4 9/16 x 6 3/8 in.),"Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 1996",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267330,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1994.197.6 (1),false,true,289171,Photographs,Photographically illustrated book,Westminster Abbey,,,,,,Artist,,Nicolaas Henneman,"Dutch, Heemskerk 1813–1898 London",,"Henneman, Nicolaas",Dutch,1813,1898,before May 1845,1845,1846,Salted paper print from paper negative,,"Gift of Jean Horblit, in memory of Harrison D. Horblit, 1994",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/289171,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.440,false,true,685780,Photographs,Carte-de-visite,[Jozef Israels],,,,,,Artist,,Willem Frederik Vinkenbos,"Dutch, Amsterdam 1831–1896 The Hague",,"Vinkenbos, Willem Frederik",Dutch,1831,1896,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/685780,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.387.1–.11,false,true,283246,Photographs,Album,Série des Roses,,,,,,Artist|Person in Photograph,,Pierre-Louis Pierson|Countess Virginia Oldoini Verasis di Castiglione,"French, 1822–1913|1835–1899",,"Pierson, Pierre-Louis|Castiglione, di, Virginia Oldoini Verasis Countess",French,1822 |1835,1913 |1899,1895,1895,1895,Albumen silver print from glass negative,Approximately 14.3 x 9.9 cm (5 5/8 x 3 7/8 in.) each,"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Albums,,http://www.metmuseum.org/art/collection/search/283246,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.388.1–.13,false,true,286855,Photographs,Album,Série des Roses,,,,,,Artist|Person in Photograph,,Pierre-Louis Pierson|Countess Virginia Oldoini Verasis di Castiglione,"French, 1822–1913|1835–1899",,"Pierson, Pierre-Louis|Castiglione, di, Virginia Oldoini Verasis Countess",French,1822 |1835,1913 |1899,1895,1895,1895,Albumen silver print from glass negative,Approximately 14.3 x 9.9 cm (5 5/8 x 3 7/8 in.) each,"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Albums,,http://www.metmuseum.org/art/collection/search/286855,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.587.1–.29,false,true,286698,Photographs,Album,[Halévy Family Album],,,,,,Artist|Maker|Person in Photograph|Person in Photograph|Artist|Person in Photograph|Person in Photograph|Person in Photograph|Artist|Person in Photograph|Person in Photograph|Person in Photograph|Person in Photograph,Assembled by|Person in photograph|Person in photograph|Person in photograph|Person in photograph|Person in photograph|Person in photograph|Person in photograph|Person in photograph|Person in photograph,Hortense Howland|Ludovic Halévy|Henri-René-Albert-Guy-de Maupassant|Comtesse Laure de Chevigné|Marquis Alfred du Lau d'Allemans|Edgar Degas|Geneviève Halévy|Ludovic Halévy|Unknown|Charles Haas|Marquis Alfred du Lau d'Allemans|Louise Halévy|Émile Straus,"French, 1835–1920|French, 1834–1908|French, Dieppe, Normandy 1850–1893 Paris|1859–1936|1833–1919|French, Paris 1834–1917 Paris|French, 1849–1926|French, 1834–1908|French|1833–1902|1833–1919|French, 1847–1930",,"Howland, Hortense|Halévy, Ludovic|Maupassant, Henri-René-Albert-Guy-de|de Chevigné, Laure Comtesse|du Lau d'Allemans, Alfred Marquis|Degas, Edgar|Halévy, Geneviève|Halévy, Ludovic|Unknown|Haas, Charles|du Lau d'Allemans, Alfred Marquis|Halévy, Louise|Straus, Émile",French,1835 |1834 |1850 |1859 |1833 |1834 |1849 |1834 |1833 |1833 |1847,1920 |1908 |1893 |1936 |1919 |1917 |1926 |1908 |1902 |1919 |1930,1886–89,1886,1889,Gelatin silver print,Album: 10 1/4 x 13 x 1 5/8 inches Print sizes vary: 7.4 x 6.4 to 13 x 18.3,"Gilman Collection, Purchase, Mrs. Walter Annenberg and The Annenberg Foundation Gift, 2005",,,,,,,,,,,,Albums,,http://www.metmuseum.org/art/collection/search/286698,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.611,false,true,283101,Photographs,Photograph,Louis-Jacques-Mandé Daguerre,,,,,,Artist|Person in Photograph,,Pierre-Ambrose Richebourg|Louis-Jacques-Mandé Daguerre,"French, 1810–1893",,"Richebourg, Pierre-Ambrose|Daguerre, Louis-Jacques-Mandé",French,1810,1893,ca. 1844,1842,1846,Daguerreotype,8.9 x 7 cm (3 1/2 x 2 3/4 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/283101,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.607.1,false,true,261799,Photographs,Photograph,"Boûlâk, Carrefour (Mosquée, Café, etc.)",,,,,,Artist|Printer,,Félix Teynard|Imprimerie Photographique de H. de Fonteny et Cie,"French, 1817–1892",,"Teynard, Félix|Imprimerie Photographique de H. de Fonteny et Cie",French,1817,1892,"1851–52, printed 1853–54",1851,1852,Salted paper print from paper negative,24.1 x 30.5 cm (9 1/2 x 12 in.),"Purchase, Lila Acheson Wallace Gift, 1976",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261799,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.607.2,false,true,261816,Photographs,Photograph,"Le Kaire, Mosquées d'Iscander-Pacha et du Sultan Haçan",,,,,,Artist|Printer,,Félix Teynard|Imprimerie Photographique de H. de Fonteny et Cie,"French, 1817–1892",,"Teynard, Félix|Imprimerie Photographique de H. de Fonteny et Cie",French,1817,1892,"1851–52, printed 1853–54",1851,1852,Salted paper print from paper negative,23.7 x 30.6 cm. (9 5/16 x 12 1/16 in.),"Purchase, Lila Acheson Wallace Gift, 1976",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261816,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.607.3,false,true,261827,Photographs,Photograph,"Le Kaire, Mosquée du Sultan Haçan (le Tombeau)",,,,,,Artist|Printer,,Félix Teynard|Imprimerie Photographique de H. de Fonteny et Cie,"French, 1817–1892",,"Teynard, Félix|Imprimerie Photographique de H. de Fonteny et Cie",French,1817,1892,"1851–52, printed 1853–54",1851,1852,Salted paper print from paper negative,25.2 x 30.5 cm. (9 15/16 x 12 in.),"Purchase, Lila Acheson Wallace Gift, 1976",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261827,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.607.4,false,true,261838,Photographs,Photograph,"Le Kaire, Mosquée Nâcéryeh",,,,,,Artist|Printer,,Félix Teynard|Imprimerie Photographique de H. de Fonteny et Cie,"French, 1817–1892",,"Teynard, Félix|Imprimerie Photographique de H. de Fonteny et Cie",French,1817,1892,"1851–52, printed 1853–54",1851,1852,Salted paper print from paper negative,24.3 x 30.4 cm. (9 9/16 x 12 in.),"Purchase, Lila Acheson Wallace Gift, 1976",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261838,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.607.5,false,true,261849,Photographs,Photograph,"Le Kaire, Mosquée d'Amrou - Intérieur - Côté du Sanctuaire",,,,,,Artist|Printer,,Félix Teynard|Imprimerie Photographique de H. de Fonteny et Cie,"French, 1817–1892",,"Teynard, Félix|Imprimerie Photographique de H. de Fonteny et Cie",French,1817,1892,"1851–52, printed 1853–54",1851,1852,Salted paper print from paper negative,24.0 x 30.5 cm. (9 7/16 x 12 in.),"Purchase, Lila Acheson Wallace Gift, 1976",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261849,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.607.6,false,true,261860,Photographs,Photograph,"Le Kaire, Tombeaux de Sultans Mamelouks",,,,,,Artist|Printer,,Félix Teynard|Imprimerie Photographique de H. de Fonteny et Cie,"French, 1817–1892",,"Teynard, Félix|Imprimerie Photographique de H. de Fonteny et Cie",French,1817,1892,"1851–52, printed 1853–54",1851,1852,Salted paper print from paper negative,24.3 x 30.7 cm. (9 9/16 x 12 1/16 in.),"Purchase, Lila Acheson Wallace Gift, 1976",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261860,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.607.7,false,true,261871,Photographs,Photograph,"Le Kaire, Cimetière des Mamelouks",,,,,,Artist|Printer,,Félix Teynard|Imprimerie Photographique de H. de Fonteny et Cie,"French, 1817–1892",,"Teynard, Félix|Imprimerie Photographique de H. de Fonteny et Cie",French,1817,1892,"1851–52, printed 1853–54",1851,1852,Salted paper print from paper negative,24.0 x 30.0 cm. (9 7/16 x 11 13/16 in.),"Purchase, Lila Acheson Wallace Gift, 1976",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261871,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.607.8,false,true,261882,Photographs,Photograph,"Djîzeh (Nécropole de Memphis), Sphinx et Pyramides",,,,,,Artist|Printer,,Félix Teynard|Imprimerie Photographique de H. de Fonteny et Cie,"French, 1817–1892",,"Teynard, Félix|Imprimerie Photographique de H. de Fonteny et Cie",French,1817,1892,"1851–52, printed 1853–54",1851,1852,Salted paper print from paper negative,23.9 x 30.0 cm. (9 7/16 x 11 13/16 in.),"Purchase, Lila Acheson Wallace Gift, 1976",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261882,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.607.9,false,true,261893,Photographs,Photograph,"Djîzeh (Nécropole de Memphis), Pyramide de Chéops (Grande Pyramide)",,,,,,Artist|Printer,,Félix Teynard|Imprimerie Photographique de H. de Fonteny et Cie,"French, 1817–1892",,"Teynard, Félix|Imprimerie Photographique de H. de Fonteny et Cie",French,1817,1892,"1851–52, printed 1853–54",1851,1852,Salted paper print from paper negative,24.4 x 30.1 cm. (9 5/8 x 11 7/8 in.),"Purchase, Lila Acheson Wallace Gift, 1976",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261893,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.607.10,false,true,261800,Photographs,Photograph,"Djîzeh (Nécropole de Memphis), Pyramide de Chéphren",,,,,,Artist|Printer,,Félix Teynard|Imprimerie Photographique de H. de Fonteny et Cie,"French, 1817–1892",,"Teynard, Félix|Imprimerie Photographique de H. de Fonteny et Cie",French,1817,1892,"1851–52, printed 1853–54",1851,1852,Salted paper print from paper negative,24.2 x 30.2 cm. (9 1/2 x 11 7/8 in.),"Purchase, Lila Acheson Wallace Gift, 1976",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261800,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.607.11,false,true,261807,Photographs,Photograph,"Abâzîz, Intérieur d'un Village Arabe",,,,,,Artist|Printer,,Félix Teynard|Imprimerie Photographique de H. de Fonteny et Cie,"French, 1817–1892",,"Teynard, Félix|Imprimerie Photographique de H. de Fonteny et Cie",French,1817,1892,"1851–52, printed 1853–54",1851,1852,Salted paper print from paper negative,24.0 x 30.3 cm. (9 7/16 x 11 15/16 in.),"Purchase, Lila Acheson Wallace Gift, 1976",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261807,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.607.12,false,true,261808,Photographs,Photograph,"El-Nâcérah, Dattiers, Rives du Nil et Barques",,,,,,Artist|Printer,,Félix Teynard|Imprimerie Photographique de H. de Fonteny et Cie,"French, 1817–1892",,"Teynard, Félix|Imprimerie Photographique de H. de Fonteny et Cie",French,1817,1892,"1851–52, printed 1853–54",1851,1852,Salted paper print from paper negative,23.2 x 23.9 cm. (9 1/8 x 9 7/16 in.),"Purchase, Lila Acheson Wallace Gift, 1976",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261808,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.607.13,false,true,261809,Photographs,Photograph,"Béni-Haçan, Architecture Hypogéene - Tombeau d'Amoneï",,,,,,Artist|Printer,,Félix Teynard|Imprimerie Photographique de H. de Fonteny et Cie,"French, 1817–1892",,"Teynard, Félix|Imprimerie Photographique de H. de Fonteny et Cie",French,1817,1892,"1851–52, printed 1853–54",1851,1852,Salted paper print from paper negative,24.5 x 30.3 cm. (9 5/8 x 11 15/16 in.),"Purchase, Lila Acheson Wallace Gift, 1976",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261809,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.607.14,false,true,261810,Photographs,Photograph,"Béni-Haçan, Architecture Hypogéene - Tombeau de Névothph",,,,,,Artist|Printer,,Félix Teynard|Imprimerie Photographique de H. de Fonteny et Cie,"French, 1817–1892",,"Teynard, Félix|Imprimerie Photographique de H. de Fonteny et Cie",French,1817,1892,"1851–52, printed 1853–54",1851,1852,Salted paper print from paper negative,23.8 x 30.4 cm. (9 3/8 x 12 in.),"Purchase, Lila Acheson Wallace Gift, 1976",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261810,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.607.15,false,true,261811,Photographs,Photograph,"Syout, Habitations Arabes sur le Bord du Nil",,,,,,Artist|Printer,,Félix Teynard|Imprimerie Photographique de H. de Fonteny et Cie,"French, 1817–1892",,"Teynard, Félix|Imprimerie Photographique de H. de Fonteny et Cie",French,1817,1892,"1851–52, printed 1853–54",1851,1852,Salted paper print from paper negative,23.9 x 30.0 cm. (9 7/16 x 11 13/16 in.),"Purchase, Lila Acheson Wallace Gift, 1976",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261811,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.607.16,false,true,261812,Photographs,Photograph,"Syout, Constructions Modernes - le Divan, etc.",,,,,,Artist|Printer,,Félix Teynard|Imprimerie Photographique de H. de Fonteny et Cie,"French, 1817–1892",,"Teynard, Félix|Imprimerie Photographique de H. de Fonteny et Cie",French,1817,1892,"1851–52, printed 1853–54",1851,1852,Salted paper print from paper negative,23.7 x 29.8 cm. (9 5/16 x 11 3/4 in.),"Purchase, Lila Acheson Wallace Gift, 1976",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261812,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.607.17,false,true,261813,Photographs,Photograph,"Syout, Pont Sur le Grand Canal",,,,,,Artist|Printer,,Félix Teynard|Imprimerie Photographique de H. de Fonteny et Cie,"French, 1817–1892",,"Teynard, Félix|Imprimerie Photographique de H. de Fonteny et Cie",French,1817,1892,"1851–52, printed 1853–54",1851,1852,Salted paper print from paper negative,23.7 x 30.4 cm. (9 5/16 x 12 in.),"Purchase, Lila Acheson Wallace Gift, 1976",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261813,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.607.18,false,true,261814,Photographs,Photograph,"Syout (Lycopolis), Statue Appartenant au Docteaur Cuny",,,,,,Artist|Printer,,Félix Teynard|Imprimerie Photographique de H. de Fonteny et Cie,"French, 1817–1892",,"Teynard, Félix|Imprimerie Photographique de H. de Fonteny et Cie",French,1817,1892,"1851–52, printed 1853–54",1851,1852,Salted paper print from paper negative,23.6 x 15.4 cm. (9 5/16 x 6 1/16 in.),"Purchase, Lila Acheson Wallace Gift, 1976",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261814,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.607.19,false,true,261815,Photographs,Photograph,"Syout, Caravansérail en Ruines",,,,,,Artist|Printer,,Félix Teynard|Imprimerie Photographique de H. de Fonteny et Cie,"French, 1817–1892",,"Teynard, Félix|Imprimerie Photographique de H. de Fonteny et Cie",French,1817,1892,"1851–52, printed 1853–54",1851,1852,Salted paper print from paper negative,24.7 x 30.3 cm. (9 3/4 x 11 15/16 in.),"Purchase, Lila Acheson Wallace Gift, 1976",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261815,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.607.20,false,true,261817,Photographs,Photograph,"Souâdj, Cimetière Musulman et Tombeau de Mouràd-Bey",,,,,,Artist|Printer,,Félix Teynard|Imprimerie Photographique de H. de Fonteny et Cie,"French, 1817–1892",,"Teynard, Félix|Imprimerie Photographique de H. de Fonteny et Cie",French,1817,1892,"1851–52, printed 1853–54",1851,1852,Salted paper print from paper negative,24.0 x 30.0 cm. (9 7/16 x 11 13/16 in.),"Purchase, Lila Acheson Wallace Gift, 1976",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261817,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.607.21,false,true,261818,Photographs,Photograph,"Souâdj, Tombeau de Mouràd-Bey - Entrée de la Mosquée",,,,,,Artist|Printer,,Félix Teynard|Imprimerie Photographique de H. de Fonteny et Cie,"French, 1817–1892",,"Teynard, Félix|Imprimerie Photographique de H. de Fonteny et Cie",French,1817,1892,"1851–52, printed 1853–54",1851,1852,Salted paper print from paper negative,23.9 x 30.5 cm. (9 7/16 x 12 in.),"Purchase, Lila Acheson Wallace Gift, 1976",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261818,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.607.22,false,true,261819,Photographs,Photograph,"Djirdjeh, Mosquée en Ruines Sur le Bord du Nil",,,,,,Artist|Printer,,Félix Teynard|Imprimerie Photographique de H. de Fonteny et Cie,"French, 1817–1892",,"Teynard, Félix|Imprimerie Photographique de H. de Fonteny et Cie",French,1817,1892,"1851–52, printed 1853–54",1851,1852,Salted paper print from paper negative,29.7 x 25.1 cm. (11 11/16 x 9 7/8 in.),"Purchase, Lila Acheson Wallace Gift, 1976",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261819,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.607.23,false,true,261820,Photographs,Photograph,"Dendérah (Tentyris), Temple d'Athôr - Vue Générale",,,,,,Artist|Printer,,Félix Teynard|Imprimerie Photographique de H. de Fonteny et Cie,"French, 1817–1892",,"Teynard, Félix|Imprimerie Photographique de H. de Fonteny et Cie",French,1817,1892,"1851–52, printed 1853–54",1851,1852,Salted paper print from paper negative,23.8 x 29.9 cm. (9 3/8 x 11 3/4 in.),"Purchase, Lila Acheson Wallace Gift, 1976",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261820,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.607.24,false,true,261821,Photographs,Photograph,"Dendérah (Tentyris), Temple d'Athôr - Face Postérieure",,,,,,Artist|Printer,,Félix Teynard|Imprimerie Photographique de H. de Fonteny et Cie,"French, 1817–1892",,"Teynard, Félix|Imprimerie Photographique de H. de Fonteny et Cie",French,1817,1892,"1851–52, printed 1853–54",1851,1852,Salted paper print from paper negative,24.7 x 30.0 cm. (9 3/4 x 11 13/16 in.),"Purchase, Lila Acheson Wallace Gift, 1976",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261821,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.607.25,false,true,261822,Photographs,Photograph,"Dendérah (Tentyris), Temple d'Athôr - Face Postérieure - Cléopatre et Cæsarion",,,,,,Artist|Printer,,Félix Teynard|Imprimerie Photographique de H. de Fonteny et Cie,"French, 1817–1892",,"Teynard, Félix|Imprimerie Photographique de H. de Fonteny et Cie",French,1817,1892,"1851–52, printed 1853–54",1851,1852,Salted paper print from paper negative,30.0 x 25.4 cm. (11 13/16 x 10 in.),"Purchase, Lila Acheson Wallace Gift, 1976",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261822,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.607.26,false,true,261823,Photographs,Photograph,"Dendérah (Tentyris), Temple d'Athôr - Sanctuaire Placé a l'Angle Sud-Ouest de la Plateforme Inférieure",,,,,,Artist|Printer,,Félix Teynard|Imprimerie Photographique de H. de Fonteny et Cie,"French, 1817–1892",,"Teynard, Félix|Imprimerie Photographique de H. de Fonteny et Cie",French,1817,1892,"1851–52, printed 1853–54",1851,1852,Salted paper print from paper negative,24.1 x 30.8 cm. (9 1/2 x 12 1/8 in.),"Purchase, Lila Acheson Wallace Gift, 1976",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261823,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.607.27,false,true,261824,Photographs,Photograph,"Dendérah (Tentyris), Mammisi - Décoration Extérieure de la Face Sud",,,,,,Artist|Printer,,Félix Teynard|Imprimerie Photographique de H. de Fonteny et Cie,"French, 1817–1892",,"Teynard, Félix|Imprimerie Photographique de H. de Fonteny et Cie",French,1817,1892,"1851–52, printed 1853–54",1851,1852,Salted paper print from paper negative,24.0 x 30.0 cm. (9 7/16 x 11 13/16 in.),"Purchase, Lila Acheson Wallace Gift, 1976",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261824,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.607.28,false,true,261825,Photographs,Photograph,"Louksor, Petit Bras du Nil - Barque de Voyageurs",,,,,,Artist|Printer,,Félix Teynard|Imprimerie Photographique de H. de Fonteny et Cie,"French, 1817–1892",,"Teynard, Félix|Imprimerie Photographique de H. de Fonteny et Cie",French,1817,1892,"1851–52, printed 1853–54",1851,1852,Salted paper print from paper negative,24.4 x 30.7 cm. (9 5/8 x 12 1/16 in.),"Purchase, Lila Acheson Wallace Gift, 1976",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261825,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.607.29,false,true,261826,Photographs,Photograph,"Louksor (Thèbes), Vue Générale des Ruines",,,,,,Artist|Printer,,Félix Teynard|Imprimerie Photographique de H. de Fonteny et Cie,"French, 1817–1892",,"Teynard, Félix|Imprimerie Photographique de H. de Fonteny et Cie",French,1817,1892,"1851–52, printed 1853–54",1851,1852,Salted paper print from paper negative,24.7 x 30.1 cm. (9 3/4 x 11 7/8 in.),"Purchase, Lila Acheson Wallace Gift, 1976",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261826,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.607.30,false,true,261828,Photographs,Photograph,"Louksor (Thèbes), Construction Antérieure - Pylône, Colosses et Obélisque",,,,,,Artist|Printer,,Félix Teynard|Imprimerie Photographique de H. de Fonteny et Cie,"French, 1817–1892",,"Teynard, Félix|Imprimerie Photographique de H. de Fonteny et Cie",French,1817,1892,"1851–52, printed 1853–54",1851,1852,Salted paper print from paper negative,23.8 x 29.9 cm. (9 3/8 x 11 3/4 in.),"Purchase, Lila Acheson Wallace Gift, 1976",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261828,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.607.31,false,true,261829,Photographs,Photograph,Louksor (Thèbes). Construction Centrale - Grande Colonnade,,,,,,Artist|Printer,,Félix Teynard|Imprimerie Photographique de H. de Fonteny et Cie,"French, 1817–1892",,"Teynard, Félix|Imprimerie Photographique de H. de Fonteny et Cie",French,1817,1892,"1851–52, printed 1853–54",1851,1852,Salted paper print from paper negative,24.8 x 30.3 cm. (9 3/4 x 11 15/16 in.),"Purchase, Lila Acheson Wallace Gift, 1976",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261829,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.607.32,false,true,261830,Photographs,Photograph,"Louksor (Thèbes), Construction Postérieure - Galeries Parallèles",,,,,,Artist|Printer,,Félix Teynard|Imprimerie Photographique de H. de Fonteny et Cie,"French, 1817–1892",,"Teynard, Félix|Imprimerie Photographique de H. de Fonteny et Cie",French,1817,1892,"1851–52, printed 1853–54",1851,1852,Salted paper print from paper negative,23.2 x 29.7 cm. (9 1/8 x 11 11/16 in.),"Purchase, Lila Acheson Wallace Gift, 1976",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261830,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.607.33,false,true,261831,Photographs,Photograph,"Louksor, Dattiers et Jardin de l'Expédition du Louksor",,,,,,Artist|Printer,,Félix Teynard|Imprimerie Photographique de H. de Fonteny et Cie,"French, 1817–1892",,"Teynard, Félix|Imprimerie Photographique de H. de Fonteny et Cie",French,1817,1892,"1851–52, printed 1853–54",1851,1852,Salted paper print from paper negative,23.7 x 30.3 cm. (9 5/16 x 11 15/16 in.),"Purchase, Lila Acheson Wallace Gift, 1976",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261831,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.607.34,false,true,261832,Photographs,Photograph,"Médînet-Abou (Thèbes), Construction Antérieures - Vue Générale",,,,,,Artist|Printer,,Félix Teynard|Imprimerie Photographique de H. de Fonteny et Cie,"French, 1817–1892",,"Teynard, Félix|Imprimerie Photographique de H. de Fonteny et Cie",French,1817,1892,"1851–52, printed 1853–54",1851,1852,Salted paper print from paper negative,24.2 x 30.2 cm. (9 1/2 x 11 7/8 in.),"Purchase, Lila Acheson Wallace Gift, 1976",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261832,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.607.35,false,true,261833,Photographs,Photograph,"Médînet-Abou (Thèbes), Construction Antérieures - Entrée Principale",,,,,,Artist|Printer,,Félix Teynard|Imprimerie Photographique de H. de Fonteny et Cie,"French, 1817–1892",,"Teynard, Félix|Imprimerie Photographique de H. de Fonteny et Cie",French,1817,1892,"1851–52, printed 1853–54",1851,1852,Salted paper print from paper negative,24.5 x 30.5 cm. (9 5/8 x 12 in.),"Purchase, Lila Acheson Wallace Gift, 1976",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261833,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.607.36,false,true,261834,Photographs,Photograph,"Médînet-Abou (Thèbes), Constructions Postérieures - Fragment de Sculptures Sur la Face Nord-Est",,,,,,Artist|Printer,,Félix Teynard|Imprimerie Photographique de H. de Fonteny et Cie,"French, 1817–1892",,"Teynard, Félix|Imprimerie Photographique de H. de Fonteny et Cie",French,1817,1892,"1851–52, printed 1853–54",1851,1852,Salted paper print from paper negative,24.6 x 30.1 cm. (9 11/16 x 11 7/8 in.),"Purchase, Lila Acheson Wallace Gift, 1976",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261834,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.607.37,false,true,261835,Photographs,Photograph,Médînet-Abou (Thèbes). Constructions Postérieures - Deuxiéme Cour,,,,,,Artist|Printer,,Félix Teynard|Imprimerie Photographique de H. de Fonteny et Cie,"French, 1817–1892",,"Teynard, Félix|Imprimerie Photographique de H. de Fonteny et Cie",French,1817,1892,"1851–52, printed 1853–54",1851,1852,Salted paper print from paper negative,25.0 x 30.3 cm. (9 13/16 x 11 15/16 in.),"Purchase, Lila Acheson Wallace Gift, 1976",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261835,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.607.38,false,true,261836,Photographs,Photograph,"Médînet-Abou (Thèbes), Constructions Postérieures - Deuxieme Cour - Galerie Nord-Ouest",,,,,,Artist|Printer,,Félix Teynard|Imprimerie Photographique de H. de Fonteny et Cie,"French, 1817–1892",,"Teynard, Félix|Imprimerie Photographique de H. de Fonteny et Cie",French,1817,1892,"1851–52, printed 1853–54",1851,1852,Salted paper print from paper negative,30.3 x 24.6 cm. (11 15/16 x 9 11/16 in.),"Purchase, Lila Acheson Wallace Gift, 1976",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261836,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.607.39,false,true,261837,Photographs,Photograph,"Médînet-Abou (Thèbes), Construction Postérieures - Deuxiéme Cour - Galerie Sud-Ouest",,,,,,Artist|Printer,,Félix Teynard|Imprimerie Photographique de H. de Fonteny et Cie,"French, 1817–1892",,"Teynard, Félix|Imprimerie Photographique de H. de Fonteny et Cie",French,1817,1892,"1851–52, printed 1853–54",1851,1852,Salted paper print from paper negative,30.5 x 25.1 cm. (12 x 9 7/8 in.),"Purchase, Lila Acheson Wallace Gift, 1976",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261837,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.607.40,false,true,261839,Photographs,Photograph,"Gournah (Thèbes), Colosses (Celui de Droite, Dit de Memnon)",,,,,,Artist|Printer,,Félix Teynard|Imprimerie Photographique de H. de Fonteny et Cie,"French, 1817–1892",,"Teynard, Félix|Imprimerie Photographique de H. de Fonteny et Cie",French,1817,1892,"1851–52, printed 1853–54",1851,1852,Salted paper print from paper negative,23.9 x 30.5 cm. (9 7/16 x 12 in.),"Purchase, Lila Acheson Wallace Gift, 1976",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261839,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.607.41,false,true,261840,Photographs,Photograph,"Gournah (Thèbes), Colosse de Gauche - Décoration de la Face Nord-Est du Trône",,,,,,Artist|Printer,,Félix Teynard|Imprimerie Photographique de H. de Fonteny et Cie,"French, 1817–1892",,"Teynard, Félix|Imprimerie Photographique de H. de Fonteny et Cie",French,1817,1892,"1851–52, printed 1853–54",1851,1852,Salted paper print from paper negative,24.4 x 30.0 cm. (9 5/8 x 11 13/16 in.),"Purchase, Lila Acheson Wallace Gift, 1976",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261840,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.607.42,false,true,261841,Photographs,Photograph,"Gournah (Thèbes), Palais Dit le Memnonium",,,,,,Artist|Printer,,Félix Teynard|Imprimerie Photographique de H. de Fonteny et Cie,"French, 1817–1892",,"Teynard, Félix|Imprimerie Photographique de H. de Fonteny et Cie",French,1817,1892,"1851–52, printed 1853–54",1851,1852,Salted paper print from paper negative,24.5 x 30.1 cm. (9 5/8 x 11 7/8 in.),"Purchase, Lila Acheson Wallace Gift, 1976",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261841,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.607.43,false,true,261842,Photographs,Photograph,"Karnak, Groupe de Dattiers Vu du Point A",,,,,,Artist|Printer,,Félix Teynard|Imprimerie Photographique de H. de Fonteny et Cie,"French, 1817–1892",,"Teynard, Félix|Imprimerie Photographique de H. de Fonteny et Cie",French,1817,1892,"1851–52, printed 1853–54",1851,1852,Salted paper print from paper negative,24.5 x 30.9 cm. (9 5/8 x 12 3/16 in.),"Purchase, Lila Acheson Wallace Gift, 1976",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261842,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.607.44,false,true,261843,Photographs,Photograph,"Karnak (Thèbes), Vue Générale des Ruines Prise du Point B",,,,,,Artist|Printer,,Félix Teynard|Imprimerie Photographique de H. de Fonteny et Cie,"French, 1817–1892",,"Teynard, Félix|Imprimerie Photographique de H. de Fonteny et Cie",French,1817,1892,"1851–52, printed 1853–54",1851,1852,Salted paper print from paper negative,23.6 x 30.4 cm. (9 5/16 x 12 in.),"Purchase, Lila Acheson Wallace Gift, 1976",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261843,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.607.45,false,true,261844,Photographs,Photograph,"Karnak (Thèbes), Grande Porte du Sud Vue du Point C",,,,,,Artist|Printer,,Félix Teynard|Imprimerie Photographique de H. de Fonteny et Cie,"French, 1817–1892",,"Teynard, Félix|Imprimerie Photographique de H. de Fonteny et Cie",French,1817,1892,"1851–52, printed 1853–54",1851,1852,Salted paper print from paper negative,24.0 x 30.3 cm. (9 7/16 x 11 15/16 in.),"Purchase, Lila Acheson Wallace Gift, 1976",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261844,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.607.46,false,true,261845,Photographs,Photograph,"Karnak (Thèbes), Vue Générale des Ruines Prise du Sud-Est, en T",,,,,,Artist|Printer,,Félix Teynard|Imprimerie Photographique de H. de Fonteny et Cie,"French, 1817–1892",,"Teynard, Félix|Imprimerie Photographique de H. de Fonteny et Cie",French,1817,1892,"1851–52, printed 1853–54",1851,1852,Salted paper print from paper negative,23.6 x 30.5 cm. (9 5/16 x 12 in.),"Purchase, Lila Acheson Wallace Gift, 1976",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261845,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.607.47,false,true,261846,Photographs,Photograph,"Karnak (Thèbes), Vue Générale des Ruines Prise du Nord-Est, en V",,,,,,Artist|Printer,,Félix Teynard|Imprimerie Photographique de H. de Fonteny et Cie,"French, 1817–1892",,"Teynard, Félix|Imprimerie Photographique de H. de Fonteny et Cie",French,1817,1892,"1851–52, printed 1853–54",1851,1852,Salted paper print from paper negative,24.3 x 30.4 cm. (9 9/16 x 12 in.),"Purchase, Lila Acheson Wallace Gift, 1976",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261846,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.607.48,false,true,261847,Photographs,Photograph,"Karnak (Thèbes), Enciente du Palais Vue du Point H",,,,,,Artist|Printer,,Félix Teynard|Imprimerie Photographique de H. de Fonteny et Cie,"French, 1817–1892",,"Teynard, Félix|Imprimerie Photographique de H. de Fonteny et Cie",French,1817,1892,"1851–52, printed 1853–54",1851,1852,Salted paper print from paper negative,23.8 x 30.7 cm (9 3/8 x 12 1/16 in.),"Purchase, Lila Acheson Wallace Gift, 1976",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261847,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.607.49,false,true,261848,Photographs,Photograph,"Karnak (Thèbes), Enciente du Palais - Détailes de Sculptures au Point N",,,,,,Artist|Printer,,Félix Teynard|Imprimerie Photographique de H. de Fonteny et Cie,"French, 1817–1892",,"Teynard, Félix|Imprimerie Photographique de H. de Fonteny et Cie",French,1817,1892,"1851–52, printed 1853–54",1851,1852,Salted paper print from paper negative,24.2 x 30.1 cm. (9 1/2 x 11 7/8 in.),"Purchase, Lila Acheson Wallace Gift, 1976",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261848,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.607.50,false,true,261850,Photographs,Photograph,"Karnak (Thèbes), Enciente du Palais - Détails de Sculptures au Point O",,,,,,Artist|Printer,,Félix Teynard|Imprimerie Photographique de H. de Fonteny et Cie,"French, 1817–1892",,"Teynard, Félix|Imprimerie Photographique de H. de Fonteny et Cie",French,1817,1892,"1851–52, printed 1853–54",1851,1852,Salted paper print from paper negative,24.5 x 30.2 cm. (9 5/8 x 11 7/8 in.),"Purchase, Lila Acheson Wallace Gift, 1976",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261850,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.607.51,false,true,261851,Photographs,Photograph,"Karnak (Thèbes), Cour du Palais - Vue Prise de Point I",,,,,,Artist|Printer,,Félix Teynard|Imprimerie Photographique de H. de Fonteny et Cie,"French, 1817–1892",,"Teynard, Félix|Imprimerie Photographique de H. de Fonteny et Cie",French,1817,1892,"1851–52, printed 1853–54",1851,1852,Salted paper print from paper negative,30.7 x 25.2 cm. (12 1/16 x 9 15/16 in.),"Purchase, Lila Acheson Wallace Gift, 1976",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261851,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.607.52,false,true,261852,Photographs,Photograph,"Karnak (Thèbes), Palais - Salle Hypostyle - Vue Générale Prise du Point Q",,,,,,Artist|Printer,,Félix Teynard|Imprimerie Photographique de H. de Fonteny et Cie,"French, 1817–1892",,"Teynard, Félix|Imprimerie Photographique de H. de Fonteny et Cie",French,1817,1892,"1851–52, printed 1853–54",1851,1852,Salted paper print from paper negative,24.7 x 30.5 cm. (9 3/4 x 12 in.),"Purchase, Lila Acheson Wallace Gift, 1976",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261852,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.607.53,false,true,261853,Photographs,Photograph,"Karnak (Thèbes), Palais - Salle Hypostyle - Colonnade Centrale Vue du Point J",,,,,,Artist|Printer,,Félix Teynard|Imprimerie Photographique de H. de Fonteny et Cie,"French, 1817–1892",,"Teynard, Félix|Imprimerie Photographique de H. de Fonteny et Cie",French,1817,1892,"1851–52, printed 1853–54",1851,1852,Salted paper print from paper negative,30.2 x 24.4 cm. (11 7/8 x 9 5/8 in.),"Purchase, Lila Acheson Wallace Gift, 1976",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261853,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.607.54,false,true,261854,Photographs,Photograph,"Karnak (Thèbes), Palais - Salle Hypostyle - Colonnade Centrale - Décoration d'un Fut",,,,,,Artist|Printer,,Félix Teynard|Imprimerie Photographique de H. de Fonteny et Cie,"French, 1817–1892",,"Teynard, Félix|Imprimerie Photographique de H. de Fonteny et Cie",French,1817,1892,"1851–52, printed 1853–54",1851,1852,Salted paper print from paper negative,25.0 x 16.3 cm. (9 13/16 x 6 7/16 in.),"Purchase, Lila Acheson Wallace Gift, 1976",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261854,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.607.55,false,true,261855,Photographs,Photograph,"Karnak (Thèbes), Palais - Salle Hypostyle - Colonnade Centrale - Chapiteaux",,,,,,Artist|Printer,,Félix Teynard|Imprimerie Photographique de H. de Fonteny et Cie,"French, 1817–1892",,"Teynard, Félix|Imprimerie Photographique de H. de Fonteny et Cie",French,1817,1892,"1851–52, printed 1853–54",1851,1852,Salted paper print from paper negative,30.3 x 25.4 cm. (11 15/16 x 10 in.),"Purchase, Lila Acheson Wallace Gift, 1976",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261855,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.607.56,false,true,261856,Photographs,Photograph,"Karnak (Thèbes), Palais - Salle Hypostyle - Fenêtre et Chapiteaux des Galleries Latérales",,,,,,Artist|Printer,,Félix Teynard|Imprimerie Photographique de H. de Fonteny et Cie,"French, 1817–1892",,"Teynard, Félix|Imprimerie Photographique de H. de Fonteny et Cie",French,1817,1892,"1851–52, printed 1853–54",1851,1852,Salted paper print from paper negative,30.2 x 25.0 cm (11 7/8 x 9 13/16 in.),"Purchase, Lila Acheson Wallace Gift, 1976",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261856,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.607.57,false,true,261857,Photographs,Photograph,"Karnak (Thèbes), Palais - Salle Hypostyle - Fenêtre et Chapiteaux des Galeries Latérales",,,,,,Artist|Printer,,Félix Teynard|Imprimerie Photographique de H. de Fonteny et Cie,"French, 1817–1892",,"Teynard, Félix|Imprimerie Photographique de H. de Fonteny et Cie",French,1817,1892,"1851–52, printed 1853–54",1851,1852,Salted paper print from paper negative,30.7 x 25.5 cm. (12 1/16 x 10 1/16 in.),"Purchase, Lila Acheson Wallace Gift, 1976",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261857,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.607.58,false,true,261858,Photographs,Photograph,"Karnak (Thèbes), Palais - Salle Hypostyle - Décoration de la Paroi Intérieure au Point L",,,,,,Artist|Printer,,Félix Teynard|Imprimerie Photographique de H. de Fonteny et Cie,"French, 1817–1892",,"Teynard, Félix|Imprimerie Photographique de H. de Fonteny et Cie",French,1817,1892,"1851–52, printed 1853–54",1851,1852,Salted paper print from paper negative,30.1 x 25.0 cm. (11 7/8 x 9 13/16 in.),"Purchase, Lila Acheson Wallace Gift, 1976",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261858,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.607.59,false,true,261859,Photographs,Photograph,"Karnak (Thèbes), Palais - Salle Hypostyle - Décoration de la Paroi Intérieure au Point M",,,,,,Artist|Printer,,Félix Teynard|Imprimerie Photographique de H. de Fonteny et Cie,"French, 1817–1892",,"Teynard, Félix|Imprimerie Photographique de H. de Fonteny et Cie",French,1817,1892,"1851–52, printed 1853–54",1851,1852,Salted paper print from paper negative,23.3 x 30.5 cm. (9 3/16 x 12 in.),"Purchase, Lila Acheson Wallace Gift, 1976",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261859,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.607.60,false,true,261861,Photographs,Photograph,"Karnak (Thèbes), Palais - Construction de Granit - Pilier Sculpté, au Point P",,,,,,Artist|Printer,,Félix Teynard|Imprimerie Photographique de H. de Fonteny et Cie,"French, 1817–1892",,"Teynard, Félix|Imprimerie Photographique de H. de Fonteny et Cie",French,1817,1892,"1851–52, printed 1853–54",1851,1852,Salted paper print from paper negative,26.8 x 13.5 cm. (10 9/16 x 5 5/16 in.),"Purchase, Lila Acheson Wallace Gift, 1976",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261861,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.607.61,false,true,261862,Photographs,Photograph,"Karnak (Thèbes), Palais - Construction de Granit - Décoration Sculptée et Piente au Point R",,,,,,Artist|Printer,,Félix Teynard|Imprimerie Photographique de H. de Fonteny et Cie,"French, 1817–1892",,"Teynard, Félix|Imprimerie Photographique de H. de Fonteny et Cie",French,1817,1892,"1851–52, printed 1853–54",1851,1852,Salted paper print from paper negative,23.7 x 30.5 cm. (9 5/16 x 12 in.),"Purchase, Lila Acheson Wallace Gift, 1976",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261862,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.607.62,false,true,261863,Photographs,Photograph,"Karnak (Thèbes), Palais - Partie Posterieure - Fragment de Colonnades Vu du Point S",,,,,,Artist|Printer,,Félix Teynard|Imprimerie Photographique de H. de Fonteny et Cie,"French, 1817–1892",,"Teynard, Félix|Imprimerie Photographique de H. de Fonteny et Cie",French,1817,1892,"1851–52, printed 1853–54",1851,1852,Salted paper print from paper negative,23.9 x 30.6 cm. (9 7/16 x 12 1/16 in.),"Purchase, Lila Acheson Wallace Gift, 1976",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261863,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.607.63,false,true,261864,Photographs,Photograph,"Karnak (Thèbes), Édifice en Ruines - Sculptures du la Paroi Intèrieure, en U",,,,,,Artist|Printer,,Félix Teynard|Imprimerie Photographique de H. de Fonteny et Cie,"French, 1817–1892",,"Teynard, Félix|Imprimerie Photographique de H. de Fonteny et Cie",French,1817,1892,"1851–52, printed 1853–54",1851,1852,Salted paper print from paper negative,24.9 x 30.3 cm. (9 13/16 x 11 15/16 in.),"Purchase, Lila Acheson Wallace Gift, 1976",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261864,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.607.64,false,true,261865,Photographs,Photograph,"Karnak (Thèbes), Grande Porte du Nord Vue du Point X",,,,,,Artist|Printer,,Félix Teynard|Imprimerie Photographique de H. de Fonteny et Cie,"French, 1817–1892",,"Teynard, Félix|Imprimerie Photographique de H. de Fonteny et Cie",French,1817,1892,"1851–52, printed 1853–54",1851,1852,Salted paper print from paper negative,23.7 x 30.6 cm. (9 5/16 x 12 1/16 in.),"Purchase, Lila Acheson Wallace Gift, 1976",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261865,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.607.65,false,true,261866,Photographs,Photograph,"Karnak (Thèbes), Troisième Pylône - Colosse de Spath Calcaire, en D",,,,,,Artist|Printer,,Félix Teynard|Imprimerie Photographique de H. de Fonteny et Cie,"French, 1817–1892",,"Teynard, Félix|Imprimerie Photographique de H. de Fonteny et Cie",French,1817,1892,"1851–52, printed 1853–54",1851,1852,Salted paper print from paper negative,24.0 x 30.3 cm. (9 7/16 x 11 15/16 in.),"Purchase, Lila Acheson Wallace Gift, 1976",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261866,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.607.66,false,true,261867,Photographs,Photograph,"Karnak (Thèbes), Premier Pylône - Ruines de la Porte et des Colosses, Vues du Point E",,,,,,Artist|Printer,,Félix Teynard|Imprimerie Photographique de H. de Fonteny et Cie,"French, 1817–1892",,"Teynard, Félix|Imprimerie Photographique de H. de Fonteny et Cie",French,1817,1892,"1851–52, printed 1853–54",1851,1852,Salted paper print from paper negative,24.3 x 30.5 cm. (9 9/16 x 12 in.),"Purchase, Lila Acheson Wallace Gift, 1976",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261867,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.607.67,false,true,261868,Photographs,Photograph,"Karnak (Thèbes), Avenue de Sphinx - Vue Générale Prise du Point G",,,,,,Artist|Printer,,Félix Teynard|Imprimerie Photographique de H. de Fonteny et Cie,"French, 1817–1892",,"Teynard, Félix|Imprimerie Photographique de H. de Fonteny et Cie",French,1817,1892,"1851–52, printed 1853–54",1851,1852,Salted paper print from paper negative,24.1 x 30.5 cm. (9 1/2 x 12 in.),"Purchase, Lila Acheson Wallace Gift, 1976",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261868,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.607.68,false,true,261869,Photographs,Photograph,"Karnak (Thèbes), Sphinx a Tête Humaine et a Tête de Bélier, en Y",,,,,,Artist|Printer,,Félix Teynard|Imprimerie Photographique de H. de Fonteny et Cie,"French, 1817–1892",,"Teynard, Félix|Imprimerie Photographique de H. de Fonteny et Cie",French,1817,1892,"1851–52, printed 1853–54",1851,1852,Salted paper print from paper negative,24.0 x 30.6 cm. (9 7/16 x 12 1/16 in.),"Purchase, Lila Acheson Wallace Gift, 1976",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261869,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.607.69,false,true,261870,Photographs,Photograph,"Erment (Hermonthis), Vue Générale des Ruines -Temple et Mammisi",,,,,,Artist|Printer,,Félix Teynard|Imprimerie Photographique de H. de Fonteny et Cie,"French, 1817–1892",,"Teynard, Félix|Imprimerie Photographique de H. de Fonteny et Cie",French,1817,1892,"1851–52, printed 1853–54",1851,1852,Salted paper print from paper negative,24.3 x 30.2 cm. (9 9/16 x 11 7/8 in.),"Purchase, Lila Acheson Wallace Gift, 1976",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261870,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.607.70,false,true,261872,Photographs,Photograph,"Esneh (Latopolis), Construction Ensablée - Paroi Extérieure - Corniche et Sculptures",,,,,,Artist|Printer,,Félix Teynard|Imprimerie Photographique de H. de Fonteny et Cie,"French, 1817–1892",,"Teynard, Félix|Imprimerie Photographique de H. de Fonteny et Cie",French,1817,1892,"1851–52, printed 1853–54",1851,1852,Salted paper print from paper negative,23.8 x 30.5 cm. (9 3/8 x 12 in.),"Purchase, Lila Acheson Wallace Gift, 1976",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261872,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.607.71,false,true,261873,Photographs,Photograph,"Esneh (Latopolis), Construction Ensablée - Architrave, Futs, et Chapiteaux",,,,,,Artist|Printer,,Félix Teynard|Imprimerie Photographique de H. de Fonteny et Cie,"French, 1817–1892",,"Teynard, Félix|Imprimerie Photographique de H. de Fonteny et Cie",French,1817,1892,"1851–52, printed 1853–54",1851,1852,Salted paper print from paper negative,25.0 x 30.7 cm. (9 13/16 x 12 1/16 in.),"Purchase, Lila Acheson Wallace Gift, 1976",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261873,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.607.72,false,true,261874,Photographs,Photograph,"Esneh, Dattiers, Sycomore et Café Sur le Bord du Nil",,,,,,Artist|Printer,,Félix Teynard|Imprimerie Photographique de H. de Fonteny et Cie,"French, 1817–1892",,"Teynard, Félix|Imprimerie Photographique de H. de Fonteny et Cie",French,1817,1892,"1851–52, printed 1853–54",1851,1852,Salted paper print from paper negative,23.8 x 30.5 cm. (9 3/8 x 12 in.),"Purchase, Lila Acheson Wallace Gift, 1976",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261874,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.607.73,false,true,261875,Photographs,Photograph,"El-Kab (Éléthya), Vue Générale de l'Hémi-Spéos",,,,,,Artist|Printer,,Félix Teynard|Imprimerie Photographique de H. de Fonteny et Cie,"French, 1817–1892",,"Teynard, Félix|Imprimerie Photographique de H. de Fonteny et Cie",French,1817,1892,"1851–52, printed 1853–54",1851,1852,Salted paper print from paper negative,23.9 x 30.5 cm. (9 7/16 x 12 in.),"Purchase, Lila Acheson Wallace Gift, 1976",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261875,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.607.74,false,true,261876,Photographs,Photograph,"El-Kab (Éléthya), Architecture Hypogéenne - Tombeau de Phapé - Sculptures Pientes",,,,,,Artist|Printer,,Félix Teynard|Imprimerie Photographique de H. de Fonteny et Cie,"French, 1817–1892",,"Teynard, Félix|Imprimerie Photographique de H. de Fonteny et Cie",French,1817,1892,"1851–52, printed 1853–54",1851,1852,Salted paper print from paper negative,25.2 x 30.2 cm. (9 15/16 x 11 7/8 in.),"Purchase, Lila Acheson Wallace Gift, 1976",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261876,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.607.75,false,true,261877,Photographs,Photograph,"Edfou (Apollonopolis Magna), Vue Générale du Temple",,,,,,Artist|Printer,,Félix Teynard|Imprimerie Photographique de H. de Fonteny et Cie,"French, 1817–1892",,"Teynard, Félix|Imprimerie Photographique de H. de Fonteny et Cie",French,1817,1892,"1851–52, printed 1853–54",1851,1852,Salted paper print from paper negative,23.9 x 30.4 cm. (9 7/16 x 12 in.),"Purchase, Lila Acheson Wallace Gift, 1976",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261877,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.607.76,false,true,261878,Photographs,Photograph,"Edfou (Apollonopolis Magna), Galerie Latérale de la Cour et Pronaos",,,,,,Artist|Printer,,Félix Teynard|Imprimerie Photographique de H. de Fonteny et Cie,"French, 1817–1892",,"Teynard, Félix|Imprimerie Photographique de H. de Fonteny et Cie",French,1817,1892,"1851–52, printed 1853–54",1851,1852,Salted paper print from paper negative,24.7 x 30.8 cm. (9 3/4 x 12 1/8 in.),"Purchase, Lila Acheson Wallace Gift, 1976",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261878,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.607.77,false,true,261879,Photographs,Photograph,"Edfou, Aspect Générale de la Ville Vue de la Plateforme Centrale du Pylône",,,,,,Artist|Printer,,Félix Teynard|Imprimerie Photographique de H. de Fonteny et Cie,"French, 1817–1892",,"Teynard, Félix|Imprimerie Photographique de H. de Fonteny et Cie",French,1817,1892,"1851–52, printed 1853–54",1851,1852,Salted paper print from paper negative,30.5 x 25.0 cm. (12 x 9 13/16 in.),"Purchase, Lila Acheson Wallace Gift, 1976",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261879,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.607.78,false,true,261880,Photographs,Photograph,"Djébel Selséleh (Silsilis), Steles Architecturales Taillées Dans les Carriéres",,,,,,Artist|Printer,,Félix Teynard|Imprimerie Photographique de H. de Fonteny et Cie,"French, 1817–1892",,"Teynard, Félix|Imprimerie Photographique de H. de Fonteny et Cie",French,1817,1892,"1851–52, printed 1853–54",1851,1852,Salted paper print from paper negative,23.5 x 30.3 cm. (9 1/4 x 11 15/16 in.),"Purchase, Lila Acheson Wallace Gift, 1976",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261880,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.607.79,false,true,261881,Photographs,Photograph,"Kôm-Ombou (Ombos), Vue Générale des Ruines",,,,,,Artist|Printer,,Félix Teynard|Imprimerie Photographique de H. de Fonteny et Cie,"French, 1817–1892",,"Teynard, Félix|Imprimerie Photographique de H. de Fonteny et Cie",French,1817,1892,"1851–52, printed 1853–54",1851,1852,Salted paper print from paper negative,23.8 x 30.7 cm. (9 3/8 x 12 1/16 in.),"Purchase, Lila Acheson Wallace Gift, 1976",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261881,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.607.80,false,true,261883,Photographs,Photograph,"Assouan, Ruines de l'Ancienne Enciente Arabe, au Sud-Est de la Ville",,,,,,Artist|Printer,,Félix Teynard|Imprimerie Photographique de H. de Fonteny et Cie,"French, 1817–1892",,"Teynard, Félix|Imprimerie Photographique de H. de Fonteny et Cie",French,1817,1892,"1851–52, printed 1853–54",1851,1852,Salted paper print from paper negative,23.8 x 30.4 cm. (9 3/8 x 12 in.),"Purchase, Lila Acheson Wallace Gift, 1976",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261883,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.607.81,false,true,261884,Photographs,Photograph,"Assouan (Syène), Carrières de Granit - Ancien Système d'Extraction",,,,,,Artist|Printer,,Félix Teynard|Imprimerie Photographique de H. de Fonteny et Cie,"French, 1817–1892",,"Teynard, Félix|Imprimerie Photographique de H. de Fonteny et Cie",French,1817,1892,"1851–52, printed 1853–54",1851,1852,Salted paper print from paper negative,23.8 x 30.3 cm. (9 3/8 x 11 15/16 in.),"Purchase, Lila Acheson Wallace Gift, 1976",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261884,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.607.82,false,true,261885,Photographs,Photograph,"Assouan, Cimetière Arabe - Inscription Funéraires",,,,,,Artist|Printer,,Félix Teynard|Imprimerie Photographique de H. de Fonteny et Cie,"French, 1817–1892",,"Teynard, Félix|Imprimerie Photographique de H. de Fonteny et Cie",French,1817,1892,"1851–52, printed 1853–54",1851,1852,Salted paper print from paper negative,23.7 x 30.9 cm. (9 5/16 x 12 3/16 in.),"Purchase, Lila Acheson Wallace Gift, 1976",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261885,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.607.83,false,true,261886,Photographs,Photograph,"Première Cataracte, Vue Générale Prise de la Point Méridionale de l'Ile d'Éléphantine",,,,,,Artist|Printer,,Félix Teynard|Imprimerie Photographique de H. de Fonteny et Cie,"French, 1817–1892",,"Teynard, Félix|Imprimerie Photographique de H. de Fonteny et Cie",French,1817,1892,"1851–52, printed 1853–54",1851,1852,Salted paper print from paper negative,24.2 x 30.7 cm. (9 1/2 x 12 1/16 in.),"Purchase, Lila Acheson Wallace Gift, 1976",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261886,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.607.84,false,true,261887,Photographs,Photograph,"Première Cataracte, Montagnes Granitiques Couvertes de Sables",,,,,,Artist|Printer,,Félix Teynard|Imprimerie Photographique de H. de Fonteny et Cie,"French, 1817–1892",,"Teynard, Félix|Imprimerie Photographique de H. de Fonteny et Cie",French,1817,1892,"1851–52, printed 1853–54",1851,1852,Salted paper print from paper negative,23.6 x 30.3 cm. (9 5/16 x 11 15/16 in.),"Purchase, Lila Acheson Wallace Gift, 1976",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261887,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.607.85,false,true,261888,Photographs,Photograph,"Ile de Fîleh (Philæ), Vue Générale Prise du Sud-Est au Point B",,,,,,Artist|Printer,,Félix Teynard|Imprimerie Photographique de H. de Fonteny et Cie,"French, 1817–1892",,"Teynard, Félix|Imprimerie Photographique de H. de Fonteny et Cie",French,1817,1892,"1851–52, printed 1853–54",1851,1852,Salted paper print from paper negative,24.2 x 30.5 cm. (9 1/2 x 12 in.),"Purchase, Lila Acheson Wallace Gift, 1976",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261888,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.607.86,false,true,261889,Photographs,Photograph,"Ile de Fîleh (Philæ), Édifice de l'Est - Vue Générale Prise du Point C",,,,,,Artist|Printer,,Félix Teynard|Imprimerie Photographique de H. de Fonteny et Cie,"French, 1817–1892",,"Teynard, Félix|Imprimerie Photographique de H. de Fonteny et Cie",French,1817,1892,"1851–52, printed 1853–54",1851,1852,Salted paper print from paper negative,20.8 x 30.5 cm. (8 3/16 x 12 in.),"Purchase, Lila Acheson Wallace Gift, 1976",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261889,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.607.87,false,true,261890,Photographs,Photograph,"Ile de Fîleh (Philæ), Édifice de l'Est - Face Latérale Vue du Point D",,,,,,Artist|Printer,,Félix Teynard|Imprimerie Photographique de H. de Fonteny et Cie,"French, 1817–1892",,"Teynard, Félix|Imprimerie Photographique de H. de Fonteny et Cie",French,1817,1892,"1851–52, printed 1853–54",1851,1852,Salted paper print from paper negative,23.5 x 30.3 cm. (9 1/4 x 11 15/16 in.),"Purchase, Lila Acheson Wallace Gift, 1976",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261890,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.607.88,false,true,261891,Photographs,Photograph,"Ile de Fîleh (Philæ), Édifice de l'Est - Façade Occidentale - Vue du Point E",,,,,,Artist|Printer,,Félix Teynard|Imprimerie Photographique de H. de Fonteny et Cie,"French, 1817–1892",,"Teynard, Félix|Imprimerie Photographique de H. de Fonteny et Cie",French,1817,1892,"1851–52, printed 1853–54",1851,1852,Salted paper print from paper negative,25.1 x 30.5 cm. (9 7/8 x 12 in.),"Purchase, Lila Acheson Wallace Gift, 1976",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261891,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.607.89,false,true,261892,Photographs,Photograph,"Ile de Fîleh (Philæ), Édifice du Sud et Partie de la Colonnade Occidentale Vue du Point V",,,,,,Artist|Printer,,Félix Teynard|Imprimerie Photographique de H. de Fonteny et Cie,"French, 1817–1892",,"Teynard, Félix|Imprimerie Photographique de H. de Fonteny et Cie",French,1817,1892,"1851–52, printed 1853–54",1851,1852,Salted paper print from paper negative,23.7 x 30.5 cm. (9 5/16 x 12 in.),"Purchase, Lila Acheson Wallace Gift, 1976",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261892,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.607.90,false,true,261894,Photographs,Photograph,"Ile de Fîleh (Philæ), Colonnade Occidentale - Ruines Vues du Point L",,,,,,Artist|Printer,,Félix Teynard|Imprimerie Photographique de H. de Fonteny et Cie,"French, 1817–1892",,"Teynard, Félix|Imprimerie Photographique de H. de Fonteny et Cie",French,1817,1892,"1851–52, printed 1853–54",1851,1852,Salted paper print from paper negative,23.2 x 30.0 cm. (9 1/8 x 11 13/16 in.),"Purchase, Lila Acheson Wallace Gift, 1976",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261894,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.607.91,false,true,261895,Photographs,Photograph,"Ile de Fîleh (Philæ), Premier Pylône - Vue Prise de la Plate-Forme de la Colonnade Orientale en P",,,,,,Artist|Printer,,Félix Teynard|Imprimerie Photographique de H. de Fonteny et Cie,"French, 1817–1892",,"Teynard, Félix|Imprimerie Photographique de H. de Fonteny et Cie",French,1817,1892,"1851–52, printed 1853–54",1851,1852,Salted paper print from paper negative,24.3 x 30.5 cm. (9 9/16 x 12 in.),"Purchase, Lila Acheson Wallace Gift, 1976",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261895,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.607.92,false,true,261896,Photographs,Photograph,"Ile de Fîleh (Philæ), Vue Génèrale Prise du Point I, Sur La Plateforme du Pylône",,,,,,Artist|Printer,,Félix Teynard|Imprimerie Photographique de H. de Fonteny et Cie,"French, 1817–1892",,"Teynard, Félix|Imprimerie Photographique de H. de Fonteny et Cie",French,1817,1892,"1851–52, printed 1853–54",1851,1852,Salted paper print from paper negative,23.8 x 30.7 cm. (9 3/8 x 12 1/16 in.),"Purchase, Lila Acheson Wallace Gift, 1976",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261896,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.607.93,false,true,261897,Photographs,Photograph,"Ile de Fîleh (Philæ), Deuxième Pylône - Partie Orientale Vue de la Plateforme Inférieure du Premier Pylône, du Point G",,,,,,Artist|Printer,,Félix Teynard|Imprimerie Photographique de H. de Fonteny et Cie,"French, 1817–1892",,"Teynard, Félix|Imprimerie Photographique de H. de Fonteny et Cie",French,1817,1892,"1851–52, printed 1853–54",1851,1852,Salted paper print from paper negative,30.2 x 23.4 cm. (11 7/8 x 9 3/16 in.),"Purchase, Lila Acheson Wallace Gift, 1976",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261897,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.607.94,false,true,261898,Photographs,Photograph,"Environs de Fîleh, Palmier Doum sur la Rive Orientale du Nil",,,,,,Artist|Printer,,Félix Teynard|Imprimerie Photographique de H. de Fonteny et Cie,"French, 1817–1892",,"Teynard, Félix|Imprimerie Photographique de H. de Fonteny et Cie",French,1817,1892,"1851–52, printed 1853–54",1851,1852,Salted paper print from paper negative,23.7 x 30.8 cm. (9 5/16 x 12 1/8 in.),"Purchase, Lila Acheson Wallace Gift, 1976",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261898,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.607.95,false,true,261899,Photographs,Photograph,"Débôd (Parembole), Vue Générale des Ruines",,,,,,Artist|Printer,,Félix Teynard|Imprimerie Photographique de H. de Fonteny et Cie,"French, 1817–1892",,"Teynard, Félix|Imprimerie Photographique de H. de Fonteny et Cie",French,1817,1892,"1851–52, printed 1853–54",1851,1852,Salted paper print from paper negative,24.1 x 30.7 cm. (9 1/2 x 12 1/16 in.),"Purchase, Lila Acheson Wallace Gift, 1976",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261899,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.607.96,false,true,261900,Photographs,Photograph,"Kardâcy, Sanctuaire, Niches et Inscriptions Taillées dans les Carrières",,,,,,Artist|Printer,,Félix Teynard|Imprimerie Photographique de H. de Fonteny et Cie,"French, 1817–1892",,"Teynard, Félix|Imprimerie Photographique de H. de Fonteny et Cie",French,1817,1892,"1851–52, printed 1853–54",1851,1852,Salted paper print from paper negative,23.7 x 30.6 cm. (9 5/16 x 12 1/16 in.),"Purchase, Lila Acheson Wallace Gift, 1976",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261900,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.607.97,false,true,261901,Photographs,Photograph,"Tâfah, Rochers Granitiques sur les Rives du Nil",,,,,,Artist|Printer,,Félix Teynard|Imprimerie Photographique de H. de Fonteny et Cie,"French, 1817–1892",,"Teynard, Félix|Imprimerie Photographique de H. de Fonteny et Cie",French,1817,1892,"1851–52, printed 1853–54",1851,1852,Salted paper print from paper negative,24.9 x 30.1 cm. (9 13/16 x 11 7/8 in.),"Purchase, Lila Acheson Wallace Gift, 1976",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261901,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.607.98,false,true,261902,Photographs,Photograph,"Kalabcheh (Talmis), Ruines du temple - Façade et Mur d'Enceinte",,,,,,Artist|Printer,,Félix Teynard|Imprimerie Photographique de H. de Fonteny et Cie,"French, 1817–1892",,"Teynard, Félix|Imprimerie Photographique de H. de Fonteny et Cie",French,1817,1892,"1851–52, printed 1853–54",1851,1852,Salted paper print from paper negative,23.6 x 30.3 cm. (9 5/16 x 11 15/16 in.),"Purchase, Lila Acheson Wallace Gift, 1976",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261902,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.607.99,false,true,261903,Photographs,Photograph,"Dandour, Vue Générale des Ruines",,,,,,Artist|Printer,,Félix Teynard|Imprimerie Photographique de H. de Fonteny et Cie,"French, 1817–1892",,"Teynard, Félix|Imprimerie Photographique de H. de Fonteny et Cie",French,1817,1892,"1851–52, printed 1853–54",1851,1852,Salted paper print from paper negative,23.2 x 30.2 cm. (9 1/8 x 11 7/8 in.),"Purchase, Lila Acheson Wallace Gift, 1976",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261903,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.607.100,false,true,261801,Photographs,Photograph,"Djerf-Hocein (Tutzis), Hemi-Spéos, Colosses de la Partie Extérieure",,,,,,Artist|Printer,,Félix Teynard|Imprimerie Photographique de H. de Fonteny et Cie,"French, 1817–1892",,"Teynard, Félix|Imprimerie Photographique de H. de Fonteny et Cie",French,1817,1892,"1851–52, printed 1853–54",1851,1852,Salted paper print from paper negative,23.7 x 30.4 cm. (9 5/16 x 12 in.),"Purchase, Lila Acheson Wallace Gift, 1976",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261801,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.607.101,false,true,261802,Photographs,Photograph,"Dakkeh (Pselcis), Vue Générale du Temple",,,,,,Artist|Printer,,Félix Teynard|Imprimerie Photographique de H. de Fonteny et Cie,"French, 1817–1892",,"Teynard, Félix|Imprimerie Photographique de H. de Fonteny et Cie",French,1817,1892,"1851–52, printed 1853–54",1851,1852,Salted paper print from paper negative,23.1 x 30.2 cm. (9 1/16 x 11 7/8 in.),"Purchase, Lila Acheson Wallace Gift, 1976",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261802,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.607.102,false,true,261803,Photographs,Photograph,"Korósko, Sycomores et Campement d'une Caravane pour le Sennâr",,,,,,Artist|Printer,,Félix Teynard|Imprimerie Photographique de H. de Fonteny et Cie,"French, 1817–1892",,"Teynard, Félix|Imprimerie Photographique de H. de Fonteny et Cie",French,1817,1892,"1851–52, printed 1853–54",1851,1852,Salted paper print from paper negative,23.4 x 29.5 cm. (9 3/16 x 11 5/8 in.),"Purchase, Lila Acheson Wallace Gift, 1976",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261803,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.607.103,false,true,261804,Photographs,Photograph,"Deîr, Carrefour et Habitation Particulière",,,,,,Artist|Printer,,Félix Teynard|Imprimerie Photographique de H. de Fonteny et Cie,"French, 1817–1892",,"Teynard, Félix|Imprimerie Photographique de H. de Fonteny et Cie",French,1817,1892,"1851–52, printed 1853–54",1851,1852,Salted paper print from paper negative,23.6 x 30.2 cm. (9 5/16 x 11 7/8 in.),"Purchase, Lila Acheson Wallace Gift, 1976",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261804,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.607.104,false,true,261805,Photographs,Photograph,"Abou-Sembil, Petit Spéos - Partie Gauche de la Façade",,,,,,Artist|Printer,,Félix Teynard|Imprimerie Photographique de H. de Fonteny et Cie,"French, 1817–1892",,"Teynard, Félix|Imprimerie Photographique de H. de Fonteny et Cie",French,1817,1892,"1851–52, printed 1853–54",1851,1852,Salted paper print from paper negative,23.3 x 30.5 cm. (9 3/16 x 12 in.),"Purchase, Lila Acheson Wallace Gift, 1976",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261805,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.607.105,false,true,261806,Photographs,Photograph,"Abou Sembil, Grand Spéos - Statues Colossales, Vues de Trois-Quarts",,,,,,Artist|Printer,,Félix Teynard|Imprimerie Photographique de H. de Fonteny et Cie,"French, 1817–1892",,"Teynard, Félix|Imprimerie Photographique de H. de Fonteny et Cie",French,1817,1892,"1851–52, printed 1853–54",1851,1852,Salted paper print from paper negative,30.0 x 24.4 cm. (11 13/16 x 9 5/8 in.),"Purchase, Lila Acheson Wallace Gift, 1976",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261806,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.63,false,true,685405,Photographs,Carte-de-visite,[Unknown Subject],,,,,,Photography Studio,,Walery Frères,"French, active 1860s–1870s",,Walery Frères,French,1860,1879,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/685405,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.227,false,true,685568,Photographs,Carte-de-visite,[Unknown Subject],,,,,,Photography Studio,,Walery Frères,"French, active 1860s–1870s",,Walery Frères,French,1860,1879,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/685568,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.200,false,true,285700,Photographs,Photograph,[Countess de Castiglione],,,,,,Person in Photograph|Artist,,Countess Virginia Oldoini Verasis di Castiglione|Pierre-Louis Pierson,"1835–1899|French, 1822–1913",,"Castiglione, di, Virginia Oldoini Verasis Countess|Pierson, Pierre-Louis",French,1835 |1822,1899 |1913,1858,1858,1858,Albumen silver print from glass negative,Image: 27.8 x 20.9 cm (10 15/16 x 8 1/4 in.) Mount: 29 x 22 cm (11 7/16 x 8 11/16 in.) Mount (2nd): 45 x 35.3 cm (17 11/16 x 13 7/8 in.) Mat: 50.8 x 40.6 cm (20 x 16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285700,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.392,false,true,285701,Photographs,Photograph,[Countess de Castiglione as Elvira at the Cheval Glass],,,,,,Person in Photograph|Artist,,Countess Virginia Oldoini Verasis di Castiglione|Pierre-Louis Pierson,"1835–1899|French, 1822–1913",,"Castiglione, di, Virginia Oldoini Verasis Countess|Pierson, Pierre-Louis",French,1835 |1822,1899 |1913,1861–67,1861,1867,Salted paper print from glass negative,Image: 14.5 x 15.4 cm (5 11/16 x 6 1/16 in.) Mount: 17.1 x 17.3 cm (6 3/4 x 6 13/16 in.) Mat: 43.2 x 35.6 cm (17 x 14 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285701,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.405,false,true,286837,Photographs,Photograph,La Comtesse de Castiglione en Reine de la Nuit,,,,,,Person in Photograph|Artist,,Countess Virginia Oldoini Verasis di Castiglione|Pierre-Louis Pierson,"1835–1899|French, 1822–1913",,"Castiglione, di, Virginia Oldoini Verasis Countess|Pierson, Pierre-Louis",French,1835 |1822,1899 |1913,1863–67,1863,1867,Albumen silver print from glass negative,Image: 10.5 x 7.4 cm (4 1/8 x 2 15/16 in.) Mount: 12.2 x 9 cm (4 13/16 x 3 9/16 in.) Mat: 35.6 x 27.9 cm (14 x 11 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286837,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.423,false,true,286787,Photographs,Photograph,[Trying for Snapshots],,,,,,Person in Photograph|Artist,,Countess Virginia Oldoini Verasis di Castiglione|Pierre-Louis Pierson,"1835–1899|French, 1822–1913",,"Castiglione, di, Virginia Oldoini Verasis Countess|Pierson, Pierre-Louis",French,1835 |1822,1899 |1913,1861–67,1861,1867,Albumen silver print from glass negative,Image: 9.6 x 7.7 cm (3 3/4 x 3 1/16 in.) Mount: 12.1 x 8.9 cm (4 3/4 x 3 1/2 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286787,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.387.1,false,true,288106,Photographs,Photograph,"[Countess de Castiglione, from Série des Roses]",,,,,,Person in Photograph|Artist,,Countess Virginia Oldoini Verasis di Castiglione|Pierre-Louis Pierson,"1835–1899|French, 1822–1913",,"Castiglione, di, Virginia Oldoini Verasis Countess|Pierson, Pierre-Louis",French,1835 |1822,1899 |1913,1895,1895,1895,Albumen silver print from glass negative,Approximately 14.3 x 9.9 cm (5 5/8 x 3 7/8 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288106,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.387.2,false,true,288107,Photographs,Photograph,"[Countess de Castiglione, from Série des Roses]",,,,,,Person in Photograph|Artist,,Countess Virginia Oldoini Verasis di Castiglione|Pierre-Louis Pierson,"1835–1899|French, 1822–1913",,"Castiglione, di, Virginia Oldoini Verasis Countess|Pierson, Pierre-Louis",French,1835 |1822,1899 |1913,1895,1895,1895,Albumen silver print from glass negative,Approximately 14.3 x 9.9 cm (5 5/8 x 3 7/8 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288107,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.387.3,false,true,288108,Photographs,Photograph,"[Countess de Castiglione, from Série des Roses]",,,,,,Person in Photograph|Artist,,Countess Virginia Oldoini Verasis di Castiglione|Pierre-Louis Pierson,"1835–1899|French, 1822–1913",,"Castiglione, di, Virginia Oldoini Verasis Countess|Pierson, Pierre-Louis",French,1835 |1822,1899 |1913,1895,1895,1895,Albumen silver print from glass negative,Approximately 14.3 x 9.9 cm (5 5/8 x 3 7/8 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288108,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.387.4,false,true,288109,Photographs,Photograph,"[Countess de Castiglione, from Série des Roses]",,,,,,Person in Photograph|Artist,,Countess Virginia Oldoini Verasis di Castiglione|Pierre-Louis Pierson,"1835–1899|French, 1822–1913",,"Castiglione, di, Virginia Oldoini Verasis Countess|Pierson, Pierre-Louis",French,1835 |1822,1899 |1913,1895,1895,1895,Albumen silver print from glass negative,Approximately 14.3 x 9.9 cm (5 5/8 x 3 7/8 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288109,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.387.6,false,true,288111,Photographs,Photograph,"[Countess de Castiglione, from Série des Roses]",,,,,,Person in Photograph|Artist,,Countess Virginia Oldoini Verasis di Castiglione|Pierre-Louis Pierson,"1835–1899|French, 1822–1913",,"Castiglione, di, Virginia Oldoini Verasis Countess|Pierson, Pierre-Louis",French,1835 |1822,1899 |1913,1895,1895,1895,Albumen silver print from glass negative,Approximately 14.3 x 9.9 cm (5 5/8 x 3 7/8 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288111,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.387.7,false,true,288112,Photographs,Photograph,"[Countess de Castiglione, from Série des Roses]",,,,,,Person in Photograph|Artist,,Countess Virginia Oldoini Verasis di Castiglione|Pierre-Louis Pierson,"1835–1899|French, 1822–1913",,"Castiglione, di, Virginia Oldoini Verasis Countess|Pierson, Pierre-Louis",French,1835 |1822,1899 |1913,1895,1895,1895,Albumen silver print from glass negative,Approximately 14.3 x 9.9 cm (5 5/8 x 3 7/8 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288112,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.387.8,false,true,288113,Photographs,Photograph,"[Countess de Castiglione, from Série des Roses]",,,,,,Person in Photograph|Artist,,Countess Virginia Oldoini Verasis di Castiglione|Pierre-Louis Pierson,"1835–1899|French, 1822–1913",,"Castiglione, di, Virginia Oldoini Verasis Countess|Pierson, Pierre-Louis",French,1835 |1822,1899 |1913,1895,1895,1895,Albumen silver print from glass negative,Approximately 14.3 x 9.9 cm (5 5/8 x 3 7/8 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288113,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.387.9,false,true,288114,Photographs,Photograph,"[Countess de Castiglione, from Série des Roses]",,,,,,Person in Photograph|Artist,,Countess Virginia Oldoini Verasis di Castiglione|Pierre-Louis Pierson,"1835–1899|French, 1822–1913",,"Castiglione, di, Virginia Oldoini Verasis Countess|Pierson, Pierre-Louis",French,1835 |1822,1899 |1913,1895,1895,1895,Albumen silver print from glass negative,Approximately 14.3 x 9.9 cm (5 5/8 x 3 7/8 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288114,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.388.1,false,true,288124,Photographs,Photograph,[Countess de Castiglione],,,,,,Person in Photograph|Artist,,Countess Virginia Oldoini Verasis di Castiglione|Pierre-Louis Pierson,"1835–1899|French, 1822–1913",,"Castiglione, di, Virginia Oldoini Verasis Countess|Pierson, Pierre-Louis",French,1835 |1822,1899 |1913,1895,1895,1895,Albumen silver print from glass negative,Approximately 14.3 x 9.9 cm (5 5/8 x 3 7/8 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288124,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.388.2,false,true,288125,Photographs,Photograph,[Countess de Castiglione],,,,,,Person in Photograph|Artist,,Countess Virginia Oldoini Verasis di Castiglione|Pierre-Louis Pierson,"1835–1899|French, 1822–1913",,"Castiglione, di, Virginia Oldoini Verasis Countess|Pierson, Pierre-Louis",French,1835 |1822,1899 |1913,1895,1895,1895,Albumen silver print from glass negative,Approximately 14.3 x 9.9 cm (5 5/8 x 3 7/8 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288125,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.388.4,false,true,288127,Photographs,Photograph,[Countess de Castiglione],,,,,,Person in Photograph|Artist,,Countess Virginia Oldoini Verasis di Castiglione|Pierre-Louis Pierson,"1835–1899|French, 1822–1913",,"Castiglione, di, Virginia Oldoini Verasis Countess|Pierson, Pierre-Louis",French,1835 |1822,1899 |1913,1895,1895,1895,Albumen silver print from glass negative,Approximately 14.3 x 9.9 cm (5 5/8 x 3 7/8 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288127,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.388.5,false,true,288128,Photographs,Photograph,[Countess de Castiglione],,,,,,Person in Photograph|Artist,,Countess Virginia Oldoini Verasis di Castiglione|Pierre-Louis Pierson,"1835–1899|French, 1822–1913",,"Castiglione, di, Virginia Oldoini Verasis Countess|Pierson, Pierre-Louis",French,1835 |1822,1899 |1913,1895,1895,1895,Albumen silver print from glass negative,Approximately 14.3 x 9.9 cm (5 5/8 x 3 7/8 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288128,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.388.6,false,true,288129,Photographs,Photograph,[Countess de Castiglione],,,,,,Person in Photograph|Artist,,Countess Virginia Oldoini Verasis di Castiglione|Pierre-Louis Pierson,"1835–1899|French, 1822–1913",,"Castiglione, di, Virginia Oldoini Verasis Countess|Pierson, Pierre-Louis",French,1835 |1822,1899 |1913,1895,1895,1895,Albumen silver print from glass negative,Approximately 14.3 x 9.9 cm (5 5/8 x 3 7/8 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288129,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.388.7,false,true,288130,Photographs,Photograph,[Countess de Castiglione],,,,,,Person in Photograph|Artist,,Countess Virginia Oldoini Verasis di Castiglione|Pierre-Louis Pierson,"1835–1899|French, 1822–1913",,"Castiglione, di, Virginia Oldoini Verasis Countess|Pierson, Pierre-Louis",French,1835 |1822,1899 |1913,1895,1895,1895,Albumen silver print from glass negative,Approximately 14.3 x 9.9 cm (5 5/8 x 3 7/8 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288130,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.388.8,false,true,288131,Photographs,Photograph,[Countess de Castiglione],,,,,,Person in Photograph|Artist,,Countess Virginia Oldoini Verasis di Castiglione|Pierre-Louis Pierson,"1835–1899|French, 1822–1913",,"Castiglione, di, Virginia Oldoini Verasis Countess|Pierson, Pierre-Louis",French,1835 |1822,1899 |1913,1895,1895,1895,Albumen silver print from glass negative,Approximately 14.3 x 9.9 cm (5 5/8 x 3 7/8 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288131,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.388.9,false,true,288132,Photographs,Photograph,[Countess de Castiglione],,,,,,Person in Photograph|Artist,,Countess Virginia Oldoini Verasis di Castiglione|Pierre-Louis Pierson,"1835–1899|French, 1822–1913",,"Castiglione, di, Virginia Oldoini Verasis Countess|Pierson, Pierre-Louis",French,1835 |1822,1899 |1913,1895,1895,1895,Albumen silver print from glass negative,Approximately 14.3 x 9.9 cm (5 5/8 x 3 7/8 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288132,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.424.2,false,true,286770,Photographs,Photograph,Ti-fille Brune,,,,,,Person in Photograph|Artist,,Countess Virginia Oldoini Verasis di Castiglione|Pierre-Louis Pierson,"1835–1899|French, 1822–1913",,"Castiglione, di, Virginia Oldoini Verasis Countess|Pierson, Pierre-Louis",French,1835 |1822,1899 |1913,1895,1895,1895,Albumen silver print from glass negative,Image: 14.3 x 9.8 cm (5 5/8 x 3 7/8 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286770,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.387.10,false,true,288115,Photographs,Photograph,"[Countess de Castiglione, from Série des Roses]",,,,,,Person in Photograph|Artist,,Countess Virginia Oldoini Verasis di Castiglione|Pierre-Louis Pierson,"1835–1899|French, 1822–1913",,"Castiglione, di, Virginia Oldoini Verasis Countess|Pierson, Pierre-Louis",French,1835 |1822,1899 |1913,1895,1895,1895,Albumen silver print from glass negative,Approximately 14.3 x 9.9 cm (5 5/8 x 3 7/8 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288115,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.388.10,false,true,288133,Photographs,Photograph,[Countess de Castiglione],,,,,,Person in Photograph|Artist,,Countess Virginia Oldoini Verasis di Castiglione|Pierre-Louis Pierson,"1835–1899|French, 1822–1913",,"Castiglione, di, Virginia Oldoini Verasis Countess|Pierson, Pierre-Louis",French,1835 |1822,1899 |1913,1895,1895,1895,Albumen silver print from glass negative,Approximately 14.3 x 9.9 cm (5 5/8 x 3 7/8 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288133,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.388.11,false,true,288219,Photographs,Photograph,[Countess de Castiglione],,,,,,Person in Photograph|Artist,,Countess Virginia Oldoini Verasis di Castiglione|Pierre-Louis Pierson,"1835–1899|French, 1822–1913",,"Castiglione, di, Virginia Oldoini Verasis Countess|Pierson, Pierre-Louis",French,1835 |1822,1899 |1913,1895,1895,1895,Albumen silver print from glass negative,Approximately 14.3 x 9.9 cm (5 5/8 x 3 7/8 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288219,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.388.12,false,true,288220,Photographs,Photograph,[Countess de Castiglione],,,,,,Person in Photograph|Artist,,Countess Virginia Oldoini Verasis di Castiglione|Pierre-Louis Pierson,"1835–1899|French, 1822–1913",,"Castiglione, di, Virginia Oldoini Verasis Countess|Pierson, Pierre-Louis",French,1835 |1822,1899 |1913,1895,1895,1895,Albumen silver print from glass negative,Approximately 14.3 x 9.9 cm (5 5/8 x 3 7/8 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288220,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2005.100.424.1, .3",false,true,286564,Photographs,Photograph,Ti-fille Brune,,,,,,Person in Photograph|Artist,,Countess Virginia Oldoini Verasis di Castiglione|Pierre-Louis Pierson,"1835–1899|French, 1822–1913",,"Castiglione, di, Virginia Oldoini Verasis Countess|Pierson, Pierre-Louis",French,1835 |1822,1899 |1913,1895,1895,1895,Albumen silver print from glass negative,Image: 14.3 x 9.8 cm (5 5/8 x 3 7/8 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286564,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.258–.262,false,true,289066,Photographs,Photograph,[Album page with ten photographs of La Comtesse mounted recto and verso],,,,,,Person in Photograph|Artist,,Countess Virginia Oldoini Verasis di Castiglione|Pierre-Louis Pierson,"1835–1899|French, 1822–1913",,"Castiglione, di, Virginia Oldoini Verasis Countess|Pierson, Pierre-Louis",French,1835 |1822,1899 |1913,1861–67,1861,1867,Albumen silver prints from glass negative,10.8 x 8.6 cm (4 1/4 x 3 3/8 in.) to 2.5 x 3.5 cm (1 x 1 3/8 in.),"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/289066,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -48.188,false,true,269214,Photographs,Photograph,Scherzo di Follia,,,,,,Artist|Person in Photograph,,Pierre-Louis Pierson|Countess Virginia Oldoini Verasis di Castiglione,"French, 1822–1913|1835–1899",,"Pierson, Pierre-Louis|Castiglione, di, Virginia Oldoini Verasis Countess",French,1822 |1835,1913 |1899,"1863–66, printed 1940s",1863,1866,Gelatin silver print from glass negative,18.7 x 12.5 cm. (7 3/8 x 4 15/16 in.),"Gift of George Davis, 1948",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269214,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.66,false,true,261518,Photographs,Carte-de-visite,Convalescente (autre),,,,,,Artist|Person in Photograph,Person in photograph,Pierre-Louis Pierson|Countess Virginia Oldoini Verasis di Castiglione,"French, 1822–1913|1835–1899",,"Pierson, Pierre-Louis|Castiglione, di, Virginia Oldoini Verasis Countess",French,1822 |1835,1913 |1899,1860s,1860,1869,Albumen silver print from glass negative,10.4 x 6.3 cm (4 1/8 x 2 1/2 in.),"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261518,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.68,false,true,261520,Photographs,Carte-de-visite,Convalescente (autre),,,,,,Artist|Person in Photograph,Person in photograph,Pierre-Louis Pierson|Countess Virginia Oldoini Verasis di Castiglione,"French, 1822–1913|1835–1899",,"Pierson, Pierre-Louis|Castiglione, di, Virginia Oldoini Verasis Countess",French,1822 |1835,1913 |1899,1860s,1860,1869,Albumen silver print from glass negative,9.9 x 6.9 cm (3 7/8 x 2 11/16 in.),"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261520,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.183,false,true,261364,Photographs,Photograph,[La Comtesse at Table with Hand to Face],,,,,,Artist|Person in Photograph,,Pierre-Louis Pierson|Countess Virginia Oldoini Verasis di Castiglione,"French, 1822–1913|1835–1899",,"Pierson, Pierre-Louis|Castiglione, di, Virginia Oldoini Verasis Countess",French,1822 |1835,1913 |1899,1860s,1860,1869,Albumen silver print from glass negative,8.9 x 12.1 cm (3 1/2 x 4 3/4 in.),"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261364,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.184,false,true,261365,Photographs,Photograph,[La Comtesse],,,,,,Artist|Person in Photograph,,Pierre-Louis Pierson|Countess Virginia Oldoini Verasis di Castiglione,"French, 1822–1913|1835–1899",,"Pierson, Pierre-Louis|Castiglione, di, Virginia Oldoini Verasis Countess",French,1822 |1835,1913 |1899,1860s,1860,1869,Albumen silver print from glass negative,11.7 x 16.2 cm (4 5/8 x 6 3/8 in.),"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261365,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.189,false,true,261370,Photographs,Photograph,[La Comtesse in robe de piqué or as Judith (?)],,,,,,Artist|Person in Photograph,,Pierre-Louis Pierson|Countess Virginia Oldoini Verasis di Castiglione,"French, 1822–1913|1835–1899",,"Pierson, Pierre-Louis|Castiglione, di, Virginia Oldoini Verasis Countess",French,1822 |1835,1913 |1899,1860s,1860,1869,Albumen silver print from glass negative with applied color,12.4 x 8.8 cm (4 7/8 x 3 7/16 in.),"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261370,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.190,false,true,261372,Photographs,Photograph,[La Comtesse in robe de piqué‚ or as Judith (?)],,,,,,Artist|Person in Photograph,,Pierre-Louis Pierson|Countess Virginia Oldoini Verasis di Castiglione,"French, 1822–1913|1835–1899",,"Pierson, Pierre-Louis|Castiglione, di, Virginia Oldoini Verasis Countess",French,1822 |1835,1913 |1899,1860s,1860,1869,Albumen silver print from glass negative with applied color,12.3 x 8.7 cm (4 13/16 x 3 7/16 in.),"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261372,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.191,false,true,261373,Photographs,Photograph,[La Comtesse in robe de piqué or as Judith (?)],,,,,,Artist|Person in Photograph,,Pierre-Louis Pierson|Countess Virginia Oldoini Verasis di Castiglione,"French, 1822–1913|1835–1899",,"Pierson, Pierre-Louis|Castiglione, di, Virginia Oldoini Verasis Countess",French,1822 |1835,1913 |1899,1860s,1860,1869,Albumen silver print from glass negative with applied color,12.2 x 8.5 cm (4 13/16 x 3 3/8 in.),"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261373,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.192,false,true,261374,Photographs,Photograph,[La Comtesse in robe de piqué or as Judith (?)],,,,,,Artist|Person in Photograph,,Pierre-Louis Pierson|Countess Virginia Oldoini Verasis di Castiglione,"French, 1822–1913|1835–1899",,"Pierson, Pierre-Louis|Castiglione, di, Virginia Oldoini Verasis Countess",French,1822 |1835,1913 |1899,1860s,1860,1869,Albumen silver print from glass negative with applied color,12.4 x 8.7 cm (4 7/8 x 3 7/16 in.),"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261374,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.257,false,true,261454,Photographs,Photograph,[La Comtesse Reclining in Dark Dress with Chain Around Neck],,,,,,Artist|Person in Photograph,,Pierre-Louis Pierson|Countess Virginia Oldoini Verasis di Castiglione,"French, 1822–1913|1835–1899",,"Pierson, Pierre-Louis|Castiglione, di, Virginia Oldoini Verasis Countess",French,1822 |1835,1913 |1899,1861–65,1861,1865,Albumen silver print from glass negative,10.2 x 12.0 cm (4 x 4 3/4 in.),"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261454,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.268,false,true,261466,Photographs,Photograph,[La Comtesse in Ermine Cape],,,,,,Artist|Person in Photograph,,Pierre-Louis Pierson|Countess Virginia Oldoini Verasis di Castiglione,"French, 1822–1913|1835–1899",,"Pierson, Pierre-Louis|Castiglione, di, Virginia Oldoini Verasis Countess",French,1822 |1835,1913 |1899,1860s,1860,1869,Albumen silver print from glass negative,9.2 x 14.9 cm (3 5/8 x 5 7/8 in.),"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261466,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.269,false,true,261467,Photographs,Photograph,"[La Comtesse in Hat with Veil and Cape with Fringe, Serie à la Ristori]",,,,,,Artist|Person in Photograph,,Pierre-Louis Pierson|Countess Virginia Oldoini Verasis di Castiglione,"French, 1822–1913|1835–1899",,"Pierson, Pierre-Louis|Castiglione, di, Virginia Oldoini Verasis Countess",French,1822 |1835,1913 |1899,1860s,1860,1869,Albumen silver print from glass negative,9.5 x 13.7 cm (3 3/4 x 5 3/8 in.),"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261467,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.270,false,true,261469,Photographs,Photograph,[La Comtesse in Cape with Fringe; Serie à la Ristori],,,,,,Artist|Person in Photograph,,Pierre-Louis Pierson|Countess Virginia Oldoini Verasis di Castiglione,"French, 1822–1913|1835–1899",,"Pierson, Pierre-Louis|Castiglione, di, Virginia Oldoini Verasis Countess",French,1822 |1835,1913 |1899,1860s,1860,1869,Albumen silver print from glass negative,8.3 x 13.0 cm (3 1/4 x 5 1/8 in.),"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261469,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.271,false,true,261471,Photographs,Photograph,[La Comtesse with Group on a Rocky Beach],,,,,,Artist|Person in Photograph,,Pierre-Louis Pierson|Countess Virginia Oldoini Verasis di Castiglione,"French, 1822–1913|1835–1899",,"Pierson, Pierre-Louis|Castiglione, di, Virginia Oldoini Verasis Countess",French,1822 |1835,1913 |1899,1860s,1860,1869,Albumen silver print from glass negative,21.3 x 17.8 cm (8 3/8 x 7 in.),"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261471,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.272,false,true,261472,Photographs,Photograph,[La Comtesse décolletée; Roses mousseuses],,,,,,Artist|Person in Photograph,,Pierre-Louis Pierson|Countess Virginia Oldoini Verasis di Castiglione,"French, 1822–1913|1835–1899",,"Pierson, Pierre-Louis|Castiglione, di, Virginia Oldoini Verasis Countess",French,1822 |1835,1913 |1899,1861–67,1861,1867,Salted paper print from glass negative,44.8 x 29.2 cm (17 5/8 x 11 1/2 in.),"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261472,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.273,false,true,261473,Photographs,Photograph,[La Comtesse at Table holding Fan],,,,,,Artist|Person in Photograph,,Pierre-Louis Pierson|Countess Virginia Oldoini Verasis di Castiglione,"French, 1822–1913|1835–1899",,"Pierson, Pierre-Louis|Castiglione, di, Virginia Oldoini Verasis Countess",French,1822 |1835,1913 |1899,1860s,1860,1869,Salted paper print from glass negative,54.9 x 36.2 cm (21 5/8 x 14 1/4 in.),"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261473,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.274,false,true,261474,Photographs,Photograph,[La Comtesse in Lace Shawl],,,,,,Artist|Person in Photograph,,Pierre-Louis Pierson|Countess Virginia Oldoini Verasis di Castiglione,"French, 1822–1913|1835–1899",,"Pierson, Pierre-Louis|Castiglione, di, Virginia Oldoini Verasis Countess",French,1822 |1835,1913 |1899,1860s,1860,1869,Salted paper print from glass negative,54.6 x 36.2 cm (21 1/2 x 14 1/4 in.),"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261474,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.275,false,true,261475,Photographs,Photograph,[La Comtesse at Table with Hand to Face],,,,,,Artist|Person in Photograph,,Pierre-Louis Pierson|Countess Virginia Oldoini Verasis di Castiglione,"French, 1822–1913|1835–1899",,"Pierson, Pierre-Louis|Castiglione, di, Virginia Oldoini Verasis Countess",French,1822 |1835,1913 |1899,1860s,1860,1869,Salted paper print from glass negative,,"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261475,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.119,false,true,283245,Photographs,Photograph,The Gaze,,,,,,Artist|Person in Photograph,,Pierre-Louis Pierson|Countess Virginia Oldoini Verasis di Castiglione,"French, 1822–1913|1835–1899",,"Pierson, Pierre-Louis|Castiglione, di, Virginia Oldoini Verasis Countess",French,1822 |1835,1913 |1899,1856–57,1856,1857,Albumen silver print from glass negative,Image: 9 x 6.6 cm (3 9/16 x 2 5/8 in.) Mount: 12.9 x 9.1 cm (5 1/16 x 3 9/16 in.) Mat: 21.9 x 15.9 cm (8 5/8 x 6 1/4 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/283245,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.194,false,true,285650,Photographs,Photograph,The White Nun,,,,,,Artist|Person in Photograph,,Pierre-Louis Pierson|Countess Virginia Oldoini Verasis di Castiglione,"French, 1822–1913|1835–1899",,"Pierson, Pierre-Louis|Castiglione, di, Virginia Oldoini Verasis Countess",French,1822 |1835,1913 |1899,1856–57,1856,1857,Albumen silver print from glass negative,Image: 19.1 x 13.8 cm (7 1/2 x 5 7/16 in.) Mount: 19.1 x 14.4 cm (7 1/2 x 5 11/16 in.) Mat: 43.2 x 35.6 cm (17 x 14 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285650,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.195,false,true,285614,Photographs,Photograph,Béatrix,,,,,,Artist|Person in Photograph,,Pierre-Louis Pierson|Countess Virginia Oldoini Verasis di Castiglione,"French, 1822–1913|1835–1899",,"Pierson, Pierre-Louis|Castiglione, di, Virginia Oldoini Verasis Countess",French,1822 |1835,1913 |1899,1856–57,1856,1857,Albumen silver print from glass negative,Image: 10.5 x 7 cm (4 1/8 x 2 3/4 in.) Mount: 12.2 x 9.1 cm (4 13/16 x 3 9/16 in.) Mat: 35.6 x 27.9 cm (14 x 11 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285614,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.197,false,true,285652,Photographs,Photograph,[The Opera Ball],,,,,,Artist|Person in Photograph,,Pierre-Louis Pierson|Countess Virginia Oldoini Verasis di Castiglione,"French, 1822–1913|1835–1899",,"Pierson, Pierre-Louis|Castiglione, di, Virginia Oldoini Verasis Countess",French,1822 |1835,1913 |1899,"1861–67, printed 1895–1910",1861,1910,Gelatin silver print from glass negative,Image: 36 x 27.9 cm (14 3/16 x 11 in.) Mat: 57.2 x 47 cm (22 1/2 x 18 1/2 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285652,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.198,false,true,285608,Photographs,Photograph,Scherzo di Follia,,,,,,Artist|Person in Photograph,,Pierre-Louis Pierson|Countess Virginia Oldoini Verasis di Castiglione,"French, 1822–1913|1835–1899",,"Pierson, Pierre-Louis|Castiglione, di, Virginia Oldoini Verasis Countess",French,1822 |1835,1913 |1899,"1861–67, printed ca. 1930",1861,1932,Gelatin silver print from glass negative,Image: 39.8 x 29.8 cm (15 11/16 x 11 3/4 in.) Mat: 61 x 50.8 cm (24 x 20 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285608,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.389,false,true,285662,Photographs,Photograph,Le Manteau d'Hermine,,,,,,Artist|Person in Photograph,,Pierre-Louis Pierson|Countess Virginia Oldoini Verasis di Castiglione,"French, 1822–1913|1835–1899",,"Pierson, Pierre-Louis|Castiglione, di, Virginia Oldoini Verasis Countess",French,1822 |1835,1913 |1899,"August 31, 1895",1895,1895,Albumen silver print from glass negative,Image: 14.4 x 10 cm (5 11/16 x 3 15/16 in.) Mat: 35.6 x 27.9 cm (14 x 11 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285662,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.390,false,true,286841,Photographs,Photograph,Rachel,,,,,,Artist|Person in Photograph,,Pierre-Louis Pierson|Countess Virginia Oldoini Verasis di Castiglione,"French, 1822–1913|1835–1899",,"Pierson, Pierre-Louis|Castiglione, di, Virginia Oldoini Verasis Countess",French,1822 |1835,1913 |1899,"September 1, 1893",1893,1893,Albumen silver print from glass negative,Image: 14.6 x 9.8 cm (5 3/4 x 3 7/8 in.) Mat: 35.6 x 27.9 cm (14 x 11 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286841,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.393,false,true,286827,Photographs,Cabinet card,Sculptural Shoulders,,,,,,Artist|Person in Photograph,,Pierre-Louis Pierson|Countess Virginia Oldoini Verasis di Castiglione,"French, 1822–1913|1835–1899",,"Pierson, Pierre-Louis|Castiglione, di, Virginia Oldoini Verasis Countess",French,1822 |1835,1913 |1899,1861–67,1861,1867,Albumen silver print from glass negative,Image: 12 x 8.5 cm (4 3/4 x 3 3/8 in.) Mount: 15.4 x 9.6 cm (6 1/16 x 3 3/4 in.) Mat: 35.6 x 27.9 cm (14 x 11 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286827,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.394,false,true,286828,Photographs,Carte-de-visite,La Dogaresse,,,,,,Artist|Person in Photograph,,Pierre-Louis Pierson|Countess Virginia Oldoini Verasis di Castiglione,"French, 1822–1913|1835–1899",,"Pierson, Pierre-Louis|Castiglione, di, Virginia Oldoini Verasis Countess",French,1822 |1835,1913 |1899,1861–67,1861,1867,Albumen silver print from glass negative,Image: 8.9 x 5.4 cm (3 1/2 x 2 1/8 in.) Mount: 10.7 x 6.2 cm (4 3/16 x 2 7/16 in.) Mat: 25.4 x 20.3 cm (10 x 8 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286828,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.403,false,true,286835,Photographs,Photograph,La Marquise Mathilde,,,,,,Artist|Person in Photograph,,Pierre-Louis Pierson|Countess Virginia Oldoini Verasis di Castiglione,"French, 1822–1913|1835–1899",,"Pierson, Pierre-Louis|Castiglione, di, Virginia Oldoini Verasis Countess",French,1822 |1835,1913 |1899,1861–66,1861,1866,Albumen silver print from glass negative,Image: 9.9 x 7.5 cm (3 7/8 x 2 15/16 in.) Mount: 12.2 x 8.9 cm (4 13/16 x 3 1/2 in.) Mat: 35.6 x 27.9 cm (14 x 11 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286835,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.404,false,true,286836,Photographs,Photograph,La Dame de Cœurs,,,,,,Artist|Person in Photograph,,Pierre-Louis Pierson|Countess Virginia Oldoini Verasis di Castiglione,"French, 1822–1913|1835–1899",,"Pierson, Pierre-Louis|Castiglione, di, Virginia Oldoini Verasis Countess",French,1822 |1835,1913 |1899,1861–63,1861,1863,Albumen silver print from glass negative,Image: 10.5 x 7.4 cm (4 1/8 x 2 15/16 in.) Mount: 12.3 x 9.1 cm (4 13/16 x 3 9/16 in.) Mat: 35.6 x 27.9 cm (14 x 11 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286836,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.411,false,true,286785,Photographs,Photograph,[Countess de Castiglione],,,,,,Artist|Person in Photograph,,Pierre-Louis Pierson|Countess Virginia Oldoini Verasis di Castiglione,"French, 1822–1913|1835–1899",,"Pierson, Pierre-Louis|Castiglione, di, Virginia Oldoini Verasis Countess",French,1822 |1835,1913 |1899,"August 31, 1895",1895,1895,Albumen silver print from glass negative,Image: 14.3 x 10.1 cm (5 5/8 x 4 in.) Mat: 59.8 x 49.8 cm (23 9/16 x 19 5/8 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286785,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.412,false,true,286843,Photographs,Photograph,[Countess de Castiglione],,,,,,Artist|Person in Photograph,,Pierre-Louis Pierson|Countess Virginia Oldoini Verasis di Castiglione,"French, 1822–1913|1835–1899",,"Pierson, Pierre-Louis|Castiglione, di, Virginia Oldoini Verasis Countess",French,1822 |1835,1913 |1899,"August 31, 1895",1895,1895,Albumen silver print from glass negative,Image: 14.3 x 10.1 cm (5 5/8 x 4 in.) Mat: 59.8 x 49.8 cm (23 9/16 x 19 5/8 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286843,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.413,false,true,286844,Photographs,Photograph,L'Armoire,,,,,,Artist|Person in Photograph,,Pierre-Louis Pierson|Countess Virginia Oldoini Verasis di Castiglione,"French, 1822–1913|1835–1899",,"Pierson, Pierre-Louis|Castiglione, di, Virginia Oldoini Verasis Countess",French,1822 |1835,1913 |1899,"September 1, 1893",1893,1893,Albumen silver print from glass negative,Image: 14.8 x 9.8 cm (5 13/16 x 3 7/8 in.) Mat: 59.8 x 49.8 cm (23 9/16 x 19 5/8 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286844,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.414,false,true,286845,Photographs,Photograph,Ristori,,,,,,Artist|Person in Photograph,,Pierre-Louis Pierson|Countess Virginia Oldoini Verasis di Castiglione,"French, 1822–1913|1835–1899",,"Pierson, Pierre-Louis|Castiglione, di, Virginia Oldoini Verasis Countess",French,1822 |1835,1913 |1899,"September 1, 1893",1893,1893,Albumen silver print from glass negative,Image: 14.3 x 9.8 cm (5 5/8 x 3 7/8 in.) Mat: 59.8 x 49.8 cm (23 9/16 x 19 5/8 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286845,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.415,false,true,286846,Photographs,Photograph,Madame Douane,,,,,,Artist|Person in Photograph,,Pierre-Louis Pierson|Countess Virginia Oldoini Verasis di Castiglione,"French, 1822–1913|1835–1899",,"Pierson, Pierre-Louis|Castiglione, di, Virginia Oldoini Verasis Countess",French,1822 |1835,1913 |1899,"September 1, 1893",1893,1893,Albumen silver print from glass negative,Image: 14.4 x 10.1 cm (5 11/16 x 4 in.) Mat: 59.8 x 49.8 cm (23 9/16 x 19 5/8 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286846,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.416,false,true,286847,Photographs,Photograph,Torino Aosta,,,,,,Artist|Person in Photograph,,Pierre-Louis Pierson|Countess Virginia Oldoini Verasis di Castiglione,"French, 1822–1913|1835–1899",,"Pierson, Pierre-Louis|Castiglione, di, Virginia Oldoini Verasis Countess",French,1822 |1835,1913 |1899,"September 1, 1893",1893,1893,Albumen silver print from glass negative,Image: 14.9 x 9.9 cm (5 7/8 x 3 7/8 in.) Mat: 59.8 x 49.8 cm (23 9/16 x 19 5/8 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286847,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.418,false,true,286822,Photographs,Photograph,[Standing at the Prie-Dieu],,,,,,Artist|Person in Photograph,,Pierre-Louis Pierson|Countess Virginia Oldoini Verasis di Castiglione,"French, 1822–1913|1835–1899",,"Pierson, Pierre-Louis|Castiglione, di, Virginia Oldoini Verasis Countess",French,1822 |1835,1913 |1899,1861–64,1861,1864,Albumen silver print from glass negative,Image: 10.5 x 8.5 cm (4 1/8 x 3 3/8 in.) Mount: 10.5 x 8.5 cm (4 1/8 x 3 3/8 in.) Mat: 35.6 x 27.9 cm (14 x 11 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286822,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.419,false,true,286823,Photographs,Photograph,Anne Boleyn,,,,,,Artist|Person in Photograph,,Pierre-Louis Pierson|Countess Virginia Oldoini Verasis di Castiglione,"French, 1822–1913|1835–1899",,"Pierson, Pierre-Louis|Castiglione, di, Virginia Oldoini Verasis Countess",French,1822 |1835,1913 |1899,1861–65,1861,1865,Albumen silver print from glass negative,Image: 13.2 x 10.1 cm (5 3/16 x 4 in.) Mount: 14.3 x 10.6 cm (5 5/8 x 4 3/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286823,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.420,false,true,286824,Photographs,Cabinet card,[Standing with a Rosary],,,,,,Artist|Person in Photograph,,Pierre-Louis Pierson|Countess Virginia Oldoini Verasis di Castiglione,"French, 1822–1913|1835–1899",,"Pierson, Pierre-Louis|Castiglione, di, Virginia Oldoini Verasis Countess",French,1822 |1835,1913 |1899,1861–67,1861,1867,Albumen silver print from glass negative,Image: 11.7 x 8.5 cm (4 5/8 x 3 3/8 in.) Mount: 15.5 x 10 cm (6 1/8 x 3 15/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286824,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.421,false,true,286825,Photographs,Photograph,La Reine d'Étrurie,,,,,,Artist|Person in Photograph,,Pierre-Louis Pierson|Countess Virginia Oldoini Verasis di Castiglione,"French, 1822–1913|1835–1899",,"Pierson, Pierre-Louis|Castiglione, di, Virginia Oldoini Verasis Countess",French,1822 |1835,1913 |1899,1863–67,1863,1867,Albumen silver print from glass negative,Image: 11.3 x 9.4 cm (4 7/16 x 3 11/16 in.) Mount: 26.8 x 21 cm (10 9/16 x 8 1/4 in.) Mat: 35.6 x 27.9 cm (14 x 11 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286825,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.422,false,true,286826,Photographs,Photograph,"[Profile with Chignon, Large]",,,,,,Artist|Person in Photograph,,Pierre-Louis Pierson|Countess Virginia Oldoini Verasis di Castiglione,"French, 1822–1913|1835–1899",,"Pierson, Pierre-Louis|Castiglione, di, Virginia Oldoini Verasis Countess",French,1822 |1835,1913 |1899,1859,1859,1859,Albumen silver print from glass negative,Image: 26.3 x 21.2 cm (10 3/8 x 8 3/8 in.) Mount: 30.9 x 23.8 cm (12 3/16 x 9 3/8 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286826,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.426,false,true,285653,Photographs,Photograph,La Mà,,,,,,Artist|Person in Photograph,,Pierre-Louis Pierson|Countess Virginia Oldoini Verasis di Castiglione,"French, 1822–1913|1835–1899",,"Pierson, Pierre-Louis|Castiglione, di, Virginia Oldoini Verasis Countess",French,1822 |1835,1913 |1899,"August 21, 1895",1895,1895,Albumen silver print from glass negative,Image: 14.1 x 10.1 cm (5 9/16 x 4 in.) Mount: 14.1 x 10.1 cm (5 9/16 x 4 in.) Mat: 35.6 x 27.9 cm (14 x 11 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285653,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.427,false,true,286848,Photographs,Photograph,Baisemain,,,,,,Artist|Person in Photograph,,Pierre-Louis Pierson|Countess Virginia Oldoini Verasis di Castiglione,"French, 1822–1913|1835–1899",,"Pierson, Pierre-Louis|Castiglione, di, Virginia Oldoini Verasis Countess",French,1822 |1835,1913 |1899,"September 1, 1893",1893,1893,Albumen silver print from glass negative,Image: 14 x 9.9 cm (5 1/2 x 3 7/8 in.) Mount: 14 x 9.9 cm (5 1/2 x 3 7/8 in.) Mat: 35.6 x 27.9 cm (14 x 11 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286848,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.428,false,true,286849,Photographs,Photograph,Arrivo,,,,,,Artist|Person in Photograph,,Pierre-Louis Pierson|Countess Virginia Oldoini Verasis di Castiglione,"French, 1822–1913|1835–1899",,"Pierson, Pierre-Louis|Castiglione, di, Virginia Oldoini Verasis Countess",French,1822 |1835,1913 |1899,"September 1, 1893",1893,1893,Albumen silver print from glass negative,Image: 14.7 x 9.8 cm (5 13/16 x 3 7/8 in.) Mount: 14.7 x 9.8 cm (5 13/16 x 3 7/8 in.) Mat: 35.6 x 27.9 cm (14 x 11 in.) Frame: 37.3 x 30 cm (14 11/16 x 11 13/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286849,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.429,false,true,286850,Photographs,Photograph,[Countess de Castiglione],,,,,,Artist|Person in Photograph,,Pierre-Louis Pierson|Countess Virginia Oldoini Verasis di Castiglione,"French, 1822–1913|1835–1899",,"Pierson, Pierre-Louis|Castiglione, di, Virginia Oldoini Verasis Countess",French,1822 |1835,1913 |1899,1895,1895,1895,Albumen silver print from glass negative,Image: 15.1 x 10.1 cm (5 15/16 x 4 in.) Mount: 15.2 x 10.2 cm (6 x 4 in.) Mat: 35.6 x 27.9 cm (14 x 11 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286850,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.430,false,true,286851,Photographs,Photograph,Au Bureau,,,,,,Artist|Person in Photograph,,Pierre-Louis Pierson|Countess Virginia Oldoini Verasis di Castiglione,"French, 1822–1913|1835–1899",,"Pierson, Pierre-Louis|Castiglione, di, Virginia Oldoini Verasis Countess",French,1822 |1835,1913 |1899,"September 1, 1893",1893,1893,Albumen silver print from glass negative,Image: 13.7 x 9.9 cm (5 3/8 x 3 7/8 in.) Mount: 13.7 x 10 cm (5 3/8 x 3 15/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286851,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.431,false,true,286852,Photographs,Photograph,À la Barre,,,,,,Artist|Person in Photograph,,Pierre-Louis Pierson|Countess Virginia Oldoini Verasis di Castiglione,"French, 1822–1913|1835–1899",,"Pierson, Pierre-Louis|Castiglione, di, Virginia Oldoini Verasis Countess",French,1822 |1835,1913 |1899,"September 1, 1893",1893,1893,Albumen silver print from glass negative,Image: 13.8 x 10 cm (5 7/16 x 3 15/16 in.) Mount: 13.8 x 10 cm (5 7/16 x 3 15/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286852,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.432,false,true,286853,Photographs,Photograph,Roses Compiègne,,,,,,Artist|Person in Photograph,,Pierre-Louis Pierson|Countess Virginia Oldoini Verasis di Castiglione,"French, 1822–1913|1835–1899",,"Pierson, Pierre-Louis|Castiglione, di, Virginia Oldoini Verasis Countess",French,1822 |1835,1913 |1899,1895,1895,1895,Albumen silver print from glass negative,Image: 14.9 x 10.1 cm (5 7/8 x 4 in.) Mount: 15 x 10.1 cm (5 7/8 x 4 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286853,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.433,false,true,286854,Photographs,Photograph,Coin Noir de la Colonne,,,,,,Artist|Person in Photograph,,Pierre-Louis Pierson|Countess Virginia Oldoini Verasis di Castiglione,"French, 1822–1913|1835–1899",,"Pierson, Pierre-Louis|Castiglione, di, Virginia Oldoini Verasis Countess",French,1822 |1835,1913 |1899,"September 1, 1893",1893,1893,Albumen silver print from glass negative,Image: 9.8 x 14.2 cm (3 7/8 x 5 9/16 in.) Mount: 9.9 x 14.2 cm (3 7/8 x 5 9/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286854,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.387.5,false,true,288110,Photographs,Photograph,"[Countess de Castiglione, from Série des Roses]",,,,,,Artist|Person in Photograph,,Pierre-Louis Pierson|Countess Virginia Oldoini Verasis di Castiglione,"French, 1822–1913|1835–1899",,"Pierson, Pierre-Louis|Castiglione, di, Virginia Oldoini Verasis Countess",French,1822 |1835,1913 |1899,1895,1895,1895,Albumen silver print from glass negative,Approximately 14.3 x 9.9 cm (5 5/8 x 3 7/8 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288110,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.388.3,false,true,288126,Photographs,Photograph,[Countess de Castiglione],,,,,,Artist|Person in Photograph,,Pierre-Louis Pierson|Countess Virginia Oldoini Verasis di Castiglione,"French, 1822–1913|1835–1899",,"Pierson, Pierre-Louis|Castiglione, di, Virginia Oldoini Verasis Countess",French,1822 |1835,1913 |1899,1895,1895,1895,Albumen silver print from glass negative,Approximately 14.3 x 9.9 cm (5 5/8 x 3 7/8 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288126,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.410 (3a),false,true,288157,Photographs,Photograph,[Countess de Castiglione as the Queen of Etruria],,,,,,Artist|Artist|Person in Photograph,Painted and retouched by|Person in photograph,Pierre-Louis Pierson|Marck|Countess Virginia Oldoini Verasis di Castiglione,"French, 1822–1913|1835–1899",", et al","Pierson, Pierre-Louis|Marck|Castiglione, di, Virginia Oldoini Verasis Countess",French,1822 |1835,1913 |1899,before 1865,1855,1865,Albumen silver print from glass negative,Window: 7 1/2 × 5 1/2 in. (19 × 14 cm) Image: 4 in. × 2 13/16 in. (10.2 × 7.2 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288157,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.410 (3b),false,true,288158,Photographs,Photograph,[Countess de Castiglione as Anne Boleyn],,,,,,Artist|Artist|Person in Photograph,Painted and retouched by|Person in photograph,Pierre-Louis Pierson|Marck|Countess Virginia Oldoini Verasis di Castiglione,"French, 1822–1913|1835–1899",", et al","Pierson, Pierre-Louis|Marck|Castiglione, di, Virginia Oldoini Verasis Countess",French,1822 |1835,1913 |1899,before 1865,1855,1865,Albumen silver print from glass negative,Window: 7 1/2 × 5 1/2 in. (19 × 14 cm) Image: 4 in. × 2 13/16 in. (10.2 × 7.2 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288158,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.263–.267,false,true,684336,Photographs,Photograph,[Album page with ten photographs of La Comtesse mounted recto and verso],,,,,,Artist|Person in Photograph,,Pierre-Louis Pierson|Countess Virginia Oldoini Verasis di Castiglione,"French, 1822–1913|1835–1899",,"Pierson, Pierre-Louis|Castiglione, di, Virginia Oldoini Verasis Countess",French,1822 |1835,1913 |1899,1861–67,1861,1867,Albumen silver prints from glass negative,10.8 x 8.6 cm (4 1/4 x 3 3/8 in.) to 2.5 x 3.5 cm (1 x 1 3/8 in.),"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/684336,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.391.1–.2,false,true,286842,Photographs,Photograph,Le Pé,,,,,,Artist|Person in Photograph,,Pierre-Louis Pierson|Countess Virginia Oldoini Verasis di Castiglione,"French, 1822–1913|1835–1899",,"Pierson, Pierre-Louis|Castiglione, di, Virginia Oldoini Verasis Countess",French,1822 |1835,1913 |1899,"August 1, 1894",1894,1894,Albumen silver print from glass negative,Image: 14.4 x 10 cm (5 11/16 x 3 15/16 in.) Mat: 35.6 x 27.9 cm (14 x 11 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286842,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.71,false,true,287478,Photographs,Photograph,[Empress Eugénie's Poodle],,,,,,Photography Studio,,André-Adolphe-Eugène Disdéri,"French, Paris 1819–1889 Paris",,"Disdéri, André-Adolphe-Eugène",French,1819,1889,1850s,1850,1859,Salted paper print from collodion glass negative,Image: 20.7 x 20.8 cm (8 1/8 x 8 3/16 in.) Sheet: 22.2 x 29 cm (8 3/4 x 11 7/16 in.),"Gilman Collection, Purchase, The Howard Gilman Foundation Gift, 2007",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287478,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -46.122.20,false,true,269069,Photographs,Photograph,Ruines gothiques,,,,,,Artist|Printer,,"A. Fays|Imprimerie photographique de Blanquart-Évrard, à Lille","French|French, active 1851–55",,"Fays, A.|Imprimerie photographique de Blanquart-Évrard, à Lille",French,1851,1855,1850–53,1850,1853,Salted paper print (Blanquart-Évrard process) from paper negative,,"Harris Brisbane Dick Fund, 1946",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269069,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.395,false,true,285479,Photographs,Photograph,Ermitage de Passy,,,,,,Artist|Artist|Person in Photograph,Painted and retouched by,Pierre-Louis Pierson|Unknown|Countess Virginia Oldoini Verasis di Castiglione,"French, 1822–1913|French|1835–1899",,"Pierson, Pierre-Louis|Unknown|Castiglione, di, Virginia Oldoini Verasis Countess",French,1822 |1835,1913 |1899,1863,1863,1863,Albumen silver print from glass negative,Image: 12 x 8.5 cm (4 3/4 x 3 3/8 in.) Mount: 13 x 9.6 cm (5 1/8 x 3 3/4 in.) Mat: 35.6 x 27.9 cm (14 x 11 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285479,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.396,false,true,285649,Photographs,Photograph,The Red Bow,,,,,,Artist|Artist|Person in Photograph,Painted and retouched by,Pierre-Louis Pierson|Unknown|Countess Virginia Oldoini Verasis di Castiglione,"French, 1822–1913|French|1835–1899",,"Pierson, Pierre-Louis|Unknown|Castiglione, di, Virginia Oldoini Verasis Countess",French,1822 |1835,1913 |1899,1861–67,1861,1867,Albumen silver print from glass negative,Image: 18.7 x 13.6 cm (7 3/8 x 5 3/8 in.) Mount: 32.3 x 23.8 cm (12 11/16 x 9 3/8 in.) Mat: 50.8 x 40.6 cm (20 x 16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285649,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.397,false,true,286829,Photographs,Photograph,Les Cothurnes,,,,,,Artist|Artist|Person in Photograph,Painted and retouched by,Pierre-Louis Pierson|Unknown|Countess Virginia Oldoini Verasis di Castiglione,"French, 1822–1913|French|1835–1899",,"Pierson, Pierre-Louis|Unknown|Castiglione, di, Virginia Oldoini Verasis Countess",French,1822 |1835,1913 |1899,1861–67,1861,1867,Albumen silver print from glass negative,Image: 5.4 x 7.5 cm (2 1/8 x 2 15/16 in.) Mount: 6.8 x 8.6 cm (2 11/16 x 3 3/8 in.) Mat: 35.6 x 27.9 cm (14 x 11 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286829,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.398,false,true,286830,Photographs,Photograph,Les Cothurnes,,,,,,Artist|Artist|Person in Photograph,Painted and retouched by,Pierre-Louis Pierson|Unknown|Countess Virginia Oldoini Verasis di Castiglione,"French, 1822–1913|French|1835–1899",,"Pierson, Pierre-Louis|Unknown|Castiglione, di, Virginia Oldoini Verasis Countess",French,1822 |1835,1913 |1899,1861–67,1861,1867,Albumen silver print from glass negative,Image: 5.4 x 7.5 cm (2 1/8 x 2 15/16 in.) Mount: 6.8 x 8.8 cm (2 11/16 x 3 7/16 in.) Mat: 35.6 x 27.9 cm (14 x 11 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286830,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.399,false,true,286831,Photographs,Photograph,La Reine d'Étrurie,,,,,,Artist|Artist|Person in Photograph,Painted and retouched by,Pierre-Louis Pierson|Unknown|Countess Virginia Oldoini Verasis di Castiglione,"French, 1822–1913|French|1835–1899",,"Pierson, Pierre-Louis|Unknown|Castiglione, di, Virginia Oldoini Verasis Countess",French,1822 |1835,1913 |1899,1863–67,1863,1867,Albumen silver print from glass negative,Image: 10.5 x 8.3 cm (4 1/8 x 3 1/4 in.) Mount: 12.9 x 9.4 cm (5 1/16 x 3 11/16 in.) Mat: 35.6 x 27.9 cm (14 x 11 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286831,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.400,false,true,286832,Photographs,Photograph,[La Finlandaise],,,,,,Artist|Artist|Person in Photograph,Painted and retouched by,Pierre-Louis Pierson|Unknown|Countess Virginia Oldoini Verasis di Castiglione,"French, 1822–1913|French|1835–1899",,"Pierson, Pierre-Louis|Unknown|Castiglione, di, Virginia Oldoini Verasis Countess",French,1822 |1835,1913 |1899,1861–67,1861,1867,Albumen silver print from glass negative,Image: 12.2 x 8.8 cm (4 13/16 x 3 7/16 in.) Mount: 13.9 x 10.9 cm (5 1/2 x 4 5/16 in.) Mat: 35.6 x 27.9 cm (14 x 11 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286832,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.401,false,true,286833,Photographs,Photograph,Costigliole,,,,,,Artist|Artist|Person in Photograph,Painted and retouched by,Pierre-Louis Pierson|Unknown|Countess Virginia Oldoini Verasis di Castiglione,"French, 1822–1913|French|1835–1899",,"Pierson, Pierre-Louis|Unknown|Castiglione, di, Virginia Oldoini Verasis Countess",French,1822 |1835,1913 |1899,1862–67,1862,1867,Albumen silver print from glass negative,Image: 12.5 x 9 cm (4 15/16 x 3 9/16 in.) Mount: 14.5 x 10.7 cm (5 11/16 x 4 3/16 in.) Mat: 35.6 x 43.2 cm (14 x 17 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286833,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.402,false,true,286834,Photographs,Photograph,Costigliole,,,,,,Artist|Artist|Person in Photograph,Painted and retouched by,Pierre-Louis Pierson|Unknown|Countess Virginia Oldoini Verasis di Castiglione,"French, 1822–1913|French|1835–1899",,"Pierson, Pierre-Louis|Unknown|Castiglione, di, Virginia Oldoini Verasis Countess",French,1822 |1835,1913 |1899,1862–67,1862,1867,Albumen silver print from glass negative,Image: 12.5 x 8.9 cm (4 15/16 x 3 1/2 in.) Mount: 14.4 x 10.9 cm (5 11/16 x 4 5/16 in.) Mat: 35.6 x 43.2 cm (14 x 17 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286834,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.406,false,true,285609,Photographs,Photograph,La Frayeur,,,,,,Artist|Artist|Person in Photograph,Painted and retouched by,Pierre-Louis Pierson|Unknown|Countess Virginia Oldoini Verasis di Castiglione,"French, 1822–1913|French|1835–1899",,"Pierson, Pierre-Louis|Unknown|Castiglione, di, Virginia Oldoini Verasis Countess",French,1822 |1835,1913 |1899,1861–67,1861,1867,Salted paper print from glass negative with applied color,Image: 23.6 x 17 cm (9 5/16 x 6 11/16 in.) Mount: 31.7 x 23.8 cm (12 1/2 x 9 3/8 in.) Mat: 57.2 x 47 cm (22 1/2 x 18 1/2 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285609,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.407,false,true,286838,Photographs,Photograph,Béatrix,,,,,,Artist|Artist|Person in Photograph,Painted and retouched by,Pierre-Louis Pierson|Unknown|Countess Virginia Oldoini Verasis di Castiglione,"French, 1822–1913|French|1835–1899",,"Pierson, Pierre-Louis|Unknown|Castiglione, di, Virginia Oldoini Verasis Countess",French,1822 |1835,1913 |1899,"1856–57, printed 1861–67",1856,1867,Salted paper print from glass negative,Image: 23.2 x 17.8 cm (9 1/8 x 7 in.) Mount: 31.7 x 23.8 cm (12 1/2 x 9 3/8 in.) Mat: 57.2 x 47 cm (22 1/2 x 18 1/2 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286838,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.408a–d,false,true,286839,Photographs,Photograph,"[Variations on the ""Elvira"" and ""Ritrosetta"" Dresses]",,,,,,Artist|Artist|Person in Photograph,Painted and retouched by,Pierre-Louis Pierson|Unknown|Countess Virginia Oldoini Verasis di Castiglione,"French, 1822–1913|French|1835–1899",,"Pierson, Pierre-Louis|Unknown|Castiglione, di, Virginia Oldoini Verasis Countess",French,1822 |1835,1913 |1899,1861–67,1861,1867,Albumen silver print from glass negative,Image: 12.4 x 8.8 cm (4 7/8 x 3 7/16 in.) A Image: 12.5 x 8.9 cm (4 15/16 x 3 1/2 in.) B Image: 12.3 x 8.8 cm (4 13/16 x 3 7/16 in.) C Image: 12.4 x 8.9 cm (4 7/8 x 3 1/2 in.) D Mount: 32.2 x 24 cm (12 11/16 x 9 7/16 in.) Mat: 50.8 x 40.6 cm (20 x 16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286839,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.409a–d,false,true,286840,Photographs,Photograph,"[Variations on the ""Elvira"" Dress]",,,,,,Artist|Artist|Person in Photograph,Painted and retouched by,Pierre-Louis Pierson|Unknown|Countess Virginia Oldoini Verasis di Castiglione,"French, 1822–1913|French|1835–1899",,"Pierson, Pierre-Louis|Unknown|Castiglione, di, Virginia Oldoini Verasis Countess",French,1822 |1835,1913 |1899,1861–67,1861,1867,Albumen silver print from glass negative,Image: 12.5 x 8.9 cm (4 15/16 x 3 1/2 in.) A Image: 12.5 x 8.7 cm (4 15/16 x 3 7/16 in.) B Image: 12.3 x 8.9 cm (4 13/16 x 3 1/2 in.) C Image: 12.3 x 8.8 cm (4 13/16 x 3 7/16 in.) D Mount: 32.2 x 24 cm (12 11/16 x 9 7/16 in.) Mat: 50.8 x 40.6 cm (20 x 16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286840,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2005.100.714.1, .2",false,true,286111,Photographs,Photograph,[Auguste Vacquerie at Marine Terrace],,,,,,Artist|Author,,Charles Victor Hugo|Auguste Vacquerie,"French, 1826–1871|French, 1819–1855",,"Hugo, Charles Victor|Vacquerie, Auguste",French,1826 |1819,1871 |1855,1855,1855,1855,Salted paper print from glass negative,Image: 4 1/8 × 2 15/16 in. (10.5 × 7.4 cm) Mount: 8 5/8 × 5 13/16 in. (21.9 × 14.8 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286111,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.187,false,true,306174,Photographs,Photograph,Arak el Emir,,,,,,Artist|Printer,Possibly printed by,Louis Vignes|Charles Nègre,"French, 1831–1896|French, 1820–1880",,"Vignes, Louis|Nègre, Charles",French,1831 |1820,1896 |1880,1864,1864,1864,Albumen silver print from glass negative,Image: 19 × 24.7 cm (7 1/2 × 9 3/4 in.) Mount: 27.7 × 35.7 cm (10 7/8 × 14 1/16 in.),"Purchase, Susan and Thomas Dunn Gift, 2013",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306174,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -46.122.9,false,true,269078,Photographs,Photograph,Joueurs de vielle,,,,,,Artist|Printer,,"Louis-Désiré Blanquart-Évrard|Imprimerie photographique de Blanquart-Évrard, à Lille","French, 1802–1872|French, active 1851–55",,"Blanquart-Évrard, Louis-Désiré|Imprimerie photographique de Blanquart-Évrard, à Lille",French,1802 |1851,1872 |1855,1850–53,1850,1853,Salted paper print from paper negative,,"Harris Brisbane Dick Fund, 1946",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269078,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -46.122.14,false,true,269062,Photographs,Photograph,"Nature morte: chaudron, cruche, et légumes, sur une table à trétaux",,,,,,Artist|Printer,,"Henri Victor Regnault|Imprimerie photographique de Blanquart-Évrard, à Lille","French, 1810–1878|French, active 1851–55",,"Regnault, Henri Victor|Imprimerie photographique de Blanquart-Évrard, à Lille",French,1810 |1851,1878 |1855,1850–53,1850,1853,Salted paper print (Blanquart-Évrard process) from paper negative,,"Harris Brisbane Dick Fund, 1946",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269062,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1977.673.10,false,true,262126,Photographs,Photograph,"Nubie. Grand Temple d'Isis, A Philoe. Vue générale prise du nord",,,,,,Artist|Printer,,"Maxime Du Camp|Imprimerie photographique de Blanquart-Évrard, à Lille","French, 1822–1894|French, active 1851–55",,"Du Camp, Maxime|Imprimerie photographique de Blanquart-Évrard, à Lille",French,1822 |1851,1894 |1855,1850,1850,1850,Salted paper print (Blanquart-Évrard process) from paper negative,16.8 x 23.1 cm. (6 5/8 x 9 1/16 in.),"Robert O. Dougan Collection, Gift of Warner Communications Inc., 1973",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/262126,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1977.673.11,false,true,262127,Photographs,Photograph,"Nubie. Grand Temple d'Isis, A Philoe",,,,,,Artist|Printer,,"Maxime Du Camp|Imprimerie photographique de Blanquart-Évrard, à Lille","French, 1822–1894|French, active 1851–55",,"Du Camp, Maxime|Imprimerie photographique de Blanquart-Évrard, à Lille",French,1822 |1851,1894 |1855,1850,1850,1850,Salted paper print (Blanquart-Évrard process) from paper negative,16.0 x 22.4 cm. (6 5/16 x 8 13/16 in.),"Robert O. Dougan Collection, Gift of Warner Communications Inc., 1977",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/262127,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1981.1229.6.1,false,true,263214,Photographs,Photograph,Égypte Moyenne. Pyramide de Chéphren,,,,,,Artist|Printer,,"Maxime Du Camp|Imprimerie photographique de Blanquart-Évrard, à Lille","French, 1822–1894|French, active 1851–55",,"Du Camp, Maxime|Imprimerie photographique de Blanquart-Évrard, à Lille",French,1822 |1851,1894 |1855,1850,1850,1850,Salted paper print (Blanquart-Évrard process) from paper negative,,"Robert O. Dougan Collection, Gift of Warner Communications Inc., 1981",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/263214,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1981.1229.6.2,false,true,263225,Photographs,Photograph,Haute-Égypte. Girgeh. Mosquèe d'Aly-Bey,,,,,,Artist|Printer,,"Maxime Du Camp|Imprimerie photographique de Blanquart-Évrard, à Lille","French, 1822–1894|French, active 1851–55",,"Du Camp, Maxime|Imprimerie photographique de Blanquart-Évrard, à Lille",French,1822 |1851,1894 |1855,1850,1850,1850,Salted paper print (Blanquart-Évrard process) from paper negative,,"Robert O. Dougan Collection, Gift of Warner Communications Inc., 1981",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/263225,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1981.1229.6.4,false,true,263234,Photographs,Photograph,Thebes. Palais de Karnak. Sculptures extérieures du Sanctuaire de granit,,,,,,Artist|Printer,,"Maxime Du Camp|Imprimerie photographique de Blanquart-Évrard, à Lille","French, 1822–1894|French, active 1851–55",,"Du Camp, Maxime|Imprimerie photographique de Blanquart-Évrard, à Lille",French,1822 |1851,1894 |1855,1850,1850,1850,Salted paper print (Blanquart-Évrard process) from paper negative,,"Robert O. Dougan Collection, Gift of Warner Communications Inc., 1981",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/263234,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1981.1229.6.5,false,true,263235,Photographs,Photograph,Thebes. Palais de Karnak. Sanctuaire de granit et salle Hypostyle,,,,,,Artist|Printer,,"Maxime Du Camp|Imprimerie photographique de Blanquart-Évrard, à Lille","French, 1822–1894|French, active 1851–55",,"Du Camp, Maxime|Imprimerie photographique de Blanquart-Évrard, à Lille",French,1822 |1851,1894 |1855,1850,1850,1850,Salted paper print (Blanquart-Évrard process) from paper negative,,"Robert O. Dougan Collection, Gift of Warner Communications Inc., 1981",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/263235,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1981.1229.6.6,false,true,263236,Photographs,Photograph,Thebes. Médinet-Habou. Partie orientale du Péristyle du Palais de Ramsès-Méiamoun,,,,,,Artist|Printer,,"Maxime Du Camp|Imprimerie photographique de Blanquart-Évrard, à Lille","French, 1822–1894|French, active 1851–55",,"Du Camp, Maxime|Imprimerie photographique de Blanquart-Évrard, à Lille",French,1822 |1851,1894 |1855,1850,1850,1850,Salted paper print (Blanquart-Évrard process) from paper negative,,"Robert O. Dougan Collection, Gift of Warner Communications Inc., 1981",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/263236,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1981.1229.6.7,false,true,263237,Photographs,Photograph,Thebes. Médinet-Habou. Runes de la ville de Papa,,,,,,Artist|Printer,,"Maxime Du Camp|Imprimerie photographique de Blanquart-Évrard, à Lille","French, 1822–1894|French, active 1851–55",,"Du Camp, Maxime|Imprimerie photographique de Blanquart-Évrard, à Lille",French,1822 |1851,1894 |1855,1850,1850,1850,Salted paper print (Blanquart-Évrard process) from paper negative,,"Robert O. Dougan Collection, Gift of Warner Communications Inc., 1981",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/263237,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1981.1229.6.10,false,true,263215,Photographs,Photograph,Haute-Égypte. Entrée de la première Cataracte,,,,,,Artist|Printer,,"Maxime Du Camp|Imprimerie photographique de Blanquart-Évrard, à Lille","French, 1822–1894|French, active 1851–55",,"Du Camp, Maxime|Imprimerie photographique de Blanquart-Évrard, à Lille",French,1822 |1851,1894 |1855,1850,1850,1850,Salted paper print (Blanquart-Évrard process) from paper negative,,"Robert O. Dougan Collection, Gift of Warner Communications Inc., 1981",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/263215,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1981.1229.6.12,false,true,263217,Photographs,Photograph,Nubie. Philoe,,,,,,Artist|Printer,,"Maxime Du Camp|Imprimerie photographique de Blanquart-Évrard, à Lille","French, 1822–1894|French, active 1851–55",,"Du Camp, Maxime|Imprimerie photographique de Blanquart-Évrard, à Lille",French,1822 |1851,1894 |1855,1850,1850,1850,Salted paper print (Blanquart-Évrard process) from paper negative,,"Robert O. Dougan Collection, Gift of Warner Communications Inc., 1981",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/263217,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1981.1229.6.13,false,true,263218,Photographs,Photograph,Nubie. Grand Temple D'Isis A Philoe. Muraille occidentale,,,,,,Artist|Printer,,"Maxime Du Camp|Imprimerie photographique de Blanquart-Évrard, à Lille","French, 1822–1894|French, active 1851–55",,"Du Camp, Maxime|Imprimerie photographique de Blanquart-Évrard, à Lille",French,1822 |1851,1894 |1855,1850,1850,1850,Salted paper print (Blanquart-Évrard process) from paper negative,,"Robert O. Dougan Collection, Gift of Warner Communications Inc., 1981",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/263218,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1981.1229.6.14,false,true,263219,Photographs,Photograph,Nubie. Rive Orientale du Nil (Village de Bab). Vue prise au sud de Philoe,,,,,,Artist|Printer,,"Maxime Du Camp|Imprimerie photographique de Blanquart-Évrard, à Lille","French, 1822–1894|French, active 1851–55",,"Du Camp, Maxime|Imprimerie photographique de Blanquart-Évrard, à Lille",French,1822 |1851,1894 |1855,1850,1850,1850,Salted paper print (Blanquart-Évrard process) from paper negative,,"Robert O. Dougan Collection, Gift of Warner Communications Inc., 1981",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/263219,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1981.1229.6.15,false,true,263220,Photographs,Photograph,Nubie. Temple et Village de Débôd. Parembole de l'itinéraire d'Antonin,,,,,,Artist|Printer,,"Maxime Du Camp|Imprimerie photographique de Blanquart-Évrard, à Lille","French, 1822–1894|French, active 1851–55",,"Du Camp, Maxime|Imprimerie photographique de Blanquart-Évrard, à Lille",French,1822 |1851,1894 |1855,1850,1850,1850,Salted paper print (Blanquart-Évrard process) from paper negative,,"Robert O. Dougan Collection, Gift of Warner Communications Inc., 1981",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/263220,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1981.1229.6.16,false,true,263221,Photographs,Photograph,Nubie. Temple de Déböd. Parembole de l'itinéraire d'Antonin,,,,,,Artist|Printer,,"Maxime Du Camp|Imprimerie photographique de Blanquart-Évrard, à Lille","French, 1822–1894|French, active 1851–55",,"Du Camp, Maxime|Imprimerie photographique de Blanquart-Évrard, à Lille",French,1822 |1851,1894 |1855,1850,1850,1850,Salted paper print (Blanquart-Évrard process) from paper negative,,"Robert O. Dougan Collection, Gift of Warner Communications Inc., 1981",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/263221,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1981.1229.6.17,false,true,263222,Photographs,Photograph,Nubie. Kalabscheh. Sculptures de la Facade postérieure du Temple,,,,,,Artist|Printer,,"Maxime Du Camp|Imprimerie photographique de Blanquart-Évrard, à Lille","French, 1822–1894|French, active 1851–55",,"Du Camp, Maxime|Imprimerie photographique de Blanquart-Évrard, à Lille",French,1822 |1851,1894 |1855,1850,1850,1850,Salted paper print (Blanquart-Évrard process) from paper negative,,"Robert O. Dougan Collection, Gift of Warner Communications Inc., 1981",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/263222,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1981.1229.6.19,false,true,263224,Photographs,Photograph,Nubie. Hémi-Spéos de Sébour. Pylônes,,,,,,Artist|Printer,,"Maxime Du Camp|Imprimerie photographique de Blanquart-Évrard, à Lille","French, 1822–1894|French, active 1851–55",,"Du Camp, Maxime|Imprimerie photographique de Blanquart-Évrard, à Lille",French,1822 |1851,1894 |1855,1850,1850,1850,Salted paper print (Blanquart-Évrard process) from paper negative,,"Robert O. Dougan Collection, Gift of Warner Communications Inc., 1981",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/263224,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1981.1229.6.20,false,true,263226,Photographs,Photograph,Nubie. Forteresse D'Ibrym (Ancienne Premmis). Vue prise au sud.,,,,,,Artist|Printer,,"Maxime Du Camp|Imprimerie photographique de Blanquart-Évrard, à Lille","French, 1822–1894|French, active 1851–55",,"Du Camp, Maxime|Imprimerie photographique de Blanquart-Évrard, à Lille",French,1822 |1851,1894 |1855,1850,1850,1850,Salted paper print (Blanquart-Évrard process) from paper negative,,"Robert O. Dougan Collection, Gift of Warner Communications Inc., 1981",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/263226,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1981.1229.6.21,false,true,263227,Photographs,Photograph,Nubie. Ibsamboul. Partie septentrionale du Spéos d'Hathor,,,,,,Artist|Printer,,"Maxime Du Camp|Imprimerie photographique de Blanquart-Évrard, à Lille","French, 1822–1894|French, active 1851–55",,"Du Camp, Maxime|Imprimerie photographique de Blanquart-Évrard, à Lille",French,1822 |1851,1894 |1855,1850,1850,1850,Salted paper print (Blanquart-Évrard process) from paper negative,,"Robert O. Dougan Collection, Gift of Warner Communications Inc., 1981",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/263227,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1981.1229.6.22,false,true,263228,Photographs,Photograph,Nubie. Ibsamboul. Entrée du Spéos d'Hathor,,,,,,Artist|Printer,,"Maxime Du Camp|Imprimerie photographique de Blanquart-Évrard, à Lille","French, 1822–1894|French, active 1851–55",,"Du Camp, Maxime|Imprimerie photographique de Blanquart-Évrard, à Lille",French,1822 |1851,1894 |1855,1850,1850,1850,Salted paper print (Blanquart-Évrard process) from paper negative,,"Robert O. Dougan Collection, Gift of Warner Communications Inc., 1981",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/263228,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1981.1229.6.23,false,true,263229,Photographs,Photograph,Nubie. Vue Cavalière de la Seconde Cataracte,,,,,,Artist|Printer,,"Maxime Du Camp|Imprimerie photographique de Blanquart-Évrard, à Lille","French, 1822–1894|French, active 1851–55",,"Du Camp, Maxime|Imprimerie photographique de Blanquart-Évrard, à Lille",French,1822 |1851,1894 |1855,1850,1850,1850,Salted paper print (Blanquart-Évrard process) from paper negative,,"Robert O. Dougan Collection, Gift of Warner Communications Inc., 1981",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/263229,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1981.1229.6.24,false,true,263230,Photographs,Photograph,Nubie. Seconde Cataracte. Dgebel-Abouoir,,,,,,Artist|Printer,,"Maxime Du Camp|Imprimerie photographique de Blanquart-Évrard, à Lille","French, 1822–1894|French, active 1851–55",,"Du Camp, Maxime|Imprimerie photographique de Blanquart-Évrard, à Lille",French,1822 |1851,1894 |1855,1850,1850,1850,Salted paper print (Blanquart-Évrard process) from paper negative,,"Robert O. Dougan Collection, Gift of Warner Communications Inc., 1981",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/263230,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1981.1229.6.25,false,true,263231,Photographs,Photograph,Palestine. Jérusalem. Partie occidentale des Murailles,,,,,,Artist|Printer,,"Maxime Du Camp|Imprimerie photographique de Blanquart-Évrard, à Lille","French, 1822–1894|French, active 1851–55",,"Du Camp, Maxime|Imprimerie photographique de Blanquart-Évrard, à Lille",French,1822 |1851,1894 |1855,1850,1850,1850,Salted paper print (Blanquart-Évrard process) from paper negative,,"Robert O. Dougan Collection, Gift of Warner Communications Inc., 1981",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/263231,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1981.1229.6.26,false,true,263232,Photographs,Photograph,Palestine. Jérusalem. Mosquée d'Omar,,,,,,Artist|Printer,,"Maxime Du Camp|Imprimerie photographique de Blanquart-Évrard, à Lille","French, 1822–1894|French, active 1851–55",,"Du Camp, Maxime|Imprimerie photographique de Blanquart-Évrard, à Lille",French,1822 |1851,1894 |1855,1850,1850,1850,Salted paper print (Blanquart-Évrard process) from paper negative,,"Robert O. Dougan Collection, Gift of Warner Communications Inc., 1981",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/263232,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -46.122.3,false,true,269072,Photographs,Photograph,Homme allongé au pied d'un chàtaignier,,,,,,Artist|Printer,,"Charles Marville|Imprimerie photographique de Blanquart-Évrard, à Lille","French, Paris 1813–1879 Paris|French, active 1851–55",,"Marville, Charles|Imprimerie photographique de Blanquart-Évrard, à Lille",French,1813 |1851,1879 |1855,1850–53,1850,1853,Salted paper print (Blanquart-Évrard process) from paper negative,20.9 x 16.2 cm (8 1/4 x 6 3/8 in.),"Harris Brisbane Dick Fund, 1946",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269072,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -46.122.5,false,true,269074,Photographs,Photograph,Allée bordée d'arbres,,,,,,Artist|Printer,,"Charles Marville|Imprimerie photographique de Blanquart-Évrard, à Lille","French, Paris 1813–1879 Paris|French, active 1851–55",,"Marville, Charles|Imprimerie photographique de Blanquart-Évrard, à Lille",French,1813 |1851,1879 |1855,1850–53,1850,1853,Salted paper print (Blanquart-Évrard process) from paper negative,,"Harris Brisbane Dick Fund, 1946",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269074,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -46.122.17,false,true,269065,Photographs,Photograph,Carrière,,,,,,Artist|Printer,,"Charles Marville|Imprimerie photographique de Blanquart-Évrard, à Lille","French, Paris 1813–1879 Paris|French, active 1851–55",,"Marville, Charles|Imprimerie photographique de Blanquart-Évrard, à Lille",French,1813 |1851,1879 |1855,1850–53,1850,1853,Salted paper print (Blanquart-Évrard process) from paper negative,,"Harris Brisbane Dick Fund, 1946",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269065,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -46.122.4,false,true,269073,Photographs,Photograph,Mt. Liban. Tronc d'un des Cèdres de Salomon,,,,,,Artist|Printer,,"Ernest Benecke|Imprimerie photographique de Blanquart-Évrard, à Lille","German, born England, 1817–1894|French, active 1851–55",,"Benecke, Ernest|Imprimerie photographique de Blanquart-Évrard, à Lille",French,1817 |1851,1894 |1855,1850–53,1850,1853,Salted paper print (Blanquart-Évrard process) from paper negative,,"Harris Brisbane Dick Fund, 1946",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269073,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2000.235,false,true,283727,Photographs,Waxed paper negative,Jacques-Joseph Ebelman on his Deathbed,,,,,,Artist,,Louis-Rémy Robert,"French, 1810–1882",,"Robert, Louis-Rémy",French,1810,1882,"March 31, 1852",1852,1852,Waxed paper negative,19 x 24.5 cm (7 1/2 x 9 5/8 in.),"Purchase, The Hite Foundation Gift, 2000",,,,,,,,,,,,Negatives,,http://www.metmuseum.org/art/collection/search/283727,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2006.248,false,true,287309,Photographs,Paper negative,[Gardens of the Château de Saint-Cloud],,,,,,Artist,,Louis-Rémy Robert,"French, 1810–1882",,"Robert, Louis-Rémy",French,1810,1882,ca. 1853,1851,1855,Paper negative,Sheet: 32.6 x 23.8 cm (12 13/16 x 9 3/8 in.),"Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2006",,,,,,,,,,,,Negatives,,http://www.metmuseum.org/art/collection/search/287309,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.686,false,true,286771,Photographs,Negative; Photograph,"[Village Scene, Brittany]",,,,,,Artist,,Louis-Rémy Robert,"French, 1810–1882",,"Robert, Louis-Rémy",French,1810,1882,ca. 1854,1852,1856,Paper negative,Image: 12 3/4 × 10 3/16 in. (32.4 × 25.8 cm) (image only),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Negatives,,http://www.metmuseum.org/art/collection/search/286771,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.868,false,true,286247,Photographs,Negative; Photograph,[Fountain at Versailles],,,,,,Artist,,Louis-Rémy Robert,"French, 1810–1882",,"Robert, Louis-Rémy",French,1810,1882,ca. 1851,1849,1853,Paper negative,Image: 10 5/16 × 12 11/16 in. (26.2 × 32.3 cm) (image only),"Gilman Collection, Purchase, Jennifer and Joseph Duke Gift, 2005",,,,,,,,,,,,Negatives,,http://www.metmuseum.org/art/collection/search/286247,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.877,false,true,286248,Photographs,Negative; Photograph,[Still Life],,,,,,Artist,,Louis-Rémy Robert,"French, 1810–1882",,"Robert, Louis-Rémy",French,1810,1882,1850,1850,1850,Paper negative,Sheet: 10 5/8 × 7 3/8 in. (27 × 18.7 cm) Image: 10 3/8 × 7 1/16 in. (26.4 × 18 cm),"Gilman Collection, Purchase, Anonymous Gifts, by exchange, 2005",,,,,,,,,,,,Negatives,,http://www.metmuseum.org/art/collection/search/286248,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.863,false,true,286571,Photographs,Negative; Photograph,"[Bas-Relief, Arch of Constantine, Rome]",,,,,,Artist,,Frédéric Flachéron,"French, 1813–1883",,"Flachéron, Frédéric",French,1813,1883,1849,1849,1849,Paper negative,Image: 12 3/4 × 9 15/16 in. (32.4 × 25.2 cm),"Gilman Collection, Purchase, Anonymous Gifts, by exchange, 2005",,,,,,,,,,,,Negatives,,http://www.metmuseum.org/art/collection/search/286571,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2000.208.2,false,true,283722,Photographs,Paper negative,[Boy in Uniform],,,,,,Artist,,Jean-Baptiste Frénet,"French, 1814–1889",,"Frénet, Jean-Baptiste",French,1814,1889,ca. 1855,1853,1857,Paper negative,24.5 x 17.9 cm (9 5/8 x 7 1/16 in. ),"Purchase, Noel and Harriette Levine Gift and Jennifer and Joseph Duke Gift, 2000",,,,,,,,,,,,Negatives,,http://www.metmuseum.org/art/collection/search/283722,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -46.122.21,false,true,269070,Photographs,Photomechanical print,[Man and Boy],,,,,,Artist,,Armand-Hippolyte-Louis Fizeau,"French, 1819–1896",,"Fizeau, Armand-Hippolyte-Louis",French,1819,1896,ca. 1841,1840,1842,Photogravure,,"Harris Brisbane Dick Fund, 1946",,,,,,,,,,,,Photographs|Prints,,http://www.metmuseum.org/art/collection/search/269070,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -46.122.22,false,true,269071,Photographs,Photomechanical print,"St. Sulpice, Paris",,,,,,Artist,,Armand-Hippolyte-Louis Fizeau,"French, 1819–1896",,"Fizeau, Armand-Hippolyte-Louis",French,1819,1896,ca. 1841,1840,1842,Photogravure,,"Harris Brisbane Dick Fund, 1946",,,,,,,,,,,,Photographs|Prints,,http://www.metmuseum.org/art/collection/search/269071,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1998.475,false,true,282737,Photographs,Album,Le Midi de la France,,,,,,Artist,,Charles Nègre,"French, 1820–1880",,"Nègre, Charles",French,1820,1880,1850s–60s,1850,1869,Albumen silver prints,24.4 x 29.4 x 6.3 cm (9 5/8 x 11 9/16 x 2 1/2 in. ),"Gift of W. Bruce and Delaney H. Lundberg, 1998",,,,,,,,,,,,Albums,,http://www.metmuseum.org/art/collection/search/282737,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1995.562,false,true,267296,Photographs,Photomechanical print,"[Chartres Cathedral, Central Portal of the South Transept; The Last Judgment]",,,,,,Artist,,Charles Nègre,"French, 1820–1880",,"Nègre, Charles",French,1820,1880,"1855, printed 1857",1855,1855,Photogravure,60.0 x 48.5 cm. (23 5/8 x 19 1/16 in.),"Gift of Charles Isaacs, 1995",,,,,,,,,,,,Photographs|Prints,,http://www.metmuseum.org/art/collection/search/267296,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.1–.175,false,true,286683,Photographs,Portfolio,"Égypte, Nubie, Syrie: Paysages et Monuments",,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,1849–50,1849,1850,Salted paper prints from paper negatives,49 x 33 x 6 cm (19 5/16 x 13 x 2 3/8 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Portfolios,,http://www.metmuseum.org/art/collection/search/286683,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.892,false,true,285681,Photographs,Negative; Photograph,[Model of a P.L.M. Locomotive],,,,,,Artist,,Dominique Roman,"French, 1824–1911",,"Roman, Dominique",French,1824,1911,ca. 1855,1853,1857,Paper negative,Image: 12 5/16 × 18 7/8 in. (31.2 × 48 cm),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Negatives,,http://www.metmuseum.org/art/collection/search/285681,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.270,false,true,307069,Photographs,Waxed Paper Negative,"[Palace of the Dey of Algiers, Algeria]",,,,,,Artist,,Gustave de Beaucorps,"French, 1825–1906",,"Beaucorps, Gustave de",French,1825,1906,1859,1859,1859,Waxed paper negative,"Image: 28.7 × 38.6 cm (11 5/16 × 15 3/16 in.) Sheet: 29.1 × 39.8 cm (11 7/16 × 15 11/16 in.), irregularly trimmed","The Horace W. Goldsmith Foundation Fund, through Joyce and Robert Menschel, 2013",,,,,,,,,,,,Negatives,,http://www.metmuseum.org/art/collection/search/307069,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2012.352 (1–14),false,true,302505,Photographs,Photographs,Souvenir 1870–71,,,,,,Artist,,Ernest Eugène Appert,"French, 1831–1891",,"Appert, Ernest Eugène",French,1831,1891,1870–71,1870,1871,Albumen silver prints from glass negatives,Sheet: 36 x 46 cm (14 3/16 x 18 1/8 in.),"Joyce F. Menschel Photography Library Fund, 2012",,,,,,,,,,,,Albums,,http://www.metmuseum.org/art/collection/search/302505,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2001.483.1–.172,false,true,284718,Photographs,Album,[Album of Paris Crime Scenes],,,,,,Artist,Attributed to,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",French,1853,1914,1901–8,1901,1908,Gelatin silver prints,Overall: 24.3 x 31cm (9 9/16 x 12 3/16in.) Page: 23 x 29 cm (9 1/16 x 11 7/16 in.),"Gilman Collection, Purchase, The Howard Gilman Foundation Gift, 2001",,,,,,,,,,,,Albums,,http://www.metmuseum.org/art/collection/search/284718,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.1240,false,true,286575,Photographs,Negative; Photograph,The Laundry,,,,,,Artist,,Louis-Adolphe Humbert de Molard,"French, Paris 1800–1874",,"Humbert de Molard, Louis-Adolphe",French,1800,1874,1840s,1840,1849,Albumen paper negative,Image: 5 13/16 × 5 5/16 in. (14.7 × 13.5 cm),"Gilman Collection, Purchase, Ann Tenenbaum and Thomas H. Lee Gift, 2005",,,,,,,,,,,,Negatives,,http://www.metmuseum.org/art/collection/search/286575,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.588.3.1–.155,false,true,286390,Photographs,Album,Demi-Monde II 57,,,,,,Artist,,André-Adolphe-Eugène Disdéri,"French, Paris 1819–1889 Paris",,"Disdéri, André-Adolphe-Eugène",French,1819,1889,1858–68,1858,1868,Albumen silver print from glass negative,,"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Albums,,http://www.metmuseum.org/art/collection/search/286390,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.371,false,true,283117,Photographs,Photograph; Album,"[Album Containing Photographs, Engravings, Drawings, and Publications Pertaining to Alexandre Dumas]",,,,,,Artist,,Nadar,"French, Paris 1820–1910 Paris",,Nadar,French,1820,1910,November 1855,1855,1855,Salted paper print from glass negative,Image: 9 3/8 × 7 5/16 in. (23.8 × 18.5 cm) Sheet: 11 1/2 × 7 5/8 in. (29.2 × 19.4 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Albums,,http://www.metmuseum.org/art/collection/search/283117,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.501 (1-47),false,true,283168,Photographs,Album,Cochinchine et Cambodge,,,,,,Artist,,Emile Gsell,"French, Sainte-Marie-aux-Mines 1838–1879 Vietnam",,"Gsell, Emile",French,1838,1879,1866,1866,1866,Albumen silver prints from glass negatives,44.8 x 37.2 x 3.4 cm (17 5/8 x 14 5/8 x 1 5/16 in.),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Albums,,http://www.metmuseum.org/art/collection/search/283168,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.590.2,false,true,675524,Photographs,Photograph,"Portail Saint-Trophime; Entrée Du Cloître, Arles, Église Métropolitaine de Saint-Trophime",,,,,,Artist,,Charles Nègre,"French, 1820–1880",,"Nègre, Charles",French,1820,1880,1852,1852,1852,Relief print,Image: 6 15/16 × 5 7/8 in. (17.6 × 15 cm) Mount: 15 7/8 × 7 1/2 in. (40.4 × 19.1 cm),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/675524,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2012.481.18,false,true,301936,Photographs,Daguerreotype,[Woman with Four Children],,,,,,Artist,,Alexandre Bertrand,"French, born 1822",,"Bertrand, Alexandre",French,1822,1922,1850s,1850,1859,Daguerreotype,Image: 14.6 × 11 cm (5 3/4 × 4 5/16 in.) Frame: 37 × 31.5 cm (14 9/16 × 12 3/8 in.),"Gift of Joyce F. Menschel, 2012",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/301936,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2010.23,true,true,291739,Photographs,Photograph,[The Salon of Baron Gros],,,,,,Artist,,Baron Jean-Baptiste-Louis Gros,"French, 1793–1870",,"Gros, Jean-Baptiste-Louis",French,1793,1870,1850–57,1850,1857,Daguerreotype,Image: 22 x 17.1 cm (8 11/16 x 6 3/4 in.),"Purchase, Fletcher Fund, Joyce F. Menschel Gift, Louis V. Bell Fund, Alfred Stieglitz Society and W. Bruce and Delaney H. Lundberg Gifts, 2010",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/291739,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1993.69.1,false,true,266980,Photographs,Photograph,[Reclining Female Nude],,,,,,Artist,,Julien Vallou de Villeneuve,"French, 1795–1866",,"Vallou-de-Villeneuve, Julien",French,1795,1866,ca. 1853,1851,1855,Salted paper print from paper negative,Image: 11.8 x 16.0 cm (4 5/8 x 6 5/16 in.),"Purchase, Lila Acheson Wallace Gift, 1993",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/266980,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1993.69.2,false,true,266981,Photographs,Photograph,[Standing Female Nude],,,,,,Artist,,Julien Vallou de Villeneuve,"French, 1795–1866",,"Vallou-de-Villeneuve, Julien",French,1795,1866,ca. 1853,1851,1855,Salted paper print from paper negative,Image: 12.0 x 16.0 cm. (4 3/4 x 6 5/16 in.),"Purchase, Lila Acheson Wallace Gift, 1993",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/266981,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1993.69.3,true,true,266982,Photographs,Photograph,"[Female Nude, Reclining, in Profile]",,,,,,Artist,,Julien Vallou de Villeneuve,"French, 1795–1866",,"Vallou-de-Villeneuve, Julien",French,1795,1866,ca. 1853,1851,1855,Salted paper print from paper negative,11.2 x 15.5 cm (4 7/16 x 6 1/8 in.),"Purchase, Lila Acheson Wallace Gift, 1993",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/266982,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.40,false,true,283114,Photographs,Photograph,[Reclining Nude],,,,,,Artist,,Julien Vallou de Villeneuve,"French, 1795–1866",,"Vallou-de-Villeneuve, Julien",French,1795,1866,1851–53,1851,1853,Salted paper print from paper negative,Image: 4 13/16 × 6 5/16 in. (12.3 × 16.1 cm) Mount: 10 11/16 in. × 14 in. (27.2 × 35.5 cm),"Gilman Collection, Purchase, Mrs. Walter Annenberg and The Annenberg Foundation Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/283114,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.32,false,true,285909,Photographs,Photograph,"[Windmills, Montmartre]",,,,,,Artist,,Hippolyte Bayard,"French, 1801–1887",,"Bayard, Hippolyte",French,1801,1887,1839,1839,1839,Direct positive on paper,"Image: 9.3 × 10.6 cm (3 11/16 × 4 3/16 in.), irregularly trimmed","Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285909,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.379,false,true,285907,Photographs,Photograph,[Still Life with Statuary],,,,,,Artist,,Hippolyte Bayard,"French, 1801–1887",,"Bayard, Hippolyte",French,1801,1887,Early 1850s,1850,1855,Albumen silver print from glass negative,Image: 26.2 x 20.2 cm (10 5/16 x 7 15/16 in.),"Gilman Collection, Purchase, William Talbott Hillman Foundation Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285907,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -38.22,false,true,268620,Photographs,Photograph,The Birth of Venus,,,,,,Artist,,Abel Niépce de St. Victor,"French, 1805–1870",,"Niépce de St. Victor, Abel",French,1805,1870,1855,1855,1855,Photogravure,5.4 x 4.1 cm. (2 1/8 x 1 5/8 in.),"Gift of Harry Stone, 1938",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268620,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.189,false,true,285971,Photographs,Photograph,Monsieur Itier's Cange Under Sail on the Nile,,,,,,Artist,,Andre-Victor-Alcide-Jules Itier,"French, 1805–1877",,"Itier, Andre-Victor-Alcide-Jules",French,1805,1877,1845–46,1845,1846,Daguerreotype,Image (visible): 4 3/8 × 5 15/16 in. (11.1 × 15.1 cm) Overall: 6 5/8 × 8 1/4 in. (16.8 × 21 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285971,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.39,false,true,283113,Photographs,Photograph,[Gardens of Saint-Cloud],,,,,,Artist,,Henri Victor Regnault,"French, 1810–1878",,"Regnault, Henri Victor",French,1810,1878,before 1855,1855,1855,Salted paper print from paper negative,Image: 16 1/8 × 14 1/16 in. (41 × 35.7 cm) Sheet: 19 in. × 15 3/16 in. (48.3 × 38.5 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/283113,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1995.3,false,true,267230,Photographs,Photograph,[Henriette-Caroline-Victoire Robert],,,,,,Artist,,Louis-Rémy Robert,"French, 1810–1882",,"Robert, Louis-Rémy",French,1810,1882,1850s,1850,1859,Salted paper print from paper negative,23.1 x 17.4 cm (9 1/16 x 6 7/8 in.),"Purchase, Joyce and Robert Menschel Gift and The Horace W. Goldsmith Foundation Gift through Joyce and Robert Menschel, 1995",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267230,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1995.104,false,true,267132,Photographs,Photograph,[Gardens of the Chàteau de Saint-Cloud],,,,,,Artist,,Louis-Rémy Robert,"French, 1810–1882",,"Robert, Louis-Rémy",French,1810,1882,ca. 1853,1851,1855,Salted paper print from paper negative,31.1 x 20.0 cm. (12 1/4 x 7 7/8 in.),"Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 1995",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267132,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1987.1052,false,true,265090,Photographs,Photograph,[Table Top Still Life with Model Cathedral and Small Sculptures],,,,,,Artist,,Louis-Rémy Robert,"French, 1810–1882",,"Robert, Louis-Rémy",French,1810,1882,ca. 1856,1854,1858,Salted paper print from glass negative,20.0 x 26.1 cm (7 7/8 x 10 1/4 in.),"Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 1987",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/265090,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1991.1044,true,true,266332,Photographs,Photograph,Alfred Thompson Gobert,,,,,,Artist,,Louis-Rémy Robert,"French, 1810–1882",,"Robert, Louis-Rémy",French,1810,1882,1849–55,1849,1855,Salted paper print from paper negative,22.6 x 16.8 cm (8 7/8 x 6 5/8 in.),"Purchase, Joyce and Robert Menschel, Mrs. Harrison D. Horblit and Paul F. Walter Gifts, and Rogers Fund, 1991",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/266332,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1996.363.2,true,true,271963,Photographs,Photograph,Romesnil,,,,,,Artist,,Louis-Rémy Robert,"French, 1810–1882",,"Robert, Louis-Rémy",French,1810,1882,1850–55,1850,1855,Salted paper print from paper negative,27 x 35.2 cm (10 5/8 x 13 7/8 in.),"Purchase, Joseph Pulitzer Bequest, 1996",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/271963,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.38,false,true,283112,Photographs,Photograph,"[The Large Tree at La Verrerie, Romesnil]",,,,,,Artist,,Louis-Rémy Robert,"French, 1810–1882",,"Robert, Louis-Rémy",French,1810,1882,ca. 1852,1850,1854,Salted paper print from paper negative,Image: 10 3/8 × 12 1/2 in. (26.3 × 31.8 cm),"Gilman Collection, Purchase, Mrs. Walter Annenberg and The Annenberg Foundation Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/283112,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.630,false,true,286246,Photographs,Photograph,"[Village Scene, Brittany]",,,,,,Artist,,Louis-Rémy Robert,"French, 1810–1882",,"Robert, Louis-Rémy",French,1810,1882,ca. 1854,1852,1856,Salted paper print from paper negative,Image: 31.8 x 26.4 cm (12 1/2 x 10 3/8 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286246,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1998.334.1,false,true,284087,Photographs,Photograph,"Barricades de la Commune, avril 71. Coin de la place Hotel de Ville & de la rue de Rivoli",,,,,,Artist,,Pierre-Ambrose Richebourg,"French, 1810–1893",,"Richebourg, Pierre-Ambrose",French,1810,1893,1871,1871,1871,Albumen silver print,10.6 x 10 cm (4 3/16 x 3 15/16 in.),"Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 1998",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/284087,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2004.106,false,true,285343,Photographs,Photograph,[Study of Leaves on a Background of Floral Lace],,,,,,Artist,,Charles Hippolyte Aubry,"French, 1811–1877",,"Aubry, Charles",French,1811,1877,1864,1864,1864,Albumen silver print from glass negative,Image: 46.7 x 36.7 cm (18 3/8 x 14 7/16 in.) Mount: 56.7 x 45.3 cm (22 5/16 x 17 13/16 in.),"Gilman Collection, Purchase, Howard Gilman Foundation Gift, 2004",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285343,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -59.600.57,false,true,270200,Photographs,Photograph,[The Tuileries after the Commune],,,,,,Artist,,Hippolyte-Auguste Collard,"French, 1811–1887",,"Collard, Hippolyte-Auguste",French,1811,1887,1871,1871,1871,Albumen silver print from glass negative,,"The Elisha Whittelsey Collection, The Elisha Whittelsey Fund, 1959",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/270200,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -59.600.58,false,true,270201,Photographs,Photograph,[The Vendôme Column After Being Torn Down by the Communards],,,,,,Artist,,Hippolyte-Auguste Collard,"French, 1811–1887",,"Collard, Hippolyte-Auguste",French,1811,1887,1871,1870,1879,Albumen silver print from glass negative,21.7 x 30.9 cm (8 9/16 x 12 3/16 in. ),"The Elisha Whittelsey Collection, The Elisha Whittelsey Fund, 1959",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/270201,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -59.600.59,false,true,270202,Photographs,Photograph,[The Hötel de Ville after the Commune],,,,,,Artist,,Hippolyte-Auguste Collard,"French, 1811–1887",,"Collard, Hippolyte-Auguste",French,1811,1887,1871,1871,1871,Albumen silver print from glass negative,,"The Elisha Whittelsey Collection, The Elisha Whittelsey Fund, 1959",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/270202,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -59.600.60,false,true,270203,Photographs,Photograph,[The Tuileries After Its Destruction by the Communards],,,,,,Artist,,Hippolyte-Auguste Collard,"French, 1811–1887",,"Collard, Hippolyte-Auguste",French,1811,1887,1871,1871,1871,Albumen silver print from glass negative,20.8 x 27 cm (8 3/16 x 10 5/8 in. ),"The Elisha Whittelsey Collection, The Elisha Whittelsey Fund, 1959",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/270203,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -59.600.61,false,true,270204,Photographs,Photograph,[Barricades pres de Ministere de la Marine et l'Hötel Crillon],,,,,,Artist,,Hippolyte-Auguste Collard,"French, 1811–1887",,"Collard, Hippolyte-Auguste",French,1811,1887,1871,1871,1871,Albumen silver print from glass negative,21.7 x 30.9 cm (8 9/16 x 12 3/16 in. ),"The Elisha Whittelsey Collection, The Elisha Whittelsey Fund, 1959",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/270204,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.436,false,true,285611,Photographs,Photograph,"[Landscape, Arras]",,,,,,Artist,,Adalbert Cuvelier,"French, 1812–1871",,"Cuvelier, Albert",French,1812,1871,1852,1852,1852,Salted paper print from paper negative,Image: 16.3 × 14.1 cm (6 7/16 × 5 9/16 in.) Mount: 20.4 × 17.5 cm (8 1/16 × 6 7/8 in.),"Gilman Collection, Purchase, Mrs. Walter Annenberg and The Annenberg Foundation Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285611,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1995.96.1,false,true,282154,Photographs,Photograph,Vue générale de Rouen,,,,,,Artist,,Edmond Bacot,"French, 1814–1875",,"Bacot, Edmond",French,1814,1875,1852–54,1852,1854,Salted paper print from glass negative,21.9 x 33.3 cm (8 5/8 x 13 1/8 in. ),"Harris Brisbane Dick Fund, 1995",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/282154,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1995.96.2,false,true,282155,Photographs,Photograph,Vue générale de la Cathédrale de Rouen,,,,,,Artist,,Edmond Bacot,"French, 1814–1875",,"Bacot, Edmond",French,1814,1875,1852–54,1852,1854,Salted paper print from glass negative,33.6 x 25.1 cm (13 1/4 x 9 7/8 in. ),"Harris Brisbane Dick Fund, 1995",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/282155,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1995.96.3,false,true,282156,Photographs,Photograph,"Bas du Portail, Côté de la Place, Cathédrale de Rouen",,,,,,Artist,,Edmond Bacot,"French, 1814–1875",,"Bacot, Edmond",French,1814,1875,1852–54,1852,1854,Salted paper print from glass negative,26.2 x 35 cm (10 5/16 x 13 3/4 in. ),"Harris Brisbane Dick Fund, 1995",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/282156,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1995.96.4,false,true,282157,Photographs,Photograph,"Haut de Portail, Côté de la Place, Cathédrale de Rouen",,,,,,Artist,,Edmond Bacot,"French, 1814–1875",,"Bacot, Edmond",French,1814,1875,1852–54,1852,1854,Salted paper print from glass negative,34.6 x 26 cm (13 5/8 x 10 1/4 in. ),"Harris Brisbane Dick Fund, 1995",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/282157,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1995.96.5,false,true,282158,Photographs,Photograph,"Portail de la Calende, Rouen Cathédral",,,,,,Artist,,Edmond Bacot,"French, 1814–1875",,"Bacot, Edmond",French,1814,1875,1852–54,1852,1854,Salted paper print from glass negative,34.4 x 26 cm (13 9/16 x 10 1/4 in. ),"Harris Brisbane Dick Fund, 1995",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/282158,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1995.96.6,false,true,282159,Photographs,Photograph,Vue générale de Saint-Ouen,,,,,,Artist,,Edmond Bacot,"French, 1814–1875",,"Bacot, Edmond",French,1814,1875,1852–54,1852,1854,Salted paper print from glass negative,24.8 x 31.1 cm (9 3/4 x 12 1/4 in. ),"Harris Brisbane Dick Fund, 1995",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/282159,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1995.96.7,false,true,282160,Photographs,Photograph,"Portail de Saint-Ouen, Rouen",,,,,,Artist,,Edmond Bacot,"French, 1814–1875",,"Bacot, Edmond",French,1814,1875,1852–54,1852,1854,Salted paper print from glass negative,34.7 x 26.3 cm (13 11/16 x 10 3/8 in. ),"Harris Brisbane Dick Fund, 1995",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/282160,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1995.96.8,false,true,282161,Photographs,Photograph,"Tour de Saint-Ouen, Rouen",,,,,,Artist,,Edmond Bacot,"French, 1814–1875",,"Bacot, Edmond",French,1814,1875,1852–54,1852,1854,Salted paper print from glass negative,33.7 x 24.9 cm (13 1/4 x 9 13/16 in. ),"Harris Brisbane Dick Fund, 1995",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/282161,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1995.96.9,false,true,282162,Photographs,Photograph,"Portail des Marmousets, Saint-Ouen de Rouen",,,,,,Artist,,Edmond Bacot,"French, 1814–1875",,"Bacot, Edmond",French,1814,1875,1852–54,1852,1854,Salted paper print from glass negative,34 x 26.1 cm (13 3/8 x 10 1/4 in. ),"Harris Brisbane Dick Fund, 1995",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/282162,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1995.96.10,true,true,282163,Photographs,Photograph,"Saint-Maclou, Rouen",,,,,,Artist,,Edmond Bacot,"French, 1814–1875",,"Bacot, Edmond",French,1814,1875,1852–54,1852,1854,Salted paper print from glass negative,34.4 x 25.6 cm (13 9/16 x 10 1/16 in.),"Harris Brisbane Dick Fund, 1995",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/282163,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1995.96.11,false,true,282164,Photographs,Photograph,"Escalier de la Basse Vieille Cour, Rouen",,,,,,Artist,,Edmond Bacot,"French, 1814–1875",,"Bacot, Edmond",French,1814,1875,1852–54,1852,1854,Salted paper print from glass negative,30.5 x 21.9 cm (12 x 8 5/8 in. ),"Harris Brisbane Dick Fund, 1995",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/282164,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1995.96.12,false,true,282165,Photographs,Photograph,"Fontaine de la Croix de Pierre, Rouen",,,,,,Artist,,Edmond Bacot,"French, 1814–1875",,"Bacot, Edmond",French,1814,1875,1852–54,1852,1854,Salted paper print from glass negative,30.9 x 22.6 cm (12 3/16 x 8 7/8 in. ),"Harris Brisbane Dick Fund, 1995",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/282165,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1995.96.13,false,true,267316,Photographs,Photograph,"Cloître Saint-Amand, Rouen",,,,,,Artist,,Edmond Bacot,"French, 1814–1875",,"Bacot, Edmond",French,1814,1875,1852–53,1852,1853,Salted paper print from glass negative,34.5 x 26.5 cm (13 9/16 x 10 7/16 in.),"Harris Brisbane Dick Fund, 1995",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267316,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1995.96.14,false,true,282166,Photographs,Photograph,"Hôtel du Bourgtheroulde, Rouen",,,,,,Artist,,Edmond Bacot,"French, 1814–1875",,"Bacot, Edmond",French,1814,1875,1852–54,1852,1854,Salted paper print from glass negative,33.6 x 24.9 cm (13 1/4 x 9 13/16 in. ),"Harris Brisbane Dick Fund, 1995",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/282166,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1995.96.15,false,true,282167,Photographs,Photograph,"Tourelle du Palais de Justice, Rouen",,,,,,Artist,,Edmond Bacot,"French, 1814–1875",,"Bacot, Edmond",French,1814,1875,1852–54,1852,1854,Salted paper print from glass negative,34.8 x 25.8 cm (13 11/16 x 10 3/16 in. ),"Harris Brisbane Dick Fund, 1995",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/282167,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1995.96.16,false,true,282168,Photographs,Photograph,"Fragment du Palais de Justice, Rouen",,,,,,Artist,,Edmond Bacot,"French, 1814–1875",,"Bacot, Edmond",French,1814,1875,1852–54,1852,1854,Salted paper print from glass negative,34.7 x 24.2 cm (13 11/16 x 9 1/2 in. ),"Harris Brisbane Dick Fund, 1995",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/282168,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1995.96.17,false,true,282169,Photographs,Photograph,"Fragment du Palais de Justice, Rouen",,,,,,Artist,,Edmond Bacot,"French, 1814–1875",,"Bacot, Edmond",French,1814,1875,1852–54,1852,1854,Salted paper print from glass negative,34.8 x 26 cm (13 11/16 x 10 1/4 in. ),"Harris Brisbane Dick Fund, 1995",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/282169,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1995.96.18,false,true,282170,Photographs,Photograph,"Notre Dame de Bonsecours, près Rouen",,,,,,Artist,,Edmond Bacot,"French, 1814–1875",,"Bacot, Edmond",French,1814,1875,1852–54,1852,1854,Salted paper print from glass negative,32.8 x 26.1 cm (12 15/16 x 10 1/4 in. ),"Harris Brisbane Dick Fund, 1995",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/282170,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1995.96.19,false,true,282171,Photographs,Photograph,"Saint-Georges de Boscherville, près Rouen",,,,,,Artist,,Edmond Bacot,"French, 1814–1875",,"Bacot, Edmond",French,1814,1875,1852–54,1852,1854,Salted paper print from glass negative,34.1 x 25.4 cm (13 7/16 x 10 in. ),"Harris Brisbane Dick Fund, 1995",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/282171,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1995.96.20,false,true,282172,Photographs,Photograph,"Cathédrale de Louviers, vue générale",,,,,,Artist,,Edmond Bacot,"French, 1814–1875",,"Bacot, Edmond",French,1814,1875,1852–54,1852,1854,Salted paper print from glass negative,32.6 x 25.4 cm (12 13/16 x 10 in. ),"Harris Brisbane Dick Fund, 1995",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/282172,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1995.96.21,false,true,282173,Photographs,Photograph,Portail de la Cathédrale de Louviers,,,,,,Artist,,Edmond Bacot,"French, 1814–1875",,"Bacot, Edmond",French,1814,1875,1852–54,1852,1854,Salted paper print from glass negative,32.7 x 24.5 cm (12 7/8 x 9 5/8 in. ),"Harris Brisbane Dick Fund, 1995",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/282173,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1995.96.22,false,true,282175,Photographs,Photograph,Portail de la Cathédrale de Louviers,,,,,,Artist,,Edmond Bacot,"French, 1814–1875",,"Bacot, Edmond",French,1814,1875,1852–54,1852,1854,Salted paper print from glass negative,34.3 x 25.4 cm (13 1/2 x 10 in. ),"Harris Brisbane Dick Fund, 1995",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/282175,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1995.96.23,false,true,282174,Photographs,Photograph,Château de Martainville,,,,,,Artist,,Edmond Bacot,"French, 1814–1875",,"Bacot, Edmond",French,1814,1875,1852–54,1852,1854,Salted paper print from glass negative,24.1 x 31.8 cm (9 1/2 x 12 1/2 in. ),"Harris Brisbane Dick Fund, 1995",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/282174,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1995.96.24,false,true,282176,Photographs,Photograph,"Rue des Petits Murs, Caen",,,,,,Artist,,Edmond Bacot,"French, 1814–1875",,"Bacot, Edmond",French,1814,1875,1852–54,1852,1854,Salted paper print from glass negative,33.7 x 25.6 cm (13 1/4 x 10 1/16 in. ),"Harris Brisbane Dick Fund, 1995",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/282176,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1995.96.25,false,true,282177,Photographs,Photograph,"Abside de Saint-Pierre, Caen",,,,,,Artist,,Edmond Bacot,"French, 1814–1875",,"Bacot, Edmond",French,1814,1875,1852–54,1852,1854,Salted paper print from glass negative,34.2 x 25.3 cm (13 7/16 x 9 15/16 in. ),"Harris Brisbane Dick Fund, 1995",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/282177,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1995.96.26,false,true,282178,Photographs,Photograph,Vue de l'Odon,,,,,,Artist,,Edmond Bacot,"French, 1814–1875",,"Bacot, Edmond",French,1814,1875,1852–54,1852,1854,Salted paper print from glass negative,33.9 x 25.6 cm (13 3/8 x 10 1/16 in. ),"Harris Brisbane Dick Fund, 1995",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/282178,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1995.96.27,false,true,282179,Photographs,Photograph,"Poissonerie, Caen",,,,,,Artist,,Edmond Bacot,"French, 1814–1875",,"Bacot, Edmond",French,1814,1875,1852–54,1852,1854,Salted paper print from glass negative,24.6 x 33.5 cm (9 11/16 x 13 3/16 in. ),"Harris Brisbane Dick Fund, 1995",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/282179,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1995.96.28,false,true,282180,Photographs,Photograph,"Abbaye aux Dames et Hospice, Caen",,,,,,Artist,,Edmond Bacot,"French, 1814–1875",,"Bacot, Edmond",French,1814,1875,1852–54,1852,1854,Salted paper print from glass negative,25.8 x 34.4 cm (10 3/16 x 13 9/16 in. ),"Harris Brisbane Dick Fund, 1995",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/282180,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.28,false,true,283102,Photographs,Photograph,[Dog],,,,,,Artist,,Louis-Auguste Bisson,"French, 1814–1876",,"Bisson, Louis-Auguste",French,1814,1876,1841–49,1841,1849,Daguerreotype,7.6 x 10.3 cm (3 x 4 1/16 in.),"Gilman Collection, Purchase, Jennifer and Joseph Duke Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/283102,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2000.208.1,false,true,283721,Photographs,Photograph,[Boy in Uniform],,,,,,Artist,,Jean-Baptiste Frénet,"French, 1814–1889",,"Frénet, Jean-Baptiste",French,1814,1889,ca. 1855,1853,1857,Salted paper print from glass negative,24.5 x 17.9 cm (9 5/8 x 7 1/16 in. ),"Purchase, Harriette and Noel Levine Gift and Jennifer and Joseph Duke Gift, 2000",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/283721,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -53.704.8,false,true,269671,Photographs,Photograph,Colonne Vendôme,,,,,,Artist,,Franck,"French, 1816–1906",,Franck,French,1816,1906,1871,1871,1871,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1953",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269671,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.380,false,true,286595,Photographs,Photograph,Canal St. Martin,,,,,,Artist,,Franck,"French, 1816–1906",,Franck,French,1816,1906,1860,1860,1860,Albumen silver print from glass negative,Image: 18.6 x 25.2 cm (7 5/16 x 9 15/16 in.),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286595,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2008.98,false,true,288428,Photographs,Photograph,"Deuxième Cataracte, Rocher d'Abouçir, Rapides et Ilots Granitiques",,,,,,Artist,,Félix Teynard,"French, 1817–1892",,"Teynard, Félix",French,1817,1892,1851,1851,1851,Salted paper print from paper negative,Image: 24.9 x 30.7 cm (9 13/16 x 12 1/16 in.) Mount: 39.9 x 51.9 cm (15 11/16 x 20 7/16 in.),"Purchase, Susan and Thomas Dunn Gift and funds from various donors, 2008",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288428,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1996.530,false,true,272026,Photographs,Photograph,"Louksor (Thèbes), Vue Générale des Ruines",,,,,,Artist,,Félix Teynard,"French, 1817–1892",,"Teynard, Félix",French,1817,1892,1851–52,1851,1852,Salted paper print from paper negative,25.4 x 30.8 cm (10 x 12 1/8 in. ),"Gift of Hans P. Kraus Jr. and Mariana Cooke, 1997",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/272026,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.431,false,true,307071,Photographs,Photograph,"Dandoûr, Nubie",,,,,,Artist,,Félix Teynard,"French, 1817–1892",,"Teynard, Félix",French,1817,1892,1851,1851,1851,Salted paper print from paper negative,Image: 23.9 × 31.1 cm (9 7/16 × 12 1/4 in.),"Funds from various donors, 2013",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/307071,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.60,false,true,283145,Photographs,Photograph,"Abo-Sembil, Grand Spéos, Statues Colossales vues de Face (Parte Inférieure)",,,,,,Artist,,Félix Teynard,"French, 1817–1892",,"Teynard, Félix",French,1817,1892,1851–52,1851,1852,Salted paper print from paper negative,Image: 24.3 x 30.8 cm (9 9/16 x 12 1/8 in.) Mount: 37.9 x 50.1 cm (14 15/16 x 19 3/4 in.),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/283145,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.61,false,true,283146,Photographs,Photograph,"Béni Haçan, Architecture Hypogéenne, Tombeau d'Amoneï",,,,,,Artist,,Félix Teynard,"French, 1817–1892",,"Teynard, Félix",French,1817,1892,1851–52,1851,1852,Salted paper print from paper negative,Image: 24.4 x 30.6 cm (9 5/8 x 12 1/16 in.) Mount: 40.4 x 52.2 cm (15 7/8 x 20 9/16 in.),"Gilman Collection, Purchase, William Talbott Hillman Foundation Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/283146,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.62,false,true,283147,Photographs,Photograph,"Séboûah, Temple, Colosse et Sphinx de la Partie Gauche de l'Avenue",,,,,,Artist,,Félix Teynard,"French, 1817–1892",,"Teynard, Félix",French,1817,1892,1851–52,1851,1852,Salted paper print from paper negative,Image: 24 × 31.1 cm (9 7/16 × 12 1/4 in.) Mount: 37.9 × 50.2 cm (14 15/16 × 19 3/4 in.),"Gilman Collection, Purchase, Ann Tenenbaum and Thomas H. Lee Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/283147,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.688,false,true,285952,Photographs,Photograph,"Louksor (Thèbes), Construction Postérieure - Galeries - Parallèles",,,,,,Artist,,Félix Teynard,"French, 1817–1892",,"Teynard, Félix",French,1817,1892,1851–52,1851,1852,Salted paper print from paper negative,Image: 24 x 30.2 cm (9 7/16 x 11 7/8 in.) Mount: 40.2 x 52.3 cm (15 13/16 x 20 9/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285952,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.689,false,true,286152,Photographs,Photograph,"Le Kaire, Mosquée Nâcéryeh",,,,,,Artist,,Félix Teynard,"French, 1817–1892",,"Teynard, Félix",French,1817,1892,published 1851,1851,1851,Salted paper print from paper negative,Image: 24 x 30.5 cm (9 7/16 x 12 in.) Mount: 40.3 x 52.2 cm (15 7/8 x 20 9/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286152,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.690,false,true,285955,Photographs,Photograph,"Abou Sembil, Grand Spéos - Statues Colossales, Vues de Trois-Quarts",,,,,,Artist,,Félix Teynard,"French, 1817–1892",,"Teynard, Félix",French,1817,1892,1851–52,1851,1852,Salted paper print from paper negative,Image: 30 x 24.4 cm (11 13/16 x 9 5/8 in.) Mount: 51.6 x 39.9 cm (20 5/16 x 15 11/16 in.),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285955,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.691,false,true,286150,Photographs,Photograph,"Dakkeh, Village et Rives du Nil",,,,,,Artist,,Félix Teynard,"French, 1817–1892",,"Teynard, Félix",French,1817,1892,1851–52,1851,1852,Salted paper print from paper negative,Image: 23.7 x 30.9 cm (9 5/16 x 12 3/16 in.) Mount: 40 x 51.8 cm (15 3/4 x 20 3/8 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286150,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.693,false,true,285946,Photographs,Photograph,"Louksor (Thèbes), Construction Antérieure - Pylône Colosses et Obélisque",,,,,,Artist,,Félix Teynard,"French, 1817–1892",,"Teynard, Félix",French,1817,1892,1851–52,1851,1852,Salted paper print from paper negative,Image: 24.3 x 30.4 cm (9 9/16 x 11 15/16 in.) Mount: 40.3 x 52.1 cm (15 7/8 x 20 1/2 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285946,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.694,false,true,285945,Photographs,Photograph,"Ile de Fîleh (Philæ), Vue Génèrale Prise du Point I, Sur La Plateforme du Pylône",,,,,,Artist,,Félix Teynard,"French, 1817–1892",,"Teynard, Félix",French,1817,1892,1851–52,1851,1852,Salted paper print from paper negative,Image: 24 x 30.5 cm (9 7/16 x 12 in.) Mount: 39.6 x 51.8 cm (15 9/16 x 20 3/8 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285945,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.695,false,true,285954,Photographs,Photograph,"Louksor, Petit Bras du Nil - Barque de Voyageurs",,,,,,Artist,,Félix Teynard,"French, 1817–1892",,"Teynard, Félix",French,1817,1892,1851–52,1851,1852,Salted paper print from paper negative,Image: 24.2 x 30.7 cm (9 1/2 x 12 1/16 in.) Mount: 40.5 x 52.4 cm (15 15/16 x 20 5/8 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285954,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.696,false,true,285948,Photographs,Photograph,"Abâzîz, Intérieure d'un Village Arabe",,,,,,Artist,,Félix Teynard,"French, 1817–1892",,"Teynard, Félix",French,1817,1892,1851–52,1851,1852,Salted paper print from paper negative,Image: 23.6 x 30.3 cm (9 5/16 x 11 15/16 in.) Mount: 39.7 x 51.7 cm (15 5/8 x 20 3/8 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285948,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.697,false,true,286091,Photographs,Photograph,"Ile de Fîleh (Philæ), Premier Pylône, Inscription Française Gravée Sur L'Ébrasement Oriental, En M",,,,,,Artist,,Félix Teynard,"French, 1817–1892",,"Teynard, Félix",French,1817,1892,1851–52,1851,1852,Salted paper print from paper negative,Image: 23.7 x 30.6 cm (9 5/16 x 12 1/16 in.) Mount: 39.9 x 51.7 cm (15 11/16 x 20 3/8 in.),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286091,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.925,false,true,286153,Photographs,Photograph,"Sébôuah, Vue Générale du Temple",,,,,,Artist,,Félix Teynard,"French, 1817–1892",,"Teynard, Félix",French,1817,1892,published 1851,1851,1851,Salted paper print from paper negative,Image: 23.8 x 30.6 cm (9 3/8 x 12 1/16 in.) Mount: 39.8 x 51.5 cm (15 11/16 x 20 1/4 in.),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286153,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.940,false,true,286151,Photographs,Photograph,"Louksor (Thèbes), Vue Générale des Ruines",,,,,,Artist,,Félix Teynard,"French, 1817–1892",,"Teynard, Félix",French,1817,1892,1851–52,1851,1852,Salted paper print from paper negative,Image: 23.6 x 30.5 cm (9 5/16 x 12 in.) Mount: 40.2 x 51.8 cm (15 13/16 x 20 3/8 in.),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286151,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.941,false,true,285947,Photographs,Photograph,"Esneh (Latopolis), Construction Ensablée - Architrave, Fûts et Chapiteaux",,,,,,Artist,,Félix Teynard,"French, 1817–1892",,"Teynard, Félix",French,1817,1892,1851–52,1851,1852,Salted paper print from paper negative,Image: 24.9 x 30.7 cm (9 13/16 x 12 1/16 in.) Mount: 40.1 x 51.8 cm (15 13/16 x 20 3/8 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285947,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.942,false,true,285956,Photographs,Photograph,"Ile de Fîleh (Philæ), Vu Générale Prise du Nord-Ouest au Point A",,,,,,Artist,,Félix Teynard,"French, 1817–1892",,"Teynard, Félix",French,1817,1892,1851–52,1851,1852,Salted paper print from paper negative,Image: 23.9 x 31.2 cm (9 7/16 x 12 5/16 in.) Mount: 39.8 x 51.6 cm (15 11/16 x 20 5/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285956,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.698,false,true,285640,Photographs,Photographs,Young Nuba Woman,,,,,,Artist,,Pierre Trémaux,"French, 1818–1895",,"Trémaux, Pierre",French,1818,1895,1853–54,1853,1854,Salted paper print from paper negative,Image: 10 9/16 × 8 1/4 in. (26.9 × 21 cm) Mount: 13 11/16 × 9 5/16 in. (34.7 × 23.6 cm),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285640,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1998.132,true,true,282234,Photographs,Photograph,The Refectory of the Imperial Asylum at Vincennes,,,,,,Artist,,Charles Nègre,"French, 1820–1880",,"Nègre, Charles",French,1820,1880,1858–59,1858,1859,Salted paper print from glass negative,34.2 x 42.5 cm (13 7/16 x 16 3/4 in.),"Gift of Hans P. Kraus Jr. and Mariana Cook, in honor of André and Marie-Thérèse Jammes, 1998",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/282234,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2000.286,false,true,283736,Photographs,Photograph,A Street in Grasse,,,,,,Artist,,Charles Nègre,"French, 1820–1880",,"Nègre, Charles",French,1820,1880,1852,1852,1852,Salted paper print from paper negative,32.9 x 23.8 cm (12 15/16 x 9 3/8 in. ),"Purchase, Jennifer and Joseph Duke Gift and several members of The Chairman's Council Gifts, 2000",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/283736,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1997.382.63,false,true,282074,Photographs,Photograph,Tarascon,,,,,,Artist,,Charles Nègre,"French, 1820–1880",,"Nègre, Charles",French,1820,1880,ca. 1852,1850,1854,Albumen silver print from paper negative,23.6 x 29.5 cm (9 5/16 x 11 5/8 in.),"The Rubel Collection, Purchase, Lila Acheson Wallace Gift, 1997",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/282074,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.52,false,true,283133,Photographs,Photograph,Spartan Soldier,,,,,,Artist,,Charles Nègre,"French, 1820–1880",,"Nègre, Charles",French,1820,1880,1859,1859,1859,Albumen silver print from glass negative,Image: 35.3 x 43.5 cm (13 7/8 x 17 1/8 in.),"Gilman Collection, Purchase, Harriette and Noel Levine Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/283133,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.204,false,true,285853,Photographs,Photograph,"Arles, Porte des Châtaignes",,,,,,Artist,,Charles Nègre,"French, 1820–1880",,"Nègre, Charles",French,1820,1880,1852,1850,1854,Salted paper print from a paper negative,Image: 22.1 x 32.1 cm (8 11/16 x 12 5/8 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285853,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.264,false,true,286170,Photographs,Photograph,"[Lord Brougham and his Family, Cannes]",,,,,,Artist,,Charles Nègre,"French, 1820–1880",,"Nègre, Charles",French,1820,1880,1862,1862,1862,Albumen silver print from glass negative,Image: 24.8 x 34 cm (9 3/4 x 13 3/8 in.),"Gilman Collection, Purchase, Harriette and Noel Levine Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286170,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.314,false,true,286169,Photographs,Photograph,[Still Life with Game Birds],,,,,,Artist,,Charles Nègre,"French, 1820–1880",,"Nègre, Charles",French,1820,1880,1859,1857,1861,Albumen silver print from glass negative,Image: 43.8 x 34.9 cm (17 1/4 x 13 3/4 in.),"Gilman Collection, Purchase, Jennifer and Joseph Duke Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286169,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.327,false,true,286165,Photographs,Photograph,[The 15th of August. Imperial Asylum at Vincennes],,,,,,Artist,,Charles Nègre,"French, 1820–1880",,"Nègre, Charles",French,1820,1880,1858,1857,1861,Albumen silver print from glass negative,"Image: 32.3 x 44.2 cm (12 11/16 x 17 3/8 in.), oval Mount: 47.5 x 60.5 cm (18 11/16 x 23 13/16 in.)","Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286165,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.328,false,true,286164,Photographs,Photograph,[Self-Portrait in Eastern Costume],,,,,,Artist,,Charles Nègre,"French, 1820–1880",,"Nègre, Charles",French,1820,1880,1855–60,1851,1852,Albumen silver print from glass negative,Image: 18.7 x 13.8 cm (7 3/8 x 5 7/16 in.) Mount: 38.1 x 28.9 cm (15 x 11 3/8 in.),"Gilman Collection, Purchase, Harriette and Noel Levine Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286164,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.560,false,true,286173,Photographs,Photograph,[Family Group],,,,,,Artist,,Charles Nègre,"French, 1820–1880",,"Nègre, Charles",French,1820,1880,1854,1854,1854,Salted paper print from glass negative,Mount: 7 3/8 in. × 8 9/16 in. (18.8 × 21.7 cm) Image: 5 11/16 × 7 1/16 in. (14.4 × 18 cm),"Gilman Collection, Purchase, Jennifer and Joseph Duke Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286173,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.755,false,true,286171,Photographs,Photograph,"Asile impériale de Vincennes, salle de jeu",,,,,,Artist,,Charles Nègre,"French, 1820–1880",,"Nègre, Charles",French,1820,1880,1858–59,1858,1859,Albumen silver print from glass negative,Image: 8 3/4 × 7 3/16 in. (22.3 × 18.2 cm),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286171,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.796,false,true,286167,Photographs,Photograph,"Asile Impériale de Vincennes, la pharmacie",,,,,,Artist,,Charles Nègre,"French, 1820–1880",,"Nègre, Charles",French,1820,1880,1858–59,1858,1859,Albumen silver print from glass negative,"Image: 17 x 17 cm (6 11/16 x 6 11/16 in.), circular","Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286167,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.832,false,true,285852,Photographs,Photograph,[Trees and Waterfalls],,,,,,Artist,,Charles Nègre,"French, 1820–1880",,"Nègre, Charles",French,1820,1880,1860–65,1860,1865,Albumen silver print from glass negative,Image: 13.3 x 10; Mount: 18.9 x 15.6,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285852,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2005.100.590.1a, b",false,true,286168,Photographs,Photograph,"Profil du Portail; Entrée Du Cloitre, Arles, Eglise Metropolitaine de Saint-Trophime",,,,,,Artist,,Charles Nègre,"French, 1820–1880",,"Nègre, Charles",French,1820,1880,1852,1852,1852,Salted paper prints from paper negatives,Image: 6 7/8 × 5 7/8 in. (17.5 × 15 cm) (each) Mount: 15 7/8 × 11 7/16 in. (40.3 × 29 cm),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286168,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -40.102.9,false,true,268659,Photographs,Photograph,[Rocky Hillside],,,,,,Artist,,Victor Prevost,"French, 1820–1881",,"Prevost, Victor",French,1820,1881,1850s,1850,1859,Salted paper print from paper negative,Image: 12 5/16 × 9 13/16 in. (31.2 × 24.9 cm),"Gift of John Goldsmith Phillips, 1940",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268659,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2000.13,true,true,283626,Photographs,Photograph,"[Oak Tree and Rocks, Forest of Fontainebleau]",,,,,,Artist,,Gustave Le Gray,"French, 1820–1884",,"Le Gray, Gustave",French,1820,1884,1849–52,1849,1852,Salted paper print from paper negative,25.2 x 35.7 cm (9 15/16 x 14 1/16 in.),"Purchase, Jennifer and Joseph Duke and Lila Acheson Wallace Gifts, 2000",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/283626,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.646,true,true,261941,Photographs,Photograph,"[The Great Wave, Sète]",,,,,,Artist,,Gustave Le Gray,"French, 1820–1884",,"Le Gray, Gustave",French,1820,1884,1857,1857,1857,Albumen silver print from glass negative,33.7 x 41.4 cm (13 1/4 x 16 5/16 in.),"Gift of John Goldsmith Phillips, 1976",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261941,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1996.293,false,true,271964,Photographs,Photograph,"Hotel de Cluny, Paris",,,,,,Artist,,Gustave Le Gray,"French, 1820–1884",,"Le Gray, Gustave",French,1820,1884,ca. 1851,1851,1851,Salted paper print from paper negative,32.4 x 24.2 cm (12 3/4 x 9 1/2 in. ),"Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel and Rogers Fund, 1996",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/271964,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2002.257,false,true,284986,Photographs,Photograph,[Soldier and Military Camel],,,,,,Artist,,Gustave Le Gray,"French, 1820–1884",,"Le Gray, Gustave",French,1820,1884,1866,1866,1866,Albumen silver print from glass negative,Image: 24.2 x 30.7 cm (9 1/2 x 12 1/16 in.) Mount: 37.7 x 43.4 cm (14 13/16 x 17 1/16 in.),"Purchase, Alfred Stieglitz Society Gifts, 2002",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/284986,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1987.1011,false,true,265065,Photographs,Photograph,"Tree Study, Forest of Fontainebleau",,,,,,Artist,,Gustave Le Gray,"French, 1820–1884",,"Le Gray, Gustave",French,1820,1884,ca. 1856,1854,1858,Albumen silver print from glass negative,31.8 x 41.4 cm. (12 1/2 x 16 5/16 in.),"Purchase, Joyce and Robert Menschel, The Howard Gilman Foundation, Harrison D. Horblit, Harriette and Noel Levine and Paul F. Walter Gifts and David Hunter McAlpin Fund; and Gift of Mr. and Mrs. Harry H. Lunn Jr., 1987",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/265065,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1991.1058,false,true,266351,Photographs,Photograph,Portail milieu d'Aubeterre,,,,,,Artist,,Gustave Le Gray,"French, 1820–1884",,"Le Gray, Gustave",French,1820,1884,1851,1851,1851,Salted paper print from paper negative,Image: 23.3 x 28.1 cm. (9 3/16 x 11 1/16 in.),"Edward Pearce Casey Fund, 1991",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/266351,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1996.99.1,false,true,267425,Photographs,Photograph,Mediterranean with Mount Agde,,,,,,Artist,,Gustave Le Gray,"French, 1820–1884",,"Le Gray, Gustave",French,1820,1884,1857,1857,1857,Albumen silver print from two glass negatives,31.8 x 40.9 cm. (12 1/2 x 16 1/8 in.),"Purchase, Joseph Pulitzer Bequest, 1996",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267425,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.645.1,false,true,261937,Photographs,Photograph,Brig on the Water,,,,,,Artist,,Gustave Le Gray,"French, 1820–1884",,"Le Gray, Gustave",French,1820,1884,1856,1856,1856,Albumen silver print from glass negative,32.1 x 40.5 cm (12 5/8 x 15 15/16 in. ),"Gift of A. Hyatt Mayor, 1976",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261937,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.440.1,false,true,291659,Photographs,Panorama,Pyramides de Gizèh,,,,,,Artist,,Gustave Le Gray,"French, 1820–1884",,"Le Gray, Gustave",French,1820,1884,1865–69,1865,1869,Albumen silver print from glass negative,Image: 31.1 x 41.8 cm (12 1/4 x 16 7/16 in.) Mount: 45.9 x 62.5 cm (18 1/16 x 24 5/8 in.),"Gift of Robert Shapazian, 2009",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/291659,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.440.2,false,true,291694,Photographs,Panorama,Pyramides de Gizèh,,,,,,Artist,,Gustave Le Gray,"French, 1820–1884",,"Le Gray, Gustave",French,1820,1884,1865–69,1865,1869,Albumen silver print from glass negative,Image: 31.1 x 41.8 cm (12 1/4 x 16 7/16 in.) Mount: 45.9 x 62.5 cm (18 1/16 x 24 5/8 in.),"Gift of Robert Shapazian, 2009",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/291694,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.440.3,false,true,291695,Photographs,Panorama,Pyramides de Gizèh,,,,,,Artist,,Gustave Le Gray,"French, 1820–1884",,"Le Gray, Gustave",French,1820,1884,1865–69,1865,1869,Albumen silver print from glass negative,Image: 31.1 x 41.8 cm (12 1/4 x 16 7/16 in.) Mount: 45.9 x 62.5 cm (18 1/16 x 24 5/8 in.),"Gift of Robert Shapazian, 2009",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/291695,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.46,false,true,283122,Photographs,Photograph,Chêne dans les rochers à Fontainebleau,,,,,,Artist,,Gustave Le Gray,"French, 1820–1884",,"Le Gray, Gustave",French,1820,1884,1849–52,1849,1852,Salted paper print from waxed-paper negative,Image: 25.4 x 36.2 cm (10 x 14 1/4 in.) Mount: 31.8 x 46.2 cm (12 1/2 x 18 3/16 in.),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/283122,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.47,false,true,283123,Photographs,Photograph,"Fontainebleau, chemin sablonneux montant",,,,,,Artist,,Gustave Le Gray,"French, 1820–1884",,"Le Gray, Gustave",French,1820,1884,ca. 1856,1854,1858,Albumen silver print from glass negative,Image: 29.9 x 37.7 cm (11 3/4 x 14 13/16 in.) Mount: 53.3 x 63.8 cm (21 x 25 1/8 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/283123,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.48,false,true,283124,Photographs,Photograph,Mer Méditerranée - Sète,,,,,,Artist,,Gustave Le Gray,"French, 1820–1884",,"Le Gray, Gustave",French,1820,1884,1857,1857,1857,Albumen silver print from two glass negatives,Image: 32.1 x 41.9 cm (12 5/8 x 16 1/2 in.) Mount: 52.7 × 67.3 cm (20 3/4 × 26 1/2 in.),"Gilman Collection, Purchase, Robert Rosenkranz Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/283124,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.49,false,true,283125,Photographs,Photograph,"[Cavalry Maneuvers, Camp de Châlons]",,,,,,Artist,,Gustave Le Gray,"French, 1820–1884",,"Le Gray, Gustave",French,1820,1884,1857,1857,1857,Albumen silver print from glass negative,Image: 26.7 x 33 cm (10 1/2 x 13 in.),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/283125,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.159.37,false,true,306320,Photographs,Photograph,"The French and English Fleets, Cherbourg",,,,,,Artist,,Gustave Le Gray,"French, 1820–1884",,"Le Gray, Gustave",French,1820,1884,August 1858,1858,1858,Albumen silver print from glass negative,Mount: 21 in. × 26 3/4 in. (53.3 × 68 cm) Image: 11 13/16 in. × 16 in. (30 × 40.7 cm),"Bequest of Maurice B. Sendak, 2012",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306320,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.159.38,false,true,306321,Photographs,Photograph,"Hollow Oak Tree, Fontainebleau",,,,,,Artist,,Gustave Le Gray,"French, 1820–1884",,"Le Gray, Gustave",French,1820,1884,1855–57,1855,1857,Albumen silver print from glass negative,Mount: 21 9/16 in. × 27 3/8 in. (54.8 × 69.6 cm) Image: 12 3/8 × 14 13/16 in. (31.5 × 37.7 cm),"Bequest of Maurice B. Sendak, 2012",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306321,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.190,false,true,286349,Photographs,Photograph,[View from Photographer's Studio],,,,,,Artist,,Gustave Le Gray,"French, 1820–1884",,"Le Gray, Gustave",French,1820,1884,1851–54,1851,1854,Salted paper print from paper negative,26.1 x 35.1 cm (10 1/4 x 13 13/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286349,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.191,false,true,286028,Photographs,Photographs,"Fête de S. A. Ismaïl Pacha à bord des bateaux de LL. A A. les princes, janvier 1867",,,,,,Artist,,Gustave Le Gray,"French, 1820–1884",,"Le Gray, Gustave",French,1820,1884,1867,1867,1867,Albumen print from paper negative,Image: 30.6 x 40.2 cm (12 1/16 x 15 13/16 in.) Mount: 50 x 64.8 cm (19 11/16 x 25 1/2 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286028,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.192,false,true,285473,Photographs,Photograph,Temple of Edfu,,,,,,Artist,,Gustave Le Gray,"French, 1820–1884",,"Le Gray, Gustave",French,1820,1884,1867,1867,1867,Albumen silver print from paper negative,Image: 31.4 x 41 cm (12 3/8 x 16 1/8 in.) Mount: 50.1 x 64.6 cm (19 3/4 x 25 7/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285473,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.258,false,true,285465,Photographs,Photograph,L'impératrice Eugénie en prière,,,,,,Artist,,Gustave Le Gray,"French, 1820–1884",,"Le Gray, Gustave",French,1820,1884,1856,1856,1856,Albumen silver print from a collodion glass negative,Image: 23.4 x 18.3 cm (9 3/16 x 7 3/16 in.) Mount: 40.1 × 27.5 cm (15 13/16 × 10 13/16 in.),"Gilman Collection, Purchase, Harriette and Noel Levine Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285465,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.273,false,true,285467,Photographs,Photograph,"La Reine Hortense - Yacht de l'empereur, Havre",,,,,,Artist,,Gustave Le Gray,"French, 1820–1884",,"Le Gray, Gustave",French,1820,1884,1856,1856,1856,Albumen silver print from glass negative,Image: 31.8 x 41.3 cm (12 1/2 x 16 1/4 in.) Mount: 53.1 x 66.3 cm (20 7/8 x 26 1/8 in.),"Gilman Collection, Purchase, Jennifer and Joseph Duke Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285467,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.274,false,true,285791,Photographs,Photograph,Nu féminin allongé sur un canapé Récamier,,,,,,Artist,,Gustave Le Gray,"French, 1820–1884",,"Le Gray, Gustave",French,1820,1884,ca. 1856,1854,1858,Albumen silver print from glass negative,Image: 21.7 x 32.9 cm (8 9/16 x 12 15/16 in.),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285791,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.311,false,true,285471,Photographs,Photograph,The Salon of 1852,,,,,,Artist,,Gustave Le Gray,"French, 1820–1884",,"Le Gray, Gustave",French,1820,1884,1852,1852,1852,Salted paper print from waxed-paper negative,Image: 24.1 × 38 cm (9 1/2 × 14 15/16 in.) Mount: 37.5 × 40.9 cm (14 3/4 × 16 1/8 in.),"Gilman Collection, Purchase, Mrs. Walter Annenberg and The Annenberg Foundation Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285471,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.612,false,true,283126,Photographs,Photograph,Scène près d'un étang au moulin du Petit-Mourmelon,,,,,,Artist,,Gustave Le Gray,"French, 1820–1884",,"Le Gray, Gustave",French,1820,1884,1857,1857,1857,Albumen silver print from glass negative,Mount: 18 9/16 × 24 9/16 in. (47.2 × 62.4 cm) Image: 11 in. × 13 7/8 in. (27.9 × 35.2 cm),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/283126,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.628,false,true,285461,Photographs,Photograph,"[View of the Seine, Paris]",,,,,,Artist,,Gustave Le Gray,"French, 1820–1884",,"Le Gray, Gustave",French,1820,1884,1857,1857,1857,Albumen silver print from glass negative,Image: 38.5 x 50.9; Mount: 49.5 x 60.7,"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285461,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.678,false,true,286160,Photographs,Photograph,Portrait de Pitre-Chevalier,,,,,,Artist,,Gustave Le Gray,"French, 1820–1884",,"Le Gray, Gustave",French,1820,1884,ca. 1853,1851,1855,Salted paper print from glass negative,"Image: 17.5 x 13.4 cm (6 7/8 x 5 1/4 in.), irregularly trimmed Mount: 17.5 x 13.4 cm (6 7/8 x 5 1/4 in.), irregularly trimmed","Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286160,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.679,false,true,285792,Photographs,Photograph,Vue de la Plaine de Thèbes prise du temple de Karnac,,,,,,Artist,,Gustave Le Gray,"French, 1820–1884",,"Le Gray, Gustave",French,1820,1884,1867,1867,1867,Albumen silver print from paper negative,Image: 32 × 41.5 cm (12 5/8 × 16 5/16 in.) Mount: 19 13/16 × 25 9/16 in. (50.3 × 65 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285792,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.680,false,true,286350,Photographs,Photograph,[An Italian Street Musician],,,,,,Artist,,Gustave Le Gray,"French, 1820–1884",,"Le Gray, Gustave",French,1820,1884,ca. 1856,1854,1858,Albumen silver print from glass negative,"Image: 34 x 26.4 cm (13 3/8 x 10 3/8 in.), partially obscured by overmat Mat: 49.6 x 38.1 cm (19 1/2 x 15 in.)","Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286350,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.850,false,true,286196,Photographs,Photograph,Portrait de Louis-Napoléon Bonaparte en Prince-Président,,,,,,Artist,,Gustave Le Gray,"French, 1820–1884",,"Le Gray, Gustave",French,1820,1884,1852,1852,1852,Albumen silver print from paper negative,Image: 20.2 x 14.7 cm (7 15/16 x 5 13/16 in.) Mount: 48.1 x 38.6 cm (18 15/16 x 15 3/16 in.),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286196,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.964,false,true,286195,Photographs,Photographs,La Messe au Camp de Châlons,,,,,,Artist,,Gustave Le Gray,"French, 1820–1884",,"Le Gray, Gustave",French,1820,1884,1857,1857,1857,Albumen silver print from glass negative,28.6 x 35.8 cm (11 1/4 x 14 1/8 in.),"Gilman Collection, Purchase, Mrs. Walter Annenberg and The Annenberg Foundation Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286195,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.965,false,true,285628,Photographs,Photographs,"[Zouaves, Camp de Châlons]",,,,,,Artist,,Gustave Le Gray,"French, 1820–1884",,"Le Gray, Gustave",French,1820,1884,1857,1857,1857,Albumen silver print from glass negative,Image: 27.9 x 35.6 cm (11 x 14 in.),"Gilman Collection, Purchase, Mrs. Walter Annenberg and The Annenberg Foundation Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285628,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.181,false,true,261362,Photographs,Photograph,[La Comtesse in Lace Shawl],,,,,,Artist,,Alphonse (Jean-Baptiste) Bernoud,"French, 1820–1889",,"Bernoud, Alphonse",French,1820,1889,1860s,1860,1869,Albumen silver print from glass negative,5.1 x 8.6 cm (2 x 3 3/8 in.),"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261362,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.503.1,false,true,289455,Photographs,Photograph,Vue du château de Pau,,,,,,Artist,,Joseph Vigier,"French, 1821–1862",,"Vigier, Joseph",French,1821,1862,1853,1853,1853,Salted paper print from paper negative,Sheet: 12 in. × 18 9/16 in. (30.5 × 47.2 cm) Image: 9 3/4 × 13 3/4 in. (24.7 × 35 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/289455,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.503.2,false,true,289456,Photographs,Photograph,Route de Pierrefitte à Luz St Sauveur,,,,,,Artist,,Joseph Vigier,"French, 1821–1862",,"Vigier, Joseph",French,1821,1862,1853,1853,1853,Salted paper print from paper negative,Sheet: 12 in. × 18 9/16 in. (30.5 × 47.2 cm) Image: 9 3/4 × 13 3/4 in. (24.7 × 35 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/289456,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.503.3,false,true,289457,Photographs,Photograph,Vallé de Lur prise du chemin de Sasie à St-Sauveur,,,,,,Artist,,Joseph Vigier,"French, 1821–1862",,"Vigier, Joseph",French,1821,1862,1850,1850,1850,Salted paper print from paper negative,Sheet: 12 in. × 18 9/16 in. (30.5 × 47.2 cm) Image: 10 1/4 × 14 7/16 in. (26 × 36.7 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/289457,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.503.4,false,true,289458,Photographs,Photograph,Vue des Eaux de Saint-Sauveur,,,,,,Artist,,Joseph Vigier,"French, 1821–1862",,"Vigier, Joseph",French,1821,1862,1853,1853,1853,Salted paper print from paper negative,Sheet: 12 in. × 18 9/16 in. (30.5 × 47.2 cm) Image: 10 13/16 × 14 5/8 in. (27.4 × 37.1 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/289458,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.503.5,false,true,289459,Photographs,Photograph,Vallée d'Argelès près de la ferme de Despourreins. St-Sauveur,,,,,,Artist,,Joseph Vigier,"French, 1821–1862",,"Vigier, Joseph",French,1821,1862,1853,1853,1853,Salted paper print from paper negative,Sheet: 12 in. × 18 9/16 in. (30.5 × 47.2 cm) Image: 10 9/16 × 14 1/16 in. (26.9 × 35.7 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/289459,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.503.6,false,true,289460,Photographs,Photograph,Pont de Sia Route de Gavarnie St Sauveur,,,,,,Artist,,Joseph Vigier,"French, 1821–1862",,"Vigier, Joseph",French,1821,1862,1853,1853,1853,Salted paper print from paper negative,Sheet: 12 in. × 18 9/16 in. (30.5 × 47.2 cm) Image: 14 1/16 × 10 9/16 in. (35.7 × 26.8 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/289460,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.503.7,false,true,289461,Photographs,Photograph,"Le chaos en allant à Gavarnie, St-Sauveur",,,,,,Artist,,Joseph Vigier,"French, 1821–1862",,"Vigier, Joseph",French,1821,1862,1853,1853,1853,Salted paper print from paper negative,Image: 10 7/8 × 14 9/16 in. (27.6 × 37 cm) Sheet: 12 in. × 18 9/16 in. (30.5 × 47.2 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/289461,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.503.8,false,true,283111,Photographs,Photograph,"Sentier du chaos, St-Sauveur",,,,,,Artist,,Joseph Vigier,"French, 1821–1862",,"Vigier, Joseph",French,1821,1862,1853,1853,1853,Salted paper print from paper negative,Image: 9 13/16 × 14 1/2 in. (24.9 × 36.9 cm) Sheet: 12 in. × 18 9/16 in. (30.5 × 47.2 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/283111,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.503.9,false,true,289462,Photographs,Photograph,"Blocs dans le chaos, St-Sauveur",,,,,,Artist,,Joseph Vigier,"French, 1821–1862",,"Vigier, Joseph",French,1821,1862,1853,1853,1853,Salted paper print from paper negative,Image: 10 1/16 × 13 9/16 in. (25.5 × 34.4 cm) Sheet: 12 in. × 18 9/16 in. (30.5 × 47.2 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/289462,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.503.10,false,true,289463,Photographs,Photograph,"Village de Gèdres, Route de Gavarnie",,,,,,Artist,,Joseph Vigier,"French, 1821–1862",,"Vigier, Joseph",French,1821,1862,1853,1853,1853,Salted paper print from paper negative,Sheet: 12 in. × 18 9/16 in. (30.5 × 47.2 cm) Image: 9 15/16 × 14 5/16 in. (25.3 × 36.3 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/289463,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.503.11,false,true,289464,Photographs,Photograph,"Cirque de Gavarnie, St Sauveur",,,,,,Artist,,Joseph Vigier,"French, 1821–1862",,"Vigier, Joseph",French,1821,1862,1853,1853,1853,Salted paper print from paper negative,Image: 9 9/16 × 14 9/16 in. (24.3 × 37 cm) Sheet: 12 in. × 18 9/16 in. (30.5 × 47.2 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/289464,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.503.12,false,true,289465,Photographs,Photograph,"Gorge d'Estaubé prise des gloriettes d'Héas, St-Sauveur",,,,,,Artist,,Joseph Vigier,"French, 1821–1862",,"Vigier, Joseph",French,1821,1862,1853,1853,1853,Salted paper print from paper negative,Image: 9 5/8 × 14 5/16 in. (24.5 × 36.4 cm) Sheet: 12 in. × 18 9/16 in. (30.5 × 47.2 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/289465,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.503.13,false,true,289466,Photographs,Photograph,"Vallée et chapelle d'Héas, St-Sauveur",,,,,,Artist,,Joseph Vigier,"French, 1821–1862",,"Vigier, Joseph",French,1821,1862,1853,1853,1853,Salted paper print from paper negative,Image: 9 1/2 × 14 1/2 in. (24.1 × 36.8 cm) Sheet: 12 in. × 18 9/16 in. (30.5 × 47.2 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/289466,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.503.14,false,true,289467,Photographs,Photograph,"Cauterets, Pont d'Espagne",,,,,,Artist,,Joseph Vigier,"French, 1821–1862",,"Vigier, Joseph",French,1821,1862,1853,1853,1853,Salted paper print from paper negative,Image: 14 5/16 × 9 1/2 in. (36.4 × 24.2 cm) Sheet: 12 in. × 18 9/16 in. (30.5 × 47.2 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/289467,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.503.15,false,true,289468,Photographs,Photograph,"Lac de Gaube, Cauterets",,,,,,Artist,,Joseph Vigier,"French, 1821–1862",,"Vigier, Joseph",French,1821,1862,1853,1853,1853,Salted paper print from paper negative,Image: 9 9/16 × 14 5/16 in. (24.3 × 36.3 cm) Sheet: 12 in. × 18 9/16 in. (30.5 × 47.2 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/289468,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.503.16,false,true,289469,Photographs,Photograph,Vue de la Vallée de Luchon prise de la tour de Castelvieilh,,,,,,Artist,,Joseph Vigier,"French, 1821–1862",,"Vigier, Joseph",French,1821,1862,1853,1853,1853,Salted paper print from paper negative,Image: 9 5/8 × 14 5/16 in. (24.5 × 36.4 cm) Sheet: 12 in. × 18 9/16 in. (30.5 × 47.2 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/289469,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.503.17,false,true,289470,Photographs,Photograph,Bains de la Raillière à Cauterets,,,,,,Artist,,Joseph Vigier,"French, 1821–1862",,"Vigier, Joseph",French,1821,1862,1853,1853,1853,Salted paper print from paper negative,Image: 9 1/2 × 14 7/16 in. (24.1 × 36.7 cm) Sheet: 12 in. × 18 9/16 in. (30.5 × 47.2 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/289470,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.503.18,false,true,289471,Photographs,Photograph,"Gorge d'Astos, prise en revenant du lac d'Oo, Luchon",,,,,,Artist,,Joseph Vigier,"French, 1821–1862",,"Vigier, Joseph",French,1821,1862,1853,1853,1853,Salted paper print from paper negative,Image: 9 3/4 × 14 5/16 in. (24.7 × 36.3 cm) Sheet: 12 in. × 18 9/16 in. (30.5 × 47.2 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/289471,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.503.19,false,true,289472,Photographs,Photograph,Vallée de Bosost prise de la capilla San Antonio,,,,,,Artist,,Joseph Vigier,"French, 1821–1862",,"Vigier, Joseph",French,1821,1862,1853,1853,1853,Salted paper print from paper negative,Image: 9 5/8 × 14 9/16 in. (24.4 × 37 cm) Sheet: 12 in. × 18 9/16 in. (30.5 × 47.2 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/289472,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.503.20,false,true,289473,Photographs,Photograph,"Pont de l'ardoise pris en revenant de la cascade des Parisiens, Luchon",,,,,,Artist,,Joseph Vigier,"French, 1821–1862",,"Vigier, Joseph",French,1821,1862,1853,1853,1853,Salted paper print from paper negative,Image: 9 5/8 × 13 7/8 in. (24.4 × 35.3 cm) Sheet: 12 in. × 18 9/16 in. (30.5 × 47.2 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/289473,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.503.21,false,true,289474,Photographs,Photograph,Village de Montaubant pris de Luchon,,,,,,Artist,,Joseph Vigier,"French, 1821–1862",,"Vigier, Joseph",French,1821,1862,1853,1853,1853,Salted paper print from paper negative,Image: 9 3/4 × 13 3/8 in. (24.8 × 34 cm) Sheet: 12 in. × 18 9/16 in. (30.5 × 47.2 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/289474,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.503.22,false,true,289475,Photographs,Photograph,"Torrent de la cascade des Demoiselles, Bagnères de Luchon.",,,,,,Artist,,Joseph Vigier,"French, 1821–1862",,"Vigier, Joseph",French,1821,1862,1853,1853,1853,Salted paper print from paper negative,Image: 14 1/8 × 9 13/16 in. (35.8 × 25 cm) Sheet: 12 in. × 18 9/16 in. (30.5 × 47.2 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/289475,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.503.23,false,true,289476,Photographs,Photograph,"Vue du lac d'Oo ou Seculejo, Bagnère de Luchon",,,,,,Artist,,Joseph Vigier,"French, 1821–1862",,"Vigier, Joseph",French,1821,1862,1853,1853,1853,Salted paper print from paper negative,Image: 10 1/8 × 14 1/16 in. (25.7 × 35.7 cm) Sheet: 12 in. × 18 9/16 in. (30.5 × 47.2 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/289476,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.503.24,false,true,289477,Photographs,Photograph,Vue de la partie de la Maladetta et des montagnes du haut du port de Vénasque,,,,,,Artist,,Joseph Vigier,"French, 1821–1862",,"Vigier, Joseph",French,1821,1862,1853,1853,1853,Salted paper print from paper negative,Image: 9 5/8 × 14 3/16 in. (24.4 × 36 cm) Sheet: 12 in. × 18 9/16 in. (30.5 × 47.2 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/289477,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.1,false,true,287066,Photographs,Photograph,"Vue générale du Kaire, prise de la Mosquée Tegloun",,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,December 1849–January 1850,1849,1850,Salted paper print from paper negative,Mount: 47 x 32 cm (18 1/2 x 12 5/8 in.) Image: 6 1/8 × 5 3/8 in. (15.5 × 13.6 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287066,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.2,false,true,287067,Photographs,Photograph,"Vue prise du quartier Franc, au Kaire",,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,December 1849–January 1850,1849,1850,Salted paper print from paper negative,Image: 5 9/16 × 8 1/4 in. (14.2 × 21 cm) Mount: 12 3/16 × 18 11/16 in. (31 × 47.5 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287067,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.3,false,true,287068,Photographs,Photograph,"Vue prise d'un Jardin du quartier Franc, au Kaire",,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,December 1849–January 1850,1849,1850,Salted paper print from paper negative,Image: 6 in. × 8 7/8 in. (15.3 × 22.5 cm) Mount: 12 5/16 × 18 11/16 in. (31.2 × 47.5 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287068,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.5,false,true,287069,Photographs,Photograph,"Dattiers et Maison du quartier Franc, au Kaire",,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,December 1849–January 1850,1849,1850,Salted paper print from paper negative,Image: 8 11/16 × 6 5/16 in. (22 × 16 cm) Mount: 12 5/16 × 18 11/16 in. (31.2 × 47.5 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287069,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.6,false,true,287070,Photographs,Photograph,"Minaret occidental de la Mosqée du Khalif Hakem, au Kaire",,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,"January 9, 1850",1850,1850,Salted paper print from paper negative,Image: 8 7/8 × 6 1/8 in. (22.5 × 15.5 cm) Mount: 12 5/16 × 18 11/16 in. (31.2 × 47.5 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287070,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.7,false,true,287071,Photographs,Photograph,"Minaret oriental de la Mosquée du Khalif Hakem, au Kaire",,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,"December 27, 1849",1849,1849,Salted paper print from paper negative,Image: 8 7/16 × 6 1/8 in. (21.5 × 15.5 cm) Mount: 12 5/16 × 18 11/16 in. (31.2 × 47.5 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287071,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.8,false,true,287072,Photographs,Photograph,"Sibyl ou Fontaine et Ecole de Souk-el-asr, au Kaire",,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,December 1849–January 1850,1849,1850,Salted paper print from paper negative,Image: 8 3/8 × 6 1/4 in. (21.2 × 15.8 cm) Mount: 12 5/16 × 18 11/16 in. (31.2 × 47.5 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287072,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.9,false,true,287073,Photographs,Photograph,"Minaret penché de la Mosquée de Bibars, au Kaire",,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,December 1849–January 1850,1849,1850,Salted paper print from paper negative,Image: 8 15/16 × 6 1/4 in. (22.7 × 15.8 cm) Mount: 12 5/16 × 18 11/16 in. (31.2 × 47.5 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287073,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.10,false,true,287074,Photographs,Photograph,"Vue d'une Mosquée ruinée près de Bab-Saïda, au Kaire",,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,December 1849–January 1850,1849,1850,Salted paper print from paper negative,Image: 6 1/4 × 8 9/16 in. (15.8 × 21.8 cm) Mount: 12 5/16 × 18 11/16 in. (31.2 × 47.5 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287074,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.11,false,true,287075,Photographs,Photograph,"Vue générale de la Mosquée et du Tombeau de Sultan Bezkouk, El-Melek-el-Dâher, au Kaire",,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,December 1849–January 1850,1849,1850,Salted paper print from paper negative,Image: 6 1/8 × 8 5/16 in. (15.6 × 21.1 cm) Mount: 12 5/16 × 18 11/16 in. (31.2 × 47.5 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287075,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.12,false,true,287076,Photographs,Photograph,"Entrée du Tombeau de Sultan Bezkouk, au Kaire",,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,December 1849–January 1850,1849,1850,Salted paper print from paper negative,Image: 8 3/4 × 6 5/16 in. (22.3 × 16 cm) Mount: 12 5/16 × 18 11/16 in. (31.2 × 47.5 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287076,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.13,false,true,287077,Photographs,Photograph,"Tombeau du Sultan Kaït-Bay, au Kaire",,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,December 1849–January 1850,1849,1850,Salted paper print from paper negative,Image: 6 in. × 8 9/16 in. (15.3 × 21.8 cm) Mount: 12 5/16 × 18 11/16 in. (31.2 × 47.5 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287077,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.14,false,true,287078,Photographs,Photograph,"Tombeau du Sultan El-Goury, au Kaire",,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,December 1849–January 1850,1849,1850,Salted paper print from paper negative,Image: 6 9/16 × 8 11/16 in. (16.6 × 22 cm) Mount: 12 5/16 × 18 11/16 in. (31.2 × 47.5 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287078,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.15,false,true,287079,Photographs,Photograph,"Mosquée et Tombeau des Ayoubites, au Kaire",,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,December 1849–January 1850,1849,1850,Salted paper print from paper negative,Image: 6 in. × 8 9/16 in. (15.3 × 21.8 cm) Mount: 12 5/16 × 18 11/16 in. (31.2 × 47.5 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287079,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.16,false,true,287080,Photographs,Photograph,"Tombeau des Sultans Mamelouks, au Kaire",,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,December 1849–January 1850,1849,1850,Salted paper print from paper negative,Image: 6 3/8 × 8 9/16 in. (16.2 × 21.8 cm) Mount: 12 5/16 × 18 11/16 in. (31.2 × 47.5 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287080,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.17,false,true,287081,Photographs,Photograph,Armes et ustensiles du Kaire,,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,December 1849–January 1850,1849,1850,Salted paper print from paper negative,Image: 8 7/8 × 6 5/16 in. (22.5 × 16 cm) Mount: 12 5/16 × 18 11/16 in. (31.2 × 47.5 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287081,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.18,false,true,287082,Photographs,Photograph,Vue de la grande pyramide (Chéops) prise à l'angle S.E.,,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,December 1849,1849,1849,Salted paper print from paper negative,Image: 6 3/8 × 8 9/16 in. (16.2 × 21.8 cm) Mount: 12 5/16 × 18 11/16 in. (31.2 × 47.5 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287082,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.19,false,true,287083,Photographs,Photograph,"Vue de la seconde Pyramide, prise au Sud-Est",,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,December 1849,1849,1849,Salted paper print from paper negative,Image: 5 7/8 × 8 9/16 in. (15 × 21.7 cm) Mount: 12 5/16 × 18 11/16 in. (31.2 × 47.5 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287083,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.20,false,true,287084,Photographs,Photograph,Vue du grand Sphinx et de la grande pyramide de Menkazeh (Mycerinus),,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,December 1849,1849,1849,Salted paper print from paper negative,Image: 6 3/8 × 8 7/16 in. (16.2 × 21.4 cm) Mount: 12 5/16 × 18 11/16 in. (31.2 × 47.5 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287084,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.21,false,true,287085,Photographs,Photograph,"Profile du grande Sphinx, pris du Sud",,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,December 1849,1849,1849,Salted paper print from paper negative,Image: 6 1/8 × 8 11/16 in. (15.6 × 22 cm) Mount: 12 5/16 × 18 11/16 in. (31.2 × 47.5 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287085,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.22,false,true,287086,Photographs,Photograph,Vue prise à Béni-Souef,,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,1850,1850,1850,Salted paper print from paper negative,Image: 5 3/4 × 8 7/8 in. (14.6 × 22.6 cm) Mount: 12 5/16 × 18 11/16 in. (31.2 × 47.5 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287086,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.23,false,true,287087,Photographs,Photograph,Vue du Village de Garara,,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,1850,1850,1850,Salted paper print from paper negative,Image: 6 1/8 × 8 7/16 in. (15.6 × 21.5 cm) Mount: 12 5/16 × 18 11/16 in. (31.2 × 47.5 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287087,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.24,false,true,287088,Photographs,Photograph,"Tombeau de Sidi-Ambarek, à Garara",,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,1850,1850,1850,Salted paper print from paper negative,Image: 5 1/2 × 8 3/8 in. (14 × 21.3 cm) Mount: 12 5/16 × 18 11/16 in. (31.2 × 47.5 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287088,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.25,false,true,287089,Photographs,Photograph,Vue de Djebel-el-teir et du Convent de la Poulie,,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,1850,1850,1850,Salted paper print from paper negative,Image: 5 5/8 × 8 3/8 in. (14.3 × 21.3 cm) Mount: 12 5/16 × 18 11/16 in. (31.2 × 47.5 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287089,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.26,false,true,287090,Photographs,Photograph,Vue de Syout - Palais du Pacha,,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,1850,1850,1850,Salted paper print from paper negative,Image: 5 7/16 × 8 9/16 in. (13.8 × 21.7 cm) Mount: 12 5/16 × 18 11/16 in. (31.2 × 47.5 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287090,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.27,false,true,287091,Photographs,Photograph,"Vue du Divan et du Palais du Gouverneur, à Syout",,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,1849–50,1849,1850,Salted paper print from paper negative,Image: 5 9/16 × 8 1/2 in. (14.2 × 21.6 cm) Mount: 12 5/16 × 18 11/16 in. (31.2 × 47.5 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287091,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.28,false,true,287092,Photographs,Photograph,Vue générale du Cimetière de Siout,,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,1849–50,1849,1850,Salted paper print from paper negative,Image: 6 9/16 × 8 9/16 in. (16.7 × 21.7 cm) Mount: 12 5/16 × 18 11/16 in. (31.2 × 47.5 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287092,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.29,false,true,287093,Photographs,Photograph,Tombeaux Musulmans à Siout,,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,1849–50,1849,1850,Salted paper print from paper negative,Image: 6 5/16 × 8 9/16 in. (16 × 21.7 cm) Mount: 12 5/16 × 18 11/16 in. (31.2 × 47.5 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287093,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.30,false,true,287094,Photographs,Photograph,"Ancienne Nécropole de Lycopolis, à Syout",,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,1849–50,1849,1850,Salted paper print from paper negative,Image: 6 5/16 × 8 9/16 in. (16 × 21.7 cm) Mount: 12 5/16 × 18 11/16 in. (31.2 × 47.5 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287094,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.31,false,true,287095,Photographs,Photograph,"Mosquée d'El-Arif et Tombeau de Mourad-Bey, à Souhadj",,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,1849–50,1849,1850,Salted paper print from paper negative,Image: 5 1/8 in. × 8 in. (13 × 20.3 cm) Mount: 12 5/16 × 18 11/16 in. (31.2 × 47.5 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287095,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.32,false,true,287096,Photographs,Photograph,Vue de Girgeh et du littoral enlevé - par l'inondation du Nil,,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,1849–50,1849,1850,Salted paper print from paper negative,Image: 5 5/8 × 8 3/16 in. (14.3 × 20.8 cm) Mount: 12 5/16 × 18 11/16 in. (31.2 × 47.5 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287096,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.33,false,true,287097,Photographs,Photograph,"Mosquée d'Ali-Bey, à Girgeh",,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,1849–50,1849,1850,Salted paper print from paper negative,Image: 6 7/16 × 8 3/4 in. (16.3 × 22.3 cm) Mount: 12 5/16 × 18 11/16 in. (31.2 × 47.5 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287097,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.34,false,true,287098,Photographs,Photograph,Mosquée de Haou (Diospolis parva),,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,1849–50,1849,1850,Salted paper print from paper negative,Image: 5 11/16 × 7 7/8 in. (14.5 × 20 cm) Mount: 12 5/16 × 18 11/16 in. (31.2 × 47.5 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287098,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.35,false,true,287099,Photographs,Photograph,"Vue du Village de Hamarneh, près de Dendérah (Rive droite)",,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,1849–50,1849,1850,Salted paper print from paper negative,Image: 5 13/16 × 8 9/16 in. (14.7 × 21.7 cm) Mount: 12 5/16 × 18 11/16 in. (31.2 × 47.5 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287099,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.36,false,true,287100,Photographs,Photograph,"Bois de Dattiers et de Doums, à Hamarneh",,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,1849–50,1849,1850,Salted paper print from paper negative,Image: 6 3/8 × 8 3/8 in. (16.2 × 21.2 cm) Mount: 12 5/16 × 18 11/16 in. (31.2 × 47.5 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287100,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.37,false,true,287101,Photographs,Photograph,Palmiers Doums à Hamarneh,,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,1849–50,1849,1850,Salted paper print from paper negative,Image: 6 5/8 × 8 3/8 in. (16.9 × 21.2 cm) Mount: 12 5/16 × 18 11/16 in. (31.2 × 47.5 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287101,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.38,false,true,287102,Photographs,Photograph,Façade du Temple d'Athor à Dendérah (Tentyris),,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,1849–50,1849,1850,Salted paper print from paper negative,Image: 6 7/16 × 8 3/8 in. (16.4 × 21.2 cm) Mount: 12 5/16 × 18 11/16 in. (31.2 × 47.5 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287102,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.39,false,true,287103,Photographs,Photograph,Façade postérieure de grande Temple de Dendérah (Tentyris),,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,1849–50,1849,1850,Salted paper print from paper negative,Image: 6 7/16 × 8 3/8 in. (16.4 × 21.2 cm) Mount: 12 5/16 × 18 11/16 in. (31.2 × 47.5 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287103,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.40,false,true,287104,Photographs,Photograph,"Hypètre d'Athor, sur la Terrasse du grande Temple de Dendérah (Tentyris)",,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,1849–50,1849,1850,Salted paper print from paper negative,Image: 6 7/16 × 8 3/8 in. (16.4 × 21.2 cm) Mount: 12 5/16 × 18 11/16 in. (31.2 × 47.5 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287104,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.41,false,true,287105,Photographs,Photograph,Bas-reliefs de la façade du Temple de Dendérah (Tentyris),,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,1849–50,1849,1850,Salted paper print from paper negative,Image: 6 1/8 × 8 9/16 in. (15.5 × 21.8 cm) Mount: 12 5/16 × 18 11/16 in. (31.2 × 47.5 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287105,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.42,false,true,287106,Photographs,Photograph,Sculptures sur la façade postérieure du grande Temple de Dendérah (Tentyris),,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,1849–50,1849,1850,Salted paper print from paper negative,Image: 6 1/8 × 8 9/16 in. (15.5 × 21.8 cm) Mount: 12 5/16 × 18 11/16 in. (31.2 × 47.5 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287106,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.43,false,true,287107,Photographs,Photograph,"Vue générale des Ruines de Louxor, prise de l'Ouest (Thèbes)",,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,1849–50,1849,1850,Salted paper print from paper negative,Image: 5 1/2 × 8 7/16 in. (14 × 21.5 cm) Mount: 12 5/16 × 18 11/16 in. (31.2 × 47.5 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287107,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.44,false,true,287108,Photographs,Photograph,"Habitation de l'équipage de l'allège de Luxor, bâtie sur la terrasse du Palais",,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,1849–50,1849,1850,Salted paper print from paper negative,Image: 5 13/16 × 8 9/16 in. (14.7 × 21.7 cm) Mount: 12 5/16 × 18 11/16 in. (31.2 × 47.5 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287108,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.45,false,true,287109,Photographs,Photograph,"Pigeonniers bâtis sur la colonnade méridionale du palais d'Aménophis III, à Louxor, Thèbes",,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,1849–50,1849,1850,Salted paper print from paper negative,Image: 5 7/8 × 8 1/2 in. (15 × 21.6 cm) Mount: 12 5/16 × 18 11/16 in. (31.2 × 47.5 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287109,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.46,false,true,287110,Photographs,Photograph,"Vue d'une partie du village de Louxor, Thèbes",,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,1849–50,1849,1850,Salted paper print from paper negative,Image: 5 5/8 × 8 1/16 in. (14.3 × 20.4 cm) Mount: 12 5/16 × 18 11/16 in. (31.2 × 47.5 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287110,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.47,false,true,287111,Photographs,Photograph,"Grande Colonnade du Palais d'Aménophis III, à Luxor, Thèbes",,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,1849–50,1849,1850,Salted paper print from paper negative,Image: 6 9/16 × 8 1/2 in. (16.6 × 21.6 cm) Mount: 12 5/16 × 18 11/16 in. (31.2 × 47.5 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287111,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.48,false,true,287112,Photographs,Photograph,"Palais et Village de Louxor, pris du Sud, Thèbes",,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,1849–50,1849,1850,Salted paper print from paper negative,Image: 6 5/16 × 8 3/8 in. (16 × 21.2 cm) Mount: 12 5/16 × 18 11/16 in. (31.2 × 47.5 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287112,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.49,false,true,287113,Photographs,Photograph,"Groupe de colonnes du Palais de Louxor, Thèbes",,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,1849–50,1849,1850,Salted paper print from paper negative,Image: 8 7/16 × 6 9/16 in. (21.4 × 16.6 cm) Mount: 12 5/16 × 18 11/16 in. (31.2 × 47.5 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287113,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.50,false,true,287114,Photographs,Photograph,"Propylone du Temple de Khons, à Karnac, Thèbes",,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,1849–50,1849,1850,Salted paper print from paper negative,Image: 7 11/16 × 6 5/16 in. (19.5 × 16 cm) Mount: 12 5/16 × 18 11/16 in. (31.2 × 47.5 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287114,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.51,false,true,287115,Photographs,Photograph,"Intérieur du Temple de Khons, à Karnac, Thèbes",,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,1849–50,1849,1850,Salted paper print from paper negative,Image: 8 1/4 × 6 1/2 in. (21 × 16.5 cm) Mount: 12 5/16 × 18 11/16 in. (31.2 × 47.5 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287115,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.52,false,true,287116,Photographs,Photograph,"Vue générale du Temple de Khons, à Karnac, Thèbes",,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,1849–50,1849,1850,Salted paper print from paper negative,Image: 6 5/16 × 8 1/4 in. (16 × 21 cm) Mount: 12 5/16 × 18 11/16 in. (31.2 × 47.5 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287116,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.53,false,true,287117,Photographs,Photograph,Vue des Pylones du Temple Khons et d'une partie du village de Karnac,,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,1849–50,1849,1850,Salted paper print from paper negative,Image: 5 1/4 × 8 1/8 in. (13.4 × 20.6 cm) Mount: 12 5/16 × 18 11/16 in. (31.2 × 47.5 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287117,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.54,false,true,287118,Photographs,Photograph,"Vue des propylées du palais de Karnac, prise du Sud-Est",,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,1849–50,1849,1850,Salted paper print from paper negative,Image: 5 1/4 × 8 1/8 in. (13.4 × 20.6 cm) Mount: 12 5/16 × 18 11/16 in. (31.2 × 47.5 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287118,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.55,false,true,287119,Photographs,Photograph,"Vue générale des ruines du Palais de Karnac, prise du Nord",,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,1849–50,1849,1850,Salted paper print from paper negative,Image: 6 in. × 8 3/8 in. (15.3 × 21.2 cm) Mount: 12 5/16 × 18 11/16 in. (31.2 × 47.5 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287119,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.56,false,true,287120,Photographs,Photograph,"Grand Pylone du Palais de Karnac, Thèbes",,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,1849–50,1849,1850,Salted paper print from paper negative,Image: 5 7/8 × 8 1/8 in. (14.9 × 20.7 cm) Mount: 12 5/16 × 18 11/16 in. (31.2 × 47.5 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287120,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.57,false,true,287121,Photographs,Photograph,"Cour des bubastites et entrée de la Salle Hypostyle du palais de Karnac, Thèbes",,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,1849–50,1849,1850,Salted paper print from paper negative,Image: 8 7/16 × 6 9/16 in. (21.5 × 16.6 cm) Mount: 18 11/16 × 12 5/16 in. (47.5 × 31.2 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287121,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.58,false,true,287122,Photographs,Photograph,Ruines de la Salle Hypostyle du Palais de Karnac - Vue prise du Sud-Ouest,,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,1849–50,1849,1850,Salted paper print from paper negative,Image: 5 3/16 × 7 15/16 in. (13.1 × 20.2 cm) Mount: 12 5/16 × 18 11/16 in. (31.2 × 47.5 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287122,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.59,false,true,287123,Photographs,Photograph,"Vue de la Salle Hypostyle du palais de Karnac, prise sur l'angle N.E.",,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,1849–50,1849,1850,Salted paper print from paper negative,Image: 6 1/8 × 8 1/4 in. (15.5 × 21 cm) Mount: 12 5/16 × 18 11/16 in. (31.2 × 47.5 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287123,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.60,false,true,287124,Photographs,Photograph,Porte Septentrionale de la Salle Hypostyle du Palais de Karnac,,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,1849–50,1849,1850,Salted paper print from paper negative,Image: 6 1/8 × 8 1/4 in. (15.5 × 21 cm) Mount: 12 5/16 × 18 11/16 in. (31.2 × 47.5 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287124,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.61,false,true,287125,Photographs,Photograph,Porte méridionale de la Salle Hypostyle du Palais de Karnac (Thèbes),,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,1849–50,1849,1850,Salted paper print from paper negative,Image: 6 in. × 8 3/8 in. (15.3 × 21.3 cm) Mount: 12 5/16 × 18 11/16 in. (31.2 × 47.5 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287125,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.62,false,true,287126,Photographs,Photograph,Obélisques du Palais de Karnac (Thèbes),,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,1849–50,1849,1850,Salted paper print from paper negative,Image: 7 11/16 × 6 5/8 in. (19.5 × 16.8 cm) Mount: 12 5/16 × 18 11/16 in. (31.2 × 47.5 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287126,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.63,false,true,287127,Photographs,Photograph,Pilier du Sanctuaire de granit du Palais de Karnac (Bas-relief représentant Thotmès III et la Déesse Athor),,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,1849–50,1849,1850,Salted paper print from paper negative,Image: 8 3/16 × 6 1/4 in. (20.8 × 15.8 cm) Mount: 18 11/16 × 12 5/16 in. (47.5 × 31.2 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287127,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.64,false,true,287128,Photographs,Photograph,Pilier du Sanctuaire de granit du Palais de Karnac (Bas-relief représentant le Pharaon Thotmès III et la Déesse Nauth),,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,1849–50,1849,1850,Salted paper print from paper negative,Image: 8 3/16 × 6 1/4 in. (20.8 × 15.8 cm) Mount: 18 11/16 × 12 5/16 in. (47.5 × 31.2 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287128,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.65,false,true,287129,Photographs,Photograph,Sculptures extérieures du Sanctuaire de granit du Palais de Karnac (Thèbes) (Philippe-Aridée conduisant la Barc de Mauth),,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,1849–50,1849,1850,Salted paper print from paper negative,Image: 6 3/8 × 8 1/4 in. (16.2 × 21 cm) Mount: 12 5/16 × 18 11/16 in. (31.2 × 47.5 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287129,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.66,false,true,287130,Photographs,Photograph,Sculptures extérieures du Santuaire de granit du palais de Karnac (Sacre de Philippe-Aridée par les Dieux Thot et Hor-hat),,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,1849–50,1849,1850,Salted paper print from paper negative,Image: 8 1/4 × 6 7/16 in. (21 × 16.3 cm) Mount: 18 11/16 × 12 5/16 in. (47.5 × 31.2 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287130,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.67,false,true,287131,Photographs,Photograph,Sculptures extérieures du Sanctuaire de granit du Palais de Karnac (Ammon assurant la couronne sur la tête de Philippe-Aridée),,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,1849–50,1849,1850,Salted paper print from paper negative,Image: 6 1/8 in. × 8 in. (15.5 × 20.3 cm) Mount: 12 5/16 × 18 11/16 in. (31.2 × 47.5 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287131,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.68,false,true,287132,Photographs,Photograph,Ruines du Palais de Karnac - Vue prise à l'extrêmité du Sanctuaire,,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,1849–50,1849,1850,Salted paper print from paper negative,Image: 6 3/8 × 8 7/16 in. (16.2 × 21.5 cm) Mount: 12 5/16 × 18 11/16 in. (31.2 × 47.5 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287132,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.69,false,true,287133,Photographs,Photograph,Promenoir de Thoutmès III - Dernières galeries du Palais de Karnac (Thèbes),,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,1849–50,1849,1850,Salted paper print from paper negative,Image: 5 13/16 in. × 8 in. (14.8 × 20.3 cm) Mount: 12 5/16 × 18 11/16 in. (31.2 × 47.5 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287133,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.70,false,true,287134,Photographs,Photograph,Vue générale des Ruines du Palais de Karnac (prise à l'Est) - Thèbes,,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,1849–50,1849,1850,Salted paper print from paper negative,Image: 5 11/16 × 8 7/16 in. (14.4 × 21.4 cm) Mount: 12 5/16 × 18 11/16 in. (31.2 × 47.5 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287134,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.71,false,true,287135,Photographs,Photograph,Palais de Gournah (Ménephtéun) à Thèbes,,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,1849–50,1849,1850,Salted paper print from paper negative,Image: 5 11/16 × 8 11/16 in. (14.5 × 22 cm) Mount: 12 5/16 × 18 11/16 in. (31.2 × 47.5 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287135,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.72,false,true,287136,Photographs,Photograph,"Vue générale des Ruines du Rhamesseum, à Thèbes (Tombeau d'Osymandian)",,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,1849–50,1849,1850,Salted paper print from paper negative,Image: 5 11/16 × 8 11/16 in. (14.5 × 22 cm) Mount: 12 5/16 × 18 11/16 in. (31.2 × 47.5 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287136,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.73,false,true,286898,Photographs,Photograph,Colosses du Ramesséum,,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,1850,1850,1850,Salted paper print from paper negative,Image: 16.4 x 21.1 cm (6 7/16 x 8 5/16 in.) Mount: 31.2 x 43.8 cm (12 5/16 x 17 1/4 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286898,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.74,false,true,287137,Photographs,Photograph,Vue générale de la Nécropole de Thèbes (Gournah),,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,1849–50,1849,1850,Salted paper print from paper negative,Mount: Approximately 32 x 47 cm (12 5/8 x 18 1/2 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287137,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.75,false,true,287138,Photographs,Photograph,"Vue des deux colosses de l'Aménophéum, à Thèbes",,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,1849–50,1849,1850,Salted paper print from paper negative,Image: 6 1/8 × 7 7/8 in. (15.5 × 20 cm) Mount: 12 5/16 × 18 11/16 in. (31.2 × 47.5 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287138,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.76,false,true,287139,Photographs,Photograph,"Colosse restauré d' Aménophis III, à Thèbes (Statue vocale ou Colosse de Memnon)",,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,1849–50,1849,1850,Salted paper print from paper negative,Image: 7 13/16 × 6 5/16 in. (19.8 × 16 cm) Mount: 12 5/16 × 18 11/16 in. (31.2 × 47.5 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287139,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.77,false,true,287140,Photographs,Photograph,"Colosse monolithe d'Amenophis III, à Thèbes",,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,1849–50,1849,1850,Salted paper print from paper negative,Image: 7 11/16 × 6 1/4 in. (19.6 × 15.9 cm) Mount: 12 5/16 × 18 11/16 in. (31.2 × 47.5 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287140,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.78,false,true,287141,Photographs,Photograph,"Siège du colosse monolithe d'Aménophis III, à Thèbes (Détails des sculptures)",,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,1849–50,1849,1850,Salted paper print from paper negative,Image: 9 1/8 × 6 7/16 in. (23.1 × 16.4 cm) Mount: 12 5/16 × 18 11/16 in. (31.2 × 47.5 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287141,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.79,false,true,287142,Photographs,Photograph,"Vue générale de Médinet-habou, prise de l'Est (Thèbes)",,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,1849–50,1849,1850,Salted paper print from paper negative,Image: 6 7/16 × 8 1/16 in. (16.3 × 20.4 cm) Mount: 12 5/16 × 18 11/16 in. (31.2 × 47.5 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287142,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.80,false,true,287143,Photographs,Photograph,"Propylées du Thoutmoséum, à Médinet-habou (Thèbes)",,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,1849–50,1849,1850,Salted paper print from paper negative,Image: 8 7/16 × 6 7/16 in. (21.4 × 16.4 cm) Mount: 12 5/16 × 18 11/16 in. (31.2 × 47.5 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287143,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.81,false,true,287144,Photographs,Photograph,"Pavillon ou Gynecée de Rhamsès-Meiamoun, à Médinet-habou",,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,1849–50,1849,1850,Salted paper print from paper negative,Image: 6 5/16 × 8 3/4 in. (16 × 22.2 cm) Mount: 12 5/16 × 18 11/16 in. (31.2 × 47.5 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287144,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.82,false,true,287145,Photographs,Photograph,"Façade latérale du Gynecée de Rhamsès-Meiamoun, Palais de Médinet-Habou, à Thèbes",,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,1849–50,1849,1850,Salted paper print from paper negative,Image: 9 in. × 6 9/16 in. (22.8 × 16.7 cm) Mount: 12 5/16 × 18 11/16 in. (31.2 × 47.5 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287145,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.83,false,true,287146,Photographs,Photograph,"Cour du Palais Rhamsès-Meiamoun, à Médinet-habou (Thèbes) (Restes d'une Eglise Copte)",,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,1849–50,1849,1850,Salted paper print from paper negative,Image: 6 3/8 × 8 3/8 in. (16.2 × 21.2 cm) Mount: 12 5/16 × 18 11/16 in. (31.2 × 47.5 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287146,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.84,false,true,287147,Photographs,Photograph,"Piliers dans la cour du palais de Rhamsès-Meiamoun, à Médinet-habou (Thèbes)",,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,1849–50,1849,1850,Salted paper print from paper negative,Image: 8 3/8 in. × 6 in. (21.2 × 15.3 cm) Mount: 12 5/16 × 18 11/16 in. (31.2 × 47.5 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287147,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.85,false,true,287148,Photographs,Photograph,"Ruines d'une ville chrétienne, à Médinet-habou (Thèbes)",,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,1849–50,1849,1850,Salted paper print from paper negative,Image: 6 1/2 × 8 15/16 in. (16.5 × 22.7 cm) Mount: 12 5/16 × 18 11/16 in. (31.2 × 47.5 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287148,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.86,false,true,287149,Photographs,Photograph,Vue du Village d'Herment,,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,1849–50,1849,1850,Salted paper print from paper negative,Image: 6 in. × 8 3/8 in. (15.3 × 21.3 cm) Mount: 12 5/16 × 18 11/16 in. (31.2 × 47.5 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287149,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.87,false,true,287150,Photographs,Photograph,Ruines du Temple d'Herment (Hermentis),,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,1849–50,1849,1850,Salted paper print from paper negative,Mount: Approximately 32 x 47 cm (12 5/8 x 18 1/2 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287150,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.88,false,true,287151,Photographs,Photograph,Tombeaux Musulmans à Herment,,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,1849–50,1849,1850,Salted paper print from paper negative,Mount: Approximately 32 x 47 cm (12 5/8 x 18 1/2 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287151,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.89,false,true,287152,Photographs,Photograph,"Tombeau de Hadji-Abdallah-el-Marabout, à Herment",,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,1849–50,1849,1850,Salted paper print from paper negative,Image: 6 1/4 × 8 1/2 in. (15.8 × 21.6 cm) Mount: 12 5/16 × 18 11/16 in. (31.2 × 47.5 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287152,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.90,false,true,287153,Photographs,Photograph,Vue générale d'Esné (No. 1 extrêmité Sud),,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,1849–50,1849,1850,Salted paper print from paper negative,Image: 5 1/2 × 8 1/2 in. (14 × 21.6 cm) Mount: 12 5/16 × 18 11/16 in. (31.2 × 47.5 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287153,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.91,false,true,287154,Photographs,Photograph,"Vue générale d'Esné (No. 2, partie médiale)",,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,1849–50,1849,1850,Salted paper print from paper negative,Image: 5 7/16 × 8 3/4 in. (13.8 × 22.2 cm) Mount: 12 5/16 × 18 11/16 in. (31.2 × 47.5 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287154,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.92,false,true,287155,Photographs,Photograph,"Vue générale d'Esné (No. 3, pointe Nord)",,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,1849–50,1849,1850,Salted paper print from paper negative,Image: 5 7/16 × 7 7/8 in. (13.8 × 20 cm) Mount: 12 5/16 × 18 11/16 in. (31.2 × 47.5 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287155,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.93,false,true,287156,Photographs,Photograph,"Palais de Mehemet-Ali, à Esné",,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,1849–50,1849,1850,Salted paper print from paper negative,Image: 5 13/16 × 8 7/16 in. (14.8 × 21.5 cm) Mount: 12 5/16 × 18 11/16 in. (31.2 × 47.5 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287156,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.94,false,true,287157,Photographs,Photograph,Vue du Village d'Edfou,,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,1849–50,1849,1850,Salted paper print from paper negative,Image: 5 13/16 × 8 1/8 in. (14.8 × 20.6 cm) Mount: 12 5/16 × 18 11/16 in. (31.2 × 47.5 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287157,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.95,false,true,287158,Photographs,Photograph,Ruines du Temple de Koum-Ombou (Ombos),,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,1849–50,1849,1850,Salted paper print from paper negative,Image: 6 5/8 × 8 3/4 in. (16.8 × 22.2 cm) Mount: 12 5/16 × 18 11/16 in. (31.2 × 47.5 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287158,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.96,false,true,287159,Photographs,Photograph,"Vue de l'île d'Eléphantine, en face d'Assouan",,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,1849–50,1849,1850,Salted paper print from paper negative,Image: 5 11/16 × 8 11/16 in. (14.4 × 22 cm) Mount: 12 5/16 × 18 11/16 in. (31.2 × 47.5 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287159,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.97,false,true,287160,Photographs,Photograph,Entrée de la première Cataracte près d'Assouan,,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,1849–50,1849,1850,Salted paper print from paper negative,Image: 5 15/16 × 8 11/16 in. (15.1 × 22 cm) Mount: 12 5/16 × 18 11/16 in. (31.2 × 47.5 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287160,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.98,false,true,287161,Photographs,Photograph,Vue prise à la première Cataracte (Rive droite),,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,1849–50,1849,1850,Salted paper print from paper negative,Image: 6 5/8 × 8 7/8 in. (16.9 × 22.6 cm) Mount: 12 5/16 × 18 11/16 in. (31.2 × 47.5 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287161,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.99,false,true,287162,Photographs,Photograph,"Vue de la première Cataracte, prise à l'Ouest, entre Assouan et Philae",,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,1849–50,1849,1850,Salted paper print from paper negative,Image: 6 in. × 8 1/4 in. (15.2 × 21 cm) Mount: 12 5/16 × 18 11/16 in. (31.2 × 47.5 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287162,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.100,false,true,287163,Photographs,Photograph,Sortie de la première Cataracte (côté S.E.),,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,1849–50,1849,1850,Salted paper print from paper negative,Image: 6 5/16 × 8 9/16 in. (16 × 21.8 cm) Mount: 12 5/16 × 18 11/16 in. (31.2 × 47.5 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287163,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.101,false,true,287164,Photographs,Photograph,Vue prise au Nord de Philae - Village de Kounoço,,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,1849–50,1849,1850,Salted paper print from paper negative,Image: 5 11/16 × 8 3/16 in. (14.5 × 20.8 cm) Mount: 12 5/16 × 18 11/16 in. (31.2 × 47.5 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287164,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.102,false,true,287165,Photographs,Photograph,Vue prise du Nord-Est de Philae - Village de Kolokina,,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,1849–50,1849,1850,Salted paper print from paper negative,Image: 5 11/16 × 8 3/16 in. (14.5 × 20.8 cm) Mount: 12 5/16 × 18 11/16 in. (31.2 × 47.5 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287165,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.103,false,true,287166,Photographs,Photograph,Vue prise à l'Est de Philae - Village de Abou-Kouli; Route d'Assouan,,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,1849–50,1849,1850,Salted paper print from paper negative,Image: 5 3/8 × 8 3/16 in. (13.6 × 20.8 cm) Mount: 12 5/16 × 18 11/16 in. (31.2 × 47.5 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287166,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.104,false,true,287167,Photographs,Photograph,Vue prise au Sud-Est de Philae - Cherk-el-Hesseh,,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,1849–50,1849,1850,Salted paper print from paper negative,Image: 6 in. × 7 15/16 in. (15.3 × 20.1 cm) Mount: 12 5/16 × 18 11/16 in. (31.2 × 47.5 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287167,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.105,false,true,287168,Photographs,Photograph,Vue prise au Sud-Est de Philae - Village d'El-Bâb,,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,1849–50,1849,1850,Salted paper print from paper negative,Image: 5 5/8 × 8 1/8 in. (14.3 × 20.7 cm) Mount: 12 5/16 × 18 11/16 in. (31.2 × 47.5 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287168,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.106,false,true,287169,Photographs,Photograph,"Vue générale de l'île de Philae, prise de la pointe Sud de l'île de Begueh",,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,1849–50,1849,1850,Salted paper print from paper negative,Image: 6 9/16 × 8 7/16 in. (16.6 × 21.4 cm) Mount: 12 5/16 × 18 11/16 in. (31.2 × 47.5 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287169,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.107,false,true,287170,Photographs,Photograph,"Vue générale de l'île de Philae, prise de l'île de Begueh (Ouest)",,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,1849–50,1849,1850,Salted paper print from paper negative,Image: 6 in. × 7 7/8 in. (15.2 × 20 cm) Mount: 12 5/16 × 18 11/16 in. (31.2 × 47.5 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287170,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.108,false,true,287171,Photographs,Photograph,"Vue générale de l'île de Philae, prise de l'Est",,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,1849–50,1849,1850,Salted paper print from paper negative,Image: 5 9/16 × 8 11/16 in. (14.1 × 22 cm) Mount: 12 5/16 × 18 11/16 in. (31.2 × 47.5 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287171,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.109,false,true,287172,Photographs,Photograph,"Grand Hypètre ou Typhonium, à Philae - Vue prise de l'Est",,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,1849–50,1849,1850,Salted paper print from paper negative,Image: 6 5/8 × 8 3/4 in. (16.8 × 22.2 cm) Mount: 12 5/16 × 18 11/16 in. (31.2 × 47.5 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287172,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.110,false,true,287173,Photographs,Photograph,"Ruines d'un Arc-de-triomphe Romain, à Philae",,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,April 1850,1850,1850,Salted paper print from paper negative,Image: 6 3/16 in. × 8 in. (15.7 × 20.3 cm) Mount: 12 5/16 × 18 11/16 in. (31.2 × 47.5 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287173,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.111,false,true,287174,Photographs,Photograph,Ensemble du Temple d'Isis à Philae - Vue prise au Nord,,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,April 1850,1850,1850,Salted paper print from paper negative,Image: 6 in. × 8 3/4 in. (15.2 × 22.2 cm) Mount: 12 5/16 × 18 11/16 in. (31.2 × 47.5 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287174,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.112,false,true,287175,Photographs,Photograph,"Dromos et Pylones du grand Temple d'Isis, à Philae",,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,"April 13, 1850",1850,1850,Salted paper print from paper negative,Image: 6 9/16 × 8 3/4 in. (16.6 × 22.2 cm) Mount: 12 5/16 × 18 11/16 in. (31.2 × 47.5 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287175,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.113,false,true,287176,Photographs,Photograph,"Colonnade latérale de la cour du Temple d'Isis, à Philae",,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,April 1850,1850,1850,Salted paper print from paper negative,Image: 6 5/8 × 8 13/16 in. (16.8 × 22.4 cm) Mount: 12 5/16 × 18 11/16 in. (31.2 × 47.5 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287176,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.114,false,true,287177,Photographs,Photograph,"Second Pylone du Temple d'Isis, à Philae",,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,"April 13, 1850",1850,1850,Salted paper print from paper negative,Image: 9 5/16 × 6 9/16 in. (23.6 × 16.6 cm) Mount: 12 5/16 × 18 11/16 in. (31.2 × 47.5 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287177,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.115,false,true,287178,Photographs,Photograph,"Proseynème scellé dans le second Pylone du Temple d'Isis, à Philae",,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,"April 15, 1850",1850,1850,Salted paper print from paper negative,Image: 6 7/16 × 8 9/16 in. (16.4 × 21.8 cm) Mount: 12 5/16 × 18 11/16 in. (31.2 × 47.5 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287178,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.116,false,true,287179,Photographs,Photograph,"Bas-relief pris sur la muraille occidentale du grand Temple d'Isis, à Philae",,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,April 1850,1850,1850,Salted paper print from paper negative,Image: 9 1/4 × 7 1/16 in. (23.5 × 17.9 cm) Mount: 12 5/16 × 18 11/16 in. (31.2 × 47.5 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287179,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.117,false,true,287180,Photographs,Photograph,Thot Ibiocéphale (Dieu des Lettres) à Philae,,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,April 1850,1850,1850,Salted paper print from paper negative,Image: 9 1/8 × 6 1/2 in. (23.2 × 16.5 cm) Mount: 12 5/16 × 18 11/16 in. (31.2 × 47.5 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287180,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.118,false,true,287181,Photographs,Photograph,"Inscription Démotique; second Pylone du Temple d'Isis, à Philae",,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,"April 15, 1850",1850,1850,Salted paper print from paper negative,Image: 6 1/4 × 9 1/8 in. (15.8 × 23.2 cm) Mount: 12 5/16 × 18 11/16 in. (31.2 × 47.5 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287181,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.119,false,true,287182,Photographs,Photograph,"Ruines et Village de Begueh, petite île à l'Ouest de Philae",,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,"April 11, 1850",1850,1850,Salted paper print from paper negative,Image: 5 7/8 × 8 3/8 in. (14.9 × 21.2 cm) Mount: 12 5/16 × 18 11/16 in. (31.2 × 47.5 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287182,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.120,false,true,287183,Photographs,Photograph,"Mosquée de Belal, au Sud de Philae (Rive droite)",,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,"April 11, 1850",1850,1850,Salted paper print from paper negative,Image: 6 1/4 × 8 9/16 in. (15.9 × 21.7 cm) Mount: 12 5/16 × 18 11/16 in. (31.2 × 47.5 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287183,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.121,false,true,287184,Photographs,Photograph,Vue générale du Temple et du Village de Déboude (Parembole),,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,"April 10, 1850",1850,1850,Salted paper print from paper negative,Image: 5 5/8 × 8 11/16 in. (14.3 × 22 cm) Mount: 12 5/16 × 18 11/16 in. (31.2 × 47.5 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287184,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.122,false,true,287185,Photographs,Photograph,Pronaos du Temple de Déboude (Parembole),,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,"April 10, 1850",1850,1850,Salted paper print from paper negative,Image: 6 5/16 × 8 9/16 in. (16 × 21.8 cm) Mount: 12 5/16 × 18 11/16 in. (31.2 × 47.5 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287185,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.123,false,true,287186,Photographs,Photograph,Vue du Temple de Kardassy,,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,"April 9, 1850",1850,1850,Salted paper print from paper negative,Image: 6 9/16 × 8 3/4 in. (16.6 × 22.2 cm) Mount: 12 5/16 × 18 11/16 in. (31.2 × 47.5 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287186,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.124,false,true,287187,Photographs,Photograph,Vue du Temple de Tafah (Taphis),,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,"April 9, 1850",1850,1850,Salted paper print from paper negative,Image: 6 15/16 × 8 9/16 in. (17.7 × 21.7 cm) Mount: 12 5/16 × 18 11/16 in. (31.2 × 47.5 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287187,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.125,false,true,287188,Photographs,Photograph,"Vue générale du Temple de Kalabcheh (Talmis), prise de la montagne",,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,"April 8, 1850",1850,1850,Salted paper print from paper negative,Image: 6 9/16 × 8 11/16 in. (16.6 × 22 cm) Mount: 12 5/16 × 18 11/16 in. (31.2 × 47.5 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287188,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.126,false,true,287189,Photographs,Photograph,Temple de Kalabcheh - Entre colonnement médial du Pronaos,,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,"April 8, 1850",1850,1850,Salted paper print from paper negative,Image: 8 7/8 × 6 9/16 in. (22.5 × 16.7 cm) Mount: 18 11/16 × 12 5/16 in. (47.5 × 31.2 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287189,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.127,false,true,287190,Photographs,Photograph,Ptolémée-Cæsarion - Bas-relief du Temple de Kalabcheh (Talmis),,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,"April 8, 1850",1850,1850,Salted paper print from paper negative,Image: 8 7/8 × 6 9/16 in. (22.5 × 16.7 cm) Mount: 18 11/16 × 12 5/16 in. (47.5 × 31.2 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287190,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.128,false,true,287191,Photographs,Photograph,Isis et Horus-Arsiési - Bas-relief du Temple de Kalabcheh (Talmis),,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,"April 8, 1850",1850,1850,Salted paper print from paper negative,Image: 8 9/16 × 6 7/16 in. (21.7 × 16.3 cm) Image: 8 7/8 × 6 9/16 in. (22.5 × 16.7 cm) Mount: 18 11/16 × 12 5/16 in. (47.5 × 31.2 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287191,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.129,false,true,287192,Photographs,Photograph,Vue du Village d'Abou-hor (Tropique du Cancer),,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,April 1850,1850,1850,Salted paper print from paper negative,Image: 5 3/4 × 8 9/16 in. (14.6 × 21.8 cm) Image: 8 7/8 × 6 9/16 in. (22.5 × 16.7 cm) Mount: 18 11/16 × 12 5/16 in. (47.5 × 31.2 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287192,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.130,false,true,287193,Photographs,Photograph,Vue prise au Village d'Abou-hor (Tropique du Cancer),,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,April 1850,1850,1850,Salted paper print from paper negative,Image: 6 1/16 × 8 13/16 in. (15.4 × 22.4 cm) Image: 8 7/8 × 6 9/16 in. (22.5 × 16.7 cm) Mount: 18 11/16 × 12 5/16 in. (47.5 × 31.2 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287193,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.131,false,true,287194,Photographs,Photograph,Propylon du Temple de Dandour (Tropique du Cancer),,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,April 1850,1850,1850,Salted paper print from paper negative,Image: 8 7/8 × 6 5/8 in. (22.5 × 16.8 cm) Image: 8 7/8 × 6 9/16 in. (22.5 × 16.7 cm) Mount: 18 11/16 × 12 5/16 in. (47.5 × 31.2 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287194,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.132,false,true,287195,Photographs,Photograph,Vue du pronaos du Temple de Dandour (Tropique du Cancer),,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,"April 7, 1850",1850,1850,Salted paper print from paper negative,Image: 6 9/16 × 8 1/2 in. (16.6 × 21.6 cm) Image: 8 7/8 × 6 9/16 in. (22.5 × 16.7 cm) Mount: 18 11/16 × 12 5/16 in. (47.5 × 31.2 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287195,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.133,false,true,287196,Photographs,Photograph,"Vue générale du Temple de Dakkeh (Pselcis), prise au Nord",,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,"April 5, 1850",1850,1850,Salted paper print from paper negative,Image: 6 in. × 8 7/16 in. (15.3 × 21.4 cm) Image: 8 7/8 × 6 9/16 in. (22.5 × 16.7 cm) Mount: 18 11/16 × 12 5/16 in. (47.5 × 31.2 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287196,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.134,false,true,287197,Photographs,Photograph,Vue de la façade du pronaos du Temple de Dakkeh (Pselcis),,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,"April 5, 1850",1850,1850,Salted paper print from paper negative,Image: 6 3/4 × 8 3/4 in. (17.1 × 22.3 cm) Image: 8 7/8 × 6 9/16 in. (22.5 × 16.7 cm) Mount: 18 11/16 × 12 5/16 in. (47.5 × 31.2 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287197,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.135,false,true,287198,Photographs,Photograph,Vue du Temple de Maharakka (Hiéra-Sycaminos),,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,"April 5, 1850",1850,1850,Salted paper print from paper negative,Image: 6 3/16 × 8 3/4 in. (15.7 × 22.2 cm) Image: 8 7/8 × 6 9/16 in. (22.5 × 16.7 cm) Mount: 18 11/16 × 12 5/16 in. (47.5 × 31.2 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287198,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.136,false,true,287199,Photographs,Photograph,Dromos du Temple de Sébona,,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,"April 3, 1850",1850,1850,Salted paper print from paper negative,Image: 6 1/8 × 8 15/16 in. (15.5 × 22.7 cm) Image: 8 7/8 × 6 9/16 in. (22.5 × 16.7 cm) Mount: 18 11/16 × 12 5/16 in. (47.5 × 31.2 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287199,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.137,false,true,287200,Photographs,Photograph,Pylones du Temple de Sébona,,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,"April 3, 1850",1850,1850,Salted paper print from paper negative,Image: 6 1/2 × 8 3/4 in. (16.5 × 22.3 cm) Image: 8 7/8 × 6 9/16 in. (22.5 × 16.7 cm) Mount: 18 11/16 × 12 5/16 in. (47.5 × 31.2 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287200,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.138,false,true,287201,Photographs,Photograph,Vue du Temple d'Amada - Coupole ruinée d'une Eglise Copte,,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,"April 2, 1850",1850,1850,Salted paper print from paper negative,Image: 8 7/8 × 6 9/16 in. (22.5 × 16.7 cm) Mount: 18 11/16 × 12 5/16 in. (47.5 × 31.2 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287201,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.139,false,true,287202,Photographs,Photograph,Vue de la Fortresse d'Ibrym,,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,"March 31, 1850",1850,1850,Salted paper print from paper negative,Image: 8 7/8 × 6 9/16 in. (22.5 × 16.7 cm) Mount: 18 11/16 × 12 5/16 in. (47.5 × 31.2 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287202,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.140,false,true,287203,Photographs,Photograph,"Vue générale des Spéos de Phré et d'Athor, à Abousembil, prise de l'île",,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,"March 29, 1850",1850,1850,Salted paper print from paper negative,Image: 5 1/4 × 8 7/16 in. (13.3 × 21.5 cm) Mount: 18 11/16 × 12 5/16 in. (47.5 × 31.2 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287203,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.141,false,true,287204,Photographs,Photograph,"Entrée du Spéos d'Athor, à Abousembil",,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,March 1850,1850,1850,Salted paper print from paper negative,Image: 8 5/8 × 6 3/8 in. (21.9 × 16.2 cm) Mount: 18 11/16 × 12 5/16 in. (47.5 × 31.2 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287204,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.142,false,true,287205,Photographs,Photograph,"Moitié de la façade du Spéos d'Athor, à Abousembil (partie Septentrionale)",,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,March 1850,1850,1850,Salted paper print from paper negative,Image: 6 3/4 × 9 1/16 in. (17.2 × 23 cm) Mount: 18 11/16 × 12 5/16 in. (47.5 × 31.2 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287205,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.143,false,true,287206,Photographs,Photograph,"Moitié de la façade du Spéos d'Athor, à Abousembil (partie méridionale)",,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,"March 28, 1850",1850,1850,Salted paper print from paper negative,Image: 6 5/8 × 8 11/16 in. (16.9 × 22.1 cm) Mount: 18 11/16 × 12 5/16 in. (47.5 × 31.2 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287206,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.144,false,true,287207,Photographs,Photograph,"Vue de la façade du Spéos de Phré, à Abousembil",,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,March 1850,1850,1850,Salted paper print from paper negative,Image: 6 5/8 × 8 11/16 in. (16.9 × 22.1 cm) Mount: 18 11/16 × 12 5/16 in. (47.5 × 31.2 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287207,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.145,false,true,287208,Photographs,Photograph,"Colosse oriental du Spéos de Phré, à Abousembil (Portrait de Rhamsès-le-grand)",,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,March 1850,1850,1850,Salted paper print from paper negative,Image: 8 7/16 × 6 9/16 in. (21.5 × 16.6 cm) Mount: 18 11/16 × 12 5/16 in. (47.5 × 31.2 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287208,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.146,false,true,287209,Photographs,Photograph,"Profil du Colosse oriental du Spéos de Phré, à Abousembil (Portrait de Rhamsès-le-grand)",,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,"March 29, 1850",1850,1850,Salted paper print from paper negative,Image: 8 7/16 × 6 9/16 in. (21.5 × 16.6 cm) Mount: 18 11/16 × 12 5/16 in. (47.5 × 31.2 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287209,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.147,false,true,287210,Photographs,Photograph,Décoration de l'entrée du grand Spéos d'Abousembil (Le Dieu Phré recevant les offrandes de Rhamsès-le-grand),,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,March 1850,1850,1850,Salted paper print from paper negative,Image: 8 11/16 × 6 5/8 in. (22 × 16.8 cm) Mount: 18 11/16 × 12 5/16 in. (47.5 × 31.2 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287210,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.148,false,true,287211,Photographs,Photograph,"Colosse médial du Spéos d'Phré, à Abousembil",,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,"March 29, 1850",1850,1850,Salted paper print from paper negative,Image: 8 3/8 × 6 5/8 in. (21.2 × 16.8 cm) Mount: 18 11/16 × 12 5/16 in. (47.5 × 31.2 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287211,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.149,false,true,283143,Photographs,Photograph,"Westernmost Colossus of the Temple of Re, Abu Simbel",,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,1850,1850,1850,Salted paper print from paper negative,Image: 9 in. × 6 1/2 in. (22.8 × 16.5 cm) Mount: 18 11/16 × 12 5/16 in. (47.5 × 31.2 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/283143,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.150,false,true,287212,Photographs,Photograph,"Vue cavalière de la seconde cataracte, prise du haut de Djebel-Aboucir",,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,"March 24, 1850",1850,1850,Salted paper print from paper negative,Image: 7 7/8 × 6 5/16 in. (20 × 16 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287212,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.151,false,true,287213,Photographs,Photograph,Vue des rapides de la Seconde Cataracte,,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,March 1850,1850,1850,Salted paper print from paper negative,Image: 4 13/16 × 8 1/2 in. (12.3 × 21.6 cm) Mount: 12 5/16 × 18 11/16 in. (31.2 × 47.5 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287213,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.152,false,true,287214,Photographs,Photograph,Vue prise à la Seconde Cataracte,,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,March 1850,1850,1850,Salted paper print from paper negative,Image: 5 1/2 × 8 11/16 in. (14 × 22 cm) Mount: 12 5/16 × 18 11/16 in. (31.3 × 47.5 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287214,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.153,false,true,287215,Photographs,Photograph,Batu-el-Hadjar. Vue prise à la Seconde Cataracte,,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,March 1850,1850,1850,Salted paper print from paper negative,Image: 6 in. × 8 7/16 in. (15.2 × 21.5 cm) Mount: 12 5/16 × 18 11/16 in. (31.2 × 47.5 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287215,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.154,false,true,287216,Photographs,Photograph,Djebel-Aboucir - Rive gauche de la Seconde Cataracte,,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,"March 25, 1850",1850,1850,Salted paper print from paper negative,Image: 6 in. × 8 11/16 in. (15.2 × 22 cm) Mount: 12 5/16 in. × 18 11/16 in. (31.2 × 47.5 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287216,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.155,false,true,287217,Photographs,Photograph,"Vue générale des ruines de Baâlbek, prise à l'Est",,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,September 1850,1850,1850,Salted paper print from paper negative,Image: 16.2 × 21.7 cm (6 3/8 × 8 9/16 in.) Mount: 12 5/8 × 18 1/2 in. (32 × 47 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287217,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.156,false,true,287218,Photographs,Photograph,Intérieur de l'enceinte du Temple de Baalbek (Héliopolis),,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,"September 15, 1850",1850,1850,Salted paper print from paper negative,Image: 6 1/4 × 8 1/8 in. (15.8 × 20.7 cm) Mount: 12 5/16 × 18 11/16 in. (31.2 × 47.5 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287218,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.157,false,true,287219,Photographs,Photograph,"Hémicycle de l'enceinte des Temples, à Baâlbek",,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,"September 15, 1850",1850,1850,Salted paper print from paper negative,Image: 8 5/8 × 6 1/2 in. (21.9 × 16.5 cm) Mount: 12 5/16 × 18 11/16 in. (31.2 × 47.5 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287219,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.158,false,true,287220,Photographs,Photograph,"Colonnade du Temple du Soleil, à Baâlbek (Héliopolis)",,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,"September 15, 1850",1850,1850,Salted paper print from paper negative,Image: 6 9/16 × 8 1/4 in. (16.7 × 21 cm) Mount: 12 5/16 × 18 11/16 in. (31.2 × 47.5 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287220,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.159,false,true,287221,Photographs,Photograph,"Colonnade du Temple du Soleil, à Baâlbek (Héliopolis)",,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,September 1850,1850,1850,Salted paper print from paper negative,Image: 8 9/16 × 6 9/16 in. (21.7 × 16.7 cm) Mount: 12 5/16 × 18 11/16 in. (31.2 × 47.5 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287221,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.160,false,true,287222,Photographs,Photograph,"Porte du Temple de Jupiter, à Baalbek (Héliopolis)",,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,September 1850,1850,1850,Salted paper print from paper negative,Image: 8 3/4 × 6 9/16 in. (22.3 × 16.7 cm) Mount: 12 5/16 × 18 11/16 in. (31.2 × 47.5 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287222,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.161,false,true,287223,Photographs,Photograph,"Colonnade intérieure du Naos du Temple de Jupiter, à Baâlbek (Héliopolis)",,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,"September 15, 1850",1850,1850,Salted paper print from paper negative,Image: 8 3/4 × 6 1/2 in. (22.2 × 16.5 cm) Mount: 12 5/16 × 18 11/16 in. (31.2 × 47.5 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287223,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.162,false,true,287224,Photographs,Photograph,"Temple de Jupiter, à Baâlbek (Héliopolis)",,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,"September 15, 1850",1850,1850,Salted paper print from paper negative,Image: 8 3/4 × 6 1/2 in. (22.2 × 16.5 cm) Mount: 12 5/16 × 18 11/16 in. (31.2 × 47.5 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287224,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.163,false,true,287225,Photographs,Photograph,"Vue du Temple de Jupiter, à Baâlbek (Héliopolis)",,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,"September 15, 1850",1850,1850,Salted paper print from paper negative,Image: 6 7/16 × 8 3/8 in. (16.4 × 21.3 cm) Mount: 12 5/16 × 18 11/16 in. (31.2 × 47.5 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287225,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.164,false,true,287226,Photographs,Photograph,"Colonnade occidental du Temple de Jupiter, à Baâlbek (Héliopolis)",,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,"September 15, 1850",1850,1850,Salted paper print from paper negative,Image: 6 11/16 × 8 11/16 in. (17 × 22 cm) Mount: 12 5/16 × 18 11/16 in. (31.2 × 47.5 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287226,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.165,false,true,287227,Photographs,Photograph,Château de David (Daoud Kalessy) et murailles de Jérusalem,,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,August 1850,1850,1850,Salted paper print from paper negative,Image: 6 1/8 × 8 9/16 in. (15.5 × 21.8 cm) Mount: 12 5/16 × 18 11/16 in. (31.2 × 47.5 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287227,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.166,false,true,287228,Photographs,Photograph,Koubbé-Nébi-Monça - Coupole de Moïse à Jérusalem,,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,August 1850,1850,1850,Salted paper print from paper negative,Image: 6 1/4 × 9 3/16 in. (15.8 × 23.3 cm) Mount: 12 5/16 × 18 11/16 in. (31.2 × 47.5 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287228,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.167,false,true,287229,Photographs,Photograph,Vue prise au Nord-Ouest de Jérusalem,,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,August 1850,1850,1850,Salted paper print from paper negative,Image: 6 1/4 × 9 3/16 in. (15.8 × 23.3 cm) Mount: 12 5/16 × 18 11/16 in. (31.2 × 47.5 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287229,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.168,false,true,287230,Photographs,Photograph,Vue de la Piscine Probatique et d'un quartier de Jérusalem,,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,August 1850,1850,1850,Salted paper print from paper negative,Image: 6 1/4 × 9 3/16 in. (15.8 × 23.3 cm) Mount: 12 5/16 × 18 11/16 in. (31.2 × 47.5 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287230,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.169,false,true,287231,Photographs,Photograph,Vue de la Mosquée d'El-Melouyeh et d'un quartier de Jérusalem,,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,August 1850,1850,1850,Salted paper print from paper negative,Image: 6 7/16 × 8 1/4 in. (16.4 × 21 cm) Mount: 12 5/16 × 18 11/16 in. (31.2 × 47.5 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287231,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.170,false,true,287232,Photographs,Photograph,"Façade de l'Eglise du St. Sépulcre, à Jérusalem (No. 1)",,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,"August 19, 1850",1850,1850,Salted paper print from paper negative,Image: 6 11/16 × 9 3/8 in. (17 × 23.8 cm) Mount: 12 5/16 × 18 11/16 in. (31.2 × 47.5 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287232,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.171,false,true,287233,Photographs,Photograph,"Façade de l'Eglise du St. Sépulcre, à Jérusalem (No. 2 partie supérieure)",,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,August 1850,1850,1850,Salted paper print from paper negative,Image: 6 11/16 × 9 3/16 in. (17 × 23.4 cm) Mount: 12 5/16 × 18 11/16 in. (31.2 × 47.5 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287233,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.172,false,true,287234,Photographs,Photograph,"La Mosquée d'Omar, à Jérusalem",,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,August 1850,1850,1850,Salted paper print from paper negative,Image: 6 11/16 × 9 1/4 in. (17 × 23.5 cm) Mount: 12 5/16 × 18 11/16 in. (31.2 × 47.5 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287234,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.173,false,true,287235,Photographs,Photograph,La Porte dorée à Jérusalem,,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,August 1850,1850,1850,Salted paper print from paper negative,Image: 9 5/16 × 6 3/8 in. (23.7 × 16.2 cm) Mount: 12 5/16 × 18 11/16 in. (31.2 × 47.5 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287235,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.376.174,false,true,287236,Photographs,Photograph,"Mosquée de Sultan Haçan, Place de Roumelich, au Kaire",,,,,,Artist,,Maxime Du Camp,"French, 1822–1894",,"Du Camp, Maxime",French,1822,1894,"December 13, 1849",1849,1849,Salted paper print from paper negative,Image: 8 1/2 × 6 7/16 in. (21.6 × 16.3 cm) Mount: 12 5/16 × 18 11/16 in. (31.2 × 47.5 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287236,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2003.538,false,true,285271,Photographs,Photograph,Viscountess Vilain,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1857,1857,1857,Salted paper print from glass negative,Image: 29.1 x 22.5 cm (11 7/16 x 8 7/8 in.),"Purchase, Harriette and Noel Levine Gift, 2003",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285271,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.1,false,true,261268,Photographs,Photograph,L'Ecstase,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,5.1 x 8.6 cm (2 x 3 3/8 in.),"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261268,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.3,false,true,261478,Photographs,Photograph,Aux écoutes,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,5.1 x 8.6 cm. (2 x 3 3/8 in.),"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261478,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.4,false,true,261489,Photographs,Photograph,L'Interrogation,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,5.1 x 8.6 cm. (2 x 3 3/8 in.),"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261489,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.5,false,true,261500,Photographs,Photograph,Les beau décolleté,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,5.1 x 8.6 cm. (2 x 3 3/8 in.),"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261500,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.6,false,true,261511,Photographs,Photograph,Méditation,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,5.1 x 8.6 cm. (2 x 3 3/8 in.),"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261511,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.7,false,true,261522,Photographs,Photograph,Le beau bras,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,5.1 x 8.6 cm. (2 x 3 3/8 in.),"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261522,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.8,false,true,261533,Photographs,Photograph,La Psyché,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,5.1 x 8.6 cm. (2 x 3 3/8 in.),"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261533,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.9,false,true,261544,Photographs,Photograph,Réverie,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,5.1 x 8.6 cm. (2 x 3 3/8 in.),"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261544,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.13,false,true,261302,Photographs,Photograph,Derelitta,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,7.3 x 10.8 cm. (2 7/8 x 4 1/4 in.),"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261302,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.14,false,true,261313,Photographs,Photograph,Derelitta,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,7.3 x 10.2 cm. (2 7/8 x 4 in.),"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261313,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.15,false,true,261324,Photographs,Photograph,Derelitta,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,7.8 x 9.5 cm. (3 1/16 x 3 3/4 in.),"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261324,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.16,false,true,261335,Photographs,Photograph,Stella,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,7.6 x 10.2 cm. (3 x 4 in.),"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261335,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.17,false,true,261346,Photographs,Photograph,La robe de moiré,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,7.3 x 10.2 cm (2 7/8 x 4 in.),"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261346,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.18,false,true,261359,Photographs,Photograph,Stella (autre),,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,7.3 x 10.2 cm. (2 7/8 x 4 in.),"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261359,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.19,false,true,261371,Photographs,Photograph,Les épaules tombantes,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,7.6 x 10.2 cm. (3 x 4 in.),"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261371,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.20,false,true,261391,Photographs,Photograph,Bal,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,7.9 x 10.8 cm. (3 1/8 x 4 1/4 in.),"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261391,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.21,false,true,261402,Photographs,Photograph,Bal,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,7.3 x 10.2 cm. (2 7/8 x 4 in.),"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261402,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.22,false,true,261413,Photographs,Photograph,Lucréce (ou la Vestale),,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,7.6 x 10.2 cm. (3 x 4 in.),"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261413,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.23,false,true,261424,Photographs,Photograph,Les rubans découpé,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,6.7 x 9.5 cm. (2 5/8 x 3 3/4 in.),"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261424,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.24,false,true,261435,Photographs,Photograph,Le dos,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,7.6 x 10.2 cm. (3 x 4 in.),"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261435,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.25,false,true,261446,Photographs,Photograph,Le regard,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,8.6 x 12.1 cm. (3 3/8 x 4 3/4 in.),"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261446,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.26,false,true,261457,Photographs,Photograph,Le chapeau à brides,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,7.6 x 11.7 cm. (3 x 4 5/8 in.),"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261457,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.27,false,true,261468,Photographs,Photograph,Le chapeau à brides,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,7.3 x 11.1 cm. (2 7/8 x 4 3/8 in.),"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261468,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.28,false,true,261476,Photographs,Photograph,L'Espagnole,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,6.7 x 8.9 cm. (2 5/8 x 3 1/2 in.),"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261476,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.29,false,true,261477,Photographs,Photograph,L'Ancre,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,5.4 x 8.6 cm. (2 1/8 x 3 3/8 in.),"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261477,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.30,false,true,261479,Photographs,Photograph,L'Orage,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,6.0 x 8.6 cm. (2 3/8 x 3 3/8 in.),"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261479,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.31,false,true,261480,Photographs,Photograph,L'accoudée,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1856–57,1856,1857,Albumen silver print from glass negative,11.1 x 7.6 cm (4 3/8 x 3 in.),"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261480,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.32,false,true,261481,Photographs,Photograph,Priére,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,7.3 x 10.5 cm. (2 7/8 x 4 1/8 in.),"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261481,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.33,false,true,261482,Photographs,Photograph,La robe d'été,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,7.6 x 11.4 cm. (3 x 4 1/2 in.),"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261482,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.34,false,true,261483,Photographs,Photograph,La Frayeur,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,9.8 x 10.8 cm. (3 7/8 x 4 1/4 in.),"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261483,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.35,false,true,261484,Photographs,Photograph,La peignoir plisié,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,8.6 x 11.1 cm. (3 3/8 x 4 3/8 in.),"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261484,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.36,false,true,261485,Photographs,Photograph,Le peignoir plisié,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,10.2 x 10.2 cm. (4 x 4 in.),"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261485,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.37,false,true,261486,Photographs,Photograph,Le Chapelet,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,8.9 x 12.4 cm. (3 1/2 x 4 7/8 in.),"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261486,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.38,false,true,261487,Photographs,Photograph,La robe de soie,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,8.6 x 11.4 cm. (3 3/8 x 4 1/2 in.),"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261487,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.39,false,true,261488,Photographs,Photograph,Petite Reine d'Etrurie,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,8.9 x 11.7 cm. (3 1/2 x 4 5/8 in.),"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261488,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.40,false,true,261490,Photographs,Photograph,La Nonne blanche,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,,"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261490,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.41,false,true,261491,Photographs,Photograph,La casagne de velours,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,,"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261491,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.42,false,true,261492,Photographs,Photograph,Le chàle de dentelles,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,,"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261492,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.43,false,true,261493,Photographs,Photograph,Beatrice,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,,"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261493,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.44,false,true,261494,Photographs,Photograph,La robe bouffante,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,,"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261494,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.45,false,true,261495,Photographs,Photograph,La cape,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,,"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261495,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.46,false,true,261496,Photographs,Photograph,Funerale,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,,"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261496,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.47,false,true,261497,Photographs,Photograph,Funerale,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,,"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261497,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.48,false,true,261498,Photographs,Photograph,Le chapeau à plumes,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,,"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261498,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.49,false,true,261499,Photographs,Photograph,Le peignoir plisie (autre),,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,,"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261499,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.50,false,true,261501,Photographs,Photograph,L'Agrèable,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,,"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261501,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.51,false,true,261502,Photographs,Photograph,Costigliole,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,,"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261502,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.52,false,true,261503,Photographs,Photograph,"Le noeud de dentelle. ""Ritrosetta""",,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,,"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261503,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.53,false,true,261504,Photographs,Photograph,"Le noeud de dentelle. ""Ritrosetta""",,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,,"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261504,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.54,false,true,261505,Photographs,Photograph,Le Chapelet (autre),,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,,"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261505,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.55,false,true,261506,Photographs,Photograph,Le voile,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,,"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261506,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.56,false,true,261507,Photographs,Photograph,L'hermine,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,,"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261507,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.57,false,true,261508,Photographs,Photograph,L'hermine,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,Image: 12.2 x 18.6 cm (4 13/16 x 7 5/16 in.) Mount: 17.4 x 13.6 cm (6 7/8 x 5 3/8 in.),"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261508,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.58,false,true,261509,Photographs,Photograph,Le chapelet (autre),,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,,"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261509,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.59,false,true,261510,Photographs,Photograph,Le chapeau à plume (autre),,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,,"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261510,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.60,false,true,261512,Photographs,Photograph,"La laçon de dessin. ""L'artiste""",,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,,"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261512,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.61,false,true,261513,Photographs,Photograph,Le Repos,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,10.2 x 7.7 cm (4 x 3 1/16 in.),"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261513,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.62,false,true,261514,Photographs,Photograph,L'Algérienne,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,10.5 x 11.4 cm (4 1/8 x 4 1/2 in.),"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261514,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.63,false,true,261515,Photographs,Photograph,Repos (autre),,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,10.2 x 7.6 cm (4 x 3 in.),"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261515,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.64,false,true,261516,Photographs,Photograph,Convalescente (colorieè),,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1861–67,1861,1867,Albumen silver print from glass negative overpainted with watercolor,22.1 x 16.2 cm (8 11/16 x 6 3/8 in.),"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261516,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.65,false,true,261517,Photographs,Photograph,L'Allongée,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,7.5 x 10.6 cm (2 15/16 x 4 3/16 in.) oval,"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261517,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.67,false,true,261519,Photographs,Photograph,Repos (autre),,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,,"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261519,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.69,false,true,261521,Photographs,Photograph,La Chemise,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Salted paper print from glass negative,,"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261521,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.70,false,true,261523,Photographs,Photograph,La Coucher,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Salted paper print from glass negative,,"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261523,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.71,false,true,261524,Photographs,Photograph,La robe de taffetas,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,,"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261524,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.72,false,true,261525,Photographs,Photograph,La robe de taffetas,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,,"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261525,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.73,false,true,261526,Photographs,Photograph,Les yeux mirés,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,Image: 17.4 x 12.8 cm (6 7/8 x 5 1/16 in.) Mount: 23.5 x 20.6 cm (9 1/4 x 8 1/8 in.),"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261526,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.74,false,true,261527,Photographs,Photograph,Les yeux mirés,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,,"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261527,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.78,false,true,261531,Photographs,Photograph,Marie Stuart,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,,"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261531,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.79,false,true,261532,Photographs,Photograph,Marie Stuart,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,,"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261532,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.80,false,true,261534,Photographs,Photograph,Marie Stuart,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,,"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261534,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.81,false,true,261535,Photographs,Photograph,Cauchoise,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,,"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261535,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.82,false,true,261536,Photographs,Photograph,La Marquise Mathilde,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,,"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261536,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.83,false,true,261537,Photographs,Photograph,Cauchoise (autre),,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,,"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261537,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.84,false,true,261538,Photographs,Photograph,Mathilde (autre),,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,,"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261538,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.85,false,true,261539,Photographs,Photograph,Mathilde,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,,"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261539,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.86,false,true,261540,Photographs,Photograph,Mathilde,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,,"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261540,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.87,false,true,261541,Photographs,Photograph,Judith,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,,"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261541,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.88,false,true,261542,Photographs,Photograph,Judith,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,,"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261542,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.89,false,true,261543,Photographs,Photograph,Judith,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,,"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261543,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.90,false,true,261545,Photographs,Photograph,Judith,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,,"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261545,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.91,false,true,261546,Photographs,Photograph,Judith,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,,"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261546,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.92,false,true,261547,Photographs,Photograph,Judith,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,,"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261547,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.93,false,true,261548,Photographs,Photograph,Reine d'Etrurie (colorieè),,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,,"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261548,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.94,false,true,261549,Photographs,Photograph,Reine d'Etrurie (colorieè),,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Salted paper print from glass negative,,"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261549,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.96,false,true,261551,Photographs,Photograph,Livetta,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,,"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261551,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.97,false,true,261552,Photographs,Photograph,Nonne blanche (tete),,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,,"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261552,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.98,false,true,261553,Photographs,Photograph,Nonne blanche (en pied),,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,,"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261553,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.99,false,true,261554,Photographs,Photograph,Soeur Elize,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,,"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261554,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.100,false,true,261270,Photographs,Photograph,Le noeud rouge,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,,"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261270,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.101,false,true,261271,Photographs,Photograph,"La Bisi. Boudoir, robe velour gris, moire rose, fleurs roses, feuilles grises (de sa mai, au revers.)",,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,11.4 x 10.7 cm (4 1/2 x 4 3/16 in.),"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261271,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.116,false,true,261287,Photographs,Photograph,Ritrosetta,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1861–64,1861,1864,Salted paper print from glass negative with applied color,14.5 x 13.1 cm (5 11/16 x 5 3/16 in.),"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261287,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.118,false,true,261289,Photographs,Photograph,Derelitta (peintre),,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,,"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261289,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.119,false,true,261290,Photographs,Photograph,La Frayeur,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1861–67,1861,1867,Salted paper print from glass negative with applied color,12.7 x 15.1 cm (5 x 5 15/16 in.),"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261290,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.120,false,true,261292,Photographs,Photograph,Virginie,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Salted paper print from glass negative with applied color,8.7 x 13.6 cm (3 7/16 x 5 3/8 in.),"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261292,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.127,false,true,261299,Photographs,Photograph,[Study of Legs],,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1861–67,1861,1867,Albumen silver print from glass negative,11.4 x 13.7 cm (4 1/2 x 5 3/8 in.),"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261299,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.128,false,true,261300,Photographs,Photograph,Les jambes,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,11.7 x 13.0 cm (4 5/8 x 5 1/8 in.),"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261300,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.129,false,true,261301,Photographs,Photograph,Les jambes,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,11.1 x 14.9 cm (4 3/8 x 5 7/8 in.),"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261301,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.130,false,true,261303,Photographs,Photograph,Sèriè à la Ristori,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,,"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261303,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.131,false,true,261304,Photographs,Photograph,Sèriè à la Ristori,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,,"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261304,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.132,false,true,261305,Photographs,Photograph,Sèriè à la Ristori,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,,"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261305,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.133,false,true,261306,Photographs,Photograph,Sèriè à la Ristori,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,,"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261306,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.134,false,true,261307,Photographs,Photograph,Sèriè à la Ristori,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,,"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261307,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.135,false,true,261308,Photographs,Photograph,Sèriè à la Ristori,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,,"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261308,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.136,false,true,261309,Photographs,Photograph,Sèriè à la Ristori,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,9.8 x 13.3 cm. (3 7/8 x 5 1/4 in.),"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261309,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.137,false,true,261310,Photographs,Photograph,Sèriè à la Ristori,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,,"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261310,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.138,false,true,261311,Photographs,Photograph,Sèriè à la Ristori,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,,"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261311,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.139,false,true,261312,Photographs,Photograph,Sèriè à la Ristori,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,,"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261312,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.140,false,true,261314,Photographs,Photograph,Sèriè à la Ristori,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,,"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261314,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.141,false,true,261315,Photographs,Photograph,Sèriè à la Ristori,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,,"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261315,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.142,false,true,261316,Photographs,Photograph,Sèriè à la Ristori,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,,"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261316,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.143,false,true,261317,Photographs,Photograph,Sèriè à la Ristori,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,9.8 x 13.3 cm. (3 7/8 x 5 1/4 in.),"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261317,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.144,false,true,261318,Photographs,Photograph,Sèriè à la Ristori,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,,"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261318,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.145,false,true,261319,Photographs,Photograph,Sèriè à la Ristori,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,,"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261319,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.146,false,true,261320,Photographs,Photograph,Sèriè à la Ristori,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,,"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261320,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.147,false,true,261321,Photographs,Photograph,Le pardessus dècoré,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,9.8 x 13.3 cm. (3 7/8 x 5 1/4 in.),"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261321,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.148,false,true,261322,Photographs,Photograph,Le pardessus dècoré,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,,"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261322,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.149,false,true,261323,Photographs,Photograph,Le pardessus dècoré,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,,"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261323,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.150,false,true,261325,Photographs,Photograph,Le Caracul (L'Astrakhan),,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,,"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261325,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.151,false,true,261326,Photographs,Photograph,Le Caracul (L'Astrakhan),,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,,"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261326,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.152,false,true,261327,Photographs,Photograph,Le Caracul (L'Astrakhan),,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,,"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261327,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.153,false,true,261328,Photographs,Photograph,Le Caracul (L'Astrakhan),,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,,"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261328,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.154,false,true,261329,Photographs,Photograph,Le Caracul (L'Astrakhan),,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,,"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261329,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.155,false,true,261330,Photographs,Photograph,Le Caracul (L'Astrakhan),,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,,"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261330,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.156,false,true,261331,Photographs,Photograph,Le Caracul (L'Astrakhan),,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,,"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261331,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.157,false,true,261332,Photographs,Photograph,Le Caracul (L'Astrakhan),,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,,"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261332,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.158,false,true,261333,Photographs,Photograph,Le Caracul (L'Astrakhan),,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,,"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261333,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.159,false,true,261334,Photographs,Photograph,Le Caracul (L'Astrakhan),,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,,"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261334,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.160,false,true,261336,Photographs,Photograph,Le Caracul (L'Astrakhan),,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,,"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261336,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.161,false,true,261337,Photographs,Photograph,Le Caracul (L'Astrakhan),,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,,"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261337,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.162,false,true,261338,Photographs,Photograph,Le Caracul (L'Astrakhan),,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,,"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261338,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.163,false,true,261339,Photographs,Photograph,Le Caracul (L'Astrakhan),,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,,"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261339,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.164,false,true,261340,Photographs,Photograph,Le Caracul (L'Astrakhan),,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,,"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261340,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.165,false,true,261341,Photographs,Photograph,Le Caracul (L'Astrakhan),,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,,"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261341,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.166,false,true,261342,Photographs,Photograph,Les étoiles de jois,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,,"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261342,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.167,false,true,261343,Photographs,Photograph,Les edoiles de jois,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,,"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261343,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.168,false,true,261344,Photographs,Photograph,Les étoiles de jois,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,,"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261344,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.169,false,true,261345,Photographs,Photograph,Les étoiles de jois,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,,"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261345,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.170,false,true,261347,Photographs,Photograph,Les Chiens,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Gelatin silver print,13.7 x 10.2 cm (5 3/8 x 4 in.),"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261347,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.171,false,true,261348,Photographs,Photograph,Les Chiens,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,10.2 x 14.3 cm (4 x 5 5/8 in.),"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261348,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.172,false,true,261349,Photographs,Photograph,Les Chiens,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Gelatin silver print,13.7 x 9.8 cm (5 3/8 x 3 7/8 in.),"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261349,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.173,false,true,261350,Photographs,Photograph,Les Chiens,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,11.7 x 8.9 cm (4 5/8 x 3 1/2 in.),"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261350,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.174,false,true,261352,Photographs,Photograph,Les Chiens,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,10.2 x 13.7 cm (4 x 5 3/8 in.),"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261352,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.175,false,true,261354,Photographs,Photograph,Les Chiens,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,9.2 x 13.3 cm (3 5/8 x 5 1/4 in.),"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261354,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.176,false,true,261355,Photographs,Photograph,Les Chiens,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1875–80,1875,1880,Albumen silver print from glass negative,22 x 16.8 cm (8 11/16 x 6 5/8 in.),"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261355,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.177,false,true,261356,Photographs,Photograph,Les dernieres,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,,"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261356,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.178,false,true,261357,Photographs,Photograph,Les dernieres,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,,"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261357,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.179,false,true,261358,Photographs,Photograph,Les dernieres,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,,"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261358,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.180,false,true,261360,Photographs,Photograph,Les dernieres,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,,"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261360,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.185,false,true,261366,Photographs,Photograph,[Reine d'Etrurie],,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1863–67,1863,1867,Albumen silver print from glass negative retouched with gouache,12.4 x 8.9 cm (4 7/8 x 3 1/2 in.),"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261366,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.186,false,true,261367,Photographs,Photograph,[Reine d'Etrurie],,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative with applied color,12.3 x 8.9 cm (4 13/16 x 3 1/2 in.),"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261367,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.187,false,true,261368,Photographs,Photograph,[Reine d'Etrurie],,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative with applied color,12.3 x 8.9 cm (4 13/16 x 3 1/2 in.),"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261368,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.188,false,true,261369,Photographs,Photograph,[Reine d'Etrurie],,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative with applied color,12.4 x 8.9 cm (4 7/8 x 3 1/2 in.),"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261369,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.194,false,true,261384,Photographs,Photograph,L'Accouchée,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Salted paper print from glass negative,18.1 x 22.5 cm. (7 1/8 x 8 7/8 in.),"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261384,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.195,false,true,261385,Photographs,Photograph,En famille,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Salted paper print from glass negative,17.8 x 21.9 cm. (7 x 8 5/8 in.),"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261385,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.196,false,true,261386,Photographs,Photograph,La Mere et L'Epoux,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,13.3 x 16.8 cm. (5 1/4 x 6 5/8 in.),"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261386,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.197,false,true,261387,Photographs,Photograph,La Colombe et le Tigre,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,14.9 x 17.8 cm. (5 7/8 x 7 in.),"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261387,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.198,false,true,261388,Photographs,Photograph,La capuche,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,9.8 x 12.7 cm. (3 7/8 x 5 in.),"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261388,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.199,false,true,261389,Photographs,Photograph,La petite chemise,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,8.9 x 11.4 cm. (3 1/2 x 4 1/2 in.),"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261389,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.200,false,true,261392,Photographs,Photograph,Le derrière,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,9.5 x 12.7 cm. (3 3/4 x 5 in.),"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261392,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.201,false,true,261393,Photographs,Photograph,La ?aurier rose,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,10.8 x 13.0 cm. (4 1/4 x 5 1/8 in.),"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261393,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.202,false,true,261394,Photographs,Photograph,La veste de cygne,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,5.1 x 8.6 cm. (2 x 3 3/8 in.),"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261394,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.203,false,true,261395,Photographs,Photograph,Autre chaise rustique,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,5.7 x 7.6 cm. (2 1/4 x 3 in.),"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261395,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.204,false,true,261396,Photographs,Photograph,Le bournous (colorie),,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,10.8 x 13.0 cm. (4 1/4 x 5 1/8 in.),"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261396,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.205,false,true,261397,Photographs,Photograph,Le treillage,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,7.0 x 9.8 cm. (2 3/4 x 3 7/8 in.),"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261397,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.206,false,true,261398,Photographs,Photograph,La veste de Cygne (autre),,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,5.1 x 2.5 cm. (2 x 1 in.),"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261398,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.207,false,true,261399,Photographs,Photograph,La veste de Cygne,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,8.6 x 12.1 cm. (3 3/8 x 4 3/4 in.),"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261399,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.208,false,true,261400,Photographs,Photograph,Le dos,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,8.3 x 10.2 cm. (3 1/4 x 4 in.),"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261400,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.209,false,true,261401,Photographs,Photograph,Encore la chaise rustique,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,11.1 x 14.0 cm. (4 3/8 x 5 1/2 in.),"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261401,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.210,false,true,261403,Photographs,Photograph,Le reflet (profile),,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,7.6 x 10.2 cm. (3 x 4 in.),"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261403,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.212,false,true,261404,Photographs,Photograph,Le fauteuil,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,6.0 x 9.5 cm. (2 3/8 x 3 3/4 in.),"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261404,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.213,false,true,261406,Photographs,Photograph,La fouriure,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,15.9 x 9.5 cm. (6 1/4 x 3 3/4 in.),"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261406,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.214,false,true,261407,Photographs,Photograph,La fouriure,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,7.3 x 10.2 cm. (2 7/8 x 4 in.),"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261407,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.215,false,true,261408,Photographs,Photograph,La frisure,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,7.6 x 9.8 cm. (3 x 3 7/8 in.),"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261408,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.216,false,true,261409,Photographs,Photograph,Les jambes croisées,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,8.9 x 12.4 cm. (3 1/2 x 4 7/8 in.),"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261409,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.217,false,true,261410,Photographs,Photograph,L'Ecossais,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,8.6 x 11.4 cm. (3 3/8 x 4 1/2 in.),"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261410,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.218,false,true,261411,Photographs,Photograph,Le liseur,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,8.6 x 12.1 cm. (3 3/8 x 4 3/4 in.),"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261411,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.219,false,true,261412,Photographs,Photograph,L'Echevelé,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,8.6 x 12.1 cm. (3 3/8 x 4 3/4 in.),"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261412,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.220,false,true,261414,Photographs,Photograph,Le chemise Russe,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,8.6 x 12.1 cm. (3 3/8 x 4 3/4 in.),"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261414,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.221,false,true,261415,Photographs,Photograph,Le chemise Russe,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,8.6 x 11.4 cm. (3 3/8 x 4 1/2 in.),"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261415,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.222,false,true,261416,Photographs,Photograph,Le montagnard,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,8.6 x 11.4 cm. (3 3/8 x 4 1/2 in.),"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261416,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.223,false,true,261417,Photographs,Photograph,Le montagnard,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,8.6 x 11.7 cm. (3 3/8 x 4 5/8 in.),"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261417,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.224,false,true,261418,Photographs,Photograph,Le montagnard,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,8.6 x 12.1 cm. (3 3/8 x 4 3/4 in.),"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261418,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.225,false,true,261419,Photographs,Photograph,Le montagnard,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,8.6 x 12.1 cm (3 3/8 x 4 3/4 in.),"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261419,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.226,false,true,261420,Photographs,Photograph,L'Enfant blanc,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,8.6 x 12.1 cm. (3 3/8 x 4 3/4 in.),"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261420,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.227,false,true,261421,Photographs,Photograph,L'Enfant blanc,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,8.6 x 12.1 cm. (3 3/8 x 4 3/4 in.),"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261421,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.228,false,true,261422,Photographs,Photograph,Le petit Russe,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,8.3 x 11.7 cm. (3 1/4 x 4 5/8 in.),"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261422,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.229,false,true,261423,Photographs,Photograph,Le petit Russe,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,8.3 x 11.4 cm. (3 1/4 x 4 1/2 in.),"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261423,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.230,false,true,261425,Photographs,Photograph,Le petit Russe,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,8.6 x 11.7 cm. (3 3/8 x 4 5/8 in.),"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261425,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.231,false,true,261426,Photographs,Photograph,Le petit Russe,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,,"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261426,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.232,false,true,261427,Photographs,Photograph,Le petit Russe,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,8.6 x 11.4 cm. (3 3/8 x 4 1/2 in.),"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261427,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.233,false,true,261428,Photographs,Photograph,Le petit Russe,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,9.5 x 11.7 cm. (3 3/4 x 4 5/8 in.),"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261428,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.234,false,true,261429,Photographs,Photograph,Le petit Russe,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,9.5 x 13.7 cm. (3 3/4 x 5 3/8 in.),"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261429,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.235,false,true,261430,Photographs,Photograph,Le petit Russe,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,9.8 x 13.3 cm (3 7/8 x 5 1/4 in.),"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261430,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.236,false,true,261431,Photographs,Photograph,Le petit Russe,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,,"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261431,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.237,false,true,261432,Photographs,Photograph,Le Grand Russe,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,9.8 x 13.3 cm. (3 7/8 x 5 1/4 in.),"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261432,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.238,false,true,261433,Photographs,Photograph,Le Grand Russe,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,9.8 x 13.3 cm. (3 7/8 x 5 1/4 in.),"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261433,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.239,false,true,261434,Photographs,Photograph,Le Grand Russe,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,9.8 x 13.3 cm. (3 7/8 x 5 1/4 in.),"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261434,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.240,false,true,261436,Photographs,Photograph,Le Grand Russe,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,9.8 x 13.3 cm. (3 7/8 x 5 1/4 in.),"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261436,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.241,false,true,261437,Photographs,Photograph,Le Grand Russe,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,9.8 x 13.3 cm. (3 7/8 x 5 1/4 in.),"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261437,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.242,false,true,261438,Photographs,Photograph,Le Grand Russe,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,9.8 x 13.3 cm. (3 7/8 x 5 1/4 in.),"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261438,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.243,false,true,261439,Photographs,Photograph,Le Grand Russe,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,,"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261439,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.244,false,true,261440,Photographs,Photograph,Le Grand Russe,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,9.8 x 13.3 cm. (3 7/8 x 5 1/4 in.),"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261440,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.245,false,true,261441,Photographs,Photograph,Le Grand Russe,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,9.8 x 13.3 cm. (3 7/8 x 5 1/4 in.),"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261441,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.251,false,true,261448,Photographs,Photograph,La fillette,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,11.8 x 8.9 cm (4 5/8 x 3 1/2 in.),"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261448,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.201,false,true,285651,Photographs,Photograph,Giorgio de Castiglione,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,"1861, printed 1895–1910",1861,1910,Gelatin silver print from glass negative,Image: 36 x 27.9 cm (14 3/16 x 11 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285651,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.173a,false,true,261351,Photographs,Photograph,Les Chiens,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,11.4 x 8.6 cm (4 1/2 x 3 3/8 in.),"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261351,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.174a,false,true,261353,Photographs,Photograph,Les Chiens,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Waxed albumen silver print from glass negative with applied color,8.3 x 5 cm (3 1/4 x 1 15/16 in.),"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261353,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.176a,false,true,282738,Photographs,Photograph,Les Chiens,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1875–80,1875,1880,Albumen silver print from glass negative,22.7 x 16.8 cm (8 15/16 x 6 5/8 in.),"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/282738,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.212a,false,true,261405,Photographs,Photograph,Le fauteuil,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,7.6 x 10.2 cm. (3 x 4 in.),"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261405,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.193.1,false,true,261375,Photographs,Photograph,La chaise rustique,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print,,"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261375,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.193.2,false,true,261376,Photographs,Photograph,Le tambour,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,,"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261376,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.193.3,false,true,261377,Photographs,Photograph,Chaise rustique (autre),,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,,"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261377,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.193.4,false,true,261378,Photographs,Photograph,Chaise rustique,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,,"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261378,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.193.5,false,true,261379,Photographs,Photograph,Chaise rustique,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,,"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261379,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.193.6,false,true,261380,Photographs,Photograph,Le furieuse,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,,"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261380,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.193.7,false,true,261381,Photographs,Photograph,Le bournous,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,,"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261381,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.193.8,false,true,261382,Photographs,Photograph,Tambour (autre),,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,,"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261382,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.193.9,false,true,261383,Photographs,Photograph,La tête renversée,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,,"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261383,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"1975.548.95a, b",false,true,261550,Photographs,Photograph,Piede de Judith,,,,,,Artist,,Pierre-Louis Pierson,"French, 1822–1913",,"Pierson, Pierre-Louis",French,1822,1913,1860s,1860,1869,Albumen silver print from glass negative,,"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261550,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.400.51,false,true,291804,Photographs,Daguerreotype,[Reclining Female Nude as Danae],,,,,,Artist,Attributed to,Bruno Braquehais,"French, 1823–1875",,"Braquehais, Bruno",French,1823,1875,1850s,1852,1858,Daguerreotype,Image: 8 x 6 cm (3 1/8 x 2 3/8 in.) Mount: 12.9 x 11.1 cm (5 1/16 x 4 3/8 in.),"Bequest of Herbert Mitchell, 2008",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/291804,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.279,false,true,286294,Photographs,Photograph,[Nude Before a Mirror],,,,,,Artist,Attributed to,Bruno Braquehais,"French, 1823–1875",,"Braquehais, Bruno",French,1823,1875,ca. 1857,1855,1859,Albumen silver print from glass negative,Image: 22.3 × 17.9 cm (8 3/4 × 7 1/16 in.) Mount: 50.1 × 39.3 cm (19 3/4 × 15 1/2 in.),"Gilman Collection, Purchase, Mr. and Mrs. Henry R. Kravis Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286294,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.45,false,true,283120,Photographs,Photograph,Emmanuel Frémiet,,,,,,Artist,,Adrien Tournachon,"French, 1825–1903",,"Tournachon, Adrien",French,1825,1903,1854–55,1854,1855,Salted paper print from glass negative,24.6 x 17.3cm (9 11/16 x 6 13/16in.),"Gilman Collection, Purchase, Alfred Stieglitz Society Gifts, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/283120,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.634,false,true,286272,Photographs,Photograph,"[Bull from Glane, Canton of Fribourg]",,,,,,Artist,,Adrien Tournachon,"French, 1825–1903",,"Tournachon, Adrien",French,1825,1903,1856,1856,1856,Salted paper print from glass negative,Image: 7 1/2 × 10 9/16 in. (19.1 × 26.8 cm) Mount: 10 5/8 × 12 13/16 in. (27 × 32.5 cm),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286272,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.176,false,true,286657,Photographs,Photograph,Auguste Vacquerie,,,,,,Artist,,Charles Victor Hugo,"French, 1826–1871",,"Hugo, Charles Victor",French,1826,1871,1853–56,1853,1856,Salted paper print from paper negative,Image: 9.2 x 7.6 cm (3 5/8 x 3 in.) Mount: 24.8 x 18.4 cm (9 3/4 x 7 1/4 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286657,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1997.382.68,false,true,282079,Photographs,Photograph,[Man in a Forest Landscape],,,,,,Artist,,Constant Alexandre Famin,"French, 1827–1888",,"Famin, Constant Alexandre",French,1827,1888,ca. 1870,1868,1872,Albumen silver print from glass negative,33.7 x 25.3 cm (13 1/4 x 9 15/16 in. ),"The Rubel Collection, Purchase, Lila Acheson Wallace Gift, 1997",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/282079,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2008.256,false,true,289145,Photographs,Photograph,Gustave Le Gray,,,,,,Artist,,Alphonse Delaunay,"French, 1827–1906",,"Delaunay, Alphonse",French,1827,1906,1854,1854,1854,Salted paper print from glass negative,Image: 22.1 x 16.5 cm (8 11/16 x 6 1/2 in.) Mount: 44 x 34.2 cm (17 5/16 x 13 7/16 in.),"Purchase, Daniel Blau Gift and 2007 Benefit Fund, 2008",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/289145,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.236,false,true,680014,Photographs,Photograph,"Second Palace at Mitla, Mexico.",,,,,,Artist,,Désiré Charnay,"French, 1828–1915",,"Charnay, Désiré",French,1828,1915,February 1860,1860,1860,Albumen silver print from glass negative,Image: 13 1/8 × 16 7/8 in. (33.3 × 42.9 cm) Mount: 21 3/16 in. × 25 in. (53.8 × 63.5 cm),"Purchase, Mr. and Mrs. John A. Moran Gift, in memory of Louise Chisholm Moran, 2015",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/680014,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.73,false,true,283172,Photographs,Photograph,"Raharla, Minister to the Queen",,,,,,Artist,,Désiré Charnay,"French, 1828–1915",,"Charnay, Désiré",French,1828,1915,1863,1863,1863,Albumen silver print from glass negative,Image: 19.4 × 12 cm (7 5/8 × 4 3/4 in.) Mount: 28.7 × 23.1 cm (11 5/16 × 9 1/8 in.),"Gilman Collection, Purchase, Joyce F. Menschel Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/283172,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.74,false,true,283173,Photographs,Photograph,[Family Group],,,,,,Artist,,Désiré Charnay,"French, 1828–1915",,"Charnay, Désiré",French,1828,1915,1863,1863,1863,Albumen silver print from glass negative,Image: 21.3 × 16.8 cm (8 3/8 × 6 5/8 in.) Mount: 28.8 × 23.2 cm (11 5/16 × 9 1/8 in.),"Gilman Collection, Purchase, Alfred Stieglitz Society Gifts, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/283173,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.565,false,true,286467,Photographs,Photograph,"La Prison, à Chichen-Itza",,,,,,Artist,,Désiré Charnay,"French, 1828–1915",,"Charnay, Désiré",French,1828,1915,1857–89,1857,1889,Albumen silver print from glass negative,Image: 33.3 x 42.4 cm (13 1/8 x 16 11/16 in.) Mount: 54 x 70.8 cm (21 1/4 x 27 7/8 in.),"Gilman Collection, Purchase, Jennifer and Joseph Duke Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286467,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.732,false,true,286280,Photographs,Photograph,"Femmes Betsimisaraka, Madagascar",,,,,,Artist,,Désiré Charnay,"French, 1828–1915",,"Charnay, Désiré",French,1828,1915,1863,1863,1863,Albumen silver print from glass negative,Image: 19.7 x 17 cm (7 3/4 x 6 11/16 in.) Mount: 28.7 x 23.1 cm (11 5/16 x 9 1/8 in.),"Gilman Collection, Purchase, Sam Salz Foundation Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286280,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.733,false,true,286146,Photographs,Photograph,Ile de la Réunion,,,,,,Artist,,Désiré Charnay,"French, 1828–1915",,"Charnay, Désiré",French,1828,1915,1863,1863,1863,Albumen silver print from glass negative,Image: 27.8 x 20.4 cm (10 15/16 x 8 1/16 in.) Mount: 35.8 x 27 cm (14 1/8 x 10 5/8 in.),"Gilman Collection, Purchase, Sam Salz Foundation Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286146,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.734,false,true,286691,Photographs,Photograph,Fougère arborescente,,,,,,Artist,,Désiré Charnay,"French, 1828–1915",,"Charnay, Désiré",French,1828,1915,1863,1863,1863,Albumen silver print from glass negative,Image: 28.7 x 22.8 cm (11 5/16 x 9 in.) Mount: 35.5 x 27.4 cm (14 x 10 13/16 in.),"Gilman Collection, Purchase, Sam Salz Foundation Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286691,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.735,false,true,286282,Photographs,Photograph,Baobab à Mohéli,,,,,,Artist,,Désiré Charnay,"French, 1828–1915",,"Charnay, Désiré",French,1828,1915,1863,1863,1863,Albumen silver print from glass negative,Image: 28.7 x 22.8 cm (11 5/16 x 9 in.) Mount: 35.4 x 26.9 cm (13 15/16 x 10 9/16 in.),"Gilman Collection, Purchase, Sam Salz Foundation Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286282,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.491.1 (8),false,true,287607,Photographs,Photograph,"[The Countess Canning, Simla]",,,,,,Artist,,Jean Baptiste Oscar Mallitte,"French, 1829–1905",,"Mallitte, Jean Baptiste Oscar",French,1829,1905,1861,1861,1861,Albumen silver print,Mount: 33 x 26.1 cm (13 x 10 1/4 in.) Image: 20.7 x 14.1 cm (8 1/8 x 5 9/16 in.),"Gilman Collection, Purchase, Cynthia Hazen Polsky Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287607,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.491.1 (46),false,true,287646,Photographs,Photograph,"[Group portrait at the Governor Generals Camp, L-R: Maj Jones, Mr. Walters, The Governor Generals Chaplain, J.C.S. and Sir E.Campbell Bart., 60th Rifles Mry. Sry. to G.G.]",,,,,,Artist,,Jean Baptiste Oscar Mallitte,"French, 1829–1905",,"Mallitte, Jean Baptiste Oscar",French,1829,1905,1858–61,1858,1861,Albumen silver print,Image: 20.5 x 24.7 cm (8 1/16 x 9 3/4 in.) Mount: 33 x 26 cm (13 x 10 1/4 in.) Print mounted vertically.,"Gilman Collection, Purchase, Cynthia Hazen Polsky Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287646,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.491.1 (52),false,true,287652,Photographs,Photograph,[Lady Canning],,,,,,Artist,,Jean Baptiste Oscar Mallitte,"French, 1829–1905",,"Mallitte, Jean Baptiste Oscar",French,1829,1905,1858–61,1858,1861,Albumen silver print,Image: 16 x 19.7 cm (6 5/16 x 7 3/4 in.) Mount: 33.1 x 26 cm (13 1/16 x 10 1/4 in.) Print mounted vertically.,"Gilman Collection, Purchase, Cynthia Hazen Polsky Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287652,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.491.1 (60),false,true,287660,Photographs,Photograph,"[Metcalfe House, Delhi]",,,,,,Artist,,Jean Baptiste Oscar Mallitte,"French, 1829–1905",,"Mallitte, Jean Baptiste Oscar",French,1829,1905,1858–61,1858,1861,Albumen silver print,Image: 21 x 28.4 cm (8 1/4 x 11 3/16 in.) Mount: 33 x 26 cm (13 x 10 1/4 in.) Pritn mounted vertically.,"Gilman Collection, Purchase, Cynthia Hazen Polsky Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287660,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.491.1 (50a),false,true,287650,Photographs,Photograph,"[Lady Canning on her Black Arab and Lord Clyde, Commander in Chief]",,,,,,Artist,,Jean Baptiste Oscar Mallitte,"French, 1829–1905",,"Mallitte, Jean Baptiste Oscar",French,1829,1905,1858–61,1858,1861,Albumen silver print,Image: 14.9 x 19.2 cm (5 7/8 x 7 9/16 in.) Mount: 33.1 x 26 cm (13 1/16 x 10 1/4 in.),"Gilman Collection, Purchase, Cynthia Hazen Polsky Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287650,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.491.1 (56b),false,true,287705,Photographs,Photograph,"[Campbell Twins in a Shigram, Governor General's Camp]",,,,,,Artist,,Jean Baptiste Oscar Mallitte,"French, 1829–1905",,"Mallitte, Jean Baptiste Oscar",French,1829,1905,1858–61,1858,1861,Albumen silver print,Image: 9.7 x 19.6 cm (3 13/16 x 7 11/16 in.) Mount: 33 x 26 cm (13 x 10 1/4 in.),"Gilman Collection, Purchase, Cynthia Hazen Polsky Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287705,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.1144,false,true,266168,Photographs,Photographs,[The Corpse of Emperor Maximilian I of Mexico],,,,,,Artist,,François Aubert,"French, 1829–1906",,"Aubert, François",French,1829,1906,1867,1867,1867,Albumen silver print,,"Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 1989",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/266168,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.212,false,true,285871,Photographs,Photograph,[The Scene of the Execution of Emperor Maximilian I of Mexico],,,,,,Artist,,François Aubert,"French, 1829–1906",,"Aubert, François",French,1829,1906,1867,1867,1867,Albumen silver print from glass negative,Image: 22.1 × 16.1 cm (8 11/16 × 6 5/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285871,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.213,false,true,285712,Photographs,Photographs,"The Shirt of the Emperor, Worn during His Execution",,,,,,Artist,,François Aubert,"French, 1829–1906",,"Aubert, François",French,1829,1906,1867,1867,1867,Albumen silver print from glass negative,Image: 22.2 × 15.8 cm (8 3/4 × 6 1/4 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285712,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.580.1,false,true,286895,Photographs,Photograph,[Emperor Maximilian's Firing Squad],,,,,,Artist,,François Aubert,"French, 1829–1906",,"Aubert, François",French,1829,1906,1867,1867,1867,Albumen silver print from glass negative,Image: 11.4 x 14.2 cm (4 7/16 x 5 9/16 in.) Mount: 18.2 x 22.1 cm (7 3/16 x 8 11/16 in.),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286895,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.820,false,true,286544,Photographs,Photograph,[Bird in Flight],,,,,,Artist,,Etienne-Jules Marey,"French, 1830–1904",,"Marey, Etienne-Jules",French,1830,1904,1886,1886,1886,Albumen silver print from glass negative,Image: 3.3 x 17.3 cm (1 5/16 x 6 13/16 in.),"Gilman Collection, Purchase, Denise and Andrew Saul Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286544,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.321,false,true,286606,Photographs,Photograph,"Tomb of Ptahmose, Saqqara (Memphis)",,,,,,Artist,,Théodule Deveria,"French, 1831–1871",,"Deveria, Théodule",French,1831,1871,1859,1859,1859,Albumen silver print from paper negative,"Image: 8 3/8 × 11 1/16 in. (21.2 × 28.1 cm), irregularly trimmed Sheet: 8 7/16 × 11 5/16 in. (21.4 × 28.8 cm), irregularly trimmed","Gilman Collection, Purchase, Joseph M. Cohen Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286606,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2010.317,false,true,292057,Photographs,Photograph,Femme turque en toilette de ville,,,,,,Artist,,Félix Bonfils,"French, 1831–1885",,"Bonfils, Félix",French,1831,1885,1870s,1870,1879,Albumen silver print from glass negative,Image: 22.2 x 16.4 cm (8 3/4 x 6 7/16 in.),"Funds from various donors, 2010",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/292057,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.561.1,false,true,261776,Photographs,Photograph,Temple d'Andour,,,,,,Artist,,Félix Bonfils,"French, 1831–1885",,"Bonfils, Félix",French,1831,1885,ca. 1870,1868,1872,Albumen silver print from glass negative,17.3 x 23.0 cm (6 13/16 x 9 1/16 in.),"Gift of Daniel Wolf, in memory of Diane R. Wolf, 1976",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261776,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.561.2,false,true,261777,Photographs,Photograph,Pyramides et le Sphinx,,,,,,Artist,,Félix Bonfils,"French, 1831–1885",,"Bonfils, Félix",French,1831,1885,"1860s–70s, printed ca. 1870",1860,1879,Albumen silver print,17.3 x 23.1 cm. (6 13/16 x 9 1/16 in.),"Gift of Daniel Wolf, in memory of Diane R. Wolf, 1976",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261777,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1973.594.87,false,true,260950,Photographs,Photograph,[The Roman Forum],,,,,,Artist,,Félix Bonfils,"French, 1831–1885",,"Bonfils, Félix",French,1831,1885,ca. 1870s,1868,1872,Albumen silver print,,"The Elisha Whittelsey Collection, The Elisha Whittelsey Fund, 1973",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260950,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1981.1128.1,false,true,262989,Photographs,Photograph,The Temple of the Sun at Baalbec,,,,,,Artist,,Félix Bonfils,"French, 1831–1885",,"Bonfils, Félix",French,1831,1885,ca. 1870,1868,1872,Albumen silver print,,"Gift of Douglas Dillon, 1981",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/262989,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1981.1128.2,false,true,262990,Photographs,Photograph,"[Details of the Colonnade of the Parthenon, Athens]",,,,,,Artist,,Félix Bonfils,"French, 1831–1885",,"Bonfils, Félix",French,1831,1885,ca. 1870s,1868,1872,Albumen silver print,Image: 22.3 x 27.7 cm (8 3/4 x 10 7/8 in.) Mount: 40.5 x 50.6 cm (15 15/16 x 19 15/16 in.),"Gift of Douglas Dillon, 1981",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/262990,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1981.1194.4,false,true,263132,Photographs,Photograph,Caire près du Mokkatam,,,,,,Artist,,Félix Bonfils,"French, 1831–1885",,"Bonfils, Félix",French,1831,1885,1870s,1870,1879,Albumen silver print from glass negative,,"Gift of Mrs. John L. Swayze, 1981",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/263132,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1981.1194.5,false,true,263133,Photographs,Photograph,"Caire. Mosquée el-Arhar, détails de la porte",,,,,,Artist,,Félix Bonfils,"French, 1831–1885",,"Bonfils, Félix",French,1831,1885,1870s,1870,1879,Albumen silver print from glass negative,,"Gift of Mrs. John L. Swayze, 1981",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/263133,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1981.1194.6,false,true,263134,Photographs,Photograph,Rue du Caire,,,,,,Artist,,Félix Bonfils,"French, 1831–1885",,"Bonfils, Félix",French,1831,1885,1870s,1870,1879,Albumen silver print from glass negative,,"Gift of Mrs. John L. Swayze, 1981",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/263134,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1981.1194.7,false,true,263135,Photographs,Photograph,Saîs coureurs au Caire,,,,,,Artist,,Félix Bonfils,"French, 1831–1885",,"Bonfils, Félix",French,1831,1885,1870s,1870,1879,Albumen silver print from glass negative,,"Gift of Mrs. John L. Swayze, 1981",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/263135,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1981.1194.8,false,true,263136,Photographs,Photograph,Le Caire - Intérieur de la mosquée El Bordei,,,,,,Artist,,Félix Bonfils,"French, 1831–1885",,"Bonfils, Félix",French,1831,1885,1870s,1870,1879,Albumen silver print from glass negative,,"Gift of Mrs. John L. Swayze, 1981",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/263136,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1985.1123.8,false,true,264380,Photographs,Photograph,Caire. Allée de Pyramides,,,,,,Artist,,Félix Bonfils,"French, 1831–1885",,"Bonfils, Félix",French,1831,1885,ca. 1870,1868,1872,Albumen silver print,,"Gift of Weston J. Naef, 1985",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/264380,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1982.1153.13,false,true,263432,Photographs,Photograph,"Cous du Kadisha, Monte Libon",,,,,,Artist,,Félix Bonfils,"French, 1831–1885",,"Bonfils, Félix",French,1831,1885,"1860s–80s, printed ca. 1870",1860,1889,Albumen silver print,,"Gift of Mrs. John L. Swayze, 1982",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/263432,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1982.1153.14,false,true,263433,Photographs,Photograph,Intérieur de la Porte de Jaffa,,,,,,Artist,,Félix Bonfils,"French, 1831–1885",,"Bonfils, Félix",French,1831,1885,ca. 1870,1868,1872,Albumen silver print,Image: 28 x 22.6 cm (11 x 8 7/8 in.),"Gift of Mrs. John L. Swayze, 1982",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/263433,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1982.1153.15,false,true,263434,Photographs,Photograph,Promenade des Pins á Beyrouth,,,,,,Artist,,Félix Bonfils,"French, 1831–1885",,"Bonfils, Félix",French,1831,1885,ca. 1870,1868,1872,Albumen silver print,,"Gift of Mrs. John L. Swayze, 1982",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/263434,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1982.1153.20,false,true,263440,Photographs,Photograph,Beyrouth. Vu du collège américain,,,,,,Artist,,Félix Bonfils,"French, 1831–1885",,"Bonfils, Félix",French,1831,1885,ca. 1870,1868,1872,Albumen silver print,,"Gift of Mrs. John L. Swayze, 1982",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/263440,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1982.1153.21,false,true,263441,Photographs,Photograph,Vue générale de Bethany - General view of Bethany,,,,,,Artist,,Félix Bonfils,"French, 1831–1885",,"Bonfils, Félix",French,1831,1885,ca. 1880,1878,1882,Albumen silver print,,"Gift of Mrs. John L. Swayze, 1982",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/263441,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1982.1153.22,false,true,263442,Photographs,Photograph,Les ponts du fleurs du chien,,,,,,Artist,,Félix Bonfils,"French, 1831–1885",,"Bonfils, Félix",French,1831,1885,ca. 1880,1878,1882,Albumen silver print,,"Gift of Mrs. John L. Swayze, 1982",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/263442,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1982.1153.23,false,true,263443,Photographs,Photograph,Panorama de Jaffa,,,,,,Artist,,Félix Bonfils,"French, 1831–1885",,"Bonfils, Félix",French,1831,1885,ca. 1880,1878,1882,Albumen silver print,,"Gift of Mrs. John L. Swayze, 1982",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/263443,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.501,false,true,685841,Photographs,Carte-de-visite,[John Leech],,,,,,Artist,,Camille Silvy,"French, 1835–1869",,"Silvy, Camille",French,1835,1869,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/685841,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.1014.2,false,true,264624,Photographs,Photograph,[Roses],,,,,,Artist,,Eugène Chauvigné,"French, 1837–1894",,"Chauvigné, Eugène",French,1837,1894,ca. 1875,1873,1877,Albumen silver print from glass negative,24.0 x 17.9 cm. (9 7/16 x 7 1/16 in.),"Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 1986",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/264624,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.366,false,true,685706,Photographs,Carte-de-visite,[Francois Adolphe Grison],,,,,,Artist,,Émile Schweitzer,"French, 1837–1903",,"Schweitzer, Émile",French,1837,1903,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/685706,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.186,false,true,306173,Photographs,Photograph,Prenant un Ris à Bord de L'Astrée,,,,,,Artist,,Félix Auguste Leclerc,"French, 1838–1896",,"Leclerc, Félix Auguste",French,1838,1896,1871,1871,1871,Albumen silver print from glass negative,Image: 25 × 18.7 cm (9 13/16 × 7 3/8 in.) Mount: 44.7 × 32.4 cm (17 5/8 × 12 3/4 in.),"Purchase, The Buddy Taub Foundation Gift, Dennis A. Roach and Jill Roach, Directors, 2013",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306173,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.848,false,true,285885,Photographs,Photograph,[Emmanuel Frémiet],,,,,,Artist,Possibly by,Edmond Bénard,"French, 1838–1907",,"Bénard, Edmond",French,1838,1907,1880s–90s,1880,1899,Albumen silver print from glass negative,Image: 20.6 × 26.4 cm (8 1/8 × 10 3/8 in.),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285885,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1995.9,false,true,267314,Photographs,Photograph,Le Nouvel Opéra de Paris (Sculpture Ornementale),,,,,,Artist,,Louis-Emile Durandelle,"French, 1839–1917",,"Durandelle, Louis-Émile",French,1839,1917,1865–72,1865,1872,Albumen silver print from glass negative,38.3 x 27.9 cm. (15 1/16 x 11 in.),"Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 1995",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267314,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -49.50.353,false,true,269271,Photographs,Photograph,[Charles Garnier in the Drafting Room While Designing the New Paris Opera],,,,,,Artist,,Louis-Emile Durandelle,"French, 1839–1917",,"Durandelle, Louis-Émile",French,1839,1917,ca. 1870,1868,1872,Albumen silver print from glass negative,,"The Elisha Whittelsey Collection, The Elisha Whittelsey Fund, 1949",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269271,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1988.1072,false,true,265933,Photographs,Photograph,La Tour St. Jacques La Boucherie à Paris,,,,,,Artist,,Charles Soulier,"French, 1840–1875",,"Soulier, Charles",French,1840,1875,ca. 1867,1865,1869,Albumen silver print from glass negative,40.7 x 30.6 cm. (16 x 12 1/16 in.),"Edward Pearce Casey Fund, 1988",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/265933,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.32.1,false,true,289160,Photographs,Photograph,Grande Salle du Conseil d'Etat,,,,,,Artist,,Charles Soulier,"French, 1840–1875",,"Soulier, Charles",French,1840,1875,May 1871,1871,1871,Albumen silver print from glass negative,Image: 19 x 24.9 cm (7 1/2 x 9 13/16 in.) Mount: 34.7 x 47.2 cm (13 11/16 x 18 9/16 in.),"Gift of Paula and Robert Hershkowitz, in memory of Sam Wagstaff, 2009",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/289160,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.32.2,false,true,289161,Photographs,Photograph,Maisons de la porte d'Auteuil,,,,,,Artist,,Charles Soulier,"French, 1840–1875",,"Soulier, Charles",French,1840,1875,May 1871,1871,1871,Albumen silver print from glass negative,Image: 19.2 x 24.9 cm (7 9/16 x 9 13/16 in.) Mount: 34.7 x 47.5 cm (13 11/16 x 18 11/16 in.),"Gift of Paula and Robert Hershkowitz, in memory of Sam Wagstaff, 2009",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/289161,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2008.527,false,true,289070,Photographs,Photograph,[Group of Adults and Children on a Village Street in the Auvergne],,,,,,Artist,,Felix Thiollier,"French, 1842–1914",,"Thiollier, Felix",French,1842,1914,ca. 1910,1905,1914,Gelatin silver print,29.4 x 39.4 cm (11 9/16 x 15 1/2 in.),"Twentieth-Century Photography Fund, 2008",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/289070,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2010.1,false,true,291656,Photographs,Photograph,[Fencer],,,,,,Artist,,Georges Demeny,"French, 1850–1917",,"Demeny, Georges",French,1850,1917,1906,1906,1906,Gelatin silver print,Image: 17.5 x 26.5 x 12.8 cm (6 7/8 x 10 7/16 in.) Mount: 20.1 x 28.7 cm (7 15/16 x 11 5/16 in.),"Purchase, Alfred Stieglitz Society Gifts, 2010",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/291656,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.16,false,true,289245,Photographs,Photograph,"Tableau synoptic des traits physionomiques: pour servir a l'étude du ""portrait parlé""",,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",French,1853,1914,ca. 1909,1909,1909,Gelatin silver print,Image: 39.4 x 29.5 cm (15 1/2 x 11 5/8 in.),"Twentieth-Century Photography Fund, 2009",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/289245,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.375.1–.434,false,true,286605,Photographs,Mugshot,[Mugshots of Suspected Anarchists from French Police Files],,,,,,Artist,,Alphonse Bertillon,"French, 1853–1914",,"Bertillon, Alphonse",French,1853,1914,1891–95,1891,1895,Albumen silver prints and gelatin silver prints,10.5 x 7 x 0.5 cm (4 1/8 x 2 3/4 x 3/16 in.) each,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286605,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.43.254,false,true,267665,Photographs,Photograph,[Study in Orange],,,,,,Artist,,René Le Bègue,"French, 1857–1914",,"Le Bègue, René",French,1857,1914,1904,1904,1904,Gum bichromate print,22.0 x 14.3 cm. (8 11/16 x 5 5/8 in.),"Alfred Stieglitz Collection, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267665,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.43.258,false,true,267669,Photographs,Photograph,Académie,,,,,,Artist,,René Le Bègue,"French, 1857–1914",,"Le Bègue, René",French,1857,1914,1902,1902,1902,Gum bichromate print,24.1 x 18.0 cm. (9 1/2 x 7 1/16 in.),"Alfred Stieglitz Collection, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267669,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -49.55.220,false,true,269345,Photographs,Photograph,[Study in Orange],,,,,,Artist,,René Le Bègue,"French, 1857–1914",,"Le Bègue, René",French,1857,1914,1903,1903,1903,Gum bichromate print,25.7 x 19.7 cm. (10 1/8 x 7 3/4 in.),"Alfred Stieglitz Collection, 1949",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269345,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.118,false,true,283240,Photographs,Photograph,"[Puyo, Robert Demachy, and Paul de Singly with Model]",,,,,,Artist,,Emile Joachim Constant Puyo,"French, 1857–1933",,"Puyo, Emile Joachim Constant",French,1857,1933,1909,1909,1909,Platinum print,Image: 2 11/16 × 3 3/16 in. (6.9 × 8.1 cm) Mount: 3 1/8 × 3 1/2 in. (7.9 × 8.9 cm),"Gilman Collection, Purchase, Harriette and Noel Levine Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/283240,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.1148,false,true,285751,Photographs,Photograph,[Montmartre],,,,,,Artist,,Emile Joachim Constant Puyo,"French, 1857–1933",,"Puyo, Emile Joachim Constant",French,1857,1933,ca. 1906,1904,1908,Bromoil print,Image: 11 7/16 × 8 7/8 in. (29.1 × 22.6 cm),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285751,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.43.56,false,true,267853,Photographs,Photograph,Panel,,,,,,Artist,,Robert Demachy,"French, 1859–1936",,"Demachy, Robert",French,1859,1936,1898,1898,1898,Gum bichromate print,14.7 x 20.2 cm (5 13/16 x 7 15/16 in. ),"Alfred Stieglitz Collection, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267853,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.43.57,false,true,267854,Photographs,Photograph,Académie,,,,,,Artist,,Robert Demachy,"French, 1859–1936",,"Demachy, Robert",French,1859,1936,1900,1900,1900,Gum bichromate print,22.2 x 17.0 cm. (8 3/4 x 6 11/16 in.),"Alfred Stieglitz Collection, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267854,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.43.470,false,true,267843,Photographs,Photograph,Honfleur,,,,,,Artist,,Robert Demachy,"French, 1859–1936",,"Demachy, Robert",French,1859,1936,1905,1900,1909,Gum bichromate print,15.1 x 21.4 cm. (5 15/16 x 8 7/16 in.),"Alfred Stieglitz Collection, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267843,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -49.55.205,false,true,269328,Photographs,Photograph,The Crowd,,,,,,Artist,,Robert Demachy,"French, 1859–1936",,"Demachy, Robert",French,1859,1936,1910,1910,1910,Oil print,15.8 x 22.8 cm. (6 1/4 x 9 in.),"Alfred Stieglitz Collection, 1949",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269328,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -49.55.206,false,true,269329,Photographs,Photograph,Dans les coulisses,,,,,,Artist,,Robert Demachy,"French, 1859–1936",,"Demachy, Robert",French,1859,1936,ca. 1897,1895,1899,Gum bichromate print,36.7 x 18.8 cm. (14 7/16 x 7 3/8 in.),"Alfred Stieglitz Collection, 1949",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269329,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2008.666.3,false,true,289550,Photographs,Photograph,Struggle,,,,,,Artist,,Robert Demachy,"French, 1859–1936",,"Demachy, Robert",French,1859,1936,1903 or earlier,1900,1903,Gum bichromate print,Image: 17.4 x 11.6 cm (6 7/8 x 4 9/16 in.) Mount: 17.7 x 11.9 cm (6 15/16 x 4 11/16 in.) Mount (2nd): 39.6 x 29.6 cm (15 9/16 x 11 5/8 in.),"Gift of Isaac Lagnado, in honor of Thomas P. Campbell, 2008",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/289550,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.115,false,true,283233,Photographs,Photograph,[Auguste Rodin's The Clenched Hand],,,,,,Artist,,Eugène Druet,"French, 1868–1917",,"Druet, Eugène",French,1868,1917,before 1898,1890,1898,Gelatin silver print,Image: 29.9 x 39.5 cm (11 3/4 x 15 9/16 in.),"Gilman Collection, Purchase, Mr. and Mrs. Henry R. Kravis Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/283233,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.186,false,true,286722,Photographs,Photograph,"[Nijinsky in ""Danse siamoise"" from the ""Orientales""]",,,,,,Artist,,Eugène Druet,"French, 1868–1917",,"Druet, Eugène",French,1868,1917,1910,1910,1910,Gelatin silver print,Image: 20.3 x 14.6 cm (8 x 5 3/4 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286722,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.621,false,true,283234,Photographs,Photograph,[Study of a Sculpture],,,,,,Artist,Attributed to,Eugène Druet,"French, 1868–1917",,"Druet, Eugène",French,1868,1917,ca. 1900,1898,1902,Gelatin silver print,39.7 x 29.8 cm (15 5/8 x 11 3/4 in.),"Gilman Collection, Purchase, Mr. and Mrs. Henry R. Kravis Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/283234,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2001.8,false,true,284433,Photographs,Photograph,[Materialization of a Woman's Face Produced by the Medium Eva C.],,,,,,Artist,,Gustave Geley,"French, 1868–1924",,"Geley, Gustave",French,1868,1924,"February 26, 1918",1918,1918,Gelatin silver print,17.9 x 13 cm (7 1/16 x 5 1/8 in.),"Gilman Collection, Purchase, The Howard Gilman Foundation Gift, 2001",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/284433,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.1090,false,true,286701,Photographs,Photograph,[Construction for the Universal Exhibition of 1855],,,,,,Artist,,Bertsch et D'Arnaud,"French, active 1850s",,Bertsch et D'Arnaud,French,1850,1859,1855,1855,1855,Salted paper print from glass negative,Image: 20.7 × 16.4 cm (8 1/8 × 6 7/16 in.) Mount: 31 × 25.1 cm (12 3/16 × 9 7/8 in.),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286701,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.651,false,true,287289,Photographs,Photograph,[Spiral of Lightning in a Thunderstorm],,,,,,Artist,,Charles Moussette,"French, active 1880s",,"Moussette, Charles",French,1880,1889,"May 12, 1886",1886,1886,Albumen silver print from glass negative,Mount: 9 13/16 in. × 6 7/8 in. (25 × 17.5 cm) Image: 6 5/8 × 4 5/8 in. (16.9 × 11.7 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287289,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.51,false,true,283132,Photographs,Photograph,"Leonardo da Vinci, Drawing for Christ in ""The Last Supper""",,,,,,Artist,,Léon Gérard,"French, active 1857–61",,"Gérard, Léon",French,1857,1861,1857–61,1857,1861,Albumen silver print from paper negative,Image: 36.3 x 26.6 cm (14 5/16 x 10 1/2 in.) Mount: 59.2 x 42.6 cm (23 5/16 x 16 3/4 in.),"Gilman Collection, Purchase, Denise and Andrew Saul Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/283132,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.460.13,false,true,290481,Photographs,Photograph,"Nuremburg, Intérieur de la Cour du Burg impérial",,,,,,Artist,,Léon Gérard,"French, active 1857–61",,"Gérard, Léon",French,1857,1861,1857,1857,1857,Albumen silver print from glass negative,Image: 34.9 x 26.9 cm (13 3/4 x 10 9/16 in.) Mount: 59.1 x 43.2 cm (23 1/4 x 17 in.),"Gift of Paul F. Walter, 2009",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/290481,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1997.382.46,true,true,282051,Photographs,Photograph,[Two Standing Female Nudes],,,,,,Artist,,Félix-Jacques-Antoine Moulin,"French, 1800–after 1875",,"Moulin, Félix-Jacques-Antoine",French,1800,1875,ca. 1850,1848,1852,Daguerreotype,visible: 14.5 x 11.1 cm (5 11/16 x 4 3/8 in.),"The Rubel Collection, Purchase, Anonymous Gift and Lila Acheson Wallace Gift, 1997",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/282051,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.1105,false,true,286524,Photographs,Photograph,Nude,,,,,,Artist,,Félix-Jacques-Antoine Moulin,"French, 1800–after 1875",,"Moulin, Félix-Jacques-Antoine",French,1800,1875,ca. 1850,1848,1852,Salted paper print from paper negative,Image: 8 13/16 × 6 3/4 in. (22.4 × 17.2 cm) Mount: 11 5/8 × 8 1/16 in. (29.6 × 20.5 cm),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286524,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1991.1075,false,true,266384,Photographs,Photograph,[Standing Male Nude],,,,,,Artist,,Charles Alphonse Marlé,"French, 1821–after 1867",,"Marlé, C.",French,1821,1921,ca. 1855,1853,1857,Salted paper print from paper negative,Image: 25.7 x 17.6 cm (10 1/8 x 6 15/16 in.),"Purchase, Ezra Mack Gift and The Horace W. Goldsmith Foundation Gift through Joyce and Robert Menschel, 1991",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/266384,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.263,false,true,285463,Photographs,Photograph,"Shelling Beans, Argentelle",,,,,,Artist,,Louis-Adolphe Humbert de Molard,"French, Paris 1800–1874",,"Humbert de Molard, Louis-Adolphe",French,1800,1874,1851,1851,1851,Salted paper print from paper negative,Image: 22.3 x 17.7 cm (8 3/4 x 6 15/16 in.),"Gilman Collection, Purchase, Mr. and Mrs. Henry R. Kravis Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285463,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.266,false,true,286023,Photographs,Photograph,"[Pont d'Ouilly on the Orne River, Normandy]",,,,,,Artist,,Louis-Adolphe Humbert de Molard,"French, Paris 1800–1874",,"Humbert de Molard, Louis-Adolphe",French,1800,1874,1850–51,1850,1851,Salted paper print from paper negative,Image: 17.9 x 22.2 cm (7 1/16 x 8 3/4 in.) Mount: 21 x 24 cm (8 1/4 x 9 7/16 in.),"Gilman Collection, Purchase, Heidi S. Steiger Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286023,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.286,false,true,286022,Photographs,Photograph,Self-Portrait,,,,,,Artist,,Louis-Adolphe Humbert de Molard,"French, Paris 1800–1874",,"Humbert de Molard, Louis-Adolphe",French,1800,1874,1846–47,1846,1847,Daguerreotype,Image: 13.3 x 10.3 cm (5 1/4 x 4 1/16 in.),"Gilman Collection, Purchase, Joseph M. Cohen Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286022,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.758,false,true,286127,Photographs,Photograph,Louise-Marie-Julie,,,,,,Artist,,Louis-Adolphe Humbert de Molard,"French, Paris 1800–1874",,"Humbert de Molard, Louis-Adolphe",French,1800,1874,1849,1849,1849,Salted paper print from paper negative,16.6 x 12.9 cm (6 9/16 x 5 1/16 in.),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286127,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.797,false,true,286024,Photographs,Photograph,Henriette-Reneé Patu,,,,,,Artist,,Louis-Adolphe Humbert de Molard,"French, Paris 1800–1874",,"Humbert de Molard, Louis-Adolphe",French,1800,1874,ca. 1845,1843,1847,Waxed salted paper print from paper negative,Image: 6 7/8 × 5 3/16 in. (17.5 × 13.2 cm),"Gilman Collection, Purchase, Ann Tenenbaum and Thomas H. Lee Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286024,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.898,false,true,286292,Photographs,Photograph,[Female Nude],,,,,,Artist,Circle of,Louis-Adolphe Humbert de Molard,"French, Paris 1800–1874",,"Humbert de Molard, Louis-Adolphe",French,1800,1874,ca. 1855,1853,1857,Salted paper print from paper negative,"14 x 11 cm (5 1/2 x 4 5/16 in.), corners clipped","Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286292,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.1241,false,true,302627,Photographs,Photograph,The Laundry,,,,,,Artist,,Louis-Adolphe Humbert de Molard,"French, Paris 1800–1874",,"Humbert de Molard, Louis-Adolphe",French,1800,1874,1840s,1840,1849,Salted paper print,Image: 7 5/16 × 5 1/2 in. (18.6 × 14 cm),"Gilman Collection, Purchase, Ann Tenenbaum and Thomas H. Lee Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/302627,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.17,false,true,685359,Photographs,Carte-de-visite,[? Aubert],,,,,,Artist,,Émile Tourtin,"French, active 1860s–70s",,"Tourtin, Émile",French,1860,1880,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/685359,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.466,false,true,685806,Photographs,Carte-de-visite,[Unknown Subject],,,,,,Artist,,Émile Tourtin,"French, active 1860s–70s",,"Tourtin, Émile",French,1860,1880,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/685806,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.516,false,true,685856,Photographs,Carte-de-visite,[Unknown Subject],,,,,,Artist,,Émile Tourtin,"French, active 1860s–70s",,"Tourtin, Émile",French,1860,1880,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/685856,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.784,false,true,686123,Photographs,Carte-de-visite,[Unknown Subject],,,,,,Artist,,Émile Tourtin,"French, active 1860s–70s",,"Tourtin, Émile",French,1860,1880,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/686123,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.860,false,true,686199,Photographs,Carte-de-visite,[Unknown Subject],,,,,,Artist,,Émile Tourtin,"French, active 1860s–70s",,"Tourtin, Émile",French,1860,1880,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/686199,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2008.145,false,true,289044,Photographs,Photograph,"Tubular Jetty, Mouth of the Adour, Port of Bayonne",,,,,,Artist,,Louis Lafon,"French, active 1870s–90s",,"Lafon, Louis",French,1870,1899,1892,1892,1892,Albumen silver print from glass negative,Image: 36.5 x 47.3 cm (14 3/8 x 18 5/8 in.),"Purchase, Alfred Stieglitz Society Gifts, 2008",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/289044,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2008.146,false,true,289045,Photographs,Photograph,Lessart Viaduct on the Rance River,,,,,,Artist,,Louis Lafon,"French, active 1870s–90s",,"Lafon, Louis",French,1870,1899,October 1879,1879,1879,Albumen silver print from glass negative,Image: 36.9 x 47.6 cm (14 1/2 x 18 3/4 in.),"Purchase, Alfred Stieglitz Society Gifts, 2008",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/289045,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2008.147,false,true,289106,Photographs,Photograph,The Foundry,,,,,,Artist,,Louis Lafon,"French, active 1870s–90s",,"Lafon, Louis",French,1870,1899,1870s–80s,1870,1889,Albumen silver print from glass negative,Image: 36.6 x 47.9 cm (14 7/16 x 18 7/8 in.),"Purchase, Alfred Stieglitz Society Gifts, 2008",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/289106,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2008.558.1,false,true,289717,Photographs,Photograph,[Factory Interior],,,,,,Artist,,Louis Lafon,"French, active 1870s–90s",,"Lafon, Louis",French,1870,1899,ca. 1880,1875,1885,Albumen silver print from glass negative,Image: 36.7 x 47 cm (14 7/16 x 18 1/2 in.),"Gift of Charles Isaacs, 2008",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/289717,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2008.558.2,false,true,289718,Photographs,Photograph,Forges,,,,,,Artist,,Louis Lafon,"French, active 1870s–90s",,"Lafon, Louis",French,1870,1899,ca. 1880,1875,1885,Albumen silver print from glass negative,Image: 35.2 x 45.5 cm (13 7/8 x 17 15/16 in.),"Gift of Charles Isaacs, 2008",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/289718,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2008.558.3,false,true,289719,Photographs,Photograph,Cour des Ateliers,,,,,,Artist,,Louis Lafon,"French, active 1870s–90s",,"Lafon, Louis",French,1870,1899,ca. 1880,1875,1885,Albumen silver print from glass negative,Image: 37 x 46.6 cm (14 9/16 x 18 3/8 in.),"Gift of Charles Isaacs, 2008",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/289719,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2002.9,false,true,284818,Photographs,Photograph,"[Angel of the Passion, Sainte-Chapelle, Paris]",,,,,,Artist,,Auguste Mestral,"French, Rans 1812–1884 Rans",,"Mestral, Auguste",French,1812,1884,1852–53,1852,1853,Salted paper print from paper negative,32.9 x 20.3 cm (12 15/16 x 8 in.),"Gilman Collection, Purchase, The Howard Gilman Foundation Gift, 2002",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/284818,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2000.290,false,true,283761,Photographs,Photograph,"[Sculpture of Virgin and Child, Notre Dame, Paris]",,,,,,Artist,,Auguste Mestral,"French, Rans 1812–1884 Rans",,"Mestral, Auguste",French,1812,1884,ca. 1851,1850,1852,Salted paper print from paper negative,35.1 x 27.6 cm (13 13/16 x 10 7/8 in. ),"Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2000",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/283761,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.1,true,true,283121,Photographs,Photograph,[Woman Seen from the Back],,,,,,Artist,,Onésipe Aguado de las Marismas,"French, Evry 1830–1893 Paris",,"Aguado de las Marismas, Onésipe",French,1830,1893,ca. 1862,1860,1864,Salted paper print from glass negative,Image: 30.8 × 25.7 cm (12 1/8 × 10 1/8 in.) Mount: 39.4 × 31 cm (15 1/2 × 12 3/16 in.),"Gilman Collection, Purchase, Joyce F. Menschel Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/283121,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.715,false,true,286694,Photographs,Photograph,[Rustic Building with Man under Trellis],,,,,,Artist,,André Giroux,"French, Paris 1801–1879 Paris",,"Giroux, André",French,1801,1879,ca. 1853,1851,1855,Salted paper print from glass negative,Image: 8 11/16 × 11 3/16 in. (22.1 × 28.4 cm) Mount: 18 9/16 × 23 13/16 in. (47.1 × 60.5 cm),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286694,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1997.107,false,true,282181,Photographs,Photograph,Rue Neuve-Coquenard (from the Rue Lamartine),,,,,,Artist,,Charles Marville,"French, Paris 1813–1879 Paris",,"Marville, Charles",French,1813,1879,1870s,1870,1879,Albumen silver print,32.6 x 27 cm (12 13/16 x 10 5/8 in. ),"Purchase, Harris Brisbane Dick Fund and Warner Communications Inc. Purchase Fund, by exchange, 1997",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/282181,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2000.292,false,true,283734,Photographs,Photograph,"[South Portal, Chartres Cathedral]",,,,,,Artist,,Charles Marville,"French, Paris 1813–1879 Paris",,"Marville, Charles",French,1813,1879,1854,1854,1854,Salted paper print from paper negative,21.5 x 15.5 cm (8 7/16 x 6 1/8 in.),"Purchase, Jennifer and Joseph Duke Gift and The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2000",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/283734,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.167,false,true,288026,Photographs,Photograph,Arts et Métiers (Ancien Modèle),,,,,,Artist,,Charles Marville,"French, Paris 1813–1879 Paris",,"Marville, Charles",French,1813,1879,1864,1864,1864,Albumen silver print from glass negative,Image: 36.6 x 24.1 cm (14 7/16 x 9 1/2 in.) Mount: 63 x 45 cm (24 13/16 x 17 11/16 in.),"Purchase, Alfred Stieglitz Society Gifts, 2007",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288026,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2008.668,false,true,289721,Photographs,Photograph,Rue du Haut-Pave (Pantheon in Distance),,,,,,Artist,,Charles Marville,"French, Paris 1813–1879 Paris",,"Marville, Charles",French,1813,1879,1865–69,1865,1869,Albumen silver print from glass negative,Image: 20.6 x 37 cm (8 1/8 x 14 9/16 in.) Mount: 42.4 x 50.5 cm (16 11/16 x 19 7/8 in.),"Gift of Howard Greenberg, Paula and Robert Hershkowitz, Charles Isaacs, and Hans P. Kraus Jr., in honor of Philippe de Montebello, 2008",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/289721,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.1141,false,true,264863,Photographs,Photograph,[Rue de Constantine],,,,,,Artist,,Charles Marville,"French, Paris 1813–1879 Paris",,"Marville, Charles",French,1813,1879,ca. 1865,1863,1867,Albumen silver print from glass negative,27.3 x 36.8 cm (10 3/4 x 14 1/2 in.),"Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 1986",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/264863,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1987.1094,false,true,265131,Photographs,Photographs,[Cloud Study over Paris],,,,,,Artist,,Charles Marville,"French, Paris 1813–1879 Paris",,"Marville, Charles",French,1813,1879,1850s,1850,1859,Albumen silver print,16.7 x 20.6 cm. (6 9/16 x 8 1/8 in.),"Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 1987",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/265131,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1988.1071,false,true,265932,Photographs,Photograph,La Bièvre,,,,,,Artist,,Charles Marville,"French, Paris 1813–1879 Paris",,"Marville, Charles",French,1813,1879,ca. 1865,1863,1867,Albumen silver print from glass negative,27.8 x 37.6 cm. (10 15/16 x 14 13/16 in.),"Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 1988",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/265932,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2010.513.2,false,true,283713,Photographs,Photograph,Rue Traversine (from the Rue d'Arras),,,,,,Artist,,Charles Marville,"French, Paris 1813–1879 Paris",,"Marville, Charles",French,1813,1879,ca. 1868,1866,1870,Albumen silver print from glass negative,34.8 x 27.5 cm (13 11/16 x 10 13/16 in. ),"Gift of Howard Stein, 2010",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/283713,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2010.513.3,false,true,283714,Photographs,Photograph,Rue du Chat-qui-Pêche (from the Rue de la Huchette),,,,,,Artist,,Charles Marville,"French, Paris 1813–1879 Paris",,"Marville, Charles",French,1813,1879,ca. 1868,1866,1870,Albumen silver print from glass negative,35.9 x 27.2 cm (14 1/8 x 10 11/16 in. ),"Gift of Howard Stein, 2010",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/283714,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.205,false,true,286547,Photographs,Photograph,Le Chat Momifié (trouvé dans les fouilles de Saint-Germain-en-Laye),,,,,,Artist,,Charles Marville,"French, Paris 1813–1879 Paris",,"Marville, Charles",French,1813,1879,ca. 1862,1860,1864,Albumen silver print from glass negative,Mount: 19 13/16 × 9 15/16 in. (50.3 × 25.3 cm) Image: 11 1/2 × 11 1/4 in. (29.2 × 28.6 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286547,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.351,false,true,286546,Photographs,Photograph,Etude de ciel,,,,,,Artist,,Charles Marville,"French, Paris 1813–1879 Paris",,"Marville, Charles",French,1813,1879,1855–56,1855,1856,Salted paper print from glass negative,Image: 16 x 21 cm (6 5/16 x 8 1/4 in.) Mount: 25.9 x 31 cm (10 3/16 x 12 3/16 in.),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286546,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.353,false,true,286336,Photographs,Photograph,Etude de ciel,,,,,,Artist,,Charles Marville,"French, Paris 1813–1879 Paris",,"Marville, Charles",French,1813,1879,1855–56,1855,1856,Albumen silver print from glass negative,Image: 15.4 x 21 cm (6 1/16 x 8 1/4 in.) Mount: 31.1 x 43.5 cm (12 1/4 x 17 1/8 in.),"Gilman Collection, Purchase, Ann Tenenbaum and Thomas H. Lee Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286336,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.355,false,true,286338,Photographs,Photographs,"Rue du Contrat-Social, de la rue de la Tonnellerie",,,,,,Artist,,Charles Marville,"French, Paris 1813–1879 Paris",,"Marville, Charles",French,1813,1879,1864–1865,1864,1865,Albumen silver print from glass negative,Image: 22.5 x 37.1 cm (8 7/8 x 14 5/8 in.) Mount: 41.3 x 60.3 cm (16 1/4 x 23 3/4 in.),"Gilman Collection, Purchase, Mr. and Mrs. Henry R. Kravis Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286338,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.357,false,true,286340,Photographs,Photograph,"[Allegorical Sculpture of Industry, Pont du Carrousel]",,,,,,Artist,,Charles Marville,"French, Paris 1813–1879 Paris",,"Marville, Charles",French,1813,1879,1852,1852,1852,Salted paper print from paper negative,Image: 21.1 x 15.2 cm (8 5/16 x 6 in.) Mount: 42.7 x 27.8 cm (16 13/16 x 10 15/16 in.),"Gilman Collection, Purchase, Mr. and Mrs. Henry R. Kravis Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286340,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.358,false,true,285698,Photographs,Photograph,"Rue Estienne, de la rue Boucher",,,,,,Artist,,Charles Marville,"French, Paris 1813–1879 Paris",,"Marville, Charles",French,1813,1879,1862–65,1862,1865,Albumen silver print from glass negative,Image: 34.3 x 27.1 cm (13 1/2 x 10 11/16 in.) Mount: 23 11/16 × 16 5/16 in. (60.2 × 41.4 cm),"Gilman Collection, Purchase, Mr. and Mrs. Henry R. Kravis Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285698,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.362,false,true,285699,Photographs,Photograph,Impasse Briare (de la Cité Coquenard),,,,,,Artist,,Charles Marville,"French, Paris 1813–1879 Paris",,"Marville, Charles",French,1813,1879,1860s,1860,1869,Albumen silver print from glass negative,Image: 62.9 x 45.2 cm (24 3/4 x 17 13/16 in.) Mount: 24 13/16 × 17 11/16 in. (63 × 45 cm),"Gilman Collection, Purchase, Mr. and Mrs. Henry R. Kravis Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285699,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.378,false,true,286545,Photographs,Photograph,Cour Saint-Guillaume,,,,,,Artist,,Charles Marville,"French, Paris 1813–1879 Paris",,"Marville, Charles",French,1813,1879,ca. 1865,1863,1867,Albumen silver print from glass negative,Image: 34.2 x 27.2 cm (13 7/16 x 10 11/16 in.) Mount: 62.9 x 45.2 cm (24 3/4 x 17 13/16 in.),"Gilman Collection, Purchase, Alfred Stieglitz Society Gifts, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286545,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1990.1130,true,true,266284,Photographs,Photograph,"[Large Figures on the North Porch, Chartres Cathedral]",,,,,,Artist,,Henri-Jean-Louis Le Secq,"French, Paris 1818–1882 Paris",,"Le Secq, Henri-Jean-Louis",French,1818,1882,1852,1852,1852,Salted paper print from paper negative,32.8 x 22.1 cm (12 15/16 x 8 11/16 in.),"Purchase, The Howard Gilman Foundation and Harriette and Noel Levine Gifts, Samuel J. Wagstaff Jr. Bequest, and Rogers Fund, 1990",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/266284,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.35,false,true,283108,Photographs,Photograph,Wooden Staircase at Chartres,,,,,,Artist,,Henri-Jean-Louis Le Secq,"French, Paris 1818–1882 Paris",,"Le Secq, Henri-Jean-Louis",French,1818,1882,1852,1852,1852,Salted paper print from paper negative,Mount: 19 13/16 in. × 13 7/8 in. (50.3 × 35.2 cm) Image: 12 13/16 × 9 3/16 in. (32.6 × 23.4 cm),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/283108,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -63.661.1,false,true,270873,Photographs,Carte-de-visite,Alphonse Karr,,,,,,Artist,,André-Adolphe-Eugène Disdéri,"French, Paris 1819–1889 Paris",,"Disdéri, André-Adolphe-Eugène",French,1819,1889,1850s,1850,1859,Albumen silver print from glass negative,,"David Hunter McAlpin Fund, 1963",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/270873,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -47.149.40,false,true,269111,Photographs,Carte-de-visite,The Imperial Court of Napoleon III,,,,,,Artist,,André-Adolphe-Eugène Disdéri,"French, Paris 1819–1889 Paris",,"Disdéri, André-Adolphe-Eugène",French,1819,1889,"ca. 1866, printed 1940s",1861,1871,Gelatin silver print,Image: 12.8 x 8 cm (5 1/16 x 3 1/8 in.) Frame: 35.6 x 27.9 cm (14 x 11 in.),"David Hunter McAlpin Fund, 1947",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269111,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1995.170.1,false,true,267168,Photographs,Photograph,Prince Lobkowitz,,,,,,Artist,,André-Adolphe-Eugène Disdéri,"French, Paris 1819–1889 Paris",,"Disdéri, André-Adolphe-Eugène",French,1819,1889,1858,1858,1858,Albumen silver print from glass negative,20.0 x 23.2 cm (7 7/8 x 9 1/8 in.),"Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 1995",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267168,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.10,false,true,261269,Photographs,Photograph,La taille,,,,,,Artist,,André-Adolphe-Eugène Disdéri,"French, Paris 1819–1889 Paris",,"Disdéri, André-Adolphe-Eugène",French,1819,1889,1860s,1860,1869,Albumen silver print from glass negative,5.1 x 8.6 cm. (2 x 3 3/8 in.),"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261269,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.11,false,true,261280,Photographs,Photograph,Le deux roses,,,,,,Artist,,André-Adolphe-Eugène Disdéri,"French, Paris 1819–1889 Paris",,"Disdéri, André-Adolphe-Eugène",French,1819,1889,1860s,1860,1869,Albumen silver print from glass negative,5.1 x 8.6 cm. (2 x 3 3/8 in.),"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261280,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.12,false,true,261291,Photographs,Photograph,La robe écossaise,,,,,,Artist,,André-Adolphe-Eugène Disdéri,"French, Paris 1819–1889 Paris",,"Disdéri, André-Adolphe-Eugène",French,1819,1889,1860s,1860,1869,Albumen silver print from glass negative,5.1 x 8.6 cm. (2 x 3 3/8 in.),"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261291,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.242,false,true,286378,Photographs,Photographs,[Profile of the Prince Imperial],,,,,,Artist,,André-Adolphe-Eugène Disdéri,"French, Paris 1819–1889 Paris",,"Disdéri, André-Adolphe-Eugène",French,1819,1889,1863,1863,1863,Albumen silver print from glass negative,Image: 25.1 x 18.3 cm (9 7/8 x 7 3/16 in.) Mount: 28.2 x 20.5 cm (11 1/8 x 8 1/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286378,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.284,false,true,286594,Photographs,Photograph,The Juggler Manoel,,,,,,Artist,,André-Adolphe-Eugène Disdéri,"French, Paris 1819–1889 Paris",,"Disdéri, André-Adolphe-Eugène",French,1819,1889,1861,1861,1861,Albumen silver print from glass negative,Image: 19.9 × 23.2 cm (7 13/16 × 9 1/8 in.),"Gilman Collection, Purchase, Ann Tenenbaum and Thomas H. Lee Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286594,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.1163,false,true,286389,Photographs,Photograph,Caroline Rosati,,,,,,Artist,,André-Adolphe-Eugène Disdéri,"French, Paris 1819–1889 Paris",,"Disdéri, André-Adolphe-Eugène",French,1819,1889,ca. 1860,1858,1862,Salted paper print from glass negative,Image: 29.8 × 23.6 cm (11 3/4 × 9 5/16 in.),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286389,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.588.4,false,true,294508,Photographs,Photograph,Dulare,,,,,,Artist,,André-Adolphe-Eugène Disdéri,"French, Paris 1819–1889 Paris",,"Disdéri, André-Adolphe-Eugène",French,1819,1889,February 1865–75,1865,1875,Albumen silver print from glass negative,Image: 20.3 × 12.1 cm (8 × 4 3/4 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/294508,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.588.5,false,true,294509,Photographs,Photograph,Honoune Keller,,,,,,Artist,,André-Adolphe-Eugène Disdéri,"French, Paris 1819–1889 Paris",,"Disdéri, André-Adolphe-Eugène",French,1819,1889,1864–65,1864,1865,Albumen silver print from glass negative,Image: 19.4 × 12.5 cm (7 5/8 × 4 15/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/294509,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.588.6,false,true,294510,Photographs,Photograph,Louis Revoil,,,,,,Artist,,André-Adolphe-Eugène Disdéri,"French, Paris 1819–1889 Paris",,"Disdéri, André-Adolphe-Eugène",French,1819,1889,February 1865–75,1865,1875,Albumen silver print from glass negative,Image: 19.5 × 24.5 cm (7 11/16 × 9 5/8 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/294510,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.588.7,false,true,294511,Photographs,Photograph,"du Beaumenil, Forsyth",,,,,,Artist,,André-Adolphe-Eugène Disdéri,"French, Paris 1819–1889 Paris",,"Disdéri, André-Adolphe-Eugène",French,1819,1889,"October 1862–February 11, 1863",1862,1863,Albumen silver print from glass negative,Image: 19.3 × 23.6 cm (7 5/8 × 9 5/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/294511,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.588.8,false,true,294512,Photographs,Photograph,Berthe,,,,,,Artist,,André-Adolphe-Eugène Disdéri,"French, Paris 1819–1889 Paris",,"Disdéri, André-Adolphe-Eugène",French,1819,1889,"January 22, 1862– April 1862",1862,1862,Albumen silver print from glass negative,Image: 20.1 × 23.3 cm (7 15/16 × 9 3/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/294512,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.588.9,false,true,294513,Photographs,Photograph,Berthe,,,,,,Artist,,André-Adolphe-Eugène Disdéri,"French, Paris 1819–1889 Paris",,"Disdéri, André-Adolphe-Eugène",French,1819,1889,"January 22, 1862–April 1862",1862,1862,Albumen silver print from glass negative,Image: 18.5 × 22.7 cm (7 5/16 × 8 15/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/294513,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.588.10,false,true,294514,Photographs,Photograph; Carte-de-visite,Schneider,,,,,,Artist,,André-Adolphe-Eugène Disdéri,"French, Paris 1819–1889 Paris",,"Disdéri, André-Adolphe-Eugène",French,1819,1889,May–August 1863,1863,1863,Albumen silver print from glass negative,Image: 18.8 × 24.3 cm (7 3/8 × 9 9/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/294514,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.588.11,false,true,294515,Photographs,Photograph; Carte-de-visite,V Queniaus,,,,,,Artist,,André-Adolphe-Eugène Disdéri,"French, Paris 1819–1889 Paris",,"Disdéri, André-Adolphe-Eugène",French,1819,1889,May–June 1860,1860,1860,Albumen silver print from glass negative,Image: 19.9 × 23.1 cm (7 13/16 × 9 1/8 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/294515,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.588.12,false,true,294727,Photographs,Photograph; Carte-de-visite,Taglione,,,,,,Artist,,André-Adolphe-Eugène Disdéri,"French, Paris 1819–1889 Paris",,"Disdéri, André-Adolphe-Eugène",French,1819,1889,September 1857–November 1858,1857,1858,Albumen silver print from glass negative,Image: 19.9 × 23.3 cm (7 13/16 × 9 3/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/294727,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.588.13,false,true,294728,Photographs,Photograph; Carte-de-visite,Danvers,,,,,,Artist,,André-Adolphe-Eugène Disdéri,"French, Paris 1819–1889 Paris",,"Disdéri, André-Adolphe-Eugène",French,1819,1889,"February–July 14, 1864",1864,1864,Albumen silver print from glass negative,Image: 20 × 24.2 cm (7 7/8 × 9 1/2 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/294728,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.588.14,false,true,294729,Photographs,Photograph; Carte-de-visite,A Mlle Schlusser,,,,,,Artist,,André-Adolphe-Eugène Disdéri,"French, Paris 1819–1889 Paris",,"Disdéri, André-Adolphe-Eugène",French,1819,1889,May–August 1861,1861,1861,Albumen silver print from glass negative,Image: 19.9 × 23.7 cm (7 13/16 × 9 5/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/294729,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.588.2.2,false,true,306153,Photographs,Photograph,Louise Abigdon,,,,,,Artist,,André-Adolphe-Eugène Disdéri,"French, Paris 1819–1889 Paris",,"Disdéri, André-Adolphe-Eugène",French,1819,1889,1866,1866,1866,Albumen silver print from glass negative,"Image: 18.4 × 24.8 cm (7 1/4 in., 24.8 cm) Sheet: 26.2 × 34.8 cm (10 5/16 × 13 11/16 in.)","Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306153,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.588.3.5,false,true,306158,Photographs,Photograph,Finali,,,,,,Artist,,André-Adolphe-Eugène Disdéri,"French, Paris 1819–1889 Paris",,"Disdéri, André-Adolphe-Eugène",French,1819,1889,1866,1866,1866,Albumen silver print from glass negative,Image: 7 3/4 × 9 1/8 in. (19.7 × 23.1 cm) Album page: 10 3/8 × 13 3/4 in. (26.3 × 35 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306158,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.588.2.53,false,true,306154,Photographs,Photograph,Beresford,,,,,,Artist,,André-Adolphe-Eugène Disdéri,"French, Paris 1819–1889 Paris",,"Disdéri, André-Adolphe-Eugène",French,1819,1889,1867,1867,1867,Albumen silver print from glass negative,Image: 18.4 × 24.8 cm (7 1/4 × 9 3/4 in.) Album page: 26.2 × 34.8 cm (10 5/16 × 13 11/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306154,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.588.2.78,false,true,306155,Photographs,Photograph,Berthe,,,,,,Artist,,André-Adolphe-Eugène Disdéri,"French, Paris 1819–1889 Paris",,"Disdéri, André-Adolphe-Eugène",French,1819,1889,1862,1862,1862,Albumen silver print from glass negative,Image: 18.4 × 24.8 cm (7 1/4 × 9 3/4 in.) Album page: 26.2 × 34.8 cm (10 5/16 × 13 11/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306155,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.588.3.10,false,true,306159,Photographs,Photograph,Gabrielle; M. Gutierrez de Estrada,,,,,,Artist,,André-Adolphe-Eugène Disdéri,"French, Paris 1819–1889 Paris",,"Disdéri, André-Adolphe-Eugène",French,1819,1889,1867,1867,1867,Albumen silver print from glass negative,Image: 7 9/16 × 9 1/4 in. (19.2 × 23.5 cm) Sheet: 10 3/8 × 13 3/4 in. (26.3 × 35 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306159,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.588.3.23,false,true,306160,Photographs,Photograph,Héloise,,,,,,Artist,,André-Adolphe-Eugène Disdéri,"French, Paris 1819–1889 Paris",,"Disdéri, André-Adolphe-Eugène",French,1819,1889,1863,1863,1863,Albumen silver print from glass negative,Image: 7 9/16 × 9 1/4 in. (19.2 × 23.5 cm) Album page: 10 3/8 × 13 3/4 in. (26.3 × 35 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306160,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.588.3.35,false,true,306161,Photographs,Photograph,Valois,,,,,,Artist,,André-Adolphe-Eugène Disdéri,"French, Paris 1819–1889 Paris",,"Disdéri, André-Adolphe-Eugène",French,1819,1889,1866,1866,1866,Albumen silver print from glass negative,Image: 7 9/16 × 9 1/4 in. (19.2 × 23.5 cm) Album page: 10 3/8 × 13 3/4 in. (26.3 × 35 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306161,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.588.3.74,false,true,306162,Photographs,Photograph,Rosalie Léon,,,,,,Artist,,André-Adolphe-Eugène Disdéri,"French, Paris 1819–1889 Paris",,"Disdéri, André-Adolphe-Eugène",French,1819,1889,1866,1866,1866,Albumen silver print from glass negative,Image: 7 1/2 × 10 1/16 in. (19 × 25.6 cm) Album page: 10 3/8 × 13 3/4 in. (26.3 × 35 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306162,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.588.3.85,false,true,306163,Photographs,Photograph,Mangin,,,,,,Artist,,André-Adolphe-Eugène Disdéri,"French, Paris 1819–1889 Paris",,"Disdéri, André-Adolphe-Eugène",French,1819,1889,1861,1861,1861,Albumen silver print from glass negative,Image: 7 1/2 × 9 7/16 in. (19 × 24 cm) Album page: 10 3/8 × 13 3/4 in. (26.3 × 35 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306163,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2011.531.1–.164,false,true,299315,Photographs,Album,Costumes V,,,,,,Artist,,André-Adolphe-Eugène Disdéri,"French, Paris 1819–1889 Paris",,"Disdéri, André-Adolphe-Eugène",French,1819,1889,1863–67,1863,1867,Albumen silver prints from glass negatives,,"Museum Accession, transferred from the Costume Institute Library",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/299315,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.588.2.106,false,true,306156,Photographs,Photograph,Boudet,,,,,,Artist,,André-Adolphe-Eugène Disdéri,"French, Paris 1819–1889 Paris",,"Disdéri, André-Adolphe-Eugène",French,1819,1889,1866,1866,1866,Albumen silver print from glass negative,Image: 18.4 × 24.8 cm (7 1/4 × 9 3/4 in.) Album page: 26.2 × 34.8 cm (10 5/16 × 13 11/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306156,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.588.2.142,false,true,306157,Photographs,Photograph,Esther David,,,,,,Artist,,André-Adolphe-Eugène Disdéri,"French, Paris 1819–1889 Paris",,"Disdéri, André-Adolphe-Eugène",French,1819,1889,1866,1866,1866,Albumen silver print from glass negative,Image: 18.4 × 24.8 cm (7 1/4 × 9 3/4 in.) Album page: 26.2 × 34.8 cm (10 5/16 × 13 11/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306157,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.588.3.109,false,true,306164,Photographs,Photograph,Amélie and Elise Gitteri,,,,,,Artist,,André-Adolphe-Eugène Disdéri,"French, Paris 1819–1889 Paris",,"Disdéri, André-Adolphe-Eugène",French,1819,1889,1866,1866,1866,Albumen silver print from glass negative,Image: 7 1/2 × 9 7/16 in. (19 × 24 cm) Album page: 10 3/8 × 13 3/4 in. (26.3 × 35 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306164,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.588.3.110,false,true,306165,Photographs,Photograph,Clara Silvois,,,,,,Artist,,André-Adolphe-Eugène Disdéri,"French, Paris 1819–1889 Paris",,"Disdéri, André-Adolphe-Eugène",French,1819,1889,1861,1861,1861,Albumen silver print from glass negative,Image: 7 1/2 × 9 7/16 in. (19 × 24 cm) Album page: 10 3/8 × 13 3/4 in. (26.3 × 35 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306165,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.588.3.134,false,true,306166,Photographs,Photograph,A. Sardou,,,,,,Artist,,André-Adolphe-Eugène Disdéri,"French, Paris 1819–1889 Paris",,"Disdéri, André-Adolphe-Eugène",French,1819,1889,1867,1867,1867,Albumen silver print from glass negative,Image: 7 1/2 × 9 7/16 in. (19 × 24 cm) Album page: 10 3/8 × 13 3/4 in. (26.3 × 35 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306166,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.588.3.152,false,true,306167,Photographs,Photograph,Léontine Walter,,,,,,Artist,,André-Adolphe-Eugène Disdéri,"French, Paris 1819–1889 Paris",,"Disdéri, André-Adolphe-Eugène",French,1819,1889,1863,1863,1863,Albumen silver print from glass negative,Image: 7 1/2 × 9 1/4 in. (19 × 23.5 cm) Album page: 10 3/8 × 13 3/4 in. (26.3 × 35 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306167,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1993.233,false,true,266902,Photographs,Photograph,Gioacchino Rossini,,,,,,Artist,,Nadar,"French, Paris 1820–1910 Paris",,Nadar,French,1820,1910,March 1856,1856,1856,Salted paper print from glass negative,Image: 24.6 x 18.3 cm (9 11/16 x 7 3/16 in.),"Purchase, Marie-Thérèse and André Jammes and Annalee Newman Gifts, The Horace W. Goldsmith Foundation Gift through Joyce and Robert Menschel, and Nancy and Edwin Marks Gift, 1993",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/266902,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1991.1174,false,true,266459,Photographs,Photograph,[Standing Female Nude],,,,,,Artist,,Nadar,"French, Paris 1820–1910 Paris",,Nadar,French,1820,1910,1860–61,1860,1861,Salted paper print from glass negative,Image: 20.2 x 13.3 cm (7 15/16 x 5 1/4 in.),"Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 1991",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/266459,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1991.1198,true,true,266480,Photographs,Photograph,Eugène Pelletan,,,,,,Artist,,Nadar,"French, Paris 1820–1910 Paris",,Nadar,French,1820,1910,1855–59,1855,1859,Salted paper print from glass negative,23.5 x 17.6 cm (9 1/4 x 6 15/16 in.),"Purchase, The Howard Gilman Foundation Gift and Rogers Fund, 1991",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/266480,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1991.1199,false,true,266481,Photographs,Photograph,Pierre-Luc-Charles Cicéri,,,,,,Artist,,Nadar,"French, Paris 1820–1910 Paris",,Nadar,French,1820,1910,1855–60,1855,1860,Salted paper print from glass negative,23.5 x 18.7 cm. (9 1/4 x 7 3/8 in.),"Gift of Marie-Thérèse and André Jammes, in memory of Samuel J. Wagstaff Jr., 1991",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/266481,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -58.553.28,false,true,270013,Photographs,Cabinet card,Jean-François Millet,,,,,,Artist,,Nadar,"French, Paris 1820–1910 Paris",,Nadar,French,1820,1910,"1856–58, printed ca. 1900",1856,1858,Albumen silver print from glass negative,,"Gift of M. Knoedler & Co. Inc., 1958",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/270013,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2010.457.1,false,true,294431,Photographs,Photograph,Hermaphrodite,,,,,,Artist,,Nadar,"French, Paris 1820–1910 Paris",,Nadar,French,1820,1910,1860,1860,1860,Albumen silver print from glass negative,Image: 23.9 x 19.2 cm (9 7/16 x 7 9/16 in.),"Bequest of Robert Shapazian, 2010",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/294431,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.42,false,true,283116,Photographs,Photograph,"[Seated Model, Partially Draped]",,,,,,Artist,,Nadar,"French, Paris 1820–1910 Paris",,Nadar,French,1820,1910,1856–59,1856,1859,Salted paper print from glass negative,Image: 11.2 x 10.5 cm (4 7/16 x 4 1/8 in.) Mount: 12.2 x 10.8 cm (4 13/16 x 4 1/4 in.),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/283116,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.159.46,false,true,306329,Photographs,Photograph,Jean-Francois Millet,,,,,,Artist,,Nadar,"French, Paris 1820–1910 Paris",,Nadar,French,1820,1910,1856–58,1856,1858,Salted paper print from paper negative,Mount: 15 7/8 in. × 11 5/8 in. (40.4 × 29.5 cm) Image: 10 9/16 × 8 3/8 in. (26.8 × 21.3 cm),"Bequest of Maurice B. Sendak, 2012",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306329,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.256,false,true,285631,Photographs,Photograph,Théophile Gautier,,,,,,Artist,,Nadar,"French, Paris 1820–1910 Paris",,Nadar,French,1820,1910,ca. 1856,1854,1858,Salted paper print from glass negative,Image: 24.8 x 19.7 cm (9 3/4 x 7 3/4 in.) oval Mount: 26.9 x 21.7 cm (10 9/16 x 8 9/16 in.),"Gilman Collection, Purchase, Harriette and Noel Levine Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285631,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.267,false,true,285630,Photographs,Photograph,[Self Portrait in American Indian Costume],,,,,,Artist,,Nadar,"French, Paris 1820–1910 Paris",,Nadar,French,1820,1910,1863,1863,1863,Salted paper print from glass negative,"Image: 22.1 x 13.2 cm (8 11/16 x 5 3/16 in.), arched, unevenly trimmed Mount: 30.8 x 21.3 cm (12 1/8 x 8 3/8 in.)","Gilman Collection, Purchase, Harriette and Noel Levine Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285630,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.313,false,true,286163,Photographs,Photograph,"[Nadar with His Wife, Ernestine, in a Balloon]",,,,,,Artist,,Nadar,"French, Paris 1820–1910 Paris",,Nadar,French,1820,1910,"ca. 1865, printed 1890s",1863,1867,Gelatin silver print from glass negative,Image: 9 x 7.8 cm (3 9/16 x 3 1/16 in.) Mount: 23 x 19.9 cm (9 1/16 x 7 13/16 in.),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286163,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.723,false,true,286070,Photographs,Photograph,Jules Janin,,,,,,Artist,,Nadar,"French, Paris 1820–1910 Paris",,Nadar,French,1820,1910,ca. 1856,1854,1858,Albumen silver print from glass negative,Image: 9 1/4 × 7 3/8 in. (23.5 × 18.7 cm) Mount: 9 5/8 × 7 3/4 in. (24.5 × 19.7 cm),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286070,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.798,false,true,285632,Photographs,Photograph,[Paul Nadar at the Breast of His Wet Nurse],,,,,,Artist,,Nadar,"French, Paris 1820–1910 Paris",,Nadar,French,1820,1910,1856,1856,1856,Salted paper print from glass negative,Image: 10 15/16 × 8 7/8 in. (27.8 × 22.5 cm),"Gilman Collection, Purchase, Jennifer and Joseph Duke Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285632,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.813,false,true,286161,Photographs,Photograph,"Catacombs, Paris",,,,,,Artist,,Nadar,"French, Paris 1820–1910 Paris",,Nadar,French,1820,1910,April 1862,1862,1862,Albumen silver print from glass negative,Image: 23.7 x 18.6 cm (9 5/16 x 7 5/16 in.) Mount: 46 x 33.6 cm (18 1/8 x 13 1/4 in.),"Gilman Collection, Purchase, Denise and Andrew Saul Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286161,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1998.56,false,true,282191,Photographs,Photograph,Daniel Halévy,,,,,,Artist,,Edgar Degas,"French, Paris 1834–1917 Paris",,"Degas, Edgar",French,1834,1917,1895,1895,1895,Gelatin silver print from glass negative,40 x 28.7 cm (15 3/4 x 11 5/16 in.),"Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 1998",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/282191,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2004.335,false,true,285444,Photographs,Photograph,[Self-Portrait with Christine and Yvonne Lerolle],,,,,,Artist,,Edgar Degas,"French, Paris 1834–1917 Paris",,"Degas, Edgar",French,1834,1917,probably 1895–96,1895,1896,Gelatin silver print,Image: 37.1 x 29.3 cm (14 5/8 x 11 9/16 in.) Mount: 55.4 x 45.5 cm (21 13/16 x 17 15/16 in.),"Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel and Rogers Fund, 2004",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285444,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1983.1092,false,true,263608,Photographs,Photograph,"Paul Poujaud, Mme. Arthur Fontaine, and Degas",,,,,,Artist,,Edgar Degas,"French, Paris 1834–1917 Paris",,"Degas, Edgar",French,1834,1917,1895,1895,1895,Gelatin silver print,29.4 x 40.5 cm. (11 9/16 x 15 15/16 in.),"The Elisha Whittelsey Collection, The Elisha Whittelsey Fund, 1983",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/263608,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2000.655.1,false,true,284452,Photographs,Photograph,"Paule Gobillard, Jeannie Gobillard, Julie Manet, and Geneviève Mallarmé",,,,,,Artist,,Edgar Degas,"French, Paris 1834–1917 Paris",,"Degas, Edgar",French,1834,1917,1895,1895,1895,Gelatin silver print,28.4 x 38.9 cm (11 3/16 x 15 5/16 in.),"Gift of Paul F. Walter, 2000",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/284452,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2000.655.2,false,true,284453,Photographs,Photograph,"Street Scene, La-Queue-en-Brie (Val-de-Marne)",,,,,,Artist,,Edgar Degas,"French, Paris 1834–1917 Paris",,"Degas, Edgar",French,1834,1917,ca. 1896,1894,1898,Gelatin silver print,28.5 x 39.5 cm (11 1/4 x 15 9/16 in. ),"Gift of Paul F. Walter, 2000",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/284453,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2010.457.4a,false,true,294434,Photographs,Photograph,[Self-Portrait with Zoé Closier],,,,,,Artist,,Edgar Degas,"French, Paris 1834–1917 Paris",,"Degas, Edgar",French,1834,1917,probably 1895,1895,1895,Gelatin silver print,Image: 5.8 x 8.8 cm (2 5/16 x 3 7/16 in.) Mount: 15 x 12 cm (5 7/8 x 4 3/4 in.),"Bequest of Robert Shapazian, 2010",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/294434,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2010.457.4b,false,true,296287,Photographs,Photograph,[Self-Portrait in Library (Hand to Chin)],,,,,,Artist,,Edgar Degas,"French, Paris 1834–1917 Paris",,"Degas, Edgar",French,1834,1917,probably 1895,1895,1895,Gelatin silver print,Image: 5.8 x 8.7 cm (2 5/16 x 3 7/16 in.) Mount: 15 x 12 cm (5 7/8 x 4 3/4 in.),"Bequest of Robert Shapazian, 2010",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/296287,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.1167,false,true,286039,Photographs,Photograph,Denise Zola,,,,,,Artist,,Émile Zola,"French, Paris 1840–1902 Paris",,"Zola, Émile",French,1840,1902,ca. 1900,1898,1902,Gelatin silver print,Image: 8 7/8 × 6 9/16 in. (22.5 × 16.6 cm) Mount: 9 7/16 × 7 1/16 in. (24 × 18 cm),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286039,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.799 (1),false,true,288397,Photographs,Photograph,Porta del Popolo,,,,,,Artist,,Eugène Constant,"French, active Italy, 1848–55",,"Constant, Eugène",French,1848,1855,1848–52,1848,1852,Albumen print from glass negative,Image: 8 11/16 × 11 5/16 in. (22 × 28.7 cm) Sheet: 12 1/8 × 18 1/2 in. (30.8 × 47 cm),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288397,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.799 (2),false,true,288398,Photographs,Photograph,Piazza del Popolo,,,,,,Artist,,Eugène Constant,"French, active Italy, 1848–55",,"Constant, Eugène",French,1848,1855,1848–52,1848,1852,Albumen print from glass negative,Image: 8 11/16 × 11 5/16 in. (22 × 28.7 cm) Sheet: 12 1/8 × 18 1/2 in. (30.8 × 47 cm),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288398,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.799 (3),false,true,288399,Photographs,Photograph,Villa Medici,,,,,,Artist,,Eugène Constant,"French, active Italy, 1848–55",,"Constant, Eugène",French,1848,1855,1848–52,1848,1852,Albumen print from glass negative,Image: 8 11/16 × 11 5/16 in. (22 × 28.7 cm) Sheet: 12 1/8 × 18 1/2 in. (30.8 × 47 cm),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288399,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.799 (4),false,true,288400,Photographs,Photograph,Fontana di Trevi,,,,,,Artist,,Eugène Constant,"French, active Italy, 1848–55",,"Constant, Eugène",French,1848,1855,1848–52,1848,1852,Albumen print from glass negative,Image: 11 1/4 × 8 9/16 in. (28.6 × 21.7 cm) Sheet: 18 1/2 × 12 1/8 in. (47 × 30.8 cm),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288400,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.799 (5),false,true,288401,Photographs,Photograph,Monte Cavallo,,,,,,Artist,,Eugène Constant,"French, active Italy, 1848–55",,"Constant, Eugène",French,1848,1855,1848–52,1848,1852,Albumen print from glass negative,Image: 8 11/16 × 11 5/16 in. (22 × 28.7 cm) Sheet: 12 1/8 × 18 1/2 in. (30.8 × 47 cm),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288401,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.799 (6),false,true,288402,Photographs,Photograph,S. Giovanni Laterano,,,,,,Artist,,Eugène Constant,"French, active Italy, 1848–55",,"Constant, Eugène",French,1848,1855,1848–52,1848,1852,Albumen print from glass negative,Image: 8 11/16 × 11 5/16 in. (22 × 28.7 cm) Sheet: 12 1/8 × 18 1/2 in. (30.8 × 47 cm),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288402,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.799 (7),false,true,288403,Photographs,Photograph,Panteon,,,,,,Artist,,Eugène Constant,"French, active Italy, 1848–55",,"Constant, Eugène",French,1848,1855,1848–52,1848,1852,Albumen print from glass negative,Image: 8 11/16 × 11 5/16 in. (22 × 28.7 cm) Sheet: 12 1/8 × 18 1/2 in. (30.8 × 47 cm),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288403,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.799 (8),false,true,288404,Photographs,Photograph,Ponte e Castel S. Angelo,,,,,,Artist,,Eugène Constant,"French, active Italy, 1848–55",,"Constant, Eugène",French,1848,1855,1848–52,1848,1852,Albumen print from glass negative,Image: 8 11/16 × 11 5/16 in. (22 × 28.7 cm) Sheet: 12 1/8 × 18 1/2 in. (30.8 × 47 cm),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288404,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.799 (9),false,true,288405,Photographs,Photograph,Veduta di Castel St Angelo. S. Pietro,,,,,,Artist,,Eugène Constant,"French, active Italy, 1848–55",,"Constant, Eugène",French,1848,1855,1848–52,1848,1852,Albumen print from glass negative,Image: 8 11/16 × 11 5/16 in. (22 × 28.7 cm) Sheet: 12 1/8 × 18 1/2 in. (30.8 × 47 cm),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288405,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.799 (10),false,true,288406,Photographs,Photograph,S. Pietro in Vaticano,,,,,,Artist,,Eugène Constant,"French, active Italy, 1848–55",,"Constant, Eugène",French,1848,1855,1848–52,1848,1852,Albumen print from glass negative,Image: 8 11/16 × 11 5/16 in. (22 × 28.7 cm) Sheet: 12 1/8 × 18 1/2 in. (30.8 × 47 cm),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288406,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.799 (11),false,true,288407,Photographs,Photograph,Foro e Colonna di Trajano,,,,,,Artist,,Eugène Constant,"French, active Italy, 1848–55",,"Constant, Eugène",French,1848,1855,1848–52,1848,1852,Albumen print from glass negative,Image: 11 5/16 × 8 11/16 in. (28.8 × 22 cm) Sheet: 18 1/2 × 12 1/8 in. (47 × 30.8 cm),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288407,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.799 (12),false,true,288408,Photographs,Photograph,Tempio di Vesta,,,,,,Artist,,Eugène Constant,"French, active Italy, 1848–55",,"Constant, Eugène",French,1848,1855,1848–52,1848,1852,Albumen print from glass negative,Image: 6 5/16 × 8 7/16 in. (16 × 21.5 cm) Sheet: 12 1/8 × 18 1/2 in. (30.8 × 47 cm),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288408,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.799 (13),false,true,288409,Photographs,Photograph,Fontana delle Tartarughe,,,,,,Artist,,Eugène Constant,"French, active Italy, 1848–55",,"Constant, Eugène",French,1848,1855,1848–52,1848,1852,Albumen print from glass negative,Image: 11 5/16 × 8 11/16 in. (28.8 × 22 cm) Sheet: 18 1/2 × 12 1/8 in. (47 × 30.8 cm),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288409,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.799 (14),false,true,288410,Photographs,Photograph,"L'arco degli argentari, adossato alla chiesa di San Giorgio al Velabro",,,,,,Artist,,Eugène Constant,"French, active Italy, 1848–55",,"Constant, Eugène",French,1848,1855,1848–52,1848,1852,Albumen print from glass negative,Image: 11 5/16 × 8 11/16 in. (28.8 × 22 cm) Sheet: 18 1/2 × 12 1/8 in. (47 × 30.8 cm),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288410,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.799 (15),false,true,288411,Photographs,Photograph,Uno dei Colossi di Campedoglio,,,,,,Artist,,Eugène Constant,"French, active Italy, 1848–55",,"Constant, Eugène",French,1848,1855,1848–52,1848,1852,Albumen print from glass negative,Image: 11 5/16 × 8 11/16 in. (28.8 × 22 cm) Sheet: 18 1/2 × 12 1/8 in. (47 × 30.8 cm),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288411,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.799 (16),false,true,288412,Photographs,Photograph,Campedoglio,,,,,,Artist,,Eugène Constant,"French, active Italy, 1848–55",,"Constant, Eugène",French,1848,1855,1848–52,1848,1852,Albumen print from glass negative,Image: 8 11/16 × 11 5/16 in. (22 × 28.7 cm) Sheet: 12 1/8 × 18 1/2 in. (30.8 × 47 cm),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288412,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.799 (17),false,true,288413,Photographs,Photograph,Arco di Settimio Severo,,,,,,Artist,,Eugène Constant,"French, active Italy, 1848–55",,"Constant, Eugène",French,1848,1855,1848–52,1848,1852,Albumen print from glass negative,Image: 8 11/16 × 11 5/16 in. (22 × 28.7 cm) Sheet: 12 1/8 × 18 1/2 in. (30.8 × 47 cm),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288413,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.799 (18),false,true,288414,Photographs,Photograph,Veduta del Foro Romano,,,,,,Artist,,Eugène Constant,"French, active Italy, 1848–55",,"Constant, Eugène",French,1848,1855,1848–52,1848,1852,Albumen print from glass negative,Image: 8 11/16 × 11 5/16 in. (22 × 28.7 cm) Sheet: 12 1/8 × 18 1/2 in. (30.8 × 47 cm),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288414,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.799 (19),false,true,288415,Photographs,Photograph,Campo Vaccino (Foro Romano),,,,,,Artist,,Eugène Constant,"French, active Italy, 1848–55",,"Constant, Eugène",French,1848,1855,1848–52,1848,1852,Albumen print from glass negative,Image: 8 1/16 × 11 3/16 in. (20.5 × 28.4 cm) Sheet: 12 1/8 × 18 1/2 in. (30.8 × 47 cm),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288415,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.799 (20),false,true,288416,Photographs,Photograph,Arco di Giano,,,,,,Artist,,Eugène Constant,"French, active Italy, 1848–55",,"Constant, Eugène",French,1848,1855,1848–52,1848,1852,Albumen print from glass negative,Image: 8 11/16 × 11 5/16 in. (22 × 28.7 cm) Sheet: 12 1/8 × 18 1/2 in. (30.8 × 47 cm),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288416,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.799 (21),false,true,287890,Photographs,Photograph,Tempo della Fortuna Virile,,,,,,Artist,,Eugène Constant,"French, active Italy, 1848–55",,"Constant, Eugène",French,1848,1855,1848–52,1848,1852,Albumen print from glass negative,Image: 8 11/16 × 11 5/16 in. (22 × 28.7 cm) Sheet: 12 1/8 × 18 1/2 in. (30.8 × 47 cm),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287890,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.799 (22),false,true,288417,Photographs,Photograph,Tempio della Concordia,,,,,,Artist,,Eugène Constant,"French, active Italy, 1848–55",,"Constant, Eugène",French,1848,1855,1848–52,1848,1852,Albumen print from glass negative,Image: 8 11/16 × 11 5/16 in. (22 × 28.7 cm) Sheet: 12 1/8 × 18 1/2 in. (30.8 × 47 cm),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288417,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.799 (23),false,true,288418,Photographs,Photograph,Rovine del Foro,,,,,,Artist,,Eugène Constant,"French, active Italy, 1848–55",,"Constant, Eugène",French,1848,1855,1848–52,1848,1852,Albumen print from glass negative,Image: 11 5/16 × 8 11/16 in. (28.7 × 22 cm) Sheet: 18 1/2 × 12 1/8 in. (47 × 30.8 cm),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288418,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.799 (24),false,true,288419,Photographs,Photograph,Tempio di Antonino e Faustina,,,,,,Artist,,Eugène Constant,"French, active Italy, 1848–55",,"Constant, Eugène",French,1848,1855,1848–52,1848,1852,Albumen print from glass negative,Image: 11 5/16 × 8 11/16 in. (28.7 × 22 cm) Sheet: 18 1/2 × 12 1/8 in. (47 × 30.8 cm),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288419,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.799 (25),false,true,288420,Photographs,Photograph,Tempio della Pace,,,,,,Artist,,Eugène Constant,"French, active Italy, 1848–55",,"Constant, Eugène",French,1848,1855,1848–52,1848,1852,Albumen print from glass negative,Image: 8 11/16 × 11 5/16 in. (22 × 28.7 cm) Sheet: 12 1/8 × 18 1/2 in. (30.8 × 47 cm),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288420,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.799 (26),false,true,288421,Photographs,Photograph,Arco di Tito,,,,,,Artist,,Eugène Constant,"French, active Italy, 1848–55",,"Constant, Eugène",French,1848,1855,1848–52,1848,1852,Album print from glass negative,Image: 6 7/16 × 8 7/16 in. (16.4 × 21.5 cm) Sheet: 12 1/8 × 18 1/2 in. (30.8 × 47 cm),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288421,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.799 (27),false,true,288422,Photographs,Photograph,Bassorilievo dell'arco di Tito,,,,,,Artist,,Eugène Constant,"French, active Italy, 1848–55",,"Constant, Eugène",French,1848,1855,1848–52,1848,1852,Albumen print from glass negative,Image: 11 5/16 × 8 5/8 in. (28.8 × 21.9 cm) Sheet: 18 1/2 × 12 1/8 in. (47 × 30.8 cm),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288422,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.799 (28),false,true,288423,Photographs,Photograph,Bassorilievo dell'arco di Tito,,,,,,Artist,,Eugène Constant,"French, active Italy, 1848–55",,"Constant, Eugène",French,1848,1855,1848–52,1848,1852,Albumen print from glass negative,Image: 11 5/16 × 8 5/8 in. (28.8 × 21.9 cm) Sheet: 18 1/2 × 12 1/8 in. (47 × 30.8 cm),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288423,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.799 (29),false,true,288424,Photographs,Photograph,Colosseo (Anfiteatro di Flavio),,,,,,Artist,,Eugène Constant,"French, active Italy, 1848–55",,"Constant, Eugène",French,1848,1855,1848–52,1848,1852,Salted paper print from glass negative,Image: 6 7/16 × 8 11/16 in. (16.3 × 22 cm) Sheet: 12 1/8 × 18 1/2 in. (30.8 × 47 cm),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288424,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.799 (30),false,true,288425,Photographs,Photograph,Arco di Constantino,,,,,,Artist,,Eugène Constant,"French, active Italy, 1848–55",,"Constant, Eugène",French,1848,1855,1848–52,1848,1852,Albumen print from glass negative,Image: 8 11/16 × 11 5/16 in. (22.1 × 28.7 cm) Sheet: 12 1/8 × 18 1/2 in. (30.8 × 47 cm),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288425,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2011.315,false,true,294767,Photographs,Photograph,Ancienne Ferme à Pérenchies,,,,,,Artist,,Alphonse Le Blondel,"French, Bréhal 1814–1875 Lille",,"Le Blondel, Alphonse",French,1814,1875,1854,1854,1854,Salted paper print from paper negative,Image: 23.5 x 29.4 cm (9 1/4 x 11 9/16 in.) Mount: 26 x 37.4 cm (10 1/4 x 14 3/4 in.),"Purchase, Alfred Stieglitz Society Gifts, 2011",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/294767,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.31,false,true,283105,Photographs,Photograph,[Postmortem],,,,,,Artist,,Alphonse Le Blondel,"French, Bréhal 1814–1875 Lille",,"Le Blondel, Alphonse",French,1814,1875,ca. 1850,1848,1852,Daguerreotype,Image (visible): 3 1/2 × 4 11/16 in. (8.9 × 11.9 cm) Overall: 6 11/16 × 8 1/4 in. (17 × 21 cm),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/283105,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -58.648.3a,false,true,270074,Photographs,Photograph,Etienne Carjat,,,,,,Artist,,Étienne Carjat,"French, Fareins 1828–1906 Paris",,"Carjat, Étienne",French,1828,1906,1870s,1870,1879,Albumen silver print,,"Gift of Helen and Janos Scholz, 1958",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/270074,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.633.1,false,true,261621,Photographs,Photograph,Jean Baptiste Camille Corot,,,,,,Artist,,Étienne Carjat,"French, Fareins 1828–1906 Paris",,"Carjat, Étienne",French,1828,1906,1870,1870,1870,Albumen silver print,,"Gift of M. Roy Fisher, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261621,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2012.446.2,false,true,420964,Photographs,Print,"Henry Monnier, from Galerie contemporaine, littéraire, artistique",,,,,,Artist,,Étienne Carjat,"French, Fareins 1828–1906 Paris",,"Carjat, Étienne",French,1828,1906,1876–1881,1876,1881,Woodburytype,9 1/2 x 7 1/2 in. (24.1 x 19.1 cm),"Gift of Eric G. Carlson, in honor of Patricia Mainardi, 2012",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/420964,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.41,false,true,283115,Photographs,Photograph,[Seated Female Nude],,,,,,Artist,,Eugène Durieu,"French, Nîmes 1800–1874 Geneva",,"Durieu, Eugène",French,1800,1874,1853–54,1853,1854,Albumen silver print from glass negative,Image: 6 13/16 × 4 11/16 in. (17.3 × 11.9 cm) Mount: 13 13/16 × 10 9/16 in. (35.1 × 26.9 cm),"Gilman Collection, Purchase, Mr. and Mrs. Henry R. Kravis Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/283115,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.569,false,true,286255,Photographs,Photograph,[Nude Study],,,,,,Artist,,Eugène Durieu,"French, Nîmes 1800–1874 Geneva",,"Durieu, Eugène",French,1800,1874,1853–54,1853,1854,Albumen silver print from glass negative,Image: 20.5 x 12.8 cm (8 1/16 x 5 1/16 in.) Mount: 32.3 x 27 cm (12 11/16 x 10 5/8 in.),"Gilman Collection, Purchase, Mr. and Mrs. Henry R. Kravis Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286255,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.724,false,true,286140,Photographs,Photograph,[Nude],,,,,,Artist,Possibly by,Eugène Durieu,"French, Nîmes 1800–1874 Geneva",,"Durieu, Eugène",French,1800,1874,ca. 1851,1849,1853,Salted paper print from paper negative,4 5/16 x 6 1/8,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286140,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.254,false,true,286667,Photographs,Photograph,"Wassileh and Lhedeh, Ghawagea",,,,,,Artist,,Ernest Benecke,"German, born England, 1817–1894",,"Benecke, Ernest",French,1817,1894,1852,1852,1852,Salted paper print from paper negative,Mount: 17 5/8 in. × 12 7/16 in. (44.8 × 31.6 cm) Image: 8 9/16 × 6 7/8 in. (21.8 × 17.4 cm),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286667,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.257,false,true,285462,Photographs,Photograph,"[Children from the Village of Kalabshah, Nubia]",,,,,,Artist,,Ernest Benecke,"German, born England, 1817–1894",,"Benecke, Ernest",French,1817,1894,1852,1852,1852,Salted paper print from paper negative,Image: 17 x 21.5 cm (6 11/16 x 8 7/16 in.),"Gilman Collection, Purchase, Alfred Stieglitz Society Gifts, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285462,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.262,false,true,286501,Photographs,Photograph,"Autopsy of the First Crocodile Onboard, Upper Egypt",,,,,,Artist,,Ernest Benecke,"German, born England, 1817–1894",,"Benecke, Ernest",French,1817,1894,1852,1852,1852,Salted paper print from paper negative,Image: 17.5 x 21.3 cm (6 7/8 x 8 3/8 in.) Mount: 31.5 x 45.1 cm (12 3/8 x 17 3/4 in.),"Gilman Collection, Purchase, William Talbott Hillman Foundation Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286501,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.318,false,true,286268,Photographs,Photograph,[Two Women],,,,,,Artist,,Ernest Benecke,"German, born England, 1817–1894",,"Benecke, Ernest",French,1817,1894,1852,1852,1852,Salted paper print from paper negative,Image: 8 1/4 × 6 1/8 in. (21 × 15.6 cm) Mount: 17 1/16 × 12 3/16 in. (43.4 × 30.9 cm),"Gilman Collection, Purchase, Sam Salz Foundation Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286268,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.319,false,true,286666,Photographs,Photograph,Egyptian Musicians (Rawabí) and Almée,,,,,,Artist,,Ernest Benecke,"German, born England, 1817–1894",,"Benecke, Ernest",French,1817,1894,"February 20, 1852",1852,1852,Salted paper print from paper negative,Image: 6 9/16 × 8 1/8 in. (16.7 × 20.6 cm) Mount: 12 3/8 × 17 5/8 in. (31.4 × 44.8 cm),"Gilman Collection, Purchase, Joseph M. Cohen Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286666,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.320,false,true,286502,Photographs,Photograph,Abu Nabut and Negro Slaves in Cairo,,,,,,Artist,,Ernest Benecke,"German, born England, 1817–1894",,"Benecke, Ernest",French,1817,1894,"April 22, 1852",1852,1852,Salted paper print from paper negative,Image: 21.8 x 17.2 cm (8 9/16 x 6 3/4 in.) Mount: 44.6 x 31.7 cm (17 9/16 x 12 1/2 in.),"Gilman Collection, Purchase, Ann Tenenbaum and Thomas H. Lee Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286502,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -62.548,false,true,270485,Photographs,Photograph,"Shop front of ""Courone d'or,"" Quai Bourbon",,,,,,Artist,,Eugène Atget,"French, Libourne 1857–1927 Paris",,"Atget, Eugène",French,1857-02-02,1927-08-04,1922,1922,1922,Gelatin silver print,Image: 17.7 x 22.4 cm (6 15/16 x 8 13/16 in.),"David Hunter McAlpin Fund, 1962",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/270485,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1994.271,true,true,267042,Photographs,Photograph,Avenue des Gobelins,,,,,,Artist,,Eugène Atget,"French, Libourne 1857–1927 Paris",,"Atget, Eugène",French,1857-02-02,1927-08-04,1927,1927,1927,Gelatin silver print from glass negative,36.8 x 28.6 cm (14 1/2 x 11 1/4 in.),"Purchase, Rogers Fund, and Joyce and Robert Menschel and Harriette and Noel Levine Gifts, 1994",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267042,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -56.610.1,false,true,269874,Photographs,Photograph,St. Cloud,,,,,,Artist,,Eugène Atget,"French, Libourne 1857–1927 Paris",,"Atget, Eugène",French,1857-02-02,1927-08-04,"1915–19, printed 1956",1915,1919,Gelatin silver print,,"David Hunter McAlpin Fund, 1956",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269874,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -56.610.2,false,true,269885,Photographs,Photograph,Nenuphars,,,,,,Artist,,Eugène Atget,"French, Libourne 1857–1927 Paris",,"Atget, Eugène",French,1857-02-02,1927-08-04,"1890s–1920s, printed 1956",1890,1929,Gelatin silver print,,"David Hunter McAlpin Fund, 1956",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269885,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -56.610.3,false,true,269887,Photographs,Photograph,Men's Fashions,,,,,,Artist,,Eugène Atget,"French, Libourne 1857–1927 Paris",,"Atget, Eugène",French,1857-02-02,1927-08-04,"1925, printed 1956",1925,1925,Gelatin silver print,,"David Hunter McAlpin Fund, 1956",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269887,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -56.610.4,false,true,269888,Photographs,Photograph,Eclipse,,,,,,Artist,,Eugène Atget,"French, Libourne 1857–1927 Paris",,"Atget, Eugène",French,1857-02-02,1927-08-04,"1911, printed 1956",1911,1911,Gelatin silver print,,"David Hunter McAlpin Fund, 1956",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269888,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -56.610.5,false,true,269889,Photographs,Photograph,Paris Interior,,,,,,Artist,,Eugène Atget,"French, Libourne 1857–1927 Paris",,"Atget, Eugène",French,1857-02-02,1927-08-04,"ca. 1910, printed 1956",1908,1912,Gelatin silver print,,"David Hunter McAlpin Fund, 1956",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269889,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -56.610.6,false,true,269890,Photographs,Photograph,Pompe Funebre (1e Classe),,,,,,Artist,,Eugène Atget,"French, Libourne 1857–1927 Paris",,"Atget, Eugène",French,1857-02-02,1927-08-04,"1910, printed 1956",1910,1910,Gelatin silver print,,"David Hunter McAlpin Fund, 1956",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269890,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -56.610.7,false,true,269891,Photographs,Photograph,Carrousel,,,,,,Artist,,Eugène Atget,"French, Libourne 1857–1927 Paris",,"Atget, Eugène",French,1857-02-02,1927-08-04,"1923, printed 1956",1923,1923,Gelatin silver print,,"David Hunter McAlpin Fund, 1956",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269891,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -56.610.8,false,true,269892,Photographs,Photograph,Marchand Abat-Jours,,,,,,Artist,,Eugène Atget,"French, Libourne 1857–1927 Paris",,"Atget, Eugène",French,1857-02-02,1927-08-04,"1899–1900, printed 1956",1899,1900,Gelatin silver print,,"David Hunter McAlpin Fund, 1956",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269892,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -56.610.9,false,true,269893,Photographs,Photograph,"Rue St. Rustique, Montmartre",,,,,,Artist,,Eugène Atget,"French, Libourne 1857–1927 Paris",,"Atget, Eugène",French,1857-02-02,1927-08-04,"1922, printed 1956",1922,1922,Gelatin silver print,,"David Hunter McAlpin Fund, 1956",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269893,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.1202,false,true,265031,Photographs,Photograph,"A la Biche, rue Geoffrey Hilaire",,,,,,Artist,,Eugène Atget,"French, Libourne 1857–1927 Paris",,"Atget, Eugène",French,1857-02-02,1927-08-04,1922,1922,1922,Matte albumen silver print,,"Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 1986",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/265031,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.1031,false,true,266094,Photographs,Photograph,"Versailles, La Terre par Massou",,,,,,Artist,,Eugène Atget,"French, Libourne 1857–1927 Paris",,"Atget, Eugène",French,1857-02-02,1927-08-04,1922–23,1922,1923,Albumen silver print from glass negative,21.7 x 17.6 cm. (8 9/16 x 6 15/16 in.),"Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 1989",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/266094,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1991.1233,true,true,266538,Photographs,Photograph,"15, rue Maître-Albert",,,,,,Artist,,Eugène Atget,"French, Libourne 1857–1927 Paris",,"Atget, Eugène",French,1857-02-02,1927-08-04,1912,1912,1912,Gelatin silver print from glass negative,23.2 x 17.6 cm (9 1/8 x 6 15/16 in.),"Rogers Fund, 1991",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/266538,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1992.5116,false,true,266754,Photographs,Photograph,"Hôtel de Lauzun, Quai d'Anjou",,,,,,Artist,,Eugène Atget,"French, Libourne 1857–1927 Paris",,"Atget, Eugène",French,1857-02-02,1927-08-04,1904–05,1904,1905,Albumen silver print from glass negative,21.6 x 17.8 cm. (8 1/2 x 7 in.),"Gift of Virginia M. Zabriskie, 1992",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/266754,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1992.5152,true,true,266853,Photographs,Photograph,"Versailles, France",,,,,,Artist,,Eugène Atget,"French, Libourne 1857–1927 Paris",,"Atget, Eugène",French,1857-02-02,1927-08-04,1923,1923,1923,Albumen silver print from glass negative,17.8 x 21.9 cm (7 x 8 5/8 in.),"The Samuel J. Wagstaff Jr. Memorial and David Hunter McAlpin Funds, The Horace W. Goldsmith Foundation Gift through Joyce and Robert Menschel, and Paul F. Walter, and Mr. and Mrs. John Walsh Gifts, 1992",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/266853,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -56.610.10,false,true,269875,Photographs,Photograph,Maison Close,,,,,,Artist,,Eugène Atget,"French, Libourne 1857–1927 Paris",,"Atget, Eugène",French,1857-02-02,1927-08-04,"1921, printed 1956",1921,1921,Gelatin silver print,,"David Hunter McAlpin Fund, 1956",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269875,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -56.610.11,false,true,269876,Photographs,Photograph,Bar de Cabaret,,,,,,Artist,,Eugène Atget,"French, Libourne 1857–1927 Paris",,"Atget, Eugène",French,1857-02-02,1927-08-04,"1900–1911, printed 1956",1900,1911,Gelatin silver print,,"David Hunter McAlpin Fund, 1956",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269876,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -56.610.12,false,true,269877,Photographs,Photograph,Street Paver,,,,,,Artist,,Eugène Atget,"French, Libourne 1857–1927 Paris",,"Atget, Eugène",French,1857-02-02,1927-08-04,"1899–1900, printed 1956",1899,1900,Gelatin silver print,,"David Hunter McAlpin Fund, 1956",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269877,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -56.610.13,false,true,269878,Photographs,Photograph,"Cour, rue de Valence",,,,,,Artist,,Eugène Atget,"French, Libourne 1857–1927 Paris",,"Atget, Eugène",French,1857-02-02,1927-08-04,"1922, printed 1956",1922,1922,Gelatin silver print,,"David Hunter McAlpin Fund, 1956",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269878,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -56.610.14,false,true,269879,Photographs,Photograph,Ragpickers' Hut,,,,,,Artist,,Eugène Atget,"French, Libourne 1857–1927 Paris",,"Atget, Eugène",French,1857-02-02,1927-08-04,"1910, printed 1956",1910,1910,Gelatin silver print,,"David Hunter McAlpin Fund, 1956",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269879,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -56.610.15,false,true,269880,Photographs,Photograph,Mannequin,,,,,,Artist,,Eugène Atget,"French, Libourne 1857–1927 Paris",,"Atget, Eugène",French,1857-02-02,1927-08-04,"1926–27, printed 1956",1926,1927,Gelatin silver print,,"David Hunter McAlpin Fund, 1956",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269880,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -56.610.16,false,true,269881,Photographs,Photograph,Street Musicians,,,,,,Artist,,Eugène Atget,"French, Libourne 1857–1927 Paris",,"Atget, Eugène",French,1857-02-02,1927-08-04,"1898–99, printed 1956",1898,1899,Gelatin silver print,,"David Hunter McAlpin Fund, 1956",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269881,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -56.610.17,false,true,269882,Photographs,Photograph,"Boucherie, Rue Christine",,,,,,Artist,,Eugène Atget,"French, Libourne 1857–1927 Paris",,"Atget, Eugène",French,1857-02-02,1927-08-04,"1920s, printed 1956",1900,1929,Gelatin silver print,,"David Hunter McAlpin Fund, 1956",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269882,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -56.610.18,false,true,269883,Photographs,Photograph,"Faucheurs, Somme",,,,,,Artist,,Eugène Atget,"French, Libourne 1857–1927 Paris",,"Atget, Eugène",French,1857-02-02,1927-08-04,"1890–98, printed 1956",1890,1898,Gelatin silver print,,"David Hunter McAlpin Fund, 1956",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269883,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -56.610.19,false,true,269884,Photographs,Photograph,Environs of Paris,,,,,,Artist,,Eugène Atget,"French, Libourne 1857–1927 Paris",,"Atget, Eugène",French,1857-02-02,1927-08-04,"1920s, printed 1956",1920,1929,Gelatin silver print,,"David Hunter McAlpin Fund, 1956",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269884,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -56.610.20,false,true,269886,Photographs,Photograph,Masque Antique,,,,,,Artist,,Eugène Atget,"French, Libourne 1857–1927 Paris",,"Atget, Eugène",French,1857-02-02,1927-08-04,"1890s–1920s, printed 1956",1890,1929,Gelatin silver print,,"David Hunter McAlpin Fund, 1956",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269886,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.594.1,false,true,259729,Photographs,Photograph,St. Cloud near Paris,,,,,,Artist,,Eugène Atget,"French, Libourne 1857–1927 Paris",,"Atget, Eugène",French,1857-02-02,1927-08-04,1923,1923,1923,Gelatin silver print,Image: 18.2 x 22.2 cm (7 3/16 x 8 3/4 in.),"The Elisha Whittelsey Collection, The Elisha Whittelsey Fund, by exchange, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/259729,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.594.2,false,true,259730,Photographs,Photograph,"Rue Laplace and Rue Valette, Paris",,,,,,Artist,,Eugène Atget,"French, Libourne 1857–1927 Paris",,"Atget, Eugène",French,1857-02-02,1927-08-04,1926,1926,1926,Gelatin silver print,Image: 22 x 17.6 cm (8 11/16 x 6 15/16 in.),"The Elisha Whittelsey Collection, The Elisha Whittelsey Fund, by exchange, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/259730,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.645.1,false,true,261632,Photographs,Photograph,"Coin rue du Cimitière, Saint-Benoît",,,,,,Artist,,Eugène Atget,"French, Libourne 1857–1927 Paris",,"Atget, Eugène",French,1857-02-02,1927-08-04,1923,1923,1923,Gelatin silver print,,"Gift of A. Hyatt Mayor, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261632,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1997.398.2,false,true,282111,Photographs,Photograph,"Hotel de Sens, rue de l'Hôtel de Ville, Paris",,,,,,Artist,,Eugène Atget,"French, Libourne 1857–1927 Paris",,"Atget, Eugène",French,1857-02-02,1927-08-04,early 1900s,1900,1909,Albumen silver print from glass negative,16.7 x 20.5 cm (6 9/16 x 8 1/16 in. ),"The Rubel Collection, Gift of William Rubel, 1997",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/282111,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1990.1026.1,false,true,266231,Photographs,Photograph,Intérieur Rue de Vaugirard,,,,,,Artist,,Eugène Atget,"French, Libourne 1857–1927 Paris",,"Atget, Eugène",French,1857-02-02,1927-08-04,1910,1910,1910,Albumen silver print from glass negative,22.0 x 17.9 cm. (8 11/16 x 7 1/16 in.),"Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 1990",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/266231,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1990.1026.2,false,true,266232,Photographs,Photograph,Cuisine,,,,,,Artist,,Eugène Atget,"French, Libourne 1857–1927 Paris",,"Atget, Eugène",French,1857-02-02,1927-08-04,ca. 1910,1908,1912,Albumen silver print from glass negative,21.4 x 17.2 cm. (8 7/16 x 6 3/4 in.),"Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 1990",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/266232,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1990.1026.3,false,true,266233,Photographs,Photograph,[Atget's Work Room with Contact Printing Frames],,,,,,Artist,,Eugène Atget,"French, Libourne 1857–1927 Paris",,"Atget, Eugène",French,1857-02-02,1927-08-04,ca. 1910,1908,1912,Albumen silver print from glass negative,20.9 x 17.3 cm. (8 1/4 x 6 13/16 in.),"Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 1990",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/266233,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.129,false,true,283261,Photographs,Photograph,Organ-grinder,,,,,,Artist,,Eugène Atget,"French, Libourne 1857–1927 Paris",,"Atget, Eugène",French,1857-02-02,1927-08-04,1898–99,1898,1899,Matte albumen silver print from glass negative,Image: 22.4 × 17.6 cm (22.4 × 17.6 cm) Sheet: 22.5 × 18 cm (8 7/8 × 7 1/16 in.),"Gilman Collection, Purchase, Ann Tenenbaum and Thomas H. Lee Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/283261,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.132,false,true,283264,Photographs,Photograph,"Versailles, The Orangerie Staircase",,,,,,Artist,,Eugène Atget,"French, Libourne 1857–1927 Paris",,"Atget, Eugène",French,1857-02-02,1927-08-04,1901,1901,1901,Albumen silver print from glass negative,Image: 16.9 x 21.7cm (6 5/8 x 8 9/16in.) Mount: 32.8 x 25.3 cm (12 15/16 x 9 15/16 in.),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/283264,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.133,false,true,283265,Photographs,Photograph,"Café, Avenue de la Grande-Armée",,,,,,Artist,,Eugène Atget,"French, Libourne 1857–1927 Paris",,"Atget, Eugène",French,1857-02-02,1927-08-04,1924–25,1924,1925,Matte albumen silver print from glass negative,17.6 × 22.7 cm (6 15/16 × 8 15/16 in.) Sheet: 17.9 × 22.8 cm (17.9 × 22.8 cm),"Gilman Collection, Purchase, Ann Tenenbaum and Thomas H. Lee Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/283265,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.134,false,true,283266,Photographs,Photograph,Rue Asselin,,,,,,Artist,,Eugène Atget,"French, Libourne 1857–1927 Paris",,"Atget, Eugène",French,1857-02-02,1927-08-04,1924–25,1924,1925,Matte albumen silver print from glass negative,22.9 x 17.6cm (9 x 6 15/16in.) Sheet: 23.1 × 18 cm (9 1/8 × 7 1/16 in.),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/283266,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.344,false,true,286477,Photographs,Photograph,Boutique Fleurs,,,,,,Artist,,Eugène Atget,"French, Libourne 1857–1927 Paris",,"Atget, Eugène",French,1857-02-02,1927-08-04,1925,1924,1924,Matte albumen silver print from glass negative,Image: 22.5 × 17.7 cm (8 13/16 × 6 15/16 in.) Sheet: 22.5 × 18.2 cm (8 7/8 × 7 3/16 in.),"Gilman Collection, Purchase, Ann Tenenbaum and Thomas H. Lee Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286477,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.435,false,true,285848,Photographs,Photograph,"Etang, Ville-d'Avray",,,,,,Artist,,Eugène Atget,"French, Libourne 1857–1927 Paris",,"Atget, Eugène",French,1857-02-02,1927-08-04,1923–25,1923,1925,Matte albumen silver print from glass negative,Image: 17.4 x 22.5 cm (6 7/8 x 8 7/8 in.),"Gilman Collection, Purchase, Ann Tenenbaum and Thomas H. Lee Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285848,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.511,false,true,286216,Photographs,Photograph,"Boulevard de Strasbourg, Corsets, Paris",,,,,,Artist,,Eugène Atget,"French, Libourne 1857–1927 Paris",,"Atget, Eugène",French,1857-02-02,1927-08-04,1912,1912,1912,Gelatin silver print from glass negative,Image: 22.4 x 17.5 cm (8 13/16 x 6 7/8 in.) Mount: 36.7 x 28.7 cm (14 7/16 x 11 5/16 in.),"Gilman Collection, Purchase, Ann Tenenbaum and Thomas H. Lee Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286216,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.512,false,true,285679,Photographs,Photograph,Versailles,,,,,,Artist,,Eugène Atget,"French, Libourne 1857–1927 Paris",,"Atget, Eugène",French,1857-02-02,1927-08-04,1924–25,1924,1925,Salted paper print from glass negative,Image: 17.5 x 21.9 cm (6 7/8 x 8 5/8 in.) Sheet: 18 × 21.9 cm (7 1/16 × 8 5/8 in.) Mat: 16 × 20 in. (40.6 × 50.8 cm),"Gilman Collection, Purchase, Ann Tenenbaum and Thomas H. Lee Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285679,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.513,false,true,286674,Photographs,Photograph,"Boutique Journaux, Rue de Sèvres, Paris",,,,,,Artist,,Eugène Atget,"French, Libourne 1857–1927 Paris",,"Atget, Eugène",French,1857-02-02,1927-08-04,1910–11,1910,1911,Matte albumen silver print from glass negative,Image: 22.7 x 18 cm (8 15/16 x 7 1/16 in.),"Gilman Collection, Purchase, Mr. and Mrs. Henry R. Kravis Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286674,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.514,false,true,285764,Photographs,Photograph,Versailles,,,,,,Artist,,Eugène Atget,"French, Libourne 1857–1927 Paris",,"Atget, Eugène",French,1857-02-02,1927-08-04,1922–27,1922,1927,Albumen silver print from glass negative,Image: 21.3 x 17.7 cm (8 3/8 x 6 15/16 in.) Mount: 31.4 x 29.3 cm (12 3/8 x 11 9/16 in.),"Gilman Collection, Purchase, Mr. and Mrs. Henry R. Kravis Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285764,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.515,false,true,286680,Photographs,Photograph,"Cabaret de l'Homme Armé, 25 rue des Blancs-Manteaux",,,,,,Artist,,Eugène Atget,"French, Libourne 1857–1927 Paris",,"Atget, Eugène",French,1857-02-02,1927-08-04,1900,1900,1900,Albumen silver print from glass negative,Image: 21.3 x 17.4 cm (8 3/8 x 6 7/8 in.) Mount: 35.4 x 27.3 cm (13 15/16 x 10 3/4 in.),"Gilman Collection, Purchase, Mrs. Walter Annenberg and The Annenberg Foundation Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286680,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.516,false,true,286197,Photographs,Photograph,"Rouen, Place Eau-de-Robec",,,,,,Artist,,Eugène Atget,"French, Libourne 1857–1927 Paris",,"Atget, Eugène",French,1857-02-02,1927-08-04,1908,1908,1908,Matte albumen silver print from glass negative,Image: 22.7 x 17.4 cm (8 15/16 x 6 7/8 in.) Sheet: 22.9 × 17.7 cm (9 in. × 6 15/16 in.),"Gilman Collection, Purchase, Sam Salz Foundation Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286197,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.517,false,true,285966,Photographs,Photograph,Grand Trianon,,,,,,Artist,,Eugène Atget,"French, Libourne 1857–1927 Paris",,"Atget, Eugène",French,1857-02-02,1927-08-04,1903,1903,1903,Matte albumen silver print from glass negative,Image: 17.2 x 23.15,"Gilman Collection, Purchase, Ann Tenenbaum and Thomas H. Lee Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285966,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.518,false,true,286577,Photographs,Photograph,Lys,,,,,,Artist,,Eugène Atget,"French, Libourne 1857–1927 Paris",,"Atget, Eugène",French,1857-02-02,1927-08-04,before 1900,1892,1900,Albumen silver print from glass negatives,Image: 21.9 x 17.7 cm (8 5/8 x 6 15/16 in.) Sheet: 22 × 18.2 cm (8 11/16 × 7 3/16 in.),"Gilman Collection, Purchase, Jennifer and Joseph Duke Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286577,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.519,false,true,286211,Photographs,Photograph,"St. Cloud, Fin mai, 7 h. soir",,,,,,Artist,,Eugène Atget,"French, Libourne 1857–1927 Paris",,"Atget, Eugène",French,1857-02-02,1927-08-04,1922,1922,1922,Matte albumen silver print from glass negative,Image: 21.8 × 17.3 cm (8 9/16 × 6 13/16 in.) Mount: 35.5 × 28.6 cm (14 in. × 11 1/4 in.),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286211,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.520,false,true,285738,Photographs,Photograph,Notre-Dame depuis le quai de la Tournelle,,,,,,Artist,,Eugène Atget,"French, Libourne 1857–1927 Paris",,"Atget, Eugène",French,1857-02-02,1927-08-04,1923,1923,1923,Albumen silver print from glass negative,"Image: 17.6 × 22.1 cm (6 15/16 × 8 11/16 in.) Sheet: 18.2 × 22.1 cm (7 3/16 in., 22.1 cm)","Gilman Collection, Purchase, Joseph M. Cohen Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285738,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.521,false,true,286644,Photographs,Photograph,Water Lilies,,,,,,Artist,,Eugène Atget,"French, Libourne 1857–1927 Paris",,"Atget, Eugène",French,1857-02-02,1927-08-04,1910 or earlier,1910,1910,Gelatin silver print from glass negative,Image: 17.6 x 22.7 cm (6 15/16 x 8 15/16 in.),"Gilman Collection, Purchase, Mrs. Walter Annenberg and The Annenberg Foundation Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286644,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.522,false,true,286417,Photographs,Photograph,"Maison Close, Versailles",,,,,,Artist,,Eugène Atget,"French, Libourne 1857–1927 Paris",,"Atget, Eugène",French,1857-02-02,1927-08-04,ca. 1921,1919,1923,Albumen silver print from glass negative,Image: 22 × 17.7 cm (8 11/16 × 6 15/16 in.) Sheet: 22 × 18.3 cm (8 11/16 × 7 3/16 in.),"Gilman Collection, Purchase, Ann Tenenbaum and Thomas H. Lee Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286417,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.523,false,true,285676,Photographs,Photograph,"La Bièvre, Boulevard d'Italie 13ème disparue en 1891; aujourd'hui rue Edmond Gondinet",,,,,,Artist,,Eugène Atget,"French, Libourne 1857–1927 Paris",,"Atget, Eugène",French,1857-02-02,1927-08-04,ca. 1898,1896,1900,Albumen silver print from glass negative,Image: 21.5 x 17.1 cm (8 7/16 x 6 3/4 in.) Mount: 37.2 x 28.8 cm (14 5/8 x 11 5/16 in.),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285676,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.524,false,true,285812,Photographs,Photograph,Joueur de Guitare,,,,,,Artist,,Eugène Atget,"French, Libourne 1857–1927 Paris",,"Atget, Eugène",French,1857-02-02,1927-08-04,1900,1900,1900,Albumen silver print from glass negative,Image: 22.2 x 17.3 cm (8 3/4 x 6 13/16 in.),"Gilman Collection, Purchase, Ann Tenenbaum and Thomas H. Lee Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285812,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.525,false,true,285809,Photographs,Photograph,"Parc de Saint-Cloud, Paris",,,,,,Artist,,Eugène Atget,"French, Libourne 1857–1927 Paris",,"Atget, Eugène",French,1857-02-02,1927-08-04,1915–20,1913,1922,Matte albumen silver print from glass negative,Image: 17.2 x 22.5 cm (6 3/4 x 8 7/8 in.) Mount: 36.7 x 28.6 cm (14 7/16 x 11 1/4 in.),"Gilman Collection, Purchase, Ann Tenenbaum and Thomas H. Lee Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285809,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.526,false,true,285810,Photographs,Photograph,Facteur,,,,,,Artist,,Eugène Atget,"French, Libourne 1857–1927 Paris",,"Atget, Eugène",French,1857-02-02,1927-08-04,ca. 1900,1898,1902,Albumen silver print from glass negative,21.7 x 16.7 cm (8 9/16 x 6 9/16 in.),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285810,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.527,false,true,286380,Photographs,Photograph,Small Market in Front of the Church in Place Saint-Médard,,,,,,Artist,,Eugène Atget,"French, Libourne 1857–1927 Paris",,"Atget, Eugène",French,1857-02-02,1927-08-04,1898,1898,1898,Gelatin silver print from glass negative,22.1 x 16.9 cm (8 11/16 x 6 5/8 in.),"Gilman Collection, Purchase, Ann Tenenbaum and Thomas H. Lee Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286380,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.528,false,true,285850,Photographs,Photograph,Vannier,,,,,,Artist,,Eugène Atget,"French, Libourne 1857–1927 Paris",,"Atget, Eugène",French,1857-02-02,1927-08-04,1910–12,1910,1912,Gelatin silver print from glass negative,Image: 16.3 x 22.3 cm (6 7/16 x 8 3/4 in.) Mount: 36.8 x 29.6 cm (14 1/2 x 11 5/8 in.),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285850,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.529,false,true,286679,Photographs,Photograph,Jardin du Luxembourg,,,,,,Artist,,Eugène Atget,"French, Libourne 1857–1927 Paris",,"Atget, Eugène",French,1857-02-02,1927-08-04,1902,1902,1902,Albumen silver print from glass negative,Image: 22.1 × 17.5 cm (8 11/16 × 6 7/8 in.) Sheet: 22.1 × 17.7 cm (8 11/16 × 6 15/16 in.),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286679,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.530,false,true,286675,Photographs,Photograph,"Terre-plein du Pont Neuf, Matinée d'Hiver",,,,,,Artist,,Eugène Atget,"French, Libourne 1857–1927 Paris",,"Atget, Eugène",French,1857-02-02,1927-08-04,1925,1925,1925,Gelatin silver print from glass negative,Image: 17.5 x 22.8 cm (6 7/8 x 9 in.) Sheet: 17.8 × 22.8 cm (7 × 9 in.),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286675,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.531,false,true,285678,Photographs,Photograph,"Fontaine, Sceaux",,,,,,Artist,,Eugène Atget,"French, Libourne 1857–1927 Paris",,"Atget, Eugène",French,1857-02-02,1927-08-04,ca. 1921,1919,1923,Gelatin silver print from glass negative,Image: 22.1 x 17.3 cm (8 11/16 x 6 13/16 in.) Mount: 37.1 x 28.8 cm (14 5/8 x 11 5/16 in.),"Gilman Collection, Purchase, Sam Salz Foundation Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285678,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.532,false,true,286125,Photographs,Photograph,"Le Château, fin Octobre, le soir, effet d'orage, vue prise du Parterre du Nord",,,,,,Artist,,Eugène Atget,"French, Libourne 1857–1927 Paris",,"Atget, Eugène",French,1857-02-02,1927-08-04,1903,1903,1903,Albumen silver print from glass negative,Image: 17.4 × 21.6 cm (6 7/8 × 8 1/2 in.) Sheet: 17.6 × 21.6 cm (6 15/16 × 8 1/2 in.),"Gilman Collection, Purchase, William Talbott Hillman Foundation Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286125,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.533,false,true,286669,Photographs,Photograph,Versailles - Cour du Parc,,,,,,Artist,,Eugène Atget,"French, Libourne 1857–1927 Paris",,"Atget, Eugène",French,1857-02-02,1927-08-04,1902,1902,1902,Albumen silver print from glass negative,Image: 21.5 × 17.5 cm (8 7/16 × 6 7/8 in.) Sheet: 21.5 × 17.9 cm (8 7/16 × 7 1/16 in.),"Gilman Collection, Purchase, Mr. and Mrs. Henry R. Kravis Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286669,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.534,false,true,286670,Photographs,Photograph,"St. Denis, Ancien Relais de la Poste d'Ecouen, Hôtel du Grand Cerf",,,,,,Artist,,Eugène Atget,"French, Libourne 1857–1927 Paris",,"Atget, Eugène",French,1857-02-02,1927-08-04,ca. 1900,1898,1902,Albumen silver print from glass negative,Image: 17.4 × 21.8 cm (6 7/8 × 8 9/16 in.) Sheet: 17.6 × 21.8 cm (6 15/16 × 8 9/16 in.),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286670,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.535,false,true,286693,Photographs,Photograph,Rue de la Montagne-Sainte-Geneviève,,,,,,Artist,,Eugène Atget,"French, Libourne 1857–1927 Paris",,"Atget, Eugène",French,1857-02-02,1927-08-04,1924,1924,1924,Matte albumen silver print from glass negative,Image: 17.4 x 22.1 cm (6 7/8 x 8 11/16 in.) Sheet: 17.7 × 22.5 cm (17.7 × 22.5 cm),"Gilman Collection, Purchase, Mr. and Mrs. Henry R. Kravis Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286693,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.536,false,true,286700,Photographs,Photograph,Boulevard de Strasbourg,,,,,,Artist,,Eugène Atget,"French, Libourne 1857–1927 Paris",,"Atget, Eugène",French,1857-02-02,1927-08-04,1912,1912,1912,Matte albumen silver print from glass negative,Image: 22.7 × 17.7 cm (8 15/16 × 6 15/16 in.) Sheet: 23 × 18 cm (9 1/16 × 7 1/16 in.),"Gilman Collection, Purchase, Ann Tenenbaum and Thomas H. Lee Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286700,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.537,false,true,286317,Photographs,Photograph,"Quai d'Anjou, 6h du matin",,,,,,Artist,,Eugène Atget,"French, Libourne 1857–1927 Paris",,"Atget, Eugène",French,1857-02-02,1927-08-04,1924,1924,1924,Albumen silver print from glass negative,17.7 x 22.8 cm (6 15/16 x 8 15/16 in.),"Gilman Collection, Purchase, William Talbott Hillman Foundation Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286317,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.538,false,true,286682,Photographs,Photograph,"Fête, Avenue de Breteuil",,,,,,Artist,,Eugène Atget,"French, Libourne 1857–1927 Paris",,"Atget, Eugène",French,1857-02-02,1927-08-04,1913,1913,1913,Matte albumen silver print from glass negative,Image: 17.5 x 23 cm (6 7/8 x 9 1/16 in.) Sheet: 18 × 23 cm (7 1/16 × 9 1/16 in.),"Gilman Collection, Purchase, Ann Tenenbaum and Thomas H. Lee Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286682,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.539,false,true,286032,Photographs,Photograph,Agave du Mexique,,,,,,Artist,,Eugène Atget,"French, Libourne 1857–1927 Paris",,"Atget, Eugène",French,1857-02-02,1927-08-04,1920s,1920,1929,Albumen silver print from glass negative,Image: 17.7 x 22.2 cm (6 15/16 x 8 3/4 in.) Sheet: 18 × 22.2 cm (7 1/16 × 8 3/4 in.),"Gilman Collection, Purchase, Denise and Andrew Saul Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286032,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.540,false,true,286678,Photographs,Photograph,97 Rue du Bac,,,,,,Artist,,Eugène Atget,"French, Libourne 1857–1927 Paris",,"Atget, Eugène",French,1857-02-02,1927-08-04,1903,1903,1903,Albumen silver print from glass negative,21.5 x 17 cm (8 7/16 x 6 11/16 in.),"Gilman Collection, Purchase, Mrs. Walter Annenberg and The Annenberg Foundation Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286678,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.541,false,true,285706,Photographs,Photograph,Boats at La Rochelle,,,,,,Artist,,Eugène Atget,"French, Libourne 1857–1927 Paris",,"Atget, Eugène",French,1857-02-02,1927-08-04,ca. 1896,1894,1898,Matte albumen silver print from glass negative,"Image: 16.6 x 22.5 cm (6 9/16 x 8 7/8 in.) Mount: 35.5 x 30.4 cm (14 x 11 15/16 in.), modern mount","Gilman Collection, Purchase, Ann Tenenbaum and Thomas H. Lee Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285706,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.542,false,true,286662,Photographs,Photograph,Jardin des Plantes,,,,,,Artist,,Eugène Atget,"French, Libourne 1857–1927 Paris",,"Atget, Eugène",French,1857-02-02,1927-08-04,1898–1900,1898,1900,Albumen silver print from glass negative,Image: 17.5 × 22 cm (6 7/8 × 8 11/16 in.) Sheet: 17.8 × 22 cm (17.8 × 22 cm),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286662,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.543,false,true,285620,Photographs,Photograph,Trianon,,,,,,Artist,,Eugène Atget,"French, Libourne 1857–1927 Paris",,"Atget, Eugène",French,1857-02-02,1927-08-04,1923–25,1923,1925,Matte albumen silver print from glass negative,Image: 17 x 22.6 cm (6 11/16 x 8 7/8 in.) Mount: 19 x 25.2 cm (7 1/2 x 9 15/16 in.),"Gilman Collection, Purchase, Mrs. Walter Annenberg and The Annenberg Foundation Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285620,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.544,false,true,285818,Photographs,Photograph,Blés,,,,,,Artist,,Eugène Atget,"French, Libourne 1857–1927 Paris",,"Atget, Eugène",French,1857-02-02,1927-08-04,1900,1900,1900,Albumen silver print from glass negative,Image: 22.5 × 17.4 cm (8 7/8 × 6 7/8 in.) Sheet: 22.5 × 17.8 cm (8 7/8 in. × 7 in.),"Gilman Collection, Purchase, Mrs. Walter Annenberg and The Annenberg Foundation Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285818,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.545,false,true,285724,Photographs,Photograph,[Window],,,,,,Artist,,Eugène Atget,"French, Libourne 1857–1927 Paris",,"Atget, Eugène",French,1857-02-02,1927-08-04,1913,1913,1913,Albumen silver print from glass negative,21.5 x 17.4 cm (8 7/16 x 6 7/8 in.),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285724,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.546,false,true,285845,Photographs,Photograph,Saint-Cloud,,,,,,Artist,,Eugène Atget,"French, Libourne 1857–1927 Paris",,"Atget, Eugène",French,1857-02-02,1927-08-04,July 1921,1921,1921,Albumen silver print from glass negative,Image: 17.5 x 21.5 cm (6 7/8 x 8 7/16 in.) Mount: 37.1 x 28.7 cm (14 5/8 x 11 5/16 in.),"Gilman Collection, Purchase, Ann Tenenbaum and Thomas H. Lee Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285845,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.547,false,true,286609,Photographs,Photograph,Parc de Sceaux,,,,,,Artist,,Eugène Atget,"French, Libourne 1857–1927 Paris",,"Atget, Eugène",French,1857-02-02,1927-08-04,1925,1925,1925,Gelatin silver print from glass negative,Image: 17.7 x 22.5 cm (6 15/16 x 8 7/8 in.),"Gilman Collection, Purchase, Denise and Andrew Saul Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286609,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.548,false,true,286045,Photographs,Photograph,"Marchand de Vin, Rue Boyer, Paris",,,,,,Artist,,Eugène Atget,"French, Libourne 1857–1927 Paris",,"Atget, Eugène",French,1857-02-02,1927-08-04,1910–11,1910,1911,Albumen silver print from glass negative,Image: 21.5 x 17.6 cm (8 7/16 x 6 15/16 in.) Sheet: 21.5 × 18.1 cm (8 7/16 × 7 1/8 in.),"Gilman Collection, Purchase, Joseph M. Cohen Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286045,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.549,false,true,286476,Photographs,Photograph,"Boutique de fruits et légumes, Rue Mouffetard",,,,,,Artist,,Eugène Atget,"French, Libourne 1857–1927 Paris",,"Atget, Eugène",French,1857-02-02,1927-08-04,1925,1925,1925,Gelatin silver print from glass negative,Image: 22.6 x 17.9 cm (8 7/8 x 7 in.),"Gilman Collection, Purchase, Ann Tenenbaum and Thomas H. Lee Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286476,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.550,false,true,285891,Photographs,Photograph,"Cour, 7 rue de Valence",,,,,,Artist,,Eugène Atget,"French, Libourne 1857–1927 Paris",,"Atget, Eugène",French,1857-02-02,1927-08-04,1922,1922,1922,Gelatin silver print from glass negative,Image: 17.2 x 22.7 cm (6 3/4 x 8 15/16 in.) Mount: 36.7 x 28.7 cm (14 7/16 x 11 5/16 in.),"Gilman Collection, Purchase, Ann Tenenbaum and Thomas H. Lee Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285891,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1987.1100.113,false,true,265148,Photographs,Photograph,Avenue des Gobelins,,,,,,Artist,,Eugène Atget,"French, Libourne 1857–1927 Paris",,"Atget, Eugène",French,1857-02-02,1927-08-04,1925,1925,1925,Gelatin silver print from glass negative,21.9 x 17.3 cm (8 5/8 x 6 13/16 in.),"Ford Motor Company Collection, Gift of Ford Motor Company and John C. Waddell, 1987",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/265148,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1987.1100.353,false,true,265413,Photographs,Photograph,Versailles,,,,,,Artist,,Eugène Atget,"French, Libourne 1857–1927 Paris",,"Atget, Eugène",French,1857-02-02,1927-08-04,1923,1923,1923,Albumen silver print from glass negative,16.9 x 21.5 cm (6 5/8 x 8 7/16 in.),"Ford Motor Company Collection, Gift of Ford Motor Company and John C. Waddell, 1987",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/265413,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.175,false,true,285969,Photographs,Photograph,"Compiègne, Présentation du Prince Impérial",,,,,,Artist,,Olympe Aguado de las Marismas,"French, Paris 1827–1894 Compiegne",,"Aguado de las Marismas, Olympe",French,1827,1894,1856,1856,1856,Albumen silver print from glass negative,Image: 11.3 × 18.8 cm (4 7/16 × 7 3/8 in.) Mount: 30.7 × 42.3 cm (12 1/16 × 16 5/8 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285969,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.249,false,true,286647,Photographs,Photograph,"[The Artist, His Mother, and Friends in Fishing Garb]",,,,,,Artist,,Olympe Aguado de las Marismas,"French, Paris 1827–1894 Compiegne",,"Aguado de las Marismas, Olympe",French,1827,1894,ca. 1860,1858,1862,Albumen silver print from glass negative,"Image: 17.7 × 14.6 cm (6 15/16 × 5 3/4 in.) Sheet: 19 × 14.6 cm (7 1/2 × 5 3/4 in.), irregularly trimmed","Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286647,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.283,false,true,286649,Photographs,Photograph,Quelques familiers de Louis Robert,,,,,,Artist,,Olympe Aguado de las Marismas,"French, Paris 1827–1894 Compiegne",,"Aguado de las Marismas, Olympe",French,1827,1894,ca. 1860,1858,1862,Albumen silver print from glass negative,Image: 17.1 × 14.3 cm (6 3/4 × 5 5/8 in.) Sheet: 17.5 × 14.3 cm (6 7/8 × 5 5/8 in.),"Gilman Collection, Purchase, Mrs. Walter Annenberg and The Annenberg Foundation Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286649,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.825,false,true,286697,Photographs,Photograph,Emperor Napoleon III,,,,,,Artist,,Olympe Aguado de las Marismas,"French, Paris 1827–1894 Compiegne",,"Aguado de las Marismas, Olympe",French,1827,1894,March 1860,1860,1860,Albumen silver print,"Image: 13.7 × 9.1 cm (13.7 × 9.1 cm), arched top Sheet: 14.2 × 9.6 cm (14.2 × 9.6 cm)","Gilman Collection, Purchase, Gift of The Howard Gilman Foundation, by exchange, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286697,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.826,false,true,286113,Photographs,Photograph,Empress Eugénie,,,,,,Artist,,Olympe Aguado de las Marismas,"French, Paris 1827–1894 Compiegne",,"Aguado de las Marismas, Olympe",French,1827,1894,1860,1860,1860,Albumen silver print from glass negative,"Image: 13.6 × 9.2 cm (5 3/8 × 3 5/8 in.), arched top Sheet: 14.1 × 9.5 cm (5 9/16 × 3 3/4 in.)","Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286113,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.882,false,true,286650,Photographs,Photograph,Louis Robert and Olympe Aguado,,,,,,Artist,,Olympe Aguado de las Marismas,"French, Paris 1827–1894 Compiegne",,"Aguado de las Marismas, Olympe",French,1827,1894,ca. 1860,1858,1862,Albumen silver print from glass negative,Image: 14.7 x 19.1 cm (5 13/16 x 7 1/2 in.) Mount: 23.2 x 29.4 cm (9 1/8 x 11 9/16 in.),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286650,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1987.1161,true,true,265726,Photographs,Photograph,"[Flower Study, Rose of Sharon]",,,,,,Artist,,Adolphe Braun,"French, Besançon 1811–1877 Dornach",,"Braun, Adolphe",French,1811,1877,ca. 1854,1852,1856,Albumen silver print from glass negative,37.5 x 41.9 cm (14 3/4 x 16 1/2 in.),"Gift of Gilman Paper Company, in memory of Samuel J. Wagstaff Jr., 1987",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/265726,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -47.149.53,false,true,269118,Photographs,Photograph,Pheasant and Grouse,,,,,,Artist,,Adolphe Braun,"French, Besançon 1811–1877 Dornach",,"Braun, Adolphe",French,1811,1877,ca. 1865,1863,1867,Carbon print,Image: 80.3 x 47.5 cm (31 5/8 x 18 11/16 in.),"Gift of E. S. Herrmann, 1947",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269118,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1973.607.1,false,true,260967,Photographs,Photograph,"Haute-Egypt, Salle Hypostyle à Karnak",,,,,,Artist,,Adolphe Braun,"French, Besançon 1811–1877 Dornach",,"Braun, Adolphe",French,1811,1877,ca. 1870,1868,1872,Albumen silver print,,"Gift of Weston J. Naef, 1973",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260967,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2011.537,false,true,296359,Photographs,Photographs,"Château de St.-Germain-en-Laye, Intérieur de la cour, Chapelle",,,,,,Artist,,Médéric Mieusement,"French, Gonneville-la-Mallet 1840–1905 Pornic",,"Mieusement, Médéric",French,1840,1905,1862–67,1862,1867,Albumen silver print from glass negative,Image: 37.5 x 28.2 cm (14 3/4 x 11 1/8 in.) Mount: 60.3 x 48 cm (23 3/4 x 18 7/8 in.),"Purchase, Peter C. Bunnell Gift, 2011",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/296359,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -46.167,false,true,269086,Photographs,Photograph,[Bearded Man with Magnifying Glass Examining a Manuscript],,,,,,Artist,,Antoine-Samuel Adam-Salomon,"French, La Ferté-sous-Jouarre 1811–1881 Paris",,"Adam-Salomon, Antoine-Samuel",French,1811,1881,1870s,1870,1879,Albumen silver print from glass negative,,"Museum Accession, 1946",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269086,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.497,false,true,285816,Photographs,Photograph,[Photographic Advertisement],,,,,,Artist,,Emile Gsell,"French, Sainte-Marie-aux-Mines 1838–1879 Vietnam",,"Gsell, Emile",French,1838,1879,1860s,1860,1869,Albumen silver print from glass negative,Image: 23.5 x 31.8 cm (9 1/4 x 12 1/2 in.),"Gilman Collection, Purchase, Cynthia Hazen Polsky Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285816,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.501 (1),false,true,288171,Photographs,Photograph,"Costumes de Théâtre, Saïgon, Cochinchine",,,,,,Artist,,Emile Gsell,"French, Sainte-Marie-aux-Mines 1838–1879 Vietnam",,"Gsell, Emile",French,1838,1879,1866,1866,1866,Albumen silver print from glass negative,24.4 x 31.1 cm (9 5/8 x 12 1/4 in.),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288171,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.501 (2),false,true,288172,Photographs,Photograph,"Femme Annamite, Saïgon, Cochinchine",,,,,,Artist,,Emile Gsell,"French, Sainte-Marie-aux-Mines 1838–1879 Vietnam",,"Gsell, Emile",French,1838,1879,1866,1866,1866,Albumen silver print from glass negative,20 x 16.7 cm (7 7/8 x 6 9/16 in.),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288172,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.501 (3),false,true,288173,Photographs,Photograph,"Prisonniers conduits par un Mata, Cochinchine",,,,,,Artist,,Emile Gsell,"French, Sainte-Marie-aux-Mines 1838–1879 Vietnam",,"Gsell, Emile",French,1838,1879,1866,1866,1866,Albumen silver print from glass negative,24.2 x 21 cm (9 1/2 x 8 1/4 in.),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288173,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.501 (4),false,true,288174,Photographs,Photograph,"Riche Annamite montant à cheval, Cochinchine",,,,,,Artist,,Emile Gsell,"French, Sainte-Marie-aux-Mines 1838–1879 Vietnam",,"Gsell, Emile",French,1838,1879,1866,1866,1866,Albumen silver print from glass negative,19.1 x 25 cm (7 1/2 x 9 13/16 in.),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288174,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.501 (5),false,true,288175,Photographs,Photograph,"Enterrement, Cochinchine",,,,,,Artist,,Emile Gsell,"French, Sainte-Marie-aux-Mines 1838–1879 Vietnam",,"Gsell, Emile",French,1838,1879,1866,1866,1866,Albumen silver print from glass negative,22.5 x 30.9 cm (8 7/8 x 12 3/16 in.),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288175,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.501 (6),false,true,288176,Photographs,Photograph,"Cérémonie religieuse dans la Pagode Chinoise de Cholen, Saïgon, Cochinchine",,,,,,Artist,,Emile Gsell,"French, Sainte-Marie-aux-Mines 1838–1879 Vietnam",,"Gsell, Emile",French,1838,1879,1866,1866,1866,Albumen silver print from glass negative,22.3 x 33.4 cm (8 3/4 x 13 1/8 in.),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288176,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.501 (7),false,true,288177,Photographs,Photograph,"Cortège d'un mariage, Cochinchine",,,,,,Artist,,Emile Gsell,"French, Sainte-Marie-aux-Mines 1838–1879 Vietnam",,"Gsell, Emile",French,1838,1879,1866,1866,1866,Albumen silver print from glass negative,20.7 x 31.1 cm (8 1/8 x 12 1/4 in.),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288177,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.501 (8),false,true,288178,Photographs,Photograph,"Vue de la Ville Chinoise (Cholen) Feuille No. 3, Saïgon, Cochinchine",,,,,,Artist,,Emile Gsell,"French, Sainte-Marie-aux-Mines 1838–1879 Vietnam",,"Gsell, Emile",French,1838,1879,1866,1866,1866,Albumen silver print from glass negative,22.3 x 30.1 cm (8 3/4 x 11 7/8 in.),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288178,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.501 (9),false,true,288179,Photographs,Photograph,"Vue de la Ville Chinoise (Cholen) Feuille No. 2, Saïgon, Cochinchine",,,,,,Artist,,Emile Gsell,"French, Sainte-Marie-aux-Mines 1838–1879 Vietnam",,"Gsell, Emile",French,1838,1879,1866,1866,1866,Albumen silver print from glass negative,23.5 x 31.4 cm (9 1/4 x 12 3/8 in.),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288179,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.501 (10),false,true,288180,Photographs,Photograph,"Vue de la Ville Chinoise (Cholen) Feuille No. 2, Saïgon, Cochinchine",,,,,,Artist,,Emile Gsell,"French, Sainte-Marie-aux-Mines 1838–1879 Vietnam",,"Gsell, Emile",French,1838,1879,1866,1866,1866,Albumen silver print from glass negative,22.3 x 30.4 cm (8 3/4 x 11 15/16 in.),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288180,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.501 (11),false,true,288181,Photographs,Photograph,"Vue de la Ville Chinoise (Cholen) Feuille No. 6, Saïgon, Cochinchine",,,,,,Artist,,Emile Gsell,"French, Sainte-Marie-aux-Mines 1838–1879 Vietnam",,"Gsell, Emile",French,1838,1879,1866,1866,1866,Albumen silver print from glass negative,23.8 x 31.2 cm (9 3/8 x 12 5/16 in.),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288181,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.501 (12),false,true,288182,Photographs,Photograph,Vue de la Ville Chinoise (Cholen) Feuille No. 4,,,,,,Artist,,Emile Gsell,"French, Sainte-Marie-aux-Mines 1838–1879 Vietnam",,"Gsell, Emile",French,1838,1879,1866,1866,1866,Albumen silver print from glass negative,23.9 x 30 cm (9 7/16 x 11 13/16 in.),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288182,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.501 (13),false,true,288183,Photographs,Photograph,"Vue de la Ville Chinoise (Cholen) Feuille No. 5, Saïgon, Cochinchine",,,,,,Artist,,Emile Gsell,"French, Sainte-Marie-aux-Mines 1838–1879 Vietnam",,"Gsell, Emile",French,1838,1879,1866,1866,1866,Albumen silver print from glass negative,24.4 x 30.4 cm (9 5/8 x 11 15/16 in.),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288183,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.501 (14),false,true,288184,Photographs,Photograph,"Bonzes de la Pagoda Chinoise (Cholen), Saïgon, Cochinchine",,,,,,Artist,,Emile Gsell,"French, Sainte-Marie-aux-Mines 1838–1879 Vietnam",,"Gsell, Emile",French,1838,1879,1866,1866,1866,Albumen silver print from glass negative,24.1 x 21.5 cm (9 1/2 x 8 7/16 in.),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288184,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.501 (15),false,true,288185,Photographs,Photograph,"Femmes Annamites, Cochinchine",,,,,,Artist,,Emile Gsell,"French, Sainte-Marie-aux-Mines 1838–1879 Vietnam",,"Gsell, Emile",French,1838,1879,1866,1866,1866,Albumen silver print from glass negative,26.3 x 23 cm (10 3/8 x 9 1/16 in.),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288185,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.501 (16),false,true,288186,Photographs,Photograph,"Musiciens Annamites, Saïgon, Cochinchine",,,,,,Artist,,Emile Gsell,"French, Sainte-Marie-aux-Mines 1838–1879 Vietnam",,"Gsell, Emile",French,1838,1879,1866,1866,1866,Albumen silver print from glass negative,23.1 x 27.9 cm (9 1/8 x 11 in.),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288186,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.501 (17),false,true,288187,Photographs,Photograph,"Chef de Village en Costume officiel, Cochinchine",,,,,,Artist,,Emile Gsell,"French, Sainte-Marie-aux-Mines 1838–1879 Vietnam",,"Gsell, Emile",French,1838,1879,1866,1866,1866,Albumen silver print from glass negative,20.8 x 16.3 cm (8 3/16 x 6 7/16 in.),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288187,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.501 (18),false,true,288188,Photographs,Photograph,"Marchands de Fruits, Cochinchine",,,,,,Artist,,Emile Gsell,"French, Sainte-Marie-aux-Mines 1838–1879 Vietnam",,"Gsell, Emile",French,1838,1879,1866,1866,1866,Albumen silver print from glass negative,15.3 x 20.1 cm (6 x 7 15/16 in.),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288188,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.501 (19),false,true,288189,Photographs,Photograph,"Miliciens mangeant le riz, Cochinchine",,,,,,Artist,,Emile Gsell,"French, Sainte-Marie-aux-Mines 1838–1879 Vietnam",,"Gsell, Emile",French,1838,1879,1866,1866,1866,Albumen silver print from glass negative,21.1 x 16.9 cm (8 5/16 x 6 5/8 in.),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288189,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.501 (20),false,true,288190,Photographs,Photograph,Pagode de la Ville Chinoise,,,,,,Artist,,Emile Gsell,"French, Sainte-Marie-aux-Mines 1838–1879 Vietnam",,"Gsell, Emile",French,1838,1879,1866,1866,1866,Albumen silver print from glass negative,14.6 x 22.8 cm (5 3/4 x 9 in.),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288190,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.501 (21),false,true,288191,Photographs,Photograph,Vue de l'Etablissement des Messageries Impériales,,,,,,Artist,,Emile Gsell,"French, Sainte-Marie-aux-Mines 1838–1879 Vietnam",,"Gsell, Emile",French,1838,1879,1866,1866,1866,Albumen silver print from glass negative,13.2 x 25 cm (5 3/16 x 9 13/16 in.),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288191,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.501 (22),false,true,288192,Photographs,Photograph,Tombeau de l'Evêque d'Adran,,,,,,Artist,,Emile Gsell,"French, Sainte-Marie-aux-Mines 1838–1879 Vietnam",,"Gsell, Emile",French,1838,1879,1866,1866,1866,Albumen silver print from glass negative,16.9 x 22.7 cm (6 5/8 x 8 15/16 in.),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288192,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.501 (23),false,true,288193,Photographs,Photograph,Matas (Miliciens indigènes),,,,,,Artist,,Emile Gsell,"French, Sainte-Marie-aux-Mines 1838–1879 Vietnam",,"Gsell, Emile",French,1838,1879,1866,1866,1866,Albumen silver print from glass negative,26.1 x 19.8 cm (10 1/4 x 7 13/16 in.),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288193,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.501 (24),false,true,288194,Photographs,Photograph,Vue de Saïgon (Feuille No. 2),,,,,,Artist,,Emile Gsell,"French, Sainte-Marie-aux-Mines 1838–1879 Vietnam",,"Gsell, Emile",French,1838,1879,1866,1866,1866,Albumen silver print from glass negative,24 x 33.5 cm (9 7/16 x 13 3/16 in.),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288194,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.501 (25),false,true,288195,Photographs,Photograph,Vue de Saïgon (Feuille No. 3),,,,,,Artist,,Emile Gsell,"French, Sainte-Marie-aux-Mines 1838–1879 Vietnam",,"Gsell, Emile",French,1838,1879,1866,1866,1866,Albumen silver print from glass negative,24.7 x 33.7 cm (9 3/4 x 13 1/4 in.),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288195,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.501 (26),false,true,288196,Photographs,Photograph,Vue de Saïgon (Feuille No. 1),,,,,,Artist,,Emile Gsell,"French, Sainte-Marie-aux-Mines 1838–1879 Vietnam",,"Gsell, Emile",French,1838,1879,1866,1866,1866,Albumen silver print from glass negative,24.7 x 34.2 cm (9 3/4 x 13 7/16 in.),"Gilman Collection, Purchase, The Horace W. Goldsmith Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288196,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.501 (27),false,true,288197,Photographs,Photograph,Portique d'entrée de la Grande Galerie de la Pagode,,,,,,Artist,,Emile Gsell,"French, Sainte-Marie-aux-Mines 1838–1879 Vietnam",,"Gsell, Emile",French,1838,1879,1866,1866,1866,Albumen silver print from glass negative,16.9 x 22.5 cm (6 5/8 x 8 7/8 in.),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288197,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.501 (28),false,true,288198,Photographs,Photograph,Façade Ouest de la Grande Pagode,,,,,,Artist,,Emile Gsell,"French, Sainte-Marie-aux-Mines 1838–1879 Vietnam",,"Gsell, Emile",French,1838,1879,1866,1866,1866,Albumen silver print from glass negative,25.1 x 30.3 cm (9 7/8 x 11 15/16 in.),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288198,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.501 (29),false,true,288199,Photographs,Photograph,Bonzerie de la Grande Pagode,,,,,,Artist,,Emile Gsell,"French, Sainte-Marie-aux-Mines 1838–1879 Vietnam",,"Gsell, Emile",French,1838,1879,1866,1866,1866,Albumen silver print from glass negative,26 x 32 cm (10 1/4 x 12 5/8 in.),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288199,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.501 (30),false,true,288200,Photographs,Photograph,Façade Nord de la Grand Pagode,,,,,,Artist,,Emile Gsell,"French, Sainte-Marie-aux-Mines 1838–1879 Vietnam",,"Gsell, Emile",French,1838,1879,1866,1866,1866,Albumen silver print from glass negative,25.2 x 31.1 cm (9 15/16 x 12 1/4 in.),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288200,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.501 (31),false,true,288201,Photographs,Photograph,Grande Pagode - Edicule Nord,,,,,,Artist,,Emile Gsell,"French, Sainte-Marie-aux-Mines 1838–1879 Vietnam",,"Gsell, Emile",French,1838,1879,1866,1866,1866,Albumen silver print from glass negative,25.6 x 30.9 cm (10 1/16 x 12 3/16 in.),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288201,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.501 (32),false,true,288202,Photographs,Photograph,Edicule extérieur Sud,,,,,,Artist,,Emile Gsell,"French, Sainte-Marie-aux-Mines 1838–1879 Vietnam",,"Gsell, Emile",French,1838,1879,1866,1866,1866,Albumen silver print from glass negative,16.5 x 22.1 cm (6 1/2 x 8 11/16 in.),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288202,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.501 (33),false,true,288203,Photographs,Photograph,Grande Pagode Porte Ouest de la 1ère enceinte,,,,,,Artist,,Emile Gsell,"French, Sainte-Marie-aux-Mines 1838–1879 Vietnam",,"Gsell, Emile",French,1838,1879,1866,1866,1866,Albumen silver print from glass negative,16.4 x 22.9 cm (6 7/16 x 9 in.),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288203,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.501 (34),false,true,286880,Photographs,Photograph,Angle d'Une Cour Intérieure de la Grande Pagode,,,,,,Artist,,Emile Gsell,"French, Sainte-Marie-aux-Mines 1838–1879 Vietnam",,"Gsell, Emile",French,1838,1879,1866,1866,1866,Albumen silver print from glass negative,22.7 x 17.7cm (8 15/16 x 6 15/16in.),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286880,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.501 (35),false,true,288204,Photographs,Photograph,Grande Pagode - Edicule extérieur Sud,,,,,,Artist,,Emile Gsell,"French, Sainte-Marie-aux-Mines 1838–1879 Vietnam",,"Gsell, Emile",French,1838,1879,1866,1866,1866,Albumen silver print from glass negative,16.5 x 22 cm (6 1/2 x 8 11/16 in.),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288204,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.501 (36),false,true,288205,Photographs,Photograph,Grande Pagode - Colonnade de l'Esplanade,,,,,,Artist,,Emile Gsell,"French, Sainte-Marie-aux-Mines 1838–1879 Vietnam",,"Gsell, Emile",French,1838,1879,1866,1866,1866,Albumen silver print from glass negative,17.3 x 12 cm (6 13/16 x 4 3/4 in.),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288205,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.501 (37),false,true,288206,Photographs,Photograph,Bonzes de la Grande Pagode,,,,,,Artist,,Emile Gsell,"French, Sainte-Marie-aux-Mines 1838–1879 Vietnam",,"Gsell, Emile",French,1838,1879,1866,1866,1866,Albumen silver prints from glass negatives,17 x 10.9 cm (6 11/16 x 4 5/16 in.),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288206,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.501 (38),false,true,288207,Photographs,Photograph,Bas-relief de la Grande Galerie de la Pagode,,,,,,Artist,,Emile Gsell,"French, Sainte-Marie-aux-Mines 1838–1879 Vietnam",,"Gsell, Emile",French,1838,1879,1866,1866,1866,Albumen silver prints from glass negatives,12.1 x 17.2 cm (4 3/4 x 6 3/4 in.),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288207,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.501 (39),false,true,288208,Photographs,Photograph,Bas-relief de la Grande Galerie de la Pagode,,,,,,Artist,,Emile Gsell,"French, Sainte-Marie-aux-Mines 1838–1879 Vietnam",,"Gsell, Emile",French,1838,1879,1866,1866,1866,Albumen silver prints from glass negatives,12.1 x 17.2 cm (4 3/4 x 6 3/4 in.),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288208,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.501 (40),false,true,288209,Photographs,Photograph,Grande Pagode - Tour Nord du 2e Etage,,,,,,Artist,,Emile Gsell,"French, Sainte-Marie-aux-Mines 1838–1879 Vietnam",,"Gsell, Emile",French,1838,1879,1866,1866,1866,Albumen silver prints from glass negatives,24.2 x 17 cm (9 1/2 x 6 11/16 in.),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288209,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.501 (41),false,true,288210,Photographs,Photograph,Détail Décoratif de la Grande Pagode,,,,,,Artist,,Emile Gsell,"French, Sainte-Marie-aux-Mines 1838–1879 Vietnam",,"Gsell, Emile",French,1838,1879,1866,1866,1866,Albumen silver prints from glass negatives,16.4 x 11.2 cm (6 7/16 x 4 7/16 in.),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288210,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.501 (42),false,true,288211,Photographs,Photograph,"S. M. Norodon, Roi du Cambodge",,,,,,Artist,,Emile Gsell,"French, Sainte-Marie-aux-Mines 1838–1879 Vietnam",,"Gsell, Emile",French,1838,1879,1866,1866,1866,Albumen silver prints from glass negatives,23.3 x 19.7 cm (9 3/16 x 7 3/4 in.),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288211,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.501 (43),false,true,288212,Photographs,Photograph,"Phra-Kéo-Pha, Frère du Roi",,,,,,Artist,,Emile Gsell,"French, Sainte-Marie-aux-Mines 1838–1879 Vietnam",,"Gsell, Emile",French,1838,1879,1866,1866,1866,Albumen silver prints from glass negatives,24.8 x 20.8 cm (9 3/4 x 8 3/16 in.),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288212,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.501 (44),false,true,288213,Photographs,Photograph,Prince Cambodgien et son Cortège,,,,,,Artist,,Emile Gsell,"French, Sainte-Marie-aux-Mines 1838–1879 Vietnam",,"Gsell, Emile",French,1838,1879,1866,1866,1866,Albumen silver prints from glass negatives,24.2 x 30 cm (9 1/2 x 11 13/16 in.),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288213,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.501 (45),false,true,288214,Photographs,Photograph,Femmes du Prince Phra-Kéo-Pha,,,,,,Artist,,Emile Gsell,"French, Sainte-Marie-aux-Mines 1838–1879 Vietnam",,"Gsell, Emile",French,1838,1879,1866,1866,1866,Albumen silver prints from glass negatives,19.9 x 27.7 cm (7 13/16 x 10 7/8 in.),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288214,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.501 (46),false,true,288215,Photographs,Photograph,Nam-Vian - Tombeaux,,,,,,Artist,,Emile Gsell,"French, Sainte-Marie-aux-Mines 1838–1879 Vietnam",,"Gsell, Emile",French,1838,1879,1866,1866,1866,Albumen silver prints from glass negatives,16.6 x 22.7 cm (6 9/16 x 8 15/16 in.),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288215,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.501 (47),false,true,288216,Photographs,Photograph,Nam-Vian - Tombeaux,,,,,,Artist,,Emile Gsell,"French, Sainte-Marie-aux-Mines 1838–1879 Vietnam",,"Gsell, Emile",French,1838,1879,1866,1866,1866,Albumen silver prints from glass negatives,15.8 x 22 cm (6 1/4 x 8 11/16 in.),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288216,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2008.144,false,true,289043,Photographs,Photograph,[Construction Site],,,,,,Artist,,Louis Lafon,"French, active 1870s–90s",,"Lafon, Louis",French,1870,1899,1880s,1880,1889,Albumen silver print from glass negative,Image: 36.8 x 47.8 cm (14 1/2 x 18 13/16 in.),"Purchase, Alfred Stieglitz Society Gifts, 2008",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/289043,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.410 (2a),false,true,288155,Photographs,Photograph,[Unknown Sitter],,,,,,Artist|Artist,Painted and retouched by,Pierre-Louis Pierson|Marck,"French, 1822–1913",", et al","Pierson, Pierre-Louis|Marck",French,1822,1913,before 1865,1855,1865,Albumen silver print from glass negative,Image: 6 5/16 × 4 3/4 in. (16 × 12 cm) Mount: 14 3/16 in. × 11 in. (36 × 28 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288155,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.410 (2b),false,true,288156,Photographs,Photograph,[Marie de Morny],,,,,,Artist|Artist,Painted and retouched by,Pierre-Louis Pierson|Marck,"French, 1822–1913",", et al","Pierson, Pierre-Louis|Marck",French,1822,1913,before 1865,1855,1865,Albumen silver print from glass negative,Window: 7 1/2 × 5 1/2 in. (19 × 14 cm) Image: 4 in. × 2 13/16 in. (10.2 × 7.2 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288156,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.410 (5a-d),false,true,288160,Photographs,Cartes-de-visite,[Unknown Sitters and Duchesse de Morny],,,,,,Artist|Artist,Painted and retouched by,Pierre-Louis Pierson|Marck,"French, 1822–1913",", et al","Pierson, Pierre-Louis|Marck",French,1822,1913,before 1865,1855,1865,Albumen silver prints from glass negatives,Image: 3 3/8 in. × 2 in. (8.6 × 5.1 cm) (each),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288160,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.410 (5e-h),false,true,288161,Photographs,Cartes-de-visite,"[Pourtalès, Metternich, Grande Duchesse de Mecklemboury, and Unknown Sitter]",,,,,,Artist|Artist,Painted and retouched by,Pierre-Louis Pierson|Marck,"French, 1822–1913",", et al","Pierson, Pierre-Louis|Marck",French,1822,1913,before 1865,1855,1865,Albumen silver prints from glass negatives,Image: 3 3/8 in. × 2 in. (8.6 × 5.1 cm) (each),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288161,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.410 (6a-d),false,true,288162,Photographs,Cartes-de-visite,[Anna Murat and Unknown Sitters],,,,,,Artist|Artist,Painted and retouched by,Pierre-Louis Pierson|Marck,"French, 1822–1913",", et al","Pierson, Pierre-Louis|Marck",French,1822,1913,before 1865,1855,1865,Albumen silver prints from glass negatives,Image: 3 3/8 in. × 2 in. (8.6 × 5.1 cm) (each),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288162,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.410 (6e-h),false,true,288163,Photographs,Cartes-de-visite,[Comtesse Walewska and Princesse Jaochim Murat],,,,,,Artist|Artist,Painted and retouched by,Pierre-Louis Pierson|Marck,"French, 1822–1913",", et al","Pierson, Pierre-Louis|Marck",French,1822,1913,before 1865,1855,1865,Albumen silver prints from glass negatives,Image: 3 3/8 in. × 2 in. (8.6 × 5.1 cm) (each),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288163,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.410 (7a-d),false,true,288164,Photographs,Cartes-de-visite,[Unknown Sitters],,,,,,Artist|Artist,Painted and retouched by,Pierre-Louis Pierson|Marck,"French, 1822–1913",", et al","Pierson, Pierre-Louis|Marck",French,1822,1913,before 1865,1855,1865,Albumen silver prints from glass negatives,Image: 3 3/8 in. × 2 in. (8.6 × 5.1 cm) (each),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288164,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.410 (7e-h),false,true,288165,Photographs,Cartes-de-visite,[Unknown Sitters],,,,,,Artist|Artist,Painted and retouched by,Pierre-Louis Pierson|Marck,"French, 1822–1913",", et al","Pierson, Pierre-Louis|Marck",French,1822,1913,before 1865,1855,1865,Albumen silver prints from glass negatives,Image: 3 3/8 in. × 2 in. (8.6 × 5.1 cm) (each),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288165,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.410 (8a-d),false,true,288166,Photographs,Cartes-de-visite,[Unknown Sitters],,,,,,Artist|Artist,Painted and retouched by,Pierre-Louis Pierson|Marck,"French, 1822–1913",", et al","Pierson, Pierre-Louis|Marck",French,1822,1913,before 1865,1855,1865,Albumen silver prints from glass negatives,Image: 3 3/8 in. × 2 in. (8.6 × 5.1 cm) (each),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288166,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.410 (8e-g),false,true,288167,Photographs,Cartes-de-visite,[Madame Demidoff and Unknown Sitters],,,,,,Artist|Artist,Painted and retouched by,Pierre-Louis Pierson|Marck,"French, 1822–1913",", et al","Pierson, Pierre-Louis|Marck",French,1822,1913,before 1865,1855,1865,Albumen silver prints from glass negatives,Image: 3 3/8 in. × 2 in. (8.6 × 5.1 cm) (each),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288167,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.1097,false,true,285685,Photographs,Photograph,Ambassade Cochinchinoise à Paris,,,,,,Artist|Artist,,Louis Rousseau|Philippe Jacques Potteau,"French, 1807–1876, active 1860s",,"Rousseau, Louis|Potteau, Philippe Jacques",French,1807,1876,1863,1863,1863,Albumen silver print from glass negative,Image: 7 in. × 4 15/16 in. (17.8 × 12.5 cm) Mount: 14 13/16 × 11 1/8 in. (37.7 × 28.2 cm),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285685,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.759.11,false,true,259815,Photographs,Photograph,[Reproduction of Napoleon on the Battlefield of Eylau by Antoine-Jean Gros],,,,,,Artist|Artist,After,Unknown|baron Antoine Jean Gros,"French|French, Paris 1771–1835 Meudon",,"Unknown|Gros, Antoine Jean, baron",French,1771,1835,1850s,1850,1859,Albumen silver print,,"Museum Accession, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/259815,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1978.602.2,false,true,262236,Photographs,Photograph,Nigella Damascena Spinnenkopf,,,,,,Artist|Printer,,Karl Blossfeldt|Jürgen Wilde,"German, 1865–1932",,"Blossfeldt, Karl|Wilde, Jürgen",German,1865,1932,"ca. 1932, printed 1976",1930,1934,Gelatin silver print,25.2 x 19.3 cm. (9 15/16 x 7 5/8 in.),"Warner Communications Inc. Purchase Fund, 1978",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/262236,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.267,false,true,685608,Photographs,Carte-de-visite,[Otto Eurmann?],,,,,,Photography Studio,,G. & A. Overbeck,"German, active 1860s",,G. & A. Overbeck,German,1855,1870,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/685608,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.39,false,true,685381,Photographs,Carte-de-visite,[Oskar Begas],,,,,,Photography Studio,,Loescher & Petsch,"German, active ca. 1860–90",,Loescher & Petsch,German,1860,1890,before 1883,1860,1883,Albumen silver print,Approx. 10.2 x 6.4 cm (4 in. × 2 1/2 in.) (10.2 × 6.4 cm),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/685381,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.517,false,true,685857,Photographs,Carte-de-visite,[Unknown Subject],,,,,,Photography Studio,,Loescher & Petsch,"German, active ca. 1860–90",,Loescher & Petsch,German,1860,1890,after 1867,1867,1890,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/685857,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.547,false,true,685887,Photographs,Carte-de-visite,[Charles Mandel],,,,,,Photography Studio,,Loescher & Petsch,"German, active ca. 1860–90",,Loescher & Petsch,German,1860,1890,after 1867,1867,1890,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/685887,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.629,false,true,685968,Photographs,Carte-de-visite,[Ludwig Johann Passini],,,,,,Photography Studio,,Loescher & Petsch,"German, active ca. 1860–90",,Loescher & Petsch,German,1860,1890,after 1867,1867,1890,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/685968,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.829,false,true,686168,Photographs,Carte-de-visite,[Rildhauer Wolff],,,,,,Photography Studio,,Loescher & Petsch,"German, active ca. 1860–90",,Loescher & Petsch,German,1860,1890,after 1867,1867,1890,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/686168,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.856,false,true,686195,Photographs,Carte-de-visite,Carl Becker,,,,,,Person in Photograph|Photography Studio,Person in photograph,Carl Becker|Loescher & Petsch,"German, active ca. 1860–90",,Becker Carl|Loescher & Petsch,German,1898 |1860,1898 |1890,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/686195,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.890,false,true,686229,Photographs,Carte-de-visite,[Karl Heffeck],,,,,,Photography Studio,,Loescher & Petsch,"German, active ca. 1860–90",,Loescher & Petsch,German,1860,1890,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/686229,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.675,false,true,686014,Photographs,Carte-de-visite,Gustav Karl Ludwig Richter,,,,,,Person in Photograph|Photography Studio,Person in photograph,Gustav Karl Ludwig Richter|Loescher & Petsch,"German, 1823–1884|German, active ca. 1860–90",,"Richter, Gustav Karl Ludwig|Loescher & Petsch",German,1823 |1860,1884 |1890,after 1867,1867,1890,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/686014,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.83,false,true,685425,Photographs,Carte-de-visite,[Meyer George von Bremen],,,,,,Artist|Person in Photograph,Person in photograph,Heinrich Graf|Johann Georg Meyer,"German, active 1860s|German, Bremen 1813–1880 Berlin",,"Graf, Heinrich|Meyer, Johann Georg",German,1813,1880,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/685425,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.84,false,true,685426,Photographs,Carte-de-visite,[Meyer George von Bremen],,,,,,Artist|Person in Photograph,Person in photograph,Heinrich Graf|Johann Georg Meyer,"German, active 1860s|German, Bremen 1813–1880 Berlin",,"Graf, Heinrich|Meyer, Johann Georg",German,1813,1880,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/685426,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.579,false,true,685918,Photographs,Carte-de-visite,[Paul Friedrich Meyerheim],,,,,,Person in Photograph|Photography Studio,Person in photograph,Paul Friedrich Meyerheim|Loescher & Petsch,"German, Berlin 1842–1915 Berlin|German, active ca. 1860–90",,"Meyerheim, Paul Friedrich|Loescher & Petsch",German,1842 |1860,1915 |1890,after 1867,1867,1890,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/685918,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.299,false,true,286358,Photographs,Photograph,Blumenbachia hieronymi,,,,,,Artist,,Karl Blossfeldt,"German, 1865–1932",,"Blossfeldt, Karl",German,1865,1932,1915–25,1915,1925,Gelatin silver print,Image: 29.8 x 23.8 cm (11 3/4 x 9 3/8 in.),"Gilman Collection, Purchase, Denise and Andrew Saul Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286358,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1987.1100.246,false,true,265295,Photographs,Photograph,"[Arrangement of 12 Female Mannequin Heads, Each with Distinct Physiognomy and Period Hair Style]",,,,,,Artist,,Peter Weller,"German, 1868–1940",,"Weller, Peter",German,1868,1940,1920s–30s,1920,1939,Gelatin silver print,16.9 x 23.3 cm (6 5/8 x 9 3/16 in.),"Ford Motor Company Collection, Gift of Ford Motor Company and John C. Waddell, 1987",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/265295,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.188,false,true,285709,Photographs,Photograph,[Adolph Hitler Leaving Landsberg Prison],,,,,,Artist,,Heinrich Hoffmann,"German, 1885–1957",,"Hoffmann, Heinrich",German,1885,1957,"December 20, 1924",1924,1924,Gelatin silver print,22.2 x 16.4 cm (8 3/4 x 6 7/16 in.) Mount: 29.3 × 22 cm (11 9/16 × 8 11/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285709,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1987.1100.334,false,true,265392,Photographs,Photograph,Junge vom Hiddensee,,,,,,Artist,,Aenne Biermann,"German, 1898–1933",,"Biermann, Aenne",German,1898,1933,ca. 1930,1928,1932,Gelatin silver print,23.5 x 17.6 cm (9 1/4 x 6 15/16 in.),"Ford Motor Company Collection, Gift of Ford Motor Company and John C. Waddell, 1987",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/265392,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.182,false,true,286038,Photographs,Photograph,Tanzbar,,,,,,Artist,,Yva (Else Simon),"German, 1900–1942",,"Simon, Else",German,1900,1942,ca. 1930,1928,1932,Gelatin silver print,Image: 24.3 x 17.8 cm (9 9/16 x 7 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286038,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.1033,false,true,266095,Photographs,Photograph,"Juggernaut Car, Madras",,,,,,Artist,,Frederick Fiebig,"German, active 1840s–50s",,"Fiebig, Frederick",German,1840,1859,1850s,1850,1859,Salted paper print from paper negative,Image: 23.9 x 18.3 cm (9 7/16 x 7 3/16 in.),"Purchase, Cynthia Hazen Polsky Gift and The Horace W. Goldsmith Foundation Gift through Joyce and Robert Menschel, 1989",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/266095,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.571,false,true,685910,Photographs,Carte-de-visite,Louis Leloir,,,,,,Artist,,W. Severin,"German, active 1840s–70s",,"Severin, W.",German,1800,1890,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/685910,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.11,false,true,685353,Photographs,Carte-de-visite,[Wilhelm Amberg],,,,,,Artist,,Loescher & Petsch,"German, active ca. 1860–90",,Loescher & Petsch,German,1860,1890,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/685353,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1983.1020,false,true,263541,Photographs,Photograph,Tête du Sphynx,,,,,,Artist,,Wilhelm Hammerschmidt,"German, born Prussia, died 1869",,"Hammerschmidt, Wilhelm",German,,1869,ca. 1860,1858,1862,Albumen silver print from glass negative,23.8 x 31.5 cm. (9 3/8 x 12 3/8 in.),"The Elisha Whittelsey Collection, The Elisha Whittelsey Fund, 1983",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/263541,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.1076,false,true,266127,Photographs,Photograph,"Porte de la Mosque, Sultan Hassan. Partie Inférieure",,,,,,Artist,,Wilhelm Hammerschmidt,"German, born Prussia, died 1869",,"Hammerschmidt, Wilhelm",German,,1869,ca. 1860,1858,1862,Albumen silver print from glass negative,23.5 x 31.2 cm. (9 1/4 x 12 1/4 in.),"Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 1989",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/266127,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1992.5050,false,true,266692,Photographs,Photograph,Temple de Dandour en Nubie,,,,,,Artist,,Wilhelm Hammerschmidt,"German, born Prussia, died 1869",,"Hammerschmidt, Wilhelm",German,,1869,1860s,1860,1869,Albumen silver print from glass negative,23.7 x 31.3 cm. (9 5/16 x 12 5/16 in.),"Gift of Ezra Mack, 1992",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/266692,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1987.1100.61,false,true,265588,Photographs,Photograph,Madame Elena Vacarescu,,,,,,Artist,,Erich Salomon,"German, Berlin 1886–1944 Auschwitz, Poland",,"Salomon, Erich",German,1886,1944,1928,1928,1928,Gelatin silver print,12.6 x 17.6 cm (4 15/16 x 6 15/16 in.),"Ford Motor Company Collection, Gift of Ford Motor Company and John C. Waddell, 1987",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/265588,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1987.1100.266,false,true,265317,Photographs,Photograph,"[Two Women, Seated and in Conversation, in Interior Setting]",,,,,,Artist,,Erich Salomon,"German, Berlin 1886–1944 Auschwitz, Poland",,"Salomon, Erich",German,1886,1944,1920s–30s,1920,1939,Gelatin silver print,16.9 x 22.8 cm. (6 5/8 x 9 in.),"Ford Motor Company Collection, Gift of Ford Motor Company and John C. Waddell, 1987",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/265317,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1987.1100.268,false,true,265319,Photographs,Photograph,[Five Gentlemen Conversing around Table],,,,,,Artist,,Erich Salomon,"German, Berlin 1886–1944 Auschwitz, Poland",,"Salomon, Erich",German,1886,1944,1920s–30s,1920,1939,Gelatin silver print,17.4 x 23.2 cm. (6 7/8 x 9 1/8 in.),"Ford Motor Company Collection, Gift of Ford Motor Company and John C. Waddell, 1987",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/265319,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1992.5013,false,true,266656,Photographs,Photographs,Horse,,,,,,Artist,,Ottomar Anschütz,"German, Lissa (Leszno, Poland) 1846–1907 Berlin",,"Anschütz, Ottomar",German,1846,1907,1884,1884,1884,Albumen silver print,,"Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 1992",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/266656,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1982.1182.965,false,true,288290,Photographs,Stereographs,"Statue of Queen Anne, St. Paul's, London",,,,,,Publisher|Artist|Person in Photograph,,European and American Views|Unknown|Queen Anne Stuart,"British, 1665–1714",,"European and American Views|Unknown|Stuart, Anne Queen",British,1665,1714,1850s–1910s,1850,1919,Albumen silver prints,Mount: 8.8 x 17.8 cm (3 7/16 x 7 in.),"Gift of Weston J. Naef, in memory of Kathleen W. Naef and Weston J. Naef Sr., 1982",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288290,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.13,false,true,685355,Photographs,Carte-de-visite,[Richard Ansdell],,,,,,Photography Studio,,John and Charles Watkins,"British, active 1867–71",,John and Charles Watkins,British,1840,1875,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/685355,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.18,false,true,685360,Photographs,Carte-de-visite,[John Ballantyne],,,,,,Photography Studio,,John and Charles Watkins,"British, active 1867–71",,John and Charles Watkins,British,1840,1875,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/685360,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.19,false,true,685361,Photographs,Carte-de-visite,[Edward Charles Barnes],,,,,,Photography Studio,,John and Charles Watkins,"British, active 1867–71",,John and Charles Watkins,British,1840,1875,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/685361,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.22,false,true,685364,Photographs,Carte-de-visite,[Sir Charles Barry],,,,,,Photography Studio,,John and Charles Watkins,"British, active 1867–71",,John and Charles Watkins,British,1840,1875,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/685364,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.23,false,true,685365,Photographs,Carte-de-visite,[Edward Middleton Barry],,,,,,Photography Studio,,John and Charles Watkins,"British, active 1867–71",,John and Charles Watkins,British,1840,1875,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/685365,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.35,false,true,685377,Photographs,Carte-de-visite,[Charles Baxter],,,,,,Photography Studio,,John and Charles Watkins,"British, active 1867–71",,John and Charles Watkins,British,1840,1875,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/685377,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.48,false,true,685390,Photographs,Carte-de-visite,[Charles Henry Bennett],,,,,,Photography Studio,,John and Charles Watkins,"British, active 1867–71",,John and Charles Watkins,British,1840,1875,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/685390,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.91,false,true,685433,Photographs,Carte-de-visite,[Sir David Brewster],,,,,,Photography Studio,,John and Charles Watkins,"British, active 1867–71",,John and Charles Watkins,British,1840,1875,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/685433,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.101,false,true,685443,Photographs,Carte-de-visite,[John Burnet],,,,,,Photography Studio,,John and Charles Watkins,"British, active 1867–71",,John and Charles Watkins,British,1840,1875,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/685443,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.102,false,true,685444,Photographs,Carte-de-visite,[Frederic William Burton],,,,,,Photography Studio,,John and Charles Watkins,"British, active 1867–71",,John and Charles Watkins,British,1840,1875,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/685444,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.113,false,true,685455,Photographs,Carte-de-visite,[Philip Hermogenes Calderon],,,,,,Photography Studio,,John and Charles Watkins,"British, active 1867–71",,John and Charles Watkins,British,1840,1875,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/685455,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.115,false,true,685457,Photographs,Carte-de-visite,[William Callow],,,,,,Photography Studio,,John and Charles Watkins,"British, active 1867–71",,John and Charles Watkins,British,1840,1875,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/685457,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.135,false,true,685477,Photographs,Carte-de-visite,[Adelaide Claxton],,,,,,Photography Studio,,John and Charles Watkins,"British, active 1867–71",,John and Charles Watkins,British,1840,1875,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/685477,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.136,false,true,685478,Photographs,Carte-de-visite,[Florence Anne Claxton],,,,,,Photography Studio,,John and Charles Watkins,"British, active 1867–71",,John and Charles Watkins,British,1840,1875,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/685478,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.137,false,true,685479,Photographs,Carte-de-visite,[Marshall C. Claxton],,,,,,Photography Studio,,John and Charles Watkins,"British, active 1867–71",,John and Charles Watkins,British,1840,1875,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/685479,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.148,false,true,685490,Photographs,Carte-de-visite,[George Vicat Cole],,,,,,Photography Studio,,John and Charles Watkins,"British, active 1867–71",,John and Charles Watkins,British,1840,1875,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/685490,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.149,false,true,685491,Photographs,Carte-de-visite,[William Collingwood],,,,,,Photography Studio,,John and Charles Watkins,"British, active 1867–71",,John and Charles Watkins,British,1840,1875,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/685491,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.155,false,true,685497,Photographs,Carte-de-visite,[Edward William Cooke],,,,,,Photography Studio,,John and Charles Watkins,"British, active 1867–71",,John and Charles Watkins,British,1840,1875,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/685497,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.158,false,true,685500,Photographs,Carte-de-visite,[Thomas Sidney Cooper],,,,,,Photography Studio,,John and Charles Watkins,"British, active 1867–71",,John and Charles Watkins,British,1840,1875,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/685500,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.159,false,true,685501,Photographs,Carte-de-visite,[Charles West Cope],,,,,,Photography Studio,,John and Charles Watkins,"British, active 1867–71",,John and Charles Watkins,British,1840,1875,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/685501,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.166,false,true,685508,Photographs,Carte-de-visite,[Samuel Cousins],,,,,,Photography Studio,,John and Charles Watkins,"British, active 1867–71",,John and Charles Watkins,British,1840,1875,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/685508,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.181,false,true,685522,Photographs,Carte-de-visite,[James Francis Danby],,,,,,Photography Studio,,John and Charles Watkins,"British, active 1867–71",,John and Charles Watkins,British,1840,1875,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/685522,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.182,false,true,685523,Photographs,Carte-de-visite,[Thomas Danby],,,,,,Photography Studio,,John and Charles Watkins,"British, active 1867–71",,John and Charles Watkins,British,1840,1875,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/685523,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.235,false,true,685576,Photographs,Carte-de-visite,[William Charles Thomas Dobson],,,,,,Photography Studio,,John and Charles Watkins,"British, active 1867–71",,John and Charles Watkins,British,1840,1875,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/685576,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.237,false,true,685578,Photographs,Carte-de-visite,[George Thomas Doo],,,,,,Photography Studio,,John and Charles Watkins,"British, active 1867–71",,John and Charles Watkins,British,1840,1875,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/685578,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.242,false,true,685583,Photographs,Carte-de-visite,[Richard Doyle],,,,,,Photography Studio,,John and Charles Watkins,"British, active 1867–71",,John and Charles Watkins,British,1840,1875,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/685583,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.247,false,true,685588,Photographs,Carte-de-visite,[Edward Duncan],,,,,,Photography Studio,,John and Charles Watkins,"British, active 1867–71",,John and Charles Watkins,British,1840,1875,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/685588,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.256,false,true,685597,Photographs,Carte-de-visite,[Joseph Durham],,,,,,Photography Studio,,John and Charles Watkins,"British, active 1867–71",,John and Charles Watkins,British,1840,1875,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/685597,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.258,false,true,685599,Photographs,Carte-de-visite,[William Dyce],,,,,,Photography Studio,,John and Charles Watkins,"British, active 1867–71",,John and Charles Watkins,British,1840,1875,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/685599,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.260,false,true,685601,Photographs,Carte-de-visite,[Sir Charles Lock Eastlake],,,,,,Photography Studio,,John and Charles Watkins,"British, active 1867–71",,John and Charles Watkins,British,1840,1875,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/685601,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.266,false,true,685607,Photographs,Carte-de-visite,[Alfred Elmore],,,,,,Photography Studio,,John and Charles Watkins,"British, active 1867–71",,John and Charles Watkins,British,1840,1875,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/685607,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.271,false,true,685612,Photographs,Carte-de-visite,[William Etty],,,,,,Photography Studio,,John and Charles Watkins,"British, active 1867–71",,John and Charles Watkins,British,1840,1875,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/685612,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.273,false,true,685614,Photographs,Carte-de-visite,[Thomas Faed],,,,,,Photography Studio,,John and Charles Watkins,"British, active 1867–71",,John and Charles Watkins,British,1840,1875,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/685614,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.277,false,true,685618,Photographs,Carte-de-visite,[Frederick William Fairholt],,,,,,Photography Studio,,John and Charles Watkins,"British, active 1867–71",,John and Charles Watkins,British,1840,1875,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/685618,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.284,false,true,685625,Photographs,Carte-de-visite,[Unknown Subject],,,,,,Photography Studio,,John and Charles Watkins,"British, active 1867–71",,John and Charles Watkins,British,1840,1875,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/685625,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.289,false,true,685630,Photographs,Carte-de-visite,[Sir William Boxall],,,,,,Photography Studio,,John and Charles Watkins,"British, active 1867–71",,John and Charles Watkins,British,1840,1875,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/685630,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.290,false,true,685631,Photographs,Carte-de-visite,[George Price Boyce],,,,,,Photography Studio,,John and Charles Watkins,"British, active 1867–71",,John and Charles Watkins,British,1840,1875,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/685631,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.317,false,true,685658,Photographs,Carte-de-visite,[Alfred Downing Fripp],,,,,,Photography Studio,,John and Charles Watkins,"British, active 1867–71",,John and Charles Watkins,British,1840,1875,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/685658,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.321,false,true,685662,Photographs,Carte-de-visite,[William Powell Frith],,,,,,Photography Studio,,John and Charles Watkins,"British, active 1867–71",,John and Charles Watkins,British,1840,1875,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/685662,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.324,false,true,685665,Photographs,Carte-de-visite,[William Edward Frost],,,,,,Photography Studio,,John and Charles Watkins,"British, active 1867–71",,John and Charles Watkins,British,1840,1875,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/685665,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.339,false,true,685679,Photographs,Carte-de-visite,[John Gibson],,,,,,Photography Studio,,John and Charles Watkins,"British, active 1867–71",,John and Charles Watkins,British,1840,1875,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/685679,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.347,false,true,685687,Photographs,Carte-de-visite,[Margaret Gillies],,,,,,Photography Studio,,John and Charles Watkins,"British, active 1867–71",,John and Charles Watkins,British,1840,1875,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/685687,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.354,false,true,685694,Photographs,Carte-de-visite,[Edward Alfred Goodall],,,,,,Photography Studio,,John and Charles Watkins,"British, active 1867–71",,John and Charles Watkins,British,1840,1875,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/685694,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.355,false,true,685695,Photographs,Carte-de-visite,[Frederick Goodall],,,,,,Photography Studio,,John and Charles Watkins,"British, active 1867–71",,John and Charles Watkins,British,1840,1875,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/685695,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.363,false,true,685703,Photographs,Carte-de-visite,[Robert Graves],,,,,,Photography Studio,,John and Charles Watkins,"British, active 1867–71",,John and Charles Watkins,British,1840,1875,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/685703,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.371,false,true,685711,Photographs,Carte-de-visite,[Unknown Subject],,,,,,Photography Studio,,John and Charles Watkins,"British, active 1867–71",,John and Charles Watkins,British,1840,1875,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/685711,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.372,false,true,685712,Photographs,Carte-de-visite,[Carl Haag],,,,,,Photography Studio,,John and Charles Watkins,"British, active 1867–71",,John and Charles Watkins,British,1840,1875,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/685712,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.378,false,true,685718,Photographs,Carte-de-visite,[Robert Hannah],,,,,,Photography Studio,,John and Charles Watkins,"British, active 1867–71",,John and Charles Watkins,British,1840,1875,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/685718,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.386,false,true,685726,Photographs,Carte-de-visite,[Solomon Alexander Hart],,,,,,Photography Studio,,John and Charles Watkins,"British, active 1867–71",,John and Charles Watkins,British,1840,1875,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/685726,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.388,false,true,685728,Photographs,Carte-de-visite,[William Harvey],,,,,,Photography Studio,,John and Charles Watkins,"British, active 1867–71",,John and Charles Watkins,British,1840,1875,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/685728,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.391,false,true,685731,Photographs,Carte-de-visite,[Edwin Hayes],,,,,,Photography Studio,,John and Charles Watkins,"British, active 1867–71",,John and Charles Watkins,British,1840,1875,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/685731,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.406,false,true,685746,Photographs,Carte-de-visite,[George Edwards Hering],,,,,,Photography Studio,,John and Charles Watkins,"British, active 1867–71",,John and Charles Watkins,British,1840,1875,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/685746,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.416,false,true,685756,Photographs,Carte-de-visite,[James Clarke Hook],,,,,,Photography Studio,,John and Charles Watkins,"British, active 1867–71",,John and Charles Watkins,British,1840,1875,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/685756,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.420,false,true,685760,Photographs,Carte-de-visite,[John Callcott Horsley],,,,,,Photography Studio,,John and Charles Watkins,"British, active 1867–71",,John and Charles Watkins,British,1840,1875,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/685760,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.426,false,true,685766,Photographs,Carte-de-visite,[Unknown Subject],,,,,,Photography Studio,,John and Charles Watkins,"British, active 1867–71",,John and Charles Watkins,British,1840,1875,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/685766,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.445,false,true,685785,Photographs,Carte-de-visite,[Samuel Philips Jackson],,,,,,Photography Studio,,John and Charles Watkins,"British, active 1867–71",,John and Charles Watkins,British,1840,1875,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/685785,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.455,false,true,685795,Photographs,Carte-de-visite,[Joseph John Jenkins],,,,,,Photography Studio,,John and Charles Watkins,"British, active 1867–71",,John and Charles Watkins,British,1840,1875,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/685795,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.459,false,true,685799,Photographs,Carte-de-visite,[Alexander Johnston],,,,,,Photography Studio,,John and Charles Watkins,"British, active 1867–71",,John and Charles Watkins,British,1840,1875,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/685799,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.460,false,true,685800,Photographs,Carte-de-visite,[E.B. Jones],,,,,,Photography Studio,,John and Charles Watkins,"British, active 1867–71",,John and Charles Watkins,British,1840,1875,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/685800,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.461,false,true,685801,Photographs,Carte-de-visite,[George Jones],,,,,,Photography Studio,,John and Charles Watkins,"British, active 1867–71",,John and Charles Watkins,British,1840,1875,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/685801,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.464,false,true,685804,Photographs,Carte-de-visite,[HenryJutsum],,,,,,Photography Studio,,John and Charles Watkins,"British, active 1867–71",,John and Charles Watkins,British,1840,1875,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/685804,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.477,false,true,685817,Photographs,Carte-de-visite,[John Prescott Knight],,,,,,Photography Studio,,John and Charles Watkins,"British, active 1867–71",,John and Charles Watkins,British,1840,1875,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/685817,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.482,false,true,685822,Photographs,Carte-de-visite,[Edward S. ? Kuntze],,,,,,Photography Studio,,John and Charles Watkins,"British, active 1867–71",,John and Charles Watkins,British,1840,1875,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/685822,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.493,false,true,685833,Photographs,Carte-de-visite,[Charles Landseer],,,,,,Photography Studio,,John and Charles Watkins,"British, active 1867–71",,John and Charles Watkins,British,1840,1875,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/685833,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.494,false,true,685834,Photographs,Carte-de-visite,[Thomas Landseer],,,,,,Photography Studio,,John and Charles Watkins,"British, active 1867–71",,John and Charles Watkins,British,1840,1875,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/685834,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.495,false,true,685835,Photographs,Carte-de-visite,[Richard James Lane],,,,,,Photography Studio,,John and Charles Watkins,"British, active 1867–71",,John and Charles Watkins,British,1840,1875,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/685835,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.500,false,true,685840,Photographs,Carte-de-visite,[Frederick Richard Lee],,,,,,Photography Studio,,John and Charles Watkins,"British, active 1867–71",,John and Charles Watkins,British,1840,1875,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/685840,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.506,false,true,685846,Photographs,Carte-de-visite,[Sir Frederic Leighton],,,,,,Photography Studio,,John and Charles Watkins,"British, active 1867–71",,John and Charles Watkins,British,1840,1875,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/685846,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.509,false,true,685849,Photographs,Carte-de-visite,[Henry LeJeune],,,,,,Photography Studio,,John and Charles Watkins,"British, active 1867–71",,John and Charles Watkins,British,1840,1875,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/685849,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.515,false,true,685855,Photographs,Carte-de-visite,[George Dunlop Leslie],,,,,,Photography Studio,,John and Charles Watkins,"British, active 1867–71",,John and Charles Watkins,British,1840,1875,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/685855,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.535,false,true,685875,Photographs,Carte-de-visite,[Charles Lucy],,,,,,Photography Studio,,John and Charles Watkins,"British, active 1867–71",,John and Charles Watkins,British,1840,1875,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/685875,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.538,false,true,685878,Photographs,Carte-de-visite,[Egron Sellif Lundgren],,,,,,Photography Studio,,John and Charles Watkins,"British, active 1867–71",,John and Charles Watkins,British,1840,1875,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/685878,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.540,false,true,685880,Photographs,Carte-de-visite,[Unknown Subject],,,,,,Photography Studio,,John and Charles Watkins,"British, active 1867–71",,John and Charles Watkins,British,1840,1875,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/685880,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.554,false,true,685894,Photographs,Carte-de-visite,[William Calder Marshall],,,,,,Photography Studio,,John and Charles Watkins,"British, active 1867–71",,John and Charles Watkins,British,1840,1875,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/685894,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.555,false,true,685895,Photographs,Carte-de-visite,[George Mason],,,,,,Photography Studio,,John and Charles Watkins,"British, active 1867–71",,John and Charles Watkins,British,1840,1875,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/685895,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.556,false,true,685896,Photographs,Carte-de-visite,[Gerald Massey],,,,,,Photography Studio,,John and Charles Watkins,"British, active 1867–71",,John and Charles Watkins,British,1840,1875,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/685896,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.561,false,true,685900,Photographs,Carte-de-visite,[David Hall McKewan],,,,,,Photography Studio,,John and Charles Watkins,"British, active 1867–71",,John and Charles Watkins,British,1840,1875,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/685900,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.580,false,true,685919,Photographs,Carte-de-visite,[John Everett Millais],,,,,,Photography Studio,,John and Charles Watkins,"British, active 1867–71",,John and Charles Watkins,British,1840,1875,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/685919,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.581,false,true,685920,Photographs,Carte-de-visite,[John Everett Millais],,,,,,Photography Studio,,John and Charles Watkins,"British, active 1867–71",,John and Charles Watkins,British,1840,1875,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/685920,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.587,false,true,685926,Photographs,Carte-de-visite,[John Henry Mole],,,,,,Photography Studio,,John and Charles Watkins,"British, active 1867–71",,John and Charles Watkins,British,1840,1875,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/685926,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.588,false,true,685927,Photographs,Carte-de-visite,[John Henry Mole],,,,,,Photography Studio,,John and Charles Watkins,"British, active 1867–71",,John and Charles Watkins,British,1840,1875,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/685927,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.593,false,true,685932,Photographs,Carte-de-visite,[Unknown Subject],,,,,,Photography Studio,,John and Charles Watkins,"British, active 1867–71",,John and Charles Watkins,British,1840,1875,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/685932,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.601,false,true,685940,Photographs,Carte-de-visite,[Unknown Subject],,,,,,Photography Studio,,John and Charles Watkins,"British, active 1867–71",,John and Charles Watkins,British,1840,1875,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/685940,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.606,false,true,685945,Photographs,Carte-de-visite,[Paul Jacob Naftel],,,,,,Photography Studio,,John and Charles Watkins,"British, active 1867–71",,John and Charles Watkins,British,1840,1875,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/685945,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.607,false,true,685946,Photographs,Carte-de-visite,[Joseph Nash],,,,,,Photography Studio,,John and Charles Watkins,"British, active 1867–71",,John and Charles Watkins,British,1840,1875,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/685946,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.609,false,true,685948,Photographs,Carte-de-visite,[Alfred Pizzey Newton],,,,,,Photography Studio,,John and Charles Watkins,"British, active 1867–71",,John and Charles Watkins,British,1840,1875,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/685948,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.610,false,true,685949,Photographs,Carte-de-visite,[Erskine Nicol],,,,,,Photography Studio,,John and Charles Watkins,"British, active 1867–71",,John and Charles Watkins,British,1840,1875,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/685949,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.613,false,true,685952,Photographs,Carte-de-visite,[Matthew Noble],,,,,,Photography Studio,,John and Charles Watkins,"British, active 1867–71",,John and Charles Watkins,British,1840,1875,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/685952,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.617,false,true,685956,Photographs,Carte-de-visite,[William Quiller Orchardson],,,,,,Photography Studio,,John and Charles Watkins,"British, active 1867–71",,John and Charles Watkins,British,1840,1875,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/685956,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.624,false,true,685963,Photographs,Carte-de-visite,[Samuel Palmer],,,,,,Photography Studio,,John and Charles Watkins,"British, active 1867–71",,John and Charles Watkins,British,1840,1875,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/685963,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.637,false,true,685976,Photographs,Carte-de-visite,[John Pettie],,,,,,Photography Studio,,John and Charles Watkins,"British, active 1867–71",,John and Charles Watkins,British,1840,1875,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/685976,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.641,false,true,685980,Photographs,Carte-de-visite,[Frederick Richard Pickersgill],,,,,,Photography Studio,,John and Charles Watkins,"British, active 1867–71",,John and Charles Watkins,British,1840,1875,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/685980,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.642,false,true,685981,Photographs,Carte-de-visite,[Frederick Richard Pickersgill],,,,,,Photography Studio,,John and Charles Watkins,"British, active 1867–71",,John and Charles Watkins,British,1840,1875,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/685981,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.649,false,true,685988,Photographs,Carte-de-visite,[Paul Falconer Poole],,,,,,Photography Studio,,John and Charles Watkins,"British, active 1867–71",,John and Charles Watkins,British,1840,1875,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/685988,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.653,false,true,685992,Photographs,Carte-de-visite,[Edward John Poynter],,,,,,Photography Studio,,John and Charles Watkins,"British, active 1867–71",,John and Charles Watkins,British,1840,1875,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/685992,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.659,false,true,685998,Photographs,Carte-de-visite,[Unknown Subject],,,,,,Photography Studio,,John and Charles Watkins,"British, active 1867–71",,John and Charles Watkins,British,1840,1875,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/685998,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.660,false,true,685999,Photographs,Carte-de-visite,[James Baker Pyne],,,,,,Photography Studio,,John and Charles Watkins,"British, active 1867–71",,John and Charles Watkins,British,1840,1875,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/685999,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.664,false,true,686003,Photographs,Carte-de-visite,[S.Read],,,,,,Photography Studio,,John and Charles Watkins,"British, active 1867–71",,John and Charles Watkins,British,1840,1875,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/686003,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.665,false,true,686004,Photographs,Carte-de-visite,[Richard Redgrave],,,,,,Photography Studio,,John and Charles Watkins,"British, active 1867–71",,John and Charles Watkins,British,1840,1875,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/686004,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.674,false,true,686013,Photographs,Carte-de-visite,[George Richmond],,,,,,Photography Studio,,John and Charles Watkins,"British, active 1867–71",,John and Charles Watkins,British,1840,1875,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/686013,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.688,false,true,686027,Photographs,Carte-de-visite,[William H. Robinson?],,,,,,Photography Studio,,John and Charles Watkins,"British, active 1867–71",,John and Charles Watkins,British,1840,1875,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/686027,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.701,false,true,686040,Photographs,Carte-de-visite,[George Augustus Sala],,,,,,Photography Studio,,John and Charles Watkins,"British, active 1867–71",,John and Charles Watkins,British,1840,1875,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/686040,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.703,false,true,686042,Photographs,Carte-de-visite,[James Sant],,,,,,Photography Studio,,John and Charles Watkins,"British, active 1867–71",,John and Charles Watkins,British,1840,1875,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/686042,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.718,false,true,686057,Photographs,Carte-de-visite,[George Gilbert Scott],,,,,,Photography Studio,,John and Charles Watkins,"British, active 1867–71",,John and Charles Watkins,British,1840,1875,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/686057,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.729,false,true,686068,Photographs,Carte-de-visite,[Arthur Sketchley],,,,,,Photography Studio,,John and Charles Watkins,"British, active 1867–71",,John and Charles Watkins,British,1840,1875,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/686068,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.732,false,true,686071,Photographs,Carte-de-visite,[Sydney Smirke],,,,,,Photography Studio,,John and Charles Watkins,"British, active 1867–71",,John and Charles Watkins,British,1840,1875,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/686071,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.733,false,true,686072,Photographs,Carte-de-visite,[Collingwood Smith],,,,,,Photography Studio,,John and Charles Watkins,"British, active 1867–71",,John and Charles Watkins,British,1840,1875,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/686072,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.734,false,true,686073,Photographs,Carte-de-visite,[George Smith],,,,,,Photography Studio,,John and Charles Watkins,"British, active 1867–71",,John and Charles Watkins,British,1840,1875,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/686073,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.735,false,true,686074,Photographs,Carte-de-visite,[Unknown Subject],,,,,,Photography Studio,,John and Charles Watkins,"British, active 1867–71",,John and Charles Watkins,British,1840,1875,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/686074,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.745,false,true,686084,Photographs,Carte-de-visite,[Unknown Subject],,,,,,Photography Studio,,John and Charles Watkins,"British, active 1867–71",,John and Charles Watkins,British,1840,1875,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/686084,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.750,false,true,686089,Photographs,Carte-de-visite,[Lumb Stocks],,,,,,Photography Studio,,John and Charles Watkins,"British, active 1867–71",,John and Charles Watkins,British,1840,1875,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/686089,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.751,false,true,686090,Photographs,Carte-de-visite,[Marcus Stone],,,,,,Photography Studio,,John and Charles Watkins,"British, active 1867–71",,John and Charles Watkins,British,1840,1875,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/686090,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.754,false,true,686093,Photographs,Carte-de-visite,[George Edward ?],,,,,,Photography Studio,,John and Charles Watkins,"British, active 1867–71",,John and Charles Watkins,British,1840,1875,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/686093,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.764,false,true,686103,Photographs,Carte-de-visite,[Frederick Taylor],,,,,,Photography Studio,,John and Charles Watkins,"British, active 1867–71",,John and Charles Watkins,British,1840,1875,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/686103,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.766,false,true,686105,Photographs,Carte-de-visite,[John Tenniel],,,,,,Photography Studio,,John and Charles Watkins,"British, active 1867–71",,John and Charles Watkins,British,1840,1875,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/686105,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.769,false,true,686108,Photographs,Carte-de-visite,[Unknown Subject],,,,,,Photography Studio,,John and Charles Watkins,"British, active 1867–71",,John and Charles Watkins,British,1840,1875,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/686108,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.803,false,true,686142,Photographs,Carte-de-visite,[E.M.Ward],,,,,,Photography Studio,,John and Charles Watkins,"British, active 1867–71",,John and Charles Watkins,British,1840,1875,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/686142,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.807,false,true,686146,Photographs,Carte-de-visite,[George Frederick Watts],,,,,,Photography Studio,,John and Charles Watkins,"British, active 1867–71",,John and Charles Watkins,British,1840,1875,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/686146,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.808,false,true,686147,Photographs,Carte-de-visite,[George Frederick Watts],,,,,,Photography Studio,,John and Charles Watkins,"British, active 1867–71",,John and Charles Watkins,British,1840,1875,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/686147,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.810,false,true,686149,Photographs,Carte-de-visite,[Thomas Webster],,,,,,Photography Studio,,John and Charles Watkins,"British, active 1867–71",,John and Charles Watkins,British,1840,1875,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/686149,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.811,false,true,686150,Photographs,Carte-de-visite,[Henry Weekes],,,,,,Photography Studio,,John and Charles Watkins,"British, active 1867–71",,John and Charles Watkins,British,1840,1875,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/686150,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.812,false,true,686151,Photographs,Carte-de-visite,[Henry Weekes],,,,,,Photography Studio,,John and Charles Watkins,"British, active 1867–71",,John and Charles Watkins,British,1840,1875,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/686151,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.814,false,true,686153,Photographs,Carte-de-visite,[Henry Tanworth Wells],,,,,,Photography Studio,,John and Charles Watkins,"British, active 1867–71",,John and Charles Watkins,British,1840,1875,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/686153,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.818,false,true,686157,Photographs,Carte-de-visite,[Richard Westmacott],,,,,,Photography Studio,,John and Charles Watkins,"British, active 1867–71",,John and Charles Watkins,British,1840,1875,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/686157,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.820,false,true,686159,Photographs,Carte-de-visite,[Josiah Wood Whymper],,,,,,Photography Studio,,John and Charles Watkins,"British, active 1867–71",,John and Charles Watkins,British,1840,1875,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/686159,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.826,false,true,686165,Photographs,Carte-de-visite,[Henry Brittan Willis],,,,,,Photography Studio,,John and Charles Watkins,"British, active 1867–71",,John and Charles Watkins,British,1840,1875,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/686165,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.830,false,true,686169,Photographs,Carte-de-visite,[W.F. Woodington],,,,,,Photography Studio,,John and Charles Watkins,"British, active 1867–71",,John and Charles Watkins,British,1840,1875,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/686169,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.835,false,true,686174,Photographs,Carte-de-visite,[William Frederick Yeames],,,,,,Photography Studio,,John and Charles Watkins,"British, active 1867–71",,John and Charles Watkins,British,1840,1875,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/686174,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.863,false,true,686202,Photographs,Carte-de-visite,[Unknown Subject],,,,,,Photography Studio,,John and Charles Watkins,"British, active 1867–71",,John and Charles Watkins,British,1840,1875,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/686202,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.864,false,true,686203,Photographs,Carte-de-visite,[Unknown Subject],,,,,,Photography Studio,,John and Charles Watkins,"British, active 1867–71",,John and Charles Watkins,British,1840,1875,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/686203,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.867,false,true,686206,Photographs,Carte-de-visite,[Unknown Subject],,,,,,Photography Studio,,John and Charles Watkins,"British, active 1867–71",,John and Charles Watkins,British,1840,1875,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/686206,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.892,false,true,686231,Photographs,Carte-de-visite,[Professor Owen],,,,,,Photography Studio,,John and Charles Watkins,"British, active 1867–71",,John and Charles Watkins,British,1840,1875,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/686231,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.917,false,true,686256,Photographs,Carte-de-visite,[Henry O'Neil],,,,,,Photography Studio,,John and Charles Watkins,"British, active 1867–71",,John and Charles Watkins,British,1840,1875,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/686256,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.1098.4,false,true,631028,Photographs,Photograph,"Field Marshall Lord Raglan, Crimea",,,,,,Artist|Publisher|Publisher,,"Roger Fenton|P. & D. Colnaghi & Co.|Thomas Agnew & Sons, Ltd.","British, 1819–1869|London",,"Fenton, Roger|Colnaghi & Co., P. & D.|Agnew, Thomas & Sons, Ltd.",British,1819 |1760,1819 |9999,1855,1855,1855,Salted paper print from collodion glass negative,Image: 7 13/16 × 5 7/8 in. (19.9 × 14.9 cm) Mount: 23 5/8 × 17 1/4 in. (60 × 43.8 cm),"Gift of Joyce F. Menschel, 2013",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/631028,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -69.635.1,false,true,271528,Photographs,Photograph,Don Quixote in His Study,,,,,,Artist|Printer,,William Frederick Lake Price|J. Spencer,"British, London 1810–1896 Lee, Kent",,"Price, William Frederick Lake|Spencer, J.",British,1810-10-10,1896-12-09,1857,1857,1857,Albumen silver print from glass negative,Image: 31.9 x 28 cm (12 9/16 x 11 in.) Mount: 42.6 x 33.3 cm (16 3/4 x 13 1/8 in.),"Gift of A. Hyatt Mayor, 1969",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/271528,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.772,false,true,285943,Photographs,Photograph,Louis-Jacques-Mandé Daguerre,,,,,,Artist|Person in Photograph,,John Jabez Edwin Mayall|Louis-Jacques-Mandé Daguerre,"British, Oldham, Lancashire 1813–1901 West Sussex",,"Mayall, John Jabez Edwin|Daguerre, Louis-Jacques-Mandé",British,1813,1901,ca. 1860,1858,1862,Albumen silver print from glass negative,Image: 7 7/16 × 5 5/16 in. (18.9 × 13.5 cm) Mount: 9 5/8 in. × 6 13/16 in. (24.5 × 17.3 cm),"Gilman Collection, Purchase, Warner Communications Inc. Purchase Fund, by exchange, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285943,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.553,false,true,685893,Photographs,Carte-de-visite,[Baron Carlo (Charles) Marochetti],,,,,,Photography Studio|Person in Photograph,Person in photograph,John and Charles Watkins|Baron Charles Marochetti,"British, active 1867–71|Italian, Turin 1805–1867",,"John and Charles Watkins|Marochetti, Charles Baron",British,1840 |1805,1875 |1867,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/685893,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.528,false,true,685868,Photographs,Carte-de-visite,[John Linnel],,,,,,Artist|Person in Photograph,Person in photograph,Eotto's School of Photography|John Linnell,"British, active 1860s|British, London 1792–1882 Redhill, Surrey",,"Eotto's School of Photography|Linnell, John",British,1792,1882,1863,1863,1863,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/685868,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.523,false,true,685863,Photographs,Carte-de-visite,[John Frederick Lewis],,,,,,Photography Studio|Person in Photograph,Person in photograph,John and Charles Watkins|John Frederick Lewis,"British, active 1867–71|British, London 1805–1876 Walton-on-Thames",,"John and Charles Watkins|Lewis, John Frederick",British,1840 |1805,1875 |1876,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/685863,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.686,false,true,686025,Photographs,Carte-de-visite,[David Roberts],,,,,,Person in Photograph|Photography Studio,Person in photograph,David Roberts|John and Charles Watkins,"British, Stockbridge, Scotland 1796–1864 London|British, active 1867–71",,"Roberts, David|John and Charles Watkins",British,1796 |1840,1864 |1875,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/686025,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.180,false,true,685521,Photographs,Carte-de-visite,[Francis Danby],,,,,,Photography Studio|Person in Photograph,Person in photograph,John and Charles Watkins|Francis Danby,"British, active 1867–71|Irish, Killinick, County Wexford 1793–1861 Exmouth",,"John and Charles Watkins|Danby, Francis",British,1840 |1793,1875 |1861,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/685521,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.55,false,true,283138,Photographs,Negative; Photograph,"Temple of Wingless Victory, Lately Restored",,,,,,Artist,,George Wilson Bridges,"British, 1788–1864",,"Bridges, George Wilson",British,1788,1864,1848,1848,1848,Paper negative,Image: 16.7 x 20.6 cm (6 9/16 x 8 1/8 in.),"Gilman Collection, Purchase, William Talbott Hillman Foundation Gift, 2005",,,,,,,,,,,,Negatives,,http://www.metmuseum.org/art/collection/search/283138,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1997.382.6,false,true,282010,Photographs,Negatives; Photographs,[Botanical Specimens],,,,,,Artist,Attributed to,Robert Hunt,"British, 1807–1887",,"Hunt, Robert",British,1807,1887,ca. 1841,1839,1843,Photogenic drawing negatives on paper and fabric,"Sheet: 11 1/8 × 11 5/16 in. (28.2 × 28.8 cm), album page Top Left Image: 4 1/8 × 3 15/16 in. (10.5 × 10 cm), irregularly trimmed Top Right Image: 4 1/2 × 3 11/16 in. (11.4 × 9.4 cm), irregularly trimmed Bottom Left Image: 1 7/8 × 3 3/8 in. (4.8 × 8.6 cm) Bottom Right Image: 2 9/16 × 3 11/16 in. (6.5 × 9.3 cm), irregularly trimmed","The Rubel Collection, Purchase, Anonymous Gift, 1997",,,,,,,,,,,,Negatives,,http://www.metmuseum.org/art/collection/search/282010,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.259,false,true,285469,Photographs,Negative; Photograph,"Crystal Palace, Hyde Park, Transept",,,,,,Artist,,Benjamin Brecknell Turner,"British, 1815–1894",,"Turner, Benjamin Brecknell",British,1815,1894,1852,1852,1852,Paper negative,Image: 30.2 x 40.1 cm (11 7/8 x 15 13/16 in.),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Negatives,,http://www.metmuseum.org/art/collection/search/285469,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.883,false,true,286446,Photographs,Negative; Photograph,"[Rural Manor, Possibly Bredicot]",,,,,,Artist,,Benjamin Brecknell Turner,"British, 1815–1894",,"Turner, Benjamin Brecknell",British,1815,1894,1852–54,1852,1854,Waxed paper negative,11 5/8 x 15 1/2,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Negatives,,http://www.metmuseum.org/art/collection/search/286446,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.801 (1–47),false,true,285799,Photographs,Album,Views of the Crystal Palace,,,,,,Artist,Attributed to,Philip Henry Delamotte,"British, 1821–1889",,"Delamotte, Philip Henry",British,1821,1889,ca. 1859,1857,1861,Albumen silver prints from glass negatives,12 1/16 × 10 5/16 × 1 in. (30.7 × 26.2 × 2.5 cm),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Albums,,http://www.metmuseum.org/art/collection/search/285799,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1997.382.5,false,true,282008,Photographs,Paper negative,[Chicken Feathers],,,,,,Artist,,Nevil Story Maskelyne,"British, 1823–1911",,"Maskelyne, Nevil Story",British,1823,1911,ca. 1840,1838,1842,Photogenic drawing negative,"Image: 7 5/16 × 7 5/8 in. (18.6 × 19.3 cm), irregularly trimmed","The Rubel Collection, Purchase, Lila Acheson Wallace and Anonymous Gifts, 1997",,,,,,,,,,,,Negatives,,http://www.metmuseum.org/art/collection/search/282008,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.284,false,true,288086,Photographs,Cabinet cards,[Follett Family Album of Children Costumed for a Fancy Dress Ball],,,,,,Artist,,Owen Angel,"British, ca. 1821–1909",,"Angel, Owen",British,1821,1909,ca. 1880,1875,1885,Albumen silver prints from glass negatives with applied color,Album: 28.6 x 22.9 x 4.4 cm (11 1/4 x 9 x 1 3/4 in.) Case,"Purchase, Joseph M. Cohen, William Talbott Hillman Foundation, Robert and Joyce Menschel Family Foundation, Robert D. and Virginia R. Joffe, Paula and Ira M. Resnick, and Maureen and Noel Testa Gifts, 2007",,,,,,,,,,,,Albums,,http://www.metmuseum.org/art/collection/search/288086,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2005.100.623a, b",false,true,283248,Photographs,Photograph; Photomechanical print,Aubrey Beardsley,,,,,,Artist,,Frederick H. Evans,"British, London 1853–1943 London",,"Evans, Frederick Henry",British,1853,1943,ca. 1894,1892,1896,Platinum print; photogravure,13.6 x 9.7 cm (5 3/8 x 3 13/16 in.); 12.3 x 9.5 cm (4 13/16 x 3 3/4 in.),"Gilman Collection, Purchase, Mr. and Mrs. Henry R. Kravis Gift, 2005",,,,,,,,,,,,Photographs|Prints,,http://www.metmuseum.org/art/collection/search/283248,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1991.1073.88,false,true,266372,Photographs,Photographically illustrated book,"Egypt, Sinai and Palestine. Supplementary Volume",,,,,,Artist,,Francis Frith,"British, Chesterfield, Derbyshire 1822–1898 Cannes, France",,"Frith, Francis",British,1822,1898,1860s,1860,1869,Albumen silver prints,,"Rogers Fund, 1908, transferred from the Library",,,,,,,,,,,,Books,,http://www.metmuseum.org/art/collection/search/266372,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1991.1073.91,false,true,266375,Photographs,Photographically illustrated book,Sinai and Palestine,,,,,,Artist,,Francis Frith,"British, Chesterfield, Derbyshire 1822–1898 Cannes, France",,"Frith, Francis",British,1822,1898,1860s,1860,1869,Albumen silver prints,,"Rogers Fund, 1908, transferred from the Library",,,,,,,,,,,,Books,,http://www.metmuseum.org/art/collection/search/266375,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -54.550.2,false,true,269745,Photographs,Print,[Peacock],,,,,,Artist,,Frederick H. Evans,"British, London 1853–1943 London",,"Evans, Frederick Henry",British,1853,1943,1925,1925,1925,Lithograph,,"Gift of Gordon Conn, 1954",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/269745,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.1066.1–.86,false,true,286659,Photographs,Photograph,[86 Stereographic Views of The International Exhibition of 1862],,,,,,Artist,,William England,British,,"England, William",British,,1896,1862,1862,1862,Albumen silver prints from glass negatives,,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286659,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.400.83,false,true,291836,Photographs,Daguerreotype,[Stereograph Still-life of Game with Rake and Onion Jar],,,,,,Artist,,T. R. Williams,"British, born 1825",,"Williams, T. R.",British,1825,1825,1854 or later,1854,1860,Daguerreotype,"Image: 7.1 x 6 cm (2 13/16 x 2 3/8 in.), each Mount: 8.3 x 17.1 cm (3 1/4 x 6 3/4 in.)","Bequest of Herbert Mitchell, 2008",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/291836,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.400.88,false,true,291841,Photographs,Daguerreotype,[Stereograph Still-life of Fowl with Initialed Barrel and Root Vegetables],,,,,,Artist,,T. R. Williams,"British, born 1825",,"Williams, T. R.",British,1825,1825,1850s,1850,1859,Daguerreotype,"Image: 6.8 x 5.6 cm (2 11/16 x 2 3/16 in.), each Mount: 8.3 x 17.5 cm (3 1/4 x 6 7/8 in.)","Bequest of Herbert Mitchell, 2008",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/291841,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.400.92,false,true,291845,Photographs,Daguerreotype,"[Stereograph Still-life with Cockatoo, Ornamental Ball, Lace, Peacock Feathers]",,,,,,Artist,,T. R. Williams,"British, born 1825",,"Williams, T. R.",British,1825,1825,1850s,1850,1859,Daguerreotype,"Image: 5.9 x 7.1 cm (2 5/16 x 2 13/16 in.), each Mount: 8.3 x 17.3 cm (3 1/4 x 6 13/16 in.)","Bequest of Herbert Mitchell, 2008",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/291845,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.400.93,false,true,291846,Photographs,Daguerreotype,"[Stereograph Still-life with Cockatoo, Ornamental Ball, Lace, Statuette]",,,,,,Artist,,T. R. Williams,"British, born 1825",,"Williams, T. R.",British,1825,1825,1850s,1850,1859,Daguerreotype,"Image: 7.1 x 5.9 cm (2 13/16 x 2 5/16 in.), each Mount: 8.4 x 17.3 cm (3 5/16 x 6 13/16 in.)","Bequest of Herbert Mitchell, 2008",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/291846,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.400.94,false,true,291847,Photographs,Daguerreotype,"[Stereograph Still-life with Cockatoo, Mirror, Ornamental Ball, Vases, and Lace]",,,,,,Artist,,T. R. Williams,"British, born 1825",,"Williams, T. R.",British,1825,1825,1850s,1850,1859,Daguerreotype,"Image: 7 x 5.9 cm (2 3/4 x 2 5/16 in.), each Mount: 8.3 x 17.5 cm (3 1/4 x 6 7/8 in.)","Bequest of Herbert Mitchell, 2008",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/291847,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1994.519,false,true,267097,Photographs,Photograph,"Portal of the Convent of Sancti Spiritu, Salamanca",,,,,,Artist,,Charles Clifford,"Welsh, 1819–1863",,"Clifford, Charles",British,1819,1863,1853,1853,1853,Albumen silver print from paper negative,Image: 39.9 x 31.4 cm (15 11/16 x 12 3/8 in.) Mount: 61.8 x 47cm (24 5/16 x 18 1/2in.),"Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 1994",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267097,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1988.1052,false,true,265920,Photographs,Photograph,"[Puente del Diablo, Martorell]",,,,,,Artist,,Charles Clifford,"Welsh, 1819–1863",,"Clifford, Charles",British,1819,1863,ca. 1856,1854,1858,Albumen silver print from glass negative,Image: 28.4 x 41.5 cm. (11 3/16 x 16 5/16 in.),"Purchase, Joyce and Robert Menschel Gift, 1988",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/265920,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.1047,false,true,266109,Photographs,Photograph,"[Cloisters of the Church of Saint John of the Kings, Toledo, Spain]",,,,,,Artist,,Charles Clifford,"Welsh, 1819–1863",,"Clifford, Charles",British,1819,1863,ca. 1858,1856,1860,Albumen silver print from glass negative,41.6 x 31.7 cm. (16 3/8 x 12 1/2 in.),"Purchase, Harriette and Noel Levine Gift, 1989",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/266109,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.1048,false,true,266110,Photographs,Photograph,"[Madrid. Facade of the Hospital of ""La Latina""]",,,,,,Artist,,Charles Clifford,"Welsh, 1819–1863",,"Clifford, Charles",British,1819,1863,ca. 1857,1855,1859,Albumen silver print from glass negative,37.7 x 28.2 cm. (14 13/16 x 11 1/8 in.),"Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 1989",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/266110,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.250.1,false,true,288044,Photographs,Photograph,Zaragoza: Porta de los Gigantes,,,,,,Artist,,Charles Clifford,"Welsh, 1819–1863",,"Clifford, Charles",British,1819,1863,1860,1860,1860,Albumen silver print from glass negative,Image: 41 x 31.7 cm (16 1/8 x 12 1/2 in.) Mount: 51 x 47.5 cm (20 1/16 x 18 11/16 in.),"Gift of C. David and Mary Robinson, 2007",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288044,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.250.2,false,true,288043,Photographs,Photograph,"[The Lion Court at the Alhambra, Viewed from Beneath the Portico Temple]",,,,,,Artist,,Charles Clifford,"Welsh, 1819–1863",,"Clifford, Charles",British,1819,1863,1862,1862,1862,Albumen silver print from glass negative,Image: 42 x 32.1 cm (16 9/16 x 12 5/8 in.) Mount: 63.3 x 46.3 cm (24 15/16 x 18 1/4 in.),"Gift of C. David and Mary Robinson, 2007",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288043,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.65,false,true,283154,Photographs,Photograph,"Principal Doorway of the Carthusian Monastery, Burgos",,,,,,Artist,,Charles Clifford,"Welsh, 1819–1863",,"Clifford, Charles",British,1819,1863,1853,1853,1853,Albumen silver print from paper negative,Image: 33.9 x 28.4cm (13 3/8 x 11 3/16in.) Mat: 71.1 x 55.9 cm (28 x 22 in.) Frame: 81.3 x 66 cm (32 x 26 in.),"Gilman Collection, Purchase, Alfred Stieglitz Society Gifts, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/283154,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.250,false,true,283156,Photographs,Photograph,"The Walnut Tree of Emperor Charles V, Yuste",,,,,,Artist,,Charles Clifford,"Welsh, 1819–1863",,"Clifford, Charles",British,1819,1863,1858,1858,1858,Albumen silver print from glass negative,Image: 41.6 × 30 cm (16 3/8 × 11 13/16 in.) Mount: 62.3 × 47.4 cm (24 1/2 × 18 11/16 in.),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/283156,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.561,false,true,286661,Photographs,Photograph,[The Armor of Philip III],,,,,,Artist,,Charles Clifford,"Welsh, 1819–1863",,"Clifford, Charles",British,1819,1863,1866,1866,1866,Albumen silver print from glass negative,Image: 33.3 x 23 cm (13 1/8 x 9 1/16 in.) Mount: 57.2 x 45.7 cm (22 1/2 x 18 in.),"Gilman Collection, Purchase, Mr. and Mrs. Henry R. Kravis Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286661,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.504.12,false,true,292002,Photographs,Photograph,"Baleares, Aldeanos de Palma y sus alrrededores",,,,,,Artist,,Charles Clifford,"Welsh, 1819–1863",,"Clifford, Charles",British,1819,1863,1860,1860,1860,Albumen silver print from glass negative,Image: 23.9 × 27.6 cm (9 7/16 × 10 7/8 in.) Mount: 42.3 × 58.1 cm (16 5/8 × 22 7/8 in.),"Gilman Collection, Purchase, Mr. and Mrs. Henry R. Kravis Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/292002,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.504.31,false,true,292003,Photographs,Photograph,"Monserrat, Vista general de la montaña desde Monistrol",,,,,,Artist,,Charles Clifford,"Welsh, 1819–1863",,"Clifford, Charles",British,1819,1863,1860,1860,1860,Albumen silver print from glass negative,Image: 31.7 × 41.5 cm (12 1/2 × 16 5/16 in.) Mount: 42.3 × 58.2 cm (16 5/8 × 22 15/16 in.),"Gilman Collection, Purchase, Mr. and Mrs. Henry R. Kravis Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/292003,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.504.46,false,true,283155,Photographs,Photograph,"Zaragoza, Patio de la Casa Conocida con el Nombre de los Infantes",,,,,,Artist,,Charles Clifford,"Welsh, 1819–1863",,"Clifford, Charles",British,1819,1863,1860,1860,1860,Albumen silver print from glass negative,Image: 30.9 × 42.5 cm (12 3/16 × 16 3/4 in.) Mount: 42.3 × 58 cm (16 5/8 × 22 13/16 in.),"Gilman Collection, Purchase, Mr. and Mrs. Henry R. Kravis Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/283155,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -46.1.79,false,true,269025,Photographs,Photograph,"[Colosseum, Rome]",,,,,,Artist,,George Wilson Bridges,"British, 1788–1864",,"Bridges, George Wilson",British,1788,1864,1850s,1850,1859,Salted paper print from paper negative,,"David Hunter McAlpin Fund, 1946",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269025,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.159.1,false,true,306202,Photographs,Photograph,"Garden of Selvia, Syracuse, Sicily",,,,,,Artist,,George Wilson Bridges,"British, 1788–1864",,"Bridges, George Wilson",British,1788,1864,1846,1846,1846,Salted paper print from paper negative,Image: 6 15/16 × 8 9/16 in. (17.7 × 21.7 cm) Sheet: 7 5/16 × 8 13/16 in. (18.5 × 22.4 cm),"Bequest of Maurice B. Sendak, 2012",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306202,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.159.2,false,true,306203,Photographs,Photograph,"Benedictine Convent, Catania",,,,,,Artist,,George Wilson Bridges,"British, 1788–1864",,"Bridges, George Wilson",British,1788,1864,1846,1846,1846,Salted paper print from paper negative,Image: 6 3/4 × 8 9/16 in. (17.1 × 21.7 cm) Sheet: 7 11/16 × 9 13/16 in. (19.5 × 24.9 cm),"Bequest of Maurice B. Sendak, 2012",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306203,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.661,false,true,286361,Photographs,Photograph,[The Photographer before his Tent on the Site of the Pyramid of Khafre (Chephren)],,,,,,Artist,,George Wilson Bridges,"British, 1788–1864",,"Bridges, George Wilson",British,1788,1864,1851,1851,1851,Salted paper print from paper negative,Mount: 10 3/8 in. × 13 9/16 in. (26.4 × 34.5 cm) Image: 6 7/16 × 8 7/16 in. (16.3 × 21.4 cm),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286361,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.846a,false,true,286359,Photographs,Photograph,Temple of Victory,,,,,,Artist,,George Wilson Bridges,"British, 1788–1864",,"Bridges, George Wilson",British,1788,1864,ca. 1848,1846,1850,Salted paper print from paper negative,Mount: 10 7/16 in. × 13 5/8 in. (26.5 × 34.6 cm) Image: 6 7/8 × 8 1/4 in. (17.5 × 21 cm),"Gilman Collection, Purchase, Joseph M. Cohen Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286359,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2004.172,false,true,285421,Photographs,Photogram,Spiraea aruncus (Tyrol),,,,,,Artist,,Anna Atkins,"British, 1799–1871",,"Atkins, Anna",British,1799,1871,1851–54,1851,1854,Cyanotype,Image: 35.1 x 24.6 cm (13 13/16 x 9 11/16 in.),"Purchase, Alfred Stieglitz Society Gifts, 2004",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285421,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -63.606.1.21,false,true,270836,Photographs,Photograph,Still Life and Embroidery,,,,,,Artist,,Robert Wilfred Skeffington Lutwidge,"British, 1802–1873",,"Lutwidge, Robert Wilfred Skeffington",British,1802,1873,1856,1856,1856,Albumen silver print,,"The Elisha Whittelsey Collection, The Elisha Whittelsey Fund, 1963",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/270836,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.382 (40),false,true,287930,Photographs,Photograph,Mr and Mrs W. Beach,,,,,,Artist,,Jane Martha St. John,"British, 1803–1882",,"St., John Jane Martha",British,1803,1882,1853–56,1853,1856,Salted paper print,Image: 15.7 × 13.8 cm (6 3/16 × 5 7/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287930,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.382 (66),false,true,287956,Photographs,Photograph,"Stone Pines, Villa Pamfili Doria, Rome",,,,,,Artist,,Jane Martha St. John,"British, 1803–1882",,"St., John Jane Martha",British,1803,1882,1856,1856,1856,Albumen silver print from paper negative,Image: 20 x 24.8 cm (7 7/8 x 9 3/4 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287956,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.382 (67),false,true,287957,Photographs,Photograph,"The Forum, Rome",,,,,,Artist,Possibly by,Jane Martha St. John,"British, 1803–1882",,"St., John Jane Martha",British,1803,1882,1853–56,1853,1856,Albumen silver print,Image: 17.5 × 24.9 cm (6 7/8 × 9 13/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287957,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.382 (68),false,true,287727,Photographs,Photograph,The Colosseum,,,,,,Artist,,Jane Martha St. John,"British, 1803–1882",,"St., John Jane Martha",British,1803,1882,1856,1856,1856,Albumen silver print from paper negative,Image: 19.5 x 25 cm (7 11/16 x 9 13/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287727,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.382 (69),false,true,287958,Photographs,Photograph,Bridge of Augustus at Nani,,,,,,Artist,Possibly by,Jane Martha St. John,"British, 1803–1882",,"St., John Jane Martha",British,1803,1882,1853–56,1853,1856,Albumen silver print,Image: 19.2 × 25 cm (7 9/16 × 9 13/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287958,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.382 (70),false,true,287959,Photographs,Photograph,St. Peters from the Pincian Hill,,,,,,Artist,Possibly by,Jane Martha St. John,"British, 1803–1882",,"St., John Jane Martha",British,1803,1882,1853–56,1853,1856,Albumen silver print,Image: 19.1 × 24.7 cm (7 1/2 × 9 3/4 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287959,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.382 (71),false,true,287960,Photographs,Photograph,"Pincian Garden, Rome",,,,,,Artist,Possibly by,Jane Martha St. John,"British, 1803–1882",,"St., John Jane Martha",British,1803,1882,1853–56,1853,1856,Albumen silver print,Image: 19.3 × 25 cm (7 5/8 × 9 13/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287960,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.382 (72),false,true,287961,Photographs,Photograph,"Pincian Garden, Rome",,,,,,Artist,Possibly by,Jane Martha St. John,"British, 1803–1882",,"St., John Jane Martha",British,1803,1882,1853–56,1853,1856,Albumen silver print,19.5 24.8,"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287961,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.382 (73),false,true,287962,Photographs,Photograph,"Old Cypress Trees in Carthusian Convent, Rome",,,,,,Artist,Possibly by,Jane Martha St. John,"British, 1803–1882",,"St., John Jane Martha",British,1803,1882,1853–56,1853,1856,Albumen silver print,Image: 24.9 × 19.7 cm (9 13/16 × 7 3/4 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287962,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.382 (81),false,true,287970,Photographs,Photograph,[Arch of Titus],,,,,,Artist,,Jane Martha St. John,"British, 1803–1882",,"St., John Jane Martha",British,1803,1882,1853–56,1853,1856,Albumen silver print,Image: 19.2 × 25 cm (7 9/16 × 9 13/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287970,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2005.100.382 (42a, b)",false,true,287932,Photographs,Photograph,"Harry Strangways, present Lord Ilchester; From a Picture Taken from a Church at Kertch",,,,,,Artist,,Jane Martha St. John,"British, 1803–1882",,"St., John Jane Martha",British,1803,1882,1853–56,1853,1856,Salted paper print; albumen silver print,Image: 14.8 × 12 cm (5 13/16 × 4 3/4 in.) Image: 10 × 9.1 cm (3 15/16 × 3 9/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287932,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2005.100.382 (60a, b)",false,true,287950,Photographs,Photograph,Clematis Cerulea; Hollyhocks,,,,,,Artist,,Jane Martha St. John,"British, 1803–1882",,"St., John Jane Martha",British,1803,1882,1853–56,1853,1856,Albumen silver print,Image: 145 × 11.4 cm (57 1/16 × 4 1/2 in.) (a) Image: 14.5 × 10.7 cm (5 11/16 × 4 3/16 in.) (b),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287950,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -63.606.1.1,false,true,270823,Photographs,Photograph,"Copy of a Bust of Her Majesty Queen Victoria, by Joseph Durham, Esq. F.S.A.",,,,,,Artist,,Hugh Welch Diamond,"British, 1808–1886",,"Diamond, Hugh Welch",British,1808,1886,1857,1857,1857,Albumen silver print from glass negative,,"The Elisha Whittelsey Collection, The Elisha Whittelsey Fund, 1963",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/270823,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.19,false,true,283091,Photographs,Photograph,"Patient, Surrey County Lunatic Asylum",,,,,,Artist,,Hugh Welch Diamond,"British, 1808–1886",,"Diamond, Hugh Welch",British,1808,1886,1850–58,1850,1858,Albumen silver print from glass negative,Mat: 11 1/4 × 9 1/8 in. (28.6 × 23.2 cm) Image: 7 1/2 × 5 1/2 in. (19.1 × 14 cm),"Gilman Collection, Purchase, Ann Tenenbaum and Thomas H. Lee Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/283091,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.812,false,true,285964,Photographs,Photograph,"[Patient, Surrey County Lunatic Asylum]",,,,,,Artist,,Hugh Welch Diamond,"British, 1808–1886",,"Diamond, Hugh Welch",British,1808,1886,1850–55,1850,1855,Albumen silver print from glass negative,Image: 18.2 x 12.9 cm (7 3/16 x 5 1/16 in.),"Gilman Collection, Purchase, Anonymous Gifts, by exchange, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285964,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -63.606.1.16,false,true,270830,Photographs,Photograph,Peasants of the Alto-Douro,,,,,,Artist,,Joseph James Forrester,"British, 1809–1862",,"Forrester, Joseph James",British,1809,1862,1856,1856,1856,Albumen silver print from glass negative,Image: 18.5 x 15.1 cm (7 5/16 x 5 15/16 in.) Mount: 44 x 30.4 cm (17 5/16 x 11 15/16 in.),"The Elisha Whittelsey Collection, The Elisha Whittelsey Fund, 1963",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/270830,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -63.606.1.25,false,true,270840,Photographs,Photograph,Study for a Picture,,,,,,Artist,,Thomas George Mackinlay,"British, 1809–1865",,"Mackinlay, Thomas George Rev.",British,1809,1865,1856,1856,1856,Albumen silver print,,"The Elisha Whittelsey Collection, The Elisha Whittelsey Fund, 1963",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/270840,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -63.606.1.31,false,true,270847,Photographs,Photograph,"Church Porch, Earlham, near Norwich",,,,,,Artist,,William Harcourt Ranking,"British, 1814–1867",,"Ranking, William Harcourt Dr.",British,1814,1867,1857,1857,1857,Albumen silver print,,"The Elisha Whittelsey Collection, The Elisha Whittelsey Fund, 1963",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/270847,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -63.606.1.38,false,true,270854,Photographs,Photograph,Bonchurch,,,,,,Artist,,Benjamin Brecknell Turner,"British, 1815–1894",,"Turner, Benjamin Brecknell",British,1815,1894,1850s,1850,1859,Gelatin silver print,,"The Elisha Whittelsey Collection, The Elisha Whittelsey Fund, 1963",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/270854,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.756,false,true,285627,Photographs,Photograph,"Pepperharrow Park, Surrey",,,,,,Artist,,Benjamin Brecknell Turner,"British, 1815–1894",,"Turner, Benjamin Brecknell",British,1815,1894,1852–54,1852,1854,Albumen silver print from paper negative,11 1/4 x 15 1/4,"Gilman Collection, Purchase, Ann Tenenbaum and Thomas H. Lee Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285627,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -63.606.1.3,false,true,270845,Photographs,Photograph,Wild Flowers,,,,,,Artist,,Mark Anthony,"British, 1817–1886",,"Anthony, Mark",British,1817,1886,ca. 1857,1855,1859,Albumen silver print,Image: 21.4 x 16.1 cm (8 7/16 x 6 5/16 in.) Mount: 44 x 30.4 cm (17 5/16 x 11 15/16 in.),"The Elisha Whittelsey Collection, The Elisha Whittelsey Fund, 1963",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/270845,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -X.663.1,false,true,271657,Photographs,Stereograph,The Temple. Collection of Antiquities,,,,,,Artist,,Roger Fenton,"British, 1819–1869",,"Fenton, Roger",British,1819,1819,1850s,1850,1859,Albumen silver print from glass negative,,Museum Accession,,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/271657,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -X.663.2,false,true,271658,Photographs,Stereograph,"The Megatherium, British Museum",,,,,,Artist,,Roger Fenton,"British, 1819–1869",,"Fenton, Roger",British,1819,1819,1850s,1850,1859,Albumen silver print from glass negative,,Museum Accession,,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/271658,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -X.663.3,false,true,271659,Photographs,Stereograph,"The Lycian Saloon, British Museum",,,,,,Artist,,Roger Fenton,"British, 1819–1869",,"Fenton, Roger",British,1819,1819,1850s,1850,1859,Albumen silver print from glass negative,,Museum Accession,,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/271659,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1988.1027,false,true,265901,Photographs,Photograph,"Falls of the Llugwy, at Pont-y-Pair",,,,,,Artist,,Roger Fenton,"British, 1819–1869",,"Fenton, Roger",British,1819,1819,1857,1857,1857,Albumen silver print from glass negative,35.8 x 42.9 cm (14 1/16 x 16 7/8 in.),"Purchase, Louis V. Bell Fund and Mrs. Jackson Burke Gift, 1988",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/265901,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -53.531.48,false,true,269654,Photographs,Photograph,"The Council of War on the Morning of the Taking of the Mamelon. Lord Raglan, Omar Pasha, Marshal Pélissier",,,,,,Artist,,Roger Fenton,"British, 1819–1869",,"Fenton, Roger",British,1819,1819,1855,1855,1855,Albumen silver print from glass negative,,"The Elisha Whittelsey Collection, The Elisha Whittelsey Fund, 1953",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269654,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.6,false,true,283078,Photographs,Photograph,"Roslin Chapel, South Porch",,,,,,Artist,,Roger Fenton,"British, 1819–1869",,"Fenton, Roger",British,1819,1819,1856,1856,1856,Salted paper print from glass negative,Image: 35.8 x 43.3 cm (14 1/8 x 17 1/16 in.) Mount: 41 x 48.2 cm (16 1/8 x 19 in.),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/283078,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.7,false,true,283079,Photographs,Photograph,Rievaulx Abbey,,,,,,Artist,,Roger Fenton,"British, 1819–1869",,"Fenton, Roger",British,1819,1819,1854,1854,1854,Albumen silver print from glass negative,Image: 35.7 x 29.8 cm (14 1/16 x 11 3/4 in.) Mount: 40.3 x 34.5 cm (15 7/8 x 13 9/16 in.),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/283079,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.8,false,true,283080,Photographs,Photograph,"Wharfe and Pool, Below the Strid",,,,,,Artist,,Roger Fenton,"British, 1819–1869",,"Fenton, Roger",British,1819,1819,1854,1854,1854,Salted paper print,Image: 34.5 x 28.2 cm (13 9/16 x 11 1/8 in.) Mount: 56.8 x 44.9 cm (22 3/8 x 17 11/16 in.),"Gilman Collection, Purchase, W. Bruce and Delaney H. Lundberg Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/283080,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.9,false,true,283081,Photographs,Photograph,"Salisbury Cathedral - The Nave, from the South Transept",,,,,,Artist,,Roger Fenton,"British, 1819–1869",,"Fenton, Roger",British,1819,1819,1858,1858,1858,Albumen silver print from glass negative,30.2 x 31.1 cm (11 7/8 x 12 1/4 in. ),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/283081,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.460.8,false,true,290476,Photographs,Photograph,[Royal Children in Tableau of the Seasons],,,,,,Artist,,Roger Fenton,"British, 1819–1869",,"Fenton, Roger",British,1819,1819,1854,1854,1854,Albumen silver print from glass negative,15.5 x 16 cm (6 1/8 x 6 5/16 in.),"Gift of Paul F. Walter, 2009",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/290476,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.460.9,false,true,290477,Photographs,Photograph,Group at Head Quarters,,,,,,Artist,,Roger Fenton,"British, 1819–1869",,"Fenton, Roger",British,1819,1819,1855,1855,1855,Salted paper print from glass negative,Image: 19.2 x 16.4 cm (7 9/16 x 6 7/16 in.) Mount: 57.7 x 40.2 cm (22 11/16 x 15 13/16 in.),"Gift of Paul F. Walter, 2009",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/290477,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1997.382.33,false,true,282038,Photographs,Photograph,Valley of the Ribble and Pendle Hill,,,,,,Artist,,Roger Fenton,"British, 1819–1869",,"Fenton, Roger",British,1819,1819,1859,1859,1859,Albumen silver print from glass negative,32.1 x 42.5 cm (12 5/8 x 16 3/4 in.),"The Rubel Collection, Purchase, Lila Acheson Wallace and Ann Tenenbaum and Thomas H. Lee Gifts, 1997",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/282038,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1997.382.34,true,true,282039,Photographs,Photograph,[Reclining Odalisque],,,,,,Artist,,Roger Fenton,"British, 1819–1869",,"Fenton, Roger",British,1819,1819,1858,1858,1858,Salted paper print from glass negative,28.5 x 39 cm (11 1/4 x 15 3/8 in.),"The Rubel Collection, Purchase, Lila Acheson Wallace, Anonymous, Joyce and Robert Menschel, Jennifer and Joseph Duke, and Ann Tenenbaum and Thomas H. Lee Gifts, 1997",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/282039,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1997.382.35,true,true,282040,Photographs,Photograph,[Landscape with Clouds],,,,,,Artist,,Roger Fenton,"British, 1819–1869",,"Fenton, Roger",British,1819,1819,probably 1856,1856,1856,Salted paper print from glass negative,"Image: 31.4 x 44.3 cm (12 3/8 x 17 7/16 in.), irregular","The Rubel Collection, Purchase, Anonymous Gift, Curator's Discretionary Grant from The Judith Rothschild Foundation, and Thomas Walther Gift, 1997",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/282040,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.15,false,true,283087,Photographs,Photograph,[Still Life with Fruit],,,,,,Artist,,Roger Fenton,"British, 1819–1869",,"Fenton, Roger",British,1819,1819,1860,1860,1860,Albumen silver print from glass negative,35.2 x 43.1cm (13 7/8 x 16 15/16in.) Mount: 46.6 x 60.3 cm (18 3/8 x 23 3/4 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/283087,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.66,false,true,283157,Photographs,Photograph,"Moscow, Domes of Churches in the Kremlin",,,,,,Artist,,Roger Fenton,"British, 1819–1869",,"Fenton, Roger",British,1819,1819,1852,1852,1852,Salted paper print from paper negative,Image: 17.9 x 21.6 cm (7 1/16 x 8 1/2 in.) Mount: 43.7 x 60.1 cm (17 3/16 x 23 11/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/283157,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.67,false,true,283158,Photographs,Photograph,"Landing Place, Railway Stores, Balaklava",,,,,,Artist,,Roger Fenton,"British, 1819–1869",,"Fenton, Roger",British,1819,1819,1855,1855,1855,Salted paper print from glass negative,Image: 27.9 x 36.4 cm (11 x 14 5/16 in.) Mount: 43.5 x 58.9 cm (17 1/8 x 23 3/16 in.),"Gilman Collection, Purchase, Alfred Stieglitz Society Gifts, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/283158,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.68,false,true,283159,Photographs,Photograph,Sebastopol from Cathcart's Hill,,,,,,Artist,,Roger Fenton,"British, 1819–1869",,"Fenton, Roger",British,1819,1819,1855,1855,1855,Salted paper print from glass negative,Image: 21.9 x 34.7 cm (8 5/8 x 13 11/16 in.) Mount: 41.7 x 57.2 cm (16 7/16 x 22 1/2 in.),"Gilman Collection, Purchase, Mrs. Walter Annenberg and The Annenberg Foundation Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/283159,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -63.606.1.14,false,true,270828,Photographs,Photograph,Birth of St. John,,,,,,Artist,,Roger Fenton,"British, 1819–1869",,"Fenton, Roger",British,1819,1819,1857,1857,1857,Albumen silver print,,"The Elisha Whittelsey Collection, The Elisha Whittelsey Fund, 1963",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/270828,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.187,false,true,286419,Photographs,Photograph,Cooking House of the 8th Hussars,,,,,,Artist,,Roger Fenton,"British, 1819–1869",,"Fenton, Roger",British,1819,1819,1855,1855,1855,Salted paper print from glass negative,Image: 15.9 x 20.2 cm (6 1/4 x 7 15/16 in.) Mount: 43.7 x 59.4 cm (17 3/16 x 23 3/8 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286419,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.275,false,true,286435,Photographs,Photograph,"Rievaulx Abbey, the High Altar",,,,,,Artist,,Roger Fenton,"British, 1819–1869",,"Fenton, Roger",British,1819,1819,1854,1854,1854,Albumen silver print from glass negative,Image: 29.5 x 36.5 cm (11 5/8 x 14 3/8 in.) Mount: 48 x 61.5 cm (18 7/8 x 24 3/16 in.),"Gilman Collection, Purchase, William Talbott Hillman Foundation Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286435,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.282,false,true,286715,Photographs,Photograph,[Lady on Horseback],,,,,,Artist,,Roger Fenton,"British, 1819–1869",,"Fenton, Roger",British,1819,1819,1850s,1850,1859,Salted paper print from glass negative,"21.3 x 22.9 cm (8 3/8 x 9 in.), irregularly trimmed","Gilman Collection, Purchase, Harriette and Noel Levine Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286715,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.285,false,true,286386,Photographs,Photograph,[Self-Portrait],,,,,,Artist,,Roger Fenton,"British, 1819–1869",,"Fenton, Roger",British,1819,1819,February 1852,1852,1852,Albumen silver print from glass negative,Image: 12.2 x 9 cm (4 13/16 x 3 9/16 in.),"Gilman Collection, Purchase, Harriette and Noel Levine Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286386,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.323,false,true,286569,Photographs,Photograph,Omar Pasha,,,,,,Artist,,Roger Fenton,"British, 1819–1869",,"Fenton, Roger",British,1819,1819,1855,1855,1855,Salted paper print from glass negative,Image: 17.9 x 13.4 cm (7 1/16 x 5 1/4 in.) Mount: 59.3 x 42.3 cm (23 3/8 x 16 5/8 in.),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286569,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.348,false,true,286462,Photographs,Photograph,South Front of the Kremlin from the Old Bridge,,,,,,Artist,,Roger Fenton,"British, 1819–1869",,"Fenton, Roger",British,1819,1819,1852,1852,1852,Salted paper print from paper negative,Image: 17.7 × 21.1 cm (6 15/16 × 8 5/16 in.),"Gilman Collection, Purchase, Harriette and Noel Levine Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286462,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.554,false,true,286568,Photographs,Photograph,"Landing Place, Ordnance Wharf, Balaklava",,,,,,Artist,,Roger Fenton,"British, 1819–1869",,"Fenton, Roger",British,1819,1819,1855,1855,1855,Salted paper print from glass negative,Image: 26 x 35.1 cm (10 1/4 x 13 13/16 in.) Mount: 43.2 x 59.4 cm (17 x 23 3/8 in.),"Gilman Collection, Purchase, Joseph M. Cohen Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286568,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.663,false,true,285999,Photographs,Photograph,Captain Burnsby of the Grenadier Guards,,,,,,Artist,,Roger Fenton,"British, 1819–1869",,"Fenton, Roger",British,1819,1819,1855,1855,1855,Salted paper print from glass negative,Image: 16.3 x 15.6 cm (6 7/16 x 6 1/8 in.) Mount: 16.1 x 16.1 cm (6 5/16 x 6 5/16 in.),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285999,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.664,false,true,286393,Photographs,Photograph,Henry Duberly and Mrs. Duberly,,,,,,Artist,,Roger Fenton,"British, 1819–1869",,"Fenton, Roger",British,1819,1819,1855,1855,1855,Salted paper print from glass negative,Image: 15.9 x 15.8 cm (6 1/4 x 6 1/4 in.) Mount: 16 x 15.9 cm (6 5/16 x 6 1/4 in.),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286393,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.665,false,true,286391,Photographs,Photograph,Major General A. H. King,,,,,,Artist,,Roger Fenton,"British, 1819–1869",,"Fenton, Roger",British,1819,1819,1855,1855,1855,Salted paper print from glass negative,Image: 17.8 x 14.7 cm (7 x 5 13/16 in.) Mount: 18.3 x 15 cm (7 3/16 x 5 7/8 in.),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286391,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.666,false,true,286567,Photographs,Photograph,"Cossack Bay, Balaklava",,,,,,Artist,,Roger Fenton,"British, 1819–1869",,"Fenton, Roger",British,1819,1819,1855,1855,1855,Salted paper print from glass negative,Image: 28.5 x 36.2 cm (11 1/4 x 14 1/4 in.) Mount: 43.2 x 59.7 cm (17 x 23 1/2 in.),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286567,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.667,false,true,286566,Photographs,Photograph,"The Genoese Castle, Balaklava",,,,,,Artist,,Roger Fenton,"British, 1819–1869",,"Fenton, Roger",British,1819,1819,1855,1855,1855,Salted paper print from glass negative,Image: 27.4 x 34.8 cm (10 13/16 x 13 11/16 in.) Mount: 43.5 x 59.8 cm (17 1/8 x 23 9/16 in.),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286566,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.668,false,true,286716,Photographs,Photograph,"Moscow, the Kremlin in the Distance",,,,,,Artist,,Roger Fenton,"British, 1819–1869",,"Fenton, Roger",British,1819,1819,1852,1852,1852,Salted paper print from paper negative,Image: 17.7 x 21.8 cm (6 15/16 x 8 9/16 in.) Mount (2nd): 43.8 x 59.1 cm (17 1/4 x 23 1/4 in.),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286716,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.669,false,true,286008,Photographs,Photograph,[Orientalist Study of a Woman],,,,,,Artist,,Roger Fenton,"British, 1819–1869",,"Fenton, Roger",British,1819,1819,1858,1858,1858,Albumen silver print from glass negative,"26.2 x 18.2 cm (10 5/16 x 7 3/16 in.), irregularly trimmed","Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286008,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.671,false,true,286035,Photographs,Photograph,Lieutenant General Sir J. L. Pennefather and Staff,,,,,,Artist,,Roger Fenton,"British, 1819–1869",,"Fenton, Roger",British,1819,1819,1855,1855,1855,Salted paper print from glass negative,Image: 14.4 x 20.8 cm (5 11/16 x 8 3/16 in.) Mount: 40.2 x 57.8 cm (15 13/16 x 22 3/4 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286035,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.672,false,true,286712,Photographs,Photograph,Aelius Caesar,,,,,,Artist,,Roger Fenton,"British, 1819–1869",,"Fenton, Roger",British,1819,1819,1854–58,1854,1858,Salted paper print from glass negative,36.8 x 29.2 cm (14 1/2 x 11 1/2 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286712,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.673,false,true,286719,Photographs,Photograph,Laughing Satyr,,,,,,Artist,,Roger Fenton,"British, 1819–1869",,"Fenton, Roger",British,1819,1819,1854–58,1854,1858,Salted paper print from glass negative,29.7 x 26.6 cm (11 11/16 x 10 1/2 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286719,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.674,false,true,286565,Photographs,Photograph,"Ely Cathedral, from the Grammar School",,,,,,Artist,,Roger Fenton,"British, 1819–1869",,"Fenton, Roger",British,1819,1819,1857,1857,1857,Albumen silver print from glass negative,Image: 35.4 x 44.3 cm (13 15/16 x 17 7/16 in.) Mount: 40.2 x 48.3 cm (15 13/16 x 19 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286565,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.1098.9,false,true,631050,Photographs,Photograph,Harbor Scene,,,,,,Artist,,Thomas Sutton,"British, 1819–1875",,"Sutton, Thomas",British,1819,1875,ca. 1855,1850,1860,Salted paper print from waxed paper negative,Image: 7 3/8 × 9 7/8 in. (18.7 × 25.1 cm) Mount: 10 1/2 in. × 13 in. (26.7 × 33 cm),"Gift of Joyce F. Menschel, 2013",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/631050,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -63.606.1.36,false,true,270852,Photographs,Photograph,Part of Tenby Town and Harbour,,,,,,Artist,,George Stokes,"British, 1819–1903",,"Stokes, George",British,1819,1903,1853,1853,1853,Albumen silver print,,"The Elisha Whittelsey Collection, The Elisha Whittelsey Fund, 1963",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/270852,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -63.606.1.22,false,true,270837,Photographs,Photograph,"Newark Abbey, near Chertsey",,,,,,Artist,,John Richardson Major,"British, 1821–1871",,"Major, John Richardson",British,1821,1871,1856,1856,1856,Albumen silver print,,"The Elisha Whittelsey Collection, The Elisha Whittelsey Fund, 1963",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/270837,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -52.524.7,false,true,269585,Photographs,Photograph,Evening,,,,,,Artist,,Philip Henry Delamotte,"British, 1821–1889",,"Delamotte, Philip Henry",British,1821,1889,1854,1854,1854,Albumen silver print from glass negative,Image: 21.3 x 16.4 cm (8 3/8 x 6 7/16 in.) Mount: 35.3 x 24.9 cm (13 7/8 x 9 13/16 in.),"David Hunter McAlpin Fund, 1952",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269585,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -52.639.6,false,true,269623,Photographs,Photograph,[The Grounds Looking Towards Penge],,,,,,Artist,,Philip Henry Delamotte,"British, 1821–1889",,"Delamotte, Philip Henry",British,1821,1889,1854,1854,1854,Albumen silver print from glass negative,,"David Hunter McAlpin Fund, 1952",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269623,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -52.639.19,false,true,269619,Photographs,Photograph,"[Storeroom with Artisans and Plaster Casts, Crystal Palace]",,,,,,Artist,,Philip Henry Delamotte,"British, 1821–1889",,"Delamotte, Philip Henry",British,1821,1889,1852,1852,1852,Albumen silver print from glass negative,Image: 22.8 × 28.1 cm (9 in. × 11 1/16 in.) Mount: 36.9 × 52.7 cm (14 1/2 × 20 3/4 in.),"David Hunter McAlpin Fund, 1952",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269619,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -52.639.34,false,true,269621,Photographs,Photograph,The Upper Gallery,,,,,,Artist,,Philip Henry Delamotte,"British, 1821–1889",,"Delamotte, Philip Henry",British,1821,1889,1854,1854,1854,Albumen silver print from glass negative,Image: 27.5 x 23.2 cm (10 13/16 x 9 1/8 in.) Mount: 52.2 x 36.8 cm (20 9/16 x 14 1/2 in.),"David Hunter McAlpin Fund, 1952",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269621,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -52.639.59,false,true,269622,Photographs,Photograph,[Carving a Sphinx],,,,,,Artist,,Philip Henry Delamotte,"British, 1821–1889",,"Delamotte, Philip Henry",British,1821,1889,1854,1854,1854,Albumen silver print from glass negative,,"David Hunter McAlpin Fund, 1952",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269622,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -63.606.1.12,false,true,270826,Photographs,Photograph,"View in Central Hall, Art Treasures Exhibition, Manchester",,,,,,Artist,,Philip Henry Delamotte,"British, 1821–1889",,"Delamotte, Philip Henry",British,1821,1889,1857,1857,1857,Albumen silver print,,"The Elisha Whittelsey Collection, The Elisha Whittelsey Fund, 1963",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/270826,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.801 (1a–d),false,true,288389,Photographs,Photograph,[Exterior View of Facade and Fountains; Exterior View of Side Pavilion; Exterior Side View of Central Transept; Exterior Side View of Central Trancept with Reclining Figure in Foreground],,,,,,Artist,Attributed to,Philip Henry Delamotte,"British, 1821–1889",,"Delamotte, Philip Henry",British,1821,1889,ca. 1859,1857,1861,Albumen silver print from glass negative,"7.9 x 8.1 cm (3 1/8 x 3 3/16 in.), each","Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288389,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.801 (2a–d),false,true,288393,Photographs,Photograph,[Two Gentlemen Seated on the Grounds of the Palace; The Palace from the Rosary; Cascades and North End of Palace; General View of Gardens and Fountains],,,,,,Artist,Attributed to,Philip Henry Delamotte,"British, 1821–1889",,"Delamotte, Philip Henry",British,1821,1889,ca. 1859,1857,1861,Albumen silver print from glass negative,"7.9 x 8.1 cm (3 1/8 x 3 3/16 in.), each","Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288393,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.801 (3a–d),false,true,288429,Photographs,Photograph,[View of Fountains; Nave Looking North; Screen of the Kings and Queens of England],,,,,,Artist,Attributed to,Philip Henry Delamotte,"British, 1821–1889",,"Delamotte, Philip Henry",British,1821,1889,ca. 1859,1857,1861,Albumen silver print from glass negative,"7.9 x 8.1 cm (3 1/8 x 3 3/16 in.), each","Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288429,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.801 (4a–d),false,true,288433,Photographs,Photograph,"[Mammoth Tree; General View of Nave, Looking South; View from North Gallery; Classical Fountain]",,,,,,Artist,Attributed to,Philip Henry Delamotte,"British, 1821–1889",,"Delamotte, Philip Henry",British,1821,1889,ca. 1859,1857,1861,Albumen silver print from glass negative,"7.9 x 8.1 cm (3 1/8 x 3 3/16 in.), each","Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288433,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.801 (5a–d),false,true,288437,Photographs,Photograph,"[View of Fountain and Byzantine Court; Bronze Fountain in Northern Nave; Monti's Fountain, and Alhambra Court; Alhambra Court from North Transept]",,,,,,Artist,Attributed to,Philip Henry Delamotte,"British, 1821–1889",,"Delamotte, Philip Henry",British,1821,1889,ca. 1859,1857,1861,Albumen silver print from glass negative,"7.9 x 8.1 cm (3 1/8 x 3 3/16 in.), each","Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288437,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.801 (6a–d),false,true,288442,Photographs,Photograph,[Alhambra Court Facade Towards the Nave; Entryway to the Alhambra Court; Side View of Alhambra Court; Alhambra Court Looking Towards the North],,,,,,Artist,Attributed to,Philip Henry Delamotte,"British, 1821–1889",,"Delamotte, Philip Henry",British,1821,1889,ca. 1859,1857,1861,Albumen silver print from glass negative,"7.9 x 8.1 cm (3 1/8 x 3 3/16 in.), each","Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288442,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.801 (7a–d),false,true,288447,Photographs,Photograph,[Alhambra and Court of Lions; View in South Transept],,,,,,Artist,Attributed to,Philip Henry Delamotte,"British, 1821–1889",,"Delamotte, Philip Henry",British,1821,1889,ca. 1859,1857,1861,Albumen silver print from glass negative,"7.9 x 8.1 cm (3 1/8 x 3 3/16 in.), each","Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288447,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.801 (8a–d),false,true,288450,Photographs,Photograph,[View From Music Court to South Transept; View in Tropical Department; View of the Egyptian Court; View of North Transept],,,,,,Artist,Attributed to,Philip Henry Delamotte,"British, 1821–1889",,"Delamotte, Philip Henry",British,1821,1889,ca. 1859,1857,1861,Albumen silver print from glass negative,"7.9 x 8.1 cm (3 1/8 x 3 3/16 in.), each","Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288450,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.801 (9a–d),false,true,288454,Photographs,Photograph,[View in Tropical Department; View of Egyptian Sphinxes],,,,,,Artist,Attributed to,Philip Henry Delamotte,"British, 1821–1889",,"Delamotte, Philip Henry",British,1821,1889,ca. 1859,1857,1861,Albumen silver print from glass negative,"7.9 x 8.1 cm (3 1/8 x 3 3/16 in.), each","Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288454,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.801 (10a–d),false,true,288458,Photographs,Photograph,[View of Sphinxes Among Foliage; View of Tropical Foliage; Egyptian Court from North-east Gallery; Colossal Egyptian Figures],,,,,,Artist,Attributed to,Philip Henry Delamotte,"British, 1821–1889",,"Delamotte, Philip Henry",British,1821,1889,ca. 1859,1857,1861,Albumen silver print from glass negative,"7.9 x 8.1 cm (3 1/8 x 3 3/16 in.), each","Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288458,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.801 (11a–d),false,true,288462,Photographs,Photograph,"[Egyptian Court, Eastern Wall of Principal Court; Egyptian Court, Principal Facade towards the Nave; Lions in the Egyptian Court; [Colonnade Adorned with Egyptian Paintings]",,,,,,Artist,Attributed to,Philip Henry Delamotte,"British, 1821–1889",,"Delamotte, Philip Henry",British,1821,1889,ca. 1859,1857,1861,Albumen silver print from glass negative,"7.9 x 8.1 cm (3 1/8 x 3 3/16 in.), each","Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288462,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.801 (12a–d),false,true,288469,Photographs,Photograph,[View Across the Egyptian Court; View through Egyptian Columns into Classical Sculpture Gallery; Side View of Egyptian Colonnade; Facade of the Hall of Columns],,,,,,Artist,Attributed to,Philip Henry Delamotte,"British, 1821–1889",,"Delamotte, Philip Henry",British,1821,1889,ca. 1859,1857,1861,Albumen silver print from glass negative,"7.9 x 8.1 cm (3 1/8 x 3 3/16 in.), each","Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288469,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.801 (13a–d),false,true,288473,Photographs,Photograph,[Interior of Egyptian Court; Classical Sculpture Gallery with Discus-Thrower; View of Egyptian Court from Classical Sculpture Gallery; Foliage in the Egyptian Court],,,,,,Artist,Attributed to,Philip Henry Delamotte,"British, 1821–1889",,"Delamotte, Philip Henry",British,1821,1889,ca. 1859,1857,1861,Albumen silver print from glass negative,"7.9 x 8.1 cm (3 1/8 x 3 3/16 in.), each","Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288473,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.801 (14a–d),false,true,288479,Photographs,Photograph,"[Elevated View of Egyptian Court; Ninevah Court; Monti's Fountain, and Nineveh Court; Assyrian Court with Workers]",,,,,,Artist,Attributed to,Philip Henry Delamotte,"British, 1821–1889",,"Delamotte, Philip Henry",British,1821,1889,ca. 1859,1857,1861,Albumen silver print from glass negative,"7.9 x 8.1 cm (3 1/8 x 3 3/16 in.), each","Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288479,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.801 (15a–d),false,true,288564,Photographs,Photograph,"[Assyrian Court, Facade Towards the Nave; Elevated View of Assyrian Court; Greek and Roman Sculpture Court; The Three Graces]",,,,,,Artist,Attributed to,Philip Henry Delamotte,"British, 1821–1889",,"Delamotte, Philip Henry",British,1821,1889,ca. 1859,1857,1861,Albumen silver print from glass negative,"7.9 x 8.1 cm (3 1/8 x 3 3/16 in.), each","Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288564,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.801 (16a–d),false,true,288570,Photographs,Photograph,"[Medieval Court; Statue of Coleoni; Equestrian Statue of Gattamelata by Donatello, from the Court of Monuments of Christian Art; Equestrian Statue of Colleone]",,,,,,Artist,Attributed to,Philip Henry Delamotte,"British, 1821–1889",,"Delamotte, Philip Henry",British,1821,1889,ca. 1859,1857,1861,Albumen silver print from glass negative,"7.9 x 8.1 cm (3 1/8 x 3 3/16 in.), each","Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288570,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.801 (17a–d),false,true,288575,Photographs,Photograph,"[Statue of a Horse; Roman Court, Portrait Busts of Emperors; Doorway of Roman Court, Flanked by Portrait Bust of Nero; Greek Court of Philosophers, Statesmen, and Generals]",,,,,,Artist,Attributed to,Philip Henry Delamotte,"British, 1821–1889",,"Delamotte, Philip Henry",British,1821,1889,ca. 1859,1857,1861,Albumen silver print from glass negative,"7.9 x 8.1 cm (3 1/8 x 3 3/16 in.), each","Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288575,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.801 (18a–d),false,true,288579,Photographs,Photograph,[View in Court of Christian Monuments; Views of Greek and Roman Sculpture Court],,,,,,Artist,Attributed to,Philip Henry Delamotte,"British, 1821–1889",,"Delamotte, Philip Henry",British,1821,1889,ca. 1859,1857,1861,Albumen silver print from glass negative,"7.9 x 8.1 cm (3 1/8 x 3 3/16 in.), each","Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288579,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.801 (19a–d),false,true,288583,Photographs,Photograph,[Views of Greek Sculpture Court including Bust of Minerva],,,,,,Artist,Attributed to,Philip Henry Delamotte,"British, 1821–1889",,"Delamotte, Philip Henry",British,1821,1889,ca. 1859,1857,1861,Albumen silver print from glass negative,"7.9 x 8.1 cm (3 1/8 x 3 3/16 in.), each","Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288583,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.801 (20a–d),false,true,288587,Photographs,Photograph,"[Views in Greek Sculpture Gallery, Including A Pieta, Apollo Belvedere, Niobe and her Family, Priest of Bacchus and Farnese Torso]",,,,,,Artist,Attributed to,Philip Henry Delamotte,"British, 1821–1889",,"Delamotte, Philip Henry",British,1821,1889,ca. 1859,1857,1861,Albumen silver print from glass negative,"7.9 x 8.1 cm (3 1/8 x 3 3/16 in.), each","Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288587,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.801 (21a–d),false,true,288591,Photographs,Photograph,[Sculptures in Roman Court; Sacrificial Altar; Greek Sculpture Gallery; Statues in Greek and Roman Sculpture Court],,,,,,Artist,Attributed to,Philip Henry Delamotte,"British, 1821–1889",,"Delamotte, Philip Henry",British,1821,1889,ca. 1859,1857,1861,Albumen silver print from glass negative,"7.9 x 8.1 cm (3 1/8 x 3 3/16 in.), each","Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288591,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.801 (22a–d),false,true,288595,Photographs,Photograph,"[Greek Court; Statue of Minerva; Ludovisi Mars; Iris, Hecate, or Lucifera]",,,,,,Artist,Attributed to,Philip Henry Delamotte,"British, 1821–1889",,"Delamotte, Philip Henry",British,1821,1889,ca. 1859,1857,1861,Albumen silver print from glass negative,"7.9 x 8.1 cm (3 1/8 x 3 3/16 in.), each","Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288595,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.801 (23a–d),false,true,288599,Photographs,Photograph,[Greek Court with Farnese Torso of a Youth; View of a Classical Fountain and Pool; Roman Gallery with Apollo Belvedere and Model of the Roman Forum; Roman Sculpture Court],,,,,,Artist,Attributed to,Philip Henry Delamotte,"British, 1821–1889",,"Delamotte, Philip Henry",British,1821,1889,ca. 1859,1857,1861,Albumen silver print from glass negative,"7.9 x 8.1 cm (3 1/8 x 3 3/16 in.), each","Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288599,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.801 (24a–d),false,true,288603,Photographs,Photograph,"[Sculpture Court Flanked by Torso of Marsyas and Sacrificial Altar; Sculpture Court with Bust of Caracalla; Greek Court with Sculptures of Mercury, Faun, and Ariadne; Roman Court with Three Sculptures of Venus]",,,,,,Artist,Attributed to,Philip Henry Delamotte,"British, 1821–1889",,"Delamotte, Philip Henry",British,1821,1889,ca. 1859,1857,1861,Albumen silver print from glass negative,"7.9 x 8.1 cm (3 1/8 x 3 3/16 in.), each","Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288603,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.801 (25a–d),false,true,288607,Photographs,Photograph,"[Greek Court with Sculpture of Discobolus; Roman Court with Sculptures of Gladiator, Mercury and Fauns; Italian Sculpture near the Great Transept]",,,,,,Artist,Attributed to,Philip Henry Delamotte,"British, 1821–1889",,"Delamotte, Philip Henry",British,1821,1889,ca. 1859,1857,1861,Albumen silver print from glass negative,"7.9 x 8.1 cm (3 1/8 x 3 3/16 in.), each","Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288607,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.801 (26a–d),false,true,288611,Photographs,Photograph,[Roman Court with Sculptures of Posidonius and Wounded Gladiator; Sculpture of Geoffrey Chaucer by Marshall; Sculpture of Shakespeare by Roubilliac],,,,,,Artist,Attributed to,Philip Henry Delamotte,"British, 1821–1889",,"Delamotte, Philip Henry",British,1821,1889,ca. 1859,1857,1861,Albumen silver print from glass negative,"7.9 x 8.1 cm (3 1/8 x 3 3/16 in.), each","Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288611,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.801 (27a–d),false,true,288615,Photographs,Photograph,[Sculptures of Abraham Duquesne; an Ancient Briton; David with his Slingshot; a Hunter],,,,,,Artist,Attributed to,Philip Henry Delamotte,"British, 1821–1889",,"Delamotte, Philip Henry",British,1821,1889,ca. 1859,1857,1861,Albumen silver print from glass negative,"7.9 x 8.1 cm (3 1/8 x 3 3/16 in.), each","Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288615,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.801 (28a–d),false,true,288619,Photographs,Photograph,"[Sculptures of a Dancing Faun, a Neapolitan Improvisatore, Homer, and Thucydides]",,,,,,Artist,Attributed to,Philip Henry Delamotte,"British, 1821–1889",,"Delamotte, Philip Henry",British,1821,1889,ca. 1859,1857,1861,Albumen silver print from glass negative,"7.9 x 8.1 cm (3 1/8 x 3 3/16 in.), each","Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288619,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.801 (29a–d),false,true,288623,Photographs,Photograph,"[Sculptures of Hector, a Dancing Girl, Corinna, and Dorothea]",,,,,,Artist,Attributed to,Philip Henry Delamotte,"British, 1821–1889",,"Delamotte, Philip Henry",British,1821,1889,ca. 1859,1857,1861,Albumen silver print from glass negative,"7.9 x 8.1 cm (3 1/8 x 3 3/16 in.), each","Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288623,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.801 (30a–d),false,true,288627,Photographs,Photograph,"[Sculptures of the Tired Hunter, a Nymph Preparing to Bathe, Godiva, and an Allegorical Figure of Night]",,,,,,Artist,Attributed to,Philip Henry Delamotte,"British, 1821–1889",,"Delamotte, Philip Henry",British,1821,1889,ca. 1859,1857,1861,Albumen silver print from glass negative,"7.9 x 8.1 cm (3 1/8 x 3 3/16 in.), each","Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288627,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.801 (31a–d),false,true,288631,Photographs,Photograph,"[Sculptures of Andromeda, the Toilet of Atalanta, Corinna, and a Naiad]",,,,,,Artist,Attributed to,Philip Henry Delamotte,"British, 1821–1889",,"Delamotte, Philip Henry",British,1821,1889,ca. 1859,1857,1861,Albumen silver print from glass negative,"7.9 x 8.1 cm (3 1/8 x 3 3/16 in.), each","Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288631,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.801 (32a–d),false,true,288635,Photographs,Photograph,"[Sculptures of Sabrina, an Allegorical Figure of Morning, a Nereide, and Eve Listening]",,,,,,Artist,Attributed to,Philip Henry Delamotte,"British, 1821–1889",,"Delamotte, Philip Henry",British,1821,1889,ca. 1859,1857,1861,Albumen silver print from glass negative,"7.9 x 8.1 cm (3 1/8 x 3 3/16 in.), each","Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288635,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.801 (33a–d),false,true,288639,Photographs,Photograph,"[Sculptures of the Medici Venus and Pomona, Venus, Esmeralda, and the Mourners]",,,,,,Artist,Attributed to,Philip Henry Delamotte,"British, 1821–1889",,"Delamotte, Philip Henry",British,1821,1889,ca. 1859,1857,1861,Albumen silver print from glass negative,"7.9 x 8.1 cm (3 1/8 x 3 3/16 in.), each","Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288639,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.801 (34a–d),false,true,288643,Photographs,Photograph,"[Sculptures of Minerva Protecting a Warrior, Una and the Lion, Children with a Pony and a Hound, and Child Play]",,,,,,Artist,Attributed to,Philip Henry Delamotte,"British, 1821–1889",,"Delamotte, Philip Henry",British,1821,1889,ca. 1859,1857,1861,Albumen silver print from glass negative,"7.9 x 8.1 cm (3 1/8 x 3 3/16 in.), each","Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288643,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.801 (35a–d),false,true,288647,Photographs,Photograph,"[Sculptures of Cain, a Hunter Defending his Family, the Massacre of the Innocents, and Allegorical Figures of the First Whisper of Love]",,,,,,Artist,Attributed to,Philip Henry Delamotte,"British, 1821–1889",,"Delamotte, Philip Henry",British,1821,1889,ca. 1859,1857,1861,Albumen silver print from glass negative,"7.9 x 8.1 cm (3 1/8 x 3 3/16 in.), each","Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288647,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.801 (36a–d),false,true,288651,Photographs,Photograph,"[Sculptures of Hylas and the Nymphs, Allegorical Figures of the Three Fates, Zephyr wooing Flora, and Michelangelo's Bacchus and Donatello's St. George]",,,,,,Artist,Attributed to,Philip Henry Delamotte,"British, 1821–1889",,"Delamotte, Philip Henry",British,1821,1889,ca. 1859,1857,1861,Albumen silver print from glass negative,"7.9 x 8.1 cm (3 1/8 x 3 3/16 in.), each","Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288651,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.801 (37a–d),false,true,288655,Photographs,Photograph,[Court of French and Italian Sculpture; Avenue in Front of Fine Arts Courts; View into Classical Sculpture Gallery; Avenue in Front of Sheffield Court],,,,,,Artist,Attributed to,Philip Henry Delamotte,"British, 1821–1889",,"Delamotte, Philip Henry",British,1821,1889,ca. 1859,1857,1861,Albumen silver print from glass negative,"7.9 x 8.1 cm (3 1/8 x 3 3/16 in.), each","Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288655,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.801 (38a–d),false,true,288682,Photographs,Photograph,[Court of Ancient Monuments; German Medieval Court; View with Statue of Albert of Bavaria; Elevated View of Central Transept],,,,,,Artist,Attributed to,Philip Henry Delamotte,"British, 1821–1889",,"Delamotte, Philip Henry",British,1821,1889,ca. 1859,1857,1861,Albumen silver print from glass negative,"7.9 x 8.1 cm (3 1/8 x 3 3/16 in.), each","Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288682,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.801 (39a–d),false,true,288686,Photographs,Photograph,[Entryway to the Renaissance Court; Doorway from an Old Palace of the Dorias; The Ghiberti Gates; View in Medieval Court],,,,,,Artist,Attributed to,Philip Henry Delamotte,"British, 1821–1889",,"Delamotte, Philip Henry",British,1821,1889,ca. 1859,1857,1861,Albumen silver print from glass negative,"7.9 x 8.1 cm (3 1/8 x 3 3/16 in.), each","Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288686,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.801 (40a–d),false,true,288690,Photographs,Photograph,[Medieval Court; The Walsingham Font; Entrance to English Medieval Court],,,,,,Artist,Attributed to,Philip Henry Delamotte,"British, 1821–1889",,"Delamotte, Philip Henry",British,1821,1889,ca. 1859,1857,1861,Albumen silver print from glass negative,"7.9 x 8.1 cm (3 1/8 x 3 3/16 in.), each","Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288690,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.801 (41a–d),false,true,288694,Photographs,Photograph,"[The Rochester Doorway; Vestibule, Garden Side of English Medieval Court; Byzantine Court Exterior and Interior]",,,,,,Artist,Attributed to,Philip Henry Delamotte,"British, 1821–1889",,"Delamotte, Philip Henry",British,1821,1889,ca. 1859,1857,1861,Albumen silver print from glass negative,"7.9 x 8.1 cm (3 1/8 x 3 3/16 in.), each","Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288694,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.801 (42a–d),false,true,288698,Photographs,Photograph,[Views of Byzantine Court with Royal Effigies],,,,,,Artist,Attributed to,Philip Henry Delamotte,"British, 1821–1889",,"Delamotte, Philip Henry",British,1821,1889,ca. 1859,1857,1861,Albumen silver print from glass negative,"7.9 x 8.1 cm (3 1/8 x 3 3/16 in.), each","Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288698,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.801 (43a–d),false,true,288702,Photographs,Photograph,"[Façade, Views, and Entrance Loggia of the Renaissance Court]",,,,,,Artist,Attributed to,Philip Henry Delamotte,"British, 1821–1889",,"Delamotte, Philip Henry",British,1821,1889,ca. 1859,1857,1861,Albumen silver print from glass negative,"7.9 x 8.1 cm (3 1/8 x 3 3/16 in.), each","Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288702,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.801 (44a–d),false,true,288723,Photographs,Photograph,[Tomb of Lorenzo de Medici; Tomb of Giuliano de Medici; Court of Christian Monuments; German Medieval Vestibule],,,,,,Artist,Attributed to,Philip Henry Delamotte,"British, 1821–1889",,"Delamotte, Philip Henry",British,1821,1889,ca. 1859,1857,1861,Albumen silver print from glass negative,"7.9 x 8.1 cm (3 1/8 x 3 3/16 in.), each","Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288723,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.801 (45a–d),false,true,288727,Photographs,Photograph,[Medieval Court; Entryway to Byzantine Court; Sheffield Court; French and Italian Mediaeval Vestibule],,,,,,Artist,Attributed to,Philip Henry Delamotte,"British, 1821–1889",,"Delamotte, Philip Henry",British,1821,1889,ca. 1859,1857,1861,Albumen silver print from glass negative,"7.9 x 8.1 cm (3 1/8 x 3 3/16 in.), each","Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288727,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.801 (46a–d),false,true,288731,Photographs,Photograph,[Entryway of Renaissance Court; Medieval Vestibule; View of the Renaissance Court; Room of Classical Reliefs and Sarcophagi],,,,,,Artist,Attributed to,Philip Henry Delamotte,"British, 1821–1889",,"Delamotte, Philip Henry",British,1821,1889,ca. 1859,1857,1861,Albumen silver print from glass negative,"7.9 x 8.1 cm (3 1/8 x 3 3/16 in.), each","Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288731,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.801 (47a–b),false,true,288735,Photographs,Photograph,[View of Large Plant with Sign Requesting Viewers Not to Touch; Telescope Gallery],,,,,,Artist,Attributed to,Philip Henry Delamotte,"British, 1821–1889",,"Delamotte, Philip Henry",British,1821,1889,ca. 1859,1857,1861,Albumen silver print from glass negative,"7.9 x 8.1 cm (3 1/8 x 3 3/16 in.), each","Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288735,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.23,false,true,283095,Photographs,Photograph,Photographic Study,,,,,,Artist,,Clementina Hawarden,"British, 1822–1865",,"Hawarden, Lady Clementina",British,1822,1865,early 1860s,1860,1864,Albumen silver print from glass negative,20.1 x 14.4 cm (7 15/16 x 5 11/16 in.),"Gilman Collection, Purchase, Harriette and Noel Levine Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/283095,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.775,false,true,286598,Photographs,Photograph,Photographic Study,,,,,,Artist,,Clementina Hawarden,"British, 1822–1865",,"Hawarden, Lady Clementina",British,1822,1865,Early 1860s,1860,1865,Albumen silver print from glass negative,Image: 3 1/4 × 2 9/16 in. (8.3 × 6.5 cm),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286598,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.620,false,true,296278,Photographs,Photograph,Frontispiece of Inquiries into Human Faculty and its Development,,,,,,Artist,,Francis Galton,"British, 1822–1911",,"Galton, Francis",British,1822,1911,1883,1883,1883,Albumen silver print from glass negative,Image: 20 x 11.8 cm (7 7/8 x 4 5/8 in.) Frame: 35.6 x 27.9 cm (14 x 11 in.),"Joyce F. Menschel Photography Library Fund, 2002, transferred from the Joyce F. Menschel Photography Library",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/296278,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.400.65,false,true,291818,Photographs,Daguerreotype,[Seated Middle-aged Man in Bow Tie and Jacket],,,,,,Artist,,John Watkins,"British, 1823–1874",,"Watkins, John",British,1823,1874,1850s,1850,1859,Daguerreotype,Image: 14 x 10 cm (5 1/2 x 3 15/16 in.) Plate: 19.1 x 15.2 cm (7 1/2 x 6 in.) Case: 1.9 x 20.2 x 16.5 cm (3/4 x 7 15/16 x 6 1/2 in.),"Bequest of Herbert Mitchell, 2008",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/291818,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.771,false,true,286549,Photographs,Photograph,Sultan,,,,,,Artist,,Nevil Story Maskelyne,"British, 1823–1911",,"Maskelyne, Nevil Story",British,1823,1911,mid-1850s,1853,1857,Salted paper print from glass negative,Image: 5 3/4 × 6 7/8 in. (14.6 × 17.5 cm); corners trimmed,"Gilman Collection, Purchase, Joseph M. Cohen Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286549,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.400.58,false,true,291811,Photographs,Daguerreotype,"[Two Young Men in Bow Ties, One Seated Holding a Book, One Standing]",,,,,,Artist,,Robert Boning,"British, 1826–1878",,"Boning, Robert",British,1826,1878,1850s,1850,1859,Daguerreotype,Image: 9.3 x 7 cm (3 11/16 x 2 3/4 in.) Plate: 10.8 x 8.3 cm (4 1/4 x 3 1/4 in.) Case: 1.1 x 11.7 x 9.5 cm (7/16 x 4 5/8 x 3 3/4 in.),"Bequest of Herbert Mitchell, 2008",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/291811,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -63.606.1.27,false,true,270842,Photographs,Photograph,"The Mouth of the East and West Lyn, Lynmouth, North Devon",,,,,,Artist,,Henry Pollock,"British, 1826–1889",,"Pollock, Henry",British,1826,1889,1856,1856,1856,Albumen silver print,,"The Elisha Whittelsey Collection, The Elisha Whittelsey Fund, 1963",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/270842,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2012.112,false,true,301892,Photographs,Photograph,"Oak Struck by Lightning, Badger, 1856.",,,,,,Artist,,Alfred Capel Cure,"British, 1826–1896",,"Capel, Cure Alfred",British,1826,1896,1856,1856,1856,Albumen silver print from paper negative,Image: 18 x 22.7 cm (7 1/16 x 8 15/16 in.) Mount: 25.2 x 30.5 cm (9 15/16 x 12 in.),"Purchase, The Buddy Taub Foundation Gift, Dennis A. Roach and Jill Roach, Directors, 2012",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/301892,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.460.2,false,true,290470,Photographs,Photograph,"Hurstmonceaux, Sussex",,,,,,Artist,,Alfred Capel Cure,"British, 1826–1896",,"Capel, Cure Alfred",British,1826,1896,1857,1857,1857,Albumen silver print from paper negative,Image: 21.2 x 27.2 cm (8 3/8 x 10 11/16 in.) Mount: 32.5 x 37.9 cm (12 13/16 x 14 15/16 in.),"Gift of Paul F. Walter, 2009",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/290470,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.460.3,false,true,290471,Photographs,Photograph,Thornton,,,,,,Artist,,Alfred Capel Cure,"British, 1826–1896",,"Capel, Cure Alfred",British,1826,1896,1860,1860,1860,Albumen silver print from paper negative,Image: 21.4 x 26.9 cm (8 7/16 x 10 9/16 in.) Mount: 32.5 x 37.9 cm (12 13/16 x 14 15/16 in.),"Gift of Paul F. Walter, 2009",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/290471,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.460.4,false,true,290472,Photographs,Photograph,Montacute House near Yeovil,,,,,,Artist,,Alfred Capel Cure,"British, 1826–1896",,"Capel, Cure Alfred",British,1826,1896,1857–60,1857,1860,Albumen silver print from paper negatives,Image: 21 x 27.1 cm (8 1/4 x 10 11/16 in.) (clippped corners) Mount: 32.5 x 37.8 cm (12 13/16 x 14 7/8 in.),"Gift of Paul F. Walter, 2009",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/290472,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.460.5,false,true,290473,Photographs,Photograph,"Interior, Tintern",,,,,,Artist,,Alfred Capel Cure,"British, 1826–1896",,"Capel, Cure Alfred",British,1826,1896,1857,1857,1857,Albumen silver print from paper negative,Image: 22 x 27.2 cm (8 11/16 x 10 11/16 in.) Mount: 32.5 x 37.9 cm (12 13/16 x 14 15/16 in.),"Gift of Paul F. Walter, 2009",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/290473,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1987.1183.39,false,true,265773,Photographs,Photograph,Peterborough,,,,,,Artist,,Alfred Capel Cure,"British, 1826–1896",,"Capel, Cure Alfred",British,1826,1896,1860,1860,1860,Albumen silver print from paper negative,21.3 x 26.9 cm. (8 3/8 x 10 9/16 in.),"Gift of Paul F. Walter, in memory of Christopher Hemphill, 1987",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/265773,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1987.1183.40,false,true,265775,Photographs,Photograph,Buildwas Abbey,,,,,,Artist,,Alfred Capel Cure,"British, 1826–1896",,"Capel, Cure Alfred",British,1826,1896,1858,1858,1858,Albumen silver print from paper negative,21.3 x 26.8 cm. (8 3/8 x 10 9/16 in.),"Gift of Paul F. Walter, in memory of Christopher Hemphill, 1987",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/265775,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1987.1183.41,false,true,265776,Photographs,Photograph,Willey,,,,,,Artist,,Alfred Capel Cure,"British, 1826–1896",,"Capel, Cure Alfred",British,1826,1896,1850s–60s,1850,1869,Albumen silver print from paper negative,17.4 x 22.0 cm. (6 7/8 x 8 11/16 in.),"Gift of Paul F. Walter, in memory of Christopher Hemphill, 1987",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/265776,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1987.1183.42,false,true,265777,Photographs,Photograph,Falaise Castle,,,,,,Artist,,Alfred Capel Cure,"British, 1826–1896",,"Capel, Cure Alfred",British,1826,1896,1856,1856,1856,Albumen silver print from paper negative,21.2 x 27.2 cm. (8 3/6 x 10 11/16 in.),"Gift of Paul F. Walter, in memory of Christopher Hemphill, 1987",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/265777,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1987.1183.43,false,true,265778,Photographs,Photograph,Malmesbury,,,,,,Artist,,Alfred Capel Cure,"British, 1826–1896",,"Capel, Cure Alfred",British,1826,1896,1850s–60s,1850,1869,Albumen silver print from paper negative,20.9 x 27.2 cm. (8 1/4 x 10 11/16 in.),"Gift of Paul F. Walter, in memory of Christopher Hemphill, 1987",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/265778,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1987.1183.44,false,true,265779,Photographs,Photograph,Wenlock Abbey,,,,,,Artist,,Alfred Capel Cure,"British, 1826–1896",,"Capel, Cure Alfred",British,1826,1896,1858,1858,1858,Albumen silver print from paper negative,21.4 x 27.2 cm. (8 7/16 x 10 11/16 in.),"Gift of Paul F. Walter, in memory of Christopher Hemphill, 1987",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/265779,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1987.1183.45,false,true,265780,Photographs,Photograph,"[photo-reproduction of Hogarth's print illustrating the Dunciad, Book I, line III]",,,,,,Artist,,Alfred Capel Cure,"British, 1826–1896",,"Capel, Cure Alfred",British,1826,1896,1850s–60s,1850,1869,Albumen silver print from paper negative,15.9 x 17.5 cm. (6 1/4 x 6 7/8 in.),"Gift of Paul F. Walter, in memory of Christopher Hemphill, 1987",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/265780,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1987.1183.46,false,true,265781,Photographs,Photograph,"St. Osyths, Essex",,,,,,Artist,,Alfred Capel Cure,"British, 1826–1896",,"Capel, Cure Alfred",British,1826,1896,1856,1856,1856,Albumen silver print from paper negative,20.8 x 26.8 cm. (8 3/16 x 10 9/16 in.),"Gift of Paul F. Walter, in memory of Christopher Hemphill, 1987",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/265781,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1987.1183.47,false,true,265782,Photographs,Photograph,Wells,,,,,,Artist,,Alfred Capel Cure,"British, 1826–1896",,"Capel, Cure Alfred",British,1826,1896,1857,1857,1857,Albumen silver print from paper negative,20.2 x 27.3 cm. (7 15/16 x 10 3/4 in.),"Gift of Paul F. Walter, in memory of Christopher Hemphill, 1987",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/265782,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1987.1183.48,false,true,265783,Photographs,Photograph,Thornton,,,,,,Artist,,Alfred Capel Cure,"British, 1826–1896",,"Capel, Cure Alfred",British,1826,1896,1860,1860,1860,Albumen silver print from paper negative,21.5 x 27.4 cm. (8 7/16 x 10 13/16 in.),"Gift of Paul F. Walter, in memory of Christopher Hemphill, 1987",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/265783,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1987.1183.49,false,true,265784,Photographs,Photograph,"West Front, Wells",,,,,,Artist,,Alfred Capel Cure,"British, 1826–1896",,"Capel, Cure Alfred",British,1826,1896,1857,1857,1857,Albumen silver print from paper negative,20.6 x 27.0 cm. (8 1/8 x 10 5/8 in.),"Gift of Paul F. Walter, in memory of Christopher Hemphill, 1987",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/265784,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1987.1183.50,false,true,265786,Photographs,Photograph,"Crowland Abbey, the West Front Under Repair",,,,,,Artist,,Alfred Capel Cure,"British, 1826–1896",,"Capel, Cure Alfred",British,1826,1896,1860,1860,1860,Albumen silver print from paper negative,21.8 x 27.0 cm. (8 9/16 x 10 5/8 in.),"Gift of Paul F. Walter, in memory of Christopher Hemphill, 1987",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/265786,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1987.1183.51,false,true,265787,Photographs,Photograph,Byland Abbey,,,,,,Artist,,Alfred Capel Cure,"British, 1826–1896",,"Capel, Cure Alfred",British,1826,1896,1856,1856,1856,Albumen silver print from paper negative,21.1 x 27.0 cm. (8 5/16 x 10 5/8 in.),"Gift of Paul F. Walter, in memory of Christopher Hemphill, 1987",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/265787,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1987.1183.52,false,true,265788,Photographs,Photograph,Haughmond Abbey,,,,,,Artist,,Alfred Capel Cure,"British, 1826–1896",,"Capel, Cure Alfred",British,1826,1896,1858,1858,1858,Albumen silver print from paper negative,21.9 x 27.4 cm. (8 5/8 x 10 13/16 in.),"Gift of Paul F. Walter, in memory of Christopher Hemphill, 1987",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/265788,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1987.1183.53,false,true,265789,Photographs,Photograph,Thornton College - Lincolnshire,,,,,,Artist,,Alfred Capel Cure,"British, 1826–1896",,"Capel, Cure Alfred",British,1826,1896,1860,1860,1860,Albumen silver print from paper negative,21.6 x 27.1 cm. (8 1/2 x 10 11/16 in.),"Gift of Paul F. Walter, in memory of Christopher Hemphill, 1987",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/265789,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1987.1183.54,false,true,265790,Photographs,Photograph,West Front - Peterboro,,,,,,Artist,,Alfred Capel Cure,"British, 1826–1896",,"Capel, Cure Alfred",British,1826,1896,1856,1856,1856,Albumen silver print from paper negative,21.0 x 27.1 cm. (8 1/4 x 10 11/16 in.),"Gift of Paul F. Walter, in memory of Christopher Hemphill, 1987",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/265790,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1987.1183.55,false,true,265791,Photographs,Photograph,"St. Pierre, Caen",,,,,,Artist,,Alfred Capel Cure,"British, 1826–1896",,"Capel, Cure Alfred",British,1826,1896,1856,1856,1856,Albumen silver print from paper negative,19.8 x 25.2 cm. (7 13/16 x 9 15/16 in.),"Gift of Paul F. Walter, in memory of Christopher Hemphill, 1987",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/265791,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1987.1183.56,false,true,265792,Photographs,Photograph,"St. Peter's in the East, Oxford",,,,,,Artist,,Alfred Capel Cure,"British, 1826–1896",,"Capel, Cure Alfred",British,1826,1896,1859,1859,1859,Albumen silver print from paper negative,21.7 x 27.5 cm. (8 9/16 x 10 13/16 in.),"Gift of Paul F. Walter, in memory of Christopher Hemphill, 1987",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/265792,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1987.1183.57,false,true,265793,Photographs,Photograph,"Keep of Tattershall Castle, Lincolnshire - 2nd Fortescue",,,,,,Artist,,Alfred Capel Cure,"British, 1826–1896",,"Capel, Cure Alfred",British,1826,1896,1860,1860,1860,Albumen silver print from paper negative,21.6 x 26.8 cm. (8 1/2 x 10 9/16 in.),"Gift of Paul F. Walter, in memory of Christopher Hemphill, 1987",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/265793,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1987.1183.58,false,true,265794,Photographs,Photograph,"""Peter""",,,,,,Artist,,Alfred Capel Cure,"British, 1826–1896",,"Capel, Cure Alfred",British,1826,1896,1860,1860,1860,Albumen silver print from glass negative,10.2 x 14.2 cm. (4 x 5 9/16 in.),"Gift of Paul F. Walter, in memory of Christopher Hemphill, 1987",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/265794,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1987.1183.59,false,true,265795,Photographs,Photograph,Reverend L. C. Cure and His Pony,,,,,,Artist,,Alfred Capel Cure,"British, 1826–1896",,"Capel, Cure Alfred",British,1826,1896,1859,1859,1859,Albumen silver print from glass negative,11.3 x 14.7 cm. (4 7/16 x 5 13/16 in.),"Gift of Paul F. Walter, in memory of Christopher Hemphill, 1987",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/265795,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1987.1183.60,false,true,265797,Photographs,Photograph,Blake House,,,,,,Artist,,Alfred Capel Cure,"British, 1826–1896",,"Capel, Cure Alfred",British,1826,1896,1860,1860,1860,Albumen silver print from glass negative,"17.2 x 23.9 cm. (6 3/4 x 9 7/16 in.), rounded corners at top","Gift of Paul F. Walter, in memory of Christopher Hemphill, 1987",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/265797,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1987.1183.61,false,true,265798,Photographs,Photograph,[Vignetted portrait of two children],,,,,,Artist,,Alfred Capel Cure,"British, 1826–1896",,"Capel, Cure Alfred",British,1826,1896,1850s–60s,1850,1869,Albumen silver print from paper negative,"8.9 x 7.9 cm. (3 1/2 x 3 1/8 in.), oval","Gift of Paul F. Walter, in memory of Christopher Hemphill, 1987",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/265798,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1987.1183.62,false,true,265799,Photographs,Photograph,"[Vignetted portrait, woman holding a baby]",,,,,,Artist,,Alfred Capel Cure,"British, 1826–1896",,"Capel, Cure Alfred",British,1826,1896,1850s–60s,1850,1869,Albumen silver print from glass negative,8.8 x 8.0 cm. (3 7/16 x 3 1/8 in.),"Gift of Paul F. Walter, in memory of Christopher Hemphill, 1987",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/265799,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1987.1183.63,false,true,265800,Photographs,Photograph,Harry,,,,,,Artist,,Alfred Capel Cure,"British, 1826–1896",,"Capel, Cure Alfred",British,1826,1896,1858,1858,1858,Albumen silver print from glass negative,13.8 x 9.9 cm. (5 7/16 x 3 7/8 in.),"Gift of Paul F. Walter, in memory of Christopher Hemphill, 1987",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/265800,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1987.1183.64,false,true,265801,Photographs,Photograph,Nephews,,,,,,Artist,,Alfred Capel Cure,"British, 1826–1896",,"Capel, Cure Alfred",British,1826,1896,1860,1860,1860,Albumen silver print from glass negative,12.0 x 6.1 cm. (4 3/4 x 2 3/8 in.),"Gift of Paul F. Walter, in memory of Christopher Hemphill, 1987",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/265801,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1987.1183.65,false,true,265802,Photographs,Photograph,Harry,,,,,,Artist,,Alfred Capel Cure,"British, 1826–1896",,"Capel, Cure Alfred",British,1826,1896,1860,1860,1860,Albumen silver print from paper negative,13.9 x 11.0 cm. (5 1/2 x 4 5/16 in.),"Gift of Paul F. Walter, in memory of Christopher Hemphill, 1987",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/265802,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1987.1183.66,false,true,265803,Photographs,Photograph,Gateway - Bury St. Edmond's,,,,,,Artist,,Alfred Capel Cure,"British, 1826–1896",,"Capel, Cure Alfred",British,1826,1896,1858,1858,1858,Albumen silver print from paper negative,"21.8 x 26.8 cm. (8 9/16 x 10 9/16 in.), top corners trimmed","Gift of Paul F. Walter, in memory of Christopher Hemphill, 1987",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/265803,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1987.1183.67,false,true,265804,Photographs,Photograph,St. Osyth's Priory,,,,,,Artist,,Alfred Capel Cure,"British, 1826–1896",,"Capel, Cure Alfred",British,1826,1896,1856,1856,1856,Albumen silver print from paper negative,21.1 x 27.2 cm. (8 5/16 x 10 11/16 in.),"Gift of Paul F. Walter, in memory of Christopher Hemphill, 1987",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/265804,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1987.1183.68,false,true,265805,Photographs,Photograph,"Village of Andelys - Chateau Gaillard, Coeur de Lion",,,,,,Artist,,Alfred Capel Cure,"British, 1826–1896",,"Capel, Cure Alfred",British,1826,1896,1856,1856,1856,Albumen silver print from paper negative,20.6 x 27.2 cm. (8 1/8 x 10 11/16 in.),"Gift of Paul F. Walter, in memory of Christopher Hemphill, 1987",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/265805,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1987.1183.69,false,true,265806,Photographs,Photograph,Wenlock,,,,,,Artist,,Alfred Capel Cure,"British, 1826–1896",,"Capel, Cure Alfred",British,1826,1896,1850s–60s,1850,1869,Albumen silver print from paper negative,21.6 x 26.9 cm. (8 1/2 x 10 9/16 in.),"Gift of Paul F. Walter, in memory of Christopher Hemphill, 1987",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/265806,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1987.1183.70,false,true,265808,Photographs,Photograph,Nether Hall,,,,,,Artist,,Alfred Capel Cure,"British, 1826–1896",,"Capel, Cure Alfred",British,1826,1896,1857,1857,1857,Albumen silver print from paper negative,21.5 x 27.4 cm. (8 7/16 x 10 13/16 in.),"Gift of Paul F. Walter, in memory of Christopher Hemphill, 1987",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/265808,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1987.1183.71,false,true,265809,Photographs,Photograph,Blake House,,,,,,Artist,,Alfred Capel Cure,"British, 1826–1896",,"Capel, Cure Alfred",British,1826,1896,1860,1860,1860,Albumen silver print from paper negative,19.8 x 23.9 cm. (7 13/16 x 9 7/16 in.),"Gift of Paul F. Walter, in memory of Christopher Hemphill, 1987",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/265809,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1987.1183.72,false,true,265810,Photographs,Photograph,"Cloisters, Magdalen",,,,,,Artist,,Alfred Capel Cure,"British, 1826–1896",,"Capel, Cure Alfred",British,1826,1896,1859,1859,1859,Albumen silver print from paper negative,21.1 x 27.1 cm. (8 5/16 x 10 11/16 in.),"Gift of Paul F. Walter, in memory of Christopher Hemphill, 1987",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/265810,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1987.1183.73,false,true,265811,Photographs,Photograph,"Green Court, Raglan",,,,,,Artist,,Alfred Capel Cure,"British, 1826–1896",,"Capel, Cure Alfred",British,1826,1896,1858,1858,1858,Albumen silver print from paper negative,21.0 x 27.0 cm. (8 1/4 x 10 5/8 in.),"Gift of Paul F. Walter, in memory of Christopher Hemphill, 1987",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/265811,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1987.1183.74,false,true,265812,Photographs,Photograph,"Town Hall, Cirencester",,,,,,Artist,,Alfred Capel Cure,"British, 1826–1896",,"Capel, Cure Alfred",British,1826,1896,1858,1858,1858,Albumen silver print from paper negative,21.8 x 27.1 cm. (8 9/16 x 10 11/16 in.),"Gift of Paul F. Walter, in memory of Christopher Hemphill, 1987",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/265812,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1987.1183.75,false,true,265813,Photographs,Photograph,"Conventual Buildings, Bury",,,,,,Artist,,Alfred Capel Cure,"British, 1826–1896",,"Capel, Cure Alfred",British,1826,1896,1858,1858,1858,Albumen silver print from paper negative,20.2 x 26.9 cm. (7 15/16 x 10 9/16 in.),"Gift of Paul F. Walter, in memory of Christopher Hemphill, 1987",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/265813,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1987.1183.76,false,true,265814,Photographs,Photograph,Layer Marney,,,,,,Artist,,Alfred Capel Cure,"British, 1826–1896",,"Capel, Cure Alfred",British,1826,1896,1857,1857,1857,Albumen silver print from paper negative,21.2 x 27.2 cm. (8 3/6 x 10 11/16 in.),"Gift of Paul F. Walter, in memory of Christopher Hemphill, 1987",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/265814,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1987.1183.77,false,true,265815,Photographs,Photograph,"Talbot's Tower, Falaise Castle",,,,,,Artist,,Alfred Capel Cure,"British, 1826–1896",,"Capel, Cure Alfred",British,1826,1896,1856,1856,1856,Albumen silver print from paper negative,21.3 x 27.8 cm. (8 3/8 x 10 15/16 in.),"Gift of Paul F. Walter, in memory of Christopher Hemphill, 1987",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/265815,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1987.1183.78,false,true,265816,Photographs,Photograph,"American Creeper, Blake House",,,,,,Artist,,Alfred Capel Cure,"British, 1826–1896",,"Capel, Cure Alfred",British,1826,1896,1860,1860,1860,Albumen silver print from paper negative,15.7 x 20.6 cm. (6 3/16 x 8 1/8 in.),"Gift of Paul F. Walter, in memory of Christopher Hemphill, 1987",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/265816,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1987.1183.79,false,true,265817,Photographs,Photograph,Brevet Lieutenant Colonel Cure,,,,,,Artist,,Alfred Capel Cure,"British, 1826–1896",,"Capel, Cure Alfred",British,1826,1896,1856,1856,1856,Albumen silver print from glass negative,21.0 x 13.6 cm. (8 1/4 x 5 3/6 in.),"Gift of Paul F. Walter, in memory of Christopher Hemphill, 1987",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/265817,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -63.606.1.15,false,true,270829,Photographs,Photograph,"The Meeting of the Waters, Killarney",,,,,,Artist,,Lord Otho Fitzgerald,"British, 1827–1882",,"Fitzgerald, Otho Lord",British,1827,1882,1854,1854,1854,Albumen silver print,,"The Elisha Whittelsey Collection, The Elisha Whittelsey Fund, 1963",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/270829,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -63.606.1.11,false,true,270825,Photographs,Photograph,"Wood-scene, Norton, Cheshire",,,,,,Artist,,Thomas Davies,"British, 1830–1880",,"Davies, Thomas",British,1830,1880,1856,1856,1856,Albumen silver print,,"The Elisha Whittelsey Collection, The Elisha Whittelsey Fund, 1963",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/270825,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -63.606.1.32,false,true,270848,Photographs,Photograph,Sparrowe's House,,,,,,Artist,,Robert Charles Ransome,"British, 1830–1886",,"Ransome, Robert Charles",British,1830,1886,1853,1853,1853,Salted paper print,,"The Elisha Whittelsey Collection, The Elisha Whittelsey Fund, 1963",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/270848,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -63.606.1.35,false,true,270851,Photographs,Photograph,The Time of Promise,,,,,,Artist,,George Shadbolt,"British, 1830–1901",,"Shadbolt, George",British,1830,1901,1857,1857,1857,Albumen silver print,,"The Elisha Whittelsey Collection, The Elisha Whittelsey Fund, 1963",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/270851,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1988.1033,false,true,265905,Photographs,Photograph,In the Valley of the Mole,,,,,,Artist,,Robert Howlett,"British, 1831–1858",,"Howlett, Robert",British,1831,1831,1855,1855,1855,Albumen silver print from glass negative,Image: 20.4 x 25.5 cm (8 1/16 x 10 1/16 in.) Mount: 30.5 x 43.7 cm (12 x 17 3/16 in.),"Purchase, Harrison D. Horblit and Harriette and Noel Levine Gifts and David Hunter McAlpin Fund, 1988",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/265905,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.11,false,true,283083,Photographs,Photograph,[Isambard Kingdom Brunel Standing Before the Launching Chains of the Great Eastern],,,,,,Artist,,Robert Howlett,"British, 1831–1858",,"Howlett, Robert",British,1831,1831,"1857, printed 1863–64",1857,1857,Albumen silver print from glass negative,Image: 27.9 x 21.5 cm (11 x 8 7/16 in.) Mount: 36.4 x 26.6 cm (14 5/16 x 10 1/2 in.),"Gilman Collection, Purchase, Harriette and Noel Levine Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/283083,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.839,false,true,285910,Photographs,Photograph,"[Men at Work Beside the Launching Chains of the ""Great Eastern""]",,,,,,Artist,,Robert Howlett,"British, 1831–1858",,"Howlett, Robert",British,1831,1831,"November 18, 1857",1857,1857,Albumen silver print from glass negative,Dome topped: 11 1/8 x 13 15/16 Mount: 16 15/16 × 21 5/16 in. (43 × 54.2 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285910,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1981.1229.51.2,false,true,263193,Photographs,Photograph,Opium smoker,,,,,,Artist,,William Thomas Saunders,"British, 1832–1892",,"Saunders, William Thomas",British,1832,1892,1867,1867,1867,Albumen silver print from glass negative,,"Robert O. Dougan Collection, Gift of Warner Communications Inc., 1981",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/263193,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1981.1229.51.3,false,true,263194,Photographs,Photograph,Pekin-car,,,,,,Artist,,William Thomas Saunders,"British, 1832–1892",,"Saunders, William Thomas",British,1832,1892,1867,1867,1867,Albumen silver print from glass negative,,"Robert O. Dougan Collection, Gift of Warner Communications Inc., 1981",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/263194,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1993.331,false,true,266921,Photographs,Photograph,The Manirung Pass,,,,,,Artist,,Samuel Bourne,"British, 1834–1912",,"Bourne, Samuel",British,1834,1912,1866,1866,1866,Albumen silver print from glass negative,Image: 23.5 x 29.7 cm. (9 1/4 x 11 11/16 in.),"Purchase, Cynthia Hazen Polsky Gift, 1993",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/266921,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1993.189.2.1,false,true,266893,Photographs,Photograph,[Mussucks for Crossing the Beas River Below Bajoura],,,,,,Artist,,Samuel Bourne,"British, 1834–1912",,"Bourne, Samuel",British,1834,1912,1866,1866,1866,Albumen silver print from glass negative,23.7 x 29.8 cm. (9 5/16 x 11 3/4 in.),"Purchase, Cynthia Hazen Polsky Gift, 1993",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/266893,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.499 (99),false,true,286879,Photographs,Photograph,The Manirung Pass,,,,,,Artist,,Samuel Bourne,"British, 1834–1912",,"Bourne, Samuel",British,1834,1912,1860s,1860,1869,Albumen silver print from glass negative,23.7 x 29.6 cm (9 5/16 x 11 5/8 in.),"Gilman Collection, Purchase, Cynthia Hazen Polsky Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286879,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -63.606.1.29,false,true,270844,Photographs,Photograph,"On the Road to Watersmeet, near Lynton, North Devon",,,,,,Artist,,Arthur Julius Pollock,"British, 1835–1890",,"Pollock, Arthur Julius",British,1835,1890,1856,1856,1856,Albumen silver print,,"The Elisha Whittelsey Collection, The Elisha Whittelsey Fund, 1963",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/270844,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.491.1 (9a),false,true,287608,Photographs,Photograph,"[The Viscountess Canning, Barrackpore]",,,,,,Artist,,John Constantine Stanley,"British, 1837–1878",,"Stanley, John Constantine",British,1837,1878,1858,1858,1858,Albumen silver print,Image: 13.3 x 9.6 cm (5 1/4 x 3 3/4 in.) Mount: 33 x 26 cm (13 x 10 1/4 in.),"Gilman Collection, Purchase, Cynthia Hazen Polsky Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287608,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.491.1 (9b),false,true,287681,Photographs,Photograph,"[The Viscountess Canning, Barrackpore]",,,,,,Artist,,John Constantine Stanley,"British, 1837–1878",,"Stanley, John Constantine",British,1837,1878,1858,1858,1858,Albumen silver print,Image: 15 x 12.5 cm (5 7/8 x 4 15/16 in.) Mount: 33 x 26 cm (13 x 10 1/4 in.),"Gilman Collection, Purchase, Cynthia Hazen Polsky Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287681,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.491.1 (14a),false,true,287613,Photographs,Photograph,"[N.E. Gate of Government House, Calcutta]",,,,,,Artist,,John Constantine Stanley,"British, 1837–1878",,"Stanley, John Constantine",British,1837,1878,1858–61,1858,1861,Albumen silver print,Image: 14.1 x 20.7 cm (5 9/16 x 8 1/8 in.) Mount: 33 x 26.4 cm (13 x 10 3/8 in.),"Gilman Collection, Purchase, Cynthia Hazen Polsky Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287613,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.491.1 (14b),false,true,287682,Photographs,Photograph,"[Great Sikh Gun taken at Ferozshah on the Night of December 21, 1845, Government House, Calcutta]",,,,,,Artist,,John Constantine Stanley,"British, 1837–1878",,"Stanley, John Constantine",British,1837,1878,1858–61,1858,1861,Albumen silver print,Image: 15.4 x 20.8 cm (6 1/16 x 8 3/16 in.) Mount: 33 x 26.4 cm (13 x 10 3/8 in.),"Gilman Collection, Purchase, Cynthia Hazen Polsky Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287682,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.491.1 (16a),false,true,287616,Photographs,Photograph,"[View of Chowringhee from Government House, Calcutta]",,,,,,Artist,,John Constantine Stanley,"British, 1837–1878",,"Stanley, John Constantine",British,1837,1878,1858–61,1858,1861,Albumen silver print,Image: 11.1 x 15.8 cm (4 3/8 x 6 1/4 in.) Mount: 33 x 26 cm (13 x 10 1/4 in.),"Gilman Collection, Purchase, Cynthia Hazen Polsky Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287616,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.491.1 (16b),false,true,287684,Photographs,Photograph,[The Maidan from Government House During the Rains],,,,,,Artist,,John Constantine Stanley,"British, 1837–1878",,"Stanley, John Constantine",British,1837,1878,1858–61,1858,1861,Albumen silver print,Image: 13.8 x 20.9 cm (5 7/16 x 8 1/4 in.) Mount: 33 x 26 cm (13 x 10 1/4 in.),"Gilman Collection, Purchase, Cynthia Hazen Polsky Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287684,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.491.1 (17a),false,true,287617,Photographs,Photograph,"[Kitchen and Stables of Government House, Calcutta]",,,,,,Artist,,John Constantine Stanley,"British, 1837–1878",,"Stanley, John Constantine",British,1837,1878,1858–61,1858,1861,Albumen silver print,Image: 12.2 x 18.8 cm (4 13/16 x 7 3/8 in.) Mount: 33 x 26.2 cm (13 x 10 5/16 in.),"Gilman Collection, Purchase, Cynthia Hazen Polsky Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287617,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.491.1 (17b),false,true,287685,Photographs,Photograph,"[Spence's Hotel & St. John's Cathedral, Calcutta]",,,,,,Artist,,John Constantine Stanley,"British, 1837–1878",,"Stanley, John Constantine",British,1837,1878,1858–61,1858,1861,Albumen silver print,Image: 15.5 x 20.1 cm (6 1/8 x 7 15/16 in.) Mount: 33 x 26.2 cm (13 x 10 5/16 in.),"Gilman Collection, Purchase, Cynthia Hazen Polsky Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287685,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.491.1 (37a),false,true,287637,Photographs,Photograph,"[Gunpowder Agents Bungalow, Ishapoor.]",,,,,,Artist,,John Constantine Stanley,"British, 1837–1878",,"Stanley, John Constantine",British,1837,1878,1858–61,1858,1861,Albumen silver print,Image: 12 x 17.2 cm (4 3/4 x 6 3/4 in.) Mount: 33.1 x 26 cm (13 1/16 x 10 1/4 in.),"Gilman Collection, Purchase, Cynthia Hazen Polsky Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287637,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.491.1 (37b),false,true,287693,Photographs,Photograph,"[Gunpowder Agents Bungalow, Ishapoor]",,,,,,Artist,,John Constantine Stanley,"British, 1837–1878",,"Stanley, John Constantine",British,1837,1878,1858–61,1858,1861,Albumen silver print,Image: 11 x 18 cm (4 5/16 x 7 1/16 in.) Mount: 33.1 x 26 cm (13 1/16 x 10 1/4 in.),"Gilman Collection, Purchase, Cynthia Hazen Polsky Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287693,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.491.1 (40a),false,true,287640,Photographs,Photograph,"[Government House, Allahabad]",,,,,,Artist,,John Constantine Stanley,"British, 1837–1878",,"Stanley, John Constantine",British,1837,1878,1858,1858,1858,Albumen silver print,Image: 7.1 x 7.6 cm (2 13/16 x 3 in.) Mount: 33 x 26 cm (13 x 10 1/4 in.),"Gilman Collection, Purchase, Cynthia Hazen Polsky Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287640,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.491.1 (40b),false,true,287696,Photographs,Photograph,"[Gardens, Government House, Allahabad]",,,,,,Artist,,John Constantine Stanley,"British, 1837–1878",,"Stanley, John Constantine",British,1837,1878,1858,1858,1858,Albumen silver print,Image: 7 x 7.8 cm (2 3/4 x 3 1/16 in.) Mount: 33 x 26 cm (13 x 10 1/4 in.),"Gilman Collection, Purchase, Cynthia Hazen Polsky Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287696,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.491.1 (40c),false,true,287697,Photographs,Photograph,"[Countess Canning with Guest, Government House, Allahabad]",,,,,,Artist,,John Constantine Stanley,"British, 1837–1878",,"Stanley, John Constantine",British,1837,1878,1858,1858,1858,Albumen silver print,Image: 7.6 x 6.9 cm (3 x 2 11/16 in.) Mount: 33 x 26 cm (13 x 10 1/4 in.),"Gilman Collection, Purchase, Cynthia Hazen Polsky Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287697,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.491.1 (40d),false,true,287698,Photographs,Photograph,"[Countess Canning with Guests, Government House, Allahabad]",,,,,,Artist,,John Constantine Stanley,"British, 1837–1878",,"Stanley, John Constantine",British,1837,1878,1858,1858,1858,Albumen silver print,Image: 7.1 x 7.5 cm (2 13/16 x 2 15/16 in.) Mount: 33 x 26 cm (13 x 10 1/4 in.),"Gilman Collection, Purchase, Cynthia Hazen Polsky Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287698,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.491.1 (40e),false,true,287699,Photographs,Photograph,"[Man and Horse, Government House, Allahabad]",,,,,,Artist,,John Constantine Stanley,"British, 1837–1878",,"Stanley, John Constantine",British,1837,1878,1858,1858,1858,Albumen silver print,Image: 6.6 x 6.9 cm (2 5/8 x 2 11/16 in.) Mount: 33 x 26 cm (13 x 10 1/4 in.),"Gilman Collection, Purchase, Cynthia Hazen Polsky Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287699,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.491.1 (40f),false,true,287700,Photographs,Photograph,"[Countess Canning with Guests, Government House, Allahabad]",,,,,,Artist,,John Constantine Stanley,"British, 1837–1878",,"Stanley, John Constantine",British,1837,1878,1858,1858,1858,Albumen silver print,Image: 7 x 7.5 cm (2 3/4 x 2 15/16 in.) Mount: 33 x 26 cm (13 x 10 1/4 in.),"Gilman Collection, Purchase, Cynthia Hazen Polsky Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287700,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.1017,false,true,266091,Photographs,Photograph,[Indian Barber],,,,,,Artist,,Willoughby Wallace Hooper,"British, 1837–1912",,"Hooper, Willoughby Wallace",British,1837,1912,1860s,1860,1869,Albumen silver print from glass negative,14.9 x 19.4 cm. (5 7/8 x 7 5/8 in.),"Purchase, Cynthia Hazen Polsky Gift, 1989",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/266091,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1985.1123.18,false,true,264378,Photographs,Photograph,"Graia, On the Red Sea, Near Ezion-Geber, Port of King Solomon",,,,,,Artist,,Frank Mason Good,"British, 1839–1928",,"Good, Frank Mason",British,1839,1928,1870s,1870,1879,Albumen silver print,,"Gift of Weston J. Naef, 1985",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/264378,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -41.21.10,false,true,268692,Photographs,Photograph,Julia Margaret Cameron,,,,,,Artist,,Henry Herschel Hay Cameron,"British, 1852–1911",,"Cameron, Henry Herschel Hay",British,1852,1911,1870,1870,1870,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1941",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268692,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -49.74.237,false,true,269472,Photographs,Photograph,"Deir el Bahari, Egypt",,,,,,Artist,,Lord Carnarvon,"British, 1866–1923",,"Carnarvon, Lord",British,1866,1923,1914,1914,1914,Gum bichromate over platinum print,,"Howard Carter Bequest, 1939",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269472,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -49.74.238,false,true,269473,Photographs,Photograph,"Dier el Bahari, Egypt",,,,,,Artist,,Lord Carnarvon,"British, 1866–1923",,"Carnarvon, Lord",British,1866,1923,1914,1914,1914,Gum bichromate over platinum print,,"Howard Carter Bequest, 1939",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269473,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.970,false,true,283250,Photographs,Photograph,[Cavorting by the Pool at Garsington],,,,,,Artist,,Lady Ottoline Violet Anne Cavendish-Bentinck Morrell,"British, 1873–1938",,"Morrell, Lady Ottoline",British,1873,1938,ca. 1916,1915,1917,Gelatin silver prints,8.8 x 6.2 cm (3 7/16 x 2 7/16 in.) and 8.8 x 6.3 cm (3 7/16 x 2 7/16 in.),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/283250,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.971,false,true,291058,Photographs,Photograph,[Cavorting by the Pool at Garsington],,,,,,Artist,,Lady Ottoline Violet Anne Cavendish-Bentinck Morrell,"British, 1873–1938",,"Morrell, Lady Ottoline",British,1873,1938,ca. 1916,1915,1917,Gelatin silver print,8.8 x 6.3 cm (3 7/16 x 2 7/16 in.),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/291058,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -46.1.106,false,true,268879,Photographs,Photograph,[Tree in Yard],,,,,,Artist,Attributed to,Samuel Buckle,"British, 1809?–1860",,"Buckle, Samuel",British,1809,1860,1850s,1850,1859,Salted paper print from paper negative,,"David Hunter McAlpin Fund, 1946",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268879,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.491.1 (2),false,true,287592,Photographs,Photograph,"[The Countess Canning, Calcutta]",,,,,,Artist,,Josiah Rowe,"British, ca. 1809–1874",,"Rowe, Josiah",British,1809,1874,1861,1861,1861,Albumen silver print,Image: 24.6 x 22.9 cm (9 11/16 x 9 in.) Mount: 33 x 26.2 cm (13 x 10 5/16 in.),"Gilman Collection, Purchase, Cynthia Hazen Polsky Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287592,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.491.1 (4),false,true,287594,Photographs,Photograph,"[The Countess Canning, Calcutta]",,,,,,Artist,,Josiah Rowe,"British, ca. 1809–1874",,"Rowe, Josiah",British,1809,1874,1861,1861,1861,Albumen silver print from glass negative,Image: 23.6 x 22.4 cm (9 5/16 x 8 13/16 in.) Mount: 33 x 26.2 cm (13 x 10 5/16 in.),"Gilman Collection, Purchase, Cynthia Hazen Polsky Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287594,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.499,false,true,685839,Photographs,Carte-de-visite,[Benjamin William Leader],,,,,,Artist,,John and Charles Watkins,"British, active 1867–71",,John and Charles Watkins,British,1840,1875,1867–1870,1867,1870,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/685839,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.1126,false,true,266157,Photographs,Photograph,"[House with Woman on Balcony, Man Standing Below]",,,,,,Artist,,Richard Dykes Alexander,"British, Ipswich 1788–1865",,"Alexander, Richard Dykes",British,1788,1865,ca. 1857,1855,1859,Salted paper print from glass negative,15.2 x 20.6 cm. (6 x 8 1/8 in.),"Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 1989",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/266157,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1971.590.2,false,true,259897,Photographs,Photograph,[Rocky Inlet with Seascape],,,,,,Artist,,Francis Bedford,"British, London 1816–1894 London",,"Bedford, Francis",British,1816,1894,1870s,1870,1879,Albumen silver print,,"The Elisha Whittelsey Collection, The Elisha Whittelsey Fund, 1971",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/259897,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1971.590.3,false,true,259898,Photographs,Photograph,[View Through Rocks' Of Tower On Hill],,,,,,Artist,,Francis Bedford,"British, London 1816–1894 London",,"Bedford, Francis",British,1816,1894,1870s,1870,1879,Albumen silver print,,"The Elisha Whittelsey Collection, The Elisha Whittelsey Fund, 1971",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/259898,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1971.590.4,false,true,259899,Photographs,Photograph,"Monuments and Chancel Steps, Tenby Church",,,,,,Artist,,Francis Bedford,"British, London 1816–1894 London",,"Bedford, Francis",British,1816,1894,1870s,1870,1879,Albumen silver print,,"The Elisha Whittelsey Collection, The Elisha Whittelsey Fund, 1971",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/259899,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1971.590.5,false,true,259900,Photographs,Photograph,Gosceau Rock and the Croft,,,,,,Artist,,Francis Bedford,"British, London 1816–1894 London",,"Bedford, Francis",British,1816,1894,1870s,1870,1879,Albumen silver print,,"The Elisha Whittelsey Collection, The Elisha Whittelsey Fund, 1971",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/259900,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.567.1,false,true,260056,Photographs,Photograph,Colwyn Bay. Rustic Bridge in the Wood,,,,,,Artist,,Francis Bedford,"British, London 1816–1894 London",,"Bedford, Francis",British,1816,1894,1870s,1870,1879,Albumen silver print from glass negative,,"The Elisha Whittelsey Collection, The Elisha Whittelsey Fund, 1972",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260056,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.567.2,false,true,260067,Photographs,Photograph,"Chargford, Holy S. Mill",,,,,,Artist,,Francis Bedford,"British, London 1816–1894 London",,"Bedford, Francis",British,1816,1894,1870s,1870,1879,Albumen silver print from glass negative,,"The Elisha Whittelsey Collection, The Elisha Whittelsey Fund, 1972",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260067,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.567.3,false,true,260069,Photographs,Photograph,Morte Point from Barraoane Bay,,,,,,Artist,,Francis Bedford,"British, London 1816–1894 London",,"Bedford, Francis",British,1816,1894,1870s,1870,1879,Albumen silver print from glass negative,,"The Elisha Whittelsey Collection, The Elisha Whittelsey Fund, 1972",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260069,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.567.4,false,true,260070,Photographs,Photograph,Pensarn Beach,,,,,,Artist,,Francis Bedford,"British, London 1816–1894 London",,"Bedford, Francis",British,1816,1894,1870s,1870,1879,Albumen silver print from glass negative,,"The Elisha Whittelsey Collection, The Elisha Whittelsey Fund, 1972",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260070,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.567.5,false,true,260071,Photographs,Photograph,Worcester. From the Severn,,,,,,Artist,,Francis Bedford,"British, London 1816–1894 London",,"Bedford, Francis",British,1816,1894,1870s,1870,1879,Albumen silver print from glass negative,,"The Elisha Whittelsey Collection, The Elisha Whittelsey Fund, 1972",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260071,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.567.6,false,true,260072,Photographs,Photograph,"Wrexham, Rossett Mill",,,,,,Artist,,Francis Bedford,"British, London 1816–1894 London",,"Bedford, Francis",British,1816,1894,1870s,1870,1879,Albumen silver print from glass negative,,"The Elisha Whittelsey Collection, The Elisha Whittelsey Fund, 1972",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260072,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.567.7,false,true,260073,Photographs,Photograph,The Wye and Symond's Yat. From Rocklands,,,,,,Artist,,Francis Bedford,"British, London 1816–1894 London",,"Bedford, Francis",British,1816,1894,1870s,1870,1879,Albumen silver print from glass negative,,"The Elisha Whittelsey Collection, The Elisha Whittelsey Fund, 1972",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260073,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.567.8,false,true,260074,Photographs,Photograph,"Stradford-on-Avon Church, from the Avon",,,,,,Artist,,Francis Bedford,"British, London 1816–1894 London",,"Bedford, Francis",British,1816,1894,1870s,1870,1879,Albumen silver print from glass negative,,"The Elisha Whittelsey Collection, The Elisha Whittelsey Fund, 1972",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260074,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.567.9,false,true,260075,Photographs,Photograph,"Ilfracombe, Capstone Parade",,,,,,Artist,,Francis Bedford,"British, London 1816–1894 London",,"Bedford, Francis",British,1816,1894,1870s,1870,1879,Albumen silver print from glass negative,,"The Elisha Whittelsey Collection, The Elisha Whittelsey Fund, 1972",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260075,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -63.606.1.5,false,true,270857,Photographs,Photograph,"At Pont y pair, Bettws-y-Coed, North Wales",,,,,,Artist,,Francis Bedford,"British, London 1816–1894 London",,"Bedford, Francis",British,1816,1894,1856,1856,1856,Albumen silver print,,"The Elisha Whittelsey Collection, The Elisha Whittelsey Fund, 1963",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/270857,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.567.10,false,true,260057,Photographs,Photograph,"Sidmount, West end of Esplanade",,,,,,Artist,,Francis Bedford,"British, London 1816–1894 London",,"Bedford, Francis",British,1816,1894,1870s,1870,1879,Albumen silver print from glass negative,,"The Elisha Whittelsey Collection, The Elisha Whittelsey Fund, 1972",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260057,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.567.11,false,true,260058,Photographs,Photograph,"Ilfracombe, The Victorian Promenade",,,,,,Artist,,Francis Bedford,"British, London 1816–1894 London",,"Bedford, Francis",British,1816,1894,1870s,1870,1879,Albumen silver print from glass negative,,"The Elisha Whittelsey Collection, The Elisha Whittelsey Fund, 1972",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260058,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.567.12,false,true,260059,Photographs,Photograph,Glen Lun. The Rustic Bridge,,,,,,Artist,,Francis Bedford,"British, London 1816–1894 London",,"Bedford, Francis",British,1816,1894,1870s,1870,1879,Albumen silver print from glass negative,,"The Elisha Whittelsey Collection, The Elisha Whittelsey Fund, 1972",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260059,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.567.13,false,true,260060,Photographs,Photograph,"Rhyl, from the Sea",,,,,,Artist,,Francis Bedford,"British, London 1816–1894 London",,"Bedford, Francis",British,1816,1894,1870s,1870,1879,Albumen silver print from glass negative,,"The Elisha Whittelsey Collection, The Elisha Whittelsey Fund, 1972",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260060,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.567.14,false,true,260061,Photographs,Photograph,"Clovelly, The New Inn and Street",,,,,,Artist,,Francis Bedford,"British, London 1816–1894 London",,"Bedford, Francis",British,1816,1894,1870s,1870,1879,Albumen silver print from glass negative,,"The Elisha Whittelsey Collection, The Elisha Whittelsey Fund, 1972",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260061,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.567.15,false,true,260062,Photographs,Photograph,Old Barmouth,,,,,,Artist,,Francis Bedford,"British, London 1816–1894 London",,"Bedford, Francis",British,1816,1894,1870s,1870,1879,Albumen silver print from glass negative,,"The Elisha Whittelsey Collection, The Elisha Whittelsey Fund, 1972",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260062,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.567.16,false,true,260063,Photographs,Photograph,"Torquay, Hesketh Crescent and Meadfoot",,,,,,Artist,,Francis Bedford,"British, London 1816–1894 London",,"Bedford, Francis",British,1816,1894,1870s,1870,1879,Albumen silver print from glass negative,,"The Elisha Whittelsey Collection, The Elisha Whittelsey Fund, 1972",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260063,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.567.17,false,true,260064,Photographs,Photograph,Rhyl. The Pavilion and Pier,,,,,,Artist,,Francis Bedford,"British, London 1816–1894 London",,"Bedford, Francis",British,1816,1894,1870s,1870,1879,Albumen silver print from glass negative,,"The Elisha Whittelsey Collection, The Elisha Whittelsey Fund, 1972",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260064,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.567.18,false,true,260065,Photographs,Photograph,Barmouth. Marine Terrace and Esplanade,,,,,,Artist,,Francis Bedford,"British, London 1816–1894 London",,"Bedford, Francis",British,1816,1894,1870s,1870,1879,Albumen silver print from glass negative,,"The Elisha Whittelsey Collection, The Elisha Whittelsey Fund, 1972",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260065,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.567.19,false,true,260066,Photographs,Photograph,Colwyn Bay. The Pool in the Wood,,,,,,Artist,,Francis Bedford,"British, London 1816–1894 London",,"Bedford, Francis",British,1816,1894,1870s,1870,1879,Albumen silver print from glass negative,,"The Elisha Whittelsey Collection, The Elisha Whittelsey Fund, 1972",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260066,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.567.20,false,true,260068,Photographs,Photograph,"Abergele, Tan-yr-ogo Cave",,,,,,Artist,,Francis Bedford,"British, London 1816–1894 London",,"Bedford, Francis",British,1816,1894,1870s,1870,1879,Albumen silver print from glass negative,,"The Elisha Whittelsey Collection, The Elisha Whittelsey Fund, 1972",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260068,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.717,false,true,285960,Photographs,Photographs,Pensarn Beach,,,,,,Artist,,Francis Bedford,"British, London 1816–1894 London",,"Bedford, Francis",British,1816,1894,1860s,1860,1869,Albumen silver print,Image: 12.6 × 19.9 cm (4 15/16 × 7 13/16 in.),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285960,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1994.215,false,true,267034,Photographs,Photograph,"Walter Churcher, ""Churcher Smileth""",,,,,,Artist,,Frederick H. Evans,"British, London 1853–1943 London",,"Evans, Frederick Henry",British,1853,1943,1900–1905,1900,1905,Platinum print,22.2 x 11.4 cm. (8 3/4 x 4 1/2 in.),"Gift of Joel Snyder, 1994",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267034,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -54.550.3,false,true,269746,Photographs,Photograph,[Angels with Interlace],,,,,,Artist,,Frederick H. Evans,"British, London 1853–1943 London",,"Evans, Frederick Henry",British,1853,1943,1900s–1910s,1900,1919,Platinum print,Image (visible): 3 9/16 × 8 15/16 in. (9.1 × 22.7 cm) Image (overall): 4 × 10 in. (10.1 × 25.4 cm) Overmat: 7 × 13 in. (17.8 × 33 cm),"Gift of Gordon Conn, 1954",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269746,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -54.550.5,false,true,269748,Photographs,Photograph,"[Needlework Altar Cloth, Durham]",,,,,,Artist,,Frederick H. Evans,"British, London 1853–1943 London",,"Evans, Frederick Henry",British,1853,1943,1911-1912,1911,1912,Platinum print on fabric,,"Gift of Gordon Conn, 1954",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269748,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -54.550.6,false,true,269749,Photographs,Photograph,Un Bon Viveur,,,,,,Artist,,Frederick H. Evans,"British, London 1853–1943 London",,"Evans, Frederick Henry",British,1853,1943,1890s–1900s,1890,1909,Platinum print,,"Gift of Gordon Conn, 1954",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269749,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -68.637.4,false,true,271352,Photographs,Photograph,Alvin Langdon Coburn,,,,,,Artist,,Frederick H. Evans,"British, London 1853–1943 London",,"Evans, Frederick Henry",British,1853,1943,ca. 1901,1899,1903,Platinum print,,"The Elisha Whittelsey Collection, The Elisha Whittelsey Fund, 1968",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/271352,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -68.637.5,false,true,271353,Photographs,Photograph,Gloucester Cathedral: North Transept,,,,,,Artist,,Frederick H. Evans,"British, London 1853–1943 London",,"Evans, Frederick Henry",British,1853,1943,1900s–1910s,1900,1919,Platinum print,,"The Elisha Whittelsey Collection, The Elisha Whittelsey Fund, 1968",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/271353,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -68.637.6,false,true,271354,Photographs,Photograph,Wells Cathedral from the Moat Path,,,,,,Artist,,Frederick H. Evans,"British, London 1853–1943 London",,"Evans, Frederick Henry",British,1853,1943,1900s–1910s,1900,1919,Platinum print,,"The Elisha Whittelsey Collection, The Elisha Whittelsey Fund, 1968",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/271354,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -68.637.7,false,true,271355,Photographs,Photograph,"Maison Jeanne d'Arc, Rouen",,,,,,Artist,,Frederick H. Evans,"British, London 1853–1943 London",,"Evans, Frederick Henry",British,1853,1943,1900s,1900,1909,Platinum print,,"The Elisha Whittelsey Collection, The Elisha Whittelsey Fund, 1968",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/271355,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -68.688.1,false,true,271404,Photographs,Photograph,"""Castle in the Air""",,,,,,Artist,,Frederick H. Evans,"British, London 1853–1943 London",,"Evans, Frederick Henry",British,1853,1943,ca. 1906,1904,1908,Platinum print,,"David Hunter McAlpin Fund, 1968",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/271404,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -68.688.3,false,true,271406,Photographs,Photograph,[Trees],,,,,,Artist,,Frederick H. Evans,"British, London 1853–1943 London",,"Evans, Frederick Henry",British,1853,1943,1900s–1910s,1900,1919,Gum bichromate print,,"David Hunter McAlpin Fund, 1968",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/271406,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -69.616.1,false,true,271523,Photographs,Photograph,In the New Forest,,,,,,Artist,,Frederick H. Evans,"British, London 1853–1943 London",,"Evans, Frederick Henry",British,1853,1943,1919,1919,1919,Platinum print,,"The Elisha Whittelsey Collection, The Elisha Whittelsey Fund, 1969",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/271523,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -69.616.2,false,true,271524,Photographs,Photograph,Bude,,,,,,Artist,,Frederick H. Evans,"British, London 1853–1943 London",,"Evans, Frederick Henry",British,1853,1943,1910s–20s,1910,1929,Platinum print,,"The Elisha Whittelsey Collection, The Elisha Whittelsey Fund, 1969",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/271524,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -69.616.3,false,true,271525,Photographs,Photograph,Bude,,,,,,Artist,,Frederick H. Evans,"British, London 1853–1943 London",,"Evans, Frederick Henry",British,1853,1943,1910s–20s,1910,1929,Platinum print,,"The Elisha Whittelsey Collection, The Elisha Whittelsey Fund, 1969",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/271525,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -69.616.4,false,true,271526,Photographs,Photograph,Dandelions,,,,,,Artist,,Frederick H. Evans,"British, London 1853–1943 London",,"Evans, Frederick Henry",British,1853,1943,1900s–1920s,1900,1929,Platinum print,,"The Elisha Whittelsey Collection, The Elisha Whittelsey Fund, 1969",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/271526,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -69.616.5,false,true,271527,Photographs,Photograph,A Stalk of Berbery,,,,,,Artist,,Frederick H. Evans,"British, London 1853–1943 London",,"Evans, Frederick Henry",British,1853,1943,1900s–1910s,1900,1919,Platinum print,,"The Elisha Whittelsey Collection, The Elisha Whittelsey Fund, 1969",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/271527,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.43.283,false,true,271766,Photographs,Photograph,Height and Light in Bourges Cathedral,,,,,,Artist,,Frederick H. Evans,"British, London 1853–1943 London",,"Evans, Frederick Henry",British,1853,1943,1903,1903,1903,Platinum print,12.0 x 7.4 cm (4 3/4 x 2 15/16 in.),"Alfred Stieglitz Collection, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/271766,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -49.55.239,false,true,271764,Photographs,Photograph,George Bernard Shaw,,,,,,Artist,,Frederick H. Evans,"British, London 1853–1943 London",,"Evans, Frederick Henry",British,1853,1943,1901,1901,1901,Platinum print,Image: 9 in. × 6 1/8 in. (22.8 × 15.6 cm) Border 1: 10 11/16 × 7 9/16 in. (27.2 × 19.2 cm) Border 2: 10 7/8 × 7 3/4 in. (27.7 × 19.7 cm) Mount: 14 3/16 × 11 1/4 in. (36 × 28.6 cm),"Alfred Stieglitz Collection, 1949",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/271764,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1997.398.3,false,true,282112,Photographs,Photograph,Lincoln Cathedral: From the Castle,,,,,,Artist,,Frederick H. Evans,"British, London 1853–1943 London",,"Evans, Frederick Henry",British,1853,1943,1896,1896,1896,Photogravure,21 x 15.8 cm (8 1/4 x 6 1/4 in. ),"The Rubel Collection, Gift of William Rubel, 1997",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/282112,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.159.8,false,true,306209,Photographs,Photograph,Redlands Woods,,,,,,Artist,,Frederick H. Evans,"British, London 1853–1943 London",,"Evans, Frederick Henry",British,1853,1943,1893,1893,1893,Platinum print,Mount: 9 15/16 in. × 6 11/16 in. (25.3 × 17 cm) Image: 6 1/16 × 4 7/16 in. (15.4 × 11.2 cm),"Bequest of Maurice B. Sendak, 2012",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306209,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1987.1183.1,false,true,265736,Photographs,Photograph,"Organ Screen, York Minster",,,,,,Artist,,Frederick H. Evans,"British, London 1853–1943 London",,"Evans, Frederick Henry",British,1853,1943,ca. 1904,1902,1906,Platinum print,20.3 x 24.4 cm. (8 x 9 5/8 in.),"Gift of Paul F. Walter, in memory of Christopher Hemphill, 1987",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/265736,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1987.1183.2,false,true,265747,Photographs,Photograph,The Little Cloisters,,,,,,Artist,,Frederick H. Evans,"British, London 1853–1943 London",,"Evans, Frederick Henry",British,1853,1943,ca. 1900,1898,1902,Platinum print,11.4 x 15.1 cm. (4 1/2 x 5 15/16 in.),"Gift of Paul F. Walter, in memory of Christopher Hemphill, 1987",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/265747,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.779,false,true,286415,Photographs,Photograph,On a French River,,,,,,Artist,,Frederick H. Evans,"British, London 1853–1943 London",,"Evans, Frederick Henry",British,1853,1943,ca. 1902,1900,1904,Gelatin silver print,Image: 24.8 x 13.1 cm (9 3/4 x 5 3/16 in.) Mount: 45.5 x 30.2 cm (17 15/16 x 11 7/8 in.),"Gilman Collection, Purchase, Mrs. Walter Annenberg and The Annenberg Foundation Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286415,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.906,false,true,286643,Photographs,Photograph,"""In Sure and Certain Hope."" York Minster",,,,,,Artist,,Frederick H. Evans,"British, London 1853–1943 London",,"Evans, Frederick Henry",British,1853,1943,1902,1902,1902,Platinum print,"Image: 20 x 14.8 cm (7 7/8 x 5 13/16 in.) Mount: 26.4 x 18.5 cm (10 3/8 x 7 5/16 in.), irregularly trimmed","Gilman Collection, Purchase, Alfred Stieglitz Society Gifts, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286643,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2010.365,false,true,294742,Photographs,Photograph,The Great British Advance in the West: A Raiding Party Waiting for the Word to Go.,,,,,,Artist,,John Warwick Brooke,"British, London 1886–1929 London",,"Brooke, John Warwick",British,1886,1929,1914–18,1914,1918,Gelatin silver print,Image: 14 x 19.1 cm (5 1/2 x 7 1/2 in.),"Twentieth-Century Photography Fund, 2010",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/294742,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1992.5030,false,true,266672,Photographs,Photograph,French Machinery,,,,,,Artist,,Charles Thurston Thompson,"British, Peckham 1816–1868 Paris",,"Thompson, Charles Thurston",British,1816,1868,1855,1855,1855,Salted paper print from glass negative,22.0 x 28.7 cm (8 11/16 x 11 5/16 in.),"Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 1992",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/266672,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -63.606.1.10,false,true,270824,Photographs,Photograph,"The Court of Lions in the Alhambra, Spain",,,,,,Artist,,John Gregory Crace,"British, London 1809–1889 Dulwich",,"Crace, John Gregory",British,1809,1889,1855,1855,1855,Albumen silver print,,"The Elisha Whittelsey Collection, The Elisha Whittelsey Fund, 1963",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/270824,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.21,false,true,283093,Photographs,Photograph,St. George and the Dragon,,,,,,Artist,,Lewis Carroll,"British, Daresbury, Cheshire 1832–1898 Guildford",,"Carroll, Lewis",British,1832,1898,"June 26, 1875",1875,1875,Albumen silver print from glass negative,Image: 11.7 × 16 cm (4 5/8 × 6 5/16 in.),"Gilman Collection, Purchase, Ann Tenenbaum and Thomas H. Lee Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/283093,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.400.64,false,true,291817,Photographs,Daguerreotype,[Seated Man Pointing to a Passage in an Open Book],,,,,,Artist,,John Jabez Edwin Mayall,"British, Oldham, Lancashire 1813–1901 West Sussex",,"Mayall, John Jabez Edwin",British,1813,1901,1850s,1853,1859,Daguerreotype,Image: 15.3 x 10.8 cm (6 x 4 1/4 in.) Plate: 19.7 x 15.2 cm (7 3/4 x 6 in.) Case: 2.1 x 21 x 16.5 cm (13/16 x 8 1/4 x 6 1/2 in.),"Bequest of Herbert Mitchell, 2008",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/291817,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -63.606.1.23,false,true,270838,Photographs,Photograph,Dr. Livingstone,,,,,,Artist,,John Jabez Edwin Mayall,"British, Oldham, Lancashire 1813–1901 West Sussex",,"Mayall, John Jabez Edwin",British,1813,1901,1857,1857,1857,Albumen silver print,,"The Elisha Whittelsey Collection, The Elisha Whittelsey Fund, 1963",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/270838,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1988.1048,false,true,265917,Photographs,Photograph,The Jewels of the Pagoda,,,,,,Artist,,Linnaeus Tripe,"British, Devonport (Plymouth Dock) 1822–1902 Devonport",,"Tripe, Linnaeus",British,1822,1902,January 1858,1858,1858,Albumen silver print from waxed paper negative,Image: 26.4 x 36.9 cm (10 3/8 x 14 1/2 in.),"Purchase, Cynthia Hazen Polsky Gift, 1988",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/265917,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1991.1152,false,true,266450,Photographs,Photograph,Elliot Marbles and Other Sculpture from the Central Museum Madras: Group 26,,,,,,Artist,,Linnaeus Tripe,"British, Devonport (Plymouth Dock) 1822–1902 Devonport",,"Tripe, Linnaeus",British,1822,1902,May–June 1858,1858,1858,Albumen silver print from dry collodion on glass negative,Image: 23.4 x 29.9 cm (9 3/16 x 11 3/4 in.) Mount: 33.1 x 45 cm (13 1/16 x 17 11/16 in.) Mat: 18 1/2 × 22 1/2 in. (47 × 57.1 cm),"Purchase, Cynthia Hazen Polsky Gift and The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 1991",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/266450,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2012.323.1,false,true,302510,Photographs,Panorama,No. 1. Prome. General View.,,,,,,Artist,,Linnaeus Tripe,"British, Devonport (Plymouth Dock) 1822–1902 Devonport",,"Tripe, Linnaeus",British,1822,1902,"August 7, 1855",1855,1855,Albumen silver prints from waxed(?) paper negatives,"Image: 24.4 x 60.8 cm (9 5/8 x 23 15/16 in.), overall Mount: 45.7 x 108 cm (18 x 42 1/2 in.), overall","Purchase, The Buddy Taub Foundation, Dennis A. Roach and Jill Roach, Directors, and Alfred Stieglitz Society Gifts, 2012",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/302510,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2012.323.5,false,true,302635,Photographs,Photograph,No. 7. Ye-nan-gyoung. Pagoda and Kyoung.,,,,,,Artist,,Linnaeus Tripe,"British, Devonport (Plymouth Dock) 1822–1902 Devonport",,"Tripe, Linnaeus",British,1822,1902,"August 14–16, 1855",1855,1855,Albumen silver print from waxed paper negative,Image: 23.7 x 33.8 cm (9 5/16 x 13 5/16 in.) Mount: 45.6 x 58.3 cm (17 15/16 x 22 15/16 in.),"Purchase, The Buddy Taub Foundation, Dennis A. Roach and Jill Roach, Directors, and Alfred Stieglitz Society Gifts, 2012",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/302635,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2012.323.6,false,true,302636,Photographs,Photograph,Pugahm Myo: Thapinyu Pagoda,,,,,,Artist,,Linnaeus Tripe,"British, Devonport (Plymouth Dock) 1822–1902 Devonport",,"Tripe, Linnaeus",British,1822,1902,"August 20–24, 1855",1855,1855,Albumen silver print from waxed paper negative,Image: 25.1 x 34.5 cm (9 7/8 x 13 9/16 in.) Mount: 45.6 x 58.3 cm (17 15/16 x 22 15/16 in.) Mat: 20 × 24 in. (50.8 × 61 cm),"Purchase, The Buddy Taub Foundation, Dennis A. Roach and Jill Roach, Directors, and Alfred Stieglitz Society Gifts, 2012",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/302636,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2012.323.8,false,true,302638,Photographs,Photograph,Pugahm Myo: Distant View of Gauda-palen Pagoda,,,,,,Artist,,Linnaeus Tripe,"British, Devonport (Plymouth Dock) 1822–1902 Devonport",,"Tripe, Linnaeus",British,1822,1902,"August 20–24, 1855",1855,1855,Albumen silver print from waxed(?) paper negative,Image: 25.3 x 34.1 cm (9 15/16 x 13 7/16 in.) Mount: 45.6 x 58.3 cm (17 15/16 x 22 15/16 in.) Mat: 20 × 24 in. (50.8 × 61 cm),"Purchase, The Buddy Taub Foundation, Dennis A. Roach and Jill Roach, Directors, and Alfred Stieglitz Society Gifts, 2012",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/302638,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1997.382.60,false,true,282071,Photographs,Photograph,Virabadra Drug as seen from near the site of the last view,,,,,,Artist,,Linnaeus Tripe,"British, Devonport (Plymouth Dock) 1822–1902 Devonport",,"Tripe, Linnaeus",British,1822,1902,December 1857–January 1858,1857,1858,Salted paper print from waxed paper negative,27.7 x 38.2 cm (10 7/8 x 15 1/16 in. ),"The Rubel Collection, Purchase, Cynthia Hazen Polsky and Lila Acheson Wallace Gifts, 1997",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/282071,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1997.382.61,false,true,282072,Photographs,Photograph,Central Museum Madras: Group 27,,,,,,Artist,,Linnaeus Tripe,"British, Devonport (Plymouth Dock) 1822–1902 Devonport",,"Tripe, Linnaeus",British,1822,1902,May–June 1858,1858,1858,Albumen silver print from dry collodion on glass negative,Image: 25.8 x 23.2 cm (10 3/16 x 9 1/8 in.) Mount: 45.1 x 33 cm (17 3/4 x 13 in.) Mat: 20 × 24 in. (50.8 × 61 cm),"The Rubel Collection, Purchase, Lila Acheson Wallace and Richard and Ronay Menschel Gifts, 1997",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/282072,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2012.323.10,false,true,302640,Photographs,Photograph,Tsagain Myo: A Roadway,,,,,,Artist,,Linnaeus Tripe,"British, Devonport (Plymouth Dock) 1822–1902 Devonport",,"Tripe, Linnaeus",British,1822,1902,"August 29–30, 1855",1855,1855,Albumen silver print from waxed(?) paper negative,Image: 24.5 x 34.1 cm (9 5/8 x 13 7/16 in.) Mount: 45.6 x 58.3 cm (17 15/16 x 22 15/16 in.) Mat: 20 × 24 in. (50.8 × 61 cm),"Purchase, The Buddy Taub Foundation, Dennis A. Roach and Jill Roach, Directors, and Alfred Stieglitz Society Gifts, 2012",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/302640,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2012.323.11,false,true,302641,Photographs,Photograph,Tsagain Myo: Litters under a shed.,,,,,,Artist,,Linnaeus Tripe,"British, Devonport (Plymouth Dock) 1822–1902 Devonport",,"Tripe, Linnaeus",British,1822,1902,"August 29–30, 1855",1855,1855,Albumen silver print from waxed(?) paper negative,Image: 27 x 34.5 cm (10 5/8 x 13 9/16 in.) Mount: 45.6 x 58.3 cm (17 15/16 x 22 15/16 in.) Mat: 20 × 24 in. (50.8 × 61 cm),"Purchase, The Buddy Taub Foundation, Dennis A. Roach and Jill Roach, Directors, and Alfred Stieglitz Society Gifts, 2012",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/302641,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2012.323.14,false,true,302644,Photographs,Photograph,Amerapoora: Corner of Mygabhoodee-tee Kyoung,,,,,,Artist,,Linnaeus Tripe,"British, Devonport (Plymouth Dock) 1822–1902 Devonport",,"Tripe, Linnaeus",British,1822,1902,"September 1–October 21, 1855",1855,1855,Albumen silver print from waxed paper negative,Image: 27.3 x 34.4 cm (10 3/4 x 13 9/16 in.) Mount: 45.6 x 58.4 cm (17 15/16 x 23 in.) Mat: 20 × 24 in. (50.8 × 61 cm),"Purchase, The Buddy Taub Foundation, Dennis A. Roach and Jill Roach, Directors, and Alfred Stieglitz Society Gifts, 2012",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/302644,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2012.323.15,false,true,302645,Photographs,Photograph,Amerapoora: Wooden Bridge,,,,,,Artist,,Linnaeus Tripe,"British, Devonport (Plymouth Dock) 1822–1902 Devonport",,"Tripe, Linnaeus",British,1822,1902,"September 1–October 21, 1855",1855,1855,"Purchase, The Buddy Taub Foundation, Dennis A. Roach and Jill Roach, Directors, and Alfred Stieglitz Society Gifts, 2012",Image: 22.3 x 32.4 cm (8 3/4 x 12 3/4 in.) Mount: 45.6 x 58.4 cm (17 15/16 x 23 in.) Mat: 20 × 24 in. (50.8 × 61 cm),"Purchase, The Buddy Taub Foundation, Dennis A. Roach and Jill Roach, Directors, and Alfred Stieglitz Society Gifts, 2012",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/302645,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2012.323.20,false,true,302650,Photographs,Photograph,Amerapoora: Shwe-doung-dyk Pagoda,,,,,,Artist,,Linnaeus Tripe,"British, Devonport (Plymouth Dock) 1822–1902 Devonport",,"Tripe, Linnaeus",British,1822,1902,"September 1–October 21, 1855",1855,1855,Albumen silver print from waxed paper negative,Image: 25.8 x 34.6 cm (10 3/16 x 13 5/8 in.) Mount: 45.5 x 58.3 cm (17 15/16 x 22 15/16 in.) Mat: 20 × 24 in. (50.8 × 61 cm),"Purchase, The Buddy Taub Foundation, Dennis A. Roach and Jill Roach, Directors, and Alfred Stieglitz Society Gifts, 2012",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/302650,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2012.323.24,false,true,302654,Photographs,Photograph,Amerapoora: Part of Balcony on the South Side of Maha-oung-meeay-liy-mhan Kyoung,,,,,,Artist,,Linnaeus Tripe,"British, Devonport (Plymouth Dock) 1822–1902 Devonport",,"Tripe, Linnaeus",British,1822,1902,"September 1–October 21, 1855",1855,1855,Albumen silver print from waxed paper negative,Image: 26.9 x 34.7 cm (10 9/16 x 13 11/16 in.) Mount: 45.6 x 58.3 cm (17 15/16 x 22 15/16 in.) Mat: 20 × 24 in. (50.8 × 61 cm),"Purchase, The Buddy Taub Foundation, Dennis A. Roach and Jill Roach, Directors, and Alfred Stieglitz Society Gifts, 2012",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/302654,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2012.323.30,false,true,302660,Photographs,Photograph,Rangoon: Henzas on the East Side of the Shwe Dagon Pagoda,,,,,,Artist,,Linnaeus Tripe,"British, Devonport (Plymouth Dock) 1822–1902 Devonport",,"Tripe, Linnaeus",British,1822,1902,November 1855,1855,1855,Albumen silver print from waxed paper negative,Image: 26.1 x 34.3 cm (10 1/4 x 13 1/2 in.) Mount: 45.7 x 58.4 cm (18 x 23 in.) Mat: 20 × 24 in. (50.8 × 61 cm),"Purchase, The Buddy Taub Foundation, Dennis A. Roach and Jill Roach, Directors, and Alfred Stieglitz Society Gifts, 2012",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/302660,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.767,false,true,285626,Photographs,Photograph,"Amerapoora, Palace of the White Elephant",,,,,,Artist,,Linnaeus Tripe,"British, Devonport (Plymouth Dock) 1822–1902 Devonport",,"Tripe, Linnaeus",British,1822,1902,1 September–21 October 1855,1855,1855,Salted paper print from waxed(?) paper negative,Image: 24.1 x 33.5 cm (9 1/2 x 13 3/16 in.),"Gilman Collection, Purchase, Cynthia Hazen Polsky Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285626,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.776,false,true,285623,Photographs,Photograph,"Amerapoora, Barracks of the Burmese Guard",,,,,,Artist,,Linnaeus Tripe,"British, Devonport (Plymouth Dock) 1822–1902 Devonport",,"Tripe, Linnaeus",British,1822,1902,1 September–21 October 1855,1855,1855,Salted paper print from waxed paper negative,Image: 26 x 34.4 cm (10 1/4 x 13 9/16 in.),"Gilman Collection, Purchase, Cynthia Hazen Polsky Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285623,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.381.1.1,false,true,287261,Photographs,Photograph,The Elephant Rock,,,,,,Artist,,Linnaeus Tripe,"British, Devonport (Plymouth Dock) 1822–1902 Devonport",,"Tripe, Linnaeus",British,1822,1902,January–February 1858,1858,1858,Albumen silver print from waxed paper negative,Image: 23.7 x 35.5 cm (9 5/16 x 14 in.) Mount: 45.3 x 57.4 cm (17 13/16 x 22 5/8 in.),"Gilman Collection, Purchase, Cynthia Hazen Polsky Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287261,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.381.1.2,false,true,287262,Photographs,Photograph,South East Angle of the Tirambur Pagoda,,,,,,Artist,,Linnaeus Tripe,"British, Devonport (Plymouth Dock) 1822–1902 Devonport",,"Tripe, Linnaeus",British,1822,1902,January–February 1858,1858,1858,Albumen silver print from waxed paper negative,Image: 26.3 x 36.4 cm (10 3/8 x 14 5/16 in.) Mount: 45 x 56.5 cm (17 11/16 x 22 1/4 in.),"Gilman Collection, Purchase, Cynthia Hazen Polsky Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287262,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.381.1.3,false,true,287263,Photographs,Photograph,View of the N. E. Angle of the Tirambur Pagoda.,,,,,,Artist,,Linnaeus Tripe,"British, Devonport (Plymouth Dock) 1822–1902 Devonport",,"Tripe, Linnaeus",British,1822,1902,January–February 1858,1858,1858,Albumen silver print from waxed paper negative,Image: 24.2 x 36.2 cm (9 1/2 x 14 1/4 in.) Mount: 45.2 x 57.3 cm (17 13/16 x 22 9/16 in.),"Gilman Collection, Purchase, Cynthia Hazen Polsky Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287263,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.381.1.4,false,true,286803,Photographs,Photograph,The Teppa-kulam,,,,,,Artist,,Linnaeus Tripe,"British, Devonport (Plymouth Dock) 1822–1902 Devonport",,"Tripe, Linnaeus",British,1822,1902,1858,1858,1858,Albumen silver print from waxed paper negative,Image: 26 x 37.5 cm (10 1/4 x 14 3/4 in.) Mount: 45.2 x 56.2 cm (17 13/16 x 22 1/8 in.),"Gilman Collection, Purchase, Cynthia Hazen Polsky Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286803,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.381.1.5,false,true,287264,Photographs,Photograph,The Tamukkam or Tamkam,,,,,,Artist,,Linnaeus Tripe,"British, Devonport (Plymouth Dock) 1822–1902 Devonport",,"Tripe, Linnaeus",British,1822,1902,January–March 1858,1858,1858,Albumen silver print from waxedpaper negative,Image: 26 x 37.2 cm (10 1/4 x 14 5/8 in.) Mount: 45.2 x 57.5 cm (17 13/16 x 22 5/8 in.),"Gilman Collection, Purchase, Cynthia Hazen Polsky Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287264,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.381.1.6,false,true,287265,Photographs,Photograph,The Causeway Across the Vaigai River,,,,,,Artist,,Linnaeus Tripe,"British, Devonport (Plymouth Dock) 1822–1902 Devonport",,"Tripe, Linnaeus",British,1822,1902,January–March 1858,1858,1858,Albumen silver print from waxed paper negative,Image: 24.9 x 35 cm (9 13/16 x 13 3/4 in.) Mount: 45.2 x 57.5 cm (17 13/16 x 22 5/8 in.),"Gilman Collection, Purchase, Cynthia Hazen Polsky Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287265,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.381.1.7,false,true,287266,Photographs,Photograph,The Neerali Mundapam,,,,,,Artist,,Linnaeus Tripe,"British, Devonport (Plymouth Dock) 1822–1902 Devonport",,"Tripe, Linnaeus",British,1822,1902,January–March 1858,1858,1858,Albumen silver print from paper negative,Image: 23.9 x 34.8 cm (9 7/16 x 13 11/16 in.) Mount: 45.3 x 57.4 cm (17 13/16 x 22 5/8 in.),"Gilman Collection, Purchase, Cynthia Hazen Polsky Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287266,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.381.1.8,false,true,287267,Photographs,Photograph,The Raya Gopuram from E.,,,,,,Artist,,Linnaeus Tripe,"British, Devonport (Plymouth Dock) 1822–1902 Devonport",,"Tripe, Linnaeus",British,1822,1902,January–March 1858,1858,1858,Albumen silver print from waxed paper negative,Image: 27 x 36.5 cm (10 5/8 x 14 3/8 in.) Mount: 45.4 x 57.2 cm (17 7/8 x 22 1/2 in.),"Gilman Collection, Purchase, Cynthia Hazen Polsky Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287267,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.381.1.9,false,true,286883,Photographs,Photograph,"Pillars in the Recessed Portico in the Roya Gopuram with the Base of One of the Four Sculptured Monoliths, Madura",,,,,,Artist,,Linnaeus Tripe,"British, Devonport (Plymouth Dock) 1822–1902 Devonport",,"Tripe, Linnaeus",British,1822,1902,January–March 1858,1858,1858,Albumen silver print from waxed paper negative,Image: 35.8 x 30.2 cm (14 1/8 x 11 7/8 in.) Mount: 45.3 x 57.5 cm (17 13/16 x 22 5/8 in.),"Gilman Collection, Purchase, Cynthia Hazen Polsky Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286883,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.381.2.1,false,true,287269,Photographs,Photograph,The Great Pagoda,,,,,,Artist,,Linnaeus Tripe,"British, Devonport (Plymouth Dock) 1822–1902 Devonport",,"Tripe, Linnaeus",British,1822,1902,January–March 1858,1858,1858,Albumen silver print from waxed paper negative,Image: 33.6 x 30.1 cm (13 1/4 x 11 7/8 in.) Mount: 57 x 45 cm (22 7/16 x 17 11/16 in.),"Gilman Collection, Purchase, Cynthia Hazen Polsky Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287269,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.381.2.2,false,true,287270,Photographs,Photograph,The Viravasuntarayan Munapam,,,,,,Artist,,Linnaeus Tripe,"British, Devonport (Plymouth Dock) 1822–1902 Devonport",,"Tripe, Linnaeus",British,1822,1902,January–March 1858,1858,1858,Albumen silver print from waxed paper negative,Image: 29.5 x 37 cm (11 5/8 x 14 9/16 in.) Mount: 46 x 57.1 cm (18 1/8 x 22 1/2 in.),"Gilman Collection, Purchase, Cynthia Hazen Polsky Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287270,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.381.2.3,false,true,287271,Photographs,Photograph,Entrance to the Thousand Pillared Mundapam in the Great Pagoda,,,,,,Artist,,Linnaeus Tripe,"British, Devonport (Plymouth Dock) 1822–1902 Devonport",,"Tripe, Linnaeus",British,1822,1902,January–March 1858,1858,1858,Albumen silver print from waxed paper negative,Image: 33 x 30.7 cm (13 x 12 1/16 in.) Mount: 57.1 x 45 cm (22 1/2 x 17 11/16 in.),"Gilman Collection, Purchase, Cynthia Hazen Polsky Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287271,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.381.2.4,false,true,287272,Photographs,Photograph,The Muduramiar Mundapam,,,,,,Artist,,Linnaeus Tripe,"British, Devonport (Plymouth Dock) 1822–1902 Devonport",,"Tripe, Linnaeus",British,1822,1902,January–March 1858,1858,1858,Albumen silver print from waxed paper negative,Image: 34.7 x 28.3 cm (13 11/16 x 11 1/8 in.) Mount: 57 x 45.2 cm (22 7/16 x 17 13/16 in.),"Gilman Collection, Purchase, Cynthia Hazen Polsky Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287272,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.381.2.5,false,true,287273,Photographs,Photograph,Side Colonnade in the Muroothappa Sarvacar Mundapam,,,,,,Artist,,Linnaeus Tripe,"British, Devonport (Plymouth Dock) 1822–1902 Devonport",,"Tripe, Linnaeus",British,1822,1902,January–March 1858,1858,1858,Albumen silver print from waxed paper negative,Image: 36.5 x 29.1 cm (14 3/8 x 11 7/16 in.) Mount: 57.1 x 45.1 cm (22 1/2 x 17 3/4 in.),"Gilman Collection, Purchase, Cynthia Hazen Polsky Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287273,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.381.2.6,false,true,287274,Photographs,Photograph,The Inner Facade of the Gateway of the East Gopuram,,,,,,Artist,,Linnaeus Tripe,"British, Devonport (Plymouth Dock) 1822–1902 Devonport",,"Tripe, Linnaeus",British,1822,1902,January–March 1858,1858,1858,Albumen silver print from waxed paper negative,Image: 29.2 x 34 cm (11 1/2 x 13 3/8 in.) Mount (2nd): 45 x 57.1 cm (17 11/16 x 22 1/2 in.),"Gilman Collection, Purchase, Cynthia Hazen Polsky Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287274,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.381.2.7,false,true,287275,Photographs,Photograph,The Kulayana Mundapam,,,,,,Artist,,Linnaeus Tripe,"British, Devonport (Plymouth Dock) 1822–1902 Devonport",,"Tripe, Linnaeus",British,1822,1902,January–March 1858,1858,1858,Albumen silver print from waxed paper negative,Image: 27 x 37.7 cm (10 5/8 x 14 13/16 in.) Mount: 45 x 57.1 cm (17 11/16 x 22 1/2 in.),"Gilman Collection, Purchase, Cynthia Hazen Polsky Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287275,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.381.2.8,false,true,287276,Photographs,Photograph,Tatta Suddhi Mundapam,,,,,,Artist,,Linnaeus Tripe,"British, Devonport (Plymouth Dock) 1822–1902 Devonport",,"Tripe, Linnaeus",British,1822,1902,January–March 1858,1858,1858,Albumen silver print from waxed paper negative,Image: 24 x 36.8 cm (9 7/16 x 14 1/2 in.) Mount: 45 x 57.1 cm (17 11/16 x 22 1/2 in.),"Gilman Collection, Purchase, Cynthia Hazen Polsky Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287276,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.381.2.9,false,true,287277,Photographs,Photograph,The Western Gopuram,,,,,,Artist,,Linnaeus Tripe,"British, Devonport (Plymouth Dock) 1822–1902 Devonport",,"Tripe, Linnaeus",British,1822,1902,January–March 1858,1858,1858,Albumen silver print from waxed paper negative,Image: 36.1 x 28.5 cm (14 3/16 x 11 1/4 in.) Mount: 42 x 39 cm (16 9/16 x 15 3/8 in.),"Gilman Collection, Purchase, Cynthia Hazen Polsky Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287277,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.381.1.10,false,true,287268,Photographs,Photograph,The Raya Gopuram from W,,,,,,Artist,,Linnaeus Tripe,"British, Devonport (Plymouth Dock) 1822–1902 Devonport",,"Tripe, Linnaeus",British,1822,1902,January–March 1858,1858,1858,Albumen silver print from waxed paper negative,Image: 27.3 x 36.3 cm (10 3/4 x 14 5/16 in.) Mount: 45 x 57.8 cm (17 11/16 x 22 3/4 in.),"Gilman Collection, Purchase, Cynthia Hazen Polsky Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287268,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.381.2.10,false,true,287278,Photographs,Photograph,"The Outer Prakarum, or Corridor Around the Temple of the God Sundareshawara",,,,,,Artist,,Linnaeus Tripe,"British, Devonport (Plymouth Dock) 1822–1902 Devonport",,"Tripe, Linnaeus",British,1822,1902,January–March 1858,1858,1858,Albumen silver print from dry collodion on glass negative,Image: 34.8 x 24.2 cm (13 11/16 x 9 1/2 in.) Mount: 57 x 45.1 cm (22 7/16 x 17 3/4 in.),"Gilman Collection, Purchase, Cynthia Hazen Polsky Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287278,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.381.2.11,false,true,283163,Photographs,Photograph,Madura. The Great Pagoda Jewels.,,,,,,Artist,,Linnaeus Tripe,"British, Devonport (Plymouth Dock) 1822–1902 Devonport",,"Tripe, Linnaeus",British,1822,1902,January–February 1858,1858,1858,Albumen silver print from dry collodion on glass negative,Image: 21.9 x 30.1 cm (8 5/8 x 11 7/8 in.) Mount: 45.1 x 57.3 cm (17 3/4 x 22 9/16 in.) Mat: 21 1/8 × 23 15/16 in. (53.6 × 60.8 cm),"Gilman Collection, Purchase, Cynthia Hazen Polsky Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/283163,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.381.2.12,false,true,287279,Photographs,Photograph,Outer Prakarum on the North Side of the Temple of the God Sundareshwara,,,,,,Artist,,Linnaeus Tripe,"British, Devonport (Plymouth Dock) 1822–1902 Devonport",,"Tripe, Linnaeus",British,1822,1902,January–March 1858,1858,1858,Albumen silver print from dry collodion on glass negative,Image: 26.1 x 34 cm (10 1/4 x 13 3/8 in.) Mount: 45.1 x 57 cm (17 3/4 x 22 7/16 in.),"Gilman Collection, Purchase, Cynthia Hazen Polsky Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287279,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.381.2.13,false,true,287280,Photographs,Photograph,View of the Sacred Tank in the Great Pagoda,,,,,,Artist,,Linnaeus Tripe,"British, Devonport (Plymouth Dock) 1822–1902 Devonport",,"Tripe, Linnaeus",British,1822,1902,January–March 1858,1858,1858,Albumen silver print from waxed paper negative,Image: 25.8 x 35.1 cm (10 3/16 x 13 13/16 in.) Mount: 45 x 57 cm (17 11/16 x 22 7/16 in.),"Gilman Collection, Purchase, Cynthia Hazen Polsky Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287280,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.381.2.14,false,true,287281,Photographs,Photograph,View of the Sacred Tank in the Great Pagoda,,,,,,Artist,,Linnaeus Tripe,"British, Devonport (Plymouth Dock) 1822–1902 Devonport",,"Tripe, Linnaeus",British,1822,1902,January–March 1858,1858,1858,Albumen silver print from waxed paper negative,Image: 29.1 x 37.5 cm (11 7/16 x 14 3/4 in.) Mount: 45 x 57 cm (17 11/16 x 22 7/16 in.),"Gilman Collection, Purchase, Cynthia Hazen Polsky Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287281,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.381.2.15,false,true,287282,Photographs,Photograph,Entrance to the Temple of Minakshi in the Great Pagoda,,,,,,Artist,,Linnaeus Tripe,"British, Devonport (Plymouth Dock) 1822–1902 Devonport",,"Tripe, Linnaeus",British,1822,1902,January–March 1858,1858,1858,Albumen silver print from waxed paper negative,Image: 30.5 x 37 cm (12 x 14 9/16 in.) Mount: 45.2 x 57 cm (17 13/16 x 22 7/16 in.),"Gilman Collection, Purchase, Cynthia Hazen Polsky Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287282,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2011.422,false,true,292071,Photographs,Photograph,[Grenadier Guards Drummer],,,,,,Artist,,Joseph Cundall,"British, Norwich, Norfolk 1818–1895 Wallington, Surrey",,"Cundall, Joseph",British,1818,1895,ca. 1856,1854,1856,Salted paper print from glass negative,Image: 23.1 x 17.4 cm (9 1/8 x 6 7/8 in.) Mount: 41.5 x 31.9 cm (16 5/16 x 12 9/16 in.),"Purchase, Alfred Stieglitz Society Gifts, 2011",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/292071,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -52.524.16,false,true,269576,Photographs,Photograph,Cottage at Jersey,,,,,,Artist,,Joseph Cundall,"British, Norwich, Norfolk 1818–1895 Wallington, Surrey",,"Cundall, Joseph",British,1818,1895,1855,1855,1855,Albumen silver print from glass negative,23.5 x 19.1 cm (9 1/4 x 7 1/2 in.),"David Hunter McAlpin Fund, 1952",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269576,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.159.7,false,true,306208,Photographs,Photograph,The Alms House,,,,,,Artist,,Joseph Cundall,"British, Norwich, Norfolk 1818–1895 Wallington, Surrey",,"Cundall, Joseph",British,1818,1895,1855,1855,1855,Albumen silver print from glass negative,Mount: 17 1/4 in. × 11 15/16 in. (43.8 × 30.4 cm) Image: 9 5/16 × 7 5/16 in. (23.7 × 18.5 cm),"Bequest of Maurice B. Sendak, 2012",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306208,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -63.606.1.9,false,true,270861,Photographs,Photograph,Highlanders,,,,,,Artist,,Joseph Cundall,"British, Norwich, Norfolk 1818–1895 Wallington, Surrey",,"Cundall, Joseph",British,1818,1895,1856,1856,1856,Albumen silver print,,"The Elisha Whittelsey Collection, The Elisha Whittelsey Fund, 1963",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/270861,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.949,false,true,283241,Photographs,Photograph,[Loie Fuller Dancing],,,,,,Artist,,Samuel Joshua Beckett,"British, Shadwell, Stepney [London] 1870–1940 Bournemouth",,"Beckett, Samuel Joshua",British,1870,1940,ca. 1900,1899,1901,Gelatin silver print,7.7 x 10.2 cm (3 1/16 x 4 in.),"Gilman Collection, Purchase, Mrs. Walter Annenberg and The Annenberg Foundation Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/283241,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.950,false,true,287806,Photographs,Photograph,[Loie Fuller Dancing],,,,,,Artist,,Samuel Joshua Beckett,"British, Shadwell, Stepney [London] 1870–1940 Bournemouth",,"Beckett, Samuel Joshua",British,1870,1940,ca. 1900,1899,1901,Gelatin silver print,"10.3 x 13.3 cm (4 x 5 1/4 in.), irregularly trimmed","Gilman Collection, Purchase, Mrs. Walter Annenberg and The Annenberg Foundation Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287806,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.951,false,true,287807,Photographs,Photograph,[Loie Fuller Dancing],,,,,,Artist,,Samuel Joshua Beckett,"British, Shadwell, Stepney [London] 1870–1940 Bournemouth",,"Beckett, Samuel Joshua",British,1870,1940,ca. 1900,1899,1901,Gelatin silver print,10.1 x 12.5 cm (4 x 4 15/16 in.),"Gilman Collection, Purchase, Mrs. Walter Annenberg and The Annenberg Foundation Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287807,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.952,false,true,285696,Photographs,Photograph,Loie Fuller Dancing,,,,,,Artist,,Samuel Joshua Beckett,"British, Shadwell, Stepney [London] 1870–1940 Bournemouth",,"Beckett, Samuel Joshua",British,1870,1940,ca. 1900,1898,1902,Gelatin silver print,10.9 x 14.3 cm (4 5/16 x 5 5/8 in.),"Gilman Collection, Purchase, Mrs. Walter Annenberg and The Annenberg Foundation Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285696,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.953,false,true,287808,Photographs,Photograph,Loie Fuller Dancing,,,,,,Artist,,Samuel Joshua Beckett,"British, Shadwell, Stepney [London] 1870–1940 Bournemouth",,"Beckett, Samuel Joshua",British,1870,1940,ca. 1900,1898,1902,Gelatin silver print,Image: 4 in. × 5 11/16 in. (10.2 × 14.4 cm),"Gilman Collection, Purchase, Mrs. Walter Annenberg and The Annenberg Foundation Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287808,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.954,false,true,287809,Photographs,Photograph,Loie Fuller Dancing,,,,,,Artist,,Samuel Joshua Beckett,"British, Shadwell, Stepney [London] 1870–1940 Bournemouth",,"Beckett, Samuel Joshua",British,1870,1940,ca. 1900,1898,1902,Gelatin silver print,Image: 5 15/16 × 4 3/8 in. (15.1 × 11.1 cm),"Gilman Collection, Purchase, Mrs. Walter Annenberg and The Annenberg Foundation Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287809,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.955,false,true,287810,Photographs,Photograph,Loie Fuller Dancing,,,,,,Artist,,Samuel Joshua Beckett,"British, Shadwell, Stepney [London] 1870–1940 Bournemouth",,"Beckett, Samuel Joshua",British,1870,1940,ca. 1900,1898,1902,Gelatin silver print,Image: 3 1/16 in. × 4 in. (7.7 × 10.2 cm),"Gilman Collection, Purchase, Mrs. Walter Annenberg and The Annenberg Foundation Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287810,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.956,false,true,287811,Photographs,Photograph,Loie Fuller Dancing,,,,,,Artist,,Samuel Joshua Beckett,"British, Shadwell, Stepney [London] 1870–1940 Bournemouth",,"Beckett, Samuel Joshua",British,1870,1940,ca. 1900,1898,1902,Gelatin silver print,Image: 3 15/16 × 5 13/16 in. (10 × 14.7 cm),"Gilman Collection, Purchase, Mrs. Walter Annenberg and The Annenberg Foundation Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287811,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.957,false,true,287812,Photographs,Photograph,Loie Fuller Dancing,,,,,,Artist,,Samuel Joshua Beckett,"British, Shadwell, Stepney [London] 1870–1940 Bournemouth",,"Beckett, Samuel Joshua",British,1870,1940,ca. 1900,1898,1902,Gelatin silver print,Image: 3 × 4 in. (7.6 × 10.1 cm),"Gilman Collection, Purchase, Mrs. Walter Annenberg and The Annenberg Foundation Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287812,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.958,false,true,287813,Photographs,Photograph,Loie Fuller Dancing,,,,,,Artist,,Samuel Joshua Beckett,"British, Shadwell, Stepney [London] 1870–1940 Bournemouth",,"Beckett, Samuel Joshua",British,1870,1940,ca. 1900,1898,1902,Gelatin silver print,Image: 4 in. × 5 11/16 in. (10.2 × 14.5 cm),"Gilman Collection, Purchase, Mrs. Walter Annenberg and The Annenberg Foundation Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287813,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.959,false,true,287814,Photographs,Photograph,Loie Fuller Dancing,,,,,,Artist,,Samuel Joshua Beckett,"British, Shadwell, Stepney [London] 1870–1940 Bournemouth",,"Beckett, Samuel Joshua",British,1870,1940,ca. 1900,1898,1902,Gelatin silver print,Image: 3 15/16 × 5 3/8 in. (10 × 13.7 cm),"Gilman Collection, Purchase, Mrs. Walter Annenberg and The Annenberg Foundation Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287814,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.960,false,true,287815,Photographs,Photograph,Loie Fuller Dancing,,,,,,Artist,,Samuel Joshua Beckett,"British, Shadwell, Stepney [London] 1870–1940 Bournemouth",,"Beckett, Samuel Joshua",British,1870,1940,ca. 1900,1898,1902,Gelatin silver print,Image: 4 in. × 5 1/8 in. (10.2 × 13 cm),"Gilman Collection, Purchase, Mrs. Walter Annenberg and The Annenberg Foundation Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287815,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.961,false,true,287816,Photographs,Photograph,Loie Fuller Dancing,,,,,,Artist,,Samuel Joshua Beckett,"British, Shadwell, Stepney [London] 1870–1940 Bournemouth",,"Beckett, Samuel Joshua",British,1870,1940,ca. 1900,1898,1902,Gelatin silver print,Image: 5 15/16 × 4 7/16 in. (15.1 × 11.2 cm),"Gilman Collection, Purchase, Mrs. Walter Annenberg and The Annenberg Foundation Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287816,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2004.217,false,true,285424,Photographs,Photograph,"[Egyptian Obelisk, ""Cleopatra's Needle,"" in Alexandria, Egypt]",,,,,,Artist,Attributed to,Francis Frith,"British, Chesterfield, Derbyshire 1822–1898 Cannes, France",,"Frith, Francis",British,1822,1898,ca. 1870,1861,1879,Albumen silver print from glass negative,Image: 15.5 x 20.8 cm (6 1/8 x 8 3/16 in.),"Funds from various donors, 2004",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285424,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.595.1,false,true,260089,Photographs,Photograph,Distant View of the Cedars of Lebanon,,,,,,Artist,,Francis Frith,"British, Chesterfield, Derbyshire 1822–1898 Cannes, France",,"Frith, Francis",British,1822,1898,ca. 1857,1855,1859,Albumen silver print from glass negative,,"The Elisha Whittelsey Collection, The Elisha Whittelsey Fund, 1972",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260089,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.595.2,false,true,260090,Photographs,Photograph,Cedars of Lebanon,,,,,,Artist,,Francis Frith,"British, Chesterfield, Derbyshire 1822–1898 Cannes, France",,"Frith, Francis",British,1822,1898,ca. 1857,1855,1859,Albumen silver print from glass negative,Image: 15.4 x 20.7 cm (6 1/16 x 8 1/8 in.) Mount: 30.5 x 38.5 cm (12 x 15 3/16 in.),"The Elisha Whittelsey Collection, The Elisha Whittelsey Fund, 1972",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260090,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.674.1,false,true,260348,Photographs,Photograph,[Six East Indian Men],,,,,,Artist,,Francis Frith,"British, Chesterfield, Derbyshire 1822–1898 Cannes, France",,"Frith, Francis",British,1822,1898,1870s,1870,1879,Albumen silver print from glass negative,,"The Elisha Whittelsey Collection, The Elisha Whittelsey Fund, 1972",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260348,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.674.2,false,true,260349,Photographs,Photograph,Rajpoots,,,,,,Artist,,Francis Frith,"British, Chesterfield, Derbyshire 1822–1898 Cannes, France",,"Frith, Francis",British,1822,1898,1870s,1870,1879,Albumen silver print from glass negative,,"The Elisha Whittelsey Collection, The Elisha Whittelsey Fund, 1972",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260349,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.674.3,false,true,260350,Photographs,Photograph,[Three East Indian Women],,,,,,Artist,,Francis Frith,"British, Chesterfield, Derbyshire 1822–1898 Cannes, France",,"Frith, Francis",British,1822,1898,1870s,1870,1879,Albumen silver print from glass negative,,"The Elisha Whittelsey Collection, The Elisha Whittelsey Fund, 1972",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260350,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.674.4,false,true,260351,Photographs,Photograph,[Four East Indian Men],,,,,,Artist,,Francis Frith,"British, Chesterfield, Derbyshire 1822–1898 Cannes, France",,"Frith, Francis",British,1822,1898,1870s,1870,1879,Albumen silver print from glass negative,,"The Elisha Whittelsey Collection, The Elisha Whittelsey Fund, 1972",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260351,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.674.5,false,true,260352,Photographs,Photograph,Marwaree Brokers,,,,,,Artist,,Francis Frith,"British, Chesterfield, Derbyshire 1822–1898 Cannes, France",,"Frith, Francis",British,1822,1898,1870s,1870,1879,Albumen silver print from glass negative,,"The Elisha Whittelsey Collection, The Elisha Whittelsey Fund, 1972",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260352,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.674.6,false,true,260353,Photographs,Photograph,Mehmans,,,,,,Artist,,Francis Frith,"British, Chesterfield, Derbyshire 1822–1898 Cannes, France",,"Frith, Francis",British,1822,1898,1870s,1870,1879,Albumen silver print from glass negative,,"The Elisha Whittelsey Collection, The Elisha Whittelsey Fund, 1972",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260353,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1973.502.1,false,true,260730,Photographs,Photograph,"Elephanta from Water Cave, Coombe Martin Bay, Watermouth",,,,,,Artist,,Francis Frith,"British, Chesterfield, Derbyshire 1822–1898 Cannes, France",,"Frith, Francis",British,1822,1898,1870s,1870,1879,Albumen silver print from glass negative,,"The Elisha Whittelsey Collection, The Elisha Whittelsey Fund, 1973",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260730,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1973.502.4,false,true,260733,Photographs,Photograph,Watermouth,,,,,,Artist,,Francis Frith,"British, Chesterfield, Derbyshire 1822–1898 Cannes, France",,"Frith, Francis",British,1822,1898,1870s,1870,1879,Albumen silver print from glass negative,,"The Elisha Whittelsey Collection, The Elisha Whittelsey Fund, 1973",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260733,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1973.502.5,false,true,260734,Photographs,Photograph,Coombe Martin Bay,,,,,,Artist,,Francis Frith,"British, Chesterfield, Derbyshire 1822–1898 Cannes, France",,"Frith, Francis",British,1822,1898,1870s,1870,1879,Albumen silver print from glass negative,,"The Elisha Whittelsey Collection, The Elisha Whittelsey Fund, 1973",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260734,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1973.597.2,false,true,260957,Photographs,Photograph,"The Written Valley, Sinai",,,,,,Artist,,Francis Frith,"British, Chesterfield, Derbyshire 1822–1898 Cannes, France",,"Frith, Francis",British,1822,1898,ca. 1857,1855,1859,Albumen silver print from glass negative,,"The Elisha Whittelsey Collection, The Elisha Whittelsey Fund, 1973",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260957,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1973.597.3,false,true,260958,Photographs,Photograph,"Mount Hermon, The Mount of Transfiguration",,,,,,Artist,,Francis Frith,"British, Chesterfield, Derbyshire 1822–1898 Cannes, France",,"Frith, Francis",British,1822,1898,ca. 1857,1855,1859,Albumen silver print from glass negative,,"The Elisha Whittelsey Collection, The Elisha Whittelsey Fund, 1973",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260958,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1973.597.4,false,true,260959,Photographs,Photograph,Bethel,,,,,,Artist,,Francis Frith,"British, Chesterfield, Derbyshire 1822–1898 Cannes, France",,"Frith, Francis",British,1822,1898,ca. 1857,1855,1859,Albumen silver print from glass negative,,"The Elisha Whittelsey Collection, The Elisha Whittelsey Fund, 1973",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260959,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1973.597.5,false,true,260960,Photographs,Photograph,The Fountain of Jerico and Probable Site of the City,,,,,,Artist,,Francis Frith,"British, Chesterfield, Derbyshire 1822–1898 Cannes, France",,"Frith, Francis",British,1822,1898,ca. 1857,1855,1859,Albumen silver print from glass negative,,"The Elisha Whittelsey Collection, The Elisha Whittelsey Fund, 1973",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260960,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1973.597.6,false,true,260961,Photographs,Photograph,"Mount Moriah, Jerusalem, from the Well of En Rogel",,,,,,Artist,,Francis Frith,"British, Chesterfield, Derbyshire 1822–1898 Cannes, France",,"Frith, Francis",British,1822,1898,ca. 1857,1855,1859,Albumen silver print from glass negative,,"The Elisha Whittelsey Collection, The Elisha Whittelsey Fund, 1973",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260961,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1973.597.7,false,true,260962,Photographs,Photograph,"Principal Source of the Jordan, Flowing From a Cave Near Banias, Near the Site of the Northern City of Dan, the Frontier Town of Israel",,,,,,Artist,,Francis Frith,"British, Chesterfield, Derbyshire 1822–1898 Cannes, France",,"Frith, Francis",British,1822,1898,ca. 1857,1855,1859,Albumen silver print from glass negative,,"The Elisha Whittelsey Collection, The Elisha Whittelsey Fund, 1973",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260962,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -66.640.2.1,false,true,271097,Photographs,Photograph,"Wady Kardassy, Nubia",,,,,,Artist,,Francis Frith,"British, Chesterfield, Derbyshire 1822–1898 Cannes, France",,"Frith, Francis",British,1822,1898,1857,1857,1857,Albumen silver print from glass negative,,"David Hunter McAlpin Fund, 1966",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/271097,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -66.640.2.3,false,true,271098,Photographs,Photograph,"The Largest of the Cedars, Mount Lebannon",,,,,,Artist,,Francis Frith,"British, Chesterfield, Derbyshire 1822–1898 Cannes, France",,"Frith, Francis",British,1822,1898,1857,1857,1857,Albumen silver print from glass negative,,"David Hunter McAlpin Fund, 1966",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/271098,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -66.640.2.4,false,true,271100,Photographs,Photograph,"Portrait, Turkish Summer Costume",,,,,,Artist,,Francis Frith,"British, Chesterfield, Derbyshire 1822–1898 Cannes, France",,"Frith, Francis",British,1822,1898,1857,1857,1857,Albumen silver print from glass negative,,"David Hunter McAlpin Fund, 1966",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/271100,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -66.640.2.6,false,true,271101,Photographs,Photograph,"The Sphynx and Great Pyramid, Geezeh",,,,,,Artist,,Francis Frith,"British, Chesterfield, Derbyshire 1822–1898 Cannes, France",,"Frith, Francis",British,1822,1898,1857,1857,1857,Albumen silver print from glass negative,,"David Hunter McAlpin Fund, 1966",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/271101,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -66.640.2.9,false,true,271102,Photographs,Photograph,"The Mosque of Omar, Jerusalem",,,,,,Artist,,Francis Frith,"British, Chesterfield, Derbyshire 1822–1898 Cannes, France",,"Frith, Francis",British,1822,1898,1857,1857,1857,Albumen silver print from glass negative,,"David Hunter McAlpin Fund, 1966",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/271102,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -66.640.1.23,false,true,271085,Photographs,Photograph,The Approach to Phil‘,,,,,,Artist,,Francis Frith,"British, Chesterfield, Derbyshire 1822–1898 Cannes, France",,"Frith, Francis",British,1822,1898,1857,1857,1857,Albumen silver print from glass negative,,"David Hunter McAlpin Fund, 1966",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/271085,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -66.640.1.27,false,true,271086,Photographs,Photograph,The Statues of Memnon. Plain of Thebes,,,,,,Artist,,Francis Frith,"British, Chesterfield, Derbyshire 1822–1898 Cannes, France",,"Frith, Francis",British,1822,1898,1857,1857,1857,Albumen silver print from glass negative,,"David Hunter McAlpin Fund, 1966",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/271086,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -66.640.1.28,false,true,271087,Photographs,Photograph,"Nablous, The Ancient Shechem",,,,,,Artist,,Francis Frith,"British, Chesterfield, Derbyshire 1822–1898 Cannes, France",,"Frith, Francis",British,1822,1898,1857,1857,1857,Albumen silver print from glass negative,,"David Hunter McAlpin Fund, 1966",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/271087,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -66.640.1.30,false,true,271088,Photographs,Photograph,Colossi and Sphynx at Wady Saboua,,,,,,Artist,,Francis Frith,"British, Chesterfield, Derbyshire 1822–1898 Cannes, France",,"Frith, Francis",British,1822,1898,1857,1857,1857,Albumen silver print from glass negative,,"David Hunter McAlpin Fund, 1966",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/271088,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -66.640.1.46,false,true,271092,Photographs,Photograph,Damascus,,,,,,Artist,,Francis Frith,"British, Chesterfield, Derbyshire 1822–1898 Cannes, France",,"Frith, Francis",British,1822,1898,1857,1857,1857,Albumen silver print from glass negative,,"David Hunter McAlpin Fund, 1966",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/271092,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -66.640.1.53,false,true,271093,Photographs,Photograph,"Valley of the Tombs of the Kings, Thebes",,,,,,Artist,,Francis Frith,"British, Chesterfield, Derbyshire 1822–1898 Cannes, France",,"Frith, Francis",British,1822,1898,1857,1857,1857,Albumen silver print from glass negative,,"David Hunter McAlpin Fund, 1966",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/271093,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -66.640.1.54,false,true,271094,Photographs,Photograph,View at Girgeh,,,,,,Artist,,Francis Frith,"British, Chesterfield, Derbyshire 1822–1898 Cannes, France",,"Frith, Francis",British,1822,1898,1857,1857,1857,Albumen silver print from glass negative,,"David Hunter McAlpin Fund, 1966",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/271094,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -66.640.1.60,false,true,271095,Photographs,Photograph,The Statues of Memnon. Plain of Thebes,,,,,,Artist,,Francis Frith,"British, Chesterfield, Derbyshire 1822–1898 Cannes, France",,"Frith, Francis",British,1822,1898,1857,1857,1857,Albumen silver print from glass negative,,"David Hunter McAlpin Fund, 1966",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/271095,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -66.640.2.32,false,true,271099,Photographs,Photograph,"Abou Simbel, Nubia",,,,,,Artist,,Francis Frith,"British, Chesterfield, Derbyshire 1822–1898 Cannes, France",,"Frith, Francis",British,1822,1898,1857,1857,1857,Albumen silver print from glass negative,,"David Hunter McAlpin Fund, 1966",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/271099,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.555,false,true,286141,Photographs,Photographically illustrated book,The Great Pyramid and The Great Sphinx,,,,,,Artist,,Francis Frith,"British, Chesterfield, Derbyshire 1822–1898 Cannes, France",,"Frith, Francis",British,1822,1898,1858,1858,1858,Albumen silver print from glass negative,Image: 38.7 x 49.3 cm (15 1/4 x 19 7/16 in.),"Gilman Collection, Purchase, William Talbott Hillman Foundation Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286141,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.633,false,true,286142,Photographs,Photograph,"The Rameseum of El-Kurneh, Thebes",,,,,,Artist,,Francis Frith,"British, Chesterfield, Derbyshire 1822–1898 Cannes, France",,"Frith, Francis",British,1822,1898,1857,1857,1857,Albumen silver print from glass negative,Image: 37.9 x 47.7 cm (14 15/16 x 18 3/4 in.) Mount: 59.9 × 73.8 cm (23 9/16 in. × 29 1/16 in.),"Gilman Collection, Purchase, Anonymous Gifts, by exchange, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286142,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.18,false,true,283090,Photographs,Photograph,"""She Never Told Her Love""",,,,,,Artist,,Henry Peach Robinson,"British, Ludlow, Shropshire 1830–1901 Tunbridge Wells, Kent",,"Robinson, Henry Peach",British,1830,1901,1857,1857,1857,Albumen silver print from glass negative,18 x 23.2cm (7 1/16 x 9 1/8in.) Frame: 41.6 x 46.4 cm (16 3/8 x 18 1/4 in.),"Gilman Collection, Purchase, Jennifer and Joseph Duke Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/283090,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.1132,false,true,286250,Photographs,Photograph,Fear,,,,,,Artist,,Henry Peach Robinson,"British, Ludlow, Shropshire 1830–1901 Tunbridge Wells, Kent",,"Robinson, Henry Peach",British,1830,1901,ca. 1860,1858,1862,Albumen silver print,Image: 7 15/16 × 6 1/16 in. (20.2 × 15.4 cm),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286250,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.760.4,false,true,259827,Photographs,Photograph,"Carro con buoi, Perugia",,,,,,Artist,,James Anderson,"British, 1813–1877",,"Anderson, James",British,1813,1877,1880s,1880,1889,Albumen silver print,,"Museum Accession, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/259827,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.384 (1-48),false,true,285734,Photographs,Album,[Album of Spirit Photographs],,,,,,Artist|Artist,,Frederick Hudson|Mr. Reeve,"British, died 1889",,"Hudson, Frederick|Reeve",British,,1889,1872,1870,1880,Albumen silver prints from glass negatives,25.4 x 19.1 cm (10 x 7 1/2 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Albums,,http://www.metmuseum.org/art/collection/search/285734,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -46.1.53,false,true,268997,Photographs,Photograph,"Lacock Abbey, Cloisters, September 12, 1855 [?]",,,,,,Artist|Artist,Possibly by,Charles Henry Talbot|Unknown,"British, 1842–1916|British",,"Talbot, Charles Henry|Unknown",British,1842,1916,"September 12, 1855 [?]",1853,1858,Salted paper print from paper negative,,"David Hunter McAlpin Fund, 1946",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268997,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2000.134,false,true,283685,Photographs,Photograph,"Catacombs, Convento dei Cappucini, Palermo",,,,,,Photography Studio|Artist,,Eugenio Interguglielmi & Company|Eugenio Interguglielmi,"Italian, active 1895–1981|Italian, Palermo 1850–1911 Palermo",,"Interguglielmi, Eugenio & Company|Interguglielmi, Eugenio",Italian,1850,1911,ca. 1895,1895,1895,Albumen silver print from glass negative,18.6 x 25.3 cm (7 5/16 x 9 15/16 in. ),"Funds from various donors, 2000",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/283685,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1994.263.168,false,true,284874,Photographs,Print,Veduta del Tempio di Giove Tonante,,,,,,Artist,,Giovanni Battista Piranesi,"Italian, Mogliano Veneto 1720–1778 Rome",,"Piranesi, Giovanni Battista",Italian,1720,1778,1740s–60s,1740,1769,Engraving,Image: 38 x 59.5 cm (14 15/16 x 23 7/16 in.),"Walker Evans Archive, 1994",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/284874,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.1095,false,true,286681,Photographs,Photograph,Tempio di Vesta,,,,,,Artist,,Pietro Dovizielli,"Italian, 1804–1885",,"Dovizielli, Pietro",Italian,1804,1885,1850s,1850,1859,Salted paper print from paper negative,Mount: 19 3/8 in. × 27 1/16 in. (49.2 × 68.7 cm) Image: 11 9/16 × 15 1/2 in. (29.3 × 39.3 cm),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286681,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.1096,false,true,286126,Photographs,Photograph,Interno del Colosseo,,,,,,Artist,,Pietro Dovizielli,"Italian, 1804–1885",,"Dovizielli, Pietro",Italian,1804,1885,ca. 1859,1854,1864,Salted paper print from paper negative,Image: 30.4 × 38.7 cm (11 15/16 × 15 1/4 in.) Mount: 49.2 × 63.7 cm (19 3/8 × 25 1/16 in.),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286126,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.548.2,false,true,261390,Photographs,Photograph,La Simple,,,,,,Artist,,Duroni et Murer,"Italian, 1807–1870",,Duroni et Murer,Italian,1807,1870,1860s,1860,1869,Albumen silver print from glass negative,5.1 x 8.9 cm. (2 x 3 1/2 in.),"David Hunter McAlpin Fund, 1975",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261390,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -62.600.64,false,true,270616,Photographs,Photograph,[Tree in Formal Garden Outside Palazzo],,,,,,Artist,,Giacomo Caneva,"Italian, 1812–1865",,"Caneva, Giacomo",Italian,1812,1865,1860s–70s,1860,1879,Albumen silver print from glass negative,,"The Elisha Whittelsey Collection, The Elisha Whittelsey Fund, 1962",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/270616,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -62.600.68,false,true,270620,Photographs,Photograph,[Roman Ruins],,,,,,Artist,Attributed to,Giacomo Caneva,"Italian, 1812–1865",,"Caneva, Giacomo",Italian,1812,1865,1860s,1860,1869,Albumen silver print,,"The Elisha Whittelsey Collection, The Elisha Whittelsey Fund, 1962",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/270620,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.57,false,true,283140,Photographs,Photograph,[Carlotta Cortudino],,,,,,Artist,,Giacomo Caneva,"Italian, 1812–1865",,"Caneva, Giacomo",Italian,1812,1865,ca. 1852,1850,1854,Salted paper print from paper negative,Image: 5 5/16 × 7 13/16 in. (13.5 × 19.9 cm) Mount: 11 5/8 × 11 5/8 in. (29.6 × 29.5 cm),"Gilman Collection, Purchase, Joseph M. Cohen Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/283140,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.570,false,true,286574,Photographs,Photograph,[Vesuvius from Mergellina],,,,,,Artist,,Giacomo Caneva,"Italian, 1812–1865",,"Caneva, Giacomo",Italian,1812,1865,ca. 1855,1853,1857,Salted paper print from paper negative,Image: 13.1 x 27.9 cm (5 3/16 x 11 in.) Mount: 26.7 x 43.5 cm (10 1/2 x 17 1/8 in.),"Gilman Collection, Purchase, Mrs. Walter Annenberg and The Annenberg Foundation Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286574,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.855,false,true,286270,Photographs,Photograph,Veduta d'alla Villa Medici od Academia di Francia,,,,,,Artist,,Giacomo Caneva,"Italian, 1812–1865",,"Caneva, Giacomo",Italian,1812,1865,ca. 1852,1847,1857,Salted paper print from paper negative,Image: 20 × 27.2 cm (7 7/8 × 10 11/16 in.) Mount: 31 × 46.2 cm (12 3/16 × 18 3/16 in.),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286270,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.37 (45),false,true,289233,Photographs,Photograph,Primo tentativo fatto in Italia del Sig. Tassinari farmacista e chimico in Castel Bolognese,,,,,,Artist,Likely,Sebastiano Tassinari,"Italian, 1814–1888",,"Tassinari, Sebastiano",Italian,1814,1888,1839–40,1839,1840,Photogenic drawing,"10.2 x 10.5 cm (4 x 4 1/8 in.), irregularly trimmed","Harris Brisbane Dick Fund, 1936",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/289233,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.37 (44a),false,true,289232,Photographs,Photograph,[Photogenic Drawing from Leaf],,,,,,Artist,Likely,Sebastiano Tassinari,"Italian, 1814–1888",,"Tassinari, Sebastiano",Italian,1814,1888,1839–40,1839,1840,Photogenic drawing,"10.6 x 14 cm (4 3/16 x 5 1/2 in.), irregularly trimmed","Harris Brisbane Dick Fund, 1936",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/289232,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.37 (44b),false,true,289234,Photographs,Photograph,[Photogenic Drawing from Leaf],,,,,,Artist,Likely,Sebastiano Tassinari,"Italian, 1814–1888",,"Tassinari, Sebastiano",Italian,1814,1888,1839–40,1839,1840,Photogenic drawing,"10.6 x 14 cm (4 3/16 x 5 1/2 in.), irregularly trimmed","Harris Brisbane Dick Fund, 1936",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/289234,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -67.606.1,false,true,271262,Photographs,Photograph,Processione sulla facciata della grande scala,,,,,,Artist,,Luigi Pesce,"Italian, 1818–1891",,"Pesce, Luigi",Italian,1818,1891,1858,1858,1858,Salted paper print from paper negative,,"Gift of Kay Gregory, 1967",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/271262,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -67.606.2,false,true,271273,Photographs,Photograph,Processione sulla facciata della Grande Scala,,,,,,Artist,,Luigi Pesce,"Italian, 1818–1891",,"Pesce, Luigi",Italian,1818,1891,1858,1858,1858,Salted paper print from paper negative,,"Gift of Kay Gregory, 1967",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/271273,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -67.606.3,false,true,271277,Photographs,Photograph,Processione sulla facciata della Grande Scala,,,,,,Artist,,Luigi Pesce,"Italian, 1818–1891",,"Pesce, Luigi",Italian,1818,1891,1858,1858,1858,Salted paper print from paper negative,,"Gift of Kay Gregory, 1967",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/271277,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -67.606.4,false,true,271278,Photographs,Photograph,Processione sulla facciata della Grande Scala,,,,,,Artist,,Luigi Pesce,"Italian, 1818–1891",,"Pesce, Luigi",Italian,1818,1891,1858,1858,1858,Salted paper print from paper negative,,"Gift of Kay Gregory, 1967",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/271278,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -67.606.5,false,true,271279,Photographs,Photograph,Teheran,,,,,,Artist,,Luigi Pesce,"Italian, 1818–1891",,"Pesce, Luigi",Italian,1818,1891,1858,1858,1858,Salted paper print from paper negative,,"Gift of Kay Gregory, 1967",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/271279,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -67.606.6,false,true,271280,Photographs,Photograph,Amharet es Schah,,,,,,Artist,,Luigi Pesce,"Italian, 1818–1891",,"Pesce, Luigi",Italian,1818,1891,1858,1858,1858,Salted paper print from paper negative,,"Gift of Kay Gregory, 1967",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/271280,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -67.606.7,false,true,271281,Photographs,Photograph,"[Peacock's Throne Room, Teheran]",,,,,,Artist,,Luigi Pesce,"Italian, 1818–1891",,"Pesce, Luigi",Italian,1818,1891,1858,1858,1858,Salted paper print from paper negative,,"Gift of Kay Gregory, 1967",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/271281,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -67.606.8,false,true,271282,Photographs,Photograph,"Outer Entrance to the King's Palace, Teheran",,,,,,Artist,,Luigi Pesce,"Italian, 1818–1891",,"Pesce, Luigi",Italian,1818,1891,1858,1858,1858,Salted paper print from paper negative,,"Gift of Kay Gregory, 1967",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/271282,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -67.606.9,false,true,271283,Photographs,Photograph,Teheran. Prime Minister's House (Nezanneh),,,,,,Artist,,Luigi Pesce,"Italian, 1818–1891",,"Pesce, Luigi",Italian,1818,1891,1858,1858,1858,Salted paper print from paper negative,,"Gift of Kay Gregory, 1967",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/271283,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -67.606.10,false,true,271263,Photographs,Photograph,Tomb of the Khan of Khiva at Teheran,,,,,,Artist,,Luigi Pesce,"Italian, 1818–1891",,"Pesce, Luigi",Italian,1818,1891,1859,1859,1859,Albumen silver print from paper negative,,"Gift of Kay Gregory, 1967",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/271263,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -67.606.11,false,true,271264,Photographs,Photograph,"British Legation, Teheran",,,,,,Artist,,Luigi Pesce,"Italian, 1818–1891",,"Pesce, Luigi",Italian,1818,1891,1858,1858,1858,Salted paper print from paper negative,,"Gift of Kay Gregory, 1967",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/271264,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -67.606.12,false,true,271265,Photographs,Photograph,"Zerghiandeh. Russian Minister's Country House, Teheran",,,,,,Artist,,Luigi Pesce,"Italian, 1818–1891",,"Pesce, Luigi",Italian,1818,1891,1858,1858,1858,Salted paper print from paper negative,,"Gift of Kay Gregory, 1967",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/271265,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -67.606.13,false,true,271266,Photographs,Photograph,Bagh-takt a chiraz,,,,,,Artist,,Luigi Pesce,"Italian, 1818–1891",,"Pesce, Luigi",Italian,1818,1891,1858,1858,1858,Salted paper print from paper negative,,"Gift of Kay Gregory, 1967",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/271266,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -67.606.14,false,true,271267,Photographs,Photograph,"Yran, Piazza a Isphaan",,,,,,Artist,,Luigi Pesce,"Italian, 1818–1891",,"Pesce, Luigi",Italian,1818,1891,1858,1858,1858,Salted paper print from paper negative,,"Gift of Kay Gregory, 1967",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/271267,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -67.606.15,false,true,271268,Photographs,Photograph,Bassirilieve a Maksci Reste,,,,,,Artist,,Luigi Pesce,"Italian, 1818–1891",,"Pesce, Luigi",Italian,1818,1891,1858,1858,1858,Salted paper print from paper negative,,"Gift of Kay Gregory, 1967",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/271268,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -67.606.16,false,true,271269,Photographs,Photograph,"Ruine sulla prima terrazza, Persepolis",,,,,,Artist,,Luigi Pesce,"Italian, 1818–1891",,"Pesce, Luigi",Italian,1818,1891,1858,1858,1858,Salted paper print from paper negative,,"Gift of Kay Gregory, 1967",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/271269,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -67.606.17,false,true,271270,Photographs,Photograph,Tomba sulla rocca a Persepolis,,,,,,Artist,,Luigi Pesce,"Italian, 1818–1891",,"Pesce, Luigi",Italian,1818,1891,1858,1858,1858,Salted paper print from paper negative,,"Gift of Kay Gregory, 1967",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/271270,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -67.606.18,false,true,271271,Photographs,Photograph,Tombe de Ciro a Morgab,,,,,,Artist,,Luigi Pesce,"Italian, 1818–1891",,"Pesce, Luigi",Italian,1818,1891,1858,1858,1858,Salted paper print from paper negative,,"Gift of Kay Gregory, 1967",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/271271,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -67.606.19,false,true,271272,Photographs,Photograph,L'antica porta d'ingrezza a Persepolis],,,,,,Artist,,Luigi Pesce,"Italian, 1818–1891",,"Pesce, Luigi",Italian,1818,1891,1858,1858,1858,Salted paper print from paper negative,,"Gift of Kay Gregory, 1967",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/271272,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -67.606.20,false,true,271274,Photographs,Photograph,"Ruine sulla terza terazza, Persepolis",,,,,,Artist,,Luigi Pesce,"Italian, 1818–1891",,"Pesce, Luigi",Italian,1818,1891,1858,1858,1858,Salted paper print from paper negative,,"Gift of Kay Gregory, 1967",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/271274,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -67.606.21,false,true,271275,Photographs,Photograph,Porta d'entrata alla ruine de Persepolis,,,,,,Artist,,Luigi Pesce,"Italian, 1818–1891",,"Pesce, Luigi",Italian,1818,1891,1858,1858,1858,Salted paper print from paper negative,,"Gift of Kay Gregory, 1967",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/271275,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -67.606.22,false,true,271276,Photographs,Photograph,Veduta generale di Persepolis presa dalla Montagna,,,,,,Artist,,Luigi Pesce,"Italian, 1818–1891",,"Pesce, Luigi",Italian,1818,1891,1858,1858,1858,Salted paper print from paper negative,,"Gift of Kay Gregory, 1967",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/271276,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1977.683.2,false,true,652099,Photographs,Photograph,"[Golestan Palace, Interior, Teheran, Iran]",,,,,,Artist,,Luigi Pesce,"Italian, 1818–1891",,"Pesce, Luigi",Italian,1818,1891,1840s–60s,1840,1869,,,"Gift of Charles K. and Irma B. Wilkinson, 1977",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/652099,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1977.683.3,false,true,652100,Photographs,Photograph,"[Golestan, The Peacock Throne, Teheran, Iran] (Takht-I Taous)",,,,,,Artist,,Luigi Pesce,"Italian, 1818–1891",,"Pesce, Luigi",Italian,1818,1891,1840s–60s,1840,1869,,,"Gift of Charles K. and Irma B. Wilkinson, 1977",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/652100,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1977.683.4,false,true,652101,Photographs,Photograph,"[Golestan Palace, Teheran, Iran] (Takht-i Marmor)",,,,,,Artist,,Luigi Pesce,"Italian, 1818–1891",,"Pesce, Luigi",Italian,1818,1891,1840s–60s,1840,1869,,,"Gift of Charles K. and Irma B. Wilkinson, 1977",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/652101,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1977.683.5,false,true,652102,Photographs,Photograph,"[Golestan, Le Salon et Fete de l'equinode, Teheran, Iran (le Pavillion du Trone)]",,,,,,Artist,Possibly by,Luigi Pesce,"Italian, 1818–1891",,"Pesce, Luigi",Italian,1818,1891,1840s–60s,1840,1869,,,"Gift of Charles K. and Irma B. Wilkinson, 1977",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/652102,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1977.683.6,false,true,652103,Photographs,Photograph,"[Gate of Government, Teheran, Iran]",,,,,,Artist,,Luigi Pesce,"Italian, 1818–1891",,"Pesce, Luigi",Italian,1818,1891,1840s–60s,1840,1869,,,"Gift of Charles K. and Irma B. Wilkinson, 1977",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/652103,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1977.683.7,false,true,652104,Photographs,Photograph,"[Plaza of Canons, Teheran, Iran] (Maydan-i Top-khaneh)",,,,,,Artist,Possibly by,Luigi Pesce,"Italian, 1818–1891",,"Pesce, Luigi",Italian,1818,1891,1840s–60s,1840,1869,,,"Gift of Charles K. and Irma B. Wilkinson, 1977",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/652104,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1977.683.8,false,true,262204,Photographs,Photograph,"[The New Gate, Teheran]",,,,,,Artist,,Luigi Pesce,"Italian, 1818–1891",,"Pesce, Luigi",Italian,1818,1891,1850s,1850,1859,Albumen silver print from paper negative,,"Gift of Charles K. and Irma B. Wilkinson, 1977",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/262204,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1977.683.9,false,true,652106,Photographs,Photograph,"[South Gate of the Arq, Teheran, Iran]",,,,,,Artist,,Luigi Pesce,"Italian, 1818–1891",,"Pesce, Luigi",Italian,1818,1891,1840s–60s,1840,1869,,,"Gift of Charles K. and Irma B. Wilkinson, 1977",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/652106,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1977.683.10,false,true,262200,Photographs,Photograph,[Palace of the Shah],,,,,,Artist,,Luigi Pesce,"Italian, 1818–1891",,"Pesce, Luigi",Italian,1818,1891,1850s,1850,1859,Albumen silver print from paper negative,,"Gift of Charles K. and Irma B. Wilkinson, 1977",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/262200,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1977.683.11,false,true,652107,Photographs,Photograph,"[Summer residence (Qasr) of the Shah, Emarat-e xoruji, Teheran, Iran]",,,,,,Artist,Possibly by,Luigi Pesce,"Italian, 1818–1891",,"Pesce, Luigi",Italian,1818,1891,1840s–60s,1840,1869,,,"Gift of Charles K. and Irma B. Wilkinson, 1977",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/652107,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1977.683.12,false,true,652108,Photographs,Photograph,"[Palace of the Shah, Teheran, Iran]",,,,,,Artist,Possibly by,Luigi Pesce,"Italian, 1818–1891",,"Pesce, Luigi",Italian,1818,1891,1840s–60s,1840,1869,,,"Gift of Charles K. and Irma B. Wilkinson, 1977",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/652108,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1977.683.13,false,true,652109,Photographs,Photograph,"[Palace of the Shah, Paying respects to the Shah/Fete de Salam, Teheran, Iran [same as 12] ]",,,,,,Artist,Possibly by,Luigi Pesce,"Italian, 1818–1891",,"Pesce, Luigi",Italian,1818,1891,1840s–60s,1840,1869,,,"Gift of Charles K. and Irma B. Wilkinson, 1977",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/652109,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1977.683.14,false,true,652110,Photographs,Photograph,"[Palace of the Shah, Teheran, Iran]",,,,,,Artist,Possibly by,Luigi Pesce,"Italian, 1818–1891",,"Pesce, Luigi",Italian,1818,1891,1840s–60s,1840,1869,,,"Gift of Charles K. and Irma B. Wilkinson, 1977",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/652110,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1977.683.15,false,true,652111,Photographs,Photograph,"[The Sublime Porte, Teheran, Iran]",,,,,,Artist,,Luigi Pesce,"Italian, 1818–1891",,"Pesce, Luigi",Italian,1818,1891,1840s–60s,1840,1869,,,"Gift of Charles K. and Irma B. Wilkinson, 1977",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/652111,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1977.683.16,false,true,652112,Photographs,Photograph,"[Mosque of Nasser-eddin Shah, Teheran, Iran]",,,,,,Artist,,Luigi Pesce,"Italian, 1818–1891",,"Pesce, Luigi",Italian,1818,1891,1840s–60s,1840,1869,,,"Gift of Charles K. and Irma B. Wilkinson, 1977",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/652112,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1977.683.17,false,true,652113,Photographs,Photograph,"[Tomb of Khan of Khiva, Uzbekistan]",,,,,,Artist,Possibly by,Luigi Pesce,"Italian, 1818–1891",,"Pesce, Luigi",Italian,1818,1891,1840s–60s,1840,1869,,,"Gift of Charles K. and Irma B. Wilkinson, 1977",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/652113,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1977.683.19,false,true,652115,Photographs,Photograph,[Departure for the huntTomb of Khan of Khiva],,,,,,Artist,Possibly by,Luigi Pesce,"Italian, 1818–1891",,"Pesce, Luigi",Italian,1818,1891,1840s–60s,1840,1869,Salted paper print,,"Gift of Charles K. and Irma B. Wilkinson, 1977",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/652115,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1977.683.20,false,true,652116,Photographs,Photograph,[Armenian Woman of Teheran],,,,,,Artist,Possibly by,Luigi Pesce,"Italian, 1818–1891",,"Pesce, Luigi",Italian,1818,1891,1840s–60s,1840,1869,Albumen silver print,,"Gift of Charles K. and Irma B. Wilkinson, 1977",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/652116,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1977.683.21,false,true,262201,Photographs,Photograph,[In the Mosque of the Damegan/The Eunuchs],,,,,,Artist,Possibly by,Luigi Pesce,"Italian, 1818–1891",,"Pesce, Luigi",Italian,1818,1891,1850s,1850,1859,Albumen silver print from paper negative,16.3 x 23.2 cm. (6 7/16 x 9 1/8 in.),"Gift of Charles K. and Irma B. Wilkinson, 1977",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/262201,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1977.683.25,false,true,652118,Photographs,Photograph,"Portrait of Ardeshir Mirza, uncle of the king",,,,,,Artist,Possibly by,Luigi Pesce,"Italian, 1818–1891",,"Pesce, Luigi",Italian,1818,1891,1840s–60s,1840,1869,Salted paper print,,"Gift of Charles K. and Irma B. Wilkinson, 1977",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/652118,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1977.683.26,false,true,652119,Photographs,Photograph,"[A Persian revue in a painting that once belonged to Ardeshir Mirza, uncle of the king.]",,,,,,Artist,Possibly by,Luigi Pesce,"Italian, 1818–1891",,"Pesce, Luigi",Italian,1818,1891,1840s–60s,1840,1869,Albumen silver print,,"Gift of Charles K. and Irma B. Wilkinson, 1977",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/652119,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1977.683.27,false,true,652120,Photographs,Photograph,"[Fath-Ali Shah, Painting that Once Belonged to Hmah [?] Saula, Uncle of the King.]",,,,,,Artist,Possibly by,Luigi Pesce,"Italian, 1818–1891",,"Pesce, Luigi",Italian,1818,1891,1840s–60s,1840,1869,Albumen silver print,,"Gift of Charles K. and Irma B. Wilkinson, 1977",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/652120,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1977.683.28,false,true,652121,Photographs,Photograph,"[RAYY, Tower of Toghrul, 1139.]",,,,,,Artist,Possibly by,Luigi Pesce,"Italian, 1818–1891",,"Pesce, Luigi",Italian,1818,1891,1840s–60s,1840,1869,Albumen silver print,,"Gift of Charles K. and Irma B. Wilkinson, 1977",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/652121,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1977.683.29,false,true,652122,Photographs,Photograph,[A General View of MESHED from the roof of a hamam.],,,,,,Artist,Possibly by,Luigi Pesce,"Italian, 1818–1891",,"Pesce, Luigi",Italian,1818,1891,1840s–60s,1840,1869,Albumen silver print,,"Gift of Charles K. and Irma B. Wilkinson, 1977",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/652122,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1977.683.30,false,true,652123,Photographs,Photograph,[Principal Gate of MESHED],,,,,,Artist,Possibly by,Luigi Pesce,"Italian, 1818–1891",,"Pesce, Luigi",Italian,1818,1891,1840s–60s,1840,1869,Albumen silver print,,"Gift of Charles K. and Irma B. Wilkinson, 1977",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/652123,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1977.683.31,false,true,652124,Photographs,Photograph,"[New Court of Imam Riza, MESHED]",,,,,,Artist,Possibly by,Luigi Pesce,"Italian, 1818–1891",,"Pesce, Luigi",Italian,1818,1891,1840s–60s,1840,1869,Albumen silver print,,"Gift of Charles K. and Irma B. Wilkinson, 1977",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/652124,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1977.683.32,false,true,302221,Photographs,Photograph,"[Main Gate of Imam Riza, Mashhad, Iran]",,,,,,Artist,Possibly by,Luigi Pesce,"Italian, 1818–1891",,"Pesce, Luigi",Italian,1818,1891,1850s,1850,1859,Albumen silver print from paper negative,Image: 16.8 x 21.1 cm (6 5/8 x 8 5/16 in.) Sheet: 17.2 x 21.1 cm (6 3/4 x 8 5/16 in.),"Gift of Charles K. and Irma B. Wilkinson, 1977",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/302221,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1977.683.33,false,true,652126,Photographs,Photograph,[Old Court of Imam Riza MESHED],,,,,,Artist,Possibly by,Luigi Pesce,"Italian, 1818–1891",,"Pesce, Luigi",Italian,1818,1891,1840s–60s,1840,1869,Albumen silver print,,"Gift of Charles K. and Irma B. Wilkinson, 1977",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/652126,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1977.683.34,false,true,652127,Photographs,Photograph,"[Court of the mosque Gawhar Shad, MESHED, 1418 (?)]",,,,,,Artist,Possibly by,Luigi Pesce,"Italian, 1818–1891",,"Pesce, Luigi",Italian,1818,1891,1840s–60s,1840,1869,Albumen silver print,,"Gift of Charles K. and Irma B. Wilkinson, 1977",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/652127,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1977.683.35,false,true,652128,Photographs,Photograph,"[Nadir Shah's Golden Gate and Minaret. Otherwise known as the Golden Iwan of Ali Shir Nawai, late 15th Century with Restorations. MESHED]",,,,,,Artist,Possibly by,Luigi Pesce,"Italian, 1818–1891",,"Pesce, Luigi",Italian,1818,1891,1840s–60s,1840,1869,Albumen silver print,,"Gift of Charles K. and Irma B. Wilkinson, 1977",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/652128,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1977.683.36,false,true,652129,Photographs,Photograph,[Mosque of the Shah],,,,,,Artist,Possibly by,Luigi Pesce,"Italian, 1818–1891",,"Pesce, Luigi",Italian,1818,1891,1840s–60s,1840,1869,Albumen silver print,,"Gift of Charles K. and Irma B. Wilkinson, 1977",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/652129,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1977.683.37,false,true,652130,Photographs,Photograph,[Tomb of Kogin Baba],,,,,,Artist,Possibly by,Luigi Pesce,"Italian, 1818–1891",,"Pesce, Luigi",Italian,1818,1891,1840s–60s,1840,1869,Albumen silver print,,"Gift of Charles K. and Irma B. Wilkinson, 1977",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/652130,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1977.683.38,false,true,652131,Photographs,Photograph,[Tomb of Seeh-i Mumin],,,,,,Artist,Possibly by,Luigi Pesce,"Italian, 1818–1891",,"Pesce, Luigi",Italian,1818,1891,1840s–60s,1840,1869,Albumen silver print,,"Gift of Charles K. and Irma B. Wilkinson, 1977",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/652131,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1977.683.39,false,true,652132,Photographs,Photograph,"[Bastam, Tomb Tower (built 1313), Khorasan]",,,,,,Artist,Possibly by,Luigi Pesce,"Italian, 1818–1891",,"Pesce, Luigi",Italian,1818,1891,1840s–60s,1840,1869,Albumen silver print,,"Gift of Charles K. and Irma B. Wilkinson, 1977",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/652132,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1977.683.40,false,true,652133,Photographs,Photograph,"[Tomb of Bayazid, BISTAM]",,,,,,Artist,Possibly by,Luigi Pesce,"Italian, 1818–1891",,"Pesce, Luigi",Italian,1818,1891,1840s–60s,1840,1869,Albumen silver print,,"Gift of Charles K. and Irma B. Wilkinson, 1977",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/652133,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1977.683.41,false,true,652134,Photographs,Photograph,[Cemetry of MESHED],,,,,,Artist,Possibly by,Luigi Pesce,"Italian, 1818–1891",,"Pesce, Luigi",Italian,1818,1891,1840s–60s,1840,1869,Albumen silver print,,"Gift of Charles K. and Irma B. Wilkinson, 1977",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/652134,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1977.683.42,false,true,652135,Photographs,Photograph,[View of TABRIZ],,,,,,Artist,Possibly by,Luigi Pesce,"Italian, 1818–1891",,"Pesce, Luigi",Italian,1818,1891,1840s–60s,1840,1869,Albumen silver print,,"Gift of Charles K. and Irma B. Wilkinson, 1977",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/652135,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1977.683.43,false,true,652136,Photographs,Photograph,"[Blue Mosque of TABRIZ, 1465.]",,,,,,Artist,Possibly by,Luigi Pesce,"Italian, 1818–1891",,"Pesce, Luigi",Italian,1818,1891,1840s–60s,1840,1869,Albumen silver print,,"Gift of Charles K. and Irma B. Wilkinson, 1977",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/652136,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1977.683.44,false,true,652137,Photographs,Photograph,[A Persian Citadel in the Environs of Sultaniye],,,,,,Artist,Possibly by,Luigi Pesce,"Italian, 1818–1891",,"Pesce, Luigi",Italian,1818,1891,1840s–60s,1840,1869,Albumen silver print,,"Gift of Charles K. and Irma B. Wilkinson, 1977",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/652137,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1977.683.45,false,true,652138,Photographs,Photograph,"[Tomb of Oljaetu, 1305-1313.]",,,,,,Artist,Possibly by,Luigi Pesce,"Italian, 1818–1891",,"Pesce, Luigi",Italian,1818,1891,1840s–60s,1840,1869,Albumen silver print,,"Gift of Charles K. and Irma B. Wilkinson, 1977",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/652138,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1977.683.46,false,true,652139,Photographs,Photograph,[Mosque at Sultaniye],,,,,,Artist,Possibly by,Luigi Pesce,"Italian, 1818–1891",,"Pesce, Luigi",Italian,1818,1891,1840s–60s,1840,1869,Albumen silver print,,"Gift of Charles K. and Irma B. Wilkinson, 1977",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/652139,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1977.683.47,false,true,652140,Photographs,Photograph,"[Mosque at Sultaniye, [same as 46] ]",,,,,,Artist,Possibly by,Luigi Pesce,"Italian, 1818–1891",,"Pesce, Luigi",Italian,1818,1891,1840s–60s,1840,1869,Albumen silver print,,"Gift of Charles K. and Irma B. Wilkinson, 1977",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/652140,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1977.683.48,false,true,652141,Photographs,Photograph,"[View of Kermanshah, Capital of Kurdistan]",,,,,,Artist,Possibly by,Luigi Pesce,"Italian, 1818–1891",,"Pesce, Luigi",Italian,1818,1891,1840s–60s,1840,1869,Salted paper print,,"Gift of Charles K. and Irma B. Wilkinson, 1977",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/652141,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1977.683.49,false,true,652142,Photographs,Photograph,[View of Koum],,,,,,Artist,Possibly by,Luigi Pesce,"Italian, 1818–1891",,"Pesce, Luigi",Italian,1818,1891,1840s–60s,1840,1869,Albumen silver print,,"Gift of Charles K. and Irma B. Wilkinson, 1977",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/652142,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1977.683.50,false,true,652143,Photographs,Photograph,[Mosque of Koum],,,,,,Artist,Possibly by,Luigi Pesce,"Italian, 1818–1891",,"Pesce, Luigi",Italian,1818,1891,1840s–60s,1840,1869,Albumen silver print,,"Gift of Charles K. and Irma B. Wilkinson, 1977",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/652143,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1977.683.51,false,true,652144,Photographs,Photograph,[The Bridge at Dizfoul],,,,,,Artist,Possibly by,Luigi Pesce,"Italian, 1818–1891",,"Pesce, Luigi",Italian,1818,1891,1840s–60s,1840,1869,Salted paper print,,"Gift of Charles K. and Irma B. Wilkinson, 1977",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/652144,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1977.683.52,false,true,652145,Photographs,Photograph,"[Ruins, Dizfoul]",,,,,,Artist,Possibly by,Luigi Pesce,"Italian, 1818–1891",,"Pesce, Luigi",Italian,1818,1891,1840s–60s,1840,1869,Albumen silver print,,"Gift of Charles K. and Irma B. Wilkinson, 1977",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/652145,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1977.683.53,false,true,652146,Photographs,Photograph,"[The Tower of 'Chihil Dukhtaran', Mausoleum of 40 daughters, 1056.]",,,,,,Artist,Possibly by,Luigi Pesce,"Italian, 1818–1891",,"Pesce, Luigi",Italian,1818,1891,1840s–60s,1840,1869,Albumen silver print,,"Gift of Charles K. and Irma B. Wilkinson, 1977",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/652146,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1977.683.54,false,true,652147,Photographs,Photograph,"[Minaret of the Mosque of 40 Columns, Chehel Dokhtar, 359b.]",,,,,,Artist,Possibly by,Luigi Pesce,"Italian, 1818–1891",,"Pesce, Luigi",Italian,1818,1891,1840s–60s,1840,1869,Albumen silver print,,"Gift of Charles K. and Irma B. Wilkinson, 1977",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/652147,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1977.683.55,false,true,652148,Photographs,Photograph,"[Minaret of the Chief Mosque at Damghan, 1026–1029]",,,,,,Artist,Possibly by,Luigi Pesce,"Italian, 1818–1891",,"Pesce, Luigi",Italian,1818,1891,1840s–60s,1840,1869,Albumen silver print,,"Gift of Charles K. and Irma B. Wilkinson, 1977",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/652148,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1977.683.56,false,true,652149,Photographs,Photograph,"[Tag-e bustan, Kermanshah, Kurdestan]",,,,,,Artist,Possibly by,Luigi Pesce,"Italian, 1818–1891",,"Pesce, Luigi",Italian,1818,1891,1840s–60s,1840,1869,Albumen silver print,,"Gift of Charles K. and Irma B. Wilkinson, 1977",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/652149,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1977.683.57,false,true,652150,Photographs,Photograph,"[Ruins of Tus, Khorasan]",,,,,,Artist,Possibly by,Luigi Pesce,"Italian, 1818–1891",,"Pesce, Luigi",Italian,1818,1891,1840s–60s,1840,1869,Albumen silver print,,"Gift of Charles K. and Irma B. Wilkinson, 1977",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/652150,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1977.683.58,false,true,652151,Photographs,Photograph,"[Other ruins in the town of Tus, Khorasan]",,,,,,Artist,Possibly by,Luigi Pesce,"Italian, 1818–1891",,"Pesce, Luigi",Italian,1818,1891,1840s–60s,1840,1869,Albumen silver print,,"Gift of Charles K. and Irma B. Wilkinson, 1977",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/652151,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1977.683.59,false,true,652152,Photographs,Photograph,"(1) [Tag-e Bustan, Kermanshah]",,,,,,Artist,,Luigi Pesce,"Italian, 1818–1891",,"Pesce, Luigi",Italian,1818,1891,1840s–60s,1840,1869,Albumen silver print,,"Gift of Charles K. and Irma B. Wilkinson, 1977",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/652152,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1977.683.60,false,true,652153,Photographs,Photograph,(2) [Persepolis],,,,,,Artist,,Luigi Pesce,"Italian, 1818–1891",,"Pesce, Luigi",Italian,1818,1891,1840s–60s,1840,1869,Albumen silver print,,"Gift of Charles K. and Irma B. Wilkinson, 1977",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/652153,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1977.683.61,false,true,652154,Photographs,Photograph,(3) [Persepolis (?)],,,,,,Artist,,Luigi Pesce,"Italian, 1818–1891",,"Pesce, Luigi",Italian,1818,1891,1840s–60s,1840,1869,Albumen silver print,,"Gift of Charles K. and Irma B. Wilkinson, 1977",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/652154,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1977.683.62,false,true,652155,Photographs,Photograph,"(4) [Naksh-i Rustam, Near Persepolis]",,,,,,Artist,,Luigi Pesce,"Italian, 1818–1891",,"Pesce, Luigi",Italian,1818,1891,1840s–60s,1840,1869,Albumen silver print,,"Gift of Charles K. and Irma B. Wilkinson, 1977",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/652155,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1977.683.63,false,true,652156,Photographs,Photograph,(5) [Persepolis],,,,,,Artist,,Luigi Pesce,"Italian, 1818–1891",,"Pesce, Luigi",Italian,1818,1891,1840s–60s,1840,1869,Albumen silver print,,"Gift of Charles K. and Irma B. Wilkinson, 1977",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/652156,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1977.683.64,false,true,652157,Photographs,Photograph,"(6) [Naksh-i Rustam, Near Persepolis]",,,,,,Artist,,Luigi Pesce,"Italian, 1818–1891",,"Pesce, Luigi",Italian,1818,1891,1840s–60s,1840,1869,Albumen silver print,,"Gift of Charles K. and Irma B. Wilkinson, 1977",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/652157,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1977.683.65,false,true,652158,Photographs,Photograph,"(7) [Tag-e Bustan, Crowning Ceremony of Shapour II, Kermanshah]",,,,,,Artist,,Luigi Pesce,"Italian, 1818–1891",,"Pesce, Luigi",Italian,1818,1891,1840s–60s,1840,1869,Albumen silver print,,"Gift of Charles K. and Irma B. Wilkinson, 1977",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/652158,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1977.683.67,false,true,652160,Photographs,Photograph,(9) Untitled,,,,,,Artist,,Luigi Pesce,"Italian, 1818–1891",,"Pesce, Luigi",Italian,1818,1891,1840s–60s,1840,1869,Albumen silver print,,"Gift of Charles K. and Irma B. Wilkinson, 1977",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/652160,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1977.683.68,false,true,652161,Photographs,Photograph,"(10) [Gate of all Nations, Persepolis, Fars]",,,,,,Artist,,Luigi Pesce,"Italian, 1818–1891",,"Pesce, Luigi",Italian,1818,1891,1840s–60s,1840,1869,Albumen silver print,,"Gift of Charles K. and Irma B. Wilkinson, 1977",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/652161,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1977.683.69,false,true,652162,Photographs,Photograph,(11) [Naksh-i Rustam],,,,,,Artist,,Luigi Pesce,"Italian, 1818–1891",,"Pesce, Luigi",Italian,1818,1891,1840s–60s,1840,1869,Albumen silver print,,"Gift of Charles K. and Irma B. Wilkinson, 1977",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/652162,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1977.683.70,false,true,652163,Photographs,Photograph,"(12) [Persepolis, (W: before restoration)",,,,,,Artist,,Luigi Pesce,"Italian, 1818–1891",,"Pesce, Luigi",Italian,1818,1891,1840s–60s,1840,1869,Albumen silver print,,"Gift of Charles K. and Irma B. Wilkinson, 1977",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/652163,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1977.683.71,false,true,652164,Photographs,Photograph,(13) [Persepolis],,,,,,Artist,,Luigi Pesce,"Italian, 1818–1891",,"Pesce, Luigi",Italian,1818,1891,1840s–60s,1840,1869,Albumen silver print,,"Gift of Charles K. and Irma B. Wilkinson, 1977",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/652164,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1977.683.72,false,true,262203,Photographs,Photograph,[Persepolis],,,,,,Artist,,Luigi Pesce,"Italian, 1818–1891",,"Pesce, Luigi",Italian,1818,1891,1850s,1850,1859,Albumen silver print from paper negative,,"Gift of Charles K. and Irma B. Wilkinson, 1977",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/262203,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1977.683.73,false,true,652165,Photographs,Photograph,"(15) [Gate of all Nations, Persepolis, Fars]",,,,,,Artist,,Luigi Pesce,"Italian, 1818–1891",,"Pesce, Luigi",Italian,1818,1891,1840s–60s,1840,1869,Albumen silver print,,"Gift of Charles K. and Irma B. Wilkinson, 1977",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/652165,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1977.683.74,false,true,652166,Photographs,Photograph,"(16) [Apadana Hall Eastern Stairway, Persepolis, Fars]",,,,,,Artist,Possibly by,Luigi Pesce,"Italian, 1818–1891",,"Pesce, Luigi",Italian,1818,1891,1840s–60s,1840,1869,Albumen silver print,,"Gift of Charles K. and Irma B. Wilkinson, 1977",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/652166,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1977.683.75,false,true,652167,Photographs,Photograph,(17) Untitled,,,,,,Artist,,Luigi Pesce,"Italian, 1818–1891",,"Pesce, Luigi",Italian,1818,1891,1840s–60s,1840,1869,Albumen silver print,,"Gift of Charles K. and Irma B. Wilkinson, 1977",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/652167,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1977.683.76,false,true,652168,Photographs,Photograph,"(18) [Inscription, Old Persian in Cuneiform]",,,,,,Artist,,Luigi Pesce,"Italian, 1818–1891",,"Pesce, Luigi",Italian,1818,1891,1840s–60s,1840,1869,Albumen silver print,,"Gift of Charles K. and Irma B. Wilkinson, 1977",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/652168,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1987.1100.456,false,true,265528,Photographs,Photograph,"[Shirtless Man with Arm Raised in Fascist Salute with Superimposed Cross, Circle, and Spray of Flowers]",,,,,,Artist,,Mario Castagneri,"Italian, 1892–1940",,"Castagneri, Mario",Italian,1892,1940,ca. 1930,1928,1932,Gelatin silver print,23.0 x 28.8 cm (9 1/16 x 11 5/16 in.),"Ford Motor Company Collection, Gift of Ford Motor Company and John C. Waddell, 1987",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/265528,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1987.1100.43,false,true,265498,Photographs,Photograph,"Stairs, Mexico City",,,,,,Artist,,Tina Modotti,"Italian, 1896–1942",,"Modotti, Tina",Italian,1896,1942,1924–26,1924,1926,Gelatin silver print,18.4 x 23.9 cm (7 1/4 x 9 7/16 in.),"Ford Motor Company Collection, Gift of Ford Motor Company and John C. Waddell, 1987",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/265498,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.657,false,true,285802,Photographs,Carte-de-visite,Vittorio Emanuele II,,,,,,Artist,,Cesare Bernieri,"Italian, active Turin 1861–70",,"Bernieri, Cesare",Italian,1861,1870,1867,1867,1867,Albumen silver print from glass negative,Image: 6.2 x 4.7 cm (2 7/16 x 1 7/8 in.) oval Sheet: 10.1 x 5.9 cm (4 x 2 5/16 in.) Mount: 10.6 x 6.4 cm (4 3/16 x 2 1/2 in.) Mat: 21.9 x 15.7 cm (8 5/8 x 6 3/16 in.) Case: 22.9 x 17.1 x 1.3 cm (9 x 6 3/4 x 1/2 in.),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285802,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1981.1194.1,false,true,263104,Photographs,Photograph,"Medinet Habou, 2me cour cote sudouest",,,,,,Artist,,Antonio Beato,"British, born Corfu, 1834–1906",,"Beato, Antonio",Italian,1834,1906,1870s,1870,1879,Albumen silver print from glass negative,,"Gift of Mrs. John L. Swayze, 1981",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/263104,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1981.1194.2,false,true,263115,Photographs,Photograph,"Luxor, vue du temple cote ouest",,,,,,Artist,,Antonio Beato,"British, born Corfu, 1834–1906",,"Beato, Antonio",Italian,1834,1906,1870s,1870,1879,Albumen silver print from glass negative,,"Gift of Mrs. John L. Swayze, 1981",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/263115,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1981.1194.3,false,true,263126,Photographs,Photograph,Temple a Denderah,,,,,,Artist,,Antonio Beato,"British, born Corfu, 1834–1906",,"Beato, Antonio",Italian,1834,1906,1870s,1870,1879,Albumen silver print from glass negative,,"Gift of Mrs. John L. Swayze, 1981",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/263126,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.562,false,true,286136,Photographs,Photograph,Interno del Colosseo,,,,,,Artist,,Giovanni Battista Altadonna,"Italian, Borgo Valsugana 1824–1890 Trento",,"Altadonna, Giovanni Battista",Italian,1824,1890,1850s,1850,1859,Albumen silver print from glass negative,"Image: 19.1 x 24.9 cm (7 1/2 x 9 13/16 in.), oval Mount: 26.8 x 35.7 cm (10 9/16 x 14 1/16 in.)","Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286136,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.633.148,false,true,260157,Photographs,Photomechanical print,Minuet,,,,,,Artist|Printer,,"Frank Eugene|F. Bruckmann Verlag, Munich","American, New York 1865–1936 Munich",,"Eugene, Frank|F. Bruckmann Verlag, Munich",American,1865,1936,"1900, printed 1909",1900,1900,Photogravure,12.7 x 17.7 cm (5 x 7 in.),"Rogers Fund, 1972",,,,,,,,,,,,Photographs|Prints,,http://www.metmuseum.org/art/collection/search/260157,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.46,false,true,301949,Photographs,"Photograph, corsage",[Mourning Corsage with Portrait of Abraham Lincoln],,,,,,Maker|Photography Studio,After,Unknown|Brady & Co.,"American|American, active 1840s–1880s",,Unknown|Brady & Co.,American,1840,1889,April 1865,1865,1865,Black and white silk with tintype set inside brass button,20 x 9 cm (7 7/8 x 3 9/16 in.) Image: 2 x 2 cm (13/16 x 13/16 in.),"Purchase, Alfred Stieglitz Society Gifts, 2013",,,,,,,,,,,,Assemblages,,http://www.metmuseum.org/art/collection/search/301949,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.633.176,false,true,260188,Photographs,Postcard,"Luitpold u. Albrecht, Kgl. Prinzen v. Bayern",,,,,,Artist|Printer,,Frank Eugene|Böhm Publishers,"American, New York 1865–1936 Munich",,"Eugene, Frank|Böhm Publishers",American,1865,1936,1908,1908,1908,Halftone,,"Rogers Fund, 1972",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/260188,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.48,false,true,268239,Photographs,Photograph,"Winter Quarters, Fort Brady",,,,,,Former Attribution,Formerly attributed to,William Frank Browne,American,,"Browne, William Frank",American,,1867,ca. 1865,1863,1867,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268239,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.737.11,false,true,260387,Photographs,Photograph,"Abandoned Farm in the Dustbowl, Coldwater District, near Dalhart, Texas, June",,,,,,Artist|Printer,,Dorothea Lange|Library of Congress,"American, 1895–1965",,"Lange, Dorothea|Library Of Congress",American,1895,1965,"1938, printed ca. 1972",1938,1938,Gelatin silver print,,"Gift of Phyllis D. Massar, 1972",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260387,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1987.1100.260,false,true,265311,Photographs,Photograph,"[Open-Air Barber at Work and Waiting Customers before Mileston, Mississippi, Post Office]",,,,,,Artist|Printer,,Marion Post Wolcott|F.S.A. studio,"American, 1910–1990",,"Wolcott, Marion Post|F.S.A. studio",American,1910,1990,1939,1939,1939,Gelatin silver print,25.0 x 32.1 cm (9 13/16 x 12 5/8 in.),"Ford Motor Company Collection, Gift of Ford Motor Company and John C. Waddell, 1987",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/265311,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.117,false,true,685459,Photographs,Carte-de-visite,[Charles Calverley],,,,,,Photography Studio,,Thompson Gallery,"American, active 1860s",,Thompson Gallery,American,1860,1869,1864–66,1864,1866,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/685459,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.263,false,true,685604,Photographs,Carte-de-visite,[Elliott and Palmer ?],,,,,,Photography Studio,,Thompson Gallery,"American, active 1860s",,Thompson Gallery,American,1860,1869,1864–66,1864,1866,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/685604,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.759,false,true,686098,Photographs,Carte-de-visite,[James Suydam],,,,,,Photography Studio,,Rintoul & Rockwood,"American, active 1860s",,Rintoul & Rockwood,American,1859,1870,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/686098,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.133,false,true,305831,Photographs,Photomontage; Cabinet card,[Man Cutting Watermelon],,,,,,Photography Studio,,Johnson,"American, active 1898–1903",,Johnson,American,1898,1903,1898–1903,1898,1903,Carbon print,Image: 9.4 × 13.2 cm (3 11/16 × 5 3/16 in.) Mount: 10.9 × 16.6 cm (4 5/16 × 6 9/16 in.),"Purchase, Greenwich ART Group Gift, 2013",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/305831,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.55,false,true,301993,Photographs,Photograph,[Major General William Tecumseh Sherman Wearing Mourning Armband],,,,,,Photography Studio,,Brady & Co.,"American, active 1840s–1880s",,Brady & Co.,American,1840,1889,1865,1865,1865,Albumen silver print from glass negative,Image: 8.5 x 5.4 cm (3 3/8 x 2 1/8 in.) Mount: 10.2 x 6.1 cm (4 x 2 3/8 in.),"Purchase, Alfred Stieglitz Society Gifts, 2013",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/301993,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.86,false,true,283187,Photographs,Photograph,[Senator and Mrs. James Henry Lane],,,,,,Photography Studio,,Brady & Co.,"American, active 1840s–1880s",,Brady & Co.,American,1840,1889,1861–66,1861,1866,Albumen silver print from glass negative,22.8 × 19.7 cm (9 × 7 3/4 in.),"Gilman Collection, Purchase, Alfred Stieglitz Society Gifts, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/283187,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.98,false,true,283205,Photographs,Photograph,Relics of Andersonville Prison,,,,,,Photography Studio,,Brady & Co.,"American, active 1840s–1880s",,Brady & Co.,American,1840,1889,June 1866,1866,1866,Albumen silver print from glass negative,Image:22.1 x 18.9cm (8 11/16 x 7 7/16in.) Mount: 13 9/16 × 10 9/16 in. (34.5 × 26.9 cm),"Gilman Collection, Purchase, Joseph M. Cohen Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/283205,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.400.22,false,true,291775,Photographs,Daguerreotype,[Bearded Man],,,,,,Photography Studio,,Brady & Co.,"American, active 1840s–1880s",,Brady & Co.,American,1840,1889,1853–57,1853,1857,Daguerreotype,Image: 9.2 x 6.8 cm (3 5/8 x 2 11/16 in.) Plate: 10.8 x 8.3 cm (4 1/4 x 3 1/4 in.) Case: 1.9 x 12.1 x 9.5 cm (3/4 x 4 3/4 x 3 3/4 in.),"Bequest of Herbert Mitchell, 2008",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/291775,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.400.77,false,true,291830,Photographs,Daguerreotype,Francis Alofsen,,,,,,Photography Studio,,Brady & Co.,"American, active 1840s–1880s",,Brady & Co.,American,1840,1889,1855,1855,1855,Daguerreotype,Image: 7 x 5.8 cm (2 3/4 x 2 5/16 in.) Plate: 8.3 x 7 cm (3 1/4 x 2 3/4 in.) Case: 2.1 x 9.4 x 8.3 cm (13/16 x 3 11/16 x 3 1/4 in.),"Bequest of Herbert Mitchell, 2008",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/291830,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.633.77,false,true,260289,Photographs,Photograph,Minuet,,,,,,Artist|Printer,,"Frank Eugene|F. Bruckmann Verlag, Munich","American, New York 1865–1936 Munich",,"Eugene, Frank|F. Bruckmann Verlag, Munich",American,1865,1936,"1900, printed 1909",1900,1900,Photogravure,12.7 x 17.7 cm (5 x 7 in.),"Rogers Fund, 1972",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260289,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.58,false,true,302000,Photographs,Carte-de-visite,[Young Woman with Beckers Tabletop Stereoscope],,,,,,Photography Studio,,Porter Photograph Parlors,"American, active Janesville, Wisconsin, 1860s",,Porter Photograph Parlors,American,1860,1869,1864–66,1864,1866,Albumen silver print from glass negative,Image: 9.1 x 5.2 cm (3 9/16 x 2 1/16 in.),"Purchase, Alfred Stieglitz Society Gifts, 2013",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/302000,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2012.12,false,true,299481,Photographs,Medal; Button,[Presidential Campaign Medal with portraits of Abraham Lincoln and Hannibal Hamlin],,,,,,Maker|Artist,After,Unknown|Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Unknown|Brady, Mathew B.",American,1823,1896,1860,1860,1860,Tintype,"Image: 1.4 cm (9/16 in.), diameter Overall: 2.4 cm (15/16 in.), diameter","Purchase, The Overbrook Foundation Gift, 2012",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/299481,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.1,false,true,267879,Photographs,Photograph,"U.S. Monitor Onondaga, James River",,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267879,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.2,false,true,267990,Photographs,Photograph,"Co. B, 30th Pennsylvania Infantry",,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267990,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.4,false,true,268211,Photographs,Photograph,"Company C, 9th Indiana Infantry (Sherman's Veterans)",,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268211,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.6,false,true,268252,Photographs,Photograph,"Potomac Creek Railroad Bridge, A.C. & F. Railroad",,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268252,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.9,false,true,268285,Photographs,Photograph,"Ironclad fleet on James River below Rebel ""Howlett House Battery""",,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1865,1865,1865,Albumen silver print from glass negative,Image: 16.8 x 20.3 cm (6 5/8 x 8 in.),"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268285,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.23,false,true,268023,Photographs,Photograph,"Pennsylvania Light Artillery, Keystone Battery",,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268023,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.24,false,true,268034,Photographs,Photograph,Camp of 30th Pennsylvania Infantry,,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268034,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.27,false,true,268067,Photographs,Photograph,"Camp of 34th Massachusetts Infantry near Fort Lyon, Virginia",,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268067,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.29,false,true,268089,Photographs,Photograph,"Pennsylvania Light Artillery, Keystone Battery",,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268089,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.30,false,true,268101,Photographs,Photograph,"Campbell Hospital, D.C.",,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268101,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.31,false,true,268112,Photographs,Photograph,"Co. E, 21st Michigan Infantry. Sherman's Volunteers",,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1861–65,1861,1865,Albumen silver print from glass negative,16.9 x 22.9 cm (6 5/8 x 9 in.),"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268112,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.33,false,true,268134,Photographs,Photograph,"Co. D, 21st Michigan Infantry. Sherman's Volunteers",,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268134,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.36,false,true,268167,Photographs,Photograph,"Pennsylvania Light Artillery, Keystone Battery",,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268167,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.37,false,true,268178,Photographs,Photograph,Washington. Harewood Hospital,,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268178,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.38,false,true,268189,Photographs,Photograph,"[Maneuvers, Winter Quarters]. Brady album, p. 129",,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1861–65,1861,1865,Albumen silver print from glass negative,12.6 x 19.8 cm (4 15/16 x 7 13/16 in.),"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268189,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.39,false,true,268200,Photographs,Photograph,"[Encampment with shacks and laundry]. Brady album, p. 129",,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268200,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.41,false,true,268223,Photographs,Photograph,Land Battery of Naval Guns,,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268223,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.42,false,true,268233,Photographs,Photograph,"[Playing Cards, Winter Quarters]. Brady album, p. 129",,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268233,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.43,false,true,268234,Photographs,Photograph,"Transports, Tennessee River",,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1864,1864,1864,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268234,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.44,false,true,268235,Photographs,Photograph,"Transports, Tennessee River at Chattanooga",,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,ca. 1864,1862,1866,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268235,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.47,false,true,268238,Photographs,Photograph,"U.S. Monitor Onondaga, James River",,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268238,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.78,false,true,268272,Photographs,Photograph,"U.S. Monitor Onondaga, James River",,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268272,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.79,false,true,268273,Photographs,Photograph,"U.S. Monitor Onondaga, James River",,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268273,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.81,false,true,268276,Photographs,Photograph,"U.S. Monitor ""Saugus"" and Gunboat ""Mendota"", Appomattox River",,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268276,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.82,false,true,268277,Photographs,Photograph,"U.S. Monitor ""Mahopac"" on the Appomattox River",,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1864,1864,1864,Albumen silver print from glass negative,Image: 12.9 x 20.1 cm (5 1/16 x 7 15/16 in.),"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268277,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.83,false,true,268278,Photographs,Photograph,"U.S. Monitor Lehigh, James River",,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268278,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.84,false,true,268279,Photographs,Photograph,"""Mendota"", 100 lb. Parrott Gun",,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268279,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.85,false,true,268280,Photographs,Photograph,"U.S. Gunboat ""Mendota"", James River",,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268280,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.86,false,true,268281,Photographs,Photograph,Camp of 44th New York Infantry,,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268281,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.87,false,true,268282,Photographs,Photograph,"Ward in Hospital. Convalescent Camp, Alexandria Virginia",,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268282,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.88,false,true,268283,Photographs,Photograph,Convalescent Camp near Alexandria,,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1861–65,1861,1865,Albumen silver print from glass negative,12.6 x 20 cm (4 15/16 x 7 7/8 in. ),"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268283,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.89,false,true,268284,Photographs,Photograph,"Barracks at Alexandria, Virginia",,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1861–65,1861,1865,Albumen silver print from glass negative,13.1 x 20.1 cm (5 3/16 x 7 15/16 in. ),"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268284,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.97,false,true,268293,Photographs,Photograph,Camp of 44th New York Infantry,,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268293,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.116,false,true,267898,Photographs,Photograph,Fortifications at City Point,,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267898,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.117,false,true,267899,Photographs,Photograph,"Summer Headquarters of General Grant, City Point, Virginia",,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267899,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.121,false,true,267904,Photographs,Photograph,"Sanitary Commission Office. Convalescent Camp, Alexandria, Virginia",,,,,,Former Attribution,Former attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267904,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.122,false,true,267905,Photographs,Photograph,Convalescent Camp near Alexandria,,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1861–65,1861,1865,Albumen silver print from glass negative,13.2 x 21 cm (5 3/16 x 8 1/4 in. ),"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267905,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.125,false,true,267908,Photographs,Photograph,"U.S. Gunboat ""Mendota"", James River",,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267908,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.126,false,true,267909,Photographs,Photograph,"U.S. Gunboat ""Mendota"", James River",,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1861–65,1861,1865,Albumen silver print from glass negative,15.1 x 21.1 cm (5 15/16 x 8 5/16 in.),"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267909,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.127,false,true,267910,Photographs,Photograph,"[Crew of U.S. Monitor ""Saugus""]. Brady album, p. 172",,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267910,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.128,false,true,267911,Photographs,Photograph,"""General Grant"" at Kingston Gap, Tennessee River",,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267911,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.129,false,true,267912,Photographs,Photograph,"U.S. Ship ""Mendota"", James River",,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267912,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.131,false,true,267915,Photographs,Photograph,"Frigates ""Santee"" and ""Constitution"" off Naval Academy, Annapolis",,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267915,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.132,false,true,267916,Photographs,Photograph,"U.S. Monitor ""Casco"" on James River, taken from a lookout tower on bank.",,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267916,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.135,false,true,267919,Photographs,Photograph,Ruins of RR Bridge. Bull Run,,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,ca. 1862,1860,1864,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267919,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.141,false,true,267926,Photographs,Photograph,139th Pennsylvania Infantry,,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1861–65,1861,1865,Albumen silver print from glass negative,12.5 x 19.1 cm (4 15/16 x 7 1/2 in.),"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267926,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.142,false,true,267927,Photographs,Photograph,"U.S. Transport ""Wau Katchie""",,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1861–65,1861,1865,Albumen silver print from glass negative,17.4 x 21.1 cm (6 7/8 x 8 5/16 in.),"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267927,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.155,false,true,267941,Photographs,Photograph,"Fort Burnham, front of Petersburg",,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267941,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.161,false,true,267948,Photographs,Photograph,"U.S. Gunboat ""Saginaw"" and Monitor ""Onondaga""",,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267948,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.163,false,true,267950,Photographs,Photograph,164th New York Infantry,,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,ca. 1861,1859,1863,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267950,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.164,false,true,267951,Photographs,Photograph,"[Regiments - unidentified]. Brady album, p. 156",,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1861–65,1861,1865,Albumen silver print from glass negative,13.5 x 20.2 cm (5 5/16 x 7 15/16 in.),"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267951,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.171,false,true,267959,Photographs,Photograph,"Transports, Tennessee River",,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1864,1864,1864,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267959,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.173,false,true,267961,Photographs,Photograph,"""Building Winter Quarters""",,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267961,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.176,false,true,267964,Photographs,Photograph,[Regiments - unidentified],,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1861–65,1861,1865,Albumen silver print from glass negative,12.7 x 20.1 cm (5 x 7 15/16 in.),"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267964,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.177,false,true,267965,Photographs,Photograph,"""At the Sutler's Store""",,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267965,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.178,false,true,267966,Photographs,Photograph,"Co. B, 170th New York Volunteers",,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,ca. 1861,1859,1863,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267966,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.179,false,true,267967,Photographs,Photograph,"Camp of 34th Massachusetts Infantry, Miner's Hill, VA. Skirmish Drill.",,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1862–63,1862,1863,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267967,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.181,false,true,267970,Photographs,Photograph,"Fort Ellsworth, Alexandria, Virginia",,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267970,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.185,false,true,267974,Photographs,Photograph,"Camp of 34th Massachusetts Infantry near Fort Lyon, Virginia",,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267974,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.187,false,true,267976,Photographs,Photograph,"[Unidentified camp with ruined chimneys in background]. Brady album, p. 130",,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1861–65,1861,1865,Albumen silver print from glass negative,11 x 20.1 cm (4 5/16 x 7 15/16 in.),"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267976,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.188,false,true,267977,Photographs,Photograph,"[Earthworks at the edge of a forest]. Brady album, p. 132",,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267977,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.189,false,true,267978,Photographs,Photograph,Cannon,,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267978,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.191,false,true,267981,Photographs,Photograph,"[Maneuvers, Union Cavalry, Winter Quarters]. Brady album, p. 127",,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267981,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.192,false,true,267982,Photographs,Photograph,139th Pennsylvania Infantry,,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267982,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.193,false,true,267983,Photographs,Photograph,Heavy Artillery,,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267983,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.194,false,true,267984,Photographs,Photograph,"[Regiments - unidentified]. Brady album, p. 156",,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267984,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.195,false,true,267985,Photographs,Photograph,139th Pennsylvania Infantry,,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267985,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.196,false,true,267986,Photographs,Photograph,Fork,,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267986,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.197,false,true,267987,Photographs,Photograph,"[Landscape with army encampment in the distance]. Brady album, p. 125",,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267987,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.198,false,true,267988,Photographs,Photograph,Fortifications,,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1861–65,1861,1865,Albumen silver print from glass negative,15.5 x 20.8 cm (6 1/8 x 8 3/16 in.),"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267988,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.199,false,true,267989,Photographs,Photograph,A Photographer's Store (Brady's?),,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267989,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.205,false,true,267997,Photographs,Photograph,Heavy Artillery,,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267997,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.206,false,true,267998,Photographs,Photograph,"[Regiments - unidentified]. Brady album, p. 157",,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267998,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.208,false,true,268000,Photographs,Photograph,"[Winter Quarters, troops with row of cabins]. Brady album, p. 128",,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1861–65,1861,1865,Albumen silver print from glass negative,12.9 x 20.3 cm (5 1/16 x 8 in.),"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268000,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.209,false,true,268001,Photographs,Photograph,"[Winter Quarters, troops with row of cabins]. Brady album, p. 128",,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268001,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.217,false,true,268010,Photographs,Photograph,"Unloading Supplies for U.S. Military Railroad opposite Richmond, Virginia",,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,ca. 1865,1863,1867,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268010,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.232,false,true,268026,Photographs,Photograph,21st Michigan Infantry. Sherman's Volunteers,,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268026,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.235,false,true,268029,Photographs,Photograph,30th Pennsylvania Infantry,,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1861–65,1861,1865,Albumen silver print from glass negative,15.9 x 20.3 cm (6 1/4 x 8 in.),"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268029,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.236,false,true,268030,Photographs,Photograph,"Maryland Heights, near Harper's Ferry, New York State Militia",,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268030,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.237,false,true,268031,Photographs,Photograph,"Co. A, 30th Pennsylvania Infantry, Camp Mott Hooton",,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268031,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.238,false,true,268032,Photographs,Photograph,30th Pennsylvania Infantry,,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268032,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.239,false,true,268033,Photographs,Photograph,139th Pennsylvania Infantry,,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1861–65,1861,1865,Albumen silver print from glass negative,12.5 x 20.2 cm (4 15/16 x 7 15/16 in.),"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268033,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.240,false,true,268035,Photographs,Photograph,Camp of 153rd New York Infantry,,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,ca. 1861,1859,1863,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268035,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.242,false,true,268037,Photographs,Photograph,"Headquarters, Co. F, 11th Rhode Island Infantry, Miner's Hill, Virginia",,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1862,1862,1862,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268037,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.243,false,true,268038,Photographs,Photograph,153rd New York Infantry,,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,ca. 1861,1859,1863,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268038,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.244,false,true,268039,Photographs,Photograph,30th Pennsylvania Infantry,,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268039,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.246,false,true,268041,Photographs,Photograph,Camp of 153rd New York Infantry,,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,ca. 1861,1859,1863,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268041,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.247,false,true,268042,Photographs,Photograph,44th Indiana Infantry,,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268042,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.248,false,true,268043,Photographs,Photograph,"Stoneman's Station, Virginia",,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268043,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.249,false,true,268044,Photographs,Photograph,"Stoneman's Station, Virginia",,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1861–65,1861,1865,Albumen silver print from glass negative,12.6 x 20 cm (4 15/16 x 7 7/8 in.),"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268044,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.250,false,true,268046,Photographs,Photograph,"Stoneman's Station, Virginia. Quartermaster Dept.",,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1861–65,1861,1865,Albumen silver print from glass negative,13 x 19.8 cm (5 1/8 x 7 13/16 in. ),"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268046,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.251,false,true,268047,Photographs,Photograph,"Stoneman's Station, Virginia. Commissary Dept.",,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268047,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.265,false,true,268062,Photographs,Photograph,U.S. Gunboat at Kingston Gap,,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268062,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.267,false,true,268064,Photographs,Photograph,Camp of 44th New York Infantry,,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268064,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.268,false,true,268065,Photographs,Photograph,"Hunting Creek, near Alexandria, Virginia",,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1861–65,1861,1865,Albumen silver print from glass negative,14.8 x 20.6 cm (5 13/16 x 8 1/8 in. ),"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268065,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.276,false,true,268074,Photographs,Photograph,"[Mitchell's Plantation, Hopewell, Virginia]",,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1863–64,1863,1864,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268074,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.277,false,true,268075,Photographs,Photograph,Union Cavalry Winter Quarters,,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1861–65,1861,1865,Albumen silver print from glass negative,15.8 x 21 cm (6 1/4 x 8 1/4 in.),"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268075,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.282,false,true,268081,Photographs,Photograph,"Camp of Construction Corps, U.S. Military Rail Road, near Manchester, Virginia",,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,ca. 1865,1863,1867,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268081,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.283,false,true,268082,Photographs,Photograph,"Hanover Junction, Pennsylvania",,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268082,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.285,false,true,268084,Photographs,Photograph,"Fort Johnson, James Island, looking toward Fort Sumter",,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268084,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.286,false,true,268085,Photographs,Photograph,"Hanover Junction Station, Pennsylvania",,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268085,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.288,false,true,268087,Photographs,Photograph,"Falls Church, Virginia",,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1861–65,1861,1865,Albumen silver print from glass negative,16.6 x 20.6 cm (6 9/16 x 8 1/8 in. ),"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268087,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.290,false,true,268090,Photographs,Photograph,"Camp Barry near Bladensberg, Maryland. Artillery Depot",,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1861–65,1861,1865,Albumen silver print from glass negative,15.8 x 21 cm (6 1/4 x 8 1/4 in. ),"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268090,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.293,false,true,268093,Photographs,Photograph,"Manchester, Virginia",,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268093,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.296,false,true,268096,Photographs,Photograph,"Fort Putnam, South Carolina",,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268096,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.297,false,true,268097,Photographs,Photograph,"Fort Johnson, James Island, South Carolina",,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268097,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.298,false,true,268098,Photographs,Photograph,"Geisboro D.C., Barracks at Fort Carroll",,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1863–64,1863,1864,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268098,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.299,false,true,268099,Photographs,Photograph,"Camp Barry near Bladensberg, Maryland. Stables",,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268099,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.300,false,true,268102,Photographs,Photograph,"Washington, D.C.",,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268102,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.301,false,true,268103,Photographs,Photograph,Washington. Harewood Hospital,,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268103,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.302,false,true,268104,Photographs,Photograph,Washington. Harewood Hospital,,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268104,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.303,false,true,268105,Photographs,Photograph,Washington. Harewood Hospital,,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268105,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.304,false,true,268106,Photographs,Photograph,Washington. Harewood Hospital,,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268106,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.305,false,true,268107,Photographs,Photograph,"Campbell Hospital near Washington, D.C.",,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268107,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.309,false,true,268111,Photographs,Photograph,Washington. Harewood Hospital,,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1861–65,1861,1865,Albumen silver print from glass negative,15.8 x 21.1 cm (6 1/4 x 8 5/16 in. ),"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268111,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.310,false,true,268113,Photographs,Photograph,"Battery Rodgers, Potomac River near Washington",,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1861–65,1861,1865,Albumen silver print from glass negative,16.6 x 20.6 cm (6 9/16 x 8 1/8 in. ),"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268113,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.311,false,true,268114,Photographs,Photograph,"Distant View of Arsenal, Washington, D.C.",,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1861–65,1861,1865,Albumen silver print from glass negative,15.5 x 20.7 cm (6 1/8 x 8 1/8 in.),"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268114,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.314,false,true,268117,Photographs,Photograph,"[Wagon in a landscape with army encampment in the distance]. Brady album, p. 125",,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268117,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.315,false,true,268118,Photographs,Photograph,[Fortifications],,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268118,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.317,false,true,268120,Photographs,Photograph,Fort Sumter,,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268120,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.318,false,true,268121,Photographs,Photograph,"6th Vermont Infantry, Camp Griffen",,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268121,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.319,false,true,268122,Photographs,Photograph,"U.S. Monitor ""Saugus""",,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268122,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.320,false,true,268124,Photographs,Photograph,Officers of U.S.S. Hunchback,,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268124,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.321,false,true,268125,Photographs,Photograph,Officers of U.S.S. Hunchback,,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268125,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.322,false,true,268126,Photographs,Photograph,Officers of U.S.S. Hunchback,,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268126,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.323,false,true,268127,Photographs,Photograph,Officers of U.S.S. Hunchback,,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268127,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.324,false,true,268128,Photographs,Photograph,Officers of U.S.S. Hunchback,,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268128,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.326,false,true,268130,Photographs,Photograph,"Officers of ""Mendota""",,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268130,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.328,false,true,268132,Photographs,Photograph,[Encampment on a bluff],,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1861–65,1861,1865,Albumen silver print from glass negative,9.8 x 10.7 cm (3 7/8 x 4 3/16 in.),"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268132,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.331,false,true,268136,Photographs,Photograph,22nd New York State Militia,,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,ca. 1861,1859,1863,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268136,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.333,false,true,268138,Photographs,Photograph,Officers of U.S.S. Hunchback,,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268138,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.336,false,true,268141,Photographs,Photograph,Cannon,,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268141,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.344,false,true,268150,Photographs,Photograph,"U.S. Monitor Lehigh, James River",,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268150,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.345,false,true,268151,Photographs,Photograph,"U.S. Monitor Lehigh, James River",,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268151,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.350,false,true,268157,Photographs,Photograph,23rd New York Infantry,,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,ca. 1861,1859,1863,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268157,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.351,false,true,268158,Photographs,Photograph,33rd New York Infantry,,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,ca. 1861,1859,1863,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268158,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.354,false,true,268161,Photographs,Photograph,Officers of U.S.S. Hunchback,,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268161,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.356,false,true,268163,Photographs,Photograph,"[Herd of Horses]. Brady album, p. 123",,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268163,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.357,false,true,268164,Photographs,Photograph,"[View of a small town with wooden sheds in distance]. Brady album, p. 123",,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268164,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.360,false,true,268168,Photographs,Photograph,[Four men in camp under a lean-to of pine boughs],,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268168,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.363,false,true,268171,Photographs,Photograph,"[Encampment alongside a stand of trees]. Brady album, p. 123",,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268171,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.364,false,true,268172,Photographs,Photograph,"[White picket fence with buildings in background]. Brady album, p. 123",,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268172,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.367,false,true,268175,Photographs,Photograph,"[Roads leading into a small town]. Brady album, p. 123",,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268175,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.369,false,true,268177,Photographs,Photograph,74th New York Infantry,,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1861,1861,1861,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268177,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.370,false,true,268179,Photographs,Photograph,"26th New York Infantry at Fort Lyon, near Alexandria, Virginia",,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268179,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.371,false,true,268180,Photographs,Photograph,Fort Sumter,,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268180,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.372,false,true,268181,Photographs,Photograph,"Camp Northumberland, 91st Pennsylvania Infantry",,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1861,1861,1861,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268181,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.373,false,true,268182,Photographs,Photograph,"Navy Yard, Washington, 71st New York State Militia",,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268182,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.375,false,true,268184,Photographs,Photograph,Fort Johnson,,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268184,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.376,false,true,268185,Photographs,Photograph,"Fort Gaines, Officers of the 55th New York Volunteers",,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268185,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.377,false,true,268186,Photographs,Photograph,"[Pleasant Valley Winery, New York]",,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1861–65,1860,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268186,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.378,false,true,268187,Photographs,Photograph,Fort Sumter,,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268187,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.379,false,true,268188,Photographs,Photograph,Swamp Angel Battery,,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268188,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.380,false,true,268190,Photographs,Photograph,"Camp Jameson, Hall's Hill, 22nd Maine Infantry",,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268190,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.382,false,true,268192,Photographs,Photograph,Sally at Fort Richardson,,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1862,1862,1862,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268192,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.384,false,true,268194,Photographs,Photograph,"Fort Totten, N.W. of Washington",,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1865,1865,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268194,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.385,false,true,268195,Photographs,Photograph,"[Cache of Cannon]. Brady album, p. 123",,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268195,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.386,false,true,268196,Photographs,Photograph,"[House framed by trees]. Brady album, p. 123",,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268196,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.390,false,true,268201,Photographs,Photograph,Gettysburg. John Burns House,,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1863,1863,1863,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268201,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.391,false,true,268202,Photographs,Panorama,Gettysburg Wheat Field,,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1863,1863,1863,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268202,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.392,false,true,268203,Photographs,Photograph,"Gettysburg, Pennsylvania",,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1863,1863,1863,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268203,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.394,false,true,268205,Photographs,Photograph,Gettysburg from the West,,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1863,1863,1863,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268205,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.562,false,true,283979,Photographs,Photograph,Confederate Torpedoes Taken from James River,,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1864,1864,1864,Albumen silver print from glass negative,13.3 x 20.6 cm (5 1/4 x 8 1/8 in. ),"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/283979,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"33.65.352, .391",false,true,268159,Photographs,Panorama,Wheat–Field in Which General Reynolds Was Shot,,,,,,Former Attribution,Formerly attributed to,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,July 1863,1863,1863,Albumen silver prints from glass negatives,Panorama: 15.7 × 40.9 cm (6 3/16 × 16 1/8 in.),"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268159,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.306,false,true,268108,Photographs,Photograph,Washington. Armory Square Hospital,,,,,,Former Attribution|Artist,Formerly attributed to,Mathew B. Brady|Unknown,"American, born Ireland, 1823?–1896 New York|American",,"Brady, Mathew B.|Unknown",American,1823,1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268108,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.307,false,true,268109,Photographs,Photograph,"Armory Square Hospital, Washington",,,,,,Former Attribution|Artist,Formerly attributed to,Mathew B. Brady|Unknown,"American, born Ireland, 1823?–1896 New York|American",,"Brady, Mathew B.|Unknown",American,1823,1896,1863–65,1863,1865,Albumen silver print from glass negative,Image: 17.3 × 20 cm (6 13/16 × 7 7/8 in.),"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268109,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.102,false,true,267883,Photographs,Photograph,"Pontoon Bridge, Broadway Landing, Appomattox River",,,,,,Artist|Former Attribution,Formerly attributed to,William Frank Browne|Mathew B. Brady,"American|American, born Ireland, 1823?–1896 New York",,"Browne, William Frank|Brady, Mathew B.",American,1823,1867 |1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267883,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.104,false,true,267885,Photographs,Photograph,"Broadway Landing, Appomattox River",,,,,,Artist|Former Attribution,Formerly attributed to,William Frank Browne|Mathew B. Brady,"American|American, born Ireland, 1823?–1896 New York",,"Browne, William Frank|Brady, Mathew B.",American,1823,1867 |1896,1864,1864,1864,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267885,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.110,false,true,267892,Photographs,Photograph,"Broadway Landing, Appomattox River",,,,,,Artist|Former Attribution,Formerly attributed to,William Frank Browne|Mathew B. Brady,"American|American, born Ireland, 1823?–1896 New York",,"Browne, William Frank|Brady, Mathew B.",American,1823,1867 |1896,1864,1864,1864,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267892,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.136,false,true,267920,Photographs,Photograph,"Fort Brady, Virginia, near Dutch Gap",,,,,,Artist|Former Attribution,Formerly attributed to,William Frank Browne|Mathew B. Brady,"American|American, born Ireland, 1823?–1896 New York",,"Browne, William Frank|Brady, Mathew B.",American,1823,1867 |1896,1865,1865,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267920,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.137,false,true,267921,Photographs,Photograph,"Near Dutch Gap, Virginia. Fort Brady",,,,,,Artist|Former Attribution,Formerly attributed to,William Frank Browne|Mathew B. Brady,"American|American, born Ireland, 1823?–1896 New York",,"Browne, William Frank|Brady, Mathew B.",American,1823,1867 |1896,ca. 1865,1863,1867,Albumen silver print from glass negative,15.8 x 20.6 cm (6 1/4 x 8 1/8 in. ),"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267921,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.138,false,true,267922,Photographs,Photograph,Distant View of Fort Brady,,,,,,Artist|Former Attribution,Formerly attributed to,William Frank Browne|Mathew B. Brady,"American|American, born Ireland, 1823?–1896 New York",,"Browne, William Frank|Brady, Mathew B.",American,1823,1867 |1896,ca. 1865,1863,1867,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267922,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.143,false,true,267928,Photographs,Photograph,Fort Darling. Masked Battery and Obstruction in James River,,,,,,Artist|Former Attribution,Formerly attributed to,William Frank Browne|Mathew B. Brady,"American|American, born Ireland, 1823?–1896 New York",,"Browne, William Frank|Brady, Mathew B.",American,1823,1867 |1896,ca. 1865,1863,1867,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267928,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.166,false,true,267953,Photographs,Photograph,"Fort Brady, Building Quarters",,,,,,Artist|Former Attribution,Formerly attributed to,William Frank Browne|Mathew B. Brady,"American|American, born Ireland, 1823?–1896 New York",,"Browne, William Frank|Brady, Mathew B.",American,1823,1867 |1896,ca. 1865,1863,1867,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267953,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.218,false,true,268011,Photographs,Photograph,"Fort Darling, James River",,,,,,Artist|Former Attribution,Formerly attributed to,William Frank Browne|Mathew B. Brady,"American|American, born Ireland, 1823?–1896 New York",,"Browne, William Frank|Brady, Mathew B.",American,1823,1867 |1896,ca. 1865,1863,1867,Albumen silver print from glass negative,15.1 x 21.4 cm (5 15/16 x 8 7/16 in. ),"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268011,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.263,false,true,268060,Photographs,Photograph,Obstructions in James River near Drewry's Bluff,,,,,,Artist|Former Attribution,Formerly attributed to,William Frank Browne|Mathew B. Brady,"American|American, born Ireland, 1823?–1896 New York",,"Browne, William Frank|Brady, Mathew B.",American,1823,1867 |1896,ca. 1865,1863,1867,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268060,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.270,false,true,268068,Photographs,Photograph,"Broadway Landing, Appomattox River",,,,,,Artist|Former Attribution,Formerly attributed to,William Frank Browne|Mathew B. Brady,"American|American, born Ireland, 1823?–1896 New York",,"Browne, William Frank|Brady, Mathew B.",American,1823,1867 |1896,1864,1864,1864,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268068,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.480,false,true,283897,Photographs,Photograph,"Fort Darling, James River",,,,,,Artist|Former Attribution,Attributed to|Formerly attributed to,William Frank Browne|Mathew B. Brady,"American|American, born Ireland, 1823?–1896 New York",,"Browne, William Frank|Brady, Mathew B.",American,1823,1867 |1896,1865 (?),1865,1865,Albumen silver print from glass negative,16.4 x 20.9 cm (6 7/16 x 8 1/4 in.),"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/283897,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.5,false,true,268241,Photographs,Photograph,Lewis House. Battlefield of Bull Run,,,,,,Artist|Former Attribution,Formerly attributed to,George N. Barnard|Mathew B. Brady,"American, 1819–1902|American, born Ireland, 1823?–1896 New York",,"Barnard, George N.|Brady, Mathew B.",American,1819 |1823,1902 |1896,1861–62,1861,1862,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268241,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.51,false,true,268243,Photographs,Photograph,"St. Michael's Church, Charleston, S.C.",,,,,,Artist|Former Attribution,Formerly attributed to,George N. Barnard|Mathew B. Brady,"American, 1819–1902|American, born Ireland, 1823?–1896 New York",,"Barnard, George N.|Brady, Mathew B.",American,1819 |1823,1902 |1896,ca. 1864,1862,1866,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268243,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.53,false,true,268245,Photographs,Photograph,"Hibernian Hall, Charleston",,,,,,Artist|Former Attribution,Formerly attributed to,George N. Barnard|Mathew B. Brady,"American, 1819–1902|American, born Ireland, 1823?–1896 New York",,"Barnard, George N.|Brady, Mathew B.",American,1819 |1823,1902 |1896,ca. 1864,1862,1866,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268245,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.106,false,true,267887,Photographs,Photograph,"Ruins of Mrs. Henry's House, Battlefield of Bull Run",,,,,,Artist|Former Attribution,Formerly attributed to,George N. Barnard|Mathew B. Brady,"American, 1819–1902|American, born Ireland, 1823?–1896 New York",,"Barnard, George N.|Brady, Mathew B.",American,1819 |1823,1902 |1896,March 1862,1862,1862,Albumen silver print from glass negative,Image: 15.9 × 20.9 cm (6 1/4 × 8 1/4 in.),"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267887,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.180,false,true,267969,Photographs,Photograph,Bull Run. Matthews House,,,,,,Artist|Former Attribution,Formerly attributed to,George N. Barnard|Mathew B. Brady,"American, 1819–1902|American, born Ireland, 1823?–1896 New York",,"Barnard, George N.|Brady, Mathew B.",American,1819 |1823,1902 |1896,1861–62,1861,1862,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267969,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.207,false,true,267999,Photographs,Photograph,"Bull Run, Mrs. Henry's House, 21 July 1861",,,,,,Artist|Former Attribution,Formerly attributed to,George N. Barnard|Mathew B. Brady,"American, 1819–1902|American, born Ireland, 1823?–1896 New York",,"Barnard, George N.|Brady, Mathew B.",American,1819 |1823,1902 |1896,1861,1861,1861,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267999,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.284,false,true,268083,Photographs,Photograph,"Commissary Headquarters, Rocky Face Ridge",,,,,,Artist|Former Attribution,Formerly attributed to,George N. Barnard|Mathew B. Brady,"American, 1819–1902|American, born Ireland, 1823?–1896 New York",,"Barnard, George N.|Brady, Mathew B.",American,1819 |1823,1902 |1896,ca. 1864,1862,1866,Albumen silver print from glass negative,12.7 x 20.1 cm (5 x 7 15/16 in. ),"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268083,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.295,false,true,268095,Photographs,Photograph,Rebel Fortifications in front of Atlanta,,,,,,Artist|Former Attribution,Formerly attributed to,George N. Barnard|Mathew B. Brady,"American, 1819–1902|American, born Ireland, 1823?–1896 New York",,"Barnard, George N.|Brady, Mathew B.",American,1819 |1823,1902 |1896,ca. 1864,1862,1866,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268095,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.366,false,true,268174,Photographs,Photograph,View on Tennessee River,,,,,,Artist|Former Attribution,Formerly attributed to,George N. Barnard|Mathew B. Brady,"American, 1819–1902|American, born Ireland, 1823?–1896 New York",,"Barnard, George N.|Brady, Mathew B.",American,1819 |1823,1902 |1896,ca. 1864,1862,1866,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268174,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.389,false,true,268199,Photographs,Photograph,Tennessee River at Bridgeport,,,,,,Artist|Former Attribution,Formerly attributed to,George N. Barnard|Mathew B. Brady,"American, 1819–1902|American, born Ireland, 1823?–1896 New York",,"Barnard, George N.|Brady, Mathew B.",American,1819 |1823,1902 |1896,ca. 1864,1862,1866,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268199,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.397,false,true,268208,Photographs,Photograph,"U.S. Transport in Rapids, Tennessee River/The Suck - Tennessee River below Chattanooga, looking down stream",,,,,,Artist|Former Attribution,Formerly attributed to,George N. Barnard|Mathew B. Brady,"American, 1819–1902|American, born Ireland, 1823?–1896 New York",,"Barnard, George N.|Brady, Mathew B.",American,1819 |1823,1902 |1896,ca. 1864,1862,1866,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268208,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.399,false,true,268210,Photographs,Photograph,"Chattanooga, Tennessee",,,,,,Artist|Former Attribution,Formerly attributed to,George N. Barnard|Mathew B. Brady,"American, 1819–1902|American, born Ireland, 1823?–1896 New York",,"Barnard, George N.|Brady, Mathew B.",American,1819 |1823,1902 |1896,ca. 1864,1862,1866,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268210,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.400,false,true,268213,Photographs,Photograph,Camp near Chattanooga,,,,,,Artist|Former Attribution,Formerly attributed to,George N. Barnard|Mathew B. Brady,"American, 1819–1902|American, born Ireland, 1823?–1896 New York",,"Barnard, George N.|Brady, Mathew B.",American,1819 |1823,1902 |1896,ca. 1864,1862,1866,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268213,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.401,false,true,268214,Photographs,Photograph,Bridge Builders Camp opposite Chattanooga,,,,,,Artist|Former Attribution,Formerly attributed to,George N. Barnard|Mathew B. Brady,"American, 1819–1902|American, born Ireland, 1823?–1896 New York",,"Barnard, George N.|Brady, Mathew B.",American,1819 |1823,1902 |1896,ca. 1864,1862,1866,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268214,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.402,false,true,268215,Photographs,Photograph,"Crutchfield House, Chattanooga, Tennessee",,,,,,Artist|Former Attribution,Formerly attributed to,George N. Barnard|Mathew B. Brady,"American, 1819–1902|American, born Ireland, 1823?–1896 New York",,"Barnard, George N.|Brady, Mathew B.",American,1819 |1823,1902 |1896,ca. 1864,1862,1866,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268215,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.403,false,true,268216,Photographs,Photograph,"Chattanooga, Tennessee. Government Stable",,,,,,Artist|Former Attribution,Formerly attributed to,George N. Barnard|Mathew B. Brady,"American, 1819–1902|American, born Ireland, 1823?–1896 New York",,"Barnard, George N.|Brady, Mathew B.",American,1819 |1823,1902 |1896,ca. 1864,1862,1866,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268216,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.404,false,true,268217,Photographs,Photograph,"Chattanooga, Tennessee",,,,,,Artist|Former Attribution,Formerly attributed to,George N. Barnard|Mathew B. Brady,"American, 1819–1902|American, born Ireland, 1823?–1896 New York",,"Barnard, George N.|Brady, Mathew B.",American,1819 |1823,1902 |1896,ca. 1864,1862,1866,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268217,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.405,false,true,268218,Photographs,Photograph,"Chief Commissary's Office, Chattanooga",,,,,,Artist|Former Attribution,Formerly attributed to,George N. Barnard|Mathew B. Brady,"American, 1819–1902|American, born Ireland, 1823?–1896 New York",,"Barnard, George N.|Brady, Mathew B.",American,1819 |1823,1902 |1896,ca. 1864,1862,1866,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268218,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.406,false,true,268219,Photographs,Photograph,Bridge Across Tennessee River at Chattanooga,,,,,,Artist|Former Attribution,Formerly attributed to,George N. Barnard|Mathew B. Brady,"American, 1819–1902|American, born Ireland, 1823?–1896 New York",,"Barnard, George N.|Brady, Mathew B.",American,1819 |1823,1902 |1896,ca. 1864,1862,1866,Albumen silver print from glass negative,8.5 x 8.9 cm (3 3/8 x 3 1/2 in. ),"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268219,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.407,false,true,268220,Photographs,Photograph,View on Tennessee River looking toward Chattanooga,,,,,,Artist|Former Attribution,Formerly attributed to,George N. Barnard|Mathew B. Brady,"American, 1819–1902|American, born Ireland, 1823?–1896 New York",,"Barnard, George N.|Brady, Mathew B.",American,1819 |1823,1902 |1896,ca. 1864,1862,1866,Albumen silver print from glass negative,7.9 x 9.1 cm (3 1/8 x 3 9/16 in. ),"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268220,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.408,false,true,268221,Photographs,Photograph,Bridge over Tennessee River at Chattanooga,,,,,,Artist|Former Attribution,Formerly attributed to,George N. Barnard|Mathew B. Brady,"American, 1819–1902|American, born Ireland, 1823?–1896 New York",,"Barnard, George N.|Brady, Mathew B.",American,1819 |1823,1902 |1896,ca. 1864,1862,1866,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268221,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.409,false,true,268222,Photographs,Photograph,Bridge Across Tennessee River at Chattanooga,,,,,,Former Attribution|Artist,Formerly attributed to,George N. Barnard|Mathew B. Brady,"American, 1819–1902|American, born Ireland, 1823?–1896 New York",,"Barnard, George N.|Brady, Mathew B.",American,1819 |1823,1902 |1896,ca. 1864,1862,1866,Albumen silver print from glass negative,13.4 x 20.5 cm (5 1/4 x 8 1/16 in. ),"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268222,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.410,false,true,268224,Photographs,Photograph,"Chattanooga, Tennessee",,,,,,Artist|Former Attribution,Formerly attributed to,George N. Barnard|Mathew B. Brady,"American, 1819–1902|American, born Ireland, 1823?–1896 New York",,"Barnard, George N.|Brady, Mathew B.",American,1819 |1823,1902 |1896,ca. 1864,1862,1866,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268224,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.411,false,true,268225,Photographs,Photograph,Tennessee River at Chattanooga (81 Lookout Mountain Spur),,,,,,Artist|Former Attribution,Formerly attributed to,George N. Barnard|Mathew B. Brady,"American, 1819–1902|American, born Ireland, 1823?–1896 New York",,"Barnard, George N.|Brady, Mathew B.",American,1819 |1823,1902 |1896,ca. 1864,1862,1866,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268225,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.412,false,true,268226,Photographs,Photograph,"Provost Marshals Headquarters, Chattanooga",,,,,,Artist|Former Attribution,Formerly attributed to,George N. Barnard|Mathew B. Brady,"American, 1819–1902|American, born Ireland, 1823?–1896 New York",,"Barnard, George N.|Brady, Mathew B.",American,1819 |1823,1902 |1896,ca. 1864,1862,1866,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268226,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.413,false,true,268227,Photographs,Photograph,"Headquarters of General Sherman or Thomas, Chattanooga",,,,,,Artist|Former Attribution,Formerly attributed to,George N. Barnard|Mathew B. Brady,"American, 1819–1902|American, born Ireland, 1823?–1896 New York",,"Barnard, George N.|Brady, Mathew B.",American,1819 |1823,1902 |1896,ca. 1864,1862,1866,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268227,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.415,false,true,268229,Photographs,Photograph,"Lookout Mountain, Tennessee",,,,,,Artist|Former Attribution,Formerly attributed to,George N. Barnard|Mathew B. Brady,"American, 1819–1902|American, born Ireland, 1823?–1896 New York",,"Barnard, George N.|Brady, Mathew B.",American,1819 |1823,1902 |1896,ca. 1864,1862,1866,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268229,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.148,false,true,267933,Photographs,Photograph,"Fort Mahone, Petersburg, Virginia",,,,,,Artist|Former Attribution,Formerly attributed to,Thomas C. Roche|Mathew B. Brady,"American, 1826–1895|American, born Ireland, 1823?–1896 New York",,"Roche, Thomas C.|Brady, Mathew B.",American,1826 |1823,1895 |1896,1864,1864,1864,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267933,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.334,false,true,268139,Photographs,Photograph,"Dead Confederates, Fort Mahone",,,,,,Artist|Former Attribution,Formerly attributed to,Thomas C. Roche|Mathew B. Brady,"American, 1826–1895|American, born Ireland, 1823?–1896 New York",,"Roche, Thomas C.|Brady, Mathew B.",American,1826 |1823,1895 |1896,1864,1864,1864,Albumen silver print from glass negative,9.9 x 9.4 cm (3 7/8 x 3 11/16 in. ),"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268139,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.340,false,true,268146,Photographs,Photograph,"Dead Confederates, Fort Mahone",,,,,,Artist|Former Attribution,Formerly attributed to,Thomas C. Roche|Mathew B. Brady,"American, 1826–1895|American, born Ireland, 1823?–1896 New York",,"Roche, Thomas C.|Brady, Mathew B.",American,1826 |1823,1895 |1896,1864,1864,1864,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268146,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.7,false,true,268263,Photographs,Photograph,"City Point, Virginia. Terminus of U.S. Military Railroad",,,,,,Artist|Former Attribution,Formerly attributed to,Andrew Joseph Russell|Mathew B. Brady,"American, 1830–1902|American, born Ireland, 1823?–1896 New York",,"Russell, Andrew Joseph|Brady, Mathew B.",American,1830 |1823,1902 |1896,1861–65,1861,1865,Albumen silver print from glass negative,16.7 x 21.8 cm (6 9/16 x 8 9/16 in. ),"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268263,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.8,false,true,268274,Photographs,Photograph,"Burial of the Dead, Fredericksburg",,,,,,Artist|Former Attribution,Formerly attributed to,Andrew Joseph Russell|Mathew B. Brady,"American, 1830–1902|American, born Ireland, 1823?–1896 New York",,"Russell, Andrew Joseph|Brady, Mathew B.",American,1830 |1823,1902 |1896,1863,1863,1863,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268274,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.14,false,true,267924,Photographs,Photograph,"Pontoon Bridge at Deep Bottom, James River",,,,,,Artist|Former Attribution,Formerly attributed to,Andrew Joseph Russell|Mathew B. Brady,"American, 1830–1902|American, born Ireland, 1823?–1896 New York",,"Russell, Andrew Joseph|Brady, Mathew B.",American,1830 |1823,1902 |1896,1864,1864,1864,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267924,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.15,false,true,267935,Photographs,Photograph,"City Point, Virginia",,,,,,Artist|Former Attribution,Formerly attributed to,Andrew Joseph Russell|Mathew B. Brady,"American, 1830–1902|American, born Ireland, 1823?–1896 New York",,"Russell, Andrew Joseph|Brady, Mathew B.",American,1830 |1823,1902 |1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267935,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.17,false,true,267957,Photographs,Photograph,"End of the Bridge after Burnside's Attack, Fredericksburg, Virginia",,,,,,Artist|Former Attribution,Formerly attributed to,Andrew Joseph Russell|Mathew B. Brady,"American, 1830–1902|American, born Ireland, 1823?–1896 New York",,"Russell, Andrew Joseph|Brady, Mathew B.",American,1830 |1823,1902 |1896,1863,1863,1863,Albumen silver print from glass negative,Image: 13.1 × 20.6 cm (5 3/16 × 8 1/8 in.),"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267957,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.18,false,true,267968,Photographs,Photograph,"Pontoon Bridge at Deep Bottom, James River",,,,,,Artist|Former Attribution,Formerly attributed to,Andrew Joseph Russell|Mathew B. Brady,"American, 1830–1902|American, born Ireland, 1823?–1896 New York",,"Russell, Andrew Joseph|Brady, Mathew B.",American,1830 |1823,1902 |1896,1864,1864,1864,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267968,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.21,false,true,268002,Photographs,Photograph,"Abandoned Camp, Falmouth, Virginia",,,,,,Artist|Former Attribution,Formerly attributed to,Andrew Joseph Russell|Mathew B. Brady,"American, 1830–1902|American, born Ireland, 1823?–1896 New York",,"Russell, Andrew Joseph|Brady, Mathew B.",American,1830 |1823,1902 |1896,1862,1862,1862,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268002,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.34,false,true,268145,Photographs,Photograph,"Headquarters, 10th Army Corps, Hatcher's Farm, Virginia",,,,,,Artist|Former Attribution,Formerly attributed to,Andrew Joseph Russell|Mathew B. Brady,"American, 1830–1902|American, born Ireland, 1823?–1896 New York",,"Russell, Andrew Joseph|Brady, Mathew B.",American,1830 |1823,1902 |1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268145,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.45,false,true,268236,Photographs,Photograph,"Pontoon Bridge at Deep Bottom, James River",,,,,,Artist|Former Attribution,Formerly attributed to,Andrew Joseph Russell|Mathew B. Brady,"American, 1830–1902|American, born Ireland, 1823?–1896 New York",,"Russell, Andrew Joseph|Brady, Mathew B.",American,1830 |1823,1902 |1896,1864,1864,1864,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268236,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.46,false,true,268237,Photographs,Photograph,"Pontoon Bridge at Deep Bottom, James River",,,,,,Artist|Former Attribution,Formerly attributed to,Andrew Joseph Russell|Mathew B. Brady,"American, 1830–1902|American, born Ireland, 1823?–1896 New York",,"Russell, Andrew Joseph|Brady, Mathew B.",American,1830 |1823,1902 |1896,1864,1864,1864,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268237,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.49,false,true,268240,Photographs,Photograph,"Military Railroad Camp, City Point, Virginia",,,,,,Artist|Former Attribution,Formerly attributed to,Andrew Joseph Russell|Mathew B. Brady,"American, 1830–1902|American, born Ireland, 1823?–1896 New York",,"Russell, Andrew Joseph|Brady, Mathew B.",American,1830 |1823,1902 |1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268240,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.58,false,true,268250,Photographs,Photograph,Entrenchments on left of Bermuda Hundred Lines,,,,,,Artist|Former Attribution,Formerly attributed to,Andrew Joseph Russell|Mathew B. Brady,"American, 1830–1902|American, born Ireland, 1823?–1896 New York",,"Russell, Andrew Joseph|Brady, Mathew B.",American,1830 |1823,1902 |1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268250,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.59,false,true,268251,Photographs,Photograph,"Port Royal, Rappahannock River",,,,,,Artist|Former Attribution,Formerly attributed to,Andrew Joseph Russell|Mathew B. Brady,"American, 1830–1902|American, born Ireland, 1823?–1896 New York",,"Russell, Andrew Joseph|Brady, Mathew B.",American,1830 |1823,1902 |1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268251,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.60,false,true,268253,Photographs,Photograph,"Port Royal, Rappahannock River",,,,,,Artist|Former Attribution,Formerly attributed to,Andrew Joseph Russell|Mathew B. Brady,"American, 1830–1902|American, born Ireland, 1823?–1896 New York",,"Russell, Andrew Joseph|Brady, Mathew B.",American,1830 |1823,1902 |1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268253,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.61,false,true,268254,Photographs,Photograph,"Wagon Train at Port Royal, Rappahannock River",,,,,,Artist|Former Attribution,Formerly attributed to,Andrew Joseph Russell|Mathew B. Brady,"American, 1830–1902|American, born Ireland, 1823?–1896 New York",,"Russell, Andrew Joseph|Brady, Mathew B.",American,1830 |1823,1902 |1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268254,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.62,false,true,268255,Photographs,Photograph,Crow's Nest Signal Tower near Bermuda Hundred,,,,,,Artist|Former Attribution,Formerly attributed to,Andrew Joseph Russell|Mathew B. Brady,"American, 1830–1902|American, born Ireland, 1823?–1896 New York",,"Russell, Andrew Joseph|Brady, Mathew B.",American,1830 |1823,1902 |1896,1861–65,1861,1865,Albumen silver print from glass negative,20.3 x 12.9 cm (8 x 5 1/16 in.),"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268255,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.63,false,true,268256,Photographs,Photograph,Extreme Left of Bermuda Hundred Lines,,,,,,Artist|Former Attribution,Formerly attributed to,Andrew Joseph Russell|Mathew B. Brady,"American, 1830–1902|American, born Ireland, 1823?–1896 New York",,"Russell, Andrew Joseph|Brady, Mathew B.",American,1830 |1823,1902 |1896,1861–65,1861,1865,Albumen silver print from glass negative,16.4 x 21.1 cm (6 7/16 x 8 5/16 in. ),"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268256,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.70,false,true,268264,Photographs,Photograph,"Pontoon Bridge, James River",,,,,,Artist|Former Attribution,Formerly attributed to,Andrew Joseph Russell|Mathew B. Brady,"American, 1830–1902|American, born Ireland, 1823?–1896 New York",,"Russell, Andrew Joseph|Brady, Mathew B.",American,1830 |1823,1902 |1896,1864,1864,1864,Albumen silver print from glass negative,12.7 x 19.7 cm (5 x 7 3/4 in.),"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268264,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.71,false,true,268265,Photographs,Photograph,Wilcox Landing,,,,,,Artist|Former Attribution,Formerly attributed to,Andrew Joseph Russell|Mathew B. Brady,"American, 1830–1902|American, born Ireland, 1823?–1896 New York",,"Russell, Andrew Joseph|Brady, Mathew B.",American,1830 |1823,1902 |1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268265,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.72,false,true,268266,Photographs,Photograph,"Confederate Prisoners for Exchange, Cox's Landing, James River",,,,,,Artist|Former Attribution,Formerly attributed to,Andrew Joseph Russell|Mathew B. Brady,"American, 1830–1902|American, born Ireland, 1823?–1896 New York",,"Russell, Andrew Joseph|Brady, Mathew B.",American,1830 |1823,1902 |1896,1864,1864,1864,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268266,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.73,false,true,268267,Photographs,Photograph,"Pontoon Bridge Train, James River",,,,,,Artist|Former Attribution,Formerly attributed to,Andrew Joseph Russell|Mathew B. Brady,"American, 1830–1902|American, born Ireland, 1823?–1896 New York",,"Russell, Andrew Joseph|Brady, Mathew B.",American,1830 |1823,1902 |1896,1864,1864,1864,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268267,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.75,false,true,268269,Photographs,Photograph,"Pontoon Bridge, James River",,,,,,Artist|Former Attribution,Formerly attributed to,Andrew Joseph Russell|Mathew B. Brady,"American, 1830–1902|American, born Ireland, 1823?–1896 New York",,"Russell, Andrew Joseph|Brady, Mathew B.",American,1830 |1823,1902 |1896,1864,1864,1864,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268269,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.76,false,true,268270,Photographs,Photograph,"Bermuda Hundred Landing, James River",,,,,,Artist|Former Attribution,Formerly attributed to,Andrew Joseph Russell|Mathew B. Brady,"American, 1830–1902|American, born Ireland, 1823?–1896 New York",,"Russell, Andrew Joseph|Brady, Mathew B.",American,1830 |1823,1902 |1896,1864,1864,1864,Albumen silver print from glass negative,14.6 x 16.5 cm (5 3/4 x 6 1/2 in. ),"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268270,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.90,false,true,268286,Photographs,Photograph,Street in Fredericksburg,,,,,,Artist|Former Attribution,Formerly attributed to,Andrew Joseph Russell|Mathew B. Brady,"American, 1830–1902|American, born Ireland, 1823?–1896 New York",,"Russell, Andrew Joseph|Brady, Mathew B.",American,1830 |1823,1902 |1896,1863,1863,1863,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268286,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.92,false,true,268288,Photographs,Photograph,"Headquarters of Capt. E.E. Camp, A.Q.M., at City Point, Virginia",,,,,,Artist|Former Attribution,Formerly attributed to,Andrew Joseph Russell|Mathew B. Brady,"American, 1830–1902|American, born Ireland, 1823?–1896 New York",,"Russell, Andrew Joseph|Brady, Mathew B.",American,1830 |1823,1902 |1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268288,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.93,false,true,268289,Photographs,Photograph,"City Point, Virginia. James River",,,,,,Artist|Former Attribution,Formerly attributed to,Andrew Joseph Russell|Mathew B. Brady,"American, 1830–1902|American, born Ireland, 1823?–1896 New York",,"Russell, Andrew Joseph|Brady, Mathew B.",American,1830 |1823,1902 |1896,1864,1864,1864,Albumen silver print from glass negative,12.2 x 22.7 cm (4 13/16 x 8 15/16 in. ),"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268289,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.94,false,true,268290,Photographs,Photograph,Bridge. U.S. Military Railroad at City Point,,,,,,Artist|Former Attribution,,Andrew Joseph Russell|Mathew B. Brady,"American, 1830–1902|American, born Ireland, 1823?–1896 New York",,"Russell, Andrew Joseph|Brady, Mathew B.",American,1830 |1823,1902 |1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268290,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.95,false,true,268291,Photographs,Photograph,"City Point, Virginia",,,,,,Artist|Former Attribution,,Andrew Joseph Russell|Mathew B. Brady,"American, 1830–1902|American, born Ireland, 1823?–1896 New York",,"Russell, Andrew Joseph|Brady, Mathew B.",American,1830 |1823,1902 |1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268291,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.96,false,true,268292,Photographs,Photograph,"Fredericksburg, Virginia",,,,,,Artist|Former Attribution,,Andrew Joseph Russell|Mathew B. Brady,"American, 1830–1902|American, born Ireland, 1823?–1896 New York",,"Russell, Andrew Joseph|Brady, Mathew B.",American,1830 |1823,1902 |1896,1863,1863,1863,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268292,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.98,false,true,268294,Photographs,Photograph,"Pontoon Bridge at Deep Bottom, James River",,,,,,Artist|Former Attribution,,Andrew Joseph Russell|Mathew B. Brady,"American, 1830–1902|American, born Ireland, 1823?–1896 New York",,"Russell, Andrew Joseph|Brady, Mathew B.",American,1830 |1823,1902 |1896,1864,1864,1864,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268294,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.99,false,true,268295,Photographs,Photograph,James River,,,,,,Artist|Former Attribution,,Andrew Joseph Russell|Mathew B. Brady,"American, 1830–1902|American, born Ireland, 1823?–1896 New York",,"Russell, Andrew Joseph|Brady, Mathew B.",American,1830 |1823,1902 |1896,1864,1864,1864,Albumen silver print from glass negative,9 x 20 cm (3 9/16 x 7 7/8 in.),"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268295,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.100,false,true,267881,Photographs,Photograph,"City Point, Virginia",,,,,,Artist|Former Attribution,,Andrew Joseph Russell|Mathew B. Brady,"American, 1830–1902|American, born Ireland, 1823?–1896 New York",,"Russell, Andrew Joseph|Brady, Mathew B.",American,1830 |1823,1902 |1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267881,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.101,false,true,267882,Photographs,Photograph,"Terminus of U.S. Military Railroad, City Point, Virginia",,,,,,Artist|Former Attribution,,Andrew Joseph Russell|Mathew B. Brady,"American, 1830–1902|American, born Ireland, 1823?–1896 New York",,"Russell, Andrew Joseph|Brady, Mathew B.",American,1830 |1823,1902 |1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267882,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.103,false,true,267884,Photographs,Photograph,"Excavating for ""Y"" at Devereaux Station, Orange & Alexandria Railroad",,,,,,Artist|Former Attribution,,Andrew Joseph Russell|Mathew B. Brady,"American, 1830–1902|American, born Ireland, 1823?–1896 New York",,"Russell, Andrew Joseph|Brady, Mathew B.",American,1830 |1823,1902 |1896,1863,1863,1863,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267884,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.112,false,true,267894,Photographs,Photograph,"City Point, Virginia",,,,,,Artist|Former Attribution,Formerly attributed to,Andrew Joseph Russell|Mathew B. Brady,"American, 1830–1902|American, born Ireland, 1823?–1896 New York",,"Russell, Andrew Joseph|Brady, Mathew B.",American,1830 |1823,1902 |1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267894,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.113,false,true,267895,Photographs,Photograph,"Headquarters of Capt. H.B. Blood, A.Q.M., at City Point, Virginia",,,,,,Artist|Former Attribution,Formerly attributed to,Andrew Joseph Russell|Mathew B. Brady,"American, 1830–1902|American, born Ireland, 1823?–1896 New York",,"Russell, Andrew Joseph|Brady, Mathew B.",American,1830 |1823,1902 |1896,1861–65,1861,1865,Albumen silver print from glass negative,12.8 x 18.9 cm (5 1/16 x 7 7/16 in. ),"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267895,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.114,false,true,267896,Photographs,Photograph,"[Wharves on the James River, City Point]. Brady album, p. 10",,,,,,Artist|Former Attribution,Formerly attributed to,Andrew Joseph Russell|Mathew B. Brady,"American, 1830–1902|American, born Ireland, 1823?–1896 New York",,"Russell, Andrew Joseph|Brady, Mathew B.",American,1830 |1823,1902 |1896,1861–65,1861,1865,Albumen silver print from glass negative,13.8 x 20.9 cm (5 7/16 x 8 1/4 in. ),"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267896,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.115,false,true,267897,Photographs,Photograph,"Commissary Department, City Point, Virginia",,,,,,Artist|Former Attribution,Formerly attributed to,Andrew Joseph Russell|Mathew B. Brady,"American, 1830–1902|American, born Ireland, 1823?–1896 New York",,"Russell, Andrew Joseph|Brady, Mathew B.",American,1830 |1823,1902 |1896,1861–65,1861,1865,Albumen silver print from glass negative,16 x 22.9 cm (6 5/16 x 9 in. ),"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267897,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.118,false,true,267900,Photographs,Panorama,"Looking Towards Marye's Heights, Fredericksburg",,,,,,Artist|Former Attribution,Formerly attributed to,Andrew Joseph Russell|Mathew B. Brady,"American, 1830–1902|American, born Ireland, 1823?–1896 New York",,"Russell, Andrew Joseph|Brady, Mathew B.",American,1830 |1823,1902 |1896,1864,1864,1864,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267900,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.119,false,true,267901,Photographs,Photograph,"Burial of the Dead, Fredericksburg",,,,,,Artist|Former Attribution,Formerly attributed to,Andrew Joseph Russell|Mathew B. Brady,"American, 1830–1902|American, born Ireland, 1823?–1896 New York",,"Russell, Andrew Joseph|Brady, Mathew B.",American,1830 |1823,1902 |1896,1863,1863,1863,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267901,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.120,false,true,267903,Photographs,Photograph,"Battery Going into Action, Fredericksburg, December 13, 1862",,,,,,Artist|Former Attribution,Formerly attributed to,Andrew Joseph Russell|Mathew B. Brady,"American, 1830–1902|American, born Ireland, 1823?–1896 New York",,"Russell, Andrew Joseph|Brady, Mathew B.",American,1830 |1823,1902 |1896,1862,1862,1862,Albumen silver print from glass negative,14.7 x 19.9 cm (5 13/16 x 7 13/16 in. ),"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267903,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.170,false,true,267958,Photographs,Photograph,Pontoon Bridge,,,,,,Artist|Former Attribution,Formerly attributed to,Andrew Joseph Russell|Mathew B. Brady,"American, 1830–1902|American, born Ireland, 1823?–1896 New York",,"Russell, Andrew Joseph|Brady, Mathew B.",American,1830 |1823,1902 |1896,1861–65,1861,1865,Albumen silver print from glass negative,13.3 x 20.4 cm (5 1/4 x 8 1/16 in.),"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267958,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.175,false,true,267963,Photographs,Photograph,"Confederate Method of Destroying Rail Roads at McCloud Mill, Virginia",,,,,,Artist|Former Attribution,Formerly attributed to,Andrew Joseph Russell|Mathew B. Brady,"American, 1830–1902|American, born Ireland, 1823?–1896 New York",,"Russell, Andrew Joseph|Brady, Mathew B.",American,1830 |1823,1902 |1896,1863,1863,1863,Albumen silver print from glass negative,Image: 16.2 × 19.6 cm (6 3/8 × 7 11/16 in.),"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267963,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.183,false,true,267972,Photographs,Photograph,"General Butler's Headquarters, Chapin's Farm, Virginia",,,,,,Artist|Former Attribution,Formerly attributed to,Andrew Joseph Russell|Mathew B. Brady,"American, 1830–1902|American, born Ireland, 1823?–1896 New York",,"Russell, Andrew Joseph|Brady, Mathew B.",American,1830 |1823,1902 |1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267972,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.210,false,true,268003,Photographs,Photograph,Confederate Trestle Work on Alexandria Railroad,,,,,,Artist|Former Attribution,Formerly attributed to,Andrew Joseph Russell|Mathew B. Brady,"American, 1830–1902|American, born Ireland, 1823?–1896 New York",,"Russell, Andrew Joseph|Brady, Mathew B.",American,1830 |1823,1902 |1896,1861–65,1861,1865,Albumen silver print from glass negative,15.2 x 21.1 cm (6 x 8 5/16 in.),"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268003,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.262,false,true,268059,Photographs,Photograph,"Deep Bottom, James River",,,,,,Artist|Former Attribution,Formerly attributed to,Andrew Joseph Russell|Mathew B. Brady,"American, 1830–1902|American, born Ireland, 1823?–1896 New York",,"Russell, Andrew Joseph|Brady, Mathew B.",American,1830 |1823,1902 |1896,1864,1864,1864,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268059,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.264,false,true,268061,Photographs,Photograph,"Unloading Supplies at Port Royal, Virginia",,,,,,Artist|Former Attribution,Formerly attributed to,Andrew Joseph Russell|Mathew B. Brady,"American, 1830–1902|American, born Ireland, 1823?–1896 New York",,"Russell, Andrew Joseph|Brady, Mathew B.",American,1830 |1823,1902 |1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268061,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.269,false,true,268066,Photographs,Photograph,"Army Wagon and Forge, City Point, Virginia",,,,,,Artist|Former Attribution,Formerly attributed to,Andrew Joseph Russell|Mathew B. Brady,"American, 1830–1902|American, born Ireland, 1823?–1896 New York",,"Russell, Andrew Joseph|Brady, Mathew B.",American,1830 |1823,1902 |1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268066,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.271,false,true,268069,Photographs,Photograph,"[Orange and Alexandria Railroad Bridge, near Union Mills, Virginia]",,,,,,Artist|Former Attribution,Formerly attributed to,Andrew Joseph Russell|Mathew B. Brady,"American, 1830–1902|American, born Ireland, 1823?–1896 New York",,"Russell, Andrew Joseph|Brady, Mathew B.",American,1830 |1823,1902 |1896,ca. 1863,1860,1865,Albumen silver print from glass negative,Image: 12.3 x 20.1 cm (4 13/16 x 7 15/16 in.),"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268069,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.275,false,true,268073,Photographs,Photograph,"Removing Dead from Battlefield, Marye's Heights, May 2, 1864",,,,,,Artist|Former Attribution,Formerly attributed to,Andrew Joseph Russell|Mathew B. Brady,"American, 1830–1902|American, born Ireland, 1823?–1896 New York",,"Russell, Andrew Joseph|Brady, Mathew B.",American,1830 |1823,1902 |1896,1864,1864,1864,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268073,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.278,false,true,268076,Photographs,Photograph,"Bridge on Orange and Alexandria Rail Road, as Repaired by Army Engineers under Colonel Herman Haupt",,,,,,Artist|Former Attribution,Formerly attributed to,Andrew Joseph Russell|Mathew B. Brady,"American, 1830–1902|American, born Ireland, 1823?–1896 New York",,"Russell, Andrew Joseph|Brady, Mathew B.",American,1830 |1823,1902 |1896,1865,1865,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268076,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.279,false,true,268077,Photographs,Photograph,"Locomotive #56, U.S. Military Railroad/City Point. Troops Ready to be Taken to the Front by Rail",,,,,,Artist|Former Attribution,Formerly attributed to,Andrew Joseph Russell|Mathew B. Brady,"American, 1830–1902|American, born Ireland, 1823?–1896 New York",,"Russell, Andrew Joseph|Brady, Mathew B.",American,1830 |1823,1902 |1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268077,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.280,false,true,268079,Photographs,Photograph,"Locomotive #133, U.S.M.R.R.",,,,,,Artist|Former Attribution,Formerly attributed to,Andrew Joseph Russell|Mathew B. Brady,"American, 1830–1902|American, born Ireland, 1823?–1896 New York",,"Russell, Andrew Joseph|Brady, Mathew B.",American,1830 |1823,1902 |1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268079,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.289,false,true,268088,Photographs,Photograph,Government Saw Mill (Chattanooga),,,,,,Artist|Former Attribution,Formerly attributed to,Andrew Joseph Russell|Mathew B. Brady,"American, 1830–1902|American, born Ireland, 1823?–1896 New York",,"Russell, Andrew Joseph|Brady, Mathew B.",American,1830 |1823,1902 |1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268088,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.291,false,true,268091,Photographs,Photograph,"Fort Beauregard, Manassas, VA",,,,,,Artist|Former Attribution,Formerly attributed to,Andrew Joseph Russell|Mathew B. Brady,"American, 1830–1902|American, born Ireland, 1823?–1896 New York",,"Russell, Andrew Joseph|Brady, Mathew B.",American,1830 |1823,1902 |1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268091,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.308,false,true,268110,Photographs,Photograph,"Camp of Construction Corps, U.S. Military Railroad at City Point",,,,,,Artist|Former Attribution,Formerly attributed to,Andrew Joseph Russell|Mathew B. Brady,"American, 1830–1902|American, born Ireland, 1823?–1896 New York",,"Russell, Andrew Joseph|Brady, Mathew B.",American,1830 |1823,1902 |1896,1861–65,1861,1865,Albumen silver print from glass negative,14.4 x 20.8 cm (5 11/16 x 8 3/16 in. ),"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268110,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.316,false,true,268119,Photographs,Photograph,"Lower Pontoon Bridge, Deep Bottom, James River",,,,,,Artist|Former Attribution,Formerly attributed to,Andrew Joseph Russell|Mathew B. Brady,"American, 1830–1902|American, born Ireland, 1823?–1896 New York",,"Russell, Andrew Joseph|Brady, Mathew B.",American,1830 |1823,1902 |1896,1864,1864,1864,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268119,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.325,false,true,268129,Photographs,Photograph,"Cox's Landing, James River",,,,,,Artist|Former Attribution,Formerly attributed to,Andrew Joseph Russell|Mathew B. Brady,"American, 1830–1902|American, born Ireland, 1823?–1896 New York",,"Russell, Andrew Joseph|Brady, Mathew B.",American,1830 |1823,1902 |1896,1864,1864,1864,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268129,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.337,false,true,268142,Photographs,Photograph,"Fort Brady, Virginia",,,,,,Artist|Former Attribution,Formerly attributed to,Andrew Joseph Russell|Mathew B. Brady,"American, 1830–1902|American, born Ireland, 1823?–1896 New York",,"Russell, Andrew Joseph|Brady, Mathew B.",American,1830 |1823,1902 |1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268142,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.339,false,true,268144,Photographs,Photograph,"Artillery Camp, City Point, Virginia",,,,,,Artist|Former Attribution,Formerly attributed to,Andrew Joseph Russell|Mathew B. Brady,"American, 1830–1902|American, born Ireland, 1823?–1896 New York",,"Russell, Andrew Joseph|Brady, Mathew B.",American,1830 |1823,1902 |1896,1861–65,1861,1865,Albumen silver print from glass negative,9.6 x 9.9 cm (3 3/4 x 3 7/8 in. ),"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268144,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.341,false,true,268147,Photographs,Photograph,Extreme Left of Bermuda Hundred Lines,,,,,,Artist|Former Attribution,Formerly attributed to,Andrew Joseph Russell|Mathew B. Brady,"American, 1830–1902|American, born Ireland, 1823?–1896 New York",,"Russell, Andrew Joseph|Brady, Mathew B.",American,1830 |1823,1902 |1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268147,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.343,false,true,268149,Photographs,Photograph,Entrenchments on left of Bermuda Hundred Lines,,,,,,Artist|Former Attribution,Formerly attributed to,Andrew Joseph Russell|Mathew B. Brady,"American, 1830–1902|American, born Ireland, 1823?–1896 New York",,"Russell, Andrew Joseph|Brady, Mathew B.",American,1830 |1823,1902 |1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268149,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.353,false,true,268160,Photographs,Photograph,"Camp of Construction Corps, U.S. Military Railroad, at City Point",,,,,,Artist|Former Attribution,Formerly attributed to,Andrew Joseph Russell|Mathew B. Brady,"American, 1830–1902|American, born Ireland, 1823?–1896 New York",,"Russell, Andrew Joseph|Brady, Mathew B.",American,1830 |1823,1902 |1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268160,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.358,false,true,268165,Photographs,Photograph,"Fort Brady, James River",,,,,,Artist|Former Attribution,Formerly attributed to,Andrew Joseph Russell|Mathew B. Brady,"American, 1830–1902|American, born Ireland, 1823?–1896 New York",,"Russell, Andrew Joseph|Brady, Mathew B.",American,1830 |1823,1902 |1896,1864,1864,1864,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268165,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.359,false,true,268166,Photographs,Photograph,"Fort Brady, Virginia",,,,,,Artist|Former Attribution,Formerly attributed to,Andrew Joseph Russell|Mathew B. Brady,"American, 1830–1902|American, born Ireland, 1823?–1896 New York",,"Russell, Andrew Joseph|Brady, Mathew B.",American,1830 |1823,1902 |1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268166,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.361,false,true,268169,Photographs,Photograph,"Fort Brady, James River",,,,,,Artist|Former Attribution,Formerly attributed to,Andrew Joseph Russell|Mathew B. Brady,"American, 1830–1902|American, born Ireland, 1823?–1896 New York",,"Russell, Andrew Joseph|Brady, Mathew B.",American,1830 |1823,1902 |1896,1864,1864,1864,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268169,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.362,false,true,268170,Photographs,Photograph,"Camp of Laborers, City Point",,,,,,Artist|Former Attribution,Formerly attributed to,Andrew Joseph Russell|Mathew B. Brady,"American, 1830–1902|American, born Ireland, 1823?–1896 New York",,"Russell, Andrew Joseph|Brady, Mathew B.",American,1830 |1823,1902 |1896,1861–65,1861,1865,Albumen silver print from glass negative,10.4 x 9.5 cm (4 1/8 x 3 3/4 in. ),"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268170,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.381,false,true,268191,Photographs,Photograph,"Camp of Construction Corps, U.S. Military Railroad at City Point",,,,,,Artist|Former Attribution,Formerly attributed to,Andrew Joseph Russell|Mathew B. Brady,"American, 1830–1902|American, born Ireland, 1823?–1896 New York",,"Russell, Andrew Joseph|Brady, Mathew B.",American,1830 |1823,1902 |1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268191,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.383,false,true,268193,Photographs,Photograph,"Manassas, Virginia",,,,,,Artist|Former Attribution,Formerly attributed to,Andrew Joseph Russell|Mathew B. Brady,"American, 1830–1902|American, born Ireland, 1823?–1896 New York",,"Russell, Andrew Joseph|Brady, Mathew B.",American,1830 |1823,1902 |1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268193,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.387,false,true,268197,Photographs,Photograph,Fortifications at Manassas,,,,,,Artist|Former Attribution,Formerly attributed to,Andrew Joseph Russell|Mathew B. Brady,"American, 1830–1902|American, born Ireland, 1823?–1896 New York",,"Russell, Andrew Joseph|Brady, Mathew B.",American,1830 |1823,1902 |1896,1861–65,1861,1865,Albumen silver print from glass negative,5.6 x 8.2 cm (2 3/16 x 3 1/4 in. ),"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268197,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.432,false,true,283849,Photographs,Photograph,Bridge. U.S. Military Railroad at City Point,,,,,,Artist|Former Attribution,Attributed to|Formerly attributed to,Andrew Joseph Russell|Mathew B. Brady,"American, 1830–1902|American, born Ireland, 1823?–1896 New York",,"Russell, Andrew Joseph|Brady, Mathew B.",American,1830 |1823,1902 |1896,1861–65,1861,1865,Albumen silver print from glass negative,16.4 x 21.3 cm (6 7/16 x 8 3/8 in.),"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/283849,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -41.142.3,false,true,271897,Photographs,Photograph,Two Pupils in Greek Dress,,,,,,Artist|Printer,,Thomas Eakins|Susan Macdowell Eakins,"American, Philadelphia, Pennsylvania 1844–1916 Philadelphia, Pennsylvania|1851–1938",,"Eakins, Thomas|Eakins, Susan Macdowell",American,1844 |1851,1916 |1938,1880s,1880,1889,Platinum print,22.4 x 16.7 cm (8 13/16 x 6 9/16 in.),"Gift of Charles Bregler, 1941",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/271897,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1990.1067,false,true,266252,Photographs,Photographically illustrated book,Ichnographs from the Sandstone of Connecticut River,,,,,,Artist,,James Deane,"American, 1801–1858",,"Deane, James",American,1801,1858,published 1861,1850,1869,Salted paper prints,,"Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 1990",,,,,,,,,,,,Books,,http://www.metmuseum.org/art/collection/search/266252,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.334,false,true,286059,Photographs,Photograph,California News,,,,,,Artist,,Gabriel Harrison,"American, 1818–1902",,"Harrison, Gabriel",American,1818,1902,ca. 1850,1848,1852,Daguerreotype,Image: 14 x 10.5 cm (5 1/2 x 4 1/8 in.) 6 × 4 13/16 in. (15.3 × 12.2 cm),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Pastels & Oil Sketches on Paper,,http://www.metmuseum.org/art/collection/search/286059,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.525 (1–61),false,true,259583,Photographs,Album,Photographic Views of Sherman's Campaign,,,,,,Artist,,George N. Barnard,"American, 1819–1902",,"Barnard, George N.",American,1819,1902,1860s,1860,1869,,,"Pfeiffer and Rogers Funds, 1970",,,,,,,,,,,,Albums,,http://www.metmuseum.org/art/collection/search/259583,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.787,false,true,285757,Photographs,Negative; Photograph,"Ordnance Wharf, City Point, Virginia",,,,,,Artist,,Thomas C. Roche,"American, 1826–1895",,"Roche, Thomas C.",American,1826,1895,1865,1865,1865,Collodion glass negative,Image: 21.6 × 25.6 cm (8 1/2 × 10 1/16 in.),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Negatives,,http://www.metmuseum.org/art/collection/search/285757,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.556 (1–73),false,true,286455,Photographs,Album,"[Trees in Calaveras Grove and Views of Yosemite, California]",,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",American,1829,1916,ca. 1878,1876,1880,Albumen silver prints from glass negatives,Image: approximately 12.5 x 12.5 cm (4 15/16 x 4 15/16 in.) each Mount: 24 x 25.1 cm (9 7/16 x 9 7/8 in.) each Album: 24.8 x 26 x 3.2 cm (9 3/4 x 10 1/4 x 1 1/4 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Albums,,http://www.metmuseum.org/art/collection/search/286455,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.1117,false,true,285972,Photographs,Photomechanical print,[Native American with a Medal of President Garfield],,,,,,Artist,,William Henry Jackson,"American, 1843–1942",,"Jackson, William Henry",American,1843,1942,1890–1910,1890,1910,Photochrom,In mat: 12 x 9 13/16,"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs|Prints,,http://www.metmuseum.org/art/collection/search/285972,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.1291,false,true,285857,Photographs,Autochrome,[Bananas],,,,,,Artist,,Frederick Dellenbaugh,"American, 1853–1935",,"Dellenbaugh, Frederick",American,1853,1935,ca. 1908,1906,1910,Autochrome,8.2 x 10.7 cm (3 1/4 x 4 1/4 in.),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Transparencies,,http://www.metmuseum.org/art/collection/search/285857,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -54.561.2,false,true,269751,Photographs,Photomechanical print,Old Woman Praying,,,,,,Artist,,Rudolph Eickemeyer,"American, 1862–1932",,"Eickemeyer, Jr., Rudolph",American,1862,1932,ca. 1900,1898,1902,Photogravure,,"Gift of Mrs. William H. Schubart, 1954",,,,,,,,,,,,Photographs|Prints,,http://www.metmuseum.org/art/collection/search/269751,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.644.5,false,true,260328,Photographs,Photomechanical print,The Bridal Rose,,,,,,Artist,,Rudolph Eickemeyer,"American, 1862–1932",,"Eickemeyer, Jr., Rudolph",American,1862,1932,1900,1900,1900,Photogravure,,"The Elisha Whittelsey Collection, The Elisha Whittelsey Fund, 1972",,,,,,,,,,,,Photographs|Prints,,http://www.metmuseum.org/art/collection/search/260328,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1987.1100.275,false,true,265327,Photographs,Photomechanical print,[Prison Work Crew (ca. 9 Members) Digging Trench and 1 Guard],,,,,,Artist,,Doris Ulmann,"American, 1882–1934",,"Ulmann, Doris",American,1882,1934,"1929–30, printed 1934",1929,1930,Photogravure from glass negative,20.3 x 28.7 cm (8 x 11 5/16 in.),"Ford Motor Company Collection, Gift of Ford Motor Company and John C. Waddell, 1987",,,,,,,,,,,,Photographs|Prints,,http://www.metmuseum.org/art/collection/search/265327,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -55.635.11,false,true,271736,Photographs,Autochrome,"Emmy and Kitty - Tutzing, Bavaria",,,,,,Artist,,Frank Eugene,"American, New York 1865–1936 Munich",,"Eugene, Frank",American,1865,1936,1907,1907,1907,Autochrome,18.1 x 13.1 cm,"Alfred Stieglitz Collection, 1955",,,,,,,,,,,,Transparencies,,http://www.metmuseum.org/art/collection/search/271736,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -55.635.12,false,true,271737,Photographs,Autochrome,Stieglitz and Emmy,,,,,,Artist,,Frank Eugene,"American, New York 1865–1936 Munich",,"Eugene, Frank",American,1865,1936,1907,1907,1907,Autochrome,13.2 x 18 cm,"Alfred Stieglitz Collection, 1955",,,,,,,,,,,,Transparencies,,http://www.metmuseum.org/art/collection/search/271737,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.633.60,false,true,260271,Photographs,Photomechanical print,Baroness von P. --- Kimono,,,,,,Artist,,Frank Eugene,"American, New York 1865–1936 Munich",,"Eugene, Frank",American,1865,1936,1900s,1900,1909,Photogravure,,"Rogers Fund, 1972",,,,,,,,,,,,Photographs|Prints,,http://www.metmuseum.org/art/collection/search/260271,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.633.62,false,true,260273,Photographs,Photomechanical print,The Summer Song,,,,,,Artist,,Frank Eugene,"American, New York 1865–1936 Munich",,"Eugene, Frank",American,1865,1936,ca. 1908,1906,1910,Halftone,,"Rogers Fund, 1972",,,,,,,,,,,,Photographs|Prints,,http://www.metmuseum.org/art/collection/search/260273,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.633.119,false,true,260126,Photographs,Photomechanical print,H.R.H. Princess Rupprecht with Prince Leopold and Albrecht Johann,,,,,,Artist,,Frank Eugene,"American, New York 1865–1936 Munich",,"Eugene, Frank",American,1865,1936,"1900–1908, printed 1910",1900,1908,Photogravure,,"Rogers Fund, 1972",,,,,,,,,,,,Photographs|Prints,,http://www.metmuseum.org/art/collection/search/260126,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.633.136,false,true,260144,Photographs,Photomechanical print,[Park with Fence],,,,,,Artist,,Frank Eugene,"American, New York 1865–1936 Munich",,"Eugene, Frank",American,1865,1936,1886,1886,1886,Photogravure,16.3 x 11.9 cm (6 7/16 x 4 11/16 in.),"Rogers Fund, 1972",,,,,,,,,,,,Photographs|Prints,,http://www.metmuseum.org/art/collection/search/260144,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.633.139,false,true,260147,Photographs,Photomechanical print,The Man in Armor,,,,,,Artist,,Frank Eugene,"American, New York 1865–1936 Munich",,"Eugene, Frank",American,1865,1936,"1898, printed 1910",1898,1898,Photogravure,,"Rogers Fund, 1972",,,,,,,,,,,,Photographs|Prints,,http://www.metmuseum.org/art/collection/search/260147,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.633.147,false,true,260156,Photographs,Photomechanical print,The Archer II,,,,,,Artist,,Frank Eugene,"American, New York 1865–1936 Munich",,"Eugene, Frank",American,1865,1936,1900s,1900,1909,Photogravure,,"Rogers Fund, 1972",,,,,,,,,,,,Photographs|Prints,,http://www.metmuseum.org/art/collection/search/260156,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.633.152,false,true,260162,Photographs,Photomechanical print,Keep a Movin'! For Monkey * Fan * Tiger or Dove: Dough or Fame: Same Old Game as Love,,,,,,Artist,,Frank Eugene,"American, New York 1865–1936 Munich",,"Eugene, Frank",American,1865,1936,1900s,1900,1909,Photogravure,12.2 x 17.9 cm (4 13/16 x 7 1/16 in.),"Rogers Fund, 1972",,,,,,,,,,,,Photographs|Prints,,http://www.metmuseum.org/art/collection/search/260162,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.633.153,false,true,260163,Photographs,Photomechanical print,Nude Man with Harp,,,,,,Artist,,Frank Eugene,"American, New York 1865–1936 Munich",,"Eugene, Frank",American,1865,1936,1908–10,1908,1910,Photogravure,,"Rogers Fund, 1972",,,,,,,,,,,,Photographs|Prints,,http://www.metmuseum.org/art/collection/search/260163,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -55.635.14,false,true,269800,Photographs,Autochrome,Mrs. Selma Schubart,,,,,,Artist,,Alfred Stieglitz,"American, Hoboken, New Jersey 1864–1946 New York",,"Stieglitz, Alfred",American,1864,1946,1907,1907,1907,Autochrome,Plate: 17.8 × 12.8 cm (7 × 5 1/16 in.) Image: 16.5 × 11.6 cm (6 1/2 × 4 9/16 in.),"Alfred Stieglitz Collection, 1955",,,,,,,,,,,,Transparencies,,http://www.metmuseum.org/art/collection/search/269800,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -55.635.16,false,true,269802,Photographs,Autochrome,Walkowitz at Lake George,,,,,,Artist,,Alfred Stieglitz,"American, Hoboken, New Jersey 1864–1946 New York",,"Stieglitz, Alfred",American,1864,1946,1916,1916,1916,Autochrome,12.7 x 17.8 cm (5 x 7 in.),"Alfred Stieglitz Collection, 1955",,,,,,,,,,,,Transparencies,,http://www.metmuseum.org/art/collection/search/269802,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -55.635.17,false,true,269803,Photographs,Autochrome,[Man in Red Sweater],,,,,,Artist,,Alfred Stieglitz,"American, Hoboken, New Jersey 1864–1946 New York",,"Stieglitz, Alfred",American,1864,1946,1907,1907,1907,Autochrome,17.9 x 12.8 cm (7 1/16 x 5 1/16 in.),"Alfred Stieglitz Collection, 1955",,,,,,,,,,,,Transparencies,,http://www.metmuseum.org/art/collection/search/269803,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -55.635.18,false,true,269804,Photographs,Autochrome,Frank Eugene,,,,,,Artist,,Alfred Stieglitz,"American, Hoboken, New Jersey 1864–1946 New York",,"Stieglitz, Alfred",American,1864,1946,1907,1907,1907,Autochrome,18 x 13 cm (7 1/16 x 5 1/8 in.),"Alfred Stieglitz Collection, 1955",,,,,,,,,,,,Transparencies,,http://www.metmuseum.org/art/collection/search/269804,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.476,false,true,285938,Photographs,Autochrome,[Two Men Playing Chess],,,,,,Artist,,Alfred Stieglitz,"American, Hoboken, New Jersey 1864–1946 New York",,"Stieglitz, Alfred",American,1864,1946,June 1907,1907,1907,Autochrome,9 x 12 cm (3.5 x 4.7 in.),"Gilman Collection, Purchase, Mr. and Mrs. Henry R. Kravis Gift, 2005",,,,,,,,,,,,Transparencies,,http://www.metmuseum.org/art/collection/search/285938,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.478,false,true,286176,Photographs,Autochrome,Portrait of Kitty,,,,,,Artist,,Alfred Stieglitz,"American, Hoboken, New Jersey 1864–1946 New York",,"Stieglitz, Alfred",American,1864,1946,ca. 1911,1909,1913,Autochrome,17.8 x 12.7 cm (7x 5 in.),"Gilman Collection, Purchase, Mrs. Walter Annenberg and The Annenberg Foundation Gift, 2005",,,,,,,,,,,,Transparencies,,http://www.metmuseum.org/art/collection/search/286176,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -49.55.19,false,true,269310,Photographs,Photomechanical print,Two Towers - New York,,,,,,Artist,,Alfred Stieglitz,"American, Hoboken, New Jersey 1864–1946 New York",,"Stieglitz, Alfred",American,1864,1946,"1911, printed in or before 1913",1911,1911,Photogravure,32.7 x 25.3 cm. (12 7/8 x 9 15/16 in.),"Alfred Stieglitz Collection, 1949",,,,,,,,,,,,Photographs|Prints,,http://www.metmuseum.org/art/collection/search/269310,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -58.577.1,false,true,270030,Photographs,Photomechanical print,Two Towers - New York,,,,,,Artist,,Alfred Stieglitz,"American, Hoboken, New Jersey 1864–1946 New York",,"Stieglitz, Alfred",American,1864,1946,"1911, printed 1913",1911,1911,Photogravure,19.5 x 15.9 cm. (7 11/16 x 6 1/4 in.),"Gift of J. B. Neumann, 1958",,,,,,,,,,,,Photographs|Prints,,http://www.metmuseum.org/art/collection/search/270030,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -58.577.2,false,true,270041,Photographs,Photomechanical print,Old and New New York,,,,,,Artist,,Alfred Stieglitz,"American, Hoboken, New Jersey 1864–1946 New York",,"Stieglitz, Alfred",American,1864,1946,"1910, printed 1911",1910,1910,Photogravure,20.2 x 15.8 cm. (7 15/16 x 6 1/4 in.),"Gift of J. B. Neumann, 1958",,,,,,,,,,,,Photographs|Prints,,http://www.metmuseum.org/art/collection/search/270041,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.43.419,false,true,267836,Photographs,Photomechanical print,The Steerage,,,,,,Artist,,Alfred Stieglitz,"American, Hoboken, New Jersey 1864–1946 New York",,"Stieglitz, Alfred",American,1864,1946,"1907, printed in or before 1913",1907,1907,Photogravure,32.2 x 25.8 cm (12 11/16 x 10 3/16 in.),"Alfred Stieglitz Collection, 1933",,,,,,,,,,,,Photographs|Prints,,http://www.metmuseum.org/art/collection/search/267836,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -58.577.18,false,true,270039,Photographs,Photomechanical print,Snapshot - In the New York Central Yards,,,,,,Artist,,Alfred Stieglitz,"American, Hoboken, New Jersey 1864–1946 New York",,"Stieglitz, Alfred",American,1864,1946,"1903, printed 1907",1903,1903,Photogravure,19.4 x 15.9 cm (7 5/8 x 6 1/4 in.),"Gift of J. B. Neumann, 1958",,,,,,,,,,,,Photographs|Prints,,http://www.metmuseum.org/art/collection/search/270039,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -58.577.20,false,true,270042,Photographs,Photomechanical print,Winter - Fifth Avenue,,,,,,Artist,,Alfred Stieglitz,"American, Hoboken, New Jersey 1864–1946 New York",,"Stieglitz, Alfred",American,1864,1946,"1893, printed 1905",1893,1893,Photogravure,21.8 x 15.4 cm. (8 9/16 x 6 1/16 in.),"Gift of J. B. Neumann, 1958",,,,,,,,,,,,Photographs|Prints,,http://www.metmuseum.org/art/collection/search/270042,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -58.577.21,false,true,270043,Photographs,Photomechanical print,Nearing Land,,,,,,Artist,,Alfred Stieglitz,"American, Hoboken, New Jersey 1864–1946 New York",,"Stieglitz, Alfred",American,1864,1946,"1904, printed 1905",1904,1904,Photogravure,21.6 x 17.4 cm. (8 1/2 x 6 7/8 in.),"Gift of J. B. Neumann, 1958",,,,,,,,,,,,Photographs|Prints,,http://www.metmuseum.org/art/collection/search/270043,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -58.577.28,false,true,270050,Photographs,Photomechanical print,The City across the River,,,,,,Artist,,Alfred Stieglitz,"American, Hoboken, New Jersey 1864–1946 New York",,"Stieglitz, Alfred",American,1864,1946,"1910, printed 1911",1910,1910,Photogravure,20.0 x 16.0 cm. (7 7/8 x 6 5/16 in.),"Gift of J. B. Neumann, 1958",,,,,,,,,,,,Photographs|Prints,,http://www.metmuseum.org/art/collection/search/270050,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -58.577.30,false,true,270053,Photographs,Photomechanical print,Lower Manhattan,,,,,,Artist,,Alfred Stieglitz,"American, Hoboken, New Jersey 1864–1946 New York",,"Stieglitz, Alfred",American,1864,1946,"1910, printed 1911",1910,1910,Photogravure,16.0 x 19.8 cm. (6 5/16 x 7 13/16 in.),"Gift of J. B. Neumann, 1958",,,,,,,,,,,,Photographs|Prints,,http://www.metmuseum.org/art/collection/search/270053,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -58.577.31,false,true,270054,Photographs,Photomechanical print,The Aeroplane,,,,,,Artist,,Alfred Stieglitz,"American, Hoboken, New Jersey 1864–1946 New York",,"Stieglitz, Alfred",American,1864,1946,"1910, printed 1911",1910,1910,Photogravure,14.5 x 17.5 cm (5 11/16 x 6 7/8 in.),"Gift of J. B. Neumann, 1958",,,,,,,,,,,,Photographs|Prints,,http://www.metmuseum.org/art/collection/search/270054,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -58.577.34,false,true,270057,Photographs,Photomechanical print,The Swimming Lesson,,,,,,Artist,,Alfred Stieglitz,"American, Hoboken, New Jersey 1864–1946 New York",,"Stieglitz, Alfred",American,1864,1946,"1906, printed 1911",1906,1906,Photogravure,14.8 x 23.0 cm. (5 13/16 x 9 1/16 in.),"Gift of J. B. Neumann, 1958",,,,,,,,,,,,Photographs|Prints,,http://www.metmuseum.org/art/collection/search/270057,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -58.577.36,false,true,270059,Photographs,Photomechanical print,"A Snapshot, Paris",,,,,,Artist,,Alfred Stieglitz,"American, Hoboken, New Jersey 1864–1946 New York",,"Stieglitz, Alfred",American,1864,1946,"1911, printed 1912",1911,1911,Photogravure,13.8 x 17.4 cm. (5 7/16 x 6 7/8 in.),"Gift of J. B. Neumann, 1958",,,,,,,,,,,,Photographs|Prints,,http://www.metmuseum.org/art/collection/search/270059,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1987.1100.116,false,true,265151,Photographs,Photograph,[View of Rooftops],,,,,,Artist,,Morton Schamberg,"American, Philadelphia, Pennsylvania 1881–1918 Philadelphia, Pennsylvania",,"Schamberg, Morton",American,1881,1918,1917,1917,1917,Gelatin silver print,23.7 x 19.0 cm (9 5/16 x 7 1/2 in.),"Ford Motor Company Collection, Gift of Ford Motor Company and John C. Waddell, 1987",,,,,,,,,,,,Pastels & Oil Sketches on Paper,,http://www.metmuseum.org/art/collection/search/265151,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.83,false,true,283183,Photographs,Photograph,[The Great Man Has Fallen],,,,,,Artist,,Robert H. Vance,"American, died 1876",,"Vance, Robert H.",American,1876,1876,1856,1856,1856,Daguerreotype,Image: 13.5 x 18.8 cm (5 5/16 x 7 3/8 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/283183,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.207,false,true,286346,Photographs,Photograph,[View in New Hampshire],,,,,,Artist,,Samuel Bemis,"American, 1789–1881",,"Bemis, Samuel",American,1789,1881,1840–41,1840,1841,Daguerreotype,Image: 5 3/4 × 7 7/8 in. (14.6 × 20 cm) Frame: 9 5/16 × 11 7/16 in. (23.6 × 29 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286346,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.1135,false,true,285732,Photographs,Photograph,[Gentleman],,,,,,Artist,,Henry Fitz Jr.,"American, 1808–1863",,"Fitz Jr., Henry",American,1808,1863,ca. 1840,1838,1842,Daguerreotype,Approx. ninth-plate,"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285732,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.400.50,false,true,291803,Photographs,Daguerreotype,"[Man Wearing Coat and Gloves, Holding Hat, Seated in Front of Painted Outdoor Backdrop]",,,,,,Artist,Attributed to,Samuel Broadbent Jr.,"American, 1810–1880",,"Broadbent, Samuel, Jr.",American,1810,1880,1840s,1840,1849,Daguerreotype,Image: 12 x 9 cm (4 3/4 x 3 9/16 in.) Plate: 14 x 10.8 cm (5 1/2 x 4 1/4 in.) Case: 2.2 x 15.2 x 12.4 cm (7/8 x 6 x 4 7/8 in.),"Bequest of Herbert Mitchell, 2008",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/291803,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.400.52,false,true,291805,Photographs,Daguerreotype,"Josiah Bunting, 85, with George M. Bunting, 17 Months",,,,,,Artist,,Samuel Broadbent Jr.,"American, 1810–1880",,"Broadbent, Samuel, Jr.",American,1810,1880,1850s,1850,1859,Daguerreotype,Image: 8.8 x 6.8 cm (3 7/16 x 2 11/16 in.) Plate: 10.8 x 8.3 cm (4 1/4 x 3 1/4 in.) Case: 2.2 x 11.7 x 9.2 cm (7/8 x 4 5/8 x 3 5/8 in.),"Bequest of Herbert Mitchell, 2008",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/291805,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.869,false,true,286550,Photographs,Photograph,"[Landscape, Pride's Crossing]",,,,,,Artist,,Samuel Masury,"American, 1818–1874",,"Masury, Samuel",American,1818,1874,ca. 1856,1854,1858,Salted paper print from paper negative,10 1/8 x 13 1/2,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286550,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1980.1056.1,false,true,262760,Photographs,Stereograph,Broadway on a Rainy Day,,,,,,Artist,,Edward Anthony,"American, 1818–1888",,"Anthony, Edward",American,1818,1888,1859,1859,1859,Albumen silver print,8.3 x 15.7 cm (3 1/4 x 6 3/16 in.),"Warner Communications Inc. Purchase Fund, 1980",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/262760,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1980.1056.4,false,true,262763,Photographs,Stereograph,[Broadway with horse-drawn carriages],,,,,,Artist,,Edward Anthony,"American, 1818–1888",,"Anthony, Edward",American,1818,1888,ca. 1860s,1858,1862,Albumen silver print,,"Warner Communications Inc. Purchase Fund, 1980",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/262763,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.457.3626,false,true,291959,Photographs,Photograph,Return of the Japanese Embassy from City Hall,,,,,,Artist,,Edward Anthony,"American, 1818–1888",,"Anthony, Edward",American,1818,1888,1860,1860,1860,Albumen silver print from glass negative,Image: 7.5 x 15 cm (2 15/16 x 5 7/8 in.) Mount: 8.2 x 17.1 cm (3 1/4 x 6 3/4 in.),"Herbert Mitchell Collection, 2007",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/291959,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.457.3682,false,true,291961,Photographs,Photograph,"The Embassy Leave the Metropolitan for the City Hall, the Seventh Regiment Form a Hollow Square With the Carriages of the Embassy in the Middle",,,,,,Artist,,Edward Anthony,"American, 1818–1888",,"Anthony, Edward",American,1818,1888,1860,1860,1860,Albumen silver print from glass negative,Image: 7.5 x 14.2 cm (2 15/16 x 5 9/16 in.) Mount: 8.2 x 17.4 cm (3 1/4 x 6 7/8 in.),"Herbert Mitchell Collection, 2007",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/291961,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.457.3683,false,true,291962,Photographs,Photograph,The Populace Begin to Gather in Front of the City Hall to Witness the Arrival of the Embassy on Their Visit to the Governor and Mayor,,,,,,Artist,,Edward Anthony,"American, 1818–1888",,"Anthony, Edward",American,1818,1888,1860,1860,1860,Albumen silver print from glass negative,Image: 7.6 x 14.2 cm (3 x 5 9/16 in.) Mount: 8.2 x 17.3 cm (3 1/4 x 6 13/16 in.),"Herbert Mitchell Collection, 2007",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/291962,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2012.19,false,true,301887,Photographs,Photograph,"Savannah, Georgia, No. 2",,,,,,Artist,,George N. Barnard,"American, 1819–1902",,"Barnard, George N.",American,1819,1902,1866,1866,1866,Albumen silver print from glass negative,Image: 25.5 x 36.1 cm (10 1/16 x 14 3/16 in.) Mount: 40.9 x 51.1 cm (16 1/8 x 20 1/8 in.),"The Horace W. Goldsmith Foundation Fund, through Joyce and Robert Menschel, 2012",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/301887,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -51.611.484,false,true,269550,Photographs,Photograph,"Rebel Works in Front of Atlanta, Georgia, No. 2",,,,,,Artist,,George N. Barnard,"American, 1819–1902",,"Barnard, George N.",American,1819,1902,1864,1864,1864,Albumen silver print,,"Gift of Mrs. Robert Ingersoll Aitken, 1951",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269550,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.525 (1),false,true,294445,Photographs,Photograph,Sherman and His Generals,,,,,,Artist,,George N. Barnard,"American, 1819–1902",,"Barnard, George N.",American,1819,1902,1860s,1860,1869,Albumen silver print from glass negative,,"Pfeiffer and Rogers Funds, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/294445,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.525 (2),false,true,294446,Photographs,Photograph,"The Capitol, Nashville, Tennessee",,,,,,Artist,,George N. Barnard,"American, 1819–1902",,"Barnard, George N.",American,1819,1902,1860s,1860,1869,Albumen silver print from glass negative,,"Pfeiffer and Rogers Funds, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/294446,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.525 (3),false,true,294447,Photographs,Photograph,Nashville from the Capitol,,,,,,Artist,,George N. Barnard,"American, 1819–1902",,"Barnard, George N.",American,1819,1902,1860s,1860,1869,Albumen silver print from glass negative,,"Pfeiffer and Rogers Funds, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/294447,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.525 (4),false,true,294448,Photographs,Photograph,Trestle Bridge at Whiteside,,,,,,Artist,,George N. Barnard,"American, 1819–1902",,"Barnard, George N.",American,1819,1902,1860s,1860,1869,Albumen silver print from glass negative,,"Pfeiffer and Rogers Funds, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/294448,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.525 (5),false,true,294449,Photographs,Photograph,Whiteside Valley Below the Bridge,,,,,,Artist,,George N. Barnard,"American, 1819–1902",,"Barnard, George N.",American,1819,1902,1860s,1860,1869,Albumen silver print from glass negative,,"Pfeiffer and Rogers Funds, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/294449,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.525 (6),false,true,294450,Photographs,Photograph,"Pass in the Racoon Range, Whiteside No. 1",,,,,,Artist,,George N. Barnard,"American, 1819–1902",,"Barnard, George N.",American,1819,1902,1860s,1860,1869,Albumen silver print from glass negative,,"Pfeiffer and Rogers Funds, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/294450,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.525 (7),false,true,294451,Photographs,Photograph,"Pass in the Raccoon Range, Whiteside No. 2",,,,,,Artist,,George N. Barnard,"American, 1819–1902",,"Barnard, George N.",American,1819,1902,1860s,1860,1869,Albumen silver print from glass negative,,"Pfeiffer and Rogers Funds, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/294451,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.525 (8),false,true,294452,Photographs,Photograph,Chattanooga from the North,,,,,,Artist,,George N. Barnard,"American, 1819–1902",,"Barnard, George N.",American,1819,1902,1860s,1860,1869,Albumen silver print from glass negative,,"Pfeiffer and Rogers Funds, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/294452,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.525 (9),false,true,294453,Photographs,Photograph,Mission Ridge from Orchard Knob,,,,,,Artist,,George N. Barnard,"American, 1819–1902",,"Barnard, George N.",American,1819,1902,1860s,1860,1869,Albumen silver print from glass negative,,"Pfeiffer and Rogers Funds, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/294453,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.281,false,true,286658,Photographs,Photograph,"Bonaventure Cemetery, Four Miles from Savannah",,,,,,Artist,,George N. Barnard,"American, 1819–1902",,"Barnard, George N.",American,1819,1902,1866,1866,1866,Albumen silver print from glass negative,Image: 34 x 26.4 cm (13 3/8 x 10 3/8 in.),"Gilman Collection, Purchase, Ann Tenenbaum and Thomas H. Lee Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286658,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.525 (10),false,true,294454,Photographs,Photograph,Orchard Knob from Mission Ridge,,,,,,Artist,,George N. Barnard,"American, 1819–1902",,"Barnard, George N.",American,1819,1902,1860s,1860,1869,Albumen silver print from glass negative,,"Pfeiffer and Rogers Funds, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/294454,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.525 (11),false,true,294455,Photographs,Photograph,The Crest of Mission Ridge,,,,,,Artist,,George N. Barnard,"American, 1819–1902",,"Barnard, George N.",American,1819,1902,1860s,1860,1869,Albumen silver print from glass negative,,"Pfeiffer and Rogers Funds, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/294455,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.525 (12),false,true,294456,Photographs,Photograph,Mission Ridge Scene of Sherman's Attack,,,,,,Artist,,George N. Barnard,"American, 1819–1902",,"Barnard, George N.",American,1819,1902,1860s,1860,1869,Albumen silver print from glass negative,,"Pfeiffer and Rogers Funds, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/294456,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.525 (13),false,true,294457,Photographs,Photograph,Chattanooga Valley from Lookout Mountain,,,,,,Artist,,George N. Barnard,"American, 1819–1902",,"Barnard, George N.",American,1819,1902,1860s,1860,1869,Albumen silver print from glass negative,,"Pfeiffer and Rogers Funds, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/294457,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.525 (14),false,true,294458,Photographs,Photograph,Chattanooga Valley from Lookout Mountain No. 2,,,,,,Artist,,George N. Barnard,"American, 1819–1902",,"Barnard, George N.",American,1819,1902,1860s,1860,1869,Albumen silver print from glass negative,,"Pfeiffer and Rogers Funds, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/294458,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.525 (15),false,true,294459,Photographs,Photograph,"Lu-La Lake, Lookout Mountain",,,,,,Artist,,George N. Barnard,"American, 1819–1902",,"Barnard, George N.",American,1819,1902,1864 or 1866,1864,1866,Albumen silver print from glass negative,Image: 25.6 × 35.9 cm (10 1/16 × 14 1/8 in.),"Pfeiffer and Rogers Funds, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/294459,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.525 (16),false,true,294460,Photographs,Photograph,"The John Ross House, Ringold, Georgia",,,,,,Artist,,George N. Barnard,"American, 1819–1902",,"Barnard, George N.",American,1819,1902,1860s,1860,1869,Albumen silver print from glass negative,,"Pfeiffer and Rogers Funds, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/294460,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.525 (17),false,true,294461,Photographs,Photograph,"Ringold, Georgia",,,,,,Artist,,George N. Barnard,"American, 1819–1902",,"Barnard, George N.",American,1819,1902,1860s,1860,1869,Albumen silver print from glass negative,,"Pfeiffer and Rogers Funds, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/294461,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.525 (18),false,true,294462,Photographs,Photograph,"Buzzard Roost, Georgia",,,,,,Artist,,George N. Barnard,"American, 1819–1902",,"Barnard, George N.",American,1819,1902,1860s,1860,1869,Albumen silver print from glass negative,,"Pfeiffer and Rogers Funds, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/294462,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.525 (19),false,true,294463,Photographs,Photograph,"Battle Ground of Resacca, Georgia No. 1",,,,,,Artist,,George N. Barnard,"American, 1819–1902",,"Barnard, George N.",American,1819,1902,1860s,1860,1869,Albumen silver print from glass negative,,"Pfeiffer and Rogers Funds, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/294463,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.525 (20),false,true,294464,Photographs,Photograph,"Battle Ground of Resacca, Georgia No. 2",,,,,,Artist,,George N. Barnard,"American, 1819–1902",,"Barnard, George N.",American,1819,1902,1860s,1860,1869,Albumen silver print from glass negative,,"Pfeiffer and Rogers Funds, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/294464,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.525 (21),false,true,294465,Photographs,Photograph,"Battle Ground of Resacca, Georgia No. 3",,,,,,Artist,,George N. Barnard,"American, 1819–1902",,"Barnard, George N.",American,1819,1902,1860s,1860,1869,Albumen silver print from glass negative,,"Pfeiffer and Rogers Funds, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/294465,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.525 (22),false,true,294466,Photographs,Photograph,"Battle Ground of Resacca, Georgia No. 4",,,,,,Artist,,George N. Barnard,"American, 1819–1902",,"Barnard, George N.",American,1819,1902,1860s,1860,1869,Albumen silver print from glass negative,,"Pfeiffer and Rogers Funds, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/294466,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.525 (23),false,true,294467,Photographs,Photograph,Defences of the Etawah Bridge,,,,,,Artist,,George N. Barnard,"American, 1819–1902",,"Barnard, George N.",American,1819,1902,1860s,1860,1869,Albumen silver print from glass negative,,"Pfeiffer and Rogers Funds, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/294467,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.525 (24),false,true,294468,Photographs,Photograph,Allatoona from the Etawah,,,,,,Artist,,George N. Barnard,"American, 1819–1902",,"Barnard, George N.",American,1819,1902,1860s,1860,1869,Albumen silver print from glass negative,,"Pfeiffer and Rogers Funds, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/294468,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.525 (25),false,true,294469,Photographs,Photograph,"Battle Field of New Hope Church, Georgia No. 1",,,,,,Artist,,George N. Barnard,"American, 1819–1902",,"Barnard, George N.",American,1819,1902,1860s,1860,1869,Albumen silver print from glass negative,,"Pfeiffer and Rogers Funds, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/294469,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.525 (26),false,true,294470,Photographs,Photograph,"Battle Field of New Hope Church, Georgia No. 2",,,,,,Artist,,George N. Barnard,"American, 1819–1902",,"Barnard, George N.",American,1819,1902,1860s,1860,1869,Albumen silver print from glass negative,,"Pfeiffer and Rogers Funds, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/294470,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.525 (27),false,true,294471,Photographs,Photograph,"The ""Hell Hole"" New Hope Church, Georgia",,,,,,Artist,,George N. Barnard,"American, 1819–1902",,"Barnard, George N.",American,1819,1902,1860s,1860,1869,Albumen silver print from glass negative,,"Pfeiffer and Rogers Funds, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/294471,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.525 (28),false,true,294472,Photographs,Photograph,"The Allatoona Pass, Georgia",,,,,,Artist,,George N. Barnard,"American, 1819–1902",,"Barnard, George N.",American,1819,1902,1860s,1860,1869,Albumen silver print from glass negative,,"Pfeiffer and Rogers Funds, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/294472,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.525 (29),false,true,294473,Photographs,Photograph,"The Allatoona Pass Looking North, Georgia",,,,,,Artist,,George N. Barnard,"American, 1819–1902",,"Barnard, George N.",American,1819,1902,1860s,1860,1869,Albumen silver print from glass negative,,"Pfeiffer and Rogers Funds, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/294473,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.525 (30),false,true,294474,Photographs,Photograph,Pine Mountain,,,,,,Artist,,George N. Barnard,"American, 1819–1902",,"Barnard, George N.",American,1819,1902,1860s,1860,1869,Albumen silver print from glass negative,,"Pfeiffer and Rogers Funds, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/294474,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.525 (31),false,true,294475,Photographs,Photograph,"The Front of Kenesaw Mountain, Georgia",,,,,,Artist,,George N. Barnard,"American, 1819–1902",,"Barnard, George N.",American,1819,1902,1860s,1860,1869,Albumen silver print from glass negative,,"Pfeiffer and Rogers Funds, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/294475,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.525 (32),false,true,294476,Photographs,Photograph,"View of Kenesaw Mountain, Georgia",,,,,,Artist,,George N. Barnard,"American, 1819–1902",,"Barnard, George N.",American,1819,1902,1860s,1860,1869,Albumen silver print from glass negative,,"Pfeiffer and Rogers Funds, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/294476,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.525 (33),false,true,294477,Photographs,Photograph,"South Bank of the Chattahoochie, Georgia",,,,,,Artist,,George N. Barnard,"American, 1819–1902",,"Barnard, George N.",American,1819,1902,1860s,1860,1869,Albumen silver print from glass negative,,"Pfeiffer and Rogers Funds, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/294477,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.525 (34),false,true,294478,Photographs,Photograph,"The Battle Field of Peach Tree Creek, Georgia",,,,,,Artist,,George N. Barnard,"American, 1819–1902",,"Barnard, George N.",American,1819,1902,1860s,1860,1869,Albumen silver print from glass negative,,"Pfeiffer and Rogers Funds, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/294478,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.525 (35),false,true,294479,Photographs,Photograph,Scene of General McPherson's Death,,,,,,Artist,,George N. Barnard,"American, 1819–1902",,"Barnard, George N.",American,1819,1902,1864 or 1866,1864,1866,Albumen silver print from glass negative,Image: 25.4 × 36.1 cm (10 × 14 3/16 in.),"Pfeiffer and Rogers Funds, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/294479,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.525 (36),false,true,294480,Photographs,Photograph,"Battle Field of Atlanta, Georgia, July 22nd 1864 No. 1",,,,,,Artist,,George N. Barnard,"American, 1819–1902",,"Barnard, George N.",American,1819,1902,1860s,1860,1869,Albumen silver print from glass negative,,"Pfeiffer and Rogers Funds, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/294480,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.525 (37),false,true,294481,Photographs,Photograph,"Battle Field of Atlanta, Georgia, July 22nd 1864 No. 2",,,,,,Artist,,George N. Barnard,"American, 1819–1902",,"Barnard, George N.",American,1819,1902,1860s,1860,1869,Albumen silver print from glass negative,,"Pfeiffer and Rogers Funds, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/294481,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.525 (38),false,true,294482,Photographs,Photograph,"The Potter House, Atlanta",,,,,,Artist,,George N. Barnard,"American, 1819–1902",,"Barnard, George N.",American,1819,1902,1860s,1860,1869,Albumen silver print from glass negative,,"Pfeiffer and Rogers Funds, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/294482,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.525 (39),false,true,294483,Photographs,Photograph,"Rebel Works in Front of Atlanta, Georgia No. 1",,,,,,Artist,,George N. Barnard,"American, 1819–1902",,"Barnard, George N.",American,1819,1902,1860s,1860,1869,Albumen silver print from glass negative,,"Pfeiffer and Rogers Funds, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/294483,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.525 (40),false,true,294484,Photographs,Photograph,"Rebel Works in Front of Atlanta, Georgia No. 2",,,,,,Artist,,George N. Barnard,"American, 1819–1902",,"Barnard, George N.",American,1819,1902,1860s,1860,1869,Albumen silver print from glass negative,,"Pfeiffer and Rogers Funds, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/294484,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.525 (41),false,true,294485,Photographs,Photograph,"Rebel Works in Front of Atlanta, Georgia No. 3",,,,,,Artist,,George N. Barnard,"American, 1819–1902",,"Barnard, George N.",American,1819,1902,1860s,1860,1869,Albumen silver print from glass negative,,"Pfeiffer and Rogers Funds, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/294485,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.525 (42),false,true,294486,Photographs,Photograph,"Rebel Works in Front of Atlanta, Georgia No. 4",,,,,,Artist,,George N. Barnard,"American, 1819–1902",,"Barnard, George N.",American,1819,1902,1860s,1860,1869,Albumen silver print from glass negative,,"Pfeiffer and Rogers Funds, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/294486,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.525 (43),false,true,294487,Photographs,Photograph,"Rebel Works in Front of Atlanta, Georgia No. 5",,,,,,Artist,,George N. Barnard,"American, 1819–1902",,"Barnard, George N.",American,1819,1902,1860s,1860,1869,Albumen silver print from glass negative,,"Pfeiffer and Rogers Funds, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/294487,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.525 (44),false,true,294488,Photographs,Photograph,Destruction of Hood's Ordinance Train,,,,,,Artist,,George N. Barnard,"American, 1819–1902",,"Barnard, George N.",American,1819,1902,1860s,1860,1869,Albumen silver print from glass negative,,"Pfeiffer and Rogers Funds, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/294488,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.525 (45),false,true,294489,Photographs,Photograph,"City of Atlanta, Georgia No. 1",,,,,,Artist,,George N. Barnard,"American, 1819–1902",,"Barnard, George N.",American,1819,1902,1860s,1860,1869,Albumen silver print from glass negative,,"Pfeiffer and Rogers Funds, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/294489,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.525 (46),false,true,294490,Photographs,Photograph,"City of Atlanta, Georgia No. 2",,,,,,Artist,,George N. Barnard,"American, 1819–1902",,"Barnard, George N.",American,1819,1902,1866,1866,1866,Albumen silver print from glass negative,Image: 25.6 × 35.5 cm (10 1/16 × 14 in.),"Pfeiffer and Rogers Funds, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/294490,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.525 (47),false,true,294491,Photographs,Photograph,"Savanah River, Near Savanah, Georgia",,,,,,Artist,,George N. Barnard,"American, 1819–1902",,"Barnard, George N.",American,1819,1902,1860s,1860,1869,Albumen silver print from glass negative,,"Pfeiffer and Rogers Funds, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/294491,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.525 (48),false,true,294492,Photographs,Photograph,"Buen-Ventura, Savanah, Georgia",,,,,,Artist,,George N. Barnard,"American, 1819–1902",,"Barnard, George N.",American,1819,1902,1860s,1860,1869,Albumen silver print from glass negative,,"Pfeiffer and Rogers Funds, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/294492,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.525 (49),false,true,294493,Photographs,Photograph,"Savanah, Georgia No. 1",,,,,,Artist,,George N. Barnard,"American, 1819–1902",,"Barnard, George N.",American,1819,1902,1860s,1860,1869,Albumen silver print from glass negative,,"Pfeiffer and Rogers Funds, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/294493,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.525 (50),false,true,294494,Photographs,Photograph,"Savanah, Georgia No. 2",,,,,,Artist,,George N. Barnard,"American, 1819–1902",,"Barnard, George N.",American,1819,1902,1860s,1860,1869,Albumen silver print from glass negative,,"Pfeiffer and Rogers Funds, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/294494,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.525 (51),false,true,294495,Photographs,Photograph,"Fountain, Savanah, Georgia",,,,,,Artist,,George N. Barnard,"American, 1819–1902",,"Barnard, George N.",American,1819,1902,1860s,1860,1869,Albumen silver print from glass negative,,"Pfeiffer and Rogers Funds, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/294495,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.525 (52),false,true,294496,Photographs,Photograph,"The New Capitol, Columbia, South Carolina",,,,,,Artist,,George N. Barnard,"American, 1819–1902",,"Barnard, George N.",American,1819,1902,1860s,1860,1869,Albumen silver print from glass negative,,"Pfeiffer and Rogers Funds, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/294496,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.525 (53),false,true,294497,Photographs,Photograph,Columbia from the Capitol,,,,,,Artist,,George N. Barnard,"American, 1819–1902",,"Barnard, George N.",American,1819,1902,1860s,1860,1869,Albumen silver print from glass negative,,"Pfeiffer and Rogers Funds, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/294497,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.525 (54),false,true,294498,Photographs,Photograph,"Ruins in Columbia, South Carolina",,,,,,Artist,,George N. Barnard,"American, 1819–1902",,"Barnard, George N.",American,1819,1902,1860s,1860,1869,Albumen silver print from glass negative,,"Pfeiffer and Rogers Funds, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/294498,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.525 (55),false,true,294499,Photographs,Photograph,"Ruins in Columbia, South Carolina No. 2",,,,,,Artist,,George N. Barnard,"American, 1819–1902",,"Barnard, George N.",American,1819,1902,1860s,1860,1869,Albumen silver print from glass negative,,"Pfeiffer and Rogers Funds, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/294499,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.525 (56),false,true,294500,Photographs,Photograph,Fort Sumpter,,,,,,Artist,,George N. Barnard,"American, 1819–1902",,"Barnard, George N.",American,1819,1902,1860s,1860,1869,Albumen silver print from glass negative,,"Pfeiffer and Rogers Funds, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/294500,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.525 (57),false,true,294501,Photographs,Photograph,Interior View of Fort Sumpter,,,,,,Artist,,George N. Barnard,"American, 1819–1902",,"Barnard, George N.",American,1819,1902,1860s,1860,1869,Albumen silver print from glass negative,,"Pfeiffer and Rogers Funds, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/294501,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.525 (58),false,true,294502,Photographs,Photograph,Exterior View of Fort Sumpter,,,,,,Artist,,George N. Barnard,"American, 1819–1902",,"Barnard, George N.",American,1819,1902,1860s,1860,1869,Albumen silver print from glass negative,,"Pfeiffer and Rogers Funds, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/294502,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.525 (59),false,true,294503,Photographs,Photograph,"Ruins of the Pinckney Mansion, Charleston, South Carolina",,,,,,Artist,,George N. Barnard,"American, 1819–1902",,"Barnard, George N.",American,1819,1902,1860s,1860,1869,Albumen silver print from glass negative,,"Pfeiffer and Rogers Funds, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/294503,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.525 (60),false,true,294504,Photographs,Photograph,"Ruins in Charleston, South Carolina",,,,,,Artist,,George N. Barnard,"American, 1819–1902",,"Barnard, George N.",American,1819,1902,1860s,1860,1869,Albumen silver print from glass negative,,"Pfeiffer and Rogers Funds, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/294504,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.525 (61),false,true,294505,Photographs,Photograph,"Ruins of the R.R. Depot, Charleston, South Carolina",,,,,,Artist,,George N. Barnard,"American, 1819–1902",,"Barnard, George N.",American,1819,1902,1860s,1860,1869,Albumen silver print from glass negative,,"Pfeiffer and Rogers Funds, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/294505,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.1232,false,true,285727,Photographs,Photograph,"Quaker Gun, Centreville, Virginia",,,,,,Artist,,George N. Barnard,"American, 1819–1902",,"Barnard, George N.",American,1819,1902,March 1862,1862,1862,Albumen silver print from glass negative,Image: 8.1 × 9.4 cm (3 3/16 × 3 11/16 in.) Mount: 10.7 × 12.6 cm (4 3/16 × 4 15/16 in.),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285727,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1997.382.52,false,true,282057,Photographs,Photograph,Niagara Falls,,,,,,Artist,Possibly by,Silas A. Holmes,"American, 1820–1886",,"Holmes, Silas S.",American,1820,1886,ca. 1855,1853,1857,Salted paper print from glass negative,30.5 x 40.4 cm (12 x 15 7/8 in. ) irregular,"The Rubel Collection, Purchase, Lila Acheson Wallace Gift, 1997",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/282057,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2011.556.3,false,true,301973,Photographs,Photograph,[Rev. Mr. Frederick T. Gray and Deacons of Old Bullfinch Street Church],,,,,,Artist,Attributed to,John Adams Whipple,"American, 1822–1891",,"Whipple, John Adams",American,1822,1891,ca. 1845,1840,1850,Daguerreotype,Plate: 10.8 x 14 cm (4 1/4 x 5 1/2 in.),"Gift of Isaac Lagnado, in honor of Elliott Cohen, 2011",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/301973,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1997.382.41,true,true,282046,Photographs,Photograph,[Cornelius Conway Felton with His Hat and Coat],,,,,,Artist,,John Adams Whipple,"American, 1822–1891",,"Whipple, John Adams",American,1822,1891,early 1850s,1850,1853,Daguerreotype,visible: 8.3 x 7 cm (3 1/4 x 2 3/4 in.) each,"The Rubel Collection, Purchase, Lila Acheson Wallace, W. Bruce and Delaney H. Lundberg, and Ann Tenenbaum and Thomas H. Lee Gifts, 1997",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/282046,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.76,false,true,283175,Photographs,Photograph,[Hypnotism],,,,,,Artist,,John Adams Whipple,"American, 1822–1891",,"Whipple, John Adams",American,1822,1891,ca. 1845,1843,1847,Daguerreotype,Image: 13.3 x 18.4 cm (5 1/4 x 7 1/4 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/283175,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.460.14,false,true,290482,Photographs,Photograph,National Congregational Council at Plymouth Rock,,,,,,Artist,,John Adams Whipple,"American, 1822–1891",,"Whipple, John Adams",American,1822,1891,"June 22, 1865",1865,1865,Albumen silver print from glass negative,Image: 37.5 x 47.6 cm (14 3/4 x 18 3/4 in.) Mount: 45.7 x 55.9 cm (18 x 22 in.),"Gift of Paul F. Walter, 2009",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/290482,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.400.43,false,true,291796,Photographs,Daguerreotype,[Double Plate: Two Men with Sideburns],,,,,,Artist,Possibly by,John Adams Whipple,"American, 1822–1891",,"Whipple, John Adams",American,1822,1891,1850s,1850,1859,Daguerreotype,"Image: 6.5 x 5.3 cm (2 9/16 x 2 1/16 in.), each Plate: 8.3 x 7 cm (3 1/4 x 2 3/4 in.), each Case: 1.9 x 9.5 x 8.3 cm (3/4 x 3 3/4 x 3 1/4 in.)","Bequest of Herbert Mitchell, 2008",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/291796,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.856,false,true,286231,Photographs,Photograph,[Two Elderly Men Conversing],,,,,,Artist,,John Adams Whipple,"American, 1822–1891",,"Whipple, John Adams",American,1822,1891,ca. 1850,1848,1852,Daguerreotype,oberall: 9 1/16 × 6 15/16 in. (23 × 17.7 cm) Image: 7 1/4 × 5 1/4 in. (18.4 × 13.4 cm); visible,"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286231,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.857,false,true,286237,Photographs,Photograph,[Self-Portrait with Artist's Brother],,,,,,Artist,,John Adams Whipple,"American, 1822–1891",,"Whipple, John Adams",American,1822,1891,1840s,1840,1849,Daguerreotype,Image: 1 7/8 × 1 7/16 in. (4.7 × 3.7 cm); visible,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286237,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.858,false,true,286230,Photographs,Photograph,[Self-Portrait with Wife and Two Daughters],,,,,,Artist,,John Adams Whipple,"American, 1822–1891",,"Whipple, John Adams",American,1822,1891,1854,1854,1854,Daguerreotype,Overall: 6 × 4 3/4 in. (15.2 × 12 cm) Image: 4 13/16 × 3 9/16 in. (12.3 × 9 cm); visible,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286230,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.56,false,true,301998,Photographs,Photograph,General George McClellan,,,,,,Artist,,Charles DeForest Fredricks,"American, 1823–1894",,"Fredricks, Charles DeForest",American,1823,1894,1862 (?),1862,1862,Albumen silver print from glass negative,Image: 9.1 x 5.4 cm (3 9/16 x 2 1/8 in.) Mount: 10.2 x 6.1 cm (4 x 2 3/8 in.),"Purchase, Alfred Stieglitz Society Gifts, 2013",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/301998,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2014.210,false,true,647196,Photographs,Photograph,[Clown],,,,,,Artist,,Charles DeForest Fredricks,"American, 1823–1894",,"Fredricks, Charles DeForest",American,1823,1894,ca. 1860,1855,1865,Albumen silver print,Image: 8 1/16 × 6 13/16 in. (20.5 × 17.3 cm) Mount: 13 5/16 × 10 1/2 in. (33.8 × 26.7 cm),"Gift of Howard Greenberg, 2014",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/647196,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.49,false,true,301963,Photographs,Carte-de-visite,"[Wounded Soldiers on Cots, possibly at Harewood Hospital]",,,,,,Artist,,Reed Brockway Bontecou,"American, 1824–1907",,"Bontecou, Reed Brockway",American,1824,1907,1865,1865,1865,Albumen silver print from glass negative,Image: 9.4 x 5.7 cm (3 11/16 x 2 1/4 in.) Mount: 10.2 x 6.1 cm (4 x 2 3/8 in.),"Purchase, Alfred Stieglitz Society Gifts, 2013",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/301963,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1992.5127,false,true,266764,Photographs,Photograph,Judson C. Albright,,,,,,Artist,,Reed Brockway Bontecou,"American, 1824–1907",,"Bontecou, Reed Brockway",American,1824,1907,1865,1865,1865,Albumen silver print from glass negative,,"Gift of Stanley B. Burns, M.D. and The Burns Archive, 1992",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/266764,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1992.5128,false,true,266765,Photographs,Photograph,Frederick A. Bentley,,,,,,Artist,,Reed Brockway Bontecou,"American, 1824–1907",,"Bontecou, Reed Brockway",American,1824,1907,1865,1865,1865,Albumen silver print from glass negative,,"Gift of Stanley B. Burns, M.D. and The Burns Archive, 1992",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/266765,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1992.5129,false,true,266766,Photographs,Photograph,John A. Dixon,,,,,,Artist,,Reed Brockway Bontecou,"American, 1824–1907",,"Bontecou, Reed Brockway",American,1824,1907,1865,1865,1865,Albumen silver print from glass negative,,"Gift of Stanley B. Burns, M.D. and The Burns Archive, 1992",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/266766,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1992.5130,false,true,266767,Photographs,Photograph,Frederick Hohmann,,,,,,Artist,,Reed Brockway Bontecou,"American, 1824–1907",,"Bontecou, Reed Brockway",American,1824,1907,1865,1865,1865,Albumen silver print from glass negative,,"Gift of Stanley B. Burns, M.D. and The Burns Archive, 1992",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/266767,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1992.5131,false,true,266768,Photographs,Photograph,"Private John Parkhurst, Company E, Second New York Heavy Artillery",,,,,,Artist,,Reed Brockway Bontecou,"American, 1824–1907",,"Bontecou, Reed Brockway",American,1824,1907,1865,1865,1865,Albumen silver print from glass negative,"Image: 18.9 × 13.1 cm (7 7/16 × 5 3/16 in.), oval","Gift of Stanley B. Burns, M.D. and The Burns Archive, 1992",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/266768,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1992.5132,false,true,266769,Photographs,Photograph,Stephen D. Wilbur,,,,,,Artist,,Reed Brockway Bontecou,"American, 1824–1907",,"Bontecou, Reed Brockway",American,1824,1907,1865,1865,1865,Albumen silver print from glass negative,,"Gift of Stanley B. Burns, M.D. and The Burns Archive, 1992",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/266769,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1992.5133,false,true,266770,Photographs,Photograph,Herman Rice,,,,,,Artist,,Reed Brockway Bontecou,"American, 1824–1907",,"Bontecou, Reed Brockway",American,1824,1907,1865,1865,1865,Albumen silver print from glass negative,,"Gift of Stanley B. Burns, M.D. and The Burns Archive, 1992",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/266770,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1992.5134,false,true,266771,Photographs,Photograph,"Private Samuel Shoop, Company F, 200th Pennsylvania Infantry",,,,,,Artist,,Reed Brockway Bontecou,"American, 1824–1907",,"Bontecou, Reed Brockway",American,1824,1907,April–May 1865,1865,1865,Albumen silver print from glass negative,Image: 18.9 × 13.1 cm (7 7/16 × 5 3/16 in.),"Gift of Stanley B. Burns, M.D. and The Burns Archive, 1992",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/266771,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1992.5135,false,true,266772,Photographs,Photograph,"Private Jacob F. Simmons, Company H, Eighty-second Pennsylvania Volunteers",,,,,,Artist,,Reed Brockway Bontecou,"American, 1824–1907",,"Bontecou, Reed Brockway",American,1824,1907,April–May 1865,1865,1865,Albumen silver print from glass negative,"Image: 18.7 × 13 cm (7 3/8 × 5 1/8 in.), oval Mount: 29.5 × 24.1 cm (11 5/8 × 9 1/2 in.)","Gift of Stanley B. Burns, M.D. and The Burns Archive, 1992",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/266772,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1992.5136,false,true,266773,Photographs,Photograph,"Corporal Israel Spotts, Company G, 200th Pennsylvania Volunteers",,,,,,Artist,,Reed Brockway Bontecou,"American, 1824–1907",,"Bontecou, Reed Brockway",American,1824,1907,April–May 1865,1865,1865,Albumen silver print from glass negative,"Image: 18.9 x 13.1 cm (7 7/16 x 5 3/16 in.), oval Mount: 29.8 x 24.3 cm (11 3/4 x 9 9/16 in.)","Gift of Stanley B. Burns, M.D. and The Burns Archive, 1992",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/266773,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1992.5137,false,true,266774,Photographs,Photograph,"Private James H. Stokes, Company H, 185th New York Volunteers",,,,,,Artist,,Reed Brockway Bontecou,"American, 1824–1907",,"Bontecou, Reed Brockway",American,1824,1907,April–May 1865,1865,1865,Albumen silver print from glass negative,"Image: 18.8 × 13 cm (7 3/8 × 5 1/8 in.), oval","Gift of Stanley B. Burns, M.D. and The Burns Archive, 1992",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/266774,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1992.5138,false,true,266775,Photographs,Photograph,Robert Stevenson,,,,,,Artist,,Reed Brockway Bontecou,"American, 1824–1907",,"Bontecou, Reed Brockway",American,1824,1907,1865,1865,1865,Albumen silver print from glass negative,,"Gift of Stanley B. Burns, M.D. and The Burns Archive, 1992",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/266775,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1992.5139,false,true,266776,Photographs,Photograph,"Privat Dennis Sullivan, Company E, Second Virginia Cavalry",,,,,,Artist,,Reed Brockway Bontecou,"American, 1824–1907",,"Bontecou, Reed Brockway",American,1824,1907,April 1865,1865,1865,Albumen silver print from glass negative,"Image: 13.1 × 18.9 cm (5 3/16 × 7 7/16 in.), oval","Gift of Stanley B. Burns, M.D. and The Burns Archive, 1992",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/266776,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1992.5140,false,true,266777,Photographs,Photograph,Andrew Wagoner,,,,,,Artist,,Reed Brockway Bontecou,"American, 1824–1907",,"Bontecou, Reed Brockway",American,1824,1907,1865,1865,1865,Albumen silver print from glass negative,,"Gift of Stanley B. Burns, M.D. and The Burns Archive, 1992",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/266777,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1992.5141,false,true,266778,Photographs,Photograph,Henry Yon,,,,,,Artist,,Reed Brockway Bontecou,"American, 1824–1907",,"Bontecou, Reed Brockway",American,1824,1907,1865,1865,1865,Albumen silver print from glass negative,,"Gift of Stanley B. Burns, M.D. and The Burns Archive, 1992",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/266778,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1992.5142,false,true,266779,Photographs,Photograph,Frederick Pilgrim,,,,,,Artist,,Reed Brockway Bontecou,"American, 1824–1907",,"Bontecou, Reed Brockway",American,1824,1907,1865,1865,1865,Albumen silver print from glass negative,,"Gift of Stanley B. Burns, M.D. and The Burns Archive, 1992",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/266779,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1992.5143,false,true,266780,Photographs,Photograph,Frederick Pilgrim,,,,,,Artist,,Reed Brockway Bontecou,"American, 1824–1907",,"Bontecou, Reed Brockway",American,1824,1907,1865,1865,1865,Albumen silver print from glass negative,,"Gift of Stanley B. Burns, M.D. and The Burns Archive, 1992",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/266780,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.99,false,true,283206,Photographs,Photograph,"Private George Ruoss, Co. G, 7th New York Volunteers",,,,,,Artist,,Reed Brockway Bontecou,"American, 1824–1907",,"Bontecou, Reed Brockway",American,1824,1907,1865–1866,1865,1866,Albumen silver print from glass negative,Image: 16.6 × 21.7 cm (6 9/16 × 8 9/16 in.) Sheet: 19.3 × 24.2 cm (7 5/8 × 9 1/2 in.) Mount: 27.7 × 35.4 cm (10 7/8 × 13 15/16 in.),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/283206,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1981.1229.4,false,true,263187,Photographs,Photograph,[Boston from a Hot-Air Balloon],,,,,,Artist,,James Wallace Black,"American, 1825–1896",,"Black, James Wallace",American,1825,1896,1860s,1860,1869,Albumen silver print from glass negative,25.6 x 20.2 cm. (10 1/16 x 7 15/16 in.),"Robert O. Dougan Collection, Gift of Warner Communications Inc., 1981",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/263187,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.87,false,true,283189,Photographs,Photograph,"Boston, as the Eagle and the Wild Goose See It",,,,,,Artist,,James Wallace Black,"American, 1825–1896",,"Black, James Wallace",American,1825,1896,1860,1860,1860,Albumen silver print from glass negative,"Image: 18.5 x 16.7 cm (7 5/16 x 6 9/16 in.), irregularly trimmed Mount: 20.3 x 17 cm (8 x 6 11/16 in.), irregularly trimmed","Gilman Collection, Purchase, Ann Tenenbaum and Thomas H. Lee Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/283189,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.1147,false,true,286653,Photographs,Photograph,Four Generations,,,,,,Artist,,James Wallace Black,"American, 1825–1896",,"Black, James Wallace",American,1825,1896,ca. 1860,1858,1862,Salted paper print,Image (Oval): 28.1 × 22.4 cm (11 1/16 × 8 13/16 in.) Mount (Oval): 27.8 × 22.2 cm (10 15/16 × 8 3/4 in.),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286653,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.1247,false,true,286300,Photographs,Photograph,[Victorian House],,,,,,Artist,,James Wallace Black,"American, 1825–1896",,"Black, James Wallace",American,1825,1896,ca. 1860,1855,1865,Albumen silver print from glass negative,Image: 24.8 × 32.5 cm (9 3/4 × 12 13/16 in.),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286300,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.1248,false,true,286299,Photographs,Photograph,"[Washington Street, Boston]",,,,,,Artist,,James Wallace Black,"American, 1825–1896",,"Black, James Wallace",American,1825,1896,ca. 1860,1855,1865,Albumen silver print from glass negative,"Image: 18 × 15.5 cm (7 1/16 × 6 1/8 in.), dome top","Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286299,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.94,false,true,283198,Photographs,Photograph,"[Ordnance Wharf, City Point, Virginia]",,,,,,Artist,,Thomas C. Roche,"American, 1826–1895",,"Roche, Thomas C.",American,1826,1895,1865,1865,1865,Albumen silver print from glass negative,Image: 21.7 x 25.5 cm (8 9/16 x 10 1/16 in.),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/283198,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2010.518,false,true,294766,Photographs,Photograph,[Winter Scene with Trestle Bridge Along the Atlantic & Great Western Railway],,,,,,Artist,,James Fitzallen Ryder,"American, 1826–1904",,"Ryder, James Fitzallen",American,1826,1904,1862–64,1862,1864,Albumen silver print from glass negative,Image: 18.7 x 23.8 cm (7 3/8 x 9 3/8 in.),"Gift of Mary and Dan Solomon, in honor of Hans P. Kraus Jr., 2010",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/294766,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1979.622,true,true,262612,Photographs,Photograph,"View on the Columbia, Cascades",,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",American,1829,1916,1867,1867,1867,Albumen silver print from glass negative,40.0 x 52.4 cm (15 3/4 x 20 5/8 in.),"Warner Communications Inc. Purchase Fund and Harris Brisbane Dick Fund, 1979",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/262612,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.1082,false,true,266132,Photographs,Photograph,"Devil's Canyon, Geysers, Looking Down",,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",American,1829,1916,1868–70,1868,1870,Albumen silver print from glass negative,39.8 x 52.4 cm. (15 11/16 x 20 5/8 in.),"Rogers Fund, 1989",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/266132,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.1083,true,true,266133,Photographs,Photograph,"The Town on the Hill, New Almaden",,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",American,1829,1916,1863,1863,1863,Albumen silver print from glass negative,39.7 x 52.3 cm (15 5/8 x 20 9/16 in.),"Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 1989",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/266133,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.540.4,false,true,259681,Photographs,Photograph,"El Capitan, Yosemite",,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",American,1829,1916,1865–66,1865,1866,Albumen silver print from glass negative,,"Rogers Fund, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/259681,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.540.6,false,true,259683,Photographs,Photograph,"Section of the Grizzly Giant with Galen Clark, Mariposa Grove, Yosemite",,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",American,1829,1916,1865–66,1865,1866,Albumen silver print from glass negative,Image: 20 5/8 × 15 11/16 in. (52.4 × 39.8 cm) Sheet: 23 13/16 × 19 1/16 in. (60.5 × 48.4 cm),"Rogers Fund, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/259683,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.643.1,false,true,260314,Photographs,Photograph,"Vernal Fall, Yosemite",,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",American,1829,1916,"1865–66, printed ca. 1875",1865,1866,Albumen silver print from glass negative,,"The Elisha Whittelsey Collection, The Elisha Whittelsey Fund, 1972",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260314,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.643.3,false,true,260317,Photographs,Photograph,"Section of the Grizzly Giant with Galen Clark, Mariposa Grove, Yosemite",,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",American,1829,1916,"1865–66, printed ca. 1876",1865,1866,Albumen silver print from glass negative,52.2 x 40.6 cm. (20 9/16 x 16 in.),"The Elisha Whittelsey Collection, The Elisha Whittelsey Fund, 1972",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260317,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.1189.1,false,true,264896,Photographs,Photograph,"Residence of Charles Bernard. 312 Oak Street, San Francisco, California",,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",American,1829,1916,ca. 1876,1874,1878,Albumen silver print from glass negative,,"Gift of Carole and Irwin Lainoff, Ruth P. Lasser and Joseph R. Lasser, Mr. and Mrs. John T. Marvin, Martin E. and Joan Messinger, Richard L. Yett and Sheri and Paul Siegel, 1986",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/264896,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.1189.2,false,true,264909,Photographs,Photograph,San Francisco,,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",American,1829,1916,"1864, printed ca. 1876",1864,1864,Albumen silver print from glass negative,,"Gift of Carole and Irwin Lainoff, Ruth P. Lasser and Joseph R. Lasser, Mr. and Mrs. John T. Marvin, Martin E. and Joan Messinger, Richard L. Yett and Sheri and Paul Siegel, 1986",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/264909,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.1189.3,false,true,264920,Photographs,Photograph,"San Francisco, from California and Powell Street",,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",American,1829,1916,"1864, printed ca. 1876",1864,1864,Albumen silver print from glass negative,,"Gift of Carole and Irwin Lainoff, Ruth P. Lasser and Joseph R. Lasser, Mr. and Mrs. John T. Marvin, Martin E. and Joan Messinger, Richard L. Yett and Sheri and Paul Siegel, 1986",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/264920,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.1189.4,false,true,264931,Photographs,Photograph,"San Francisco, from Rincon Hill",,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",American,1829,1916,"1864, printed ca. 1876",1864,1864,Albumen silver print from glass negative,,"Gift of Carole and Irwin Lainoff, Ruth P. Lasser and Joseph R. Lasser, Mr. and Mrs. John T. Marvin, Martin E. and Joan Messinger, Richard L. Yett and Sheri and Paul Siegel, 1986",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/264931,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.1189.5,false,true,264942,Photographs,Photograph,"San Francisco, from California and Powell Streets",,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",American,1829,1916,"1864, printed ca. 1876",1864,1864,Albumen silver print from glass negative,,"Gift of Carole and Irwin Lainoff, Ruth P. Lasser and Joseph R. Lasser, Mr. and Mrs. John T. Marvin, Martin E. and Joan Messinger, Richard L. Yett and Sheri and Paul Siegel, 1986",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/264942,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.1189.6,false,true,264953,Photographs,Photograph,South Side of California Street,,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",American,1829,1916,"1864, printed ca. 1876",1864,1864,Albumen silver print from glass negative,,"Gift of Carole and Irwin Lainoff, Ruth P. Lasser and Joseph R. Lasser, Mr. and Mrs. John T. Marvin, Martin E. and Joan Messinger, Richard L. Yett and Sheri and Paul Siegel, 1986",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/264953,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.1189.7,false,true,264964,Photographs,Photograph,North Side of California Street,,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",American,1829,1916,"1864, printed ca. 1876",1864,1864,Albumen silver print from glass negative,,"Gift of Carole and Irwin Lainoff, Ruth P. Lasser and Joseph R. Lasser, Mr. and Mrs. John T. Marvin, Martin E. and Joan Messinger, Richard L. Yett and Sheri and Paul Siegel, 1986",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/264964,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.1189.8,false,true,264975,Photographs,Photograph,"First Street, San Francisco",,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",American,1829,1916,"1864, printed ca. 1876",1864,1864,Albumen silver print from glass negative,,"Gift of Carole and Irwin Lainoff, Ruth P. Lasser and Joseph R. Lasser, Mr. and Mrs. John T. Marvin, Martin E. and Joan Messinger, Richard L. Yett and Sheri and Paul Siegel, 1986",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/264975,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.1189.9,false,true,264986,Photographs,Photograph,The Golden Gate,,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",American,1829,1916,"1868–69, printed ca. 1876",1868,1869,Albumen silver print from glass negative,,"Gift of Carole and Irwin Lainoff, Ruth P. Lasser and Joseph R. Lasser, Mr. and Mrs. John T. Marvin, Martin E. and Joan Messinger, Richard L. Yett and Sheri and Paul Siegel, 1986",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/264986,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.1189.10,false,true,264897,Photographs,Photograph,"Alcatraz Island, San Francisco",,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",American,1829,1916,"1868–69, printed ca. 1876",1868,1869,Albumen silver print from glass negative,,"Gift of Carole and Irwin Lainoff, Ruth P. Lasser and Joseph R. Lasser, Mr. and Mrs. John T. Marvin, Martin E. and Joan Messinger, Richard L. Yett and Sheri and Paul Siegel, 1986",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/264897,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.1189.11,false,true,264900,Photographs,Photograph,Cliff House and Seal Rock,,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",American,1829,1916,"1868–69, printed ca. 1876",1868,1869,Albumen silver print from glass negative,,"Gift of Carole and Irwin Lainoff, Ruth P. Lasser and Joseph R. Lasser, Mr. and Mrs. John T. Marvin, Martin E. and Joan Messinger, Richard L. Yett and Sheri and Paul Siegel, 1986",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/264900,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.1189.12,false,true,264901,Photographs,Photograph,Seal Rocks,,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",American,1829,1916,"1868–69, printed ca. 1876",1868,1869,Albumen silver print from glass negative,,"Gift of Carole and Irwin Lainoff, Ruth P. Lasser and Joseph R. Lasser, Mr. and Mrs. John T. Marvin, Martin E. and Joan Messinger, Richard L. Yett and Sheri and Paul Siegel, 1986",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/264901,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.1189.13,false,true,264902,Photographs,Photograph,"Oakland, from Military Academy",,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",American,1829,1916,"1864, printed ca. 1876",1864,1864,Albumen silver print from glass negative,,"Gift of Carole and Irwin Lainoff, Ruth P. Lasser and Joseph R. Lasser, Mr. and Mrs. John T. Marvin, Martin E. and Joan Messinger, Richard L. Yett and Sheri and Paul Siegel, 1986",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/264902,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.1189.14,false,true,264903,Photographs,Photograph,General View of Yosemite,,,,,,Artist,Attributed to,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",American,1829,1916,"ca. 1872, printed ca. 1876",1870,1874,Albumen silver print from glass negative,,"Gift of Carole and Irwin Lainoff, Ruth P. Lasser and Joseph R. Lasser, Mr. and Mrs. John T. Marvin, Martin E. and Joan Messinger, Richard L. Yett and Sheri and Paul Siegel, 1986",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/264903,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.1189.15,false,true,264904,Photographs,Photograph,Yosemite Valley,,,,,,Artist,Attributed to,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",American,1829,1916,"ca. 1872, printed ca. 1876",1870,1874,Albumen silver print from glass negative,,"Gift of Carole and Irwin Lainoff, Ruth P. Lasser and Joseph R. Lasser, Mr. and Mrs. John T. Marvin, Martin E. and Joan Messinger, Richard L. Yett and Sheri and Paul Siegel, 1986",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/264904,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.1189.16,false,true,264905,Photographs,Photograph,Yosemite Valley from Union Point,,,,,,Artist,Attributed to,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",American,1829,1916,"ca. 1872, printed ca. 1876",1870,1874,Albumen silver print from glass negative,,"Gift of Carole and Irwin Lainoff, Ruth P. Lasser and Joseph R. Lasser, Mr. and Mrs. John T. Marvin, Martin E. and Joan Messinger, Richard L. Yett and Sheri and Paul Siegel, 1986",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/264905,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.1189.17,false,true,264906,Photographs,Photograph,Yosemite Valley from Glacier Point,,,,,,Artist,Attributed to,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",American,1829,1916,"ca. 1872, printed ca. 1876",1870,1874,Albumen silver print from glass negative,,"Gift of Carole and Irwin Lainoff, Ruth P. Lasser and Joseph R. Lasser, Mr. and Mrs. John T. Marvin, Martin E. and Joan Messinger, Richard L. Yett and Sheri and Paul Siegel, 1986",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/264906,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.1189.18,false,true,264907,Photographs,Photograph,Looking Up Yosemite Valley,,,,,,Artist,Attributed to,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",American,1829,1916,"ca. 1872, printed ca. 1876",1870,1874,Albumen silver print from glass negative,,"Gift of Carole and Irwin Lainoff, Ruth P. Lasser and Joseph R. Lasser, Mr. and Mrs. John T. Marvin, Martin E. and Joan Messinger, Richard L. Yett and Sheri and Paul Siegel, 1986",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/264907,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.1189.19,false,true,264908,Photographs,Photograph,Looking Down Yosemite Valley,,,,,,Artist,Attributed to,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",American,1829,1916,"ca. 1872, printed ca. 1876",1870,1874,Albumen silver print from glass negative,,"Gift of Carole and Irwin Lainoff, Ruth P. Lasser and Joseph R. Lasser, Mr. and Mrs. John T. Marvin, Martin E. and Joan Messinger, Richard L. Yett and Sheri and Paul Siegel, 1986",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/264908,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.1189.20,false,true,264910,Photographs,Photograph,"Yosemite Falls, 2,634 feet",,,,,,Artist,Attributed to,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",American,1829,1916,"ca. 1872, printed ca. 1876",1870,1874,Albumen silver print from glass negative,,"Gift of Carole and Irwin Lainoff, Ruth P. Lasser and Joseph R. Lasser, Mr. and Mrs. John T. Marvin, Martin E. and Joan Messinger, Richard L. Yett and Sheri and Paul Siegel, 1986",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/264910,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.1189.21,false,true,264911,Photographs,Photograph,"Yosemite Falls, 2,634 feet",,,,,,Artist,Attributed to,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",American,1829,1916,"ca. 1872, printed ca. 1876",1870,1874,Albumen silver print from glass negative,,"Gift of Carole and Irwin Lainoff, Ruth P. Lasser and Joseph R. Lasser, Mr. and Mrs. John T. Marvin, Martin E. and Joan Messinger, Richard L. Yett and Sheri and Paul Siegel, 1986",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/264911,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.1189.22,false,true,264912,Photographs,Photograph,"Cathedral Rocks, Yosemite",,,,,,Artist,Attributed to,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",American,1829,1916,"ca. 1872, printed ca. 1876",1870,1874,Albumen silver print from glass negative,,"Gift of Carole and Irwin Lainoff, Ruth P. Lasser and Joseph R. Lasser, Mr. and Mrs. John T. Marvin, Martin E. and Joan Messinger, Richard L. Yett and Sheri and Paul Siegel, 1986",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/264912,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.1189.23,false,true,264913,Photographs,Photograph,Cathedral Rocks and Spires,,,,,,Artist,Attributed to,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",American,1829,1916,"ca. 1872, printed ca. 1876",1870,1874,Albumen silver print from glass negative,,"Gift of Carole and Irwin Lainoff, Ruth P. Lasser and Joseph R. Lasser, Mr. and Mrs. John T. Marvin, Martin E. and Joan Messinger, Richard L. Yett and Sheri and Paul Siegel, 1986",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/264913,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.1189.24,false,true,264914,Photographs,Photograph,"Cathedral Rocks, Yosemite",,,,,,Artist,Attributed to,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",American,1829,1916,"ca. 1872, printed ca. 1876",1870,1874,Albumen silver print from glass negative,,"Gift of Carole and Irwin Lainoff, Ruth P. Lasser and Joseph R. Lasser, Mr. and Mrs. John T. Marvin, Martin E. and Joan Messinger, Richard L. Yett and Sheri and Paul Siegel, 1986",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/264914,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.1189.25,false,true,264915,Photographs,Photograph,"The Domes, Yosemite",,,,,,Artist,Attributed to,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",American,1829,1916,"ca. 1872, printed ca. 1876",1870,1874,Albumen silver print from glass negative,,"Gift of Carole and Irwin Lainoff, Ruth P. Lasser and Joseph R. Lasser, Mr. and Mrs. John T. Marvin, Martin E. and Joan Messinger, Richard L. Yett and Sheri and Paul Siegel, 1986",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/264915,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.1189.26,false,true,264916,Photographs,Photograph,"The Domes, Yosemite",,,,,,Artist,Attributed to,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",American,1829,1916,"ca. 1872, printed ca. 1876",1870,1874,Albumen silver print from glass negative,,"Gift of Carole and Irwin Lainoff, Ruth P. Lasser and Joseph R. Lasser, Mr. and Mrs. John T. Marvin, Martin E. and Joan Messinger, Richard L. Yett and Sheri and Paul Siegel, 1986",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/264916,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.1189.27,false,true,264917,Photographs,Photograph,"North and South Dome, Yosemite",,,,,,Artist,Attributed to,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",American,1829,1916,"ca. 1872, printed ca. 1876",1870,1874,Albumen silver print from glass negative,,"Gift of Carole and Irwin Lainoff, Ruth P. Lasser and Joseph R. Lasser, Mr. and Mrs. John T. Marvin, Martin E. and Joan Messinger, Richard L. Yett and Sheri and Paul Siegel, 1986",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/264917,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.1189.28,false,true,264918,Photographs,Photograph,"North and South Dome, Yosemite",,,,,,Artist,Attributed to,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",American,1829,1916,"ca. 1872, printed ca. 1876",1870,1874,Albumen silver print from glass negative,,"Gift of Carole and Irwin Lainoff, Ruth P. Lasser and Joseph R. Lasser, Mr. and Mrs. John T. Marvin, Martin E. and Joan Messinger, Richard L. Yett and Sheri and Paul Siegel, 1986",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/264918,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.1189.29,false,true,264919,Photographs,Photograph,"North Dome, 3,725 feet, Yosemite",,,,,,Artist,Attributed to,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",American,1829,1916,"ca. 1872, printed ca. 1876",1870,1874,Albumen silver print from glass negative,,"Gift of Carole and Irwin Lainoff, Ruth P. Lasser and Joseph R. Lasser, Mr. and Mrs. John T. Marvin, Martin E. and Joan Messinger, Richard L. Yett and Sheri and Paul Siegel, 1986",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/264919,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.1189.30,false,true,264921,Photographs,Photograph,"South Dome, Yosemite",,,,,,Artist,Attributed to,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",American,1829,1916,"ca. 1872, printed ca. 1876",1870,1874,Albumen silver print from glass negative,,"Gift of Carole and Irwin Lainoff, Ruth P. Lasser and Joseph R. Lasser, Mr. and Mrs. John T. Marvin, Martin E. and Joan Messinger, Richard L. Yett and Sheri and Paul Siegel, 1986",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/264921,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.1189.31,false,true,264922,Photographs,Photograph,"South Dome, 6,000 feet",,,,,,Artist,Attributed to,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",American,1829,1916,"ca. 1872, printed ca. 1876",1870,1874,Albumen silver print from glass negative,,"Gift of Carole and Irwin Lainoff, Ruth P. Lasser and Joseph R. Lasser, Mr. and Mrs. John T. Marvin, Martin E. and Joan Messinger, Richard L. Yett and Sheri and Paul Siegel, 1986",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/264922,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.1189.32,false,true,264923,Photographs,Photograph,"South Dome, 6,000 feet",,,,,,Artist,Attributed to,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",American,1829,1916,"ca. 1872, printed ca. 1876",1870,1874,Albumen silver print from glass negative,,"Gift of Carole and Irwin Lainoff, Ruth P. Lasser and Joseph R. Lasser, Mr. and Mrs. John T. Marvin, Martin E. and Joan Messinger, Richard L. Yett and Sheri and Paul Siegel, 1986",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/264923,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.1189.33,false,true,264924,Photographs,Photograph,"South Dome, 6,000 feet, Yosemite",,,,,,Artist,Attributed to,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",American,1829,1916,"ca. 1872, printed ca. 1876",1870,1874,Albumen silver print from glass negative,Image: 33.5 x 26.8 cm (13 3/16 x 10 9/16 in.),"Gift of Carole and Irwin Lainoff, Ruth P. Lasser and Joseph R. Lasser, Mr. and Mrs. John T. Marvin, Martin E. and Joan Messinger, Richard L. Yett and Sheri and Paul Siegel, 1986",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/264924,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.1189.34,false,true,264925,Photographs,Photograph,Merced River,,,,,,Artist,Attributed to,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",American,1829,1916,"ca. 1872, printed ca. 1876",1870,1874,Albumen silver print from glass negative,,"Gift of Carole and Irwin Lainoff, Ruth P. Lasser and Joseph R. Lasser, Mr. and Mrs. John T. Marvin, Martin E. and Joan Messinger, Richard L. Yett and Sheri and Paul Siegel, 1986",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/264925,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.1189.35,false,true,264926,Photographs,Photograph,"Merced River, Yosemite",,,,,,Artist,Attributed to,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",American,1829,1916,"ca. 1872, printed ca. 1876",1870,1874,Albumen silver print from glass negative,,"Gift of Carole and Irwin Lainoff, Ruth P. Lasser and Joseph R. Lasser, Mr. and Mrs. John T. Marvin, Martin E. and Joan Messinger, Richard L. Yett and Sheri and Paul Siegel, 1986",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/264926,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.1189.36,false,true,264927,Photographs,Photograph,"Merced River, Yosemite",,,,,,Artist,Attributed to,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",American,1829,1916,"ca. 1872, printed ca. 1876",1870,1874,Albumen silver print from glass negative,,"Gift of Carole and Irwin Lainoff, Ruth P. Lasser and Joseph R. Lasser, Mr. and Mrs. John T. Marvin, Martin E. and Joan Messinger, Richard L. Yett and Sheri and Paul Siegel, 1986",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/264927,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.1189.37,false,true,264928,Photographs,Photograph,"Mirror Lake, Yosemite",,,,,,Artist,Attributed to,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",American,1829,1916,"ca. 1872, printed ca. 1876",1870,1874,Albumen silver print from glass negative,,"Gift of Carole and Irwin Lainoff, Ruth P. Lasser and Joseph R. Lasser, Mr. and Mrs. John T. Marvin, Martin E. and Joan Messinger, Richard L. Yett and Sheri and Paul Siegel, 1986",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/264928,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.1189.38,false,true,264929,Photographs,Photograph,"Mirror View of the Three Brothers, Yosemite",,,,,,Artist,Attributed to,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",American,1829,1916,"ca. 1872, printed ca. 1876",1870,1874,Albumen silver print from glass negative,,"Gift of Carole and Irwin Lainoff, Ruth P. Lasser and Joseph R. Lasser, Mr. and Mrs. John T. Marvin, Martin E. and Joan Messinger, Richard L. Yett and Sheri and Paul Siegel, 1986",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/264929,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.1189.39,false,true,264930,Photographs,Photograph,"Mirror View of Cathedral Rocks, Yosemite",,,,,,Artist,Attributed to,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",American,1829,1916,"ca. 1872, printed ca. 1876",1870,1874,Albumen silver print from glass negative,,"Gift of Carole and Irwin Lainoff, Ruth P. Lasser and Joseph R. Lasser, Mr. and Mrs. John T. Marvin, Martin E. and Joan Messinger, Richard L. Yett and Sheri and Paul Siegel, 1986",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/264930,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.1189.40,false,true,264932,Photographs,Photograph,"Cap of Liberty and Nevada Fall, Yosemite",,,,,,Artist,Attributed to,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",American,1829,1916,"ca. 1872, printed ca. 1876",1870,1874,Albumen silver print from glass negative,,"Gift of Carole and Irwin Lainoff, Ruth P. Lasser and Joseph R. Lasser, Mr. and Mrs. John T. Marvin, Martin E. and Joan Messinger, Richard L. Yett and Sheri and Paul Siegel, 1986",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/264932,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.1189.41,false,true,264933,Photographs,Photograph,"Nevada Fall, 700 feet, Yosemite",,,,,,Artist,Attributed to,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",American,1829,1916,"ca. 1872, printed ca. 1876",1870,1874,Albumen silver print from glass negative,Image: 33.6 × 26.7 cm (13 1/4 × 10 1/2 in.) Sheet: 42.3 × 33 cm (16 5/8 × 13 in.),"Gift of Carole and Irwin Lainoff, Ruth P. Lasser and Joseph R. Lasser, Mr. and Mrs. John T. Marvin, Martin E. and Joan Messinger, Richard L. Yett and Sheri and Paul Siegel, 1986",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/264933,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.1189.42,false,true,264934,Photographs,Photograph,"Lower Yosemite Fall, 1,600 feet",,,,,,Artist,Attributed to,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",American,1829,1916,"ca. 1872, printed ca. 1876",1870,1874,Albumen silver print from glass negative,,"Gift of Carole and Irwin Lainoff, Ruth P. Lasser and Joseph R. Lasser, Mr. and Mrs. John T. Marvin, Martin E. and Joan Messinger, Richard L. Yett and Sheri and Paul Siegel, 1986",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/264934,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.1189.43,false,true,264935,Photographs,Photograph,"Bridal Veil Fall, 940 feet, Yosemite",,,,,,Artist,Attributed to,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",American,1829,1916,"ca. 1872, printed ca. 1876",1870,1874,Albumen silver print from glass negative,,"Gift of Carole and Irwin Lainoff, Ruth P. Lasser and Joseph R. Lasser, Mr. and Mrs. John T. Marvin, Martin E. and Joan Messinger, Richard L. Yett and Sheri and Paul Siegel, 1986",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/264935,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.1189.44,false,true,264936,Photographs,Photograph,"Washington Tower, Yosemite",,,,,,Artist,Attributed to,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",American,1829,1916,"ca. 1872, printed ca. 1876",1870,1874,Albumen silver print from glass negative,,"Gift of Carole and Irwin Lainoff, Ruth P. Lasser and Joseph R. Lasser, Mr. and Mrs. John T. Marvin, Martin E. and Joan Messinger, Richard L. Yett and Sheri and Paul Siegel, 1986",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/264936,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.1189.45,false,true,264937,Photographs,Photograph,"Washington Tower, Yosemite",,,,,,Artist,Attributed to,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",American,1829,1916,"ca. 1872, printed ca. 1876",1870,1874,Albumen silver print from glass negative,,"Gift of Carole and Irwin Lainoff, Ruth P. Lasser and Joseph R. Lasser, Mr. and Mrs. John T. Marvin, Martin E. and Joan Messinger, Richard L. Yett and Sheri and Paul Siegel, 1986",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/264937,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.1189.46,false,true,264938,Photographs,Photograph,"Eagle Point, 4,000 feet, Yosemite",,,,,,Artist,Attributed to,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",American,1829,1916,"ca. 1872, printed ca. 1876",1870,1874,Albumen silver print from glass negative,,"Gift of Carole and Irwin Lainoff, Ruth P. Lasser and Joseph R. Lasser, Mr. and Mrs. John T. Marvin, Martin E. and Joan Messinger, Richard L. Yett and Sheri and Paul Siegel, 1986",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/264938,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.1189.47,false,true,264939,Photographs,Photograph,"Vernal Falls, 350 feet, Yosemite",,,,,,Artist,Attributed to,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",American,1829,1916,"ca. 1872, printed ca. 1876",1870,1874,Albumen silver print from glass negative,,"Gift of Carole and Irwin Lainoff, Ruth P. Lasser and Joseph R. Lasser, Mr. and Mrs. John T. Marvin, Martin E. and Joan Messinger, Richard L. Yett and Sheri and Paul Siegel, 1986",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/264939,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.1189.48,false,true,264940,Photographs,Photograph,"Mirror View of El Capitan, Yosemite",,,,,,Artist,Attributed to,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",American,1829,1916,"ca. 1872, printed ca. 1876",1870,1874,Albumen silver print from glass negative,,"Gift of Carole and Irwin Lainoff, Ruth P. Lasser and Joseph R. Lasser, Mr. and Mrs. John T. Marvin, Martin E. and Joan Messinger, Richard L. Yett and Sheri and Paul Siegel, 1986",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/264940,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.1189.49,false,true,264941,Photographs,Photograph,"Mirror View of Sentinel Rock, Yosemite",,,,,,Artist,Attributed to,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",American,1829,1916,"ca. 1872, printed ca. 1876",1870,1874,Albumen silver print from glass negative,Image: 13 1/4 × 10 1/2 in. (33.7 × 26.6 cm) Sheet: 16 5/8 × 13 7/16 in. (42.2 × 34.2 cm),"Gift of Carole and Irwin Lainoff, Ruth P. Lasser and Joseph R. Lasser, Mr. and Mrs. John T. Marvin, Martin E. and Joan Messinger, Richard L. Yett and Sheri and Paul Siegel, 1986",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/264941,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.1189.50,false,true,264943,Photographs,Photograph,"Sentinel Rock, Yosemite",,,,,,Artist,Attributed to,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",American,1829,1916,"ca. 1872, printed ca. 1876",1870,1874,Albumen silver print from glass negative,,"Gift of Carole and Irwin Lainoff, Ruth P. Lasser and Joseph R. Lasser, Mr. and Mrs. John T. Marvin, Martin E. and Joan Messinger, Richard L. Yett and Sheri and Paul Siegel, 1986",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/264943,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.1189.51,false,true,264944,Photographs,Photograph,"Sentinel Rock, Yosemite",,,,,,Artist,Attributed to,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",American,1829,1916,"ca. 1872, printed ca. 1876",1870,1874,Albumen silver print from glass negative,,"Gift of Carole and Irwin Lainoff, Ruth P. Lasser and Joseph R. Lasser, Mr. and Mrs. John T. Marvin, Martin E. and Joan Messinger, Richard L. Yett and Sheri and Paul Siegel, 1986",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/264944,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.1189.52,false,true,264945,Photographs,Photograph,"Magic Tower, Yosemite",,,,,,Artist,Attributed to,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",American,1829,1916,"ca. 1872, printed ca. 1876",1870,1874,Albumen silver print from glass negative,,"Gift of Carole and Irwin Lainoff, Ruth P. Lasser and Joseph R. Lasser, Mr. and Mrs. John T. Marvin, Martin E. and Joan Messinger, Richard L. Yett and Sheri and Paul Siegel, 1986",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/264945,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.1189.53,false,true,264946,Photographs,Photograph,"Eagle Point, Yosemite",,,,,,Artist,Attributed to,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",American,1829,1916,"ca. 1872, printed ca. 1876",1870,1874,Albumen silver print from glass negative,,"Gift of Carole and Irwin Lainoff, Ruth P. Lasser and Joseph R. Lasser, Mr. and Mrs. John T. Marvin, Martin E. and Joan Messinger, Richard L. Yett and Sheri and Paul Siegel, 1986",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/264946,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.1189.54,false,true,264947,Photographs,Photograph,"The Three Brothers, Yosemite",,,,,,Artist,Attributed to,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",American,1829,1916,"ca. 1872, printed ca. 1876",1870,1874,Albumen silver print from glass negative,,"Gift of Carole and Irwin Lainoff, Ruth P. Lasser and Joseph R. Lasser, Mr. and Mrs. John T. Marvin, Martin E. and Joan Messinger, Richard L. Yett and Sheri and Paul Siegel, 1986",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/264947,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.1189.55,false,true,264948,Photographs,Photograph,"The Three Brothers, Yosemite",,,,,,Artist,Attributed to,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",American,1829,1916,"ca. 1872, printed ca. 1876",1870,1874,Albumen silver print from glass negative,,"Gift of Carole and Irwin Lainoff, Ruth P. Lasser and Joseph R. Lasser, Mr. and Mrs. John T. Marvin, Martin E. and Joan Messinger, Richard L. Yett and Sheri and Paul Siegel, 1986",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/264948,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.1189.56,false,true,264949,Photographs,Photograph,The Hotel from Mammoth Grove,,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",American,1829,1916,"ca. 1872, printed ca. 1876",1870,1874,Albumen silver print from glass negative,,"Gift of Carole and Irwin Lainoff, Ruth P. Lasser and Joseph R. Lasser, Mr. and Mrs. John T. Marvin, Martin E. and Joan Messinger, Richard L. Yett and Sheri and Paul Siegel, 1986",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/264949,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.1189.57,false,true,264950,Photographs,Photograph,Mammoth Grove Hotel,,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",American,1829,1916,"ca. 1872, printed ca. 1876",1870,1874,Albumen silver print from glass negative,Image: 25.9 x 32.4 cm (10 3/16 x 12 3/4 in.),"Gift of Carole and Irwin Lainoff, Ruth P. Lasser and Joseph R. Lasser, Mr. and Mrs. John T. Marvin, Martin E. and Joan Messinger, Richard L. Yett and Sheri and Paul Siegel, 1986",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/264950,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.1189.58,false,true,264951,Photographs,Photograph,"The Tripod, 94 feet circumference",,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",American,1829,1916,"1865–66, printed ca. 1876",1865,1866,Albumen silver print from glass negative,,"Gift of Carole and Irwin Lainoff, Ruth P. Lasser and Joseph R. Lasser, Mr. and Mrs. John T. Marvin, Martin E. and Joan Messinger, Richard L. Yett and Sheri and Paul Siegel, 1986",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/264951,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.1189.59,false,true,264952,Photographs,Photograph,Cosmopolitan Saloon,,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",American,1829,1916,"ca. 1872, printed ca. 1876",1870,1874,Albumen silver print from glass negative,,"Gift of Carole and Irwin Lainoff, Ruth P. Lasser and Joseph R. Lasser, Mr. and Mrs. John T. Marvin, Martin E. and Joan Messinger, Richard L. Yett and Sheri and Paul Siegel, 1986",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/264952,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.1189.60,false,true,264954,Photographs,Photograph,The House over a Stump of a Big Tree,,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",American,1829,1916,"1865–66, printed ca. 1876",1865,1866,Albumen silver print from glass negative,,"Gift of Carole and Irwin Lainoff, Ruth P. Lasser and Joseph R. Lasser, Mr. and Mrs. John T. Marvin, Martin E. and Joan Messinger, Richard L. Yett and Sheri and Paul Siegel, 1986",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/264954,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.1189.61,false,true,264955,Photographs,Photograph,The House Built over the Stump of a Big Tree,,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",American,1829,1916,"1865–66, printed ca. 1876",1865,1866,Albumen silver print from glass negative,,"Gift of Carole and Irwin Lainoff, Ruth P. Lasser and Joseph R. Lasser, Mr. and Mrs. John T. Marvin, Martin E. and Joan Messinger, Richard L. Yett and Sheri and Paul Siegel, 1986",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/264955,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.1189.62,false,true,264956,Photographs,Photograph,"The Sentinels, 315 feet, Yosemite",,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",American,1829,1916,"ca. 1872, printed ca. 1876",1870,1874,Albumen silver print from glass negative,,"Gift of Carole and Irwin Lainoff, Ruth P. Lasser and Joseph R. Lasser, Mr. and Mrs. John T. Marvin, Martin E. and Joan Messinger, Richard L. Yett and Sheri and Paul Siegel, 1986",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/264956,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.1189.63,false,true,264957,Photographs,Photograph,"The Sentinel, 315 feet",,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",American,1829,1916,"1865–66, printed ca. 1876",1865,1866,Albumen silver print from glass negative,,"Gift of Carole and Irwin Lainoff, Ruth P. Lasser and Joseph R. Lasser, Mr. and Mrs. John T. Marvin, Martin E. and Joan Messinger, Richard L. Yett and Sheri and Paul Siegel, 1986",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/264957,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.1189.64,false,true,264958,Photographs,Photograph,Father of the Forest,,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",American,1829,1916,"1865–66, printed ca. 1876",1865,1866,Albumen silver print from glass negative,,"Gift of Carole and Irwin Lainoff, Ruth P. Lasser and Joseph R. Lasser, Mr. and Mrs. John T. Marvin, Martin E. and Joan Messinger, Richard L. Yett and Sheri and Paul Siegel, 1986",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/264958,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.1189.65,false,true,264959,Photographs,Photograph,"The Father of the Forest, 112 feet circumference, Calaveras Grove",,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",American,1829,1916,"1865–66, printed ca. 1876",1865,1866,Albumen silver print from glass negative,,"Gift of Carole and Irwin Lainoff, Ruth P. Lasser and Joseph R. Lasser, Mr. and Mrs. John T. Marvin, Martin E. and Joan Messinger, Richard L. Yett and Sheri and Paul Siegel, 1986",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/264959,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.1189.66,false,true,264960,Photographs,Photograph,Mother of the Forest,,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",American,1829,1916,"1865–66, printed ca. 1876",1865,1866,Albumen silver print from glass negative,,"Gift of Carole and Irwin Lainoff, Ruth P. Lasser and Joseph R. Lasser, Mr. and Mrs. John T. Marvin, Martin E. and Joan Messinger, Richard L. Yett and Sheri and Paul Siegel, 1986",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/264960,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.1189.67,false,true,264961,Photographs,Photograph,"Section of the Grizzly Giant, 101 feet circumference",,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",American,1829,1916,"1865–66, printed ca. 1876",1865,1866,Albumen silver print from glass negative,Image: 33.5 x 26.6 cm (13 3/16 x 10 1/2 in.),"Gift of Carole and Irwin Lainoff, Ruth P. Lasser and Joseph R. Lasser, Mr. and Mrs. John T. Marvin, Martin E. and Joan Messinger, Richard L. Yett and Sheri and Paul Siegel, 1986",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/264961,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.1189.68,false,true,264962,Photographs,Photograph,Pioneer's Cabin,,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",American,1829,1916,"1865–66, printed ca. 1876",1865,1866,Albumen silver print from glass negative,,"Gift of Carole and Irwin Lainoff, Ruth P. Lasser and Joseph R. Lasser, Mr. and Mrs. John T. Marvin, Martin E. and Joan Messinger, Richard L. Yett and Sheri and Paul Siegel, 1986",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/264962,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.1189.69,false,true,264963,Photographs,Photograph,"The Three Graces, 272 feet",,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",American,1829,1916,"1865–66, printed ca. 1876",1865,1866,Albumen silver print from glass negative,,"Gift of Carole and Irwin Lainoff, Ruth P. Lasser and Joseph R. Lasser, Mr. and Mrs. John T. Marvin, Martin E. and Joan Messinger, Richard L. Yett and Sheri and Paul Siegel, 1986",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/264963,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.1189.70,false,true,264965,Photographs,Photograph,Looking Up Pluto's Chimney,,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",American,1829,1916,"1865–66, printed ca. 1876",1865,1866,Albumen silver print from glass negative,,"Gift of Carole and Irwin Lainoff, Ruth P. Lasser and Joseph R. Lasser, Mr. and Mrs. John T. Marvin, Martin E. and Joan Messinger, Richard L. Yett and Sheri and Paul Siegel, 1986",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/264965,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.1189.71,false,true,264966,Photographs,Photograph,"Hutchings Hotel, Yosemite",,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",American,1829,1916,"ca. 1872, printed ca. 1876",1870,1874,Albumen silver print from glass negative,,"Gift of Carole and Irwin Lainoff, Ruth P. Lasser and Joseph R. Lasser, Mr. and Mrs. John T. Marvin, Martin E. and Joan Messinger, Richard L. Yett and Sheri and Paul Siegel, 1986",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/264966,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.1189.72,false,true,264967,Photographs,Photograph,Geyser Road,,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",American,1829,1916,"1868–70, printed ca. 1876",1868,1870,Albumen silver print from glass negative,,"Gift of Carole and Irwin Lainoff, Ruth P. Lasser and Joseph R. Lasser, Mr. and Mrs. John T. Marvin, Martin E. and Joan Messinger, Richard L. Yett and Sheri and Paul Siegel, 1986",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/264967,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.1189.73,false,true,264968,Photographs,Photograph,Sulphur Creek and Flume-road to Geysers,,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",American,1829,1916,"1868–70, printed ca. 1876",1868,1870,Albumen silver print from glass negative,,"Gift of Carole and Irwin Lainoff, Ruth P. Lasser and Joseph R. Lasser, Mr. and Mrs. John T. Marvin, Martin E. and Joan Messinger, Richard L. Yett and Sheri and Paul Siegel, 1986",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/264968,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.1189.74,false,true,264969,Photographs,Photograph,Sulphur Creek and Road to Geysers,,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",American,1829,1916,"1868–70, printed ca. 1876",1868,1870,Albumen silver print from glass negative,,"Gift of Carole and Irwin Lainoff, Ruth P. Lasser and Joseph R. Lasser, Mr. and Mrs. John T. Marvin, Martin E. and Joan Messinger, Richard L. Yett and Sheri and Paul Siegel, 1986",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/264969,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.1189.75,false,true,264970,Photographs,Photograph,"Hot Sulphur Springs, Santa Barbara",,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",American,1829,1916,"1876, printed ca. 1876",1876,1876,Albumen silver print from glass negative,,"Gift of Carole and Irwin Lainoff, Ruth P. Lasser and Joseph R. Lasser, Mr. and Mrs. John T. Marvin, Martin E. and Joan Messinger, Richard L. Yett and Sheri and Paul Siegel, 1986",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/264970,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.1189.76,false,true,264971,Photographs,Photograph,"Hot Sulphur Springs, Santa Barbara",,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",American,1829,1916,"1876, printed ca. 1876",1876,1876,Albumen silver print from glass negative,,"Gift of Carole and Irwin Lainoff, Ruth P. Lasser and Joseph R. Lasser, Mr. and Mrs. John T. Marvin, Martin E. and Joan Messinger, Richard L. Yett and Sheri and Paul Siegel, 1986",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/264971,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.1189.77,false,true,264972,Photographs,Photograph,Santa Barbara and Mission Church,,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",American,1829,1916,"1876, printed ca. 1876",1876,1876,Albumen silver print from glass negative,,"Gift of Carole and Irwin Lainoff, Ruth P. Lasser and Joseph R. Lasser, Mr. and Mrs. John T. Marvin, Martin E. and Joan Messinger, Richard L. Yett and Sheri and Paul Siegel, 1986",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/264972,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.1189.78,false,true,264973,Photographs,Photograph,"Old Mission Church, Santa Barbara",,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",American,1829,1916,"1876, printed ca. 1876",1876,1876,Albumen silver print from glass negative,,"Gift of Carole and Irwin Lainoff, Ruth P. Lasser and Joseph R. Lasser, Mr. and Mrs. John T. Marvin, Martin E. and Joan Messinger, Richard L. Yett and Sheri and Paul Siegel, 1986",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/264973,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.1189.79,false,true,264974,Photographs,Photograph,San Luis Obispo,,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",American,1829,1916,"1876, printed ca. 1876",1876,1876,Albumen silver print from glass negative,,"Gift of Carole and Irwin Lainoff, Ruth P. Lasser and Joseph R. Lasser, Mr. and Mrs. John T. Marvin, Martin E. and Joan Messinger, Richard L. Yett and Sheri and Paul Siegel, 1986",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/264974,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.1189.80,false,true,264976,Photographs,Photograph,"Steel's Ranch, San Luis Obispo",,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",American,1829,1916,"1876, printed ca. 1876",1876,1876,Albumen silver print from glass negative,,"Gift of Carole and Irwin Lainoff, Ruth P. Lasser and Joseph R. Lasser, Mr. and Mrs. John T. Marvin, Martin E. and Joan Messinger, Richard L. Yett and Sheri and Paul Siegel, 1986",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/264976,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.1189.81,false,true,264977,Photographs,Photograph,San Diego,,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",American,1829,1916,"1876, printed ca. 1876",1876,1876,Albumen silver print from glass negative,,"Gift of Carole and Irwin Lainoff, Ruth P. Lasser and Joseph R. Lasser, Mr. and Mrs. John T. Marvin, Martin E. and Joan Messinger, Richard L. Yett and Sheri and Paul Siegel, 1986",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/264977,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.1189.82,false,true,264978,Photographs,Photograph,Los Angeles,,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",American,1829,1916,"1876, printed ca. 1876",1876,1876,Albumen silver print from glass negative,,"Gift of Carole and Irwin Lainoff, Ruth P. Lasser and Joseph R. Lasser, Mr. and Mrs. John T. Marvin, Martin E. and Joan Messinger, Richard L. Yett and Sheri and Paul Siegel, 1986",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/264978,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.1189.83,false,true,264979,Photographs,Photograph,"Lake Vineyard and Orange Grove, Los Angeles",,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",American,1829,1916,"1876, printed ca. 1876",1876,1876,Albumen silver print from glass negative,,"Gift of Carole and Irwin Lainoff, Ruth P. Lasser and Joseph R. Lasser, Mr. and Mrs. John T. Marvin, Martin E. and Joan Messinger, Richard L. Yett and Sheri and Paul Siegel, 1986",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/264979,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.1189.84,false,true,264980,Photographs,Photograph,Vineyard of Camulos Ranch,,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",American,1829,1916,"1876, printed ca. 1876",1876,1876,Albumen silver print from glass negative,,"Gift of Carole and Irwin Lainoff, Ruth P. Lasser and Joseph R. Lasser, Mr. and Mrs. John T. Marvin, Martin E. and Joan Messinger, Richard L. Yett and Sheri and Paul Siegel, 1986",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/264980,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.1189.85,false,true,264981,Photographs,Photograph,Santa Margarita Ranch,,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",American,1829,1916,"1876, printed ca. 1876",1876,1876,Albumen silver print from glass negative,,"Gift of Carole and Irwin Lainoff, Ruth P. Lasser and Joseph R. Lasser, Mr. and Mrs. John T. Marvin, Martin E. and Joan Messinger, Richard L. Yett and Sheri and Paul Siegel, 1986",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/264981,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.1189.86,false,true,264982,Photographs,Photograph,Virginia City,,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",American,1829,1916,"1875, printed ca. 1876",1875,1875,Albumen silver print from glass negative,,"Gift of Carole and Irwin Lainoff, Ruth P. Lasser and Joseph R. Lasser, Mr. and Mrs. John T. Marvin, Martin E. and Joan Messinger, Richard L. Yett and Sheri and Paul Siegel, 1986",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/264982,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.1189.87,false,true,264983,Photographs,Photograph,Virginia City,,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",American,1829,1916,"1875, printed ca. 1876",1875,1875,Albumen silver print from glass negative,,"Gift of Carole and Irwin Lainoff, Ruth P. Lasser and Joseph R. Lasser, Mr. and Mrs. John T. Marvin, Martin E. and Joan Messinger, Richard L. Yett and Sheri and Paul Siegel, 1986",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/264983,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.1189.88,false,true,264984,Photographs,Photograph,Sutro Tunnel's Road to Virginia City,,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",American,1829,1916,"1875, printed ca. 1876",1875,1875,Albumen silver print from glass negative,,"Gift of Carole and Irwin Lainoff, Ruth P. Lasser and Joseph R. Lasser, Mr. and Mrs. John T. Marvin, Martin E. and Joan Messinger, Richard L. Yett and Sheri and Paul Siegel, 1986",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/264984,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.1189.89,false,true,264985,Photographs,Photograph,Road View to Sutro Tunnel,,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",American,1829,1916,"1875, printed ca. 1876",1875,1875,Albumen silver print from glass negative,,"Gift of Carole and Irwin Lainoff, Ruth P. Lasser and Joseph R. Lasser, Mr. and Mrs. John T. Marvin, Martin E. and Joan Messinger, Richard L. Yett and Sheri and Paul Siegel, 1986",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/264985,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.1189.90,false,true,264987,Photographs,Photograph,Buildings of Sutro Tunnel,,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",American,1829,1916,"1875, printed ca. 1876",1875,1875,Albumen silver print from glass negative,,"Gift of Carole and Irwin Lainoff, Ruth P. Lasser and Joseph R. Lasser, Mr. and Mrs. John T. Marvin, Martin E. and Joan Messinger, Richard L. Yett and Sheri and Paul Siegel, 1986",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/264987,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.1189.91,false,true,264988,Photographs,Photograph,Sutro Tunnel Shaft No. 2,,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",American,1829,1916,"1875, printed ca. 1876",1875,1875,Albumen silver print from glass negative,,"Gift of Carole and Irwin Lainoff, Ruth P. Lasser and Joseph R. Lasser, Mr. and Mrs. John T. Marvin, Martin E. and Joan Messinger, Richard L. Yett and Sheri and Paul Siegel, 1986",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/264988,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.1189.92,false,true,264989,Photographs,Photograph,Sutro Tunnel Shaft No. 2,,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",American,1829,1916,"1875, printed ca. 1876",1875,1875,Albumen silver print from glass negative,,"Gift of Carole and Irwin Lainoff, Ruth P. Lasser and Joseph R. Lasser, Mr. and Mrs. John T. Marvin, Martin E. and Joan Messinger, Richard L. Yett and Sheri and Paul Siegel, 1986",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/264989,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.1189.93,false,true,264990,Photographs,Photograph,Sutro Tunnel Shaft No. 3,,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",American,1829,1916,"1875, printed ca. 1876",1875,1875,Albumen silver print from glass negative,,"Gift of Carole and Irwin Lainoff, Ruth P. Lasser and Joseph R. Lasser, Mr. and Mrs. John T. Marvin, Martin E. and Joan Messinger, Richard L. Yett and Sheri and Paul Siegel, 1986",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/264990,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.1189.94,false,true,264991,Photographs,Photograph,"Eureka Quartz Mill and Flume, Nevada",,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",American,1829,1916,"1875, printed ca. 1876",1875,1875,Albumen silver print from glass negative,,"Gift of Carole and Irwin Lainoff, Ruth P. Lasser and Joseph R. Lasser, Mr. and Mrs. John T. Marvin, Martin E. and Joan Messinger, Richard L. Yett and Sheri and Paul Siegel, 1986",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/264991,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.1189.95,false,true,264992,Photographs,Photograph,"Eureka Quartz Mill and Flume, Nevada",,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",American,1829,1916,"1875, printed ca. 1876",1875,1875,Albumen silver print from glass negative,,"Gift of Carole and Irwin Lainoff, Ruth P. Lasser and Joseph R. Lasser, Mr. and Mrs. John T. Marvin, Martin E. and Joan Messinger, Richard L. Yett and Sheri and Paul Siegel, 1986",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/264992,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.1189.96,false,true,264993,Photographs,Photograph,"Passage of the Dalles, Oregon",,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",American,1829,1916,"1867, printed ca. 1876",1867,1867,Albumen silver print from glass negative,,"Gift of Carole and Irwin Lainoff, Ruth P. Lasser and Joseph R. Lasser, Mr. and Mrs. John T. Marvin, Martin E. and Joan Messinger, Richard L. Yett and Sheri and Paul Siegel, 1986",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/264993,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.1189.97,false,true,264994,Photographs,Photograph,"Cascades, Oregon",,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",American,1829,1916,"1867, printed ca. 1876",1867,1867,Albumen silver print from glass negative,,"Gift of Carole and Irwin Lainoff, Ruth P. Lasser and Joseph R. Lasser, Mr. and Mrs. John T. Marvin, Martin E. and Joan Messinger, Richard L. Yett and Sheri and Paul Siegel, 1986",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/264994,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.1189.98,false,true,264995,Photographs,Photograph,"Cape Horn, Oregon",,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",American,1829,1916,"1867, printed ca. 1876",1867,1867,Albumen silver print from glass negative,,"Gift of Carole and Irwin Lainoff, Ruth P. Lasser and Joseph R. Lasser, Mr. and Mrs. John T. Marvin, Martin E. and Joan Messinger, Richard L. Yett and Sheri and Paul Siegel, 1986",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/264995,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.1189.99,false,true,264996,Photographs,Photograph,"Islands in the Upper Cascades, Oregon",,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",American,1829,1916,"1867, printed ca. 1876",1867,1867,Albumen silver print from glass negative,,"Gift of Carole and Irwin Lainoff, Ruth P. Lasser and Joseph R. Lasser, Mr. and Mrs. John T. Marvin, Martin E. and Joan Messinger, Richard L. Yett and Sheri and Paul Siegel, 1986",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/264996,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.107,false,true,283220,Photographs,Photograph,"Sugar Loaf Islands, Farallons",,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",American,1829,1916,1868–69,1868,1869,Albumen silver print from glass negative,Image: 40 x 52.4; Mount: 55.3 x 66.3,"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/283220,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.108,false,true,283221,Photographs,Photograph,"Multnomah Falls Cascade, Columbia River",,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",American,1829,1916,1867,1867,1867,Albumen silver print from glass negative,Image: 52.4 x 40 cm (20 5/8 x 15 3/4 in.) Mount: 64.7 x 49.5 cm (25 1/2 x 19 1/2 in.),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/283221,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.109,true,true,283222,Photographs,Photograph,Cape Horn near Celilo,,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",American,1829,1916,1867,1867,1867,Albumen silver print from glass negative,Image: 40 x 52.4cm (15 3/4 x 20 5/8in.) Mount: 54.5 x 68.6 cm (21 7/16 x 27 in.),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/283222,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.110,false,true,283223,Photographs,Photograph,"Strait of Carquennes, from South Vallejo",,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",American,1829,1916,1868–69,1868,1869,Albumen silver print from glass negative,Image: 40.3 x 52.5 cm (15 7/8 x 20 11/16 in.) Mount: 54.4 x 66.4 cm (21 7/16 x 26 1/8 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/283223,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.174,false,true,285468,Photographs,Photograph,"Multnomah Falls, Oregon",,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",American,1829,1916,"1867, printed later",1867,1867,Albumen silver print from glass negative,Image: 52.1 x 38.7 cm (20 1/2 x 15 1/4 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285468,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.339,false,true,285987,Photographs,Photograph,"Indian Sweat House, Mendicino County, Colorado",,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",American,1829,1916,1863,1863,1863,Albumen silver print from glass negative,Image: 40 x 52.4 cm (15 3/4 x 20 5/8 in.) Mount: 55.1 x 68.3 cm (21 11/16 x 26 7/8 in.),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285987,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.493,false,true,286513,Photographs,Photograph,"Cape Horn, Columbia River, Oregon",,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",American,1829,1916,1867,1867,1867,Albumen silver print from glass negative,Image: 52.1 x 39 cm (20 1/2 x 15 3/8 in.) Frame: 76.2 x 63.5 cm (30 x 25 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286513,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.618,false,true,283219,Photographs,Photograph,"The Grisly Giant, Mariposa Grove, Yosemite",,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",American,1829,1916,1861,1861,1861,Albumen silver print from glass negative,Image: 52.3 x 40.7; Mount: 61.4 x 54.1,"Gilman Collection, Purchase, Gift of The Howard Gilman Foundation, by exchange, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/283219,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.1189.100,false,true,264898,Photographs,Photograph,"Tooth Bridge, Oregon",,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",American,1829,1916,"1867, printed ca. 1876",1867,1867,Albumen silver print from glass negative,,"Gift of Carole and Irwin Lainoff, Ruth P. Lasser and Joseph R. Lasser, Mr. and Mrs. John T. Marvin, Martin E. and Joan Messinger, Richard L. Yett and Sheri and Paul Siegel, 1986",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/264898,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.1189.101,false,true,264899,Photographs,Photograph,"Castle Rock, Oregon",,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",American,1829,1916,"1867, printed ca. 1876",1867,1867,Albumen silver print from glass negative,,"Gift of Carole and Irwin Lainoff, Ruth P. Lasser and Joseph R. Lasser, Mr. and Mrs. John T. Marvin, Martin E. and Joan Messinger, Richard L. Yett and Sheri and Paul Siegel, 1986",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/264899,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.1183,false,true,285825,Photographs,Photograph,"2637 Ft. Yosemite Fall, Front View",,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",American,1829,1916,1861,1861,1861,Albumen silver print from glass negative,"Image: 40.9 x 51.3 cm (16 1/8 x 20 3/16 in.), arch-topped Mount: 53.6 x 67 cm (21 1/8 x 26 3/8 in.)","Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285825,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.1184,false,true,285714,Photographs,Photograph,Mt. Broderick and Nevada Fall. Fall = 700 ft.,,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",American,1829,1916,"1861, Yosemite",1861,1861,Albumen silver print from glass negative,Image: 41.8 × 52.1 cm (16 7/16 × 20 1/2 in.) Mount: 53.9 × 66.9 cm (21 1/4 × 26 5/16 in.),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285714,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.1185,false,true,285715,Photographs,Photograph,North Dome on left - Royal Arches - Washington Column,,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",American,1829,1916,"1861, Yosemite",1861,1861,Albumen silver print from glass negative,Image: 42.5 × 51.5 cm (16 3/4 × 20 1/4 in.) Mount: 53.8 × 67.3 cm (21 3/16 × 26 1/2 in.),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285715,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.1186,false,true,286077,Photographs,Photograph,Yosemite Fall. Down the Valley. 2637 Ft.,,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",American,1829,1916,"1861, Yosemite",1861,1861,Albumen silver print from glass negative,Image: 44 × 50.9 cm (17 5/16 × 20 1/16 in.) Mount: 53.5 × 67 cm (21 1/16 × 26 3/8 in.),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286077,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.1187,false,true,285860,Photographs,Photograph,"Cascade, Nevada Fall on Left, View above Vernal Fall",,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",American,1829,1916,1861,1861,1861,Albumen silver print from glass negative,"Image: 41.2 x 52.4 cm (16 1/4 x 20 5/8 in.), arch-topped Mount: 53.5 x 66.7 cm (21 1/16 x 26 1/4 in.)","Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285860,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.1188,false,true,286423,Photographs,Photograph,Sentinel. Front View: 3270,,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",American,1829,1916,"1861, Yosemite",1861,1861,Albumen silver print from glass negative,Image: 43.1 × 51.6 cm (16 15/16 × 20 5/16 in.) Mount: 53.9 × 67 cm (21 1/4 × 26 3/8 in.),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286423,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.1189,false,true,286425,Photographs,Photograph,Cathedral Rock,,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",American,1829,1916,1861,1861,1861,Albumen silver print from glass negative,"Image: 42.1 x 50.8 cm (16 9/16 x 20 in.), arch-topped Mount: 53.7 x 66.7 cm (21 1/8 x 26 1/4 in.)","Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286425,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.1190,false,true,286427,Photographs,Photograph,"Mt. Broderick in Distant Centre, Piroyac, Falling Chrystals, Vernal Fall",,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",American,1829,1916,1861,1861,1861,Albumen silver print from glass negative,"Image: 42.4 x 51.5 cm (16 11/16 x 20 1/4 in.), arch-topped Mount: 53.4 x 67 cm (21 x 26 3/8 in.)","Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286427,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.1256,false,true,286052,Photographs,Photograph,"Pohono, Bridal Veil, 900 Feet, Yosemite",,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",American,1829,1916,1861,1861,1861,Albumen silver print from glass negative,Image: 16 1/2 × 20 9/16 in. (41.9 × 52.3 cm) Mount: 21 5/16 in. × 26 7/16 in. (54.1 × 67.2 cm),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286052,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.1257,false,true,285824,Photographs,Photograph,"Up the Valley, North Dome in Center, Sentinel on Left",,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",American,1829,1916,1861,1861,1861,Albumen silver print from glass negative,Image: 16 1/2 × 20 9/16 in. (41.9 × 52.3 cm) Mount: 21 5/16 in. × 26 7/16 in. (54.1 × 67.2 cm),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285824,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.1258,false,true,285994,Photographs,Photograph,"River View, Down the Valley, Cathedral Rock on Left",,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",American,1829,1916,"1861, Yosemite",1861,1861,Albumen silver print from glass negative,Image: 15 9/16 × 20 9/16 in. (39.5 × 52.2 cm) Mount: 21 1/16 × 26 1/2 in. (53.5 × 67.3 cm),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285994,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.1259,false,true,285713,Photographs,Photograph,"Camp Grove, Near Sentinel",,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",American,1829,1916,"1861, Yosemite",1861,1861,Albumen silver print from glass negative,Image: 15 1/2 × 19 5/8 in. (39.3 × 49.9 cm) Mount: 21 1/4 in. × 26 3/8 in. (54 × 67 cm),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285713,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.1260,false,true,286458,Photographs,Photograph,"Tasayac, or the Half Dome, 4967 Feet",,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",American,1829,1916,"1861, Yosemite",1861,1861,Albumen silver print from glass negative,Image: 15 13/16 × 20 1/4 in. (40.1 × 51.5 cm) Image: 21 1/4 × 26 3/8 in. (54 × 67 cm),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286458,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.1261,false,true,286459,Photographs,Photograph,"Nevada Fall, 700 Feet",,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",American,1829,1916,1861,1861,1861,Albumen silver print from glass negative,Image: 42.2 x 51; Mount: 53.6 x 67,"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286459,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.1262,false,true,286460,Photographs,Photograph,"Tutucanula, El Capitan",,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",American,1829,1916,1861,1861,1861,Albumen silver print from glass negative,Image: 51.3 x 41.4; Mount: 66.5 x 53.6,"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286460,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.1263,false,true,286072,Photographs,Photograph,"River View, Sentinel, 3270 Feet",,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",American,1829,1916,1861,1861,1861,Albumen silver print from glass negative,Image: 52.1 x 41.6; Mount: 60.8 x 53.6,"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286072,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.1264,false,true,286073,Photographs,Photograph,"Three Brothers, Front View, 4480 Feet",,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",American,1829,1916,1861,1861,1861,Albumen silver print from glass negative,Image: 20 3/8 × 16 1/8 in. (51.7 × 40.9 cm) Mount: 26 5/16 in. × 21 1/8 in. (66.9 × 53.6 cm),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286073,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.1265,false,true,286074,Photographs,Photograph,"Outline View of the Half Dome, 4967 Feet",,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",American,1829,1916,1861,1861,1861,Albumen silver print from glass negative,Image: 20 1/16 × 16 5/16 in. (51 × 41.5 cm) Mount: 21 1/4 × 26 3/8 in. (54 × 67 cm),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286074,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.1266,false,true,286075,Photographs,Photograph,"River View, Sentinel, 3270 Feet",,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",American,1829,1916,1861,1861,1861,Albumen silver print from glass negative,Image: 20 1/4 × 16 7/16 in. (51.5 × 41.7 cm) Mount: 21 1/4 × 26 3/8 in. (54 × 67 cm),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286075,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.1267,false,true,286076,Photographs,Photograph,Cathedral Towers,,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",American,1829,1916,1861,1861,1861,Albumen silver print from glass negative,Image: 20 3/8 × 16 1/8 in. (51.7 × 40.9 cm) Mount: 26 5/16 in. × 21 1/8 in. (66.9 × 53.6 cm),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286076,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.1268,false,true,286078,Photographs,Photograph,The Lake at the Foot of Half Dome,,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",American,1829,1916,1861,1861,1861,Albumen silver print from glass negative,Image: 16 1/2 × 20 9/16 in. (41.9 × 52.3 cm) Mount: 21 5/16 in. × 26 7/16 in. (54.1 × 67.2 cm),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286078,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.1269,false,true,285858,Photographs,Photograph,"Cathedral Rock, Down the Valley",,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",American,1829,1916,1861,1861,1861,Albumen silver print from glass negative,Image: 16 1/2 × 20 9/16 in. (41.9 × 52.3 cm) Mount: 21 5/16 in. × 26 7/16 in. (54.1 × 67.2 cm),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285858,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.1270,false,true,285859,Photographs,Photograph,"Section of Grisly Giant, Mariposa Grove",,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",American,1829,1916,1861,1861,1861,Albumen silver print from glass negative,Image: 16 5/16 × 20 11/16 in. (41.5 × 52.5 cm) Mount: 21 1/4 × 26 3/8 in. (54 × 67 cm),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285859,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.1271,false,true,285861,Photographs,Photograph,"River View Down Valley, Cathedral Rock on Left",,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",American,1829,1916,1861,1861,1861,Albumen silver print from glass negative,Image: 15 9/16 × 20 9/16 in. (39.5 × 52.2 cm) Mount: 21 1/16 × 26 1/2 in. (53.5 × 67.3 cm),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285861,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.1272,false,true,286510,Photographs,Photograph,"Tacoye, The North Dome, 3729 Feet",,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",American,1829,1916,1861,1861,1861,Albumen silver print from glass negative,Image: 19 15/16 × 16 5/16 in. (50.6 × 41.4 cm) Mount: 21 1/4 × 26 3/8 in. (54 × 67 cm),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286510,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.1273,false,true,286424,Photographs,Photograph,"Cathedral Rock, River View",,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",American,1829,1916,1861,1861,1861,Albumen silver print from glass negative,Image: 41.5 x 59.6; Mount: 53.4 x 67,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286424,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.1274,false,true,286426,Photographs,Photograph,"Yosemite Falls, River View, 2637 Feet",,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",American,1829,1916,1861,1861,1861,Albumen silver print from glass negative,Image: 16 1/2 × 19 1/2 in. (41.9 × 49.5 cm) Mount: 21 1/4 × 26 3/8 in. (54 × 67 cm),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286426,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.1275,false,true,286428,Photographs,Photograph,"Pompomasos (Leaping Frogs), Three Brothers",,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",American,1829,1916,1861,1861,1861,Albumen silver print from glass negative,Image: 42.4 x 52.2; Mount: 53.7 x 69.3,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286428,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.1276,false,true,286457,Photographs,Photograph,"Tutucanula, El Capitan, 4000 Feet",,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",American,1829,1916,1861,1861,1861,Albumen silver print from glass negative,Image: 16 1/2 × 19 1/2 in. (41.9 × 49.5 cm) Mount: 21 1/4 × 26 3/8 in. (54 × 67 cm),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286457,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.1084.1–.3,false,true,689997,Photographs,Panorama,"View from the Sentinel Dome, Yosemite",,,,,,Artist,,Carleton E. Watkins,"American, 1829–1916",,"Watkins, Carleton E.",American,1829,1916,1865–66,1865,1866,Albumen silver prints from glass negatives,Image 1: 40.3 × 52.1 cm (15 7/8 × 20 1/2 in.) Mount 1: 53.4 x 68.7cm (21 x 27 1/16in.) Image 2: 40.0 x 52.5 cm (15 3/4 x 20 11/16 in.) Mount 2: 53.3 x 68.8cm (21 x 27 1/16in.) Image 3: 40.8 x 52.5cm (16 1/16 x 20 11/16in.) Mount 3: 53.2 x 68.7cm (20 15/16 x 27 1/16in.),"Purchase, Joseph Pulitzer Bequest, 1989",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/689997,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.1196,false,true,265001,Photographs,Photograph,"Hanging Rock, Foot of Echo Cañon",,,,,,Artist,,Andrew Joseph Russell,"American, 1830–1902",,"Russell, Andrew Joseph",American,1830,1902,1867–68,1867,1868,Albumen silver print from glass negative,Image: 21.3 x 28.2 cm (8 3/8 x 11 1/8 in.),"Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 1986",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/265001,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.91,false,true,283193,Photographs,Photograph,"Slave Pen, Alexandria, Virginia",,,,,,Artist,,Andrew Joseph Russell,"American, 1830–1902",,"Russell, Andrew Joseph",American,1830,1902,1863,1863,1863,Albumen silver print from glass negative,Image: 25.6 x 36.5cm (10 1/16 x 14 3/8in.) Mount: 12 3/16 × 16 9/16 in. (31 × 42 cm),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/283193,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.1237,false,true,286065,Photographs,Photograph,"Fort Laramie, Wyoming",,,,,,Artist,Attributed to,Ridgway Glover,"American, 1831–1866",,"Glover, Ridgway",American,1831,1866,ca. 1866,1864,1868,Albumen silver print from glass negative,"Image: 12.8 × 17.8 cm (5 1/16 × 7 in.), oval Mount: 20.3 × 25.3 cm (8 × 9 15/16 in.)","Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286065,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1980.1056.5,false,true,262764,Photographs,Stereograph,"[Ship in Ice, Greenland Expedition]",,,,,,Artist,,Isaac Israel Hayes,"American, 1832–1881",,"Hayes, Isaac Israel",American,1832,1881,ca. 1859,1857,1861,Albumen silver print from glass negative,,"Warner Communications Inc. Purchase Fund, 1980",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/262764,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1980.1056.7,false,true,262766,Photographs,Stereograph,Esquimau,,,,,,Artist,,Isaac Israel Hayes,"American, 1832–1881",,"Hayes, Isaac Israel",American,1832,1881,ca. 1859,1857,1861,Albumen silver print from glass negative,,"Warner Communications Inc. Purchase Fund, 1980",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/262766,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.458,false,true,685798,Photographs,Carte-de-visite,[E. Johnson],,,,,,Artist,,George Gardner Rockwood,"American, 1832–1911",,"Rockwood, George Gardner",American,1832,1911,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/685798,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.471,false,true,685811,Photographs,Carte-de-visite,[John Frederick Kensett],,,,,,Artist,,George Gardner Rockwood,"American, 1832–1911",,"Rockwood, George Gardner",American,1832,1911,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/685811,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.725,false,true,686064,Photographs,Carte-de-visite,[Aaron Draper Shattuck],,,,,,Artist,,George Gardner Rockwood,"American, 1832–1911",,"Rockwood, George Gardner",American,1832,1911,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/686064,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.752,false,true,686091,Photographs,Carte-de-visite,[W.O.? Stone],,,,,,Artist,,George Gardner Rockwood,"American, 1832–1911",,"Rockwood, George Gardner",American,1832,1911,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/686091,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.771,false,true,686110,Photographs,Carte-de-visite,[Laurent ?],,,,,,Artist,,George Gardner Rockwood,"American, 1832–1911",,"Rockwood, George Gardner",American,1832,1911,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/686110,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.804,false,true,686143,Photographs,Carte-de-visite,[Unknown Subject],,,,,,Artist,,George Gardner Rockwood,"American, 1832–1911",,"Rockwood, George Gardner",American,1832,1911,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/686143,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.855,false,true,686194,Photographs,Carte-de-visite,[Unknown Subject],,,,,,Artist,,George Gardner Rockwood,"American, 1832–1911",,"Rockwood, George Gardner",American,1832,1911,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/686194,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.391,false,true,288841,Photographs,Photograph,"[Lula Lake and Upper Falls on Rock Creek, near Lookout Mountain, Georgia]",,,,,,Artist,,Isaac H. Bonsall,"American, 1833–1909",,"Bonsall, Isaac H.",American,1833,1909,1864–65,1864,1865,Albumen silver print from glass negative,Image: 26.5 x 33.7 cm (10 7/16 x 13 1/4 in.),"Purchase, Celia Tompkins Hegyi Gift, 2007",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288841,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.392,false,true,288840,Photographs,Photograph,"Lulah Falls, Lookout Mountain, Georgia",,,,,,Artist,,Isaac H. Bonsall,"American, 1833–1909",,"Bonsall, Isaac H.",American,1833,1909,1864–65,1864,1865,Albumen silver print from glass negative,Image: 21.2 x 26.6 cm (8 3/8 x 10 1/2 in.),"Purchase, Celia Tompkins Hegyi Gift, 2007",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288840,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.238,false,true,286559,Photographs,Photograph,Deck of U.S. Ship Vermont,,,,,,Artist,,Henry P. Moore,"American, 1833–1911",,"Moore, Henry P.",American,1833,1911,ca. 1863,1861,1865,Albumen silver print from glass negative,Image: 13.3 x 20.2 cm (5 1/4 x 7 15/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286559,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.897,false,true,286558,Photographs,Photograph,"Contrabands Aboard U.S. Ship Vermont, Port Royal, South Carolina",,,,,,Artist,,Henry P. Moore,"American, 1833–1911",,"Moore, Henry P.",American,1833,1911,1861,1861,1861,Albumen silver print from glass negative,Image: 12.9 × 20.8 cm (5 1/16 × 8 3/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286558,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.1137,false,true,286557,Photographs,Photograph,"Negroes (Gwine to de Field), Hopkinson's Plantation, Edisto Island, South Carolina",,,,,,Artist,,Henry P. Moore,"American, 1833–1911",,"Moore, Henry P.",American,1833,1911,1862,1862,1862,Albumen silver print from glass negative,Image: 15.2 × 20.4 cm (6 × 8 1/16 in.),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286557,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.52,false,true,301982,Photographs,Photograph,"Frank Wyatt, One of General Dodge's Band, Corinth, Mississippi",,,,,,Artist,,George W. Armstead,"American, 1833–1912",,"Armstead, George W.",American,1833,1912,"September 18, 1863",1863,1863,Albumen silver print from glass negative,Image: 9 x 5.4 cm (3 9/16 x 2 1/8 in.),"Purchase, Alfred Stieglitz Society Gifts, 2013",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/301982,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.1139,false,true,285746,Photographs,Photograph,"[Hudson River Seen from United State Military Academy at West Point, New York]",,,,,,Artist,,George Kendall Warren,"American, 1834–1884",,"Warren, George Kendall",American,1834,1884,1867,1867,1867,Albumen silver print,Image: 6 1/4 × 9 1/8 in. (15.9 × 23.2 cm) Mount: 10 1/8 in. × 13 3/16 in. (25.7 × 33.5 cm),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285746,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.1140,false,true,285745,Photographs,Photograph,"""Sam,"" the Black Peddlar",,,,,,Artist,,George Kendall Warren,"American, 1834–1884",,"Warren, George Kendall",American,1834,1884,ca. 1858,1856,1860,Salted paper print,Image: 6 9/16 in. × 5 in. (16.7 × 12.7 cm) Mount: 11 7/16 in. × 8 1/2 in. (29.1 × 21.6 cm),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285745,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -46.29.9,false,true,269088,Photographs,Carte-de-visite,Steamer R.E. Lee Racing with Natches When Nearing St. Louis,,,,,,Artist,,Robert Benecke,"American, 1835–1903",,"Benecke, Robert",American,1835,1903,ca. 1870,1868,1872,Albumen silver print,,"Gift of Mrs. Lawrence Fowler, 1946",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269088,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.1245,false,true,286312,Photographs,Photograph,"Ma-ni-mic, Cheyenne Chief",,,,,,Artist,,William Stinson Soule,"American, 1836–1908",,"Soule, William Stinson",American,1836,1908,1869–74,1869,1874,Albumen silver print from glass negative,Image: 7 7/16 × 5 3/8 in. (18.9 × 13.7 cm) Mount: 14 3/4 in. × 10 7/8 in. (37.4 × 27.7 cm),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286312,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1999.367.5,false,true,283336,Photographs,Photograph,[Wisconsin Landscape],,,,,,Artist,,Henry Hamilton Bennett,"American, 1843–1908",,"Bennett, Henry Hamilton",American,1843,1908,1889,1889,1889,Albumen silver print,17 x 22 cm (6 11/16 x 8 11/16 in. ),"Bequest of Winthrop Edey, 1999",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/283336,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1974.530,false,true,261106,Photographs,Photograph,"Mammoth Hot Springs, Pulpit Terraces",,,,,,Artist,,William Henry Jackson,"American, 1843–1942",,"Jackson, William Henry",American,1843,1942,ca. 1883,1881,1885,Albumen silver print from glass negative,43.0 x 53.1 cm. (16 15/16 x 20 15/16 in.),"Rogers Fund, 1974",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261106,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1973.641.3,false,true,261003,Photographs,Photograph,"Tantalus Cañon, Utah",,,,,,Artist,,William Henry Jackson,"American, 1843–1942",,"Jackson, William Henry",American,1843,1942,1870s,1870,1879,Albumen silver print from glass negative,,"Gift of A. Hyatt Mayor, 1973",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261003,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.1098.7,false,true,631031,Photographs,Photograph,"Hanging Rock, Clear Creek Canyon",,,,,,Artist,,William Henry Jackson,"American, 1843–1942",,"Jackson, William Henry",American,1843,1942,1870s,1870,1879,Albumen silver print,Image: 21 1/4 × 17 7/16 in. (54 × 44.3 cm) Mount: 27 7/8 in. × 22 in. (70.8 × 55.9 cm),"Gift of Joyce F. Menschel, 2013",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/631031,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -42.120.1,false,true,268759,Photographs,Photograph,White Sailboat in Long Island Sound,,,,,,Artist,,Charles E. Bolles,"American, 1845–1919",,"Bolles, Charles E.",American,1845,1919,"1890s, printed 1897",1890,1899,Platinum print,,"Gift of Marion P. Bolles, 1942",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268759,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -42.120.2,false,true,268760,Photographs,Photograph,Sailboat,,,,,,Artist,,Charles E. Bolles,"American, 1845–1919",,"Bolles, Charles E.",American,1845,1919,"1890s, printed 1897",1890,1899,Platinum print,,"Gift of Marion P. Bolles, 1942",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268760,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -42.120.3,false,true,268761,Photographs,Photograph,"Hauling in Nets, Long Island Sound",,,,,,Artist,,Charles E. Bolles,"American, 1845–1919",,"Bolles, Charles E.",American,1845,1919,"1890s, printed 1897",1890,1899,Platinum print,,"Gift of Marion P. Bolles, 1942",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268761,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.572,false,true,285710,Photographs,Photograph,Otoe Delegation,,,,,,Artist,,Charles Milton Bell,"American, 1848–1893",,"Bell, Charles Milton",American,1848,1893,1881,1881,1881,Albumen silver print from glass negative,24 x 28.8 cm (9 7/16 x 11 5/16 in.),"Gilman Collection, Purchase, Joseph M. Cohen Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285710,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.619,false,true,283224,Photographs,Photograph,Red Cloud,,,,,,Artist,,Charles Milton Bell,"American, 1848–1893",,"Bell, Charles Milton",American,1848,1893,1880,1880,1880,Albumen silver print from glass negative,Image: 14 × 9.9 cm (5 1/2 × 3 7/8 in.) Mount: 16.5 × 10.8 cm (6 1/2 × 4 1/4 in.),"Gilman Collection, Purchase, Sam Salz Foundation Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/283224,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -46.164,false,true,269083,Photographs,Photograph,Mr. and Mrs. Charles E. Tiffany in Louis C. Tiffany's Studio,,,,,,Artist,,George Collins Cox,"American, 1851–1902",,"Cox, George Collins",American,1851,1902,ca. 1890,1888,1892,Platinum print (?),18.8 x 23.6 cm (7 3/8 x 9 5/16 in. ),"Museum Accession, 1946",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269083,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -46.165,false,true,269084,Photographs,Photograph,"Frances and Ethel de Forest, daughters of Robert de Forest",,,,,,Artist,,George Collins Cox,"American, 1851–1902",,"Cox, George Collins",American,1851,1902,ca. 1890,1888,1892,Albumen silver print,19.2 x 23.7 cm (7 9/16 x 9 5/16 in. ),"Museum Accession, 1946",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269084,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.43.132,true,true,267530,Photographs,Photograph,Blessed Art Thou among Women,,,,,,Artist,,Gertrude Käsebier,"American, 1852–1934",,"Käsebier, Gertrude",American,1852,1934,1899,1899,1899,Platinum print,23 x 13.2 cm (9 1/16 x 5 3/16 in.),"Alfred Stieglitz Collection, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267530,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.43.136,false,true,267534,Photographs,Photograph,The Sketch,,,,,,Artist,,Gertrude Käsebier,"American, 1852–1934",,"Käsebier, Gertrude",American,1852,1934,1903,1903,1903,Platinum print,15.3 x 20.7 cm. (6 x 8 1/8 in.),"Alfred Stieglitz Collection, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267534,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.43.142,false,true,267541,Photographs,Photograph,Happy Days,,,,,,Artist,,Gertrude Käsebier,"American, 1852–1934",,"Käsebier, Gertrude",American,1852,1934,1902,1902,1902,Platinum print,19.8 x 14.9 cm. (7 13/16 x 5 7/8 in.),"Alfred Stieglitz Collection, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267541,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -49.55.188,false,true,269308,Photographs,Photograph,[F. Holland Day],,,,,,Artist,,Gertrude Käsebier,"American, 1852–1934",,"Käsebier, Gertrude",American,1852,1934,ca. 1898,1896,1900,Platinum print,18.0 x 13.6 cm (7 1/16 x 5 3/8 in.),"Alfred Stieglitz Collection, 1949",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269308,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1984.1024.1,false,true,263962,Photographs,Photograph,"William M. Ivins, Jr.",,,,,,Artist,,Gertrude Käsebier,"American, 1852–1934",,"Käsebier, Gertrude",American,1852,1934,ca. 1910,1905,1915,Platinum print,Image: 7 5/8 × 5 5/16 in. (19.3 × 13.5 cm) Sheet: 7 5/8 × 5 5/16 in. (19.3 × 13.5 cm),"Gift of Barbara Ivins, 1984",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/263962,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.889,false,true,285990,Photographs,Photograph,Baron Adolph de Meyer,,,,,,Artist,,Gertrude Käsebier,"American, 1852–1934",,"Käsebier, Gertrude",American,1852,1934,1903,1903,1903,Platinum print,"Image: 21.7 x 13.9 cm (8 9/16 x 5 1/2 in.), irregularly trimmed Mount: 22.1 x 14.2 cm (8 11/16 x 5 9/16 in.)","Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285990,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.905,false,true,286366,Photographs,Photograph,"Turner Family, Woburn, Massachusetts",,,,,,Artist,,Gertrude Käsebier,"American, 1852–1934",,"Käsebier, Gertrude",American,1852,1934,ca. 1910,1908,1912,Gelatin silver print on tissue,33.2 x 26.1 cm (13 1/16 x 10 1/4 in.),"Gilman Collection, Purchase, Harriette and Noel Levine Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286366,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1983.1041.2,false,true,263549,Photographs,Photograph,[Group with Horse-Drawn Carriage],,,,,,Artist,,Christian Barthelmess,"American, 1854–1906",,"Barthelmess, Christian",American,1854,1906,1890s,1890,1899,Albumen silver print from glass negative,10.2 x 15.8 cm. (4 x 6 1/4 in.),"David Hunter McAlpin Fund, 1983",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/263549,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.113,false,true,283229,Photographs,Photograph,[Children Fishing],,,,,,Artist,,William James Mullins,"American, 1860–1917",,"Mullins, William James",American,1860,1917,ca. 1900,1899,1901,Platinum print,Image: 9.1 x 25.7cm (3 9/16 x 10 1/8in.) Mount: 9.8 × 26.4 cm (3 7/8 in. × 10 3/8 in.),"Gilman Collection, Purchase, Jennifer and Joseph Duke Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/283229,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -30.89.1,false,true,267474,Photographs,Photograph,"Oak, Mission Ridge, Santa Barbara, California",,,,,,Artist,,Henry Ravell,"American, 1860–1930",,"Ravell, Henry",American,1860,1930,1910s,1910,1919,Gum bichromate print,,"Gift of Mrs. Florence D. R. Lothrop, in memory of her brother, Henry Ravell, 1930",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267474,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -30.89.2,false,true,267481,Photographs,Photograph,"Cypress, Pebble Beach, California",,,,,,Artist,,Henry Ravell,"American, 1860–1930",,"Ravell, Henry",American,1860,1930,1910s,1910,1919,Gum bichromate print,,"Gift of Mrs. Florence D. R. Lothrop, in memory of her brother, Henry Ravell, 1930",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267481,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -30.89.3,false,true,267482,Photographs,Photograph,"Cypress, Pebble Beach, California",,,,,,Artist,,Henry Ravell,"American, 1860–1930",,"Ravell, Henry",American,1860,1930,1910s,1910,1919,Gum bichromate print,,"Gift of Mrs. Florence D. R. Lothrop, in memory of her brother, Henry Ravell, 1930",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267482,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -30.89.4,false,true,267483,Photographs,Photograph,"Cypress, Pebble Beach, California",,,,,,Artist,,Henry Ravell,"American, 1860–1930",,"Ravell, Henry",American,1860,1930,1910s,1910,1919,Gum bichromate print,,"Gift of Mrs. Florence D. R. Lothrop, in memory of her brother, Henry Ravell, 1930",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267483,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -30.89.5,false,true,267484,Photographs,Photograph,"Tortilla Women in the Plaza, Mexico",,,,,,Artist,,Henry Ravell,"American, 1860–1930",,"Ravell, Henry",American,1860,1930,1910s,1910,1919,Gum bichromate print,,"Gift of Mrs. Florence D. R. Lothrop, in memory of her brother, Henry Ravell, 1930",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267484,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -30.89.6,false,true,267485,Photographs,Photograph,"Cypress at Pebble Beach, California",,,,,,Artist,,Henry Ravell,"American, 1860–1930",,"Ravell, Henry",American,1860,1930,1910s,1910,1919,Gum bichromate print,,"Gift of Mrs. Florence D. R. Lothrop, in memory of her brother, Henry Ravell, 1930",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267485,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -30.89.7,false,true,267486,Photographs,Photograph,"Cypress, Pebble Beach, California",,,,,,Artist,,Henry Ravell,"American, 1860–1930",,"Ravell, Henry",American,1860,1930,1910s,1910,1919,Gum bichromate print,,"Gift of Mrs. Florence D. R. Lothrop, in memory of her brother, Henry Ravell, 1930",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267486,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -30.89.8,false,true,267487,Photographs,Photograph,"Cypress, Pebble Beach, California",,,,,,Artist,,Henry Ravell,"American, 1860–1930",,"Ravell, Henry",American,1860,1930,1910s,1910,1919,Gum bichromate print,,"Gift of Mrs. Florence D. R. Lothrop, in memory of her brother, Henry Ravell, 1930",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267487,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -30.89.9,false,true,267488,Photographs,Photograph,Fog and Cypress Trees,,,,,,Artist,,Henry Ravell,"American, 1860–1930",,"Ravell, Henry",American,1860,1930,1910s,1910,1919,Gum bichromate print,,"Gift of Mrs. Florence D. R. Lothrop, in memory of her brother, Henry Ravell, 1930",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267488,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -30.89.10,false,true,267475,Photographs,Photograph,"Jesuit Church, Guanajuato, Mexico",,,,,,Artist,,Henry Ravell,"American, 1860–1930",,"Ravell, Henry",American,1860,1930,1910s,1910,1919,Gum bichromate print,,"Gift of Mrs. Florence D. R. Lothrop, in memory of her brother, Henry Ravell, 1930",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267475,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -30.89.11,false,true,267476,Photographs,Photograph,Pottery Sellers by the Church Door,,,,,,Artist,,Henry Ravell,"American, 1860–1930",,"Ravell, Henry",American,1860,1930,1910s,1910,1919,Gum bichromate print,,"Gift of Mrs. Florence D. R. Lothrop, in memory of her brother, Henry Ravell, 1930",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267476,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -30.89.12,false,true,267477,Photographs,Photograph,"A Church Dome at Cuernavaca, Mexico",,,,,,Artist,,Henry Ravell,"American, 1860–1930",,"Ravell, Henry",American,1860,1930,1910s,1910,1919,Gum bichromate print,,"Gift of Mrs. Florence D. R. Lothrop, in memory of her brother, Henry Ravell, 1930",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267477,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -30.89.13,false,true,267478,Photographs,Photograph,Lunching in the Market Place,,,,,,Artist,,Henry Ravell,"American, 1860–1930",,"Ravell, Henry",American,1860,1930,1910s,1910,1919,Gum or carbon transfer with applied media,,"Gift of Mrs. Florence D. R. Lothrop, in memory of her brother, Henry Ravell, 1930",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267478,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -30.89.14,false,true,267479,Photographs,Photograph,Water and Trees of the Viga Canal near Mexico City,,,,,,Artist,,Henry Ravell,"American, 1860–1930",,"Ravell, Henry",American,1860,1930,1910s,1910,1919,Gum bichromate print,,"Gift of Mrs. Florence D. R. Lothrop, in memory of her brother, Henry Ravell, 1930",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267479,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -30.89.15,false,true,267480,Photographs,Photograph,"The Plaza, Market Day, Taxco, Mexico",,,,,,Artist,,Henry Ravell,"American, 1860–1930",,"Ravell, Henry",American,1860,1930,1910s,1910,1919,Gum bichromate print,,"Gift of Mrs. Florence D. R. Lothrop, in memory of her brother, Henry Ravell, 1930",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267480,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.644.2,false,true,260325,Photographs,Photograph,"A Study, No. 1",,,,,,Artist,,Rudolph Eickemeyer,"American, 1862–1932",,"Eickemeyer, Jr., Rudolph",American,1862,1932,1901,1901,1901,Gelatin silver print,,"The Elisha Whittelsey Collection, The Elisha Whittelsey Fund, 1972",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260325,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.644.3,false,true,260326,Photographs,Photograph,[Ships Dockside in a Harbor],,,,,,Artist,,Rudolph Eickemeyer,"American, 1862–1932",,"Eickemeyer, Jr., Rudolph",American,1862,1932,ca. 1900,1898,1902,Platinum print,,"The Elisha Whittelsey Collection, The Elisha Whittelsey Fund, 1972",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260326,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.644.4,false,true,260327,Photographs,Photograph,[Ships on a Beach with Two Long Boats and Two Men Sweeping],,,,,,Artist,,Rudolph Eickemeyer,"American, 1862–1932",,"Eickemeyer, Jr., Rudolph",American,1862,1932,ca. 1900,1898,1902,Platinum print,,"The Elisha Whittelsey Collection, The Elisha Whittelsey Fund, 1972",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260327,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.482,false,true,286256,Photographs,Photograph,Tired Butterfly,,,,,,Artist,,Rudolph Eickemeyer,"American, 1862–1932",,"Eickemeyer, Jr., Rudolph",American,1862,1932,1902,1902,1902,Carbon print,Image: 19.1 x 24.1 cm (7 1/2 x 9 1/2 in.) Sheet: 22.4 x 25.7 cm (8 13/16 x 10 1/8 in.),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286256,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.150,false,true,669361,Photographs,Photograph; Photomicrograph,[Snow Crystal],,,,,,Artist,,Wilson Alwyn Bentley,"American, 1865–1931",,"Bentley, Wilson Alwyn",American,1865,1931,1890s–1920s,1890,1929,Gelatin silver print,Image: 2 15/16 × 3 1/8 in. (7.5 × 8 cm) Sheet: 3 1/16 × 3 15/16 in. (7.7 × 10 cm),"Purchase, Alfred Stieglitz Society Gifts, 2015",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/669361,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.151,false,true,670594,Photographs,Photograph; Photomicrograph,[Snow Crystal],,,,,,Artist,,Wilson Alwyn Bentley,"American, 1865–1931",,"Bentley, Wilson Alwyn",American,1865,1931,1890s–1920s,1890,1929,Gelatin silver print,Image: 2 15/16 × 3 1/8 in. (7.5 × 8 cm) Sheet: 2 15/16 in. × 4 in. (7.5 × 10.1 cm),"Purchase, Alfred Stieglitz Society Gifts, 2015",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/670594,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.152,false,true,670606,Photographs,Photograph; Photomicrograph,[Snow Crystal],,,,,,Artist,,Wilson Alwyn Bentley,"American, 1865–1931",,"Bentley, Wilson Alwyn",American,1865,1931,1890s–1920s,1890,1929,Gelatin silver print,Image: 3 in. × 3 3/16 in. (7.6 × 8.1 cm) Sheet: 3 × 4 in. (7.6 × 10.1 cm),"Purchase, Alfred Stieglitz Society Gifts, 2015",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/670606,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.153,false,true,670607,Photographs,Photograph; Photomicrograph,[Snow Crystal],,,,,,Artist,,Wilson Alwyn Bentley,"American, 1865–1931",,"Bentley, Wilson Alwyn",American,1865,1931,1890s–1920s,1890,1929,Gelatin silver print,Image: 3 in. × 3 1/4 in. (7.6 × 8.3 cm) Sheet: 3 × 4 in. (7.6 × 10.1 cm),"Purchase, Alfred Stieglitz Society Gifts, 2015",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/670607,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.154,false,true,670608,Photographs,Photograph; Photomicrograph,[Snow Crystal],,,,,,Artist,,Wilson Alwyn Bentley,"American, 1865–1931",,"Bentley, Wilson Alwyn",American,1865,1931,1890s–1920s,1890,1929,Gelatin silver print,Image: 2 15/16 × 3 1/8 in. (7.4 × 8 cm) Sheet: 3 × 4 in. (7.6 × 10.1 cm),"Purchase, Alfred Stieglitz Society Gifts, 2015",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/670608,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.155,false,true,670609,Photographs,Photograph; Photomicrograph,[Snow Crystal],,,,,,Artist,,Wilson Alwyn Bentley,"American, 1865–1931",,"Bentley, Wilson Alwyn",American,1865,1931,1890s–1920s,1890,1929,Gelatin silver print,Image: 3 in. × 3 1/8 in. (7.6 × 7.9 cm) Sheet: 3 × 4 in. (7.6 × 10.1 cm),"Purchase, Alfred Stieglitz Society Gifts, 2015",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/670609,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.156,false,true,670610,Photographs,Photograph; Photomicrograph,[Snow Crystal],,,,,,Artist,,Wilson Alwyn Bentley,"American, 1865–1931",,"Bentley, Wilson Alwyn",American,1865,1931,1890s–1920s,1890,1929,Gelatin silver print,Image: 2 7/8 × 2 15/16 in. (7.3 × 7.4 cm) Sheet: 3 × 4 in. (7.6 × 10.1 cm),"Purchase, Alfred Stieglitz Society Gifts, 2015",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/670610,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.157,false,true,670611,Photographs,Photograph; Photomicrograph,[Snow Crystal],,,,,,Artist,,Wilson Alwyn Bentley,"American, 1865–1931",,"Bentley, Wilson Alwyn",American,1865,1931,1890s–1920s,1890,1929,Gelatin silver print,Image: 2 15/16 in. × 3 in. (7.5 × 7.6 cm) Sheet: 3 1/4 in. × 4 in. (8.3 × 10.1 cm),"Purchase, Alfred Stieglitz Society Gifts, 2015",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/670611,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.158,false,true,670612,Photographs,Photograph; Photomicrograph,[Dew on a Blade of Grass],,,,,,Artist,,Wilson Alwyn Bentley,"American, 1865–1931",,"Bentley, Wilson Alwyn",American,1865,1931,1890s–1920s,1890,1929,Gelatin silver print,Image: 4 in. × 2 15/16 in. (10.1 × 7.5 cm),"Purchase, Alfred Stieglitz Society Gifts, 2015",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/670612,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.159,false,true,670613,Photographs,Photograph; Photomicrograph,[Snow Crystal],,,,,,Artist,,Wilson Alwyn Bentley,"American, 1865–1931",,"Bentley, Wilson Alwyn",American,1865,1931,1890s–1920s,1890,1929,Gelatin silver print,Image: 2 15/16 × 3 7/16 in. (7.5 × 8.8 cm) Sheet: 2 15/16 in. × 4 in. (7.5 × 10.2 cm),"Purchase, Alfred Stieglitz Society Gifts, 2015",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/670613,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.160,false,true,670614,Photographs,Photograph; Photomicrograph,[Snow Crystal],,,,,,Artist,,Wilson Alwyn Bentley,"American, 1865–1931",,"Bentley, Wilson Alwyn",American,1865,1931,1890s–1920s,1890,1929,Gelatin silver print,Image: 2 15/16 × 3 1/4 in. (7.4 × 8.2 cm) Sheet: 3 × 4 in. (7.6 × 10.1 cm),"Purchase, Alfred Stieglitz Society Gifts, 2015",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/670614,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.161,false,true,670615,Photographs,Photograph; Photomicrograph,[Snow Crystal],,,,,,Artist,,Wilson Alwyn Bentley,"American, 1865–1931",,"Bentley, Wilson Alwyn",American,1865,1931,1890s–1920s,1890,1929,Gelatin silver print,Image: 2 15/16 × 3 7/16 in. (7.4 × 8.8 cm) Sheet: 2 15/16 in. × 4 in. (7.4 × 10.1 cm),"Purchase, Alfred Stieglitz Society Gifts, 2015",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/670615,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.162,false,true,670616,Photographs,Photograph; Photomicrograph,[Snow Crystal],,,,,,Artist,,Wilson Alwyn Bentley,"American, 1865–1931",,"Bentley, Wilson Alwyn",American,1865,1931,1890s–1920s,1890,1929,Gelatin silver print,Image: 2 7/8 in. × 3 in. (7.3 × 7.6 cm) Sheet: 2 15/16 in. × 4 in. (7.5 × 10.1 cm),"Purchase, Alfred Stieglitz Society Gifts, 2015",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/670616,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.163,false,true,670617,Photographs,Photograph; Photomicrograph,[Snow Crystal],,,,,,Artist,,Wilson Alwyn Bentley,"American, 1865–1931",,"Bentley, Wilson Alwyn",American,1865,1931,1890s–1920s,1890,1929,Gelatin silver print,,"Purchase, Alfred Stieglitz Society Gifts, 2015",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/670617,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.164,false,true,670618,Photographs,Photograph; Photomicrograph,[Snow Crystal],,,,,,Artist,,Wilson Alwyn Bentley,"American, 1865–1931",,"Bentley, Wilson Alwyn",American,1865,1931,1890s–1920s,1890,1929,Gelatin silver print,Image: 2 3/4 × 2 7/8 in. (7 × 7.3 cm) Sheet: 3 × 4 in. (7.6 × 10.2 cm),"Purchase, Alfred Stieglitz Society Gifts, 2015",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/670618,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.165,false,true,670619,Photographs,Photograph; Photomicrograph,[Snow Crystal],,,,,,Artist,,Wilson Alwyn Bentley,"American, 1865–1931",,"Bentley, Wilson Alwyn",American,1865,1931,1890s–1920s,1890,1929,Gelatin silver print,Image: 2 15/16 × 3 1/4 in. (7.5 × 8.2 cm) Sheet: 2 15/16 × 4 1/16 in. (7.5 × 10.3 cm),"Purchase, Alfred Stieglitz Society Gifts, 2015",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/670619,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.166,false,true,670620,Photographs,Photograph; Photomicrograph,[Snow Crystal],,,,,,Artist,,Wilson Alwyn Bentley,"American, 1865–1931",,"Bentley, Wilson Alwyn",American,1865,1931,1890s–1920s,1890,1929,Gelatin silver print,Image: 3 in. × 3 3/16 in. (7.6 × 8.1 cm) Sheet: 3 × 4 in. (7.6 × 10.2 cm),"Purchase, Alfred Stieglitz Society Gifts, 2015",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/670620,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.167,false,true,670621,Photographs,Photograph; Photomicrograph,[Snow Crystal],,,,,,Artist,,Wilson Alwyn Bentley,"American, 1865–1931",,"Bentley, Wilson Alwyn",American,1865,1931,1890s–1920s,1890,1929,Gelatin silver print,Image: 3 in. × 3 5/16 in. (7.6 × 8.4 cm) Sheet: 3 × 4 in. (7.6 × 10.1 cm),"Purchase, Alfred Stieglitz Society Gifts, 2015",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/670621,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.168,false,true,670622,Photographs,Photograph; Photomicrograph,[Snow Crystal],,,,,,Artist,,Wilson Alwyn Bentley,"American, 1865–1931",,"Bentley, Wilson Alwyn",American,1865,1931,1890s–1920s,1890,1929,Gelatin silver print,Image: 2 15/16 × 3 5/16 in. (7.4 × 8.4 cm) Sheet: 3 × 4 in. (7.6 × 10.2 cm),"Purchase, Alfred Stieglitz Society Gifts, 2015",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/670622,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.169,false,true,670623,Photographs,Photograph; Photomicrograph,[Snow Crystal],,,,,,Artist,,Wilson Alwyn Bentley,"American, 1865–1931",,"Bentley, Wilson Alwyn",American,1865,1931,1890s–1920s,1890,1929,Gelatin silver print,Image: 2 15/16 in. × 3 in. (7.5 × 7.6 cm) Sheet: 2 15/16 × 3 7/8 in. (7.5 × 9.9 cm),"Purchase, Alfred Stieglitz Society Gifts, 2015",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/670623,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.170,false,true,670624,Photographs,Photograph; Photomicrograph,[Snow Crystal],,,,,,Artist,,Wilson Alwyn Bentley,"American, 1865–1931",,"Bentley, Wilson Alwyn",American,1865,1931,1890s–1920s,1890,1929,Gelatin silver print,Image: 2 15/16 × 3 1/4 in. (7.5 × 8.3 cm) Sheet: 3 × 4 in. (7.6 × 10.1 cm),"Purchase, Alfred Stieglitz Society Gifts, 2015",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/670624,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.171,false,true,670625,Photographs,Photograph; Photomicrograph,[Snow Crystal],,,,,,Artist,,Wilson Alwyn Bentley,"American, 1865–1931",,"Bentley, Wilson Alwyn",American,1865,1931,1890s–1920s,1890,1929,Gelatin silver print,Image: 3 in. × 3 1/16 in. (7.6 × 7.7 cm) Sheet: 3 × 4 in. (7.6 × 10.1 cm),"Purchase, Alfred Stieglitz Society Gifts, 2015",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/670625,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.172,false,true,670626,Photographs,Photograph; Photomicrograph,[Snow Crystal],,,,,,Artist,,Wilson Alwyn Bentley,"American, 1865–1931",,"Bentley, Wilson Alwyn",American,1865,1931,1890s–1920s,1890,1929,Gelatin silver print,Image: 2 15/16 × 2 3/4 in. (7.4 × 7 cm) Sheet: 2 15/16 × 3 15/16 in. (7.5 × 10 cm),"Purchase, Alfred Stieglitz Society Gifts, 2015",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/670626,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.173,false,true,670627,Photographs,Photograph; Photomicrograph,[Snow Crystal],,,,,,Artist,,Wilson Alwyn Bentley,"American, 1865–1931",,"Bentley, Wilson Alwyn",American,1865,1931,1890s–1920s,1890,1929,Gelatin silver print,Image: 2 15/16 in. × 3 in. (7.4 × 7.6 cm) Sheet: 3 1/16 in. × 4 in. (7.7 × 10.2 cm),"Purchase, Alfred Stieglitz Society Gifts, 2015",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/670627,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.174,false,true,670628,Photographs,Photograph; Photomicrograph,[Snow Crystal],,,,,,Artist,,Wilson Alwyn Bentley,"American, 1865–1931",,"Bentley, Wilson Alwyn",American,1865,1931,1890s–1920s,1890,1929,Gelatin silver print,Image: 2 15/16 × 3 1/2 in. (7.4 × 8.9 cm) Sheet: 3 × 4 in. (7.6 × 10.1 cm),"Purchase, Alfred Stieglitz Society Gifts, 2015",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/670628,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.175,false,true,670629,Photographs,Photograph; Photomicrograph,[Snow Crystal],,,,,,Artist,,Wilson Alwyn Bentley,"American, 1865–1931",,"Bentley, Wilson Alwyn",American,1865,1931,1890s–1920s,1890,1929,Gelatin silver print,Image: 3 in. × 3 3/16 in. (7.6 × 8.1 cm) Sheet: 3 × 4 in. (7.6 × 10.1 cm),"Purchase, Alfred Stieglitz Society Gifts, 2015",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/670629,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.176,false,true,670630,Photographs,Photograph; Photomicrograph,[Snow Crystal],,,,,,Artist,,Wilson Alwyn Bentley,"American, 1865–1931",,"Bentley, Wilson Alwyn",American,1865,1931,1890s–1920s,1890,1929,Gelatin silver print,Image: 2 15/16 in. × 3 in. (7.4 × 7.6 cm) Sheet: 3 × 4 in. (7.6 × 10.1 cm),"Purchase, Alfred Stieglitz Society Gifts, 2015",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/670630,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.177,false,true,670631,Photographs,Photograph; Photomicrograph,[Snow Crystal],,,,,,Artist,,Wilson Alwyn Bentley,"American, 1865–1931",,"Bentley, Wilson Alwyn",American,1865,1931,1890s–1920s,1890,1929,Gelatin silver print,Image: 2 13/16 × 2 15/16 in. (7.2 × 7.5 cm) Sheet: 2 7/8 × 3 5/8 in. (7.3 × 9.2 cm),"Purchase, Alfred Stieglitz Society Gifts, 2015",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/670631,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.178,false,true,670632,Photographs,Photograph; Photomicrograph,[Snow Crystal],,,,,,Artist,,Wilson Alwyn Bentley,"American, 1865–1931",,"Bentley, Wilson Alwyn",American,1865,1931,1890s–1920s,1890,1929,Gelatin silver print,Image: 3 × 3 in. (7.6 × 7.6 cm) Sheet: 3 1/4 in. × 4 in. (8.2 × 10.1 cm),"Purchase, Alfred Stieglitz Society Gifts, 2015",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/670632,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.179,false,true,670633,Photographs,Photograph; Photomicrograph,[Snow Crystal],,,,,,Artist,,Wilson Alwyn Bentley,"American, 1865–1931",,"Bentley, Wilson Alwyn",American,1865,1931,1890s–1920s,1890,1929,Gelatin silver print,Image: 2 7/8 in. × 3 in. (7.3 × 7.6 cm) Sheet: 3 × 4 in. (7.6 × 10.1 cm),"Purchase, Alfred Stieglitz Society Gifts, 2015",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/670633,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.180,false,true,670634,Photographs,Photograph; Photomicrograph,[Snow Crystal],,,,,,Artist,,Wilson Alwyn Bentley,"American, 1865–1931",,"Bentley, Wilson Alwyn",American,1865,1931,1890s–1920s,1890,1929,Gelatin silver print,Image: 2 7/8 × 2 15/16 in. (7.3 × 7.5 cm) Sheet: 2 15/16 in. × 4 in. (7.5 × 10.2 cm),"Purchase, Alfred Stieglitz Society Gifts, 2015",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/670634,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.181,false,true,670635,Photographs,Photograph; Photomicrograph,[Snow Crystal],,,,,,Artist,,Wilson Alwyn Bentley,"American, 1865–1931",,"Bentley, Wilson Alwyn",American,1865,1931,1890s–1920s,1890,1929,Gelatin silver print,Image: 2 15/16 × 3 1/4 in. (7.5 × 8.3 cm) Sheet: 3 × 4 in. (7.6 × 10.1 cm),"Purchase, Alfred Stieglitz Society Gifts, 2015",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/670635,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.182,false,true,670636,Photographs,Photograph; Photomicrograph,[Snow Crystal],,,,,,Artist,,Wilson Alwyn Bentley,"American, 1865–1931",,"Bentley, Wilson Alwyn",American,1865,1931,1890s–1920s,1890,1929,Gelatin silver print,Image: 2 15/16 × 2 7/8 in. (7.4 × 7.3 cm) Sheet: 2 15/16 in. × 4 in. (7.5 × 10.2 cm),"Purchase, Alfred Stieglitz Society Gifts, 2015",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/670636,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.183,false,true,670637,Photographs,Photograph; Photomicrograph,[Snow Crystal],,,,,,Artist,,Wilson Alwyn Bentley,"American, 1865–1931",,"Bentley, Wilson Alwyn",American,1865,1931,1890s–1920s,1890,1929,Gelatin silver print,,"Purchase, Alfred Stieglitz Society Gifts, 2015",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/670637,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.184,false,true,670638,Photographs,Photograph; Photomicrograph,[Snow Crystal],,,,,,Artist,,Wilson Alwyn Bentley,"American, 1865–1931",,"Bentley, Wilson Alwyn",American,1865,1931,1890s–1920s,1890,1929,Gelatin silver print,Image: 2 15/16 × 3 1/8 in. (7.4 × 7.9 cm) Sheet: 3 × 4 in. (7.6 × 10.2 cm),"Purchase, Alfred Stieglitz Society Gifts, 2015",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/670638,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.185,false,true,670639,Photographs,Photograph; Photomicrograph,[Snow Crystal],,,,,,Artist,,Wilson Alwyn Bentley,"American, 1865–1931",,"Bentley, Wilson Alwyn",American,1865,1931,1890s–1920s,1890,1929,Gelatin silver print,Image: 2 15/16 × 3 1/4 in. (7.4 × 8.2 cm) Sheet: 3 × 4 in. (7.6 × 10.1 cm),"Purchase, Alfred Stieglitz Society Gifts, 2015",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/670639,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.186,false,true,670640,Photographs,Photograph; Photomicrograph,[Snow Crystal],,,,,,Artist,,Wilson Alwyn Bentley,"American, 1865–1931",,"Bentley, Wilson Alwyn",American,1865,1931,1890s–1920s,1890,1929,Gelatin silver print,Image: 2 13/16 in. × 3 in. (7.1 × 7.6 cm) Sheet: 2 13/16 in. × 4 in. (7.2 × 10.1 cm),"Purchase, Alfred Stieglitz Society Gifts, 2015",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/670640,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.187,false,true,670641,Photographs,Photograph; Photomicrograph,[Snow Crystal],,,,,,Artist,,Wilson Alwyn Bentley,"American, 1865–1931",,"Bentley, Wilson Alwyn",American,1865,1931,1890s–1920s,1890,1929,Gelatin silver print,Image: 2 15/16 in. × 3 in. (7.5 × 7.6 cm) Sheet: 3 1/8 in. × 4 in. (7.9 × 10.1 cm),"Purchase, Alfred Stieglitz Society Gifts, 2015",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/670641,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.188,false,true,670642,Photographs,Photograph; Photomicrograph,[Snow Crystal],,,,,,Artist,,Wilson Alwyn Bentley,"American, 1865–1931",,"Bentley, Wilson Alwyn",American,1865,1931,1890s–1920s,1890,1929,Gelatin silver print,Image: 2 13/16 × 2 7/8 in. (7.2 × 7.3 cm) Sheet: 3 1/16 in. × 4 in. (7.7 × 10.2 cm),"Purchase, Alfred Stieglitz Society Gifts, 2015",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/670642,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.189,false,true,670643,Photographs,Photograph; Photomicrograph,[Snow Crystal],,,,,,Artist,,Wilson Alwyn Bentley,"American, 1865–1931",,"Bentley, Wilson Alwyn",American,1865,1931,1890s–1920s,1890,1929,Gelatin silver print,Image: 2 13/16 × 2 15/16 in. (7.1 × 7.4 cm) Sheet: 2 15/16 in. × 4 in. (7.5 × 10.1 cm),"Purchase, Alfred Stieglitz Society Gifts, 2015",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/670643,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.190,false,true,670644,Photographs,Photograph; Photomicrograph,[Snow Crystal],,,,,,Artist,,Wilson Alwyn Bentley,"American, 1865–1931",,"Bentley, Wilson Alwyn",American,1865,1931,1890s–1920s,1890,1929,Gelatin silver print,Image: 2 15/16 × 3 1/8 in. (7.5 × 7.9 cm) Sheet: 3 in. × 4 1/16 in. (7.6 × 10.3 cm),"Purchase, Alfred Stieglitz Society Gifts, 2015",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/670644,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.191,false,true,670645,Photographs,Photograph; Photomicrograph,[Snow Crystal],,,,,,Artist,,Wilson Alwyn Bentley,"American, 1865–1931",,"Bentley, Wilson Alwyn",American,1865,1931,1890s–1920s,1890,1929,Gelatin silver print,Image: 2 3/4 in. × 3 in. (7 × 7.6 cm) Sheet: 2 13/16 in. × 4 in. (7.2 × 10.1 cm),"Purchase, Alfred Stieglitz Society Gifts, 2015",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/670645,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.192,false,true,670646,Photographs,Photograph; Photomicrograph,[Snow Crystal],,,,,,Artist,,Wilson Alwyn Bentley,"American, 1865–1931",,"Bentley, Wilson Alwyn",American,1865,1931,1890s–1920s,1890,1929,Gelatin silver print,Image: 2 13/16 × 2 7/8 in. (7.1 × 7.3 cm) Sheet: 2 15/16 in. × 4 in. (7.5 × 10.1 cm),"Purchase, Alfred Stieglitz Society Gifts, 2015",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/670646,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.193,false,true,670647,Photographs,Photograph; Photomicrograph,[Snow Crystal],,,,,,Artist,,Wilson Alwyn Bentley,"American, 1865–1931",,"Bentley, Wilson Alwyn",American,1865,1931,1890s–1920s,1890,1929,Gelatin silver print,Image: 3 in. × 3 1/4 in. (7.6 × 8.3 cm) Sheet: 3 × 4 in. (7.6 × 10.1 cm),"Purchase, Alfred Stieglitz Society Gifts, 2015",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/670647,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.194,false,true,670648,Photographs,Photograph; Photomicrograph,[Snow Crystal],,,,,,Artist,,Wilson Alwyn Bentley,"American, 1865–1931",,"Bentley, Wilson Alwyn",American,1865,1931,1890s–1920s,1890,1929,Gelatin silver print,Image: 2 15/16 × 3 3/16 in. (7.5 × 8.1 cm) Sheet: 3 × 4 in. (7.6 × 10.1 cm),"Purchase, Alfred Stieglitz Society Gifts, 2015",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/670648,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.195,false,true,670649,Photographs,Photograph; Photomicrograph,[Snow Crystal],,,,,,Artist,,Wilson Alwyn Bentley,"American, 1865–1931",,"Bentley, Wilson Alwyn",American,1865,1931,1890s–1920s,1890,1929,Gelatin silver print,Image: 2 13/16 × 2 7/8 in. (7.1 × 7.3 cm) Sheet: 3 in. × 3 11/16 in. (7.6 × 9.4 cm),"Purchase, Alfred Stieglitz Society Gifts, 2015",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/670649,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.196,false,true,670650,Photographs,Photograph; Photomicrograph,[Snow Crystal],,,,,,Artist,,Wilson Alwyn Bentley,"American, 1865–1931",,"Bentley, Wilson Alwyn",American,1865,1931,1890s–1920s,1890,1929,Gelatin silver print,Image: 3 in. × 3 1/8 in. (7.6 × 8 cm) Sheet: 3 × 4 in. (7.6 × 10.1 cm),"Purchase, Alfred Stieglitz Society Gifts, 2015",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/670650,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.197,false,true,670651,Photographs,Photograph; Photomicrograph,[Snow Crystal],,,,,,Artist,,Wilson Alwyn Bentley,"American, 1865–1931",,"Bentley, Wilson Alwyn",American,1865,1931,1890s–1920s,1890,1929,Gelatin silver print,Image: 2 7/8 × 3 1/16 in. (7.3 × 7.8 cm) Sheet: 3 × 4 in. (7.6 × 10.1 cm),"Purchase, Alfred Stieglitz Society Gifts, 2015",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/670651,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.198,false,true,670652,Photographs,Photograph; Photomicrograph,[Snow Crystal],,,,,,Artist,,Wilson Alwyn Bentley,"American, 1865–1931",,"Bentley, Wilson Alwyn",American,1865,1931,1890s–1920s,1890,1929,Gelatin silver print,Image: 2 15/16 in. × 3 in. (7.4 × 7.6 cm) Sheet: 3 × 4 in. (7.6 × 10.1 cm),"Purchase, Alfred Stieglitz Society Gifts, 2015",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/670652,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.199,false,true,670653,Photographs,Photograph; Photomicrograph,[Snow Crystal],,,,,,Artist,,Wilson Alwyn Bentley,"American, 1865–1931",,"Bentley, Wilson Alwyn",American,1865,1931,1890s–1920s,1890,1929,Gelatin silver print,Image: 2 15/16 × 3 1/8 in. (7.4 × 8 cm) Sheet: 3 in. × 3 15/16 in. (7.6 × 10 cm),"Purchase, Alfred Stieglitz Society Gifts, 2015",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/670653,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.200,false,true,670654,Photographs,Photograph; Photomicrograph,[Snow Crystal],,,,,,Artist,,Wilson Alwyn Bentley,"American, 1865–1931",,"Bentley, Wilson Alwyn",American,1865,1931,1890s–1920s,1890,1929,Gelatin silver print,Image: 2 15/16 × 3 1/8 in. (7.4 × 7.9 cm) Sheet: 3 × 4 in. (7.6 × 10.1 cm),"Purchase, Alfred Stieglitz Society Gifts, 2015",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/670654,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.201,false,true,669362,Photographs,Photograph,[Frost],,,,,,Artist,,Wilson Alwyn Bentley,"American, 1865–1931",,"Bentley, Wilson Alwyn",American,1865,1931,ca. 1910,1905,1915,Gelatin silver print,Image: 3 in. × 3 11/16 in. (7.6 × 9.4 cm) Sheet: 3 1/16 × 3 15/16 in. (7.7 × 10 cm),"Purchase, Alfred Stieglitz Society Gifts, 2015",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/669362,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.202,false,true,669363,Photographs,Photograph,[Dew on a Spider Web],,,,,,Artist,,Wilson Alwyn Bentley,"American, 1865–1931",,"Bentley, Wilson Alwyn",American,1865,1931,ca. 1910,1905,1915,Gelatin silver print,Image: 3 in. × 3 5/8 in. (7.6 × 9.2 cm) Sheet: 3 in. × 4 7/16 in. (7.6 × 11.2 cm),"Purchase, Alfred Stieglitz Society Gifts, 2015",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/669363,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.203,false,true,669364,Photographs,Photograph,[Self-Portrait with Camera],,,,,,Artist,,Wilson Alwyn Bentley,"American, 1865–1931",,"Bentley, Wilson Alwyn",American,1865,1931,ca. 1910,1905,1915,Gelatin silver print,Image: 2 13/16 × 3 3/4 in. (7.1 × 9.5 cm) Sheet: 3 in. × 4 1/16 in. (7.6 × 10.3 cm),"Purchase, Alfred Stieglitz Society Gifts, 2015",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/669364,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.55.3,false,true,286768,Photographs,Photograph,[Snow Crystal],,,,,,Artist,,Wilson Alwyn Bentley,"American, 1865–1931",,"Bentley, Wilson Alwyn",American,1865,1931,ca. 1910,1905,1915,Gelatin silver print,Image: 7.4 x 9 cm (2 15/16 x 3 9/16 in.),"Josh Rosenthal Fund, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286768,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2010.188,false,true,292061,Photographs,Postcard,Carving One of Our Watermelons,,,,,,Artist,,William H. Martin,"American, 1865–1940",,"Martin, William H.",American,1865,1940,1909,1909,1909,Gelatin silver print,Image: 8.7 x 14 cm (3 7/16 x 5 1/2 in.) Frame: 55.9 x 71.1 cm (22 x 28 in.) (Multiple postcards in frame),"Twentieth-Century Photography Fund, 2010",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/292061,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.460.5,false,true,288710,Photographs,Postcard,A Load of Fancy Poultry,,,,,,Artist,,William H. Martin,"American, 1865–1940",,"Martin, William H.",American,1865,1940,1909,1909,1909,Gelatin silver print,Image: 8.4 x 14 cm (3 5/16 x 5 1/2 in.) Frame: 55.9 x 71.1 cm (22 x 28 in.) (Multiple postcards in frame),"Gift of Charles Isaacs and Carol Nigro, 2007",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288710,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.460.6,false,true,288711,Photographs,Postcard,A Unique Bungalow,,,,,,Artist,,William H. Martin,"American, 1865–1940",,"Martin, William H.",American,1865,1940,1909,1909,1909,Gelatin silver print,Image: 8.4 x 14 cm (3 5/16 x 5 1/2 in.) Frame: 55.9 x 71.1 cm (22 x 28 in.) (Multiple postcards in frame),"Gift of Charles Isaacs and Carol Nigro, 2007",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288711,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.460.7,false,true,288712,Photographs,Postcard,Great Sport Shooting Rabbits in Iowa,,,,,,Artist,,William H. Martin,"American, 1865–1940",,"Martin, William H.",American,1865,1940,1909,1909,1909,Gelatin silver print,Image: 8.4 x 14 cm (3 5/16 x 5 1/2 in.) Frame: 55.9 x 71.1 cm (22 x 28 in.) (Multiple postcards in frame),"Gift of Charles Isaacs and Carol Nigro, 2007",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288712,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.460.8,false,true,288713,Photographs,Postcard,Harvesting Wheat in Iowa,,,,,,Artist,,William H. Martin,"American, 1865–1940",,"Martin, William H.",American,1865,1940,1909,1909,1909,Gelatin silver print,Image: 8.4 x 14 cm (3 5/16 x 5 1/2 in.) Frame: 55.9 x 71.1 cm (22 x 28 in.) (Multiple postcards in frame),"Gift of Charles Isaacs and Carol Nigro, 2007",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288713,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -61.608.20,false,true,270412,Photographs,Photograph,"Maxim Gorky and Zena Peschkoff, His Adopted Son",,,,,,Artist,,Alice Boughton,"American, 1865–1943",,"Boughton, Alice",American,1865,1943,ca. 1910,1908,1912,Platinum print,,"Gift of Miss Elma Loines, 1961",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/270412,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1987.1100.432.1,false,true,265501,Photographs,Photograph,[Man Sitting at Edge of Swimming Pool with Surface Water Reflections],,,,,,Artist,,Louis Fleckenstein,"American, 1866–1943",,"Fleckenstein, Louis",American,1866,1943,1931,1931,1931,Gelatin silver print,25.2 x 20.1 cm (9 15/16 x 7 15/16 in.),"Ford Motor Company Collection, Gift of Ford Motor Company and John C. Waddell, 1987",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/265501,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1987.1100.432.2,false,true,265502,Photographs,Photograph,[The Boy Scouts Swimming Pool at Idyllwild],,,,,,Artist,,Louis Fleckenstein,"American, 1866–1943",,"Fleckenstein, Louis",American,1866,1943,1931,1931,1931,Gelatin silver print,7.8 x 5.4 cm (3 1/16 x 2 1/8 in.),"Ford Motor Company Collection, Gift of Ford Motor Company and John C. Waddell, 1987",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/265502,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1977.630.45,false,true,262075,Photographs,Photograph,[Road Through Flooded Land],,,,,,Artist,,Morgan Whitney,"American, 1869–1913",,"Whitney, Morgan",American,1869,1913,1890s–1900s,1890,1909,Platinum print,16.7 x 11.5 cm. (6 9/16 x 4 1/2 in.),"Gift of Mr. and Mrs. Morgan Whitney, 1977",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/262075,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.43.174,false,true,267576,Photographs,Photograph,A Sioux Chief,,,,,,Artist,,Joseph T. Keiley,"American, 1869–1914",,"Keiley, Joseph T.",American,1869,1914,ca. 1898,1896,1900,Platinum print,19.3 x 14.1 cm. (7 5/8 x 5 9/16 in.),"Alfred Stieglitz Collection, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267576,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.43.179,false,true,267581,Photographs,Photograph,[The Averted Head - A Study in Flesh Tones],,,,,,Artist,,Joseph T. Keiley,"American, 1869–1914",,"Keiley, Joseph T.",American,1869,1914,1899,1899,1899,Platinum print,16.5 x 10.5 cm. (6 1/2 x 4 1/8 in.),"Alfred Stieglitz Collection, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267581,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.43.185,false,true,267588,Photographs,Photograph,A Bacchante,,,,,,Artist,,Joseph T. Keiley,"American, 1869–1914",,"Keiley, Joseph T.",American,1869,1914,1899,1899,1899,Platinum-palladium print,24.5 x 19.3 cm. (9 5/8 x 7 5/8 in.),"Alfred Stieglitz Collection, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267588,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.43.187,false,true,267590,Photographs,Photograph,Indian Head,,,,,,Artist,,Joseph T. Keiley,"American, 1869–1914",,"Keiley, Joseph T.",American,1869,1914,1898,1898,1898,Platinum print,19.8 x 14.5 cm. (7 13/16 x 5 11/16 in.),"Alfred Stieglitz Collection, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267590,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.43.417,false,true,267834,Photographs,Photograph,A Sioux Chief,,,,,,Artist,,Joseph T. Keiley,"American, 1869–1914",,"Keiley, Joseph T.",American,1869,1914,1898,1898,1898,Platinum print,19.5 x 13.0 cm. (7 11/16 x 5 1/8 in.),"Alfred Stieglitz Collection, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267834,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.291,false,true,285686,Photographs,Photograph,In the Circus,,,,,,Artist,,Harry Cogswell Rubincam,"American, 1871–1940",,"Rubincam, Harry Cogswell",American,1871,1940,1905,1905,1905,Platinum print,"Image: 21.2 x 15.7 cm (8 3/8 x 6 3/16 in.), irregular Sheet: 21.6 x 16.4 cm (8 1/2 x 6 7/16 in.) Mount: 37.8 x 28.8 cm (14 7/8 x 11 5/16 in.)","Gilman Collection, Purchase, Ann Tenenbaum and Thomas H. Lee Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285686,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.126,false,true,283258,Photographs,Photograph,"[Lynching, Russellville, Kentucky]",,,,,,Artist,,Minor B. Wade,"American, 1874–1932",,"Wade, Minor B.",American,1874,1932,1908,1908,1908,Gelatin silver print,Image: 11.8 x 9.1 cm (4 5/8 x 3 9/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/283258,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1998.91,false,true,282210,Photographs,Photograph,Midnight at the Bowery Mission Bread Line,,,,,,Artist,,Lewis Hine,"American, 1874–1940",,"Hine, Lewis",American,1874,1940,1906–7,1906,1907,Gelatin silver print,11.5 x 14.9 cm (4 1/2 x 5 7/8 in. ),"Gift of John C. Waddell, 1998",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/282210,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.338,false,true,286820,Photographs,Photograph,"Jo Lehman, a 7 year old newsboy. 824 Third Ave., N.Y. City. He was selling in this Saloon. I asked him about the badge he was wearing. ""Oh! Dat's me bruder's,"" he said. Location: New York, New York.",,,,,,Artist,,Lewis Hine,"American, 1874–1940",,"Hine, Lewis",American,1874,1940,July 1910,1910,1910,Gelatin silver print,Image: 11.6 x 9.5 cm (4 9/16 x 3 3/4 in.),"Gilman Collection, Purchase, The Howard Gilman Foundation Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286820,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2014.438,false,true,650860,Photographs,Photograph,"The Morning Attendance at the Mill School, Huntsville, Alabama",,,,,,Artist,,Lewis Hine,"American, 1874–1940",,"Hine, Lewis",American,1874,1940,December 1913,1913,1913,Gelatin silver print,Image: 4 7/16 × 6 7/16 in. (11.3 × 16.4 cm) Sheet: 4 15/16 × 6 15/16 in. (12.5 × 17.6 cm),"Purchase, The Overbrook Foundation Gift and funds from various donors, 2014",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/650860,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -54.549.56,false,true,269725,Photographs,Photograph,Steamfitter,,,,,,Artist,,Lewis Hine,"American, 1874–1940",,"Hine, Lewis",American,1874,1940,1910s,1910,1919,Gelatin silver print,Image: 34.5 × 24.7 cm (13 9/16 × 9 3/4 in.) Sheet: 35.4 × 27.9 cm (13 15/16 × 11 in.),"Gift of Clarence McK. Lewis, 1954",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269725,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.588.1,false,true,259722,Photographs,Photograph,"Boy carrying hats. Blee[c]ker St., N.Y.",,,,,,Artist,,Lewis Hine,"American, 1874–1940",,"Hine, Lewis",American,1874,1940,February 1912,1912,1912,Gelatin silver print,11.5 x 14.7 cm (4 1/2 x 5 13/16 in.),"Bequest of Edwin De T. Bechtel, by exchange, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/259722,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.588.6,false,true,259727,Photographs,Photograph,"Ivey Mill, Hickory, N.C. Little one, 3 years old, who visits and plays in the mill. Daughter of the overseer.",,,,,,Artist,,Lewis Hine,"American, 1874–1940",,"Hine, Lewis",American,1874,1940,November 1898,1908,1908,Gelatin silver print,Image: 11.7 x 16.9 cm (4 5/8 x 6 5/8 in.),"Bequest of Edwin De T. Bechtel, by exchange, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/259727,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.727.1,true,true,259797,Photographs,Photograph,"11:00 A.M. Monday, May 9th, 1910. Newsies at Skeeter's Branch, Jefferson near Franklin. They were all smoking. Location: St. Louis, Missouri.",,,,,,Artist,,Lewis Hine,"American, 1874–1940",,"Hine, Lewis",American,1874,1940,"May 9, 1910",1910,1910,Gelatin silver print,9.1 x 11.9 cm (3 9/16 x 4 11/16 in.),"Gift of Phyllis D. Massar, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/259797,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2011.553.7,false,true,301919,Photographs,Photograph,"Addie Card, 12 years. Spinner in North Pownal Cotton Mill. Girls in mill say she is ten years. She admitted to me she was twelve; that she started during school vacation and now would ""stay"". Location: Vermont",,,,,,Artist,,Lewis Hine,"American, 1874–1940",,"Hine, Lewis",American,1874,1940,1910,1910,1910,Gelatin silver print,Image: 17.7 x 12.7 cm (6 15/16 x 5 in.) Sheet: 16.8 x 11.9 cm (6 5/8 x 4 11/16 in.),"Gift of Joyce F. Menschel, 2011",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/301919,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2011.553.8,false,true,301920,Photographs,Photograph,"Mill Children #440, South Carolina",,,,,,Artist,,Lewis Hine,"American, 1874–1940",,"Hine, Lewis",American,1874,1940,1908,1908,1908,Gelatin silver print,Image: 11.9 x 16.9 cm (4 11/16 x 6 5/8 in.),"Gift of Joyce F. Menschel, 2011",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/301920,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1993.43.289,false,true,266949,Photographs,Photograph,Three National Child Labor Committee Exhibition Panels,,,,,,Artist,,Lewis Hine,"American, 1874–1940",,"Hine, Lewis",American,1874,1940,1913–14,1913,1914,Gelatin silver print,8.9 x 15.0 cm (3 1/2 x 5 15/16 in.),"Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 1993",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/266949,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.128,false,true,283260,Photographs,Photograph,"Newsboy asleep on stairs with papers, Jersey City, New Jersey",,,,,,Artist,,Lewis Hine,"American, 1874–1940",,"Hine, Lewis",American,1874,1940,February 1912,1912,1912,Gelatin silver print,Image: 11.5 x 16.8 cm (4 1/2 x 6 5/8 in.),"Gilman Collection, Purchase, Ann Tenenbaum and Thomas H. Lee Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/283260,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.730,false,true,285844,Photographs,Photograph,"Addie Card, 12 years. Spinner in North Pownal Cotton Mill. Girls in mill say she is ten years. She admitted to me she was twelve; that she started during school vacation and now would ""stay"". Location: Vermont",,,,,,Artist,,Lewis Hine,"American, 1874–1940",,"Hine, Lewis",American,1874,1940,August 1910,1910,1910,Gelatin silver print,Image: 24.4 x 19.3 cm (9 5/8 x 7 5/8 in.) Sheet: 25.4 x 20.4 cm (10 x 8 1/16 in.),"Gilman Collection, Purchase, Anonymous Gifts, by exchange, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285844,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1987.1100.119,false,true,265154,Photographs,Photograph,"Icarus, Empire State Building",,,,,,Artist,,Lewis Hine,"American, 1874–1940",,"Hine, Lewis",American,1874,1940,1930,1930,1930,Gelatin silver print,18.7 x 23.7 cm (7 3/8 x 9 5/16 in.),"Ford Motor Company Collection, Gift of Ford Motor Company and John C. Waddell, 1987",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/265154,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1987.1100.146,false,true,265184,Photographs,Photograph,Steamfitter,,,,,,Artist,,Lewis Hine,"American, 1874–1940",,"Hine, Lewis",American,1874,1940,1921,1921,1921,Gelatin silver print,42.1 x 30.9 cm (16 9/16 x 12 3/16 in.),"Ford Motor Company Collection, Gift of Ford Motor Company and John C. Waddell, 1987",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/265184,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1987.1100.325,false,true,265382,Photographs,Photograph,Empire State Building,,,,,,Artist,,Lewis Hine,"American, 1874–1940",,"Hine, Lewis",American,1874,1940,1930s,1930,1939,Gelatin silver print,10.0 x 12.2 cm (3 15/16 x 4 13/16 in.),"Ford Motor Company Collection, Gift of Ford Motor Company and John C. Waddell, 1987",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/265382,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1987.1100.486,false,true,265559,Photographs,Photograph,"Icarus, Empire State Building",,,,,,Artist,,Lewis Hine,"American, 1874–1940",,"Hine, Lewis",American,1874,1940,1930,1930,1930,Gelatin silver print,9.1 x 11.5 cm (3 9/16 x 4 1/2 in.),"Ford Motor Company Collection, Gift of Ford Motor Company and John C. Waddell, 1987",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/265559,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.660.11,false,true,259735,Photographs,Photograph,"Financial District, From the Hotel Bossert",,,,,,Artist,,Samuel H. Gottscho,"American, 1875–1971",,"Gottscho, Samuel H.",American,1875,1971,"1933, printed later",1933,1933,Gelatin silver print,16.7 x 24 cm (6 9/16 x 9 7/16 in. ),"Purchase, Florance Waterbury Bequest, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/259735,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1987.1100.96,false,true,265626,Photographs,Photograph,"[Locomotive, with Entrance to Perisphere of 1939 New York World's Fair in Background]",,,,,,Artist,,Samuel H. Gottscho,"American, 1875–1971",,"Gottscho, Samuel H.",American,1875,1971,ca. 1939,1937,1941,Gelatin silver print,33.0 x 25.4 cm (13 x 10 in.),"Ford Motor Company Collection, Gift of Ford Motor Company and John C. Waddell, 1987",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/265626,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1987.1100.402,false,true,265468,Photographs,Photograph,"[Fountains, 1939 New York World's Fair, with Trylon and Perisphere in Background]",,,,,,Artist,,Samuel H. Gottscho,"American, 1875–1971",,"Gottscho, Samuel H.",American,1875,1971,ca. 1939,1937,1941,Gelatin silver print,31.7 x 25.4 cm (12 1/2 x 10 in.),"Ford Motor Company Collection, Gift of Ford Motor Company and John C. Waddell, 1987",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/265468,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1987.1100.403,false,true,265469,Photographs,Photograph,"[1939 New York World's Fair, Entrance to Perisphere]",,,,,,Artist,,Samuel H. Gottscho,"American, 1875–1971",,"Gottscho, Samuel H.",American,1875,1971,ca. 1939,1937,1941,Gelatin silver print,31.4 x 25.5 cm. (12 3/8 x 10 1/16 in.),"Ford Motor Company Collection, Gift of Ford Motor Company and John C. Waddell, 1987",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/265469,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1987.1100.404,false,true,265470,Photographs,Photograph,"Trylon and Perisphere, New York World's Fair",,,,,,Artist,,Samuel H. Gottscho,"American, 1875–1971",,"Gottscho, Samuel H.",American,1875,1971,ca. 1939,1937,1941,Gelatin silver print,33.0 x 21.6 cm (13 x 8 1/2 in.),"Ford Motor Company Collection, Gift of Ford Motor Company and John C. Waddell, 1987",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/265470,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1987.1100.243,false,true,265292,Photographs,Photograph,[Old Man with Boy],,,,,,Artist,,Doris Ulmann,"American, 1882–1934",,"Ulmann, Doris",American,1882,1934,1920s–30s,1920,1939,Platinum print,20.4 x 15.4 cm (8 x 6 1/16 in.),"Ford Motor Company Collection, Gift of Ford Motor Company and John C. Waddell, 1987",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/265292,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1987.1100.274,false,true,265326,Photographs,Photograph,[Bell Ringer Outside a Church],,,,,,Artist,,Doris Ulmann,"American, 1882–1934",,"Ulmann, Doris",American,1882,1934,1920s–30s,1920,1939,Platinum print,20.1 x 15.1 cm (7 15/16 x 5 15/16 in.),"Ford Motor Company Collection, Gift of Ford Motor Company and John C. Waddell, 1987",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/265326,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1987.1100.276,false,true,265328,Photographs,Photograph,[Man with Bridle],,,,,,Artist,,Doris Ulmann,"American, 1882–1934",,"Ulmann, Doris",American,1882,1934,1920s–30s,1920,1939,Platinum print,20.4 x 15.1 cm (8 x 5 15/16 in.),"Ford Motor Company Collection, Gift of Ford Motor Company and John C. Waddell, 1987",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/265328,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1987.1100.284,false,true,265336,Photographs,Photograph,The Corn Crib,,,,,,Artist,,Doris Ulmann,"American, 1882–1934",,"Ulmann, Doris",American,1882,1934,1918,1918,1918,Bromoil print,20.5 x 15.9 cm (8 1/16 x 6 1/4 in.),"Ford Motor Company Collection, Gift of Ford Motor Company and John C. Waddell, 1987",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/265336,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1987.1100.271,false,true,265323,Photographs,Photograph,Grotesque Shadows,,,,,,Artist,,Roland E. Schneider,"American, 1884–1934",,"Schneider, Roland E.",American,1884,1934,1920s,1920,1929,Gelatin silver print,23.3 x 18.8 cm (9 3/16 x 7 3/8 in.),"Ford Motor Company Collection, Gift of Ford Motor Company and John C. Waddell, 1987",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/265323,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2001.390,false,true,284645,Photographs,Photograph,"Alabama Plow Girl, near Eutaw, Alabama",,,,,,Artist,,Dorothea Lange,"American, 1895–1965",,"Lange, Dorothea",American,1895,1965,1936,1936,1936,Gelatin silver print,19.1 x 19.4 cm (7 1/2 x 7 5/8 in. ),"Purchase, Alfred Stieglitz Society Gifts, 2001",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/284645,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2000.351.3,false,true,284150,Photographs,Photograph,"[Migrant Pea Picker's Makeshift Home, Nipomo, California]",,,,,,Artist,,Dorothea Lange,"American, 1895–1965",,"Lange, Dorothea",American,1895,1965,February 1936,1936,1936,Gelatin silver print,18.2 x 24.5 cm (7 3/16 x 9 5/8 in. ),"Purchase, Jennifer and Joseph Duke Gift, 2000",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/284150,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2001.296,false,true,284650,Photographs,Photograph,"[African American Mother and Child on Bed in their Cabin near Jefferson, Texas]",,,,,,Artist,,Russell Lee,"American, 1903–1986",,"Lee, Russell",American,1903,1986,1939,1939,1939,Gelatin silver print,18.2 x 24.3 cm (7 3/16 x 9 9/16 in. ),"Purchase, Alfred Stieglitz Society Gifts, 2001",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/284650,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2001.297,false,true,284653,Photographs,Photograph,"[Cotton Pickers with Knee Pads, Lehi, Arkansas]",,,,,,Artist,,Russell Lee,"American, 1903–1986",,"Lee, Russell",American,1903,1986,1938,1938,1938,Gelatin silver print,16.5 x 24.2 cm (6 1/2 x 9 1/2 in. ),"Purchase, Alfred Stieglitz Society Gifts, 2001",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/284653,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2001.298,false,true,284663,Photographs,Photograph,"[African-American Family at Gee's Bend, Alabama]",,,,,,Artist,,Arthur Rothstein,"American, 1915–1985",,"Rothstein, Arthur",American,1915,1985,1937,1937,1937,Gelatin silver print,18.1 x 24.1 cm (7 1/8 x 9 1/2 in. ),"Purchase, Alfred Stieglitz Society Gifts, 2001",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/284663,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1987.1100.64,false,true,265591,Photographs,Photograph,"Son of Sharecropper - Mississippi Country, Arkansas",,,,,,Artist,,Arthur Rothstein,"American, 1915–1985",,"Rothstein, Arthur",American,1915,1985,1935,1935,1935,Gelatin silver print,25.5 x 20.2 cm (10 1/16 x 7 15/16 in.),"Ford Motor Company Collection, Gift of Ford Motor Company and John C. Waddell, 1987",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/265591,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2011.478,false,true,299311,Photographs,Carte-de-visite,"Rebecca, Charley and Rosa, Slave Children from New Orleans",,,,,,Artist,,Myron H. Kimball,"American, active 1860s",,"Kimball, Myron H.",American,1860,1860,1863–64,1863,1864,Albumen silver print from glass negative,Image: 8.4 x 5.4 cm (3 5/16 x 2 1/8 in.) Mount: 10.1 x 6.2 cm (4 x 2 7/16 in.),"The Horace W. Goldsmith Foundation Fund, through Joyce and Robert Menschel, 2011",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/299311,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2014.273,false,true,645493,Photographs,Photograph,"A Burial Party, Cold Harbor, Virginia.",,,,,,Artist,,John Reekie,"American, active 1860s",,"Reekie, John",American,1860,1869,April 1865,1865,1865,Albumen silver print from glass negative,Image: 7 × 9 in. (17.8 × 22.9 cm) Mount: 12 5/8 × 17 1/2 in. (32 × 44.4 cm),"Purchase, W. Bruce and Delaney H. Lundberg Gift and The Horace W. Goldsmith Foundation Fund, through Joyce and Robert Menschel, 2014",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/645493,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.36,false,true,685378,Photographs,Carte-de-visite,[William Holbrook Beard],,,,,,Artist,,J. T. Upson,"American, active 1860s",,"Upson, J. T.",American,1859,1870,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/685378,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.50,false,true,685392,Photographs,Carte-de-visite,[Eugene Benson],,,,,,Artist,,Maurice Stadtfeld,"American, active 1860s",,"Stadtfeld, Maurice",American,1860,1860,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/685392,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.92,false,true,283194,Photographs,Photograph,Emancipated Slaves Brought from Louisiana by Colonel George H. Banks,,,,,,Artist,,Myron H. Kimball,"American, active 1860s",,"Kimball, Myron H.",American,1860,1860,December 1863,1863,1863,Albumen silver print from glass negative,"Image: 13.2 x 18.3cm (5 3/16 x 7 3/16in.), oblong oval Mat: 19.9 x 25.2 cm (7 13/16 x 9 15/16 in.)","Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/283194,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.286,false,true,685627,Photographs,Carte-de-visite,[George Augustus Baker],,,,,,Artist,,Maurice Stadtfeld,"American, active 1860s",,"Stadtfeld, Maurice",American,1860,1860,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/685627,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.402,false,true,685742,Photographs,Carte-de-visite,[William John Hennessy],,,,,,Artist,,Maurice Stadtfeld,"American, active 1860s",,"Stadtfeld, Maurice",American,1860,1860,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/685742,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.737,false,true,686076,Photographs,Carte-de-visite,[Morrell],,,,,,Artist,,Maurice Stadtfeld,"American, active 1860s",,"Stadtfeld, Maurice",American,1860,1860,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/686076,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.815,false,true,686154,Photographs,Carte-de-visite,[A.H. Wenzler],,,,,,Artist,,Maurice Stadtfeld,"American, active 1860s",,"Stadtfeld, Maurice",American,1860,1860,1860s,1860,1869,Albumen silver print,Approx. 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/686154,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.1231,false,true,285894,Photographs,Photograph,"Duryea Zouaves, Fort Schuyler Adjuant Mess",,,,,,Artist,,Stacy,"American, active 1860s",,Stacy,American,1860,1869,"May 18, 1861",1861,1861,Albumen silver print from glass negative,5 9/16 x 7 1/2,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285894,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.457.3681,false,true,291274,Photographs,Photographs,Reception of the Japanese Embassy at the Battery,,,,,,Artist,Attributed to,George Stacy,"American, active 1860s",,"Stacy, George",American,1860,1860,1860,1860,1860,Albumen silver print from glass negative,"Image: 7.4 x 14.6 cm (2 15/16 x 5 3/4 in.), arch-topped Mount: 18.4 x 17.5 cm (7 1/4 x 6 7/8 in.)","Herbert Mitchell Collection, 2007",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/291274,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.386,false,true,285781,Photographs,Photograph,James Hyatt Inhaling Chlorine Gas,,,,,,Artist,,Peter Welling,"American, active c. 1850s",,"Welling, Peter",American,1850,1859,1850–55,1850,1855,Daguerreotype,Image: 2 11/16 × 2 3/16 in. (6.9 × 5.6 cm); visible Overall: 3 5/8 × 3 1/8 in. (9.2 × 8 cm),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285781,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.329,false,true,285986,Photographs,Photographs,"The Lincoln Funeral Train, Philadelphia",,,,,,Artist,,Charles L. Philippi,"American, active 1861–74",,"Philippi, Charles L.",American,1861,1874,"April 22–24, 1865",1865,1865,Albumen silver print from glass negative,Image: 24 x 33.8 cm (9 7/16 x 13 5/16 in.) Mount: 26.5 x 34.8 cm (10 7/16 x 13 11/16 in.),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285986,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2000.124,false,true,283717,Photographs,Photograph,"Gibson's Breaker, Rushdale, Pennsylvania",,,,,,Artist,,Thomas H. Johnson,"American, active 1860s–70s",,"Johnson, Thomas H.",American,1860,1870,1860s,1860,1869,Albumen silver print from glass negative,30.3 x 38.7 cm (11 15/16 x 15 1/4 in. ),"Purchase, Alfred Stieglitz Society Gifts, 2000",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/283717,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.1145,false,true,286517,Photographs,Cabinet card,Musical Mokes,,,,,,Artist,,J. Wood,"American, active 1870s–80s",,"Wood, J.",American,1870,1889,1860s,1860,1869,Albumen silver print from glass negative,Image: 5 7/8 × 3 7/8 in. (14.9 × 9.8 cm),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286517,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.1142,false,true,286633,Photographs,Photograph,12 O'clock in the Deadening,,,,,,Artist,,John Horgan Jr.,"American, active 1880s–90s",,"Horgan Jr, John",American,1880,1899,ca. 1891,1889,1893,Albumen silver print from glass negative,Image: 17 in. × 19 1/4 in. (43.2 × 48.9 cm) Mount: 20 1/4 × 24 3/16 in. (51.5 × 61.5 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286633,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.400.41,false,true,291794,Photographs,Daguerreotype,[Young Man],,,,,,Artist,,Knickerbocker Gallery,"American, active ca. 1841–59",,Knickerbocker Gallery,American,1841,1859,1850s,1850,1859,Daguerreotype,Image: 7 x 5.7 cm (2 3/4 x 2 1/4 in.) Plate: 8.3 x 7 cm (3 1/4 x 2 3/4 in.) Case: 1.6 x 9.2 x 7.9 cm (5/8 x 3 5/8 x 3 1/8 in.),"Bequest of Herbert Mitchell, 2008",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/291794,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2011.477,false,true,299310,Photographs,Carte-de-visite,"Learning is Wealth—Wilson, Charley, Rebecca, and Rosa, Slaves from New Orleans",,,,,,Artist,,Charles Paxson,"American, active New York, 1860s",,"Paxson, Charles",American,1860,1869,1863–64,1863,1864,Albumen silver print from glass negative,Image: 8.5 x 5.3 cm (3 3/8 x 2 1/16 in.) Mount: 10.1 x 6.1 cm (4 x 2 3/8 in.),"The Horace W. Goldsmith Foundation Fund, through Joyce and Robert Menschel, 2011",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/299310,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1982.1182.1914,false,true,288312,Photographs,Stereographs,"German's Day, California Midwinter Exposition",,,,,,Artist,,Kilburn Brothers,"American, active ca. 1865–1890",,Kilburn Brothers,American,1863,1892,1850s–1910s,1850,1919,Albumen silver prints,Mount: 8.9 x 17.8 cm (3 1/2 x 7 in.),"Gift of Weston J. Naef, in memory of Kathleen W. Naef and Weston J. Naef Sr., 1982",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288312,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2004.454.2,false,true,285488,Photographs,Photograph,"[Interborough Rapid Transit (IRT) Construction, Broadway Looking North at 101st Street, New York City]",,,,,,Artist,,William B.,"American, active ca. 1900–1939",,"B., William",American,1900,1939,1900,1900,1900,Platinum print,Image: 19 x 24 cm (7 1/2 x 9 7/16 in.),"Purchase, Marlene Nathan Meyerson Family Foundation Gift, 2004",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285488,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2004.454.10,false,true,285486,Photographs,Photograph,"[Interborough Rapid Transit (IRT) Construction, 25th Street and Fourth Avenue, New York City]",,,,,,Artist,,W. R. C.,"American, active ca. 1900s–1930s",,"C., W. R.",American,1900,1939,1906,1906,1906,Platinum print,Image: 19 x 24 cm (7 1/2 x 9 7/16 in.),"Purchase, Marlene Nathan Meyerson Family Foundation Gift, 2004",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285486,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -57.670.2,false,true,269959,Photographs,Photograph,"[Stern-Wheeler Arriving at Silver Springs, Florida, after an Overnight Run up the St. Johns, Oklawaha, & Silver Rivers]",,,,,,Artist,,George Barker,"American, born Canada, 1844–1894",,"Barker, George",American,1844,1894,1886,1886,1886,Albumen silver print from glass negative,41.3 x 51.3 cm. (16 1/4 x 20 3/16 in.),"Gift of A. Hyatt Mayor, 1957",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269959,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.494.50,false,true,289322,Photographs,Photograph,Canton City,,,,,,Artist,,Milton M. Miller,"American, active China, 1830–1899",,"Miller, Milton",American,1830,1899,ca. 1869,1869,1869,Albumen silver print from glass negative,Image: 8 1/4 × 11 7/16 in. (20.9 × 29 cm),"Gilman Collection, Purchase, Robert Rosenkranz Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/289322,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.494.54,false,true,289325,Photographs,Photograph,Mandarin Wife,,,,,,Artist,,Milton M. Miller,"American, active China, 1830–1899",,"Miller, Milton",American,1830,1899,1860–1863,1860,1863,Albumen silver print from glass negative,Image: 9 in. × 6 3/4 in. (22.9 × 17.2 cm),"Gilman Collection, Purchase, Robert Rosenkranz Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/289325,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.494.55,false,true,289326,Photographs,Photograph,Madarin Wife,,,,,,Artist,,Milton M. Miller,"American, active China, 1830–1899",,"Miller, Milton",American,1830,1899,1861–1863,1861,1863,Albumen silver print from glass negative,Image: 11 1/8 × 8 3/4 in. (28.3 × 22.2 cm),"Gilman Collection, Purchase, Robert Rosenkranz Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/289326,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.494.57,false,true,289328,Photographs,Photograph,Mandarin with Family,,,,,,Artist,,Milton M. Miller,"American, active China, 1830–1899",,"Miller, Milton",American,1830,1899,1860–1863,1860,1863,Albumen silver print from glass negative,Image: 8 3/16 × 11 1/8 in. (20.8 × 28.2 cm),"Gilman Collection, Purchase, Robert Rosenkranz Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/289328,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2004.251,false,true,285438,Photographs,Photograph,"[Waterfall, Constantine]",,,,,,Artist,,John Beasley Greene,"American, active France, 1832–1856",,"Greene, John Beasley",American,1832,1856,1856,1856,1856,Salted paper print from paper negative,Image: 23.3 x 30.1 cm (9 3/16 x 11 7/8 in.),"Purchase, Alfred Stieglitz Society Gifts, Anonymous Foundation Gift, W. Bruce and Delaney H. Lundberg Gift, and Marian and James H. Cohen Gift, in memory of their son, Michael Harrison Cohen, 2004",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285438,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.1063,true,true,266121,Photographs,Photograph,Medinet-Habu,,,,,,Artist,,John Beasley Greene,"American, active France, 1832–1856",,"Greene, John Beasley",American,1832,1856,1854,1854,1854,Salted paper print from paper negative,23.4 x 30.1 cm (9 3/16 x 11 7/8 in.),"Purchase, The Howard Gilman Foundation Gift, 1989",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/266121,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.63,false,true,283148,Photographs,Photograph,[The Nile in front of the Theban Hills],,,,,,Artist,,John Beasley Greene,"American, active France, 1832–1856",,"Greene, John Beasley",American,1832,1856,1853–54,1853,1854,Salted paper print from paper negative,Mount: 18 7/16 × 24 1/8 in. (46.9 × 61.2 cm) Image: 8 3/4 × 11 7/8 in. (22.3 × 30.2 cm),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/283148,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.64,false,true,283149,Photographs,Photograph,Dakkeh,,,,,,Artist,,John Beasley Greene,"American, active France, 1832–1856",,"Greene, John Beasley",American,1832,1856,1853–54,1853,1854,Salted paper print from paper negative,Image: 9 3/16 × 11 7/8 in. (23.4 × 30.2 cm) Mount: 18 1/2 × 24 1/8 in. (47 × 61.2 cm),"Gilman Collection, Purchase, Joseph M. Cohen Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/283149,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.276,false,true,286665,Photographs,Photograph,[Excavations near the Sphinx],,,,,,Artist,,John Beasley Greene,"American, active France, 1832–1856",,"Greene, John Beasley",American,1832,1856,1853,1853,1853,Salted paper print from paper negative,Image: 22.3 x 30.2 cm (8 3/4 x 11 7/8 in.) Mount: 18 11/16 × 24 5/16 in. (47.5 × 61.8 cm),"Gilman Collection, Purchase, Ann Tenenbaum and Thomas H. Lee Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286665,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.759,false,true,286131,Photographs,Photograph,"Etude de Palmiers, Bords du Nil, Kalabschi",,,,,,Artist,,John Beasley Greene,"American, active France, 1832–1856",,"Greene, John Beasley",American,1832,1856,1853–54,1853,1854,Salted paper print from paper negative,9 1/4 x 11 3/4,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286131,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.760,false,true,286130,Photographs,Photograph,[Island of Philae],,,,,,Artist,,John Beasley Greene,"American, active France, 1832–1856",,"Greene, John Beasley",American,1832,1856,1853–54,1853,1854,Salted paper print from paper negative,9 x 11 3/4,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286130,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.761,false,true,286135,Photographs,Photograph,Temple de Deboud,,,,,,Artist,,John Beasley Greene,"American, active France, 1832–1856",,"Greene, John Beasley",American,1832,1856,1853–54,1853,1854,Salted paper print from paper negative,9 1/8 x 11,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286135,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.762,false,true,286690,Photographs,Photograph,"[Boat in Harbor, Algeria]",,,,,,Artist,,John Beasley Greene,"American, active France, 1832–1856",,"Greene, John Beasley",American,1832,1856,1853–54,1853,1854,Salted paper print from paper negative,8 7/8 x 11 1/8,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286690,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.763,false,true,286689,Photographs,Photograph,"[Antiquities in the Museum at Cherchell, Algeria]",,,,,,Artist,,John Beasley Greene,"American, active France, 1832–1856",,"Greene, John Beasley",American,1832,1856,1853–54,1853,1854,Salted paper print from paper negative,Image: 11 7/16 × 8 15/16 in. (29.1 × 22.7 cm),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286689,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.764,false,true,286688,Photographs,Photograph,"[Constantine, Algeria]",,,,,,Artist,,John Beasley Greene,"American, active France, 1832–1856",,"Greene, John Beasley",American,1832,1856,1853–54,1853,1854,Salted paper print from paper negative,Image: 9 3/16 × 11 13/16 in. (23.4 × 30 cm),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286688,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.765,false,true,286129,Photographs,Photograph,"[Fragment of an Egyptian Statue in the Museum at Cherchell, Algeria]",,,,,,Artist,,John Beasley Greene,"American, active France, 1832–1856",,"Greene, John Beasley",American,1832,1856,1856,1856,1856,Salted paper print from paper negative,11 3/8 x 9 1/8,"Gilman Collection, Purchase, Mr. and Mrs. Henry R. Kravis Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286129,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.766,false,true,286124,Photographs,Photograph,"[Tents, Algeria]",,,,,,Artist,,John Beasley Greene,"American, active France, 1832–1856",,"Greene, John Beasley",American,1832,1856,1856,1856,1856,Salted paper print from paper negative,Image: 8 9/16 × 11 3/4 in. (21.7 × 29.8 cm),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286124,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.783,false,true,286094,Photographs,Photograph,"[Bank of the Rhumel, near Constantine, Algeria]",,,,,,Artist,,John Beasley Greene,"American, active France, 1832–1856",,"Greene, John Beasley",American,1832,1856,ca. 1855–56,1853,1858,Salted paper print from paper negative,9 3/8 x 12 1/8,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286094,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.849,false,true,285974,Photographs,Photograph,"Amada, Temple",,,,,,Artist,,John Beasley Greene,"American, active France, 1832–1856",,"Greene, John Beasley",American,1832,1856,1853–54,1853,1854,Salted paper print from paper negative,Image: 8 3/4 × 11 3/4 in. (22.3 × 29.9 cm) Mount: 18 9/16 × 24 1/8 in. (47.1 × 61.2 cm),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285974,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.400.28,false,true,291781,Photographs,Daguerreotype,[Seated Elderly Woman Wearing Plaid Dress and Bonnet],,,,,,Artist,,William Hardy Kent,"American, England 1819–1907 England",,"Kent, William Hardy",American,1819,1819,1854–60,1854,1860,Daguerreotype,Image: 9.2 x 6.4 cm (3 5/8 x 2 1/2 in.) Plate: 10.8 x 8.3 cm (4 1/4 x 3 1/4 in.) Frame: 11.7 x 9.4 cm (4 5/8 x 3 11/16 in.),"Bequest of Herbert Mitchell, 2008",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/291781,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.400.29,false,true,291782,Photographs,Daguerreotype,[Seated Middle-aged Woman Dressed in Finery],,,,,,Artist,,William Hardy Kent,"American, England 1819–1907 England",,"Kent, William Hardy",American,1819,1819,1854–60,1854,1860,Daguerreotype,Image: 9 x 6.3 cm (3 9/16 x 2 1/2 in.) Plate: 10.8 x 8.3 cm (4 1/4 x 3 1/4 in.) Frame: 12.1 x 9.2 cm (4 3/4 x 3 5/8 in.),"Bequest of Herbert Mitchell, 2008",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/291782,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.43.66,false,true,267861,Photographs,Photograph,Song of the Lily,,,,,,Artist,,Frank Eugene,"American, New York 1865–1936 Munich",,"Eugene, Frank",American,1865,1936,1897,1897,1897,Platinum print,17.0 x 12.1 cm. (6 11/16 x 4 3/4 in.),"Alfred Stieglitz Collection, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267861,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.43.68,false,true,267863,Photographs,Photograph,Frau Willi Geiger (Clara),,,,,,Artist,,Frank Eugene,"American, New York 1865–1936 Munich",,"Eugene, Frank",American,1865,1936,1900–1908,1900,1908,Platinum print,11.9 x 17.0 cm. (4 11/16 x 6 11/16 in.),"Alfred Stieglitz Collection, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267863,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.43.69,false,true,271730,Photographs,Photograph,Brigitta Wenz,,,,,,Artist,,Frank Eugene,"American, New York 1865–1936 Munich",,"Eugene, Frank",American,1865,1936,1900,1900,1900,Platinum print,12.0 x 16.8 cm. (4 3/4 x 6 5/8 in.),"Alfred Stieglitz Collection, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/271730,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.43.70,false,true,267865,Photographs,Photograph,Summer,,,,,,Artist,,Frank Eugene,"American, New York 1865–1936 Munich",,"Eugene, Frank",American,1865,1936,1898,1898,1898,Platinum print,17.7 x 6.3 cm. (7 x 2 1/2 in.),"Alfred Stieglitz Collection, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267865,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.43.74,false,true,271746,Photographs,Photograph,Dr. Emanuel Lasker and His Brother,,,,,,Artist,,Frank Eugene,"American, New York 1865–1936 Munich",,"Eugene, Frank",American,1865,1936,1907,1907,1907,Platinum print,15.5 x 12.1 cm. (6 1/8 x 4 3/4 in.),"Alfred Stieglitz Collection, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/271746,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.43.76,false,true,271741,Photographs,Photograph,The Man in Armor (Self-Portrait),,,,,,Artist,,Frank Eugene,"American, New York 1865–1936 Munich",,"Eugene, Frank",American,1865,1936,1898,1898,1898,Platinum print,17.3 x 11.9 cm. (6 13/16 x 4 11/16 in.),"Alfred Stieglitz Collection, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/271741,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.43.87,false,true,271733,Photographs,Photograph,"Stieglitz, Steichen and Kuehn Admiring the Work of Frank Eugene",,,,,,Artist,,Frank Eugene,"American, New York 1865–1936 Munich",,"Eugene, Frank",American,1865,1936,1907,1907,1907,Platinum print,12.1 x 16.8 cm. (4 3/4 x 6 5/8 in.),"Alfred Stieglitz Collection, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/271733,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -55.635.7,false,true,271750,Photographs,Photograph,Alfred Stieglitz,,,,,,Artist,,Frank Eugene,"American, New York 1865–1936 Munich",,"Eugene, Frank",American,1865,1936,1907,1907,1907,Platinum print,16.5 x 11.8 cm. (6 1/2 x 4 5/8 in.),"Alfred Stieglitz Collection, 1955",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/271750,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.633.33,false,true,260240,Photographs,Photograph,Adam and Eve,,,,,,Artist,,Frank Eugene,"American, New York 1865–1936 Munich",,"Eugene, Frank",American,1865,1936,"1900s, printed 1909",1900,1909,Photogravure,17.8 x 12.8 cm. (7 x 5 1/16 in.),"Rogers Fund, 1972",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260240,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.633.61,false,true,260272,Photographs,Photograph,Miss Gladys Lawrence - The Seashell,,,,,,Artist,,Frank Eugene,"American, New York 1865–1936 Munich",,"Eugene, Frank",American,1865,1936,1910–13,1910,1913,Platinum print,,"Rogers Fund, 1972",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260272,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.633.63,false,true,260274,Photographs,Photograph,The Guitar Player,,,,,,Artist,,Frank Eugene,"American, New York 1865–1936 Munich",,"Eugene, Frank",American,1865,1936,ca. 1908,1906,1910,Platinum print,,"Rogers Fund, 1972",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260274,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.633.64,false,true,260275,Photographs,Photograph,The Oriental Bride,,,,,,Artist,,Frank Eugene,"American, New York 1865–1936 Munich",,"Eugene, Frank",American,1865,1936,1900s,1900,1909,Platinum print,,"Rogers Fund, 1972",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260275,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.633.65,false,true,260276,Photographs,Photograph,Joachim's Daughter,,,,,,Artist,,Frank Eugene,"American, New York 1865–1936 Munich",,"Eugene, Frank",American,1865,1936,1899,1899,1899,Platinum print,,"Rogers Fund, 1972",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260276,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.633.66,false,true,260277,Photographs,Photograph,Snakecharmer,,,,,,Artist,,Frank Eugene,"American, New York 1865–1936 Munich",,"Eugene, Frank",American,1865,1936,1900–1908,1900,1908,Platinum print,,"Rogers Fund, 1972",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260277,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.633.67,false,true,260278,Photographs,Photograph,Snakecharmer,,,,,,Artist,,Frank Eugene,"American, New York 1865–1936 Munich",,"Eugene, Frank",American,1865,1936,1900–1908,1900,1908,Platinum print,,"Rogers Fund, 1972",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260278,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.633.68,false,true,260279,Photographs,Photograph,Miss Nan N. - Indian Festival,,,,,,Artist,,Frank Eugene,"American, New York 1865–1936 Munich",,"Eugene, Frank",American,1865,1936,1900s,1900,1909,Platinum print,,"Rogers Fund, 1972",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260279,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.633.69,false,true,260280,Photographs,Photograph,The Misses Ide in Samoa,,,,,,Artist,,Frank Eugene,"American, New York 1865–1936 Munich",,"Eugene, Frank",American,1865,1936,ca. 1908,1906,1910,Platinum print,11.8 x 16.1 cm (4 5/8 x 6 5/16 in.),"Rogers Fund, 1972",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260280,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.633.70,false,true,260282,Photographs,Photograph,Miss Ide,,,,,,Artist,,Frank Eugene,"American, New York 1865–1936 Munich",,"Eugene, Frank",American,1865,1936,1890–1903,1890,1903,Platinum print,,"Rogers Fund, 1972",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260282,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.633.71,false,true,260283,Photographs,Photograph,Baroness von W. of Vienna,,,,,,Artist,,Frank Eugene,"American, New York 1865–1936 Munich",,"Eugene, Frank",American,1865,1936,1900s,1900,1909,Platinum print,,"Rogers Fund, 1972",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260283,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.633.72,false,true,260284,Photographs,Photograph,Mirzl Wach. The Sister of Charity,,,,,,Artist,,Frank Eugene,"American, New York 1865–1936 Munich",,"Eugene, Frank",American,1865,1936,1900s,1900,1909,Platinum print,,"Rogers Fund, 1972",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260284,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.633.73,false,true,260285,Photographs,Photograph,Emmy G.,,,,,,Artist,,Frank Eugene,"American, New York 1865–1936 Munich",,"Eugene, Frank",American,1865,1936,1900–1908,1900,1908,Platinum print,,"Rogers Fund, 1972",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260285,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.633.74,false,true,260286,Photographs,Photograph,Emmy Geiger,,,,,,Artist,,Frank Eugene,"American, New York 1865–1936 Munich",,"Eugene, Frank",American,1865,1936,1900–1908,1900,1908,Platinum print,11.9 x 16.4 cm (4 11/16 x 6 7/16 in.),"Rogers Fund, 1972",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260286,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.633.75,false,true,260287,Photographs,Photograph,Emmy G.,,,,,,Artist,,Frank Eugene,"American, New York 1865–1936 Munich",,"Eugene, Frank",American,1865,1936,1900–1908,1900,1908,Platinum print,,"Rogers Fund, 1972",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260287,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.633.76,false,true,260288,Photographs,Photograph,Mrs. Wilm,,,,,,Artist,,Frank Eugene,"American, New York 1865–1936 Munich",,"Eugene, Frank",American,1865,1936,1909,1909,1909,Platinum print,,"Rogers Fund, 1972",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260288,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.633.78,false,true,260290,Photographs,Photograph,Lisl Bosse,,,,,,Artist,,Frank Eugene,"American, New York 1865–1936 Munich",,"Eugene, Frank",American,1865,1936,1900s,1900,1909,Platinum print,,"Rogers Fund, 1972",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260290,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.633.79,false,true,260291,Photographs,Photograph,Hortensia,,,,,,Artist,,Frank Eugene,"American, New York 1865–1936 Munich",,"Eugene, Frank",American,1865,1936,1898,1898,1898,Platinum print,,"Rogers Fund, 1972",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260291,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.633.81,false,true,260294,Photographs,Photograph,Miss Lilian C. Wiver in Her Workshop,,,,,,Artist,,Frank Eugene,"American, New York 1865–1936 Munich",,"Eugene, Frank",American,1865,1936,1900s,1900,1909,Platinum print,,"Rogers Fund, 1972",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260294,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.633.82,false,true,260295,Photographs,Photograph,Miss Lilian C. Wiver with Her Angora,,,,,,Artist,,Frank Eugene,"American, New York 1865–1936 Munich",,"Eugene, Frank",American,1865,1936,1900s,1900,1909,Platinum print,Image: 16.7 × 12 cm (6 9/16 × 4 3/4 in.) Mount: 25.3 × 20 cm (9 15/16 × 7 7/8 in.),"Rogers Fund, 1972",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260295,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.633.83,false,true,260296,Photographs,Photograph,Thilda H. - The Veiled Lady,,,,,,Artist,,Frank Eugene,"American, New York 1865–1936 Munich",,"Eugene, Frank",American,1865,1936,1900s,1900,1909,Platinum print,,"Rogers Fund, 1972",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260296,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.633.84,false,true,260297,Photographs,Photograph,Sweet Alice (Ben Bolt),,,,,,Artist,,Frank Eugene,"American, New York 1865–1936 Munich",,"Eugene, Frank",American,1865,1936,1900s,1900,1909,Platinum print,,"Rogers Fund, 1972",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260297,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.633.85,false,true,260298,Photographs,Photograph,The Painter's Wife,,,,,,Artist,,Frank Eugene,"American, New York 1865–1936 Munich",,"Eugene, Frank",American,1865,1936,1900s,1900,1909,Platinum print,,"Rogers Fund, 1972",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260298,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.633.86,false,true,260299,Photographs,Photograph,The Diva at Home,,,,,,Artist,,Frank Eugene,"American, New York 1865–1936 Munich",,"Eugene, Frank",American,1865,1936,1900s,1900,1909,Platinum print,,"Rogers Fund, 1972",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260299,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.633.87,false,true,260300,Photographs,Photograph,The Diva and Her Most Trusty Friend and Companion,,,,,,Artist,,Frank Eugene,"American, New York 1865–1936 Munich",,"Eugene, Frank",American,1865,1936,1900–1908,1900,1908,Platinum print,,"Rogers Fund, 1972",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260300,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.633.89,false,true,260302,Photographs,Photograph,On the Wabash (Miss D.),,,,,,Artist,,Frank Eugene,"American, New York 1865–1936 Munich",,"Eugene, Frank",American,1865,1936,1900s,1900,1909,Platinum print,,"Rogers Fund, 1972",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260302,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.633.90,false,true,260304,Photographs,Photograph,Frau Clara G.,,,,,,Artist,,Frank Eugene,"American, New York 1865–1936 Munich",,"Eugene, Frank",American,1865,1936,1900–1908,1900,1908,Platinum print,,"Rogers Fund, 1972",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260304,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.633.91,false,true,260305,Photographs,Photograph,Miss L.L.L.,,,,,,Artist,,Frank Eugene,"American, New York 1865–1936 Munich",,"Eugene, Frank",American,1865,1936,1900s,1900,1909,Platinum print,,"Rogers Fund, 1972",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260305,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.633.92,false,true,260306,Photographs,Photograph,Lise Lotte Lindström,,,,,,Artist,,Frank Eugene,"American, New York 1865–1936 Munich",,"Eugene, Frank",American,1865,1936,1900s,1900,1909,Platinum print,,"Rogers Fund, 1972",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260306,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.633.93,false,true,260307,Photographs,Photograph,Baroness Haltvayne,,,,,,Artist,,Frank Eugene,"American, New York 1865–1936 Munich",,"Eugene, Frank",American,1865,1936,1900s,1900,1909,Platinum print,,"Rogers Fund, 1972",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260307,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.633.94,false,true,260308,Photographs,Photograph,Maria von Seidl,,,,,,Artist,,Frank Eugene,"American, New York 1865–1936 Munich",,"Eugene, Frank",American,1865,1936,1900s,1900,1909,Platinum print,,"Rogers Fund, 1972",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260308,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.633.95,false,true,260309,Photographs,Photograph,Marie Struthers,,,,,,Artist,,Frank Eugene,"American, New York 1865–1936 Munich",,"Eugene, Frank",American,1865,1936,1890–1903,1890,1903,Platinum print,,"Rogers Fund, 1972",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260309,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.633.96,false,true,260310,Photographs,Photograph,The Pearl Necklace,,,,,,Artist,,Frank Eugene,"American, New York 1865–1936 Munich",,"Eugene, Frank",American,1865,1936,1900s,1900,1909,Platinum print,,"Rogers Fund, 1972",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260310,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.633.97,false,true,260311,Photographs,Photograph,Dolly Varden,,,,,,Artist,,Frank Eugene,"American, New York 1865–1936 Munich",,"Eugene, Frank",American,1865,1936,1900s,1900,1909,Platinum print,,"Rogers Fund, 1972",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260311,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.633.98,false,true,260312,Photographs,Photograph,Dolly Varden,,,,,,Artist,,Frank Eugene,"American, New York 1865–1936 Munich",,"Eugene, Frank",American,1865,1936,1900s,1900,1909,Platinum print,,"Rogers Fund, 1972",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260312,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.633.99,false,true,260313,Photographs,Photograph,Lydia Leslie Lydie - Candlestick,,,,,,Artist,,Frank Eugene,"American, New York 1865–1936 Munich",,"Eugene, Frank",American,1865,1936,1900s,1900,1909,Platinum print,,"Rogers Fund, 1972",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260313,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.633.100,false,true,260106,Photographs,Photograph,Fritzi von Derra - The Exotic Dancer,,,,,,Artist,,Frank Eugene,"American, New York 1865–1936 Munich",,"Eugene, Frank",American,1865,1936,1900s,1900,1909,Platinum print,,"Rogers Fund, 1972",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260106,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.633.101,false,true,260107,Photographs,Photograph,Fritzi von Derra - Greek Dancer,,,,,,Artist,,Frank Eugene,"American, New York 1865–1936 Munich",,"Eugene, Frank",American,1865,1936,1900s,1900,1909,Platinum print,,"Rogers Fund, 1972",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260107,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.633.102,false,true,260108,Photographs,Photograph,Fritzi von Derra - The Greek Dancer,,,,,,Artist,,Frank Eugene,"American, New York 1865–1936 Munich",,"Eugene, Frank",American,1865,1936,1900s,1900,1909,Platinum print,,"Rogers Fund, 1972",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260108,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.633.103,false,true,260109,Photographs,Photograph,Fritzi von Derra - The Greek Dancer,,,,,,Artist,,Frank Eugene,"American, New York 1865–1936 Munich",,"Eugene, Frank",American,1865,1936,1900s,1900,1909,Platinum print,,"Rogers Fund, 1972",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260109,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.633.104,false,true,260110,Photographs,Photograph,Fritzi von Derra - The Greek Dancer,,,,,,Artist,,Frank Eugene,"American, New York 1865–1936 Munich",,"Eugene, Frank",American,1865,1936,1900s,1900,1909,Platinum print,,"Rogers Fund, 1972",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260110,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.633.105,false,true,260111,Photographs,Photograph,Fritzi von Derra - The Oriental Dancer,,,,,,Artist,,Frank Eugene,"American, New York 1865–1936 Munich",,"Eugene, Frank",American,1865,1936,1900s,1900,1909,Platinum print,,"Rogers Fund, 1972",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260111,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.633.106,false,true,260112,Photographs,Photograph,The Baronin B. and Miss M. - Rosenkavalier,,,,,,Artist,,Frank Eugene,"American, New York 1865–1936 Munich",,"Eugene, Frank",American,1865,1936,1900s,1900,1909,Platinum print,,"Rogers Fund, 1972",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260112,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.633.107,false,true,260113,Photographs,Photograph,"""Fisherman's Luck"" - Henry Heyligers and Wife",,,,,,Artist,,Frank Eugene,"American, New York 1865–1936 Munich",,"Eugene, Frank",American,1865,1936,1900s,1900,1909,Platinum print,,"Rogers Fund, 1972",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260113,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.633.108,false,true,260114,Photographs,Photograph,Frau Frieda and Franz S,,,,,,Artist,,Frank Eugene,"American, New York 1865–1936 Munich",,"Eugene, Frank",American,1865,1936,1900s,1900,1909,Platinum print,,"Rogers Fund, 1972",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260114,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.633.109,false,true,260115,Photographs,Photograph,Four Sisters,,,,,,Artist,,Frank Eugene,"American, New York 1865–1936 Munich",,"Eugene, Frank",American,1865,1936,1900s,1900,1909,Platinum print,,"Rogers Fund, 1972",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260115,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.633.110,false,true,260117,Photographs,Photograph,The Cake Walk,,,,,,Artist,,Frank Eugene,"American, New York 1865–1936 Munich",,"Eugene, Frank",American,1865,1936,1900s,1900,1909,Platinum print,,"Rogers Fund, 1972",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260117,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.633.111,false,true,260118,Photographs,Photograph,Ritual Vestalis,,,,,,Artist,,Frank Eugene,"American, New York 1865–1936 Munich",,"Eugene, Frank",American,1865,1936,1900s,1900,1909,Platinum print,,"Rogers Fund, 1972",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260118,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.633.112,false,true,260119,Photographs,Photograph,Slumbering Maidens,,,,,,Artist,,Frank Eugene,"American, New York 1865–1936 Munich",,"Eugene, Frank",American,1865,1936,1900s,1900,1909,Platinum print,,"Rogers Fund, 1972",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260119,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.633.113,false,true,260120,Photographs,Photograph,The Graduating Class,,,,,,Artist,,Frank Eugene,"American, New York 1865–1936 Munich",,"Eugene, Frank",American,1865,1936,1913,1913,1913,Platinum print,,"Rogers Fund, 1972",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260120,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.633.114,false,true,260121,Photographs,Photograph,"Misses Weaver H. Patties' School for Girls, Munich",,,,,,Artist,,Frank Eugene,"American, New York 1865–1936 Munich",,"Eugene, Frank",American,1865,1936,1912,1912,1912,Platinum print,,"Rogers Fund, 1972",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260121,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.633.115,false,true,260122,Photographs,Photograph,Marie R. and Cryma,,,,,,Artist,,Frank Eugene,"American, New York 1865–1936 Munich",,"Eugene, Frank",American,1865,1936,1900s,1900,1909,Platinum print,,"Rogers Fund, 1972",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260122,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.633.116,false,true,260123,Photographs,Photograph,SKH Prinzregent Ludwig von Bayern,,,,,,Artist,,Frank Eugene,"American, New York 1865–1936 Munich",,"Eugene, Frank",American,1865,1936,1912–13,1912,1913,Platinum print,23.1 x 15.8 cm (9 1/16 x 6 3/16 in.),"Rogers Fund, 1972",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260123,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.633.117,false,true,260124,Photographs,Photograph,Crown Prince Ludwig III of Bavaria,,,,,,Artist,,Frank Eugene,"American, New York 1865–1936 Munich",,"Eugene, Frank",American,1865,1936,1912,1912,1912,Platinum print,,"Rogers Fund, 1972",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260124,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.633.118,false,true,260125,Photographs,Photograph,Crown Prince Rupprecht of Bavaria,,,,,,Artist,,Frank Eugene,"American, New York 1865–1936 Munich",,"Eugene, Frank",American,1865,1936,1900–1908,1900,1908,Platinum print,,"Rogers Fund, 1972",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260125,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.633.120,false,true,260128,Photographs,Photograph,H.R.H. Prince Leopold and Prince Johann Albrecht of Bavaria,,,,,,Artist,,Frank Eugene,"American, New York 1865–1936 Munich",,"Eugene, Frank",American,1865,1936,1900–1908,1900,1908,Platinum print,,"Rogers Fund, 1972",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260128,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.633.121,false,true,260129,Photographs,Photograph,H.R.H. Prince Leopold of Bavaria,,,,,,Artist,,Frank Eugene,"American, New York 1865–1936 Munich",,"Eugene, Frank",American,1865,1936,1900–1908,1900,1908,Platinum print,,"Rogers Fund, 1972",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260129,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.633.122,false,true,260130,Photographs,Photograph,H.R.H. Prince Leopold and His Hobbyhorse,,,,,,Artist,,Frank Eugene,"American, New York 1865–1936 Munich",,"Eugene, Frank",American,1865,1936,1900–1908,1900,1908,Platinum print,,"Rogers Fund, 1972",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260130,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.633.123,false,true,260131,Photographs,Photograph,"H.R.H. Prince Albrecht Johann. ""With Neither Crown Nor Scepter""",,,,,,Artist,,Frank Eugene,"American, New York 1865–1936 Munich",,"Eugene, Frank",American,1865,1936,1900–1908,1900,1908,Platinum print,,"Rogers Fund, 1972",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260131,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.633.124,false,true,260132,Photographs,Photograph,"H.R.H. King Friedrich August of Saxony, Taken Shortly Before Dethronement",,,,,,Artist,,Frank Eugene,"American, New York 1865–1936 Munich",,"Eugene, Frank",American,1865,1936,ca. 1913,1911,1915,Platinum print,,"Rogers Fund, 1972",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260132,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.633.125,false,true,260133,Photographs,Photograph,Dr. Paul Heyse,,,,,,Artist,,Frank Eugene,"American, New York 1865–1936 Munich",,"Eugene, Frank",American,1865,1936,1900–1914,1900,1914,Platinum print,,"Rogers Fund, 1972",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260133,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.633.126,false,true,260134,Photographs,Photograph,"Sigmund von Hausegger, Kapellmeister",,,,,,Artist,,Frank Eugene,"American, New York 1865–1936 Munich",,"Eugene, Frank",American,1865,1936,1910–24,1910,1924,Platinum print,,"Rogers Fund, 1972",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260134,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.633.127,false,true,260135,Photographs,Photograph,Professor Fritz von Miller,,,,,,Artist,,Frank Eugene,"American, New York 1865–1936 Munich",,"Eugene, Frank",American,1865,1936,1910–13,1910,1913,Platinum print,,"Rogers Fund, 1972",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260135,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.633.128,false,true,260136,Photographs,Photograph,Professor Georg Witkowski,,,,,,Artist,,Frank Eugene,"American, New York 1865–1936 Munich",,"Eugene, Frank",American,1865,1936,1900s,1900,1909,Platinum print,,"Rogers Fund, 1972",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260136,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.633.129,false,true,260137,Photographs,Photograph,Jesko von Puttkamer,,,,,,Artist,,Frank Eugene,"American, New York 1865–1936 Munich",,"Eugene, Frank",American,1865,1936,1910–23,1910,1923,Platinum print,,"Rogers Fund, 1972",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260137,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.633.131,false,true,260139,Photographs,Photograph,Joseph Pennell,,,,,,Artist,,Frank Eugene,"American, New York 1865–1936 Munich",,"Eugene, Frank",American,1865,1936,1910–24,1910,1924,Platinum print,,"Rogers Fund, 1972",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260139,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.633.132,false,true,260140,Photographs,Photograph,"Stieglitz, Steichen, Smith and Kuehn Admiring the Work of Frank Eugene",,,,,,Artist,,Frank Eugene,"American, New York 1865–1936 Munich",,"Eugene, Frank",American,1865,1936,1907,1907,1907,Platinum print,Image: 10.2 x 16.2 cm (4 x 6 3/8 in.) Mount: 25.2 x 17.5 cm (9 15/16 x 6 7/8 in.),"Rogers Fund, 1972",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260140,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.633.133,false,true,260141,Photographs,Photograph,Josef Geis as Beckmeser,,,,,,Artist,,Frank Eugene,"American, New York 1865–1936 Munich",,"Eugene, Frank",American,1865,1936,1900s,1900,1909,Platinum print,,"Rogers Fund, 1972",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260141,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.633.134,false,true,260142,Photographs,Photograph,[Farmyard],,,,,,Artist,,Frank Eugene,"American, New York 1865–1936 Munich",,"Eugene, Frank",American,1865,1936,1886,1886,1886,Gum bichromate print,14.3 x 19.4 cm (5 5/8 x 7 5/8 in.),"Rogers Fund, 1972",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260142,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.633.135,false,true,260143,Photographs,Photograph,[Landscape with River and Trees],,,,,,Artist,,Frank Eugene,"American, New York 1865–1936 Munich",,"Eugene, Frank",American,1865,1936,1886,1886,1886,Gum bichromate print,19.3 x 16.0 cm (7 5/8 x 6 5/16 in.),"Rogers Fund, 1972",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260143,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.633.137,false,true,260145,Photographs,Photograph,Frank Jefferson,,,,,,Artist,,Frank Eugene,"American, New York 1865–1936 Munich",,"Eugene, Frank",American,1865,1936,1898,1898,1898,Platinum print,Image: 16.8 × 11.8 cm (6 5/8 × 4 5/8 in.),"Rogers Fund, 1972",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260145,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.633.138,false,true,260146,Photographs,Photograph,The Man in Armor,,,,,,Artist,,Frank Eugene,"American, New York 1865–1936 Munich",,"Eugene, Frank",American,1865,1936,1898,1898,1898,Platinum print,17.6 x 12.9 cm (6 15/16 x 5 1/16 in.),"Rogers Fund, 1972",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260146,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.633.140,false,true,260149,Photographs,Photograph,Alfred Stieglitz,,,,,,Artist,,Frank Eugene,"American, New York 1865–1936 Munich",,"Eugene, Frank",American,1865,1936,1890s,1890,1899,Platinum print,16.4 x 11.0 cm (6 7/16 x 4 5/16 in.),"Rogers Fund, 1972",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260149,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.633.143,false,true,260152,Photographs,Photograph,Fredy,,,,,,Artist,,Frank Eugene,"American, New York 1865–1936 Munich",,"Eugene, Frank",American,1865,1936,1901,1901,1901,Platinum print,,"Rogers Fund, 1972",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260152,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.633.144,false,true,260153,Photographs,Photograph,Fredy,,,,,,Artist,,Frank Eugene,"American, New York 1865–1936 Munich",,"Eugene, Frank",American,1865,1936,1901,1901,1901,Platinum print,,"Rogers Fund, 1972",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260153,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.633.145,false,true,260154,Photographs,Photograph,Fredy,,,,,,Artist,,Frank Eugene,"American, New York 1865–1936 Munich",,"Eugene, Frank",American,1865,1936,1901,1901,1901,Platinum print,,"Rogers Fund, 1972",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260154,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.633.146,false,true,260155,Photographs,Photograph,Fredy,,,,,,Artist,,Frank Eugene,"American, New York 1865–1936 Munich",,"Eugene, Frank",American,1865,1936,1901,1901,1901,Platinum print,,"Rogers Fund, 1972",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260155,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.633.149,false,true,260158,Photographs,Photograph,Emanuel von Seidl,,,,,,Artist,,Frank Eugene,"American, New York 1865–1936 Munich",,"Eugene, Frank",American,1865,1936,1900–1908,1900,1908,Platinum print,,"Rogers Fund, 1972",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260158,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.633.150,false,true,260160,Photographs,Photograph,The Studio,,,,,,Artist,,Frank Eugene,"American, New York 1865–1936 Munich",,"Eugene, Frank",American,1865,1936,1910s,1910,1919,Platinum print,,"Rogers Fund, 1972",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260160,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.633.151,false,true,260161,Photographs,Postcard,The Studio,,,,,,Artist,,Frank Eugene,"American, New York 1865–1936 Munich",,"Eugene, Frank",American,1865,1936,1910s,1910,1919,Platinum print,,"Rogers Fund, 1972",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260161,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.633.154,false,true,260164,Photographs,Photograph,[Anna Königer Smith?],,,,,,Artist,,Frank Eugene,"American, New York 1865–1936 Munich",,"Eugene, Frank",American,1865,1936,1900s,1900,1909,Platinum print,,"Rogers Fund, 1972",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260164,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.633.155,false,true,260165,Photographs,Photograph,Gustel Königer,,,,,,Artist,,Frank Eugene,"American, New York 1865–1936 Munich",,"Eugene, Frank",American,1865,1936,1909,1909,1909,Platinum print,,"Rogers Fund, 1972",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260165,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.633.156,false,true,260166,Photographs,Photograph,Gustel Königer,,,,,,Artist,,Frank Eugene,"American, New York 1865–1936 Munich",,"Eugene, Frank",American,1865,1936,1909,1909,1909,Platinum print,,"Rogers Fund, 1972",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260166,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.633.157,false,true,260167,Photographs,Photograph,Anne,,,,,,Artist,,Frank Eugene,"American, New York 1865–1936 Munich",,"Eugene, Frank",American,1865,1936,1911,1911,1911,Platinum print,,"Rogers Fund, 1972",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260167,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.633.158,false,true,260168,Photographs,Photograph,Friedel Wearing a Kimono,,,,,,Artist,,Frank Eugene,"American, New York 1865–1936 Munich",,"Eugene, Frank",American,1865,1936,1911,1911,1911,Platinum print,,"Rogers Fund, 1972",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260168,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.633.159,false,true,260169,Photographs,Photograph,Friedel Wearing a Kimono,,,,,,Artist,,Frank Eugene,"American, New York 1865–1936 Munich",,"Eugene, Frank",American,1865,1936,1911,1911,1911,Platinum print,,"Rogers Fund, 1972",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260169,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.633.160,false,true,260171,Photographs,Photograph,Friedel Wearing a Kimono,,,,,,Artist,,Frank Eugene,"American, New York 1865–1936 Munich",,"Eugene, Frank",American,1865,1936,1911,1911,1911,Platinum print,,"Rogers Fund, 1972",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260171,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.633.161,false,true,260172,Photographs,Photograph,Ellen,,,,,,Artist,,Frank Eugene,"American, New York 1865–1936 Munich",,"Eugene, Frank",American,1865,1936,1909,1909,1909,Platinum print,,"Rogers Fund, 1972",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260172,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.633.162,false,true,260173,Photographs,Photograph,Anne Köninger and Frederick L. Smith,,,,,,Artist,,Frank Eugene,"American, New York 1865–1936 Munich",,"Eugene, Frank",American,1865,1936,1912,1912,1912,Platinum print,,"Rogers Fund, 1972",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260173,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.633.164,false,true,260175,Photographs,Photograph,Anne Königer and Frederick L. Smith,,,,,,Artist,,Frank Eugene,"American, New York 1865–1936 Munich",,"Eugene, Frank",American,1865,1936,1912,1912,1912,Platinum print,,"Rogers Fund, 1972",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260175,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.633.166,false,true,260177,Photographs,Photograph,Anne Königer and Frederick L. Smith,,,,,,Artist,,Frank Eugene,"American, New York 1865–1936 Munich",,"Eugene, Frank",American,1865,1936,1912,1912,1912,Platinum print,,"Rogers Fund, 1972",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260177,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.633.167,false,true,260178,Photographs,Photograph,Anne Königer and Frederick L. Smith,,,,,,Artist,,Frank Eugene,"American, New York 1865–1936 Munich",,"Eugene, Frank",American,1865,1936,1912,1912,1912,Platinum print,,"Rogers Fund, 1972",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260178,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.633.168,false,true,260179,Photographs,Photograph,Kal Smith,,,,,,Artist,,Frank Eugene,"American, New York 1865–1936 Munich",,"Eugene, Frank",American,1865,1936,1912,1912,1912,Platinum print,17.0 x 11.8 cm (6 11/16 x 4 5/8 in.),"Rogers Fund, 1972",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260179,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.633.169,false,true,260180,Photographs,Photograph,Kal Smith,,,,,,Artist,,Frank Eugene,"American, New York 1865–1936 Munich",,"Eugene, Frank",American,1865,1936,1912,1912,1912,Platinum print,,"Rogers Fund, 1972",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260180,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.633.170,false,true,260182,Photographs,Photograph,"Count and Countes LaRosée, Bride and Bridegroom",,,,,,Artist,,Frank Eugene,"American, New York 1865–1936 Munich",,"Eugene, Frank",American,1865,1936,1900–1911,1900,1911,Platinum print,,"Rogers Fund, 1972",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260182,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.633.171,false,true,260183,Photographs,Photograph,Johanna,,,,,,Artist,,Frank Eugene,"American, New York 1865–1936 Munich",,"Eugene, Frank",American,1865,1936,1919,1919,1919,Platinum print,,"Rogers Fund, 1972",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260183,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.633.172,false,true,260184,Photographs,Photograph,Johanna,,,,,,Artist,,Frank Eugene,"American, New York 1865–1936 Munich",,"Eugene, Frank",American,1865,1936,1921,1921,1921,Platinum print,,"Rogers Fund, 1972",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260184,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.633.173,false,true,260185,Photographs,Photograph,Frank Eugene with Herr von Martine and Others,,,,,,Artist,,Frank Eugene,"American, New York 1865–1936 Munich",,"Eugene, Frank",American,1865,1936,1920,1920,1920,Platinum print,,"Rogers Fund, 1972",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260185,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.633.174,false,true,260186,Photographs,Photograph,Self-Portrait with Dolls,,,,,,Artist,,Frank Eugene,"American, New York 1865–1936 Munich",,"Eugene, Frank",American,1865,1936,1923,1923,1923,Platinum print,,"Rogers Fund, 1972",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260186,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.633.175,false,true,260187,Photographs,Photograph,"Learning How to ""Sit Up!""",,,,,,Artist,,Frank Eugene,"American, New York 1865–1936 Munich",,"Eugene, Frank",American,1865,1936,1900s,1900,1909,Platinum print,Image: 9.5 x 4.4 cm (3 3/4 x 1 3/4 in.),"Rogers Fund, 1972",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260187,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.633.177,false,true,260189,Photographs,Photograph,Self-Portrait,,,,,,Artist,,Frank Eugene,"American, New York 1865–1936 Munich",,"Eugene, Frank",American,1865,1936,1924,1924,1924,Platinum print,,"Rogers Fund, 1972",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260189,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.633.179,false,true,260191,Photographs,Photograph,"Mein Grossvater Selinger, 93 Jahre",,,,,,Artist,,Frank Eugene,"American, New York 1865–1936 Munich",,"Eugene, Frank",American,1865,1936,1921,1921,1921,Platinum print,,"Rogers Fund, 1972",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260191,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.633.191,false,true,260205,Photographs,Photograph,Frank Eugene,,,,,,Artist,,Frank Eugene,"American, New York 1865–1936 Munich",,"Eugene, Frank",American,1865,1936,1936,1936,1936,Platinum print,,"Rogers Fund, 1972",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260205,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.633.192,false,true,260206,Photographs,Photograph,Frank Eugene,,,,,,Artist,,Frank Eugene,"American, New York 1865–1936 Munich",,"Eugene, Frank",American,1865,1936,1936,1936,1936,Platinum print,,"Rogers Fund, 1972",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260206,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.633.195,false,true,260209,Photographs,Photograph,House and Church in Germany,,,,,,Artist,,Frank Eugene,"American, New York 1865–1936 Munich",,"Eugene, Frank",American,1865,1936,1920,1920,1920,Platinum print,,"Rogers Fund, 1972",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260209,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.633.196,false,true,260210,Photographs,Photograph,Anne Königer Smith,,,,,,Artist,,Frank Eugene,"American, New York 1865–1936 Munich",,"Eugene, Frank",American,1865,1936,1912,1912,1912,Platinum print,,"Rogers Fund, 1972",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260210,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.633.197,false,true,260211,Photographs,Photograph,Frederick L. Smith,,,,,,Artist,,Frank Eugene,"American, New York 1865–1936 Munich",,"Eugene, Frank",American,1865,1936,ca. 1915,1915,1915,Platinum print,16.2 x 11.3 cm (6 3/8 x 4 7/16 in.),"Rogers Fund, 1972",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/260211,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.721,false,true,286684,Photographs,Photograph,The Great White Cloud,,,,,,Artist,,Frank Eugene,"American, New York 1865–1936 Munich",,"Eugene, Frank",American,1865,1936,ca. 1910,1908,1912,Platinum print,Image: 11.6 × 16.1 cm (4 9/16 × 6 5/16 in.) Mount: 34 × 26.9 cm (13 3/8 × 10 9/16 in.),"Gilman Collection, Purchase, Sam Salz Foundation Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286684,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -53.679.1805,false,true,269660,Photographs,Photograph,Apple Blossoms,,,,,,Artist,,Louis Comfort Tiffany,"American, New York 1848–1933 New York",,"Tiffany, Louis Comfort",American,1848,1933,1890s–1900s,1890,1909,Albumen silver print,,"The Elisha Whittelsey Collection, The Elisha Whittelsey Fund, 1953",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269660,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2010.34,false,true,291651,Photographs,Photograph,"Artillery, Quartermaster Sergeant",,,,,,Artist,Attributed to,Oliver H. Willard,"American, active 1850s–70s, died 1875",,"Willard, Oliver H.",American,1775,1875,1866,1866,1866,Albumen silver print from glass negative,Image: 20.3 × 14.8 cm (8 × 5 13/16 in.) Mount: 33.3 x 25.9 cm (13 1/8 x 10 3/16 in.),"The Horace W. Goldsmith Foundation Fund, through Joyce and Robert Menschel, 2010",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/291651,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2010.35,false,true,291652,Photographs,Photograph,"Artillery, Musician",,,,,,Artist,Attributed to,Oliver H. Willard,"American, active 1850s–70s, died 1875",,"Willard, Oliver H.",American,1775,1875,1866,1866,1866,Albumen silver print from glass negative,Image: 19.9 x 14.9 cm (7 13/16 x 5 7/8 in.) Mount: 33.3 x 25.9 cm (13 1/8 x 10 3/16 in.),"The Horace W. Goldsmith Foundation Fund, through Joyce and Robert Menschel, 2010",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/291652,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2010.36,false,true,291653,Photographs,Photograph,"Light Artillery, Sergeant Major",,,,,,Artist,Attributed to,Oliver H. Willard,"American, active 1850s–70s, died 1875",,"Willard, Oliver H.",American,1775,1875,1866,1866,1866,Albumen silver print from glass negative,Image: 20.3 x 15.2 cm (8 x 6 in.) Mount: 33.3 x 25.7 cm (13 1/8 x 10 1/8 in.),"Purchase, Saundra B. Lane Gift, in honor of Charles Isaacs, 2010",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/291653,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2010.37,false,true,291654,Photographs,Photograph,"Ordnance, Private",,,,,,Artist,Attributed to,Oliver H. Willard,"American, active 1850s–70s, died 1875",,"Willard, Oliver H.",American,1775,1875,1866,1866,1866,Albumen silver print from glass negative,Image: 20.2 x 15 cm (7 15/16 x 5 7/8 in.) Mount: 33.3 x 25.9 cm (13 1/8 x 10 3/16 in.),"The Horace W. Goldsmith Foundation Fund, through Joyce and Robert Menschel, 2010",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/291654,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2010.38,false,true,291655,Photographs,Photograph,"Fatigue, Marching Order",,,,,,Artist,Attributed to,Oliver H. Willard,"American, active 1850s–70s, died 1875",,"Willard, Oliver H.",American,1775,1875,1866,1866,1866,Albumen silver print from glass negative,Image: 20.1 x 15 cm (7 15/16 x 5 7/8 in.) Mount: 33.2 x 25.7 cm (13 1/16 x 10 1/8 in.),"The Horace W. Goldsmith Foundation Fund, through Joyce and Robert Menschel, 2010",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/291655,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1999.185,false,true,282767,Photographs,Photograph,[Man Holding Patent Office Book],,,,,,Artist,Attributed to,Oliver H. Willard,"American, active 1850s–70s, died 1875",,"Willard, Oliver H.",American,1775,1875,ca. 1857,1856,1858,Salted paper print from glass negative,21.6 x 16.5 cm (8 1/2 x 6 1/2 in. ),"Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 1999",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/282767,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -X.676,false,true,271672,Photographs,Cabinet card,[The British Soprano Euphrosyne Parepa-Rosa (1836-1874)],,,,,,Artist,,Jeremiah Gurney,"American, 1812–1895 Coxsackie, New York",,"Gurney, Jeremiah",American,1812,1895-04-21,1870s,1870,1879,Albumen silver print from glass negative,13.3 x 8.3 cm (5 1/4 x 3 1/4 in. ),Museum Accession,,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/271672,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -X.701.4,false,true,271712,Photographs,Cabinet card,"[Wohes Family, New York]",,,,,,Artist,,Jeremiah Gurney,"American, 1812–1895 Coxsackie, New York",,"Gurney, Jeremiah",American,1812,1895-04-21,1870s,1870,1870,Albumen silver print from glass negative,13.7 x 9.2 cm. (5 3/8 x 3 5/8 in.),Museum Accession,,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/271712,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.400.23,false,true,291776,Photographs,Daguerreotype,[Pair of Portraits of Man and Woman (Husband and Wife?)],,,,,,Artist,,Jeremiah Gurney,"American, 1812–1895 Coxsackie, New York",,"Gurney, Jeremiah",American,1812,1895-04-21,1852–60,1852,1860,Daguerreotype,"Image: 9 x 6.4 cm (3 9/16 x 2 1/2 in.), each Plate: 10.8 x 8.3 cm (4 1/4 x 3 1/4 in.), each Case: 1.9 x 11.9 x 9.5 cm (3/4 x 4 11/16 x 3 3/4 in.)","Bequest of Herbert Mitchell, 2008",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/291776,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.325,false,true,285472,Photographs,Photograph,[Two Girls in Identical Dresses],,,,,,Artist,,Jeremiah Gurney,"American, 1812–1895 Coxsackie, New York",,"Gurney, Jeremiah",American,1812,1895-04-21,ca. 1857,1852,1863,Daguerreotype,Image: 11.3 x 8.2 cm (4 7/16 x 3 1/4 in.),"Gilman Collection, Purchase, Marlene Nathan Meyerson Family Foundation Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285472,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.1136,false,true,286062,Photographs,carte-de-visite,"James B. ""Wild Bill"" Hickock",,,,,,Artist,,Jeremiah Gurney,"American, 1812–1895 Coxsackie, New York",,"Gurney, Jeremiah",American,1812,1895-04-21,ca. 1873,1871,1875,Albumen silver print from glass negative,Image: 3 5/8 × 2 1/4 in. (9.2 × 5.7 cm) Mount: 4 1/8 × 2 1/2 in. (10.4 × 6.3 cm),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286062,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.48,false,true,301954,Photographs,Photograph,"[Camp Scene with Soldiers of the 22nd New York State Militia, Harper's Ferry, Virginia]",,,,,,Artist,,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1862,1862,1862,Albumen silver print from glass negative,Image: 8.7 x 5.8 cm (3 7/16 x 2 5/16 in.) Mount: 10.2 x 6.1 cm (4 x 2 3/8 in.),"Purchase, Alfred Stieglitz Society Gifts, 2013",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/301954,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -56.517.4,false,true,269852,Photographs,Photograph,President Martin Van Buren,,,,,,Artist,,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1855–58,1855,1858,Salted paper print from glass negative,48.3 x 39.7 cm (19 x 15 5/8 in.),"David Hunter McAlpin Fund, 1956",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269852,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1997.382.48,false,true,282053,Photographs,Photograph,Grenville Kane,,,,,,Artist,,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,late 1850s,1857,1859,Ambrotype,visible: 12.2 x 8.9 cm (4 13/16 x 3 1/2 in.),"The Rubel Collection, Purchase, Lila Acheson Wallace Gift, 1997",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/282053,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.84,false,true,283184,Photographs,Photograph,[Commodore Matthew Calbraith Perry],,,,,,Artist,,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1856–58,1856,1858,Salted paper print from glass negative,Image : 33.1 x 28.4cm (13 1/16 x 11 3/16in.) Mount: 18 1/8 in. × 14 in. (46 × 35.5 cm),"Gilman Collection, Purchase, Joseph M. Cohen Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/283184,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.85,false,true,283186,Photographs,Photograph,[Portrait of a Man],,,,,,Artist,,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,ca. 1857,1855,1859,Salted paper print from glass negative,Mount: 11 3/16 in. × 9 1/8 in. (28.4 × 23.2 cm) Image: 9 15/16 × 7 9/16 in. (25.3 × 19.2 cm),"Gilman Collection, Purchase, Harriette and Noel Levine Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/283186,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.280,false,true,286046,Photographs,Photographs,Lilliputian Souvenir,,,,,,Artist,,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1862–63,1862,1863,Albumen silver print from glass negative,Image: 7.7 x 4.5 cm (3 1/16 x 1 3/4 in.) each Mount: 8.8 x 5.2 cm (3 7/16 x 2 1/16 in.) each Mount (2nd): 51.7 x 41.7 cm (20 3/8 x 16 7/16 in.),"Gilman Collection, Purchase, Ann Tenenbaum and Thomas H. Lee Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286046,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.496,false,true,286624,Photographs,Photograph,Peter Force,,,,,,Artist,,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,ca. 1858,1856,1860,Salted paper print from glass negative,Image: 34.4 × 27.2 cm (13 9/16 × 10 11/16 in.) Mount: 50.4 × 47.3 cm (19 13/16 × 18 5/8 in.),"Gilman Collection, Purchase, Joseph M. Cohen Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286624,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.567,false,true,285877,Photographs,Photograph,Madame Medori,,,,,,Artist,,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,ca. 1857,1855,1859,Salted paper print from glass negative,Image: 20.6 x 15.6 cm (8 1/8 x 6 1/8 in.),"Gilman Collection, Purchase, Mrs. Walter Annenberg and The Annenberg Foundation Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285877,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.754,false,true,286586,Photographs,Cabinet card,Frederick Douglass,,,,,,Artist,,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,ca. 1880,1878,1882,Albumen silver print from glass negative,Image: 14.7 × 10.2 cm (5 13/16 × 4 in.) Mount: 16.5 × 10.8 cm (6 1/2 × 4 1/4 in.),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286586,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.1108,false,true,286620,Photographs,Photograph,"Lieutenent General Scott, General-in-Chief U.S. Army, & Staff",,,,,,Artist,,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,"September 6, 1861",1861,1861,Albumen silver print from glass negative,Image: 26.4 × 36.5 cm (10 3/8 × 14 3/8 in.) Mount: 35.6 × 43.1 cm (14 × 16 15/16 in.),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286620,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.1115,false,true,286097,Photographs,Photograph,"[Japanese Embassy, Navy Yard, Washington, DC]",,,,,,Artist,,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1860,1860,1860,Albumen silver print from glass negative,Image: 37.5 x 48.1 cm (14 3/4 x 18 15/16 in.) Mount: 41.8 x 49.6 cm (16 7/16 x 19 1/2 in.),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286097,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.1125,false,true,285889,Photographs,Photograph,"Second Corps Hospital, Washington, D.C.",,,,,,Artist,,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,ca. 1863,1861,1865,Albumen silver print from glass negative,Image: 7.7 × 9.7 cm (3 1/16 × 3 13/16 in.) Mount: 22.9 × 26.8 cm (9 × 10 9/16 in.),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285889,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.1129,false,true,285835,Photographs,Photograph,"Fortifications, Manassas, Occupied by 13th Mass.",,,,,,Artist,,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1862,1862,1862,Albumen silver print from glass negative,Image: 7 5/16 × 9 1/4 in. (18.6 × 23.5 cm) Mount: 10 5/8 × 13 5/8 in. (27 × 34.6 cm),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285835,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.1130,false,true,286262,Photographs,Photograph,Ruins of Stone Bridge - Bull Run,,,,,,Artist,,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1862,1862,1862,Albumen silver print from glass negative,Image: 18.8 × 23.1 cm (7 3/8 × 9 1/8 in.) Mount: 26.9 × 34.7 cm (10 9/16 × 13 11/16 in.),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286262,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.1131,false,true,285834,Photographs,Photograph,Stone Bridge - Bull Run,,,,,,Artist,,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1862,1862,1862,Albumen silver print from glass negative,Image: 18.4 × 23.1 cm (7 1/4 × 9 1/8 in.) Mount: 27 × 34.7 cm (10 5/8 × 13 11/16 in.),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285834,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.1213,false,true,286582,Photographs,Photograph,General Robert E. Lee,,,,,,Artist,,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1865,1865,1865,Albumen silver print from glass negative,Image: 14 × 9.3 cm (5 1/2 × 3 11/16 in.),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286582,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.1226,false,true,286343,Photographs,Photograph,Major General David E. Twiggs,,,,,,Artist,,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,ca. 1859,1857,1861,Salted paper print from glass negative,Image: 22.5 × 14.9 cm (8 7/8 × 5 7/8 in.),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286343,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.1233,false,true,286629,Photographs,Photograph,Edward Everett,,,,,,Artist,,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,ca. 1858,1853,1863,Salted paper print from glass negative,Image: 47.1 × 38.5 cm (18 9/16 × 15 3/16 in.),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286629,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.1234,false,true,285959,Photographs,Photograph,General William Ward and Staff,,,,,,Artist,,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,ca. 1861,1856,1866,Albumen silver print from glass negative,Image: 24.6 × 19 cm (9 11/16 × 7 1/2 in.),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285959,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.1218a,false,true,286612,Photographs,Photographs,Robert E. Lee,,,,,,Artist,,Mathew B. Brady,"American, born Ireland, 1823?–1896 New York",,"Brady, Mathew B.",American,1823,1896,1869,1869,1869,Albumen silver print from glass negative,"Mount: 32.1 × 30.6 cm (12 5/8 × 12 1/16 in.) Image: 20.6 × 15.6 cm (8 1/8 × 6 1/8 in.), oval","Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286612,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -28.126,false,true,267441,Photographs,Multiple exposure; Photograph,Dorothy True,,,,,,Artist,,Alfred Stieglitz,"American, Hoboken, New Jersey 1864–1946 New York",,"Stieglitz, Alfred",American,1864,1946,1919,1919,1919,Gelatin silver print,24.3 x 19.3 cm (9 9/16 x 7 5/8 in.),"Gift of Paul Rosenfeld, 1928",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267441,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -28.129,false,true,267460,Photographs,Photograph,Georgia O'Keeffe – Hands and Thimble,,,,,,Artist,,Alfred Stieglitz,"American, Hoboken, New Jersey 1864–1946 New York",,"Stieglitz, Alfred",American,1864,1946,1919,1919,1919,Palladium print,23.5 x 18.4 cm (9 1/4 x 7 1/4 in.),"Gift of Mrs. Rebecca S. Strand, 1928",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267460,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -49.55.6,false,true,269458,Photographs,Photograph,The Terminal,,,,,,Artist,,Alfred Stieglitz,"American, Hoboken, New Jersey 1864–1946 New York",,"Stieglitz, Alfred",American,1864,1946,"1893, printed 1920s–30s",1893,1893,Gelatin silver print,8.9 x 11.5 cm (3 1/2 x 4 1/2 in.),"Alfred Stieglitz Collection, 1949",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269458,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -49.55.7,false,true,269459,Photographs,Photograph,An Icy Night,,,,,,Artist,,Alfred Stieglitz,"American, Hoboken, New Jersey 1864–1946 New York",,"Stieglitz, Alfred",American,1864,1946,"1898, printed 1920–1939",1898,1898,Gelatin silver print from glass negative,9.2 x 11.8 cm. (3 5/8 x 4 5/8 in.),"Alfred Stieglitz Collection, 1949",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269459,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -49.55.8,false,true,269460,Photographs,Photograph,"The Street, Fifth Avenue",,,,,,Artist,,Alfred Stieglitz,"American, Hoboken, New Jersey 1864–1946 New York",,"Stieglitz, Alfred",American,1864,1946,"1900–1901, printed 1903–4",1900,1901,Photogravure,30.6 x 23.3 cm. (12 1/16 x 9 3/16 in.),"Alfred Stieglitz Collection, 1949",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269460,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -49.55.9,false,true,269461,Photographs,Photograph,The Hand of Man,,,,,,Artist,,Alfred Stieglitz,"American, Hoboken, New Jersey 1864–1946 New York",,"Stieglitz, Alfred",American,1864,1946,"1902, printed 1910",1902,1902,Photogravure,24.2 x 31.9 cm (9 1/2 x 12 9/16 in.),"Alfred Stieglitz Collection, 1949",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269461,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.469,false,true,288987,Photographs,Photograph,Margaret Treadwell,,,,,,Artist,,Alfred Stieglitz,"American, Hoboken, New Jersey 1864–1946 New York",,"Stieglitz, Alfred",American,1864,1946,1921,1921,1921,Platinum-palladium print,25.4 x 20.3 cm (10 x 8 in. ),"Gift of John Pritzker, 2007",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288987,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -28.127.1,false,true,267442,Photographs,Photograph,Georgia O'Keeffe,,,,,,Artist,,Alfred Stieglitz,"American, Hoboken, New Jersey 1864–1946 New York",,"Stieglitz, Alfred",American,1864,1946,1918,1918,1918,Platinum-palladium print,24.5 x 19.6 cm (9 5/8 x 7 11/16 in. ),"Gift of David A. Schulte, 1928",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267442,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -28.127.6,false,true,267447,Photographs,Photograph,"Music – A Sequence of Ten Cloud Photographs, No. 1",,,,,,Artist,,Alfred Stieglitz,"American, Hoboken, New Jersey 1864–1946 New York",,"Stieglitz, Alfred",American,1864,1946,1922,1922,1922,Platinum print,19.2 x 24.1 cm. (7 9/16 x 9 1/2 in.),"Gift of David A. Schulte, 1928",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267447,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -28.127.7,false,true,267448,Photographs,Photograph,The Dancing Trees,,,,,,Artist,,Alfred Stieglitz,"American, Hoboken, New Jersey 1864–1946 New York",,"Stieglitz, Alfred",American,1864,1946,1922,1922,1922,Palladium print,24.2 x 19.3 cm (9 1/2 x 7 5/8 in.),"Gift of David A. Schulte, 1928",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267448,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -28.128.4,false,true,267454,Photographs,Photograph,Equivalent,,,,,,Artist,,Alfred Stieglitz,"American, Hoboken, New Jersey 1864–1946 New York",,"Stieglitz, Alfred",American,1864,1946,1925,1925,1925,Gelatin silver print,11.8 x 9.2 cm (4 5/8 x 3 5/8 in.),"Alfred Stieglitz Collection, 1928",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267454,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -28.128.5,false,true,267455,Photographs,Photograph,Songs of the Sky,,,,,,Artist,,Alfred Stieglitz,"American, Hoboken, New Jersey 1864–1946 New York",,"Stieglitz, Alfred",American,1864,1946,1924,1924,1924,Gelatin silver print,9.2 x 11.8 cm (3 5/8 x 4 5/8 in.),"Alfred Stieglitz Collection, 1928",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267455,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -28.128.7,false,true,267457,Photographs,Photograph,Equivalent,,,,,,Artist,,Alfred Stieglitz,"American, Hoboken, New Jersey 1864–1946 New York",,"Stieglitz, Alfred",American,1864,1946,1925,1925,1925,Gelatin silver print,11.8 x 9.2 cm (4 5/8 x 3 5/8 in.),"Alfred Stieglitz Collection, 1928",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267457,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -28.128.8,false,true,267458,Photographs,Photograph,Equivalent,,,,,,Artist,,Alfred Stieglitz,"American, Hoboken, New Jersey 1864–1946 New York",,"Stieglitz, Alfred",American,1864,1946,1925,1925,1925,Gelatin silver print,9.3 x 11.9 cm.(3 11/16 x 4 11/16 in.),"Alfred Stieglitz Collection, 1928",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267458,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -28.128.9,false,true,267459,Photographs,Photograph,Equivalent,,,,,,Artist,,Alfred Stieglitz,"American, Hoboken, New Jersey 1864–1946 New York",,"Stieglitz, Alfred",American,1864,1946,1925,1925,1925,Gelatin silver print,11.9 x 9.1 cm (4 11/16 x 3 9/16 in.),"Alfred Stieglitz Collection, 1928",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267459,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -28.130.1,false,true,267461,Photographs,Photograph,Georgia O'Keeffe — Hand and Breasts,,,,,,Artist,,Alfred Stieglitz,"American, Hoboken, New Jersey 1864–1946 New York",,"Stieglitz, Alfred",American,1864,1946,1919,1919,1919,Palladium print,18.2 x 23.1 cm (7 3/16 x 9 1/8 in. ),"Gift of Mrs. Alma Wertheim, 1928",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267461,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -28.130.2,false,true,267462,Photographs,Photograph,Georgia O'Keeffe—Torso,,,,,,Artist,,Alfred Stieglitz,"American, Hoboken, New Jersey 1864–1946 New York",,"Stieglitz, Alfred",American,1864,1946,1918,1918,1918,Gelatin silver print,23.6 x 18.8 cm (9 5/16 x 7 3/8 in.),"Gift of Mrs. Alma Wertheim, 1928",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267462,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -49.55.12,false,true,269280,Photographs,Photograph,After Working Hours - The Ferry Boat,,,,,,Artist,,Alfred Stieglitz,"American, Hoboken, New Jersey 1864–1946 New York",,"Stieglitz, Alfred",American,1864,1946,"1910, printed in or before 1913",1910,1910,Photogravure,33.2 x 25.9 cm. (13 1/16 x 10 3/16 in.),"Alfred Stieglitz Collection, 1949",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269280,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -49.55.13,false,true,269281,Photographs,Photograph,The Steerage,,,,,,Artist,,Alfred Stieglitz,"American, Hoboken, New Jersey 1864–1946 New York",,"Stieglitz, Alfred",American,1864,1946,"1907, printed 1915",1907,1907,Photogravure,32.2 x 25.8 cm. (12 11/16 x 10 3/16 in.),"Alfred Stieglitz Collection, 1949",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269281,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -49.55.15,false,true,269283,Photographs,Photograph,The City of Ambitions,,,,,,Artist,,Alfred Stieglitz,"American, Hoboken, New Jersey 1864–1946 New York",,"Stieglitz, Alfred",American,1864,1946,"1910, printed 1910–13",1910,1910,Photogravure,33.8 x 26.0 cm (13 5/16 x 10 1/4 in.),"Alfred Stieglitz Collection, 1949",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269283,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -49.55.16,false,true,269284,Photographs,Photograph,The Flatiron,,,,,,Artist,,Alfred Stieglitz,"American, Hoboken, New Jersey 1864–1946 New York",,"Stieglitz, Alfred",American,1864,1946,"1903, printed in or before 1910",1903,1903,Photogravure,32.8 x 16.7 cm (12 15/16 x 6 9/16 in.),"Alfred Stieglitz Collection, 1949",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269284,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -49.55.17,false,true,269290,Photographs,Photograph,Old and New New York,,,,,,Artist,,Alfred Stieglitz,"American, Hoboken, New Jersey 1864–1946 New York",,"Stieglitz, Alfred",American,1864,1946,"1910, printed in or before 1913",1910,1910,Photogravure,33.2 x 25.5 cm (13 1/16 x 10 1/16 in.),"Alfred Stieglitz Collection, 1949",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269290,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -49.55.18,false,true,269300,Photographs,Photograph,The Terminal,,,,,,Artist,,Alfred Stieglitz,"American, Hoboken, New Jersey 1864–1946 New York",,"Stieglitz, Alfred",American,1864,1946,"1893, printed 1913 or before",1893,1893,Photogravure,25.5 x 33.5 cm (10 1/16 x 13 3/16 in.),"Alfred Stieglitz Collection, 1949",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269300,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -49.55.21,false,true,269333,Photographs,Photograph,Shadows in Lake,,,,,,Artist,,Alfred Stieglitz,"American, Hoboken, New Jersey 1864–1946 New York",,"Stieglitz, Alfred",American,1864,1946,1916,1916,1916,Gelatin silver print,11.5 x 9.1 cm (4 1/2 x 3 9/16 in.),"Alfred Stieglitz Collection, 1949",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269333,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -49.55.22,false,true,269344,Photographs,Photograph,Rebecca Salsbury Strand,,,,,,Artist,,Alfred Stieglitz,"American, Hoboken, New Jersey 1864–1946 New York",,"Stieglitz, Alfred",American,1864,1946,1922,1922,1922,Gelatin silver print,9.3 x 11.5 cm. (3 11/16 x 4 1/2 in.),"Alfred Stieglitz Collection, 1949",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269344,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -49.55.23,false,true,269355,Photographs,Photograph,Katherine,,,,,,Artist,,Alfred Stieglitz,"American, Hoboken, New Jersey 1864–1946 New York",,"Stieglitz, Alfred",American,1864,1946,1921,1921,1921,Gelatin silver print,10.4 x 8.2 cm. (4 1/16 x 3 1/4 in.),"Alfred Stieglitz Collection, 1949",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269355,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -49.55.24,false,true,269360,Photographs,Photograph,Spiritual America,,,,,,Artist,,Alfred Stieglitz,"American, Hoboken, New Jersey 1864–1946 New York",,"Stieglitz, Alfred",American,1864,1946,1923,1923,1923,Gelatin silver print,11.6 x 9.2 cm. (4 9/16 x 3 5/8 in.),"Alfred Stieglitz Collection, 1949",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269360,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -49.55.26,false,true,269376,Photographs,Photograph,Equivalent,,,,,,Artist,,Alfred Stieglitz,"American, Hoboken, New Jersey 1864–1946 New York",,"Stieglitz, Alfred",American,1864,1946,1926,1926,1926,Gelatin silver print,11.6 x 9.2 cm (4 9/16 x 3 5/8 in.),"Alfred Stieglitz Collection, 1949",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269376,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -49.55.29,false,true,269404,Photographs,Photograph,Equivalent,,,,,,Artist,,Alfred Stieglitz,"American, Hoboken, New Jersey 1864–1946 New York",,"Stieglitz, Alfred",American,1864,1946,1926,1926,1926,Gelatin silver print,11.8 x 9.2 cm (4 5/8 x 3 5/8 in.),"Alfred Stieglitz Collection, 1949",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269404,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -49.55.30,false,true,269408,Photographs,Photograph,"Equivalent, Set C2 No. 1",,,,,,Artist,,Alfred Stieglitz,"American, Hoboken, New Jersey 1864–1946 New York",,"Stieglitz, Alfred",American,1864,1946,1929,1929,1929,Gelatin silver print,11.6 x 9.4 cm. (4 9/16 x 3 11/16 in.),"Alfred Stieglitz Collection, 1949",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269408,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -49.55.31,false,true,269419,Photographs,Photograph,"Equivalent, Set C2 No. 2",,,,,,Artist,,Alfred Stieglitz,"American, Hoboken, New Jersey 1864–1946 New York",,"Stieglitz, Alfred",American,1864,1946,1929,1929,1929,Gelatin silver print,11.7 x 9.3 cm (4 5/8 x 3 11/16 in.),"Alfred Stieglitz Collection, 1949",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269419,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -49.55.33,false,true,269438,Photographs,Photograph,"Equivalent, Set C2 No. 4",,,,,,Artist,,Alfred Stieglitz,"American, Hoboken, New Jersey 1864–1946 New York",,"Stieglitz, Alfred",American,1864,1946,1929,1929,1929,Gelatin silver print,11.8 x 9.4 cm. (4 5/8 x 3 11/16 in.),"Alfred Stieglitz Collection, 1949",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269438,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -49.55.34,false,true,269441,Photographs,Photograph,"Equivalent, Set C2 No. 5",,,,,,Artist,,Alfred Stieglitz,"American, Hoboken, New Jersey 1864–1946 New York",,"Stieglitz, Alfred",American,1864,1946,1929,1929,1929,Gelatin silver print,11.7 x 9.3 cm. (4 5/8 x 3 11/16 in.),"Alfred Stieglitz Collection, 1949",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269441,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -49.55.35,true,true,269442,Photographs,Photograph,From the Back Window – 291,,,,,,Artist,,Alfred Stieglitz,"American, Hoboken, New Jersey 1864–1946 New York",,"Stieglitz, Alfred",American,1864,1946,1915,1915,1915,Platinum print,25.1 x 20.2 cm (9 7/8 x 7 15/16 in.),"Alfred Stieglitz Collection, 1949",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269442,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -49.55.36,false,true,269443,Photographs,Photograph,291 – Picasso-Braque Exhibition,,,,,,Artist,,Alfred Stieglitz,"American, Hoboken, New Jersey 1864–1946 New York",,"Stieglitz, Alfred",American,1864,1946,1915,1915,1915,Platinum print,19.4 x 24.4 cm (7 5/8 x 9 5/8 in.),"Alfred Stieglitz Collection, 1949",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269443,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -49.55.37,false,true,269444,Photographs,Photograph,Leo Stein,,,,,,Artist,,Alfred Stieglitz,"American, Hoboken, New Jersey 1864–1946 New York",,"Stieglitz, Alfred",American,1864,1946,1917,1917,1917,Platinum print,24.6 x 19.7 cm. (9 11/16 x 7 3/4 in.),"Alfred Stieglitz Collection, 1949",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269444,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -49.55.38,false,true,269445,Photographs,Photograph,Hodge Kirnon,,,,,,Artist,,Alfred Stieglitz,"American, Hoboken, New Jersey 1864–1946 New York",,"Stieglitz, Alfred",American,1864,1946,1917,1917,1917,Palladium print,24.6 x 19.9 cm. (9 11/16 x 7 13/16 in.),"Alfred Stieglitz Collection, 1949",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269445,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -49.55.39,false,true,269446,Photographs,Photograph,John Marin,,,,,,Artist,,Alfred Stieglitz,"American, Hoboken, New Jersey 1864–1946 New York",,"Stieglitz, Alfred",American,1864,1946,1921–22,1921,1922,Palladium print,24.2 x 19.3 cm. (9 1/2 x 7 5/8 in.),"Alfred Stieglitz Collection, 1949",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269446,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -49.55.40,false,true,269448,Photographs,Photograph,Katherine Dudley,,,,,,Artist,,Alfred Stieglitz,"American, Hoboken, New Jersey 1864–1946 New York",,"Stieglitz, Alfred",American,1864,1946,1922,1922,1922,Gelatin silver print,24.7 x 19.4 cm. (9 3/4 x 7 5/8 in.),"Alfred Stieglitz Collection, 1949",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269448,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -49.55.41,false,true,269449,Photographs,Photograph,House and Grape Leaves,,,,,,Artist,,Alfred Stieglitz,"American, Hoboken, New Jersey 1864–1946 New York",,"Stieglitz, Alfred",American,1864,1946,1934,1934,1934,Gelatin silver print,24.2 x 19.3 cm (9 1/2 x 7 5/8 in.),"Alfred Stieglitz Collection, 1949",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269449,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -49.55.44,false,true,269452,Photographs,Photograph,Equivalent 27C,,,,,,Artist,,Alfred Stieglitz,"American, Hoboken, New Jersey 1864–1946 New York",,"Stieglitz, Alfred",American,1864,1946,1933,1933,1933,Gelatin silver print,24.0 x 19.1 cm. (9 7/16 x 7 1/2 in.),"Alfred Stieglitz Collection, 1949",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269452,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -49.55.45,false,true,269453,Photographs,Photograph,"From My Window at the Shelton, West",,,,,,Artist,,Alfred Stieglitz,"American, Hoboken, New Jersey 1864–1946 New York",,"Stieglitz, Alfred",American,1864,1946,1931,1931,1931,Gelatin silver print,24.2 x 19.1 cm (9 1/2 x 7 1/2 in.),"Alfred Stieglitz Collection, 1949",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269453,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -49.55.46,false,true,269454,Photographs,Photograph,"From My Window at An American Place, North",,,,,,Artist,,Alfred Stieglitz,"American, Hoboken, New Jersey 1864–1946 New York",,"Stieglitz, Alfred",American,1864,1946,1931,1931,1931,Gelatin silver print,18.9 x 24.0 cm (7 7/16 x 9 7/16 in.),"Alfred Stieglitz Collection, 1949",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269454,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -49.55.47,false,true,269455,Photographs,Photograph,"From My Window at An American Place, North",,,,,,Artist,,Alfred Stieglitz,"American, Hoboken, New Jersey 1864–1946 New York",,"Stieglitz, Alfred",American,1864,1946,1931,1931,1931,Gelatin silver print,23.5 x 18.6 cm (9 1/4 x 7 5/16 in.),"Alfred Stieglitz Collection, 1949",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269455,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -49.55.48,false,true,269456,Photographs,Photograph,"From My Window at An American Place, Southwest",,,,,,Artist,,Alfred Stieglitz,"American, Hoboken, New Jersey 1864–1946 New York",,"Stieglitz, Alfred",American,1864,1946,1932,1932,1932,Gelatin silver print,19.2 x 24.1 cm (7 9/16 x 9 1/2 in.),"Alfred Stieglitz Collection, 1949",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269456,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -58.577.5,false,true,270062,Photographs,Photograph,The Street - Design for a Poster,,,,,,Artist,,Alfred Stieglitz,"American, Hoboken, New Jersey 1864–1946 New York",,"Stieglitz, Alfred",American,1864,1946,"1900–1901, printed 1903",1900,1901,Photogravure,17.7 x 13.3 cm. (7 x 5 1/4 in.),"Gift of J. B. Neumann, 1958",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/270062,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1997.61.2,false,true,271640,Photographs,Photograph,Georgia O'Keeffe—Hands,,,,,,Artist,,Alfred Stieglitz,"American, Hoboken, New Jersey 1864–1946 New York",,"Stieglitz, Alfred",American,1864,1946,1917,1917,1917,Platinum print,22.6 x 16.8 cm (8 7/8 x 6 5/8 in.),"Gift of Georgia O'Keeffe, through the generosity of The Georgia O'Keeffe Foundation and Jennifer and Joseph Duke, 1997",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/271640,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1997.61.6,false,true,271591,Photographs,Photograph,Georgia O'Keeffe,,,,,,Artist,,Alfred Stieglitz,"American, Hoboken, New Jersey 1864–1946 New York",,"Stieglitz, Alfred",American,1864,1946,1918,1918,1918,Palladium print,24.5 x 19.2 cm (9 5/8 x 7 9/16 in. ),"Gift of Georgia O'Keeffe, through the generosity of The Georgia O'Keeffe Foundation and Jennifer and Joseph Duke, 1997",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/271591,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -28.128.10,false,true,267450,Photographs,Photograph,Rain Drops,,,,,,Artist,,Alfred Stieglitz,"American, Hoboken, New Jersey 1864–1946 New York",,"Stieglitz, Alfred",American,1864,1946,1927,1927,1927,Gelatin silver print,9.2 x 11.7 cm. (3 5/8 x 4 5/8 in.),"Alfred Stieglitz Collection, 1928",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267450,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -28.128.11,false,true,267451,Photographs,Photograph,Equivalents,,,,,,Artist,,Alfred Stieglitz,"American, Hoboken, New Jersey 1864–1946 New York",,"Stieglitz, Alfred",American,1864,1946,1927,1927,1927,Gelatin silver print,9.1 x 11.8 cm. (3 9/16 x 4 5/8 in.),"Alfred Stieglitz Collection, 1928",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267451,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -58.577.11,false,true,270032,Photographs,Photogram,The Terminal,,,,,,Artist,,Alfred Stieglitz,"American, Hoboken, New Jersey 1864–1946 New York",,"Stieglitz, Alfred",American,1864,1946,"1893, printed 1911",1893,1893,Photogravure,12.1 x 16.0 cm (4 3/4 x 6 5/16 in.),"Gift of J. B. Neumann, 1958",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/270032,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -58.577.16,false,true,270037,Photographs,Photograph,"Snapshot - From My Window, Berlin",,,,,,Artist,,Alfred Stieglitz,"American, Hoboken, New Jersey 1864–1946 New York",,"Stieglitz, Alfred",American,1864,1946,"1890s–1900s, printed 1907",1890,1909,Photogravure,21.1 x 17.0 cm. (8 5/16 x 6 11/16 in.),"Gift of J. B. Neumann, 1958",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/270037,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -58.577.37,false,true,270060,Photographs,Photograph,The Flat-iron,,,,,,Artist,,Alfred Stieglitz,"American, Hoboken, New Jersey 1864–1946 New York",,"Stieglitz, Alfred",American,1864,1946,1903,1903,1903,Photogravure,17.0 x 8.4 cm. (6 11/16 x 3 5/16 in.),"Gift of J. B. Neumann, 1958",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/270060,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1997.61.11,false,true,271588,Photographs,Photograph,Georgia O'Keeffe,,,,,,Artist,,Alfred Stieglitz,"American, Hoboken, New Jersey 1864–1946 New York",,"Stieglitz, Alfred",American,1864,1946,1919–21,1919,1921,Palladium print,24.1 x 19.5 cm (9 1/2 x 7 11/16 in. ),"Gift of Georgia O'Keeffe, through the generosity of The Georgia O'Keeffe Foundation and Jennifer and Joseph Duke, 1997",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/271588,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1997.61.18,false,true,271617,Photographs,Photograph,Georgia O'Keeffe — Hands,,,,,,Artist,,Alfred Stieglitz,"American, Hoboken, New Jersey 1864–1946 New York",,"Stieglitz, Alfred",American,1864,1946,1919,1919,1919,Palladium print,22.9 x 18.9 cm (9 x 7 7/16 in. ),"Gift of Georgia O'Keeffe, through the generosity of The Georgia O'Keeffe Foundation and Jennifer and Joseph Duke, 1997",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/271617,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1997.61.19,true,true,271615,Photographs,Photograph,Georgia O'Keeffe — Neck,,,,,,Artist,,Alfred Stieglitz,"American, Hoboken, New Jersey 1864–1946 New York",,"Stieglitz, Alfred",American,1864,1946,1921,1921,1921,Palladium print,23.6 x 19.2 cm (9 5/16 x 7 9/16 in.),"Gift of Georgia O'Keeffe, through the generosity of The Georgia O'Keeffe Foundation and Jennifer and Joseph Duke, 1997",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/271615,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1997.61.20,false,true,271607,Photographs,Photograph,Georgia O'Keeffe,,,,,,Artist,,Alfred Stieglitz,"American, Hoboken, New Jersey 1864–1946 New York",,"Stieglitz, Alfred",American,1864,1946,1918,1918,1918,Palladium print,22.8 x 18.6 cm (9 x 7 5/16 in. ),"Gift of Georgia O'Keeffe, through the generosity of The Georgia O'Keeffe Foundation and Jennifer and Joseph Duke, 1997",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/271607,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1997.61.21,false,true,271629,Photographs,Photograph,Georgia O'Keeffe,,,,,,Artist,,Alfred Stieglitz,"American, Hoboken, New Jersey 1864–1946 New York",,"Stieglitz, Alfred",American,1864,1946,1918,1918,1918,Palladium print,23.2 x 19.2 cm (9 1/8 x 7 9/16 in.),"Gift of Georgia O'Keeffe, through the generosity of The Georgia O'Keeffe Foundation and Jennifer and Joseph Duke, 1997",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/271629,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1997.61.23,false,true,271623,Photographs,Photograph,Georgia O'Keeffe — Breasts,,,,,,Artist,,Alfred Stieglitz,"American, Hoboken, New Jersey 1864–1946 New York",,"Stieglitz, Alfred",American,1864,1946,1919,1919,1919,Palladium print,24.4 x 19.3 cm (9 5/8 x 7 5/8 in. ),"Gift of Georgia O'Keeffe, through the generosity of The Georgia O'Keeffe Foundation and Jennifer and Joseph Duke, 1997",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/271623,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1997.61.24,false,true,271585,Photographs,Photograph,Georgia O'Keeffe,,,,,,Artist,,Alfred Stieglitz,"American, Hoboken, New Jersey 1864–1946 New York",,"Stieglitz, Alfred",American,1864,1946,1924,1924,1924,Palladium print,23.8 x 19.3 cm (9 3/8 x 7 5/8 in. ),"Gift of Georgia O'Keeffe, through the generosity of The Georgia O'Keeffe Foundation and Jennifer and Joseph Duke, 1997",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/271585,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1997.61.25,true,true,271570,Photographs,Photograph,Georgia O'Keeffe,,,,,,Artist,,Alfred Stieglitz,"American, Hoboken, New Jersey 1864–1946 New York",,"Stieglitz, Alfred",American,1864,1946,1918,1918,1918,Palladium print,11.7 x 9 cm (4 5/8 x 3 9/16 in.),"Gift of Georgia O'Keeffe, through the generosity of The Georgia O'Keeffe Foundation and Jennifer and Joseph Duke, 1997",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/271570,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1997.61.26,false,true,271592,Photographs,Photograph,Georgia O'Keeffe—Hand,,,,,,Artist,,Alfred Stieglitz,"American, Hoboken, New Jersey 1864–1946 New York",,"Stieglitz, Alfred",American,1864,1946,1918,1918,1918,Platinum print,11.8 x 9.1 cm (4 5/8 x 3 9/16 in. ),"Gift of Georgia O'Keeffe, through the generosity of The Georgia O'Keeffe Foundation and Jennifer and Joseph Duke, 1997",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/271592,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1997.61.29,false,true,271605,Photographs,Photograph,Georgia O'Keeffe,,,,,,Artist,,Alfred Stieglitz,"American, Hoboken, New Jersey 1864–1946 New York",,"Stieglitz, Alfred",American,1864,1946,1921,1921,1921,Palladium print,23.5 x 18.1 cm (9 1/4 x 7 1/8 in. ),"Gift of Georgia O'Keeffe, through the generosity of The Georgia O'Keeffe Foundation and Jennifer and Joseph Duke, 1997",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/271605,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1997.61.31,false,true,271598,Photographs,Photograph,Georgia O'Keeffe,,,,,,Artist,,Alfred Stieglitz,"American, Hoboken, New Jersey 1864–1946 New York",,"Stieglitz, Alfred",American,1864,1946,1918,1918,1918,Gelatin silver print,23.5 x 19 cm (9 1/4 x 7 1/2 in. ),"Gift of Georgia O'Keeffe, through the generosity of The Georgia O'Keeffe Foundation and Jennifer and Joseph Duke, 1997",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/271598,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1997.61.36,false,true,271634,Photographs,Photograph,Georgia O'Keeffe,,,,,,Artist,,Alfred Stieglitz,"American, Hoboken, New Jersey 1864–1946 New York",,"Stieglitz, Alfred",American,1864,1946,1932,1932,1932,Gelatin silver print,23.5 x 18.9 cm (9 1/4 x 7 7/16 in. ),"Gift of Georgia O'Keeffe, through the generosity of The Georgia O'Keeffe Foundation and Jennifer and Joseph Duke, 1997",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/271634,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1997.61.37,false,true,271610,Photographs,Photograph,Georgia O'Keeffe—Hands and Horse Skull,,,,,,Artist,,Alfred Stieglitz,"American, Hoboken, New Jersey 1864–1946 New York",,"Stieglitz, Alfred",American,1864,1946,1931,1931,1931,Gelatin silver print,19.2 x 24 cm (7 9/16 x 9 7/16 in. ),"Gift of Georgia O'Keeffe, through the generosity of The Georgia O'Keeffe Foundation and Jennifer and Joseph Duke, 1997",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/271610,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1997.61.39,false,true,271582,Photographs,Photograph,Georgia O'Keeffe—Hand and Wheel,,,,,,Artist,,Alfred Stieglitz,"American, Hoboken, New Jersey 1864–1946 New York",,"Stieglitz, Alfred",American,1864,1946,1933,1933,1933,Gelatin silver print,24.1 x 19.5 cm (9 1/2 x 7 11/16 in.),"Gift of Georgia O'Keeffe, through the generosity of The Georgia O'Keeffe Foundation and Jennifer and Joseph Duke, 1997",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/271582,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1997.61.40,false,true,271579,Photographs,Photograph,Georgia O'Keeffe,,,,,,Artist,,Alfred Stieglitz,"American, Hoboken, New Jersey 1864–1946 New York",,"Stieglitz, Alfred",American,1864,1946,1933,1933,1933,Gelatin silver print,23.9 x 18.9 cm (9 7/16 x 7 7/16 in. ),"Gift of Georgia O'Keeffe, through the generosity of The Georgia O'Keeffe Foundation and Jennifer and Joseph Duke, 1997",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/271579,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1997.61.43,false,true,271600,Photographs,Photograph,Georgia O'Keeffe,,,,,,Artist,,Alfred Stieglitz,"American, Hoboken, New Jersey 1864–1946 New York",,"Stieglitz, Alfred",American,1864,1946,1930,1930,1930,Gelatin silver print,24.1 x 18.8 cm (9 1/2 x 7 3/8 in. ),"Gift of Georgia O'Keeffe, through the generosity of The Georgia O'Keeffe Foundation and Jennifer and Joseph Duke, 1997",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/271600,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1997.61.55,false,true,271572,Photographs,Photograph,Georgia O'Keeffe—Feet,,,,,,Artist,,Alfred Stieglitz,"American, Hoboken, New Jersey 1864–1946 New York",,"Stieglitz, Alfred",American,1864,1946,1918,1918,1918,Platinum print,24.1 x 19.5 cm (9 1/2 x 7 11/16 in. ),"Gift of Georgia O'Keeffe, through the generosity of The Georgia O'Keeffe Foundation and Jennifer and Joseph Duke, 1997",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/271572,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1997.61.61,false,true,271604,Photographs,Photograph,Georgia O'Keeffe,,,,,,Artist,,Alfred Stieglitz,"American, Hoboken, New Jersey 1864–1946 New York",,"Stieglitz, Alfred",American,1864,1946,1919–21,1919,1921,Palladium print,23.7 x 19 cm (9 5/16 x 7 1/2 in. ),"Gift of Georgia O'Keeffe, through the generosity of The Georgia O'Keeffe Foundation and Jennifer and Joseph Duke, 1997",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/271604,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1997.61.63,false,true,271603,Photographs,Photograph,Georgia O'Keeffe,,,,,,Artist,,Alfred Stieglitz,"American, Hoboken, New Jersey 1864–1946 New York",,"Stieglitz, Alfred",American,1864,1946,ca. 1920,1918,1922,Gelatin silver print,9.2 x 11.5 cm (3 5/8 x 4 1/2 in. ),"Gift of Georgia O'Keeffe, through the generosity of The Georgia O'Keeffe Foundation and Jennifer and Joseph Duke, 1997",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/271603,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1997.61.64,false,true,271590,Photographs,Photograph,Georgia O'Keeffe with Matisse Sculpture,,,,,,Artist,,Alfred Stieglitz,"American, Hoboken, New Jersey 1864–1946 New York",,"Stieglitz, Alfred",American,1864,1946,1921,1921,1921,Palladium print,24.4 x 19.2 cm (9 5/8 x 7 9/16 in. ),"Gift of Georgia O'Keeffe, through the generosity of The Georgia O'Keeffe Foundation and Jennifer and Joseph Duke, 1997",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/271590,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1997.61.66,false,true,271587,Photographs,Photograph,Georgia O'Keeffe,,,,,,Artist,,Alfred Stieglitz,"American, Hoboken, New Jersey 1864–1946 New York",,"Stieglitz, Alfred",American,1864,1946,1922,1922,1922,Palladium print,18.9 x 24 cm (7 7/16 x 9 7/16 in. ),"Gift of Georgia O'Keeffe, through the generosity of The Georgia O'Keeffe Foundation and Jennifer and Joseph Duke, 1997",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/271587,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1997.61.69,false,true,271602,Photographs,Photograph,Georgia O'Keeffe,,,,,,Artist,,Alfred Stieglitz,"American, Hoboken, New Jersey 1864–1946 New York",,"Stieglitz, Alfred",American,1864,1946,1932,1932,1932,Gelatin silver print,19.1 x 24 cm (7 1/2 x 9 7/16 in.),"Gift of Georgia O'Keeffe, through the generosity of The Georgia O'Keeffe Foundation and Jennifer and Joseph Duke, 1997",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/271602,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1997.61.71,false,true,271624,Photographs,Photograph,Georgia O'Keeffe – Torso,,,,,,Artist,,Alfred Stieglitz,"American, Hoboken, New Jersey 1864–1946 New York",,"Stieglitz, Alfred",American,1864,1946,1931,1931,1931,Gelatin silver print,10.2 x 23.7 cm (4 x 9 5/16 in.),"Gift of Georgia O'Keeffe, through the generosity of The Georgia O'Keeffe Foundation and Jennifer and Joseph Duke, 1997",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/271624,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1997.61.74,false,true,271614,Photographs,Photograph,[Margaret Prosser's Clasped Hands in Lap],,,,,,Artist,,Alfred Stieglitz,"American, Hoboken, New Jersey 1864–1946 New York",,"Stieglitz, Alfred",American,1864,1946,1933,1933,1933,Gelatin silver print,9.2 x 11.5 cm (3 5/8 x 4 1/2 in. ),"Gift of Georgia O'Keeffe, through the generosity of The Georgia O'Keeffe Foundation and Jennifer and Joseph Duke, 1997",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/271614,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.159.49,false,true,306332,Photographs,Photograph,"Kitty Stieglitz, Central Park, New York",,,,,,Artist,,Alfred Stieglitz,"American, Hoboken, New Jersey 1864–1946 New York",,"Stieglitz, Alfred",American,1864,1946,1901,1901,1901,Photogravure,Mount: 10 3/8 in. × 6 15/16 in. (26.4 × 17.6 cm) Image: 4 13/16 × 6 3/16 in. (12.2 × 15.7 cm),"Bequest of Maurice B. Sendak, 2012",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/306332,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1987.1100.11,false,true,265144,Photographs,Photograph,"From My Window at the Shelton, North",,,,,,Artist,,Alfred Stieglitz,"American, Hoboken, New Jersey 1864–1946 New York",,"Stieglitz, Alfred",American,1864,1946,1931,1931,1931,Gelatin silver print,24.2 x 19.2 cm (9 1/2 x 7 9/16 in.),"Ford Motor Company Collection, Gift of Ford Motor Company and John C. Waddell, 1987",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/265144,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.178,false,true,285933,Photographs,Photograph,"Spring Showers, the Coach",,,,,,Artist,,Alfred Stieglitz,"American, Hoboken, New Jersey 1864–1946 New York",,"Stieglitz, Alfred",American,1864,1946,1899–1900,1899,1900,Platinum print,Image: 3 3/8 × 1 13/16 in. (8.6 × 4.6 cm) Mount (1): 3 9/16 in. × 2 in. (9 × 5.1 cm) Mount (2): 6 13/16 × 3 15/16 in. (17.3 × 10 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285933,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.290,false,true,283271,Photographs,Photograph,Marsden Hartley,,,,,,Artist,,Alfred Stieglitz,"American, Hoboken, New Jersey 1864–1946 New York",,"Stieglitz, Alfred",American,1864,1946,1916,1916,1916,Gelatin silver print,"Image: 24.8 x 19.8cm (9 3/4 x 7 13/16in.) Mount: 55.8 cm, 46 3/16 in. (21 15/16 in., 117.3 cm)","Gilman Collection, Purchase, Gift of Marsden Hartley, by exchange, and Gift of Grace M. Mayer, by exchange, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/283271,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.459,false,true,285929,Photographs,Photograph,The Hand of Man,,,,,,Artist,,Alfred Stieglitz,"American, Hoboken, New Jersey 1864–1946 New York",,"Stieglitz, Alfred",American,1864,1946,"1902, printed 1920s–30s",1902,1902,Gelatin silver print,Image: 8.8 x 11.8 cm (3 7/16 x 4 5/8 in.) Mount: 8.8 x 11.8 cm (3 7/16 x 4 5/8 in.) Mount (2nd): 31.3 x 24.7 cm (12 5/16 x 9 3/4 in.),"Gilman Collection, Purchase, Ann Tenenbaum and Thomas H. Lee Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285929,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.479,false,true,286177,Photographs,Photograph,Grass,,,,,,Artist,,Alfred Stieglitz,"American, Hoboken, New Jersey 1864–1946 New York",,"Stieglitz, Alfred",American,1864,1946,1933,1933,1933,Gelatin silver print,Image: 14.9 x 18.6 cm (5 7/8 x 7 5/16 in.) Mount: 14.9 x 18.6 cm (5 7/8 x 7 5/16 in.) Mount (2nd): 34.7 x 27.5 cm (13 11/16 x 10 13/16 in.),"Gilman Collection, Purchase, Ann Tenenbaum and Thomas H. Lee Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286177,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1987.1100.103,false,true,265137,Photographs,Photograph,Gable and Apples,,,,,,Artist,,Alfred Stieglitz,"American, Hoboken, New Jersey 1864–1946 New York",,"Stieglitz, Alfred",American,1864,1946,1922,1922,1922,Gelatin silver print,11.4 x 9.0 cm (4 1/2 x 3 9/16 in.),"Ford Motor Company Collection, Gift of Ford Motor Company and John C. Waddell, 1987",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/265137,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1987.1100.457,false,true,265529,Photographs,Photograph,Equivalent No. 314,,,,,,Artist,,Alfred Stieglitz,"American, Hoboken, New Jersey 1864–1946 New York",,"Stieglitz, Alfred",American,1864,1946,1926,1926,1926,Gelatin silver print,11.9 x 9.1 cm (4 11/16 x 3 9/16 in.),"Ford Motor Company Collection, Gift of Ford Motor Company and John C. Waddell, 1987",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/265529,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.1149,false,true,285942,Photographs,Photograph,"Fragment from Balustrade of the Temple of Athena Nike, Acropolis, Athens",,,,,,Artist,,William James Stillman,"American, Schenectady, New York 1828–1901 Surrey",,"Stillman, William James",American,1828,1901,ca. 1882,1877,1887,Albumen silver print from glass negative,Image: 10 1/2 × 8 1/8 in. (26.7 × 20.6 cm) Mount: 15 7/8 in. × 13 15/16 in. (40.4 × 35.4 cm),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285942,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.240,false,true,681809,Photographs,Tintype,[Ku Klux Klansman with Two Pistols and a Sword],,,,,,Artist,,Owen A. Kenefick,"American, Lawrence, Massachusetts ca. 1858–after 1930",,"Kenefick, Owen A.",American,1850,1950,1880–1905,1880,1905,Tintype,Tintype: 3 1/2 × 2 9/16 in. (8.9 × 6.5 cm) Passe-Partout: 4 7/8 × 3 1/8 in. (12.4 × 7.9 cm) Paper Folder (Open): 4 7/8 × 6 1/4 in. (12.4 × 15.9 cm),"The Horace W. Goldsmith Foundation Fund, through Joyce and Robert Menschel, 2015",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/681809,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.75,false,true,283174,Photographs,Photograph,[Portrait of a Young Man],,,,,,Artist,,Samuel F. B. Morse,"American, Charlestown, Massachusetts 1791–1872 New York",,"Morse, Samuel F. B.",American,1791,1872,1840,1840,1840,Daguerreotype,"Image: 5 x 4.2 cm (1 15/16 x 1 5/8 in.), oval","Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/283174,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.1110,false,true,286619,Photographs,Photograph,"[Fortifications Near Charleston, South Carolina]",,,,,,Artist,Attributed to,George Smith Cook,"American, Stratford, Connecticut 1819–1902 Bel Air, Virginia",,"Cook, George Smith",American,1819,1902,ca. 1861,1861,1861,Albumen silver print from glass negative,Image: 15 × 20.6 cm (5 7/8 × 8 1/8 in.) Mount: 23.2 × 27.1 cm (9 1/8 × 10 11/16 in.),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286619,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2001.389,false,true,284644,Photographs,Photograph,Grave,,,,,,Artist,,Walker Evans,"American, St. Louis, Missouri 1903–1975 New Haven, Connecticut",,"Evans, Walker",American,1903,1975,1936,1936,1936,Gelatin silver print,19.4 x 24.2 cm (7 5/8 x 9 1/2 in. ),"Purchase, Alfred Stieglitz Society Gifts, 2001",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/284644,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1987.1100.482,true,true,265556,Photographs,Photograph,"Penny Picture Display, Savannah",,,,,,Artist,,Walker Evans,"American, St. Louis, Missouri 1903–1975 New Haven, Connecticut",,"Evans, Walker",American,1903,1975,1936,1936,1936,Gelatin silver print,24.7 x 19.3 cm (9 3/4 x 7 5/8 in.),"Ford Motor Company Collection, Gift of Ford Motor Company and John C. Waddell, 1987",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/265556,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.43.147,false,true,267546,Photographs,Photograph,[Mrs. James Brown Potter or Mrs Potter Palmer],,,,,,Artist,,F. Holland Day,"American, Norwood, Massachusetts 1864–1933 Norwood, Massachusetts",,"Day, F. Holland",American,1864,1933,1896,1896,1896,Platinum print,13.4 x 10.8 cm. (5 1/4 x 4 1/4 in.),"Alfred Stieglitz Collection, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267546,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.43.148,false,true,267547,Photographs,Photograph,Zaida Ben-Yusuf,,,,,,Artist,,F. Holland Day,"American, Norwood, Massachusetts 1864–1933 Norwood, Massachusetts",,"Day, F. Holland",American,1864,1933,1898,1898,1898,Platinum print,Image: 16.3 x 10.9 cm (6 7/16 x 4 5/16 in.) Mount: 17.1 x 11.5 cm (6 3/4 x 4 1/2 in.),"Alfred Stieglitz Collection, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267547,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.43.151,false,true,267551,Photographs,Photograph,Kahlil Gibran with Book,,,,,,Artist,,F. Holland Day,"American, Norwood, Massachusetts 1864–1933 Norwood, Massachusetts",,"Day, F. Holland",American,1864,1933,1896,1896,1896,Platinum print,15.9 x 12.0 cm. (6 1/4 x 4 3/4 in.),"Alfred Stieglitz Collection, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267551,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.43.157,false,true,267557,Photographs,Photograph,An Ethiopian Chief,,,,,,Artist,,F. Holland Day,"American, Norwood, Massachusetts 1864–1933 Norwood, Massachusetts",,"Day, F. Holland",American,1864,1933,ca. 1897,1895,1899,Platinum print,18.1 x 18.4 cm. (7 1/8 x 7 1/4 in.),"Alfred Stieglitz Collection, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267557,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.43.158,false,true,267558,Photographs,Photograph,Menelek,,,,,,Artist,,F. Holland Day,"American, Norwood, Massachusetts 1864–1933 Norwood, Massachusetts",,"Day, F. Holland",American,1864,1933,1897,1897,1897,Platinum print,24.5 x 19.5 cm (9 5/8 x 7 11/16 in.),"Alfred Stieglitz Collection, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267558,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.43.159,false,true,267559,Photographs,Photograph,Portrait,,,,,,Artist,,F. Holland Day,"American, Norwood, Massachusetts 1864–1933 Norwood, Massachusetts",,"Day, F. Holland",American,1864,1933,1898,1898,1898,Platinum print,15.7 x 9.8 cm. (6 3/16 x 3 7/8 in.),"Alfred Stieglitz Collection, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267559,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.43.160,false,true,267561,Photographs,Photograph,The Vigil,,,,,,Artist,,F. Holland Day,"American, Norwood, Massachusetts 1864–1933 Norwood, Massachusetts",,"Day, F. Holland",American,1864,1933,1899,1899,1899,Platinum print,Image: 16.2 x 11 cm (6 3/8 x 4 5/16 in.) Mount: 30.9 x 23.1 cm (12 3/16 x 9 1/8 in.),"Alfred Stieglitz Collection, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267561,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.43.161,false,true,267562,Photographs,Photograph,[Boy Piping],,,,,,Artist,,F. Holland Day,"American, Norwood, Massachusetts 1864–1933 Norwood, Massachusetts",,"Day, F. Holland",American,1864,1933,ca. 1896,1894,1898,Platinum print,11.8 x 16.6 cm. (4 5/8 x 6 9/16 in.),"Alfred Stieglitz Collection, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267562,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.43.165,false,true,267566,Photographs,Photograph,Portrait of a Man with Book,,,,,,Artist,,F. Holland Day,"American, Norwood, Massachusetts 1864–1933 Norwood, Massachusetts",,"Day, F. Holland",American,1864,1933,1896,1896,1896,Platinum print,Image: 16 x 11.7 cm (6 5/16 x 4 5/8 in.) Mount: 16.3 x 11.9 cm (6 7/16 x 4 11/16 in.) Mount (2nd): 16.6 x 12.1 cm (6 9/16 x 4 3/4 in.) Mount (3rd): 19 x 14.6 cm (7 1/2 x 5 3/4 in.) Mount (4th): 19.2 x 14.9 cm (7 9/16 x 5 7/8 in.) Mount (5th): 36 x 27.8 cm (14 3/16 x 10 15/16 in.),"Alfred Stieglitz Collection, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267566,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.43.166,false,true,267567,Photographs,Photograph,Ebony and Ivory,,,,,,Artist,,F. Holland Day,"American, Norwood, Massachusetts 1864–1933 Norwood, Massachusetts",,"Day, F. Holland",American,1864,1933,ca. 1897,1895,1899,Platinum print,18.3 x 20.0 cm (7 3/16 x 7 7/8 in.),"Alfred Stieglitz Collection, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267567,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.43.355,false,true,267770,Photographs,Photograph,[Draped Nude Lounging on the Grass],,,,,,Artist,,F. Holland Day,"American, Norwood, Massachusetts 1864–1933 Norwood, Massachusetts",,"Day, F. Holland",American,1864,1933,ca. 1897,1895,1899,Platinum print,Image: 11.8 x 15.8 cm (4 5/8 x 6 1/4 in.) Mount: 15.2 x 19.3 cm (6 x 7 5/8 in.),"Alfred Stieglitz Collection, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267770,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.43.356,false,true,267771,Photographs,Photograph,The Entombment,,,,,,Artist,,F. Holland Day,"American, Norwood, Massachusetts 1864–1933 Norwood, Massachusetts",,"Day, F. Holland",American,1864,1933,1898,1898,1898,Platinum print,6.7 x 16.6 cm. (2 5/8 x 6 9/16 in.),"Alfred Stieglitz Collection, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267771,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.43.357,false,true,267772,Photographs,Photograph,The Honey Gatherer,,,,,,Artist,,F. Holland Day,"American, Norwood, Massachusetts 1864–1933 Norwood, Massachusetts",,"Day, F. Holland",American,1864,1933,ca. 1898,1896,1900,Platinum print,Image: 12.5 cm (4 15/16 in.) diameter Mount: 14 x 13.6 cm (5 1/2 x 5 3/8 in.) Mount (2nd): 14.7 x 14.4 cm (5 13/16 x 5 11/16 in.),"Alfred Stieglitz Collection, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267772,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.43.358,false,true,267773,Photographs,Photograph,[Mrs. James Brown Potter or Mrs. Potter Palmer],,,,,,Artist,,F. Holland Day,"American, Norwood, Massachusetts 1864–1933 Norwood, Massachusetts",,"Day, F. Holland",American,1864,1933,1896,1896,1896,Platinum print,14.2 x 11.9 cm. (5 9/16 x 4 11/16 in.),"Alfred Stieglitz Collection, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267773,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.43.361,false,true,267777,Photographs,Photograph,Kahlil Gibran with Book,,,,,,Artist,,F. Holland Day,"American, Norwood, Massachusetts 1864–1933 Norwood, Massachusetts",,"Day, F. Holland",American,1864,1933,1896,1896,1896,Platinum print,16.0 x 11.8 cm. (6 5/16 x 4 5/8 in.),"Alfred Stieglitz Collection, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267777,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.43.367,false,true,267783,Photographs,Photograph,Zaîda Ben-Yusuf,,,,,,Artist,,F. Holland Day,"American, Norwood, Massachusetts 1864–1933 Norwood, Massachusetts",,"Day, F. Holland",American,1864,1933,1890s,1890,1899,Platinum print,16.5 x 12.2 cm. (6 1/2 x 4 13/16 in.),"Alfred Stieglitz Collection, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267783,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -49.55.175,false,true,269295,Photographs,Photograph,The Seven Words,,,,,,Artist,,F. Holland Day,"American, Norwood, Massachusetts 1864–1933 Norwood, Massachusetts",,"Day, F. Holland",American,1864,1933,1898,1898,1898,Platinum print,each approx: 14.0 x 11.5 cm (5 1/2 x 4 1/2 in.),"Alfred Stieglitz Collection, 1949",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269295,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -49.55.222,false,true,269347,Photographs,Photograph,The Seven Words,,,,,,Artist,,F. Holland Day,"American, Norwood, Massachusetts 1864–1933 Norwood, Massachusetts",,"Day, F. Holland",American,1864,1933,1898,1898,1898,Platinum print,7.9 x 32.8 cm. (3 1/8 x 12 15/16 in.),"Alfred Stieglitz Collection, 1949",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269347,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2011.366,false,true,294845,Photographs,Photograph,[Man Serving Head on a Platter],,,,,,Artist,,William Robert Bowles,"American, Campbellsville, Kentucky 1861–1918 Hopkinsville, Kentucky",,"Bowles, William Robert",American,1861,1918,ca. 1900,1895,1905,Gelatin silver print,Image: 9.8 x 13.9 cm (3 7/8 x 5 1/2 in.) Mount: 14.6 x 17.5 cm (5 3/4 x 6 7/8 in.) Frame: 27.9 x 35.6 cm (11 x 14 in.),"Twentieth-Century Photography Fund, 2011",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/294845,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -44.75.7,false,true,271868,Photographs,Photograph,[Three Boys Wading in a Creek],,,,,,Artist,,Thomas Eakins,"American, Philadelphia, Pennsylvania 1844–1916 Philadelphia, Pennsylvania",,"Eakins, Thomas",American,1844,1916,1883,1883,1883,Platinum print,,"Gift of Charles Bregler, 1944",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/271868,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -41.142.2,false,true,271896,Photographs,Photograph,"[William H. Macdowell and Margaret Eakins in Saltville (or Clinch Mountain), Virginia]",,,,,,Artist,Attributed to,Thomas Eakins,"American, Philadelphia, Pennsylvania 1844–1916 Philadelphia, Pennsylvania",,"Eakins, Thomas",American,1844,1916,1880–82,1880,1882,Platinum print,"Image: 27.4 x 20 cm (10 13/16 x 7 7/8 in.), irregular","Gift of Charles Bregler, 1941",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/271896,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -43.87.17,true,true,271885,Photographs,Photograph,Two Pupils in Greek Dress,,,,,,Artist,,Thomas Eakins,"American, Philadelphia, Pennsylvania 1844–1916 Philadelphia, Pennsylvania",,"Eakins, Thomas",American,1844,1916,1883,1883,1883,Platinum print,36.8 x 26.7 cm. (14 1/2 x 10 1/2 in.),"David Hunter McAlpin Fund, 1943",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/271885,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -43.87.20,false,true,271887,Photographs,Photograph,"Bill Duckett Nude, at the Art Students’ League of Philadelphia",,,,,,Artist,,Thomas Eakins,"American, Philadelphia, Pennsylvania 1844–1916 Philadelphia, Pennsylvania",,"Eakins, Thomas",American,1844,1916,ca. 1889,1884,1894,Platinum print,23.3 x 22 cm (9 3/16 x 8 11/16 in.),"David Hunter McAlpin Fund, 1943",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/271887,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -43.87.22,false,true,271889,Photographs,Photograph,"[Thomas Eakins, Nude, Playing Pipes]",,,,,,Artist,,Thomas Eakins,"American, Philadelphia, Pennsylvania 1844–1916 Philadelphia, Pennsylvania",,"Eakins, Thomas",American,1844,1916,ca. 1883,1883,1883,Platinum print,22.7 x 16.6 cm (8 15/16 x 6 9/16 in.) irregular,"David Hunter McAlpin Fund, 1943",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/271889,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -43.87.23,true,true,271890,Photographs,Photograph,[Thomas Eakins and John Laurie Wallace on a Beach],,,,,,Artist,,Thomas Eakins,"American, Philadelphia, Pennsylvania 1844–1916 Philadelphia, Pennsylvania",,"Eakins, Thomas",American,1844,1916,ca. 1883,1882,1884,Platinum print,"25.5 x 20.4 cm (10 1/16 x 8 1/16 in.), irregular","David Hunter McAlpin Fund, 1943",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/271890,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -43.87.24,false,true,271891,Photographs,Photograph,[Nude Men in the Garden],,,,,,Artist,,Thomas Eakins,"American, Philadelphia, Pennsylvania 1844–1916 Philadelphia, Pennsylvania",,"Eakins, Thomas",American,1844,1916,1870s,1870,1879,Platinum print,,"David Hunter McAlpin Fund, 1943",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/271891,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -43.87.25,false,true,271892,Photographs,Photograph,[Female Nude from the Back],,,,,,Artist,,Thomas Eakins,"American, Philadelphia, Pennsylvania 1844–1916 Philadelphia, Pennsylvania",,"Eakins, Thomas",American,1844,1916,ca. 1889,1887,1891,Platinum print,7.1 x 13.4 cm (2 13/16 x 5 1/4 in.),"David Hunter McAlpin Fund, 1943",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/271892,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -44.75.10,false,true,271873,Photographs,Photograph,"[Man Walking, ""Stroboscopic"" Photograph]",,,,,,Artist,,Thomas Eakins,"American, Philadelphia, Pennsylvania 1844–1916 Philadelphia, Pennsylvania",,"Eakins, Thomas",American,1844,1916,"1880s, printed 1930s–40s",1880,1889,Gelatin silver print,,"Gift of Charles Bregler, 1944",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/271873,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1983.1103,false,true,271854,Photographs,Photograph,[Betty Reynolds with Doll on Lap],,,,,,Artist,,Thomas Eakins,"American, Philadelphia, Pennsylvania 1844–1916 Philadelphia, Pennsylvania",,"Eakins, Thomas",American,1844,1916,ca. 1885,1883,1887,Gelatin silver print,5.5 x 5.3 cm (2 3/16 x 2 1/16 in.),"David Hunter McAlpin Fund, 1983",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/271854,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1979.547.2,false,true,271856,Photographs,Photograph,[Mrs. Eakins or Her Sister Doll],,,,,,Artist,,Thomas Eakins,"American, Philadelphia, Pennsylvania 1844–1916 Philadelphia, Pennsylvania",,"Eakins, Thomas",American,1844,1916,1880s–90s,1880,1899,Albumen silver print,,"Gift of Julius Ravzin, 1979",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/271856,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1985.1027.12,false,true,271805,Photographs,Photograph,"Katie Crowell in Avondale, Pennsylvania",,,,,,Artist,,Thomas Eakins,"American, Philadelphia, Pennsylvania 1844–1916 Philadelphia, Pennsylvania",,"Eakins, Thomas",American,1844,1916,1887,1887,1887,Albumen silver print,,"Gift of Arthur and Carol Goldberg, 1985",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/271805,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1985.1027.17,false,true,271810,Photographs,Photograph,[Four Cats],,,,,,Artist,,Thomas Eakins,"American, Philadelphia, Pennsylvania 1844–1916 Philadelphia, Pennsylvania",,"Eakins, Thomas",American,1844,1916,ca. 1895,1893,1897,Platinum print,5.5 x 13.6 cm (2 3/16 x 5 3/8 in.),"Gift of Joan and Martin E. Messinger, 1985",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/271810,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1985.1027.20,false,true,271813,Photographs,Photograph,Self-Portrait,,,,,,Artist,,Thomas Eakins,"American, Philadelphia, Pennsylvania 1844–1916 Philadelphia, Pennsylvania",,"Eakins, Thomas",American,1844,1916,1889–94,1889,1894,Platinum print,,"Gift of Mimi and Ariel Halpern, 1985",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/271813,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1985.1027.24,false,true,271817,Photographs,Photograph,William J. Crowell with Ella,,,,,,Artist,,Thomas Eakins,"American, Philadelphia, Pennsylvania 1844–1916 Philadelphia, Pennsylvania",,"Eakins, Thomas",American,1844,1916,1880,1880,1880,Albumen silver print,9.5 x 7.2 cm (3 3/4 x 2 13/16 in.),"Gift of Harry D. Nelson Jr., 1985",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/271817,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1985.1027.41,false,true,271834,Photographs,Photograph,[Cornfield in Back of the Barn],,,,,,Artist,,Thomas Eakins,"American, Philadelphia, Pennsylvania 1844–1916 Philadelphia, Pennsylvania",,"Eakins, Thomas",American,1844,1916,1883,1883,1883,Albumen silver print,,"Gift of Joan and Martin E. Messinger, 1985",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/271834,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1985.1027.42,false,true,271835,Photographs,Photograph,"[Two Boys Playing at the Creek, July 4, 1883]",,,,,,Artist,,Thomas Eakins,"American, Philadelphia, Pennsylvania 1844–1916 Philadelphia, Pennsylvania",,"Eakins, Thomas",American,1844,1916,1883,1883,1883,Albumen silver print,8.8 x 11.1 cm (3 7/16 x 4 3/8 in.),"Gift of Joseph R. Lasser and Ruth P. Lasser, 1985",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/271835,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1985.1027.43,false,true,271836,Photographs,Photograph,"[Three Children and a Dog Playing in the Creek, July 4, 1883]",,,,,,Artist,,Thomas Eakins,"American, Philadelphia, Pennsylvania 1844–1916 Philadelphia, Pennsylvania",,"Eakins, Thomas",American,1844,1916,1883,1883,1883,Albumen silver print,8.9 x 11.2 cm (3 1/2 x 4 7/16 in.),"Gift of Robert D. English, 1985",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/271836,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1985.1027.45,false,true,271838,Photographs,Photograph,"[Three Children and a Dog Playing in the Creek, July 4, 1883]",,,,,,Artist,,Thomas Eakins,"American, Philadelphia, Pennsylvania 1844–1916 Philadelphia, Pennsylvania",,"Eakins, Thomas",American,1844,1916,1883,1883,1883,Albumen silver print,,"Gift of Daniel P. and Nancy C. Paduano, 1985",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/271838,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1985.1027.51,false,true,271844,Photographs,Photograph,"[Frances Crowell with Unidentified Boy, Katie, James, and Frances Crowell]",,,,,,Artist,,Thomas Eakins,"American, Philadelphia, Pennsylvania 1844–1916 Philadelphia, Pennsylvania",,"Eakins, Thomas",American,1844,1916,1890,1890,1890,Platinum print,7.8 x 9.2 cm (3 1/16 x 3 5/8 in.),"Gift of Joseph R. Lasser and Ruth P. Lasser, 1985",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/271844,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1985.1027.59,false,true,271852,Photographs,Photograph,"[Thomas Eakins's Horse Billy and Two Crowell Children at Avondale, Pennsylvania]",,,,,,Artist,,Thomas Eakins,"American, Philadelphia, Pennsylvania 1844–1916 Philadelphia, Pennsylvania",,"Eakins, Thomas",American,1844,1916,ca. 1892,1890,1894,Platinum print,8.1 x 9.8 cm (3 3/16 x 3 7/8 in.) irregular,"Gift of Joseph R. Lasser and Ruth P. Lasser, 1985",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/271852,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.589,false,true,285754,Photographs,Photograph,[Self-Portrait],,,,,,Artist,,Thomas Eakins,"American, Philadelphia, Pennsylvania 1844–1916 Philadelphia, Pennsylvania",,"Eakins, Thomas",American,1844,1916,ca. 1880,1878,1882,Platinum print,Image: 15.7 x 10 cm (6 3/16 x 3 15/16 in.),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285754,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.1109,false,true,285842,Photographs,Photograph,[African-American Man],,,,,,Artist,,Thomas Eakins,"American, Philadelphia, Pennsylvania 1844–1916 Philadelphia, Pennsylvania",,"Eakins, Thomas",American,1844,1916,ca. 1884,1882,1886,Gelatin silver print,Image: 3 13/16 × 2 11/16 in. (9.7 × 6.8 cm),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285842,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1974.671,false,true,261176,Photographs,Photograph,Herbert,,,,,,Artist,,Morton Schamberg,"American, Philadelphia, Pennsylvania 1881–1918 Philadelphia, Pennsylvania",,"Schamberg, Morton",American,1881,1918,1912,1912,1912,Gelatin silver print,,"Gift of Jean Loeb Whitehill, 1974",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261176,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.43.351,false,true,267766,Photographs,Photograph,Portrait Study,,,,,,Artist,,Morton Schamberg,"American, Philadelphia, Pennsylvania 1881–1918 Philadelphia, Pennsylvania",,"Schamberg, Morton",American,1881,1918,1917,1917,1917,Gelatin silver print,24.1 x 17.7 cm. (9 1/2 x 7 in.),"Alfred Stieglitz Collection, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267766,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1973.664.1,false,true,261015,Photographs,Photograph,Jeanne,,,,,,Artist,,Morton Schamberg,"American, Philadelphia, Pennsylvania 1881–1918 Philadelphia, Pennsylvania",,"Schamberg, Morton",American,1881,1918,1917,1917,1917,Gelatin silver print,,"Gift of Jean Loeb Whitehill, 1973",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261015,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1973.664.2,false,true,261026,Photographs,Photograph,Jeanne,,,,,,Artist,,Morton Schamberg,"American, Philadelphia, Pennsylvania 1881–1918 Philadelphia, Pennsylvania",,"Schamberg, Morton",American,1881,1918,1917,1917,1917,Gelatin silver print,,"Gift of Jean Loeb Whitehill, 1973",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261026,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1973.664.3,false,true,261027,Photographs,Photograph,Jeanne,,,,,,Artist,,Morton Schamberg,"American, Philadelphia, Pennsylvania 1881–1918 Philadelphia, Pennsylvania",,"Schamberg, Morton",American,1881,1918,1917,1917,1917,Gelatin silver print,,"Gift of Jean Loeb Whitehill, 1973",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261027,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1973.664.4,false,true,261028,Photographs,Photograph,Jeanne and Regena,,,,,,Artist,,Morton Schamberg,"American, Philadelphia, Pennsylvania 1881–1918 Philadelphia, Pennsylvania",,"Schamberg, Morton",American,1881,1918,1917,1917,1917,Gelatin silver print,,"Gift of Jean Loeb Whitehill, 1973",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261028,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1973.664.5,false,true,261029,Photographs,Photograph,Regena,,,,,,Artist,,Morton Schamberg,"American, Philadelphia, Pennsylvania 1881–1918 Philadelphia, Pennsylvania",,"Schamberg, Morton",American,1881,1918,1917,1917,1917,Gelatin silver print,,"Gift of Jean Loeb Whitehill, 1973",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261029,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1973.664.6,false,true,261030,Photographs,Photograph,Regena,,,,,,Artist,,Morton Schamberg,"American, Philadelphia, Pennsylvania 1881–1918 Philadelphia, Pennsylvania",,"Schamberg, Morton",American,1881,1918,1912,1912,1912,Gelatin silver print,,"Gift of Jean Loeb Whitehill, 1973",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261030,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1973.664.7,false,true,261031,Photographs,Photograph,Regena and Herbert,,,,,,Artist,,Morton Schamberg,"American, Philadelphia, Pennsylvania 1881–1918 Philadelphia, Pennsylvania",,"Schamberg, Morton",American,1881,1918,1912,1912,1912,Gelatin silver print,,"Gift of Jean Loeb Whitehill, 1973",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261031,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1973.664.8,false,true,261032,Photographs,Photograph,Herbert,,,,,,Artist,,Morton Schamberg,"American, Philadelphia, Pennsylvania 1881–1918 Philadelphia, Pennsylvania",,"Schamberg, Morton",American,1881,1918,1912,1912,1912,Gelatin silver print,,"Gift of Jean Loeb Whitehill, 1973",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261032,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1973.664.9,false,true,261033,Photographs,Photograph,Herbert,,,,,,Artist,,Morton Schamberg,"American, Philadelphia, Pennsylvania 1881–1918 Philadelphia, Pennsylvania",,"Schamberg, Morton",American,1881,1918,1912,1912,1912,Gelatin silver print,,"Gift of Jean Loeb Whitehill, 1973",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261033,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1973.664.10,false,true,261016,Photographs,Photograph,Jeanne,,,,,,Artist,,Morton Schamberg,"American, Philadelphia, Pennsylvania 1881–1918 Philadelphia, Pennsylvania",,"Schamberg, Morton",American,1881,1918,1912,1912,1912,Gelatin silver print,,"Gift of Jean Loeb Whitehill, 1973",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261016,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1973.664.11,false,true,261017,Photographs,Photograph,Jeanne,,,,,,Artist,,Morton Schamberg,"American, Philadelphia, Pennsylvania 1881–1918 Philadelphia, Pennsylvania",,"Schamberg, Morton",American,1881,1918,1912,1912,1912,Gelatin silver print,,"Gift of Jean Loeb Whitehill, 1973",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261017,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1973.664.12,false,true,261018,Photographs,Photograph,Jeanne and Richard,,,,,,Artist,,Morton Schamberg,"American, Philadelphia, Pennsylvania 1881–1918 Philadelphia, Pennsylvania",,"Schamberg, Morton",American,1881,1918,1912,1912,1912,Gelatin silver print,,"Gift of Jean Loeb Whitehill, 1973",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261018,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1973.664.13,false,true,261019,Photographs,Photograph,Jeanne and Richard,,,,,,Artist,,Morton Schamberg,"American, Philadelphia, Pennsylvania 1881–1918 Philadelphia, Pennsylvania",,"Schamberg, Morton",American,1881,1918,1912,1912,1912,Gelatin silver print,,"Gift of Jean Loeb Whitehill, 1973",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261019,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1973.664.14,false,true,261020,Photographs,Photograph,Richard,,,,,,Artist,,Morton Schamberg,"American, Philadelphia, Pennsylvania 1881–1918 Philadelphia, Pennsylvania",,"Schamberg, Morton",American,1881,1918,1912,1912,1912,Gelatin silver print,,"Gift of Jean Loeb Whitehill, 1973",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261020,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1973.664.15,false,true,261021,Photographs,Photograph,Etellea,,,,,,Artist,,Morton Schamberg,"American, Philadelphia, Pennsylvania 1881–1918 Philadelphia, Pennsylvania",,"Schamberg, Morton",American,1881,1918,1912,1912,1912,Gelatin silver print,,"Gift of Jean Loeb Whitehill, 1973",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261021,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1973.664.16,false,true,261022,Photographs,Photograph,Jeanne,,,,,,Artist,,Morton Schamberg,"American, Philadelphia, Pennsylvania 1881–1918 Philadelphia, Pennsylvania",,"Schamberg, Morton",American,1881,1918,1915,1915,1915,Gelatin silver print,,"Gift of Jean Loeb Whitehill, 1973",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261022,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1973.664.17,false,true,261023,Photographs,Photograph,Jeanne,,,,,,Artist,,Morton Schamberg,"American, Philadelphia, Pennsylvania 1881–1918 Philadelphia, Pennsylvania",,"Schamberg, Morton",American,1881,1918,1912,1912,1912,Gelatin silver print,,"Gift of Jean Loeb Whitehill, 1973",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261023,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1973.664.18,false,true,261024,Photographs,Photograph,Morton Schamberg,,,,,,Artist,,Morton Schamberg,"American, Philadelphia, Pennsylvania 1881–1918 Philadelphia, Pennsylvania",,"Schamberg, Morton",American,1881,1918,ca. 1912,1910,1914,Gelatin silver print,,"Gift of Jean Loeb Whitehill, 1973",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261024,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1973.664.19,false,true,261025,Photographs,Photograph,"Morton, Richard, Jeanne, Gilbert, Jesse, Etellea, Yvonne, Henry, Regena, and Herbert",,,,,,Artist,,Morton Schamberg,"American, Philadelphia, Pennsylvania 1881–1918 Philadelphia, Pennsylvania",,"Schamberg, Morton",American,1881,1918,1912,1912,1912,Gelatin silver print,,"Gift of Jean Loeb Whitehill, 1973",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/261025,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -47.62.2,false,true,271903,Photographs,Glass positive,[Man on a Ladder],,,,,,Artist,,Thomas Eakins,"American, Philadelphia, Pennsylvania 1844–1916 Philadelphia, Pennsylvania",,"Eakins, Thomas",American,1844,1916,1880s,1880,1889,Glass positive,,"Gift of Charles Bregler, 1947",,,,,,,,,,,,Transparencies,,http://www.metmuseum.org/art/collection/search/271903,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -47.62.3,false,true,271904,Photographs,Glass positive,Margaret Eakins,,,,,,Artist,,Thomas Eakins,"American, Philadelphia, Pennsylvania 1844–1916 Philadelphia, Pennsylvania",,"Eakins, Thomas",American,1844,1916,1880s,1880,1889,Glass positive,,"Gift of Charles Bregler, 1947",,,,,,,,,,,,Transparencies,,http://www.metmuseum.org/art/collection/search/271904,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -47.62.4,false,true,271905,Photographs,Glass positive,[Female portrait],,,,,,Artist,,Thomas Eakins,"American, Philadelphia, Pennsylvania 1844–1916 Philadelphia, Pennsylvania",,"Eakins, Thomas",American,1844,1916,1880s,1880,1889,Glass positive,,"Gift of Charles Bregler, 1947",,,,,,,,,,,,Transparencies,,http://www.metmuseum.org/art/collection/search/271905,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -47.62.5,false,true,271902,Photographs,Glass positive,Frank MacDowell,,,,,,Artist,,Thomas Eakins,"American, Philadelphia, Pennsylvania 1844–1916 Philadelphia, Pennsylvania",,"Eakins, Thomas",American,1844,1916,1880s,1880,1889,Glass positive,,"Gift of Charles Bregler, 1947",,,,,,,,,,,,Transparencies,,http://www.metmuseum.org/art/collection/search/271902,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -44.75.9,false,true,271870,Photographs,Photograph,Edmund Quinn Fencing,,,,,,Artist,Circle of,Thomas Eakins,"American, Philadelphia, Pennsylvania 1844–1916 Philadelphia, Pennsylvania",,"Eakins, Thomas",American,1844,1916,1880s,1880,1889,Gelatin silver print,Image: 9.1 x 11 cm (3 9/16 x 4 5/16 in.),"Gift of Charles Bregler, 1944",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/271870,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -41.142.4,false,true,271898,Photographs,Photograph,[Woman Playing Cello],,,,,,Artist,,Thomas Eakins,"American, Philadelphia, Pennsylvania 1844–1916 Philadelphia, Pennsylvania",,"Eakins, Thomas",American,1844,1916,1880s,1880,1889,Platinum print,,"Gift of Charles Bregler, 1941",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/271898,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -43.87.10,false,true,271878,Photographs,Photograph,Mary Macdowell,,,,,,Artist,,Thomas Eakins,"American, Philadelphia, Pennsylvania 1844–1916 Philadelphia, Pennsylvania",,"Eakins, Thomas",American,1844,1916,1880s,1880,1889,Platinum print,,"David Hunter McAlpin Fund, 1943",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/271878,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -43.87.11,false,true,271879,Photographs,Photograph,Elizabeth MacDowell Kenton,,,,,,Artist,,Thomas Eakins,"American, Philadelphia, Pennsylvania 1844–1916 Philadelphia, Pennsylvania",,"Eakins, Thomas",American,1844,1916,1880s,1880,1889,Albumen silver print,,"David Hunter McAlpin Fund, 1943",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/271879,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -43.87.14,false,true,271882,Photographs,Photograph,[Woman in White Laced-bodice Dress in Studio of Thomas Eakins],,,,,,Artist,,Thomas Eakins,"American, Philadelphia, Pennsylvania 1844–1916 Philadelphia, Pennsylvania",,"Eakins, Thomas",American,1844,1916,1880s,1880,1889,Platinum print,23.3 x 14.9 cm (9 3/16 x 5 7/8 in.),"David Hunter McAlpin Fund, 1943",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/271882,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -43.87.21,false,true,271888,Photographs,Photograph,[Standing Male Nude with Pipes],,,,,,Artist,,Thomas Eakins,"American, Philadelphia, Pennsylvania 1844–1916 Philadelphia, Pennsylvania",,"Eakins, Thomas",American,1844,1916,1880s,1880,1889,Platinum print,22.9 x 17.3 cm (9 x 6 13/16 in.),"David Hunter McAlpin Fund, 1943",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/271888,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -44.75.11,false,true,271869,Photographs,Photograph,[Thomas Eakins in Swim Suit],,,,,,Artist,,Thomas Eakins,"American, Philadelphia, Pennsylvania 1844–1916 Philadelphia, Pennsylvania",,"Eakins, Thomas",American,1844,1916,1880s,1880,1889,Albumen silver print,,"Gift of Charles Bregler, 1944",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/271869,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1979.547.1,false,true,271855,Photographs,Photograph,Mr. MacDowell,,,,,,Artist,,Thomas Eakins,"American, Philadelphia, Pennsylvania 1844–1916 Philadelphia, Pennsylvania",,"Eakins, Thomas",American,1844,1916,1880s,1880,1889,Albumen silver print,,"Gift of Julius Ravzin, 1979",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/271855,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1979.547.3,false,true,271857,Photographs,Photograph,[Mrs. Louis Kentin in Empire Dress],,,,,,Artist,,Thomas Eakins,"American, Philadelphia, Pennsylvania 1844–1916 Philadelphia, Pennsylvania",,"Eakins, Thomas",American,1844,1916,1880s,1880,1889,Albumen silver print,,"Gift of Julius Ravzin, 1979",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/271857,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1985.1027.11,false,true,271804,Photographs,Photograph,[Thomas Eakins's Dog Harry and Another Setter],,,,,,Artist,,Thomas Eakins,"American, Philadelphia, Pennsylvania 1844–1916 Philadelphia, Pennsylvania",,"Eakins, Thomas",American,1844,1916,1880s,1880,1889,Platinum print,8.5 x 10.4 cm (3 3/8 x 4 1/8 in.),"Gift of John T. Marvin, 1985",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/271804,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2011.199,false,true,296297,Photographs,Cabinet card,[Man in Bottle],,,,,,Artist,,John C. Higgins,"American, active 1880s–90s",,"Higgins, John C.",American,1880,1899,ca. 1888,1883,1893,Albumen silver print from glass negative,Image: 13.5 x 10 cm (5 5/16 x 3 15/16 in.) Mount: 16.4 x 10.6 cm (6 7/16 x 4 3/16 in.) Frame: 35.6 x 27.9 cm (14 x 11 in.),"Purchase, Susan and Thomas Dunn Gift, 2011",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/296297,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.460.1,false,true,290469,Photographs,Photograph,Niagara Falls,,,,,,Artist,,George Barker,"American, born Canada, 1844–1894",,"Barker, George",American,1844,1894,ca. 1888,1885,1890,Albumen silver print from glass negative,Image: 49.1 x 42.5 cm (19 5/16 x 16 3/4 in.) Sheet: 50.8 x 43.3 cm (20 x 17 1/16 in.) Mount: 54.7 x 48.8 cm (21 9/16 x 19 3/16 in.),"Gift of Paul F. Walter, 2009",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/290469,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.1174.3,false,true,302666,Photographs,Carte-de-visite,"The Evacuation of Fort Sumter, April 1861",,,,,,Publisher,,Edward Anthony,"American, 1818–1888",,"Anthony, Edward",American,1818,1888,April 1861,1861,1861,Albumen silver print from glass negative,Image: 3 1/8 × 1 15/16 in. (7.9 × 5 cm) Mount: 4 3/4 in. × 3 3/8 in. (12 × 8.5 cm),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/302666,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.1174.5,false,true,302668,Photographs,Carte-de-visite,"The Evacuation of Fort Sumter, April 1861",,,,,,Publisher,,Edward Anthony,"American, 1818–1888",,"Anthony, Edward",American,1818,1888,April 1861,1861,1861,Albumen silver print from glass negative,Image: 1 15/16 × 3 1/8 in. (5 × 7.9 cm) Mount: 3 3/8 in. × 4 3/4 in. (8.5 × 12 cm),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/302668,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.1174.6,false,true,302669,Photographs,Carte-de-visite,"The Evacuation of Fort Sumter, April 1861",,,,,,Publisher,,Edward Anthony,"American, 1818–1888",,"Anthony, Edward",American,1818,1888,April 1861,1861,1861,Albumen silver print from glass negative,Image: 1 15/16 × 3 1/8 in. (5 × 7.9 cm) Mount: 3 3/8 in. × 4 3/4 in. (8.5 × 12 cm),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/302669,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.1174.10,false,true,302673,Photographs,Carte-de-visite,"The Evacuation of Fort Sumter, April 1861",,,,,,Publisher,,Edward Anthony,"American, 1818–1888",,"Anthony, Edward",American,1818,1888,April 1861,1861,1861,Albumen silver print from glass negative,Image: 1 15/16 × 3 1/8 in. (5 × 7.9 cm) Mount: 3 3/8 in. × 4 3/4 in. (8.5 × 12 cm),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/302673,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.1174.12,false,true,302675,Photographs,Carte-de-visite,"The Evacuation of Fort Sumter, April 1861",,,,,,Publisher,,Edward Anthony,"American, 1818–1888",,"Anthony, Edward",American,1818,1888,April 1861,1861,1861,Albumen silver print from glass negative,Image: 1 15/16 × 3 1/8 in. (5 × 7.9 cm) Mount: 3 3/8 in. × 4 3/4 in. (8.5 × 12 cm),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/302675,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.1174.14,false,true,302677,Photographs,Carte-de-visite,"The Evacuation of Fort Sumter, April 1861",,,,,,Publisher,,Edward Anthony,"American, 1818–1888",,"Anthony, Edward",American,1818,1888,April 1861,1861,1861,Albumen silver print from glass negative,Image: 1 15/16 × 3 1/8 in. (5 × 7.9 cm) Mount: 3 3/8 in. × 4 3/4 in. (8.5 × 12 cm),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/302677,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1982.1182.939,false,true,288280,Photographs,Stereographs,"Tower of London, London, England",,,,,,Artist|Publisher,,Benneville Lloyd Singley|Keystone View Company,"American, Union Township, Pennsylvania 1864–1938 Meadville, Pennsylvania",,"Singley, Benneville Lloyd|Keystone View Company",American,1864,1938,1850s–1910s,1850,1919,Albumen silver prints,Mount: 8.9 x 17.9 cm (3 1/2 x 7 1/16 in.),"Gift of Weston J. Naef, in memory of Kathleen W. Naef and Weston J. Naef Sr., 1982",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288280,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1982.1182.958–.962,false,true,288288,Photographs,Stereographs,"[Group of 5 Stereograph Views of the Thames River at Night, London, England]",,,,,,Artist|Publisher,,Benneville Lloyd Singley|Keystone View Company,"American, Union Township, Pennsylvania 1864–1938 Meadville, Pennsylvania",,"Singley, Benneville Lloyd|Keystone View Company",American,1864,1938,1850s–1910s,1850,1919,Albumen silver prints,,"Gift of Weston J. Naef, in memory of Kathleen W. Naef and Weston J. Naef Sr., 1982",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288288,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1982.1182.992–.995,false,true,288294,Photographs,Stereographs,[Group of 4 Stereograph Views of Babies],,,,,,Artist|Publisher,,Benneville Lloyd Singley|Keystone View Company,"American, Union Township, Pennsylvania 1864–1938 Meadville, Pennsylvania",,"Singley, Benneville Lloyd|Keystone View Company",American,1864,1938,1850s–1910s,1850,1919,Albumen silver prints,Mounts approximately: 8.9 x 17.8 cm (3 1/2 x 7 in.),"Gift of Weston J. Naef, in memory of Kathleen W. Naef and Weston J. Naef Sr., 1982",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288294,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.505.1 (1b),false,true,288478,Photographs,Photograph,[Young Japanese Woman],,,,,,Artist,,Suzuki Shin'ichi,"Japanese, 1835–1919",,"Suzuki, Shin'ichi",Japanese,1835,1919,1870s,1870,1879,Albumen silver print from glass negative,24.8 x 19.8 cm (9 3/4 x 7 13/16 in.),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288478,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.505.1 (2b),false,true,283170,Photographs,Photograph,Actor in Samurai Armor,,,,,,Artist,,Suzuki Shin'ichi,"Japanese, 1835–1919",,"Suzuki, Shin'ichi",Japanese,1835,1919,1870s,1870,1879,Albumen silver print from glass negative,24.6 x 18.9 cm (9 11/16 x 7 7/16 in.),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/283170,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.505.1 (4b),false,true,288483,Photographs,Photograph,[Woman in Traditional Japanese Garment Photographed from Behind],,,,,,Artist,,Suzuki Shin'ichi,"Japanese, 1835–1919",,"Suzuki, Shin'ichi",Japanese,1835,1919,1870s,1870,1879,Albumen silver print from glass negative,25.1 x 20 cm (9 7/8 x 7 7/8 in.),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288483,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.505.1 (5b),false,true,288484,Photographs,Photograph,[Young Japanese Woman],,,,,,Artist,,Suzuki Shin'ichi,"Japanese, 1835–1919",,"Suzuki, Shin'ichi",Japanese,1835,1919,1870s,1870,1879,Albumen silver print from glass negative,25 x 19.9 cm (9 13/16 x 7 13/16 in.),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288484,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.505.1 (9b),false,true,288490,Photographs,Photograph,"Street Minstrel, Gose",,,,,,Artist,,Suzuki Shin'ichi,"Japanese, 1835–1919",,"Suzuki, Shin'ichi",Japanese,1835,1919,1870s,1870,1879,Albumen silver print from glass negative,25.2 x 20.2 cm (9 15/16 x 7 15/16 in.),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288490,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.505.1 (10b),false,true,288492,Photographs,Photograph,Farm laborer with rain coat (mino),,,,,,Artist,,Suzuki Shin'ichi,"Japanese, 1835–1919",,"Suzuki, Shin'ichi",Japanese,1835,1919,1870s,1870,1879,Albumen silver print from glass negative,25.2 x 19.7 cm (9 15/16 x 7 3/4 in.),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288492,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.505.1 (11b),false,true,288494,Photographs,Photograph,Professional Singers,,,,,,Artist,,Suzuki Shin'ichi,"Japanese, 1835–1919",,"Suzuki, Shin'ichi",Japanese,1835,1919,1870s,1870,1879,Albumen silver print from glass negative,25.1 x 20.1 cm (9 7/8 x 7 15/16 in.),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288494,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.505.1 (12b),false,true,288496,Photographs,Photograph,Newsman,,,,,,Artist,,Suzuki Shin'ichi,"Japanese, 1835–1919",,"Suzuki, Shin'ichi",Japanese,1835,1919,1870s,1870,1879,Albumen silver print from glass negative,25.3 x 20.1 cm (9 15/16 x 7 15/16 in.),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288496,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.505.1 (13b),false,true,288498,Photographs,Photograph,Tea House waitress,,,,,,Artist,,Suzuki Shin'ichi,"Japanese, 1835–1919",,"Suzuki, Shin'ichi",Japanese,1835,1919,1870s,1870,1879,Albumen silver print from glass negative,25.1 x 20.2 cm (9 7/8 x 7 15/16 in.),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288498,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.505.1 (14b),false,true,288500,Photographs,Photograph,La Toilette,,,,,,Artist,,Suzuki Shin'ichi,"Japanese, 1835–1919",,"Suzuki, Shin'ichi",Japanese,1835,1919,1870s,1870,1879,Albumen silver print from glass negative,25.2 x 20.3 cm (9 15/16 x 8 in.),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288500,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.505.1 (15b),false,true,288502,Photographs,Photograph,[Japanese Women in Traditional Dress],,,,,,Artist,,Suzuki Shin'ichi,"Japanese, 1835–1919",,"Suzuki, Shin'ichi",Japanese,1835,1919,1870s,1870,1879,Albumen silver print from glass negative,25.2 x 20 cm (9 15/16 x 7 7/8 in.),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288502,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.505.1 (16b),false,true,288505,Photographs,Photograph,[Japanese Women in Traditional Dress],,,,,,Artist,,Suzuki Shin'ichi,"Japanese, 1835–1919",,"Suzuki, Shin'ichi",Japanese,1835,1919,1870s,1870,1879,Albumen silver print from glass negative,25.2 x 20 cm (9 15/16 x 7 7/8 in.),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288505,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.505.1 (17b),false,true,288507,Photographs,Photograph,[Japanese Woman in Traditional Dress],,,,,,Artist,,Suzuki Shin'ichi,"Japanese, 1835–1919",,"Suzuki, Shin'ichi",Japanese,1835,1919,1870s,1870,1879,Albumen silver print from glass negative,25.2 x 19.7 cm (9 15/16 x 7 3/4 in.),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288507,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.505.1 (18b),false,true,288509,Photographs,Photograph,Osaki Kioto [illegible] dancer,,,,,,Artist,,Suzuki Shin'ichi,"Japanese, 1835–1919",,"Suzuki, Shin'ichi",Japanese,1835,1919,1870s,1870,1879,Albumen silver print from glass negative,25.2 x 19.9 cm (9 15/16 x 7 13/16 in.),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288509,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.505.1 (19b),false,true,288511,Photographs,Photograph,[Two Japanese Women in Traditional Dress],,,,,,Artist,,Suzuki Shin'ichi,"Japanese, 1835–1919",,"Suzuki, Shin'ichi",Japanese,1835,1919,1870s,1870,1879,Albumen silver print from glass negative,25.2 x 19.9 cm (9 15/16 x 7 13/16 in.),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288511,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.505.1 (20b),false,true,288513,Photographs,Photograph,[Two Japanese Women in Traditional Dress with Fan and Screen],,,,,,Artist,,Suzuki Shin'ichi,"Japanese, 1835–1919",,"Suzuki, Shin'ichi",Japanese,1835,1919,1870s,1870,1879,Albumen silver print from glass negative,25.2 x 19.7 cm (9 15/16 x 7 3/4 in.),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288513,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.505.1 (21b),false,true,288514,Photographs,Photograph,[Japanese Man Preparing a Fish],,,,,,Artist,,Suzuki Shin'ichi,"Japanese, 1835–1919",,"Suzuki, Shin'ichi",Japanese,1835,1919,1870s,1870,1879,Albumen silver print from glass negative,25.2 x 19.9 cm (9 15/16 x 7 13/16 in.),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288514,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.505.1 (22b),false,true,288516,Photographs,Photograph,[A Japanese Woman and a Japanese Boy in Traditional Dress],,,,,,Artist,,Suzuki Shin'ichi,"Japanese, 1835–1919",,"Suzuki, Shin'ichi",Japanese,1835,1919,1870s,1870,1879,Albumen silver print from glass negative,25.2 x 20.1 cm (9 15/16 x 7 15/16 in.),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288516,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.505.1 (23b),false,true,288518,Photographs,Photograph,[Japanese Woman with Parasol],,,,,,Artist,,Suzuki Shin'ichi,"Japanese, 1835–1919",,"Suzuki, Shin'ichi",Japanese,1835,1919,1870s,1870,1879,Albumen silver print from glass negative,25.1 x 20.3 cm (9 7/8 x 8 in.),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288518,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.505.1 (24b),false,true,288520,Photographs,Photograph,Florist,,,,,,Artist,,Suzuki Shin'ichi,"Japanese, 1835–1919",,"Suzuki, Shin'ichi",Japanese,1835,1919,1870s,1870,1879,Albumen silver print from glass negative,25.1 x 20.3 cm (9 7/8 x 8 in.),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288520,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.505.1 (25b),false,true,288522,Photographs,Photograph,[Two Japanese Women Posing with Fans],,,,,,Artist,,Suzuki Shin'ichi,"Japanese, 1835–1919",,"Suzuki, Shin'ichi",Japanese,1835,1919,1870s,1870,1879,Albumen silver print from glass negative,25.2 x 20.1 cm (9 15/16 x 7 15/16 in.),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288522,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.505.1 (26b),false,true,288524,Photographs,Photograph,[Japanese Woman in Traditional Dress Posing with a Child on her Back],,,,,,Artist,,Suzuki Shin'ichi,"Japanese, 1835–1919",,"Suzuki, Shin'ichi",Japanese,1835,1919,1870s,1870,1879,Albumen silver print from glass negative,25 x 20.2 cm (9 13/16 x 7 15/16 in.),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288524,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.505.1 (27b),false,true,288526,Photographs,Photograph,[Japanese Woman in Traditional Dress Posing with Cat and Instrument],,,,,,Artist,,Suzuki Shin'ichi,"Japanese, 1835–1919",,"Suzuki, Shin'ichi",Japanese,1835,1919,1870s,1870,1879,Albumen silver print from glass negative,25.2 x 20 cm (9 15/16 x 7 7/8 in.),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288526,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.505.1 (28b),false,true,288527,Photographs,Photograph,[Japanese Woman in Traditional Dress Posing Outdoors],,,,,,Artist,,Suzuki Shin'ichi,"Japanese, 1835–1919",,"Suzuki, Shin'ichi",Japanese,1835,1919,1870s,1870,1879,Albumen silver print from glass negative,25.2 x 19.8 cm (9 15/16 x 7 13/16 in.),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288527,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.505.1 (29b),false,true,288529,Photographs,Photograph,Cobbler,,,,,,Artist,,Suzuki Shin'ichi,"Japanese, 1835–1919",,"Suzuki, Shin'ichi",Japanese,1835,1919,1870s,1870,1879,Albumen silver print from glass negative,25.3 x 20.2 cm (9 15/16 x 7 15/16 in.),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288529,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.505.1 (30b),false,true,288530,Photographs,Photograph,[Two Japanese Men in Traditional Dress],,,,,,Artist,,Suzuki Shin'ichi,"Japanese, 1835–1919",,"Suzuki, Shin'ichi",Japanese,1835,1919,1870s,1870,1879,Albumen silver print from glass negative,20.1 x 25.2 cm (7 15/16 x 9 15/16 in.),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288530,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.505.1 (40b),false,true,288548,Photographs,Photograph,[Japanese Woman in Carriage],,,,,,Artist,,Suzuki Shin'ichi,"Japanese, 1835–1919",,"Suzuki, Shin'ichi",Japanese,1835,1919,1870s,1870,1879,Albumen silver print from glass negative,20.1 x 25.2 cm (7 15/16 x 9 15/16 in.),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288548,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.505.1 (41b),false,true,288549,Photographs,Photograph,[Landscape with Buddha Sculpture],,,,,,Artist,,Suzuki Shin'ichi,"Japanese, 1835–1919",,"Suzuki, Shin'ichi",Japanese,1835,1919,1870s,1870,1879,Albumen silver print from glass negative,25.1 x 20.1 cm (9 7/8 x 7 15/16 in.),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288549,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.505.1 (43b),false,true,288553,Photographs,Photograph,[Buddha Sculpture],,,,,,Artist,,Suzuki Shin'ichi,"Japanese, 1835–1919",,"Suzuki, Shin'ichi",Japanese,1835,1919,1870s,1870,1879,Albumen silver print from glass negative,25.2 x 20 cm (9 15/16 x 7 7/8 in.),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288553,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2004.283a, b",false,true,285443,Photographs,Photograph,[Geisha with Attendant],,,,,,Artist,,Yokoyama Matsusaburō,"Japanese, 1838–1884",,"Yokoyama, Matsusaburō",Japanese,1838,1884,1860s,1860,1869,Ambrotype,Image: 10 x 7.6 cm (3 15/16 x 3 in.) Case: 1.3 x 11.1 x 8.6 cm (1/2 x 4 3/8 x 3 3/8 in.),"Funds from various donors, 2004",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285443,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.1064.1,false,true,264743,Photographs,Photograph,"Mutsuhito, The Meiji Emperor",,,,,,Artist,,Uchida Kuichi,"Japanese, 1844–1875",,"Uchida, Kuichi",Japanese,1844,1875,1872,1872,1872,Albumen silver print from glass negative,25.1 x 19.5 cm (9 7/8 x 7 11/16 in.),"The Elisha Whittelsey Collection, The Elisha Whittelsey Fund, 1986",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/264743,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2004.282a, b",false,true,285442,Photographs,Photograph,Kenji Morita,,,,,,Artist,,Fujita,"Japanese, active 1880s",,Fujita,Japanese,1880,1880,1886,1886,1886,Ambrotype,Image: 9.6 x 6.8 cm (3 3/4 x 2 11/16 in.) Case: 1.3 x 11.1 x 8.3 cm (1/2 x 4 3/8 x 3 1/4 in.),"Funds from various donors, 2004",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285442,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.454.1.27,false,true,291461,Photographs,Album,"Barracks Post, Place de la Bastille; Canal Tunnel and July Column",,,,,,Artist|Author,,Alphonse J. Liébert|Alfred d'Aunay,"French, 1827–1913|French",,"Liébert, Alphonse J.|d'Aunay, Alfred",French|French,1827,1913,1871,1871,1871,Albumen silver print from glass negative,"Images approx.: 19 x 25 cm (7 1/2 x 9 13/16 in.), or the reverse Mounts: 32.8 x 41.3 cm (12 15/16 x 16 1/4 in.), or the reverse","Joyce F. Menschel Photography Library Fund, 2007",,,,,,,,,,,,Albums,,http://www.metmuseum.org/art/collection/search/291461,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.454.1.1–.33,false,true,288894,Photographs,Album,"Les Ruines de Paris et de ses Environs 1870-1871: Cent Photographies: Premier Volume. Par A. Liébert, text par Alfred d'Aunay.",,,,,,Artist|Author,,Alphonse J. Liébert|Alfred d'Aunay,"French, 1827–1913|French",,"Liébert, Alphonse J.|d'Aunay, Alfred",French|French,1827,1913,1870–71,1870,1871,Albumen silver prints from glass negatives,"Images approx.: 19 x 25 cm (7 1/2 x 9 13/16 in.), or the reverse Mounts: 32.8 x 41.3 cm (12 15/16 x 16 1/4 in.), or the reverse","Joyce F. Menschel Photography Library Fund, 2007",,,,,,,,,,,,Albums,,http://www.metmuseum.org/art/collection/search/288894,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.454.2.1–.32,false,true,288895,Photographs,Album,"Les Ruines de Paris et de ses Environs 1870-1871: Cent Photographies: Second Volume. Par A. Liébert, text par Alfred d'Aunay.",,,,,,Artist|Author,,Alphonse J. Liébert|Alfred d'Aunay,"French, 1827–1913|French",,"Liébert, Alphonse J.|d'Aunay, Alfred",French|French,1827,1913,1870–71,1870,1871,Albumen silver prints from glass negatives,"Images approx.: 19 x 25 cm (7 1/2 x 9 13/16 in.), or the reverse Mounts: 32.8 x 41.3 cm (12 15/16 x 16 1/4 in.), or the reverse","Joyce F. Menschel Photography Library Fund, 2007",,,,,,,,,,,,Albums,,http://www.metmuseum.org/art/collection/search/288895,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1993.270,false,true,266917,Photographs,Album,Album d'Études–Poses,,,,,,Artist|Editor,,Louis Igout|A. Calavas,"French, 1837–1881|French",,"Igout, Louis|Calavas, A.",French|French,1837,1881,ca. 1880,1878,1882,Albumen silver prints from glass negatives,,"Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 1993",,,,,,,,,,,,Albums,,http://www.metmuseum.org/art/collection/search/266917,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.1–.179,false,true,285688,Photographs,Portfolio,"Jerusalem, Etude et reproduction photographique des monuments de la ville sainte depuis l' époque judaique jusqu'à nos jours 1856",,,,,,Artist|Printer,,"Auguste Salzmann|Imprimerie photographique de Blanquart-Évrard, à Lille","French, 1824–1872|French, active 1851–55",,"Salzmann, Auguste|Imprimerie photographique de Blanquart-Évrard, à Lille",French|French,1824 |1851,1872 |1855,"1854, printed 1856",1854,1859,Salted paper prints from paper negatives,,"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Portfolios,,http://www.metmuseum.org/art/collection/search/285688,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.175–.179,false,true,681092,Photographs,Portfolio,"Jerusalem, Etude et reproduction photographique des monuments de la ville sainte depuis l' époque judaique jusqu'à nos jours 1856",,,,,,Artist|Printer,,"Auguste Salzmann|Imprimerie photographique de Blanquart-Évrard, à Lille","French, 1824–1872|French, active 1851–55",,"Salzmann, Auguste|Imprimerie photographique de Blanquart-Évrard, à Lille",French|French,1824 |1851,1872 |1855,1854,1854,1859,Ink on paper,,"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Portfolios,,http://www.metmuseum.org/art/collection/search/681092,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -64.677.4,false,true,270956,Photographs,Photomechanical print,Charles Baudelaire,,,,,,Printer|Artist,,Goupil et Cie|Étienne Carjat,"French, active 1850–84|French, Fareins 1828–1906 Paris",,"Goupil et Cie|Carjat, Étienne",French|French,1850 |1828,1884 |1906,ca. 1863,1861,1865,Woodburytype,,"David Hunter McAlpin Fund, 1964",,,,,,,,,,,,Photographs|Prints,,http://www.metmuseum.org/art/collection/search/270956,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.454.1.4,false,true,291458,Photographs,Photograph,"Tuileries Palace, Burned. General View",,,,,,Artist|Author,,Alphonse J. Liébert|Alfred d'Aunay,"French, 1827–1913|French",,"Liébert, Alphonse J.|d'Aunay, Alfred",French|French,1827,1913,1871,1871,1871,Albumen silver print from glass negative,"Images approx.: 19 x 25 cm (7 1/2 x 9 13/16 in.), or the reverse Mounts: 32.8 x 41.3 cm (12 15/16 x 16 1/4 in.), or the reverse","Joyce F. Menschel Photography Library Fund, 2007",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/291458,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.454.1.12,false,true,291459,Photographs,Photograph,"Finance Ministry, Burned. Exterior View",,,,,,Artist|Author,,Alphonse J. Liébert|Alfred d'Aunay,"French, 1827–1913|French",,"Liébert, Alphonse J.|d'Aunay, Alfred",French|French,1827,1913,1871,1871,1871,Albumen silver print from glass negative,"Images approx.: 19 x 25 cm (7 1/2 x 9 13/16 in.), or the reverse Mounts: 32.8 x 41.3 cm (12 15/16 x 16 1/4 in.), or the reverse","Joyce F. Menschel Photography Library Fund, 2007",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/291459,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.454.1.26,false,true,291460,Photographs,Photograph,"Place de la Bastille, Burned",,,,,,Artist|Author,,Alphonse J. Liébert|Alfred d'Aunay,"French, 1827–1913|French",,"Liébert, Alphonse J.|d'Aunay, Alfred",French|French,1827,1913,1871,1871,1871,Albumen silver print from glass negative,"Images approx.: 19 x 25 cm (7 1/2 x 9 13/16 in.), or the reverse Mounts: 32.8 x 41.3 cm (12 15/16 x 16 1/4 in.), or the reverse","Joyce F. Menschel Photography Library Fund, 2007",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/291460,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.454.2.3,false,true,291457,Photographs,Photograph,Neuilly Bombarded. General View of the rue de Chezy,,,,,,Author|Artist,,Alfred d'Aunay|Alphonse J. Liébert,"French|French, 1827–1913",,"d'Aunay, Alfred|Liébert, Alphonse J.",French|French,1827,1913,1871,1871,1871,Albumen silver print from glass negative,"Images approx.: 19 x 25 cm (7 1/2 x 9 13/16 in.), or the reverse Mounts: 32.8 x 41.3 cm (12 15/16 x 16 1/4 in.), or the reverse","Joyce F. Menschel Photography Library Fund, 2007",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/291457,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.33,false,true,283106,Photographs,Photograph,[Henriette-Caroline-Victoire Robert],,,,,,Artist|Person in Photograph,Person in photograph,Louis-Rémy Robert|Henriette-Caroline-Victoire Robert,"French, 1810–1882|French, 1834–1934",,"Robert, Louis-Rémy|Robert, Henriette-Caroline-Victoire",French|French,1810 |1834,1882 |1934,ca. 1850,1848,1852,Salted paper print from paper negative,Mount: 12 13/16 in. × 10 3/8 in. (32.5 × 26.3 cm) Image: 8 11/16 × 5 13/16 in. (22 × 14.7 cm),"Gilman Collection, Purchase, Mrs. Walter Annenberg and The Annenberg Foundation Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/283106,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.46,false,true,286945,Photographs,Photograph,"Jérusalem, Vallée de Josaphat, Tombeau d'Absalon",,,,,,Artist|Printer,,"Auguste Salzmann|Imprimerie photographique de Blanquart-Évrard, à Lille","French, 1824–1872|French, active 1851–55",,"Salzmann, Auguste|Imprimerie photographique de Blanquart-Évrard, à Lille",French|French,1824 |1851,1872 |1855,1854,1854,1859,Salted paper print from paper negative,Image: 32.2 x 23.2 cm (12 11/16 x 9 1/8 in.) Mount: 59.8 x 44.8 cm (23 9/16 x 17 5/8 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286945,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.47,false,true,286946,Photographs,Photograph,"Jérusalem, Vallée de Josaphat, Détails du Tombeau d'Absalom",,,,,,Artist|Printer,,"Auguste Salzmann|Imprimerie photographique de Blanquart-Évrard, à Lille","French, 1824–1872|French, active 1851–55",,"Salzmann, Auguste|Imprimerie photographique de Blanquart-Évrard, à Lille",French|French,1824 |1851,1872 |1855,1854,1854,1859,Salted paper print from paper negative,Image: 33 x 23.1 cm (13 x 9 1/8 in.) Mount: 60 x 44.6 cm (23 5/8 x 17 9/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286946,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.48,false,true,286947,Photographs,Photograph,"Jérusalem, Vallée de Josaphat, Fronton du Tombeau de Josaphat",,,,,,Artist|Printer,,"Auguste Salzmann|Imprimerie photographique de Blanquart-Évrard, à Lille","French, 1824–1872|French, active 1851–55",,"Salzmann, Auguste|Imprimerie photographique de Blanquart-Évrard, à Lille",French|French,1824 |1851,1872 |1855,1854,1854,1859,Salted paper print from paper negative,Image: 23.5 x 32.5 cm (9 1/4 x 12 13/16 in.) Mount: 44.8 x 59.5 cm (17 5/8 x 23 7/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286947,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.49,false,true,286948,Photographs,Photograph,"Jérusalem, Vallée de Josaphat, Grotte sépulcrale, 1",,,,,,Artist|Printer,,"Auguste Salzmann|Imprimerie photographique de Blanquart-Évrard, à Lille","French, 1824–1872|French, active 1851–55",,"Salzmann, Auguste|Imprimerie photographique de Blanquart-Évrard, à Lille",French|French,1824 |1851,1872 |1855,1854,1854,1859,Salted paper print from paper negative,Image: 23.2 x 33.5 cm (9 1/8 x 13 3/16 in.) Mount: 44.8 x 59.8 cm (17 5/8 x 23 9/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286948,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.50,false,true,286949,Photographs,Photograph,"Jérusalem, Vallée de Josaphat, Grottes sépulcrales, 2",,,,,,Artist|Printer,,"Auguste Salzmann|Imprimerie photographique de Blanquart-Évrard, à Lille","French, 1824–1872|French, active 1851–55",,"Salzmann, Auguste|Imprimerie photographique de Blanquart-Évrard, à Lille",French|French,1824 |1851,1872 |1855,1854,1854,1859,Salted paper print from paper negative,Image: 22.7 x 33.2 cm (8 15/16 x 13 1/16 in.) Mount: 44.7 x 60.4 cm (17 5/8 x 23 3/4 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286949,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.51,false,true,286950,Photographs,Photograph,"Jérusalem, Tombeau des rois de Juda, Cour extérieure",,,,,,Artist|Printer,,"Auguste Salzmann|Imprimerie photographique de Blanquart-Évrard, à Lille","French, 1824–1872|French, active 1851–55",,"Salzmann, Auguste|Imprimerie photographique de Blanquart-Évrard, à Lille",French|French,1824 |1851,1872 |1855,1854,1854,1859,Salted paper print from paper negative,Image: 23.2 x 32.2 cm (9 1/8 x 12 11/16 in.) Mount: 44.6 x 59.9 cm (17 9/16 x 23 9/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286950,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.52,false,true,286951,Photographs,Photograph,"Jérusalem, Tombeau des rois de Juda, Intérieur de la cour",,,,,,Artist|Printer,,"Auguste Salzmann|Imprimerie photographique de Blanquart-Évrard, à Lille","French, 1824–1872|French, active 1851–55",,"Salzmann, Auguste|Imprimerie photographique de Blanquart-Évrard, à Lille",French|French,1824 |1851,1872 |1855,1854,1854,1859,Salted paper print from paper negative,Image: 23.4 x 31.9 cm (9 3/16 x 12 9/16 in.) Mount: 44.6 x 60.1 cm (17 9/16 x 23 11/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286951,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.53,false,true,286952,Photographs,Photograph,"Jérusalem, Tombeau des rois de Juda, Frise supérieure et centrale",,,,,,Artist|Printer,,"Auguste Salzmann|Imprimerie photographique de Blanquart-Évrard, à Lille","French, 1824–1872|French, active 1851–55",,"Salzmann, Auguste|Imprimerie photographique de Blanquart-Évrard, à Lille",French|French,1824 |1851,1872 |1855,1854,1854,1859,Salted paper print from paper negative,Image: 23.2 x 32.2 cm (9 1/8 x 12 11/16 in.) Mount: 44.5 x 59.7 cm (17 1/2 x 23 1/2 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286952,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.54,false,true,286953,Photographs,Photograph,"Jérusalem, Tombeau des rois de Juda, Encadrement de feuillages et de fruits",,,,,,Artist|Printer,,"Auguste Salzmann|Imprimerie photographique de Blanquart-Évrard, à Lille","French, 1824–1872|French, active 1851–55",,"Salzmann, Auguste|Imprimerie photographique de Blanquart-Évrard, à Lille",French|French,1824 |1851,1872 |1855,1854,1854,1859,Salted paper print from paper negative,Image: 23.4 x 32.3 cm (9 3/16 x 12 11/16 in.) Mount: 45.1 x 59.9 cm (17 3/4 x 23 9/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286953,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.55,false,true,286954,Photographs,Photograph,"Jérusalem, Tombeau des rois de Juda, Fragments d'un sarcophage",,,,,,Artist|Printer,,"Auguste Salzmann|Imprimerie photographique de Blanquart-Évrard, à Lille","French, 1824–1872|French, active 1851–55",,"Salzmann, Auguste|Imprimerie photographique de Blanquart-Évrard, à Lille",French|French,1824 |1851,1872 |1855,1854,1854,1859,Salted paper print from paper negative,Image: 23.6 x 33.6 cm (9 5/16 x 13 1/4 in.) Mount: 44.8 x 59.9 cm (17 5/8 x 23 9/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286954,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.56,false,true,286955,Photographs,Photograph,"Jérusalem, Tombeau des rois de Juda, Couvercle de sarcophage et fragment d'une porte en pierre",,,,,,Artist|Printer,,"Auguste Salzmann|Imprimerie photographique de Blanquart-Évrard, à Lille","French, 1824–1872|French, active 1851–55",,"Salzmann, Auguste|Imprimerie photographique de Blanquart-Évrard, à Lille",French|French,1824 |1851,1872 |1855,1854,1854,1859,Salted paper print from paper negative,Image: 23.3 x 33.2 cm (9 3/16 x 13 1/16 in.) Mount: 44.8 x 59.7 cm (17 5/8 x 23 1/2 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286955,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.57,false,true,286956,Photographs,Photograph,"Jérusalem, Escalier antique taillé dans le roc, conduisant à l'ancienne Porte du Fumier",,,,,,Artist|Printer,,"Auguste Salzmann|Imprimerie photographique de Blanquart-Évrard, à Lille","French, 1824–1872|French, active 1851–55",,"Salzmann, Auguste|Imprimerie photographique de Blanquart-Évrard, à Lille",French|French,1824 |1851,1872 |1855,1854,1854,1859,Salted paper print from paper negative,Image: 33.3 x 23.5 cm (13 1/8 x 9 1/4 in.) Mount: 59.9 x 44.7 cm (23 9/16 x 17 5/8 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286956,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.58,false,true,286957,Photographs,Photograph,"Jérusalem, Tombeau des Juges, Vue générale",,,,,,Artist|Printer,,"Auguste Salzmann|Imprimerie photographique de Blanquart-Évrard, à Lille","French, 1824–1872|French, active 1851–55",,"Salzmann, Auguste|Imprimerie photographique de Blanquart-Évrard, à Lille",French|French,1824 |1851,1872 |1855,1854,1854,1859,Salted paper print from paper negative,Image: 23.2 x 33.3 cm (9 1/8 x 13 1/8 in.) Mount: 45.2 x 60.2 cm (17 13/16 x 23 11/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286957,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.59,false,true,286958,Photographs,Photograph,"Jérusalem, Tombeau des Juges, Détails",,,,,,Artist|Printer,,"Auguste Salzmann|Imprimerie photographique de Blanquart-Évrard, à Lille","French, 1824–1872|French, active 1851–55",,"Salzmann, Auguste|Imprimerie photographique de Blanquart-Évrard, à Lille",French|French,1824 |1851,1872 |1855,1854,1854,1859,Salted paper print from paper negative,Image: 23.2 x 32.5 cm (9 1/8 x 12 13/16 in.) Mount: 44.8 x 60.1 cm (17 5/8 x 23 11/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286958,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.60,false,true,286959,Photographs,Photograph,"Jérusalem, Tombeau Juif, à trois milles Nord-Nord-Ouest de Jérusalem",,,,,,Artist|Printer,,"Auguste Salzmann|Imprimerie photographique de Blanquart-Évrard, à Lille","French, 1824–1872|French, active 1851–55",,"Salzmann, Auguste|Imprimerie photographique de Blanquart-Évrard, à Lille",French|French,1824 |1851,1872 |1855,1854,1854,1859,Salted paper print from paper negative,Image: 23 x 33.3 cm (9 1/16 x 13 1/8 in.) Mount: 45 x 60.3 cm (17 11/16 x 23 3/4 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286959,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.61,false,true,286960,Photographs,Photograph,"Jérusalem, Tombeau Juif, Détails",,,,,,Artist|Printer,,"Auguste Salzmann|Imprimerie photographique de Blanquart-Évrard, à Lille","French, 1824–1872|French, active 1851–55",,"Salzmann, Auguste|Imprimerie photographique de Blanquart-Évrard, à Lille",French|French,1824 |1851,1872 |1855,1854,1854,1859,Salted paper print from paper negative,Image: 23.7 x 33.6 cm (9 5/16 x 13 1/4 in.) Mount: 44.9 x 60.4 cm (17 11/16 x 23 3/4 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286960,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.62,false,true,286961,Photographs,Photograph,"Jérusalem, Tombeau de Salomon, Vue générale",,,,,,Artist|Printer,,"Auguste Salzmann|Imprimerie photographique de Blanquart-Évrard, à Lille","French, 1824–1872|French, active 1851–55",,"Salzmann, Auguste|Imprimerie photographique de Blanquart-Évrard, à Lille",French|French,1824 |1851,1872 |1855,1854,1854,1859,Salted paper print from paper negative,Image: 22.9 x 32.7 cm (9 x 12 7/8 in.) Mount: 44.7 x 60.1 cm (17 5/8 x 23 11/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286961,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.63,false,true,286962,Photographs,Photograph,"Jérusalem, Tombeau de Salomon, Détails",,,,,,Artist|Printer,,"Auguste Salzmann|Imprimerie photographique de Blanquart-Évrard, à Lille","French, 1824–1872|French, active 1851–55",,"Salzmann, Auguste|Imprimerie photographique de Blanquart-Évrard, à Lille",French|French,1824 |1851,1872 |1855,1854,1854,1859,Salted paper print from paper negative,Image: 23 x 33.1 cm (9 1/16 x 13 1/16 in.) Mount: 44.8 x 60.1 cm (17 5/8 x 23 11/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286962,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.64,false,true,286963,Photographs,Photograph,"Jérusalem, Birket-Mamillah",,,,,,Artist|Printer,,"Auguste Salzmann|Imprimerie photographique de Blanquart-Évrard, à Lille","French, 1824–1872|French, active 1851–55",,"Salzmann, Auguste|Imprimerie photographique de Blanquart-Évrard, à Lille",French|French,1824 |1851,1872 |1855,1854,1854,1859,Salted paper print from paper negative,Image: 23.7 x 32.8 cm (9 5/16 x 12 15/16 in.) Mount: 44.6 x 60.1 cm (17 9/16 x 23 11/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286963,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.65,false,true,286964,Photographs,Photograph,"Jérusalem, Birket-es-Soutlan",,,,,,Artist|Printer,,"Auguste Salzmann|Imprimerie photographique de Blanquart-Évrard, à Lille","French, 1824–1872|French, active 1851–55",,"Salzmann, Auguste|Imprimerie photographique de Blanquart-Évrard, à Lille",French|French,1824 |1851,1872 |1855,1854,1854,1859,Salted paper print from paper negative,Image: 23.2 x 32.8 cm (9 1/8 x 12 15/16 in.) Mount: 44.8 x 60.1 cm (17 5/8 x 23 11/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286964,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.66,false,true,286965,Photographs,Photograph,"Jérusalem, Birket-Hammam-el-Batrak",,,,,,Artist|Printer,,"Auguste Salzmann|Imprimerie photographique de Blanquart-Évrard, à Lille","French, 1824–1872|French, active 1851–55",,"Salzmann, Auguste|Imprimerie photographique de Blanquart-Évrard, à Lille",French|French,1824 |1851,1872 |1855,1854,1854,1859,Salted paper print from paper negative,Image: 23.5 x 33.2 cm (9 1/4 x 13 1/16 in.) Mount: 44.8 x 59.5 cm (17 5/8 x 23 7/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286965,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.67,false,true,286966,Photographs,Photograph,"Jérusalem, Carrière à la Porte de Damas",,,,,,Artist|Printer,,"Auguste Salzmann|Imprimerie photographique de Blanquart-Évrard, à Lille","French, 1824–1872|French, active 1851–55",,"Salzmann, Auguste|Imprimerie photographique de Blanquart-Évrard, à Lille",French|French,1824 |1851,1872 |1855,1854,1854,1859,Salted paper print from paper negative,Image: 23.5 x 33.4 cm (9 1/4 x 13 1/8 in.) Mount: 44.7 x 59.4 cm (17 5/8 x 23 3/8 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286966,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.68,false,true,286967,Photographs,Photograph,"Jérusalem, Birket-Hammam-Setty-Mariam",,,,,,Artist|Printer,,"Auguste Salzmann|Imprimerie photographique de Blanquart-Évrard, à Lille","French, 1824–1872|French, active 1851–55",,"Salzmann, Auguste|Imprimerie photographique de Blanquart-Évrard, à Lille",French|French,1824 |1851,1872 |1855,1854,1854,1859,Salted paper print from paper negative,Image: 23.1 x 33.1 cm (9 1/8 x 13 1/16 in.) Mount: 44.5 x 59.6 cm (17 1/2 x 23 7/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286967,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.69,false,true,286968,Photographs,Photograph,"Jérusalem, Tour de David",,,,,,Artist|Printer,,"Auguste Salzmann|Imprimerie photographique de Blanquart-Évrard, à Lille","French, 1824–1872|French, active 1851–55",,"Salzmann, Auguste|Imprimerie photographique de Blanquart-Évrard, à Lille",French|French,1824 |1851,1872 |1855,1854,1854,1859,Salted paper print from paper negative,Image: 23.4 x 32.9 cm (9 3/16 x 12 15/16 in.) Mount: 44.6 x 59.6 cm (17 9/16 x 23 7/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286968,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.70,false,true,286969,Photographs,Photograph,"Jérusalem, Restes de scupltures judaïques",,,,,,Artist|Printer,,"Auguste Salzmann|Imprimerie photographique de Blanquart-Évrard, à Lille","French, 1824–1872|French, active 1851–55",,"Salzmann, Auguste|Imprimerie photographique de Blanquart-Évrard, à Lille",French|French,1824 |1851,1872 |1855,1854,1854,1859,Salted paper print from paper negative,Image: 23.5 x 33 cm (9 1/4 x 13 in.) Mount: 44.5 x 60.6 cm (17 1/2 x 23 7/8 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286969,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.71,false,true,283150,Photographs,Photograph,"Jérusalem, Sarcophage judaïque",,,,,,Artist|Printer,,"Auguste Salzmann|Imprimerie photographique de Blanquart-Évrard, à Lille","French, 1824–1872|French, active 1851–55",,"Salzmann, Auguste|Imprimerie photographique de Blanquart-Évrard, à Lille",French|French,1824 |1851,1872 |1855,1854,1854,1859,Salted paper print from paper negative,Image: 23.2 × 32.7 cm (9 1/8 × 12 7/8 in.) Mount: 44.5 x 59.7 cm (17 1/2 x 23 1/2 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/283150,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.72,false,true,286970,Photographs,Photograph,"Jérusalem, Casque trouvé dans le Jourdain, 1",,,,,,Artist|Printer,,"Auguste Salzmann|Imprimerie photographique de Blanquart-Évrard, à Lille","French, 1824–1872|French, active 1851–55",,"Salzmann, Auguste|Imprimerie photographique de Blanquart-Évrard, à Lille",French|French,1824 |1851,1872 |1855,1854,1854,1859,Salted paper print from paper negative,Image: 23.5 x 33.3 cm (9 1/4 x 13 1/8 in.) Mount: 45.1 x 60.2 cm (17 3/4 x 23 11/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286970,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.73,false,true,286971,Photographs,Photograph,"Jérusalem, Casque trouvé dans le Jourdain, 2",,,,,,Artist|Printer,,"Auguste Salzmann|Imprimerie photographique de Blanquart-Évrard, à Lille","French, 1824–1872|French, active 1851–55",,"Salzmann, Auguste|Imprimerie photographique de Blanquart-Évrard, à Lille",French|French,1824 |1851,1872 |1855,1854,1854,1859,Salted paper print from paper negative,Image: 23.2 x 33 cm (9 1/8 x 13 in.) Mount: 44.7 x 59.9 cm (17 5/8 x 23 9/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286971,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.74,false,true,286972,Photographs,Photograph,"Jérusalem, Fragments judaïque et romain",,,,,,Artist|Printer,,"Auguste Salzmann|Imprimerie photographique de Blanquart-Évrard, à Lille","French, 1824–1872|French, active 1851–55",,"Salzmann, Auguste|Imprimerie photographique de Blanquart-Évrard, à Lille",French|French,1824 |1851,1872 |1855,1854,1854,1859,Salted paper print from paper negative,Image: 23.5 x 30.9 cm (9 1/4 x 12 3/16 in.) Mount: 44.8 x 59.7 cm (17 5/8 x 23 1/2 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286972,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.75,false,true,286973,Photographs,Photograph,"Jérusalem, Forteresse de Soin",,,,,,Artist|Printer,,"Auguste Salzmann|Imprimerie photographique de Blanquart-Évrard, à Lille","French, 1824–1872|French, active 1851–55",,"Salzmann, Auguste|Imprimerie photographique de Blanquart-Évrard, à Lille",French|French,1824 |1851,1872 |1855,1854,1854,1859,Salted paper print from paper negative,Image: 23.5 x 33.3 cm (9 1/4 x 13 1/8 in.) Mount: 44.6 x 60 cm (17 9/16 x 23 5/8 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286973,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.76,false,true,286974,Photographs,Photograph,"Jérusalem, Arc de l'Ecce-Homo",,,,,,Artist|Printer,,"Auguste Salzmann|Imprimerie photographique de Blanquart-Évrard, à Lille","French, 1824–1872|French, active 1851–55",,"Salzmann, Auguste|Imprimerie photographique de Blanquart-Évrard, à Lille",French|French,1824 |1851,1872 |1855,1854,1854,1859,Salted paper print from paper negative,Image: 33.2 x 23.5 cm (13 1/16 x 9 1/4 in.) Mount: 59.9 x 45.2 cm (23 9/16 x 17 13/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286974,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.77,false,true,286975,Photographs,Photograph,"Jérusalem, Arc de l'Ecce-Homo, Détails",,,,,,Artist|Printer,,"Auguste Salzmann|Imprimerie photographique de Blanquart-Évrard, à Lille","French, 1824–1872|French, active 1851–55",,"Salzmann, Auguste|Imprimerie photographique de Blanquart-Évrard, à Lille",French|French,1824 |1851,1872 |1855,1854,1854,1859,Salted paper print from paper negative,Image: 33.3 x 23.3 cm (13 1/8 x 9 3/16 in.) Mount: 59.8 x 44.8 cm (23 9/16 x 17 5/8 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286975,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.78,false,true,286976,Photographs,Photograph,"Jérusalem, Fontaine de Saint-Philippe",,,,,,Artist|Printer,,"Auguste Salzmann|Imprimerie photographique de Blanquart-Évrard, à Lille","French, 1824–1872|French, active 1851–55",,"Salzmann, Auguste|Imprimerie photographique de Blanquart-Évrard, à Lille",French|French,1824 |1851,1872 |1855,1854,1854,1859,Salted paper print from paper negative,Image: 33.7 x 23.6 cm (13 1/4 x 9 5/16 in.) Mount: 60.3 x 44.5 cm (23 3/4 x 17 1/2 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286976,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.79,false,true,286977,Photographs,Photograph,"Jérusalem, Colonne de la Porte judiciaire",,,,,,Artist|Printer,,"Auguste Salzmann|Imprimerie photographique de Blanquart-Évrard, à Lille","French, 1824–1872|French, active 1851–55",,"Salzmann, Auguste|Imprimerie photographique de Blanquart-Évrard, à Lille",French|French,1824 |1851,1872 |1855,1854,1854,1859,Salted paper print from paper negative,Image: 33.2 x 23.6 cm (13 1/16 x 9 5/16 in.) Mount: 59.9 x 44.5 cm (23 9/16 x 17 1/2 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286977,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.80,false,true,286978,Photographs,Photograph,"Jérusalem, Grotte de Jérémie",,,,,,Artist|Printer,,"Auguste Salzmann|Imprimerie photographique de Blanquart-Évrard, à Lille","French, 1824–1872|French, active 1851–55",,"Salzmann, Auguste|Imprimerie photographique de Blanquart-Évrard, à Lille",French|French,1824 |1851,1872 |1855,1854,1854,1859,Salted paper print from paper negative,Image: 22.8 x 32.5 cm (9 x 12 13/16 in.) Mount: 44.7 x 59.3 cm (17 5/8 x 23 3/8 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286978,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.81,false,true,286979,Photographs,Photograph,"Jérusalem, Via Dolorosa, Reste antique",,,,,,Artist|Printer,,"Auguste Salzmann|Imprimerie photographique de Blanquart-Évrard, à Lille","French, 1824–1872|French, active 1851–55",,"Salzmann, Auguste|Imprimerie photographique de Blanquart-Évrard, à Lille",French|French,1824 |1851,1872 |1855,1854,1854,1859,Salted paper print from paper negative,Image: 33.3 x 23.3 cm (13 1/8 x 9 3/16 in.) Mount: 60.2 x 44.8 cm (23 11/16 x 17 5/8 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286979,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.83,false,true,286980,Photographs,Photograph,"Jérusalem, Saint-Sépulcre, Entrée principale",,,,,,Artist|Printer,,"Auguste Salzmann|Imprimerie photographique de Blanquart-Évrard, à Lille","French, 1824–1872|French, active 1851–55",,"Salzmann, Auguste|Imprimerie photographique de Blanquart-Évrard, à Lille",French|French,1824 |1851,1872 |1855,1854,1854,1859,Salted paper print from paper negative,Image: 23.4 x 32.4 cm (9 3/16 x 12 3/4 in.) Mount: 45 x 60.1 cm (17 11/16 x 23 11/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286980,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.84,false,true,286981,Photographs,Photograph,"Jérusalem, Saint-Sépulcre, Bas-relief (porte d'entrée)",,,,,,Artist|Printer,,"Auguste Salzmann|Imprimerie photographique de Blanquart-Évrard, à Lille","French, 1824–1872|French, active 1851–55",,"Salzmann, Auguste|Imprimerie photographique de Blanquart-Évrard, à Lille",French|French,1824 |1851,1872 |1855,1854,1854,1859,Salted paper print from paper negative,Image: 23.4 x 31.6 cm (9 3/16 x 12 7/16 in.) Mount: 45.1 x 59.9 cm (17 3/4 x 23 9/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286981,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.85,false,true,286982,Photographs,Photograph,"Jérusalem, Saint-Sépulcre, Bas-relief (porte murée)",,,,,,Artist|Printer,,"Auguste Salzmann|Imprimerie photographique de Blanquart-Évrard, à Lille","French, 1824–1872|French, active 1851–55",,"Salzmann, Auguste|Imprimerie photographique de Blanquart-Évrard, à Lille",French|French,1824 |1851,1872 |1855,1854,1854,1859,Salted paper print from paper negative,Image: 23.6 x 32.8 cm (9 5/16 x 12 15/16 in.) Mount: 44.3 x 60.5 cm (17 7/16 x 23 13/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286982,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.87,false,true,286983,Photographs,Photograph,"Jérusalem, Saint-Sépulcre, Détails de la porte",,,,,,Artist|Printer,,"Auguste Salzmann|Imprimerie photographique de Blanquart-Évrard, à Lille","French, 1824–1872|French, active 1851–55",,"Salzmann, Auguste|Imprimerie photographique de Blanquart-Évrard, à Lille",French|French,1824 |1851,1872 |1855,1854,1854,1859,Salted paper print from paper negative,Image: 32.3 x 23.3 cm (12 11/16 x 9 3/16 in.) Mount: 60.3 x 44.7 cm (23 3/4 x 17 5/8 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286983,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.88,false,true,286984,Photographs,Photograph,"Jérusalem, Saint-Sépulcre, Détails des chapiteaux de la porte principale",,,,,,Artist|Printer,,"Auguste Salzmann|Imprimerie photographique de Blanquart-Évrard, à Lille","French, 1824–1872|French, active 1851–55",,"Salzmann, Auguste|Imprimerie photographique de Blanquart-Évrard, à Lille",French|French,1824 |1851,1872 |1855,1854,1854,1859,Salted paper print from paper negative,Image: 32.9 x 23.2 cm (12 15/16 x 9 1/8 in.) Mount: 59.6 x 44.4 cm (23 7/16 x 17 1/2 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286984,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.89,false,true,286985,Photographs,Photograph,"Jérusalem, Saint-Sépulcre, Partie supérieure de la façade",,,,,,Artist|Printer,,"Auguste Salzmann|Imprimerie photographique de Blanquart-Évrard, à Lille","French, 1824–1872|French, active 1851–55",,"Salzmann, Auguste|Imprimerie photographique de Blanquart-Évrard, à Lille",French|French,1824 |1851,1872 |1855,1854,1854,1859,Salted paper print from paper negative,Image: 23.4 x 32.9 cm (9 3/16 x 12 15/16 in.) Mount: 44.6 x 60 cm (17 9/16 x 23 5/8 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286985,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.91,false,true,286986,Photographs,Photograph,"Jérusalem, Saint-Sépulcre, Chapelle du Calvaire",,,,,,Artist|Printer,,"Auguste Salzmann|Imprimerie photographique de Blanquart-Évrard, à Lille","French, 1824–1872|French, active 1851–55",,"Salzmann, Auguste|Imprimerie photographique de Blanquart-Évrard, à Lille",French|French,1824 |1851,1872 |1855,1854,1854,1859,Salted paper print from paper negative,Image: 32.5 x 23.4 cm (12 13/16 x 9 3/16 in.) Mount: 59.8 x 44.6 cm (23 9/16 x 17 9/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286986,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.92,false,true,286987,Photographs,Photograph,"Jérusalem, Saint-Sépulcre, Détails de la Chapelle du Calvaire",,,,,,Artist|Printer,,"Auguste Salzmann|Imprimerie photographique de Blanquart-Évrard, à Lille","French, 1824–1872|French, active 1851–55",,"Salzmann, Auguste|Imprimerie photographique de Blanquart-Évrard, à Lille",French|French,1824 |1851,1872 |1855,1854,1854,1859,Salted paper print from paper negative,Image: 32.7 x 23.4 cm (12 7/8 x 9 3/16 in.) Mount: 59.9 x 44.6 cm (23 9/16 x 17 9/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286987,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.93,false,true,286988,Photographs,Photograph,"Jérusalem, Saint-Sépulcre, Intérieur de la Chapelle du Calvaire",,,,,,Artist|Printer,,"Auguste Salzmann|Imprimerie photographique de Blanquart-Évrard, à Lille","French, 1824–1872|French, active 1851–55",,"Salzmann, Auguste|Imprimerie photographique de Blanquart-Évrard, à Lille",French|French,1824 |1851,1872 |1855,1854,1854,1859,Salted paper print from paper negative,Image: 32.9 x 23.4 cm (12 15/16 x 9 3/16 in.) Mount: 59.9 x 44.7 cm (23 9/16 x 17 5/8 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286988,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.94,false,true,286989,Photographs,Photograph,"Jérusalem, Saint-Sépulcre, Détails de la façade",,,,,,Artist|Printer,,"Auguste Salzmann|Imprimerie photographique de Blanquart-Évrard, à Lille","French, 1824–1872|French, active 1851–55",,"Salzmann, Auguste|Imprimerie photographique de Blanquart-Évrard, à Lille",French|French,1824 |1851,1872 |1855,1854,1854,1859,Salted paper print from paper negative,Image: 22.7 × 33.1 cm (8 15/16 × 13 1/16 in.) Mount: 59.9 x 44.6 cm (23 9/16 x 17 9/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286989,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.95,false,true,286990,Photographs,Photograph,"Jérusalem, Saint-Sépulcre, Vue générale, 1",,,,,,Artist|Printer,,"Auguste Salzmann|Imprimerie photographique de Blanquart-Évrard, à Lille","French, 1824–1872|French, active 1851–55",,"Salzmann, Auguste|Imprimerie photographique de Blanquart-Évrard, à Lille",French|French,1824 |1851,1872 |1855,1854,1854,1859,Salted paper print from paper negative,Image: 23.1 × 33.1 cm (9 1/8 × 13 1/16 in.) Mount: 60 x 44.6 cm (23 5/8 x 17 9/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286990,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.98,false,true,286991,Photographs,Photograph,"Jérusalem, Saint-Sépulcre, Clocher",,,,,,Artist|Printer,,"Auguste Salzmann|Imprimerie photographique de Blanquart-Évrard, à Lille","French, 1824–1872|French, active 1851–55",,"Salzmann, Auguste|Imprimerie photographique de Blanquart-Évrard, à Lille",French|French,1824 |1851,1872 |1855,1854,1854,1859,Salted paper print from paper negative,Image: 32.9 x 23.5 cm (12 15/16 x 9 1/4 in.) Mount: 60 x 45.2 cm (23 5/8 x 17 13/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286991,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.99,false,true,286992,Photographs,Photograph,"Jérusalem, Saint-Sépulcre, Détails du Clocher",,,,,,Artist|Printer,,"Auguste Salzmann|Imprimerie photographique de Blanquart-Évrard, à Lille","French, 1824–1872|French, active 1851–55",,"Salzmann, Auguste|Imprimerie photographique de Blanquart-Évrard, à Lille",French|French,1824 |1851,1872 |1855,1854,1854,1859,Salted paper print from paper negative,Image: 32.5 × 23.5 cm (12 13/16 × 9 1/4 in.) Mount: 60.2 x 44.6 cm (23 11/16 x 17 9/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286992,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.101,false,true,286993,Photographs,Photograph,"Jérusalem, Saint-Sépulcre, Porte Ouest",,,,,,Artist|Printer,,"Auguste Salzmann|Imprimerie photographique de Blanquart-Évrard, à Lille","French, 1824–1872|French, active 1851–55",,"Salzmann, Auguste|Imprimerie photographique de Blanquart-Évrard, à Lille",French|French,1824 |1851,1872 |1855,1854,1854,1859,Salted paper print from paper negative,Image: 33.3 × 23.7 cm (13 1/8 × 9 5/16 in.) Mount: 59.6 x 44.7 cm (23 7/16 x 17 5/8 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286993,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.102,false,true,286994,Photographs,Photograph,"Jérusalem, Saint-Sépulcre, Face Ouest, Rue du Patriarche",,,,,,Artist|Printer,,"Auguste Salzmann|Imprimerie photographique de Blanquart-Évrard, à Lille","French, 1824–1872|French, active 1851–55",,"Salzmann, Auguste|Imprimerie photographique de Blanquart-Évrard, à Lille",French|French,1824 |1851,1872 |1855,1854,1854,1859,Salted paper print from paper negative,Image: 32.4 × 23.8 cm (12 3/4 × 9 3/8 in.) Mount: 59.5 x 45 cm (23 7/16 x 17 11/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286994,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.103,false,true,286995,Photographs,Photograph,"Jérusalem, Saint-Sépulcre, Colonne du parvis",,,,,,Artist|Printer,,"Auguste Salzmann|Imprimerie photographique de Blanquart-Évrard, à Lille","French, 1824–1872|French, active 1851–55",,"Salzmann, Auguste|Imprimerie photographique de Blanquart-Évrard, à Lille",French|French,1824 |1851,1872 |1855,1854,1854,1859,Salted paper print from paper negative,Image: 32.6 x 23 cm (12 13/16 x 9 1/16 in.) Mount: 59.6 x 45 cm (23 7/16 x 17 11/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286995,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.104,false,true,286996,Photographs,Photograph,"Jérusalem, Epée de Godefroy de Bouillon",,,,,,Artist|Printer,,"Auguste Salzmann|Imprimerie photographique de Blanquart-Évrard, à Lille","French, 1824–1872|French, active 1851–55",,"Salzmann, Auguste|Imprimerie photographique de Blanquart-Évrard, à Lille",French|French,1824 |1851,1872 |1855,1854,1854,1859,Salted paper print from paper negative,Image: 32.6 x 23.1 cm (12 13/16 x 9 1/8 in.) Mount: 60 x 44.5 cm (23 5/8 x 17 1/2 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286996,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.106,false,true,286997,Photographs,Photograph,"Jérusalem, Sainte-Marie-la-Grande, Portail",,,,,,Artist|Printer,,"Auguste Salzmann|Imprimerie photographique de Blanquart-Évrard, à Lille","French, 1824–1872|French, active 1851–55",,"Salzmann, Auguste|Imprimerie photographique de Blanquart-Évrard, à Lille",French|French,1824 |1851,1872 |1855,1854,1854,1859,Salted paper print from paper negative,Image: 21.7 × 33.4 cm (8 9/16 × 13 1/8 in.) Mount: 44.9 x 59.4 cm (17 11/16 x 23 3/8 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286997,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.107,false,true,286998,Photographs,Photograph,"Jérusalem, Sainte-Marie-la-Grande, Détails de la porte",,,,,,Artist|Printer,,"Auguste Salzmann|Imprimerie photographique de Blanquart-Évrard, à Lille","French, 1824–1872|French, active 1851–55",,"Salzmann, Auguste|Imprimerie photographique de Blanquart-Évrard, à Lille",French|French,1824 |1851,1872 |1855,1854,1854,1859,Salted paper print from paper negative,Image: 23.3 x 32.7 cm (9 3/16 x 12 7/8 in.) Mount: 44.7 x 60.3 cm (17 5/8 x 23 3/4 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286998,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.108,false,true,286999,Photographs,Photograph,"Jérusalem, Sainte-Marie-la-Grande, Cloître",,,,,,Artist|Printer,,"Auguste Salzmann|Imprimerie photographique de Blanquart-Évrard, à Lille","French, 1824–1872|French, active 1851–55",,"Salzmann, Auguste|Imprimerie photographique de Blanquart-Évrard, à Lille",French|French,1824 |1851,1872 |1855,1854,1854,1859,Salted paper print from paper negative,Image: 23 x 32.9 cm (9 1/16 x 12 15/16 in.) Mount: 44.3 x 60.4 cm (17 7/16 x 23 3/4 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286999,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.109,false,true,287000,Photographs,Photograph,"Jérusalem, Sainte-Marie-la-Grande, Vue générale",,,,,,Artist|Printer,,"Auguste Salzmann|Imprimerie photographique de Blanquart-Évrard, à Lille","French, 1824–1872|French, active 1851–55",,"Salzmann, Auguste|Imprimerie photographique de Blanquart-Évrard, à Lille",French|French,1824 |1851,1872 |1855,1854,1854,1859,Salted paper print from paper negative,Image: 23.5 x 33.5 cm (9 1/4 x 13 3/16 in.) Mount: 44.9 x 59.7 cm (17 11/16 x 23 1/2 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287000,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.110,false,true,287001,Photographs,Photograph,"Jérusalem, Sainte-Marie-la-Latine",,,,,,Artist|Printer,,"Auguste Salzmann|Imprimerie photographique de Blanquart-Évrard, à Lille","French, 1824–1872|French, active 1851–55",,"Salzmann, Auguste|Imprimerie photographique de Blanquart-Évrard, à Lille",French|French,1824 |1851,1872 |1855,1854,1854,1859,Salted paper print from paper negative,Image: 23.4 x 32.7 cm (9 3/16 x 12 7/8 in.) Mount: 44.9 x 61 cm (17 11/16 x 24 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287001,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.111,false,true,287002,Photographs,Photograph,"Jérusalem, Enceinte de l'Hopital des Chevaliers-de-Saint-Jean, Côté Sud",,,,,,Artist|Printer,,"Auguste Salzmann|Imprimerie photographique de Blanquart-Évrard, à Lille","French, 1824–1872|French, active 1851–55",,"Salzmann, Auguste|Imprimerie photographique de Blanquart-Évrard, à Lille",French|French,1824 |1851,1872 |1855,1854,1854,1859,Salted paper print from paper negative,Image: 23.4 x 33.1 cm (9 3/16 x 13 1/16 in.) Mount: 45.1 x 59.8 cm (17 3/4 x 23 9/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287002,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.112,false,true,287003,Photographs,Photograph,"Jérusalem, Fontaine du Couvent grec",,,,,,Artist|Printer,,"Auguste Salzmann|Imprimerie photographique de Blanquart-Évrard, à Lille","French, 1824–1872|French, active 1851–55",,"Salzmann, Auguste|Imprimerie photographique de Blanquart-Évrard, à Lille",French|French,1824 |1851,1872 |1855,1854,1854,1859,Salted paper print from paper negative,Image: 23.4 x 32.3 cm (9 3/16 x 12 11/16 in.) Mount: 44.8 x 60 cm (17 5/8 x 23 5/8 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287003,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.113,false,true,287004,Photographs,Photograph,"Jérusalem, Église Sainte-Anne, Vue générale",,,,,,Artist|Printer,,"Auguste Salzmann|Imprimerie photographique de Blanquart-Évrard, à Lille","French, 1824–1872|French, active 1851–55",,"Salzmann, Auguste|Imprimerie photographique de Blanquart-Évrard, à Lille",French|French,1824 |1851,1872 |1855,1854,1854,1859,Salted paper print from paper negative,Image: 33.2 x 23.7 cm (13 1/16 x 9 5/16 in.) Mount: 60.4 x 44.8 cm (23 3/4 x 17 5/8 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287004,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.114,false,true,287005,Photographs,Photograph,"Jérusalem, Église Sainte-Anne, Façade",,,,,,Artist|Printer,,"Auguste Salzmann|Imprimerie photographique de Blanquart-Évrard, à Lille","French, 1824–1872|French, active 1851–55",,"Salzmann, Auguste|Imprimerie photographique de Blanquart-Évrard, à Lille",French|French,1824 |1851,1872 |1855,1854,1854,1859,Salted paper print from paper negative,Image: 32.5 x 23.4 cm (12 13/16 x 9 3/16 in.) Mount: 59.3 x 44.6 cm (23 3/8 x 17 9/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287005,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.115,false,true,287006,Photographs,Photograph,"Jérusalem, Église Sainte-Anne, Détails du portail",,,,,,Artist|Printer,,"Auguste Salzmann|Imprimerie photographique de Blanquart-Évrard, à Lille","French, 1824–1872|French, active 1851–55",,"Salzmann, Auguste|Imprimerie photographique de Blanquart-Évrard, à Lille",French|French,1824 |1851,1872 |1855,1854,1854,1859,Salted paper print from paper negative,Image: 23.2 x 32.7 cm (9 1/8 x 12 7/8 in.) Mount: 45.1 x 59.8 cm (17 3/4 x 23 9/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287006,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.116,false,true,287007,Photographs,Photograph,"Jérusalem, Tombeau de la Vierge",,,,,,Artist|Printer,,"Auguste Salzmann|Imprimerie photographique de Blanquart-Évrard, à Lille","French, 1824–1872|French, active 1851–55",,"Salzmann, Auguste|Imprimerie photographique de Blanquart-Évrard, à Lille",French|French,1824 |1851,1872 |1855,1854,1854,1859,Salted paper print from paper negative,Image: 23.5 x 33 cm (9 1/4 x 13 in.) Mount: 44.6 x 60.1 cm (17 9/16 x 23 11/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287007,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.117,false,true,287008,Photographs,Photograph,"Jérusalem, Chapelle de l'Ascension",,,,,,Artist|Printer,,"Auguste Salzmann|Imprimerie photographique de Blanquart-Évrard, à Lille","French, 1824–1872|French, active 1851–55",,"Salzmann, Auguste|Imprimerie photographique de Blanquart-Évrard, à Lille",French|French,1824 |1851,1872 |1855,1854,1854,1859,Salted paper print from paper negative,Image: 23.2 x 33.3 cm (9 1/8 x 13 1/8 in.) Mount: 44.7 x 59.5 cm (17 5/8 x 23 7/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287008,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.118,false,true,287009,Photographs,Photograph,"Jérusalem, Église de Sainte-Marie-Madeleine",,,,,,Artist|Printer,,"Auguste Salzmann|Imprimerie photographique de Blanquart-Évrard, à Lille","French, 1824–1872|French, active 1851–55",,"Salzmann, Auguste|Imprimerie photographique de Blanquart-Évrard, à Lille",French|French,1824 |1851,1872 |1855,1854,1854,1859,Salted paper print from paper negative,Image: 23.4 x 33.2 cm (9 3/16 x 13 1/16 in.) Mount: 45 x 59.3 cm (17 11/16 x 23 3/8 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287009,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.119,false,true,287010,Photographs,Photograph,"Jérusalem, Palais de rois de Jérusalem, Vue générale",,,,,,Artist|Printer,,"Auguste Salzmann|Imprimerie photographique de Blanquart-Évrard, à Lille","French, 1824–1872|French, active 1851–55",,"Salzmann, Auguste|Imprimerie photographique de Blanquart-Évrard, à Lille",French|French,1824 |1851,1872 |1855,1854,1854,1859,Salted paper print from paper negative,Image: 23.3 x 33.3 cm (9 3/16 x 13 1/8 in.) Mount: 44.8 x 59.6 cm (17 5/8 x 23 7/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287010,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.120,false,true,287011,Photographs,Photograph,"Jérusalem, Palais de rois de Jérusalem, Entrée principale",,,,,,Artist|Printer,,"Auguste Salzmann|Imprimerie photographique de Blanquart-Évrard, à Lille","French, 1824–1872|French, active 1851–55",,"Salzmann, Auguste|Imprimerie photographique de Blanquart-Évrard, à Lille",French|French,1824 |1851,1872 |1855,1854,1854,1859,Salted paper print from paper negative,Image: 32.9 x 23.3 cm (12 15/16 x 9 3/16 in.) Mount: 59.7 x 44.7 cm (23 1/2 x 17 5/8 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287011,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.121,false,true,287012,Photographs,Photograph,"Jérusalem, Auberge d'Allemagne",,,,,,Artist|Printer,,"Auguste Salzmann|Imprimerie photographique de Blanquart-Évrard, à Lille","French, 1824–1872|French, active 1851–55",,"Salzmann, Auguste|Imprimerie photographique de Blanquart-Évrard, à Lille",French|French,1824 |1851,1872 |1855,1854,1854,1859,Salted paper print from paper negative,Image: 33.2 x 23.4 cm (13 1/16 x 9 3/16 in.) Mount: 60.1 x 44.7 cm (23 11/16 x 17 5/8 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287012,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.122,false,true,287013,Photographs,Photograph,"Jérusalem, Couvent Arménien, Ornements, 1",,,,,,Artist|Printer,,"Auguste Salzmann|Imprimerie photographique de Blanquart-Évrard, à Lille","French, 1824–1872|French, active 1851–55",,"Salzmann, Auguste|Imprimerie photographique de Blanquart-Évrard, à Lille",French|French,1824 |1851,1872 |1855,1854,1854,1859,Salted paper print from paper negative,Image: 31 x 22.9 cm (12 3/16 x 9 in.) Mount: 59.9 x 44.8 cm (23 9/16 x 17 5/8 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287013,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.123,false,true,287014,Photographs,Photograph,"Jérusalem, Couvent Arménien, Ornements, 2",,,,,,Artist|Printer,,"Auguste Salzmann|Imprimerie photographique de Blanquart-Évrard, à Lille","French, 1824–1872|French, active 1851–55",,"Salzmann, Auguste|Imprimerie photographique de Blanquart-Évrard, à Lille",French|French,1824 |1851,1872 |1855,1854,1854,1859,Salted paper print from paper negative,Image: 23.1 x 33 cm (9 1/8 x 13 in.) Mount: 44.9 x 60 cm (17 11/16 x 23 5/8 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287014,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.124,false,true,287015,Photographs,Photograph,"Jérusalem, Chapelle anglaise",,,,,,Artist|Printer,,"Auguste Salzmann|Imprimerie photographique de Blanquart-Évrard, à Lille","French, 1824–1872|French, active 1851–55",,"Salzmann, Auguste|Imprimerie photographique de Blanquart-Évrard, à Lille",French|French,1824 |1851,1872 |1855,1854,1854,1859,Salted paper print from paper negative,Image: 23.5 x 32.6 cm (9 1/4 x 12 13/16 in.) Mount: 44.8 x 60 cm (17 5/8 x 23 5/8 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287015,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.125,false,true,287016,Photographs,Photograph,"Jérusalem, Beit-Lehem, Vue générale",,,,,,Artist|Printer,,"Auguste Salzmann|Imprimerie photographique de Blanquart-Évrard, à Lille","French, 1824–1872|French, active 1851–55",,"Salzmann, Auguste|Imprimerie photographique de Blanquart-Évrard, à Lille",French|French,1824 |1851,1872 |1855,1854,1854,1859,Salted paper print from paper negative,Image: 22.6 x 27.6 cm (8 7/8 x 10 7/8 in.) Mount: 44.6 x 60.3 cm (17 9/16 x 23 3/4 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287016,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.126,false,true,287017,Photographs,Photograph,"Beit-Lehem, Mosaïque de l'Église I",,,,,,Artist|Printer,,"Auguste Salzmann|Imprimerie photographique de Blanquart-Évrard, à Lille","French, 1824–1872|French, active 1851–55",,"Salzmann, Auguste|Imprimerie photographique de Blanquart-Évrard, à Lille",French|French,1824 |1851,1872 |1855,1854,1854,1859,Salted paper print from paper negative,,"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287017,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.127,false,true,287018,Photographs,Photograph,"Beit-Lehem, Mosaïque de l'Église II",,,,,,Artist|Printer,,"Auguste Salzmann|Imprimerie photographique de Blanquart-Évrard, à Lille","French, 1824–1872|French, active 1851–55",,"Salzmann, Auguste|Imprimerie photographique de Blanquart-Évrard, à Lille",French|French,1824 |1851,1872 |1855,1854,1854,1859,Salted paper print from paper negative,,"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287018,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.128,false,true,287019,Photographs,Photograph,"Jérusalem, Mosquée d'Omar, côté ouest",,,,,,Artist|Printer,,"Auguste Salzmann|Imprimerie photographique de Blanquart-Évrard, à Lille","French, 1824–1872|French, active 1851–55",,"Salzmann, Auguste|Imprimerie photographique de Blanquart-Évrard, à Lille",French|French,1824 |1851,1872 |1855,1854,1854,1859,Salted paper print from paper negative,Image: 23.4 x 33 cm (9 3/16 x 13 in.) Mount: 44.5 x 59.3 cm (17 1/2 x 23 3/8 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287019,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.129,false,true,287020,Photographs,Photograph,"Jérusalem, Mosquée d'Omar, côté est, Intérieur de l'enceinte, 1",,,,,,Artist|Printer,,"Auguste Salzmann|Imprimerie photographique de Blanquart-Évrard, à Lille","French, 1824–1872|French, active 1851–55",,"Salzmann, Auguste|Imprimerie photographique de Blanquart-Évrard, à Lille",French|French,1824 |1851,1872 |1855,1854,1854,1859,Salted paper print from paper negative,Image: 22.1 x 26.8 cm (8 11/16 x 10 9/16 in.) Mount: 44.8 x 60.3 cm (17 5/8 x 23 3/4 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287020,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.130,false,true,287021,Photographs,Photograph,"Jérusalem, Mosquée d'Omar, côté Est, Intérieur de l'enceinte, 2",,,,,,Artist|Printer,,"Auguste Salzmann|Imprimerie photographique de Blanquart-Évrard, à Lille","French, 1824–1872|French, active 1851–55",,"Salzmann, Auguste|Imprimerie photographique de Blanquart-Évrard, à Lille",French|French,1824 |1851,1872 |1855,1854,1854,1859,Salted paper print from paper negative,Image: 22.6 x 33 cm (8 7/8 x 13 in.) Mount: 44.8 x 60.2 cm (17 5/8 x 23 11/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287021,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.131,false,true,287022,Photographs,Photograph,"Jérusalem, Mosquée d'Omar, côté Nord, Intérieur de l'enceinte",,,,,,Artist|Printer,,"Auguste Salzmann|Imprimerie photographique de Blanquart-Évrard, à Lille","French, 1824–1872|French, active 1851–55",,"Salzmann, Auguste|Imprimerie photographique de Blanquart-Évrard, à Lille",French|French,1824 |1851,1872 |1855,1854,1854,1859,Salted paper print from paper negative,Image: 23.4 x 32.8 cm (9 3/16 x 12 15/16 in.) Mount: 44.5 x 59.8 cm (17 1/2 x 23 9/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287022,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.132,false,true,287023,Photographs,Photograph,"Jérusalem, Mosquée d'Omar, côté Ouest, Intérieur de l'enceinte",,,,,,Artist|Printer,,"Auguste Salzmann|Imprimerie photographique de Blanquart-Évrard, à Lille","French, 1824–1872|French, active 1851–55",,"Salzmann, Auguste|Imprimerie photographique de Blanquart-Évrard, à Lille",French|French,1824 |1851,1872 |1855,1854,1854,1859,Salted paper print from paper negative,Image: 22.8 x 32.5 cm (9 x 12 13/16 in.) Mount: 44.6 x 59.6 cm (17 9/16 x 23 7/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287023,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.133,false,true,287024,Photographs,Photograph,"Jérusalem, Minaret de la Rue du Patriarche",,,,,,Artist|Printer,,"Auguste Salzmann|Imprimerie photographique de Blanquart-Évrard, à Lille","French, 1824–1872|French, active 1851–55",,"Salzmann, Auguste|Imprimerie photographique de Blanquart-Évrard, à Lille",French|French,1824 |1851,1872 |1855,1854,1854,1859,Salted paper print from paper negative,Image: 33 x 23.6 cm (13 x 9 5/16 in.) Mount: 60.3 x 44.6 cm (23 3/4 x 17 9/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287024,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.134,false,true,287025,Photographs,Photograph,"Jérusalem, Minaret de l'ancienne mosquée d'Abd-es-Samed",,,,,,Artist|Printer,,"Auguste Salzmann|Imprimerie photographique de Blanquart-Évrard, à Lille","French, 1824–1872|French, active 1851–55",,"Salzmann, Auguste|Imprimerie photographique de Blanquart-Évrard, à Lille",French|French,1824 |1851,1872 |1855,1854,1854,1859,Salted paper print from paper negative,Image: 32.8 x 23.3 cm (12 15/16 x 9 3/16 in.) Mount: 59.4 x 45.1 cm (23 3/8 x 17 3/4 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287025,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.135,false,true,287026,Photographs,Photograph,"Jérusalem, Porte de Jaffa, Vue extérieure",,,,,,Artist|Printer,,"Auguste Salzmann|Imprimerie photographique de Blanquart-Évrard, à Lille","French, 1824–1872|French, active 1851–55",,"Salzmann, Auguste|Imprimerie photographique de Blanquart-Évrard, à Lille",French|French,1824 |1851,1872 |1855,1854,1854,1859,Salted paper print from paper negative,Image: 31.8 x 23 cm (12 1/2 x 9 1/16 in.) Mount: 59.3 x 44.9 cm (23 3/8 x 17 11/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287026,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.136,false,true,287027,Photographs,Photograph,"Jérusalem, Porte de Jaffa, Vue générale",,,,,,Artist|Printer,,"Auguste Salzmann|Imprimerie photographique de Blanquart-Évrard, à Lille","French, 1824–1872|French, active 1851–55",,"Salzmann, Auguste|Imprimerie photographique de Blanquart-Évrard, à Lille",French|French,1824 |1851,1872 |1855,1854,1854,1859,Salted paper print from paper negative,,"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287027,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.137,false,true,287028,Photographs,Photograph,"Jérusalem, Porte de Jaffa, Intérieur",,,,,,Artist|Printer,,"Auguste Salzmann|Imprimerie photographique de Blanquart-Évrard, à Lille","French, 1824–1872|French, active 1851–55",,"Salzmann, Auguste|Imprimerie photographique de Blanquart-Évrard, à Lille",French|French,1824 |1851,1872 |1855,1854,1854,1859,Salted paper print from paper negative,Image: 31.9 x 23.1 cm (12 9/16 x 9 1/8 in.) Mount: 59.6 x 44.9 cm (23 7/16 x 17 11/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287028,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.138,false,true,287029,Photographs,Photograph,"Jérusalem, Porte de Jaffa, Inscription",,,,,,Artist|Printer,,"Auguste Salzmann|Imprimerie photographique de Blanquart-Évrard, à Lille","French, 1824–1872|French, active 1851–55",,"Salzmann, Auguste|Imprimerie photographique de Blanquart-Évrard, à Lille",French|French,1824 |1851,1872 |1855,1854,1854,1859,Salted paper print from paper negative,,"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287029,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.139,false,true,287030,Photographs,Photograph,"Jérusalem, Porte de Jaffa, Inscription de la fausse porte",,,,,,Artist|Printer,,"Auguste Salzmann|Imprimerie photographique de Blanquart-Évrard, à Lille","French, 1824–1872|French, active 1851–55",,"Salzmann, Auguste|Imprimerie photographique de Blanquart-Évrard, à Lille",French|French,1824 |1851,1872 |1855,1854,1854,1859,Salted paper print from paper negative,Image: 23.5 x 33.2 cm (9 1/4 x 13 1/16 in.) Mount: 44.5 x 59.5 cm (17 1/2 x 23 7/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287030,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.140,false,true,287031,Photographs,Photograph,"Jérusalem, Porte de David, Vue extérieure",,,,,,Artist|Printer,,"Auguste Salzmann|Imprimerie photographique de Blanquart-Évrard, à Lille","French, 1824–1872|French, active 1851–55",,"Salzmann, Auguste|Imprimerie photographique de Blanquart-Évrard, à Lille",French|French,1824 |1851,1872 |1855,1854,1854,1859,Salted paper print from paper negative,Image: 32.8 x 23.5 cm (12 15/16 x 9 1/4 in.) Mount: 59.8 x 44.8 cm (23 9/16 x 17 5/8 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287031,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.141,false,true,287032,Photographs,Photograph,"Jérusalem, Porte de David, Intérieur",,,,,,Artist|Printer,,"Auguste Salzmann|Imprimerie photographique de Blanquart-Évrard, à Lille","French, 1824–1872|French, active 1851–55",,"Salzmann, Auguste|Imprimerie photographique de Blanquart-Évrard, à Lille",French|French,1824 |1851,1872 |1855,1854,1854,1859,Salted paper print from paper negative,Image: 33.1 x 23.7 cm (13 1/16 x 9 5/16 in.) Mount: 60.2 x 44.4 cm (23 11/16 x 17 1/2 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287032,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.142,false,true,287033,Photographs,Photograph,"Jérusalem, Porte de Mograbins",,,,,,Artist|Printer,,"Auguste Salzmann|Imprimerie photographique de Blanquart-Évrard, à Lille","French, 1824–1872|French, active 1851–55",,"Salzmann, Auguste|Imprimerie photographique de Blanquart-Évrard, à Lille",French|French,1824 |1851,1872 |1855,1854,1854,1859,Salted paper print from paper negative,,"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287033,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.143,false,true,287034,Photographs,Photograph,"Jérusalem, Porte Saint-Étienne, Vue extérieure",,,,,,Artist|Printer,,"Auguste Salzmann|Imprimerie photographique de Blanquart-Évrard, à Lille","French, 1824–1872|French, active 1851–55",,"Salzmann, Auguste|Imprimerie photographique de Blanquart-Évrard, à Lille",French|French,1824 |1851,1872 |1855,1854,1854,1859,Salted paper print from paper negative,Image: 32.4 x 23.7 cm (12 3/4 x 9 5/16 in.) Mount: 59.7 x 44.7 cm (23 1/2 x 17 5/8 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287034,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.144,false,true,287035,Photographs,Photograph,"Jérusalem, Porte Saint-Étienne, Intérieur",,,,,,Artist|Printer,,"Auguste Salzmann|Imprimerie photographique de Blanquart-Évrard, à Lille","French, 1824–1872|French, active 1851–55",,"Salzmann, Auguste|Imprimerie photographique de Blanquart-Évrard, à Lille",French|French,1824 |1851,1872 |1855,1854,1854,1859,Salted paper print from paper negative,Image: 23 x 32.7 cm (9 1/16 x 12 7/8 in.) Mount: 45 x 59.8 cm (17 11/16 x 23 9/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287035,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.145,false,true,287036,Photographs,Photograph,"Jérusalem, Porte d'Hérode",,,,,,Artist|Printer,,"Auguste Salzmann|Imprimerie photographique de Blanquart-Évrard, à Lille","French, 1824–1872|French, active 1851–55",,"Salzmann, Auguste|Imprimerie photographique de Blanquart-Évrard, à Lille",French|French,1824 |1851,1872 |1855,1854,1854,1859,Salted paper print from paper negative,Image: 23.1 x 33 cm (9 1/8 x 13 in.) Mount: 44.8 x 60.3 cm (17 5/8 x 23 3/4 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287036,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.146,false,true,287037,Photographs,Photograph,"Jérusalem, Porte de Damas, Vue extérieure",,,,,,Artist|Printer,,"Auguste Salzmann|Imprimerie photographique de Blanquart-Évrard, à Lille","French, 1824–1872|French, active 1851–55",,"Salzmann, Auguste|Imprimerie photographique de Blanquart-Évrard, à Lille",French|French,1824 |1851,1872 |1855,1854,1854,1859,Salted paper print from paper negative,Image: 22.8 x 32.4 cm (9 x 12 3/4 in.) Mount: 44.4 x 59.6 cm (17 1/2 x 23 7/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287037,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.147,false,true,287038,Photographs,Photograph,"Jérusalem, Porte de Damas, Intérieur",,,,,,Artist|Printer,,"Auguste Salzmann|Imprimerie photographique de Blanquart-Évrard, à Lille","French, 1824–1872|French, active 1851–55",,"Salzmann, Auguste|Imprimerie photographique de Blanquart-Évrard, à Lille",French|French,1824 |1851,1872 |1855,1854,1854,1859,Salted paper print from paper negative,Image: 23.1 x 32.6 cm (9 1/8 x 12 13/16 in.) Mount: 44.5 x 59.5 cm (17 1/2 x 23 7/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287038,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.148,false,true,287039,Photographs,Photograph,"Jérusalem, Escalier arabe de Sainte-Marie-la-Grande",,,,,,Artist|Printer,,"Auguste Salzmann|Imprimerie photographique de Blanquart-Évrard, à Lille","French, 1824–1872|French, active 1851–55",,"Salzmann, Auguste|Imprimerie photographique de Blanquart-Évrard, à Lille",French|French,1824 |1851,1872 |1855,1854,1854,1859,Salted paper print from paper negative,Image: 32.9 x 23.4 cm (12 15/16 x 9 3/16 in.) Mount: 59.6 x 44.7 cm (23 7/16 x 17 5/8 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287039,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.149,false,true,287040,Photographs,Photograph,"Jérusalem, Escalier arabe de Sainte-Marie-la-Grande, Détails de la partie supérieure",,,,,,Artist|Printer,,"Auguste Salzmann|Imprimerie photographique de Blanquart-Évrard, à Lille","French, 1824–1872|French, active 1851–55",,"Salzmann, Auguste|Imprimerie photographique de Blanquart-Évrard, à Lille",French|French,1824 |1851,1872 |1855,1854,1854,1859,Salted paper print from paper negative,Image: 32.7 x 23.4 cm (12 7/8 x 9 3/16 in.) Mount: 59.5 x 44.6 cm (23 7/16 x 17 9/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287040,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.150,false,true,287041,Photographs,Photograph,"Jérusalem, Hospital de Sainte-Hélène, Face Sud",,,,,,Artist|Printer,,"Auguste Salzmann|Imprimerie photographique de Blanquart-Évrard, à Lille","French, 1824–1872|French, active 1851–55",,"Salzmann, Auguste|Imprimerie photographique de Blanquart-Évrard, à Lille",French|French,1824 |1851,1872 |1855,1854,1854,1859,Salted paper print from paper negative,Image: 32.5 x 23.7 cm (12 13/16 x 9 5/16 in.) Mount: 59.6 x 44.6 cm (23 7/16 x 17 9/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287041,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.151,false,true,287042,Photographs,Photograph,"Jérusalem, Hospital de Sainte-Hélène, Intérieur",,,,,,Artist|Printer,,"Auguste Salzmann|Imprimerie photographique de Blanquart-Évrard, à Lille","French, 1824–1872|French, active 1851–55",,"Salzmann, Auguste|Imprimerie photographique de Blanquart-Évrard, à Lille",French|French,1824 |1851,1872 |1855,1854,1854,1859,Salted paper print from paper negative,Image: 23 x 33 cm (9 1/16 x 13 in.) Mount: 44.8 x 60.4 cm (17 5/8 x 23 3/4 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287042,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.152,false,true,287043,Photographs,Photograph,"Jérusalem, Fontaine Arabe, 1",,,,,,Artist|Printer,,"Auguste Salzmann|Imprimerie photographique de Blanquart-Évrard, à Lille","French, 1824–1872|French, active 1851–55",,"Salzmann, Auguste|Imprimerie photographique de Blanquart-Évrard, à Lille",French|French,1824 |1851,1872 |1855,1854,1854,1859,Salted paper print from paper negative,,"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287043,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.153,false,true,287044,Photographs,Photograph,"Jérusalem, Fontaine Arabe, 2",,,,,,Artist|Printer,,"Auguste Salzmann|Imprimerie photographique de Blanquart-Évrard, à Lille","French, 1824–1872|French, active 1851–55",,"Salzmann, Auguste|Imprimerie photographique de Blanquart-Évrard, à Lille",French|French,1824 |1851,1872 |1855,1854,1854,1859,Salted paper print from paper negative,Image: 33.1 x 23.5 cm (13 1/16 x 9 1/4 in.) Mount: 59.8 x 44.8 cm (23 9/16 x 17 5/8 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287044,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.154,false,true,287045,Photographs,Photograph,"Jérusalem, Fontaine Arabe, 3",,,,,,Artist|Printer,,"Auguste Salzmann|Imprimerie photographique de Blanquart-Évrard, à Lille","French, 1824–1872|French, active 1851–55",,"Salzmann, Auguste|Imprimerie photographique de Blanquart-Évrard, à Lille",French|French,1824 |1851,1872 |1855,1854,1854,1859,Salted paper print from paper negative,Image: 33.1 x 23.5 cm (13 1/16 x 9 1/4 in.) Mount: 60.3 x 44.6 cm (23 3/4 x 17 9/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287045,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.155,false,true,287046,Photographs,Photograph,"Jérusalem, Fontaine Arabe, 4",,,,,,Artist|Printer,,"Auguste Salzmann|Imprimerie photographique de Blanquart-Évrard, à Lille","French, 1824–1872|French, active 1851–55",,"Salzmann, Auguste|Imprimerie photographique de Blanquart-Évrard, à Lille",French|French,1824 |1851,1872 |1855,1854,1854,1859,Salted paper print from paper negative,Image: 32.8 x 23.4 cm (12 15/16 x 9 3/16 in.) Mount: 59.6 x 45 cm (23 7/16 x 17 11/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287046,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.156,false,true,287047,Photographs,Photograph,"Jérusalem, Rue du quartier arabe, 1",,,,,,Artist|Printer,,"Auguste Salzmann|Imprimerie photographique de Blanquart-Évrard, à Lille","French, 1824–1872|French, active 1851–55",,"Salzmann, Auguste|Imprimerie photographique de Blanquart-Évrard, à Lille",French|French,1824 |1851,1872 |1855,1854,1854,1859,Salted paper print from paper negative,Image: 32.7 x 23.6 cm (12 7/8 x 9 5/16 in.) Mount: 59.6 x 44.6 cm (23 7/16 x 17 9/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287047,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.157,false,true,287048,Photographs,Photograph,"Jérusalem, Rue du quartier arabe, 2",,,,,,Artist|Printer,,"Auguste Salzmann|Imprimerie photographique de Blanquart-Évrard, à Lille","French, 1824–1872|French, active 1851–55",,"Salzmann, Auguste|Imprimerie photographique de Blanquart-Évrard, à Lille",French|French,1824 |1851,1872 |1855,1854,1854,1859,Salted paper print from paper negative,Image: 33.5 x 23.6 cm (13 3/16 x 9 5/16 in.) Mount: 60.3 x 45 cm (23 3/4 x 17 11/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287048,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.158,false,true,287049,Photographs,Photograph,"Jérusalem, Ornements arabes",,,,,,Artist|Printer,,"Auguste Salzmann|Imprimerie photographique de Blanquart-Évrard, à Lille","French, 1824–1872|French, active 1851–55",,"Salzmann, Auguste|Imprimerie photographique de Blanquart-Évrard, à Lille",French|French,1824 |1851,1872 |1855,1854,1854,1859,Salted paper print from paper negative,Image: 23.4 x 32.1 cm (9 3/16 x 12 5/8 in.) Mount: 44.8 x 60 cm (17 5/8 x 23 5/8 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287049,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.159,false,true,287050,Photographs,Photograph,"Jérusalem, Fenêtre arabe",,,,,,Artist|Printer,,"Auguste Salzmann|Imprimerie photographique de Blanquart-Évrard, à Lille","French, 1824–1872|French, active 1851–55",,"Salzmann, Auguste|Imprimerie photographique de Blanquart-Évrard, à Lille",French|French,1824 |1851,1872 |1855,1854,1854,1859,Salted paper print from paper negative,Image: 33.5 x 23.3 cm (13 3/16 x 9 3/16 in.) Mount: 60.1 x 44.8 cm (23 11/16 x 17 5/8 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287050,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.160,false,true,287051,Photographs,Photograph,"Jérusalem, Détails de la porte d'un Dôme sépulcral",,,,,,Artist|Printer,,"Auguste Salzmann|Imprimerie photographique de Blanquart-Évrard, à Lille","French, 1824–1872|French, active 1851–55",,"Salzmann, Auguste|Imprimerie photographique de Blanquart-Évrard, à Lille",French|French,1824 |1851,1872 |1855,1854,1854,1859,Salted paper print from paper negative,Image: 23 x 32.8 cm (9 1/16 x 12 15/16 in.) Mount: 44.8 x 59.9 cm (17 5/8 x 23 9/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287051,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.161,false,true,287052,Photographs,Photograph,"Jérusalem, Tombeau arabe",,,,,,Artist|Printer,,"Auguste Salzmann|Imprimerie photographique de Blanquart-Évrard, à Lille","French, 1824–1872|French, active 1851–55",,"Salzmann, Auguste|Imprimerie photographique de Blanquart-Évrard, à Lille",French|French,1824 |1851,1872 |1855,1854,1854,1859,Salted paper print from paper negative,Image: 23.2 x 32.9 cm (9 1/8 x 12 15/16 in.) Mount: 44.9 x 60.4 cm (17 11/16 x 23 3/4 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287052,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.162,false,true,287053,Photographs,Photograph,"Jérusalem, Porte de la citadelle",,,,,,Artist|Printer,,"Auguste Salzmann|Imprimerie photographique de Blanquart-Évrard, à Lille","French, 1824–1872|French, active 1851–55",,"Salzmann, Auguste|Imprimerie photographique de Blanquart-Évrard, à Lille",French|French,1824 |1851,1872 |1855,1854,1854,1859,Salted paper print from paper negative,Image: 32.9 x 23.1 cm (12 15/16 x 9 1/8 in.) Mount: 59.6 x 44.7 cm (23 7/16 x 17 5/8 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287053,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.163,false,true,287054,Photographs,Photograph,"Jérusalem, Porte de la citadelle, Inscription",,,,,,Artist|Printer,,"Auguste Salzmann|Imprimerie photographique de Blanquart-Évrard, à Lille","French, 1824–1872|French, active 1851–55",,"Salzmann, Auguste|Imprimerie photographique de Blanquart-Évrard, à Lille",French|French,1824 |1851,1872 |1855,1854,1854,1859,Salted paper print from paper negative,Image: 22.5 x 33 cm (8 7/8 x 13 in.) Mount: 45 x 60.4 cm (17 11/16 x 23 3/4 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287054,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.164,false,true,287055,Photographs,Photograph,"Jérusalem, Aqueduc de Ponce-Pilate, Inscription",,,,,,Artist|Printer,,"Auguste Salzmann|Imprimerie photographique de Blanquart-Évrard, à Lille","French, 1824–1872|French, active 1851–55",,"Salzmann, Auguste|Imprimerie photographique de Blanquart-Évrard, à Lille",French|French,1824 |1851,1872 |1855,1854,1854,1859,Salted paper print from paper negative,Image: 22.8 x 32.8 cm (9 x 12 15/16 in.) Mount: 45 x 59.4 cm (17 11/16 x 23 3/8 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287055,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.165,false,true,287056,Photographs,Photograph,"Jérusalem, Maison du mauvais riche",,,,,,Artist|Printer,,"Auguste Salzmann|Imprimerie photographique de Blanquart-Évrard, à Lille","French, 1824–1872|French, active 1851–55",,"Salzmann, Auguste|Imprimerie photographique de Blanquart-Évrard, à Lille",French|French,1824 |1851,1872 |1855,1854,1854,1859,Salted paper print from paper negative,Image: 33.5 x 23.6 cm (13 3/16 x 9 5/16 in.) Mount: 60.3 x 44.8 cm (23 3/4 x 17 5/8 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287056,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.166,false,true,287057,Photographs,Photograph,"Jérusalem, Mont Sion",,,,,,Artist|Printer,,"Auguste Salzmann|Imprimerie photographique de Blanquart-Évrard, à Lille","French, 1824–1872|French, active 1851–55",,"Salzmann, Auguste|Imprimerie photographique de Blanquart-Évrard, à Lille",French|French,1824 |1851,1872 |1855,1854,1854,1859,Salted paper print from paper negative,Image: 22.9 x 33.3 cm (9 x 13 1/8 in.) Mount: 45 x 59.5 cm (17 11/16 x 23 7/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287057,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.167,false,true,287058,Photographs,Photograph,"Jérusalem, Forteresse de David (citadelle), Face Ouest",,,,,,Artist|Printer,,"Auguste Salzmann|Imprimerie photographique de Blanquart-Évrard, à Lille","French, 1824–1872|French, active 1851–55",,"Salzmann, Auguste|Imprimerie photographique de Blanquart-Évrard, à Lille",French|French,1824 |1851,1872 |1855,1854,1854,1859,Salted paper print from paper negative,Image: 32.6 x 23.7 cm (12 13/16 x 9 5/16 in.) Mount: 44.4 x 59.5 cm (17 1/2 x 23 7/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287058,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.168,false,true,287059,Photographs,Photograph,"Jérusalem, Pins du Couvent arménien",,,,,,Artist|Printer,,"Auguste Salzmann|Imprimerie photographique de Blanquart-Évrard, à Lille","French, 1824–1872|French, active 1851–55",,"Salzmann, Auguste|Imprimerie photographique de Blanquart-Évrard, à Lille",French|French,1824 |1851,1872 |1855,1854,1854,1859,Salted paper print from paper negative,Image: 23 x 32.9 cm (9 1/16 x 12 15/16 in.) Mount: 44.5 x 59.4 cm (17 1/2 x 23 3/8 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287059,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.169,false,true,287060,Photographs,Photograph,"Jérusalem, Côté Est de Jérusalem",,,,,,Artist|Printer,,"Auguste Salzmann|Imprimerie photographique de Blanquart-Évrard, à Lille","French, 1824–1872|French, active 1851–55",,"Salzmann, Auguste|Imprimerie photographique de Blanquart-Évrard, à Lille",French|French,1824 |1851,1872 |1855,1854,1854,1859,Salted paper print from paper negative,Image: 22.6 x 32.2 cm (8 7/8 x 12 11/16 in.) Mount: 44.9 x 60.4 cm (17 11/16 x 23 3/4 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287060,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.170,false,true,287061,Photographs,Photograph,"Jérusalem, Côté Sud de Jérusalem",,,,,,Artist|Printer,,"Auguste Salzmann|Imprimerie photographique de Blanquart-Évrard, à Lille","French, 1824–1872|French, active 1851–55",,"Salzmann, Auguste|Imprimerie photographique de Blanquart-Évrard, à Lille",French|French,1824 |1851,1872 |1855,1854,1854,1859,Salted paper print from paper negative,Image: 23.4 x 33 cm (9 3/16 x 13 in.) Mount: 44.8 x 59.7 cm (17 5/8 x 23 1/2 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287061,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.171,false,true,287062,Photographs,Photograph,"Jérusalem, Chemin de Naplouse",,,,,,Artist|Printer,,"Auguste Salzmann|Imprimerie photographique de Blanquart-Évrard, à Lille","French, 1824–1872|French, active 1851–55",,"Salzmann, Auguste|Imprimerie photographique de Blanquart-Évrard, à Lille",French|French,1824 |1851,1872 |1855,1854,1854,1859,Salted paper print from paper negative,Image: 23.1 x 33 cm (9 1/8 x 13 in.) Mount: 44.8 x 59.5 cm (17 5/8 x 23 7/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287062,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.172,false,true,287063,Photographs,Photograph,"Jérusalem, Chemin de Beit-Lehem",,,,,,Artist|Printer,,"Auguste Salzmann|Imprimerie photographique de Blanquart-Évrard, à Lille","French, 1824–1872|French, active 1851–55",,"Salzmann, Auguste|Imprimerie photographique de Blanquart-Évrard, à Lille",French|French,1824 |1851,1872 |1855,1854,1854,1859,Salted paper print from paper negative,Image: 23.3 x 32.7 cm (9 3/16 x 12 7/8 in.) Mount: 44.5 x 59.5 cm (17 1/2 x 23 7/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287063,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.173,false,true,287064,Photographs,Photograph,"Jérusalem, Vue générale de la Vallée de Hinnom",,,,,,Artist|Printer,,"Auguste Salzmann|Imprimerie photographique de Blanquart-Évrard, à Lille","French, 1824–1872|French, active 1851–55",,"Salzmann, Auguste|Imprimerie photographique de Blanquart-Évrard, à Lille",French|French,1824 |1851,1872 |1855,1854,1854,1859,Salted paper print from paper negative,Image: 23.5 x 33.5 cm (9 1/4 x 13 3/16 in.) Mount: 44.5 x 59.5 cm (17 1/2 x 23 7/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287064,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.174,false,true,287065,Photographs,Photograph,"Jérusalem, Côté Nord de Jérusalem",,,,,,Artist|Printer,,"Auguste Salzmann|Imprimerie photographique de Blanquart-Évrard, à Lille","French, 1824–1872|French, active 1851–55",,"Salzmann, Auguste|Imprimerie photographique de Blanquart-Évrard, à Lille",French|French,1824 |1851,1872 |1855,1854,1854,1859,Salted paper print from paper negative,Image: 22.8 x 32.6 cm (9 x 12 13/16 in.) Mount: 45.1 x 60.1 cm (17 3/4 x 23 11/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/287065,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.1,false,true,286901,Photographs,Photograph,"Jérusalem, Enceinte du Temple, Côté Ouest, Heit-el-Morharby",,,,,,Printer|Artist,,"Imprimerie photographique de Blanquart-Évrard, à Lille|Auguste Salzmann","French, active 1851–55|French, 1824–1872",,"Imprimerie photographique de Blanquart-Évrard, à Lille|Salzmann, Auguste",French|French,1851 |1824,1855 |1872,1854,1854,1859,Salted paper print from paper negative,Image: 23.2 × 33.2 cm (9 1/8 × 13 1/16 in.) Mount: 44.1 x 60 cm (17 3/8 x 23 5/8 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286901,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.2,false,true,286902,Photographs,Photograph,"Jérusalem, Enceinte du Temple, Arche du Pont Salomonien qui reliait Moria à Sion",,,,,,Printer|Artist,,"Imprimerie photographique de Blanquart-Évrard, à Lille|Auguste Salzmann","French, active 1851–55|French, 1824–1872",,"Imprimerie photographique de Blanquart-Évrard, à Lille|Salzmann, Auguste",French|French,1851 |1824,1855 |1872,1854,1854,1859,Salted paper print from paper negative,Image: 23.2 x 32.5 cm (9 1/8 x 12 13/16 in.) Mount: 44.6 x 59.6 cm (17 9/16 x 23 7/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286902,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.3,false,true,286903,Photographs,Photograph,"Jérusalem, Enceinte du Temple, Mosquée El-Aksa, angle Sud-Ouest",,,,,,Printer|Artist,,"Imprimerie photographique de Blanquart-Évrard, à Lille|Auguste Salzmann","French, active 1851–55|French, 1824–1872",,"Imprimerie photographique de Blanquart-Évrard, à Lille|Salzmann, Auguste",French|French,1851 |1824,1855 |1872,1854,1854,1859,Salted paper print from paper negative,Image: 23.1 x 32.6 cm (9 1/8 x 12 13/16 in.) Mount: 44.6 x 60 cm (17 9/16 x 23 5/8 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286903,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.4,false,true,286904,Photographs,Photograph,"Jérusalem, Enceinte du Temple, Face sud de l'angle Sud-Est",,,,,,Printer|Artist,,"Imprimerie photographique de Blanquart-Évrard, à Lille|Auguste Salzmann","French, active 1851–55|French, 1824–1872",,"Imprimerie photographique de Blanquart-Évrard, à Lille|Salzmann, Auguste",French|French,1851 |1824,1855 |1872,1854,1854,1859,Salted paper print from paper negative,Image: 23.3 x 32.8 cm (9 3/16 x 12 15/16 in.) Mount: 44.7 x 60.5 cm (17 5/8 x 23 13/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286904,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.5,false,true,286905,Photographs,Photograph,"Jérusalem, Enceinte du Temple, Vue générale de la face Sud 1",,,,,,Printer|Artist,,"Imprimerie photographique de Blanquart-Évrard, à Lille|Auguste Salzmann","French, active 1851–55|French, 1824–1872",,"Imprimerie photographique de Blanquart-Évrard, à Lille|Salzmann, Auguste",French|French,1851 |1824,1855 |1872,1854,1854,1859,Salted paper print from paper negative,Image: 23 x 31.7 cm (9 1/16 x 12 1/2 in.) Mount: 44.7 x 60.2 cm (17 5/8 x 23 11/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286905,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.6,false,true,286906,Photographs,Photograph,"Jérusalem, Enceinte du Temple, Vue générale de la face Sud 2",,,,,,Printer|Artist,,"Imprimerie photographique de Blanquart-Évrard, à Lille|Auguste Salzmann","French, active 1851–55|French, 1824–1872",,"Imprimerie photographique de Blanquart-Évrard, à Lille|Salzmann, Auguste",French|French,1851 |1824,1855 |1872,1854,1854,1859,Salted paper print from paper negative,Image: 23.1 x 32.7 cm (9 1/8 x 12 7/8 in.) Mount: 44.7 x 60 cm (17 5/8 x 23 5/8 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286906,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.7,false,true,286907,Photographs,Photograph,"Jérusalem, Enceinte du Temple, Porte hérodienne",,,,,,Printer|Artist,,"Imprimerie photographique de Blanquart-Évrard, à Lille|Auguste Salzmann","French, active 1851–55|French, 1824–1872",,"Imprimerie photographique de Blanquart-Évrard, à Lille|Salzmann, Auguste",French|French,1851 |1824,1855 |1872,1854,1854,1859,Salted paper print from paper negative,Image: 32.9 x 23.1 cm (12 15/16 x 9 1/8 in.) Mount: 59.5 x 44.5 cm (23 7/16 x 17 1/2 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286907,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.8,false,true,286908,Photographs,Photograph,"Jérusalem, Enceinte du Temple, Triple porte romaine",,,,,,Printer|Artist,,"Imprimerie photographique de Blanquart-Évrard, à Lille|Auguste Salzmann","French, active 1851–55|French, 1824–1872",,"Imprimerie photographique de Blanquart-Évrard, à Lille|Salzmann, Auguste",French|French,1851 |1824,1855 |1872,1854,1854,1859,Salted paper print from paper negative,Image: 23.4 x 31.9 cm (9 3/16 x 12 9/16 in.) Mount: 44.6 x 59.6 cm (17 9/16 x 23 7/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286908,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.9,false,true,286909,Photographs,Photograph,"Jérusalem, Enceinte du Temple, Moulure judaïque formant pied-droit de l'une des portes romaines",,,,,,Printer|Artist,,"Imprimerie photographique de Blanquart-Évrard, à Lille|Auguste Salzmann","French, active 1851–55|French, 1824–1872",,"Imprimerie photographique de Blanquart-Évrard, à Lille|Salzmann, Auguste",French|French,1851 |1824,1855 |1872,1854,1854,1859,Salted paper print from paper negative,Image: 32.7 x 22.7 cm (12 7/8 x 8 15/16 in.) Mount: 60 x 44.9 cm (23 5/8 x 17 11/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286909,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.10,false,true,286910,Photographs,Photograph,"Jérusalem, Enceinte du Temple, Face Sud de l'angle Sud-Est",,,,,,Printer|Artist,,"Imprimerie photographique de Blanquart-Évrard, à Lille|Auguste Salzmann","French, active 1851–55|French, 1824–1872",,"Imprimerie photographique de Blanquart-Évrard, à Lille|Salzmann, Auguste",French|French,1851 |1824,1855 |1872,1854,1854,1859,Salted paper print from paper negative,Image: 33.1 x 23.6 cm (13 1/16 x 9 5/16 in.) Mount: 59.8 x 44.6 cm (23 9/16 x 17 9/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286910,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.11,false,true,286911,Photographs,Photograph,"Jérusalem, Enceinte du Temple, Face Est de l'angle Sud-Est",,,,,,Printer|Artist,,"Imprimerie photographique de Blanquart-Évrard, à Lille|Auguste Salzmann","French, active 1851–55|French, 1824–1872",,"Imprimerie photographique de Blanquart-Évrard, à Lille|Salzmann, Auguste",French|French,1851 |1824,1855 |1872,1854,1854,1859,Salted paper print from paper negative,Image: 33 x 23.5 cm (13 x 9 1/4 in.) Mount: 60 x 44.7 cm (23 5/8 x 17 5/8 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286911,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.12,false,true,286912,Photographs,Photograph,"Jérusalem, Enceinte du Temple, Vue générale de la face Est, Pl. 1",,,,,,Printer|Artist,,"Imprimerie photographique de Blanquart-Évrard, à Lille|Auguste Salzmann","French, active 1851–55|French, 1824–1872",,"Imprimerie photographique de Blanquart-Évrard, à Lille|Salzmann, Auguste",French|French,1851 |1824,1855 |1872,1854,1854,1859,Salted paper print from paper negative,Image: 23.1 x 31.8 cm (9 1/8 x 12 1/2 in.) Mount: 44.5 x 60 cm (17 1/2 x 23 5/8 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286912,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.13,false,true,286913,Photographs,Photograph,"Jérusalem, Enceinte du Temple, Vue générale de la face Est, Pl. 2",,,,,,Printer|Artist,,"Imprimerie photographique de Blanquart-Évrard, à Lille|Auguste Salzmann","French, active 1851–55|French, 1824–1872",,"Imprimerie photographique de Blanquart-Évrard, à Lille|Salzmann, Auguste",French|French,1851 |1824,1855 |1872,1854,1854,1859,Salted paper print from paper negative,Image: 22.5 x 32.9 cm (8 7/8 x 12 15/16 in.) Mount: 44.8 x 60.5 cm (17 5/8 x 23 13/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286913,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.14,false,true,286914,Photographs,Photograph,"Jérusalem, Enceinte du Temple, Vue générale de la face Est, Pl. 3",,,,,,Printer|Artist,,"Imprimerie photographique de Blanquart-Évrard, à Lille|Auguste Salzmann","French, active 1851–55|French, 1824–1872",,"Imprimerie photographique de Blanquart-Évrard, à Lille|Salzmann, Auguste",French|French,1851 |1824,1855 |1872,1854,1854,1859,Salted paper print from paper negative,Image: 22.8 × 33.2 cm (9 in. × 13 1/16 in.) Mount: 44.8 x 58.8 cm (17 5/8 x 23 1/8 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286914,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.15,false,true,286915,Photographs,Photograph,"Jérusalem, Enceinte du Temple, Poterne de Josaphat",,,,,,Printer|Artist,,"Imprimerie photographique de Blanquart-Évrard, à Lille|Auguste Salzmann","French, active 1851–55|French, 1824–1872",,"Imprimerie photographique de Blanquart-Évrard, à Lille|Salzmann, Auguste",French|French,1851 |1824,1855 |1872,1854,1854,1859,Salted paper print from paper negative,Image: 32.3 x 23.2 cm (12 11/16 x 9 1/8 in.) Mount: 60 x 44.7 cm (23 5/8 x 17 5/8 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286915,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.16,false,true,286916,Photographs,Photograph,"Jérusalem, Enceinte du Temple, Porte Dorée",,,,,,Printer|Artist,,"Imprimerie photographique de Blanquart-Évrard, à Lille|Auguste Salzmann","French, active 1851–55|French, 1824–1872",,"Imprimerie photographique de Blanquart-Évrard, à Lille|Salzmann, Auguste",French|French,1851 |1824,1855 |1872,1854,1854,1859,Salted paper print from paper negative,Image: 32 x 22.4 cm (12 5/8 x 8 13/16 in.) Mount: 60 x 44.8 cm (23 5/8 x 17 5/8 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286916,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.17,false,true,286917,Photographs,Photograph,"Jérusalem, Enceinte du Temple, Détails de la Porte Dorée",,,,,,Printer|Artist,,"Imprimerie photographique de Blanquart-Évrard, à Lille|Auguste Salzmann","French, active 1851–55|French, 1824–1872",,"Imprimerie photographique de Blanquart-Évrard, à Lille|Salzmann, Auguste",French|French,1851 |1824,1855 |1872,1854,1854,1859,Salted paper print from paper negative,Image: 32.2 x 22.9 cm (12 11/16 x 9 in.) Mount: 60 x 44.7 cm (23 5/8 x 17 5/8 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286917,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.18,false,true,286918,Photographs,Photograph,"Jérusalem, Enceinte du Temple, Face Est de l'angle Nord-Est",,,,,,Printer|Artist,,"Imprimerie photographique de Blanquart-Évrard, à Lille|Auguste Salzmann","French, active 1851–55|French, 1824–1872",,"Imprimerie photographique de Blanquart-Évrard, à Lille|Salzmann, Auguste",French|French,1851 |1824,1855 |1872,1854,1854,1859,Salted paper print from paper negative,Image: 22.8 x 31.8 cm (9 x 12 1/2 in.) Mount: 44.5 x 60 cm (17 1/2 x 23 5/8 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286918,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.19,false,true,286919,Photographs,Photograph,"Jérusalem, Enceinte du Temple, Face Nord de l'angle Nord-Est",,,,,,Printer|Artist,,"Imprimerie photographique de Blanquart-Évrard, à Lille|Auguste Salzmann","French, active 1851–55|French, 1824–1872",,"Imprimerie photographique de Blanquart-Évrard, à Lille|Salzmann, Auguste",French|French,1851 |1824,1855 |1872,1854,1854,1859,Salted paper print from paper negative,Image: 32.1 x 22.7 cm (12 5/8 x 8 15/16 in.) Mount: 59.5 x 44.7 cm (23 7/16 x 17 5/8 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286919,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.20,false,true,286920,Photographs,Photograph,"Jérusalem, Enceinte du Temple, Piscine probatique, 1",,,,,,Printer|Artist,,"Imprimerie photographique de Blanquart-Évrard, à Lille|Auguste Salzmann","French, active 1851–55|French, 1824–1872",,"Imprimerie photographique de Blanquart-Évrard, à Lille|Salzmann, Auguste",French|French,1851 |1824,1855 |1872,1854,1854,1859,Salted paper print from paper negative,Image: 22.7 x 32.8 cm (8 15/16 x 12 15/16 in.) Mount: 44.4 x 59.9 cm (17 1/2 x 23 9/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286920,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.21,false,true,286921,Photographs,Photograph,"Jérusalem, Enceinte du Temple, Piscine probatique, 2",,,,,,Printer|Artist,,"Imprimerie photographique de Blanquart-Évrard, à Lille|Auguste Salzmann","French, active 1851–55|French, 1824–1872",,"Imprimerie photographique de Blanquart-Évrard, à Lille|Salzmann, Auguste",French|French,1851 |1824,1855 |1872,1854,1854,1859,Salted paper print from paper negative,Image: 23.1 x 32.4 cm (9 1/8 x 12 3/4 in.) Mount: 44.7 x 60 cm (17 5/8 x 23 5/8 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286921,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.22,false,true,286922,Photographs,Photograph,"Jérusalem, Enceinte du Temple, Détails de l'appareil de la piscine probatique",,,,,,Printer|Artist,,"Imprimerie photographique de Blanquart-Évrard, à Lille|Auguste Salzmann","French, active 1851–55|French, 1824–1872",,"Imprimerie photographique de Blanquart-Évrard, à Lille|Salzmann, Auguste",French|French,1851 |1824,1855 |1872,1854,1854,1859,Salted paper print from paper negative,Image: 22.8 x 32.5 cm (9 x 12 13/16 in.) Mount: 44.6 x 60.2 cm (17 9/16 x 23 11/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286922,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.23,false,true,286923,Photographs,Photograph,"Jérusalem, Enceinte du Temple, Angle Nord-Ouest et minaret élevé en l'an 697 de l'Hégire",,,,,,Printer|Artist,,"Imprimerie photographique de Blanquart-Évrard, à Lille|Auguste Salzmann","French, active 1851–55|French, 1824–1872",,"Imprimerie photographique de Blanquart-Évrard, à Lille|Salzmann, Auguste",French|French,1851 |1824,1855 |1872,1854,1854,1859,Salted paper print from paper negative,Image: 33.1 x 23.4 cm (13 1/16 x 9 3/16 in.) Mount: 60.3 x 44.6 cm (23 3/4 x 17 9/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286923,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.24,false,true,286924,Photographs,Photograph,"Jérusalem, Vallée de Hinnom, Tombeaux antiques",,,,,,Printer|Artist,,"Imprimerie photographique de Blanquart-Évrard, à Lille|Auguste Salzmann","French, active 1851–55|French, 1824–1872",,"Imprimerie photographique de Blanquart-Évrard, à Lille|Salzmann, Auguste",French|French,1851 |1824,1855 |1872,1854,1854,1859,Salted paper print from paper negative,Image: 23.4 x 33 cm (9 3/16 x 13 in.) Mount: 44.6 x 60.1 cm (17 9/16 x 23 11/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286924,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.25,false,true,286925,Photographs,Photograph,"Jérusalem, Vallée de Hinnom, Inscription tumulaire grecque, 1",,,,,,Printer|Artist,,"Imprimerie photographique de Blanquart-Évrard, à Lille|Auguste Salzmann","French, active 1851–55|French, 1824–1872",,"Imprimerie photographique de Blanquart-Évrard, à Lille|Salzmann, Auguste",French|French,1851 |1824,1855 |1872,1854,1854,1859,Salted paper print from paper negative,Image: 23.5 x 32.7 cm (9 1/4 x 12 7/8 in.) Mount: 44.7 x 60.2 cm (17 5/8 x 23 11/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286925,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.26,false,true,286926,Photographs,Photograph,"Jérusalem, Vallée de Hinnom, Inscription tumulaire grecque, 2",,,,,,Printer|Artist,,"Imprimerie photographique de Blanquart-Évrard, à Lille|Auguste Salzmann","French, active 1851–55|French, 1824–1872",,"Imprimerie photographique de Blanquart-Évrard, à Lille|Salzmann, Auguste",French|French,1851 |1824,1855 |1872,1854,1854,1859,Salted paper print from paper negative,Image: 23 x 32.5 cm (9 1/16 x 12 13/16 in.) Mount: 44.5 x 59.8 cm (17 1/2 x 23 9/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286926,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.27,false,true,286927,Photographs,Photograph,"Jérusalem, Vallée de Hinnom, Retraite des Apôtres",,,,,,Printer|Artist,,"Imprimerie photographique de Blanquart-Évrard, à Lille|Auguste Salzmann","French, active 1851–55|French, 1824–1872",,"Imprimerie photographique de Blanquart-Évrard, à Lille|Salzmann, Auguste",French|French,1851 |1824,1855 |1872,1854,1854,1859,Salted paper print from paper negative,Image: 23.5 x 32.8 cm (9 1/4 x 12 15/16 in.) Mount: 44.8 x 59.7 cm (17 5/8 x 23 1/2 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286927,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.28,false,true,286928,Photographs,Photograph,"Jérusalem, Vallée de Hinnom, Détails de la frise de la retraite des Apôtres",,,,,,Printer|Artist,,"Imprimerie photographique de Blanquart-Évrard, à Lille|Auguste Salzmann","French, active 1851–55|French, 1824–1872",,"Imprimerie photographique de Blanquart-Évrard, à Lille|Salzmann, Auguste",French|French,1851 |1824,1855 |1872,1854,1854,1859,Salted paper print from paper negative,Image: 21.9 x 32.4 cm (8 5/8 x 12 3/4 in.) Mount: 44.6 x 58.9 cm (17 9/16 x 23 3/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286928,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.29,false,true,286929,Photographs,Photograph,"Jérusalem, Vallée de Hinnom, Ensemble du flanc droit",,,,,,Printer|Artist,,"Imprimerie photographique de Blanquart-Évrard, à Lille|Auguste Salzmann","French, active 1851–55|French, 1824–1872",,"Imprimerie photographique de Blanquart-Évrard, à Lille|Salzmann, Auguste",French|French,1851 |1824,1855 |1872,1854,1854,1859,Salted paper print from paper negative,Image: 23.4 x 33.3 cm (9 3/16 x 13 1/8 in.) Mount: 44.5 x 59.7 cm (17 1/2 x 23 1/2 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286929,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.30,false,true,286930,Photographs,Photograph,"Jérusalem, Vallée de Hinnom, Détails du flanc droit, 1",,,,,,Printer|Artist,,"Imprimerie photographique de Blanquart-Évrard, à Lille|Auguste Salzmann","French, active 1851–55|French, 1824–1872",,"Imprimerie photographique de Blanquart-Évrard, à Lille|Salzmann, Auguste",French|French,1851 |1824,1855 |1872,1854,1854,1859,Salted paper print from paper negative,Image: 23.1 x 32.3 cm (9 1/8 x 12 11/16 in.) Mount: 44.7 x 60.4 cm (17 5/8 x 23 3/4 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286930,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.31,false,true,286931,Photographs,Photograph,"Jérusalem, Vallée de Hinnom, Détails du flanc droit, 2",,,,,,Printer|Artist,,"Imprimerie photographique de Blanquart-Évrard, à Lille|Auguste Salzmann","French, active 1851–55|French, 1824–1872",,"Imprimerie photographique de Blanquart-Évrard, à Lille|Salzmann, Auguste",French|French,1851 |1824,1855 |1872,1854,1854,1859,Salted paper print from paper negative,Image: 23.5 x 33 cm (9 1/4 x 13 in.) Mount: 44.6 x 49.7 cm (17 9/16 x 19 9/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286931,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.32,false,true,286932,Photographs,Photograph,"Jérusalem, Champ du sang",,,,,,Printer|Artist,,"Imprimerie photographique de Blanquart-Évrard, à Lille|Auguste Salzmann","French, active 1851–55|French, 1824–1872",,"Imprimerie photographique de Blanquart-Évrard, à Lille|Salzmann, Auguste",French|French,1851 |1824,1855 |1872,1854,1854,1859,Salted paper print from paper negative,Image: 23.4 x 33 cm (9 3/16 x 13 in.) Mount: 44.6 x 59.7 cm (17 9/16 x 23 1/2 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286932,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.33,false,true,286933,Photographs,Photograph,"Jérusalem, Vallée de Hinnom, Tombeau antique à fronton triangulaire et à crossettes",,,,,,Printer|Artist,,"Imprimerie photographique de Blanquart-Évrard, à Lille|Auguste Salzmann","French, active 1851–55|French, 1824–1872",,"Imprimerie photographique de Blanquart-Évrard, à Lille|Salzmann, Auguste",French|French,1851 |1824,1855 |1872,1854,1854,1859,Salted paper print from paper negative,Image: 32.6 x 23.4 cm (12 13/16 x 9 3/16 in.) Mount: 59.6 x 44.4 cm (23 7/16 x 17 1/2 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286933,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.34,false,true,286934,Photographs,Photograph,"Jérusalem, Piscine de Siloe, Vue générale",,,,,,Printer|Artist,,"Imprimerie photographique de Blanquart-Évrard, à Lille|Auguste Salzmann","French, active 1851–55|French, 1824–1872",,"Imprimerie photographique de Blanquart-Évrard, à Lille|Salzmann, Auguste",French|French,1851 |1824,1855 |1872,1854,1854,1859,Salted paper print from paper negative,Image: 23.4 x 32.5 cm (9 3/16 x 12 13/16 in.) Mount: 44.6 x 60.1 cm (17 9/16 x 23 11/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286934,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.35,false,true,286935,Photographs,Photograph,"Jérusalem, Piscine de Siloe, Détails",,,,,,Printer|Artist,,"Imprimerie photographique de Blanquart-Évrard, à Lille|Auguste Salzmann","French, active 1851–55|French, 1824–1872",,"Imprimerie photographique de Blanquart-Évrard, à Lille|Salzmann, Auguste",French|French,1851 |1824,1855 |1872,1854,1854,1859,Salted paper print from paper negative,Image: 23.5 x 33.3 cm (9 1/4 x 13 1/8 in.) Mount: 44.7 x 60.2 cm (17 5/8 x 23 11/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286935,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.36,false,true,286936,Photographs,Photograph,"Jérusalem, Piscine de Siloe, Canal taillé dans le roc",,,,,,Printer|Artist,,"Imprimerie photographique de Blanquart-Évrard, à Lille|Auguste Salzmann","French, active 1851–55|French, 1824–1872",,"Imprimerie photographique de Blanquart-Évrard, à Lille|Salzmann, Auguste",French|French,1851 |1824,1855 |1872,1854,1854,1859,Salted paper print from paper negative,Image: 22.7 x 32.3 cm (8 15/16 x 12 11/16 in.) Mount: 44.6 x 60 cm (17 9/16 x 23 5/8 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286936,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.37,false,true,286937,Photographs,Photograph,"Jérusalem, Village de Siloam, Vue générale",,,,,,Printer|Artist,,"Imprimerie photographique de Blanquart-Évrard, à Lille|Auguste Salzmann","French, active 1851–55|French, 1824–1872",,"Imprimerie photographique de Blanquart-Évrard, à Lille|Salzmann, Auguste",French|French,1851 |1824,1855 |1872,1854,1854,1859,Salted paper print from paper negative,Image: 23.2 x 32.2 cm (9 1/8 x 12 11/16 in.) Mount: 44.5 x 60 cm (17 1/2 x 23 5/8 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286937,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.38,false,true,286938,Photographs,Photograph,"Jérusalem, Village de Siloam, Monolithe de forme égyptienne, 1",,,,,,Printer|Artist,,"Imprimerie photographique de Blanquart-Évrard, à Lille|Auguste Salzmann","French, active 1851–55|French, 1824–1872",,"Imprimerie photographique de Blanquart-Évrard, à Lille|Salzmann, Auguste",French|French,1851 |1824,1855 |1872,1854,1854,1859,Salted paper print from paper negative,Image: 23.4 x 33.1 cm (9 3/16 x 13 1/16 in.) Mount: 44.6 x 59.5 cm (17 9/16 x 23 7/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286938,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.39,false,true,286939,Photographs,Photograph,"Jérusalem, Village de Siloam, Monolithe de forme égyptienne, 2",,,,,,Printer|Artist,,"Imprimerie photographique de Blanquart-Évrard, à Lille|Auguste Salzmann","French, active 1851–55|French, 1824–1872",,"Imprimerie photographique de Blanquart-Évrard, à Lille|Salzmann, Auguste",French|French,1851 |1824,1855 |1872,1854,1854,1859,Salted paper print from paper negative,Image: 23.1 x 32.4 cm (9 1/8 x 12 3/4 in.) Mount: 44.7 x 59.7 cm (17 5/8 x 23 1/2 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286939,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.40,false,true,286940,Photographs,Photograph,"Jérusalem, Village de Siloam, Monolithe de forme égyptienne, 3",,,,,,Printer|Artist,,"Imprimerie photographique de Blanquart-Évrard, à Lille|Auguste Salzmann","French, active 1851–55|French, 1824–1872",,"Imprimerie photographique de Blanquart-Évrard, à Lille|Salzmann, Auguste",French|French,1851 |1824,1855 |1872,1854,1854,1859,Salted paper print from paper negative,Image: 23 x 32.5 cm (9 1/16 x 12 13/16 in.) Mount: 44.7 x 60.7 cm (17 5/8 x 23 7/8 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286940,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.41,false,true,286941,Photographs,Photograph,"Jérusalem, Vallée de Josaphat, Faces Ouest et Nord, 1",,,,,,Printer|Artist,,"Imprimerie photographique de Blanquart-Évrard, à Lille|Auguste Salzmann","French, active 1851–55|French, 1824–1872",,"Imprimerie photographique de Blanquart-Évrard, à Lille|Salzmann, Auguste",French|French,1851 |1824,1855 |1872,1854,1854,1859,Salted paper print from paper negative,Image: 22.8 x 32.8 cm (9 x 12 15/16 in.) Mount: 44.5 x 60.4 cm (17 1/2 x 23 3/4 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286941,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.42,false,true,286942,Photographs,Photograph,"Jérusalem, Vallée de Josaphat, Face Ouest et Nord, 2",,,,,,Printer|Artist,,"Imprimerie photographique de Blanquart-Évrard, à Lille|Auguste Salzmann","French, active 1851–55|French, 1824–1872",,"Imprimerie photographique de Blanquart-Évrard, à Lille|Salzmann, Auguste",French|French,1851 |1824,1855 |1872,1854,1854,1859,Salted paper print from paper negative,Image: 23.5 x 32.5 cm (9 1/4 x 12 13/16 in.) Mount: 45 x 59.5 cm (17 11/16 x 23 7/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286942,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.43,false,true,286943,Photographs,Photograph,"Jérusalem, Vallée de Josaphat, Vue générale",,,,,,Printer|Artist,,"Imprimerie photographique de Blanquart-Évrard, à Lille|Auguste Salzmann","French, active 1851–55|French, 1824–1872",,"Imprimerie photographique de Blanquart-Évrard, à Lille|Salzmann, Auguste",French|French,1851 |1824,1855 |1872,1854,1854,1859,Salted paper print from paper negative,Image: 22.3 x 32.2 cm (8 3/4 x 12 11/16 in.) Mount: 44.4 x 59.6 cm (17 1/2 x 23 7/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286943,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.44,false,true,286944,Photographs,Photograph,"Jérusalem, Vallée de Josaphat, Tombeau de Zacharie",,,,,,Printer|Artist,,"Imprimerie photographique de Blanquart-Évrard, à Lille|Auguste Salzmann","French, active 1851–55|French, 1824–1872",,"Imprimerie photographique de Blanquart-Évrard, à Lille|Salzmann, Auguste",French|French,1851 |1824,1855 |1872,1854,1854,1859,Salted paper print from paper negative,Image: 23.4 x 31.1 cm (9 3/16 x 12 1/4 in.) Mount: 44.8 x 64.3 cm (17 5/8 x 25 5/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286944,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.45,false,true,286894,Photographs,Photograph,"Jérusalem, Vallée de Josaphat, Tombeau de St. Jacques",,,,,,Printer|Artist,,"Imprimerie photographique de Blanquart-Évrard, à Lille|Auguste Salzmann","French, active 1851–55|French, 1824–1872",,"Imprimerie photographique de Blanquart-Évrard, à Lille|Salzmann, Auguste",French|French,1851 |1824,1855 |1872,1854,1854,1859,Salted paper print from paper negative,Image: 23.3 x 31.7 cm (9 3/16 x 12 1/2 in.) Mount: 44.6 x 59.7 cm (17 9/16 x 23 1/2 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286894,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.82,false,true,286797,Photographs,Photograph,"Jérusalem, Saint Sépulcre, Façade",,,,,,Printer|Artist,,"Imprimerie photographique de Blanquart-Évrard, à Lille|Auguste Salzmann","French, active 1851–55|French, 1824–1872",,"Imprimerie photographique de Blanquart-Évrard, à Lille|Salzmann, Auguste",French|French,1851 |1824,1855 |1872,1854,1854,1859,Salted paper print from paper negative,Image: 33.3 x 23.7 cm (13 1/8 x 9 5/16 in.) Mount: 60.2 x 44.6 cm (23 11/16 x 17 9/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286797,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.86,false,true,286798,Photographs,Photograph,"Jérusalem, Saint Sépulcre, détails des chapiteaux",,,,,,Printer|Artist,,"Imprimerie photographique de Blanquart-Évrard, à Lille|Auguste Salzmann","French, active 1851–55|French, 1824–1872",,"Imprimerie photographique de Blanquart-Évrard, à Lille|Salzmann, Auguste",French|French,1851 |1824,1855 |1872,1854,1854,1859,Salted paper print from paper negative,Image: 23.2 x 32.5 cm (9 1/8 x 12 13/16 in.) Mount: 44.6 x 60.2 cm (17 9/16 x 23 11/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286798,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.90,false,true,286799,Photographs,Photograph,"Jérusalem, Saint Sépulcre, Vue générale de la Chapelle du Calvaire",,,,,,Printer|Artist,,"Imprimerie photographique de Blanquart-Évrard, à Lille|Auguste Salzmann","French, active 1851–55|French, 1824–1872",,"Imprimerie photographique de Blanquart-Évrard, à Lille|Salzmann, Auguste",French|French,1851 |1824,1855 |1872,1854,1854,1859,Salted paper print from paper negative,Image: 33.3 x 23.7 cm (13 1/8 x 9 5/16 in.) Mount: 60.2 x 44.6 cm (23 11/16 x 17 9/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286799,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.96,false,true,286800,Photographs,Photograph,"Jérusalem, Saint Sépulcre, vue générale, 2",,,,,,Printer|Artist,,"Imprimerie photographique de Blanquart-Évrard, à Lille|Auguste Salzmann","French, active 1851–55|French, 1824–1872",,"Imprimerie photographique de Blanquart-Évrard, à Lille|Salzmann, Auguste",French|French,1851 |1824,1855 |1872,1854,1854,1859,Salted paper print from paper negative,Image: 23.2 x 33 cm (9 1/8 x 13 in.) Mount: 44.6 x 60.2 cm (17 9/16 x 23 11/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286800,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.97,false,true,286801,Photographs,Photograph,"Jérusalem, Saint Sépulcre, coupole",,,,,,Printer|Artist,,"Imprimerie photographique de Blanquart-Évrard, à Lille|Auguste Salzmann","French, active 1851–55|French, 1824–1872",,"Imprimerie photographique de Blanquart-Évrard, à Lille|Salzmann, Auguste",French|French,1851 |1824,1855 |1872,1854,1854,1859,Salted paper print from paper negative,Image: 33.2 x 23.5 cm (13 1/16 x 9 1/4 in.) Mount: 60.2 x 44.6 cm (23 11/16 x 17 9/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286801,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.100,false,true,286802,Photographs,Photograph,"Jérusalem, Saint Sépulcre, abside",,,,,,Printer|Artist,,"Imprimerie photographique de Blanquart-Évrard, à Lille|Auguste Salzmann","French, active 1851–55|French, 1824–1872",,"Imprimerie photographique de Blanquart-Évrard, à Lille|Salzmann, Auguste",French|French,1851 |1824,1855 |1872,1854,1854,1859,Salted paper print from paper negative,Image: 23.5 x 31 cm (9 1/4 x 12 3/16 in.) Mount: 44.6 x 60.2 cm (17 9/16 x 23 11/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286802,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.373.105,false,true,286793,Photographs,Photograph,"Jérusalem, croix en argent ciselé, donnée par Louis XIII à l'Eglise du Saint Sépulcre",,,,,,Printer|Artist,,"Imprimerie photographique de Blanquart-Évrard, à Lille|Auguste Salzmann","French, active 1851–55|French, 1824–1872",,"Imprimerie photographique de Blanquart-Évrard, à Lille|Salzmann, Auguste",French|French,1851 |1824,1855 |1872,1854,1854,1859,Salted paper prints from paper negative,Image: 34.8 x 23.5 cm (13 11/16 x 9 1/4 in.) Mount: 60.2 x 44.6 cm (23 11/16 x 17 9/16 in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286793,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.54,false,true,283137,Photographs,Photograph,[The Ascent of Mont Blanc],,,,,,Artist|Photography Studio,,Auguste-Rosalie Bisson|Bisson Frères,"French, 1826–1900|French, active 1852–1863",,"Bisson, Auguste-Rosalie|Bisson Frères",French|French,1826 |1852,1900 |1863,1861,1861,1861,Albumen silver print from glass negative,Image: 39.6 x 23.7 cm (15 9/16 x 9 5/16 in.) Mount: 63 x 45.7 cm (24 13/16 x 18 in.),"Gilman Collection, Purchase, Alfred Stieglitz Society Gifts, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/283137,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.1249,false,true,286265,Photographs,Photograph,François Flameng,,,,,,Artist|Person in Photograph,Person in photograph,Edmond Bénard|François Flameng,"French, 1838–1907|French, Paris 1856–1923 Paris",,"Bénard, Edmond|Flameng, François",French|French,1838 |1856,1907 |1923,1880s–90s,1880,1899,Albumen silver print from glass negative,Image: 20.1 × 26 cm (7 15/16 × 10 1/4 in.),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286265,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2011.65,false,true,294768,Photographs,Photograph,Charles Delahaye,,,,,,Artist|Person in Photograph,,Charles Marville|Charles Hippolyte Delahaye,"French, Paris 1813–1879 Paris|French, 1835/36– 1878 Paris",,"Marville, Charles|Delahaye, Charles Hippolyte",French|French,1813 |1835,1879 |1878,1852–53,1852,1853,Salted paper print from paper negative,Image: 21.6 x 15.9 cm (8 1/2 x 6 1/4 in.) Mount: 45 x 31.5 cm (17 11/16 x 12 3/8 in.),"Purchase, W. Bruce and Delaney H. Lundberg and Christian Keesee Charitable Trust Gifts, 2011",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/294768,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1998.57,true,true,282190,Photographs,Photograph,Pierrot Laughing,,,,,,Artist|Artist|Person in Photograph,,Adrien Tournachon|Nadar|Jean-Charles Deburau,"French, 1825–1903|French, Paris 1820–1910 Paris|French, 1829–1873",,"Tournachon, Adrien|Nadar|Deburau, Jean-Charles",French|French,1825 |1820 |1829,1903 |1910 |1873,1855,1855,1855,Gelatin-coated salted paper print (vernis-cuir),27.3 x 19.8 cm (10 3/4 x 7 13/16 in.),"Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 1998",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/282190,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.43,false,true,283118,Photographs,Photograph,Pierrot Running,,,,,,Artist|Artist|Person in Photograph,Person in photograph,Adrien Tournachon|Nadar|Jean-Charles Deburau,"French, 1825–1903|French, Paris 1820–1910 Paris|French, 1829–1873",,"Tournachon, Adrien|Nadar|Deburau, Jean-Charles",French|French,1825 |1820 |1829,1903 |1910 |1873,1854–55,1854,1855,Albumen silver print from glass negative,Image: 26.5 x 20.8cm (10 7/16 x 8 3/16in.) Mount: 41.3 x 34.5 cm (16 1/4 x 13 9/16 in.),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/283118,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.173.1–.48,false,true,305829,Photographs,Portfolio,Mécanisme de la physionomie humaine ou Analyse électro-physiologique de l’expression des passions applicable à la pratique des arts plastiques,,,,,,Artist|Artist,,Guillaume-Benjamin-Amand Duchenne de Boulogne|Adrien Tournachon,"French, 1806–1875|French, 1825–1903",,"Duchenne de Boulogne, Guillaume-Benjamin-Amand|Tournachon, Adrien",French|French,1806 |1825,1875 |1903,"1854–56, printed 1862",1854,1856,Albumen silver prints from glass negatives,,"Purchase, The Buddy Taub Foundation Gift, Dennis A. Roach and Jill Roach, Directors; Harris Brisbane Dick and William E. Dodge Funds; and W. Bruce and Delaney H. Lundberg Gift, 2013",,,,,,,,,,,,Portfolios,,http://www.metmuseum.org/art/collection/search/305829,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.1156,false,true,285664,Photographs,Stereograph; Autochrome,[Tulips],,,,,,Artist|Artist,,Auguste-Marie-Louis-Nicolas Lumière|Louis-Jean Lumière,"French, Besançon 1862–1954 Lyon|French, Besançon 1864–1948 Bandol",and,"Lumière, Auguste-Marie-Louis-Nicolas|Lumière, Louis-Jean",French|French,1862 |1864,1954 |1948,1896–1903,1896,1903,Trichromie,8.5 x 17.8cm,"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Transparencies,,http://www.metmuseum.org/art/collection/search/285664,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.1157,false,true,285666,Photographs,Stereograph; Autochrome,[Still Life of Flowers in a Stein],,,,,,Artist|Artist,,Auguste-Marie-Louis-Nicolas Lumière|Louis-Jean Lumière,"French, Besançon 1862–1954 Lyon|French, Besançon 1864–1948 Bandol",and,"Lumière, Auguste-Marie-Louis-Nicolas|Lumière, Louis-Jean",French|French,1862 |1864,1954 |1948,1896–1903,1896,1903,Trichromie,2 13/16 × 6 9/16 in. (7.2 × 16.7 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Transparencies,,http://www.metmuseum.org/art/collection/search/285666,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.1155,false,true,285665,Photographs,Autochrome,[Peacock],,,,,,Artist|Artist,,Louis-Jean Lumière|Auguste-Marie-Louis-Nicolas Lumière,"French, Besançon 1864–1948 Bandol|French, Besançon 1862–1954 Lyon",and,"Lumière, Louis-Jean|Lumière, Auguste-Marie-Louis-Nicolas",French|French,1864 |1862,1948 |1954,ca. 1907,1902,1912,Autochrome,13.0 x 17.7 cm (5 1/8 x 7in.),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Transparencies,,http://www.metmuseum.org/art/collection/search/285665,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.631,false,true,286695,Photographs,Photograph,[Empress Eugénie and the Prince Imperial],,,,,,Artist|Artist,Possibly by|Attributed to,Louise Deglane|François-Benjamin-Maria Delessert,"French|French, 1817–1868",,"Deglane, Louise|Delessert, François-Benjamin-Marie",French|French,1817,1868,1862,1862,1862,Albumen silver print from glass negative,21.6 x 16 cm (8 1/2 x 6 5/16 in.),"Gilman Collection, Purchase, Gift of The Howard Gilman Foundation, by exchange, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286695,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1993.248,false,true,266904,Photographs,Photograph,Icono-photographique. Mécanisme de la Physionomie Humaine. Fig. 65,,,,,,Artist|Artist,,Guillaume-Benjamin-Amand Duchenne de Boulogne|Adrien Tournachon,"French, 1806–1875|French, 1825–1903",,"Duchenne de Boulogne, Guillaume-Benjamin-Amand|Tournachon, Adrien",French|French,1806 |1825,1875 |1903,"1854–56, printed 1862",1854,1856,Albumen silver print from glass negative,12.0 x 9.2 cm (4 3/4 x 3 5/8 in.),"Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 1993",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/266904,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.229,false,true,307063,Photographs,Photograph,Faradisation du muscle frontal,,,,,,Artist|Artist,Possibly with,Guillaume-Benjamin-Amand Duchenne de Boulogne|Adrien Tournachon,"French, 1806–1875|French, 1825–1903",,"Duchenne de Boulogne, Guillaume-Benjamin-Amand|Tournachon, Adrien",French|French,1806 |1825,1875 |1903,"1854–56, printed 1862",1854,1856,Albumen silver print from glass negative,Image: 18.6 × 11.7 cm (7 5/16 × 4 5/8 in.) Mount: 27 × 18.6 cm (10 5/8 × 7 5/16 in.),"The Horace W. Goldsmith Foundation Fund, through Joyce and Robert Menschel, 2013",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/307063,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.173.1,false,true,623026,Photographs,Photograph,Figure 3: The face of an old man... photographed in repose.,,,,,,Artist|Artist,,Guillaume-Benjamin-Amand Duchenne de Boulogne|Adrien Tournachon,"French, 1806–1875|French, 1825–1903",,"Duchenne de Boulogne, Guillaume-Benjamin-Amand|Tournachon, Adrien",French|French,1806 |1825,1875 |1903,"1854–56, printed 1862",1854,1856,Albumen silver print from glass negative,Image (Oval): 28.3 × 20.3 cm (11 1/8 × 8 in.) Sheet: 30 × 22.9 cm (11 13/16 × 9 in.) Mount: 40.1 × 28.4 cm (15 13/16 × 11 3/16 in.),"Purchase, The Buddy Taub Foundation Gift, Dennis A. Roach and Jill Roach, Directors; Harris Brisbane Dick and William E. Dodge Funds; and W. Bruce and Delaney H. Lundberg Gift, 2013",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/623026,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.173.2,false,true,623027,Photographs,Photograph,Figure 4: The face in repose of a young man,,,,,,Artist|Artist,,Guillaume-Benjamin-Amand Duchenne de Boulogne|Adrien Tournachon,"French, 1806–1875|French, 1825–1903",,"Duchenne de Boulogne, Guillaume-Benjamin-Amand|Tournachon, Adrien",French|French,1806 |1825,1875 |1903,"1854–56, printed 1862",1854,1856,Albumen silver print from glass negative,Image (Oval): 28.5 × 20.3 cm (11 1/4 × 8 in.) Sheet: 30 × 22.8 cm (11 13/16 × 9 in.) Mount: 40.1 × 28.6 cm (15 13/16 × 11 1/4 in.),"Purchase, The Buddy Taub Foundation Gift, Dennis A. Roach and Jill Roach, Directors; Harris Brisbane Dick and William E. Dodge Funds; and W. Bruce and Delaney H. Lundberg Gift, 2013",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/623027,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.173.3,false,true,623028,Photographs,Photograph,Figure 6: The grimice produced is similar to a tic of the face,,,,,,Artist|Artist,,Guillaume-Benjamin-Amand Duchenne de Boulogne|Adrien Tournachon,"French, 1806–1875|French, 1825–1903",,"Duchenne de Boulogne, Guillaume-Benjamin-Amand|Tournachon, Adrien",French|French,1806 |1825,1875 |1903,"1854–56, printed 1862",1854,1856,Albumen silver print from glass negative,Image (Oval): 28.3 × 20.4 cm (11 1/8 × 8 1/16 in.) Sheet: 29.8 × 22.4 cm (11 3/4 × 8 13/16 in.) Mount: 40.1 × 28.6 cm (15 13/16 × 11 1/4 in.),"Purchase, The Buddy Taub Foundation Gift, Dennis A. Roach and Jill Roach, Directors; Harris Brisbane Dick and William E. Dodge Funds; and W. Bruce and Delaney H. Lundberg Gift, 2013",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/623028,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.173.4,false,true,623029,Photographs,Photograph,Figure 8: Contraction of the right m. frontalis.,,,,,,Artist|Artist,,Guillaume-Benjamin-Amand Duchenne de Boulogne|Adrien Tournachon,"French, 1806–1875|French, 1825–1903",,"Duchenne de Boulogne, Guillaume-Benjamin-Amand|Tournachon, Adrien",French|French,1806 |1825,1875 |1903,"1854–56, printed 1862",1854,1856,Albumen silver print from glass negative,Image (Oval): 28.4 × 20.2 cm (11 3/16 × 7 15/16 in.) Sheet: 29.8 × 22.9 cm (11 3/4 × 9 in.) Mount: 40.1 × 28.6 cm (15 13/16 × 11 1/4 in.),"Purchase, The Buddy Taub Foundation Gift, Dennis A. Roach and Jill Roach, Directors; Harris Brisbane Dick and William E. Dodge Funds; and W. Bruce and Delaney H. Lundberg Gift, 2013",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/623029,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.173.5,false,true,623030,Photographs,Photograph,Figure 9: A study of m. frontalis in maximum contraction,,,,,,Artist|Artist,,Guillaume-Benjamin-Amand Duchenne de Boulogne|Adrien Tournachon,"French, 1806–1875|French, 1825–1903",,"Duchenne de Boulogne, Guillaume-Benjamin-Amand|Tournachon, Adrien",French|French,1806 |1825,1875 |1903,"1854–56, printed 1862",1854,1856,Albumen silver print from glass negative,Image (Oval): 28.4 × 20.3 cm (11 3/16 × 8 in.) Sheet: 29.6 × 22.1 cm (11 5/8 × 8 11/16 in.) Mount: 40.2 × 28.2 cm (15 13/16 × 11 1/8 in.),"Purchase, The Buddy Taub Foundation Gift, Dennis A. Roach and Jill Roach, Directors; Harris Brisbane Dick and William E. Dodge Funds; and W. Bruce and Delaney H. Lundberg Gift, 2013",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/623030,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.173.6,false,true,623031,Photographs,Photograph,Figure 10: Showing the expressive lines of m. frontalis in a young girl,,,,,,Artist|Artist,,Guillaume-Benjamin-Amand Duchenne de Boulogne|Adrien Tournachon,"French, 1806–1875|French, 1825–1903",,"Duchenne de Boulogne, Guillaume-Benjamin-Amand|Tournachon, Adrien",French|French,1806 |1825,1875 |1903,"1854–56, printed 1862",1854,1856,Albumen silver print from glass negative,Image (Oval): 28.4 × 20.4 cm (11 3/16 × 8 1/16 in.) Sheet: 30 × 22.4 cm (11 13/16 × 8 13/16 in.) Mount: 40.2 × 28.4 cm (15 13/16 × 11 3/16 in.),"Purchase, The Buddy Taub Foundation Gift, Dennis A. Roach and Jill Roach, Directors; Harris Brisbane Dick and William E. Dodge Funds; and W. Bruce and Delaney H. Lundberg Gift, 2013",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/623031,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.173.7,false,true,623032,Photographs,Photograph,Figure 12: A study of the contraction of and the expression produced by the superior part of m. orbicularis oculi,,,,,,Artist|Artist,,Guillaume-Benjamin-Amand Duchenne de Boulogne|Adrien Tournachon,"French, 1806–1875|French, 1825–1903",,"Duchenne de Boulogne, Guillaume-Benjamin-Amand|Tournachon, Adrien",French|French,1806 |1825,1875 |1903,"1854–56, printed 1862",1854,1856,Albumen silver print from glass negative,Image (Oval): 28.3 × 20.4 cm (11 1/8 × 8 1/16 in.) Sheet: 29.5 × 22.1 cm (11 5/8 × 8 11/16 in.) Mount: 40.2 × 28.5 cm (15 13/16 × 11 1/4 in.),"Purchase, The Buddy Taub Foundation Gift, Dennis A. Roach and Jill Roach, Directors; Harris Brisbane Dick and William E. Dodge Funds; and W. Bruce and Delaney H. Lundberg Gift, 2013",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/623032,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.173.8,false,true,623033,Photographs,Photograph,"Figure 15: Mediation, mental concentration",,,,,,Artist|Artist,,Guillaume-Benjamin-Amand Duchenne de Boulogne|Adrien Tournachon,"French, 1806–1875|French, 1825–1903",,"Duchenne de Boulogne, Guillaume-Benjamin-Amand|Tournachon, Adrien",French|French,1806 |1825,1875 |1903,"1854–56, printed 1862",1854,1856,Albumen silver print from glass negative,Image (Oval): 28.3 × 20.3 cm (11 1/8 × 8 in.) Sheet: 29.9 × 22.1 cm (11 3/4 × 8 11/16 in.) Mount: 40.1 × 28.5 cm (15 13/16 × 11 1/4 in.),"Purchase, The Buddy Taub Foundation Gift, Dennis A. Roach and Jill Roach, Directors; Harris Brisbane Dick and William E. Dodge Funds; and W. Bruce and Delaney H. Lundberg Gift, 2013",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/623033,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.173.9,false,true,623034,Photographs,Photograph,Figure 16: Expression of severity,,,,,,Artist|Artist,,Guillaume-Benjamin-Amand Duchenne de Boulogne|Adrien Tournachon,"French, 1806–1875|French, 1825–1903",,"Duchenne de Boulogne, Guillaume-Benjamin-Amand|Tournachon, Adrien",French|French,1806 |1825,1875 |1903,"1854–56, printed 1862",1854,1856,Albumen silver print from glass negative,Image (Oval): 28.5 × 20.4 cm (11 1/4 × 8 1/16 in.) Sheet: 29.9 × 21.9 cm (11 3/4 × 8 5/8 in.) Mount: 40.2 × 28.4 cm (15 13/16 × 11 3/16 in.),"Purchase, The Buddy Taub Foundation Gift, Dennis A. Roach and Jill Roach, Directors; Harris Brisbane Dick and William E. Dodge Funds; and W. Bruce and Delaney H. Lundberg Gift, 2013",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/623034,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.173.10,false,true,623035,Photographs,Photograph,"Figure 17: On the right, electrization of m. procerus: severity, aggression. On the left: attention.",,,,,,Artist|Artist,,Guillaume-Benjamin-Amand Duchenne de Boulogne|Adrien Tournachon,"French, 1806–1875|French, 1825–1903",,"Duchenne de Boulogne, Guillaume-Benjamin-Amand|Tournachon, Adrien",French|French,1806 |1825,1875 |1903,"1854–56, printed 1862",1854,1856,Albumen silver print from glass negative,Image (Oval): 28.3 × 20.5 cm (11 1/8 × 8 1/16 in.) Sheet: 29.7 × 22.7 cm (11 11/16 × 8 15/16 in.) Mount: 40.2 × 28.5 cm (15 13/16 × 11 1/4 in.),"Purchase, The Buddy Taub Foundation Gift, Dennis A. Roach and Jill Roach, Directors; Harris Brisbane Dick and William E. Dodge Funds; and W. Bruce and Delaney H. Lundberg Gift, 2013",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/623035,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.173.11,false,true,623036,Photographs,Photograph,"Figure 18: Aggression, wickedness",,,,,,Artist|Artist,,Guillaume-Benjamin-Amand Duchenne de Boulogne|Adrien Tournachon,"French, 1806–1875|French, 1825–1903",,"Duchenne de Boulogne, Guillaume-Benjamin-Amand|Tournachon, Adrien",French|French,1806 |1825,1875 |1903,"1854–56, printed 1862",1854,1856,Albumen silver print from glass negative,Image (Oval): 28.5 × 20.4 cm (11 1/4 × 8 1/16 in.) Sheet: 29.9 × 22.6 cm (11 3/4 × 8 7/8 in.) Mount: 40 × 28.3 cm (15 3/4 × 11 1/8 in.),"Purchase, The Buddy Taub Foundation Gift, Dennis A. Roach and Jill Roach, Directors; Harris Brisbane Dick and William E. Dodge Funds; and W. Bruce and Delaney H. Lundberg Gift, 2013",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/623036,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.173.12,false,true,623037,Photographs,Photograph,Figure 19: Suffering,,,,,,Artist|Artist,,Guillaume-Benjamin-Amand Duchenne de Boulogne|Adrien Tournachon,"French, 1806–1875|French, 1825–1903",,"Duchenne de Boulogne, Guillaume-Benjamin-Amand|Tournachon, Adrien",French|French,1806 |1825,1875 |1903,"1854–56, printed 1862",1854,1856,Albumen silver print from glass negative,"Image (Oval): 28.2 × 20.4 cm (11 1/8 × 8 1/16 in.) Sheet: 29.4 cm, 22.2 gr (11 9/16) Mount: 40.3 × 28 cm (15 7/8 × 11 in.)","Purchase, The Buddy Taub Foundation Gift, Dennis A. Roach and Jill Roach, Directors; Harris Brisbane Dick and William E. Dodge Funds; and W. Bruce and Delaney H. Lundberg Gift, 2013",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/623037,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.173.13,false,true,623038,Photographs,Photograph,"Figure 20: Profound suffering, with resignation",,,,,,Artist|Artist,,Guillaume-Benjamin-Amand Duchenne de Boulogne|Adrien Tournachon,"French, 1806–1875|French, 1825–1903",,"Duchenne de Boulogne, Guillaume-Benjamin-Amand|Tournachon, Adrien",French|French,1806 |1825,1875 |1903,"1854–56, printed 1862",1854,1856,Albumen silver print from glass negative,Image (Oval): 28.5 × 20.4 cm (11 1/4 × 8 1/16 in.) Sheet: 29.8 × 22.4 cm (11 3/4 × 8 13/16 in.) Mount: 40.3 × 28.5 cm (15 7/8 × 11 1/4 in.),"Purchase, The Buddy Taub Foundation Gift, Dennis A. Roach and Jill Roach, Directors; Harris Brisbane Dick and William E. Dodge Funds; and W. Bruce and Delaney H. Lundberg Gift, 2013",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/623038,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.173.14,false,true,623039,Photographs,Photograph,Figure 21: Painful recollection and recollection or calling something to mind,,,,,,Artist|Artist,,Guillaume-Benjamin-Amand Duchenne de Boulogne|Adrien Tournachon,"French, 1806–1875|French, 1825–1903",,"Duchenne de Boulogne, Guillaume-Benjamin-Amand|Tournachon, Adrien",French|French,1806 |1825,1875 |1903,"1854–56, printed 1862",1854,1856,Albumen silver print from glass negative,Image (Oval): 28.4 × 20.3 cm (11 3/16 × 8 in.) Sheet: 29.7 × 22.7 cm (11 11/16 × 8 15/16 in.) Mount: 40.2 × 28.6 cm (15 13/16 × 11 1/4 in.),"Purchase, The Buddy Taub Foundation Gift, Dennis A. Roach and Jill Roach, Directors; Harris Brisbane Dick and William E. Dodge Funds; and W. Bruce and Delaney H. Lundberg Gift, 2013",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/623039,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.173.15,false,true,623040,Photographs,Photograph,Figure 22: No painful expression,,,,,,Artist|Artist,,Guillaume-Benjamin-Amand Duchenne de Boulogne|Adrien Tournachon,"French, 1806–1875|French, 1825–1903",,"Duchenne de Boulogne, Guillaume-Benjamin-Amand|Tournachon, Adrien",French|French,1806 |1825,1875 |1903,"1854–56, printed 1862",1854,1856,Albumen silver print from glass negative,Image (Oval): 28.5 × 20.4 cm (11 1/4 × 8 1/16 in.) Sheet: 29.7 × 22.6 cm (11 11/16 × 8 7/8 in.) Mount: 40.3 × 28.1 cm (15 7/8 × 11 1/16 in.),"Purchase, The Buddy Taub Foundation Gift, Dennis A. Roach and Jill Roach, Directors; Harris Brisbane Dick and William E. Dodge Funds; and W. Bruce and Delaney H. Lundberg Gift, 2013",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/623040,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.173.16,false,true,623041,Photographs,Photograph,"Figure 24: Extreme pain to the point of exhaustion, the head of Christ and memory of love or ecstatic gaze.",,,,,,Artist|Artist,,Guillaume-Benjamin-Amand Duchenne de Boulogne|Adrien Tournachon,"French, 1806–1875|French, 1825–1903",,"Duchenne de Boulogne, Guillaume-Benjamin-Amand|Tournachon, Adrien",French|French,1806 |1825,1875 |1903,"1854–56, printed 1862",1854,1856,Albumen silver print from glass negative,Image (Oval): 28.3 × 20.3 cm (11 1/8 × 8 in.) Sheet: 29.9 × 22 cm (11 3/4 × 8 11/16 in.) Mount: 40.2 × 28.5 cm (15 13/16 × 11 1/4 in.),"Purchase, The Buddy Taub Foundation Gift, Dennis A. Roach and Jill Roach, Directors; Harris Brisbane Dick and William E. Dodge Funds; and W. Bruce and Delaney H. Lundberg Gift, 2013",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/623041,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.173.17,false,true,623042,Photographs,Photograph,Figure 25: Not an expression of pain,,,,,,Artist|Artist,,Guillaume-Benjamin-Amand Duchenne de Boulogne|Adrien Tournachon,"French, 1806–1875|French, 1825–1903",,"Duchenne de Boulogne, Guillaume-Benjamin-Amand|Tournachon, Adrien",French|French,1806 |1825,1875 |1903,"1854–56, printed 1862",1854,1856,Albumen silver print from glass negative,Image (Oval): 28.4 × 20.4 cm (11 3/16 × 8 1/16 in.) Sheet: 29.3 × 22 cm (11 9/16 × 8 11/16 in.) Mount: 40.2 × 28.5 cm (15 13/16 × 11 1/4 in.),"Purchase, The Buddy Taub Foundation Gift, Dennis A. Roach and Jill Roach, Directors; Harris Brisbane Dick and William E. Dodge Funds; and W. Bruce and Delaney H. Lundberg Gift, 2013",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/623042,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.173.18,false,true,623043,Photographs,Photograph,"Figure 26: Expression of painful attention and attention, attentive gaze.",,,,,,Artist|Artist,,Guillaume-Benjamin-Amand Duchenne de Boulogne|Adrien Tournachon,"French, 1806–1875|French, 1825–1903",,"Duchenne de Boulogne, Guillaume-Benjamin-Amand|Tournachon, Adrien",French|French,1806 |1825,1875 |1903,"1854–56, printed 1862",1854,1856,Albumen silver print from glass negative,Image (Oval): 28.5 × 20.3 cm (11 1/4 × 8 in.) Sheet: 29.7 × 22.5 cm (11 11/16 × 8 7/8 in.) Mount: 40.3 × 28 cm (15 7/8 × 11 in.),"Purchase, The Buddy Taub Foundation Gift, Dennis A. Roach and Jill Roach, Directors; Harris Brisbane Dick and William E. Dodge Funds; and W. Bruce and Delaney H. Lundberg Gift, 2013",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/623043,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.173.19,false,true,623044,Photographs,Photograph,Figure 27: Expression proportionally more pained,,,,,,Artist|Artist,,Guillaume-Benjamin-Amand Duchenne de Boulogne|Adrien Tournachon,"French, 1806–1875|French, 1825–1903",,"Duchenne de Boulogne, Guillaume-Benjamin-Amand|Tournachon, Adrien",French|French,1806 |1825,1875 |1903,"1854–56, printed 1862",1854,1856,Albumen silver print from glass negative,Image (Oval): 28.4 × 20.3 cm (11 3/16 × 8 in.) Sheet: 29.9 × 22.2 cm (11 3/4 × 8 3/4 in.) Mount: 40.2 × 28.5 cm (15 13/16 × 11 1/4 in.),"Purchase, The Buddy Taub Foundation Gift, Dennis A. Roach and Jill Roach, Directors; Harris Brisbane Dick and William E. Dodge Funds; and W. Bruce and Delaney H. Lundberg Gift, 2013",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/623044,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.173.20,false,true,623045,Photographs,Photograph,Figure 34: Grimace,,,,,,Artist|Artist,,Guillaume-Benjamin-Amand Duchenne de Boulogne|Adrien Tournachon,"French, 1806–1875|French, 1825–1903",,"Duchenne de Boulogne, Guillaume-Benjamin-Amand|Tournachon, Adrien",French|French,1806 |1825,1875 |1903,"1854–56, printed 1862",1854,1856,Albumen silver print from glass negative,Image (Oval): 28.4 × 20.4 cm (11 3/16 × 8 1/16 in.) Sheet: 29.9 × 22.5 cm (11 3/4 × 8 7/8 in.) Mount: 40.2 × 28.5 cm (15 13/16 × 11 1/4 in.),"Purchase, The Buddy Taub Foundation Gift, Dennis A. Roach and Jill Roach, Directors; Harris Brisbane Dick and William E. Dodge Funds; and W. Bruce and Delaney H. Lundberg Gift, 2013",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/623045,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.173.21,false,true,623046,Photographs,Photograph,Figure 36: Scornful laughter and scornful disgust,,,,,,Artist|Artist,,Guillaume-Benjamin-Amand Duchenne de Boulogne|Adrien Tournachon,"French, 1806–1875|French, 1825–1903",,"Duchenne de Boulogne, Guillaume-Benjamin-Amand|Tournachon, Adrien",French|French,1806 |1825,1875 |1903,"1854–56, printed 1862",1854,1856,Albumen silver print from glass negative,Image (Oval): 28.2 × 20.5 cm (11 1/8 × 8 1/16 in.) Sheet: 29.6 × 22.3 cm (11 5/8 × 8 3/4 in.) Mount: 40.2 × 28.1 cm (15 13/16 × 11 1/16 in.),"Purchase, The Buddy Taub Foundation Gift, Dennis A. Roach and Jill Roach, Directors; Harris Brisbane Dick and William E. Dodge Funds; and W. Bruce and Delaney H. Lundberg Gift, 2013",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/623046,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.173.22,false,true,623047,Photographs,Photograph,Figure 39: The attention attracted by an object that provokes lascivious ideas and desires.,,,,,,Artist|Artist,,Guillaume-Benjamin-Amand Duchenne de Boulogne|Adrien Tournachon,"French, 1806–1875|French, 1825–1903",,"Duchenne de Boulogne, Guillaume-Benjamin-Amand|Tournachon, Adrien",French|French,1806 |1825,1875 |1903,"1854–56, printed 1862",1854,1856,Albumen silver print from glass negative,Image (Oval): 28.5 × 20.4 cm (11 1/4 × 8 1/16 in.) Sheet: 29.8 × 22.8 cm (11 3/4 × 9 in.) Mount: 40.2 × 28.2 cm (15 13/16 × 11 1/8 in.),"Purchase, The Buddy Taub Foundation Gift, Dennis A. Roach and Jill Roach, Directors; Harris Brisbane Dick and William E. Dodge Funds; and W. Bruce and Delaney H. Lundberg Gift, 2013",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/623047,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.173.23,false,true,623048,Photographs,Photograph,"Figure 42: Gaiety expressed by the ideas of lustfulness, cynicism, and lewdness.",,,,,,Artist|Artist,,Guillaume-Benjamin-Amand Duchenne de Boulogne|Adrien Tournachon,"French, 1806–1875|French, 1825–1903",,"Duchenne de Boulogne, Guillaume-Benjamin-Amand|Tournachon, Adrien",French|French,1806 |1825,1875 |1903,"1854–56, printed 1862",1854,1856,Albumen silver print from glass negative,Image (Oval): 28.6 × 20.3 cm (11 1/4 × 8 in.) Sheet: 29.9 × 22.4 cm (11 3/4 × 8 13/16 in.) Mount: 40.2 × 28.4 cm (15 13/16 × 11 3/16 in.),"Purchase, The Buddy Taub Foundation Gift, Dennis A. Roach and Jill Roach, Directors; Harris Brisbane Dick and William E. Dodge Funds; and W. Bruce and Delaney H. Lundberg Gift, 2013",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/623048,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.173.24,false,true,623049,Photographs,Photograph,Figure 45: Pain and despair.,,,,,,Artist|Artist,,Guillaume-Benjamin-Amand Duchenne de Boulogne|Adrien Tournachon,"French, 1806–1875|French, 1825–1903",,"Duchenne de Boulogne, Guillaume-Benjamin-Amand|Tournachon, Adrien",French|French,1806 |1825,1875 |1903,"1854–56, printed 1862",1854,1856,Albumen silver print from glass negative,Image (Oval): 28.6 × 20.4 cm (11 1/4 × 8 1/16 in.) Sheet: 29.8 × 22.9 cm (11 3/4 × 9 in.) Mount: 40.1 × 28.4 cm (15 13/16 × 11 3/16 in.),"Purchase, The Buddy Taub Foundation Gift, Dennis A. Roach and Jill Roach, Directors; Harris Brisbane Dick and William E. Dodge Funds; and W. Bruce and Delaney H. Lundberg Gift, 2013",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/623049,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.173.25,false,true,623050,Photographs,Photograph,Figure 47: A suggestion of this same weeping,,,,,,Artist|Artist,,Guillaume-Benjamin-Amand Duchenne de Boulogne|Adrien Tournachon,"French, 1806–1875|French, 1825–1903",,"Duchenne de Boulogne, Guillaume-Benjamin-Amand|Tournachon, Adrien",French|French,1806 |1825,1875 |1903,"1854–56, printed 1862",1854,1856,Albumen silver print from glass negative,Image (Oval): 27.9 × 20.3 cm (11 × 8 in.) Sheet: 29.6 × 21.8 cm (11 5/8 × 8 9/16 in.) Mount: 40.2 × 28.4 cm (15 13/16 × 11 3/16 in.),"Purchase, The Buddy Taub Foundation Gift, Dennis A. Roach and Jill Roach, Directors; Harris Brisbane Dick and William E. Dodge Funds; and W. Bruce and Delaney H. Lundberg Gift, 2013",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/623050,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.173.26,false,true,623051,Photographs,Photograph,"Figure 48: Mild weeping, pity and feeble false laughter",,,,,,Artist|Artist,,Guillaume-Benjamin-Amand Duchenne de Boulogne|Adrien Tournachon,"French, 1806–1875|French, 1825–1903",,"Duchenne de Boulogne, Guillaume-Benjamin-Amand|Tournachon, Adrien",French|French,1806 |1825,1875 |1903,"1854–56, printed 1862",1854,1856,Albumen silver print from glass negative,Image (Oval): 28.5 × 20.4 cm (11 1/4 × 8 1/16 in.) Sheet: 30.2 × 23.1 cm (11 7/8 × 9 1/8 in.) Mount: 40.2 × 28.5 cm (15 13/16 × 11 1/4 in.),"Purchase, The Buddy Taub Foundation Gift, Dennis A. Roach and Jill Roach, Directors; Harris Brisbane Dick and William E. Dodge Funds; and W. Bruce and Delaney H. Lundberg Gift, 2013",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/623051,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.173.27,false,true,623052,Photographs,Photograph,Figure 49: Painful weeping and forward looking.,,,,,,Artist|Artist,,Guillaume-Benjamin-Amand Duchenne de Boulogne|Adrien Tournachon,"French, 1806–1875|French, 1825–1903",,"Duchenne de Boulogne, Guillaume-Benjamin-Amand|Tournachon, Adrien",French|French,1806 |1825,1875 |1903,"1854–56, printed 1862",1854,1856,Albumen silver print from glass negative,Image (Oval): 28.6 × 20.5 cm (11 1/4 × 8 1/16 in.) Sheet: 29.5 × 22.8 cm (11 5/8 × 9 in.) Mount: 40.1 × 28.7 cm (15 13/16 × 11 5/16 in.),"Purchase, The Buddy Taub Foundation Gift, Dennis A. Roach and Jill Roach, Directors; Harris Brisbane Dick and William E. Dodge Funds; and W. Bruce and Delaney H. Lundberg Gift, 2013",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/623052,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.173.28,false,true,623053,Photographs,Photograph,Figure 50: Affected weeping and face in repose,,,,,,Artist|Artist,,Guillaume-Benjamin-Amand Duchenne de Boulogne|Adrien Tournachon,"French, 1806–1875|French, 1825–1903",,"Duchenne de Boulogne, Guillaume-Benjamin-Amand|Tournachon, Adrien",French|French,1806 |1825,1875 |1903,"1854–56, printed 1862",1854,1856,Albumen silver print from glass negative,Image (Oval): 28.5 × 20.4 cm (11 1/4 × 8 1/16 in.) Sheet: 30.1 × 22.6 cm (11 7/8 × 8 7/8 in.) Mount: 40.3 × 28.4 cm (15 7/8 × 11 3/16 in.),"Purchase, The Buddy Taub Foundation Gift, Dennis A. Roach and Jill Roach, Directors; Harris Brisbane Dick and William E. Dodge Funds; and W. Bruce and Delaney H. Lundberg Gift, 2013",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/623053,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.173.29,false,true,623054,Photographs,Photograph,Figure 52: Voluntary retraction of the lower lip,,,,,,Artist|Artist,,Guillaume-Benjamin-Amand Duchenne de Boulogne|Adrien Tournachon,"French, 1806–1875|French, 1825–1903",,"Duchenne de Boulogne, Guillaume-Benjamin-Amand|Tournachon, Adrien",French|French,1806 |1825,1875 |1903,"1854–56, printed 1862",1854,1856,Albumen silver print from glass negative,Image (Oval): 28.4 × 20.3 cm (11 3/16 × 8 in.) Sheet: 29.2 × 22.2 cm (11 1/2 × 8 3/4 in.) Mount: 40.1 × 28.4 cm (15 13/16 × 11 3/16 in.),"Purchase, The Buddy Taub Foundation Gift, Dennis A. Roach and Jill Roach, Directors; Harris Brisbane Dick and William E. Dodge Funds; and W. Bruce and Delaney H. Lundberg Gift, 2013",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/623054,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.173.30,false,true,623055,Photographs,Photograph,Figure 53: Whimpering and false laughter,,,,,,Artist|Artist,,Guillaume-Benjamin-Amand Duchenne de Boulogne|Adrien Tournachon,"French, 1806–1875|French, 1825–1903",,"Duchenne de Boulogne, Guillaume-Benjamin-Amand|Tournachon, Adrien",French|French,1806 |1825,1875 |1903,"1854–56, printed 1862",1854,1856,Albumen silver print from glass negative,Image (Oval): 28.6 × 20.5 cm (11 1/4 × 8 1/16 in.) Mount: 40.2 × 28.5 cm (15 13/16 × 11 1/4 in.) Sheet: 29.7 × 22.1 cm (11 11/16 × 8 11/16 in.),"Purchase, The Buddy Taub Foundation Gift, Dennis A. Roach and Jill Roach, Directors; Harris Brisbane Dick and William E. Dodge Funds; and W. Bruce and Delaney H. Lundberg Gift, 2013",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/623055,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.173.31,false,true,623056,Photographs,Photograph,Figure 54: Voluntary lowering of the lower jaw,,,,,,Artist|Artist,,Guillaume-Benjamin-Amand Duchenne de Boulogne|Adrien Tournachon,"French, 1806–1875|French, 1825–1903",,"Duchenne de Boulogne, Guillaume-Benjamin-Amand|Tournachon, Adrien",French|French,1806 |1825,1875 |1903,"1854–56, printed 1862",1854,1856,Albumen silver print from glass negative,Image (Oval): 28.6 × 20.6 cm (11 1/4 × 8 1/8 in.) Sheet: 30 × 23.1 cm (11 13/16 × 9 1/8 in.) Mount: 40.3 × 28.6 cm (15 7/8 × 11 1/4 in.),"Purchase, The Buddy Taub Foundation Gift, Dennis A. Roach and Jill Roach, Directors; Harris Brisbane Dick and William E. Dodge Funds; and W. Bruce and Delaney H. Lundberg Gift, 2013",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/623056,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.173.32,false,true,623057,Photographs,Photograph,Figure 55: Astonishment badly rendered by the subject: a ridiculous and inane expression.,,,,,,Artist|Artist,,Guillaume-Benjamin-Amand Duchenne de Boulogne|Adrien Tournachon,"French, 1806–1875|French, 1825–1903",,"Duchenne de Boulogne, Guillaume-Benjamin-Amand|Tournachon, Adrien",French|French,1806 |1825,1875 |1903,"1854–56, printed 1862",1854,1856,Albumen silver print from glass negative,Image (Oval): 28.3 × 20.4 cm (11 1/8 × 8 1/16 in.) Sheet: 29.8 × 23.1 cm (11 3/4 × 9 1/8 in.) Mount: 40.2 × 28.5 cm (15 13/16 × 11 1/4 in.),"Purchase, The Buddy Taub Foundation Gift, Dennis A. Roach and Jill Roach, Directors; Harris Brisbane Dick and William E. Dodge Funds; and W. Bruce and Delaney H. Lundberg Gift, 2013",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/623057,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.173.33,false,true,623058,Photographs,Photograph,Figure 56: Surprise,,,,,,Artist|Artist,,Guillaume-Benjamin-Amand Duchenne de Boulogne|Adrien Tournachon,"French, 1806–1875|French, 1825–1903",,"Duchenne de Boulogne, Guillaume-Benjamin-Amand|Tournachon, Adrien",French|French,1806 |1825,1875 |1903,"1854–56, printed 1862",1854,1856,Albumen silver print from glass negative,Image (Oval): 28.3 × 20.3 cm (11 1/8 × 8 in.) Sheet: 30 × 22.6 cm (11 13/16 × 8 7/8 in.) Mount: 40.2 × 28.5 cm (15 13/16 × 11 1/4 in.),"Purchase, The Buddy Taub Foundation Gift, Dennis A. Roach and Jill Roach, Directors; Harris Brisbane Dick and William E. Dodge Funds; and W. Bruce and Delaney H. Lundberg Gift, 2013",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/623058,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.173.34,false,true,623059,Photographs,Photograph,"Figure 57: Astonishment, stupefaction, amazement",,,,,,Artist|Artist,,Guillaume-Benjamin-Amand Duchenne de Boulogne|Adrien Tournachon,"French, 1806–1875|French, 1825–1903",,"Duchenne de Boulogne, Guillaume-Benjamin-Amand|Tournachon, Adrien",French|French,1806 |1825,1875 |1903,"1854–56, printed 1862",1854,1856,Albumen silver print from glass negative,"Image (Oval): 28.3 × 20.3 cm (11 1/8 × 8 in.) Sheet: 29.9 × 22.7 cm (11 3/4 in., 22.7 kg) Mount: 40.2 × 28.4 cm (15 13/16 × 11 3/16 in.)","Purchase, The Buddy Taub Foundation Gift, Dennis A. Roach and Jill Roach, Directors; Harris Brisbane Dick and William E. Dodge Funds; and W. Bruce and Delaney H. Lundberg Gift, 2013",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/623059,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.173.35,false,true,623060,Photographs,Photograph,Figure 60: Fright,,,,,,Artist|Artist,,Guillaume-Benjamin-Amand Duchenne de Boulogne|Adrien Tournachon,"French, 1806–1875|French, 1825–1903",,"Duchenne de Boulogne, Guillaume-Benjamin-Amand|Tournachon, Adrien",French|French,1806 |1825,1875 |1903,"1854–56, printed 1862",1854,1856,Albumen silver print from glass negative,Image (Oval): 28.3 × 20.3 cm (11 1/8 × 8 in.) Sheet: 29.8 × 23 cm (11 3/4 × 9 1/16 in.) Mount: 40.2 × 28.5 cm (15 13/16 × 11 1/4 in.),"Purchase, The Buddy Taub Foundation Gift, Dennis A. Roach and Jill Roach, Directors; Harris Brisbane Dick and William E. Dodge Funds; and W. Bruce and Delaney H. Lundberg Gift, 2013",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/623060,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.173.36,false,true,623061,Photographs,Photograph,"Figure 62: Terror, semiprofile",,,,,,Artist|Artist,,Guillaume-Benjamin-Amand Duchenne de Boulogne|Adrien Tournachon,"French, 1806–1875|French, 1825–1903",,"Duchenne de Boulogne, Guillaume-Benjamin-Amand|Tournachon, Adrien",French|French,1806 |1825,1875 |1903,"1854–56, printed 1862",1854,1856,Albumen silver print from glass negative,Image: 28.4 × 20.3 cm (11 3/16 × 8 in.) Sheet: 29.7 × 22.3 cm (11 11/16 × 8 3/4 in.) Mount: 40.3 × 28.5 cm (15 7/8 × 11 1/4 in.),"Purchase, The Buddy Taub Foundation Gift, Dennis A. Roach and Jill Roach, Directors; Harris Brisbane Dick and William E. Dodge Funds; and W. Bruce and Delaney H. Lundberg Gift, 2013",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/623061,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.173.37,false,true,623062,Photographs,Photograph,Figure 63: Expression of terror,,,,,,Artist|Artist,,Guillaume-Benjamin-Amand Duchenne de Boulogne|Adrien Tournachon,"French, 1806–1875|French, 1825–1903",,"Duchenne de Boulogne, Guillaume-Benjamin-Amand|Tournachon, Adrien",French|French,1806 |1825,1875 |1903,"1854–56, printed 1862",1854,1856,Albumen silver print from glass negative,Image (Oval): 28.2 × 20.3 cm (11 1/8 × 8 in.) Sheet: 29.5 × 22.2 cm (11 5/8 × 8 3/4 in.) Mount: 40.3 × 28.5 cm (15 7/8 × 11 1/4 in.),"Purchase, The Buddy Taub Foundation Gift, Dennis A. Roach and Jill Roach, Directors; Harris Brisbane Dick and William E. Dodge Funds; and W. Bruce and Delaney H. Lundberg Gift, 2013",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/623062,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.173.38,false,true,623063,Photographs,Photograph,"Figure 66: Head of Arrotino (the spy, the knife grinder, and so on)",,,,,,Artist|Artist,,Guillaume-Benjamin-Amand Duchenne de Boulogne|Adrien Tournachon,"French, 1806–1875|French, 1825–1903",,"Duchenne de Boulogne, Guillaume-Benjamin-Amand|Tournachon, Adrien",French|French,1806 |1825,1875 |1903,"1854–56, printed 1862",1854,1856,Albumen silver print from glass negative,Image (Oval): 28.5 × 20.4 cm (11 1/4 × 8 1/16 in.) Sheet: 30 × 22.7 cm (11 13/16 × 8 15/16 in.) Mount: 40.1 × 28.6 cm (15 13/16 × 11 1/4 in.),"Purchase, The Buddy Taub Foundation Gift, Dennis A. Roach and Jill Roach, Directors; Harris Brisbane Dick and William E. Dodge Funds; and W. Bruce and Delaney H. Lundberg Gift, 2013",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/623063,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.173.39,false,true,623064,Photographs,Photograph,Figure 70: Head of the Laocoön of Rome,,,,,,Artist|Artist,,Guillaume-Benjamin-Amand Duchenne de Boulogne|Adrien Tournachon,"French, 1806–1875|French, 1825–1903",,"Duchenne de Boulogne, Guillaume-Benjamin-Amand|Tournachon, Adrien",French|French,1806 |1825,1875 |1903,"1854–56, printed 1862",1854,1856,Albumen silver print from glass negative,Image (Oval): 28.2 × 20.3 cm (11 1/8 × 8 in.) Sheet: 29.1 × 21.4 cm (11 7/16 × 8 7/16 in.) Mount: 40.3 × 28.6 cm (15 7/8 × 11 1/4 in.),"Purchase, The Buddy Taub Foundation Gift, Dennis A. Roach and Jill Roach, Directors; Harris Brisbane Dick and William E. Dodge Funds; and W. Bruce and Delaney H. Lundberg Gift, 2013",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/623064,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.173.40,false,true,623065,Photographs,Photograph,Figure 71: Same head as in Plate 70,,,,,,Artist|Artist,,Guillaume-Benjamin-Amand Duchenne de Boulogne|Adrien Tournachon,"French, 1806–1875|French, 1825–1903",,"Duchenne de Boulogne, Guillaume-Benjamin-Amand|Tournachon, Adrien",French|French,1806 |1825,1875 |1903,"1854–56, printed 1862",1854,1856,Albumen silver print from glass negative,Image (Oval): 28.3 × 20.4 cm (11 1/8 × 8 1/16 in.) Sheet: 29.7 × 22.7 cm (11 11/16 × 8 15/16 in.) Mount: 40.2 × 28.2 cm (15 13/16 × 11 1/8 in.),"Purchase, The Buddy Taub Foundation Gift, Dennis A. Roach and Jill Roach, Directors; Harris Brisbane Dick and William E. Dodge Funds; and W. Bruce and Delaney H. Lundberg Gift, 2013",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/623065,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.173.41,false,true,623066,Photographs,Photograph,Figure 73: Head of Niobe,,,,,,Artist|Artist,,Guillaume-Benjamin-Amand Duchenne de Boulogne|Adrien Tournachon,"French, 1806–1875|French, 1825–1903",,"Duchenne de Boulogne, Guillaume-Benjamin-Amand|Tournachon, Adrien",French|French,1806 |1825,1875 |1903,"1854–56, printed 1862",1854,1856,Albumen silver print from glass negative,Image (Oval): 28.6 × 20.4 cm (11 1/4 × 8 1/16 in.) Sheet: 29.2 × 21.5 cm (11 1/2 × 8 7/16 in.) Mount: 40.1 × 28.3 cm (15 13/16 × 11 1/8 in.),"Purchase, The Buddy Taub Foundation Gift, Dennis A. Roach and Jill Roach, Directors; Harris Brisbane Dick and William E. Dodge Funds; and W. Bruce and Delaney H. Lundberg Gift, 2013",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/623066,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.173.42,false,true,623067,Photographs,Photograph,Figure 75: Nun saying her prayers,,,,,,Artist|Artist,,Guillaume-Benjamin-Amand Duchenne de Boulogne|Adrien Tournachon,"French, 1806–1875|French, 1825–1903",,"Duchenne de Boulogne, Guillaume-Benjamin-Amand|Tournachon, Adrien",French|French,1806 |1825,1875 |1903,"1854–56, printed 1862",1854,1856,Albumen silver print from glass negative,Image (Oval): 28.8 × 22 cm (11 5/16 × 8 11/16 in.) Sheet: 30.5 × 23.4 cm (12 × 9 3/16 in.) Mount: 40.2 × 28.6 cm (15 13/16 × 11 1/4 in.),"Purchase, The Buddy Taub Foundation Gift, Dennis A. Roach and Jill Roach, Directors; Harris Brisbane Dick and William E. Dodge Funds; and W. Bruce and Delaney H. Lundberg Gift, 2013",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/623067,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.173.43,false,true,623068,Photographs,Photograph,Figure 78: Scene of coquetry,,,,,,Artist|Artist,,Guillaume-Benjamin-Amand Duchenne de Boulogne|Adrien Tournachon,"French, 1806–1875|French, 1825–1903",,"Duchenne de Boulogne, Guillaume-Benjamin-Amand|Tournachon, Adrien",French|French,1806 |1825,1875 |1903,"1854–56, printed 1862",1854,1856,Albumen silver print from glass negative,Image (Oval): 28.5 × 20.3 cm (11 1/4 × 8 in.) Sheet: 30.1 × 21.9 cm (11 7/8 × 8 5/8 in.) Mount: 40.2 × 28.6 cm (15 13/16 × 11 1/4 in.),"Purchase, The Buddy Taub Foundation Gift, Dennis A. Roach and Jill Roach, Directors; Harris Brisbane Dick and William E. Dodge Funds; and W. Bruce and Delaney H. Lundberg Gift, 2013",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/623068,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.173.44,false,true,623069,Photographs,Photograph,"Figure 81: Lady Macbeth, moderate expression of cruelty",,,,,,Artist|Artist,,Guillaume-Benjamin-Amand Duchenne de Boulogne|Adrien Tournachon,"French, 1806–1875|French, 1825–1903",,"Duchenne de Boulogne, Guillaume-Benjamin-Amand|Tournachon, Adrien",French|French,1806 |1825,1875 |1903,"1854–56, printed 1862",1854,1856,Albumen silver print from glass negative,Image: 28.4 × 22.7 cm (11 3/16 × 8 15/16 in.) Sheet: 29.7 × 23.6 cm (11 11/16 × 9 5/16 in.) Mount: 40.2 × 28.6 cm (15 13/16 × 11 1/4 in.),"Purchase, The Buddy Taub Foundation Gift, Dennis A. Roach and Jill Roach, Directors; Harris Brisbane Dick and William E. Dodge Funds; and W. Bruce and Delaney H. Lundberg Gift, 2013",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/623069,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.173.45,false,true,623070,Photographs,Photograph,"Figure 82: Lady Macbeth, strong expression of cruelty",,,,,,Artist|Artist,,Guillaume-Benjamin-Amand Duchenne de Boulogne|Adrien Tournachon,"French, 1806–1875|French, 1825–1903",,"Duchenne de Boulogne, Guillaume-Benjamin-Amand|Tournachon, Adrien",French|French,1806 |1825,1875 |1903,"1854–56, printed 1862",1854,1856,Albumen silver print from glass negative,Image (Oval): 28.4 × 20.3 cm (11 3/16 × 8 in.) Sheet: 29.2 × 21.9 cm (11 1/2 × 8 5/8 in.) Mount: 40.3 × 28.1 cm (15 7/8 × 11 1/16 in.),"Purchase, The Buddy Taub Foundation Gift, Dennis A. Roach and Jill Roach, Directors; Harris Brisbane Dick and William E. Dodge Funds; and W. Bruce and Delaney H. Lundberg Gift, 2013",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/623070,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.173.46,false,true,623071,Photographs,Photograph,"Figure 83: Lady Macbeth, ferocious cruelty",,,,,,Artist|Artist,,Guillaume-Benjamin-Amand Duchenne de Boulogne|Adrien Tournachon,"French, 1806–1875|French, 1825–1903",,"Duchenne de Boulogne, Guillaume-Benjamin-Amand|Tournachon, Adrien",French|French,1806 |1825,1875 |1903,"1854–56, printed 1862",1854,1856,Albumen silver print from glass negative,Image: 28.3 × 21.4 cm (11 1/8 × 8 7/16 in.) Sheet: 30 × 22.9 cm (11 13/16 × 9 in.) Mount: 40.2 × 28.4 cm (15 13/16 × 11 3/16 in.),"Purchase, The Buddy Taub Foundation Gift, Dennis A. Roach and Jill Roach, Directors; Harris Brisbane Dick and William E. Dodge Funds; and W. Bruce and Delaney H. Lundberg Gift, 2013",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/623071,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1994.417,true,true,267087,Photographs,Photograph,[Landscape with Cottage],,,,,,Artist|Artist,,Marie-Charles-Isidore Choiselat|Stanislas Ratel,"French, 1815–1858|French, 1824–1904",,"Choiselat, Marie-Charles-Isidore & Stanislas Ratel|Ratel, Stanislas",French|French,1815 |1824,1858 |1904,1844,1844,1844,Daguerreotype,16.4 x 21.7 cm (6 7/16 x 8 9/16 in.),"Louis V. Bell Fund, 1994",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267087,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.29,false,true,283103,Photographs,Photograph,[The Pavillon de Flore and the Tuileries Gardens],,,,,,Artist|Artist,,Marie-Charles-Isidore Choiselat|Stanislas Ratel,"French, 1815–1858|French, 1824–1904",,"Choiselat, Marie-Charles-Isidore & Stanislas Ratel|Ratel, Stanislas",French|French,1815 |1824,1858 |1904,1849,1849,1849,Daguerreotype,15.2 x 18.7 cm (6 x 7 3/8 in.),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/283103,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.185,false,true,286110,Photographs,Photograph,Défilé sur le Pont-Royal,,,,,,Artist|Artist,,Marie-Charles-Isidore Choiselat|Stanislas Ratel,"French, 1815–1858|French, 1824–1904",,"Choiselat, Marie-Charles-Isidore & Stanislas Ratel|Ratel, Stanislas",French|French,1815 |1824,1858 |1904,"May 1, 1844",1844,1844,Daguerreotype,Image: 6 1/16 × 4 7/16 in. (15.4 × 11.3 cm) Frame: 8 3/4 × 7 1/16 in. (22.3 × 18 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286110,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.954,false,true,629811,Photographs,Photograph,"[Pompeii, Pompey's Lane (or Street of Pompeii), Tomb Monument of Mamia]",,,,,,Artist|Artist,,Firmin-Eugène Le Dien|Gustave Le Gray,"French, 1817–1865|French, 1820–1884",,"Le Dien, Firmin-Eugène|Le Gray, Gustave",French|French,1817 |1820,1865 |1884,ca. 1853,1848,1858,Salted paper print from a waxed paper negative,Mount: 13 13/16 in. × 19 3/16 in. (35.1 × 48.8 cm) Image: 8 15/16 × 12 7/16 in. (22.7 × 31.6 cm),"Purchase, Mr. and Mrs. John A. Moran Gift, in memory of Louise Chisholm Moran, 2013",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/629811,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.955,false,true,629831,Photographs,Photograph,"Amalfi, Cathedral",,,,,,Artist|Artist,,Firmin-Eugène Le Dien|Gustave Le Gray,"French, 1817–1865|French, 1820–1884",,"Le Dien, Firmin-Eugène|Le Gray, Gustave",French|French,1817 |1820,1865 |1884,1853,1853,1853,Salted paper print from a waxed paper negative,Mount: 19 5/16 in. × 13 13/16 in. (49.1 × 35.1 cm) Image: 13 1/8 × 9 5/16 in. (33.3 × 23.7 cm),"Purchase, Alfred Stieglitz Society Gifts, 2013",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/629831,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -39.108,false,true,268645,Photographs,Photograph,Maison Élevée Rue St. Georges par M. Renaud,,,,,,Artist|Artist,From a daguerreotype plate by,Armand-Hippolyte-Louis Fizeau|Noël-Marie-Paymal Lerebours,"French, 1819–1896|French, 1807–1873",,"Fizeau, Armand-Hippolyte-Louis|Lerebours, Noël-Marie-Paymal",French|French,1819 |1807,1896 |1873,ca. 1841,1839,1843,Photogravure,,"Gift of the Museum of Modern Art, 1939",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268645,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -47.152,false,true,269123,Photographs,Photograph,Excursions Daguerriennes. Vues et monuments les plus remarquables du globe,,,,,,Artist|Artist,,Armand-Hippolyte-Louis Fizeau|Noël-Marie-Paymal Lerebours,"French, 1819–1896|French, 1807–1873",,"Fizeau, Armand-Hippolyte-Louis|Lerebours, Noël-Marie-Paymal",French|French,1819 |1807,1896 |1873,1840s,1840,1849,"Etchings, aquatints, lithographs, and photogravures after daguerreotypes",,"David Hunter McAlpin Fund, 1947",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/269123,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2012.140,false,true,302241,Photographs,Photograph,"Electro–Physiologie, Figure 64",,,,,,Artist|Artist,,Adrien Tournachon|Guillaume-Benjamin-Amand Duchenne de Boulogne,"French, 1825–1903|French, 1806–1875",,"Tournachon, Adrien|Duchenne de Boulogne, Guillaume-Benjamin-Amand",French|French,1825 |1806,1903 |1875,"1854–56, printed 1862",1854,1856,Albumen silver print from glass negative,Image: 29.8 x 22.3 cm (11 3/4 x 8 3/4 in.) Mount: 40.1 x 28.5 cm (15 13/16 x 11 1/4 in.),"Purchase, The Buddy Taub Foundation Gift, Dennis A. Roach and Jill Roach, Directors, 2012",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/302241,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.558,false,true,286696,Photographs,Photograph,Marine Terrace,,,,,,Artist|Artist,,Charles Victor Hugo|Auguste Vacquerie,"French, 1826–1871|French, 1819–1855",,"Hugo, Charles Victor|Vacquerie, Auguste",French|French,1826 |1819,1871 |1855,"October 9, 1855",1855,1855,Salted paper print from glass negative,Image: 6.4 x 9.5 cm (2 1/2 x 3 3/4 in.) Mount: 5 13/16 × 8 11/16 in. (14.8 × 22.1 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286696,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.819,false,true,286434,Photographs,Photograph,Victor Hugo,,,,,,Artist|Artist,,Charles Victor Hugo|Auguste Vacquerie,"French, 1826–1871|French, 1819–1855",,"Hugo, Charles Victor|Vacquerie, Auguste",French|French,1826 |1819,1871 |1855,1852,1852,1852,Salted paper print from paper negative,Image: 3 15/16 in. × 3 in. (10 × 7.6 cm),"Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/286434,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1987.1054,false,true,265094,Photographs,Photograph,Chronophotograph,,,,,,Artist|Artist,,Etienne-Jules Marey|Charles Fremont,"French, 1830–1904|French, 1855–1930",,"Marey, Etienne-Jules|Fremont, Charles",French|French,1830 |1855,1904 |1930,1894,1894,1894,Gelatin silver print from glass negative,16.3 x 20.2 cm (6 7/16 x 7 15/16 in.),"Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel and Rogers Fund, 1987",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/265094,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1993.219,false,true,266895,Photographs,Photograph,Nebuleuse de la Lyre,,,,,,Artist|Artist,,Paul Henry|Prosper Henry,"French, 1848–1905|French, 1849–1903",,"Henry, Paul|Henry, Prosper",French|French,1848 |1849,1905 |1903,ca. 1885,1883,1887,Albumen silver print from glass negative,Image: 22.8 x 16.4 cm. (9 x 6 7/16 in.),"Gift of Arnold H. Crane, by exchange, and Purchase, The Horace W. Goldsmith Foundation Gift through Joyce and Robert Menschel, 1993",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/266895,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.124,false,true,283255,Photographs,Photograph,"A Section of the Constellation Cygnus (August 13, 1885)",,,,,,Artist|Artist,,Paul Henry|Prosper Henry,"French, 1848–1905|French, 1849–1903",,"Henry, Paul|Henry, Prosper",French|French,1848 |1849,1905 |1903,1885,1885,1885,Albumen silver print from glass negative,25.8 x 21.2cm (10 3/16 x 8 3/8in.) Mount: 31.4 × 23.8 cm (12 3/8 × 9 3/8 in.),"Gilman Collection, Purchase, Robert Rosenkranz Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/283255,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2012.59,false,true,299470,Photographs,Photograph,[Male Musculature Study],,,,,,Artist|Artist,,Albert Londe|Paul Marie Louis Pierre Richer,"French, 1858–1917|French, 1849–1933",,"Londe, Albert|Richer, Paul Marie Louis Pierre",French|French,1858 |1849,1917 |1933,ca. 1890,1885,1895,Albumen silver print,Image: 14.9 x 9.6 cm (5 7/8 x 3 3/4 in.) Mount: 14.9 x 9.9 cm (5 7/8 x 3 7/8 in.),"Gift of Charles Isaacs and Carol Nigro, 2012",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/299470,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.34,false,true,283107,Photographs,Photograph,The Ramparts of Carcassonne,,,,,,Artist|Artist,,Gustave Le Gray|Auguste Mestral,"French, 1820–1884|French, Rans 1812–1884 Rans",,"Le Gray, Gustave|Mestral, Auguste",French|French,1820 |1812,1884 |1884,1851,1851,1851,Salted paper print from waxed paper negative,Image: 23.5 x 33.2 cm (9 1/4 x 13 1/16 in.) Mount: 31.2 x 45.2 cm (12 5/16 x 17 13/16 in.),"Gilman Collection, Purchase, Harriette and Noel Levine Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/283107,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.799 (1-30),false,true,285904,Photographs,Album,Roma,,,,,,Artist|Publisher,,Eugène Constant|Edouard Mauche et Cie,"French, active Italy, 1848–55",,"Constant, Eugène|Edouard Mauche et Cie",French|French,1848,1855,1848–52,1848,1852,Salted paper print from glass negative,Prints approx. 8 3/4 x 11 1/4,"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Albums,,http://www.metmuseum.org/art/collection/search/285904,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.1174.1–.16,false,true,285641,Photographs,Album,The Evacuation of Fort Sumter,,,,,,Publisher|Publisher,,Osborn's Gallery|Edward Anthony,"American, active Charleston, South Carolina, 1850s–1860s|American, 1818–1888",,"Osborn's Gallery|Anthony, Edward",American|American,1850 |1818,1869 |1888,April 1861,1861,1861,Albumen silver prints from glass negatives,Album: 12.6 × 9.4 × 2.5 cm (4 15/16 × 3 11/16 × 1 in.),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Albums,,http://www.metmuseum.org/art/collection/search/285641,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1999.89,false,true,282753,Photographs,Album; Photographs; Locket; Pendant,[Miniature Wedding Album of General Tom Thumb and Lavinia Warren],,,,,,Photography Studio|Person in Photograph|Person in Photograph,,Mathew B. Brady|General Tom Thumb|Lavinia Warren,"American, born Ireland, 1823?–1896 New York|American, 1838–1883|American, 1841–1919",,"Brady, Mathew B.|Thumb, Tom|Warren, Lavinia",American|American,1823 |1838 |1841,1896 |1883 |1919,ca. 1863,1862,1864,"Albumen silver prints, brass","Overall: 1 1/16 × 13/16 × 3/8 in. (2.7 × 2 × 1 cm) Images: 7/8 × 13/16 in. (2.3 × 2 cm), each","Joyce F. Menschel Photography Library Fund, 1999",,,,,,,,,,,,Albums|Jewelry,,http://www.metmuseum.org/art/collection/search/282753,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2011.479,false,true,299298,Photographs,Postcard,"Animas Canyon, Colorado",,,,,,Publisher|Artist,,Detroit Publishing Company|William Henry Jackson,"American|American, 1843–1942",,"Detroit Publishing Company|Jackson, William Henry",American|American,1843,1942,1906,1906,1906,Chromolithograph,Image: 8.1 x 12.3 cm (3 3/16 x 4 13/16 in.) 8.9 x 14 cm (3 1/2 x 5 1/2 in.) Frame: 27.9 x 35.6 cm (11 x 14 in.),"Funds from various donors, 2011",,,,,,,,,,,,Prints,,http://www.metmuseum.org/art/collection/search/299298,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2011.476,false,true,299309,Photographs,Photograph,"Slave Pen, Alexandria, Virginia",,,,,,Photography Studio|Publisher,,Brady & Co.|E. & H. T. Anthony,"American, active 1840s–1880s|American",,Brady & Co.|E. & H. T. Anthony,American|American,1840,1889,1862,1862,1862,Albumen silver print from glass negative,Image: 8 x 16 cm (3 1/8 x 6 5/16 in.) Mount: 8.4 x 17.5 cm (3 5/16 x 6 7/8 in.),"The Horace W. Goldsmith Foundation Fund, through Joyce and Robert Menschel, 2011",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/299309,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1982.1065.14,false,true,263363,Photographs,Photograph,Nude,,,,,,Printer|Artist,,Morris & Bendien|Charles W. Gilhousen,"American, New York|American, 1867–1929",,"Morris & Bendien|Gilhousen, Charles W.",American|American,1867,1929,1917,1917,1917,Gelatin silver print,Image: 9 3/16 × 6 11/16 in. (23.4 × 17 cm) Mount: 9 9/16 in. × 7 5/16 in. (24.3 × 18.5 cm),"Gift of Rita McNamara Pleet, 1982",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/263363,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.457.916,false,true,291117,Photographs,Stereograph,"[Stereographic View of Statue of Simon Bolivar by R. de la Cora, Central Park, New York]",,,,,,Publisher|Publisher,,Edward Anthony|Henry T. Anthony,"American, 1818–1888|American, 1814–1884",,"Anthony, Edward|Anthony, Henry T.",American|American,1818 |1814,1888 |1884,1884–98,1884,1898,Gelatin silver print from glass negative,"Image: 8.3 x 14.5 cm (3 1/4 x 5 11/16 in.), overall Mount: 8.8 x 17.7 cm (3 7/16 x 6 15/16 in.)","Herbert Mitchell Collection, 2007",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/291117,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.100,false,true,283210,Photographs,Photograph,Sky Chief (Tirawahut Resaru),,,,,,Artist|Printer,Attributed to,Edric L. Eaton|William Henry Jackson,"American, 1836–1890|American, 1843–1942",,"Eaton, Edric L.|Jackson, William Henry",American|American,1836 |1843,1890 |1942,ca. 1867,1865,1869,Albumen silver print from glass negative,Image: 18.6 × 13.2 cm (7 5/16 × 5 3/16 in.) Mount: 35.4 × 27.8 cm (13 15/16 × 10 15/16 in.),"Gilman Collection, Purchase, Sam Salz Foundation Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/283210,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2012.13,false,true,299482,Photographs,Medal; Button,[Presidential Campaign Medal with Portraits of Abraham Lincoln and Andrew Johnson],,,,,,Maker|Photography Studio|Artist,After,Unknown|Brady & Co.|Thomas Le Mere,"American, active 1840s–1880s|American, active 1860s",,"Unknown|Brady & Co.|Le Mere, Thomas",American|American,1840 |1860,1889 |1869,1864,1864,1864,Tintype,"Image: 1.6 cm (5/8 in.), diameter Overall: 2.5 cm (1 in.), diameter","Purchase, The Overbrook Foundation Gift, 2012",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/299482,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.1116,false,true,285876,Photographs,Photograph,Abraham Lincoln,,,,,,Artist|Photography Studio,,Anthony Berger|Brady & Co.,"American, active 1860s|American, active 1840s–1880s",,"Berger, Anthony|Brady & Co.",American|American,1860 |1840,1869 |1889,"February 9, 1864",1864,1864,Albumen silver print from glass negative,Image: 41 × 24.2 cm (16 1/8 × 9 1/2 in.) Mount: 50.8 × 34.4 cm (20 × 13 9/16 in.),"Gilman Collection, Purchase, The Horace W. Goldsmith Foundation Gift, through Joyce and Robert Menschel, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/285876,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1982.1182.2017–.2034,false,true,288317,Photographs,Stereographs,[Group of 18 Stereograph Views of the 1884/1885 New Orleans Centennial International Exhibition],,,,,,Publisher|Artist|Artist,,Centennial Photographic Company|Unknown|Edward Livingston Wilson,"American, founded 1876|American|American, 1838–1903",,"Centennial Photographic Company|Unknown|Wilson, Edward Livingston",American|American,1876 |1838,1903,1850s–1910s,1850,1919,Albumen silver prints,Mounts: 10.8 x 17.8 cm (4 1/4 x 7 in.),"Gift of Weston J. Naef, in memory of Kathleen W. Naef and Weston J. Naef Sr., 1982",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/288317,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.182,false,true,267971,Photographs,Photograph,Yorktown Landing,,,,,,Artist|Former Attribution,Formerly attributed to,James F. Gibson|Mathew B. Brady,"American, born 1828|American, born Ireland, 1823?–1896 New York",,"Gibson, James F.|Brady, Mathew B.",American|American,1828 |1823,1928 |1896,1862,1862,1862,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267971,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.50,false,true,268242,Photographs,Photograph,Water Battery at Yorktown,,,,,,Former Attribution|Artist,Formerly attributed to,Mathew B. Brady|James F. Gibson,"American, born Ireland, 1823?–1896 New York|American, born 1828",,"Brady, Mathew B.|Gibson, James F.",American|American,1823 |1828,1896 |1928,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268242,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.252,false,true,268048,Photographs,Photograph,"Water Battery, Yorktown",,,,,,Former Attribution|Artist,Formerly attributed to,Mathew B. Brady|James F. Gibson,"American, born Ireland, 1823?–1896 New York|American, born 1828",,"Brady, Mathew B.|Gibson, James F.",American|American,1823 |1828,1896 |1928,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268048,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.418,false,true,268232,Photographs,Photograph,"Confederate Fortifications, Yorktown, Virginia",,,,,,Former Attribution|Artist,Formerly attributed to,Mathew B. Brady|James F. Gibson,"American, born Ireland, 1823?–1896 New York|American, born 1828",,"Brady, Mathew B.|Gibson, James F.",American|American,1823 |1828,1896 |1928,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268232,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.52,false,true,268244,Photographs,Photograph,Charleston,,,,,,Artist|Former Attribution,Mathew B. Brady,George N. Barnard|Mathew B. Brady,"American, 1819–1902|American, born Ireland, 1823?–1896 New York",,"Barnard, George N.|Brady, Mathew B.",American|American,1819 |1823,1902 |1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268244,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.40,false,true,268212,Photographs,Photograph,"Laborers at Quartermaster's Wharf, Alexandria, Virginia",,,,,,Former Attribution|Artist,Formerly attributed to|Attributed to,Mathew B. Brady|Andrew Joseph Russell,"American, born Ireland, 1823?–1896 New York|American, 1830–1902",,"Brady, Mathew B.|Russell, Andrew Joseph",American|American,1823 |1830,1896 |1902,1863–65,1863,1865,Albumen silver print from glass negative,Image: 13.2 × 20.2 cm (5 3/16 × 7 15/16 in.),"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268212,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.190,false,true,267980,Photographs,Photograph,Cannon,,,,,,Former Attribution|Artist,Formerly attributed to,Mathew B. Brady|Andrew Joseph Russell,"American, born Ireland, 1823?–1896 New York|American, 1830–1902",,"Brady, Mathew B.|Russell, Andrew Joseph",American|American,1823 |1830,1896 |1902,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267980,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.659.560,false,true,291992,Photographs,Carte-de-visite,[Jervis McEntee],,,,,,Person in Photograph|Artist,,Jervis McEntee|Austin Augustus Turner,"American, Rondout, New York 1828–1891 Rondout, New York|American, ca. 1813–1866",,"McEntee, Jervis|Turner, Austin Augustus",American|American,1828 |1810,1891 |1866,1860s,1860,1869,Albumen silver print from glass negative,Image: 9.4 x 5.1 cm (3 11/16 x 2 in.) Mount: 10.2 x 6.3 cm (4 x 2 1/2 in.),"The Albert Ten Eyck Gardner Collection, Gift of the Centennial Committee, 1970",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/291992,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.281,false,true,268080,Photographs,Photograph,"Confederate Earthworks, Centreville, Virginia",,,,,,Artist|Artist|Former Attribution,Formerly attributed to,George N. Barnard|James F. Gibson|Mathew B. Brady,"American, 1819–1902|American, born 1828|American, born Ireland, 1823?–1896 New York",,"Barnard, George N.|Gibson, James F.|Brady, Mathew B.",American|American,1819 |1828 |1823,1902 |1928 |1896,1862,1862,1862,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268080,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.68,false,true,268261,Photographs,Photograph,[A Union Station on the James River Established for Extracting Gunpowder from Confederate Torpedoes],,,,,,Former Attribution|Artist|Former Attribution,Attributed to|Formerly attributed to,Andrew Joseph Russell|Egbert Guy Fowx|Mathew B. Brady,"American, 1830–1902|American, born 1821|American, born Ireland, 1823?–1896 New York",,"Russell, Andrew Joseph|Fowx, Egbert Guy|Brady, Mathew B.",American|American,1830 |1821 |1823,1902 |1896,1864,1864,1864,Albumen silver print from glass negative,12.9 x 19.6 cm (5 1/16 x 7 11/16 in.),"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268261,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.69,false,true,268262,Photographs,Photograph,[A Union Station on the James River Established for Extracting Gunpowder from Confederate Torpedoes],,,,,,Former Attribution|Artist|Former Attribution,Attributed to|Formerly attributed to,Andrew Joseph Russell|Egbert Guy Fowx|Mathew B. Brady,"American, 1830–1902|American, born 1821|American, born Ireland, 1823?–1896 New York",,"Russell, Andrew Joseph|Fowx, Egbert Guy|Brady, Mathew B.",American|American,1830 |1821 |1823,1902 |1896,1864,1864,1864,Albumen silver print from glass negative,Image: 13.3 x 20.6 cm (5 1/4 x 8 1/8 in.),"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268262,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.172,false,true,267960,Photographs,Photograph,Pontoon Bridge,,,,,,Former Attribution|Artist|Former Attribution,Formerly attributed to,Andrew Joseph Russell|Egbert Guy Fowx|Mathew B. Brady,"American, 1830–1902|American, born 1821|American, born Ireland, 1823?–1896 New York",,"Russell, Andrew Joseph|Fowx, Egbert Guy|Brady, Mathew B.",American|American,1830 |1821 |1823,1902 |1896,1861–65,1861,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267960,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.253,false,true,268049,Photographs,Photograph,Dutch Gap Canal,,,,,,Artist|Artist|Former Attribution,Formerly attributed to,Andrew Joseph Russell|Egbert Guy Fowx|Mathew B. Brady,"American, 1830–1902|American, born 1821|American, born Ireland, 1823?–1896 New York",,"Russell, Andrew Joseph|Fowx, Egbert Guy|Brady, Mathew B.",American|American,1830 |1821 |1823,1902 |1896,1865,1865,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268049,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.254,false,true,268050,Photographs,Photograph,Dutch Gap Canal,,,,,,Artist|Artist|Former Attribution,Formerly attributed to,Andrew Joseph Russell|Egbert Guy Fowx|Mathew B. Brady,"American, 1830–1902|American, born 1821|American, born Ireland, 1823?–1896 New York",,"Russell, Andrew Joseph|Fowx, Egbert Guy|Brady, Mathew B.",American|American,1830 |1821 |1823,1902 |1896,1865,1865,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268050,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.347,false,true,268153,Photographs,Photograph,"Butler's Lookout Tower, Opposite Dutch Gap",,,,,,Artist|Artist|Former Attribution,Mathew B. Brady,Andrew Joseph Russell|Egbert Guy Fowx|Mathew B. Brady,"American, 1830–1902|American, born 1821|American, born Ireland, 1823?–1896 New York",,"Russell, Andrew Joseph|Fowx, Egbert Guy|Brady, Mathew B.",American|American,1830 |1821 |1823,1902 |1896,1865,1865,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268153,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.32,false,true,268123,Photographs,Photograph,Dutch Gap Canal,,,,,,Artist|Artist|Former Attribution,Formerly attributed to,Egbert Guy Fowx|Andrew Joseph Russell|Mathew B. Brady,"American, born 1821|American, 1830–1902|American, born Ireland, 1823?–1896 New York",,"Fowx, Egbert Guy|Russell, Andrew Joseph|Brady, Mathew B.",American|American,1821 |1830 |1823,1902 |1896,1865,1865,1865,Albumen silver print from glass negative,16.4 x 22.5 cm (6 7/16 x 8 7/8 in. ),"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268123,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.74,false,true,268268,Photographs,Photograph,"Dutch Gap Canal, James River",,,,,,Artist|Artist|Former Attribution,Formerly attributed to,Egbert Guy Fowx|Andrew Joseph Russell|Mathew B. Brady,"American, born 1821|American, 1830–1902|American, born Ireland, 1823?–1896 New York",,"Fowx, Egbert Guy|Russell, Andrew Joseph|Brady, Mathew B.",American|American,1821 |1830 |1823,1902 |1896,1864,1864,1864,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268268,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.234,false,true,268028,Photographs,Photograph,Dutch Gap Canal,,,,,,Artist|Artist|Former Attribution,Formerly attributed to,Egbert Guy Fowx|Andrew Joseph Russell|Mathew B. Brady,"American, born 1821|American, 1830–1902|American, born Ireland, 1823?–1896 New York",,"Fowx, Egbert Guy|Russell, Andrew Joseph|Brady, Mathew B.",American|American,1821 |1830 |1823,1902 |1896,1865,1865,1865,Albumen silver print from glass negative,,"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268028,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.65.174,false,true,267962,Photographs,Photograph,"Procession of Troops and Civilians on Way to Dedication of Soldiers' National Cemetery, Gettysburg, Pennsylvania",,,,,,Artist|Artist|Former Attribution,Formerly attributed to,Isaac G. Tyson|Charles J. Tyson|Mathew B. Brady,"American, 1833–1913|American, 1838–1906|American, born Ireland, 1823?–1896 New York",and,"Tyson, Isaac G.|Tyson, Charles J.|Brady, Mathew B.",American|American,1833 |1838 |1823,1913 |1906 |1896,"November 19, 1863",1863,1863,Albumen silver print from glass negative,Image: 17.6 × 20.8 cm (6 15/16 × 8 3/16 in.),"Harris Brisbane Dick Fund, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267962,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.288,false,true,283251,Photographs,Photograph,[Portrait of F. Holland Day with Male Nude],,,,,,Person in Photograph|Artist,,F. Holland Day|Clarence H. White,"American, Norwood, Massachusetts 1864–1933 Norwood, Massachusetts|American, 1871–1925",,"Day, F. Holland|White, Clarence H.",American|American,1864 |1871,1933 |1925,1902,1902,1902,Platinum print,Image: 24.2 x 18.8 cm (9 1/2 x 7 3/8 in.),"Gilman Collection, Purchase, Harriette and Noel Levine Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/283251,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.14.8,false,true,268361,Photographs,Photograph,[Students from the Emerson School for Girls],,,,,,Artist|Photography Studio,,Albert Sands Southworth|Southworth and Hawes,"American, West Fairlee, Vermont 1811–1894 Charlestown, Massachusetts|American, active 1843–1863",,"Southworth, Albert Sands|Southworth and Hawes",American|American,1811 |1843,1894 |1863,ca. 1850,1848,1852,Daguerreotype,21.6 x 16.5 cm (8 1/2 x 6 1/2 in.),"Gift of I. N. Phelps Stokes, Edward S. Hawes, Alice Mary Hawes, and Marion Augusta Hawes, 1937",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/268361,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.79,false,true,283178,Photographs,Photograph,[Albert Sands Southworth],,,,,,Artist|Photography Studio,,Albert Sands Southworth|Southworth and Hawes,"American, West Fairlee, Vermont 1811–1894 Charlestown, Massachusetts|American, active 1843–1863",,"Southworth, Albert Sands|Southworth and Hawes",American|American,1811 |1843,1894 |1863,ca. 1845–50,1845,1850,Daguerreotype,"Overall: 28.3 × 23.4 cm (11 1/8 × 9 3/16 in.) Image: 11.8 × 8.5 cm (4 5/8 in., 8.5 cm); visible","Gilman Collection, Gift of The Howard Gilman Foundation, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/283178,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.89,false,true,283191,Photographs,Photograph,Abraham Lincoln,,,,,,Artist|Person in Photograph,,William Marsh|Abraham Lincoln,"American, active Springfield, Illinois, 1850s–1860s|American, Hardin County, Kentucky 1809–1865 Washington, D.C.",,"Marsh, William|Lincoln, Abraham",American|American,1850 |1809,1869 |1865,"May 20, 1860",1860,1860,Salted paper print from glass negative,Image: 19.9 x 14.5 cm (7 13/16 x 5 11/16 in.),"Gilman Collection, Purchase, Joyce F. Menschel Gift, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/283191,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.1174.1,false,true,302664,Photographs,Carte-de-visite,"The Evacuation of Fort Sumter, April 1861",,,,,,Publisher|Artist,,Osborn's Gallery|J. M. Osborn,"American, active Charleston, South Carolina, 1850s–1860s|American, active Charleston, South Carolina, 1850s–1860s",,"Osborn's Gallery|Osborn, J. M.",American|American,1850 |1850,1869 |1869,April 1861,1861,1861,Albumen silver print from glass negative,Image: 1 15/16 × 3 1/8 in. (5 × 7.9 cm) Mount: 3 3/8 in. × 4 3/4 in. (8.5 × 12 cm),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/302664,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.1174.2,false,true,302665,Photographs,Carte-de-visite,"The Evacuation of Fort Sumter, April 1861",,,,,,Publisher|Artist,,Osborn's Gallery|J. M. Osborn,"American, active Charleston, South Carolina, 1850s–1860s|American, active Charleston, South Carolina, 1850s–1860s",,"Osborn's Gallery|Osborn, J. M.",American|American,1850 |1850,1869 |1869,April 1861,1861,1861,Albumen silver print from glass negative,Image: 1 15/16 × 3 1/8 in. (5 × 7.9 cm) Mount: 3 3/8 in. × 4 3/4 in. (8.5 × 12 cm),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/302665,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.1174.15,false,true,302678,Photographs,Carte-de-visite,"Salient with North-west Casemates, Fort Sumter",,,,,,Publisher|Artist,,Osborn's Gallery|J. M. Osborn,"American, active Charleston, South Carolina, 1850s–1860s|American, active Charleston, South Carolina, 1850s–1860s",,"Osborn's Gallery|Osborn, J. M.",American|American,1850 |1850,1869 |1869,April 1861,1861,1861,Albumen silver print from glass negative,Image: 6 × 7.3 cm (2 3/8 × 2 7/8 in.) Mount: 8.5 × 12 cm (3 3/8 × 4 3/4 in.),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/302678,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.100.1174.16,false,true,302679,Photographs,Carte-de-visite,"The Evacuation of Fort Sumter, April 1861",,,,,,Publisher|Artist,,Osborn's Gallery|J. M. Osborn,"American, active Charleston, South Carolina, 1850s–1860s|American, active Charleston, South Carolina, 1850s–1860s",,"Osborn's Gallery|Osborn, J. M.",American|American,1850 |1850,1869 |1869,April 1861,1861,1861,Albumen silver print from glass negative,Image: 1 15/16 × 3 1/4 in. (5 × 8.2 cm) Mount: 3 3/8 in. × 4 3/4 in. (8.5 × 12 cm),"Gilman Collection, Museum Purchase, 2005",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/302679,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.400.12,false,true,291765,Photographs,Daguerreotype,[Young Woman Wearing Lace Collar and Brooch],,,,,,Photography Studio|Artist|Artist,Attributed to|Attributed to|Attributed to,Southworth and Hawes|Albert Sands Southworth|Josiah Johnson Hawes,"American, active 1843–1863|American, West Fairlee, Vermont 1811–1894 Charlestown, Massachusetts|American, Wayland, Massachusetts 1808–1901 Crawford Notch, New Hampshire",,"Southworth and Hawes|Southworth, Albert Sands|Hawes, Josiah Johnson",American|American,1843 |1811 |1808,1863 |1894 |1901,1850s,1850,1859,Daguerreotype,Image: 8.9 x 6.6 cm (3 1/2 x 2 5/8 in.) Plate: 10.6 x 8.1 cm (4 3/16 x 3 3/16 in.) Case: 1.9 x 11.7 x 9.4 cm (3/4 x 4 5/8 x 3 11/16 in.),"Bequest of Herbert Mitchell, 2008",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/291765,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.400.15,false,true,291768,Photographs,Daguerreotype,[Young Man in Three-piece Suit and Bow Tie],,,,,,Photography Studio|Artist|Artist,Attributed to|Attributed to,Southworth and Hawes|Albert Sands Southworth|Josiah Johnson Hawes,"American, active 1843–1863|American, West Fairlee, Vermont 1811–1894 Charlestown, Massachusetts|American, Wayland, Massachusetts 1808–1901 Crawford Notch, New Hampshire",,"Southworth and Hawes|Southworth, Albert Sands|Hawes, Josiah Johnson",American|American,1843 |1811 |1808,1863 |1894 |1901,1850s,1850,1859,Daguerreotype,Image: 9.9 x 7.5 cm (3 7/8 x 2 15/16 in.) Plate: 10.8 x 8.3 cm (4 1/4 x 3 1/4 in.) Case: 1.9 x 11.7 x 9.5 cm (3/4 x 4 5/8 x 3 3/4 in.),"Bequest of Herbert Mitchell, 2008",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/291768,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.1189.1–.101,false,true,264895,Photographs,Album,Ambroise Bernard Album,,,,,,Artist|Artist,Attributed to,Carleton E. Watkins|Carleton E. Watkins,"American, 1829–1916|American, 1829–1916",,"Watkins, Carleton E.|Watkins, Carleton E.",American|American,1829 |1829,1916 |1916,1870s,1870,1879,Albumen silver prints,43.5 x 37.5 cm (17 1/8 x 14 3/4 in.),"Gift of Carole and Irwin Lainoff, Ruth P. Lasser and Joseph R. Lasser, Mr. and Mrs. John T. Marvin, Martin E. and Joan Messinger, Richard L. Yett and Sheri and Paul Siegel, 1986",,,,,,,,,,,,Albums,,http://www.metmuseum.org/art/collection/search/264895,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1980.1056.3,false,true,262762,Photographs,Stereograph,"[Broadway, New York City, in the rain]",,,,,,Artist|Artist,,Henry T. Anthony|Edward Anthony,"American, 1814–1884|American, 1818–1888",,"Anthony, Henry T.|Anthony, Edward",American|American,1814 |1818,1884 |1888,ca. 1860s,1858,1862,Albumen silver print,,"Warner Communications Inc. Purchase Fund, 1980",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/262762,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1981.1229.19,false,true,263167,Photographs,Photograph,The Moon,,,,,,Artist|Artist,,John Adams Whipple|James Wallace Black,"American, 1822–1891|American, 1825–1896",,"Whipple, John Adams|Black, James Wallace",American|American,1822 |1825,1891 |1896,1857–60,1857,1860,Salted paper print from glass negative,21 x 15.7 cm (8 1/4 x 6 3/16 in. ),"Robert O. Dougan Collection, Gift of Warner Communications Inc., 1981",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/263167,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1981.1229.55,false,true,263204,Photographs,Photograph,The Moon,,,,,,Artist|Artist,,John Adams Whipple|James Wallace Black,"American, 1822–1891|American, 1825–1896",,"Whipple, John Adams|Black, James Wallace",American|American,1822 |1825,1891 |1896,1857–60,1857,1860,Salted paper print from glass negative,21 x 16.2 cm (8 1/4 x 6 3/8 in. ),"Robert O. Dougan Collection, Gift of Warner Communications Inc., 1981",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/263204,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.43.391,false,true,267805,Photographs,Photograph,Torso,,,,,,Artist|Artist,,Clarence H. White|Alfred Stieglitz,"American, 1871–1925|American, Hoboken, New Jersey 1864–1946 New York",,"White, Clarence H.|Stieglitz, Alfred",American|American,1871 |1864,1925 |1946,"1907, printed 1907–9",1907,1907,Platinum print,24.0 x 18.9 cm. (9 7/16 x 7 7/16 in.),"Alfred Stieglitz Collection, 1933",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/267805,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -17.190.386,false,true,464366,Medieval Art,Statuette,Mourner,French,,,,,Artist|Artist,,Étienne Bobillet|Paul Mosselman,"Franco-Netherlandish, active Bourges, 1453|Franco-Netherlandish, active Bourges, 1453",,Bobillet Étienne|Mosselman Paul,Franco-Netherlandish|Franco-Netherlandish,1453 |1453,1453 |1453,ca. 1453,1453,1453,Alabaster,Overall: 15 3/16 x 5 5/16 x 3 7/8 in. (38.6 x 13.5 x 9.8 cm),"Gift of J. Pierpont Morgan, 1917",,,,,,,,,,,,Sculpture-Stone,,http://www.metmuseum.org/art/collection/search/464366,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -27.18.1,false,true,466663,Medieval Art,sculpture,Griffin and Other Monster,Spanish,,,,,Artist,,Gil de Siloe,"Spanish, active 1475–1505",,"Siloe, Gil de",Spanish,1475,1505,15th century,1400,1499,Alabaster,Overall: 18 5/16 x 9 7/8 x 9 1/4 in. (46.5 x 25.1 x 23.5 cm),"Rogers Fund, 1927",,,,,,,,,,,,Sculpture,,http://www.metmuseum.org/art/collection/search/466663,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -30.4.227,false,true,479678,Medieval Art,Facsimile,"Facsimile of the Apse Painting in Tomb 25, Bagawat Necropolis, Kharga Oasis",Egyptian,,,,,Artist,,Charles K. Wilkinson,,,"Wilkinson, Charles K.",,1897,1986,A.D. 2nd century or later,100,199,Tempera on paper,Overall: 26 3/4 x 18 7/8 in. (67.9 x 47.9 cm) Framed: 27 7/8 x 20 7/16 x 7/8 in. (70.8 x 51.9 x 2.2 cm),"Rogers Fund, 1930",,,,,,,,,,,,Reproductions-Paintings,,http://www.metmuseum.org/art/collection/search/479678,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -30.4.229,false,true,479680,Medieval Art,Facsimile,"Facsimile of Painting in the Chapel of Peace, Bagawat Necropolis, Kharga Oasis",Egyptian,,,,,Artist,,Charles K. Wilkinson,,,"Wilkinson, Charles K.",,1897,1986,A.D. 2nd–6th century,100,599,Tempera on paper,Overall: 22 1/16 x 13 3/4 in. (56 x 34.9 cm) Framed: 23 1/4 x 15 1/8 x 7/8 in. (59.1 x 38.4 x 2.2 cm),"Rogers Fund, 1930",,,,,,,,,,,,Reproductions-Paintings,,http://www.metmuseum.org/art/collection/search/479680,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -48.154.1,false,true,468257,Medieval Art,Relief,Holy Family,South German,,,,,Artist,,Niclaus Weckmann,1481–1528,,WECKMANN NICLAUS,,1481,1528,ca. 1500,1500,1500,Limewood with traces of paint and gilding,Overall: 31 7/8 x 19 11/16 x 7 3/4 in. (81 x 50 x 19.7 cm),"Gift of Alastair Bradley Martin, 1948",,,,,,,,,,,,Sculpture-Wood,,http://www.metmuseum.org/art/collection/search/468257,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"06.163a, b",false,true,462887,Medieval Art,Statuette,Female Saint,South Netherlandish,,,,,Artist,,Jan van Steffesweert,"Maastricht, ca. 1460–1531",,"van Steffesweert, Jan",,1460,1531,ca. 1520,1520,1520,Oak with traces of polychromy,Overall: 32 1/4 x 11 1/16 x 9 7/8 in. (81.9 x 28.1 x 25.1 cm),"Rogers Fund, 1906",,,,,,,,,,,,Sculpture-Wood,,http://www.metmuseum.org/art/collection/search/462887,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"51.28a, b",false,true,468372,Medieval Art,Sculpture,"The Three Magi, from an Adoration Group",South German,,,,,Artist,Workshop of,Hans Thoman,"German, active Memmingen, ca. 1514–25",,"Thoman, Hans",,1514,1525,ca. 1515–20,1515,1520,"Wood, gesso, paint, gilding",Overall (Balthasar and Melchior): 23 1/2 x 12 1/4 x 6 1/2 in. (59.7 x 31.1 x 16.5 cm) Overall (Gaspar): 22 1/2 x 9 1/4 x 5 1/4 in. (57.2 x 23.5 x 13.3 cm) Base: 4 x 20 x 10 in. (10.2 x 50.8 x 25.4 cm),"Purchase, Joseph Pulitzer Bequest, 1951",,,,,,,,,,,,Sculpture-Wood,,http://www.metmuseum.org/art/collection/search/468372,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -31.134.3,false,true,467415,Medieval Art,Choir Book; Manuscript cutting,"Manuscript Illumination with the Visitation in an Initial D, from a Choir Book",French,,,,,Artist,,Spanish Forger,"French, active late 19th–early 20th century",,Spanish Forger,French,1870,1935,late 19th–early 20th century,1875,1925,"Tempera, ink, and gold on parchment",Overall: 6 7/8 x 4 1/2 in. (17.5 x 11.5 cm) Mat size: 19 3/16 x 14 3/16 in. (48.8 x 36.1 cm),"Bequest of Gwynne M. Andrews, 1930",,,,,,,,,,,,Manuscripts and Illuminations,,http://www.metmuseum.org/art/collection/search/467415,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -16.32.213,false,true,463816,Medieval Art,Relief,Meeting of Saints Joachim and Anne at the Golden Gate,North German,,,,,Artist,,Benedikt Dreyer,"German, active Lübeck, ca. 1500–1525",,Dreyer Benedikt,German,1510,1530,ca. 1515–20,1515,1520,Oak with polychromy and gilding,Overall: 23 x 19 1/4 x 4 7/8 in. (58.4 x 48.9 x 12.4 cm),"Gift of J. Pierpont Morgan, 1916",,,,,,,,,,,,Sculpture-Wood,,http://www.metmuseum.org/art/collection/search/463816,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"10.197a, b",false,true,463412,Medieval Art,Beaker and cover,Beaker and Cover,German,,,,,Artist,Probably,Friedrich Hillebrand,"German, 1580–1608",,"Hillebrand, Friedrich",German,1580,1608,19th century (16th century style),1800,1900,"Silver, partially gilt",Overall: 14 15/16 x 5 1/8 in. (37.9 x 13 cm),"Rogers Fund, 1910",,,,,,,,,,,,Metalwork-Silver,,http://www.metmuseum.org/art/collection/search/463412,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -07.65.1,false,true,463042,Medieval Art,Bell,The Bell of Saint Patrick Shrine,Irish,,,,,Maker,,Elkington & Co.,"British, Birmingham, 1829–1963",,Elkington & Co.,British,1829,1963,early 20th century (original dated 1091–1105),1091,1105,"Bronze, gold, silver, gems",Overall: 10 1/2 x 6 3/16 x 4 1/2 in. (26.6 x 15.7 x 11.4 cm),"Rogers Fund, 1906",,,,,,,,,,,,Reproductions-Metalwork,,http://www.metmuseum.org/art/collection/search/463042,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -08.233.4,false,true,463161,Medieval Art,Shrine,Shrine of Saint Patrick's Tooth,Irish,,,,,Maker,,Elkington & Co.,"British, Birmingham, 1829–1963",,Elkington & Co.,British,1829,1963,early 20th century (original dated 1376),1376,1376,"Bronze, gilt, gem stones",12 x 9 1/4 x 2 1/2 in. (30.5 x 23.5 x 6.4 cm),"Rogers Fund, 1908",,,,,,,,,,,,Reproductions-Metalwork,,http://www.metmuseum.org/art/collection/search/463161,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"11.125.15a, b",false,true,463505,Medieval Art,Salt cellar,Salt Cellar,British,,,,,Maker,,Elkington & Co.,"British, Birmingham, 1829–1963",,Elkington & Co.,British,1829,1963,early 20th century (original dated late 15th century),1450,1500,"Silver gilt, glass",Overall: 15 1/16 x 5 1/4 in. (38.2 x 13.4 cm) Lid: 5 9/16 x 4 1/4 in. (14.2 x 10.8 cm) Cellar: 9 1/2 x 5 1/4 in. (24.2 x 13.4 cm),"Dodge Fund, 1911",,,,,,,,,,,,Reproductions-Metalwork,,http://www.metmuseum.org/art/collection/search/463505,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2003.456,false,true,474389,Medieval Art,Statue,Virgin and Child,British,,,,,Artist,Attributed to,Alexander of Abingdon,"British, active 1291–1317",,Alexander,British,1250,1350,ca. 1275–1325,1275,1325,Caen Limestone,Overall: 59 1/4 x 19 3/8 x 11 3/4 in. (150.5 x 49.2 x 29.8 cm),"Purchase, Edward J. Gallagher Jr. Bequest, in memory of his father, Edward Joseph Gallagher, his mother, Ann Hay Gallagher, and his son, Edward Joseph Gallagher III; and Caroline Howard Hyman Gift, 2003",,,,,,,,,,,,Sculpture-Stone,,http://www.metmuseum.org/art/collection/search/474389,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -17.190.489,false,true,464453,Medieval Art,Altarpiece,Altarpiece,North Italian,,,,,Artist,,Baldassare degli Embriachi,"Italian, active 1390–1409",,"Embriachi, Baldassare degli",Italian,1390,1409,ca. 1390–1400,1390,1400,"Bone framed with intarsia and horn, traces of paint and gilding",without wooden base: 50 1/2 x 60 1/2 in. (128.3 x 153.7 cm),"Gift of J. Pierpont Morgan, 1917",,,,,,,,,,,,Ivories-Bone,,http://www.metmuseum.org/art/collection/search/464453,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -27.248.1,false,true,466677,Medieval Art,Book of Hours; Manuscript leaf,"Manuscript Leaf with Adoration of the Holy Name, from a Book of Hours",South Netherlandish,,,,,Artist,Influence of,Simon Bening,"Netherlandish, Ghent (?) 1483/84–1561 Bruges",,"Bening, Simon",Netherlandish,1483,1561,after 1530 (?),1530,1530,"Tempera, ink and shell gold on parchment",5 7/8 x 4 3/16 in. (15 x 10.6 cm) Mat: 12 × 10 in. (30.5 × 25.4 cm),"Gift of Alice M. Dike, 1927",,,,,,,,,,,,Manuscripts and Illuminations,,http://www.metmuseum.org/art/collection/search/466677,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -55.120.2,false,true,471730,The Cloisters,Painting,Saint Michael,North Spanish,,,,,Artist,,Master of Belmonte,"Spanish, Aragon, active ca. 1460–90",,Master of Belmonte,Spanish,1455,1490,1450–1500,1450,1500,Tempera and oil on wood,Overall: 85 1/2 x 47 in. (217.2 x 119.4 cm),"The Cloisters Collection, 1955",,,,,,,,,,,,Paintings-Panels,,http://www.metmuseum.org/art/collection/search/471730,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2000.135,false,true,472854,The Cloisters,Panel,Stained-Glass Panel with a Coat of Arms and a Female Supporter,Swiss,,,,,Artist,Workshop of,Lukas Zeiner,"Swiss, active ca. 1480–1510",,Zeiner Lukas,Swiss,1480,1510,1500–1505,1500,1505,"Pot metal and colorless glass, vitreous paint, and silver stain, lead",14 7/8 x 19 7/8 in. (37.8 x 50.5 cm),"Purchase, Bequest of Jane Hayward, by exchange, 2000",,,,,,,,,,,,Glass-Stained,,http://www.metmuseum.org/art/collection/search/472854,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1988.304.3,false,true,469916,The Cloisters,Roundel,Roundel with Saint Jerome in his Study,South Netherlandish,,,,,Artist,Based on a design by,Pseudo-Ortkens,"South Netherlandish, active Antwerp and Brussels, ca. 1500–30",,Pseudo-Ortkens,South Netherlandish,1500,1530,ca. 1520,1520,1520,"Colorless glass, vitreous paint and silver stain",Overall: 9 in. (22.8 cm),"The Cloisters Collection, 1988",,,,,,,,,,,,Glass-Stained,,http://www.metmuseum.org/art/collection/search/469916,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -25.120.368,false,true,472330,The Cloisters,Statuette,Virgin,French,,,,,Artist,,Joan Avesta,"Spanish, active Catalonia and southwest France, 1355–1390",,"Avesta, Joan",Spanish,1355,1390,ca. 1370–90,1367,1393,"Alabaster, traces of gilt, paint",Overall: 25 9/16 x 9 7/8 x 8 5/8 in. (64.9 x 25.1 x 21.9 cm),"The Cloisters Collection, 1925",,,,,,,,,,,,Sculpture-Stone,,http://www.metmuseum.org/art/collection/search/472330,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2010.22.1,false,true,478972,The Cloisters,Panel,Gathering Manna,German,,,,,Artist|Artist,Workshop of|Based on a design by,Friedrich Brunner|Jan Pollack,"German|Polish (?), active Bavaria, ca. 1479–died 1519",,"Brunner Friedrich|Pollack, Jan",German|Polish (?),1479,1519,1497–99,1497,1499,"Pot-metal glass, vitreous paint, and silver stain",Overall: 19 3/4 x 20 7/8 in. (50.2 x 53 cm),"Purchase, The Cloisters Collection and Gift of The Hearst Foundation, by exchange, 2010",,,,,,,,,,,,Glass-Stained,,http://www.metmuseum.org/art/collection/search/478972,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2010.22.2,false,true,478997,The Cloisters,Panel,Storing up Manna,German,,,,,Artist|Artist,Workshop of|Based on a design by,Friedrich Brunner|Jan Pollack,"German|Polish (?), active Bavaria, ca. 1479–died 1519",,"Brunner Friedrich|Pollack, Jan",German|Polish (?),1479,1519,1497–99,1497,1499,"Pot-metal glass, vitreous paint, and silver stain",Overall: 19 3/4 x 20 7/8 in. (50.2 x 53 cm),"Purchase, The Cloisters Collection and Gift of The Hearst Foundation, by exchange, 2010",,,,,,,,,,,,Glass-Stained,,http://www.metmuseum.org/art/collection/search/478997,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -55.1,false,true,468466,The Cloisters,Shrine,Reliquary Shrine of Saint Barbara,European,,,,,Artist,Workshop of,Louis Marcy (Luigi Parmeggiani),"Italian, 1860–1945",(?),"Marcy, Louis",,1860,1945,ca. 1880–1900 (14th–15th century style),1880,1900,"Silver, Silver-gilt",Overall: 14 13/16 x 11 7/16 x 5 13/16 in. (37.6 x 29 x 14.8 cm),"The Cloisters Collection, 1955",,,,,,,,,,,,Metalwork-Silver,,http://www.metmuseum.org/art/collection/search/468466,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2008.278,true,true,478211,The Cloisters,Dish,Dish with Abraham and Melchizedek,South German,,,,,Artist,,Hans of Landshut,"German, Landshut, active late 15th century",,Hans of Landshut,German,1400,1550,1498,1498,1498,Free-blown glass with paint and metallic foils,Overall: 14 1/2 x 1 5/8 in. (36.9 x 4.2 cm),"The Cloisters Collection, 2008",,,,,,,,,,,,Glass-Miscellany,,http://www.metmuseum.org/art/collection/search/478211,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1985.146,false,true,469897,The Cloisters,Roundel,Roundel with Christ Taking Leave of His Mother,South German,,,,,Artist,After,Hans Schäufelein,"German, Nuremberg ca. 1480–ca. 1540 Nördlingen",,"Schäufelein, Hans",German,1480,1540,1507–15,1507,1515,"Colorless glass, vitreous paint and silver stain",Overall: 6 1/2 in. (16.5 cm),"Gift of Louis R. Slattery, in honor of Ashton Hawkins, 1985",,,,,,,,,,,,Glass-Stained,,http://www.metmuseum.org/art/collection/search/469897,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1979.185,false,true,469840,The Cloisters,Roundel,Roundel with Netting Quail,German,,,,,Artist,After a design by,Augustin Hirschvogel,"German, Nuremberg 1503–1553 Vienna",,"Hirschvolgel, Augustin",German,1503,1553,16th century,1500,1600,"Colorless glass, vitreous paint, silver stain and cold enamel",Overall: 9 1/2 in. (24.1 cm),"The Cloisters Collection, 1979",,,,,,,,,,,,Glass-Stained,,http://www.metmuseum.org/art/collection/search/469840,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -61.266,false,true,471908,The Cloisters,Reliquary bust,Reliquary Bust of Saint Juliana,Italian,,,,,Artist,Circle of,Giovanni di Bartolo,"Italian, active 1364–1404",,"di Bartolo, Giovanni",Italian,1364,1404,ca. 1376,1371,1381,"Copper, gilding, gesso, and tempera paint",Overall: 11 1/16 x 9 x 8 3/8 in. (28.1 x 22.9 x 21.3 cm),"The Cloisters Collection, 1961",,,,,,,,,,,,Metalwork-Copper,,http://www.metmuseum.org/art/collection/search/471908,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -32.24.40,false,true,471103,The Cloisters,Roundel,Roundel with the Hanging of Haman,North Netherlandish (?),,,,,Artist,Style of,Jan Swart van Groningen,"Netherlandish, Groningen ca. 1490/1500–1553 or later Antwerp",(?),"van Groningen, Jan Swart",Netherlandish,1490,1553,ca. 1530–40,1530,1540,"Colorless glass, silver stain, vitreous paint",Overall Diam.: 9 in. (22.9 cm),"The Cloisters Collection, 1932",,,,,,,,,,,,Glass-Stained,,http://www.metmuseum.org/art/collection/search/471103,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -GB1321 .M68 1766,true,true,682011,The Libraries,,Dialogo sobre hua nova obra no Rio Tejo ...,,,,,,Author,,Bento de Moura Portugal,"Portuguese, 1702–1776",,"Moura Portugal, Bento de",Portuguese,1702,1776,1776 (?),1771,1781,,"235 pages, [7] leaves of plates (some folded); Overall: 8 1/16 × 6 1/16 × 1 1/2 in. (20.4 × 15.4 × 3.8 cm)","Gift of Jayne Wrightsman, 2008",,,,,Portugal,,,,,,,||,,http://www.metmuseum.org/art/collection/search/682011,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -PN6231.P6 B63 1669,true,true,681546,The Libraries,,De' ragguagli di Parnaso: Centuria prima-seconda,,,,,,Printer|Author|Binder|Author,,Johannes Blaeu|Girolamo Briani|Simier|Traiano Boccalini,"Dutch|Italian, 1581–1646|Italian, 1556–1613",,"Blaeu, Johannes|Briani, Girolamo|Simier|Boccalini, Traiano",Dutch|Italian|Italian,1581 |0 |1556,1646 |0 |1613,1669,1669,1669,,"2 volumes: [16], 471, [55] pages; 415, [41], 139, [25] pages; Height: 6 1/8 in. (15.5 cm)","Gift of Jayne Wrightsman, 2008",,Amsterdam,,,Netherlands,,,,,,,|,,http://www.metmuseum.org/art/collection/search/681546,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -PQ4272.F5 A34 1697,true,true,681534,The Libraries,,"Contes et nouvelles de Bocace florentin : traduction libre, accommodée au gout de ce temps, & enrichie de figures en taille-douce gravées par Mr. Romain de Hooge",,,,,,Binder|Printer|Author|Printmaker,,Kleihnans|George Gallet|Giovanni Boccaccio|Romeyn de Hooghe,"Italian, Paris 1313–1375 Certaldo, Tuscany|Dutch, 1645–1708",,"Kleihnans|Gallet, George|Boccaccio, Giovanni|Hooghe, Romeyn de",Netherlandish|Italian|Dutch,0 |1313 |1645,0 |1375 |1708,1697,1697,1697,,"2 volumes: illustrations, etchings; Height: 6 5/16 in. (16 cm)","Gift of Jayne Wrightsman, 2008",,Amsterdam,,,Netherlands,,,,,,,|,,http://www.metmuseum.org/art/collection/search/681534,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -BX2010 .A2 1794,true,true,681389,The Libraries,,"Uffizio della Settimana Santa : colle rubriche volgari, argomenti de' Salmi, spiegazione delle cerimonie e misterj, con osservazioni, e riflessioni divote dell'abate Alessandro Mazzinelli",,,,,,Author|Artist|Author|Artist|Artist|Publisher,After|After|After|Presso,Alessandro Mazzinelli|Giovanni Battista Pacetti|Catholic Church|Giuseppe Passeri (Passari)|Annibale Carracci|Luigi Perego Salvioni,"Italian, active ca. 1700|Italian, 1693–1743|Italian, Rome 1654–1714 Rome|Italian, Bologna 1560–1609 Rome",,"Mazzinelli, Alessandro|Pacetti, Giovanni Battista|Catholic Church|Passeri, Giuseppe|Carracci, Annibale|Salvioni",Italian|Italian|Italian|Italian,1650 |1693 |0 |1654 |1560,1750 |1743 |0 |1714 |1609,1794,1794,1794,,3 volumes in 1 (560 pages) : illustrations ; Height: 8 11/16 in. (22 cm),"Gift of Jayne Wrightsman, 2008",,Rome,,,Italy,,,,,,,|,,http://www.metmuseum.org/art/collection/search/681389,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -240.7 T344 F,true,true,591832,The Libraries,,"Academie de l'espee; ou se domonstrent par reigles mathématiques sur le fondement d'un cercle mystérieux, la théorie et pratique des vrais . . .","Anvers, 1628",,,,,Publisher|Artist|Author,Printer:|Engraver:,probably the Elseviers of Leyden|probably A. Boslwert|Girard Thibault,"Flemish, died ca. 1629",,Elseviers|Boslwert A.|Thibault Girard,Flemish,1629,1629,1628,1628,1628,Illustrated book,2 pts. in 1vol.; H: 22 in. (56 cm),Purchased with income from the Jacob S. Rogers Fund,,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/591832,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -216.7 T71,true,true,591860,The Libraries,,The Treatyse of Fysshynge wyth an Angle from the book of Saint Albans,"New York, C. Scribner's Sons, 1903",,,,,Author|Author,Introduction by:,William Loring Andrews|Juliana Berners,"American, 1837–1920|British, b. 1388",,"Andrews, William Loring|Berners Juliana",American|British,1837 |1388,1920 |1388,1903,1903,1903,Illustrated book,H: 7 7/8 in. (20 cm),Presented by Mr. and Mrs. Edward Dean Adams,,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/591860,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -239 B63 Q,true,true,591831,The Libraries,,"Descriptio pbvlicae gratvlationis, spectacvlorvm et lvdorvm, in aventv sereniss: Principis Ernesti Archidvcis Avstriae Dvcis Vrgvndiae","Antwerp: Ex Officina Plantíníana, 1595",,,,,Author|Engraver,,Jean Boch|Peeter van der Borcht,"Belgian, 1545–1608|Netherlandish, Mechelen ca. 1545–1608 Antwerp",,"Boch Jean|Borcht, Peeter van der",Belgian|Netherlandish,1545 |1540,1608 |1608,1595,1595,1595,Illustrated book,174 pp.; 15 x 10 1/4 in. (38 x 26 cm),Presented by Mrs. S. P. Avery,,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/591831,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -920.84 M292,true,true,591865,The Libraries,,Morte d'Arthur,"London: J. M. Dent and Co., 1893",,,,,Author|Illustrator,,Sir Thomas Malory|Aubrey Vincent Beardsley,"British, 1415/18–1471|British, Brighton, Sussex 1872–1898 Menton",,"Malory Thomas Sir|Beardsley, Aubrey Vincent",British|British,1415 |1872,1471 |1898,1893,1893,1893,Illustrated book,12 pts.; H: 10 1/4 in. (26 cm),,,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/591865,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -903.6 R541 F,true,true,591848,The Libraries,,"The Holy Land, Syria, Idumea, Arabia, Egypt & Nubia","London : F. G. Moon, 1842–49",,,,,Author|Author|Author,Lithographed by|Historical descriptions by,Louis Haghe|Rev. George Croly|David Roberts,"Belgian, Tournai 1806–1885 Surrey|1780–1860|British, Stockbridge, Scotland 1796–1864 London",,"Haghe, Louis|Croly George Rev.|Roberts, David","Belgian|British, Scottish",1806 |1780 |1796,1885 |1860 |1864,1842–49,1842,1849,Illustrated books,3 vols.; H: 24 3/4 in. (63 cm),Presented by Charles Lanier,,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/591848,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -146.8 M821 Q,true,true,591863,The Libraries,,"Catalogue of the Collection of Jewels and Precious Works of Art, the Property of J. Pierpont Morgan","London: Chiswick Press, 1910 (deluxe ed.)",,,,,Author|Author,,J. Pierpont Morgan|George Charles Williamson,"British, 1858–1942",,"Morgan, J. Pierpont|Williamson, George Charles",American|British,1837 |1858,1913 |1942,1910,1910,1910,"Illustrated book, fine binding",H: 15 3/4 in. (39 cm),Presented by J. Pierpont Morgan,,,,,,,,,,,,|,,http://www.metmuseum.org/art/collection/search/591863,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -PQ2067.T28 D4 1770,true,true,681573,The Libraries,,"Les oeuvres morales de Mr. Diderot : contenant son traité De l'amitié, et celui Des passions",,,,,,Author|Author|Publisher,Erroneously attributed to|Aux depens de,Marie Geneviève Charlotte Darlus Thiroux d'Arconville|Denis Diderot|La Compagnie des libraires associés,"French, 1720–1805|French, 1713–1784",,"Thiroux d'Arconville, Marie Geneviève Charlotte Darlus|Diderot, Denis|Compagnie des libraires associés",French|French|French,1720 |1713,1805 |1784,1770,1770,1770,,2 volumes bound in 1; Height: 6 11/16 in. (17 cm),"Gift of Jayne Wrightsman, 2008",,,,,,,,,,,,|,,http://www.metmuseum.org/art/collection/search/681573,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -PN1489 .M37 1651,true,true,682013,The Libraries,,L'eschole de Salerne en vers burlesques & duo poemata macaronica: de bello huguenotico: et De gestis magnanimi & prudentissimi Baldi,,,,,,Author|Author|Binder|Author|Author,,Docteur (Louis) Martin|Remy Belleau|Robert Joly|Simon Moynet|Theophilo Folengo,"active 17th century|French, 1527?–1577|French, 1870?–1924|Italian, 1496–1544",,"Martin, Docteur (Louis)|Belleau, Remy|Joly, Robert |Moynet, Simon|Folengo, Theophilo",French|French|Italian,1600 |1527 |1870 |1496,1699 |1577 |1924 |1544,1651,1651,1651,,"139, [1] pages; Height: 5 1/8 in. (13 cm)","Gift of Jayne Wrightsman, 2008",,,,,,,,,,,,|,,http://www.metmuseum.org/art/collection/search/682013,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -BS75 1785b Q,true,true,681149,The Libraries,,Bibliorum Sacrorum Vulgatae versionis,,,,,,Printer|Author,,François Ambroise Didot|Josiah W. (Josiah Willard) Gibbs,"French, 1730–1804|American, 1790–1861",,"Didot, François Ambroise|Gibbs, Josiah W. (Josiah Willard)",French|American,1730 |1790,1804 |1861,1785,1785,1785,,"2 v. (ix [i.e. vii], [1], 596; [4], 548 p.) ; Height: 12 3/16 in. (31 cm)","Gift of Jayne Wrightsman, 2008",,Paris,,,France,,,,,,,|,,http://www.metmuseum.org/art/collection/search/681149,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -DA447.G7 H26 1876,true,true,681556,The Libraries,,Mémoires du Comte de Grammont : histoire amoureuse de la cour d'Angleterre sous Charles II / par Antoine Hamilton ; préface et notes par Benjamin Pifteau,,,,,,Author|Illustrator|Printer|Binder,,Count Anthony Hamilton|Jules Adolphe Chauvet|Leon Lamire|Marius Michel et fils,"Irish, ca. 1646–1720|French|French|French",,"Hamilton, Anthony, Count|Chauvet, Jules Adolphe|Lamire, Leon|Marius Michel et fils",Irish|French|French|French,1646,1720,1876,1876,1876,,20 cm,"Gift of Jayne Wrightsman, 2008",,Paris,,,France,,,,,,,|,,http://www.metmuseum.org/art/collection/search/681556,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -PA4375.M8 D3 1772,true,true,681388,The Libraries,,"Traité de Plutarque, sur la manière de discerner un flatteur d'avec un ami et Le banquet des sept sages: dialogue du même auteur revu & corrigé sur des manuscrits de la Bibliothèque du roi; avec une version françoise & des notes",,,,,,Author|Publisher|Artist,De,Plutarch|L'Imprimerie Royale|François Jean Gabriel de La Porte du Theil,"Greek, ca. A.D. 45–ca. 125|French, 1742–1815",,"Plutarch|L'Imprimerie Royale|La Porte du Theil, François Jean Gabriel de",Greek|French|French,0045 |1742,0125 |1815,1772,1772,1772,,"xii, 335, [1] pages ; Height: 8 1/4 in. (21 cm)","Gift of Jayne Wrightsman, 2008",,Paris,,,France,,,,,,,|,,http://www.metmuseum.org/art/collection/search/681388,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -SB472.32.E54 W48 1771,true,true,680835,The Libraries,,"L'art de former les jardins modernes, ou, L'art des jardins anglois : traduit de l'anglois : à quoi le traducteur a ajouté un discours préliminaire sur l'origine de l'art, des notes sur le texte, & une description détaillée des jardins de Stowe, accompagnée du plan",,,,,,Translator|Author|Publisher,Chez,François de Paule Latapie|Thomas Whately|Charles Antoine Jombert,"French, 1739–1823|British, died 1772|French, 1712–1784",,"Latapie, François de Paule|Whately, Thomas|Jombert, Charles Antoine",French|British|French,1739 |1672 |1712,1823 |1772 |1784,1771,1771,1771,,"lxiv, 406 p., 1 folded leaf of plates, plan, Height: 8 11/16 in. (22 cm)","Gift of Jayne Wrightsman, 2009",,Paris,,,France,,,,,,,|,,http://www.metmuseum.org/art/collection/search/680835,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -BV4823 .D48 1741,true,true,681547,The Libraries,,"De l'imitation de Jesus-Christ, traduction nouvelle ; ornée de figures en taille-douce",,,,,,Designer|Engraver|Engraver,Engravings drawn by,Antoine Humblot|J. B. Guélard|Claude Duflos,"French, died 1758|French, active ca. 1730|French, Coucy-le-Château 1665–1727 Paris",,"Humblot, Antoine|Guélard, J. B.|Duflos, Claude",French|French|French,1658 |1730 |1665,1758 |1730 |1727,1741,1741,1741,,"[8], xx, 608 p., [4] leaves of plates ; ill. (engravings) ; Height: 8 1/4 in. (21 cm)","Gift of Jayne Wrightsman, 2008",,Paris,,,France,,,,,,,|,,http://www.metmuseum.org/art/collection/search/681547,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -PQ1809 .A1 1795,true,true,681545,The Libraries,,Contes et nouvelles en vers par Jean de la Fontaine,,,,,,Author|Author|Draftsman|Printer,,Nicolas Boileau Despréaux|Jean de La Fontaine|Charles Dominique Joseph Eisen|Pierre Didot l'ainé,"French, 1636–1711|French, Château-Thierry 1621–1695 Paris|French, Valenciennes 1720–1778 Brussels|French, 1761–1853",,"Boileau Despréaux, Nicolas|La Fontaine, Jean de|Eisen, Charles Dominique Joseph|Didot, Pierre l'ainé",French|French|French|French,1636 |1621 |1720 |1761,1711 |1695 |1778 |1853,1795,1795,1795,,"2 volumes, portraits, Height: 5 1/2 in. (14 cm)","Gift of Jayne Wrightsman, 2009",,Paris,,,France,,,,,,,|,,http://www.metmuseum.org/art/collection/search/681545,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -NE2049.5.W38 W38 1710,true,true,682014,The Libraries,,Figures françoises et comiques nouvellement inventées par M. Watteau; Figures de modes dessinées et gravées à l'eau forte par Watteau; a terminées au buin par Thomassin le fils,,,,,,Artist|Engraver|Engraver|Engraver|Binder,,Antoine Watteau|Charles Nicolas Cochin I|Louis Desplaces|Henri Simon Thomassin|Hardy-Mennil,"French, Valenciennes 1684–1721 Nogent-sur-Marne|French, Paris 1688–1754 Paris|French, Paris 1682–1739 Paris|French, Paris 1687–1741",,"Watteau, Antoine|Cochin, Charles Nicolas, I|Desplaces, Louis|Thomassin, Henri Simon|Hardy-Mennil",French|French|French|French,1684 |1688 |1682 |1687,1721 |1754 |1739 |1687,1710?–?1720,1705,1725,,"[12], [8] leaves; Height: 10 1/4 in. (26 cm)","Gift of Jayne Wrightsman, 2008",,Paris,,,France,,,,,,,|,,http://www.metmuseum.org/art/collection/search/682014,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -PQ1795 .T5 1790,true,true,681128,The Libraries,,"Les aventures de Télémaque, fils d'Ulysse par M. de Fénélon ; avec figures en taille-douce, dessinées par MM. Cochin et Moreau le jeune",,,,,,Author|Engraver|Engraver|Binder,,François de Salignac de La Mothe-Fénelon|Charles Nicolas Cochin II|Jean Michel Moreau the Younger|Jean Claude Bozerian,"French, Château de Fénelon, Périgord 1651–1715 Cambrai|French, Paris 1715–1790 Paris|French, Paris 1741–1814 Paris|French, 1762–1840",,"Fénelon, François de Salignac de La Mothe-|Cochin, Charles Nicolas, II|Moreau, Jean Michel, the Younger|Bozerian, Jean Claude",French|French|French|French,1651 |1715 |1741 |1762,1715 |1790 |1814 |1840,1790,1790,1790,,2 volumes: illustrations (engravings); Height: 9 13/16 in. (25 cm),"Gift of Jayne Wrightsman, 2008",,Paris,,,France,,,,,,,|,,http://www.metmuseum.org/art/collection/search/681128,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -PA6525.H4 N3 1762,true,true,682012,The Libraries,,Epistole eroiche di P. Ovidio Nasone,,,,,,Author|Illustrator|Publisher|Author|Translator,,Ovid|Carlo Gregori|Durand|Giovan Stefano Conti|Remigio Nannini,"Roman, Sulmo 43 B.C.–A.D. 17 Tomis, Moesia|Italian, Lucca 1702–1759 Florence|French, 18th century|Italian, 1720–1791|Italian, 1521?–?1581",,"Ovid|Gregori, Carlo|Durand|Conti, Giovan Stefano|Nannini, Remigio",Roman|Italian|French|Italian|Italian,-0043 |1702 |1700 |1720 |1521,0017 |1759 |1800 |1791 |1581,1762,1762,1762,,"xii, 323 pages: portraits; Height: 8 1/4 in. (21 cm)","Gift of Jayne Wrightsman, 2008",,Paris,,,France,,,,,,,|,,http://www.metmuseum.org/art/collection/search/682012,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -PQ1821 1773,true,true,681563,The Libraries,,"Oeuvres de Moliere : avec des remarques grammaticales, des avertissemens et des observations sur chaque piéce, par M. Bret",,,,,,Author|Printer|Author|Author|Illustrator|Illustrator|Publisher,De l'imprimerie de|Par,M. (Antoine) Bret|Michel Lambert|Jean-Baptiste Poquelin Molière|Voltaire|Pierre Mignard |Jean Michel Moreau the Younger|La Compagnie des libraires associés,"French, Dijon 1717–1792 Paris|French, 1722?–1787|French, 1622–1673|1694–1778|French, Troyes 1612–1695 Paris|French, Paris 1741–1814 Paris",,"Bret, M. (Antoine)|Lambert, Michel|Molière, Jean-Baptiste Poquelin|Voltaire|Mignard, Pierre|Moreau, Jean Michel, the Younger|Compagnie des libraires associés",French|French|French|French|French|French,1717 |1722 |1622 |1694 |1612 |1741,1792 |1787 |1673 |1778 |1695 |1814,1773,1773,1773,,"6 volumes: illustrations, portraits, engravings; Height: 8 1/4 in. (21 cm)","Gift of Jayne Wrightsman, 2008",,Paris,,,France,,,,,,,|,,http://www.metmuseum.org/art/collection/search/681563,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -AE25 .E533 1765 Q,true,true,716648,The Libraries,,"Planches pour l'Encyclopédie, ou pour le Dictionaire raisonné des sciences, des arts libéraux, et des arts méchaniques, avec leur explication",,,,,,Publisher|Author,,Vincenzo Giuntini|Denis Diderot,"French, 1713–1784",,"Giuntini, Vincenzo|Diderot, Denis",,1713,1784,1765–1776,1765,1776,,"11 volumes, illustrations, height: 16 9/16 in. (42 cm)",Bequest of Marianne Khuner,,Lucca,,,Italy,,,,,,,,,http://www.metmuseum.org/art/collection/search/716648,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -NK7983.A1 W36 1752,true,true,705290,The Libraries,,博古圖錄,,,,,,Author,,Fu Wang,"Chinese, 1079–1126",,"Wang, Fu",,1079,1126,1752,1752,1752,,16 volumes : illustrations ; Height: 11 13/16 in. (30 cm),,,,,,China,,,,,,,,,http://www.metmuseum.org/art/collection/search/705290,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -230.51 N55,true,true,715005,The Libraries,,三禮圖,,,,,,Publisher|Author,,Tong zhi tang|Chongyi Nie,"Chinese, active 10th century",,"Tong zhi tang|Nie, Chongyi",,0900,0999,1676,1676,1676,,4 volumes : illustrations ; height: 11 7/16 in. (29 cm),"Purchased with income from the Jacob S. Rogers Fund, 1940",,Beijing,,,China,,,,,,,,,http://www.metmuseum.org/art/collection/search/715005,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -NK1483 .J7 1867 Q,true,true,739504,The Libraries,,Examples of Chinese ornament selected from objects in the South Kensington Museum and other collections,,,,,,Publisher|Author,,S. & T. Gilbert|Owen Jones,"London|British, London 1809–1874 London",,"Gilbert, S. & T.|Jones, Owen",,1800 |1809,1900 |1874,1867,1867,1867,,"3 pages, leaf, 5-15 pages : illustrations, color plate ; Height: 13 3/4 in. (35 cm)",,,London,,,England,,,,,,,,,http://www.metmuseum.org/art/collection/search/739504,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -GT2050 .U813 1884,true,true,726677,The Libraries,,"The sunshade, the glove, the muff",,,,,,Illustrator|Publisher|Author,,Paul Avril|J. C. Nimmo and Bain|Octave Uzanne,"French, born 1843|London|French, Auxerre 1851–1931",,"Avril, Paul|Nimmo, J. C. and Bain|Uzanne, Octave",,1843 |1875 |1851,1943 |1899 |1931,1884,1884,1884,,"viii, 138 pages : illustrations (some color) ; Height: 11 in. (28 cm)",,,London,,,England,,,,,,,,,http://www.metmuseum.org/art/collection/search/726677,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -AE25 .E532 1758 Q,true,true,716639,The Libraries,,"Encyclopédie, ou Dictionnaire raisonné des sciences, des arts et des métiers, par une société de gens de lettres",,,,,,Author|Publisher|Author|Author,,Ottaviano Diodati|Vincenzo Giuntini|Jean Le Rond d'Alembert|Denis Diderot,"Italian, 1716–1786|French, 1717–1783|French, 1713–1784",,"Diodati, Ottaviano|Giuntini, Vincenzo|Alembert, Jean Le Rond d'|Diderot, Denis",,1716 |1717 |1713,1786 |1783 |1784,1758–1771,1758,1771,,"17 volumes, illustrations, height: 16 9/16 in. (42 cm)",Bequest of Marianne Khuner,,Lucca,,,Italy,,,,,,,,,http://www.metmuseum.org/art/collection/search/716639,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -171.1 C17,true,true,699505,The Libraries,,"Dialogos de la pintvra : sv defensa, origen, essecia, definicion, modos y diferencias",,,,,,Author|Publisher|Engraver|Engraver,,Vicente Carducho|Francisco Martínez|Francisco López|Francisco Fernández,"Italian, 1570/78–1638|Spanish, active 1627–45|Spanish, ca. 1552–1629|Spanish, 1605–1646",,"Carducho, Vincente|Martínez, Francisco|López, Francisco|Fernández, Francisco",,1570 |1625 |1550 |1605,1638 |1645 |1630 |1646,1633,1633,1633,,"[18], 229, [24] pages, illustrations, height: 7 7/8 in. (20 cm)",,,Madrid,,,Spain,,,,,,,,,http://www.metmuseum.org/art/collection/search/699505,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -U820.A9 S34 1603 F,true,true,697348,The Libraries,,Der aller durchleuchtigisten und grosmächtigen Kayser ... Königen ... Herren vom Adel ... Bildtnussen und kurtz Beschreibungen ihrer so wol in Fridts- als Kriegszzeiten verrichten fürnembsten Thaten und Handlungen : deren Waffen und Rüstungen ... auss allen Landen der Welt ... in dem Schloss Ombrass ... zu ewiger Gedächtnuss auffbehalten werden ...,,,,,,Author|Translator|Publisher|Engraver|Illustrator,,Jacob Schrenck von Nozing|Johann Engelbert Noyse von Campenhouten|Daniel Baur|Dominicus Custos|Giovanni Battista Fontana,"died 1612|active 1603|died 1639|German, Antwerp after 1550–1612 Augsburg|Italian, ca. 1524–1587",,"Schrenck von Nozing, Jacob|Noyse von Campenhouten, Johann Engelbert|Baur, Daniel|Custos, Dominicus|Fontana, Giovanni Battista",,1603 |1550 |1524,1612 |1603 |1639 |1612 |1587,1603,1603,1603,,"126 engraved plates, Height: 19 5/16 in. (49 cm)",,,Innsbruck,,,Austria,,,,,,,,,http://www.metmuseum.org/art/collection/search/697348,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -QB7 .C15 1772,true,true,681349,The Libraries,,"Calendrier belgique, curieux et utile, contenant les evenemens historiques sur les jours de l'an, et les travaux à faire dans les jardins en chaque mois de l'année, la description des tableaux remarquables, que l'on trouve dans la ville de Gand, avec les noms des peintres pour l'annėe MDCCLXXII",,,,,,Publisher,,Pierre de Goesin,,,Pierre de Goesin,,0,0,1772,1772,1772,,"108 pages, Height: 4 5/16 in. (11 cm)","Gift of Jayne Wrightsman, 2009",,Ghent,,,Belgium,,,,,,,|,,http://www.metmuseum.org/art/collection/search/681349,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -110.5B75 C44,true,true,591837,The Libraries,,Le pitture e sculture di Brescia che sono esposte al pubblico con un' appendice di alcune private gallerie,"Brescia: [s.n.], 1760",,,,,Author,,Luigi Chizzola,,,"Chizzola, Luigi",,1690,1790,1760,1760,1760,Illustrated book,H: 8 1/4 in. (21 cm),Purchased with income from the Jacob S. Rogers Fund,,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/591837,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -201.9F88 B73,true,true,591862,The Libraries,,"Edwin Davis French, A Memorial: His Life, His Art","New York: De Vinne Press, 1908",,,,,Author,,Ira Hutchinson Brainerd,1862–1935,,Brainerd Ira Hutchinson,,1862,1935,1908,1908,1908,Illustrated book,H: 9 7/ 8 in. (25 cm),Presented by Mr. William L. Andrews,,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/591862,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -911.1. M362,true,true,591836,The Libraries,,Il Ritratto di Venezia,"Venice: Presso Gio. Giacomo Hertz, 1684",,,,,Author,,Domenico Martinelli,active 1663–1669,,Martinellie Domenico,,1663,1669,1684,1684,1684,Printed book,H: 6 3/4 in. (17 cm),Presented by Bobby and Allan Weissglass,,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/591836,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -161 L841,true,true,700238,The Libraries,,"The cabinet-makers' London book of prices, and designs of cabinet work, calculated for the convenience of cabinet makers in general, whereby the price of executing any piece of work may be easily found",,,,,,Printer|Publisher|Author,,W. Brown and A. O'Neil|London Society of Cabinet Makers|London Society of Cabinet Makers,London,,"Brown, W. and O'Neil A.|London Society of Cabinet Makers|London Society of Cabinet Makers",,1700,1850,1793,1793,1793,,"xvi, 266, 24 pages, 29 leaves of plates, height: 10 5/8 in. (27 cm)",,,London,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/700238,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -170.1 R674,true,true,738466,The Libraries,,Modern chromatics : with applications to art and industry,,,,,,Author,,Ogden Nicholas Rood,1831-1902,,"Rood, Ogden Nicholas",,1831,1902,1879,1879,1879,,"3 pages, 1 leaf, [v]-viii, [9]-329 pages : color frontispiece, illustrations, diagrams ; Height: 7 7/8 in. (20 cm)",,,New York,New York,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/738466,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -551 G41,true,true,700032,The Libraries,,Petri Gyllii De Bosporo thracio libri III ; Petri Gyllii De topographia Constantinopoleos,,,,,,Publisher|Author,,"Apvd Gvlielmvm, svb scvto veneto|Pierre Gilles",1490–1555,,"Apvd Gvlielmvm, svb scvto veneto|Gilles, Pierre",,1490,1555,1562,1562,1562,,2 parts in 1 volume ; Height: 9 13/16 in. (25 cm),,,Lvgdvni,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/700032,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -146.9 T34,true,true,700050,The Libraries,,"Traité de l'horlogerie, méchanique et pratique, approuvé par l'Academie royale des sciences",,,,,,Author,,Antoine Thiout l'aîné,1692–1767,,"Thiout, Antoine",,1690,1770,1741,1741,1741,,"2 volumes ([26], 400 pages, 50, 41 folded leaves of plates) : illustrations ; Height: 10 1/4 in. (26 cm)",,,Paris,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/700050,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -170.1 C424,true,true,738463,The Libraries,,"The principles of harmony and contrast of colours, and their applications to the arts",,,,,,Author|Translator,,Michel Eugène Chevreul|Charles Martel,1786-1889|-1865,,"Chevreul, Michel Eugène|Martel, Charles",,1786,1889 |1865,1872,1872,1872,,"xlvi, 465 pages, 3 plates ; Height: 7 1/2 in. (19 cm)",,,London,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/738463,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -106.1 V28 F,true,true,704714,The Libraries,,Mr. Vanderbilt's house and collection,,,,,,Publisher|Author,,George Barrie|Edward Strahan (Earl Shinn),"American, 1838–1886",,"Barrie|Strahan, Edward",,1838,1886,1883–84,1883,1884,,"4 volumes, illustrations, height: 18 7/8 in. (48 cm)",,,Boston,Massachusetts,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/704714,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -161 H41,true,true,700240,The Libraries,,"The cabinet-maker and upholsterer's guide, or, Repository of designs for every article of household furniture, in the newest and most approved taste : displaying a great variety of patterns for chairs, stools ... in the plainest and most enriched styles : with a scale to each, and an explanation in letter press : also the plan of a room, shewing the proper distribution of the furniture ... from drawings",,,,,,Publisher|Author,,I. & J. Taylor|A. Hepplewhite & Co.,"London|British, 18th century",,"Taylor, I. & J.|Hepplewhite, A. & Co.",,1750 |1750,1850 |1800,1788,1788,1788,,"30 pages, 125 leaves of plates, illustrations, height: 14 9/16 in. (37 cm)",,,London,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/700240,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -F595 .R38 1895,true,true,738758,The Libraries,,Pony tracks,,,,,,Publisher|Artist,,Harper & Brothers|Frederic Remington,"American, New York|American, Canton, New York 1861–1909 Ridgefield, Connecticut",,"Harper & Brothers|Remington, Frederic",,1833 |1861,1962 |1909,1895,1895,1895,,"viii pages, 1 leaf, 269 pages including plates : illustrations, frontispiece ; Height: 9 1/16 in. (23 cm)",Gift of Friends of the Thomas J. Watson Library,,New York,New York,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/738758,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -NK2229 .S54 1793,true,true,700232,The Libraries,,"The cabinet-maker and upholsterer's drawing-book, in three parts",,,,,,Publisher|Printer|Author,,Thomas Sheraton|Thomas Bensley|Thomas Sheraton,"British, Stockton-on-Tees 1751–1806 London|London|British, Stockton-on-Tees 1751–1806 London",,"Sheraton, Thomas|Bensley, Thomas|Sheraton, Thomas",,1751 |1759 |1751,1806 |1835 |1806,1793–94,1793,1794,,"2 volumes, illustrations, height: 10 5/8 in. (27 cm)",,,London,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/700232,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -NK1510 .J7 1868 Q,true,true,700091,The Libraries,,The grammar of ornament,,,,,,Author|Artist|Artist|Publisher|Artist,,Owen Jones|J. O. Westwood|Matthew Digby Wyatt|Bernard Quaritch Ltd.|J. B. Waring,"British, London 1809–1874 London|British, Rowde, Wiltshire 1820–1877 Cowbridge, South Glamorgan",,"Jones, Owen|Westwood, J. O.|Wyatt, Matthew Digby Sir|Bernard Quaritch Ltd.|Waring, J. B.",,1809 |1805 |1820,1874 |1893 |1877,1868,1868,1868,,"157 pages : illustrations, plates ; Height: 13 3/4 in. (35 cm)",,,London,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/700091,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -PS2267 .A1 1891,true,true,738765,The Libraries,,The song of Hiawatha,,,,,,Publisher|Illustrator|Author,,Houghton Mifflin Company|Frederic Remington|Henry Wadsworth Longfellow,"American, Canton, New York 1861–1909 Ridgefield, Connecticut|American, Portland, Maine 1807–1882 Cambridge, Massachusetts",,"Houghton Mifflin Company|Remington, Frederic|Longfellow, Henry Wadsworth",,1861 |1807,1909 |1882,1891,1891,1891,,"xviii, 242 pages : frontispiece (portrait), illustrations, plates ; Height: 9 7/16 in. (24 cm)",Gift of Friends of the Thomas J. Watson Library,,New York,New York,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/738765,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -DH188.H67 W47 1568,true,true,682010,The Libraries,,"La dedvction de l'innocence de messire Philippe Baron de Montmorency, Conte de Hornes, franc seigneur de Vveert, admiral & capitaine general de la Mer du pais bas ... contre la malicievse apprehension, indeüe detention, injuste procedure, fausse accusation, iniques sentences et tyrannicque exécution en sa personne à grand tort, par voye de faict perpetrees",,,,,,Author,,Jacques de Wesenbeke,1523?–?1577,,"Wesenbeke, Jacques de",,1523,1577,1568,1568,1568,,"[16], 573 [i.e. 572], [2] pages ; Overall: 6 1/8 × 4 1/8 × 1 3/8 in. (15.6 × 10.5 × 3.5 cm)","Gift of Jayne Wrightsman, 2008",,,,,,,,,,,,|,,http://www.metmuseum.org/art/collection/search/682010,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -M2149.5 .C5 1768,true,true,681259,The Libraries,,"Loffice de Noël, 1768",,,,,,Author|Calligrapher,,Catholic Church|Baudouin,,,Catholic Church|Baudouin,,0 |0,0 |0,1768,1768,1768,,209 pages : music ; Height: 8 1/4 in. (21 cm),"Gift of Jayne Wrightsman, 2008",,Versailles,,,France,,,,,,,||,,http://www.metmuseum.org/art/collection/search/681259,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -GV1801 .L6 1889,true,true,726717,The Libraries,,Les jeux du cirque et la vie foraine,,,,,,Author|Illustrator|Publisher,,"Hugues Le Roux|Jules Garnier|E. Plon, Nourrit et Cie.","French, 1847–1889",,"Le Roux Hugues|Garnier, Jules|Plon E., Nourrit et Cie.",,1847,1889,1889,1889,1889,,"v, 250 pages : illustrations(some color), color portraits ; Height: 11 13/16 in. (30 cm)",,,Paris,,,France,,,,,,,,,http://www.metmuseum.org/art/collection/search/726717,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -AE25 .E531 1762 Q,true,true,707455,The Libraries,,"Recueil de planches, sur les sciences, les arts libéraux, et les arts méchaniques : avec leur explication",,,,,,Author|Publisher|Author|Author,,Pierre Mouchon|Briasson|Jean Le Rond d'Alembert|Denis Diderot,"French, 1733–1797|French, 1717–1783|French, 1713–1784",,"Mouchon, Pierre|Briasson|Alembert, Jean Le Rond d'|Diderot, Denis",,1733 |1717 |1713,1797 |1783 |1784,1762–72,1762,1772,,11 volumes ; H: 15 3/4 in. (40 cm),"Jane E. Andrews Fund, in memory of her husband, William Loring Andrews, 1955",,Paris,,,France,,,,,,,,,http://www.metmuseum.org/art/collection/search/707455,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -100.1 B591,true,true,726673,The Libraries,,"Grammaire des arts du dessin : architecture, sculpture, peinture",,,,,,Illustrator|Author,,Léon Gaucherel|Charles Blanc,"French, Paris 1816–1886|French, Castres 1813–1882 Paris",,"Gaucherel, Léon|Blanc, Charles",,1816 |1813,1886 |1882,1876,1876,1876,,"691 pages : illustrations, color plate, diagrams ; Height: 11 in. (28 cm)",,,Paris,,,France,,,,,,,,,http://www.metmuseum.org/art/collection/search/726673,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -100.1 B59,true,true,726669,The Libraries,,"Grammaire des arts du dessin : architecture, sculpture, peinture",,,,,,Author|Illustrator,,Charles Blanc|David-Pierre Giottino Humbert de Superville,"French, Castres 1813–1882 Paris|Dutch, The Hague 1770–1849 Leiden",,"Blanc, Charles|Humbert, de Superville David-Pierre Giottino",,1813 |1770,1882 |1849,1870,1870,1870,,"743 pages : illustrations, color plate ; Height: 11 in. (28 cm)",,,Paris,,,France,,,,,,,,,http://www.metmuseum.org/art/collection/search/726669,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -BX2016.A5 F7 1792,true,true,681246,The Libraries,,"L'office de l'Église en françois contenant les offices pour toute l'année, plusieurs prières tirées de l'écriture-sainte & des saints pères, les hymnes en vers françois, avec une instruction pour les fidèles",,,,,,Author|Publisher,Chez,Catholic Church|Langlois,,,Catholic Church|Langlois,,0,0,1792,1792,1792,,"[12], 612, [2] pages ; Height: 7 1/16 in. (18 cm)","Gift of Jayne Wrightsman, 2008",,Paris,,,France,,,,,,,|,,http://www.metmuseum.org/art/collection/search/681246,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -AY831 .Z7 1792,true,true,679732,The Libraries,,"Almanach royal, année bissextile M.DCC.LXCII : présenté a sa Majeste pour la premiere fois en 1699 par Laurent d'Houry",,,,,,Editor|Publisher,,Laurent Charles d' Houry|Imprimerie de Testu,"French, 1717?–1786",,"Houry, Laurent Charles d'|Imprimerie de Testu",,1717,1786,1791,1791,1791,,"679 pages, 1 folded map, Height: 7 7/8 in. (20 cm)","Gift of Jayne Wrightsman, 2009",,Paris,,,France,,,,,,,|,,http://www.metmuseum.org/art/collection/search/679732,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -273.4D26 AL2,true,true,726678,The Libraries,,"Honoré Daumier, l'homme et l'œuvre : ouvrage orné d'un portrait à l'ea-forte, de deux héliogravures et de 47 illustrations",,,,,,Subject of book|Author,,Honoré Daumier|Arsène Alexandre,"French, Marseilles 1808–1879 Valmondois",,"Daumier, Honoré|Alexandre, Arsène",,1808,1879,1888,1888,1888,,"4 pages of leaves, 383 pages : 12 plates, 1 portrait, illustrations ; Height: 10 5/8 in. (27 cm)",,,Paris,,,France,,,,,,,,,http://www.metmuseum.org/art/collection/search/726678,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -912.63 D23,true,true,591835,The Libraries,,"Asia; oder, Genaue und grundliche Beschreibung des gantzen Syrien und Palestins, oder belobten Landes . . .","Amsterdam : Jacob von Meursen, 1681",,,,,Author,,Olfert Dapper,"Dutch, 1635–1689",,"Dapper, Olfert",Dutch,1639,1689,1681,1681,1681,Illustrated book,2 pts. in 1 vol.; H: 12 1/4 in. (31 cm),Presented by Mrs. John C. McVoy,,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/591835,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -DC130.L14 L14 1749,true,true,681559,The Libraries,,"Memoires et réflexions sur les principaux évenemens du regne de Louis XIV, & sur le caractere de ceux qui y ont eu la principale part. Par mr. l.m.d.L.F",,,,,,Author,,"Charles Auguste, marquis de La Fare","French, 1644–1712",,"Auguste, Charles, marquis de La Fare",French,1644,1712,1749,1749,1749,,15 cm,"Gift of Jayne Wrightsman, 2008",,Amsterdam,,,Netherlands,,,,,,,|,,http://www.metmuseum.org/art/collection/search/681559,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -250 T34,true,true,591839,The Libraries,,"The Theory and Practice of Gardening : Wherein is Fully Handled all that Relates to Fine Gardens, Commonly called Pleasure-Gardens, as Parterres, Groves, Bowling-Greens &c. ....","London: printed by Geo. James, 1712",,,,,Author,,Antoine Joseph Dézallier d'Argenville,"French, Paris 1680–1765 Paris",,"Dézallier d'Argenville, Antoine Joseph",French,1680,1765,1712,1712,1712,Illustrated book,H: 10 1/4 in. (26 cm),"Presented in memory of Daniel W. Langton, Landscape Architect, by Mrs. Langton",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/591839,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -PQ1954.A54 L4 1741,true,true,681502,The Libraries,,"Lettres cabalistiques, ou, Correspondance philosophique, historique & critique, entre deux cabalistes, divers esprits elementaires, & le seigneur Astaroth",,,,,,Author|Publisher,Chez,"Jean Baptiste de Boyer, marquis d'Argens|Pierre Paupie","French, 1704–1771",,"Argens, Jean-Baptiste de Boyer, marquis d'|Pierre Paupie",French,1704 |0,1771 |0,1741,1741,1741,,"6 volumes: illustrations, portraits; Height: 6 5/16 in. (16 cm)","Gift of Jayne Wrightsman, 2008",,,,,,,,,,,,|,,http://www.metmuseum.org/art/collection/search/681502,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -AY831 .Z7 1783,true,true,680708,The Libraries,,Almanach royal : année bissextile M.DCC.LXXXIV. présenté a Sa Majesté pour la premiere fois en 1699 par Laurent d'Houry ...,,,,,,Publisher,,Laurent Charles d' Houry,"French, 1717?–1786",,"Houry, Laurent Charles d'",French,1717,1786,1783,1783,1783,,683 pages,"Gift of Jayne Wrightsman, 2009",,Paris,,,,,,,,,,|,,http://www.metmuseum.org/art/collection/search/680708,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -ND616 .G35 1812,true,true,682015,The Libraries,,"Galerie Giustiniani, ou, Catalogue figuré des tableaux de cette célèbre galerie, transportée d'Italie en France, accompagné d'observations critiques et historiques, et de soixante-douze planches gravée au trait, contenant environ cent cinquante sujets",,,,,,Editor|Printer|Binder,,Charles-Paul Landon|Chaignieau|Simier,French,,"Landon, Charles-Paul|Chaignieau|Simier",French,0019 |0,0019 |0,1812,1812,1812,,"160 pages, 73 leaves of plates; Height: 8 11/16 in. (22 cm)","Gift of Jayne Wrightsman, 2008",,Paris,,,France,,,,,,,|,,http://www.metmuseum.org/art/collection/search/682015,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -BX9454 .S25 1688,true,true,681386,The Libraries,,"Réponse aux plaintes des protestans touchant la prétendüe persecution de France. : Où l'on expose le sentiment de Calvin, & de tous les plus célebres ministres, sur les peines dûës aux hérétiques. On découves aussi plusieurs particularitez dignes d'être sçûës, touchant la Réformation & les réformateurs",,,,,,Printer|Author,Chez,Arnold Seneuse|Denis de Sainte-Marthe,"French, 1650–1725",,Arnold Seneuse|Denis de Sainte-Marthe,French,1650,1725,1688,1688,1688,,"[60], 10 [i.e. 310], [38] pages","Gift of Jayne Wrightsman, 2008",,Paris,,,France,,,,,,,|,,http://www.metmuseum.org/art/collection/search/681386,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -PA6558 .A5 1726,true,true,679476,The Libraries,,"Histoire secrette de Neron, ov, Le festin de Trimalcion, traduit de Petrone, avec des notes historiques par M. Lavaur ...",,,,,,Author|Author,,M. (Guillaume de) Lavaur|Petronius Arbiter,"French, 1653–1730",,"Lavaur, M. (Guillaume de)|Petronius Arbiter",French,1653,1730,1726,1726,1726,,"2 volumes bound in 1 (2 pages, leaf, lxxij, 192 pages; 1 leaf, 193-447, 3 pages), Height: 6 5/16 in. (16 cm)","Gift of Jayne Wrightsman, 2009",,Paris,,,France,,,,,,,|,,http://www.metmuseum.org/art/collection/search/679476,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -TS1484 .B56 1777,true,true,681551,The Libraries,,Mémoire sur un rouet a filer des deux mains a la fois,,,,,,Author|Publisher,,de Bernieres|Clousier,"died 1783|French, active 18th century",,"Bernieres, de|Clousier",French,1683 |1700,1783 |1800,1777,1777,1777,,"vi, 7-22 pages: illustrated plates; Height: 9 13/16 in. (25 cm)","Gift of Jayne Wrightsman, 2008",,Paris,,,France,,,,,,,|,,http://www.metmuseum.org/art/collection/search/681551,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -AY831 .Z7 1788,true,true,680818,The Libraries,,"Almanach royal, année bissextile M.DCC.LXXXVIII, présenté a sa Majeste pour la premiere fois en 1699",,,,,,Publisher,,Laurent Charles d' Houry,"French, 1717?–1786",,"Houry, Laurent Charles d'",French,1717,1786,1787,1787,1787,,"716 pages, Height: 7 7/8 in. (20 cm)","Gift of Jayne Wrightsman, 2009",,Paris,,,France,,,,,,,|,,http://www.metmuseum.org/art/collection/search/680818,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -AY831 .Z7 1784a,true,true,680752,The Libraries,,"Almanach royal, année bissextile M.DCC.LXXXIV.",,,,,,Publisher,,Laurent Charles d' Houry,"French, 1717?–1786",,"Houry, Laurent Charles d'",French,1717,1786,1783,1783,1783,,"127 pages, Height: 4 5/16 in. (11 cm)","Gift of Jayne Wrightsman, 2009",,Paris,,,France,,,,,,,|,,http://www.metmuseum.org/art/collection/search/680752,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -221.1 K63,true,true,591844,The Libraries,,"Phonurgia nova, sive conjugium mechanico-physicum artis & natvrae paranympha phonosophia concinnatum . . .","Campidonae: Rudolphum Dreherr, 1673",,,,,Author,,Athanasius Kircher,"German, 1602–1680",,Kirchner Athanasius,German,1602,1680,1673,1673,1673,Illustrated book,H: 13 3/8 in. (34 cm),"Purchased with income from the Jacob S. Rogers Fund and Bought with the income from the bequest of Nathaniel I. Bowditch, of Boston, (Class of 1812)",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/591844,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -532 L551,true,true,591849,The Libraries,,Denkmaler aus Aegypten und Aethiopien: nach den Zeichnungen der von Deiner Majestat dem Konige von Preussen Friedrich Wilhelm IV Nach Diesen Landern Gesendeten und in den Jahren 1842–1845 Ausgefuhrten Wissenschaftlichen Expedition . . .,"Leipzig, 1897–1913",,,,,Author|Author|Author,,Richard Lepsius|Édouard F. Naville|Ludwig Borchardt,"German, 1810–1884",,"Lepsius, Richard|Naville, Édouard F.|Borchardt, Ludwig",German,1810 |1844 |1863,1884 |1926 |1938,1913,1913,1913,Illustrated books,5 vols.; H: 25 1/4 in. (64 cm),Purchased with income from the Jacob S. Rogers Fund,,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/591849,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -254 B63,true,true,591830,The Libraries,,"Architectura Curiosa Nova: Das ist, neue ergotzliche Sinn und Kunstreiche auch nutzliche Bau- und Wasser-Kunst . . .","Nuremberg : [s.n.], [1664]",,,,,Author,,Georg Andreas Böckler,"German, Cronheim 1644–1698 Ansbach",,"Böckler, Georg Andreas",German,1644,1698,1664,1664,1664,"Printed book, engraved plates",4 pts. in 1 vol.; H: 13 3/8 in. (34 cm),Purchased with income from the Jacob S. Rogers Fund,,Nuremberg,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/591830,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -125.97 D932,true,true,591824,The Libraries,,"""Alberti Dvreri pictoris et architecti praestantissimi De vrbibvs...""","Paris: Officina Christiani Wecheli, 1535",,,,,Author,,Albrecht Dürer,"German, Nuremberg 1471–1528 Nuremberg",,"Dürer, Albrecht",German,1471,1528,1535,1535,1535,Illustrated book,78 pp.; H: 13 3/4 in. (35 cm),Purchased with income from the Jacob S. Rogers Fund,,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/591824,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -912.1 K63 Q,true,true,591834,The Libraries,,"Athanasii Kircheri e Soc. Jesu China monumentis: qua sacris qua profanis, nec non variis naturae & artis spectaculis, aliarumque rerum memorabilium argumentis illustrata",,,,,,Author,,Athanasius Kircher,"German, 1602–1680",,Kirchner Athanasius,German,1602,1680,1667,1667,1667,Printed book,H: 14 5/8 in. (37 cm),"Jane E. Andrews Fund, in memory of her husband, William Loring Andrews",,Amsterdam: Apud Joannem Janssonium a Waesberge,"& Elizeum Weyerstraet, 1667",,,,,,,,,,,http://www.metmuseum.org/art/collection/search/591834,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -916.3 An4 F,true,true,591854,The Libraries,,The New Zealanders,"London: T. McLean, 1847",,,,,Author,,George French Angas,"British, 1822–1886",,Angas George French,British,1822,1886,1847,1847,1847,Illustrated book,H: 22 1/8 in. ( 56 cm),Purchased with income from the Jacob S. Rogers Fund,,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/591854,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -161.1 C44 Q,true,true,591840,The Libraries,,"The Gentleman and Cabinet-maker's Director: Being a Large Collection of . . . Designs of Household Furniture in the Gothic, Chinese and Modern Taste . . .","London: Thomas Chippendale, 1754",,,,,Author,,Thomas Chippendale,"British, baptised Otley, West Yorkshire 1718–1779 London",,"Chippendale, Thomas",British,1718,1779,1754,1754,1754,"Printed book, engraved plates",17 3/4 x 12 1/4 in. (45 x 31 cm),Library Purchase,,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/591840,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -926.1 M642,true,true,591864,The Libraries,,Paradise Lost,"Hammersmith: Doves Press, 1902",,,,,Author,,John Milton,"British, London 1608–1674 London",,"Milton, John",British,1608,1674,1902,1902,1902,Printed book,386 + [2] pp.; H: 9 1/2 in. (24 cm),Presented by Alice M. Dike,,,,,,,,,,,,|,,http://www.metmuseum.org/art/collection/search/591864,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -143.7W41 W413,true,true,591841,The Libraries,,An Address to the Workmen in the Pottery on the Subject of Entering into the Service of Foreign Manufacturers,"Newcastle: J. Smith, 1783 (1st ed.)",,,,,Artist,,Josiah Wedgwood,"British, Burslem, Stoke-on-Trent 1730–1795 Burslem, Stoke-on-Trent",,"Wedgwood, Josiah",British,1730,1795,1783,1783,1783,Printed book,24 pp.; H: 7 7/8 in. (20 cm),Purchased with income from the Jacob S. Rogers Fund,,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/591841,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -170.9 F46,true,true,591855,The Libraries,,"On Painting in Oil and Water Colours, Landscape and Portraits . . .",,,,,,Author,,Theodore Henry Adolphus Fielding,"British, Yorkshire 1781–1851 Croyden",,"Fielding, Theodore Henry Adolphus",British,1781,1851,1839,1839,1839,Illustrated book,H: 11 in. (28 cm),Purchased with income from the Jacob S. Rogers Fund,,London: published for the author,"by Ackermann and Co., 1839",,,,,,,,,,,http://www.metmuseum.org/art/collection/search/591855,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -920.3 W52 F,true,true,591842,The Libraries,,The letters of Sir Richard Westmacott,,,,,,Author,,Sir Richard Westmacott,"British, 1775–1856",,"Westmacott, Richard, Sir",British,1775,1856,19th century,1800,1899,Collection of letters,"18 vols. (ca. 1,400 items) plus index",Purchased with income from the Jacob S. Rogers Fund,,,,,,,,,,,,|,,http://www.metmuseum.org/art/collection/search/591842,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -KKL273.1.Z94 C4 1815,true,true,681372,The Libraries,,Codice civile generale austriaco,,,,,,Binder,,Luigi Lodigiani,"Italian, 1778–1843",,"Lodigiani, Luigi",Italian,1778,1843,1815,1815,1815,,Length: 9 13/16 in. (25 cm),"Gift of Jayne Wrightsman, 2008",,Milan,,,Italy,,,,,,,|,,http://www.metmuseum.org/art/collection/search/681372,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -226 B641,true,true,591845,The Libraries,,Descrizione degli' istromenti armonici d'ogni genere . . .,Presso Pietro Paolo Montignani-Mirabili,,,,,Author,,Filippo Buonanni,"Italian, 1638–1725",,"Buonanni, Filippo",Italian,1638,1725,1806,1806,1806,"Printed books, engraved plates",2 vols.,Presented by Mr. S.P. Avery,,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/591845,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -532 Eg961,true,true,591847,The Libraries,,"Narrative of the Operations and Recent Discoveries Within the Pyramids, Temples, Tombs and Excavations in Egypt and Nubia; and of a Journey to the Coast of the Red Sea, in Search of the Ancient Berenice; and Another to the Oasis of Jupiter Ammon","London: J. Murray, 1820",,,,,Author,,Giovanni Battista Belzoni,"Italian, 1778–1823",,"Belzoni, Giovanni Battista",Italian,1778,1823,1820,1820,1820,Printed book,xix + 533 pp.; H: 11 3/8 in. (29 cm),Presented by Mr. Theodore M. Davis,,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/591847,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -109A L83,true,true,591829,The Libraries,,"Trattato dell' arte della pittura, scultura et architetettura","Milan: [s.n.], 1585",,,,,Author,,Giovanni Paolo Lomazzo,"Italian, Milan 1538–1600 Milan",,"Lomazzo, Giovanni Paolo",Italian,1538,1600,1585,1585,1585,Printed book,H: 8 5/8 in. (22 cm),Purchased with income from the Library Fund,,Milan,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/591829,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -120.32P17 P17,true,true,591828,The Libraries,,I quattro libri dell'architettura di Andrea Palladio . . .,"Venice: Domenico de'Franceschi, 1570",,,,,Author,,Andrea Palladio,"Italian, Padua 1508–1580 Vicenza",,"Palladio, Andrea",Italian,1508,1580,1570,1570,1570,Illustrated book,"4 pts. in 1 vol., 128 pp.; H: 4 3/4 in. (12 cm)",Library Purchase,,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/591828,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -131.1M58 C75,true,true,591825,The Libraries,,Vita di Michelagnolo Buonarroti raccolta per Ascanio Condivi da la Ripa Transone,"Rome: Antonio Blado, 1553 (1st ed.)",,,,,Author,,Ascanio Condivi,"Italian, Ripatransone 1525–1574 Ripatransone",,"Condivi, Ascanio",Italian,1525,1574,1553,1553,1553,Illustrated book,H: 7 7/8 in. (20 cm),Purchased with income from the Jacob S. Rogers Fund,,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/591825,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -120 Se6,true,true,591827,The Libraries,,"Bononiensis de architectura libri quinque quibus cuncta fere architectonicae facultatis mysteria docte, perspicue . . .",,,,,,Author,,Sebastiano Serlio,"Italian, Bologna 1475–1554 Fontainebleau",,"Serlio, Sebastiano",Italian,1475,1554,1568–69,1568,1569,Illustrated book,5 pts. in 1 vol.; H: 12 5/8 in. (32 cm),Purchased with income from the Jacob S. Rogers Fund,,Venice: Apud Francifcum de Francifcis Senenfem,"& Joanneum Chriegher, 1568–1569",,,,,,,,,,,http://www.metmuseum.org/art/collection/search/591827,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -U101 .M7814 1760,true,true,681554,The Libraries,,"Memoires de Montecuculi, generalissime des troupes de l'empereur : divisés en trois livres : I. De l'art militaire en général, II. De la guerre contre le turc, III. Relation de la campagne de 1664",,,,,,Author,,Prince Raimondo Montecuccoli,"Italian, 1609–1680",,"Montecuccoli, Raimondo, Prince",Italian,1609,1680,1760,1760,1760,,"xl, 510, [4] pages: leaves of plates, illustrations, engravings; Height: 6 11/16 in. (17 cm)","Gift of Jayne Wrightsman, 2008",,Paris,,,France,,,,,,,|,,http://www.metmuseum.org/art/collection/search/681554,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -201.9Av3 Av32,true,true,591857,The Libraries,,"S. P. Avery, Engraver on Wood","New York: [s.n.], [18--]",,,,,Author,,Samuel Putnam Avery Sr.,"American, New York 1822–1904 New York",,"Avery, Samuel Putnam, Sr.",American,1822,1904,1800s,1800,1810,Scrapbook,Height: 11 13/16 in. (30 cm),Presented by Emma Avery Welcher and Amy Ogden Welcher,,,,,,,,,,,,||,,http://www.metmuseum.org/art/collection/search/591857,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -543.1N48 M97 Q,true,true,591850,The Libraries,,"A Descriptive Atlas of the Cesnola Collection of Cypriote Antiquities in the Metropolitan Museum of Art, New York (1885–1903)","Boston: J. R. Osgood, 1885–1903",,,,,Author|Author,Introduction by,Ernst Curtius|Luigi Palma di Cesnola,,,"Curtius Ernst|Cesnola, Luigi Palma di",American,1832,1904,1885–1904,1885,1904,Illustrated book,3 vols.; H: 17 3/8 in. (44 cm),Presented by General L. P. di Cesnola,,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/591850,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -UD S83 1842 v.1,true,true,591852,The Libraries,,"Incidents of Travel in Central America, Chiapas, and Yucatan","London: J. Murray, 1842 (new ed.)",,,,,Author,,John Lloyd Stephens,"American, 1805–1852",,Stephens John Lloyd,American,1805,1852,1842,1842,1842,Printed book,2 vols.; H: 9 in. (23 cm),"Gift of the Dept. of Twentieth Century Art, 1998",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/591852,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -UD S83 1843 v.2,true,true,591853,The Libraries,,Incidents of Travel in Yucatan,"New York: Harper Bros., 1843",,,,,Author,,John Lloyd Stephens,"American, 1805–1852",,Stephens John Lloyd,American,1805,1852,1843,1843,1843,Illustrated book,2 vols.; 9 in. (23 cm),"Gift of the Dept. of Twentieth Century Art, 1998",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/591853,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -272.4 Au8,true,true,591859,The Libraries,,Autographs and Sketches from Artist Friends to Samuel P. Avery,,,,,,Author,,Samuel Putnam Avery Sr.,"American, New York 1822–1904 New York",,"Avery, Samuel Putnam, Sr.",American,1822,1904,1874–80,1874,1880,Letter-book (manuscript),1 vol.; H: 11 3/8 in. (29 cm),Presented by Amy Ogden Welcher and Emma Avery Welcher,,,,,,,,,,,,||,,http://www.metmuseum.org/art/collection/search/591859,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -201.9 Av3,true,true,591858,The Libraries,,Diaries of Samuel P. Avery,,,,,,Author,,Samuel Putnam Avery Sr.,"American, New York 1822–1904 New York",,"Avery, Samuel Putnam, Sr.",American,1822,1904,1871–82,1871,1882,Bound manuscript,5 vols.; H: 7 1/8 in. (18 cm),"Presented by Emma Avery Welcher, Amy Ogden Welcher and Alice Lee (Mrs. C. Telford) Erickson. (Avery's granddaughters)",,,,,,,,,,,,|,,http://www.metmuseum.org/art/collection/search/591858,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -192H221 B61,true,true,591861,The Libraries,,"Scrapbook containing newspaper and magazine clippings, calling cards, sketches, photographs of paintings, and two sales catalogs of Harnett's (William Michael Harnett, 1848–1892) work",,,,,,Author,,William Ignatius Blemly,American,,Blemly William Ignatius,American,0018,0018,,1848,1900,Scrapbook,108 pp.; H: 10 1/4 in. (26 cm),Presented by James Maroney in friendship for H. Barbara Weinberg,,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/591861,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -AE25 .E53 1751 Q,true,true,591843,The Libraries,,"Encyclopédie, ou Dictionnaire raisonné des sciences, des arts et des métiers",,,,,,Publisher|Author|Author,,Briasson|Denis Diderot|Jean Le Rond d'Alembert,"French, 1713–1784|French, 1717–1783",,"Briasson|Diderot, Denis|Alembert, Jean Le Rond d'",French|French,1713 |1717,1784 |1783,1751–65,1751,1765,,17 volumes ; H: 15 3/4 in. (40 cm),Bequest of Marianne Khuner,,Paris,,,France,,,,,,,,,http://www.metmuseum.org/art/collection/search/591843,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -AY831 .Z7 1784,true,true,681357,The Libraries,,"Le calendrier de la cour : tiré des ephémérides, pour l'année bissextile mil sept-cent quatre-vingt-quatre : contenant le lieu du soleil, son lever, son coucher, sa déclinaison: le lever de la lune & son coucher, &c. : avec la naissance des rois, reines, princes & princesses de l'Europe imprimé pour la famille royale et maison de sa Majesté",,,,,,Printer|Author,Chez,La Veuve Hérissant|Jacques Collombat,"French, 1668–1744",,"Hérissant, La Veuve|Collombat, Jacques",French|French,1668,1744,1784,1784,1784,,Height: 4 5/16 in. (11 cm),"Gift of Jayne Wrightsman, 2009",,Paris,,,France,,,,,,,|,,http://www.metmuseum.org/art/collection/search/681357,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -PQ1189 .M66 1765,true,true,680832,The Libraries,,"Anthologie françoise, ou, Chansons choisies, depuis le 13a siècle jusqu'à présent",,,,,,Publisher|Author|Author,,Barbou|Jean Monnet|Anne Gabriel Meusnier de Querlon,"French, 1703–1785|French, 1702–1780",,"Barbou|Monnet, Jean|de Querlon, Anne Gabriel Meusnier",French|French,0 |1703 |1702,0 |1785 |1780,1765,1765,1765,,"3 volumes, frontispiece, portraits, plates, Height: 7 1/2 in. (19 cm)","Gift of Jayne Wrightsman, 2009",,Paris,,,France,,,,,,,|,,http://www.metmuseum.org/art/collection/search/680832,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2000.194.1, .2",false,true,26555,Arms and Armor,Pair of flintlock pistols,Pair of Flintlock Pistols,"Scottish, Doune",,,,,Gunsmith,,Alexander Campbell,"Scottish, Doune, died 1790",,"Campbell, Alexander",Scottish,1690,1790,ca. 1750–70,1725,1795,"Steel, silver",L. 11 3/4 in. (29.8 cm),"Gift of Edward Coe Embury Jr., Philip Aymar Embury, and Dorothy Embury Staats, in memory of Aymar Embury II and his wife, Jane Embury Benepe, 2000",,Doune,Perthshire,,,,,,,,,Firearms-Pistols-Flintlock,,http://www.metmuseum.org/art/collection/search/26555,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -52.208.5,false,true,24824,Arms and Armor,Wheellock pistol,Wheellock Pistol,"Swiss, Zürich",,,,,Gunsmith,,Felix Werder,"Swiss, Zurich, 1591–1673",,"Werder, Felix",Swiss,1591,1673,dated 1640,1640,1640,"Steel, bronze, gold, wood",Cal. .50 in. (12.7 mm); L. 24 3/8 in. (61.9 cm); L. of barrel 17 in. (43.2 cm); L. of lockplate 6 5/8 in. (16.8 cm); Wt. 2 lb. 4 oz. (1021 g),"Gift of Alan Rutherfurd Stuyvesant, 1952",,Zürich,,,,,,,,,,Firearms-Pistols,,http://www.metmuseum.org/art/collection/search/24824,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -10.42,false,true,22115,Arms and Armor,Wheellock pistol,Wheellock Pistol,"Swiss, Zürich",,,,,Gunsmith,,Felix Werder,"Swiss, Zurich, 1591–1673",,"Werder, Felix",Swiss,1591,1673,dated 1630,1630,1630,"Steel, bronze, wood (beech), silver",L. 23 1/4 in. (59.1 cm); L. of barrel 16 in. (40.6 cm); L. of plug 1 3/4 in. (4.4 cm); L. of lock 6 1/2 in. (16.5 cm); Cal. 458 in. (11.6 mm); Wt. 3 lb. 7 oz. (1559 g),"Rogers Fund, 1910",,Zürich,,,,,,,,,,Firearms-Pistols-Wheellock,,http://www.metmuseum.org/art/collection/search/22115,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"14.25.790a, b",false,true,22285,Arms and Armor,Armored skirt (base),Armored Skirt (Base),"Austrian, Innsbruck",,,,,Armorer,Attributed to,Konrad Seusenhofer,"Austrian, Innsbruck, died 1517",,"Seusenhofer, Konrad",Austrian,1417,1517,ca. 1510–15,1485,1540,"Steel, gold",D. 1/16 in. (0.2 cm); Wt. 12 lb. 14 oz. (5840 g),"Gift of William H. Riggs, 1913",,Innsbruck,,,,,,,,,,Armor for Man,,http://www.metmuseum.org/art/collection/search/22285,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.158.121,false,true,22824,Arms and Armor,Left pauldron (shoulder defense) of a boy's costume armor,Left Pauldron (Shoulder Defense) from a Boy's Costume Armor,"Austrian, Innsbruck",,,,,Armorer,,Hans Seusenhofer,"Austrian, Innsbruck, 1470–1555",,"Seusenhofer, Hans",Austrian,1470,1555,ca. 1532,1507,1557,"Steel, copper alloy, gold",H. 4 1/2 in. (11.4 cm); W. 4 1/2 in. (11.4 cm); D. 3 1/2 in. (8.9 cm),"Bashford Dean Memorial Collection, Funds from various donors, 1929",,Innsbruck,,,,,,,,,,Armor Parts,,http://www.metmuseum.org/art/collection/search/22824,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.158.304,false,true,23287,Arms and Armor,Left tasset (thigh defense) from a boy's costume armor,Left Tasset (Thigh Defense) from a Boy's Costume Armor,"Austrian, Innsbruck",,,,,Armorer,,Hans Seusenhofer,"Austrian, Innsbruck, 1470–1555",,"Seusenhofer, Hans",Austrian,1470,1555,ca. 1532,1507,1557,"Steel, copper alloy, gold",H. 8 3/8 in. (21.3 cm); W. 6 in. (15.2 cm); D. 4 1/2 in. (11.4 cm),"Bashford Dean Memorial Collection, Funds from various donors, 1929",,Innsbruck,,,,,,,,,,Armor Parts-Thigh and Leg Defense,,http://www.metmuseum.org/art/collection/search/23287,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"29.157.13a, b",false,true,27958,Arms and Armor,Rapier with scabbard,Rapier with Scabbard,"German; blade, Spanish, Toledo",,,,,Bladesmith,Blade signed by,Juan Martinez,"Spanish, Toledo, active ca. 1600",,"Martinez, Juan",Spanish,1575,1625,ca. 1580,1555,1605,"Steel, leather, gold, copper wire, wood, velvet",L. 48 3/4 in. (123.8 cm); W. 8 1/2 in. (21.6 cm); D. 5 in. (12.7 cm); Wt. 3 lb. 5 oz. (1507 g); Wt. of scabbard 6 oz. (170.1 g),"Bashford Dean Memorial Collection, Gift of Mr. and Mrs. Robert W. de Forest, 1929",,Toledo,,,,,,,,,,Swords,,http://www.metmuseum.org/art/collection/search/27958,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"29.157.13a, b, .14",false,true,690007,Arms and Armor,Rapier and parrying dagger,Rapier and Parrying Dagger,"German; rapier blade, Spanish, Toledo",,,,,Bladesmith,Rapier blade signed by,Juan Martinez,"Spanish, Toledo, active ca. 1600",,"Martinez, Juan",Spanish,1575,1625,ca. 1580,1555,1605,"Steel, gold, copper wire, wood, velvet",L. 48 3/4 in. (123.8 cm); W. 8 1/2 in. (21.6 cm); D. 5 in. (12.7 cm); Wt. 3 lb. 5 oz. (1507 g),"Bashford Dean Memorial Collection, Gift of Mr. and Mrs. Robert W. de Forest, 1929",,Toledo,,,,,,,,,,Swords,,http://www.metmuseum.org/art/collection/search/690007,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1997.356,false,true,26554,Arms and Armor,Snaphaunce hunting rifle,Snaphaunce Hunting Rifle,"Swedish, Stockholm",,,,,Stock maker,Signed by,Jonas Schertiger the Younger,"Swedish, active 1715–died 1748",,"Schertiger, Jonas the Younger",Swedish,1715,1748,dated 1722,1722,1722,"Steel, wood (walnut), brass, horn",L. 43 3/8 in. (117.8 cm),"Purchase, Gifts of Prince Albrecht Radziwill and Charles M. Schott Jr., by exchange, and Rogers Fund, 1997",,Stockholm,,,,,,,,,,Firearms,,http://www.metmuseum.org/art/collection/search/26554,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -60.151,false,true,24849,Arms and Armor,Barbute,Barbute,"Italian, Brescia",,,,,Armorer,,"Jacopo da Cannobio, called Bichignola","Italian, Brescia, active ca. 1460",,"Cannobio, Jacobo de","Italian, Brescia",1435,1485,ca. 1460,1435,1485,Steel,H. 11 3/8 in. (28.9 cm); W. 7 7/8 in. (20 cm); D. 10 5/8 in. (27 cm); Wt. 5 lb. 4 oz. (2381 g),"Gift of Mrs. George A. Douglass, in memory of her husband, 1960",,,Brescia,,,,,,,,,Helmets,,http://www.metmuseum.org/art/collection/search/24849,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -04.3.152,false,true,21962,Arms and Armor,Hunting knife,Hunting Knife,"Austrian, Hall",,,,,Bladesmith,,Hans Sumersperger,"Austrian, Hall, active 1492–1498",,"Sumersperger, Hans",Austrian,1492,1498,ca. 1500,1475,1525,"Steel, copper alloy, wood, bone, mother-of-pearl",L. 18 5/8 in. (47.29 cm); W. 2 1/2 in. (6.35 cm),"Rogers Fund, 1904",,Hall,Tyrol,,,,,,,,,Knives,,http://www.metmuseum.org/art/collection/search/21962,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.158.704,false,true,23342,Arms and Armor,Hunting sword,Hunting Sword,"Austrian, Hall",,,,,Sword maker,Attributed to,Hans Sumersperger,"Austrian, Hall, active 1492–1498",,"Sumersperger, Hans",Austrian,1492,1498,ca. 1500,1475,1525,"Steel, copper alloy, horn, bone",L. 49 1/2 in. (125.7 cm); L. of blade 40 in. (101.6 cm); W. of blade 1 7/16 in. (3.6 cm); Wt. 3 ls. 9 oz. (1615.9 g),"Bashford Dean Memorial Collection, Funds from various donors, 1929",,Hall,Tyrol,,,,,,,,,Swords,,http://www.metmuseum.org/art/collection/search/23342,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.150.5a,false,true,23089,Arms and Armor,Sallet,Sallet,"Austrian, Innsbruck",,,,,Armorer,,Jörg Wagner,"Austrian, Innsbruck, recorded 1485–92",,"Wagner, Jörg",Austrian,1485,1492,ca. 1485–95,1460,1520,Steel,D. of tail 7 1/2 in. (19.1 cm),"Bashford Dean Memorial Collection, Bequest of Bashford Dean, 1928",,Innsbruck,,,,,,,,,,Helmets,,http://www.metmuseum.org/art/collection/search/23089,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1991.4,false,true,24945,Arms and Armor,Elements of a light-cavalry armor,Elements of a Light-Cavalry Armor,"Austrian, Innsbruck or Mühlau",,,,,Armorer,,Christian Schreiner the Younger,"Austrian, Mühlau, recorded 1499–1528",,"Schreiner the Younger, Christian",Austrian,1499,1528,ca. 1505–10,1480,1535,"Steel, leather",H. as mounted approximately 32 in. (81.28 cm); Wt. 21 lb. 7 oz. (9724 g),"Purchase, Mr. and Mrs. Arthur Ochs Sulzberger Gift, in honor of Helmut Nickel, 1991",,Innsbruck,,,,,,,,,,Armor for Man-1/2 Armor,,http://www.metmuseum.org/art/collection/search/24945,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -04.3.229,false,true,21984,Arms and Armor,Sallet,Sallet,"Austrian, Innsbruck",,,,,Armorer,Attributed to,Adrian Treytz the Elder,"Austrian, Innsbruck, active ca. 1473–92",,"Treytz the Elder, Adrian",Austrian,1448,1517,ca. 1480,1455,1505,Steel,H. 9 3/4 in. (24.8 cm); W. 11 1/2 in. (29.2 cm); D. 14 5/8 in. (37.1 cm); Wt. 6 lb. 2 oz. (2778 g),"Rogers Fund, 1904",,Innsbruck,,,,,,,,,,Helmets,,http://www.metmuseum.org/art/collection/search/21984,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.158.35,false,true,23234,Arms and Armor,Close helmet,Close Helmet,"Austrian, Innsbruck",,,,,Armorer,,Hans Maystetter,"Austrian, Innsbruck, documented 1508–33",,"Maystetter, Hans",Austrian,1508,1533,ca. 1505–10,1480,1535,Steel,H. 11 in. (27.9 cm); W. 9 in. (22.9 cm); D. 10 3/8 in. (26.4 cm); Wt. 5 lb. 8 oz. (2495 g),"Bashford Dean Memorial Collection, Funds from various donors, 1929",,Innsbruck,,,,,,,,,,Helmets,,http://www.metmuseum.org/art/collection/search/23234,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -28.74.4,false,true,23040,Arms and Armor,Breastplate,Breastplate,"Austrian, Innsbruck",,,,,Armorer,,Hans Maystetter,"Austrian, Innsbruck, documented 1508–33",,"Maystetter, Hans",Austrian,1508,1533,ca. 1510,1485,1535,Steel,H. 17 1/2 in. (44.45 cm); Wt. 5 lb. 11 oz. (2580 g),"Gift of George D. Pratt, 1928",,Innsbruck,,,,,,,,,,Armor Parts-Breastplates,,http://www.metmuseum.org/art/collection/search/23040,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"14.25.1408a, b",false,true,22382,Arms and Armor,Pair of flintlock pistols,Pair of Flintlock Pistols,"Southern Netherlandish, Aachen",,,,,Gunsmith,,Leonardus Graeff,"Aachen (now Germany), active ca. 1670–80",,"Graeff, Leonardus",Aachen,1645,1705,ca. 1675–85,1650,1710,"Steel, gold, silver, ivory",L. of each 19 3/8 in. (49.2 cm); L. of each barrel 12 3/16 in. (30.9 cm); Cal. of each barrel .52 in. (13.2 mm); Wt. of each 2 lb. 3 oz. (992 g),"Gift of William H. Riggs, 1913",,Aachen,,,,,,,,,,Firearms-Pistols-Flintlock,,http://www.metmuseum.org/art/collection/search/22382,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"50.203.1, .2",false,true,24815,Arms and Armor,Pair of wheellock rifles,Pair of Wheellock Rifles Made for Emperor Leopold I (1640–1705),"Bohemian, Prague",,,,,Gunsmith,,Caspar Neireiter,"Bohemian, Prague, recorded 1667–ca. 1730",,"Neireiter, Caspar",Bohemian,1667,1755,ca. 1670–80,1645,1705,"Steel, silver, wood (walnut)",L. of each 43 3/8 in. (110.2 cm); L. of each barrel 32 1/16 in. (81.4 cm); Cal. of each .544 in. (13.8 mm); Wt. of 50.203.1 9 lb. 4 oz. (4196 g); Wt. of 50.203.2 9 lb. 3 oz. (4167 g),"Purchase, Joseph Pulitzer Bequest, 1950",,Prague,,,,,,,,,,Firearms-Guns-Wheellock,,http://www.metmuseum.org/art/collection/search/24815,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -38.137a–c,false,true,24661,Arms and Armor,"Helmet, breastplate, and backplate","Helmet, Breastplate, and Backplate","Flemish, possibly Antwerp",,,,,Armorer,Signed on the backplate by,D. G. V. Lochorst,"Flemish, possibly Antwerp, active ca. 1575",,Lochorst D. G. V.,Flemish,1550,1600,ca. 1575,1550,1600,"Steel, leather, textile (velvet, wool)",H. as mounted 30 1/4 in. (36.8 cm); Wt. 12 lb. 3 oz. (5529 g); helmet (a): H. 15 1/2 in. (39.37 cm); Wt. 3 lb. 12 oz. (1695 g); breastplate (b): H. 18 1/2 in. (47.0 cm); W. 12 in. (30.5 cm); Wt. 4 lb. 9 oz. (2073 g); backplate (c): H. 15 in. (38.1 cm); W. 12 in. (30.5 cm); Wt. 3 lb. 14 oz. (1765 g),"Gift of Christian A. Zabriskie, 1938",,Antwerp,Antwerpen,,,,,,,,,Armor for Man,,http://www.metmuseum.org/art/collection/search/24661,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -09.210.2,false,true,22110,Arms and Armor,Basket-hilted sword,Basket-hilted Sword,"hilt, British; blade, German",,,,,Bladesmith,Blade by,Johannes Wundes the Younger,"Germany, Solingen, active mid-17th century",,"Wundes the Younger, Johannes",Germany,1625,1675,blade dated 1662,1662,1662,"Steel, wood, silver",L. 39 7/8 in. (101.3 cm); L. of blade 34 1/4 in. (87 cm); W. 5 in. (12.7 cm); Wt. 2 lb. 6 oz. (1077 g),"Rogers Fund, 1909",,,,,,,,,,,,Swords,,http://www.metmuseum.org/art/collection/search/22110,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -04.3.163,false,true,21965,Arms and Armor,Wheellock rifle,Wheellock Rifle,"Silesian, Cieszyn",,,,,Gunsmith,,Johannes Hartel,"Polish, Cieszyn (Silesian), active ca. 1650",,"Hartel, Johannes","Polish, Cieszyn (Silesian)",1625,1675,ca. 1650–60,1625,1685,"Steel, silver, wood (red beech), staghorn, mother of pearl",L. 42 3/4 in. (108.6 cm); Cal. 17/32 in. (13.5 mm); L. of barrel 30 7/8 in. (78.4 cm); L. of trigger 8 1 4/ in. (21 cm); Wt. 7 lb. 11 oz. (3500 g),"Rogers Fund, 1904",,Cieszyn,,,,,,,,,,Firearms-Guns-Wheellock,,http://www.metmuseum.org/art/collection/search/21965,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1987.397,false,true,24930,Arms and Armor,Flintlock gun,Gun with Flintlock a la moda,"Spanish, Madrid",,,,,Gunsmith,,Gabriel de Algora,"Spanish, Madrid, documented 1733–died 1761",,"Algora, Gabriel de",Spanish,1733,1761,dated 1744,1744,1744,"Steel, gold, wood",L. of barrel 38 3/4 in. (98.4 cm),"Purchase, Gifts of George D. Pratt, Charles M. Schott Jr., and Bashford Dean, and Bashford Dean Memorial Collection, Funds from various donors, by exchange, 1987",,Madrid,,,,,,,,,,Firearms,,http://www.metmuseum.org/art/collection/search/24930,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"27.160.12, .13",false,true,22985,Arms and Armor,Pair of pistols,Pair of Pistols with Flintlocks a la moda,"Spanish, Madrid",,,,,Gunsmith,,Gabriel de Algora,"Spanish, Madrid, documented 1733–died 1761",,"Algora, Gabriel de",Spanish,1733,1761,ca. 1735–40,1710,1765,"Steel, wood (walnut), gold, silver, horn",L. of each 19 in. (48.3 cm),"Gift of Archer M. Huntington, 1927",,Madrid,,,,,,,,,,Firearms-Pistols-Flintlock,,http://www.metmuseum.org/art/collection/search/22985,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"1986.265.1, .2",true,true,24927,Arms and Armor,Pair of flintlock pistols,Pair of Flintlock Pistols of Empress Catherine the Great (1729–1796),"Russian, Saint Petersburg",,,,,Gunsmith,,Johan Adolph Grecke,"Russian, Saint Petersburg, recorded 1755–90",,Grecke Johan Adolph,Russian,1755,1790,1786,1786,1786,"Steel, ivory, gold, brass",L. of each 14 1/2 in. (36.8 cm),"Gift of John M. Schiff, in memory of Edith Baker Schiff, 1986",,Saint Petersburg,,,,,,,,,,Firearms-Pistols-Flintlock,,http://www.metmuseum.org/art/collection/search/24927,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.158.52,false,true,22775,Arms and Armor,Armet,Armet,Flemish,,,,,Armorer,Possibly by,Guillem Margot,"Flemish, active in Brussels, recorded 1505–20",,"Margot, Guillem",Flemish,1505,1520,ca. 1505,1480,1530,"Steel, copper alloy",H. 12 in. (30.5 cm); W. 8 7/16 in. (21.4 cm); D. 13 in. (33 cm); Wt. 9 lb. 4 oz. (4192 g),"Bashford Dean Memorial Collection, Funds from various donors, 1929",,,,,,,,,,,,Helmets,,http://www.metmuseum.org/art/collection/search/22775,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"29.158.1b–d, g–m",false,true,23226,Arms and Armor,Composed armor,Composed Armor,"Italian, probably Milan",,,,,Armorer,Left elbow cop (h) marked by,Guillem Margot,"Flemish, active in Brussels, recorded 1505–20",,"Margot, Guillem",Flemish,1505,1520,ca. 1500–1520,1475,1545,"Steel, leather",Wt. 16 lb. 15 oz. (7682.7 g),"Bashford Dean Memorial Collection, Funds from various donors, 1929",,probably Milan,,,,,,,,,,Armor for Man,,http://www.metmuseum.org/art/collection/search/23226,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1993.14,true,true,24953,Arms and Armor,Short sword (Yatagan),Short Sword (Yatagan) from the Court of Süleyman the Magnificent (reigned 1520–66),"Turkish, Istanbul",,,,,Sword maker,Workshop of,Ahmed Tekelü,"possibly Iranian, active Istanbul, ca. 1520–30",,"Tekelü, Ahmed",possibly Iranian,1515,1530,ca. 1525–30,1500,1555,"Steel, gold, ivory (walrus), silver, turquoise, pearls, rubies",L. 23 3/8 in. (59.3 cm); L. of blade 18 3/8 in. (46.7 cm); Wt. 1 lb. 8 oz. (691 g),"Purchase, Lila Acheson Wallace Gift, 1993",,Istanbul,,,,,,,,,,Swords,,http://www.metmuseum.org/art/collection/search/24953,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -42.50.32,false,true,24701,Arms and Armor,Sallet,Sallet,"Austrian, Innsbruck or Mühlau",,,,,Armorer,,Kaspar Riederer,"Austrian, Innsbruck and Mühlau, active 1455–99",,"Riederer, Kaspar",Austrian,1455,1499,ca. 1480,1455,1505,"Steel, textile",H. 9 1/2 in. (24.13 cm); W. 9 in. (22.86 cm); D. 14 7/8 in. (37.77 cm); Wt. 6 lb. 8 oz. (2948 g),"Gift of Stephen V. Grancsay, 1942",,Innsbruck or Mühlau,,,,,,,,,,Helmets,,http://www.metmuseum.org/art/collection/search/24701,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.25.1661b,false,true,22400,Arms and Armor,Rondel,Rondel for a Shaffron (Horse's Head Defense),"Austrian, Innsbruck or Mühlau",,,,,Armorer,,Kaspar Riederer,"Austrian, Innsbruck and Mühlau, active 1455–99",,"Riederer, Kaspar",Austrian,1455,1499,ca. 1485–95,1460,1520,Steel,Diam. 7 1/2 in. (19 cm); Wt. 8 oz. (226 g),"Gift of William H. Riggs, 1913",,Innsbruck or Mühlau,,,,,,,,,,Equestrian Equipment-Shaffrons,,http://www.metmuseum.org/art/collection/search/22400,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"48.26a, b",false,true,24785,Arms and Armor,Hunting sword,Hunting Sword,"Austrian, Vienna",,,,,Decorator|Decorator,,Emanuel Pioté|Jacob H. Köchert,"Austrian, Vienna 1781–1865|Austrian, Vienna 1795–1868",,"Pioté, Emanuel|Köchert, Jacob H.",Austrian|Austrian,1781 |1795,1865 |1868,ca. 1825,1800,1850,"Steel, gold, enamel, agate, wood, leather",L. 26 15/16 in. (68.5 cm),"Rogers Fund, 1948",,Vienna,,,,,,,,,,Swords,,http://www.metmuseum.org/art/collection/search/24785,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.150.6,false,true,23090,Arms and Armor,Half armor,Half Armor,"German, Augsburg",,,,,Armorer|Armorer,Helmet and reinforcing pieces attributed to,Kolman Helmschmid|Kolman Helmschmid,"German, Augsburg 1471–1532|German, Augsburg 1471–1532",,"Helmschmid, Kolman|Helmschmid, Kolman",German|German,1471 |1471,1532 |1532,ca. 1510–20 and later,1485,1545,Steel,"as mounted, H. 37 in. (94 cm)","Bashford Dean Memorial Collection, Bequest of Bashford Dean, 1928",,Augsburg,,,,,,,,,,Armor for Man-1/2 Armor,,http://www.metmuseum.org/art/collection/search/23090,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"29.150.1d, e",false,true,23085,Arms and Armor,Pair of tassets,Pair of Tassets of Emperor Charles V of Austria (1500–1558),"German, Augsburg",,,,,Armorer|Armorer,or,Kolman Helmschmid|Desiderius Helmschmid,"German, Augsburg 1471–1532|German, Augsburg, 1513–1579",,"Helmschmid, Kolman|Helmschmid, Desiderius",German|German,1471 |1513,1532 |1579,ca. 1530–40,1505,1565,"Steel, gold",L. of each 8 3/4 in. (22.2 cm); W. of each 9 3/8 in. (23.8 cm); Wt. of right tasset 1 lb. 1 oz. (482 g); Wt. of left tasset 1 lb. 2 oz. (510 g),"Bashford Dean Memorial Collection, Bequest of Bashford Dean, 1928",,Augsburg,,,,,,,,,,Armor Parts,,http://www.metmuseum.org/art/collection/search/23085,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -38.143a,false,true,35795,Arms and Armor,Armet,Armet,"German, Augsburg",,,,,Armorer|Armorer,Attributed to|Attributed to,Kolman Helmschmid|Desiderius Helmschmid,"German, Augsburg 1471–1532|German, Augsburg, 1513–1579",,"Helmschmid, Kolman|Helmschmid, Desiderius",German|German,1471 |1513,1532 |1579,"ca. 1525; bolts, modern",1500,1850,"Steel, gold",Wt. 8 lb. 8 oz. (3849 g),"Gift of Marshall Field, 1938",,Augsburg,,,,,,,,,,Armor for Man,,http://www.metmuseum.org/art/collection/search/35795,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -25.135.66,false,true,22885,Arms and Armor,Burgonet,Burgonet,"German, Augsburg",,,,,Armorer|Decorator,Attributed to|Embossed decoration attributed to,Desiderius Helmschmid|Jörg Sigman,"German, Augsburg, 1513–1579|German, Augsburg, 1527–1601",,"Helmschmid, Desiderius|Sigman, Jörg",German|German,1513 |1527,1579 |1601,ca. 1550–55,1525,1580,Steel,H. 11 1/4 in. (28.6 cm); W. 8 3/4 in. (22.3 cm); D. 15 in. (38.1 cm); Wt. 4 lb. 11 oz. (2126 g),"Gift of William H. Riggs, 1913",,Augsburg,,,,,,,,,,Helmets,,http://www.metmuseum.org/art/collection/search/22885,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2002.233.2–.26,false,true,25106,Arms and Armor,Twenty-five inked impressions of engraved firearms ornament,"Twenty-Five Inked Impressions (or ""Pulls"") of Engraved Firearms Ornament",American and German,,,,,Decorator,,Gustave Young,"American (born Prussia), 1827–1895 Springfield, Massachusetts",,"Young, Gustave","American, born Prussia",1827,1895,ca. 1845–65,1845,1865,Ink on paper,"various sizes, largest: 1 5/8 x 6 3/4 in. (4.1 x 17.1 cm); smallest: 5/8 x 2 in. (1.6 x 5.1 cm)","Gift of Herbert G. Houze, 2002",,,,,,,,,,,,Works on Paper-Prints,,http://www.metmuseum.org/art/collection/search/25106,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -27.206a–l,false,true,23016,Arms and Armor,Foot-combat armor,Foot-Combat Armor of Prince-Elector Christian I of Saxony (reigned 1586–91),"German, Augsburg",,,,,Armorer|Decorator,Decoration attributed to,Anton Peffenhauser|Jörg Sorg the Younger,"German, Augsburg, 1525–1603|German, Augsburg, ca. 1522–1603",,"Peffenhauser, Anton|Sorg the Younger, Jörg",German|German,1525 |1500,1603 |1625,1591,1591,1591,"Steel, gold, leather, copper alloy",H. 38 11/16 in. (98.2 cm); Wt. 46 lb. 3 oz. (20.96 kg); helmet Wt. 11 lb. 9 oz. (5245 g),"Gift of Henry Walters, 1927",,Augsburg,,,,,,,,,,Armor for Man-1/2 Armor,,http://www.metmuseum.org/art/collection/search/23016,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.155.2,false,true,23207,Arms and Armor,Field and tournament armor,"Field and Tournament Armor of Johann Wilhelm (1530–1573), Duke of Saxe-Weimar","German, Augsburg",,,,,Etcher|Armorer,Etched decoration attributed to|Attributed to,Jörg Sorg the Younger|Anton Peffenhauser,"German, Augsburg, ca. 1522–1603|German, Augsburg, 1525–1603",,"Sorg the Younger, Jörg|Peffenhauser, Anton",German|German,1500 |1525,1625 |1603,ca. 1565,1540,1590,"Steel, gold, brass, textile, leather",Wt. 61 lb. 1 oz. (27.7 kg),"Bashford Dean Memorial Collection, Gift of Helen Fahnestock Hubbard, in memory of her father, Harris C. Fahnestock, 1929",,Augsburg,,,,,,,,,,Armor for Man,,http://www.metmuseum.org/art/collection/search/23207,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"14.25.901a, b",false,true,22339,Arms and Armor,Pair of gauntlets,Pair of Gauntlets from a Garniture of Armor of Philip II of Spain (reigned 1554–58),"German, Augsburg",,,,,Etcher|Armorer,,Ulrich Holzmann|Desiderius Helmschmid,"German, Augsburg, recorded 1535–62|German, Augsburg, 1513–1579",,"Holzmann, Ulrich|Helmschmid, Desiderius",German|German,1535 |1513,1562 |1579,1546,1546,1546,"Steel, leather, gold",Gauntlet (a); H. 10 in. (25.5 cm); W. 4 3/4 in. (12 cm); D. 5 in. (12.7 cm); Wt. 1 lb. 1 oz. (475 g); gauntlet (b); H. 10 in. (25.5 cm); W. 4 3/4 in. (12 cm); D. 5 in. (12.7 cm); Wt. 16 oz. (439 g),"Gift of William H. Riggs, 1913",,Augsburg,,,,,,,,,,Armor Parts-Gauntlets,,http://www.metmuseum.org/art/collection/search/22339,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -28.125,false,true,23045,Arms and Armor,Wheellock rifle,Wheellock Rifle Made for Emperor Leopold I,"German, Schwäbisch Gmünd",,,,,Stock maker|Barrelsmith,,Johann Michael Maucher|I. C. Schefl,"German, Schwäbisch Gmünd, 1645–1701|German, Graz, 17th century",,"Maucher, Johann Michael|Schefl, I. C.",German|German,1670 |1601,1701 |1700,ca. 1685,1660,1710,"Steel, pearwood, ivory, mother-of-pearl",L. 43 1/2 in. (110.5 cm); L. of barrel 30 1/4 in. (76.8 cm); Cal. .50 in. (12.7 mm); L. of lock 8 1/16 in. (20.5 cm); L. of stock 43 9/16 in. (110.6 cm); Wt. 8 lb. 10 oz. (3912 g),"Fletcher Fund, 1928",,Schwäbisch Gmünd,,,,,,,,,,Firearms-Guns-Wheellock,,http://www.metmuseum.org/art/collection/search/23045,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.25.1200,false,true,22369,Arms and Armor,Rapier,Rapier,"probably French, Paris",,,,,Hilt Maker|Bladesmith,Hilt signed,Bouqueton|Johannes Wundes,"French, active early 17th century|German, Solingen, ca. 1560–1610",,"Bouqueton|Wundes, Johannes",French|German,1600 |1535,1650 |1635,ca. 1610–20,1585,1645,"Steel, silver, wood",L. 48 1/8 in. (122.2 cm); L. of blade 41 1/2 in. (105.4 cm); W. 9 1/2 in. (24.1 cm); Wt. 3 lb. 6 oz. (1530.9 g),"Gift of William H. Riggs, 1913",,probably Paris,,,,,,,,,,Swords,,http://www.metmuseum.org/art/collection/search/22369,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -38.143b–d,false,true,24660,Arms and Armor,Cuirass and tassets (torso and hip defense),Cuirass and Tassets (Torso and Hip Defense),"German, Augsburg",,,,,Armorer|Decorator,Attributed to|Etching attributed to,Kolman Helmschmid|Daniel Hopfer,"German, Augsburg 1471–1532|German, Kaufbeuren 1471–1536 Augsburg",,"Helmschmid, Kolman|Hopfer, Daniel",German|German,1471 |1471,1532 |1536,ca. 1510–20,1485,1545,"Steel, leather",H. 41 1/2 in. (105.4 cm); Wt. 19 lb. 8 oz. (8845 g),"Gift of Marshall Field, 1938",,Augsburg,,,,,,,,,,Armor,,http://www.metmuseum.org/art/collection/search/24660,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.158.668,false,true,23339,Arms and Armor,Wheellock rifle,Wheellock Rifle,"German, Munich and Augsburg",,,,,Stock maker|Steel-chiseler,,Elias Becker|Caspar Spät,"German, Augsburg, recorded 1633–74|German, Munich, ca. 1611–1691",,"Becker, Elias|Spät, Caspar",German|German,1633 |1586,1674 |1716,dated 1668,1668,1668,"Steel, gold, wood (ebony), staghorn, bone",L. 30 9/16 in. (77.6 cm); L. of barrel 30 9/16 in. (77.6 cm); Cal. .55 in. (13.9 mm); Wt. 7 lb. 14 oz. (3572 g),"Bashford Dean Memorial Collection, Funds from various donors, 1929",,Munich and Augsburg,,,,,,,,,,Firearms-Guns-Wheellock,,http://www.metmuseum.org/art/collection/search/23339,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -04.3.180,false,true,21968,Arms and Armor,Wheellock rifle,Wheellock Rifle,"German, Munich and Augsburg",,,,,Steel-chiseler|Stock maker,,Caspar Spät|Elias Becker,"German, Munich, ca. 1611–1691|German, Augsburg, recorded 1633–74",,"Spät, Caspar|Becker, Elias",German|German,1586 |1633,1716 |1674,ca. 1640–50,1615,1675,"Steel, gold, fruitwood, staghorn, bone",L. 41 7/8 in. (106.4 cm); L. of barrel 30 9/16 in. (77.6 cm); Cal. .46 in. (11.7 mm); Wt. 7 lb. 6 oz. (3350 g),"Rogers Fund, 1904",,Munich and Augsburg,,,,,,,,,,Firearms-Guns-Wheellock,,http://www.metmuseum.org/art/collection/search/21968,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -16.135,false,true,22626,Arms and Armor,Miquelet flintlock gun,Miquelet Flintlock Gun Made for Charles IV of Spain (reigned 1788–1808),"Spanish, Eibar",,,,,Barrelsmith|Lock maker,,Antonio Guisasola|Juan Navaro,"Spanish, Eibar, recorded 1796–1833|Spanish, Eibar, active ca. 1800",,"Guisasola, Antonio|Navaro, Juan",Spanish|Spanish,1796 |1775,1833 |1825,dated 1796,1796,1796,"Steel, gold, silver, wood (walnut)",L. 48 1/2 in. (123.2 cm); L. of barrel 34 in. (86.4 cm); Cal. .68 in. (17.3 mm); Wt. 5 lb. 6 oz. (2438 g),"Rogers Fund, 1916",,Eibar,,,,,,,,,,Firearms-Guns-Miquelet,,http://www.metmuseum.org/art/collection/search/22626,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -57.118,false,true,27568,Arms and Armor,Fencing book,"Gründtliche Beschreibung der freyen ritterlichen unnd adelichen Kunst des Fechtens in allerley gebreuchlichen Wehren mit vil schönen und nützlichen Figuren gezieret und fürgestellet (A Thorough Description of the Free Knightly and Noble Art of Fencing, in All the Typical Guards, Adorned and Arranged with Many Beautiful and Useful Figures)","German, Strasbourg",,,,,Author|Artist,Woodcuts by,Joachim Meyer|Tobias Stimmer,"German, active 16th–17th century|German, active 16th–17th century",,"Meyer, Joachim|Stimmer, Tobias",German|German,1550 |1550,1650 |1650,dated 1570,1570,1570,"Ink, paper",,"Gift of Christian A. Zabriskie, 1957",,Strassburg,,,,,,,,,,Books & Manuscripts,,http://www.metmuseum.org/art/collection/search/27568,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"19.131.1a–r, t–w, .2a–f, l",false,true,22741,Arms and Armor,Armor garniture,"Armor Garniture, Probably of King Henry VIII of England (reigned 1509–47)","British, Greenwich",,,,,Designer|Armorer,Design of the decoration attributed to|Made in the,Hans Holbein the Younger|Royal Workshops at Greenwich,"German, Augsburg 1497/98–1543 London|British, Greenwich, 1511–1640s",,"Holbein, Hans, the Younger|Royal Workshops at Greenwich",German|British,1497 |1511,1543 |1650,dated 1527,1527,1527,"Steel, gold, leather, copper alloys",H. 73 in. (185.4 cm); Wt. 62 lb. 12 oz. (28.45 kg),"Purchase, William H. Riggs Gift and Rogers Fund, 1919",,Greenwich,,,,,,,,,,Armor for Horse and Man,,http://www.metmuseum.org/art/collection/search/22741,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.77,true,true,24860,Arms and Armor,Rapier,Rapier of Prince-Elector Christian II of Saxony (1583–1611),"hilt, German, Dresden; blade, Spanish, Toledo",,,,,Sword cutler|Bladesmith,Hilt by|Blade by,Israel Schuech|Juan Martinez,"German, Dresden, active ca. 1590–1610|Spanish, Toledo, active ca. 1600",,"Schuech, Israel|Martinez, Juan",German|Spanish,1565 |1575,1635 |1625,dated 1606,1606,1606,"Steel, bronze, gold, enamel, paste jewels, cameos, pearls, wood",L. 48 in. (121.9 cm); L. of blade 41 1/4 in. (104.8 cm); W. 6 3/4 in. (17.2 cm); Wt. 3 lb. 4 oz. (1474 g),"Fletcher Fund, 1970",,Dresden|Toledo,,,,,,,,,,Swords,,http://www.metmuseum.org/art/collection/search/24860,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.25.1397,false,true,22379,Arms and Armor,Wheellock rifle,Wheellock Rifle,"German, Munich",,,,,Steel-chiseler|Stock maker,,Daniel Sadeler|Hieronymus Borstorffer,"German, Munich, recorded 1602–1632|German, Munich, recorded 1597–1637",,"Sadeler, Daniel|Borstorffer, Hieronymus",German|German,1602 |1597,1632 |1637,ca. 1610–30,1585,1655,"Steel, gold, wood (ebony, fruitwood), ivory, horn",L. 42 3/8 in. (107.6 cm); L. of barrel 30 7/8 in. (78.4 cm); Cal. .56 in. (14.2 mm); Wt. 8 lb. 13 oz. (4000 g),"Gift of William H. Riggs, 1913",,Munich,,,,,,,,,,Firearms,,http://www.metmuseum.org/art/collection/search/22379,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.25.1396,false,true,22378,Arms and Armor,Wheellock rifle,Wheellock Rifle,"German, Munich",,,,,Steel-chiseler|Stock maker,,Daniel Sadeler|Hieronymus Borstorffer,"German, Munich, recorded 1602–1632|German, Munich, recorded 1597–1637",,"Sadeler, Daniel|Borstorffer, Hieronymus",German|German,1602 |1597,1632 |1637,ca. 1610–30,1585,1655,"Steel, gold, wood (fruitwood), ivory, horn",L. 43 5/8 in. (110.8 cm); L. of barrel 31 15/16 in. (81.1 cm); Cal..56 in. (14.2 mm); Wt. 7 lb. 10 oz. (3450 g),"Gift of William H. Riggs, 1913",,Munich,,,,,,,,,,Firearms-Guns-Wheellock,,http://www.metmuseum.org/art/collection/search/22378,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -32.109.5,false,true,32216,Arms and Armor,Waist lame,Waist Lame,"German, Landshut",,,,,Armorer|Armorer,Attributed to|Attributed to,Wolfgang Grosschedel|Franz Grosschedel,"German, Landshut, active ca. 1517–62|German, Landshut, recorded 1555–79",,"Grosschedel, Wolfgang|Grosschedel, Franz",German|German,1517 |1555,1562 |1579,ca. 1555–60,1530,1585,Steel,H. 2 1/2 in. (6.35 cm); W. 13 in. (33.0 cm); Wt. 5 oz. (141.7 g),"Rogers Fund, 1932",,Landshut,,,,,,,,,,Armor Parts,,http://www.metmuseum.org/art/collection/search/32216,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -32.109.6,false,true,32217,Arms and Armor,Waist lame,Waist Lame,"German, Landshut",,,,,Armorer|Armorer,Attributed to|Attributed to,Wolfgang Grosschedel|Franz Grosschedel,"German, Landshut, active ca. 1517–62|German, Landshut, recorded 1555–79",,"Grosschedel, Wolfgang|Grosschedel, Franz",German|German,1517 |1555,1562 |1579,ca. 1555–60,1530,1585,Steel,H. 2 1/2 in. (6.4 cm); W. 13 in. (33 cm); Wt. 3 oz. (85 g),"Rogers Fund, 1932",,Landshut,,,,,,,,,,Armor Parts,,http://www.metmuseum.org/art/collection/search/32217,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"32.109.1a, b",false,true,32212,Arms and Armor,Right shoulder and arm defense,Right Shoulder and Arm Defense,"German, Landshut",,,,,Armorer|Armorer,Attributed to|Attributed to,Wolfgang Grosschedel|Franz Grosschedel,"German, Landshut, active ca. 1517–62|German, Landshut, recorded 1555–79",,"Grosschedel, Wolfgang|Grosschedel, Franz",German|German,1517 |1555,1562 |1579,ca. 1555–60,1530,1585,"Steel, leather, copper alloy",Shoulder defense (a); H. 8 1/2 in. (21.6 cm); W. 9 in. (22.9 cm); D. 8 3/4 in. (22.2 cm); Wt. 34.1 oz. (966.7 g); shoulder defense (a) and arm defense (b); H. approx. 28 in. (71.1 cm); W. approx. 10 in. (25.4 cm); Wt. 5 lb. 9 oz. (2523.1 g),"Rogers Fund, 1932",,Landshut,,,,,,,,,,Armor Parts,,http://www.metmuseum.org/art/collection/search/32212,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"32.109.2a, b",false,true,32213,Arms and Armor,Left shoulder and arm defense,Left Shoulder and Arm Defense,"German, Landshut",,,,,Armorer|Armorer,Attributed to|Attributed to,Wolfgang Grosschedel|Franz Grosschedel,"German, Landshut, active ca. 1517–62|German, Landshut, recorded 1555–79",,"Grosschedel, Wolfgang|Grosschedel, Franz",German|German,1517 |1555,1562 |1579,ca. 1555–60,1530,1585,Steel,L. approx. 28 in. (71.1 cm); W. approx. 10 in. (25.4 cm); Wt. 5 lb. 6 oz. (2438.1 g),"Rogers Fund, 1932",,Landshut,,,,,,,,,,Armor Parts,,http://www.metmuseum.org/art/collection/search/32213,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -32.109.1–.6,false,true,690153,Arms and Armor,Elements of an armor garniture,Elements of an Armor Garniture,"German, Landshut",,,,,Armorer|Armorer,Attributed to|Attributed to,Wolfgang Grosschedel|Franz Grosschedel,"German, Landshut, active ca. 1517–62|German, Landshut, recorded 1555–79",,"Grosschedel, Wolfgang|Grosschedel, Franz",German|German,1517 |1555,1562 |1579,ca. 1555–60,1530,1585,Steel,"right shoulder and arm defense (32.109.1a, b): H. approx. 28 in. (71.1 cm); W. approx. 10 in. (25.4 cm); Wt. 5 lb. 9 oz. (2523.1 g); left shoulder and arm defense (32.109.2a, b): L. approx. 28 in. (71.1 cm); W. approx. 10 in. (25.4 cm); Wt. 5 lb. 6 oz. (2438.1 g); right thigh and knee defense (32.109.3a–c): L. 20 in. (50.8 cm); W. 8 1/2 in. (21.6 cm); Wt. 3 lb. 3 oz. (1445.8 g); falling buffe (32.109.4): H. 7 1/2 in. (19.1 cm); W. 6 1/2 in. (16.5 cm); Wt. 1 lb. 1 oz. (481.9 g); waist lame (32.109.5): H. 2 1/2 in. (6.35 cm); W. 13 in. (33.0 cm); Wt. 5 oz. (141.7 g); waist lame (32.109.6): H. 2 1/2 in. (6.4 cm); W. 13 in. (33 cm); Wt. 3 oz. (85 g)","Rogers Fund, 1932",,Landshut,,,,,,,,,,Armor Parts,,http://www.metmuseum.org/art/collection/search/690153,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -04.3.278,false,true,21998,Arms and Armor,Composed armor,Composed Armor,"German, Landshut and Augsburg; gorget, Italian",,,,,Armorer|Armorer,"Helmet, arm defenses, gauntlets and leg defenses by|Helmet, arm defenses, gauntlets and leg defenses by",Wolfgang Grosschedel|Franz Grosschedel,"German, Landshut, active ca. 1517–62|German, Landshut, recorded 1555–79",,"Grosschedel, Wolfgang|Grosschedel, Franz",German|German,1517 |1555,1562 |1579,ca. 1550–80,1525,1605,"Steel, gold, leather, textile, copper alloy",Wt. 51 lb. 4 oz. (23.25 kg),"Rogers Fund, 1904",,Landshut|Augsburg,,,,,,,,,,Armor for Man,,http://www.metmuseum.org/art/collection/search/21998,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -32.109.4,false,true,32215,Arms and Armor,Falling buffe for a helmet,Falling Buffe for a Helmet,"German, Landshut",,,,,Armorer|Armorer,Attributed to|Attributed to,Wolfgang Grosschedel|Franz Grosschedel,"German, Landshut, active ca. 1517–62|German, Landshut, recorded 1555–79",,"Grosschedel, Wolfgang|Grosschedel, Franz",German|German,1517 |1555,1562 |1579,ca. 1555–60,1530,1585,Steel,H. 7 1/2 in. (19.1 cm); W. 6 1/2 in. (16.5 cm); Wt. 1 lb. 1 oz. (481.9 g),"Rogers Fund, 1932",,Landshut,,,,,,,,,,Helmets Parts,,http://www.metmuseum.org/art/collection/search/32215,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -32.109.3a–c,false,true,32214,Arms and Armor,Right thigh and knee defense,Right Thigh and Knee Defense,"German, Landshut",,,,,Armorer|Armorer,Attributed to|Attributed to,Wolfgang Grosschedel|Franz Grosschedel,"German, Landshut, active ca. 1517–62|German, Landshut, recorded 1555–79",,"Grosschedel, Wolfgang|Grosschedel, Franz",German|German,1517 |1555,1562 |1579,ca. 1555–60,1530,1585,Steel,L. 20 in. (50.8 cm); W. 8 1/2 in. (21.6 cm); Wt. 3 lb. 3 oz. (1445.8 g),"Rogers Fund, 1932",,Landshut,,,,,,,,,,Armor Parts-Knee Defenses,,http://www.metmuseum.org/art/collection/search/32214,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"04.3.278; 22.147.6, .7",false,true,35774,Arms and Armor,Composed armor,Composed Armor,"German, Landshut and Augsburg; gorget, Italian",,,,,Armorer|Armorer,"Helmet, arm defenses, gauntlets and leg defenses by|Helmet, arm defenses, gauntlets and leg defenses by",Franz Grosschedel|Wolfgang Grosschedel,"German, Landshut, recorded 1555–79|German, Landshut, active ca. 1517–62",,"Grosschedel, Franz|Grosschedel, Wolfgang",German|German,1555 |1517,1579 |1562,ca. 1550–80,1525,1605,"Steel, gold, leather, textile, copper alloy",Wt. 51 lb. 4 oz. (23.25 kg),"Rogers Fund, 1904; gauntlets: Fletcher Fund, 1922",,Landshut|Augsburg,,,,,,,,,,Armor for Man,,http://www.metmuseum.org/art/collection/search/35774,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.25.1425,true,true,22387,Arms and Armor,Double-barreled wheellock pistol,Double-Barreled Wheellock Pistol Made for Emperor Charles V (reigned 1519–56),"German, Munich",,,,,Etcher|Gunsmith,,Ambrosius Gemlich|Peter Peck,"German, Munich and Landshut, active ca. 1520–50|German, Munich, 1503–1596",,"Gemlich, Ambrosius|Peck, Peter",German|German,1495 |1503,1575 |1596,ca. 1540–45,1515,1570,"Steel, gold, wood (cherry), staghorn",L. 19 3/8 in. (49.2 cm); L. of upper barrel 10 in. (25.4 cm); L. of lower barrel 7 5/8 in. (19.4 cm); Cal. of each barrel .46 in. (11.7 mm); Wt. 5 lb. 10 oz. (2550 g),"Gift of William H. Riggs, 1913",,Munich,,,,,,,,,,Firearms-Pistols-Wheellock,,http://www.metmuseum.org/art/collection/search/22387,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"14.25.1398a, b",false,true,22380,Arms and Armor,Pair of wheellock pistols,Pair of Wheellock Pistols Made for the Bodyguard of the Prince-Elector of Saxony,"German, Dresden",,,,,Stock maker|Gunsmith,,Hans Fleischer|Simon Helbig,"German, Dresden, recorded 1590–ca. 1625|German, Dresden, recorded 1609–1642",,"Fleischer, Hans|Helbig, Simon",German|German,1590 |1609,1650 |1642,ca. 1610,1585,1635,"Steel, gold, brass, staghorn, wood (beech)",L. of each pistol 29 1/8 in. (73.9 cm); L. of each barrel 8 3/4 in. (47.6 cm); Cal. of each pistol .57 in. (14.5 mm); Wt. of each pistol 4 lb. 5 oz. (1956 g),"Gift of William H. Riggs, 1913",,Dresden,,,,,,,,,,Firearms-Pistols-Wheellock,,http://www.metmuseum.org/art/collection/search/22380,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.154.5,false,true,29594,Arms and Armor,Miquelet pistol,Miquelet Pistol,"Spanish, Barcelona",,,,,Gunsmith|Gunsmith,,Llorens Torrens|Jacinto Jaumeandreu,"Spanish, Catalonia, active 1793–1824|Spanish, Catalonia, active about 1750–1807",,"Torrens, Llorens|Jaumeandreu, Jacinto",Spanish|Spanish,1793 |1725,1824 |1832,ca. 1790–1807,1765,1832,"Steel, gold, wood (walnut)","Caliber, .665 in, (16.89 mm) Weight, 1 lb. 12 oz. (800 g) Length, 14 11/16 in. (37.31 cm) Length of barrel, 8 13/16 in. (22.38 cm)","Roger's Fund, 1937",,Barcelona,,,,,,,,,,Firearms-Pistols,,http://www.metmuseum.org/art/collection/search/29594,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.25.621,false,true,26505,Arms and Armor,Helmet,Close Helmet for a Boy,"German, Augsburg",,,,,Armorer|Armorer|Decorator,Possibly attributed to|Possibly attributed to|Etching in the style of,Kolman Helmschmid|Desiderius Helmschmid|Daniel Hopfer,"German, Augsburg 1471–1532|German, Augsburg, 1513–1579|German, Kaufbeuren 1471–1536 Augsburg",,"Helmschmid, Kolman|Helmschmid, Desiderius|Hopfer, Daniel",German|German|German,1471 |1513 |1471,1532 |1579 |1536,ca. 1530–40,1505,1565,"Steel, leather, copper alloy",H. 11 in. (27.9 cm); W. 7 3/4 in. (19.7 cm); D. 11 in. (27.9 cm); Wt. 4 lb. 4 oz. (1921 g),"Gift of William H. Riggs, 1913",,Augsburg,,,,,,,,,,Helmets,,http://www.metmuseum.org/art/collection/search/26505,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -04.3.218,false,true,21980,Arms and Armor,Reinforce for a helmet (Gupfe),Reinforce for a Helmet (Gupfe),"German, Augsburg",,,,,Armorer|Etcher|Armorer,,Mattheus Frauenpreis the Elder|Jörg Sorg the Younger|Mattheus Frauenpreis the Younger,"German, Augsburg, recorded 1510–49|German, Augsburg, ca. 1522–1603|German, Augsburg, 1530–1604",,"Frauenpreis the Elder, Mattheus|Sorg the Younger, Jörg|Frauenpreis the Younger, Mattheus",German|German|German,1529 |1500 |1530,1549 |1625 |1604,1549–50,1549,1550,"Steel, gold",H. 5 5/8 in. (14.3 cm); W. 8 11/16 in. (22.1 cm); D. 9 1/8 in. (23.2 cm); Wt. 1 lb. 5 oz. (595 g),"Rogers Fund, 1904",,Augsburg,,,,,,,,,,Helmets,,http://www.metmuseum.org/art/collection/search/21980,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -68.157.1,false,true,24854,Arms and Armor,Percussion revolver,"Colt Model 1849 Pocket Percussion Revolver, Serial no. 81015","American, Hartford, Connecticut",,,,,Engraver|Manufacturer,,Gustave Young|Samuel Colt,"American (born Prussia), 1827–1895 Springfield, Massachusetts|American, Hartford, Connecticut 1814–1862",,"Young, Gustave|Colt, Samuel","American, born Prussia|American",1827 |1814,1895 |1862,ca. 1853,1828,1878,"Steel, brass, silver, ivory",L. of barrel 6 in. (15.24 cm); Cal. .32 in. (8 mm),"Gift of John E. Parsons, 1968",,Hartford,Connecticut,,,,,,,,,Firearms-Pistols-Revolvers,,http://www.metmuseum.org/art/collection/search/24854,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2010.481a–l,false,true,35690,Arms and Armor,Percussion revolver with case and accessories,"Colt Model 1851 Navy Percussion Revolver, Serial Number 29705, with Case and Accessories","American, Hartford, Connecticut",,,,,Engraver|Manufacturer,Engraved by,Gustave Young|Samuel Colt,"American (born Prussia), 1827–1895 Springfield, Massachusetts|American, Hartford, Connecticut 1814–1862",,"Young, Gustave|Colt, Samuel","American, born Prussia|American",1827 |1814,1895 |1862,ca. 1853–54,1828,1879,"Steel, brass, silver, wood, copper, tin, lead, paper",L. of pistol 13 in. (33 cm); L. of barrel 7 1/2 in. (19 cm); Cal. .36 in. (9 mm); case: 14 1/4 x 6 3/8 x 2 1/4 in. (36.2 x 16.2 x 5.6 cm),"Gift of Jack Sayre, 2010",,Hartford,Connecticut,,,,,,,,,Firearms-Pistols-Percussion,,http://www.metmuseum.org/art/collection/search/35690,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -42.50.46,false,true,24708,Arms and Armor,Smallsword hilt,Smallsword Hilt,"British, possibly Birmingham",,,,,Hilt Maker|Hilt Maker,,Matthew Boulton|Josiah Wedgwood,"British, Birmingham 1728–1809 Birmingham|British, Burslem, Stoke-on-Trent 1730–1795 Burslem, Stoke-on-Trent",,"Boulton, Matthew|Wedgwood, Josiah",British|British,1728 |1730,1809 |1795,ca. 1790,1765,1815,"Steel, Wedgwood jasperware",L. 6 7/8 in. (17.5 cm); W. 4 1/2 in. (11.4 cm),"Gift of Stephen V. Grancsay, 1942",,possibly Birmingham,,,,,,,,,,Swords,,http://www.metmuseum.org/art/collection/search/24708,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1985.264a–r,false,true,24921,Arms and Armor,Revolver with case and accessories,"Cased Colt Model 1862 Police Revolver, Serial no. 9174, with Thuer Conversion for Self-contained Cartridges, and Accessories","American, Hartford, Connecticut and New York",,,,,Manufacturer|Steel-chiseler|Maker,Decoration attributed to|Case made by,"Samuel Colt|Louis D. Nimschke|Schuyler, Hartley and Graham","American, Hartford, Connecticut 1814–1862|American, New York, active ca. 1850–1900|American, New York, 19th century",,"Colt, Samuel|Nimschke, Louis D.|Schuyler, Hartley and Graham",American|American|American,1814 |1825 |1801,1862 |1925 |1900,1862,1862,1862,"Steel, gold, silver, brass, wood (rosewood), textile",Cal. .36 in. (9 mm); case 15 x 9 in. (38 x 23 cm),"Gift of Mr. and Mrs. Jerry D. Berger, 1985",,Hartford|New York,Connecticut|New York,,,,,,,,,Firearms-Pistols-Revolvers,,http://www.metmuseum.org/art/collection/search/24921,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -42.50.7a–n,false,true,24684,Arms and Armor,Double-barreled flintlock shotgun with exchangeable percussion locks and barrels,Double-Barreled Flintlock Shotgun with Exchangeable Percussion Locks and Barrels,"French, Versailles and Paris",,,,,Barrelsmith|Gunsmith|Lock maker|Retailer,Exchangeable percussion barrels by|Exchangeable percussion locks by|Exchangeable percussion locks and barrels originally sold by,Léopold Bernard|Nicolas Noël Boutet|B. Montagnon|G. Zaoué,"French, Paris, active 1832–70|French, Versailles and Paris, 1761–1833|French, active ca. 1850|French, active 1857–61",,"Bernard, Léopold|Boutet, Nicholas-Noël|Montagnon, B.|Zaoué, G.",French|French|French|French,1832 |1761 |1825 |1857,1870 |1833 |1875 |1861,"ca. 1818–20; exchangeable percussion locks and barrels, dated 1860",1793,1860,"Steel, gold, wood (walnut), silver, horn",L. of gun 47 3/8 in. (120.3 cm); L. of double barrel 31 7/8 in. (81.0 cm); L. of percussion locks 5 in. (12.7 cm); L. of bullet pouch 7 1/4 in. (18.4 cm); L. of priming flask 6 5/8 in. (16.8 cm); L. of bullet mold 6 3/4 in. (17.1 cm); L. of ramrod extension 10 3/8 in. (26.4 cm); L. of hammer extractor 2 3/16 in. (5.6 cm); L. of brush for cleaning barrel 2 13/16 in. (7.1 cm); L. of bullet extractor 1 5/16 in. (3.3 cm); L. of screwdriver 6 in. (15.2 cm); L. of bullet extractor 3 1/8 in. (7.9 cm); L. of touch hole cleaner 3 3/16 in. (8.1 cm); Cal. .61 in. (15.5 mm); Wt. of gun 6 lbs. 15 oz. (3150 g); Wt. of double barrel 3 lbs. 8 oz. (1600 g); Wt. of percussion locks 5 oz. (150 g); Wt. of bullet pouch 2.5 oz. (71 g); Wt. of priming flask 4.6 oz. (131 g); Wt. of bullet mold 7 oz. (198 g),"Gift of Stephen V. Grancsay, 1942",,Versailles and Paris,,,,,,,,,,Firearms,,http://www.metmuseum.org/art/collection/search/24684,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1987.274,false,true,24929,Arms and Armor,Flintlock gun,Flintlock Gun,"French, Saint-Etienne",,,,,Steel-chiseler|Barrelsmith|Stock maker,,Louis Jaley|Nicolas Carteron|Joseph Blachon,"French, Saint-Étienne 1696–1773|French, Saint-Étienne, recorded ca. 1733–63|French, Saint-Étienne, recorded ca. 1725–35",,"Jaley, Louis|Carteron, Nicolas|Blachon Joseph",French|French|French,1696 |1708 |1700,1773 |1788 |1760,dated 1735,1735,1735,"Steel, gold, wood, silver",L. 57 1/2 in. (146.1 cm); L. of barrel 41 7/8 in. (106.5 cm); caliber .62 in. (15.5 cm),"Harris Brisbane Dick and Rogers Funds, 1987",,Saint-Etienne,,,,,,,,,,Firearms,,http://www.metmuseum.org/art/collection/search/24929,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"14.25.1661b;29.150.5,.70,.80;29.156.66k...",false,true,35789,Arms and Armor,Composite armor,Composite Armor,"Austrian, Innsbruck and Mühlau",,,,,Armorer|Armorer|Armorer,Rondel by|Sallet by|Breastplate and backplate by,Kaspar Riederer|Jörg Wagner|Hans Prunner,"Austrian, Innsbruck and Mühlau, active 1455–99|Austrian, Innsbruck, recorded 1485–92|Austrian, Innsbruck, recorded 1482–99",,"Riederer, Kaspar|Wagner, Jörg|Prunner, Hans",Austrian|Austrian|Austrian,1455 |1485 |1482,1499 |1492 |1499,comprehensively ca. 1485–95,1460,1520,Steel,,"Rondel (14.25.1661b): Gift of William H. Riggs, 1913; sallet, backplate, breastplate (29.150.5a, .70, .80): Bashford Dean Memorial Collection, Bequest of Bashford Dean, 1928; couter (29.156.66k): Bashford Dean Memorial Collection, Gift of Edward S. Harkness, 1929; gauntlet (29.158.255b): Bashford Dean Memorial Collection, Funds from various donors, 1929",,Innsbruck and Mühlau,,,,,,,,,,Armor for Horse and Man,,http://www.metmuseum.org/art/collection/search/35789,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2012.162,false,true,35833,Arms and Armor,Cup-hilted rapier,Cup-Hilted Rapier,"Italian, Milan",,,,,Hilt Maker|Bladesmith|Bladesmith,Attributed to|Attributed to,Francesco Maria Rivolta|Francisco Ruiz the Elder|Francisco Ruiz the Younger,"Italian, Milan, active second half of 17th century|Spanish, Toledo, died after 1617|Spanish, Toledo, active first half of 17th century",", or his son.|, or his father.","Rivolta, Francesco Maria|Ruiz the Elder, Francisco|Ruiz the Younger, Francisco","Italian, Milan|Spanish, Toledo|Spanish, Toledo",1650 |1550 |1600,1700 |1650 |1650,ca. 1670,1645,1695,"Steel, iron wire, wood, textile (felt)",L. 44 5/16 in. (112.5 cm); W. 10 1/8 in. (25.7 cm); D. 5 in. (12.7 cm); Wt. 1 lb 12 oz. (795 g),"Purchase, Arthur Ochs Sulzberger Gift, 2012",,Milan,,,,,,,,,,Swords,,http://www.metmuseum.org/art/collection/search/35833,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2011.486,false,true,35796,Arms and Armor,Engraving,"Two Armorial Trophies marked by the incorporation of a Broken Amphora on the Left, Plate 2 from the Libro de' Trofei","Italian, Rome",,,,,Artist|Former Attribution|Artist|Publisher,,"Anonymous, Italian|Enea Vico|Polidoro da Caravaggio|Antonio Lafreri","Italian, Parma 1523–1567 Ferrara|Italian, Caravaggio ca. 1499–ca. 1543 Messina|French, Orgelet, Franche-Comte ca. 1512–1577 Rome",", 16th century","Anonymous, Italian|Vico, Enea|Caravaggio, Polidoro da|Lafreri, Antonio",Italian|Italian|French,1400 |1523 |1494 |1507,2050 |1567 |1548 |1577,ca. 1550–53,1545,1555,Engraving,9 3/4 x 6 15/16 in. (24.8 x 17.6 cm),"Bequest of Stephen V. Grancsay, 1980",,Rome|Rome,,,,,,,,,,Works on Paper-Prints,,http://www.metmuseum.org/art/collection/search/35796,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -58.171.1,false,true,24844,Arms and Armor,Percussion revolver,"Colt Walker Percussion Revolver, serial no. 1017","American, Whitneyville, Connecticut",,,,,Designer|Engraver|Designer|Manufacturer,,Samuel Colt|Waterman Lilly Ormsby|Captain Samuel Hamilton Walker|Eli Whitney Jr.,"American, Hartford, Connecticut 1814–1862|American, Hampton, Connecticut 1809–1883 Brooklyn, New York|American, died 1847|American, 1820–1888",,"Colt, Samuel|Ormsby, Waterman Lilly|Walker, Samuel Hamilton Captain|Whitney, Eli Jr.",American|American|American|American,1814 |1809 |1747 |1820,1862 |1883 |1847 |1888,1847,1847,1847,"Steel, brass, wood (walnut)",L. 15 1/2 in. (39.37 cm); L. of barrel 9 in. (22.86 cm); Cal. .44 in. (11 mm),"Gift of John E. Parsons, 1958",,Whitneyville,Connecticut,,,,,,,,,Firearms-Pistols-Revolvers,,http://www.metmuseum.org/art/collection/search/24844,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"26.259.5, .6",false,true,22962,Arms and Armor,Pair of flintlock pistols,"Pair of Flintlock Pistols Made for Ferdinand IV, King of Naples and Sicily (1751–1825)","Italo-Spanish, Naples",,,,,Manufacturer|Gunsmith|Barrelsmith,,Royal Arms Manufactory at Torre Annunziata|Michele Battista|Emanuel Esteva,"Italian, Naples, established 1757|Spanish, active in Naples, Italy, recorded about 1760–90|Spanish, active in Naples, Italy, recorded about 1768–73",,"Royal Arms Manufactory at Torre Annunziata|Battista, Michele|Esteva, Emanuel",Italian|Spanish|Spanish,1757 |1735 |1743,1900 |1815 |1798,ca. 1768,1743,1793,"Steel, gold, wood (walnut), silver",L. of each 17 3/8 in. (44.1 cm); L. of each barrel 11 1/16 in. (28.1 cm); Cal. .63 in. (16.0 mm); Wt. of each 2 lb. 4 oz. (1021 g),"Gift of Henry Walters, 1926",,Naples,,,,,,,,,,Firearms-Pistols,,http://www.metmuseum.org/art/collection/search/22962,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"28.196.3, .4",false,true,23051,Arms and Armor,Pair of pistols,Pair of Pistols with Flintlocks a Las Tres Modas,"Spanish, Eibar",,,,,Gunsmith,,Workshop of the Ybarzabel family,"Spanish, Eibar, recorded 1784–1891",,Workshop of the Ybarzabel family,Spanish,1784,1891,late 18th century,1750,1800,"Steel, gold, wood (walnut)",L. of each 11 in. (27.9 cm); L. of each barrel 6 5/8 in. (16.8 cm); Cal. of each .61 in. (15.5 mm); Wt. of each 1 lb. 5 oz. (600 g),"Rogers Fund, 1928",,Eibar,,,,,,,,,,Firearms-Pistols,,http://www.metmuseum.org/art/collection/search/23051,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -19.53.87,false,true,22736,Arms and Armor,Powder flask,Powder Flask,"Spanish, Madrid",,,,,Maker,,Joseph Cano,"Spanish, Madrid, documented 1733–died 1751",,"Cano, Joseph",Spanish,1733,1751,ca. 1740–50,1715,1775,"Tortoiseshell, steel, gold",L. 9 1/2 in. (24.1 cm),"Gift of Charles M. Schott Jr., 1917",,Madrid,,,,,,,,,,Firearms Accessories-Flasks & Primers,,http://www.metmuseum.org/art/collection/search/22736,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -04.3.36,true,true,21940,Arms and Armor,Crossbow,Crossbow of Count Ulrich V of Württemberg (1413–1480),"German, probably Stuttgart",,,,,Maker,Attributed to,Heinrich Heid von Winterthur,"probably Swiss, active Stuttgart, recorded 1453–1460",,"von Winterthur, Heinrich Heid",probably Swiss,1453,1460,dated 1460,1460,1460,"Wood (European hornbeam), horn, animal sinew, staghorn, birch bark, iron alloy, copper alloy, pigment",L. 28 1/16 in. (71.2 cm); W. 25 5/8 in. (65 cm); Wt. 6 lb. 9 oz. (2972 g),"Rogers Fund, 1904",,probably Stuttgart,,,,,,,,,,Archery Equipment-Crossbows,,http://www.metmuseum.org/art/collection/search/21940,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -22.150,false,true,35654,Arms and Armor,Portrait,"Cosimo II de' Medici (1590–1621), Grand Duke of Tuscany",Flemish,,,,,Artist,Workshop of,Justus Sustermans,"Flemish, Antwerp 1597–1681 Florence",,"Sustermans, Justus",Flemish,1597,1681,1597–1681,1597,1681,"Oil on canvas, transferred from wood",78 x 48 in. (198.1 x 121.9 cm),"Gift of Bashford Dean, 1922",,,,,,,,,,,,Miscellaneous-Paintings & Portraits,,http://www.metmuseum.org/art/collection/search/35654,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.590,false,true,35962,Arms and Armor,Album of bit designs,Album of Bit Designs,Bohemian,,,,,Designer,,Rudolf Franz Ferdinand von Talmberg,"Bohemian, ca. 1645–1702",,"Talmberg, Rudolf Franz Ferdinand von",Bohemian,1620,1727,1674,1674,1674,Ink on paper,Covers: 14 1/2 x 9 3/4 in. (36.8 x 24.8 cm); sheets: 14 1/4 x 9 1/2 in. (36.2 x 24.1 cm); plates: approx. 12 1/4 x 4 in. (31.5 x 10 cm),"Purchase, The Elisha Whittelsey Collection, The Elisha Whittelsey Fund; Joseph M. Scheuner and Kenneth and Vivian Lam Gifts; and Bequest of Stephen V. Grancsay, Rogers Fund, Helmut Nickel Gift, and funds from various donors, by exchange, 2013",,,,,,,,,,,,Works on Paper-Prints,,http://www.metmuseum.org/art/collection/search/35962,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -26.261.14,false,true,27937,Arms and Armor,Presentation coin,Presentation Coin (Doppelguldiner) Showing Maximilian I (1459–1519),"Flemish, Antwerp; dies cut in Hall, Austria",,,,,Designer,,Ulrich Ursentaler,"Austrian, Hall, recorded 1508–35, Master of the Mint (Münz–Weister) at Hall in 1535",,Ursentaler Ulrich,Austrian,1508,1535,"minted, 1517; dies cut and dated, 1509",1509,1517,Silver,Diam. 2 1/8 in. (5.4 cm); thickness 1/8 in. (0.3 cm); Wt. 1.9 oz. (53.9 g),"Gift of George D. Pratt, 1926",,Antwerp|Hall,,,,,,,,,,Miscellaneous-Coins and Medals,,http://www.metmuseum.org/art/collection/search/27937,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -04.3.59,false,true,21945,Arms and Armor,Mace,Mace Made for Henry II of France,French,,,,,Damascener,,Diego de Çaias,"Spanish, recorded 1535–49",,Çaias Diego de,Spanish,1535,1549,ca. 1540,1515,1565,"Steel, gold, silver",L. 24 in. (60.9 cm); W. 4 1/2 in. (11.4 cm); Wt. 3 lb. 8 oz. (1588 g),"Rogers Fund, 1904",,,,,,,,,,,,Shafted Weapons,,http://www.metmuseum.org/art/collection/search/21945,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.514.2,false,true,26835,Arms and Armor,Percussion target pistol,Percussion Exhibition Pistol,French,,,,,Gunsmith|Decorator|Barrelsmith,Signed by|Decorated by|Barrel by,Gilles Michel Louis Moutier-Le Page|Antoine Vechte|Léopold Bernard,"French, 1810–1887|French, 1799–1868|French, Paris, active 1832–70",,"Moutier-Le Page|Vechte, Antoine|Bernard, Léopold",French|French|French,1842 |1799 |1832,1865 |1868 |1870,dated 1851,1851,1851,"Steel, gold",L. 15 7/8 in. (40.3 cm); L. of barrel 10 1/4 in. (26.0 cm); Cal. .46 in. (11.7 mm),"Purchase, Ronald S. Lauder Gift, 2013",,Paris|London,,,,,,,,,,Firearms-Pistols-Percussion,,http://www.metmuseum.org/art/collection/search/26835,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2013.514.1, .2",false,true,35866,Arms and Armor,Two percussion target pistols,Two Percussion Exhibition Pistols,French,,,,,Gunsmith|Decorator|Barrelsmith,Signed by|Decorated by|Barrels by,Gilles Michel Louis Moutier-Le Page|Antoine Vechte|Léopold Bernard,"French, 1810–1887|French, 1799–1868|French, Paris, active 1832–70",,"Moutier-Le Page|Vechte, Antoine|Bernard, Léopold",French|French|French,1842 |1799 |1832,1865 |1868 |1870,dated 1849 and 1851,1849,1851,"Steel, gold",2013.514.1: L. 16 in. (40.7 cm); L. of barrel 10 1/4 in. (26.1 cm); Cal. .45 in. (11.4 mm); 2013.514.2: L. 15 7/8 in. (40.3 cm); L. of barrel 10 1/4 in. (26.0 cm); Cal. .46 in. (11.7 mm),"Purchase, Ronald S. Lauder Gift, 2013",,,,,,,,,,,,Firearms-Pistols-Percussion,,http://www.metmuseum.org/art/collection/search/35866,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -55.218,false,true,27558,Arms and Armor,Double-barreled shotgun,Double-barreled Shotgun,French,,,,,Barrelsmith|Gunsmith|Gunsmith,,Antoine Giraud|Nicolas Bouillet|Jean François Brunon,"French, active ca. 1760–80|French, Paris, recorded 1776–1800|French, St. Etienne, born 1737–recorded until 1784",,"Giraud, Antoine|Bouillet, Nicolas|Brunon, Jean François",French|French|French,1735 |1776 |1737,1805 |1800 |1784,"dated 1784, converted to percussion locks ca. 1840–50",1784,1875,"Steel, wood",L. of barrel 34 in. (86.4 cm); Cal. .58 in. (14.7 mm); Wt. 6 lb. 1 oz. (2750 g),"Rogers Fund, 1955",,,,,,,,,,,,Firearms,,http://www.metmuseum.org/art/collection/search/27558,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1992.305.2,false,true,24951,Arms and Armor,Print,"Print of Designs for a Pommel, Quillons, and Locket",British,,,,,Engraver|Designer,After,Wenceslaus Hollar|Hans Holbein the Younger,"Bohemian, Prague 1607–1677 London|German, Augsburg 1497/98–1543 London",,"Hollar, Wenceslaus|Holbein, Hans, the Younger",Bohemian|German,1607 |1497,1677 |1543,1645,1645,1645,Ink on paper,6 1/8 x 4 1/2 in. (15.5 x 11.4 cm),"Gift of Lois Earl Blumka, 1992",,,,,,,,,,,,Works on Paper-Prints,,http://www.metmuseum.org/art/collection/search/24951,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.99,false,true,679156,Arms and Armor,Drawing,"Design for the Decoration of Two Firearms Accessories, a Ladle and a Screwdriver",Spanish,,,,,Designer,,Eusebio Zuloaga,"Spanish, Madrid and Eibar 1808–1898",,"Zuloaga, Eusebio",Spanish,1808,1898,ca. 1850–51,1825,1876,"Pen, ink, and colored wash on paper",8 7/8 x 9 3/8 in. (22.5 x 24 cm),"Purchase, Arthur Ochs Sulzberger Bequest, 2015",,,,,,,,,,,,Works on Paper-Drawings,,http://www.metmuseum.org/art/collection/search/679156,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.100,false,true,679157,Arms and Armor,Drawing,Design for Two Sides of a Dagger Sheath,Spanish,,,,,Designer,,Eusebio Zuloaga,"Spanish, Madrid and Eibar 1808–1898",,"Zuloaga, Eusebio",Spanish,1808,1898,ca. 1850–55,1825,1880,"Pen, ink, and wash on paper",13 5/8 x 10 1/8 in. (34.5 x 25.7 cm),"Purchase, Arthur Ochs Sulzberger Bequest, 2015",,,,,,,,,,,,Works on Paper-Drawings,,http://www.metmuseum.org/art/collection/search/679157,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.101,false,true,679158,Arms and Armor,Drawing,Designs for the Decoration of a Percussion Pistol,Spanish,,,,,Designer,,Eusebio Zuloaga,"Spanish, Madrid and Eibar 1808–1898",,"Zuloaga, Eusebio",Spanish,1808,1898,ca. 1847,1822,1872,"Pen, ink, colored wash, and silver on paper",13 1/2 x 17 5/8 in. (34.5 x 44.8 cm),"Purchase, Arthur Ochs Sulzberger Bequest, 2015",,,,,,,,,,,,Works on Paper-Drawings,,http://www.metmuseum.org/art/collection/search/679158,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2011.35,false,true,35697,Arms and Armor,Mounting for a short sword (Wakizashi goshirae),Mounting for a Short Sword (Wakizashi Goshirae),Japanese,,,,,Lacquer worker|Fittings maker,,Shibata Zeshin|Tsuchiya Masayoshi Yasuchika,"Japanese, 1807–1891|Japanese, died 1860",,"Shibata Zeshin|Yasuchika, Tsuchiya Masayoshi",Japanese|Japanese,1807 |1760,1891 |1860,"mounting 19th century; grip ornaments 16th century; hilt collar (fuchi), dated 1849",1500,1900,"Wood, lacquer, ray skin (samé), thread, copper-gold alloy (shakudō), brass, iron",L. 26 1/8 in. (66.4 cm),"Purchase, The Howard Mansfield Collection, Gift of Howard Mansfield, by exchange, 2011",,,,,,,,,,,,Sword Fittings,,http://www.metmuseum.org/art/collection/search/35697,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.120.101,false,true,24626,Arms and Armor,Sword guard (Tsuba),Sword Guard (Tsuba),Japanese,,,,,Fittings maker|Decorator,Inscribed by|Inscribed by,Kanō Natsuo|Toshiyoshi,"Japanese, 1828–1898|Japanese, recorded 1865–84",,"Natsuo, Kanō|Toshiyoshi",Japanese|Japanese,1828 |1865,1898 |1884,mid-19th century,1825,1875,"Copper-silver alloy (shibuichi), copper-gold alloy (shakudō), gold, silver, copper",H. 2 3/4 in. (7 cm); W. 2 3/8 in. (6 cm); thickness 3/16 in. (0.5 cm); Wt. 4.3 oz. (121.9 g),"The Howard Mansfield Collection, Gift of Howard Mansfield, 1936",,,,,,,,,,,,Sword Furniture-Tsuba,,http://www.metmuseum.org/art/collection/search/24626,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.120.418a–c,false,true,24631,Arms and Armor,Blade and mounting for a short sword (Wakizashi),Blade and Mounting for a Short Sword (Wakizashi),Japanese,,,,,Swordsmith|Fittings maker,Blade attributed to|Mounting by,Yasumitsu|Iwamoto Konkan,"Japanese, Muromachi period, 15th century|Japanese, Edo period, 1744–1801",,Yasumitsu|Iwamoto Konkan,Japanese|Japanese,1400 |1744,1500 |1801,"blade, 15th century; mounting, 18th century",1401,1900,"Steel, wood, lacquer, ray skin (samé); thread, copper-gold alloy (shakudō), copper-silver alloy (shibuichi)",L. 27 1/16 in. (68.8 cm); L. of blade 24 5/16 in. (61.8 cm); L. of cutting edge 19 7/8 in. (50.5 cm); D. of curvature 9/16 in. (1.45 cm); L. of scabbard 19 3/4 in. (50.2 cm); knife (c); L. of blade 7 11/16 in. (19.5 cm); Wt. 0.4 oz. (11.3 g),"The Howard Mansfield Collection, Gift of Howard Mansfield, 1936",,,,,,,,,,,,Swords,,http://www.metmuseum.org/art/collection/search/24631,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"36.120.417a, b",false,true,24630,Arms and Armor,Blade and mounting for a sword (Katana),Blade and Mounting for a Sword (Katana),Japanese,,,,,Swordsmith|Fittings maker,Blade inscribed by|Mounting by,Sukemitsu of Bizen|Iwamoto Konkan,"Japanese, Bizen, Muromachi period, active ca. 1440|Japanese, Edo period, 1744–1801",,Sukemitsu of Bizen|Iwamoto Konkan,Japanese|Japanese,1400 |1744,1500 |1801,"blade, dated 1440; mounting, 18th century",1440,1900,"Steel, wood, lacquer, ray skin (samé), thread, copper-gold alloy (shakudō), copper-silver alloy (shibuichi)",L. 36 1/2 in. (92.7 cm); L. of blade 30 11/16 in. (77.9 cm); L of cutting edge 24 13/16 in. (63.1 cm); D. of curvature 11/16 in. (1.8 cm),"The Howard Mansfield Collection, Gift of Howard Mansfield, 1936",,,Okayama Prefecture,,,,,,,,,Swords,,http://www.metmuseum.org/art/collection/search/24630,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.100.172,true,true,22521,Arms and Armor,Armor (Gusoku),Armor (Gusoku),Japanese,,,,,Armorer|Armorer,"Helmet bowl signed|Breastplate inscribed inside,",Saotome Iyetada|Myōchin Munesuke,"Japanese, Edo period, active early–mid-19th century|Japanese, Edo period, 1688–1735",,"Iyetada, Saotome|Munesuke, Myōchin",Japanese|Japanese,1800 |1688,1875 |1735,16th and 18th centuries,1501,1800,"Iron, lacquer, silk, gilt copper",H. 67 1/2 in. (171.5 cm),"Gift of Bashford Dean, 1914",,,,,,,,,,,,Armor for Man,,http://www.metmuseum.org/art/collection/search/22521,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.100.172; 14.100.527–.528,false,true,35721,Arms and Armor,Armor (Gusoku),Armor (Gusoku),Japanese,,,,,Armorer|Armorer,"Helmet bowl signed|Breastplate inscribed inside,",Saotome Iyetada|Myōchin Munesuke,"Japanese, Edo period, active early–mid-19th century|Japanese, Edo period, 1688–1735",,"Iyetada, Saotome|Munesuke, Myōchin",Japanese|Japanese,1800 |1688,1875 |1735,16th and 18th centuries,1501,1800,"Iron, lacquer, silk, gilt copper",H. 67 1/2 in. (171.5 cm); L. of each foot defense 9 3/4 in. (24.8 cm); W. of each foot defense 4 1/4 in. (10.8 cm),"Gift of Bashford Dean, 1914",,,,,,,,,,,,Armor for Man,,http://www.metmuseum.org/art/collection/search/35721,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -91.2.84,false,true,21913,Arms and Armor,Blade and mounting for a short sword (Wakizashi),Blade and Mounting for a Short Sword (Wakizashi),Japanese,,,,,Swordsmith|Decorator|Fittings maker,Blade inscribed by|Engraving on blade attributed to|Sword guard (Tsuba) inscribed by,Naotane Taikei|Honjo Yoshitane|Yukinaka,"Japanese, Yamagata 1778–1857 Edo|Japanese, Edo period, 19th century|Japanese, Hagi, active 19th century",,"Naotane Taikei|Yoshitane, Honjo|Yukinaka",Japanese|Japanese|Japanese,1778 |1801 |1801,1857 |1900 |1900,"blade, dated 1839; mounting, early–mid 19th century",1801,1900,"Steel, wood, lacquer, rayskin (samé), thread, iron, copper-silver alloy (shibuichi), gold, silver",L. 26 3/4 in. (67.9 cm); L. of blade 23 3/8 in. (59.4 cm); L. of cutting edge 17 27/32 in. (45.3 cm); D. of curvature 9/64 in. (0.36 cm),"Gift of Brayton Ives and W. T. Walters, 1891",,Hagi,Nagato Province (Yamaguchi Prefecture),,,,,,,,,Swords,,http://www.metmuseum.org/art/collection/search/21913,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"91.2.56, .84",false,true,638804,Arms and Armor,Blades and mountings for a pair of swords (Daishō),Blades and Mountings for a Pair of Swords (Daishō),Japanese,,,,,Swordsmith|Decorator|Fittings maker,Blades inscribed by|Engraving on blades attributed to|Sword guards (Tsuba) inscribed by,Naotane Taikei|Honjo Yoshitane|Yukinaka,"Japanese, Yamagata 1778–1857 Edo|Japanese, Edo period, 19th century|Japanese, Hagi, active 19th century",,"Naotane Taikei|Yoshitane, Honjo|Yukinaka",Japanese|Japanese|Japanese,1778 |1801 |1801,1857 |1900 |1900,"blades, dated 1839; mountings, 19th century",1801,1900,"Steel, wood, lacquer, rayskin, silk, iron, copper-gold alloy (shakudō), copper, gold, silver, copper-silver alloy (shibuichi)",L. of sword (katana) 39 1/4 in. (99.8 cm); L. of sword (katana) blade 36 31/32 in. (93.9 cm); L. of sword (katana) blade cutting edge 28 5/8 in. (72.7 cm); D. of sword (katana) blade curvature 1 7/32 in. (3.1 cm); L. of short sword (wakizashi) 26 3/4 in. (67.9 cm); L. of short sword (wakizashi) blade 17 27/32 in. (45.3 cm); D. of short sword (wakizashi) blade curvature 9/64 in. (0.36 cm),"Gift of Brayton Ives and W. T. Walters, 1891",,Hagi,Nagato Province (Yamaguchi Prefecture),,,,,,,,,Swords,,http://www.metmuseum.org/art/collection/search/638804,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"36.120.417a, b, .418a–c",true,true,27789,Arms and Armor,Blades and mountings for a pair of swords (Daishō),Blades and Mountings for a Pair of Swords (Daishō),Japanese,,,,,Swordsmith|Swordsmith|Fittings maker,Long sword (katana) blade inscribed by|Short sword (wakizashi) blade attributed to|Set of sword mountings by,Sukemitsu of Bizen|Yasumitsu|Iwamoto Konkan,"Japanese, Bizen, Muromachi period, active ca. 1440|Japanese, Muromachi period, 15th century|Japanese, Edo period, 1744–1801",,Sukemitsu of Bizen|Yasumitsu|Iwamoto Konkan,Japanese|Japanese|Japanese,1400 |1400 |1744,1500 |1500 |1801,"sword (katana) blade, dated 1440; short sword (wakizashi) blade, 15th century; mountings, late 18th century",1401,1900,"Steel, wood, lacquer, copper-silver alloy (shibuichi), gold, copper, rayskin, silk",L. of sword (katana) 34 1/8 in. (86.7 cm); L. of sword (katana) blade 25 1/4 in. (64.1 cm); L. of sword (katana) scabbard 28 1/4 in. (71.8 cm); L. of short sword (wakizashi) 26 in. (66.0 cm); L. of short sword (wakizashi) blade 20 3/4 in. (52.7 cm); L. of short sword (wakizashi) scabbard 19 3/4 in. (50.2 cm); L. of knife (kozuka) for short sword (wakizashi) mounting 8 3/4 in. (22.2 cm),"The Howard Mansfield Collection, Gift of Howard Mansfield, 1936",,,,,,,,,,,,Swords,,http://www.metmuseum.org/art/collection/search/27789,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1995.52,false,true,26558,Arms and Armor,Cover for a smallsword hilt,Cover for a Smallsword Hilt,"French, Paris",,,,,Sword maker,,Guillaume Pagés,"French, Paris, recorded 1709–56",,Pagés Guillaume,French (Paris),1709,1756,ca. 1725–50,1700,1775,"Leather, ink",L. 8 1/4 in. (20.9 cm); W. 6 7/8 in. (17.5 cm),"Purchase, Rogers Fund, by exchange, 1995",,Paris,,,,,,,,,,Miscellaneous,,http://www.metmuseum.org/art/collection/search/26558,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.514.1,false,true,26893,Arms and Armor,Percussion target pistol,Percussion Exhibition Pistol,"French, Paris",,,,,Gunsmith|Decorator|Barrelsmith,Signed by|Decorated by|Barrel by,Gilles Michel Louis Moutier-Le Page|Antoine Vechte|Léopold Bernard,"French, 1810–1887|French, 1799–1868|French, Paris, active 1832–70",,"Moutier-Le Page|Vechte, Antoine|Bernard, Léopold",French|French|French,1842 |1799 |1832,1865 |1868 |1870,dated 1849,1849,1849,"Steel, gold",L. 16 in. (40.7 cm); L. of barrel 10 1/4 in. (26.1 cm); Cal. .45 in. (11.4 mm),"Purchase, Ronald S. Lauder Gift, 2013",,Paris,,,,,,,,,,Firearms-Pistols-Percussion,,http://www.metmuseum.org/art/collection/search/26893,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2013.512.1, .2",false,true,26980,Arms and Armor,Pair of percussion target pistols,Pair of Percussion Target Pistols Made for Display at the 1844 Exposition des Produits de l'Industrie in Paris,"French, Paris",,,,,Gunsmith|Designer|Barrelsmith,Signed by|Designed by|Barrels by,Alfred Gauvain|Michel Liénard|Léopold Bernard,"French, Paris 1801–1889 Paris|French, La Bouille 1810–1870 Brussels|French, Paris, active 1832–70",,"Gauvain, Alfred|Liénard, Michel|Bernard, Léopold",French|French|French,1801 |1810 |1832,1889 |1870 |1870,dated 1844,1844,1844,"Steel, wood (ebony)",L. of each pistol 16 5/8 in. (42.3 cm); L. of each barrel 11 in. (27.8 cm); Cal. of each barrel .50 in. (13 mm),"Purchase, Arthur Ochs Sulzberger Bequest, 2013",,Paris,,,,,,,,,,Firearms-Pistols-Percussion,,http://www.metmuseum.org/art/collection/search/26980,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2013.513.1, .2",false,true,26981,Arms and Armor,Pair of percussion target pistols,"Pair of Percussion Target Pistols Made for Display at the Crystal Palace Exhibition in London, 1851","French, Paris",,,,,Gunsmith|Designer|Barrelsmith,Signed by|Designed by|Barrels by,Alfred Gauvain|Michel Liénard|Léopold Bernard,"French, Paris 1801–1889 Paris|French, La Bouille 1810–1870 Brussels|French, Paris, active 1832–70",,"Gauvain, Alfred|Liénard, Michel|Bernard, Léopold",French|French|French,1801 |1810 |1832,1889 |1870 |1870,dated 1851,1851,1851,"Steel, wood (ebony), gold",L. of each pistol 17 1/4 in. (44 cm); L. of each barrel 11 3/8 in. (28.8 cm); Cal. of each barrel .46 in. (12 mm),"Purchase, Arthur Ochs Sulzberger Bequest, 2013",,Paris,,,,,,,,,,Firearms-Pistols-Percussion,,http://www.metmuseum.org/art/collection/search/26981,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2011.310.1–.13,false,true,35701,Arms and Armor,Pattern book of firearms ornament,Plusievrs Pieces et Ornements Darquebuzerie (4th extended edition),"French, Paris",,,,,Engraver|Publisher|Engraver|Engraver,Initial eight-plate edition by|Editions with eight and thirdteen plates issued by|Designs by|Expanded edition produced by,Claude Simonin|Laurent le Languedoc|De Lacollombe|Gilles-Antoine Demarteau,"French, Nantes ca. 1635–1693 Nantes|French, Paris, active ca. 1705|French, Paris, active ca. 1702–ca. 1736|French, Paris, 1756–1802",", published in 1684 and reissued in1685|, included in the 1705 edition","Simonin, Claude|Languedoc, Laurent le|Lacollombe, De|Demarteau, Gilles-Antoine",French|French|French|French,1635 |1680 |1702 |1756,1693 |1730 |1736 |1802,ca. 1776,1751,1801,"Ink, paper",9 1/2 x 12 3/4 in. (24.1 x 32.4 cm) to 9 1/4 x 14 in. (23.5 x 35.6 cm),"Purchase, Kenneth and Vivian Lam Gift, 2011",,Paris,,,,,,,,,,Works on Paper-Prints,,http://www.metmuseum.org/art/collection/search/35701,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1993.415,true,true,24957,Arms and Armor,Double-barrel breech-loading pinfire shotgun,Double-Barrel Breech-Loading Pinfire Shotgun,"French, Paris",,,,,Barrelsmith|Gunsmith|Engraver|Goldsmith|Goldsmith,Designed and steel chiseled by|Designed and steel chiseled by,Léopold Bernard|J. C. A. Brun|Jean-Claude Tissot|François-Auguste Fannière|François-Joseph-Louis Fannière,"French, Paris, active 1832–70|French, Paris, active 1849–72|French, Paris, 1811–1889|French, Paris, 1818–1900|French, Paris, 1822–1897",,"Bernard, Léopold|Brun, J. C. A.|Tissot, Jean-Claude|Fannière, François-Auguste|Fannière, François-Joseph-Louis",French|French|French|French|French,1832 |1849 |1811 |1818 |1822,1870 |1872 |1889 |1900 |1897,dated 1866,1866,1866,"Steel, wood (walnut), gold",L. 44 1/8 in. (112 cm),"Purchase, The Sulzberger Foundation Inc. Gift and Rogers Fund; Bashford Dean Memorial Collection, Funds from various donors, Gift of William H. Riggs, The Collection of Giovanni P. Morosini, presented by his daughter Giulia, and Gift of Charles M. Schott Jr., by exchange; and gifts and funds from various donors, 1993",,Paris,,,,,,,,,,Firearms-Guns-Wheellock,,http://www.metmuseum.org/art/collection/search/24957,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1981.363.1–.3,false,true,24888,Arms and Armor,Smallsword,Smallsword,"British, London",,,,,Enameler|Goldsmith,"Signed AT, probably for|Attributed to",Simon Augustin Toussaint|James Morisset,"British, London, active 1768–85|English, London, active 1768–1800",,"Toussaint, Simon Augustin|Morisset James",British|British,1768 |1768,1785 |1800,ca. 1780–85,1755,1810,"Gold, enamel, steel",Sword (a); L. 39 in. (99.1 cm); box (c); L. 41 in. (104.1 cm); W. 6 3/16 in. (15.7 cm); D. 5 1/4 in. (13.3 cm),"Gift of Dr. and Mrs. John C. Weber, 1981",,London,,,,,,,,,,Swords,,http://www.metmuseum.org/art/collection/search/24888,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"1992.330.1, .2",false,true,24952,Arms and Armor,Pair of flintlock pistols,Pair of Flintlock Pistols,"British, London",,,,,Gunsmith|Silversmith,Attributed to,Samuel Brunn|Michael Barnett,"English, London, recorded 1795–1820|English, London, 1758–ca. 1823",,"Brunn, Samuel|Barnett, Michael",British|British,1795 |1758,1820 |1850,hallmarked for 1800–1801,1800,1801,"Steel, wood (walnut), silver, gold",L. of each pistol 16 in. (40.6 cm); L. of each barrel 10 1/8 in. (25.7 cm); Cal. of each pistol .603 in. (15.3 mm),"Purchase, Harris Brisbane Dick Fund and Gift of George D. Pratt, by exchange, 1992",,London,,,,,,,,,,Firearms,,http://www.metmuseum.org/art/collection/search/24952,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1992.305.1,false,true,24950,Arms and Armor,Print,Print of Designs for Hilt and Sheath Fittings,"British, London",,,,,Engraver|Designer,After,Wenceslaus Hollar|Hans Holbein the Younger,"Bohemian, Prague 1607–1677 London|German, Augsburg 1497/98–1543 London",,"Hollar, Wenceslaus|Holbein, Hans, the Younger",Bohemian|German,1607 |1497,1677 |1543,1644,1644,1644,Ink on paper,5 7/8 x 3 3/4 in. (14.9 x 9.5 cm),"Gift of Lois Earl Blumka, 1992",,London,,,,,,,,,,Works on Paper-Prints,,http://www.metmuseum.org/art/collection/search/24950,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2011.361–.362,false,true,35702,Arms and Armor,Pair of miquelet flintlock pistols,Pair of Miquelet Flintlock Pistols,"Colonial Spanish, probably Mexico",,,,,Gunsmith,Signed by,Francisco Pintan,active mid-18th century,,"Pintan, Francisco",,1725,1775,dated 1757,1757,1757,"Steel, wood (family leguminosae), silver",L. of 2011.361: 10 7/8 in. (27.5 cm); L. of barrel of 2011.361: 6 in. (15.2 cm); L. of 2011.362: 10 5/8 in. (27.2 cm); L. of barrel of 2011.362: 6 in. (15.2 cm),"Purchase, Arthur Ochs Sulzberger Gift, 2011",,,,,,,,,,,,Firearms-Guns-Miquelet,,http://www.metmuseum.org/art/collection/search/35702,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2014.282,false,true,650965,Arms and Armor,Close helmet,Close Helmet,"Italian, Milan",,,,,Armorer,,Gian Giacomo Negroli,"Italian, Milan 1463–1543",,"Negroli, Gian Giacomo",,1463,1543,ca. 1510–20,1485,1545,"Steel, gold",H. 13 1/8 in. (33.3 cm); W. 8 1/2 in. (21.6 cm); D. 12 in. (30.5 cm); Wt. 3 lb. 6 oz. (1530 g),"Purchase, Arthur Ochs Sulzberger Gift, 2014",,Milan,,,,,,,,,,Helmets,,http://www.metmuseum.org/art/collection/search/650965,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -39.38,false,true,26533,Arms and Armor,Close-helmet,Close-Helmet,"German, Nuremberg",,,,,Armorer,Attributed to,Kunz Lochner,"German, Nuremberg, 1510–1567",,"Lochner, Kunz",,1510,1567,1550,1525,1575,"Steel, leather",H. 12 3/4 (32.4 cm); W. 8 1/8 in. (20.6 cm); D. 13 in. (33 cm); Wt. 6 lb. 9 oz. (2975 g),"Gift of Mrs. Theodore Offerman, in memory of her husband, 1939",,Nuremberg,,,,,,,,,,Helmets,,http://www.metmuseum.org/art/collection/search/26533,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -32.130.5a–m,false,true,23937,Arms and Armor,Armor,"Armor of Henry Herbert (1534–1601), Second Earl of Pembroke","British, Greenwich",,,,,Armorer,Made in the,Royal Workshops at Greenwich,"British, Greenwich, 1511–1640s",,Royal Workshops at Greenwich,,1511,1650,ca. 1585–86,1560,1611,"Steel, gold",Wt. 60 lb. 1 oz. (27.24 kg),"Rogers Fund, 1932",,Greenwich,,,,,,,,,,Armor for Man,,http://www.metmuseum.org/art/collection/search/23937,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.25.1191,false,true,662414,Arms and Armor,Rapier,Rapier,"hilt, Italian; blade, German, Solingen",,,,,Bladesmith,Blade by,Clemens Hartkopf,"German, Solingen, active ca. 1625",,"Hartkopf, Clemens",,1600,1650,ca. 1625,1600,1650,"Steel, silver, wood, copper alloy",L. 49 1/2 in. (125.7 cm); L. of blade 43 in. (109.2 cm); W. 8 1/2 in. (21.6 cm); D. 5 in. (12.7 cm); Wt. 2 lb. 10 oz. (1190.7 g),"Gift of William H. Riggs, 1913",,,,,,,,,,,,Swords,,http://www.metmuseum.org/art/collection/search/662414,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.446a–d,false,true,679294,Arms and Armor,Wheellock rifle with spanner and accessories,"Wheellock Rifle with Spanner, Shot Extracting Tool, and Shooting Patch","German, Augsburg",,,,,Gunsmith,,Martin Kammerer,"German, Augsburg, active 1654–67",,"Kammerer, Martin",,1654,1667,ca. 1665,1640,1690,"Steel, iron, gold, wood, antler, copper alloy, enamel, bone, textile",rifle (2015.446a): L. 43 1/2 in. (110.5 cm); L. of barrel 32 1/2 in. (82.6 cm); W. 8 1/4 in. (21 cm); D. 4 1/4 in. (10.8 cm); Wt. 9 lb. 7 oz. (4280.8 g); spanner (2015.446b): L. 7 3/4 in. (19.7 cm); W. 7/8 in. (2.2 cm); D. 1 9/16 in. (4 cm); Wt. 4 oz. (113.4 g); shot extracting tool (2015.446c): L. 1 13/16 in. (4.6 cm); Diam. 3/8 in. (1 cm); Wt. 0.5 oz. (14.2 g); shooting patch (2015.446d): Diam. approx. 1 3/8 in. (3.5 cm),"Purchase, Arthur Ochs Sulzberger Bequest, 2015",,Augsburg,,,,,,,,,,Firearms-Guns-Wheellock,,http://www.metmuseum.org/art/collection/search/679294,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"32.130.4a, b",false,true,23938,Arms and Armor,Rapier,Rapier of Ambrogio Spinola (1569–1630),"Northern European, possibly France",,,,,Hilt Maker,Hilt inscribed,M. I. F.,"northern European, active ca. 1600",,M. I. F.,,1575,1625,ca. 1600,1575,1625,Steel,L. 46 1/8 in. (117.1 cm); L. of blade 39 3/4 in. (101 cm),"Rogers Fund, 1932",,,,,,,,,,,,Swords,,http://www.metmuseum.org/art/collection/search/23938,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.25.1052,false,true,27437,Arms and Armor,Rapier,Rapier,"hilt, probably German; blade, Italian, Milan",,,,,Bladesmith,Blade by,Pietro Caino,"Italian, Milan, active second half 16th century",,"Caino, Pietro",,1550,1600,late 16th century,1550,1600,"Steel, wood, iron",L. 45 5/8 in. (115.9 cm); L. of blade 40 1/2 in. (102.9 cm); W. 7 in. (17.8 cm); D. 5 in. (12.7 cm); Wt. 2 lb. 9 oz. (1162.3 g),"Gift of William H. Riggs, 1913",,Milan,,,,,,,,,,Swords,,http://www.metmuseum.org/art/collection/search/27437,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -19.131.2g,false,true,734140,Arms and Armor,Saddle cloth,"Saddle Cloth Associated with Armor Garniture, Probably of King Henry VIII of England (reigned 1509–47)","British, Greenwich",,,,,Armorer|Designer,Made in the|Design of the decoration attributed to,Royal Workshops at Greenwich|Hans Holbein the Younger,"British, Greenwich, 1511–1640s|German, Augsburg 1497/98–1543 London",,"Royal Workshops at Greenwich|Holbein, Hans, the Younger",,1511 |1497,1650 |1543,dated 1527,1527,1527,Textile,,"Purchase, William H. Riggs Gift and Rogers Fund, 1919",,Greenwich,,,,,,,,,,Equestrian Equipment-Saddles,,http://www.metmuseum.org/art/collection/search/734140,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"22.147.4a, b",false,true,22851,Arms and Armor,Right thigh and knee defense (cuisse and poleyn),Right Thigh and Knee Defense (Cuisse and Poleyn) for the Armor of Sir John Scudamore (1541 or 1542–1623),"British, Greenwich",,,,,Armorer,Made under the direction of,Jacob Halder,"British, master armorer at the royal workshops at Greenwich, documented in England 1558–1608",,"Halder, Jacob",,1558,1608,ca. 1587,1562,1612,"Steel, gold, leather",22.147.4a: H. 4 1/2 in. (11.43 cm); 22.147.4b: H. 7 3/4 in. (19.69 cm); W. 5 in. (12.7 cm),"Fletcher Fund, 1922",,Greenwich,,,,,,,,,,Armor Parts-Knee Defenses,,http://www.metmuseum.org/art/collection/search/22851,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -39.121a–n,true,true,24671,Arms and Armor,Armor,"Armor of Henry II, King of France (reigned 1547–59)","French, possibly Paris",,,,,Designer|Designer|Designer,Part of the decoration design by|Part of the decoration design possibly by|Part of the decoration design possibly by,Jean Cousin the Elder|Étienne Delaune|Baptiste Pellerin,"French, Souci (?) ca. 1490–ca. 1560 Paris (?)|French, Orléans 1518/19–1583 Strasbourg|French, documented in Étampes 1542–75 Paris",,"Cousin, Jean, the Elder|Delaune, Étienne|Pellerin, Baptiste",,1485 |1518 |1542,1565 |1583 |1575,ca. 1555,1530,1580,"Steel, gold, silver, leather, textile",H. 74 in. (187.96 cm); Wt. 53 lb. 4 oz. (24.20 kg),"Harris Brisbane Dick Fund, 1939",,possibly Paris,,,,,,,,,,Armor for Man,,http://www.metmuseum.org/art/collection/search/24671,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2015.295a, b",false,true,681022,Arms and Armor,Cuirass,Cuirass,"French, Klingenthal, Alsace",,,,,Designer|Decorator|Manufactory,Designed by|Etched and gilt by|Manufactured at,"François-Joseph Bisch|François-Xavier Bisch|Coulaux Frères, Manufacture Royale d'Armes de Klingenthal","French, Klingenthal, Alsace 1756–1831|French, active in Klingenthal and Boersch, Alsace 1793–1841|French, Klingenthal, Alsace 1801–1836",,"Bisch, François-Joseph|Bisch, François-Xavier|Coulaux Frères, Manufacture Royale d'Armes de Klingenthal",,1756 |1793 |1801,1831 |1841 |1836,ca. 1825,1820,1830,"Steel, gold, copper alloy, leather",H. 17 1/2 in. (44.5 cm); W. 14 1/8 in. (35.9 cm); D. 12 3/4 in. (32.4 cm); Wt. 12 lb. 2.2 oz. (5505.5 g); breastplate: H. 17 1/2 in. (44.5 cm); W. 14 1/8 in. (35.9 cm); D. 7 in. (17.8 cm); Wt. 8 lb. 0.5 oz. (3642.9 g); backplate: H. 16 15/16 in. (43 cm); W. 14 1/8 in. (35.9 cm); D. 5 3/4 in. (14.6 cm); Wt. 4 lb. 1.7 oz. (1862.6 g),"Purchase, Arthur Ochs Sulzberger Bequest, 2015",,Klingenthal,Alsace,,,,,,,,,Armor Parts-Cuirasses,,http://www.metmuseum.org/art/collection/search/681022,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2016.649,false,true,717696,Arms and Armor,Album of photographs,"Album of Photographs of Views of the Interior of the Ottoman Military Museum in the Former Church of St. Irene, Constantinople",Turkish,,,,,Photographer,,Abdullah Frères,"Ottoman, 1858–1899",,Abdullah Frères,,1858,1899,1891,1891,1891,"Albumen prints, paper, leather, textile, gold",L. 17 3/4 in. (45.1 cm); H. 13 in. (33.0 cm); W. 2 3/8 in. (6.0 cm),"Gift of Howard Ricketts in Memory of Roy and Neil Cole, Collectors, 2016",,,,,,,,,,,,Photographs,,http://www.metmuseum.org/art/collection/search/717696,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -22.122.48,false,true,32997,Arms and Armor,Medal,Medal Showing Prince Charles and Prince Henry,Scottish,,,,,Artist,,Ottone Hamerani,"Italian, Rome 1694–1768",,Hamerani Ottone,,1694,1768,1729,1729,1729,Bronze,Diam. 1 13/16 in. (4.6 cm); thickness 1/4 in. (0.6 cm); Wt. 1.8 oz. (51 g),"Gift of Bashford Dean, 1922",,,,,,,,,,,,Miscellaneous-Coins and Medals,,http://www.metmuseum.org/art/collection/search/32997,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -22.122.60,false,true,33009,Arms and Armor,Medal,"Medal Showing Charles III of Spain, 1778","Spanish, Mexico",,,,,Artist,,Jerónimo Antonio Gil,"Spanish, Zamora 1731–1798 Mexico",,"Gil, Jerónimo Antonio",,1732,1798,1778,1778,1778,Bronze,Diam. 2 5/16 in. (5.9 cm); thickness 1/4 in. (0.6 cm); Wt. 3.9 oz. (110.6 g),"Gift of Bashford Dean, 1922",,,,,,,,,,,,Miscellaneous-Coins and Medals,,http://www.metmuseum.org/art/collection/search/33009,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2016.205a, b",false,true,712927,Arms and Armor,Pair of stirrups (Abumi),Pair of Stirrups (Abumi),"Japanese, Kashu",,,,,Artist,Inscribed by,Sanemitsu,"Japanese, active in Ka shū, possibly early 18th century",,Sanemitsu,,1675,1775,probably 18th century,1675,1825,"Iron, wood, silver, lacquer, paper",left: L. 10.7 in. (27.2 cm); W. 5.03 in. (12.8 cm); H. 9.68 in. (24.6 cm); right: L. 10.86 in. (27.6 cm); W. 5.03 in. (12.8 cm); H. 9.76 in. (24.8 cm),"Purchase, Gift of Morihiro and Sumiko Ogawa, in memory of Charles Baber, 2016",,Kanazawa City,Ishikawa Prefecture,,,,,,,,,Equestrian Equipment-Stirrups,,http://www.metmuseum.org/art/collection/search/712927,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.25.1573,false,true,22397,Arms and Armor,Pellet and bolt crossbow combined with a wheel-lock gun,Pellet and Bolt Crossbow Combined with a Wheel-Lock Gun,Central European; possibly Southern German or Austrian,,,,,Designer,Decoration based on designs by,Jacob Floris,1524–1581,", published in Antwerp in 1564.","Floris, Jacob",,1524,1581,ca. 1570–1600,1545,1625,"Steel, wood (cherry), staghorn, hemp, felt","L. 28 1/2 in. (72.4 cm); W. 26 1/2 in. (67.2 cm); Wt. 11 lb. 7 oz. (5,197 g)","Gift of William H. Riggs, 1913",,,,,,,,,,,,Archery Equipment-Crossbows,,http://www.metmuseum.org/art/collection/search/22397,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.597,false,true,693886,Arms and Armor,Medal,Medal of Kolman Helmschmid (1471–1532),"German, Augsburg",,,,,Designer,After a model by,Hans Kels the Younger,"German, 1508/10–1565",,"Kels, Hans, the Younger",,1508,1565,dated 1532,1532,1532,Lead,Diam. 1 15/16 in. (50 mm); Wt. 1.6 oz. (44.96 g),"Purchase, Kenneth and Vivian Lam Gift, 2015",,Augsburg,,,,,,,,,,Medals,,http://www.metmuseum.org/art/collection/search/693886,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -16.29.1–.2,false,true,22609,Arms and Armor,Miniature Italian-style armor for man and horse,Miniature Italian-Style Armor for Man and Horse,French,,,,,Armorer,Possibly made by,Granger LeBlanc,"French, active ca. 1840–70",,"LeBlanc, Granger",,1815,1895,ca. 1860,1835,1885,Steel,H. as mounted 17 7/8 in. (45.4 cm),"Gift of William Oothout, 1916",,,,,,,,,,,,Armor-Miniatures,,http://www.metmuseum.org/art/collection/search/22609,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2016.311,false,true,716772,Arms and Armor,Engraving,Plate Six from Nouveavx Desseins D’Arquebvseries,French,,,,,Engraver,Signed by,Gilles Demarteau,"French, Liège 1722–1776 Paris",,"Demarteau, Gilles",,1722,1776,dated 1749,1749,1749,Engraving,sheet: 11 7/8 x 9 in. (30.16 x 22.86 cm); plate: 9 1/8 x 6 3/8 in. (23.17 x 16.19 cm),"Purchase, Michael H. Pourfar Gift, 2016",,,,,,,,,,,,Works on Paper-Engravings,,http://www.metmuseum.org/art/collection/search/716772,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2016.312,false,true,716774,Arms and Armor,Engraving,Plate Ten from Nouveavx Desseins D’Arquebvseries,French,,,,,Engraver,Signed by,Gilles Demarteau,"French, Liège 1722–1776 Paris",,"Demarteau, Gilles",,1722,1776,dated 1744,1744,1744,Engraving,sheet: 11 7/8 x 9 1/8 in. (30.16 x 23.17 cm); plate: 8 1/2 x 6 3/8 in. (21.59 x 16.19 cm),"Purchase, Michael H. Pourfar Gift, 2016",,,,,,,,,,,,Works on Paper-Engravings,,http://www.metmuseum.org/art/collection/search/716774,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"26.145.291a, b",false,true,32526,Arms and Armor,Smallsword with scabbard,Smallsword with Scabbard,French,,,,,Sword cutler,,C. Liger,"French, Paris, recorded 1770–93",,"Liger, C.",,1770,1793,ca. 1780,1770,1793,"Steel, silver, gold, wood, textile, fishskin",L. with scabbard 38 5/8 in. (98.1 cm); L. without scabbard 38 1/8 in. (96.8 cm); L. of blade 31 1/2 in. (80 cm); W. 4 1/2 in. (11.4 cm); D. 3 1/4 in. (8.3 cm); Wt. 13 oz. (368.54 g); Wt. of scabbard 2.5 oz. (70.9 g),"Gift of Jean Jacques Reubell, in memory of his mother, Julia C. Coster, and of his wife, Adeline E. Post, both of New York City, 1926",,,,,,,,,,,,Swords,,http://www.metmuseum.org/art/collection/search/32526,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -22.122.19,false,true,32971,Arms and Armor,Medal,"Medal Showing Henry IV of France (b. 1553, r. 1589–1610) and Marie de Médicis (1573–1642)",French,,,,,Artist,,Guillaume Dupré,"French, 1579–1640",,Dupré Guillaume,,1579,1640,dated 1603,1603,1603,Bronze,Diam. 2 9/16 in. (6.5 cm); thickness 1/4 in. (0.6 cm); Wt. 2.7 oz. (76.5 g),"Gift of Bashford Dean, 1922",,,,,,,,,,,,Miscellaneous-Coins and Medals,,http://www.metmuseum.org/art/collection/search/32971,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2016.245,false,true,716365,Arms and Armor,Etching and engraving,Design for a Rapier Hilt and Scabbard Chape,French,,,,,Artist,,Pierre Woeiriot de Bouzey II,"French, Neufchâteau 1532–1599 Damblain",,"de Bouzey, Pierre Woeiriot II",,1532,1599,1555,1555,1555,"Etching, engraving",Diam. 7 1/2 in. (19.1 cm),"Purchase, Gift of Andrew Solomon, 2016",,,,,,,,,,,,Works on Paper-Engravings,,http://www.metmuseum.org/art/collection/search/716365,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2016.697,false,true,739608,Arms and Armor,Engraving,"Design for a Sword-belt, a Knife and a Stylus",French,,,,,Artist,,Pierre Woeiriot de Bouzey II,"French, Neufchâteau 1532–1599 Damblain",,"de Bouzey, Pierre Woeiriot II",,1532,1599,ca. 1555,1530,1580,Engraving,7 3/16 x 9 3/4 in. (18.3 x 24.7 cm),"Purchase, Kenneth and Vivian Lam Gift, 2016",,,,,,,,,,,,Works on Paper-Engravings,,http://www.metmuseum.org/art/collection/search/739608,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2014.627,false,true,665952,Arms and Armor,Drawing,Drawing with Seven Designs for Firearms Ornament,French,,,,,Artist,,Jean-Francois Lucas,"French, 1747–1825",,"Lucas, Jean-Francois",,1747,1825,1806,1806,1806,"Pen and black ink, with gray wash and traces of graphite, on paper",10 15/16 x 7 13/16 in. (27.8 x 19.8 cm),"Purchase, Arthur Ochs Sulzberger Gift, 2014",,,,,,,,,,,,Drawings,,http://www.metmuseum.org/art/collection/search/665952,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -22.122.43,false,true,32992,Arms and Armor,Medal,"Medal Commemorating the Marriage of James III and Princess Clementina, 1719",British,,,,,Artist,Signed on the reverse by,Ottone Hamerani,"Italian, Rome 1694–1768",,Hamerani Ottone,,1694,1768,1719,1719,1719,Bronze,Diam. 1 7/8 in. (4.8 cm); thickness 3/16 in. (0.5 cm); Wt. 1.6 oz. (45.4 g),"Gift of Bashford Dean, 1922",,,,,,,,,,,,Miscellaneous-Coins and Medals,,http://www.metmuseum.org/art/collection/search/32992,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -16.154.2a–l,false,true,22629,Arms and Armor,Infantry armor,Infantry Armor,Italian,,,,,Armorer,"Upper plate of the gorget, skirt lames, and cheek pieces made by",Daniel Tachaux,"French, 1857–1928, active in France and America","in the Metropolitan Museum of Art, Armor Workshop","Tachaux, Daniel",,1857,1928,"dated 1571; upper plate of the gorget, skirt lames, and cheek pieces, 1917",1571,1917,"Steel, leather, brass",Wt. 33 lb. 11 oz. (15.28 kg),"Rogers Fund, 1916",,,,,,,,,,,,Armor for Man-1/2 Armor,,http://www.metmuseum.org/art/collection/search/22629,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2008.314,false,true,35640,Arms and Armor,Letter,"Letter from Henry Knox (1750–1806), Secretary of War, to Colonel Marinus Willet (1740–1830)",American,,,,,Author,,General Henry Knox,"American, Boston, Massachusetts 1750–1806 Thomaston, Maine",,"Knox, Henry General",,1750,1806,"dated May 27, 1786",1786,1786,Ink on paper,12 1/2 x 7 7/8 in. (31.7 x 20 cm),"Purchase, Arthur Ochs Sulzberger Gift, 2008",,,,,,,,,,,,Works on Paper,,http://www.metmuseum.org/art/collection/search/35640,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2016.403.5,false,true,722411,Arms and Armor,Engraving,Plate Eight from Nouveavx Desseins D'Arquebvseries,"French, Paris",,,,,Engraver,,Gilles Demarteau,"French, Liège 1722–1776 Paris",,"Demarteau, Gilles",,1722,1776,dated 1743,1743,1743,Engraving,sheet: 11 7/8 x 9 1/16 in. (30.2 x 23 cm); plate: 7 7/16 x 5 1/4 in. (18.9 x 13.3 cm),"Purchase, Arthur Ochs Sulzberger Gift, 2016",,Paris,,,,,,,,,,Works on Paper-Engravings,,http://www.metmuseum.org/art/collection/search/722411,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2016.403.6,false,true,722414,Arms and Armor,Engraving,Plate Nine from Nouveavx Desseins D'Arquebvseries,"French, Paris",,,,,Engraver,,Gilles Demarteau,"French, Liège 1722–1776 Paris",,"Demarteau, Gilles",,1722,1776,dated 1743,1743,1743,Engraving,sheet: 11 3/4 x 9 1/8 in. (29.8 x 23.2 cm); plate: 7 1/4 x 5 1/4 in. (18.4 x 13.3 cm),"Purchase, Arthur Ochs Sulzberger Gift, 2016",,Paris,,,,,,,,,,Works on Paper-Engravings,,http://www.metmuseum.org/art/collection/search/722414,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2016.403.7,false,true,722417,Arms and Armor,Engraving,Plate Eleven from Nouveavx Desseins D'Arquebvseries,"French, Paris",,,,,Engraver,,Gilles Demarteau,"French, Liège 1722–1776 Paris",,"Demarteau, Gilles",,1722,1776,ca. 1749,1724,1774,Engraving,sheet: 11 1/2 x 9 1/4 in. (29.2 x 23.5 cm); plate: 8 7/16 x 6 7/16 in. (21.4 x 16.3 cm),"Purchase, Arthur Ochs Sulzberger Gift, 2016",,Paris,,,,,,,,,,Works on Paper-Engravings,,http://www.metmuseum.org/art/collection/search/722417,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2016.403.18,false,true,722508,Arms and Armor,Engraving,Plate Nine from Nouveavx Desseins D'Arquebvseries,"French, Paris",,,,,Engraver,,Gilles Demarteau,"French, Liège 1722–1776 Paris",,"Demarteau, Gilles",,1722,1776,dated 1743,1743,1743,Engraving,sheet: 8 1/2 x 6 5/8 in. (21.6 x 16.8 cm); plate: 7 1/4 x 5 1/4 in. (18.4 x 13.3 cm),"Purchase, Arthur Ochs Sulzberger Gift, 2016",,Paris,,,,,,,,,,Works on Paper-Engravings,,http://www.metmuseum.org/art/collection/search/722508,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2016.403.20,false,true,722509,Arms and Armor,Engraving,Plate Eleven from Nouveavx Desseins D'Arquebvseries,"French, Paris",,,,,Engraver,,Gilles Demarteau,"French, Liège 1722–1776 Paris",,"Demarteau, Gilles",,1722,1776,ca. 1749,1724,1774,Engraving,sheet: 9 x 7 in. (22.9 x 17.8 cm); plate: 8 7/16 x 6 7/16 in. (21.4 x 16.3 cm),"Purchase, Arthur Ochs Sulzberger Gift, 2016",,Paris,,,,,,,,,,Works on Paper-Engravings,,http://www.metmuseum.org/art/collection/search/722509,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2016.403.21,false,true,722510,Arms and Armor,Engraving,Plate Eight from Nouveavx Desseins D'Arquebvseries,"French, Paris",,,,,Engraver,,Gilles Demarteau,"French, Liège 1722–1776 Paris",,"Demarteau, Gilles",,1722,1776,dated 1743,1743,1743,Engraving,sheet: 8 5/8 x 6 7/8 (21.9 x 17.5 cm); plate: 7 7/16 x 5 1/4 in. (18.9 x 13.3 cm),"Purchase, Arthur Ochs Sulzberger Gift, 2016",,Paris,,,,,,,,,,Works on Paper-Engravings,,http://www.metmuseum.org/art/collection/search/722510,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2016.403.8,false,true,722420,Arms and Armor,Etching and engraving,"Design of a Flintlock, Side Plate, Butt Plate, and Trigger Guard, unnumbered plate from Nouveaux Desseins d'Arquebuserie Inventez et Gravez par Le Sr. Gillot","French, Paris",,,,,Engraver,,Claude Gillot,"French, Langres 1673–1722 Paris",,"Gillot, Claude",,1673,1722,ca. 1715,1690,1740,"Etching, engraving",Sheet: 9 5/8 x 6 9/16 in. (24.4 x 16.6 cm),"Purchase, Arthur Ochs Sulzberger Gift, 2016",,Paris,,,,,,,,,,Works on Paper-Engravings,,http://www.metmuseum.org/art/collection/search/722420,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2016.403.2,false,true,722405,Arms and Armor,Engraving,Plate Two from Nouveavx Desseins D'Arquebvseries,"French, Paris",,,,,Engraver,,De Lacollombe,"French, Paris, active ca. 1702–ca. 1736",,"Lacollombe, De",,1702,1736,dated 1730,1730,1730,Engraving,sheet: 11 7/8 x 9 in. (30.2 x 22.9 cm); plate: 9 3/8 x 6 5/8 (23.8 x 16.8 cm),"Purchase, Arthur Ochs Sulzberger Gift, 2016",,Paris,,,,,,,,,,Works on Paper-Engravings,,http://www.metmuseum.org/art/collection/search/722405,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2016.403.3,false,true,722406,Arms and Armor,Engraving,Plate Four from Nouveavx Desseins D'Arquebvseries,"French, Paris",,,,,Engraver,,De Lacollombe,"French, Paris, active ca. 1702–ca. 1736",,"Lacollombe, De",,1702,1736,ca. 1730,1705,1755,Engraving,sheet: 11 3/4 x 9 1/8 in. (29.8 x 23.2 cm); plate: 9 1/4 x 6 5/8 in. (23.5 x 16.8 cm),"Purchase, Arthur Ochs Sulzberger Gift, 2016",,Paris,,,,,,,,,,Works on Paper-Engravings,,http://www.metmuseum.org/art/collection/search/722406,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2016.403.4,false,true,722409,Arms and Armor,Engraving,Plate Seven from Nouveavx Desseins D'Arquebvseries,"French, Paris",,,,,Engraver,,De Lacollombe,"French, Paris, active ca. 1702–ca. 1736",,"Lacollombe, De",,1702,1736,ca. 1730,1705,1755,Engraving,sheet: 12 x 9 in. (30.5 x 22.9 cm); plate: 9 3/8 x 6 3/8 in. (23.8 x 16.2 cm),"Purchase, Arthur Ochs Sulzberger Gift, 2016",,Paris,,,,,,,,,,Works on Paper-Engravings,,http://www.metmuseum.org/art/collection/search/722409,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2016.403.19,false,true,716437,Arms and Armor,Engraving,Plate Three from Nouveavx Desseins D'Arquebvseries,"French, Paris",,,,,Engraver,,De Lacollombe,"French, Paris, active ca. 1702–ca. 1736",,"Lacollombe, De",,1702,1736,ca. 1730,1705,1755,Engraving,sheet: 8 x 6 3/8 in. (20.3 x 16.2 cm),"Purchase, Arthur Ochs Sulzberger Gift, 2016",,Paris,,,,,,,,,,Works on Paper-Engravings,,http://www.metmuseum.org/art/collection/search/716437,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2016.403.1,false,true,722401,Arms and Armor,Engraving,Plate One (Title Page) from Nouveavx Desseins D'Arquebvseries,"French, Paris",,,,,Engraver|Engraver,,Gilles Demarteau|De Lacollombe,"French, Liège 1722–1776 Paris|French, Paris, active ca. 1702–ca. 1736",,"Demarteau, Gilles|Lacollombe, De",,1722 |1702,1776 |1736,dated 1730,1730,1730,Engraving,sheet: 11 1/2 x 9 in. (29.2 x 22.9 cm); plate: 9 3/8 x 6 5/8 in. (23.8 x 16.8 cm),"Purchase, Arthur Ochs Sulzberger Gift, 2016",,,,,,,,,,,,Works on Paper-Engravings,,http://www.metmuseum.org/art/collection/search/722401,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2013.905,.906;2016.311,.312,.403.1–.7,.409",false,true,726774,Arms and Armor,Engravings,Nouveavx Desseins D'Arquebvseries,"French, Paris",,,,,Engraver|Engraver,,Gilles Demarteau|De Lacollombe,"French, Liège 1722–1776 Paris|French, Paris, active ca. 1702–ca. 1736",,"Demarteau, Gilles|Lacollombe, De",,1722 |1702,1776 |1736,ca. 1705–49,1680,1774,Engravings,plate 1 (2016.403.1): sheet: 11 1/2 x 9 in. (29.2 x 22.9 cm); plate: 9 3/8 x 6 5/8 in. (23.8 x 16.8 cm); plate 2 (2016.403.2): sheet: 11 7/8 x 9 in. (30.2 x 22.9 cm); plate: 9 3/8 x 6 5/8 (23.8 x 16.8 cm); plate 3 (2013.906): 10 1/2 x 7 1/2 in. (26.7 x 19.1 cm); plate 4 (2016.403.3): sheet: 11 3/4 x 9 1/8 in. (29.8 x 23.2 cm); plate: 9 1/4 x 6 5/8 in. (23.5 x 16.8 cm); plate 5 (2016.409): sheet: 11 1/2 x 9 in. (29.2 x 22.9 cm); plate: 9 3/8 x 6 5/8 in. (23.8 x 16.8 cm); plate 6 (2016.311): sheet: 11 7/8 x 9 in. (30.16 x 22.86 cm); plate: 9 1/8 x 6 3/8 in. (23.17 x 16.19 cm); plate 7 (2016.403.4): sheet: 12 x 9 in. (30.5 x 22.9 cm); plate: 9 3/8 x 6 3/8 in. (23.8 x 16.2 cm); plate 8 (2016.403.5): sheet: 11 7/8 x 9 1/16 in. (30.2 x 23 cm); plate: 7 7/16 x 5 1/4 in. (18.9 x 13.3 cm); plate 9 (2016.403.5): sheet: 11 3/4 x 9 1/8 in. (29.8 x 23.2 cm); plate: 7 1/4 x 5 1/4 in. (18.4 x 13.3 cm); plate 10 (2016.312): sheet: 11 7/8 x 9 1/8 in. (30.16 x 23.17 cm); plate: 8 1/2 x 6 3/8 in. (21.59 x 16.19 cm); plate 11 (2016.403.7): sheet: 11 1/2 x 9 1/4 in. (29.2 x 23.5 cm); plate: 8 7/16 x 6 7/16 in. (21.4 x 16.3 cm); plate 12 (2013.905): 7 1/2 x 9 3/8 in. (19 x 23.8 cm),"2013.905, .906: Purchase, Bequest of Stephen V. Grancsay, Rogers Fund, Helmut Nickel Gift, and funds from various donors, by exchange, 2013; 2016.311, .312: Purchase, Michael H. Pourfar Gift, 2016; 2016.403.1–.7: Purchase, Arthur Ochs Sulzberger Gift, 2016; 2016.409: Purchase, Marica F. Vilcek Gift, 2016",,Paris,,,,,,,,,,Works on Paper-Engravings,,http://www.metmuseum.org/art/collection/search/726774,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2016.403.9,false,true,722489,Arms and Armor,Engraving,Plate One from Plusieurs Models des plus nouuelles manieres qui sont en usage en l'Art de Arquebuzerie,"French, Paris",,,,,Publisher|Publisher|Engraver,,Le Hollandois|Thuraine|C. Jacquinet,"French, Paris, active mid-17th century|French, Paris, active mid-17th century|French, Paris, active mid-17th century",,"Hollandois, Le|Thuraine|Jacquinet, C.",,1625 |1625 |1625,1675 |1675 |1675,ca. 1660,1635,1685,Engraving,sheet: 7 x 5 in. (17.8 x 12.7 cm),"Purchase, Arthur Ochs Sulzberger Gift, 2016",,Paris,,,,,,,,,,Works on Paper-Engravings,,http://www.metmuseum.org/art/collection/search/722489,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2016.403.10,false,true,722495,Arms and Armor,Engraving,Plate Two from Plusieurs Models des plus nouuelles manieres qui sont en usage en l'Art de Arquebuzerie,"French, Paris",,,,,Publisher|Publisher|Engraver,,Le Hollandois|Thuraine|C. Jacquinet,"French, Paris, active mid-17th century|French, Paris, active mid-17th century|French, Paris, active mid-17th century",,"Hollandois, Le|Thuraine|Jacquinet, C.",,1625 |1625 |1625,1675 |1675 |1675,ca. 1660,1635,1685,Engraving,sheet: 7 7/8 x 5 3/4 in. (20 x 14.6 cm); plate: 7 7/16 x 5 5/16 in. (18.9 x 13.5 cm),"Purchase, Arthur Ochs Sulzberger Gift, 2016",,Paris,,,,,,,,,,Works on Paper-Engravings,,http://www.metmuseum.org/art/collection/search/722495,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2016.403.11,false,true,722497,Arms and Armor,Engraving,Plate Three from Plusieurs Models des plus nouuelles manieres qui sont en usage en l'Art de Arquebuzerie,"French, Paris",,,,,Engraver|Publisher|Publisher,,C. Jacquinet|Le Hollandois|Thuraine,"French, Paris, active mid-17th century|French, Paris, active mid-17th century|French, Paris, active mid-17th century",,"Jacquinet, C.|Hollandois, Le|Thuraine",,1625 |1625 |1625,1675 |1675 |1675,ca. 1660,1635,1685,Engraving,sheet: 7 x 5 1/8 in. (17.8 x 13 cm),"Purchase, Arthur Ochs Sulzberger Gift, 2016",,Paris,,,,,,,,,,Works on Paper-Engravings,,http://www.metmuseum.org/art/collection/search/722497,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2016.403.12,false,true,722499,Arms and Armor,Engraving,Plate Five from Plusieurs Models des plus nouuelles manieres qui sont en usage en l'Art de Arquebuzerie,"French, Paris",,,,,Publisher|Engraver|Publisher,,Le Hollandois|C. Jacquinet|Thuraine,"French, Paris, active mid-17th century|French, Paris, active mid-17th century|French, Paris, active mid-17th century",,"Hollandois, Le|Jacquinet, C.|Thuraine",,1625 |1625 |1625,1675 |1675 |1675,ca. 1660,1635,1685,Engraving,sheet: 7 1/4 x 5 1/4 in. (18.4 x 13.3 cm),"Purchase, Arthur Ochs Sulzberger Gift, 2016",,Paris,,,,,,,,,,Works on Paper-Engravings,,http://www.metmuseum.org/art/collection/search/722499,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2016.403.13,false,true,722500,Arms and Armor,Engraving,Plate Six from Plusieurs Models des plus nouuelles manieres qui sont en usage en l'Art de Arquebuzerie,"French, Paris",,,,,Publisher|Publisher|Engraver,,Thuraine|Le Hollandois|C. Jacquinet,"French, Paris, active mid-17th century|French, Paris, active mid-17th century|French, Paris, active mid-17th century",,"Thuraine|Hollandois, Le|Jacquinet, C.",,1625 |1625 |1625,1675 |1675 |1675,ca. 1660,1635,1685,Engraving,sheet: 7 1/16 x 5 3/8 in. (17.9 x 13.7 cm),"Purchase, Arthur Ochs Sulzberger Gift, 2016",,Paris,,,,,,,,,,Works on Paper-Engravings,,http://www.metmuseum.org/art/collection/search/722500,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2016.403.14,false,true,722502,Arms and Armor,Engraving,Plate Seven from Plusieurs Models des plus nouuelles manieres qui sont en usage en l'Art de Arquebuzerie,"French, Paris",,,,,Publisher|Publisher|Engraver,,Thuraine|Le Hollandois|C. Jacquinet,"French, Paris, active mid-17th century|French, Paris, active mid-17th century|French, Paris, active mid-17th century",,"Thuraine|Hollandois, Le|Jacquinet, C.",,1625 |1625 |1625,1675 |1675 |1675,ca. 1660,1635,1685,Engraving,sheet: 7 1/2 x 5 1/2 in. (19.1 x 14 cm),"Purchase, Arthur Ochs Sulzberger Gift, 2016",,Paris,,,,,,,,,,Works on Paper-Engravings,,http://www.metmuseum.org/art/collection/search/722502,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2016.403.15,false,true,722503,Arms and Armor,Engraving,Plate Eight from Plusieurs Models des plus nouuelles manieres qui sont en usage en l'Art de Arquebuzerie,"French, Paris",,,,,Publisher|Publisher|Engraver,,Le Hollandois|Thuraine|C. Jacquinet,"French, Paris, active mid-17th century|French, Paris, active mid-17th century|French, Paris, active mid-17th century",,"Hollandois, Le|Thuraine|Jacquinet, C.",,1625 |1625 |1625,1675 |1675 |1675,ca. 1660,1635,1685,Engraving,sheet: 7 1/16 x 5 1/8 in. (17.9 x 13 cm),"Purchase, Arthur Ochs Sulzberger Gift, 2016",,Paris,,,,,,,,,,Works on Paper-Engravings,,http://www.metmuseum.org/art/collection/search/722503,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2016.403.16,false,true,722505,Arms and Armor,Engraving,Plate Nine from Plusieurs Models des plus nouuelles manieres qui sont en usage en l'Art de Arquebuzerie,"French, Paris",,,,,Publisher|Publisher|Engraver,,Le Hollandois|Thuraine|C. Jacquinet,"French, Paris, active mid-17th century|French, Paris, active mid-17th century|French, Paris, active mid-17th century",,"Hollandois, Le|Thuraine|Jacquinet, C.",,1625 |1625 |1625,1675 |1675 |1675,ca. 1660,1635,1685,Engraving,sheet: 7 1/8 x 5 in. (18.1 x 5 cm),"Purchase, Arthur Ochs Sulzberger Gift, 2016",,Paris,,,,,,,,,,Works on Paper-Engravings,,http://www.metmuseum.org/art/collection/search/722505,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2016.403.17,false,true,722506,Arms and Armor,Engraving,Plate Ten from Plusieurs Models des plus nouuelles manieres qui sont en usage en l'Art de Arquebuzerie,"French, Paris",,,,,Publisher|Engraver|Publisher,,Le Hollandois|C. Jacquinet|Thuraine,"French, Paris, active mid-17th century|French, Paris, active mid-17th century|French, Paris, active mid-17th century",,"Hollandois, Le|Jacquinet, C.|Thuraine",,1625 |1625 |1625,1675 |1675 |1675,ca. 1660,1635,1685,Engraving,sheet: 7 x 5 1/16 in. (17.8 x 12.8 cm),"Purchase, Arthur Ochs Sulzberger Gift, 2016",,Paris,,,,,,,,,,Works on Paper-Engravings,,http://www.metmuseum.org/art/collection/search/722506,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2016.698,false,true,742566,Arms and Armor,"Design for a sword hilt, scabbard, and belt fittings","Design for a Sword Hilt, Scabbard, and Belt Fittings","French, Paris",,,,,Artist,Attributed to,Eugène Julienne,"French, Paris 1808–1875 Paris",,"Julienne, Eugène",,1808,1875,ca. 1840–50,1815,1875,Watercolor and ink on paper,12 3/8 x 18 3/4 in. (32 x 48 cm),"Purchase, Kenneth and Vivian Lam Gift, 2016",,Paris,,,,,,,,,,Works on Paper-Drawings,,http://www.metmuseum.org/art/collection/search/742566,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2016.409,false,true,718296,Arms and Armor,Plate five from Nouveavx Desseins D'Arquebvseries,Plate Five from Nouveavx Desseins D'Arquebvseries,"French, Paris",,,,,Artist,,De Lacollombe,"French, Paris, active ca. 1702–ca. 1736",,"Lacollombe, De",,1702,1736,dated 1730,1730,1730,Engraving,sheet: 11 1/2 x 9 in. (29.2 x 22.9 cm); plate: 9 3/8 x 6 5/8 in. (23.8 x 16.8 cm),"Purchase, Marica F. Vilcek Gift, 2016",,Paris,,,,,,,,,,Works on Paper-Engravings,,http://www.metmuseum.org/art/collection/search/718296,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2004.118.1, .2",false,true,27017,Arms and Armor,Pair of tubelock hammers,Pair of Tubelock Hammers,"British, London",,,,,Gunsmith,,Joseph Manton,"British, Grantham, Lincolnshire 1766–1835 London",,Manton Joseph,,1766,1835,ca. 1819–20,1819,1820,Steel,H. of each 2 5/16 in. (6 cm); Wt. of each 1.1 oz (31 g),"Purchase, Fletcher Fund, by exchange",,London,,,,,,,,,,Firearms Parts,,http://www.metmuseum.org/art/collection/search/27017,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -35.81.3a–f,false,true,32180,Arms and Armor,Repeating flintlock pistol,"Flintlock Repeating Pistol with Lorenzoni Action, bearing the Crests of Vice Admiral Horatio Nelson, with Case and Acccessories","British, London",,,,,Gunsmith,,Harvey Walklate Mortimer,"British, Newcastle-under-Lyme 1753–1819 Hampstead-heath (now London)",,"Mortimer, Harvey Walklate",,1753,1819,ca. 1798–1799,1793,1803,"pistol: steel, wood (walnut), silver; reserve barrel: steel; bullet mould: steel; punch; steel; wrench: steel; case: wood (mahogany), brass, textile, paper",Pistol (a); L. 14 1/2 in. (36.8 cm); L. of barrel 6 in. (15.2 cm); Cal. .55 in. (14 mm); Wt. 3 lb. 15 oz. (1786 g); reserve barrel (b); L. 6 1/8 in. (15.6 cm); Wt. 14.4 oz. (408.2 g); bullet mould (c); L. 5 3/8 in. (13.7 cm); Wt. 2.6 oz. (73.7 g); punch (d); L. 5 1/2 in. (14 cm); Wt. 4.4 oz. (124.7 g); wrench (e); L. 3 3/4 in. (9.5 cm); Wt. 1.6 oz. (45.4 g); case (f); H. 4 1/8 in. (10.5 cm); W. 15 9/16 in. (39.5 cm); D. 7 5/8 in. (19.4 cm); Wt. 6 lb. 7.2 oz. (2925.7 g),"Bequest of Charles N. Daly, 1934",,London,,,,,,,,,,Firearms-Pistols-Flintlock,,http://www.metmuseum.org/art/collection/search/32180,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -26.145.343,false,true,22943,Arms and Armor,Smallsword,Smallsword,"Dutch, Amsterdam",,,,,Sword maker,,Jan Nieuwland,"Dutch, Amsterdam, active 1747–1807",,"Nieuwland, Jan",Dutch,1747,1807,ca. 1750,1725,1775,"Silver, porcelain (Meissen), steel, gold, textile",L. 34 13/16 in. (88.5 cm); L. of blade 28 3/4 in. (73 cm); W. 3 7/8 in. (9.8 cm); D. 2 3/4 in. (7 cm); Wt. 14 oz. (396.9 g),"Gift of Jean Jacques Reubell, in memory of his mother, Julia C. Coster, and of his wife, Adeline E. Post, both of New York City, 1926",,Amsterdam,,,,,,,,,,Swords,,http://www.metmuseum.org/art/collection/search/22943,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"19.53.13, .14",false,true,22732,Arms and Armor,Pair of wheellock pistols,Pair of Wheellock Pistols,"French, Lisieux",,,,,Gunsmith,,Pierre Le Bourgeois,"French, Lisieux, died 1627",,"Le Bourgeois, Pierre",French,1527,1627,ca. 1610–20,1585,1645,"Steel, gold, wood, silver, mother-of-pearl","L. of each pistol 23 5/16 in. (59.2 cm); Cal. of each pistol, .49 in. (12.5 mm); L. of each barrel 15 5/8 in. (39.7 cm); Wt. of 19.53.13, 2 lb. 8 oz. (1134 g); Wt. of 19.56.14, 2 lb. 10 oz. (1191 g)","Gift of Charles M. Schott Jr., 1917",,Lisieux,,,,,,,,,,Firearms-Pistols,,http://www.metmuseum.org/art/collection/search/22732,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.58a–e,false,true,23950,Arms and Armor,Double-barreled flintlock shotgun,Double-Barreled Flintlock Shotgun,"French, Versailles",,,,,Gunsmith,,Nicolas Noël Boutet,"French, Versailles and Paris, 1761–1833",,"Boutet, Nicholas-Noël",French,1761,1833,dated 1801,1801,1801,"Steel, wood (walnut, ebony), gold, silver",L. 48 3/8 in. (122.9 cm); L. of barrel 33 in. (83.8 cm); Cal. .60 in. (15.2 mm); Wt. 7 lb. 6 oz. (3350 g),"Rogers Fund, 1936",,Versailles,,,,,,,,,,Firearms,,http://www.metmuseum.org/art/collection/search/23950,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.179.1a–q,true,true,24861,Arms and Armor,"Cased set of a flintlock rifle, a pair of pistols, and accessories","Cased Set of a Flintlock Rifle, a Pair of Pistols, and Accessories","French, Versailles",,,,,Gunsmith,,Nicolas Noël Boutet,"French, Versailles and Paris, 1761–1833",,"Boutet, Nicholas-Noël",French,1761,1833,ca. 1800,1775,1825,"Steel, wood (walnut, mahogany), silver, gold, horn, velvet",L. of rifle 43 1/2 in. (110.5 cm); L. of barrel of rifle 27 5/8 in. (70.2 cm); Cal. of rifle .64 in. (16.3 mm); Wt. of rifle 6 lb. 7 oz. (2920 g); L. of each pistol 17 in. (43.17 cm); L. of barrel 11 5/8 in. (29.53 cm); Cal. of each pistol .52 in. (13.2 mm); Wt. of each pistol 2 lbs. 2 oz. (963.9 g); Dim. of case 46 7/16 x 15 15/16 x 2 3/16 in. (118 x 40.5 x 5.5 cm),"Fletcher Fund, 1970",,Versailles,,,,,,,,,,Firearms-Guns-Flintlock,,http://www.metmuseum.org/art/collection/search/24861,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2013.581a, b",false,true,35956,Arms and Armor,Prototype for helmet model no. 2,Prototype for Helmet Model No. 2,"American, New York",,,,,Armorer,,Daniel Tachaux,"French, 1857–1928, active in France and America",,"Tachaux, Daniel",French,1857,1928,1917,1917,1917,"Steel, pressed paper or cardboard",H. 8 in. (20.3 cm); W. 10 in. (25.4 cm); D. 13 3/8 in. (34 cm); Wt. 2 lb. 4 oz. (1020 g),"Purchase, Gift of Bashford Dean, by exchange, 2013",,New York,New York,,,,,,,,,Helmets,,http://www.metmuseum.org/art/collection/search/35956,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -11.128.1o,false,true,35902,Arms and Armor,Right thigh and knee defense (cuisse and poleyn),Right Thigh and Knee Defense (Cuisse and Poleyn) for the Armor of Sir John Scudamore (1541 or 1542–1623),"American, New York",,,,,Armorer,,Daniel Tachaux,"French, 1857–1928, active in France and America",,"Tachaux, Daniel",French,1857,1928,dated 1913,1913,1913,"Steel, gold, leather",H. 16 1/2 in. (41.9 cm); W. 7 11/16 in. (19.5 cm); D. 5 in. (12.7 cm); Wt. 2 lb. 12 oz. (1247.4 g),"Frederick C. Hewitt Fund, 1911",,New York,New York,,,,,,,,,Armor Parts,,http://www.metmuseum.org/art/collection/search/35902,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -04.3.291a–q,false,true,35928,Arms and Armor,Jousting armor,Armor for the Joust of Peace,"German; restorations, French, Paris",,,,,Armorer,Helmet (04.3.291a) made by,Daniel Tachaux,"French, 1857–1928, active in France and America",,"Tachaux, Daniel",French,1857,1928,ca. 1500 and later,1475,1960,"Steel, copper alloy, leather, textile, horn","Helmet (a); Wt. 21.5 lb. (9752 g); breastplate (c); Wt. 23 lb. (10.4 kg); tassets (e, f); Wt. of each 6 lb. (2723 g); backplate (i); Wt. 5 lb. (2268 g); rondels (o, p); Wt. of each 5 lb. (2268 g); shield (q); Wt. 6 lb. (2723 g); Wt. overall 85.5 lb. (38.78 kg)","Armor for man and Shaffron: Rogers Fund, 1904; spurs: Bashford Dean Memorial Collection, Funds from various donors, 1929; bit, stirrups, and lance coronel: Bashford Dean Memorial Collection, Bequest of Bashford Dean, 1928; vamplate for the lance: Gift of William H. Riggs, 1913",,Paris,,,,,,,,,,Armor for Man,,http://www.metmuseum.org/art/collection/search/35928,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"04.3.291a–q, .292; etc.",false,true,21926,Arms and Armor,Jousting armor,Jousting Armor,"German; restorations, French, Paris",,,,,Armorer,Helmet made by,Daniel Tachaux,"French, 1857–1928, active in France and America",,"Tachaux, Daniel",French,1857,1928,ca. 1500 and later,1475,1900,"Steel, copper alloy, leather, textile, horn","Helmet (a); Wt. 21.5 lb. (9752 g); breastplate (c); Wt. 23 lb. (10.4 kg); tassets (e, f); Wt. of each 6 lb. (2723 g); backplate (i); Wt. 5 lb. (2268 g); rondels (o, p); Wt. of each 5 lb. (2268 g); shield (q); Wt. 6 lb. (2723 g); Wt. overall 85.5 lb. (38.78 kg)","Rogers Fund, 1904",,Paris,,,,,,,,,,Armor for Horse and Man,,http://www.metmuseum.org/art/collection/search/21926,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2011.354,false,true,35703,Arms and Armor,Flintlock sporting gun,Flintlock Sporting Gun of Empress Margarita Teresa of Spain (1651–1673),"Austrian, Vienna",,,,,Gunsmith,,Jacques Lamarre,"French, recorded Paris 1657–1700 Vienna, Austria",,"Lamarre, Jacques",French,1657,1700,ca. 1670–73,1645,1698,"Steel, wood (burl walnut), silver, copper alloy, gold",L. 52 1/8 in. (132.4 cm); L. of barrel 38 3/8 in. (97.5 cm),"Purchase, Arthur Ochs Sulzberger and Irene Roosevelt Aitken Gifts, 2011",,Vienna,,,,,,,,,,Firearms-Guns-Flintlock,,http://www.metmuseum.org/art/collection/search/35703,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.223,true,true,24865,Arms and Armor,Flintlock gun,"Flintlock Gun of Louis XIII (1601–1643), King of France","French, Lisieux",,,,,Gunsmith|Gunsmith,,Pierre Le Bourgeois|Marin Le Bourgeois,"French, Lisieux, died 1627|French, Lisieux, ca. 1550–1634",,"Le Bourgeois, Pierre|Le Bourgeois, Marin",French,1527 |1550,1627 |1634,ca. 1620,1595,1645,"Steel, brass, silver, gold, wood (walnut), mother-of-pearl",L. 55 5/16 in. (140.5 cm); Cal. .59 in. (55 mm); L. of barrel 41 in. (104.1 cm); L. of lockplate 6 9/16 in. (16.7 cm); Wt. 5 lb. 11 oz. (2580 g),"Rogers Fund and Harris Brisbane Dick Fund, 1972",,Lisieux,,,,,,,,,,Firearms-Guns-Flintlock,,http://www.metmuseum.org/art/collection/search/24865,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2004.101.1,false,true,652955,Arms and Armor,Drawings,Design for the Decoration of the Grip of a Pocket Pistol,"French, Versailles",,,,,Designer,Workshop of,Nicolas Noël Boutet,"French, Versailles and Paris, 1761–1833",,"Boutet, Nicholas-Noël",French,1761,1833,ca. 1795–1805,1770,1830,"Pencil, ink, gray wash on paper",3 3/8 x 7 7/16 in. (8.6 x 18.9 cm),"Purchase, Clay P. Bedford Gift, 2004",,Versailles,,,,,,,,,,Works on Paper-Drawings,,http://www.metmuseum.org/art/collection/search/652955,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2004.101.2,false,true,652958,Arms and Armor,Drawings,Design for the Decoration of the Grip of a Pocket Pistol,"French, Versailles",,,,,Designer,Workshop of,Nicolas Noël Boutet,"French, Versailles and Paris, 1761–1833",,"Boutet, Nicholas-Noël",French,1761,1833,ca. 1797–1805,1772,1830,"Pencil, ink, gray wash on paper",3 5/8 x 2 13/16 in. (9.2 x 7.1 cm),"Purchase, Clay P. Bedford Gift, 2004",,Versailles,,,,,,,,,,Works on Paper-Drawings,,http://www.metmuseum.org/art/collection/search/652958,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2004.101.3,false,true,652959,Arms and Armor,Drawings,Design for the Decoration of the Grip of a Pocket Pistol,"French, Versailles",,,,,Designer,Workshop of,Nicolas Noël Boutet,"French, Versailles and Paris, 1761–1833",,"Boutet, Nicholas-Noël",French,1761,1833,ca. 1797–1805,1772,1830,"Pencil, ink, gray wash on paper",4 x 3 1/2 in. (10.2 x 8.9 cm),"Purchase, Clay P. Bedford Gift, 2004",,Versailles,,,,,,,,,,Works on Paper-Drawings,,http://www.metmuseum.org/art/collection/search/652959,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2004.101.4,false,true,652960,Arms and Armor,Drawings,Design for the Decoration of the Grip of a Pocket Pistol,"French, Versailles",,,,,Designer,Workshop of,Nicolas Noël Boutet,"French, Versailles and Paris, 1761–1833",,"Boutet, Nicholas-Noël",French,1761,1833,ca. 1797–1805,1772,1830,"Pencil, ink, gray wash on paper",5 5/8 x 4 in. (14.3 x 10.2 cm),"Purchase, Clay P. Bedford Gift, 2004",,Versailles,,,,,,,,,,Works on Paper-Drawings,,http://www.metmuseum.org/art/collection/search/652960,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2004.101.5,false,true,652961,Arms and Armor,Drawings,Design for the Decoration of the Side Plate of a Firearm,"French, Versailles",,,,,Designer,Workshop of,Nicolas Noël Boutet,"French, Versailles and Paris, 1761–1833",,"Boutet, Nicholas-Noël",French,1761,1833,ca. 1797–1805,1772,1830,"Pencil, ink, gray wash on paper",2 3/4 x 6 1/4 in. (7 x 15.9 cm),"Purchase, Clay P. Bedford Gift, 2004",,Versailles,,,,,,,,,,Works on Paper-Drawings,,http://www.metmuseum.org/art/collection/search/652961,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2004.101.6,false,true,652962,Arms and Armor,Drawings,Design for the Decoration of the Side Plate of a Firearm,"French, Versailles",,,,,Designer,Workshop of,Nicolas Noël Boutet,"French, Versailles and Paris, 1761–1833",,"Boutet, Nicholas-Noël",French,1761,1833,ca. 1797–1805,1772,1830,"Pencil, ink, gray wash on paper",1 7/16 x 5 in. (3.7 x 12.7 cm),"Purchase, Clay P. Bedford Gift, 2004",,Versailles,,,,,,,,,,Works on Paper-Drawings,,http://www.metmuseum.org/art/collection/search/652962,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2004.101.7,false,true,652963,Arms and Armor,Drawings,Designs for the Decoration of Firearms,"French, Versailles",,,,,Designer,Workshop of,Nicolas Noël Boutet,"French, Versailles and Paris, 1761–1833",,"Boutet, Nicholas-Noël",French,1761,1833,ca. 1797–1805,1772,1830,"Pencil, ink, gray wash on paper",3 3/16 x 7 3/4 in. (8.1 x 19.7 cm),"Purchase, Clay P. Bedford Gift, 2004",,Versailles,,,,,,,,,,Works on Paper-Drawings,,http://www.metmuseum.org/art/collection/search/652963,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2004.101.8,false,true,652964,Arms and Armor,Drawings,Designs for the Decoration of the Frizzen and Jaws of the Cock of a Firearm,"French, Versailles",,,,,Designer,Workshop of,Nicolas Noël Boutet,"French, Versailles and Paris, 1761–1833",,"Boutet, Nicholas-Noël",French,1761,1833,ca. 1797–1805,1772,1830,"Pencil, ink, gray wash on paper",3 1/2 x 1 1/2 in. (8.9 x 3.8 cm),"Purchase, Clay P. Bedford Gift, 2004",,Versailles,,,,,,,,,,Works on Paper-Drawings,,http://www.metmuseum.org/art/collection/search/652964,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2004.101.9,false,true,652965,Arms and Armor,Drawings,Designs for the Decoration of the Jaw and Profile of the Cock of a Firearm,"French, Versailles",,,,,Designer,Workshop of,Nicolas Noël Boutet,"French, Versailles and Paris, 1761–1833",,"Boutet, Nicholas-Noël",French,1761,1833,ca. 1797–1805,1772,1830,"Pencil, ink, gray wash on paper",2 1/4 x 3 in. (5.7 x 7.6 cm),"Purchase, Clay P. Bedford Gift, 2004",,Versailles,,,,,,,,,,Works on Paper-Drawings,,http://www.metmuseum.org/art/collection/search/652965,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2004.101.10,false,true,652966,Arms and Armor,Drawings,Designs for the Decoration of the Jaw and Profile of the Cock of a Firearm,"French, Versailles",,,,,Designer,Workshop of,Nicolas Noël Boutet,"French, Versailles and Paris, 1761–1833",,"Boutet, Nicholas-Noël",French,1761,1833,ca. 1795–1805,1770,1830,"Pencil, ink, gray wash on paper",2 1/4 x 3 1/8 in. (5.7 x 7.9 cm),"Purchase, Clay P. Bedford Gift, 2004",,Versailles,,,,,,,,,,Works on Paper-Drawings,,http://www.metmuseum.org/art/collection/search/652966,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2004.101.11,false,true,652967,Arms and Armor,Drawings,Design for the Decoration of the Jaw of the Cock of a Firearm,"French, Versailles",,,,,Designer,Workshop of,Nicolas Noël Boutet,"French, Versailles and Paris, 1761–1833",,"Boutet, Nicholas-Noël",French,1761,1833,ca. 1795–1805,1770,1830,"Pencil, ink, gray wash on paper",1 x 1 in. (2.5 x 2.5 cm),"Purchase, Clay P. Bedford Gift, 2004",,Versailles,,,,,,,,,,Works on Paper-Drawings,,http://www.metmuseum.org/art/collection/search/652967,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2004.101.12,false,true,652968,Arms and Armor,Drawings,Design for the Decoration of Firearms,"French, Versailles",,,,,Designer,Workshop of,Nicolas Noël Boutet,"French, Versailles and Paris, 1761–1833",,"Boutet, Nicholas-Noël",French,1761,1833,ca. 1795–1805,1770,1830,"Pencil, ink, gray wash on paper",6 3/4 x 3 1/2 in. (17.1 x 8.9 cm),"Purchase, Clay P. Bedford Gift, 2004",,Versailles,,,,,,,,,,Works on Paper-Drawings,,http://www.metmuseum.org/art/collection/search/652968,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2004.101.13,false,true,652969,Arms and Armor,Drawings,Design for the Decoration of Firearms,"French, Versailles",,,,,Designer,Workshop of,Nicolas Noël Boutet,"French, Versailles and Paris, 1761–1833",,"Boutet, Nicholas-Noël",French,1761,1833,ca. 1795–1805,1770,1830,"Pencil, ink, gray wash on paper",6 1/2 x 3 in. (16.5 x 7.6 cm),"Purchase, Clay P. Bedford Gift, 2004",,Versailles,,,,,,,,,,Works on Paper-Drawings,,http://www.metmuseum.org/art/collection/search/652969,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2004.101.14,false,true,652970,Arms and Armor,Drawings,Design for the Decoration of a Gun Stock,"French, Versailles",,,,,Designer,Workshop of,Nicolas Noël Boutet,"French, Versailles and Paris, 1761–1833",,"Boutet, Nicholas-Noël",French,1761,1833,ca. 1795–1805,1770,1830,"Pencil, ink, gray wash on paper",3 1/4 x 7 1/8 in. (8.3 x 18.1 cm),"Purchase, Clay P. Bedford Gift, 2004",,Versailles,,,,,,,,,,Works on Paper-Drawings,,http://www.metmuseum.org/art/collection/search/652970,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2004.101.15,false,true,652971,Arms and Armor,Drawings,Design for the Decoration of Firearms,"French, Versailles",,,,,Designer,Workshop of,Nicolas Noël Boutet,"French, Versailles and Paris, 1761–1833",,"Boutet, Nicholas-Noël",French,1761,1833,ca. 1795–1805,1770,1830,"Pencil, ink, gray wash on paper",2 7/8 x 2 1/2 in. (7.3 x 6.4 cm),"Purchase, Clay P. Bedford Gift, 2004",,Versailles,,,,,,,,,,Works on Paper-Drawings,,http://www.metmuseum.org/art/collection/search/652971,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2004.101.16,false,true,652972,Arms and Armor,Drawings,Design for the Decoration of Firearms,"French, Versailles",,,,,Designer,Workshop of,Nicolas Noël Boutet,"French, Versailles and Paris, 1761–1833",,"Boutet, Nicholas-Noël",French,1761,1833,ca. 1795–1805,1770,1830,"Pencil, ink, gray wash on paper",3 3/8 x 4 5/8 in. (8.6 x 11.7 cm),"Purchase, Clay P. Bedford Gift, 2004",,Versailles,,,,,,,,,,Works on Paper-Drawings,,http://www.metmuseum.org/art/collection/search/652972,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2004.101.17,false,true,652973,Arms and Armor,Drawings,Design for the Decoration of the Surround of the Ramrod Pipe of a Firearm,"French, Versailles",,,,,Designer,Workshop of,Nicolas Noël Boutet,"French, Versailles and Paris, 1761–1833",,"Boutet, Nicholas-Noël",French,1761,1833,ca. 1795–1805,1770,1830,"Pencil, ink, gray wash on paper",2 5/8 x 3 7/8 in. (6.7 x 9.8 cm),"Purchase, Clay P. Bedford Gift, 2004",,Versailles,,,,,,,,,,Works on Paper-Drawings,,http://www.metmuseum.org/art/collection/search/652973,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2004.101.18,false,true,652974,Arms and Armor,Drawings,Design for the Decoration of the Surround of the Ramrod Pipe of a Firearm,"French, Versailles",,,,,Designer,Workshop of,Nicolas Noël Boutet,"French, Versailles and Paris, 1761–1833",,"Boutet, Nicholas-Noël",French,1761,1833,ca. 1795–1805,1770,1830,"Pencil, ink, gray wash on paper",3 1/4 x 1 7/8 in. (8.3 x 4.8 cm),"Purchase, Clay P. Bedford Gift, 2004",,Versailles,,,,,,,,,,Works on Paper-Drawings,,http://www.metmuseum.org/art/collection/search/652974,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2004.101.19,false,true,652975,Arms and Armor,Drawings,Design for the Decoration of the Surround of the Barrel Tang of a Firearm,"French, Versailles",,,,,Designer,Workshop of,Nicolas Noël Boutet,"French, Versailles and Paris, 1761–1833",,"Boutet, Nicholas-Noël",French,1761,1833,ca. 1795–1805,1770,1830,"Pencil, ink, gray wash on paper",5 1/8 x 3 1/8 in. (13 x 7.9 cm),"Purchase, Clay P. Bedford Gift, 2004",,Versailles,,,,,,,,,,Works on Paper-Drawings,,http://www.metmuseum.org/art/collection/search/652975,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2004.101.20,false,true,652978,Arms and Armor,Drawings,Designs for the Decoration of Firearms,"French, Versailles",,,,,Designer,Workshop of,Nicolas Noël Boutet,"French, Versailles and Paris, 1761–1833",,"Boutet, Nicholas-Noël",French,1761,1833,ca. 1797–1805,1772,1830,"Pencil, ink, gray wash on paper",6 1/8 x 8 5/8 in. (15.6 x 21.9 cm),"Purchase, Clay P. Bedford Gift, 2004",,Versailles,,,,,,,,,,Works on Paper-Drawings,,http://www.metmuseum.org/art/collection/search/652978,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2004.101.21,false,true,652980,Arms and Armor,Drawings,Design for the Decoration of Firearms,"French, Versailles",,,,,Designer,Workshop of,Nicolas Noël Boutet,"French, Versailles and Paris, 1761–1833",,"Boutet, Nicholas-Noël",French,1761,1833,ca. 1797–1805,1772,1830,"Pencil, ink, gray wash on paper",3 3/4 x 1 3/4 in. (9.5 x 4.5 cm),"Purchase, Clay P. Bedford Gift, 2004",,Versailles,,,,,,,,,,Works on Paper-Drawings,,http://www.metmuseum.org/art/collection/search/652980,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2004.101.22,false,true,652981,Arms and Armor,Drawings,Designs for the Decoration of Firearms,"French, Versailles",,,,,Designer,Workshop of,Nicolas Noël Boutet,"French, Versailles and Paris, 1761–1833",,"Boutet, Nicholas-Noël",French,1761,1833,ca. 1797–1805,1772,1830,"Pencil, ink, gray wash on paper",4 1/8 x 3 3/4 in. (10.5 x 9.5 cm),"Purchase, Clay P. Bedford Gift, 2004",,Versailles,,,,,,,,,,Works on Paper-Drawings,,http://www.metmuseum.org/art/collection/search/652981,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2004.101.23,false,true,652982,Arms and Armor,Drawings,Design for the Decoration of Firearms,"French, Versailles",,,,,Designer,Workshop of,Nicolas Noël Boutet,"French, Versailles and Paris, 1761–1833",,"Boutet, Nicholas-Noël",French,1761,1833,ca. 1797–1805,1772,1830,"Pencil, ink, gray wash on paper",4 1/8 x 2 1/2 in. (10.5 x 6.4 cm),"Purchase, Clay P. Bedford Gift, 2004",,Versailles,,,,,,,,,,Works on Paper-Drawings,,http://www.metmuseum.org/art/collection/search/652982,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2004.101.24,false,true,652983,Arms and Armor,Drawings,Design for the Decoration of Firearms,"French, Versailles",,,,,Designer,Workshop of,Nicolas Noël Boutet,"French, Versailles and Paris, 1761–1833",,"Boutet, Nicholas-Noël",French,1761,1833,ca. 1797–1805,1772,1830,"Pencil, ink, gray wash on paper",4 1/2 x 1 3/4 in. (11.4 x 4.5 cm),"Purchase, Clay P. Bedford Gift, 2004",,Versailles,,,,,,,,,,Works on Paper-Drawings,,http://www.metmuseum.org/art/collection/search/652983,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2004.101.25,false,true,652984,Arms and Armor,Drawings,Design for the Decoration of Firearms,"French, Versailles",,,,,Designer,Workshop of,Nicolas Noël Boutet,"French, Versailles and Paris, 1761–1833",,"Boutet, Nicholas-Noël",French,1761,1833,ca. 1797–1805,1772,1830,"Pencil, ink, gray wash on paper",2 3/4 x 2 5/8 in. (7 x 6.7 cm),"Purchase, Clay P. Bedford Gift, 2004",,Versailles,,,,,,,,,,Works on Paper-Drawings,,http://www.metmuseum.org/art/collection/search/652984,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2004.101.26,false,true,652985,Arms and Armor,Drawings,Design for the Decoration of Firearms,"French, Versailles",,,,,Designer,Workshop of,Nicolas Noël Boutet,"French, Versailles and Paris, 1761–1833",,"Boutet, Nicholas-Noël",French,1761,1833,ca. 1797–1805,1772,1830,"Pencil, ink, gray wash on paper",1 5/8 x 2 in. (4.1 x 5.1 cm),"Purchase, Clay P. Bedford Gift, 2004",,Versailles,,,,,,,,,,Works on Paper-Drawings,,http://www.metmuseum.org/art/collection/search/652985,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2004.101.27,false,true,652986,Arms and Armor,Drawings,Design for the Decoration of Firearms,"French, Versailles",,,,,Designer,Workshop of,Nicolas Noël Boutet,"French, Versailles and Paris, 1761–1833",,"Boutet, Nicholas-Noël",French,1761,1833,ca. 1797–1805,1772,1830,"Pencil, ink, gray wash on paper",2 1/8 x 1 7/8 in. (5.4 x 4.8 cm),"Purchase, Clay P. Bedford Gift, 2004",,Versailles,,,,,,,,,,Works on Paper-Drawings,,http://www.metmuseum.org/art/collection/search/652986,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2004.101.28,false,true,652987,Arms and Armor,Drawings,Design for the Decoration of Firearms,"French, Versailles",,,,,Designer,Workshop of,Nicolas Noël Boutet,"French, Versailles and Paris, 1761–1833",,"Boutet, Nicholas-Noël",French,1761,1833,ca. 1797–1805,1772,1830,"Pencil, ink, gray wash on paper",1 1/8 x 1 3/4 in. (2.9 x 4.5 cm),"Purchase, Clay P. Bedford Gift, 2004",,Versailles,,,,,,,,,,Works on Paper-Drawings,,http://www.metmuseum.org/art/collection/search/652987,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2004.101.29,false,true,652988,Arms and Armor,Drawings,Design for the Decoration of the Surround of the Barrel Tang of a Firearm,"French, Versailles",,,,,Designer,Workshop of,Nicolas Noël Boutet,"French, Versailles and Paris, 1761–1833",,"Boutet, Nicholas-Noël",French,1761,1833,ca. 1797–1805,1772,1830,"Pencil, ink, gray wash on paper",4 1/8 x 3 3/4 in. (10.5 x 9.5 cm),"Purchase, Clay P. Bedford Gift, 2004",,Versailles,,,,,,,,,,Works on Paper-Drawings,,http://www.metmuseum.org/art/collection/search/652988,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2004.101.30,false,true,652989,Arms and Armor,Drawings,Partial Design for the Decoration of the Surround of the Barrel Tang of a Firearm,"French, Versailles",,,,,Designer,Workshop of,Nicolas Noël Boutet,"French, Versailles and Paris, 1761–1833",,"Boutet, Nicholas-Noël",French,1761,1833,ca. 1797–1805,1772,1830,"Pencil, ink, gray wash on paper",4 1/4 x 4 1/8 in. (10.8 x 10.5 cm),"Purchase, Clay P. Bedford Gift, 2004",,Versailles,,,,,,,,,,Works on Paper-Drawings,,http://www.metmuseum.org/art/collection/search/652989,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2004.101.31,false,true,652990,Arms and Armor,Drawings,Two Designs for the Decoration of Barrel Tang Surrounds of a Firearm,"French, Versailles",,,,,Designer,Workshop of,Nicolas Noël Boutet,"French, Versailles and Paris, 1761–1833",,"Boutet, Nicholas-Noël",French,1761,1833,ca. 1797–1805,1772,1830,"Pencil, ink, gray wash on paper",4 1/8 x 7 7/8 in. (10.5 x 20 cm),"Purchase, Clay P. Bedford Gift, 2004",,Versailles,,,,,,,,,,Works on Paper-Drawings,,http://www.metmuseum.org/art/collection/search/652990,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2004.101.32,false,true,652991,Arms and Armor,Drawings,Thirteen Designs for the Decoration of Firearms,"French, Versailles",,,,,Designer,Workshop of,Nicolas Noël Boutet,"French, Versailles and Paris, 1761–1833",,"Boutet, Nicholas-Noël",French,1761,1833,ca. 1797–1805,1772,1830,"Pencil, ink, gray wash on paper",5 3/4 x 8 1/4 in. (14.6 x 21 cm),"Purchase, Clay P. Bedford Gift, 2004",,Versailles,,,,,,,,,,Works on Paper-Drawings,,http://www.metmuseum.org/art/collection/search/652991,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2004.101.33,false,true,652992,Arms and Armor,Drawings,Four Designs for the Decoration of Firearms,"French, Versailles",,,,,Designer,Workshop of,Nicolas Noël Boutet,"French, Versailles and Paris, 1761–1833",,"Boutet, Nicholas-Noël",French,1761,1833,ca. 1797–1805,1772,1830,"Pencil, ink, gray wash on paper",7 3/4 x 5 7/8 in. (19.7 x 14.9 cm),"Purchase, Clay P. Bedford Gift, 2004",,Versailles,,,,,,,,,,Works on Paper-Drawings,,http://www.metmuseum.org/art/collection/search/652992,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2004.101.34,false,true,652993,Arms and Armor,Drawings,Design for the Decoration of Firearms,"French, Versailles",,,,,Designer,Workshop of,Nicolas Noël Boutet,"French, Versailles and Paris, 1761–1833",,"Boutet, Nicholas-Noël",French,1761,1833,ca. 1797–1805,1772,1830,"Pencil, ink, gray wash on paper",8 1/2 x 3 3/4 in. (21.6 x 9.5 cm),"Purchase, Clay P. Bedford Gift, 2004",,Versailles,,,,,,,,,,Works on Paper-Drawings,,http://www.metmuseum.org/art/collection/search/652993,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2004.101.35,false,true,652994,Arms and Armor,Drawings,Designs for the Decoration of Firearms,"French, Versailles",,,,,Designer,Workshop of,Nicolas Noël Boutet,"French, Versailles and Paris, 1761–1833",,"Boutet, Nicholas-Noël",French,1761,1833,ca. 1797–1805,1772,1830,"Pencil, ink, gray wash on paper",8 1/8 x 3 5/8 in. (20.6 x 9.2 cm),"Purchase, Clay P. Bedford Gift, 2004",,Versailles,,,,,,,,,,Works on Paper-Drawings,,http://www.metmuseum.org/art/collection/search/652994,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2004.101.36,false,true,652995,Arms and Armor,Drawings,Design for the Decoration of Firearms,"French, Versailles",,,,,Designer,Workshop of,Nicolas Noël Boutet,"French, Versailles and Paris, 1761–1833",,"Boutet, Nicholas-Noël",French,1761,1833,ca. 1797–1805,1772,1830,"Pencil, ink, gray wash on paper",2 3/4 x 5 1/4 in. (7 x 13.3 cm),"Purchase, Clay P. Bedford Gift, 2004",,Versailles,,,,,,,,,,Works on Paper-Drawings,,http://www.metmuseum.org/art/collection/search/652995,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2004.101.37,false,true,652996,Arms and Armor,Drawings,Design for the Decoration of Firearms,"French, Versailles",,,,,Designer,Workshop of,Nicolas Noël Boutet,"French, Versailles and Paris, 1761–1833",,"Boutet, Nicholas-Noël",French,1761,1833,ca. 1797–1805,1772,1830,"Pencil, ink, gray wash on paper",2 3/4 x 3 7/8 in. (7 x 9.8 cm),"Purchase, Clay P. Bedford Gift, 2004",,Versailles,,,,,,,,,,Works on Paper-Drawings,,http://www.metmuseum.org/art/collection/search/652996,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2004.101.38,false,true,652997,Arms and Armor,Drawings,Design for the Decoration of Firearms,"French, Versailles",,,,,Designer,Workshop of,Nicolas Noël Boutet,"French, Versailles and Paris, 1761–1833",,"Boutet, Nicholas-Noël",French,1761,1833,ca. 1797–1805,1772,1830,"Pencil, ink, gray wash on paper",3 x 2 3/4 in. (7.6 x 7 cm),"Purchase, Clay P. Bedford Gift, 2004",,Versailles,,,,,,,,,,Works on Paper-Drawings,,http://www.metmuseum.org/art/collection/search/652997,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2004.101.39,false,true,652998,Arms and Armor,Drawings,Partial Design for the Decoration of Firearms,"French, Versailles",,,,,Designer,Workshop of,Nicolas Noël Boutet,"French, Versailles and Paris, 1761–1833",,"Boutet, Nicholas-Noël",French,1761,1833,ca. 1797–1805,1772,1830,"Pencil, ink, gray wash on paper",3 7/8 x 2 3/4 in. (9.8 x 7 cm),"Purchase, Clay P. Bedford Gift, 2004",,Versailles,,,,,,,,,,Works on Paper-Drawings,,http://www.metmuseum.org/art/collection/search/652998,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2004.101.40,false,true,652999,Arms and Armor,Drawings,Design for the Decoration of Firearms,"French, Versailles",,,,,Designer,Workshop of,Nicolas Noël Boutet,"French, Versailles and Paris, 1761–1833",,"Boutet, Nicholas-Noël",French,1761,1833,ca. 1797–1805,1772,1830,"Pencil, ink, gray wash on paper",2 x 4 7/8 in. (5.1 x 12.4 cm),"Purchase, Clay P. Bedford Gift, 2004",,Versailles,,,,,,,,,,Works on Paper-Drawings,,http://www.metmuseum.org/art/collection/search/652999,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2004.101.41,false,true,653000,Arms and Armor,Drawings,Design for the Decoration of the Barrel Tang Surround of a Firearm,"French, Versailles",,,,,Designer,Workshop of,Nicolas Noël Boutet,"French, Versailles and Paris, 1761–1833",,"Boutet, Nicholas-Noël",French,1761,1833,ca. 1797–1805,1772,1830,"Pencil, ink, gray wash on paper",3 7/8 x 1 7/8 in. (9.8 x 4.8 cm),"Purchase, Clay P. Bedford Gift, 2004",,Versailles,,,,,,,,,,Works on Paper-Drawings,,http://www.metmuseum.org/art/collection/search/653000,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2004.101.42,false,true,653001,Arms and Armor,Drawings,Design for the Decoration of the Barrel Tang Surround of a Firearm,"French, Versailles",,,,,Designer,Workshop of,Nicolas Noël Boutet,"French, Versailles and Paris, 1761–1833",,"Boutet, Nicholas-Noël",French,1761,1833,ca. 1797–1805,1772,1830,"Pencil, ink, gray wash on paper",5 1/4 x 2 7/8 in. (13.3 x 7.3 cm),"Purchase, Clay P. Bedford Gift, 2004",,Versailles,,,,,,,,,,Works on Paper-Drawings,,http://www.metmuseum.org/art/collection/search/653001,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2004.101.43,false,true,653002,Arms and Armor,Drawings,Design for the Decoration of the Barrel Tang Surround of a Firearm,"French, Versailles",,,,,Designer,Workshop of,Nicolas Noël Boutet,"French, Versailles and Paris, 1761–1833",,"Boutet, Nicholas-Noël",French,1761,1833,ca. 1797–1805,1772,1830,"Pencil, ink, gray wash on paper",2 7/8 x 3 1/4 in (7.3 x 8.3 cm),"Purchase, Clay P. Bedford Gift, 2004",,Versailles,,,,,,,,,,Works on Paper-Drawings,,http://www.metmuseum.org/art/collection/search/653002,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2004.101.44,false,true,653003,Arms and Armor,Drawings,Design for the Decoration of the Surround of the Barrel Flat and Rear Sight of a Firearm,"French, Versailles",,,,,Designer,Workshop of,Nicolas Noël Boutet,"French, Versailles and Paris, 1761–1833",,"Boutet, Nicholas-Noël",French,1761,1833,ca. 1797–1805,1772,1830,"Pencil, ink, gray wash on paper",4 1/8 x 3 1/2 in. (10.5 x 8.9 cm),"Purchase, Clay P. Bedford Gift, 2004",,Versailles,,,,,,,,,,Works on Paper-Drawings,,http://www.metmuseum.org/art/collection/search/653003,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2004.101.45,false,true,653004,Arms and Armor,Drawings,Design for the Decoration of the Surround of the Rear Sight of a Firearm,"French, Versailles",,,,,Designer,Workshop of,Nicolas Noël Boutet,"French, Versailles and Paris, 1761–1833",,"Boutet, Nicholas-Noël",French,1761,1833,ca. 1797–1805,1772,1830,"Pencil, ink, gray wash on paper",3 3/4 x 3 5/8 in. (9.5 x 9.2 cm),"Purchase, Clay P. Bedford Gift, 2004",,Versailles,,,,,,,,,,Works on Paper-Drawings,,http://www.metmuseum.org/art/collection/search/653004,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2004.101.46,false,true,653005,Arms and Armor,Drawings,Design for the Decoration of Firearms,"French, Versailles",,,,,Designer,Workshop of,Nicolas Noël Boutet,"French, Versailles and Paris, 1761–1833",,"Boutet, Nicholas-Noël",French,1761,1833,ca. 1797–1805,1772,1830,"Pencil, ink, gray wash on paper",2 1/8 x 1 3/4 in. (5.4 x 4.5 cm),"Purchase, Clay P. Bedford Gift, 2004",,Versailles,,,,,,,,,,Works on Paper-Drawings,,http://www.metmuseum.org/art/collection/search/653005,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2004.101.47,false,true,653006,Arms and Armor,Drawings,Design for the Decoration of Firearms,"French, Versailles",,,,,Designer,Workshop of,Nicolas Noël Boutet,"French, Versailles and Paris, 1761–1833",,"Boutet, Nicholas-Noël",French,1761,1833,ca. 1797–1805,1772,1830,"Pencil, ink, gray wash on paper",3 5/8 x 2 7/8 in. (6.7 x 7.3 cm),"Purchase, Clay P. Bedford Gift, 2004",,Versailles,,,,,,,,,,Works on Paper-Drawings,,http://www.metmuseum.org/art/collection/search/653006,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2004.101.48,false,true,653007,Arms and Armor,Drawings,Design for the Decoration of Firearms,"French, Versailles",,,,,Designer,Workshop of,Nicolas Noël Boutet,"French, Versailles and Paris, 1761–1833",,"Boutet, Nicholas-Noël",French,1761,1833,ca. 1797–1805,1772,1830,"Pencil, ink, gray wash on paper",2 7/8 x 1 7/8 in. (7.3 x 4.8 cm),"Purchase, Clay P. Bedford Gift, 2004",,Versailles,,,,,,,,,,Works on Paper-Drawings,,http://www.metmuseum.org/art/collection/search/653007,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2004.101.49,false,true,653008,Arms and Armor,Drawings,Two Designs for the Decoration of Firearms,"French, Versailles",,,,,Designer,Workshop of,Nicolas Noël Boutet,"French, Versailles and Paris, 1761–1833",,"Boutet, Nicholas-Noël",French,1761,1833,ca. 1797–1805,1772,1830,"Pencil, ink, gray wash on paper",1 1/8 x 2 1/2 in. (2.9 x 6.4 cm),"Purchase, Clay P. Bedford Gift, 2004",,Versailles,,,,,,,,,,Works on Paper-Drawings,,http://www.metmuseum.org/art/collection/search/653008,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2004.101.50,false,true,653009,Arms and Armor,Drawings,Design for the Decoration of Firearms,"French, Versailles",,,,,Designer,Workshop of,Nicolas Noël Boutet,"French, Versailles and Paris, 1761–1833",,"Boutet, Nicholas-Noël",French,1761,1833,ca. 1797–1805,1772,1830,"Pencil, ink, gray wash on paper",2 1/8 x 1 1/2 in. (5.4 x 3.8 cm),"Purchase, Clay P. Bedford Gift, 2004",,Versailles,,,,,,,,,,Works on Paper-Drawings,,http://www.metmuseum.org/art/collection/search/653009,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2004.101.51,false,true,653010,Arms and Armor,Drawings,Two Designs for the Decoration of Firearms,"French, Versailles",,,,,Designer,Workshop of,Nicolas Noël Boutet,"French, Versailles and Paris, 1761–1833",,"Boutet, Nicholas-Noël",French,1761,1833,ca. 1797–1805,1772,1830,"Pencil, ink, gray wash on paper",2 1/8 x 2 3/8 in. (5.4 x 6 cm),"Purchase, Clay P. Bedford Gift, 2004",,Versailles,,,,,,,,,,Works on Paper-Drawings,,http://www.metmuseum.org/art/collection/search/653010,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2004.101.52,false,true,653011,Arms and Armor,Drawings,Two Designs for the Decoration of Firearms,"French, Versailles",,,,,Designer,Workshop of,Nicolas Noël Boutet,"French, Versailles and Paris, 1761–1833",,"Boutet, Nicholas-Noël",French,1761,1833,ca. 1797–1805,1772,1830,"Pencil, ink, gray wash on paper",2 1/8 x 2 1/2 in. (5.4 x 6.4 cm),"Purchase, Clay P. Bedford Gift, 2004",,Versailles,,,,,,,,,,Works on Paper-Drawings,,http://www.metmuseum.org/art/collection/search/653011,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2004.101.53,false,true,653012,Arms and Armor,Drawings,Designs for the Decoration of Firearms,"French, Versailles",,,,,Designer,Workshop of,Nicolas Noël Boutet,"French, Versailles and Paris, 1761–1833",,"Boutet, Nicholas-Noël",French,1761,1833,ca. 1797–1805,1772,1830,"Pencil, ink, gray wash on paper",3 5/8 x 2 in. (9.2 x 5.1 cm),"Purchase, Clay P. Bedford Gift, 2004",,Versailles,,,,,,,,,,Works on Paper-Drawings,,http://www.metmuseum.org/art/collection/search/653012,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2004.101.54,false,true,653013,Arms and Armor,Drawings,Design for the Decoration of a Pistol Grip,"French, Versailles",,,,,Designer,Workshop of,Nicolas Noël Boutet,"French, Versailles and Paris, 1761–1833",,"Boutet, Nicholas-Noël",French,1761,1833,ca. 1797–1805,1772,1830,"Pencil, ink, gray wash on paper",4 x 8 1/2 in. (10.2 x 21.6 cm),"Purchase, Clay P. Bedford Gift, 2004",,Versailles,,,,,,,,,,Works on Paper-Drawings,,http://www.metmuseum.org/art/collection/search/653013,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2004.101.55,false,true,653014,Arms and Armor,Drawings,Pair of Designs for the Decoration of the Grips of Pocket Pistols,"French, Versailles",,,,,Designer,Workshop of,Nicolas Noël Boutet,"French, Versailles and Paris, 1761–1833",,"Boutet, Nicholas-Noël",French,1761,1833,ca. 1797–1805,1772,1830,"Pencil, ink, gray wash on paper",3 1/4 x 7 1/4 in. (8.3 x 18.4 cm),"Purchase, Clay P. Bedford Gift, 2004",,Versailles,,,,,,,,,,Works on Paper-Drawings,,http://www.metmuseum.org/art/collection/search/653014,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2004.101.56,false,true,653015,Arms and Armor,Drawings,Pair of Designs for the Decoration of the Grips of Pocket Pistols,"French, Versailles",,,,,Designer,Workshop of,Nicolas Noël Boutet,"French, Versailles and Paris, 1761–1833",,"Boutet, Nicholas-Noël",French,1761,1833,ca. 1797–1805,1772,1830,"Pencil, ink, gray wash on paper",3 1/4 x 7 1/8 in. (8.3 x 18.1 cm),"Purchase, Clay P. Bedford Gift, 2004",,Versailles,,,,,,,,,,Works on Paper-Drawings,,http://www.metmuseum.org/art/collection/search/653015,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2004.101.57,false,true,653016,Arms and Armor,Drawings,Designs for the Decoration of Firearms,"French, Versailles",,,,,Designer,Workshop of,Nicolas Noël Boutet,"French, Versailles and Paris, 1761–1833",,"Boutet, Nicholas-Noël",French,1761,1833,ca. 1797–1805,1772,1830,"Pencil, ink, gray wash on paper",2 1/4 x 2 in. (5.7 x 5.1 cm),"Purchase, Clay P. Bedford Gift, 2004",,Versailles,,,,,,,,,,Works on Paper-Drawings,,http://www.metmuseum.org/art/collection/search/653016,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2004.101.58,false,true,653017,Arms and Armor,Drawings,Designs for the Decoration of Firearms,"French, Versailles",,,,,Designer,Workshop of,Nicolas Noël Boutet,"French, Versailles and Paris, 1761–1833",,"Boutet, Nicholas-Noël",French,1761,1833,ca. 1797–1805,1772,1830,"Pencil, ink, gray wash on paper",2 7/8 x 6 in. (7.3 x 15.2 cm),"Purchase, Clay P. Bedford Gift, 2004",,Versailles,,,,,,,,,,Works on Paper-Drawings,,http://www.metmuseum.org/art/collection/search/653017,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2004.101.59,false,true,653018,Arms and Armor,Drawings,Designs for the Decoration of Firearms,"French, Versailles",,,,,Designer,Workshop of,Nicolas Noël Boutet,"French, Versailles and Paris, 1761–1833",,"Boutet, Nicholas-Noël",French,1761,1833,ca. 1797–1805,1772,1830,"Pencil, ink, gray wash on paper",1 7/8 x 6 1/8 in. (4.8 x 15.6 cm),"Purchase, Clay P. Bedford Gift, 2004",,Versailles,,,,,,,,,,Works on Paper-Drawings,,http://www.metmuseum.org/art/collection/search/653018,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2004.101.60,false,true,653019,Arms and Armor,Drawings,Design for the Decoration of Firearms,"French, Versailles",,,,,Designer,Workshop of,Nicolas Noël Boutet,"French, Versailles and Paris, 1761–1833",,"Boutet, Nicholas-Noël",French,1761,1833,ca. 1797–1805,1772,1830,"Pencil, ink, gray wash on paper",5 1/4 x 2 1/4 in. (13.3 x 5.7 cm),"Purchase, Clay P. Bedford Gift, 2004",,Versailles,,,,,,,,,,Works on Paper-Drawings,,http://www.metmuseum.org/art/collection/search/653019,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2004.101.61,false,true,653020,Arms and Armor,Drawings,Design for the Decoration of Firearms,"French, Versailles",,,,,Designer,Workshop of,Nicolas Noël Boutet,"French, Versailles and Paris, 1761–1833",,"Boutet, Nicholas-Noël",French,1761,1833,ca. 1797–1805,1772,1830,"Pencil, ink, gray wash on paper",5 3/4 x 2 1/4 in. (14.6 x 5.7 cm),"Purchase, Clay P. Bedford Gift, 2004",,Versailles,,,,,,,,,,Works on Paper-Drawings,,http://www.metmuseum.org/art/collection/search/653020,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2004.101.62,false,true,653021,Arms and Armor,Drawings,Designs for the Decoration of Firearms,"French, Versailles",,,,,Designer,Workshop of,Nicolas Noël Boutet,"French, Versailles and Paris, 1761–1833",,"Boutet, Nicholas-Noël",French,1761,1833,ca. 1797–1805,1772,1830,"Pencil, ink, gray wash on paper",5 5/8 x 2 1/4 in. (14.3 x 5.7 cm),"Purchase, Clay P. Bedford Gift, 2004",,Versailles,,,,,,,,,,Works on Paper-Drawings,,http://www.metmuseum.org/art/collection/search/653021,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2004.101.63,false,true,653022,Arms and Armor,Drawings,Design for the Decoration of the Barrel of a Firearm,"French, Versailles",,,,,Designer,Workshop of,Nicolas Noël Boutet,"French, Versailles and Paris, 1761–1833",,"Boutet, Nicholas-Noël",French,1761,1833,ca. 1797–1805,1772,1830,"Pencil, ink, gray wash on paper",11 1/4 x 3 1/4 in. (28.6 x 8.3 cm),"Purchase, Clay P. Bedford Gift, 2004",,Versailles,,,,,,,,,,Works on Paper-Drawings,,http://www.metmuseum.org/art/collection/search/653022,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2004.101.64,false,true,653023,Arms and Armor,Drawings,Design for the Decoration of Firearms,"French, Versailles",,,,,Designer,Workshop of,Nicolas Noël Boutet,"French, Versailles and Paris, 1761–1833",,"Boutet, Nicholas-Noël",French,1761,1833,ca. 1797–1805,1772,1830,"Pencil, ink, gray wash on paper",3 1/8 x 1 7/8 in. (7.9 x 4.8 cm),"Purchase, Clay P. Bedford Gift, 2004",,Versailles,,,,,,,,,,Works on Paper-Drawings,,http://www.metmuseum.org/art/collection/search/653023,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2004.101.65,false,true,653024,Arms and Armor,Drawings,Designs for the Decoration of Firearms,"French, Versailles",,,,,Designer,Workshop of,Nicolas Noël Boutet,"French, Versailles and Paris, 1761–1833",,"Boutet, Nicholas-Noël",French,1761,1833,ca. 1797–1805,1772,1830,"Pencil, ink, gray wash on paper",3 5/8 x 1 3/4 in. (9.2 x 4.5 cm),"Purchase, Clay P. Bedford Gift, 2004",,Versailles,,,,,,,,,,Works on Paper-Drawings,,http://www.metmuseum.org/art/collection/search/653024,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2004.101.66,false,true,653025,Arms and Armor,Drawings,Designs for the Decoration of Firearms,"French, Versailles",,,,,Designer,Workshop of,Nicolas Noël Boutet,"French, Versailles and Paris, 1761–1833",,"Boutet, Nicholas-Noël",French,1761,1833,ca. 1797–1805,1772,1830,"Pencil, ink, gray wash on paper",3 3/4 x 7/8 in. (9.5 x 2.2 cm),"Purchase, Clay P. Bedford Gift, 2004",,Versailles,,,,,,,,,,Works on Paper-Drawings,,http://www.metmuseum.org/art/collection/search/653025,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2004.101.67,false,true,653026,Arms and Armor,Drawings,Design for the Decoration of Firearms,"French, Versailles",,,,,Designer,Workshop of,Nicolas Noël Boutet,"French, Versailles and Paris, 1761–1833",,"Boutet, Nicholas-Noël",French,1761,1833,ca. 1797–1805,1772,1830,"Pencil, ink, gray wash on paper",4 1/4 x 1 1/8 in. (10.8 x 2.9 cm),"Purchase, Clay P. Bedford Gift, 2004",,Versailles,,,,,,,,,,Works on Paper-Drawings,,http://www.metmuseum.org/art/collection/search/653026,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2004.101.68,false,true,653027,Arms and Armor,Drawings,Design for the Decoration of Firearms,"French, Versailles",,,,,Designer,Workshop of,Nicolas Noël Boutet,"French, Versailles and Paris, 1761–1833",,"Boutet, Nicholas-Noël",French,1761,1833,ca. 1797–1805,1772,1830,"Pencil, ink, gray wash on paper",4 x 1 3/4 in. (10.2 x 4.5 cm),"Purchase, Clay P. Bedford Gift, 2004",,Versailles,,,,,,,,,,Works on Paper-Drawings,,http://www.metmuseum.org/art/collection/search/653027,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2004.101.69,false,true,653028,Arms and Armor,Drawings,Design for the Decoration of Firearms,"French, Versailles",,,,,Designer,Workshop of,Nicolas Noël Boutet,"French, Versailles and Paris, 1761–1833",,"Boutet, Nicholas-Noël",French,1761,1833,ca. 1797–1805,1772,1830,"Pencil, ink, gray wash on paper",2 1/8 x 5 1/4 in. (5.4 x 13.3 cm),"Purchase, Clay P. Bedford Gift, 2004",,Versailles,,,,,,,,,,Works on Paper-Drawings,,http://www.metmuseum.org/art/collection/search/653028,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2004.101.70,false,true,653029,Arms and Armor,Drawings,Design for the Decoration of Firearms,"French, Versailles",,,,,Designer,Workshop of,Nicolas Noël Boutet,"French, Versailles and Paris, 1761–1833",,"Boutet, Nicholas-Noël",French,1761,1833,ca. 1797–1805,1772,1830,"Pencil, ink, gray wash on paper",2 1/2 x 6 1/2 in. (6.4 x 16.5 cm),"Purchase, Clay P. Bedford Gift, 2004",,Versailles,,,,,,,,,,Works on Paper-Drawings,,http://www.metmuseum.org/art/collection/search/653029,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2004.101.71,false,true,653030,Arms and Armor,Drawings,Design for the Decoration of Firearms,"French, Versailles",,,,,Designer,Workshop of,Nicolas Noël Boutet,"French, Versailles and Paris, 1761–1833",,"Boutet, Nicholas-Noël",French,1761,1833,ca. 1797–1805,1772,1830,"Pencil, ink, gray wash on paper",1 3/4 x 5 7/8 in. (4.5 x 14.9 cm),"Purchase, Clay P. Bedford Gift, 2004",,Versailles,,,,,,,,,,Works on Paper-Drawings,,http://www.metmuseum.org/art/collection/search/653030,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2004.101.72,false,true,653031,Arms and Armor,Drawings,Design for the Decoration of Firearms,"French, Versailles",,,,,Designer,Workshop of,Nicolas Noël Boutet,"French, Versailles and Paris, 1761–1833",,"Boutet, Nicholas-Noël",French,1761,1833,ca. 1797–1805,1772,1830,"Pencil, ink, gray wash on paper",1 3/4 x 5 7/8 in. (4.5 x 14.9 cm),"Purchase, Clay P. Bedford Gift, 2004",,Versailles,,,,,,,,,,Works on Paper-Drawings,,http://www.metmuseum.org/art/collection/search/653031,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2004.101.1–.72,false,true,26900,Arms and Armor,Drawings,Designs for the Decoration of Firearms,"French, Versailles",,,,,Designer,Workshop of,Nicolas Noël Boutet,"French, Versailles and Paris, 1761–1833",,"Boutet, Nicholas-Noël",French,1761,1833,ca. 1795–1805,1770,1830,"Pencil, ink, gray wash on paper","various sizes, largest: 8 3/8 x 6 1/16 in. (21.2 x 15.3 cm); smallest: 1 x 5/8 in. (2.5 x 1.5 cm)","Purchase, Clay P. Bedford Gift, 2004",,Versailles,,,,,,,,,,Works on Paper-Drawings,,http://www.metmuseum.org/art/collection/search/26900,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -22.122.14,false,true,32967,Arms and Armor,Medal,"François II, King of France, King Consort of Scotland","French, probably Paris",,,,,Designer,After designs of 1559–60 by,Étienne Delaune,"French, Orléans 1518/19–1583 Strasbourg",,"Delaune, Étienne",French,1518,1583,struck ca. 1600–1635,1559,1660,Bronze,Diam. 2 1/16 in. (5.2 cm),"Gift of Bashford Dean, 1922",,,,,,,,,,,,Miscellaneous-Coins and Medals,,http://www.metmuseum.org/art/collection/search/32967,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2004.57,false,true,26920,Arms and Armor,Engraving,Engraving of Firearms Parts,"French, Strasbourg",,,,,Designer,,Perrier,"French, Strasbourg, active mid-18th century",,Perrier,French,1725,1775,ca. 1750,1725,1775,Ink on paper,18 3/4 x 25 1/4 in. (47.5 x 64 cm),"Purchase, Jonathan and Elizabeth Roberts Gift, 2004",,Strasbourg,,,,,,,,,,Works on Paper-Prints,,http://www.metmuseum.org/art/collection/search/26920,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2014.674,false,true,667460,Arms and Armor,Pommel plate,Pommel Plate for a Saddle in the Style of the Late Middles Ages,French,,,,,Maker|Goldsmith,Workshop of,Louis Marcy (Luigi Parmeggiani)|Henri Husson,"Italian, 1860–1945|French, 1851–1914",,"Marcy, Louis|Husson, Henri",French,1860 |1852,1945 |1914,late 19th–early 20th century,1850,1950,"Copper alloy, gold, enamel",H. 13 3/4 in. (34.9 cm); W. 16 1/8 in. (41 cm),"Purchase, Arthur Ochs Sulzberger Gift, 2014",,,,,,,,,,,,Equestrian Equipment-Saddles,,http://www.metmuseum.org/art/collection/search/667460,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -23.149.1,false,true,32850,Arms and Armor,Kidney Dagger,Kidney Dagger in Gothic Style,French,,,,,Maker,,Henri Husson,"French, 1851–1914",,"Husson, Henri",French,1852,1914,ca. 1880–90,1855,1915,"Steel, wood (walnut), copper, black enamel",L. 17 in. (43.2 cm),"Gift of Jacques Reubell, 1923",,Mantes,Île-de-France,,,,,,,,,Forgeries,,http://www.metmuseum.org/art/collection/search/32850,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2013.493.1a, b",false,true,35879,Arms and Armor,Medal with case,Lamarck Medal with Case,French,,,,,Maker,,François-Léon Sicard,"French, 1862–1934",,"Sicard, François-Léon",French,1862,1934,ca. 1910,1885,1935,"Bronze, leather, textile",Diam. 2 3/8 in. (6.0 cm); Diam. of case 2 3/4 in. (7.0 cm); D. of case 1/2 in. (1.3 cm),"Gift of Dean K. Boorman, 2013",,,,,,,,,,,,Miscellaneous-Coins and Medals,,http://www.metmuseum.org/art/collection/search/35879,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.324,false,true,35366,Arms and Armor,Engraving,Engraving of Firearms Ornament,French,,,,,Artist,,Claude Simonin,"French, Nantes ca. 1635–1693 Nantes",,"Simonin, Claude",French,1635,1693,1693 or 1695,1693,1695,Ink on paper,8 1/2 x 6 5/16 in. (21.8 x 16.2 cm),"Purchase, funds from various donors, by exchange, 2007",,,,,,,,,,,,Works on Paper-Prints,,http://www.metmuseum.org/art/collection/search/35366,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"14.25.1433a, b",false,true,22389,Arms and Armor,Pair of wheellock pistols,Pair of Wheellock Pistols,French,,,,,Designer,Decoration on the stocks copied in part from engravings by,Étienne Delaune,"French, Orléans 1518/19–1583 Strasbourg",,"Delaune, Étienne",French,1518,1583,ca. 1570–80,1545,1605,"Steel, gold, silver, brass, wood (walnut), staghorn, pigment",L. of each pistol 20 1/8 in. (51.1 cm); Cal. of 14.25.1433a .444 in (11.2 mm); Cal. of 14.25.1433b .463 in. (11.3 mm); L. of each barrel 12 in. (30.5 cm); L. of plug 7 1/2 in. (19.1 cm); Diam. at muzzle 3/4 in. (19.1 mm); Diam. at breech 1 5/16 in. (3.3 cm); L. of lock 5 5/8 in. (14.3 cm); Wt. of 14.25.1433a 4 lb. 2 oz. (1871 g); Wt. of 14.25.1433b 4 lb. (1814 g),"Gift of William H. Riggs, 1913",,,,,,,,,,,,Firearms-Guns-Wheellock,,http://www.metmuseum.org/art/collection/search/22389,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"14.25.1433a, b; .1523",false,true,35867,Arms and Armor,Pair of wheellock pistols with matching priming flask/spanner,Pair of Wheellock Pistols with Matching Priming Flask/Spanner,French,,,,,Designer,Decoration on the stocks copied in part from engravings by,Étienne Delaune,"French, Orléans 1518/19–1583 Strasbourg",,"Delaune, Étienne",French,1518,1583,ca. 1570–80,1545,1605,"Steel, gold, silver, brass, wood (walnut), staghorn, brass wire, pigment","Cal. of each pistol, .44 in (11.18 mm); Wt. of 14.25.1433a: 4 lb. 2 oz. (1871 g); Wt. of 14.25.1433b: 4 lb. (1814 g); L. of each pistol, 20 1/8 in. (51.1 cm); L. of priming flask/spanner, 5 7/8 in. (14.91 cm)","Gift of William H. Riggs, 1913",,,,,,,,,,,,Firearms-Pistols-Wheellock,,http://www.metmuseum.org/art/collection/search/35867,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1974.4,false,true,24869,Arms and Armor,Flintlock gun,Flintlock Gun,German,,,,,Designer,Decoration follows closely designs of the engraver,De Lacollombe,"French, Paris, active ca. 1702–ca. 1736",,"Lacollombe, De",French,1702,1736,ca. 1730–40,1705,1765,"Steel, silver, wood (walnut)","L. 56 3/8 in. (143.2 cm); L. of barrel 41 1/16 in. (104.3 cm); L. of lockplate 5 7/8 in. (14.9 cm), Cal. .62 in. (15.8 mm); Wt. 6 lb. 10 oz. (3005 g)","Purchase, Bashford Dean Bequest, James Elwood Jones Jr. Gift, and Rogers and Fletcher Funds, 1974",,,,,,,,,,,,Firearms,,http://www.metmuseum.org/art/collection/search/24869,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.511.1–.3a–k,false,true,26891,Arms and Armor,Cased pair of percussion target pistols with loading and cleaning accessories,"Cased Pair of Percussion Target Pistols with Loading and Cleaning Accessories, Made for Henri Charles Ferdinand Marie Dieudonné d'Artois, Duke of Bordeaux, Count of Chambord (1820–1883)","French, Paris",,,,,Gunsmith,,Jean André Prosper Henri Le Page,"French, 1792–1854",,"Le Page, Jean André Prosper Henri",French,1792,1854,dated 1829,1829,1829,"Steel, gold, wood (ebony, walnut, amboyna), silver, velvet, ivory",case L. 15 in. (38 cm); W. 9 7/8 in. (25 cm); H. 3 1/4 in. ( 8 cm); pistol L. (each) 11 1/4 in. (28.6 cm); barrel L. (each) 6 3/8 in. (17 cm); Cal. (each) .43 in. (11 mm),"Purchase, Arthur Ochs Sulzberger Bequest and Irene Roosevelt Aitken Gift, 2013",,,Paris,,,,,,,,,Firearms-Pistols-Percussion,,http://www.metmuseum.org/art/collection/search/26891,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.189a–g,false,true,24936,Arms and Armor,Breech-loading rimfire cartridge pistol with case and accessories,Breech-Loading Rimfire Cartridge Pistol with Case and Accessories,"French, Paris",,,,,Gunsmith,,Louis Nicolas Auguste Flobert,"French, Paris, 1819–1894",,"Flobert, Louis Nicolas Auguste",French,1819,1894,ca. 1855,1830,1880,"Steel, gold, ivory, leather, textile",Pistol (a); L. 13 3/8 in. (33.9 cm); L. of barrel 8 3/8 in. (21.2 cm); Cal. .22 in. (5.6 mm); forked cleaning rod (b); L. 9 9/16 in. (24.3 cm); Wt. 1 oz. (28.3 g); scourging rod (c); L. 9 11/16 in. (24.6 cm); Wt. 1.1 oz. (31.2 g); cartridge box (d); H. 1 1/4 in. (3.2 cm); Diam. 2 in. (5.1 cm); Wt. 1.7 oz. (48.2 g); screwdriver (e); L. 3 1/16 in. (7.8 cm); Wt. 0.5 oz. (14.2 g); case (f); H. 2 3/16 in. (5.6 cm); W. 14 1/2 in. (36.8 cm); D. 8 5/8 in. (21.9 cm); Wt. 1 lb. 14.6 oz. (867.5 g); key (g); L. 1 1/8 in. (2.9 cm),"Purchase, John Stoneacre Ellis Collection, Gift of Mrs. Ellis and Augustus Van Horne Ellis, by exchange, 1989",,Paris,,,,,,,,,,Firearms-Pistols,,http://www.metmuseum.org/art/collection/search/24936,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -35.81.4,false,true,23949,Arms and Armor,Percussion pistol with case,Percussion Pistol with Case,"French, Paris",,,,,Gunsmith,,Alfred Gauvain,"French, Paris 1801–1889 Paris",,"Gauvain, Alfred",French,1801,1889,possibly 1844,1819,1869,"Steel, wood (ebony), gold, wood, textile",L. of pistol 16 7/8 in. (42.9 cm); Cal. .44 in. (11.2 mm); Wt. 3 lb. 4 oz. (1474 g),"Bequest of Charles N. Daly, 1934",,Paris,,,,,,,,,,Firearms-Pistols-Percussion,,http://www.metmuseum.org/art/collection/search/23949,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.318a–m,false,true,27165,Arms and Armor,Cased pair of percussion pistols with accessories,Cased Pair of Percussion Pistols with Accessories,"French, Paris",,,,,Gunsmith,,Louis-Julien Gastinne-Renette,"French, Paris 1812–1885 Paris",,Gastinne-Renette Louis-Julien,French,1812,1885,dated 1856,1856,1856,"Steel, brass, gold, wood (ebony), textile (baizé, wool), copper, leather","Pistols (a, b); L. of each 16 in. (40.7 cm); L. of each barrel 10 5/8 in. (27 cm); Cal. of each .46 in. (12 mm); Wt. of each 2 lb. 3 oz. (992.2 g); ramrod (c); L. 11 1/4 in. (28.6 cm); Wt. 1.5 oz. (42.5 g); cleaning rod (d); L. 12 3/8 in. (31.4 cm); Wt. 1.8 oz. (51 g); mallet (e); L. 7 7/8 in. (20 cm); W. 2 1/2 in. (6.4 cm); Wt. 5.3 oz. (150.3 g); screwdriver (f); L. 6 1/4 in. (15.9 cm); Wt. 2.6 oz. (73.7 g); bullet mould (g); L. 7 7/8 in. (20 cm); Wt. 8.8 oz. (249.5 g); powder flask (h); H. 4 in. (10.2 cm); Wt. 4.2 oz. (119.1 g); patch box (i); H. 1 in. (2.5 cm); Diam. 1 1/4 in. (3.2 cm); Wt. 0.4 oz. (11.3 g); nipple box (j); H. 1 11/16 in. (4.3 cm); Diam. 1 7/8 in. (4.8 cm); Wt. 1.5 oz. (42.5 g); spoon (k); L. 4 in. (10.2 cm); Wt. 0.4 oz. (11.3 g); case (l); H. 2 7/8 in. (7.3 cm); W. 18 7/8 in. (47.9 cm); D. 8 1/4 in. (21 cm); Wt. 7 lb. 4.7 oz. (3308.4 g); leather case (m); H. 3 3/8 in. (8.6 cm); W. 19 3/8 in. (49.2 cm); D. 8 3/4 in. (22.2 cm); Wt. 3 lb. 7.5 oz. (1573.4 g)","Purchase, John Stoneacre Ellis Collection, Gift of Mrs. Ellis and Augustus Van Horne Ellis, and Gift of Charles M. Schott, by exchange, 1989",,Paris,,,,,,,,,,Firearms-Pistols-Percussion,,http://www.metmuseum.org/art/collection/search/27165,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2006.350.1–.19,false,true,34812,Arms and Armor,Engravings,Nouveaux Ornemans D'Arquebuseries,"French, Paris",,,,,Engraver,,Gilles Demarteau,"French, Liège 1722–1776 Paris",,"Demarteau, Gilles",French,1722,1776,ca. 1750–55,1725,1780,Ink on paper,10 1/4 x 6 3/4 in. (26 x 17.1 cm),"Purchase, Gift of Russell B. Aitken, by exchange, 2006",,Paris,,,,,,,,,,Works on Paper-Prints,,http://www.metmuseum.org/art/collection/search/34812,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2002.521.1a, b, .2a, b",false,true,26587,Arms and Armor,Pair of double-barreled flintlock pistols,Pair of Double-Barreled Flintlock Pistols,"French, Paris",,,,,Gunsmith,,François-Alexander Chasteau,"French, Paris, recorded 1741–84",,"Chasteau, François-Alexander",French,1741,1784,1752–53,1752,1753,"Steel, silver, gold, wood (walnut), whalebone",L. of each 14 3/8 in. (36.5 cm); Wt. of each 36 oz. (1030 g),"Gift of Walter A. Eberstadt, 2002",,Paris,,,,,,,,,,Firearms-Pistols-Flintlock,,http://www.metmuseum.org/art/collection/search/26587,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"17.87.3a, b",true,true,22631,Arms and Armor,Smallsword with scabbard,Congressional Presentation Sword with Scabbard of Colonel Marinus Willett (1740–1830),"French, Paris",,,,,Sword maker,,C. Liger,"French, Paris, recorded 1770–93",,"Liger, C.",French,1770,1793,hallmarked for 1785–86,1785,1786,"Steel, silver, gold, fish skin, textile, wood",sword L. 39 5/8 in. (100.6 cm); scabbard L. 33 1/4 in. (84.5 cm),"Bequest of George Willett Van Nest, 1916",,Paris,,,,,,,,,,Swords,,http://www.metmuseum.org/art/collection/search/22631,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -04.3.65,false,true,21949,Arms and Armor,Partisan,"Partisan Carried by the Bodyguard of Louis XIV (1638–1715, reigned from 1643)","French, Paris",,,,,Sword cutler,Inscription probably refers to,Bonaventure Ravoisie,"French, Paris, recorded 1678–1709",,"Ravoisie, Bonaventure",French,1678,1709,ca. 1678–1709,1678,1709,"Steel, gold, wood, textile",L. 94 1/8 in. (239 cm); L. of head 22 9/16 in. (57.3 cm); W. of head 6 1/2 in. (16.5 cm),"Rogers Fund, 1904",,,,,,,,,,,,Shafted Weapons,,http://www.metmuseum.org/art/collection/search/21949,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.3,true,true,24937,Arms and Armor,Armor,"Armor of Infante Luis, Prince of Asturias (1707–1724)","French, Paris",,,,,Armorer,Signature probably refers to,Jean Drouart,"French, Paris, died before October 1715",,"Drouart, Jean",French,1615,1715,dated 1712,1687,1737,"Steel, gold, brass, silk, cotton, metallic yarn, paper",H. 28 in. (71.1 cm),"Purchase, Armand Hammer, Occidental Petroleum Corporation Gift, 1989",,Paris,,,,,,,,,,Armor for Child,,http://www.metmuseum.org/art/collection/search/24937,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1990.114.2,false,true,24943,Arms and Armor,Flintlock pistol,Flintlock Pistol Made for Charles XI of Sweden (1655–1697),"French, Paris",,,,,Gunsmith,,Bertrand Piraube,"French, Paris, recorded ca. 1663–1725",,"Piraube, Bertrand",French,1638,1750,dated 1676,1676,1676,"Steel, gold, wood (walnut)",L. 21 in. (53.3 cm),"Purchase, Annie Laurie Aitken Charitable Trust Gift, 1990",,Paris,,,,,,,,,,Firearms,,http://www.metmuseum.org/art/collection/search/24943,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1988.65.1–.2,true,true,24931,Arms and Armor,Costume armor,Costume Armor in the Classical Style,"French, Paris",,,,,Maker,Helmet includes original paper label of,Hallé,"French, Paris, active ca. 1780–1800",,Hallé,French,1755,1825,ca. 1788–90,1763,1815,"Linen, papier-mâché, bole, gold leaf, graphite (helmet); silk, cotton, metal coils and spangles, metallic yarn (tunic)",armor: H. 26 3/4 (68 cm); W. 22 7/16 (57 cm); D. 11 in. (28 cm); helmet: H. 15 3/4 (40 cm); W. 7 11/16 (19.5 cm); D. 13 3/4 in. (35 cm),"Funds from various donors, 1988",,Paris,,,,,,,,,,Costumes,,http://www.metmuseum.org/art/collection/search/24931,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"1988.65.1–.2; 1995.93a, b",false,true,626019,Arms and Armor,Costume armor and sword in the classical style,Costume Armor and Sword in the Classical Style,"French, Paris",,,,,Maker,Helmet includes original paper label of,Hallé,"French, Paris, active ca. 1780–1800",,Hallé,French,1755,1825,ca. 1788–90,1763,1815,"Linen, papier-mâché, bole, gold leaf, graphite (helmet); silk, cotton, metal coils and spangles, metallic yarn (tunic); steel, wood, gesso, silver, gold leaf (sword)","Helmet, 15 3/4 x 7 11/16 x 13 3/4 in. (40 x 19.5 x 35 cm); Armor, 26 3/4 x 22 7/16 x 11 in. (68 x 57 x 28 cm); Sword L., 31 7/8 in. (81 cm)","Armor: Funds from various donors, 1988; sword: Purchase, Gift of Estate of James Hazen Hyde, by exchange, and Rogers Fund, 1995",,,,,,,,,,,,Costumes,,http://www.metmuseum.org/art/collection/search/626019,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.144.2a–n,false,true,27367,Arms and Armor,Cased pair of percussion pistols,Cased Pair of Percussion Pistols,"French, Paris",,,,,Artist,,Louis-Julien Gastinne-Renette,"French, Paris 1812–1885 Paris",,Gastinne-Renette Louis-Julien,French,1812,1885,mid-19th century,1851,1900,"Steel, wood (ebony), gold, velvet, brass, baize","L. of each pistol (a, b) 16 9/16 in. (42 cm); L. of each barrel 11 1/4 in. (28.6 cm); Cal. of each barrel .46 in. (11.7 mm); Wt. of each 2 lb. 6.7 oz. (1097.1 g); case (c); H. 3 in. (7.6 cm); W. 19 in. (48.3 cm); D. 10 1/2 in. (26.7 cm); Wt. 6 lb. 1.2 oz. (2755.6 g); screwdriver (d); L. 5 3/8 in. (13.7 cm); Wt. 1.7 oz. (48.2 g); nipple wrench (e); L. 5 1/2 in. (14 cm); Wt. 2.2 oz. (62.4 g); bullet mould (f); L. 5 7/8 in. (14.9 cm); Wt. 4.2 oz. (119.1 g); powder measure (g); L. 3 5/8 in. (9.2 cm); Wt. 0.4 oz. (11.3 g); patch box (h); H. 1 1/2 in. (3.8 cm); Diam. 1 1/2 in. (3.8 cm); Wt. 1.1 oz. (31.2 g); percussion cap box with contents (i); H. 1 3/8 in. (3.5 cm); Diam. 2 1/8 in. (5.4 cm); Wt. 2.6 oz. (73.7 g); mallet (j); L. 7 7/8 in. (20 cm); W. 2 1/2 in. (6.4 cm); Wt. 4.7 oz. (133.2 g); ramrod (k); L. 11 1/4 in. (28.6 cm); Wt. 1.5 oz. (42.5 g); cleaning rod (l); L. 11 7/8 in. (30.2 cm); Wt. 1.7 oz. (48.2 g); key (n); L. 1 1/2 in. (3.8 cm); Wt. 0.2 oz. (5.7 cm)","Gift of Mrs. George Henry Warren, in memory of her husband, 1972",,Paris,,,,,,,,,,Firearms-Pistols-Percussion,,http://www.metmuseum.org/art/collection/search/27367,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2012.138,false,true,35854,Arms and Armor,Drawing,Design for a Percussion Pistol,"French, Paris",,,,,Artist,,Martin Riester,"French, Colmar 1819–1883 Paris",,"Riester, Martin",French,1819,1883,dated 1850,1850,1850,"Pen, ink, and pencil on paper",11 x 7 1/8 in. (28 x 18 cm),"Purchase, James C. Meade Gift, 2012",,Paris,,,,,,,,,,Works on Paper-Drawings,,http://www.metmuseum.org/art/collection/search/35854,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -25.135.174,false,true,27290,Arms and Armor,Drawing,Architectural Drawing of the Exterior of the Comte de Nieuwerkerke's House,"French, Paris",,,,,Artist,Studio of,Hector-Martin Lefuel,"French, Versailles 1810–1880 Paris",,"Lefuel, Hector-Martin",French,1810,1880,ca. 1870,1845,1895,Pen on paper,16 1/8 x 9 3/8 in. (40.9 x 23.8 cm),"Gift of William H. Riggs, 1913",,Paris,,,,,,,,,,Works on Paper-Drawings,,http://www.metmuseum.org/art/collection/search/27290,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.25.1878,false,true,436699,Arms and Armor,Painting,William H. Riggs (1837–1924) in Sixteenth-Century Half-Armor,"French, Paris",,,,,Artist,,Ferdinand Humbert,"French, 1842–1934",,"Humbert, Ferdinand",French,1842,1934,dated 1871,1871,1871,Oil on canvas,22 x 15 in. (55.9 x 38.1 cm),"Gift of William H. Riggs, 1913",,Paris,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436699,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2003.174a–gg,false,true,26743,Arms and Armor,"Album of thirty-five original drawings of swords, firearms, and related items",Designs for the Ornament of Swords and Firearms,"French, Paris",,,,,Designer,Workshop of,Louis-François Devisme,"French, Paris, active 1833–1886",,"Devisme, Louis-François",French,1833,1886,ca. 1850–60,1825,1885,"Leather, paper, pencil, ink, colored wash",19 x 12 1/2 in. (48.2 x 31.7 cm),"Purchase, Arthur Ochs Sulzberger Gift, 2003",,Paris,,,,,,,,,,Works on Paper-Drawings,,http://www.metmuseum.org/art/collection/search/26743,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.25.454,false,true,22209,Arms and Armor,Partisan,"Partisan Carried by the Bodyguard of Louis XIV (1638–1715, reigned from 1643)","French, Paris",,,,,Designer,,Jean Berain,"French, Saint-Mihiel 1640–1711 Paris",,"Berain, Jean",French,1640,1711,ca. 1670–80,1645,1705,"Steel, gold, wood, textile",L. 86 11/16 in. (220.2 cm); L. of head 20 9/16 in. (52.2 cm) W. of head 6 1/16 in. (15.4 cm),"Gift of William H. Riggs, 1913",,Paris,,,,,,,,,,Shafted Weapons,,http://www.metmuseum.org/art/collection/search/22209,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1978.498,false,true,24877,Arms and Armor,Drawing,Design for Shield,"French, Paris",,,,,Designer,,Étienne Delaune,"French, Orléans 1518/19–1583 Strasbourg",,"Delaune, Étienne",French,1518,1583,ca. 1550,1550,1550,"Paper, chalk",7 3/4 x 3 3/4 in. (19.7 x 9.6 cm),"Purchase, Rogers Fund, 1978",,Paris,,,,,,,,,,Works on Paper-Drawings,,http://www.metmuseum.org/art/collection/search/24877,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.170,false,true,23348,Arms and Armor,Powder horn,Powder Flask,"German, Nuremberg",,,,,Silversmith,,Jeremias Ritter,"German, recorded 1605–46",,"Ritter, Jeremias",German,1605,1646,ca. 1610,1585,1635,"Staghorn, silver, gold",L. 8 1/4 in. (21 cm); W. 4 in. (10.2 cm),"Rogers Fund, 1929",,Nuremberg,,,,,,,,,,Firearms Accessories-Powder Horns,,http://www.metmuseum.org/art/collection/search/23348,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.153.1,false,true,23201,Arms and Armor,Jousting sallet (Rennhut),"Jousting Sallet Made for Louis II (1506–1526), King of Hungary and Bohemia","German, Augsburg",,,,,Armorer,Attributed to,Kolman Helmschmid,"German, Augsburg 1471–1532",,"Helmschmid, Kolman",German,1471,1532,ca. 1525,1500,1550,"Steel, copper alloy, gold",H. 10 in. (25.4 cm); W. 15 in. (38.1 cm); D. 10 in. (25.4 cm); Wt. 9 lb. (4082 g),"Bashford Dean Memorial Collection, Gift of Mr. and Mrs. Alexander McMillan Welch, 1929",,Augsburg,,,,,,,,,,Helmets,,http://www.metmuseum.org/art/collection/search/23201,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -04.3.286a,false,true,35825,Arms and Armor,Close helmet with mask visor in form of a human face,Close Helmet with Mask Visor in Form of a Human Face,"German, Augsburg",,,,,Armorer,Attributed to,Kolman Helmschmid,"German, Augsburg 1471–1532",,"Helmschmid, Kolman",German,1471,1532,ca. 1515,1490,1540,"Steel, gold",H. 12 in. (30.5 cm); W. 9 3/4 in. (24.8 cm); D. 13 in. (33 cm); Wt. 4 lb. 12 oz. (2146 g),"Rogers Fund, 1904",,Augsburg,,,,,,,,,,Helmets,,http://www.metmuseum.org/art/collection/search/35825,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -27.159.18,false,true,26441,Arms and Armor,Closed burgonet,Closed Burgonet,"German, Augsburg",,,,,Armorer,Attributed to,Kolman Helmschmid,"German, Augsburg 1471–1532",,"Helmschmid, Kolman",German,1471,1532,ca. 1525–30,1500,1555,"Steel, leather",H. 14 5/8 in. (37.1 cm); W. 9 in. (22.9 cm); D. 12 7/8 in. (32.7 cm); Wt. 6 lb. 10 oz. (3004 g),"Gift of George D. Pratt, 1927",,Augsburg,,,,,,,,,,Helmets,,http://www.metmuseum.org/art/collection/search/26441,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"29.150.6a, b",false,true,35826,Arms and Armor,Armet for the tourney,Armet for the Tourney,"German, Augsburg",,,,,Armorer,Attributed to,Kolman Helmschmid,"German, Augsburg 1471–1532",,"Helmschmid, Kolman",German,1471,1532,ca. 1515–20,1490,1545,"Steel, copper alloy",H. 12 1/2 in. (31.8 cm); W. 9 3/4 in. (24.8 cm); D. 11 7/16 in. (29.1 cm); Wt. 8 lb. 8 oz. (3866 g),"Bashford Dean Memorial Collection, Bequest of Bashford Dean, 1928",,Augsburg,,,,,,,,,,Helmets,,http://www.metmuseum.org/art/collection/search/35826,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -24.179,false,true,22875,Arms and Armor,Backplate and hoguine (rump defense) from a costume armor,Backplate and Hoguine (Rump Defense) from a Costume Armor,"German, Augsburg",,,,,Armorer,,Kolman Helmschmid,"German, Augsburg 1471–1532",,"Helmschmid, Kolman",German,1471,1532,ca. 1525,1500,1550,"Steel, gold",H. 27 in. (68.6 cm); W. 18 in. (45.7 cm),"Gift of Bashford Dean, 1924",,Augsburg,,,,,,,,,,Armor Parts,,http://www.metmuseum.org/art/collection/search/22875,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"26.188.1, .2",false,true,22949,Arms and Armor,Pair of Vambraces (arm defenses) from a costume armor,Pair of Vambraces (Arm Defenses) from a Costume Armor,"German, Augsburg",,,,,Armorer,,Kolman Helmschmid,"German, Augsburg 1471–1532",,"Helmschmid, Kolman",German,1471,1532,ca. 1525,1500,1550,"Steel, gold",H. 16 in. (40.6 cm); W. 8 in. (20.3 cm); D. 12 in. (30.5 cm),"Mrs. Stephen V. Harkness Fund, 1926",,Augsburg,,,,,,,,,,Armor Parts,,http://www.metmuseum.org/art/collection/search/22949,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"29.158.363a, b",false,true,23307,Arms and Armor,Top lames of vambraces (arm defenses) from a costume armor,Top Lames of Vambraces (Arm Defenses) from a Costume Armor,"German, Augsburg",,,,,Armorer,,Kolman Helmschmid,"German, Augsburg 1471–1532",,"Helmschmid, Kolman",German,1471,1532,ca. 1525,1500,1550,"Steel, copper alloy, gold",H. 11 1/2 in. (29.2 cm); W. 10 in. (25.4 cm); D. 12 in. (30.5 cm),"Bashford Dean Memorial Collection, Funds from various donors, 1929",,Augsburg,,,,,,,,,,Armor Parts,,http://www.metmuseum.org/art/collection/search/23307,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -04.3.286,false,true,22000,Arms and Armor,Armor,Armor,"German, Augsburg and Landshut",,,,,Armorer,Helmet attributed to,Kolman Helmschmid,"German, Augsburg 1471–1532",,"Helmschmid, Kolman",German,1471,1532,ca. 1515 and later,1490,1900,"Steel, gold, leather",Helmet (04.3.286a); H. 12 in. (30.5 cm); W. 9 3/4 in. (24.8 cm); D. 13 in. (33 cm); Wt. 4 lb. 12 oz. (2146 g),"Rogers Fund, 1904",,Augsburg and Landshut,,,,,,,,,,Armor for Man,,http://www.metmuseum.org/art/collection/search/22000,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"24.179; 26.188.1, .2; 29.158.363a, b",true,true,27790,Arms and Armor,Portions of a costume armor,Portions of a Costume Armor,"German, Augsburg",,,,,Armorer,,Kolman Helmschmid,"German, Augsburg 1471–1532",,"Helmschmid, Kolman",German,1471,1532,ca. 1525,1500,1550,"Steel, gold",H. 27 in. (68.6 cm); W. 18 in. (45.7 cm),"Backplate and rump: Gift of Bashford Dean, 1924; vambraces: Mrs. Stephen V. Harkness Fund, 1926; top lames of vambraces: Bashford Dean Memorial Collection, Funds from various donors, 1929",,Augsburg,,,,,,,,,,Armor for Man,,http://www.metmuseum.org/art/collection/search/27790,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.151.3a–s,false,true,23199,Arms and Armor,Armor,Three-Quarter Armor,"German, Augsburg",,,,,Armorer,Pauldrons and vambraces attributed to,Kolman Helmschmid,"German, Augsburg 1471–1532",,"Helmschmid, Kolman",German,1471,1532,ca. 1525 and later,1500,1925,"Steel, leather",Wt. 48 lb. 7 oz. (21.97 kg); helmet (a); H. 12 in. (30.5 cm); W. 11 in. (27.9 cm); D. 12 1/2 in. (31.8 cm); Wt. 6 lb. 1.2 oz. (2755.6 g); gauntlet (p); H. 10 1/2 in. (26.7 cm); W. 5 in. (12.7 cm); D. 4 3/8 in. (11.1 cm); Wt. 1 lb. 4.5 oz. (581.2 g); gauntlet (q); H. 10 1/2 in. (26.7 cm); W. 5 in. (12.7 cm); D. 4 3/8 in. (11.1 cm); Wt. 1 lb. 5.5 oz. (609.5 g); mail sleeve (r): L. 25 1/2 in. (65.0 cm); H. at shoulder 10 3/16 in. (26.0 cm); Diam. (outside) of chest links 5/16 in. (7.6 mm); Diam. (inside) of chest links 3/16 in. (4.8 mm); Diam. (outside) of sleeve links 5/16 in. (7.7 mm); Diam. (inside) of sleeve links 7/32 in. (5.8 mm); mail sleeve (s): L. 26 13/16 in. (68.0 cm); H. at shoulder 11 in. (28.0 cm); Diam. (outside) of chest links 11/32 in. (8.6 mm); Diam. (inside) of chest links 3/16 in. (4.8 mm); Diam. (outside) of sleeve links 9/32 in. (7.1 mm); Diam. (inside) of sleeve links 7/32 in. (5.6 mm).,"Bashford Dean Memorial Collection, Gift of Mrs. Bashford Dean, 1929",,Augsburg,,,,,,,,,,Armor for Man-3/4 Armor,,http://www.metmuseum.org/art/collection/search/23199,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.25.828,false,true,22302,Arms and Armor,Left pauldron (shoulder defense),Left Shoulder Defense (Pauldron),"German, Augsburg",,,,,Armorer,,Kolman Helmschmid,"German, Augsburg 1471–1532",,"Helmschmid, Kolman",German,1471,1532,ca. 1525,1500,1550,"Steel, copper alloy, leather",H. 10 1/4 in. (26 cm); W. 9 1/4 in. (23.5 cm); D. 12 in. (30.5 cm); Wt. 2 lb. 10.4 oz. (1202 g),"Gift of William H. Riggs, 1913",,Augsburg,,,,,,,,,,Armor Parts-Arms & Shoulders,,http://www.metmuseum.org/art/collection/search/22302,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -20.151.7–.8,false,true,22749,Arms and Armor,Two ear guards,Two Ear Guards from a Shaffron (Horse's Head Defense) of Emperor Charles V (1500–1558),"German, Augsburg",,,,,Armorer,Attributed to,Desiderius Helmschmid,"German, Augsburg, 1513–1579",,"Helmschmid, Desiderius",German,1513,1579,1544,1544,1544,"Steel, gold","20.151.7; H. 5 3/8 in. (13.7 cm); W. 2 3/4 in. (7 cm), Wt. 2 oz. (47 g); 20.151.8; H. 5 1/4 in. (13.3 cm); W. 2 7/8 in. (7.3 cm), Wt. 2 oz. (51 g)","Rogers Fund, 1920",,Augsburg,,,,,,,,,,Equestrian Equipment-Shaffrons,,http://www.metmuseum.org/art/collection/search/22749,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.154.1a–u,false,true,23203,Arms and Armor,Armor for the tilt,Armor for the Tilt,"German, Augsburg",,,,,Armorer,Attributed to,Anton Peffenhauser,"German, Augsburg, 1525–1603",,"Peffenhauser, Anton",German,1525,1603,ca. 1580,1555,1605,"Steel, brass, leather",H. 68 3/4 in. (174.6 cm); W. at shoulders 18 in. (45.72 cm); Wt. 81 lb. (36.8 kg),"Bashford Dean Memorial Collection, Gift of Helen Fahnestock Hubbard, in memory of her father, Harris C. Fahnestock, 1929",,Augsburg,,,,,,,,,,Armor for Man,,http://www.metmuseum.org/art/collection/search/23203,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -38.163.1a–d,false,true,26534,Arms and Armor,Elements of an armor garniture,Elements from a Garniture Made for Christian I of Saxony (1560–1591),"German, Augsburg",,,,,Armorer,,Anton Peffenhauser,"German, Augsburg, 1525–1603",,"Peffenhauser, Anton",German,1525,1603,1582,1582,1582,"Steel, gold, leather, brass",breastplate (a): 19 1/2 x 15 3/8 in. (49.6 x 39 cm); backplate (b): 17 1/2 x 14 3/16 in. (44.5 x 36 cm); right cuisse (c): 16 x 7 3/8 in. (40.6 x 18.7 cm); Wt. 2 lb. 6 oz. (1088 g); left cuisse (d): 16 1/2 x 7 1/2 in. (41.9 x 19 cm); Wt. 2 lb. 7 oz. (1098 g),"Fletcher Fund, 1938",,Augsburg,,,,,,,,,,Armor for Man,,http://www.metmuseum.org/art/collection/search/26534,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.25.689a–p,false,true,22252,Arms and Armor,Jousting armor,Jousting Armor,"German, Augsburg",,,,,Armorer,Left Vambrace attributed to,Anton Peffenhauser,"German, Augsburg, 1525–1603",,"Peffenhauser, Anton",German,1525,1603,ca. 1580,1555,1605,"Steel, leather",Wt. 55 lb. (24.95 kg),"Gift of William H. Riggs, 1913",,Augsburg,,,,,,,,,,Armor for Man,,http://www.metmuseum.org/art/collection/search/22252,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"29.154.1a–u; 14.25.689h, .894; 29.158.204",false,true,35873,Arms and Armor,Armor for the tilt,Armor for the Tilt,"German, Augsburg",,,,,Armorer,Attributed to,Anton Peffenhauser,"German, Augsburg, 1525–1603",,"Peffenhauser, Anton",German,1525,1603,ca. 1580,1555,1605,"Steel, brass, leather",H. 68 3/4 in. (174.6 cm); W. at shoulders 18 in. (45.72 cm); Wt. 81 lb. (36.8 kg),"armor: Bashford Dean Memorial Collection, Gift of Helen Fahnestock Hubbard, in memory of her father, Harris C. Fahnestock, 1929; left vambrace and left gauntlet: Gift of William H. Riggs, 1913; mail brayette: Bashford Dean Memorial Collection, Funds from various donors, 1929",,Augsburg,,,,,,,,,,Armor for Man,,http://www.metmuseum.org/art/collection/search/35873,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1984.72,false,true,24903,Arms and Armor,Gauntlet for the left hand,Gauntlet for the Left Hand,"German, Augsburg",,,,,Goldsmith,Gilt copper ornament attributed to,Jörg Sigman,"German, Augsburg, 1527–1601",,"Sigman, Jörg",German,1527,1601,ca. 1557,1532,1582,"Steel, gold, copper alloy, leather",L. 12 in. (30.5 cm),"Purchase, Bequest of Stephen V. Grancsay, by exchange, 1984",,Augsburg,,,,,,,,,,Armor Parts-Gauntlets,,http://www.metmuseum.org/art/collection/search/24903,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2010.165,false,true,35367,Arms and Armor,Cross hilt sword,Cross Hilt Sword,"hilt, British, London; blade, German, Solingen",,,,,Bladesmith,Blade signed by,Clemens Horn,"German, Solingen, 1580–1630",,"Horn, Clemens",German,1580,1630,1600–1625,1600,1625,"Iron, silver, wood, copper alloy, steel, gold",L. 39 1/4 in. (99.7 cm); L. of blade 30 1/4 in. (76.8 cm); W. 8 3/4 in. (22.2 cm); Wt. 2 lb. 6.5 oz (1093 g),"Purchase, Arthur Ochs Sulzberger Gift, 2010",,London|Solingen,,,,,,,,,,Swords,,http://www.metmuseum.org/art/collection/search/35367,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.164a–x,true,true,23944,Arms and Armor,Armor,Armor of Emperor Ferdinand I (1503–1564),"German, Nuremberg",,,,,Armorer,,Kunz Lochner,"German, Nuremberg, 1510–1567",,"Lochner, Kunz",German,1510,1567,dated 1549,1549,1549,"Steel, brass, leather",H. 67 in. (170.2 cm); Wt. 52 lb. 14 oz. (24 kg),"Purchase, Rogers Fund and George D. Pratt Gift, 1933",,Nuremberg,,,,,,,,,,Armor,,http://www.metmuseum.org/art/collection/search/23944,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.25.628,false,true,26507,Arms and Armor,Burgonet,Burgonet,"German, Nuremberg",,,,,Armorer,Attributed to,Kunz Lochner,"German, Nuremberg, 1510–1567",,"Lochner, Kunz",German,1510,1567,ca. 1540–50,1515,1575,"Steel, leather, textile",H. 12 1/2 in. (31.8 cm); W. 7 7/8 in. (20 cm); D. 12 1/4 in. (31.1 cm); Wt. 3 lb. 15 oz. (1786 g),"Gift of William H. Riggs, 1913",,Nuremberg,,,,,,,,,,Helmets,,http://www.metmuseum.org/art/collection/search/26507,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.25.856,false,true,26581,Arms and Armor,Left pauldron (shoulder defense),"Left Pauldron (Shoulder Defense) Belonging to an Armor for Field and Tournament Made for Duke Nikolaus ""The Black"" Radziwill (1515–1565), Duke of Nesvizh and Olyka, Prince of the Empire, Grand Chancellor and Marshal of Lithuania","German, Nuremberg",,,,,Armorer,,Kunz Lochner,"German, Nuremberg, 1510–1567",,"Lochner, Kunz",German,1510,1567,ca. 1555; probably repainted later,1530,1900,"Steel, gold, paint",H. 8 1/2 in. (12.6 cm); W. 12 1/2 in. (31.7 cm); Wt. 1 lb. 10 oz. (730 g),"Gift of William H. Riggs, 1913",,Nuremberg,,,,,,,,,,Armor Parts,,http://www.metmuseum.org/art/collection/search/26581,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.151.2a–s,false,true,23198,Arms and Armor,Armor,Armor,"German, Nuremberg",,,,,Armorer,,Kunz Lochner,"German, Nuremberg, 1510–1567",,"Lochner, Kunz",German,1510,1567,"dated 1548, with later restorations",1548,1900,"Steel, leather, copper alloy, textile",Wt. approx. 56 lb. (25.4 kg),"Bashford Dean Memorial Collection, Gift of Mrs. Bashford Dean, 1929",,Nuremberg,,,,,,,,,,Armor for Man,,http://www.metmuseum.org/art/collection/search/23198,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.25.854,false,true,26579,Arms and Armor,Crinet plate,"Crinet Plate Belonging to an Armor for Field and Tournament Made for Duke Nikolaus ""The Black"" Radziwill (1515–1565), Duke of Nesvizh and Olyka, Prince of the Empire, Grand Chancellor and Marshal of Lithuania","German, Nuremberg",,,,,Armorer,,Kunz Lochner,"German, Nuremberg, 1510–1567",,"Lochner, Kunz",German,1510,1567,ca. 1555; probably repainted later,1530,1900,"Steel, brass, gold, paint",L. 13 7/8 in. (35.5 cm); H. 3 7/8 in. (9.8 cm); Wt. 9 oz. (261 g),"Gift of William H. Riggs, 1913",,Nuremberg,,,,,,,,,,Armor for Horse,,http://www.metmuseum.org/art/collection/search/26579,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -32.69a–q,false,true,23358,Arms and Armor,Horse armor,"Horse Armor Made for Johann Ernst, Duke of Saxony-Coburg (1521–1553)","German, Nuremberg",,,,,Armorer,,Kunz Lochner,"German, Nuremberg, 1510–1567",,"Lochner, Kunz",German,1510,1567,dated 1548,1548,1548,"Steel, leather, copper alloy, textile",Wt. including saddle 92 lb. (41.73 kg) Bit: H. 6 in (15.2 cm); W. 11 in (27.9 cm),"Rogers Fund, 1932",,Nuremberg,,,,,,,,,,Armor for Horse,,http://www.metmuseum.org/art/collection/search/23358,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.25.854; 21.42,false,true,684263,Arms and Armor,Crinet plate and shaffron,"Crinet Plate and Shaffron Belonging to an Armor for Field and Tournament Made for Duke Nikolaus ""The Black"" Radziwill (1515–1565), Duke of Nesvizh and Olyka, Prince of the Empire, Grand Chancellor and Marshal of Lithuania","German, Nuremberg",,,,,Armorer,,Kunz Lochner,"German, Nuremberg, 1510–1567",,"Lochner, Kunz",German,1510,1567,ca. 1555; probably repainted and eye guards restored later,1530,1900,"Steel, brass, gold, paint",crinet plate: L. 13 7/8 in. (35.5 cm); H. 3 7/8 in. (9.8 cm); Wt. 9 oz. (261 g); shaffron: H. 23 1/4 in. (59 cm); W. 13 1/4 in. (33.6 cm); Wt. 4 lb. 8 oz. (2034 g),"Crinet plate: Gift of William H. Riggs, 1913; shaffron: Rogers Fund, 1921",,Nuremberg,,,,,,,,,,Armor for Horse,,http://www.metmuseum.org/art/collection/search/684263,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"14.25.854–.856, .865, .881a, b; 21.42",false,true,684262,Arms and Armor,Portions of an armor garniture for field and tournament,"Portions of an Armor Garniture for Field and Tournament Made for Duke Nikolaus ""The Black"" Radziwill (1515–1565), Duke of Nesvizh and Olyka, Prince of the Empire, Grand Chancellor and Marshal of Lithuania","German, Nuremberg",,,,,Armorer,,Kunz Lochner,"German, Nuremberg, 1510–1567",,"Lochner, Kunz",German,1510,1567,ca. 1555; probably repainted and shaffron eye guards restored later,1530,1900,"Steel, brass, gold, paint, leather, textile (velvet)",crinet plate (14.25.854): L. 13 7/8 in. (35.5 cm); H. 3 7/8 in. (9.8 cm); Wt. 9 oz. (261 g); vamplate (14.25.855): D. 8 1/2 in. (33.3 cm); H. 4 7/8 in. (12.4 cm); Diam. 13 1/8 in. (33.3 cm); Wt. 3 lb. 3 oz. (1454 g); left pauldron (14.25.856): H. 8 1/2 in. (12.6 cm); W. 12 1/2 in. (31.7 cm); Wt. 1 lb. 10 oz. (730 g); crinet plate (14.25.865): H. 2 3/8 in. (6 cm); W. 10 5/8 in. (27 cm); D. 6 5/16 in. (16 cm); Wt. 8 oz. (236 g); right tasset (14.25.881a): H. 6 in. (15.2 cm); W. 7 3/4 in. (19.7 cm); Wt. 1 lb. 5 oz. (587 g); left tasset (14.25.881b): H. 7 1/2 in. (19 cm); W. 8 1/2 in. (21.6 cm); Wt. 1 lb. 10 oz. (726 g); shaffron (21.42): H. 23 1/4 in. (59 cm); W. 13 1/4 in. (33.6 cm); Wt. 4 lb. 8 oz. (2034 g),"Gift of William H. Riggs, 1913",,Nuremberg,,,,,,,,,,Armor for Horse,,http://www.metmuseum.org/art/collection/search/684262,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.25.855,false,true,26580,Arms and Armor,Vamplate (handguard for the lance) for use in the field and tilt,"Vamplate Belonging to an Armor for Field and Tournament Made for Duke Nikolaus ""The Black"" Radziwill (1515–1565), Duke of Nesvizh and Olyka, Prince of the Empire, Grand Chancellor and Marshal of Lithuania","German, Nuremberg",,,,,Armorer,,Kunz Lochner,"German, Nuremberg, 1510–1567",,"Lochner, Kunz",German,1510,1567,ca. 1555; probably repainted later,1530,1900,"Steel, gold, paint",D. 8 1/2 in. (33.3 cm); H. 4 7/8 in. (12.4 cm); Diam. 13 1/8 in. (33.3 cm); Wt. 3 lb. 3 oz. (1454 g),"Gift of William H. Riggs, 1913",,Nuremberg,,,,,,,,,,Shafted Weapons,,http://www.metmuseum.org/art/collection/search/26580,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.25.865,false,true,22316,Arms and Armor,Crinet plate,"Crinet Plate Belonging to an Armor for Field and Tournament Made for Duke Nikolaus ""The Black"" Radziwill (1515–1565), Duke of Nesvizh and Olyka, Prince of the Empire, Grand Chancellor and Marshal of Lithuaniac","German, Nuremberg",,,,,Armorer,,Kunz Lochner,"German, Nuremberg, 1510–1567",,"Lochner, Kunz",German,1510,1567,ca. 1555,1530,1580,"Steel, gold, leather, textile (velvet)",H. 2 3/8 in. (6 cm); W. 10 5/8 in. (27 cm); D. 6 5/16 in. (16 cm); Wt. 8 oz. (236 g),"Gift of William H. Riggs, 1913",,Nuremberg,,,,,,,,,,Equestrian Equipment,,http://www.metmuseum.org/art/collection/search/22316,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.151.2a–s; 29.158.183–.184; 32.69a–q,false,true,35739,Arms and Armor,Armor for man and horse,Armor for Man and Horse,"German, Nuremberg",,,,,Armorer,,Kunz Lochner,"German, Nuremberg, 1510–1567",,"Lochner, Kunz",German,1510,1567,"dated 1548, with later restorations",1548,1900,"Steel, leather, copper alloy, textile",man's armor: Wt. approx. 56 lb. (25.4 kg); horse armor with saddle: Wt. 92 lb. (41.7 kg),"man's armor: Bashford Dean Memorial Collection, Gift of Mrs. Bashford Dean, 1929; mail sleeves: Bashford Dean Memorial Collection, Funds from various donors, 1929; horse armor: Rogers Fund, 1932",,Nuremberg,,,,,,,,,,Armor for Horse and Man,,http://www.metmuseum.org/art/collection/search/35739,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -21.42,false,true,26583,Arms and Armor,Shaffron (Horse's head defense),"Shaffron (Horse's Head Defense) Belonging to an Armor for Field and Tournament Made for Duke Nikolaus ""The Black"" Radziwill (1515–1565), Duke of Nesvizh and Olyka, Prince of the Empire, Grand Chancellor and Marshal of Lithuania","German, Nuremberg",,,,,Armorer,,Kunz Lochner,"German, Nuremberg, 1510–1567",,"Lochner, Kunz",German,1510,1567,ca. 1555; probably repainted and eye guards restored later,1530,1900,"Steel, brass, gold, paint",H. 23 1/4 in. (59 cm); W. 13 1/4 in. (33.6 cm); D. 7 1/2 in. (19.1 cm); Wt. 4 lb. 8 oz. (2034 g),"Rogers Fund, 1921",,Nuremberg,,,,,,,,,,Equestrian Equipment-Shaffrons,,http://www.metmuseum.org/art/collection/search/26583,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"14.25.881a, b",false,true,26582,Arms and Armor,Pair of tassets (thigh defenses) for the tilt,"Pair of Tassets (Thigh Defenses) Belonging to an Armor for Field and Tournament Made for Duke Nikolaus ""The Black"" Radziwill (1515–1565), Duke of Nesvizh and Olyka, Prince of the Empire, Grand Chancellor and Marshal of Lithuania","German, Nuremberg",,,,,Armorer,,Kunz Lochner,"German, Nuremberg, 1510–1567",,"Lochner, Kunz",German,1510,1567,ca. 1555; probably repainted later,1530,1900,"Steel, gold, paint",right tasset (a): H. 6 in. (15.2 cm); W. 7 3/4 in. (19.7 cm); Wt. 1 lb. 5 oz. (587 g); left tasset (b): H. 7 1/2 in. (19 cm); W. 8 1/2 in. (21.6 cm); Wt. 1 lb. 10 oz. (726 g),"Gift of William H. Riggs, 1913",,Nuremberg,,,,,,,,,,Armor Parts-Thigh and Leg Defense,,http://www.metmuseum.org/art/collection/search/26582,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"26.145.243a, b",true,true,22914,Arms and Armor,Hunting sword with scabbard,Hunting Sword with Scabbard,"German, possibly Munich",,,,,Sword maker,Grip attributed to,Joseph Deutschmann,"German, Imst 1717–1787 Passau",,"Deutschmann, Joseph",German,1717,1787,ca. 1740,1715,1765,"Steel, silver, ivory, wood, leather",L. 29 1/2 in. (74.9 cm); L. of blade 23 1/4 in. (59 cm); W. of blade 1 1/8 in. (2.8 cm); D. of blade 5/16 in. (0.8 cm);Wt. 1 lb. 2 oz. (510.29 g); Wt. of scabbard 1 lb 6 oz. (624 g),"Gift of Jean Jacques Reubell, in memory of his mother, Julia C. Coster, and of his wife, Adeline E. Post, both of New York City, 1926",,possibly Munich,,,,,,,,,,Swords-Hunting,,http://www.metmuseum.org/art/collection/search/22914,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"69.295.1, .2",false,true,24859,Arms and Armor,Pair of flintlock pistols,Pair of Flintlock Pistols,"German, Regensburg",,,,,Gunsmith,,Johann Andreas Kuchenreuter,"German, Regensburg, 1716–1795",,"Kuchenreuter, Johann Andreas",German,1716,1795,ca. 1760–70,1735,1795,"Steel, wood, bronze, gold, silver, horn",L. 16 3/4 in. (42.6 cm); L. of barrel 11 1/4 in. (28.6 cm); L. of lockplate 4 1/2 in. (11.4 cm); Cal. .56 in. (14.2 mm); Wt. 1 lb. 13 oz. (822 g),"Purchase, Bashford Dean Bequest, 1969",,Regensburg,,,,,,,,,,Firearms-Pistols-Flintlock,,http://www.metmuseum.org/art/collection/search/24859,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.156.45,false,true,23213,Arms and Armor,Sallet,Sallet of Emperor Maximilian I (1459–1519),"German, Augsburg",,,,,Armorer,Attributed to,Lorenz Helmschmid,"German, Augsburg, ca. 1445–1516",,"Helmschmid, Lorenz",German,1425,1525,ca. 1490–95,1465,1520,"Steel, copper alloy, gold",H. 12 in. (30.5 cm); W. 9 in. (22.9 cm); D. 12 3/8 in. (31.4 cm); Wt. 4 lb. 15.7 oz. (2261 g),"Bashford Dean Memorial Collection, Gift of Edward S. Harkness, 1929",,Augsburg,,,,,,,,,,Helmets,,http://www.metmuseum.org/art/collection/search/23213,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"29.156.67h, i",false,true,23208,Arms and Armor,Pair of tournament pauldrons (shoulder defenses),Pair of Tournament Pauldrons (Shoulder Defenses),"German, Augsburg",,,,,Armorer,Marked by,Lorenz Helmschmid,"German, Augsburg, ca. 1445–1516",,"Helmschmid, Lorenz",German,1425,1525,ca. 1500,1475,1525,"Steel, copper alloy",Wt. of right pauldron 3 lb. 15 oz. (1786 g); Wt. of left pauldron 4 lb. (1814.4 g),"Bashford Dean Memorial Collection, Gift of Edward S. Harkness, 1929",,Augsburg,,,,,,,,,,Armor Parts-Arms & Shoulders,,http://www.metmuseum.org/art/collection/search/23208,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"04.3.280, .282; 23.58; 26.234.3",false,true,35727,Arms and Armor,Portions of an armor garniture,Portions of an Armor Garniture,"German, Augsburg",,,,,Etcher,,Jörg Sorg the Younger,"German, Augsburg, ca. 1522–1603",,"Sorg the Younger, Jörg",German,1500,1625,"ca. 1550–55, some etched decoration, 19th century",1525,1900,"Steel, gold, leather, velvet",,"04.3.280, .282: Rogers Fund, 1904; 23.58: Anonymous Gift, in memory of Cornelius Stevenson, 1923; 26.234.3: Gift of George D. Pratt, 1926",,Augsburg,,,,,,,,,,Armor for Man,,http://www.metmuseum.org/art/collection/search/35727,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -04.3.282,false,true,32198,Arms and Armor,Portions of an armor garniture,Portions of an Armor Garniture,"German, Augsburg",,,,,Etcher,,Jörg Sorg the Younger,"German, Augsburg, ca. 1522–1603",,"Sorg the Younger, Jörg",German,1500,1625,"ca. 1550–55, some etched decoration, 19th century",1525,1900,"Steel, gold, leather, velvet",,"Rogers Fund, 1904",,Augsburg,,,,,,,,,,Armor for Man-1/2 Armor,,http://www.metmuseum.org/art/collection/search/32198,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -04.3.288,false,true,25419,Arms and Armor,Pommel plate,Pommel Plate,"German, Augsburg",,,,,Etcher,Attributed to,Jörg Sorg the Younger,"German, Augsburg, ca. 1522–1603",,"Sorg the Younger, Jörg",German,1500,1625,ca. 1550,1525,1575,"Steel, gold","9 x 6 in. (22.9 x 15.2 cm), wt. 7 oz. (185 g)","Rogers Fund, 1904",,Augsburg,Bavaria,,,,,,,,,Equestrian Equipment-Saddles,,http://www.metmuseum.org/art/collection/search/25419,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -25.135.88,false,true,22890,Arms and Armor,Wheellock pistol,Wheellock Pistol Made for Maximilian I of Bavaria (1573–1651),"German, Munich",,,,,Steel-chiseler,,Emanuel Sadeler,"German, Munich, active 1594–1610",,"Sadeler, Emanuel",German,1594,1610,ca. 1600–1610,1575,1635,"Steel, gold",L. 14 3/16 in. (36.0 cm); L. of barrel 8 9/16 in. (21.7 cm); Cal. .45 in. (11.4 mm); Wt. 2 lb. 12 oz. (1247 g),"Gift of William H. Riggs, 1913",,Munich,,,,,,,,,,Firearms-Pistols,,http://www.metmuseum.org/art/collection/search/22890,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.25.1419,false,true,22383,Arms and Armor,Wheellock pistol,Wheellock Pistol,"German, Nuremberg",,,,,Gunsmith,,Peter Danner,"German, Nuremberg, ca. 1580–1602",,"Danner, Peter",German,1555,1627,ca. 1580,1555,1605,"Steel, bronze, gold, wood (walnut), staghorn",L. 19 3/4 in. (50.2 cm); L. of barrel 12 in. (30.5 cm); Cal. .528 in. (13.4 mm); L. of lock 7 5/8 in. (19.4 cm); L. of plug 7 3/8 in. (18.7 cm); Wt. 3 lb. 14 oz. (1758 g),"Gift of William H. Riggs, 1913",,Nuremberg,,,,,,,,,,Firearms-Pistols-Wheellock,,http://www.metmuseum.org/art/collection/search/22383,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.158.11,false,true,26445,Arms and Armor,Sallet,Sallet,"German, Basel",,,,,Armorer,Attributed to,Hans Blarer the Younger,"German, Basel, documented 1453–83",,"Blarer, Hans the Younger",German,1453,1483,ca. 1470–80,1445,1505,Steel,H. 9 3/4 (24.8 cm); W. 8 in. (20.2 cm); D. 14 3/4 (37.4 cm); Wt. 7 lb. 2 oz. (3232 g),"Bashford Dean Memorial Collection, Funds from various donors, 1929",,Basel,,,,,,,,,,Helmets,,http://www.metmuseum.org/art/collection/search/26445,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -04.3.183,false,true,21970,Arms and Armor,Wheellock spanner with priming flask and screwdriver,Wheellock Spanner with Priming Flask and Screwdriver,"German, Munich",,,,,Steel-chiseler,Workshop of,Daniel Sadeler,"German, Munich, recorded 1602–1632",,"Sadeler, Daniel",German,1602,1632,ca. 1610–30,1585,1655,"Steel, gold",L. 9 in. (22.9 cm); W. 2 3/4 in. (7 cm),"Rogers Fund, 1904",,Munich,,,,,,,,,,Firearms Accessories-Flasks & Primers,,http://www.metmuseum.org/art/collection/search/21970,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -04.3.267a,false,true,35890,Arms and Armor,Close helmet,Close Helmet,"German, Landshut",,,,,Armorer,Attributed to,Wolfgang Grosschedel,"German, Landshut, active ca. 1517–62",,"Grosschedel, Wolfgang",German,1517,1562,ca. 1560,1535,1585,"Steel, leather, copper alloy",H. 14 3/4 in. (37.5 cm); W. 9 1/8 in. (23.2 cm); D. 13 1/4 in. (33.7 cm); Wt. 11 lb. 3.1 oz. (5077.4 g),"Rogers Fund, 1904",,Landshut,,,,,,,,,,Helmets,,http://www.metmuseum.org/art/collection/search/35890,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -23.261,false,true,22874,Arms and Armor,Armor for man and horse,Armor for Man and Horse Armor Presumably Made for Baron Pankraz von Freyberg (1508–1565),"German, Landshut",,,,,Armorer,,Wolfgang Grosschedel,"German, Landshut, active ca. 1517–62",,"Grosschedel, Wolfgang",German,1517,1562,"man's armor, ca. 1535–40; horse armor, dated 1554; saddle steels, later restorations",1510,1900,"Steel; leather, copper alloy, textile",Wt. of man's armor approx. 55 lb. 11 oz. (25.25 kg); Wt. of horse armor with saddle 65 lb. 7 oz. (29.69 kg),"Fletcher Fund, 1923",,Landshut,,,,,,,,,,Armor for Horse and Man,,http://www.metmuseum.org/art/collection/search/22874,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.25.633,false,true,22234,Arms and Armor,Morion,Morion for the Bodyguard of the Prince-Elector of Saxony,"German, Nuremberg",,,,,Armorer,Probably,Martin Schneider the Younger,"German, Nuremberg, active ca. 1610–20",,"Schneider the Younger, Martin",German,1610,1620,ca. 1570,1545,1595,"Steel, gold, brass, leather",H. 11 9/16 in. (29.4 cm); W. 9 1/4 in. (23.5 cm); D. 13 3/4 in. (34.9 cm); Wt. 3 lb. 5 oz. (1503 g),"Gift of William H. Riggs, 1913",,Nuremberg,,,,,,,,,,Helmets,,http://www.metmuseum.org/art/collection/search/22234,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.25.684a–i,false,true,22251,Arms and Armor,Half armor,Half Armor,"German, Nuremberg",,,,,Armorer,Attributed to,Martin Schneider the Younger,"German, Nuremberg, active ca. 1610–20",,"Schneider the Younger, Martin",German,1610,1620,1610–20,1610,1620,Steel,Wt. 41 lb. 15 oz. (19.01 kg),"Gift of William H. Riggs, 1913",,Nuremberg,,,,,,,,,,Armor for Man-1/2 Armor,,http://www.metmuseum.org/art/collection/search/22251,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.150.8a,false,true,23091,Arms and Armor,Sallet,Sallet,"German, Landshut",,,,,Armorer,,Matthes Deutsch,"German, Landshut, documented 1485–1505",,"Deutsch, Matthes",German,1485,1505,ca. 1490,1465,1515,"Steel, textile",H. 9 3/4 in. (24.8 cm); W. 9 3/4 in. (24.8 cm); D. 15 in. (38.1 cm); Wt. 9 lb. 4 oz. (4190 g),"Bashford Dean Memorial Collection, Bequest of Bashford Dean, 1928",,Landshut,,,,,,,,,,Helmets,,http://www.metmuseum.org/art/collection/search/23091,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -50.160a–x,false,true,24814,Arms and Armor,Composed armor,Composed Armor,"European, Italian and German",,,,,Armorer,Right pauldron (shoulder defense) marked by,Matthes Deutsch,"German, Landshut, documented 1485–1505",,"Deutsch, Matthes",German,1485,1505,15th century and later,1400,1900,"Steel, iron, copper alloy (latten), leather, brass","Wt. approx. 41 lb. 12 oz. (18.94 kg); 50.160i (gauntlet); L. 11 3/4 in. (29.8 cm); W. 5 3/8 in. (13.7 cm); D. 5 in. (12.7 cm); Wt. 1 lb. (453.6 g); 50.160h (elbow reinforce); H. 8 3/8 in. (21.3 cm); W. 4 1/4 in. (10.8 cm); D. 9 1/2 in. (24.1 cm); Wt. 12.2 oz. (345.9 g); 50.160o (greave); H. 14 13/16 in. (37.6 cm); W. 4 5/8 in. (11.7 cm); D. 5 5/16 in. (13.5 cm); Wt. 1 lb. 9.2 oz. (714.4 g); 50.160n (thigh defense); H. 20 3/8 in. (51.8 cm); W. 6 11/16 in. (17 cm); D. 7 1/2 in. (19.1 cm); Wt. 4 lb. 5.5 oz. (1970.3 g); 50.160u (mail collar with integral bevor); H. 15 5/8 in. (37 cm); W. 31 5/16 in. (79.5 cm); Diam. (outside) of collar links 11/32 in. (8.3 mm); Diam. (inside) of collar links 7/32 in. (5.9 mm); Diam. (outside) of shawl links 5/16 in. (7.7 mm); Diam. (inside) of shawl links 1/4 in. (6.3 mm); 50.160v (mail sleeve); L. 29 1/8 in. (74 cm) W. 13 in. (33 cm); Diam. (outside) of links, 9/32 in. (7.2 mm); Diam. (inside) of links, 3/16 in. (5 mm); 50.160w (mail sleeve): L. 27 5/8 in. (70.0 cm); W. 12 3/16 in. (31.0 mm); Diam. (outside) of links 9/32 in. (6.8 mm); Diam. (inside) of links, 3/16 in. (4.6 mm); 50.160x (pair of mail paunces); H. 8 11/16 in. (22 cm); H. of skirt 14 13/16 in. (37.5 cm); W. of bravette 26 in. (66 cm); Diam. of waist 32 1/8 in. (81.5 cm); Diam. (outside) of links 13/32 in. (10.6 mm); Diam. (inside) of links, 7/32 in. (5.6 mm)","Gift of Mrs. Alexander McMillan Welch, in memory of her sister, Mary Alice Dyckman Dean (Mrs. Bashford Dean), 1950",,Landshut,,,,,,,,,,Armor for Man,,http://www.metmuseum.org/art/collection/search/24814,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.25.756,false,true,22282,Arms and Armor,Vamplate (Hand guard for a lance),Vamplate (Hand Guard for a Lance),"German, Landshut",,,,,Armorer,,Matthes Deutsch,"German, Landshut, documented 1485–1505",,"Deutsch, Matthes",German,1485,1505,ca. 1490,1465,1515,Steel,H. 16 3/8 in. (41.5 cm); W. 11 3/8 in. (28.9 cm),"Gift of William H. Riggs, 1913",,Landshut,,,,,,,,,,Shafted Weapons,,http://www.metmuseum.org/art/collection/search/22282,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -25.188.18,false,true,22899,Arms and Armor,Glaive,Glaive of Emperor Rudolf II (reigned 1576–1612),"German, Augsburg",,,,,Etcher,,Hans Stromair,"German, Augsburg, 1524 or 1525–ca. 1583",,"Stromair, Hans",German,1524,1600,dated 1577,1577,1577,"Steel, wood, textile, gold",L. 100 1/2 in. (255.27 cm); blade L. 23 in. (58.42 cm),"Gift of George D. Pratt, 1925",,Augsburg,,,,,,,,,,Shafted Weapons,,http://www.metmuseum.org/art/collection/search/22899,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -04.3.158,false,true,21963,Arms and Armor,Hunting knife combined with wheellock pistol,Hunting Knife Combined with Wheellock Pistol,"German, Munich",,,,,Etcher,,Ambrosius Gemlich,"German, Munich and Landshut, active ca. 1520–50",,"Gemlich, Ambrosius",German,1495,1575,"blade ca. 1528–29, etched with a calendar for the years 1529–34; barrel dated 1540 or 1546",1528,1546,"Steel, gold, staghorn, bronze",L. 18 1/4 in. (46.4 cm); L. of barrel 12 3/8 in. (31.4 cm); L. of blade 13 1/4 in. (33.7 cm); Cal. .28 in. (7.1 mm),"Rogers Fund, 1904",,Munich,,,,,,,,,,Combination Weapons,,http://www.metmuseum.org/art/collection/search/21963,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.25.1655,false,true,27181,Arms and Armor,Crinet,Crinet,"German, Nuremberg",,,,,Armorer,,Valentin Siebenbürger,"German, Nuremberg, ca. 1510–1564, master in 1531",,"Siebenbürger, Valentin",German,1485,1564,ca. 1535,1510,1560,Steel,"L. 37 1/2 in. (95.2 cm); W. 11 3/4 in. (29.9 cm); Wt. 6 lb. 14 oz. (3,070 g)","Gift of William H. Riggs, 1913",,Nuremberg,,,,,,,,,,Armor for Horse,,http://www.metmuseum.org/art/collection/search/27181,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -96.5.115c,false,true,26575,Arms and Armor,Breastplate,Breastplate,"German, Nuremberg",,,,,Armorer,,Valentin Siebenbürger,"German, Nuremberg, ca. 1510–1564, master in 1531",,"Siebenbürger, Valentin",German,1485,1564,ca. 1530–35,1505,1560,Steel,H. 15 in. (38 cm); W. 13 1/2 in. (34.5 cm),"John Stoneacre Ellis Collection, Gift of Mrs. Ellis and Augustus Van Horne Ellis, 1896",,Nuremberg,,,,,,,,,,Armor Parts-Breastplates,,http://www.metmuseum.org/art/collection/search/26575,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"19.131.1a–r, t–w, .2a–c; 27.183.16",true,true,35775,Arms and Armor,Armor garniture,"Armor Garniture, Probably of King Henry VIII of England (reigned 1509–47)","British, Greenwich",,,,,Armorer|Designer,Made at the|Design of the decoration attributed to,Royal Workshops at Greenwich|Hans Holbein the Younger,"British, Greenwich, 1511–1640s|German, Augsburg 1497/98–1543 London",,"Royal Workshops at Greenwich|Holbein, Hans, the Younger",German,1511 |1497,1650 |1543,dated 1527,1527,1527,"Steel, gold, leather, copper alloys",H. 73 in. (185.4 cm); Wt. 62 lb. 12 oz. (28.45 kg),"Armor: Purchase, William H. Riggs Gift and Rogers Fund, 1919; mail brayette: Gift of Prince Albrecht Radziwill, 1927",,Greenwich,,,,,,,,,,Armor for Horse and Man,,http://www.metmuseum.org/art/collection/search/35775,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"1985.248a, b",false,true,24924,Arms and Armor,Crossbow (Halbe rüstung) with winder (cranequin),Crossbow (Halbe Rüstung) with Winder (Cranequin),"German, Dresden",,,,,Maker,Crossbow by,Johann Gottfried Hänisch the Elder,"German, Dresden 1696–1778",,"Hänisch the Elder, Johann Gottfried",German,1696,1778,dated 1742,1742,1742,"Steel, wood, staghorn, copper alloy, hemp, wool, gold, iron alloy","L. of crossbow, 28 13/16 in. (73.2 cm); W. of crossbow, 24 15/16 in. (63.3 cm); Wt. of crossbow, 10 lb. 7 oz. (4,817 g); L. of cranequin (without crank), 14 1/4 in. (36.2 cm); L. of crank, 10 3/4 in. (27.3 cm)","Purchase, Louis V. Bell Fund and Bequest of Stephen V. Grancsay, by exchange, 1985",,Dresden,Saxony,,,,,,,,,Archery Equipment-Crossbows,,http://www.metmuseum.org/art/collection/search/24924,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"29.158.650a, b",false,true,23337,Arms and Armor,Crossbow (Halbe rüstung) with winder (Cranequin),Crossbow (Halbe Rüstung) with Winder (Cranequin),"crossbow, German, probably Dresden; winder, possibly German",,,,,Maker,Crossbow attributed to,Johann Gottfried Hänisch the Elder,"German, Dresden 1696–1778",,"Hänisch the Elder, Johann Gottfried",German,1696,1778,"crossbow, ca. 1720–30; winder, ca. 1575–1600",1550,1755,"Steel, wood (walnut), staghorn, copper alloy, hemp, leather, silk, gold, iron alloy, wool",crossbow: L. 26 15/16 in. (68.4 cm); W. 29 7/8 in. (75.8 cm); Wt. 13 lb. 15 oz. (6329 g); winder: L. 14 in. (35.5 cm); W. 4 1/8 in. (10.4 cm); Wt. 5 lb. 3 oz. (2341 g),"Bashford Dean Memorial Collection, Funds from various donors, 1929",,probably Dresden,Saxony,,,,,,,,,Archery Equipment-Crossbows,,http://www.metmuseum.org/art/collection/search/23337,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2010.315,false,true,35684,Arms and Armor,Light crossbow (Schnepper),Light Crossbow (Schnepper) from the Armory of Sedlitz Palace,"German, Dresden",,,,,Maker,,Johann Gottfried Hänisch the Elder,"German, Dresden, 1696–1778",,"Hänisch the Elder, Johann Gottfried",German,1696,1778,dated 1733,1733,1733,"Steel, wood (walnut), staghorn, hemp, wool, gold","L. 27 11/16 in. (70.3 cm); W. 22 1/2 in. (57.1 cm); Wt. 4 lb. 5 oz. (1,949 g)","Purchase, Arthur Ochs Sulzberger Gift, 2010",,Dresden,,,,,,,,,,Archery Equipment-Crossbows,,http://www.metmuseum.org/art/collection/search/35684,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2011.429,false,true,35706,Arms and Armor,"Small crossbow (Schnepper), probably for a woman or child","Small Crossbow (Schnepper), Probably for a Woman or Child","German, Dresden",,,,,Maker,,Johann Gottfried Hänisch the Elder,"German, Dresden, 1696–1778",,"Hänisch the Elder, Johann Gottfried",German,1696,1778,dated 1738,1738,1738,"Steel, wood (fruitwood, probably plum or cherry), staghorn, hemp, wool, gold, horn",L. 22 7/32 in. (56.4 cm); W. 16 15/16 in. (43 cm); Wt. 1 lb. 14 1/2 oz. (866 g),"Purchase, Arthur Ochs Sulzberger Gift, 2011",,Dresden,Saxony,,,,,,,,,Archery Equipment-Crossbows,,http://www.metmuseum.org/art/collection/search/35706,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -69.199,false,true,24858,Arms and Armor,Box for crossbow bolts (Bolzenkasten),"Box For Crossbow Bolts (Bolzenkasten), Probably Made for William IV, Duke of Bavaria (r. 1508–50)","German, Munich",,,,,Maker,,Hans Wagner the Elder,"German, Munich, recorded 1539–56",,"Wagner the Elder, Hans",German,1539,1556,dated 1539,1539,1539,"Wood (lid and front panel: fruitwood, possibly pear; bottom: walnut; inlay: possibly sycamore; later repairs: mahogany moldings and Indian rosewood veneers on sides), staghorn, iron, gold, paste, paper",H. 3 5/16 in. (8.4 cm); W. 16 in. (40.6 cm); D. 7 31/32 in. (20.2 cm),"Purchase, Bashford Dean Bequest, 1969",,Munich,,,,,,,,,,Archery Equipment,,http://www.metmuseum.org/art/collection/search/24858,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -42.50.8,false,true,24689,Arms and Armor,Wheellock rifle,Wheellock Rifle,"German, Schwäbisch Gmünd",,,,,Maker,,Johann Michael Maucher,"German, Schwäbisch Gmünd, 1645–1701",,"Maucher, Johann Michael",German,1670,1701,ca. 1680–90,1655,1715,"Steel, wood (cherry), ivory, mother-of-pearl",L. 41 5/8 in. (105.7 cm); L. of barrel 30 13/16 in. (78.3 cm); L. of lock 7 1/8 in. (18.1 cm); Cal. .56 in. (14.2 mm); Wt. 7 lb. 11 oz. (3500 g),"Gift of Stephen V. Grancsay, 1942",,Schwäbisch Gmünd,,,,,,,,,,Firearms,,http://www.metmuseum.org/art/collection/search/24689,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.156.47,false,true,23214,Arms and Armor,Close helmet for the tourney,Close Helmet for the Tourney,"Austrian, Innsbruck",,,,,Designer,Ornament copied from a design by,Daniel Hopfer,"German, Kaufbeuren 1471–1536 Augsburg",,"Hopfer, Daniel",German,1471,1536,dated 1552,1552,1552,"Steel, leather, brass",H. 10 1/2 in. (26.7 cm); W. 8 5/16 in. (21.1 cm); D. 12 1/4 in. (31.1 cm); Wt. 6 lb. 3 oz. (2803 g),"Bashford Dean Memorial Collection, Gift of Edward S. Harkness, 1929",,Innsbruck,,,,,,,,,,Helmets,,http://www.metmuseum.org/art/collection/search/23214,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.25.370,false,true,26781,Arms and Armor,Glaive,"Glaive of Maximilian III Joseph, Prince-Elector of Bavaria (reigned 1745–77)",German,,,,,Decorator,Inscribed:,Jungwierth,"German, ca. 1770",,Jungwierth,German,1745,1795,dated 1771,1771,1771,"Steel, wood, gold, copper alloy, textile",L. 8 ft. 5 3/4 in. (258.4 cm); L. of head 27 1/2 in. (69.9 cm); W. 3 3/4 in. (9.5 cm); Wt. 7 lb. 0.9 oz. (3200.7 g),"Gift of William H. Riggs, 1913",,,,,,,,,,,,Shafted Weapons,,http://www.metmuseum.org/art/collection/search/26781,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -49.8,false,true,24797,Arms and Armor,Hunting sword,Hunting Sword,German,,,,,Sword maker,,Johann Georg Klett,"German, 1720–1793",,Klett Johann Georg,German,1720,1793,ca. 1750,1725,1775,"Steel, silver, gold, bloodstone",L. 29 in. (73.7 cm),"Rogers Fund, 1949",,,,,,,,,,,,Swords,,http://www.metmuseum.org/art/collection/search/24797,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.25.702,false,true,22263,Arms and Armor,Armor,Armor,German,,,,,Armorer,Gorget plate of the helmet possibly marked by,Hans Michel,"German, Nuremberg 1539–1599",,"Michel, Hans",German,1539,1599,16th century and later,1500,1900,"Steel, brass, leather",Wt. 41 lb. 9 oz. (18.85 kg); Wt. of helmet 10 lb. 8 oz. (4.75 kg),"Gift of William H. Riggs, 1913",,,,,,,,,,,,Armor for Man,,http://www.metmuseum.org/art/collection/search/22263,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.150.4b–t,false,true,23083,Arms and Armor,Armor,Armor,German,,,,,Etcher,Etched decoration on poleyns (knee defeneses) attributed to,Heilig Jörg,"German, active ca. 1490–1505",,"Jörg, Heilig",German,1465,1530,ca. 1500 and later,1475,1900,"Steel, leather",H. 71 in. (180.3 cm); Wt. 39 lb. 2 oz. (17.75 kg),"Bashford Dean Memorial Collection, Bequest of Bashford Dean, 1928",,,,,,,,,,,,Armor for Man,,http://www.metmuseum.org/art/collection/search/23083,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2014.434,false,true,647195,Arms and Armor,Hand-colored engraving,Ludi equestres. Das Thurnieren (The Tournament),German,,,,,Artist,,Martin Engelbrecht,"German, Augsburg 1684–1756 Augsburg",,"Engelbrecht, Martin",German,1684,1756,ca. 1730,1705,1755,"Paper, ink, polychromy",9 1/2 x 15 3/16 in. (241 x 385 mm),"Purchase, Kenneth and Vivian Lam Gift, 2014",,,,,,,,,,,,Works on Paper-Engravings,,http://www.metmuseum.org/art/collection/search/647195,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2012.204a, b",false,true,35859,Arms and Armor,Sword with scabbard,"Sword with Scabbard of Faustin I (1782–1867), Emperor of Haiti","British, Birmingham",,,,,Sword cutler,,Robert Mole,"British, Birmingham,1800–1856",,"Mole, Robert",British,1800,1856,1850,1825,1875,"Steel, silver, gold, wood, textile (velvet)",L. 39 1/2 in. (100.3 cm),"Bequest of William S. Delafield Sr., 2012",,Birmingham,,,,,,,,,,Swords,,http://www.metmuseum.org/art/collection/search/35859,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2012.15,false,true,35782,Arms and Armor,Helmet for a harquebusier,Helmet for a Harquebusier,"British, London or Greenwich",,,,,Armorer,Probably made in the,Royal Workshops at Greenwich,"British, Greenwich, 1511–1640s",,Royal Workshops at Greenwich,British,1511,1650,ca. 1630–40,1605,1665,"Steel, silver, gold, copper alloy, textile",H. 13 7/8 (35.3 cm); W. 9 3/8 (23.8 cm); D. 16 7/16 in. (41.7 cm); Wt. 4 lb. 7 oz. (2010 g),"Purchase, Arthur Ochs Sulzberger Gift, 2012",,probably Greenwich,,,,,,,,,,Helmets,,http://www.metmuseum.org/art/collection/search/35782,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -21.180a–c,false,true,22768,Arms and Armor,Smallsword hilt with storage case,Smallsword Hilt with Storage Case,"British, Birmingham or London",,,,,Decorator,,"Rundell, Bridge and Rundell","British, London, active 1797–1843",,"Rundell, Bridge and Rundell",British,1797,1843,ca. 1785–1800,1760,1825,"Steel, wood, leather, silk",Hilt (a); L. including tang 8 1/4 in. (21 cm); L. excluding tang 6 15/16 in. (17.7 cm); W. 4 1/2 in. (11.4 cm); D. 3 1/2 in. (8.9 cm); Wt. 10 oz. (284.1 g); case (b); 5.2 oz. (147.4 g); trade card (c); 2 1/2 x 3 9/16 in. (6.4 x 9 cm),"Gift of Richard Hoe Lawrence, 1921",,London or Birmingham,,,,,,,,,,Swords,,http://www.metmuseum.org/art/collection/search/22768,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -26.145.311,false,true,22930,Arms and Armor,Smallsword,Smallsword,"British, probably London",,,,,Sword cutler,Attributed to,John Bland,"British, London, active ca. 1780–85",,"Bland, John",British,1755,1810,ca. 1780–85,1755,1810,"Gold, steel",L. 39 in. (99.1 cm); L. of blade 32 3/16 in. (81.7 cm); Wt. 1 lb. (454 g),"Gift of Jean Jacques Reubell, in memory of his mother, Julia C. Coster, and of his wife, Adeline E. Post, both of New York City, 1926",,London,,,,,,,,,,Swords,,http://www.metmuseum.org/art/collection/search/22930,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -15.113.1–.5; 29.158.885,true,true,27792,Arms and Armor,Harquebusier's armor with buff coat,"Harquebusier's Armor of Pedro II, King of Portugal (reigned 1683–1706) with Buff Coat","British, London; buff coat, European",,,,,Armorer|Armorer,Armor attributed to|Helmet cheek pieces and metal plates on shoulder straps made by,Richard Holden|Daniel Tachaux,"British, London, recorded 1658–1708|French, 1857–1928, active in France and America",,"Holden, Richard|Tachaux, Daniel",British,1658 |1857,1708 |1928,"ca. 1683 and later; buff coat, 17th–18th century",1601,1916,"Steel, gold, leather, textile",armor (15.113.1–.5): Wt. 43 lb. 5 oz. (19.6 kg); helmet: 14 x 11 x 15 1/4 in. (35.6 x 27.9 x 38.7 cm); Wt. 9 lb. 10 oz. (4354 g); breastplate: 18 1/2 x 16 5/16 x 7 13/16 in. (47 x 41.4 x 19.8 cm); Wt. 10 lb. 14 oz. (4944 g); backplate: 17 1/2 x 16 1/4 x 8 1/2 in. (44.5 x 41.3 x 21.6 cm); Wt. 11 lb. 5 oz. (5126 g); reinforcing breastplate: 17 3/8 x 16 3/16 x 6 13/16 in. (44.1 x 41.1 x 17.3 cm); Wt. 9 lb. 6 oz. (4264 g); bridle gauntlet: 5 1/2 x 19 7/16 x 6 3/4 in. (14 x 49.4 x 17.1 cm); Wt. 2 lb. 2 oz. (953 g); buff coat (29.158.885): L. 35 in. (88.9 cm),"Armor: Rogers Fund, 1915; buff coat: Bashford Dean Memorial Collection, Funds from various donors, 1929",,London,,,,,,,,,,Armor for Man,,http://www.metmuseum.org/art/collection/search/27792,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.98.1,false,true,24612,Arms and Armor,Terminal lame of a grand guard,"Terminal Lame of a Grand Guard from the Armor Garniture of George Clifford (1558–1605), Third Earl of Cumberland","British, Greenwich",,,,,Armorer,Made under the direction of,Jacob Halder,"British, master armorer at the royal workshops at Greenwich, documented in England 1558–1608",,"Halder, Jacob",British,1558,1608,1586,1586,1586,"Steel, gold",H. 10 1/4 in. (26.0 cm); W. 8 3/8 in. (21.3 cm),"Rogers Fund, 1936",,Greenwich,,,,,,,,,,Armor Parts,,http://www.metmuseum.org/art/collection/search/24612,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -32.130.6a–y,true,true,23939,Arms and Armor,Armor garniture,"Armor Garniture of George Clifford (1558–1605), Third Earl of Cumberland","British, Greenwich",,,,,Armorer,Made under the direction of,Jacob Halder,"British, master armorer at the royal workshops at Greenwich, documented in England 1558–1608",,"Halder, Jacob",British,1558,1608,1586,1586,1586,"Steel, gold, leather, textile",H. 69 1/2 in. (176.5 cm); Wt. 60 lb. (27.2 kg),"Munsey Fund, 1932",,Greenwich,,,,,,,,,,Armor for Man,,http://www.metmuseum.org/art/collection/search/23939,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -11.128.1a–p,false,true,22138,Arms and Armor,Field armor,Field Armor Probably of Sir John Scudamore (1541 or 1542–1623),"British, Greenwich",,,,,Armorer|Armorer,"Made under the direction of|Helmet, left pauldron, gauntlets, and right sabaton made by",Jacob Halder|Daniel Tachaux,"British, master armorer at the royal workshops at Greenwich, documented in England 1558–1608|French, 1857–1928, active in France and America","in The Metropolitan Museum of Art, Armor Workshop","Halder, Jacob|Tachaux, Daniel",British,1558 |1857,1608 |1928,"ca. 1587, restored and completed 1915",1562,1915,"Steel, gold, leather",Wt. 68 lb. 8 oz. (31.07 kg),"Frederick C. Hewitt Fund, 1911",,Greenwich|New York,New York,,,,,,,,,Armor for Man,,http://www.metmuseum.org/art/collection/search/22138,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -11.128.2a–n,false,true,22139,Arms and Armor,Armor,Armor of Sir James Scudamore (1558–1619),"British, Greenwich",,,,,Armorer|Armorer,"Breastplate, backplate, and gauntlets made by|Made under the direction of",Daniel Tachaux|Jacob Halder,"French, 1857–1928, active in France and America|British, master armorer at the royal workshops at Greenwich, documented in England 1558–1608","in the Metropolitan Museum of Art, Armor Workshop","Tachaux, Daniel|Halder, Jacob",British,1857 |1558,1928 |1608,ca. 1595–96,1570,1621,"Steel, gold, leather",H. 70 1/4 in. (178.5 cm); Wt. 50 lb. 7 oz. (22.88 kg),"Frederick C. Hewitt Fund, 1911",,Greenwich,,,,,,,,,,Armor for Man,,http://www.metmuseum.org/art/collection/search/22139,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"11.128.1a–p; 22.147.4a, b, .11",false,true,35896,Arms and Armor,Field armor,Field Armor Probably of Sir John Scudamore (1541 or 1542–1623),"British, Greenwich",,,,,Armorer|Armorer,"Helmet, left pauldron, gauntlets, and right sabaton made by|Made under the direction of",Daniel Tachaux|Jacob Halder,"French, 1857–1928, active in France and America|British, master armorer at the royal workshops at Greenwich, documented in England 1558–1608","in The Metropolitan Museum of Art, Armor Workshop","Tachaux, Daniel|Halder, Jacob",British,1857 |1558,1928 |1608,"ca. 1587, restored and completed 1915",1562,1915,"Steel, gold, leather",Wt. 68 lb. 8 oz. (31.07 kg),"armor: Frederick C. Hewitt Fund, 1911; right thigh and knee defense, and left foot defense: Fletcher Fund, 1922",,Greenwich|New York,New York,,,,,,,,,Armor for Man,,http://www.metmuseum.org/art/collection/search/35896,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2011.90,false,true,35705,Arms and Armor,Mezzotint,"George Wallis, The Late Celebrated Antiquary and Gunsmith of Hull",British,,,,,Engraver,Engraved by,John Raphael Smith,"British, baptized Derby 1751–1812 Doncaster",after a painting by John Harrison,"Smith, John Raphael",British,1751,1812,"June 20, 1804",1804,1804,Mezzotint on white wove paper,Sheet: 17 3/4 x 14 7/8 in. (45 x 37.7 cm); plate: 15 x 11 in. (38 x 28 cm),"Purchase, Arthur Ochs Sulzberger Gift, 2011",,,,,,,,,,,,Works on Paper-Engravings,,http://www.metmuseum.org/art/collection/search/35705,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2006.427,false,true,35148,Arms and Armor,Sketchbook,Memorandum Book Showing Colored Sketches Mostly of European Helmets,British,,,,,Artist,,William Burges,1827–1881,,"Burges, William",British,1827,1881,ca. 1880,1880,1880,"Paper, leather, silver",H. 3 3/4 in. (9.5 cm); W. 2 3/8 in. (6 cm),"Purchase, Gift of Bashford Dean, by exchange, 2006",,,,,,,,,,,,Books & Manuscripts,,http://www.metmuseum.org/art/collection/search/35148,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.465,false,true,28564,Arms and Armor,Medal,Medal Awarded to the Sword Makers Robert Mole and Sons,British,,,,,Artist,,William Wyon,"British, Birmingham 1795–1851 Brighton",,"Wyon, William",British,1795,1851,1851,1851,1851,Bronze,Diam. 1 3/4 in. (4.4 cm); thickness 5/16 in. (0.8 cm); Wt. 2.2 oz. (62.4 g),"Gift of Herbert G. Houze, 2005",,,,,,,,,,,,Miscellaneous-Coins and Medals,,http://www.metmuseum.org/art/collection/search/28564,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -15.113.1–.5,false,true,22605,Arms and Armor,Harquebusier's armor,"Harquebusier's Armor of Pedro II, King of Portugal (reigned 1683–1706)","British, London",,,,,Armorer|Armorer,Attributed to|Helmet cheek pieces and metal plates on shoulder straps made by,Richard Holden|Daniel Tachaux,"British, London, recorded 1658–1708|French, 1857–1928, active in France and America",,"Holden, Richard|Tachaux, Daniel",British,1658 |1857,1708 |1928,ca. 1683 and later,1655,1916,"Steel, gold, leather, textile",Wt. 43 lb. 5 oz. (19.6 kg); helmet: 14 x 11 x 15 1/4 in. (35.6 x 27.9 x 38.7 cm); Wt. 9 lb. 10 oz. (4354 g); breastplate: 18 1/2 x 16 5/16 x 7 13/16 in. (47 x 41.4 x 19.8 cm); Wt. 10 lb. 14 oz. (4944 g); backplate: 17 1/2 x 16 1/4 x 8 1/2 in. (44.5 x 41.3 x 21.6 cm); Wt. 11 lb. 5 oz. (5126 g); reinforcing breastplate: 17 3/8 x 16 3/16 x 6 13/16 in. (44.1 x 41.1 x 17.3 cm); Wt. 9 lb. 6 oz. (4264 g); bridle gauntlet: 5 1/2 x 19 7/16 x 6 3/4 in. (14 x 49.4 x 17.1 cm); Wt. 2 lb. 2 oz. (953 g),"Rogers Fund, 1915",,London,,,,,,,,,,Armor for Man,,http://www.metmuseum.org/art/collection/search/22605,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2006.471.1, .2",false,true,34183,Arms and Armor,Pair of pistols,Pair of Snaphaunce Pistols,"Italian, Bargi",,,,,Gunsmith,,"Matteo Cecchi, called Acquafresca","Italian, Bargi, 1651–1738",,"Cecchi, Matteo",Italian,1651,1738,ca. 1690,1690,1690,"Steel, silver, wood (ebony)",L. of each pistol 21 1/2 in. (54.7 cm); L. of each barrel 15 in. (38.2 cm); Cal. of each barrel 1/2 in. (13 mm),"Purchase, Arthur Ochs Sulzberger Gift, 2006",,Bargi,,,,,,,,,,Firearms-Pistols-Snaphaunce,,http://www.metmuseum.org/art/collection/search/34183,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -04.3.202,false,true,21974,Arms and Armor,Helmet all'antica,Helmet all'Antica,"Italian, Milan",,,,,Armorer,Attributed to,Filippo Negroli,"Italian, Milan ca. 1510–1579",,"Negroli, Filippo",Italian,1485,1605,ca. 1532–35,1507,1560,Steel,H. 11 1/4 in. (85 cm); W. 8 1/4 in. (20.9 cm); D. 9 in. (22.7 cm); Wt. 2 lb. 2 oz. (964 g),"Rogers Fund, 1904",,Milan,,,,,,,,,,Helmets,,http://www.metmuseum.org/art/collection/search/21974,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -17.190.1720,true,true,22634,Arms and Armor,Burgonet,Burgonet,"Italian, Milan",,,,,Armorer,,Filippo Negroli,"Italian, Milan ca. 1510–1579",,"Negroli, Filippo",Italian,1485,1605,dated 1543,1543,1543,"Steel, gold, textile",H. 9 1/2 in. (24.1 cm); W. 7 5/16 in. (18.6 cm); D. 11 1/2 in. (29.2 cm); Wt. 4 lb. 2 oz. (1871 g),"Gift of J. Pierpont Morgan, 1917",,Milan,,,,,,,,,,Helmets,,http://www.metmuseum.org/art/collection/search/22634,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -26.53,false,true,22903,Arms and Armor,Close helmet,Close Helmet,"Italian, Milan",,,,,Armorer,Attributed to,Giovan Paolo Negroli,"Italian, Milan ca. 1513–1569",,"Negroli, Giovan Paolo",Italian,1488,1594,ca. 1540–45,1515,1570,"Steel, copper alloy, gold",H. 10 3/4 in. (27.3 cm); W. 11 1/2 in. (29.2 cm); D. 15 in. (38.1 cm); Wt. 6 lb. 8 oz. (3068 g),"Purchase, Rogers Fund and George D. Pratt Gift, 1926",,Milan,,,,,,,,,,Helmets,,http://www.metmuseum.org/art/collection/search/22903,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.25.1855,false,true,22408,Arms and Armor,Breastplate,Breastplate,"Italian, Milan",,,,,Armorer,,Giovan Paolo Negroli,"Italian, Milan ca. 1513–1569",,"Negroli, Giovan Paolo",Italian,1488,1594,ca. 1540–45,1515,1570,"Steel, gold",W. 16 3/8 in. (41.6 cm); H. 23 1/4 in. (59.1 cm),"Gift of William H. Riggs, 1913",,Milan,,,,,,,,,,Armor Parts-Breastplates,,http://www.metmuseum.org/art/collection/search/22408,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.25.603,false,true,22230,Arms and Armor,Close helmet with falling buffe,Close Helmet with Falling Buffe,"Italian, Milan",,,,,Armorer,Attributed to,Pompeo della Cesa,"Italian, Milan, ca. 1537–1610",,"Cesa, Pompeo della",Italian,1512,1635,ca. 1590–95,1565,1620,"Steel, gold",H. 11 in. (27.9 cm); W. 8 7/8 in. (22.5 cm); D. 12 1/2 in. (31.8 cm); Wt. 6 lb. 2 oz. (2778 g),"Gift of William H. Riggs, 1913",,Milan,,,,,,,,,,Helmets,,http://www.metmuseum.org/art/collection/search/22230,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.25.656,false,true,22239,Arms and Armor,Close helmet,Close Helmet,"Italian, Milan",,,,,Armorer,Attributed to,Pompeo della Cesa,"Italian, Milan, ca. 1537–1610",,"Cesa, Pompeo della",Italian,1512,1635,ca. 1585,1560,1610,"Steel, gold, brass",H. 11 5/8 in. (29.5 cm); W. 9 1/2 in. (24.1 cm); D. 11 in. (27.9 cm); Wt. 5 lb. 14 oz. (2665 g),"Gift of William H. Riggs, 1913",,Milan,,,,,,,,,,Helmets,,http://www.metmuseum.org/art/collection/search/22239,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2001.72a–d,false,true,25114,Arms and Armor,Portions of an armor,Portions of an Armor for Vincenzo Luigi di Capua (d. 1627),"Italian, Milan",,,,,Armorer,,Pompeo della Cesa,"Italian, Milan, ca. 1537–1610",,"Cesa, Pompeo della",Italian,1512,1635,ca. 1595,1570,1620,"Steel, gold, leather, copper alloy",H. as mounted 19 in. (48 cm),"Purchase, Arthur Ochs Sulzberger Gift, 2001",,Milan,,,,,,,,,,Armor for Man,,http://www.metmuseum.org/art/collection/search/25114,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2001.72; 2013.618,false,true,642980,Arms and Armor,Portions of an armor,Portions of an Armor for Vincenzo Luigi di Capua (d. 1627),"Italian, Milan",,,,,Armorer,,Pompeo della Cesa,"Italian, Milan, ca. 1537–1610",,"Cesa, Pompeo della",Italian,1512,1635,ca. 1595,1570,1620,"Steel, gold, leather, copper alloy",H. as mounted 19 in. (48 cm),"Gorget, pauldrons, and breastplate: Purchase, Arthur Ochs Sulzberger Gift, 2001; Backplate: Purchase, Gift in honor of Maximilian and Alexander Saga, 2013",,Milan,,,,,,,,,,Armor for Man,,http://www.metmuseum.org/art/collection/search/642980,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.618,false,true,35960,Arms and Armor,Backplate,Backplate of an Armor for Vincenzo Luigi di Capua (d. 1627),"Italian, Milan",,,,,Armorer,,Pompeo della Cesa,"Italian, Milan, ca. 1537–1610",,"Cesa, Pompeo della",Italian,1512,1635,ca. 1595,1570,1620,"Steel, gold, leather, copper alloy",H. approx. 15 in. (38 cm); W. approx. 11 in. (28 cm),"Purchase, Gift in honor of Maximilian and Alexander Saga, 2013",,Milan,,,,,,,,,,Armor Parts-Backplates,,http://www.metmuseum.org/art/collection/search/35960,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"28.196.15, .16",false,true,23049,Arms and Armor,Pair of pistols,Pair of Pistols with Flintlocks alla Fiorentina,"Italian, Pistoia",,,,,Gunsmith,Attributed to,Cristiano Leoni,"Italian, Pistoia, active ca. 1780",,"Leoni, Cristiano",Italian,1755,1805,ca. 1750–75,1725,1800,"Steel, wood",L. of each 18 3/8 in. (46.7 cm); L. of each barrel 13 3/8 in. (33.9 cm); Cal. of each .46 in. (11.7 mm); Wt. of each 1 lb. 8 oz. (680 g),"Rogers Fund, 1928",,Pistoia,,,,,,,,,,Firearms-Pistols,,http://www.metmuseum.org/art/collection/search/23049,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -04.3.162,false,true,21964,Arms and Armor,Wheellock gun,Wheellock gun,"Italian, Brescia",,,,,Barrelsmith,,Cominazzo workshop,"Italian, Brescia, mid-17th century",,Cominazzo workshop,Italian,1625,1675,mid-17th century,1625,1675,"Steel, wood (walnut)",L. 45 1/2 in. (115.6 cm); L. of barrel 32 3/4 in. (83.2 cm); Cal. .58 in. (14.7 mm); Wt. 9 lb. 6 oz. (4250 g),"Rogers Fund, 1904",,Brescia,,,,,,,,,,Firearms-Guns-Wheellock,,http://www.metmuseum.org/art/collection/search/21964,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"04.3.23a, b",false,true,21935,Arms and Armor,Rapier with scabbard,Rapier with Scabbard,French or Italian,,,,,Goldsmith,Decoration attributed to,Gasparo Mola,"Italian, Coldre ca. 1580–1640 Rome",,"Mola, Gasparo",Italian,1575,1640,ca. 1620–30,1595,1655,"Steel, gold, silver, leather, wood",L. 49 1/4 in. (125.1 cm); W. 8 3/4 in. (22.2 cm); Wt. of scabbard 7.5 oz. (212.6 g),"Rogers Fund, 1904",,,,,,,,,,,,Swords,,http://www.metmuseum.org/art/collection/search/21935,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -19.128.1–.2,false,true,22740,Arms and Armor,Pair of gauntlets for a child,Pair of Gauntlets for a Child,"Italian, Milan",,,,,Armorer,,Lucio Piccinino,"Italian, Milan, active ca. 1575–90",,"Piccinino, Lucio",Italian,1550,1615,ca. 1585,1560,1610,"Steel, gold, silver",L. of each 7 1/8 in. (18.1 cm); W. of each 4 1/2 in. (12.4 cm); D. of each 4 3/8 in. (11.1 cm); Wt. of each 8 oz. (226.8 g),"Rogers Fund, 1919",,Milan,,,,,,,,,,Armor Parts-Gauntlets,,http://www.metmuseum.org/art/collection/search/22740,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -04.3.270a–o,false,true,21996,Arms and Armor,Half armor,"Half Armor Probably Made for Don Gonzalo Fernández de Córdoba y Fernández de Córdoba, Duke of Sessa (1520/1524–1578)","Italian, Milan",,,,,Armorer,Attributed to,Lucio Piccinino,"Italian, Milan, active ca. 1575–90",,"Piccinino, Lucio",Italian,1550,1615,ca. 1560 and later,1535,1900,"Steel, gold",Wt. approx. 30 lb. 7 oz. (13.8 kg); Wt. of helmet approx. 7 lb. 11 oz. (,"Rogers Fund, 1904",,Milan,,,,,,,,,,Armor for Man-1/2 Armor,,http://www.metmuseum.org/art/collection/search/21996,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"14.25.714a–h, j",false,true,22184,Arms and Armor,Armor,Armor of the Dukes of Alba,"Italian, Milan",,,,,Armorer,,Lucio Piccinino,"Italian, Milan, active ca. 1575–90",,"Piccinino, Lucio",Italian,1550,1615,ca. 1575–85,1550,1610,"Steel, gold, silver","Wt. 25 lbs. 12 oz. (11.68 kg); Helmet (a) H. 12 in. (30.5 cm); W. 8 1/2 in. (21.6 cm); D. 11 1/2 in. (29.2 cm); Wt. 5 lbs. 1 oz. (2,296.31 g); Colletin (b) H. 7 in. (17.8 cm); W. 11 7/16 in. (29.1 cm); Wt. 2 lbs. 2 oz. (963.88 g); Breastplate (c) H. 18 1/8 in. (46.0 cm); W. 15 in. (38.1 cm); D. 7 7/8 in. (19.9 cm); Wt. 5 lbs. 4 oz (2,381.36 g); Backplate (d) H. 14 5/8 in. (37.2 cm); W. 14 5/8 in. (37.2 cm); Wt. 3 lbs. 3 oz. (1,445.83 g); Pauldron right (e) H. 10 1/2 in. (26.7 cm); W. 11 5/8 in. (29.5 cm); Wt. 2 lbs. 9 oz. (1,162.33 g); Pauldron left (f) H. 10 3/8 in. (26.4 cm); W. 11 in. (27.9 cm); Wt. 2 lbs. 12 oz. (1,247.38 g); Arm Defense right (g) L. 18 1/2 in. (46.9 cm); Wt. 2 lbs. 7 oz. (1,105.63 g); Arm Defense left (h) L. 18 1/4 in. (46.4 cm); Wt. 2 lbs. 6 oz. (1,077.28 g)","Gift of William H. Riggs, 1913",,Milan,,,,,,,,,,Armor for Man-1/2 Armor,,http://www.metmuseum.org/art/collection/search/22184,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -11.89.2,false,true,22135,Arms and Armor,Cup-hilted rapier,Cup-Hilted Rapier,"Italian, Milan",,,,,Sword maker,Cup signed,Carlo Piccinino,"Italian, Milan, active ca. 1650–75",,"Piccinino, Carlo",Italian,1625,1700,ca. 1650–75,1625,1700,"Steel, iron wire",L. 44 in. (111.8 cm); L. of blade 37 1/4 in. (94.6 cm); W. 12 1/16 in. (30.6 cm); Wt. 2 lb. 5 oz. (1048.9 g),"Gift of J. Pierpont Morgan, 1911",,Milan,,,,,,,,,,Swords,,http://www.metmuseum.org/art/collection/search/22135,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.150.9aa,false,true,35853,Arms and Armor,Shaffron (Horse's head defense),Shaffron (Horse's Head Defense),"Italian, possibly Brescia",,,,,Armorer,Stamped with marks attributed to,Ambrogio de Osma,"Italian, Brescia, documented 1446–75",,"Osma, Ambrogio de",Italian,1446,1475,ca. 1460–70,1435,1495,Steel,H. 21 1/2 in. (54.6 cm); W. 9 1/4 in. (23.5 cm); Wt. 1 lb. 15 oz. (878.8 g),"Bashford Dean Memorial Collection, Bequest of Bashford Dean, 1928",,possibly Brescia,,,,,,,,,,Equestrian Equipment-Shaffrons,,http://www.metmuseum.org/art/collection/search/35853,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"28.196.17, .18",false,true,23048,Arms and Armor,Pair of flintlock pistols,Pair of Flintlock Pistols,"Italian, Brescia",,,,,Gunsmith,,Girolamo Francino,"Italian, Brescia, recorded 1666–1709",,"Francino, Girolamo",Italian,1666,1709,ca. 1650–60,1625,1685,"Steel, wood (walnut)",L. of each 22 1/2 in. (57.2 cm); L. of each barrel 15 13/16 in. (40.2 cm); Cal. of each .52 in. (13.2 mm); Wt. of each 1 lb. 15 oz. (879 g),"Rogers Fund, 1928",,Brescia,,,,,,,,,,Firearms-Pistols-Flintlock,,http://www.metmuseum.org/art/collection/search/23048,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.158.1e–f,false,true,26584,Arms and Armor,Arm defenses,Arm Defenses,"Italian, Milan",,,,,Armorer,Both arms marked by,Domenico Negroli,"Italian, Milan, active 1492–ca. 1516",,"Negroli, Domenico",Italian,1492,1531,ca. 1510,1485,1535,Steel,Wt. of right arm (e) 2 lb. 10 oz. (1190.7 g); Wt. of left arm (f) 2 lb. 6 oz. (1077.3 g),"Bashford Dean Memorial Collection, Funds from various donors, 1929",,Milan,,,,,,,,,,Armor Parts-Arms & Shoulders,,http://www.metmuseum.org/art/collection/search/26584,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.25.598,false,true,22227,Arms and Armor,Close-helmet,Close-Helmet for the Tournament on Foot,"Italian, Milan",,,,,Armorer,,the Master of the Castle Mark,"Italian, Milan, active ca. 1590–1620",,"Master of the Castle Mark, the",Italian,1565,1645,ca. 1600–1610,1575,1635,"Steel, gold",H. 13 in. (33 cm); W. 9 1/2 in. (24.1 cm); D. 11 9/16 in. (29.4 cm); Wt. 11 lb. 13 oz. (5360 g),"Gift of William H. Riggs, 1913",,Milan,,,,,,,,,,Helmets,,http://www.metmuseum.org/art/collection/search/22227,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.25.605a,false,true,22232,Arms and Armor,Close helmet for foot combat,Close Helmet for Foot Combat,"Italian, Milan",,,,,Armorer,Attributed to,the Master of the Castle Mark,"Italian, Milan, active ca. 1590–1620",,"Master of the Castle Mark, the",Italian,1565,1645,ca. 1600–1610,1575,1635,"Steel, gold, leather",H. 12 1/2 in. (31.8 cm); W. 9 3/8 in. (23.8 cm); D. 12 1/4 in. (31.1 cm); Wt. 14 lb. 7 oz. (6450 g),"Gift of William H. Riggs, 1913",,Milan,,,,,,,,,,Helmets,,http://www.metmuseum.org/art/collection/search/22232,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2014.154,false,true,639897,Arms and Armor,Backplate,Backplate,"Italian, Milan",,,,,Armorer,Probably by,Francesco Negroli,"Italian, Milan, died before December 1519",,"Negroli, Francesco",Italian,1450,1519,ca. 1505–10,1480,1535,Steel,19 3/8 x 15 1/4 x 6 3/4 in. (49.3 x 39 x 17.3 cm); Wt. 5 lb. 6 oz. (2461 g),"Purchase, Arthur Ochs Sulzberger Gift, 2014",,Milan,,,,,,,,,,Armor Parts-Backplates,,http://www.metmuseum.org/art/collection/search/639897,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.25.1662,false,true,27460,Arms and Armor,Crinet,Crinet,Franco-Italian,,,,,Armorer,Attributed to,Romain des Ursins,"Italian, Milan, recorded in Lyon 1493–95",,"des Ursins, Romain",Italian,1493,1495,"ca. 1480–95; bottom lame and mail fringe, restored ca. 1863–1900",1455,1900,Steel,"L. 33 7/8 in. (85.9 cm); W. 6 7/8 in. (17.5 cm); Wt. 9 lb. 10 oz. (4,360 g)","Gift of William H. Riggs, 1913",,,,,,,,,,,,Armor for Horse,,http://www.metmuseum.org/art/collection/search/27460,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -04.3.253,false,true,25403,Arms and Armor,Shaffron (horse's head defense),"Shaffron (Horse's Head Defense) of Henry II of France, When Dauphin",Franco-Italian,,,,,Armorer,Attributed to,Romain des Ursins,"Italian, Milan, recorded in Lyon 1493–95",,"des Ursins, Romain",Italian,1493,1495,"ca. 1490–1500, redecorated 1539",1465,1539,"Steel, gold, brass",H. 27 1/2 in. (69.8 cm); W. 15 in. (38.1 cm); Wt. 5 lb. 3 oz. (2350 g),"Rogers Fund, 1904",,,,,,,,,,,,Equestrian Equipment-Shaffrons,,http://www.metmuseum.org/art/collection/search/25403,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.25.1661a,false,true,26921,Arms and Armor,Shaffron (Horse's head defense),Shaffron (Horse's Head Defense),Franco-Italian,,,,,Armorer,Attributed to,Romain des Ursins,"Italian, Milan, recorded in Lyon 1493–95",,"des Ursins, Romain",Italian,1493,1495,"ca. 1480–95; ear guards, eye guards, and plume holder, 19th century restorations",1455,1900,"Steel, textile",H. 24 7/8 (63.2 cm); W. 13 in. (33 cm); D. 9 1/2 in. (24.1 cm); Wt. 4 lb. 4 oz. (1918 g),"Gift of William H. Riggs, 1913",,,,,,,,,,,,Equestrian Equipment-Shaffrons,,http://www.metmuseum.org/art/collection/search/26921,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"14.25.1661a, .1662",false,true,35725,Arms and Armor,Shaffron and crinet (Horse's head and neck defenses),Shaffron and Crinet (Horse's Head and Neck Defenses),Franco-Italian,,,,,Armorer,Attributed to,Romain des Ursins,"Italian, Milan, recorded in Lyon 1493–95",,"des Ursins, Romain",Italian,1493,1495,"ca. 1480–95, with 19th century restorations",1455,1900,"Steel, textile",14.25.1661a (shaffron); H. 24 7/8 in. (63.2 cm); W 13 in. (33 cm); D. 9 1/2 in. (24.1 cm); Wt. 4 lb. 4 oz. (1918 g); 14.25.1662 (crinet); L. 33 7/8 in. (85.9 cm.); W. 6 7/8 in. (17.5 cm); Wt. 9 lb. 10 oz. (4360 g),"Gift of William H. Riggs, 1913",,,,,,,,,,,,Equestrian Equipment-Shaffrons,,http://www.metmuseum.org/art/collection/search/35725,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -42.50.2,true,true,24686,Arms and Armor,Armet,Armet,"Italian, probably Milan",,,,,Armorer,"Stamped with the armorer's name,",LIONARDO,"Italian, probably active in Milan, ca. 1440",,LIONARDO,Italian,1415,1465,ca. 1440,1415,1465,"Steel, copper alloy",H. 10 3/8 in. (26.3 cm); W. 8 1/4 in. (21 cm); D. 11 in. (27.9 cm); Wt. 9 lb. 4 oz. (4196 g),"Gift of Stephen V. Grancsay, 1942",,probably Milan,,,,,,,,,,Helmets,,http://www.metmuseum.org/art/collection/search/24686,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -56.23,false,true,24837,Arms and Armor,Trigger guard of a gun,Trigger Guard of a Gun made for a Prince of the House of the Medici,"Italian, Reggio Emilia",,,,,Steel-chiseler,,Pietro Ancini,"Italian, Reggio Emilia, recorded 1616–1702",,"Ancini, Pietro",Italian,1616,1702,dated 1643,1643,1643,Steel,L. 8 1/4 in. (21 cm),"Purchase, Bashford Dean Bequest, 1956",,Reggio Emilia,,,,,,,,,,Firearms Parts,,http://www.metmuseum.org/art/collection/search/24837,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -42.50.14,false,true,24692,Arms and Armor,Barbute,Barbute,"Italian, Milan",,,,,Armorer,,Bernardino da Carnago,"Italian, active in Milan and Naples, ca. 1475",,Carnago Bernardino da,Italian,1450,1500,ca. 1475,1450,1500,Steel,H. 12 1/8 in. (30.8 cm); W. 8 1/8 in. (20.6 cm); D. 10 3/4 in. (27.3 cm); Wt. 6 lb. 8 oz. (2948 g),"Gift of Stephen V. Grancsay, 1942",,,Milan,,,,,,,,,Helmets,,http://www.metmuseum.org/art/collection/search/24692,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.25.579,false,true,22223,Arms and Armor,Barbute,Barbute,"Italian, Brescia",,,,,Armorer,,Pietro da Castello,"Italian, Brescia, documented 1469–86, died before 1498",,"Castello, Pietro da",Italian,1469,1498,ca. 1470–80,1445,1505,Steel,H. 10 1/4 in. (26 cm); W. 7 1/2 in. (19.1 cm); D. 10 in. (25.4 cm); Wt. 4 lb. 14 oz. (2211 g),"Gift of William H. Riggs, 1913",,Brescia,,,,,,,,,,Helmets,,http://www.metmuseum.org/art/collection/search/22223,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -42.50.16,true,true,24693,Arms and Armor,Shield,Shield,"Italian, probably Bologna",,,,,Maker,Attributed to,Girolamo da Treviso,"Italian, Treviso ca. 1498–1544 Boulogne-sur-Mer",,"Treviso, Girolamo da",Italian,1498,1544,ca. 1535,1510,1560,"Wood, linen, gesso, gold leaf, polychromy",Diam. 24 5/8 in. (62.53 cm),"Gift of Stephen V. Grancsay, 1942",,probably Bologna,,,,,,,,,,Shell,,http://www.metmuseum.org/art/collection/search/24693,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1991.251,false,true,27136,Arms and Armor,Drawing,Design for the Hilt of a Small-Sword,"Italian, Rome",,,,,Artist,Workshop of,Luigi Valadier,"Italian, Rome 1726–1785 Rome",,"Valadier, Luigi",Italian,1726,1785,ca. 1780–90,1755,1815,"Pen, ink, wash, graphite, vellum",9 5/8 x 6 5/8 in. (24.4 x 16.8 cm),"Rogers Fund, 1991",,Rome,,,,,,,,,,Works on Paper-Drawings,,http://www.metmuseum.org/art/collection/search/27136,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.25.1870,false,true,436343,Arms and Armor,Painting,"Alfonso II d'Este (1533–1597), Duke of Ferrara","Italian, Ferrara",,,,,Artist,,Italian (Ferrarese) Painter,late 16th century,,Italian (Ferrarese) Painter,Italian,1400,1499,late 16th century,1570,1599,Oil on canvas,47 x 35 3/4 in. (119.4 x 90.8 cm),"Gift of William H. Riggs, 1913",,Ferrara,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436343,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.25.1874,false,true,436729,Arms and Armor,Painting,"Alfonso I d'Este (1476–1534), Duke of Ferrara","Italian, Ferrara",,,,,Artist,,Italian (Ferrarese) Painter,second quarter 16th century,,Italian (Ferrarese) Painter,Italian,1400,1499,second quarter 16th century,1525,1550,Oil on canvas,52 7/8 x 38 1/4 in. (134.3 x 97.2 cm),"Gift of William H. Riggs, 1913",,Ferrara,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436729,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1993.234,false,true,24956,Arms and Armor,Design for a saddle plate,Design for the Pommel Plate of a Saddle from a Garniture of Alessandro Farnese (1545–1592),"Italian, Parma",,,,,Designer,,Andrea Casalini,"Italian, Parma, died 1597",,"Casalini, Andrea",Italian,1500,1597,ca. 1575–80,1575,1580,"Pen and brown ink, with color washes, on paper",19 1/2 x 16 3/8 in. (49.5 x 39.2 cm),"Purchase, Fletcher Fund and Gift of William H. Riggs, by exchange, 1993",,Parma,,,,,,,,,,Works on Paper-Drawings,,http://www.metmuseum.org/art/collection/search/24956,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -11.89.5,false,true,22136,Arms and Armor,Sallet,Sallet,Italian,,,,,Armorer,Attributed to,Pietro Giacomo da Castello,"Italian, documented 1485–1525",,"Castello, Pietro Giacomo da",Italian,1485,1525,ca. 1510–20,1485,1545,Steel,"H. without crest, 11 1/2 in. (29.2 cm); W. 7 5/8 in. (19.4 cm); D. 10 1/2 in. (26.7 cm); Wt. 4 lb. 3 oz. (1899 g)","Gift of J. Pierpont Morgan, 1911",,,,,,,,,,,,Helmets,,http://www.metmuseum.org/art/collection/search/22136,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.156.77,false,true,25367,Arms and Armor,Medal,"Medal of Vicenzo II Gonzaga, 7th Duke of Mantua",Italian,,,,,Artist,,Gasparo Morone,"Italian, born Milan (?), died Rome, 1669",,"Morone, Gasparo",Italian,1569,1669,ca. 1625,1625,1625,"Bronze, gold",Diam. 1 11/16 in. (4.3 cm); thickness 1/8 in. (0.3 cm); Wt. 0.8 oz. (22.7 g),"Bashford Dean Memorial Collection, Gift of Edward S. Harkness, 1929",,,,,,,,,,,,Miscellaneous,,http://www.metmuseum.org/art/collection/search/25367,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"22.19a, b",true,true,22769,Arms and Armor,Presentation sword with scabbard,Presentation Sword and Scabbard of Brigadier General Daniel Davis (1777–1814) of the New York Militia,"American, New York",,,,,Silversmith,,John Targee,"American, ca. 1774–1850",,"Targee, John",American,1769,1850,ca. 1815–17,1790,1842,"Steel, gold, silver",L. 37 1/4 in. (94.6 cm),"Gift of Francis P. Garvan, 1922",,New York,New York,,,,,,,,,Swords,,http://www.metmuseum.org/art/collection/search/22769,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.8a–c,true,true,35650,Arms and Armor,Sword with scabbard,Congressional Presentation Sword and Scabbard of Major General John E. Wool (1784–1869),"American, probably Baltimore",,,,,Sword cutler,,Samuel Jackson,"American, Baltimore, active 1833–70",,"Jackson, Samuel",American,1833,1870,1854–55,1854,1855,"Steel, gold, brass, diamonds, rubies",L. with scabbard 39 3/16 in. (99.6 cm); L. without scabbard 38 13/16 in. (98.5 cm); L. of blade 31 7/16 in. (79.9 cm); W. 5 5/8 in. (14.3 cm),"Purchase, Arthur Ochs Sulzberger and Mr. and Mrs. Robert G. Goelet Gifts, 2009",,Baltimore,Maryland,,,,,,,,,Swords,,http://www.metmuseum.org/art/collection/search/35650,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.131.7,false,true,24649,Arms and Armor,Powder horn,Powder Horn,Colonial American,,,,,Engraver,,Jacob Gay,"American, New York, recorded 1758–87",,"Gay, Jacob",American,1758,1887,dated 1759,1759,1759,"Horn (cow), wood",L. 13 in. (33 cm); Diam. 3 1/2 in. (8.9 cm); Wt. 14.8 oz. (419.6 g),"The Collection of J. H. Grenville Gilbert, of Ware, Massachusetts, Gift of Mrs. Gilbert, 1937",,,New York,,,,,,,,,Firearms Accessories-Powder Horns,,http://www.metmuseum.org/art/collection/search/24649,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.127,false,true,679351,Arms and Armor,Sword,Cavalry Officer's Saber,"American, Baltimore",,,,,Silversmith,Hilt by,John Lynch,"American, Baltimore 1761–1848 Baltimore",,"Lynch, John",American,1761,1848,ca. 1810,1785,1835,"Steel, silver, wood, gold",H. 39 7/8 in. (101.3 cm); H. of blade 34 1/4 in. (87 cm); W. 3 3/4 in. (9.5 cm); Wt. 1 lb. 9.6 oz. (725.7 g),"Purchase, Arthur Ochs Sulzberger Bequest, 2015",,Baltimore,Maryland,,,,,,,,,Swords,,http://www.metmuseum.org/art/collection/search/679351,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1995.336,true,true,24960,Arms and Armor,Percussion revolver,"Colt Third Model Dragoon Percussion Revolver, Serial Number 12406","American, Hartford, Connecticut",,,,,Manufacturer,,Samuel Colt,"American, Hartford, Connecticut 1814–1862",,"Colt, Samuel",American,1814,1862,ca. 1853,1828,1878,"Steel, brass, gold, wood (walnut)",L. 14 in. (35.6 cm); L. of barrel 7 1/2 in. (19.1 cm); Cal. .44 in. (11.2 mm); case; H. 3 in. (7.6 cm); W. 16 3/16 in. (41.1 cm); D. 8 1/8 in. (20.6 cm); Wt. 3 lb. 9.8 oz. (224 g),"Gift of George and Butonne Repaire, 1995",,Hartford,Connecticut,,,,,,,,,Firearms-Pistols-Revolvers,,http://www.metmuseum.org/art/collection/search/24960,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -55.206.1,false,true,24835,Arms and Armor,Percussion revolver,"Colt Paterson Percussion Revolver, No. 5, Holster Model, serial no. 528","American, Paterson, New Jersey",,,,,Manufacturer,,Samuel Colt,"American, Hartford, Connecticut 1814–1862",,"Colt, Samuel",American,1814,1862,ca. 1838–40,1813,1865,"Steel, silver, ivory",L. 16 3/4 in. (42.55 cm); L. of barrel 12 in. (30.48 cm); Cal. .40 in. (10.2 mm),"Gift of John E. Parsons, 1955",,Paterson,New Jersey,,,,,,,,,Firearms-Pistols-Revolvers,,http://www.metmuseum.org/art/collection/search/24835,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -55.206.2,false,true,24836,Arms and Armor,Percussion revolver,"Colt Model 1861 Navy Percussion Revolver, serial no. 12240","American, Hartford, Connecticut",,,,,Manufacturer,,Samuel Colt,"American, Hartford, Connecticut 1814–1862",,"Colt, Samuel",American,1814,1862,1863,1863,1863,"Steel, brass, wood (walnut)",L. 12 3/4 in. (32.39 cm); L. of barrel 7 1/2 in. (19.05 cm); Cal. .36 in. (9.1 mm),"Gift of John E. Parsons, 1955",,Hartford,Connecticut,,,,,,,,,Firearms-Pistols-Revolvers,,http://www.metmuseum.org/art/collection/search/24836,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -56.167.2,false,true,24839,Arms and Armor,Percussion revolver,"Colt Dragoon Percussion Revolver, Third Model, serial no. 12403","American, Hartford, Connecticut",,,,,Manufacturer,,Samuel Colt,"American, Hartford, Connecticut 1814–1862",,"Colt, Samuel",American,1814,1862,1852,1852,1852,"Steel, brass, silver, wood (walnut)",L. 14 in. (35.56 cm); L. of barrel 7 1/2 in. (19.05 cm); Cal. .44 in. (11 mm),"Gift of John E. Parsons, 1956",,Hartford,Connecticut,,,,,,,,,Firearms-Pistols-Revolvers,,http://www.metmuseum.org/art/collection/search/24839,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -57.166.1,false,true,24841,Arms and Armor,Percussion revolver,"Colt Paterson Percussion Revolver, No. 5, Holster Model, serial no. 940","American, Paterson, New Jersey",,,,,Manufacturer,,Samuel Colt,"American, Hartford, Connecticut 1814–1862",,"Colt, Samuel",American,1814,1862,ca. 1840,1815,1865,"Steel, silver, mother-of-pearl",L. 14 in. (35.56 cm); L. of barrel 9 in. (22.86 cm); Cal. .40 in. (10 mm),"Gift of John E. Parsons, 1957",,Paterson,New Jersey,,,,,,,,,Firearms-Pistols-Revolvers,,http://www.metmuseum.org/art/collection/search/24841,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -58.171.3,false,true,24840,Arms and Armor,Revolver,"Colt Model 1851 Navy Revolver with Thuer Conversion for Self-Contained Cartridges, Serial no. 27060","American, Hartford, Connecticut",,,,,Manufacturer,,Samuel Colt,"American, Hartford, Connecticut 1814–1862",,"Colt, Samuel",American,1814,1862,"1853; converted for cartidges, ca. 1868–71",1853,1896,"Steel, brass, silver, ivory",L. 13 in. (33.02 cm); L. of barrel 7 1/2 in. (19.05 cm); Cal. .36 in. (9 mm),"Gift of John E. Parsons, 1958",,Hartford,Connecticut,,,,,,,,,Firearms-Pistols-Revolvers,,http://www.metmuseum.org/art/collection/search/24840,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -59.143.4,false,true,24848,Arms and Armor,Revolver,"""Peacemaker"" Colt Single-Action Army Revolver, serial no. 4519","American, Hartford, Connecticut",,,,,Manufacturer,,Samuel Colt,"American, Hartford, Connecticut 1814–1862",,"Colt, Samuel",American,1814,1862,1874,1874,1874,"Steel, iron, wood",L.13 in. (33.02 cm); L. of barrel 7 1/2 in. (19.05 cm); Cal. .45 in. (11 mm),"Gift of John E. Parsons, 1959",,Hartford,Connecticut,,,,,,,,,Firearms-Pistols-Revolvers,,http://www.metmuseum.org/art/collection/search/24848,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -68.157.2,false,true,24855,Arms and Armor,Percussion revolver,"Colt Model 1851 Navy Percussion Revolver, serial no. 2","American, Hartford, Connecticut",,,,,Manufacturer,,Samuel Colt,"American, Hartford, Connecticut 1814–1862",,"Colt, Samuel",American,1814,1862,1850,1850,1850,"Steel, brass, silver, wood (walnut)",L. of barrel 7 1/2 in. (19.05 cm); Cal. .38 in. (10 mm),"Gift of John E. Parsons, 1968",,Hartford,Connecticut,,,,,,,,,Firearms-Pistols-Revolvers,,http://www.metmuseum.org/art/collection/search/24855,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1983.442a–o,false,true,24892,Arms and Armor,Percussion revolver with case and accessories,"Cased Colt Model 1860 Army Percussion Revolver, Serial no. 7569, with Accessories","American, Hartford, Connecticut",,,,,Manufacturer,,Samuel Colt,"American, Hartford, Connecticut 1814–1862",,"Colt, Samuel",American,1814,1862,1861,1861,1861,"Steel, brass, silver, gold, wood (mahogany), textile",L. 14 1/2 in. (36.83 cm); L. of barrel 8 in. (20.32 cm); Cal. .44 in. (11 mm),"Gift of Mr. and Mrs. Jerry D. Berger, 1983",,Hartford,Connecticut,,,,,,,,,Firearms-Pistols-Revolvers,,http://www.metmuseum.org/art/collection/search/24892,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -56.167.1a–g,false,true,24838,Arms and Armor,Percussion revolver with case and accessories,"Colt Paterson Pocket Percussion Revolver, Fourth Ehlers Model, serial no. 152, with Case and Accessories","American, Paterson, New Jersey",,,,,Manufacturer,,Samuel Colt,"American, Hartford, Connecticut 1814–1862",,"Colt, Samuel",American,1814,1862,ca. 1840–43,1815,1868,"Steel, wood (walnut)",Revolver (a); L. 6 1/2 in. (16.5 cm); L. of barrel 3 in. (7.6 cm); Cal. .28 in. (7.1 mm); bullet mould (b); L. 3 3/16 in. (8.1 cm); W. (when closed) 1 3/4 in. (4.5 cm); Wt. 2.2 oz. (62.4 g); primer (c); L. 3 11/16 in. (9.4 cm); Diam. 1 1/8 in. (2.8 cm); Wt. 3.3 oz. (93.6 g); cleaning rod (d); L. 4 5/8 in. (11.7 cm); Wt. 1.2 oz. (34 g); spanner-screwdriver (e); L. 2 5/8 in. (6.7 cm); W. 2 in. (5.1 cm); Wt. 0.9 oz. (25.5 g); case (f); H. 2 in. (5.1 cm); W. 10 in. (25.4 cm); D. 5 7/8 in. (14.9 cm); Wt. 1 lb. 0.8 oz. (476.3 g); key (g); L. 1 1/8 in. (2.8 cm),"Gift of John E. Parsons, 1956",,Paterson,New Jersey,,,,,,,,,Firearms-Pistols-Revolvers,,http://www.metmuseum.org/art/collection/search/24838,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -57.166.2a–e,false,true,24842,Arms and Armor,Percussion revolver with case and accessories,"Colt Model 1855 Pocket Percussion Revolver, Serial no. 4460, with Case and Accessories","American, Hartford, Connecticut",,,,,Manufacturer,,Samuel Colt,"American, Hartford, Connecticut 1814–1862",,"Colt, Samuel",American,1814,1862,1855,1855,1855,"Steel, wood (oak)",L. 7 7/8 in. (19.99 cm); L. of barrel 3 1/2 in. (8.89 cm); Cal. .28 in. (7 mm); case 9 3/8 x 5 3/4 x 1 7/8 in. (23.8 x 14.61 x 4.75 cm); L. of spanner-screwdriver 3 1/4 in. (8.26 cm); greatest W. of spanner-screwdriver 1 3/8 in. (3.48 cm); L. of primer 4 5/8 in. (11.73 cm); greatest W. of primer 2 in. (5.08 cm); L. of bullet mould 4 3/8 in. (11.1 cm),"Gift of John E. Parsons, 1957",,Hartford,Connecticut,,,,,,,,,Firearms-Pistols-Revolvers,,http://www.metmuseum.org/art/collection/search/24842,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -59.143.1a–h,false,true,24846,Arms and Armor,Percussion revolver with case and accessories,"Colt Paterson Percussion Revolver, No. 3, Belt Model, Serial no. 156, with Case and Accessories","American, Paterson, New Jersey",,,,,Manufacturer,,Samuel Colt,"American, Hartford, Connecticut 1814–1862",,"Colt, Samuel",American,1814,1862,ca. 1838,1813,1863,"Steel, silver, brass, wood (walnut), copper, velvet","Revolver (a); L. 9 5/8 in. (24.4 cm); L. of barrel 5 1/2 in. (14 cm); Cal. .34 in. (8 mm); extra cylinder (b); L. 1 1/4 in. (3.2 cm); Diam. 1 1/4 in. (3.2 cm); Wt. 4.2 oz. (119.1 g); combination patch, powder and ball cylinder (c); L. 5 3/8 in. (13.6 cm); Diam. 1 5/16 in. (3.3 cm); Wt. 12.2 oz. (345.9 g); bullet mould (d); L. 3 11/16 in. (9.4 cm); W. 1 1/4 in. (3.2 cm); Wt. 2.2 oz. (62.4 g); combination screwdriver and pricker (e); L. 4 11/16 in. (11.9 cm); Wt. 1.7 oz. (48.2 g); percussion cap box (f); L. 3 5/16 in. (8.4 cm); Diam. 1 13/16 in. (4.6 cm); Wt. 3.1 oz. (87.9 g); cleaning rod (g); L. 6 7/8 in. (17.5 cm); Wt. 1.1 oz. (31.2 g); case (h); H. 2 1/8 in. (5.4 cm); W. 10 3/8 in. (26.3 cm); D. 7 in. (17.8 cm); Wt. 2 lb. 3.3 oz. (1000.7 g)","Gift of John E. Parsons, 1959",,Paterson,New Jersey,,,,,,,,,Firearms-Pistols-Revolvers,,http://www.metmuseum.org/art/collection/search/24846,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -48.125,false,true,24794,Arms and Armor,Percussion revolver,"Colt Dragoon Percussion Revolver, Third Model, serial no. 13096","American, Hartford, Connecticut",,,,,Manufacturer,,Samuel Colt,"American, Hartford, Connecticut 1814–1862",,"Colt, Samuel",American,1814,1862,1853,1853,1853,"Steel, brass, silver, wood (walnut)",L. 14 3/4 in. (37.47 cm); L. of barrel 7 1/2 in. (19.05 cm); Cal. .45 in. (11.4 mm),"Purchase, Friends of Albert Foster Jr. Gifts, in his memory, 1948",,Hartford,Connecticut,,,,,,,,,Firearms-Pistols-Percussion,,http://www.metmuseum.org/art/collection/search/24794,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2012.105a, b",false,true,35845,Arms and Armor,Sword with scabbard,Sword and Scabbard of Captain Richard French,"American, Chicopee, Massachusetts",,,,,Manufacturer,,Ames Manufacturing Company,"American, Chicopee, Massachusetts, 1829–1935",,Ames Manufacturing Company,American,1829,1935,1850,1825,1875,"Gold, brass, steel",L. 40 in. (101.6 cm); L. of blade 32 1/4 in. (81.9 cm),"Purchase, Arthur Ochs Sulzberger Gift, 2012",,Chicopee,Massachusetts,,,,,,,,,Swords,,http://www.metmuseum.org/art/collection/search/35845,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -96.5.36,false,true,21921,Arms and Armor,Flintlock duelling pistol,Flintlock Duelling Pistol,"American, Middletown, Connecticut",,,,,Gunsmith,,Simeon North,"American, Middletown, Connecticut, 1765–1852",,North Simeon,American,1765,1852,ca. 1815–20,1790,1845,"Steel, gold, silver, wood (walnut, hickory), horn",L. 15 3/4 in. (40.01 cm); L. of barrel 10 in. (25.4 cm); Cal. .56 in. (14.2 mm); Wt. 2 lb. 2 oz. (964 g),"John Stoneacre Ellis Collection, Gift of Mrs. Ellis and Augustus Van Horne Ellis, 1896",,Middletown,Connecticut,,,,,,,,,Firearms-Pistols,,http://www.metmuseum.org/art/collection/search/21921,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"96.5.36, .149",false,true,35815,Arms and Armor,Pair of flintlock duelling pistols,Pair of Flintlock Duelling Pistols,"American, Middletown, Connecticut",,,,,Gunsmith,,Simeon North,"American, Middletown, Connecticut, 1765–1852",,North Simeon,American,1765,1852,ca. 1815–20,1790,1845,"Steel, gold, silver, wood (walnut, hickory), horn","L. of each 15 3/4 in. (40 cm); L. of barrel of each 10 in. (25.4 cm); Cal. of each .56 in. (14.2 mm); Wt. of 96.5.36, 2 lb. 2 oz. (964 g); Wt. of 96.5.149, 2 lb. (907 g)","John Stoneacre Ellis Collection, Gift of Mrs. Ellis and Augustus Van Horne Ellis, 1896",,Middletown,Connecticut,,,,,,,,,Firearms-Pistols,,http://www.metmuseum.org/art/collection/search/35815,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -96.5.149,false,true,25035,Arms and Armor,Flintlock duelling pistol,Flintlock Duelling Pistol,"American, Middletown, Connecticut",,,,,Gunsmith,,Simeon North,"American, Middletown, Connecticut, 1765–1852",,North Simeon,American,1765,1852,ca. 1815–20,1790,1845,"Steel, gold, silver, wood (walnut, hickory), horn",L. 15 3/4 in. (40 cm); L. of barrel 10 in. (25.4 cm); Cal. .56 in. (14.2 mm); Wt. 2 lb. (907 g),"John Stoneacre Ellis Collection, Gift of Mrs. Ellis and Augustus Van Horne Ellis, 1896",,Middletown,Connecticut,,,,,,,,,Firearms-Pistols-Flintlock,,http://www.metmuseum.org/art/collection/search/25035,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.126,false,true,679349,Arms and Armor,Sword,Cavalry Officer's Saber,"American, Philadelphia",,,,,Silversmith,Hilt by,Parry & Musgrave,"American, Philadelphia 1792–1796 Philadelphia",,Parry & Musgrave,American,1792,1796,ca. 1793–95,1768,1820,"Steel, silver, wood, textile, copper",H. 41 in. (104.1 cm); H. of blade 34 1/2 in. (87.6 cm); W. 4 5/8 in. (11.7 cm); D. 4 in. (10.2 cm); Wt. 1 lb. 10.3 oz. (745.6 g),"Purchase, Arthur Ochs Sulzberger Bequest, 2015",,Philadelphia,Pennsylvania,,,,,,,,,Swords,,http://www.metmuseum.org/art/collection/search/679349,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -38.116a–k,false,true,24666,Arms and Armor,Flintlock rifle with case and bullet mould,Flintlock Rifle with Case and Bullet Mould Made for Colonel Jacob Bates (1746–1836),"American, Shrewsbury, Massachusetts",,,,,Gunsmith,,Silas Allen Jr.,"American, Shrewsbury, Massachusetts, 1785–1868",,Allen Silas,American,1785,1868,ca. 1820,1795,1845,"Steel, silver, brass, wood, silver wire",Rifle (a); L. 55 3/4 in. (141.61 cm); L. of barrel 40 1/2 in. (102.87 cm); Cal. .51 in. (12.7 mm); Wt. 11 lb. 2 oz. (5046 g); bullet mould (c); L. 6 1/4 in. (15.9 cm); Wt. 5.6 oz. (158.8 g),"Gift of Christian A. Zabriskie, 1938",,Shrewsbury,Massachusetts,,,,,,,,,Firearms,,http://www.metmuseum.org/art/collection/search/24666,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.582,false,true,35957,Arms and Armor,Experimental helmet prototype,American Helmet Model No. 5,"American, Philadelphia, Pennsylvania",,,,,Manufacturer,,Hale & Kilburn Company,"American, Philadelphia, Pennsylvania 1873–1933 Indianapolis, Indiana",,Hale & Kilburn Company,American,1873,1933,1918,1918,1918,"Steel, paint, leather, textile, string",H. 9 1/4 in. (23.5 cm); W. 10 3/4 in. (27.3 cm); D. 12 5/8 in. (32.1 cm); Wt. 2 lb. 8 oz. (1130 g),"Purchase, Gift of Bashford Dean, by exchange, 2013",,Philadelphia,Pennsylvania,,,,,,,,,Helmets,,http://www.metmuseum.org/art/collection/search/35957,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -42.22,true,true,24681,Arms and Armor,Flintlock rifle,Flintlock Rifle,"American, Pennsylvania",,,,,Gunsmith,,Jacob Kuntz,"American, Allentown, Pennsylvania 1780–1876 Philadelphia, Pennsylvania",,"Kuntz, Jacob",American,1780,1876,ca. 1810–15,1785,1840,"Steel, brass, wood (maple), silver, bone, horn",L. 59 1/4 in. (150.5 cm); L. of barrel 43 in. (109.22 cm); Cal. .46 in. (11.7 mm); Wt. 9 lb. 13 oz. (4451 g),"Gift of Wilfrid Wood, 1956",,,Pennsylvania,,,,,,,,,Firearms-Guns-Flintlock,,http://www.metmuseum.org/art/collection/search/24681,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2013.622a, b",false,true,625200,Arms and Armor,Presentation sword and scabbard,Congressional Presentation Sword and Scabbard of Peleg K. Dunham (1794–1822),American,,,,,Bladesmith|Decorator,Possibly decorated by,"William Rose Sr., 1783–1856|John Meer",active ca. 1795–1834,,"Rose, William Sr.|Meer, John",American,1783 |1795,1856 |1834,1817,1817,1817,"Steel, copper alloy (brass), gold, leather",L. with scabbard 39 5/16 in. (99.8 cm); L. without scabbard 38 1/2 in. (97.8 cm); L. of blade 32 5/16 in. (82 cm); W. 4 5/8 in. (11.7 cm); D. 1 7/8 in. (4.7 cm); Wt. 1 lb. 11 oz. (888 g); Wt. of scabbard 1 lb. 1 oz. (481.9 g),"Purchase, Arthur Ochs Sulberger Gift, 2013",,,,,,,,,,,,Swords,,http://www.metmuseum.org/art/collection/search/625200,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -35.52,false,true,32177,Arms and Armor,Letter,Letter from George Washington to New York Governor George Clinton,American,,,,,Author|Maker,In the hand of,George Washington|Tench Tilghman,"American, 1732–1799|1774–1786",,"Washington, George|Tilghman, Tench",American,1732 |1774,1799 |1786,1780,1780,1780,"Ink, paper",13 1/2 x 8 3/8 in. (34.2 x 21.2 cm),"Rogers Fund, 1935",,New Windsor,New York,,,,,,,,,Works on Paper-Miscellaneous,,http://www.metmuseum.org/art/collection/search/32177,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.178,false,true,23349,Arms and Armor,Armor (Gusoku),Armor (Gusoku),Japanese and European,,,,,Armorer,Helmet signed by,Saotome Iyetada,"Japanese, Edo period, active early–mid-19th century",,"Iyetada, Saotome",Japanese,1800,1875,early–mid-19th century,1800,1875,"Iron, silk, copper, gold",,"Gift of Edith McCagg, in memory of her husband, Louis B. McCagg, 1929",,,,,,,,,,,,Armor for Man,,http://www.metmuseum.org/art/collection/search/23349,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1999.270,false,true,26636,Arms and Armor,Sword guard (Tsuba),Sword Guard (Tsuba),"Japanese, Tokyo",,,,,Fittings maker,Inscribed by,Joka,"Japanese, Tokyo, active late 18th–early 19th century",", after Yasuchika",Joka,Japanese,1750,1850,early 19th century,1801,1850,"Iron, lacquer, gold",H. 3 1/4 in. (8.3 cm); W. 3 in. (7.6 cm),"Gift of Ryoichi Iida, 1999",,Tokyo,,,,,,,,,,Sword Furniture-Tsuba,,http://www.metmuseum.org/art/collection/search/26636,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -43.120.723,false,true,28455,Arms and Armor,Sword guard (Tsuba),Sword Guard (Tsuba),Japanese,,,,,Fittings maker,Inscribed by,Ishiguro Masatsune,"Japanese, 1760–1828",,Ishiguro Masatsune,Japanese,1760,1828,late 18th–early 19th century,1750,1850,"Copper-gold alloy (shakudō), copper-silver alloy (shibuichi), gold, copper",H. 2 15/16 in. (7.5 cm); W. 2 3/4 in. (7 cm); thickness 5/16 in. (0.8 cm); Wt. 5.7 oz. (161.6 g),"Gift of Herman A. E. and Paul C. Jaehne, 1943",,,,,,,,,,,,Sword Furniture-Tsuba,,http://www.metmuseum.org/art/collection/search/28455,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -43.120.724,false,true,28456,Arms and Armor,Sword guard (Tsuba),Sword Guard (Tsuba),Japanese,,,,,Fittings maker,Inscribed by,Ishiguro Masatsune,"Japanese, 1760–1828",,Ishiguro Masatsune,Japanese,1760,1828,late 18th–early 19th century,1750,1850,"Copper-gold alloy (shakudō), copper-silver alloy (shibuichi), gold, copper",H. 3 1/8 in. (7.9 cm); W. 2 15/16 in. (7.5 cm); thickness 5/16 in. (0.8 cm); Wt. 7 oz. (198.4 g),"Gift of Herman A. E. and Paul C. Jaehne, 1943",,,,,,,,,,,,Sword Furniture-Tsuba,,http://www.metmuseum.org/art/collection/search/28456,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"43.120.723, .724",false,true,657341,Arms and Armor,Pair of sword guards (daishō tsuba),Pair of Sword Guards (Daishō Tsuba),Japanese,,,,,Fittings maker,Inscribed by,Ishiguro Masatsune,"Japanese, 1760–1828",,Ishiguro Masatsune,Japanese,1760,1828,late 18th–early 19th century,1750,1850,"Copper-gold alloy (shakudō), copper-silver alloy (shibuichi), gold, copper",43.120.723: H. 2 15/16 in. (7.5 cm); W. 2 3/4 in. (7 cm); thickness 5/16 in. (0.8 cm); Wt. 5.7 oz. (161.6 g); 43.120.724: H. 3 1/8 in. (7.9 cm); W. 2 15/16 in. (7.5 cm); thickness 5/16 in. (0.8 cm); Wt. 7 oz. (198.4 g),"Gift of Herman A. E. and Paul C. Jaehne, 1943",,,,,,,,,,,,Sword Furniture-Tsuba,,http://www.metmuseum.org/art/collection/search/657341,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -43.120.467,false,true,25362,Arms and Armor,Knife handle (Kozuka),Knife Handle (Kozuka),Japanese,,,,,Fittings maker,Inscribed by,Iwama Masayoshi (Shoro),"Japanese, 1764–1837",,Iwama Masayoshi (Shoro),Japanese,1764,1837,late 18th–early 19th century,1750,1850,"Copper-silver alloy (shibuichi), copper-gold alloy (shakudō), gold, silver, copper",L. 3 13/16 in. (9.7 cm); W. 9/16 in. (1.4 cm); thickness 1/4 in. (0.6 cm); Wt. 1.4 oz. (39.7 g),"Gift of Herman A. E. and Paul C. Jaehne, 1943",,,,,,,,,,,,Sword Furniture-Kozuka,,http://www.metmuseum.org/art/collection/search/25362,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.120.335,false,true,29721,Arms and Armor,Knife handle (Kozuka),Knife Handle (Kozuka),Japanese,,,,,Fittings maker,Inscribed by,Kanō Natsuo,"Japanese, 1828–1898",,"Natsuo, Kanō",Japanese,1828,1898,mid-19th century,1825,1875,"Copper-silver alloy (shibuichi), silver, copper-gold alloy (shakudō)",L. 3 13/16 in. (9.7 cm); W. 9/16 in. (1.4 cm); thickness 3/16 in. (0.5 cm); Wt. 1.2 oz. (34 g),"The Howard Mansfield Collection, Gift of Howard Mansfield, 1936",,,,,,,,,,,,Sword Furniture-Kozuka,,http://www.metmuseum.org/art/collection/search/29721,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.120.339,false,true,29725,Arms and Armor,Knife handle (Kozuka),Knife Handle (Kozuka),Japanese,,,,,Fittings maker,Inscribed by,Kanō Natsuo,"Japanese, 1828–1898",,"Natsuo, Kanō",Japanese,1828,1898,mid-19th century,1825,1875,"Copper-silver alloy (shibuichi), silver",L. 3 1/4 in. (8.3 cm); W. 9/16 in. (1.4 cm); thickness 3/16 in. (0.5 cm); Wt. 1 oz. (28.3 g),"The Howard Mansfield Collection, Gift of Howard Mansfield, 1936",,,,,,,,,,,,Sword Furniture-Kozuka,,http://www.metmuseum.org/art/collection/search/29725,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -07.109.5,false,true,22102,Arms and Armor,Blade and mounting for a slung sword (Tachi),Blade and Mounting for a Slung Sword (Tachi),Japanese,,,,,Sword maker,,Naganori,"Japanese, active 13th century",,Naganori,Japanese,1201,1300,"blade, late 13th–early 14th century; mounting, 18th–early 19th century",1201,1900,"Steel, wood, lacquer, rayskin (samé), thread, gold, silver",L. 32 7/8 in. (83. 5 cm); L. of blade 25 15/16 in. (65.8 cm); L. of cutting edge 19 5/8 in. (49.8 cm); D. of curvature 5/16 in. (0.75 cm),"Gift of the family of Dr. Francis E. Doughty, 1907",,,,,,,,,,,,Swords,,http://www.metmuseum.org/art/collection/search/22102,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -32.75.321,false,true,23393,Arms and Armor,Ceremonial arrowhead (Yanonē),Ceremonial Arrowhead (Yanonē),Japanese,,,,,Steel-chiseler,,Umetada Motoshige,"Japanese, Edo period, died 1675",,"Motoshige, Umetada",Japanese,1575,1675,dated 1645,1645,1645,Steel,L. 12 5/8 in. (32.1 cm); L. of head 5 3/4 in. (14.6 cm); W. 2 5/8 in. (6.7 cm); Wt. 6.7 oz. (189.9 g),"The Collection of Giovanni P. Morosini, presented by his daughter Giulia, 1932",,,,,,,,,,,,Archery Equipment-Arrowheads,,http://www.metmuseum.org/art/collection/search/23393,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -32.75.327,false,true,23399,Arms and Armor,Ceremonial arrowhead (Yanonē),Ceremonial Arrowhead (Yanonē),Japanese,,,,,Steel-chiseler,,Umetada Motoshige,"Japanese, Edo period, died 1675",,"Motoshige, Umetada",Japanese,1575,1675,dated 1645,1645,1645,"Iron, gold",L. 11 1/4 in. (28.6 cm); L. of head 4 3/4 in. (12.1 cm); W. 5 in. (12.7 cm); Wt. 6.4 oz. (181.4 g),"The Collection of Giovanni P. Morosini, presented by his daughter Giulia, 1932",,,,,,,,,,,,Archery Equipment-Arrowheads,,http://www.metmuseum.org/art/collection/search/23399,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -32.75.330,false,true,23402,Arms and Armor,Ceremonial arrowhead (Yanoné),Ceremonial Arrowhead (Yanoné),Japanese,,,,,Steel-chiseler,,Umetada Motoshige,"Japanese, Edo period, died 1675",,"Motoshige, Umetada",Japanese,1575,1675,dated 1645,1645,1645,Steel,L. 12 1/8 in. (30.8 cm); L. of head 5 7/16 in. (13.8 cm); W. 2 1/2 in. (6.4 cm); Wt. 6.5 oz. (184.3 g),"The Collection of Giovanni P. Morosini, presented by his daughter Giulia, 1932",,,,,,,,,,,,Archery Equipment-Arrowheads,,http://www.metmuseum.org/art/collection/search/23402,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -32.75.334,false,true,23406,Arms and Armor,Ceremonial arrowhead (Yanonē),Ceremonial Arrowhead (Yanonē),Japanese,,,,,Steel-chiseler,,Umetada Motoshige,"Japanese, Edo period, died 1675",,"Motoshige, Umetada",Japanese,1575,1675,dated 1645,1645,1645,Steel,L. 11 3/8 in. (28.9 cm); L. of head 4 1/2 in. (11.4 cm); W. 2 5/16 in. (5.9 cm); Wt. 5.2 oz. (147.4 g),"The Collection of Giovanni P. Morosini, presented by his daughter Giulia, 1932",,,,,,,,,,,,Archery Equipment-Arrowheads,,http://www.metmuseum.org/art/collection/search/23406,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -32.75.337,false,true,23409,Arms and Armor,Ceremonial arrowhead (Yanonē),Ceremonial Arrowhead (Yanonē),Japanese,,,,,Steel-chiseler,,Umetada Motoshige,"Japanese, Edo period, died 1675",,"Motoshige, Umetada",Japanese,1575,1675,dated 1645,1645,1645,Steel,L. 11 in. (27.9 cm); L. of head 4 1/8 in. (10.5 cm); W. 2 1/8 in. (5.4 cm); Wt. 3.9 oz. (110.6 g),"The Collection of Giovanni P. Morosini, presented by his daughter Giulia, 1932",,,,,,,,,,,,Archery Equipment-Arrowheads,,http://www.metmuseum.org/art/collection/search/23409,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -32.75.339,false,true,23411,Arms and Armor,Ceremonial arrowhead (Yanonē),Ceremonial Arrowhead (Yanonē),Japanese,,,,,Steel-chiseler,,Umetada Motoshige,"Japanese, Edo period, died 1675",,"Motoshige, Umetada",Japanese,1575,1675,dated 1645,1645,1645,Steel,L. 11 3/4 in. (29.9 cm); L. of head 3 7/8 in. (9.8 cm); W. 2 1/8 in. (5.4 cm); Wt. 4.3 oz. (121.9 g),"The Collection of Giovanni P. Morosini, presented by his daughter Giulia, 1932",,,,,,,,,,,,Archery Equipment-Arrowheads,,http://www.metmuseum.org/art/collection/search/23411,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -32.75.340,false,true,23412,Arms and Armor,Ceremonial arrowhead (Yanonē),Ceremonial Arrowhead (Yanonē),Japanese,,,,,Steel-chiseler,,Umetada Motoshige,"Japanese, Edo period, died 1675",,"Motoshige, Umetada",Japanese,1575,1675,dated August 1645,1645,1645,Steel,L. 11 3/4 in. (29.9 cm); L. of head 5 1/8 in. (13 cm); W. 2 3/8 in. (6 cm); Wt. 5.3 oz. (150.3 g),"The Collection of Giovanni P. Morosini, presented by his daughter Giulia, 1932",,,,,,,,,,,,Archery Equipment-Arrowheads,,http://www.metmuseum.org/art/collection/search/23412,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -32.75.398,false,true,23469,Arms and Armor,Ceremonial arrowhead (Yanonē),Ceremonial Arrowhead (Yanonē),Japanese,,,,,Steel-chiseler,,Umetada Motoshige,"Japanese, Edo period, died 1675",,"Motoshige, Umetada",Japanese,1575,1675,dated August 1645,1645,1645,Steel,L. 12 3/4 in. (32.4 cm); L. of head 6 in. (15.7 cm); W. 2 3/4 in. (7 cm); Wt. 7.1 oz. (201.3 g),"The Collection of Giovanni P. Morosini, presented by his daughter Giulia, 1932",,,,,,,,,,,,Archery Equipment-Arrowheads,,http://www.metmuseum.org/art/collection/search/23469,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -32.75.399,false,true,23470,Arms and Armor,Ceremonial arrowhead (Yanonē),Ceremonial Arrowhead (Yanonē),Japanese,,,,,Steel-chiseler,,Umetada Motoshige,"Japanese, Edo period, died 1675",,"Motoshige, Umetada",Japanese,1575,1675,dated 1645,1645,1645,Steel,L. 11 1/4 in. (28.6 cm); L. of head 4 3/8 in. (11.1 cm); W. 3 3/8 in. (8.6 cm); Wt. 5.7 oz. (161.6 g),"The Collection of Giovanni P. Morosini, presented by his daughter Giulia, 1932",,,,,,,,,,,,Archery Equipment-Arrowheads,,http://www.metmuseum.org/art/collection/search/23470,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -32.75.403,false,true,23474,Arms and Armor,Ceremonial arrowhead (Yanonē),Ceremonial Arrowhead (Yanonē),Japanese,,,,,Steel-chiseler,,Umetada Motoshige,"Japanese, Edo period, died 1675",,"Motoshige, Umetada",Japanese,1575,1675,dated 1645,1645,1645,Steel,L. 10 5/8 in. (27 cm); L. of head 4 1/8 in. (10.5 cm); W. 1 3/4 in. (4.5 cm); Wt. 3.5 oz. (99.2 g),"The Collection of Giovanni P. Morosini, presented by his daughter Giulia, 1932",,,,,,,,,,,,Archery Equipment-Arrowheads,,http://www.metmuseum.org/art/collection/search/23474,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -32.75.406,false,true,23477,Arms and Armor,Ceremonial arrowhead (Yanonē),Ceremonial Arrowhead (Yanonē),Japanese,,,,,Steel-chiseler,,Umetada Motoshige,"Japanese, Edo period, died 1675",,"Motoshige, Umetada",Japanese,1575,1675,dated 1645,1645,1645,Steel,L. 10 1/2 in. (26.7 cm); L. of head 3 7/8 in. (9.8 cm); W. 2 in. (5.1 cm); Wt. 3.5 oz. (99.2 g),"The Collection of Giovanni P. Morosini, presented by his daughter Giulia, 1932",,,,,,,,,,,,Archery Equipment-Arrowheads,,http://www.metmuseum.org/art/collection/search/23477,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -32.75.409,false,true,23480,Arms and Armor,Ceremonial arrowhead (Yanonē),Ceremonial Arrowhead (Yanonē),Japanese,,,,,Steel-chiseler,,Umetada Motoshige,"Japanese, Edo period, died 1675",,"Motoshige, Umetada",Japanese,1575,1675,dated 1645,1645,1645,Steel,L. 10 5/8 in. (27 cm); L. of head 4 1/16 in. (10.3 cm); W. 1 7/8 in. (4.8 cm); Wt. 3.8 oz. (107.7 g),"The Collection of Giovanni P. Morosini, presented by his daughter Giulia, 1932",,,,,,,,,,,,Archery Equipment-Arrowheads,,http://www.metmuseum.org/art/collection/search/23480,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -13.112.13,false,true,22161,Arms and Armor,Mask,Mask,Japanese,,,,,Armorer,Inscribed by,Myōchin Muneakira,"Japanese, Edo period, 1673–1745",,"Muneakira, Myōchin",Japanese,1673,1745,dated 1715,1715,1715,"Iron, lacquer",H. 7 3/4 in. (19.7 cm); W. 6 3/4 in. (17.2 cm),"Rogers Fund, 1913",,,,,,,,,,,,Armor Parts-Masks,,http://www.metmuseum.org/art/collection/search/22161,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.100.45,false,true,22469,Arms and Armor,Mask,Mask,Japanese,,,,,Armorer,Inscribed by,Myōchin Muneakira,"Japanese, Edo period, 1673–1745",,"Muneakira, Myōchin",Japanese,1673,1745,dated 1713,1713,1713,"Iron, lacquer",H. 8 11/16 in. (22.2 cm); W. 6 3/4 in. (17.2 cm),"Gift of Bashford Dean, 1914",,,,,,,,,,,,Armor Parts-Masks,,http://www.metmuseum.org/art/collection/search/22469,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -13.112.4,false,true,22154,Arms and Armor,Armor (Gusoku),Armor (Gusoku),Japanese,,,,,Armorer,Signed by,Myōchin Munesuke,"Japanese, Edo period, 1688–1735",,"Munesuke, Myōchin",Japanese,1688,1735,early 18th century,1700,1750,"Iron, lacquer, silk, copper, gold",,"Rogers Fund, 1913",,,,,,,,,,,,Armor for Man,,http://www.metmuseum.org/art/collection/search/22154,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.60.27,false,true,34931,Arms and Armor,Sword guard (Tsuba),Sword Guard (Tsuba),Japanese,,,,,Fittings maker,Inscribed by,Hisanori,"Japanese, active ca. 17th century",,Hisanori,Japanese,1575,1725,possibly 17th century,1575,1725,"Brass, copper-gold alloy (shakudō)",H. 3 1/4 in. (8.3 cm); W. 3 1/8 in. (7.9 cm); thickness 3/16 in. (0.5 cm); Wt. 4.4 oz. (124.7 g),"Gift of Mrs. Mary E. Larkin Joline, 1914",,,,,,,,,,,,Sword Furniture-Tsuba,,http://www.metmuseum.org/art/collection/search/34931,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2001.574,false,true,24978,Arms and Armor,Blade for a sword (Katana),Blade for a Sword (Katana),Japanese,,,,,Swordsmith,Blade inscribed by,Masazane,"Japanese, Ise, documented 1515–26",,Masazane,Japanese,1515,1526,dated 1526,1526,1526,Steel,L. 36 1/8 in. (91.8 cm); L. of blade edge 29 9/16 in. (75.1 cm); D. of curvature 3/32 in. (2.4 cm),"Purchase, Arthur Ochs Sulzberger Gift, 2001",,,,,,,,,,,,Swords,,http://www.metmuseum.org/art/collection/search/24978,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2007.478.2a, b",false,true,27600,Arms and Armor,Blade for a sword (Katana),Blade for a Sword (Katana),Japanese,,,,,Sword maker,Signed,Etchu no kami Fujiwara Takahira,"Japanese, active early 17th century",,"Takahira, Etchu no kami Fujiwara",Japanese,1600,1650,dated June 1622,1622,1622,Steel,L. 36 1/2 in. (92.8 cm); L. of cutting edge 28 1/16 in. (71.5 cm); D. of curvature 1 in. (1.5 cm),"Gift of Etsuko O. Morris and John H. Morris Jr., in memory of Dr. Frederick M. Pederson, 2007",,,,,,,,,,,,Sword Blades,,http://www.metmuseum.org/art/collection/search/27600,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"1991.373a, b",true,true,24948,Arms and Armor,Blade for a dagger (Tantō),Blade for a Dagger (Tantō),Japanese,,,,,Swordsmith,Blade inscribed by,Rai Kunitoshi,"Japanese, active ca. 1290–ca. 1320",,Kunitoshi Rai,Japanese,1265,1345,ca. 1315–16,1290,1341,Steel,L. 13 5/8 in. (34.6 cm); L. of cutting edge 9 3/8 in. (23.8 cm); W. of blade at hilt 15/16 in. (2.4 cm); Wt. 7 oz. (185 g),"Gift of Mr. and Mrs. Robert Andrews Izard, 1991",,,,,,,,,,,,Daggers,,http://www.metmuseum.org/art/collection/search/24948,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1998.127.2,false,true,26571,Arms and Armor,Blade and mounting for a dagger (Tantō),Blade and Mounting for a Dagger (Tantō),Japanese,,,,,Swordsmith,Blade inscribed by,Uda Kunimitsu,"Japanese, active early–mid-14th century",,"Kunimitsu, Uda",Japanese,1300,1375,"blade, dated November, 1333; mounting, 19th century",1333,1900,"Steel, wood, lacquer, ray skin (samé), leather, copper, gold, silver, gold, shark skin, copper-gold alloy (shakudō)",L. 17 5/8 in. (44.7 cm); L. of cutting edge 12 in. (30.4 cm),"Gift of Peter H. B. Frelinghuysen, 1998",,,,,,,,,,,,Daggers,,http://www.metmuseum.org/art/collection/search/26571,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -09.49.2,false,true,22108,Arms and Armor,Hand cannon,Hand Cannon,Japanese,,,,,Gunsmith,Inscribed by,Kazuki Nobumichi,"Japanese, active late 18th–early 19th century",,"Nobumichi, Kazuki",Japanese,1750,1850,late 18th–early 19th century,1750,1850,"Iron, gold, silver, wood, brass",L. 37 in. (94 cm),"Gift of Marshall C. Lefferts, 1909",,,,,,,,,,,,Firearms-Hand Cannon,,http://www.metmuseum.org/art/collection/search/22108,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -45.24.52a–d,false,true,24727,Arms and Armor,Set of sword fittings (Mitokoromono),Set of Sword Fittings (Mitokoromono),Japanese,,,,,Fittings maker,,Gotō Sōjō,"Japanese, ca. 1461–1538, second-generation Gotō master",,Gotō Sōjō,Japanese,1461,1538,late 15th–early 16th century,1450,1550,"Copper-gold alloy (shakudō), gold","L. of hair dressing tool (kogai) (a) 8 5/16 in. (21.1 cm); L. of each grip ornament (menuki) (b, c) 1 11/32 in. (3.4 cm); L. of knife handle (kozuka) (d) 3 13/16 in. (9.7 cm)","Rogers Fund, 1945",,,,,,,,,,,,"Sword Furniture-Fittings, Sets of",,http://www.metmuseum.org/art/collection/search/24727,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"36.120.407a, b",false,true,29684,Arms and Armor,Helmet (Hoshi kabuto) in the 16th-century style,Helmet (Hoshi- Kabuto) in the 16th-Century Style,Japanese,,,,,Armorer,Inscribed by,Saotame Iesuek,"Japanese, Miyazaki Prefecture, active late 17th–early 18th century",,"Iesuek, Saotame",Japanese,1650,1750,probably late 17th–early 18th century,1625,1775,"Iron, lacquer, silk",H. 11 3/4 in. (29.9 cm); W. 13 in. (33 cm),"The Howard Mansfield Collection, Gift of Howard Mansfield, 1936",,,,,,,,,,,,Helmets,,http://www.metmuseum.org/art/collection/search/29684,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -17.208.65,false,true,35060,Arms and Armor,Sword guard (Tsuba),Sword Guard (Tsuba),Japanese,,,,,Fittings maker,Inscribed by,Nobuiye Myōchin,"Japanese, ca. 1504–1554",,"Myōchin, Nobuiye",Japanese,1479,1579,16th century,1501,1600,Iron,H. 3 1/8 in. (7.9 cm); W. 3 in. (7.6 cm); thickness 3/16 in. (0.5 cm); Wt. 4.4 oz. (124.7 g),"Gift of a Trustee of the Museum, 1917",,,,,,,,,,,,Sword Furniture-Tsuba,,http://www.metmuseum.org/art/collection/search/35060,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -04.4.9a–l,false,true,22022,Arms and Armor,Armor (Gusoku),Armor (Gusoku),Japanese,,,,,Armorer,Inscribed by,Yukinoshita Sadaiyé,"Japanese, active 17th century",,"Sadaiyé, Yukinoshita",Japanese,1601,1700,17th century,1601,1700,"Iron, lacquer, silk, gilt copper",as mounted: H. 53 in. (134.6 cm); W. 32 in. (81.3 cm); D. 20 in. (50.8 cm),"Rogers Fund, 1904",,,,,,,,,,,,Armor for Man,,http://www.metmuseum.org/art/collection/search/22022,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -32.75.318,false,true,23390,Arms and Armor,Ceremonial arrowhead (Yanonē),Ceremonial Arrowhead (Yanonē),Japanese,,,,,Steel-chiseler,,Umetada Yoshinobu,"Japanese, Edo period, 17th century",,"Yoshinobu, Umetada",Japanese,1601,1700,17th century,1601,1700,Steel,L. 18 5/8 in. (47.3 cm); L. of head 6 3/4 in. (17.1 cm); W. 3 1/2 in. (8.9 cm); Wt. 6.9 oz. (195.6 g),"The Collection of Giovanni P. Morosini, presented by his daughter Giulia, 1932",,,,,,,,,,,,Archery Equipment-Arrowheads,,http://www.metmuseum.org/art/collection/search/23390,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -48.63.4,false,true,24790,Arms and Armor,Helmet (Hoshi-kabuto),Helmet (Hoshi-Kabuto),Japanese,,,,,Armorer,Inscribed by,Iyehisa of Nara,"Japanese, Nara, active 17th century",,"Japanese, Nara Iyehisa of Nara",Japanese,1601,1700,17th century,1601,1700,"Iron, lacquer, gilt copper, copper-gold alloy (shakūdo), silver, silk, gilt leather",H. 11 in. (27.9 cm); L. 15 1/2 in. (39.4 cm),"Rogers Fund, 1948",,,,,,,,,,,,Helmets,,http://www.metmuseum.org/art/collection/search/24790,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.120.91,false,true,30090,Arms and Armor,Sword guard (Tsuba),Sword Guard (Tsuba),Japanese,,,,,Fittings maker,Inscribed by,Ichinomiya Nagatsune,"Japanese, 1721–1786",,Ichinomiya Nagatsune,Japanese,1721,1786,18th century,1701,1800,"Iron, silver",H. 3 1/8 in. (7.9 cm); W. 3 in. (7.6 cm); thickness 3/8 in. (1 cm); Wt. 7.6 oz. (215.5 g),"The Howard Mansfield Collection, Gift of Howard Mansfield, 1936",,,,,,,,,,,,Sword Furniture-Tsuba,,http://www.metmuseum.org/art/collection/search/30090,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -19.71.8,false,true,33366,Arms and Armor,Sword guard (Tsuba),Sword Guard (Tsuba),Japanese,,,,,Fittings maker,Inscribed by,Ōmori Teruhide,"Japanese, Edo period, 1730–1798",,"Teruhide, Ōmori",Japanese,1730,1798,18th century,1701,1800,"Copper-gold alloy (shakudō), gold, copper-silver alloy (shibuichi)",H. 2 7/8 in. (7.3 cm); W. 2 3/4 in. (7 cm); thickness 5/16 in. (0.8 cm); Wt. 5.7 oz. (161.6 g),"Charles Stewart Smith Memorial Fund, 1919",,,,,,,,,,,,Sword Furniture-Tsuba,,http://www.metmuseum.org/art/collection/search/33366,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -43.120.728,false,true,24712,Arms and Armor,Sword guard (Tsuba),Sword Guard (Tsuba),Japanese,,,,,Fittings maker,Inscribed by,Gotō Ichijō,"Japanese, 1791–1876",,Gotō Ichijō,Japanese,1791,1876,19th century,1801,1900,"Copper-gold alloy (shakudō), silver, gold, copper",H. 2 11/16 in. (6.8 cm); W. 2 1/2 in. (6.4 cm); thickness 3/16 in. (0.5 cm); Wt. 4 oz. (113.4 g),"Gift of Herman A. E. and Paul C. Jaehne, 1943",,,,,,,,,,,,Sword Furniture-Tsuba,,http://www.metmuseum.org/art/collection/search/24712,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2012.528.3,false,true,35913,Arms and Armor,Sword guard (Tsuba),Sword Guard (Tsuba),Japanese,,,,,Fittings maker,Inscribed by,Gotō Mitsuakira,"Japanese, 1816–1856",,"Mitsuakira, Gotō",Japanese,1816,1856,19th century,1801,1900,"Copper-gold alloy (shakudō), gold, silver, copper",H. 2 3/4 in. (7 cm); W. 2 9/16 in. (6.5 cm); thickness 5/16 in. (0.8 cm); Wt. 4.4 oz. (124.7 g),"Gift of the Baber Family, in loving memory of Charles Chenault Baber, 2012",,,,,,,,,,,,Sword Furniture-Tsuba,,http://www.metmuseum.org/art/collection/search/35913,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -17.208.42,false,true,35038,Arms and Armor,Knife handle (Kozuka),Knife Handle (Kozuka),Japanese,,,,,Fittings maker,Inscribed by,Ginshōtei Tōmei,"Japanese, 1817–1870",,"Tōmei, Ginshōtei",Japanese,1817,1870,19th century,1801,1900,"Iron, gold, copper-silver alloy (shibuichi)",L. 3 13/16 in. (9.7 cm); W. 9/16 in. (1.4 cm); thickness 1/4 in. (0.6 cm); Wt. 1.4 oz. (39.7 g),"Gift of a Trustee of the Museum, 1917",,,,,,,,,,,,Sword Furniture-Kozuka,,http://www.metmuseum.org/art/collection/search/35038,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -17.208.32a–e,false,true,35031,Arms and Armor,Set of sword fittings (Mitokoromono),Set of Sword Fittings (Mitokoromono),Japanese,,,,,Fittings maker,Inscribed by,Ginshōtei Tōmei,"Japanese, 1817–1870",,"Tōmei, Ginshōtei",Japanese,1817,1870,19th century,1801,1900,"Iron, gold, copper-silver alloy (shibuichi)",sword guard (tsuba) 17.208.32a: 2 5/8 x 2 3/8 in. (6.7 x 6.0 cm); cord knob (kurigata) 17.208.32b: 1 1/4 x 3/4 in. (3.2 x 1.9 cm); socket brace for knife (uragawara) 17.208.32c 1 1/4 x 3/4 in. (3.2 x 1.9 cm); pommel cap (kashira) 17.208.32d: 1 3/8 x 3/4 in. (3.5 x 1.9 cm); hilt collar (fuchi) 17.208.32e: 1 1/2 x 3/4 in. (3.8 x 1.9 cm),"Gift of a Trustee of the Museum, 1917",,,,,,,,,,,,"Sword Furniture-Fittings, Sets of",,http://www.metmuseum.org/art/collection/search/35031,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.120.99,false,true,24625,Arms and Armor,Sword guard (Tsuba),Sword Guard (Tsuba),Japanese,,,,,Fittings maker,,Kanō Natsuo,"Japanese, 1828–1898",,"Natsuo, Kanō",Japanese,1828,1898,19th century,1801,1900,"Copper-gold alloy (shakudō), gold, copper",H. 2 1/4 in. (5.7 cm); W. 1 15/16 in. (4.9 cm); thickness 3/16 in. (0.5 cm); Wt. 2.4 oz. (68 g),"The Howard Mansfield Collection, Gift of Howard Mansfield, 1936",,,,,,,,,,,,Sword Furniture-Tsuba,,http://www.metmuseum.org/art/collection/search/24625,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.120.79,true,true,24623,Arms and Armor,Sword guard (Tsuba),Sword Guard (Tsuba),Japanese,,,,,Fittings maker,Inscribed by,Ishiguro Masayoshi,"Japanese, 1772–after 1851",,Ishiguro Masayoshi,Japanese,1772,1851,19th century,1801,1900,"Copper-gold alloy (shakudō), gold, copper-silver alloy (shibuichi), copper",H. 2 7/8 in. (7.3 cm); W. 2 5/8 in. (6.7 cm); thickness 5/16 in. (0.8 cm); Wt. 5.4 oz. (153.1 g),"The Howard Mansfield Collection, Gift of Howard Mansfield, 1936",,,,,,,,,,,,Sword Furniture-Tsuba,,http://www.metmuseum.org/art/collection/search/24623,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -48.63.5a–l,false,true,24786,Arms and Armor,Fittings for a pair of swords (Daishō soroi-mono),Fittings for a Pair of Swords (Daishō Soroi-Mono),Japanese,,,,,Fittings maker,Inscribed by,Masayoshi,"Japanese, born 1784",,Masayoshi,Japanese,1784,1884,early 19th century,1800,1850,"Copper-gold alloy (shakudō), gold, silver, copper-silver alloy (shibuichi), copper","Sword guards (Tsuba) (a, b); H. of each 2 7/8 in. (7.3 cm); W. of each 2 11/16 in. (6.8 cm); thickness of each 3/8 in. (0.5 cm); Wt. of each 5.2 oz. (147.4 g); knife handles (Kozuka) (c, d); 3 7/8 x 9/16 in. (9.8 x 1.4 cm); sword-hilt collar (fuchi) (e); 1 1/2 x 13/16 in. (3.8 x 2.1 cm); pommel (kashira) (f); 1 3/8 x 11/16 in. (3.5 x 1.7 cm); sword-hilt collar (fuchi) (g); 1 1/2 x 7/8 in. (3.8 x 2.2 cm); pommel (kashira) (h); 1 3/8 x 3/4 in. (3.5 x 1.9 cm); each of a pair of menuki (i, j); 1 1/16 x 5/8 in. (2.7 x 1.6 cm); each of a pair of menuki (k, l); 1 x 9/16 in. (2.5 x 1.4 cm)","Rogers Fund, 1948",,,,,,,,,,,,"Sword Furniture-Fittings, Sets of",,http://www.metmuseum.org/art/collection/search/24786,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2006.262.1, .2",false,true,33557,Arms and Armor,Pair of sword guards (Tsuba),Pair of Sword Guards (Tsuba),Japanese,,,,,Fittings maker,Inscribed on the inner face of each tsuba by,Sunagawa Mao-Yoshi,"Japanese, active early–mid-19th century",,Sunagawa Mao-Yoshi,Japanese,1800,1875,early 19th century,1800,1850,"Copper-gold alloy (shakudō), gold, copper",2006.262.1: H. 2 15/16 in. (7.4 cm); W. 2 3/4 in. (7.0 cm); 2006.262.2: H. 2 13/16 in. (7.1 cm); W. 2 9/16 in. (6.5 cm),"Purchase, Gift of Mrs. George A. Crocker (Elizabeth Masten), by exchange, 2006",,,,,,,,,,,,Sword Furniture-Tsuba,,http://www.metmuseum.org/art/collection/search/33557,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -43.120.537,false,true,25355,Arms and Armor,Knife handle (Kozuka),Knife Handle (Kozuka),Japanese,,,,,Maker,,Hamano Toshihiro,"Japanese, died 1861",,Hamano Toshihiro,Japanese,,1861,late 18th–mid-19th century,1775,1870,"Copper-silver alloy (shibuichi), gold, copper, silver, copper-gold alloy (shakudō)",L. 3 13/16 in. (9.7 cm); W. 9/16 in. (1.4 cm); thickness 3/16 in. (0.5 cm); Wt. 1.2 oz. (34 g),"Gift of Herman A. E. and Paul C. Jaehne, 1943",,,,,,,,,,,,Sword Furniture-Kozuka,,http://www.metmuseum.org/art/collection/search/25355,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -43.120.692,false,true,24711,Arms and Armor,Sword guard (Tsuba),Sword Guard (Tsuba),Japanese,,,,,Maker,Attributed to the,Hirata School,"Japanese, Edo period",,Hirata School,Japanese,1615,1868,ca. 1615–1868,1590,1900,"Copper-gold alloy (shakudō), gold, enameled cloisonné (shippō), copper",H. 2 7/8 in. (7.3 cm); W. 2 15/16 in. (7.5 cm); thickness 1/4 in. (0.6 cm); Wt. 5.4 oz. (153.1 g),"Gift of Herman A. E. and Paul C. Jaehne, 1943",,,,,,,,,,,,Sword Furniture-Tsuba,,http://www.metmuseum.org/art/collection/search/24711,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2004.460,false,true,27692,Arms and Armor,Knife handle (Kozuka),Knife Handle (Kozuka),Japanese,,,,,Maker,,Goto Mitsunori,"Japanese, 1646–1712",,Goto Mitsunori,Japanese,1646,1712,early 18th century,1701,1712,"Copper-gold alloy (shakudō), gold",L. 3 13/16 in. (9.7 cm); W. 9/16 in. (1.4 cm); thickness 1/4 in. (0.6 cm); Wt. 1.2 oz. (34 g),"Purchase, Arthur Ochs Sulzberger Gift, 2004",,,,,,,,,,,,Sword Furniture-Kozuka,,http://www.metmuseum.org/art/collection/search/27692,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -43.120.293,false,true,25262,Arms and Armor,Knife handle (Kozuka),Knife Handle (Kozuka),Japanese,,,,,Maker,,Hamano Masayuki,"Japanese, 1696–1769",,Hamano Masayuki,Japanese,1696,1769,early–mid-18th century,1701,1775,"Copper, gold, copper alloy (sentoku), silver",L. 3 13/16 in. (9.7 cm); W. 9/16 in. (1.4 cm); thickness 5/16 in. (0.8 cm); Wt. 1.3 oz. (36.9 g),"Gift of Herman A. E. and Paul C. Jaehne, 1943",,,,,,,,,,,,Sword Furniture-Kozuka,,http://www.metmuseum.org/art/collection/search/25262,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2004.459a–c,false,true,27691,Arms and Armor,Pair of sword-grip ornaments (Menuki),Pair of Sword-Grip Ornaments (Menuki),Japanese,,,,,Maker,,Tsu Jumpo (Jimpo),"Japanese, 1721–1762",,Tsu Jumpo,Japanese,1721,1762,ca. 1750,1750,1750,Gold,L. of each 2 in. (5 cm); W. of each 1/2 in. (1.3 cm); Wt. of each 0.3 oz. (8.5 g),"Purchase, Arthur Ochs Sulzberger Gift, 2004",,,,,,,,,,,,Sword Furniture-Menuki,,http://www.metmuseum.org/art/collection/search/27691,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.40.913,false,true,26912,Arms and Armor,Knife handle (Kozuka),Knife Handle (Kozuka),Japanese,,,,,Maker,,Iwama Masayoshi (Shoro),"Japanese, 1764–1837",,Iwama Masayoshi (Shoro),Japanese,1764,1837,dated 1828,1828,1828,"Copper-silver alloy (shibuichi), gold, copper-gold alloy (shakudō)",L. 3 13/16 in. (9.7 cm); W. 9/16 in. (1.4 cm); thickness 1/4 in. (0.6 cm); Wt. 1.2 oz. (34 g),"Bequest of Benjamin Altman, 1913",,,,,,,,,,,,Sword Furniture-Kozuka,,http://www.metmuseum.org/art/collection/search/26912,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -43.120.300,false,true,25266,Arms and Armor,Knife handle (Kozuka),Knife Handle (Kozuka),Japanese,,,,,Maker,,Iwama Masayoshi (Shoro),"Japanese, 1764–1837",,Iwama Masayoshi (Shoro),Japanese,1764,1837,late 18th–mid-19th century,1775,1837,"Copper, silver, gold, copper-gold alloy (shakudō)",L. 3 13/16 in. (9.7 cm); W. 9/16 in. (1.4 cm); thickness 3/16 in. (0.5 cm); Wt. 1.1 oz. (31.2 g),"Gift of Herman A. E. and Paul C. Jaehne, 1943",,,,,,,,,,,,Sword Furniture-Kozuka,,http://www.metmuseum.org/art/collection/search/25266,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -43.120.456,false,true,25330,Arms and Armor,Knife handle (Kozuka),Knife Handle (Kozuka),Japanese,,,,,Maker,,Iwama Masayoshi (Shoro),"Japanese, 1764–1837",,Iwama Masayoshi (Shoro),Japanese,1764,1837,late 18th–early 19th century,1801,1900,"Copper-gold alloy (shakudō), silver, gold",L. 3 13/16 in. (9.7 cm); W. 9/16 in. (1.4 cm); thickness 1/4 in. (0.6 cm); Wt. 1.3 oz. (36.9 g),"Gift of Herman A. E. and Paul C. Jaehne, 1943",,,,,,,,,,,,Sword Furniture-Kozuka,,http://www.metmuseum.org/art/collection/search/25330,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -43.120.455,false,true,25329,Arms and Armor,Knife handle (Kozuka),Knife Handle (Kozuka),Japanese,,,,,Maker,,Hamano Naochika,"Japanese, 1784–1808",,Hamano Naochika,Japanese,1784,1808,late 18th–early 19th century,1801,1900,"Copper-gold alloy (shakudō), gold, silver, copper",L. 3 13/16 in. (9.7 cm); W. 9/16 in. (1.4 cm); thickness 1/4 in. (0.6 cm); Wt. 1.3 oz. (36.9 g),"Gift of Herman A. E. and Paul C. Jaehne, 1943",,,,,,,,,,,,Sword Furniture-Kozuka,,http://www.metmuseum.org/art/collection/search/25329,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -43.120.459,false,true,25331,Arms and Armor,Knife handle (Kozuka),Knife Handle (Kozuka),Japanese,,,,,Maker,,Furukawa Jochin,"Japanese, died ca.1750",,Furukawa Jochin,Japanese,1750,1750,early 18th century,1701,1760,Copper-silver alloy (shibuichi),L. 3 3/4 in. (9.5 cm); W. 9/16 in. (1.4 cm); thickness 3/16 in. (0.5 cm); Wt. 1.1 oz. (31.2 g),"Gift of Herman A. E. and Paul C. Jaehne, 1943",,,,,,,,,,,,Sword Furniture-Kozuka,,http://www.metmuseum.org/art/collection/search/25331,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -43.120.421,false,true,25311,Arms and Armor,Knife handle (Kozuka),Knife Handle (Kozuka),Japanese,,,,,Maker,,Katsutora,"Japanese, died ca.1825",,Katsutora,Japanese,,1825,late 18th century–early 19th century,1765,1835,"Copper-silver alloy (shibuichi), silver",L. 3 3/4 in. (9.5 cm); W. 9/16 in. (1.4 cm); thickness 3/16 in. (0.5 cm); Wt. 1.1 oz. (31.2 g),"Gift of Herman A. E. and Paul C. Jaehne, 1943",,,,,,,,,,,,Sword Furniture-Kozuka,,http://www.metmuseum.org/art/collection/search/25311,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -43.120.430,false,true,25317,Arms and Armor,Knife handle (Kozuka),Knife Handle (Kozuka),Japanese,,,,,Maker,,Hamano Haruyuki,"Japanese, died ca.1830",,Hamano Haruyuki,Japanese,1830,1830,late 18th–early 19th century,1740,1850,"Copper-silver alloy (shibuichi), gold, silver, copper, copper-gold alloy (shakudō)",L. 3 13/16 in. (9.7 cm); W. 9/16 in. (1.4 cm); thickness 3/16 in. (0.5 cm); Wt. 1.2 oz. (34 g),"Gift of Herman A. E. and Paul C. Jaehne, 1943",,,,,,,,,,,,Sword Furniture-Kozuka,,http://www.metmuseum.org/art/collection/search/25317,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -43.120.477,false,true,25339,Arms and Armor,Knife handle (Kozuka),Knife Handle (Kozuka),Japanese,,,,,Maker,,Hamano Haruchika,"Japanese, died ca.1850",,Hamano Haruchika,Japanese,1850,1850,late 18th–early 19th century,1750,1860,"Copper-silver alloy (shibuichi), gold, silver",L. 3 13/16 in. (9.7 cm); W. 9/16 in. (1.4 cm); thickness 3/16 in. (0.5 cm); Wt. 1.1 oz. (31.2 g),"Gift of Herman A. E. and Paul C. Jaehne, 1943",,,,,,,,,,,,Sword Furniture-Kozuka,,http://www.metmuseum.org/art/collection/search/25339,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -43.120.478,false,true,25340,Arms and Armor,Knife handle (Kozuka),Knife Handle (Kozuka),Japanese,,,,,Maker,,Hamano Naotsune,"Japanese, died ca.1850",,Hamano Naotsune,Japanese,,1850,late 18th–early 19th century,1750,1860,"Copper-silver alloy (shibuichi), gold, silver",L. 3 13/16 in. (9.7 cm); W. 9/16 in. (1.4 cm); thickness 3/16 in. (0.5 cm); Wt. 1.2 oz. (34 g),"Gift of Herman A. E. and Paul C. Jaehne, 1943",,,,,,,,,,,,Sword Furniture-Kozuka,,http://www.metmuseum.org/art/collection/search/25340,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -12.37.143,false,true,25647,Arms and Armor,Knife handle (Kozuka),Knife Handle (Kozuka),Japanese,,,,,Maker,,Akichika Oishi,"Japanese, active ca.1850",,Oishi Akichika,Japanese,1850,1850,late 18th–early 19th century,1800,1825,"Copper-silver alloy (shibuichi), copper-gold alloy (shakudō), gold, copper",L. 3 13/16 in. (9.7 cm); W. 9/16 in. (1.4 cm); thickness 1/4 in. (0.6 cm); Wt. 1.2 oz. (34 g),"Rogers Fund, 1912",,,,,,,,,,,,Sword Furniture-Kozuka,,http://www.metmuseum.org/art/collection/search/25647,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -32.43.1,false,true,23356,Arms and Armor,Saddle (Kura),Saddle (Kura),Japanese,,,,,Maker,Signed by,Chikara,"Japanese, active ca. 1610",,Chikara,Japanese,1585,1635,dated 1610,1610,1610,"Wood, mother-of-pearl, linen, lacquer, gold",H. 12 1/4 in. (31.1 cm); W. 15 1/2 in. (39.4 cm),"Gift of Fredrick C. MacDonell, 1932",,,,,,,,,,,,Equestrian Equipment-Saddles,,http://www.metmuseum.org/art/collection/search/23356,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -43.120.317,false,true,25270,Arms and Armor,Knife handle (Kozuka),Knife Handle (Kozuka),Japanese,,,,,Maker,,Ichijosai Hironaga (Hirotoshi),"Japanese, died ca. 1800–25",,Ichijosai Hironaga (Hirotoshi),Japanese,1800,1825,mid-18th–early 19th century,1725,1825,"Copper-silver alloy (shibuichi), gold, copper",L. 3 13/16 in. (9.7 cm); W. 9/16 in. (1.4 cm); thickness 5/16 in. (0.8 cm); Wt. 1.3 oz. (36.9 g),"Gift of Herman A. E. and Paul C. Jaehne, 1943",,,,,,,,,,,,Sword Furniture-Kozuka,,http://www.metmuseum.org/art/collection/search/25270,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -43.120.512,false,true,25349,Arms and Armor,Knife handle (Kozuka),Knife Handle (Kozuka),Japanese,,,,,Maker,,Ichijosai Hironaga (Hirotoshi),"Japanese, died ca. 1800–25",,Ichijosai Hironaga (Hirotoshi),Japanese,1800,1825,late 17th–early 18th century,1801,1900,"Copper-silver alloy (shibuichi), gold, silver, copper, copper-gold alloy (shakudō)",L. 3 13/16 in. (9.7 cm); W. 9/16 in. (1.4 cm); thickness 1/4 in. (0.6 cm); Wt. 1.1 oz. (31.2 g),"Gift of Herman A. E. and Paul C. Jaehne, 1943",,,,,,,,,,,,Sword Furniture-Kozuka,,http://www.metmuseum.org/art/collection/search/25349,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -19.115.2,true,true,22739,Arms and Armor,Mask,Mask,Japanese,,,,,Maker,Inscribed by,Myōchin Muneakira,"Japanese, Edo period, 1673–1745",,"Muneakira, Myōchin",Japanese,1673,1745,dated 1745,1745,1745,"Iron, lacquer, textile (silk)",L. 9 1/2 in. (24.1 cm); W. 7 in. (17.8 cm),"Rogers Fund, 1919",,,,,,,,,,,,Armor Parts-Masks,,http://www.metmuseum.org/art/collection/search/22739,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -13.112.20,false,true,22176,Arms and Armor,Okimono in the form of a raven,Okimono in the Form of a Raven,Japanese,,,,,Maker,,Myōchin Munesuke,"Japanese, Edo period, 1688–1735",,"Munesuke, Myōchin",Japanese,1688,1735,early 18th century,1700,1750,Steel,L. 18 in. (45.7 cm),"Rogers Fund, 1913",,,,,,,,,,,,Miscellaneous,,http://www.metmuseum.org/art/collection/search/22176,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -43.120.178,false,true,25218,Arms and Armor,Knife handle (Kozuka),Knife Handle (Kozuka),Japanese,,,,,Maker,,Gotō Eijō,"Japanese, 1577–1617, sixth-generation Gotō master",,Gotō Eijō,Japanese,1577,1617,17th century,1601,1700,"Gold, copper-gold alloy (shakudō), silver",L. 3 13/16 in. (9.7 cm); W. 9/16 in. (1.4 cm); thickness 1/4 in. (0.6 cm); Wt. 1.4 oz. (39.7 g),"Gift of Herman A. E. and Paul C. Jaehne, 1943",,,,,,,,,,,,Sword Furniture-Kozuka,,http://www.metmuseum.org/art/collection/search/25218,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -43.120.111,false,true,25189,Arms and Armor,Knife handle (Kozuka),Knife Handle (Kozuka),Japanese,,,,,Maker,,Gotō Renjō (Mitsutomo),"Japanese, 1628–1708, tenth-generation Gotō master",,"Renjō (Mitsutomo), Gotō",Japanese,1628,1708,17th century,1601,1700,"Copper-gold alloy (shakudō), gold, silver",L. 3 13/16 in. (9.7 cm); W. 9/16 in. (1.4 cm); thickness 1/4 in. (0.6 cm); Wt. 1 oz. (28.3 g),"Gift of Herman A. E. and Paul C. Jaehne, 1943",,,,,,,,,,,,Sword Furniture-Kozuka,,http://www.metmuseum.org/art/collection/search/25189,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -43.120.487,false,true,25345,Arms and Armor,Knife handle (Kozuka),Knife Handle (Kozuka),Japanese,,,,,Maker,,Gotō Renjō (Mitsutomo),"Japanese, 1628–1708, tenth-generation Gotō master",,"Renjō (Mitsutomo), Gotō",Japanese,1628,1708,17th century,1601,1700,"Copper-gold alloy (shakudō), gold",L. 3 13/16 in. (9.7 cm); W. 9/16 in. (1.4 cm); thickness 1/4 in. (0.6 cm); Wt. 1.4 oz. (39.7 g),"Gift of Herman A. E. and Paul C. Jaehne, 1943",,,,,,,,,,,,Sword Furniture-Kozuka,,http://www.metmuseum.org/art/collection/search/25345,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -43.120.341,false,true,25280,Arms and Armor,Knife handle (Kozuka),Knife Handle (Kozuka),Japanese,,,,,Maker,,Genshosai Masaharu,"Japanese, died 1724",,Genshosai Masaharu,Japanese,,1724,19th century,1801,1900,"Copper-silver alloy (shibuichi), gold, copper",L. 3 13/16 in. (9.7 cm); W. 9/16 in. (1.4 cm); thickness 1/4 in. (0.6 cm); Wt. 1.2 oz. (34 g),"Gift of Herman A. E. and Paul C. Jaehne, 1943",,,,,,,,,,,,Sword Furniture-Kozuka,,http://www.metmuseum.org/art/collection/search/25280,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.120.372,false,true,26151,Arms and Armor,Knife handle (Kozuka),Knife Handle (Kozuka),Japanese,,,,,Maker,,Hamano Toshihiro,"Japanese, died 1861",,Hamano Toshihiro,Japanese,,1861,19th century,1801,1900,"Copper-silver alloy (shibuichi), gold, silver, copper-gold alloy (shakudō)",L. 3 13/16 in. (9.7 cm); W. 9/16 in. (1.4 cm); thickness 3/16 in. (0.5 cm); Wt. 1.2 oz. (34 g),"The Howard Mansfield Collection, Gift of Howard Mansfield, 1936",,,,,,,,,,,,Sword Furniture-Kozuka,,http://www.metmuseum.org/art/collection/search/26151,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -43.120.426,false,true,25314,Arms and Armor,Knife handle (Kozuka),Knife Handle (Kozuka),Japanese,,,,,Maker,,Hamano Toshihiro,"Japanese, died 1861",,Hamano Toshihiro,Japanese,,1861,19th century,1801,1900,"Copper-silver alloy (shibuichi), gold, silver, copper",L. 3 13/16 in. (9.7 cm); W. 9/16 in. (1.4 cm); thickness 1/4 in. (0.6 cm); Wt. 1.2 oz. (34 g),"Gift of Herman A. E. and Paul C. Jaehne, 1943",,,,,,,,,,,,Sword Furniture-Kozuka,,http://www.metmuseum.org/art/collection/search/25314,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -43.120.569,false,true,25733,Arms and Armor,Knife handle (Kozuka),Knife Handle (Kozuka),Japanese,,,,,Maker,,Joken Mori,"Japanese, died 1866",,Mori Joken,Japanese,,1866,19th century,1801,1900,"Copper-silver alloy (shibuichi), iron",L. 3 13/16 in. (9.7 cm); W. 9/16 in. (1.4 cm); thickness 3/16 in. (0.5 cm); Wt. 1 oz. (28.3 g),"Gift of Herman A. E. and Paul C. Jaehne, 1943",,,,,,,,,,,,Sword Furniture-Kozuka,,http://www.metmuseum.org/art/collection/search/25733,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -43.120.439,false,true,25319,Arms and Armor,Knife handle (Kozuka),Knife Handle (Kozuka),Japanese,,,,,Maker,,Gotō Ichijō,"Japanese, 1791–1876",,Gotō Ichijō,Japanese,1791,1876,19th century,1801,1900,"Copper-gold alloy (shakudō), gold, silver, copper",L. 3 13/16 in. (9.7 cm); W. 9/16 in. (1.4 cm); thickness 3/16 in. (0.5 cm); Wt. 1.1 oz. (31.2 g),"Gift of Herman A. E. and Paul C. Jaehne, 1943",,,,,,,,,,,,Sword Furniture-Kozuka,,http://www.metmuseum.org/art/collection/search/25319,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -43.120.318,false,true,25271,Arms and Armor,Knife handle (Kozuka),Knife Handle (Kozuka),Japanese,,,,,Maker,,Tanaka Kiyotoshi (Kiyonaga),"Japanese, 1804–1876",,Tanaka Kiyotoshi (Kiyonaga) Ryuso,Japanese,1804,1876,19th century,1801,1876,"Copper-silver alloy (shibuichi), gold, copper-gold alloy (shakudō)",L. 3 13/16 in. (9.7 cm); W. 9/16 in. (1.4 cm); thickness 1/4 in. (0.6 cm); Wt. 1.3 oz. (36.9 g),"Gift of Herman A. E. and Paul C. Jaehne, 1943",,,,,,,,,,,,Sword Furniture-Kozuka,,http://www.metmuseum.org/art/collection/search/25271,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -43.120.182,false,true,25220,Arms and Armor,Knife handle (Kozuka),Knife Handle (Kozuka),Japanese,,,,,Maker,,Iwama Nobuyoshi,"Japanese, 1807–1878",,Iwama Nobuyoshi,Japanese,1807,1878,19th century,1801,1900,"Copper-silver alloy (shibuichi), silver, gold",L. 3 13/16 in. (9.7 cm); W. 9/16 in. (1.4 cm); thickness 1/4 in. (0.6 cm); Wt. 1.2 oz. (34 g),"Gift of Herman A. E. and Paul C. Jaehne, 1943",,,,,,,,,,,,Sword Furniture-Kozuka,,http://www.metmuseum.org/art/collection/search/25220,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -43.120.366,false,true,25286,Arms and Armor,Knife handle (Kozuka),Knife Handle (Kozuka),Japanese,,,,,Maker,,Iwama Nobuyoshi,"Japanese, 1807–1878",,Iwama Nobuyoshi,Japanese,1807,1878,19th century,1801,1900,"Copper-silver alloy (shibuichi), gold, silver",L. 3 13/16 in. (9.7 cm); W. 9/16 in. (1.4 cm); thickness 1/4 in. (0.6 cm); Wt. 1.2 oz. (34 g),"Gift of Herman A. E. and Paul C. Jaehne, 1943",,,,,,,,,,,,Sword Furniture-Kozuka,,http://www.metmuseum.org/art/collection/search/25286,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -43.120.56,false,true,25180,Arms and Armor,Knife handle (Kozuka),Knife Handle (Kozuka),Japanese,,,,,Maker,,Funada Yoshinaga,"Japanese, 1812–1863",,Funada Yoshinaga,Japanese,1812,1863,19th century,1801,1900,"Copper-silver alloy (shibuichi), copper, gold, silver",L. 3 3/4 in. (9.5 cm); W. 9/16 in. (1.4 cm); thickness 1/4 in. (0.6 cm); Wt. 1.2 oz. (34 g),"Gift of Herman A. E. and Paul C. Jaehne, 1943",,,,,,,,,,,,Sword Furniture-Kozuka,,http://www.metmuseum.org/art/collection/search/25180,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.120.237,false,true,26117,Arms and Armor,Knife handle (Kozuka),Knife Handle (Kozuka),Japanese,,,,,Maker,,Hamano Haruchika,"Japanese, died ca.1850",,Hamano Haruchika,Japanese,1850,1850,19th century,1801,1900,"Copper-silver alloy (shibuichi), gold, copper, copper-gold alloy (shakudō)",L. 3 13/16 in. (9.7 cm); W. 9/16 in. (1.4 cm); thickness 1/4 in. (0.6 cm); Wt. 1.2 oz. (34 g),"The Howard Mansfield Collection, Gift of Howard Mansfield, 1936",,,,,,,,,,,,Sword Furniture-Kozuka,,http://www.metmuseum.org/art/collection/search/26117,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -43.120.240,false,true,25244,Arms and Armor,Knife handle (Kozuka),Knife Handle (Kozuka),Japanese,,,,,Maker,,Hamano Haruchika,"Japanese, died ca.1850",,Hamano Haruchika,Japanese,1850,1850,19th century,1801,1900,"Copper-silver alloy (shibuichi), gold, copper-gold alloy (shakudō)",L. 3 13/16 in. (9.7 cm); W. 9/16 in. (1.4 cm); thickness 3/16 in. (0.5 cm); Wt. 1 oz. (28.3 g),"Gift of Herman A. E. and Paul C. Jaehne, 1943",,,,,,,,,,,,Sword Furniture-Kozuka,,http://www.metmuseum.org/art/collection/search/25244,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -43.120.385,false,true,25297,Arms and Armor,Knife handle (Kozuka),Knife Handle (Kozuka),Japanese,,,,,Maker,,Hamano Haruchika,"Japanese, died ca.1850",,Hamano Haruchika,Japanese,1850,1850,19th century,1801,1900,"Copper-gold alloy (shakudō), iron, gold, silver, copper, brass",L. 3 13/16 in. (9.7 cm); W. 9/16 in. (1.4 cm); thickness 3/16 in. (0.5 cm); Wt. 1.1 oz. (31.2 g),"Gift of Herman A. E. and Paul C. Jaehne, 1943",,,,,,,,,,,,Sword Furniture-Kozuka,,http://www.metmuseum.org/art/collection/search/25297,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -43.120.184,false,true,25221,Arms and Armor,Knife handle (Kozuka),Knife Handle (Kozuka),Japanese,,,,,Maker,,Marukawa Hiroyoshi,"Japanese, died 1841 or 1842",,Marukawa Hiroyoshi,Japanese,1841,1841,19th century,1801,1900,"Copper-silver alloy (shibuichi), silver, gold",L. 3 13/16 in. (9.7 cm); W. 9/16 in. (1.4 cm); thickness 1/4 in. (0.6 cm); Wt. 1.1 oz. (31.2 g),"Gift of Herman A. E. and Paul C. Jaehne, 1943",,,,,,,,,,,,Sword Furniture-Kozuka,,http://www.metmuseum.org/art/collection/search/25221,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -43.120.393,false,true,25301,Arms and Armor,Knife handle (Kozuka),Knife Handle (Kozuka),Japanese,,,,,Maker,,Ichijosai Hironaga (Hirotoshi),"Japanese, died ca. 1800–25",,Ichijosai Hironaga (Hirotoshi),Japanese,1800,1825,19th century,1801,1900,"Copper-silver alloy (shibuichi), gold, silver, copper, copper-gold alloy (shakudō)",L. 3 13/16 in. (9.7 cm); W. 9/16 in. (1.4 cm); thickness 3/16 in. (0.5 cm); Wt. 1.2 oz. (34 g),"Gift of Herman A. E. and Paul C. Jaehne, 1943",,,,,,,,,,,,Sword Furniture-Kozuka,,http://www.metmuseum.org/art/collection/search/25301,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -43.120.214,false,true,25235,Arms and Armor,Knife handle (Kozuka),Knife Handle (Kozuka),Japanese,,,,,Maker,,Ichiyosai Hironao,"Japanese, died ca. 1825–50",,Ichiyosai Hironao,Japanese,1825,1850,19th century,1801,1900,"Copper-silver alloy (shibuichi), gold, silver",L. 3 13/16 in. (9.7 cm); W. 9/16 in. (1.4 cm); thickness 1/4 in. (0.6 cm); Wt. 1.2 oz. (34 g),"Gift of Herman A. E. and Paul C. Jaehne, 1943",,,,,,,,,,,,Sword Furniture-Kozuka,,http://www.metmuseum.org/art/collection/search/25235,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -43.120.337,false,true,25279,Arms and Armor,Knife handle (Kozuka),Knife Handle (Kozuka),Japanese,,,,,Maker,,Ichiyosai Hironao,"Japanese, died ca. 1825–50",,Ichiyosai Hironao,Japanese,1825,1850,19th century,1801,1900,"Copper-silver alloy (shibuichi), gold, copper, silver, copper-gold alloy (shakudō)",L. 3 13/16 in. (9.7 cm); W. 9/16 in. (1.4 cm); thickness 1/4 in. (0.6 cm); Wt. 1.2 oz. (34 g),"Gift of Herman A. E. and Paul C. Jaehne, 1943",,,,,,,,,,,,Sword Furniture-Kozuka,,http://www.metmuseum.org/art/collection/search/25279,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -43.120.451,false,true,25325,Arms and Armor,Knife handle (Kozuka),Knife Handle (Kozuka),Japanese,,,,,Maker,,Gotō Mitsuyoshi (Shinjō),"Japanese, 1780–1843, fifteenth-generation Gotō master",,Gotō Mitsuyoshi (Shinjō),Japanese,1780,1843,19th century,1801,1900,"Copper-gold alloy (shakudō), gold",L. 3 13/16 in. (9.7 cm); W. 9/16 in. (1.4 cm); thickness 1/4 in. (0.6 cm); Wt. 1.4 oz. (39.7 g),"Gift of Herman A. E. and Paul C. Jaehne, 1943",,,,,,,,,,,,Sword Furniture-Kozuka,,http://www.metmuseum.org/art/collection/search/25325,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -43.120.529,false,true,25353,Arms and Armor,Knife handle (Kozuka),Knife Handle (Kozuka),Japanese,,,,,Maker,,Hamano Naoyuki,"Japanese, 1754–1795",,Hamano Naoyuki,Japanese,1754,1795,late 18th century,1775,1799,"Copper-silver alloy (shibuichi), gold",L. 3 13/16 in. (9.7 cm); W. 9/16 in. (1.4 cm); thickness 3/16 in. (0.5 cm); Wt. 1.1 oz. (31.2 g),"Gift of Herman A. E. and Paul C. Jaehne, 1943",,,,,,,,,,,,Sword Furniture-Kozuka,,http://www.metmuseum.org/art/collection/search/25353,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -43.120.253,false,true,25251,Arms and Armor,Knife handle (Kozuka),Knife Handle (Kozuka),Japanese,,,,,Maker,,Hamano Haruchika,"Japanese, died ca.1850",,Hamano Haruchika,Japanese,1850,1850,early 19th century,1801,1850,"Copper-silver alloy (shibuichi), gold",L. 3 13/16 in. (9.7 cm); W. 9/16 in. (1.4 cm); thickness 3/16 in. (0.5 cm); Wt. 1.1 oz. (31.2 g),"Gift of Herman A. E. and Paul C. Jaehne, 1943",,,,,,,,,,,,Sword Furniture-Kozuka,,http://www.metmuseum.org/art/collection/search/25251,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -43.120.391,false,true,25300,Arms and Armor,Knife handle (Kozuka),Knife Handle (Kozuka),Japanese,,,,,Maker,,Marukawa Hiroyoshi,"Japanese, died 1841 or 1842",,Marukawa Hiroyoshi,Japanese,1841,1841,early 19th century,1801,1850,"Copper-silver alloy (shibuichi), gold, silver, copper-gold alloy (shakudō)",L. 3 3/4 in. (9.5 cm); W. 9/16 in. (1.4 cm); thickness 1/4 in. (0.6 cm); Wt. 1.1 oz. (31.2 g),"Gift of Herman A. E. and Paul C. Jaehne, 1943",,,,,,,,,,,,Sword Furniture-Kozuka,,http://www.metmuseum.org/art/collection/search/25300,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -43.120.356,false,true,25282,Arms and Armor,Knife handle (Kozuka),Knife Handle (Kozuka),Japanese,,,,,Maker,,Ichijosai Hironaga (Hirotoshi),"Japanese, died ca. 1800–25",,Ichijosai Hironaga (Hirotoshi),Japanese,1800,1825,early 19th century,1801,1850,"Copper-silver alloy (shibuichi), copper, gold",L. 3 13/16 in. (9.7 cm); W. 9/16 in. (1.4 cm); thickness 3/16 in. (0.5 cm); Wt. 1.2 oz. (34 g),"Gift of Herman A. E. and Paul C. Jaehne, 1943",,,,,,,,,,,,Sword Furniture-Kozuka,,http://www.metmuseum.org/art/collection/search/25282,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -11.105,false,true,22137,Arms and Armor,Okimono in the form of an eagle,Eagle with Outstreched Wings,Japanese,,,,,Artist,,Suzuki Chōkichi,"Japanese, 1848–1919",,"Japanese Chōkichi, Suzuki",Japanese,1848,1919,late 19th century,1850,1900,"Iron, pigment, shakudo, shibuichi, wood","H. (without base), 17 in. (43.2 cm); W. of wingspan, 55 in. (139.7 cm)","Gift of James R. Steers, 1911",,,,,,,,,,,,Miscellaneous-Ironwork,,http://www.metmuseum.org/art/collection/search/22137,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"1997.194.1, .2",false,true,24964,Arms and Armor,Stirrups (Abumi),Stirrups (Abumi),Japanese,,,,,Artist,,Ujiyoshi,"Japanese, active 18th century",,Ujiyoshi,Japanese,0018,0018,18th century,1701,1800,"Iron, silver",H. of each 10 in. (25.4 cm); L. of each 11 1/4 in. (28.6 cm); W. of each 5 1/4 in. (13.3 cm); Wt. of each 5 lb. 6 oz. (2438 g),"Purchase, Gift of Estate of James Hazen Hyde, by exchange, 1997",,,,,,,,,,,,Equestrian Equipment-Stirrups,,http://www.metmuseum.org/art/collection/search/24964,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"04.3.195, .196",false,true,21924,Arms and Armor,Pair of flintlock pistols,"Pair of Flintlock Pistols Made for Christian Ernst, Margrave of Brandenburg-Ansbach-Bayreuth-Kulmbach","Franco-German, Erlangen-Neustadt",,,,,Gunsmith|Barrelsmith,,Charles Froment|M. Bréat,"French, active Germany, 1657–1722|French, active Germany, ca. 1680–90",,"Froment, Charles|Bréat, M.",French|French,1657 |1680,1722 |1680,ca. 1686–90,1661,1715,"Steel, wood, silver",L. of each 19 3/4 in. (50.2 cm); L. of each barrel 12 5/8 in. (32.1 cm); L. of each plug 2 3/8 in. (6 cm); Diam. at muzzle of each 5/8 in. (1.6 cm); Diam. at breech of each 1 in. (2.5 cm); L. of each lock 5 in. (12.7 cm); Cal. of each .545 in. (13.8 mm); Wt. of each 2 lb. 2 oz. (950 g),"Rogers Fund, 1904",,Erlangen,,,,,,,,,,Firearms-Pistols-Flintlock,,http://www.metmuseum.org/art/collection/search/21924,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2014.595,false,true,667967,Arms and Armor,Smallsword,Smallsword Presented by the City of Paris to Commandant Ildefonse Favé (1812–1894),French,,,,,Hilt Maker|Manufacturer,,Paul Bled|Lepage-Moutier,"French, Falaise 1807–1881|French 1842–1868",,"Bled, Paul|Lepage-Moutier",French|French,1807 |1842,1881 |1868,dated 1856,1831,1881,"Steel, gold",L. 36 in. (91.5 cm); L. of blade 30 1/8 in. (76.6 cm); W. 3 1/4 in. (8.3 cm); Wt. 1 lb. 0.6 oz. (470.6 g),"Gift of Peter Finer, in honor of Stuart Pyhrr, 2014",,,,,,,,,,,,Swords,,http://www.metmuseum.org/art/collection/search/667967,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1991.5,false,true,24946,Arms and Armor,Double-barreled percussion shotgun,Double-Barreled Percussion Shotgun,"French, Paris",,,,,Barrelsmith|Gunsmith,,Léopold Bernard|Louis Perrin,"French, Paris, active 1832–70|French, Paris, active 1823–65",,"Bernard, Léopold|Perrin Louis",French|French,1832 |1823,1870 |1865,dated 1854,1854,1854,"Steel, wood (walnut), silver",L. 46 3/5 in. (118.4 cm); L. of barrel 29 1/8 in. (73.9 cm),"Purchase, Rogers Fund, The Sulzberger Foundation Inc. Gift, Gifts of William H. Riggs, Bill and Joyce Anderson, Charles M. Schott Jr., Mr. and Mrs. Robert W. de Forest, William B. Osgood Field, Christian A. Zabriskie, Dr. Albert T. Weston, Henry Victor Burgy, and Bequest of Alan Rutherfurd Stuyvesant, by exchange, and The Collection of Giovanni P. Morosini, presented by his daughter Giulia, John Stoneacre Ellis Collection, Gift of Mrs. Ellis and Augustus Van Horne Ellis, and Bashford Dean Memorial Collection, funds from various donors, by exchange, 1991",,Paris,,,,,,,,,,Firearms,,http://www.metmuseum.org/art/collection/search/24946,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1990.114.1,false,true,24942,Arms and Armor,Flintlock gun,Flintlock Gun,"French, Paris",,,,,Barrelsmith|Gunsmith,,Le Faure|Mollier,"French, Paris, active ca. 1750–90|French, Paris, active ca. 1750",,Le Faure|Mollier,French|French,1725 |1725,1815 |1775,ca. 1750,1725,1775,"Steel, gold, wood (walnut), silver",L. 58 in. (147.3 cm),"Purchase, Annie Laurie Aitken Charitable Trust Gift, 1990",,Paris,,,,,,,,,,Firearms,,http://www.metmuseum.org/art/collection/search/24942,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -27.203,false,true,23015,Arms and Armor,Double-barreled flintlock shotgun,Double-Barreled Flintlock Shotgun,"French, Paris",,,,,Gunsmith|Barrelsmith,,François Pirmet|Jean Le Clerc,"French, Paris, recorded 1779–1818|French, Paris, recorded 1807–10",,"Pirmet, François|Le Clerc, Jean",French|French,1779 |1807,1818 |1810,dated 1809,1809,1809,"Steel, wood (walnut), silver, gold",L. 48 in. (121.9 cm); L. of barrel 32 1/2 in. (82.6 cm); L. of lock 5 1/4 in. (13.3 cm); Cal. .59 in. (15.1 mm); Wt. 6 lb. 10 oz. (3000 g),"Rogers Fund, 1927",,Paris,,,,,,,,,,Firearms,,http://www.metmuseum.org/art/collection/search/23015,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1982.136,false,true,24891,Arms and Armor,Hunting sword,Hunting Sword of Prince Camillo Borghese (1775–1832),"French, Paris",,,,,Goldsmith|Maker,,Antoine-Modeste Fournera|François Pirmet,"French, Paris, documented 1806–17|French, Paris, recorded 1779–1818",,"Fournera, Antoine-Modeste|Pirmet, François",French|French,1806 |1779,1817 |1818,1809–13,1809,1813,"Silver-gilt, steel, leather, mother-of-pearl",L. with scabbard 27 in. (68.6 cm); Wt. with scabbard 1 lb. 10 oz. (737 g); L. of sword 25 7/16 in. (64.6 cm); W. of hilt 4 5/8 in. (11.7 cm); L. of blade 19 3/4 in. (50.2 cm); W. of blade 1 3/16 in. (3 cm); L. of scabbard 21 3/8 in. (54.3 cm),"Purchase, The Sulzberger Foundation, Inc. and David G. Alexander Gifts, and Bequest of Stephen V. Grancsay, by exchange, 1982",,Paris,,,,,,,,,,Swords-Hunting,,http://www.metmuseum.org/art/collection/search/24891,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.229,false,true,27164,Arms and Armor,Sword hilt,Sword Hilt,"French, Paris",,,,,Designer|Goldsmith,Designed and modeled by|Executed by,Albert-Ernest Carrier-Belleuse|Lucien Falize,"French, Anizy-le-Château 1824–1887 Sèvres|French, Paris, 1842–1897",,"Carrier-Belleuse, Albert Ernest|Falize, Lucien",French|French,1824 |1842,1887 |1897,1881–82,1881,1882,"Bronze, gold",H. 6 1/2 in. (16.5 cm); W. 5 3/16 in. (13.2 cm); D. 4 1/2 in. (11.4 cm); Wt. 1 lb. 12 oz. (799.5 g),"Purchase, Gift of William H. Riggs, by exchange, 1989",,Paris,,,,,,,,,,Swords,,http://www.metmuseum.org/art/collection/search/27164,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.905,false,true,625352,Arms and Armor,Ornament print from a firearms pattern book,Plate twelve from Nouveavx Desseins D'Arquebvseries,"French, Paris",,,,,Engraver|Publisher,Published by,De Lacollombe|Gilles Demarteau,"French, Paris, active ca. 1702–ca. 1736|French, Liège 1722–1776 Paris",,"Lacollombe, De|Demarteau, Gilles",French|French,1702 |1722,1736 |1776,dated 1736,1736,1736,Engraving,7 1/2 x 9 3/8 in. (19 x 23.8 cm),"Purchase, Mr. and Mrs. Robert G. Goelet Gift, 2013",,Paris,,,,,,,,,,Works on Paper,,http://www.metmuseum.org/art/collection/search/625352,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.906,false,true,625353,Arms and Armor,Ornament print from a firearms pattern book,Plate three from Nouveavx Desseins D'Arquebvseries,"French, Paris",,,,,Engraver|Publisher,,De Lacollombe|Gilles Demarteau,"French, Paris, active ca. 1702–ca. 1736|French, Liège 1722–1776 Paris",,"Lacollombe, De|Demarteau, Gilles",French|French,1702 |1722,1736 |1776,ca. 1705–30,1680,1755,Engraving,10 1/2 x 7 1/2 in. (26.7 x 19.1 cm),"Purchase, Bequest of Stephen V. Grancsay, Rogers Fund, Helmut Nickel Gift, and funds from various donors, by exchange, 2013",,Paris,,,,,,,,,,Works on Paper,,http://www.metmuseum.org/art/collection/search/625353,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.907,false,true,625354,Arms and Armor,Ornament print from a firearms pattern book,Plate seven from Nouveavx Desseins D'Arquebvseries,"French, Paris",,,,,Engraver|Publisher,,De Lacollombe|Gilles Demarteau,"French, Paris, active ca. 1702–ca. 1736|French, Liège 1722–1776 Paris",,"Lacollombe, De|Demarteau, Gilles",French|French,1702 |1722,1736 |1776,ca. 1705–30,1680,1755,Engraving,11 x 8 1/4 in. (27.9 x 20.9 cm),"Purchase, Bequest of Stephen V. Grancsay, Rogers Fund, Helmut Nickel Gift, and funds from various donors, by exchange, 2013",,Paris,,,,,,,,,,Works on Paper,,http://www.metmuseum.org/art/collection/search/625354,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"04.3.64, .65; 14.25.454",false,true,626023,Arms and Armor,Three partisans,"Three Partisans Carried by the Bodyguard of Louis XIV (1638–1715, reigned from 1643)","French, Paris",,,,,Designer|Sword cutler,14.25.454 designed by|Inscription on 04.3.65 probably refers to,Jean Berain|Bonaventure Ravoisie,"French, Saint-Mihiel 1640–1711 Paris|French, Paris, recorded 1678–1709",,"Berain, Jean|Ravoisie, Bonaventure",French|French,1640 |1678,1711 |1709,ca. 1670–80,1645,1705,"Steel, blued and damascened with gold; wood; textile","04.3.64: Head, 20 3/4 x 5 7/8 in. (52.7 x 15 cm); Overall, 94 3/8 in. (239.7 cm); 04.3.65: Head, 22 9/16 x 6 1/2 in. (57.3 x 16.5 cm); Overall, 94 1/8 in. (239 cm); Head, 20 9/16 x 6 1/16 in. (52.2 x 15.4 cm); Overall, 86 11/16 in. (220.2 cm)","04.3.64, .65: Rogers Fund, 1904; 14.25.454: Gift of William H. Riggs, 1913",,,,,,,,,,,,Shafted Weapons,,http://www.metmuseum.org/art/collection/search/626023,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"28.196.1a, b, .2a–l",false,true,23052,Arms and Armor,Cased pair of flintlock pistols with accessories,Cased Pair of Flintlock Pistols with Accessories,"French, Paris",,,,,Lock maker|Mount maker,Silver mounts hallmarked by,François Pirmet|Nicolas Noël Boutet,"French, Paris, recorded 1779–1818|French, Versailles and Paris, 1761–1833",,"Pirmet, François|Boutet, Nicholas-Noël",French|French,1779 |1761,1818 |1833,ca. 1810,1785,1835,"Steel, gold, wood (walnut, mahogany), silver, tortoiseshell, velvet","L. of each pistol 15 1/4 in. (38.7 cm); L. of each barrel 9 3/8 in. (23.8 cm); Cal. of barrel 28.196.1a, .48 in. (12 mm); Cal. of barrel 28.196.1b, .49 in. (12 mm); Wt. of each pistol 2 lb. 5 oz. (1050 g)","Rogers Fund, 1928",,Paris,,,,,,,,,,Firearms-Pistols,,http://www.metmuseum.org/art/collection/search/23052,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"04.3.192, .193",false,true,21927,Arms and Armor,Pair of wheellock pistols,Pair of Wheellock Pistols,"French, Paris",,,,,Gunsmith|Engraver,Gilt foliate decoration attributed to,François Du Clos|Thomas Picquot,"French, Paris, recorded 1636–active ca. 1650|French, Paris, recorded 1636–38",,"Du Clos, François|Picquot, Thomas",French|French,1636 |1636,1675 |1638,ca. 1640,1615,1665,"Steel, gold, brass, wood, silver, mother-of-pearl",04.3.192; L. 23 1/8 in. (58.7 cm); L. of barrel 15 1/2 in. (39.4 cm); Cal. .52 in. (13.2 mm); Wt. 2 lb. 3 oz. (992 g) 04.3.193; L. 23 1/4 in. (59.1 cm); L. of barrel 15 1/2 in. (39.4 cm); Cal. .53 in. (13.5 mm); Wt. 2 lb. 3 oz. (992 g),"Rogers Fund, 1904",,Paris,,,,,,,,,,Firearms-Pistols-Wheellock,,http://www.metmuseum.org/art/collection/search/21927,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -28.196.5–.6a–c,false,true,23050,Arms and Armor,Cased pair of double-barreled turn-off flintlock pistols,Cased Pair of Double-Barreled Turn-Off Flintlock Pistols,"French, Paris",,,,,Gunsmith|Engraver,Possibly,Jean Lepage|Fleury Montagny,"French, Paris, 1746–1834|French, born St. Étienne, February 4, 1760–died 1836, Marseilles",,"Lepage, Jean|Montagny, Fleury",French|French,1746 |1760,1834 |1836,ca. 1800,1775,1825,"Steel, wood (boxwood), brass, velvet",L. of each pistol 8 in. (20.3 cm); L. of each barrel 3 7/8 in. (9.8 cm); Cal. of each .46 in. (11.7 mm); Wt. of each pistol 1 lb. 5 oz. (600 g); bullet mould (28.196.6a); L. 5 3/4 in. (14.6 cm); Wt. 4.3 oz. (121.9 g); wrench (28.196.6b); L. 3 7/8 in. (9.8 cm); Wt. 2.6 oz. (73.7 g); case (28.196.6c); H. 3 7/16 in. (8.7 cm); W. 11 1/2 in. (29.2 cm); D. 6 7/8 in. (17.5 cm); Wt. 2 lb. 15 oz. (1332.4 g),"Rogers Fund, 1928",,Paris,,,,,,,,,,Firearms-Pistols-Flintlock,,http://www.metmuseum.org/art/collection/search/23050,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -54.173,false,true,27552,Arms and Armor,Drawing,Design for the Right Pauldron of a Parade Armor,"French, Paris",,,,,Artist|Artist,or attributed to|Attributed to,Jean Cousin the Elder|Étienne Delaune,"French, Souci (?) ca. 1490–ca. 1560 Paris (?)|French, Orléans 1518/19–1583 Strasbourg",,"Cousin, Jean, the Elder|Delaune, Étienne",French|French,1485 |1518,1565 |1583,ca. 1555,1530,1580,Pen and ink with watercolor wash on paper,10 x 6 9/16 in. (25.4 x 16.6 cm),"Rogers Fund, 1954",,Paris,,,,,,,,,,Works on Paper-Drawings,,http://www.metmuseum.org/art/collection/search/27552,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.104,false,true,23943,Arms and Armor,Detached flintlock,Detached Flintlock alla Fiorentina,"Italian, Bargi",,,,,Gunsmith|Gunsmith,or,"Matteo Cecchi, called Acquafresca|Sebastiano Cecchi, called Acquafresca","Italian, Bargi, 1651–1738|Italian, Bargi, 1619–1692",,"Cecchi, Matteo|Cecchi, Sebastiano",Italian|Italian,1651 |1619,1738 |1692,dated 1679,1679,1679,Steel,H. 2 3/4 in. (7.0 cm); W. 5 3/4 in. (14.6 cm),"Rogers Fund, 1933",,Bargi,,,,,,,,,,Firearms-Pistols-Snaphaunce,,http://www.metmuseum.org/art/collection/search/23943,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.150.9,false,true,26585,Arms and Armor,Composed armor for man and horse,Composed Armor for Man and Horse,"European, Italy, Spain, Flanders and England",,,,,Armorer|Armorer,Left reinforcing elbow stamped with marks belonging to the|Shaffron stamped with marks attributed to,Missaglia workshop|Ambrogio de Osma,"Italian, Milan, recorded 1430–1529|Italian, Brescia, documented 1446–75",,"Missaglia workshop|Osma, Ambrogio de",Italian|Italian,1430 |1446,1529 |1475,ca. 1450–1525 and later,1425,1550,"Steel, brass, leather, textile (velvet, wool), iron",Wt. of armor for man 59 lb. 4 oz. (26.88 kg); Wt. of helmet 3 lb. 12 oz. (1697 g); 29.150.9v (mail shirt): H. 31 1/2 in. (80.0 cm); W. 45 11/16 in. (116.0 cm); W. of chest 24 in. (61.0 cm); Diam. (outside) of solid links 13/32 in. (10.2 mm); Diam. (inside) of solid links 9/32 in. (7.3 mm); Diam. (outside) of riveted links 3/8 in. (9.5 mm); Diam. (inside) of riveted links 9/32 in. (7.0 mm); Diam. (outside) of latten solid links 3/8 in. (9.1 mm); Diam. (inside) of latten solid links 1/4 in. (6.4 mm); Diam. (outside) of latten riveted links 3/8 in. (9.3 mm); Diam. (inside) of lattened riveted links 5/16 in. (7.8 mm); Diam. (outside) of collar links 11/32 in. (8.5 mm); Diam. (inside) of collar links 1/4 in. (6.2 mm).,"Bashford Dean Memorial Collection, Bequest of Bashford Dean, 1928",,,,,,,,,,,,Armor for Horse and Man,,http://www.metmuseum.org/art/collection/search/26585,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -49.163.5,false,true,24811,Arms and Armor,Carbine,Carbine with Flintlock alla Fiorentina,"Italian, Brescia",,,,,Steel-chiseler|Barrelsmith,,Carlo Bottarelli|Giovanni Lazzarino Cominazzo,"Italian, Brescia, active ca. 1660–90|Italian, Brescia, active mid-17th century",,"Bottarelli, Carlo|Cominazzo, Giovanni Lazzarino",Italian|Italian,1635 |1625,1715 |1675,ca. 1660–70,1635,1695,"Steel, wood (walnut)",L. 37 3/4 in. (95.9 cm),"Gift of Alan Rutherfurd Stuyvesant, 1949",,Brescia,,,,,,,,,,Firearms,,http://www.metmuseum.org/art/collection/search/24811,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"47.110.1, .2",false,true,24784,Arms and Armor,Pair of wheellock pistols,Pair of Wheellock Pistols,"Italian, Brescia",,,,,Lock maker|Barrelsmith,Pistols made and decorated by,Giovan Antonio Gavacciolo|Lazarino Cominazzo,"Italian, Brescia, active mid-17th century|Italian, Brescia, active mid-17th century",,"Gavacciolo, Giovan Antonio|Cominazzo, Lazarino",Italian|Italian,1625 |1625,1675 |1675,mid-17th century,1625,1675,"Steel, wood (walnut)",L. of 47.110.1: 22 11/16 in. (57.7 cm); Cal. of 47.110.1: .495 in. (12.6 mm); Wt. of 47.110.1: 2 lb. 3 oz. (997 g); L. of 47.110.2: 22 13/16 in. (57.9 cm); Cal. of 47.110.2: .485 in. (12.3 mm); Wt. of 47.110.2: 2 lb. 3 oz. (1003 g),"Rogers Fund, 1947",,Brescia,,,,,,,,,,Firearms-Pistols-Wheellock,,http://www.metmuseum.org/art/collection/search/24784,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1990.137a–j,false,true,24941,Arms and Armor,Percussion revolver with case,"Cased Six-Barreled Revolving Percussion Pistol (""Pepperbox"")","American, Norwich, Connecticut",,,,,Manufacturer|Manufacturer,,Ethan Allen|Charles T. Thurber,"American, 1808–1871|American, active 1837–55",,"Allen|Thurber, Charles T.",American|American,1808 |1837,1871 |1855,1842–47,1842,1847,"Steel, gold, silver, ivory, wood (rosewood), velvet",L. of pistol 7 in. (17.78 cm); case 12 15/16 x 8 3/16 x 2 5/8 in. (32.84 x 20.78 x 6.65 cm),"Gift of Eric Vaule, 1990",,Norwich,Connecticut,,,,,,,,,Firearms-Pistols-Percussion,,http://www.metmuseum.org/art/collection/search/24941,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1992.375,false,true,26562,Arms and Armor,Percussion target rifle,Percussion Target Rifle,"American, New Bedford, Massachusetts",,,,,Gunsmith|Gunsmith,,Julius Grudchos|Selmar Eggers,"American, active ca. 1856–1860|American, active ca. 1856–1860",,Grudchos Julius|Eggers Selmar,American|American,1856 |1856,1860 |1860,ca. 1855–60,1830,1885,"Wood (walnut), steel, silver, gold, baleen, ivory",L. 49 1/4 in. (125.1 cm),"Purchase, Bashford Dean Memorial Collection, Funds from various donors, by exchange, 1992",,New Bedford,Massachusetts,,,,,,,,,Firearms-Guns-Percussion,,http://www.metmuseum.org/art/collection/search/26562,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -59.143.3,false,true,24847,Arms and Armor,Percussion revolver,"Colt Navy Percussion Revolver, Confederate Model, serial no. 2651","American, Griswoldville, Georgia",,,,,Designer|Manufacturer,,Samuel Colt|The Griswold and Grier Company,"American, Hartford, Connecticut 1814–1862|American, 19th century",,"Colt, Samuel|The Griswold and Grier Company",American|American,1814 |1801,1862 |1900,1862–64,1862,1864,"Steel, brass, wood",L. 13 1/8 in. (33.3 cm); L. of barrel 7 1/2 in. (19.1 cm); Cal. .36 in. (9 mm),"Gift of John E. Parsons, 1959",,Griswoldville,Georgia,,,,,,,,,Firearms-Pistols-Revolvers,,http://www.metmuseum.org/art/collection/search/24847,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2014.699,false,true,675957,Arms and Armor,Revolver,"Colt Model 1862 Police Revolver, Serial No. 38549","American, Hartford, Connecticut and New York",,,,,Manufacturer|Designer,Grip designed by,Colt's Patent Fire Arms Manufacturing Company|John Quincy Adams Ward,"American, Hartford, Connecticut, 1855–present|American, Urbana, Ohio 1830–1910 New York",,"Colt's Patent Fire Arms Manufacturing Company|Ward, John Quincy Adams",American|American,1855 |1830,2014 |1910,ca. 1868,1840,1890,"Steel, gold, copper alloy (brass)",L. of pistol 11 in. (27.9 cm); Cal. .36 in. (9.1 mm),"Gift of W. C. Foxley, 2014",,Hartford|New York,Connecticut|New York,,,,,,,,,Firearms-Pistols,,http://www.metmuseum.org/art/collection/search/675957,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.300.2028,false,true,156809,Costume Institute,Fan,Fan,Dutch,,,,,Artist,,Carl Gustav Klingstedt,"Swedish, 1657–1734",,Klingstedt Carl Gustav,Swedish,1657,1734,1715–25,1715,1725,"ivory, mother-of-pearl, parchment, gouache, paint",,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Millicent V. Hearst, 1962",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/156809,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2004.28a, b",false,true,98556,Costume Institute,Necklace,Necklace,Swiss,,,,,Designer,,Jean Dunand,"French (born Switzerland), Lancy 1877–1942 Paris",,"Dunand, Jean","French, born Switzerland",1877,1942,ca. 1927,1922,1932,"(a,b) metal, lacquer",,"Purchase, Friends of The Costume Institute Gifts, 2004",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/98556,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"C.I.61.7a, b",false,true,100854,Costume Institute,Evening dress,Evening dress,French,,,,,Design House,,Francevramant,"France, founded 1935",,Francevramant,France,1935,1935,1934–36,1934,1936,silk,,"Gift of Mrs. Leonard Feist, 1961",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/100854,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.300.3041a–c,false,true,157935,Costume Institute,Wedding Corset,Wedding corset,French,,,,,Department Store|Manufacturer,,Stern Brothers|Corset Parisien,"American, founded New York, 1867|French",,Stern Brothers|Corset Parisien,American|French,1867,2001,1881–82,1881,1882,"silk, baleen, cotton, metal",,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Mrs. M. Disbrow, 1944",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/157935,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1974.95,false,true,81638,Costume Institute,Dinner dress,Dinner dress,French,,,,,Designer,,Vitaldi Babani,"French, born Middle East, active 1895–1940",,"Babani, Vitaldi","French, born Middle East",1895,1940,ca. 1919,1914,1924,"silk, fur, metallic thread",,"Gift of Anita Zahn, 1974",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/81638,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1992.69,false,true,81635,Costume Institute,Dress,Dress,French,,,,,Designer,,Vitaldi Babani,"French, born Middle East, active 1895–1940",,"Babani, Vitaldi","French, born Middle East",1895,1940,ca. 1920,1915,1925,"silk, metal",,"Purchase, Barbara and Gregory Reynolds Gift, 1992",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/81635,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.274,false,true,81637,Costume Institute,Evening dress,Evening dress,French,,,,,Designer,,Vitaldi Babani,"French, born Middle East, active 1895–1940",,"Babani, Vitaldi","French, born Middle East",1895,1940,early 1920s,1920,1925,"silk, metallic thread",,"Gift of Anita Zahn, 1976",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/81637,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2006.465,false,true,135706,Costume Institute,Evening coat,Evening coat,French,,,,,Designer,,Vitaldi Babani,"French, born Middle East, active 1895–1940",,"Babani, Vitaldi","French, born Middle East",1895,1940,early 1920s,1920,1925,"silk, metal",,"Gift of Richard and Judith Webb, 2006",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/135706,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2008.273,false,true,145416,Costume Institute,Robe,Robe,French,,,,,Designer,,Vitaldi Babani,"French, born Middle East, active 1895–1940",,"Babani, Vitaldi","French, born Middle East",1895,1940,1920s,1920,1929,"silk, metallic",,"Gift of Jacqueline Loewe Fowler, 2008",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/145416,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.134.15,false,true,81636,Costume Institute,Evening dress,Evening dress,French,,,,,Designer,,Vitaldi Babani,"French, born Middle East, active 1895–1940",,"Babani, Vitaldi","French, born Middle East",1895,1940,1920–23,1920,1923,"silk, metallic thread",,"Gift of Alice Roosevelt Longworth, 1976",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/81636,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -C.I.45.91.2,false,true,81521,Costume Institute,Evening dress,Evening dress,French,,,,,Designer,,Vitaldi Babani,"French, born Middle East, active 1895–1940",,"Babani, Vitaldi","French, born Middle East",1895,1940,ca. 1925,1920,1930,"silk, metal thread",,"Gift of Mrs. Aline Bernstein, 1945",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/81521,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -C.I.45.91.3,false,true,109551,Costume Institute,Blouse,Blouse,French,,,,,Designer,,Vitaldi Babani,"French, born Middle East, active 1895–1940",,"Babani, Vitaldi","French, born Middle East",1895,1940,ca. 1925,1925,1930,silk,,"Gift of Mrs. Aline Bernstein, 1945",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/109551,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -C.I.45.91.5,false,true,96129,Costume Institute,Dress,Dress,French,,,,,Designer,,Vitaldi Babani,"French, born Middle East, active 1895–1940",,"Babani, Vitaldi","French, born Middle East",1895,1940,1925,1925,1925,[no medium available],,"Gift of Mrs. Aline Bernstein, 1945",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/96129,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -C.I.46.16.12,false,true,96130,Costume Institute,Evening dress,Evening dress,French,,,,,Designer,,Vitaldi Babani,"French, born Middle East, active 1895–1940",,"Babani, Vitaldi","French, born Middle East",1895,1940,1924–25,1924,1925,silk,,"Gift of Mrs. Sophie Gimbel, 1946",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/96130,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"1980.443.2a, b",false,true,81639,Costume Institute,Tea gown,Tea gown,French,,,,,Designer,,Vitaldi Babani,"French, born Middle East, active 1895–1940",,"Babani, Vitaldi","French, born Middle East",1895,1940,1917,1917,1917,"silk, metallic thread, cotton",,"Gift of Mrs. Benjamin H. Namm, 1980",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/81639,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2009.300.490a, b",false,true,159027,Costume Institute,Evening dress,Evening dress,French,,,,,Designer,,Vitaldi Babani,"French, born Middle East, active 1895–1940",,"Babani, Vitaldi","French, born Middle East",1895,1940,ca. 1920,1918,1922,"silk, metal",,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of George Mangini, 1971",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/159027,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -C.I.66.14.2,true,true,81136,Costume Institute,Waistcoat,Waistcoat,British,,,,,Designer|Manufacturer,Textile by|Textile by,Anna Maria Garthwaite|Peter Lekeux,"British, 1690–1763|British, 1716–1768",,"Garthwaite, Anna Maria|Lekeux Peter",British|British,1690 |1716,1763 |1768,1747,1747,1747,"silk, wool, metallic",,"Purchase, Irene Lewisohn Bequest, 1966",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/81136,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.300.7540a–c,false,true,174593,Costume Institute,Walking suit,Walking suit,British,,,,,Design House|Designer,,House of Lucile|Lucile,"British, founded 1895|British, 1863–1935",,Lucile House of|Lucile,British|British,1895 |1863,1895 |1935,1910–12,1910,1912,silk,,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Mrs. Frederick H. Prince, Jr., 1967",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/174593,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1979.116.1,false,true,81634,Costume Institute,Evening coat,Evening coat,Italian,,,,,Designer,Attributed to,Vitaldi Babani,"French, born Middle East, active 1895–1940",,"Babani, Vitaldi","French, born Middle East",1895,1940,early 1920s,1920,1925,silk,,"Gift of Beatrice S. Bartlett, 1979",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/81634,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1995.506.1,false,true,80217,Costume Institute,Dress,Dress,Italian,,,,,Designer,,Vitaldi Babani,"French, born Middle East, active 1895–1940",,"Babani, Vitaldi","French, born Middle East",1895,1940,ca. 1926,1921,1931,"silk, metal",,"Gift of Richard and Judith Webb, 1995",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/80217,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1995.506.2,false,true,80218,Costume Institute,Dress,Dress,Italian,,,,,Designer,,Vitaldi Babani,"French, born Middle East, active 1895–1940",,"Babani, Vitaldi","French, born Middle East",1895,1940,ca. 1926,1921,1931,"silk, metal",,"Gift of Richard and Judith Webb, 1995",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/80218,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1995.506.3,false,true,80219,Costume Institute,Coat,Coat,Italian,,,,,Designer,,Vitaldi Babani,"French, born Middle East, active 1895–1940",,"Babani, Vitaldi","French, born Middle East",1895,1940,ca. 1926,1921,1931,"silk, metal",,"Gift of Richard and Judith Webb, 1995",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/80219,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1978.184.4a–c,false,true,84070,Costume Institute,Costume,Costume,American,,,,,Designer,,Léon Bakst,"Russian, Grodno 1866–1924 Paris",,"Bakst, Léon",Russian,1866,1924,1922–23,1922,1923,"silk, cotton, metallic thread, glass, plastic",,"Purchase, Marcia Sand Bequest, in memory of her daughter, Tiger (Joan) Morse, 1978",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/84070,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2008.190.149,false,true,141787,Costume Institute,Pendant,"""Mobius""",American,,,,,Designer,,Georg Jensen,"Danish, Rådvad 1866–1935 Hellerup",,"Georg, Jensen",Danish,1866,1935,1969,1969,1969,metal,,"Gift of Muriel Kallis Newman, 2008",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/141787,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2010.487.6a, b",false,true,159725,Costume Institute,Evening dress,Evening dress,American,,,,,Designer,,Catherine Donovan,"American (born Ireland), 1826 (?)–1906",,"Donovan, Catherine","American, born Ireland",1826,1906,1890s,1890,1899,"silk, cotton",,"Gift of Christopher Scholz and Ines Elskop, 2010",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/159725,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2009.300.990a, b",false,true,159582,Costume Institute,Evening dress,Evening dress,American,,,,,Designer,,Catherine Donovan,"American (born Ireland), 1826 (?)–1906",,"Donovan, Catherine","American, born Ireland",1826,1906,1900–1903,1900,1903,"silk, linen, metal",,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of George R. Cook III, 1980",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/159582,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.300.375a–c,false,true,158731,Costume Institute,Ensemble,Ensemble,American,,,,,Designer,,Catherine Donovan,"American (born Ireland), 1826 (?)–1906",,"Donovan, Catherine","American, born Ireland",1826,1906,1900–1903,1900,1903,"silk, linen",,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Mrs. Daniel M. McKeon and Robert Hoguet, Jr., 1965",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/158731,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2009.300.6290a, b",false,true,173337,Costume Institute,Afternoon dress,Afternoon dress,American,,,,,Designer,,Catherine Donovan,"American (born Ireland), 1826 (?)–1906",,"Donovan, Catherine","American, born Ireland",1826,1906,ca. 1883,1881,1885,Silk,,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of the Princess Viggo in accordance with the wishes of the Misses Hewitt, 1931",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/173337,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.300.7535a–d,false,true,174588,Costume Institute,Dress,Dress,American,,,,,Designer,,Catherine Donovan,"American (born Ireland), 1826 (?)–1906",,"Donovan, Catherine","American, born Ireland",1826,1906,ca. 1885,1883,1887,Cotton,,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Mrs. Frederick H. Prince, Jr., 1967",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/174588,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.300.3120a–c,false,true,158022,Costume Institute,Waist Cincher,Waist cincher,probably French,,,,,Design House,Attributed to,Redfern,1847–1940,,Redfern,,1847,1940,ca. 1900,1898,1902,"silk, bone, metal, elastic, cotton",,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of E. A. Meister, 1950",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/158022,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.300.1240,false,true,155934,Costume Institute,Evening overdress,Evening overdress,probably French,,,,,Design House,Attributed to,Callot Soeurs,"French, active 1895–1937",,Callot Soeurs,,1895,1937,ca. 1920,1918,1922,"silk, linen, metal",,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Mercedes de Acosta, 1955",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/155934,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -C.I.40.130.2a–c,false,true,106752,Costume Institute,Ensemble,Ensemble,American or European,,,,,Design House,Attributed to,Callot Soeurs,"French, active 1895–1937",,Callot Soeurs,,1895,1937,1875–1925,1875,1925,silk,,"Gift of Miss Mercedes de Acosta, 1940",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/106752,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.300.1771,false,true,156523,Costume Institute,Bonnet,Bonnet,probably French,,,,,Design House,,Redfern,1847–1940,,Redfern,,1847,1940,ca. 1888,1886,1890,silk,,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Mrs. James Dowd Lester, 1942",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/156523,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.300.3278a–c,false,true,158197,Costume Institute,Waist Cincher,Waist cincher,French,,,,,Design House,,Redfern,1847–1940,,Redfern,,1847,1940,1900–1910,1900,1910,"silk, bone, metal",,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Mrs. George A. Bonaventure in memory of Mrs. James Steel, 1965",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/158197,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.300.3768a–c,false,true,158740,Costume Institute,Ensemble,Ensemble,French,,,,,Design House,,Redfern,1847–1940,,Redfern,,1847,1940,ca. 1930,1928,1932,"silk, wool, beads, metal, fur",,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Mrs. Robert S. Kilborne, 1958",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/158740,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2015.732a, b",false,true,693846,Costume Institute,Dress,Dress,French,,,,,Design House|Designer,,House of Lanvin|Jeanne Lanvin,"French, founded 1889|French, 1867–1946",,"Lanvin, House of|Lanvin, Jeanne",,1889 |1867,9999 |1946,winter 1926–27,1926,1927,"silk, metal",,"Gift of Dean L. Merceron, 2015",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/693846,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -49.3.32a–e,false,true,107066,Costume Institute,Ensemble,Ensemble,French,,,,,Designer,,Redfern,1847–1940,,Redfern,,1847,1940,1887–89,1887,1889,"wool, silk, cotton, metallic thread",,"Gift of Orme Wilson and R. Thornton Wilson, in memory of their mother, Mrs. Caroline Schermerhorn Astor Wilson, 1949",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/107066,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.300.258,false,true,157422,Costume Institute,Dinner dress,Dinner dress,British,,,,,Design House,,Redfern,1847–1940,,Redfern,,1847,1940,1909–11,1909,1911,"silk, metal",,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Mrs. Robert S. Kilborne, 1958",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/157422,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.300.3189,false,true,158098,Costume Institute,Evening cape,Evening cape,British,,,,,Design House,,Redfern,1847–1940,,Redfern,,1847,1940,1901,1901,1901,silk,,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Mrs. Robert S. Kilborne, 1958",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/158098,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2009.300.107a, b",false,true,155754,Costume Institute,Walking suit,Walking suit,British,,,,,Design House,,Redfern,1847–1940,,Redfern,,1847,1940,ca. 1910,1908,1912,wool,,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Mrs. J. W. Post, 1944",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/155754,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2009.300.6575a, b",false,true,173574,Costume Institute,Dinner dress,Dinner dress,American,,,,,Retailer,,"Wechsler, Abraham & Company",,,"Wechsler, Abraham & Company",,1865,1994,1876–78,1876,1878,Silk,,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Mrs. James McF. Baker, 1948",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/173574,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"1981.515.1a, b",false,true,92336,Costume Institute,Dress,Dress,American,,,,,Designer,,Redfern,1847–1940,,Redfern,,1847,1940,ca. 1892,1887,1897,"silk, cotton",,"Gift of Mrs. Peter H. B. Frelinghuysen, 1981",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/92336,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.300.3009a–d,false,true,157899,Costume Institute,Evening dress,Evening dress,American,,,,,Designer,,Mme. Olympe,"American, born France, 1830",,Olympe Mme.,,1830,1930,ca. 1865,1863,1867,"silk, mother-of-pearl",,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Mrs. H. E. Rifflard, 1932",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/157899,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -C.I.69.9.1,false,true,106486,Costume Institute,Jacket,Jacket,probably French,,,,,Designer,,Revillon Frères,"French, founded 1723",,Revillon Frères,French,1723,1850,1902–3,1902,1903,"fur, silk",,"Gift of Mr. Hayward R. Alker, 1969",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/106486,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -C.I.38.98.2,false,true,106446,Costume Institute,Evening coat,Evening coat,American or European,,,,,Designer,Attributed to,Callot Soeurs,"French, active 1895–1937",,Callot Soeurs,French,1895,1937,1908–10,1908,1910,"silk, fur",,"Gift of Mrs. Sidney W. Ffoulkes, 1938",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/106446,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -C.I.45.36.2,false,true,106461,Costume Institute,Dress,Dress,French,,,,,Design House,,Beer,French,,Beer,French,,1929,late 19th–early 20th century,1875,1925,[no medium available],,"Gift of Mrs. George Kent, 1945",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/106461,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"C.I.64.46.3a, b",false,true,99746,Costume Institute,Dress,Dress,French,,,,,Design House,,Beer,French,,Beer,French,,1929,ca. 1925,1920,1930,"silk, metallic thread, beading",,"Gift of Mrs. William Dubilier, 1964",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/99746,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2009.300.4084a, b",false,true,170488,Costume Institute,Wedding Shoes,Wedding shoes,French,,,,,Maker|Retailer,,Esté|R.W.H. Rogers,"French, 1821–1839",,Esté|Rogers R.W.H.,French,1821,1839,1840–49,1840,1849,Silk,,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Mrs. Chauncey E. Low, 1924",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/170488,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.300.3024,false,true,157916,Costume Institute,Evening coat,Evening coat,French,,,,,Design House,,Rouff,"French, 1844–1914",,Rouff,French,1844,1914,1895–1905,1895,1905,"silk, rhinestones",,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Mrs. William E. S. Griswold, 1941",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/157916,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2009.300.332a, b",false,true,158254,Costume Institute,Evening dress,Evening dress,French,,,,,Design House,,Rouff,"French, 1844–1914",,Rouff,French,1844,1914,ca. 1897,1895,1899,silk,,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Mrs. Jason Westerfield, 1963",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/158254,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2009.300.376a, b",false,true,158742,Costume Institute,Dinner dress,Dinner dress,French,,,,,Design House,,Rouff,"French, 1844–1914",,Rouff,French,1844,1914,1900–1903,1900,1903,"silk, jet beads, rhinestones",,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Mrs. Daniel M. McKeon and Robert Hoguet, Jr., 1965",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/158742,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2009.300.2339a, b",false,true,157153,Costume Institute,Evening dress,Evening dress,French,,,,,Design House,,Rouff,"French, 1844–1914",,Rouff,French,1844,1914,ca. 1895,1893,1897,"silk, metal",,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Mrs. William E. S. Griswold, 1941",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/157153,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2009.300.6476a, b",false,true,173496,Costume Institute,Evening dress,Evening dress,French,,,,,Design House,,Rouff,"French, 1844–1914",,Rouff,French,1844,1914,1883–96,1883,1896,Silk,,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Mrs. William E. S. Griswold, 1941",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/173496,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2009.300.6477a, b",false,true,173497,Costume Institute,Evening dress,Evening dress,French,,,,,Design House,,Rouff,"French, 1844–1914",,Rouff,French,1844,1914,ca. 1895,1893,1897,Silk,,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Mrs. William E. S. Griswold, 1941",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/173497,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2009.300.8354a, b",false,true,175365,Costume Institute,Evening dress,Evening dress,French,,,,,Design House,,Rouff,"French, 1844–1914",,Rouff,French,1844,1914,1896–97,1896,1897,Silk,,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Designated Purchase Fund, 1990",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/175365,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2009.300.7006a, b",false,true,173996,Costume Institute,Dress,Dress,French,,,,,Design House,,Chéruit,"French, 1906–1935",,Chéruit,French,1906,1935,1912,1912,1912,Cotton,,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of the estate of Valerie Dreyfus, 1957",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/173996,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -C.I.47.6,false,true,99657,Costume Institute,Evening dress,Evening dress,French,,,,,Design House,,Myrbor,"French, 1922–1936",,Myrbor,French,1922,1936,1923,1923,1923,silk,,"Gift of Miss Ida Brenner, 1947",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/99657,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.300.3247,false,true,158163,Costume Institute,Evening dress,Evening dress,French,,,,,Design House,,Myrbor,"French, 1922–1936",,Myrbor,French,1922,1936,ca. 1926,1924,1928,"silk, metal",,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Mrs. V. D. Crisp, 1963",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/158163,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.300.3248,false,true,158164,Costume Institute,Evening dress,Evening dress,French,,,,,Design House,,Myrbor,"French, 1922–1936",,Myrbor,French,1922,1936,1924,1924,1924,"silk, metal",,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Mrs. V. D. Crisp, 1963",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/158164,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2009.300.3246a, b",false,true,158162,Costume Institute,Evening ensemble,Evening ensemble,French,,,,,Design House,,Myrbor,"French, 1922–1936",,Myrbor,French,1922,1936,1929,1929,1929,"silk, metal",,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Mrs. V. D. Crisp, 1963",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/158162,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -C.I.56.21,false,true,99596,Costume Institute,Evening coat,Evening coat,French,,,,,Design House,,Revillon Frères,"French, founded 1723",,Revillon Frères,French,1723,1850,1928,1928,1928,"silk, fur, glass, embroidery",,"Gift of Mrs. B. A. Goodman, in memory of Mrs. Gussie A. Matz, 1956",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/99596,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -C.I.59.55,false,true,99597,Costume Institute,Evening wrap,Evening wrap,French,,,,,Design House,,Revillon Frères,"French, founded 1723",,Revillon Frères,French,1723,1850,ca. 1923,1918,1928,"silk, fur",,"Gift of Mrs. Gustavus Ober Jr., 1959",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/99597,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2006.74.28,false,true,128422,Costume Institute,Cape,Cape,French,,,,,Design House,,Revillon Frères,"French, founded 1723",,Revillon Frères,French,1723,1850,ca. 1988,1983,1993,"leather, fur",,"Gift of Muriel Kallis Newman, 2006",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/128422,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.300.8377,false,true,175388,Costume Institute,Stole,Stole,French,,,,,Design House,,Revillon Frères,"French, founded 1723",,Revillon Frères,French,1723,1850,ca. 1935,1933,1937,"Fur, silk",,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Lady Emilia Dreher Armstrong, 1993",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/175388,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2003.256.3a, b",false,true,93634,Costume Institute,Accessory Set,Accessory set,French,,,,,Design House,,Revillon Frères,"French, founded 1723",,Revillon Frères,French,1723,1850,ca. 1960,1955,1965,"a) fur, rhinestone, metal, plastic; b) fur, silk",,"Gift of Laura Johnson, 2003",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/93634,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"1973.112a, b",false,true,84498,Costume Institute,Evening shoes,Evening shoes,French,,,,,Manufacturer,,"F. Pinet, Paris","French, founded 1855",,"F. Pinet, Paris",French,1855,1855,1926,1926,1926,silk,,"Gift of Mrs. James A. Cole, 1973",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/84498,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"49.3.5a, b",false,true,105066,Costume Institute,Dress,Dress,French,,,,,Design House,,House of Rouff,"French, founded 1929",,Rouff House of,French,1929,1929,1905–7,1905,1907,silk,,"Gift of Orme Wilson and R. Thornton Wilson, in memory of their mother, Mrs. Caroline Schermerhorn Astor Wilson, 1949",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/105066,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1979.84,false,true,85998,Costume Institute,Evening dress,Evening dress,French,,,,,Design House,,Callot Soeurs,"French, active 1895–1937",,Callot Soeurs,French,1895,1937,fall/winter 1921–22,1921,1922,"silk, cotton, metal, glass",,"Gift of Sandra Grey-Fretty, 1979",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/85998,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1993.50,false,true,94273,Costume Institute,Slip,Slip,French,,,,,Design House,,Callot Soeurs,"French, active 1895–1937",,Callot Soeurs,French,1895,1937,1927,1927,1927,"silk, cotton",,"Isabel Shults Fund, 1993",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/94273,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1993.60,false,true,94277,Costume Institute,Evening dress,Evening dress,French,,,,,Design House,,Callot Soeurs,"French, active 1895–1937",,Callot Soeurs,French,1895,1937,ca. 1927,1922,1932,"silk, metal",,"Isabel Shults Fund, 1993",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/94277,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1993.95,false,true,80789,Costume Institute,Slip,Slip,French,,,,,Design House,,Callot Soeurs,"French, active 1895–1937",,Callot Soeurs,French,1895,1937,ca. 1927,1922,1932,"silk, cotton",,"Gift of Martin M. Kamer, Switzerland, 1993",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/80789,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2014.64,false,true,631535,Costume Institute,Dress,Dress,French,,,,,Design House,,Callot Soeurs,"French, active 1895–1937",,Callot Soeurs,French,1895,1937,winter 1916–17,1916,1917,"silk, glass, metal",,"Millia Davenport and Zipporah Fleisher Fund, 2013",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/631535,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -44.95.3,false,true,86000,Costume Institute,Dress,Dress,French,,,,,Design House,,Callot Soeurs,"French, active 1895–1937",,Callot Soeurs,French,1895,1937,1925–26,1925,1926,"silk, metal thread",,"Gift of Isabel Shults, 1944",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/86000,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -44.95.4,false,true,85995,Costume Institute,Dress,Dress,French,,,,,Design House,,Callot Soeurs,"French, active 1895–1937",,Callot Soeurs,French,1895,1937,1925,1925,1925,silk,,"Gift of Isabel Shults, 1944",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/85995,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -49.3.22,false,true,105415,Costume Institute,Negligée,Negligée,French,,,,,Design House,,Callot Soeurs,"French, active 1895–1937",,Callot Soeurs,French,1895,1937,1898–1902,1898,1902,silk,,"Gift of Orme Wilson and R. Thornton Wilson, in memory of their mother, Mrs. Caroline Schermerhorn Astor Wilson, 1949",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/105415,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -40.182.9,false,true,86024,Costume Institute,Ball gown,Ball gown,French,,,,,Design House,,Callot Soeurs,"French, active 1895–1937",,Callot Soeurs,French,1895,1937,1897–1905,1897,1905,"silk, metal thread",,"Gift of Grace Rainey Rogers, 1940",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/86024,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -C.I.40.4,false,true,82547,Costume Institute,Evening dress,Evening dress,French,,,,,Design House,,Callot Soeurs,"French, active 1895–1937",,Callot Soeurs,French,1895,1937,1926–27,1926,1927,"silk, metal thread",,"Gift of Mrs. John Magnin, 1940",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/82547,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.37.2,false,true,106439,Costume Institute,Evening coat,Evening coat,French,,,,,Design House,,Callot Soeurs,"French, active 1895–1937",,Callot Soeurs,French,1895,1937,ca. 1900,1895,1905,"wool, silk, fur",,"Gift of Mrs. G. Macculloch Miller, 1976",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/106439,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -35.134.11,false,true,101637,Costume Institute,Dress,Dress,French,,,,,Design House,,Callot Soeurs,"French, active 1895–1937",,Callot Soeurs,French,1895,1937,1900,1900,1900,"silk, metal thread",,"Gift of Susan Dwight Bliss, 1935",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/101637,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1977.303.1,false,true,84506,Costume Institute,Evening dress,Evening dress,French,,,,,Design House,,Callot Soeurs,"French, active 1895–1937",,Callot Soeurs,French,1895,1937,fall/winter 1920–21,1920,1921,"silk, metallic thread",,"Gift of David Toser, 1977",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/84506,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1977.303.2,false,true,85997,Costume Institute,Evening dress,Evening dress,French,,,,,Design House,,Callot Soeurs,"French, active 1895–1937",,Callot Soeurs,French,1895,1937,1922–23,1922,1923,"silk, glass, metallic threads and cellophane",,"Gift of David Toser, 1977",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/85997,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1978.288.8,false,true,86008,Costume Institute,Evening dress,Evening dress,French,,,,,Design House,,Callot Soeurs,"French, active 1895–1937",,Callot Soeurs,French,1895,1937,ca. 1925,1920,1930,"silk, metallic thread, glass",,"Gift of Julia B. Henry, 1978",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/86008,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1978.288.9,false,true,86009,Costume Institute,Dance dress,Dance dress,French,,,,,Design House,,Callot Soeurs,"French, active 1895–1937",,Callot Soeurs,French,1895,1937,1926–27,1926,1927,"silk, cotton, metallic thread, glass",,"Gift of Julia B. Henry, 1978",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/86009,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1981.380.2,true,true,81139,Costume Institute,Evening dress,Evening dress,French,,,,,Design House,,Callot Soeurs,"French, active 1895–1937",,Callot Soeurs,French,1895,1937,1910–14,1910,1914,"cotton, silk, metal",,"The Jacqueline Loewe Fowler Costume Collection, Gift of Jacqueline Loewe Fowler, 1981",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/81139,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -C.I.46.4.8,false,true,82093,Costume Institute,Evening dress,Evening dress,French,,,,,Design House,,Callot Soeurs,"French, active 1895–1937",,Callot Soeurs,French,1895,1937,1922,1922,1922,"silk, beads",,"Gift of Mrs. Harrison Williams, Lady Mendl, and Mrs. Ector Munn, 1946",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/82093,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -C.I.51.113,false,true,86002,Costume Institute,Evening dress,Evening dress,French,,,,,Design House,,Callot Soeurs,"French, active 1895–1937",,Callot Soeurs,French,1895,1937,1925–26,1925,1926,"silk, silver thread",,"Gift of Mrs. Nathaniel Bowdich Potter, 1951",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/86002,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -C.I.63.2.2,false,true,86019,Costume Institute,Evening dress,Evening dress,French,,,,,Design House,,Callot Soeurs,"French, active 1895–1937",,Callot Soeurs,French,1895,1937,1930,1930,1930,silk,,"Gift of Mrs. Russell W. Davenport, 1963",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/86019,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1978.288.10,false,true,83188,Costume Institute,Evening dress,Evening dress,French,,,,,Design House,,Callot Soeurs,"French, active 1895–1937",,Callot Soeurs,French,1895,1937,ca. 1924,1919,1929,"cotton, metallic thread, glass",,"Gift of Julia B. Henry, 1978",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/83188,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1978.288.12,false,true,86011,Costume Institute,Evening dress,Evening dress,French,,,,,Design House,,Callot Soeurs,"French, active 1895–1937",,Callot Soeurs,French,1895,1937,1924–25,1924,1925,"cotton, plastic, metallic thread",,"Gift of Julia B. Henry, 1978",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/86011,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"44.95.1a, b",false,true,86005,Costume Institute,Dress,Dress,French,,,,,Design House,,Callot Soeurs,"French, active 1895–1937",,Callot Soeurs,French,1895,1937,1925,1925,1925,silk,,"Gift of Isabel Shults, 1944",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/86005,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"44.95.2a, b",false,true,86006,Costume Institute,Dress,Dress,French,,,,,Design House,,Callot Soeurs,"French, active 1895–1937",,Callot Soeurs,French,1895,1937,1925,1925,1925,silk,,"Gift of Isabel Shults, 1944",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/86006,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -C.I.40.27.2,false,true,83430,Costume Institute,Evening dress,Evening dress,French,,,,,Design House,,Callot Soeurs,"French, active 1895–1937",,Callot Soeurs,French,1895,1937,1909–10,1909,1910,silk,,"Gift of Miss Agnes Miles Carpenter, 1940",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/83430,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -C.I.43.13.5,false,true,85993,Costume Institute,Dress,Dress,French,,,,,Design House,,Callot Soeurs,"French, active 1895–1937",,Callot Soeurs,French,1895,1937,early 1920s,1920,1925,silk,,"Gift of Mrs. John Jay Whitehead, 1943",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/85993,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -C.I.51.97.1,false,true,86026,Costume Institute,Evening dress,Evening dress,French,,,,,Design House,,Callot Soeurs,"French, active 1895–1937",,Callot Soeurs,French,1895,1937,1918–19,1918,1919,silk,,"Purchase, Irene Lewisohn Bequest, 1951",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/86026,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -C.I.51.97.4,false,true,86028,Costume Institute,Evening dress,Evening dress,French,,,,,Design House,,Callot Soeurs,"French, active 1895–1937",,Callot Soeurs,French,1895,1937,1913,1913,1913,"silk, sequins",,"Purchase, Irene Lewisohn Bequest, 1951",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/86028,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -C.I.51.97.6,false,true,106735,Costume Institute,Evening coat,Evening coat,French,,,,,Design House,,Callot Soeurs,"French, active 1895–1937",,Callot Soeurs,French,1895,1937,1916–17,1916,1917,"silk, metallic, fur",,"Purchase, Irene Lewisohn Bequest, 1951",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/106735,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -C.I.52.19.2,false,true,86003,Costume Institute,Evening dress,Evening dress,French,,,,,Design House,,Callot Soeurs,"French, active 1895–1937",,Callot Soeurs,French,1895,1937,1920s,1920,1929,"silk, metallic",,"Gift of Madame Veronique Wolf and Madame Frederic Bon, 1952",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/86003,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -C.I.52.19.3,false,true,86029,Costume Institute,Evening dress,Evening dress,French,,,,,Design House,,Callot Soeurs,"French, active 1895–1937",,Callot Soeurs,French,1895,1937,1919,1919,1919,"silk, metallic",,"Gift of Madame Veronique Wolf and Madame Frederic Bon, 1952",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/86029,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -C.I.65.16.2,false,true,96131,Costume Institute,Dress,Dress,French,,,,,Design House,,Callot Soeurs,"French, active 1895–1937",,Callot Soeurs,French,1895,1937,1908,1908,1908,[no medium available],,"Gift of Mrs. John C. Tomlinson, 1965",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/96131,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -C.I.66.43.1,false,true,86031,Costume Institute,Evening dress,Evening dress,French,,,,,Design House,,Callot Soeurs,"French, active 1895–1937",,Callot Soeurs,French,1895,1937,ca. 1910,1905,1915,silk,,"Gift of Mrs. William M. Haupt, 1966",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/86031,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -C.I.66.58.3,false,true,99777,Costume Institute,Tea gown,Tea gown,French,,,,,Design House,,Callot Soeurs,"French, active 1895–1937",,Callot Soeurs,French,1895,1937,late 1920s,1925,1929,silk,,"Gift of Mrs. Leon L. Roos, 1966",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/99777,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"35.134.6a, b",false,true,86023,Costume Institute,Dress,Dress,French,,,,,Design House,,Callot Soeurs,"French, active 1895–1937",,Callot Soeurs,French,1895,1937,1899,1899,1899,silk,,"Gift of Susan Dwight Bliss, 1935",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/86023,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -C.I.44.64.10,false,true,84585,Costume Institute,Evening dress,Evening dress,French,,,,,Design House,,Callot Soeurs,"French, active 1895–1937",,Callot Soeurs,French,1895,1937,1923–24,1923,1924,silk,,"Gift of Miss Isabel Shults, 1944",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/84585,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -C.I.44.64.12,false,true,82550,Costume Institute,Evening dress,Evening dress,French,,,,,Design House,,Callot Soeurs,"French, active 1895–1937",,Callot Soeurs,French,1895,1937,1925–26,1925,1926,silk,,"Gift of Miss Isabel Shults, 1944",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/82550,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -C.I.44.64.15,false,true,85994,Costume Institute,Evening dress,Evening dress,French,,,,,Design House,,Callot Soeurs,"French, active 1895–1937",,Callot Soeurs,French,1895,1937,1924–25,1924,1925,silk,,"Gift of Miss Isabel Shults, 1944",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/85994,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -C.I.44.64.65,false,true,86018,Costume Institute,Scarf,Scarf,French,,,,,Design House,,Callot Soeurs,"French, active 1895–1937",,Callot Soeurs,French,1895,1937,1925–26,1925,1926,silk,,"Gift of Miss Isabel Shults, 1944",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/86018,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -C.I.57.22.11,false,true,86030,Costume Institute,Evening wrap,Evening wrap,French,,,,,Design House,,Callot Soeurs,"French, active 1895–1937",,Callot Soeurs,French,1895,1937,1914,1914,1914,silk,,"Gift of Estate of Valerie Dreyfus, 1957",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/86030,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -C.I.58.34.11,false,true,82610,Costume Institute,Afternoon dress,Afternoon dress,French,,,,,Design House,,Callot Soeurs,"French, active 1895–1937",,Callot Soeurs,French,1895,1937,ca. 1926,1921,1931,silk,,"Gift of Mrs. John Chambers Hughes, 1958",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/82610,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"1973.104.3a, b",false,true,110042,Costume Institute,Dress,Dress,French,,,,,Design House,,Callot Soeurs,"French, active 1895–1937",,Callot Soeurs,French,1895,1937,ca. 1922,1917,1927,[no medium available],,"Gift of Mrs. Leon L. Roos, 1973",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/110042,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"1978.184.5a, b",false,true,83224,Costume Institute,Afternoon dress,Afternoon dress,French,,,,,Design House,,Callot Soeurs,"French, active 1895–1937",,Callot Soeurs,French,1895,1937,1915,1915,1915,"cotton, silk",,"Purchase, Marcia Sand Bequest, in memory of her daughter, Tiger (Joan) Morse, 1978",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/83224,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"1978.288.7a, b",true,true,81113,Costume Institute,Dress,Dress,French,,,,,Design House,,Callot Soeurs,"French, active 1895–1937",,Callot Soeurs,French,1895,1937,ca. 1924,1919,1929,"wool, silk, metallic thread",,"Gift of Julia B. Henry, 1978",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/81113,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"1983.290.1a, b",false,true,86021,Costume Institute,Ensemble,Ensemble,French,,,,,Design House,,Callot Soeurs,"French, active 1895–1937",,Callot Soeurs,French,1895,1937,ca. 1929,1924,1934,"silk, fur",,"Gift of Isabel Shults, 1983",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/86021,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"1978.288.11a, b",false,true,86010,Costume Institute,Evening dress,Evening dress,French,,,,,Design House,,Callot Soeurs,"French, active 1895–1937",,Callot Soeurs,French,1895,1937,1925–26,1925,1926,"cotton, silk, plastic, glass",,"Gift of Julia B. Henry, 1978",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/86010,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"C.I.40.37.2a, b",false,true,86014,Costume Institute,Coat,Coat,French,,,,,Design House,,Callot Soeurs,"French, active 1895–1937",,Callot Soeurs,French,1895,1937,1920s,1920,1929,silk,,"Gift of Mrs. William Bamberger, 1940",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/86014,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"C.I.40.90.6a, b",false,true,92226,Costume Institute,Suit,Suit,French,,,,,Design House,,Callot Soeurs,"French, active 1895–1937",,Callot Soeurs,French,1895,1937,1915–17,1915,1917,wool,,"Gift of Mme. Louis Cerlian, 1940",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/92226,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -C.I.41.107a–c,false,true,84937,Costume Institute,Cocktail Suit,Cocktail suit,French,,,,,Design House,,Callot Soeurs,"French, active 1895–1937",,Callot Soeurs,French,1895,1937,1935,1935,1935,silk,,"Gift of Mrs. William Bamberger, 1941",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/84937,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"C.I.46.46.8a, b",false,true,86001,Costume Institute,Evening dress,Evening dress,French,,,,,Design House,,Callot Soeurs,"French, active 1895–1937",,Callot Soeurs,French,1895,1937,1920s,1920,1929,[no medium available],,"Gift of Mrs. Harrison Williams, Lady Mendl, and Mrs. Ector Munn, 1946",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/86001,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"C.I.51.97.2a, b",false,true,86027,Costume Institute,Evening dress,Evening dress,French,,,,,Design House,,Callot Soeurs,"French, active 1895–1937",,Callot Soeurs,French,1895,1937,1915–16,1915,1916,"silk, metallic",,"Purchase, Irene Lewisohn Bequest, 1951",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/86027,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"C.I.52.19.1a, b",false,true,86007,Costume Institute,Dress,Dress,French,,,,,Design House,,Callot Soeurs,"French, active 1895–1937",,Callot Soeurs,French,1895,1937,1920s,1920,1929,"silk, metallic",,"Gift of Madame Veronique Wolf and Madame Frederic Bon, 1952",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/86007,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"C.I.60.42.6a, b",false,true,86025,Costume Institute,Dress,Dress,French,,,,,Design House,,Callot Soeurs,"French, active 1895–1937",,Callot Soeurs,French,1895,1937,fall/winter 1910–11,1910,1911,"silk, metal, glass",,"Gift of Mrs. Howard Crosby Brokaw, 1960",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/86025,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1978.288.13a–c,false,true,86012,Costume Institute,Evening dress,Evening dress,French,,,,,Design House,,Callot Soeurs,"French, active 1895–1937",,Callot Soeurs,French,1895,1937,ca. 1923,1918,1928,"silk, cotton, plastic",,"Gift of Julia B. Henry, 1978",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/86012,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"C.I.44.64.11a, b",false,true,86004,Costume Institute,Evening dress,Evening dress,French,,,,,Design House,,Callot Soeurs,"French, active 1895–1937",,Callot Soeurs,French,1895,1937,1925–26,1925,1926,silk,,"Gift of Miss Isabel Shults, 1944",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/86004,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"C.I.44.64.14a, b",false,true,85999,Costume Institute,Evening dress,Evening dress,French,,,,,Design House,,Callot Soeurs,"French, active 1895–1937",,Callot Soeurs,French,1895,1937,1926,1926,1926,silk,,"Gift of Miss Isabel Shults, 1944",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/85999,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"C.I.51.97.25a, b",false,true,106867,Costume Institute,Suit,Suit,French,,,,,Design House,,Callot Soeurs,"French, active 1895–1937",,Callot Soeurs,French,1895,1937,1917,1917,1917,"wool, fur",,"Purchase, Irene Lewisohn Bequest, 1951",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/106867,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -C.I.67.64.1a–c,false,true,96117,Costume Institute,Afternoon dress,Afternoon dress,French,,,,,Design House,,Callot Soeurs,"French, active 1895–1937",,Callot Soeurs,French,1895,1937,1920–22,1920,1922,[no medium available],,"Gift of Ms. Ruth T. Constantino, 1967",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/96117,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -C.I.67.64.2a–c,false,true,85996,Costume Institute,Dress,Dress,French,,,,,Design House,,Callot Soeurs,"French, active 1895–1937",,Callot Soeurs,French,1895,1937,1920–22,1920,1922,[no medium available],,"Gift of Ruth T. Costantino, 1967",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/85996,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -C.I.44.64.13a–c,false,true,84586,Costume Institute,Evening ensemble,Evening ensemble,French,,,,,Design House,,Callot Soeurs,"French, active 1895–1937",,Callot Soeurs,French,1895,1937,1925–26,1925,1926,silk,,"Gift of Miss Isabel Shults, 1944",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/84586,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -C.I.44.64.19a–d,false,true,82551,Costume Institute,Pajamas,Pajamas,French,,,,,Design House,,Callot Soeurs,"French, active 1895–1937",,Callot Soeurs,French,1895,1937,1926–27,1926,1927,silk,,"Gift of Miss Isabel Shults, 1944",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/82551,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -C.I.51.70.18a–d,false,true,82632,Costume Institute,Evening dress,Evening dress,French,,,,,Design House,,Callot Soeurs,"French, active 1895–1937",,Callot Soeurs,French,1895,1937,1922,1922,1922,"silk, whalebone, steel",,"Gift of Mrs. Robert Lovett, 1951",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/82632,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"C.I.51.97.10a, b",false,true,83256,Costume Institute,Dress,Dress,French,,,,,Design House,,Jacques Doucet,"French, Paris 1853–1929 Paris",,"Doucet, Jacques",French,1853,1929,1918–19,1918,1919,cotton,,"Purchase, Irene Lewisohn Bequest, 1951",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/83256,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.300.7477,false,true,174537,Costume Institute,Evening bodice,Evening bodice,French,,,,,Designer|Design House,Possibly|Possibly,Charles Frederick Worth|Worth and Bobergh,"French (born England), Bourne 1825–1895 Paris",,"Worth, Charles Frederick|Worth and Bobergh",French,1825,1895,ca. 1865,1863,1867,Silk,,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Mrs. Roland A. Goodman, 1966",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/174537,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.300.1372a–d,false,true,156080,Costume Institute,Evening ensemble,Evening ensemble,French,,,,,Designer|Design House,,Charles Frederick Worth|Worth and Bobergh,"French (born England), Bourne 1825–1895 Paris",,"Worth, Charles Frederick|Worth and Bobergh",French,1825,1895,1862–65,1862,1865,silk,,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Designated Purchase Fund, 1987",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/156080,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2009.300.4249a, b",false,true,168479,Costume Institute,Evening slippers,Evening slippers,French,,,,,Maker,,Esté,"French, 1821–1839",,Esté,French,1821,1839,1860–69,1860,1869,"Silk, metal",,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Miriam Storrs Coe, 1934",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/168479,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2002.10a–t,false,true,83332,Costume Institute,Book,"Le bonheur du jour; ou, Les graces a la mode",French,,,,,Artist,,George Barbier,"French, Nantes 1882–1932 Paris",,"Barbier, George",French,1882,1932,1924,1924,1924,paper,,"Purchase, The Paul D. Schurgot Foundation Inc. Gift, 2002",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/83332,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.300.1254,false,true,155948,Costume Institute,Evening dress,Evening dress,French,,,,,Designer,,Georges Doeuillet,"French, 1865–1929",,"Doeuillet, Georges",French,1865,1929,1926–28,1926,1928,silk,,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Mark Mooring, 1957",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/155948,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.300.1338,false,true,156042,Costume Institute,Evening dress,Evening dress,French,,,,,Designer,,Georges Doeuillet,"French, 1865–1929",,"Doeuillet, Georges",French,1865,1929,1910–13,1910,1913,"silk, rhinestones",,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Mrs. Frederick H. Prince, Jr., 1967",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/156042,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.300.4949,false,true,169172,Costume Institute,Evening stole,Evening stole,French,,,,,Designer,,Mme. Jeanne Paquin,"French, 1869–1936",,Paquin Jeanne,French,1869,1936,1920–30,1920,1930,Silk,,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of the estate of Valerie Dreyfus, 1957",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/169172,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1981.328.3,false,true,94862,Costume Institute,Evening dress,Evening dress,French,,,,,Designer,,Jeanne Hallée,"French, 1880–1914",,Hallée Jeanne,French,1880,1914,1911–15,1911,1915,"silk, glass",,"Isabel Shults Fund, 1981",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/94862,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1981.328.4,false,true,94863,Costume Institute,Evening dress,Evening dress,French,,,,,Designer,,Jeanne Hallée,"French, 1880–1914",,Hallée Jeanne,French,1880,1914,1910–14,1910,1914,"silk, glass",,"Isabel Shults Fund, 1981",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/94863,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1981.328.5,false,true,94864,Costume Institute,Evening dress,Evening dress,French,,,,,Designer,,Jeanne Hallée,"French, 1880–1914",,Hallée Jeanne,French,1880,1914,1910–14,1910,1914,"silk, glass",,"Isabel Shults Fund, 1981",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/94864,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1981.328.8,false,true,94865,Costume Institute,Evening dress,Evening dress,French,,,,,Designer,,Jeanne Hallée,"French, 1880–1914",,Hallée Jeanne,French,1880,1914,1913–14,1913,1914,"silk, metal",,"Isabel Shults Fund, 1981",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/94865,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1981.328.9,false,true,94866,Costume Institute,Evening dress,Evening dress,French,,,,,Designer,,Jeanne Hallée,"French, 1880–1914",,Hallée Jeanne,French,1880,1914,1910–14,1910,1914,"silk, glass",,"Isabel Shults Fund, 1981",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/94866,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -C.I.49.2.4,false,true,94861,Costume Institute,Afternoon dress,Afternoon dress,French,,,,,Designer,,Jeanne Hallée,"French, 1880–1914",,Hallée Jeanne,French,1880,1914,1912,1912,1912,silk,,"Gift of Howard Sturges, in memory of his mother, Mrs. Howard O. Sturges, 1949",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/94861,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -C.I.49.2.5,false,true,84591,Costume Institute,Dress,Dress,French,,,,,Designer,,Jeanne Hallée,"French, 1880–1914",,Hallée Jeanne,French,1880,1914,1907,1907,1907,silk,,"Gift of Howard Sturges, in memory of his mother, Mrs. Howard O. Sturges, 1949",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/84591,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1981.328.10,false,true,94860,Costume Institute,Evening dress,Evening dress,French,,,,,Designer,,Jeanne Hallée,"French, 1880–1914",,Hallée Jeanne,French,1880,1914,1910–14,1910,1914,"silk, metal",,"Isabel Shults Fund, 1981",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/94860,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -C.I.37.51.1,false,true,94859,Costume Institute,Opera cape,Opera cape,French,,,,,Designer,,Jeanne Hallée,"French, 1880–1914",,Hallée Jeanne,French,1880,1914,ca. 1905,1900,1910,silk,,"Gift of V. Everett Macy Estate, 1937",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/94859,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"1995.5.1a, b",false,true,80189,Costume Institute,Ball gown,Ball gown,French,,,,,Designer,,Jeanne Hallée,"French, 1880–1914",,Hallée Jeanne,French,1880,1914,1905,1905,1905,"silk, metal",,"Purchase, Irene Lewisohn Trust Gift, 1995",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/80189,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"35.134.10a, b",false,true,94869,Costume Institute,Evening dress,Evening dress,French,,,,,Designer,,Jeanne Hallée,"French, 1880–1914",,Hallée Jeanne,French,1880,1914,1906–7,1906,1907,silk,,"Gift of Susan Dwight Bliss, 1935",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/94869,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"C.I.56.51a, b",false,true,104767,Costume Institute,Riding Habit,Riding habit,French,,,,,Designer,,Jeanne Hallée,"French, 1880–1914",,Hallée Jeanne,French,1880,1914,1910,1910,1910,"wool, silk",,"Gift of Mrs. Albert Ten Eyck Gardner, 1956",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/104767,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"C.I.50.40.4a, b",false,true,94867,Costume Institute,Evening dress,Evening dress,French,,,,,Designer,,Jeanne Hallée,"French, 1880–1914",,Hallée Jeanne,French,1880,1914,1901–5,1901,1905,"silk, metallic, glass",,"Gift of Estate of Annie-May Hegeman, 1950",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/94867,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -C.I.64.7.5a–c,false,true,83435,Costume Institute,Evening ensemble,Evening ensemble,French,,,,,Designer,,Jeanne Hallée,"French, 1880–1914",,Hallée Jeanne,French,1880,1914,1913–14,1913,1914,"(a) silk, metallic thread, glass beading; (b, c) silk, leather, metallic thread",,"Gift of Mrs. David J. Colton, 1964",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/83435,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2009.300.374a, b",false,true,158720,Costume Institute,Dinner dress,Dinner dress,French,,,,,Designer,,Jeanne Hallée,"French, 1880–1914",,Hallée Jeanne,French,1880,1914,1894–96,1894,1896,"cotton, silk, metal",,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Mrs. Daniel M. McKeon and Robert Hoguet, Jr., 1965",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/158720,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -C.I.62.36.4a–c,false,true,107166,Costume Institute,Dress,Dress,French,,,,,Designer,,Jeanne Hallée,"French, 1880–1914",,Hallée Jeanne,French,1880,1914,ca. 1892,1887,1897,silk,,"Gift of Mrs. Ogden W. Ross, 1962",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/107166,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2009.300.3098a, b",false,true,157996,Costume Institute,Afternoon dress,Afternoon dress,French,,,,,Designer,,Jeanne Hallée,"French, 1880–1914",,Hallée Jeanne,French,1880,1914,ca. 1900,1898,1902,silk,,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Marion Litchfield, 1950",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/157996,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.300.430a–c,false,true,158961,Costume Institute,Afternoon dress,Afternoon dress,French,,,,,Designer,,Jeanne Hallée,"French, 1880–1914",,Hallée Jeanne,French,1880,1914,ca. 1903,1901,1905,silk,,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Mrs. Frederick H. Prince, Jr., 1967",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/158961,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1978.337.1,false,true,99580,Costume Institute,Evening cape,Evening cape,French,,,,,Designer,,Chéruit,"French, 1906–1935",,Chéruit,French,1906,1935,ca. 1920,1915,1925,silk,,"Gift of the Estate of Mrs. Julia M. Weldon from Mary McDougall, 1978",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/99580,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -C.I.51.97.5,false,true,106775,Costume Institute,Evening wrap,Evening wrap,French,,,,,Designer,,Chéruit,"French, 1906–1935",,Chéruit,French,1906,1935,1918–19,1918,1919,silk,,"Purchase, Irene Lewisohn Bequest, 1951",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/106775,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -C.I.58.42.3,false,true,106482,Costume Institute,Evening wrap,Evening wrap,French,,,,,Designer,,Chéruit,"French, 1906–1935",,Chéruit,French,1906,1935,1902–4,1902,1904,"silk, metal",,"Gift of Mrs. Lawrence Tibbett, 1958",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/106482,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"C.I.46.4.21a, b",false,true,82106,Costume Institute,Tea gown,Tea gown,French,,,,,Designer,,Chéruit,"French, 1906–1935",,Chéruit,French,1906,1935,1922,1922,1922,silk,,"Gift of Mrs. Harrison Williams, Lady Mendl, and Mrs. Ector Munn, 1946",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/82106,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1979.264,false,true,106763,Costume Institute,Duster,Duster,French,,,,,Designer,,Revillon Frères,"French, founded 1723",,Revillon Frères,French,1723,1850,1914–20,1914,1920,silk,,"Gift of Charles D. Wood in honor of Kathryn Wood, 1979",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/106763,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1993.359,false,true,80823,Costume Institute,Coat,Coat,French,,,,,Designer,,Revillon Frères,"French, founded 1723",,Revillon Frères,French,1723,1850,ca. 1931,1926,1936,"fur, silk",,"Gift of Douglas Dillon, 1993",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/80823,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1999.501,false,true,81868,Costume Institute,Coat,Coat,French,,,,,Designer,,Revillon Frères,"French, founded 1723",,Revillon Frères,French,1723,1850,ca. 1950,1945,1955,fur,,"Gift of Gilbert S. Kahn in memory of Janet Annenberg Hooker, 2000",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/81868,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1977.60.11,false,true,122982,Costume Institute,Cape,Cape,French,,,,,Designer,,Revillon Frères,"French, founded 1723",,Revillon Frères,French,1723,1850,1940s,1940,1949,fur,,"Gift of Roger Goiran, 1977",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/122982,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.300.297,false,true,157854,Costume Institute,Evening coat,Evening coat,French,,,,,Designer,,Revillon Frères,"French, founded 1723",,Revillon Frères,French,1723,1850,ca. 1926,1924,1928,"fur, silk",,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Mrs. Edward H. Pflueger in memory of Florence Hazard Murphy, 1961",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/157854,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.300.455,false,true,158988,Costume Institute,Evening coat,Evening coat,French,,,,,Designer,,Revillon Frères,"French, founded 1723",,Revillon Frères,French,1723,1850,ca. 1930,1928,1932,fur,,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Anonymous gift, 1967",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/158988,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -C.I.58.67.14,false,true,106738,Costume Institute,Evening wrap,Evening wrap,French,,,,,Designer,,Revillon Frères,"French, founded 1723",,Revillon Frères,French,1723,1850,ca. 1917,1912,1922,silk,,"Gift of Mrs. Robert S. Kilborne, 1958",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/106738,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.300.5742,false,true,169949,Costume Institute,Muff,Muff,French,,,,,Designer,,Revillon Frères,"French, founded 1723",,Revillon Frères,French,1723,1850,first quarter 20th century,1900,1925,"Fur, silk",,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Mrs. Robert G. Olmsted and Constable MacCracken, 1969",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/169949,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"1976.269.13a, b",false,true,106227,Costume Institute,Suit,Suit,French,,,,,Designer,,Revillon Frères,"French, founded 1723",,Revillon Frères,French,1723,1850,1967–70,1967,1970,"fur, leather",,"Gift of Mrs. Morton Jay Seifter, 1976",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/106227,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2009.300.1069a, b",false,true,155742,Costume Institute,Coat,Coat,French,,,,,Designer,,Revillon Frères,"French, founded 1723",,Revillon Frères,French,1723,1850,ca. 1983,1981,1985,"fur, leather",,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Anonymous gift, 1994",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/155742,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"1972.30.3a, b",false,true,113590,Costume Institute,Shoes,Shoes,French,,,,,Designer,,"F. Pinet, Paris","French, founded 1855",,"F. Pinet, Paris",French,1855,1855,1920,1920,1920,[no medium available],,"Gift of Madame Lilliana Teruzzi, given in memory of her mother, Mrs. Isak Walker Weiman, 1972",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/113590,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"1973.260.5a, b",false,true,83481,Costume Institute,Evening shoes,Evening shoes,French,,,,,Designer,,"F. Pinet, Paris","French, founded 1855",,"F. Pinet, Paris",French,1855,1855,1930s,1930,1939,"silk, leather",,"Purchase, Irene Lewisohn Bequest, 1973",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/83481,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"C.I.47.58.1a, b",false,true,113471,Costume Institute,Shoes,Shoes,French,,,,,Designer,,"F. Pinet, Paris","French, founded 1855",,"F. Pinet, Paris",French,1855,1855,1920s,1920,1929,leather,,"Gift of Mr. James Stewart Cushman, 1947",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/113471,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"C.I.52.22.1a, b",false,true,113181,Costume Institute,Slippers,Slippers,French,,,,,Designer,,"F. Pinet, Paris","French, founded 1855",,"F. Pinet, Paris",French,1855,1855,1910,1910,1910,silk,,"Gift of Miss Susan W. Street, 1952",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/113181,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"C.I.45.36.17a, b",false,true,113469,Costume Institute,Shoes,Shoes,French,,,,,Designer,,"F. Pinet, Paris","French, founded 1855",,"F. Pinet, Paris",French,1855,1855,ca. 1920s,1915,1935,leather,,"Gift of Mrs. George Kent, 1945",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/113469,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"25.200a, b",false,true,85359,Costume Institute,Visiting dress,Visiting dress,French,,,,,Designer,,Emile Pingat,"French, active 1860–96",,Pingat Emile,French,1860,1896,ca. 1872,1867,1877,silk,,"GIft of Mrs. George D. Cross, 1925",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/85359,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -C.I.60.6.7,false,true,107875,Costume Institute,Cloak,Cloak,French,,,,,Designer,,Emile Pingat,"French, active 1860–96",,Pingat Emile,French,1860,1896,1879–80,1879,1880,silk,,"Gift of Chauncey Stillman, 1960",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/107875,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -C.I.60.6.8,false,true,107154,Costume Institute,Cape,Cape,French,,,,,Designer,,Emile Pingat,"French, active 1860–96",,Pingat Emile,French,1860,1896,1891–93,1891,1893,silk,,"Gift of Chauncey Stillman, 1960",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/107154,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -C.I.60.6.9,false,true,107876,Costume Institute,Dinner dress,Dinner dress,French,,,,,Designer,,Emile Pingat,"French, active 1860–96",,Pingat Emile,French,1860,1896,1877–80,1877,1880,"silk, jet",,"Gift of Chauncey Stillman, 1960",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/107876,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.300.67,false,true,159226,Costume Institute,Evening coat,Evening coat,French,,,,,Designer,,Emile Pingat,"French, active 1860–96",,Pingat Emile,French,1860,1896,1888–90,1888,1890,"silk, beads",,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Lillian E. Glenn Peirce and Mabel Glenn Cooper, 1929",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/159226,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.300.76,false,true,159326,Costume Institute,Afternoon jacket,Afternoon jacket,French,,,,,Designer,,Emile Pingat,"French, active 1860–96",,Pingat Emile,French,1860,1896,1885–90,1885,1890,silk,,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of the Princess Viggo in accordance with the wishes of the Misses Hewitt, 1931",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/159326,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -C.I.47.64.2,false,true,107148,Costume Institute,Cape,Cape,French,,,,,Designer,,Emile Pingat,"French, active 1860–96",,Pingat Emile,French,1860,1896,1890s,1890,1899,"silk, jet",,"Gift of Misses Irene and Emily Braman, 1947",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/107148,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.300.139,false,true,156099,Costume Institute,Evening jacket,Evening jacket,French,,,,,Designer,,Emile Pingat,"French, active 1860–96",,Pingat Emile,French,1860,1896,ca. 1893,1891,1895,"silk, jet, feathers, metal",,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Marion Litchfield, 1950",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/156099,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.300.140,false,true,156111,Costume Institute,Evening cape,Evening cape,French,,,,,Designer,,Emile Pingat,"French, active 1860–96",,Pingat Emile,French,1860,1896,1885–89,1885,1889,"silk, feathers",,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Marion Litchfield, 1950",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/156111,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.300.141,false,true,156122,Costume Institute,Evening cape,Evening cape,French,,,,,Designer,,Emile Pingat,"French, active 1860–96",,Pingat Emile,French,1860,1896,ca. 1891,1889,1893,"wool, silk, fur, beads",,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Marion Litchfield, 1950",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/156122,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.300.337,false,true,158299,Costume Institute,Mantle,Mantle,French,,,,,Designer,,Emile Pingat,"French, active 1860–96",,Pingat Emile,French,1860,1896,ca. 1891,1889,1893,"wool, silk, metal, feathers",,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Cornelia Gracie Henshaw, 1964",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/158299,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.300.484,false,true,159020,Costume Institute,Evening cloak,Evening cloak,French,,,,,Designer,,Emile Pingat,"French, active 1860–96",,Pingat Emile,French,1860,1896,1885–89,1885,1889,"silk, metal",,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Anonymous gift, In memory of Mrs. John Roebling, 1970",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/159020,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.300.485,false,true,159021,Costume Institute,Cape,Cape,French,,,,,Designer,,Emile Pingat,"French, active 1860–96",,Pingat Emile,French,1860,1896,ca. 1895,1893,1897,"wool, silk, jet",,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of the Van Tassell family, 1970",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/159021,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -C.I.39.112.3,false,true,106645,Costume Institute,Coat,Coat,French,,,,,Designer,,Emile Pingat,"French, active 1860–96",,Pingat Emile,French,1860,1896,1860–96,1860,1896,silk,,"Gift of Mrs. Roswell Skeel, Jr., 1939",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/106645,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -C.I.60.42.13,false,true,84561,Costume Institute,Opera cloak,Opera cloak,French,,,,,Designer,,Emile Pingat,"French, active 1860–96",,Pingat Emile,French,1860,1896,ca. 1882,1877,1887,"silk, fur, feathers, metal",,"Gift of Mrs. Howard Crosby Brokaw, 1960",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/84561,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.300.6294,false,true,175581,Costume Institute,Evening dolman,Evening dolman,French,,,,,Designer,,Emile Pingat,"French, active 1860–96",,Pingat Emile,French,1860,1896,ca. 1885,1883,1887,"Silk, jet beads",,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of the Princess Viggo in accordance with the wishes of the Misses Hewitt, 1931",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/175581,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.300.7431,false,true,174512,Costume Institute,Evening cape,Evening cape,French,,,,,Designer,,Emile Pingat,"French, active 1860–96",,Pingat Emile,French,1860,1896,1890,1890,1890,"Silk, metallic, beads, stones",,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Mrs. Robert G. Olmsted, 1965",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/174512,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"1978.295.7a, b",false,true,107064,Costume Institute,Ball gown,Ball gown,French,,,,,Designer,,Emile Pingat,"French, active 1860–96",,Pingat Emile,French,1860,1896,1891–93,1891,1893,"silk, cotton",,"Purchase, Marcia Sand Bequest, in memory of her daughter, Tiger (Joan) Morse, 1978",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/107064,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -C.I.43.66a–c,false,true,107765,Costume Institute,Ensemble,Ensemble,French,,,,,Designer,,Emile Pingat,"French, active 1860–96",,Pingat Emile,French,1860,1896,1866–68,1866,1868,silk,,"Gift of Mrs. Price Collier, 1943",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/107765,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"C.I.60.6.4a, b",false,true,107195,Costume Institute,Dress,Dress,French,,,,,Designer,,Emile Pingat,"French, active 1860–96",,Pingat Emile,French,1860,1896,1891–93,1891,1893,silk,,"Gift of Chauncey Stillman, 1960",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/107195,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1978.295.3a–c,false,true,107058,Costume Institute,Evening dress,Evening dress,French,,,,,Designer,,Emile Pingat,"French, active 1860–96",,Pingat Emile,French,1860,1896,1891–93,1891,1893,"silk, metallic thread, glass",,"Purchase, Marcia Sand Bequest, in memory of her daughter, Tiger (Joan) Morse, 1978",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/107058,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"C.I.69.33.1a, b",false,true,82430,Costume Institute,Ball gown,Ball gown,French,,,,,Designer,,Emile Pingat,"French, active 1860–96",,Pingat Emile,French,1860,1896,ca. 1860,1855,1865,silk,,"Gift of Mary Pierrepont Beckwith, 1969",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/82430,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2009.300.628a, b",false,true,159180,Costume Institute,Dress,Dress,French,,,,,Designer,,Emile Pingat,"French, active 1860–96",,Pingat Emile,French,1860,1896,ca. 1885,1883,1887,"silk, cotton",,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Lillian E. Glenn Peirce and Mabel Glenn Cooper, 1929",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/159180,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -C.I.69.33.12a–c,false,true,82642,Costume Institute,Ball gown,Ball gown,French,,,,,Designer,,Emile Pingat,"French, active 1860–96",,Pingat Emile,French,1860,1896,ca. 1864,1863,1865,silk,,"Gift of Mary Pierrepont Beckwith, 1969",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/82642,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1984.33,false,true,86022,Costume Institute,Evening dress,Evening dress,French,,,,,Designer,Attributed to,Callot Soeurs,"French, active 1895–1937",,Callot Soeurs,French,1895,1937,ca. 1927,1922,1932,silk,,"Purchase, German Fur Industry; Brenner Couture Inc. Leisure Dynamics Foundation, 1983",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/86022,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1984.167,false,true,106443,Costume Institute,Opera coat,Opera coat,French,,,,,Designer,,Callot Soeurs,"French, active 1895–1937",,Callot Soeurs,French,1895,1937,ca. 1907,1902,1912,"silk, metal, feathers",,"Gift of Karen Roston, 1984",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/106443,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1978.184.6,false,true,106848,Costume Institute,Afternoon dress,Afternoon dress,French,,,,,Designer,,Callot Soeurs,"French, active 1895–1937",,Callot Soeurs,French,1895,1937,1915,1915,1915,"cotton, silk",,"Purchase, Marcia Sand Bequest, in memory of her daughter, Tiger (Joan) Morse, 1978",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/106848,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1979.150.1,false,true,109777,Costume Institute,Evening dress,Evening dress,French,,,,,Designer,,Callot Soeurs,"French, active 1895–1937",,Callot Soeurs,French,1895,1937,ca. 1911,1906,1916,"silk, cotton, metallic thread, metal beads",,"Gift of Mrs. W. Allston Flagg, 1979",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/109777,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1982.133.3,false,true,108933,Costume Institute,Tea gown,Tea gown,French,,,,,Designer,,Callot Soeurs,"French, active 1895–1937",,Callot Soeurs,French,1895,1937,1906–8,1906,1908,"silk, cotton",,"Hoechst Fiber Industries Fund, 1982",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/108933,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2000.169.1,false,true,86032,Costume Institute,Dress,Dress,French,,,,,Designer,Attributed to,Callot Soeurs,"French, active 1895–1937",,Callot Soeurs,French,1895,1937,ca. 1918,1913,1923,silk,,"Gift of Richard Martin, 2000",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/86032,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -C.I.66.58.4,false,true,81734,Costume Institute,Dress,Dress,French,,,,,Designer,Attributed to,Callot Soeurs,"French, active 1895–1937",,Callot Soeurs,French,1895,1937,1926,1926,1926,silk,,"Gift of Mrs. Leon L. Roos, 1966",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/81734,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"1982.369.2a, b",false,true,109793,Costume Institute,Promenade suit,Promenade suit,French,,,,,Designer,,Callot Soeurs,"French, active 1895–1937",,Callot Soeurs,French,1895,1937,ca. 1911,1906,1916,"silk, cotton",,"Purchase, German Fur Industry Gift, 1982",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/109793,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"C.I.65.39.1a, b",false,true,84006,Costume Institute,Evening dress,Evening dress,French,,,,,Designer,Attributed to,Callot Soeurs,"French, active 1895–1937",,Callot Soeurs,French,1895,1937,ca. 1925,1925,1925,"silk, metallic thread",,"Gift of Mrs. Robin Craven, 1965",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/84006,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -C.I.44.64.16a–d,false,true,86015,Costume Institute,Loungewear,Loungewear,French,,,,,Designer,,Callot Soeurs,"French, active 1895–1937",,Callot Soeurs,French,1895,1937,1926–27,1926,1927,silk,,"Gift of Miss Isabel Shults, 1944",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/86015,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -C.I.44.64.17a–c,false,true,86016,Costume Institute,Loungewear,Loungewear,French,,,,,Designer,,Callot Soeurs,"French, active 1895–1937",,Callot Soeurs,French,1895,1937,1926–27,1926,1927,silk,,"Gift of Miss Isabel Shults, 1944",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/86016,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -C.I.44.64.18a–c,false,true,86017,Costume Institute,Loungewear,Loungewear,French,,,,,Designer,,Callot Soeurs,"French, active 1895–1937",,Callot Soeurs,French,1895,1937,1926–27,1926,1927,silk,,"Gift of Miss Isabel Shults, 1944",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/86017,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -C.I.58.30,false,true,119398,Costume Institute,Scarf,Scarf,French,,,,,Designer,Textile by,René-Jules Lalique,"French, Aÿ 1860–1945 Paris",,"Lalique, René-Jules",French,1860,1945,early 20th century,1900,1950,silk,,"Purchase, Irene Lewisohn Bequest, 1958",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/119398,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1983.113.7,false,true,121909,Costume Institute,Belt Buckle,Belt buckle,French,,,,,Designer,attributed to,René-Jules Lalique,"French, Aÿ 1860–1945 Paris",,"Lalique, René-Jules",French,1860,1945,ca. 1932,1927,1937,"glass, metal",,"Gift of Judith Leiber, 1983",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/121909,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -49.3.19,false,true,105413,Costume Institute,Evening dress,Evening dress,French,,,,,Designer,,Jacques Doucet,"French, Paris 1853–1929 Paris",,"Doucet, Jacques",French,1853,1929,1905–8,1905,1908,silk,,"Gift of Orme Wilson and R. Thornton Wilson, in memory of their mother, Mrs. Caroline Schermerhorn Astor Wilson, 1949",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/105413,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -49.3.20,false,true,95195,Costume Institute,Evening dress,Evening dress,French,,,,,Designer,,Jacques Doucet,"French, Paris 1853–1929 Paris",,"Doucet, Jacques",French,1853,1929,1907–8,1907,1908,silk,,"Gift of Orme Wilson and R. Thornton Wilson, in memory of their mother, Mrs. Caroline Schermerhorn Astor Wilson, 1949",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/95195,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1974.242.1,false,true,101303,Costume Institute,Evening coat,Evening coat,French,,,,,Designer,,Jacques Doucet,"French, Paris 1853–1929 Paris",,"Doucet, Jacques",French,1853,1929,ca. 1910,1905,1915,"silk, metallic tread, fur",,"Gift of Olivia Constable, 1974",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/101303,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.390.8,false,true,81749,Costume Institute,Visiting dress,Visiting dress,French,,,,,Designer,,Jacques Doucet,"French, Paris 1853–1929 Paris",,"Doucet, Jacques",French,1853,1929,ca. 1903,1898,1908,"wool, cotton, silk, metallic thread",,"Gift of Irma A. Bloomingdale, 1976",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/81749,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1995.177.2,false,true,79775,Costume Institute,Opera cape,Opera cape,French,,,,,Designer,,Jacques Doucet,"French, Paris 1853–1929 Paris",,"Doucet, Jacques",French,1853,1929,ca. 1890,1885,1895,"silk, fur",,"Purchase, Irene Lewisohn Trust Gift, 1995",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/79775,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1977.329.29,false,true,110065,Costume Institute,Scarf,Scarf,French,,,,,Designer,,Jacques Doucet,"French, Paris 1853–1929 Paris",,"Doucet, Jacques",French,1853,1929,1930s–40s,1930,1949,silk,,"Gift of Col. Edgar W. Garbisch, 1977",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/110065,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"49.3.26a, b",false,true,93763,Costume Institute,Ball gown,Ball gown,French,,,,,Designer,,Jacques Doucet,"French, Paris 1853–1929 Paris",,"Doucet, Jacques",French,1853,1929,1897,1897,1897,silk,,"Gift of Orme Wilson and R. Thornton Wilson, in memory of their mother, Mrs. Caroline Schermerhorn Astor Wilson, 1949",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/93763,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"49.3.27a, b",false,true,84645,Costume Institute,Ball gown,Ball gown,French,,,,,Designer,,Jacques Doucet,"French, Paris 1853–1929 Paris",,"Doucet, Jacques",French,1853,1929,1898–1900,1898,1900,silk,,"Gift of Orme Wilson and R. Thornton Wilson, in memory of their mother, Mrs. Caroline Schermerhorn Astor Wilson, 1949",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/84645,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"49.3.30a, b",false,true,84646,Costume Institute,Ball gown,Ball gown,French,,,,,Designer,,Jacques Doucet,"French, Paris 1853–1929 Paris",,"Doucet, Jacques",French,1853,1929,1898–1900,1898,1900,silk,,"Gift of Orme Wilson and R. Thornton Wilson, in memory of their mother, Mrs. Caroline Schermerhorn Astor Wilson, 1949",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/84646,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"49.3.33a, b",false,true,105047,Costume Institute,Suit,Suit,French,,,,,Designer,,Jacques Doucet,"French, Paris 1853–1929 Paris",,"Doucet, Jacques",French,1853,1929,1903–6,1903,1906,cotton,,"Gift of Orme Wilson and R. Thornton Wilson, in memory of their mother, Mrs. Caroline Schermerhorn Astor Wilson, 1949",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/105047,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -C.I.51.13.1,false,true,83429,Costume Institute,Evening dress,Evening dress,French,,,,,Designer,,Jacques Doucet,"French, Paris 1853–1929 Paris",,"Doucet, Jacques",French,1853,1929,1905–7,1905,1907,silk,,"Gift of Miss Marie Louise Constable, 1951",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/83429,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.15a–c,true,true,81138,Costume Institute,Ensemble,Ensemble,French,,,,,Designer,,Jacques Doucet,"French, Paris 1853–1929 Paris",,"Doucet, Jacques",French,1853,1929,1920–23,1920,1923,"wool, silk, glass",,"Gift of Mrs. W.G. Constable, 1975",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/81138,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"1984.337a, b",false,true,107637,Costume Institute,Dress,Dress,French,,,,,Designer,,Jacques Doucet,"French, Paris 1853–1929 Paris",,"Doucet, Jacques",French,1853,1929,ca. 1880,1875,1885,"silk, cotton",,"Purchase, Irene Lewisohn and Alice L. Crowley Bequests, 1984",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/107637,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.300.128,false,true,155977,Costume Institute,Evening cape,Evening cape,French,,,,,Designer,,Jacques Doucet,"French, Paris 1853–1929 Paris",,"Doucet, Jacques",French,1853,1929,1900–1905,1900,1905,"wool, silk, rhinestones",,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Orme and R. Thornton Wilson in memory of Caroline Schermerhorn Astor Wilson, 1949",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/155977,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.300.431,false,true,158962,Costume Institute,Dress,Dress,French,,,,,Designer,,Jacques Doucet,"French, Paris 1853–1929 Paris",,"Doucet, Jacques",French,1853,1929,ca. 1910,1908,1912,"silk, cotton",,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Mrs. Frederick H. Prince, Jr., 1967",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/158962,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.300.471,false,true,159006,Costume Institute,Evening coat,Evening coat,French,,,,,Designer,,Jacques Doucet,"French, Paris 1853–1929 Paris",,"Doucet, Jacques",French,1853,1929,fall/winter 1902,1902,1902,"wool, fur, silk",,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Mrs. Robert G. Olmsted and Constable MacCracken, 1969",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/159006,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -47.25.1a–c,false,true,102260,Costume Institute,Evening dress,Evening dress,French,,,,,Designer,,Jacques Doucet,"French, Paris 1853–1929 Paris",,"Doucet, Jacques",French,1853,1929,ca. 1904,1899,1909,silk,,"Gift of Marie L. Constable, 1947",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/102260,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.300.1154,false,true,155837,Costume Institute,Evening dress,Evening dress,French,,,,,Designer,,Jacques Doucet,"French, Paris 1853–1929 Paris",,"Doucet, Jacques",French,1853,1929,ca. 1910,1908,1912,"silk, fur, linen",,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Orme and R. Thornton Wilson in memory of Caroline Schermerhorn Astor Wilson, 1949",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/155837,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.300.3196,false,true,158106,Costume Institute,Tea gown,Tea gown,French,,,,,Designer,,Jacques Doucet,"French, Paris 1853–1929 Paris",,"Doucet, Jacques",French,1853,1929,ca. 1907,1905,1909,"silk, linen",,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Rodman A. Heeren, 1959",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/158106,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.300.3258,false,true,158174,Costume Institute,Afternoon jacket,Afternoon jacket,French,,,,,Designer,,Jacques Doucet,"French, Paris 1853–1929 Paris",,"Doucet, Jacques",French,1853,1929,ca. 1905,1903,1907,"silk, cotton, metal, wood",,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Mrs. Roland A. Goodman, 1964",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/158174,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.174.1a–d,false,true,109758,Costume Institute,Wedding Ensemble,Wedding ensemble,French,,,,,Designer,(a),Jacques Doucet,"French, Paris 1853–1929 Paris",,"Doucet, Jacques",French,1853,1929,ca. 1907,1902,1912,"cotton, silk, wax",,"Gift of the Alice Langhorne Washburn Estate, 1976",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/109758,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1977.303.3a–c,false,true,110064,Costume Institute,Evening ensemble,Evening ensemble,French,,,,,Designer,,Jacques Doucet,"French, Paris 1853–1929 Paris",,"Doucet, Jacques",French,1853,1929,ca. 1925,1920,1930,"silk, glass, plastic",,"Gift of David Toser, 1977",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/110064,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2009.300.579a, b",false,true,159124,Costume Institute,Afternoon dress,Afternoon dress,French,,,,,Designer,,Jacques Doucet,"French, Paris 1853–1929 Paris",,"Doucet, Jacques",French,1853,1929,1900–1903,1900,1903,"silk, linen, rhinestones",,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Designated Purchase Fund, 1990",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/159124,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2009.300.892a, b",false,true,159473,Costume Institute,Visiting dress,Visiting dress,French,,,,,Designer,,Jacques Doucet,"French, Paris 1853–1929 Paris",,"Doucet, Jacques",French,1853,1929,1900–1905,1900,1905,silk,,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Mrs. Robert G. Olmsted, 1965",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/159473,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"C.I.44.64.41a, b",false,true,106672,Costume Institute,Evening dress,Evening dress,French,,,,,Designer,,Jacques Doucet,"French, Paris 1853–1929 Paris",,"Doucet, Jacques",French,1853,1929,1893–96,1893,1896,silk,,"Gift of Miss Isabel Shults, 1944",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/106672,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -C.I.51.13.2a–c,false,true,106438,Costume Institute,Evening dress,Evening dress,French,,,,,Designer,,Jacques Doucet,"French, Paris 1853–1929 Paris",,"Doucet, Jacques",French,1853,1929,1906–7,1906,1907,silk,,"Gift of Miss Marie Louise Constable, 1951",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/106438,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2009.300.1153a, b",false,true,155836,Costume Institute,Afternoon dress,Afternoon dress,French,,,,,Designer,,Jacques Doucet,"French, Paris 1853–1929 Paris",,"Doucet, Jacques",French,1853,1929,ca. 1903,1901,1905,"cotton, silk",,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Orme and R. Thornton Wilson in memory of Caroline Schermerhorn Astor Wilson, 1949",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/155836,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2009.300.1557a, b",false,true,156285,Costume Institute,Afternoon dress,Afternoon dress,French,,,,,Designer,,Jacques Doucet,"French, Paris 1853–1929 Paris",,"Doucet, Jacques",French,1853,1929,ca. 1903,1901,1905,silk,,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Mrs. Robert G. Olmsted, 1965",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/156285,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2009.300.2533a, b",false,true,157371,Costume Institute,Evening dress,Evening dress,French,,,,,Designer,,Jacques Doucet,"French, Paris 1853–1929 Paris",,"Doucet, Jacques",French,1853,1929,1898–1900,1898,1900,"silk, metal, plastic",,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Mrs. Daniel M. McKeon and Robert Hoguet, Jr., 1965",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/157371,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2009.300.3274a, b",false,true,158193,Costume Institute,Ball gown,Ball gown,French,,,,,Designer,,Jacques Doucet,"French, Paris 1853–1929 Paris",,"Doucet, Jacques",French,1853,1929,1898–1902,1898,1902,"silk, metal, linen",,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Mrs. Daniel M. McKeon and Robert Hoguet, Jr., 1965",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/158193,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2009.300.3309a, b",false,true,158232,Costume Institute,Ball gown,Ball gown,French,,,,,Designer,,Jacques Doucet,"French, Paris 1853–1929 Paris",,"Doucet, Jacques",French,1853,1929,ca. 1902,1900,1904,"silk, metal",,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Mrs. Frederick H. Prince, Jr., 1967",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/158232,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2009.300.3346a, b",false,true,158273,Costume Institute,Afternoon suit,Afternoon suit,French,,,,,Designer,,Jacques Doucet,"French, Paris 1853–1929 Paris",,"Doucet, Jacques",French,1853,1929,fall/winter 1904,1904,1904,silk,,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Mrs. Robert G. Olmsted and Constable MacCracken, 1969",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/158273,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2009.300.7070a, b",false,true,174206,Costume Institute,Evening dress,Evening dress,French,,,,,Designer,,Jacques Doucet,"French, Paris 1853–1929 Paris",,"Doucet, Jacques",French,1853,1929,1905–10,1905,1910,Silk,,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Mrs. Alfred Roberts, 1959",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/174206,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2009.300.7409a, b",false,true,174491,Costume Institute,Dinner dress,Dinner dress,French,,,,,Designer,,Jacques Doucet,"French, Paris 1853–1929 Paris",,"Doucet, Jacques",French,1853,1929,1900–1905,1900,1905,"Silk, linen",,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Mrs. Daniel M. McKeon and Robert Hoguet, Jr., 1965",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/174491,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2009.300.7430a, b",false,true,174511,Costume Institute,Evening dress,Evening dress,French,,,,,Designer,,Jacques Doucet,"French, Paris 1853–1929 Paris",,"Doucet, Jacques",French,1853,1929,ca. 1905,1903,1907,"Silk, sequins, beads, metallic",,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Mrs. Robert G. Olmsted, 1965",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/174511,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.300.891a–d,false,true,159472,Costume Institute,Evening dress,Evening dress,French,,,,,Designer,,Jacques Doucet,"French, Paris 1853–1929 Paris",,"Doucet, Jacques",French,1853,1929,ca. 1902,1900,1904,"silk, linen",,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Mrs. Robert G. Olmsted, 1965",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/159472,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -C.I.44.64.56a–c,false,true,85067,Costume Institute,Evening dress,Evening dress,French,,,,,Designer,,Jacques Doucet,"French, Paris 1853–1929 Paris",", Paris","Doucet, Jacques",French,1853,1929,1897–1900,1897,1900,"silk, beads",,"Gift of Miss Isabel Shults, 1944",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/85067,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.300.3275a–c,false,true,158194,Costume Institute,Ball gown,Ball gown,French,,,,,Designer,,Jacques Doucet,"French, Paris 1853–1929 Paris",,"Doucet, Jacques",French,1853,1929,1898–1900,1898,1900,"silk, metal",,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Mrs. Daniel M. McKeon and Robert Hoguet, Jr., 1965",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/158194,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -42.148,false,true,83467,Costume Institute,Hat,Hat,French,,,,,Designer,,Paul Poiret,"French, Paris 1879–1944 Paris",,"Poiret, Paul",French,1879,1944,1928,1928,1928,[no medium available],,"Gift of Mrs. Francis Henry Taylor, 1942",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/83467,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1980.86,false,true,81562,Costume Institute,Evening coat,Evening coat,French,,,,,Designer,,Paul Poiret,"French, Paris 1879–1944 Paris",,"Poiret, Paul",French,1879,1944,1913–19,1913,1919,"silk, metallic thread",,"Purchase, the Kyoto Institute: Koichi Tsukamoto, President Fund, 1980",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/81562,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1978.277,false,true,105657,Costume Institute,Evening dress,Evening dress,French,,,,,Designer,,Paul Poiret,"French, Paris 1879–1944 Paris",,"Poiret, Paul",French,1879,1944,ca. 1922,1917,1927,"silk, metallic thread, plastic, glass",,"Purchase, Friends of The Costume Institute Gifts, 1978",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/105657,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1979.428,false,true,84616,Costume Institute,Dress,Dress,French,,,,,Designer,,Paul Poiret,"French, Paris 1879–1944 Paris",,"Poiret, Paul",French,1879,1944,1922,1922,1922,"silk, metallic thread",,"Gift of Leone B. Moats, in memory of Mrs. Wallace Payne Moats, 1979",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/84616,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1982.249,false,true,97118,Costume Institute,Robe de Style,Robe de Style,French,,,,,Designer,,Paul Poiret,"French, Paris 1879–1944 Paris",,"Poiret, Paul",French,1879,1944,1925,1925,1925,silk,,"Gift of Mrs. Ivor Bevan, 1982",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/97118,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.188,false,true,121167,Costume Institute,Dress,"""Rosière""",French,,,,,Designer,,Paul Poiret,"French, Paris 1879–1944 Paris",,"Poiret, Paul",French,1879,1944,1911,1911,1911,linen,,"Catharine Breyer Van Bomel Foundation Fund, 2005",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/121167,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.191,false,true,121170,Costume Institute,Headdress,Flonflon,French,,,,,Designer,,Paul Poiret,"French, Paris 1879–1944 Paris",,"Poiret, Paul",French,1879,1944,ca. 1920,1915,1925,"silk, metallic thread, feathers",,"Purchase, Judith and Gerson Leiber Fund, 2005",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/121170,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.195,false,true,121174,Costume Institute,Tunic,Tunic,French,,,,,Designer,,Paul Poiret,"French, Paris 1879–1944 Paris",,"Poiret, Paul",French,1879,1944,ca. 1920,1915,1925,cotton,,"Catharine Breyer Van Bomel Foundation Fund, 2005",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/121174,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.196,false,true,121175,Costume Institute,Tunic,Tunic,French,,,,,Designer,,Paul Poiret,"French, Paris 1879–1944 Paris",,"Poiret, Paul",French,1879,1944,ca. 1920,1915,1925,cotton,,"Catharine Breyer Van Bomel Foundation Fund, 2005",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/121175,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.200,false,true,121192,Costume Institute,Coat,"""Manteau D'Auto""",French,,,,,Designer,,Paul Poiret,"French, Paris 1879–1944 Paris",,"Poiret, Paul",French,1879,1944,1912,1912,1912,"linen, silk, cellulose",,"Isabel Shults Fund, 2005",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/121192,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.201,false,true,121193,Costume Institute,Coat,Coat,French,,,,,Designer,,Paul Poiret,"French, Paris 1879–1944 Paris",,"Poiret, Paul",French,1879,1944,1918,1918,1918,"wool, rayon",,"Isabel Shults Fund, 2005",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/121193,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.203,false,true,121195,Costume Institute,Jacket,Jacket,French,,,,,Designer,,Paul Poiret,"French, Paris 1879–1944 Paris",,"Poiret, Paul",French,1879,1944,1912,1912,1912,"wool, cotton, rayon",,"Catharine Breyer Van Bomel Foundation Fund, 2005",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/121195,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.205,false,true,121197,Costume Institute,Coat,"""Pré Catelan""",French,,,,,Designer,,Paul Poiret,"French, Paris 1879–1944 Paris",,"Poiret, Paul",French,1879,1944,1918,1918,1918,"silk, metallic thread",,"Millia Davenport and Zipporah Fleisher Fund, 2005",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/121197,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.206,false,true,121198,Costume Institute,Headdress,Headdress,French,,,,,Designer,,Paul Poiret,"French, Paris 1879–1944 Paris",,"Poiret, Paul",French,1879,1944,ca. 1920,1915,1925,"cotton, metal, ceramic",,"Gerson and Judith Leiber Foundation Fund, 2005",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/121198,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.207,false,true,121199,Costume Institute,Coat,"""Paris""",French,,,,,Designer,,Paul Poiret,"French, Paris 1879–1944 Paris",,"Poiret, Paul",French,1879,1944,1919,1919,1919,"silk, wool, metallic thread",,"Purchase, Friends of The Costume Institute Gifts, 2005",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/121199,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.208,false,true,121200,Costume Institute,Headdress,Headdress,French,,,,,Designer,,Paul Poiret,"French, Paris 1879–1944 Paris",,"Poiret, Paul",French,1879,1944,ca. 1910,1905,1915,metal,,"Gerson and Judith Leiber Foundation Fund, 2005",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/121200,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.209,false,true,121201,Costume Institute,Jacket,"""Steppe""",French,,,,,Designer,,Paul Poiret,"French, Paris 1879–1944 Paris",,"Poiret, Paul",French,1879,1944,1912,1912,1912,"silk, wool, fur, cotton",,"Catharine Breyer Van Bomel Foundation Fund, 2005",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/121201,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.210,false,true,121202,Costume Institute,Dress,Mademoiselle,French,,,,,Designer,,Paul Poiret,"French, Paris 1879–1944 Paris",,"Poiret, Paul",French,1879,1944,1923,1923,1923,wool,,"Catharine Breyer Van Bomel Foundation Fund, 2005",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/121202,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.211,false,true,121203,Costume Institute,Hat,"""Bahia""",French,,,,,Designer,,Paul Poiret,"French, Paris 1879–1944 Paris",,"Poiret, Paul",French,1879,1944,1914,1914,1914,"silk, metallic thread, jet, glass, silver, cotton",,"Gerson and Judith Leiber Foundation Fund, 2005",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/121203,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.386,false,true,123612,Costume Institute,Dress,Dress,French,,,,,Designer,,Paul Poiret,"French, Paris 1879–1944 Paris",,"Poiret, Paul",French,1879,1944,1912,1912,1912,silk,,"Purchase, Friends of The Costume Institute Gifts, 2005",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/123612,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.513,false,true,129937,Costume Institute,Dress,Dress,French,,,,,Designer,Attributed to,Paul Poiret,"French, Paris 1879–1944 Paris",,"Poiret, Paul",French,1879,1944,1920s,1920,1929,silk,,"Purchase, Friends of The Costume Institute Gifts, 2005",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/129937,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.146,false,true,136302,Costume Institute,Dress,"""Irudree""",French,,,,,Designer,,Paul Poiret,"French, Paris 1879–1944 Paris",,"Poiret, Paul",French,1879,1944,1922,1922,1922,"metallic, silk",,"Purchase, Friends of The Costume Institute Gifts, 2007",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/136302,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2008.288,false,true,141903,Costume Institute,Opera coat,Opera coat,French,,,,,Designer,,Paul Poiret,"French, Paris 1879–1944 Paris",,"Poiret, Paul",French,1879,1944,1911,1911,1911,silk,,"Alfred Z. Solomon-Janet A. Sloane Endowment Fund, 2008",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/141903,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -C.I.51.47,false,true,81560,Costume Institute,Dress,Dress,French,,,,,Designer,,Paul Poiret,"French, Paris 1879–1944 Paris",,"Poiret, Paul",French,1879,1944,ca. 1925,1920,1930,"silk, leather",,"Gift of Mrs. Kenneth Maconochie, 1951",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/81560,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1973.279.1,false,true,81684,Costume Institute,Shawl,Shawl,French,,,,,Designer,,Paul Poiret,"French, Paris 1879–1944 Paris",,"Poiret, Paul",French,1879,1944,1920s,1920,1929,"silk, metal",,"Gift of Henry F. Callahan, 1973",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/81684,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1978.367.2,false,true,97117,Costume Institute,Dance dress,Dance dress,French,,,,,Designer,,Paul Poiret,"French, Paris 1879–1944 Paris",,"Poiret, Paul",French,1879,1944,ca. 1926,1921,1931,"silk, cotton, plastic, glass",,"Gift of Mary Van Rensselaer Thayer, 1978",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/97117,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1982.281.1,false,true,113915,Costume Institute,Picture hat,Picture hat,French,,,,,Designer,,Paul Poiret,"French, Paris 1879–1944 Paris",,"Poiret, Paul",French,1879,1944,ca. 1921,1916,1926,"horse hair, cotton",,"Purchase, Gifts from Various Donors Fund, 1982",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/113915,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1982.350.2,false,true,81563,Costume Institute,Opera coat,Opera coat,French,,,,,Designer,,Paul Poiret,"French, Paris 1879–1944 Paris",,"Poiret, Paul",French,1879,1944,1912,1912,1912,"silk, metal",,"Purchase, Irene Lewisohn Bequest, 1982",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/81563,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1983.115.2,false,true,105663,Costume Institute,Wedding Dress,Wedding dress,French,,,,,Designer,,Paul Poiret,"French, Paris 1879–1944 Paris",,"Poiret, Paul",French,1879,1944,1925,1925,1925,"silk, metal",,"Given by the children of Mrs. Kenneth F. Simpson in memory of her parents, Mr. & Mrs. Nathan T. Porter, 1983",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/105663,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"1983.8a, b",true,true,81781,Costume Institute,Fancy dress costume,Fancy dress costume,French,,,,,Designer,,Paul Poiret,"French, Paris 1879–1944 Paris",,"Poiret, Paul",French,1879,1944,1911,1911,1911,"metal, silk, cotton",,"Purchase, Irene Lewisohn Trust Gift, 1983",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/81781,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.516.7,false,true,109411,Costume Institute,Teddy,Teddy,French,,,,,Designer,,Paul Poiret,"French, Paris 1879–1944 Paris",,"Poiret, Paul",French,1879,1944,1920s,1920,1929,silk,,"Gift of Miriam W. Coletti, 1986",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/109411,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1988.226.2,false,true,97119,Costume Institute,Coat,Coat,French,,,,,Designer,,Paul Poiret,"French, Paris 1879–1944 Paris",,"Poiret, Paul",French,1879,1944,ca. 1925,1920,1930,"silk, fur",,"Gift of Mrs. John Campbell White, 1988",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/97119,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.165.2,false,true,105666,Costume Institute,Dress,Dress,French,,,,,Designer,,Paul Poiret,"French, Paris 1879–1944 Paris",,"Poiret, Paul",French,1879,1944,1928,1928,1928,"silk, glass",,"Gift of Mary C. Hartshorne, 1989",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/105666,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -C.I.50.117,false,true,82558,Costume Institute,Dress,Dress,French,,,,,Designer,,Paul Poiret,"French, Paris 1879–1944 Paris",,"Poiret, Paul",French,1879,1944,1925,1925,1925,"wool, silk",,"Gift of Mrs. Alfred Rheinstein, 1950",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/82558,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -C.I.64.7.2,false,true,84568,Costume Institute,Coat,Coat,French,,,,,Designer,,Paul Poiret,"French, Paris 1879–1944 Paris",,"Poiret, Paul",French,1879,1944,1923,1923,1923,wool,,"Gift of Mrs. David J. Colton, 1964",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/84568,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.516.16,false,true,116106,Costume Institute,Scarf,Scarf,French,,,,,Designer,,Paul Poiret,"French, Paris 1879–1944 Paris",,"Poiret, Paul",French,1879,1944,1920s,1920,1929,silk,,"Gift of Miriam W. Coletti, 1986",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/116106,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2007.58a, b",false,true,131994,Costume Institute,Boots,"""Favereau""",French,,,,,Designer,,Paul Poiret,"French, Paris 1879–1944 Paris",,"Poiret, Paul",French,1879,1944,1920,1920,1920,"leather, wood",,"Alfred Z. Solomon-Janet A. Sloane Endowment Fund, 2007",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/131994,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.300.39,false,true,158888,Costume Institute,Evening cape,Evening cape,French,,,,,Designer,,Paul Poiret,"French, Paris 1879–1944 Paris",,"Poiret, Paul",French,1879,1944,ca. 1920,1918,1922,silk,,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Brooklyn Museum Collection",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/158888,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -C.I.43.85.1,false,true,97120,Costume Institute,Dinner dress,Dinner dress,French,,,,,Designer,,Paul Poiret,"French, Paris 1879–1944 Paris",,"Poiret, Paul",French,1879,1944,ca. 1923,1923,1923,silk,,"Gift of Mrs. Muriel Draper, 1943",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/97120,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -C.I.51.48.3,false,true,105655,Costume Institute,Evening dress,Evening dress,French,,,,,Designer,,Paul Poiret,"French, Paris 1879–1944 Paris",,"Poiret, Paul",French,1879,1944,1924,1924,1924,"silk, metallic",,"Gift of Mrs. Robert L. Dodge, 1951",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/105655,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -C.I.51.52.1,false,true,105667,Costume Institute,Fancy dress costume,Fancy dress costume,French,,,,,Designer,,Paul Poiret,"French, Paris 1879–1944 Paris",,"Poiret, Paul",French,1879,1944,1900–1944,1900,1944,"silk, metallic, simulated pearls",,"Gift of Mrs. Mary S. Thomas, 1951",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/105667,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -C.I.58.34.8,false,true,105665,Costume Institute,Evening dress,Evening dress,French,,,,,Designer,,Paul Poiret,"French, Paris 1879–1944 Paris",,"Poiret, Paul",French,1879,1944,1927,1927,1927,"silk, metal, plastic",,"Gift of Mrs. John Chambers Hughes, 1958",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/105665,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -C.I.61.40.4,true,true,81123,Costume Institute,Coat,Coat,French,,,,,Designer,,Paul Poiret,"French, Paris 1879–1944 Paris",,"Poiret, Paul",French,1879,1944,ca. 1919,1919,1919,"silk, wool, fur, leather",,"Gift of Mrs. David J. Colton, 1961",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/81123,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2005.190a, b",false,true,121169,Costume Institute,Dress,Butard,French,,,,,Designer,,Paul Poiret,"French, Paris 1879–1944 Paris",,"Poiret, Paul",French,1879,1944,1912,1912,1912,"a, b) linen",,"Millia Davenport and Zipporah Fleisher Fund, 2005",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/121169,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2005.367a, b",false,true,123613,Costume Institute,Ensemble,Ensemble,French,,,,,Designer,,Paul Poiret,"French, Paris 1879–1944 Paris",,"Poiret, Paul",French,1879,1944,1912,1912,1912,"a) silk, fur; b) silk",,"Purchase, Friends of The Costume Institute Gifts, 2005",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/123613,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.300.316,false,true,158066,Costume Institute,Evening cape,Evening cape,French,,,,,Designer,,Paul Poiret,"French, Paris 1879–1944 Paris",,"Poiret, Paul",French,1879,1944,ca. 1920,1918,1922,"wool, metal, silk",,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Ogden Goelet, Peter Goelet, and Madison Clews, 1962",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/158066,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.300.549,false,true,159091,Costume Institute,Evening coat,Evening coat,French,,,,,Designer,,Paul Poiret,"French, Paris 1879–1944 Paris",,"Poiret, Paul",French,1879,1944,spring/summer 1917,1917,1917,silk,,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Designated Purchase Fund, 1983",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/159091,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.300.560,false,true,159105,Costume Institute,Jacket,Jacket,French,,,,,Designer,Attributed to,Paul Poiret,"French, Paris 1879–1944 Paris",,"Poiret, Paul",French,1879,1944,1924,1924,1924,"wool, cotton",,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Mary Sefton Thomas, 1986",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/159105,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.193a–g,false,true,121172,Costume Institute,Ensemble,"""Théâtre des Champs-Élysées""",French,,,,,Designer,,Paul Poiret,"French, Paris 1879–1944 Paris",,"Poiret, Paul",French,1879,1944,1913,1913,1913,"a,c-e) silk, rhinestones; b) silk; f, g) silk, leather",,"Purchase, The Paul D. Schurgot Foundation Inc. Gift, 2005",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/121172,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.194a–c,false,true,121173,Costume Institute,Evening ensemble,Evening ensemble,French,,,,,Designer,,Paul Poiret,"French, Paris 1879–1944 Paris",,"Poiret, Paul",French,1879,1944,ca. 1920,1915,1925,"a, b) silk; c) silk, enamel",,"Catharine Breyer Van Bomel Foundation Fund, 2005",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/121173,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.204a–c,false,true,121196,Costume Institute,Ensemble,"""Feuille d'automne""",French,,,,,Designer,,Paul Poiret,"French, Paris 1879–1944 Paris",,"Poiret, Paul",French,1879,1944,1916,1916,1916,"a, b) silk; c) fur, silk",,"Catharine Breyer Van Bomel Foundation Fund, 2005",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/121196,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.300.1289,false,true,155987,Costume Institute,Evening dress,Evening dress,French,,,,,Designer,,Paul Poiret,"French, Paris 1879–1944 Paris",,"Poiret, Paul",French,1879,1944,1910,1910,1910,"silk, linen",,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Ogden Goelet, Peter Goelet and Madison Clews in memory of Mrs. Henry Clews, 1961",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/155987,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.300.1339,false,true,156043,Costume Institute,Evening coat,Evening coat,French,,,,,Designer,Attributed to,Paul Poiret,"French, Paris 1879–1944 Paris",,"Poiret, Paul",French,1879,1944,ca. 1925,1923,1927,synthetic,,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Mrs. John Chapman, 1967",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/156043,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.300.1368,false,true,156074,Costume Institute,Evening coat,Evening coat,French,,,,,Designer,,Paul Poiret,"French, Paris 1879–1944 Paris",,"Poiret, Paul",French,1879,1944,ca. 1912,1910,1914,"silk, metal",,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Purchased with funds given by Mrs. Carl L. Selden, 1985",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/156074,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.300.2111,false,true,156902,Costume Institute,Cloche,Cloche,French,,,,,Designer,,Paul Poiret,"French, Paris 1879–1944 Paris",,"Poiret, Paul",French,1879,1944,ca. 1923,1921,1925,"wool, leather, silk",,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Mrs. Frederick H. Prince, Jr., 1967",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/156902,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.300.2503,false,true,157337,Costume Institute,Evening dress,Evening dress,French,,,,,Designer,,Paul Poiret,"French, Paris 1879–1944 Paris",,"Poiret, Paul",French,1879,1944,1912–14,1912,1914,silk,,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Mrs. Andrew J. Love, 1962",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/157337,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.300.3360,false,true,158288,Costume Institute,Evening dress,"""Robe Sabat""",French,,,,,Designer,,Paul Poiret,"French, Paris 1879–1944 Paris",,"Poiret, Paul",French,1879,1944,1921,1921,1921,"silk, metal",,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Augustus Graham School of Design Fund, 1973",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/158288,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.300.8212,false,true,175230,Costume Institute,Evening coat,Evening coat,French,,,,,Designer,,Paul Poiret,"French, Paris 1879–1944 Paris",,"Poiret, Paul",French,1879,1944,1924,1924,1924,"Silk, synthetic",,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Mary Sefton Thomas, 1986",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/175230,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"C.I.44.82a, b",false,true,105658,Costume Institute,Dress,Dress,French,,,,,Designer,,Paul Poiret,"French, Paris 1879–1944 Paris",,"Poiret, Paul",French,1879,1944,1922–24,1922,1924,silk,,"Gift of Mrs. Dudley Wadsworth, 1944",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/105658,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"1978.367.3a, b",false,true,105660,Costume Institute,Evening ensemble,Evening ensemble,French,,,,,Designer,,Paul Poiret,"French, Paris 1879–1944 Paris",,"Poiret, Paul",French,1879,1944,1924,1924,1924,"cotton, fur, metallic thread, silk",,"Gift of Mary Van Rensselaer Thayer, 1978",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/105660,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"1978.367.4a, b",false,true,105659,Costume Institute,Evening dress,Evening dress,French,,,,,Designer,,Paul Poiret,"French, Paris 1879–1944 Paris",,"Poiret, Paul",French,1879,1944,1923,1923,1923,"silk, metallic thread",,"Gift of Mary Van Rensselaer Thayer, 1978",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/105659,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"1988.226.1a, b",false,true,105661,Costume Institute,Ensemble,Ensemble,French,,,,,Designer,,Paul Poiret,"French, Paris 1879–1944 Paris",,"Poiret, Paul",French,1879,1944,ca. 1925,1920,1930,"silk, felt, metallic thread",,"Gift of Mrs. John Campbell White, 1988",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/105661,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"1995.588.3a, b",false,true,80338,Costume Institute,Ensemble,Ensemble,French,,,,,Designer,,Paul Poiret,"French, Paris 1879–1944 Paris",,"Poiret, Paul",French,1879,1944,ca. 1922,1922,1922,silk,,"Gift of Miriam K. W. Coletti, 1995",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/80338,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1979.569.6a–c,false,true,105664,Costume Institute,Ensemble,Ensemble,French,,,,,Designer,,Paul Poiret,"French, Paris 1879–1944 Paris",,"Poiret, Paul",French,1879,1944,1925–26,1925,1926,"wool, metal, leather",,"Gift of Mrs. C. O. Kalman, 1979",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/105664,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1983.115.3a–k,false,true,105662,Costume Institute,Evening dress,Evening dress,French,,,,,Designer,,Paul Poiret,"French, Paris 1879–1944 Paris",,"Poiret, Paul",French,1879,1944,ca. 1925,1920,1930,"silk, metal",,"Gift of the children of Mrs. Kenneth F. Simpson, in her memory, 1983",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/105662,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"C.I.43.85.2a, b",false,true,82549,Costume Institute,Evening dress,Evening dress,French,,,,,Designer,,Paul Poiret,"French, Paris 1879–1944 Paris",,"Poiret, Paul",French,1879,1944,1922–23,1922,1923,silk,,"Gift of Mrs. Muriel Draper, 1943",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/82549,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"C.I.50.84.2a, b",false,true,97388,Costume Institute,Evening ensemble,Evening ensemble,French,,,,,Designer,,Paul Poiret,"French, Paris 1879–1944 Paris",,"Poiret, Paul",French,1879,1944,fall/winter 1928–29,1928,1929,silk,,"Gift of Miss Frances McFadden, 1950",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/97388,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"C.I.51.48.1a, b",false,true,105669,Costume Institute,Fancy dress costume,Fancy dress costume,French,,,,,Designer,,Paul Poiret,"French, Paris 1879–1944 Paris",,"Poiret, Paul",French,1879,1944,20th century,1900,1944,"silk, metallic, synthetic gems",,"Gift of Mrs. Robert L. Dodge, 1951",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/105669,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"C.I.51.48.4a, b",false,true,85419,Costume Institute,Evening dress,Evening dress,French,,,,,Designer,,Paul Poiret,"French, Paris 1879–1944 Paris",,"Poiret, Paul",French,1879,1944,ca. 1925,1920,1930,"metallic, simulated pearls",,"Gift of Mrs. Robert L. Dodge, 1951",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/85419,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"C.I.51.48.5a, b",false,true,105656,Costume Institute,Evening dress,Evening dress,French,,,,,Designer,,Paul Poiret,"French, Paris 1879–1944 Paris",,"Poiret, Paul",French,1879,1944,1920s,1920,1929,"sequins, silk, metallic, beads",,"Gift of Mrs. Robert L. Dodge, 1951",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/105656,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -C.I.58.34.7a–c,false,true,97121,Costume Institute,Afternoon ensemble,Afternoon ensemble,French,,,,,Designer,,Paul Poiret,"French, Paris 1879–1944 Paris",,"Poiret, Paul",French,1879,1944,1927,1927,1927,silk,,"Gift of Mrs. John Chambers Hughes, 1958",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/97121,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2009.300.1304a, b",false,true,156005,Costume Institute,Evening dress,Evening dress,French,,,,,Designer,,Paul Poiret,"French, Paris 1879–1944 Paris",,"Poiret, Paul",French,1879,1944,ca. 1930,1928,1932,silk,,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Mrs. Anthony Wilson, 1963",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/156005,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2009.300.7190a, b",false,true,174318,Costume Institute,Evening dress,Evening dress,French,,,,,Designer,,Paul Poiret,"French, Paris 1879–1944 Paris",,"Poiret, Paul",French,1879,1944,ca. 1928,1926,1930,Silk,,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Mrs. Peter W. Lyon, 1962",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/174318,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2009.300.7309a, b",false,true,175768,Costume Institute,Evening dress,Evening dress,French,,,,,Designer,,Paul Poiret,"French, Paris 1879–1944 Paris",,"Poiret, Paul",French,1879,1944,ca. 1932,1930,1934,Silk,,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Mrs. Anthony Wilson, 1963",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/175768,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -C.I.51.70.19a–c,false,true,81676,Costume Institute,Evening dress,"""Arrow of Gold""",French,,,,,Designer,,Paul Poiret,"French, Paris 1879–1944 Paris",,"Poiret, Paul",French,1879,1944,1925,1925,1925,"silk, metallic thread",,"Gift of Mrs. Robert A. Lovett, 1951",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/81676,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.300.1094a–g,false,true,155770,Costume Institute,Evening ensemble,Evening ensemble,French,,,,,Designer,,Charles Frederick Worth,"French (born England), Bourne 1825–1895 Paris",,"Worth, Charles Frederick",French,1825,1895,1887,1887,1887,"silk, metal",,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Edith Gardiner, 1926",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/155770,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2010.487.1,false,true,159717,Costume Institute,Dolman,Dolman,French,,,,,Designer,,Emile Pingat,"French, active 1860–96",,Pingat Emile,French,1860,1896,1880s,1880,1889,"silk, linen",,"Gift of Christopher Scholz and Ines Elskop, 2010",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/159717,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"C.I.37.59.1a, b",false,true,107660,Costume Institute,Dress,Dress,French,,,,,Designer,,Jacques Doucet,"French, Paris 1853–1929 Paris",,"Doucet, Jacques",French,1853,1929,1880s,1880,1889,silk,,"Gift of Mrs. M. Villone, 1937",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/107660,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2009.300.7758a, b",false,true,174797,Costume Institute,Promenade dress,Promenade dress,French,,,,,Designer,,Emile Pingat,"French, active 1860–96",,Pingat Emile,French,1860,1896,ca. 1888,1886,1890,"Silk, metallic",,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Anonymous gift in memory of Mrs. John Roebling, 1970",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/174797,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1977.69,false,true,84504,Costume Institute,Evening dress,Evening dress,British,,,,,Design House,,Callot Soeurs,"French, active 1895–1937",,Callot Soeurs,French,1895,1937,ca. 1930,1925,1935,"silk, metallic thread",,"Gift of Reneé C. Rinaldi and Michelle R. Rinaldi, 1977",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/84504,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -C.I.48.38.2,false,true,106772,Costume Institute,Cloak,Cloak,American,,,,,Designer,,Revillon Frères,"French, founded 1723",,Revillon Frères,French,1723,1850,1919,1919,1919,silk,,"Gift of Mrs. Carlo Vicario, 1948",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/106772,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"1979.346.121a, b",false,true,86013,Costume Institute,Dress,Dress,American,,,,,Designer,Attributed to,Callot Soeurs,"French, active 1895–1937",,Callot Soeurs,French,1895,1937,ca. 1924,1919,1929,silk,,"Gift of The New York Historical Society, 1979",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/86013,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -C.I.62.36.1a–d,false,true,94892,Costume Institute,Evening ensemble,Evening ensemble,European,,,,,Designer,,Jeanne Hallée,"French, 1880–1914",,Hallée Jeanne,French,1880,1914,1897–98,1897,1898,"(a–d) silk; (c, d) leather",,"Gift of Mrs. Ogden W. Ross, 1962",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/94892,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.303.1,false,true,127572,Costume Institute,Parasol,Parasol,Japanese,,,,,Designer,Attributed to,Paul Poiret,"French, Paris 1879–1944 Paris",,"Poiret, Paul",French,1879,1944,ca. 1910,1905,1915,[no medium available],,"Gift of Elizabeth A. Tilson, 1975",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/127572,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -C.I.62.55.1,false,true,86190,Costume Institute,Dress,Dress,Indian,,,,,Department Store,,Liberty & Co.,"British, founded London, 1875",,Liberty & Co.,British,1875,2050,late 18th–early 19th century,1775,1825,[no medium available],,"Gift of Mr. and Mrs. Clarence Stein, 1962",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/86190,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -C.I.44.99.4a–c,false,true,106302,Costume Institute,Evening dress,Evening dress,probably British,,,,,Designer,Attributed to,Lucile,"British, 1863–1935",,Lucile,British,1863,1935,ca. 1917,1912,1922,silk,,"Gift of Miss Isabel Shults and Mrs. Bertha Shults Dougherty, 1944",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/106302,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.300.8103,false,true,175128,Costume Institute,Dress,Dress,French,,,,,Maker,,House of Lucile,"British, founded 1895",,Lucile House of,British,1895,1895,ca. 1925,1923,1927,Silk,,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Designated Purchase Fund, 1984",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/175128,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2001.576a, b",false,true,83042,Costume Institute,Shoes,Shoes,British,,,,,Manufacturer,,E. Pattison,"British, 1800–1850",,E. Pattison,British,1800,1850,1806–15,1806,1815,"(a, b) leather, silk",,"Purchase, Gerson and Judith Leiber Foundation Gift, 2001",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/83042,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -C.I.68.53.9,false,true,81527,Costume Institute,Dress,Dress,British,,,,,Design House,,Liberty & Co.,"British, founded London, 1875",,Liberty & Co.,British,1875,2050,1891,1891,1891,silk,,"Gift of Mrs. James G. Flockhart, 1968",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/81527,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1985.155,false,true,81513,Costume Institute,Evening dress,Evening dress,British,,,,,Design House,Attributed to,Liberty & Co.,"British, founded London, 1875",,Liberty & Co.,British,1875,2050,1880s,1880,1889,silk,,"Purchase, Gifts from Various Donors, 1985",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/81513,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1995.294,false,true,80875,Costume Institute,Evening dress,Evening dress,British,,,,,Designer,,Lucile,"British, 1863–1935",,Lucile,British,1863,1935,1910s,1910,1919,metallic thread,,"Purchase, New School for Social Research Fund, 1993",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/80875,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -C.I.56.48,false,true,106869,Costume Institute,Afternoon dress,Afternoon dress,British,,,,,Designer,,Lucile,"British, 1863–1935",,Lucile,British,1863,1935,1913,1913,1913,"silk, metal",,"Gift of Mrs. E. Theophilus MacDermott, 1956",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/106869,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -C.I.68.75,false,true,94636,Costume Institute,Evening dress,Evening dress,British,,,,,Designer,,Lucile,"British, 1863–1935",,Lucile,British,1863,1935,1915–16,1915,1916,[no medium available],,"Gift of Miss Barbara Jane Pentlarge, 1968",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/94636,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.217.1,false,true,94669,Costume Institute,Evening dress,Evening dress,British,,,,,Designer,,Lucile,"British, 1863–1935",,Lucile,British,1863,1935,ca. 1916,1911,1921,"silk, cotton",,"Gift of the Staten Island Institute of Arts & Sciences, pursuant to the instructions of Mr. and Mrs. Elisha Dyer, 1976",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/94669,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.217.3,false,true,94637,Costume Institute,Evening dress,Evening dress,British,,,,,Designer,,Lucile,"British, 1863–1935",,Lucile,British,1863,1935,ca. 1918,1913,1923,"silk, cotton, metal",,"Gift of the Staten Island Institute of Arts & Sciences, pursuant to the instructions of Mr. and Mrs. Elisha Dyer, 1976",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/94637,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.217.6,false,true,94670,Costume Institute,Evening dress,Evening dress,British,,,,,Designer,,Lucile,"British, 1863–1935",,Lucile,British,1863,1935,1922,1922,1922,"silk, cotton",,"Gift of the Staten Island Institute of Arts & Sciences, pursuant to the instructions of Mr. and Mrs. Elisha Dyer, 1976",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/94670,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1979.369.2,false,true,94672,Costume Institute,Evening dress,Evening dress,British,,,,,Designer,,Lucile,"British, 1863–1935",,Lucile,British,1863,1935,ca. 1913,1908,1918,"silk, cotton, glass, plastic",,"Gift of Mrs. Kingsley Mabon, 1979",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/94672,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1979.569.2,false,true,94663,Costume Institute,Afternoon dress,Afternoon dress,British,,,,,Designer,,Lucile,"British, 1863–1935",,Lucile,British,1863,1935,1917,1917,1917,"silk, cotton",,"Gift of Mrs. C. O. Kalman, 1979",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/94663,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1979.569.4,false,true,94664,Costume Institute,Evening dress,Evening dress,British,,,,,Designer,,Lucile,"British, 1863–1935",,Lucile,British,1863,1935,1919,1919,1919,"silk, cotton",,"Gift of Mrs. C. O. Kalman, 1979",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/94664,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1980.126.32,false,true,107456,Costume Institute,Hat,Hat,British,,,,,Designer,,Lucile,"British, 1863–1935",,Lucile,British,1863,1935,1914–20,1914,1920,"silk, glass, metal, cotton",,"Gift of Mrs. Alan L. Corey Jr., Mrs. William T. Newbold, and Mrs. A.G. Paine, II, 1980",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/107456,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -C.I.47.57.1,false,true,94665,Costume Institute,Dance dress,Dance dress,British,,,,,Designer,,Lucile,"British, 1863–1935",,Lucile,British,1863,1935,1939,1939,1939,"silk, fur, metallic thread",,"Gift of Irene Castle (Mrs. George Enzinger), 1947",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/94665,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -C.I.47.57.2,false,true,102428,Costume Institute,Muff,Muff,British,,,,,Designer,,Lucile,"British, 1863–1935",,Lucile,British,1863,1935,1914,1914,1914,fur,,"Gift of Irene Castle (Mrs. George Enzinger), 1947",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/102428,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -C.I.47.57.4,false,true,108239,Costume Institute,Hat,Hat,British,,,,,Designer,,Lucile,"British, 1863–1935",,Lucile,British,1863,1935,1914–19,1914,1919,"silk, cotton",,"Gift of Irene Castle (Mrs. George Enzinger), 1947",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/108239,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -C.I.47.57.5,false,true,94635,Costume Institute,Dance dress,Dance dress,British,,,,,Designer,,Lucile,"British, 1863–1935",,Lucile,British,1863,1935,ca. 1921,1916,1926,"silk, artificial pearls, horsehair",,"Gift of Irene Castle (Mrs. George Enzinger), 1947",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/94635,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -C.I.47.57.6,false,true,106877,Costume Institute,Dance dress,Dance dress,British,,,,,Designer,,Lucile,"British, 1863–1935",,Lucile,British,1863,1935,1914–29,1914,1929,"silk, metal thread, glass, horsehair, cotton",,"Gift of Irene Castle (Mrs. George Enzinger), 1947",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/106877,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"1978.112a, b",false,true,95130,Costume Institute,Wedding Dress,Wedding dress,British,,,,,Designer,,Lucile,"British, 1863–1935",,Lucile,British,1863,1935,1915,1915,1915,"silk, cotton, plastic, metal",,"Gift of Charles V. Hickox, 1978",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/95130,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -C.I.44.64.33,false,true,94667,Costume Institute,Evening dress,Evening dress,British,,,,,Designer,,Lucile,"British, 1863–1935",,Lucile,British,1863,1935,ca. 1915,1910,1920,silk,,"Gift of Miss Isabel Shults, 1944",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/94667,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -C.I.44.64.36,false,true,94668,Costume Institute,Evening dress,Evening dress,British,,,,,Designer,,Lucile,"British, 1863–1935",,Lucile,British,1863,1935,1914–16,1914,1916,silk,,"Gift of Miss Isabel Shults, 1944",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/94668,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -C.I.44.64.38,false,true,108238,Costume Institute,Afternoon dress,Afternoon dress,British,,,,,Designer,,Lucile,"British, 1863–1935",,Lucile,British,1863,1935,ca. 1914,1909,1919,silk,,"Gift of Miss Isabel Shults, 1944",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/108238,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -C.I.44.64.39,false,true,107905,Costume Institute,Evening dress,Evening dress,British,,,,,Designer,,Lucile,"British, 1863–1935",,Lucile,British,1863,1935,1916–18,1916,1918,[no medium available],,"Gift of Miss Isabel Shults, 1944",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/107905,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -C.I.44.64.43,false,true,94662,Costume Institute,Evening dress,Evening dress,British,,,,,Designer,,Lucile,"British, 1863–1935",,Lucile,British,1863,1935,1914,1914,1914,silk,,"Gift of Miss Isabel Shults, 1944",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/94662,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"C.I.39.41a, b",false,true,94666,Costume Institute,Dress,Dress,British,,,,,Designer,,Lucile,"British, 1863–1935",,Lucile,British,1863,1935,1900–1935,1900,1935,silk,,"Gift of Mrs. Edna Woolman Newton, 1939",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/94666,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"1978.288.1a, b",false,true,82580,Costume Institute,Dress,Dress,British,,,,,Designer,,Lucile,"British, 1863–1935",,Lucile,British,1863,1935,1916–17,1916,1917,"silk, cotton",,"Gift of Julia B. Henry, 1978",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/82580,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"1979.569.3a, b",false,true,94638,Costume Institute,Dinner dress,Dinner dress,British,,,,,Designer,,Lucile,"British, 1863–1935",,Lucile,British,1863,1935,1918,1918,1918,"silk, cotton, metal",,"Gift of Mrs. C. O. Kalman, 1979",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/94638,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"C.I.47.57.3a, b",false,true,107906,Costume Institute,Dance dress,Dance dress,British,,,,,Designer,,Lucile,"British, 1863–1935",,Lucile,British,1863,1935,1914,1914,1914,"silk, cotton",,"Gift of Irene Castle (Mrs. George Enzinger), 1947",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/107906,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2009.300.3307a, b",false,true,158230,Costume Institute,Suit,Suit,British,,,,,Designer,,Lucile,"British, 1863–1935",,Lucile,British,1863,1935,1910–12,1910,1912,"wool, silk, metal",,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Mrs. Frederick H. Prince, Jr., 1967",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/158230,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -C.I.44.64.37a–c,false,true,94661,Costume Institute,Evening dress,Evening dress,British,,,,,Designer,,Lucile,"British, 1863–1935",,Lucile,British,1863,1935,1916–18,1916,1918,silk,,"Gift of Miss Isabel Shults, 1944",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/94661,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1978.582.98,false,true,113127,Costume Institute,Boater,Boater,British,,,,,Designer,,James Lock & Co. Ltd,"British, founded 1676",,Lock & Co. Ltd James,British,1676,1676,1950s,1950,1959,straw,,"Gift of Marvin B. Patterson (Mrs. Jefferson Patterson), 1978",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/113127,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.291.11,false,true,112803,Costume Institute,Fedora,Fedora,British,,,,,Designer,,James Lock & Co. Ltd,"British, founded 1676",,Lock & Co. Ltd James,British,1676,1676,1935–49,1935,1949,wool (probably),,"Gift of Jane de Rochemont, 1986",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/112803,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.291.14,false,true,112805,Costume Institute,Derby,Derby,British,,,,,Designer,,James Lock & Co. Ltd,"British, founded 1676",,Lock & Co. Ltd James,British,1676,1676,1930–49,1930,1949,wool (probably),,"Gift of Jane de Rochemont, 1986",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/112805,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2006.271.41,false,true,129921,Costume Institute,Hat,Hat,British,,,,,Designer,,James Lock & Co. Ltd,"British, founded 1676",,Lock & Co. Ltd James,British,1676,1676,1970s,1970,1979,"wool, silk",,"Bequest of Yolande Fielding–Scheftel, 2006",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/129921,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.300.2214,false,true,157015,Costume Institute,Riding Hat,Riding hat,British,,,,,Designer,,James Lock & Co. Ltd,"British, founded 1676",,Lock & Co. Ltd James,British,1676,1676,ca. 1930,1928,1932,"fur, wool, silk",,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Mrs. William Randolph Hearst, Jr., 1985",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/157015,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1978.472.2a–j,false,true,84675,Costume Institute,Riding Ensemble,Riding ensemble,British,,,,,Designer,(d) Hat by,James Lock & Co. Ltd,"British, founded 1676",,Lock & Co. Ltd James,British,1676,1676,ca. 1936,1931,1941,a) cotton b) silk c) Linen d) silk e–h) leather i) silk j) leather,,"Gift of Mrs. C. Suydam Cutting, 1978",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/84675,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2003.594.1a–j,false,true,98589,Costume Institute,Ensemble,Ensemble,British,,,,,Designer,"(g, h) Hat by",James Lock & Co. Ltd,"British, founded 1676",,Lock & Co. Ltd James,British,1676,1676,ca. 1950,1945,1955,"a) wool, silk; b-d) wool; e) cotton; f-h) wool; i,j) leather",,"Gift of Mrs. Doreen Simmons, 2003",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/98589,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"1981.301a–i,l–q",false,true,108600,Costume Institute,Hunting ensemble,Hunting ensemble,British,,,,,Designer,(l),James Lock & Co. Ltd,"British, founded 1676",,Lock & Co. Ltd James,British,1676,1676,ca. 1930,1925,1935,"wool, cotton, leather",,"Gift of Thomas A. Bradley, Jr., 1981",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/108600,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.300.3384,false,true,158314,Costume Institute,Tea gown,Tea gown,British,,,,,Designer,,Liberty & Co.,"British, founded London, 1875",,Liberty & Co.,British,1875,2050,ca. 1885,1883,1887,silk,,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Designated Purchase Fund, 1986",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/158314,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.300.2104,false,true,156894,Costume Institute,Hat,Hat,British,,,,,Designer,,William Charles Brown,"British, active late 19th century",,"Brown, William Charles",British,1800,1899,ca. 1870,1868,1872,"straw, silk, feathers",,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Mrs. Frederick H. Prince, Jr., 1967",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/156894,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"C.I.46.46.6a, b",false,true,86742,Costume Institute,Dress,Dress,American,,,,,Designer,,Lucile,"British, 1863–1935",,Lucile,British,1863,1935,1910–12,1910,1912,silk,,"Gift of Mrs. Harrison Williams, Lady Mendl, and Mrs. Ector Munn, 1946",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/86742,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2009.300.4707a, b",false,true,168912,Costume Institute,Evening shoes,Evening shoes,French,,,,,Maker,,Pierre Yantorny,"Italian, 1874–1936",,"Yantorny, Pierre",Italian,1874,1936,1914–19,1914,1919,"Silk, rhinestones",,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Mercedes de Acosta, 1953",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/168912,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2009.300.5679a, b",false,true,169894,Costume Institute,Shoes,Shoes,French,,,,,Maker,,Pierre Yantorny,"Italian, 1874–1936",,"Yantorny, Pierre",Italian,1874,1936,1925–30,1925,1930,"Leather, metal",,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Mrs. Edward G. Sparrow, 1969",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/169894,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2009.300.5681a, b",false,true,169895,Costume Institute,Shoes,Shoes,French,,,,,Maker,,Pierre Yantorny,"Italian, 1874–1936",,"Yantorny, Pierre",Italian,1874,1936,1925–30,1925,1930,"Leather, silk, metal",,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Mrs. Edward G. Sparrow, 1969",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/169895,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2009.300.5683a, b",false,true,169896,Costume Institute,Shoes,Shoes,French,,,,,Maker,,Pierre Yantorny,"Italian, 1874–1936",,"Yantorny, Pierre",Italian,1874,1936,1925–30,1925,1930,Leather,,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Mrs. Edward G. Sparrow, 1969",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/169896,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2009.300.5686a, b",false,true,169898,Costume Institute,Shoes,Shoes,French,,,,,Maker,,Pierre Yantorny,"Italian, 1874–1936",,"Yantorny, Pierre",Italian,1874,1936,1925–30,1925,1930,Leather,,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Mrs. Edward G. Sparrow, 1969",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/169898,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.300.4708a–f,false,true,168913,Costume Institute,Shoe Trees,Shoe trees,French,,,,,Maker,,Pierre Yantorny,"Italian, 1874–1936",,"Yantorny, Pierre",Italian,1874,1936,1914–19,1914,1919,"Wood, metal",,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Mercedes de Acosta, 1953",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/168913,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.300.5680a–f,false,true,170470,Costume Institute,Shoe Trees,Shoe trees,French,,,,,Maker,,Pierre Yantorny,"Italian, 1874–1936",,"Yantorny, Pierre",Italian,1874,1936,1925–30,1925,1930,"Wood, metal",,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Mrs. Edward G. Sparrow, 1969",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/170470,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.300.5682a–f,false,true,170471,Costume Institute,Shoe Trees,Shoe trees,French,,,,,Maker,,Pierre Yantorny,"Italian, 1874–1936",,"Yantorny, Pierre",Italian,1874,1936,1925–30,1925,1930,"Wood, metal",,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Mrs. Edward G. Sparrow, 1969",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/170471,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.300.5684a–d,false,true,169897,Costume Institute,Shoes,Shoes,French,,,,,Maker,,Pierre Yantorny,"Italian, 1874–1936",,"Yantorny, Pierre",Italian,1874,1936,1925–30,1925,1930,Leather,,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Mrs. Edward G. Sparrow, 1969",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/169897,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.300.5685a–f,false,true,170472,Costume Institute,Shoe Trees,Shoe trees,French,,,,,Maker,,Pierre Yantorny,"Italian, 1874–1936",,"Yantorny, Pierre",Italian,1874,1936,1925–30,1925,1930,"Wood, metal",,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Mrs. Edward G. Sparrow, 1969",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/170472,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.300.5687a–f,false,true,170473,Costume Institute,Shoe Trees,Shoe trees,French,,,,,Maker,,Pierre Yantorny,"Italian, 1874–1936",,"Yantorny, Pierre",Italian,1874,1936,1925–30,1925,1930,"Wood, metal",,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Mrs. Edward G. Sparrow, 1969",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/170473,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.300.6176a–f,false,true,170492,Costume Institute,Shoe Trees,Shoe trees,French,,,,,Maker,,Pierre Yantorny,"Italian, 1874–1936",,"Yantorny, Pierre",Italian,1874,1936,1914–19,1914,1919,"Wood, metal",,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/170492,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.300.6177a–f,false,true,170493,Costume Institute,Shoe Trees,Shoe trees,French,,,,,Maker,,Pierre Yantorny,"Italian, 1874–1936",,"Yantorny, Pierre",Italian,1874,1936,1914–19,1914,1919,"Wood, metal",,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/170493,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -C.I.53.76.25,false,true,172080,Costume Institute,Trunk,Trunk,French,,,,,Designer,,Pierre Yantorny,"Italian, 1874–1936",,"Yantorny, Pierre",Italian,1874,1936,1914–19,1914,1919,"wood, silk",,"Gift of Capezio Inc., 1953",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/172080,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -C.I.53.76.26,false,true,175904,Costume Institute,Trunk,Trunk,French,,,,,Designer,,Pierre Yantorny,"Italian, 1874–1936",,"Yantorny, Pierre",Italian,1874,1936,1914–19,1914,1919,"wood, silk",,"Gift of Capezio Inc., 1953",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/175904,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.378a–j,false,true,113560,Costume Institute,Shoes,Shoes,French,,,,,Designer,,Pierre Yantorny,"Italian, 1874–1936",,"Yantorny, Pierre",Italian,1874,1936,1910–20,1910,1920,leather,,"Gift of Mrs. John E. Roosevelt, 1976",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/113560,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"C.I.49.2.9a, b",false,true,113555,Costume Institute,Pumps,Pumps,French,,,,,Designer,,Pierre Yantorny,"Italian, 1874–1936",,"Yantorny, Pierre",Italian,1874,1936,1913–14,1913,1914,"silk, leather",,"Gift of Howard Sturges, in memory of his mother, Mrs. Howard O. Sturges, 1949",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/113555,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"1978.583.31a, b",false,true,84615,Costume Institute,Evening shoes,Evening shoes,French,,,,,Designer,Attributed to,Pierre Yantorny,"Italian, 1874–1936",,"Yantorny, Pierre",Italian,1874,1936,1920–25,1920,1925,"silk, leather",,"Gift of Mrs. John Scholz (Helen Marshall), 1978",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/84615,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -C.I.49.2.7a–f,false,true,113564,Costume Institute,Shoe Trees,Shoe trees,French,,,,,Designer,,Pierre Yantorny,"Italian, 1874–1936",,"Yantorny, Pierre",Italian,1874,1936,early 20th century,1900,1950,wood,,"Gift of Howard Sturges, in memory of his mother, Mrs. Howard O. Sturges, 1949",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/113564,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1978.583.27a–h,false,true,113561,Costume Institute,Shoes,Shoes,French,,,,,Designer,,Pierre Yantorny,"Italian, 1874–1936",,"Yantorny, Pierre",Italian,1874,1936,late 1920s,1925,1929,leather,,"Gift of Mrs. John Scholz (Helen Marshall), 1978",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/113561,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1978.583.28a–h,false,true,113557,Costume Institute,Evening shoes,Evening shoes,French,,,,,Designer,,Pierre Yantorny,"Italian, 1874–1936",,"Yantorny, Pierre",Italian,1874,1936,1920–25,1920,1925,"silk, metallic thread, leather, wood",,"Gift of Mrs. John Scholz (Helen Marshall), 1978",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/113557,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1978.583.29a–h,false,true,113558,Costume Institute,Evening shoes,Evening shoes,French,,,,,Designer,,Pierre Yantorny,"Italian, 1874–1936",,"Yantorny, Pierre",Italian,1874,1936,1920–25,1920,1925,"silk, metallic thread, leather",,"Gift of Mrs. John Scholz (Helen Marshall), 1978",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/113558,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1978.583.30a–h,false,true,113559,Costume Institute,Evening shoes,Evening shoes,French,,,,,Designer,,Pierre Yantorny,"Italian, 1874–1936",,"Yantorny, Pierre",Italian,1874,1936,1920–25,1920,1925,"silk, metallic thread, leather, wood",,"Gift of Mrs. John Scholz (Helen Marshall), 1978",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/113559,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -C.I.53.76.1a–h,false,true,112919,Costume Institute,Pumps,Pumps,French,,,,,Designer,,Pierre Yantorny,"Italian, 1874–1936",,"Yantorny, Pierre",Italian,1874,1936,1914–19,1914,1919,"leather, silk",,"Gift of Capezio Inc., 1953",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/112919,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -C.I.53.76.2a–h,false,true,112920,Costume Institute,Pumps,Pumps,French,,,,,Designer,,Pierre Yantorny,"Italian, 1874–1936",,"Yantorny, Pierre",Italian,1874,1936,1914–19,1914,1919,"leather, silk",,"Gift of Capezio Inc., 1953",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/112920,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -C.I.53.76.3a–g,false,true,112921,Costume Institute,Pumps,Pumps,French,,,,,Designer,,Pierre Yantorny,"Italian, 1874–1936",,"Yantorny, Pierre",Italian,1874,1936,1914–19,1914,1919,"leather, silk",,"Gift of Capezio Inc., 1953",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/112921,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -C.I.53.76.4a–h,false,true,112922,Costume Institute,Pumps,Pumps,French,,,,,Designer,,Pierre Yantorny,"Italian, 1874–1936",,"Yantorny, Pierre",Italian,1874,1936,1914–19,1914,1919,"leather, silk",,"Gift of Capezio Inc., 1953",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/112922,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -C.I.53.76.5a–f,false,true,112923,Costume Institute,Pumps,Pumps,French,,,,,Designer,,Pierre Yantorny,"Italian, 1874–1936",,"Yantorny, Pierre",Italian,1874,1936,1914–19,1914,1919,"leather, silk",,"Gift of Capezio Inc., 1953",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/112923,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -C.I.53.76.6a–h,false,true,112924,Costume Institute,Pumps,Pumps,French,,,,,Designer,,Pierre Yantorny,"Italian, 1874–1936",,"Yantorny, Pierre",Italian,1874,1936,1914–19,1914,1919,"leather, silk",,"Gift of Capezio Inc., 1953",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/112924,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -C.I.53.76.7a–h,false,true,112925,Costume Institute,Pumps,Pumps,French,,,,,Designer,,Pierre Yantorny,"Italian, 1874–1936",,"Yantorny, Pierre",Italian,1874,1936,1914–19,1914,1919,"leather, silk",,"Gift of Capezio Inc., 1953",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/112925,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -C.I.53.76.8a–h,false,true,112926,Costume Institute,Pumps,Pumps,French,,,,,Designer,,Pierre Yantorny,"Italian, 1874–1936",,"Yantorny, Pierre",Italian,1874,1936,1914–19,1914,1919,"leather, silk",,"Gift of Capezio Inc., 1953",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/112926,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -C.I.53.76.9a–h,false,true,112927,Costume Institute,Pumps,Pumps,French,,,,,Designer,,Pierre Yantorny,"Italian, 1874–1936",,"Yantorny, Pierre",Italian,1874,1936,1910–19,1910,1919,"leather, silk",,"Gift of Capezio Inc., 1953",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/112927,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -C.I.69.12.1a–h,false,true,113563,Costume Institute,Pumps,Pumps,French,,,,,Designer,,Pierre Yantorny,"Italian, 1874–1936",,"Yantorny, Pierre",Italian,1874,1936,1913–17,1913,1917,"(a, b) silk, metallic thread; (c–h) wood",,"Gift of Elizabeth Hudson, 1969",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/113563,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -C.I.69.12.2a–h,false,true,105033,Costume Institute,Pumps,Pumps,French,,,,,Designer,,Pierre Yantorny,"Italian, 1874–1936",,"Yantorny, Pierre",Italian,1874,1936,1913–18,1913,1918,"(a, b) silk; (c–h) wood",,"Gift of Elizabeth Hudson, 1969",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/105033,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2009.300.1178a, b",false,true,155863,Costume Institute,Evening shoes,Evening shoes,French,,,,,Designer,,Pierre Yantorny,"Italian, 1874–1936",,"Yantorny, Pierre",Italian,1874,1936,1914–19,1914,1919,"silk, metal, jet",,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Mercedes de Acosta, 1953",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/155863,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2009.300.1459a, b",false,true,156176,Costume Institute,Mules,Mules,French,,,,,Designer,,Pierre Yantorny,"Italian, 1874–1936",,"Yantorny, Pierre",Italian,1874,1936,1914–19,1914,1919,"silk, metal",,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Mercedes de Acosta, 1953",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/156176,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2009.300.1592a, b",false,true,156323,Costume Institute,Evening pumps,Evening pumps,French,,,,,Designer,,Pierre Yantorny,"Italian, 1874–1936",,"Yantorny, Pierre",Italian,1874,1936,1925–30,1925,1930,"metal, rhinestones",,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Mrs. Edward G. Sparrow, 1969",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/156323,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2009.300.1593a, b",false,true,156324,Costume Institute,Evening pumps,Evening pumps,French,,,,,Designer,,Pierre Yantorny,"Italian, 1874–1936",,"Yantorny, Pierre",Italian,1874,1936,1925–30,1925,1930,"silk, rhinestones",,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Mrs. Edward G. Sparrow, 1969",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/156324,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2009.300.1853a, b",false,true,156614,Costume Institute,Pumps,Pumps,French,,,,,Designer,,Pierre Yantorny,"Italian, 1874–1936",,"Yantorny, Pierre",Italian,1874,1936,1914–19,1914,1919,"leather, metal",,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Mercedes de Acosta, 1953",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/156614,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -C.I.53.76.10a–h,false,true,112928,Costume Institute,Pumps,Pumps,French,,,,,Designer,,Pierre Yantorny,"Italian, 1874–1936",,"Yantorny, Pierre",Italian,1874,1936,1914–19,1914,1919,"leather, silk",,"Gift of Capezio Inc., 1953",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/112928,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -C.I.53.76.11a–h,false,true,112929,Costume Institute,Pumps,Pumps,French,,,,,Designer,,Pierre Yantorny,"Italian, 1874–1936",,"Yantorny, Pierre",Italian,1874,1936,1914–19,1914,1919,"leather, silk",,"Gift of Capezio Inc., 1953",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/112929,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -C.I.53.76.12a–h,false,true,112930,Costume Institute,Pumps,Pumps,French,,,,,Designer,,Pierre Yantorny,"Italian, 1874–1936",,"Yantorny, Pierre",Italian,1874,1936,1914–19,1914,1919,"leather, silk",,"Gift of Capezio Inc., 1953",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/112930,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -C.I.53.76.13a–h,false,true,112931,Costume Institute,Pumps,Pumps,French,,,,,Designer,,Pierre Yantorny,"Italian, 1874–1936",,"Yantorny, Pierre",Italian,1874,1936,1914–19,1914,1919,"leather, silk",,"Gift of Capezio Inc., 1953",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/112931,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -C.I.53.76.14a–f,false,true,112932,Costume Institute,Pumps,Pumps,French,,,,,Designer,,Pierre Yantorny,"Italian, 1874–1936",,"Yantorny, Pierre",Italian,1874,1936,1914–19,1914,1919,"leather, silk",,"Gift of Capezio Inc., 1953",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/112932,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -C.I.53.76.15a–h,false,true,112933,Costume Institute,Pumps,Pumps,French,,,,,Designer,,Pierre Yantorny,"Italian, 1874–1936",,"Yantorny, Pierre",Italian,1874,1936,1914–19,1914,1919,"leather, silk",,"Gift of Capezio Inc., 1953",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/112933,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -C.I.53.76.16a–e,false,true,112934,Costume Institute,Pumps,Pumps,French,,,,,Designer,,Pierre Yantorny,"Italian, 1874–1936",,"Yantorny, Pierre",Italian,1874,1936,1914–19,1914,1919,"leather, silk",,"Gift of Capezio Inc., 1953",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/112934,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -C.I.53.76.17a–h,false,true,112935,Costume Institute,Pumps,Pumps,French,,,,,Designer,,Pierre Yantorny,"Italian, 1874–1936",,"Yantorny, Pierre",Italian,1874,1936,1914–19,1914,1919,"leather, silk",,"Gift of Capezio Inc., 1953",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/112935,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -C.I.53.76.18a–h,false,true,112936,Costume Institute,Pumps,Pumps,French,,,,,Designer,,Pierre Yantorny,"Italian, 1874–1936",,"Yantorny, Pierre",Italian,1874,1936,1914–19,1914,1919,"leather, silk",,"Gift of Capezio Inc., 1953",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/112936,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -C.I.53.76.19a–h,false,true,112937,Costume Institute,Pumps,Pumps,French,,,,,Designer,,Pierre Yantorny,"Italian, 1874–1936",,"Yantorny, Pierre",Italian,1874,1936,1914–19,1914,1919,"leather, silk",,"Gift of Capezio Inc., 1953",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/112937,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -C.I.53.76.20a–g,false,true,104759,Costume Institute,Pumps,Pumps,French,,,,,Designer,,Pierre Yantorny,"Italian, 1874–1936",,"Yantorny, Pierre",Italian,1874,1936,1914–19,1914,1919,"leather, silk",,"Gift of Capezio Inc., 1953",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/104759,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -C.I.53.76.21a–h,false,true,112938,Costume Institute,Pumps,Pumps,French,,,,,Designer,,Pierre Yantorny,"Italian, 1874–1936",,"Yantorny, Pierre",Italian,1874,1936,1914–19,1914,1919,"leather, silk, metallic",,"Gift of Capezio Inc., 1953",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/112938,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -C.I.53.76.22a–h,false,true,112939,Costume Institute,Shoes,Shoes,French,,,,,Designer,,Pierre Yantorny,"Italian, 1874–1936",,"Yantorny, Pierre",Italian,1874,1936,1914–19,1914,1919,"leather, silk",,"Gift of Capezio Inc., 1953",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/112939,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -C.I.53.76.23a–h,false,true,112940,Costume Institute,Shoes,Shoes,French,,,,,Designer,,Pierre Yantorny,"Italian, 1874–1936",,"Yantorny, Pierre",Italian,1874,1936,1914–19,1914,1919,"leather, silk",,"Gift of Capezio Inc., 1953",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/112940,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -C.I.53.76.24a–g,false,true,112941,Costume Institute,Shoes,Shoes,French,,,,,Designer,,Pierre Yantorny,"Italian, 1874–1936",,"Yantorny, Pierre",Italian,1874,1936,1914–19,1914,1919,"leather, silk",,"Gift of Capezio Inc., 1953",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/112941,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.300.1179a–f,false,true,155864,Costume Institute,Shoe Trees,Shoe trees,French,,,,,Designer,,Pierre Yantorny,"Italian, 1874–1936",,"Yantorny, Pierre",Italian,1874,1936,1914–19,1914,1919,"wood, metal",,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Mercedes de Acosta, 1953",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/155864,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.300.1457a–d,false,true,156174,Costume Institute,Shoes,Shoes,French,,,,,Designer,,Pierre Yantorny,"Italian, 1874–1936",,"Yantorny, Pierre",Italian,1874,1936,1914–19,1914,1919,leather,,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Mercedes de Acosta, 1953",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/156174,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.300.1458a–f,false,true,156175,Costume Institute,Shoe Trees,Shoe trees,French,,,,,Designer,,Pierre Yantorny,"Italian, 1874–1936",,"Yantorny, Pierre",Italian,1874,1936,1914–19,1914,1919,"wood, metal",,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Mercedes de Acosta, 1953",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/156175,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.300.1854a–f,false,true,156615,Costume Institute,Shoe Trees,Shoe trees,French,,,,,Designer,,Pierre Yantorny,"Italian, 1874–1936",,"Yantorny, Pierre",Italian,1874,1936,1914–19,1914,1919,"wood, metal",,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Mercedes de Acosta, 1953",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/156615,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.300.2144a–d,false,true,156937,Costume Institute,Shoes,Shoes,French,,,,,Designer,,Pierre Yantorny,"Italian, 1874–1936",,"Yantorny, Pierre",Italian,1874,1936,1925–30,1925,1930,leather,,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Mrs. Edward G. Sparrow, 1969",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/156937,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.300.2145a–f,false,true,156938,Costume Institute,Shoe Trees,Shoe trees,French,,,,,Designer,,Pierre Yantorny,"Italian, 1874–1936",,"Yantorny, Pierre",Italian,1874,1936,1925–30,1925,1930,wood,,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Mrs. Edward G. Sparrow, 1969",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/156938,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.300.3408a–f,false,true,158343,Costume Institute,Shoe Trees,Shoe trees,French,,,,,Designer,,Pierre Yantorny,"Italian, 1874–1936",,"Yantorny, Pierre",Italian,1874,1936,1925–30,1925,1930,"wood, metal",,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Mrs. Edward G. Sparrow, 1969",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/158343,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.300.3866a–h,false,true,158850,Costume Institute,Shoes,Shoes,French,,,,,Designer,,Pierre Yantorny,"Italian, 1874–1936",,"Yantorny, Pierre",Italian,1874,1936,1925–30,1925,1930,"leather, wood, metal",,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Mrs. Edward G. Sparrow, 1969",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/158850,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -C.I.40.130.12a–h,false,true,104725,Costume Institute,Shoes,Shoes,French,,,,,Designer,,Pierre Yantorny,"Italian, 1874–1936",,"Yantorny, Pierre",Italian,1874,1936,1911–15,1911,1915,"silk, wood",,"Gift of Miss Mercedes de Acosta, 1940",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/104725,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -C.I.40.130.13a–h,false,true,110469,Costume Institute,Shoes,Shoes,French,,,,,Designer,,Pierre Yantorny,"Italian, 1874–1936",,"Yantorny, Pierre",Italian,1874,1936,1910–15,1910,1915,"leather, wood",,"Gift of Miss Mercedes de Acosta, 1940",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/110469,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1978.46,false,true,81652,Costume Institute,Evening coat,Evening coat,Italian,,,,,Designer,,Maria Gallenga,"Italian, Rome 1880–1944 Umbria",,Gallenga Maria,Italian,1880,1944,1925–26,1925,1926,"silk, fur",,"Gift of Helen M. Woodruff, 1978",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/81652,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1980.96,false,true,81625,Costume Institute,Evening cape,Evening cape,Italian,,,,,Designer,,Maria Gallenga,"Italian, Rome 1880–1944 Umbria",,Gallenga Maria,Italian,1880,1944,early 1920s,1920,1925,"silk, glass, metallic thread",,"Gift of Leafie Freda, 1980",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/81625,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1978.170,false,true,81651,Costume Institute,Evening cape,Evening cape,Italian,,,,,Designer,,Maria Gallenga,"Italian, Rome 1880–1944 Umbria",,Gallenga Maria,Italian,1880,1944,1923–24,1923,1924,silk,,"Gift of Mrs. A. Winslow Meade, 1978",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/81651,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1982.160,false,true,81593,Costume Institute,Evening dress,Evening dress,Italian,,,,,Designer,,Maria Gallenga,"Italian, Rome 1880–1944 Umbria",,Gallenga Maria,Italian,1880,1944,second quarter 20th century,1925,1950,silk,,"Gift of Mr. and Mrs. James Pinckney Kinard, in memory of her mother, the late Agnes J. Dodds, (Mrs. Robert James Dodds), 1982",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/81593,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2008.191,false,true,141972,Costume Institute,Dress,Dress,Italian,,,,,Designer,,Maria Gallenga,"Italian, Rome 1880–1944 Umbria",,Gallenga Maria,Italian,1880,1944,early 20th century,1900,1950,silk,,"Gift of Anne Parmelee Reed Dean, In Memory of Elizabeth Burd Thompson Reed, 2008",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/141972,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1978.32.1,false,true,81653,Costume Institute,Evening dress,Evening dress,Italian,,,,,Designer,,Maria Gallenga,"Italian, Rome 1880–1944 Umbria",,Gallenga Maria,Italian,1880,1944,1924,1924,1924,silk,,"Gift of Mrs. Douglas Delanoy, 1978",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/81653,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1978.32.2,false,true,81654,Costume Institute,Evening dress,Evening dress,Italian,,,,,Designer,,Maria Gallenga,"Italian, Rome 1880–1944 Umbria",,Gallenga Maria,Italian,1880,1944,1924,1924,1924,silk,,"Gift of Mrs. Douglas Delanoy, 1978",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/81654,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1995.28.5,false,true,80161,Costume Institute,Dress,Dress,Italian,,,,,Designer,,Maria Gallenga,"Italian, Rome 1880–1944 Umbria",,Gallenga Maria,Italian,1880,1944,1920s,1920,1929,silk,,"Gift of Estate of Lillian Gish, 1995",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/80161,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -C.I.63.14,false,true,81508,Costume Institute,Tea gown,Tea gown,Italian,,,,,Designer,,Maria Gallenga,"Italian, Rome 1880–1944 Umbria",,Gallenga Maria,Italian,1880,1944,ca. 1930,1925,1935,silk,,"Gift of Louise Rorimer Dushkin, 1963",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/81508,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.165.1,false,true,81650,Costume Institute,Evening coat,Evening coat,Italian,,,,,Designer,,Maria Gallenga,"Italian, Rome 1880–1944 Umbria",,Gallenga Maria,Italian,1880,1944,ca. 1926,1921,1931,silk,,"Gift of Mary C. Hartshorne, 1989",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/81650,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1991.187.1,false,true,81641,Costume Institute,Evening dress,Evening dress,Italian,,,,,Designer,,Maria Gallenga,"Italian, Rome 1880–1944 Umbria",,Gallenga Maria,Italian,1880,1944,first half 20th century,1900,1950,"silk, glass",,"Gift of Mrs. Paxton T. Dunn in memory of her mother, Hildreth Meiere, 1991",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/81641,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1991.187.2,false,true,81642,Costume Institute,Evening dress,Evening dress,Italian,,,,,Designer,,Maria Gallenga,"Italian, Rome 1880–1944 Umbria",,Gallenga Maria,Italian,1880,1944,1920s,1920,1929,"silk, glass",,"Gift of Mrs. Paxton T. Dunn in memory of her mother, Hildreth Meiere, 1991",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/81642,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1991.187.3,false,true,81643,Costume Institute,Evening dress,Evening dress,Italian,,,,,Designer,,Maria Gallenga,"Italian, Rome 1880–1944 Umbria",,Gallenga Maria,Italian,1880,1944,1920s,1920,1929,"silk, glass",,"Gift of Mrs. Paxton T. Dunn in memory of her mother, Hildreth Meiere, 1991",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/81643,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1991.187.4,false,true,81522,Costume Institute,Evening dress,Evening dress,Italian,,,,,Designer,,Maria Gallenga,"Italian, Rome 1880–1944 Umbria",,Gallenga Maria,Italian,1880,1944,early 20th century,1900,1950,"silk, glass",,"Gift of Mrs. Paxton T. Dunn in memory of her mother, Hildreth Meriere, 1991",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/81522,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1991.187.7,false,true,81644,Costume Institute,Evening coat,Evening coat,Italian,,,,,Designer,,Maria Gallenga,"Italian, Rome 1880–1944 Umbria",,Gallenga Maria,Italian,1880,1944,1920s,1920,1929,silk,,"Gift of Mrs. Paxton T. Dunn in memory of her mother, Hildreth Meiere, 1991",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/81644,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1991.187.8,false,true,81645,Costume Institute,Scarf,Scarf,Italian,,,,,Designer,,Maria Gallenga,"Italian, Rome 1880–1944 Umbria",,Gallenga Maria,Italian,1880,1944,first quarter 20th century,1900,1925,silk,,"Gift of Mrs. Paxton T. Dunn in memory of her mother, Hildreth Meiere, 1991",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/81645,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1991.187.9,false,true,81646,Costume Institute,Scarf,Scarf,Italian,,,,,Designer,,Maria Gallenga,"Italian, Rome 1880–1944 Umbria",,Gallenga Maria,Italian,1880,1944,first quarter 20th century,1900,1925,"silk, glass",,"Gift of Mrs. Paxton T. Dunn in memory of her mother, Hildreth Meiere, 1991",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/81646,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1979.569.15,false,true,81597,Costume Institute,Evening dress,Evening dress,Italian,,,,,Designer,Attributed to,Maria Gallenga,"Italian, Rome 1880–1944 Umbria",,Gallenga Maria,Italian,1880,1944,1925–30,1925,1930,"silk, glass",,"Gift of Mrs. C. O. Kalman, 1979",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/81597,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1991.187.10,false,true,81647,Costume Institute,Purse,Purse,Italian,,,,,Designer,,Maria Gallenga,"Italian, Rome 1880–1944 Umbria",,Gallenga Maria,Italian,1880,1944,first quarter 20th century,1900,1925,silk,,"Gift of Mrs. Paxton T. Dunn in memory of her mother, Hildreth Meiere, 1991",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/81647,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1991.187.11,false,true,81648,Costume Institute,Belt,Belt,Italian,,,,,Designer,,Maria Gallenga,"Italian, Rome 1880–1944 Umbria",,Gallenga Maria,Italian,1880,1944,first quarter 20th century,1900,1925,"metal, glass",,"Gift of Mrs. Paxton T. Dunn in memory of her mother, Hildreth Meiere, 1991",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/81648,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1991.187.12,false,true,81649,Costume Institute,Belt,Belt,Italian,,,,,Designer,,Maria Gallenga,"Italian, Rome 1880–1944 Umbria",,Gallenga Maria,Italian,1880,1944,first quarter 20th century,1900,1925,silk,,"Gift of Mrs. Paxton T. Dunn in memory of her mother, Hildreth Meiere, 1991",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/81649,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.300.342,false,true,158356,Costume Institute,Evening cape,Evening cape,Italian,,,,,Designer,,Maria Gallenga,"Italian, Rome 1880–1944 Umbria",,Gallenga Maria,Italian,1880,1944,1925,1925,1925,"silk, metal",,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Mrs. Cecil Lubell, 1964",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/158356,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"1995.28.3a, b",false,true,80159,Costume Institute,Dress,Dress,Italian,,,,,Designer,,Maria Gallenga,"Italian, Rome 1880–1944 Umbria",,Gallenga Maria,Italian,1880,1944,1920s,1920,1929,silk,,"Gift of Estate of Lillian Gish, 1995",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/80159,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.300.6903,false,true,173923,Costume Institute,Evening overdress,Evening overdress,Italian,,,,,Designer,Attributed to,Maria Gallenga,"Italian, Rome 1880–1944 Umbria",,Gallenga Maria,Italian,1880,1944,ca. 1920,1918,1922,"Silk, metallic",,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Mrs. Thomas Brown Rudd, 1955",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/173923,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"1991.187.6a, b",false,true,81523,Costume Institute,Evening ensemble,Evening ensemble,Italian,,,,,Designer,,Maria Gallenga,"Italian, Rome 1880–1944 Umbria",,Gallenga Maria,Italian,1880,1944,early 20th century,1900,1925,"silk, glass",,"Gift of Mrs. Paxton T. Dunn in memory of her mother, Hildreth Meiere, 1991",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/81523,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1991.187.5a–c,false,true,81590,Costume Institute,Evening ensemble,Evening ensemble,Italian,,,,,Designer,,Maria Gallenga,"Italian, Rome 1880–1944 Umbria",,Gallenga Maria,Italian,1880,1944,1920s,1920,1929,"silk, glass, fur",,"Gift of Mrs. Paxton T. Dunn in memory of her mother, Hildreth Meiere, 1991",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/81590,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.300.23a–c,false,true,157221,Costume Institute,Evening dress,Evening dress,Italian,,,,,Designer,,Maria Gallenga,"Italian, Rome 1880–1944 Umbria",,Gallenga Maria,Italian,1880,1944,1926,1926,1926,"silk, metal",,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Mary Cheney Platt, 1966",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/157221,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2009.300.312a, b",false,true,158032,Costume Institute,Evening dress,Evening dress,Italian,,,,,Designer,,Maria Gallenga,"Italian, Rome 1880–1944 Umbria",,Gallenga Maria,Italian,1880,1944,ca. 1920,1918,1922,"silk, metal, glass beads",,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Natalie Rector, 1962",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/158032,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2009.300.479a, b",false,true,159014,Costume Institute,Evening dress,Evening dress,Italian,,,,,Designer,,Maria Gallenga,"Italian, Rome 1880–1944 Umbria",,Gallenga Maria,Italian,1880,1944,ca. 1920,1918,1922,"silk, metal, glass",,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Mrs. Philip J. Roosevelt, 1969",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/159014,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -C.I.43.113.1,false,true,109103,Costume Institute,Nightgown,Nightgown,American or European,,,,,Department Store,,B. Altman & Co.,"American, 1865–1990",,B. Altman & Co.,American,1865,1990,1894,1894,1894,[no medium available],,"Gift of Mrs. William Rosenfeld, 1943",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/109103,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -C.I.43.113.8,false,true,109109,Costume Institute,Drawers,Drawers,American or European,,,,,Department Store,,B. Altman & Co.,"American, 1865–1990",,B. Altman & Co.,American,1865,1990,1894,1894,1894,[no medium available],,"Gift of Mrs. William Rosenfeld, 1943",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/109109,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -C.I.43.113.11,false,true,109104,Costume Institute,Corset Cover,Corset cover,American or European,,,,,Department Store,,B. Altman & Co.,"American, 1865–1990",,B. Altman & Co.,American,1865,1990,1894,1894,1894,[no medium available],,"Gift of Mrs. William Rosenfeld, 1943",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/109104,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -C.I.39.24a–c,false,true,109013,Costume Institute,Lingerie,Lingerie,American or European,,,,,Department Store,,B. Altman & Co.,"American, 1865–1990",,B. Altman & Co.,American,1865,1990,1881,1881,1881,cotton,,"Gift of Miss Gertrude M. Oppenheimer, 1939",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/109013,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -49.3.45,false,true,105424,Costume Institute,Slippers,Slippers,French,,,,,Department Store,,B. Altman & Co.,"American, 1865–1990",,B. Altman & Co.,American,1865,1990,1850,1850,1850,"silk, leather, pearls",,"Gift of Orme Wilson and R. Thornton Wilson, in memory of their mother, Mrs. Caroline Schermerhorn Astor Wilson, 1949",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/105424,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -49.3.46,false,true,105428,Costume Institute,Slippers,Slippers,French,,,,,Department Store,,B. Altman & Co.,"American, 1865–1990",,B. Altman & Co.,American,1865,1990,1850,1850,1850,"silk, leather, pearls",,"Gift of Orme Wilson and R. Thornton Wilson, in memory of their mother, Mrs. Caroline Schermerhorn Astor Wilson, 1949",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/105428,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"C.I.42.131.3a, b",false,true,103558,Costume Institute,Dress,Dress,French,,,,,Department Store,,B. Altman & Co.,"American, 1865–1990",,B. Altman & Co.,American,1865,1990,1883,1883,1883,[no medium available],,"Gift of Mrs. R. E. Seeligman, 1942",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/103558,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.246.9a–d,false,true,105006,Costume Institute,Ensemble,Ensemble,American,,,,,Manufacturer,"(c, d)",Alfred J. Cammeyer,1849–1913,,"Cammeyer, Alfred J.",American,1875,1940,1895–98,1895,1898,"wool, leather, canvas",,"Gift of Anne L. Maxwell, in memory of her mother, Julia H. Lawrence, 1989",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/105006,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"C.I.48.39.7a, b",false,true,113195,Costume Institute,Pumps,Pumps,American,,,,,Manufacturer,,Alfred J. Cammeyer,1849–1913,,"Cammeyer, Alfred J.",American,1875,1940,1890s,1890,1899,"silk, leather, glass",,"Gift of Mrs. E. R. Gerkin, 1948",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/113195,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2009.300.2039a, b",false,true,156821,Costume Institute,Stockings,Stockings,American,,,,,Manufacturer,,Alfred J. Cammeyer,1849–1913,,"Cammeyer, Alfred J.",American,1875,1940,1900–1915,1900,1915,silk,,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Mrs. Albert Ogden in memory of Sheldon Stewart, 1964",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/156821,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2009.300.4762a, b",false,true,168967,Costume Institute,Carriage boots,Carriage boots,American,,,,,Department Store,,Alfred J. Cammeyer,1849–1913,,"Cammeyer, Alfred J.",American,1875,1940,1895–1915,1895,1915,"Silk, fur",,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Charles D. Cords, 1954",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/168967,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2009.300.4945a, b",false,true,169168,Costume Institute,Evening shoes,Evening shoes,American,,,,,Department Store,,Alfred J. Cammeyer,1849–1913,,"Cammeyer, Alfred J.",American,1875,1940,1915–20,1915,1920,"Leather, beads",,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Adeline Delbon, 1957",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/169168,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2009.300.5256a, b",false,true,169470,Costume Institute,Slippers,Slippers,American,,,,,Department Store,,Alfred J. Cammeyer,1849–1913,,"Cammeyer, Alfred J.",American,1875,1940,ca. 1891,1889,1893,Leather,,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Mrs. Ferris J. Meigs, 1961",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/169470,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2009.300.5597a, b",false,true,169810,Costume Institute,Evening shoes,Evening shoes,American,,,,,Department Store,,Alfred J. Cammeyer,1849–1913,,"Cammeyer, Alfred J.",American,1875,1940,ca. 1925,1923,1927,Leather; metallic,,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Mrs. Frederick H. Prince, Jr., 1967",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/169810,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2009.300.5696a, b",false,true,169907,Costume Institute,Slippers,Slippers,American,,,,,Department Store,,Alfred J. Cammeyer,1849–1913,,"Cammeyer, Alfred J.",American,1875,1940,1893–99,1893,1899,Silk,,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Mrs. William T. Rose, 1969",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/169907,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2009.300.5954a, b",false,true,170345,Costume Institute,Oxfords,Oxfords,American,,,,,Department Store,,Alfred J. Cammeyer,1849–1913,,"Cammeyer, Alfred J.",American,1875,1940,ca. 1898,1896,1900,"Cotton, leather",,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Vera Maxwell, 1985",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/170345,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2009.300.6071a, b",false,true,170286,Costume Institute,Evening pumps,Evening pumps,American,,,,,Department Store,,Alfred J. Cammeyer,1849–1913,,"Cammeyer, Alfred J.",American,1875,1940,1907,1907,1907,Silk,,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Ann Ellis, 1993",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/170286,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.300.2250,false,true,157055,Costume Institute,Cage crinoline,Cage crinoline,American,,,,,Manufacturer,,Royal Worcester Corset Company,"American, 1864–1950",,Royal Worcester Corset Company,American,1864,1950,1862–63,1862,1863,"linen, metal",,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Brooklyn Museum Collection",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/157055,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.300.6642,false,true,173656,Costume Institute,Corset,Corset,American,,,,,Manufacturer,,Royal Worcester Corset Company,"American, 1864–1950",,Royal Worcester Corset Company,American,1864,1950,ca. 1880,1878,1882,"Cotton, bone",,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of E. A. Meister, 1950",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/173656,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.300.6643,false,true,173657,Costume Institute,Corset,Corset,American,,,,,Manufacturer,,Royal Worcester Corset Company,"American, 1864–1950",,Royal Worcester Corset Company,American,1864,1950,ca. 1885,1883,1887,"Cotton, metal, bone",,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of E.A. Meister, 1950",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/173657,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2009.300.3110a, b",false,true,158012,Costume Institute,Corset,Corset,American,,,,,Manufacturer,,Worcester Corset Company,"American, 1864–1950",,Worcester Corset Company,American,1864,1950,ca. 1880,1878,1882,"cotton, metal, bone",,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of E. A. Meister, 1950",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/158012,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2009.300.6633a, b",false,true,175650,Costume Institute,Corset,Corset,American,,,,,Manufacturer,,Royal Worcester Corset Company,"American, 1864–1950",,Royal Worcester Corset Company,American,1864,1950,ca. 1862,1860,1864,"Cotton, metal, bone",,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of E. A. Meister, 1950",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/175650,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2009.300.6635a, b",false,true,175652,Costume Institute,Corset,Corset,American,,,,,Manufacturer,,Royal Worcester Corset Company,"American, 1864–1950",,Royal Worcester Corset Company,American,1864,1950,ca. 1869,1867,1871,"Cotton, metal",,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of E. A. Meister, 1950",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/175652,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2009.300.6636a, b",false,true,175653,Costume Institute,Corset,Corset,American,,,,,Manufacturer,Probably,Royal Worcester Corset Company,"American, 1864–1950",,Royal Worcester Corset Company,American,1864,1950,ca. 1872,1870,1874,"Cotton, bone",,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of E. A. Meister, 1950",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/175653,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2009.300.6637a, b",false,true,173651,Costume Institute,Corset,Corset,American,,,,,Manufacturer,,Royal Worcester Corset Company,"American, 1864–1950",,Royal Worcester Corset Company,American,1864,1950,ca. 1870,1868,1872,"Cotton, metal, bone",,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of E. A. Meister, 1950",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/173651,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2009.300.6638a, b",false,true,173652,Costume Institute,Corset,Corset,American,,,,,Manufacturer,,Royal Worcester Corset Company,"American, 1864–1950",,Royal Worcester Corset Company,American,1864,1950,ca. 1872,1870,1874,"Cotton, metal, bone",,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of E. A. Meister, 1950",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/173652,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2009.300.6650a, b",false,true,175660,Costume Institute,Corset,Corset,American,,,,,Manufacturer,,Royal Worcester Corset Company,"American, 1864–1950",,Royal Worcester Corset Company,American,1864,1950,ca. 1890,1888,1892,"Cotton, metal, bone",,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of E.A. Meister, 1950",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/175660,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.300.3101a–c,false,true,158002,Costume Institute,Corset,Corset,American,,,,,Manufacturer,,Worcester Skirt Company,"American, 1864–1950",,Worcester Skirt Company,American,1864,1950,1861–63,1861,1863,"cotton, bone",,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of E. A. Meister, 1950",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/158002,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.300.3105a–c,false,true,158006,Costume Institute,Corset,Corset,American,,,,,Manufacturer,,Royal Worcester Corset Company,"American, 1864–1950",,Royal Worcester Corset Company,American,1864,1950,1876,1876,1876,"cotton, metal, bone",,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of E. A. Meister, 1950",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/158006,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.300.3106a–c,false,true,158007,Costume Institute,Corset,Corset,American,,,,,Manufacturer,,Royal Worcester Corset Company,"American, 1864–1950",,Royal Worcester Corset Company,American,1864,1950,1876,1876,1876,"silk, cotton, metal, bone",,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of E. A. Meister, 1950",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/158007,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.300.3112a–d,false,true,158014,Costume Institute,Corset,Corset,American,,,,,Manufacturer,,Royal Worcester Corset Company,"American, 1864–1950",,Royal Worcester Corset Company,American,1864,1950,ca. 1885,1883,1887,"cotton, metal, bone",,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of E.A. Meister, 1950",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/158014,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.300.3113a–c,false,true,158015,Costume Institute,Corset,Corset,American,,,,,Manufacturer,,Royal Worcester Corset Company,"American, 1864–1950",,Royal Worcester Corset Company,American,1864,1950,ca. 1890,1888,1892,"cotton, metal, bone",,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of E.A. Meister, 1950",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/158015,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.300.3496a–e,false,true,158439,Costume Institute,Corset,"""Queen Bess""",American,,,,,Manufacturer,,Royal Worcester Corset Company,"American, 1864–1950",,Royal Worcester Corset Company,American,1864,1950,1876,1876,1876,"silk, bone, metal",,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of E. A. Meister, 1950",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/158439,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2009.300.6584a, b",false,true,173583,Costume Institute,Spats,Spats,American,,,,,Department Store,,B. Altman & Co.,"American, 1865–1990",,B. Altman & Co.,American,1865,1990,ca. 1890,1888,1892,Wool,,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Bequest of Matilda Alice Shaw, 1948",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/173583,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.300.223,false,true,157033,Costume Institute,Knickerbockers,Knickerbockers,American,,,,,Department Store,,"Browning, King & Company","American, 1868–1934",,"Browning, King & Company",American,1868,1934,ca. 1925,1923,1927,wool,,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Mrs. Wesley Wallace Tillotson, 1956",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/157033,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.300.257,false,true,157411,Costume Institute,Smoking jacket,Smoking jacket,American,,,,,Department Store,,"Browning, King & Company","American, 1868–1934",,"Browning, King & Company",American,1868,1934,1905–15,1905,1915,wool,,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Simon Spiegal, 1958",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/157411,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.300.2762a–d,false,true,157625,Costume Institute,Corset,Corset,American,,,,,Manufacturer,,"O'Conner, Moffatt & Company","American, 1868–1945",,"O'Conner, Moffatt & Company",American,1868,1945,1918–19,1918,1919,"cotton, bone, metal, elastic",,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of E. A. Meister, 1950",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/157625,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1991.258,false,true,84226,Costume Institute,Fan,Fan,American,,,,,Manufacturer,,H.C.F. Koch,"American, 1890–1930",,Koch H.C.F.,American,1890,1930,ca. 1880,1875,1885,"pasteboard, wood",,"Gift of The Brooklyn Historical Society, 1991",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/84226,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.300.7532,false,true,174585,Costume Institute,Cravat,Cravat,American,,,,,Retailer,,Saks & Company,"American, 1902–1924",,Saks & Company,American,1902,1924,1920–30,1920,1930,Cotton,,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Albert Moss, 1967",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/174585,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2009.300.5784a, b",false,true,169988,Costume Institute,Evening shoes,Evening shoes,American,,,,,Retailer,,Saks & Company,"American, 1902–1924",,Saks & Company,American,1902,1924,ca. 1933,1931,1935,"Silk, leather",,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Helen Gray, 1970",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/169988,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -C.I.53.2,false,true,84013,Costume Institute,Opera cloak,Opera cloak,American,,,,,Department Store,,Lord & Taylor,"American, founded 1826",,Lord & Taylor,American,1826,2050,1850s,1850,1859,silk,,"Gift of Mrs. Josephine Mingle Tennant, 1953",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/84013,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1979.34.2a–d,false,true,81661,Costume Institute,Dinner dress,Dinner dress,American,,,,,Department Store,,Lord & Taylor,"American, founded 1826",,Lord & Taylor,American,1826,2050,1877–83,1877,1883,"silk, glass",,"Gift of Elizabeth Kellogg Ammidon, 1979",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/81661,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.300.6669,false,true,173676,Costume Institute,Ribbon,Ribbon,American,,,,,Manufacturer,,W. B. Conkey Co.,"American, Chicago, Illinois",,"Conkey Co., W. B.",American,1850,1950,1893,1893,1893,"Silk, metallic, pigment",,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of E.A. Meister, 1950",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/173676,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.300.2351,false,true,157167,Costume Institute,Parasol,Parasol,American,,,,,Department Store,,Stern Brothers,"American, founded New York, 1867",,Stern Brothers,American,1867,2001,ca. 1870,1868,1872,"silk, wood, metal",,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Lillian E. Glenn Peirce in memory of Mrs. Luther G. Tillotson, 1944",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/157167,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.300.2528,false,true,157365,Costume Institute,Parasol,Parasol,American,,,,,Department Store,,Stern Brothers,"American, founded New York, 1867",,Stern Brothers,American,1867,2001,1876,1876,1876,"silk, wood, glass, metal",,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Genevieve Doherty in memory of Mrs. John Henry, 1964",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/157365,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2009.300.4709a, b",false,true,168914,Costume Institute,Wedding Slippers,Wedding slippers,American,,,,,Department Store,,Stern Brothers,"American, founded New York, 1867",,Stern Brothers,American,1867,2001,1880,1880,1880,Silk,,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Polly Dix, 1954",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/168914,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.300.6567,false,true,180660,Costume Institute,Dinner shoes,Dinner shoes,American,,,,,Manufacturer,,A.E. Little & Co.,"American, Lynn, Massachusetts 1898–1934",,"Little & Co., A.E.",American,1898,1934,ca. 1916,1911,1921,"Leather, beads",,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of the estate of Carrie Chapman Catt, 1947",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/180660,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2009.300.1423a, b",false,true,156137,Costume Institute,Evening shoes,Evening shoes,American,,,,,Manufacturer,,A.E. Little & Co.,"American, Lynn, Massachusetts 1898–1934",,"Little & Co., A.E.",American,1898,1934,ca. 1916,1914,1918,leather,,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Theodora Wilbour, 1932",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/156137,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2009.300.4226a, b",false,true,168459,Costume Institute,Dinner shoes,Dinner shoes,American,,,,,Manufacturer,,A.E. Little & Co.,"American, Lynn, Massachusetts 1898–1934",,"Little & Co., A.E.",American,1898,1934,ca. 1910,1908,1912,"Leather, beads",,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Theodora Wilbour, 1932",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/168459,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2009.300.4509a, b",false,true,168717,Costume Institute,Evening pumps,Evening pumps,American,,,,,Manufacturer,,A.E. Little & Co.,"American, Lynn, Massachusetts 1898–1934",,"Little & Co., A.E.",American,1898,1934,1918,1918,1918,Silk,,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Eleanor Curnow, 1946",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/168717,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2009.300.5012a, b",false,true,169234,Costume Institute,Dinner shoes,Dinner shoes,American,,,,,Manufacturer,,A.E. Little & Co.,"American, Lynn, Massachusetts 1898–1934",,"Little & Co., A.E.",American,1898,1934,1915,1915,1915,"Leather, beads",,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Mrs. Hollis K. Thayer, 1958",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/169234,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2009.300.5340a, b",false,true,169558,Costume Institute,Evening shoes,Evening shoes,American,,,,,Manufacturer,,A.E. Little & Co.,"American, Lynn, Massachusetts 1898–1934",,"Little & Co., A.E.",American,1898,1934,1915–16,1915,1916,"Leather, beads",,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Mrs. William B. Parker, 1964",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/169558,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.300.1455a–d,false,true,156172,Costume Institute,Evening oxfords,Evening oxfords,American,,,,,Department Store,,Alfred J. Cammeyer,"American, founded New York, active 1875–1930s",,"Cammeyer, Alfred J.",American,1875,1940,ca. 1891,1889,1893,leather,,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Marion Fisher, 1952",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/156172,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.300.6950a–c,false,true,173956,Costume Institute,Morning suit,Morning suit,American,,,,,Maker,,"Browning, King & Company","American, 1868–1934",,"Browning, King & Company",American,1868,1934,1913,1913,1913,"Wool, silk",,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Mrs. Wesley Wallace Tillotson, 1956",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/173956,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"C.I.53.39a, b",false,true,113633,Costume Institute,Shoes,Shoes,American,,,,,Designer,,Alfred J. Cammeyer,1849–1913,,"Cammeyer, Alfred J.",American,1875,1940,1925–30,1925,1930,leather,,"Gift of Mrs. Peter A. Cohn, 1953",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/113633,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"C.I.49.57.2a, b",false,true,113160,Costume Institute,Evening slippers,Evening slippers,American,,,,,Designer,,Alfred J. Cammeyer,1849–1913,,"Cammeyer, Alfred J.",American,1875,1940,1900–1910,1900,1910,leather,,"Gift of Mrs. Robert S. Dixon, 1949",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/113160,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"C.I.51.72.2a, b",false,true,113180,Costume Institute,Wedding Shoes,Wedding shoes,American,,,,,Designer,,Alfred J. Cammeyer,1849–1913,,"Cammeyer, Alfred J.",American,1875,1940,1896,1896,1896,"leather, silk",,"Gift of Miss Mildred Mendelson, 1951",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/113180,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"C.I.56.33.33a, b",false,true,113615,Costume Institute,Shoes,Shoes,American,,,,,Designer,,Alfred J. Cammeyer,1849–1913,,"Cammeyer, Alfred J.",American,1875,1940,1928,1928,1928,"leather, straw",,"Gift of Mrs. Sidney Bernard, 1956",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/113615,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1979.424.1,false,true,96026,Costume Institute,Cocktail Dress,Cocktail dress,American,,,,,Designer,,Ceil Chapman,"American, born 1912",,Chapman Ceil,American,1912,1912,1950–53,1950,1953,rayon,,"Gift of Ann M. Kivlan, 1979",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/96026,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1979.424.4,false,true,89341,Costume Institute,Cocktail Dress,Cocktail dress,American,,,,,Designer,,Ceil Chapman,"American, born 1912",,Chapman Ceil,American,1912,1912,1955–59,1955,1959,silk,,"Gift of Ann M. Kivlan, 1979",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/89341,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2002.196.3,false,true,84293,Costume Institute,Dress,Dress,American,,,,,Designer,,Ceil Chapman,"American, born 1912",,Chapman Ceil,American,1912,1912,late 1940s,1945,1955,synthetic,,"Gift of Mary (Howard) de Liagre, 2002",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/84293,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"1993.185a, b",false,true,80752,Costume Institute,Wedding Dress,Wedding dress,American,,,,,Designer,,Ceil Chapman,"American, born 1912",,Chapman Ceil,American,1912,1912,1948,1948,1948,cotton,,"Gift of Sophie Mitropoulos, 1993",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/80752,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.300.7196,false,true,174324,Costume Institute,Dinner dress,Dinner dress,American,,,,,Designer,,Ceil Chapman,"American, born 1912",,Chapman Ceil,American,1912,1912,1945,1945,1945,Synthetic,,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of George F. Hoag, 1962",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/174324,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.300.7204,false,true,174332,Costume Institute,Evening dress,Evening dress,American,,,,,Designer,,Ceil Chapman,"American, born 1912",,Chapman Ceil,American,1912,1912,1953,1953,1953,Silk,,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Hanna T. Rose, 1962",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/174332,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2000.366.1a, b",false,true,81763,Costume Institute,Cocktail Dress,Cocktail dress,American,,,,,Designer,,Ceil Chapman,"American, born 1912",,Chapman Ceil,American,1912,1912,1950s,1950,1959,synthetic,,"Anonymous Gift, 2000",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/81763,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1995.238a–z,false,true,79894,Costume Institute,Vanity Case,Vanity case,American,,,,,Designer,,Richard Hudnut,"American, 1855–1928",,"Hudnut, RIchard",American,1855,1928,1920s,1920,1929,"leather, brass",,"Gift of Lois Small Zabriskie, 1995",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/79894,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.300.5369,false,true,169584,Costume Institute,Apron,Apron,American,,,,,Designer,,L. S. Plaut & Company,"American, 1870–1923",,Plaut & Company L. S.,American,1870,1923,1930–39,1930,1939,Cotton,,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Mrs. Albert Ogden in memory of Sheldon Stewart, 1964",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/169584,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.300.1315,false,true,156017,Costume Institute,Evening dress,Evening dress,American,,,,,Designer,,Peggy Hoyt,"American, 1893–1937",,Hoyt Peggy,American,1893,1937,spring/summer 1928,1928,1928,"silk, rhinestones",,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Anonymous gift, 1964",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/156017,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2009.300.6926a, b",false,true,173946,Costume Institute,Riding Habit,Riding Habit,American,,,,,Designer,,Saks & Company,"American, 1902–1924",,Saks & Company,American,1902,1924,1922,1922,1922,"Wool, leather",,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Mrs. A. E. Laurancelle, 1956",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/173946,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -C.I.68.53.5a–h,true,true,81137,Costume Institute,Wedding Ensemble,Wedding ensemble,American,,,,,Designer,,Herman Rossberg,"American, active 1880s",,Rossberg Herman,American,1880,1880,1887,1887,1887,wool,,"Gift of Mrs. James G. Flockhart, 1968",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/81137,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.300.104,false,true,155711,Costume Institute,Evening coat,Evening coat,American,,,,,Designer,,C. G. Gunther's Sons,"American, founded 1820",,Gunther,American,1820,1820,ca. 1930,1928,1932,"silk, fur, metal",,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Mrs. J. W. Post, 1944",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/155711,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.300.3862,false,true,158846,Costume Institute,Cape (Tippet),Tippet,American,,,,,Designer,,C. G. Gunther's Sons,"American, founded 1820",,Gunther,American,1820,1820,ca. 1865,1863,1867,"fur, silk",,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Emma Crampton Trainer, 1968",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/158846,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.300.6528,false,true,173541,Costume Institute,Evening coat,Evening coat,American,,,,,Designer,,C. G. Gunther's Sons,"American, founded 1820",,Gunther,American,1820,1820,1914,1914,1914,"Silk, metallic",,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Mrs. J. W. Post, 1944",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/173541,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2009.300.478a, b",false,true,159013,Costume Institute,Accessory Set,Accessory set,American,,,,,Designer,,C. G. Gunther's Sons,"American, founded 1820",,Gunther,American,1820,1820,1890–99,1890,1899,"fur, silk",,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Eleanor F. Peck, 1969",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/159013,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -C.I.57.38.1,false,true,107859,Costume Institute,Cape,Cape,American,,,,,Designer,,Lord & Taylor,"American, founded 1826",,Lord & Taylor,American,1826,2050,ca. 1885,1880,1890,"jet, silk, cotton",,"Gift of Lord and Taylor, 1957",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/107859,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1979.152.1,false,true,91143,Costume Institute,Hunting coat,Hunting coat,American,,,,,Designer,,John Patterson & Co.,"American, founded 1852",,Patterson John,American,1852,1852,late 19th century,1850,1899,"wool, silk, metal",,"Gift of Jessie Leonard Hill, 1979",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/91143,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -C.I.69.24.1,false,true,81727,Costume Institute,Coat,Coat,American,,,,,Designer,,John Patterson & Co.,"American, founded 1852",,Patterson John,American,1852,1852,1890s,1890,1899,wool,,"Gift of Mrs. Edward E. Murray, 1969",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/81727,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.300.4969,false,true,169192,Costume Institute,Cap,Cap,American,,,,,Designer,,John Patterson & Co.,"American, founded 1852",,Patterson John,American,1852,1852,1929,1929,1929,"Wool, leather, silk",,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Anonymous gift, 1957",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/169192,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.300.7030,false,true,174020,Costume Institute,Coat,Coat,American,,,,,Designer,,John Patterson & Co.,"American, founded 1852",,Patterson John,American,1852,1852,1929,1929,1929,Wool,,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Anonymous gift, 1957",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/174020,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2009.300.820a, b",false,true,159394,Costume Institute,Uniform Coat,Uniform coat,American,,,,,Designer,,John Patterson & Co.,"American, founded 1852",,Patterson John,American,1852,1852,1915,1915,1915,"wool, fur",,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Anonymous gift, 1957",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/159394,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.300.819a–f,false,true,159391,Costume Institute,Uniform,Uniform,American,,,,,Designer,,John Patterson & Co.,"American, founded 1852",,Patterson John,American,1852,1852,1928,1928,1928,"wool, leather",,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Anonymous gift, 1957",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/159391,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.300.7029a–c,false,true,174019,Costume Institute,Uniform,Uniform,American,,,,,Designer,,John Patterson & Co.,"American, founded 1852",,Patterson John,American,1852,1852,1928,1928,1928,"Wool, leather",,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Anonymous gift, 1957",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/174019,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"1981.21.2a, b",false,true,92337,Costume Institute,Dinner dress,Dinner dress,American,,,,,Designer,,Alice M. Dunstan,"American, active 1892–1926",,Dunstan Alice M.,American,1892,1926,ca. 1895,1890,1900,silk,,"Purchase, Irene Lewisohn Bequest, 1981",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/92337,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.300.3289,false,true,158208,Costume Institute,Evening dress,Evening dress,American,,,,,Designer,,Herbert Luey,"American, Northfield, Massachusetts 1860–1916 Brooklyn",,Luey Herbert,American,1860,1916,1912–14,1912,1914,silk,,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Amelia Beard Hollenback, 1966",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/158208,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.300.6561,false,true,173568,Costume Institute,Evening dress,Evening dress,American,,,,,Designer,,Herbert Luey,"American, Northfield, Massachusetts 1860–1916 Brooklyn",,Luey Herbert,American,1860,1916,1908–11,1908,1911,"Silk, beads, metallic",,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Helen Rice, 1946",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/173568,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.300.7483,false,true,175822,Costume Institute,Dinner dress,Dinner dress,American,,,,,Designer,,Herbert Luey,"American, Northfield, Massachusetts 1860–1916 Brooklyn",,Luey Herbert,American,1860,1916,1908–10,1908,1910,"Silk, cotton",,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Amelia Beard Hollenback, 1966",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/175822,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2009.300.3030a, b",false,true,157922,Costume Institute,Evening dress,Evening dress,American,,,,,Designer,,Herbert Luey,"American, Northfield, Massachusetts 1860–1916 Brooklyn",,Luey Herbert,American,1860,1916,ca. 1890,1888,1892,"silk, linen",,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Mrs. James Dowd Lester, 1942",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/157922,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2009.300.6764a, b",false,true,173779,Costume Institute,Bridesmaid dress,Bridesmaid dress,American,,,,,Designer,,Herbert Luey,"American, Northfield, Massachusetts 1860–1916 Brooklyn",,Luey Herbert,American,1860,1916,1880,1880,1880,Silk,,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Polly Dix, 1954",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/173779,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.8,false,true,96595,Costume Institute,Evening coat,Evening coat,French,,,,,Designer|Design House,,Jeanne Lanvin|House of Lanvin,"French, 1867–1946|French, founded 1889",,"Lanvin, Jeanne|Lanvin, House of",French|French,1867 |1889,1946 |9999,1926,1926,1926,[no medium available],,"Gift of Mrs. Juliet Mason, 1972",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/96595,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1984.7,false,true,104694,Costume Institute,Picture hat,Picture hat,French,,,,,Designer|Design House,Attributed to|Attributed to,Jeanne Lanvin|House of Lanvin,"French, 1867–1946|French, founded 1889",,"Lanvin, Jeanne|Lanvin, House of",French|French,1867 |1889,1946 |9999,ca. 1922,1917,1927,"silk, glass",,"Purchase, Gifts in memory of Elizabeth Lawrence, 1983",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/104694,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1974.91,false,true,96596,Costume Institute,Evening coat,Evening coat,French,,,,,Designer|Design House,,Jeanne Lanvin|House of Lanvin,"French, 1867–1946|French, founded 1889",,"Lanvin, Jeanne|Lanvin, House of",French|French,1867 |1889,1946 |9999,1931–32,1931,1932,[no medium available],,"Gift of Pamela Rankin Smith, 1974",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/96596,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.77,false,true,104257,Costume Institute,Bonnet,Bonnet,French,,,,,Designer|Design House,,Jeanne Lanvin|House of Lanvin,"French, 1867–1946|French, founded 1889",,"Lanvin, Jeanne|Lanvin, House of",French|French,1867 |1889,1946 |9999,ca. 1910,1905,1915,"cotton, metallic thread, silk",,"Purchase, Irene Lewisohn Bequest, 1989",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/104257,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.393,false,true,84806,Costume Institute,Evening dress,Evening dress,French,,,,,Designer|Design House,,Jeanne Lanvin|House of Lanvin,"French, 1867–1946|French, founded 1889",,"Lanvin, Jeanne|Lanvin, House of",French|French,1867 |1889,1946 |9999,1933–37,1933,1937,synthetic,,"Gift of Mrs. Hilda Sutton, 1975",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/84806,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.215,false,true,82597,Costume Institute,Evening coat,Evening coat,French,,,,,Designer|Design House,,Jeanne Lanvin|House of Lanvin,"French, 1867–1946|French, founded 1889",,"Lanvin, Jeanne|Lanvin, House of",French|French,1867 |1889,1946 |9999,spring/summer 1927,1927,1927,"cotton, wool",,"Isabel Shults Fund, 1986",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/82597,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2002.162,false,true,84146,Costume Institute,Dress,Dress,French,,,,,Designer|Design House,,Jeanne Lanvin|House of Lanvin,"French, 1867–1946|French, founded 1889",,"Lanvin, Jeanne|Lanvin, House of",French|French,1867 |1889,1946 |9999,1916,1916,1916,"silk, glass",,"Gift of Charles Kleibacker, 2002",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/84146,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1980.98.4,false,true,84818,Costume Institute,Evening cape,Evening cape,French,,,,,Designer|Design House,,Jeanne Lanvin|House of Lanvin,"French, 1867–1946|French, founded 1889",,"Lanvin, Jeanne|Lanvin, House of",French|French,1867 |1889,1946 |9999,late 1930s,1935,1939,"cotton, plastic",,"Gift of Sven E. Hsia, 1980",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/84818,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.30.13,false,true,96597,Costume Institute,Evening wrap,Evening wrap,French,,,,,Designer|Design House,,Jeanne Lanvin|House of Lanvin,"French, 1867–1946|French, founded 1889",,"Lanvin, Jeanne|Lanvin, House of",French|French,1867 |1889,1946 |9999,1929,1929,1929,fur,,"Gift of Madame Lilliana Teruzzi, 1972",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/96597,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1973.199.1,false,true,84813,Costume Institute,Evening dress,"""Fusée""",French,,,,,Designer|Design House,,Jeanne Lanvin|House of Lanvin,"French, 1867–1946|French, founded 1889",,"Lanvin, Jeanne|Lanvin, House of",French|French,1867 |1889,1946 |9999,1938,1938,1938,silk,,"Gift of Mrs. Lawrence W. Snell, 1973",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/84813,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1979.122.1,false,true,81478,Costume Institute,Robe de Style,Robe de Style,French,,,,,Designer|Design House,,Jeanne Lanvin|House of Lanvin,"French, 1867–1946|French, founded 1889",,"Lanvin, Jeanne|Lanvin, House of",French|French,1867 |1889,1946 |9999,1924–25,1924,1925,"silk, cotton",,"Gift of Mrs. William B. Given Jr., 1979",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/81478,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1979.122.2,false,true,81479,Costume Institute,Coat,Coat,French,,,,,Designer|Design House,,Jeanne Lanvin|House of Lanvin,"French, 1867–1946|French, founded 1889",,"Lanvin, Jeanne|Lanvin, House of",French|French,1867 |1889,1946 |9999,fall/winter 1924–25,1924,1925,"silk, wool, fur, metal",,"Gift of Mrs. William B. Given Jr., 1979",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/81479,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1979.344.8,false,true,84802,Costume Institute,Dinner dress,Dinner dress,French,,,,,Designer|Design House,,Jeanne Lanvin|House of Lanvin,"French, 1867–1946|French, founded 1889",,"Lanvin, Jeanne|Lanvin, House of",French|French,1867 |1889,1946 |9999,spring/summer 1934,1934,1934,silk,,"Gift of Mrs. Anthony Wilson, 1979",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/84802,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1979.344.9,false,true,84803,Costume Institute,Dinner dress,Dinner dress,French,,,,,Designer|Design House,,Jeanne Lanvin|House of Lanvin,"French, 1867–1946|French, founded 1889",,"Lanvin, Jeanne|Lanvin, House of",French|French,1867 |1889,1946 |9999,1934,1934,1934,silk,,"Gift of Mrs. Anthony Wilson, 1979",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/84803,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1979.574.1,false,true,96601,Costume Institute,Coat,Coat,French,,,,,Designer|Design House,,Jeanne Lanvin|House of Lanvin,"French, 1867–1946|French, founded 1889",,"Lanvin, Jeanne|Lanvin, House of",French|French,1867 |1889,1946 |9999,fall/winter 1927–28,1927,1928,"wool, silk",,"Gift of Mrs. Herbert Parsons, 1979",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/96601,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1995.28.20,false,true,80176,Costume Institute,Coat,Coat,French,,,,,Designer|Design House,,Jeanne Lanvin|House of Lanvin,"French, 1867–1946|French, founded 1889",,"Lanvin, Jeanne|Lanvin, House of",French|French,1867 |1889,1946 |9999,fall/winter 1932–33,1932,1933,"silk, fur (possibly ermine)",,"Gift of Estate of Lillian Gish, 1995",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/80176,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1995.588.2,false,true,80337,Costume Institute,Robe de Style,Robe de Style,French,,,,,Designer|Design House,,Jeanne Lanvin|House of Lanvin,"French, 1867–1946|French, founded 1889",,"Lanvin, Jeanne|Lanvin, House of",French|French,1867 |1889,1946 |9999,ca. 1926,1921,1931,silk,,"Gift of Miriam K. W. Coletti, 1995",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/80337,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -C.I.62.8.5,false,true,82646,Costume Institute,Robe de Style,Robe de Style,French,,,,,Designer|Design House,,Jeanne Lanvin|House of Lanvin,"French, 1867–1946|French, founded 1889",,"Lanvin, Jeanne|Lanvin, House of",French|French,1867 |1889,1946 |9999,1922,1922,1922,"silk, metal, glass",,"Gift of Mrs. Stephen C. Clark, 1962",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/82646,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.198.10,false,true,103869,Costume Institute,Coat,Coat,French,,,,,Designer|Design House,,Jeanne Lanvin|House of Lanvin,"French, 1867–1946|French, founded 1889",,"Lanvin, Jeanne|Lanvin, House of",French|French,1867 |1889,1946 |9999,fall/winter 1930–31,1930,1931,silk,,"Gift of Mrs. William Rhinelander Stewart, 1976",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/103869,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.198.11,false,true,103870,Costume Institute,Coat,Coat,French,,,,,Designer|Design House,,Jeanne Lanvin|House of Lanvin,"French, 1867–1946|French, founded 1889",,"Lanvin, Jeanne|Lanvin, House of",French|French,1867 |1889,1946 |9999,fall/winter 1930–31,1930,1931,silk,,"Gift of Mrs. William Rhinelander Stewart, 1976",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/103870,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.198.12,false,true,103279,Costume Institute,Dress,Dress,French,,,,,Designer|Design House,,Jeanne Lanvin|House of Lanvin,"French, 1867–1946|French, founded 1889",,"Lanvin, Jeanne|Lanvin, House of",French|French,1867 |1889,1946 |9999,fall/winter 1930–31,1930,1931,"cotton, silk",,"Gift of Mrs. William Rhinelander Stewart, 1976",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/103279,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1978.165.19,false,true,84810,Costume Institute,Evening dress,Evening dress,French,,,,,Designer|Design House,,Jeanne Lanvin|House of Lanvin,"French, 1867–1946|French, founded 1889",,"Lanvin, Jeanne|Lanvin, House of",French|French,1867 |1889,1946 |9999,1937–39,1937,1939,"silk, cotton",,"Gift of Mrs. Stephen M. Kellen, 1978",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/84810,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1978.295.22,false,true,96599,Costume Institute,Evening coat,Evening coat,French,,,,,Designer|Design House,,Jeanne Lanvin|House of Lanvin,"French, 1867–1946|French, founded 1889",,"Lanvin, Jeanne|Lanvin, House of",French|French,1867 |1889,1946 |9999,ca. 1917,1912,1922,silk,,"Purchase, Marcia Sand Bequest, in memory of her daughter, Tiger (Joan) Morse, 1978",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/96599,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1979.344.18,false,true,108910,Costume Institute,Teddy,Teddy,French,,,,,Designer|Design House,,Jeanne Lanvin|House of Lanvin,"French, 1867–1946|French, founded 1889",,"Lanvin, Jeanne|Lanvin, House of",French|French,1867 |1889,1946 |9999,1920s,1920,1929,silk,,"Gift of Mrs. Anthony Wilson, 1979",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/108910,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1979.344.19,false,true,84807,Costume Institute,Teddy,Teddy,French,,,,,Designer|Design House,,Jeanne Lanvin|House of Lanvin,"French, 1867–1946|French, founded 1889",,"Lanvin, Jeanne|Lanvin, House of",French|French,1867 |1889,1946 |9999,mid-1930s,1933,1937,silk,,"Gift of Mrs. Anthony Wilson, 1979",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/84807,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1984.587.17,false,true,96602,Costume Institute,Ski jacket,Ski jacket,French,,,,,Designer|Design House,,Jeanne Lanvin|House of Lanvin,"French, 1867–1946|French, founded 1889",,"Lanvin, Jeanne|Lanvin, House of",French|French,1867 |1889,1946 |9999,fall/winter 1924–25,1924,1925,wool,,"Gift of Miriam Whitney Coletti, 1984",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/96602,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -C.I.40.22.1,false,true,102920,Costume Institute,Dress,Dress,French,,,,,Designer|Design House,,Jeanne Lanvin|House of Lanvin,"French, 1867–1946|French, founded 1889",,"Lanvin, Jeanne|Lanvin, House of",French|French,1867 |1889,1946 |9999,1910,1910,1910,silk,,"Gift of Mrs. Whitewright Watson, 1940",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/102920,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -C.I.40.22.2,false,true,102921,Costume Institute,Dress,Dress,French,,,,,Designer|Design House,,Jeanne Lanvin|House of Lanvin,"French, 1867–1946|French, founded 1889",,"Lanvin, Jeanne|Lanvin, House of",French|French,1867 |1889,1946 |9999,1910,1910,1910,silk,,"Gift of Mrs. Whitewright Watson, 1940",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/102921,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -C.I.56.49.1,false,true,81970,Costume Institute,Robe de Style,Robe de Style,French,,,,,Designer|Design House,,Jeanne Lanvin|House of Lanvin,"French, 1867–1946|French, founded 1889",,"Lanvin, Jeanne|Lanvin, House of",French|French,1867 |1889,1946 |9999,1924–25,1924,1925,silk,,"Gift of Mrs. W.R. Grace, 1956",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/81970,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -C.I.62.58.1,false,true,81462,Costume Institute,Robe de Style,Robe de Style,French,,,,,Designer|Design House,,Jeanne Lanvin|House of Lanvin,"French, 1867–1946|French, founded 1889",,"Lanvin, Jeanne|Lanvin, House of",French|French,1867 |1889,1946 |9999,spring/summer 1924,1924,1924,"silk, metallic thread, glass",,"Gift of Mrs. Albert Spalding, 1962",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/81462,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -C.I.63.38.3,false,true,109363,Costume Institute,Blouse,Blouse,French,,,,,Designer|Design House,,Jeanne Lanvin|House of Lanvin,"French, 1867–1946|French, founded 1889",,"Lanvin, Jeanne|Lanvin, House of",French|French,1867 |1889,1946 |9999,1946,1946,1946,silk,,"Gift of Mrs. Moore Montgomery, 1963",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/109363,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -C.I.66.58.1,false,true,84065,Costume Institute,Evening jacket,Evening jacket,French,,,,,Designer|Design House,,Jeanne Lanvin|House of Lanvin,"French, 1867–1946|French, founded 1889",,"Lanvin, Jeanne|Lanvin, House of",French|French,1867 |1889,1946 |9999,1936–37,1936,1937,"silk, metallic thread, fur",,"Gift of Mrs. Leon L. Roos, 1966",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/84065,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.300.357,false,true,158521,Costume Institute,Evening dress,Evening dress,French,,,,,Designer|Design House,,Jeanne Lanvin|House of Lanvin,"French, 1867–1946|French, founded 1889",,"Lanvin, Jeanne|Lanvin, House of",French|French,1867 |1889,1946 |9999,spring/summer 1938,1938,1938,"silk, mother-of-pearl, beads",,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Mrs. Ian B. MacDonald, 1965",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/158521,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.300.1288,false,true,155986,Costume Institute,Headdress,Headdress,French,,,,,Designer|Design House,,Jeanne Lanvin|House of Lanvin,"French, 1867–1946|French, founded 1889",,"Lanvin, Jeanne|Lanvin, House of",French|French,1867 |1889,1946 |9999,ca. 1925,1923,1927,"cotton, silk",,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Rodman A. Heeren, 1961",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/155986,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.300.1365,false,true,156071,Costume Institute,Evening dress,"""Phèdre""",French,,,,,Designer|Design House,,Jeanne Lanvin|House of Lanvin,"French, 1867–1946|French, founded 1889",,"Lanvin, Jeanne|Lanvin, House of",French|French,1867 |1889,1946 |9999,fall/winter 1933,1933,1933,"silk, metal",,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Mark Walsh, 1984",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/156071,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.300.1376,false,true,156084,Costume Institute,Evening dress,Evening dress,French,,,,,Designer|Design House,,Jeanne Lanvin|House of Lanvin,"French, 1867–1946|French, founded 1889",,"Lanvin, Jeanne|Lanvin, House of",French|French,1867 |1889,1946 |9999,fall/winter 1934–35,1934,1935,"silk, metal",,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of George Drew, 1988",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/156084,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.300.2221,false,true,157023,Costume Institute,Evening shawl,Evening shawl,French,,,,,Designer|Design House,Attributed to|Attributed to,Jeanne Lanvin|House of Lanvin,"French, 1867–1946|French, founded 1889",,"Lanvin, Jeanne|Lanvin, House of",French|French,1867 |1889,1946 |9999,ca. 1922,1920,1924,"silk, metal",,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Designated Purchase Fund, 1986",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/157023,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.300.2249,false,true,157053,Costume Institute,Evening belt,Evening belt,French,,,,,Designer|Design House,,Jeanne Lanvin|House of Lanvin,"French, 1867–1946|French, founded 1889",,"Lanvin, Jeanne|Lanvin, House of",French|French,1867 |1889,1946 |9999,ca. 1925,1923,1927,silk,,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Brooklyn Museum Collection",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/157053,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.300.2401,false,true,157223,Costume Institute,Hat,Hat,French,,,,,Designer|Design House,,Jeanne Lanvin|House of Lanvin,"French, 1867–1946|French, founded 1889",,"Lanvin, Jeanne|Lanvin, House of",French|French,1867 |1889,1946 |9999,1932,1932,1932,silk,,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Mrs. Frank L. Babbott, 1954",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/157223,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.300.2506,false,true,157340,Costume Institute,Robe de Style,Robe de Style,French,,,,,Designer|Design House,,Jeanne Lanvin|House of Lanvin,"French, 1867–1946|French, founded 1889",,"Lanvin, Jeanne|Lanvin, House of",French|French,1867 |1889,1946 |9999,fall/winter 1926,1926,1926,"silk, rhinestones, pearls",,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Ogden Goelet, Peter Goelet, and Madison Clews, 1962",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/157340,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.300.2520,false,true,157356,Costume Institute,Evening dress,Evening dress,French,,,,,Designer|Design House,,Jeanne Lanvin|House of Lanvin,"French, 1867–1946|French, founded 1889",,"Lanvin, Jeanne|Lanvin, House of",French|French,1867 |1889,1946 |9999,ca. 1935,1933,1937,"silk, metal",,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Ogden Goelet, Peter Goelet, and Madison Clews, 1963",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/157356,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.300.2543,false,true,157382,Costume Institute,Evening bag,Evening bag,French,,,,,Designer|Design House,,Jeanne Lanvin|House of Lanvin,"French, 1867–1946|French, founded 1889",,"Lanvin, Jeanne|Lanvin, House of",French|French,1867 |1889,1946 |9999,1925–35,1925,1935,"silk, metal Silk, metallic",,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of the executors of the estate of Clara M. Blum in memory of Mr. and Mrs. Albert Blum, 1966",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/157382,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.300.2563,false,true,157403,Costume Institute,Evening cloche,Evening cloche,French,,,,,Designer|Design House,,Jeanne Lanvin|House of Lanvin,"French, 1867–1946|French, founded 1889",,"Lanvin, Jeanne|Lanvin, House of",French|French,1867 |1889,1946 |9999,ca. 1925,1923,1927,"cotton, metal",,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Mrs. Frederick H. Prince, Jr., 1967",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/157403,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.300.2565,false,true,157405,Costume Institute,Evening dress,Evening dress,French,,,,,Designer|Design House,Attributed to|Attributed to,Jeanne Lanvin|House of Lanvin,"French, 1867–1946|French, founded 1889",,"Lanvin, Jeanne|Lanvin, House of",French|French,1867 |1889,1946 |9999,ca. 1922,1920,1924,silk,,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Mrs. Frederick H. Prince, Jr., 1967",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/157405,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.300.2572,false,true,157414,Costume Institute,Evening cloche,Evening cloche,French,,,,,Designer|Design House,,Jeanne Lanvin|House of Lanvin,"French, 1867–1946|French, founded 1889",,"Lanvin, Jeanne|Lanvin, House of",French|French,1867 |1889,1946 |9999,ca. 1925,1923,1927,"silk, metal",,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Mrs. Frederick H. Prince, Jr., 1967",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/157414,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.300.2631,false,true,157479,Costume Institute,Blouse,Blouse,French,,,,,Designer|Design House,,Jeanne Lanvin|House of Lanvin,"French, 1867–1946|French, founded 1889",,"Lanvin, Jeanne|Lanvin, House of",French|French,1867 |1889,1946 |9999,ca. 1928,1926,1930,silk,,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Martin Kamer, 1984",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/157479,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.300.2635,false,true,157483,Costume Institute,Evening dress,"""Jolibois""",French,,,,,Designer|Design House,,Jeanne Lanvin|House of Lanvin,"French, 1867–1946|French, founded 1889",,"Lanvin, Jeanne|Lanvin, House of",French|French,1867 |1889,1946 |9999,fall/winter 1922–23,1922,1923,silk,,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Louise Gross, 1986",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/157483,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.300.2640,false,true,157490,Costume Institute,Evening coat,Evening coat,French,,,,,Designer|Design House,,Jeanne Lanvin|House of Lanvin,"French, 1867–1946|French, founded 1889",,"Lanvin, Jeanne|Lanvin, House of",French|French,1867 |1889,1946 |9999,spring/summer 1926,1926,1926,silk,,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Gardner and Diana Thoenen in Memory of Meredith Smith Thoenen, 1986",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/157490,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.300.2782,false,true,157646,Costume Institute,Evening dress,Evening dress,French,,,,,Designer|Design House,,Jeanne Lanvin|House of Lanvin,"French, 1867–1946|French, founded 1889",,"Lanvin, Jeanne|Lanvin, House of",French|French,1867 |1889,1946 |9999,1920,1920,1920,"silk, rhinestones, linen",,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Countess Edna E. de Frise, 1953",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/157646,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.300.2802,false,true,157669,Costume Institute,Robe de Style,Robe de Style,French,,,,,Designer|Design House,,Jeanne Lanvin|House of Lanvin,"French, 1867–1946|French, founded 1889",,"Lanvin, Jeanne|Lanvin, House of",French|French,1867 |1889,1946 |9999,1922,1922,1922,"metal, silk",,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Mrs. Norman W. Wassman, 1956",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/157669,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.300.2860,false,true,157733,Costume Institute,Evening dress,Evening dress,French,,,,,Designer|Design House,,Jeanne Lanvin|House of Lanvin,"French, 1867–1946|French, founded 1889",,"Lanvin, Jeanne|Lanvin, House of",French|French,1867 |1889,1946 |9999,1925,1925,1925,"silk, metal",,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Helen Appleton Read, 1962",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/157733,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.300.3225,false,true,158139,Costume Institute,Evening dress,Evening dress,French,,,,,Designer|Design House,,Jeanne Lanvin|House of Lanvin,"French, 1867–1946|French, founded 1889",,"Lanvin, Jeanne|Lanvin, House of",French|French,1867 |1889,1946 |9999,ca. 1923,1921,1925,silk,,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Mr. and Mrs. Maxime L. Hermanos, 1961",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/158139,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.300.3400,false,true,158335,Costume Institute,Evening dress,Evening dress,French,,,,,Designer|Design House,,Jeanne Lanvin|House of Lanvin,"French, 1867–1946|French, founded 1889",,"Lanvin, Jeanne|Lanvin, House of",French|French,1867 |1889,1946 |9999,fall/winter 1938–39,1938,1939,"silk, metal",,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Brooklyn Museum Collection",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/158335,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.300.6756,false,true,173768,Costume Institute,Evening dress,Evening dress,French,,,,,Designer|Design House,Attributed to|Attributed to,Jeanne Lanvin|House of Lanvin,"French, 1867–1946|French, founded 1889",,"Lanvin, Jeanne|Lanvin, House of",French|French,1867 |1889,1946 |9999,ca. 1920,1918,1922,Silk,,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Countess Edna E. de Frise, 1953",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/173768,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.300.6767,false,true,173782,Costume Institute,Evening coat,Evening coat,French,,,,,Designer|Design House,,Jeanne Lanvin|House of Lanvin,"French, 1867–1946|French, founded 1889",,"Lanvin, Jeanne|Lanvin, House of",French|French,1867 |1889,1946 |9999,winter 1932–33,1932,1933,Silk,,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Mrs. Frank L. Babbott, 1954",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/173782,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.300.6768,false,true,173783,Costume Institute,Hat,Hat,French,,,,,Designer|Design House,,Jeanne Lanvin|House of Lanvin,"French, 1867–1946|French, founded 1889",,"Lanvin, Jeanne|Lanvin, House of",French|French,1867 |1889,1946 |9999,1932,1932,1932,Wool,,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Mrs. Frank L. Babbott, 1954",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/173783,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.300.6850,false,true,173863,Costume Institute,Evening dress,Evening dress,French,,,,,Designer|Design House,,Jeanne Lanvin|House of Lanvin,"French, 1867–1946|French, founded 1889",,"Lanvin, Jeanne|Lanvin, House of",French|French,1867 |1889,1946 |9999,winter 1928,1928,1928,Silk,,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Arturo and Paul Peralta-Ramos, 1955",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/173863,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.300.7025,false,true,174015,Costume Institute,Evening dress,Evening dress,French,,,,,Designer|Design House,Attributed to|Attributed to,Jeanne Lanvin|House of Lanvin,"French, 1867–1946|French, founded 1889",,"Lanvin, Jeanne|Lanvin, House of",French|French,1867 |1889,1946 |9999,1930,1930,1930,Silk,,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Mrs. George B. Wells, 1957",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/174015,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.300.7328,false,true,175780,Costume Institute,Overcoat,Overcoat,French,,,,,Designer|Design House,,Jeanne Lanvin|House of Lanvin,"French, 1867–1946|French, founded 1889",,"Lanvin, Jeanne|Lanvin, House of",French|French,1867 |1889,1946 |9999,1934,1934,1934,Wool,,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of A. C. Moss, 1964",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/175780,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.300.7533,false,true,174586,Costume Institute,Hat,Hat,French,,,,,Designer|Design House,,Jeanne Lanvin|House of Lanvin,"French, 1867–1946|French, founded 1889",,"Lanvin, Jeanne|Lanvin, House of",French|French,1867 |1889,1946 |9999,ca. 1918,1916,1920,"Cotton, wire",,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Mrs. Frederick H. Prince, Jr., 1967",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/174586,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.300.7534,false,true,174587,Costume Institute,Hat,Hat,French,,,,,Designer|Design House,,Jeanne Lanvin|House of Lanvin,"French, 1867–1946|French, founded 1889",,"Lanvin, Jeanne|Lanvin, House of",French|French,1867 |1889,1946 |9999,ca. 1935,1933,1937,"Straw, silk",,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Mrs. Frederick H. Prince, Jr., 1967",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/174587,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.300.8114,false,true,175139,Costume Institute,Evening dress,Evening dress,French,,,,,Designer|Design House,Possibly|Possibly,Jeanne Lanvin|House of Lanvin,"French, 1867–1946|French, founded 1889",,"Lanvin, Jeanne|Lanvin, House of",French|French,1867 |1889,1946 |9999,ca. 1927,1925,1929,"Cotton, beads, metallic",,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Rena Gill, 1985",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/175139,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"C.I.60.19a, b",false,true,84022,Costume Institute,Evening ensemble,"""Ko.I.Noor""",French,,,,,Designer|Design House,,Jeanne Lanvin|House of Lanvin,"French, 1867–1946|French, founded 1889",,"Lanvin, Jeanne|Lanvin, House of",French|French,1867 |1889,1946 |9999,spring/summer 1927,1927,1927,"silk, metal b) silk, metal",,"Gift of Mme. Yves Lanvin, 1960",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/84022,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"C.I.61.22a, b",false,true,103601,Costume Institute,Suit,Suit,French,,,,,Designer|Design House,,Jeanne Lanvin|House of Lanvin,"French, 1867–1946|French, founded 1889",,"Lanvin, Jeanne|Lanvin, House of",French|French,1867 |1889,1946 |9999,1927,1927,1927,"wool, silk",,"Gift of Mrs. Dale Scott, 1961",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/103601,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"1993.423.1a, b",false,true,80887,Costume Institute,Evening ensemble,Evening ensemble,French,,,,,Designer|Design House,,Jeanne Lanvin|House of Lanvin,"French, 1867–1946|French, founded 1889",,"Lanvin, Jeanne|Lanvin, House of",French|French,1867 |1889,1946 |9999,ca. 1934,1929,1939,"silk, metal",,"Gift of Miriam W. Coletti, 1993",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/80887,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1982.528.7a–c,false,true,84892,Costume Institute,Evening dress,Evening dress,French,,,,,Designer|Design House,Attributed to|Attributed to,Jeanne Lanvin|House of Lanvin,"French, 1867–1946|French, founded 1889",,"Lanvin, Jeanne|Lanvin, House of",French|French,1867 |1889,1946 |9999,ca. 1935,1930,1940,silk,,"Gift of Mrs. Charles C. Paterson, 1982",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/84892,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.377.1a–c,false,true,81484,Costume Institute,Evening dress,Evening dress,French,,,,,Designer|Design House,,Jeanne Lanvin|House of Lanvin,"French, 1867–1946|French, founded 1889",,"Lanvin, Jeanne|Lanvin, House of",French|French,1867 |1889,1946 |9999,ca. 1930,1925,1935,silk,,"Gift of Mrs. Jill L. Leinbach & Mr. James L. Long in memory of their mother, Mrs. Jane P. Long, 1986",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/81484,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2009.300.96a, b",false,true,159558,Costume Institute,Evening coat,Evening coat,French,,,,,Designer|Design House,,Jeanne Lanvin|House of Lanvin,"French, 1867–1946|French, founded 1889",,"Lanvin, Jeanne|Lanvin, House of",French|French,1867 |1889,1946 |9999,fall/winter 1932–33,1932,1933,"wool, fur",,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Mrs. Frank L. Babbott, Jr., 1941",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/159558,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"C.I.46.4.18a, b",true,true,82103,Costume Institute,Evening dress,"""Cyclone""",French,,,,,Designer|Design House,,Jeanne Lanvin|House of Lanvin,"French, 1867–1946|French, founded 1889",,"Lanvin, Jeanne|Lanvin, House of",French|French,1867 |1889,1946 |9999,1939,1939,1939,"silk, spangles",,"Gift of Mrs. Harrison Williams, Lady Mendl, and Mrs. Ector Munn, 1946",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/82103,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"C.I.54.49.3a, b",false,true,103584,Costume Institute,Ensemble,Ensemble,French,,,,,Designer|Design House,,Jeanne Lanvin|House of Lanvin,"French, 1867–1946|French, founded 1889",,"Lanvin, Jeanne|Lanvin, House of",French|French,1867 |1889,1946 |9999,ca. 1921,1916,1926,wool,,"Gift of Mrs. Roland L. Redmond, 1954",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/103584,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1978.165.18a–c,false,true,84809,Costume Institute,Ensemble,Ensemble,French,,,,,Designer|Design House,Attributed to,Jeanne Lanvin|House of Lanvin,"French, 1867–1946|French, founded 1889",,"Lanvin, Jeanne|Lanvin, House of",French|French,1867 |1889,1946 |9999,1937–38,1937,1938,a) silk b) cotton c) glass,,"Gift of Mrs. Stephen M. Kellen, 1978",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/84809,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1978.184.12a–c,false,true,94981,Costume Institute,Evening ensemble,Evening ensemble,French,,,,,Designer|Design House,,Jeanne Lanvin|House of Lanvin,"French, 1867–1946|French, founded 1889",,"Lanvin, Jeanne|Lanvin, House of",French|French,1867 |1889,1946 |9999,1930,1930,1930,"silk, metallic thread",,"Purchase, Marcia Sand Bequest, in memory of her daughter, Tiger (Joan) Morse, 1978",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/94981,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2009.300.436a, b",false,true,158967,Costume Institute,Evening dress,Evening dress,French,,,,,Designer|Design House,,Jeanne Lanvin|House of Lanvin,"French, 1867–1946|French, founded 1889",,"Lanvin, Jeanne|Lanvin, House of",French|French,1867 |1889,1946 |9999,spring/summer 1937,1937,1937,"silk, leather",,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Mrs. Frederick H. Prince, Jr., 1967",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/158967,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -C.I.46.4.17a–c,false,true,82102,Costume Institute,Dinner dress,Dinner dress,French,,,,,Designer|Design House,,Jeanne Lanvin|House of Lanvin,"French, 1867–1946|French, founded 1889",,"Lanvin, Jeanne|Lanvin, House of",French|French,1867 |1889,1946 |9999,1939,1939,1939,"silk, spangles",,"Gift of Mrs. Harrison Williams, Lady Mendl, and Mrs. Ector Munn, 1946",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/82102,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"C.I.58.34.19a, b",false,true,84557,Costume Institute,Dress,Dress,French,,,,,Designer|Design House,,Jeanne Lanvin|House of Lanvin,"French, 1867–1946|French, founded 1889",,"Lanvin, Jeanne|Lanvin, House of",French|French,1867 |1889,1946 |9999,1937,1937,1937,"cotton, silk",,"Gift of Mrs. John Chambers Hughes, 1958",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/84557,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"C.I.58.34.24a, b",false,true,84239,Costume Institute,Suit,Suit,French,,,,,Designer|Design House,,Jeanne Lanvin|House of Lanvin,"French, 1867–1946|French, founded 1889",,"Lanvin, Jeanne|Lanvin, House of",French|French,1867 |1889,1946 |9999,1940,1940,1940,silk,,"Gift of Mrs. John Chambers Hughes, 1958",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/84239,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1978.582.199a–e,false,true,97353,Costume Institute,Wedding Ensemble,Wedding ensemble,French,,,,,Designer|Design House,,Jeanne Lanvin|House of Lanvin,"French, 1867–1946|French, founded 1889",,"Lanvin, Jeanne|Lanvin, House of",French|French,1867 |1889,1946 |9999,1940,1940,1940,"linen, silk, cotton",,"Gift of Marvin B. Patterson (Mrs. Jefferson Patterson), 1978",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/97353,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2009.300.1259a, b",false,true,155953,Costume Institute,Evening ensemble,Evening ensemble,French,,,,,Designer|Design House,,Jeanne Lanvin|House of Lanvin,"French, 1867–1946|French, founded 1889",,"Lanvin, Jeanne|Lanvin, House of",French|French,1867 |1889,1946 |9999,spring/summer 1935,1935,1935,silk,,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Ogden Goelet, Peter Goelet and Madison Clews in memory of Mrs. Henry Clews, 1960",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/155953,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2009.300.1318a, b",false,true,156020,Costume Institute,Evening dress,"""Roseraie""",French,,,,,Designer|Design House,,Jeanne Lanvin|House of Lanvin,"French, 1867–1946|French, founded 1889",,"Lanvin, Jeanne|Lanvin, House of",French|French,1867 |1889,1946 |9999,spring/summer 1923,1923,1923,silk,,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Anonymous gift, 1964",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/156020,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2009.300.1335a, b",false,true,156039,Costume Institute,Evening dress,Evening dress,French,,,,,Designer|Design House,,Jeanne Lanvin|House of Lanvin,"French, 1867–1946|French, founded 1889",,"Lanvin, Jeanne|Lanvin, House of",French|French,1867 |1889,1946 |9999,spring/summer 1938,1938,1938,silk,,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Mrs. Frederick H. Prince, Jr., 1967",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/156039,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2009.300.2228a, b",false,true,157030,Costume Institute,Evening dress,Evening dress,French,,,,,Designer|Design House,,Jeanne Lanvin|House of Lanvin,"French, 1867–1946|French, founded 1889",,"Lanvin, Jeanne|Lanvin, House of",French|French,1867 |1889,1946 |9999,spring/summer 1923,1923,1923,"silk, metal, feather",,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Designated Purchase Fund, 1988",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/157030,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2009.300.2887a, b",false,true,157761,Costume Institute,Evening dress,Evening dress,French,,,,,Designer|Design House,,Jeanne Lanvin|House of Lanvin,"French, 1867–1946|French, founded 1889",,"Lanvin, Jeanne|Lanvin, House of",French|French,1867 |1889,1946 |9999,fall/winter 1925–26,1925,1926,"silk, metal",,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Mrs. Frederick H. Prince, Jr., 1967",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/157761,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2009.300.3182a, b",false,true,158091,Costume Institute,Evening dress,Evening dress,French,,,,,Designer|Design House,,Jeanne Lanvin|House of Lanvin,"French, 1867–1946|French, founded 1889",,"Lanvin, Jeanne|Lanvin, House of",French|French,1867 |1889,1946 |9999,fall/winter 1930–31,1930,1931,silk,,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Mrs. George B. Wells, 1957",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/158091,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2009.300.3310a, b",false,true,158233,Costume Institute,Evening dress,Evening dress,French,,,,,Designer|Design House,,Jeanne Lanvin|House of Lanvin,"French, 1867–1946|French, founded 1889",,"Lanvin, Jeanne|Lanvin, House of",French|French,1867 |1889,1946 |9999,spring/summer 1937,1937,1937,synthetic,,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Mrs. Frederick H. Prince, Jr., 1967",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/158233,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2009.300.7310a, b",false,true,175769,Costume Institute,Evening dress,Evening dress,French,,,,,Designer|Design House,,Jeanne Lanvin|House of Lanvin,"French, 1867–1946|French, founded 1889",,"Lanvin, Jeanne|Lanvin, House of",French|French,1867 |1889,1946 |9999,winter 1930–31,1930,1931,Silk,,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Mrs. Anthony Wilson, 1963",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/175769,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.300.1364a–c,false,true,156070,Costume Institute,Evening ensemble,Evening ensemble,French,,,,,Designer|Design House,,Jeanne Lanvin|House of Lanvin,"French, 1867–1946|French, founded 1889",,"Lanvin, Jeanne|Lanvin, House of",French|French,1867 |1889,1946 |9999,spring/summer 1923,1923,1923,"silk, metal, rhinestones",,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Designated Purchase Fund, 1984",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/156070,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.300.2442a–c,false,true,157269,Costume Institute,Evening dress,Evening dress,French,,,,,Designer|Design House,,Jeanne Lanvin|House of Lanvin,"French, 1867–1946|French, founded 1889",,"Lanvin, Jeanne|Lanvin, House of",French|French,1867 |1889,1946 |9999,ca. 1935,1933,1937,"silk, metal",,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Mrs. Robert E. Blum, 1956",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/157269,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.300.2564a–d,false,true,157404,Costume Institute,Evening dress,Evening dress,French,,,,,Designer|Design House,,Jeanne Lanvin|House of Lanvin,"French, 1867–1946|French, founded 1889",,"Lanvin, Jeanne|Lanvin, House of",French|French,1867 |1889,1946 |9999,fall/winter 1923–24,1923,1924,"silk, metal",,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Mrs. Frederick H. Prince, Jr., 1967",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/157404,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.300.2628a–c,false,true,157476,Costume Institute,Ensemble,Ensemble,French,,,,,Designer|Design House,,Jeanne Lanvin|House of Lanvin,"French, 1867–1946|French, founded 1889",,"Lanvin, Jeanne|Lanvin, House of",French|French,1867 |1889,1946 |9999,ca. 1926,1924,1928,"silk, wool",,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Designated Purchase Fund, 1984",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/157476,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.300.2655a–c,false,true,157506,Costume Institute,Evening dress,Evening dress,French,,,,,Designer|Design House,,Jeanne Lanvin|House of Lanvin,"French, 1867–1946|French, founded 1889",,"Lanvin, Jeanne|Lanvin, House of",French|French,1867 |1889,1946 |9999,fall/winter 1926–27,1926,1927,"silk, metal",,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Brooklyn Museum Collection",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/157506,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.300.7026a–c,false,true,174016,Costume Institute,Evening dress,Evening dress,French,,,,,Designer|Design House,,Jeanne Lanvin|House of Lanvin,"French, 1867–1946|French, founded 1889",,"Lanvin, Jeanne|Lanvin, House of",French|French,1867 |1889,1946 |9999,winter 1930–31,1930,1931,Silk,,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of Mrs. George B. Wells, 1957",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/174016,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1979.90,false,true,110001,Costume Institute,Necktie,Necktie,French,,,,,Design House|Designer,,House of Lanvin|Jeanne Lanvin,"French, founded 1889|French, 1867–1946",,"Lanvin, House of|Lanvin, Jeanne",French|French,1889 |1867,9999 |1946,1974,1974,1974,silk,,"Gift of John Michael Powers, Jr., 1979",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/110001,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1984.54,false,true,109995,Costume Institute,Evening dress,Evening dress,French,,,,,Design House|Designer,Attributed to|Attributed to,House of Lanvin|Jeanne Lanvin,"French, founded 1889|French, 1867–1946",,"Lanvin, House of|Lanvin, Jeanne",French|French,1889 |1867,9999 |1946,ca. 1933,1928,1938,"silk, cotton",,"Gift of Gabriella de Balogh, 1984",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/109995,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.30.2,false,true,94721,Costume Institute,Robe de Style,Robe de Style,French,,,,,Design House|Designer,,House of Lanvin|Jeanne Lanvin,"French, founded 1889|French, 1867–1946",,"Lanvin, House of|Lanvin, Jeanne",French|French,1889 |1867,9999 |1946,fall/winter 1926–27,1926,1927,"silk, plastic, glass",,"Gift of Mrs. Gilbert W. Chapman, 1976",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/94721,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1978.29.3,false,true,94699,Costume Institute,Afternoon dress,Afternoon dress,French,,,,,Design House|Designer,,House of Lanvin|Jeanne Lanvin,"French, founded 1889|French, 1867–1946",,"Lanvin, House of|Lanvin, Jeanne",French|French,1889 |1867,9999 |1946,1928–29,1928,1929,silk,,"Gift of Mr. and Mrs. Charles Abrams, 1978",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/94699,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.303.2,false,true,109980,Costume Institute,Hat,Hat,French,,,,,Design House|Designer,,House of Lanvin|Jeanne Lanvin,"French, founded 1889|French, 1867–1946",,"Lanvin, House of|Lanvin, Jeanne",French|French,1889 |1867,9999 |1946,1914–20,1914,1920,[no medium available],,"Gift of Elizabeth A. Tilson, 1975",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/109980,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.303.3,false,true,83472,Costume Institute,Hat,Hat,French,,,,,Design House|Designer,,House of Lanvin|Jeanne Lanvin,"French, founded 1889|French, 1867–1946",,"Lanvin, House of|Lanvin, Jeanne",French|French,1889 |1867,9999 |1946,1918–25,1918,1925,"horsehair, silk, feathers",,"Gift of Elizabeth A. Tilson, 1975",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/83472,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1978.184.7,false,true,94692,Costume Institute,Robe de Style,Robe de Style,French,,,,,Designer|Designer,,House of Lanvin|Jeanne Lanvin,"French, founded 1889|French, 1867–1946",,"Lanvin, House of|Lanvin, Jeanne",French|French,1889 |1867,9999 |1946,1920–25,1920,1925,"silk, metallic thread, glass, plastic",,"Purchase, Marcia Sand Bequest, in memory of her daughter, Tiger (Joan) Morse, 1978",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/94692,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1978.184.8,false,true,94715,Costume Institute,Robe de Style,Robe de Style,French,,,,,Design House|Designer,,House of Lanvin|Jeanne Lanvin,"French, founded 1889|French, 1867–1946",,"Lanvin, House of|Lanvin, Jeanne",French|French,1889 |1867,9999 |1946,fall/winter 1926–27,1926,1927,silk,,"Purchase, Marcia Sand Bequest, in memory of her daughter, Tiger (Joan) Morse, 1978",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/94715,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1979.87.43,false,true,107363,Costume Institute,Hat,Hat,French,,,,,Design House|Designer,,House of Lanvin|Jeanne Lanvin,"French, founded 1889|French, 1867–1946",,"Lanvin, House of|Lanvin, Jeanne",French|French,1889 |1867,9999 |1946,1967–69,1967,1969,wool,,"Gift of Janet A. Sloane, 1979",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/107363,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1980.487.2,false,true,94722,Costume Institute,Robe de Style,Robe de Style,French,,,,,Design House|Designer,,House of Lanvin|Jeanne Lanvin,"French, founded 1889|French, 1867–1946",,"Lanvin, House of|Lanvin, Jeanne",French|French,1889 |1867,9999 |1946,fall/winter 1924–25,1924,1925,"silk, metallic thread",,"Gift of Mrs. Loretta Hines Howard, 1980",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/94722,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1985.365.1,false,true,109985,Costume Institute,Wedding Dress,Wedding dress,French,,,,,Design House|Designer,,House of Lanvin|Jeanne Lanvin,"French, founded 1889|French, 1867–1946",,"Lanvin, House of|Lanvin, Jeanne",French|French,1889 |1867,9999 |1946,fall/winter 1926–27,1926,1927,silk,,"Gift of Varney Thompson Elliott and Rosemary Thompson Franciscus, in memory of their mother, Margaret Whitney Thompson, 1985",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/109985,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1985.365.5,false,true,94720,Costume Institute,Dress,Dress,French,,,,,Design House|Designer,,House of Lanvin|Jeanne Lanvin,"French, founded 1889|French, 1867–1946",,"Lanvin, House of|Lanvin, Jeanne",French|French,1889 |1867,9999 |1946,1927,1927,1927,silk,,"Gift of Varney Thompson Elliott and Rosemary Thompson Franciscus, in memory of their mother, Margaret Whitney Thompson, 1985",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/94720,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.377.2,false,true,94727,Costume Institute,Evening dress,Evening dress,French,,,,,Design House|Designer,,House of Lanvin|Jeanne Lanvin,"French, founded 1889|French, 1867–1946",,"Lanvin, House of|Lanvin, Jeanne",French|French,1889 |1867,9999 |1946,ca. 1930,1925,1935,silk,,"Gift of Mrs. Jill L. Leinbach & Mr. James L. Long in memory of their mother, Mrs. Jane P. Long, 1986",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/94727,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.516.4,false,true,94703,Costume Institute,Dress,Dress,French,,,,,Design House|Designer,,House of Lanvin|Jeanne Lanvin,"French, founded 1889|French, 1867–1946",,"Lanvin, House of|Lanvin, Jeanne",French|French,1889 |1867,9999 |1946,1925–29,1925,1929,"silk, feathers",,"Gift of Miriam W. Coletti, 1986",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/94703,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.516.9,false,true,91818,Costume Institute,Ski trousers,Ski trousers,French,,,,,Design House|Designer,Attributed to,House of Lanvin|Jeanne Lanvin,"French, founded 1889|French, 1867–1946",,"Lanvin, House of|Lanvin, Jeanne",French|French,1889 |1867,9999 |1946,ca. 1925,1920,1930,"wool, silk",,"Gift of Miriam W. Coletti, 1986",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/91818,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -C.I.62.8.2,false,true,94718,Costume Institute,Robe de Style,Robe de Style,French,,,,,Design House|Designer,,House of Lanvin|Jeanne Lanvin,"French, founded 1889|French, 1867–1946",,"Lanvin, House of|Lanvin, Jeanne",French|French,1889 |1867,9999 |1946,1922,1922,1922,silk,,"Gift of Mrs. Stephen C. Clark, 1962",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/94718,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.516.10,false,true,109982,Costume Institute,Belt,Belt,French,,,,,Design House|Designer,Attributed to|Attributed to,House of Lanvin|Jeanne Lanvin,"French, founded 1889|French, 1867–1946",,"Lanvin, House of|Lanvin, Jeanne",French|French,1889 |1867,9999 |1946,1920,1920,1920,"leather, steel",,"Gift of Miriam W. Coletti, 1986",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/109982,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2015.95a, b",false,true,680139,Costume Institute,Dress,Dress,French,,,,,Design House|Designer,,House of Lanvin|Jeanne Lanvin,"French, founded 1889|French, 1867–1946",,"Lanvin, House of|Lanvin, Jeanne",French|French,1889 |1867,9999 |1946,1928,1928,1928,"silk, metal, glass",,"Purchase, Friends of The Costume Institute Gifts, 2015",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/680139,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -C.I.46.93.1,false,true,94725,Costume Institute,Evening dress,Evening dress,French,,,,,Design House|Designer,,House of Lanvin|Jeanne Lanvin,"French, founded 1889|French, 1867–1946",,"Lanvin, House of|Lanvin, Jeanne",French|French,1889 |1867,9999 |1946,fall/winter 1930–31,1930,1931,silk,,"Gift of Mrs. Charles Leibman, 1946",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/94725,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -C.I.50.84.1,false,true,94726,Costume Institute,Evening dress,Evening dress,French,,,,,Design House|Designer,,House of Lanvin|Jeanne Lanvin,"French, founded 1889|French, 1867–1946",,"Lanvin, House of|Lanvin, Jeanne",French|French,1889 |1867,9999 |1946,spring/summer 1930,1930,1930,silk,,"Gift of Miss Frances McFadden, 1950",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/94726,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -C.I.56.49.3,false,true,94719,Costume Institute,Robe de Style,Robe de Style,French,,,,,Design House|Designer,Attributed to|Attributed to,House of Lanvin|Jeanne Lanvin,"French, founded 1889|French, 1867–1946",,"Lanvin, House of|Lanvin, Jeanne",French|French,1889 |1867,9999 |1946,1925,1925,1925,"silk, beading",,"Gift of Mrs. W. R. Grace, 1956",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/94719,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -C.I.57.46.2,false,true,94702,Costume Institute,Dress,Dress,French,,,,,Design House|Designer,,House of Lanvin|Jeanne Lanvin,"French, founded 1889|French, 1867–1946",,"Lanvin, House of|Lanvin, Jeanne",French|French,1889 |1867,9999 |1946,ca. 1927,1922,1932,"silk, wool, metal",,"Gift of Mrs. Seaman Schepps, 1957",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/94702,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"1976.28.2a, b",false,true,94705,Costume Institute,Evening dress,Evening dress,French,,,,,Design House|Designer,,House of Lanvin|Jeanne Lanvin,"French, founded 1889|French, 1867–1946",,"Lanvin, House of|Lanvin, Jeanne",French|French,1889 |1867,9999 |1946,spring/summer 1927,1927,1927,"silk, metallic thread",,"Gift of Mrs. Herbert Bayer, 1976",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/94705,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"1976.30.1a, b",false,true,94723,Costume Institute,Robe de Style,Robe de Style,French,,,,,Design House|Designer,,House of Lanvin|Jeanne Lanvin,"French, founded 1889|French, 1867–1946",,"Lanvin, House of|Lanvin, Jeanne",French|French,1889 |1867,9999 |1946,fall/winter 1926–27,1926,1927,"silk, plastic, glass",,"Gift of Mrs. Gilbert W. Chapman, 1976",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/94723,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"1973.199.2a, b",false,true,94694,Costume Institute,Evening dress,Evening dress,French,,,,,Design House|Designer,,House of Lanvin|Jeanne Lanvin,"French, founded 1889|French, 1867–1946",,"Lanvin, House of|Lanvin, Jeanne",French|French,1889 |1867,9999 |1946,1949,1949,1949,silk,,"Gift of Mrs. Lawrence W. Snell, 1973",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/94694,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"C.I.62.8.1a, b",false,true,94717,Costume Institute,Robe de Style,Robe de Style,French,,,,,Design House|Designer,,House of Lanvin|Jeanne Lanvin,"French, founded 1889|French, 1867–1946",,"Lanvin, House of|Lanvin, Jeanne",French|French,1889 |1867,9999 |1946,1922,1922,1922,"silk, glass, metal",,"Gift of Mrs. Stephen C. Clark, 1962",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/94717,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"C.I.62.8.3a, b",false,true,94716,Costume Institute,Robe de Style,Robe de Style,French,,,,,Design House|Designer,,House of Lanvin|Jeanne Lanvin,"French, founded 1889|French, 1867–1946",,"Lanvin, House of|Lanvin, Jeanne",French|French,1889 |1867,9999 |1946,1922,1922,1922,"cotton, silk, glass, metal",,"Gift of Mrs. Stephen C. Clark, 1962",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/94716,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"C.I.62.8.4a, b",false,true,109983,Costume Institute,Robe de Style,Robe de Style,French,,,,,Design House|Designer,,House of Lanvin|Jeanne Lanvin,"French, founded 1889|French, 1867–1946",,"Lanvin, House of|Lanvin, Jeanne",French|French,1889 |1867,9999 |1946,1922,1922,1922,"cotton, metal, silk",,"Gift of Mrs. Stephen C. Clark, 1962",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/109983,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1977.201.9a–c,false,true,94724,Costume Institute,Robe de Style,Robe de Style,French,,,,,Design House|Designer,,House of Lanvin|Jeanne Lanvin,"French, founded 1889|French, 1867–1946",,"Lanvin, House of|Lanvin, Jeanne",French|French,1889 |1867,9999 |1946,1931,1931,1931,"silk, plastic",,"Gift of Esmé O'Brien Hammond, 1977",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/94724,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"1979.344.10a, b",false,true,94706,Costume Institute,Robe de Style,Robe de Style,French,,,,,Design House|Designer,,House of Lanvin|Jeanne Lanvin,"French, founded 1889|French, 1867–1946",,"Lanvin, House of|Lanvin, Jeanne",French|French,1889 |1867,9999 |1946,1922,1922,1922,"silk, glass, metallic thread",,"Gift of Mrs. Anthony Wilson, 1979",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/94706,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1979.59.13a–c,false,true,92035,Costume Institute,Suit,Suit,French,,,,,Design House|Designer,,House of Lanvin|Jeanne Lanvin,"French, founded 1889|French, 1867–1946",,"Lanvin, House of|Lanvin, Jeanne",French|French,1889 |1867,9999 |1946,1931,1931,1931,wool,,"Gift of Stanley F. Waldman, President, The Manhattan Galleries, Inc., 1979",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/92035,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"C.I.62.58.2a, b",false,true,94704,Costume Institute,Evening dress,Evening dress,French,,,,,Design House|Designer,,House of Lanvin|Jeanne Lanvin,"French, founded 1889|French, 1867–1946",,"Lanvin, House of|Lanvin, Jeanne",French|French,1889 |1867,9999 |1946,spring/summer 1923,1923,1923,"silk, metallic thread, glass beads",,"Gift of Mrs. Albert Spaulding, 1962",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/94704,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -C.I.58.34.2a–c,false,true,94707,Costume Institute,Afternoon dress,Afternoon dress,French,,,,,Design House|Designer,,House of Lanvin|Jeanne Lanvin,"French, founded 1889|French, 1867–1946",,"Lanvin, House of|Lanvin, Jeanne",French|French,1889 |1867,9999 |1946,1920,1920,1920,silk,,"Gift of Mrs. John Chambers Hughes, 1958",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/94707,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -C.I.61.40.1a–c,false,true,109986,Costume Institute,Robe de Style,Robe de Style,French,,,,,Design House|Designer,,House of Lanvin|Jeanne Lanvin,"French, founded 1889|French, 1867–1946",,"Lanvin, House of|Lanvin, Jeanne",French|French,1889 |1867,9999 |1946,ca. 1927,1922,1932,"silk, metal",,"Gift of Mrs. David J. Colton, 1961",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/109986,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2001.341.1,false,true,82615,Costume Institute,Engraving,The Vanity of Women: Masks and Bustles,Dutch,,,,,Artist,Attributed to,Maerten de Vos,"Netherlandish, Antwerp 1532–1603 Antwerp",,"Vos, Maerten de",Netherlandish,1532,1603,ca. 1600,1595,1605,engraved paper,,"Purchase, Irene Lewisohn Trust Gift, 2001",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/82615,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2001.341.2,false,true,82616,Costume Institute,Engraving,The Pride of Women: Ruffs,Dutch,,,,,Artist,,Maerten de Vos,"Netherlandish, Antwerp 1532–1603 Antwerp",,"Vos, Maerten de",Netherlandish,1532,1603,ca. 1600,1595,1605,engraved paper,,"Purchase, Irene Lewisohn Trust Gift, 2001",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/82616,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.300.3102a–c,false,true,158003,Costume Institute,Corset,Corset,American,,,,,Department Store|Manufacturer,,Day & Horton|Worcester Skirt Company,"American|American, 1864–1950",,Day & Horton|Worcester Skirt Company,American|American,1864,1950,1866–67,1866,1867,"cotton, metal, bone",,"Brooklyn Museum Costume Collection at The Metropolitan Museum of Art, Gift of the Brooklyn Museum, 2009; Gift of E. A. Meister, 1950",,,,,,,,,,,,,,http://www.metmuseum.org/art/collection/search/158003,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -17.134.8,false,true,435608,European Paintings,"Painting, miniature",Portrait of a Man,,,,,,Artist,,Barrois,ca. 1790,,Barrois,French(?),1790,1790,ca. 1790,1785,1795,Ivory,Diameter 2 3/4 in. (67 mm),"Bequest of Ella Church Strobell, 1917",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/435608,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -95.14.72,false,true,437485,European Paintings,"Painting, miniature",Portrait of a Girl,,,,,,Artist,,Sampson Towgood Roch,"Irish, 1759–1847",,"Roch, Sampson Towgood",Irish,1759,1847,ca. 1790,1785,1795,Ivory,"Oval, 2 1/4 x 1 7/8 in. (58 x 48 mm)","The Moses Lazarus Collection, Gift of Josephine and Sarah Lazarus, in memory of their father, 1888–95",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/437485,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -15.43.291,false,true,437141,European Paintings,"Painting, miniature",Portrait of a Man,,,,,,Artist,,Monogrammist IS,"Flemish, dated 1621",,Monogrammist IS,Flemish,1621,1621,1621,1621,1621,Oil on copper,"Oval, 4 1/8 x 3 5/8 in. (105 x 91 mm)","Bequest of Mary Anna Palmer Draper, 1914",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/437141,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.145.10,false,true,437783,European Paintings,"Painting, miniature",Portrait of a Woman,,,,,,Artist,,Mikail Ivanovich Terebenev,"Russian, 1795–1866",,"Terebenev, Mikail Ivanovich",Russian,1795,1866,ca. 1830,1825,1835,Ivory,"Oval, 2 7/8 x 2 1/4 in. (72 x 57 mm)","Gift of Humanities Fund Inc., 1972",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/437783,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -95.14.53,false,true,436610,European Paintings,"Painting, miniature",The Painter Louis Joseph Maurice (1730–1820),,,,,,Artist,,Peter Adolf Hall,"Swedish, 1739–1793",,"Hall, Peter Adolf",Swedish,1739,1793,1772,1772,1772,Ivory,"Oval, 3 3/8 x 2 5/8 in. (84 x 67 mm)","The Moses Lazarus Collection, Gift of Josephine and Sarah Lazarus, in memory of their father, 1888–95",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/436610,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -26.168.45,false,true,436612,European Paintings,"Painting, miniature",Portrait of a Young Woman,,,,,,Artist,,Peter Adolf Hall,"Swedish, 1739–1793",,"Hall, Peter Adolf",Swedish,1739,1793,ca. 1790,1785,1795,Ivory,"Oval, 2 3/4 x 2 1/8 in. (70 x 55 mm)","Bequest of Collis P. Huntington, 1900",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/436612,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -40.186.1,false,true,435685,European Paintings,"Painting, miniature",A Man with the Initials AC,,,,,,Artist,,John Bogle,"British, 1746?–1803",,"Bogle, John","British, Scottish",1746,1803,1774,1774,1774,Ivory,"Oval, 1 3/8 x 1 1/8 in. (36 x 30 mm)","Gift of Mrs. S. M. Breckenridge Long, 1940",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/435685,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -40.186.2,false,true,435686,European Paintings,"Painting, miniature",A Woman with the Initials MCC,,,,,,Artist,,John Bogle,"British, 1746?–1803",,"Bogle, John","British, Scottish",1746,1803,1773,1773,1773,Ivory,"Oval, 1 5/8 x 1 3/8 in. (41 x 34 mm)","Gift of Mrs. S. M. Breckenridge Long, 1940",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/435686,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -62.122.61,false,true,437484,European Paintings,"Painting, miniature",Portrait of a Man,,,,,,Artist,,Charles Robertson,"Irish, ca. 1760–1821",,"Robertson, Charles",Irish,1760,1821,1810,1810,1810,Ivory,"Oval, 2 1/2 x 2 in. (61 x 50 mm)","Bequest of Millie Bruhl Fredrick, 1962",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/437484,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.136.13,false,true,436865,European Paintings,"Painting, miniature",Vertumnus and Pomona,,,,,,Artist,,Thomas Lefebure,"Flemish, ca. 1636–1720",,"Lefebure, Thomas",Flemish,1636,1720,1676,1676,1676,Paper,"Image exclusive of gold rim and dark brown border, 6 3/4 x 5 in. (172 x 126 mm)","Bequest of Margaret Crane Hurlbut, 1933",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/436865,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -38.50.333,false,true,437142,European Paintings,"Painting, miniature",Traveling Players,,,,,,Artist,,Monogrammist JG,"Northern European, ca. 1630",,Monogrammist JG,Northern European,1630,1630,ca. 1630,1625,1635,Vellum laid on wood,2 3/4 x 3 1/2 in. (70 x 90 mm),"Bequest of Kate Read Blacque, in memory of her husband, Valentine Alexander Blacque, 1937",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/437142,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -48.187.740,false,true,437139,European Paintings,"Painting, miniature",An Artist Painting a Heraldic Shield in a Cabinet of Curiosities,,,,,,Artist,,Monogrammist FA,"Northern European, dated 1664",,Monogrammist FA,Northern European,1664,1664,1664,1664,1664,Vellum laid on card,4 1/4 x 6 in. (106 x 152 mm),"Bequest of Catherine D. Wentworth, 1948",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/437139,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -95.14.81,false,true,435821,European Paintings,"Painting, miniature","Portrait of a Woman, Said to Be Emma (1765–1815), Lady Hamilton",,,,,,Artist,,Adam Buck,"Irish, Cork 1759–1833 London",,"Buck, Adam",Irish,1759,1833,1804,1804,1804,Ivory,Diameter 1 1/4 in. (32 mm),"The Moses Lazarus Collection, Gift of Josephine and Sarah Lazarus, in memory of their father, 1888–95",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/435821,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -25.106.27,false,true,436844,European Paintings,"Painting, miniature",Portrait of a Man,,,,,,Artist,,Jean-Baptiste Ponce Lambert,"Swiss (?), active ca. 1801–12",,"Lambert, Jean-Baptiste Ponce",Swiss,1801,1812,1801,1801,1801,Ivory,Diameter 2 3/8 in. (59 mm),"Gift of Mrs. Louis V. Bell, in memory of her husband, 1925",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/436844,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -62.122.27,false,true,436674,European Paintings,"Painting, miniature",Portrait of a Man,,,,,,Artist,Attributed to,Nathaniel Hone,"Irish, Dublin 1718–1784 London",,"Hone, Nathaniel",Irish,1718,1784,ca. 1760,1755,1765,Enamel,"Oval, 1 1/8 x 1 in. (30 x 25 mm)","Bequest of Millie Bruhl Fredrick, 1962",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/436674,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -25.106.1,false,true,435636,European Paintings,"Painting, miniature",Portrait of a Woman,,,,,,Artist,,Rudolphe Bel,"Swiss, active by 1822–died 1849",,"Bel, Rudolphe",Swiss,1822,1849,1822,1822,1822,Paper stretched over metal,"Oval, 5 x 3 1/2 in. (126 x 88 mm)","Gift of Mrs. Louis V. Bell, in memory of her husband, 1925",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/435636,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -49.7.25,false,true,436258,European Paintings,Painting,Self-Portrait,,,,,,Artist,,Anthony van Dyck,"Flemish, Antwerp 1599–1641 London",,"Dyck, Anthony van",Flemish,1599,1641,ca. 1620–21,1620,1621,Oil on canvas,47 1/8 x 34 5/8 in. (119.7 x 87.9 cm),"The Jules Bache Collection, 1949",,,,,,,,,,,,Painted Canvases,,http://www.metmuseum.org/art/collection/search/436258,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -57.37,false,true,436259,European Paintings,"Painting, drawing",Study Head of a Young Woman,,,,,,Artist,,Anthony van Dyck,"Flemish, Antwerp 1599–1641 London",,"Dyck, Anthony van",Flemish,1599,1641,ca. 1618–20,1618,1620,"Oil on paper, laid down on wood",22 1/4 x 16 3/8 in. (56.5 x 41.6 cm),"Gift of Mrs. Ralph J. Hines, 1957",,,,,,,,,,,,Pastels & Oil Sketches on Paper,,http://www.metmuseum.org/art/collection/search/436259,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -45.110.4,false,true,436059,European Paintings,"Painting, miniature",Princess María Francisca de Asis de Borbón and Her Son Infante Carlos Luis María Fernando de Borbón,,,,,,Artist,,Luis de la Cruz y Rios,"Spanish, active by 1815–died 1850",,"Cruz y Rios, Luis de la",Spanish,1815,1850,1818,1818,1818,Ivory,Obverse and reverse each 2 1/4 x 1 3/4 in. (57 x 47 mm),"Gift of Mrs. John LaPorte Given, 1945",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/436059,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -25.106.6,false,true,437614,European Paintings,"Painting, miniature",Portrait of a Young Woman,,,,,,Artist,Attributed to,Piat Joseph Sauvage,"Flemish, Tournai 1744–1818 Tournai",,"Sauvage, Piat Joseph",Flemish,1744,1818,ca. 1790–95,1790,1795,Ivory,Diameter 2 1/8 in. (54 mm),"Gift of Mrs. Louis V. Bell, in memory of her husband, 1925",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/437614,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.390,false,true,437482,European Paintings,"Painting, miniature",Portrait of a Man,,,,,,Artist,,Andrew Robertson,"British, Aberdeen, Scotland 1777–1845 London",,"Robertson, Andrew","British, Scottish",1777,1845,1828,1828,1828,Ivory,"Oval, 3 3/4 x 2 7/8 in. (80 x 63 mm)","Morris K. Jesup Fund, 1986",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/437482,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -24.21,false,true,437483,European Paintings,"Painting, miniature",Sir Joshua Reynolds (1723–1792),,,,,,Artist,,Archibald Robertson,"American, Moneymusk, Scotland 1765–1835 New York",,"Robertson, Archibald","American, Scottish",1765,1835,1786–91,1786,1791,Ivory,"Oval, 3 x 2 3/8 in. (75 x 60 mm)","Bequest of Geraldine Winslow Goddard, 1923",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/437483,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -45.110.3,false,true,436289,European Paintings,"Painting, miniature",Self-Portrait,,,,,,Artist,Attributed to,John Faed,"British, Burley Mill, Scotland 1820–1902 Burley Mill, Scotland",,"Faed, John","British, Scottish",1820,1902,ca. 1850,1845,1855,Ivory,"Oval, 2 5/8 x 2 1/8 in. (68 x 55 mm)","Gift of Mrs. John LaPorte Given, 1945",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/436289,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -61.101.8,false,true,436577,European Paintings,Painting,Saint Andrew,,,,,,Artist,Workshop of,El Greco,"Spanish, ca. 1610",,Greco El,Greek,1540,1614,ca. 1610,1605,1615,Oil on canvas,43 1/4 x 25 1/4 in. (109.9 x 64.1 cm),"Bequest of Stephen C. Clark, 1960",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436577,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1971.115.2,false,true,436816,European Paintings,Painting,The Lacemaker,,,,,,Artist,,Bernhard Keil,"Danish, 1624–1687",,"Keil, Bernhard",Danish,1624,1687,ca. 1665,1660,1670,Oil on canvas,28 1/4 x 38 1/4 in. (71.8 x 97.2 cm),"Bequest of Edward Fowles, 1971",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436816,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -64.294,false,true,436698,European Paintings,Painting,Still Life: A Basket of Grapes and Other Fruit,,,,,,Artist,,Jacob van Hulsdonck,"Flemish, 1582–1647",,"Hulsdonck, Jacob van",Flemish,1582,1647,probably ca. 1635–45,1635,1645,Oil on wood,19 5/8 x 25 1/2 in. (49.8 x 64.8 cm),"The Alfred N. Punnett Endowment Fund, 1964",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436698,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -45.128.14,false,true,436049,European Paintings,Painting,Philip IV (1605–1665) in Parade Armor,,,,,,Artist,,Gaspar de Crayer,"Flemish, 1584–1669",,"Crayer, Gaspar de",Flemish,1584,1669,ca. 1628,1623,1633,Oil on canvas,72 x 46 1/2 in. (182.9 x 118.1 cm),"Bequest of Helen Hay Whitney, 1944",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436049,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -71.45,false,true,436427,European Paintings,Painting,A Partridge and Small Game Birds,,,,,,Artist,,Jan Fyt,"Flemish, 1611–1661",,"Fyt, Jan",Flemish,1611,1661,1650s,1650,1659,Oil on canvas,18 1/4 x 14 1/4 in. (46.4 x 36.2 cm),"Purchase, 1871",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436427,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -81.1.652,false,true,437867,European Paintings,Painting,A Bouquet of Flowers in a Crystal Vase,,,,,,Artist,,Nicolaes van Veerendael,"Flemish, 1640–1691",,"Veerendael, Nicolaes van",Flemish,1640,1691,1662,1662,1662,Oil on canvas,19 1/2 x 15 7/8 in. (49.5 x 40.3 cm),"Bequest of Stephen Whitney Phoenix, 1881",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437867,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -32.75.4,false,true,436693,European Paintings,Painting,A Musical Gathering at the Court of the Elector Karl Albrecht of Bavaria,,,,,,Artist,,Peter Jacob Horemans,"Flemish, 1700–1776",,"Horemans, Peter Jacob",Flemish,1700,1776,1730,1730,1730,Oil on canvas,34 1/2 x 42 in. (87.6 x 106.7 cm),"The Collection of Giovanni P. Morosini, presented by his daughter Giulia, 1932",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436693,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -48.187.737,false,true,436290,European Paintings,Painting,Flowers by a Stone Vase,,,,,,Artist,,Peter Faes,"Flemish, 1750–1814",,"Faes, Peter",Flemish,1750,1814,1786,1786,1786,Oil on wood,20 x 14 7/8 in. (50.8 x 37.8 cm),"Bequest of Catherine D. Wentworth, 1948",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436290,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -48.187.738,false,true,436291,European Paintings,Painting,Flowers in a Stone Vase,,,,,,Artist,,Peter Faes,"Flemish, 1750–1814",,"Faes, Peter",Flemish,1750,1814,1786,1786,1786,Oil on wood,19 1/2 x 15 1/8 in. (49.5 x 38.4 cm),"Bequest of Catherine D. Wentworth, 1948",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436291,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -50.50,false,true,437177,European Paintings,Painting,"Count Giacomo Durazzo (1717–1794) in the Guise of a Huntsman with His Wife (Ernestine Aloisia Ungnad von Weissenwolff, 1732–1794)",,,,,,Artist,,Martin van Meytens the Younger,"Swedish, 1695–1770",,"Meytens, Martin van, the Younger",Swedish,1695,1770,probably early 1760s,1760,1763,Oil on canvas,90 1/8 x 75 in. (228.9 x 190.5 cm),"Gift of Mr. and Mrs. Nate B. Spingold, 1950",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437177,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1971.101,false,true,436776,European Paintings,Painting,The Dead Christ in the Tomb with Two Angels,,,,,,Artist,,Abraham Janssen van Nuyssen,"Flemish, ca. 1575–1632",,"Janssen van Nuyssen, Abraham",Flemish,1575,1632,ca. 1610,1605,1615,Oil on canvas,45 3/8 x 58 in. (115.3 x 147.3 cm),"Gift of James Belden, in memory of Evelyn Berry Belden, 1971",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436776,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -89.15.17,false,true,437729,European Paintings,Painting,Saint Michael the Archangel,,,,,,Artist,,Ignacio de Ries,"Spanish, 1616–after 1665",,"Ries, Ignacio de",Spanish,1616,1665,1640s,1640,1649,Oil on canvas,64 3/4 x 43 1/4 in. (164.5 x 109.9 cm),"Marquand Collection, Gift of Henry G. Marquand, 1889",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437729,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -59.189.2,false,true,436808,European Paintings,Painting,"Edward Smith Stanley (1752–1834), Twelfth Earl of Derby, with His First Wife (Lady Elizabeth Hamilton, 1753–1797) and Their Son (Edward Smith Stanley, 1775–1851)",,,,,,Artist,,Angelica Kauffmann,"Swiss, Chur 1741–1807 Rome",,"Kauffmann, Angelica",Swiss,1741,1807,ca. 1776,1771,1781,Oil on canvas,50 x 40 in. (127 x 101.6 cm),"Gift of Bernard M. Baruch, in memory of his wife, Annie Griffen Baruch, 1959",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436808,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -25.110.187,false,true,436809,European Paintings,Painting,The Sorrow of Telemachus,,,,,,Artist,,Angelica Kauffmann,"Swiss, Chur 1741–1807 Rome",,"Kauffmann, Angelica",Swiss,1741,1807,1783,1783,1783,Oil on canvas,32 3/4 x 45 in. (83.2 x 114.3 cm),"Bequest of Collis P. Huntington, 1900",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436809,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -25.110.188,false,true,436810,European Paintings,Painting,Telemachus and the Nymphs of Calypso,,,,,,Artist,,Angelica Kauffmann,"Swiss, Chur 1741–1807 Rome",,"Kauffmann, Angelica",Swiss,1741,1807,1782,1782,1782,Oil on canvas,32 1/2 x 44 1/4 in. (82.6 x 112.4 cm),"Bequest of Collis P. Huntington, 1900",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436810,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -22.45.10,false,true,437238,European Paintings,Painting,Still Life of Fruit and Game,,,,,,Artist,,Pieter van Overschee,"Flemish, active ca. 1645–61",,"Overschee, Pieter van",Flemish,1645,1661,1645,1645,1645,Oil on wood,32 7/8 x 46 3/4 in. (83.5 x 118.7 cm),"Bequest of Grace Wilkes, 1921",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437238,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -89.22,false,true,436318,European Paintings,Painting,Madame Gaye,,,,,,Artist,,Mariano Fortuny Marsal,"Spanish, Reus 1838–1874 Rome",,"Fortuny Marsal, Mariano",Spanish,1838,1874,1865,1865,1865,Oil on canvas,54 x 39 1/2 in. (137.2 x 100.3 cm),"Gift of Alfred Corning Clark, 1889",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436318,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.1134,false,true,634108,European Paintings,Painting,The Dream of the Shepherd (Der Traum des Hirten),,,,,,Artist,,Ferdinand Hodler,"Swiss, Bern 1853–1918 Geneva",,"Hodler, Ferdinand",Swiss,1853,1918,1896,1896,1896,Oil on canvas,98 1/2 × 51 3/8 in. (250.2 × 130.5 cm),"Purchase, European Paintings Funds, Lila Acheson Wallace Gift, Catharine Lorillard Wolfe Collection, Wolfe Fund, Charles and Jessie Price Gift, funds from various donors, and Bequests of Collis P. Huntington and Isaac D. Fletcher, by exchange, 2013",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/634108,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.145.6,false,true,437198,European Paintings,Painting,The Nightingale Sings,,,,,,Artist,,Mikhail Vasilievich Nesterov,"Russian, Ufa 1862–1942 Moscow",,"Nesterov, Mikhail Vasilievich",Russian,1862,1942,1923,1923,1923,Oil on canvas,31 7/8 x 27 3/8 in. (81 x 69.5 cm),"Gift of Humanities Fund Inc., 1972",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437198,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -06.1198,false,true,436872,European Paintings,Painting,Study for a Portrait of a Woman,,,,,,Artist,,Sir Peter Lely (Pieter van der Faes),"British, Soest 1618–1680 London",,"Lely, Peter, Sir (Pieter van der Faes)","Dutch, British",1618,1680,1670s,1670,1679,Oil on canvas,26 1/2 x 21 1/8 in. (67.3 x 53.7 cm),"Rogers Fund, 1906",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436872,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2001.613,false,true,438407,European Paintings,Painting,Man Holding a Jug,,,,,,Artist,,Michiel Sweerts,"Flemish, Brussels 1618–1664 Goa",,"Sweerts, Michiel",Flemish,1618,1664,ca. 1660,1655,1665,Oil on canvas,19 3/8 x 15 3/8 in. (49.2 x 39.1 cm),"Gift of Herman and Lila Shickman, 2001",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/438407,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1984.459.1,true,true,437769,European Paintings,Painting,Clothing the Naked,,,,,,Artist,,Michiel Sweerts,"Flemish, Brussels 1618–1664 Goa",,"Sweerts, Michiel",Flemish,1618,1664,ca. 1661,1656,1666,Oil on canvas,32 1/4 x 45 in. (81.9 x 114.3 cm),"Gift of Mr. and Mrs. Charles Wrightsman, 1984",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437769,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1980.411,false,true,436423,European Paintings,Painting,The Night-Hag Visiting Lapland Witches,,,,,,Artist,,Henry Fuseli,"Swiss, Zürich 1741–1825 London",,"Fuseli, Henry",Swiss,1741,1825,1796,1796,1796,Oil on canvas,40 x 49 3/4 in. (101.6 x 126.4 cm),"Purchase, Bequest of Lillian S. Timken, by exchange, and Victor Wilbour Memorial, The Alfred N. Punnett Endowment, Marquand and Charles B. Curtis Funds, 1980",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436423,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -93.29,false,true,437084,European Paintings,Painting,Landscape with a Battle between Two Rams,,,,,,Artist,,Jan Miel,"Flemish, Beveren 1599–1664 Turin",,"Miel, Jan",Flemish,1599,1664,ca. 1640,1635,1645,Oil on canvas,68 1/4 x 97 5/8 in. (173.4 x 248 cm),"Gift of Princess Brancaccio, 1893",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437084,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -15.30.48,false,true,437662,European Paintings,Painting,"William Archer Shee (1810–1899), the Artist's Son",,,,,,Artist,,Sir Martin Archer Shee,"Irish, Dublin 1769–1850 Brighton",,"Shee, Martin Archer, Sir",Irish,1769,1850,ca. 1820,1815,1825,Oil on canvas,30 x 24 3/4 in. (76.2 x 62.9 cm),"Bequest of Maria DeWitt Jesup, from the collection of her husband, Morris K. Jesup, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437662,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.145.4,false,true,437631,European Paintings,Painting,Night Scene on the Volga,,,,,,Artist,,Alexei Kondratievich Savrasov,"Russian, Moscow 1830–1897 Moscow",,"Savrasov, Alexei Kondratievich",Russian,1830,1897,1871,1871,1871,Oil on wood,12 7/8 x 21 1/2 in. (32.7 x 54.6 cm),"Gift of Humanities Fund Inc., 1972",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437631,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -87.15.57,false,true,437460,European Paintings,Painting,A Canal in Venice,,,,,,Artist,,Martín Rico y Ortega,"Spanish, Madrid 1833–1908 Venice",,"Rico y Ortega, Martín",Spanish,1833,1908,ca. 1875,1875,1875,Oil on canvas,19 3/4 x 26 3/4 in. (50.2 x 67.9 cm),"Catharine Lorillard Wolfe Collection, Bequest of Catharine Lorillard Wolfe, 1887",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437460,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1982.60.39,false,true,437053,European Paintings,Painting,The Afternoon Meal (La Merienda),,,,,,Artist,,Luis Meléndez,"Spanish, Naples 1716–1780 Madrid",,"Meléndez, Luis",Spanish,1716,1780,ca. 1772,1772,1772,Oil on canvas,41 1/2 x 60 1/2 in. (105.4 x 153.7 cm),"The Jack and Belle Linsky Collection, 1982",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437053,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -83.11,false,true,436879,European Paintings,Painting,"Auction Sale in Clinton Hall, New York, 1876",,,,,,Artist,,Ignacio de León y Escosura,"Spanish, Oviedo 1834–1901 Toledo",,"León y Escosura, Ignacio de",Spanish,1834,1901,1876,1876,1876,Oil on canvas,22 3/8 x 31 5/8 in. (56.8 x 80.3 cm),"Gift of the artist, 1883",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436879,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -44.147.1,false,true,437004,European Paintings,"Painting, part of an altarpiece",Saint Adalbert and Saint Procopius,,,,,,Artist,,Master of Eggenburg,"Austrian, Tirol, active 1490–1500",,Master of Eggenburg,Austrian,1490,1500,ca. 1490–1500,1490,1500,"Oil on spruce, gold ground","Painted surface, including black border, 27 1/8 x 17 in. (68.9 x 43.2 cm)","Gift of William Rosenwald, 1944",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437004,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -44.147.2,false,true,437005,European Paintings,"Painting, part of an altarpiece",The Burial of Saint Wenceslas,,,,,,Artist,,Master of Eggenburg,"Austrian, Tirol, active 1490–1500",,Master of Eggenburg,Austrian,1490,1500,ca. 1490–1500,1490,1500,Oil and gold on spruce,"Painted surface, including black border, 27 1/8 x 17 in. (68.9 x 43.2 cm)","Gift of William Rosenwald, 1944",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437005,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -46.150.1,false,true,437754,European Paintings,Painting,After the Ball,,,,,,Artist,,Alfred Stevens,"Belgian, Brussels 1823–1906 Paris",,"Stevens, Alfred",Belgian,1823,1906,1874,1874,1874,Oil on canvas,37 3/4 x 27 1/8 in. (95.9 x 68.9 cm),"Gift of Estate of Marie L. Russell, 1946",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437754,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -87.15.56,false,true,437756,European Paintings,Painting,The Japanese Robe,,,,,,Artist,,Alfred Stevens,"Belgian, Brussels 1823–1906 Paris",,"Stevens, Alfred",Belgian,1823,1906,ca. 1872,1868,1877,Oil on canvas,36 1/2 x 25 1/8 in. (92.7 x 63.8 cm),"Catharine Lorillard Wolfe Collection, Bequest of Catharine Lorillard Wolfe, 1887",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437756,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -20.33,false,true,437169,European Paintings,Painting,Maude Adams (1872–1953) as Joan of Arc,,,,,,Artist,,Alphonse Mucha,"Czech, Ivančice 1860–1939 Prague",,"Mucha, Alphonse",Czech,1860,1939,1909,1909,1909,Oil on canvas,82 1/4 x 30 in. (208.9 x 76.2 cm),"Gift of A. J. Kobler, 1920",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437169,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -71.41,false,true,436257,European Paintings,Painting,Saint Rosalie Interceding for the Plague-stricken of Palermo,,,,,,Artist,,Anthony van Dyck,"Flemish, Antwerp 1599–1641 London",,"Dyck, Anthony van",Flemish,1599,1641,1624,1624,1624,Oil on canvas,39 1/4 x 29 in. (99.7 x 73.7 cm),"Purchase, 1871",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436257,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -22.221,false,true,436260,European Paintings,Painting,Study Head of an Old Man with a White Beard,,,,,,Artist,,Anthony van Dyck,"Flemish, Antwerp 1599–1641 London",,"Dyck, Anthony van",Flemish,1599,1641,ca. 1617–20,1617,1620,Oil on wood,26 x 20 1/4 in. (66 x 51.4 cm),"Egleston Fund, 1922",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436260,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -49.7.26,false,true,436256,European Paintings,Painting,"Robert Rich (1587–1658), Second Earl of Warwick",,,,,,Artist,,Anthony van Dyck,"Flemish, Antwerp 1599–1641 London",,"Dyck, Anthony van",Flemish,1599,1641,ca. 1632–35,1632,1635,Oil on canvas,"81 7/8 x 50 3/8 in. (208 x 128 cm), with added strip of 2 1/8 in. (5.4 cm) at top","The Jules Bache Collection, 1949",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436256,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -51.33.1,false,true,436261,European Paintings,Painting,Virgin and Child,,,,,,Artist,,Anthony van Dyck,"Flemish, Antwerp 1599–1641 London",,"Dyck, Anthony van",Flemish,1599,1641,ca. 1620,1615,1625,Oil on wood,25 1/4 x 19 1/2 in. (64.1 x 49.5 cm),"Fletcher Fund, 1951",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436261,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -60.71.5,false,true,436262,European Paintings,Painting,Virgin and Child with Saint Catherine of Alexandria,,,,,,Artist,,Anthony van Dyck,"Flemish, Antwerp 1599–1641 London",,"Dyck, Anthony van",Flemish,1599,1641,ca. 1630,1625,1635,Oil on canvas,43 x 35 3/4 in. (109.2 x 90.8 cm); with added strips 44 1/8 x 37 in. (112.1 x 94 cm),"Bequest of Lillian S. Timken, 1959",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436262,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -49.145.1,false,true,436264,European Paintings,"Painting, monochrome",A Man Riding a Horse,,,,,,Artist,Attributed to,Anthony van Dyck,"Flemish, Antwerp 1599–1641 London",,"Dyck, Anthony van",Flemish,1599,1641,ca. 1630,1625,1635,Oil on wood,10 1/8 x 8 7/8 in. (25.7 x 22.5 cm),"Gift of Mr. and Mrs. Siegfried Bieber, 1949",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436264,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -49.145.2,false,true,436263,European Paintings,"Painting, monochrome",A Man Mounting a Horse,,,,,,Artist,Attributed to,Anthony van Dyck,"Flemish, Antwerp 1599–1641 London",,"Dyck, Anthony van",Flemish,1599,1641,ca. 1630,1625,1635,Oil on wood,10 x 8 3/4 in. (25.4 x 22.2 cm),"Gift of Mr. and Mrs. Siegfried Bieber, 1949",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436263,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -89.15.11,false,true,436254,European Paintings,Painting,Portrait of a Man,,,,,,Artist,,Anthony van Dyck,"Flemish, Antwerp 1599–1641 London",,"Dyck, Anthony van",Flemish,1599,1641,ca. 1618,1613,1623,Oil on wood,41 3/4 x 28 5/8 in. (106 x 72.7 cm),"Marquand Collection, Gift of Henry G. Marquand, 1889",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436254,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -89.15.16,false,true,436252,European Paintings,Painting,"James Stuart (1612–1655), Duke of Richmond and Lennox",,,,,,Artist,,Anthony van Dyck,"Flemish, Antwerp 1599–1641 London",,"Dyck, Anthony van",Flemish,1599,1641,ca. 1633–35,1633,1635,Oil on canvas,85 x 50 1/4 in. (215.9 x 127.6 cm),"Marquand Collection, Gift of Henry G. Marquand, 1889",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436252,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.40.615,false,true,436255,European Paintings,Painting,"Portrait of a Woman, Called the Marchesa Durazzo",,,,,,Artist,,Anthony van Dyck,"Flemish, Antwerp 1599–1641 London",,"Dyck, Anthony van",Flemish,1599,1641,probably ca. 1622–25,1622,1625,Oil on canvas,44 5/8 x 37 3/4 in. (113.3 x 95.9 cm),"Bequest of Benjamin Altman, 1913",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436255,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.40.619,true,true,436253,European Paintings,Painting,Lucas van Uffel (died 1637),,,,,,Artist,,Anthony van Dyck,"Flemish, Antwerp 1599–1641 London",,"Dyck, Anthony van",Flemish,1599,1641,ca. 1622,1621,1627,Oil on canvas,49 x 39 5/8 in. (124.5 x 100.6 cm),"Bequest of Benjamin Altman, 1913",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436253,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2003.42.20,false,true,438644,European Paintings,Painting,"View on the Quirinal Hill, Rome",,,,,,Artist,,Simon Denis,"Flemish, Antwerp 1755–1813 Naples",,"Denis, Simon",Flemish,1755,1813,1800,1800,1800,"Oil on paper, laid down on canvas",11 5/8 x 16 1/8 in. (29.5 x 41 cm),"The Whitney Collection, Gift of Wheelock Whitney III, and Purchase, Gift of Mr. and Mrs. Charles S. McVeigh, by exchange, 2003",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/438644,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.400.40,false,true,439365,European Paintings,Painting,Aniene River at Tivoli,,,,,,Artist,,Simon Denis,"Flemish, Antwerp 1755–1813 Naples",,"Denis, Simon",Flemish,1755,1813,ca. 1786–89,1786,1806,Oil on paper,12 3/4 x 11 1/2 in. (32.4 x 29.2 cm),"Thaw Collection, Jointly Owned by The Metropolitan Museum of Art and The Morgan Library & Museum, Gift of Eugene V. Thaw, 2009",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/439365,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -02.24,false,true,437528,European Paintings,Painting,The Holy Family with Saints Francis and Anne and the Infant Saint John the Baptist,,,,,,Artist,,Peter Paul Rubens,"Flemish, Siegen 1577–1640 Antwerp",,"Rubens, Peter Paul",Flemish,1577,1640,early or mid-1630s,1630,1636,Oil on canvas,69 1/2 x 82 1/2 in. (176.5 x 209.6 cm),"Gift of James Henry Smith, 1902",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437528,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -10.73,false,true,437536,European Paintings,Painting,Wolf and Fox Hunt,,,,,,Artist,,Peter Paul Rubens,"Flemish, Siegen 1577–1640 Antwerp",and Workshop,"Rubens, Peter Paul",Flemish,1577,1640,ca. 1616,1611,1621,Oil on canvas,96 5/8 x 148 1/8 in. (245.4 x 376.2 cm),"John Stewart Kennedy Fund, 1910",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437536,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -44.22,false,true,437523,European Paintings,Painting,Atalanta and Meleager,,,,,,Artist,,Peter Paul Rubens,"Flemish, Siegen 1577–1640 Antwerp",,"Rubens, Peter Paul",Flemish,1577,1640,ca. 1616,1611,1621,Oil on wood,52 1/2 x 42 in. (133.4 x 106.7 cm),"Fletcher Fund, 1944",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437523,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.162,false,true,437535,European Paintings,Painting,Venus and Adonis,,,,,,Artist,,Peter Paul Rubens,"Flemish, Siegen 1577–1640 Antwerp",,"Rubens, Peter Paul",Flemish,1577,1640,probably mid-1630s,1634,1636,Oil on canvas,"With added strips, 77 3/4 x 95 5/8 in. (197.5 x 242.9 cm)","Gift of Harry Payne Bingham, 1937",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437535,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -42.187,false,true,437534,European Paintings,"Painting, sketch",The Triumph of Henry IV,,,,,,Artist,,Peter Paul Rubens,"Flemish, Siegen 1577–1640 Antwerp",,"Rubens, Peter Paul",Flemish,1577,1640,ca. 1630,1625,1635,Oil on wood,19 1/2 x 32 7/8 in. (49.5 x 83.5 cm),"Rogers Fund, 1942",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437534,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.218,false,true,437531,European Paintings,Painting,"Portrait of a Woman, Probably Susanna Lunden (Susanna Fourment, 1599–1628)",,,,,,Artist,,Peter Paul Rubens,"Flemish, Siegen 1577–1640 Antwerp",,"Rubens, Peter Paul",Flemish,1577,1640,ca. 1625–27,1625,1627,Oil on wood,"30 1/4 x 23 5/8 in. (76.8 x 60 cm), including added strip of 3 3/4 in. (9.5 cm) at bottom","Gift of Mr. and Mrs. Charles Wrightsman, 1976",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437531,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1981.238,true,true,437532,European Paintings,Painting,"Rubens, His Wife Helena Fourment (1614–1673), and Their Son Frans (1633–1678)",,,,,,Artist,,Peter Paul Rubens,"Flemish, Siegen 1577–1640 Antwerp",,"Rubens, Peter Paul",Flemish,1577,1640,ca. 1635,1630,1640,Oil on wood,80 1/4 x 62 1/4 in. (203.8 x 158.1 cm),"Gift of Mr. and Mrs. Charles Wrightsman, in honor of Sir John Pope-Hennessy, 1981",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437532,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1990.196,false,true,437526,European Paintings,Painting,A Forest at Dawn with a Deer Hunt,,,,,,Artist,,Peter Paul Rubens,"Flemish, Siegen 1577–1640 Antwerp",,"Rubens, Peter Paul",Flemish,1577,1640,ca. 1635,1630,1640,Oil on wood,24 1/4 x 35 1/2 in. (61.5 x 90.2 cm),"Purchase, The Annenberg Foundation, Mrs. Charles Wrightsman, Michel David-Weill, The Dillon Fund, Henry J. and Drue Heinz Foundation, Lola Kramarsky, Annette de la Renta, Mr. and Mrs. Arthur Ochs Sulzberger, The Vincent Astor Foundation, and Peter J. Sharp Gifts; special funds, gifts, and other gifts and bequests, by exchange, 1990",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437526,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -55.135.1,false,true,437529,European Paintings,Painting,"The Holy Family with Saint Elizabeth, Saint John, and a Dove",,,,,,Artist,,Peter Paul Rubens,"Flemish, Siegen 1577–1640 Antwerp",,"Rubens, Peter Paul",Flemish,1577,1640,ca. 1608–9,1608,1609,Oil on wood,26 x 20 1/4 in. (66 x 51.4 cm),"Bequest of Ada Small Moore, 1955",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437529,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.160.12,false,true,437527,European Paintings,Painting,The Glorification of the Eucharist,,,,,,Artist,,Peter Paul Rubens,"Flemish, Siegen 1577–1640 Antwerp",,"Rubens, Peter Paul",Flemish,1577,1640,ca. 1630–32,1630,1632,Oil on wood,28 x 19 in. (71.1 x 48.3 cm),"Bequest of Ogden Mills, 1929",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437527,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -67.187.99,false,true,437533,European Paintings,Painting,Study of Two Heads,,,,,,Artist,,Peter Paul Rubens,"Flemish, Siegen 1577–1640 Antwerp",,"Rubens, Peter Paul",Flemish,1577,1640,ca. 1609,1604,1614,Oil on wood,27 1/2 x 20 1/2 in. (69.9 x 52.1 cm),"Bequest of Miss Adelaide Milton de Groot (1876–1967), 1967",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437533,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1982.60.24,false,true,437530,European Paintings,Painting,"Portrait of a Man, Possibly an Architect or Geographer",,,,,,Artist,,Peter Paul Rubens,"Flemish, Siegen 1577–1640 Antwerp",,"Rubens, Peter Paul",Flemish,1577,1640,1597,1597,1597,Oil on copper,8 1/2 x 5 3/4 in. (21.6 x 14.6 cm),"The Jack and Belle Linsky Collection, 1982",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437530,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1984.433.336,false,true,437524,European Paintings,"Painting, sketch",The Coronation of the Virgin,,,,,,Artist,,Peter Paul Rubens,"Flemish, Siegen 1577–1640 Antwerp",,"Rubens, Peter Paul",Flemish,1577,1640,ca. 1632–33,1632,1633,Oil on wood,19 5/8 x 16 in. (49.8 x 40.6 cm),"Bequest of Scofield Thayer, 1982",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437524,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1984.433.20,false,true,483334,European Paintings,Painting,"Landscape, Kragerø",,,,,,Artist,,Edvard Munch,"Norwegian, Løten 1863–1944 Ekely",,"Munch, Edvard",Norwegian,1863,1944,1912,1912,1912,Oil on canvas,28 1/2 x 39 1/2 in. (72.4 x 100.3 cm),"Bequest of Scofield Thayer, 1982",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/483334,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -34.73,true,true,437455,European Paintings,Painting,The Holy Family with Saints Anne and Catherine of Alexandria,,,,,,Artist,,Jusepe de Ribera (called Lo Spagnoletto),"Spanish, Játiva 1591–1652 Naples",,"Ribera, Jusepe de (called Lo Spagnoletto)",Spanish,1591,1652,1648,1648,1648,Oil on canvas,82 1/2 x 60 3/4 in. (209.6 x 154.3 cm),"Samuel D. Lee Fund, 1934",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437455,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2012.416,false,true,441971,European Paintings,Painting,The Tears of Saint Peter,,,,,,Artist,,Jusepe de Ribera (called Lo Spagnoletto),"Spanish, Játiva 1591–1652 Naples",,"Ribera, Jusepe de (called Lo Spagnoletto)",Spanish,1591,1652,ca. 1612–13,1612,1613,Oil on canvas,63 3/4 x 45 in. (161.9 x 114.3 cm),"Purchase, Gift of Mrs. William M. Haupt, from the collection of Mrs. James B. Haggin, by exchange, and 2011 Benefit Fund, 2012",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/441971,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1971.86,true,true,437869,European Paintings,Painting,Juan de Pareja (1606–1670),,,,,,Artist,,Velázquez (Diego Rodríguez de Silva y Velázquez),"Spanish, Seville 1599–1660 Madrid",,Velázquez (Diego Rodríguez de Silva y Velázquez),Spanish,1599,1660,1650,1650,1650,Oil on canvas,32 x 27 1/2 in. (81.3 x 69.9 cm),"Purchase, Fletcher and Rogers Funds, and Bequest of Miss Adelaide Milton de Groot (1876–1967), by exchange, supplemented by gifts from friends of the Museum, 1971",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437869,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -49.7.42,false,true,437874,European Paintings,Painting,Portrait of a Man,,,,,,Artist,,Velázquez (Diego Rodríguez de Silva y Velázquez),"Spanish, Seville 1599–1660 Madrid",,Velázquez (Diego Rodríguez de Silva y Velázquez),Spanish,1599,1660,ca. 1630–35,1630,1635,Oil on canvas,27 x 21 3/4 in. (68.6 x 55.2 cm),"The Jules Bache Collection, 1949",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437874,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -49.7.43,false,true,437870,European Paintings,Painting,"María Teresa (1638–1683), Infanta of Spain",,,,,,Artist,,Velázquez (Diego Rodríguez de Silva y Velázquez),"Spanish, Seville 1599–1660 Madrid",,Velázquez (Diego Rodríguez de Silva y Velázquez),Spanish,1599,1660,1651–54,1651,1654,Oil on canvas,Overall 13 1/2 x 15 3/4 in. (34.3 x 40 cm); original painted surface 12 7/8 x 15 1/8 in. (32.7 x 38.4 cm),"The Jules Bache Collection, 1949",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437870,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -89.15.29,false,true,437875,European Paintings,Painting,Portrait of a Man,,,,,,Artist,Workshop of,Velázquez,"Spanish, Seville 1599–1660 Madrid",,Velázquez,Spanish,1599,1660,ca. 1650,1645,1655,Oil on canvas,27 1/4 x 22 1/4 in. (69.2 x 56.5 cm),"Marquand Collection, Gift of Henry G. Marquand, 1889",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437875,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.40.631,false,true,437871,European Paintings,Painting,The Supper at Emmaus,,,,,,Artist,,Velázquez (Diego Rodríguez de Silva y Velázquez),"Spanish, Seville 1599–1660 Madrid",,Velázquez (Diego Rodríguez de Silva y Velázquez),Spanish,1599,1660,1622–23,1622,1623,Oil on canvas,48 1/2 x 52 1/4 in. (123.2 x 132.7 cm),"Bequest of Benjamin Altman, 1913",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437871,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.40.639,false,true,437873,European Paintings,Painting,"Philip IV (1605–1665), King of Spain",,,,,,Artist,,Velázquez (Diego Rodríguez de Silva y Velázquez),"Spanish, Seville 1599–1660 Madrid",,Velázquez (Diego Rodríguez de Silva y Velázquez),Spanish,1599,1660,probably 1624,1624,1624,Oil on canvas,78 3/4 x 40 1/2 in. (200 x 102.9 cm),"Bequest of Benjamin Altman, 1913",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437873,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -87.15.39,false,true,437902,European Paintings,Painting,Examining Antique Arms,,,,,,Artist,,José Villegas y Cordero,"Spanish, Seville 1848–1921 Madrid",,"Villegas y Cordero, José",Spanish,1848,1921,1870,1870,1870,Oil on wood,15 5/8 x 12 1/2 in. (39.7 x 31.8 cm),"Catharine Lorillard Wolfe Collection, Bequest of Catharine Lorillard Wolfe, 1887",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437902,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -71.11,false,true,436799,European Paintings,Painting,The Holy Family with Saint Anne and the Young Baptist and His Parents,,,,,,Artist,,Jacob Jordaens,"Flemish, Antwerp 1593–1678 Antwerp",,"Jordaens, Jacob",Flemish,1593,1678,early 1620s and 1650s,1620,1659,Oil on wood,66 7/8 x 59 in. (169.9 x 149.9 cm),"Purchase, 1871",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436799,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -67.187.76,false,true,436798,European Paintings,Painting,The Holy Family with Shepherds,,,,,,Artist,,Jacob Jordaens,"Flemish, Antwerp 1593–1678 Antwerp",,"Jordaens, Jacob",Flemish,1593,1678,1616,1616,1616,"Oil on canvas, transferred from wood",42 x 30 in. (106.7 x 76.2 cm),"Bequest of Miss Adelaide Milton de Groot (1876–1967), 1967",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436798,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1991.444,false,true,435813,European Paintings,Painting,Aeneas and the Sibyl in the Underworld,,,,,,Artist,,Jan Brueghel the Younger,"Flemish, Antwerp 1601–1678 Antwerp",,"Brueghel, Jan, the Younger",Flemish,1601,1678,1630s,1630,1639,Oil on copper,10 1/2 x 14 1/8 in. (26.7 x 35.9 cm),"Gift of Mrs. Erna S. Blade, in memory of her uncle, Sigmund Herrmann, 1991",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435813,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -67.187.58,false,true,435814,European Paintings,Painting,A Basket of Flowers,,,,,,Artist,,Jan Brueghel the Younger,"Flemish, Antwerp 1601–1678 Antwerp",,"Brueghel, Jan, the Younger",Flemish,1601,1678,probably 1620s,1620,1629,Oil on wood,18 1/2 x 26 7/8 in. (47 x 68.3 cm),"Bequest of Miss Adelaide Milton de Groot (1876–1967), 1967",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435814,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -07.225.272,false,true,437624,European Paintings,"Painting, grisaille overdoor",The Triumph of Bacchus,,,,,,Artist,,Piat Joseph Sauvage,"Flemish, Tournai 1744–1818 Tournai",,"Sauvage, Piat Joseph",Flemish,1744,1818,early 1780s,1780,1783,Oil on canvas,19 1/4 x 46 1/8 in. (48.9 x 117.2 cm),"Gift of J. Pierpont Morgan, 1906",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437624,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2014.228,false,true,652416,European Paintings,Painting,The Crucifixion,,,,,,Artist,,Pedro Orrente,"Spanish, Murcia 1580–1645 Valencia",,"Orrente, Pedro",Spanish,1580,1645,ca. 1625–30,1625,1630,Oil on canvas,48 3/4 × 40 1/2 in. (123.8 × 102.9 cm),"Purchase, Charles and Jessie Price and Fern and George Wachter Gifts, 2014",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/652416,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -43.13,true,true,437175,European Paintings,Painting,Virgin and Child,,,,,,Artist,,Bartolomé Estebán Murillo,"Spanish, Seville 1617–1682 Seville",,"Murillo, Bartolomé Estebán",Spanish,1617,1682,ca. 1670–72,1670,1672,Oil on canvas,65 1/4 x 43 in. (165.7 x 109.2 cm),"Rogers Fund, 1943",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437175,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -27.219,false,true,437173,European Paintings,Painting,Don Andrés de Andrade y la Cal,,,,,,Artist,,Bartolomé Estebán Murillo,"Spanish, Seville 1617–1682 Seville",,"Murillo, Bartolomé Estebán",Spanish,1617,1682,ca. 1665–72,1665,1672,Oil on canvas,79 x 47 in. (200.7 x 119.4 cm),"Bequest of Collis P. Huntington, by exchange, 1927",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437173,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -54.190,false,true,437174,European Paintings,Painting,A Knight of Alcántara or Calatrava,,,,,,Artist,,Bartolomé Estebán Murillo,"Spanish, Seville 1617–1682 Seville",,"Murillo, Bartolomé Estebán",Spanish,1617,1682,ca. 1650–55,1650,1655,Oil on canvas,"Overall, with added strips, 77 x 43 3/4 in. (195.6 x 111.1 cm); original canvas 77 x 38 1/2 in. (195.6 x 97.8 cm)","Gift of Rudolf J. Heinemann, 1954",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437174,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.100.17,false,true,437172,European Paintings,Painting,The Crucifixion,,,,,,Artist,,Bartolomé Estebán Murillo,"Spanish, Seville 1617–1682 Seville",,"Murillo, Bartolomé Estebán",Spanish,1617,1682,ca. 1675,1670,1680,Oil on canvas,20 x 13 in. (50.8 x 33 cm),"Bequest of Harry G. Sperling, 1971",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437172,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -54.168,false,true,437862,European Paintings,Painting,Pietà,,,,,,Artist,,Juan de Valdés Leal,"Spanish, Seville 1622–1690 Seville",,"Valdés Leal, Juan de",Spanish,1622,1690,ca. 1657–60,1657,1660,Oil on canvas,63 1/4 x 56 1/2 in. (160.7 x 143.5 cm),"Victor Wilbour Memorial Fund, 1954",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437862,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.285,false,true,440727,European Paintings,Painting,German Landscape with View towards a Broad Valley,,,,,,Artist,,Fritz Petzholdt,"Danish, Copenhagen 1805–1838 Patras",,"Petzholdt, Fritz",Danish,1805,1838,ca. 1829–30,1824,1834,"Oil on paper, laid down on canvas",Original paper support: 5 3/16 x 9 11/16 in. (13.2 x 24.6 cm) Paper support mounted on stretched canvas: 5 13/16 x 10 1/4 in. (14.8 x 26 cm),"Gift of Wheelock Whitney III, in honor of Eugene V. Thaw, 2009",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/440727,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -72.2,false,true,437778,European Paintings,Painting,Judith with the Head of Holofernes,,,,,,Artist,,David Teniers the Younger,"Flemish, Antwerp 1610–1690 Brussels",,"Teniers, David, the Younger",Flemish,1610,1690,1650s,1650,1659,Oil on copper,14 1/2 x 10 3/8 in. (36.8 x 26.4 cm),"Gift of Gouverneur Kemble, 1872",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437778,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -71.99,false,true,437779,European Paintings,Painting,Peasants Dancing and Feasting,,,,,,Artist,,David Teniers the Younger,"Flemish, Antwerp 1610–1690 Brussels",,"Teniers, David, the Younger",Flemish,1610,1690,ca. 1660,1655,1665,Oil on canvas,25 1/8 x 29 1/2 in. (63.8 x 74.9 cm); with added strip 26 7/8 x 29 1/2 in. (68.3 x 74.9 cm),"Purchase, 1871",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437779,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -64.65.5,false,true,437777,European Paintings,Painting,Guardroom with the Deliverance of Saint Peter,,,,,,Artist,,David Teniers the Younger,"Flemish, Antwerp 1610–1690 Brussels",,"Teniers, David, the Younger",Flemish,1610,1690,ca. 1645–47,1645,1647,Oil on wood,21 3/4 x 29 7/8 in. (55.2 x 75.9 cm),"Gift of Edith Neuman de Végvár, in honor of her husband, Charles Neuman de Végvár, 1964",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437777,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2012.447,false,true,441967,European Paintings,Painting,View over Hallingdal,,,,,,Artist,,Johan Christian Dahl,"Norwegian, Bergen 1788–1857 Dresden",,"Dahl, Johan Christian",Norwegian,1788,1857,1844,1844,1844,Oil on canvas,9 1/2 x 14 3/8 in. (24.1 x 36.5 cm),"Gift of Asbjorn R. Lunde, in memory of his brother, Karl Lunde, 2012",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/441967,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.164.2,false,true,438954,European Paintings,Painting,Mother and Child by the Sea,,,,,,Artist,,Johan Christian Dahl,"Norwegian, Bergen 1788–1857 Dresden",,"Dahl, Johan Christian",Norwegian,1788,1857,1830,1830,1830,Oil on canvas,6 1/4 x 8 1/8 in. (15.9 x 20.6 cm),"Gift of Eugene V. Thaw, 2007",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/438954,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.145.1,false,true,437441,European Paintings,Painting,Portrait of a Boy,,,,,,Artist,,Ilia Efimovich Repin,"Russian, Chuguev 1844–1930 Kuokkala",,"Repin, Ilia Efimovich",Russian,1844,1930,1884,1884,1884,Oil on canvas,22 1/2 x 17 3/8 in. (57.2 x 44.1 cm),"Gift of Humanities Fund Inc., 1972",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437441,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.145.2,false,true,437442,European Paintings,Painting,Vsevolod Mikhailovich Garshin (1855–1888),,,,,,Artist,,Ilia Efimovich Repin,"Russian, Chuguev 1844–1930 Kuokkala",,"Repin, Ilia Efimovich",Russian,1844,1930,1884,1884,1884,Oil on canvas,35 x 27 1/4 in. (88.9 x 69.2 cm),"Gift of Humanities Fund Inc., 1972",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437442,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.280.4,false,true,437440,European Paintings,Painting,Shepherd with a Flock of Sheep,,,,,,Artist,,Ilia Efimovich Repin,"Russian, Chuguev 1844–1930 Kuokkala",,"Repin, Ilia Efimovich",Russian,1844,1930,1870,1870,1870,Oil on canvas board,4 7/8 x 8 7/8 in. (12.4 x 22.5 cm),"Bequest of Mary Jane Dastich, in memory of her husband, General Frank Dastich, 1975",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437440,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.164.7,false,true,438952,European Paintings,Painting,View from the Citadel Ramparts in Copenhagen by Moonlight,,,,,,Artist,,Martinus Rørbye,"Danish, Drammen 1803–1848 Copenhagen",,"Rørbye, Martinus",Danish,1803,1848,1839,1839,1839,Oil on canvas,11 3/8 x 9 5/8 in. (28.9 x 24.4 cm),"Gift of Eugene V. Thaw, 2007",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/438952,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.164.6,false,true,438951,European Paintings,Painting,An Evening beside Lake Arresø,,,,,,Artist,,Johan Thomas Lundbye,"Danish, Kalundborg 1818–1848 Bedsted",,"Lundbye, Johan Thomas",Danish,1818,1848,ca. 1837,1832,1842,Oil on canvas,9 x 11 3/4 in. (22.9 x 29.8 cm),"Gift of Eugene V. Thaw, 2007",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/438951,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.164.4,false,true,438949,European Paintings,Painting,Columns of the Temple of Neptune at Paestum,,,,,,Artist,,Constantin Hansen,"Danish, Rome 1804–1880 Frederiksberg",,"Hansen, Constantin",Danish,1804,1880,1838,1838,1838,Oil on canvas,12 5/8 x 10 in. (32.1 x 25.4 cm),"Gift of Eugene V. Thaw, 2007",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/438949,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -43.101,false,true,437046,European Paintings,Painting,"María Teresa (1638–1683), Infanta of Spain",,,,,,Artist,,Juan Bautista Martínez del Mazo,"Spanish, Cuenca ca. 1612–1667 Madrid",,"Mazo, Juan Bautista Martínez del",Spanish,1612,1667,ca. 1645,1640,1650,Oil on canvas,58 1/4 x 40 1/2 in. (148 x 102.9 cm),"Rogers Fund, 1943",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437046,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -87.15.99,false,true,437923,European Paintings,Painting,"A Day in October, near Waxholm, Sweden",,,,,,Artist,,Alfred Wahlberg,"Swedish, Stockholm 1834–1906 Tranås",,"Wahlberg, Alfred",Swedish,1834,1906,1873,1873,1873,Oil on canvas,41 x 64 1/2 in. (104.1 x 163.8 cm),"Catharine Lorillard Wolfe Collection, Bequest of Catharine Lorillard Wolfe, 1887",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437923,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -64.148,true,true,436819,European Paintings,Painting,Mäda Primavesi (1903–2000),,,,,,Artist,,Gustav Klimt,"Austrian, Baumgarten 1862–1918 Vienna",,"Klimt, Gustav",Austrian,1862,1918,1912–13,1912,1913,Oil on canvas,59 x 43 1/2 in. (149.9 x 110.5 cm),"Gift of André and Clara Mertens, in memory of her mother, Jenny Pulitzer Steiner, 1964",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436819,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1980.412,false,true,436820,European Paintings,Painting,Serena Pulitzer Lederer (1867–1943),,,,,,Artist,,Gustav Klimt,"Austrian, Baumgarten 1862–1918 Vienna",,"Klimt, Gustav",Austrian,1862,1918,1899,1899,1899,Oil on canvas,75 1/8 x 33 5/8 in. (190.8 x 85.4 cm),"Purchase, Wolfe Fund, and Rogers and Munsey Funds, Gift of Henry Walters, and Bequests of Catharine Lorillard Wolfe and Collis P. Huntington, by exchange, 1980",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436820,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2012.25,false,true,441768,European Paintings,Painting,"A Section of the Via Sacra, Rome (The Church of Saints Cosmas and Damian)",,,,,,Artist,,Christoffer Wilhelm Eckersberg,"Danish, Blåkrog 1783–1853 Copenhagen",,"Eckersberg, Christoffer Wilhelm",Danish,1783,1853,ca. 1814–15,1814,1815,Oil on canvas,12 3/8 x 17 1/8 in. (31.4 x 43.5 cm),"Nineteenth-Century, Modern, and Contemporary Funds, 2012",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/441768,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -87.4.7,false,true,435599,European Paintings,Painting,Boatmen of Barcelona,,,,,,Artist,,Dionisio Baixeras y Verdaguer,"Spanish, Barcelona 1862–1943 Barcelona",,"Baixeras y Verdaguer, Dionisio",Spanish,1862,1943,1886,1886,1886,Oil on canvas,59 x 83 in. (149.9 x 210.8 cm),"Gift of George I. Seney, 1886",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435599,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -58.81,false,true,437707,European Paintings,Painting,"Mrs. Winthrop W. Aldrich (Harriet Alexander, 1888–1972)",,,,,,Artist,,Joaquín Sorolla y Bastida,"Spanish, Valencia 1863–1923 Cercedilla",,"Sorolla y Bastida, Joaquín",Spanish,1863,1923,1909,1909,1909,Oil on canvas,40 x 30 3/8 in. (101.6 x 77.2 cm),"Gift of Harriet Alexander Aldrich, 1958",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437707,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -09.71.2,false,true,437704,European Paintings,Painting,"The Bath, Jávea",,,,,,Artist,,Joaquín Sorolla y Bastida,"Spanish, Valencia 1863–1923 Cercedilla",,"Sorolla y Bastida, Joaquín",Spanish,1863,1923,1905,1905,1905,Oil on canvas,35 1/2 x 50 1/2 in. (90.2 x 128.3 cm),"Catharine Lorillard Wolfe Collection, Wolfe Fund, 1909",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437704,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -09.71.3,false,true,437706,European Paintings,Painting,"Señora de Sorolla (Clotilde García del Castillo, 1865–1929) in Black",,,,,,Artist,,Joaquín Sorolla y Bastida,"Spanish, Valencia 1863–1923 Cercedilla",,"Sorolla y Bastida, Joaquín",Spanish,1863,1923,1906,1906,1906,Oil on canvas,73 1/2 x 46 3/4 in. (186.7 x 118.7 cm),"Catharine Lorillard Wolfe Collection, Wolfe Fund, 1909",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437706,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -22.119.1,false,true,437705,European Paintings,Painting,"Castle of San Servando, Toledo",,,,,,Artist,,Joaquín Sorolla y Bastida,"Spanish, Valencia 1863–1923 Cercedilla",,"Sorolla y Bastida, Joaquín",Spanish,1863,1923,1906,1906,1906,Oil on canvas,26 1/4 x 36 1/2 in. (66.7 x 92.7 cm),"Gift of Archer M. Huntington, 1922",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437705,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1990.233,false,true,436825,European Paintings,Painting,"Valdemar Hjartvar Købke (1813–1893), the Artist's Brother",,,,,,Artist,,Christen Købke,"Danish, Copenhagen 1810–1848 Copenhagen",,"Købke, Christen",Danish,1810,1848,ca. 1838,1833,1843,Oil on canvas,21 1/8 x 18 1/4 in. (53.7 x 46.4 cm),"Catharine Lorillard Wolfe Collection, Wolfe Fund, 1990",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436825,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2012.203,false,true,441933,European Paintings,Painting,"Moonlight, Strandgade 30",,,,,,Artist,,Vilhelm Hammershøi,"Danish, Copenhagen 1864–1916 Copenhagen",,"Hammershøi, Vilhelm",Danish,1864,1916,1900–1906,1900,1906,Oil on canvas,16 1/8 x 20 1/8 in. (41 x 51.1 cm),"Purchase, European Paintings Funds, and Annette de la Renta Gift, 2012",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/441933,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -32.100.1,false,true,435806,European Paintings,Painting,A Peasant Woman Picking Fleas off a Dog,,,,,,Artist,,Adriaen Brouwer,"Flemish, Oudenaarde 1605/6–1638 Antwerp",,"Brouwer, Adriaen",Flemish,1605,1638,ca. 1626–27,1626,1627,Oil on wood,Oval 7 1/8 x 5 3/8 in. (18.1 x 13.7 cm); set in rectangular panel 8 x 6 1/4 in. (20.3 x 15.9 cm),"The Friedsam Collection, Bequest of Michael Friedsam, 1931",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435806,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -32.100.21,false,true,435807,European Paintings,Painting,The Smokers,,,,,,Artist,,Adriaen Brouwer,"Flemish, Oudenaarde 1605/6–1638 Antwerp",,"Brouwer, Adriaen",Flemish,1605,1638,ca. 1636,1631,1638,Oil on wood,18 1/4 x 14 1/2 in. (46.4 x 36.8 cm),"The Friedsam Collection, Bequest of Michael Friedsam, 1931",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435807,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -06.289,false,true,436541,European Paintings,Painting,Sebastián Martínez y Pérez (1747–1800),,,,,,Artist,,Goya (Francisco de Goya y Lucientes),"Spanish, Fuendetodos 1746–1828 Bordeaux",,Goya (Francisco de Goya y Lucientes),Spanish,1746,1828,1792,1792,1792,Oil on canvas,36 5/8 x 26 5/8 in. (93 x 67.6 cm),"Rogers Fund, 1906",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436541,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -61.259,false,true,436546,European Paintings,Painting,"José Costa y Bonells (died l870), Called Pepito",,,,,,Artist,,Goya (Francisco de Goya y Lucientes),"Spanish, Fuendetodos 1746–1828 Bordeaux",,Goya (Francisco de Goya y Lucientes),Spanish,1746,1828,ca. 1810,1805,1815,Oil on canvas,41 3/8 x 33 1/4 in. (105.1 x 84.5 cm),"Gift of Countess Bismarck, 1961",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436546,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -49.7.41,true,true,436545,European Paintings,Painting,Manuel Osorio Manrique de Zuñiga (1784–1792),,,,,,Artist,,Goya (Francisco de Goya y Lucientes),"Spanish, Fuendetodos 1746–1828 Bordeaux",,Goya (Francisco de Goya y Lucientes),Spanish,1746,1828,1787–88,1787,1788,Oil on canvas,50 x 40 in. (127 x 101.6 cm),"The Jules Bache Collection, 1949",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436545,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -55.145.1,false,true,436542,European Paintings,Painting,"Ignacio Garcini y Queralt (1752–1825), Brigadier of Engineers",,,,,,Artist,,Goya (Francisco de Goya y Lucientes),"Spanish, Fuendetodos 1746–1828 Bordeaux",,Goya (Francisco de Goya y Lucientes),Spanish,1746,1828,1804,1804,1804,Oil on canvas,41 x 32 3/4 in. (104.1 x 83.2 cm),"Bequest of Harry Payne Bingham, 1955",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436542,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -55.145.2,false,true,436543,European Paintings,Painting,Josefa de Castilla Portugal y van Asbrock de Garcini (1775–about 1850),,,,,,Artist,,Goya (Francisco de Goya y Lucientes),"Spanish, Fuendetodos 1746–1828 Bordeaux",,Goya (Francisco de Goya y Lucientes),Spanish,1746,1828,1804,1804,1804,Oil on canvas,41 x 32 3/8 in. (104.1 x 82.2 cm),"Bequest of Harry Payne Bingham, 1955",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436543,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.10,false,true,436548,European Paintings,Painting,Majas on a Balcony,,,,,,Artist,Attributed to,Goya (Francisco de Goya y Lucientes),"Spanish, Fuendetodos 1746–1828 Bordeaux",,Goya (Francisco de Goya y Lucientes),Spanish,1746,1828,ca. 1800–1810,1800,1810,Oil on canvas,76 3/4 x 49 1/2in. (194.9 x 125.7cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436548,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -30.95.242,false,true,436544,European Paintings,Painting,"Tiburcio Pérez y Cuervo (1785/86–1841), the Architect",,,,,,Artist,,Goya (Francisco de Goya y Lucientes),"Spanish, Fuendetodos 1746–1828 Bordeaux",,Goya (Francisco de Goya y Lucientes),Spanish,1746,1828,1820,1820,1820,Oil on canvas,40 1/4 x 32 in. (102.2 x 81.3 cm),"Theodore M. Davis Collection, Bequest of Theodore M. Davis, 1915",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436544,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1982.60.3,false,true,437298,European Paintings,Painting,The Presentation in the Temple,,,,,,Artist,,Alvaro Pirez,"Portuguese, Évora, active 1411–34 Italy",,"Pirez, Alvaro",Portuguese,1411,1434,probably ca. 1430,1425,1435,Tempera and gold on wood,13 3/8 x 15 7/8 in. (34 x 40.3 cm),"The Jack and Belle Linsky Collection, 1982",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437298,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -45.94.2,false,true,437750,European Paintings,Painting,A Renaissance Portico with Elegant Figures,,,,,,Artist,,Hendrick van Steenwijck II,"Flemish, Antwerp (?) ca. 1580–1649 Leiden",,"Steenwijck, Hendrick van, II",Flemish,1475,1649,ca. 1615,1610,1620,Oil on copper,Diameter 4 3/8 in. (11.1 cm),"Gift of Mrs. James Eads Switzer, in memory of her aunt, Yrene Ceballos de Sanz, 1945",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437750,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1983.452,false,true,436817,European Paintings,Painting,A Mountainous Landscape with a Waterfall,,,,,,Artist,,Kerstiaen de Keuninck,"Flemish, Kortrijk ca. 1560–1632/33 Antwerp",,"Keuninck, Kerstiaen de",Flemish,1560,1633,ca. 1600,1595,1605,Oil on wood,27 1/4 x 48 in. (69.2 x 121.9 cm),"Purchase, Anonymous Gift, L. H. P. Klotz and George T. Delacorte Jr. Gifts; Rogers, Marquand, Charles B. Curtis, and The Alfred N. Punnett Endowment Funds; and Gift of Eugen Boross and Bequest of Collis P. Huntington, by exchange, 1983",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436817,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1974.100,false,true,436833,European Paintings,Painting,Red Sunset on the Dnieper,,,,,,Artist,,Arkhip Ivanovich Kuindzhi,"Russian, Mariupol 1842–1910 St. Petersburg",,"Kuindzhi, Arkhip Ivanovich",Russian,1842,1910,1905–8,1905,1908,Oil on canvas,53 x 74 in. (134.6 x 188 cm),"Rogers Fund, 1974",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436833,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.145.5,false,true,437321,European Paintings,Painting,Christ and the Woman Taken in Adultery,,,,,,Artist,,Vasilii Dmitriviech Polenov,"Russian, St. Petersburg 1844–1927 Polenovo",,"Polenov, Vasilii Dmitrievich",Russian,1844,1927,1884,1884,1884,Oil on canvas,9 1/2 x 17 in. (24.1 x 43.2 cm),"Gift of Humanities Fund Inc., 1972",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437321,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2014.149,false,true,641257,European Paintings,Painting,Head of Christ,,,,,,Artist,,Fernando Yáñez de la Almedina,"Spanish, Almedina, ca. 1475?–1536 Valencia",,"Yáñez de la Almedina, Fernando",Spanish,1470,1536,ca. 1506,1501,1511,Oil on poplar,16 1/2 × 12 in. (41.9 × 30.5 cm),"Purchase, The Morris and Alma Schapiro Fund Gift, and Bequest of George D. Pratt, by exchange, 2014",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/641257,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -20.104,false,true,437969,European Paintings,"Painting, part of an altarpiece",The Battle between Christians and Moors at El Sotillo,,,,,,Artist,,Francisco de Zurbarán,"Spanish, Fuente de Cantos 1598–1664 Madrid",,"Zurbarán, Francisco de",Spanish,1598,1664,ca. 1637–39,1637,1639,Oil on canvas,"Arched top, 131 7/8 x 75 1/4 in. (335 x 191.1 cm)","Kretschmar Fund, 1920",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437969,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -27.137,true,true,437971,European Paintings,Painting,The Young Virgin,,,,,,Artist,,Francisco de Zurbarán,"Spanish, Fuente de Cantos 1598–1664 Madrid",,"Zurbarán, Francisco de",Spanish,1598,1664,ca. 1632–33,1632,1633,Oil on canvas,46 x 37 in. (116.8 x 94 cm),"Fletcher Fund, 1927",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437971,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.100.21,false,true,437970,European Paintings,Painting,Saint Benedict,,,,,,Artist,,Francisco de Zurbarán,"Spanish, Fuente de Cantos 1598–1664 Madrid",,"Zurbarán, Francisco de",Spanish,1598,1664,ca. 1640–45,1640,1645,Oil on canvas,74 x 40 3/4 in. (188 x 103.5 cm),"Bequest of Harry G. Sperling, 1971",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437970,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -26.90,true,true,435683,European Paintings,Painting,Island of the Dead,,,,,,Artist,,Arnold Böcklin,"Swiss, Basel 1827–1901 San Domenico, Italy",,"Böcklin, Arnold",Swiss,1827,1901,1880,1880,1880,Oil on wood,29 x 48 in. (73.7 x 121.9 cm),"Reisinger Fund, 1926",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435683,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -26.100,false,true,435684,European Paintings,Painting,Roman Landscape,,,,,,Artist,Attributed to,Arnold Böcklin,"Swiss, Basel 1827–1901 San Domenico, Italy",,"Böcklin, Arnold",Swiss,1827,1901,ca. 1850–52,1850,1852,"Oil on canvas, several pieces joined",12 1/2 x 18 1/8 in. (31.8 x 46 cm),"Gift of Fearon Galleries Inc., 1926",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435684,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -34.83.2,false,true,436861,European Paintings,Painting,A Masked Ball in Bohemia,,,,,,Artist,Attributed to,Andreas Altomonte,"Austrian, Warsaw or Vienna 1699–1780 Vienna",,"Altomonte, Andreas",Austrian,1699,1780,ca. 1748,1743,1753,Oil on canvas,19 x 38 in. (48.3 x 96.5 cm),"Bequest of Mariana Griswold Van Rensselaer, 1934",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436861,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -15.30.56,false,true,437179,European Paintings,Painting,"Near Penshurst, Kent",,,,,,Artist,,Patrick Nasmyth,"British, Edinburgh, Scotland 1787–1831 London",,"Nasmyth, Patrick","British, Scottish",1787,1831,1828,1828,1828,Oil on wood,27 1/2 x 36 1/4 in. (69.9 x 92.1 cm),"Bequest of Maria DeWitt Jesup, from the collection of her husband, Morris K. Jesup, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437179,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -05.42,false,true,436570,European Paintings,Painting,The Adoration of the Shepherds,,,,,,Artist,,El Greco (Domenikos Theotokopoulos),"Greek, Iráklion (Candia) 1540/41–1614 Toledo",,"Greco, El (Domenikos Theotokopoulos)",Greek,1540,1614,ca. 1605–10,1605,1610,Oil on canvas,56 7/8 x 39 7/8 in. (144.5 x 101.3 cm); with added strips 64 1/2 x 42 in. (163.8 x 106.7 cm),"Rogers Fund, 1905",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436570,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -56.48,false,true,436576,European Paintings,Painting,The Vision of Saint John,,,,,,Artist,,El Greco (Domenikos Theotokopoulos),"Greek, Iráklion (Candia) 1540/41–1614 Toledo",,"Greco, El (Domenikos Theotokopoulos)",Greek,1540,1614,ca. 1609–14,1609,1614,Oil on canvas,87 1/2 x 76in. (222.3 x 193cm); with added strips 88 1/2 x 78 1/2 in. (224.8 x 199.4 cm) [top truncated],"Rogers Fund, 1956",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436576,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1978.416,false,true,436572,European Paintings,Painting,Christ Healing the Blind,,,,,,Artist,,El Greco (Domenikos Theotokopoulos),"Greek, Iráklion (Candia) 1540/41–1614 Toledo",,"Greco, El (Domenikos Theotokopoulos)",Greek,1540,1614,ca. 1570,1565,1575,Oil on canvas,47 x 57 1/2 in. (119.4 x 146.1 cm),"Gift of Mr. and Mrs. Charles Wrightsman, 1978",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436572,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -24.197.1,false,true,436574,European Paintings,Painting,Portrait of an Old Man,,,,,,Artist,,El Greco (Domenikos Theotokopoulos),"Greek, Iráklion (Candia) 1540/41–1614 Toledo",,"Greco, El (Domenikos Theotokopoulos)",Greek,1540,1614,ca. 1595–1600,1595,1600,Oil on canvas,20 3/4 x 18 3/8 in. (52.7 x 46.7 cm),"Purchase, Joseph Pulitzer Bequest, 1924",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436574,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.5,true,true,436573,European Paintings,Painting,Cardinal Fernando Niño de Guevara (1541–1609),,,,,,Artist,,El Greco (Domenikos Theotokopoulos),"Greek, Iráklion (Candia) 1540/41–1614 Toledo",,"Greco, El (Domenikos Theotokopoulos)",Greek,1540,1614,ca. 1600,1595,1605,Oil on canvas,67 1/4 x 42 1/2in. (170.8 x 108cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436573,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.6,true,true,436575,European Paintings,Painting,View of Toledo,,,,,,Artist,,El Greco (Domenikos Theotokopoulos),"Greek, Iráklion (Candia) 1540/41–1614 Toledo",,"Greco, El (Domenikos Theotokopoulos)",Greek,1540,1614,ca. 1598–99,1598,1599,Oil on canvas,47 3/4 x 42 3/4 in. (121.3 x 108.6 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436575,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -41.190.17,false,true,436571,European Paintings,Painting,The Adoration of the Shepherds,,,,,,Artist,,El Greco (Domenikos Theotokopoulos) and Workshop,"Greek, Iráklion (Candia) 1540/41–1614 Toledo",,,Greek,1540,1614,ca. 1612–14,1612,1614,Oil on canvas,43 1/2 x 25 5/8 in. (110.5 x 65.1 cm),"Bequest of George Blumenthal, 1941",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436571,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -50.70,false,true,437217,European Paintings,Painting,Moses and Aaron before Pharaoh: An Allegory of the Dinteville Family,,,,,,Artist,,Master of the Dinteville Allegory,"Netherlandish or French, active mid-16th century",,Master of the Dinteville Allegory,Netherlandish/French,1525,1575,1537,1537,1537,Oil on wood,69 1/2 x 75 7/8 in. (176.5 x 192.7 cm),"Wentworth Fund, 1950",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437217,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -15.30.52,false,true,437939,European Paintings,Painting,The Highland Family,,,,,,Artist,,Sir David Wilkie,"British, Cults, Scotland 1785–1841 off Gibraltar",,"Wilkie, David, Sir","British, Scottish",1785,1841,1824,1824,1824,Oil on wood,24 x 36 in. (61 x 91.4 cm),"Bequest of Maria DeWitt Jesup, from the collection of her husband, Morris K. Jesup, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437939,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -91.26.1,false,true,436821,European Paintings,Painting,Girl Building a House of Cards,,,,,,Artist,Attributed to,Thomas Frye,"Irish, Dublin, born ca. 1711–12, died 1762 London",,"Frye, Thomas",Irish,1711,1762,mid-18th century,1731,1762,Oil on canvas,30 1/8 x 25 1/4 in. (76.5 x 64.1 cm),"Marquand Collection, Gift of Henry G. Marquand, 1890",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436821,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.63,false,true,435586,European Paintings,Painting,Ivan Rodin,,,,,,Artist,,Abram Efimovich Arkhipov,"Russian, Egorovo Riazan province 1862–1930 Moscow",,"Arkhipov, Abram Efimovich",Russian,1862,1930,1928,1928,1928,Oil on canvas,44 x 34 1/4 in. (111.8 x 87 cm),"Gift of George D. Pratt, 1929",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435586,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.28,false,true,439117,European Paintings,"Painting, grisaille",The Glorification of the Royal Hungarian Saints,,,,,,Artist,,Franz Anton Maulbertsch,"Austrian, Langenargen am Bodensee 1724–1796 Vienna",,"Maulbertsch, Franz Anton",Austrian,1724,1796,ca. 1772–73,1772,1773,Oil on canvas,27 1/2 x 19 7/8 in. (70 x 50.5 cm),"Purchase, Friends of European Paintings Gifts, 2007",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/439117,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2008.420,false,true,439844,European Paintings,Painting,Heroic Landscape with Rainbow,,,,,,Artist,,Joseph Anton Koch,"Austrian, Obergibeln bei Elbigenalp 1768–1839 Rome",,"Koch, Joseph Anton",Austrian,1768,1839,1824,1824,1824,Oil on canvas,42 3/4 x 37 3/4 in. (108.6 x 95.9 cm),"Purchase, Anne Cox Chambers Gift, Gift of Alfred and Katrin Romney, by exchange, and Nineteenth-Century, Modern, and Contemporary Art Funds, 2008",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/439844,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -76.10,false,true,435572,European Paintings,"Painting, part of an altarpiece",Saint Giles with Christ Triumphant over Satan and the Mission of the Apostles,,,,,,Artist,,Miguel Alcañiz (or Miquel Alcanyís),"Spanish, Valencian, active by 1408–died after 1447",,"Alcañiz, Miguel (or Miquel Alcanyís)",Spanish,1408,1447,ca. 1408,1403,1413,"Tempera on wood, gold ground","Overall 59 5/8 x 39 1/2 in. (151.4 x 100.3 cm); upper left panel, painted surface 24 1/8 x 16 7/8 in. (61.3 x 42.9 cm); lower left panel, painted surface 24 5/8 x 16 7/8 in. (62.5 x 42.9 cm); right panel, painted surface 46 1/8 x 16 7/8 in. (117.2 x 42.9 cm)","Gift of J. Bruyn Andrews, 1876",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435572,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -05.32.3,false,true,437941,European Paintings,Painting,Lake Nemi and Genzano from the Terrace of the Capuchin Monastery,,,,,,Artist,,Richard Wilson,"British, Penegoes, Wales 1712/13–1782 Denbighshire, Wales",,"Wilson, Richard","British, Welsh",1712,1782,ca. 1756–57,1756,1757,Oil on canvas,16 7/8 x 21 1/8 in. (42.9 x 53.7 cm),"Gift of George A. Hearn, 1905",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437941,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -49.142,false,true,437355,European Paintings,Painting,George Harley Drummond (1783–1855),,,,,,Artist,,Sir Henry Raeburn,"British, Stockbridge, Scotland 1756–1823 Edinburgh, Scotland",,"Raeburn, Henry, Sir","British, Scottish",1756,1923,ca. 1808–9,1808,1809,Oil on canvas,94 1/4 x 58 in. (239.4 x 147.3 cm),"Gift of Mrs. Guy Fairfax Cary, in memory of her mother, Mrs. Burke Roche, 1949",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437355,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -45.59.2,false,true,437364,European Paintings,Painting,William Scott-Elliot of Arkleton (1811–1901),,,,,,Artist,,Sir Henry Raeburn,"British, Stockbridge, Scotland 1756–1823 Edinburgh, Scotland",,"Raeburn, Henry, Sir","British, Scottish",1756,1923,ca. 1815–16,1815,1816,Oil on canvas,47 3/8 x 36 5/8 in. (120.3 x 93 cm),"Fletcher Fund, 1945",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437364,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -96.30.5,false,true,437363,European Paintings,Painting,William Forsyth (1749–1814),,,,,,Artist,,Sir Henry Raeburn,"British, Stockbridge, Scotland 1756–1823 Edinburgh, Scotland",,"Raeburn, Henry, Sir","British, Scottish",1756,1923,ca. 1800,1795,1805,Oil on canvas,30 x 24 7/8 in. (76.2 x 63.2 cm),"Gift of Arthur H. Hearn, 1896",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437363,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1980.305,false,true,437361,European Paintings,Painting,"Mrs. Richard Alexander Oswald (Louisa Johnston, ?born about 1760, died 1797)",,,,,,Artist,,Sir Henry Raeburn,"British, Stockbridge, Scotland 1756–1823 Edinburgh, Scotland",,"Raeburn, Henry, Sir","British, Scottish",1756,1923,ca. 1794,1789,1798,Oil on canvas,48 1/2 x 40 7/8 in. (123.2 x 103.8 cm),"Gift of Mrs. Paul Moore, 1980",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437361,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -50.145.31,false,true,437354,European Paintings,Painting,The Drummond Children,,,,,,Artist,,Sir Henry Raeburn,"British, Stockbridge, Scotland 1756–1823 Edinburgh, Scotland",,"Raeburn, Henry, Sir","British, Scottish",1756,1923,ca. 1808–9,1808,1809,Oil on canvas,94 1/4 x 60 1/4 in. (239.4 x 153 cm),"Bequest of Mary Stillman Harkness, 1950",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437354,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -50.145.32,false,true,437365,European Paintings,Painting,"William Robertson (1753–1835), Lord Robertson",,,,,,Artist,,Sir Henry Raeburn,"British, Stockbridge, Scotland 1756–1823 Edinburgh, Scotland",,"Raeburn, Henry, Sir","British, Scottish",1756,1923,1805,1805,1805,Oil on canvas,49 1/2 x 39 1/4 in. (125.7 x 99.7 cm),"Bequest of Mary Stillman Harkness, 1950",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437365,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -17.120.119,false,true,436611,European Paintings,"Painting, miniature",Portrait of a Woman,,,,,,Artist,Style of,Peter Adolf Hall,ca. 1780,,"Hall, Peter Adolf",Swedish,1739,1793,,1775,1785,Ivory,Diameter 2 in. (52 mm),"Mr. and Mrs. Isaac D. Fletcher Collection, Bequest of Isaac D. Fletcher, 1917",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/436611,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -62.122.137,false,true,436613,European Paintings,"Painting, miniature",Portrait of a Woman,,,,,,Artist,Style of,Peter Adolf Hall,ca. 1780,,"Hall, Peter Adolf",Swedish,1739,1793,,1775,1785,Ivory,Diameter 2 3/8 in. (62 mm),"Bequest of Millie Bruhl Fredrick, 1962",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/436613,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -24.80.508,false,true,437459,European Paintings,"Painting, miniature","John Churchill (1650–1722), First Duke of Marlborough",,,,,,Artist,,Christian Richter,"Swedish, 1678–1732",,"Richter, Christian",Swedish,1678,1732,,1698,1732,Vellum,"Oval, 3 1/4 x 2 5/8 in. (82 x 67 mm)","Bequest of Mary Clark Thompson, 1923",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/437459,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -62.122.103,false,true,435588,European Paintings,"Painting, miniature","A Young Knight of the Garter, Possibly George Augustus (1683–1760), Later George II of Great Britain and Ireland",,,,,,Artist,Style of,Benjamin Arlaud,"Continental, ca. 1706",,"Arlaud, Benjamin",Swiss,1701,1717,,1701,1711,Vellum,"Oval, 2 x 1 3/4 in. (52 x 45 mm)","Bequest of Millie Bruhl Fredrick, 1962",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/435588,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -62.122.104,false,true,435589,European Paintings,"Painting, miniature","A Woman, Possibly Sophia Dorothea (1687–1757), Later Queen of Prussia",,,,,,Artist,Style of,Benjamin Arlaud,"Continental, ca. 1706",,"Arlaud, Benjamin",Swiss,1701,1717,,1701,1711,Vellum,"Oval, 2 x 1 3/4 in. (52 x 45 mm)","Bequest of Millie Bruhl Fredrick, 1962",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/435589,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -25.106.17,false,true,435758,European Paintings,"Painting, miniature",A Woman Playing a Harp,,,,,,Artist,,Joseph Marie Bouton,"French (?), 1768–1823",,"Bouton, Joseph Marie",French(?),1768,1823,,1788,1823,Ivory,Diameter 3 1/2 in. (90 mm),"Gift of Mrs. Louis V. Bell, in memory of her husband, 1925",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/435758,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -62.122.74,false,true,437626,European Paintings,"Painting, miniature",Putti Harvesting Wheat,,,,,,Artist,Style of,Piat Joseph Sauvage,"French, late 18th century",,"Sauvage, Piat Joseph",Flemish,1744,1818,,1770,1799,Oil on wood,Painted surface diameter 3 in. (76 mm),"Bequest of Millie Bruhl Fredrick, 1962",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/437626,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -55.107.1,false,true,435587,European Paintings,"Painting, miniature","Caroline of Ansbach (1683–1737), Consort of George II of Great Britain and Ireland",,,,,,Artist,Attributed to,Benjamin Arlaud,"Swiss, active ca. 1701–17",,"Arlaud, Benjamin",Swiss,1701,1717,,1701,1717,Ivory,"Oval, 2 5/8 x 2 1/4 in. (68 x 57 mm)","Gift of Estate of Isaac A. Josephi, 1955",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/435587,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -62.122.106,false,true,436234,European Paintings,"Painting, miniature",Portrait of an Officer,,,,,,Artist,,Andrew Dunn,"Irish, active ca. 1800–1820",,"Dunn, Andrew",Irish,1800,1820,,1800,1820,Ivory,"Oval, 2 5/8 x 2 1/8 in. (67 x 55 mm)","Bequest of Millie Bruhl Fredrick, 1962",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/436234,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -18.73,false,true,436242,European Paintings,"Painting, miniature",Margaret Rieche Richard,,,,,,Artist,,Dupuy,"German (?), active ca. 1801–17",,Dupuy,German (?),1801,1817,,1801,1817,Enamel,Diameter 2 1/8 in. (54 mm),"Bequest of Georgiana Emily Reynolds, 1918",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/436242,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -62.122.71,false,true,437278,European Paintings,"Painting, miniature",Portrait of a Woman,,,,,,Artist,Attributed to,Jean Petitot,"Swiss, Geneva 1607–1691 Geneva",,"Petitot, Jean",Swiss,1607,1691,,1627,1691,Vellum,"Oval, 1 3/8 x 1 1/8 in. (36 x 30 mm)","Bequest of Millie Bruhl Fredrick, 1962",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/437278,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -71.80,false,true,436995,European Paintings,Painting,Beggars at a Doorway,,,,,,Artist,,"Master of the Béguins, French or Flemish, active 1650–60 (possibly Abraham Willemsens, Flemish, active by 1627, died 1672)",,,"Master of the Béguins (possibly Abraham Willemsens, Flemish, act",French/Flemish,1627,1672,,1650,1660,Oil on canvas,20 1/4 x 23 3/8 in. (51.4 x 59.4 cm),"Purchase, 1871",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436995,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.145.14,false,true,437554,European Paintings,"Painting, icon",The Annunciation,,,,,,Artist,,"Russian Painter, second half 16th century",,,"Russian Painter, second half 16th century",Russian,1550,1599,,1550,1599,Tempera on wood,13 x 10 3/8 in. (33 x 26.4 cm),"Gift of Humanities Fund Inc., 1972",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437554,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -39.65.9,false,true,436874,European Paintings,Painting,"Barbara Villiers (1640–1709), Duchess of Cleveland",,,,,,Artist,Workshop of,Sir Peter Lely,"British, after 1670",,"Lely, Peter, Sir","Dutch, British",1618,1680,,1670,1727,Oil on canvas,89 x 54 in. (226.1 x 137.2 cm),"Bequest of Jacob Ruppert, 1939",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436874,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.11,false,true,436552,European Paintings,Painting,"María Luisa of Parma (1751–1819), Queen of Spain",,,,,,Artist,Copy after,Goya,"Spanish, after 1800",,Goya,Spanish,1746,1828,,1800,1907,Oil on canvas,43 1/2 x 33 1/2 in. (110.5 x 85.1 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436552,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -71.1,false,true,436048,European Paintings,Painting,The Meeting of Alexander the Great and Diogenes,,,,,,Artist,,Gaspar de Crayer,"Flemish, 1584–1669",,"Crayer, Gaspar de",Flemish,1584,1669,,1605,1669,Oil on canvas,"88 3/4 x 127 5/8 in. (225.4 x 324.2 cm), including added strips of 13 1/2 in. (34.3 cm) at left and 15 1/2 in. (39.4 cm) at right","Purchase, 1871",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436048,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -71.43,false,true,436424,European Paintings,"Painting, possibly an overdoor",A Basket and Birds,,,,,,Artist,,Jan Fyt,"Flemish, 1611–1661",,"Fyt, Jan",Flemish,1611,1661,,1631,1661,Oil on canvas,23 3/4 x 30 1/4 in. (60.3 x 76.8 cm),"Purchase, 1871",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436424,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -71.44,false,true,436425,European Paintings,"Painting, possibly an overdoor",A Hare and Birds,,,,,,Artist,,Jan Fyt,"Flemish, 1611–1661",,"Fyt, Jan",Flemish,1611,1661,,1631,1661,Oil on canvas,23 7/8 x 31 in. (60.6 x 78.7 cm),"Purchase, 1871",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436425,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -32.100.141,false,true,436426,European Paintings,Painting,"A Hare, Partridges, and Fruit",,,,,,Artist,,Jan Fyt,"Flemish, 1611–1661",,"Fyt, Jan",Flemish,1611,1661,,1611,1661,Oil on canvas,37 1/2 x 43 1/2 in. (95.3 x 110.5 cm),"The Friedsam Collection, Bequest of Michael Friedsam, 1931",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436426,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -71.12,false,true,437592,European Paintings,Painting,The Yard of the Inn at Emmaus,,,,,,Artist,,David Ryckaert III,"Flemish, 1612–1661",,"Ryckaert, David, III",Flemish,1612,1661,,1632,1661,Oil on canvas,35 5/8 x 45 3/8 in. (90.5 x 115.3 cm),"Purchase, 1871",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437592,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -71.13,false,true,437591,European Paintings,Painting,Rustic Interior,,,,,,Artist,,David Ryckaert III,"Flemish, 1612–1661",,"Ryckaert, David, III",Flemish,1612,1661,,1632,1661,Oil on canvas,36 3/8 x 45 5/8 in. (92.4 x 115.9 cm),"Purchase, 1871",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437591,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -71.118,false,true,436752,European Paintings,Painting,Pomegranates and Other Fruit in a Landscape,,,,,,Artist,,Abraham Brueghel,"Flemish, 1631–1697",,"Brueghel, Abraham",Flemish,1631,1697,,1650,1674,Oil on canvas,24 3/8 x 29 1/8 in. (61.9 x 74 cm),"Purchase, 1871",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436752,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -71.93,false,true,436118,European Paintings,Painting,The Forge,,,,,,Artist,,Léonard Defrance,"Flemish, 1735–1805",,"Defrance, Léonard",Flemish,1735,1805,,1755,1805,Oil on wood,12 5/8 x 16 1/2 in. (32.1 x 41.9 cm),"Purchase, 1871",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436118,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -71.105,false,true,436119,European Paintings,Painting,The Rope Dance,,,,,,Artist,,Léonard Defrance,"Flemish, 1735–1805",,"Defrance, Léonard",Flemish,1735,1805,,1755,1805,Oil on wood,19 7/8 x 28 5/8 in. (50.5 x 72.7 cm),"Purchase, 1871",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436119,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -32.75.3,false,true,437697,European Paintings,Painting,Soldiers Bivouacking,,,,,,Artist,,Pieter Snayers,"Flemish, 1592–?1667",,"Snayers, Pieter",Flemish,1592,1667,,1612,1667,Oil on wood,28 5/8 x 41 1/8 in. (72.7 x 104.5 cm),"The Collection of Giovanni P. Morosini, presented by his daughter Giulia, 1932",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437697,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -42.23.1,false,true,436265,European Paintings,"Painting, monochrome",Sir Peter Paul Rubens (1577–1640),,,,,,Artist,Copy after,Anthony van Dyck,"Flemish, 17th century",,"Dyck, Anthony van",Flemish,1599,1641,,1600,1699,Oil on wood,10 x 7 5/8 in. (25.4 x 19.4 cm),"Bequest of Bertha H. Buswell, 1941",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436265,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -32.100.2,false,true,435808,European Paintings,Painting,The Brawl,,,,,,Artist,Copy after,Adriaen Brouwer,"Flemish, 17th century",,"Brouwer, Adriaen",Flemish,1605,1638,,1600,1699,Oil on wood,9 5/8 x 7 1/2 in. (24.4 x 19.1 cm),"The Friedsam Collection, Bequest of Michael Friedsam, 1931",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435808,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -45.128.13,false,true,437766,European Paintings,Painting,Ferdinando II de' Medici (1610–1670) as a Boy,,,,,,Artist,Copy after,Justus Sustermans,"Flemish, 17th century",,"Sustermans, Justus",Flemish,1597,1681,,1600,1699,Oil on canvas,51 7/8 x 40 1/2 in. (131.8 x 102.9 cm),"Bequest of Helen Hay Whitney, 1944",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437766,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.12,false,true,436553,European Paintings,Painting,A City on a Rock,,,,,,Artist,Style of,Goya,"Spanish, 19th century",,Goya,Spanish,1746,1828,,1800,1899,Oil on canvas,33 x 41 in. (83.8 x 104.1 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436553,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -00.16,false,true,437539,European Paintings,Painting,Cambyses Appointing Otanes Judge,,,,,,Artist,Copy after,Peter Paul Rubens,probably 18th century,,"Rubens, Peter Paul",Flemish,1577,1640,,1700,1799,Oil on wood,18 x 17 1/2 in. (45.7 x 44.5 cm),"Gift of William E. Dodge, 1900",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437539,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1982.60.12,false,true,436992,European Paintings,Painting,The Sense of Sight,,,,,,Artist,,Juan Dò,"Spanish, 1604?–?1656",,"Dò, Juan",Spanish,1604,1656,,1625,1649,Oil on canvas,"Overall, with added strips, 29 7/8 x 24 7/8 in. (75.9 x 63.2 cm); without additions 27 3/4 x 21 3/4 in. (70.5 x 55.2 cm)","The Jack and Belle Linsky Collection, 1982",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436992,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -50.145.19,false,true,436549,European Paintings,Painting,Don Bernardo de Iriarte (1735–1814),,,,,,Artist,Copy after,Goya,"Spanish, 1797 or later",,Goya,Spanish,1746,1828,,1797,1797,Oil on canvas,42 1/2 x 33 1/2 in. (108 x 85.1 cm),"Bequest of Mary Stillman Harkness, 1950",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436549,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.100.18,false,true,437600,European Paintings,Painting,Portrait of a Woman,,,,,,Artist,,Alonzo Sánchez Coello,"Spanish, 1531/32–1588",,"Sánchez Coello, Alonzo",Spanish,1531,1588,,1551,1588,Oil on canvas,38 3/4 x 28 3/8 in. (98.4 x 72.1 cm),"Bequest of Harry G. Sperling, 1971",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437600,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.79.17,false,true,437100,European Paintings,"Painting, icon",The Dormition of the Virgin,,,,,,Artist,,Ioannes Mokos,"Greek, active 1680–1724",,"Mokos, Ioannes",Greek,1680,1724,,1680,1724,"Tempera and oil on wood, gold ground",13 1/2 x 11 1/4 in. (34.3 x 28.6 cm),"Gift of Mrs. Henry Morgenthau, 1933",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437100,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1983.498,false,true,436925,European Paintings,Painting,"Laure de Sade, Comtesse Adhéaume de Chevigné",,,,,,Artist,,Federico de Madrazo y de Ochoa,"Spanish, Paris 1875–1934",,"Madrazo y de Ochoa, Federico de",Spanish,1875,1934,,1895,1934,Oil on canvas,45 1/2 x 29 in. (115.6 x 73.7 cm),"Anonymous Gift, 1983",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436925,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.158.746,false,true,437855,European Paintings,"Painting, icon",Christ Bearing the Cross,,,,,,Artist,,Nicolaos Tzafouris,"Greek, ca. 1455–1500/1501",,"Tzafouris, Nicolaos",Greek,1455,1501,,1489,1500,"Oil and tempera on wood, gold ground",27 1/4 x 21 1/2 in. (69.2 x 54.6 cm),"Bashford Dean Memorial Collection, Funds from various donors, 1929",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437855,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1981.365.1,false,true,436990,European Paintings,Painting,The Martyrdom of Saint Lawrence; (reverse) Giving Drink to the Thirsty,,,,,,Artist,,Master of the Acts of Mercy,"Austrian, Salzburg, ca. 1465",,Master of the Acts of Mercy,Austrian,1460,1470,,1460,1470,"Oil on fir, (obverse) gold ground",Painted surface 29 x 18 3/8 in. (73.7 x 46.7 cm),"Gift of The Jack and Belle Linsky Foundation, 1981",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436990,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -71.32,false,true,437817,European Paintings,Painting,Group Portrait: A Wedding Celebration,,,,,,Artist,,Gillis van Tilborgh,"Flemish, ca. 1625–ca. 1678",,"Tilborgh, Gillis van",Flemish,1625,1678,,1645,1678,Oil on canvas,45 1/2 x 63 1/4 in. (115.6 x 160.7 cm),"Purchase, 1871",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437817,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -88.3.82,false,true,437232,European Paintings,Painting,Saint Anne Enthroned with the Virgin and Child,,,,,,Artist,,Osma Master,"Spanish, Castilian, ca. 1500",,Osma Master,Spanish,1500,1500,,1495,1505,Tempera and gold on wood,59 x 32 in. (149.9 x 81.3 cm),"Gift of Coudert Brothers, 1888",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437232,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -58.145.1,false,true,435822,European Paintings,Painting,The Annunciation,,,,,,Artist,,Budapest Master,"Spanish, Castilian, ca. 1500",,Budapest Master,Spanish,1500,1500,,1495,1505,"Oil and gold on canvas, transferred from wood",32 x 20 1/4 in. (81.3 x 51.4 cm),"Bequest of Muriel Stokes, 1958",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435822,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -30.95.243,false,true,436550,European Paintings,Painting,Infanta María Luisa (1782–1824) and Her Son Carlos Luis (1799–1883),,,,,,Artist,Copy after,Goya,"Spanish, 1800 or shortly after",,Goya,Spanish,1746,1828,,1800,1805,Oil on canvas,39 1/8 x 27 in. (99.4 x 68.6 cm),"Theodore M. Davis Collection, Bequest of Theodore M. Davis, 1915",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436550,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -39.65.3,false,true,436871,European Paintings,Painting,"Mary Capel (1630–1715), Later Duchess of Beaufort, and Her Sister Elizabeth (1633–1678), Countess of Carnarvon",,,,,,Artist,,Sir Peter Lely (Pieter van der Faes),"British, Soest 1618–1680 London",,"Lely, Peter, Sir (Pieter van der Faes)","Dutch, British",1618,1680,,1652,1662,Oil on canvas,51 1/4 x 67 in. (130.2 x 170.2 cm),"Bequest of Jacob Ruppert, 1939",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436871,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -39.65.6,false,true,436873,European Paintings,Painting,Sir Henry Capel (1638–1696),,,,,,Artist,,Sir Peter Lely (Pieter van der Faes),"British, Soest 1618–1680 London",,"Lely, Peter, Sir (Pieter van der Faes)","Dutch, British",1618,1680,,1654,1664,Oil on canvas,49 3/4 x 40 1/2 in. (126.4 x 102.9 cm),"Bequest of Jacob Ruppert, 1939",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436873,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.79.14,false,true,437856,European Paintings,"Painting, icon",Head of Christ,,,,,,Artist,,Emmanuel Tzanès,"Greek, active by 1636–died 1690",,"Tzanès, Emmanuel",Greek,1636,1690,,1636,1690,"Tempera on wood, gold ground",8 3/8 x 7 1/8 in. (21.3 x 18.1 cm),"Gift of Mrs. Henry Morgenthau, 1933",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437856,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.79.15,false,true,437858,European Paintings,Painting,Head of the Virgin,,,,,,Artist,,Emmanuel Tzanès,"Greek, active by 1636–died 1690",,"Tzanès, Emmanuel",Greek,1636,1690,,1636,1690,"Tempera on wood, gold ground",8 3/8 x 7 1/8 in. (21.3 x 18.1 cm),"Gift of Mrs. Henry Morgenthau, 1933",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437858,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.79.18,false,true,437857,European Paintings,Painting,Head of Saint John the Baptist,,,,,,Artist,,Emmanuel Tzanès,"Greek, active by 1636–died 1690",,"Tzanès, Emmanuel",Greek,1636,1690,,1636,1690,"Tempera on wood, gold ground",8 3/8 x 7 1/8 in. (21.3 x 18.1 cm),"Gift of Mrs. Henry Morgenthau, 1933",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437857,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -15.30.71,false,true,437461,European Paintings,Painting,On the Seine,,,,,,Artist,,Martín Rico y Ortega,"Spanish, Madrid 1833–1908 Venice",,"Rico y Ortega, Martín",Spanish,1833,1908,,1853,1908,Oil on canvas,15 1/4 x 25 1/2 in. (38.7 x 64.8 cm),"Bequest of Maria DeWitt Jesup, from the collection of her husband, Morris K. Jesup, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437461,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -81.1.666,false,true,437462,European Paintings,Painting,A Spanish Garden,,,,,,Artist,,Martín Rico y Ortega,"Spanish, Madrid 1833–1908 Venice",,"Rico y Ortega, Martín",Spanish,1833,1908,,1853,1908,Oil on canvas,24 x 15 1/4 in. (61 x 38.7 cm),"Bequest of Stephen Whitney Phoenix, 1881",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437462,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -87.15.58,false,true,437045,European Paintings,Painting,The Last Token: A Christian Martyr,,,,,,Artist,,Gabriel Max,"Austrian, Prague 1840–1915 Munich",,"Max, Gabriel",Austrian,1840,1915,,1860,1915,Oil on canvas,67 1/2 x 47 in. (171.5 x 119.4 cm),"Catharine Lorillard Wolfe Collection, Bequest of Catharine Lorillard Wolfe, 1887",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437045,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -23.103.3,false,true,437279,European Paintings,Painting,Market Scene,,,,,,Artist,,August Xaver Karl von Pettenkofen,"Austrian, Vienna 1821–1889 Vienna",,"Pettenkofen, August Xaver Karl von",Austrian,1821,1889,,1841,1889,Oil on wood,4 x 8 1/2 in. (10.2 x 21.6 cm),"The John Hobart Warren Bequest, 1923",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437279,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -71.96,false,true,437074,European Paintings,Painting,A Cavalry Engagement,,,,,,Artist,,Adam Frans van der Meulen,"Flemish, Brussels 1632–1690 Paris",,"Meulen, Adam Frans van der",Flemish,1632,1690,,1652,1690,Oil on wood,8 5/8 x 12 1/2 in. (21.9 x 31.8 cm),"Purchase, 1871",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437074,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -91.26.4,false,true,437543,European Paintings,Painting,Susanna and the Elders,,,,,,Artist,Workshop of,Peter Paul Rubens,"Flemish, Siegen 1577–1640 Antwerp",,"Rubens, Peter Paul",Flemish,1577,1640,,1597,1640,Oil on wood,18 1/4 x 25 3/8 in. (46.4 x 64.5 cm),"Marquand Collection, Gift of Henry G. Marquand, 1890",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437543,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -17.190.19,false,true,437542,European Paintings,Painting,Saint Teresa of Ávila Interceding for Souls in Purgatory,,,,,,Artist,Workshop of,Peter Paul Rubens,"Flemish, Siegen 1577–1640 Antwerp",,"Rubens, Peter Paul",Flemish,1577,1640,,1597,1640,Oil on wood,25 1/4 x 19 1/4 in. (64.1 x 48.9 cm),"Gift of J. Pierpont Morgan, 1917",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437542,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -32.100.37,false,true,437540,European Paintings,Painting,Frans Francken I (1542–1616),,,,,,Artist,Workshop of,Peter Paul Rubens,"Flemish, Siegen 1577–1640 Antwerp",,"Rubens, Peter Paul",Flemish,1577,1640,,1597,1640,Oil on wood,25 1/4 x 19 1/8 in. (64.1 x 48.6 cm),"The Friedsam Collection, Bequest of Michael Friedsam, 1931",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437540,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -32.100.42,false,true,437544,European Paintings,Painting,Virgin and Child,,,,,,Artist,Workshop of,Peter Paul Rubens,"Flemish, Siegen 1577–1640 Antwerp",,"Rubens, Peter Paul",Flemish,1577,1640,,1597,1640,Oil on wood,39 3/4 x 30 3/8 in. (101 x 77.2 cm),"The Friedsam Collection, Bequest of Michael Friedsam, 1931",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437544,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.162.2,false,true,436971,European Paintings,Painting,The Flood Gate,,,,,,Artist,,Émile van Marcke,"French, Sèvres 1827–1890 Hyères",,"Marcke, Émile van",Belgian,1827,1890,,1847,1890,Oil on canvas,24 1/2 x 32 1/2 in. (62.2 x 82.6 cm),"Bequest of Susan P. Colgate, in memory of her husband, Romulus R. Colgate, 1936",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436971,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -89.15.18,false,true,437872,European Paintings,Painting,"Mariana of Austria (1634–1696), Queen of Spain",,,,,,Artist,Workshop of,Velázquez,"Spanish, Seville 1599–1660 Madrid",,Velázquez,Spanish,1599,1660,,1619,1660,Oil on canvas,32 1/4 x 39 1/2 in. (81.9 x 100.3 cm),"Marquand Collection, Gift of Henry G. Marquand, 1889",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437872,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -57.104,false,true,437861,European Paintings,Painting,Portrait of a Boy with a Falcon,,,,,,Artist,,Wallerant Vaillant,"Flemish, Lille 1623–1677 Amsterdam",,"Vaillant, Wallerant",Flemish,1623,1677,,1643,1677,Oil on canvas,29 3/4 x 25 in. (75.6 x 63.5 cm),"Purchase, George T. Delacorte Jr. Gift, 1957",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437861,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -07.225.306ab,false,true,437620,European Paintings,"Painting, grisaille",Nymph and Putti in a Vintage Scene; Nymph with a Wreath and Putti with Garlands of Flowers,,,,,,Artist,,Piat Joseph Sauvage,"Flemish, Tournai 1744–1818 Tournai",,"Sauvage, Piat Joseph",Flemish,1744,1818,,1763,1818,Oil on slate,(a) 9 1/2 x 23 1/4 in. (24.1 x 59.1 cm); (b) 9 5/8 x 23 3/4 in. (24.4 x 60.3 cm),"Gift of J. Pierpont Morgan, 1906",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437620,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -87.15.133,false,true,436937,European Paintings,Painting,The Dream after the Ball,,,,,,Artist,,Hans Makart,"Austrian, Salzburg 1840–1884 Vienna",,"Makart, Hans",Austrian,1840,1884,,1860,1884,Oil on canvas,62 3/8 x 37 1/4 in. (158.4 x 94.6 cm),"Catharine Lorillard Wolfe Collection, Bequest of Catharine Lorillard Wolfe, 1887",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436937,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -22.47.3,false,true,437781,European Paintings,Painting,Landscape with Thatched Cottages,,,,,,Artist,Workshop of,David Teniers the Younger,"Flemish, Antwerp 1610–1690 Brussels",,"Teniers, David, the Younger",Flemish,1610,1690,,1630,1690,Oil on wood,5 3/4 x 7 3/4 in. (14.6 x 19.7 cm),"Bequest of John Henry Abegg, 1921",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437781,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -89.15.22,false,true,437780,European Paintings,Painting,Shepherds and Sheep,,,,,,Artist,,David Teniers the Younger,"Flemish, Antwerp 1610–1690 Brussels",,"Teniers, David, the Younger",Flemish,1610,1690,,1630,1690,Oil on wood,6 5/8 x 9 in. (16.8 x 22.9 cm),"Marquand Collection, Gift of Henry G. Marquand, 1889",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437780,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -89.15.25,false,true,437776,European Paintings,Painting,The Good Samaritan,,,,,,Artist,,David Teniers the Younger,"Flemish, Antwerp 1610–1690 Brussels",,"Teniers, David, the Younger",Flemish,1610,1690,,1630,1690,Oil on wood,6 3/4 x 9 in. (17.1 x 22.9 cm),"Marquand Collection, Gift of Henry G. Marquand, 1889",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437776,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -71.46,false,true,437918,European Paintings,Painting,Portrait of a Young Woman,,,,,,Artist,,Cornelis de Vos,"Flemish, Hulst 1584/85–1651 Antwerp",,"Vos, Cornelis de",Flemish,1584,1651,,1603,1651,Oil on canvas,"46 1/2 x 37 1/4 in. (118.1 x 94.6 cm), including added strip of 2 3/4 in. (7 cm) at top","Purchase, 1871",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437918,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -89.15.37,false,true,437917,European Paintings,Painting,Portrait of a Woman,,,,,,Artist,,Cornelis de Vos,"Flemish, Hulst 1584/85–1651 Antwerp",,"Vos, Cornelis de",Flemish,1584,1651,,1603,1651,Oil on wood,49 3/8 x 38 in. (125.4 x 96.5 cm),"Marquand Collection, Gift of Henry G. Marquand, 1889",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437917,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -41.190.28a–d,false,true,437152,European Paintings,"Painting, retable",Virgin and Child Enthroned with Scenes from the Life of the Virgin,,,,,,Artist,,Morata Master,"Spanish, Aragonese, late 15th century",,Morata Master,Spanish,1470,1499,,1470,1499,Tempera and gold on wood,"Central panel, below, 52 x 34 5/8 in. (132.1 x 87.9 cm); central panel, above, 43 3/4 x 34 1/2 in. (111.1 x 87.6 cm); each side panel 84 1/2 x 22 3/4 in. (214.6 x 57.8 cm)","Bequest of George Blumenthal, 1941",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437152,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -56.224.1,false,true,436077,European Paintings,Painting,Portrait of a Woman,,,,,,Artist,,Michael Dahl,"Swedish, Stockholm 1659–1743 London",,"Dahl, Michael",Swedish,1659,1743,,1696,1743,Oil on canvas,77 1/4 x 51 3/4 in. (196.2 x 131.4 cm),"Gift of Margaret Bruguière, in memory of Louis Bruguière, 1956",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436077,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -81.1.662,false,true,436829,European Paintings,Painting,Hugo van der Goes Painting the Portrait of Mary of Burgundy,,,,,,Artist,,Guillaume Koller,"Belgian, Vienna 1829–1884 near Nancy",,"Koller, Guillaume",Belgian,1829,1884,,1849,1884,Oil on wood,23 3/8 x 34 in. (59.4 x 86.4 cm),"Bequest of Stephen Whitney Phoenix, 1881",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436829,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -87.15.61,false,true,437637,European Paintings,Painting,Lost: Souvenir of Auvergne,,,,,,Artist,,August Friedrich Albrecht Schenck,"Danish, Glückstadt 1828–1901 Ecouen",,"Schenck, August Friedrich Albrecht",Danish,1828,1901,,1848,1901,Oil on canvas,58 x 97 3/4 in. (147.3 x 248.3 cm),"Catharine Lorillard Wolfe Collection, Bequest of Catharine Lorillard Wolfe, 1887",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437637,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -32.100.3,false,true,435805,European Paintings,Painting,A Peasant with a Bird,,,,,,Artist,,Adriaen Brouwer,"Flemish, Oudenaarde 1605/6–1638 Antwerp",,"Brouwer, Adriaen",Flemish,1605,1638,,1626,1638,Oil on wood,Oval 7 1/8 x 5 1/2 in. (18.1 x 14 cm); set in rectangular panel 8 x 6 1/4 in. (20.3 x 15.9 cm),"The Friedsam Collection, Bequest of Michael Friedsam, 1931",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435805,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -51.70,false,true,436551,European Paintings,Painting,"Ferdinand VII (1784–1833), When Prince of Asturias",,,,,,Artist,,Goya,"Spanish, Fuendetodos 1746–1828 Bordeaux",and Workshop,Goya,Spanish,1746,1828,,1800,1805,Oil on canvas,32 3/4 x 26 1/4 in. (83.2 x 66.7 cm),"Gift of René Fribourg, 1951",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436551,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -22.181,false,true,436554,European Paintings,Painting,Bullfight in a Divided Ring,,,,,,Artist,Attributed to,Goya (Francisco de Goya y Lucientes),"Spanish, Fuendetodos 1746–1828 Bordeaux",,Goya (Francisco de Goya y Lucientes),Spanish,1746,1828,,1800,1829,Oil on canvas,38 3/4 x 49 3/4 in. (98.4 x 126.4 cm),"Catharine Lorillard Wolfe Collection, Wolfe Fund, 1922",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436554,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.180,false,true,436547,European Paintings,Painting,Narcisa Barañana de Goicoechea,,,,,,Artist,Attributed to,Goya (Francisco de Goya y Lucientes),"Spanish, Fuendetodos 1746–1828 Bordeaux",,Goya (Francisco de Goya y Lucientes),Spanish,1746,1828,,1766,1828,Oil on canvas,44 1/4 x 30 3/4 in. (112.4 x 78.1 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436547,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -65.220.2,false,true,437972,European Paintings,Painting,The Crucifixion,,,,,,Artist,Workshop of,Francisco de Zurbarán,"Spanish, Fuente de Cantos 1598–1664 Madrid",,"Zurbarán, Francisco de",Spanish,1598,1664,,1618,1664,Oil on canvas,"Arched top, 112 x 75 7/8 in. (284.5 x 192.7 cm)","Gift of George R. Hann, 1965",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437972,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.14,false,true,436198,European Paintings,Painting,Saint Cecilia,,,,,,Artist,,Abraham van Diepenbeeck,"Flemish, 's Hertogenbosch 1596–1675 Antwerp",,"Diepenbeeck, Abraham van",Flemish,1596,1675,,1616,1675,Oil on canvas,47 7/8 x 40 3/4 in. (121.6 x 103.5 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436198,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -58.145.2,false,true,435847,European Paintings,Painting,"Saint Vincent, Patron Saint of Lisbon",,,,,,Artist,,Frei Carlos,"Portuguese, active second quarter 16th century",,"Carlos, Frei",Portuguese,1525,1549,,1525,1549,Oil on wood,64 x 20 7/8 in. (162.6 x 53 cm),"Bequest of Muriel Stokes, 1958",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435847,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.280.6,false,true,437270,European Paintings,Painting,Head of a Man,,,,,,Artist,,Vasilii Grigorievich Perov,"Russian, Tobolsk 1834–1882 Kuz'minki, Moscow",,"Perov, Vasilii Grigorievich",Russian,1834,1882,,1854,1882,Oil on canvas board,20 7/8 x 13 7/8 in. (53 x 35.2 cm),"Bequest of Mary Jane Dastich, in memory of her husband, General Frank Dastich, 1975",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437270,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -53.180,false,true,437360,European Paintings,Painting,"Lady Maitland (Catherine Connor, died 1865)",,,,,,Artist,,Sir Henry Raeburn,"British, Stockbridge, Scotland 1756–1823 Edinburgh, Scotland",,"Raeburn, Henry, Sir","British, Scottish",1756,1923,,1776,1823,Oil on canvas,49 3/4 x 39 3/4 in. (126.4 x 101 cm),"Gift of Jessie Woolworth Donahue, 1953",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437360,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -46.13.5,false,true,437358,European Paintings,Painting,Janet Law,,,,,,Artist,,Sir Henry Raeburn,"British, Stockbridge, Scotland 1756–1823 Edinburgh, Scotland",,"Raeburn, Henry, Sir","British, Scottish",1756,1923,,1776,1823,Oil on canvas,35 1/4 x 27 1/4 in. (89.5 x 69.2 cm),"Bequest of Helen Swift Neilson, 1945",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437358,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -60.94.1,false,true,437356,European Paintings,Painting,Alexander Maconochie (1777–1861) of Meadowbank,,,,,,Artist,,Sir Henry Raeburn,"British, Stockbridge, Scotland 1756–1823 Edinburgh, Scotland",,"Raeburn, Henry, Sir","British, Scottish",1756,1923,,1776,1823,Oil on canvas,30 1/4 x 25 in. (76.8 x 63.5 cm),"Gift of William P. Clyde, 1960",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437356,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -60.71.13,false,true,437359,European Paintings,Painting,John Gray (1731–1811) of Newholm,,,,,,Artist,,Sir Henry Raeburn,"British, Stockbridge, Scotland 1756–1823 Edinburgh, Scotland",,"Raeburn, Henry, Sir","British, Scottish",1756,1923,,1776,1823,Oil on canvas,49 3/8 x 40 in. (125.4 x 101.6 cm),"Bequest of Lillian S. Timken, 1959",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437359,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -65.181.13,false,true,437357,European Paintings,Painting,James Johnston of Straiton (died 1841),,,,,,Artist,,Sir Henry Raeburn,"British, Stockbridge, Scotland 1756–1823 Edinburgh, Scotland",,"Raeburn, Henry, Sir","British, Scottish",1756,1923,,1776,1823,Oil on canvas,35 1/4 x 27 1/4 in. (89.5 x 69.2 cm),"Bequest of Adele L. Lehman, in memory of Arthur Lehman, 1965",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437357,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.339.2,false,true,437755,European Paintings,Painting,In the Studio,,,,,,Artist,,Alfred Stevens,"Belgian, Brussels 1823–1906 Paris",,"Stevens, Alfred",Belgian,1823,1906,1888,1888,1888,Oil on canvas,42 x 53 1/2 in. (106.7 x 135.9 cm),"Gift of Mrs. Charles Wrightsman, 1986",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437755,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -30.95.241,false,true,435590,European Paintings,Painting,Family Group in a Landscape,,,,,,Artist|Artist,and,Jacques d'Arthois|Flemish Painter,"Flemish, 1613–ca. 1686|ca. 1645",,"Arthois, Jacques d'|Flemish Painter",Flemish|Flemish,1613,1686,ca. 1645,1640,1650,Oil on canvas,49 1/8 x 60 1/8 in. (124.8 x 152.7 cm),"Theodore M. Davis Collection, Bequest of Theodore M. Davis, 1915",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435590,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -71.50,false,true,437189,European Paintings,Painting,Interior of a Gothic Church at Night,,,,,,Artist|Artist,and,Pieter Neeffs the Younger|Frans Francken III,"Flemish, 1620–after 1675|Flemish, 1607–1667",,"Neeffs, Pieter, the Younger|Francken, Frans, III",Flemish|Flemish,1620 |1607,1675 |1667,ca. 1660,1655,1665,Oil on wood,10 x 7 3/4 in. (25.4 x 19.7 cm),"Purchase, 1871",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437189,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -30.58.20,false,true,437187,European Paintings,Painting,Interior of a Gothic Church by Day,,,,,,Artist|Artist,and,Frans Francken III|Pieter Neeffs the Elder,"Flemish, 1607–1667|Flemish, active 1605–1656/61",,"Francken, Frans, III|Neeffs, Pieter, the Elder",Flemish|Flemish,1607 |1605,1667 |1661,probably ca. 1635–40,1635,1640,Oil on copper,5 1/8 x 6 1/2 in. (13 x 16.5 cm),"Bequest of Edward C. Post, 1915",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437187,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -30.58.21,false,true,437188,European Paintings,Painting,Interior of a Gothic Church at Night,,,,,,Artist|Artist,and,Pieter Neeffs the Elder|Frans Francken III,"Flemish, active 1605–1656/61|Flemish, 1607–1667",,"Neeffs, Pieter, the Elder|Francken, Frans, III",Flemish|Flemish,1605 |1607,1661 |1667,probably ca. 1635–40,1635,1640,Oil on copper,5 1/8 x 6 1/2 in. (13 x 16.5 cm),"Bequest of Edward C. Post, 1915",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437188,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -32.100.52,false,true,436987,European Paintings,Painting,The Rest on the Flight into Egypt,,,,,,Artist|Artist,and|Follower of,Master of the Liège Disciples at Emmaus|Quentin Metsys,"Netherlandish, active mid-16th century|Netherlandish, mid-16th century",,"Master of the Liège Disciples at Emmaus|Metsys, Quentin",Netherlandish|Netherlandish,1530 |1466,1569 |1530,ca. 1540,1535,1545,Oil on wood,37 1/2 x 30 1/4 in. (95.3 x 76.8 cm),"The Friedsam Collection, Bequest of Michael Friedsam, 1931",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436987,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -52.125,false,true,437868,European Paintings,Painting,"Don Gaspar de Guzmán (1587–1645), Count-Duke of Olivares",,,,,,Artist|Artist,Attributed to|and/or,Velázquez (Diego Rodríguez de Silva y Velázquez)|Juan Bautista Martínez del Mazo,"Spanish, Seville 1599–1660 Madrid|Spanish, Cuenca ca. 1612–1667 Madrid",,"Velázquez (Diego Rodríguez de Silva y Velázquez)|Mazo, Juan Bautista Martínez del",Spanish|Spanish,1599 |1612,1660 |1667,ca. 1635,1630,1640,Oil on canvas,50 1/4 x 41 in. (127.6 x 104.1 cm),"Fletcher Fund, 1952",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437868,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -"2011.485a, b",false,true,441672,European Paintings,"Painting, from the wing of an altarpiece",The Dormition of the Virgin; (reverse) Christ Carrying the Cross,,,,,,Artist|Artist,and Attributed to the,Hans Schäufelein|Master of Engerda,"German, Nuremberg ca. 1480–ca. 1540 Nördlingen|German, active ca. 1510–20",,"Schäufelein, Hans|Master of Engerda",German|German,1480 |1510,1540 |1520,ca. 1510,1505,1515,Oil and gold on fir,55 x 53 1/8 in. (139.7 x 134.9 cm),"Purchase, Lila Acheson Wallace, Karen and Mo Zukerman, Kowitz Family Foundation, Anonymous, and Hester Diamond Gifts, 2011",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/441672,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -45.141,false,true,437525,European Paintings,Painting,The Feast of Acheloüs,,,,,,Artist|Artist,and,Peter Paul Rubens|Jan Brueghel the Elder,"Flemish, Siegen 1577–1640 Antwerp|Netherlandish, Brussels 1568–1625 Antwerp",,"Rubens, Peter Paul|Brueghel, Jan, the Elder",Flemish|Netherlandish,1577 |1568,1640 |1625,ca. 1615,1610,1620,Oil on wood,42 1/2 x 64 1/2 in. (108 x 163.8 cm),"Gift of Alvin and Irwin Untermyer, in memory of their parents, 1945",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437525,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.179,false,true,437843,European Paintings,Painting,Portrait of a Man,,,,,,Artist|Artist,Attributed to|Fraudulent Imitation of,Gaspare Traversi|Goya,"Italian, Neapolitan, ca. 1722–1770",,"Traversi, Gaspare|Goya",Italian|Spanish,1722 |1746,1770 |1828,,1742,1770,Oil on canvas,22 x 17 1/2 in. (55.9 x 44.5 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437843,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -06.1039,false,true,437919,European Paintings,Painting,Two Tritons at the Feast of Acheloüs,,,,,,Artist|Artist,Attributed to|and Attributed to,Cornelis de Vos|Frans Snyders,"Flemish, Hulst 1584/85–1651 Antwerp|Flemish, Antwerp 1579–1657 Antwerp",,"Vos, Cornelis de|Snyders, Frans",Flemish|Flemish,1584 |1579,1651 |1657,,1603,1651,Oil on canvas,62 3/4 x 45 7/8 in. (159.4 x 116.5 cm),"Marquand Fund, 1906",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437919,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -39.143,false,true,437324,European Paintings,Painting,Portrait of a Young Woman,,,,,,Artist|Artist,?,Pieter Jansz. Pourbus|Pieter Jansz. Pourbus,"Netherlandish, Gouda? 1524–1584 Bruges|Netherlandish, Gouda? 1524–1584 Bruges",,"Pourbus, Pieter Jansz.|Pourbus, Pieter Jansz.",Netherlandish|Netherlandish,1524 |1524,1584 |1584,,1544,1584,Oil on wood,15 1/2 x 12 1/2 in. (39.4 x 31.8 cm),"Charles B. Curtis Fund, 1939",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437324,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2016.392,false,true,726543,European Paintings,Painting,Monsignor Giuseppe Spina (1756–1828),,,,,,Artist,,Angelica Kauffmann,"Swiss, Chur 1741–1807 Rome",,"Kauffmann, Angelica",,1741,1807,1798,1798,1798,Oil on canvas,37 5/8 × 31 1/2 in. (95.5 × 80 cm),"Gift of Carlo Orsi, 2016",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/726543,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.438,false,true,678013,European Paintings,Painting,Tiger in Repose,,,,,,Artist,,Antoine-Louis Barye,"French, Paris 1796–1875 Paris",,"Barye, Antoine-Louis",,1796,1875,ca. 1850–65,1845,1870,Oil on canvas,10 3/4 × 14 in. (27.3 × 35.6 cm),"Gift of Eugene V. Thaw, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/678013,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2016.306,false,true,712013,European Paintings,Painting,"Grape Vines and Fruit, with Three Wagtails",,,,,,Artist,,Bartolomeo Cavarozzi,"Italian, Viterbo 1587–1625 Rome",,"Cavarozzi, Bartolomeo",,1587,1625,ca. 1615–18,1615,1618,Oil on canvas,40 × 61 3/4 in. (101.6 × 156.8 cm),"Gift of Claire and Giovanni Sarti, in honor of Keith Christiansen, 2016",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/712013,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.645,false,true,702752,European Paintings,Painting,Christ and the Woman of Samaria,,,,,,Artist,,Benedetto Luti,"Italian, Florence 1666–1724 Rome",,"Luti, Benedetto",,1666,1724,1715–20,1715,1720,Oil on copper,15 × 12 1/8 in. (38.2 × 30.9 cm),"Purchase, Rogers Fund, by exchange, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/702752,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2016.485,false,true,712946,European Paintings,Painting,The Calling of Saint Matthew,,,,,,Artist,,Giovanni Battista Caracciolo,"Italian, Naples 1578–1635 Naples",,"Caracciolo, Giovanni Battista",,1578,1635,ca. 1625–30,1625,1630,Oil on canvas,51 3/8 × 61 1/2 in. (130.5 × 156.2 cm),"Purchase, The Morris and Alma Schapiro Fund Gift, 2016",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/712946,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2016.653,false,true,695496,European Paintings,Painting,Bust of a Man (Saint Matthias?),,,,,,Artist,,Giovanni Battista Piazzetta,"Italian, Venice 1682–1754 Venice",,"Piazzetta, Giovanni Battista",,1682,1754,ca. 1715–20,1715,1720,Oil on canvas,17 1/2 × 14 3/4 in. (44.5 × 37.5 cm),"Gift of Dianne Modestini and Eugene V. Thaw, 2016",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/695496,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -07.225.314a,false,true,437625,European Paintings,"Painting, grisaille overdoor",The Triumph of Bacchus,,,,,,Artist,,Piat Joseph Sauvage,"Flemish, Tournai 1744–1818 Tournai",,"Sauvage, Piat Joseph",,1744,1818,1780s,1780,1789,Oil on canvas,14 x 32 7/8 in. (35.6 x 83.5 cm),"Gift of J. Pierpont Morgan, 1906",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437625,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -07.225.314b,false,true,437618,European Paintings,"Painting, grisaille",Infant Bacchanal,,,,,,Artist,,Piat Joseph Sauvage,"Flemish, Tournai 1744–1818 Tournai",,"Sauvage, Piat Joseph",,1744,1818,1780s,1780,1789,Oil on canvas,10 1/4 x 29 5/8 in. (26 x 75.2 cm),"Gift of J. Pierpont Morgan, 1906",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437618,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.761,false,true,440900,European Paintings,Painting,Saint Dominic in Penitence,,,,,,Artist,,Filippo Tarchiani,"Italian, Castello 1576–1645 Florence",,"Tarchiani, Filippo",,1576,1645,ca. 1607,1602,1612,Oil on canvas,52 x 43 in. (132.1 x 109.2 cm),"Gift of Brian J. Brille, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/440900,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2016.507,false,true,712539,European Paintings,Painting,Saint Philip Neri (1515–1595),,,,,,Artist,,Carlo Dolci,"Italian, Florence 1616–1687 Florence",,"Dolci, Carlo",,1616,1687,1645 or 1646,1645,1646,Oil on canvas,17 1/4 × 14 1/4 in. (43.8 × 36.2 cm),"Purchase, George Delacorte Fund Gift, in memory of George T. Delacorte Jr., Ronald S. Lauder, Mr. and Mrs. Richard L. Chilton Jr., and Mr. and Mrs. Frederick W. Beinecke Gifts, 2016",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/712539,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.263,false,true,679686,European Paintings,Painting,Hortensia,,,,,,Artist,,Fernand Khnopff,"Belgian, Grembergen 1858–1921 Brussels",,"Khnopff, Fernand",,1858,1921,1884,1884,1884,Oil on canvas,18 13/16 × 23 1/2 in. (47.8 × 59.7 cm),"Purchase, Bequest of Julia W. Emmons, by exchange, and Catharine Lorillard Wolfe Collection, Wolfe Fund, and Gift of Charles Hack and the Hearn Family Trust, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/679686,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -32.100.55,false,true,435760,European Paintings,Painting,The Man of Sorrows,,,,,,Artist,Workshop of,Aelbert Bouts,"Netherlandish, Leuven ca. 1451/54–1549",,"Bouts, Aelbert",,1451,1549,ca. 1525,1520,1530,Oil on oak,"Arched top, 17 1/2 x 11 1/4 in. (44.5 x 28.6 cm)","The Friedsam Collection, Bequest of Michael Friedsam, 1931",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435760,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2015.398,false,true,687513,European Paintings,Painting,The Lamentation,,,,,,Artist,,Luis de Morales,"Spanish, Plasencia (?) 1510/11–1586 Alcántara",,"Morales, Luis de",,1510,1586,ca. 1560,1555,1565,Oil on walnut,35 × 24 5/8 in. (89 × 62.5 cm),"Purchase, Alejandro Santo Domingo and Annette de la Renta Gifts; Bequests of George D. Pratt and of Annette B. McFadden, and Gifts of Estate of George Quackenbush, in his memory, of Dr. and Mrs. Max A. Goldzieher, of Francis Neilson, of Dr. Foo Chu and Dr. Marguerite Hainje-Chu, of Mr. and Mrs. Harold H. Burns, and of Mr. and Mrs. Joshua Logan, and other gifts and bequests, by exchange; Victor Wilbour Fund; and Hester Diamond Gift, 2015",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/687513,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2016.39,false,true,439977,European Paintings,Painting,Christ Carrying the Cross,,,,,,Artist,,Jan Gossart (called Mabuse),"Netherlandish, Maubeuge ca. 1478–1532 Antwerp (?)",,"Gossart, Jan (called Mabuse)",,1478,1532,ca. 1520–25,1520,1525,Oil on oak,9 7/8 × 7 1/2 in. (25.1 × 19 cm),"Gift of Honorable J. William Middendorf II, and Purchase, Walter and Leonore Annenberg and The Annenberg Foundation Gift, Director's Fund, Gift of George A. Hearn, by exchange, and Marquand and The Alfred N. Punnett Endowment Funds, 2016",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/439977,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2016.63,false,true,670765,European Paintings,Painting,The Death of Cleopatra,,,,,,Artist,,Guido Cagnacci,"Italian, Santarcangelo di Romagna 1601–1663 Vienna",,"Cagnacci, Guido",,1601,1663,ca. 1645–55,1645,1655,Oil on canvas,37 3/8 × 29 1/2 in. (95 × 75 cm),"Purchase, Diane Burke Gift, Gift of J. Pierpont Morgan, by exchange, Friends of European Paintings Gifts, Gwynne Andrews Fund, Lila Acheson Wallace, Charles and Jessie Price, and Álvaro Saieh Bendeck Gifts, Gift and Bequest of George Blumenthal and Fletcher Fund, by exchange, and Michel David-Weill Gift, 2016",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/670765,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -57.54,false,true,436983,European Paintings,"Painting, miniature","Pierre Louis Dubus (1721–1799), Called Préville, of the Comédie-Française",,,,,,Artist,Attributed to,Jean-Baptiste Massé,"French, Paris 1687–1767 Paris",,"Massé, Jean-Baptiste",,1687,1767,,1707,1767,Ivory laid on card,Diameter 1 5/8 in. (42 mm),"Rogers Fund, 1957",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/436983,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -71.156–57,false,true,435763,European Paintings,Painting,The Mourning Virgin; The Man of Sorrows,,,,,,Artist,Posthumous Workshop Copy after,Dieric Bouts,"Netherlandish, Leuven, ca. 1525",,"Bouts, Dieric",,1457,1475,,1520,1530,Oil on oak,Each 16 x 12 1/2 in. (40.6 x 31.8 cm),"Purchase, 1871",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435763,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -47.138,false,true,436689,European Paintings,Painting,"Mrs. Whaley (died 1798, Isle of Man)",,,,,,Artist,Attributed to,George Chinnery,"British, London 1774–1852 Macau",,"Chinnery, George",,1774,1852,,1794,1798,Oil on canvas,93 1/2 x 58 in. (237.5 x 147.3 cm),"Gift of Henry S. Morgan, 1947",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436689,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -07.225.265,false,true,437613,European Paintings,"Painting, grisaille",Venus and Cupid,,,,,,Artist,Attributed to,Piat Joseph Sauvage,"Flemish, Tournai 1744–1818 Tournai",,"Sauvage, Piat Joseph",,1744,1818,,1764,1818,Oil on canvas,49 7/8 x 29 1/4 in. (126.7 x 74.3 cm),"Gift of J. Pierpont Morgan, 1906",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437613,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -21.134.3a–c,false,true,437491,European Paintings,"Painting, parts of a polyptych",Saint Michael; The Mass of Saint Gregory; Saint Jerome,,,,,,Artist,,Master of the Saint Catherine Legend,"Netherlandish, active ca. 1470–1500",,Master of the Saint Catherine Legend,,1470,1500,,1450,1499,Oil on wood,Central panel 6 1/8 x 3 3/4 in. (15.6 x 9.5 cm); left panel 6 1/4 x 3 7/8 in. (15.9 x 9.8 cm); right panel 6 1/4 x 3 3/4 in. (15.9 x 9.5 cm),"Bequest of William H. Herriman, 1920",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437491,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -26.26,false,true,437035,European Paintings,Painting,The Lamentation,,,,,,Artist,Follower of the,Master of the Virgin among Virgins,"Netherlandish, active late 15th century",,Master of the Virgin among Virgins,,1460,1495,,1470,1499,Oil on wood,34 7/8 x 20 1/4 in. (88.6 x 51.4 cm),"Rogers Fund, 1926",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437035,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -26.244,false,true,435725,European Paintings,Painting,Christ's Descent into Hell,,,,,,Artist,Follower of,Hieronymus Bosch,"Netherlandish, second quarter 16th century",,"Bosch, Hieronymus",,1450,1516,,1550,1560,Oil on wood,21 x 46 in. (53.3 x 116.8 cm),"Harris Brisbane Dick Fund, 1926",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435725,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.100.13,false,true,436347,European Paintings,Painting,Trompe l'oeil with Palettes and Miniature,,,,,,Artist,Attributed to,Jean François de Le Motte,"French, born before 1635–died in or after 1685",,"Le Motte, Jean François de",,1635,1685,,1670,1699,Oil on canvas,46 7/8 x 36 1/8 in. (119.1 x 91.8 cm),"Bequest of Harry G. Sperling, 1971",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436347,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -15.133,false,true,436702,European Paintings,Painting,The Temptation of Saint Anthony,,,,,,Artist,Attributed to,Pieter Huys,"Netherlandish, Antwerp, active by 1545–died 1584 Antwerp",,"Huys, Pieter",,1545,1584,,1545,1584,Oil on wood,43 x 59 in. (109.2 x 149.9 cm),"Anonymous Gift, 1915",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436702,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -32.75.16,false,true,435624,European Paintings,"Painting, miniature",Portrait of a Man,,,,,,Artist,,David Baudringhien,"Dutch, ca. 1581–1650",,"Baudringhien, David",Dutch,1581,1650,1627,1627,1627,Oil on copper,"Oval, 3 3/4 x 3 in. (95 x 76 mm)","The Collection of Giovanni P. Morosini, presented by his daughter Giulia, 1932",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/435624,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2004.392,false,true,438740,European Paintings,"Painting, miniature",Portrait of a Woman,,,,,,Artist,,Nicolaes Maes,"Dutch, Dordrecht 1634–1693 Amsterdam",,"Maes, Nicolaes",Dutch,1634,1693,1657,1657,1657,Oil on copper,4 5/8 x 3 3/8 in. (117 x 86 mm),"Gift of Lila and Herman Shickman, 2004",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/438740,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.331.7,false,true,438379,European Paintings,Painting,Young Woman with a Red Necklace,,,,,,Artist,Style of,Rembrandt,"Dutch, ca. 1645",,Rembrandt,Dutch,1606,1669,ca. 1645,1640,1650,Oil on wood,"Overall, with added strips, 8 1/2 x 7 1/4 in. (21.6 x 18.4 cm)","From the Collection of Rita and Frits Markus, Bequest of Rita Markus, 2005",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/438379,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -32.100.8,false,true,436627,European Paintings,Painting,Frans Hals (1582/83–1666),,,,,,Artist,Copy after,Frans Hals,"Dutch, 17th century",,"Hals, Frans",Dutch,1582,1666,probably 1650s,1650,1659,Oil on wood,12 7/8 x 11 in. (32.7 x 27.9 cm),"The Friedsam Collection, Bequest of Michael Friedsam, 1931",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436627,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.40.610,false,true,437414,European Paintings,Painting,Pilate Washing His Hands,,,,,,Artist,Style of,Rembrandt,"Dutch, 17th century",,Rembrandt,Dutch,1606,1669,probably 1660s,1600,1699,Oil on canvas,51 1/4 x 65 3/4 in. (130.2 x 167 cm),"Bequest of Benjamin Altman, 1913",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437414,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -11.73,false,true,435769,European Paintings,Painting,The Judgment of Solomon,,,,,,Artist,,Leonaert Bramer,"Dutch, Delft 1596–1674 Delft",,"Bramer, Leonaert",Dutch,1596,1674,1640s,1640,1649,Oil on wood,31 1/8 x 40 1/2 in. (79.1 x 102.9 cm),"Gift of National Surety Company, 1911",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435769,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -89.15.21,true,true,437881,European Paintings,Painting,Young Woman with a Water Pitcher,,,,,,Artist,,Johannes Vermeer,"Dutch, Delft 1632–1675 Delft",,"Vermeer, Johannes",Dutch,1632,1675,ca. 1662,1657,1667,Oil on canvas,18 x 16 in. (45.7 x 40.6 cm),"Marquand Collection, Gift of Henry G. Marquand, 1889",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437881,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.40.611,false,true,437878,European Paintings,Painting,A Maid Asleep,,,,,,Artist,,Johannes Vermeer,"Dutch, Delft 1632–1675 Delft",,"Vermeer, Johannes",Dutch,1632,1675,ca. 1656–57,1656,1657,Oil on canvas,34 1/2 x 30 1/8 in. (87.6 x 76.5 cm),"Bequest of Benjamin Altman, 1913",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437878,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -25.110.24,false,true,437880,European Paintings,Painting,Woman with a Lute,,,,,,Artist,,Johannes Vermeer,"Dutch, Delft 1632–1675 Delft",,"Vermeer, Johannes",Dutch,1632,1675,ca. 1662–63,1662,1663,Oil on canvas,20 1/4 x 18 in. (51.4 x 45.7 cm),"Bequest of Collis P. Huntington, 1900",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437880,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -32.100.18,false,true,437877,European Paintings,Painting,Allegory of the Catholic Faith,,,,,,Artist,,Johannes Vermeer,"Dutch, Delft 1632–1675 Delft",,"Vermeer, Johannes",Dutch,1632,1675,ca. 1670–72,1670,1672,Oil on canvas,45 x 35 in. (114.3 x 88.9 cm),"The Friedsam Collection, Bequest of Michael Friedsam, 1931",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437877,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1979.396.1,true,true,437879,European Paintings,Painting,Study of a Young Woman,,,,,,Artist,,Johannes Vermeer,"Dutch, Delft 1632–1675 Delft",,"Vermeer, Johannes",Dutch,1632,1675,ca. 1665–67,1665,1667,Oil on canvas,17 1/2 x 15 3/4 in. (44.5 x 40 cm),"Gift of Mr. and Mrs. Charles Wrightsman, in memory of Theodore Rousseau Jr., 1979",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437879,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1982.60.29,false,true,435598,European Paintings,Painting,"Portrait of a Man, Possibly a Botanist",,,,,,Artist,Attributed to,David Bailly,"Dutch, Leiden 1584–1657 Leiden",,"Bailly, David",Dutch,1584,1657,1641,1641,1641,Oil on wood,33 x 24 1/2 in. (83.8 x 62.2 cm),"The Jack and Belle Linsky Collection, 1982",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435598,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -40.64,false,true,436209,European Paintings,Painting,An Evening School,,,,,,Artist,,Gerrit Dou,"Dutch, Leiden 1613–1675 Leiden",,"Dou, Gerrit",Dutch,1613,1675,ca. 1655–57,1655,1657,Oil on wood,"Arched top, 10 x 9 in. (25.4 x 22.9 cm)","Bequest of Lillian M. Ellis, 1940",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436209,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.40.607,false,true,436210,European Paintings,Painting,Self-Portrait,,,,,,Artist,,Gerrit Dou,"Dutch, Leiden 1613–1675 Leiden",,"Dou, Gerrit",Dutch,1613,1675,ca. 1665,1660,1670,Oil on wood,19 1/4 x 15 3/8 in. (48.9 x 39.1 cm),"Bequest of Benjamin Altman, 1913",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436210,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -58.89,true,true,437749,European Paintings,Painting,Merry Company on a Terrace,,,,,,Artist,,Jan Steen,"Dutch, Leiden 1626–1679 Leiden",,"Steen, Jan",Dutch,1626,1679,ca. 1670,1665,1675,Oil on canvas,55 1/2 x 51 3/4 in. (141 x 131.4 cm),"Fletcher Fund, 1958",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437749,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -46.13.2,false,true,437748,European Paintings,Painting,The Lovesick Maiden,,,,,,Artist,,Jan Steen,"Dutch, Leiden 1626–1679 Leiden",,"Steen, Jan",Dutch,1626,1679,ca. 1660,1655,1665,Oil on canvas,34 x 39 in. (86.4 x 99.1 cm),"Bequest of Helen Swift Neilson, 1945",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437748,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1982.60.31,false,true,437747,European Paintings,Painting,The Dissolute Household,,,,,,Artist,,Jan Steen,"Dutch, Leiden 1626–1679 Leiden",,"Steen, Jan",Dutch,1626,1679,ca. 1663–64,1663,1664,Oil on canvas,42 1/2 x 35 1/2 in. (108 x 90.2 cm),"The Jack and Belle Linsky Collection, 1982",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437747,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -20.155.6,false,true,437876,European Paintings,Painting,Entrance to a Dutch Port,,,,,,Artist,,Willem van de Velde II,"Dutch, Leiden 1633–1707 London",,"Velde, Willem van de, II",Dutch,1633,1707,ca. 1665,1660,1670,Oil on canvas,25 7/8 x 30 5/8 in. (65.7 x 77.8 cm),"Bequest of William K. Vanderbilt, 1920",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437876,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -60.71.3,false,true,437088,European Paintings,Painting,The Serenade,,,,,,Artist,,Frans van Mieris the Elder,"Dutch, Leiden 1635–1681 Leiden",,"Mieris, Frans van, the Elder",Dutch,1635,1681,ca. 1678–80,1678,1680,Oil on wood,"Arched top, 5 3/4 x 4 3/8 in. (14.6 x 11.1 cm)","Bequest of Lillian S. Timken, 1959",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437088,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -71.70,false,true,437687,European Paintings,Painting,Johan Hulshout (1623–1687),,,,,,Artist,,Pieter van Slingelandt,"Dutch, Leiden 1640–1691 Leiden",,"Slingelandt, Pieter van",Dutch,1640,1691,ca. 1670 or slightly later,1665,1675,Oil on wood,14 1/2 x 11 3/4 in. (36.8 x 29.8 cm),"Purchase, 1871",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437687,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -60.55.1,false,true,437688,European Paintings,Painting,Portrait of a Man,,,,,,Artist,Attributed to,Pieter van Slingelandt,"Dutch, Leiden 1640–1691 Leiden",,"Slingelandt, Pieter van",Dutch,1640,1691,ca. 1680,1675,1685,Oil on copper,"Oval, 3 3/8 x 2 1/2 in. (8.6 x 6.4 cm)","Bequest of Rupert L. Joseph, 1959",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437688,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.23.2,false,true,437915,European Paintings,Painting,"Interior of the Oude Kerk, Delft",,,,,,Artist,,Hendrick van Vliet,"Dutch, Delft 1611/12–1675 Delft",,"Vliet, Hendrick van",Dutch,1611,1675,1660,1660,1660,Oil on canvas,32 1/2 x 26 in. (82.6 x 66 cm),"Gift of Clarence Dillon, 1976",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437915,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -57.30.1,false,true,436788,European Paintings,Painting,Portrait of a Man,,,,,,Artist,,Cornelis Jonson van Ceulen the Elder,"Dutch, London 1593–1661 Utrecht",,"Jonson van Ceulen, Cornelis, the Elder",Dutch,1593,1661,1648,1648,1648,Oil on canvas,Overall 40 3/4 x 31 1/2 in. (103.5 x 80 cm); painted surface 40 3/4 x 31 1/8 in. (103.5 x 79.1 cm),"Gift of Mrs. J. E. Spingarn, 1957",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436788,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -57.30.2,false,true,436789,European Paintings,Painting,Portrait of a Woman,,,,,,Artist,,Cornelis Jonson van Ceulen the Elder,"Dutch, London 1593–1661 Utrecht",,"Jonson van Ceulen, Cornelis, the Elder",Dutch,1593,1661,1648,1648,1648,Oil on canvas,Overall 40 3/4 x 31 1/2 in. (103.5 x 80 cm); painted surface 40 3/4 x 30 7/8 in. (103.5 x 78.4 cm),"Gift of Mrs. J. E. Spingarn, 1957",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436789,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -95.7,false,true,437102,European Paintings,Painting,Landscape with a Cottage,,,,,,Artist,,Pieter de Molijn,"Dutch, London 1595–1661 Haarlem",,"Molijn, Pieter de",Dutch,1595,1661,1629,1629,1629,Oil on wood,14 3/4 x 21 3/4 in. (37.5 x 55.2 cm),"Gift of Henry G. Marquand, 1895",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437102,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -41.116.3,false,true,436790,European Paintings,Painting,Portrait of a Man with a Watch,,,,,,Artist,,Cornelis Jonson van Ceulen the Younger,"Dutch, London 1634–1715 Utrecht",,"Jonson van Ceulen, Cornelis, the Younger",Dutch,1634,1715,1657,1657,1657,Oil on canvas,33 x 27 3/4 in. (83.8 x 70.5 cm),"Given in memory of Felix M. Warburg by his wife and children, 1941",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436790,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1974.109,false,true,437633,European Paintings,Painting,Cephalus and Procris,,,,,,Artist,,Godfried Schalcken,"Dutch, Made 1643–1706 The Hague",,"Schalcken, Godfried",Dutch,1643,1706,probably 1680s,1680,1689,Oil on canvas,25 1/2 x 31 3/8 in. (64.8 x 79.7 cm),"Rogers Fund, 1974",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437633,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.40.810,false,true,437040,European Paintings,Painting,Changing Pasture,,,,,,Artist,,Anton Mauve,"Dutch, Zaandam 1838–1888 Arnhem",,"Mauve, Anton",Dutch,1838,1888,ca. 1880s,1858,1888,Oil on canvas,24 x 39 5/8 in. (61 x 100.6 cm),"Bequest of Benjamin Altman, 1913",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437040,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.40.624,false,true,437406,European Paintings,Painting,"Portrait of a Man (""The Auctioneer"")",,,,,,Artist,Follower of,Rembrandt,"Dutch, third quarter 17th century",,Rembrandt,Dutch,1606,1669,probably ca. 1658–62,1658,1662,Oil on canvas,42 3/4 x 34 in. (108.6 x 86.4 cm),"Bequest of Benjamin Altman, 1913",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437406,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -25.110.27,false,true,436309,European Paintings,Painting,Bearded Man with a Velvet Cap,,,,,,Artist,,Govert Flinck,"Dutch, Cleve 1615–1660 Amsterdam",,"Flinck, Govert",Dutch,1615,1660,1645,1645,1645,Oil on wood,23 3/4 x 20 5/8 in. (60.3 x 52.4 cm),"Bequest of Collis P. Huntington, 1900",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436309,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -71.5,false,true,437920,European Paintings,Painting,A Vase with Flowers,,,,,,Artist,,Jacob Vosmaer,"Dutch, Delft ca. 1584–1641 Delft",,"Vosmaer, Jacob",Dutch,1584,1641,probably 1613,1613,1613,Oil on wood,33 1/2 x 24 5/8 in. (85.1 x 62.5 cm),"Purchase, 1871",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437920,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -71.108,false,true,436615,European Paintings,Painting,A Banquet,,,,,,Artist,,Dirck Hals,"Dutch, Haarlem 1591–1656 Haarlem",,"Hals, Dirck",Dutch,1591,1656,1628,1628,1628,Oil on wood,16 x 26 in. (40.6 x 66 cm),"Purchase, 1871",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436615,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1981.318,false,true,437323,European Paintings,Painting,A Brazilian Landscape,,,,,,Artist,,Frans Post,"Dutch, Haarlem 1612–1680 Haarlem",,"Post, Frans",Dutch,1612,1680,1650,1650,1650,Oil on wood,24 x 36 in. (61 x 91.4 cm),"Purchase, Rogers Fund, special funds, James S. Deely Gift, and Gift of Edna H. Sachs and other gifts and bequests, by exchange, 1981",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437323,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1971.48,false,true,437953,European Paintings,Painting,A Man and a Woman on Horseback,,,,,,Artist,,Philips Wouwerman,"Dutch, Haarlem 1619–1668 Haarlem",,"Wouwerman, Philips",Dutch,1619,1668,ca. 1653–54,1653,1654,Oil on wood,12 1/8 x 16 1/4 in. (30.8 x 41.3 cm),"Purchase, Pfeiffer Fund, Joseph Pulitzer Bequest, and Gift of Dr. Ernest G. Stillman, by exchange, 1971",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437953,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -06.288,false,true,437956,European Paintings,Painting,Kitchen Scene,,,,,,Artist,,Peter Wtewael,"Dutch, Utrecht 1596–1660 Utrecht",,"Wtewael, Peter",Dutch,1596,1660,1620s,1620,1629,Oil on canvas,44 3/4 x 63 in. (113.7 x 160 cm),"Rogers Fund, 1906",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437956,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -49.7.38,false,true,435714,European Paintings,Painting,Curiosity,,,,,,Artist,,Gerard ter Borch the Younger,"Dutch, Zwolle 1617–1681 Deventer",,"Borch, Gerard ter, the Younger",Dutch,1617,1681,ca. 1660–62,1660,1662,Oil on canvas,30 x 24 1/2 in. (76.2 x 62.2 cm),"The Jules Bache Collection, 1949",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435714,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -89.15.15,false,true,435715,European Paintings,Painting,Portrait of a Seated Man,,,,,,Artist,,Gerard ter Borch the Younger,"Dutch, Zwolle 1617–1681 Deventer",,"Borch, Gerard ter, the Younger",Dutch,1617,1681,late 1650s or early 1660s,1657,1663,Oil on wood,14 1/8 x 12 in. (35.9 x 30.5 cm),"Marquand Collection, Gift of Henry G. Marquand, 1889",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435715,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.40.617,false,true,435717,European Paintings,Painting,A Woman Playing the Theorbo-Lute and a Cavalier,,,,,,Artist,,Gerard ter Borch the Younger,"Dutch, Zwolle 1617–1681 Deventer",,"Borch, Gerard ter, the Younger",Dutch,1617,1681,ca. 1658,1653,1663,Oil on wood,14 1/2 x 12 3/4 in. (36.8 x 32.4 cm),"Bequest of Benjamin Altman, 1913",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435717,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -17.190.10,false,true,435718,European Paintings,Painting,A Young Woman at Her Toilet with a Maid,,,,,,Artist,,Gerard ter Borch the Younger,"Dutch, Zwolle 1617–1681 Deventer",,"Borch, Gerard ter, the Younger",Dutch,1617,1681,ca. 1650–51,1650,1651,Oil on wood,18 3/4 x 13 5/8 in. (47.6 x 34.6 cm),"Gift of J. Pierpont Morgan, 1917",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435718,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1982.60.30,false,true,435716,European Paintings,Painting,The Van Moerkerken Family,,,,,,Artist,,Gerard ter Borch the Younger,"Dutch, Zwolle 1617–1681 Deventer",,"Borch, Gerard ter, the Younger",Dutch,1617,1681,ca. 1653–54,1653,1654,Oil on wood,16 1/4 x 14 in. (41.3 x 35.6 cm),"The Jack and Belle Linsky Collection, 1982",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435716,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -41.116.2,false,true,436211,European Paintings,Painting,Portrait of a Man (Self-Portrait?),,,,,,Artist,,Willem Drost,"Dutch, Amsterdam 1633–1659 Venice",,"Drost, Willem",Dutch,1633,1659,1653 or 1655,1653,1655,Oil on canvas,34 1/8 x 28 1/2 in. (86.7 x 72.4 cm),"Given in memory of Felix M. Warburg by his wife and children, 1941",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436211,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -30.95.268,false,true,437415,European Paintings,Painting,The Sibyl,,,,,,Artist,,Willem Drost,"Dutch, Amsterdam 1633–1659 Venice",,"Drost, Willem",Dutch,1633,1659,ca. 1654,1649,1659,Oil on canvas,38 1/2 x 30 3/4 in. (97.8 x 78.1 cm),"Theodore M. Davis Collection, Bequest of Theodore M. Davis, 1915",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437415,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.331.4,false,true,438376,European Paintings,Painting,"Still Life with Oysters, a Silver Tazza, and Glassware",,,,,,Artist,,Willem Claesz Heda,"Dutch, Haarlem? 1594–1680 Haarlem",,"Heda, Willem Claesz",Dutch,1594,1680,1635,1635,1635,Oil on wood,19 5/8 x 31 3/4 in. (49.8 x 80.6 cm),"From the Collection of Rita and Frits Markus, Bequest of Rita Markus, 2005",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/438376,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -71.62,false,true,436558,European Paintings,Painting,View of Haarlem and the Haarlemmer Meer,,,,,,Artist,,Jan van Goyen,"Dutch, Leiden 1596–1656 The Hague",,"Goyen, Jan van",Dutch,1596,1656,1646,1646,1646,Oil on wood,13 5/8 x 19 7/8 in. (34.6 x 50.5 cm),"Purchase, 1871",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436558,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.25,false,true,436555,European Paintings,Painting,Sandy Road with a Farmhouse,,,,,,Artist,,Jan van Goyen,"Dutch, Leiden 1596–1656 The Hague",,"Goyen, Jan van",Dutch,1596,1656,1627,1627,1627,Oil on wood,12 1/8 x 16 1/4 in. (30.8 x 41.3 cm),"Bequest of Myra Mortimer Pinter, 1972",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436555,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -64.65.1,false,true,436559,European Paintings,Painting,Castle by a River,,,,,,Artist,,Jan van Goyen,"Dutch, Leiden 1596–1656 The Hague",,"Goyen, Jan van",Dutch,1596,1656,1647,1647,1647,Oil on wood,26 x 38 1/4 in. (66 x 97.2 cm),"Gift of Edith Neuman de Végvár, in honor of her husband, Charles Neuman de Végvár, 1964",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436559,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -32.100.6,false,true,436556,European Paintings,Painting,Country House near the Water,,,,,,Artist,,Jan van Goyen,"Dutch, Leiden 1596–1656 The Hague",,"Goyen, Jan van",Dutch,1596,1656,1646,1646,1646,Oil on wood,14 3/8 x 13 in. (36.5 x 33 cm),"The Friedsam Collection, Bequest of Michael Friedsam, 1931",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436556,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -45.146.3,false,true,436557,European Paintings,Painting,The Pelkus Gate near Utrecht,,,,,,Artist,,Jan van Goyen,"Dutch, Leiden 1596–1656 The Hague",,"Goyen, Jan van",Dutch,1596,1656,1646,1646,1646,Oil on wood,"14 1/2 x 22 1/2 in. (36.8 x 57.2 cm) Frame, 23 1/4 x 31 x 2 3/4 in. (59.1 x 78.7 x 7 cm)","Gift of Francis Neilson, 1945",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436557,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.331.2,false,true,438374,European Paintings,Painting,A Beach with Fishing Boats,,,,,,Artist,,Jan van Goyen,"Dutch, Leiden 1596–1656 The Hague",,"Goyen, Jan van",Dutch,1596,1656,probably 1653,1653,1653,Oil on wood,11 x 17 in. (27.9 x 43.2 cm),"From the Collection of Rita and Frits Markus, Bequest of Rita Markus, 2005",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/438374,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.331.3,false,true,438375,European Paintings,Painting,A View of The Hague from the Northwest,,,,,,Artist,,Jan van Goyen,"Dutch, Leiden 1596–1656 The Hague",,"Goyen, Jan van",Dutch,1596,1656,1647,1647,1647,Oil on wood,26 x 37 7/8 in. (66 x 96.2 cm),"From the Collection of Rita and Frits Markus, Bequest of Rita Markus, 2005",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/438375,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -43.125,false,true,437391,European Paintings,Painting,Portrait of a Young Woman with a Fan,,,,,,Artist,,Rembrandt (Rembrandt van Rijn),"Dutch, Leiden 1606–1669 Amsterdam",,Rembrandt (Rembrandt van Rijn),Dutch,1606,1669,1633,1633,1633,Oil on canvas,49 1/2 x 39 3/4 in. (125.7 x 101 cm),"Gift of Helen Swift Neilson, 1943",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437391,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -61.198,true,true,437394,European Paintings,Painting,Aristotle with a Bust of Homer,,,,,,Artist,,Rembrandt (Rembrandt van Rijn),"Dutch, Leiden 1606–1669 Amsterdam",,Rembrandt (Rembrandt van Rijn),Dutch,1606,1669,1653,1653,1653,Oil on canvas,56 1/2 x 53 3/4 in. (143.5 x 136.5 cm),"Purchase, special contributions and funds given or bequeathed by friends of the Museum, 1961",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437394,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -64.126,false,true,437387,European Paintings,Painting,Portrait of a Man,,,,,,Artist,,Rembrandt (Rembrandt van Rijn),"Dutch, Leiden 1606–1669 Amsterdam",,Rembrandt (Rembrandt van Rijn),Dutch,1606,1669,1632,1632,1632,Oil on wood,"Oval, 29 3/4 x 20 1/2 in. (75.6 x 52.1 cm)","Gift of Mrs. Lincoln Ellsworth, in memory of Lincoln Ellsworth, 1964",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437387,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -49.7.35,false,true,437395,European Paintings,Painting,"The Standard Bearer (Floris Soop, 1604–1657)",,,,,,Artist,,Rembrandt (Rembrandt van Rijn),"Dutch, Leiden 1606–1669 Amsterdam",,Rembrandt (Rembrandt van Rijn),Dutch,1606,1669,1654,1654,1654,Oil on canvas,55 1/4 x 45 1/4in. (140.3 x 114.9cm),"The Jules Bache Collection, 1949",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437395,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -91.26.7,false,true,437400,European Paintings,Painting,Portrait of a Man,,,,,,Artist,,Rembrandt (Rembrandt van Rijn),"Dutch, Leiden 1606–1669 Amsterdam",,Rembrandt (Rembrandt van Rijn),Dutch,1606,1669,ca. 1655–60,1655,1660,Oil on canvas,32 7/8 x 25 3/8 in. (83.5 x 64.5 cm),"Marquand Collection, Gift of Henry G. Marquand, 1890",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437400,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -20.155.2,false,true,437385,European Paintings,Painting,"Man in Oriental Costume (""The Noble Slav"")",,,,,,Artist,,Rembrandt (Rembrandt van Rijn),"Dutch, Leiden 1606–1669 Amsterdam",,Rembrandt (Rembrandt van Rijn),Dutch,1606,1669,1632,1632,1632,Oil on canvas,60 1/8 x 43 3/4in. (152.7 x 111.1cm),"Bequest of William K. Vanderbilt, 1920",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437385,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -26.101.9,false,true,437396,European Paintings,Painting,Hendrickje Stoffels (1626–1663),,,,,,Artist,,Rembrandt (Rembrandt van Rijn),"Dutch, Leiden 1606–1669 Amsterdam",,Rembrandt (Rembrandt van Rijn),Dutch,1606,1669,mid-1650s,1654,1656,Oil on canvas,30 7/8 x 27 1/8 in. (78.4 x 68.9 cm),"Gift of Archer M. Huntington, in memory of his father, Collis Potter Huntington, 1926",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437396,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.1,false,true,437392,European Paintings,Painting,"Herman Doomer (born about 1595, died 1650)",,,,,,Artist,,Rembrandt (Rembrandt van Rijn),"Dutch, Leiden 1606–1669 Amsterdam",,Rembrandt (Rembrandt van Rijn),Dutch,1606,1669,1640,1640,1640,Oil on wood,29 5/8 x 21 3/4 in. (75.2 x 55.2 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437392,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.3,false,true,437386,European Paintings,Painting,"Portrait of a Man, probably a Member of the Van Beresteyn Family",,,,,,Artist,,Rembrandt (Rembrandt van Rijn),"Dutch, Leiden 1606–1669 Amsterdam",,Rembrandt (Rembrandt van Rijn),Dutch,1606,1669,1632,1632,1632,Oil on canvas,44 x 35 in. (111.8 x 88.9 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437386,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.4,false,true,437388,European Paintings,Painting,"Portrait of a Woman, probably a Member of the Van Beresteyn Family",,,,,,Artist,,Rembrandt (Rembrandt van Rijn),"Dutch, Leiden 1606–1669 Amsterdam",,Rembrandt (Rembrandt van Rijn),Dutch,1606,1669,1632,1632,1632,Oil on canvas,44 x 35 in. (111.8 x 88.9 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437388,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.40.618,true,true,437397,European Paintings,Painting,Self-Portrait,,,,,,Artist,,Rembrandt (Rembrandt van Rijn),"Dutch, Leiden 1606–1669 Amsterdam",,Rembrandt (Rembrandt van Rijn),Dutch,1606,1669,1660,1660,1660,Oil on canvas,31 5/8 x 26 1/2 in. (80.3 x 67.3 cm),"Bequest of Benjamin Altman, 1913",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437397,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.40.620,false,true,437401,European Paintings,Painting,Portrait of a Man Holding Gloves,,,,,,Artist,,Rembrandt (Rembrandt van Rijn),"Dutch, Leiden 1606–1669 Amsterdam",,Rembrandt (Rembrandt van Rijn),Dutch,1606,1669,1648,1648,1648,Oil on wood,31 3/4 x 26 1/2 in. (80.6 x 67.3 cm),"Bequest of Benjamin Altman, 1913",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437401,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.40.621,false,true,437399,European Paintings,Painting,Man with a Magnifying Glass,,,,,,Artist,,Rembrandt (Rembrandt van Rijn),"Dutch, Leiden 1606–1669 Amsterdam",,Rembrandt (Rembrandt van Rijn),Dutch,1606,1669,early 1660s,1660,1663,Oil on canvas,36 x 29 1/4 in. (91.4 x 74.3 cm),"Bequest of Benjamin Altman, 1913",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437399,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.40.622,false,true,437402,European Paintings,Painting,Woman with a Pink,,,,,,Artist,,Rembrandt (Rembrandt van Rijn),"Dutch, Leiden 1606–1669 Amsterdam",,Rembrandt (Rembrandt van Rijn),Dutch,1606,1669,early 1660s,1660,1663,Oil on canvas,36 1/4 x 29 3/8 in. (92.1 x 74.6 cm),"Bequest of Benjamin Altman, 1913",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437402,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.40.625,false,true,437390,European Paintings,Painting,Portrait of a Woman,,,,,,Artist,,Rembrandt (Rembrandt van Rijn),"Dutch, Leiden 1606–1669 Amsterdam",,Rembrandt (Rembrandt van Rijn),Dutch,1606,1669,1633,1633,1633,Oil on wood,"Oval, 26 3/4 x 19 3/4 in. (67.9 x 50.2 cm)","Bequest of Benjamin Altman, 1913",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437390,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.40.651,false,true,437393,European Paintings,Painting,The Toilet of Bathsheba,,,,,,Artist,,Rembrandt (Rembrandt van Rijn),"Dutch, Leiden 1606–1669 Amsterdam",,Rembrandt (Rembrandt van Rijn),Dutch,1606,1669,1643,1643,1643,Oil on wood,22 1/2 x 30 in. (57.2 x 76.2 cm),"Bequest of Benjamin Altman, 1913",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437393,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -26.101.10,false,true,437398,European Paintings,Painting,Flora,,,,,,Artist,,Rembrandt (Rembrandt van Rijn),"Dutch, Leiden 1606–1669 Amsterdam",,Rembrandt (Rembrandt van Rijn),Dutch,1606,1669,ca. 1654,1649,1659,Oil on canvas,39 3/8 x 36 1/8 in. (100 x 91.8 cm),"Gift of Archer M. Huntington, in memory of his father, Collis Potter Huntington, 1926",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437398,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -32.100.23,false,true,437389,European Paintings,Painting,Bellona,,,,,,Artist,,Rembrandt (Rembrandt van Rijn),"Dutch, Leiden 1606–1669 Amsterdam",,Rembrandt (Rembrandt van Rijn),Dutch,1606,1669,1633,1633,1633,Oil on canvas,50 x 38 3/8 in. (127 x 97.5 cm),"The Friedsam Collection, Bequest of Michael Friedsam, 1931",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437389,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -91.26.11,false,true,437070,European Paintings,Painting,A Musical Party,,,,,,Artist,,Gabriël Metsu,"Dutch, Leiden 1629–1667 Amsterdam",,"Metsu, Gabriël",Dutch,1629,1667,1659,1659,1659,Oil on canvas,24 1/2 x 21 3/8 in. (62.2 x 54.3 cm),"Marquand Collection, Gift of Henry G. Marquand, 1890",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437070,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -17.190.20,false,true,437071,European Paintings,Painting,The Visit to the Nursery,,,,,,Artist,,Gabriël Metsu,"Dutch, Leiden 1629–1667 Amsterdam",,"Metsu, Gabriël",Dutch,1629,1667,1661,1661,1661,Oil on canvas,30 1/2 x 32 in. (77.5 x 81.3 cm),"Gift of J. Pierpont Morgan, 1917",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437071,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1982.60.32,false,true,437073,European Paintings,Painting,A Woman Seated at a Window,,,,,,Artist,,Gabriël Metsu,"Dutch, Leiden 1629–1667 Amsterdam",,"Metsu, Gabriël",Dutch,1629,1667,early 1660s,1660,1663,Oil on wood,10 7/8 x 8 7/8 in. (27.6 x 22.5 cm),"The Jack and Belle Linsky Collection, 1982",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437073,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -71.160,false,true,437178,European Paintings,Painting,The Newborn Baby,,,,,,Artist,,Matthijs Naiveu,"Dutch, Leiden 1647–1726 Amsterdam",,"Naiveu, Matthijs",Dutch,1647,1726,1675,1675,1675,Oil on canvas,25 1/4 x 31 1/2 in. (64.1 x 80 cm),"Purchase, 1871",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437178,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -43.118,false,true,436843,European Paintings,Painting,Apollo and Aurora,,,,,,Artist,,Gerard de Lairesse,"Dutch, Liège 1641–1711 Amsterdam",,"Lairesse, Gerard de",Dutch,1641,1711,1671,1671,1671,Oil on canvas,80 1/2 x 76 1/8 in. (204.5 x 193.4 cm),"Gift of Manuel E. and Ellen G. Rionda, 1943",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436843,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -87.15.30,false,true,436826,European Paintings,Painting,"Winter Landscape, Holland",,,,,,Artist,,Barend Cornelis Koekkoek,"Dutch, Middelburg 1803–1862 Cleve",,"Koekkoek, Barend Cornelis",Dutch,1803,1862,1833,1833,1833,Oil on wood,14 x 17 in. (35.6 x 43.2 cm),"Catharine Lorillard Wolfe Collection, Bequest of Catharine Lorillard Wolfe, 1887",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436826,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -87.15.45,false,true,436827,European Paintings,Painting,Sunset on the Rhine,,,,,,Artist,,Barend Cornelis Koekkoek,"Dutch, Middelburg 1803–1862 Cleve",,"Koekkoek, Barend Cornelis",Dutch,1803,1862,1853,1853,1853,Oil on canvas,32 1/4 x 42 3/8 in. (81.9 x 107.6 cm),"Catharine Lorillard Wolfe Collection, Bequest of Catharine Lorillard Wolfe, 1887",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436827,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -92.1.49,false,true,436975,European Paintings,Painting,Reverie,,,,,,Artist,,Matthys Maris,"Dutch, The Hague 1839–1917 London",,"Maris, Matthys",Dutch,1839,1917,1875,1875,1875,Oil on canvas,12 x 9 1/4 in. (30.5 x 23.5 cm),"Bequest of Elizabeth U. Coles, in memory of her son, William F. Coles, 1892",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436975,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -27.250.1,false,true,436671,European Paintings,Painting,Peacocks,,,,,,Artist,,Melchior d' Hondecoeter,"Dutch, Utrecht 1636–1695 Amsterdam",,"Hondecoeter, Melchior d'",Dutch,1636,1695,1683,1683,1683,Oil on canvas,74 7/8 x 53 in. (190.2 x 134.6 cm),"Gift of Samuel H. Kress, 1927",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436671,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -49.7.33,false,true,436621,European Paintings,Painting,"Portrait of a Man, Possibly Nicolaes Pietersz Duyst van Voorhout (born about 1600, died 1650)",,,,,,Artist,,Frans Hals,"Dutch, Antwerp 1582/83–1666 Haarlem",,"Hals, Frans",Dutch,1582,1666,ca. 1636–38,1636,1638,Oil on canvas,31 3/4 x 26 in. (80.6 x 66 cm),"The Jules Bache Collection, 1949",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436621,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -49.7.34,false,true,436617,European Paintings,Painting,Portrait of a Bearded Man with a Ruff,,,,,,Artist,,Frans Hals,"Dutch, Antwerp 1582/83–1666 Haarlem",,"Hals, Frans",Dutch,1582,1666,1625,1625,1625,Oil on canvas,30 x 25 in. (76.2 x 63.5 cm),"The Jules Bache Collection, 1949",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436617,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -91.26.9,false,true,436623,European Paintings,Painting,Portrait of a Man,,,,,,Artist,,Frans Hals,"Dutch, Antwerp 1582/83–1666 Haarlem",,"Hals, Frans",Dutch,1582,1666,early 1650s,1650,1653,Oil on canvas,43 1/2 x 34 in. (110.5 x 86.4 cm),"Marquand Collection, Gift of Henry G. Marquand, 1890",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436623,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.8,false,true,436619,European Paintings,Painting,Petrus Scriverius (1576–1660),,,,,,Artist,,Frans Hals,"Dutch, Antwerp 1582/83–1666 Haarlem",,"Hals, Frans",Dutch,1582,1666,1626,1626,1626,Oil on wood,8 3/4 x 6 1/2 in. (22.2 x 16.5 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436619,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.9,false,true,436618,European Paintings,Painting,"Anna van der Aar (born 1576/77, died after 1626)",,,,,,Artist,,Frans Hals,"Dutch, Antwerp 1582/83–1666 Haarlem",,"Hals, Frans",Dutch,1582,1666,1626,1626,1626,Oil on wood,8 3/4 x 6 1/2 in. (22.2 x 16.5 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436618,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -89.15.34,false,true,436625,European Paintings,Painting,The Smoker,,,,,,Artist,,Frans Hals,"Dutch, Antwerp 1582/83–1666 Haarlem",,"Hals, Frans",Dutch,1582,1666,ca. 1623–25,1623,1625,Oil on wood,"Octagonal, 18 3/8 x 19 1/2 in. (46.7 x 49.5 cm)","Marquand Collection, Gift of Henry G. Marquand, 1889",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436625,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -91.26.10,false,true,436624,European Paintings,Painting,Portrait of a Woman,,,,,,Artist,,Frans Hals,"Dutch, Antwerp 1582/83–1666 Haarlem",,"Hals, Frans",Dutch,1582,1666,"ca. 1650, reworked probably 18th century",1645,1655,Oil on canvas,39 3/8 x 32 1/4 in. (100 x 81.9 cm),"Marquand Collection, Gift of Henry G. Marquand, 1890",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436624,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.40.602,true,true,436616,European Paintings,Painting,"Young Man and Woman in an Inn (""Yonker Ramp and His Sweetheart"")",,,,,,Artist,,Frans Hals,"Dutch, Antwerp 1582/83–1666 Haarlem",,"Hals, Frans",Dutch,1582,1666,1623,1623,1623,Oil on canvas,41 1/2 x 31 1/4 in. (105.4 x 79.4 cm),"Bequest of Benjamin Altman, 1913",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436616,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.40.604,false,true,436626,European Paintings,Painting,Boy with a Lute,,,,,,Artist,,Frans Hals,"Dutch, Antwerp 1582/83–1666 Haarlem",,"Hals, Frans",Dutch,1582,1666,ca. 1625,1620,1630,Oil on canvas,28 3/8 x 23 1/4 in. (72.1 x 59.1 cm),"Bequest of Benjamin Altman, 1913",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436626,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.40.605,true,true,436622,European Paintings,Painting,Merrymakers at Shrovetide,,,,,,Artist,,Frans Hals,"Dutch, Antwerp 1582/83–1666 Haarlem",,"Hals, Frans",Dutch,1582,1666,ca. 1616–17,1616,1617,Oil on canvas,51 3/4 x 39 1/4 in. (131.4 x 99.7 cm),"Bequest of Benjamin Altman, 1913",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436622,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -26.101.11,false,true,436620,European Paintings,Painting,Paulus Verschuur (1606–1667),,,,,,Artist,,Frans Hals,"Dutch, Antwerp 1582/83–1666 Haarlem",,"Hals, Frans",Dutch,1582,1666,1643,1643,1643,Oil on canvas,46 3/4 x 37 in. (118.7 x 94 cm),"Gift of Archer M. Huntington, in memory of his father, Collis Potter Huntington, 1926",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436620,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1991.305,false,true,435770,European Paintings,Painting,The Preaching of John the Baptist,,,,,,Artist,,Bartholomeus Breenbergh,"Dutch, Deventer 1598–1657 Amsterdam",,"Breenbergh, Bartholomeus",Dutch,1598,1657,1634,1634,1634,Oil on wood,21 1/2 x 29 5/8 in. (54.6 x 75.2 cm),"Purchase, The Annenberg Foundation Gift, 1991",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435770,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -56.228,true,true,435817,European Paintings,Painting,The Crucifixion with the Virgin and Saint John,,,,,,Artist,,Hendrick ter Brugghen,"Dutch, The Hague? 1588–1629 Utrecht",,"Brugghen, Hendrick ter",Dutch,1588,1629,ca. 1624–25,1624,1625,Oil on canvas,61 x 40 1/4 in. (154.9 x 102.2 cm),"Funds from various donors, 1956",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435817,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -71.78,false,true,436636,European Paintings,Painting,Still Life with a Glass and Oysters,,,,,,Artist,,Jan Davidsz de Heem,"Dutch, Utrecht 1606–1683/84 Antwerp",,"Heem, Jan Davidsz de",Dutch,1606,1684,ca. 1640,1635,1645,Oil on wood,9 7/8 x 7 1/2 in. (25.1 x 19.1 cm),"Purchase, 1871",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436636,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -12.195,false,true,436637,European Paintings,Painting,Still Life: A Banqueting Scene,,,,,,Artist,,Jan Davidsz de Heem,"Dutch, Utrecht 1606–1683/84 Antwerp",,"Heem, Jan Davidsz de",Dutch,1606,1684,probably ca. 1640–41,1640,1641,Oil on canvas,53 1/4 x 73 in. (135.3 x 185.4 cm),"Charles B. Curtis Fund, 1912",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436637,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -11.144,false,true,436830,European Paintings,Painting,A Panoramic Landscape with a Country Estate,,,,,,Artist,,Philips Koninck,"Dutch, Amsterdam 1619–1688 Amsterdam",,"Koninck, Philips",Dutch,1619,1688,ca. 1649,1644,1654,Oil on canvas,56 3/8 x 68 1/4 in. (143.2 x 173.4 cm),"John Stewart Kennedy Fund, 1911",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436830,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1980.4,false,true,436831,European Paintings,Painting,An Extensive Wooded Landscape,,,,,,Artist,,Philips Koninck,"Dutch, Amsterdam 1619–1688 Amsterdam",,"Koninck, Philips",Dutch,1619,1688,1670s,1670,1679,Oil on canvas,32 3/4 x 44 5/8 in. (83.2 x 113.3 cm),"Purchase, Mr. and Mrs. David T. Schiff and George T. Delacorte Jr. Gifts, special funds, and Bequest of Mary Cushing Fosburgh and other gifts and bequests, by exchange, 1980",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436831,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -63.43.2,false,true,436832,European Paintings,Painting,Wide River Landscape,,,,,,Artist,,Philips Koninck,"Dutch, Amsterdam 1619–1688 Amsterdam",,"Koninck, Philips",Dutch,1619,1688,ca. 1648–49,1648,1649,Oil on canvas,16 1/4 x 22 7/8 in. (41.3 x 58.1 cm),"Gift of Edith Neuman de Végvár, in honor of her husband, Charles Neuman de Végvár, 1963",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436832,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -26.260.8,false,true,436267,European Paintings,Painting,A Musical Party,,,,,,Artist,,Gerbrand van den Eeckhout,"Dutch, Amsterdam 1621–1674 Amsterdam",,"Eeckhout, Gerbrand van den",Dutch,1621,1674,early 1650s,1650,1653,Oil on canvas,20 x 24 1/2 in. (50.8 x 62.2 cm),"Bequest of Annie C. Kane, 1926",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436267,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -25.110.16,false,true,436266,European Paintings,Painting,Isaac Blessing Jacob,,,,,,Artist,,Gerbrand van den Eeckhout,"Dutch, Amsterdam 1621–1674 Amsterdam",,"Eeckhout, Gerbrand van den",Dutch,1621,1674,1642,1642,1642,Oil on canvas,39 5/8 x 50 1/2 in. (100.6 x 128.3 cm),"Bequest of Collis P. Huntington, 1900",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436266,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -12.31,false,true,435842,European Paintings,Painting,A State Yacht and Other Craft in Calm Water,,,,,,Artist,,Jan van de Cappelle,"Dutch, Amsterdam 1626–1679 Amsterdam",,"Cappelle, Jan van de",Dutch,1626,1679,ca. 1660,1655,1665,Oil on wood,27 1/2 x 36 3/8 in. (69.9 x 92.4 cm),"Francis L. Leland Fund, 1912",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435842,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.40.614,false,true,436652,European Paintings,Painting,Entrance to a Village,,,,,,Artist,,Meyndert Hobbema,"Dutch, Amsterdam 1638–1709 Amsterdam",,"Hobbema, Meyndert",Dutch,1638,1709,ca. 1665,1660,1670,Oil on wood,29 1/2 x 43 3/8 in. (74.9 x 110.2 cm),"Bequest of Benjamin Altman, 1913",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436652,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -50.145.22,false,true,436653,European Paintings,Painting,Woodland Road,,,,,,Artist,,Meyndert Hobbema,"Dutch, Amsterdam 1638–1709 Amsterdam",,"Hobbema, Meyndert",Dutch,1638,1709,ca. 1670,1665,1675,Oil on canvas,37 1/4 x 51 in. (94.6 x 129.5 cm),"Bequest of Mary Stillman Harkness, 1950",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436653,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -07.225.298,false,true,437946,European Paintings,"Painting, sketch for a ceiling decoration",Allegory of the Arts,,,,,,Artist,,Jacob de Wit,"Dutch, Amsterdam 1695–1754 Amsterdam",,"Wit, Jacob de",Dutch,1695,1754,1742,1742,1742,Oil on canvas,18 7/8 x 23 1/4 in. (47.9 x 59.1 cm),"Gift of J. Pierpont Morgan, 1906",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437946,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -07.225.301,false,true,437947,European Paintings,"Painting, sketch for a ceiling decoration",Flora and Zephyr,,,,,,Artist,,Jacob de Wit,"Dutch, Amsterdam 1695–1754 Amsterdam",,"Wit, Jacob de",Dutch,1695,1754,1743,1743,1743,Oil on canvas,20 7/8 x 24 7/8 in. (53 x 63.2 cm),"Gift of J. Pierpont Morgan, 1906",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437947,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1981.239,false,true,436270,European Paintings,Painting,Conversation Piece (The Sense of Smell),,,,,,Artist,,Jan Ekels the Younger,"Dutch, Amsterdam 1759–1793 Amsterdam",,"Ekels, Jan, the Younger",Dutch,1759,1793,probably 1791,1791,1791,Oil on canvas,25 7/8 x 23 1/2 in. (65.7 x 59.7 cm),"Gift of Mr. and Mrs. Bertram L. Podell, 1981",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436270,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -49.107,false,true,435904,European Paintings,Painting,Still Life with a Skull and a Writing Quill,,,,,,Artist,,Pieter Claesz,"Dutch, Berchem? 1596/97–1660 Haarlem",,"Claesz, Pieter",Dutch,1596,1660,1628,1628,1628,Oil on wood,9 1/2 x 14 1/8 in. (24.1 x 35.9 cm),"Rogers Fund, 1949",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435904,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -57.68,false,true,435690,European Paintings,Painting,Petronella Elias (1648–1667) with a Basket of Fruit,,,,,,Artist,,Ferdinand Bol,"Dutch, Dordrecht 1616–1680 Amsterdam",,"Bol, Ferdinand",Dutch,1616,1680,1657,1657,1657,Oil on canvas,31 5/8 x 26 in. (80.3 x 66 cm),"Purchase, George T. Delacorte Jr. Gift, 1957",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435690,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -30.95.269,false,true,435689,European Paintings,Painting,Portrait of a Woman,,,,,,Artist,,Ferdinand Bol,"Dutch, Dordrecht 1616–1680 Amsterdam",,"Bol, Ferdinand",Dutch,1616,1680,1642,1642,1642,Oil on canvas,34 3/8 x 28 in. (87.3 x 71.1 cm),"Theodore M. Davis Collection, Bequest of Theodore M. Davis, 1915",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435689,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -34.83.1,false,true,436065,European Paintings,Painting,Children and a Cow,,,,,,Artist,Attributed to,Aelbert Cuyp,"Dutch, Dordrecht 1620–1691 Dordrecht",,"Cuyp, Aelbert",Dutch,1620,1691,1635–39,1635,1639,Oil on wood,17 1/4 x 21 1/2 in. (43.8 x 54.6 cm),"Bequest of Mariana Griswold Van Rensselaer, in memory of her father, George Griswold, 1934",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436065,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.40.616,false,true,436064,European Paintings,Painting,Young Herdsmen with Cows,,,,,,Artist,,Aelbert Cuyp,"Dutch, Dordrecht 1620–1691 Dordrecht",,"Cuyp, Aelbert",Dutch,1620,1691,ca. 1655–60,1655,1660,Oil on canvas,44 1/8 x 52 1/8 in. (112.1 x 132.4 cm),"Bequest of Benjamin Altman, 1913",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436064,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -25.110.15,false,true,436062,European Paintings,Painting,Piping Shepherds,,,,,,Artist,,Aelbert Cuyp,"Dutch, Dordrecht 1620–1691 Dordrecht",,"Cuyp, Aelbert",Dutch,1620,1691,ca. 1643–44,1643,1644,Oil on canvas,35 3/4 x 47 in. (90.8 x 119.4 cm),"Bequest of Collis P. Huntington, 1900",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436062,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -32.100.20,false,true,436063,European Paintings,Painting,"Equestrian Portrait of Cornelis (1639–1680) and Michiel Pompe van Meerdervoort (1638–1653) with Their Tutor and Coachman (""Starting for the Hunt"")",,,,,,Artist,,Aelbert Cuyp,"Dutch, Dordrecht 1620–1691 Dordrecht",,"Cuyp, Aelbert",Dutch,1620,1691,ca. 1652–53,1652,1653,Oil on canvas,43 1/4 x 61 1/2in. (109.9 x 156.2cm),"The Friedsam Collection, Bequest of Michael Friedsam, 1931",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436063,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1973.155.2,false,true,436061,European Paintings,Painting,Landscape with the Flight into Egypt,,,,,,Artist,,Aelbert Cuyp,"Dutch, Dordrecht 1620–1691 Dordrecht",,"Cuyp, Aelbert",Dutch,1620,1691,ca. 1650,1645,1655,Oil on wood,18 x 22 7/8 in. (45.7 x 58.1 cm),"Bequest of Josephine Bieber, in memory of her husband, Siegfried Bieber, 1970",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436061,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1992.133,false,true,436680,European Paintings,Painting,The Annunciation of the Death of the Virgin,,,,,,Artist,,Samuel van Hoogstraten,"Dutch, Dordrecht 1627–1678 Dordrecht",,"Hoogstraten, Samuel van",Dutch,1627,1678,ca. 1670,1665,1675,Oil on canvas,26 x 20 3/4 in. (66 x 52.7 cm),"Purchase, Rogers Fund and Joseph Pulitzer Bequest, 1992",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436680,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1982.60.33,false,true,435670,European Paintings,Painting,A Young Woman and a Cavalier,,,,,,Artist,,Cornelis Bisschop,"Dutch, Dordrecht 1630–1674 Dordrecht",,"Bisschop, Cornelis",Dutch,1630,1674,early 1660s,1660,1663,Oil on canvas,38 1/2 x 34 3/4 in. (97.8 x 88.3 cm),"The Jack and Belle Linsky Collection, 1982",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435670,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -06.1325,false,true,436933,European Paintings,Painting,Portrait of a Woman,,,,,,Artist,,Nicolaes Maes,"Dutch, Dordrecht 1634–1693 Amsterdam",,"Maes, Nicolaes",Dutch,1634,1693,ca. 1665–70,1665,1670,Oil on canvas,44 x 35 1/4 in. (111.8 x 89.5 cm),"Rogers Fund, 1906",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436933,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1971.73,false,true,436929,European Paintings,Painting,Abraham Dismissing Hagar and Ishmael,,,,,,Artist,,Nicolaes Maes,"Dutch, Dordrecht 1634–1693 Amsterdam",,"Maes, Nicolaes",Dutch,1634,1693,1653,1653,1653,Oil on canvas,34 1/2 x 27 1/2 in. (87.6 x 69.9 cm),"Gift of Mrs. Edward Brayton, 1971",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436929,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -11.149.3,false,true,436930,European Paintings,Painting,"Ingena Rotterdam (died 1704), Betrothed of Admiral Jacob Binkes",,,,,,Artist,,Nicolaes Maes,"Dutch, Dordrecht 1634–1693 Amsterdam",,"Maes, Nicolaes",Dutch,1634,1693,1676,1676,1676,Oil on canvas,17 1/4 x 13 in. (43.8 x 33 cm),"Gift of J. Pierpont Morgan, 1911",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436930,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -32.100.5,false,true,436932,European Paintings,Painting,The Lacemaker,,,,,,Artist,,Nicolaes Maes,"Dutch, Dordrecht 1634–1693 Amsterdam",,"Maes, Nicolaes",Dutch,1634,1693,ca. 1656,1651,1661,Oil on canvas,17 3/4 x 20 3/4 in. (45.1 x 52.7 cm),"The Friedsam Collection, Bequest of Michael Friedsam, 1931",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436932,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.40.612,false,true,436934,European Paintings,Painting,Young Woman Peeling Apples,,,,,,Artist,,Nicolaes Maes,"Dutch, Dordrecht 1634–1693 Amsterdam",,"Maes, Nicolaes",Dutch,1634,1693,ca. 1655,1650,1660,Oil on wood,21 1/2 x 18 in. (54.6 x 45.7 cm),"Bequest of Benjamin Altman, 1913",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436934,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -68.190,false,true,437089,European Paintings,Painting,Portrait of a Man,,,,,,Artist,Circle of,Arnold Boonen,"Dutch, Dordrecht 1669–1729 Amsterdam",,"Boonen, Arnold",Dutch,1669,1729,ca. 1720,1715,1725,Oil on canvas,22 1/4 x 18 3/4 in. (56.5 x 47.6 cm),"Gift of Marcel Aubry, 1968",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437089,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -91.26.8,false,true,437760,European Paintings,Painting,Landscape with Cattle,,,,,,Artist,,Jacob van Strij,"Dutch, Dordrecht 1756–1815 Dordrecht",,"Strij, Jacob van",Dutch,1756,1815,probably ca. 1800,1795,1805,Oil on wood,31 1/2 x 42 1/4 in. (80 x 107.3 cm),"Marquand Collection, Gift of Henry G. Marquand, 1890",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437760,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -71.23,false,true,436890,European Paintings,Painting,Battle Scene,,,,,,Artist,,Johannes Lingelbach,"Dutch, Frankfurt 1622–1674 Amsterdam",,"Lingelbach, Johannes",Dutch,1622,1674,1671,1671,1671,Oil on canvas,44 3/8 x 63 1/4 in. (112.7 x 160.7 cm),"Purchase, 1871",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436890,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -71.123,false,true,436889,European Paintings,Painting,Peasants Dancing,,,,,,Artist,,Johannes Lingelbach,"Dutch, Frankfurt 1622–1674 Amsterdam",,"Lingelbach, Johannes",Dutch,1622,1674,1651,1651,1651,Oil on canvas,26 1/2 x 29 1/2 in. (67.3 x 74.9 cm),"Purchase, 1871",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436889,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -64.65.2,false,true,436647,European Paintings,Painting,The Huis ten Bosch at The Hague and Its Formal Garden (View from the South),,,,,,Artist,,Jan van der Heyden,"Dutch, Gorinchem 1637–1712 Amsterdam",,"Heyden, Jan van der",Dutch,1637,1712,ca. 1668–70,1668,1670,Oil on wood,15 3/8 x 21 3/4 in. (39.1 x 55.2 cm),"Gift of Edith Neuman de Végvár, in honor of her husband, Charles Neuman de Végvár, 1964",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436647,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -64.65.3,false,true,436648,European Paintings,Painting,The Huis ten Bosch at The Hague and Its Formal Garden (View from the East),,,,,,Artist,,Jan van der Heyden,"Dutch, Gorinchem 1637–1712 Amsterdam",,"Heyden, Jan van der",Dutch,1637,1712,ca. 1668–70,1668,1670,Oil on wood,15 3/8 x 21 5/8 in. (39.1 x 54.9 cm),"Gift of Edith Neuman de Végvár, in honor of her husband, Charles Neuman de Végvár, 1964",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436648,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -71.69,false,true,436806,European Paintings,Painting,Interior of a Kitchen,,,,,,Artist,,Willem Kalf,"Dutch, Rotterdam 1619–1693 Amsterdam",,"Kalf, Willem",Dutch,1619,1693,ca. 1642–44,1642,1644,Oil on wood,10 1/2 x 12 1/2 in. (26.7 x 31.8 cm),"Purchase, 1871",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436806,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -53.111,false,true,436805,European Paintings,Painting,"Still Life with Fruit, Glassware, and a Wanli Bowl",,,,,,Artist,,Willem Kalf,"Dutch, Rotterdam 1619–1693 Amsterdam",,"Kalf, Willem",Dutch,1619,1693,1659,1659,1659,Oil on canvas,23 x 20 in. (58.4 x 50.8 cm),"Maria DeWitt Jesup Fund, 1953",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436805,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -58.144,false,true,436677,European Paintings,Painting,Paying the Hostess,,,,,,Artist,,Pieter de Hooch,"Dutch, Rotterdam 1629–1684 Amsterdam",,"Hooch, Pieter de",Dutch,1629,1684,ca. 1670,1665,1675,Oil on canvas,37 1/4 x 43 3/4 in. (94.6 x 111.1 cm),"Gift of Stuart Borchard and Evelyn B. Metzger, 1958",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436677,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.7,false,true,436678,European Paintings,Painting,The Visit,,,,,,Artist,,Pieter de Hooch,"Dutch, Rotterdam 1629–1684 Amsterdam",,"Hooch, Pieter de",Dutch,1629,1684,ca. 1657,1652,1662,Oil on wood,26 3/4 x 23 in. (67.9 x 58.4 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436678,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.40.613,false,true,436675,European Paintings,Painting,Interior with a Young Couple,,,,,,Artist,,Pieter de Hooch,"Dutch, Rotterdam 1629–1684 Amsterdam",,"Hooch, Pieter de",Dutch,1629,1684,probably ca. 1662–65,1662,1665,Oil on canvas,21 5/8 x 24 3/4 in. (54.9 x 62.9 cm),"Bequest of Benjamin Altman, 1913",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436675,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -32.100.15,false,true,436676,European Paintings,Painting,"Woman with a Water Pitcher, and a Man by a Bed (""The Maidservant"")",,,,,,Artist,,Pieter de Hooch,"Dutch, Rotterdam 1629–1684 Amsterdam",,"Hooch, Pieter de",Dutch,1629,1684,ca. 1667–70,1667,1670,Oil on canvas,24 1/4 x 20 1/2 in. (61.5 x 52.1 cm),"The Friedsam Collection, Bequest of Michael Friedsam, 1931",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436676,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.100.25,false,true,436679,European Paintings,Painting,A Woman and Two Men in an Arbor,,,,,,Artist,,Pieter de Hooch,"Dutch, Rotterdam 1629–1684 Amsterdam",,"Hooch, Pieter de",Dutch,1629,1684,ca. 1657–58,1657,1658,Oil on wood,Overall 17 3/8 x 14 3/4 in. (44.1 x 37.5 cm); painted surface 17 x 14 3/8 in. (43.2 x 36.5 cm),"Bequest of Harry G. Sperling, 1971",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436679,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1980.203.5,false,true,437227,European Paintings,Painting,The Love Letter,,,,,,Artist,,Jacob Ochtervelt,"Dutch, Rotterdam 1634–1682 Amsterdam",,"Ochtervelt, Jacob",Dutch,1634,1682,early 1670s,1670,1673,Oil on canvas,36 x 25 in. (91.4 x 63.5 cm),"Gift of Mr. and Mrs. Walter Mendelsohn, 1980",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437227,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -25.110.37,false,true,436060,European Paintings,Painting,Bacchus and Nymphs in a Landscape,,,,,,Artist,,Abraham van Cuylenborch,"Dutch, Utrecht ca. 1620–1658 Utrecht",,"Cuylenborch, Abraham van",Dutch,1620,1658,probably 1640s,1640,1649,Oil on wood,22 7/8 x 28 3/8 in. (58.1 x 72.1 cm),"Bequest of Collis P. Huntington, 1900",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436060,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -71.125,false,true,435655,European Paintings,Painting,Rest,,,,,,Artist,,Nicolaes Berchem,"Dutch, Haarlem 1621/22–1683 Amsterdam",,"Berchem, Nicolaes",Dutch,1622,1683,1644,1644,1644,Oil on wood,17 x 13 1/2 in. (43.2 x 34.3 cm),"Purchase, 1871",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435655,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -89.15.4,false,true,437546,European Paintings,Painting,The Forest Stream,,,,,,Artist,,Jacob van Ruisdael,"Dutch, Haarlem 1628/29–1682 Amsterdam",,"Ruisdael, Jacob van",Dutch,1628,1682,ca. 1660,1655,1665,Oil on canvas,39 1/4 x 50 7/8 in. (99.7 x 129.2 cm),"Marquand Collection, Gift of Henry G. Marquand, 1889",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437546,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.40.623,true,true,437549,European Paintings,Painting,Wheat Fields,,,,,,Artist,,Jacob van Ruisdael,"Dutch, Haarlem 1628/29–1682 Amsterdam",,"Ruisdael, Jacob van",Dutch,1628,1682,ca. 1670,1665,1675,Oil on canvas,39 3/8 x 51 1/4 in. (100 x 130.2 cm),"Bequest of Benjamin Altman, 1913",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437549,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -25.110.18,false,true,437548,European Paintings,Painting,Mountain Torrent,,,,,,Artist,,Jacob van Ruisdael,"Dutch, Haarlem 1628/29–1682 Amsterdam",,"Ruisdael, Jacob van",Dutch,1628,1682,1670s,1670,1679,Oil on canvas,21 1/4 x 16 1/2 in. (54 x 41.9 cm),"Bequest of Collis P. Huntington, 1900",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437548,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -32.100.14,false,true,437547,European Paintings,Painting,Grainfields,,,,,,Artist,,Jacob van Ruisdael,"Dutch, Haarlem 1628/29–1682 Amsterdam",,"Ruisdael, Jacob van",Dutch,1628,1682,mid- or late 1660s,1664,1669,Oil on canvas,18 1/2 x 22 1/2 in. (47 x 57.2 cm),"The Friedsam Collection, Bequest of Michael Friedsam, 1931",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437547,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -65.181.10,false,true,437545,European Paintings,Painting,Landscape with a Village in the Distance,,,,,,Artist,,Jacob van Ruisdael,"Dutch, Haarlem 1628/29–1682 Amsterdam",,"Ruisdael, Jacob van",Dutch,1628,1682,1646,1646,1646,Oil on wood,30 x 43 in. (76.2 x 109.2 cm),"Bequest of Adele L. Lehman, in memory of Arthur Lehman, 1965",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437545,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1974.368,false,true,436672,European Paintings,Painting,Christ among the Doctors,,,,,,Artist,,Abraham Hondius,"Dutch, Rotterdam ca. 1631–1691 London",,"Hondius, Abraham",Dutch,1631,1691,1668,1668,1668,Oil on wood,15 x 19 1/2 in. (38.1 x 49.5 cm),"Gift of Dr. and Mrs. Carl F. Culicchia, 1974",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436672,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2001.403,false,true,438490,European Paintings,Painting,"Interior of the Oude Kerk, Delft",,,,,,Artist,,Emanuel de Witte,"Dutch, Alkmaar ca. 1616–1692 Amsterdam",,"Witte, Emanuel de",Dutch,1616,1692,probably 1650,1650,1650,Oil on wood,19 x 13 5/8 in. (48.3 x 34.6 cm),"Purchase, Lila Acheson Wallace, Virgilia and Walter C. Klein, The Walter C. Klein Foundation, Edwin Weisl Jr., and Frank E. Richardson Gifts, and Bequest of Theodore Rousseau and Gift of Lincoln Kirstein, by exchange, 2001",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/438490,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -71.60,false,true,437190,European Paintings,Painting,The Farrier,,,,,,Artist,,Aert van der Neer,"Dutch, Gorinchem 1603/4–1677 Amsterdam",,"Neer, Aert van der",Dutch,1603,1677,early or mid-1650s,1650,1656,Oil on wood,19 x 24 1/8 in. (48.3 x 61.3 cm),"Purchase, 1871",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437190,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -17.190.11,false,true,437191,European Paintings,Painting,Landscape at Sunset,,,,,,Artist,,Aert van der Neer,"Dutch, Gorinchem 1603/4–1677 Amsterdam",,"Neer, Aert van der",Dutch,1603,1677,1650s,1650,1659,Oil on canvas,20 x 28 1/8 in. (50.8 x 71.4 cm),"Gift of J. Pierpont Morgan, 1917",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437191,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -32.100.11,false,true,437192,European Paintings,Painting,Sports on a Frozen River,,,,,,Artist,,Aert van der Neer,"Dutch, Gorinchem 1603/4–1677 Amsterdam",,"Neer, Aert van der",Dutch,1603,1677,probably ca. 1660,1655,1665,Oil on wood,9 1/8 x 13 3/4 in. (23.2 x 34.9 cm),"The Friedsam Collection, Bequest of Michael Friedsam, 1931",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437192,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -89.15.6,false,true,437218,European Paintings,Painting,The Card Party,,,,,,Artist,,Caspar Netscher,"Dutch, Heidelberg 1639?–1684 The Hague",,"Netscher, Caspar",Dutch,1639,1684,ca. 1665,1660,1670,Oil on canvas,19 3/4 x 17 3/4 in. (50.2 x 45.1 cm),"Marquand Collection, Gift of Henry G. Marquand, 1889",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437218,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -53.155,false,true,436976,European Paintings,Painting,"Still Life with Poppy, Insects, and Reptiles",,,,,,Artist,,Otto Marseus van Schrieck,"Dutch, Nijmegen 1619/20–1678 Amsterdam",,"Marseus van Schrieck, Otto",Dutch,1619,1678,ca. 1670,1665,1675,Oil on canvas,26 7/8 x 20 3/4 in. (68.3 x 52.7 cm),"Rogers Fund, 1953",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436976,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -89.15.27,false,true,436630,European Paintings,Painting,Portrait of a Woman,,,,,,Artist,,Adriaen Hanneman,"Dutch, The Hague 1603/4–1671 The Hague",,"Hanneman, Adriaen",Dutch,1603,1671,ca. 1653,1648,1658,Oil on canvas,31 1/2 x 25 in. (80 x 63.5 cm),"Marquand Collection, Gift of Henry G. Marquand, 1889",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436630,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.100.22,false,true,437282,European Paintings,Painting,Man with a Celestial Globe,,,,,,Artist,,Nicolaes Eliasz Pickenoy,"Dutch, Amsterdam 1588–1650/56 Amsterdam",,"Pickenoy, Nicolaes Eliasz",Dutch,1588,1656,1624,1624,1624,Oil on wood,41 1/4 x 30 in. (104.8 x 76.2 cm),"Bequest of Harry G. Sperling, 1971",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437282,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -32.100.12,false,true,435723,European Paintings,Painting,Barnyard Scene,,,,,,Artist,,Anthonie van Borssom,"Dutch, Amsterdam 1630/31–1677 Amsterdam",,"Borssom, Anthonie van",Dutch,1631,1677,ca. 1650–55,1650,1655,Oil on canvas,20 x 27 in. (50.8 x 68.6 cm),"The Friedsam Collection, Bequest of Michael Friedsam, 1931",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435723,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -06.1289,false,true,437090,European Paintings,Painting,"Charles I (1600–1649), King of England",,,,,,Artist,,Daniël Mijtens,"Dutch, Delft ca. 1590–1647/48 The Hague",,"Mijtens, Daniël",Dutch,1585,1648,1629,1629,1629,Oil on canvas,78 7/8 x 55 3/8 in. (200.3 x 140.7 cm),"Gift of George A. Hearn, 1906",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437090,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -89.15.7,false,true,437703,European Paintings,Painting,A Kitchen,,,,,,Artist,,Hendrick Sorgh,"Dutch, Rotterdam 1609/11–1670 Rotterdam",,"Sorgh, Hendrick",Dutch,1609,1670,ca. 1643,1638,1648,Oil on wood,20 1/2 x 17 3/8 in. (52.1 x 44.1 cm),"Marquand Collection, Gift of Henry G. Marquand, 1889",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437703,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1971.254,false,true,435663,European Paintings,Painting,Still Life with Lobster and Fruit,,,,,,Artist,,Abraham van Beyeren,"Dutch, The Hague 1620/21–1690 Overschie",,"Beyeren, Abraham van",Dutch,1620,1690,probably early 1650s,1650,1653,Oil on wood,38 x 31 in. (96.5 x 78.7 cm),"Gift of Edith Neuman de Végvár, in honor of her husband, Charles Neuman de Végvár, 1971",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435663,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -20.155.5,false,true,436784,European Paintings,Painting,Scene in a Courtyard,,,,,,Artist,,Ludolf de Jongh,"Dutch, Overschie 1616–1679 Hillegersberg",,"Jongh, Ludolf de",Dutch,1616,1679,early 1660s,1660,1663,Oil on canvas,26 1/2 x 32 3/8 in. (67.3 x 82.2 cm),"Bequest of William K. Vanderbilt, 1920",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436784,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -49.30,true,true,437980,European Paintings,Painting,Cypresses,,,,,,Artist,,Vincent van Gogh,"Dutch, Zundert 1853–1890 Auvers-sur-Oise",,"Gogh, Vincent van",Dutch,1853,1890,1889,1889,1889,Oil on canvas,36 3/4 x 29 1/8 in. (93.4 x 74 cm),"Rogers Fund, 1949",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437980,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -49.41,false,true,436524,European Paintings,Painting,Sunflowers,,,,,,Artist,,Vincent van Gogh,"Dutch, Zundert 1853–1890 Auvers-sur-Oise",,"Gogh, Vincent van",Dutch,1853,1890,1887,1887,1887,Oil on canvas,17 x 24 in. (43.2 x 61 cm),"Rogers Fund, 1949",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436524,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -58.187,false,true,436528,European Paintings,Painting,Irises,,,,,,Artist,,Vincent van Gogh,"Dutch, Zundert 1853–1890 Auvers-sur-Oise",,"Gogh, Vincent van",Dutch,1853,1890,1890,1890,1890,Oil on canvas,29 x 36 1/4 in. (73.7 x 92.1 cm),"Gift of Adele R. Levy, 1958",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436528,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1984.393,false,true,436531,European Paintings,Painting,Peasant Woman Cooking by a Fireplace,,,,,,Artist,,Vincent van Gogh,"Dutch, Zundert 1853–1890 Auvers-sur-Oise",,"Gogh, Vincent van",Dutch,1853,1890,1885,1885,1885,Oil on canvas,17 3/8 x 15 in. (44.1 x 38.1 cm),"Gift of Mr. and Mrs. Mortimer Hays, 1984",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436531,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1993.132,true,true,436535,European Paintings,Painting,Wheat Field with Cypresses,,,,,,Artist,,Vincent van Gogh,"Dutch, Zundert 1853–1890 Auvers-sur-Oise",,"Gogh, Vincent van",Dutch,1853,1890,1889,1889,1889,Oil on canvas,28 7/8 × 36 3/4 in. (73.2 × 93.4 cm),"Purchase, The Annenberg Foundation Gift, 1993",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436535,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1995.535,false,true,436536,European Paintings,Painting,Women Picking Olives,,,,,,Artist,,Vincent van Gogh,"Dutch, Zundert 1853–1890 Auvers-sur-Oise",,"Gogh, Vincent van",Dutch,1853,1890,1889,1889,1889,Oil on canvas,28 5/8 x 36 in. (72.7 x 91.4 cm),"The Walter H. and Leonore Annenberg Collection, Gift of Walter H. and Leonore Annenberg, 1995, Bequest of Walter H. Annenberg, 2002",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436536,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1996.435,false,true,437984,European Paintings,Painting,"La Berceuse (Woman Rocking a Cradle; Augustine-Alix Pellicot Roulin, 1851–1930)",,,,,,Artist,,Vincent van Gogh,"Dutch, Zundert 1853–1890 Auvers-sur-Oise",,"Gogh, Vincent van",Dutch,1853,1890,1889,1889,1889,Oil on canvas,36 1/2 x 29 in. (92.7 x 73.7 cm),"The Walter H. and Leonore Annenberg Collection, Gift of Walter H. and Leonore Annenberg, 1996, Bequest of Walter H. Annenberg, 2002",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437984,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -51.112.3,false,true,436529,European Paintings,Painting,"L'Arlésienne: Madame Joseph-Michel Ginoux (Marie Julien, 1848–1911)",,,,,,Artist,,Vincent van Gogh,"Dutch, Zundert 1853–1890 Auvers-sur-Oise",,"Gogh, Vincent van",Dutch,1853,1890,1888–89,1888,1889,Oil on canvas,36 x 29 in. (91.4 x 73.7 cm),"Bequest of Sam A. Lewisohn, 1951",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436529,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -64.165.2,false,true,436526,European Paintings,Painting,"First Steps, after Millet",,,,,,Artist,,Vincent van Gogh,"Dutch, Zundert 1853–1890 Auvers-sur-Oise",,"Gogh, Vincent van",Dutch,1853,1890,1890,1890,1890,Oil on canvas,28 1/2 x 35 7/8 in. (72.4 x 91.1 cm),"Gift of George N. and Helen M. Richard, 1964",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436526,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1993.400.4,false,true,436525,European Paintings,Painting,Bouquet of Flowers in a Vase,,,,,,Artist,,Vincent van Gogh,"Dutch, Zundert 1853–1890 Auvers-sur-Oise",,"Gogh, Vincent van",Dutch,1853,1890,1890,1890,1890,Oil on canvas,25 5/8 x 21 1/4 in. (65.1 x 54 cm),"The Walter H. and Leonore Annenberg Collection, Gift of Walter H. and Leonore Annenberg, 1993, Bequest of Walter H. Annenberg, 2002",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436525,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1993.400.5,false,true,436534,European Paintings,Painting,Roses,,,,,,Artist,,Vincent van Gogh,"Dutch, Zundert 1853–1890 Auvers-sur-Oise",,"Gogh, Vincent van",Dutch,1853,1890,1890,1890,1890,Oil on canvas,36 5/8 x 29 1/8 in. (93 x 74 cm),"The Walter H. and Leonore Annenberg Collection, Gift of Walter H. and Leonore Annenberg, 1993, Bequest of Walter H. Annenberg, 2002",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436534,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1998.325.1,false,true,437998,European Paintings,Painting,Olive Trees,,,,,,Artist,,Vincent van Gogh,"Dutch, Zundert 1853–1890 Auvers-sur-Oise",,"Gogh, Vincent van",Dutch,1853,1890,1889,1853,1890,Oil on canvas,28 5/8 x 36 1/4 in. (72.7 x 92.1 cm),"The Walter H. and Leonore Annenberg Collection, Gift of Walter H. and Leonore Annenberg, 1998, Bequest of Walter H. Annenberg, 2002",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437998,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -67.187.70a,false,true,436532,European Paintings,Painting,Self-Portrait with a Straw Hat (obverse: The Potato Peeler),,,,,,Artist,,Vincent van Gogh,"Dutch, Zundert 1853–1890 Auvers-sur-Oise",,"Gogh, Vincent van",Dutch,1853,1890,1887,1887,1887,Oil on canvas,16 x 12 1/2 in. (40.6 x 31.8 cm),"Bequest of Miss Adelaide Milton de Groot (1876–1967), 1967",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436532,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -67.187.70b,false,true,438722,European Paintings,Painting,The Potato Peeler (reverse: Self-Portrait with a Straw Hat),,,,,,Artist,,Vincent van Gogh,"Dutch, Zundert 1853–1890 Auvers-sur-Oise",,"Gogh, Vincent van",Dutch,1853,1890,1885,1885,1885,Oil on canvas,16 x 12 1/2 in. (40.6 x 31.8 cm),"Bequest of Miss Adelaide Milton de Groot (1876–1967), 1967",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/438722,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -71.6,false,true,436634,European Paintings,Painting,A Vase of Flowers,,,,,,Artist,,Margareta Haverman,"Dutch, active by 1716–died 1722 or later",,"Haverman, Margareta",Dutch,1716,1722,1716,1716,1716,Oil on wood,31 1/4 x 23 3/4 in. (79.4 x 60.3 cm),"Purchase, 1871",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436634,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -71.170,false,true,437897,European Paintings,Painting,Abraham's Parting from the Family of Lot,,,,,,Artist,,Jan Victors,"Dutch, Amsterdam 1619–1676/77 East Indies",,"Victors, Jan",Dutch,1619,1677,ca. 1655–65,1655,1665,Oil on canvas,58 x 65 1/8 in. (147.3 x 165.4 cm),"Purchase, 1871",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437897,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -50.55,false,true,437937,European Paintings,Painting,"Gamepiece with a Dead Heron (""Falconer's Bag"")",,,,,,Artist,,Jan Weenix,"Dutch, Amsterdam ca. 1641?–1719 Amsterdam",,"Weenix, Jan",Dutch,1641,1719,1695,1695,1695,Oil on canvas,52 3/4 x 43 3/4 in. (134 x 111.1 cm),"Rogers Fund, 1950",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437937,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.100.23,false,true,436288,European Paintings,Painting,Abraham Dismissing Hagar and Ishmael,,,,,,Artist,,Barent Fabritius,"Dutch, Middenbeemster 1624–1673 Amsterdam",,"Fabritius, Barent",Dutch,1624,1673,1658,1658,1658,Oil on wood,19 1/2 x 14 in. (49.5 x 35.6 cm),"Bequest of Harry G. Sperling, 1971",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436288,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.40.609,false,true,437412,European Paintings,Painting,Old Woman Cutting Her Nails,,,,,,Artist,Style of,Rembrandt,"Dutch, second or third quarter 17th century",,Rembrandt,Dutch,1606,1669,ca. 1655–60,1655,1660,Oil on canvas,49 5/8 x 40 1/8 in. (126.1 x 101.9 cm),"Bequest of Benjamin Altman, 1913",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437412,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.261,false,true,435713,European Paintings,Painting,"The Disillusioned Medea (""The Enchantress"")",,,,,,Artist,,Paulus Bor,"Dutch, Amersfoort ca. 1601–1669 Amersfoort",,"Bor, Paulus",Dutch,1601,1669,ca. 1640,1635,1645,Oil on canvas,61 1/4 x 44 1/4 in. (155.6 x 112.4 cm),"Gift of Ben Heller, 1972",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435713,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1973.155.1,false,true,437352,European Paintings,Painting,A Party of Merrymakers,,,,,,Artist,,Pieter Jansz. Quast,"Dutch, Amsterdam (?) 1605/6–1647 Amsterdam",,"Quast, Pieter Jansz.",Dutch,1605,1647,ca. 1635–38,1635,1638,Oil on wood,14 3/4 x 19 1/2 in. (37.5 x 49.5 cm),"Bequest of Josephine Bieber, in memory of her husband, Siegfried Bieber, 1970",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437352,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -64.65.4,false,true,436818,European Paintings,Painting,A Musician and His Daughter,,,,,,Artist,,Thomas de Keyser,"Dutch, Amsterdam (?) 1596/97–1667 Amsterdam",,"Keyser, Thomas de",Dutch,1596,1667,1629,1629,1629,Oil on wood,29 1/2 x 20 3/4 in. (74.9 x 52.7 cm),"Gift of Edith Neuman de Végvár, in honor of her husband, Charles Neuman de Végvár, 1964",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436818,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.331.5,false,true,438377,European Paintings,Painting,Portrait of a Man with a Shell,,,,,,Artist,,Thomas de Keyser,"Dutch, Amsterdam (?) 1596/97–1667 Amsterdam",,"Keyser, Thomas de",Dutch,1596,1667,ca. 1625–26,1625,1626,Oil on wood,9 3/8 x 6 3/4 in. (23.8 x 17.1 cm),"From the Collection of Rita and Frits Markus, Bequest of Rita Markus, 2005",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/438377,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.331.6,false,true,438378,European Paintings,Painting,Portrait of a Woman with a Balance,,,,,,Artist,,Thomas de Keyser,"Dutch, Amsterdam (?) 1596/97–1667 Amsterdam",,"Keyser, Thomas de",Dutch,1596,1667,ca. 1625–26,1625,1626,Oil on wood,9 1/8 x 6 7/8 in. (23.2 x 17.5 cm),"From the Collection of Rita and Frits Markus, Bequest of Rita Markus, 2005",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/438378,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -12.202,false,true,437374,European Paintings,Painting,Portrait of a Woman,,,,,,Artist,,Jan van Ravesteyn,"Dutch, Culemborg (?) ca. 1572–1657 The Hague",,"Ravesteyn, Jan van",Dutch,1572,1657,1635,1635,1635,Oil on wood,26 7/8 x 22 7/8 in. (68.3 x 58.1 cm),"Gift of Henry Goldman, 1912",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437374,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -16.39,false,true,436785,European Paintings,Painting,Honfleur,,,,,,Artist,,Johan Barthold Jongkind,"Dutch, Latrop 1819–1891 La-Côte-Saint-André",,"Jongkind, Johan Barthold",Dutch,1819,1891,1865,1865,1865,Oil on canvas,20 1/2 x 32 1/8 in. (52.1 x 81.6 cm),"Catharine Lorillard Wolfe Collection, Wolfe Fund, 1916",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436785,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2001.652,false,true,438381,European Paintings,Painting,View from the Quai d'Orsay,,,,,,Artist,,Johan Barthold Jongkind,"Dutch, Latrop 1819–1891 La-Côte-Saint-André",,"Jongkind, Johan Barthold",Dutch,1819,1891,1854,1854,1854,"Oil on canvas, mounted on wood",17 1/4 x 26 in. (43.8 x 66 cm),"Bequest of Meta Cecile Schwarz, 2001",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/438381,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1980.203.3,false,true,436787,European Paintings,Painting,The Pont Neuf,,,,,,Artist,,Johan Barthold Jongkind,"Dutch, Latrop 1819–1891 La-Côte-Saint-André",,"Jongkind, Johan Barthold",Dutch,1819,1891,1849–50,1849,1850,Oil on canvas,21 1/2 x 32 1/8 in. (54.6 x 81.6 cm),"Gift of Mr. and Mrs. Walter Mendelsohn, 1980",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436787,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -06.1200,false,true,437914,European Paintings,Painting,Calm Sea,,,,,,Artist,,Simon de Vlieger,"Dutch, Rotterdam (?) ca. 1600/1601–1653 Weesp",,"Vlieger, Simon de",Dutch,1600,1653,after 1640,1640,1653,Oil on wood,14 3/4 x 17 1/2 in. (37.5 x 44.5 cm),"Rogers Fund, 1906",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437914,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -39.65.7,false,true,437945,European Paintings,Painting,Portrait of a Woman,,,,,,Artist,,Willem Wissing,"Dutch, Amsterdam or The Hague 1656–1687 Stamford",,"Wissing, Willem",Dutch,1656,1687,ca. 1687,1682,1687,Oil on canvas,49 3/4 x 40 1/4 in. (126.4 x 102.2 cm),"Bequest of Jacob Ruppert, 1939",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437945,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -71.110,false,true,435771,European Paintings,Painting,The Spinner,,,,,,Artist,,Quirijn van Brekelenkam,"Dutch, Zwammerdam (?), after 1622–ca. 1669 Leiden",,"Brekelenkam, Quirijn van",Dutch,1622,1669,1653,1653,1653,Oil on wood,19 x 25 1/4 in. (48.3 x 64.1 cm),"Purchase, 1871",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435771,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -32.100.19,false,true,435772,European Paintings,Painting,Sentimental Conversation,,,,,,Artist,,Quirijn van Brekelenkam,"Dutch, Zwammerdam (?), after 1622–ca. 1669 Leiden",,"Brekelenkam, Quirijn van",Dutch,1622,1669,early 1660s,1660,1663,Oil on wood,16 1/4 x 13 7/8 in. (41.3 x 35.2 cm),"The Friedsam Collection, Bequest of Michael Friedsam, 1931",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435772,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -71.19,false,true,435918,European Paintings,Painting,Vanitas Still Life,,,,,,Artist,,Edwaert Collier,"Dutch, Breda ca. 1640?–after 1707 London or Leiden",,"Collier, Edwaert",Dutch,1640,1707,1662,1662,1662,Oil on wood,37 x 44 1/8 in. (94 x 112.1 cm),"Purchase, 1871",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435918,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.162.1,false,true,437893,European Paintings,Painting,Portrait of a Man,,,,,,Artist,,Johannes Verspronck,"Dutch, Haarlem, born ca. 1601–3, died 1662 Haarlem",,"Verspronck, Johannes",Dutch,1601,1662,1645,1645,1645,Oil on canvas,31 1/4 x 25 1/4 in. (79.4 x 64.1 cm),"Bequest of Susan P. Colgate, in memory of her husband, Romulus R. Colgate, 1936",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437893,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -71.63,false,true,437921,European Paintings,Painting,Portrait of a Man,,,,,,Artist,,Abraham de Vries,"Dutch, The Hague (?) ca. 1590–1649/50 The Hague (?)",,"Vries, Abraham de",Dutch,1585,1650,1643,1643,1643,Oil on wood,25 1/4 x 21 in. (64.1 x 53.3 cm),"Purchase, 1871",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437921,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -73.2,false,true,436642,European Paintings,Painting,The Musician,,,,,,Artist,,Bartholomeus van der Helst,"Dutch, Haarlem, born ca. 1612–15, died 1670 Amsterdam",,"Helst, Bartholomeus van der",Dutch,1612,1670,1662,1662,1662,Oil on canvas,54 1/2 x 43 3/4 in. (138.4 x 111.1 cm),"Purchase, 1873",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436642,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -71.73,false,true,436641,European Paintings,Painting,Portrait of a Man,,,,,,Artist,,Bartholomeus van der Helst,"Dutch, Haarlem, born ca. 1612–15, died 1670 Amsterdam",,"Helst, Bartholomeus van der",Dutch,1612,1670,1647,1647,1647,Oil on wood,"Oval, 26 1/4 x 21 5/8 in. (66.7 x 54.9 cm)","Purchase, 1871",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436641,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -71.75,false,true,437588,European Paintings,Painting,Drawing the Eel,,,,,,Artist,,Salomon van Ruysdael,"Dutch, Naarden, born ca. 1600–1603, died 1670 Haarlem",,"Ruysdael, Salomon van",Dutch,1600,1670,early 1650s,1650,1653,Oil on wood,29 1/2 x 41 3/4 in. (74.9 x 106 cm),"Purchase, 1871",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437588,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -71.98,false,true,437587,European Paintings,Painting,Marine,,,,,,Artist,,Salomon van Ruysdael,"Dutch, Naarden, born ca. 1600–1603, died 1670 Haarlem",,"Ruysdael, Salomon van",Dutch,1600,1670,1650,1650,1650,Oil on wood,13 5/8 x 17 1/8 in. (34.6 x 43.5 cm),"Purchase, 1871",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437587,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -06.1201,false,true,437586,European Paintings,Painting,A Country Road,,,,,,Artist,,Salomon van Ruysdael,"Dutch, Naarden, born ca. 1600–1603, died 1670 Haarlem",,"Ruysdael, Salomon van",Dutch,1600,1670,1648,1648,1648,Oil on canvas,38 7/8 x 52 7/8 in. (98.7 x 134.3 cm),"Rogers Fund, 1906",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437586,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -15.30.4,false,true,437589,European Paintings,Painting,Ferry near Gorinchem,,,,,,Artist,,Salomon van Ruysdael,"Dutch, Naarden, born ca. 1600–1603, died 1670 Haarlem",,"Ruysdael, Salomon van",Dutch,1600,1670,1646,1646,1646,Oil on canvas,41 7/8 x 52 1/2 in. (106.4 x 133.4 cm),"Bequest of Maria DeWitt Jesup, from the collection of her husband, Morris K. Jesup, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437589,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -60.55.4,false,true,437585,European Paintings,Painting,Market by the Seashore,,,,,,Artist,,Salomon van Ruysdael,"Dutch, Naarden, born ca. 1600–1603, died 1670 Haarlem",,"Ruysdael, Salomon van",Dutch,1600,1670,1637,1637,1637,Oil on wood,16 x 23 3/8 in. (40.6 x 59.4 cm),"Bequest of Rupert L. Joseph, 1959",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437585,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.331.8,false,true,438380,European Paintings,Painting,Fishing Boats on a River,,,,,,Artist,,Salomon van Ruysdael,"Dutch, Naarden, born ca. 1600–1603, died 1670 Haarlem",,"Ruysdael, Salomon van",Dutch,1600,1670,early 1660s,1660,1663,Oil on wood,"Overall, with added strips, 14 3/4 x 21 7/8 in. (37.5 x 55.6 cm); painted surface 14 1/4 x 21 1/4 in. (36.2 x 54 cm)","From the Collection of Rita and Frits Markus, Bequest of Rita Markus, 2005",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/438380,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1971.102,false,true,436221,European Paintings,Painting,A Couple in an Interior with a Gypsy Fortune-Teller,,,,,,Artist,,Jacob Duck,"Dutch, Utrecht, born ca. 1598–1600, died 1667 Utrecht",,"Duck, Jacob",Dutch,1598,1667,ca. 1632–33,1632,1633,Oil on wood,"Oval, 9 7/8 x 13 in. (25.1 x 33 cm)","Gift of Dr. and Mrs. Richard W. Levy, 1971",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436221,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.331.1,false,true,438373,European Paintings,Painting,A Winter Landscape with Ice Skaters and an Imaginary Castle,,,,,,Artist,,Christoffel van den Berghe,"Dutch, Antwerp ca. 1590–1628 or later, active Middelburg",,"Berghe, Christoffel van den",Dutch,1590,1628,ca. 1615–20,1615,1620,Oil on wood,"Overall, with added strips, 11 x 18 3/8 in. (27.9 x 46.7 cm); painted surface 10 3/4 x 18 in. (27.3 x 45.7 cm)","From the Collection of Rita and Frits Markus, Bequest of Rita Markus, 2005",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/438373,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1981.25,false,true,437757,European Paintings,Painting,Old Woman Praying,,,,,,Artist,,Matthias Stom,"Dutch, Amersfoort?, born ca. 1599–1600, died after 1652 ?Italy",,"Stom, Matthias",Dutch,1599,1652,late 1630s or early 1640s,1637,1643,Oil on canvas,30 5/8 x 25 1/8 in. (77.8 x 63.8 cm),"Gift of Ian Woodner, 1981",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437757,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -32.75.15,false,true,435625,European Paintings,"Painting, miniature",Portrait of a Woman,,,,,,Artist,,David Baudringhien,"Dutch, ca. 1581–1650",,"Baudringhien, David",Dutch,1581,1650,,1601,1650,Oil on copper,"Oval, 3 3/4 x 3 in. (95 x 76 mm)","The Collection of Giovanni P. Morosini, presented by his daughter Giulia, 1932",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/435625,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -17.120.222,false,true,437404,European Paintings,Painting,Head of Christ,,,,,,Artist,Style of,Rembrandt,"Dutch, 1650s",,Rembrandt,Dutch,1606,1669,,1626,1669,Oil on canvas,16 3/4 x 13 1/2 in. (42.5 x 34.3 cm); with added strips 18 5/8 x 14 5/8 in. (47.3 x 37.1 cm),"Mr. and Mrs. Isaac D. Fletcher Collection, Bequest of Isaac D. Fletcher, 1917",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437404,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -49.7.40,false,true,437882,European Paintings,Painting,A Young Woman Reading,,,,,,Artist,Imitator of,Johannes Vermeer,ca. 1925–27,,"Vermeer, Johannes",Dutch,1632,1675,,1900,1924,Oil on canvas,7 3/4 x 5 3/4 in. (19.7 x 14.6 cm),"The Jules Bache Collection, 1949",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437882,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -60.71.14,false,true,437403,European Paintings,Painting,Christ and the Woman of Samaria,,,,,,Artist,Style of,Rembrandt,"Dutch, ca. 1655",,Rembrandt,Dutch,1606,1669,,1650,1660,Oil on wood,25 x 19 1/4 in. (63.5 x 48.9 cm),"Bequest of Lillian S. Timken, 1959",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437403,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -07.225.257,false,true,437949,European Paintings,"Painting, grisaille",Children Playing with a Goat,,,,,,Artist,Style of,Jacob de Wit,"Dutch, 18th century",,"Wit, Jacob de",Dutch,1695,1754,,1715,1754,Oil on canvas,26 3/4 x 41 in. (67.9 x 104.1 cm),"Gift of J. Pierpont Morgan, 1906",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437949,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -32.100.16,false,true,435843,European Paintings,Painting,Winter Scene,,,,,,Artist,Style of,Jan van de Cappelle,18th or 19th century,,"Cappelle, Jan van de",Dutch,1626,1679,,1644,1679,Oil on oak,13 3/8 x 19 1/2 in. (34 x 49.5 cm),"The Friedsam Collection, Bequest of Michael Friedsam, 1931",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435843,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -53.18,false,true,437419,European Paintings,Painting,Rembrandt (1606–1669) as a Young Man,,,,,,Artist,Style of,Rembrandt,"Dutch, ca. 1630–35",,Rembrandt,Dutch,1606,1669,,1600,1699,Oil on wood,8 5/8 x 6 1/2 in. (21.9 x 16.5 cm),"Bequest of Evander B. Schley, 1952",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437419,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -89.15.3,false,true,437418,European Paintings,Painting,Man with a Beard,,,,,,Artist,Style of,Rembrandt,17th century or later,,Rembrandt,Dutch,1606,1669,,1600,1889,Oil on canvas,28 7/8 x 25 1/4 in. (73.3 x 64.1 cm),"Marquand Collection, Gift of Henry G. Marquand, 1889",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437418,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.40.608,false,true,437420,European Paintings,Painting,Rembrandt's Son Titus (1641–1668),,,,,,Artist,Style of,Rembrandt,17th century or later,,Rembrandt,Dutch,1606,1669,,1650,1883,Oil on canvas,31 1/8 x 23 1/4 in. (79.1 x 59.1 cm),"Bequest of Benjamin Altman, 1913",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437420,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -65.181.11,false,true,436560,European Paintings,Painting,River View with a Village Church,,,,,,Artist,Style of,Jan van Goyen,"Dutch, mid-17th century",,"Goyen, Jan van",Dutch,1596,1656,,1630,1669,Oil on canvas,25 1/2 x 38 1/2 in. (64.8 x 97.8 cm),"Bequest of Adele L. Lehman, in memory of Arthur Lehman, 1965",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436560,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -21.134.5,false,true,437072,European Paintings,Painting,Tavern Scene,,,,,,Artist,Copy after,Gabriël Metsu,"Dutch, late 17th century",,"Metsu, Gabriël",Dutch,1629,1667,,1649,1667,Oil on wood,14 3/8 x 12 5/8 in. (36.5 x 32.1 cm),"Bequest of William H. Herriman, 1920",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437072,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -60.71.16,false,true,437421,European Paintings,Painting,Study Head of an Old Man,,,,,,Artist,Style of,Rembrandt,"Dutch, mid- to late 1630s",,Rembrandt,Dutch,1606,1669,,1600,1699,Oil on wood,8 1/4 x 6 7/8 in. (21 x 17.5 cm),"Bequest of Lillian S. Timken, 1959",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437421,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.102,false,true,437410,European Paintings,Painting,Portrait of a Man with a Breastplate and Plumed Hat,,,,,,Artist,Style of,Rembrandt,"Dutch, mid- to late 1640s",,Rembrandt,Dutch,1606,1669,,1625,1674,Oil on canvas,47 3/4 x 38 3/4 in. (121.3 x 98.4 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437410,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.103,false,true,437409,European Paintings,Painting,Portrait of a Woman,,,,,,Artist,Style of,Rembrandt,"Dutch, mid- to late 1640s",,Rembrandt,Dutch,1606,1669,,1625,1674,Oil on canvas,47 5/8 x 38 5/8 in. (121 x 98.1 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437409,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -49.7.36,false,true,437413,European Paintings,Painting,Man in a Red Cloak,,,,,,Artist,Style of,Rembrandt,"Dutch, 1650s or early 1660s",,Rembrandt,Dutch,1606,1669,,1625,1674,Oil on wood,15 1/8 x 12 1/4 in. (38.4 x 31.1 cm),"The Jules Bache Collection, 1949",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437413,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.198,false,true,437233,European Paintings,Painting,Man with a Tankard,,,,,,Artist,Style of,Adriaen van Ostade,"Dutch, second half 17th century",,"Ostade, Adriaen van",Dutch,1610,1685,,1650,1699,Oil on wood,10 1/8 x 8 1/2 in. (25.7 x 21.6 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437233,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1985.88,false,true,437043,European Paintings,Painting,A Shepherdess and Her Flock,,,,,,Artist,,Anton Mauve,"Dutch, Zaandam 1838–1888 Arnhem",,"Mauve, Anton",Dutch,1838,1888,,1858,1888,Oil on canvas,17 7/8 x 25 1/4 in. (45.4 x 64.1 cm),"Gift of Cole J. Younger, 1985",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437043,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -43.86.8,false,true,437041,European Paintings,Painting,Gathering Wood,,,,,,Artist,,Anton Mauve,"Dutch, Zaandam 1838–1888 Arnhem",,"Mauve, Anton",Dutch,1838,1888,,1858,1888,Oil on canvas,16 1/2 x 13 in. (41.9 x 33 cm),"Bequest of Richard De Wolfe Brixey, 1943",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437041,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.40.812,false,true,437044,European Paintings,Painting,Twilight,,,,,,Artist,,Anton Mauve,"Dutch, Zaandam 1838–1888 Arnhem",,"Mauve, Anton",Dutch,1838,1888,,1858,1888,Oil on canvas,25 7/8 x 17 7/8 in. (65.7 x 45.4 cm),"Bequest of Benjamin Altman, 1913",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437044,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.40.816,false,true,437042,European Paintings,Painting,The Return to the Fold,,,,,,Artist,,Anton Mauve,"Dutch, Zaandam 1838–1888 Arnhem",,"Mauve, Anton",Dutch,1838,1888,,1858,1888,Oil on canvas,19 3/4 x 33 7/8 in. (50.2 x 86 cm),"Bequest of Benjamin Altman, 1913",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437042,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -49.7.37,false,true,437407,European Paintings,Painting,Christ with a Staff,,,,,,Artist,Follower of,Rembrandt,"Dutch, third quarter 17th century",,Rembrandt,Dutch,1606,1669,,1650,1674,Oil on canvas,37 1/2 x 32 1/2 in. (95.3 x 82.6 cm),"The Jules Bache Collection, 1949",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437407,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -60.71.15,false,true,437405,European Paintings,Painting,"A Young Woman as a Shepherdess (""Saskia as Flora"")",,,,,,Artist,,Govert Flinck,"Dutch, Cleve 1615–1660 Amsterdam",,"Flinck, Govert",Dutch,1615,1660,,1635,1660,"Oil on canvas, transferred from wood","Oval, 26 1/4 x 19 7/8 in. (66.7 x 50.5 cm)","Bequest of Lillian S. Timken, 1959",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437405,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.373,false,true,437408,European Paintings,Painting,Man in a Beret,,,,,,Artist,Style of,Rembrandt,"Dutch, fourth quarter 17th century",,Rembrandt,Dutch,1606,1669,,1675,1699,Oil on canvas,29 7/8 x 24 3/4 in. (75.9 x 62.9 cm),"Gift of Charles S. Payson, 1975",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437408,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -71.76,false,true,436628,European Paintings,Painting,Malle Babbe,,,,,,Artist,Style of,Frans Hals,"Dutch, second quarter 17th century",,"Hals, Frans",Dutch,1582,1666,,1625,1649,Oil on canvas,29 1/2 x 24 in. (74.9 x 61 cm),"Purchase, 1871",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436628,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.2,false,true,435597,European Paintings,Painting,Portrait of an Old Woman,,,,,,Artist,Style of,Jacob Backer,"Dutch, second quarter 17th century",,"Backer, Jacob",Dutch,1608,1651,,1625,1649,Oil on wood,28 x 24 in. (71.1 x 61 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435597,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -71.116,false,true,437922,European Paintings,Painting,The Pigeon House,,,,,,Artist,,Roelof van Vries,"Dutch, Haarlem 1630/31–after 1681",,"Vries, Roelof van",Dutch,1630,1681,,1650,1681,Oil on canvas,14 1/2 x 12 in. (36.8 x 30.5 cm),"Purchase, 1871",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437922,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -50.145.33,false,true,437417,European Paintings,Painting,"Lieven Willemsz van Coppenol (born about 1599, died 1671 or later)",,,,,,Artist,,Rembrandt (Rembrandt van Rijn),"Dutch, Leiden 1606–1669 Amsterdam",,Rembrandt (Rembrandt van Rijn),Dutch,1606,1669,,1626,1669,Oil on wood,14 3/8 x 11 3/8 in. (36.5 x 28.9 cm),"Bequest of Mary Stillman Harkness, 1950",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437417,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.136.3,false,true,436974,European Paintings,Painting,Canal Side,,,,,,Artist,,Jacob Maris,"Dutch, The Hague 1837–1899 Karlsbad",,"Maris, Jacob",Dutch,1837,1899,,1857,1899,Oil on canvas,5 3/8 x 7 in. (13.7 x 17.8 cm),"Bequest of Margaret Crane Hurlbut, 1933",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436974,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -11.92,false,true,435633,European Paintings,Painting,"Skating at Sloten, near Amsterdam",,,,,,Artist,,Johannes Abrahamsz Beerstraten,"Dutch, Amsterdam 1622–1666 Amsterdam",,"Beerstraten, Johannes Abrahamsz",Dutch,1622,1666,,1642,1666,Oil on canvas,36 1/4 x 51 5/8 in. (92.1 x 131.1 cm),"Rogers Fund, 1911",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435633,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -07.225.296,false,true,437948,European Paintings,"Painting, sketch for a ceiling decoration",Allegory of Government: Wisdom Defeating Discord,,,,,,Artist,,Jacob de Wit,"Dutch, Amsterdam 1695–1754 Amsterdam",,"Wit, Jacob de",Dutch,1695,1754,,1715,1754,Oil on canvas,20 1/8 x 15 3/8 in. (51.1 x 39.1 cm),"Gift of J. Pierpont Morgan, 1906",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437948,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -11.149.2,false,true,436931,European Paintings,Painting,"Admiral Jacob Binkes (born about 1640, died 1677)",,,,,,Artist,,Nicolaes Maes,"Dutch, Dordrecht 1634–1693 Amsterdam",,"Maes, Nicolaes",Dutch,1634,1693,,1654,1693,Oil on canvas,17 1/4 x 12 7/8 in. (43.8 x 32.7 cm),"Gift of J. Pierpont Morgan, 1911",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436931,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.40.603,false,true,435596,European Paintings,Painting,Old Woman in an Armchair,,,,,,Artist,Attributed to,Jacob Backer,"Dutch, Harlingen 1608–1651 Amsterdam",,"Backer, Jacob",Dutch,1608,1651,,1629,1639,Oil on canvas,50 3/8 x 39 1/8 in. (128 x 99.4 cm),"Bequest of Benjamin Altman, 1913",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435596,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.40.629,false,true,436212,European Paintings,Painting,Young Woman with a Pearl Necklace,,,,,,Artist,Copy after,Willem Drost,"Dutch, late 17th or early 18th century",,"Drost, Willem",Dutch,1633,1659,,1670,1729,Oil on canvas,33 1/8 x 24 1/2 in. (84.1 x 62.2 cm),"Bequest of Benjamin Altman, 1913",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436212,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -30.95.260,false,true,437171,European Paintings,Painting,The Old Castle,,,,,,Artist,,Emanuel Murant,"Dutch, Amsterdam 1622–1700 Leeuwarden",,"Murant, Emanuel",Dutch,1622,1700,,1642,1700,Oil on wood,15 5/8 x 21 7/8 in. (39.7 x 55.6 cm),"Theodore M. Davis Collection, Bequest of Theodore M. Davis, 1915",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437171,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -87.8.13,false,true,436724,European Paintings,Painting,Expectation,,,,,,Artist,,Jozef Israëls,"Dutch, Groningen 1824–1911 Scheveningen",,"Israëls, Jozef",Dutch,1824,1911,,1844,1911,Oil on canvas,71 1/2 x 54 in. (181.6 x 137.2 cm),"Gift of George I. Seney, 1887",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436724,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -17.120.227,false,true,436725,European Paintings,Painting,Grandmother's Treasure,,,,,,Artist,,Jozef Israëls,"Dutch, Groningen 1824–1911 Scheveningen",,"Israëls, Jozef",Dutch,1824,1911,,1844,1911,Oil on canvas,27 x 35 5/8 in. (68.6 x 90.5 cm),"Mr. and Mrs. Isaac D. Fletcher Collection, Bequest of Isaac D. Fletcher, 1917",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436725,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -32.100.9,false,true,437193,European Paintings,Painting,The Reader,,,,,,Artist,,Eglon van der Neer,"Dutch, Amsterdam 1635/36–1703 Düsseldorf",,"Neer, Eglon van der",Dutch,1635,1703,,1654,1703,Oil on canvas,15 x 11 in. (38.1 x 27.9 cm),"The Friedsam Collection, Bequest of Michael Friedsam, 1931",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437193,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -71.84,false,true,437416,European Paintings,Painting,Man in Armor (Mars?),,,,,,Artist,Style of,Rembrandt,"Dutch, second or third quarter 17th century",,Rembrandt,Dutch,1606,1669,,1625,1674,Oil on canvas,40 1/8 x 35 5/8 in. (101.9 x 90.5 cm),"Purchase, 1871",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437416,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.40.601,false,true,437411,European Paintings,Painting,Man with a Steel Gorget,,,,,,Artist,Style of,Rembrandt,"Dutch, second or third quarter 17th century",,Rembrandt,Dutch,1606,1669,,1625,1674,Oil on canvas,37 1/8 x 30 5/8 in. (94.3 x 77.8 cm),"Bequest of Benjamin Altman, 1913",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437411,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1971.255,false,true,437351,European Paintings,Painting,Paul and Barnabas at Lystra,,,,,,Artist,,Jacob Pynas,"Dutch, Amsterdam 1592/93–after 1650 Amsterdam (?)",,"Pynas, Jacob",Dutch,1592,1650,,1605,1650,Oil on wood,19 x 28 7/8 in. (48.3 x 73.3 cm),"Gift of Emile E. Wolf, 1971",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437351,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -71.135,false,true,437590,European Paintings,Painting,View of the Town of Alkmaar,,,,,,Artist,,Salomon van Ruysdael,"Dutch, Naarden, born ca. 1600–1603, died 1670 Haarlem",,"Ruysdael, Salomon van",Dutch,1600,1670,,1620,1670,Oil on wood,20 1/4 x 33 in. (51.4 x 83.8 cm),"Purchase, 1871",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437590,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -56.13,false,true,436527,European Paintings,Painting,The Flowering Orchard,,,,,,Artist,,Vincent van Gogh,"Dutch, Zundert 1853–1890 Auvers-sur-Oise",,"Gogh, Vincent van",Dutch,1853,1890,1888,1888,1888,Oil on canvas,28 1/2 x 21 in. (72.4 x 53.3 cm),"The Mr. and Mrs. Henry Ittleson Jr. Purchase Fund, 1956",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436527,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -62.24,false,true,436530,European Paintings,Painting,Oleanders,,,,,,Artist,,Vincent van Gogh,"Dutch, Zundert 1853–1890 Auvers-sur-Oise",,"Gogh, Vincent van",Dutch,1853,1890,1888,1888,1888,Oil on canvas,23 3/4 x 29 in. (60.3 x 73.7 cm),"Gift of Mr. and Mrs. John L. Loeb, 1962",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436530,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1992.374,false,true,436533,European Paintings,Painting,Shoes,,,,,,Artist,,Vincent van Gogh,"Dutch, Zundert 1853–1890 Auvers-sur-Oise",,"Gogh, Vincent van",Dutch,1853,1890,1888,1888,1888,Oil on canvas,18 x 21 3/4 in. (45.7 x 55.2 cm),"Purchase, The Annenberg Foundation Gift, 1992",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436533,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -25.106.20,false,true,437253,European Paintings,"Painting, miniature",Abbé Charles Bossut (1730–1814),,,,,,Artist,,Pierre Pasquier,"French, 1731–1806",,"Pasquier, Pierre",French,1731,1806,1772,1772,1772,Enamel,"Oval, 2 3/8 x 2 in. (62 x 50 mm)","Gift of Mrs. Louis V. Bell, in memory of her husband, 1925",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/437253,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -25.106.32,false,true,437254,European Paintings,"Painting, miniature",Gérard de Vesme,,,,,,Artist,,Pierre Pasquier,"French, 1731–1806",,"Pasquier, Pierre",French,1731,1806,177(3?),1771,1779,Enamel,"Oval, 2 1/8 x 1 3/4 in. (54 x 44 mm)","Gift of Mrs. Louis V. Bell, in memory of her husband, 1925",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/437254,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -60.14,false,true,436328,European Paintings,"Painting, miniature",Portrait of a Boy,,,,,,Artist,,Marie Anne Gérard Fragonard (Madame Fragonard),"French, 1745–1823",,"Fragonard, Marie Anne Gérard (Madame Fragonard)",French,1745,1823,ca. 1775,1770,1780,Ivory,"Oval, 2 7/8 x 2 3/8 in. (73 x 59 mm)","Rogers Fund, 1960",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/436328,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -61.77,false,true,436605,European Paintings,"Painting, miniature","Alexandre Théodore Victor (1760–1829), Comte de Lameth",,,,,,Artist,,Jean Urbain Guérin,"French, 1761–1836",,"Guérin, Jean Urbain",French,1761,1836,ca. 1789–90,1789,1790,Ivory laid on card,Diameter 3 in. (75 mm),"Rogers Fund, 1961",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/436605,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -62.122.87,false,true,436606,European Paintings,"Painting, miniature",Portrait of a Woman,,,,,,Artist,,Jean Urbain Guérin,"French, 1761–1836",,"Guérin, Jean Urbain",French,1761,1836,ca. 1815,1810,1820,Ivory,"Oval, 2 3/8 x 1 3/4 in. (59 x 46 mm)","Bequest of Millie Bruhl Fredrick, 1962",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/436606,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -26.168.9,false,true,436855,European Paintings,"Painting, miniature",Portrait of a Woman,,,,,,Artist,,Étienne Charles Le Guay,"French, 1762–1846",,"Le Guay, Étienne Charles",French,1762,1846,ca. 1800,1795,1805,Ivory,"Oval, 3 1/2 x 3 in. (90 x 75 mm)","Bequest of Collis P. Huntington, 1900",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/436855,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -25.106.16,false,true,436848,European Paintings,"Painting, miniature",Portrait of a Young Woman,,,,,,Artist,Attributed to,Jean Antoine Laurent,"French, 1763–1832",,"Laurent, Jean Antoine",French,1763,1832,ca. 1795,1790,1800,Ivory,"Octagonal, 2 3/8 x 2 7/8 in. (63 x 73 mm)","Gift of Mrs. Louis V. Bell, in memory of her husband, 1925",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/436848,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.145.11,false,true,436862,European Paintings,"Painting, miniature",Portrait of a Man,,,,,,Artist,,Jeanne Philiberte Ledoux,"French, 1767–1840",,"Ledoux, Jeanne Philiberte",French,1767,1840,ca. 1790,1785,1795,Ivory,Diameter 2 1/4 in. (57 mm),"Gift of Humanities Fund Inc., 1972",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/436862,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -62.122.108,false,true,436924,European Paintings,"Painting, miniature",Portrait of a Man,,,,,,Artist,,Ferdinand Machéra,"French, 1776–1843",,"Machéra, Ferdinand",French,1776,1843,1827,1827,1827,Ivory,"Oval, 2 1/4 x 1 5/8 in. (57 x 43 mm)","Bequest of Millie Bruhl Fredrick, 1962",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/436924,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -48.187.742,false,true,436768,European Paintings,"Painting, miniature",Portrait of a Woman,,,,,,Artist,,Nicolas Henri Jacob,"French, 1782–1871",,"Jacob, Nicolas Henri",French,1782,1871,1817,1817,1817,Card,"Oval, 8 1/4 x 6 3/4 in. (208 x 171 mm)","Bequest of Catherine D. Wentworth, 1948",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/436768,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -95.14.90,false,true,435841,European Paintings,"Painting, miniature",Baron Joseph Dominique Louis (1755–1837),,,,,,Artist,,Pierre Laurent Canon,"French, 1787–1852",,"Canon, Pierre Laurent",French,1787,1852,1844,1844,1844,Ivory,"Oval, 4 1/8 x 3 1/8 in. (105 x 80 mm)","The Moses Lazarus Collection, Gift of Josephine and Sarah Lazarus, in memory of their father, 1888–95",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/435841,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -62.122.90,false,true,437486,European Paintings,"Painting, miniature",Portrait of a Woman,,,,,,Artist,,François-Théodore Rochard,"French, 1798–1858",,"Rochard, François-Théodore",French,1798,1858,1829,1829,1829,Ivory,3 7/8 x 3 1/4 in. (98 x 83 mm),"Bequest of Millie Bruhl Fredrick, 1962",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/437486,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -42.53.1,false,true,437075,European Paintings,"Painting, miniature","Portrait of a Woman, Said to Be Marie de Mautesson",,,,,,Artist,,François Meuret,"French, 1800–1887",,"Meuret, François",French,1800,1887,ca. 1830,1825,1835,Ivory,2 1/4 x 1 7/8 in. (58 x 47 mm),"Gift of Helen O. Brice, 1942",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/437075,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -24.80.533,false,true,435767,European Paintings,"Painting, miniature",Portrait of a Man,,,,,,Artist,Attributed to,Joseph Boze,"French, 1745–1825/26",,"Boze, Joseph",French,1745,1826,probably ca. 1790,1785,1795,Ivory,"Oval, 1 7/8 x 1 1/2 in. (47 x 38 mm)","Bequest of Mary Clark Thompson, 1923",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/435767,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -15.43.298,false,true,437907,European Paintings,"Painting, miniature",Madame Ingouf,,,,,,Artist,,Vincent,"French, active ca. 1790",,Vincent,French,1790,1790,ca. 1790,1785,1795,Ivory,Diameter 4 1/8 in. (105 mm),"Bequest of Mary Anna Palmer Draper, 1914",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/437907,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -68.222.9,false,true,436109,European Paintings,"Painting, miniature","Benjamin Franklin (1706–1790), after a Painting by Greuze of 1777",,,,,,Artist,,Charles Paul Jérôme de Bréa,"French, ca. 1739–1820",,"Bréa, Charles Paul Jérôme de",French,1739,1820,1777,1777,1777,Ivory,"Oval, 3 3/8 x 2 3/4 in. (85 x 68 mm)","Gift of J. William Middendorf II, 1968",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/436109,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -62.122.67,false,true,436110,European Paintings,"Painting, miniature","Marie-Thérèse-Charlotte (1778–1851), Daughter of Louis XVI",,,,,,Artist,,Jacques Joseph de Gault,"French, 1738–after 1812",,"Gault, Jacques Joseph de",French,1738,1812,1795,1795,1795,Ivory,Diameter 2 3/8 in. (60 mm),"Bequest of Millie Bruhl Fredrick, 1962",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/436110,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -95.14.94,false,true,436919,European Paintings,"Painting, miniature","Portrait of a Man, Said to Be James Madison (1751–1836)",,,,,,Artist,,Annibal Christian Loutherbourg,"French, 1765–after 1795",,"Loutherbourg, Annibal Christian",French,1765,1795,1795,1795,1795,Ivory,"Oval, 2 1/2 x 2 1/4 in. (65 x 56 mm)","The Moses Lazarus Collection, Gift of Josephine and Sarah Lazarus, in memory of their father, 1888–95",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/436919,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.136.8,false,true,436181,European Paintings,"Painting, miniature",Portrait of a Woman,,,,,,Artist,,Jacques Delaplace,"French, 1767–after 1831",,"Delaplace, Jacques",French,1767,1831,ca. 1805–10,1805,1810,Ivory extended by card,2 1/2 x 2 1/4 in. (64 x 55 mm),"Bequest of Margaret Crane Hurlbut, 1933",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/436181,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -45.110.1,false,true,435662,European Paintings,"Painting, miniature",Portrait of a Woman,,,,,,Artist,,Vincent Bertrand,"French, 1770–after 1817",,"Bertrand, Vincent",French,1770,1817,ca. 1810,1805,1815,Ivory,4 1/4 x 4 in. (110 x 100 mm),"Gift of Mrs. John LaPorte Given, 1945",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/435662,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -42.150.15,false,true,435661,European Paintings,"Painting, miniature",Portrait of a Woman,,,,,,Artist,,Vincent Bertrand,"French, 1770–after 1817",,"Bertrand, Vincent",French,1770,1817,ca. 1810,1805,1815,Ivory,"Oval, 2 5/8 x 2 in. (58 x 48 mm)","Gift of Mrs. Heyward Cutting, 1942",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/435661,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -95.14.51,false,true,435719,European Paintings,"Painting, miniature",Portrait of a Woman,,,,,,Artist,,Joseph Bordes,"French, 1773–after 1835",,"Bordes, Joseph",French,1773,1835,1808,1808,1808,Ivory,"Octagonal, 4 1/8 x 3 1/8 in. (106 x 80 mm)","The Moses Lazarus Collection, Gift of Josephine and Sarah Lazarus, in memory of their father, 1888–95",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/435719,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -95.14.83,false,true,435721,European Paintings,"Painting, miniature",Portrait of a Man,,,,,,Artist,,Joseph Bordes,"French, 1773–after 1835",,"Bordes, Joseph",French,1773,1835,ca. 1810,1805,1815,Ivory set into card,"Oval, 7 1/8 x 5 7/8 in. (181 x 149 mm)","The Moses Lazarus Collection, Gift of Josephine and Sarah Lazarus, in memory of their father, 1888–95",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/435721,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -95.14.86,false,true,435720,European Paintings,"Painting, miniature",Portrait of a Woman,,,,,,Artist,,Joseph Bordes,"French, 1773–after 1835",,"Bordes, Joseph",French,1773,1835,1812,1812,1812,Ivory,"Oval, 4 x 3 1/8 in. (104 x 82 mm)","The Moses Lazarus Collection, Gift of Josephine and Sarah Lazarus, in memory of their father, 1888–95",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/435720,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -24.80.525,false,true,437627,European Paintings,"Painting, miniature",A River Landscape,,,,,,Artist,,Edmé Charles de Lioux de Savignac,"French, active ca. 1766–72",,"Savignac, Edmé Charles de Lioux de",French,1766,1772,ca. 1766–72,1766,1772,Paper,Diameter 3 in. (77 mm),"Bequest of Mary Clark Thompson, 1923",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/437627,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -24.80.527,false,true,437628,European Paintings,"Painting, miniature",A Picnic,,,,,,Artist,,Edmé Charles de Lioux de Savignac,"French, active ca. 1766–72",,"Savignac, Edmé Charles de Lioux de",French,1766,1772,ca. 1766–72,1766,1772,Paper,Diameter 3 in. (77 mm),"Bequest of Mary Clark Thompson, 1923",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/437628,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -25.106.21,false,true,437905,European Paintings,"Painting, miniature",A Man with the Initials JD,,,,,,Artist,,Villers,"French, active ca. 1781–93",,Villers,French,1781,1793,1790,1790,1790,Ivory,Diameter 2 1/2 in. (62 mm),"Gift of Mrs. Louis V. Bell, in memory of her husband, 1925",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/437905,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -26.168.55,false,true,437904,European Paintings,"Painting, miniature",Portrait of a Woman,,,,,,Artist,,Villers,"French, active ca. 1781–93",,Villers,French,1781,1793,ca. 1790,1785,1793,Ivory,"Oval, 2 3/8 x 1 7/8 in. (60 x 48 mm)","Bequest of Collis P. Huntington, 1900",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/437904,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -25.106.35,false,true,436860,European Paintings,"Painting, miniature",Portrait of a Man,,,,,,Artist,,J. Lecourt,"French, active ca. 1804–30",,"Lecourt, J.",French,1804,1830,ca. 1810,1805,1815,Ivory,Diameter 2 in. (50 mm),"Gift of Mrs. Louis V. Bell, in memory of her husband, 1925",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/436860,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -56.7,false,true,437292,European Paintings,"Painting, drawing",A Shipwreck in a Storm,,,,,,Artist,,Jean Pillement,"French, Lyons 1728–1808 Lyons",,"Pillement, Jean",French,1728,1808,1782,1782,1782,Pastel on gessoed canvas,24 3/4 x 36 in. (62.9 x 91.4 cm),"Gift of Martin Birnbaum, 1956",,,,,,,,,,,,Pastels & Oil Sketches on Paper,,http://www.metmuseum.org/art/collection/search/437292,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -08.60,false,true,436717,European Paintings,"Painting, miniature","Mrs. Rufus Prime (Augusta Temple Palmer, 1807–1840)",,,,,,Artist,,Jean-Baptiste Isabey,"French, Nancy 1767–1855 Paris",,"Isabey, Jean-Baptiste",French,1767,1855,1828,1828,1828,Card,"Oval, 5 3/8 x 4 in. (138 x 102 mm)","Gift of Cornelia Prime, 1908",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/436717,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -42.53.5,false,true,436715,European Paintings,"Painting, miniature",Napoléon I (1769–1821),,,,,,Artist,,Jean-Baptiste Isabey,"French, Nancy 1767–1855 Paris",,"Isabey, Jean-Baptiste",French,1767,1855,1812,1812,1812,Ivory,"Oval, 2 1/4 x 1 3/8 in. (56 x 36 mm)","Gift of Helen O. Brice, 1942",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/436715,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -47.33.3,false,true,436714,European Paintings,"Painting, miniature",Napoléon I (1769–1821),,,,,,Artist,,Jean-Baptiste Isabey,"French, Nancy 1767–1855 Paris",,"Isabey, Jean-Baptiste",French,1767,1855,1810,1810,1810,Ivory,"Oval, 2 x 1 1/8 in. (50 x 30 mm)","Gift of Junius S. and Henry S. Morgan, 1947",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/436714,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -25.106.7,false,true,436710,European Paintings,"Painting, miniature",The Reader,,,,,,Artist,,Jean-Baptiste Isabey,"French, Nancy 1767–1855 Paris",,"Isabey, Jean-Baptiste",French,1767,1855,1790,1790,1790,Ivory,Diameter 3 1/8 in. (79 mm),"Gift of Mrs. Louis V. Bell, in memory of her husband, 1925",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/436710,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -32.75.20,false,true,436713,European Paintings,"Painting, miniature","Madame Jean-Baptiste Isabey (Jeanne Laurice de Salienne, died 1829)",,,,,,Artist,,Jean-Baptiste Isabey,"French, Nancy 1767–1855 Paris",,"Isabey, Jean-Baptiste",French,1767,1855,ca. 1796–1800,1796,1800,Ivory,Diameter 3 3/8 in. (86 mm),"The Collection of Giovanni P. Morosini, presented by his daughter Giulia, 1932",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/436713,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -55.111.2,false,true,436718,European Paintings,"Painting, miniature",Portrait of a Woman,,,,,,Artist,,Jean-Baptiste Isabey,"French, Nancy 1767–1855 Paris",,"Isabey, Jean-Baptiste",French,1767,1855,ca. 1815,1810,1820,Ivory,"Oval, 3 x 2 1/4 in. (76 x 56 mm)","Bequest of Helen Winslow Durkee Mileham, 1954",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/436718,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -62.122.88,false,true,436711,European Paintings,"Painting, miniature",Portrait of a Woman,,,,,,Artist,,Jean-Baptiste Isabey,"French, Nancy 1767–1855 Paris",,"Isabey, Jean-Baptiste",French,1767,1855,ca. 1795,1790,1800,Ivory,Diameter 2 7/8 in. (73 mm),"Bequest of Millie Bruhl Fredrick, 1962",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/436711,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2011.84,false,true,441183,European Paintings,"Painting, drawing","Double Portrait Presumed to Represent François de Jullienne (1722–1754) and His Wife (Marie Élisabeth de Séré de Rieux, 1724–1795)",,,,,,Artist,,Charles Antoine Coypel,"French, Paris 1694–1752 Paris",,"Coypel, Charles Antoine",French,1694,1752,1743,1743,1743,"Pastel, black chalk, watercolor, and traces of black chalk underdrawing on four joined sheets of handmade blue laid paper, mounted on canvas and adhered to a keyed stretcher",39 3/8 x 31 1/2 in. (100 x 80 cm),"Purchase, Mrs. Charles Wrightsman Gift, in honor of Annette de la Renta, 2011",,,,,,,,,,,,Pastels & Oil Sketches on Paper,,http://www.metmuseum.org/art/collection/search/441183,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1977.383,false,true,437596,European Paintings,"Painting, drawing",The Two Sisters,,,,,,Artist,,"Jean Claude Richard, Abbé de Saint-Non","French, Paris 1727–1791 Paris",,"Saint-Non, Jean Claude Richard, Abbé de",French,1727,1791,1770,1770,1770,"Pastel on paper, laid down on canvas",31 5/8 x 25 in. (80.3 x 63.5 cm),"Gift of Daniel Wildenstein, 1977",,,,,,,,,,,,Pastels & Oil Sketches on Paper,,http://www.metmuseum.org/art/collection/search/437596,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -32.75.10,false,true,435834,European Paintings,"Painting, miniature","Louis XVI (1754–1793), King of France",,,,,,Artist,Attributed to,Antoine François Callet,"French, Paris 1741–1823 Paris",,"Callet, Antoine François",French,1741,1823,1787,1787,1787,Ivory,Diameter 2 5/8 in. (67 mm),"The Collection of Giovanni P. Morosini, presented by his daughter Giulia, 1932",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/435834,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.441,false,true,439405,European Paintings,"Painting, drawing",Madame Élisabeth de France (1764–1794),,,,,,Artist,,Adélaïde Labille-Guiard,"French, Paris 1749–1803 Paris",,"Labille-Guiard, Adélaïde",French,1749,1803,ca. 1787,1782,1792,"Pastel on blue paper, seven sheets joined, laid down on canvas","Oval, 31 x 25 3/4 in. (78.7 x 65.4 cm.)","Gift of Mrs. Frederick M. Stafford, 2007",,,,,,,,,,,,Pastels & Oil Sketches on Paper,,http://www.metmuseum.org/art/collection/search/439405,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -25.106.4,false,true,435736,European Paintings,"Painting, miniature",Portrait of a Woman,,,,,,Artist,,Étienne Bouchardy,"French, Paris 1797–1849 Paris",,"Bouchardy, Étienne",French,1797,1849,1832,1832,1832,Ivory laid on card,"Oval, 3 1/4 x 2 5/8 in. (82 x 67 mm)","Gift of Mrs. Louis V. Bell, in memory of her husband, 1925",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/435736,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.136.7,false,true,435735,European Paintings,"Painting, miniature",Portrait of a Woman,,,,,,Artist,,Étienne Bouchardy,"French, Paris 1797–1849 Paris",,"Bouchardy, Étienne",French,1797,1849,1838,1838,1838,Ivory,4 7/8 x 4 1/8 in. (125 x 104 mm),"Bequest of Margaret Crane Hurlbut, 1933",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/435735,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1997.391.1,false,true,437993,European Paintings,"Painting, drawing",At the Milliner's,,,,,,Artist,,Edgar Degas,"French, Paris 1834–1917 Paris",,"Degas, Edgar",French,1834,1917,1881,1881,1881,"Pastel on five pieces of wove paper, backed with paper, and laid down on canvas",27 1/4 x 27 1/4 in. (69.2 x 69.2 cm),"The Walter H. and Leonore Annenberg Collection, Gift of Walter H. and Leonore Annenberg, 1997, Bequest of Walter H. Annenberg, 2002",,,,,,,,,,,,Pastels & Oil Sketches on Paper,,http://www.metmuseum.org/art/collection/search/437993,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -25.106.13,false,true,437668,European Paintings,"Painting, miniature",Portrait of a Man,,,,,,Artist,,Louis Marie Sicardi,"French, Avignon 1743–1825 Paris",,"Sicardi, Louis Marie",French,1743,1825,ca. 1780,1775,1785,Ivory,"Oval, 1 3/8 x 1 1/4 in. (36 x 32 mm)","Gift of Mrs. Louis V. Bell, in memory of her husband, 1925",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/437668,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2003.26,false,true,438615,European Paintings,"Painting, drawing",Olivier Journu (1724–1764),,,,,,Artist,,Jean-Baptiste Perronneau,"French, Paris 1715–1783 Amsterdam",,"Perronneau, Jean-Baptiste",French,1715,1783,1756,1756,1756,"Pastel on blue-gray laid paper, laid down on canvas",22 7/8 x 18 1/2 in. (58.1 x 47 cm),"Wrightsman Fund, 2003",,,,,,,,,,,,Pastels & Oil Sketches on Paper,,http://www.metmuseum.org/art/collection/search/438615,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -26.168.17,false,true,436233,European Paintings,"Painting, miniature","Portrait of a Woman, Said to Be Madame Récamier (1777–1849)",,,,,,Artist,,Nicolas François Dun,"French, Lunéville 1764–1832 Naples",,"Dun, Nicolas François",French,1764,1832,ca. 1812–14,1812,1814,Ivory,"Oval, 2 3/8 x 1 7/8 in. (60 x 48 mm)","Bequest of Collis P. Huntington, 1900",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/436233,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -07.225.287,false,true,436349,European Paintings,"Painting, drawing",Panthers of Bacchus Eating Grapes,,,,,,Artist,,Alexandre François Desportes,"French, Champigneulle 1661–1743 Paris",,"Desportes, Alexandre François",French,1661,1743,ca. 1719–20,1719,1720,"Oil on paper, laid down on card (paste-paper)",13 5/8 x 6 3/4 in. (34.6 x 17.1 cm),"Gift of J. Pierpont Morgan, 1906",,,,,,,,,,,,Pastels & Oil Sketches on Paper,,http://www.metmuseum.org/art/collection/search/436349,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -62.122.69,false,true,437167,European Paintings,"Painting, miniature","Louis XVI (1754–1793), King of France",,,,,,Artist,,Jean Laurent Mosnier,"French, Paris 1743/44–1808 St. Petersburg",,"Mosnier, Jean Laurent",French,1743,1808,1790,1790,1790,Ivory,Diameter 2 3/4 in. (69 mm),"Bequest of Millie Bruhl Fredrick, 1962",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/437167,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -35.89.1,false,true,435915,European Paintings,"Painting, miniature","Charles de Cossé (1506–1563), Count of Brissac",,,,,,Artist,,Jean Clouet,"French, active by 1516–died 1540/41 Paris",,"Clouet, Jean",French,1516,1541,ca. 1535,1530,1540,Vellum,Diameter 1 1/2 in. (37 mm),"Fletcher Fund, 1935",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/435915,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -59.72.1,false,true,437594,European Paintings,"Painting, miniature",Portrait of a Churchman,,,,,,Artist,,E. Jean Saillant,"French, active by 1620–died in or after 1638",,"Saillant, E. Jean",French,1620,1638,1628,1628,1628,Vellum stretched over copper,"Octagonal, 6 1/8 x 4 3/4 in. (157 x 122 mm)","Rogers Fund, 1959",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/437594,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -59.72.2,false,true,437595,European Paintings,"Painting, miniature",Portrait of a Man,,,,,,Artist,,E. Jean Saillant,"French, active by 1620–died in or after 1638",,"Saillant, E. Jean",French,1620,1638,ca. 1628,1623,1633,Vellum stretched over wood,"Octagonal, 6 3/4 x 5 5/8 in. (170 x 142 mm)","Rogers Fund, 1959",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/437595,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -62.122.73,false,true,437353,European Paintings,"Painting, miniature",Portrait of a Man,,,,,,Artist,,Edme Quenedey,"French, Riceys-le-Haut (Aube) 1756–1830 Paris",,"Quenedey, Edme",French,1756,1830,ca. 1780,1775,1785,Ivory,Diameter 2 in. (49 mm),"Bequest of Millie Bruhl Fredrick, 1962",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/437353,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2002.439,false,true,438585,European Paintings,"Painting, drawing",Jean Charles Garnier d'Isle (1697–1755),,,,,,Artist,,Maurice Quentin de La Tour,"French, Saint-Quentin 1704–1788 Saint-Quentin",,"La Tour, Maurice Quentin de",French,1704,1788,ca. 1750,1745,1755,"Pastel and gouache on blue paper, laid down on canvas",25 3/8 x 21 1/4 in. (64.5 x 54 cm),"Purchase, Walter and Leonore Annenberg and The Annenberg Foundation Gift, 2002",,,,,,,,,,,,Pastels & Oil Sketches on Paper,,http://www.metmuseum.org/art/collection/search/438585,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -26.46.1,false,true,437345,European Paintings,Drawing,Cider,,,,,,Artist,,Pierre Puvis de Chavannes,"French, Lyons 1824–1898 Paris",,"Puvis de Chavannes, Pierre",French,1824,1898,ca. 1864,1859,1869,"Oil on paper, laid down on canvas",51 x 99 1/4 in. (129.5 x 252.1 cm),"Catharine Lorillard Wolfe Collection, Wolfe Fund, 1926",,,,,,,,,,,,Drawings,,http://www.metmuseum.org/art/collection/search/437345,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -26.46.2,false,true,437348,European Paintings,Drawing,The River,,,,,,Artist,,Pierre Puvis de Chavannes,"French, Lyons 1824–1898 Paris",,"Puvis de Chavannes, Pierre",French,1824,1898,ca. 1864,1859,1869,"Oil on paper, laid down on canvas",51 x 99 1/4 in. (129.5 x 252.1 cm),"Catharine Lorillard Wolfe Collection, Wolfe Fund, 1926",,,,,,,,,,,,Drawings,,http://www.metmuseum.org/art/collection/search/437348,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -22.27.2,false,true,435974,European Paintings,Drawing,Lake Albano and Castel Gandolfo,,,,,,Artist,,Camille Corot,"French, Paris 1796–1875 Paris",,"Corot, Camille",French,1796,1875,1826–27,1826,1827,"Oil on paper, laid down on wood",9 x 15 1/2 in. (22.9 x 39.4 cm),"Purchase, Dikran G. Kelekian Gift, 1922",,,,,,,,,,,,Drawings,,http://www.metmuseum.org/art/collection/search/435974,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -50.71.2,false,true,435973,European Paintings,Drawing,Italian Landscape,,,,,,Artist,,Camille Corot,"French, Paris 1796–1875 Paris",,"Corot, Camille",French,1796,1875,ca. 1825–28,1825,1828,"Oil on paper, laid down on canvas",5 x 10 5/8 in. (12.7 x 27 cm),"Gift of Mr. and Mrs. William B. Jaffe, 1950",,,,,,,,,,,,Drawings,,http://www.metmuseum.org/art/collection/search/435973,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1979.404,false,true,435970,European Paintings,Drawing,Fontainebleau: Oak Trees at Bas-Bréau,,,,,,Artist,,Camille Corot,"French, Paris 1796–1875 Paris",,"Corot, Camille",French,1796,1875,1832 or 1833,1832,1833,"Oil on paper, laid down on wood",15 5/8 x 19 1/2 in. (39.7 x 49.5 cm),"Catharine Lorillard Wolfe Collection, Wolfe Fund, 1979",,,,,,,,,,,,Drawings,,http://www.metmuseum.org/art/collection/search/435970,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.296,false,true,435660,European Paintings,Drawing,Ravine at Sorrento,,,,,,Artist,,Édouard Bertin,"French, Paris 1797–1871 Paris",,"Bertin, Édouard",French,1797,1871,1821 or later,1821,1871,Oil on paper mounted on board,16 1/8 x 11 5/8 in. (41 x 29.5 cm),"Purchase, Karen B. Cohen Gift, 1986",,,,,,,,,,,,Drawings,,http://www.metmuseum.org/art/collection/search/435660,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.55,false,true,436953,European Paintings,Drawing,George Moore (1852–1933),,,,,,Artist,,Édouard Manet,"French, Paris 1832–1883 Paris",,"Manet, Édouard",French,1832,1883,1879,1879,1879,Pastel on canvas,21 3/4 x 13 7/8 in. (55.2 x 35.2 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Drawings,,http://www.metmuseum.org/art/collection/search/436953,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.56,false,true,436958,European Paintings,Drawing,Mademoiselle Isabelle Lemonnier (1857–1926),,,,,,Artist,,Édouard Manet,"French, Paris 1832–1883 Paris",,"Manet, Édouard",French,1832,1883,1879–82,1879,1882,Pastel on canvas,22 x 18 1/4 in. (55.9 x 46.4 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Drawings,,http://www.metmuseum.org/art/collection/search/436958,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.561,false,true,436959,European Paintings,Drawing,"Mademoiselle Lucie Delabigne (1859–1910), Called Valtesse de la Bigne",,,,,,Artist,,Édouard Manet,"French, Paris 1832–1883 Paris",,"Manet, Édouard",French,1832,1883,1879,1879,1879,Pastel on canvas,21 3/4 x 14 in. (55.2 x 35.6 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Drawings,,http://www.metmuseum.org/art/collection/search/436959,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -56.231,false,true,436170,European Paintings,Drawing,Woman Combing Her Hair,,,,,,Artist,,Edgar Degas,"French, Paris 1834–1917 Paris",,"Degas, Edgar",French,1834,1917,ca. 1888–90,1888,1890,"Pastel on light green wove paper, now discolored to warm gray, affixed to original pulpboard mount",24 1/8 x 18 1/8 in. (61.3 x 46 cm),"Gift of Mr. and Mrs. Nate B. Spingold, 1956",,,,,,,,,,,,Drawings,,http://www.metmuseum.org/art/collection/search/436170,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -22.27.3,false,true,436151,European Paintings,Drawing,The Milliner,,,,,,Artist,,Edgar Degas,"French, Paris 1834–1917 Paris",,"Degas, Edgar",French,1834,1917,ca. 1882,1877,1887,"Pastel and charcoal on warm gray wove paper, now discolored to buff (watermark MICHALLET), laid down on dark brown wove paper",18 3/4 x 24 1/2 in. (47.6 x 62.2 cm),"Purchase, Rogers Fund and Dikran G. Kelekian Gift, 1922",,,,,,,,,,,,Drawings,,http://www.metmuseum.org/art/collection/search/436151,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1971.185,false,true,436132,European Paintings,Drawing,The Dance Lesson,,,,,,Artist,,Edgar Degas,"French, Paris 1834–1917 Paris",,"Degas, Edgar",French,1834,1917,ca. 1879,1874,1884,"Pastel and black chalk on three pieces of wove paper, joined together",25 3/8 x 22 1/8 in. (64.5 x 56.2 cm),"H. O. Havemeyer Collection, Gift of Adaline Havemeyer Perkins, in memory of her father, Horace Havemeyer, 1971",,,,,,,,,,,,Drawings,,http://www.metmuseum.org/art/collection/search/436132,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -61.101.6,false,true,436158,European Paintings,Drawing,Self-Portrait,,,,,,Artist,,Edgar Degas,"French, Paris 1834–1917 Paris",,"Degas, Edgar",French,1834,1917,ca. 1855–56,1855,1856,"Oil on paper, laid down on canvas",16 x 13 1/2 in. (40.6 x 34.3 cm),"Bequest of Stephen C. Clark, 1960",,,,,,,,,,,,Drawings,,http://www.metmuseum.org/art/collection/search/436158,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -61.101.7,false,true,436159,European Paintings,Drawing,The Singer in Green,,,,,,Artist,,Edgar Degas,"French, Paris 1834–1917 Paris",,"Degas, Edgar",French,1834,1917,ca. 1884,1879,1889,Pastel on light blue laid paper,23 3/4 x 18 1/4 in. (60.3 x 46.4 cm),"Bequest of Stephen C. Clark, 1960",,,,,,,,,,,,Drawings,,http://www.metmuseum.org/art/collection/search/436159,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.35,false,true,436173,European Paintings,Drawing,Woman Having Her Hair Combed,,,,,,Artist,,Edgar Degas,"French, Paris 1834–1917 Paris",,"Degas, Edgar",French,1834,1917,ca. 1886–88,1886,1888,"Pastel on light green wove paper, now discolored to warm gray, affixed to original pulpboard mount",29 1/8 x 23 7/8 in. (74 x 60.6 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Drawings,,http://www.metmuseum.org/art/collection/search/436173,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.36,false,true,436172,European Paintings,Drawing,Woman Drying Her Foot,,,,,,Artist,,Edgar Degas,"French, Paris 1834–1917 Paris",,"Degas, Edgar",French,1834,1917,1885–86,1885,1886,"Pastel on buff wove paper, affixed to original pulpboard mount",19 3/4 x 21 1/4 in. (50.2 x 54 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Drawings,,http://www.metmuseum.org/art/collection/search/436172,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.37,false,true,436128,European Paintings,Drawing,Woman with a Towel,,,,,,Artist,,Edgar Degas,"French, Paris 1834–1917 Paris",,"Degas, Edgar",French,1834,1917,1894 or 1898,1894,1894,Pastel on cream-colored wove paper with red and blue fibers throughout,37 3/4 x 30in. (95.9 x 76.2cm) Frame: 44 1/4 x 36 1/4 x 1 7/8 in. (112.4 x 92.1 x 4.8 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Drawings,,http://www.metmuseum.org/art/collection/search/436128,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.38,false,true,436126,European Paintings,Drawing,At the Milliner's,,,,,,Artist,,Edgar Degas,"French, Paris 1834–1917 Paris",,"Degas, Edgar",French,1834,1917,1882,1882,1882,"Pastel on pale gray wove paper (industrial wrapping paper), laid down on silk bolting",30 x 34 in. (76.2 x 86.4 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Drawings,,http://www.metmuseum.org/art/collection/search/436126,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.39,false,true,436156,European Paintings,Drawing,The Rehearsal Onstage,,,,,,Artist,,Edgar Degas,"French, Paris 1834–1917 Paris",,"Degas, Edgar",French,1834,1917,ca. 1874,1871,1877,"Pastel over brush-and-ink drawing on thin cream-colored wove paper, laid down on bristol board and mounted on canvas",21 x 28 1/2 in. (53.3 x 72.4 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Drawings,,http://www.metmuseum.org/art/collection/search/436156,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.40,false,true,436124,European Paintings,Drawing,"The Artist's Cousin, Probably Mrs. William Bell (Mathilde Musson, 1841–1878)",,,,,,Artist,,Edgar Degas,"French, Paris 1834–1917 Paris",,"Degas, Edgar",French,1834,1917,1873,1873,1873,"Pastel on green wove paper, now darkened to brown",18 5/8 x 15 1/8 in. (47.3 x 38.4 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Drawings,,http://www.metmuseum.org/art/collection/search/436124,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.41,false,true,436127,European Paintings,Drawing,Woman Bathing in a Shallow Tub,,,,,,Artist,,Edgar Degas,"French, Paris 1834–1917 Paris",,"Degas, Edgar",French,1834,1917,1885,1885,1885,"Charcoal and pastel on light green wove paper, now discolored to warm gray, laid down on silk bolting",32 x 22 1/8in. (81.3 x 56.2cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Drawings,,http://www.metmuseum.org/art/collection/search/436127,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.160.26,false,true,436155,European Paintings,Drawing,The Rehearsal of the Ballet Onstage,,,,,,Artist,,Edgar Degas,"French, Paris 1834–1917 Paris",,"Degas, Edgar",French,1834,1917,ca. 1874,1871,1877,"Oil colors freely mixed with turpentine, with traces of watercolor and pastel over pen-and-ink drawing on cream-colored wove paper, laid down on bristol board and mounted on canvas",21 3/8 x 28 3/4 in. (54.3 x 73 cm),"H. O. Havemeyer Collection, Gift of Horace Havemeyer, 1929",,,,,,,,,,,,Drawings,,http://www.metmuseum.org/art/collection/search/436155,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1991.277.1,false,true,436154,European Paintings,Drawing,Portraits at the Stock Exchange,,,,,,Artist,,Edgar Degas,"French, Paris 1834–1917 Paris",,"Degas, Edgar",French,1834,1917,ca. 1878–79,1878,1879,"Pastel on paper, pieced, and laid down on canvas",28 3/8 x 22 7/8 in. (72.1 x 58.1 cm),"Gift of Janice H. Levin, 1991",,,,,,,,,,,,Drawings,,http://www.metmuseum.org/art/collection/search/436154,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1999.288.3,false,true,437994,European Paintings,Drawing,Race Horses,,,,,,Artist,,Edgar Degas,"French, Paris 1834–1917 Paris",,"Degas, Edgar",French,1834,1917,ca. 1885–88,1885,1888,Pastel on wood,11 7/8 x 16 in. (30.2 x 40.6 cm),"The Walter H. and Leonore Annenberg Collection, Gift of Walter H. and Leonore Annenberg, 1999, Bequest of Walter H. Annenberg, 2002",,,,,,,,,,,,Drawings,,http://www.metmuseum.org/art/collection/search/437994,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.332.1,false,true,438518,European Paintings,Drawing,Dancer,,,,,,Artist,,Edgar Degas,"French, Paris 1834–1917 Paris",,"Degas, Edgar",French,1834,1917,1880–85,1880,1885,"Pastel on paper, laid down on board",19 1/4 x 12 3/4 in. (48.9 x 32.4 cm),"Gift of The Philip and Janice Levin Foundation, 2007",,,,,,,,,,,,Drawings,,http://www.metmuseum.org/art/collection/search/438518,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.185,false,true,436125,European Paintings,Drawing,Woman on a Sofa,,,,,,Artist,,Edgar Degas,"French, Paris 1834–1917 Paris",,"Degas, Edgar",French,1834,1917,1875,1875,1875,"Oil colors freely mixed with turpentine, with touches of pastel, over graphite underdrawing, on pink paper",19 1/8 x 16 3/4 in. (48.6 x 42.5 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Drawings,,http://www.metmuseum.org/art/collection/search/436125,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.187,false,true,436165,European Paintings,Drawing,Two Dancers,,,,,,Artist,,Edgar Degas,"French, Paris 1834–1917 Paris",,"Degas, Edgar",French,1834,1917,1873,1873,1873,"Dark brown wash and white gouache on bright pink commercially coated wove paper, now faded to pale pink",24 1/8 x 15 1/2 in. (61.3 x 39.4 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Drawings,,http://www.metmuseum.org/art/collection/search/436165,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.188,false,true,436135,European Paintings,Drawing,Dancer with a Fan,,,,,,Artist,,Edgar Degas,"French, Paris 1834–1917 Paris",,"Degas, Edgar",French,1834,1917,ca. 1880,1875,1885,Pastel on gray-green laid paper,24 x 16 1/2 in. (61 x 41.9 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Drawings,,http://www.metmuseum.org/art/collection/search/436135,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.189,false,true,436166,European Paintings,Drawing,Two Dancers,,,,,,Artist,,Edgar Degas,"French, Paris 1834–1917 Paris",,"Degas, Edgar",French,1834,1917,ca. 1879,1874,1879,Charcoal and white chalk on green commercially coated wove paper,25 1/8 x 19 1/4 in. (63.8 x 48.9 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Drawings,,http://www.metmuseum.org/art/collection/search/436166,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.190,false,true,436131,European Paintings,Drawing,Bather Stepping into a Tub,,,,,,Artist,,Edgar Degas,"French, Paris 1834–1917 Paris",,"Degas, Edgar",French,1834,1917,ca. 1890,1885,1895,"Pastel and charcoal on blue laid paper, mounted at perimeter on backing board",22 x 18 3/4 in. (55.9 x 47.6 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Drawings,,http://www.metmuseum.org/art/collection/search/436131,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.553,false,true,436171,European Paintings,Drawing,Woman Drying Her Arm,,,,,,Artist,,Edgar Degas,"French, Paris 1834–1917 Paris",,"Degas, Edgar",French,1834,1917,late 1880s–early 1890s,1885,1895,"Pastel and charcoal on light pink wove paper, discolored at the edges",12 x 17 1/2in. (30.5 x 44.5cm) Frame: 17 1/8 x 22 7/8 x 1 7/8 in. (43.5 x 58.1 x 4.8 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Drawings,,http://www.metmuseum.org/art/collection/search/436171,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.554,false,true,436143,European Paintings,Drawing,Fan Mount: The Ballet,,,,,,Artist,,Edgar Degas,"French, Paris 1834–1917 Paris",,"Degas, Edgar",French,1834,1917,1879,1879,1879,"Watercolor, India ink, silver, and gold on silk",6 1/8 x 21 1/4 in. (15.6 x 54 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Drawings,,http://www.metmuseum.org/art/collection/search/436143,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.555,false,true,436142,European Paintings,Drawing,Fan Mount: Ballet Girls,,,,,,Artist,,Edgar Degas,"French, Paris 1834–1917 Paris",,"Degas, Edgar",French,1834,1917,1879,1879,1879,"Watercolor, silver, and gold on silk",7 1/2 x 22 3/4 in. (19.1 x 57.8 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Drawings,,http://www.metmuseum.org/art/collection/search/436142,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.556,false,true,436157,European Paintings,Drawing,Russian Dancer,,,,,,Artist,,Edgar Degas,"French, Paris 1834–1917 Paris",,"Degas, Edgar",French,1834,1917,1899,1899,1899,Pastel over charcoal on tracing paper,24 3/8 x 18 in. (61.9 x 45.7 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Drawings,,http://www.metmuseum.org/art/collection/search/436157,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.557,false,true,436136,European Paintings,Drawing,Dancer with a Fan,,,,,,Artist,,Edgar Degas,"French, Paris 1834–1917 Paris",,"Degas, Edgar",French,1834,1917,ca. 1890–95,1890,1895,Pastel and charcoal on buff-colored wove tracing paper,21 7/8 x 19 1/4 in. (55.6 x 48.9 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Drawings,,http://www.metmuseum.org/art/collection/search/436136,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.558,false,true,436163,European Paintings,Drawing,Three Dancers Preparing for Class,,,,,,Artist,,Edgar Degas,"French, Paris 1834–1917 Paris",,"Degas, Edgar",French,1834,1917,after 1878,1878,1885,Pastel on buff-colored wove paper,21 1/2 x 20 1/2 in. (54.6 x 52.1 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Drawings,,http://www.metmuseum.org/art/collection/search/436163,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1974.356.30,false,true,436134,European Paintings,Drawing,Dancer Onstage,,,,,,Artist,,Edgar Degas,"French, Paris 1834–1917 Paris",,"Degas, Edgar",French,1834,1917,ca. 1877,1872,1882,"Gouache over graphite underdrawing on thin wove commercially coated yellow paper, laid down on board",Paper 6 3/4 x 8 3/8 in. (17.1 x 21.3 cm); board 7 x 9 in. (17.8 x 22.9 cm),"The Lesley and Emma Sheafer Collection, Bequest of Emma A. Sheafer, 1973",,,,,,,,,,,,Drawings,,http://www.metmuseum.org/art/collection/search/436134,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -56.50,false,true,437379,European Paintings,Drawing,Bouquet of Flowers,,,,,,Artist,,Odilon Redon,"French, Bordeaux 1840–1916 Paris",,"Redon, Odilon",French,1840,1916,probably ca. 1905,1900,1910,Pastel on paper,31 5/8 x 25 1/4 in. (80.3 x 64.1 cm),"Gift of Mrs. George B. Post, 1956",,,,,,,,,,,,Drawings,,http://www.metmuseum.org/art/collection/search/437379,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -60.54,false,true,437377,European Paintings,Drawing,"Madame Arthur Fontaine (Marie Escudier, born 1865)",,,,,,Artist,,Odilon Redon,"French, Bordeaux 1840–1916 Paris",,"Redon, Odilon",French,1840,1916,1901,1901,1901,Pastel on paper,28 1/2 x 22 1/2 in. (72.4 x 57.2 cm),"The Mr. and Mrs. Henry Ittleson Jr. Purchase Fund, 1960",,,,,,,,,,,,Drawings,,http://www.metmuseum.org/art/collection/search/437377,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -32.100.133,false,true,437521,European Paintings,Drawing,A Village in a Valley,,,,,,Artist,,Théodore Rousseau,"French, Paris 1812–1867 Barbizon",,"Rousseau, Théodore",French,1812,1867,late 1820s,1825,1835,"Oil on paper, mounted on canvas",9 1/8 x 16 in. (23.2 x 40.6 cm),"The Friedsam Collection, Bequest of Michael Friedsam, 1931",,,,,,,,,,,,Drawings,,http://www.metmuseum.org/art/collection/search/437521,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1990.117,false,true,435612,European Paintings,Drawing,"The Artist's Wife (Périe, 1849–1887) Reading",,,,,,Artist,,Albert Bartholomé,"French, Thiverval 1848–1928 Paris",,"Bartholomé, Albert",French,1848,1928,1883,1883,1883,"Pastel and charcoal on wove paper, laid down on blue wove paper, laid down on stretched canvas",19 7/8 x 24 1/8 in. (50.5 x 61.3 cm),"Catharine Lorillard Wolfe Collection, Wolfe Fund, 1990",,,,,,,,,,,,Drawings,,http://www.metmuseum.org/art/collection/search/435612,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1974.356.34,false,true,437435,European Paintings,Drawing,The Milliner,,,,,,Artist,,Auguste Renoir,"French, Limoges 1841–1919 Cagnes-sur-Mer",,"Renoir, Auguste",French,1841,1919,1877,1877,1877,Pastel on paper,21 x 16 1/4 in. (53.3 x 41.3 cm),"The Lesley and Emma Sheafer Collection, Bequest of Emma A. Sheafer, 1973",,,,,,,,,,,,Drawings,,http://www.metmuseum.org/art/collection/search/437435,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -64.153,false,true,437833,European Paintings,Drawing,"Madame Thadée Natanson (Misia Godebska, 1872–1950) at the Theater",,,,,,Artist,,Henri de Toulouse-Lautrec,"French, Albi 1864–1901 Saint-André-du-Bois",,"Toulouse-Lautrec, Henri de",French,1864,1901,1895,1895,1895,Oil on cardboard,24 1/2 x 29 1/2 in. (62.2 x 74.9 cm),"Gift of Mr. and Mrs. Richard Rodgers, 1964",,,,,,,,,,,,Drawings,,http://www.metmuseum.org/art/collection/search/437833,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2003.20.14,false,true,438017,European Paintings,Drawing,Henri-Gabriel Ibels (1867–1936),,,,,,Artist,,Henri de Toulouse-Lautrec,"French, Albi 1864–1901 Saint-André-du-Bois",,"Toulouse-Lautrec, Henri de",French,1864,1901,1892–93,1892,1893,Oil on cardboard,20 1/2 x 15 1/2 in. (52.1 x 39.4 cm),"The Walter H. and Leonore Annenberg Collection, Bequest of Walter H. Annenberg, 2002",,,,,,,,,,,,Drawings,,http://www.metmuseum.org/art/collection/search/438017,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1994.105,false,true,437315,European Paintings,Drawing,Fan Mount: The Cabbage Gatherers,,,,,,Artist,,Camille Pissarro,"French, Charlotte Amalie, Saint Thomas 1830–1903 Paris",,"Pissarro, Camille",French,1830,1903,ca. 1878–79,1850,1903,Gouache on silk,6 1/2 x 20 1/2 in. (16.5 x 52.1 cm),"Purchase, Leonora Brenauer Bequest, in memory of her father, Joseph B. Brenauer, 1994",,,,,,,,,,,,Drawings,,http://www.metmuseum.org/art/collection/search/437315,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -54.195,false,true,436336,European Paintings,"Painting, polyptych",The Pérussis Altarpiece,,,,,,Artist,Circle of,Nicolas Froment,,,"Froment, Nicolas",French,1460,1484,1480,1480,1480,Oil and gold on wood,"Three panels, each 54 1/2 x 23 in. (138.4 x 58.4 cm)","Purchase, Mary Wetmore Shively Bequest, in memory of her husband, Henry L. Shively, M.D., 1954",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436336,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -06.1268,false,true,435672,European Paintings,Painting,Angelica and Medoro,,,,,,Artist,,Jacques Blanchard,"French, 1600–1638",,"Blanchard, Jacques",French,1600,1638,possibly early 1630s,1630,1633,Oil on canvas,With added strip at top 47 7/8 x 69 1/4 in. (121.6 x 175.9 cm),"Gift of George A. Hearn, 1906",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435672,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -43.85.1,false,true,436709,European Paintings,Painting,Ingres (1780–1867) as a Young Man,,,,,,Artist,?,Madame Gustave Héquet,"French, 1845–1865",,"Héquet, Gustave, Madame",French,1845,1865,1850–60,1850,1860,Oil on canvas,34 x 27 1/ 2 in. (86.4 x 69.9 cm),"Bequest of Grace Rainey Rogers, 1943",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436709,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -67.187.101,false,true,489542,European Paintings,Painting,The Large Bouquet,,,,,,Artist,,Séraphine Louis,"French, 1864–1942",,"Louis, Séraphine",French,1864,1942,ca. 1907,1902,1912,Oil on canvas,57 1/2 x 44 3/4 in. (146.1 x 113.7 cm),"Bequest of Miss Adelaide Milton de Groot (1876–1967), 1967",,,,,,,,,,,,Paintings,"© 2016 Artists Rights Society (ARS), New York",http://www.metmuseum.org/art/collection/search/489542,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -27.59,false,true,437082,European Paintings,Painting,The Baker's Cart,,,,,,Artist,,Jean Michelin,"French, ca. 1616–1670",,"Michelin, Jean",French,1616,1670,1656,1656,1656,Oil on canvas,38 3/4 x 49 3/8 in. (98.4 x 125.4 cm),"Fletcher Fund, 1927",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437082,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -32.100.119,false,true,437143,European Paintings,Painting,Portrait of a Man in White,,,,,,Artist,,Monogrammist LAM,"French, active 1568–74",,Monogrammist LAM,French,1568,1574,1574,1574,1574,Oil on wood,16 1/8 x 9 1/2 in. (41 x 24.1 cm),"The Friedsam Collection, Bequest of Michael Friedsam, 1931",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437143,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -53.160,false,true,436190,European Paintings,Painting,Louis-Félix Amiel (1802–1864),,,,,,Artist,,Eugène Devéria,"French, Paris 1805–1865 Pau",,"Devéria, Eugène",French,1805,1865,1837,1837,1837,Oil on canvas,24 x 19 3/4 in. (61 x 50.2 cm),"Rogers Fund, 1953",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436190,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2004.243,false,true,438737,European Paintings,Painting,Faustine Léo (1832–1865),,,,,,Artist,,Henri Lehmann,"French, Kiel 1814–1882 Paris",,"Lehmann, Henri",French,1814,1882,1842,1842,1842,Oil on canvas,39 3/8 x 32 in. (100 x 81.3 cm),"Purchase, Wolfe Fund and Mr. and Mrs. Frank E. Richardson Gift, 2004",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/438737,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1995.317,false,true,437865,European Paintings,Painting,The Hunt Breakfast,,,,,,Artist,,Carle (Charles André) Vanloo,"French, Nice 1705–1765 Paris",,"Vanloo, Carle (Charles André)",French,1705,1765,ca. 1737,1732,1742,Oil on canvas,23 1/4 x 19 1/2 in. (59.1 x 49.5 cm),"Wrightsman Fund, 1995",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437865,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -07.225.285,false,true,437845,European Paintings,"Painting, study for a tapestry cartoon",The Triumph of Mordecai,,,,,,Artist,,Jean François de Troy,"French, Paris 1679–1752 Rome",,"Troy, Jean François de",French,1679,1752,ca. 1736,1731,1741,Oil on canvas,33 7/8 x 59 1/8 in. (86 x 150.2 cm),"Gift of J. Pierpont Morgan, 1906",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437845,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2002.31,false,true,438546,European Paintings,Painting,Madame Charles Maurice de Talleyrand Périgord (1761–1835),,,,,,Artist,,baron François Gérard,"French, Rome 1770–1837 Paris",,"Gérard, François, baron",French,1770,1837,ca. 1804,1799,1809,Oil on canvas,88 7/8 x 64 7/8 in. (225.7 x 164.8 cm),"Wrightsman Fund, 2002",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/438546,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2012.348,false,true,441969,European Paintings,Painting,"Charles Maurice de Talleyrand Périgord (1754–1838), Prince de Bénévent",,,,,,Artist,,baron François Gérard,"French, Rome 1770–1837 Paris",,"Gérard, François, baron",French,1770,1837,1808,1808,1808,Oil on canvas,83 7/8 x 57 7/8 in. (213 x 147 cm),"Purchase, Mrs. Charles Wrightsman Gift, 2012",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/441969,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1994.190,false,true,437340,European Paintings,Painting,"Charles Maurice de Talleyrand Périgord (1754–1838), Prince de Talleyrand",,,,,,Artist,,Pierre Paul Prud'hon,"French, Cluny 1758–1823 Paris",,"Prud'hon, Pierre Paul",French,1758,1823,1817,1817,1817,Oil on canvas,85 x 55 7/8 in. (215.9 x 141.9 cm),"Purchase, Mrs. Charles Wrightsman Gift, in memory of Jacqueline Bouvier Kennedy Onassis, 1994",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437340,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -39.15,false,true,437512,European Paintings,Painting,The Banks of the Bièvre near Bicêtre,,,,,,Artist,,Henri Rousseau (le Douanier),"French, Laval 1844–1910 Paris",,"Rousseau, Henri (le Douanier)",French,1844,1910,ca. 1908–09,1903,1913,Oil on canvas,21 1/2 x 18 in. (54.6 x 45.7 cm),"Gift of Marshall Field, 1939",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437512,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -51.112.5,true,true,438822,European Paintings,Painting,The Repast of the Lion,,,,,,Artist,,Henri Rousseau (le Douanier),"French, Laval 1844–1910 Paris",,"Rousseau, Henri (le Douanier)",French,1844,1910,ca. 1907,1902,1912,Oil on canvas,44 3/4 x 63 in. (113.7 x 160 cm),"Bequest of Sam A. Lewisohn, 1951",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/438822,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1978.493,false,true,435673,European Paintings,Painting,The Outer Harbor of Brest,,,,,,Artist,,Henri Joseph van Blarenberghe,"French, Lille 1750–1826 Lille",,"Blarenberghe, Henri Joseph van",French,1750,1826,1773,1773,1773,Oil on canvas,29 1/4 x 42 1/8 in. (74.3 x 107 cm),"Gift of Mrs. Vincent Astor, 1978",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435673,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -49.4,false,true,435849,European Paintings,Painting,"Mrs. William Astor (Caroline Webster Schermerhorn, 1831–1908)",,,,,,Artist,,Carolus-Duran (Charles-Auguste-Émile Durant),"French, Lille 1837–1917 Paris",,"Carolus-Duran, Charles-Auguste-Émile Durant",French,1837,1917,1890,1890,1890,Oil on canvas,83 1/2 x 42 1/4 in. (212.1 x 107.3 cm),"Gift of R. Thornton Wilson and Orme Wilson, 1949",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435849,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2014.608,false,true,669033,European Paintings,Painting,Henri Fantin-Latour (1836–1904),,,,,,Artist,,Carolus-Duran (Charles-Auguste-Émile Durant),"French, Lille 1837–1917 Paris",,"Carolus-Duran, Charles-Auguste-Émile Durant",French,1837,1917,1861,1861,1861,Oil on canvas,18 × 14 7/8 in. (45.7 × 37.8 cm),"Purchase, Marisa I. Alonso Bequest and Elizabeth and Thomas Easton Gift, in memory of their mother, Joan K. Easton, 2014",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/669033,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -26.260.9,false,true,437265,European Paintings,Painting,"Maria Luisa of Parma (1751–1819), Later Queen of Spain",,,,,,Artist,,Laurent Pécheux,"French, Lyons 1729–1821 Turin",,"Pécheux, Laurent",French,1729,1821,1765,1765,1765,Oil on canvas,90 7/8 x 64 3/4 in. (230.8 x 164.5 cm),"Bequest of Annie C. Kane, 1926",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437265,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2003.42.42,false,true,438665,European Paintings,Painting,Pope Gregory XVI Visiting the Church of San Benedetto at Subiaco,,,,,,Artist,,Jean-François Montessuy,"French, Lyons 1804–1876 Lyons",,"Montessuy, Jean-François",French,1804,1876,1843,1843,1843,Oil on canvas,49 1/4 x 55 3/8 in. (125.1 x 140.7 cm),"The Whitney Collection, Gift of Wheelock Whitney III, and Purchase, Gift of Mr. and Mrs. Charles S. McVeigh, by exchange, 2003",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/438665,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -87.20.1,false,true,437052,European Paintings,Painting,"1807, Friedland",,,,,,Artist,,Ernest Meissonier,"French, Lyons 1815–1891 Paris",,"Meissonier, Ernest",French,1815,1891,ca. 1861–75,1860,1875,Oil on canvas,53 1/2 x 95 1/2 in. (135.9 x 242.6 cm),"Gift of Henry Hilton, 1887",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437052,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -08.136.7,false,true,437050,European Paintings,Painting,Soldier Playing the Theorbo,,,,,,Artist,,Ernest Meissonier,"French, Lyons 1815–1891 Paris",,"Meissonier, Ernest",French,1815,1891,1865,1865,1865,Oil on wood,11 1/2 x 8 5/8 in. (29.2 x 21.9 cm),"Bequest of Martha T. Fiske Collord, in memory of her first husband, Josiah M. Fiske, 1908",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437050,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -87.15.37,false,true,437051,European Paintings,Painting,A General and His Aide-de-camp,,,,,,Artist,,Ernest Meissonier,"French, Lyons 1815–1891 Paris",,"Meissonier, Ernest",French,1815,1891,1869,1869,1869,Oil on wood,7 3/4 x 10 7/8 in. (19.7 x 27.6 cm),"Catharine Lorillard Wolfe Collection, Bequest of Catharine Lorillard Wolfe, 1887",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437051,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -25.110.39,false,true,437049,European Paintings,Painting,The Card Players,,,,,,Artist,,Ernest Meissonier,"French, Lyons 1815–1891 Paris",,"Meissonier, Ernest",French,1815,1891,1863,1863,1863,Oil on wood,13 7/8 x 10 1/2 in. (35.2 x 26.7 cm),"Bequest of Collis P. Huntington, 1900",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437049,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -30.20,false,true,437350,European Paintings,Painting,Tamaris,,,,,,Artist,,Pierre Puvis de Chavannes,"French, Lyons 1824–1898 Paris",,"Puvis de Chavannes, Pierre",French,1824,1898,ca. 1886–87,1886,1887,Oil on canvas,10 x 15 1/2 in. (25.4 x 39.4 cm),"H. O. Havemeyer Collection, Gift of Mrs. J. Watson Webb, 1930",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437350,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -06.177,false,true,437344,European Paintings,Painting,The Shepherd's Song,,,,,,Artist,,Pierre Puvis de Chavannes,"French, Lyons 1824–1898 Paris",,"Puvis de Chavannes, Pierre",French,1824,1898,1891,1891,1891,Oil on canvas,41 1/8 x 43 1/4 in. (104.5 x 109.9 cm),"Rogers Fund, 1906",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437344,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -58.15.1,false,true,437347,European Paintings,Painting,Ludus pro patria (Patriotic Games),,,,,,Artist,,Pierre Puvis de Chavannes,"French, Lyons 1824–1898 Paris",,"Puvis de Chavannes, Pierre",French,1824,1898,ca. 1883–89,1878,1894,Oil on canvas,13 1/8 x 52 7/8 in. (33.3 x 134.3 cm),"Gift of Mrs. Harry Payne Bingham, 1958",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437347,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -58.15.2,false,true,437346,European Paintings,Painting,Inter artes et naturam (Between Art and Nature),,,,,,Artist,,Pierre Puvis de Chavannes,"French, Lyons 1824–1898 Paris",,"Puvis de Chavannes, Pierre",French,1824,1898,ca. 1890–95,1885,1900,Oil on canvas,15 7/8 x 44 3/4 in. (40.3 x 113.7 cm),"Gift of Mrs. Harry Payne Bingham, 1958",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437346,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -30.95.253,false,true,437349,European Paintings,Painting,Sleep,,,,,,Artist,,Pierre Puvis de Chavannes,"French, Lyons 1824–1898 Paris",,"Puvis de Chavannes, Pierre",French,1824,1898,ca. 1867–70,1867,1870,Oil on canvas,26 1/8 x 41 3/4 in. (66.4 x 106 cm),"Theodore M. Davis Collection, Bequest of Theodore M. Davis, 1915",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437349,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.117,false,true,437343,European Paintings,Painting,The Allegory of the Sorbonne,,,,,,Artist,,Pierre Puvis de Chavannes,"French, Lyons 1824–1898 Paris",,"Puvis de Chavannes, Pierre",French,1824,1898,1889,1889,1889,Oil on canvas,32 5/8 x 180 1/4 in. (82.9 x 457.8 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437343,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -50.189,false,true,436836,European Paintings,Painting,Allegory of Music,,,,,,Artist,,Laurent de La Hyre,"French, Paris 1606–1656 Paris",,"La Hyre, Laurent de",French,1606,1656,1649,1649,1649,Oil on canvas,41 5/8 x 56 3/4 in. (105.7 x 144.1 cm),"Charles B. Curtis Fund, 1950",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436836,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1984.342,false,true,436858,European Paintings,Painting,The Rape of Tamar,,,,,,Artist,,Eustache Le Sueur,"French, Paris 1616–1655 Paris",,"Le Sueur, Eustache",French,1616,1655,probably ca. 1640,1635,1645,Oil on canvas,74 1/2 x 63 1/2 in. (189.2 x 161.3 cm),"Purchase, Mr. and Mrs. Charles Wrightsman Gift, 1984",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436858,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.183,false,true,442761,European Paintings,Painting,The Sacrifice of Polyxena,,,,,,Artist,,Charles Le Brun,"French, Paris 1619–1690 Paris",,"Le Brun, Charles",French,1619,1690,1647,1647,1647,Oil on canvas,67 5/16 × 51 9/16 in. (171 × 131 cm),"Purchase, 2012 Benefit Fund, and Bequest of Grace Wilkes and Fletcher Fund, by exchange, 2013",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/442761,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2014.250,false,true,626692,European Paintings,Painting,Everhard Jabach (1618–1695) and His Family,,,,,,Artist,,Charles Le Brun,"French, Paris 1619–1690 Paris",,"Le Brun, Charles",French,1619,1690,ca. 1660,1655,1665,Oil on canvas,110 1/4 × 129 1/8 in. (280 × 328 cm),"Purchase, Mrs. Charles Wrightsman Gift, in honor of Keith Christiansen, 2014",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/626692,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -03.37.2,false,true,436846,European Paintings,Painting,"Portrait of a Woman, Possibly Madame Claude Lambert de Thorigny (Marie Marguerite Bontemps, 1668–1701), and an Enslaved Servant",,,,,,Artist,,Nicolas de Largillierre,"French, Paris 1656–1746 Paris",,"Largillierre, Nicolas de",French,1656,1746,1696,1696,1696,Oil on canvas,55 x 42 in. (139.7 x 106.7 cm),"Rogers Fund, 1903",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436846,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1973.311.4,false,true,436847,European Paintings,Painting,André François Alloys de Theys d'Herculais (1692–1779),,,,,,Artist,,Nicolas de Largillierre,"French, Paris 1656–1746 Paris",,"Largillierre, Nicolas de",French,1656,1746,1727,1727,1727,Oil on canvas,54 1/4 x 41 1/2 in. (137.8 x 105.4 cm),"Gift of Mr. and Mrs. Charles Wrightsman, 1973",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436847,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -45.172,false,true,437181,European Paintings,Painting,Madame Marsollier and Her Daughter,,,,,,Artist,,Jean Marc Nattier,"French, Paris 1685–1766 Paris",,"Nattier, Jean Marc",French,1685,1766,1749,1749,1749,Oil on canvas,57 1/2 x 45 in. (146.1 x 114.3 cm),"Bequest of Florence S. Schuette, 1945",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437181,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -03.37.3,false,true,437183,European Paintings,Painting,Madame de Maison-Rouge as Diana,,,,,,Artist,,Jean Marc Nattier,"French, Paris 1685–1766 Paris",,"Nattier, Jean Marc",French,1685,1766,1756,1756,1756,Oil on canvas,53 3/4 x 41 3/8 in. (136.5 x 105.1 cm),"Rogers Fund, 1903",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437183,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -56.100.2,false,true,437184,European Paintings,Painting,The Spring (La Source),,,,,,Artist,,Jean Marc Nattier,"French, Paris 1685–1766 Paris",,"Nattier, Jean Marc",French,1685,1766,1738,1738,1738,Oil on canvas,31 3/4 x 25 5/8 in. (80.6 x 65.1 cm),"Gift of Jessie Woolworth Donahue, 1956",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437184,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -58.102.1,false,true,437185,European Paintings,Painting,"Marie Françoise de La Cropte de St. Abre, Marquise d'Argence",,,,,,Artist,,Jean Marc Nattier,"French, Paris 1685–1766 Paris",,"Nattier, Jean Marc",French,1685,1766,1744,1744,1744,Oil on canvas,32 1/2 x 25 1/2 in. (82.6 x 64.8 cm),"Gift of Jessie Woolworth Donahue, 1958",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437185,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1982.60.42,false,true,437182,European Paintings,Painting,"Portrait of a Woman, Called the Marquise Perrin de Cypierre",,,,,,Artist,,Jean Marc Nattier,"French, Paris 1685–1766 Paris",,"Nattier, Jean Marc",French,1685,1766,1753,1753,1753,Oil on canvas,"31 1/2 x 25 1/4 in. (80 x 64.1 cm), with later additions of 1 1/4 in. (3.2 cm) at bottom, 1 in. (2.5 cm) at left, and 1/2 in. (1.3 cm) at right","The Jack and Belle Linsky Collection, 1982",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437182,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2004.85,false,true,438726,European Paintings,Painting,The Servant Justified,,,,,,Artist,,Nicolas Lancret,"French, Paris 1690–1743 Paris",,"Lancret, Nicolas",French,1690,1743,ca. 1740,1735,1745,Oil on copper,11 x 14 in. (27.9 x 35.6 cm),"Purchase, Walter and Leonore Annenberg and The Annenberg Foundation Gift, 2004",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/438726,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2004.86,false,true,438727,European Paintings,Painting,Brother Philippe's Geese,,,,,,Artist,,Nicolas Lancret,"French, Paris 1690–1743 Paris",,"Lancret, Nicolas",French,1690,1743,ca. 1736,1731,1741,Oil on copper,10 3/4 x 13 7/8 in. (27.3 x 35.2 cm),"Purchase, Walter and Leonore Annenberg and The Annenberg Foundation Gift, 2004",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/438727,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -59.9,false,true,435887,European Paintings,Painting,The Silver Tureen,,,,,,Artist,,Jean Siméon Chardin,"French, Paris 1699–1779 Paris",,"Chardin, Jean Siméon",French,1699,1779,ca. 1728–30,1728,1730,Oil on canvas,30 x 42 1/2 in. (76.2 x 108 cm),"Fletcher Fund, 1959",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435887,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -49.24,true,true,435888,European Paintings,Painting,Soap Bubbles,,,,,,Artist,,Jean Siméon Chardin,"French, Paris 1699–1779 Paris",,"Chardin, Jean Siméon",French,1699,1779,ca. 1733–34,1733,1734,Oil on canvas,24 x 24 7/8 in. (61 x 63.2 cm),"Wentworth Fund, 1949",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435888,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -44.141,false,true,435743,European Paintings,Painting,The Dispatch of the Messenger,,,,,,Artist,,François Boucher,"French, Paris 1703–1770 Paris",,"Boucher, François",French,1703,1770,1765,1765,1765,Oil on canvas,"Oval, 12 5/8 x 10 1/2 in. (32.1 x 26.7 cm)","Gift of Mrs. Joseph Heine, in memory of her husband, I. D. Levy, 1944",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435743,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -66.167,false,true,435744,European Paintings,Painting,Virgin and Child with the Young Saint John the Baptist and Angels,,,,,,Artist,,François Boucher,"French, Paris 1703–1770 Paris",,"Boucher, François",French,1703,1770,1765,1765,1765,Oil on canvas,"Oval, 16 1/8 x 13 5/8 in. (41 x 34.6 cm)","Gift of Adelaide Milton de Groot, in memory of the de Groot and Hawley families, 1966",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435744,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -49.7.46,false,true,435738,European Paintings,Painting,The Interrupted Sleep,,,,,,Artist,,François Boucher,"French, Paris 1703–1770 Paris",,"Boucher, François",French,1703,1770,1750,1750,1750,Oil on canvas,Overall 32 1/4 x 29 5/8 in. (81.9 x 75.2 cm); painted surface (irregular oval) 31 x 27 3/4 in. (78.7 x 70.5 cm),"The Jules Bache Collection, 1949",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435738,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -20.155.9,true,true,435739,European Paintings,"Painting, overdoor",The Toilette of Venus,,,,,,Artist,,François Boucher,"French, Paris 1703–1770 Paris",,"Boucher, François",French,1703,1770,1751,1751,1751,Oil on canvas,42 5/8 x 33 1/2 in. (108.3 x 85.1 cm),"Bequest of William K. Vanderbilt, 1920",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435739,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -53.225.1,false,true,435745,European Paintings,Painting,Shepherd's Idyll,,,,,,Artist,,François Boucher,"French, Paris 1703–1770 Paris",,"Boucher, François",French,1703,1770,1768,1768,1768,Oil on canvas,94 1/2 x 93 1/2 in. (240 x 237.5 cm),"Gift of Julia A. Berwind, 1953",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435745,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -53.225.2,false,true,435746,European Paintings,Painting,Washerwomen,,,,,,Artist,,François Boucher,"French, Paris 1703–1770 Paris",,"Boucher, François",French,1703,1770,1768,1768,1768,Oil on canvas,95 x 93 in. (241.3 x 236.2 cm),"Gift of Julia A. Berwind, 1953",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435746,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -69.155.1,false,true,435740,European Paintings,Painting,Allegory of Autumn,,,,,,Artist,,François Boucher,"French, Paris 1703–1770 Paris",and Workshop,"Boucher, François",French,1703,1770,1753,1753,1753,Oil on canvas,"Irregular, 44 3/4 x 63 3/4 in. (113.7 x 161.9 cm)","Purchase, Mr. and Mrs. Charles Wrightsman Gift, 1969",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435740,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -69.155.2,false,true,435741,European Paintings,Painting,Allegory of Lyric Poetry,,,,,,Artist,,François Boucher,"French, Paris 1703–1770 Paris",and Workshop,"Boucher, François",French,1703,1770,1753,1753,1753,Oil on canvas,"Irregular, 45 1/4 x 62 3/4 in. (114.9 x 159.4 cm)","Purchase, Mr. and Mrs. Charles Wrightsman Gift, 1969",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435741,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1982.60.44,false,true,435737,European Paintings,Painting,Imaginary Landscape with the Palatine Hill from Campo Vaccino,,,,,,Artist,,François Boucher,"French, Paris 1703–1770 Paris",,"Boucher, François",French,1703,1770,1734,1734,1734,Oil on canvas,25 x 31 7/8 in. (63.5 x 81 cm),"The Jack and Belle Linsky Collection, 1982",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435737,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1982.60.45,false,true,435747,European Paintings,Painting,"Jupiter, in the Guise of Diana, and Callisto",,,,,,Artist,,François Boucher,"French, Paris 1703–1770 Paris",,"Boucher, François",French,1703,1770,1763,1763,1763,Oil on canvas,"Oval, 25 1/2 x 21 5/8 in. (64.8 x 54.9 cm)","The Jack and Belle Linsky Collection, 1982",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435747,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1982.60.46,false,true,435742,European Paintings,Painting,Angelica and Medoro,,,,,,Artist,,François Boucher,"French, Paris 1703–1770 Paris",,"Boucher, François",French,1703,1770,1763,1763,1763,Oil on canvas,"Oval, 26 1/4 x 22 1/8 in. (66.7 x 56.2 cm)","The Jack and Belle Linsky Collection, 1982",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435742,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -69.129,false,true,437286,European Paintings,Painting,The Death of Harmonia,,,,,,Artist,,Jean-Baptiste Marie Pierre,"French, Paris 1714–1789 Paris",,"Pierre, Jean-Baptiste Marie",French,1714,1789,ca. 1740–41,1740,1741,Oil on canvas,77 1/2 x 58 1/4 in. (196.9 x 148 cm),"Gift of Mr. and Mrs. Harry N. Abrams, by exchange, 1969",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437286,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -49.7.47,false,true,436214,European Paintings,Painting,"Marie Rinteau, called Mademoiselle de Verrières",,,,,,Artist,,François Hubert Drouais,"French, Paris 1727–1775 Paris",,"Drouais, François Hubert",French,1727,1775,1761,1761,1761,Oil on canvas,45 1/2 x 34 5/8 in. (115.6 x 87.9 cm),"The Jules Bache Collection, 1949",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436214,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -64.159.1,false,true,436215,European Paintings,Painting,Madame Sophie de France (1734–1782),,,,,,Artist,,François Hubert Drouais,"French, Paris 1727–1775 Paris",,"Drouais, François Hubert",French,1727,1775,1762,1762,1762,Oil on canvas,25 5/8 x 20 7/8 in. (65.1 x 53 cm),"Gift of Barbara Lowe Fallass, 1964",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436215,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -65.242.2,false,true,436216,European Paintings,Painting,Portrait of a Young Woman as a Vestal Virgin,,,,,,Artist,,François Hubert Drouais,"French, Paris 1727–1775 Paris",,"Drouais, François Hubert",French,1727,1775,1767,1767,1767,Oil on canvas,31 1/2 x 24 7/8 in. (80 x 63.2 cm),"Gift of Mrs. William M. Haupt, from the collection of Mrs. James B. Haggin, 1965",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436216,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -17.120.210,false,true,436213,European Paintings,Painting,"Portrait of a Woman, Said to be Madame Charles Simon Favart (Marie Justine Benoîte Duronceray, 1727–1772)",,,,,,Artist,,François Hubert Drouais,"French, Paris 1727–1775 Paris",,"Drouais, François Hubert",French,1727,1775,1757,1757,1757,Oil on canvas,31 1/2 x 25 1/2 in. (80 x 64.8 cm),"Mr. and Mrs. Isaac D. Fletcher Collection, Bequest of Isaac D. Fletcher, 1917",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436213,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -35.40.1,false,true,437479,European Paintings,Painting,The Return of the Cattle,,,,,,Artist,,Hubert Robert,"French, Paris 1733–1808 Paris",,"Robert, Hubert",French,1733,1808,ca. 1773–75,1773,1775,Oil on canvas,80 3/4 x 48 in. (205.1 x 121.9 cm),"Bequest of Lucy Work Hewitt, 1934",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437479,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -35.40.2,false,true,437468,European Paintings,Painting,The Portico of a Country Mansion,,,,,,Artist,,Hubert Robert,"French, Paris 1733–1808 Paris",,"Robert, Hubert",French,1733,1808,1773,1773,1773,Oil on canvas,80 3/4 x 48 1/4 in. (205.1 x 122.6 cm),"Bequest of Lucy Work Hewitt, 1934",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437468,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -17.190.25,false,true,437470,European Paintings,Painting,The Mouth of a Cave,,,,,,Artist,,Hubert Robert,"French, Paris 1733–1808 Paris",,"Robert, Hubert",French,1733,1808,1784,1784,1784,Oil on canvas,68 3/4 x 31 1/4 in. (174.6 x 79.4 cm),"Gift of J. Pierpont Morgan, 1917",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437470,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -07.225.504,false,true,437864,European Paintings,Painting,Vase of Flowers and Conch Shell,,,,,,Artist,,Anne Vallayer-Coster,"French, Paris 1744–1818 Paris",,"Vallayer-Coster, Anne",French,1744,1818,1780,1780,1780,Oil on canvas,"Oval, 19 3/4 x 15 in. (50.2 x 38.1 cm)","Gift of J. Pierpont Morgan, 1906",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437864,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -53.225.5,false,true,436840,European Paintings,Painting,"Self-Portrait with Two Pupils, Marie Gabrielle Capet (1761–1818) and Marie Marguerite Carreaux de Rosemond (died 1788)",,,,,,Artist,,Adélaïde Labille-Guiard,"French, Paris 1749–1803 Paris",,"Labille-Guiard, Adélaïde",French,1749,1803,1785,1785,1785,Oil on canvas,83 x 59 1/2 in. (210.8 x 151.1 cm),"Gift of Julia A. Berwind, 1953",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436840,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -57.103,false,true,436875,European Paintings,Painting,The Interior of an Atelier of a Woman Painter,,,,,,Artist,,Marie Victoire Lemoine,"French, Paris 1754–1820 Paris",,"Lemoine, Marie Victoire",French,1754,1820,1789,1789,1789,Oil on canvas,45 7/8 x 35 in. (116.5 x 88.9 cm),"Gift of Mrs. Thorneycroft Ryle, 1957",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436875,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1982.60.49,false,true,437775,European Paintings,Painting,The Billiard Room,,,,,,Artist,,Nicolas Antoine Taunay,"French, Paris 1755–1830 Paris",,"Taunay, Nicolas Antoine",French,1755,1830,ca. 1808,1803,1813,Oil on wood,6 3/8 x 8 5/8 in. (16.2 x 21.9 cm),"The Jack and Belle Linsky Collection, 1982",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437775,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -54.182,false,true,437900,European Paintings,Painting,"Comtesse de la Châtre (Marie Charlotte Louise Perrette Aglaé Bontemps, 1762–1848)",,,,,,Artist,,Élisabeth Louise Vigée Le Brun,"French, Paris 1755–1842 Paris",,"Vigée Le Brun, Élisabeth Louise",French,1755,1842,1789,1789,1789,Oil on canvas,45 x 34 1/2 in. (114.3 x 87.6 cm),"Gift of Jessie Woolworth Donahue, 1954",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437900,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -49.7.53,false,true,437899,European Paintings,Painting,Alexandre Charles Emmanuel de Crussol-Florensac (1743–1815),,,,,,Artist,,Élisabeth Louise Vigée Le Brun,"French, Paris 1755–1842 Paris",,"Vigée Le Brun, Élisabeth Louise",French,1755,1842,1787,1787,1787,Oil on wood,35 3/8 x 25 1/2 in. (89.9 x 64.8 cm),"The Jules Bache Collection, 1949",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437899,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -50.135.2,false,true,437898,European Paintings,Painting,"Madame Grand (Noël Catherine Vorlée, 1761–1835)",,,,,,Artist,,Élisabeth Louise Vigée Le Brun,"French, Paris 1755–1842 Paris",,"Vigée Le Brun, Élisabeth Louise",French,1755,1842,1783,1783,1783,Oil on canvas,"Oval, 36 1/4 x 28 1/2 in. (92.1 x 72.4 cm)","Bequest of Edward S. Harkness, 1940",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437898,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -25.110.8,false,true,437080,European Paintings,Painting,The Mill of Montmartre,,,,,,Artist,,Georges Michel,"French, Paris 1763–1843 Paris",,"Michel, Georges",French,1763,1843,probably ca. 1820,1783,1843,Oil on canvas,29 x 40 in. (73.7 x 101.6 cm),"Bequest of Collis P. Huntington, 1900",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437080,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -53.61.4,false,true,435650,European Paintings,Painting,"Madame Philippe Panon Desbassayns de Richemont (Jeanne Eglé Mourgue, 1778–1855) and Her Son, Eugène (1800–1859)",,,,,,Artist,,Marie Guillelmine Benoist,"French, Paris 1768–1826 Paris",,"Benoist, Marie Guillelmine",French,1768,1826,1802,1802,1802,Oil on canvas,46 x 35 1/4 in. (116.8 x 89.5 cm),"Gift of Julia A. Berwind, 1953",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435650,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -62.254,false,true,437887,European Paintings,Painting,Bertel Thorvaldsen (1768–1844) with the Bust of Horace Vernet,,,,,,Artist,,Horace Vernet,"French, Paris 1789–1863 Paris",,"Vernet, Horace",French,1789,1863,1833 or later,1833,1833,Oil on canvas,38 x 29 1/2 in. (96.5 x 74.9 cm),"Gift of Dr. Rudolf J. Heinemann, 1962",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437887,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1998.84,false,true,438033,European Paintings,Painting,Jean-Louis-André-Théodore Gericault (1791–1824),,,,,,Artist,,Horace Vernet,"French, Paris 1789–1863 Paris",,"Vernet, Horace",French,1789,1863,probably 1822 or 1823,1822,1823,Oil on canvas,18 5/8 x 15 1/8 in. (47.3 x 38.4 cm),"Purchase, Gift of Joanne Toor Cummings, by exchange, 1998",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/438033,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -87.15.47,false,true,437888,European Paintings,Painting,The Start of the Race of the Riderless Horses,,,,,,Artist,,Horace Vernet,"French, Paris 1789–1863 Paris",,"Vernet, Horace",French,1789,1863,1820,1820,1820,Oil on canvas,18 1/8 x 21 1/4 in. (46 x 54 cm),"Catharine Lorillard Wolfe Collection, Bequest of Catharine Lorillard Wolfe, 1887",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437888,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1994.376,false,true,437078,European Paintings,Painting,Waterfall at Mont-Dore,,,,,,Artist,,Achille-Etna Michallon,"French, Paris 1796–1822 Paris",,"Michallon, Achille-Etna",French,1796,1822,1818,1818,1818,Oil on canvas,16 1/4 x 22 1/8 in. (41.3 x 56.2 cm),"Purchase, Wolfe Fund and Nancy Richardson Gift, 1994",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437078,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -28.90,false,true,435991,European Paintings,Painting,A Woman Reading,,,,,,Artist,,Camille Corot,"French, Paris 1796–1875 Paris",,"Corot, Camille",French,1796,1875,1869 and 1870,1869,1870,Oil on canvas,21 3/8 x 14 3/4 in. (54.3 x 37.5 cm),"Gift of Louise Senff Cameron, in memory of her uncle, Charles H. Senff, 1928",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435991,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -30.13,false,true,435977,European Paintings,Painting,Mother and Child,,,,,,Artist,,Camille Corot,"French, Paris 1796–1875 Paris",,"Corot, Camille",French,1796,1875,probably 1860s,1860,1869,Oil on wood,12 3/4 x 8 7/8 in. (32.4 x 22.5 cm),"H. O. Havemeyer Collection, Gift of Mrs. P. H. B. Frelinghuysen, 1930",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435977,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -38.64,false,true,435962,European Paintings,Painting,Hagar in the Wilderness,,,,,,Artist,,Camille Corot,"French, Paris 1796–1875 Paris",,"Corot, Camille",French,1796,1875,1835,1835,1835,Oil on canvas,71 x 106 1/2 in. (180.3 x 270.5 cm),"Rogers Fund, 1938",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435962,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1974.3,false,true,435972,European Paintings,Painting,Honfleur: Calvary,,,,,,Artist,,Camille Corot,"French, Paris 1796–1875 Paris",,"Corot, Camille",French,1796,1875,ca. 1830,1825,1835,Oil on wood,11 3/4 x 16 1/8 in. (29.8 x 41 cm),"Purchase, Mr. and Mrs. Richard J. Bernhard Gift, by exchange, 1974",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435972,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -11.45.4,false,true,435983,European Paintings,Painting,River with a Distant Tower,,,,,,Artist,,Camille Corot,"French, Paris 1796–1875 Paris",,"Corot, Camille",French,1796,1875,1865,1865,1865,Oil on canvas,21 1/2 x 30 7/8 in. (54.6 x 78.4 cm),"Bequest of Robert Graham Dun, 1900",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435983,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1984.75,false,true,435985,European Paintings,Painting,"Study for ""The Destruction of Sodom""",,,,,,Artist,,Camille Corot,"French, Paris 1796–1875 Paris",,"Corot, Camille",French,1796,1875,1843,1843,1843,Oil on canvas,14 1/8 x 19 5/8 in. (35.9 x 49.8 cm),"Catharine Lorillard Wolfe Collection, Wolfe Fund, 1984",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435985,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -21.70.4,false,true,435989,European Paintings,Painting,The Banks of the Seine at Conflans,,,,,,Artist,,Camille Corot,"French, Paris 1796–1875 Paris",,"Corot, Camille",French,1796,1875,ca. 1865–70,1865,1870,Oil on canvas,18 1/4 x 21 7/8 in. (46.4 x 55.6 cm),"Bequest of Eloise Lawrence Breese Norrie, 1921",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435989,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.40.811,false,true,435969,European Paintings,Painting,The Ferryman,,,,,,Artist,,Camille Corot,"French, Paris 1796–1875 Paris",,"Corot, Camille",French,1796,1875,ca. 1865,1860,1870,Oil on canvas,26 1/8 x 19 3/8 in. (66.4 x 49.2 cm),"Bequest of Benjamin Altman, 1913",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435969,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.40.813,false,true,435979,European Paintings,Painting,A Pond in Picardy,,,,,,Artist,,Camille Corot,"French, Paris 1796–1875 Paris",,"Corot, Camille",French,1796,1875,ca. 1867,1862,1872,Oil on canvas,17 x 25 in. (43.2 x 63.5 cm),"Bequest of Benjamin Altman, 1913",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435979,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.40.817,false,true,435975,European Paintings,Painting,A Lane through the Trees,,,,,,Artist,,Camille Corot,"French, Paris 1796–1875 Paris",,"Corot, Camille",French,1796,1875,ca. 1870–73,1870,1873,Oil on canvas,24 x 18 in. (61 x 45.7 cm),"Bequest of Benjamin Altman, 1913",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435975,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -25.110.17,false,true,435987,European Paintings,Painting,A Village Street: Dardagny,,,,,,Artist,,Camille Corot,"French, Paris 1796–1875 Paris",,"Corot, Camille",French,1796,1875,"1852, 1857, or 1863",1852,1863,Oil on canvas,13 1/2 x 9 1/2 in. (34.3 x 24.1 cm),"Bequest of Collis P. Huntington, 1900",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435987,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.18,false,true,435967,European Paintings,Painting,"The Burning of Sodom (formerly ""The Destruction of Sodom"")",,,,,,Artist,,Camille Corot,"French, Paris 1796–1875 Paris",,"Corot, Camille",French,1796,1875,1843 and 1857,1843,1857,Oil on canvas,36 3/8 x 71 3/8 in. (92.4 x 181.3 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435967,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.19,false,true,435963,European Paintings,Painting,Bacchante by the Sea,,,,,,Artist,,Camille Corot,"French, Paris 1796–1875 Paris",,"Corot, Camille",French,1796,1875,1865,1865,1865,Oil on wood,15 1/4 x 23 3/8 in. (38.7 x 59.4 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435963,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.160.33,false,true,435976,European Paintings,Painting,The Letter,,,,,,Artist,,Camille Corot,"French, Paris 1796–1875 Paris",,"Corot, Camille",French,1796,1875,ca. 1865,1860,1870,Oil on wood,21 1/2 x 14 1/4 in. (54.6 x 36.2 cm),"H. O. Havemeyer Collection, Gift of Horace Havemeyer, 1929",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435976,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -30.95.272,false,true,435968,European Paintings,Painting,The Environs of Paris,,,,,,Artist,,Camille Corot,"French, Paris 1796–1875 Paris",,"Corot, Camille",French,1796,1875,1860s,1860,1869,Oil on wood,13 1/2 x 20 1/4 in. (34.3 x 51.4 cm),"Theodore M. Davis Collection, Bequest of Theodore M. Davis, 1915",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435968,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -87.15.141,false,true,435988,European Paintings,Painting,Ville-d'Avray,,,,,,Artist,,Camille Corot,"French, Paris 1796–1875 Paris",,"Corot, Camille",French,1796,1875,1870,1870,1870,Oil on canvas,21 5/8 x 31 1/2 in. (54.9 x 80 cm),"Catharine Lorillard Wolfe Collection, Bequest of Catharine Lorillard Wolfe, 1887",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435988,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -17.120.212,false,true,435964,European Paintings,Painting,The Gypsies,,,,,,Artist,,Camille Corot,"French, Paris 1796–1875 Paris",,"Corot, Camille",French,1796,1875,1872,1872,1872,Oil on canvas,21 3/4 x 31 1/2 in. (55.2 x 80 cm),"Mr. and Mrs. Isaac D. Fletcher Collection, Bequest of Isaac D. Fletcher, 1917",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435964,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -17.120.225,false,true,435990,European Paintings,Painting,A Woman Gathering Faggots at Ville-d'Avray,,,,,,Artist,,Camille Corot,"French, Paris 1796–1875 Paris",,"Corot, Camille",French,1796,1875,ca. 1871–74,1871,1874,Oil on canvas,28 3/8 x 22 1/2 in. (72.1 x 57.2 cm),"Mr. and Mrs. Isaac D. Fletcher Collection, Bequest of Isaac D. Fletcher, 1917",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435990,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1980.203.4,false,true,435986,European Paintings,Painting,View of Lormes,,,,,,Artist,,Camille Corot,"French, Paris 1796–1875 Paris",,"Corot, Camille",French,1796,1875,early 1840s,1840,1844,Oil on canvas,6 1/2 x 21 5/8 in. (16.5 x 54.9 cm),"Gift of Mr. and Mrs. Walter Mendelsohn, 1980",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435986,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1999.288.2,false,true,437991,European Paintings,Painting,The Curious Little Girl,,,,,,Artist,,Camille Corot,"French, Paris 1796–1875 Paris",,"Corot, Camille",French,1796,1875,1860–64,1816,1875,"Oil on cardboard, laid down on wood",16 1/4 x 11 1/4 in. (41.3 x 28.6 cm),"The Walter H. and Leonore Annenberg Collection, Gift of Walter H. and Leonore Annenberg, 1999, Bequest of Walter H. Annenberg, 2002",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437991,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2003.42.13,false,true,438635,European Paintings,Painting,Waterfall at Terni,,,,,,Artist,,Camille Corot,"French, Paris 1796–1875 Paris",,"Corot, Camille",French,1796,1875,1826,1826,1826,"Oil on paper, laid down on wood",10 1/2 x 12 1/8 in. (26.7 x 30.8 cm),"The Whitney Collection, Gift of Wheelock Whitney III, and Purchase, Gift of Mr. and Mrs. Charles S. McVeigh, by exchange, 2003",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/438635,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.193,false,true,435978,European Paintings,Painting,The Muse: History,,,,,,Artist,,Camille Corot,"French, Paris 1796–1875 Paris",,"Corot, Camille",French,1796,1875,ca. 1865,1860,1870,Oil on canvas,18 1/8 x 13 7/8 in. (46 x 35.2 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435978,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.562,false,true,435971,European Paintings,Painting,Girl Weaving a Garland,,,,,,Artist,,Camille Corot,"French, Paris 1796–1875 Paris",,"Corot, Camille",French,1796,1875,1860–65,1860,1865,Oil on canvas,16 1/2 x 11 3/4 in. (41.9 x 29.8 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435971,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.563,false,true,435981,European Paintings,Painting,Reverie,,,,,,Artist,,Camille Corot,"French, Paris 1796–1875 Paris",,"Corot, Camille",French,1796,1875,ca. 1860–65,1855,1870,Oil on wood,19 5/8 x 14 3/8 in. (49.8 x 36.5 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435981,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.564,false,true,435980,European Paintings,Painting,Portrait of a Child,,,,,,Artist,,Camille Corot,"French, Paris 1796–1875 Paris",,"Corot, Camille",French,1796,1875,ca. 1835,1830,1840,Oil on wood,12 5/8 x 9 1/4 in. (32.1 x 23.5 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435980,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.565,true,true,435984,European Paintings,Painting,Sibylle,,,,,,Artist,,Camille Corot,"French, Paris 1796–1875 Paris",,"Corot, Camille",French,1796,1875,ca. 1870,1865,1875,Oil on canvas,32 1/4 x 25 1/2 in. (81.9 x 64.8 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435984,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.598,false,true,435965,European Paintings,Painting,Bacchante in a Landscape,,,,,,Artist,,Camille Corot,"French, Paris 1796–1875 Paris",,"Corot, Camille",French,1796,1875,1865–70,1865,1870,Oil on canvas,12 1/8 x 24 1/4 in. (30.8 x 61.5 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435965,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -32.100.136,false,true,435966,European Paintings,Painting,Boatman among the Reeds,,,,,,Artist,,Camille Corot,"French, Paris 1796–1875 Paris",,"Corot, Camille",French,1796,1875,ca. 1865,1860,1870,Oil on canvas,23 1/2 x 32 in. (59.7 x 81.3 cm),"The Friedsam Collection, Bequest of Michael Friedsam, 1931",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435966,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.201.13,false,true,435961,European Paintings,Painting,Toussaint Lemaistre (1807/8–1888),,,,,,Artist,,Camille Corot,"French, Paris 1796–1875 Paris",,"Corot, Camille",French,1796,1875,1833,1833,1833,Oil on canvas,15 1/8 x 11 5/8 in. (38.4 x 29.5 cm),"Bequest of Joan Whitney Payson, 1975",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435961,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.164.5,false,true,438950,European Paintings,Painting,A Storm off the Normandy Coast,,,,,,Artist,,Eugène Isabey,"French, Paris 1803–1886 Lagny",,"Isabey, Eugène",French,1803,1886,possibly ca. 1850,1840,1860,"Oil on paper, laid down on canvas",13 x 20 in. (33 x 50.8 cm),"Gift of The Eugene Victor Thaw Art Foundation, 2007",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/438950,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -97.40,false,true,436772,European Paintings,Painting,The Sheepfold,,,,,,Artist,,Charles Jacque,"French, Paris 1813–1894 Paris",,"Jacque, Charles",French,1813,1894,1857,1857,1857,Oil on wood,18 1/8 x 36 1/8 in. (46 x 91.8 cm),"Catharine Lorillard Wolfe Collection, Wolfe Fund, 1897",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436772,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -87.15.106,false,true,436418,European Paintings,Painting,Jerusalem from the Mount of Olives,,,,,,Artist,,Charles-Théodore Frère,"French, Paris 1814–1888 Paris",,"Frère, Charles-Théodore",French,1814,1888,by 1880,1870,1880,Oil on canvas,29 1/2 x 43 1/2 in. (74.9 x 110.5 cm),"Catharine Lorillard Wolfe Collection, Bequest of Catharine Lorillard Wolfe, 1887",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436418,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -03.29,false,true,436084,European Paintings,Painting,Boats on the Seacoast at Étaples,,,,,,Artist,,Charles-François Daubigny,"French, Paris 1817–1878 Paris",,"Daubigny, Charles-François",French,1817,1878,1871,1871,1871,Oil on wood,13 1/2 x 22 7/8 in. (34.3 x 58.1 cm),"Catharine Lorillard Wolfe Collection, Wolfe Fund, 1903",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436084,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -11.45.3,false,true,436089,European Paintings,Painting,The Hamlet of Optevoz,,,,,,Artist,,Charles-François Daubigny,"French, Paris 1817–1878 Paris",,"Daubigny, Charles-François",French,1817,1878,ca. 1852,1849,1857,Oil on canvas,22 3/4 x 36 1/2 in. (57.8 x 92.7 cm),"Bequest of Robert Graham Dun, 1900",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436089,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -08.136.4,false,true,436090,European Paintings,Painting,Landscape with a Sunlit Stream,,,,,,Artist,,Charles-François Daubigny,"French, Paris 1817–1878 Paris",,"Daubigny, Charles-François",French,1817,1878,ca. 1877,1877,1877,Oil on canvas,25 1/8 x 18 7/8 in. (63.8 x 47.9 cm),"Bequest of Martha T. Fiske Collord, in memory of her first husband, Josiah M. Fiske, 1908",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436090,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -25.110.3,false,true,436085,European Paintings,Painting,Apple Blossoms,,,,,,Artist,,Charles-François Daubigny,"French, Paris 1817–1878 Paris",,"Daubigny, Charles-François",French,1817,1878,1873,1873,1873,Oil on canvas,23 1/8 x 33 3/8 in. (58.7 x 84.8 cm),"Bequest of Collis P. Huntington, 1900",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436085,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -64.149.7,false,true,436081,European Paintings,Painting,Landscape on a River,,,,,,Artist,,Charles-François Daubigny,"French, Paris 1817–1878 Paris",,"Daubigny, Charles-François",French,1817,1878,1863,1863,1863,Oil on wood,8 1/4 x 15 in. (21 x 38.1 cm),"Gift of Mary V. T. Eberstadt, 1964",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436081,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.40.815,false,true,436080,European Paintings,Painting,The Banks of the Oise,,,,,,Artist,,Charles-François Daubigny,"French, Paris 1817–1878 Paris",,"Daubigny, Charles-François",French,1817,1878,1863,1863,1863,Oil on wood,14 3/4 x 26 3/8 in. (37.5 x 67 cm),"Bequest of Benjamin Altman, 1913",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436080,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.40.818,false,true,436083,European Paintings,Painting,A River Landscape with Storks,,,,,,Artist,,Charles-François Daubigny,"French, Paris 1817–1878 Paris",,"Daubigny, Charles-François",French,1817,1878,1864,1864,1864,Oil on wood,9 1/2 x 17 5/8 in. (24.1 x 44.8 cm),"Bequest of Benjamin Altman, 1913",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436083,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -30.95.275,false,true,436088,European Paintings,Painting,Portejoie on the Seine,,,,,,Artist,,Charles-François Daubigny,"French, Paris 1817–1878 Paris",,"Daubigny, Charles-François",French,1817,1878,1850–1878,1850,1878,Oil on wood,9 5/8 x 17 3/8 in. (24.4 x 44.1 cm),"Theodore M. Davis Collection, Bequest of Theodore M. Davis, 1915",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436088,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -87.15.120,false,true,436086,European Paintings,Painting,The Seine: Morning,,,,,,Artist,,Charles-François Daubigny,"French, Paris 1817–1878 Paris",,"Daubigny, Charles-François",French,1817,1878,1874,1874,1874,Oil on wood,15 1/4 x 27 1/4 in. (38.7 x 69.2 cm),"Catharine Lorillard Wolfe Collection, Bequest of Catharine Lorillard Wolfe, 1887",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436086,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -17.138.2,false,true,436970,European Paintings,Painting,Penelope,,,,,,Artist,,Charles-François Marchal,"French, Paris 1825–1877 Paris",,"Marchal, Charles-François",French,1825,1877,ca. 1868,1845,1877,Oil on canvas,43 1/2 x 19 1/2 in. (110.5 x 49.5 cm),"Gift of Mrs. Adolf Obrig, in memory of her husband, 1917",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436970,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -21.134.1,false,true,437153,European Paintings,Painting,Oedipus and the Sphinx,,,,,,Artist,,Gustave Moreau,"French, Paris 1826–1898 Paris",,"Moreau, Gustave",French,1826,1898,1864,1864,1864,Oil on canvas,81 1/4 x 41 1/4 in. (206.4 x 104.8 cm),"Bequest of William H. Herriman, 1920",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437153,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -87.15.119,false,true,436184,European Paintings,Painting,Objects of Art from the Louvre,,,,,,Artist,,Blaise-Alexandre Desgoffe,"French, Paris 1830–1901 Paris",,"Desgoffe, Blaise-Alexandre",French,1830,1901,1874,1874,1874,Oil on canvas,28 3/4 x 36 1/4 in. (73 x 92.1 cm),"Catharine Lorillard Wolfe Collection, Bequest of Catharine Lorillard Wolfe, 1887",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436184,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -10.36,false,true,436952,European Paintings,Painting,The Funeral,,,,,,Artist,,Édouard Manet,"French, Paris 1832–1883 Paris",,"Manet, Édouard",French,1832,1883,ca. 1867,1867,1867,Oil on canvas,28 5/8 x 35 5/8 in. (72.7 x 90.5 cm),"Catharine Lorillard Wolfe Collection, Wolfe Fund, 1909",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436952,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -57.10,false,true,436951,European Paintings,Painting,Fishing,,,,,,Artist,,Édouard Manet,"French, Paris 1832–1883 Paris",,"Manet, Édouard",French,1832,1883,ca. 1862–63,1862,1863,Oil on canvas,30 1/4 x 48 1/2 in. (76.8 x 123.2 cm),"Purchase, Mr. and Mrs. Richard J. Bernhard Gift, 1957",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436951,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -55.193,false,true,436954,European Paintings,Painting,George Moore (1852–1933) at the Café,,,,,,Artist,,Édouard Manet,"French, Paris 1832–1883 Paris",,"Manet, Édouard",French,1832,1883,1878 or 1879,1878,1879,Oil on canvas,25 3/4 x 32 in. (65.4 x 81.3 cm),"Gift of Mrs. Ralph J. Hines, 1955",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436954,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -59.129,false,true,436955,European Paintings,Painting,Head of Jean-Baptiste Faure (1830–1914),,,,,,Artist,,Édouard Manet,"French, Paris 1832–1883 Paris",,"Manet, Édouard",French,1832,1883,1882–83,1882,1883,Oil on canvas,18 1/8 x 14 7/8 in. (46 x 37.8 cm),"Gift of Mrs. Ralph J. Hines, 1959",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436955,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -49.58.2,false,true,436944,European Paintings,Painting,The Spanish Singer,,,,,,Artist,,Édouard Manet,"French, Paris 1832–1883 Paris",,"Manet, Édouard",French,1832,1883,1860,1860,1860,Oil on canvas,58 x 45 in. (147.3 x 114.3 cm),"Gift of William Church Osborn, 1949",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436944,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -50.71.1,false,true,436956,European Paintings,Painting,Jean-Baptiste Faure (1830–1914),,,,,,Artist,,Édouard Manet,"French, Paris 1832–1883 Paris",,"Manet, Édouard",French,1832,1883,1882–83,1882,1883,Oil on canvas,23 1/4 x 19 1/2 in. (59.1 x 49.5 cm),"Gift of Mr. and Mrs. William B. Jaffe, 1950",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436956,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -89.21.2,false,true,436948,European Paintings,Painting,Boy with a Sword,,,,,,Artist,,Édouard Manet,"French, Paris 1832–1883 Paris",,"Manet, Édouard",French,1832,1883,1861,1861,1861,Oil on canvas,51 5/8 x 36 3/4 in. (131.1 x 93.4 cm),"Gift of Erwin Davis, 1889",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436948,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -89.21.3,false,true,436964,European Paintings,Painting,Young Lady in 1866,,,,,,Artist,,Édouard Manet,"French, Paris 1832–1883 Paris",,"Manet, Édouard",French,1832,1883,1866,1866,1866,Oil on canvas,72 7/8 x 50 5/8 in. (185.1 x 128.6 cm),"Gift of Erwin Davis, 1889",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436964,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1999.442,false,true,438144,European Paintings,Painting,"The ""Kearsarge"" at Boulogne",,,,,,Artist,,Édouard Manet,"French, Paris 1832–1883 Paris",,"Manet, Édouard",French,1832,1883,1864,1864,1864,Oil on canvas,32 1/8 x 39 3/8 in. (81.6 x 100 cm),"Gift of Peter H. B. Frelinghuysen, and Purchase, Mr. and Mrs. Richard J. Bernhard Gift, by exchange, Gifts of Mr. and Mrs. Richard Rodgers and Joanne Toor Cummings, by exchange, and Drue Heinz Trust, The Dillon Fund, The Vincent Astor Foundation, Mr. and Mrs. Henry R. Kravis, The Charles Engelhard Foundation, and Florence and Herbert Irving Gifts, 1999",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/438144,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -56.230.1,false,true,436963,European Paintings,Painting,Strawberries,,,,,,Artist,,Édouard Manet,"French, Paris 1832–1883 Paris",,"Manet, Édouard",French,1832,1883,ca. 1882,1882,1882,Oil on canvas,8 3/8 x 10 1/2 in. (21.3 x 26.7 cm),"Gift of Mr. and Mrs. Nate B. Spingold, 1956",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436963,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.51,false,true,436950,European Paintings,Painting,The Dead Christ with Angels,,,,,,Artist,,Édouard Manet,"French, Paris 1832–1883 Paris",,"Manet, Édouard",French,1832,1883,1864,1864,1864,Oil on canvas,70 5/8 x 59 in. (179.4 x 149.9 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436950,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.52,false,true,436960,European Paintings,Painting,A Matador,,,,,,Artist,,Édouard Manet,"French, Paris 1832–1883 Paris",,"Manet, Édouard",French,1832,1883,1866–67,1866,1867,Oil on canvas,67 3/8 x 44 1/2 in. (171.1 x 113 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436960,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.53,true,true,436945,European Paintings,Painting,Mademoiselle V. . . in the Costume of an Espada,,,,,,Artist,,Édouard Manet,"French, Paris 1832–1883 Paris",,"Manet, Édouard",French,1832,1883,1862,1862,1862,Oil on canvas,65 x 50 1/4 in. (165.1 x 127.6 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436945,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.54,false,true,438819,European Paintings,Painting,Young Man in the Costume of a Majo,,,,,,Artist,,Édouard Manet,"French, Paris 1832–1883 Paris",,"Manet, Édouard",French,1832,1883,1863,1863,1863,Oil on canvas,74 x 49 1/8 in. (188 x 124.8 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/438819,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -67.187.81,false,true,436957,European Paintings,Painting,"Madame Édouard Manet (Suzanne Leenhoff, 1830–1906)",,,,,,Artist,,Édouard Manet,"French, Paris 1832–1883 Paris",,"Manet, Édouard",French,1832,1883,ca. 1873,1870,1876,Oil on canvas,39 1/2 x 30 7/8 in. (100.3 x 78.4 cm),"Bequest of Miss Adelaide Milton de Groot (1876–1967), 1967",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436957,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1997.391.4,false,true,438002,European Paintings,Painting,"Madame Manet (Suzanne Leenhoff, 1830–1906) at Bellevue",,,,,,Artist,,Édouard Manet,"French, Paris 1832–1883 Paris",,"Manet, Édouard",French,1832,1883,1880,1880,1880,Oil on canvas,31 3/4 x 23 3/4 in. (80.6 x 60.3 cm),"The Walter H. and Leonore Annenberg Collection, Gift of Walter H. and Leonore Annenberg, 1997, Bequest of Walter H. Annenberg, 2002",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/438002,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.114,false,true,436949,European Paintings,Painting,"Copy after Delacroix's ""Bark of Dante""",,,,,,Artist,,Édouard Manet,"French, Paris 1832–1883 Paris",,"Manet, Édouard",French,1832,1883,ca. 1859,1854,1864,Oil on canvas,13 x 16 1/8 in. (33 x 41 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436949,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.115,true,true,436947,European Paintings,Painting,Boating,,,,,,Artist,,Édouard Manet,"French, Paris 1832–1883 Paris",,"Manet, Édouard",French,1832,1883,1874,1874,1874,Oil on canvas,38 1/4 x 51 1/4 in. (97.2 x 130.2 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436947,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.201.14,false,true,436965,European Paintings,Painting,The Monet Family in Their Garden at Argenteuil,,,,,,Artist,,Édouard Manet,"French, Paris 1832–1883 Paris",,"Manet, Édouard",French,1832,1883,1874,1874,1874,Oil on canvas,24 x 39 1/4 in. (61 x 99.7 cm),"Bequest of Joan Whitney Payson, 1975",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436965,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.201.16,false,true,436961,European Paintings,Painting,Peonies,,,,,,Artist,,Édouard Manet,"French, Paris 1832–1883 Paris",,"Manet, Édouard",French,1832,1883,1864–65,1864,1865,Oil on canvas,23 3/8 x 13 7/8 in. (59.4 x 35.2 cm),"Bequest of Joan Whitney Payson, 1975",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436961,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -39.161,false,true,436144,European Paintings,Painting,James-Jacques-Joseph Tissot (1836–1902),,,,,,Artist,,Edgar Degas,"French, Paris 1834–1917 Paris",,"Degas, Edgar",French,1834,1917,ca. 1867–68,1867,1868,Oil on canvas,59 5/8 x 44 in. (151.4 x 111.8 cm),"Rogers Fund, 1939",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436144,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -57.171,false,true,436152,European Paintings,Painting,Portrait of a Woman in Gray,,,,,,Artist,,Edgar Degas,"French, Paris 1834–1917 Paris",,"Degas, Edgar",French,1834,1917,ca. 1865,1860,1870,Oil on canvas,36 x 28 1/2 in. (91.4 x 72.4 cm),"Gift of Mr. and Mrs. Edwin C. Vogel, 1957",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436152,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -66.65.2,false,true,436120,European Paintings,Painting,The Old Italian Woman,,,,,,Artist,,Edgar Degas,"French, Paris 1834–1917 Paris",,"Degas, Edgar",French,1834,1917,1857,1857,1857,Oil on canvas,29 1/2 x 24 in. (74.9 x 61 cm),"Bequest of Charles Goldman, 1966",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436120,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1992.380,false,true,436168,European Paintings,Painting,Two Men,,,,,,Artist,,Edgar Degas,"French, Paris 1834–1917 Paris",,"Degas, Edgar",French,1834,1917,ca. 1865–69,1865,1869,Oil on wood,10 5/8 x 8 1/8 in. (27 x 20.6 cm),"Gift of Yvonne Lamon, 1992",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436168,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.529,false,true,438857,European Paintings,Painting,Male Nude,,,,,,Artist,,Edgar Degas,"French, Paris 1834–1917 Paris",,"Degas, Edgar",French,1834,1917,1856,1856,1856,Oil on canvas,13 3/4 x 24 1/4 in. (34.9 x 61.6 cm),"Gift of Philip and Catherine Korsant, 2007",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/438857,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2008.277,false,true,438156,European Paintings,Painting,Young Woman with Ibis,,,,,,Artist,,Edgar Degas,"French, Paris 1834–1917 Paris",,"Degas, Edgar",French,1834,1917,1860–62,1860,1862,Oil on canvas,39 3/8 x 29 1/2 in. (100 x 74.9 cm),"Gift of Stephen Mazoh and Purchase, Bequest of Gioconda King, by exchange, 2008",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/438156,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1987.47.1,true,true,438817,European Paintings,Painting,The Dance Class,,,,,,Artist,,Edgar Degas,"French, Paris 1834–1917 Paris",,"Degas, Edgar",French,1834,1917,1874,1874,1874,Oil on canvas,32 7/8 x 30 3/8 in. (83.5 x 77.2 cm),"Bequest of Mrs. Harry Payne Bingham, 1986",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/438817,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.34,false,true,436139,European Paintings,Painting,Dancers Practicing at the Barre,,,,,,Artist,,Edgar Degas,"French, Paris 1834–1917 Paris",,"Degas, Edgar",French,1834,1917,1877,1877,1877,Mixed media on canvas,29 3/4 x 32 in. (75.6 x 81.3 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436139,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.42,false,true,436140,European Paintings,Painting,"Dancers, Pink and Green",,,,,,Artist,,Edgar Degas,"French, Paris 1834–1917 Paris",,"Degas, Edgar",French,1834,1917,ca. 1890,1885,1895,Oil on canvas,32 3/8 x 29 3/4 in. (82.2 x 75.6 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436140,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.43,false,true,436162,European Paintings,Painting,Sulking,,,,,,Artist,,Edgar Degas,"French, Paris 1834–1917 Paris",,"Degas, Edgar",French,1834,1917,ca. 1870,1870,1870,Oil on canvas,12 3/4 x 18 1/4 in. (32.4 x 46.4 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436162,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.44,false,true,436122,European Paintings,Painting,The Collector of Prints,,,,,,Artist,,Edgar Degas,"French, Paris 1834–1917 Paris",,"Degas, Edgar",French,1834,1917,1866,1866,1866,Oil on canvas,20 7/8 x 15 3/4 in. (53 x 40 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436122,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.45,false,true,436149,European Paintings,Painting,"Madame Théodore Gobillard (Yves Morisot, 1838–1893)",,,,,,Artist,,Edgar Degas,"French, Paris 1834–1917 Paris",,"Degas, Edgar",French,1834,1917,1869,1869,1869,Oil on canvas,21 3/4 x 25 5/8 in. (55.2 x 65.1 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436149,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.46,false,true,436174,European Paintings,Painting,A Woman Ironing,,,,,,Artist,,Edgar Degas,"French, Paris 1834–1917 Paris",,"Degas, Edgar",French,1834,1917,1873,1873,1873,Oil on canvas,21 3/8 x 15 1/2 in. (54.3 x 39.4 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436174,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.127,false,true,436138,European Paintings,Painting,Dancers in the Rehearsal Room with a Double Bass,,,,,,Artist,,Edgar Degas,"French, Paris 1834–1917 Paris",,"Degas, Edgar",French,1834,1917,ca. 1882–85,1882,1885,Oil on canvas,15 3/8 x 35 1/4 in. (39.1 x 89.5 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436138,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.128,false,true,436121,European Paintings,Painting,A Woman Seated beside a Vase of Flowers (Madame Paul Valpinçon?),,,,,,Artist,,Edgar Degas,"French, Paris 1834–1917 Paris",,"Degas, Edgar",French,1834,1917,1865,1865,1865,Oil on canvas,29 x 36 1/2 in. (73.7 x 92.7 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436121,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.181,false,true,436145,European Paintings,Painting,Joseph-Henri Altès (1826–1895),,,,,,Artist,,Edgar Degas,"French, Paris 1834–1917 Paris",,"Degas, Edgar",French,1834,1917,1868,1868,1868,Oil on canvas,9 7/8 x 7 7/8 in. (25.1 x 20 cm); with added strips 10 5/8 x 8 1/2 in. (27 x 21.6 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436145,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.182,false,true,436150,European Paintings,Painting,Mademoiselle Marie Dihau (1843–1935),,,,,,Artist,,Edgar Degas,"French, Paris 1834–1917 Paris",,"Degas, Edgar",French,1834,1917,1867–68,1867,1868,Oil on canvas,8 3/4 x 10 3/4 in. (22.2 x 27.3 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436150,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.183,false,true,436153,European Paintings,Painting,Portrait of a Young Woman,,,,,,Artist,,Edgar Degas,"French, Paris 1834–1917 Paris",,"Degas, Edgar",French,1834,1917,ca. 1885,1880,1890,Oil on canvas,10 3/4 x 8 3/4 in. (27.3 x 22.2 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436153,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.184,false,true,436141,European Paintings,Painting,The Dancing Class,,,,,,Artist,,Edgar Degas,"French, Paris 1834–1917 Paris",,"Degas, Edgar",French,1834,1917,ca. 1870,1870,1870,Oil on wood,7 3/4 x 10 5/8 in. (19.7 x 27 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436141,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.552,false,true,436123,European Paintings,Painting,"The Ballet from ""Robert le Diable""",,,,,,Artist,,Edgar Degas,"French, Paris 1834–1917 Paris",,"Degas, Edgar",French,1834,1917,1871,1871,1871,Oil on canvas,26 x 21 3/8 in. (66 x 54.3 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436123,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -87.15.101,false,true,437895,European Paintings,Painting,The Reprimand,,,,,,Artist,,Jean-Georges Vibert,"French, Paris 1840–1902 Paris",,"Vibert, Jean-Georges",French,1840,1902,1874,1874,1874,Oil on canvas,20 3/8 x 33 in. (51.8 x 83.8 cm),"Catharine Lorillard Wolfe Collection, Bequest of Catharine Lorillard Wolfe, 1887",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437895,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -25.110.140,false,true,437896,European Paintings,Painting,The Missionary's Adventures,,,,,,Artist,,Jean-Georges Vibert,"French, Paris 1840–1902 Paris",,"Vibert, Jean-Georges",French,1840,1902,ca. 1883,1878,1888,Oil on wood,39 x 53 in. (99.1 x 134.6 cm),"Bequest of Collis P. Huntington, 1900",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437896,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -87.15.74,false,true,436519,European Paintings,Painting,Before the Mirror,,,,,,Artist,,Pierre-Paul-Léon Glaize,"French, Paris 1842–1932 Paris",,"Glaize, Pierre-Paul-Léon",French,1842,1932,1873,1873,1873,Oil on canvas,39 3/4 x 29 7/8 in. (101 x 75.9 cm),"Catharine Lorillard Wolfe Collection, Bequest of Catharine Lorillard Wolfe, 1887",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436519,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -87.15.90,false,true,436870,European Paintings,Painting,Choosing the Dinner,,,,,,Artist,,Alexandre-Louis Leloir,"French, Paris 1843–1884 Paris",,"Leloir, Alexandre-Louis",French,1843,1884,1872,1872,1872,Oil on canvas,12 1/4 x 18 3/8 in. (31.1 x 46.7 cm),"Catharine Lorillard Wolfe Collection, Bequest of Catharine Lorillard Wolfe, 1887",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436870,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -59.185,false,true,435649,European Paintings,Painting,Judith,,,,,,Artist,,Benjamin-Constant (Jean-Joseph-Benjamin Constant),"French, Paris 1845–1902 Paris",,Benjamin-Constant (Jean-Joseph-Benjamin Constant),French,1845,1902,possibly ca. 1886,1883,1889,Oil on canvas,47 1/2 x 31 1/2 in. (120.7 x 80 cm),"Gift of J. E. Gombos, 1959",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435649,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -48.115,false,true,436189,European Paintings,Painting,Gendarmes d'Ordonnance,,,,,,Artist,,Édouard Detaille,"French, Paris 1848–1912 Paris",,"Detaille, Édouard",French,1848,1912,1894,1894,1894,Oil on canvas,22 x 16 5/8 in. (55.9 x 42.2 cm),"Gift of Estate of George Albert Draper, 1948",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436189,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -87.20.2,false,true,436188,European Paintings,Painting,The Defense of Champigny,,,,,,Artist,,Édouard Detaille,"French, Paris 1848–1912 Paris",,"Detaille, Édouard",French,1848,1912,1879,1879,1879,Oil on canvas,48 x 84 3/4 in. (121.9 x 215.3 cm),"Gift of Henry Hilton, 1887",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436188,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -08.136.5,false,true,436187,European Paintings,Painting,A Dragoon on Horseback,,,,,,Artist,,Édouard Detaille,"French, Paris 1848–1912 Paris",,"Detaille, Édouard",French,1848,1912,1876,1876,1876,Oil on wood,9 1/2 x 5 3/8 in. (24.1 x 13.7 cm),"Bequest of Martha T. Fiske Collord, in memory of her first husband, Josiah M. Fiske, 1908",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436187,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -87.8.12,false,true,436880,European Paintings,Painting,The Organ Rehearsal,,,,,,Artist,,Henry Lerolle,"French, Paris 1848–1929 Paris",,"Lerolle, Henry",French,1848,1929,1885,1885,1885,Oil on canvas,93 1/4 x 142 3/4 in. (236.9 x 362.6 cm),"Gift of George I. Seney, 1887",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436880,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -17.120.228,false,true,437366,European Paintings,Painting,"The Fletcher Mansion, New York City",,,,,,Artist,,Jean-François Raffaëlli,"French, Paris 1850–1924 Paris",,"Raffaëlli, Jean-François",French,1850,1924,1899,1899,1899,Oil on canvas,23 3/4 x 32 in. (60.3 x 81.3 cm),"Mr. and Mrs. Isaac D. Fletcher Collection, Bequest of Isaac D. Fletcher, 1917",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437366,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -67.187.80,false,true,436923,European Paintings,Painting,"Morning, Interior",,,,,,Artist,,Maximilien Luce,"French, Paris 1858–1941 Paris",,"Luce, Maximilien",French,1858,1941,1890,1890,1890,Oil on canvas,25 1/2 x 31 7/8 in. (64.8 x 81 cm),"Bequest of Miss Adelaide Milton de Groot (1876–1967), 1967",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436923,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -59.16.5,false,true,437659,European Paintings,Painting,View of the Seine,,,,,,Artist,,Georges Seurat,"French, Paris 1859–1891 Paris",,"Seurat, Georges",French,1859,1891,1882–83,1882,1883,Oil on wood,6 1/4 x 9 3/4 in. (15.9 x 24.8 cm),"Bequest of Mabel Choate, in memory of her father, Joseph Hodges Choate, 1958",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437659,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1980.342,false,true,437657,European Paintings,Painting,Landscape at Saint-Ouen,,,,,,Artist,,Georges Seurat,"French, Paris 1859–1891 Paris",,"Seurat, Georges",French,1859,1891,1878 or 1879,1878,1879,"Oil on wood, mounted on wood",Overall 6 7/8 x 10 3/8 in. (17.5 x 26.4 cm); painted surface 6 5/8 x 10 in. (16.8 x 25.4 cm),"Gift of Bernice Richard, 1980",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437657,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1985.237,false,true,437655,European Paintings,Painting,The Forest at Pontaubert,,,,,,Artist,,Georges Seurat,"French, Paris 1859–1891 Paris",,"Seurat, Georges",French,1859,1891,1881,1881,1881,Oil on canvas,31 1/8 x 24 5/8 in. (79.1 x 62.5 cm),"Purchase, Gift of Raymonde Paul, in memory of her brother, C. Michael Paul, by exchange, 1985",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437655,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -51.112.6,false,true,437658,European Paintings,Painting,"Study for ""A Sunday on La Grande Jatte""",,,,,,Artist,,Georges Seurat,"French, Paris 1859–1891 Paris",,"Seurat, Georges",French,1859,1891,1884,1884,1884,Oil on canvas,27 3/4 x 41 in. (70.5 x 104.1 cm),"Bequest of Sam A. Lewisohn, 1951",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437658,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2002.62.3,false,true,438015,European Paintings,Painting,"Gray Weather, Grande Jatte",,,,,,Artist,,Georges Seurat,"French, Paris 1859–1891 Paris",,"Seurat, Georges",French,1859,1891,ca. 1886–88,1886,1888,Oil on canvas,27 3/4 x 34 in. (70.5 x 86.4 cm),"The Walter H. and Leonore Annenberg Collection, Gift of Walter H. and Leonore Annenberg, 2002, Bequest of Walter H. Annenberg, 2002",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/438015,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -61.101.17,true,true,437654,European Paintings,Painting,Circus Sideshow (Parade de cirque),,,,,,Artist,,Georges Seurat,"French, Paris 1859–1891 Paris",,"Seurat, Georges",French,1859,1891,1887–88,1887,1888,Oil on canvas,39 1/4 x 59 in. (99.7 x 149.9 cm),"Bequest of Stephen C. Clark, 1960",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437654,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -67.187.102,false,true,437656,European Paintings,Painting,The Gardener,,,,,,Artist,,Georges Seurat,"French, Paris 1859–1891 Paris",,"Seurat, Georges",French,1859,1891,1882–83,1882,1883,Oil on wood,6 1/4 x 9 3/4 in. (15.9 x 24.8 cm),"Bequest of Miss Adelaide Milton de Groot (1876–1967), 1967",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437656,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -55.220.1,false,true,437672,European Paintings,Painting,"Notre-Dame-de-la-Garde (La Bonne-Mère), Marseilles",,,,,,Artist,,Paul Signac,"French, Paris 1863–1935 Paris",,"Signac, Paul",French,1863,1935,1905–6,1905,1906,Oil on canvas,35 x 45 3/4 in. (88.9 x 116.2 cm),"Gift of Robert Lehman, 1955",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437672,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.201.19,false,true,437671,European Paintings,Painting,"The Jetty at Cassis, Opus 198",,,,,,Artist,,Paul Signac,"French, Paris 1863–1935 Paris",,"Signac, Paul",French,1863,1935,1889,1889,1889,Oil on canvas,18 1/4 x 25 5/8 in. (46.4 x 65.1 cm),"Bequest of Joan Whitney Payson, 1975",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437671,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -41.17,false,true,436454,European Paintings,Painting,Alfred Dedreux (1810–1860) as a Child,,,,,,Artist,,Théodore Gericault,"French, Rouen 1791–1824 Paris",,"Gericault, Théodore",French,1791,1824,ca. 1819–20,1819,1820,Oil on canvas,18 x 15 in. (45.7 x 38.1 cm),"The Alfred N. Punnett Endowment Fund, 1941",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436454,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2011.5,false,true,441226,European Paintings,Painting,Lions in a Mountainous Landscape,,,,,,Artist,,Théodore Gericault,"French, Rouen 1791–1824 Paris",,"Gericault, Théodore",French,1791,1824,ca. 1818–20,1813,1825,Oil on wood,19 x 23 1/2 in. (48.3 x 59.7 cm),"Purchase, Nineteenth-Century, Modern, and Contemporary Funds and Lila Acheson Wallace Gift, 2011",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/441226,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.183,true,true,436455,European Paintings,Painting,Evening: Landscape with an Aqueduct,,,,,,Artist,,Théodore Gericault,"French, Rouen 1791–1824 Paris",,"Gericault, Théodore",French,1791,1824,1818,1818,1818,Oil on canvas,98 1/2 x 86 1/2 in. (250.2 x 219.7 cm),"Purchase, Gift of James A. Moffett 2nd, in memory of George M. Moffett, by exchange, 1989",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436455,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1974.356.27,false,true,437844,European Paintings,Painting,Comedy,,,,,,Artist,,Pierre Charles Trémolières,"French, Cholet 1703–1739 Paris",,"Trémolières, Pierre Charles",French,1703,1739,ca. 1736,1731,1741,"Oil on canvas, enlarged",18 3/4 x 23 1/2 in. (47.6 x 59.7 cm),"The Lesley and Emma Sheafer Collection, Bequest of Emma A. Sheafer, 1973",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437844,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -46.30,false,true,436321,European Paintings,Painting,Roman Interior,,,,,,Artist,,Jean Honoré Fragonard,"French, Grasse 1732–1806 Paris",,"Fragonard, Jean Honoré",French,1732,1806,ca. 1760,1755,1765,Oil on canvas,19 1/4 x 23 3/8 in. (48.9 x 59.4 cm),"Harris Brisbane Dick Fund, 1946",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436321,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.118,false,true,436323,European Paintings,Painting,A Woman with a Dog,,,,,,Artist,,Jean Honoré Fragonard,"French, Grasse 1732–1806 Paris",,"Fragonard, Jean Honoré",French,1732,1806,ca. 1769,1764,1774,Oil on canvas,32 x 25 3/4 in. (81.3 x 65.4 cm),"Fletcher Fund, 1937",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436323,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -53.161,false,true,436327,European Paintings,Painting,Allegory of Vigilance,,,,,,Artist,,Jean Honoré Fragonard,"French, Grasse 1732–1806 Paris",,"Fragonard, Jean Honoré",French,1732,1806,ca. 1772,1767,1777,Oil on canvas,"Oval, 27 1/8 x 21 5/8 in. (68.9 x 54.9 cm)","Gift of René Fribourg, 1953",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436327,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -65.226,false,true,436320,European Paintings,Painting,Portrait of a Young Woman,,,,,,Artist,,Jean Honoré Fragonard,"French, Grasse 1732–1806 Paris",,"Fragonard, Jean Honoré",French,1732,1806,1770s,1770,1779,Oil on canvas,"Oval, 31 3/4 x 25 in. (80.6 x 63.5 cm)","Bequest of Margaret V. Haggin, 1965",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436320,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -49.7.49,true,true,436322,European Paintings,Painting,The Love Letter,,,,,,Artist,,Jean Honoré Fragonard,"French, Grasse 1732–1806 Paris",,"Fragonard, Jean Honoré",French,1732,1806,early 1770s,1770,1773,Oil on canvas,32 3/4 x 26 3/8 in. (83.2 x 67 cm),"The Jules Bache Collection, 1949",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436322,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -49.7.50,false,true,436319,European Paintings,Painting,The Cascade,,,,,,Artist,,Jean Honoré Fragonard,"French, Grasse 1732–1806 Paris",,"Fragonard, Jean Honoré",French,1732,1806,ca. 1775,1770,1780,Oil on wood,11 1/2 x 9 1/2 in. (29.2 x 24.1 cm),"The Jules Bache Collection, 1949",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436319,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -49.7.51,false,true,436324,European Paintings,Painting,A Shaded Avenue,,,,,,Artist,,Jean Honoré Fragonard,"French, Grasse 1732–1806 Paris",,"Fragonard, Jean Honoré",French,1732,1806,ca. 1775,1770,1780,Oil on wood,11 1/2 x 9 1/2 in. (29.2 x 24.1 cm),"The Jules Bache Collection, 1949",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436324,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -53.61.5,false,true,436326,European Paintings,Painting,The Two Sisters,,,,,,Artist,,Jean Honoré Fragonard,"French, Grasse 1732–1806 Paris",,"Fragonard, Jean Honoré",French,1732,1806,ca. 1769–70,1769,1770,Oil on canvas,28 1/4 x 22 in. (71.8 x 55.9 cm),"Gift of Julia A. Berwind, 1953",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436326,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -56.100.1,false,true,436325,European Paintings,Painting,The Stolen Kiss,,,,,,Artist,,Jean Honoré Fragonard,"French, Grasse 1732–1806 Paris",,"Fragonard, Jean Honoré",French,1732,1806,ca. 1760,1755,1765,Oil on canvas,19 x 25 in. (48.3 x 63.5 cm),"Gift of Jessie Woolworth Donahue, 1956",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436325,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2002.441,false,true,438603,European Paintings,Painting,"François Gérard (1770–1837), later Baron Gérard",,,,,,Artist,,baron Antoine Jean Gros,"French, Paris 1771–1835 Meudon",,"Gros, Antoine Jean, baron",French,1771,1835,ca. 1790,1785,1795,Oil on canvas,22 1/8 x 18 5/8 in. (56.2 x 47.3 cm),"Gift of Mrs. Charles Wrightsman, 2002",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/438603,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -27.200,false,true,436483,European Paintings,Painting,Pygmalion and Galatea,,,,,,Artist,,Jean-Léon Gérôme,"French, Vesoul 1824–1904 Paris",,"Gérôme, Jean-Léon",French,1824,1904,ca. 1890,1885,1895,Oil on canvas,35 x 27 in. (88.9 x 68.6 cm),"Gift of Louis C. Raegner, 1927",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436483,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -05.13.4,false,true,436481,European Paintings,Painting,"Cafe House, Cairo (Casting Bullets)",,,,,,Artist,,Jean-Léon Gérôme,"French, Vesoul 1824–1904 Paris",,"Gérôme, Jean-Léon",French,1824,1904,1884 or earlier,1870,1884,Oil on canvas,21 1/2 x 24 3/4 in. (54.6 x 62.9 cm),"Bequest of Henry H. Cook, 1905",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436481,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.162.4,false,true,436484,European Paintings,Painting,Tiger and Cubs,,,,,,Artist,,Jean-Léon Gérôme,"French, Vesoul 1824–1904 Paris",,"Gérôme, Jean-Léon",French,1824,1904,ca. 1884,1879,1889,Oil on canvas,29 x 36 in. (73.7 x 91.4 cm),"Bequest of Susan P. Colgate, in memory of her husband, Romulus R. Colgate, 1936",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436484,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -87.15.130,false,true,436482,European Paintings,Painting,Prayer in the Mosque,,,,,,Artist,,Jean-Léon Gérôme,"French, Vesoul 1824–1904 Paris",,"Gérôme, Jean-Léon",French,1824,1904,1871,1871,1871,Oil on canvas,35 x 29 1/2 in. (88.9 x 74.9 cm),"Catharine Lorillard Wolfe Collection, Bequest of Catharine Lorillard Wolfe, 1887",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436482,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2008.547.1,true,true,440723,European Paintings,Painting,Bashi-Bazouk,,,,,,Artist,,Jean-Léon Gérôme,"French, Vesoul 1824–1904 Paris",,"Gérôme, Jean-Léon",French,1824,1904,1868–69,1868,1869,Oil on canvas,31 3/4 x 26 in. (80.6 x 66 cm),"Gift of Mrs. Charles Wrightsman, 2008",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/440723,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.121,false,true,436026,European Paintings,Painting,Spring Flowers,,,,,,Artist,Copy after,Gustave Courbet,"French, second half 19th century",,"Courbet, Gustave",French,1819,1877,ca. 1855–60,1855,1860,Oil on canvas,23 3/4 x 32 1/4 in. (60.3 x 81.9 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436026,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1983.405,false,true,437894,European Paintings,Painting,Eugène Joseph Stanislas Foullon d'Écotier (1753–1821),,,,,,Artist,,Antoine Vestier,"French, Avallon 1740–1824 Paris",,"Vestier, Antoine",French,1740,1824,1785,1785,1785,Oil on canvas,"Oval, 31 5/8 x 25 1/8 in. (80.3 x 63.8 cm)","Gift of Mr. and Mrs. Charles Wrightsman, 1983",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437894,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2002.69,false,true,438559,European Paintings,Painting,The Sack of Jerusalem by the Romans,,,,,,Artist,,François Joseph Heim,"French, Belfort 1787–1865 Paris",,"Heim, François Joseph",French,1787,1865,1824,1824,1824,Oil on canvas,14 x 15 in. (35.6 x 38.1 cm),"Catharine Lorillard Wolfe Collection, Wolfe Fund, 2002",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/438559,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2003.20.8,false,true,438009,European Paintings,Painting,"The Pink Dress (Albertie-Marguerite Carré, later Madame Ferdinand-Henri Himmes, 1854–1935)",,,,,,Artist,,Berthe Morisot,"French, Bourges 1841–1895 Paris",,"Morisot, Berthe",French,1841,1895,ca. 1870,1865,1875,Oil on canvas,21 1/2 x 26 1/2 in. (54.6 x 67.3 cm),"The Walter H. and Leonore Annenberg Collection, Bequest of Walter H. Annenberg, 2002",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/438009,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -67.187.89,false,true,437159,European Paintings,Painting,Young Woman Knitting,,,,,,Artist,,Berthe Morisot,"French, Bourges 1841–1895 Paris",,"Morisot, Berthe",French,1841,1895,ca. 1883,1878,1888,Oil on canvas,19 3/4 x 23 5/8 in. (50.2 x 60 cm),"Bequest of Miss Adelaide Milton de Groot (1876–1967), 1967",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437159,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1979.97,false,true,435856,European Paintings,Painting,Self-Portrait,,,,,,Artist,,Eugène Carrière,"French, Gournay 1849–1906 Paris",,"Carrière, Eugène",French,1849,1906,ca. 1893,1888,1898,Oil on canvas,16 1/4 x 12 7/8 in. (41.3 x 32.7 cm),"Purchase, Albert Otten Foundation Gift, 1979",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435856,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.428,false,true,442849,European Paintings,Painting,Women Sewing at a Table,,,,,,Artist,,Eugène Carrière,"French, Gournay 1849–1906 Paris",,"Carrière, Eugène",French,1849,1906,ca. 1894–96,1894,1896,Oil on canvas,10 1/4 × 15 in. (26 × 38.1 cm),"Gift of Ariane and Alain Kirili, 2013",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/442849,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -46.150.2,false,true,435627,European Paintings,Painting,In the Sun,,,,,,Artist,,Charles-Édouard de Beaumont,"French, Lannion 1821–1888 Paris",,"Beaumont, Charles-Édouard de",French,1821,1888,1875,1875,1875,Oil on canvas,23 1/2 x 37 3/4 in. (59.7 x 95.9 cm),"Gift of Estate of Marie L. Russell, 1946",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435627,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2008.660,false,true,496203,European Paintings,Painting,Woman Standing Beside Railing with Poodle,,,,,,Artist,,Paul Ranson,"French, Limoges 1864–1909 Paris",,"Ranson, Paul",French,1864,1909,ca. 1895,1895,1895,Oil on panel,33 1/2 x 11 5/8 in. (85.1 x 29.5 cm),"Gift of Mrs. Patricia Altschul, 2008",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/496203,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -51.32,false,true,437104,European Paintings,Painting,Dr. Leclenché,,,,,,Artist,,Claude Monet,"French, Paris 1840–1926 Giverny",,"Monet, Claude",French,1840,1926,1864,1864,1864,Oil on canvas,18 x 12 3/4 in. (45.7 x 32.4 cm),"Gift of Mr. and Mrs. Edwin C. Vogel, 1951",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437104,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -57.183,false,true,437130,European Paintings,Painting,Apples and Grapes,,,,,,Artist,,Claude Monet,"French, Paris 1840–1926 Giverny",,"Monet, Claude",French,1840,1926,1879–80,1879,1880,Oil on canvas,26 5/8 x 35 1/4 in. (67.6 x 89.5 cm),"Gift of Henry R. Luce, 1957",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437130,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -59.142,false,true,437108,European Paintings,Painting,The Parc Monceau,,,,,,Artist,,Claude Monet,"French, Paris 1840–1926 Giverny",,"Monet, Claude",French,1840,1926,1878,1878,1878,Oil on canvas,28 5/8 x 21 3/8 in. (72.7 x 54.3 cm),"The Mr. and Mrs. Henry Ittleson Jr. Purchase Fund, 1959",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437108,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -59.206,false,true,437107,European Paintings,Painting,Landscape: The Parc Monceau,,,,,,Artist,,Claude Monet,"French, Paris 1840–1926 Giverny",,"Monet, Claude",French,1840,1926,1876,1876,1876,Oil on canvas,23 1/2 x 32 1/2 in. (59.7 x 82.6 cm),"Bequest of Loula D. Lasker, New York City, 1961",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437107,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -60.154,false,true,437126,European Paintings,Painting,Île aux Orties near Vernon,,,,,,Artist,,Claude Monet,"French, Paris 1840–1926 Giverny",,"Monet, Claude",French,1840,1926,1897,1897,1897,Oil on canvas,28 7/8 x 36 1/2 in. (73.3 x 92.7 cm),"Gift of Mr. and Mrs. Charles S. McVeigh, 1960",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437126,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -64.210,false,true,437131,European Paintings,Painting,"The Bodmer Oak, Fontainebleau Forest",,,,,,Artist,,Claude Monet,"French, Paris 1840–1926 Giverny",,"Monet, Claude",French,1840,1926,1865,1865,1865,Oil on canvas,37 7/8 x 50 7/8 in. (96.2 x 129.2 cm),"Gift of Sam Salz and Bequest of Julia W. Emmons, by exchange, 1964",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437131,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -67.241,true,true,437133,European Paintings,Painting,Garden at Sainte-Adresse,,,,,,Artist,,Claude Monet,"French, Paris 1840–1926 Giverny",,"Monet, Claude",French,1840,1926,1867,1867,1867,Oil on canvas,38 5/8 x 51 1/8 in. (98.1 x 129.9 cm),"Purchase, special contributions and funds given or bequeathed by friends of the Museum, 1967",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437133,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -51.30.3,false,true,437111,European Paintings,Painting,Vétheuil in Summer,,,,,,Artist,,Claude Monet,"French, Paris 1840–1926 Giverny",,"Monet, Claude",French,1840,1926,1880,1880,1880,Oil on canvas,23 5/8 x 39 1/4 in. (60 x 99.7 cm),"Bequest of William Church Osborn, 1951",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437111,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -51.30.4,false,true,437136,European Paintings,Painting,Regatta at Sainte-Adresse,,,,,,Artist,,Claude Monet,"French, Paris 1840–1926 Giverny",,"Monet, Claude",French,1840,1926,1867,1867,1867,Oil on canvas,29 5/8 x 40 in. (75.2 x 101.6 cm),"Bequest of William Church Osborn, 1951",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437136,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -51.30.5,false,true,438823,European Paintings,Painting,The Manneporte (Étretat),,,,,,Artist,,Claude Monet,"French, Paris 1840–1926 Giverny",,"Monet, Claude",French,1840,1926,1883,1883,1883,Oil on canvas,25 3/4 x 32 in. (65.4 x 81.3 cm),"Bequest of William Church Osborn, 1951",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/438823,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1983.532,false,true,437137,European Paintings,Painting,Water Lilies,,,,,,Artist,,Claude Monet,"French, Paris 1840–1926 Giverny",,"Monet, Claude",French,1840,1926,1916–19,1916,1919,Oil on canvas,51 1/4 x 79 in. (130.2 x 200.7 cm),"Gift of Louise Reinhardt Smith, 1983",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437137,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1984.341,false,true,437113,European Paintings,Painting,Cabin of the Customs Watch,,,,,,Artist,,Claude Monet,"French, Paris 1840–1926 Giverny",,"Monet, Claude",French,1840,1926,1882,1882,1882,Oil on canvas,24 x 32 1/4 in. (61 x 81.9 cm),"Bequest of Julia B. Engel, 1984",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437113,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2000.195,false,true,438435,European Paintings,Painting,Jean Monet (1867–1913) on His Hobby Horse,,,,,,Artist,,Claude Monet,"French, Paris 1840–1926 Giverny",,"Monet, Claude",French,1840,1926,1872,1872,1872,Oil on canvas,23 7/8 x 29 1/4 in. (60.6 x 74.3 cm),"Gift of Sara Lee Corporation, 2000",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/438435,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -26.186.1,false,true,437106,European Paintings,Painting,Spring (Fruit Trees in Bloom),,,,,,Artist,,Claude Monet,"French, Paris 1840–1926 Giverny",,"Monet, Claude",French,1840,1926,1873,1873,1873,Oil on canvas,24 1/2 x 39 5/8 in. (62.2 x 100.6 cm),"Bequest of Mary Livingston Willard, 1926",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437106,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -31.67.11,false,true,437119,European Paintings,Painting,The Manneporte near Étretat,,,,,,Artist,,Claude Monet,"French, Paris 1840–1926 Giverny",,"Monet, Claude",French,1840,1926,1886,1886,1886,Oil on canvas,32 x 25 3/4 in. (81.3 x 65.4 cm),"Bequest of Lillie P. Bliss, 1931",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437119,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -56.135.1,false,true,437110,European Paintings,Painting,View of Vétheuil,,,,,,Artist,,Claude Monet,"French, Paris 1840–1926 Giverny",,"Monet, Claude",French,1840,1926,1880,1880,1880,Oil on canvas,31 1/2 x 23 3/4 in. (80 x 60.3 cm),"Bequest of Julia W. Emmons, 1956",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437110,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -56.135.4,false,true,437125,European Paintings,Painting,Morning on the Seine near Giverny,,,,,,Artist,,Claude Monet,"French, Paris 1840–1926 Giverny",,"Monet, Claude",French,1840,1926,1897,1897,1897,Oil on canvas,32 1/8 x 36 5/8 in. (81.6 x 93 cm),"Bequest of Julia W. Emmons, 1956",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437125,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -56.135.5,false,true,437138,European Paintings,Painting,Île aux Fleurs near Vétheuil,,,,,,Artist,,Claude Monet,"French, Paris 1840–1926 Giverny",,"Monet, Claude",French,1840,1926,1880,1880,1880,Oil on canvas,26 x 32 in. (66 x 81.3 cm),"Bequest of Julia W. Emmons, 1956",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437138,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -56.135.6,false,true,437128,European Paintings,Painting,The Houses of Parliament (Effect of Fog),,,,,,Artist,,Claude Monet,"French, Paris 1840–1926 Giverny",,"Monet, Claude",French,1840,1926,1903–4,1903,1904,Oil on canvas,32 x 36 3/8 in. (81.3 x 92.4 cm),"Bequest of Julia W. Emmons, 1956",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437128,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -59.188.1,false,true,437129,European Paintings,Painting,The Doge's Palace Seen from San Giorgio Maggiore,,,,,,Artist,,Claude Monet,"French, Paris 1840–1926 Giverny",,"Monet, Claude",French,1840,1926,1908,1908,1908,Oil on canvas,25 3/4 x 36 1/2 in. (65.4 x 92.7 cm),"Gift of Mr. and Mrs. Charles S. McVeigh, 1959",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437129,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2000.93.1,false,true,438005,European Paintings,Painting,Camille Monet (1847–1879) in the Garden at Argenteuil,,,,,,Artist,,Claude Monet,"French, Paris 1840–1926 Giverny",,"Monet, Claude",French,1840,1926,1876,1876,1876,Oil on canvas,32 1/8 x 23 5/8 in. (81.6 x 60 cm),"The Walter H. and Leonore Annenberg Collection, Gift of Walter H. and Leonore Annenberg, 2000, Bequest of Walter H. Annenberg, 2002",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/438005,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2002.62.1,false,true,438003,European Paintings,Painting,Camille Monet (1847–1879) on a Garden Bench,,,,,,Artist,,Claude Monet,"French, Paris 1840–1926 Giverny",,"Monet, Claude",French,1840,1926,1873,1873,1873,Oil on canvas,23 7/8 x 31 5/8 in. (60.6 x 80.3 cm),"The Walter H. and Leonore Annenberg Collection, Gift of Walter H. and Leonore Annenberg, 2002, Bequest of Walter H. Annenberg, 2002",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/438003,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2003.20.7,false,true,438006,European Paintings,Painting,"The Stroller (Suzanne Hoschedé, later Mrs. Theodore Earl Butler, 1868–1899)",,,,,,Artist,,Claude Monet,"French, Paris 1840–1926 Giverny",,"Monet, Claude",French,1840,1926,1887,1887,1887,Oil on canvas,39 5/8 x 27 3/4 in. (100.6 x 70.5 cm),"The Walter H. and Leonore Annenberg Collection, Bequest of Walter H. Annenberg, 2002",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/438006,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -30.95.250,false,true,437124,European Paintings,Painting,Rouen Cathedral: The Portal (Sunlight),,,,,,Artist,,Claude Monet,"French, Paris 1840–1926 Giverny",,"Monet, Claude",French,1840,1926,1894,1894,1894,Oil on canvas,39 1/4 x 25 7/8 in. (99.7 x 65.7 cm),"Theodore M. Davis Collection, Bequest of Theodore M. Davis, 1915",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437124,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -30.95.251,false,true,437118,European Paintings,Painting,The Valley of the Nervia,,,,,,Artist,,Claude Monet,"French, Paris 1840–1926 Giverny",,"Monet, Claude",French,1840,1926,1884,1884,1884,Oil on canvas,26 x 32 in. (66 x 81.3 cm),"Theodore M. Davis Collection, Bequest of Theodore M. Davis, 1915",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437118,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -30.95.271,false,true,437109,European Paintings,Painting,The Seine at Vétheuil,,,,,,Artist,,Claude Monet,"French, Paris 1840–1926 Giverny",,"Monet, Claude",French,1840,1926,1880,1880,1880,Oil on canvas,23 3/4 x 39 1/2 in. (60.3 x 100.3 cm),"Theodore M. Davis Collection, Bequest of Theodore M. Davis, 1915",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437109,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -67.187.87,false,true,437117,European Paintings,Painting,Palm Trees at Bordighera,,,,,,Artist,,Claude Monet,"French, Paris 1840–1926 Giverny",,"Monet, Claude",French,1840,1926,1884,1884,1884,Oil on canvas,25 1/2 x 32in. (64.8 x 81.3cm),"Bequest of Miss Adelaide Milton de Groot (1876–1967), 1967",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437117,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -67.187.88,false,true,437120,European Paintings,Painting,Rapids on the Petite Creuse at Fresselines,,,,,,Artist,,Claude Monet,"French, Paris 1840–1926 Giverny",,"Monet, Claude",French,1840,1926,1889,1889,1889,Oil on canvas,25 3/4 x 36 1/8 in. (65.4 x 91.8 cm),"Bequest of Miss Adelaide Milton de Groot (1876–1967), 1967",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437120,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1998.325.2,false,true,438008,European Paintings,Painting,Water Lilies,,,,,,Artist,,Claude Monet,"French, Paris 1840–1926 Giverny",,"Monet, Claude",French,1840,1926,1919,1919,1919,Oil on canvas,39 3/4 x 78 3/4 in. (101 x 200 cm),"The Walter H. and Leonore Annenberg Collection, Gift of Walter H. and Leonore Annenberg, 1998, Bequest of Walter H. Annenberg, 2002",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/438008,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2001.202.5,false,true,438004,European Paintings,Painting,Poppy Fields near Argenteuil,,,,,,Artist,,Claude Monet,"French, Paris 1840–1926 Giverny",,"Monet, Claude",French,1840,1926,1875,1875,1875,Oil on canvas,21 1/4 x 29 in. (54 x 73.7 cm),"The Walter H. and Leonore Annenberg Collection, Gift of Walter H. and Leonore Annenberg, 2001, Bequest of Walter H. Annenberg, 2002",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/438004,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2001.202.6,false,true,438007,European Paintings,Painting,The Path through the Irises,,,,,,Artist,,Claude Monet,"French, Paris 1840–1926 Giverny",,"Monet, Claude",French,1840,1926,1914–17,1914,1917,Oil on canvas,78 7/8 x 70 7/8 in. (200.3 x 180 cm),"The Walter H. and Leonore Annenberg Collection, Gift of Walter H. and Leonore Annenberg, 2001, Bequest of Walter H. Annenberg, 2002",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/438007,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.106,false,true,437115,European Paintings,Painting,Chrysanthemums,,,,,,Artist,,Claude Monet,"French, Paris 1840–1926 Giverny",,"Monet, Claude",French,1840,1926,1882,1882,1882,Oil on canvas,39 1/2 x 32 1/4 in. (100.3 x 81.9 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437115,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.107,false,true,437112,European Paintings,Painting,Bouquet of Sunflowers,,,,,,Artist,,Claude Monet,"French, Paris 1840–1926 Giverny",,"Monet, Claude",French,1840,1926,1881,1881,1881,Oil on canvas,39 3/4 x 32 in. (101 x 81.3 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437112,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.108,false,true,437123,European Paintings,Painting,Ice Floes,,,,,,Artist,,Claude Monet,"French, Paris 1840–1926 Giverny",,"Monet, Claude",French,1840,1926,1893,1893,1893,Oil on canvas,26 x 39 1/2 in. (66 x 100.3 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437123,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.109,false,true,437122,European Paintings,Painting,Haystacks (Effect of Snow and Sun),,,,,,Artist,,Claude Monet,"French, Paris 1840–1926 Giverny",,"Monet, Claude",French,1840,1926,1891,1891,1891,Oil on canvas,25 3/4 x 36 1/4 in. (65.4 x 92.1 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437122,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.110,false,true,437121,European Paintings,Painting,The Four Trees,,,,,,Artist,,Claude Monet,"French, Paris 1840–1926 Giverny",,"Monet, Claude",French,1840,1926,1891,1891,1891,Oil on canvas,32 1/4 x 32 1/8 in. (81.9 x 81.6 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437121,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.111,false,true,437105,European Paintings,Painting,The Green Wave,,,,,,Artist,,Claude Monet,"French, Paris 1840–1926 Giverny",,"Monet, Claude",French,1840,1926,ca. 1866–67,1861,1871,Oil on canvas,19 1/8 x 25 1/2 in. (48.6 x 64.8 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437105,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.112,false,true,437135,European Paintings,Painting,La Grenouillère,,,,,,Artist,,Claude Monet,"French, Paris 1840–1926 Giverny",,"Monet, Claude",French,1840,1926,1869,1869,1869,Oil on canvas,29 3/8 x 39 1/4 in. (74.6 x 99.7 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437135,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.113,false,true,437127,European Paintings,Painting,Bridge over a Pond of Water Lilies,,,,,,Artist,,Claude Monet,"French, Paris 1840–1926 Giverny",,"Monet, Claude",French,1840,1926,1899,1899,1899,Oil on canvas,36 1/2 x 29 in. (92.7 x 73.7 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437127,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -06.1233.2,false,true,436075,European Paintings,Painting,Madonna of the Rose,,,,,,Artist,,Pascal-Adolphe-Jean Dagnan-Bouveret,"French, Paris 1852–1929 Quincey",,"Dagnan-Bouveret, Pascal-Adolphe-Jean",French,1852,1929,1885,1885,1885,Oil on canvas,33 3/4 x 27 in. (85.7 x 68.6 cm),"Catharine Lorillard Wolfe Collection, Wolfe Fund, 1906",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436075,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -31.132.34,false,true,436076,European Paintings,Painting,The Pardon in Brittany,,,,,,Artist,,Pascal-Adolphe-Jean Dagnan-Bouveret,"French, Paris 1852–1929 Quincey",,"Dagnan-Bouveret, Pascal-Adolphe-Jean",French,1852,1929,1886,1886,1886,Oil on canvas,45 1/8 x 33 3/8 in. (114.6 x 84.8 cm),"Gift of George F. Baker, 1931",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436076,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -17.120.220,false,true,437846,European Paintings,Painting,Going to Market,,,,,,Artist,,Constant Troyon,"French, Sèvres 1810–1865 Paris",,"Troyon, Constant",French,1810,1865,1860,1860,1860,Oil on canvas,16 1/8 x 12 7/8 in. (41 x 32.7 cm),"Mr. and Mrs. Isaac D. Fletcher Collection, Bequest of Isaac D. Fletcher, 1917",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437846,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -87.15.111,false,true,436866,European Paintings,Painting,Graziella,,,,,,Artist,,Jules-Joseph Lefebvre,"French, Tournan 1836–1912 Paris",,"Lefebvre, Jules-Joseph",French,1836,1912,1878,1878,1878,Oil on canvas,78 3/4 x 44 1/4 in. (200 x 112.4 cm),"Catharine Lorillard Wolfe Collection, Bequest of Catharine Lorillard Wolfe, 1887",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436866,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -71.91,false,true,436583,European Paintings,Painting,Study Head of a Woman,,,,,,Artist,,Jean-Baptiste Greuze,"French, Tournus 1725–1805 Paris",,"Greuze, Jean-Baptiste",French,1725,1805,ca. 1780,1775,1785,Oil on wood,18 1/2 x 16 in. (47 x 40.6 cm),"Purchase, 1871",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436583,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -56.55.3,false,true,436587,European Paintings,Painting,Jean Jacques Caffiéri (1725–1792),,,,,,Artist,,Jean-Baptiste Greuze,"French, Tournus 1725–1805 Paris",,"Greuze, Jean-Baptiste",French,1725,1805,ca. 1763,1758,1768,Oil on canvas,"Oval, 25 1/4 x 20 3/4 in. (64.1 x 52.7 cm)","Bequest of Ethel Tod Humphrys, 1956",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436587,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -66.28.1,false,true,436581,European Paintings,Painting,"Charles Claude de Flahaut (1730–1809), Comte d'Angiviller",,,,,,Artist,,Jean-Baptiste Greuze,"French, Tournus 1725–1805 Paris",,"Greuze, Jean-Baptiste",French,1725,1805,1763,1763,1763,Oil on canvas,25 1/4 x 21 1/4 in. (64.1 x 54 cm),"Gift of Edith C. Blum (et al.) Executors, in memory of Mr. and Mrs. Albert Blum, 1966",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436581,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1970.295,false,true,436580,European Paintings,Painting,Aegina Visited by Jupiter,,,,,,Artist,,Jean-Baptiste Greuze,"French, Tournus 1725–1805 Paris",,"Greuze, Jean-Baptiste",French,1725,1805,ca. 1767–69,1767,1769,Oil on canvas,57 7/8 x 77 1/8 in. (147 x 195.9 cm),"Gift of Harry N. Abrams and Purchase, Joseph Pulitzer Bequest, Pfeiffer, Fletcher, and Rogers Funds, 1970",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436580,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -20.155.8,true,true,436579,European Paintings,Painting,Broken Eggs,,,,,,Artist,,Jean-Baptiste Greuze,"French, Tournus 1725–1805 Paris",,"Greuze, Jean-Baptiste",French,1725,1805,1756,1756,1756,Oil on canvas,28 3/4 x 37 in. (73 x 94 cm),"Bequest of William K. Vanderbilt, 1920",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436579,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -55.205.2,false,true,436584,European Paintings,Painting,"Madame Jean-Baptiste Nicolet (Anne Antoinette Desmoulins, 1743–1817)",,,,,,Artist,,Jean-Baptiste Greuze,"French, Tournus 1725–1805 Paris",,"Greuze, Jean-Baptiste",French,1725,1805,late 1780s,1787,1789,Oil on wood,25 1/4 x 21 in. (64.1 x 53.3 cm),"Gift of Colonel and Mrs. Jacques Balsan, 1955",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436584,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -65.242.3,false,true,436586,European Paintings,Painting,Princess Varvara Nikolaevna Gagarina (1762–1802),,,,,,Artist,,Jean-Baptiste Greuze,"French, Tournus 1725–1805 Paris",,"Greuze, Jean-Baptiste",French,1725,1805,ca. 1780–82,1780,1782,Oil on canvas,"Oval, 31 1/2 x 25 in. (80 x 63.5 cm)","Gift of Mrs. William M. Haupt, from the collection of Mrs. James B. Haggin, 1965",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436586,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -67.187.72,false,true,436582,European Paintings,Painting,Head of a Young Woman,,,,,,Artist,,Jean-Baptiste Greuze,"French, Tournus 1725–1805 Paris",,"Greuze, Jean-Baptiste",French,1725,1805,possibly 1780s,1780,1789,Oil on canvas,16 1/8 x 12 3/4 in. (41 x 32.4 cm),"Bequest of Miss Adelaide Milton de Groot (1876–1967), 1967",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436582,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -32.100.137,false,true,436588,European Paintings,Painting,Head of a Young Boy,,,,,,Artist,,Jean-Baptiste Greuze,"French, Tournus 1725–1805 Paris",,"Greuze, Jean-Baptiste",French,1725,1805,1763,1763,1763,Oil on canvas,18 7/8 x 15 3/8 in. (47.9 x 39.1 cm),"The Friedsam Collection, Bequest of Michael Friedsam, 1931",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436588,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -06.144,false,true,437885,European Paintings,Painting,The Triumph of Aemilius Paulus,,,,,,Artist,,Carle (Antoine Charles Horace) Vernet,"French, Bordeaux 1758–1836 Paris",,"Vernet, Carle (Antoine Charles Horace)",French,1758,1836,1789,1789,1789,Oil on canvas,51 1/8 x 172 1/2 in. (129.9 x 438.2 cm),"Gift of Darius O. Mills, 1906",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437885,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -27.29,false,true,437380,European Paintings,Painting,The Chariot of Apollo,,,,,,Artist,,Odilon Redon,"French, Bordeaux 1840–1916 Paris",,"Redon, Odilon",French,1840,1916,1905–16,1905,1916,Oil on canvas,26 x 32 in. (66 x 81.3 cm),"Anonymous Gift, 1927",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437380,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -64.266,false,true,437378,European Paintings,Painting,Bouquet in a Chinese Vase,,,,,,Artist,,Odilon Redon,"French, Bordeaux 1840–1916 Paris",,"Redon, Odilon",French,1840,1916,ca. 1912–14,1890,1916,Oil on canvas,25 1/2 x 19 5/8 in. (64.8 x 49.8 cm),"The Mr. and Mrs. Henry Ittleson Jr. Purchase Fund, 1964",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437378,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -59.16.3,false,true,437382,European Paintings,Painting,Vase of Flowers (Pink Background),,,,,,Artist,,Odilon Redon,"French, Bordeaux 1840–1916 Paris",,"Redon, Odilon",French,1840,1916,ca. 1906,1901,1911,Oil on canvas,28 5/8 x 21 1/4 in. (72.7 x 54 cm),"Bequest of Mabel Choate, in memory of her father, Joseph Hodges Choate, 1958",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437382,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -60.19.1,false,true,437383,European Paintings,Painting,Pandora,,,,,,Artist,,Odilon Redon,"French, Bordeaux 1840–1916 Paris",,"Redon, Odilon",French,1840,1916,ca. 1914,1909,1919,Oil on canvas,56 1/2 x 24 1/2 in. (143.5 x 62.2 cm),"Bequest of Alexander M. Bing, 1959",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437383,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -53.140.5,false,true,437381,European Paintings,Painting,Etruscan Vase with Flowers,,,,,,Artist,,Odilon Redon,"French, Bordeaux 1840–1916 Paris",,"Redon, Odilon",French,1840,1916,1900–1910,1900,1910,Tempera on canvas,32 x 23 1/4 in. (81.3 x 59.1 cm),"Maria DeWitt Jesup Fund, 1951; acquired from The Museum of Modern Art, Lillie P. Bliss Collection",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437381,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -51.34,false,true,435886,European Paintings,Painting,Jean-Baptiste Colbert (1619–1683),,,,,,Artist,,Philippe de Champaigne,"French, Brussels 1602–1674 Paris",,"Champaigne, Philippe de",French,1602,1674,1655,1655,1655,Oil on canvas,36 1/4 x 28 1/2 in. (92.1 x 72.4 cm),"Gift of The Wildenstein Foundation Inc., 1951",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435886,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2004.31,false,true,438724,European Paintings,Painting,The Annunciation,,,,,,Artist,,Philippe de Champaigne,"French, Brussels 1602–1674 Paris",,"Champaigne, Philippe de",French,1602,1674,ca. 1644,1639,1649,Oil on oak,"Overall, 28 x 28 3/4 in. (71.1 x 73 cm); painted surface, 27 1/4 x 27 3/4 in. (69.2 x 70.5 cm)","Wrightsman Fund, 2004",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/438724,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -10.41,false,true,436295,European Paintings,Painting,Portrait of a Woman,,,,,,Artist,,Henri Fantin-Latour,"French, Grenoble 1836–1904 Buré",,"Fantin-Latour, Henri",French,1836,1904,1885,1885,1885,Oil on canvas,39 1/2 x 32 in. (100.3 x 81.3 cm),"Catharine Lorillard Wolfe Collection, Wolfe Fund, 1910",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436295,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1980.3,false,true,436293,European Paintings,Painting,Still Life with Flowers and Fruit,,,,,,Artist,,Henri Fantin-Latour,"French, Grenoble 1836–1904 Buré",,"Fantin-Latour, Henri",French,1836,1904,1866,1866,1866,Oil on canvas,28 3/4 x 23 5/8 in. (73 x 60 cm),"Purchase, Mr. and Mrs. Richard J. Bernhard Gift, by exchange, 1980",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436293,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -66.194,false,true,436294,European Paintings,Painting,Still Life with Pansies,,,,,,Artist,,Henri Fantin-Latour,"French, Grenoble 1836–1904 Buré",,"Fantin-Latour, Henri",French,1836,1904,1874,1874,1874,Oil on canvas,18 1/2 x 22 1/4 in. (47 x 56.5 cm),"The Mr. and Mrs. Henry Ittleson Jr. Purchase Fund, 1966",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436294,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1995.91,false,true,436297,European Paintings,Painting,Self-Portrait,,,,,,Artist,,Henri Fantin-Latour,"French, Grenoble 1836–1904 Buré",,"Fantin-Latour, Henri",French,1836,1904,ca. 1858,1856,1904,"Oil on canvas, laid down on canvas",Overall 10 3/8 x 8 3/8 in. (26.4 x 21.3 cm); original canvas 10 x 7 7/8 in. (25.4 x 20 cm),"Catharine Lorillard Wolfe Collection, Wolfe Fund, 1995",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436297,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1987.119,false,true,436292,European Paintings,Painting,Still Life with Roses and Fruit,,,,,,Artist,,Henri Fantin-Latour,"French, Grenoble 1836–1904 Buré",,"Fantin-Latour, Henri",French,1836,1904,1863,1863,1863,Oil on canvas,13 5/8 x 16 3/8 in. (34.6 x 41.6 cm),"Bequest of Alice A. Hay, 1987",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436292,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1996.517,false,true,437985,European Paintings,Painting,Pansies,,,,,,Artist,,Henri Fantin-Latour,"French, Grenoble 1836–1904 Buré",,"Fantin-Latour, Henri",French,1836,1904,1903,1903,1903,Oil on canvas,9 x 11 1/8 in. (22.9 x 28.3 cm),"Gift of Paul O. Fabri, 1996",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437985,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1997.347,false,true,438031,European Paintings,Painting,Summer Flowers,,,,,,Artist,,Henri Fantin-Latour,"French, Grenoble 1836–1904 Buré",,"Fantin-Latour, Henri",French,1836,1904,1880,1880,1880,Oil on canvas,20 x 24 3/8 in. (50.8 x 61.9 cm),"Gift of Susan S. Dillon, 1997",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/438031,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.636,false,true,629928,European Paintings,Painting,Potted Pansies,,,,,,Artist,,Henri Fantin-Latour,"French, Grenoble 1836–1904 Buré",,"Fantin-Latour, Henri",French,1836,1904,1883,1883,1883,Oil on canvas,11 × 13 1/2 in. (27.9 × 34.3 cm),"Gift of Susan S. Dillon, 2013",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/629928,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -23.280.9,false,true,436296,European Paintings,Painting,The Palace of Aurora,,,,,,Artist,,Henri Fantin-Latour,"French, Grenoble 1836–1904 Buré",,"Fantin-Latour, Henri",French,1836,1904,1902,1856,1904,Oil on canvas,18 1/8 x 15 in. (46 x 38.1 cm),"Bequest of Anne D. Thomson, 1923",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436296,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2003.20.6,false,true,437995,European Paintings,Painting,Roses in a Bowl,,,,,,Artist,,Henri Fantin-Latour,"French, Grenoble 1836–1904 Buré",,"Fantin-Latour, Henri",French,1836,1904,1883,1883,1883,Oil on canvas,11 3/4 x 16 3/8 in. (29.8 x 41.6 cm),"The Walter H. and Leonore Annenberg Collection, Bequest of Walter H. Annenberg, 2002",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437995,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2001.202.3,false,true,437997,European Paintings,Painting,Asters and Fruit on a Table,,,,,,Artist,,Henri Fantin-Latour,"French, Grenoble 1836–1904 Buré",,"Fantin-Latour, Henri",French,1836,1904,1868,1868,1868,Oil on canvas,22 3/8 x 21 5/8 in. (56.8 x 54.9 cm),"The Walter H. and Leonore Annenberg Collection, Gift of Walter H. and Leonore Annenberg, 2001, Bequest of Walter H. Annenberg, 2002",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437997,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -49.114,false,true,437647,European Paintings,Painting,Spring in Brittany,,,,,,Artist,,Paul Sébillot,"French, Matignon 1843–1918 Paris",,"Sébillot, Paul",French,1843,1918,1874,1874,1874,Oil on wood,14 x 10 3/4 in. (35.6 x 27.3 cm),"Gift of Paul-Yves Sébillot, 1949",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437647,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -71.57,false,true,437235,European Paintings,Painting,Ducks Resting in Sunshine,,,,,,Artist,,Jean-Baptiste Oudry,"French, Paris 1686–1755 Beauvais",,"Oudry, Jean-Baptiste",French,1686,1755,1753,1753,1753,Oil on canvas,25 1/2 x 31 3/4 in. (64.8 x 80.6 cm),"Purchase, 1871",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437235,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -71.89,false,true,437234,European Paintings,Painting,Dog Guarding Dead Game,,,,,,Artist,,Jean-Baptiste Oudry,"French, Paris 1686–1755 Beauvais",,"Oudry, Jean-Baptiste",French,1686,1755,1753,1753,1753,Oil on canvas,25 1/2 x 31 3/4 in. (64.8 x 80.6 cm),"Purchase, 1871",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437234,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -31.45,true,true,436105,European Paintings,Painting,The Death of Socrates,,,,,,Artist,,Jacques Louis David,"French, Paris 1748–1825 Brussels",,"David, Jacques Louis",French,1748,1825,1787,1787,1787,Oil on canvas,51 x 77 1/4 in. (129.5 x 196.2 cm),"Catharine Lorillard Wolfe Collection, Wolfe Fund, 1931",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436105,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1977.10,true,true,436106,European Paintings,Painting,"Antoine-Laurent Lavoisier (1743–1794) and His Wife (Marie-Anne-Pierrette Paulze, 1758–1836)",,,,,,Artist,,Jacques Louis David,"French, Paris 1748–1825 Brussels",,"David, Jacques Louis",French,1748,1825,1788,1788,1788,Oil on canvas,102 1/4 x 76 5/8 in. (259.7 x 194.6 cm),"Purchase, Mr. and Mrs. Charles Wrightsman Gift, in honor of Everett Fahy, 1977",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436106,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -65.14.5,false,true,436107,European Paintings,Painting,General Étienne-Maurice Gérard (1773–1852),,,,,,Artist,,Jacques Louis David,"French, Paris 1748–1825 Brussels",,"David, Jacques Louis",French,1748,1825,1816,1816,1816,Oil on canvas,77 5/8 x 53 5/8 in. (197.2 x 136.2 cm),"Purchase, Rogers and Fletcher Funds, and Mary Wetmore Shively Bequest, in memory of her husband, Henry L. Shively, M.D., 1965",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436107,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -11.4,false,true,438816,European Paintings,Painting,The Forest in Winter at Sunset,,,,,,Artist,,Théodore Rousseau,"French, Paris 1812–1867 Barbizon",,"Rousseau, Théodore",French,1812,1867,ca. 1846–67,1846,1867,Oil on canvas,64 x 102 3/8 in. (162.6 x 260 cm),"Gift of P. A. B. Widener, 1911",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/438816,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -03.28,false,true,437516,European Paintings,Painting,An Old Chapel in a Valley,,,,,,Artist,,Théodore Rousseau,"French, Paris 1812–1867 Barbizon",,"Rousseau, Théodore",French,1812,1867,ca. 1835,1832,1867,Oil on wood,10 1/2 x 13 7/8 in. (26.7 x 35.2 cm),"Catharine Lorillard Wolfe Collection, Wolfe Fund, 1903",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437516,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -96.27,false,true,437514,European Paintings,Painting,"The Edge of the Woods at Monts-Girard, Fontainebleau Forest",,,,,,Artist,,Théodore Rousseau,"French, Paris 1812–1867 Barbizon",,"Rousseau, Théodore",French,1812,1867,1852–54,1852,1854,Oil on wood,31 1/2 x 48 in. (80 x 121.9 cm),"Catharine Lorillard Wolfe Collection, Wolfe Fund, 1896",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437514,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -11.45.5,false,true,437515,European Paintings,Painting,A Meadow Bordered by Trees,,,,,,Artist,,Théodore Rousseau,"French, Paris 1812–1867 Barbizon",,"Rousseau, Théodore",French,1812,1867,ca. 1845,1840,1850,Oil on wood,16 3/8 x 24 3/8 in. (41.6 x 61.9 cm),"Bequest of Robert Graham Dun, 1900",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437515,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -25.110.4,false,true,437520,European Paintings,Painting,Sunset near Arbonne,,,,,,Artist,,Théodore Rousseau,"French, Paris 1812–1867 Barbizon",,"Rousseau, Théodore",French,1812,1867,ca. 1860–65,1860,1865,Oil on wood,25 1/4 x 39 in. (64.1 x 99.1 cm),"Bequest of Collis P. Huntington, 1900",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437520,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.40.814,false,true,437517,European Paintings,Painting,A Path among the Rocks,,,,,,Artist,,Théodore Rousseau,"French, Paris 1812–1867 Barbizon",,"Rousseau, Théodore",French,1812,1867,probably 1861,1832,1867,Oil on wood,15 x 23 5/8 in. (38.1 x 60 cm),"Bequest of Benjamin Altman, 1913",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437517,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -25.110.52,false,true,437518,European Paintings,Painting,A River in a Meadow,,,,,,Artist,,Théodore Rousseau,"French, Paris 1812–1867 Barbizon",,"Rousseau, Théodore",French,1812,1867,probably late 1830s–early 1840s,1835,1845,Oil on wood,16 3/4 x 26 1/8 in. (42.5 x 66.4 cm),"Bequest of Collis P. Huntington, 1900",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437518,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1982.320,false,true,437513,European Paintings,Painting,Still Life with Ham,,,,,,Artist,,Philippe Rousseau,"French, Paris 1816–1887 Acquigny",,"Rousseau, Philippe",French,1816,1887,1870s,1870,1879,Oil on canvas,28 3/4 x 36 1/4 in. (73 x 92.1 cm),"Catharine Lorillard Wolfe Collection, Wolfe Fund, 1982",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437513,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -81.1.656,false,true,435603,European Paintings,Painting,A Footman Sleeping,,,,,,Artist,,Charles Bargue,"French, Paris 1825/26–1883 Paris",,"Bargue, Charles",French,1825,1883,1871,1871,1871,Oil on wood,13 3/4 x 10 1/4 in. (34.9 x 26 cm),"Bequest of Stephen Whitney Phoenix, 1881",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435603,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -87.15.102,false,true,435604,European Paintings,Painting,A Bashi-Bazouk,,,,,,Artist,,Charles Bargue,"French, Paris 1825/26–1883 Paris",,"Bargue, Charles",French,1825,1883,1875,1875,1875,Oil on canvas,18 1/4 x 13 1/8 in. (46.4 x 33.3 cm),"Catharine Lorillard Wolfe Collection, Bequest of Catharine Lorillard Wolfe, 1887",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435604,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -16.95,false,true,437384,European Paintings,Painting,Salomé,,,,,,Artist,,Henri Regnault,"French, Paris 1843–1871 Buzenval",,"Regnault, Henri",French,1843,1871,1870,1870,1870,Oil on canvas,63 x 40 1/2 in. (160 x 102.9 cm),"Gift of George F. Baker, 1916",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437384,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2003.42.54,false,true,438677,European Paintings,Painting,"The Banks of the Rance, Brittany",,,,,,Artist,,Pierre Henri de Valenciennes,"French, Toulouse 1750–1819 Paris",,"Valenciennes, Pierre Henri de",French,1750,1819,possibly 1785,1785,1819,"Oil on paper, laid down on canvas",8 3/8 x 19 3/8 in. (21.3 x 49.2 cm),"The Whitney Collection, Gift of Wheelock Whitney III, and Purchase, Gift of Mr. and Mrs. Charles S. McVeigh, by exchange, 2003",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/438677,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.40.819,false,true,436193,European Paintings,Painting,The Edge of the Woods,,,,,,Artist,,Narcisse-Virgile Diaz de la Peña,"French, Bordeaux 1808–1876 Menton",,"Diaz de la Peña, Narcisse-Virgile",French,1808,1876,1872,1872,1872,Oil on wood,14 7/8 x 18 1/2 in. (37.8 x 47 cm),"Bequest of Benjamin Altman, 1913",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436193,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -25.110.30,false,true,436191,European Paintings,Painting,Diana,,,,,,Artist,,Narcisse-Virgile Diaz de la Peña,"French, Bordeaux 1808–1876 Menton",,"Diaz de la Peña, Narcisse-Virgile",French,1808,1876,1849,1849,1849,Oil on canvas,46 1/2 x 27 3/4 in. (118.1 x 70.5 cm),"Bequest of Collis P. Huntington, 1900",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436191,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -25.110.92,false,true,436196,European Paintings,Painting,The Forest of Fontainebleau,,,,,,Artist,,Narcisse-Virgile Diaz de la Peña,"French, Bordeaux 1808–1876 Menton",,"Diaz de la Peña, Narcisse-Virgile",French,1808,1876,1874,1874,1874,Oil on wood,18 5/8 x 23 5/8 in. (47.3 x 60 cm),"Bequest of Collis P. Huntington, 1900",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436196,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -17.120.214,false,true,436192,European Paintings,Painting,Autumn: The Woodland Pond,,,,,,Artist,,Narcisse-Virgile Diaz de la Peña,"French, Bordeaux 1808–1876 Menton",,"Diaz de la Peña, Narcisse-Virgile",French,1808,1876,1867,1867,1867,Oil on canvas,19 3/4 x 26 in. (50.2 x 66 cm),"Mr. and Mrs. Isaac D. Fletcher Collection, Bequest of Isaac D. Fletcher, 1917",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436192,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -17.120.230,false,true,436195,European Paintings,Painting,A Vista through Trees: Fontainebleau,,,,,,Artist,,Narcisse-Virgile Diaz de la Peña,"French, Bordeaux 1808–1876 Menton",,"Diaz de la Peña, Narcisse-Virgile",French,1808,1876,1873,1873,1873,Oil on wood,12 3/4 x 17 1/4 in. (32.4 x 43.8 cm),"Mr. and Mrs. Isaac D. Fletcher Collection, Bequest of Isaac D. Fletcher, 1917",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436195,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -38.75,false,true,437099,European Paintings,Painting,Woman with a Rake,,,,,,Artist,,Jean-François Millet,"French, Gruchy 1814–1875 Barbizon",,"Millet, Jean-François",French,1814,1875,probably 1856–57,1856,1857,Oil on canvas,15 5/8 x 13 1/2 in. (39.7 x 34.3 cm),"Gift of Stephen C. Clark, 1938",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437099,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -50.151,false,true,437095,European Paintings,Painting,Calling the Cows Home,,,,,,Artist,,Jean-François Millet,"French, Gruchy 1814–1875 Barbizon",,"Millet, Jean-François",French,1814,1875,ca. 1872,1872,1872,Oil on wood,37 1/4 x 25 1/2 in. (94.6 x 64.8 cm),"Gift of Mrs. Arthur Whitney, 1950",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437095,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -15.30.24,false,true,437096,European Paintings,Painting,Garden Scene,,,,,,Artist,,Jean-François Millet,"French, Gruchy 1814–1875 Barbizon",,"Millet, Jean-François",French,1814,1875,1854,1854,1854,Oil on canvas,6 3/4 x 8 3/8 in. (17.1 x 21.3 cm),"Bequest of Maria DeWitt Jesup, from the collection of her husband, Morris K. Jesup, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437096,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1983.446,false,true,437098,European Paintings,Painting,Shepherdess Seated on a Rock,,,,,,Artist,,Jean-François Millet,"French, Gruchy 1814–1875 Barbizon",,"Millet, Jean-François",French,1814,1875,1856,1856,1856,Oil on wood,14 1/8 x 11 1/8 in. (35.9 x 28.3 cm),"Gift of Douglas Dillon, 1983",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437098,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2002.613,false,true,438616,European Paintings,Painting,Retreat from the Storm,,,,,,Artist,,Jean-François Millet,"French, Gruchy 1814–1875 Barbizon",,"Millet, Jean-François",French,1814,1875,ca. 1846,1846,1846,Oil on canvas,18 1/4 x 15 in. (46.4 x 38.1 cm),"Gift of Sarina Tang and Peter M. Wood, 2002",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/438616,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -60.71.12,false,true,437097,European Paintings,Painting,Haystacks: Autumn,,,,,,Artist,,Jean-François Millet,"French, Gruchy 1814–1875 Barbizon",,"Millet, Jean-François",French,1814,1875,ca. 1874,1869,1875,Oil on canvas,33 1/2 x 43 3/8 in. (85.1 x 110.2 cm),"Bequest of Lillian S. Timken, 1959",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437097,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -17.120.209,false,true,437094,European Paintings,Painting,Autumn Landscape with a Flock of Turkeys,,,,,,Artist,,Jean-François Millet,"French, Gruchy 1814–1875 Barbizon",,"Millet, Jean-François",French,1814,1875,1872–73,1872,1873,Oil on canvas,31 7/8 x 39 in. (81 x 99.1 cm),"Mr. and Mrs. Isaac D. Fletcher Collection, Bequest of Isaac D. Fletcher, 1917",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437094,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1997.371,false,true,438032,European Paintings,Painting,"Study for ""Portrait of an Indian""",,,,,,Artist,,Anne Louis Girodet-Trioson,"French, Montargis 1767–1824 Paris",,"Girodet-Trioson, Anne Louis",French,1767,1824,ca. 1807,1802,1812,Oil on canvas,16 x 12 7/8 in. (40.6 x 32.7 cm),"Purchase, Gift of Joanne Toor Cummings, by exchange, 1997",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/438032,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1999.101,false,true,438389,European Paintings,Painting,"Madame Jacques-Louis-Étienne Reizet (Colette-Désirée-Thérèse Godefroy, 1782–1850)",,,,,,Artist,,Anne Louis Girodet-Trioson,"French, Montargis 1767–1824 Paris",,"Girodet-Trioson, Anne Louis",French,1767,1824,1823,1823,1823,Oil on canvas,23 3/4 x 19 1/2 in. (60.3 x 49.5 cm),"Purchase, Gifts of Joanne Toor Cummings, Mr. and Mrs. Richard Rodgers, Raymonde Paul, and Estate of Dorothy Lichtensteiger, by exchange, 1999",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/438389,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -38.65,false,true,436708,European Paintings,Painting,Odalisque in Grisaille,,,,,,Artist,,Jean Auguste Dominique Ingres,"French, Montauban 1780–1867 Paris",and Workshop,"Ingres, Jean Auguste Dominique",French,1780,1867,ca. 1824–34,1824,1834,Oil on canvas,32 3/4 x 43 in. (83.2 x 109.2 cm),"Catharine Lorillard Wolfe Collection, Wolfe Fund, 1938",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436708,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -19.77.1,false,true,436706,European Paintings,Painting,Jacques-Louis Leblanc (1774–1846),,,,,,Artist,,Jean Auguste Dominique Ingres,"French, Montauban 1780–1867 Paris",,"Ingres, Jean Auguste Dominique",French,1780,1867,1823,1823,1823,Oil on canvas,47 5/8 x 37 5/8 in. (121 x 95.6 cm),"Catharine Lorillard Wolfe Collection, Wolfe Fund, 1918",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436706,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -19.77.2,true,true,436703,European Paintings,Painting,"Madame Jacques-Louis Leblanc (Françoise Poncelle, 1788–1839)",,,,,,Artist,,Jean Auguste Dominique Ingres,"French, Montauban 1780–1867 Paris",,"Ingres, Jean Auguste Dominique",French,1780,1867,1823,1823,1823,Oil on canvas,47 x 36 1/2 in. (119.4 x 92.7 cm),"Catharine Lorillard Wolfe Collection, Wolfe Fund, 1918",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436703,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -43.85.2,false,true,436704,European Paintings,Painting,Edmond Cavé (1794–1852),,,,,,Artist,,Jean Auguste Dominique Ingres,"French, Montauban 1780–1867 Paris",,"Ingres, Jean Auguste Dominique",French,1780,1867,1844,1844,1844,Oil on canvas,16 x 12 7/8 in. (40.6 x 32.7 cm),"Bequest of Grace Rainey Rogers, 1943",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436704,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -43.85.3,false,true,436707,European Paintings,Painting,"Madame Edmond Cavé (Marie-Élisabeth Blavot, born 1810)",,,,,,Artist,,Jean Auguste Dominique Ingres,"French, Montauban 1780–1867 Paris",,"Ingres, Jean Auguste Dominique",French,1780,1867,ca. 1831–34,1826,1839,Oil on canvas,16 x 12 7/8 in. (40.6 x 32.7 cm),"Bequest of Grace Rainey Rogers, 1943",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436707,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.186,false,true,438434,European Paintings,Painting,The Virgin Adoring the Host,,,,,,Artist,,Jean Auguste Dominique Ingres,"French, Montauban 1780–1867 Paris",,"Ingres, Jean Auguste Dominique",French,1780,1867,1852,1852,1852,Oil on canvas,15 7/8 x 12 7/8 in. (40.3 x 32.7 cm),"Gift of Lila and Herman Shickman, 2005",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/438434,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.23,true,true,438818,European Paintings,Painting,Joseph-Antoine Moltedo (born 1775),,,,,,Artist,,Jean Auguste Dominique Ingres,"French, Montauban 1780–1867 Paris",,"Ingres, Jean Auguste Dominique",French,1780,1867,ca. 1810,1805,1815,Oil on canvas,29 5/8 x 22 7/8 in. (75.2 x 58.1 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/438818,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.100.6,false,true,436183,European Paintings,Painting,Departure of the Amazons,,,,,,Artist,,Claude Déruet,"French, Nancy ca. 1588–1660 Nancy",,"Déruet, Claude",French,1583,1660,1620s,1620,1629,Oil on canvas,20 x 26 in. (50.8 x 66 cm),"Bequest of Harry G. Sperling, 1971",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436183,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.100.7,false,true,436182,European Paintings,Painting,Triumph of the Amazons,,,,,,Artist,,Claude Déruet,"French, Nancy ca. 1588–1660 Nancy",,"Déruet, Claude",French,1583,1660,1620s,1620,1629,Oil on canvas,20 1/4 x 26 in. (51.4 x 66 cm),"Bequest of Harry G. Sperling, 1971",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436182,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -17.120.204,false,true,437903,European Paintings,Painting,Marie Joséphine Charlotte du Val d'Ognes (died 1868),,,,,,Artist,,Marie Denise Villers,"French, Paris 1774–1821 Paris (?)",,"Villers, Marie Denise",French,1774,1821,1801,1801,1801,Oil on canvas,63 1/2 x 50 5/8in. (161.3 x 128.6cm),"Mr. and Mrs. Isaac D. Fletcher Collection, Bequest of Isaac D. Fletcher, 1917",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437903,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -60.6,false,true,437464,European Paintings,Painting,Louis XV (1710–1774) at the Age of Five in the Costume of the Sacre,,,,,,Artist,,Hyacinthe Rigaud,"French, Perpignan 1659–1743 Paris",and Workshop,"Rigaud, Hyacinthe",French,1659,1743,ca. 1716–24,1716,1724,Oil on canvas,77 x 55 1/2 in. (195.6 x 141 cm),"Purchase, Mary Wetmore Shively Bequest, in memory of her husband, Henry L. Shively, M.D., 1960",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437464,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -59.119,false,true,437463,European Paintings,Painting,Portrait of a Military Officer,,,,,,Artist,,Hyacinthe Rigaud,"French, Perpignan 1659–1743 Paris",,"Rigaud, Hyacinthe",French,1659,1743,ca. 1710,1705,1715,Oil on canvas,54 x 41 3/8 in. (137.2 x 105.1 cm),"The Alfred N. Punnett Endowment Fund, 1959",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437463,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -48.187.733,false,true,437465,European Paintings,Painting,Portrait of a Man,,,,,,Artist,,Hyacinthe Rigaud,"French, Perpignan 1659–1743 Paris",,"Rigaud, Hyacinthe",French,1659,1743,1693,1693,1693,Oil on canvas,"Oval, 32 1/2 x 25 3/4 in. (82.6 x 65.4 cm)","Bequest of Catherine D. Wentworth, 1948",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437465,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -87.15.54,false,true,436644,European Paintings,Painting,A Bather (Echo),,,,,,Artist,,Jean-Jacques Henner,"French, Bernwiller 1829–1905 Paris",,"Henner, Jean-Jacques",French,1829,1905,1881,1881,1881,Oil on canvas,38 1/8 x 27 3/4 in. (96.8 x 70.5 cm),"Catharine Lorillard Wolfe Collection, Bequest of Catharine Lorillard Wolfe, 1887",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436644,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -87.25,true,true,435702,European Paintings,Painting,The Horse Fair,,,,,,Artist,,Rosa Bonheur,"French, Bordeaux 1822–1899 Thomery",,"Bonheur, Rosa",French,1822,1899,1852–55,1852,1855,Oil on canvas,96 1/4 x 199 1/2 in. (244.5 x 506.7 cm),"Gift of Cornelius Vanderbilt, 1887",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435702,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -87.15.77,false,true,435704,European Paintings,Painting,A Limier Briquet Hound,,,,,,Artist,,Rosa Bonheur,"French, Bordeaux 1822–1899 Thomery",,"Bonheur, Rosa",French,1822,1899,ca. 1856,1856,1856,Oil on canvas,14 1/2 x 18 in. (36.8 x 45.7 cm),"Catharine Lorillard Wolfe Collection, Bequest of Catharine Lorillard Wolfe, 1887",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435704,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -87.15.109,false,true,435703,European Paintings,Painting,Weaning the Calves,,,,,,Artist,,Rosa Bonheur,"French, Bordeaux 1822–1899 Thomery",,"Bonheur, Rosa",French,1822,1899,1879,1879,1879,Oil on canvas,25 5/8 x 32 in. (65.1 x 81.3 cm),"Catharine Lorillard Wolfe Collection, Bequest of Catharine Lorillard Wolfe, 1887",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435703,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2012.575,false,true,438158,European Paintings,Painting,Springtime,,,,,,Artist,,Pierre-Auguste Cot,"French, Bédarieux 1837–1883 Paris",,"Cot, Pierre-Auguste",French,1837,1883,1873,1873,1873,Oil on canvas,84 x 50 in. (213.4 x 127 cm),"Gift of Steven and Alexandra Cohen, 2012",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/438158,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -87.15.134,false,true,435997,European Paintings,Painting,The Storm,,,,,,Artist,,Pierre-Auguste Cot,"French, Bédarieux 1837–1883 Paris",,"Cot, Pierre-Auguste",French,1837,1883,1880,1880,1880,Oil on canvas,92 1/4 x 61 3/4 in. (234.3 x 156.8 cm),"Catharine Lorillard Wolfe Collection, Bequest of Catharine Lorillard Wolfe, 1887",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435997,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -47.12,false,true,435907,European Paintings,Painting,Sunrise,,,,,,Artist,,Claude Lorrain (Claude Gellée),"French, Chamagne 1604/5?–1682 Rome",,Claude Lorrain (Claude Gellée),French,1604,1682,possibly 1646–47,1646,1647,Oil on canvas,40 1/2 x 52 3/4 in. (102.9 x 134 cm),"Fletcher Fund, 1947",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435907,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -28.117,false,true,435905,European Paintings,Painting,The Ford,,,,,,Artist,,Claude Lorrain (Claude Gellée),"French, Chamagne 1604/5?–1682 Rome",,Claude Lorrain (Claude Gellée),French,1604,1682,possibly 1636,1636,1636,Oil on canvas,29 1/4 x 39 3/4 in. (74.3 x 101 cm),"Fletcher Fund, 1928",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435905,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -55.119,true,true,435908,European Paintings,Painting,The Trojan Women Setting Fire to Their Fleet,,,,,,Artist,,Claude Lorrain (Claude Gellée),"French, Chamagne 1604/5?–1682 Rome",,Claude Lorrain (Claude Gellée),French,1604,1682,ca. 1643,1643,1643,Oil on canvas,41 3/8 x 59 7/8 in. (105.1 x 152.1 cm),"Fletcher Fund, 1955",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435908,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1978.205,false,true,435909,European Paintings,Painting,View of La Crescenza,,,,,,Artist,,Claude Lorrain (Claude Gellée),"French, Chamagne 1604/5?–1682 Rome",,Claude Lorrain (Claude Gellée),French,1604,1682,1648–50,1648,1650,Oil on canvas,15 1/4 x 22 7/8 in. (38.7 x 58.1 cm),"Purchase, The Annenberg Fund Inc. Gift, 1978",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435909,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -65.181.12,false,true,435906,European Paintings,Painting,Pastoral Landscape: The Roman Campagna,,,,,,Artist,,Claude Lorrain (Claude Gellée),"French, Chamagne 1604/5?–1682 Rome",,Claude Lorrain (Claude Gellée),French,1604,1682,ca. 1639,1634,1644,Oil on canvas,40 x 53 1/2 in. (101.6 x 135.9 cm),"Bequest of Adele L. Lehman, in memory of Arthur Lehman, 1965",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435906,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -89.21.1,true,true,435621,European Paintings,Painting,Joan of Arc,,,,,,Artist,,Jules Bastien-Lepage,"French, Damvillers 1848–1884 Paris",,"Bastien-Lepage, Jules",French,1848,1884,1879,1879,1879,Oil on canvas,100 x 110 in. (254 x 279.4 cm),"Gift of Erwin Davis, 1889",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435621,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2012.156,false,true,438099,European Paintings,Painting,"The Public Viewing David’s ""Coronation"" at the Louvre",,,,,,Artist,,Louis Léopold Boilly,"French, La Bassée 1761–1845 Paris",,"Boilly, Louis Léopold",French,1761,1845,1810,1810,1810,Oil on canvas,24 1/4 x 32 1/2 in. (61.6 x 82.6 cm),"Gift of Mrs. Charles Wrightsman, 2012",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/438099,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -71.56,false,true,437328,European Paintings,Painting,Midas Washing at the Source of the Pactolus,,,,,,Artist,,Nicolas Poussin,"French, Les Andelys 1594–1665 Rome",,"Poussin, Nicolas",French,1594,1665,ca. 1627,1622,1632,Oil on canvas,38 3/8 x 28 5/8 in. (97.5 x 72.7 cm),"Purchase, 1871",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437328,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -46.160,true,true,437329,European Paintings,Painting,The Abduction of the Sabine Women,,,,,,Artist,,Nicolas Poussin,"French, Les Andelys 1594–1665 Rome",,"Poussin, Nicolas",French,1594,1665,probably 1633–34,1633,1634,Oil on canvas,60 7/8 x 82 5/8 in. (154.6 x 209.9 cm),"Harris Brisbane Dick Fund, 1946",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437329,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -24.45.1,true,true,437326,European Paintings,Painting,Blind Orion Searching for the Rising Sun,,,,,,Artist,,Nicolas Poussin,"French, Les Andelys 1594–1665 Rome",,"Poussin, Nicolas",French,1594,1665,1658,1658,1658,Oil on canvas,46 7/8 x 72 in. (119.1 x 182.9 cm),"Fletcher Fund, 1924",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437326,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -24.45.2,false,true,437330,European Paintings,Painting,Saints Peter and John Healing the Lame Man,,,,,,Artist,,Nicolas Poussin,"French, Les Andelys 1594–1665 Rome",,"Poussin, Nicolas",French,1594,1665,1655,1655,1655,Oil on canvas,49 1/2 x 65 in. (125.7 x 165.1 cm),"Marquand Fund, 1924",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437330,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1977.1.2,false,true,437327,European Paintings,Painting,The Companions of Rinaldo,,,,,,Artist,,Nicolas Poussin,"French, Les Andelys 1594–1665 Rome",,"Poussin, Nicolas",French,1594,1665,ca. 1633,1628,1638,Oil on canvas,46 1/2 x 40 1/4 in. (118.1 x 102.2 cm),"Gift of Mr. and Mrs. Charles Wrightsman, 1977",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437327,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1997.117.6,false,true,438025,European Paintings,Painting,The Rest on the Flight into Egypt,,,,,,Artist,,Nicolas Poussin,"French, Les Andelys 1594–1665 Rome",,"Poussin, Nicolas",French,1594,1665,ca. 1627,1622,1632,Oil on canvas,30 x 25 in. (76.2 x 63.5 cm),"Bequest of Lore Heinemann, in memory of her husband, Dr. Rudolf J. Heinemann, 1996",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/438025,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -15.30.20,false,true,437220,European Paintings,Painting,A Cavalryman,,,,,,Artist,,Alphonse-Marie-Adolphe de Neuville,"French, Saint-Omer 1835–1885 Paris",,"Neuville, Alphonse-Marie-Adolphe de",French,1835,1885,1884,1884,1884,Oil on canvas,18 1/8 x 15 in. (46 x 38.1 cm),"Bequest of Maria DeWitt Jesup, from the collection of her husband, Morris K. Jesup, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437220,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -25.110.26,false,true,437219,European Paintings,Painting,The Spy,,,,,,Artist,,Alphonse-Marie-Adolphe de Neuville,"French, Saint-Omer 1835–1885 Paris",,"Neuville, Alphonse-Marie-Adolphe de",French,1835,1885,1880,1880,1880,Oil on canvas,51 1/4 x 84 in. (130.2 x 213.4 cm),"Bequest of Collis P. Huntington, 1900",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437219,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -28.113,false,true,436207,European Paintings,Painting,Don Quixote and Sancho Panza Entertained by Basil and Quiteria,,,,,,Artist,,Gustave Doré,"French, Strasbourg 1832–1883 Paris",,"Doré, Gustave",French,1832,1883,1863?,1863,1863,Oil on canvas,36 1/4 x 28 3/4 in. (92.1 x 73 cm),"Gift of Mrs. William A. McFadden and Mrs. Giles Whiting, 1928",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436207,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -87.15.21,false,true,435774,European Paintings,Painting,A Peasant Girl Knitting,,,,,,Artist,,Jules Breton,"French, Courrières 1827–1906 Paris",,"Breton, Jules",French,1827,1906,ca. 1870,1865,1875,Oil on canvas,22 5/8 x 18 1/2 in. (57.5 x 47 cm),"Catharine Lorillard Wolfe Collection, Bequest of Catharine Lorillard Wolfe, 1887",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435774,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -25.110.66,false,true,435773,European Paintings,Painting,The Weeders,,,,,,Artist,,Jules Breton,"French, Courrières 1827–1906 Paris",,"Breton, Jules",French,1827,1906,1868,1868,1868,Oil on canvas,28 1/8 x 50 1/4 in. (71.4 x 127.6 cm),"Bequest of Collis P. Huntington, 1900",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435773,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1974.2,false,true,435755,European Paintings,Painting,The Baptism of Christ,,,,,,Artist,,Sébastien Bourdon,"French, Montpellier 1616–1671 Paris",,"Bourdon, Sébastien",French,1616,1671,ca. 1650,1645,1655,Oil on canvas,Overall 59 3/4 x 46 1/2 in. (151.8 x 118.1 cm); painted surface (oval) 59 1/8 x 45 1/2 in. (150.2 x 115.6 cm),"Purchase, George T. Delacorte Jr. Gift, 1974",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435755,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1985.90,false,true,435756,European Paintings,Painting,A Classical Landscape,,,,,,Artist,,Sébastien Bourdon,"French, Montpellier 1616–1671 Paris",,"Bourdon, Sébastien",French,1616,1671,probably 1660s,1660,1669,Oil on canvas,27 1/2 x 36 1/4 in. (69.9 x 92.1 cm),"Gift of Atwood A. Allaire, Pamela Askew, and Phoebe A. DesMarais, in memory of their mother, Constance Askew, 1985",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435756,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -94.24.1,false,true,435831,European Paintings,Painting,The Birth of Venus,,,,,,Artist,,Alexandre Cabanel,"French, Montpellier 1823–1889 Paris",,"Cabanel, Alexandre",French,1823,1889,1875,1875,1875,Oil on canvas,41 3/4 x 71 7/8 in. (106 x 182.6 cm),"Gift of John Wolfe, 1893",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435831,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -23.103.1,false,true,435832,European Paintings,Painting,Florentine Poet,,,,,,Artist,,Alexandre Cabanel,"French, Montpellier 1823–1889 Paris",,"Cabanel, Alexandre",French,1823,1889,1861,1861,1861,Oil on wood,12 x 19 7/8 in. (30.5 x 50.5 cm),"The John Hobart Warren Bequest, 1923",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435832,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -65.258.1,false,true,435829,European Paintings,Painting,Echo,,,,,,Artist,,Alexandre Cabanel,"French, Montpellier 1823–1889 Paris",,"Cabanel, Alexandre",French,1823,1889,1874,1874,1874,Oil on canvas,38 1/2 x 26 1/4 in. (97.8 x 66.7 cm),"Gift of Mary Phelps Smith, in memory of her husband, Howard Caswell Smith, 1965",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435829,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -87.15.82,false,true,435830,European Paintings,Painting,Catharine Lorillard Wolfe (1828–1887),,,,,,Artist,,Alexandre Cabanel,"French, Montpellier 1823–1889 Paris",,"Cabanel, Alexandre",French,1823,1889,1876,1876,1876,Oil on canvas,67 1/2 x 42 3/4 in. (171.5 x 108.6 cm),"Catharine Lorillard Wolfe Collection, Bequest of Catharine Lorillard Wolfe, 1887",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435830,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -32.100.102,false,true,435638,European Paintings,"Painting, triptych",The Le Cellier Altarpiece,,,,,,Artist,,Jean Bellegambe,"French, Douai ca. 1470–1535/36 Douai",,"Bellegambe, Jean",French,1470,1536,1509,1509,1509,Oil on wood,Shaped top: central panel 40 x 24 in. (101.6 x 61 cm); left wing 37 3/4 x 10 in. (95.9 x 25.4 cm); right wing 37 1/2 x 9 1/2 in. (95.3 x 24.1 cm),"The Friedsam Collection, Bequest of Michael Friedsam, 1931",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435638,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -32.100.125,false,true,435637,European Paintings,"Painting, wing of a triptych","Charles Coguin, Abbot of Anchin",,,,,,Artist,,Jean Bellegambe,"French, Douai ca. 1470–1535/36 Douai",,"Bellegambe, Jean",French,1470,1536,ca. 1509–13,1509,1513,Oil on wood,"Arched top, 26 3/4 x 11 3/8 in. (67.9 x 28.9 cm)","The Friedsam Collection, Bequest of Michael Friedsam, 1931",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435637,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -59.140,false,true,435751,European Paintings,Painting,Village by a River,,,,,,Artist,,Eugène Boudin,"French, Honfleur 1824–1898 Deauville",,"Boudin, Eugène",French,1824,1898,probably 1867,1867,1867,Oil on wood,14 x 23 in. (35.6 x 58.4 cm),"Gift of Arthur J. Neumark, 1959",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435751,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -07.88.4,false,true,435749,European Paintings,Painting,On the Beach at Trouville,,,,,,Artist,,Eugène Boudin,"French, Honfleur 1824–1898 Deauville",,"Boudin, Eugène",French,1824,1898,1863,1863,1863,Oil on wood,10 x 18 in. (25.4 x 45.7 cm),"Bequest of Amelia B. Lazarus, 1907",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435749,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -39.65.2,false,true,435750,European Paintings,Painting,Beaulieu: The Bay of Fourmis,,,,,,Artist,,Eugène Boudin,"French, Honfleur 1824–1898 Deauville",,"Boudin, Eugène",French,1824,1898,1892,1892,1892,Oil on canvas,21 5/8 x 35 1/2 in. (54.9 x 90.2 cm),"Bequest of Jacob Ruppert, 1939",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435750,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2003.20.1,false,true,437987,European Paintings,Painting,"On the Beach, Dieppe",,,,,,Artist,,Eugène Boudin,"French, Honfleur 1824–1898 Deauville",,"Boudin, Eugène",French,1824,1898,1864,1864,1864,Oil on wood,12 1/2 x 11 1/2 in. (31.8 x 29.2 cm),"The Walter H. and Leonore Annenberg Collection, Bequest of Walter H. Annenberg, 2002",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437987,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2003.20.2,false,true,438551,European Paintings,Painting,"On the Beach, Sunset",,,,,,Artist,,Eugène Boudin,"French, Honfleur 1824–1898 Deauville",,"Boudin, Eugène",French,1824,1898,1865,1865,1865,Oil on wood,15 x 23 in. (38.1 x 58.4 cm),"The Walter H. and Leonore Annenberg Collection, Bequest of Walter H. Annenberg, 2002",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/438551,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1999.288.1,false,true,437988,European Paintings,Painting,Princess Pauline Metternich (1836–1921) on the Beach,,,,,,Artist,,Eugène Boudin,"French, Honfleur 1824–1898 Deauville",,"Boudin, Eugène",French,1824,1898,ca. 1865–67,1844,1898,"Oil on cardboard, laid down on wood",11 5/8 x 9 1/4 in. (29.5 x 23.5 cm),"The Walter H. and Leonore Annenberg Collection, Gift of Walter H. and Leonore Annenberg, 1999, Bequest of Walter H. Annenberg, 2002",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437988,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -67.213,false,true,436241,European Paintings,Painting,Cows Crossing a Ford,,,,,,Artist,,Jules Dupré,"French, Nantes 1811–1889 L'Isle-Adam",,"Dupré, Jules",French,1811,1889,1836,1836,1836,Oil on canvas,14 1/4 x 24 5/8 in. (36.2 x 62.5 cm),"Gift of Mrs. Leon L. Watters, in memory of Leon Laizer Watters, 1967",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436241,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2002.68,false,true,438543,European Paintings,Painting,Still Life with Shells and a Chip-Wood Box,,,,,,Artist,,Sebastian Stoskopff,"French, Strasbourg 1597–1657 Idstein",,"Stoskopff, Sebastian",French,1597,1657,late 1620s,1626,1629,Oil on canvas,18 1/2 x 23 3/8 in. (47 x 59.4 cm),"Wrightsman Fund, 2002",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/438543,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.27,false,true,437256,European Paintings,Painting,Concert Champêtre,,,,,,Artist,,Jean-Baptiste Joseph Pater,"French, Valenciennes 1695–1736 Paris",,"Pater, Jean-Baptiste Joseph",French,1695,1736,ca. 1734,1729,1739,Oil on canvas,20 1/2 x 26 3/4 in. (52.1 x 67.9 cm),"Purchase, Joseph Pulitzer Bequest, 1937",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437256,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -49.7.52,false,true,437257,European Paintings,Painting,The Fair at Bezons,,,,,,Artist,,Jean-Baptiste Joseph Pater,"French, Valenciennes 1695–1736 Paris",,"Pater, Jean-Baptiste Joseph",French,1695,1736,ca. 1733,1728,1738,Oil on canvas,42 x 56 in. (106.7 x 142.2 cm),"The Jules Bache Collection, 1949",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437257,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -56.55.1,false,true,437260,European Paintings,Painting,Troops on the March,,,,,,Artist,,Jean-Baptiste Joseph Pater,"French, Valenciennes 1695–1736 Paris",,"Pater, Jean-Baptiste Joseph",French,1695,1736,ca. 1725,1720,1730,Oil on canvas,21 1/4 x 25 3/4 in. (54 x 65.4 cm),"Bequest of Ethel Tod Humphrys, 1956",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437260,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -56.55.2,false,true,437259,European Paintings,Painting,Troops at Rest,,,,,,Artist,,Jean-Baptiste Joseph Pater,"French, Valenciennes 1695–1736 Paris",,"Pater, Jean-Baptiste Joseph",French,1695,1736,ca. 1725,1720,1730,Oil on canvas,21 1/4 x 25 3/4 in. (54 x 65.4 cm),"Bequest of Ethel Tod Humphrys, 1956",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437259,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -63.120,false,true,436520,European Paintings,Painting,"Marie Adélaïde de Savoie (1685–1712), Duchesse de Bourgogne",,,,,,Artist,,Pierre Gobert,"French, Fontainebleau 1662–1744 Paris",,"Gobert, Pierre",French,1662,1744,1710,1710,1710,Oil on canvas,"Oval, 28 3/4 x 23 1/4 in. (73 x 59.1 cm)","Gift of the Marquis de La Bégassière, 1963",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436520,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -67.55.1,false,true,436222,European Paintings,Painting,Self-Portrait with a Harp,,,,,,Artist,,Rose Adélaïde Ducreux,"French, Paris 1761–1802 Santo Domingo",,"Ducreux, Rose Adélaïde",French,1761,1802,1791,1791,1791,Oil on canvas,76 x 50 3/4 in. (193 x 128.9 cm),"Bequest of Susan Dwight Bliss, 1966",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436222,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.160.36,false,true,436115,European Paintings,Painting,The Good Samaritan,,,,,,Artist,,Alexandre-Gabriel Decamps,"French, Paris 1803–1860 Fontainebleau",,"Decamps, Alexandre-Gabriel",French,1803,1860,by 1853,1845,1860,Oil on canvas,36 5/8 x 29 1/8 in. (93 x 74 cm),"H. O. Havemeyer Collection, Gift of Horace Havemeyer, 1929",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436115,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.196,false,true,436114,European Paintings,Painting,The Experts,,,,,,Artist,,Alexandre-Gabriel Decamps,"French, Paris 1803–1860 Fontainebleau",,"Decamps, Alexandre-Gabriel",French,1803,1860,1837,1837,1837,Oil on canvas,18 1/4 x 25 1/4 in. (46.4 x 64.1 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436114,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2014.736,false,true,671456,European Paintings,Painting,Chrysanthemums in the Garden at Petit-Gennevilliers,,,,,,Artist,,Gustave Caillebotte,"French, Paris 1848–1894 Gennevilliers",,"Caillebotte, Gustave",French,1848,1894,1893,1893,1893,Oil on canvas,38 5/8 × 23 1/2 in. (98 × 59.8 cm),"Gift of the Honorable John C. Whitehead, 2014",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/671456,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -55.35,false,true,435653,European Paintings,Painting,"Sunday at the Church of Saint-Philippe-du-Roule, Paris",,,,,,Artist,,Jean Béraud,"French, St. Petersburg 1849–1936 Paris",,"Béraud, Jean",French,1849,1936,1877,1877,1877,Oil on canvas,23 3/8 x 31 7/8 in. (59.4 x 81 cm),"Gift of Mr. and Mrs. William B. Jaffe, 1955",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435653,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -52.48.1,false,true,435654,European Paintings,Painting,A Windy Day on the Pont des Arts,,,,,,Artist,,Jean Béraud,"French, St. Petersburg 1849–1936 Paris",,"Béraud, Jean",French,1849,1936,ca. 1880–81,1880,1881,Oil on canvas,15 5/8 x 22 1/4 in. (39.7 x 56.5 cm),"Bequest of Eda K. Loeb, 1951",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435654,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -69.161,false,true,436235,European Paintings,Painting,Madame de Saint-Maurice,,,,,,Artist,,Joseph Siffred Duplessis,"French, Carpentras 1725–1802 Versailles",,"Duplessis, Joseph Siffred",French,1725,1802,1776,1776,1776,Oil on canvas,39 1/2 x 31 7/8 in. (100.3 x 81 cm),"Bequest of James A. Aborn, 1968",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436235,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -32.100.132,false,true,436236,European Paintings,Painting,Benjamin Franklin (1706–1790),,,,,,Artist,,Joseph Siffred Duplessis,"French, Carpentras 1725–1802 Versailles",,"Duplessis, Joseph Siffred",French,1725,1802,1778,1778,1778,Oil on canvas,"Oval, 28 1/2 x 23 in. (72.4 x 58.4 cm)","The Friedsam Collection, Bequest of Michael Friedsam, 1931",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436236,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2002.151,false,true,438510,European Paintings,Painting,The Outskirts of a Village,,,,,,Artist,,Edmond-François Aman-Jean,"French, Chevry-Cossigny 1858–1936 Paris",,"Aman-Jean, Edmond-François",French,1858,1936,ca. 1880,1875,1885,Oil on panel,4 7/8 x 8 9/16 in. (12.4 x 21.7 cm),"Catharine Lorillard Wolfe Collection, Wolfe Fund, 2002",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/438510,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -09.198,false,true,436092,European Paintings,Painting,Don Quixote and the Dead Mule,,,,,,Artist,,Honoré Daumier,"French, Marseilles 1808–1879 Valmondois",,"Daumier, Honoré",French,1808,1879,after 1864,1828,1879,Oil on wood,9 3/4 x 18 1/8 in. (24.8 x 46 cm),"Catharine Lorillard Wolfe Collection, Wolfe Fund, 1909",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436092,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -47.122,false,true,436091,European Paintings,Painting,The Laundress,,,,,,Artist,,Honoré Daumier,"French, Marseilles 1808–1879 Valmondois",,"Daumier, Honoré",French,1808,1879,186[3?],1863,1863,Oil on wood,19 1/4 x 13 in. (48.9 x 33 cm),"Bequest of Lillie P. Bliss, 1931",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436091,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -54.143.1,false,true,436093,European Paintings,Painting,The Drinkers,,,,,,Artist,,Honoré Daumier,"French, Marseilles 1808–1879 Valmondois",,"Daumier, Honoré",French,1808,1879,by 1861,1841,1861,Oil on wood,14 3/8 x 11 in. (36.5 x 27.9 cm),"Bequest of Margaret Seligman Lewisohn, in memory of her husband, Sam A. Lewisohn, 1954",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436093,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.129,true,true,436095,European Paintings,Painting,The Third-Class Carriage,,,,,,Artist,,Honoré Daumier,"French, Marseilles 1808–1879 Valmondois",,"Daumier, Honoré",French,1808,1879,ca. 1862–64,1862,1864,Oil on canvas,25 3/4 x 35 1/2 in. (65.4 x 90.2 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436095,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -56.183,false,true,437148,European Paintings,Painting,Flowers in a Blue Vase,,,,,,Artist,,Adolphe Monticelli,"French, Marseilles 1824–1886 Marseilles",,"Monticelli, Adolphe",French,1824,1886,1879–1883,1879,1883,Oil on wood,"Overall, with added strip at right, 26 1/2 x 19 1/4 in. (67.3 x 48.9 cm)","Gift of Mr. and Mrs. Werner E. Josten, 1956",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437148,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -87.15.107,false,true,437069,European Paintings,Painting,"Falling Leaves, Allegory of Autumn",,,,,,Artist,,Hugues Merle,"French, Saint-Marcellin 1823–1881 Paris",,"Merle, Hugues",French,1823,1881,1872,1872,1872,Oil on canvas,68 7/8 x 43 1/4 in. (174.9 x 109.9 cm),"Catharine Lorillard Wolfe Collection, Bequest of Catharine Lorillard Wolfe, 1887",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437069,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -59.21,false,true,437439,European Paintings,Painting,A Young Girl with Daisies,,,,,,Artist,,Auguste Renoir,"French, Limoges 1841–1919 Cagnes-sur-Mer",,"Renoir, Auguste",French,1841,1919,1889,1889,1889,Oil on canvas,25 5/8 x 21 1/4 in. (65.1 x 54 cm),"The Mr. and Mrs. Henry Ittleson Jr. Purchase Fund, 1959",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437439,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -07.122,true,true,438815,European Paintings,Painting,"Madame Georges Charpentier (Marguérite-Louise Lemonnier, 1848–1904) and Her Children, Georgette-Berthe (1872–1945) and Paul-Émile-Charles (1875–1895)",,,,,,Artist,,Auguste Renoir,"French, Limoges 1841–1919 Cagnes-sur-Mer",,"Renoir, Auguste",French,1841,1919,1878,1878,1878,Oil on canvas,60 1/2 x 74 7/8 in. (153.7 x 190.2 cm),"Catharine Lorillard Wolfe Collection, Wolfe Fund, 1907",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/438815,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -51.200,false,true,437424,European Paintings,Painting,"Madame Édouard Bernier (Marie-Octavie-Stéphanie Laurens, 1838–1920)",,,,,,Artist,,Auguste Renoir,"French, Limoges 1841–1919 Cagnes-sur-Mer",,"Renoir, Auguste",French,1841,1919,1871,1871,1871,Oil on canvas,30 3/4 x 24 1/2 in. (78.1 x 62.2 cm),"Gift of Margaret Seligman Lewisohn, in memory of her husband, Sam A. Lewisohn, and of her sister-in-law, Adele Lewisohn Lehman, 1951",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437424,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -56.218,false,true,437428,European Paintings,Painting,Still Life with Peaches and Grapes,,,,,,Artist,,Auguste Renoir,"French, Limoges 1841–1919 Cagnes-sur-Mer",,"Renoir, Auguste",French,1841,1919,1881,1881,1881,Oil on canvas,21 1/4 x 25 5/8 in. (54 x 65.1 cm),"The Mr. and Mrs. Henry Ittleson Jr. Purchase Fund, 1956",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437428,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -61.190,false,true,437433,European Paintings,Painting,"The Farm at Les Collettes, Cagnes",,,,,,Artist,,Auguste Renoir,"French, Limoges 1841–1919 Cagnes-sur-Mer",,"Renoir, Auguste",French,1841,1919,1908–14,1908,1914,Oil on canvas,21 1/2 x 25 3/4in. (54.6 x 65.4cm),"Bequest of Charlotte Gina Abrams, in memory of her husband, Lucien Abrams, 1961",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437433,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -64.150,false,true,437438,European Paintings,Painting,Young Girl in a Pink-and-Black Hat,,,,,,Artist,,Auguste Renoir,"French, Limoges 1841–1919 Cagnes-sur-Mer",,"Renoir, Auguste",French,1841,1919,ca. 1891,1886,1896,Oil on canvas,16 x 12 3/4 in. (40.6 x 32.4 cm),"Gift of Kathryn B. Miller, 1964",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437438,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2010.454,false,true,441104,European Paintings,Painting,Still Life with Flowers and Prickly Pears,,,,,,Artist,,Auguste Renoir,"French, Limoges 1841–1919 Cagnes-sur-Mer",,"Renoir, Auguste",French,1841,1919,ca. 1885,1880,1890,Oil on canvas,28 7/8 x 23 3/8 in. (73.3 x 59.4 cm),"Bequest of Catherine Vance Gaisman, 2010",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/441104,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -51.112.4,false,true,437434,European Paintings,Painting,In the Meadow,,,,,,Artist,,Auguste Renoir,"French, Limoges 1841–1919 Cagnes-sur-Mer",,"Renoir, Auguste",French,1841,1919,1888–92,1888,1892,Oil on canvas,32 x 25 3/4 in. (81.3 x 65.4 cm),"Bequest of Sam A. Lewisohn, 1951",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437434,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -56.135.7,false,true,437426,European Paintings,Painting,View of the Seacoast near Wargemont in Normandy,,,,,,Artist,,Auguste Renoir,"French, Limoges 1841–1919 Cagnes-sur-Mer",,"Renoir, Auguste",French,1841,1919,1880,1880,1880,Oil on canvas,19 7/8 x 24 1/2 in. (50.5 x 62.2 cm),"Bequest of Julia W. Emmons, 1956",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437426,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -56.135.8,false,true,437427,European Paintings,Painting,The Bay of Naples,,,,,,Artist,,Auguste Renoir,"French, Limoges 1841–1919 Cagnes-sur-Mer",,"Renoir, Auguste",French,1841,1919,1881,1881,1881,Oil on canvas,23 1/2 x 32 in. (59.7 x 81.3 cm),"Bequest of Julia W. Emmons, 1956",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437427,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -56.135.9,false,true,437431,European Paintings,Painting,"Hills around the Bay of Moulin Huet, Guernsey",,,,,,Artist,,Auguste Renoir,"French, Limoges 1841–1919 Cagnes-sur-Mer",,"Renoir, Auguste",French,1841,1919,1883,1883,1883,Oil on canvas,18 1/8 x 25 3/4 in. (46 x 65.4 cm),"Bequest of Julia W. Emmons, 1956",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437431,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2002.62.2,false,true,438010,European Paintings,Painting,Nini in the Garden (Nini Lopez),,,,,,Artist,,Auguste Renoir,"French, Limoges 1841–1919 Cagnes-sur-Mer",,"Renoir, Auguste",French,1841,1919,1876,1876,1876,Oil on canvas,24 3/8 x 20 in. (61.9 x 50.8 cm),"The Walter H. and Leonore Annenberg Collection, Gift of Walter H. and Leonore Annenberg, 2002, Bequest of Walter H. Annenberg, 2002",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/438010,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2003.20.9,false,true,438011,European Paintings,Painting,"Eugène Murer (Hyacinthe-Eugène Meunier, 1841–1906)",,,,,,Artist,,Auguste Renoir,"French, Limoges 1841–1919 Cagnes-sur-Mer",,"Renoir, Auguste",French,1841,1919,1877,1877,1877,Oil on canvas,18 1/2 x 15 1/2 in. (47 x 39.4 cm),"The Walter H. and Leonore Annenberg Collection, Bequest of Walter H. Annenberg, 2002",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/438011,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -61.101.12,false,true,437429,European Paintings,Painting,Still Life with Peaches,,,,,,Artist,,Auguste Renoir,"French, Limoges 1841–1919 Cagnes-sur-Mer",,"Renoir, Auguste",French,1841,1919,1881,1881,1881,Oil on canvas,21 x 25 1/2 in. (53.3 x 64.8 cm),"Bequest of Stephen C. Clark, 1960",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437429,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -61.101.13,false,true,437432,European Paintings,Painting,"Tilla Durieux (Ottilie Godeffroy, 1880–1971)",,,,,,Artist,,Auguste Renoir,"French, Limoges 1841–1919 Cagnes-sur-Mer",,"Renoir, Auguste",French,1841,1919,1914,1914,1914,Oil on canvas,36 1/4 x 29 in. (92.1 x 73.7 cm),"Bequest of Stephen C. Clark, 1960",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437432,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -61.101.14,false,true,437437,European Paintings,Painting,A Waitress at Duval's Restaurant,,,,,,Artist,,Auguste Renoir,"French, Limoges 1841–1919 Cagnes-sur-Mer",,"Renoir, Auguste",French,1841,1919,ca. 1875,1870,1880,Oil on canvas,39 1/2 x 28 1/8 in. (100.3 x 71.4 cm),"Bequest of Stephen C. Clark, 1960",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437437,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -61.101.15,false,true,437425,European Paintings,Painting,Marguerite-Thérèse (Margot) Berard (1874–1956),,,,,,Artist,,Auguste Renoir,"French, Limoges 1841–1919 Cagnes-sur-Mer",,"Renoir, Auguste",French,1841,1919,1879,1879,1879,Oil on canvas,16 1/8 x 12 3/4 in. (41 x 32.4 cm),"Bequest of Stephen C. Clark, 1960",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437425,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2003.20.10,false,true,438012,European Paintings,Painting,Bouquet of Chrysanthemums,,,,,,Artist,,Auguste Renoir,"French, Limoges 1841–1919 Cagnes-sur-Mer",,"Renoir, Auguste",French,1841,1919,1881,1881,1881,Oil on canvas,26 x 21 7/8 in. (66 x 55.6 cm),"The Walter H. and Leonore Annenberg Collection, Bequest of Walter H. Annenberg, 2002",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/438012,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2003.20.12,false,true,438013,European Paintings,Painting,Reclining Nude,,,,,,Artist,,Auguste Renoir,"French, Limoges 1841–1919 Cagnes-sur-Mer",,"Renoir, Auguste",French,1841,1919,1883,1883,1883,Oil on canvas,25 5/8 x 32 in. (65.1 x 81.3 cm),"The Walter H. and Leonore Annenberg Collection, Bequest of Walter H. Annenberg, 2002",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/438013,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.125,false,true,437430,European Paintings,Painting,By the Seashore,,,,,,Artist,,Auguste Renoir,"French, Limoges 1841–1919 Cagnes-sur-Mer",,"Renoir, Auguste",French,1841,1919,1883,1883,1883,Oil on canvas,36 1/4 x 28 1/2 in. (92.1 x 72.4 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437430,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1974.356.32,false,true,437436,European Paintings,Painting,A Road in Louveciennes,,,,,,Artist,,Auguste Renoir,"French, Limoges 1841–1919 Cagnes-sur-Mer",,"Renoir, Auguste",French,1841,1919,ca. 1870,1865,1875,Oil on canvas,15 x 18 1/4 in. (38.1 x 46.4 cm),"The Lesley and Emma Sheafer Collection, Bequest of Emma A. Sheafer, 1973",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437436,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1987.279,false,true,437180,European Paintings,Painting,The Rebuke of Adam and Eve,,,,,,Artist,,Charles Joseph Natoire,"French, Nîmes 1700–1777 Castel Gandolfo",,"Natoire, Charles Joseph",French,1700,1777,1740,1740,1740,Oil on copper,26 3/4 x 19 3/4 in. (67.9 x 50.2 cm),"Purchase, Mr. and Mrs. Frank E. Richardson III, George T. Delacorte Jr., and Mr. and Mrs. Henry J. Heinz II Gifts; Victor Wilbour Memorial, Marquand, and The Alfred N. Punnett Endowment Funds; and The Edward Joseph Gallagher III Memorial Collection, Edward J. Gallagher Jr. Bequest, 1987",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437180,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -87.15.22,false,true,436030,European Paintings,Painting,Soap Bubbles,,,,,,Artist,,Thomas Couture,"French, Senlis 1815–1879 Villiers-le-Bel",,"Couture, Thomas",French,1815,1879,ca. 1859,1859,1859,Oil on canvas,51 1/2 x 38 5/8 in. (130.8 x 98.1 cm),"Catharine Lorillard Wolfe Collection, Bequest of Catharine Lorillard Wolfe, 1887",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436030,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -60.122,false,true,435753,European Paintings,Painting,The Proposal,,,,,,Artist,,William Bouguereau,"French, La Rochelle 1825–1905 La Rochelle",,"Bouguereau, William",French,1825,1905,1872,1872,1872,Oil on canvas,64 3/8 x 44 in. (163.5 x 111.8 cm),"Gift of Mrs. Elliot L. Kamen, in memory of her father, Bernard R. Armour, 1960",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435753,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1993.402,false,true,435752,European Paintings,Painting,Young Mother Gazing at Her Child,,,,,,Artist,,William Bouguereau,"French, La Rochelle 1825–1905 La Rochelle",,"Bouguereau, William",French,1825,1905,1871,1871,1871,Oil on canvas,56 x 40 1/2 in. (142.2 x 102.9 cm),"Bequest of Zene Montgomery Pyle, 1993",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435752,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -87.15.32,false,true,435754,European Paintings,Painting,Breton Brother and Sister,,,,,,Artist,,William Bouguereau,"French, La Rochelle 1825–1905 La Rochelle",,"Bouguereau, William",French,1825,1905,1871,1871,1871,Oil on canvas,50 7/8 x 35 1/8 in. (129.2 x 89.2 cm),"Catharine Lorillard Wolfe Collection, Bequest of Catharine Lorillard Wolfe, 1887",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435754,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1998.170,false,true,438098,European Paintings,Painting,Tea,,,,,,Artist,,James Tissot,"French, Nantes 1836–1902 Chenecey-Buillon",,"Tissot, James",French,1836,1902,1872,1872,1872,Oil on wood,26 x 18 7/8 in. (66 x 47.9 cm),"Gift of Mrs. Charles Wrightsman, 1998",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/438098,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2006.278,false,true,438887,European Paintings,Painting,In Full Sunlight (En plein soleil),,,,,,Artist,,James Tissot,"French, Nantes 1836–1902 Chenecey-Buillon",,"Tissot, James",French,1836,1902,ca. 1881,1876,1886,Oil on wood,9 3/4 x 13 7/8 in. (24.8 x 35.2 cm),"Gift of Mrs. Charles Wrightsman, 2006",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/438887,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.359,false,true,440729,European Paintings,Painting,Spring Morning,,,,,,Artist,,James Tissot,"French, Nantes 1836–1902 Chenecey-Buillon",,"Tissot, James",French,1836,1902,ca. 1875,1870,1880,Oil on canvas,22 x 16 3/4 in. (55.9 x 42.5 cm),"Gift of Mrs. Charles Wrightsman, 2009",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/440729,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -19.84,false,true,436015,European Paintings,Painting,Louis Gueymard (1822–1880) as Robert le Diable,,,,,,Artist,,Gustave Courbet,"French, Ornans 1819–1877 La Tour-de-Peilz",,"Courbet, Gustave",French,1819,1877,1857,1857,1857,Oil on canvas,58 1/2 x 42 in. (148.6 x 106.7 cm),"Gift of Elizabeth Milbank Anderson, 1919",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436015,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.77,false,true,436014,European Paintings,Painting,Hunting Dogs with Dead Hare,,,,,,Artist,,Gustave Courbet,"French, Ornans 1819–1877 La Tour-de-Peilz",,"Courbet, Gustave",French,1819,1877,1857,1857,1857,Oil on canvas,36 1/2 x 58 1/2 in. (92.7 x 148.6 cm),"H. O. Havemeyer Collection, Gift of Horace Havemeyer, 1933",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436014,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -40.175,false,true,438820,European Paintings,Painting,Young Ladies of the Village,,,,,,Artist,,Gustave Courbet,"French, Ornans 1819–1877 La Tour-de-Peilz",,"Courbet, Gustave",French,1819,1877,1851–52,1851,1852,Oil on canvas,76 3/4 x 102 3/4 in. (194.9 x 261 cm),"Gift of Harry Payne Bingham, 1940",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/438820,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -22.27.1,false,true,436021,European Paintings,Painting,The Sea,,,,,,Artist,,Gustave Courbet,"French, Ornans 1819–1877 La Tour-de-Peilz",,"Courbet, Gustave",French,1819,1877,1865 or later,1865,1877,Oil on canvas,20 x 24 in. (50.8 x 61 cm),"Purchase, Dikran G. Kelekian Gift, 1922",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436021,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -99.11.3,false,true,436012,European Paintings,Painting,The Fishing Boat,,,,,,Artist,,Gustave Courbet,"French, Ornans 1819–1877 La Tour-de-Peilz",,"Courbet, Gustave",French,1819,1877,1865,1839,1877,Oil on canvas,25 1/2 x 32 in. (64.8 x 81.3 cm),"Gift of Mary Goldenberg, 1899",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436012,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1995.537,false,true,436025,European Paintings,Painting,View of Ornans,,,,,,Artist,,Gustave Courbet,"French, Ornans 1819–1877 La Tour-de-Peilz",,"Courbet, Gustave",French,1819,1877,probably mid-1850s,1839,1877,Oil on canvas,28 3/4 x 36 1/4 in. (73 x 92.1 cm),"Bequest of Alice Tully, 1993",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436025,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -22.16.13,false,true,436013,European Paintings,Painting,The Hidden Brook,,,,,,Artist,,Gustave Courbet,"French, Ornans 1819–1877 La Tour-de-Peilz",,"Courbet, Gustave",French,1819,1877,ca. 1873–77,1839,1877,Oil on canvas,23 3/8 x 29 3/4 in. (59.4 x 75.6 cm),"From the Collection of James Stillman, Gift of Dr. Ernest G. Stillman, 1922",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436013,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -22.16.14,false,true,436020,European Paintings,Painting,River and Rocks,,,,,,Artist,,Gustave Courbet,"French, Ornans 1819–1877 La Tour-de-Peilz",,"Courbet, Gustave",French,1819,1877,1873–77,1873,1877,Oil on canvas,19 5/8 x 23 7/8 in. (49.8 x 60.6 cm),"From the Collection of James Stillman, Gift of Dr. Ernest G. Stillman, 1922",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436020,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -22.16.15,false,true,436009,European Paintings,Painting,"A Brook in a Clearing (possibly ""Brook, Valley of Fontcouverte; Study"")",,,,,,Artist,,Gustave Courbet,"French, Ornans 1819–1877 La Tour-de-Peilz",,"Courbet, Gustave",French,1819,1877,probably 1862,1862,1862,Oil on canvas,20 3/4 x 25 1/2 in. (52.7 x 64.8 cm),"From the Collection of James Stillman, Gift of Dr. Ernest G. Stillman, 1922",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436009,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.57,true,true,436002,European Paintings,Painting,Woman with a Parrot,,,,,,Artist,,Gustave Courbet,"French, Ornans 1819–1877 La Tour-de-Peilz",,"Courbet, Gustave",French,1819,1877,1866,1866,1866,Oil on canvas,51 x 77 in. (129.5 x 195.6 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436002,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.58,false,true,436022,European Paintings,Painting,The Source,,,,,,Artist,,Gustave Courbet,"French, Ornans 1819–1877 La Tour-de-Peilz",,"Courbet, Gustave",French,1819,1877,1862,1862,1862,Oil on canvas,47 1/4 x 29 1/4 in. (120 x 74.3 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436022,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.59,false,true,436024,European Paintings,Painting,Woman in a Riding Habit (L'Amazone),,,,,,Artist,,Gustave Courbet,"French, Ornans 1819–1877 La Tour-de-Peilz",,"Courbet, Gustave",French,1819,1877,1856,1856,1856,Oil on canvas,45 1/2 x 35 1/8 in. (115.6 x 89.2 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436024,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.60,false,true,436018,European Paintings,Painting,Nude with Flowering Branch,,,,,,Artist,,Gustave Courbet,"French, Ornans 1819–1877 La Tour-de-Peilz",,"Courbet, Gustave",French,1819,1877,1863,1863,1863,Oil on canvas,29 1/2 x 24 in. (74.9 x 61 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436018,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.61,false,true,436007,European Paintings,Painting,After the Hunt,,,,,,Artist,,Gustave Courbet,"French, Ornans 1819–1877 La Tour-de-Peilz",,"Courbet, Gustave",French,1819,1877,ca. 1859,1859,1859,Oil on canvas,93 x 73 1/4 in. (236.2 x 186.1 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436007,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.62,false,true,436004,European Paintings,Painting,The Woman in the Waves,,,,,,Artist,,Gustave Courbet,"French, Ornans 1819–1877 La Tour-de-Peilz",,"Courbet, Gustave",French,1819,1877,1868,1868,1868,Oil on canvas,25 3/4 x 21 1/4 in. (65.4 x 54 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436004,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.63,false,true,436001,European Paintings,Painting,"Jo, La Belle Irlandaise",,,,,,Artist,,Gustave Courbet,"French, Ornans 1819–1877 La Tour-de-Peilz",,"Courbet, Gustave",French,1819,1877,1865–66,1865,1866,Oil on canvas,22 x 26 in. (55.9 x 66 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436001,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.160.34,false,true,436011,European Paintings,Painting,The Deer,,,,,,Artist,,Gustave Courbet,"French, Ornans 1819–1877 La Tour-de-Peilz",,"Courbet, Gustave",French,1819,1877,ca. 1865,1860,1870,Oil on canvas,29 3/8 x 36 3/8 in. (74.6 x 92.4 cm),"H. O. Havemeyer Collection, Gift of Horace Havemeyer, 1929",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436011,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.160.35,false,true,436006,European Paintings,Painting,Marine: The Waterspout,,,,,,Artist,,Gustave Courbet,"French, Ornans 1819–1877 La Tour-de-Peilz",,"Courbet, Gustave",French,1819,1877,1870,1870,1870,Oil on canvas,27 1/8 x 39 1/4 in. (68.9 x 99.7 cm),"H. O. Havemeyer Collection, Gift of Horace Havemeyer, 1929",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436006,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.118,false,true,436000,European Paintings,Painting,Madame de Brayer,,,,,,Artist,,Gustave Courbet,"French, Ornans 1819–1877 La Tour-de-Peilz",,"Courbet, Gustave",French,1819,1877,1858,1858,1858,Oil on canvas,36 x 28 5/8 in. (91.4 x 72.7 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436000,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.120,false,true,436017,European Paintings,Painting,Charles Suisse,,,,,,Artist,,Gustave Courbet,"French, Ornans 1819–1877 La Tour-de-Peilz",,"Courbet, Gustave",French,1819,1877,1861,1861,1861,Oil on canvas,23 1/4 x 19 3/8 in. (59.1 x 49.2 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436017,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.122,false,true,436023,European Paintings,Painting,The Source of the Loue,,,,,,Artist,,Gustave Courbet,"French, Ornans 1819–1877 La Tour-de-Peilz",,"Courbet, Gustave",French,1819,1877,1864,1864,1864,Oil on canvas,39 1/4 x 56 in. (99.7 x 142.2 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436023,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.124,false,true,436003,European Paintings,Painting,The Young Bather,,,,,,Artist,,Gustave Courbet,"French, Ornans 1819–1877 La Tour-de-Peilz",,"Courbet, Gustave",French,1819,1877,1866,1866,1866,Oil on canvas,51 1/4 x 38 1/4 in. (130.2 x 97.2 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436003,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.130,false,true,436016,European Paintings,Painting,"Madame Auguste Cuoq (Mathilde Desportes, 1827–1910)",,,,,,Artist,,Gustave Courbet,"French, Ornans 1819–1877 La Tour-de-Peilz",,"Courbet, Gustave",French,1819,1877,ca. 1852–57,1852,1857,Oil on canvas,69 1/2 x 42 1/2 in. (176.5 x 108 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436016,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.132,false,true,436008,European Paintings,Painting,Alphonse Promayet (1822–1872),,,,,,Artist,,Gustave Courbet,"French, Ornans 1819–1877 La Tour-de-Peilz",,"Courbet, Gustave",French,1819,1877,1851,1851,1851,Oil on canvas,42 1/8 x 27 5/8 in. (107 x 70.2 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436008,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.201,false,true,436019,European Paintings,Painting,Portrait of a Man,,,,,,Artist,,Gustave Courbet,"French, Ornans 1819–1877 La Tour-de-Peilz",,"Courbet, Gustave",French,1819,1877,probably ca. 1862,1857,1867,Oil on canvas,16 1/4 x 13 1/8 in. (41.3 x 33.3 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436019,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.566,false,true,436005,European Paintings,Painting,The Calm Sea,,,,,,Artist,,Gustave Courbet,"French, Ornans 1819–1877 La Tour-de-Peilz",,"Courbet, Gustave",French,1819,1877,1869,1869,1869,Oil on canvas,23 1/2 x 28 3/4 in. (59.7 x 73 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436005,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -46.68,false,true,435914,European Paintings,Painting,Guillaume Budé (1467–1540),,,,,,Artist,,Jean Clouet,"French, active by 1516–died 1540/41 Paris",,"Clouet, Jean",French,1516,1541,ca. 1536,1531,1541,Oil on wood,15 5/8 x 13 1/2 in. (39.7 x 34.3 cm),"Maria DeWitt Jesup Fund, 1946",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435914,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2008.459,true,true,439933,European Paintings,Painting,Lute Player,,,,,,Artist,,Valentin de Boulogne,"French, Coulommiers-en-Brie 1591–1632 Rome",,Valentin de Boulogne,French,1591,1632,ca. 1625–26,1625,1626,Oil on canvas,50 1/2 x 39 in. (128.3 x 99.1 cm),"Purchase, Walter and Leonore Annenberg Acquisitions Endowment Fund; Director's Fund; Acquisitions Fund; James and Diane Burke and Mr. and Mrs. Mark Fisch Gifts; Louis V. Bell, Harris Brisbane Dick, Fletcher, and Rogers Funds and Joseph Pulitzer Bequest, 2008",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/439933,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -51.33.2,true,true,437837,European Paintings,Painting,The Sofa,,,,,,Artist,,Henri de Toulouse-Lautrec,"French, Albi 1864–1901 Saint-André-du-Bois",,"Toulouse-Lautrec, Henri de",French,1864,1901,ca. 1894–96,1894,1896,Oil on cardboard,24 3/4 x 31 7/8 in. (62.9 x 81 cm),"Rogers Fund, 1951",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437837,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2003.20.13,false,true,438016,European Paintings,Painting,The Streetwalker,,,,,,Artist,,Henri de Toulouse-Lautrec,"French, Albi 1864–1901 Saint-André-du-Bois",,"Toulouse-Lautrec, Henri de",French,1864,1901,ca. 1890–91,1890,1891,Oil on cardboard,25 1/2 x 21 in. (64.8 x 53.3 cm),"The Walter H. and Leonore Annenberg Collection, Bequest of Walter H. Annenberg, 2002",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/438016,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2003.20.15,false,true,438018,European Paintings,Painting,Woman before a Mirror,,,,,,Artist,,Henri de Toulouse-Lautrec,"French, Albi 1864–1901 Saint-André-du-Bois",,"Toulouse-Lautrec, Henri de",French,1864,1901,1897,1897,1897,Oil on cardboard,24 1/2 x 18 1/2 in. (62.2 x 47 cm),"The Walter H. and Leonore Annenberg Collection, Bequest of Walter H. Annenberg, 2002",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/438018,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -67.187.108,false,true,437835,European Paintings,Painting,"The Englishman (William Tom Warrener, 1861–1934) at the Moulin Rouge",,,,,,Artist,,Henri de Toulouse-Lautrec,"French, Albi 1864–1901 Saint-André-du-Bois",,"Toulouse-Lautrec, Henri de",French,1864,1901,1892,1892,1892,Oil on cardboard,33 3/4 x 26 in. (85.7 x 66 cm),"Bequest of Miss Adelaide Milton de Groot (1876–1967), 1967",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437835,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1974.356.35,false,true,437839,European Paintings,Painting,Émilie,,,,,,Artist,,Henri de Toulouse-Lautrec,"French, Albi 1864–1901 Saint-André-du-Bois",,"Toulouse-Lautrec, Henri de",French,1864,1901,late 1890s,1897,1899,Oil on wood,16 1/4 x 12 3/4 in. (41.3 x 32.4 cm),"The Lesley and Emma Sheafer Collection, Bequest of Emma A. Sheafer, 1973",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437839,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1974.356.36,false,true,437834,European Paintings,Painting,Mademoiselle Nys,,,,,,Artist,,Henri de Toulouse-Lautrec,"French, Albi 1864–1901 Saint-André-du-Bois",,"Toulouse-Lautrec, Henri de",French,1864,1901,1899,1899,1899,Oil on unprimed wood,10 5/8 x 8 5/8 in. (27 x 21.9 cm),"The Lesley and Emma Sheafer Collection, Bequest of Emma A. Sheafer, 1973",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437834,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.201.15,false,true,437838,European Paintings,Painting,Woman in the Garden of Monsieur Forest,,,,,,Artist,,Henri de Toulouse-Lautrec,"French, Albi 1864–1901 Saint-André-du-Bois",,"Toulouse-Lautrec, Henri de",French,1864,1901,1889–91,1889,1891,Oil on canvas,21 7/8 x 18 1/4 in. (55.6 x 46.4 cm),"Bequest of Joan Whitney Payson, 1975",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437838,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1979.135.14,false,true,437836,European Paintings,Painting,Albert (René) Grenier (1858–1925),,,,,,Artist,,Henri de Toulouse-Lautrec,"French, Albi 1864–1901 Saint-André-du-Bois",,"Toulouse-Lautrec, Henri de",French,1864,1901,1887,1887,1887,Oil on wood,13 3/8 x 10 in. (34 x 25.4 cm),"Bequest of Mary Cushing Fosburgh, 1978",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437836,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -80.8,false,true,435709,European Paintings,Painting,John Taylor Johnston (1820–1893),,,,,,Artist,,Léon Bonnat,"French, Bayonne 1833–1922 Monchy-Saint-Eloi",,"Bonnat, Léon",French,1833,1922,1880,1880,1880,Oil on canvas,52 1/2 x 44 in. (133.4 x 111.8 cm),"Gift of the Trustees, 1880",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435709,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -87.15.97,false,true,435711,European Paintings,Painting,An Egyptian Peasant Woman and Her Child,,,,,,Artist,,Léon Bonnat,"French, Bayonne 1833–1922 Monchy-Saint-Eloi",,"Bonnat, Léon",French,1833,1922,1869–70,1869,1870,Oil on canvas,73 1/2 x 41 1/2 in. (186.7 x 105.4 cm),"Catharine Lorillard Wolfe Collection, Bequest of Catharine Lorillard Wolfe, 1887",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435711,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -87.15.137,false,true,435708,European Paintings,Painting,Roman Girl at a Fountain,,,,,,Artist,,Léon Bonnat,"French, Bayonne 1833–1922 Monchy-Saint-Eloi",,"Bonnat, Léon",French,1833,1922,1875,1875,1875,Oil on canvas,67 x 39 1/2 in. (170.2 x 100.3 cm),"Catharine Lorillard Wolfe Collection, Bequest of Catharine Lorillard Wolfe, 1887",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435708,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -23.103.2,false,true,436419,European Paintings,Painting,The Arab Falconer,,,,,,Artist,,Eugène Fromentin,"French, La Rochelle 1820–1876 Saint-Maurice",,"Fromentin, Eugène",French,1820,1876,1864,1864,1864,Oil on canvas,42 3/4 x 28 1/2 in. (108.6 x 72.4 cm),"The John Hobart Warren Bequest, 1923",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436419,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -87.15.64,false,true,436420,European Paintings,Painting,Arabs Crossing a Ford,,,,,,Artist,,Eugène Fromentin,"French, La Rochelle 1820–1876 Saint-Maurice",,"Fromentin, Eugène",French,1820,1876,1873,1873,1873,Oil on wood,20 x 24 1/2 in. (50.8 x 62.2 cm),"Catharine Lorillard Wolfe Collection, Bequest of Catharine Lorillard Wolfe, 1887",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436420,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2012.542,false,true,442356,European Paintings,Painting,"Still Life with Silver and Gold Plate, Shells, and a Sword",,,,,,Artist,,Meiffren Conte,"French, Marseilles ca. 1630–1705 Marseilles",,"Conte, Meiffren",French,1625,1705,fourth quarter 17th century,1675,1699,Oil on canvas,40 × 50 3/4 in. (101.6 × 128.9 cm),"Gift of Mrs. Russell B. Aitken, 2012",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/442356,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.166,false,true,439120,European Paintings,Painting,The Mass of Saint Basil,,,,,,Artist,,Pierre Hubert Subleyras,"French, Saint-Gilles-du-Gard 1699–1749 Rome",,"Subleyras, Pierre Hubert",French,1699,1749,1746,1746,1746,"Oil on canvas, transferred from canvas",54 x 31 1/8 in. (137 x 79 cm),"Wrightsman Fund, 2007",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/439120,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.145,false,true,440464,European Paintings,Painting,"Pope Benedict XIV (Prospero Lambertini, 1675–1758)",,,,,,Artist,,Pierre Hubert Subleyras,"French, Saint-Gilles-du-Gard 1699–1749 Rome",,"Subleyras, Pierre Hubert",French,1699,1749,1746,1746,1746,Oil on canvas,25 1/4 x 19 1/4 in. (64.1 x 48.9 cm),"Purchase, Friends of European Paintings Gifts, Bequest of Joan Whitney Payson, by exchange, Gwynne Andrews Fund, Charles and Jessie Price Gift, and Valerie Delacorte Fund Gift, in memory of George T. Delacorte, 2009",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/440464,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -86.6,false,true,436632,European Paintings,Painting,Moonrise,,,,,,Artist,,Henri-Joseph Harpignies,"French, Valenciennes 1819–1916 Saint-Privé",,"Harpignies, Henri-Joseph",French,1819,1916,1885,1885,1885,Oil on canvas,34 1/2 x 64 1/4 in. (87.6 x 163.2 cm),"Gift of Arnold and Tripp, 1886",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436632,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -60.30,true,true,436838,European Paintings,Painting,The Fortune-Teller,,,,,,Artist,,Georges de La Tour,"French, Vic-sur-Seille 1593–1653 Lunéville",,"La Tour, Georges de",French,1593,1653,probably 1630s,1630,1639,Oil on canvas,40 1/8 x 48 5/8 in. (101.9 x 123.5 cm),"Rogers Fund, 1960",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436838,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1978.517,false,true,436839,European Paintings,Painting,The Penitent Magdalen,,,,,,Artist,,Georges de La Tour,"French, Vic-sur-Seille 1593–1653 Lunéville",,"La Tour, Georges de",French,1593,1653,ca. 1640,1635,1645,Oil on canvas,52 1/2 x 40 1/4 in. (133.4 x 102.2 cm),"Gift of Mr. and Mrs. Charles Wrightsman, 1978",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436839,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2011.597,false,true,441755,European Paintings,Painting,Landscape Study with Clouds,,,,,,Artist,,Émile Loubon,"French, Aix-en-Provence 1809–1863 Marseilles",,"Loubon, Émile",French,1809,1863,ca. 1829–31,1829,1831,Oil on cardboard,5 7/8 x 9 1/16 in. (15 x 23 cm),"Catharine Lorillard Wolfe Collection, Wolfe Fund, 2011",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/441755,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -03.30,true,true,438814,European Paintings,Painting,The Abduction of Rebecca,,,,,,Artist,,Eugène Delacroix,"French, Charenton-Saint-Maurice 1798–1863 Paris",,"Delacroix, Eugène",French,1798,1863,1846,1846,1846,Oil on canvas,39 1/2 x 32 1/4 in. (100.3 x 81.9 cm),"Catharine Lorillard Wolfe Collection, Wolfe Fund, 1903",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/438814,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -22.27.4,false,true,436177,European Paintings,Painting,George Sand's Garden at Nohant,,,,,,Artist,,Eugène Delacroix,"French, Charenton-Saint-Maurice 1798–1863 Paris",,"Delacroix, Eugène",French,1798,1863,1840s,1842,1863,Oil on canvas,17 7/8 x 21 3/4 in. (45.4 x 55.2 cm),"Purchase, Dikran G. Kelekian Gift, 1922",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436177,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.328,false,true,436180,European Paintings,Painting,The Natchez,,,,,,Artist,,Eugène Delacroix,"French, Charenton-Saint-Maurice 1798–1863 Paris",,"Delacroix, Eugène",French,1798,1863,1823–24 and 1835,1823,1835,Oil on canvas,35 1/2 x 46 in. (90.2 x 116.8 cm),"Purchase, Gifts of George N. and Helen M. Richard and Mr. and Mrs. Charles S. McVeigh and Bequest of Emma A. Sheafer, by exchange, 1989",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436180,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1994.430,false,true,436179,European Paintings,Painting,"Madame Henri François Riesener (Félicité Longrois, 1786–1847)",,,,,,Artist,,Eugène Delacroix,"French, Charenton-Saint-Maurice 1798–1863 Paris",,"Delacroix, Eugène",French,1798,1863,1835,1835,1835,Oil on canvas,29 1/4 x 23 3/4 in. (74.3 x 60.3 cm),"Gift of Mrs. Charles Wrightsman, 1994",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436179,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2008.101,false,true,439631,European Paintings,Painting,Ovid among the Scythians,,,,,,Artist,,Eugène Delacroix,"French, Charenton-Saint-Maurice 1798–1863 Paris",,"Delacroix, Eugène",French,1798,1863,1862,1862,1862,"Oil on paper, laid down on wood",12 5/8 x 19 3/4 in. (32.1 x 50.2 cm),"Wrightsman Fund, in honor of Philippe de Montebello, 2008",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/439631,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -67.187.60,false,true,436175,European Paintings,Painting,Basket of Flowers,,,,,,Artist,,Eugène Delacroix,"French, Charenton-Saint-Maurice 1798–1863 Paris",,"Delacroix, Eugène",French,1798,1863,1848–49,1848,1849,Oil on canvas,42 1/4 x 56 in. (107.3 x 142.2 cm),"Bequest of Miss Adelaide Milton de Groot (1876–1967), 1967",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436175,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -67.187.61,false,true,436178,European Paintings,Painting,Hamlet and His Mother,,,,,,Artist,,Eugène Delacroix,"French, Charenton-Saint-Maurice 1798–1863 Paris",,"Delacroix, Eugène",French,1798,1863,1849,1849,1849,Oil on canvas,10 3/4 x 7 1/8 in. (27.3 x 18.1 cm),"Bequest of Miss Adelaide Milton de Groot (1876–1967), 1967",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436178,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.131,false,true,436176,European Paintings,Painting,Christ Asleep during the Tempest,,,,,,Artist,,Eugène Delacroix,"French, Charenton-Saint-Maurice 1798–1863 Paris",,"Delacroix, Eugène",French,1798,1863,ca. 1853,1853,1853,Oil on canvas,20 x 24 in. (50.8 x 61 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436176,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1988.221,false,true,435626,European Paintings,Painting,Porte de la Reine at Aigues-Mortes,,,,,,Artist,,Jean-Frédéric Bazille,"French, Montpellier 1841–1870 Beaune-la-Rolande",,"Bazille, Jean-Frédéric",French,1841,1870,1867,1867,1867,Oil on canvas,31 3/4 x 39 1/4 in. (80.6 x 99.7 cm),"Purchase, Gift of Raymonde Paul, in memory of her brother, C. Michael Paul, by exchange, 1988",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435626,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -34.138,true,true,437926,European Paintings,Painting,Mezzetin,,,,,,Artist,,Antoine Watteau,"French, Valenciennes 1684–1721 Nogent-sur-Marne",,"Watteau, Antoine",French,1684,1721,ca. 1718–20,1718,1720,Oil on canvas,21 3/4 x 17 in. (55.2 x 43.2 cm),"Munsey Fund, 1934",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437926,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -49.7.54,false,true,437925,European Paintings,Painting,The French Comedians,,,,,,Artist,,Antoine Watteau,"French, Valenciennes 1684–1721 Nogent-sur-Marne",,"Watteau, Antoine",French,1684,1721,ca. 1720,1715,1725,Oil on canvas,22 1/2 x 28 3/4 in. (57.2 x 73 cm),"The Jules Bache Collection, 1949",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437925,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -48.187.736,false,true,437456,European Paintings,Painting,Breton Fishermen and Their Families,,,,,,Artist,,Théodule-Augustin Ribot,"French, Saint-Nicolas-d'Attez 1823–1891 Colombes",,"Ribot, Théodule-Augustin",French,1823,1891,possibly ca. 1880–85,1860,1891,Oil on canvas,21 3/4 x 18 1/4 in. (55.2 x 46.4 cm),"Bequest of Catherine D. Wentworth, 1948",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437456,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -80.5.2,false,true,436569,European Paintings,Painting,The Choir of the Capuchin Church in Rome,,,,,,Artist,,François Marius Granet,"French, Aix-en-Provence 1775–1849 Aix-en-Provence",,"Granet, François Marius",French,1775,1849,1814–15,1814,1815,Oil on canvas,77 1/2 x 58 1/4 in. (196.9 x 148 cm),"Gift of P. L. Everard, 1880",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436569,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1996.181,false,true,437973,European Paintings,Painting,"Ponte San Rocco and Waterfalls, Tivoli",,,,,,Artist,,François Marius Granet,"French, Aix-en-Provence 1775–1849 Aix-en-Provence",,"Granet, François Marius",French,1775,1849,ca. 1810–20,1810,1820,Oil on canvas,14 7/8 x 11 1/8 in. (37.8 x 28.3 cm),"Purchase, Leonora Brenauer Bequest, in memory of her father, Joseph B. Brenauer; Wolfe Fund, and Wolfe Fund, by exchange, 1996",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437973,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.164.3,false,true,438948,European Paintings,Painting,"View in the Stables of the Villa of Maecenas, Tivoli",,,,,,Artist,,François Marius Granet,"French, Aix-en-Provence 1775–1849 Aix-en-Provence",,"Granet, François Marius",French,1775,1849,ca. 1805–10,1800,1815,"Oil on paper, laid down on canvas",10 1/2 x 8 3/4 in. (26.7 x 22.2 cm),"Gift of Eugene V. Thaw, 2007",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/438948,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -13.66,false,true,435885,European Paintings,Painting,View of the Domaine Saint-Joseph,,,,,,Artist,,Paul Cézanne,"French, Aix-en-Provence 1839–1906 Aix-en-Provence",,"Cézanne, Paul",French,1839,1906,late 1880s,1886,1889,Oil on canvas,25 5/8 x 32 in. (65.1 x 81.3 cm),"Catharine Lorillard Wolfe Collection, Wolfe Fund, 1913",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435885,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -62.45,false,true,435876,European Paintings,Painting,"Madame Cézanne (Hortense Fiquet, 1850–1922) in a Red Dress",,,,,,Artist,,Paul Cézanne,"French, Aix-en-Provence 1839–1906 Aix-en-Provence",,"Cézanne, Paul",French,1839,1906,1888–90,1888,1890,Oil on canvas,45 7/8 x 35 1/4 in. ( 116.5 x 89.5 cm),"The Mr. and Mrs. Henry Ittleson Jr. Purchase Fund, 1962",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435876,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -57.181,false,true,435871,European Paintings,Painting,Gardanne,,,,,,Artist,,Paul Cézanne,"French, Aix-en-Provence 1839–1906 Aix-en-Provence",,"Cézanne, Paul",French,1839,1906,1885–86,1885,1886,Oil on canvas,31 1/2 x 25 1/4 in. (80 x 64.1 cm),"Gift of Dr. and Mrs. Franz H. Hirschland, 1957",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435871,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -61.103,false,true,435866,European Paintings,Painting,Apples,,,,,,Artist,,Paul Cézanne,"French, Aix-en-Provence 1839–1906 Aix-en-Provence",,"Cézanne, Paul",French,1839,1906,1878–79,1878,1879,Oil on canvas,9 x 13 in. (22.9 x 33 cm),"The Mr. and Mrs. Henry Ittleson Jr. Purchase Fund, 1961",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435866,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1994.420,false,true,435878,European Paintings,Painting,Mont Sainte-Victoire,,,,,,Artist,,Paul Cézanne,"French, Aix-en-Provence 1839–1906 Aix-en-Provence",,"Cézanne, Paul",French,1839,1906,ca. 1902–6,1902,1906,Oil on canvas,22 1/2 x 38 1/4 in. (57.2 x 97.2 cm),"The Walter H. and Leonore Annenberg Collection, Gift of Walter H. and Leonore Annenberg, 1994, Bequest of Walter H. Annenberg, 2002",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435878,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2001.473,false,true,438136,European Paintings,Painting,The Fishermen (Fantastic Scene),,,,,,Artist,,Paul Cézanne,"French, Aix-en-Provence 1839–1906 Aix-en-Provence",,"Cézanne, Paul",French,1839,1906,ca. 1875,1870,1880,Oil on canvas,21 3/4 x 32 1/4 in. (55.2 x 81.9 cm),"Gift of Heather Daniels and Katharine Whild, and Purchase, The Annenberg Foundation Gift, Gift of Joanne Toor Cummings, by exchange, Wolfe Fund, and Ellen Lichtenstein and Joanne Toor Cummings Bequests, Mr. and Mrs. Richard J. Bernhard Gift, Gift of Mr. and Mrs. Richard Rodgers, and Wolfe Fund, by exchange, and funds from various donors, 2001",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/438136,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -51.112.1,true,true,435882,European Paintings,Painting,Still Life with Apples and a Pot of Primroses,,,,,,Artist,,Paul Cézanne,"French, Aix-en-Provence 1839–1906 Aix-en-Provence",,"Cézanne, Paul",French,1839,1906,ca. 1890,1885,1895,Oil on canvas,28 3/4 x 36 3/8 in. (73 x 92.4 cm),"Bequest of Sam A. Lewisohn, 1951",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435882,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -53.140.1,false,true,435870,European Paintings,Painting,"Antoine Dominique Sauveur Aubert (born 1817), the Artist's Uncle",,,,,,Artist,,Paul Cézanne,"French, Aix-en-Provence 1839–1906 Aix-en-Provence",,"Cézanne, Paul",French,1839,1906,1866,1866,1866,Oil on canvas,31 3/8 x 25 1/4 in. (79.7 x 64.1 cm),"Wolfe Fund, 1951; acquired from The Museum of Modern Art, Lillie P. Bliss Collection",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435870,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -61.101.1,true,true,435868,European Paintings,Painting,The Card Players,,,,,,Artist,,Paul Cézanne,"French, Aix-en-Provence 1839–1906 Aix-en-Provence",,"Cézanne, Paul",French,1839,1906,1890–92,1890,1892,Oil on canvas,25 3/4 x 32 1/4 in. (65.4 x 81.9 cm),"Bequest of Stephen C. Clark, 1960",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435868,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -61.101.2,false,true,435875,European Paintings,Painting,"Madame Cézanne (Hortense Fiquet, 1850–1922) in the Conservatory",,,,,,Artist,,Paul Cézanne,"French, Aix-en-Provence 1839–1906 Aix-en-Provence",,"Cézanne, Paul",French,1839,1906,1891,1891,1891,Oil on canvas,36 1/4 x 28 3/4 in. (92.1 x 73 cm),"Bequest of Stephen C. Clark, 1960",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435875,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -61.101.3,false,true,435883,European Paintings,Painting,Still Life with Apples and Pears,,,,,,Artist,,Paul Cézanne,"French, Aix-en-Provence 1839–1906 Aix-en-Provence",,"Cézanne, Paul",French,1839,1906,ca. 1891–92,1891,1892,Oil on canvas,17 5/8 x 23 1/8 in. (44.8 x 58.7 cm),"Bequest of Stephen C. Clark, 1960",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435883,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -61.101.4,false,true,435881,European Paintings,Painting,Still Life with a Ginger Jar and Eggplants,,,,,,Artist,,Paul Cézanne,"French, Aix-en-Provence 1839–1906 Aix-en-Provence",,"Cézanne, Paul",French,1839,1906,1893–94,1893,1894,Oil on canvas,28 1/2 x 36 in. (72.4 x 91.4 cm),"Bequest of Stephen C. Clark, 1960",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435881,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -61.101.5,false,true,435879,European Paintings,Painting,The Pool at the Jas de Bouffan,,,,,,Artist,,Paul Cézanne,"French, Aix-en-Provence 1839–1906 Aix-en-Provence",,"Cézanne, Paul",French,1839,1906,late 1880s,1886,1889,Oil on canvas,25 1/2 x 31 7/8 in. (64.8 x 81 cm),"Bequest of Stephen C. Clark, 1960",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435879,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1997.60.1,false,true,437989,European Paintings,Painting,Dish of Apples,,,,,,Artist,,Paul Cézanne,"French, Aix-en-Provence 1839–1906 Aix-en-Provence",,"Cézanne, Paul",French,1839,1906,ca. 1876–77,1876,1877,Oil on canvas,18 1/8 x 21 3/4 in. (46 x 55.2 cm),"The Walter H. and Leonore Annenberg Collection, Gift of Walter H. and Leonore Annenberg, 1997, Bequest of Walter H. Annenberg, 2002",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437989,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1997.60.2,false,true,437990,European Paintings,Painting,Seated Peasant,,,,,,Artist,,Paul Cézanne,"French, Aix-en-Provence 1839–1906 Aix-en-Provence",,"Cézanne, Paul",French,1839,1906,ca. 1892–96,1892,1896,Oil on canvas,21 1/2 x 17 3/4 in. (54.6 x 45.1 cm),"The Walter H. and Leonore Annenberg Collection, Gift of Walter H. and Leonore Annenberg, 1997, Bequest of Walter H. Annenberg, 2002",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437990,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.64,false,true,435877,European Paintings,Painting,Mont Sainte-Victoire and the Viaduct of the Arc River Valley,,,,,,Artist,,Paul Cézanne,"French, Aix-en-Provence 1839–1906 Aix-en-Provence",,"Cézanne, Paul",French,1839,1906,1882–85,1882,1885,Oil on canvas,25 3/4 x 32 1/8 in. (65.4 x 81.6 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435877,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.65,false,true,435873,European Paintings,Painting,Gustave Boyer (b. 1840) in a Straw Hat,,,,,,Artist,,Paul Cézanne,"French, Aix-en-Provence 1839–1906 Aix-en-Provence",,"Cézanne, Paul",French,1839,1906,1870–71,1870,1871,"Oil on paper, laid down on canvas",21 5/8 x 15 1/4 in. (54.9 x 38.7 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435873,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.66,true,true,435884,European Paintings,Painting,"Still Life with Jar, Cup, and Apples",,,,,,Artist,,Paul Cézanne,"French, Aix-en-Provence 1839–1906 Aix-en-Provence",,"Cézanne, Paul",French,1839,1906,ca. 1877,1872,1882,Oil on canvas,23 7/8 x 29 in. (60.6 x 73.7 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435884,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.67,false,true,435872,European Paintings,Painting,The Gulf of Marseilles Seen from L'Estaque,,,,,,Artist,,Paul Cézanne,"French, Aix-en-Provence 1839–1906 Aix-en-Provence",,"Cézanne, Paul",French,1839,1906,ca. 1885,1880,1890,Oil on canvas,28 3/4 x 39 1/2 in. (73 x 100.3 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435872,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1993.400.1,false,true,435869,European Paintings,Painting,"Antoine Dominique Sauveur Aubert (born 1817), the Artist's Uncle, as a Monk",,,,,,Artist,,Paul Cézanne,"French, Aix-en-Provence 1839–1906 Aix-en-Provence",,"Cézanne, Paul",French,1839,1906,1866,1866,1866,Oil on canvas,25 5/8 x 21 1/2 in. (65.1 x 54.6 cm),"The Walter H. and Leonore Annenberg Collection, Gift of Walter H. and Leonore Annenberg, 1993, Bequest of Walter H. Annenberg, 2002",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435869,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1993.400.2,false,true,435874,European Paintings,Painting,The House with the Cracked Walls,,,,,,Artist,,Paul Cézanne,"French, Aix-en-Provence 1839–1906 Aix-en-Provence",,"Cézanne, Paul",French,1839,1906,1892–94,1892,1894,Oil on canvas,31 1/2 x 25 1/4 in. (80 x 64.1 cm),"The Walter H. and Leonore Annenberg Collection, Gift of Walter H. and Leonore Annenberg, 1993, Bequest of Walter H. Annenberg, 2002",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435874,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.194,false,true,435880,European Paintings,Painting,Rocks in the Forest,,,,,,Artist,,Paul Cézanne,"French, Aix-en-Provence 1839–1906 Aix-en-Provence",,"Cézanne, Paul",French,1839,1906,1890s,1890,1899,Oil on canvas,28 7/8 x 36 3/8 in. (73.3 x 92.4 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435880,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.201.12,false,true,435867,European Paintings,Painting,Bathers,,,,,,Artist,,Paul Cézanne,"French, Aix-en-Provence 1839–1906 Aix-en-Provence",,"Cézanne, Paul",French,1839,1906,1874–75,1874,1875,Oil on canvas,15 x 18 1/8 in. (38.1 x 46 cm),"Bequest of Joan Whitney Payson, 1975",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435867,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -60.5,false,true,437311,European Paintings,Painting,"Rue de l'Épicerie, Rouen (Effect of Sunlight)",,,,,,Artist,,Camille Pissarro,"French, Charlotte Amalie, Saint Thomas 1830–1903 Paris",,"Pissarro, Camille",French,1830,1903,1898,1898,1898,Oil on canvas,32 x 25 5/8 in. (81.3 x 65.1 cm),"Purchase, Mr. and Mrs. Richard J. Bernhard Gift, 1960",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437311,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -66.36,false,true,437314,European Paintings,Painting,The Garden of the Tuileries on a Winter Afternoon,,,,,,Artist,,Camille Pissarro,"French, Charlotte Amalie, Saint Thomas 1830–1903 Paris",,"Pissarro, Camille",French,1830,1903,1899,1899,1899,Oil on canvas,29 x 36 1/4 in. (73.7 x 92.1 cm),"Gift of Katrin S. Vietor, in loving memory of Ernest G. Vietor, 1966",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437314,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -56.182,false,true,437300,European Paintings,Painting,"A Cowherd at Valhermeil, Auvers-sur-Oise",,,,,,Artist,,Camille Pissarro,"French, Charlotte Amalie, Saint Thomas 1830–1903 Paris",,"Pissarro, Camille",French,1830,1903,1874,1874,1874,Oil on canvas,21 5/8 x 36 1/4 in. (54.9 x 92.1 cm),"Gift of Edna H. Sachs, 1956",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437300,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -58.133,false,true,437309,European Paintings,Painting,Steamboats in the Port of Rouen,,,,,,Artist,,Camille Pissarro,"French, Charlotte Amalie, Saint Thomas 1830–1903 Paris",,"Pissarro, Camille",French,1830,1903,1896,1896,1896,Oil on canvas,18 x 21 1/2 in. (45.7 x 54.6 cm),"Gift of Arthur J. Neumark, 1958",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437309,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -60.174,false,true,437310,European Paintings,Painting,The Boulevard Montmartre on a Winter Morning,,,,,,Artist,,Camille Pissarro,"French, Charlotte Amalie, Saint Thomas 1830–1903 Paris",,"Pissarro, Camille",French,1830,1903,1897,1897,1897,Oil on canvas,25 1/2 x 32 in. (64.8 x 81.3 cm),"Gift of Katrin S. Vietor, in loving memory of Ernest G. Vietor, 1960",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437310,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -64.156,false,true,437301,European Paintings,Painting,The Public Garden at Pontoise,,,,,,Artist,,Camille Pissarro,"French, Charlotte Amalie, Saint Thomas 1830–1903 Paris",,"Pissarro, Camille",French,1830,1903,1874,1874,1874,Oil on canvas,23 5/8 x 28 3/4 in. (60 x 73 cm),"Gift of Mr. and Mrs. Arthur Murray, 1964",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437301,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -51.30.2,false,true,437299,European Paintings,Painting,"Jalais Hill, Pontoise",,,,,,Artist,,Camille Pissarro,"French, Charlotte Amalie, Saint Thomas 1830–1903 Paris",,"Pissarro, Camille",French,1830,1903,1867,1867,1867,Oil on canvas,34 1/4 x 45 1/4 in. (87 x 114.9 cm),"Bequest of William Church Osborn, 1951",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437299,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1979.414,false,true,437312,European Paintings,Painting,The Garden of the Tuileries on a Winter Afternoon,,,,,,Artist,,Camille Pissarro,"French, Charlotte Amalie, Saint Thomas 1830–1903 Paris",,"Pissarro, Camille",French,1830,1903,1899,1899,1899,Oil on canvas,28 7/8 x 36 3/8 in. (73.3 x 92.4 cm),"Gift from the Collection of Marshall Field III, 1979",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437312,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1983.166,false,true,437317,European Paintings,Painting,Still Life with Apples and Pitcher,,,,,,Artist,,Camille Pissarro,"French, Charlotte Amalie, Saint Thomas 1830–1903 Paris",,"Pissarro, Camille",French,1830,1903,1872,1872,1872,Oil on canvas,18 1/4 x 22 1/4 in. (46.4 x 56.5 cm),"Purchase, Mr. and Mrs. Richard J. Bernhard Gift, by exchange, 1983",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437317,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2004.359,false,true,438738,European Paintings,Painting,"Haystacks, Morning, Éragny",,,,,,Artist,,Camille Pissarro,"French, Charlotte Amalie, Saint Thomas 1830–1903 Paris",,"Pissarro, Camille",French,1830,1903,1899,1899,1899,Oil on canvas,25 x 31 1/2 in. (63.5 x 80 cm),"Bequest of Douglas Dillon, 2003",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/438738,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -56.184.1,false,true,437303,European Paintings,Painting,"Washerwoman, Study",,,,,,Artist,,Camille Pissarro,"French, Charlotte Amalie, Saint Thomas 1830–1903 Paris",,"Pissarro, Camille",French,1830,1903,1880,1880,1880,Oil on canvas,28 3/4 x 23 1/4 in. (73 x 59.1 cm),"Gift of Mr. and Mrs. Nate B. Spingold, 1956",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437303,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -64.154.1,false,true,437305,European Paintings,Painting,A Washerwoman at Éragny,,,,,,Artist,,Camille Pissarro,"French, Charlotte Amalie, Saint Thomas 1830–1903 Paris",,"Pissarro, Camille",French,1830,1903,1893,1893,1893,Oil on canvas,18 x 15 in. (45.7 x 38.1 cm),"Gift of Mr. and Mrs. Richard Rodgers, 1964",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437305,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1980.21.1,false,true,437308,European Paintings,Painting,"Morning, An Overcast Day, Rouen",,,,,,Artist,,Camille Pissarro,"French, Charlotte Amalie, Saint Thomas 1830–1903 Paris",,"Pissarro, Camille",French,1830,1903,1896,1896,1896,Oil on canvas,21 3/8 x 25 5/8 in. (54.3 x 65.1 cm),"Bequest of Grégoire Tarnopol, 1979, and Gift of Alexander Tarnopol, 1980",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437308,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -67.187.93,false,true,437307,European Paintings,Painting,"Poplars, Éragny",,,,,,Artist,,Camille Pissarro,"French, Charlotte Amalie, Saint Thomas 1830–1903 Paris",,"Pissarro, Camille",French,1830,1903,1895,1895,1895,Oil on canvas,36 1/2 x 25 1/2 in. (92.7 x 64.8 cm),"Bequest of Miss Adelaide Milton de Groot (1876–1967), 1967",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437307,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1973.311.5,false,true,437304,European Paintings,Painting,Two Young Peasant Women,,,,,,Artist,,Camille Pissarro,"French, Charlotte Amalie, Saint Thomas 1830–1903 Paris",,"Pissarro, Camille",French,1830,1903,1891–92,1891,1892,Oil on canvas,35 1/4 x 45 7/8 in. (89.5 x 116.5 cm),"Gift of Mr. and Mrs. Charles Wrightsman, 1973",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437304,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1991.277.2,false,true,437316,European Paintings,Painting,"Côte des Grouettes, near Pontoise",,,,,,Artist,,Camille Pissarro,"French, Charlotte Amalie, Saint Thomas 1830–1903 Paris",,"Pissarro, Camille",French,1830,1903,probably 1878,1878,1878,Oil on canvas,29 1/8 x 23 5/8 in. (74 x 60 cm),"Gift of Janice H. Levin, 1991",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437316,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.126,false,true,437306,European Paintings,Painting,Bather in the Woods,,,,,,Artist,,Camille Pissarro,"French, Charlotte Amalie, Saint Thomas 1830–1903 Paris",,"Pissarro, Camille",French,1830,1903,1895,1895,1895,Oil on canvas,23 3/4 x 28 3/4 in. (60.3 x 73 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437306,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1979.135.16,false,true,437302,European Paintings,Painting,Barges at Pontoise,,,,,,Artist,,Camille Pissarro,"French, Charlotte Amalie, Saint Thomas 1830–1903 Paris",,"Pissarro, Camille",French,1830,1903,1876,1876,1876,Oil on canvas,18 1/8 x 21 5/8 in. (46 x 54.9 cm),"Bequest of Mary Cushing Fosburgh, 1978",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437302,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -39.182,false,true,436451,European Paintings,Painting,Tahitian Landscape,,,,,,Artist,,Paul Gauguin,"French, Paris 1848–1903 Atuona, Hiva Oa, Marquesas Islands",,"Gauguin, Paul",French,1848,1903,1892,1892,1892,Oil on canvas,25 3/8 x 18 5/8 in. (64.5 x 47.3 cm),"Anonymous Gift, 1939",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436451,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -49.58.1,false,true,436446,European Paintings,Painting,Two Tahitian Women,,,,,,Artist,,Paul Gauguin,"French, Paris 1848–1903 Atuona, Hiva Oa, Marquesas Islands",,"Gauguin, Paul",French,1848,1903,1899,1899,1899,Oil on canvas,37 x 28 1/2 in. (94 x 72.4 cm),"Gift of William Church Osborn, 1949",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436446,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -51.112.2,true,true,438821,European Paintings,Painting,Ia Orana Maria (Hail Mary),,,,,,Artist,,Paul Gauguin,"French, Paris 1848–1903 Atuona, Hiva Oa, Marquesas Islands",,"Gauguin, Paul",French,1848,1903,1891,1891,1891,Oil on canvas,44 3/4 x 34 1/2 in. (113.7 x 87.6 cm),"Bequest of Sam A. Lewisohn, 1951",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/438821,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -54.143.2,false,true,436448,European Paintings,Painting,A Farm in Brittany,,,,,,Artist,,Paul Gauguin,"French, Paris 1848–1903 Atuona, Hiva Oa, Marquesas Islands",,"Gauguin, Paul",French,1848,1903,ca. 1894,1894,1894,Oil on canvas,28 1/2 x 35 5/8 in. (72.4 x 90.5 cm),"Bequest of Margaret Seligman Lewisohn, in memory of her husband, Sam A. Lewisohn, 1954",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436448,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1997.60.3,false,true,438000,European Paintings,Painting,Three Tahitian Women,,,,,,Artist,,Paul Gauguin,"French, Paris 1848–1903 Atuona, Hiva Oa, Marquesas Islands",,"Gauguin, Paul",French,1848,1903,1896,1896,1896,Oil on wood,9 5/8 x 17 in. (24.4 x 43.2 cm),"The Walter H. and Leonore Annenberg Collection, Gift of Walter H. and Leonore Annenberg, 1997, Bequest of Walter H. Annenberg, 2002",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/438000,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1993.400.3,false,true,436449,European Paintings,Painting,The Siesta,,,,,,Artist,,Paul Gauguin,"French, Paris 1848–1903 Atuona, Hiva Oa, Marquesas Islands",,"Gauguin, Paul",French,1848,1903,ca. 1892–94,1892,1894,Oil on canvas,35 x 45 3/4 in. (88.9 x 116.2 cm),"The Walter H. and Leonore Annenberg Collection, Gift of Walter H. and Leonore Annenberg, 1993, Bequest of Walter H. Annenberg, 2002",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436449,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1997.391.2,false,true,437999,European Paintings,Painting,Still Life with Teapot and Fruit,,,,,,Artist,,Paul Gauguin,"French, Paris 1848–1903 Atuona, Hiva Oa, Marquesas Islands",,"Gauguin, Paul",French,1848,1903,1896,1896,1896,Oil on canvas,18 3/4 x 26 in. (47.6 x 66 cm),"The Walter H. and Leonore Annenberg Collection, Gift of Walter H. and Leonore Annenberg, 1997, Bequest of Walter H. Annenberg, 2002",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437999,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1997.391.3,false,true,438001,European Paintings,Painting,Two Women,,,,,,Artist,,Paul Gauguin,"French, Paris 1848–1903 Atuona, Hiva Oa, Marquesas Islands",,"Gauguin, Paul",French,1848,1903,1901 or 1902,1901,1902,Oil on canvas,29 x 36 1/4 in. (73.7 x 92.1 cm),"The Walter H. and Leonore Annenberg Collection, Gift of Walter H. and Leonore Annenberg, 1997, Bequest of Walter H. Annenberg, 2002",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/438001,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1996.285,false,true,437974,European Paintings,Painting,Scene in the Jewish Quarter of Constantine,,,,,,Artist,,Théodore Chassériau,"French, Le Limon, Saint-Domingue, West Indies 1819–1856 Paris",,"Chassériau, Théodore",French,1819,1856,1851,1851,1851,Oil on canvas,22 3/8 x 18 1/2 in. (56.8 x 47 cm),"Purchase, The Annenberg Foundation Gift, 1996",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437974,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2001.720,false,true,438541,European Paintings,Painting,"Scene from the Gallic Wars: The Gaul Littavicus, Betraying the Roman Cause, Flees to Gergovie to Support Vercingétorix",,,,,,Artist,,Théodore Chassériau,"French, Le Limon, Saint-Domingue, West Indies 1819–1856 Paris",,"Chassériau, Théodore",French,1819,1856,ca. 1838–40,1838,1840,Oil on canvas,13 1/2 x 17 3/4 in. (34.3 x 45.1 cm),"Gift of Lisa and William O'Reilly, 2001",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/438541,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2002.291,false,true,438590,European Paintings,Painting,"Comtesse de La Tour-Maubourg (Marie-Louise-Charlotte-Gabrielle Thomas de Pange, 1816–1850)",,,,,,Artist,,Théodore Chassériau,"French, Le Limon, Saint-Domingue, West Indies 1819–1856 Paris",,"Chassériau, Théodore",French,1819,1856,1841,1841,1841,Oil on canvas,52 x 37 1/4 in. (132.1 x 94.6 cm),"Wrightsman Fund, 2002",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/438590,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -97.18,false,true,436329,European Paintings,Painting,Gathering Olives at Tivoli,,,,,,Artist,,François-Louis Français,"French, Plombières-les-Bains 1814–1897 Plombières-les-Bains",,"Français, François-Louis",French,1814,1897,1868,1868,1868,Oil on canvas,83 3/4 x 51 5/8 in. (212.7 x 131.1 cm),"Gift of I. Montaignac, 1897",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436329,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -26.168.60,false,true,436720,European Paintings,"Painting, miniature",Napoléon I (1769–1821),,,,,,Artist,Style of,Jean-Baptiste Isabey,19th century,,"Isabey, Jean-Baptiste",French,1767,1855,,1800,1899,Ivory,"Oval, 2 3/4 x 2 in. (70 x 50 mm)","Bequest of Collis P. Huntington, 1900",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/436720,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -24.80.526,false,true,436066,European Paintings,"Painting, miniature",Portrait of an Officer,,,,,,Artist,,D. B.,"French, 1812",,"B., D.",French,1812,1812,,1812,1812,Ivory,"Oval, 2 3/4 x 2 1/4 in. (72 x 58 mm)","Bequest of Mary Clark Thompson, 1923",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/436066,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -95.14.50,false,true,435592,European Paintings,"Painting, miniature",Portrait of a Woman with Tapestry Work,,,,,,Artist,Style of,Jean-Baptiste Jacques Augustin,ca. 1800–1810,,"Augustin, Jean-Baptiste Jacques",French,1759,1832,,1800,1810,Ivory extended by card,5 5/8 x 4 5/8 in. (140 x 117 mm),"The Moses Lazarus Collection, Gift of Josephine and Sarah Lazarus, in memory of their father, 1888–95",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/435592,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -24.80.512,false,true,435913,European Paintings,"Painting, miniature","Henry III (1551–1589), King of France",,,,,,Artist,Style of,François Clouet,"French, 1578 or later",,"Clouet, François",French,1536,1572,,1578,1599,Vellum laid on wood,2 1/4 x 1 3/4 in. (58 x 44 mm),"Bequest of Mary Clark Thompson, 1923",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/435913,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -62.122.63,false,true,435891,European Paintings,"Painting, miniature","Leda and the Swan, after Boucher",,,,,,Artist,Style of,Jacques Charlier,probably 19th century,,"Charlier, Jacques",French,1720,1790,,1800,1899,Ivory,2 x 2 7/8 in. (50 x 72 mm),"Bequest of Millie Bruhl Fredrick, 1962",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/435891,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -62.122.80,false,true,437599,European Paintings,"Painting, miniature",Portrait of a Man,,,,,,Artist,Attributed to,Jean-Baptiste Sambat,"French, ca. 1760–1827",,"Sambat, Jean-Baptiste",French,1760,1827,,1780,1827,Ivory,Diameter 2 5/8 in. (70 mm),"Bequest of Millie Bruhl Fredrick, 1962",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/437599,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -25.106.26,false,true,435903,European Paintings,"Painting, miniature",Portrait of an Officer,,,,,,Artist,,Charles Pierre Cior,"French, 1769–after 1838",,"Cior, Charles Pierre",French,1769,1838,,1789,1838,Ivory,Diameter 3 in. (77 mm),"Gift of Mrs. Louis V. Bell, in memory of her husband, 1925",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/435903,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -62.122.66,false,true,436219,European Paintings,"Painting, miniature",Portrait of a Woman,,,,,,Artist,,Frédéric Dubois,"French, active ca. 1780–1819",,"Dubois, Frédéric",French,1780,1819,,1793,1794,Ivory,Diameter 2 5/8 in. (66 mm),"Bequest of Millie Bruhl Fredrick, 1962",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/436219,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -07.225.291,false,true,435748,European Paintings,"Painting, drawing",Study for a Monument to a Princely Figure,,,,,,Artist,,François Boucher,"French, Paris 1703–1770 Paris",,"Boucher, François",French,1703,1770,,1723,1770,"Oil on paper, laid down on canvas",15 x 12 5/8 in. (38.1 x 32.1 cm),"Gift of J. Pierpont Morgan, 1906",,,,,,,,,,,,Pastels & Oil Sketches on Paper,,http://www.metmuseum.org/art/collection/search/435748,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -67.187.2,false,true,435893,European Paintings,Drawing,Fantasia,,,,,,Artist,,Jules Chéret,"French, Paris 1836–1932 Nice",,"Chéret, Jules",French,1836,1932,,1856,1933,Pastel on canvas,25 5/8 x 18 1/4 in. (65.1 x 46.4 cm),"Bequest of Miss Adelaide Milton de Groot (1876–1967), 1967",,,,,,,,,,,,Drawings,,http://www.metmuseum.org/art/collection/search/435893,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -64.165.1,false,true,436137,European Paintings,Drawing,The Dancers,,,,,,Artist,,Edgar Degas,"French, Paris 1834–1917 Paris",,"Degas, Edgar",French,1834,1917,,1854,1917,Pastel and charcoal on paper,28 x 23 1/4 in. (71.1 x 59.1 cm),"Gift of George N. and Helen M. Richard, 1964",,,,,,,,,,,,Drawings,,http://www.metmuseum.org/art/collection/search/436137,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1974.356.31,false,true,436167,European Paintings,Drawing,"Two Dancers, Half-length",,,,,,Artist,,Edgar Degas,"French, Paris 1834–1917 Paris",,"Degas, Edgar",French,1834,1917,,1854,1917,Pastel on paper,18 3/8 x 21 5/8 in. (46.7 x 54.9 cm),"The Lesley and Emma Sheafer Collection, Bequest of Emma A. Sheafer, 1973",,,,,,,,,,,,Drawings,,http://www.metmuseum.org/art/collection/search/436167,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.21,false,true,437093,European Paintings,Painting,Mercury and Battus,,,,,,Artist,,Francisque Millet,"French, 1642–1679",,"Millet, Francisque",French,1642,1679,,1662,1679,Oil on canvas,47 x 70 in. (119.4 x 177.8 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437093,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.100.11,false,true,436346,European Paintings,Painting,"An Allegory, Probably of the Peace of Utrecht of 1713",,,,,,Artist,,Antoine Rivalz,"French, 1667–1735",,"Rivalz, Antoine",French,1667,1735,,1670,1699,Oil on canvas,18 5/8 x 22 in. (47.3 x 55.9 cm),"Bequest of Harry G. Sperling, 1971",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436346,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -55.205.1,false,true,437831,European Paintings,"Painting, sketch",Jean Marc Nattier (1685–1766),,,,,,Artist,,Louis Tocqué,"French, 1696–1772",,"Tocqué, Louis",French,1696,1772,,1716,1772,Oil on canvas,30 1/2 x 23 1/4 in. (77.5 x 59.1 cm),"Gift of Colonel and Mrs. Jacques Balsan, 1955",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437831,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -60.71.20,false,true,437928,European Paintings,Painting,The Country Dance,,,,,,Artist,Copy after,Antoine Watteau,"French, late 18th century",,"Watteau, Antoine",French,1684,1721,,1704,1721,Oil on wood,Diameter 8 1/2 in. (21.6 cm),"Bequest of Lillian S. Timken, 1959",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437928,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -60.71.21,false,true,437927,European Paintings,Painting,The Cascade,,,,,,Artist,Copy after,Antoine Watteau,"French, late 18th century",,"Watteau, Antoine",French,1684,1721,,1704,1721,Oil on wood,Diameter 8 1/2 in. (21.6 cm),"Bequest of Lillian S. Timken, 1959",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437927,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -48.187.739,false,true,437886,European Paintings,Painting,Harbor Scene with a Grotto and Fishermen Hauling in Nets,,,,,,Artist,Style of,Joseph Vernet,"French, late 18th century",,"Vernet, Joseph",French,1714,1789,,1770,1799,Oil on canvas,22 3/4 x 42 1/8 in. (57.8 x 107 cm),"Bequest of Catherine D. Wentworth, 1948",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437886,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -67.187.69,false,true,436450,European Paintings,Painting,Still Life,,,,,,Artist,Style of,Paul Gauguin,"French, late 19th century",,"Gauguin, Paul",French,1848,1903,,1870,1899,Oil on canvas,15 1/8 x 18 1/4 in. (38.4 x 46.4 cm),"Bequest of Miss Adelaide Milton de Groot (1876–1967), 1967",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436450,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -08.227.1,false,true,436229,European Paintings,Painting,Imaginary Landscape,,,,,,Artist,,Gaspard Dughet,"French, Rome 1615–1675 Rome",,"Dughet, Gaspard",French,1615,1675,,1635,1675,Oil on canvas,37 7/8 x 60 1/2 in. (96.2 x 153.7 cm),"Rogers Fund, 1908",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436229,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -07.225.258,false,true,436837,European Paintings,"Painting, overdoor",Allegory of Winter,,,,,,Artist,,Jacques de La Joue the Younger,"French, Paris 1686–1761 Paris",,"La Joue, Jacques de, the Younger",French,1686,1761,,1706,1761,Oil on canvas,"Irregular, 39 1/4 x 41 5/8 in. (99.7 x 105.7 cm)","Gift of J. Pierpont Morgan, 1906",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436837,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.100.8,false,true,436230,European Paintings,Painting,Card Players in a Drawing Room,,,,,,Artist,,Pierre Louis Dumesnil the Younger,"French, Paris 1698–1781 Paris",,"Dumesnil, Pierre Louis, the Younger",French,1698,1781,,1718,1781,Oil on canvas,31 1/8 x 38 3/4 in. (79.1 x 98.4 cm),"Bequest of Harry G. Sperling, 1971",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436230,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -49.7.48,false,true,436217,European Paintings,Painting,Boy with a Black Spaniel,,,,,,Artist,,François Hubert Drouais,"French, Paris 1727–1775 Paris",,"Drouais, François Hubert",French,1727,1775,,1747,1775,Oil on canvas,"Oval, 25 3/8 x 21 in. (64.5 x 53.3 cm)","The Jules Bache Collection, 1949",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436217,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -65.242.1,false,true,436218,European Paintings,Painting,Boy with a House of Cards,,,,,,Artist,,François Hubert Drouais,"French, Paris 1727–1775 Paris",,"Drouais, François Hubert",French,1727,1775,,1747,1775,Oil on canvas,"Oval, 28 x 23 in. (71.1 x 58.4 cm)","Gift of Mrs. William M. Haupt, from the collection of Mrs. James B. Haggin, 1965",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436218,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -17.190.26,false,true,437477,European Paintings,Painting,The Fountain,,,,,,Artist,,Hubert Robert,"French, Paris 1733–1808 Paris",,"Robert, Hubert",French,1733,1808,,1753,1808,Oil on canvas,68 1/4 x 31 3/8 in. (173.4 x 79.7 cm),"Gift of J. Pierpont Morgan, 1917",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437477,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -17.190.27,false,true,437480,European Paintings,Painting,The Swing,,,,,,Artist,,Hubert Robert,"French, Paris 1733–1808 Paris",,"Robert, Hubert",French,1733,1808,,1753,1808,Oil on canvas,68 1/4 x 34 5/8 in. (173.4 x 87.9 cm),"Gift of J. Pierpont Morgan, 1917",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437480,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -17.190.28,false,true,437476,European Paintings,Painting,The Dance,,,,,,Artist,,Hubert Robert,"French, Paris 1733–1808 Paris",,"Robert, Hubert",French,1733,1808,,1753,1808,Oil on canvas,68 1/4 x 33 5/8 in. (173.4 x 85.4 cm),"Gift of J. Pierpont Morgan, 1917",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437476,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -17.190.29,false,true,437473,European Paintings,Painting,The Bathing Pool,,,,,,Artist,,Hubert Robert,"French, Paris 1733–1808 Paris",,"Robert, Hubert",French,1733,1808,,1753,1808,Oil on canvas,68 3/4 x 48 3/4 in. (174.6 x 123.8 cm),"Gift of J. Pierpont Morgan, 1917",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437473,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -17.190.30,false,true,437481,European Paintings,Painting,Wandering Minstrels,,,,,,Artist,,Hubert Robert,"French, Paris 1733–1808 Paris",,"Robert, Hubert",French,1733,1808,,1753,1808,Oil on canvas,68 3/4 x 48 1/4 in. (174.6 x 122.6 cm),"Gift of J. Pierpont Morgan, 1917",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437481,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -17.190.31,false,true,437472,European Paintings,"Painting, overdoor",Arches in Ruins,,,,,,Artist,,Hubert Robert,"French, Paris 1733–1808 Paris",,"Robert, Hubert",French,1733,1808,,1753,1808,Oil on canvas,23 1/8 x 61 1/4 in. (58.7 x 155.6 cm),"Gift of J. Pierpont Morgan, 1917",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437472,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -17.190.32,false,true,437475,European Paintings,"Painting, overdoor",A Colonnade in Ruins,,,,,,Artist,,Hubert Robert,"French, Paris 1733–1808 Paris",,"Robert, Hubert",French,1733,1808,,1753,1808,Oil on canvas,23 x 61 1/8 in. (58.4 x 155.3 cm),"Gift of J. Pierpont Morgan, 1917",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437475,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -07.225.264a,false,true,437474,European Paintings,"Painting, overdoor",Bridge over a Cascade,,,,,,Artist,,Hubert Robert,"French, Paris 1733–1808 Paris",,"Robert, Hubert",French,1733,1808,,1753,1808,Oil on canvas,32 x 54 1/8 in. (81.3 x 137.5 cm),"Gift of J. Pierpont Morgan, 1906",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437474,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -07.225.264b,false,true,437471,European Paintings,"Painting, overdoor",Aqueduct in Ruins,,,,,,Artist,,Hubert Robert,"French, Paris 1733–1808 Paris",,"Robert, Hubert",French,1733,1808,,1753,1808,Oil on canvas,32 1/8 x 54 1/8 in. (81.6 x 137.5 cm),"Gift of J. Pierpont Morgan, 1906",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437471,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -80.2,false,true,437079,European Paintings,Painting,Landscape with a Plowed Field and a Village,,,,,,Artist,,Georges Michel,"French, Paris 1763–1843 Paris",,"Michel, Georges",French,1763,1843,,1783,1843,Oil on canvas,20 1/8 x 27 5/8 in. (51.1 x 70.2 cm),"Gift of Paul Durand-Ruel, 1880",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437079,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.400.44,false,true,440336,European Paintings,Painting,View in the Roman Campagna,,,,,,Artist,,Alexandre Desgoffe,"French, Paris 1805–1882 Paris",,Desgoffe Alexandre,French,1805,1882,,1834,1837,"Oil on paper, laid down on canvas",5 5/8 x 14 3/8 in. (14.3 x 36.5 cm),"Thaw Collection, Jointly Owned by The Metropolitan Museum of Art and The Morgan Library & Museum, Gift of Eugene V. Thaw, 2009",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/440336,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -60.71.10,false,true,436774,European Paintings,Painting,Springtime,,,,,,Artist,,Charles Jacque,"French, Paris 1813–1894 Paris",,"Jacque, Charles",French,1813,1894,,1833,1894,Oil on wood,16 x 11 1/2 in. (40.6 x 29.2 cm),"Bequest of Lillian S. Timken, 1959",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436774,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -60.155,false,true,436849,European Paintings,Painting,The Village of La Celle-sous-Moret,,,,,,Artist,,Eugène Lavieille,"French, Paris 1820–1889 Paris",,"Lavieille, Eugène",French,1820,1889,,1840,1889,Oil on wood,13 5/8 x 23 in. (34.6 x 58.4 cm),"Gift of Arthur Wiesenberger, 1960",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436849,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -08.123,false,true,437367,European Paintings,Painting,"Place Saint-Germain-des-Prés, Paris",,,,,,Artist,,Jean-François Raffaëlli,"French, Paris 1850–1924 Paris",,"Raffaëlli, Jean-François",French,1850,1924,,1870,1924,Oil on canvas,27 1/2 x 31 1/2 in. (69.9 x 80 cm),"Catharine Lorillard Wolfe Collection, Wolfe Fund, 1908",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437367,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -66.217,false,true,436312,European Paintings,Painting,Recess of the Court,,,,,,Artist,,Jean-Louis Forain,"French, Reims 1852–1931 Paris",,"Forain, Jean-Louis",French,1852,1931,,1872,1931,Oil on canvas,23 7/8 x 28 7/8 in. (60.6 x 73.3 cm),"Gift of Mr. and Mrs. Arthur Wiesenberger, 1966",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436312,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -07.225.461,false,true,435639,European Paintings,"Painting, overdoor",Vase of Flowers in a Niche,,,,,,Artist,Attributed to,Michel Bruno Bellengé,"French, Rouen 1726–1793 Rouen",,"Bellengé, Michel Bruno",French,1726,1793,,1746,1793,Oil on canvas,48 3/8 x 55 in. (122.9 x 139.7 cm),"Gift of J. Pierpont Morgan, 1906",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435639,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.119,false,true,436028,European Paintings,Painting,"Portrait of a Woman, Called Héloïse Abélard",,,,,,Artist,Style of,Gustave Courbet,"French, second half 19th century",,"Courbet, Gustave",French,1819,1877,,1850,1899,Oil on canvas,25 3/8 x 21 1/8 in. (64.5 x 53.7 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436028,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.123,false,true,436027,European Paintings,Painting,Apples,,,,,,Artist,Style of,Gustave Courbet,"French, second half 19th century",,"Courbet, Gustave",French,1819,1877,,1850,1899,Oil on canvas,13 x 17 3/8 in. (33 x 44.1 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436027,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.138,false,true,435857,European Paintings,Painting,Landscape with a Cave,,,,,,Artist,,Théodore Caruelle d'Aligny,"French, Chaumes 1798–1871 Lyons",,"Aligny, Théodore Caruelle d'",French,1798,1871,,1818,1871,Oil on canvas,24 1/2 x 18 in. (62.2 x 45.7 cm),"Catharine Lorillard Wolfe Collection, Wolfe Fund, 1989",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435857,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -63.138.5,false,true,435855,European Paintings,Painting,The First Communion,,,,,,Artist,,Eugène Carrière,"French, Gournay 1849–1906 Paris",,"Carrière, Eugène",French,1849,1906,,1869,1906,Oil on canvas,25 3/4 x 21 in. (65.4 x 53.3 cm),"Gift of Chester Dale, 1963",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435855,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -25.110.19,false,true,437847,European Paintings,Painting,Road in the Woods,,,,,,Artist,,Constant Troyon,"French, Sèvres 1810–1865 Paris",,"Troyon, Constant",French,1810,1865,,1840,1860,Oil on canvas,22 7/8 x 19 in. (58.1 x 48.3 cm),"Bequest of Collis P. Huntington, 1900",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437847,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -07.233.39,false,true,437319,European Paintings,Painting,Mother and Child,,,,,,Artist,,Antoine-Émile Plassan,"French, Bordeaux 1817–1903 Paris",,"Plassan, Antoine-Émile",French,1817,1903,,1837,1903,Oil on wood,10 5/8 x 8 5/8 in. (27 x 21.9 cm),"Bequest of Margarette A. Jones, 1905",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437319,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -43.86.7,false,true,437519,European Paintings,Painting,A River Landscape,,,,,,Artist,,Théodore Rousseau,"French, Paris 1812–1867 Barbizon",,"Rousseau, Théodore",French,1812,1867,,1832,1867,Oil on wood,16 3/8 x 24 7/8 in. (41.6 x 63.2 cm),"Bequest of Richard De Wolfe Brixey, 1943",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437519,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -15.30.26,false,true,435862,European Paintings,Painting,The Route Nationale at Samer,,,,,,Artist,,Jean-Charles Cazin,"French, Samer 1841–1901 Lavandou",,"Cazin, Jean-Charles",French,1841,1901,,1861,1901,Oil on canvas,41 1/2 x 48 1/4 in. (105.4 x 122.6 cm),"Bequest of Maria DeWitt Jesup, from the collection of her husband, Morris K. Jesup, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435862,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.100.5,false,true,436108,European Paintings,Painting,Head of a Child,,,,,,Artist,Style of,Jacques Louis David,"French, first quarter 19th century",,"David, Jacques Louis",French,1748,1825,,1768,1825,Oil on canvas,15 3/4 x 12 5/8 in. (40 x 32.1 cm),"Bequest of Harry G. Sperling, 1971",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436108,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -32.100.124,false,true,435912,European Paintings,Painting,"Charles IX (1550–1574), King of France",,,,,,Artist,Style of,François Clouet,"French, painted shortly after 1561",,"Clouet, François",French,1536,1572,,1561,1566,Oil on wood,12 3/8 x 9 in. (31.4 x 22.9 cm),"The Friedsam Collection, Bequest of Michael Friedsam, 1931",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435912,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.20,false,true,437331,European Paintings,Painting,Orpheus and Eurydice,,,,,,Artist,Style of,Nicolas Poussin,"French, third quarter 17th century",,"Poussin, Nicolas",French,1594,1665,,1650,1674,Oil on canvas,47 1/2 x 70 3/4 in. (120.7 x 179.7 cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437331,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1985.118,false,true,436705,European Paintings,Painting,Head of Saint John the Evangelist,,,,,,Artist,,Jean Auguste Dominique Ingres,"French, Montauban 1780–1867 Paris",,"Ingres, Jean Auguste Dominique",French,1780,1867,,1818,1856,"Oil on canvas, laid down on wood",15 1/2 x 10 5/8 in. (39.4 x 27 cm),"Catharine Lorillard Wolfe Collection, Purchase, Bequest of Catharine Lorillard Wolfe, by exchange, and Wolfe Fund, 1985",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436705,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.20.2,false,true,436645,European Paintings,Painting,Young Woman Praying,,,,,,Artist,,Jean-Jacques Henner,"French, Bernwiller 1829–1905 Paris",,"Henner, Jean-Jacques",French,1829,1905,,1849,1905,Oil on canvas,24 7/8 x 17 7/8 in. (63.2 x 45.4 cm),"Bequest of Emma T. Gary, 1937",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436645,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.100.2,false,true,435688,European Paintings,Painting,Portrait of a Woman,,,,,,Artist,,Louis Léopold Boilly,"French, La Bassée 1761–1845 Paris",,"Boilly, Louis Léopold",French,1761,1845,,1781,1845,Oil on canvas,8 3/4 x 6 7/8 in. (22.2 x 17.5 cm),"Bequest of Harry G. Sperling, 1971",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435688,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.100.3,false,true,435687,European Paintings,Painting,Portrait of a Man,,,,,,Artist,,Louis Léopold Boilly,"French, La Bassée 1761–1845 Paris",,"Boilly, Louis Léopold",French,1761,1845,,1781,1845,Oil on canvas,8 3/4 x 6 7/8 in. (22.2 x 17.5 cm),"Bequest of Harry G. Sperling, 1971",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435687,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -90.25,false,true,435701,European Paintings,Painting,Environs of Fontainebleau: Woodland and Cattle,,,,,,Artist,,Auguste-François Bonheur,"French, Bordeaux 1824–1884 Bellevue",,"Bonheur, Auguste-François",French,1824,1884,,1844,1884,Oil on canvas,104 1/2 x 157 1/4 in. (265.4 x 399.4 cm),"Gift of James Clinch Smith and his sisters, in memory of their mother, 1890",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435701,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -49.7.39,false,true,435757,European Paintings,Painting,Portrait of a Young Boy,,,,,,Artist,Attributed to,Sébastien Bourdon,"French, Montpellier 1616–1671 Paris",,"Bourdon, Sébastien",French,1616,1671,,1636,1671,Oil on canvas,23 1/4 x 19 3/4 in. (59.1 x 50.2 cm),"The Jules Bache Collection, 1949",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435757,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1997.257,false,true,438030,European Paintings,Painting,Valley of the River Loire,,,,,,Artist,,Jules Dupré,"French, Nantes 1811–1889 L'Isle-Adam",,"Dupré, Jules",French,1811,1889,,1831,1889,Oil on wood,10 3/4 x 19 1/4 in. (27.3 x 48.9 cm),"Bequest of Mr. and Mrs. Richard S. Richards, 1997",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/438030,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1982.60.43,false,true,437258,European Paintings,Painting,The Golden Age,,,,,,Artist,,Jean-Baptiste Joseph Pater,"French, Valenciennes 1695–1736 Paris",,"Pater, Jean-Baptiste Joseph",French,1695,1736,,1715,1736,Oil on wood,6 3/8 x 9 in. (16.2 x 22.9 cm),"The Jack and Belle Linsky Collection, 1982",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437258,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -64.315,false,true,436186,European Paintings,Painting,Still Life with Silver,,,,,,Artist,,Alexandre François Desportes,"French, Champigneulle 1661–1743 Paris",,"Desportes, Alexandre François",French,1661,1743,,1681,1743,Oil on canvas,103 x 73 3/4 in. (261.6 x 187.3 cm),"Purchase, Mary Wetmore Shively Bequest, in memory of her husband, Henry L. Shively, M.D., 1964",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436186,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.100.9,false,true,437039,European Paintings,Painting,Classical Landscape with Figures,,,,,,Artist,,Henri Mauperché,"French, Paris (?) ca. 1602–1686 Paris",,"Mauperché, Henri",French,1602,1686,,1622,1686,Oil on canvas,27 7/8 x 44 1/4 in. (70.8 x 112.4 cm),"Bequest of Harry G. Sperling, 1971",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437039,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -87.15.93,false,true,436116,European Paintings,Painting,The Night Patrol at Smyrna,,,,,,Artist,,Alexandre-Gabriel Decamps,"French, Paris 1803–1860 Fontainebleau",,"Decamps, Alexandre-Gabriel",French,1803,1860,,1823,1860,Oil on canvas,29 1/4 x 36 3/8 in. (74.3 x 92.4 cm),"Catharine Lorillard Wolfe Collection, Bequest of Catharine Lorillard Wolfe, 1887",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436116,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1982.179.12,false,true,436780,European Paintings,Painting,Captain Swaton,,,,,,Artist,Attributed to,Paulin Jénot,"French, active by 1886, died after 1930",,"Jénot, Paulin",French,1886,1930,,1886,1930,Oil on canvas,16 1/8 x 13 in. (41 x 33 cm),"Gift of Raymonde Paul, in memory of her brother, C. Michael Paul, 1982",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436780,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -95.21,false,true,436238,European Paintings,Painting,Benjamin Franklin (1706–1790),,,,,,Artist,Workshop of,Joseph Siffred Duplessis,"French, Carpentras 1725–1802 Versailles",,"Duplessis, Joseph Siffred",French,1725,1802,,1745,1802,Oil on canvas,"Oval, 27 5/8 x 22 1/4 in. (70.2 x 56.5 cm)","Gift of George A. Lucas, 1895",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436238,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -07.267,false,true,437147,European Paintings,Painting,The Court of the Princess,,,,,,Artist,,Adolphe Monticelli,"French, Marseilles 1824–1886 Marseilles",,"Monticelli, Adolphe",French,1824,1886,,1844,1886,Oil on wood,15 x 23 3/8 in. (38.1 x 59.4 cm),"Catharine Lorillard Wolfe Collection, Wolfe Fund, 1907",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437147,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -67.187.198,false,true,437149,European Paintings,Painting,Four Figures,,,,,,Artist,,Adolphe Monticelli,"French, Marseilles 1824–1886 Marseilles",,"Monticelli, Adolphe",French,1824,1886,,1844,1886,Oil on wood,9 3/4 x 7 3/4 in. (24.8 x 19.7 cm),"Bequest of Miss Adelaide Milton de Groot (1876–1967), 1967",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437149,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -87.24,false,true,437269,European Paintings,Painting,"January: Cernay, near Rambouillet",,,,,,Artist,,Léon-Germain Pelouse,"French, Pierrelaye 1838–1891 Pierrelaye",,"Pelouse, Léon-Germain",French,1838,1891,,1858,1891,Oil on canvas,35 3/8 x 46 1/4 in. (89.9 x 117.5 cm),"Gift of Mabel Schaus, 1887",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437269,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -07.225.438a,false,true,436268,European Paintings,Painting,Putti with a Medallion,,,,,,Artist,,Charles Dominique Joseph Eisen,"French, Valenciennes 1720–1778 Brussels",,"Eisen, Charles Dominique Joseph",French,1720,1778,,1740,1778,Oil on wood,"Oval, 25 3/8 x 21 1/4 in. (64.5 x 54 cm)","Gift of J. Pierpont Morgan, 1906",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436268,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -07.225.438b,false,true,436269,European Paintings,Painting,Putti with a Medallion,,,,,,Artist,,Charles Dominique Joseph Eisen,"French, Valenciennes 1720–1778 Brussels",,"Eisen, Charles Dominique Joseph",French,1720,1778,,1740,1778,Oil on wood,"Oval, 25 1/2 x 21 1/4 in. (64.8 x 54 cm)","Gift of J. Pierpont Morgan, 1906",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436269,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -67.212,false,true,436010,European Paintings,Painting,A Brook in the Forest,,,,,,Artist,,Gustave Courbet,"French, Ornans 1819–1877 La Tour-de-Peilz",,"Courbet, Gustave",French,1819,1877,,1839,1877,Oil on canvas,19 7/8 x 24 1/8 in. (50.5 x 61.3 cm),"Gift of Ralph Weiler, 1967",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436010,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -45.128.12,false,true,435916,European Paintings,Painting,"Henry II (1519–1559), King of France",,,,,,Artist,Workshop of,François Clouet,"French, Tours (?), active by 1536–died 1572 Paris",,"Clouet, François",French,1536,1572,,1536,1572,"Oil on canvas, transferred from wood",61 1/2 x 53 in. (156.2 x 134.6 cm),"Bequest of Helen Hay Whitney, 1944",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435916,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2001.202.4,false,true,437996,European Paintings,Painting,Roses and Lilies,,,,,,Artist,,Henri Fantin-Latour,"French, Grenoble 1836–1904 Buré",,"Fantin-Latour, Henri",French,1836,1904,1888,1888,1888,Oil on canvas,23 1/2 x 18 in. (59.7 x 45.7 cm),"The Walter H. and Leonore Annenberg Collection, Gift of Walter H. and Leonore Annenberg, 2001, Bequest of Walter H. Annenberg, 2002",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437996,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1998.325.3,false,true,438014,European Paintings,Painting,"The Daughters of Catulle Mendès, Huguette (1871–1964), Claudine (1876–1937), and Helyonne (1879–1955)",,,,,,Artist,,Auguste Renoir,"French, Limoges 1841–1919 Cagnes-sur-Mer",,"Renoir, Auguste",French,1841,1919,1888,1888,1888,Oil on canvas,63 3/4 x 51 1/8 in. (161.9 x 129.9 cm),"The Walter H. and Leonore Annenberg Collection, Gift of Walter H. and Leonore Annenberg, 1998, Bequest of Walter H. Annenberg, 2002",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/438014,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -95.14.68,false,true,436067,European Paintings,"Painting, miniature","Joseph II (1741–1790), Emperor of Austria",,,,,,Artist,,Adam Ludwig d'Argent,"German, 1748–1829",,"Argent, Adam Ludwig d'",German,1748,1829,ca. 1780,1775,1785,Enamel,"Oval, 1 1/4 x 1 in. (32 x 26 mm)","The Moses Lazarus Collection, Gift of Josephine and Sarah Lazarus, in memory of their father, 1888–95",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/436067,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -24.80.511,false,true,437653,European Paintings,"Painting, miniature",Portrait of a Woman,,,,,,Artist,,Carl August Senff,"German, 1770–1838",,"Senff, Carl August",German,1770,1838,1808,1808,1808,Ivory,"Oval, 2 3/8 x 2 in. (60 x 50 mm)","Bequest of Mary Clark Thompson, 1923",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/437653,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -41.67,false,true,436887,European Paintings,"Painting, miniature",Prince Klemens Wenzel Lothar von Metternich (1773–1859),,,,,,Artist,,Friedrich Johann Gottlieb Lieder,"German, 1780–1859",,"Lieder, Friedrich Johann Gottlieb",German,1780,1859,1822,1822,1822,Card laid on recent support,"Oval, 8 3/4 x 6 3/4 in. (224 x 172 mm)","Fletcher Fund, 1941",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/436887,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -95.14.85,false,true,437632,European Paintings,"Painting, miniature",Joseph and Karl August von Klein,,,,,,Artist,,Heinrich Franz Schalck,"German, 1791–1832",,"Schalk, Heinrich Franz",German,1791,1832,ca. 1810–15,1810,1815,Ivory,4 5/8 x 5 1/8 in. (117 x 131 mm),"The Moses Lazarus Collection, Gift of Josephine and Sarah Lazarus, in memory of their father, 1888–95",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/437632,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -95.14.52,false,true,436633,European Paintings,"Painting, miniature","Ali Pasha (born about 1741, died 1822)",,,,,,Artist,,Jacob Ritter von Hartmann,"German, 1795–1873",,"Hartmann, Jacob Ritter von",German,1795,1873,1822,1822,1822,Ivory,"Oval, 4 1/8 x 3 1/4 in. (105 x 84 mm)","The Moses Lazarus Collection, Gift of Josephine and Sarah Lazarus, in memory of their father, 1888–95",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/436633,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -41.191.3,false,true,436639,European Paintings,"Painting, miniature",Henriette Sontag (1806–1854),,,,,,Artist,,Franz Napoleon Heigel,"German, 1813–1888",,"Heigel, Franz Napoleon",German,1813,1888,ca. 1835,1830,1840,Ivory,"Oval, 2 7/8 x 2 3/8 in. (73 x 60 mm)","Gift of Mrs. Thomas Hunt, 1941",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/436639,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -50.57,false,true,437761,European Paintings,"Painting, miniature",Portrait of a Woman,,,,,,Artist,,Peter Edward Stroely,"German, 1768–after 1826",,"Stroely, Peter Edward",German,1768,1826,ca. 1800,1795,1805,Ivory,"Octagonal, 3 3/8 x 2 5/8 in. (87 x 67 mm)","Rogers Fund, 1950",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/437761,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -24.80.519,false,true,437786,European Paintings,"Painting, miniature",Diana,,,,,,Artist,,Carl Friedrich Thienpondt,"German, Berlin 1730–1796 Warsaw",,"Thienpondt, Carl Friedrich",German,1730,1796,ca. 1760,1755,1765,Enamel,2 7/8 x 2 1/4 in. (74 x 59 mm),"Bequest of Mary Clark Thompson, 1923",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/437786,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -24.80.520,false,true,437785,European Paintings,"Painting, miniature",A Sea Nymph,,,,,,Artist,,Carl Friedrich Thienpondt,"German, Berlin 1730–1796 Warsaw",,"Thienpondt, Carl Friedrich",German,1730,1796,ca. 1760,1755,1765,Enamel,2 7/8 x 2 1/4 in. (74 x 59 mm),"Bequest of Mary Clark Thompson, 1923",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/437785,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -95.14.78,false,true,437076,European Paintings,"Painting, miniature",Hannah Mahady,,,,,,Artist,,Jeremiah Meyer,"German, Tübingen 1735–1789 Kew",,"Meyer, Jeremiah",German,1735,1789,ca. 1760,1755,1765,Ivory,"Oval, 7/8 x 5/8 in. (22 x 16 mm)","The Moses Lazarus Collection, Gift of Josephine and Sarah Lazarus, in memory of their father, 1888–95",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/437076,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -41.68,false,true,436421,European Paintings,"Painting, miniature","Maria Louisa (1745–1792), Empress of Austria",,,,,,Artist,,Heinrich Friedrich Füger,"German, Heilbronn 1751–1818 Vienna",,"Füger, Heinrich Friedrich",German,1751,1818,ca. 1790,1785,1795,Ivory,"Octagonal, 1 1/4 x 7/8 in. (31 x 22 mm)","Fletcher Fund, 1941",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/436421,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -95.14.61,false,true,437960,European Paintings,"Painting, miniature",Richard Abell,,,,,,Artist,,Christian Friedrich Zincke,"German, Dresden 1683/85–1767 London",,"Zincke, Christian Friedrich",German,1683,1767,1724,1724,1724,Enamel,"Oval, 1 3/4 x 1 3/8 in. (45 x 35 mm)","The Moses Lazarus Collection, Gift of Josephine and Sarah Lazarus, in memory of their father, 1888–95",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/437960,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -95.14.99,false,true,437961,European Paintings,"Painting, miniature",Mrs. Vanderbank,,,,,,Artist,,Christian Friedrich Zincke,"German, Dresden 1683/85–1767 London",,"Zincke, Christian Friedrich",German,1683,1767,ca. 1730,1725,1735,Enamel,"Oval, 1 3/8 x 1 1/2 in. (35 x 38 mm)","The Moses Lazarus Collection, Gift of Josephine and Sarah Lazarus, in memory of their father, 1888–95",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/437961,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -25.205,false,true,436663,European Paintings,"Painting, miniature","Thomas Wriothesley (1505–1550), First Earl of Southampton",,,,,,Artist,,Hans Holbein the Younger,"German, Augsburg 1497/98–1543 London",,"Holbein, Hans, the Younger",German,1497,1543,ca. 1535,1530,1540,Vellum laid on card,"Irregular, cut down, 1 1/8 x 1 in. (28 x 25 mm)","Rogers Fund, 1925",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/436663,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -50.69.1,false,true,436661,European Paintings,"Painting, miniature",William Roper (1493/94–1578),,,,,,Artist,,Hans Holbein the Younger,"German, Augsburg 1497/98–1543 London",,"Holbein, Hans, the Younger",German,1497,1543,1535–36,1535,1536,Vellum laid on card,Diameter 1 3/4 in. (45 mm),"Rogers Fund, 1950",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/436661,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -50.69.2,false,true,436662,European Paintings,"Painting, miniature","Margaret More (1505–1544), Wife of William Roper",,,,,,Artist,,Hans Holbein the Younger,"German, Augsburg 1497/98–1543 London",,"Holbein, Hans, the Younger",German,1497,1543,1535–36,1535,1536,Vellum laid on playing card,Diameter 1 3/4 in. (45 mm),"Rogers Fund, 1950",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/436662,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2005.231,false,true,438780,European Paintings,"Painting, drawing",Pleasure,,,,,,Artist,,Anton Raphael Mengs,"German, Ústi nad Labem (Aussig) 1728–1779 Rome",,"Mengs, Anton Raphael",German,1728,1779,ca. 1754,1749,1759,"Pastel on paper, laid down on canvas","Oval, 24 3/8 x 19 1/4 in. (61.9 x 48.9 cm)","Victor Wilbour Memorial, The Alfred N. Punnett Endowment, and Marquand Funds, 2005",,,,,,,,,,,,Pastels & Oil Sketches on Paper,,http://www.metmuseum.org/art/collection/search/438780,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -49.7.31,false,true,436670,European Paintings,Painting,"Edward VI (1537–1553), When Duke of Cornwall",,,,,,Artist,Workshop of,Hans Holbein the Younger,,,"Holbein, Hans, the Younger",German,1497,1543,ca. 1545; reworked 1547 or later,1540,1547,Oil and gold on oak,Diameter 12 3/4 in. (32.4 cm),"The Jules Bache Collection, 1949",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436670,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.40.646,false,true,436669,European Paintings,Painting,"Lady Rich (Elizabeth Jenks, died 1558)",,,,,,Artist,Workshop of,Hans Holbein the Younger,,,"Holbein, Hans, the Younger",German,1497,1543,ca. 1540,1535,1545,Oil and gold on oak,17 1/2 x 13 3/8 in. (44.5 x 34 cm),"Bequest of Benjamin Altman, 1913",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436669,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -32.100.61,false,true,436044,European Paintings,Painting,Portrait of a Man,,,,,,Artist,Circle of,Lucas Cranach the Elder,,,"Cranach, Lucas, the Elder",German,1472,1553,1537,1537,1537,Oil on alder,22 x 16 3/4 in. (55.9 x 42.5 cm),"The Friedsam Collection, Bequest of Michael Friedsam, 1931",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436044,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1982.60.37,false,true,436287,European Paintings,Painting,Heinrich(?) vom Rhein zum Mohren (1477–1536),,,,,,Artist,Copy after,Conrad Faber von Creuznach,,,"Faber von Creuznach, Conrad",German,1524,1553,late 1520s,1527,1529,Oil and gold on oak,Overall 21 3/4 x 15 5/8 in. (55.2 x 39.7 cm); painted surface 21 1/2 x 15 in. (54.6 x 38.1 cm),"The Jack and Belle Linsky Collection, 1982",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436287,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -62.267.1,false,true,435818,European Paintings,Painting,Portrait of a Man,,,,,,Artist,,Barthel Bruyn the Elder,"German, 1493–1555",,"Bruyn, Barthel, the Elder",German,1493,1555,1533,1533,1533,Oil on oak,"Overall, with arched top, 12 x 8 7/8 in. (30.5 x 22.5 cm); painted surface 11 3/4 x 8 1/8 in. (29.8 x 20.6 cm)","Gift of James A. Moffett 2nd, 1962",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435818,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -62.267.2,false,true,435819,European Paintings,Painting,Portrait of a Woman,,,,,,Artist,,Barthel Bruyn the Elder,"German, 1493–1555",,"Bruyn, Barthel, the Elder",German,1493,1555,1533,1533,1533,Oil on oak,"Overall, with arched top, 12 x 8 7/8 in. (30.5 x 22.5 cm); painted surface 11 3/4 x 8 1/8 in. (29.8 x 20.6 cm)","Gift of James A. Moffett 2nd, 1962",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435819,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1978.554.2,false,true,436590,European Paintings,Painting,"The Empress Elizabeth of Russia (1709–1762) on Horseback, Attended by a Page",,,,,,Artist,Attributed to,Georg Christoph Grooth,"German, 1716–1749",,"Grooth, Georg Christoph",German,1716,1749,after 1743–49,1743,1749,Oil on canvas,31 3/8 x 24 1/2 in. (79.7 x 62.2 cm),"Gift of Mr. and Mrs. Nathaniel Spear Jr., 1978",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436590,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2006.116,false,true,438849,European Paintings,Painting,Landscape,,,,,,Artist,Circle of,Carl Rottmann,"German, 1797–1850",,"Rottmann, Carl",German,1797,1850,ca. 1835–45,1830,1850,"Oil on paper, laid down on board",8 7/8 x 10 5/8 in. (22.5 x 27 cm),"Purchase, Gift of Joanne Toor Cummings, by exchange, 2006",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/438849,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -32.100.50,false,true,435820,European Paintings,Painting,Portrait of a Woman of the Slosgin Family of Cologne,,,,,,Artist,,Barthel Bruyn the Younger,"German, ca. 1530–before 1610",,"Bruyn, Barthel, the Younger",German,1530,1610,1557,1557,1557,Oil on oak,"Shaped top, 17 3/4 x 14 1/8 in. (45.1 x 35.9 cm)","The Friedsam Collection, Bequest of Michael Friedsam, 1931",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435820,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2002.564,false,true,438617,European Paintings,Painting,"The Children of Martin Anton Heckscher: Johann Gustav Wilhelm Moritz (1797–1865), Carl Martin Adolph (1796–1850), and Leopold (born 1792)",,,,,,Artist,,Johann Heinrich Wilhelm Tischbein,"German, Haina 1751–1829 Eutin",,"Tischbein, Johann Heinrich Wilhelm",German,1751,1829,1805,1805,1805,Oil on canvas,58 x 45 in. (147.3 x 114.3 cm),"Gift of the family of August Heckscher II, in his memory, 2002",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/438617,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -16.148.2,false,true,436886,European Paintings,Painting,The Ropewalk in Edam,,,,,,Artist,,Max Liebermann,"German, Berlin 1847–1935 Berlin",,"Liebermann, Max",German,1847,1935,1904,1904,1904,Oil on canvas,39 3/4 x 28 in. (101 x 71.1 cm),"Reisinger Fund, 1916",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436886,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.70,false,true,439065,European Paintings,Painting,The Family of Mr. Westfal in the Conservatory,,,,,,Artist,,Eduard Gaertner,"German, Berlin 1801–1877 Zechlin",,"Gaertner, Eduard",German,1801,1877,1836,1836,1836,Oil on canvas,9 3/8 x 7 7/8 in. (23.8 x 20 cm),"Purchase, funds from various donors, by exchange, 2007",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/439065,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2006.258,false,true,438848,European Paintings,Painting,Parochialstrasse in Berlin,,,,,,Artist,,Eduard Gaertner,"German, Berlin 1801–1877 Zechlin",,"Gaertner, Eduard",German,1801,1877,1831,1831,1831,Oil on canvas,16 x 11 in. (40.6 x 27.9 cm),"Catharine Lorillard Wolfe Collection, Wolfe Fund, and funds from various donors, by exchange, 2006",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/438848,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.64,false,true,440726,European Paintings,Painting,The Artist's Sitting Room in Ritterstrasse,,,,,,Artist,,Adolph Menzel,"German, Breslau 1815–1905 Berlin",,"Menzel, Adolph",German,1815,1905,1851,1851,1851,Oil on cardboard,12 5/8 x 10 5/8 in. (32.1 x 27 cm),"Purchase, Nineteenth-Century, Modern and Contemporary Funds, Leonora Brenauer Bequest, in memory of her father, Joseph B. Brenauer, Catharine Lorillard Wolfe Collection, Wolfe Fund, and Paul L. and Marlene A. Herring and John D. Herring Gift, 2009",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/440726,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -08.19,false,true,436036,European Paintings,Painting,"Johann (1498–1537), Duke of Saxony",,,,,,Artist,,Lucas Cranach the Elder,"German, Kronach 1472–1553 Weimar",,"Cranach, Lucas, the Elder",German,1472,1553,ca. 1534–37,1534,1537,Oil on beech,25 5/8 x 17 3/8 in. (65.1 x 44.1 cm),"Rogers Fund, 1908",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436036,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -11.15,false,true,436038,European Paintings,Painting,Judith with the Head of Holofernes,,,,,,Artist,,Lucas Cranach the Elder,"German, Kronach 1472–1553 Weimar",,"Cranach, Lucas, the Elder",German,1472,1553,ca. 1530,1525,1535,Oil on linden,35 1/4 x 24 3/8 in. (89.5 x 61.9 cm),"Rogers Fund, 1911",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436038,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -57.22,false,true,436039,European Paintings,Painting,The Martyrdom of Saint Barbara,,,,,,Artist,,Lucas Cranach the Elder,"German, Kronach 1472–1553 Weimar",,"Cranach, Lucas, the Elder",German,1472,1553,ca. 1510,1505,1515,Oil on linden,Overall 60 3/8 x 54 1/4 in. (153.4 x 137.8 cm); painted surface 59 3/8 x 53 1/8 in. (150.8 x 134.9 cm),"Rogers Fund, 1957",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436039,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -28.221,true,true,436037,European Paintings,Painting,The Judgment of Paris,,,,,,Artist,,Lucas Cranach the Elder,"German, Kronach 1472–1553 Weimar",,"Cranach, Lucas, the Elder",German,1472,1553,ca. 1528,1523,1533,Oil on beech,40 1/8 x 28in. (101.9 x 71.1cm),"Rogers Fund, 1928",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436037,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -71.128,false,true,436046,European Paintings,Painting,"Johann I (1468–1532), the Constant, Elector of Saxony",,,,,,Artist,Workshop of,Lucas Cranach the Elder,"German, Kronach 1472–1553 Weimar",,"Cranach, Lucas, the Elder",German,1472,1553,1532–33,1532,1533,"Oil on canvas, transferred from wood, with letterpress-printed paper labels",8 1/4 x 5 7/8 in. (21 x 14.9 cm),"Purchase, 1871",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436046,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2006.469,false,true,439081,European Paintings,Painting,Saint Maurice,,,,,,Artist,,Lucas Cranach the Elder and Workshop,"German, Kronach 1472–1553 Weimar",,"Cranach, Lucas, the Elder, and Workshop",German,1472,1553,ca. 1520–25,1520,1525,Oil on linden,54 x 15 1/2 in. (137.2 x 39.4 cm),"Bequest of Eva F. Kollsman, 2005",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/439081,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -46.179.1,false,true,436043,European Paintings,Painting,"Friedrich III (1463–1525), the Wise, Elector of Saxony",,,,,,Artist,,Lucas Cranach the Elder and Workshop,"German, Kronach 1472–1553 Weimar",,"Cranach, Lucas, the Elder, and Workshop",German,1472,1553,1533,1533,1533,"Oil on beech, with letterpress-printed paper labels",8 x 5 5/8 in. (20.3 x 14.3 cm),"Gift of Robert Lehman, 1946",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436043,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -46.179.2,false,true,436045,European Paintings,Painting,"Johann I (1468–1532), the Constant, Elector of Saxony",,,,,,Artist,,Lucas Cranach the Elder and Workshop,"German, Kronach 1472–1553 Weimar",,"Cranach, Lucas, the Elder, and Workshop",German,1472,1553,1532–33,1532,1533,"Oil on beech, with letterpress-printed paper labels",8 x 5 5/8 in. (20.3 x 14.3 cm),"Gift of Robert Lehman, 1946",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436045,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -55.220.2,false,true,436047,European Paintings,Painting,Martin Luther (1483–1546),,,,,,Artist,Workshop of,Lucas Cranach the Elder,"German, Kronach 1472–1553 Weimar",,"Cranach, Lucas, the Elder",German,1472,1553,probably 1532,1532,1532,Oil on wood,13 1/8 x 9 1/8 in. (33.3 x 23.2 cm),"Gift of Robert Lehman, 1955",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436047,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1981.57.1,false,true,436033,European Paintings,Painting,Lukas Spielhausen,,,,,,Artist,,Lucas Cranach the Elder,"German, Kronach 1472–1553 Weimar",,"Cranach, Lucas, the Elder",German,1472,1553,1532,1532,1532,Oil and gold on beech,20 x 14 3/8 in. (50.8 x 36.5 cm),"Bequest of Gula V. Hirschland, 1980",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436033,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -29.100.24,false,true,436040,European Paintings,Painting,Portrait of a Man with a Rosary,,,,,,Artist,,Lucas Cranach the Elder,"German, Kronach 1472–1553 Weimar",,"Cranach, Lucas, the Elder",German,1472,1553,ca. 1508,1503,1513,Oil on oak,18 3/4 x 13 7/8 in. (47.6 x 35.2cm),"H. O. Havemeyer Collection, Bequest of Mrs. H. O. Havemeyer, 1929",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436040,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1982.60.48,false,true,436042,European Paintings,Painting,Venus and Cupid,,,,,,Artist,,Lucas Cranach the Elder,"German, Kronach 1472–1553 Weimar",,"Cranach, Lucas, the Elder",German,1472,1553,ca. 1525–27,1525,1527,Oil on wood,Diameter 4 3/4 in. (12.1 cm),"The Jack and Belle Linsky Collection, 1982",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436042,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.201.11,false,true,436041,European Paintings,Painting,Samson and Delilah,,,,,,Artist,,Lucas Cranach the Elder,"German, Kronach 1472–1553 Weimar",,"Cranach, Lucas, the Elder",German,1472,1553,ca. 1528–30,1528,1530,Oil on beech,22 1/2 x 14 7/8 in. (57.2 x 37.8cm),"Bequest of Joan Whitney Payson, 1975",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436041,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -39.65.8,false,true,436823,European Paintings,Painting,"Charles Beauclerk (1670–1726), Duke of St. Albans",,,,,,Artist,,Sir Godfrey Kneller,"German, Lübeck 1646–1723 London",,"Kneller, Godfrey, Sir",German,1646,1723,ca. 1690–95,1690,1695,Oil on canvas,49 7/8 x 40 1/2 in. (126.7 x 102.9 cm),"Bequest of Jacob Ruppert, 1939",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436823,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -96.30.6,false,true,436824,European Paintings,Painting,"Lady Mary Berkeley, Wife of Thomas Chambers",,,,,,Artist,,Sir Godfrey Kneller,"German, Lübeck 1646–1723 London",,"Kneller, Godfrey, Sir",German,1646,1723,ca. 1700,1695,1705,Oil on canvas,29 x 25 in. (73.7 x 63.5 cm),"Gift of George A. Hearn, 1896",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436824,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -85.9,false,true,436199,European Paintings,Painting,Christ Healing the Sick,,,,,,Artist,,Christian Wilhelm Ernst Dietrich,"German, Weimar 1712–1774 Dresden",,"Dietrich, Christian Wilhelm Ernst",German,1712,1774,1742,1742,1742,Oil on canvas,35 1/8 x 41 3/8 in. (89.2 x 105.1 cm),"Gift of William H. Webb, 1885",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436199,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -71.162,false,true,436200,European Paintings,Painting,The Adoration of the Shepherds,,,,,,Artist,,Christian Wilhelm Ernst Dietrich,"German, Weimar 1712–1774 Dresden",,"Dietrich, Christian Wilhelm Ernst",German,1712,1774,1760s,1760,1769,Oil on canvas,21 5/8 x 28 3/4 in. (54.9 x 73 cm),"Purchase, 1871",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436200,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.192,false,true,439122,European Paintings,Painting,Gothic Windows in the Ruins of the Monastery at Oybin,,,,,,Artist,,Carl Gustav Carus,"German, Leipzig 1789–1869 Dresden",,"Carus, Carl Gustav",German,1789,1869,ca. 1828,1823,1833,Oil on canvas,17 x 13 1/4 in. (43.2 x 33.7 cm) Frame: 21 3/4 x 17 7/8 x 2 1/4 in. (55.2 x 45.4 x 5.7 cm),"Purchase, 2005 Benefit Fund, and Anna-Maria and Stephen Kellen Foundation and Eugene V. Thaw Gifts, 2007",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/439122,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.164.1,false,true,438947,European Paintings,Painting,An Overgrown Mineshaft,,,,,,Artist,,Carl Gustav Carus,"German, Leipzig 1789–1869 Dresden",,"Carus, Carl Gustav",German,1789,1869,ca. 1824,1819,1829,"Oil on paper, laid down on cardboard",11 1/4 x 8 1/4 in. (28.6 x 21 cm),"Gift of Eugene V. Thaw, 2007",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/438947,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -16.16,false,true,436609,European Paintings,Painting,In the Studio,,,,,,Artist,,Hugo von Habermann,"German, Dillingen 1849–1929 Munich",,"Habermann, Hugo von",German,1849,1929,1885,1885,1885,Oil on canvas,39 5/8 x 37 3/4 in. (100.6 x 95.9 cm),"Reisinger Fund, 1916",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436609,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -56.190,false,true,437648,European Paintings,Painting,Sir James Dashwood (1715–1779),,,,,,Artist,,Enoch Seeman the Younger,"German, Danzig ca. 1690–1744 London",,"Seeman, Enoch, the Younger",German,1685,1744,1737,1737,1737,Oil on canvas,96 x 60 1/4 in. (243.8 x 153 cm),"Victor Wilbour Memorial Fund, 1956",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437648,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2008.6,false,true,439346,European Paintings,Painting,At the Edge of the Forest,,,,,,Artist,,August Heinrich,"German, Dresden 1794–1822 Innsbruck",,"Heinrich, August",German,1794,1822,ca. 1820,1815,1825,Oil on canvas,10 3/4 x 12 3/4 in. (27.3 x 32.4 cm),"Catharine Lorillard Wolfe Collection, Wolfe Fund; and Wolfe Fund and Gift of Frederick Loeser, by exchange, 2008",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/439346,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2010.279,false,true,440888,European Paintings,Painting,Window,,,,,,Artist,,Anton Dieffenbach,"German, Wiesbaden 1831–1904 Hohwald",,"Dieffenbach, Anton",German,1831,1904,1856,1856,1856,"Oil on paper, laid down on canvas",14 3/8 x 9 7/8 in. (36.5 x 25.1 cm),"Purchase, Gifts of Mr. and Mrs. Charles Zadok and William Schaus and Bequest of Mary Jane Dastich, by exchange, 2010",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/440888,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -17.120.203,false,true,437860,European Paintings,Painting,Going Home,,,,,,Artist,,Fritz von Uhde,"German, Wolkenburg 1848–1911 Munich",,"von Uhde, Fritz",German,1848,1911,ca. 1889,1884,1894,Oil on wood,30 7/8 x 39 1/4 in. (78.4 x 99.7 cm),"Mr. and Mrs. Isaac D. Fletcher Collection, Bequest of Isaac D. Fletcher, 1917",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437860,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -06.1038,false,true,436657,European Paintings,Painting,"Benedikt von Hertenstein (born about 1495, died 1522)",,,,,,Artist,,Hans Holbein the Younger,"German, Augsburg 1497/98–1543 London",,"Holbein, Hans, the Younger",German,1497,1543,1517,1517,1517,"Oil and gold on paper, laid down on wood",Overall 20 1/2 x 15 in. (52.4 x 38.1 cm); painted surface 20 3/8 x 14 5/8 in. (51.4 x 37.1 cm),"Rogers Fund, aided by subscribers, 1906",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436657,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -49.7.28,false,true,436668,European Paintings,Painting,Portrait of a Man (Sir Ralph Sadler?),,,,,,Artist,Workshop of,Hans Holbein the Younger,"German, Augsburg 1497/98–1543 London",,"Holbein, Hans, the Younger",German,1497,1543,1535,1535,1535,Oil and gold on oak,Diameter 12 in. (30.5 cm),"The Jules Bache Collection, 1949",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436668,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -49.7.29,false,true,436659,European Paintings,Painting,Derick Berck of Cologne,,,,,,Artist,,Hans Holbein the Younger,"German, Augsburg 1497/98–1543 London",,"Holbein, Hans, the Younger",German,1497,1543,1536,1536,1536,"Oil on canvas, transferred from wood",21 x 16 3/4 in. (53.3 x 42.5 cm),"The Jules Bache Collection, 1949",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436659,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -49.7.30,false,true,436667,European Paintings,Painting,Portrait of a Young Woman,,,,,,Artist,Workshop of,Hans Holbein the Younger,"German, Augsburg 1497/98–1543 London",,"Holbein, Hans, the Younger",German,1497,1543,ca. 1540–45,1540,1545,Oil and gold on oak,11 1/8 x 9 1/8 in. (28.3 x 23.2 cm),"The Jules Bache Collection, 1949",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436667,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -50.135.4,true,true,436658,European Paintings,Painting,Hermann von Wedigh III (died 1560),,,,,,Artist,,Hans Holbein the Younger,"German, Augsburg 1497/98–1543 London",,"Holbein, Hans, the Younger",German,1497,1543,1532,1532,1532,Oil and gold on oak,"16 5/8 x 12 3/4 in. (42.2 x 32.4 cm), with added strip of 1/2 in. (1.3 cm) at bottom","Bequest of Edward S. Harkness, 1940",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436658,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.40.637,false,true,436665,European Paintings,Painting,"Lady Lee (Margaret Wyatt, born about 1509)",,,,,,Artist,Workshop of,Hans Holbein the Younger,"German, Augsburg 1497/98–1543 London",,"Holbein, Hans, the Younger",German,1497,1543,early 1540s,1540,1543,Oil and gold on oak,17 3/8 × 13 3/8 in. (44.1 × 34 cm),"Bequest of Benjamin Altman, 1913",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436665,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -50.145.24,false,true,436660,European Paintings,Painting,Portrait of a Man in a Red Cap,,,,,,Artist,,Hans Holbein the Younger,"German, Augsburg 1497/98–1543 London",,"Holbein, Hans, the Younger",German,1497,1543,1532–35,1532,1535,"Oil and gold on parchment, laid down on linden","Overall, with engaged frame, diameter 5 in. (12.7 cm); painted surface diameter 3 3/4 in. (9.5 cm)","Bequest of Mary Stillman Harkness, 1950",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436660,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2000.51,true,true,438417,European Paintings,Painting,Two Men Contemplating the Moon,,,,,,Artist,,Caspar David Friedrich,"German, Greifswald 1774–1840 Dresden",,"Friedrich, Caspar David",German,1774,1840,ca. 1825–30,1825,1830,Oil on canvas,13 3/4 x 17 1/4 in. (34.9 x 43.8 cm),"Wrightsman Fund, 2000",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/438417,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -87.15.23,false,true,437975,European Paintings,Painting,Sunset after a Storm on the Coast of Sicily,,,,,,Artist,,Andreas Achenbach,"German, Kassel 1815–1910 Düsseldorf",,"Achenbach, Andreas",German,1815,1910,1853,1853,1853,Oil on canvas,32 3/4 x 42 1/4 in. (83.2 x 107.3 cm),"Catharine Lorillard Wolfe Collection, Bequest of Catharine Lorillard Wolfe, 1887",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437975,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -71.34,false,true,437759,European Paintings,Painting,Portrait of a Woman,,,,,,Artist,,Bernhard Strigel,"German, Memmingen 1460–1528 Memmingen",,"Strigel, Bernhard",German,1460,1528,ca. 1510–15,1510,1515,Oil on linden,15 1/8 x 10 1/2 in. (38.4 x 26.7 cm),"Purchase, 1871",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437759,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -17.190.5,false,true,436245,European Paintings,Painting,Virgin and Child,,,,,,Artist,,Albrecht Dürer,"German, Nuremberg 1471–1528 Nuremberg",,"Dürer, Albrecht",German,1471,1528,1516,1516,1516,Oil on spruce,11 x 7 3/8 in. (27.9 x 18.7 cm); set in panel 11 x 8 1/4 in. (27.9 x 22.2 cm),"Gift of J. Pierpont Morgan, 1917",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436245,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.40.633,true,true,436244,European Paintings,Painting,Virgin and Child with Saint Anne,,,,,,Artist,,Albrecht Dürer,"German, Nuremberg 1471–1528 Nuremberg",,"Dürer, Albrecht",German,1471,1528,probably 1519,1519,1519,Oil on linden,23 5/8 x 19 5/8 in. (60 x 49.8 cm),"Bequest of Benjamin Altman, 1913",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436244,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -32.100.64,false,true,436243,European Paintings,Painting,Salvator Mundi,,,,,,Artist,,Albrecht Dürer,"German, Nuremberg 1471–1528 Nuremberg",,"Dürer, Albrecht",German,1471,1528,ca. 1505,1500,1510,Oil on linden,22 7/8 x 18 1/2in. (58.1 x 47cm),"The Friedsam Collection, Bequest of Michael Friedsam, 1931",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436243,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -12.194,false,true,435635,European Paintings,Painting,Chancellor Leonhard von Eck (1480–1550),,,,,,Artist,,Barthel Beham,"German, Nuremberg ca. 1502–1540 Italy",,"Beham, Barthel",German,1502,1540,1527,1527,1527,Oil on spruce,22 1/8 x 14 7/8 in. (56.2 x 37.8 cm),"John Stewart Kennedy Fund, 1912",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435635,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -16.15,false,true,437848,European Paintings,Painting,Landscape,,,,,,Artist,,Wilhelm Trübner,"German, Heidelberg 1851–1917 Karlsruhe",,"Trübner, Wilhelm",German,1851,1917,1910,1910,1910,Oil on canvas,29 7/8 x 24 1/4 in. (75.9 x 61.5 cm),"Reisinger Fund, 1916",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437848,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -12.115,false,true,435585,European Paintings,Painting,Portrait of a Man and His Wife (Lorenz Kraffter and Honesta Merz?),,,,,,Artist,,Ulrich Apt the Elder,"German, Augsburg ca. 1460–1532 Augsburg",,"Apt, Ulrich, the Elder",German,1460,1532,1512,1512,1512,Oil on linden,13 x 24 7/8 in. (33 x 63.2 cm),"Rogers Fund, 1912",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435585,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -89.15.20,false,true,435776,European Paintings,Painting,Joseph Interpreting the Dreams of Pharaoh,,,,,,Artist,Attributed to,Jörg Breu the Younger,"German, Augsburg ca. 1510–1547 Augsburg",,"Breu, Jörg, the Younger",German,1510,1547,ca. 1534–47,1534,1547,Distemper on linen,67 5/8 x 57 1/4 in. (171.8 x 145.4 cm),"Marquand Collection, Gift of Henry G. Marquand, 1889",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435776,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.164.8,false,true,438953,European Paintings,Painting,The Cemetery at Pronoia near Nauplia,,,,,,Artist,,Carl Rottmann,"German, Handschuhsheim 1797–1850 Munich",,"Rottmann, Carl",German,1797,1850,ca. 1841–47,1836,1847,Oil on canvas,10 x 12 in. (25.4 x 30.5 cm),"Gift of Eugene V. Thaw, 2007",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/438953,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -39.65.4,false,true,436877,European Paintings,Painting,Prince Regent Luitpold of Bavaria (1821–1912),,,,,,Artist,,Franz von Lenbach,"German, Schrobenhausen 1836–1904 Munich",,"Lenbach, Franz von",German,1836,1904,1902,1902,1902,Oil on board,30 x 24 1/4 in. (76.2 x 61.5 cm),"Bequest of Jacob Ruppert, 1939",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436877,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -25.110.46,false,true,436876,European Paintings,Painting,"Marion Lenbach (1892–1947), the Artist's Daughter",,,,,,Artist,,Franz von Lenbach,"German, Schrobenhausen 1836–1904 Munich",,"Lenbach, Franz von",German,1836,1904,1900,1900,1900,Oil on canvas,58 7/8 x 41 1/2 in. (149.5 x 105.4 cm),"Bequest of Collis P. Huntington, 1900",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436876,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -43.161,false,true,436997,European Paintings,"Painting, part of the wing of an altarpiece",The Crucifixion,,,,,,Artist,,Master of the Berswordt Altar,"German, Westphalian, active ca. 1400–35",,Master of the Berswordt Altar,German,1400,1435,ca. 1400,1395,1405,"Oil, egg(?), and gold on plywood, transferred from wood",23 1/2 x 17 in. (59.7 x 43.2 cm),"Rogers Fund, 1943",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436997,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2001.216.2,false,true,438466,European Paintings,Painting,The Flagellation,,,,,,Artist,,Master of the Berswordt Altar,"German, Westphalian, active ca. 1400–35",,Master of the Berswordt Altar,German,1400,1435,ca. 1400,1395,1405,"Oil, egg(?), and gold on plywood, transferred from wood",22 3/4 x 16 7/8 in. (57.8 x 42.9 cm),"Bequest of Hertha Katz, 2000",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/438466,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1982.60.35,false,true,436034,European Paintings,Painting,Christ and the Adulteress,,,,,,Artist,,Lucas Cranach the Younger and Workshop,"German, Wittenberg 1515–1586 Wittenberg",,"Cranach, Lucas, the Younger, and Workshop",German,1515,1586,ca. 1545–50,1545,1550,Oil on beech,6 1/4 x 8 1/2 in. (15.9 x 21.6 cm),"The Jack and Belle Linsky Collection, 1982",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436034,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1982.60.36,false,true,436035,European Paintings,Painting,Christ Blessing the Children,,,,,,Artist,,Lucas Cranach the Younger and Workshop,"German, Wittenberg 1515–1586 Wittenberg",,"Cranach, Lucas, the Younger, and Workshop",German,1515,1586,ca. 1545–50,1545,1550,Oil on beech,6 1/2 x 8 3/4 in. (16.5 x 22.2 cm),"The Jack and Belle Linsky Collection, 1982",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436035,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2006.566,false,true,439118,European Paintings,Painting,The Reverend Philip Cocks (1735–1797),,,,,,Artist,,Johan Joseph Zoffany,"German, near Frankfurt 1733–1810 London",,"Zoffany, Johan Joseph",German,1733,1810,late 1760s,1767,1769,Oil on canvas,35 1/2 x 27 1/4 in. (90.2 x 69.2 cm),"Gift of Mrs. Henry A. Grunwald, 2006",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/439118,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -21.84,false,true,436835,European Paintings,Painting,The Ascension of Christ,,,,,,Artist,,Hans Süss von Kulmbach,"German, Kulmbach ca. 1480–1522 Nuremberg",,"Kulmbach, Hans Süss von",German,1480,1522,1513,1513,1513,Oil on fir,Overall 24 1/4 x 15 in. (61.5 x 38.1 cm); painted surface 24 1/4 x 14 1/8 in. (61.5 x 35.9 cm),"Rogers Fund, 1921",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436835,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -17.190.21,false,true,436834,European Paintings,Painting,Portrait of a Young Man; (reverse) Girl Making a Garland,,,,,,Artist,,Hans Süss von Kulmbach,"German, Kulmbach ca. 1480–1522 Nuremberg",,"Kulmbach, Hans Süss von",German,1480,1522,ca. 1508,1503,1513,Oil on poplar,7 x 5 1/2 in. (17.8 x 14 cm),"Gift of J. Pierpont Morgan, 1917",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436834,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2008.7,false,true,439333,European Paintings,Painting,Wanderer in the Storm,,,,,,Artist,,Julius von Leypold,"German, Dresden 1806–1874 Niederlößnitz",,"Leypold, Julius von",German,1806,1874,1835,1835,1835,Oil on canvas,16 3/4 x 22 1/4 in. (42.5 x 56.5 cm),"Catharine Lorillard Wolfe Collection, Wolfe Fund, 2008",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/439333,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -01.21,false,true,437944,European Paintings,Painting,Florinda,,,,,,Artist,,Franz Xaver Winterhalter,"German, Menzenschwand 1805–1873 Frankfurt",,"Winterhalter, Franz Xaver",German,1805,1873,1853,1853,1853,Oil on canvas,70 1/4 x 96 3/4 in. (178.4 x 245.7 cm),"Bequest of William H. Webb, 1899",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437944,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1978.403,false,true,437942,European Paintings,Painting,"The Empress Eugénie (Eugénie de Montijo, 1826–1920, Condesa de Teba)",,,,,,Artist,,Franz Xaver Winterhalter,"German, Menzenschwand 1805–1873 Frankfurt",,"Winterhalter, Franz Xaver",German,1805,1873,1854,1854,1854,Oil on canvas,36 1/2 x 29 in. (92.7 x 73.7 cm),"Purchase, Mr. and Mrs. Claus von Bülow Gift, 1978",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437942,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -67.187.119,false,true,437943,European Paintings,Painting,"Countess Alexander Nikolaevitch Lamsdorff (Maria Ivanovna Beck, 1835–1866)",,,,,,Artist,,Franz Xaver Winterhalter,"German, Menzenschwand 1805–1873 Frankfurt",,"Winterhalter, Franz Xaver",German,1805,1873,1859,1859,1859,Oil on canvas,57 1/4 x 45 1/4 in. (145.4 x 114.9 cm),"Bequest of Miss Adelaide Milton de Groot (1876–1967), 1967",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437943,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -89.15.28,false,true,437237,European Paintings,Painting,Portrait of a Woman,,,,,,Artist,,Jürgen Ovens,"German, Tönning 1623–1678 Friedrichstadt",,"Ovens, Jürgen",German,1623,1678,1650,1650,1650,Oil on canvas,49 3/8 x 37 3/4 in. (125.4 x 95.9 cm),"Marquand Collection, Gift of Henry G. Marquand, 1889",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437237,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -87.2,false,true,437295,European Paintings,Painting,Thusnelda at the Triumphal Entry of Germanicus into Rome,,,,,,Artist,,Karl Theodor von Piloty,"German, Munich 1826–1886 Ambach bei Munich",,"Piloty, Karl Theodor von",German,1826,1886,ca. 1875,1870,1880,Oil on canvas,53 x 77 1/4 in. (134.6 x 196.2 cm),"Gift of Horace Russell, 1887",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437295,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1999.121,false,true,438390,European Paintings,Painting,Nymph and Shepherd,,,,,,Artist,,Johann Liss,"German, Oldenburg ca. 1595/1600–1631 Verona",,"Liss, Johann",German,1595,1631,ca. 1625,1620,1630,Oil on canvas,41 1/8 x 37 3/8 in. (104.5 x 94.9 cm),"Purchase, Lila Acheson Wallace Gift, Victor Wilbour Memorial Fund, The Alfred N. Punnett Endowment Fund, and Marquand and Curtis Funds, and Bequests of Theodore M. Davis and Helen R. Bleibtreu, by exchange, 1999",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/438390,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -21.152.1,false,true,436305,European Paintings,Painting,Still Life,,,,,,Artist,,Georg Flegel,"German, Olomouc (Olmütz) 1566–1638 Frankfurt",,"Flegel, Georg",German,1566,1638,probably ca. 1625–30,1625,1630,Oil on wood,10 5/8 x 13 3/8 in. (27 x 34 cm),"Gift of Dr. W. Bopp, 1921",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436305,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -48.141,false,true,437067,European Paintings,Painting,Johann Joachim Winckelmann (1717–1768),,,,,,Artist,,Anton Raphael Mengs,"German, Ústi nad Labem (Aussig) 1728–1779 Rome",,"Mengs, Anton Raphael",German,1728,1779,ca. 1777,1772,1782,Oil on canvas,25 x 19 3/8 in. (63.5 x 49.2 cm),"Harris Brisbane Dick Fund, 1948",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437067,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2010.445,false,true,441115,European Paintings,Painting,Self-Portrait,,,,,,Artist,,Anton Raphael Mengs,"German, Ústi nad Labem (Aussig) 1728–1779 Rome",,"Mengs, Anton Raphael",German,1728,1779,1776,1776,1776,Oil on canvas,35 1/2 x 25 7/8 in. (90 x 65.5 cm),"Harris Brisbane Dick Fund, 2010",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/441115,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -09.48,false,true,437787,European Paintings,Painting,At Lake Garda,,,,,,Artist,,Hans Thoma,"German, Bernau im Schwarzwald 1839–1924 Karlsruhe",,"Thoma, Hans",German,1839,1924,1907,1907,1907,Oil on millboard,33 x 26 3/4 in. (83.8 x 67.9 cm),"Gift of Hugo Reisinger, 1909",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437787,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -14.40.630,false,true,436942,European Paintings,Painting,Ulrich Fugger the Younger (1490–1525),,,,,,Artist,,Hans Maler,"German, Ulm, born ca. 1480, died ca. 1526–29 Schwaz (?)",,"Maler, Hans",German,1475,1529,1525,1525,1525,Oil on linden,15 7/8 x 12 3/4 in. (40.3 x 32.4 cm),"Bequest of Benjamin Altman, 1913",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436942,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -32.100.33,false,true,436941,European Paintings,Painting,Sebastian Andorfer (1469–1537),,,,,,Artist,,Hans Maler,"German, Ulm, born ca. 1480, died ca. 1526–29 Schwaz (?)",,"Maler, Hans",German,1475,1529,1517,1517,1517,Oil on Swiss stone pine,17 x 14 1/8 in. (43.2 x 35.9 cm),"The Friedsam Collection, Bequest of Michael Friedsam, 1931",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436941,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1997.157,false,true,437983,European Paintings,Painting,A Roman Landscape with Figures,,,,,,Artist,,Goffredo Wals,"German, Cologne, born ca. 1590–95, died 1638–40 Calabria",,"Wals, Goffredo",German,1590,1640,probably 1630s,1630,1639,Oil on copper,Diameter 16 in. (40.6 cm),"Wrightsman Fund, 1997",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437983,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1983.451,true,true,435600,European Paintings,Painting,Saint John on Patmos,,,,,,Artist,,Hans Baldung (called Hans Baldung Grien),"German, Schwäbisch Gmünd (?) 1484/85–1545 Strasbourg (Strassburg)",,"Baldung, Hans (called Hans Baldung Grien)",German,1484,1545,ca. 1511,1506,1516,"Oil, gold, and white metal on spruce",Overall 35 1/4 x 30 1/4 in. (89.5 x 76.8 cm); painted surface 34 3/8 x 29 3/4 in. (87.3 x 75.6 cm),"Purchase, Rogers and Fletcher Funds; The Vincent Astor Foundation, The Dillon Fund, The Charles Engelhard Foundation, Lawrence A. Fleischman, Mrs. Henry J. Heinz II, The Willard T. C. Johnson Foundation Inc., Reliance Group Holdings Inc., Baron H. H. Thyssen-Bornemisza, and Mr. and Mrs. Charles Wrightsman Gifts; Joseph Pulitzer Bequest; special funds; and other gifts and bequests, by exchange, 1983",,,,,,,,,,,,Paintings,"The following credit line may be used for photographs, reproductions, etc.: Contributions from various donors supplemented by Museum purchase funds, 1983",http://www.metmuseum.org/art/collection/search/435600,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -42.53.3,false,true,436640,European Paintings,"Painting, miniature",Lola Montez (1818–1861),,,,,,Artist,Attributed to,Josef Heigel,"German, 1780–1837",,"Heigel, Josef",German,1780,1837,,1800,1837,Ivory,"Oval, 2 1/2 x 2 1/8 in. (65 x 54 mm)","Gift of Helen O. Brice, 1942",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/436640,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -62.122.109,false,true,436666,European Paintings,"Painting, miniature","Portrait of a Man, Said to Be Arnold Franz",,,,,,Artist,Imitator of,Hans Holbein the Younger,17th or early 18th century,,"Holbein, Hans, the Younger",German,1497,1543,,1600,1729,Vellum laid on card,Diameter 2 1/8 in. (53 mm),"Bequest of Millie Bruhl Fredrick, 1962",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/436666,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -62.122.154,false,true,437752,European Paintings,"Painting, miniature",Portrait of a Man,,,,,,Artist,Attributed to,Theodor Friedrich Stein,"German, active ca. 1750–88",,"Stein, Theodor Friedrich",German,1750,1788,,1750,1788,Ivory,1 5/8 x 2 1/4 in. (41 x 62 mm),"Bequest of Millie Bruhl Fredrick, 1962",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/437752,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -95.14.66,false,true,437224,European Paintings,"Painting, miniature",Portrait of a Man,,,,,,Artist,,Johann Esaias Nilson,"German, Augsburg 1721–1788 Augsburg",,"Nilson, Johann Esaias",German,1721,1788,,1741,1788,Ivory,"Oval, 1 7/8 x 1 1/2 in. (49 x 37 mm)","The Moses Lazarus Collection, Gift of Josephine and Sarah Lazarus, in memory of their father, 1888–95",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/437224,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -48.187.495,false,true,437962,European Paintings,"Painting, miniature",Portrait of a Young Man,,,,,,Artist,,Christian Friedrich Zincke,"German, Dresden 1683/85–1767 London",,"Zincke, Christian Friedrich",German,1683,1767,,1703,1767,Enamel,"Oval, 1 3/4 x 1 3/8 in. (45 x 36 mm)","Bequest of Catherine D. Wentworth, 1948",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/437962,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -62.122.114,false,true,437963,European Paintings,"Painting, miniature",Portrait of a Woman,,,,,,Artist,Attributed to,Christian Friedrich Zincke,"German, Dresden 1683/85–1767 London",,"Zincke, Christian Friedrich",German,1683,1767,,1703,1767,Enamel,"Oval, 1 7/8 x 1 1/2 in. (46 x 38 mm)","Bequest of Millie Bruhl Fredrick, 1962",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/437963,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -62.122.115,false,true,437959,European Paintings,"Painting, miniature",Portrait of a Man,,,,,,Artist,,Christian Friedrich Zincke,"German, Dresden 1683/85–1767 London",,"Zincke, Christian Friedrich",German,1683,1767,,1703,1767,Enamel,"Oval, 1 7/8 x 1 1/2 in. (47 x 38 mm)","Bequest of Millie Bruhl Fredrick, 1962",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/437959,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -51.160,false,true,437068,European Paintings,"Painting, miniature",The Vision of Saint Anthony of Padua,,,,,,Artist,,Anton Raphael Mengs,"German, Ústi nad Labem (Aussig) 1728–1779 Rome",,"Mengs, Anton Raphael",German,1728,1779,,1758,1758,Ivory,5 1/8 x 3 3/4 in. (130 x 96 mm),"Gift of Harry G. Friedman, 1951",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/437068,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2006.151.1,false,true,438844,European Paintings,Painting,"Portrait of a Woman, Said to Be Caritas Pirckheimer (1467–1532)",,,,,,Artist,Imitator of,Albrecht Dürer,20th century,,"Dürer, Albrecht",German,1471,1528,,1900,1999,Oil on linen,18 1/2 x 14 1/2 in. (47 x 36.8 cm),"Gift of Julie and Lawrence Salander, 2006",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/438844,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -22.96,false,true,435764,European Paintings,Painting,Virgin and Child,,,,,,Artist,Workshop or Circle of,Hans Traut,"German, ca. 1500",,"Traut, Hans",German,1477,1516,,1495,1505,"Oil, gold, and silver on linden",15 5/8 x 12 1/8 in. (39.7 x 30.8 cm),"Purchase, Joseph Pulitzer Bequest, 1922",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435764,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -07.245.1,false,true,437494,European Paintings,Painting,"Friedrich I (1460–1536), Margrave of Brandenburg-Ansbach",,,,,,Artist,Attributed to,Franz Wolfgang Rohrich,"German, 1787–1834",,"Rohrich, Franz Wolfgang",German,1787,1834,,1807,1834,Oil on canvas,30 1/4 x 22 3/8 in. (76.8 x 56.8 cm),"Gift of Laura Wolcott Lowndes, in memory of her father, Lucius Tuckerman, 1907",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437494,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -07.245.2,false,true,437495,European Paintings,Painting,Sophia (1464–1512) of Poland,,,,,,Artist,,Franz Wolfgang Rohrich,"German, 1787–1834",,"Rohrich, Franz Wolfgang",German,1787,1834,,1807,1834,Oil on canvas,30 1/4 x 22 1/4 in. (76.8 x 56.5 cm),"Gift of Laura Wolcott Lowndes, in memory of her father, Lucius Tuckerman, 1907",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437495,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -22.174,false,true,436591,European Paintings,Painting,Portrait of a Woman,,,,,,Artist,,Johann Nikolaus Grooth,"German, 1723?–1797",,"Grooth, Johann Nikolaus",German,1723,1797,,1743,1797,Oil on canvas,32 x 25 5/8 in. (81.3 x 65.1 cm),"Gift of Édouard Jonas, 1922",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436591,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -20.155.4,false,true,436664,European Paintings,Painting,"Lady Guildford (Mary Wotton, born 1500)",,,,,,Artist,Copy after,Hans Holbein the Younger,"British, 16th century",,"Holbein, Hans, the Younger",German,1497,1543,,1527,1527,Oil and gold on oak,32 1/8 x 26 1/8 in. (81.6 x 66.4 cm),"Bequest of William K. Vanderbilt, 1920",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436664,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1982.60.22,false,true,436781,European Paintings,Painting,The Adoration of the Christ Child,,,,,,Artist,Follower of,Jan Joest of Kalkar,"Netherlandish, active ca. 1515",,"Joest of Kalkar, Jan",German,1510,1520,,1510,1520,Oil on wood,Overall 41 x 28 1/4 in. (104.1 x 71.8 cm); painted surface 41 x 27 5/8 in. (104.1 x 70.2 cm),"The Jack and Belle Linsky Collection, 1982",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436781,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -87.15.110,false,true,436815,European Paintings,Painting,Crusaders before Jerusalem,,,,,,Artist,,Wilhelm von Kaulbach,"German, Arolsen 1804–1874 Munich",,"Kaulbach, Wilhelm von",German,1804,1874,,1825,1874,Oil on canvas,61 5/8 x 74 1/2 in. (156.5 x 189.2 cm),"Catharine Lorillard Wolfe Collection, Bequest of Catharine Lorillard Wolfe, 1887",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436815,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -71.142,false,true,436201,European Paintings,Painting,"Surprised, or Infidelity Found Out",,,,,,Artist,,Christian Wilhelm Ernst Dietrich,"German, Weimar 1712–1774 Dresden",,"Dietrich, Christian Wilhelm Ernst",German,1712,1774,,1732,1774,Oil on canvas,28 3/4 x 28 5/8 in. (73 x 72.7 cm),"Purchase, 1871",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436201,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -94.24.2,false,true,437642,European Paintings,Painting,Battle Scene: Arabs Making a Detour,,,,,,Artist,,Adolf Schreyer,"German, Frankfurt 1828–1899 Kronberg",,"Schreyer, Adolf",German,1828,1899,,1848,1899,Oil on canvas,59 3/8 x 99 1/2 in. (150.8 x 252.7 cm),"Gift of John Wolfe, 1893",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437642,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -87.15.127,false,true,437641,European Paintings,Painting,Arabs on the March,,,,,,Artist,,Adolf Schreyer,"German, Frankfurt 1828–1899 Kronberg",,"Schreyer, Adolf",German,1828,1899,,1848,1899,Oil on canvas,22 5/8 x 37 3/4 in. (57.5 x 95.9 cm),"Catharine Lorillard Wolfe Collection, Bequest of Catharine Lorillard Wolfe, 1887",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437641,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1982.60.38,false,true,435804,European Paintings,Painting,Katharina Merian,,,,,,Artist,Attributed to,Hans Brosamer,"German, active by 1536, probably died 1552",,"Brosamer, Hans",German,1536,1552,,1536,1552,"Oil, gold, and white metal on linden",Overall 18 1/4 x 13 1/8 in. (46.4 x 33.3 cm); painted surface 17 5/8 x 13 1/8 in. (44.8 x 33.3 cm),"The Jack and Belle Linsky Collection, 1982",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435804,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1982.60.34ab,false,true,437639,European Paintings,Painting,Christ before Pilate; The Resurrection,,,,,,Artist,,Ludwig Schongauer,"German, Colmar ca. 1440/55–1493/94 Colmar",,"Schongauer, Ludwig",German,1435,1494,,1479,1494,Oil on fir,"(a) overall 15 1/8 x 8 1/4 in. (38.4 x 21 cm), painted surface 14 3/8 x 7 3/4 in. (36.5 x 19.7 cm); (b) overall 15 1/8 x 8 1/4 in. (38.4 x 21 cm), painted surface 14 1/2 x 7 3/4 in. (36.8 x 19.7 cm)","The Jack and Belle Linsky Collection, 1982",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437639,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -12.75,false,true,436286,European Paintings,Painting,Portrait of a Man with a Moor's Head on His Signet Ring,,,,,,Artist,,Conrad Faber von Creuznach,"German, Kreuznach, active by 1524–died 1552/53 Frankfurt",,"Faber von Creuznach, Conrad",German,1524,1553,,1524,1553,"Oil, gold, and white metal on linden",20 7/8 x 14 1/8 in. (53 x 35.9 cm),"John Stewart Kennedy Fund, 1912",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436286,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -62.122.33,false,true,435999,European Paintings,"Painting, miniature",Portrait of a Woman,,,,,,Artist,,Samuel Cotes,"British, 1734–1818",,"Cotes, Samuel",British,1734,1818,1767,1767,1767,Ivory,"Oval, 1 1/2 x 1 1/8 in. (38 x 30 mm)","Bequest of Millie Bruhl Fredrick, 1962",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/435999,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.136.14,false,true,436058,European Paintings,"Painting, miniature",Portrait of a Woman,,,,,,Artist,,Richard Crosse,"British, 1742–1810",,"Crosse, Richard",British,1742,1810,possibly ca. 1780,1775,1785,Ivory,"Oval, 2 1/4 x 1 7/8 in. (58 x 48 mm)","Bequest of Margaret Crane Hurlbut, 1933",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/436058,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -26.168.59,false,true,437320,European Paintings,"Painting, miniature",Elizabeth Bushby,,,,,,Artist,,Andrew Plimer,"British, 1763–1837",,"Plimer, Andrew",British,1763,1837,1804,1804,1804,Ivory,"Oval, 3 x 2 1/4 in. (75 x 57 mm)","Bequest of Collis P. Huntington, 1900",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/437320,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -95.14.95,false,true,437950,European Paintings,"Painting, miniature",An Interesting Story (Miss Ray),,,,,,Artist,,William Wood,"British, 1769–1810",,"Wood, William",British,1769,1810,1806,1806,1806,Ivory,4 3/4 x 3 7/8 in. (119 x 97 mm),"The Moses Lazarus Collection, Gift of Josephine and Sarah Lazarus, in memory of their father, 1888–95",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/437950,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -39.122,false,true,437225,European Paintings,"Painting, miniature",Portrait of a Young Woman,,,,,,Artist,,James Nixon,"British, ca. 1741–1812",,"Nixon, James",British,1736,1812,ca. 1780–85,1780,1785,Ivory,"Oval, 3 3/4 x 3 in. (97 x 75 mm)","Fletcher Fund, 1939",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/437225,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -62.122.42,false,true,437226,European Paintings,"Painting, miniature",Portrait of a Woman,,,,,,Artist,,James Nixon,"British, ca. 1741–1812",,"Nixon, James",British,1736,1812,ca. 1790,1785,1795,Ivory,"Oval, 2 1/4 x 1 7/8 in. (56 x 47 mm)","Bequest of Millie Bruhl Fredrick, 1962",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/437226,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -95.14.60,false,true,437048,European Paintings,"Painting, miniature","Portrait of a Woman, Said to Be Lady Sophia Boyle",,,,,,Artist,,Anne Foldsone Mee,"British, ca. 1770–1851",,"Mee, Anne Foldsone",British,1765,1851,ca. 1790,1785,1795,Ivory,"Oval, 2 5/8 x 2 1/8 in. (67 x 53 mm)","The Moses Lazarus Collection, Gift of Josephine and Sarah Lazarus, in memory of their father, 1888–95",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/437048,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -17.190.1150,false,true,437047,European Paintings,"Painting, miniature, snuffbox","Portrait of a Woman, Possibly Barbara (1768–1829), Marchioness of Donegall",,,,,,Artist,,Anne Foldsone Mee,"British, ca. 1770–1851",,"Mee, Anne Foldsone",British,1765,1851,ca. 1790,1785,1795,Ivory,"Oval, 2 7/8 x 2 1/8 in. (73 x 53 mm)","Gift of J. Pierpont Morgan, 1917",,,,,,,,,,,,Miniatures|Metalwork-Gold and Platinum,,http://www.metmuseum.org/art/collection/search/437047,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -50.187.44,false,true,435634,European Paintings,"Painting, miniature",Miss Chambers,,,,,,Artist,,Isabella Beetham,"British, 1750–after 1809",,"Beetham, Isabella",British,1750,1809,after 1782,1782,1809,Ivory,"Oval, 2 1/4 x 1 7/8 in. (59 x 48 mm)","The Glenn Tilley Morse Collection, Bequest of Glenn Tilley Morse, 1950",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/435634,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -45.95,false,true,436635,European Paintings,"Painting, miniature",Agnes Sewell,,,,,,Artist,,Thomas Hazlehurst,"British, ca. 1740–ca. 1821",,"Hazlehurst, Thomas",British,1740,1821,possibly ca. 1800,1795,1805,Ivory,"Oval, 3 x 2 3/8 in. (75 x 60 mm)","Gift of Elise Shackelford Black, 1945",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/436635,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -62.122.23,false,true,437140,European Paintings,"Painting, miniature",Portrait of a Man,,,,,,Artist,Attributed to,Monogrammist FS (Franciszek Smiadecki?),"British, active ca. 1650–65",,Monogrammist FS (Franciszek Smiadecki?),British,1650,1665,ca. 1650,1645,1655,Oil on card with gessoed back,"Oval, 2 5/8 x 2 1/4 in. (67 x 56 mm)","Bequest of Millie Bruhl Fredrick, 1962",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/437140,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -24.80.510,false,true,437176,European Paintings,"Painting, miniature",Portrait of a Man,,,,,,Artist,,David Myers,"British, active ca. 1659–76",,"Myers, David",British,1659,1676,1664,1664,1664,Vellum laid on prepared gessoed card,"Oval, 2 1/2 x 2 in. (62 x 51 mm)","Bequest of Mary Clark Thompson, 1923",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/437176,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -15.43.287,false,true,437644,European Paintings,"Painting, miniature",Portrait of a Man,,,,,,Artist,Attributed to,Noah Seaman,"British, active ca. 1724–41",,"Seaman, Noah",British,1724,1741,ca. 1730,1725,1735,Enamel,"Oval, 1 7/8 x 1 1/2 in. (47 x 37 mm)","Bequest of Mary Anna Palmer Draper, 1914",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/437644,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -62.122.35,false,true,436779,European Paintings,"Painting, miniature",Portrait of a Woman,,,,,,Artist,,James Jennings,"British, active ca. 1763–93",,"Jennings, James",British,1763,1793,probably early 1770s,1770,1773,Ivory,"Oval, 1 5/8 x 1 1/4 in. (40 x 34 mm)","Bequest of Millie Bruhl Fredrick, 1962",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/436779,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -24.80.509,false,true,436500,European Paintings,"Painting, miniature","Portrait of a Man, Said to Be John Cecil (1628–1678), Fourth Earl of Exeter",,,,,,Artist,Attributed to,Richard Gibson,"British, 1605/15?–1690 London",,"Gibson, Richard",British,1605,1690,ca. 1670,1665,1675,Vellum laid on card,"Oval, 2 7/8 x 2 3/8 in. (72 x 59 mm)","Bequest of Mary Clark Thompson, 1923",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/436500,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -44.36.1,false,true,436316,European Paintings,"Painting, miniature",Portrait of a Woman,,,,,,Artist,,Thomas Forster,"British, active ca. 1690–1713",,"Forster, Thomas",British,1690,1713,1701,1701,1701,Plumbago on vellum,"Oval, 4 1/4 x 3 1/4 in. (107 x 82 mm)","Rogers Fund, 1944",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/436316,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -44.36.2,false,true,436314,European Paintings,"Painting, miniature",Portrait of a Man,,,,,,Artist,,Thomas Forster,"British, active ca. 1690–1713",,"Forster, Thomas",British,1690,1713,1700,1700,1700,Plumbago on vellum,"Oval, 4 3/8 x 3 5/8 in. (112 x 92 mm)","Rogers Fund, 1944",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/436314,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -44.36.4,false,true,436317,European Paintings,"Painting, miniature",Portrait of a Man,,,,,,Artist,,Thomas Forster,"British, active ca. 1690–1713",,"Forster, Thomas",British,1690,1713,1705,1705,1705,Plumbago on vellum,"Oval, 5 1/8 x 4 in. (130 x 102 mm)","Rogers Fund, 1944",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/436317,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -44.36.5,false,true,436315,European Paintings,"Painting, miniature",Portrait of a Woman,,,,,,Artist,,Thomas Forster,"British, active ca. 1690–1713",,"Forster, Thomas",British,1690,1713,1700,1700,1700,Plumbago on vellum,"Oval, 4 3/8 x 3 1/2 in. (112 x 90 mm)","Rogers Fund, 1944",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/436315,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -30.111.1,false,true,435609,European Paintings,"Painting, miniature",Portrait of a Man,,,,,,Artist,,John Barry,"British, active ca. 1784–1827",,"Barry, John",British,1784,1827,ca. 1790,1785,1795,Ivory,"Oval, 2 1/2 x 2 1/8 in. (64 x 54 mm)","Gift of Mrs. Sherwood Eddy, 1930",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/435609,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -30.111.2,false,true,435610,European Paintings,"Painting, miniature",Portrait of a Woman,,,,,,Artist,,John Barry,"British, active ca. 1784–1827",,"Barry, John",British,1784,1827,ca. 1790,1785,1795,Ivory,"Oval, 2 1/2 x 2 1/8 in. (64 x 54 mm)","Gift of Mrs. Sherwood Eddy, 1930",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/435610,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -62.122.40,false,true,435611,European Paintings,"Painting, miniature","Portrait of a Man, Said to Be John Durham",,,,,,Artist,,John Barry,"British, active ca. 1784–1827",,"Barry, John",British,1784,1827,ca. 1790,1785,1795,Ivory,"Oval, 2 5/8 x 2 1/4 in. (67 x 56 mm)","Bequest of Millie Bruhl Fredrick, 1962",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/435611,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -32.75.8,false,true,437264,European Paintings,"Painting, miniature",Sir Joshua Reynolds (1723–1792),,,,,,Artist,,Thomas Peat,"British, active ca. 1791–1831",,"Peat, Thomas",British,1791,1831,1792,1792,1792,Enamel,"Oval, 4 x 3 1/8 in. (101 x 80 mm)","The Collection of Giovanni P. Morosini, presented by his daughter Giulia, 1932",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/437264,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -44.36.3,false,true,436901,European Paintings,"Painting, miniature",Portrait of a Man,,,,,,Artist,,David Loggan,"British, Gdansk 1634–1692 London",,"Loggan, David",British,1634,1692,1680,1680,1680,Plumbago on vellum,"Oval, 5 1/4 x 4 1/4 in. (132 x 107 mm)","Rogers Fund, 1944",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/436901,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -44.36.6,false,true,437938,European Paintings,"Painting, miniature",Portrait of a Man,,,,,,Artist,,Robert White,"British, London 1645–1703 London",,"White, Robert",British,1645,1703,1690,1690,1690,Plumbago on vellum,"Oval, 4 1/4 x 3 3/8 in. (106 x 85 mm)","Rogers Fund, 1944",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/437938,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -26.168.71,false,true,437663,European Paintings,"Painting, miniature",The Hours,,,,,,Artist,,Samuel Shelley,"British, London 1756–1808 London",,"Shelley, Samuel",British,1756,1808,1801,1801,1801,Ivory,5 1/2 x 4 1/4 in. (140 x 109 mm),"Bequest of Collis P. Huntington, 1900",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/437663,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -62.122.39,false,true,437664,European Paintings,"Painting, miniature",Portrait of a Man,,,,,,Artist,,Samuel Shelley,"British, London 1756–1808 London",,"Shelley, Samuel",British,1756,1808,probably ca. 1800,1795,1805,Ivory,"Oval, 3 x 2 3/8 in. (75 x 60 mm)","Bequest of Millie Bruhl Fredrick, 1962",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/437664,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -67.131,false,true,437582,European Paintings,"Painting, drawing",Robert Shurlock (1772–1847),,,,,,Artist,,John Russell,"British, Guildford 1745–1806 Hull",,"Russell, John",British,1745,1806,1801,1801,1801,"Pastel on paper, laid down on canvas",23 3/4 x 17 3/8 in. (60.3 x 44.1 cm),"Gift of Alan R. Shurlock, 1967",,,,,,,,,,,,Pastels & Oil Sketches on Paper,,http://www.metmuseum.org/art/collection/search/437582,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -67.132,false,true,437580,European Paintings,"Painting, drawing","Mrs. Robert Shurlock (Henrietta Ann Jane Russell, 1775–1849) and Her Daughter Ann",,,,,,Artist,,John Russell,"British, Guildford 1745–1806 Hull",,"Russell, John",British,1745,1806,1801,1801,1801,"Pastel on paper, laid down on canvas",23 7/8 x 17 3/4 in. (60.6 x 45.1 cm),"Gift of Geoffrey Shurlock, 1967",,,,,,,,,,,,Pastels & Oil Sketches on Paper,,http://www.metmuseum.org/art/collection/search/437580,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -61.182.1,false,true,437579,European Paintings,"Painting, drawing",William Man Godschall (1720–1802),,,,,,Artist,,John Russell,"British, Guildford 1745–1806 Hull",,"Russell, John",British,1745,1806,1791,1791,1791,"Pastel on paper, laid down on canvas",23 3/4 x 17 3/4 in. (60.3 x 45.1 cm),"Gift of Mr. and Mrs. Arthur Wiesenberger, 1961",,,,,,,,,,,,Pastels & Oil Sketches on Paper,,http://www.metmuseum.org/art/collection/search/437579,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -61.182.2,false,true,437583,European Paintings,"Painting, drawing","Mrs. William Man Godschall (Sarah Godschall, 1730–1795)",,,,,,Artist,,John Russell,"British, Guildford 1745–1806 Hull",,"Russell, John",British,1745,1806,1791,1791,1791,"Pastel on paper, laid down on canvas",23 3/4 x 17 3/4 in. (60.3 x 45.1 cm),"Gift of Mr. and Mrs. Arthur Wiesenberger, 1961",,,,,,,,,,,,Pastels & Oil Sketches on Paper,,http://www.metmuseum.org/art/collection/search/437583,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.217.2,false,true,437581,European Paintings,"Painting, drawing",Mrs. Robert Shurlock Sr. (Ann Manwaring),,,,,,Artist,,John Russell,"British, Guildford 1745–1806 Hull",,"Russell, John",British,1745,1806,1801,1801,1801,"Pastel on paper, laid down on canvas",24 x 17 7/8 in. (61 x 45.4 cm),"Gift of Olive Shurlock Sjölander, 1975",,,,,,,,,,,,Pastels & Oil Sketches on Paper,,http://www.metmuseum.org/art/collection/search/437581,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -11.218,false,true,436277,European Paintings,"Painting, miniature","Portrait of a Man, Said to Be Mr. De Wolf",,,,,,Artist,,George Engleheart,"British, Kew 1750–1829 Blackheath",,"Engleheart, George",British,1750,1829,ca. 1805,1800,1810,Ivory,"Oval, 3 1/4 x 2 1/2 in. (81 x 62 mm)","Gift of Alfred Ram, 1911",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/436277,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -95.14.55,false,true,436273,European Paintings,"Painting, miniature",Portrait of a Man,,,,,,Artist,,George Engleheart,"British, Kew 1750–1829 Blackheath",,"Engleheart, George",British,1750,1829,ca. 1780,1775,1785,Ivory,"Oval, 1 1/4 x 1 1/8 in. (32 x 29 mm)","The Moses Lazarus Collection, Gift of Josephine and Sarah Lazarus, in memory of their father, 1888–95",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/436273,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -26.168.14,false,true,436275,European Paintings,"Painting, miniature",Colonel Woodford,,,,,,Artist,,George Engleheart,"British, Kew 1750–1829 Blackheath",,"Engleheart, George",British,1750,1829,probably 1788,1788,1788,Ivory,"Oval, 2 1/8 x 1 3/4 in. (55 x 44 mm)","Bequest of Collis P. Huntington, 1900",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/436275,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -38.146.16,false,true,436274,European Paintings,"Painting, miniature","Mrs. Peter De Lancey (Elizabeth Colden, 1720–1784)",,,,,,Artist,,George Engleheart,"British, Kew 1750–1829 Blackheath",,"Engleheart, George",British,1750,1829,1783,1778,1788,Ivory,"Oval, 1 3/8 x 1 1/8 in. (34 x 28 mm)","Fletcher Fund, 1938",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/436274,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -49.122.1,false,true,437690,European Paintings,"Painting, miniature",Mrs. Charlotte Lennox,,,,,,Artist,,John Smart,"British, Norfolk 1741–1811 London",,"Smart, John",British,1741,1811,1777,1777,1777,Pencil and watercolor on card,"Oval, 2 1/2 x 2 in. (62 x 51 mm)","Rogers Fund, 1949",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/437690,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -59.23.75,false,true,437694,European Paintings,"Painting, miniature",Miss Ramus,,,,,,Artist,,John Smart,"British, Norfolk 1741–1811 London",,"Smart, John",British,1741,1811,ca. 1770,1765,1775,Pencil and watercolor on paper,2 1/4 x 2 in. (57 x 51 mm),"Bequest of Alexandrine Sinsheimer, 1958",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/437694,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -59.23.76,false,true,437691,European Paintings,"Painting, miniature",Mrs. Comyns,,,,,,Artist,,John Smart,"British, Norfolk 1741–1811 London",,"Smart, John",British,1741,1811,ca. 1760,1755,1765,Pencil and some watercolor on paper,1 3/4 x 1 5/8 in. (46 x 42 mm),"Bequest of Alexandrine Sinsheimer, 1958",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/437691,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -59.23.77,false,true,437693,European Paintings,"Painting, miniature","Sir George Armytage (1761–1836), Fourth Baronet",,,,,,Artist,,John Smart,"British, Norfolk 1741–1811 London",,"Smart, John",British,1741,1811,possibly ca. 1763,1758,1768,Pencil and watercolor on paper,2 1/8 x 2 in. (54 x 49 mm),"Bequest of Alexandrine Sinsheimer, 1958",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/437693,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -59.23.79,false,true,437692,European Paintings,"Painting, miniature",Mrs. Caroline Deas,,,,,,Artist,,John Smart,"British, Norfolk 1741–1811 London",,"Smart, John",British,1741,1811,ca. 1760,1755,1765,Pencil and watercolor on paper,2 1/4 x 2 1/4 in. (56 x 55 mm),"Bequest of Alexandrine Sinsheimer, 1958",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/437692,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -24.80.493,false,true,437689,European Paintings,"Painting, miniature",Sir William Hood,,,,,,Artist,,John Smart,"British, Norfolk 1741–1811 London",,"Smart, John",British,1741,1811,ca. 1766,1761,1771,Ivory,"Oval, 1 1/2 x 1 1/4 in. (37 x 32 mm)","Bequest of Mary Clark Thompson, 1923",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/437689,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -95.14.47,false,true,435700,European Paintings,"Painting, miniature","Algernon Percy (1602–1668), Tenth Earl of Northumberland, after Van Dyck",,,,,,Artist,,Henry Bone,"British, Truro 1755–1834 Somerstown",,"Bone, Henry",British,1755,1834,1827,1827,1827,Enamel,7 x 5 3/8 in. (178 x 136 mm),"The Moses Lazarus Collection, Gift of Josephine and Sarah Lazarus, in memory of their father, 1888–95",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/435700,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -95.14.82,false,true,435696,European Paintings,"Painting, miniature","Thomas Howard (1585–1646), Second Earl of Arundel, after Rubens",,,,,,Artist,,Henry Bone,"British, Truro 1755–1834 Somerstown",,"Bone, Henry",British,1755,1834,1808,1808,1808,Enamel,7 1/4 x 5 3/4 in. (185 x 147 mm),"The Moses Lazarus Collection, Gift of Josephine and Sarah Lazarus, in memory of their father, 1888–95",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/435696,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -24.80.503,false,true,435698,European Paintings,"Painting, miniature","Matthew Baillie (1761–1823), F.R.S., after Hoppner",,,,,,Artist,,Henry Bone,"British, Truro 1755–1834 Somerstown",,"Bone, Henry",British,1755,1834,1817,1817,1817,Enamel,5 1/8 x 4 in. (130 x 103 mm),"Bequest of Mary Clark Thompson, 1923",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/435698,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -24.80.521,false,true,435695,European Paintings,"Painting, miniature","Henry Hope (1735/36–1811), after Jones",,,,,,Artist,,Henry Bone,"British, Truro 1755–1834 Somerstown",,"Bone, Henry",British,1755,1834,1802,1802,1802,Enamel,"Oval, 2 1/8 x 1 3/4 in. (54 x 43 mm)","Bequest of Mary Clark Thompson, 1923",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/435695,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -24.80.523,false,true,435699,European Paintings,"Painting, miniature","Charles X (1757–1836), King of France, after Gérard",,,,,,Artist,,Henry Bone,"British, Truro 1755–1834 Somerstown",,"Bone, Henry",British,1755,1834,1829,1829,1829,Enamel,14 1/4 x 10 1/4 in. (364 x 260 mm),"Bequest of Mary Clark Thompson, 1923",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/435699,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -26.168.61,false,true,435697,European Paintings,"Painting, miniature","George IV (1762–1830) as Prince Regent, after Lawrence",,,,,,Artist,,Henry Bone,"British, Truro 1755–1834 Somerstown",,"Bone, Henry",British,1755,1834,1816,1816,1816,Enamel,"Oval, 2 1/2 x 2 in. (64 x 49 mm)","Bequest of Collis P. Huntington, 1900",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/435697,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -35.89.2,false,true,436650,European Paintings,"Painting, miniature",Portrait of a Woman,,,,,,Artist,,Nicholas Hilliard,"British, Exeter ca. 1547–1619 London",,"Hilliard, Nicholas",British,1542,1619,1597,1597,1597,Vellum,"Oval, 1 7/8 x 1 1/2 in. (47 x 39 mm)","Fletcher Fund, 1935",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/436650,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -35.89.4,false,true,436649,European Paintings,"Painting, miniature","Portrait of a Young Man, Probably Robert Devereux (1566–1601), Second Earl of Essex",,,,,,Artist,,Nicholas Hilliard,"British, Exeter ca. 1547–1619 London",,"Hilliard, Nicholas",British,1542,1619,1588,1588,1588,Vellum laid on card,"Oval, 1 5/8 x 1 3/8 in. (40 x 33 mm)","Fletcher Fund, 1935",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/436649,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -32.100.311,false,true,436651,European Paintings,"Painting, miniature",Portrait of a Woman,,,,,,Artist,,Nicholas Hilliard,"British, Exeter ca. 1547–1619 London",,"Hilliard, Nicholas",British,1542,1619,ca. 1590,1585,1595,Vellum laid on card,"Oval, 1 x 7/8 in. (27 x 22 mm)","The Friedsam Collection, Bequest of Michael Friedsam, 1931",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/436651,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -49.33,false,true,435941,European Paintings,"Painting, miniature","Henry Carey (1596–1661), Second Earl of Monmouth",,,,,,Artist,,Samuel Cooper,"British, London (?) 1608?–1672 London",,"Cooper, Samuel",British,1608,1672,1649,1649,1649,Vellum on prepared card,"Oval, 2 1/2 x 2 in. (64 x 52 mm)","Rogers Fund, 1949",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/435941,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -24.80.516,false,true,435942,European Paintings,"Painting, miniature","Portrait of a Woman, Said to Be Lucy Percy (1600?–1660), Countess of Carlisle",,,,,,Artist,,Samuel Cooper,"British, London (?) 1608?–1672 London",,"Cooper, Samuel",British,1608,1672,1653,1653,1653,Vellum laid on prepared card,"Oval, 2 1/2 x 2 in. (65 x 50 mm)","Bequest of Mary Clark Thompson, 1923",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/435942,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -35.89.3,false,true,436696,European Paintings,"Painting, miniature","Dr. Brian Walton (born about 1600, died 1661)",,,,,,Artist,,John Hoskins,"British, active by ca. 1615–died 1665",,"Hoskins, John",British,1615,1665,1657,1657,1657,Vellum laid on card,"Oval, 2 3/4 x 2 1/4 in. (72 x 58 mm)","Fletcher Fund, 1935",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/436696,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -24.80.505,false,true,436694,European Paintings,"Painting, miniature",Endymion Porter (1587–1649),,,,,,Artist,,John Hoskins,"British, active by ca. 1615–died 1665",,"Hoskins, John",British,1615,1665,ca. 1630,1625,1635,Vellum,"Oval, 3 1/8 x 2 5/8 in. (80 x 66 mm)","Bequest of Mary Clark Thompson, 1923",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/436694,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -62.122.22,false,true,436695,European Paintings,"Painting, miniature","Portrait of a Man, Said to Be Philip Wharton (1613–1696), Fourth Baron Wharton",,,,,,Artist,,John Hoskins,"British, active by ca. 1615–died 1665",,"Hoskins, John",British,1615,1665,1648,1648,1648,Vellum on prepared card,"Oval, 2 3/4 x 2 1/4 in. (69 x 56 mm)","Bequest of Millie Bruhl Fredrick, 1962",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/436695,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -62.122.28,false,true,436673,European Paintings,"Painting, miniature","Portrait of a Woman, Said to Be Lady Agnes Anne Wrothesley",,,,,,Artist,,Horace Hone,"British, London ca. 1754/56–1825 London",,"Hone, Horace",British,1749,1825,1791,1791,1791,Ivory,"Oval, 2 x 1 5/8 in. (52 x 41 mm)","Bequest of Millie Bruhl Fredrick, 1962",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/436673,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -62.49,false,true,435994,European Paintings,"Painting, miniature",Self-Portrait,,,,,,Artist,,Richard Cosway,"British, Oakford, Devon 1742–1821 London",,"Cosway, Richard",British,1742,1821,ca. 1770–75,1770,1775,Ivory,"Oval, 2 x 1 5/8 in. (50 x 42 mm)","Gift of Charlotte Guilford Muhlhofer, 1962",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/435994,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -24.80.513,false,true,436202,European Paintings,"Painting, miniature",Sir Henry Blount (1602–1682),,,,,,Artist,,Nicholas Dixon,"British, active by ca. 1660–died after 1708",,"Dixon, Nicholas",British,1660,1708,1660s,1660,1669,Vellum laid on card,"Oval, 2 5/8 x 2 1/8 in. (67 x 55 mm)","Bequest of Mary Clark Thompson, 1923",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/436202,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1975.217.1,false,true,437584,European Paintings,Drawing,Robert Shurlock (1772–1847),,,,,,Artist,Attributed to,William Russell,"British, London 1784–1870 Highgate",,"Russell, William",British,1784,1870,ca. 1805,1800,1810,Pastel on paper,23 7/8 x 17 7/8 in. (60.6 x 45.4 cm),"Gift of Olive Shurlock Sjölander, 1975",,,,,,,,,,,,Drawings,,http://www.metmuseum.org/art/collection/search/437584,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -61.233,false,true,436859,European Paintings,Drawing,Catania and Mount Etna,,,,,,Artist,,Edward Lear,"British, London 1812–1888 San Remo",,"Lear, Edward",British,1812,1888,1847,1847,1847,Oil on board,12 1/4 x 19 in. (31.1 x 48.3 cm),"Rogers Fund, 1961",,,,,,,,,,,,Drawings,,http://www.metmuseum.org/art/collection/search/436859,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1990.75,false,true,436845,European Paintings,Painting,"Copy after Rubens's ""Wolf and Fox Hunt""",,,,,,Artist,,Sir Edwin Henry Landseer,,,"Landseer, Edwin Henry, Sir",British,1802,1873,ca. 1824–26,1824,1826,Oil on wood,16 x 23 7/8 in. (40.6 x 60.6 cm),"Catharine Lorillard Wolfe Collection, Wolfe Fund, 1990",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436845,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -92.10.42,false,true,436226,European Paintings,Painting,"Homeward Bound: ""The Great Eastern""",,,,,,Artist,,Robert Charles Dudley,"British, 1826–1909",,"Dudley, Robert Charles",British,1826,1909,ca. 1866,1861,1871,Oil on canvas,44 3/4 x 67 1/4 in. (113.7 x 170.8 cm),"Gift of Cyrus W. Field, 1892",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436226,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -92.10.43,false,true,436224,European Paintings,Painting,Awaiting the Reply,,,,,,Artist,,Robert Charles Dudley,"British, 1826–1909",,"Dudley, Robert Charles",British,1826,1909,ca. 1866,1861,1871,Oil on canvas,23 1/4 x 33 1/2 in. (59.1 x 85.1 cm),"Gift of Cyrus W. Field, 1892",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436224,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -92.10.44,false,true,436223,European Paintings,Painting,Landing the Shore End of the Atlantic Cable,,,,,,Artist,,Robert Charles Dudley,"British, 1826–1909",,"Dudley, Robert Charles",British,1826,1909,1866,1866,1866,Oil on canvas,22 1/2 x 33 in. (57.2 x 83.8 cm),"Gift of Cyrus W. Field, 1892",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436223,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -92.10.45,false,true,436225,European Paintings,Painting,Grappling for the Lost Cable,,,,,,Artist,,Robert Charles Dudley,"British, 1826–1909",,"Dudley, Robert Charles",British,1826,1909,ca. 1866,1861,1871,Oil on canvas,22 3/4 x 33 1/8 in. (57.8 x 84.1 cm),"Gift of Cyrus W. Field, 1892",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436225,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -92.10.46,false,true,436227,European Paintings,Painting,Landing at Newfoundland,,,,,,Artist,,Robert Charles Dudley,"British, 1826–1909",,"Dudley, Robert Charles",British,1826,1909,ca. 1866,1861,1871,Oil on canvas,22 3/4 x 33 1/4 in. (57.8 x 84.5 cm),"Gift of Cyrus W. Field, 1892",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436227,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -92.10.47,false,true,436228,European Paintings,Painting,Making the Splice between the Shore End and the Ocean Cable,,,,,,Artist,,Robert Charles Dudley,"British, 1826–1909",,"Dudley, Robert Charles",British,1826,1909,ca. 1866,1861,1871,Oil on canvas,22 3/4 x 33 1/4 in. (57.8 x 84.5 cm),"Gift of Cyrus W. Field, 1892",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436228,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -15.30.34,false,true,436442,European Paintings,Painting,The Painter's Daughter Mary (1750–1826),,,,,,Artist,Copy after,Thomas Gainsborough,"British, mid-19th century",,"Gainsborough, Thomas",British,1727,1788,mid-19th century,1830,1869,Oil on canvas,17 1/4 x 13 7/8 in. (43.8 x 35.2 cm),"Bequest of Maria DeWitt Jesup, from the collection of her husband, Morris K. Jesup, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436442,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -44.27,false,true,437262,European Paintings,Painting,"Henry Frederick (1594–1612), Prince of Wales, with Sir John Harington (1592–1614), in the Hunting Field",,,,,,Artist,,Robert Peake the Elder,"British, ca. 1551–1619 London",,"Peake, Robert, the Elder",British,1546,1619,1603,1603,1603,Oil on canvas,79 1/2 x 58 in. (201.9 x 147.3 cm),"Purchase, Joseph Pulitzer Bequest, 1944",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437262,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -51.194.1,false,true,437263,European Paintings,Painting,"Princess Elizabeth (1596–1662), Later Queen of Bohemia",,,,,,Artist,,Robert Peake the Elder,"British, ca. 1551–1619 London",,"Peake, Robert, the Elder",British,1546,1619,ca. 1606,1601,1611,Oil on canvas,60 3/4 x 31 1/4 in. (154.3 x 79.4 cm),"Gift of Kate T. Davison, in memory of her husband, Henry Pomeroy Davison, 1951",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437263,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2013.155,false,true,441769,European Paintings,Painting,"Virgil's Tomb by Moonlight, with Silius Italicus Declaiming",,,,,,Artist,,Joseph Wright (Wright of Derby),"British, Derby 1734–1797 Derby",,"Wright, Joseph (Wright of Derby)",British,1734,1797,1779,1779,1779,Oil on canvas,40 x 50 in. (101.6 x 127 cm),"Purchase, Lila Acheson Wallace Gift, Gifts of Mrs. William M. Haupt, Josephine Bay Paul, and Estate of George Quackenbush, in his memory, by exchange, The Morris and Alma Schapiro Fund Gift, and funds from various donors, 2013",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/441769,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.264.6,false,true,437954,European Paintings,Painting,Portrait of a Woman,,,,,,Artist,,Joseph Wright (Wright of Derby),"British, Derby 1734–1797 Derby",,"Wright, Joseph (Wright of Derby)",British,1734,1797,ca. 1770,1765,1775,Oil on canvas,49 7/8 x 40 in. (126.7 x 101.6 cm),"Gift of Heathcote Art Foundation, 1986",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437954,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -43.132.4,false,true,435895,European Paintings,Painting,Self-Portrait,,,,,,Artist,,George Chinnery,"British, London 1774–1852 Macau",,"Chinnery, George",British,1774,1852,1825–28,1825,1828,Oil on canvas,8 5/8 x 7 1/4 in. (21.9 x 18.4 cm),"Rogers Fund, 1943",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435895,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -36.111,false,true,436656,European Paintings,Painting,The Wedding of Stephen Beckingham and Mary Cox,,,,,,Artist,,William Hogarth,"British, London 1697–1764 London",,"Hogarth, William",British,1697,1764,1729,1729,1729,Oil on canvas,50 1/2 x 40 1/2 in. (128.3 x 102.9 cm),"Marquand Fund, 1936",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436656,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -44.159,false,true,437280,European Paintings,Painting,The Strong Family,,,,,,Artist,,Charles Philips,"British, London 1703–1747 London",,"Philips, Charles",British,1703,1747,1732,1732,1732,Oil on canvas,29 5/8 x 37 in. (75.2 x 94 cm),"Gift of Robert Lehman, 1944",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437280,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.357,false,true,440568,European Paintings,Painting,The Saithwaite Family,,,,,,Artist,,Francis Wheatley,"British, London 1747–1801 London",,"Wheatley, Francis",British,1747,1801,ca. 1785,1780,1790,Oil on canvas,38 3/4 x 50 in. (98.4 x 127 cm),"Gift of Mrs. Charles Wrightsman, 2009",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/440568,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -51.30.1,false,true,435671,European Paintings,Painting,The Angel Appearing to Zacharias,,,,,,Artist,,William Blake,"British, London 1757–1827 London",,"Blake, William",British,1757,1827,1799–1800,1799,1800,"Pen and black ink, tempera, and glue size on canvas",10 1/2 x 15 in. (26.7 x 38.1 cm),"Bequest of William Church Osborn, 1951",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435671,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -01.20,false,true,436686,European Paintings,Painting,"Mrs. Richard Bache (Sarah Franklin, 1743–1808)",,,,,,Artist,,John Hoppner,"British, London 1758–1810 London",,"Hoppner, John",British,1758,1810,1793,1793,1793,Oil on canvas,30 1/8 x 24 7/8 in. (76.5 x 63.2 cm),"Catharine Lorillard Wolfe Collection, Wolfe Fund, 1901",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436686,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -06.1242,false,true,436690,European Paintings,Painting,"Portrait of a Woman; (reverse, now covered by relining canvas) Study of a Child's Head",,,,,,Artist,,John Hoppner,"British, London 1758–1810 London",,"Hoppner, John",British,1758,1810,1790s,1790,1799,Oil on canvas,30 x 24 7/8 in. (76.2 x 63.2 cm),"Gift of William T. and Eleanor Blodgett, 1906",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436690,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -46.13.3,false,true,436684,European Paintings,Painting,Major Thomas Pechell (1753–1826),,,,,,Artist,,John Hoppner,"British, London 1758–1810 London",,"Hoppner, John",British,1758,1810,1799,1799,1799,Oil on canvas,30 x 24 7/8 in. (76.2 x 63.2 cm),"Bequest of Helen Swift Neilson, 1945",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436684,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -46.13.4,false,true,436688,European Paintings,Painting,"Mrs. Thomas Pechell (Charlotte Clavering, died 1841)",,,,,,Artist,,John Hoppner,"British, London 1758–1810 London",,"Hoppner, John",British,1758,1810,1799,1799,1799,Oil on canvas,30 x 25 in. (76.2 x 63.5 cm),"Bequest of Helen Swift Neilson, 1945",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436688,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -53.59.3,false,true,436692,European Paintings,Painting,The Sackville Children,,,,,,Artist,,John Hoppner,"British, London 1758–1810 London",,"Hoppner, John",British,1758,1810,1796,1796,1796,Oil on canvas,60 x 49 in. (152.4 x 124.5 cm),"Bequest of Thomas W. Lamont, 1948",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436692,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -15.30.41,false,true,436685,European Paintings,Painting,"Mrs. John Garden (Ann Garden, 1769–1842) and Her Children, John (1796–1854) and Ann Margaret (born 1793)",,,,,,Artist,,John Hoppner,"British, London 1758–1810 London",,"Hoppner, John",British,1758,1810,1796 or 1797,1796,1797,Oil on canvas,50 1/8 x 39 7/8 in. (127.3 x 101.3 cm),"Bequest of Maria DeWitt Jesup, from the collection of her husband, Morris K. Jesup, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436685,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -59.189.3,false,true,436683,European Paintings,Painting,Lady Hester King (died 1873),,,,,,Artist,,John Hoppner,"British, London 1758–1810 London",,"Hoppner, John",British,1758,1810,probably 1805,1804,1810,Oil on canvas,30 x 25 in. (76.2 x 63.5 cm),"Gift of Bernard M. Baruch, in memory of his wife, Annie Griffen Baruch, 1959",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436683,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -25.110.20,false,true,437161,European Paintings,Painting,The Bell Inn,,,,,,Artist,,George Morland,"British, London 1763–1804 London",,"Morland, George",British,1763,1804,late 1780s,1787,1789,Oil on canvas,20 1/2 x 26 1/4 in. (52.1 x 66.7 cm),"Bequest of Collis P. Huntington, 1900",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437161,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -96.29,true,true,437854,European Paintings,Painting,Whalers,,,,,,Artist,,Joseph Mallord William Turner,"British, London 1775–1851 London",,"Turner, Joseph Mallord William",British,1775,1851,ca. 1845,1840,1850,Oil on canvas,36 1/8 x 48 1/4 in. (91.8 x 122.6 cm),"Catharine Lorillard Wolfe Collection, Wolfe Fund, 1896",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437854,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -99.31,true,true,437853,European Paintings,Painting,"Venice, from the Porch of Madonna della Salute",,,,,,Artist,,Joseph Mallord William Turner,"British, London 1775–1851 London",,"Turner, Joseph Mallord William",British,1775,1851,ca. 1835,1830,1835,Oil on canvas,36 x 48 1/8 in. (91.4 x 122.2 cm),"Bequest of Cornelius Vanderbilt, 1899",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437853,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -89.15.9,false,true,437852,European Paintings,Painting,"Saltash with the Water Ferry, Cornwall",,,,,,Artist,,Joseph Mallord William Turner,"British, London 1775–1851 London",,"Turner, Joseph Mallord William",British,1775,1851,1811,1811,1811,Oil on canvas,35 3/8 x 47 1/2 in. (89.9 x 120.7 cm),"Marquand Collection, Gift of Henry G. Marquand, 1889",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437852,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -05.39.1,false,true,437936,European Paintings,Painting,Ariadne,,,,,,Artist,,George Frederic Watts,"British, London 1817–1904 London",,"Watts, George Frederic",British,1817,1904,1894,1894,1894,Oil on canvas,24 x 20 in. (61 x 50.8 cm),"Rogers Fund, 1905",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437936,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -97.41.3,false,true,437935,European Paintings,Painting,"An Old Bridge at Hendon, Middlesex",,,,,,Artist,,Frederick Waters Watts,"British, Bath 1800–1870 Hampstead",,"Watts, Frederick Waters",British,1800,1870,ca. 1828,1820,1833,Oil on canvas,21 3/4 x 32 3/4 in. (55.2 x 83.2 cm),"Gift of George A. Hearn, 1897",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437935,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -55.89,false,true,436853,European Paintings,Painting,Lady Maria Conyngham (died 1843),,,,,,Artist,,Sir Thomas Lawrence,"British, Bristol 1769–1830 London",,"Lawrence, Thomas, Sir",British,1769,1830,ca. 1824–25,1824,1825,Oil on canvas,36 1/4 x 28 1/4 in. (92.1 x 71.8 cm),"Gift of Jessie Woolworth Donahue, 1955",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436853,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -25.110.1,false,true,436850,European Paintings,Painting,"The Calmady Children (Emily, 1818–?1906, and Laura Anne, 1820–1894)",,,,,,Artist,,Sir Thomas Lawrence,"British, Bristol 1769–1830 London",,"Lawrence, Thomas, Sir",British,1769,1830,1823,1789,1830,Oil on canvas,30 7/8 x 30 1/8 in. (78.4 x 76.5 cm),"Bequest of Collis P. Huntington, 1900",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436850,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -50.135.5,true,true,436851,European Paintings,Painting,"Elizabeth Farren (born about 1759, died 1829), Later Countess of Derby",,,,,,Artist,,Sir Thomas Lawrence,"British, Bristol 1769–1830 London",,"Lawrence, Thomas, Sir",British,1769,1830,1790,1790,1790,Oil on canvas,94 x 57 1/2 in. (238.8 x 146.1 cm),"Bequest of Edward S. Harkness, 1940",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436851,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -06.1279,false,true,436441,European Paintings,Painting,Wooded Upland Landscape,,,,,,Artist,,Thomas Gainsborough,"British, Sudbury 1727–1788 London",,"Gainsborough, Thomas",British,1727,1788,probably 1783,1783,1783,Oil on canvas,47 3/8 x 58 1/8 in. (120.3 x 147.6 cm),"Gift of George A. Hearn, 1906",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436441,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -45.59.1,false,true,436437,European Paintings,Painting,"Mrs. William Tennant (Mary Wylde, died 1798)",,,,,,Artist,,Thomas Gainsborough,"British, Sudbury 1727–1788 London",,"Gainsborough, Thomas",British,1727,1788,1780s,1780,1789,Oil on canvas,49 1/2 x 40 in. (125.7 x 101.6 cm),"Fletcher Fund, 1945",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436437,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -89.15.8,false,true,436433,European Paintings,Painting,A Boy with a Cat—Morning,,,,,,Artist,,Thomas Gainsborough,"British, Sudbury 1727–1788 London",,"Gainsborough, Thomas",British,1727,1788,1787,1787,1787,Oil on canvas,59 1/4 x 47 1/2 in. (150.5 x 120.7 cm),"Marquand Collection, Gift of Henry G. Marquand, 1889",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436433,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -20.155.1,true,true,436435,European Paintings,Painting,Mrs. Grace Dalrymple Elliott (1754?–1823),,,,,,Artist,,Thomas Gainsborough,"British, Sudbury 1727–1788 London",,"Gainsborough, Thomas",British,1727,1788,1778,1778,1778,Oil on canvas,92 1/4 x 60 1/2in. (234.3 x 153.7cm),"Bequest of William K. Vanderbilt, 1920",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436435,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -50.145.16,false,true,436431,European Paintings,Painting,Charles Rousseau Burney (1747–1819),,,,,,Artist,,Thomas Gainsborough,"British, Sudbury 1727–1788 London",,"Gainsborough, Thomas",British,1727,1788,ca. 1780,1775,1785,Oil on canvas,30 1/4 x 25 1/8 in. (76.8 x 63.8 cm),"Bequest of Mary Stillman Harkness, 1950",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436431,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -50.145.17,false,true,436440,European Paintings,Painting,Cottage Children (The Wood Gatherers),,,,,,Artist,,Thomas Gainsborough,"British, Sudbury 1727–1788 London",,"Gainsborough, Thomas",British,1727,1788,1787,1787,1787,Oil on canvas,58 1/8 x 47 3/8 in. (147.6 x 120.3 cm),"Bequest of Mary Stillman Harkness, 1950",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436440,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -17.120.224,false,true,436439,European Paintings,Painting,"Portrait of a Young Woman, Called Miss Sparrow",,,,,,Artist,,Thomas Gainsborough,"British, Sudbury 1727–1788 London",,"Gainsborough, Thomas",British,1727,1788,1770s,1770,1779,Oil on canvas,30 1/8 x 24 7/8 in. (76.5 x 63.2 cm),"Mr. and Mrs. Isaac D. Fletcher Collection, Bequest of Isaac D. Fletcher, 1917",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436439,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -89.15.14,false,true,436057,European Paintings,Painting,"Hautbois Common, Norfolk",,,,,,Artist,,John Crome,"British, Norwich 1768–1821 Norwich",,"Crome, John",British,1768,1821,probably ca. 1810,1805,1815,Oil on canvas,22 x 35 in. (55.9 x 88.9 cm),"Marquand Collection, Gift of Henry G. Marquand, 1889",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436057,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -87.16,false,true,437449,European Paintings,Painting,The Honorable Henry Fane (1739–1802) with Inigo Jones and Charles Blair,,,,,,Artist,,Sir Joshua Reynolds,"British, Plympton 1723–1792 London",,"Reynolds, Joshua, Sir",British,1723,1792,1761–66,1761,1766,Oil on canvas,100 1/4 x 142 in. (254.6 x 360.7 cm),"Gift of Junius S. Morgan, 1887",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437449,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -48.181,false,true,437445,European Paintings,Painting,"George Capel, Viscount Malden (1757–1839), and Lady Elizabeth Capel (1755–1834)",,,,,,Artist,,Sir Joshua Reynolds,"British, Plympton 1723–1792 London",,"Reynolds, Joshua, Sir",British,1723,1792,1768,1768,1768,Oil on canvas,71 1/2 x 57 1/4 in. (181.6 x 145.4 cm),"Gift of Henry S. Morgan, 1948",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437445,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -54.192,false,true,437451,European Paintings,Painting,John Barker (1707–1787),,,,,,Artist,,Sir Joshua Reynolds,"British, Plympton 1723–1792 London",,"Reynolds, Joshua, Sir",British,1723,1792,1786,1786,1786,Oil on canvas,68 1/4 x 47 1/2 in. (173.4 x 120.7 cm),"Gift of Ruth Armour, 1954",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437451,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -06.1241,false,true,437454,European Paintings,Painting,"Mrs. George Baldwin (Jane Maltass, 1763–1839)",,,,,,Artist,Workshop of,Sir Joshua Reynolds,"British, Plympton 1723–1792 London",,"Reynolds, Joshua, Sir",British,1723,1792,1782 or later,1782,1792,Oil on canvas,36 1/8 x 29 1/8 in. (91.8 x 74 cm),"Gift of William T. Blodgett and his sister Eleanor Blodgett, in memory of their father, William T. Blodgett, one of the founders of the Museum, 1906",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437454,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -15.30.38,false,true,437448,European Paintings,Painting,"Georgiana Augusta Frederica Elliott (1782–1813), Later Lady Charles Bentinck",,,,,,Artist,,Sir Joshua Reynolds,"British, Plympton 1723–1792 London",and Workshop,"Reynolds, Joshua, Sir",British,1723,1792,1784,1784,1784,Oil on canvas,35 x 30 in. (88.9 x 76.2 cm),"Bequest of Maria DeWitt Jesup, from the collection of her husband, Morris K. Jesup, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437448,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -20.155.3,true,true,437447,European Paintings,Painting,Captain George K. H. Coussmaker (1759–1801),,,,,,Artist,,Sir Joshua Reynolds,"British, Plympton 1723–1792 London",,"Reynolds, Joshua, Sir",British,1723,1792,1782,1782,1782,Oil on canvas,93 3/4 x 57 1/4 in. (238.1 x 145.4 cm),"Bequest of William K. Vanderbilt, 1920",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437447,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -50.238.2,false,true,437444,European Paintings,Painting,"Anne Dashwood (1743–1830), Later Countess of Galloway",,,,,,Artist,,Sir Joshua Reynolds,"British, Plympton 1723–1792 London",,"Reynolds, Joshua, Sir",British,1723,1792,1764,1764,1764,Oil on canvas,"52 1/2 x 46 3/4 in. (133.4 x 118.7 cm), with strip of 7 1/8 in. (18.1 cm) folded over the top of the stretcher","Gift of Lillian S. Timken, 1950",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437444,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1987.47.2,false,true,437450,European Paintings,Painting,"Mrs. Lewis Thomas Watson (Mary Elizabeth Milles, 1767–1818)",,,,,,Artist,,Sir Joshua Reynolds,"British, Plympton 1723–1792 London",,"Reynolds, Joshua, Sir",British,1723,1792,1789,1789,1789,Oil on canvas,50 x 40 in. (127 x 101.6 cm),"Bequest of Mrs. Harry Payne Bingham, 1986",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437450,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -25.110.10,false,true,437452,European Paintings,Painting,"Lady Smith (Charlotte Delaval) and Her Children (George Henry, Louisa, and Charlotte)",,,,,,Artist,,Sir Joshua Reynolds,"British, Plympton 1723–1792 London",,"Reynolds, Joshua, Sir",British,1723,1792,1787,1787,1787,Oil on canvas,55 3/8 x 44 1/8 in. (140.7 x 112.1 cm),"Bequest of Collis P. Huntington, 1900",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437452,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.264.5,false,true,437443,European Paintings,Painting,"Thomas (1740–1825) and Martha Neate (1741–after 1795) with His Tutor, Thomas Needham",,,,,,Artist,,Sir Joshua Reynolds,"British, Plympton 1723–1792 London",,"Reynolds, Joshua, Sir",British,1723,1792,1748,1748,1748,Oil on canvas,66 1/8 x 71 in. (168 x 180.3 cm),"Gift of Heathcote Art Foundation, 1986",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437443,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1980.468,false,true,437764,European Paintings,Painting,The Third Duke of Dorset's Hunter with a Groom and a Dog,,,,,,Artist,,George Stubbs,"British, Liverpool 1724–1806 London",,"Stubbs, George",British,1724,1806,1768,1768,1768,Oil on canvas,40 x 49 3/4 in. (101.6 x 126.4 cm),"Bequest of Mrs. Paul Moore, 1980",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437764,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -09.1.1,false,true,437751,European Paintings,Painting,"Richmond Castle, Yorkshire",,,,,,Artist,,Philip Wilson Steer,"British, Birkenhead 1860–1942 London",,"Steer, Philip Wilson",British,1860,1942,1903,1903,1903,Oil on canvas,29 1/8 x 34 1/2 in. (74 x 87.6 cm),"Catharine Lorillard Wolfe Collection, Wolfe Fund, 1908",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437751,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -47.26,true,true,435826,European Paintings,Painting,The Love Song,,,,,,Artist,,Sir Edward Burne-Jones,"British, Birmingham 1833–1898 Fulham",,"Burne-Jones, Edward, Sir",British,1833,1898,1868–77,1868,1877,Oil on canvas,45 x 61 3/8 in. (114.3 x 155.9 cm),"The Alfred N. Punnett Endowment Fund, 1947",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435826,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -96.28,false,true,436869,European Paintings,Painting,Lachrymae,,,,,,Artist,,"Frederic, Lord Leighton","British, Scarborough 1830–1896 London",,"Leighton, Frederic, Lord",British,1830,1896,ca. 1894–95,1894,1895,Oil on canvas,62 x 24 3/4 in. (157.5 x 62.9 cm),"Catharine Lorillard Wolfe Collection, Wolfe Fund, 1896",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436869,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -06.1328,false,true,437092,European Paintings,Painting,Portia,,,,,,Artist,,Sir John Everett Millais,"British, Southampton 1829–1896 London",,"Millais, John Everett, Sir",British,1829,1896,1886,1886,1886,Oil on canvas,49 1/4 x 33 in. (125.1 x 83.8 cm),"Catharine Lorillard Wolfe Collection, Wolfe Fund, 1906",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437092,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -64.287,false,true,437680,European Paintings,Painting,The Bridge at Villeneuve-la-Garenne,,,,,,Artist,,Alfred Sisley,"British, Paris 1839–1899 Moret-sur-Loing",,"Sisley, Alfred",British,1839,1899,1872,1872,1872,Oil on canvas,19 1/2 x 25 3/4 in. (49.5 x 65.4 cm),"Gift of Mr. and Mrs. Henry Ittleson Jr., 1964",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437680,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1992.366,false,true,437686,European Paintings,Painting,Rue Eugène Moussoir at Moret: Winter,,,,,,Artist,,Alfred Sisley,"British, Paris 1839–1899 Moret-sur-Loing",,"Sisley, Alfred",British,1839,1899,1891,1891,1891,Oil on canvas,18 3/8 x 22 1/4 in. (46.7 x 56.5 cm),"Bequest of Ralph Friedman, 1992",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437686,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -64.154.2,false,true,437685,European Paintings,Painting,The Road from Versailles to Louveciennes,,,,,,Artist,,Alfred Sisley,"British, Paris 1839–1899 Moret-sur-Loing",,"Sisley, Alfred",British,1839,1899,probably 1879,1859,1899,Oil on canvas,18 x 22 in. (45.7 x 55.9 cm),"Gift of Mr. and Mrs. Richard Rodgers, 1964",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437685,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1991.277.3,false,true,437683,European Paintings,Painting,Sahurs Meadows in Morning Sun,,,,,,Artist,,Alfred Sisley,"British, Paris 1839–1899 Moret-sur-Loing",,"Sisley, Alfred",British,1839,1899,1894,1894,1894,Oil on canvas,28 3/4 x 36 1/4 in. (73 x 92.1 cm),"Gift of Janice H. Levin, 1991",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437683,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -67.187.103,false,true,437682,European Paintings,Painting,View of Marly-le-Roi from Coeur-Volant,,,,,,Artist,,Alfred Sisley,"British, Paris 1839–1899 Moret-sur-Loing",,"Sisley, Alfred",British,1839,1899,1876,1876,1876,Oil on canvas,25 3/4 x 36 3/8 in. (65.4 x 92.4 cm),"Bequest of Miss Adelaide Milton de Groot (1876–1967), 1967",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437682,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1976.201.18,false,true,437684,European Paintings,Painting,The Road from Moret to Saint-Mammès,,,,,,Artist,,Alfred Sisley,"British, Paris 1839–1899 Moret-sur-Loing",,"Sisley, Alfred",British,1839,1899,1883–85,1883,1885,Oil on canvas,19 7/8 x 24 1/4 in. (50.5 x 61.5 cm),"Bequest of Joan Whitney Payson, 1975",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437684,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -26.128,false,true,435923,European Paintings,Painting,Stoke-by-Nayland,,,,,,Artist,,John Constable,"British, East Bergholt 1776–1837 Hampstead",,"Constable, John",British,1776,1837,ca. 1810–11,1805,1815,Oil on canvas,11 1/8 x 14 1/4 in. (28.3 x 36.2 cm),"Charles B. Curtis Fund, 1926",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435923,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -06.1272,false,true,435921,European Paintings,Painting,"Mrs. James Pulham Sr. (Frances Amys, ca. 1766–1856)",,,,,,Artist,,John Constable,"British, East Bergholt 1776–1837 Hampstead",,"Constable, John",British,1776,1837,1818,1818,1818,Oil on canvas,29 3/4 x 24 3/4 in. (75.6 x 62.9 cm),"Gift of George A. Hearn, 1906",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435921,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -50.145.8,true,true,435922,European Paintings,Painting,Salisbury Cathedral from the Bishop's Grounds,,,,,,Artist,,John Constable,"British, East Bergholt 1776–1837 Hampstead",,"Constable, John",British,1776,1837,ca. 1825,1820,1830,Oil on canvas,34 5/8 x 44 in. (87.9 x 111.8 cm),"Bequest of Mary Stillman Harkness, 1950",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435922,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -20.40,false,true,436079,European Paintings,Painting,Uvedale Tomkyns Price (1685–1764) and Members of His Family,,,,,,Artist,,Bartholomew Dandridge,"British, London 1691–in or after 1754 London",,"Dandridge, Bartholomew",British,1691,1754,possibly early 1730s,1730,1733,Oil on canvas,40 1/4 x 62 1/2 in. (102.2 x 158.8 cm),"Rogers Fund, 1920",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436079,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2008.547.3,false,true,440725,European Paintings,Painting,"The Two Central Figures in ""Derby Day""",,,,,,Artist,,William Powell Frith,"British, Aldfield, Yorkshire 1819–1909 London",,"Frith, William Powell",British,1819,1909,1860,1860,1860,Oil on canvas,18 x 12 1/2 in. (45.7 x 31.8 cm),"Gift of Mrs. Charles Wrightsman, 2008",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/440725,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1998.451.2,false,true,490260,European Paintings,Painting,"Maple Street, London",,,,,,Artist,,Walter Richard Sickert,"British, Munich 1860–1942 Bathampton, Somerset",,"Sickert, Walter Richard",British,1860,1942,ca. 1915–23,1910,1928,Oil on canvas,30 1/4 × 20 1/8 in. (76.8 × 51.1 cm),"Gift of Emma Swan Hall, 1998",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/490260,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1998.451.3,false,true,490261,European Paintings,Painting,The Antique Shop,,,,,,Artist,,Walter Richard Sickert,"British, Munich 1860–1942 Bathampton, Somerset",,"Sickert, Walter Richard",British,1860,1942,ca. 1906,1901,1911,Oil on cardboard,9 1/2 x 7 1/2 in. (24.1 x 19.1 cm),"Gift of Emma Swan Hall, 1998",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/490261,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1979.135.17,false,true,437670,European Paintings,Painting,The Cigarette (Jeanne Daurmont),,,,,,Artist,,Walter Richard Sickert,"British, Munich 1860–1942 Bathampton, Somerset",,"Sickert, Walter Richard",British,1860,1942,1906,1906,1906,Oil on canvas,20 x 16 in. (50.8 x 40.6 cm),"Bequest of Mary Cushing Fosburgh, 1978",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437670,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1984.433.24,false,true,483377,European Paintings,Painting,Reclining Nude (Thin Adeline),,,,,,Artist,,Walter Richard Sickert,"British, Munich 1860–1942 Bathampton, Somerset",,"Sickert, Walter Richard",British,1860,1942,1906,1906,1906,Oil on canvas,18 1/8 × 15 1/8 in. (46 × 38.4 cm),"Bequest of Scofield Thayer, 1982",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/483377,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2001.45,false,true,438449,European Paintings,Painting,View near Rouen,,,,,,Artist,,Richard Parkes Bonington,"British, Arnold, Nottinghamshire 1802–1828 London",,"Bonington, Richard Parkes",British,1802,1828,ca. 1825,1820,1830,Oil on millboard,11 x 13 in. (27.9 x 33 cm),"Purchase, Gift of Joanne Toor Cummings, by exchange, 2001",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/438449,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -45.146.1,false,true,435707,European Paintings,Painting,Roadside Halt,,,,,,Artist,,Richard Parkes Bonington,"British, Arnold, Nottinghamshire 1802–1828 London",,"Bonington, Richard Parkes",British,1802,1828,1826,1826,1826,Oil on canvas,18 1/4 x 14 7/8 in. (46.4 x 37.8 cm),"Gift of Francis Neilson, 1945",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435707,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -05.32.1,false,true,435632,European Paintings,Painting,Portrait of a Woman,,,,,,Artist,,Sir William Beechey,"British, Burford, Oxfordshire 1753–1839 Hampstead",,"Beechey, William, Sir",British,1753,1839,ca. 1805,1800,1810,Oil on canvas,50 x 40 1/4 in. (127 x 102.2 cm),"Gift of George A. Hearn, 1905",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435632,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -45.59.4,false,true,437501,European Paintings,Painting,"Mrs. Bryan Cooke (Frances Puleston, 1765–1818)",,,,,,Artist,,George Romney,"British, Beckside, Lancashire 1734–1802 Kendal, Cumbria",,"Romney, George",British,1734,1802,ca. 1787–91,1787,1791,Oil on canvas,50 x 39 1/2 in. (127 x 100.3 cm),"Fletcher Fund, 1945",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437501,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -49.7.57,false,true,437500,European Paintings,Painting,"Lady Elizabeth Stanley (1753–1797), Countess of Derby",,,,,,Artist,,George Romney,"British, Beckside, Lancashire 1734–1802 Kendal, Cumbria",,"Romney, George",British,1734,1802,1776–78,1776,1778,Oil on canvas,50 x 40 in. (127 x 101.6 cm),"The Jules Bache Collection, 1949",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437500,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -15.30.37,false,true,437504,European Paintings,Painting,Self-Portrait,,,,,,Artist,,George Romney,"British, Beckside, Lancashire 1734–1802 Kendal, Cumbria",,"Romney, George",British,1734,1802,1795,1795,1795,Oil on canvas,30 x 25 in. (76.2 x 63.5 cm),"Bequest of Maria DeWitt Jesup, from the collection of her husband, Morris K. Jesup, 1914",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437504,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -58.102.2,false,true,437498,European Paintings,Painting,"Portrait of a Woman, Said to Be Emily Bertie Pott (died 1782)",,,,,,Artist,,George Romney,"British, Beckside, Lancashire 1734–1802 Kendal, Cumbria",,"Romney, George",British,1734,1802,1781,1781,1781,Oil on canvas,29 3/4 x 24 7/8 in. (75.6 x 63.2 cm),"Gift of Jessie Woolworth Donahue, 1958",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437498,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1974.159,false,true,436863,European Paintings,Painting,General Garibaldi's Residence at Caprera,,,,,,Artist,,Frederick Richard Lee,"British, Barnstaple 1798–1879 Hermon Station, Malmsbury, Cape Colony, South Africa",,"Lee, Frederick Richard",British,1798,1879,1865,1865,1865,Oil on canvas,34 1/4 x 54 3/8 in. (87 x 138.1 cm),"Gift of Dr. Melvin Goldberg, 1974",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436863,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -62.122.60,false,true,436589,European Paintings,"Painting, miniature",Sir Joshua Reynolds (1723–1792),,,,,,Artist,Style of,William Grimaldi,1773 or later,,"Grimaldi, William",British,1751,1830,,1773,1773,Ivory,"Oval, 1 7/8 x 1 1/2 in. (47 x 37 mm)","Bequest of Millie Bruhl Fredrick, 1962",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/436589,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -41.191.2,false,true,437696,European Paintings,"Painting, miniature",Portrait of an Officer,,,,,,Artist,Imitator of,John Smart,1784 or later?,,"Smart, John",British,1741,1811,,1784,1789,Ivory,"Oval, 1 3/4 x 1 1/4 in. (45 x 32 mm)","Gift of Mrs. Thomas Hunt, 1941",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/437696,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -62.122.32,false,true,437665,European Paintings,"Painting, miniature",Portrait of a Boy,,,,,,Artist,Style of,Samuel Shelley,late 18th century,,"Shelley, Samuel",British,1756,1808,,1770,1799,Ivory,"Oval, 1 1/4 x 1 in. (33 x 27 mm)","Bequest of Millie Bruhl Fredrick, 1962",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/437665,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1984.442,false,true,436878,European Paintings,"Painting, miniature","Copy after ""Rubens, His Wife Helena Fourment (1614–1673), and Their Son Frans (1633–1678)""",,,,,,Artist,,Bernard Lens,"British, 1682–1740",,"Lens, Bernard",British,1682,1740,,1721,1721,Vellum,15 1/2 x 11 7/8 in. (394 x 302 mm),"Purchase, Mr. and Mrs. Charles Wrightsman Gift, 1984",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/436878,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -24.80.524,false,true,436777,European Paintings,"Painting, miniature",Admiral Adam Duncan (1731–1804),,,,,,Artist,Attributed to,Philip Jean,"British, 1755–1802",,"Jean, Philip",British,1755,1802,,1775,1802,Ivory,"Oval, 2 x 1 1/2 in. (50 x 40 mm)","Bequest of Mary Clark Thompson, 1923",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/436777,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -25.106.29,false,true,436778,European Paintings,"Painting, miniature",Portrait of a Man,,,,,,Artist,Attributed to,Philip Jean,"British, 1755–1802",,"Jean, Philip",British,1755,1802,,1775,1802,Ivory,"Oval, 1 3/4 x 1 1/2 in. (46 x 37 mm)","Gift of Mrs. Louis V. Bell, in memory of her husband, 1925",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/436778,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -62.122.36,false,true,435919,European Paintings,"Painting, miniature","George Howard (1773–1848), Lord Morpeth",,,,,,Artist,,Richard Collins,"British, 1755–1831",,"Collins, Richard",British,1755,1831,,1775,1831,Ivory,"Oval, 2 x 1 5/8 in. (52 x 41 mm)","Bequest of Millie Bruhl Fredrick, 1962",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/435919,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -95.14.97,false,true,437951,European Paintings,"Painting, miniature","Portrait of a Man, Said to Be Mr. Fitzgerald",,,,,,Artist,,William Wood,"British, 1769–1810",,"Wood, William",British,1769,1810,,1789,1810,Ivory,"Oval, 3 1/4 x 2 5/8 in. (81 x 66 mm)","The Moses Lazarus Collection, Gift of Josephine and Sarah Lazarus, in memory of their father, 1888–95",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/437951,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -95.14.69,false,true,435920,European Paintings,"Painting, miniature",Portrait of a Man,,,,,,Artist,,Samuel Collins,"British, 1735?–1768",,"Collins, Samuel",British,1735,1768,,1755,1768,Ivory,"Oval, 1 5/8 x 1 1/4 in. (40 x 32 mm)","The Moses Lazarus Collection, Gift of Josephine and Sarah Lazarus, in memory of their father, 1888–95",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/435920,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -62.122.21,false,true,435943,European Paintings,"Painting, miniature","Charles II (1630–1685), King of England",,,,,,Artist,Style of,Samuel Cooper,"British, probably after 1672",,"Cooper, Samuel",British,1608,1672,,1672,1700,Vellum laid on prepared card with gessoed back,"Oval, 1 1/4 x 1 1/8 in. (33 x 27 mm)","Bequest of Millie Bruhl Fredrick, 1962",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/435943,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -33.136.15,false,true,436298,European Paintings,"Painting, miniature",Portrait of a Woman,,,,,,Artist,,Joshua Wilson Faulkner,"British, active ca. 1809–20",,"Faulkner, Joshua Wilson",British,1809,1820,,1809,1820,Ivory,4 x 3 1/8 in. (100 x 80 mm),"Bequest of Margaret Crane Hurlbut, 1933",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/436298,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -59.23.78,false,true,437695,European Paintings,"Painting, miniature","Portrait of a Woman, Said to Be Lady Dering",,,,,,Artist,,John Smart,"British, Norfolk 1741–1811 London",,"Smart, John",British,1741,1811,,1761,1811,Pencil and watercolor on paper,2 1/4 x 2 1/8 in. (56 x 54 mm),"Bequest of Alexandrine Sinsheimer, 1958",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/437695,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -68.213,false,true,437277,European Paintings,"Painting, miniature",A Man with the Initials FM,,,,,,Artist,Attributed to,William Pether,"British, Carlisle ca. 1738–1821 Bristol",,"Pether, William",British,1733,1821,,1751,1821,Ivory,"Oval, 3 x 2 1/4 in. (75 x 56 mm)","Gift of Lilliana Teruzzi, 1968",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/437277,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -24.80.499,false,true,435996,European Paintings,"Painting, miniature",Ensign Lionel Robert Tollemache (1774–1793),,,,,,Artist,,Richard Cosway,"British, Oakford, Devon 1742–1821 London",,"Cosway, Richard",British,1742,1821,,1762,1821,Ivory,"Oval, 3 1/8 x 2 1/2 in. (81 x 65 mm)","Bequest of Mary Clark Thompson, 1923",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/435996,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -05.31,false,true,436279,European Paintings,"Painting, sketch",The Three Graces,,,,,,Artist,,William Etty,"British, York 1787–1849 York",,"Etty, William",British,1787,1849,,1807,1849,Oil on millboard,22 1/2 x 18 3/4 in. (57.2 x 47.6 cm),"Rogers Fund, 1905",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436279,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -59.131,false,true,436278,European Paintings,Painting,Allegory,,,,,,Artist,,William Etty,"British, York 1787–1849 York",,"Etty, William",British,1787,1849,,1807,1849,"Oil on canvas, laid down on wood","Oval, 28 x 34 1/2 in. (71.1 x 87.6 cm)","Gift of Martin Birnbaum, 1959",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436278,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -39.65.1,false,true,437503,European Paintings,Painting,"Mrs. George Horsley (Charlotte Mary Talbot, died 1828)",,,,,,Artist,Attributed to,John Westbrooke Chandler,"British, 1763?–?1807 Edinburgh",,"Chandler, John Westbrooke",British,1763,1807,,1783,1807,Oil on canvas,30 x 24 7/8 in. (76.2 x 63.2 cm),"Bequest of Jacob Ruppert, 1939",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437503,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -60.94.2,false,true,437103,European Paintings,Painting,Harbor Scene: An English Ship with Sails Loosened Firing a Gun,,,,,,Artist,,Peter Monamy,"British, London 1681–1749 London",,"Monamy, Peter",British,1681,1749,,1704,1749,Oil on canvas,48 x 59 in. (121.9 x 149.9 cm),"Gift of William P. Clyde, 1960",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437103,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -53.113,false,true,436691,European Paintings,Painting,"Richard Humphreys, the Boxer",,,,,,Artist,,John Hoppner,"British, London 1758–1810 London",,"Hoppner, John",British,1758,1810,,1778,1810,Oil on canvas,55 3/4 x 44 1/4 in. (141.6 x 112.4 cm),"The Alfred N. Punnett Endowment Fund, 1953",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436691,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -65.203,false,true,436687,European Paintings,Painting,"Mrs. Richard Brinsley Sheridan (Hester Jane Ogle, 1775/76–1817) and Her Son (Charles Brinsley Sheridan, 1796–1843)",,,,,,Artist,,John Hoppner,"British, London 1758–1810 London",,"Hoppner, John",British,1758,1810,,1778,1810,Oil on canvas,93 3/4 x 59 in. (238.1 x 149.9 cm),"Gift of Mrs. Carll Tucker, 1965",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436687,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -95.27.2,false,true,436631,European Paintings,Painting,Self-Portrait,,,,,,Artist,,George Henry Harlow,"British, London 1787–1819 London",,"Harlow, George Henry",British,1787,1819,,1807,1819,Oil on canvas,30 x 25 in. (76.2 x 63.5 cm),"Gift of George A. Hearn, 1895",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436631,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.264.4,false,true,437239,European Paintings,Painting,"The Grandchildren of Sir William Heathcote, 3rd Baronet",,,,,,Artist,,William Owen,"British, Ludlow 1769–1825 London",,"Owen, William",British,1769,1825,,1789,1825,Oil on canvas,55 1/4 x 67 1/2 in. (140.3 x 171.5 cm),"Gift of Heathcote Art Foundation, 1986",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437239,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -65.181.9,false,true,436852,European Paintings,Painting,John Julius Angerstein (1736–1823),,,,,,Artist,,Sir Thomas Lawrence,"British, Bristol 1769–1830 London",and Workshop,"Lawrence, Thomas, Sir",British,1769,1830,,1789,1830,Oil on canvas,36 x 28 in. (91.4 x 71.1 cm),"Bequest of Adele L. Lehman, in memory of Arthur Lehman, 1965",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436852,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -49.7.55,false,true,436432,European Paintings,Painting,Queen Charlotte,,,,,,Artist,,Thomas Gainsborough,"British, Sudbury 1727–1788 London",,"Gainsborough, Thomas",British,1727,1788,,1747,1788,Oil on canvas,23 3/4 x 17 1/2 in. (60.3 x 44.5 cm),"The Jules Bache Collection, 1949",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436432,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -60.71.7,false,true,436438,European Paintings,Painting,"John Hobart (1723–1793), 2nd Earl of Buckinghamshire",,,,,,Artist,,Thomas Gainsborough,"British, Sudbury 1727–1788 London",,"Gainsborough, Thomas",British,1727,1788,,1747,1788,Oil on canvas,29 1/2 x 24 3/4 in. (74.9 x 62.9 cm),"Bequest of Lillian S. Timken, 1959",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436438,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -66.88.1,false,true,436436,European Paintings,Painting,"Mrs. Ralph Izard (Alice De Lancey, 1746/47–1832)",,,,,,Artist,,Thomas Gainsborough,"British, Sudbury 1727–1788 London",,"Gainsborough, Thomas",British,1727,1788,,1747,1788,Oil on canvas,"Oval, 30 1/4 x 25 1/8 in. (76.8 x 63.8 cm)","Bequest of Jeanne King deRham, in memory of her father, David H. King Jr., 1966",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436436,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1990.200,false,true,436434,European Paintings,Painting,Lieutenant Colonel Paul Pechell (1724–1800),,,,,,Artist,,Thomas Gainsborough,"British, Sudbury 1727–1788 London",,"Gainsborough, Thomas",British,1727,1788,,1747,1788,Oil on canvas,30 1/8 x 25 1/8 in. (76.5 x 63.8 cm),"Gift of Mr. and Mrs. Harry Payne Bingham Jr., 1990",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436434,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -39.65.5,false,true,435998,European Paintings,Painting,"Admiral Harry Paulet (1719/20–1794), Sixth Duke of Bolton",,,,,,Artist,,Francis Cotes,"British, London 1726–1770 Richmond",,"Cotes, Francis",British,1726,1770,,1760,1770,Oil on canvas,50 x 40 in. (127 x 101.6 cm),"Bequest of Jacob Ruppert, 1939",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435998,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -44.56,false,true,437643,European Paintings,Painting,The Building of Westminster Bridge,,,,,,Artist,,Samuel Scott,"British, London ca. 1702–1772 Bath",,"Scott, Samuel",British,1697,1772,,1722,1772,Oil on canvas,24 x 44 3/8 in. (61 x 112.7 cm),"Purchase, Charles B. Curtis Fund and Joseph Pulitzer Bequest, 1944",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437643,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -45.59.3,false,true,437446,European Paintings,Painting,"Mrs. Horton, Later Viscountess Maynard (died 1814/15)",,,,,,Artist,,Sir Joshua Reynolds,"British, Plympton 1723–1792 London",,"Reynolds, Joshua, Sir",British,1723,1792,,1767,1769,Oil on canvas,36 1/4 x 28 in. (92.1 x 71.1 cm),"Fletcher Fund, 1945",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437446,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -42.152.1,false,true,437453,European Paintings,Painting,Portrait of a Woman,,,,,,Artist,,Sir Joshua Reynolds,"British, Plympton 1723–1792 London",,"Reynolds, Joshua, Sir",British,1723,1792,,1743,1792,Oil on canvas,29 5/8 x 24 1/2 in. (75.2 x 62.2 cm),"Bequest of George D. Pratt, 1935",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437453,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -50.145.18,false,true,436240,European Paintings,Painting,Mrs. John Puget (Catherine Hawkins),,,,,,Artist,Attributed to,Richard Gainsborough Dupont,"British, Sudbury 1789–1874 Sudbury",,"Dupont, Richard Gainsborough",British,1789,1874,,1774,1797,Oil on copper,6 x 4 3/4 in. (15.2 x 12.1 cm),"Bequest of Mary Stillman Harkness, 1950",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436240,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -87.15.79,false,true,436868,European Paintings,Painting,Lucia,,,,,,Artist,,"Frederic, Lord Leighton","British, Scarborough 1830–1896 London",,"Leighton, Frederic, Lord",British,1830,1896,,1850,1896,Oil on canvas,14 7/8 x 10 in. (37.8 x 25.4 cm),"Catharine Lorillard Wolfe Collection, Bequest of Catharine Lorillard Wolfe, 1887",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436868,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -69.104,false,true,435993,European Paintings,Painting,"Marianne Dorothy Harland (1759–1785), Later Mrs. William Dalrymple",,,,,,Artist,,Richard Cosway,"British, Oakford, Devon 1742–1821 London",,"Cosway, Richard",British,1742,1821,,1762,1821,Oil on canvas,28 x 36 1/8 in. (71.1 x 91.8 cm),"Gift of Mrs. William M. Haupt, from the collection of Mrs. James B. Haggin, 1969",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435993,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -49.7.56,false,true,436239,European Paintings,Painting,"Anne Elizabeth Cholmley (1769–1788), Later Lady Mulgrave",,,,,,Artist,,Gainsborough Dupont,"British, Sudbury, Suffolk 1754–1797 London",,"Dupont, Gainsborough",British,1754,1797,,1774,1797,Oil on wood,Overall 7 1/8 x 5 3/4 in. (18.1 x 14.6 cm); painted surface 6 x 4 3/4 in. (15.2 x 12.1 cm),"The Jules Bache Collection, 1949",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436239,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -56.54.1,false,true,437660,European Paintings,Painting,Portrait of a Horseman,,,,,,Artist,,James Seymour,"British, London ca. 1702–1752 Southwark (London)",,"Seymour, James",British,1697,1752,,1748,1748,Oil on canvas,37 x 51 5/8 in. (94 x 131.1 cm),"Gift of the children of the late Otto H. and Addie W. Kahn (Lady Maud E. Marriott, Mrs. Margaret D. Ryan, Roger W. Kahn, and Gilbert W. Kahn), 1956",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437660,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.264.2,false,true,435630,European Paintings,Painting,Edward Miles (1752–1828),,,,,,Artist,,Sir William Beechey,"British, Burford, Oxfordshire 1753–1839 Hampstead",,"Beechey, William, Sir",British,1753,1839,,1785,1785,Oil on canvas,11 7/8 x 9 7/8 in. (30.2 x 25.1 cm),"Gift of Heathcote Art Foundation, 1986",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435630,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1986.264.3,false,true,435631,European Paintings,Painting,"George IV (1762–1830), When Prince of Wales",,,,,,Artist,,Sir William Beechey,"British, Burford, Oxfordshire 1753–1839 Hampstead",and Workshop,"Beechey, William, Sir",British,1753,1839,,1773,1839,Oil on canvas,56 1/4 x 44 1/2 in. (142.9 x 113 cm),"Gift of Heathcote Art Foundation, 1986",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435631,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -50.169,false,true,437499,European Paintings,Painting,Portrait of a Man,,,,,,Artist,,George Romney,"British, Beckside, Lancashire 1734–1802 Kendal, Cumbria",,"Romney, George",British,1734,1802,,1754,1802,Oil on canvas,30 x 24 3/4 in. (76.2 x 62.9 cm),"Gift of Mr. and Mrs. Edwin C. Vogel, 1950",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437499,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -53.220,false,true,437505,European Paintings,Painting,Admiral Sir Chaloner Ogle (1726–1816),,,,,,Artist,,George Romney,"British, Beckside, Lancashire 1734–1802 Kendal, Cumbria",,"Romney, George",British,1734,1802,,1754,1802,Oil on canvas,30 x 24 5/8 in. (76.2 x 62.5 cm),"Gift of Lennen and Newell Inc., 1953",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437505,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -45.59.5,false,true,437502,European Paintings,Painting,"Mrs. Charles Frederick (Martha Rigden, died 1794)",,,,,,Artist,,George Romney,"British, Beckside, Lancashire 1734–1802 Kendal, Cumbria",,"Romney, George",British,1734,1802,,1754,1802,Oil on canvas,29 3/4 x 24 3/4 in. (75.6 x 62.9 cm),"Fletcher Fund, 1945",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437502,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1974.289.2,false,true,435775,European Paintings,Painting,Kynance,,,,,,Artist,,John Brett,"British, Bletchingly 1831–1902 London",,"Brett, John",British,1831,1902,1888,1888,1888,Oil on canvas,7 x 14 1/8 in. (17.8 x 35.9 cm),"Bequest of Theodore Rousseau Jr., 1973",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435775,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1972.145.7,false,true,435726,European Paintings,"Painting, miniature",Count Alexander Ivanovich Sollogoub (1788–1844),,,,,,Artist,,Domenico Bossi,"Italian, Venetian, 1765–1853",,"Bossi, Domenico",Italian,1765,1853,1810,1810,1810,Ivory,"Oval, 2 3/8 x 1 7/8 in. (60 x 47 mm)","Gift of Humanities Fund Inc., 1972",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/435726,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1992.104,false,true,436977,European Paintings,"Painting, miniature",Napoléon I (1769–1821) on Horseback,,,,,,Artist,,Luigi Marta,"Italian, Neapolitan, 1790–1858",,"Marta, Luigi",Italian,1790,1858,1830,1830,1830,Ivory,5 3/4 x 7 1/2 in. (146 x 191 mm),"Gift of Gloria Zicht, 1992",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/436977,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.360,false,true,439272,European Paintings,"Painting, drawing",Study of a Boy in a Blue Jacket,,,,,,Artist,,Benedetto Luti,"Italian, Florence 1666–1724 Rome",,"Luti, Benedetto",Italian,1666,1724,1717,1717,1717,"Pastel and chalk on blue laid paper, laid down on paste paper",16 x 13 in. (40.6 x 33 cm),"Gwynne Andrews Fund, 2007",,,,,,,,,,,,Pastels & Oil Sketches on Paper,,http://www.metmuseum.org/art/collection/search/439272,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2007.361,false,true,439273,European Paintings,"Painting, drawing",Study of a Girl in Red,,,,,,Artist,,Benedetto Luti,"Italian, Florence 1666–1724 Rome",,"Luti, Benedetto",Italian,1666,1724,1717,1717,1717,"Pastel and chalk on blue laid paper, laid down on paste paper",16 1/2 x 13 3/8 in. (41.9 x 34 cm),"Gwynne Andrews Fund, 2007",,,,,,,,,,,,Pastels & Oil Sketches on Paper,,http://www.metmuseum.org/art/collection/search/439273,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -49.122.2,false,true,435854,European Paintings,"Painting, miniature",Portrait of a Man,,,,,,Artist,,Rosalba Carriera,"Italian, Venice 1673–1757 Venice",,"Carriera, Rosalba",Italian,1673,1757,ca. 1710,1705,1715,Ivory,"Oval, 3 x 2 1/4 in. (76 x 59 mm)","Rogers Fund, 1949",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/435854,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2002.22,false,true,438544,European Paintings,"Painting, drawing","Gustavus Hamilton (1710–1746), Second Viscount Boyne, in Masquerade Costume",,,,,,Artist,,Rosalba Carriera,"Italian, Venice 1673–1757 Venice",,"Carriera, Rosalba",Italian,1673,1757,1730–31,1730,1731,"Pastel on paper, laid down on canvas",22 1/4 x 16 7/8 in. (56.5 x 42.9 cm),"Purchase, George Delacorte Fund Gift, in memory of George T. Delacorte Jr., and Gwynne Andrews, Victor Wilbour Memorial, and Marquand Funds, 2002",,,,,,,,,,,,Pastels & Oil Sketches on Paper,,http://www.metmuseum.org/art/collection/search/438544,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1999.287,false,true,438393,European Paintings,"Painting, miniature",Pope Pius VII (1742–1823),,,,,,Artist,Attributed to,Bianca Boni,"Italian, Florentine, active early 19th century",,"Boni, Bianca",Italian,1800,1829,ca. 1820,1815,1825,Watercolor on ivory,Diameter 2 3/4 in. (70 mm),"Bequest of Francesca Rospigliosi, 1998",,,,,,,,,,,,Miniatures,,http://www.metmuseum.org/art/collection/search/438393,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -11.126.2,false,true,436032,European Paintings,"Painting, cassone panel",The Legend of Cloelia,,,,,,Artist,,Guidoccio di Giovanni Cozzarelli,"Italian, Sienese, 1450–1516",,"Cozzarelli, Guidoccio di Giovanni",Italian,1450,1516,ca. 1480,1475,1485,Tempera and gold on wood,Overall 17 3/4 x 45 1/2 in. (45.1 x 115.6 cm); painted surface 15 1/8 x 43 1/4 in. (38.4 x 109.9 cm),"Frederick C. Hewitt Fund, 1911",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436032,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2010.289,false,true,441024,European Paintings,Painting,Head of an Old Woman,,,,,,Artist,,Orazio Borgianni,"Italian, Rome 1578–1616 Rome",,"Borgianni, Orazio",Italian,1578,1616,after 1610,1611,1616,Oil on canvas,20 7/8 x 15 3/8 in. (53 x 39 cm),"Purchase, Gwynne Andrews Fund and Marco Voena and Luigi Koelliker Gift, 2010",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/441024,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2012.543,false,true,442183,European Paintings,Painting,A Cardinal's Procession,,,,,,Artist,,Ottavio Leoni (Il Padovano),"Italian, Rome 1578–1630 Rome",,"Leoni, Ottavio (Il Padovano)",Italian,1578,1630,1621,1621,1621,Oil on copper,15 1/2 x 14 3/4 in. (39.4 x 37.5 cm),"Gift of Damon Mezzacappa, 2012",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/442183,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2014.277,false,true,643540,European Paintings,Painting,Portrait of a Woman,,,,,,Artist,,Giovanni Battista Gaulli (Il Baciccio),"Italian, Genoa 1639–1709 Rome",,"Gaulli, Giovanni Battista",Italian,1639,1709,ca. 1670s,1670,1679,Oil on canvas,28 5/8 × 23 1/4 in. (72.7 × 59.1 cm),"Gift of Álvaro Saieh Bendeck, Jean-Luc Baroni, and Fabrizio Moretti, in honor of Keith Christiansen, 2014",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/643540,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1987.75,false,true,437784,European Paintings,Painting,Alexander the Great Rescued from the River Cydnus,,,,,,Artist,,Pietro Testa,"Italian, Lucca 1612–1650 Rome",,"Testa, Pietro",Italian,1612,1650,ca. 1650,1645,1655,Oil on canvas,38 x 54 in. (96.5 x 137.2 cm),"Gift of Eula M. Ganz, in memory of Paul H. Ganz, 1987",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437784,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -03.37.1,false,true,435623,European Paintings,Painting,Portrait of a Young Man,,,,,,Artist,,Pompeo Batoni,"Italian, Lucca 1708–1787 Rome",,"Batoni, Pompeo",Italian,1708,1787,ca. 1760–65,1760,1765,Oil on canvas,97 1/8 x 69 1/4 in. (246.7 x 175.9 cm),"Rogers Fund, 1903",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435623,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1982.438,false,true,435622,European Paintings,Painting,Diana and Cupid,,,,,,Artist,,Pompeo Batoni,"Italian, Lucca 1708–1787 Rome",,"Batoni, Pompeo",Italian,1708,1787,1761,1761,1761,Oil on canvas,49 x 68 in. (124.5 x 172.7 cm),"Purchase, The Charles Engelhard Foundation, Robert Lehman Foundation Inc., Mrs. Haebler Frantz, April R. Axton, L. H. P. Klotz, and David Mortimer Gifts; and Gifts of Mr. and Mrs. Charles Wrightsman, George Blumenthal, and J. Pierpont Morgan, Bequests of Millie Bruhl Fredrick and Mary Clark Thompson, and Rogers Fund, by exchange, 1982",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435622,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1971.93,false,true,437607,European Paintings,Painting,Paradise,,,,,,Artist,,Carlo Saraceni,"Italian, Venetian, 1579?–1620",,"Saraceni, Carlo",Italian,1579,1620,ca. 1598,1593,1603,Oil on copper,Overall 21 3/8 x 18 7/8 in. (54.3 x 47.9 cm); painted surface 20 7/8 x 18 3/8 in. (53 x 46.7 cm),"Theodore M. Davis Collection, Bequest of Theodore M. Davis, by exchange, 1971",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437607,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -13.212,false,true,436908,European Paintings,Painting,Saint Catherine of Alexandria,,,,,,Artist,,Pietro Lorenzetti,"Italian, active Siena 1320–44",,"Lorenzetti, Pietro",Italian,1320,1344,shortly after 1342,1342,1344,"Tempera on wood, gold ground",Overall 26 x 16 1/4 in. (66 x 41.3 cm); painted surface 24 1/2 x 16 1/4 in. (62.2 x 41.3 cm),"Rogers Fund, 1913",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436908,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2002.436,true,true,438605,European Paintings,Painting,The Crucifixion,,,,,,Artist,,Pietro Lorenzetti,"Italian, active Siena 1320–44",,"Lorenzetti, Pietro",Italian,1320,1344,1340s,1340,1344,Tempera and gold leaf on wood,Overall 16 1/2 x 12 1/2 in. (41.9 x 31.8 cm); painted surface 14 1/8 x 10 1/8 in. (35.9 x 25.7 cm),"Purchase, Lila Acheson Wallace Gift and Gwynne Andrews Fund, 2002",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/438605,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -92.1.62,false,true,436943,European Paintings,Painting,A Circus Boy,,,,,,Artist,,Antonio Mancini,"Italian, Albano 1852–1930 Rome",,"Mancini, Antonio",Italian,1852,1930,1872,1872,1872,Oil on canvas,59 5/8 x 28 1/2 in. (151.4 x 72.4 cm),"Bequest of Elizabeth U. Coles, in memory of her son, William F. Coles, 1892",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436943,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1984.191,false,true,436935,European Paintings,Painting,The Tame Magpie,,,,,,Artist,,Alessandro Magnasco,"Italian, Genoa 1667–1749 Genoa",,"Magnasco, Alessandro",Italian,1667,1749,ca. 1707–8,1707,1708,Oil on canvas,25 x 29 1/2 in. (63.5 x 74.9 cm),"Purchase, Katherine D. W. Glover Gift, 1984",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436935,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -41.100.13,false,true,435659,European Paintings,Painting,Madonna and Child with Angels,,,,,,Artist,,Bernardino da Genoa,"Italian, Genoese, active in 1515",,Bernardino da Genoa,Italian,1515,1515,1515,1515,1515,Oil on wood,29 3/8 x 22 5/8 in. (74.6 x 57.5 cm),"Gift of George Blumenthal, 1941",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435659,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1973.165,false,true,435833,European Paintings,Painting,Herodias,,,,,,Artist,,Francesco Cairo,"Italian, Milan 1607–1665 Milan",,"Cairo, Francesco",Italian,1607,1665,before 1635,1627,1635,Oil on canvas,29 5/8 x 24 5/8 in. (75.2 x 62.5 cm),"Gift of Paul Ganz, in memory of Rudolf Wittkower, 1973",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435833,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2012.100.2,false,true,441230,European Paintings,Painting,A Female Martyr Saint,,,,,,Artist,,Carlo Francesco Nuvolone,"Italian, Milan 1609–1662 Milan",,"Nuvolone, Carlo Francesco",Italian,1609,1662,ca. 1650,1645,1655,Oil on wood,20 x 16 3/8 in. (50.8 x 41.6 cm),"Bequest of Anna Mont, in memory of Frederick Mont, 2010",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/441230,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -30.15,false,true,435864,European Paintings,Painting,A Woman with a Dog,,,,,,Artist,,Giacomo Ceruti,"Italian, Milan 1698–1767 Milan",,"Ceruti, Giacomo",Italian,1698,1767,1740s,1740,1749,Oil on canvas,38 x 28 1/2 in. (96.5 x 72.4 cm),"Maria DeWitt Jesup Fund, 1930",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435864,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -59.40,false,true,437745,European Paintings,Painting,Judith with the Head of Holofernes,,,,,,Artist,,Massimo Stanzione,"Italian, Neapolitan, 1585–1656",,"Stanzione, Massimo",Italian,1585,1656,ca. 1640,1635,1645,Oil on canvas,78 1/2 x 57 1/2 in. (199.4 x 146.1 cm),"Gift of Edward W. Carter, 1959",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437745,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -71.17,false,true,437376,European Paintings,Painting,A Cat Stealing Fish,,,,,,Artist,,Giuseppe Recco,"Italian, Neapolitan, 1634–1695",,"Recco, Giuseppe",Italian,1634,1695,late 1660s,1667,1669,Oil on canvas,38 x 50 1/2 in. (96.5 x 128.3 cm),"Purchase, 1871",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437376,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -06.1046,false,true,436512,European Paintings,"Painting, predella panel",Paradise,,,,,,Artist,,Giovanni di Paolo (Giovanni di Paolo di Grazia),"Italian, Siena 1398–1482 Siena",,Giovanni di Paolo (Giovanni di Paolo di Grazia),Italian,1398,1482,1445,1445,1445,"Tempera and gold on canvas, transferred from wood",Overall 18 1/2 x 16 in. (47 x 40.6 cm); painted surface 17 1/2 x 15 1/8 in. (44.5 x 38.4 cm),"Rogers Fund, 1906",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436512,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -41.100.4,false,true,436513,European Paintings,"Painting, predella panel",The Presentation of Christ in the Temple,,,,,,Artist,,Giovanni di Paolo (Giovanni di Paolo di Grazia),"Italian, Siena 1398–1482 Siena",,Giovanni di Paolo (Giovanni di Paolo di Grazia),Italian,1398,1482,ca. 1435,1430,1440,Tempera and gold on wood,Overall 15 1/2 x 18 1/8 in. (39.4 x 46 cm); painted surface 15 1/4 x 17 1/4 in. (38.7 x 43.8 cm),"Gift of George Blumenthal, 1941",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436513,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -88.3.111,false,true,436515,European Paintings,Painting,Saints Matthew and Francis,,,,,,Artist,,Giovanni di Paolo (Giovanni di Paolo di Grazia),"Italian, Siena 1398–1482 Siena",,Giovanni di Paolo (Giovanni di Paolo di Grazia),Italian,1398,1482,ca. 1435,1430,1440,"Tempera on wood, gold ground","Overall, with added strips, 54 5/8 x 34 3/4 in. (138.7 x 88.3 cm); painted surface 52 7/8 x 33 1/2 in. (134.3 x 85.1 cm)","Gift of Coudert Brothers, 1888",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436515,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1982.60.4,false,true,436509,European Paintings,Painting,The Adoration of the Magi,,,,,,Artist,,Giovanni di Paolo (Giovanni di Paolo di Grazia),"Italian, Siena 1398–1482 Siena",,Giovanni di Paolo (Giovanni di Paolo di Grazia),Italian,1398,1482,ca. 1460,1455,1465,Tempera and gold on wood,10 5/8 x 9 1/8 in. (27 x 23.2 cm),"The Jack and Belle Linsky Collection, 1982",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436509,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -32.100.76,false,true,436508,European Paintings,"Painting, polyptych",Madonna and Child with Saints,,,,,,Artist,,Giovanni di Paolo (Giovanni di Paolo di Grazia),"Italian, Siena 1398–1482 Siena",,Giovanni di Paolo (Giovanni di Paolo di Grazia),Italian,1398,1482,1454,1454,1454,"Tempera on wood, gold ground","Central panel 82 3/4 x 25 7/8 in. (210.2 x 65.7 cm); left panels 70 7/8 x 16 7/8 in. (180 x 42.9 cm), 70 7/8 x 16 3/4 in. (180 x 42.5 cm); right panels 70 7/8 x 16 7/8 in. (180 x 42.9 cm), 70 7/8 x 16 3/4 in. (180 x 42.5 cm)","The Friedsam Collection, Bequest of Michael Friedsam, 1931",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436508,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -41.190.16,false,true,436510,European Paintings,Painting,Madonna and Child with Two Angels and a Donor,,,,,,Artist,,Giovanni di Paolo (Giovanni di Paolo di Grazia),"Italian, Siena 1398–1482 Siena",,Giovanni di Paolo (Giovanni di Paolo di Grazia),Italian,1398,1482,ca. 1445,1440,1450,"Tempera on wood, gold ground (partly checkered with modern red glazes)",Shaped top: overall 57 1/8 x 32 in. (145.1 x 81.3 cm); painted surface 54 1/4 x 32 in. (137.8 x 81.3 cm),"Bequest of George Blumenthal, 1941",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436510,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -32.100.83a–d,false,true,436514,European Paintings,"Painting, pilasters of an altarpiece","Saints Catherine of Alexandria, Barbara, Agatha, and Margaret",,,,,,Artist,,Giovanni di Paolo (Giovanni di Paolo di Grazia),"Italian, Siena 1398–1482 Siena",,Giovanni di Paolo (Giovanni di Paolo di Grazia),Italian,1398,1482,ca. 1470,1465,1475,"Tempera on wood, gold ground","(a) overall 18 3/4 x 6 in. (47.6 x 15.2 cm), painted surface 18 1/4 x 5 1/2 in. (46.4 x 14 cm); (b) overall 18 3/4 x 6 in. (47.6 x 15.2 cm), painted surface 18 3/8 x 5 5/8 in. (46.7 x 14.3 cm); (c) overall 18 3/4 x 6 in. (47.6 x 15.2 cm), painted surface 18 3/8 x 5 3/8 in. (46.7 x 13.7 cm); (d) overall 18 3/4 x 6 in. (47.6 x 15.2 cm), painted surface 18 1/4 x 5 5/8 in. (46.4 x 14.3 cm)","The Friedsam Collection, Bequest of Michael Friedsam, 1931",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436514,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -58.189.1,false,true,437606,European Paintings,"Painting, predella panel",The Massacre of the Innocents,,,,,,Artist,,Sano di Pietro (Ansano di Pietro di Mencio),"Italian, Siena 1405–1481 Siena",,Sano di Pietro (Ansano di Pietro di Mencio),Italian,1405,1481,ca. 1470,1465,1475,Tempera on wood,11 7/8 x 17 3/8 in. (30.2 x 44.1 cm),"Gift of Irma N. Straus, 1958",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437606,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -58.189.2,false,true,437602,European Paintings,"Painting, predella panel",The Adoration of the Magi,,,,,,Artist,,Sano di Pietro (Ansano di Pietro di Mencio),"Italian, Siena 1405–1481 Siena",,Sano di Pietro (Ansano di Pietro di Mencio),Italian,1405,1481,ca. 1470,1465,1475,Tempera and gold on wood,11 7/8 x 18 3/4 in. (30.2 x 47.6 cm),"Gift of Irma N. Straus, 1958",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437602,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -64.189.4,false,true,437605,European Paintings,"Painting, portable triptych",Madonna and Child; Saint John the Baptist; Saint Jerome,,,,,,Artist,,Sano di Pietro (Ansano di Pietro di Mencio),"Italian, Siena 1405–1481 Siena",,Sano di Pietro (Ansano di Pietro di Mencio),Italian,1405,1481,ca. 1450–55,1450,1455,"Tempera on wood, gold ground","Central panel, overall, with engaged frame, 17 3/8 x 12 5/8 in. (44.1 x 32.1 cm), painted surface 14 3/4 x 10 1/8 in. (37.5 x 25.7 cm); each wing, overall, with engaged frame, 17 3/8 x 6 1/4 in. (44.1x 15.9 cm), painted surface 15 1/2 x 4 5/8 in. (39.4 x 11.7 cm)","Gift of Irma N. Straus, 1964",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437605,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -65.181.7,false,true,437603,European Paintings,"Painting, predella panel",The Burial of Saint Martha,,,,,,Artist,,Sano di Pietro (Ansano di Pietro di Mencio),"Italian, Siena 1405–1481 Siena",,Sano di Pietro (Ansano di Pietro di Mencio),Italian,1405,1481,ca. 1460–70,1455,1465,Tempera and gold on wood,5 1/2 x 11 1/2 in. (14 x 29.2 cm),"Bequest of Adele L. Lehman, in memory of Arthur Lehman, 1965",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437603,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1987.290.2ab,false,true,437604,European Paintings,"Painting, portable altarpiece","Madonna and Child with the Dead Christ, Saints Agnes and Catherine of Alexandria, and Two Angels",,,,,,Artist,,Sano di Pietro (Ansano di Pietro di Mencio),"Italian, Siena 1405–1481 Siena",,Sano di Pietro (Ansano di Pietro di Mencio),Italian,1405,1481,ca. 1470–80,1465,1475,"Tempera on wood, gold ground","Main panel, overall, with engaged (modern) frame, 12 3/4 x 11 3/4 in. (32.4 x 29.8 cm), painted surface 10 7/8 x 9 7/8 in. (27.6 x 25.1 cm); predella, overall, with engaged (modern) frame, 3 5/8 x 12 5/8 in. (9.2 x 32.1 cm), painted surface 2 5/8 x 11 7/8 in. (6.7 x 30.2 cm)","Anonymous Bequest, 1984",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437604,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -20.182,false,true,436330,European Paintings,"Painting, fragment of a cassone panel",Goddess of Chaste Love,,,,,,Artist,,Francesco di Giorgio Martini,"Italian, Siena 1439–1501 Siena",,Francesco di Giorgio Martini,Italian,1439,1501,1468–75,1468,1475,Tempera and gold on wood,15 1/2 x 17 1/4 in. (39.4 x 43.8 cm),"Marquand Fund, 1920",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436330,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1996.341,false,true,438029,European Paintings,"Painting, lunette",The Man of Sorrows with Two Angels,,,,,,Artist,Workshop of,Francesco di Giorgio Martini,"Italian, Siena 1439–1501 Siena",,Francesco di Giorgio Martini,Italian,1439,1501,ca. 1470,1465,1475,Tempera on wood,Frame 53 3/4 x 32 5/8 in. (136.5 x 82.9 cm); painted surface 6 7/8 x 18 1/4 in. (17.5 x 46.4 cm),"Anonymous Gift, in memory of Kurt Cassirer, 1996",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/438029,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -61.43,false,true,437195,European Paintings,Painting,Madonna and Child with Saints Jerome and Mary Magdalen,,,,,,Artist,,Neroccio de' Landi,"Italian, Siena 1447–1500 Siena",,Neroccio de' Landi,Italian,1447,1500,ca. 1490,1485,1495,Tempera on wood,24 x 17 1/4 in. (61 x 43.8 cm),"Gift of Samuel H. Kress Foundation, by exchange, 1961",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437195,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -22.60.61,false,true,437291,European Paintings,Painting,Processional Crucifix,,,,,,Artist,,Pietro di Francesco Orioli,"Italian, Siena 1458–1496 Siena",,Pietro di Francesco Orioli,Italian,1458,1496,ca. 1480s,1478,1496,"Tempera on wood, gold ground",Overall 21 1/4 x 18 1/2 in. (54 x 47 cm); painted surface 18 5/8 x 14 in. (47.3 x 35.6 cm),"The Bequest of Michael Dreicer, 1921",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437291,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -16.30ab,true,true,437372,European Paintings,"Painting, altarpiece",Madonna and Child Enthroned with Saints,,,,,,Artist,,Raphael (Raffaello Sanzio or Santi),"Italian, Urbino 1483–1520 Rome",,Raphael (Raffaello Sanzio or Santi),Italian,1483,1520,ca. 1504,1499,1509,Oil and gold on wood,"Main panel, overall 67 7/8 x 67 7/8 in. (172.4 x 172.4 cm), painted surface 66 3/4 x 66 1/2 in. (169.5 x 168.9 cm); lunette, overall 29 1/2 x 70 7/8 in. (74.9 x 180 cm), painted surface 25 1/2 x 67 1/2 in. (64.8 x 171.5 cm)","Gift of J. Pierpont Morgan, 1916",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437372,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -06.180,false,true,436498,European Paintings,Painting,The Man of Sorrows,,,,,,Artist,,Michele Giambono (Michele Giovanni Bono),"Italian, active Venice 1420–62",,"Giambono, Michele (Michele Giovanni Bono)",Italian,1420,1462,ca. 1430,1425,1435,Tempera and gold on wood,"Overall, with engaged frame, 21 5/8 x 15 1/4 in. (54.9 x 38.7 cm); painted surface 18 1/2 x 12 1/4 in. (47 x 31.1 cm)","Rogers Fund, 1906",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436498,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -50.229.1,false,true,437912,European Paintings,Painting,The Death of the Virgin,,,,,,Artist,,Bartolomeo Vivarini,"Italian, active Venice 1450–91",,"Vivarini, Bartolomeo",Italian,1450,1491,1485,1485,1485,Tempera on wood,"Arched top, 74 3/4 x 59 in. (189.9 x 149.9 cm)","Gift of Robert Lehman, 1950",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437912,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -65.181.1,false,true,437913,European Paintings,Painting fragment,A Saint (Mark?) Reading,,,,,,Artist,,Bartolomeo Vivarini,"Italian, active Venice 1450–91",,"Vivarini, Bartolomeo",Italian,1450,1491,ca. 1470,1465,1475,"Tempera on wood, gold ground",18 5/8 x 14 3/4 in. (47.3 x 37.5 cm),"Bequest of Adele L. Lehman, in memory of Arthur Lehman, 1965",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437913,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1971.155,true,true,435853,European Paintings,Painting,The Coronation of the Virgin,,,,,,Artist,,Annibale Carracci,"Italian, Bologna 1560–1609 Rome",,"Carracci, Annibale",Italian,1560,1609,after 1595,1595,1609,Oil on canvas,46 3/8 x 55 5/8 in. (117.8 x 141.3 cm),"Purchase, Bequest of Miss Adelaide Milton de Groot (1876–1967), by exchange, and Dr. and Mrs. Manuel Porter and sons Gift, in honor of Mrs. Sarah Porter, 1971",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435853,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1998.188,false,true,438338,European Paintings,Painting,The Burial of Christ,,,,,,Artist,,Annibale Carracci,"Italian, Bologna 1560–1609 Rome",,"Carracci, Annibale",Italian,1560,1609,1595,1595,1595,Oil on copper,17 1/4 x 13 3/4 in. (43.8 x 34.9 cm),"Purchase, Edwin L. Weisl Jr. Gift, 1998",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/438338,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2009.252,false,true,438813,European Paintings,Painting,Saint John the Baptist Bearing Witness,,,,,,Artist,,Annibale Carracci,"Italian, Bologna 1560–1609 Rome",,"Carracci, Annibale",Italian,1560,1609,ca. 1600,1595,1605,Oil on copper,21 3/8 x 17 1/8 in. (54.3 x 43.5 cm),"Gift of Fabrizio Moretti and Adam Williams, in honor of Everett Fahy, 2009",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/438813,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -57.23,false,true,437763,European Paintings,Painting,Tobias Curing His Father's Blindness,,,,,,Artist,,Bernardo Strozzi,"Italian, Genoa 1581–1644 Venice",,"Strozzi, Bernardo",Italian,1581,1644,1630–35,1630,1635,Oil on canvas,57 1/2 x 88 in. (146.1 x 223.5 cm),"Purchase, Mary Wetmore Shively Bequest, in memory of her husband, Henry L. Shively, M.D., 1957",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437763,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2014.270,false,true,647338,European Paintings,Painting,Saint Francis in Ecstasy,,,,,,Artist,,Giovanni Benedetto Castiglione (Il Grechetto),"Italian, Genoa 1609–1664 Mantua",,"Castiglione, Giovanni Benedetto (Il Grechetto)",Italian,1609,1664,ca. 1650,1645,1655,Oil on canvas,77 × 53 1/4 in. (195.6 × 135.3 cm),"Purchase, Lila Acheson Wallace Gift; Gwynne Andrews Fund; and Gift in memory of Felix M. Warburg from his wife and children, Bequest and Gift of George Blumenthal, Bequests of Theodore M. Davis, Adele L. Lehman, in memory of Arthur Lehman, Helen Hay Whitney, Jean Fowles, in memory of her first husband, R. Langton Douglas, and Gifts of Coudert Brothers and Harry Payne Bingham Jr., by exchange, 2014",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/647338,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1982.60.1,false,true,437243,European Paintings,Painting,Saint Romuald,,,,,,Artist,,Guido Palmeruccio (Guiduccio Palmerucci),"Italian, Gubbio, active 1315–49",,"Palmeruccio, Guido (Guiduccio Palmerucci)",Italian,1315,1349,possibly 1320s,1315,1349,"Tempera on wood, gold ground","Overall, with engaged frame, 18 1/8 x 10 3/4 in. (46 x 27.3 cm)","The Jack and Belle Linsky Collection, 1982",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437243,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -22.60.59,false,true,435616,European Paintings,Painting,Saint Dominic Resuscitating Napoleone Orsini,,,,,,Artist,,Bartolomeo degli Erri,"Italian, Modena, active 1460–79",,Bartolomeo degli Erri,Italian,1460,1479,1467–74,1467,1474,"Tempera on canvas, transferred from wood",14 x 17 1/2 in. (35.6 x 44.5 cm),"The Bequest of Michael Dreicer, 1921",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435616,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1973.311.3,false,true,436604,European Paintings,Painting,The Vocation of Saint Aloysius (Luigi) Gonzaga,,,,,,Artist,,Guercino (Giovanni Francesco Barbieri),"Italian, Cento 1591–1666 Bologna",,Guercino (Giovanni Francesco Barbieri),Italian,1591,1666,ca. 1650,1645,1655,Oil on canvas,140 x 106 in. (355.6 x 269.2 cm),"Gift of Mr. and Mrs. Charles Wrightsman, 1973",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436604,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1984.459.2,true,true,436603,European Paintings,Painting,Samson Captured by the Philistines,,,,,,Artist,,Guercino (Giovanni Francesco Barbieri),"Italian, Cento 1591–1666 Bologna",,Guercino (Giovanni Francesco Barbieri),Italian,1591,1666,1619,1619,1619,Oil on canvas,75 1/4 x 93 1/4 in. (191.1 x 236.9 cm),"Gift of Mr. and Mrs. Charles Wrightsman, 1984",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436603,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -47.71,false,true,435693,European Paintings,Painting,"Consuelo Vanderbilt (1876–1964), Duchess of Marlborough, and Her Son, Lord Ivor Spencer-Churchill (1898–1956)",,,,,,Artist,,Giovanni Boldini,"Italian, Ferrara 1842–1931 Paris",,"Boldini, Giovanni",Italian,1842,1931,1906,1906,1906,Oil on canvas,87 1/4 x 67 in. (221.6 x 170.2 cm),"Gift of Consuelo Vanderbilt Balsan, 1946",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435693,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -59.78,false,true,435692,European Paintings,Painting,"Mrs. Charles Warren-Cram (Ella Brooks Carter, 1846–1896)",,,,,,Artist,,Giovanni Boldini,"Italian, Ferrara 1842–1931 Paris",,"Boldini, Giovanni",Italian,1842,1931,1885,1885,1885,Oil on canvas,19 3/8 x 14 in. (49.2 x 35.6 cm),"Gift of Mrs. Edward C. Moën, 1959",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435692,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -87.15.81,false,true,435691,European Paintings,Painting,Gossip,,,,,,Artist,,Giovanni Boldini,"Italian, Ferrara 1842–1931 Paris",,"Boldini, Giovanni",Italian,1842,1931,1873,1873,1873,Oil on wood,7 x 9 1/2 in. (17.8 x 24.1 cm),"Catharine Lorillard Wolfe Collection, Bequest of Catharine Lorillard Wolfe, 1887",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435691,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -08.136.12,false,true,435694,European Paintings,Painting,The Dispatch-Bearer,,,,,,Artist,,Giovanni Boldini,"Italian, Ferrara 1842–1931 Paris",,"Boldini, Giovanni",Italian,1842,1931,?1879,1865,1931,Oil on wood,16 3/4 x 13 1/2 in. (42.5 x 34.3 cm),"Bequest of Martha T. Fiske Collord, in memory of her first husband, Josiah M. Fiske, 1908",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435694,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -2011.26,false,true,441227,European Paintings,Painting,The Holy Family with the Infant Saint John the Baptist,,,,,,Artist,,Perino del Vaga (Pietro Buonaccorsi),"Italian, Florence 1501–1547 Rome",,Perino del Vaga (Pietro Buonaccorsi),Italian,1501,1547,ca. 1524–26,1524,1526,Oil on wood,34 3/4 x 25 5/8 in. (88.3 x 65.1 cm),"Purchase, Acquisitions Fund, Mr. and Mrs. Mark Fisch, Denise and Andrew Saul, and Friends of European Paintings Gifts, Gwynne Andrews Fund, Mr. and Mrs. J. Tomilson Hill, Jon and Barbara Landau, Charles and Jessie Price, Hester Diamond, and Fern and George Wachter Gifts, 2011",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/441227,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1989.225,false,true,436891,European Paintings,Painting,Tobit Burying the Dead,,,,,,Artist,,Andrea di Lione,"Italian, Naples 1610–1685 Naples",,"Lione, Andrea di",Italian,1610,1685,1640s,1640,1649,Oil on canvas,50 1/4 x 68 1/2 in. (127.6 x 174 cm),"Gwynne Andrews Fund, 1989",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436891,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1973.311.2,false,true,436502,European Paintings,Painting,The Annunciation,,,,,,Artist,,Luca Giordano,"Italian, Naples 1634–1705 Naples",,"Giordano, Luca",Italian,1634,1705,1672,1672,1672,Oil on canvas,93 1/8 x 66 7/8 in. (236.5 x 169.9 cm),"Gift of Mr. and Mrs. Charles Wrightsman, 1973",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436502,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -71.31,false,true,437246,European Paintings,Painting,"Interior of Saint Peter's, Rome",,,,,,Artist,,Giovanni Paolo Panini,"Italian, Piacenza 1691–1765 Rome",,"Panini, Giovanni Paolo",Italian,1691,1765,after 1754,1754,1765,Oil on canvas,29 1/8 x 39 1/4 in. (74 x 99.7 cm),"Purchase, 1871",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437246,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -52.63.1,false,true,437244,European Paintings,Painting,Ancient Rome,,,,,,Artist,,Giovanni Paolo Panini,"Italian, Piacenza 1691–1765 Rome",,"Panini, Giovanni Paolo",Italian,1691,1765,1757,1757,1757,Oil on canvas,67 3/4 x 90 1/2 in. (172.1 x 229.9 cm),"Gwynne Andrews Fund, 1952",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437244,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -52.63.2,false,true,437245,European Paintings,Painting,Modern Rome,,,,,,Artist,,Giovanni Paolo Panini,"Italian, Piacenza 1691–1765 Rome",,"Panini, Giovanni Paolo",Italian,1691,1765,1757,1757,1757,Oil on canvas,67 3/4 x 91 3/4 in. (172.1 x 233 cm),"Gwynne Andrews Fund, 1952",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437245,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -41.100.34,false,true,436920,European Paintings,Painting,Madonna and Child,,,,,,Artist,,Luca di Tommè di Nuto,"Italian, Sienese, active 1356–89",,Luca di Tommè di Nuto,Italian,1356,1389,ca. 1360–65,1360,1365,"Tempera on wood, transferred from wood, gold ground","Shaped top, 52 7/8 x 23 1/8 in. (134.3 x 58.7 cm)","Gift of George Blumenthal, 1941",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/436920,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -41.100.35–37,false,true,437335,European Paintings,"Painting, triptych",Madonna and Child with Saints,,,,,,Artist,,Priamo della Quercia (Priamo del Pietro),"Italian, Sienese, active 1442–67",,Priamo della Quercia (Priamo del Pietro),Italian,1442,1467,ca. 1442,1442,1467,"Tempera on wood, gold ground",Central panel 43 1/4 x 22 1/2 in. (109.9 x 57.2 cm); left wing 45 1/2 x 22 in. (115.6 x 55.9 cm); right wing 45 1/4 x 22 1/4 in. (114.9 x 56.5 cm),"Gift of George Blumenthal, 1941",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437335,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -13.75,false,true,437821,European Paintings,Painting,The Miracle of the Loaves and Fishes,,,,,,Artist,,Jacopo Tintoretto (Jacopo Robusti),"Italian, Venice 1519–1594 Venice",,"Tintoretto, Jacopo (Jacopo Robusti)",Italian,1519,1594,ca. 1545–50,1545,1550,Oil on canvas,61 x 160 1/2 in. (154.9 x 407.7 cm),"Francis L. Leland Fund, 1913",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437821,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -58.49,false,true,437818,European Paintings,Painting,Portrait of a Young Man,,,,,,Artist,,Jacopo Tintoretto (Jacopo Robusti),"Italian, Venice 1519–1594 Venice",,"Tintoretto, Jacopo (Jacopo Robusti)",Italian,1519,1594,1551,1551,1551,Oil on canvas,54 1/2 x 42 in. (138.4 x 106.7 cm),"Gift of Lionel F. Straus Jr., in memory of his parents, Mr. and Mrs. Lionel F. Straus, 1958",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437818,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -10.206,false,true,437819,European Paintings,Painting,Doge Alvise Mocenigo (1507–1577) Presented to the Redeemer,,,,,,Artist,,Jacopo Tintoretto (Jacopo Robusti),"Italian, Venice 1519–1594 Venice",,"Tintoretto, Jacopo (Jacopo Robusti)",Italian,1519,1594,probably 1577,1577,1577,Oil on canvas,38 1/4 x 78 in. (97.2 x 198.1 cm),"John Stewart Kennedy Fund, 1910",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437819,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -41.100.12,false,true,437822,European Paintings,Painting,Portrait of a Man,,,,,,Artist,,Jacopo Tintoretto (Jacopo Robusti),"Italian, Venice 1519–1594 Venice",,"Tintoretto, Jacopo (Jacopo Robusti)",Italian,1519,1594,ca. 1540,1535,1545,Oil on canvas,44 3/8 x 35 in. (112.7 x 88.9 cm),"Gift of George Blumenthal, 1941",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437822,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1984.458,false,true,437268,European Paintings,Painting,Bacchus and Ariadne,,,,,,Artist,,Giovanni Antonio Pellegrini,"Italian, Venice 1675–1741 Venice",,"Pellegrini, Giovanni Antonio",Italian,1675,1741,1720s,1720,1729,Oil on canvas,46 x 50 1/2 in. (116.8 x 128.3 cm),"Gift of Mr. and Mrs. Eugene Victor Thaw, 1984",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437268,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1985.5,false,true,435573,European Paintings,Painting,Flora and Zephyr,,,,,,Artist,,Jacopo Amigoni,"Italian, Venice 1682–1752 Madrid",,"Amigoni, Jacopo",Italian,1682,1752,1730s,1730,1739,Oil on canvas,84 x 58 in. (213.4 x 147.3 cm),"Purchase, Rudolph and Lentilhon G. von Fluegge Foundation Inc. Gift, 1985",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/435573,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -67.187.90,false,true,437281,European Paintings,Painting,Saint Christopher Carrying the Infant Christ,,,,,,Artist,,Giovanni Battista Piazzetta,"Italian, Venice 1682–1754 Venice",,"Piazzetta, Giovanni Battista",Italian,1682,1754,1730s,1730,1739,Oil on canvas,28 1/4 x 22 1/8 in. (71.8 x 56.2 cm),"Bequest of Miss Adelaide Milton de Groot (1876–1967), 1967",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437281,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -23.128,false,true,437798,European Paintings,"Painting, ceiling decoration",The Glorification of the Barbaro Family,,,,,,Artist,,Giovanni Battista Tiepolo,"Italian, Venice 1696–1770 Madrid",,"Tiepolo, Giovanni Battista",Italian,1696,1770,ca. 1750,1745,1755,Oil on canvas,"Irregular oval, 96 x 183 3/4 in. (243.8 x 466.7 cm)","Anonymous Gift, in memory of Oliver H. Payne, 1923",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437798,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -71.121,false,true,437800,European Paintings,"Painting, sketch",The Investiture of Bishop Harold as Duke of Franconia,,,,,,Artist,,Giovanni Battista Tiepolo,"Italian, Venice 1696–1770 Madrid",,"Tiepolo, Giovanni Battista",Italian,1696,1770,ca. 1751–52,1751,1752,Oil on canvas,28 1/4 x 20 1/4 in. (71.8 x 51.4 cm),"Purchase, 1871",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437800,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1984.49,false,true,437796,European Paintings,"Painting, grisaille",A Female Allegorical Figure,,,,,,Artist,,Giovanni Battista Tiepolo,"Italian, Venice 1696–1770 Madrid",,"Tiepolo, Giovanni Battista",Italian,1696,1770,ca. 1740–50,1740,1750,"Oil on canvas, gold ground","Oval, 32 x 24 7/8 in. (81.3 x 63.2 cm)","Gift of Mr. and Mrs. Charles Wrightsman, 1984",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437796,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -1977.1.3,true,true,437790,European Paintings,"Painting, sketch",Allegory of the Planets and Continents,,,,,,Artist,,Giovanni Battista Tiepolo,"Italian, Venice 1696–1770 Madrid",,"Tiepolo, Giovanni Battista",Italian,1696,1770,1752,1752,1752,Oil on canvas,73 x 54 7/8 in. (185.4 x 139.4 cm),"Gift of Mr. and Mrs. Charles Wrightsman, 1977",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437790,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -37.165.2,false,true,437803,European Paintings,"Painting, sketch",Saint Thecla Praying for the Plague-Stricken,,,,,,Artist,,Giovanni Battista Tiepolo,"Italian, Venice 1696–1770 Madrid",,"Tiepolo, Giovanni Battista",Italian,1696,1770,1758–59,1758,1759,Oil on canvas,32 x 17 5/8 in. (81.3 x 44.8 cm),"Rogers Fund, 1937",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437803,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -43.85.12,false,true,437804,European Paintings,Painting,Virtue and Abundance,,,,,,Artist,,Giovanni Battista Tiepolo,"Italian, Venice 1696–1770 Madrid",and Workshop,"Tiepolo, Giovanni Battista",Italian,1696,1770,1760,1760,1760,"Fresco, transferred to canvas",Diameter 114 in. (289.6 cm),"Bequest of Grace Rainey Rogers, 1943",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437804,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" -43.85.21,false,true,437806,European Paintings,"Painting, grisaille",Prudence,,,,,,Artist,Workshop of,Giovanni Battista Tiepolo,"Italian, Venice 1696–1770 Madrid",,"Tiepolo, Giovanni Battista",Italian,1696,1770,1760,1760,1760,"Fresco, transferred to canvas","Oval, 49 1/8 x 36 1/4 in. (124.8 x 92.1 cm)","Bequest of Grace Rainey Rogers, 1943",,,,,,,,,,,,Paintings,,http://www.metmuseum.org/art/collection/search/437806,2017-02-06 08:00:16.000000 UTC,"Metropolitan Museum of Art, New York, NY" diff --git a/notebooks/geo/geoseries.ipynb b/notebooks/geo/geoseries.ipynb deleted file mode 100644 index 1159b8d31de..00000000000 --- a/notebooks/geo/geoseries.ipynb +++ /dev/null @@ -1,1051 +0,0 @@ -{ - "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": [ - "# Analyzing a GEOGRAPHY column with `bigframes.geopandas.GeoSeries`" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [], - "source": [ - "import bigframes\n", - "import bigframes.geopandas\n", - "import bigframes.pandas as bpd\n", - "bpd.options.display.progress_bar = None" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## 1. Load the Counties table from the Census Bureau US Boundaries dataset" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/usr/local/google/home/arwas/src1/python-bigquery-dataframes/bigframes/session/_io/bigquery/read_gbq_table.py:280: DefaultIndexWarning: Table 'bigquery-public-data.geo_us_boundaries.counties' is clustered\n", - "and/or partitioned, but BigQuery DataFrames was not able to find a\n", - "suitable index. To avoid this warning, set at least one of:\n", - "`index_col` or `filters`.\n", - " warnings.warn(msg, category=bfe.DefaultIndexWarning)\n" - ] - } - ], - "source": [ - "df = bpd.read_gbq(\"bigquery-public-data.geo_us_boundaries.counties\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### 2. Create a series from the int_point_geom column" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": {}, - "outputs": [], - "source": [ - "point_geom_series = df['int_point_geom']" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## The `GeoSeries` constructor accepts local data or a `bigframes.pandas.Series` object." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### 1. Create a GeoSeries from local data with `Peek`" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "18 POINT (-83.91172 42.60253)\n", - "86 POINT (-90.13369 43.00102)\n", - "177 POINT (-117.23219 48.54382)\n", - "208 POINT (-84.50352 36.43523)\n", - "300 POINT (-91.85079 43.29299)\n", - "Name: int_point_geom, dtype: geometry" - ] - }, - "execution_count": 5, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "five_geo_points = point_geom_series.peek(n = 5)\n", - "five_geo_points" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### 2. Convert the five geo points to `GeoSeries`" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "0 POINT (-83.91172 42.60253)\n", - "1 POINT (-90.13369 43.00102)\n", - "2 POINT (-117.23219 48.54382)\n", - "3 POINT (-84.50352 36.43523)\n", - "4 POINT (-91.85079 43.29299)\n", - "dtype: geometry" - ] - }, - "execution_count": 6, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "geo_points = bigframes.geopandas.GeoSeries(\n", - " [point for point in five_geo_points]\n", - ")\n", - "geo_points" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### 3. Retrieve the x (longitude) and y (latitude) from the GeoSeries with `.x` and `.y`." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### Note: TypeError is raised if `.x` and `.y` are used with a geometry type other than `Point`." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### `.x`" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "0 -83.911718\n", - "1 -90.133691\n", - "2 -117.232191\n", - "3 -84.50352\n", - "4 -91.850788\n", - "dtype: Float64" - ] - }, - "execution_count": 7, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "geo_points.x" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### `.y`" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "0 42.602532\n", - "1 43.001021\n", - "2 48.543825\n", - "3 36.435234\n", - "4 43.292989\n", - "dtype: Float64" - ] - }, - "execution_count": 8, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "geo_points.y" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### 4. Alternatively, use the `.geo` accessor to access GeoSeries methods from a `bigframes.pandas.Series` object." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### `geo.x`" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "0 -101.298265\n", - "1 -99.111085\n", - "2 -66.58687\n", - "3 -102.601791\n", - "4 -71.578625\n", - "5 -88.961529\n", - "6 -87.492986\n", - "7 -82.422666\n", - "8 -100.208166\n", - "9 -85.815939\n", - "10 -101.681133\n", - "11 -119.516659\n", - "12 -89.398306\n", - "13 -107.78848\n", - "14 -91.159306\n", - "15 -113.887042\n", - "16 -83.470416\n", - "17 -98.520146\n", - "18 -83.911718\n", - "19 -87.321865\n", - "20 -91.727626\n", - "21 -93.466093\n", - "22 -101.143324\n", - "23 -78.657634\n", - "24 -94.272323\n", - "dtype: Float64" - ] - }, - "execution_count": 9, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "point_geom_series.geo.x" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### `geo.y`" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "0 46.710819\n", - "1 29.353661\n", - "2 18.211152\n", - "3 38.835646\n", - "4 41.869768\n", - "5 39.860237\n", - "6 36.892059\n", - "7 38.143642\n", - "8 34.524623\n", - "9 30.862007\n", - "10 40.180165\n", - "11 46.228125\n", - "12 36.054196\n", - "13 38.154731\n", - "14 38.761902\n", - "15 44.928506\n", - "16 30.447232\n", - "17 29.448671\n", - "18 42.602532\n", - "19 34.529776\n", - "20 33.957675\n", - "21 42.037538\n", - "22 29.875285\n", - "23 36.299884\n", - "24 44.821657\n", - "dtype: Float64" - ] - }, - "execution_count": 10, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "point_geom_series.geo.y" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Retrive the `area` of different geometry shapes. " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### 1. Create a geometry collection from local data with `Peek`" - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "304 POLYGON ((-88.69875 38.56219, -88.69876 38.562...\n", - "288 POLYGON ((-100.55792 46.24588, -100.5579 46.24...\n", - "42 POLYGON ((-98.09779 30.49744, -98.0978 30.4971...\n", - "775 POLYGON ((-90.33573 41.67043, -90.33592 41.669...\n", - "83 POLYGON ((-85.98402 35.6552, -85.98402 35.6551...\n", - "Name: county_geom, dtype: geometry" - ] - }, - "execution_count": 11, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "geom_series = df[\"county_geom\"].peek(n = 5)\n", - "geom_series" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### 2. Convert the geometry collection to `GeoSeries`" - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "0 POLYGON ((-88.69875 38.56219, -88.69876 38.562...\n", - "1 POLYGON ((-100.55792 46.24588, -100.5579 46.24...\n", - "2 POLYGON ((-98.09779 30.49744, -98.0978 30.4971...\n", - "3 POLYGON ((-90.33573 41.67043, -90.33592 41.669...\n", - "4 POLYGON ((-85.98402 35.6552, -85.98402 35.6551...\n", - "dtype: geometry" - ] - }, - "execution_count": 12, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "five_geom = bigframes.geopandas.GeoSeries(\n", - " [point for point in geom_series]\n", - ")\n", - "five_geom" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "tags": [ - "raises-exception" - ] - }, - "source": [ - "## Note: `GeoSeries.area` raises NotImplementedError. " - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "metadata": { - "tags": [ - "raises-exception" - ] - }, - "outputs": [ - { - "ename": "NotImplementedError", - "evalue": "GeoSeries.area is not supported. Use bigframes.bigquery.st_area(series), instead. Share your usecase with the BigQuery DataFrames team at the https://bit.ly/bigframes-feedback survey. You are currently running BigFrames version 1.41.0.", - "output_type": "error", - "traceback": [ - "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[0;31mNotImplementedError\u001b[0m Traceback (most recent call last)", - "Cell \u001b[0;32mIn[13], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m \u001b[43mfive_geom\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43marea\u001b[49m\n", - "File \u001b[0;32m~/src1/python-bigquery-dataframes/bigframes/geopandas/geoseries.py:67\u001b[0m, in \u001b[0;36mGeoSeries.area\u001b[0;34m(self, crs)\u001b[0m\n\u001b[1;32m 48\u001b[0m \u001b[38;5;129m@property\u001b[39m\n\u001b[1;32m 49\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[38;5;21marea\u001b[39m(\u001b[38;5;28mself\u001b[39m, crs\u001b[38;5;241m=\u001b[39m\u001b[38;5;28;01mNone\u001b[39;00m) \u001b[38;5;241m-\u001b[39m\u001b[38;5;241m>\u001b[39m bigframes\u001b[38;5;241m.\u001b[39mseries\u001b[38;5;241m.\u001b[39mSeries: \u001b[38;5;66;03m# type: ignore\u001b[39;00m\n\u001b[1;32m 50\u001b[0m \u001b[38;5;250m \u001b[39m\u001b[38;5;124;03m\"\"\"Returns a Series containing the area of each geometry in the GeoSeries\u001b[39;00m\n\u001b[1;32m 51\u001b[0m \u001b[38;5;124;03m expressed in the units of the CRS.\u001b[39;00m\n\u001b[1;32m 52\u001b[0m \n\u001b[0;32m (...)\u001b[0m\n\u001b[1;32m 65\u001b[0m \u001b[38;5;124;03m GeoSeries.area is not supported. Use bigframes.bigquery.st_area(series), instead.\u001b[39;00m\n\u001b[1;32m 66\u001b[0m \u001b[38;5;124;03m \"\"\"\u001b[39;00m\n\u001b[0;32m---> 67\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mNotImplementedError\u001b[39;00m(\n\u001b[1;32m 68\u001b[0m \u001b[38;5;124mf\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mGeoSeries.area is not supported. Use bigframes.bigquery.st_area(series), instead. \u001b[39m\u001b[38;5;132;01m{\u001b[39;00mconstants\u001b[38;5;241m.\u001b[39mFEEDBACK_LINK\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m\"\u001b[39m\n\u001b[1;32m 69\u001b[0m )\n", - "\u001b[0;31mNotImplementedError\u001b[0m: GeoSeries.area is not supported. Use bigframes.bigquery.st_area(series), instead. Share your usecase with the BigQuery DataFrames team at the https://bit.ly/bigframes-feedback survey. You are currently running BigFrames version 1.41.0." - ] - } - ], - "source": [ - "five_geom.area" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### 3. Use `bigframes.bigquery.st_area` to retrieve the `area` in square meters instead. See: https://cloud.google.com/bigquery/docs/reference/standard-sql/geography_functions#st_area" - ] - }, - { - "cell_type": "code", - "execution_count": 14, - "metadata": {}, - "outputs": [], - "source": [ - "import bigframes.bigquery as bbq" - ] - }, - { - "cell_type": "code", - "execution_count": 15, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "0 1851741847.416806\n", - "1 4018075889.856168\n", - "2 2652483302.084653\n", - "3 1167209931.07698\n", - "4 1124055521.2818\n", - "dtype: Float64" - ] - }, - "execution_count": 15, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "geom_area = bbq.st_area(five_geom)\n", - "geom_area" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Use `GeoSeries.from_xy()` to create a GeoSeries of `Point` geometries. " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### 1. Reuse the `geo_points.x` and `geo_points.y` results by passing them to `.from_xy()` " - ] - }, - { - "cell_type": "code", - "execution_count": 16, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "0 POINT (-83.91172 42.60253)\n", - "1 POINT (-90.13369 43.00102)\n", - "2 POINT (-117.23219 48.54382)\n", - "3 POINT (-84.50352 36.43523)\n", - "4 POINT (-91.85079 43.29299)\n", - "dtype: geometry" - ] - }, - "execution_count": 16, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "bigframes.geopandas.GeoSeries.from_xy(geo_points.x, geo_points.y)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Use `GeoSeries.to_wkt()` to convert geo points from geometry data type to Well-Knonw Text (WKT)." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### 1. Reuse the `geo_points`" - ] - }, - { - "cell_type": "code", - "execution_count": 17, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "0 POINT(-83.9117183 42.6025316)\n", - "1 POINT(-90.1336915 43.0010208)\n", - "2 POINT(-117.2321913 48.5438247)\n", - "3 POINT(-84.50352 36.435234)\n", - "4 POINT(-91.850788 43.2929889)\n", - "dtype: string" - ] - }, - "execution_count": 17, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "geo_to_wkts = bigframes.geopandas.GeoSeries.to_wkt(geo_points)\n", - "geo_to_wkts" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Use `GeoSeries.from_wkt()` to convert geo points from Well-Knonw Text (WKT) to geometry data type." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### 1. Reuse `geo_to_wkts` results from `GeoSeries.to_wkts`" - ] - }, - { - "cell_type": "code", - "execution_count": 18, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "0 POINT (-83.91172 42.60253)\n", - "1 POINT (-90.13369 43.00102)\n", - "2 POINT (-117.23219 48.54382)\n", - "3 POINT (-84.50352 36.43523)\n", - "4 POINT (-91.85079 43.29299)\n", - "dtype: geometry" - ] - }, - "execution_count": 18, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "wkts_from_geo = bigframes.geopandas.GeoSeries.from_wkt(geo_to_wkts)\n", - "wkts_from_geo" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Discover the set-theoretic boundary of geometry objects with `GeoSeries.boundary`" - ] - }, - { - "cell_type": "code", - "execution_count": 19, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "0 POLYGON ((0 0, 1 1, 0 1, 0 0))\n", - "1 POLYGON ((10 0, 10 5, 0 0, 10 0))\n", - "2 POLYGON ((0 0, 2 2, 2 0, 0 0))\n", - "3 LINESTRING (0 0, 1 1, 0 1)\n", - "4 POINT (0 1)\n", - "dtype: geometry" - ] - }, - "execution_count": 19, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "from shapely.geometry import Polygon, LineString, Point\n", - "geom_obj = bigframes.geopandas.GeoSeries(\n", - " [\n", - " Polygon([(0, 0), (1, 1), (0, 1)]),\n", - " Polygon([(10, 0), (10, 5), (0, 0)]),\n", - " Polygon([(0, 0), (2, 2), (2, 0)]),\n", - " LineString([(0, 0), (1, 1), (0, 1)]),\n", - " Point(0, 1),\n", - " ]\n", - ")\n", - "geom_obj" - ] - }, - { - "cell_type": "code", - "execution_count": 20, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "0 LINESTRING (0 0, 1 1, 0 1, 0 0)\n", - "1 LINESTRING (10 0, 10 5, 0 0, 10 0)\n", - "2 LINESTRING (0 0, 2 2, 2 0, 0 0)\n", - "3 MULTIPOINT (0 0, 0 1)\n", - "4 GEOMETRYCOLLECTION EMPTY\n", - "dtype: geometry" - ] - }, - "execution_count": 20, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "geom_obj.geo.boundary" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Find the `difference` between two `GeoSeries` " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Reuse `five_geom` and `geom_obj` to find the difference between the geometry objects" - ] - }, - { - "cell_type": "code", - "execution_count": 21, - "metadata": { - "tags": [ - "raises-exception" - ] - }, - "outputs": [ - { - "data": { - "text/plain": [ - "0 POLYGON ((-88.69875 38.56219, -88.69876 38.562...\n", - "1 POLYGON ((-100.55792 46.24588, -100.5579 46.24...\n", - "2 GEOMETRYCOLLECTION EMPTY\n", - "3 POLYGON ((-90.33573 41.67043, -90.33592 41.669...\n", - "4 POLYGON ((-85.98402 35.6552, -85.98402 35.6551...\n", - "dtype: geometry" - ] - }, - "execution_count": 21, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "five_geom.difference(geom_obj)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Find the difference between a `GeoSeries` and a single geometry shape." - ] - }, - { - "cell_type": "code", - "execution_count": 22, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "0 POLYGON ((-88.69875 38.56219, -88.69876 38.562...\n", - "1 None\n", - "2 None\n", - "3 None\n", - "4 None\n", - "dtype: geometry" - ] - }, - "execution_count": 22, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "five_geom.difference([Polygon([(0, 0), (10, 0), (10, 10), (0, 0)])])" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Find the difference in `GeoSeries` with the same shapes" - ] - }, - { - "cell_type": "code", - "execution_count": 23, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "0 GEOMETRYCOLLECTION EMPTY\n", - "1 GEOMETRYCOLLECTION EMPTY\n", - "2 GEOMETRYCOLLECTION EMPTY\n", - "3 GEOMETRYCOLLECTION EMPTY\n", - "4 GEOMETRYCOLLECTION EMPTY\n", - "dtype: geometry" - ] - }, - "execution_count": 23, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "five_geom.difference(five_geom)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## You can also use`BigQuery.st_difference()` to find the difference between two `GeoSeries`. See, https://cloud.google.com/bigquery/docs/reference/standard-sql/geography_functions#st_difference" - ] - }, - { - "cell_type": "code", - "execution_count": 24, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "0 POLYGON ((-88.69875 38.56219, -88.69876 38.562...\n", - "1 POLYGON ((-100.55792 46.24588, -100.5579 46.24...\n", - "2 GEOMETRYCOLLECTION EMPTY\n", - "3 POLYGON ((-90.33573 41.67043, -90.33592 41.669...\n", - "4 POLYGON ((-85.98402 35.6552, -85.98402 35.6551...\n", - "dtype: geometry" - ] - }, - "execution_count": 24, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "bbq.st_difference(five_geom, geom_obj)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Find the difference between a `GeoSeries` and a single geometry shape." - ] - }, - { - "cell_type": "code", - "execution_count": 25, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "0 POLYGON ((-88.69875 38.56219, -88.69876 38.562...\n", - "1 None\n", - "2 None\n", - "3 None\n", - "4 None\n", - "dtype: geometry" - ] - }, - "execution_count": 25, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "bbq.st_difference(five_geom, [Polygon([(0, 0), (10, 0), (10, 10), (0, 0)])])" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Find the difference in GeoSeries with the same shapes" - ] - }, - { - "cell_type": "code", - "execution_count": 26, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "0 GEOMETRYCOLLECTION EMPTY\n", - "1 GEOMETRYCOLLECTION EMPTY\n", - "2 GEOMETRYCOLLECTION EMPTY\n", - "3 GEOMETRYCOLLECTION EMPTY\n", - "4 GEOMETRYCOLLECTION EMPTY\n", - "dtype: geometry" - ] - }, - "execution_count": 26, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "bbq.st_difference(geom_obj, geom_obj)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Use `GeoSeries.intersection()` to find the intersecting points in two geometry shapes " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Reuse `wkts_from_geo` and `geom_obj`" - ] - }, - { - "cell_type": "code", - "execution_count": 27, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "0 GEOMETRYCOLLECTION EMPTY\n", - "1 GEOMETRYCOLLECTION EMPTY\n", - "2 POLYGON ((-98.09779 30.49744, -98.0978 30.4971...\n", - "3 GEOMETRYCOLLECTION EMPTY\n", - "4 GEOMETRYCOLLECTION EMPTY\n", - "dtype: geometry" - ] - }, - "execution_count": 27, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "five_geom.intersection(geom_obj)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Find the difference between a `GeoSeries` and a single geometry shape." - ] - }, - { - "cell_type": "code", - "execution_count": 28, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "0 GEOMETRYCOLLECTION EMPTY\n", - "1 None\n", - "2 None\n", - "3 None\n", - "4 None\n", - "dtype: geometry" - ] - }, - "execution_count": 28, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "five_geom.intersection([Polygon([(0, 0), (10, 0), (10, 10), (0, 0)])])" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## You can also use`BigQuery.st_intersection()` to find the intersecting points between two `GeoSeries`. See, https://cloud.google.com/bigquery/docs/reference/standard-sql/geography_functions#st_intersection" - ] - }, - { - "cell_type": "code", - "execution_count": 29, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "0 GEOMETRYCOLLECTION EMPTY\n", - "1 GEOMETRYCOLLECTION EMPTY\n", - "2 POLYGON ((-98.09779 30.49744, -98.0978 30.4971...\n", - "3 GEOMETRYCOLLECTION EMPTY\n", - "4 GEOMETRYCOLLECTION EMPTY\n", - "dtype: geometry" - ] - }, - "execution_count": 29, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "bbq.st_intersection(five_geom, geom_obj)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Find the difference between a `GeoSeries` and a single geometry shape." - ] - }, - { - "cell_type": "code", - "execution_count": 30, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "0 GEOMETRYCOLLECTION EMPTY\n", - "1 None\n", - "2 None\n", - "3 None\n", - "4 None\n", - "dtype: geometry" - ] - }, - "execution_count": 30, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "bbq.st_intersection(five_geom, [Polygon([(0, 0), (1, 0), (10, 10), (0, 0)])])" - ] - } - ], - "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.9.19" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/notebooks/getting_started/bq_dataframes_template.ipynb b/notebooks/getting_started/bq_dataframes_template.ipynb deleted file mode 100644 index 664a3a68d33..00000000000 --- a/notebooks/getting_started/bq_dataframes_template.ipynb +++ /dev/null @@ -1,1407 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 1, - "metadata": { - "id": "ur8xi4C7S06n" - }, - "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": { - "id": "JAPoU8Sm5E6e" - }, - "source": [ - "# Get started with BigQuery DataFrames\n", - "\n", - "\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", - " \"Vertex\n", - " Open in Vertex AI Workbench\n", - " \n", - " \n", - " \n", - " \"BQ\n", - " Open in BQ Studio\n", - " \n", - "
" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "24743cf4a1e1" - }, - "source": [ - "**_NOTE_**: This notebook has been tested in the following environment:\n", - "\n", - "* Python version = 3.10" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "tvgnzT1CKxrO" - }, - "source": [ - "## Overview\n", - "\n", - "BigQuery DataFrames (also known as BigFrames) provides a Pythonic DataFrame and machine learning (ML) API powered by the BigQuery engine.\n", - "\n", - "* `bigframes.pandas` provides a pandas API for analytics. Many workloads can be\n", - " migrated from pandas to bigframes by just changing a few imports.\n", - "* `bigframes.ml` provides a scikit-learn-like API for ML.\n", - "* `bigframes.ml.llm` provides API for large language models including Gemini.\n", - "\n", - "You can learn more about [BigQuery DataFrames](https://cloud.google.com/bigquery/docs/bigquery-dataframes-introduction) and its [API reference](https://cloud.google.com/python/docs/reference/bigframes/latest).\n", - "\n", - "For any issues or feedback please reach out to bigframes-feedback@google.com." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "BF1j6f9HApxa" - }, - "source": [ - "## Before you begin\n", - "\n", - "Complete the tasks in this section to set up your environment." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "Yq7zKYWelRQP" - }, - "source": [ - "### Install the python package\n", - "\n", - "You need the [bigframes](https://pypi.org/project/bigframes/) python package to be installed. If you don't have that, uncomment and run the following cell and *restart the kernel*." - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [], - "source": [ - "#%pip install --upgrade bigframes" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "WReHDGG5g0XY" - }, - "source": [ - "### Set your project id and location\n", - "\n", - "Following are some quick references:\n", - "\n", - "* Google Cloud Project: https://cloud.google.com/resource-manager/docs/creating-managing-projects.\n", - "* BigQuery Location: https://cloud.google.com/bigquery/docs/locations." - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": { - "id": "oM1iC_MfAts1" - }, - "outputs": [], - "source": [ - "PROJECT_ID = \"bigframes-dev\" # @param {type: \"string\"}\n", - "LOCATION = \"US\" # @param {type: \"string\"}" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "960505627ddf" - }, - "source": [ - "### Import library" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": { - "id": "PyQmSRbKA8r-" - }, - "outputs": [], - "source": [ - "import bigframes.pandas as bpd" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "init_aip:mbsdk,all" - }, - "source": [ - "\n", - "### Set BigQuery DataFrames options" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": { - "id": "NPPMuw2PXGeo" - }, - "outputs": [], - "source": [ - "# Note: The project option is not required in all environments.\n", - "# For example, In BigQuery Studio, the project ID is automatically detected,\n", - "# But in Google Colab it must be set by the user.\n", - "bpd.options.bigquery.project = PROJECT_ID\n", - "\n", - "# Note: The location option is not required.\n", - "# It defaults to the location of the first table or query\n", - "# passed to read_gbq(). For APIs where a location can't be\n", - "# auto-detected, the location defaults to the \"US\" location.\n", - "bpd.options.bigquery.location = LOCATION\n", - "\n", - "# Note: BigQuery DataFrames objects are by default fully ordered like Pandas.\n", - "# If ordering is not important for you, you can uncomment the following\n", - "# expression to run BigQuery DataFrames in partial ordering mode.\n", - "bpd.options.bigquery.ordering_mode = \"partial\"\n", - "\n", - "# Note: By default BigQuery DataFrames emits out BigQuery job metadata via a\n", - "# progress bar. But in this notebook let's disable the progress bar to keep the\n", - "# experience less verbose. If you would like the default behavior, please\n", - "# comment out the following expression. \n", - "bpd.options.display.progress_bar = None" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "pDfrKwMKE_dK" - }, - "source": [ - "If you want to reset the project and/or location of the created DataFrame or Series objects, reset the session by executing `bpd.close_session()`. After that, you can redo the above steps." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "9EMAqR37AfLS" - }, - "source": [ - "## Create a BigQuery DataFrames DataFrame\n", - "\n", - "You can create a BigQuery DataFrames DataFrame by reading data from any of the\n", - "following locations:\n", - "\n", - "* A local data file\n", - "* Data stored in a BigQuery table\n", - "* A data file stored in Cloud Storage\n", - "* An in-memory pandas DataFrame\n", - "\n", - "Note that the DataFrame does not copy the data to the local memory, instead\n", - "keeps the underlying data in a BigQuery table during read and analysis. That's\n", - "how it can handle really large size of data (at BigQuery Scale) independent of\n", - "the local memory.\n", - "\n", - "For simplicity, speed and cost efficiency, this tutorial uses the\n", - "[`penguins`](https://pantheon.corp.google.com/bigquery?ws=!1m5!1m4!4m3!1sbigquery-public-data!2sml_datasets!3spenguins)\n", - "table from BigQuery public data, which contains 27 KB data about a set of\n", - "penguins - species, island of residence, culmen length and depth, flipper length\n", - "and sex. There is a version of this data in the Cloud Storage\n", - "[cloud samples data](https://pantheon.corp.google.com/storage/browser/_details/cloud-samples-data/vertex-ai/bigframe/penguins.csv)\n", - "as well." - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": { - "id": "Vyex9BQI-BNa" - }, - "outputs": [], - "source": [ - "# This is how you read a BigQuery table\n", - "df = bpd.read_gbq(\"bigquery-public-data.ml_datasets.penguins\")\n", - "\n", - "# This is how you would read a csv from the Cloud Storage\n", - "#df = bpd.read_csv(\"gs://cloud-samples-data/vertex-ai/bigframe/penguins.csv\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We can use `peek` to preview a few rows (selected arbitrarily) from the dataframes:" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "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", - "
speciesislandculmen_length_mmculmen_depth_mmflipper_length_mmbody_mass_gsex
0Adelie Penguin (Pygoscelis adeliae)Dream36.618.4184.03475.0FEMALE
1Adelie Penguin (Pygoscelis adeliae)Dream39.819.1184.04650.0MALE
2Adelie Penguin (Pygoscelis adeliae)Dream40.918.9184.03900.0MALE
3Chinstrap penguin (Pygoscelis antarctica)Dream46.517.9192.03500.0FEMALE
4Adelie Penguin (Pygoscelis adeliae)Dream37.316.8192.03000.0FEMALE
\n", - "
" - ], - "text/plain": [ - " species island culmen_length_mm \\\n", - "0 Adelie Penguin (Pygoscelis adeliae) Dream 36.6 \n", - "1 Adelie Penguin (Pygoscelis adeliae) Dream 39.8 \n", - "2 Adelie Penguin (Pygoscelis adeliae) Dream 40.9 \n", - "3 Chinstrap penguin (Pygoscelis antarctica) Dream 46.5 \n", - "4 Adelie Penguin (Pygoscelis adeliae) Dream 37.3 \n", - "\n", - " culmen_depth_mm flipper_length_mm body_mass_g sex \n", - "0 18.4 184.0 3475.0 FEMALE \n", - "1 19.1 184.0 4650.0 MALE \n", - "2 18.9 184.0 3900.0 MALE \n", - "3 17.9 192.0 3500.0 FEMALE \n", - "4 16.8 192.0 3000.0 FEMALE " - ] - }, - "execution_count": 7, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "df.peek()" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "gE6CEALjDZZV" - }, - "source": [ - "We just created a DataFrame, `df`, refering to the entirety of the source table data, without downloading it to the local machine." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "rwPLjqW2Ajzh" - }, - "source": [ - "## Inspect and manipulate data in BigQuery DataFrames" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "bExmYlL_ELtV" - }, - "source": [ - "### Using pandas API\n", - "\n", - "You can use pandas API on the BigQuery DataFrames DataFrame as you normally would in Pandas, but computation happens in the BigQuery query engine instead of your local environment." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "EJIZJaNXFQzh" - }, - "source": [ - "Let's compute the mean of the `body_mass_g` series:" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": { - "id": "YKwCW7Nsavap" - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "average_body_mass: 4201.754385964914\n" - ] - } - ], - "source": [ - "average_body_mass = df[\"body_mass_g\"].mean()\n", - "print(f\"average_body_mass: {average_body_mass}\")" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "DSs1cnca-MOU" - }, - "source": [ - "Calculate the mean `body_mass_g` by `species` using the `groupby` operation:" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "metadata": { - "id": "4PyKMR61-Mjy" - }, - "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", - "
body_mass_g
species
Adelie Penguin (Pygoscelis adeliae)3700.662252
Chinstrap penguin (Pygoscelis antarctica)3733.088235
Gentoo penguin (Pygoscelis papua)5076.01626
\n", - "

3 rows × 1 columns

\n", - "
[3 rows x 1 columns in total]" - ], - "text/plain": [ - " body_mass_g\n", - "species \n", - "Adelie Penguin (Pygoscelis adeliae) 3700.662252\n", - "Chinstrap penguin (Pygoscelis antarctica) 3733.088235\n", - "Gentoo penguin (Pygoscelis papua) 5076.01626\n", - "\n", - "[3 rows x 1 columns]" - ] - }, - "execution_count": 9, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "df[[\"species\", \"body_mass_g\"]].groupby(by=df[\"species\"]).mean(numeric_only=True)" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "6sf9kZ2C9Ixe" - }, - "source": [ - "You can confirm that the calculations were run in BigQuery by clicking \"Open job\" from the previous cells' output. This takes you to the BigQuery console to view the SQL statement and job details." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Using SQL functions\n", - "\n", - "The [bigframes.bigquery module](https://cloud.google.com/python/docs/reference/bigframes/latest/bigframes.bigquery) provides many [BigQuery SQL functions](https://cloud.google.com/bigquery/docs/reference/standard-sql/functions-all) which may not have a pandas-equivalent." - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "metadata": {}, - "outputs": [], - "source": [ - "import bigframes.bigquery" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The `bigframes.bigquery.struct()` function creates a new STRUCT Series with subfields for each column in a DataFrames." - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "0 {'culmen_length_mm': 36.6, 'culmen_depth_mm': ...\n", - "1 {'culmen_length_mm': 39.8, 'culmen_depth_mm': ...\n", - "2 {'culmen_length_mm': 40.9, 'culmen_depth_mm': ...\n", - "3 {'culmen_length_mm': 46.5, 'culmen_depth_mm': ...\n", - "4 {'culmen_length_mm': 37.3, 'culmen_depth_mm': ...\n", - "dtype: struct[pyarrow]" - ] - }, - "execution_count": 11, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "lengths = bigframes.bigquery.struct(\n", - " df[[\"culmen_length_mm\", \"culmen_depth_mm\", \"flipper_length_mm\"]]\n", - ")\n", - "lengths.peek()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Use the `bigframes.bigquery.sql_scalar()` function to access arbitrary SQL syntax representing a single column expression." - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "0 18.4\n", - "1 19.1\n", - "2 18.9\n", - "3 17.9\n", - "4 16.8\n", - "dtype: Float64" - ] - }, - "execution_count": 12, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "shortest = bigframes.bigquery.sql_scalar(\n", - " \"LEAST({0}, {1}, {2})\",\n", - " columns=[df['culmen_depth_mm'], df['culmen_length_mm'], df['flipper_length_mm']],\n", - ")\n", - "shortest.peek()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Visualize data" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### First party visualizations\n", - "\n", - "BigQuery DataFrames provides a number of visualizations via the `plot` method and [accessor](https://cloud.google.com/python/docs/reference/bigframes/latest/bigframes.operations.plotting.PlotAccessor) on the DataFrame and Series objects." - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "" - ] - }, - "execution_count": 13, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "image/png": "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yMhI7r33Xi5cuEDjxo3566+/2LlzJ48//jj9+/enc+fOANx33324uLiwdu1avLy8+Pzzz+nXrx9nz56lVq1a6npXrlzJ77//Tnp6OiNHjmT27NmlTsYVMWnSJE6ePMkPP/xA7dq1WbFiBYMGDeLYsWM0atQIgLy8PN5//32WLl2KVqvlkUce4fnnn+e7774D4J133uG7775j8eLFREREMHfuXFauXMltt90GwNy5czl79iwtWrTgjTfeAMDf318NVF5++WU++OAD/P39efLJJ3n88cfZsWPHDTnG1sD17QULeOell5jz2We8NH06Dz30EA0aNGDatGmEhoby+OOPM2nSJNauXVvpY3o9JEgRQghRruzsbObOncv8+fN57LHHAGjYsCE9evSwyyxUhtlsZtGiRXh4eNCsWTNuu+02zpw5w5o1a9BqtTRp0oR33nmHzZs307lzZ7Zv387evXtJTk7GyckJgPfff5+VK1eyfPlyJkyYoK53yZIleHh4APDoo4+yadOmSgcpsbGxLF68mNjYWGrXrg3A888/z7p161i8eDFvvfUWYJmg77PPPqNhw4aAJbCxBhsA8+bNY9q0aQwfPhyA+fPns2bNGvV5Ly8vHB0dcXV1JSgoqFQ7Zs2aRe/evQF46aWXGDJkCAUFBRWaJO1qx1gDNAwI4J233mLjihW09vICs9lyzIYN44ExY9B7ezN16lS6du3Kq6++ysCBAwF47rnnGDNmTKWO599xywQpCxYsYMGCBZhMpupuihBC/G0uDjpOvjGw2rZdUadOncJgMNCvX78q235YWJgaSAAEBgai0+nsJgILDAwkOTkZgCNHjpCTk4Ovr6/devLz8+26HkquNzg4WF1HZRw7dgyTyUTjxo3tHjcYDHZtcHV1VQOUktvLzMwkKSmJTp06qc/rdDrat2+PuTgguJZWrVrZrRsgOTmZ0NDQa742LCwMd3d3FKMRxWjE38sLbcOGmJKTMefkYC4owN/Li6RLl1CMRvV1rTt0QOflBVjeA4CWLVuqzwcGBlJQUEBWVhaenp4V2o+/45YJUiZOnMjEiRPJysrCq/gACiHErUqj0VSqy6W6XG0Kf2tQYVvjUpE7PDs4ONj9rtFoynzMejLPyckhODi4zMyN7fDmq62jMnJyctDpdBw4cACdzj6gc3d3v+r2qvLmkbbrt85UbLs/itls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", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "(\n", - " df\n", - " .sort_values(\"body_mass_g\")\n", - " .reset_index(drop=True)\n", - " .plot(title=\"Numeric features\", logy=True)\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": 14, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "" - ] - }, - "execution_count": 14, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "image/png": "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GGzevLncHZKePHky0tLSFF9JSUnv3bZojMO7d0QmqmiKPt8c00NEFdEnHYGJjo7GkydPlO4fk5+fj9OnT2Pp0qU4cuQIcnJykJqaqnQU5vHjx7CwsAAAWFhYIDIyUmm/RVcpvbvN369cevz4MeRy+XsnVdPW1oa2tvZHvQ91dXUYGRkp7sejp6fHycuowigsLERmZiaePHkCIyMjqKurCx2JiKjEfVKBadu2rcoU7UOGDEHNmjURGBgIa2traGpqIiwsTHGH4bi4OCQmJipuGujp6Ym5c+fiyZMnivEUoaGhkMvliinvPT09cfDgQaXXCQ0NVbnx4H9RVJbevakgUUViZGSk+JwTEVU0n1RgDAwMULt2baVllSpVgomJiWK5n58fxo8fj8qVK0Mul2PUqFHw9PREkyZNAADt27eHq6srBg4ciKCgIKSkpGDKlCkYOXKk4gjK8OHDsXTpUkyaNAm+vr44fvw4tm/fjgMHDpTEewbw9oaClpaWMDMz443/qMLR1NTkkRciqtBK/FYCixYtgpqaGnr16oXs7Gx4e3tj+fLlivXq6urYv38/RowYAU9PT1SqVAmDBg3CrFmzFNvY29vjwIEDGDduHBYvXoxq1aphzZo18Pb2Lum4UFdX5w96IiIikZHkrQSIiIjEgPPAlNKtBIiIiIiEwAJDREREosMCQ0RERKLDAkNERESiwwJDREREosMCQ0RERKLDAkNERESiwwJDREREosMCQ0RERKLDAkNERESiwwJDREREolPiN3MkIhKrkr7vTHm/5wyRmPEIDBEREYkOj8CQIKR4h1UiIio5PAJDREREosMCQ0RERKLDAkNERESiwwJDREREosMCQ0RERKLDAkNERESiwwJDREREosMCQ0RERKLDAkNERESiwwJDREREosMCQ0RERKLDAkNERESiwwJDREREosMCQ0RERKLDAkNERESiwwJDREREosMCQ0RERKLDAkNERESiwwJDREREosMCQ0RERKLDAkNERESiwwJDREREosMCQ0RERKLDAkNERESiwwJDREREosMCQ0RERKLDAkNERESiwwJDREREosMCQ0RERKLDAkNERESiwwJDREREosMCQ0RERKLDAkNERESiwwJDREREosMCQ0RERKLDAkNERESiwwJDREREosMCQ0RERKLDAkNERESiwwJDREREosMCQ0RE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", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "means = df.groupby(\"species\").mean(numeric_only=True)\n", - "means.plot.bar()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Integration with open source visualizations\n", - "\n", - "BigQuery Dataframes is also compatible with several open source visualization packages, such as `matplotlib`." - ] - }, - { - "cell_type": "code", - "execution_count": 15, - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAyUAAAGFCAYAAADjF1xYAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjYsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvq6yFwwAAAAlwSFlzAAAPYQAAD2EBqD+naQAAaqdJREFUeJzt3XdYU2f/BvA7CXvvoaKIIOIAcY/WhQquVl9brVqr1bpn3f5srVZttY5W62hrrVrrfm2rtdZdfRUnKrgQEcEJIlN2IMnvD2pqZAgynoTcn+vi0uScPOc+IUC+ecaRqFQqFYiIiIiIiASRig5ARERERET6jUUJEREREREJxaKEiIiIiIiEYlFCRERERERCsSghIiIiIiKhWJQQEREREZFQLEqIiIiIiEgoFiVERERERCQUixIiIiIiIhKKRQkREREREQnFooSIiIiIiIRiUUJEREREREKxKCEiIiIiIqFYlBARERERkVAsSoiIiIiISCgWJUREREREJBSLEiIiIiIiEopFCRERERERCcWihIiIiIiIhGJRQkREREREQrEoISIiIiIioViUEBERERGRUCxKiIiIiIhIKBYlREREREQkFIsSIiIiIiISikUJEREREREJxaKEiIiIiIiEYlFCRERCzZs3D40bNy7x/jExMZBIJAgNDQUAnDhxAhKJBCkpKRWST9t06NABkydPLnM7iYmJcHJyQkxMTJnb0iUSiQS///47gIKvpcrwOsd8+Xvu7u6Ob775plxztWrVCnv27CnXNolKg0UJERGVq7Nnz0Imk6FHjx6Vcrw2bdogNjYW1tbWr93Gpk2bIJFIIJFIIJVKUaNGDXz44YeIj48vx6Tl49dff8WCBQvK3M6iRYvw9ttvw93dHcC/b5aff9nb26Nr1664cuVKmY+lrdzc3BAbG4uGDRuKjlIqFy9exMiRI8u1zU8++QSzZs2CUqks13aJSopFCRERlasNGzZgwoQJ+N///ofHjx9X+PGMjIzg4uICiURSpnasrKwQGxuLhw8fYv369fjrr78wePDgckpZfuzs7GBpaVmmNjIzM7FhwwYMHz68wLajR48iNjYWhw4dQnp6Orp161Zle6FkMhlcXFxgYGAgOkqpODo6wszMrFzb7NatG9LS0vDXX3+Va7tEJcWihIiIyk16ejp27tyJMWPGoEePHti0aVOBfRYv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EVUlSkV+oaJS5hcfUqnoRERERK/EooSIiIiIiITiR2hERERERCQUixIiIiIiIhKKRQkREREREQnFooSIiIiIiIRiUUJEREREREKxKCEiIiIiIqFYlBARERERkVAsSoiIiIiISCgWJUREREREJBSLEiIiIiIiEopFCRERERERCcWihIiIiIiIhGJRQkREREREQrEoISIiIiIioViUEBERERGRUCxKiIiIiIhIKBYlREREREQkFIsSIiIiIiISikUJEREREREJxaKEiIiIiIiEYlFCRERERERCsSghIiIiIiKhWJQQEREREZFQLEqIiIiIiEgoFiVERERERCQUixIiIiIiIhKKRQkREREREQnFooSIiIiIiIRiUUJEREREREKxKCEiIiIiIqFYlBARERERkVAsSoiIiIiISCgWJUREREREJBSLEiIiIiIiEopFCRERERERCcWihIiIiIiIhGJRQkREREREQrEoISIiIiIioViUEBERERGRUCxKiIiIiIhIKBYlREREREQkFIsSIiIiIiISikUJEREREREJxaKEiIiIiIiEYlFCRERERERC/T+jLVh8VAgPvQAAAABJRU5ErkJggg==", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "import matplotlib.pyplot as plt\n", - "\n", - "# plotting a histogram\n", - "species_counts = df[\"species\"].value_counts()\n", - "plt.pie(species_counts, labels=species_counts.index, autopct='%1.1f%%')\n", - "plt.show()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Pandas interoperability\n", - "\n", - "BigQuery DataFrames can be converted from and to Pandas DataFrame with `to_pandas` and `read_pandas` respectively.\n", - "This could be handy to take advantage of the capabilities of the two systems.\n", - "\n", - "> Note: `to_pandas` converts the BigQuery DataFrame to Pandas DataFrame by bringing all the data in memory, which would be an issue\n", - "for large data, as your machine may not have enough memory to accommodate that." - ] - }, - { - "cell_type": "code", - "execution_count": 16, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\n", - "We have a dataframe of \n", - "\n", - "\n", - "We have a dataframe of \n", - "\n", - "\n", - "We have a dataframe of \n", - "\n" - ] - } - ], - "source": [ - "def print_type(df):\n", - " print(f\"\\nWe have a dataframe of {type(df)}\\n\")\n", - "\n", - "# The original bigframes dataframe\n", - "cur_df = df\n", - "print_type(cur_df)\n", - "\n", - "# Convert to pandas dataframe\n", - "cur_df = cur_df.to_pandas()\n", - "print_type(cur_df)\n", - "\n", - "# Convert back to bigframes dataframe\n", - "cur_df = bpd.read_pandas(cur_df)\n", - "print_type(cur_df)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Machine Learning with BigQuery DataFrames" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Clean and prepare data\n", - "\n", - "We're are going to start with supervised learning, where a Linear Regression model will learn to predict the body mass (output variable `y`) using input features such as flipper length, sex, species, and more (features `X`)." - ] - }, - { - "cell_type": "code", - "execution_count": 17, - "metadata": {}, - "outputs": [], - "source": [ - "# Drop any rows that has missing (NA) values\n", - "df = df.dropna()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Part of preparing data for a machine learning task is splitting it into subsets for training and testing to ensure that the solution is not overfitting. By default, BQML will automatically manage splitting the data for you. However, BQML also supports manually splitting out your training data.\n", - "\n", - "Performing a manual data split can be done with `bigframes.ml.model_selection.train_test_split` like so:" - ] - }, - { - "cell_type": "code", - "execution_count": 18, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\n", - " df_train shape: (267, 7)\n", - " df_test shape: (67, 7)\n", - "\n" - ] - } - ], - "source": [ - "from bigframes.ml.model_selection import train_test_split\n", - "\n", - "\n", - "# This will split df into test and training sets, with 20% of the rows in the test set,\n", - "# and the rest in the training set\n", - "df_train, df_test = train_test_split(df, test_size=0.2)\n", - "\n", - "# Show the shape of the data after the split\n", - "print(f\"\"\"\n", - " df_train shape: {df_train.shape}\n", - " df_test shape: {df_test.shape}\n", - "\"\"\")" - ] - }, - { - "cell_type": "code", - "execution_count": 19, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\n", - " X_train shape: (267, 6)\n", - " X_test shape: (67, 7)\n", - " y_train shape: (267, 1)\n", - " y_test shape: (67, 1)\n", - "\n" - ] - } - ], - "source": [ - "# Isolate input features and output variable into DataFrames\n", - "X_train = df_train[[\n", - " 'island',\n", - " 'culmen_length_mm',\n", - " 'culmen_depth_mm',\n", - " 'flipper_length_mm',\n", - " 'sex',\n", - " 'species',\n", - "]]\n", - "y_train = df_train[['body_mass_g']]\n", - "\n", - "X_test = df_test[[\n", - " 'island',\n", - " 'culmen_length_mm',\n", - " 'culmen_depth_mm',\n", - " 'flipper_length_mm',\n", - " 'sex',\n", - " 'species',\n", - " # Include the actual body_mass_g so that we can compare with the predicted\n", - " # without a join.\n", - " 'body_mass_g'\n", - "]]\n", - "y_test = df_test[['body_mass_g']]\n", - "\n", - "# Print the shapes of features and label\n", - "print(f\"\"\"\n", - " X_train shape: {X_train.shape}\n", - " X_test shape: {X_test.shape}\n", - " y_train shape: {y_train.shape}\n", - " y_test shape: {y_test.shape}\n", - "\"\"\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Define pipeline\n", - "\n", - "This step is subjective to the problem. Although a model can be directly trained on the original data, it is often useful to apply some preprocessing to the original data.\n", - "In this example we want to apply a [`ColumnTransformer`](https://cloud.google.com/python/docs/reference/bigframes/latest/bigframes.ml.compose.ColumnTransformer) in which we apply [`OneHotEncoder`](https://cloud.google.com/python/docs/reference/bigframes/latest/bigframes.ml.preprocessing.OneHotEncoder) to the category features and [`StandardScaler`](https://cloud.google.com/python/docs/reference/bigframes/latest/bigframes.ml.preprocessing.StandardScaler) to the numeric features." - ] - }, - { - "cell_type": "code", - "execution_count": 20, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "Pipeline(steps=[('preproc',\n", - " ColumnTransformer(transformers=[('onehot', OneHotEncoder(),\n", - " ['island', 'species', 'sex']),\n", - " ('scaler', StandardScaler(),\n", - " ['culmen_depth_mm',\n", - " 'culmen_length_mm',\n", - " 'flipper_length_mm'])])),\n", - " ('linreg', LinearRegression(fit_intercept=False))])" - ] - }, - "execution_count": 20, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "from bigframes.ml.linear_model import LinearRegression\n", - "from bigframes.ml.pipeline import Pipeline\n", - "from bigframes.ml.compose import ColumnTransformer\n", - "from bigframes.ml.preprocessing import StandardScaler, OneHotEncoder\n", - "\n", - "preprocessing = ColumnTransformer([\n", - " (\"onehot\", OneHotEncoder(), [\"island\", \"species\", \"sex\"]),\n", - " (\"scaler\", StandardScaler(), [\"culmen_depth_mm\", \"culmen_length_mm\", \"flipper_length_mm\"]),\n", - "])\n", - "\n", - "model = LinearRegression(fit_intercept=False)\n", - "\n", - "pipeline = Pipeline([\n", - " ('preproc', preprocessing),\n", - " ('linreg', model)\n", - "])\n", - "\n", - "# View the pipeline\n", - "pipeline" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Train and Predict\n", - "\n", - "Supervised learning is when we train a model on input-output pairs, and then ask it to predict the output for new inputs. An example of such a predictor is `bigframes.ml.linear_models.LinearRegression`." - ] - }, - { - "cell_type": "code", - "execution_count": 21, - "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", - "
predicted_body_mass_gislandculmen_length_mmculmen_depth_mmflipper_length_mmsexspeciesbody_mass_g
03271.548077Biscoe37.918.6172.0FEMALEAdelie Penguin (Pygoscelis adeliae)3150.0
13224.661209Biscoe37.716.0183.0FEMALEAdelie Penguin (Pygoscelis adeliae)3075.0
23395.403541Biscoe34.518.1187.0FEMALEAdelie Penguin (Pygoscelis adeliae)2900.0
33943.436439Biscoe40.118.9188.0MALEAdelie Penguin (Pygoscelis adeliae)4300.0
43986.662895Biscoe41.418.6191.0MALEAdelie Penguin (Pygoscelis adeliae)3700.0
\n", - "
" - ], - "text/plain": [ - " predicted_body_mass_g island culmen_length_mm culmen_depth_mm \\\n", - "0 3271.548077 Biscoe 37.9 18.6 \n", - "1 3224.661209 Biscoe 37.7 16.0 \n", - "2 3395.403541 Biscoe 34.5 18.1 \n", - "3 3943.436439 Biscoe 40.1 18.9 \n", - "4 3986.662895 Biscoe 41.4 18.6 \n", - "\n", - " flipper_length_mm sex species body_mass_g \n", - "0 172.0 FEMALE Adelie Penguin (Pygoscelis adeliae) 3150.0 \n", - "1 183.0 FEMALE Adelie Penguin (Pygoscelis adeliae) 3075.0 \n", - "2 187.0 FEMALE Adelie Penguin (Pygoscelis adeliae) 2900.0 \n", - "3 188.0 MALE Adelie Penguin (Pygoscelis adeliae) 4300.0 \n", - "4 191.0 MALE Adelie Penguin (Pygoscelis adeliae) 3700.0 " - ] - }, - "execution_count": 21, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# Learn from the training data how to predict output y\n", - "pipeline.fit(X_train, y_train)\n", - "\n", - "# Predict y for the test data\n", - "y_pred = pipeline.predict(X_test)\n", - "\n", - "# View predictions preview\n", - "y_pred.peek()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Evaluate results\n", - "\n", - "Some models include a convenient `.score(X, y)` method for evaulation with a preset accuracy metric:" - ] - }, - { - "cell_type": "code", - "execution_count": 22, - "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", - "
mean_absolute_errormean_squared_errormean_squared_log_errormedian_absolute_errorr2_scoreexplained_variance
0231.91425278873.6004210.005172178.7249850.8905490.890566
\n", - "

1 rows × 6 columns

\n", - "
[1 rows x 6 columns in total]" - ], - "text/plain": [ - " mean_absolute_error mean_squared_error mean_squared_log_error \\\n", - " 231.914252 78873.600421 0.005172 \n", - "\n", - " median_absolute_error r2_score explained_variance \n", - " 178.724985 0.890549 0.890566 \n", - "\n", - "[1 rows x 6 columns]" - ] - }, - "execution_count": 22, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "pipeline.score(X_test.drop(columns=[\"body_mass_g\"]), y_test)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "For a more general approach, the library `bigframes.ml.metrics` is provided:" - ] - }, - { - "cell_type": "code", - "execution_count": 23, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "np.float64(0.8905492944632485)" - ] - }, - "execution_count": 23, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "from bigframes.ml.metrics import r2_score\n", - "\n", - "r2_score(y_pred['body_mass_g'], y_pred[\"predicted_body_mass_g\"])" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Generative AI with BigQuery DataFrames\n", - "\n", - "BigQuery DataFrames integration with the Large Language Models (LLM) supported by BigQuery ML. Check out the [`bigframes.ml.llm`](https://cloud.google.com/python/docs/reference/bigframes/latest/bigframes.ml.llm) module for all the available models.\n", - "\n", - "To use this feature you would need to have a few additional APIs enabled and IAM roles configured. Please make sure of that by following [this documentation](https://cloud.google.com/bigquery/docs/use-bigquery-dataframes#remote-models) and then uncomment the code in the following cells to try out the integration with Gemini." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Create prompts\n", - "\n", - "A \"prompt\" text column can be initialized either directly or via the pandas APIs. For simplicity let's use a direct initialization here." - ] - }, - { - "cell_type": "code", - "execution_count": 24, - "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", - "
prompt
0What is BigQuery?
1What is BQML?
2What is BigQuery DataFrames?
\n", - "

3 rows × 1 columns

\n", - "
[3 rows x 1 columns in total]" - ], - "text/plain": [ - " prompt\n", - "0 What is BigQuery?\n", - "1 What is BQML?\n", - "2 What is BigQuery DataFrames?\n", - "\n", - "[3 rows x 1 columns]" - ] - }, - "execution_count": 24, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "df = bpd.DataFrame(\n", - " {\n", - " \"prompt\": [\"What is BigQuery?\", \"What is BQML?\", \"What is BigQuery DataFrames?\"],\n", - " })\n", - "df" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Generate responses\n", - "\n", - "Here we will use the [`GeminiTextGenerator`](https://cloud.google.com/python/docs/reference/bigframes/latest/bigframes.ml.llm.GeminiTextGenerator) LLM to answer the questions. Read the [GeminiTextGenerator API documentation](https://cloud.google.com/python/docs/reference/bigframes/latest/bigframes.ml.llm.GeminiTextGenerator) for all the model versions supported via the `model_name` param." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# from bigframes.ml.llm import GeminiTextGenerator\n", - "\n", - "# model = GeminiTextGenerator(model_name=\"gemini-2.5-flash\")\n", - "\n", - "# pred = model.predict(df)\n", - "# pred" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Let's print the full text response for the question \"What is BigQuery DataFrames?\"." - ] - }, - { - "cell_type": "code", - "execution_count": 26, - "metadata": {}, - "outputs": [], - "source": [ - "# import IPython.display\n", - "\n", - "# IPython.display.Markdown(pred.loc[2][\"ml_generate_text_llm_result\"])" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "TpV-iwP9qw9c" - }, - "source": [ - "## Cleaning up\n", - "\n", - "To clean up all Google Cloud resources used in this project, you can [delete the Google Cloud\n", - "project](https://cloud.google.com/resource-manager/docs/creating-managing-projects#shutting_down_projects) you used for the tutorial.\n", - "\n", - "To remove any temporary cloud artifacts (inclusing BQ tables) created in the current BigQuery DataFrames session, simply call `close_session`." - ] - }, - { - "cell_type": "code", - "execution_count": 27, - "metadata": {}, - "outputs": [], - "source": [ - "# Delete the temporary cloud artifacts created during the bigframes session \n", - "bpd.close_session()" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "wCsmt0IwFkDy" - }, - "source": [ - "## Summary and next steps\n", - "\n", - "1. You created BigQuery DataFrames objects, and inspected and manipulated data with pandas APIs at BigQuery scale and speed.\n", - "\n", - "1. You also created ML model from a DataFrame and used them to run predictions on another DataFrame.\n", - "\n", - "1. You got access to Google's state-of-the-art Gemini LLM through simple pythonic API.\n", - "\n", - "Learn more about BigQuery DataFrames in the documentation [BigQuery DataFrames](https://cloud.google.com/bigquery/docs/bigquery-dataframes-introduction) and its [API reference](https://cloud.google.com/python/docs/reference/bigframes/latest).\n", - "\n", - "Also, find more sample notebooks in the [GitHub repo](https://github.com/googleapis/python-bigquery-dataframes/tree/main/notebooks), including the [pypi.ipynb](https://github.com/googleapis/python-bigquery-dataframes/blob/main/notebooks/dataframes/pypi.ipynb) that processes 400+ TB data at the cost and efficiency close to direct SQL by taking advantage of the [partial ordering](https://cloud.google.com/python/docs/reference/bigframes/latest/bigframes._config.bigquery_options.BigQueryOptions#bigframes__config_bigquery_options_BigQueryOptions_ordering_mode) mode." - ] - } - ], - "metadata": { - "colab": { - "provenance": [], - "toc_visible": true - }, - "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.16" - } - }, - "nbformat": 4, - "nbformat_minor": 0 -} diff --git a/notebooks/getting_started/getting_started_bq_dataframes.ipynb b/notebooks/getting_started/getting_started_bq_dataframes.ipynb index f9fb950c534..6cc6acc9935 100644 --- a/notebooks/getting_started/getting_started_bq_dataframes.ipynb +++ b/notebooks/getting_started/getting_started_bq_dataframes.ipynb @@ -2,7 +2,7 @@ "cells": [ { "cell_type": "code", - "execution_count": 1, + "execution_count": null, "metadata": { "id": "ur8xi4C7S06n" }, @@ -29,18 +29,18 @@ "id": "JAPoU8Sm5E6e" }, "source": [ - "# BigQuery DataFrames Quickstart Guide\n", + "# Get started with BigQuery DataFrames\n", "\n", "\n", "\n", " \n", " \n", @@ -49,13 +49,7 @@ " \"Vertex\n", " Open in Vertex AI Workbench\n", " \n", - " \n", - " \n", + " \n", "
\n", " \n", - " \"Colab Run in Colab\n", + " \"Colab Run in Colab\n", " \n", " \n", " \n", - " \"GitHub\n", + " \"GitHub\n", " View on GitHub\n", " \n", " \n", - " \n", - " \"BQ\n", - " Open in BQ Studio\n", - " \n", - "
" ] }, @@ -67,7 +61,7 @@ "source": [ "**_NOTE_**: This notebook has been tested in the following environment:\n", "\n", - "* Python version = 3.12" + "* Python version = 3.10" ] }, { @@ -78,17 +72,31 @@ "source": [ "## Overview\n", "\n", - "In this guide, you learn how to install BigQuery DataFrames, load data into a BigQuery DataFrames DataFrame, and inspect and manipulate the data using pandas and a custom Python function, running at BigQuery scale.\n", + "Use this notebook to get started with BigQuery DataFrames, including setup, installation, and basic tutorials.\n", + "\n", + "BigQuery DataFrames provides a Pythonic DataFrame and machine learning (ML) API powered by the BigQuery engine.\n", + "\n", + "* `bigframes.pandas` provides a pandas-like API for analytics.\n", + "* `bigframes.ml` provides a scikit-learn-like API for ML.\n", + "\n", + "Learn more about [BigQuery DataFrames](https://cloud.google.com/python/docs/reference/bigframes/latest)." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "d975e698c9a4" + }, + "source": [ + "### Objective\n", + "\n", + "In this tutorial, you learn how to install BigQuery DataFrames, load data into a BigQuery DataFrames DataFrame, and inspect and manipulate the data using pandas and a custom Python function, running at BigQuery scale.\n", "\n", "The steps include:\n", "\n", - "- Installing the BigQuery Dataframes package.\n", - "- Setting up the environment.\n", "- Creating a BigQuery DataFrames DataFrame: Access data from a local CSV to create a BigQuery DataFrames DataFrame.\n", "- Inspecting and manipulating data: Use pandas to perform data cleaning and preparation on the DataFrame.\n", - "- Deploying a custom function: Deploy a [remote function ](https://cloud.google.com/bigquery/docs/remote-functions)that runs a scalar Python function at BigQuery scale.\n", - "\n", - "You can learn more about [BigQuery DataFrames](https://cloud.google.com/python/docs/reference/bigframes/latest)." + "- Deploying a custom function: Deploy a [remote function ](https://cloud.google.com/bigquery/docs/remote-functions)that runs a scalar Python function at BigQuery scale." ] }, { @@ -137,112 +145,11 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": null, "metadata": { "id": "mfPoOwPLGpSr" }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Requirement already satisfied: bigframes in /usr/local/google/home/swast/src/github.com/googleapis/python-bigquery-dataframes-2/venv/lib/python3.10/site-packages (2.17.0)\n", - "Requirement already satisfied: cloudpickle>=2.0.0 in /usr/local/google/home/swast/src/github.com/googleapis/python-bigquery-dataframes-2/venv/lib/python3.10/site-packages (from bigframes) (3.1.1)\n", - "Requirement already satisfied: fsspec>=2023.3.0 in /usr/local/google/home/swast/src/github.com/googleapis/python-bigquery-dataframes-2/venv/lib/python3.10/site-packages (from bigframes) (2025.9.0)\n", - "Requirement already satisfied: gcsfs!=2025.5.0,>=2023.3.0 in /usr/local/google/home/swast/src/github.com/googleapis/python-bigquery-dataframes-2/venv/lib/python3.10/site-packages (from bigframes) (2025.9.0)\n", - "Requirement already satisfied: geopandas>=0.12.2 in /usr/local/google/home/swast/src/github.com/googleapis/python-bigquery-dataframes-2/venv/lib/python3.10/site-packages (from bigframes) (1.1.1)\n", - "Requirement already satisfied: google-auth<3.0,>=2.15.0 in /usr/local/google/home/swast/src/github.com/googleapis/python-bigquery-dataframes-2/venv/lib/python3.10/site-packages (from bigframes) (2.40.3)\n", - "Requirement already satisfied: google-cloud-bigquery>=3.36.0 in /usr/local/google/home/swast/src/github.com/googleapis/python-bigquery-dataframes-2/venv/lib/python3.10/site-packages (from google-cloud-bigquery[bqstorage,pandas]>=3.36.0->bigframes) (3.36.0)\n", - "Requirement already satisfied: google-cloud-bigquery-storage<3.0.0,>=2.30.0 in /usr/local/google/home/swast/src/github.com/googleapis/python-bigquery-dataframes-2/venv/lib/python3.10/site-packages (from bigframes) (2.33.0)\n", - "Requirement already satisfied: google-cloud-functions>=1.12.0 in /usr/local/google/home/swast/src/github.com/googleapis/python-bigquery-dataframes-2/venv/lib/python3.10/site-packages (from bigframes) (1.20.4)\n", - "Requirement already satisfied: google-cloud-bigquery-connection>=1.12.0 in /usr/local/google/home/swast/src/github.com/googleapis/python-bigquery-dataframes-2/venv/lib/python3.10/site-packages (from bigframes) (1.18.3)\n", - "Requirement already satisfied: google-cloud-resource-manager>=1.10.3 in /usr/local/google/home/swast/src/github.com/googleapis/python-bigquery-dataframes-2/venv/lib/python3.10/site-packages (from bigframes) (1.14.2)\n", - "Requirement already satisfied: google-cloud-storage>=2.0.0 in /usr/local/google/home/swast/src/github.com/googleapis/python-bigquery-dataframes-2/venv/lib/python3.10/site-packages (from bigframes) (3.3.1)\n", - "Requirement already satisfied: grpc-google-iam-v1>=0.14.2 in /usr/local/google/home/swast/src/github.com/googleapis/python-bigquery-dataframes-2/venv/lib/python3.10/site-packages (from bigframes) (0.14.2)\n", - "Requirement already satisfied: numpy>=1.24.0 in /usr/local/google/home/swast/src/github.com/googleapis/python-bigquery-dataframes-2/venv/lib/python3.10/site-packages (from bigframes) (2.2.6)\n", - "Requirement already satisfied: pandas>=1.5.3 in /usr/local/google/home/swast/src/github.com/googleapis/python-bigquery-dataframes-2/venv/lib/python3.10/site-packages (from bigframes) (2.3.2)\n", - "Requirement already satisfied: pandas-gbq>=0.26.1 in /usr/local/google/home/swast/src/github.com/googleapis/python-bigquery-dataframes-2/venv/lib/python3.10/site-packages (from bigframes) (0.29.2)\n", - "Requirement already satisfied: pyarrow>=15.0.2 in /usr/local/google/home/swast/src/github.com/googleapis/python-bigquery-dataframes-2/venv/lib/python3.10/site-packages (from bigframes) (21.0.0)\n", - "Requirement already satisfied: pydata-google-auth>=1.8.2 in /usr/local/google/home/swast/src/github.com/googleapis/python-bigquery-dataframes-2/venv/lib/python3.10/site-packages (from bigframes) (1.9.1)\n", - "Requirement already satisfied: requests>=2.27.1 in /usr/local/google/home/swast/src/github.com/googleapis/python-bigquery-dataframes-2/venv/lib/python3.10/site-packages (from bigframes) (2.32.5)\n", - "Requirement already satisfied: shapely>=1.8.5 in /usr/local/google/home/swast/src/github.com/googleapis/python-bigquery-dataframes-2/venv/lib/python3.10/site-packages (from bigframes) (2.1.1)\n", - "Requirement already satisfied: sqlglot>=23.6.3 in /usr/local/google/home/swast/src/github.com/googleapis/python-bigquery-dataframes-2/venv/lib/python3.10/site-packages (from bigframes) (27.11.0)\n", - "Requirement already satisfied: tabulate>=0.9 in /usr/local/google/home/swast/src/github.com/googleapis/python-bigquery-dataframes-2/venv/lib/python3.10/site-packages (from bigframes) (0.9.0)\n", - "Requirement already satisfied: ipywidgets>=7.7.1 in /usr/local/google/home/swast/src/github.com/googleapis/python-bigquery-dataframes-2/venv/lib/python3.10/site-packages (from bigframes) (8.1.7)\n", - "Requirement already satisfied: humanize>=4.6.0 in /usr/local/google/home/swast/src/github.com/googleapis/python-bigquery-dataframes-2/venv/lib/python3.10/site-packages (from bigframes) (4.13.0)\n", - "Requirement already satisfied: matplotlib>=3.7.1 in /usr/local/google/home/swast/src/github.com/googleapis/python-bigquery-dataframes-2/venv/lib/python3.10/site-packages (from bigframes) (3.10.6)\n", - "Requirement already satisfied: db-dtypes>=1.4.2 in /usr/local/google/home/swast/src/github.com/googleapis/python-bigquery-dataframes-2/venv/lib/python3.10/site-packages (from bigframes) (1.4.3)\n", - "Requirement already satisfied: atpublic<6,>=2.3 in /usr/local/google/home/swast/src/github.com/googleapis/python-bigquery-dataframes-2/venv/lib/python3.10/site-packages (from bigframes) (5.1)\n", - "Requirement already satisfied: python-dateutil<3,>=2.8.2 in /usr/local/google/home/swast/src/github.com/googleapis/python-bigquery-dataframes-2/venv/lib/python3.10/site-packages (from bigframes) (2.9.0.post0)\n", - "Requirement already satisfied: pytz>=2022.7 in /usr/local/google/home/swast/src/github.com/googleapis/python-bigquery-dataframes-2/venv/lib/python3.10/site-packages (from bigframes) (2025.2)\n", - "Requirement already satisfied: toolz<2,>=0.11 in /usr/local/google/home/swast/src/github.com/googleapis/python-bigquery-dataframes-2/venv/lib/python3.10/site-packages (from bigframes) (1.0.0)\n", - "Requirement already satisfied: typing-extensions<5,>=4.5.0 in /usr/local/google/home/swast/src/github.com/googleapis/python-bigquery-dataframes-2/venv/lib/python3.10/site-packages (from bigframes) (4.15.0)\n", - "Requirement already satisfied: rich<14,>=12.4.4 in /usr/local/google/home/swast/src/github.com/googleapis/python-bigquery-dataframes-2/venv/lib/python3.10/site-packages (from bigframes) (13.9.4)\n", - "Requirement already satisfied: cachetools<6.0,>=2.0.0 in /usr/local/google/home/swast/src/github.com/googleapis/python-bigquery-dataframes-2/venv/lib/python3.10/site-packages (from google-auth<3.0,>=2.15.0->bigframes) (5.5.2)\n", - "Requirement already satisfied: pyasn1-modules>=0.2.1 in /usr/local/google/home/swast/src/github.com/googleapis/python-bigquery-dataframes-2/venv/lib/python3.10/site-packages (from google-auth<3.0,>=2.15.0->bigframes) (0.4.2)\n", - "Requirement already satisfied: rsa<5,>=3.1.4 in /usr/local/google/home/swast/src/github.com/googleapis/python-bigquery-dataframes-2/venv/lib/python3.10/site-packages (from google-auth<3.0,>=2.15.0->bigframes) (4.9.1)\n", - "Requirement already satisfied: google-api-core!=2.0.*,!=2.1.*,!=2.10.*,!=2.2.*,!=2.3.*,!=2.4.*,!=2.5.*,!=2.6.*,!=2.7.*,!=2.8.*,!=2.9.*,<3.0.0,>=1.34.0 in /usr/local/google/home/swast/src/github.com/googleapis/python-bigquery-dataframes-2/venv/lib/python3.10/site-packages (from google-api-core[grpc]!=2.0.*,!=2.1.*,!=2.10.*,!=2.2.*,!=2.3.*,!=2.4.*,!=2.5.*,!=2.6.*,!=2.7.*,!=2.8.*,!=2.9.*,<3.0.0,>=1.34.0->google-cloud-bigquery-storage<3.0.0,>=2.30.0->bigframes) (2.25.1)\n", - "Requirement already satisfied: proto-plus<2.0.0,>=1.22.0 in /usr/local/google/home/swast/src/github.com/googleapis/python-bigquery-dataframes-2/venv/lib/python3.10/site-packages (from google-cloud-bigquery-storage<3.0.0,>=2.30.0->bigframes) (1.26.1)\n", - "Requirement already satisfied: protobuf!=3.20.0,!=3.20.1,!=4.21.0,!=4.21.1,!=4.21.2,!=4.21.3,!=4.21.4,!=4.21.5,<7.0.0,>=3.20.2 in /usr/local/google/home/swast/src/github.com/googleapis/python-bigquery-dataframes-2/venv/lib/python3.10/site-packages (from google-cloud-bigquery-storage<3.0.0,>=2.30.0->bigframes) (6.32.0)\n", - "Requirement already satisfied: googleapis-common-protos<2.0.0,>=1.56.2 in /usr/local/google/home/swast/src/github.com/googleapis/python-bigquery-dataframes-2/venv/lib/python3.10/site-packages (from google-api-core!=2.0.*,!=2.1.*,!=2.10.*,!=2.2.*,!=2.3.*,!=2.4.*,!=2.5.*,!=2.6.*,!=2.7.*,!=2.8.*,!=2.9.*,<3.0.0,>=1.34.0->google-api-core[grpc]!=2.0.*,!=2.1.*,!=2.10.*,!=2.2.*,!=2.3.*,!=2.4.*,!=2.5.*,!=2.6.*,!=2.7.*,!=2.8.*,!=2.9.*,<3.0.0,>=1.34.0->google-cloud-bigquery-storage<3.0.0,>=2.30.0->bigframes) (1.70.0)\n", - "Requirement already satisfied: grpcio<2.0.0,>=1.33.2 in /usr/local/google/home/swast/src/github.com/googleapis/python-bigquery-dataframes-2/venv/lib/python3.10/site-packages (from google-api-core[grpc]!=2.0.*,!=2.1.*,!=2.10.*,!=2.2.*,!=2.3.*,!=2.4.*,!=2.5.*,!=2.6.*,!=2.7.*,!=2.8.*,!=2.9.*,<3.0.0,>=1.34.0->google-cloud-bigquery-storage<3.0.0,>=2.30.0->bigframes) (1.74.0)\n", - "Requirement already satisfied: grpcio-status<2.0.0,>=1.33.2 in /usr/local/google/home/swast/src/github.com/googleapis/python-bigquery-dataframes-2/venv/lib/python3.10/site-packages (from google-api-core[grpc]!=2.0.*,!=2.1.*,!=2.10.*,!=2.2.*,!=2.3.*,!=2.4.*,!=2.5.*,!=2.6.*,!=2.7.*,!=2.8.*,!=2.9.*,<3.0.0,>=1.34.0->google-cloud-bigquery-storage<3.0.0,>=2.30.0->bigframes) (1.74.0)\n", - "Requirement already satisfied: six>=1.5 in /usr/local/google/home/swast/src/github.com/googleapis/python-bigquery-dataframes-2/venv/lib/python3.10/site-packages (from python-dateutil<3,>=2.8.2->bigframes) (1.17.0)\n", - "Requirement already satisfied: charset_normalizer<4,>=2 in /usr/local/google/home/swast/src/github.com/googleapis/python-bigquery-dataframes-2/venv/lib/python3.10/site-packages (from requests>=2.27.1->bigframes) (3.4.3)\n", - "Requirement already satisfied: idna<4,>=2.5 in /usr/local/google/home/swast/src/github.com/googleapis/python-bigquery-dataframes-2/venv/lib/python3.10/site-packages (from requests>=2.27.1->bigframes) (3.10)\n", - "Requirement already satisfied: urllib3<3,>=1.21.1 in /usr/local/google/home/swast/src/github.com/googleapis/python-bigquery-dataframes-2/venv/lib/python3.10/site-packages (from requests>=2.27.1->bigframes) (2.5.0)\n", - "Requirement already satisfied: certifi>=2017.4.17 in /usr/local/google/home/swast/src/github.com/googleapis/python-bigquery-dataframes-2/venv/lib/python3.10/site-packages (from requests>=2.27.1->bigframes) (2025.8.3)\n", - "Requirement already satisfied: markdown-it-py>=2.2.0 in /usr/local/google/home/swast/src/github.com/googleapis/python-bigquery-dataframes-2/venv/lib/python3.10/site-packages (from rich<14,>=12.4.4->bigframes) (4.0.0)\n", - "Requirement already satisfied: pygments<3.0.0,>=2.13.0 in /usr/local/google/home/swast/src/github.com/googleapis/python-bigquery-dataframes-2/venv/lib/python3.10/site-packages (from rich<14,>=12.4.4->bigframes) (2.19.2)\n", - "Requirement already satisfied: pyasn1>=0.1.3 in /usr/local/google/home/swast/src/github.com/googleapis/python-bigquery-dataframes-2/venv/lib/python3.10/site-packages (from rsa<5,>=3.1.4->google-auth<3.0,>=2.15.0->bigframes) (0.6.1)\n", - "Requirement already satisfied: packaging>=24.2.0 in /usr/local/google/home/swast/src/github.com/googleapis/python-bigquery-dataframes-2/venv/lib/python3.10/site-packages (from db-dtypes>=1.4.2->bigframes) (25.0)\n", - "Requirement already satisfied: aiohttp!=4.0.0a0,!=4.0.0a1 in /usr/local/google/home/swast/src/github.com/googleapis/python-bigquery-dataframes-2/venv/lib/python3.10/site-packages (from gcsfs!=2025.5.0,>=2023.3.0->bigframes) (3.12.15)\n", - "Requirement already satisfied: decorator>4.1.2 in /usr/local/google/home/swast/src/github.com/googleapis/python-bigquery-dataframes-2/venv/lib/python3.10/site-packages (from gcsfs!=2025.5.0,>=2023.3.0->bigframes) (5.2.1)\n", - "Requirement already satisfied: google-auth-oauthlib in /usr/local/google/home/swast/src/github.com/googleapis/python-bigquery-dataframes-2/venv/lib/python3.10/site-packages (from gcsfs!=2025.5.0,>=2023.3.0->bigframes) (1.2.2)\n", - "Requirement already satisfied: aiohappyeyeballs>=2.5.0 in /usr/local/google/home/swast/src/github.com/googleapis/python-bigquery-dataframes-2/venv/lib/python3.10/site-packages (from aiohttp!=4.0.0a0,!=4.0.0a1->gcsfs!=2025.5.0,>=2023.3.0->bigframes) (2.6.1)\n", - "Requirement already satisfied: aiosignal>=1.4.0 in /usr/local/google/home/swast/src/github.com/googleapis/python-bigquery-dataframes-2/venv/lib/python3.10/site-packages (from aiohttp!=4.0.0a0,!=4.0.0a1->gcsfs!=2025.5.0,>=2023.3.0->bigframes) (1.4.0)\n", - "Requirement already satisfied: async-timeout<6.0,>=4.0 in /usr/local/google/home/swast/src/github.com/googleapis/python-bigquery-dataframes-2/venv/lib/python3.10/site-packages (from aiohttp!=4.0.0a0,!=4.0.0a1->gcsfs!=2025.5.0,>=2023.3.0->bigframes) (5.0.1)\n", - "Requirement already satisfied: attrs>=17.3.0 in /usr/local/google/home/swast/src/github.com/googleapis/python-bigquery-dataframes-2/venv/lib/python3.10/site-packages (from aiohttp!=4.0.0a0,!=4.0.0a1->gcsfs!=2025.5.0,>=2023.3.0->bigframes) (25.3.0)\n", - "Requirement already satisfied: frozenlist>=1.1.1 in /usr/local/google/home/swast/src/github.com/googleapis/python-bigquery-dataframes-2/venv/lib/python3.10/site-packages (from aiohttp!=4.0.0a0,!=4.0.0a1->gcsfs!=2025.5.0,>=2023.3.0->bigframes) (1.7.0)\n", - "Requirement already satisfied: multidict<7.0,>=4.5 in /usr/local/google/home/swast/src/github.com/googleapis/python-bigquery-dataframes-2/venv/lib/python3.10/site-packages (from aiohttp!=4.0.0a0,!=4.0.0a1->gcsfs!=2025.5.0,>=2023.3.0->bigframes) (6.6.4)\n", - "Requirement already satisfied: propcache>=0.2.0 in /usr/local/google/home/swast/src/github.com/googleapis/python-bigquery-dataframes-2/venv/lib/python3.10/site-packages (from aiohttp!=4.0.0a0,!=4.0.0a1->gcsfs!=2025.5.0,>=2023.3.0->bigframes) (0.3.2)\n", - "Requirement already satisfied: yarl<2.0,>=1.17.0 in /usr/local/google/home/swast/src/github.com/googleapis/python-bigquery-dataframes-2/venv/lib/python3.10/site-packages (from aiohttp!=4.0.0a0,!=4.0.0a1->gcsfs!=2025.5.0,>=2023.3.0->bigframes) (1.20.1)\n", - "Requirement already satisfied: pyogrio>=0.7.2 in /usr/local/google/home/swast/src/github.com/googleapis/python-bigquery-dataframes-2/venv/lib/python3.10/site-packages (from geopandas>=0.12.2->bigframes) (0.11.1)\n", - "Requirement already satisfied: pyproj>=3.5.0 in /usr/local/google/home/swast/src/github.com/googleapis/python-bigquery-dataframes-2/venv/lib/python3.10/site-packages (from geopandas>=0.12.2->bigframes) (3.7.1)\n", - "Requirement already satisfied: google-cloud-core<3.0.0,>=2.4.1 in /usr/local/google/home/swast/src/github.com/googleapis/python-bigquery-dataframes-2/venv/lib/python3.10/site-packages (from google-cloud-bigquery>=3.36.0->google-cloud-bigquery[bqstorage,pandas]>=3.36.0->bigframes) (2.4.3)\n", - "Requirement already satisfied: google-resumable-media<3.0.0,>=2.0.0 in /usr/local/google/home/swast/src/github.com/googleapis/python-bigquery-dataframes-2/venv/lib/python3.10/site-packages (from google-cloud-bigquery>=3.36.0->google-cloud-bigquery[bqstorage,pandas]>=3.36.0->bigframes) (2.7.2)\n", - "Requirement already satisfied: google-crc32c<2.0dev,>=1.0 in /usr/local/google/home/swast/src/github.com/googleapis/python-bigquery-dataframes-2/venv/lib/python3.10/site-packages (from google-resumable-media<3.0.0,>=2.0.0->google-cloud-bigquery>=3.36.0->google-cloud-bigquery[bqstorage,pandas]>=3.36.0->bigframes) (1.7.1)\n", - "Requirement already satisfied: comm>=0.1.3 in /usr/local/google/home/swast/src/github.com/googleapis/python-bigquery-dataframes-2/venv/lib/python3.10/site-packages (from ipywidgets>=7.7.1->bigframes) (0.2.3)\n", - "Requirement already satisfied: ipython>=6.1.0 in /usr/local/google/home/swast/src/github.com/googleapis/python-bigquery-dataframes-2/venv/lib/python3.10/site-packages (from ipywidgets>=7.7.1->bigframes) (8.37.0)\n", - "Requirement already satisfied: traitlets>=4.3.1 in /usr/local/google/home/swast/src/github.com/googleapis/python-bigquery-dataframes-2/venv/lib/python3.10/site-packages (from ipywidgets>=7.7.1->bigframes) (5.14.3)\n", - "Requirement already satisfied: widgetsnbextension~=4.0.14 in /usr/local/google/home/swast/src/github.com/googleapis/python-bigquery-dataframes-2/venv/lib/python3.10/site-packages (from ipywidgets>=7.7.1->bigframes) (4.0.14)\n", - "Requirement already satisfied: jupyterlab_widgets~=3.0.15 in /usr/local/google/home/swast/src/github.com/googleapis/python-bigquery-dataframes-2/venv/lib/python3.10/site-packages (from ipywidgets>=7.7.1->bigframes) (3.0.15)\n", - "Requirement already satisfied: exceptiongroup in /usr/local/google/home/swast/src/github.com/googleapis/python-bigquery-dataframes-2/venv/lib/python3.10/site-packages (from ipython>=6.1.0->ipywidgets>=7.7.1->bigframes) (1.3.0)\n", - "Requirement already satisfied: jedi>=0.16 in /usr/local/google/home/swast/src/github.com/googleapis/python-bigquery-dataframes-2/venv/lib/python3.10/site-packages (from ipython>=6.1.0->ipywidgets>=7.7.1->bigframes) (0.19.2)\n", - "Requirement already satisfied: matplotlib-inline in /usr/local/google/home/swast/src/github.com/googleapis/python-bigquery-dataframes-2/venv/lib/python3.10/site-packages (from ipython>=6.1.0->ipywidgets>=7.7.1->bigframes) (0.1.7)\n", - "Requirement already satisfied: pexpect>4.3 in /usr/local/google/home/swast/src/github.com/googleapis/python-bigquery-dataframes-2/venv/lib/python3.10/site-packages (from ipython>=6.1.0->ipywidgets>=7.7.1->bigframes) (4.9.0)\n", - "Requirement already satisfied: prompt_toolkit<3.1.0,>=3.0.41 in /usr/local/google/home/swast/src/github.com/googleapis/python-bigquery-dataframes-2/venv/lib/python3.10/site-packages (from ipython>=6.1.0->ipywidgets>=7.7.1->bigframes) (3.0.52)\n", - "Requirement already satisfied: stack_data in /usr/local/google/home/swast/src/github.com/googleapis/python-bigquery-dataframes-2/venv/lib/python3.10/site-packages (from ipython>=6.1.0->ipywidgets>=7.7.1->bigframes) (0.6.3)\n", - "Requirement already satisfied: wcwidth in /usr/local/google/home/swast/src/github.com/googleapis/python-bigquery-dataframes-2/venv/lib/python3.10/site-packages (from prompt_toolkit<3.1.0,>=3.0.41->ipython>=6.1.0->ipywidgets>=7.7.1->bigframes) (0.2.13)\n", - "Requirement already satisfied: parso<0.9.0,>=0.8.4 in /usr/local/google/home/swast/src/github.com/googleapis/python-bigquery-dataframes-2/venv/lib/python3.10/site-packages (from jedi>=0.16->ipython>=6.1.0->ipywidgets>=7.7.1->bigframes) (0.8.5)\n", - "Requirement already satisfied: mdurl~=0.1 in /usr/local/google/home/swast/src/github.com/googleapis/python-bigquery-dataframes-2/venv/lib/python3.10/site-packages (from markdown-it-py>=2.2.0->rich<14,>=12.4.4->bigframes) (0.1.2)\n", - "Requirement already satisfied: contourpy>=1.0.1 in /usr/local/google/home/swast/src/github.com/googleapis/python-bigquery-dataframes-2/venv/lib/python3.10/site-packages (from matplotlib>=3.7.1->bigframes) (1.3.2)\n", - "Requirement already satisfied: cycler>=0.10 in /usr/local/google/home/swast/src/github.com/googleapis/python-bigquery-dataframes-2/venv/lib/python3.10/site-packages (from matplotlib>=3.7.1->bigframes) (0.12.1)\n", - "Requirement already satisfied: fonttools>=4.22.0 in /usr/local/google/home/swast/src/github.com/googleapis/python-bigquery-dataframes-2/venv/lib/python3.10/site-packages (from matplotlib>=3.7.1->bigframes) (4.59.2)\n", - "Requirement already satisfied: kiwisolver>=1.3.1 in /usr/local/google/home/swast/src/github.com/googleapis/python-bigquery-dataframes-2/venv/lib/python3.10/site-packages (from matplotlib>=3.7.1->bigframes) (1.4.9)\n", - "Requirement already satisfied: pillow>=8 in /usr/local/google/home/swast/src/github.com/googleapis/python-bigquery-dataframes-2/venv/lib/python3.10/site-packages (from matplotlib>=3.7.1->bigframes) (11.3.0)\n", - "Requirement already satisfied: pyparsing>=2.3.1 in /usr/local/google/home/swast/src/github.com/googleapis/python-bigquery-dataframes-2/venv/lib/python3.10/site-packages (from matplotlib>=3.7.1->bigframes) (3.2.3)\n", - "Requirement already satisfied: tzdata>=2022.7 in /usr/local/google/home/swast/src/github.com/googleapis/python-bigquery-dataframes-2/venv/lib/python3.10/site-packages (from pandas>=1.5.3->bigframes) (2025.2)\n", - "Requirement already satisfied: setuptools in /usr/local/google/home/swast/src/github.com/googleapis/python-bigquery-dataframes-2/venv/lib/python3.10/site-packages (from pandas-gbq>=0.26.1->bigframes) (65.5.0)\n", - "Requirement already satisfied: requests-oauthlib>=0.7.0 in /usr/local/google/home/swast/src/github.com/googleapis/python-bigquery-dataframes-2/venv/lib/python3.10/site-packages (from google-auth-oauthlib->gcsfs!=2025.5.0,>=2023.3.0->bigframes) (2.0.0)\n", - "Requirement already satisfied: ptyprocess>=0.5 in /usr/local/google/home/swast/src/github.com/googleapis/python-bigquery-dataframes-2/venv/lib/python3.10/site-packages (from pexpect>4.3->ipython>=6.1.0->ipywidgets>=7.7.1->bigframes) (0.7.0)\n", - "Requirement already satisfied: oauthlib>=3.0.0 in /usr/local/google/home/swast/src/github.com/googleapis/python-bigquery-dataframes-2/venv/lib/python3.10/site-packages (from requests-oauthlib>=0.7.0->google-auth-oauthlib->gcsfs!=2025.5.0,>=2023.3.0->bigframes) (3.3.1)\n", - "Requirement already satisfied: executing>=1.2.0 in /usr/local/google/home/swast/src/github.com/googleapis/python-bigquery-dataframes-2/venv/lib/python3.10/site-packages (from stack_data->ipython>=6.1.0->ipywidgets>=7.7.1->bigframes) (2.2.1)\n", - "Requirement already satisfied: asttokens>=2.1.0 in /usr/local/google/home/swast/src/github.com/googleapis/python-bigquery-dataframes-2/venv/lib/python3.10/site-packages (from stack_data->ipython>=6.1.0->ipywidgets>=7.7.1->bigframes) (3.0.0)\n", - "Requirement already satisfied: pure-eval in /usr/local/google/home/swast/src/github.com/googleapis/python-bigquery-dataframes-2/venv/lib/python3.10/site-packages (from stack_data->ipython>=6.1.0->ipywidgets>=7.7.1->bigframes) (0.2.3)\n" - ] - } - ], + "outputs": [], "source": [ "!pip install bigframes" ] @@ -260,7 +167,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": null, "metadata": { "id": "f200f10a1da3" }, @@ -279,7 +186,7 @@ "id": "BF1j6f9HApxa" }, "source": [ - "## Environment setup\n", + "## Before you begin\n", "\n", "Complete the tasks in this section to set up your environment." ] @@ -333,7 +240,10 @@ }, "outputs": [], "source": [ - "PROJECT_ID = \"\" # @param {type:\"string\"}" + "PROJECT_ID = \"\" # @param {type:\"string\"}\n", + "\n", + "# Set the project id\n", + "! gcloud config set project {PROJECT_ID}" ] }, { @@ -349,7 +259,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": null, "metadata": { "id": "eF-Twtc4XGem" }, @@ -393,7 +303,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": null, "metadata": { "id": "254614fa0c46" }, @@ -415,7 +325,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": null, "metadata": { "id": "603adbbf0532" }, @@ -436,13 +346,13 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": null, "metadata": { "id": "PyQmSRbKA8r-" }, "outputs": [], "source": [ - "import bigframes.pandas as bpd" + "import bigframes.pandas as bf" ] }, { @@ -457,27 +367,14 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": null, "metadata": { "id": "NPPMuw2PXGeo" }, "outputs": [], "source": [ - "# Note: The project option is not required in all environments.\n", - "# On BigQuery Studio, the project ID is automatically detected.\n", - "bpd.options.bigquery.project = PROJECT_ID\n", - "\n", - "# Note: The location option is not required.\n", - "# It defaults to the location of the first table or query\n", - "# passed to read_gbq(). For APIs where a location can't be\n", - "# auto-detected, the location defaults to the \"US\" location.\n", - "bpd.options.bigquery.location = REGION\n", - "\n", - "# Note: By default BigQuery DataFrames emits out BigQuery job metadata via a\n", - "# progress bar. But in this notebook let's disable the progress bar to keep the\n", - "# experience less verbose. If you would like the default behavior, please\n", - "# comment out the following expression. \n", - "bpd.options.display.progress_bar = None" + "bf.options.bigquery.project = PROJECT_ID\n", + "bf.options.bigquery.location = REGION" ] }, { @@ -486,7 +383,7 @@ "id": "pDfrKwMKE_dK" }, "source": [ - "If you want to reset the location of the created DataFrame or Series objects, reset the session by executing `bpd.close_session()`. After that, you can reuse `bpd.options.bigquery.location` to specify another location." + "If you want to reset the location of the created DataFrame or Series objects, reset the session by executing `bf.close_session()`. After that, you can reuse `bf.options.bigquery.location` to specify another location." ] }, { @@ -530,7 +427,7 @@ }, "outputs": [], "source": [ - "# bq_df_sample = bpd.read_gbq(\"bigquery-samples.wikipedia_pageviews.200809h\")" + "# bq_df_sample = bf.read_gbq(\"bigquery-samples.wikipedia_pageviews.200809h\")" ] }, { @@ -550,7 +447,7 @@ "source": [ "Uncomment and run the following cell to see pandas in action over your new BigQuery DataFrames DataFrame.\n", "\n", - "This code uses regex to filter the DataFrame to include only rows with Wikipedia page titles containing the word \"Google\", sums the total views by page title, and then returns the top 10 results." + "This code uses regex to filter the DataFrame to include only rows with Wikipedia page titles containing the word \"Google\", sums the total views by page title, and then returns the top 100 results." ] }, { @@ -559,114 +456,12 @@ "metadata": { "id": "XfGq5apK-D_e" }, - "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", - "
titleviews
21911Google1414560
27669Google_Chrome962482
28394Google_Earth383566
29184Google_Maps205089
27251Google_Android99450
33900Google_search97665
31825Google_chrome78399
30204Google_Street_View71580
40798Image:Google_Chrome.png60746
35222Googleplex53848
\n", - "

10 rows × 2 columns

\n", - "
[10 rows x 2 columns in total]" - ], - "text/plain": [ - " title views\n", - "21911 Google 1414560\n", - "27669 Google_Chrome 962482\n", - "28394 Google_Earth 383566\n", - "29184 Google_Maps 205089\n", - "27251 Google_Android 99450\n", - "33900 Google_search 97665\n", - "31825 Google_chrome 78399\n", - "30204 Google_Street_View 71580\n", - "40798 Image:Google_Chrome.png 60746\n", - "35222 Googleplex 53848\n", - "\n", - "[10 rows x 2 columns]" - ] - }, - "execution_count": 11, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "# bq_df_sample[bq_df_sample.title.str.contains(r\"[Gg]oogle\")]\\\n", - "# .groupby(['title'], as_index=False)['views'].sum(numeric_only=True)\\\n", - "# .sort_values('views', ascending=False)\\\n", - "# .head(10)" + "# .groupby(['title'], as_index=False)['views'].sum(numeric_only=True)\\\n", + "# .sort_values('views', ascending=False)\\\n", + "# .head(100)" ] }, { @@ -731,16 +526,12 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": null, "metadata": { "id": "SvyXzkRl783u" }, "outputs": [], "source": [ - "# BigQuery DataFrames can read directly from GCS.\n", - "fn = 'gs://cloud-samples-data/vertex-ai/bigframe/penguins.csv'\n", - "\n", - "# Or from a local file.\n", "# fn = 'penguins.csv'" ] }, @@ -757,7 +548,7 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": null, "metadata": { "id": "3QHQYlnoBLpt" }, @@ -783,15 +574,13 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": null, "metadata": { "id": "EDAaIwHpQCDZ" }, "outputs": [], "source": [ - "# If order is not important, use the \"bigquery\" engine to\n", - "# allow BigQuery DataFrames to read directly from GCS.\n", - "df_from_local = bpd.read_csv(fn, engine=\"bigquery\")" + "df_from_local = bf.read_csv(fn)" ] }, { @@ -800,124 +589,18 @@ "id": "U-RVfNCu_h_h" }, "source": [ - "Take a look at the rows randomly sampled from the DataFrame:" + "Take a look at the first few rows of the DataFrame:" ] }, { "cell_type": "code", - "execution_count": 15, + "execution_count": null, "metadata": { "id": "_gPD0Zn1Stdb" }, - "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", - "
speciesislandculmen_length_mmculmen_depth_mmflipper_length_mmbody_mass_gsex
41Gentoo penguin (Pygoscelis papua)Biscoe49.816.82305700MALE
73Gentoo penguin (Pygoscelis papua)Biscoe46.816.12155500MALE
75Gentoo penguin (Pygoscelis papua)Biscoe49.616.02255700MALE
93Adelie Penguin (Pygoscelis adeliae)Biscoe35.516.21953350FEMALE
299Chinstrap penguin (Pygoscelis antarctica)Dream52.018.12014050MALE
\n", - "
" - ], - "text/plain": [ - " species island culmen_length_mm \\\n", - "41 Gentoo penguin (Pygoscelis papua) Biscoe 49.8 \n", - "73 Gentoo penguin (Pygoscelis papua) Biscoe 46.8 \n", - "75 Gentoo penguin (Pygoscelis papua) Biscoe 49.6 \n", - "93 Adelie Penguin (Pygoscelis adeliae) Biscoe 35.5 \n", - "299 Chinstrap penguin (Pygoscelis antarctica) Dream 52.0 \n", - "\n", - " culmen_depth_mm flipper_length_mm body_mass_g sex \n", - "41 16.8 230 5700 MALE \n", - "73 16.1 215 5500 MALE \n", - "75 16.0 225 5700 MALE \n", - "93 16.2 195 3350 FEMALE \n", - "299 18.1 201 4050 MALE " - ] - }, - "execution_count": 15, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "df_from_local.peek()" + "outputs": [], + "source": [ + "df_from_local.head()" ] }, { @@ -942,19 +625,11 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": null, "metadata": { "id": "ZSP7gt13QrQt" }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Dataset birds created.\n" - ] - } - ], + "outputs": [], "source": [ "DATASET_ID = \"birds\" # @param {type:\"string\"}\n", "\n", @@ -977,27 +652,13 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": null, "metadata": { "id": "oP1NIAmUBjop" }, - "outputs": [ - { - "data": { - "text/plain": [ - "'bigframes-dev.birds.penguins'" - ] - }, - "execution_count": 17, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ - "df_from_local.to_gbq(\n", - " f\"{PROJECT_ID}.{DATASET_ID}.penguins\",\n", - " if_exists=\"replace\",\n", - ")" + "df_from_local.to_gbq(PROJECT_ID + \".\" + DATASET_ID + \".penguins\")" ] }, { @@ -1021,121 +682,15 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": null, "metadata": { "id": "IBuo-d6dWfsA" }, - "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", - "
speciesislandculmen_length_mmculmen_depth_mmflipper_length_mmbody_mass_gsex
79Gentoo penguin (Pygoscelis papua)Biscoe43.314.02084575FEMALE
118Adelie Penguin (Pygoscelis adeliae)Biscoe40.618.61833550MALE
213Adelie Penguin (Pygoscelis adeliae)Torgersen42.119.11954000MALE
315Adelie Penguin (Pygoscelis adeliae)Torgersen38.719.01953450FEMALE
338Chinstrap penguin (Pygoscelis antarctica)Dream40.916.61873200FEMALE
\n", - "
" - ], - "text/plain": [ - " species island culmen_length_mm \\\n", - "79 Gentoo penguin (Pygoscelis papua) Biscoe 43.3 \n", - "118 Adelie Penguin (Pygoscelis adeliae) Biscoe 40.6 \n", - "213 Adelie Penguin (Pygoscelis adeliae) Torgersen 42.1 \n", - "315 Adelie Penguin (Pygoscelis adeliae) Torgersen 38.7 \n", - "338 Chinstrap penguin (Pygoscelis antarctica) Dream 40.9 \n", - "\n", - " culmen_depth_mm flipper_length_mm body_mass_g sex \n", - "79 14.0 208 4575 FEMALE \n", - "118 18.6 183 3550 MALE \n", - "213 19.1 195 4000 MALE \n", - "315 19.0 195 3450 FEMALE \n", - "338 16.6 187 3200 FEMALE " - ] - }, - "execution_count": 18, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "query_or_table = f\"{PROJECT_ID}.{DATASET_ID}.penguins\"\n", - "bq_df = bpd.read_gbq(query_or_table)\n", - "bq_df.peek()" + "outputs": [], + "source": [ + "query_or_table = f\"\"\"{PROJECT_ID}.{DATASET_ID}.penguins\"\"\"\n", + "bq_df = bf.read_gbq(query_or_table)\n", + "bq_df.head()" ] }, { @@ -1169,34 +724,13 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": null, "metadata": { "id": "6i6HkFJZa8na" }, - "outputs": [ - { - "data": { - "text/plain": [ - "133 \n", - "279 3150\n", - "34 3400\n", - "96 3600\n", - "208 3950\n", - "18 3800\n", - "64 2850\n", - "310 3175\n", - "118 3550\n", - "2 3075\n", - "Name: body_mass_g, dtype: Int64" - ] - }, - "execution_count": 19, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "bq_df[\"body_mass_g\"].peek(10)" + "outputs": [], + "source": [ + "bq_df[\"body_mass_g\"].head(10)" ] }, { @@ -1210,19 +744,11 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": null, "metadata": { "id": "YKwCW7Nsavap" }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "average_body_mass: 4201.754385964914\n" - ] - } - ], + "outputs": [], "source": [ "average_body_mass = bq_df[\"body_mass_g\"].mean()\n", "print(f\"average_body_mass: {average_body_mass}\")" @@ -1239,74 +765,13 @@ }, { "cell_type": "code", - "execution_count": 21, + "execution_count": null, "metadata": { "id": "4PyKMR61-Mjy" }, - "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", - "
body_mass_g
species
Adelie Penguin (Pygoscelis adeliae)3700.662252
Chinstrap penguin (Pygoscelis antarctica)3733.088235
Gentoo penguin (Pygoscelis papua)5076.01626
\n", - "

3 rows × 1 columns

\n", - "
[3 rows x 1 columns in total]" - ], - "text/plain": [ - " body_mass_g\n", - "species \n", - "Adelie Penguin (Pygoscelis adeliae) 3700.662252\n", - "Chinstrap penguin (Pygoscelis antarctica) 3733.088235\n", - "Gentoo penguin (Pygoscelis papua) 5076.01626\n", - "\n", - "[3 rows x 1 columns]" - ] - }, - "execution_count": 21, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "bq_df[[\"species\", \"body_mass_g\"]].groupby(by=bq_df[\"species\"]).mean(numeric_only=True)" + "outputs": [], + "source": [ + "bq_df[\"species\", \"body_mass_g\"].groupby(by=bq_df[\"species\"]).mean(numeric_only=True).head()" ] }, { @@ -1335,9 +800,9 @@ "id": "zjw8toUbHuRD" }, "source": [ - "Running the cell below creates a custom function using the `remote_function` method. This function categorizes a value into one of two buckets: >= 3500 or <3500.\n", + "Running the cell below creates a custom function using the `remote_function` method. This function categorizes a value into one of two buckets: >= 4000 or <4000.\n", "\n", - "> Note: Creating a function requires a [BigQuery connection](https://cloud.google.com/bigquery/docs/remote-functions#create_a_remote_function). This code assumes a pre-created connection named `bigframes-default-connection`. If\n", + "> Note: Creating a function requires a [BigQuery connection](https://cloud.google.com/bigquery/docs/remote-functions#create_a_remote_function). This code assumes a pre-created connection named `bigframes-rf-conn`. If\n", "the connection is not already created, BigQuery DataFrames attempts to create one assuming the [necessary APIs\n", "and IAM permissions](https://cloud.google.com/python/docs/reference/bigframes/latest/bigframes.pandas#bigframes_pandas_remote_function) are set up in the project.\n", "\n", @@ -1346,17 +811,17 @@ }, { "cell_type": "code", - "execution_count": 23, + "execution_count": null, "metadata": { "id": "rSWTOG-vb2Fc" }, "outputs": [], "source": [ - "@bpd.remote_function(cloud_function_service_account=\"default\")\n", - "def get_bucket(num: float) -> str:\n", - " if not num: return \"NA\"\n", - " boundary = 3500\n", - " return \"at_or_above_3500\" if num >= boundary else \"below_3500\"" + "@bf.remote_function([float], str, bigquery_connection='bigframes-rf-conn')\n", + "def get_bucket(num):\n", + " if not num: return \"NA\"\n", + " boundary = 4000\n", + " return \"at_or_above_4000\" if num >= boundary else \"below_4000\"" ] }, { @@ -1372,20 +837,11 @@ }, { "cell_type": "code", - "execution_count": 24, + "execution_count": null, "metadata": { "id": "6ejPXoyEQpWE" }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Cloud Function Name projects/bigframes-dev/locations/us-central1/functions/bigframes-sessioncf7a5d-aa59468b9d6c757c1256e46c9f71ebe3\n", - "Remote Function Name bigframes-dev._63cfa399614a54153cc386c27d6c0c6fdb249f9e.bigframes_sessioncf7a5d_aa59468b9d6c757c1256e46c9f71ebe3\n" - ] - } - ], + "outputs": [], "source": [ "CLOUD_FUNCTION_NAME = format(get_bucket.bigframes_cloud_function)\n", "print(\"Cloud Function Name \" + CLOUD_FUNCTION_NAME)\n", @@ -1404,113 +860,14 @@ }, { "cell_type": "code", - "execution_count": 25, + "execution_count": null, "metadata": { "id": "NxSd9WZFcIji" }, - "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", - "
body_mass_gbody_mass_bucket
133<NA>NA
2793150below_3500
343400below_3500
963600at_or_above_3500
2083950at_or_above_3500
183800at_or_above_3500
642850below_3500
3103175below_3500
1183550at_or_above_3500
23075below_3500
\n", - "
" - ], - "text/plain": [ - " body_mass_g body_mass_bucket\n", - "133 NA\n", - "279 3150 below_3500\n", - "34 3400 below_3500\n", - "96 3600 at_or_above_3500\n", - "208 3950 at_or_above_3500\n", - "18 3800 at_or_above_3500\n", - "64 2850 below_3500\n", - "310 3175 below_3500\n", - "118 3550 at_or_above_3500\n", - "2 3075 below_3500" - ] - }, - "execution_count": 25, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "bq_df = bq_df.assign(body_mass_bucket=bq_df['body_mass_g'].apply(get_bucket))\n", - "bq_df[['body_mass_g', 'body_mass_bucket']].peek(10)" + "bq_df[['body_mass_g', 'body_mass_bucket']].head(10)" ] }, { @@ -1542,17 +899,7 @@ }, { "cell_type": "code", - "execution_count": 26, - "metadata": {}, - "outputs": [], - "source": [ - "# Delete the temporary cloud artifacts created during the bigframes session \n", - "bpd.close_session()" - ] - }, - { - "cell_type": "code", - "execution_count": 27, + "execution_count": null, "metadata": { "id": "sx_vKniMq9ZX" }, @@ -1569,7 +916,7 @@ }, { "cell_type": "code", - "execution_count": 28, + "execution_count": null, "metadata": { "id": "_dTCXvCxtPw9" }, @@ -1578,14 +925,14 @@ "# # Delete the BigQuery Connection\n", "# from google.cloud import bigquery_connection_v1 as bq_connection\n", "# client = bq_connection.ConnectionServiceClient()\n", - "# CONNECTION_ID = f\"projects/{PROJECT_ID}/locations/{REGION}/connections/bigframes-default-connection\"\n", + "# CONNECTION_ID = f\"projects/{PROJECT_ID}/locations/{REGION}/connections/bigframes-rf-conn\"\n", "# client.delete_connection(name=CONNECTION_ID)\n", "# print(\"Deleted connection '{}'.\".format(CONNECTION_ID))" ] }, { "cell_type": "code", - "execution_count": 29, + "execution_count": null, "metadata": { "id": "EDAIIfcpwNOF" }, @@ -1597,7 +944,7 @@ }, { "cell_type": "code", - "execution_count": 30, + "execution_count": null, "metadata": { "id": "QwumLUKmVpuH" }, @@ -1615,21 +962,8 @@ "toc_visible": true }, "kernelspec": { - "display_name": "venv", - "language": "python", + "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.16" } }, "nbformat": 4, diff --git a/notebooks/getting_started/magics.ipynb b/notebooks/getting_started/magics.ipynb deleted file mode 100644 index 1f2cf7a409b..00000000000 --- a/notebooks/getting_started/magics.ipynb +++ /dev/null @@ -1,406 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "91edcf7b", - "metadata": {}, - "source": [ - "# %%bqsql cell magics\n", - "\n", - "The BigQuery DataFrames (aka BigFrames) package provides a `%%bqsql` cell magics for Jupyter environments.\n", - "\n", - "To use it, first activate the extension:" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "id": "98cd0489", - "metadata": {}, - "outputs": [], - "source": [ - "%load_ext bigframes" - ] - }, - { - "cell_type": "markdown", - "id": "f18fdc63", - "metadata": {}, - "source": [ - "Now, use the magics by including SQL in the body." - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "269c5862", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - " Query processed 0 Bytes. [Job bigframes-dev:US.job_UVe7FsupxF3CbYuLcLT7fpw9dozg details]\n", - " " - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "1e2fb7b019754d31b11323a054f97f47", - "version_major": 2, - "version_minor": 1 - }, - "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", - "
stategenderyearnamenumber
0HIF1999Ariana10
1HIF2002Jordyn10
2HIF2006Mya10
3HIF2010Jordyn10
4HIM1921Nobuo10
5HIM1925Ralph10
6HIM1926Hisao10
7HIM1927Moses10
8HIM1933Larry10
9HIM1933Alfredo10
\n", - "

10 rows × 5 columns

\n", - "
[5552452 rows x 5 columns in total]" - ], - "text/plain": [ - "state gender year name number\n", - " HI F 1999 Ariana 10\n", - " HI F 2002 Jordyn 10\n", - " HI F 2006 Mya 10\n", - " HI F 2010 Jordyn 10\n", - " HI M 1921 Nobuo 10\n", - " HI M 1925 Ralph 10\n", - " HI M 1926 Hisao 10\n", - " HI M 1927 Moses 10\n", - " HI M 1933 Larry 10\n", - " HI M 1933 Alfredo 10\n", - "...\n", - "\n", - "[5552452 rows x 5 columns]" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "%%bqsql\n", - "SELECT * FROM `bigquery-public-data.usa_names.usa_1910_2013`" - ] - }, - { - "cell_type": "markdown", - "id": "8771e10f", - "metadata": {}, - "source": [ - "The output DataFrame can be saved to a variable." - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "30bb6327", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - " Query processed 0 Bytes. [Job bigframes-dev:US.c142adf3-cd95-42da-bbdc-c176b36b934f details]\n", - " " - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "%%bqsql mydf\n", - "SELECT * FROM `bigquery-public-data.usa_names.usa_1910_2013`" - ] - }, - { - "cell_type": "markdown", - "id": "533e2e9e", - "metadata": {}, - "source": [ - "You can chain cells together using format strings. DataFrame objects are automatically turned into table expressions." - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "6a8a8123", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - " Query processed 88.1 MB in a moment of slot time.\n", - " " - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "c4889de9296440428de90defb5c58070", - "version_major": 2, - "version_minor": 1 - }, - "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", - "
total_countname
0304036Tracy
1293876Travis
2203784Troy
3150127Trevor
496397Tristan
589996Tracey
665546Trinity
750112Traci
849657Trenton
945692Trent
\n", - "

10 rows × 2 columns

\n", - "
[238 rows x 2 columns in total]" - ], - "text/plain": [ - " total_count name\n", - "0 304036 Tracy\n", - "1 293876 Travis\n", - "2 203784 Troy\n", - "3 150127 Trevor\n", - "4 96397 Tristan\n", - "5 89996 Tracey\n", - "6 65546 Trinity\n", - "7 50112 Traci\n", - "8 49657 Trenton\n", - "9 45692 Trent\n", - "...\n", - "\n", - "[238 rows x 2 columns]" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "%%bqsql\n", - "SELECT sum(number) as total_count, name\n", - "FROM {mydf}\n", - "WHERE name LIKE 'Tr%'\n", - "GROUP BY name\n", - "ORDER BY total_count DESC" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "d2a17078", - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "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.18" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/notebooks/getting_started/ml_fundamentals.ipynb b/notebooks/getting_started/ml_fundamentals.ipynb new file mode 100644 index 00000000000..2f566dd7049 --- /dev/null +++ b/notebooks/getting_started/ml_fundamentals.ipynb @@ -0,0 +1,3408 @@ +{ + "cells": [ + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Using ML - ML fundamentals\n", + "\n", + "The `bigframes.ml` module implements Scikit-Learn's machine learning API in\n", + "BigQuery DataFrames. It exposes BigQuery's ML capabilities in a simple, popular\n", + "API that works seamlessly with the rest of the BigQuery DataFrames API." + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "0c8a8bc0b4d64448aef68d6a98fae666", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "HTML(value='Query job 28e903c6-e874-4b99-8f53-0755e0b0c188 is RUNNING. \n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
islandculmen_length_mmculmen_depth_mmflipper_length_mmsexspecies
penguin_id
156Biscoe46.214.5209.0FEMALEGentoo penguin (Pygoscelis papua)
189Biscoe35.318.9187.0FEMALEAdelie Penguin (Pygoscelis adeliae)
279Biscoe45.114.5215.0FEMALEGentoo penguin (Pygoscelis papua)
245Biscoe49.516.2229.0MALEGentoo penguin (Pygoscelis papua)
343Torgersen37.320.5199.0MALEAdelie Penguin (Pygoscelis adeliae)
\n", + "

5 rows × 6 columns

\n", + "[5 rows x 6 columns in total]" + ], + "text/plain": [ + " island culmen_length_mm culmen_depth_mm flipper_length_mm \\\n", + "penguin_id \n", + "156 Biscoe 46.2 14.5 209.0 \n", + "189 Biscoe 35.3 18.9 187.0 \n", + "279 Biscoe 45.1 14.5 215.0 \n", + "245 Biscoe 49.5 16.2 229.0 \n", + "343 Torgersen 37.3 20.5 199.0 \n", + "\n", + " sex species \n", + "penguin_id \n", + "156 FEMALE Gentoo penguin (Pygoscelis papua) \n", + "189 FEMALE Adelie Penguin (Pygoscelis adeliae) \n", + "279 FEMALE Gentoo penguin (Pygoscelis papua) \n", + "245 MALE Gentoo penguin (Pygoscelis papua) \n", + "343 MALE Adelie Penguin (Pygoscelis adeliae) \n", + "\n", + "[5 rows x 6 columns]" + ] + }, + "execution_count": 20, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# If we look at the data, we can see that random rows were selected for\n", + "# each side of the split\n", + "X_test.head(5)" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "d6dd794f89724099950dcc927d63d0f5", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "HTML(value='Query job d5a173bd-a7dc-42fa-8468-b088d47ccfe0 is RUNNING.
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
standard_scaled_culmen_length_mmstandard_scaled_culmen_depth_mmstandard_scaled_flipper_length_mm
penguin_id
0-1.3441880.642519-1.193942
1-0.7500471.005876-1.193942
2-0.5458110.90206-1.193942
4-1.214219-0.188011-0.619171
5-0.1187720.694427-0.619171
60.568203-0.291828-0.619171
71.2366110.642519-0.044401
9-0.6757791.524957-0.044401
10-0.5643780.902060.530369
11-0.8985820.798243-1.122096
12-1.26992-0.136103-1.122096
130.586770.071529-1.122096
14-1.826927-0.032287-1.122096
15-1.26992-0.343736-1.122096
160.34540.071529-0.547325
18-0.7686140.382978-0.547325
19-1.1213850.486795-0.547325
200.5125020.33107-0.547325
211.3851461.057784-0.547325
22-0.675779-0.032287-0.547325
241.0695090.538703-0.547325
26-0.434410.6944270.027445
281.9235861.8883140.027445
301.2923120.6944270.027445
31-1.994029-0.551368-1.62502
\n", + "

25 rows × 3 columns

\n", + "[267 rows x 3 columns in total]" + ], + "text/plain": [ + " standard_scaled_culmen_length_mm standard_scaled_culmen_depth_mm \\\n", + "penguin_id \n", + "0 -1.344188 0.642519 \n", + "1 -0.750047 1.005876 \n", + "2 -0.545811 0.90206 \n", + "4 -1.214219 -0.188011 \n", + "5 -0.118772 0.694427 \n", + "6 0.568203 -0.291828 \n", + "7 1.236611 0.642519 \n", + "9 -0.675779 1.524957 \n", + "10 -0.564378 0.90206 \n", + "11 -0.898582 0.798243 \n", + "12 -1.26992 -0.136103 \n", + "13 0.58677 0.071529 \n", + "14 -1.826927 -0.032287 \n", + "15 -1.26992 -0.343736 \n", + "16 0.3454 0.071529 \n", + "18 -0.768614 0.382978 \n", + "19 -1.121385 0.486795 \n", + "20 0.512502 0.33107 \n", + "21 1.385146 1.057784 \n", + "22 -0.675779 -0.032287 \n", + "24 1.069509 0.538703 \n", + "26 -0.43441 0.694427 \n", + "28 1.923586 1.888314 \n", + "30 1.292312 0.694427 \n", + "31 -1.994029 -0.551368 \n", + "\n", + " standard_scaled_flipper_length_mm \n", + "penguin_id \n", + "0 -1.193942 \n", + "1 -1.193942 \n", + "2 -1.193942 \n", + "4 -0.619171 \n", + "5 -0.619171 \n", + "6 -0.619171 \n", + "7 -0.044401 \n", + "9 -0.044401 \n", + "10 0.530369 \n", + "11 -1.122096 \n", + "12 -1.122096 \n", + "13 -1.122096 \n", + "14 -1.122096 \n", + "15 -1.122096 \n", + "16 -0.547325 \n", + "18 -0.547325 \n", + "19 -0.547325 \n", + "20 -0.547325 \n", + "21 -0.547325 \n", + "22 -0.547325 \n", + "24 -0.547325 \n", + "26 0.027445 \n", + "28 0.027445 \n", + "30 0.027445 \n", + "31 -1.62502 \n", + "...\n", + "\n", + "[267 rows x 3 columns]" + ] + }, + "execution_count": 22, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from bigframes.ml.preprocessing import StandardScaler\n", + "\n", + "# StandardScaler will only work on numeric columns\n", + "numeric_columns = [\"culmen_length_mm\", \"culmen_depth_mm\", \"flipper_length_mm\"]\n", + "\n", + "scaler = StandardScaler()\n", + "scaler.fit(X_train[numeric_columns])\n", + "\n", + "# Now, standardscaler should transform the numbers to have mean of zero\n", + "# and standard deviation of one:\n", + "scaler.transform(X_train[numeric_columns])" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "74f3c24c0a434e12bf6a56dc4809b501", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "HTML(value='Query job c6268b07-0d3d-4fe0-971d-cc99fd98cd7e is RUNNING.
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
standard_scaled_culmen_length_mmstandard_scaled_culmen_depth_mmstandard_scaled_flipper_length_mm
penguin_id
30.4939350.382978-0.619171
81.0509420.953968-0.044401
171.2551781.1616-0.547325
23-1.3070540.694427-0.547325
251.5151140.4867950.027445
271.2366111.2654170.027445
291.4037130.9539680.027445
340.4196680.538703-1.62502
35-1.4555890.694427-1.050249
390.3268331.1616-0.475479
51-1.0656840.227254-0.978403
52-0.2487410.071529-0.978403
600.5310690.382978-0.403633
610.4011010.90206-0.403633
64-1.4555890.33107-0.403633
65-0.5643780.642519-0.403633
671.2737451.3173250.171138
832.6291280.33107-1.409481
85-1.2884870.746335-0.83471
93-0.5086770.4867950.314831
1040.382534-0.032287-0.762864
105-1.0656840.746335-0.762864
1081.1623430.382978-0.762864
1131.4965471.2135090.386677
130-0.3415751.213509-0.044401
\n", + "

25 rows × 3 columns

\n", + "[67 rows x 3 columns in total]" + ], + "text/plain": [ + " standard_scaled_culmen_length_mm standard_scaled_culmen_depth_mm \\\n", + "penguin_id \n", + "3 0.493935 0.382978 \n", + "8 1.050942 0.953968 \n", + "17 1.255178 1.1616 \n", + "23 -1.307054 0.694427 \n", + "25 1.515114 0.486795 \n", + "27 1.236611 1.265417 \n", + "29 1.403713 0.953968 \n", + "34 0.419668 0.538703 \n", + "35 -1.455589 0.694427 \n", + "39 0.326833 1.1616 \n", + "51 -1.065684 0.227254 \n", + "52 -0.248741 0.071529 \n", + "60 0.531069 0.382978 \n", + "61 0.401101 0.90206 \n", + "64 -1.455589 0.33107 \n", + "65 -0.564378 0.642519 \n", + "67 1.273745 1.317325 \n", + "83 2.629128 0.33107 \n", + "85 -1.288487 0.746335 \n", + "93 -0.508677 0.486795 \n", + "104 0.382534 -0.032287 \n", + "105 -1.065684 0.746335 \n", + "108 1.162343 0.382978 \n", + "113 1.496547 1.213509 \n", + "130 -0.341575 1.213509 \n", + "\n", + " standard_scaled_flipper_length_mm \n", + "penguin_id \n", + "3 -0.619171 \n", + "8 -0.044401 \n", + "17 -0.547325 \n", + "23 -0.547325 \n", + "25 0.027445 \n", + "27 0.027445 \n", + "29 0.027445 \n", + "34 -1.62502 \n", + "35 -1.050249 \n", + "39 -0.475479 \n", + "51 -0.978403 \n", + "52 -0.978403 \n", + "60 -0.403633 \n", + "61 -0.403633 \n", + "64 -0.403633 \n", + "65 -0.403633 \n", + "67 0.171138 \n", + "83 -1.409481 \n", + "85 -0.83471 \n", + "93 0.314831 \n", + "104 -0.762864 \n", + "105 -0.762864 \n", + "108 -0.762864 \n", + "113 0.386677 \n", + "130 -0.044401 \n", + "...\n", + "\n", + "[67 rows x 3 columns]" + ] + }, + "execution_count": 23, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# We can then repeat this transformation on new data\n", + "scaler.transform(X_test[numeric_columns])" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Composing transformers\n", + "\n", + "To process data where different columns need different preprocessors, `bigframes.composition.ColumnTransformer` can be employed:" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "d642a617d27f4e2493c80dbdd1686193", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "HTML(value='Query job a8d8afa4-d91e-487e-8709-8727a73ab453 is RUNNING.
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
onehotencoded_islandstandard_scaled_culmen_length_mmstandard_scaled_culmen_depth_mmstandard_scaled_flipper_length_mmonehotencoded_sexonehotencoded_species
penguin_id
0[{'index': 2, 'value': 1.0}]-1.3441880.642519-1.193942[{'index': 2, 'value': 1.0}][{'index': 1, 'value': 1.0}]
1[{'index': 2, 'value': 1.0}]-0.7500471.005876-1.193942[{'index': 3, 'value': 1.0}][{'index': 1, 'value': 1.0}]
2[{'index': 2, 'value': 1.0}]-0.5458110.90206-1.193942[{'index': 3, 'value': 1.0}][{'index': 1, 'value': 1.0}]
4[{'index': 2, 'value': 1.0}]-1.214219-0.188011-0.619171[{'index': 2, 'value': 1.0}][{'index': 1, 'value': 1.0}]
5[{'index': 2, 'value': 1.0}]-0.1187720.694427-0.619171[{'index': 3, 'value': 1.0}][{'index': 1, 'value': 1.0}]
6[{'index': 2, 'value': 1.0}]0.568203-0.291828-0.619171[{'index': 2, 'value': 1.0}][{'index': 2, 'value': 1.0}]
7[{'index': 2, 'value': 1.0}]1.2366110.642519-0.044401[{'index': 2, 'value': 1.0}][{'index': 2, 'value': 1.0}]
9[{'index': 2, 'value': 1.0}]-0.6757791.524957-0.044401[{'index': 3, 'value': 1.0}][{'index': 1, 'value': 1.0}]
10[{'index': 2, 'value': 1.0}]-0.5643780.902060.530369[{'index': 3, 'value': 1.0}][{'index': 1, 'value': 1.0}]
11[{'index': 2, 'value': 1.0}]-0.8985820.798243-1.122096[{'index': 3, 'value': 1.0}][{'index': 1, 'value': 1.0}]
12[{'index': 2, 'value': 1.0}]-1.26992-0.136103-1.122096[{'index': 2, 'value': 1.0}][{'index': 1, 'value': 1.0}]
13[{'index': 2, 'value': 1.0}]0.586770.071529-1.122096[{'index': 2, 'value': 1.0}][{'index': 2, 'value': 1.0}]
14[{'index': 2, 'value': 1.0}]-1.826927-0.032287-1.122096[{'index': 2, 'value': 1.0}][{'index': 1, 'value': 1.0}]
15[{'index': 2, 'value': 1.0}]-1.26992-0.343736-1.122096[{'index': 2, 'value': 1.0}][{'index': 1, 'value': 1.0}]
16[{'index': 2, 'value': 1.0}]0.34540.071529-0.547325[{'index': 2, 'value': 1.0}][{'index': 2, 'value': 1.0}]
18[{'index': 2, 'value': 1.0}]-0.7686140.382978-0.547325[{'index': 3, 'value': 1.0}][{'index': 1, 'value': 1.0}]
19[{'index': 2, 'value': 1.0}]-1.1213850.486795-0.547325[{'index': 3, 'value': 1.0}][{'index': 1, 'value': 1.0}]
20[{'index': 2, 'value': 1.0}]0.5125020.33107-0.547325[{'index': 2, 'value': 1.0}][{'index': 2, 'value': 1.0}]
21[{'index': 2, 'value': 1.0}]1.3851461.057784-0.547325[{'index': 3, 'value': 1.0}][{'index': 2, 'value': 1.0}]
22[{'index': 2, 'value': 1.0}]-0.675779-0.032287-0.547325[{'index': 2, 'value': 1.0}][{'index': 1, 'value': 1.0}]
24[{'index': 2, 'value': 1.0}]1.0695090.538703-0.547325[{'index': 3, 'value': 1.0}][{'index': 2, 'value': 1.0}]
26[{'index': 2, 'value': 1.0}]-0.434410.6944270.027445[{'index': 3, 'value': 1.0}][{'index': 1, 'value': 1.0}]
28[{'index': 2, 'value': 1.0}]1.9235861.8883140.027445[{'index': 3, 'value': 1.0}][{'index': 2, 'value': 1.0}]
30[{'index': 2, 'value': 1.0}]1.2923120.6944270.027445[{'index': 3, 'value': 1.0}][{'index': 2, 'value': 1.0}]
31[{'index': 2, 'value': 1.0}]-1.994029-0.551368-1.62502[{'index': 2, 'value': 1.0}][{'index': 1, 'value': 1.0}]
\n", + "

25 rows × 6 columns

\n", + "[267 rows x 6 columns in total]" + ], + "text/plain": [ + " onehotencoded_island standard_scaled_culmen_length_mm \\\n", + "penguin_id \n", + "0 [{'index': 2, 'value': 1.0}] -1.344188 \n", + "1 [{'index': 2, 'value': 1.0}] -0.750047 \n", + "2 [{'index': 2, 'value': 1.0}] -0.545811 \n", + "4 [{'index': 2, 'value': 1.0}] -1.214219 \n", + "5 [{'index': 2, 'value': 1.0}] -0.118772 \n", + "6 [{'index': 2, 'value': 1.0}] 0.568203 \n", + "7 [{'index': 2, 'value': 1.0}] 1.236611 \n", + "9 [{'index': 2, 'value': 1.0}] -0.675779 \n", + "10 [{'index': 2, 'value': 1.0}] -0.564378 \n", + "11 [{'index': 2, 'value': 1.0}] -0.898582 \n", + "12 [{'index': 2, 'value': 1.0}] -1.26992 \n", + "13 [{'index': 2, 'value': 1.0}] 0.58677 \n", + "14 [{'index': 2, 'value': 1.0}] -1.826927 \n", + "15 [{'index': 2, 'value': 1.0}] -1.26992 \n", + "16 [{'index': 2, 'value': 1.0}] 0.3454 \n", + "18 [{'index': 2, 'value': 1.0}] -0.768614 \n", + "19 [{'index': 2, 'value': 1.0}] -1.121385 \n", + "20 [{'index': 2, 'value': 1.0}] 0.512502 \n", + "21 [{'index': 2, 'value': 1.0}] 1.385146 \n", + "22 [{'index': 2, 'value': 1.0}] -0.675779 \n", + "24 [{'index': 2, 'value': 1.0}] 1.069509 \n", + "26 [{'index': 2, 'value': 1.0}] -0.43441 \n", + "28 [{'index': 2, 'value': 1.0}] 1.923586 \n", + "30 [{'index': 2, 'value': 1.0}] 1.292312 \n", + "31 [{'index': 2, 'value': 1.0}] -1.994029 \n", + "\n", + " standard_scaled_culmen_depth_mm \\\n", + "penguin_id \n", + "0 0.642519 \n", + "1 1.005876 \n", + "2 0.90206 \n", + "4 -0.188011 \n", + "5 0.694427 \n", + "6 -0.291828 \n", + "7 0.642519 \n", + "9 1.524957 \n", + "10 0.90206 \n", + "11 0.798243 \n", + "12 -0.136103 \n", + "13 0.071529 \n", + "14 -0.032287 \n", + "15 -0.343736 \n", + "16 0.071529 \n", + "18 0.382978 \n", + "19 0.486795 \n", + "20 0.33107 \n", + "21 1.057784 \n", + "22 -0.032287 \n", + "24 0.538703 \n", + "26 0.694427 \n", + "28 1.888314 \n", + "30 0.694427 \n", + "31 -0.551368 \n", + "\n", + " standard_scaled_flipper_length_mm onehotencoded_sex \\\n", + "penguin_id \n", + "0 -1.193942 [{'index': 2, 'value': 1.0}] \n", + "1 -1.193942 [{'index': 3, 'value': 1.0}] \n", + "2 -1.193942 [{'index': 3, 'value': 1.0}] \n", + "4 -0.619171 [{'index': 2, 'value': 1.0}] \n", + "5 -0.619171 [{'index': 3, 'value': 1.0}] \n", + "6 -0.619171 [{'index': 2, 'value': 1.0}] \n", + "7 -0.044401 [{'index': 2, 'value': 1.0}] \n", + "9 -0.044401 [{'index': 3, 'value': 1.0}] \n", + "10 0.530369 [{'index': 3, 'value': 1.0}] \n", + "11 -1.122096 [{'index': 3, 'value': 1.0}] \n", + "12 -1.122096 [{'index': 2, 'value': 1.0}] \n", + "13 -1.122096 [{'index': 2, 'value': 1.0}] \n", + "14 -1.122096 [{'index': 2, 'value': 1.0}] \n", + "15 -1.122096 [{'index': 2, 'value': 1.0}] \n", + "16 -0.547325 [{'index': 2, 'value': 1.0}] \n", + "18 -0.547325 [{'index': 3, 'value': 1.0}] \n", + "19 -0.547325 [{'index': 3, 'value': 1.0}] \n", + "20 -0.547325 [{'index': 2, 'value': 1.0}] \n", + "21 -0.547325 [{'index': 3, 'value': 1.0}] \n", + "22 -0.547325 [{'index': 2, 'value': 1.0}] \n", + "24 -0.547325 [{'index': 3, 'value': 1.0}] \n", + "26 0.027445 [{'index': 3, 'value': 1.0}] \n", + "28 0.027445 [{'index': 3, 'value': 1.0}] \n", + "30 0.027445 [{'index': 3, 'value': 1.0}] \n", + "31 -1.62502 [{'index': 2, 'value': 1.0}] \n", + "\n", + " onehotencoded_species \n", + "penguin_id \n", + "0 [{'index': 1, 'value': 1.0}] \n", + "1 [{'index': 1, 'value': 1.0}] \n", + "2 [{'index': 1, 'value': 1.0}] \n", + "4 [{'index': 1, 'value': 1.0}] \n", + "5 [{'index': 1, 'value': 1.0}] \n", + "6 [{'index': 2, 'value': 1.0}] \n", + "7 [{'index': 2, 'value': 1.0}] \n", + "9 [{'index': 1, 'value': 1.0}] \n", + "10 [{'index': 1, 'value': 1.0}] \n", + "11 [{'index': 1, 'value': 1.0}] \n", + "12 [{'index': 1, 'value': 1.0}] \n", + "13 [{'index': 2, 'value': 1.0}] \n", + "14 [{'index': 1, 'value': 1.0}] \n", + "15 [{'index': 1, 'value': 1.0}] \n", + "16 [{'index': 2, 'value': 1.0}] \n", + "18 [{'index': 1, 'value': 1.0}] \n", + "19 [{'index': 1, 'value': 1.0}] \n", + "20 [{'index': 2, 'value': 1.0}] \n", + "21 [{'index': 2, 'value': 1.0}] \n", + "22 [{'index': 1, 'value': 1.0}] \n", + "24 [{'index': 2, 'value': 1.0}] \n", + "26 [{'index': 1, 'value': 1.0}] \n", + "28 [{'index': 2, 'value': 1.0}] \n", + "30 [{'index': 2, 'value': 1.0}] \n", + "31 [{'index': 1, 'value': 1.0}] \n", + "...\n", + "\n", + "[267 rows x 6 columns]" + ] + }, + "execution_count": 24, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from bigframes.ml.compose import ColumnTransformer\n", + "from bigframes.ml.preprocessing import OneHotEncoder\n", + "\n", + "# Create an aggregate transform that applies StandardScaler to the numeric columns,\n", + "# and OneHotEncoder to the string columns\n", + "preproc = ColumnTransformer([\n", + " (\"scale\", StandardScaler(), [\"culmen_length_mm\", \"culmen_depth_mm\", \"flipper_length_mm\"]),\n", + " (\"encode\", OneHotEncoder(), [\"species\", \"sex\", \"island\"])])\n", + "\n", + "# Now we can fit all columns of the training data\n", + "preproc.fit(X_train)\n", + "\n", + "processed_X_train = preproc.transform(X_train)\n", + "processed_X_test = preproc.transform(X_test)\n", + "\n", + "processed_X_train" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Predictors\n", + "\n", + "Predictors are estimators that learn and make predictions. In addition to `.fit(...)`, the predictor implements a `.predict(...)` method, which will use what was learned during `.fit(...)` to predict some output.\n", + "\n", + "Predictors can be further broken down into two categories:\n", + "\n", + "#### Supervised predictors\n", + "\n", + "Supervised learning is when we train a model on input-output pairs, and then ask it to predict the output for new inputs. An example of such a predictor is `bigframes.ml.linear_models.LinearRegression`." + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "5db4c5c80ba4417db151aa561dab5ee7", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "HTML(value='Query job ceced0cc-13a7-4b14-b42c-4d5f69e7e49a is RUNNING.
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
predicted_body_mass_g
penguin_id
33394.116212
84048.683645
173976.452358
233541.580346
254032.842027
274118.34983
294087.765797
343183.75379
353418.800633
393519.18471
513398.133564
523223.614107
603445.012713
613505.637004
643515.903779
654028.361259
674159.991956
833348.167212
853485.048557
934172.872284
1043299.300454
1053515.68617
1083405.222757
1134209.13832
1304197.90382
\n", + "

25 rows × 1 columns

\n", + "[67 rows x 1 columns in total]" + ], + "text/plain": [ + " predicted_body_mass_g\n", + "penguin_id \n", + "3 3394.116212\n", + "8 4048.683645\n", + "17 3976.452358\n", + "23 3541.580346\n", + "25 4032.842027\n", + "27 4118.34983\n", + "29 4087.765797\n", + "34 3183.75379\n", + "35 3418.800633\n", + "39 3519.18471\n", + "51 3398.133564\n", + "52 3223.614107\n", + "60 3445.012713\n", + "61 3505.637004\n", + "64 3515.903779\n", + "65 4028.361259\n", + "67 4159.991956\n", + "83 3348.167212\n", + "85 3485.048557\n", + "93 4172.872284\n", + "104 3299.300454\n", + "105 3515.68617\n", + "108 3405.222757\n", + "113 4209.13832\n", + "130 4197.90382\n", + "...\n", + "\n", + "[67 rows x 1 columns]" + ] + }, + "execution_count": 28, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "pipeline.fit(X_train, y_train)\n", + "\n", + "predicted_y_test = pipeline.predict(X_test)\n", + "predicted_y_test" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In the backend, a pipeline will actually be compiled into a single model with an embedded TRANSFORM step." + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Evaluating results\n", + "\n", + "Some models include a convenient `.score(X, y)` method for evaulation with a preset accuracy metric:" + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "2d32081be31f44abb8de67e2209d76cd", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "HTML(value='Query job 2a043039-670f-4eb8-9cf0-765ee6ed7de6 is RUNNING.
\n", - "\n", - " \n", - " \n", - " \"Colab Run in Colab\n", - " \n", - " \n", - " \n", - " \n", - " \"GitHub\n", - " View on GitHub\n", - " \n", - " \n", - " \n", - " \n", - " \"Vertex\n", - " Open in Vertex AI Workbench\n", - " \n", - " \n", - " \n", - " \n", - " \"BQ\n", - " Open in BQ Studio\n", - " \n", - " \n", - "" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "24743cf4a1e1" - }, - "source": [ - "**_NOTE_**: This notebook has been tested in the following environment:\n", - "\n", - "* Python version = 3.10" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "tvgnzT1CKxrO" - }, - "source": [ - "## Overview\n", - "\n", - "The `bigframes.ml` module implements Scikit-Learn's machine learning API in\n", - "BigQuery DataFrames. It exposes BigQuery's ML capabilities in a simple, popular\n", - "API that works seamlessly with the rest of the BigQuery DataFrames API.\n", - "\n", - "Learn more about [BigQuery DataFrames](https://cloud.google.com/python/docs/reference/bigframes/latest)." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "d975e698c9a4" - }, - "source": [ - "### Objective\n", - "\n", - "In this tutorial, you will walk through an end-to-end machine learning workflow using BigQuery DataFrames. You will load data, manipulate and prepare it for model training, build supervised and unsupervised models, and evaluate and save a model for future use; all using built-in BigQuery DataFrames functionality." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "08d289fa873f" - }, - "source": [ - "### Dataset\n", - "\n", - "This tutorial uses the [```penguins``` table](https://console.cloud.google.com/bigquery?p=bigquery-public-data&d=ml_datasets&t=penguins) (a BigQuery public dataset), which contains data on a set of penguins including species, island of residence, weight, culmen length and depth, flipper length, and sex." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "aed92deeb4a0" - }, - "source": [ - "### Costs\n", - "\n", - "This tutorial uses billable components of Google Cloud:\n", - "\n", - "* BigQuery (storage and compute)\n", - "* BigQuery ML\n", - "\n", - "Learn about [BigQuery storage pricing](https://cloud.google.com/bigquery/pricing#storage),\n", - "[BigQuery compute pricing](https://cloud.google.com/bigquery/pricing#analysis_pricing_models),\n", - "and [BigQuery ML pricing](https://cloud.google.com/bigquery/pricing#bqml),\n", - "and use the [Pricing Calculator](https://cloud.google.com/products/calculator/)\n", - "to generate a cost estimate based on your projected usage." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "i7EUnXsZhAGF" - }, - "source": [ - "## Installation\n", - "\n", - "Depending on your Jupyter environment, you might have to install packages." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "NRTcBQPZpKWd" - }, - "source": [ - "**Vertex AI Workbench or Colab**\n", - "\n", - "Do nothing, BigQuery DataFrames package is already installed." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "bdOJtFo1pRnc" - }, - "source": [ - "**Local JupyterLab instance**\n", - "\n", - "Uncomment and run the following cell:" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": { - "id": "mfPoOwPLGpSr" - }, - "outputs": [], - "source": [ - "# !pip install bigframes" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "BF1j6f9HApxa" - }, - "source": [ - "## Before you begin\n", - "\n", - "Complete the tasks in this section to set up your environment." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "Yq7zKYWelRQP" - }, - "source": [ - "### Set up your Google Cloud project\n", - "\n", - "**The following steps are required, regardless of your notebook environment.**\n", - "\n", - "1. [Select or create a Google Cloud project](https://console.cloud.google.com/cloud-resource-manager). When you first create an account, you get a $300 credit towards your compute/storage costs.\n", - "\n", - "2. [Make sure that billing is enabled for your project](https://cloud.google.com/billing/docs/how-to/modify-project).\n", - "\n", - "3. [Click here](https://console.cloud.google.com/flows/enableapi?apiid=bigquery.googleapis.com) to enable the BigQuery API.\n", - "\n", - "4. If you are running this notebook locally, install the [Cloud SDK](https://cloud.google.com/sdk)." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "WReHDGG5g0XY" - }, - "source": [ - "#### Set your project ID\n", - "\n", - "If you don't know your project ID, try the following:\n", - "* Run `gcloud config list`.\n", - "* Run `gcloud projects list`.\n", - "* See the support page: [Locate the project ID](https://support.google.com/googleapi/answer/7014113)." - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": { - "id": "oM1iC_MfAts1" - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Updated property [core/project].\n" - ] - } - ], - "source": [ - "PROJECT_ID = \"\" # @param {type:\"string\"}\n", - "\n", - "# Set the project id\n", - "! gcloud config set project {PROJECT_ID}" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "region" - }, - "source": [ - "#### Set the region\n", - "\n", - "You can also change the `REGION` variable used by BigQuery. Learn more about [BigQuery regions](https://cloud.google.com/bigquery/docs/locations#supported_locations)." - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": { - "id": "eF-Twtc4XGem" - }, - "outputs": [], - "source": [ - "REGION = \"US\" # @param {type: \"string\"}" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "XcW9adriUQRc" - }, - "source": [ - "#### Set the dataset ID\n", - "\n", - "As part of this notebook, you will save BigQuery ML models to your Google Cloud project, which requires a dataset. Create the dataset, if needed, and provide the ID here as the `DATASET` variable used by BigQuery. Learn how to create a [BigQuery dataset](https://cloud.google.com/bigquery/docs/datasets)." - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": { - "id": "BbMh9JHvUHAn" - }, - "outputs": [], - "source": [ - "DATASET = \"\" # @param {type: \"string\"}" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "NwxfWoR5UGwO" - }, - "source": [] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "sBCra4QMA2wR" - }, - "source": [ - "### Authenticate your Google Cloud account\n", - "\n", - "Depending on your Jupyter environment, you might have to manually authenticate. Follow the relevant instructions below." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "74ccc9e52986" - }, - "source": [ - "**Vertex AI Workbench**\n", - "\n", - "Do nothing, you are already authenticated." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "de775a3773ba" - }, - "source": [ - "**Local JupyterLab instance**\n", - "\n", - "Uncomment and run the following cell:" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": { - "id": "254614fa0c46" - }, - "outputs": [], - "source": [ - "# ! gcloud auth login" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "ef21552ccea8" - }, - "source": [ - "**Colab**\n", - "\n", - "Uncomment and run the following cell:" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": { - "id": "603adbbf0532" - }, - "outputs": [], - "source": [ - "# from google.colab import auth\n", - "# auth.authenticate_user()" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "960505627ddf" - }, - "source": [ - "### Import libraries" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": { - "id": "PyQmSRbKA8r-" - }, - "outputs": [], - "source": [ - "import bigframes.pandas as bpd" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "init_aip:mbsdk,all" - }, - "source": [ - "\n", - "### Set BigQuery DataFrames options" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "metadata": { - "id": "NPPMuw2PXGeo" - }, - "outputs": [], - "source": [ - "# Note: The project option is not required in all environments.\n", - "# On BigQuery Studio, the project ID is automatically detected.\n", - "bpd.options.bigquery.project = PROJECT_ID\n", - "\n", - "# Note: The location option is not required.\n", - "# It defaults to the location of the first table or query\n", - "# passed to read_gbq(). For APIs where a location can't be\n", - "# auto-detected, the location defaults to the \"US\" location.\n", - "bpd.options.bigquery.location = REGION" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "pDfrKwMKE_dK" - }, - "source": [ - "If you want to reset the location of the created DataFrame or Series objects, reset the session by executing `bpd.reset_session()`. After that, you can reuse `bpd.options.bigquery.location` to specify another location." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "LjfRpSruzg5j" - }, - "source": [ - "## Import data into BigQuery DataFrames\n", - "\n", - "You can create a DataFrame by reading data from a BigQuery table." - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "metadata": { - "id": "d86W4hNqzZJb" - }, - "outputs": [], - "source": [ - "df = bpd.read_gbq(\"bigquery-public-data.ml_datasets.penguins\")\n", - "df = df.dropna()\n", - "\n", - "# BigQuery DataFrames creates a default numbered index, which we can give a name\n", - "df.index.name = \"penguin_id\"" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "pDfCJ6-LkRB1" - }, - "source": [ - "Take a look at a few rows of the DataFrame:" - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "metadata": { - "id": "arGaUZVWkSwT" - }, - "outputs": [ - { - "data": { - "text/html": [ - "Query job d3acda60-1059-4bb0-9912-ed374491c5c3 is DONE. 28.9 kB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job 51c6aa1c-ff98-4805-921e-00830e125e56 is DONE. 0 Bytes processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job 01e2cb6d-604b-4cdd-afb0-8f515a9da951 is DONE. 501 Bytes processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "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", - "
speciesislandculmen_length_mmculmen_depth_mmflipper_length_mmbody_mass_gsex
penguin_id
0Gentoo penguin (Pygoscelis papua)Biscoe50.515.9225.05400.0MALE
1Gentoo penguin (Pygoscelis papua)Biscoe45.114.5215.05000.0FEMALE
2Adelie Penguin (Pygoscelis adeliae)Torgersen41.418.5202.03875.0MALE
3Adelie Penguin (Pygoscelis adeliae)Torgersen38.617.0188.02900.0FEMALE
4Gentoo penguin (Pygoscelis papua)Biscoe46.514.8217.05200.0FEMALE
\n", - "

5 rows × 7 columns

\n", - "
[5 rows x 7 columns in total]" - ], - "text/plain": [ - " species island culmen_length_mm \\\n", - "penguin_id \n", - "0 Gentoo penguin (Pygoscelis papua) Biscoe 50.5 \n", - "1 Gentoo penguin (Pygoscelis papua) Biscoe 45.1 \n", - "2 Adelie Penguin (Pygoscelis adeliae) Torgersen 41.4 \n", - "3 Adelie Penguin (Pygoscelis adeliae) Torgersen 38.6 \n", - "4 Gentoo penguin (Pygoscelis papua) Biscoe 46.5 \n", - "\n", - " culmen_depth_mm flipper_length_mm body_mass_g sex \n", - "penguin_id \n", - "0 15.9 225.0 5400.0 MALE \n", - "1 14.5 215.0 5000.0 FEMALE \n", - "2 18.5 202.0 3875.0 MALE \n", - "3 17.0 188.0 2900.0 FEMALE \n", - "4 14.8 217.0 5200.0 FEMALE \n", - "\n", - "[5 rows x 7 columns]" - ] - }, - "execution_count": 11, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "df.head()" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "WkUIcMXPkahu" - }, - "source": [ - "## Clean and prepare data" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "DScncEoDkiTG" - }, - "source": [ - "We're are going to start with supervised learning, where a Linear Regression model will learn to predict the body mass (output variable `y`) using input features such as flipper length, sex, species, and more (features `X`)." - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "metadata": { - "id": "B9mW93o9z_-L" - }, - "outputs": [], - "source": [ - "# Isolate input features and output variable into DataFrames\n", - "X = df[['island', 'culmen_length_mm', 'culmen_depth_mm', 'flipper_length_mm', 'sex', 'species']]\n", - "y = df[['body_mass_g']]" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "wkw0Cs62k_cl" - }, - "source": [ - "Part of preparing data for a machine learning task is splitting it into subsets for training and testing to ensure that the solution is not overfitting. By default, BQML will automatically manage splitting the data for you. However, BQML also supports manually splitting out your training data.\n", - "\n", - "Performing a manual data split can be done with `bigframes.ml.model_selection.train_test_split` like so:" - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "metadata": { - "id": "NysWAWmvlAxB" - }, - "outputs": [ - { - "data": { - "text/html": [ - "Query job 7bd14e04-b3b4-4281-b5be-187f7baad62f is DONE. 28.9 kB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job 240cc7db-19ac-4bd3-8e76-a79f75ded077 is DONE. 28.9 kB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job 91194fee-d9b9-4cb9-a469-e49e9d77c624 is DONE. 28.9 kB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job 84c71647-956b-4385-8dce-c8bc70a917c8 is DONE. 28.9 kB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job 9c94600b-2231-4d04-8e3a-fb46f8892b6a is DONE. 28.9 kB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "X_train shape: (267, 6)\n", - "X_test shape: (67, 6)\n", - "y_train shape: (267, 1)\n", - "y_test shape: (67, 1)\n" - ] - } - ], - "source": [ - "from bigframes.ml.model_selection import train_test_split\n", - "\n", - "# This will split X and y into test and training sets, with 20% of the rows in the test set,\n", - "# and the rest in the training set\n", - "X_train, X_test, y_train, y_test = train_test_split(\n", - " X, y, test_size=0.2)\n", - "\n", - "# Show the shape of the data after the split\n", - "print(f\"\"\"X_train shape: {X_train.shape}\n", - "X_test shape: {X_test.shape}\n", - "y_train shape: {y_train.shape}\n", - "y_test shape: {y_test.shape}\"\"\")" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "faFnVnNolydu" - }, - "source": [ - "If we look at the data, we can see that random rows were selected for\n", - "each side of the split:" - ] - }, - { - "cell_type": "code", - "execution_count": 14, - "metadata": { - "id": "f8bz1HwLlyLP" - }, - "outputs": [ - { - "data": { - "text/html": [ - "Query job 8ad534c1-eb49-4616-b7a6-f7d8b044b8bf is DONE. 28.9 kB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job 3793de66-fb3c-4ca4-a337-aa708c718cc5 is DONE. 0 Bytes processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job 66524afb-4509-4927-8902-4a72826e83c4 is DONE. 456 Bytes processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "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", - "
islandculmen_length_mmculmen_depth_mmflipper_length_mmsexspecies
penguin_id
188Dream51.518.7187.0MALEChinstrap penguin (Pygoscelis antarctica)
251Biscoe49.516.1224.0MALEGentoo penguin (Pygoscelis papua)
231Biscoe45.713.9214.0FEMALEGentoo penguin (Pygoscelis papua)
271Biscoe59.617.0230.0MALEGentoo penguin (Pygoscelis papua)
128Biscoe38.817.2180.0MALEAdelie Penguin (Pygoscelis adeliae)
\n", - "

5 rows × 6 columns

\n", - "
[5 rows x 6 columns in total]" - ], - "text/plain": [ - " island culmen_length_mm culmen_depth_mm flipper_length_mm \\\n", - "penguin_id \n", - "188 Dream 51.5 18.7 187.0 \n", - "251 Biscoe 49.5 16.1 224.0 \n", - "231 Biscoe 45.7 13.9 214.0 \n", - "271 Biscoe 59.6 17.0 230.0 \n", - "128 Biscoe 38.8 17.2 180.0 \n", - "\n", - " sex species \n", - "penguin_id \n", - "188 MALE Chinstrap penguin (Pygoscelis antarctica) \n", - "251 MALE Gentoo penguin (Pygoscelis papua) \n", - "231 FEMALE Gentoo penguin (Pygoscelis papua) \n", - "271 MALE Gentoo penguin (Pygoscelis papua) \n", - "128 MALE Adelie Penguin (Pygoscelis adeliae) \n", - "\n", - "[5 rows x 6 columns]" - ] - }, - "execution_count": 14, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "X_test.head(5)" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "v4ic7GQEl67Y" - }, - "source": [ - "Note that the `y_test` data matches the same rows in `X_test`:" - ] - }, - { - "cell_type": "code", - "execution_count": 15, - "metadata": { - "id": "PflbhKGkl8v2" - }, - "outputs": [ - { - "data": { - "text/html": [ - "Query job 6a87fcc2-f2d0-44f5-8ab2-08f109c2b70d is DONE. 28.9 kB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job ed8e49f8-0f4c-4ef2-bbc2-b8c5ef9fd064 is DONE. 0 Bytes processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job 97fea642-03aa-49fd-943e-f4efa5a87f0f is DONE. 120 Bytes processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "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", - "
body_mass_g
penguin_id
1883250.0
2515650.0
2314400.0
2716050.0
1283800.0
\n", - "

5 rows × 1 columns

\n", - "
[5 rows x 1 columns in total]" - ], - "text/plain": [ - " body_mass_g\n", - "penguin_id \n", - "188 3250.0\n", - "251 5650.0\n", - "231 4400.0\n", - "271 6050.0\n", - "128 3800.0\n", - "\n", - "[5 rows x 1 columns]" - ] - }, - "execution_count": 15, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "y_test.head(5)" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "Dkf52IdvmSaj" - }, - "source": [ - "## Estimators\n", - "\n", - "Following scikit-learn, all learning components are \"estimators\"; objects that can learn from training data and then apply themselves to new data. Estimators share the following patterns:\n", - "\n", - "- a constructor that takes a list of parameters\n", - "- a standard string representation that shows the class name and all non-default parameters, e.g. `LinearRegression(fit_intercept=False)`\n", - "- a `.fit(..)` method to fit the estimator to training data\n", - "\n", - "There estimators can be further broken down into two main subtypes:\n", - " 1. Transformers\n", - " 2. Predictors\n", - "\n", - "Let's walk through each of these with our example model." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "55oNSWQ2Q5te" - }, - "source": [ - "### Transformers\n", - "\n", - "Transformers are estimators that are used to prepare data for consumption by other estimators ('preprocessing'). In addition to `.fit(...)`, the transformer implements a `.transform(...)` method, which will apply a transformation based on what was computed during `.fit(..)`. With this pattern dynamic preprocessing steps can be applied to both training and test/production data consistently.\n", - "\n", - "An example of a transformer is `bigframes.ml.preprocessing.StandardScaler`, which rescales a dataset to have a mean of zero and a standard deviation of one:" - ] - }, - { - "cell_type": "code", - "execution_count": 16, - "metadata": { - "id": "yhATDMR-mkdF" - }, - "outputs": [ - { - "data": { - "text/html": [ - "Query job aee64759-42bb-44d6-b8c7-1c737cdd6eed is DONE. 28.9 kB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job acb29d04-a20d-4f1c-8d90-51c7e8ac9922 is DONE. 0 Bytes processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job 2bd034db-7d9b-467c-be17-49bca094cceb is DONE. 28.9 kB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job 5dfb583a-1ced-4f2a-94b9-f1282263134d is DONE. 2.1 kB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job 8fe87288-4a95-49f4-9895-7c41c1004901 is DONE. 0 Bytes processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job 7ebcecee-beff-402d-ac71-6384014a54da is DONE. 8.5 kB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "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", - "
standard_scaled_culmen_length_mmstandard_scaled_culmen_depth_mmstandard_scaled_flipper_length_mm
penguin_id
01.20778-0.6515311.772656
2-0.4556020.6628550.100476
3-0.967412-0.095445-0.917372
40.476623-1.2076171.191028
5-1.6254540.359535-0.626559
7-0.345929-1.864810.682104
80.842202-1.5614911.409139
90.3486710.865068-0.263041
100.9335961.2189410.827511
11-1.460943-0.297658-0.771966
121.317454-0.4493181.409139
13-0.236255-1.7637040.900214
140.549739-0.297658-0.626559
160.970154-1.0054041.481842
17-1.058807-0.348211-0.190338
181.354012-1.5109371.263732
19-0.053466-1.6625971.191028
20-0.199697-1.5109370.609401
211.1529430.763962-0.190338
22-1.2050380.308982-0.699262
24-0.7846231.775028-0.699262
25-0.839461.724474-0.771966
26-0.6201130.359535-0.990076
270.330392-0.095445-0.408448
292.194842-0.0954451.990767
\n", - "

25 rows × 3 columns

\n", - "
[267 rows x 3 columns in total]" - ], - "text/plain": [ - " standard_scaled_culmen_length_mm standard_scaled_culmen_depth_mm \\\n", - "penguin_id \n", - "0 1.20778 -0.651531 \n", - "2 -0.455602 0.662855 \n", - "3 -0.967412 -0.095445 \n", - "4 0.476623 -1.207617 \n", - "5 -1.625454 0.359535 \n", - "7 -0.345929 -1.86481 \n", - "8 0.842202 -1.561491 \n", - "9 0.348671 0.865068 \n", - "10 0.933596 1.218941 \n", - "11 -1.460943 -0.297658 \n", - "12 1.317454 -0.449318 \n", - "13 -0.236255 -1.763704 \n", - "14 0.549739 -0.297658 \n", - "16 0.970154 -1.005404 \n", - "17 -1.058807 -0.348211 \n", - "18 1.354012 -1.510937 \n", - "19 -0.053466 -1.662597 \n", - "20 -0.199697 -1.510937 \n", - "21 1.152943 0.763962 \n", - "22 -1.205038 0.308982 \n", - "24 -0.784623 1.775028 \n", - "25 -0.83946 1.724474 \n", - "26 -0.620113 0.359535 \n", - "27 0.330392 -0.095445 \n", - "29 2.194842 -0.095445 \n", - "\n", - " standard_scaled_flipper_length_mm \n", - "penguin_id \n", - "0 1.772656 \n", - "2 0.100476 \n", - "3 -0.917372 \n", - "4 1.191028 \n", - "5 -0.626559 \n", - "7 0.682104 \n", - "8 1.409139 \n", - "9 -0.263041 \n", - "10 0.827511 \n", - "11 -0.771966 \n", - "12 1.409139 \n", - "13 0.900214 \n", - "14 -0.626559 \n", - "16 1.481842 \n", - "17 -0.190338 \n", - "18 1.263732 \n", - "19 1.191028 \n", - "20 0.609401 \n", - "21 -0.190338 \n", - "22 -0.699262 \n", - "24 -0.699262 \n", - "25 -0.771966 \n", - "26 -0.990076 \n", - "27 -0.408448 \n", - "29 1.990767 \n", - "...\n", - "\n", - "[267 rows x 3 columns]" - ] - }, - "execution_count": 16, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "from bigframes.ml.preprocessing import StandardScaler\n", - "\n", - "# StandardScaler will only work on numeric columns\n", - "numeric_columns = [\"culmen_length_mm\", \"culmen_depth_mm\", \"flipper_length_mm\"]\n", - "\n", - "scaler = StandardScaler()\n", - "scaler.fit(X_train[numeric_columns])\n", - "\n", - "# Now, standardscaler should transform the numbers to have mean of zero\n", - "# and standard deviation of one:\n", - "scaler.transform(X_train[numeric_columns])" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "vhywHzH-ml-W" - }, - "source": [ - "We can then repeat this transformation on the test data:" - ] - }, - { - "cell_type": "code", - "execution_count": 17, - "metadata": { - "id": "TfwSLOTXmspI" - }, - "outputs": [ - { - "data": { - "text/html": [ - "Query job 6639e06d-3920-4c64-84d8-b40ce042188c is DONE. 28.9 kB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job 579dfb14-6d39-44c0-9b92-eb6a40c46df8 is DONE. 536 Bytes processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job 7f613d94-a68c-42d5-8afe-0413b32de3a0 is DONE. 0 Bytes processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job 140e8b5f-a24b-43a3-831f-30a29a4bd7ea is DONE. 2.1 kB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "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", - "
standard_scaled_culmen_length_mmstandard_scaled_culmen_depth_mmstandard_scaled_flipper_length_mm
penguin_id
10.220718-1.3592771.045621
15-0.5104390.157322-0.771966
28-1.0588070.713408-0.771966
321.4636851.1683880.39129
33-0.2545340.056215-0.990076
34-0.5104390.4606420.318587
371.3540120.511195-0.263041
41-0.674949-0.095445-1.789814
47-1.1684810.662855-0.117634
520.4583440.308982-0.699262
56-1.0405280.460642-1.135483
57-0.9674120.005662-0.117634
620.988433-0.7526381.191028
651.7561481.3706010.318587
670.677691-1.3592771.045621
75-1.1136441.421155-0.771966
810.6776910.561748-0.408448
89-0.8577390.713408-0.771966
92-0.8029020.308982-0.917372
93-0.3093711.168388-0.263041
96-0.3093710.662855-1.499
100-0.9125760.814515-0.771966
1010.549739-1.3087241.554546
102-0.1265820.662855-0.626559
1071.20778-1.0054041.118325
\n", - "

25 rows × 3 columns

\n", - "
[67 rows x 3 columns in total]" - ], - "text/plain": [ - " standard_scaled_culmen_length_mm standard_scaled_culmen_depth_mm \\\n", - "penguin_id \n", - "1 0.220718 -1.359277 \n", - "15 -0.510439 0.157322 \n", - "28 -1.058807 0.713408 \n", - "32 1.463685 1.168388 \n", - "33 -0.254534 0.056215 \n", - "34 -0.510439 0.460642 \n", - "37 1.354012 0.511195 \n", - "41 -0.674949 -0.095445 \n", - "47 -1.168481 0.662855 \n", - "52 0.458344 0.308982 \n", - "56 -1.040528 0.460642 \n", - "57 -0.967412 0.005662 \n", - "62 0.988433 -0.752638 \n", - "65 1.756148 1.370601 \n", - "67 0.677691 -1.359277 \n", - "75 -1.113644 1.421155 \n", - "81 0.677691 0.561748 \n", - "89 -0.857739 0.713408 \n", - "92 -0.802902 0.308982 \n", - "93 -0.309371 1.168388 \n", - "96 -0.309371 0.662855 \n", - "100 -0.912576 0.814515 \n", - "101 0.549739 -1.308724 \n", - "102 -0.126582 0.662855 \n", - "107 1.20778 -1.005404 \n", - "\n", - " standard_scaled_flipper_length_mm \n", - "penguin_id \n", - "1 1.045621 \n", - "15 -0.771966 \n", - "28 -0.771966 \n", - "32 0.39129 \n", - "33 -0.990076 \n", - "34 0.318587 \n", - "37 -0.263041 \n", - "41 -1.789814 \n", - "47 -0.117634 \n", - "52 -0.699262 \n", - "56 -1.135483 \n", - "57 -0.117634 \n", - "62 1.191028 \n", - "65 0.318587 \n", - "67 1.045621 \n", - "75 -0.771966 \n", - "81 -0.408448 \n", - "89 -0.771966 \n", - "92 -0.917372 \n", - "93 -0.263041 \n", - "96 -1.499 \n", - "100 -0.771966 \n", - "101 1.554546 \n", - "102 -0.626559 \n", - "107 1.118325 \n", - "...\n", - "\n", - "[67 rows x 3 columns]" - ] - }, - "execution_count": 17, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "scaler.transform(X_test[numeric_columns])" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "9enAdjzPmwmv" - }, - "source": [ - "#### Composing transformers\n", - "\n", - "To process data where different columns need different preprocessors, `bigframes.composition.ColumnTransformer` can be employed.\n", - "\n", - "Let's create an aggregate transform that applies `StandardScalar` to the numeric columns and `OneHotEncoder` to the string columns." - ] - }, - { - "cell_type": "code", - "execution_count": 18, - "metadata": { - "id": "I8Wwx3emmz2J" - }, - "outputs": [ - { - "data": { - "text/html": [ - "Query job c16fdb5d-3f18-4f85-8a31-705ef4680be5 is DONE. 28.9 kB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job 8c94a7c1-7f12-44be-b389-7c854ceead4b is DONE. 0 Bytes processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job 1287628d-1380-4495-a5e9-6806440206bc is DONE. 22.9 kB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job 03163e1a-c789-4046-b71a-b4b4e7bbc043 is DONE. 2.1 kB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job 86f39b30-00db-4ada-8699-0fe49c94eb2d is DONE. 29.2 kB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job d5b0e8b0-12cd-47f6-85d2-806b2c252d37 is DONE. 536 Bytes processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job 459cdc90-d1f3-4580-9137-9b93d44ca991 is DONE. 0 Bytes processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job 80d10913-7263-44e6-89f7-719eac4158a3 is DONE. 21.4 kB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "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", - "
onehotencoded_islandstandard_scaled_culmen_length_mmstandard_scaled_culmen_depth_mmstandard_scaled_flipper_length_mmonehotencoded_sexonehotencoded_species
penguin_id
0[{'index': 1, 'value': 1.0}]1.20778-0.6515311.772656[{'index': 3, 'value': 1.0}][{'index': 3, 'value': 1.0}]
2[{'index': 3, 'value': 1.0}]-0.4556020.6628550.100476[{'index': 3, 'value': 1.0}][{'index': 1, 'value': 1.0}]
3[{'index': 3, 'value': 1.0}]-0.967412-0.095445-0.917372[{'index': 2, 'value': 1.0}][{'index': 1, 'value': 1.0}]
4[{'index': 1, 'value': 1.0}]0.476623-1.2076171.191028[{'index': 2, 'value': 1.0}][{'index': 3, 'value': 1.0}]
5[{'index': 1, 'value': 1.0}]-1.6254540.359535-0.626559[{'index': 2, 'value': 1.0}][{'index': 1, 'value': 1.0}]
7[{'index': 1, 'value': 1.0}]-0.345929-1.864810.682104[{'index': 2, 'value': 1.0}][{'index': 3, 'value': 1.0}]
8[{'index': 1, 'value': 1.0}]0.842202-1.5614911.409139[{'index': 3, 'value': 1.0}][{'index': 3, 'value': 1.0}]
9[{'index': 3, 'value': 1.0}]0.3486710.865068-0.263041[{'index': 3, 'value': 1.0}][{'index': 1, 'value': 1.0}]
10[{'index': 2, 'value': 1.0}]0.9335961.2189410.827511[{'index': 3, 'value': 1.0}][{'index': 2, 'value': 1.0}]
11[{'index': 3, 'value': 1.0}]-1.460943-0.297658-0.771966[{'index': 2, 'value': 1.0}][{'index': 1, 'value': 1.0}]
12[{'index': 1, 'value': 1.0}]1.317454-0.4493181.409139[{'index': 3, 'value': 1.0}][{'index': 3, 'value': 1.0}]
13[{'index': 1, 'value': 1.0}]-0.236255-1.7637040.900214[{'index': 2, 'value': 1.0}][{'index': 3, 'value': 1.0}]
14[{'index': 2, 'value': 1.0}]0.549739-0.297658-0.626559[{'index': 2, 'value': 1.0}][{'index': 2, 'value': 1.0}]
16[{'index': 1, 'value': 1.0}]0.970154-1.0054041.481842[{'index': 3, 'value': 1.0}][{'index': 3, 'value': 1.0}]
17[{'index': 1, 'value': 1.0}]-1.058807-0.348211-0.190338[{'index': 2, 'value': 1.0}][{'index': 1, 'value': 1.0}]
18[{'index': 1, 'value': 1.0}]1.354012-1.5109371.263732[{'index': 3, 'value': 1.0}][{'index': 3, 'value': 1.0}]
19[{'index': 1, 'value': 1.0}]-0.053466-1.6625971.191028[{'index': 2, 'value': 1.0}][{'index': 3, 'value': 1.0}]
20[{'index': 1, 'value': 1.0}]-0.199697-1.5109370.609401[{'index': 2, 'value': 1.0}][{'index': 3, 'value': 1.0}]
21[{'index': 2, 'value': 1.0}]1.1529430.763962-0.190338[{'index': 2, 'value': 1.0}][{'index': 2, 'value': 1.0}]
22[{'index': 2, 'value': 1.0}]-1.2050380.308982-0.699262[{'index': 2, 'value': 1.0}][{'index': 1, 'value': 1.0}]
24[{'index': 1, 'value': 1.0}]-0.7846231.775028-0.699262[{'index': 2, 'value': 1.0}][{'index': 1, 'value': 1.0}]
25[{'index': 3, 'value': 1.0}]-0.839461.724474-0.771966[{'index': 3, 'value': 1.0}][{'index': 1, 'value': 1.0}]
26[{'index': 1, 'value': 1.0}]-0.6201130.359535-0.990076[{'index': 2, 'value': 1.0}][{'index': 1, 'value': 1.0}]
27[{'index': 2, 'value': 1.0}]0.330392-0.095445-0.408448[{'index': 2, 'value': 1.0}][{'index': 2, 'value': 1.0}]
29[{'index': 1, 'value': 1.0}]2.194842-0.0954451.990767[{'index': 3, 'value': 1.0}][{'index': 3, 'value': 1.0}]
\n", - "

25 rows × 6 columns

\n", - "
[267 rows x 6 columns in total]" - ], - "text/plain": [ - " onehotencoded_island standard_scaled_culmen_length_mm \\\n", - "penguin_id \n", - "0 [{'index': 1, 'value': 1.0}] 1.20778 \n", - "2 [{'index': 3, 'value': 1.0}] -0.455602 \n", - "3 [{'index': 3, 'value': 1.0}] -0.967412 \n", - "4 [{'index': 1, 'value': 1.0}] 0.476623 \n", - "5 [{'index': 1, 'value': 1.0}] -1.625454 \n", - "7 [{'index': 1, 'value': 1.0}] -0.345929 \n", - "8 [{'index': 1, 'value': 1.0}] 0.842202 \n", - "9 [{'index': 3, 'value': 1.0}] 0.348671 \n", - "10 [{'index': 2, 'value': 1.0}] 0.933596 \n", - "11 [{'index': 3, 'value': 1.0}] -1.460943 \n", - "12 [{'index': 1, 'value': 1.0}] 1.317454 \n", - "13 [{'index': 1, 'value': 1.0}] -0.236255 \n", - "14 [{'index': 2, 'value': 1.0}] 0.549739 \n", - "16 [{'index': 1, 'value': 1.0}] 0.970154 \n", - "17 [{'index': 1, 'value': 1.0}] -1.058807 \n", - "18 [{'index': 1, 'value': 1.0}] 1.354012 \n", - "19 [{'index': 1, 'value': 1.0}] -0.053466 \n", - "20 [{'index': 1, 'value': 1.0}] -0.199697 \n", - "21 [{'index': 2, 'value': 1.0}] 1.152943 \n", - "22 [{'index': 2, 'value': 1.0}] -1.205038 \n", - "24 [{'index': 1, 'value': 1.0}] -0.784623 \n", - "25 [{'index': 3, 'value': 1.0}] -0.83946 \n", - "26 [{'index': 1, 'value': 1.0}] -0.620113 \n", - "27 [{'index': 2, 'value': 1.0}] 0.330392 \n", - "29 [{'index': 1, 'value': 1.0}] 2.194842 \n", - "\n", - " standard_scaled_culmen_depth_mm \\\n", - "penguin_id \n", - "0 -0.651531 \n", - "2 0.662855 \n", - "3 -0.095445 \n", - "4 -1.207617 \n", - "5 0.359535 \n", - "7 -1.86481 \n", - "8 -1.561491 \n", - "9 0.865068 \n", - "10 1.218941 \n", - "11 -0.297658 \n", - "12 -0.449318 \n", - "13 -1.763704 \n", - "14 -0.297658 \n", - "16 -1.005404 \n", - "17 -0.348211 \n", - "18 -1.510937 \n", - "19 -1.662597 \n", - "20 -1.510937 \n", - "21 0.763962 \n", - "22 0.308982 \n", - "24 1.775028 \n", - "25 1.724474 \n", - "26 0.359535 \n", - "27 -0.095445 \n", - "29 -0.095445 \n", - "\n", - " standard_scaled_flipper_length_mm onehotencoded_sex \\\n", - "penguin_id \n", - "0 1.772656 [{'index': 3, 'value': 1.0}] \n", - "2 0.100476 [{'index': 3, 'value': 1.0}] \n", - "3 -0.917372 [{'index': 2, 'value': 1.0}] \n", - "4 1.191028 [{'index': 2, 'value': 1.0}] \n", - "5 -0.626559 [{'index': 2, 'value': 1.0}] \n", - "7 0.682104 [{'index': 2, 'value': 1.0}] \n", - "8 1.409139 [{'index': 3, 'value': 1.0}] \n", - "9 -0.263041 [{'index': 3, 'value': 1.0}] \n", - "10 0.827511 [{'index': 3, 'value': 1.0}] \n", - "11 -0.771966 [{'index': 2, 'value': 1.0}] \n", - "12 1.409139 [{'index': 3, 'value': 1.0}] \n", - "13 0.900214 [{'index': 2, 'value': 1.0}] \n", - "14 -0.626559 [{'index': 2, 'value': 1.0}] \n", - "16 1.481842 [{'index': 3, 'value': 1.0}] \n", - "17 -0.190338 [{'index': 2, 'value': 1.0}] \n", - "18 1.263732 [{'index': 3, 'value': 1.0}] \n", - "19 1.191028 [{'index': 2, 'value': 1.0}] \n", - "20 0.609401 [{'index': 2, 'value': 1.0}] \n", - "21 -0.190338 [{'index': 2, 'value': 1.0}] \n", - "22 -0.699262 [{'index': 2, 'value': 1.0}] \n", - "24 -0.699262 [{'index': 2, 'value': 1.0}] \n", - "25 -0.771966 [{'index': 3, 'value': 1.0}] \n", - "26 -0.990076 [{'index': 2, 'value': 1.0}] \n", - "27 -0.408448 [{'index': 2, 'value': 1.0}] \n", - "29 1.990767 [{'index': 3, 'value': 1.0}] \n", - "\n", - " onehotencoded_species \n", - "penguin_id \n", - "0 [{'index': 3, 'value': 1.0}] \n", - "2 [{'index': 1, 'value': 1.0}] \n", - "3 [{'index': 1, 'value': 1.0}] \n", - "4 [{'index': 3, 'value': 1.0}] \n", - "5 [{'index': 1, 'value': 1.0}] \n", - "7 [{'index': 3, 'value': 1.0}] \n", - "8 [{'index': 3, 'value': 1.0}] \n", - "9 [{'index': 1, 'value': 1.0}] \n", - "10 [{'index': 2, 'value': 1.0}] \n", - "11 [{'index': 1, 'value': 1.0}] \n", - "12 [{'index': 3, 'value': 1.0}] \n", - "13 [{'index': 3, 'value': 1.0}] \n", - "14 [{'index': 2, 'value': 1.0}] \n", - "16 [{'index': 3, 'value': 1.0}] \n", - "17 [{'index': 1, 'value': 1.0}] \n", - "18 [{'index': 3, 'value': 1.0}] \n", - "19 [{'index': 3, 'value': 1.0}] \n", - "20 [{'index': 3, 'value': 1.0}] \n", - "21 [{'index': 2, 'value': 1.0}] \n", - "22 [{'index': 1, 'value': 1.0}] \n", - "24 [{'index': 1, 'value': 1.0}] \n", - "25 [{'index': 1, 'value': 1.0}] \n", - "26 [{'index': 1, 'value': 1.0}] \n", - "27 [{'index': 2, 'value': 1.0}] \n", - "29 [{'index': 3, 'value': 1.0}] \n", - "...\n", - "\n", - "[267 rows x 6 columns]" - ] - }, - "execution_count": 18, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "from bigframes.ml.compose import ColumnTransformer\n", - "from bigframes.ml.preprocessing import OneHotEncoder\n", - "\n", - "# Create an aggregate transform that applies StandardScaler to the numeric columns,\n", - "# and OneHotEncoder to the string columns\n", - "preproc = ColumnTransformer([\n", - " (\"scale\", StandardScaler(), [\"culmen_length_mm\", \"culmen_depth_mm\", \"flipper_length_mm\"]),\n", - " (\"encode\", OneHotEncoder(), [\"species\", \"sex\", \"island\"])])\n", - "\n", - "# Now we can fit all columns of the training data\n", - "preproc.fit(X_train)\n", - "\n", - "processed_X_train = preproc.transform(X_train)\n", - "processed_X_test = preproc.transform(X_test)\n", - "\n", - "# View the processed training data\n", - "processed_X_train" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "JhoO4fctm4Q5" - }, - "source": [ - "### Predictors\n", - "\n", - "Predictors are estimators that learn and make predictions. In addition to `.fit(...)`, the predictor implements a `.predict(...)` method, which will use what was learned during `.fit(...)` to predict some output.\n", - "\n", - "Predictors can be further broken down into two categories:\n", - "* Supervised predictors\n", - "* Unsupervised predictors" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "TqLItVyjslP8" - }, - "source": [ - "#### Supervised predictors\n", - "\n", - "Supervised learning is when we train a model on input-output pairs, and then ask it to predict the output for new inputs. An example of such a predictor is `bigframes.ml.linear_models.LinearRegression`." - ] - }, - { - "cell_type": "code", - "execution_count": 19, - "metadata": { - "id": "ZeloMmopm8KI" - }, - "outputs": [ - { - "data": { - "text/html": [ - "Query job a59bf4cc-4c92-4a68-96b1-7465fbcb3ed0 is DONE. 21.4 kB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job 6860c534-a218-4a55-866d-a6e011399cd9 is DONE. 28.9 kB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job 1b3e8da6-2d64-4337-872e-55b874f00596 is DONE. 0 Bytes processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job fc118469-8dd7-4187-a3c1-7c5c2f1c5e36 is DONE. 5.7 kB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job 544c5453-cd10-4a08-a338-601d85142df8 is DONE. 536 Bytes processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job 41c82cc9-7268-40ae-a736-f7a5f2c8b413 is DONE. 0 Bytes processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job e9836f6b-160d-4ce4-88b6-0b04f40a1549 is DONE. 5.9 kB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "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", - "
predicted_body_mass_gonehotencoded_islandstandard_scaled_culmen_length_mmstandard_scaled_culmen_depth_mmstandard_scaled_flipper_length_mmonehotencoded_sexonehotencoded_species
penguin_id
14772.376044[{'index': 1, 'value': 1.0}]0.220718-1.3592771.045621[{'index': 2, 'value': 1.0}][{'index': 3, 'value': 1.0}]
153883.373922[{'index': 2, 'value': 1.0}]-0.5104390.157322-0.771966[{'index': 3, 'value': 1.0}][{'index': 1, 'value': 1.0}]
283479.709088[{'index': 2, 'value': 1.0}]-1.0588070.713408-0.771966[{'index': 2, 'value': 1.0}][{'index': 1, 'value': 1.0}]
324223.853626[{'index': 2, 'value': 1.0}]1.4636851.1683880.39129[{'index': 3, 'value': 1.0}][{'index': 2, 'value': 1.0}]
333197.623474[{'index': 2, 'value': 1.0}]-0.2545340.056215-0.990076[{'index': 2, 'value': 1.0}][{'index': 2, 'value': 1.0}]
344155.26742[{'index': 2, 'value': 1.0}]-0.5104390.4606420.318587[{'index': 3, 'value': 1.0}][{'index': 1, 'value': 1.0}]
373991.314095[{'index': 2, 'value': 1.0}]1.3540120.511195-0.263041[{'index': 3, 'value': 1.0}][{'index': 2, 'value': 1.0}]
413232.648242[{'index': 3, 'value': 1.0}]-0.674949-0.095445-1.789814[{'index': 2, 'value': 1.0}][{'index': 1, 'value': 1.0}]
474017.740788[{'index': 2, 'value': 1.0}]-1.1684810.662855-0.117634[{'index': 3, 'value': 1.0}][{'index': 1, 'value': 1.0}]
523365.080596[{'index': 2, 'value': 1.0}]0.4583440.308982-0.699262[{'index': 2, 'value': 1.0}][{'index': 2, 'value': 1.0}]
563791.332002[{'index': 1, 'value': 1.0}]-1.0405280.460642-1.135483[{'index': 3, 'value': 1.0}][{'index': 1, 'value': 1.0}]
573547.892992[{'index': 1, 'value': 1.0}]-0.9674120.005662-0.117634[{'index': 2, 'value': 1.0}][{'index': 1, 'value': 1.0}]
625372.087702[{'index': 1, 'value': 1.0}]0.988433-0.7526381.191028[{'index': 3, 'value': 1.0}][{'index': 3, 'value': 1.0}]
654263.232169[{'index': 2, 'value': 1.0}]1.7561481.3706010.318587[{'index': 3, 'value': 1.0}][{'index': 2, 'value': 1.0}]
675234.45894[{'index': 1, 'value': 1.0}]0.677691-1.3592771.045621[{'index': 3, 'value': 1.0}][{'index': 3, 'value': 1.0}]
753979.314516[{'index': 1, 'value': 1.0}]-1.1136441.421155-0.771966[{'index': 3, 'value': 1.0}][{'index': 1, 'value': 1.0}]
813481.331391[{'index': 2, 'value': 1.0}]0.6776910.561748-0.408448[{'index': 2, 'value': 1.0}][{'index': 2, 'value': 1.0}]
893915.240555[{'index': 2, 'value': 1.0}]-0.8577390.713408-0.771966[{'index': 3, 'value': 1.0}][{'index': 1, 'value': 1.0}]
923425.563946[{'index': 2, 'value': 1.0}]-0.8029020.308982-0.917372[{'index': 2, 'value': 1.0}][{'index': 1, 'value': 1.0}]
934141.497717[{'index': 1, 'value': 1.0}]-0.3093711.168388-0.263041[{'index': 3, 'value': 1.0}][{'index': 1, 'value': 1.0}]
963394.72289[{'index': 2, 'value': 1.0}]-0.3093710.662855-1.499[{'index': 2, 'value': 1.0}][{'index': 1, 'value': 1.0}]
1003507.226918[{'index': 2, 'value': 1.0}]-0.9125760.814515-0.771966[{'index': 2, 'value': 1.0}][{'index': 1, 'value': 1.0}]
1014922.286202[{'index': 1, 'value': 1.0}]0.549739-1.3087241.554546[{'index': 2, 'value': 1.0}][{'index': 3, 'value': 1.0}]
1024016.243221[{'index': 2, 'value': 1.0}]-0.1265820.662855-0.626559[{'index': 3, 'value': 1.0}][{'index': 1, 'value': 1.0}]
1074933.655362[{'index': 1, 'value': 1.0}]1.20778-1.0054041.118325[{'index': 2, 'value': 1.0}][{'index': 3, 'value': 1.0}]
\n", - "

25 rows × 7 columns

\n", - "
[67 rows x 7 columns in total]" - ], - "text/plain": [ - " predicted_body_mass_g onehotencoded_island \\\n", - "penguin_id \n", - "1 4772.376044 [{'index': 1, 'value': 1.0}] \n", - "15 3883.373922 [{'index': 2, 'value': 1.0}] \n", - "28 3479.709088 [{'index': 2, 'value': 1.0}] \n", - "32 4223.853626 [{'index': 2, 'value': 1.0}] \n", - "33 3197.623474 [{'index': 2, 'value': 1.0}] \n", - "34 4155.26742 [{'index': 2, 'value': 1.0}] \n", - "37 3991.314095 [{'index': 2, 'value': 1.0}] \n", - "41 3232.648242 [{'index': 3, 'value': 1.0}] \n", - "47 4017.740788 [{'index': 2, 'value': 1.0}] \n", - "52 3365.080596 [{'index': 2, 'value': 1.0}] \n", - "56 3791.332002 [{'index': 1, 'value': 1.0}] \n", - "57 3547.892992 [{'index': 1, 'value': 1.0}] \n", - "62 5372.087702 [{'index': 1, 'value': 1.0}] \n", - "65 4263.232169 [{'index': 2, 'value': 1.0}] \n", - "67 5234.45894 [{'index': 1, 'value': 1.0}] \n", - "75 3979.314516 [{'index': 1, 'value': 1.0}] \n", - "81 3481.331391 [{'index': 2, 'value': 1.0}] \n", - "89 3915.240555 [{'index': 2, 'value': 1.0}] \n", - "92 3425.563946 [{'index': 2, 'value': 1.0}] \n", - "93 4141.497717 [{'index': 1, 'value': 1.0}] \n", - "96 3394.72289 [{'index': 2, 'value': 1.0}] \n", - "100 3507.226918 [{'index': 2, 'value': 1.0}] \n", - "101 4922.286202 [{'index': 1, 'value': 1.0}] \n", - "102 4016.243221 [{'index': 2, 'value': 1.0}] \n", - "107 4933.655362 [{'index': 1, 'value': 1.0}] \n", - "\n", - " standard_scaled_culmen_length_mm standard_scaled_culmen_depth_mm \\\n", - "penguin_id \n", - "1 0.220718 -1.359277 \n", - "15 -0.510439 0.157322 \n", - "28 -1.058807 0.713408 \n", - "32 1.463685 1.168388 \n", - "33 -0.254534 0.056215 \n", - "34 -0.510439 0.460642 \n", - "37 1.354012 0.511195 \n", - "41 -0.674949 -0.095445 \n", - "47 -1.168481 0.662855 \n", - "52 0.458344 0.308982 \n", - "56 -1.040528 0.460642 \n", - "57 -0.967412 0.005662 \n", - "62 0.988433 -0.752638 \n", - "65 1.756148 1.370601 \n", - "67 0.677691 -1.359277 \n", - "75 -1.113644 1.421155 \n", - "81 0.677691 0.561748 \n", - "89 -0.857739 0.713408 \n", - "92 -0.802902 0.308982 \n", - "93 -0.309371 1.168388 \n", - "96 -0.309371 0.662855 \n", - "100 -0.912576 0.814515 \n", - "101 0.549739 -1.308724 \n", - "102 -0.126582 0.662855 \n", - "107 1.20778 -1.005404 \n", - "\n", - " standard_scaled_flipper_length_mm onehotencoded_sex \\\n", - "penguin_id \n", - "1 1.045621 [{'index': 2, 'value': 1.0}] \n", - "15 -0.771966 [{'index': 3, 'value': 1.0}] \n", - "28 -0.771966 [{'index': 2, 'value': 1.0}] \n", - "32 0.39129 [{'index': 3, 'value': 1.0}] \n", - "33 -0.990076 [{'index': 2, 'value': 1.0}] \n", - "34 0.318587 [{'index': 3, 'value': 1.0}] \n", - "37 -0.263041 [{'index': 3, 'value': 1.0}] \n", - "41 -1.789814 [{'index': 2, 'value': 1.0}] \n", - "47 -0.117634 [{'index': 3, 'value': 1.0}] \n", - "52 -0.699262 [{'index': 2, 'value': 1.0}] \n", - "56 -1.135483 [{'index': 3, 'value': 1.0}] \n", - "57 -0.117634 [{'index': 2, 'value': 1.0}] \n", - "62 1.191028 [{'index': 3, 'value': 1.0}] \n", - "65 0.318587 [{'index': 3, 'value': 1.0}] \n", - "67 1.045621 [{'index': 3, 'value': 1.0}] \n", - "75 -0.771966 [{'index': 3, 'value': 1.0}] \n", - "81 -0.408448 [{'index': 2, 'value': 1.0}] \n", - "89 -0.771966 [{'index': 3, 'value': 1.0}] \n", - "92 -0.917372 [{'index': 2, 'value': 1.0}] \n", - "93 -0.263041 [{'index': 3, 'value': 1.0}] \n", - "96 -1.499 [{'index': 2, 'value': 1.0}] \n", - "100 -0.771966 [{'index': 2, 'value': 1.0}] \n", - "101 1.554546 [{'index': 2, 'value': 1.0}] \n", - "102 -0.626559 [{'index': 3, 'value': 1.0}] \n", - "107 1.118325 [{'index': 2, 'value': 1.0}] \n", - "\n", - " onehotencoded_species \n", - "penguin_id \n", - "1 [{'index': 3, 'value': 1.0}] \n", - "15 [{'index': 1, 'value': 1.0}] \n", - "28 [{'index': 1, 'value': 1.0}] \n", - "32 [{'index': 2, 'value': 1.0}] \n", - "33 [{'index': 2, 'value': 1.0}] \n", - "34 [{'index': 1, 'value': 1.0}] \n", - "37 [{'index': 2, 'value': 1.0}] \n", - "41 [{'index': 1, 'value': 1.0}] \n", - "47 [{'index': 1, 'value': 1.0}] \n", - "52 [{'index': 2, 'value': 1.0}] \n", - "56 [{'index': 1, 'value': 1.0}] \n", - "57 [{'index': 1, 'value': 1.0}] \n", - "62 [{'index': 3, 'value': 1.0}] \n", - "65 [{'index': 2, 'value': 1.0}] \n", - "67 [{'index': 3, 'value': 1.0}] \n", - "75 [{'index': 1, 'value': 1.0}] \n", - "81 [{'index': 2, 'value': 1.0}] \n", - "89 [{'index': 1, 'value': 1.0}] \n", - "92 [{'index': 1, 'value': 1.0}] \n", - "93 [{'index': 1, 'value': 1.0}] \n", - "96 [{'index': 1, 'value': 1.0}] \n", - "100 [{'index': 1, 'value': 1.0}] \n", - "101 [{'index': 3, 'value': 1.0}] \n", - "102 [{'index': 1, 'value': 1.0}] \n", - "107 [{'index': 3, 'value': 1.0}] \n", - "\n", - "[67 rows x 7 columns]" - ] - }, - "execution_count": 19, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "from bigframes.ml.linear_model import LinearRegression\n", - "\n", - "linreg = LinearRegression()\n", - "\n", - "# Learn from the training data how to predict output y\n", - "linreg.fit(processed_X_train, y_train)\n", - "\n", - "# Predict y for the test data\n", - "predicted_y_test = linreg.predict(processed_X_test)\n", - "\n", - "# View predictions\n", - "predicted_y_test" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "z42qesW_nAIf" - }, - "source": [ - "#### Unsupervised predictors\n", - "\n", - "In unsupervised learning, there are no known outputs in the training data, instead the model learns on input data alone and predicts something else. An example of an unsupervised predictor is `bigframes.ml.cluster.KMeans`, which learns how to fit input data to a target number of clusters." - ] - }, - { - "cell_type": "code", - "execution_count": 20, - "metadata": { - "id": "M13zd02znCIg" - }, - "outputs": [ - { - "data": { - "text/html": [ - "Query job 728068d3-2349-4636-a030-016b500a9812 is DONE. 23.5 kB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job 37bac685-2afa-4ece-b3a3-e0b84a92c65f is DONE. 0 Bytes processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job 38416629-4615-45f5-9e27-d9164124f755 is DONE. 6.2 kB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job 0241ea1c-8d96-418a-b3d6-08d819854954 is DONE. 536 Bytes processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job 405bcf9b-d652-42f3-931e-12ca0310fe4f is DONE. 0 Bytes processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job 21ca6f31-2ea2-4f71-b030-c738bf5afe27 is DONE. 10.2 kB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "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", - "
CENTROID_IDNEAREST_CENTROIDS_DISTANCEonehotencoded_islandstandard_scaled_culmen_length_mmstandard_scaled_culmen_depth_mmstandard_scaled_flipper_length_mmonehotencoded_sexonehotencoded_species
penguin_id
13[{'CENTROID_ID': 3, 'DISTANCE': 0.857057881337...[{'index': 1, 'value': 1.0}]0.220718-1.3592771.045621[{'index': 2, 'value': 1.0}][{'index': 3, 'value': 1.0}]
154[{'CENTROID_ID': 4, 'DISTANCE': 1.181613302004...[{'index': 2, 'value': 1.0}]-0.5104390.157322-0.771966[{'index': 3, 'value': 1.0}][{'index': 1, 'value': 1.0}]
281[{'CENTROID_ID': 1, 'DISTANCE': 1.006856853050...[{'index': 2, 'value': 1.0}]-1.0588070.713408-0.771966[{'index': 2, 'value': 1.0}][{'index': 1, 'value': 1.0}]
322[{'CENTROID_ID': 2, 'DISTANCE': 1.237504384283...[{'index': 2, 'value': 1.0}]1.4636851.1683880.39129[{'index': 3, 'value': 1.0}][{'index': 2, 'value': 1.0}]
332[{'CENTROID_ID': 2, 'DISTANCE': 1.656439702919...[{'index': 2, 'value': 1.0}]-0.2545340.056215-0.990076[{'index': 2, 'value': 1.0}][{'index': 2, 'value': 1.0}]
344[{'CENTROID_ID': 4, 'DISTANCE': 1.343792119214...[{'index': 2, 'value': 1.0}]-0.5104390.4606420.318587[{'index': 3, 'value': 1.0}][{'index': 1, 'value': 1.0}]
372[{'CENTROID_ID': 2, 'DISTANCE': 0.816670297369...[{'index': 2, 'value': 1.0}]1.3540120.511195-0.263041[{'index': 3, 'value': 1.0}][{'index': 2, 'value': 1.0}]
411[{'CENTROID_ID': 1, 'DISTANCE': 1.317560921596...[{'index': 3, 'value': 1.0}]-0.674949-0.095445-1.789814[{'index': 2, 'value': 1.0}][{'index': 1, 'value': 1.0}]
474[{'CENTROID_ID': 4, 'DISTANCE': 1.135112005343...[{'index': 2, 'value': 1.0}]-1.1684810.662855-0.117634[{'index': 3, 'value': 1.0}][{'index': 1, 'value': 1.0}]
522[{'CENTROID_ID': 2, 'DISTANCE': 1.004096945181...[{'index': 2, 'value': 1.0}]0.4583440.308982-0.699262[{'index': 2, 'value': 1.0}][{'index': 2, 'value': 1.0}]
564[{'CENTROID_ID': 4, 'DISTANCE': 1.218648668822...[{'index': 1, 'value': 1.0}]-1.0405280.460642-1.135483[{'index': 3, 'value': 1.0}][{'index': 1, 'value': 1.0}]
571[{'CENTROID_ID': 1, 'DISTANCE': 1.238466630273...[{'index': 1, 'value': 1.0}]-0.9674120.005662-0.117634[{'index': 2, 'value': 1.0}][{'index': 1, 'value': 1.0}]
623[{'CENTROID_ID': 3, 'DISTANCE': 0.876984617451...[{'index': 1, 'value': 1.0}]0.988433-0.7526381.191028[{'index': 3, 'value': 1.0}][{'index': 3, 'value': 1.0}]
652[{'CENTROID_ID': 2, 'DISTANCE': 1.439604004538...[{'index': 2, 'value': 1.0}]1.7561481.3706010.318587[{'index': 3, 'value': 1.0}][{'index': 2, 'value': 1.0}]
673[{'CENTROID_ID': 3, 'DISTANCE': 0.763112987694...[{'index': 1, 'value': 1.0}]0.677691-1.3592771.045621[{'index': 3, 'value': 1.0}][{'index': 3, 'value': 1.0}]
754[{'CENTROID_ID': 4, 'DISTANCE': 1.075788925734...[{'index': 1, 'value': 1.0}]-1.1136441.421155-0.771966[{'index': 3, 'value': 1.0}][{'index': 1, 'value': 1.0}]
812[{'CENTROID_ID': 2, 'DISTANCE': 0.777307801541...[{'index': 2, 'value': 1.0}]0.6776910.561748-0.408448[{'index': 2, 'value': 1.0}][{'index': 2, 'value': 1.0}]
894[{'CENTROID_ID': 4, 'DISTANCE': 0.891303183824...[{'index': 2, 'value': 1.0}]-0.8577390.713408-0.771966[{'index': 3, 'value': 1.0}][{'index': 1, 'value': 1.0}]
921[{'CENTROID_ID': 1, 'DISTANCE': 0.934676470689...[{'index': 2, 'value': 1.0}]-0.8029020.308982-0.917372[{'index': 2, 'value': 1.0}][{'index': 1, 'value': 1.0}]
934[{'CENTROID_ID': 4, 'DISTANCE': 0.984620018517...[{'index': 1, 'value': 1.0}]-0.3093711.168388-0.263041[{'index': 3, 'value': 1.0}][{'index': 1, 'value': 1.0}]
961[{'CENTROID_ID': 1, 'DISTANCE': 1.446939975674...[{'index': 2, 'value': 1.0}]-0.3093710.662855-1.499[{'index': 2, 'value': 1.0}][{'index': 1, 'value': 1.0}]
1001[{'CENTROID_ID': 1, 'DISTANCE': 1.101117711572...[{'index': 2, 'value': 1.0}]-0.9125760.814515-0.771966[{'index': 2, 'value': 1.0}][{'index': 1, 'value': 1.0}]
1013[{'CENTROID_ID': 3, 'DISTANCE': 0.823832007899...[{'index': 1, 'value': 1.0}]0.549739-1.3087241.554546[{'index': 2, 'value': 1.0}][{'index': 3, 'value': 1.0}]
1024[{'CENTROID_ID': 4, 'DISTANCE': 0.995348310182...[{'index': 2, 'value': 1.0}]-0.1265820.662855-0.626559[{'index': 3, 'value': 1.0}][{'index': 1, 'value': 1.0}]
1073[{'CENTROID_ID': 3, 'DISTANCE': 0.930021405831...[{'index': 1, 'value': 1.0}]1.20778-1.0054041.118325[{'index': 2, 'value': 1.0}][{'index': 3, 'value': 1.0}]
\n", - "

25 rows × 8 columns

\n", - "
[67 rows x 8 columns in total]" - ], - "text/plain": [ - " CENTROID_ID NEAREST_CENTROIDS_DISTANCE \\\n", - "penguin_id \n", - "1 3 [{'CENTROID_ID': 3, 'DISTANCE': 0.857057881337... \n", - "15 4 [{'CENTROID_ID': 4, 'DISTANCE': 1.181613302004... \n", - "28 1 [{'CENTROID_ID': 1, 'DISTANCE': 1.006856853050... \n", - "32 2 [{'CENTROID_ID': 2, 'DISTANCE': 1.237504384283... \n", - "33 2 [{'CENTROID_ID': 2, 'DISTANCE': 1.656439702919... \n", - "34 4 [{'CENTROID_ID': 4, 'DISTANCE': 1.343792119214... \n", - "37 2 [{'CENTROID_ID': 2, 'DISTANCE': 0.816670297369... \n", - "41 1 [{'CENTROID_ID': 1, 'DISTANCE': 1.317560921596... \n", - "47 4 [{'CENTROID_ID': 4, 'DISTANCE': 1.135112005343... \n", - "52 2 [{'CENTROID_ID': 2, 'DISTANCE': 1.004096945181... \n", - "56 4 [{'CENTROID_ID': 4, 'DISTANCE': 1.218648668822... \n", - "57 1 [{'CENTROID_ID': 1, 'DISTANCE': 1.238466630273... \n", - "62 3 [{'CENTROID_ID': 3, 'DISTANCE': 0.876984617451... \n", - "65 2 [{'CENTROID_ID': 2, 'DISTANCE': 1.439604004538... \n", - "67 3 [{'CENTROID_ID': 3, 'DISTANCE': 0.763112987694... \n", - "75 4 [{'CENTROID_ID': 4, 'DISTANCE': 1.075788925734... \n", - "81 2 [{'CENTROID_ID': 2, 'DISTANCE': 0.777307801541... \n", - "89 4 [{'CENTROID_ID': 4, 'DISTANCE': 0.891303183824... \n", - "92 1 [{'CENTROID_ID': 1, 'DISTANCE': 0.934676470689... \n", - "93 4 [{'CENTROID_ID': 4, 'DISTANCE': 0.984620018517... \n", - "96 1 [{'CENTROID_ID': 1, 'DISTANCE': 1.446939975674... \n", - "100 1 [{'CENTROID_ID': 1, 'DISTANCE': 1.101117711572... \n", - "101 3 [{'CENTROID_ID': 3, 'DISTANCE': 0.823832007899... \n", - "102 4 [{'CENTROID_ID': 4, 'DISTANCE': 0.995348310182... \n", - "107 3 [{'CENTROID_ID': 3, 'DISTANCE': 0.930021405831... \n", - "\n", - " onehotencoded_island standard_scaled_culmen_length_mm \\\n", - "penguin_id \n", - "1 [{'index': 1, 'value': 1.0}] 0.220718 \n", - "15 [{'index': 2, 'value': 1.0}] -0.510439 \n", - "28 [{'index': 2, 'value': 1.0}] -1.058807 \n", - "32 [{'index': 2, 'value': 1.0}] 1.463685 \n", - "33 [{'index': 2, 'value': 1.0}] -0.254534 \n", - "34 [{'index': 2, 'value': 1.0}] -0.510439 \n", - "37 [{'index': 2, 'value': 1.0}] 1.354012 \n", - "41 [{'index': 3, 'value': 1.0}] -0.674949 \n", - "47 [{'index': 2, 'value': 1.0}] -1.168481 \n", - "52 [{'index': 2, 'value': 1.0}] 0.458344 \n", - "56 [{'index': 1, 'value': 1.0}] -1.040528 \n", - "57 [{'index': 1, 'value': 1.0}] -0.967412 \n", - "62 [{'index': 1, 'value': 1.0}] 0.988433 \n", - "65 [{'index': 2, 'value': 1.0}] 1.756148 \n", - "67 [{'index': 1, 'value': 1.0}] 0.677691 \n", - "75 [{'index': 1, 'value': 1.0}] -1.113644 \n", - "81 [{'index': 2, 'value': 1.0}] 0.677691 \n", - "89 [{'index': 2, 'value': 1.0}] -0.857739 \n", - "92 [{'index': 2, 'value': 1.0}] -0.802902 \n", - "93 [{'index': 1, 'value': 1.0}] -0.309371 \n", - "96 [{'index': 2, 'value': 1.0}] -0.309371 \n", - "100 [{'index': 2, 'value': 1.0}] -0.912576 \n", - "101 [{'index': 1, 'value': 1.0}] 0.549739 \n", - "102 [{'index': 2, 'value': 1.0}] -0.126582 \n", - "107 [{'index': 1, 'value': 1.0}] 1.20778 \n", - "\n", - " standard_scaled_culmen_depth_mm \\\n", - "penguin_id \n", - "1 -1.359277 \n", - "15 0.157322 \n", - "28 0.713408 \n", - "32 1.168388 \n", - "33 0.056215 \n", - "34 0.460642 \n", - "37 0.511195 \n", - "41 -0.095445 \n", - "47 0.662855 \n", - "52 0.308982 \n", - "56 0.460642 \n", - "57 0.005662 \n", - "62 -0.752638 \n", - "65 1.370601 \n", - "67 -1.359277 \n", - "75 1.421155 \n", - "81 0.561748 \n", - "89 0.713408 \n", - "92 0.308982 \n", - "93 1.168388 \n", - "96 0.662855 \n", - "100 0.814515 \n", - "101 -1.308724 \n", - "102 0.662855 \n", - "107 -1.005404 \n", - "\n", - " standard_scaled_flipper_length_mm onehotencoded_sex \\\n", - "penguin_id \n", - "1 1.045621 [{'index': 2, 'value': 1.0}] \n", - "15 -0.771966 [{'index': 3, 'value': 1.0}] \n", - "28 -0.771966 [{'index': 2, 'value': 1.0}] \n", - "32 0.39129 [{'index': 3, 'value': 1.0}] \n", - "33 -0.990076 [{'index': 2, 'value': 1.0}] \n", - "34 0.318587 [{'index': 3, 'value': 1.0}] \n", - "37 -0.263041 [{'index': 3, 'value': 1.0}] \n", - "41 -1.789814 [{'index': 2, 'value': 1.0}] \n", - "47 -0.117634 [{'index': 3, 'value': 1.0}] \n", - "52 -0.699262 [{'index': 2, 'value': 1.0}] \n", - "56 -1.135483 [{'index': 3, 'value': 1.0}] \n", - "57 -0.117634 [{'index': 2, 'value': 1.0}] \n", - "62 1.191028 [{'index': 3, 'value': 1.0}] \n", - "65 0.318587 [{'index': 3, 'value': 1.0}] \n", - "67 1.045621 [{'index': 3, 'value': 1.0}] \n", - "75 -0.771966 [{'index': 3, 'value': 1.0}] \n", - "81 -0.408448 [{'index': 2, 'value': 1.0}] \n", - "89 -0.771966 [{'index': 3, 'value': 1.0}] \n", - "92 -0.917372 [{'index': 2, 'value': 1.0}] \n", - "93 -0.263041 [{'index': 3, 'value': 1.0}] \n", - "96 -1.499 [{'index': 2, 'value': 1.0}] \n", - "100 -0.771966 [{'index': 2, 'value': 1.0}] \n", - "101 1.554546 [{'index': 2, 'value': 1.0}] \n", - "102 -0.626559 [{'index': 3, 'value': 1.0}] \n", - "107 1.118325 [{'index': 2, 'value': 1.0}] \n", - "\n", - " onehotencoded_species \n", - "penguin_id \n", - "1 [{'index': 3, 'value': 1.0}] \n", - "15 [{'index': 1, 'value': 1.0}] \n", - "28 [{'index': 1, 'value': 1.0}] \n", - "32 [{'index': 2, 'value': 1.0}] \n", - "33 [{'index': 2, 'value': 1.0}] \n", - "34 [{'index': 1, 'value': 1.0}] \n", - "37 [{'index': 2, 'value': 1.0}] \n", - "41 [{'index': 1, 'value': 1.0}] \n", - "47 [{'index': 1, 'value': 1.0}] \n", - "52 [{'index': 2, 'value': 1.0}] \n", - "56 [{'index': 1, 'value': 1.0}] \n", - "57 [{'index': 1, 'value': 1.0}] \n", - "62 [{'index': 3, 'value': 1.0}] \n", - "65 [{'index': 2, 'value': 1.0}] \n", - "67 [{'index': 3, 'value': 1.0}] \n", - "75 [{'index': 1, 'value': 1.0}] \n", - "81 [{'index': 2, 'value': 1.0}] \n", - "89 [{'index': 1, 'value': 1.0}] \n", - "92 [{'index': 1, 'value': 1.0}] \n", - "93 [{'index': 1, 'value': 1.0}] \n", - "96 [{'index': 1, 'value': 1.0}] \n", - "100 [{'index': 1, 'value': 1.0}] \n", - "101 [{'index': 3, 'value': 1.0}] \n", - "102 [{'index': 1, 'value': 1.0}] \n", - "107 [{'index': 3, 'value': 1.0}] \n", - "\n", - "[67 rows x 8 columns]" - ] - }, - "execution_count": 20, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "from bigframes.ml.cluster import KMeans\n", - "\n", - "# Specify KMeans with four clusters\n", - "kmeans = KMeans(n_clusters=4)\n", - "\n", - "# Fit data\n", - "kmeans.fit(processed_X_train)\n", - "\n", - "# View predictions\n", - "kmeans.predict(processed_X_test)" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "DFwsIbscnEvh" - }, - "source": [ - "## Pipelines\n", - "\n", - "Transfomers and predictors can be chained into a single estimator component using `bigframes.ml.pipeline.Pipeline`:" - ] - }, - { - "cell_type": "code", - "execution_count": 21, - "metadata": { - "id": "Ku2OXqgJnEeR" - }, - "outputs": [ - { - "data": { - "text/plain": [ - "Pipeline(steps=[('preproc',\n", - " ColumnTransformer(transformers=[('scale', StandardScaler(),\n", - " ['culmen_length_mm',\n", - " 'culmen_depth_mm',\n", - " 'flipper_length_mm']),\n", - " ('encode', OneHotEncoder(),\n", - " ['species', 'sex',\n", - " 'island'])])),\n", - " ('linreg', LinearRegression())])" - ] - }, - "execution_count": 21, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "from bigframes.ml.pipeline import Pipeline\n", - "\n", - "pipeline = Pipeline([\n", - " ('preproc', preproc),\n", - " ('linreg', linreg)\n", - "])\n", - "\n", - "# Print our pipeline\n", - "pipeline" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "cCQCY_6wnKz_" - }, - "source": [ - "The pipeline simplifies the workflow by applying each of its component steps automatically:" - ] - }, - { - "cell_type": "code", - "execution_count": 22, - "metadata": { - "id": "hsF7FYagnMko" - }, - "outputs": [ - { - "data": { - "text/html": [ - "Query job 95b43592-b198-4f9e-a990-4e837b82121f is DONE. 24.8 kB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job 615b2afb-0c76-45d6-82c7-bde7c8b2b3a4 is DONE. 8.5 kB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job cf2ed3ca-01bf-4cb6-a71a-d6e30a8428f6 is DONE. 0 Bytes processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job d9780763-1d2b-494d-a778-20364c52bd08 is DONE. 29.6 kB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job f01296ba-7cd0-4d06-b25a-b5697e46bbf7 is DONE. 536 Bytes processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job 5b6fe451-2f8e-471e-a6a0-00b9bffaa826 is DONE. 0 Bytes processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job 6a81883b-0514-4251-9f63-490b6346bb8b is DONE. 6.1 kB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "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", - "
predicted_body_mass_gislandculmen_length_mmculmen_depth_mmflipper_length_mmsexspecies
penguin_id
14772.374547Biscoe45.114.5215.0FEMALEGentoo penguin (Pygoscelis papua)
153883.371052Dream41.117.5190.0MALEAdelie Penguin (Pygoscelis adeliae)
283479.706166Dream38.118.6190.0FEMALEAdelie Penguin (Pygoscelis adeliae)
324223.851137Dream51.919.5206.0MALEChinstrap penguin (Pygoscelis antarctica)
333197.620461Dream42.517.3187.0FEMALEChinstrap penguin (Pygoscelis antarctica)
344155.265191Dream41.118.1205.0MALEAdelie Penguin (Pygoscelis adeliae)
373991.311319Dream51.318.2197.0MALEChinstrap penguin (Pygoscelis antarctica)
413232.644783Torgersen40.217.0176.0FEMALEAdelie Penguin (Pygoscelis adeliae)
474017.738303Dream37.518.5199.0MALEAdelie Penguin (Pygoscelis adeliae)
523365.077659Dream46.417.8191.0FEMALEChinstrap penguin (Pygoscelis antarctica)
563791.328893Biscoe38.218.1185.0MALEAdelie Penguin (Pygoscelis adeliae)
573547.890609Biscoe38.617.2199.0FEMALEAdelie Penguin (Pygoscelis adeliae)
625372.086117Biscoe49.315.7217.0MALEGentoo penguin (Pygoscelis papua)
654263.229571Dream53.519.9205.0MALEChinstrap penguin (Pygoscelis antarctica)
675234.457401Biscoe47.614.5215.0MALEGentoo penguin (Pygoscelis papua)
753979.311469Biscoe37.820.0190.0MALEAdelie Penguin (Pygoscelis adeliae)
813481.328573Dream47.618.3195.0FEMALEChinstrap penguin (Pygoscelis antarctica)
893915.237615Dream39.218.6190.0MALEAdelie Penguin (Pygoscelis adeliae)
923425.560982Dream39.517.8188.0FEMALEAdelie Penguin (Pygoscelis adeliae)
934141.494969Biscoe42.219.5197.0MALEAdelie Penguin (Pygoscelis adeliae)
963394.719445Dream42.218.5180.0FEMALEAdelie Penguin (Pygoscelis adeliae)
1003507.223965Dream38.918.8190.0FEMALEAdelie Penguin (Pygoscelis adeliae)
1014922.284991Biscoe46.914.6222.0FEMALEGentoo penguin (Pygoscelis papua)
1024016.240318Dream43.218.5192.0MALEAdelie Penguin (Pygoscelis adeliae)
1074933.653758Biscoe50.515.2216.0FEMALEGentoo penguin (Pygoscelis papua)
\n", - "

25 rows × 7 columns

\n", - "
[67 rows x 7 columns in total]" - ], - "text/plain": [ - " predicted_body_mass_g island culmen_length_mm \\\n", - "penguin_id \n", - "1 4772.374547 Biscoe 45.1 \n", - "15 3883.371052 Dream 41.1 \n", - "28 3479.706166 Dream 38.1 \n", - "32 4223.851137 Dream 51.9 \n", - "33 3197.620461 Dream 42.5 \n", - "34 4155.265191 Dream 41.1 \n", - "37 3991.311319 Dream 51.3 \n", - "41 3232.644783 Torgersen 40.2 \n", - "47 4017.738303 Dream 37.5 \n", - "52 3365.077659 Dream 46.4 \n", - "56 3791.328893 Biscoe 38.2 \n", - "57 3547.890609 Biscoe 38.6 \n", - "62 5372.086117 Biscoe 49.3 \n", - "65 4263.229571 Dream 53.5 \n", - "67 5234.457401 Biscoe 47.6 \n", - "75 3979.311469 Biscoe 37.8 \n", - "81 3481.328573 Dream 47.6 \n", - "89 3915.237615 Dream 39.2 \n", - "92 3425.560982 Dream 39.5 \n", - "93 4141.494969 Biscoe 42.2 \n", - "96 3394.719445 Dream 42.2 \n", - "100 3507.223965 Dream 38.9 \n", - "101 4922.284991 Biscoe 46.9 \n", - "102 4016.240318 Dream 43.2 \n", - "107 4933.653758 Biscoe 50.5 \n", - "\n", - " culmen_depth_mm flipper_length_mm sex \\\n", - "penguin_id \n", - "1 14.5 215.0 FEMALE \n", - "15 17.5 190.0 MALE \n", - "28 18.6 190.0 FEMALE \n", - "32 19.5 206.0 MALE \n", - "33 17.3 187.0 FEMALE \n", - "34 18.1 205.0 MALE \n", - "37 18.2 197.0 MALE \n", - "41 17.0 176.0 FEMALE \n", - "47 18.5 199.0 MALE \n", - "52 17.8 191.0 FEMALE \n", - "56 18.1 185.0 MALE \n", - "57 17.2 199.0 FEMALE \n", - "62 15.7 217.0 MALE \n", - "65 19.9 205.0 MALE \n", - "67 14.5 215.0 MALE \n", - "75 20.0 190.0 MALE \n", - "81 18.3 195.0 FEMALE \n", - "89 18.6 190.0 MALE \n", - "92 17.8 188.0 FEMALE \n", - "93 19.5 197.0 MALE \n", - "96 18.5 180.0 FEMALE \n", - "100 18.8 190.0 FEMALE \n", - "101 14.6 222.0 FEMALE \n", - "102 18.5 192.0 MALE \n", - "107 15.2 216.0 FEMALE \n", - "\n", - " species \n", - "penguin_id \n", - "1 Gentoo penguin (Pygoscelis papua) \n", - "15 Adelie Penguin (Pygoscelis adeliae) \n", - "28 Adelie Penguin (Pygoscelis adeliae) \n", - "32 Chinstrap penguin (Pygoscelis antarctica) \n", - "33 Chinstrap penguin (Pygoscelis antarctica) \n", - "34 Adelie Penguin (Pygoscelis adeliae) \n", - "37 Chinstrap penguin (Pygoscelis antarctica) \n", - "41 Adelie Penguin (Pygoscelis adeliae) \n", - "47 Adelie Penguin (Pygoscelis adeliae) \n", - "52 Chinstrap penguin (Pygoscelis antarctica) \n", - "56 Adelie Penguin (Pygoscelis adeliae) \n", - "57 Adelie Penguin (Pygoscelis adeliae) \n", - "62 Gentoo penguin (Pygoscelis papua) \n", - "65 Chinstrap penguin (Pygoscelis antarctica) \n", - "67 Gentoo penguin (Pygoscelis papua) \n", - "75 Adelie Penguin (Pygoscelis adeliae) \n", - "81 Chinstrap penguin (Pygoscelis antarctica) \n", - "89 Adelie Penguin (Pygoscelis adeliae) \n", - "92 Adelie Penguin (Pygoscelis adeliae) \n", - "93 Adelie Penguin (Pygoscelis adeliae) \n", - "96 Adelie Penguin (Pygoscelis adeliae) \n", - "100 Adelie Penguin (Pygoscelis adeliae) \n", - "101 Gentoo penguin (Pygoscelis papua) \n", - "102 Adelie Penguin (Pygoscelis adeliae) \n", - "107 Gentoo penguin (Pygoscelis papua) \n", - "\n", - "[67 rows x 7 columns]" - ] - }, - "execution_count": 22, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "pipeline.fit(X_train, y_train)\n", - "\n", - "predicted_y_test = pipeline.predict(X_test)\n", - "predicted_y_test" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "SiLzpsg8nRXn" - }, - "source": [ - "In the backend, a pipeline will actually be compiled into a single model with an embedded TRANSFORM step." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "sTzAxTv1nUKZ" - }, - "source": [ - "## Evaluating results\n", - "\n", - "Some models include a convenient `.score(X, y)` method for evaulation with a preset accuracy metric:" - ] - }, - { - "cell_type": "code", - "execution_count": 23, - "metadata": { - "id": "Q8nR1ZqznU-B" - }, - "outputs": [ - { - "data": { - "text/html": [ - "Query job c098e1d1-b3ed-4ec5-94c7-6ba3b2b59e3f is DONE. 29.6 kB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job 035234b0-537a-44ce-adff-bb51c40b4ffa is DONE. 0 Bytes processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job b4a2a367-3e06-4fa3-9f00-bdbca884cfdd is DONE. 48 Bytes processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "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", - "
mean_absolute_errormean_squared_errormean_squared_log_errormedian_absolute_errorr2_scoreexplained_variance
0225.88351277765.9892810.004457179.5480410.8731660.873315
\n", - "

1 rows × 6 columns

\n", - "
[1 rows x 6 columns in total]" - ], - "text/plain": [ - " mean_absolute_error mean_squared_error mean_squared_log_error \\\n", - "0 225.883512 77765.989281 0.004457 \n", - "\n", - " median_absolute_error r2_score explained_variance \n", - "0 179.548041 0.873166 0.873315 \n", - "\n", - "[1 rows x 6 columns]" - ] - }, - "execution_count": 23, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# In the case of a pipeline, this will be equivalent to calling .score on the contained LinearRegression\n", - "pipeline.score(X_test, y_test)" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "UHM7jls6nY8A" - }, - "source": [ - "For a more general approach, the library `bigframes.ml.metrics` is provided:" - ] - }, - { - "cell_type": "code", - "execution_count": 24, - "metadata": { - "id": "vdEN4Ob9nan4" - }, - "outputs": [ - { - "data": { - "text/html": [ - "Query job 20ec1716-3e8e-4d3f-ba08-1f7b9970ce3f is DONE. 28.9 kB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job 6f628f3b-62df-4a5a-8e05-0b313db0ed07 is DONE. 28.9 kB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job c4eee1e5-146f-4a52-8499-83fe5f701f53 is DONE. 30.0 kB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/plain": [ - "0.8731660699616813" - ] - }, - "execution_count": 24, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "from bigframes.ml.metrics import r2_score\n", - "\n", - "r2_score(y_test, predicted_y_test[\"predicted_body_mass_g\"])" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "opn4ycPyneVh" - }, - "source": [ - "## Save to BigQuery\n", - "\n", - "Estimators can be saved to BigQuery as BQML models, and loaded again in future.\n", - "\n", - "Saving requires `bigquery.tables.create` permission, and loading requires `bigquery.models.getMetadata` permission.\n", - "These permissions can be at project level or the dataset level.\n", - "\n", - "If you have those permissions, please go ahead and uncomment the code in the following cells and run." - ] - }, - { - "cell_type": "code", - "execution_count": 25, - "metadata": { - "id": "fb0HpkdpnigJ" - }, - "outputs": [ - { - "data": { - "text/html": [ - "Copy job 06c2b62d-a7aa-46a5-a04a-2f189bafc5ee is DONE. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/plain": [ - "Pipeline(steps=[('transform',\n", - " ColumnTransformer(transformers=[('ont_hot_encoder',\n", - " OneHotEncoder(max_categories=1000001,\n", - " min_frequency=0),\n", - " 'island'),\n", - " ('standard_scaler',\n", - " StandardScaler(),\n", - " 'culmen_length_mm'),\n", - " ('standard_scaler',\n", - " StandardScaler(),\n", - " 'culmen_depth_mm'),\n", - " ('standard_scaler',\n", - " StandardScaler(),\n", - " 'flipper_length_mm'),\n", - " ('ont_hot_encoder',\n", - " OneHotEncoder(max_categories=1000001,\n", - " min_frequency=0),\n", - " 'sex'),\n", - " ('ont_hot_encoder',\n", - " OneHotEncoder(max_categories=1000001,\n", - " min_frequency=0),\n", - " 'species')])),\n", - " ('estimator',\n", - " LinearRegression(optimize_strategy='NORMAL_EQUATION'))])" - ] - }, - "execution_count": 25, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "linreg.to_gbq(f\"{DATASET}.penguins_model\", replace=True)" - ] - }, - { - "cell_type": "code", - "execution_count": 26, - "metadata": { - "id": "_zNOBlHdnkII" - }, - "outputs": [ - { - "data": { - "text/plain": [ - "Pipeline(steps=[('transform',\n", - " ColumnTransformer(transformers=[('ont_hot_encoder',\n", - " OneHotEncoder(max_categories=1000001,\n", - " min_frequency=0),\n", - " 'island'),\n", - " ('standard_scaler',\n", - " StandardScaler(),\n", - " 'culmen_length_mm'),\n", - " ('standard_scaler',\n", - " StandardScaler(),\n", - " 'culmen_depth_mm'),\n", - " ('standard_scaler',\n", - " StandardScaler(),\n", - " 'flipper_length_mm'),\n", - " ('ont_hot_encoder',\n", - " OneHotEncoder(max_categories=1000001,\n", - " min_frequency=0),\n", - " 'sex'),\n", - " ('ont_hot_encoder',\n", - " OneHotEncoder(max_categories=1000001,\n", - " min_frequency=0),\n", - " 'species')])),\n", - " ('estimator',\n", - " LinearRegression(optimize_strategy='NORMAL_EQUATION'))])" - ] - }, - "execution_count": 26, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "bpd.read_gbq_model(f\"{DATASET}.penguins_model\")" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "RfV-du5uTcBB" - }, - "source": [ - "We can also save the pipeline to BigQuery. BigQuery will save this as a single model, with the pre-processing steps embedded in the TRANSFORM property:" - ] - }, - { - "cell_type": "code", - "execution_count": 27, - "metadata": { - "id": "P76_TQ3IR6nB" - }, - "outputs": [ - { - "data": { - "text/html": [ - "Copy job a0ed8c1b-3a3f-4995-853c-e151d41560d7 is DONE. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/plain": [ - "Pipeline(steps=[('transform',\n", - " ColumnTransformer(transformers=[('ont_hot_encoder',\n", - " OneHotEncoder(max_categories=1000001,\n", - " min_frequency=0),\n", - " 'island'),\n", - " ('standard_scaler',\n", - " StandardScaler(),\n", - " 'culmen_length_mm'),\n", - " ('standard_scaler',\n", - " StandardScaler(),\n", - " 'culmen_depth_mm'),\n", - " ('standard_scaler',\n", - " StandardScaler(),\n", - " 'flipper_length_mm'),\n", - " ('ont_hot_encoder',\n", - " OneHotEncoder(max_categories=1000001,\n", - " min_frequency=0),\n", - " 'sex'),\n", - " ('ont_hot_encoder',\n", - " OneHotEncoder(max_categories=1000001,\n", - " min_frequency=0),\n", - " 'species')])),\n", - " ('estimator',\n", - " LinearRegression(optimize_strategy='NORMAL_EQUATION'))])" - ] - }, - "execution_count": 27, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "pipeline.to_gbq(f\"{DATASET}.penguins_pipeline\", replace=True)" - ] - }, - { - "cell_type": "code", - "execution_count": 28, - "metadata": { - "id": "GKvlKFjAbToJ" - }, - "outputs": [ - { - "data": { - "text/plain": [ - "Pipeline(steps=[('transform',\n", - " ColumnTransformer(transformers=[('ont_hot_encoder',\n", - " OneHotEncoder(max_categories=1000001,\n", - " min_frequency=0),\n", - " 'island'),\n", - " ('standard_scaler',\n", - " StandardScaler(),\n", - " 'culmen_length_mm'),\n", - " ('standard_scaler',\n", - " StandardScaler(),\n", - " 'culmen_depth_mm'),\n", - " ('standard_scaler',\n", - " StandardScaler(),\n", - " 'flipper_length_mm'),\n", - " ('ont_hot_encoder',\n", - " OneHotEncoder(max_categories=1000001,\n", - " min_frequency=0),\n", - " 'sex'),\n", - " ('ont_hot_encoder',\n", - " OneHotEncoder(max_categories=1000001,\n", - " min_frequency=0),\n", - " 'species')])),\n", - " ('estimator',\n", - " LinearRegression(optimize_strategy='NORMAL_EQUATION'))])" - ] - }, - "execution_count": 28, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "bpd.read_gbq_model(f\"{DATASET}.penguins_pipeline\")" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "wCsmt0IwFkDy" - }, - "source": [ - "## Summary and next steps\n", - "\n", - "You've completed an end-to-end machine learning workflow using the built-in capabilities of BigQuery DataFrames.\n", - "\n", - "Learn more about BigQuery DataFrames in the [documentation](https://cloud.google.com/python/docs/reference/bigframes/latest) and find more sample notebooks in the [GitHub repo](https://github.com/googleapis/python-bigquery-dataframes/tree/main/notebooks)." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "TpV-iwP9qw9c" - }, - "source": [ - "### Cleaning up\n", - "\n", - "To clean up all Google Cloud resources used in this project, you can [delete the Google Cloud\n", - "project](https://cloud.google.com/resource-manager/docs/creating-managing-projects#shutting_down_projects) you used for the tutorial.\n", - "\n", - "Otherwise, you can uncomment the remaining cells and run them to delete the individual resources you created in this tutorial:" - ] - }, - { - "cell_type": "code", - "execution_count": 29, - "metadata": { - "id": "QwumLUKmVpuH" - }, - "outputs": [], - "source": [ - "# # Delete the BQML models\n", - "# MODEL_NAME = f\"{PROJECT_ID}:{DATASET}.penguins_model\"\n", - "# ! bq rm -f --model {MODEL_NAME}\n", - "# PIPELINE_NAME = f\"{PROJECT_ID}:{DATASET}.penguins_pipeline\"\n", - "# ! bq rm -f --model {PIPELINE_NAME}" - ] - } - ], - "metadata": { - "colab": { - "provenance": [] - }, - "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.11.1" - } - }, - "nbformat": 4, - "nbformat_minor": 0 -} diff --git a/notebooks/getting_started/pandas_extensions.ipynb b/notebooks/getting_started/pandas_extensions.ipynb deleted file mode 100644 index c511eab9b4a..00000000000 --- a/notebooks/getting_started/pandas_extensions.ipynb +++ /dev/null @@ -1,160 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# BigQuery extension for pandas\n", - "\n", - "BigQuery DataFrames provides a pandas extension to execute BigQuery SQL scalar functions directly on pandas DataFrames." - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [], - "source": [ - "import pandas as pd\n", - "import bigframes # This import registers the bigquery accessor." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "By default, BigQuery DataFrames selects a location to process data based on the\n", - "data location, but using a pandas object doesn't provide such informat. If\n", - "processing location is important to you, configure the location before using the\n", - "accessor." - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [], - "source": [ - "import bigframes.pandas as bpd\n", - "\n", - "bpd.reset_session()\n", - "bpd.options.bigquery.location = \"US\"" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Using `sql_scalar`\n", - "\n", - "The `bigquery.sql_scalar` method allows you to apply a SQL scalar function to a pandas DataFrame by converting it to BigFrames, executing the SQL in BigQuery, and returning the result as a pandas Series." - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - " Query processed 0 Bytes in a moment of slot time.\n", - " " - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/plain": [ - "0 2.0\n", - "1 3.0\n", - "2 4.0\n", - "dtype: Float64" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "df = pd.DataFrame({\"a\": [1.5, 2.5, 3.5]})\n", - "result = df.bigquery.sql_scalar(\"ROUND({0}, 0)\")\n", - "result" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "You can also use multiple columns." - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - " Query processed 0 Bytes in a moment of slot time.\n", - " " - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/plain": [ - "0 11\n", - "1 22\n", - "2 33\n", - "dtype: Int64" - ] - }, - "execution_count": 4, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "df = pd.DataFrame({\"a\": [1, 2, 3], \"b\": [10, 20, 30]})\n", - "result = df.bigquery.sql_scalar(\"{a} + {b}\")\n", - "result" - ] - } - ], - "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.12.9" - } - }, - "nbformat": 4, - "nbformat_minor": 4 -} diff --git a/notebooks/kaggle/bq_dataframes_ai_forecast.ipynb b/notebooks/kaggle/bq_dataframes_ai_forecast.ipynb deleted file mode 100644 index 87ef9f6e96d..00000000000 --- a/notebooks/kaggle/bq_dataframes_ai_forecast.ipynb +++ /dev/null @@ -1,1741 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": { - "_cell_guid": "b1076dfc-b9ad-4769-8c92-a6c4dae69d19", - "_uuid": "8f2839f25d086af736a60e9eeb907d3b93b6e0e5" - }, - "source": [ - "# BigQuery DataFrames (BigFrames) AI Forecast\n", - "\n", - "This notebook is adapted from https://github.com/googleapis/python-bigquery-dataframes/blob/main/notebooks/generative_ai/bq_dataframes_ai_forecast.ipynb to work in the Kaggle runtime. It introduces forecasting with GenAI Foundation Model with BigFrames AI.\n", - "\n", - "Install the bigframes package and upgrade other packages that are already included in Kaggle but have versions incompatible with bigframes." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "trusted": true - }, - "outputs": [], - "source": [ - "%pip install --upgrade bigframes google-cloud-automl google-cloud-translate google-ai-generativelanguage tensorflow " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**Important:** restart the kernel by going to \"Run -> Restart & clear cell outputs\" before continuing.\n", - "\n", - "Configure bigframes to use your GCP project. First, go to \"Add-ons -> Google Cloud SDK\" and click the \"Attach\" button. Then," - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": { - "execution": { - "iopub.execute_input": "2025-08-18T19:16:10.449828Z", - "iopub.status.busy": "2025-08-18T19:16:10.449563Z", - "iopub.status.idle": "2025-08-18T19:16:10.618943Z", - "shell.execute_reply": "2025-08-18T19:16:10.617631Z", - "shell.execute_reply.started": "2025-08-18T19:16:10.449803Z" - }, - "trusted": true - }, - "outputs": [], - "source": [ - "from kaggle_secrets import UserSecretsClient\n", - "user_secrets = UserSecretsClient()\n", - "user_credential = user_secrets.get_gcloud_credential()\n", - "user_secrets.set_tensorflow_credential(user_credential)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "execution": { - "iopub.execute_input": "2025-08-18T19:20:00.851870Z", - "iopub.status.busy": "2025-08-18T19:20:00.851472Z", - "iopub.status.idle": "2025-08-18T19:20:00.858175Z", - "shell.execute_reply": "2025-08-18T19:20:00.857098Z", - "shell.execute_reply.started": "2025-08-18T19:20:00.851842Z" - }, - "trusted": true - }, - "outputs": [], - "source": [ - "PROJECT = \"swast-scratch\" # replace with your project\n", - "\n", - "\n", - "import bigframes.pandas as bpd\n", - "bpd.options.bigquery.project = PROJECT\n", - "bpd.options.bigquery.ordering_mode = \"partial\" # Optional: partial ordering mode can accelerate executions and save costs" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## 1. Create a BigFrames DataFrames from BigQuery public data." - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": { - "execution": { - "iopub.execute_input": "2025-08-18T19:20:02.255184Z", - "iopub.status.busy": "2025-08-18T19:20:02.254706Z", - "iopub.status.idle": "2025-08-18T19:20:04.754064Z", - "shell.execute_reply": "2025-08-18T19:20:04.752940Z", - "shell.execute_reply.started": "2025-08-18T19:20:02.255149Z" - }, - "trusted": true - }, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/usr/local/lib/python3.11/dist-packages/bigframes/core/log_adapter.py:175: TimeTravelCacheWarning: Reading cached table from 2025-08-18 19:19:20.590271+00:00 to avoid\n", - "incompatibilies with previous reads of this table. To read the latest\n", - "version, set `use_cache=False` or close the current session with\n", - "Session.close() or bigframes.pandas.close_session().\n", - " return method(*args, **kwargs)\n" - ] - }, - { - "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
02018020921350835967882018-02-09 21:35:08+00:0010th Ave at E 15th St2222018-02-09 21:48:17+00:0010th Ave at E 15th St2223596<NA>...<NA>37.792714-122.2487837.792714-122.248781984MaleYesPOINT (-122.24878 37.79271)POINT (-122.24878 37.79271)
12017081523574224919652017-08-15 23:57:42+00:0010th St at Fallon St2012017-08-16 00:13:48+00:0010th Ave at E 15th St2222491<NA>...<NA>37.797673-122.26299737.792714-122.24878<NA><NA><NA>POINT (-122.26300 37.79767)POINT (-122.24878 37.79271)
22018022816572536325602018-02-28 16:57:25+00:0010th St at Fallon St2012018-02-28 17:06:46+00:0010th Ave at E 15th St2223632<NA>...<NA>37.797673-122.26299737.792714-122.248781984MaleYesPOINT (-122.26300 37.79767)POINT (-122.24878 37.79271)
32017111700460913374972017-11-17 00:46:09+00:0010th St at Fallon St2012017-11-17 00:54:26+00:0010th Ave at E 15th St2221337<NA>...<NA>37.797673-122.26299737.792714-122.24878<NA><NA><NA>POINT (-122.26300 37.79767)POINT (-122.24878 37.79271)
42018022019132312575962018-02-20 19:13:23+00:0010th St at Fallon St2012018-02-20 19:23:19+00:0010th Ave at E 15th St2221257<NA>...<NA>37.797673-122.26299737.792714-122.248781984MaleYesPOINT (-122.26300 37.79767)POINT (-122.24878 37.79271)
520170824232500127913412017-08-24 23:25:00+00:0010th St at Fallon St2012017-08-24 23:47:22+00:0010th Ave at E 15th St2221279<NA>...<NA>37.797673-122.26299737.792714-122.248781969Male<NA>POINT (-122.26300 37.79767)POINT (-122.24878 37.79271)
62018011618004732914892018-01-16 18:00:47+00:0010th St at Fallon St2012018-01-16 18:08:56+00:0010th Ave at E 15th St2223291<NA>...<NA>37.797673-122.26299737.792714-122.248781984MaleYesPOINT (-122.26300 37.79767)POINT (-122.24878 37.79271)
72018040815560118311052018-04-08 15:56:01+00:0013th St at Franklin St3382018-04-08 16:14:26+00:0010th Ave at E 15th St222183<NA>...<NA>37.803189-122.27057937.792714-122.248781987FemaleNoPOINT (-122.27058 37.80319)POINT (-122.24878 37.79271)
82018031418570322046192018-03-14 18:57:03+00:0013th St at Franklin St3382018-03-14 19:07:23+00:0010th Ave at E 15th St2222204<NA>...<NA>37.803189-122.27057937.792714-122.248781982OtherNoPOINT (-122.27058 37.80319)POINT (-122.24878 37.79271)
92017081920533114907432017-08-19 20:53:31+00:002nd Ave at E 18th St2002017-08-19 21:05:54+00:0010th Ave at E 15th St2221490<NA>...<NA>37.800214-122.2538137.792714-122.24878<NA><NA><NA>POINT (-122.25381 37.80021)POINT (-122.24878 37.79271)
102017111818232819603532017-11-18 18:23:28+00:002nd Ave at E 18th St2002017-11-18 18:29:22+00:0010th Ave at E 15th St2221960<NA>...<NA>37.800214-122.2538137.792714-122.248781988Male<NA>POINT (-122.25381 37.80021)POINT (-122.24878 37.79271)
112017081020445483912562017-08-10 20:44:54+00:002nd Ave at E 18th St2002017-08-10 21:05:50+00:0010th Ave at E 15th St222839<NA>...<NA>37.800214-122.2538137.792714-122.24878<NA><NA><NA>POINT (-122.25381 37.80021)POINT (-122.24878 37.79271)
122018011716565535045002018-01-17 16:56:55+00:00El Embarcadero at Grand Ave1972018-01-17 17:05:16+00:0010th Ave at E 15th St2223504<NA>...<NA>37.808848-122.2496837.792714-122.248781987MaleNoPOINT (-122.24968 37.80885)POINT (-122.24878 37.79271)
132018011116131013058582018-01-11 16:13:10+00:00Frank H Ogawa Plaza72018-01-11 16:27:28+00:0010th Ave at E 15th St2221305<NA>...<NA>37.804562-122.27173837.792714-122.248781984MaleYesPOINT (-122.27174 37.80456)POINT (-122.24878 37.79271)
1420180224182655121512352018-02-24 18:26:55+00:00Frank H Ogawa Plaza72018-02-24 18:47:31+00:0010th Ave at E 15th St2221215<NA>...<NA>37.804562-122.27173837.792714-122.248781969MaleNoPOINT (-122.27174 37.80456)POINT (-122.24878 37.79271)
152018030916214834508572018-03-09 16:21:48+00:00Frank H Ogawa Plaza72018-03-09 16:36:06+00:0010th Ave at E 15th St2223450<NA>...<NA>37.804562-122.27173837.792714-122.248781984MaleYesPOINT (-122.27174 37.80456)POINT (-122.24878 37.79271)
162018010219322327179142018-01-02 19:32:23+00:00Frank H Ogawa Plaza72018-01-02 19:47:38+00:0010th Ave at E 15th St2222717<NA>...<NA>37.804562-122.27173837.792714-122.248781984MaleYesPOINT (-122.27174 37.80456)POINT (-122.24878 37.79271)
172018031619102837515642018-03-16 19:10:28+00:00Frank H Ogawa Plaza72018-03-16 19:19:52+00:0010th Ave at E 15th St2223751<NA>...<NA>37.804562-122.27173837.792714-122.248781987MaleNoPOINT (-122.27174 37.80456)POINT (-122.24878 37.79271)
18201712121524032278542017-12-12 15:24:03+00:00Frank H Ogawa Plaza72017-12-12 15:38:17+00:0010th Ave at E 15th St222227<NA>...<NA>37.804562-122.27173837.792714-122.248781984Male<NA>POINT (-122.27174 37.80456)POINT (-122.24878 37.79271)
192018031314370337249172018-03-13 14:37:03+00:00Grand Ave at Webster St1812018-03-13 14:52:20+00:0010th Ave at E 15th St2223724<NA>...<NA>37.811377-122.26519237.792714-122.248781989MaleNoPOINT (-122.26519 37.81138)POINT (-122.24878 37.79271)
202017120617555934265192017-12-06 17:55:59+00:00Lake Merritt BART Station1632017-12-06 18:04:39+00:0010th Ave at E 15th St2223426<NA>...<NA>37.79732-122.2653237.792714-122.248781986Male<NA>POINT (-122.26532 37.79732)POINT (-122.24878 37.79271)
21201804042100344513662018-04-04 21:00:34+00:00Lake Merritt BART Station1632018-04-04 21:06:41+00:0010th Ave at E 15th St222451<NA>...<NA>37.79732-122.2653237.792714-122.248781987MaleNoPOINT (-122.26532 37.79732)POINT (-122.24878 37.79271)
222018012319071617876262018-01-23 19:07:16+00:00Lake Merritt BART Station1632018-01-23 19:17:43+00:0010th Ave at E 15th St2221787<NA>...<NA>37.79732-122.2653237.792714-122.248781987MaleNoPOINT (-122.26532 37.79732)POINT (-122.24878 37.79271)
232017082710570611579732017-08-27 10:57:06+00:00Lake Merritt BART Station1632017-08-27 11:13:19+00:0010th Ave at E 15th St2221157<NA>...<NA>37.79732-122.2653237.792714-122.24878<NA><NA><NA>POINT (-122.26532 37.79732)POINT (-122.24878 37.79271)
24201709071348372074114342017-09-07 13:48:37+00:00Lake Merritt BART Station1632017-09-07 16:59:12+00:0010th Ave at E 15th St2222074<NA>...<NA>37.79732-122.2653237.792714-122.24878<NA><NA><NA>POINT (-122.26532 37.79732)POINT (-122.24878 37.79271)
\n", - "

25 rows × 21 columns

\n", - "
[1947417 rows x 21 columns in total]" - ], - "text/plain": [ - " trip_id duration_sec start_date \\\n", - "201802092135083596 788 2018-02-09 21:35:08+00:00 \n", - "201708152357422491 965 2017-08-15 23:57:42+00:00 \n", - "201802281657253632 560 2018-02-28 16:57:25+00:00 \n", - "201711170046091337 497 2017-11-17 00:46:09+00:00 \n", - "201802201913231257 596 2018-02-20 19:13:23+00:00 \n", - "201708242325001279 1341 2017-08-24 23:25:00+00:00 \n", - "201801161800473291 489 2018-01-16 18:00:47+00:00 \n", - " 20180408155601183 1105 2018-04-08 15:56:01+00:00 \n", - "201803141857032204 619 2018-03-14 18:57:03+00:00 \n", - "201708192053311490 743 2017-08-19 20:53:31+00:00 \n", - "201711181823281960 353 2017-11-18 18:23:28+00:00 \n", - " 20170810204454839 1256 2017-08-10 20:44:54+00:00 \n", - "201801171656553504 500 2018-01-17 16:56:55+00:00 \n", - "201801111613101305 858 2018-01-11 16:13:10+00:00 \n", - "201802241826551215 1235 2018-02-24 18:26:55+00:00 \n", - "201803091621483450 857 2018-03-09 16:21:48+00:00 \n", - "201801021932232717 914 2018-01-02 19:32:23+00:00 \n", - "201803161910283751 564 2018-03-16 19:10:28+00:00 \n", - " 20171212152403227 854 2017-12-12 15:24:03+00:00 \n", - "201803131437033724 917 2018-03-13 14:37:03+00:00 \n", - "201712061755593426 519 2017-12-06 17:55:59+00:00 \n", - " 20180404210034451 366 2018-04-04 21:00:34+00:00 \n", - "201801231907161787 626 2018-01-23 19:07:16+00:00 \n", - "201708271057061157 973 2017-08-27 10:57:06+00:00 \n", - "201709071348372074 11434 2017-09-07 13:48:37+00:00 \n", - "\n", - " start_station_name start_station_id end_date \\\n", - " 10th Ave at E 15th St 222 2018-02-09 21:48:17+00:00 \n", - " 10th St at Fallon St 201 2017-08-16 00:13:48+00:00 \n", - " 10th St at Fallon St 201 2018-02-28 17:06:46+00:00 \n", - " 10th St at Fallon St 201 2017-11-17 00:54:26+00:00 \n", - " 10th St at Fallon St 201 2018-02-20 19:23:19+00:00 \n", - " 10th St at Fallon St 201 2017-08-24 23:47:22+00:00 \n", - " 10th St at Fallon St 201 2018-01-16 18:08:56+00:00 \n", - " 13th St at Franklin St 338 2018-04-08 16:14:26+00:00 \n", - " 13th St at Franklin St 338 2018-03-14 19:07:23+00:00 \n", - " 2nd Ave at E 18th St 200 2017-08-19 21:05:54+00:00 \n", - " 2nd Ave at E 18th St 200 2017-11-18 18:29:22+00:00 \n", - " 2nd Ave at E 18th St 200 2017-08-10 21:05:50+00:00 \n", - "El Embarcadero at Grand Ave 197 2018-01-17 17:05:16+00:00 \n", - " Frank H Ogawa Plaza 7 2018-01-11 16:27:28+00:00 \n", - " Frank H Ogawa Plaza 7 2018-02-24 18:47:31+00:00 \n", - " Frank H Ogawa Plaza 7 2018-03-09 16:36:06+00:00 \n", - " Frank H Ogawa Plaza 7 2018-01-02 19:47:38+00:00 \n", - " Frank H Ogawa Plaza 7 2018-03-16 19:19:52+00:00 \n", - " Frank H Ogawa Plaza 7 2017-12-12 15:38:17+00:00 \n", - " Grand Ave at Webster St 181 2018-03-13 14:52:20+00:00 \n", - " Lake Merritt BART Station 163 2017-12-06 18:04:39+00:00 \n", - " Lake Merritt BART Station 163 2018-04-04 21:06:41+00:00 \n", - " Lake Merritt BART Station 163 2018-01-23 19:17:43+00:00 \n", - " Lake Merritt BART Station 163 2017-08-27 11:13:19+00:00 \n", - " Lake Merritt BART Station 163 2017-09-07 16:59:12+00:00 \n", - "\n", - " end_station_name end_station_id bike_number zip_code ... \\\n", - "10th Ave at E 15th St 222 3596 ... \n", - "10th Ave at E 15th St 222 2491 ... \n", - "10th Ave at E 15th St 222 3632 ... \n", - "10th Ave at E 15th St 222 1337 ... \n", - "10th Ave at E 15th St 222 1257 ... \n", - "10th Ave at E 15th St 222 1279 ... \n", - "10th Ave at E 15th St 222 3291 ... \n", - "10th Ave at E 15th St 222 183 ... \n", - "10th Ave at E 15th St 222 2204 ... \n", - "10th Ave at E 15th St 222 1490 ... \n", - "10th Ave at E 15th St 222 1960 ... \n", - "10th Ave at E 15th St 222 839 ... \n", - "10th Ave at E 15th St 222 3504 ... \n", - "10th Ave at E 15th St 222 1305 ... \n", - "10th Ave at E 15th St 222 1215 ... \n", - "10th Ave at E 15th St 222 3450 ... \n", - "10th Ave at E 15th St 222 2717 ... \n", - "10th Ave at E 15th St 222 3751 ... \n", - "10th Ave at E 15th St 222 227 ... \n", - "10th Ave at E 15th St 222 3724 ... \n", - "10th Ave at E 15th St 222 3426 ... \n", - "10th Ave at E 15th St 222 451 ... \n", - "10th Ave at E 15th St 222 1787 ... \n", - "10th Ave at E 15th St 222 1157 ... \n", - "10th Ave at E 15th St 222 2074 ... \n", - "\n", - "c_subscription_type start_station_latitude start_station_longitude \\\n", - " 37.792714 -122.24878 \n", - " 37.797673 -122.262997 \n", - " 37.797673 -122.262997 \n", - " 37.797673 -122.262997 \n", - " 37.797673 -122.262997 \n", - " 37.797673 -122.262997 \n", - " 37.797673 -122.262997 \n", - " 37.803189 -122.270579 \n", - " 37.803189 -122.270579 \n", - " 37.800214 -122.25381 \n", - " 37.800214 -122.25381 \n", - " 37.800214 -122.25381 \n", - " 37.808848 -122.24968 \n", - " 37.804562 -122.271738 \n", - " 37.804562 -122.271738 \n", - " 37.804562 -122.271738 \n", - " 37.804562 -122.271738 \n", - " 37.804562 -122.271738 \n", - " 37.804562 -122.271738 \n", - " 37.811377 -122.265192 \n", - " 37.79732 -122.26532 \n", - " 37.79732 -122.26532 \n", - " 37.79732 -122.26532 \n", - " 37.79732 -122.26532 \n", - " 37.79732 -122.26532 \n", - "\n", - " end_station_latitude end_station_longitude member_birth_year \\\n", - " 37.792714 -122.24878 1984 \n", - " 37.792714 -122.24878 \n", - " 37.792714 -122.24878 1984 \n", - " 37.792714 -122.24878 \n", - " 37.792714 -122.24878 1984 \n", - " 37.792714 -122.24878 1969 \n", - " 37.792714 -122.24878 1984 \n", - " 37.792714 -122.24878 1987 \n", - " 37.792714 -122.24878 1982 \n", - " 37.792714 -122.24878 \n", - " 37.792714 -122.24878 1988 \n", - " 37.792714 -122.24878 \n", - " 37.792714 -122.24878 1987 \n", - " 37.792714 -122.24878 1984 \n", - " 37.792714 -122.24878 1969 \n", - " 37.792714 -122.24878 1984 \n", - " 37.792714 -122.24878 1984 \n", - " 37.792714 -122.24878 1987 \n", - " 37.792714 -122.24878 1984 \n", - " 37.792714 -122.24878 1989 \n", - " 37.792714 -122.24878 1986 \n", - " 37.792714 -122.24878 1987 \n", - " 37.792714 -122.24878 1987 \n", - " 37.792714 -122.24878 \n", - " 37.792714 -122.24878 \n", - "\n", - " member_gender bike_share_for_all_trip start_station_geom \\\n", - " Male Yes POINT (-122.24878 37.79271) \n", - " POINT (-122.26300 37.79767) \n", - " Male Yes POINT (-122.26300 37.79767) \n", - " POINT (-122.26300 37.79767) \n", - " Male Yes POINT (-122.26300 37.79767) \n", - " Male POINT (-122.26300 37.79767) \n", - " Male Yes POINT (-122.26300 37.79767) \n", - " Female No POINT (-122.27058 37.80319) \n", - " Other No POINT (-122.27058 37.80319) \n", - " POINT (-122.25381 37.80021) \n", - " Male POINT (-122.25381 37.80021) \n", - " POINT (-122.25381 37.80021) \n", - " Male No POINT (-122.24968 37.80885) \n", - " Male Yes POINT (-122.27174 37.80456) \n", - " Male No POINT (-122.27174 37.80456) \n", - " Male Yes POINT (-122.27174 37.80456) \n", - " Male Yes POINT (-122.27174 37.80456) \n", - " Male No POINT (-122.27174 37.80456) \n", - " Male POINT (-122.27174 37.80456) \n", - " Male No POINT (-122.26519 37.81138) \n", - " Male POINT (-122.26532 37.79732) \n", - " Male No POINT (-122.26532 37.79732) \n", - " Male No POINT (-122.26532 37.79732) \n", - " POINT (-122.26532 37.79732) \n", - " POINT (-122.26532 37.79732) \n", - "\n", - " end_station_geom \n", - "POINT (-122.24878 37.79271) \n", - "POINT (-122.24878 37.79271) \n", - "POINT (-122.24878 37.79271) \n", - "POINT (-122.24878 37.79271) \n", - "POINT (-122.24878 37.79271) \n", - "POINT (-122.24878 37.79271) \n", - "POINT (-122.24878 37.79271) \n", - "POINT (-122.24878 37.79271) \n", - "POINT (-122.24878 37.79271) \n", - "POINT (-122.24878 37.79271) \n", - "POINT (-122.24878 37.79271) \n", - "POINT (-122.24878 37.79271) \n", - "POINT (-122.24878 37.79271) \n", - "POINT (-122.24878 37.79271) \n", - "POINT (-122.24878 37.79271) \n", - "POINT (-122.24878 37.79271) \n", - "POINT (-122.24878 37.79271) \n", - "POINT (-122.24878 37.79271) \n", - "POINT (-122.24878 37.79271) \n", - "POINT (-122.24878 37.79271) \n", - "POINT (-122.24878 37.79271) \n", - "POINT (-122.24878 37.79271) \n", - "POINT (-122.24878 37.79271) \n", - "POINT (-122.24878 37.79271) \n", - "POINT (-122.24878 37.79271) \n", - "...\n", - "\n", - "[1947417 rows x 21 columns]" - ] - }, - "execution_count": 5, - "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\n", - "\n", - "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": 6, - "metadata": { - "execution": { - "iopub.execute_input": "2025-08-18T19:20:44.398876Z", - "iopub.status.busy": "2025-08-18T19:20:44.397712Z", - "iopub.status.idle": "2025-08-18T19:20:44.421504Z", - "shell.execute_reply": "2025-08-18T19:20:44.420509Z", - "shell.execute_reply.started": "2025-08-18T19:20:44.398742Z" - }, - "trusted": true - }, - "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": 7, - "metadata": { - "execution": { - "iopub.execute_input": "2025-08-18T19:20:57.500413Z", - "iopub.status.busy": "2025-08-18T19:20:57.499571Z", - "iopub.status.idle": "2025-08-18T19:21:02.999663Z", - "shell.execute_reply": "2025-08-18T19:21:02.998792Z", - "shell.execute_reply.started": "2025-08-18T19:20:57.500376Z" - }, - "trusted": true - }, - "outputs": [ - { - "data": { - "text/html": [ - "Query job e3df71d2-9248-491a-8e5f-4bb5bfedb686 is DONE. 58.7 MB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "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", - "2018-01-01 00:00:00+00:00 20\n", - "2018-01-01 01:00:00+00:00 25\n", - "2018-01-01 02:00:00+00:00 13\n", - "2018-01-01 03:00:00+00:00 11\n", - "2018-01-01 05:00:00+00:00 4\n", - "2018-01-01 06:00:00+00:00 8\n", - "2018-01-01 07:00:00+00:00 8\n", - "2018-01-01 08:00:00+00:00 20\n", - "2018-01-01 09:00:00+00:00 30\n", - "2018-01-01 10:00:00+00:00 41\n", - "2018-01-01 11:00:00+00:00 45\n", - "2018-01-01 12:00:00+00:00 54\n", - "2018-01-01 13:00:00+00:00 57\n", - "2018-01-01 14:00:00+00:00 68\n", - "2018-01-01 15:00:00+00:00 86\n", - "2018-01-01 16:00:00+00:00 72\n", - "2018-01-01 17:00:00+00:00 72\n", - "2018-01-01 18:00:00+00:00 47\n", - "2018-01-01 19:00:00+00:00 32\n", - "2018-01-01 20:00:00+00:00 34\n", - "2018-01-01 21:00:00+00:00 27\n", - "2018-01-01 22:00:00+00:00 15\n", - "2018-01-01 23:00:00+00:00 6\n", - "2018-01-02 00:00:00+00:00 2\n", - "2018-01-02 01:00:00+00:00 1\n", - "...\n", - "\n", - "[2842 rows x 2 columns]" - ] - }, - "execution_count": 7, - "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": 8, - "metadata": { - "execution": { - "iopub.execute_input": "2025-08-18T19:22:58.944068Z", - "iopub.status.busy": "2025-08-18T19:22:58.943589Z", - "iopub.status.idle": "2025-08-18T19:23:11.364356Z", - "shell.execute_reply": "2025-08-18T19:23:11.363152Z", - "shell.execute_reply.started": "2025-08-18T19:22:58.944036Z" - }, - "trusted": true - }, - "outputs": [ - { - "data": { - "text/html": [ - "Query job 3f1225a8-b80b-4dfa-a7cf-94b93e7c18c2 is DONE. 68.2 kB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "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-04-24 12:00:00+00:00144.5777280.95120.01921169.136247
12018-04-25 00:00:00+00:0054.2155150.9546.839461.591631
22018-04-26 05:00:00+00:008.1405330.95-14.61327230.894339
32018-04-26 14:00:00+00:00198.7449490.95174.982268222.50763
42018-04-27 02:00:00+00:009.918060.95-26.74994846.586069
52018-04-29 03:00:00+00:0032.0633390.95-35.73097899.857656
62018-04-27 04:00:00+00:0025.7571110.958.17803743.336184
72018-04-30 06:00:00+00:0089.8084560.9515.214961164.401952
82018-04-30 02:00:00+00:00-10.5841750.95-60.77202439.603674
92018-04-30 05:00:00+00:0018.1181110.95-40.90213377.138355
102018-04-24 07:00:00+00:00359.0369570.95250.880334467.193579
112018-04-25 10:00:00+00:00227.2720490.95170.918819283.625279
122018-04-27 15:00:00+00:00208.6313630.95188.977435228.285291
132018-04-25 13:00:00+00:00159.7999110.95150.066363169.53346
142018-04-26 12:00:00+00:00190.2269440.95177.898865202.555023
152018-04-24 04:00:00+00:0011.1623380.95-18.58104140.905717
162018-04-24 14:00:00+00:00136.708160.95134.165413139.250907
172018-04-28 21:00:00+00:0065.3088990.9563.00091567.616883
182018-04-29 20:00:00+00:0071.7888490.95-2.49023146.067928
192018-04-30 15:00:00+00:00142.5609440.9541.495553243.626334
202018-04-26 18:00:00+00:00533.7838130.95412.068752655.498875
212018-04-28 03:00:00+00:0025.3797610.9522.56575228.193769
222018-04-30 12:00:00+00:00158.3133850.9579.466457237.160313
232018-04-25 07:00:00+00:00358.7565920.95276.305603441.207581
242018-04-27 22:00:00+00:00103.5890960.9594.45235112.725842
\n", - "

25 rows × 6 columns

\n", - "
[168 rows x 6 columns in total]" - ], - "text/plain": [ - " forecast_timestamp forecast_value confidence_level \\\n", - "2018-04-24 12:00:00+00:00 144.577728 0.95 \n", - "2018-04-25 00:00:00+00:00 54.215515 0.95 \n", - "2018-04-26 05:00:00+00:00 8.140533 0.95 \n", - "2018-04-26 14:00:00+00:00 198.744949 0.95 \n", - "2018-04-27 02:00:00+00:00 9.91806 0.95 \n", - "2018-04-29 03:00:00+00:00 32.063339 0.95 \n", - "2018-04-27 04:00:00+00:00 25.757111 0.95 \n", - "2018-04-30 06:00:00+00:00 89.808456 0.95 \n", - "2018-04-30 02:00:00+00:00 -10.584175 0.95 \n", - "2018-04-30 05:00:00+00:00 18.118111 0.95 \n", - "2018-04-24 07:00:00+00:00 359.036957 0.95 \n", - "2018-04-25 10:00:00+00:00 227.272049 0.95 \n", - "2018-04-27 15:00:00+00:00 208.631363 0.95 \n", - "2018-04-25 13:00:00+00:00 159.799911 0.95 \n", - "2018-04-26 12:00:00+00:00 190.226944 0.95 \n", - "2018-04-24 04:00:00+00:00 11.162338 0.95 \n", - "2018-04-24 14:00:00+00:00 136.70816 0.95 \n", - "2018-04-28 21:00:00+00:00 65.308899 0.95 \n", - "2018-04-29 20:00:00+00:00 71.788849 0.95 \n", - "2018-04-30 15:00:00+00:00 142.560944 0.95 \n", - "2018-04-26 18:00:00+00:00 533.783813 0.95 \n", - "2018-04-28 03:00:00+00:00 25.379761 0.95 \n", - "2018-04-30 12:00:00+00:00 158.313385 0.95 \n", - "2018-04-25 07:00:00+00:00 358.756592 0.95 \n", - "2018-04-27 22:00:00+00:00 103.589096 0.95 \n", - "\n", - " prediction_interval_lower_bound prediction_interval_upper_bound \\\n", - " 120.01921 169.136247 \n", - " 46.8394 61.591631 \n", - " -14.613272 30.894339 \n", - " 174.982268 222.50763 \n", - " -26.749948 46.586069 \n", - " -35.730978 99.857656 \n", - " 8.178037 43.336184 \n", - " 15.214961 164.401952 \n", - " -60.772024 39.603674 \n", - " -40.902133 77.138355 \n", - " 250.880334 467.193579 \n", - " 170.918819 283.625279 \n", - " 188.977435 228.285291 \n", - " 150.066363 169.53346 \n", - " 177.898865 202.555023 \n", - " -18.581041 40.905717 \n", - " 134.165413 139.250907 \n", - " 63.000915 67.616883 \n", - " -2.49023 146.067928 \n", - " 41.495553 243.626334 \n", - " 412.068752 655.498875 \n", - " 22.565752 28.193769 \n", - " 79.466457 237.160313 \n", - " 276.305603 441.207581 \n", - " 94.45235 112.725842 \n", - "\n", - "ai_forecast_status \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "...\n", - "\n", - "[168 rows x 6 columns]" - ] - }, - "execution_count": 8, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# Using all the data except the last week (2842-168) for training. And predict the last week (168).\n", - "result = df_grouped.head(2842-168).ai.forecast(timestamp_column=\"trip_hour\", data_column=\"num_trips\", horizon=168) \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": 9, - "metadata": { - "execution": { - "iopub.execute_input": "2025-08-18T19:27:08.306367Z", - "iopub.status.busy": "2025-08-18T19:27:08.305886Z", - "iopub.status.idle": "2025-08-18T19:27:08.318514Z", - "shell.execute_reply": "2025-08-18T19:27:08.317016Z", - "shell.execute_reply.started": "2025-08-18T19:27:08.306336Z" - }, - "trusted": true - }, - "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": "markdown", - "metadata": {}, - "source": [ - "Plot a line chart and compare with the actual result." - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "metadata": { - "execution": { - "iopub.execute_input": "2025-08-18T19:27:19.461528Z", - "iopub.status.busy": "2025-08-18T19:27:19.461164Z", - "iopub.status.idle": "2025-08-18T19:27:20.737558Z", - "shell.execute_reply": "2025-08-18T19:27:20.736422Z", - "shell.execute_reply.started": "2025-08-18T19:27:19.461497Z" - }, - "trusted": true - }, - "outputs": [ - { - "data": { - "text/plain": [ - "" - ] - }, - "execution_count": 10, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAABREAAAKnCAYAAAARNgr5AAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjcuMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8pXeV/AAAACXBIWXMAAA9hAAAPYQGoP6dpAAEAAElEQVR4nOz9e7wlV13mjz+rau99ujtN5zZJd6IhBIkDgQAx+IU2jjAQE0JEgSCKjEM0oz95BRnIgMoYSQgIyA9QhKCoEHAQncEvMBi5JEQCSEK4XwQHFIgdzI0Rkk6TdJ+9q9b3j6pVtdbatc/pWmudtfap87xfr37tc+ldp2rvXatqPev5fB4hpZQghBBCCCGEEEIIIYSQBWSpd4AQQgghhBBCCCGEELLcUEQkhBBCCCGEEEIIIYSsCUVEQgghhBBCCCGEEELImlBEJIQQQgghhBBCCCGErAlFREIIIYQQQgghhBBCyJpQRCSEEEIIIYQQQgghhKwJRURCCCGEEEIIIYQQQsiaUEQkhBBCCCGEEEIIIYSsySj1DrhSliVuvfVW3O9+94MQIvXuEEIIIYQQQgghhBCyqZBS4p577sGJJ56ILFvba7hpRcRbb70VJ510UurdIIQQQgghhBBCCCFkU3PLLbfgB3/wB9f8P5tWRLzf/e4HoDrIXbt2Jd4bQgghhBBCCCGEEEI2F/v378dJJ53U6GxrsWlFRFXCvGvXLoqIhBBCCCGEEEIIIYQ4cjitAhmsQgghhBBCCCGEEEIIWROKiIQQQgghhBBCCCGEkDWhiEgIIYQQQgghhBBCCFmTTdsT8XCQUmI2m6EoitS7Qog3eZ5jNBodVp8CQgghhBBCCCGEkJAMVkRcXV3FbbfdhnvvvTf1rhASjB07duCEE07AZDJJvSuEEEIIIYQQQgjZQgxSRCzLEt/61reQ5zlOPPFETCYTurfIpkZKidXVVXznO9/Bt771LZx66qnIMnYjIIQQQgghhBBCSBwGKSKurq6iLEucdNJJ2LFjR+rdISQI27dvx3g8xr/8y79gdXUV27ZtS71LhBBCCCGEEEII2SIM2spEpxYZGvxME0IIIYQQQgghJAVUJAghhBBCCCGEEEIIIWtCEZEQQgghhBBCCCGEELImFBFJUC6//HI88pGPTL0bhBBCCCGEEEIIISQgFBHJujzucY/D85///MP6vy984Qtx3XXXbewOEUIIIYQQQgghhJCoDDKdmcRHSomiKLBz507s3Lkz9e4QQgghhBBCCCGEkIBsGSeilBL3rs6S/JNSHvZ+Pu5xj8Pznvc8/MZv/AaOOeYY7NmzB5dffjkA4Oabb4YQAl/4whea/3/XXXdBCIHrr78eAHD99ddDCIEPfehDOOOMM7B9+3Y8/vGPx5133okPfOADeMhDHoJdu3bhF37hF3Dvvfeuuz8XXnghPvrRj+L1r389hBAQQuDmm29u/s4HPvABnHnmmVhZWcHf//3fz5UzX3jhhXjKU56Cl770pTjuuOOwa9cu/Nqv/RpWV1eb//PXf/3XOP3007F9+3Yce+yxOPvss/H973//sF8zQgghhBBCCCGEELKxbBkn4n3TAqe95ENJ/vZXrzgXOyaH/1K//e1vxyWXXIKbbroJN954Iy688EKcddZZOPXUUw97G5dffjne+MY3YseOHXjGM56BZzzjGVhZWcE73/lOHDhwAE996lPxhje8Ab/5m7+55nZe//rX4+tf/zoe9rCH4YorrgAAHHfccbj55psBAL/1W7+F17zmNXjgAx+Io48+uhEzda677jps27YN119/PW6++Wb80i/9Eo499lj87u/+Lm677TY885nPxKtf/Wo89alPxT333IOPf/zjvYRXQgghhBBCCCGEELKxbBkRcTPx8Ic/HJdddhkA4NRTT8Ub3/hGXHfddb1ExJe//OU466yzAAAXXXQRXvziF+Mb3/gGHvjABwIAnv70p+MjH/nIuiLikUceiclkgh07dmDPnj1zv7/iiivwkz/5k2tuYzKZ4K1vfSt27NiBhz70objiiivwohe9CC972ctw2223YTab4WlPexpOPvlkAMDpp59+2MdJCCGEEEIIIYQQQjaeLSMibh/n+OoV5yb72314+MMfbnx/wgkn4M4773Texu7du7Fjx45GQFQ/+9SnPtVrm1086lGPWvf/POIRj8COHTua7/fu3YsDBw7glltuwSMe8Qg84QlPwOmnn45zzz0X55xzDp7+9Kfj6KOP9t43QgghhBBCCCGEEBKGLSMiCiF6lRSnZDweG98LIVCWJbKsamGpl/pOp9N1tyGEWLhNX4444giv5+d5jmuvvRY33HADrrnmGrzhDW/Ab//2b+Omm27CKaec4r1/hBBCCCGEEEIIIcSfLROsMgSOO+44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BAA8/elPx0c+8pF1RcQjjzwSk8kEO3bswJ49e+Z+f8UVV+Anf/In19zGZDLBW9/6VuzYsQMPfehDccUVV+BFL3oRXvayl+G2227DbDbD0572NJx88skAgNNPP33d49u5cyeOOuooADD26zWveQ1+8zd/Ez//8z8PAPi93/s9fOQjH8Ef/MEf4Morr2z+3/Of/3w87WlPa76/7LLL8NrXvrb52SmnnIKvfvWrePOb34xnP/vZeOc734nvfOc7+PSnP41jjjkGAPCgBz2oef4jHvEIPOIRj2i+f9nLXob3vOc9eN/73ofnPve52LdvH4444gj81E/9FO53v/vh5JNPxhlnnHFYrzEhhBBCCCFDgD0RCSEpoRPRn60jIo53VI7AVH+7Bw9/+MON70844QTceeedztvYvXs3duzY0QiI6mef+tSnem2zi0c96lHr/p9HPOIR2LGjfQ327t2LAwcO4JZbbsEjHvEIPOEJT8Dpp5+Oc889F+eccw6e/vSn4+ijj+69L/v378ett97aiKeKs846C1/84hcX7vf3v/99fOMb38BFF12EX/mVX2l+PpvNcOSRRwIAvvCFL+CMM85oBESbAwcO4PLLL8ff/u3fNsLofffdh3379gEAfvInfxInn3wyHvjAB+KJT3winvjEJ+KpT32q8boQQgghhBAyZOhEJISkRBU8UkR0Z+uIiEL0KilOiR3yIYRAWZbIsqqFpV7qO51O192GEGLhNn054gi/1zTPc1x77bW44YYbcM011+ANb3gDfvu3fxs33XQTTjnlFO/9W4S+3wcOHAAA/Omf/ike/ehHz+0fAGzfvn3N7b3whS/Etddei9e85jV40IMehO3bt+PpT3960/vxfve7Hz73uc/h+uuvxzXXXIOXvOQluPzyy/HpT3+6cVQSQgghhBAyZGTjROQEnhASn7JU4U7VYkaeuQe0blW2TrDKAFB98vQegHrIykYxmUxQFIXz87/4xS/ivvvua77/5Cc/iZ07d+Kkk04CUAmaZ511Fl760pfi85//PCaTCd7znvf0/ju7du3CiSeeiE984hPGzz/xiU/gtNNOW/i83bt348QTT8Q3v/lNPOhBDzL+KSHz4Q9/OL7whS/gu9/9buc2PvGJT+DCCy/EU5/6VJx++unYs2fPXDjKaDTC2WefjVe/+tX40pe+hJtvvhl/93d/B8D/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", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "df_all = df_all.set_index(\"trip_hour\")\n", - "df_all.plot.line(figsize=(16, 8))" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "trusted": true - }, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kaggle": { - "accelerator": "none", - "dataSources": [ - { - "databundleVersionId": 13391012, - "sourceId": 110281, - "sourceType": "competition" - } - ], - "dockerImageVersionId": 31089, - "isGpuEnabled": false, - "isInternetEnabled": true, - "language": "python", - "sourceType": "notebook" - }, - "kernelspec": { - "display_name": "Python 3", - "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.11.13" - } - }, - "nbformat": 4, - "nbformat_minor": 4 -} diff --git a/notebooks/kaggle/describe-product-images-with-bigframes-multimodal.ipynb b/notebooks/kaggle/describe-product-images-with-bigframes-multimodal.ipynb deleted file mode 100644 index 1a7de9b837f..00000000000 --- a/notebooks/kaggle/describe-product-images-with-bigframes-multimodal.ipynb +++ /dev/null @@ -1,1131 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "876eb80c", - "metadata": { - "_cell_guid": "b1076dfc-b9ad-4769-8c92-a6c4dae69d19", - "_uuid": "8f2839f25d086af736a60e9eeb907d3b93b6e0e5" - }, - "source": [ - "# Describe product images with BigFrames multimodal DataFrames\n", - "\n", - "Based on notebook at https://github.com/googleapis/python-bigquery-dataframes/blob/main/notebooks/multimodal/multimodal_dataframe.ipynb\n", - "\n", - "This notebook is introducing BigFrames Multimodal features:\n", - "\n", - "1. Create Multimodal DataFrame\n", - "2. Combine unstructured data with structured data\n", - "3. Conduct image transformations\n", - "4. Use LLM models to ask questions and generate embeddings on images\n", - "5. PDF chunking function\n", - "\n", - "Install the bigframes package and upgrade other packages that are already included in Kaggle but have versions incompatible with bigframes." - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "id": "0506e15e", - "metadata": { - "trusted": true - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Requirement already satisfied: bigframes in /usr/local/google/home/shuowei/src/google-cloud-python/google-cloud-python/packages/bigframes/.venv/lib/python3.13/site-packages (2.39.0)\n", - "Requirement already satisfied: google-cloud-automl in /usr/local/google/home/shuowei/src/google-cloud-python/google-cloud-python/packages/bigframes/.venv/lib/python3.13/site-packages (2.19.0)\n", - "Requirement already satisfied: google-cloud-translate in /usr/local/google/home/shuowei/src/google-cloud-python/google-cloud-python/packages/bigframes/.venv/lib/python3.13/site-packages (3.26.0)\n", - "Requirement already satisfied: google-ai-generativelanguage in /usr/local/google/home/shuowei/src/google-cloud-python/google-cloud-python/packages/bigframes/.venv/lib/python3.13/site-packages (0.11.0)\n", - "Requirement already satisfied: tensorflow in /usr/local/google/home/shuowei/src/google-cloud-python/google-cloud-python/packages/bigframes/.venv/lib/python3.13/site-packages (2.21.0)\n", - "Requirement already satisfied: cloudpickle>=2.0.0 in /usr/local/google/home/shuowei/src/google-cloud-python/google-cloud-python/packages/bigframes/.venv/lib/python3.13/site-packages (from bigframes) (3.1.2)\n", - "Requirement already satisfied: fsspec>=2023.3.0 in /usr/local/google/home/shuowei/src/google-cloud-python/google-cloud-python/packages/bigframes/.venv/lib/python3.13/site-packages (from bigframes) (2026.1.0)\n", - "Requirement already satisfied: gcsfs!=2025.5.0,!=2026.2.0,!=2026.3.0,>=2023.3.0 in /usr/local/google/home/shuowei/src/google-cloud-python/google-cloud-python/packages/bigframes/.venv/lib/python3.13/site-packages (from bigframes) (2026.1.0)\n", - "Requirement already satisfied: geopandas>=0.12.2 in /usr/local/google/home/shuowei/src/google-cloud-python/google-cloud-python/packages/bigframes/.venv/lib/python3.13/site-packages (from bigframes) (1.1.3)\n", - "Requirement already satisfied: google-auth<3.0,>=2.15.0 in /usr/local/google/home/shuowei/src/google-cloud-python/google-cloud-python/packages/bigframes/.venv/lib/python3.13/site-packages (from bigframes) (2.49.1)\n", - "Requirement already satisfied: google-cloud-bigquery>=3.36.0 in /usr/local/google/home/shuowei/src/google-cloud-python/google-cloud-python/packages/bigframes/.venv/lib/python3.13/site-packages (from google-cloud-bigquery[bqstorage,pandas]>=3.36.0->bigframes) (3.41.0)\n", - "Requirement already satisfied: google-cloud-bigquery-storage<3.0.0,>=2.30.0 in /usr/local/google/home/shuowei/src/google-cloud-python/google-cloud-python/packages/bigframes/.venv/lib/python3.13/site-packages (from bigframes) (2.37.0)\n", - "Requirement already satisfied: google-cloud-functions>=1.12.0 in /usr/local/google/home/shuowei/src/google-cloud-python/google-cloud-python/packages/bigframes/.venv/lib/python3.13/site-packages (from bigframes) (1.23.0)\n", - "Requirement already satisfied: google-cloud-bigquery-connection>=1.12.0 in /usr/local/google/home/shuowei/src/google-cloud-python/google-cloud-python/packages/bigframes/.venv/lib/python3.13/site-packages (from bigframes) (1.21.0)\n", - "Requirement already satisfied: google-cloud-resource-manager>=1.10.3 in /usr/local/google/home/shuowei/src/google-cloud-python/google-cloud-python/packages/bigframes/.venv/lib/python3.13/site-packages (from bigframes) (1.17.0)\n", - "Requirement already satisfied: google-cloud-storage>=2.0.0 in /usr/local/google/home/shuowei/src/google-cloud-python/google-cloud-python/packages/bigframes/.venv/lib/python3.13/site-packages (from bigframes) (3.10.1)\n", - "Requirement already satisfied: google-crc32c<2.0.0,>=1.0.0 in /usr/local/google/home/shuowei/src/google-cloud-python/google-cloud-python/packages/bigframes/.venv/lib/python3.13/site-packages (from bigframes) (1.8.0)\n", - "Requirement already satisfied: grpc-google-iam-v1>=0.14.2 in /usr/local/google/home/shuowei/src/google-cloud-python/google-cloud-python/packages/bigframes/.venv/lib/python3.13/site-packages (from bigframes) (0.14.4)\n", - "Requirement already satisfied: numpy>=1.24.0 in /usr/local/google/home/shuowei/src/google-cloud-python/google-cloud-python/packages/bigframes/.venv/lib/python3.13/site-packages (from bigframes) (2.4.4)\n", - "Requirement already satisfied: pandas>=1.5.3 in /usr/local/google/home/shuowei/src/google-cloud-python/google-cloud-python/packages/bigframes/.venv/lib/python3.13/site-packages (from bigframes) (2.3.3)\n", - "Requirement already satisfied: pandas-gbq>=0.26.1 in /usr/local/google/home/shuowei/src/google-cloud-python/google-cloud-python/packages/bigframes/.venv/lib/python3.13/site-packages (from bigframes) (0.34.1)\n", - "Requirement already satisfied: pyarrow>=15.0.2 in /usr/local/google/home/shuowei/src/google-cloud-python/google-cloud-python/packages/bigframes/.venv/lib/python3.13/site-packages (from bigframes) (21.0.0)\n", - "Requirement already satisfied: pydata-google-auth>=1.8.2 in /usr/local/google/home/shuowei/src/google-cloud-python/google-cloud-python/packages/bigframes/.venv/lib/python3.13/site-packages (from bigframes) (1.9.1)\n", - "Requirement already satisfied: requests>=2.27.1 in /usr/local/google/home/shuowei/src/google-cloud-python/google-cloud-python/packages/bigframes/.venv/lib/python3.13/site-packages (from bigframes) (2.33.1)\n", - "Requirement already satisfied: shapely>=1.8.5 in /usr/local/google/home/shuowei/src/google-cloud-python/google-cloud-python/packages/bigframes/.venv/lib/python3.13/site-packages (from bigframes) (2.1.2)\n", - "Requirement already satisfied: tabulate>=0.9 in /usr/local/google/home/shuowei/src/google-cloud-python/google-cloud-python/packages/bigframes/.venv/lib/python3.13/site-packages (from bigframes) (0.10.0)\n", - "Requirement already satisfied: humanize>=4.6.0 in /usr/local/google/home/shuowei/src/google-cloud-python/google-cloud-python/packages/bigframes/.venv/lib/python3.13/site-packages (from bigframes) (4.15.0)\n", - "Requirement already satisfied: matplotlib>=3.7.1 in /usr/local/google/home/shuowei/src/google-cloud-python/google-cloud-python/packages/bigframes/.venv/lib/python3.13/site-packages (from bigframes) (3.10.8)\n", - "Requirement already satisfied: db-dtypes>=1.4.2 in /usr/local/google/home/shuowei/src/google-cloud-python/google-cloud-python/packages/bigframes/.venv/lib/python3.13/site-packages (from bigframes) (1.5.1)\n", - "Requirement already satisfied: pyiceberg>=0.7.1 in /usr/local/google/home/shuowei/src/google-cloud-python/google-cloud-python/packages/bigframes/.venv/lib/python3.13/site-packages (from bigframes) (0.11.1)\n", - "Requirement already satisfied: atpublic<6,>=2.3 in /usr/local/google/home/shuowei/src/google-cloud-python/google-cloud-python/packages/bigframes/.venv/lib/python3.13/site-packages (from bigframes) (5.1)\n", - "Requirement already satisfied: python-dateutil<3,>=2.8.2 in /usr/local/google/home/shuowei/src/google-cloud-python/google-cloud-python/packages/bigframes/.venv/lib/python3.13/site-packages (from bigframes) (2.9.0.post0)\n", - "Requirement already satisfied: pytz>=2022.7 in /usr/local/google/home/shuowei/src/google-cloud-python/google-cloud-python/packages/bigframes/.venv/lib/python3.13/site-packages (from bigframes) (2026.1.post1)\n", - "Requirement already satisfied: toolz<2,>=0.11 in /usr/local/google/home/shuowei/src/google-cloud-python/google-cloud-python/packages/bigframes/.venv/lib/python3.13/site-packages (from bigframes) (1.1.0)\n", - "Requirement already satisfied: typing-extensions<5,>=4.5.0 in /usr/local/google/home/shuowei/src/google-cloud-python/google-cloud-python/packages/bigframes/.venv/lib/python3.13/site-packages (from bigframes) (4.15.0)\n", - "Requirement already satisfied: rich<14,>=12.4.4 in /usr/local/google/home/shuowei/src/google-cloud-python/google-cloud-python/packages/bigframes/.venv/lib/python3.13/site-packages (from bigframes) (13.9.4)\n", - "Requirement already satisfied: google-api-core<3.0.0,>=2.11.0 in /usr/local/google/home/shuowei/src/google-cloud-python/google-cloud-python/packages/bigframes/.venv/lib/python3.13/site-packages (from google-api-core[grpc]<3.0.0,>=2.11.0->google-cloud-automl) (2.30.2)\n", - "Requirement already satisfied: grpcio<2.0.0,>=1.33.2 in /usr/local/google/home/shuowei/src/google-cloud-python/google-cloud-python/packages/bigframes/.venv/lib/python3.13/site-packages (from google-cloud-automl) (1.80.0)\n", - "Requirement already satisfied: proto-plus<2.0.0,>=1.22.3 in /usr/local/google/home/shuowei/src/google-cloud-python/google-cloud-python/packages/bigframes/.venv/lib/python3.13/site-packages (from google-cloud-automl) (1.27.2)\n", - "Requirement already satisfied: protobuf<8.0.0,>=4.25.8 in /usr/local/google/home/shuowei/src/google-cloud-python/google-cloud-python/packages/bigframes/.venv/lib/python3.13/site-packages (from google-cloud-automl) (6.33.6)\n", - "Requirement already satisfied: google-cloud-core<3.0.0,>=2.0.0 in /usr/local/google/home/shuowei/src/google-cloud-python/google-cloud-python/packages/bigframes/.venv/lib/python3.13/site-packages (from google-cloud-translate) (2.5.1)\n", - "Requirement already satisfied: absl-py>=1.0.0 in /usr/local/google/home/shuowei/src/google-cloud-python/google-cloud-python/packages/bigframes/.venv/lib/python3.13/site-packages (from tensorflow) (2.4.0)\n", - "Requirement already satisfied: astunparse>=1.6.0 in /usr/local/google/home/shuowei/src/google-cloud-python/google-cloud-python/packages/bigframes/.venv/lib/python3.13/site-packages (from tensorflow) (1.6.3)\n", - "Requirement already satisfied: flatbuffers>=25.9.23 in /usr/local/google/home/shuowei/src/google-cloud-python/google-cloud-python/packages/bigframes/.venv/lib/python3.13/site-packages (from tensorflow) (25.12.19)\n", - "Requirement already satisfied: gast!=0.5.0,!=0.5.1,!=0.5.2,>=0.2.1 in /usr/local/google/home/shuowei/src/google-cloud-python/google-cloud-python/packages/bigframes/.venv/lib/python3.13/site-packages (from tensorflow) (0.7.0)\n", - "Requirement already satisfied: google_pasta>=0.1.1 in /usr/local/google/home/shuowei/src/google-cloud-python/google-cloud-python/packages/bigframes/.venv/lib/python3.13/site-packages (from tensorflow) (0.2.0)\n", - "Requirement already satisfied: libclang>=13.0.0 in /usr/local/google/home/shuowei/src/google-cloud-python/google-cloud-python/packages/bigframes/.venv/lib/python3.13/site-packages (from tensorflow) (18.1.1)\n", - "Requirement already satisfied: opt_einsum>=2.3.2 in /usr/local/google/home/shuowei/src/google-cloud-python/google-cloud-python/packages/bigframes/.venv/lib/python3.13/site-packages (from tensorflow) (3.4.0)\n", - "Requirement already satisfied: packaging in /usr/local/google/home/shuowei/src/google-cloud-python/google-cloud-python/packages/bigframes/.venv/lib/python3.13/site-packages (from tensorflow) (26.0)\n", - "Requirement already satisfied: setuptools in /usr/local/google/home/shuowei/src/google-cloud-python/google-cloud-python/packages/bigframes/.venv/lib/python3.13/site-packages (from tensorflow) (82.0.1)\n", - "Requirement already satisfied: six>=1.12.0 in /usr/local/google/home/shuowei/src/google-cloud-python/google-cloud-python/packages/bigframes/.venv/lib/python3.13/site-packages (from tensorflow) (1.17.0)\n", - "Requirement already satisfied: termcolor>=1.1.0 in /usr/local/google/home/shuowei/src/google-cloud-python/google-cloud-python/packages/bigframes/.venv/lib/python3.13/site-packages (from tensorflow) (3.3.0)\n", - "Requirement already satisfied: wrapt>=1.11.0 in /usr/local/google/home/shuowei/src/google-cloud-python/google-cloud-python/packages/bigframes/.venv/lib/python3.13/site-packages (from tensorflow) (2.1.2)\n", - "Requirement already satisfied: keras>=3.12.0 in /usr/local/google/home/shuowei/src/google-cloud-python/google-cloud-python/packages/bigframes/.venv/lib/python3.13/site-packages (from tensorflow) (3.14.0)\n", - "Requirement already satisfied: h5py<3.15.0,>=3.11.0 in /usr/local/google/home/shuowei/src/google-cloud-python/google-cloud-python/packages/bigframes/.venv/lib/python3.13/site-packages (from tensorflow) (3.14.0)\n", - "Requirement already satisfied: ml_dtypes<1.0.0,>=0.5.1 in /usr/local/google/home/shuowei/src/google-cloud-python/google-cloud-python/packages/bigframes/.venv/lib/python3.13/site-packages (from tensorflow) (0.5.4)\n", - "Requirement already satisfied: wheel<1.0,>=0.23.0 in /usr/local/google/home/shuowei/src/google-cloud-python/google-cloud-python/packages/bigframes/.venv/lib/python3.13/site-packages (from astunparse>=1.6.0->tensorflow) (0.47.0)\n", - "Requirement already satisfied: aiohttp!=4.0.0a0,!=4.0.0a1 in /usr/local/google/home/shuowei/src/google-cloud-python/google-cloud-python/packages/bigframes/.venv/lib/python3.13/site-packages (from gcsfs!=2025.5.0,!=2026.2.0,!=2026.3.0,>=2023.3.0->bigframes) (3.13.5)\n", - "Requirement already satisfied: decorator>4.1.2 in /usr/local/google/home/shuowei/src/google-cloud-python/google-cloud-python/packages/bigframes/.venv/lib/python3.13/site-packages (from gcsfs!=2025.5.0,!=2026.2.0,!=2026.3.0,>=2023.3.0->bigframes) (5.2.1)\n", - "Requirement already satisfied: google-auth-oauthlib in /usr/local/google/home/shuowei/src/google-cloud-python/google-cloud-python/packages/bigframes/.venv/lib/python3.13/site-packages (from gcsfs!=2025.5.0,!=2026.2.0,!=2026.3.0,>=2023.3.0->bigframes) (1.3.1)\n", - "Requirement already satisfied: google-cloud-storage-control in /usr/local/google/home/shuowei/src/google-cloud-python/google-cloud-python/packages/bigframes/.venv/lib/python3.13/site-packages (from gcsfs!=2025.5.0,!=2026.2.0,!=2026.3.0,>=2023.3.0->bigframes) (1.11.0)\n", - "Requirement already satisfied: pyogrio>=0.7.2 in /usr/local/google/home/shuowei/src/google-cloud-python/google-cloud-python/packages/bigframes/.venv/lib/python3.13/site-packages (from geopandas>=0.12.2->bigframes) (0.12.1)\n", - "Requirement already satisfied: pyproj>=3.5.0 in /usr/local/google/home/shuowei/src/google-cloud-python/google-cloud-python/packages/bigframes/.venv/lib/python3.13/site-packages (from geopandas>=0.12.2->bigframes) (3.7.2)\n", - "Requirement already satisfied: googleapis-common-protos<2.0.0,>=1.63.2 in /usr/local/google/home/shuowei/src/google-cloud-python/google-cloud-python/packages/bigframes/.venv/lib/python3.13/site-packages (from google-api-core<3.0.0,>=2.11.0->google-api-core[grpc]<3.0.0,>=2.11.0->google-cloud-automl) (1.74.0)\n", - "Requirement already satisfied: grpcio-status<2.0.0,>=1.33.2 in /usr/local/google/home/shuowei/src/google-cloud-python/google-cloud-python/packages/bigframes/.venv/lib/python3.13/site-packages (from google-api-core[grpc]<3.0.0,>=2.11.0->google-cloud-automl) (1.80.0)\n", - "Requirement already satisfied: pyasn1-modules>=0.2.1 in /usr/local/google/home/shuowei/src/google-cloud-python/google-cloud-python/packages/bigframes/.venv/lib/python3.13/site-packages (from google-auth<3.0,>=2.15.0->bigframes) (0.4.2)\n", - "Requirement already satisfied: cryptography>=38.0.3 in /usr/local/google/home/shuowei/src/google-cloud-python/google-cloud-python/packages/bigframes/.venv/lib/python3.13/site-packages (from google-auth<3.0,>=2.15.0->bigframes) (46.0.7)\n", - "Requirement already satisfied: google-resumable-media<3.0.0,>=2.0.0 in /usr/local/google/home/shuowei/src/google-cloud-python/google-cloud-python/packages/bigframes/.venv/lib/python3.13/site-packages (from google-cloud-bigquery>=3.36.0->google-cloud-bigquery[bqstorage,pandas]>=3.36.0->bigframes) (2.8.2)\n", - "Requirement already satisfied: namex in /usr/local/google/home/shuowei/src/google-cloud-python/google-cloud-python/packages/bigframes/.venv/lib/python3.13/site-packages (from keras>=3.12.0->tensorflow) (0.1.0)\n", - "Requirement already satisfied: optree in /usr/local/google/home/shuowei/src/google-cloud-python/google-cloud-python/packages/bigframes/.venv/lib/python3.13/site-packages (from keras>=3.12.0->tensorflow) (0.19.0)\n", - "Requirement already satisfied: contourpy>=1.0.1 in /usr/local/google/home/shuowei/src/google-cloud-python/google-cloud-python/packages/bigframes/.venv/lib/python3.13/site-packages (from matplotlib>=3.7.1->bigframes) (1.3.3)\n", - "Requirement already satisfied: cycler>=0.10 in /usr/local/google/home/shuowei/src/google-cloud-python/google-cloud-python/packages/bigframes/.venv/lib/python3.13/site-packages (from matplotlib>=3.7.1->bigframes) (0.12.1)\n", - "Requirement already satisfied: fonttools>=4.22.0 in /usr/local/google/home/shuowei/src/google-cloud-python/google-cloud-python/packages/bigframes/.venv/lib/python3.13/site-packages (from matplotlib>=3.7.1->bigframes) (4.62.1)\n", - "Requirement already satisfied: kiwisolver>=1.3.1 in /usr/local/google/home/shuowei/src/google-cloud-python/google-cloud-python/packages/bigframes/.venv/lib/python3.13/site-packages (from matplotlib>=3.7.1->bigframes) (1.5.0)\n", - "Requirement already satisfied: pillow>=8 in /usr/local/google/home/shuowei/src/google-cloud-python/google-cloud-python/packages/bigframes/.venv/lib/python3.13/site-packages (from matplotlib>=3.7.1->bigframes) (12.2.0)\n", - "Requirement already satisfied: pyparsing>=3 in /usr/local/google/home/shuowei/src/google-cloud-python/google-cloud-python/packages/bigframes/.venv/lib/python3.13/site-packages (from matplotlib>=3.7.1->bigframes) (3.3.2)\n", - "Requirement already satisfied: tzdata>=2022.7 in /usr/local/google/home/shuowei/src/google-cloud-python/google-cloud-python/packages/bigframes/.venv/lib/python3.13/site-packages (from pandas>=1.5.3->bigframes) (2026.1)\n", - "Requirement already satisfied: psutil>=5.9.8 in /usr/local/google/home/shuowei/src/google-cloud-python/google-cloud-python/packages/bigframes/.venv/lib/python3.13/site-packages (from pandas-gbq>=0.26.1->bigframes) (7.2.2)\n", - "Requirement already satisfied: mmh3<6.0.0,>=4.0.0 in /usr/local/google/home/shuowei/src/google-cloud-python/google-cloud-python/packages/bigframes/.venv/lib/python3.13/site-packages (from pyiceberg>=0.7.1->bigframes) (5.2.1)\n", - "Requirement already satisfied: click<9.0.0,>=7.1.1 in /usr/local/google/home/shuowei/src/google-cloud-python/google-cloud-python/packages/bigframes/.venv/lib/python3.13/site-packages (from pyiceberg>=0.7.1->bigframes) (8.3.2)\n", - "Requirement already satisfied: strictyaml<2.0.0,>=1.7.0 in /usr/local/google/home/shuowei/src/google-cloud-python/google-cloud-python/packages/bigframes/.venv/lib/python3.13/site-packages (from pyiceberg>=0.7.1->bigframes) (1.7.3)\n", - "Requirement already satisfied: pydantic!=2.12.0,!=2.12.1,!=2.4.0,!=2.4.1,<3.0,>=2.0 in /usr/local/google/home/shuowei/src/google-cloud-python/google-cloud-python/packages/bigframes/.venv/lib/python3.13/site-packages (from pyiceberg>=0.7.1->bigframes) (2.12.5)\n", - "Requirement already satisfied: tenacity<10.0.0,>=8.2.3 in /usr/local/google/home/shuowei/src/google-cloud-python/google-cloud-python/packages/bigframes/.venv/lib/python3.13/site-packages (from pyiceberg>=0.7.1->bigframes) (9.1.4)\n", - "Requirement already satisfied: pyroaring<2.0.0,>=1.0.0 in /usr/local/google/home/shuowei/src/google-cloud-python/google-cloud-python/packages/bigframes/.venv/lib/python3.13/site-packages (from pyiceberg>=0.7.1->bigframes) (1.0.4)\n", - "Requirement already satisfied: cachetools<7.0,>=5.5 in /usr/local/google/home/shuowei/src/google-cloud-python/google-cloud-python/packages/bigframes/.venv/lib/python3.13/site-packages (from pyiceberg>=0.7.1->bigframes) (6.2.6)\n", - "Requirement already satisfied: zstandard<1.0.0,>=0.13.0 in /usr/local/google/home/shuowei/src/google-cloud-python/google-cloud-python/packages/bigframes/.venv/lib/python3.13/site-packages (from pyiceberg>=0.7.1->bigframes) (0.25.0)\n", - "Requirement already satisfied: charset_normalizer<4,>=2 in /usr/local/google/home/shuowei/src/google-cloud-python/google-cloud-python/packages/bigframes/.venv/lib/python3.13/site-packages (from requests>=2.27.1->bigframes) (3.4.7)\n", - "Requirement already satisfied: idna<4,>=2.5 in /usr/local/google/home/shuowei/src/google-cloud-python/google-cloud-python/packages/bigframes/.venv/lib/python3.13/site-packages (from requests>=2.27.1->bigframes) (3.11)\n", - "Requirement already satisfied: urllib3<3,>=1.26 in /usr/local/google/home/shuowei/src/google-cloud-python/google-cloud-python/packages/bigframes/.venv/lib/python3.13/site-packages (from requests>=2.27.1->bigframes) (2.6.3)\n", - "Requirement already satisfied: certifi>=2023.5.7 in /usr/local/google/home/shuowei/src/google-cloud-python/google-cloud-python/packages/bigframes/.venv/lib/python3.13/site-packages (from requests>=2.27.1->bigframes) (2026.2.25)\n", - "Requirement already satisfied: markdown-it-py>=2.2.0 in /usr/local/google/home/shuowei/src/google-cloud-python/google-cloud-python/packages/bigframes/.venv/lib/python3.13/site-packages (from rich<14,>=12.4.4->bigframes) (4.0.0)\n", - "Requirement already satisfied: pygments<3.0.0,>=2.13.0 in /usr/local/google/home/shuowei/src/google-cloud-python/google-cloud-python/packages/bigframes/.venv/lib/python3.13/site-packages (from rich<14,>=12.4.4->bigframes) (2.20.0)\n", - "Requirement already satisfied: aiohappyeyeballs>=2.5.0 in /usr/local/google/home/shuowei/src/google-cloud-python/google-cloud-python/packages/bigframes/.venv/lib/python3.13/site-packages (from aiohttp!=4.0.0a0,!=4.0.0a1->gcsfs!=2025.5.0,!=2026.2.0,!=2026.3.0,>=2023.3.0->bigframes) (2.6.1)\n", - "Requirement already satisfied: aiosignal>=1.4.0 in /usr/local/google/home/shuowei/src/google-cloud-python/google-cloud-python/packages/bigframes/.venv/lib/python3.13/site-packages (from aiohttp!=4.0.0a0,!=4.0.0a1->gcsfs!=2025.5.0,!=2026.2.0,!=2026.3.0,>=2023.3.0->bigframes) (1.4.0)\n", - "Requirement already satisfied: attrs>=17.3.0 in /usr/local/google/home/shuowei/src/google-cloud-python/google-cloud-python/packages/bigframes/.venv/lib/python3.13/site-packages (from aiohttp!=4.0.0a0,!=4.0.0a1->gcsfs!=2025.5.0,!=2026.2.0,!=2026.3.0,>=2023.3.0->bigframes) (26.1.0)\n", - "Requirement already satisfied: frozenlist>=1.1.1 in /usr/local/google/home/shuowei/src/google-cloud-python/google-cloud-python/packages/bigframes/.venv/lib/python3.13/site-packages (from aiohttp!=4.0.0a0,!=4.0.0a1->gcsfs!=2025.5.0,!=2026.2.0,!=2026.3.0,>=2023.3.0->bigframes) (1.8.0)\n", - "Requirement already satisfied: multidict<7.0,>=4.5 in /usr/local/google/home/shuowei/src/google-cloud-python/google-cloud-python/packages/bigframes/.venv/lib/python3.13/site-packages (from aiohttp!=4.0.0a0,!=4.0.0a1->gcsfs!=2025.5.0,!=2026.2.0,!=2026.3.0,>=2023.3.0->bigframes) (6.7.1)\n", - "Requirement already satisfied: propcache>=0.2.0 in /usr/local/google/home/shuowei/src/google-cloud-python/google-cloud-python/packages/bigframes/.venv/lib/python3.13/site-packages (from aiohttp!=4.0.0a0,!=4.0.0a1->gcsfs!=2025.5.0,!=2026.2.0,!=2026.3.0,>=2023.3.0->bigframes) (0.4.1)\n", - "Requirement already satisfied: yarl<2.0,>=1.17.0 in /usr/local/google/home/shuowei/src/google-cloud-python/google-cloud-python/packages/bigframes/.venv/lib/python3.13/site-packages (from aiohttp!=4.0.0a0,!=4.0.0a1->gcsfs!=2025.5.0,!=2026.2.0,!=2026.3.0,>=2023.3.0->bigframes) (1.23.0)\n", - "Requirement already satisfied: cffi>=2.0.0 in /usr/local/google/home/shuowei/src/google-cloud-python/google-cloud-python/packages/bigframes/.venv/lib/python3.13/site-packages (from cryptography>=38.0.3->google-auth<3.0,>=2.15.0->bigframes) (2.0.0)\n", - "Requirement already satisfied: requests-oauthlib>=0.7.0 in /usr/local/google/home/shuowei/src/google-cloud-python/google-cloud-python/packages/bigframes/.venv/lib/python3.13/site-packages (from google-auth-oauthlib->gcsfs!=2025.5.0,!=2026.2.0,!=2026.3.0,>=2023.3.0->bigframes) (2.0.0)\n", - "Requirement already satisfied: mdurl~=0.1 in /usr/local/google/home/shuowei/src/google-cloud-python/google-cloud-python/packages/bigframes/.venv/lib/python3.13/site-packages (from markdown-it-py>=2.2.0->rich<14,>=12.4.4->bigframes) (0.1.2)\n", - "Requirement already satisfied: pyasn1<0.7.0,>=0.6.1 in /usr/local/google/home/shuowei/src/google-cloud-python/google-cloud-python/packages/bigframes/.venv/lib/python3.13/site-packages (from pyasn1-modules>=0.2.1->google-auth<3.0,>=2.15.0->bigframes) (0.6.3)\n", - "Requirement already satisfied: annotated-types>=0.6.0 in /usr/local/google/home/shuowei/src/google-cloud-python/google-cloud-python/packages/bigframes/.venv/lib/python3.13/site-packages (from pydantic!=2.12.0,!=2.12.1,!=2.4.0,!=2.4.1,<3.0,>=2.0->pyiceberg>=0.7.1->bigframes) (0.7.0)\n", - "Requirement already satisfied: pydantic-core==2.41.5 in /usr/local/google/home/shuowei/src/google-cloud-python/google-cloud-python/packages/bigframes/.venv/lib/python3.13/site-packages (from pydantic!=2.12.0,!=2.12.1,!=2.4.0,!=2.4.1,<3.0,>=2.0->pyiceberg>=0.7.1->bigframes) (2.41.5)\n", - "Requirement already satisfied: typing-inspection>=0.4.2 in /usr/local/google/home/shuowei/src/google-cloud-python/google-cloud-python/packages/bigframes/.venv/lib/python3.13/site-packages (from pydantic!=2.12.0,!=2.12.1,!=2.4.0,!=2.4.1,<3.0,>=2.0->pyiceberg>=0.7.1->bigframes) (0.4.2)\n", - "Requirement already satisfied: pycparser in /usr/local/google/home/shuowei/src/google-cloud-python/google-cloud-python/packages/bigframes/.venv/lib/python3.13/site-packages (from cffi>=2.0.0->cryptography>=38.0.3->google-auth<3.0,>=2.15.0->bigframes) (3.0)\n", - "Requirement already satisfied: oauthlib>=3.0.0 in /usr/local/google/home/shuowei/src/google-cloud-python/google-cloud-python/packages/bigframes/.venv/lib/python3.13/site-packages (from requests-oauthlib>=0.7.0->google-auth-oauthlib->gcsfs!=2025.5.0,!=2026.2.0,!=2026.3.0,>=2023.3.0->bigframes) (3.3.1)\n", - "\n", - "\u001b[1m[\u001b[0m\u001b[34;49mnotice\u001b[0m\u001b[1;39;49m]\u001b[0m\u001b[39;49m A new release of pip is available: \u001b[0m\u001b[31;49m24.2\u001b[0m\u001b[39;49m -> \u001b[0m\u001b[32;49m26.1\u001b[0m\n", - "\u001b[1m[\u001b[0m\u001b[34;49mnotice\u001b[0m\u001b[1;39;49m]\u001b[0m\u001b[39;49m To update, run: \u001b[0m\u001b[32;49mpip install --upgrade pip\u001b[0m\n", - "Note: you may need to restart the kernel to use updated packages.\n" - ] - } - ], - "source": [ - "%pip install --upgrade bigframes google-cloud-automl google-cloud-translate google-ai-generativelanguage tensorflow " - ] - }, - { - "cell_type": "markdown", - "id": "c749e07c", - "metadata": {}, - "source": [ - "**Important:** restart the kernel by going to \"Run -> Restart & clear cell outputs\" before continuing.\n", - "\n", - "Configure bigframes to use your GCP project. First, go to \"Add-ons -> Google Cloud SDK\" and click the \"Attach\" button. Then," - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "5e00777d", - "metadata": { - "execution": { - "iopub.execute_input": "2025-08-18T20:17:14.873201Z", - "iopub.status.busy": "2025-08-18T20:17:14.872905Z", - "iopub.status.idle": "2025-08-18T20:17:14.946971Z", - "shell.execute_reply": "2025-08-18T20:17:14.945996Z", - "shell.execute_reply.started": "2025-08-18T20:17:14.873171Z" - }, - "trusted": true - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Not running on Kaggle, skipping Kaggle secrets initialization.\n" - ] - } - ], - "source": [ - "try:\n", - " from kaggle_secrets import UserSecretsClient\n", - " user_secrets = UserSecretsClient()\n", - " user_credential = user_secrets.get_gcloud_credential()\n", - " user_secrets.set_tensorflow_credential(user_credential)\n", - " print(\"Successfully authenticated using Kaggle secrets.\")\n", - "except ImportError:\n", - " print(\"Not running on Kaggle, skipping Kaggle secrets initialization.\")\n", - "except Exception as e:\n", - " print(f\"Could not initialize Kaggle secrets: {e}\")" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "b2e171de", - "metadata": { - "execution": { - "iopub.execute_input": "2025-08-18T20:17:25.574192Z", - "iopub.status.busy": "2025-08-18T20:17:25.573874Z", - "iopub.status.idle": "2025-08-18T20:17:45.102002Z", - "shell.execute_reply": "2025-08-18T20:17:45.101140Z", - "shell.execute_reply.started": "2025-08-18T20:17:25.574168Z" - }, - "trusted": true - }, - "outputs": [], - "source": [ - "PROJECT = \"bigframes-dev\" # replace with your project. \n", - "# Refer to https://cloud.google.com/bigquery/docs/multimodal-data-dataframes-tutorial#required_roles for your required permissions\n", - "\n", - "LOCATION = \"us\" # replace with your location.\n", - "DATASET_ID = \"bigframes_samples\" # replace with your dataset ID.\n", - "OUTPUT_BUCKET = \"bigframes_blob_test\" # replace with your GCS bucket. \n", - "\n", - "FULL_CONNECTION_ID = f\"{PROJECT}.{LOCATION}.bigframes-default-connection\"\n", - "\n", - "import bigframes\n", - "# Setup project\n", - "bigframes.options.bigquery.project = PROJECT\n", - "bigframes.options.bigquery.location = LOCATION\n", - "\n", - "# Display options\n", - "bigframes.options.display.blob_display_width = 300\n", - "bigframes.options.display.progress_bar = None\n", - "\n", - "import bigframes.pandas as bpd\n", - "import bigframes.bigquery as bbq\n", - "\n", - "def get_runtime_json_str(series, mode=\"R\", with_metadata=False):\n", - " \"\"\"Get runtime JSON from objectref.\"\"\"\n", - " s = bbq.obj.fetch_metadata(series) if with_metadata else series\n", - " runtime = bbq.obj.get_access_url(s, mode=mode)\n", - " return bbq.to_json_string(runtime)\n", - "\n", - "def get_metadata(series):\n", - " metadata_obj = bbq.obj.fetch_metadata(series)\n", - " return bbq.json_query(metadata_obj.struct.field(\"details\"), \"$.gcs_metadata\")\n", - "\n", - "def get_content_type(series):\n", - " return bbq.json_value(get_metadata(series), \"$.content_type\")\n", - "\n", - "def get_size(series):\n", - " return bbq.json_value(get_metadata(series), \"$.size\").astype(\"Int64\")\n", - "\n", - "def get_updated(series):\n", - " return bpd.to_datetime(bbq.json_value(get_metadata(series), \"$.updated\").astype(\"Int64\"), unit=\"us\", utc=True)\n", - "\n", - "from IPython.display import HTML, display\n", - "\n", - "def render_images(df):\n", - " \"\"\"Helper to display BigFrames DataFrame with rendered image previews.\"\"\"\n", - " import bigframes.pandas as bpd\n", - " import bigframes.bigquery as bbq\n", - " import bigframes\n", - " from bigframes import dtypes\n", - " import json\n", - " \n", - " if isinstance(df, bpd.Series):\n", - " df = df.to_frame()\n", - " \n", - " object_cols = [\n", - " col for col, dtype in zip(df.columns, df.dtypes)\n", - " if dtype == dtypes.OBJ_REF_DTYPE\n", - " ]\n", - " \n", - " if not object_cols:\n", - " display(df)\n", - " return\n", - "\n", - " limit = bigframes.options.display.max_rows or 10\n", - " view_df = df.head(limit)\n", - " \n", - " runtime_cols = {\n", - " col: get_runtime_json_str(view_df[col], mode=\"R\", with_metadata=False) \n", - " for col in object_cols\n", - " }\n", - " \n", - " pandas_json_df = bpd.DataFrame(runtime_cols).to_pandas()\n", - " final_pd = view_df.to_pandas()\n", - " \n", - " width = bigframes.options.display.blob_display_width or 300\n", - " IMAGE_EXTENSIONS = (\".png\", \".jpg\", \".jpeg\", \".gif\", \".webp\")\n", - " \n", - " def format_cell_html(raw_json):\n", - " if not raw_json:\n", - " return \"\"\n", - " try:\n", - " obj_rt = json.loads(raw_json)\n", - " if \"access_urls\" not in obj_rt:\n", - " err = obj_rt.get(\"errors\", [{\"message\": \"URL Generation Failed\"}])[0].get(\"message\")\n", - " return f'Error: {err}'\n", - " \n", - " uri = obj_rt.get(\"objectref\", {}).get(\"uri\", \"\")\n", - " url = obj_rt[\"access_urls\"][\"read_url\"]\n", - " \n", - " if uri and str(uri).lower().endswith(IMAGE_EXTENSIONS):\n", - " return f''\n", - " \n", - " return f'{uri if uri else \"view\"}'\n", - " except:\n", - " return \"Format Error\"\n", - "\n", - " for col in object_cols:\n", - " final_pd[col] = pandas_json_df[col].map(format_cell_html)\n", - " \n", - " display(HTML(final_pd.to_html(escape=False)))" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "d17afaf1", - "metadata": { - "execution": { - "iopub.execute_input": "2025-08-18T20:17:45.103530Z", - "iopub.status.busy": "2025-08-18T20:17:45.103249Z", - "iopub.status.idle": "2025-08-18T20:17:47.424586Z", - "shell.execute_reply": "2025-08-18T20:17:47.423762Z", - "shell.execute_reply.started": "2025-08-18T20:17:45.103499Z" - }, - "trusted": true - }, - "outputs": [], - "source": [ - "import gcsfs\n", - "import bigframes.bigquery as bbq\n", - "\n", - "# List files using gcsfs (public bucket)\n", - "fs = gcsfs.GCSFileSystem(anon=True)\n", - "uris = fs.glob(\"gs://cloud-samples-data/bigquery/tutorials/cymbal-pets/images/*\")\n", - "\n", - "# Ensure URIs have gs:// prefix\n", - "uris = [u if u.startswith(\"gs://\") else f\"gs://{u}\" for u in uris]\n", - "\n", - "# Read the URIs into a BigQuery DataFrame using UNNEST\n", - "# We take the first 5 for this example\n", - "df_image = bpd.read_gbq(f\"SELECT uri FROM UNNEST({uris[:5]}) as uri\")\n", - "\n", - "# Create the object reference column\n", - "df_image['image'] = bbq.obj.make_ref(df_image['uri'], authorizer=FULL_CONNECTION_ID)\n", - "df_image = df_image[['image']]" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "id": "3e84b922", - "metadata": { - "execution": { - "iopub.execute_input": "2025-08-18T20:17:47.425873Z", - "iopub.status.busy": "2025-08-18T20:17:47.425578Z", - "iopub.status.idle": "2025-08-18T20:18:07.919961Z", - "shell.execute_reply": "2025-08-18T20:18:07.918942Z", - "shell.execute_reply.started": "2025-08-18T20:17:47.425844Z" - }, - "trusted": true - }, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/usr/local/google/home/shuowei/src/google-cloud-python/google-cloud-python/packages/bigframes/bigframes/dtypes.py:1044: JSONDtypeWarning: JSON columns will be represented as pandas.ArrowDtype(pyarrow.json_())\n", - "instead of using `db_dtypes` in the future when available in pandas\n", - "(https://github.com/pandas-dev/pandas/issues/60958) and pyarrow.\n", - " warnings.warn(msg, bigframes.exceptions.JSONDtypeWarning)\n" - ] - }, - { - "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", - "
image
0
1
2
3
4
" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "# Take only the 5 images to deal with. Preview the content of the Mutimodal DataFrame\n", - "df_image = df_image.head(5)\n", - "render_images(df_image)" - ] - }, - { - "cell_type": "markdown", - "id": "b0eaa73c", - "metadata": {}, - "source": [ - "# 2. Combine unstructured data with structured data\n", - "\n", - "Now you can put more information into the table to describe the files. Such as author info from inputs, or other metadata from the gcs object itself." - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "id": "7d64fb54", - "metadata": { - "execution": { - "iopub.execute_input": "2025-08-18T20:18:07.922593Z", - "iopub.status.busy": "2025-08-18T20:18:07.921884Z", - "iopub.status.idle": "2025-08-18T20:18:35.549725Z", - "shell.execute_reply": "2025-08-18T20:18:35.548942Z", - "shell.execute_reply.started": "2025-08-18T20:18:07.922551Z" - }, - "trusted": true - }, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/usr/local/google/home/shuowei/src/google-cloud-python/google-cloud-python/packages/bigframes/bigframes/dtypes.py:1044: JSONDtypeWarning: JSON columns will be represented as pandas.ArrowDtype(pyarrow.json_())\n", - "instead of using `db_dtypes` in the future when available in pandas\n", - "(https://github.com/pandas-dev/pandas/issues/60958) and pyarrow.\n", - " warnings.warn(msg, bigframes.exceptions.JSONDtypeWarning)\n" - ] - }, - { - "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", - "
imageauthorcontent_typesizeupdated
0aliceimage/png7157662025-03-20 17:44:38+00:00
1bobimage/png11674062025-03-20 17:44:38+00:00
2bobimage/png11508922025-03-20 17:44:39+00:00
3aliceimage/png17365332025-03-20 17:44:39+00:00
4bobimage/png4397402025-03-20 17:44:39+00:00
" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "# Combine unstructured data with structured data\n", - "df_image[\"author\"] = [\"alice\", \"bob\", \"bob\", \"alice\", \"bob\"] # type: ignore\n", - "df_image[\"content_type\"] = get_content_type(df_image[\"image\"])\n", - "df_image[\"size\"] = get_size(df_image[\"image\"])\n", - "df_image[\"updated\"] = get_updated(df_image[\"image\"])\n", - "render_images(df_image)" - ] - }, - { - "cell_type": "markdown", - "id": "a23ef0e4", - "metadata": {}, - "source": [ - "Then you can filter the rows based on the structured data. And for different content types, you can display them respectively or together." - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "id": "ce102df0", - "metadata": { - "execution": { - "iopub.execute_input": "2025-08-18T20:18:55.300314Z", - "iopub.status.busy": "2025-08-18T20:18:55.299993Z", - "iopub.status.idle": "2025-08-18T20:19:09.154492Z", - "shell.execute_reply": "2025-08-18T20:19:09.153315Z", - "shell.execute_reply.started": "2025-08-18T20:18:55.300289Z" - }, - "trusted": true - }, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/usr/local/google/home/shuowei/src/google-cloud-python/google-cloud-python/packages/bigframes/bigframes/dtypes.py:1044: JSONDtypeWarning: JSON columns will be represented as pandas.ArrowDtype(pyarrow.json_())\n", - "instead of using `db_dtypes` in the future when available in pandas\n", - "(https://github.com/pandas-dev/pandas/issues/60958) and pyarrow.\n", - " warnings.warn(msg, bigframes.exceptions.JSONDtypeWarning)\n" - ] - }, - { - "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", - "
imageauthorcontent_typesizeupdated
0aliceimage/png7157662025-03-20 17:44:38+00:00
3aliceimage/png17365332025-03-20 17:44:39+00:00
" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "# filter images and display, you can also display audio and video types\n", - "filtered_df = df_image[df_image[\"author\"] == \"alice\"]\n", - "render_images(filtered_df)" - ] - }, - { - "cell_type": "markdown", - "id": "db2b3b12", - "metadata": {}, - "source": [ - "# 3. Conduct image transformations\n", - "\n", - "BigFrames Multimodal DataFrame provides image(and other) transformation functions. Such as image_blur, image_resize and image_normalize. The output can be saved to GCS folders or to BQ as bytes." - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "id": "283036f5", - "metadata": { - "execution": { - "iopub.execute_input": "2025-08-18T20:19:22.950652Z", - "iopub.status.busy": "2025-08-18T20:19:22.950277Z", - "iopub.status.idle": "2025-08-18T20:31:51.799997Z", - "shell.execute_reply": "2025-08-18T20:31:51.798840Z", - "shell.execute_reply.started": "2025-08-18T20:19:22.950625Z" - }, - "trusted": true - }, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/usr/local/google/home/shuowei/src/google-cloud-python/google-cloud-python/packages/bigframes/bigframes/pandas/__init__.py:211: PreviewWarning: udf is in preview.\n", - " return global_session.with_default_session(\n", - "/usr/local/google/home/shuowei/src/google-cloud-python/google-cloud-python/packages/bigframes/bigframes/dataframe.py:4695: FunctionAxisOnePreviewWarning: DataFrame.apply with parameter axis=1 scenario is in preview.\n", - " warnings.warn(msg, category=bfe.FunctionAxisOnePreviewWarning)\n", - "/usr/local/google/home/shuowei/src/google-cloud-python/google-cloud-python/packages/bigframes/bigframes/dtypes.py:1044: JSONDtypeWarning: JSON columns will be represented as pandas.ArrowDtype(pyarrow.json_())\n", - "instead of using `db_dtypes` in the future when available in pandas\n", - "(https://github.com/pandas-dev/pandas/issues/60958) and pyarrow.\n", - " warnings.warn(msg, bigframes.exceptions.JSONDtypeWarning)\n" - ] - }, - { - "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", - "
imageblurred
0
1
2
3
4
" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "@bpd.udf(\n", - " input_types=[str, str, int, int],\n", - " output_type=str,\n", - " dataset=DATASET_ID,\n", - " name=\"image_blur_kaggle\",\n", - " bigquery_connection=FULL_CONNECTION_ID,\n", - " packages=[\"opencv-python-headless\", \"numpy\", \"requests\"],\n", - ")\n", - "def image_blur(src_rt: str, dst_rt: str, kx: int, ky: int) -> str:\n", - " import json\n", - " import cv2 as cv\n", - " import numpy as np\n", - " import requests\n", - " \n", - " src_obj = json.loads(src_rt)\n", - " if \"access_urls\" not in src_obj:\n", - " raise ValueError(f\"Missing 'access_urls' in source object. Response: {src_obj}\")\n", - " src_url = src_obj[\"access_urls\"][\"read_url\"]\n", - " \n", - " response = requests.get(src_url, timeout=30)\n", - " response.raise_for_status()\n", - " \n", - " img = cv.imdecode(np.frombuffer(response.content, np.uint8), cv.IMREAD_UNCHANGED)\n", - " if img is None:\n", - " raise ValueError(\"cv.imdecode failed\")\n", - " \n", - " img_blurred = cv.blur(img, ksize=(int(kx), int(ky)))\n", - " success, encoded = cv.imencode(\".jpeg\", img_blurred)\n", - " \n", - " if not success:\n", - " raise ValueError(\"cv.imencode failed\")\n", - " \n", - " if dst_rt: # GCS Output Mode\n", - " dst_obj = json.loads(dst_rt)\n", - " if \"access_urls\" not in dst_obj:\n", - " raise ValueError(f\"Missing 'access_urls' in destination object. Response: {dst_obj}\")\n", - " dst_url = dst_obj[\"access_urls\"][\"write_url\"]\n", - " \n", - " requests.put(dst_url, data=encoded.tobytes(), headers={\"Content-Type\": \"image/jpeg\"}, timeout=30).raise_for_status()\n", - " return dst_obj[\"objectref\"][\"uri\"]\n", - " return \"\"\n", - "\n", - "def apply_transformation(series, dst_folder, udf, *args, verbose=False):\n", - " import os\n", - " dst_folder = os.path.join(dst_folder, \"\")\n", - " metadata = bbq.obj.fetch_metadata(series)\n", - " current_uri = metadata.struct.field(\"uri\")\n", - " dst_uri = current_uri.str.replace(r\"^.*\\/(.*)$\", rf\"{dst_folder}\\1\", regex=True)\n", - " \n", - " # Bypass synchronous validation via JSON initialization\n", - " dst_blob_df = bpd.DataFrame({\"uri\": dst_uri})\n", - " dst_blob_df[\"authorizer\"] = FULL_CONNECTION_ID\n", - " dst_blob = bbq.obj.make_ref(bbq.to_json(bbq.struct(dst_blob_df)))\n", - "\n", - " df_transform = bpd.DataFrame({\n", - " \"src_rt\": get_runtime_json_str(series, mode=\"R\"),\n", - " \"dst_rt\": get_runtime_json_str(dst_blob, mode=\"RW\"),\n", - " })\n", - " res = df_transform[[\"src_rt\", \"dst_rt\"]].apply(udf, axis=1, args=args)\n", - " \n", - " if verbose:\n", - " return res\n", - " \n", - " res_df = bpd.DataFrame({\"uri\": res})\n", - " res_df[\"authorizer\"] = FULL_CONNECTION_ID\n", - " return bbq.obj.make_ref(bbq.to_json(bbq.struct(res_df)))\n", - "\n", - "# Apply Blur Transformation\n", - "df_image[\"blurred\"] = apply_transformation(\n", - " df_image[\"image\"], f\"gs://{OUTPUT_BUCKET}/image_blur_transformed/\",\n", - " image_blur, 20, 20\n", - ")\n", - "render_images(df_image[[\"image\", \"blurred\"]])" - ] - }, - { - "cell_type": "markdown", - "id": "2d68a468", - "metadata": {}, - "source": [ - "# 4. Use LLM models to ask questions and generate embeddings on images" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "id": "662054a0", - "metadata": { - "execution": { - "iopub.execute_input": "2025-08-18T20:36:13.954686Z", - "iopub.status.busy": "2025-08-18T20:36:13.954340Z", - "iopub.status.idle": "2025-08-18T20:36:43.225449Z", - "shell.execute_reply": "2025-08-18T20:36:43.224579Z", - "shell.execute_reply.started": "2025-08-18T20:36:13.954661Z" - }, - "trusted": true - }, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/usr/local/google/home/shuowei/src/google-cloud-python/google-cloud-python/packages/bigframes/bigframes/core/logging/log_adapter.py:183: FutureWarning: Since upgrading the default model can cause unintended breakages, the\n", - "default model will be removed in BigFrames 3.0. Please supply an\n", - "explicit model to avoid this message.\n", - " return method(*args, **kwargs)\n", - "/usr/local/google/home/shuowei/src/google-cloud-python/google-cloud-python/packages/bigframes/bigframes/session/__init__.py:437: FutureWarning: You are using the BigFrames session default connection: bigframes-\n", - "default-connection, which can be different from the\n", - "BigQuery project default connection. This default\n", - "connection may change in the future.\n", - " warnings.warn(msg, category=FutureWarning)\n" - ] - } - ], - "source": [ - "from bigframes.ml import llm\n", - "gemini = llm.GeminiTextGenerator()" - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "id": "a31730ff", - "metadata": { - "execution": { - "iopub.execute_input": "2025-08-18T20:36:43.227798Z", - "iopub.status.busy": "2025-08-18T20:36:43.227457Z", - "iopub.status.idle": "2025-08-18T20:37:25.238649Z", - "shell.execute_reply": "2025-08-18T20:37:25.237623Z", - "shell.execute_reply.started": "2025-08-18T20:36:43.227764Z" - }, - "trusted": true - }, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/usr/local/google/home/shuowei/src/google-cloud-python/google-cloud-python/packages/bigframes/bigframes/dtypes.py:1044: JSONDtypeWarning: JSON columns will be represented as pandas.ArrowDtype(pyarrow.json_())\n", - "instead of using `db_dtypes` in the future when available in pandas\n", - "(https://github.com/pandas-dev/pandas/issues/60958) and pyarrow.\n", - " warnings.warn(msg, bigframes.exceptions.JSONDtypeWarning)\n" - ] - }, - { - "data": { - "text/html": [ - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
ml_generate_text_llm_resultimage
0Please provide me with the picture! I need to see the image to tell you what the item is and what color the picture is.\\n
1To answer your question accurately, I need you to provide me with the picture you are referring to. Once you provide the picture, I can analyze it and tell you what item is in the picture and what color the picture is.
" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "# Ask the same question on the images\n", - "df_image = df_image.head(2)\n", - "answer = gemini.predict(df_image, prompt=[\"what item is it?\", \"what color is the picture?\"])\n", - "render_images(answer[[\"ml_generate_text_llm_result\", \"image\"]])" - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "id": "f5d2a1ed", - "metadata": { - "execution": { - "iopub.execute_input": "2025-08-18T20:37:25.239875Z", - "iopub.status.busy": "2025-08-18T20:37:25.239607Z", - "iopub.status.idle": "2025-08-18T20:37:25.263034Z", - "shell.execute_reply": "2025-08-18T20:37:25.262002Z", - "shell.execute_reply.started": "2025-08-18T20:37:25.239847Z" - }, - "trusted": true - }, - "outputs": [], - "source": [ - "# Ask different questions\n", - "df_image[\"question\"] = [\"what item is it?\", \"what color is the picture?\"]" - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "id": "fb67bf8e", - "metadata": { - "execution": { - "iopub.execute_input": "2025-08-18T20:37:25.264585Z", - "iopub.status.busy": "2025-08-18T20:37:25.264072Z", - "iopub.status.idle": "2025-08-18T20:38:10.129667Z", - "shell.execute_reply": "2025-08-18T20:38:10.128677Z", - "shell.execute_reply.started": "2025-08-18T20:37:25.264518Z" - }, - "trusted": true - }, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/usr/local/google/home/shuowei/src/google-cloud-python/google-cloud-python/packages/bigframes/bigframes/dtypes.py:1044: JSONDtypeWarning: JSON columns will be represented as pandas.ArrowDtype(pyarrow.json_())\n", - "instead of using `db_dtypes` in the future when available in pandas\n", - "(https://github.com/pandas-dev/pandas/issues/60958) and pyarrow.\n", - " warnings.warn(msg, bigframes.exceptions.JSONDtypeWarning)\n", - "/usr/local/google/home/shuowei/src/google-cloud-python/google-cloud-python/packages/bigframes/bigframes/dtypes.py:1044: JSONDtypeWarning: JSON columns will be represented as pandas.ArrowDtype(pyarrow.json_())\n", - "instead of using `db_dtypes` in the future when available in pandas\n", - "(https://github.com/pandas-dev/pandas/issues/60958) and pyarrow.\n", - " warnings.warn(msg, bigframes.exceptions.JSONDtypeWarning)\n" - ] - }, - { - "data": { - "text/html": [ - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
ml_generate_text_llm_resultimage
0The item is a glass aquarium.
1Dark brown
" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "answer_alt = gemini.predict(df_image, prompt=[df_image[\"question\"], df_image[\"image\"]])\n", - "render_images(answer_alt[[\"ml_generate_text_llm_result\", \"image\"]])" - ] - }, - { - "cell_type": "code", - "execution_count": 14, - "id": "0cf33170", - "metadata": { - "execution": { - "iopub.execute_input": "2025-08-18T20:38:10.130851Z", - "iopub.status.busy": "2025-08-18T20:38:10.130617Z", - "iopub.status.idle": "2025-08-18T20:39:04.790416Z", - "shell.execute_reply": "2025-08-18T20:39:04.789398Z", - "shell.execute_reply.started": "2025-08-18T20:38:10.130833Z" - }, - "trusted": true - }, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/usr/local/google/home/shuowei/src/google-cloud-python/google-cloud-python/packages/bigframes/bigframes/core/logging/log_adapter.py:183: FutureWarning: Since upgrading the default model can cause unintended breakages, the\n", - "default model will be removed in BigFrames 3.0. Please supply an\n", - "explicit model to avoid this message.\n", - " return method(*args, **kwargs)\n", - "/usr/local/google/home/shuowei/src/google-cloud-python/google-cloud-python/packages/bigframes/bigframes/session/__init__.py:437: FutureWarning: You are using the BigFrames session default connection: bigframes-\n", - "default-connection, which can be different from the\n", - "BigQuery project default connection. This default\n", - "connection may change in the future.\n", - " warnings.warn(msg, category=FutureWarning)\n", - "/usr/local/google/home/shuowei/src/google-cloud-python/google-cloud-python/packages/bigframes/bigframes/dtypes.py:1044: JSONDtypeWarning: JSON columns will be represented as pandas.ArrowDtype(pyarrow.json_())\n", - "instead of using `db_dtypes` in the future when available in pandas\n", - "(https://github.com/pandas-dev/pandas/issues/60958) and pyarrow.\n", - " warnings.warn(msg, bigframes.exceptions.JSONDtypeWarning)\n" - ] - }, - { - "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", - "
ml_generate_embedding_resultml_generate_embedding_statusml_generate_embedding_start_secml_generate_embedding_end_seccontent
0[ 0.03416207 0.0419732 -0.0227391 ... -0.03...<NA><NA>{\"access_urls\":{\"expiry_time\":\"2026-05-02T03:3...
1[ 0.01908903 0.0193082 -0.00221754 ... 0.00...<NA><NA>{\"access_urls\":{\"expiry_time\":\"2026-05-02T03:3...
\n", - "

2 rows × 5 columns

\n", - "
[2 rows x 5 columns in total]" - ], - "text/plain": [ - " ml_generate_embedding_result \\\n", - "0 [ 0.03416207 0.0419732 -0.0227391 ... -0.03... \n", - "1 [ 0.01908903 0.0193082 -0.00221754 ... 0.00... \n", - "\n", - " ml_generate_embedding_status ml_generate_embedding_start_sec \\\n", - "0 \n", - "1 \n", - "\n", - " ml_generate_embedding_end_sec \\\n", - "0 \n", - "1 \n", - "\n", - " content \n", - "0 {\"access_urls\":{\"expiry_time\":\"2026-05-02T03:3... \n", - "1 {\"access_urls\":{\"expiry_time\":\"2026-05-02T03:3... \n", - "\n", - "[2 rows x 5 columns]" - ] - }, - "execution_count": 14, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# Generate embeddings.\n", - "embed_model = llm.MultimodalEmbeddingGenerator()\n", - "embeddings = embed_model.predict(df_image[\"image\"])\n", - "embeddings" - ] - } - ], - "metadata": { - "kaggle": { - "accelerator": "none", - "dataSources": [ - { - "databundleVersionId": 13391012, - "sourceId": 110281, - "sourceType": "competition" - } - ], - "dockerImageVersionId": 31089, - "isGpuEnabled": false, - "isInternetEnabled": true, - "language": "python", - "sourceType": "notebook" - }, - "kernelspec": { - "display_name": "Python 3", - "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.11.13" - } - }, - "nbformat": 4, - "nbformat_minor": 4 -} diff --git a/notebooks/kaggle/vector-search-with-bigframes-over-national-jukebox.ipynb b/notebooks/kaggle/vector-search-with-bigframes-over-national-jukebox.ipynb deleted file mode 100644 index 317ba0f1adb..00000000000 --- a/notebooks/kaggle/vector-search-with-bigframes-over-national-jukebox.ipynb +++ /dev/null @@ -1,1201 +0,0 @@ -{ - "cells": [ - { - "id": "f4ece66a", - "cell_type": "markdown", - "source": [ - "# Creating a searchable index of the National Jukebox\n", - "\n", - "_Extracting text from audio and indexing it with BigQuery DataFrames_\n", - "\n", - "* Tim Swena (formerly, Swast)\n", - "* swast@google.com\n", - "* https://vis.social/@timswast on Mastodon\n", - "\n", - "This notebook lives in\n", - "\n", - "* https://github.com/tswast/code-snippets\n", - "* at https://github.com/tswast/code-snippets/blob/main/2025/national-jukebox/transcribe_songs.ipynb\n", - "\n", - "To follow along, you'll need a Google Cloud project\n", - "\n", - "* Go to https://cloud.google.com/free to start a free trial." - ], - "metadata": { - "@deathbeds/jupyterlab-fonts": { - "styles": { - "": { - "body[data-jp-deck-mode='presenting'] &": { - "zoom": "194%" - } - } - } - }, - "editable": true, - "slideshow": { - "slide_type": "subslide" - }, - "tags": [] - }, - "execution_count": null - }, - { - "id": "bc01a1d3", - "cell_type": "markdown", - "source": [ - "The National Jukebox is a project of the USA Library of Congress to provide access to thousands of acoustic sound recordings from the very earliest days of the commercial record industry.\n", - "\n", - "* Learn more at https://www.loc.gov/collections/national-jukebox/about-this-collection/\n", - "\n", - "\"recording" - ], - "metadata": { - "@deathbeds/jupyterlab-fonts": { - "styles": { - "": { - "body[data-jp-deck-mode='presenting'] &": { - "z-index": "0", - "zoom": "216%" - } - } - } - }, - "slideshow": { - "slide_type": "slide" - } - }, - "execution_count": null - }, - { - "id": "4fc7c468", - "cell_type": "markdown", - "source": [ - "\n", - "To search the National Jukebox, we combine powerful features of BigQuery:\n", - "\n", - "\"audio\n", - "\n", - "1. Integrations with multi-modal AI models to extract information from unstructured data, in this case audio files.\n", - "\n", - " https://cloud.google.com/bigquery/docs/multimodal-data-dataframes-tutorial\n", - " \n", - "2. Vector search to find similar text using embedding models.\n", - "\n", - " https://cloud.google.com/bigquery/docs/vector-index-text-search-tutorial\n", - "\n", - "3. BigQuery DataFrames to use Python instead of SQL.\n", - "\n", - " https://cloud.google.com/bigquery/docs/bigquery-dataframes-introduction" - ], - "metadata": { - "@deathbeds/jupyterlab-fonts": { - "styles": { - "": { - "body[data-jp-deck-mode='presenting'] &": { - "z-index": "0", - "zoom": "181%" - } - } - } - }, - "slideshow": { - "slide_type": "slide" - } - }, - "execution_count": null - }, - { - "id": "90f2e543", - "cell_type": "markdown", - "source": [ - "## Getting started with BigQuery DataFrames (bigframes)\n", - "\n", - "Install the bigframes package." - ], - "metadata": { - "@deathbeds/jupyterlab-fonts": { - "styles": { - "": { - "body[data-jp-deck-mode='presenting'] &": { - "zoom": "275%" - } - } - } - }, - "slideshow": { - "slide_type": "slide" - } - }, - "execution_count": null - }, - { - "id": "56694cb4", - "cell_type": "code", - "source": [ - "%pip install --upgrade bigframes google-cloud-automl google-cloud-translate google-ai-generativelanguage tensorflow " - ], - "metadata": { - "@deathbeds/jupyterlab-fonts": { - "styles": { - "": { - "body[data-jp-deck-mode='presenting'] &": { - "zoom": "214%" - } - } - } - }, - "execution": { - "iopub.execute_input": "2025-08-14T15:53:02.494188Z", - "iopub.status.busy": "2025-08-14T15:53:02.493469Z", - "iopub.status.idle": "2025-08-14T15:53:08.492291Z", - "shell.execute_reply": "2025-08-14T15:53:08.491183Z", - "shell.execute_reply.started": "2025-08-14T15:53:02.494152Z" - }, - "trusted": true - }, - "execution_count": null, - "outputs": [] - }, - { - "id": "fa84ad03", - "cell_type": "markdown", - "source": [ - "**Important:** restart the kernel by going to \"Run -> Restart & clear cell outputs\" before continuing.\n", - "\n", - "Configure bigframes to use your GCP project. First, go to \"Add-ons -> Google Cloud SDK\" and click the \"Attach\" button. Then," - ], - "metadata": { - "@deathbeds/jupyterlab-fonts": { - "styles": { - "": { - "body[data-jp-deck-mode='presenting'] &": { - "z-index": "4", - "zoom": "236%" - } - } - } - } - }, - "execution_count": null - }, - { - "id": "1fbd4f9e", - "cell_type": "code", - "source": [ - "from kaggle_secrets import UserSecretsClient\n", - "user_secrets = UserSecretsClient()\n", - "user_credential = user_secrets.get_gcloud_credential()\n", - "user_secrets.set_tensorflow_credential(user_credential)" - ], - "metadata": { - "execution": { - "iopub.execute_input": "2025-08-14T15:53:08.494636Z", - "iopub.status.busy": "2025-08-14T15:53:08.494313Z", - "iopub.status.idle": "2025-08-14T15:53:08.609706Z", - "shell.execute_reply": "2025-08-14T15:53:08.608705Z", - "shell.execute_reply.started": "2025-08-14T15:53:08.494604Z" - }, - "trusted": true - }, - "execution_count": null, - "outputs": [] - }, - { - "id": "0b0b1cd8", - "cell_type": "code", - "source": [ - "import bigframes._config\n", - "import bigframes.pandas as bpd\n", - "\n", - "PROJECT_ID = \"your-project-id\" # @param {type:\"string\"}\n", - "bpd.options.bigquery.location = \"US\"\n", - "\n", - "# Set to your GCP project ID.\n", - "bpd.options.bigquery.project = PROJECT_ID" - ], - "metadata": { - "@deathbeds/jupyterlab-fonts": { - "styles": { - "": { - "body[data-jp-deck-mode='presenting'] &": { - "zoom": "193%" - } - } - } - }, - "execution": { - "iopub.execute_input": "2025-08-14T15:53:08.610982Z", - "iopub.status.busy": "2025-08-14T15:53:08.610686Z", - "iopub.status.idle": "2025-08-14T15:53:17.658993Z", - "shell.execute_reply": "2025-08-14T15:53:17.657745Z", - "shell.execute_reply.started": "2025-08-14T15:53:08.610961Z" - }, - "trusted": true - }, - "execution_count": null, - "outputs": [] - }, - { - "id": "32e58a7f", - "cell_type": "markdown", - "source": [ - "## Reading data\n", - "\n", - "BigQuery DataFrames can read data from BigQuery, GCS, or even local sources. With `engine=\"bigquery\"`, BigQuery's distributed processing reads the file without it ever having to reach your local Python environment." - ], - "metadata": { - "@deathbeds/jupyterlab-fonts": { - "styles": { - "": { - "body[data-jp-deck-mode='presenting'] &": { - "zoom": "207%" - } - } - } - }, - "slideshow": { - "slide_type": "slide" - } - }, - "execution_count": null - }, - { - "id": "e52aa9e8", - "cell_type": "code", - "source": [ - "df = bpd.read_json(\n", - " \"gs://cloud-samples-data/third-party/usa-loc-national-jukebox/jukebox.jsonl\",\n", - " engine=\"bigquery\",\n", - " orient=\"records\",\n", - " lines=True,\n", - ")" - ], - "metadata": { - "@deathbeds/jupyterlab-fonts": { - "styles": { - "": { - "body[data-jp-deck-mode='presenting'] &": { - "zoom": "225%" - } - } - } - }, - "execution": { - "iopub.execute_input": "2025-08-14T15:53:17.662234Z", - "iopub.status.busy": "2025-08-14T15:53:17.661901Z", - "iopub.status.idle": "2025-08-14T15:53:34.486799Z", - "shell.execute_reply": "2025-08-14T15:53:34.485777Z", - "shell.execute_reply.started": "2025-08-14T15:53:17.662207Z" - }, - "trusted": true - }, - "execution_count": null, - "outputs": [] - }, - { - "id": "0c1fca97", - "cell_type": "code", - "source": [ - "# Use `peek()` instead of `head()` to see arbitrary rows rather than the \"first\" rows.\n", - "df.peek()" - ], - "metadata": { - "@deathbeds/jupyterlab-fonts": { - "styles": { - "": { - "body[data-jp-deck-mode='presenting'] &": { - "zoom": "122%" - } - } - } - }, - "execution": { - "iopub.execute_input": "2025-08-14T15:53:34.488610Z", - "iopub.status.busy": "2025-08-14T15:53:34.488332Z", - "iopub.status.idle": "2025-08-14T15:53:40.347014Z", - "shell.execute_reply": "2025-08-14T15:53:40.345773Z", - "shell.execute_reply.started": "2025-08-14T15:53:34.488589Z" - }, - "slideshow": { - "slide_type": "slide" - }, - "trusted": true - }, - "execution_count": null, - "outputs": [] - }, - { - "id": "4a13e789", - "cell_type": "code", - "source": [ - "df.shape" - ], - "metadata": { - "@deathbeds/jupyterlab-fonts": { - "styles": { - "": { - "body[data-jp-deck-mode='presenting'] &": { - "zoom": "134%" - } - } - } - }, - "execution": { - "iopub.execute_input": "2025-08-14T15:53:40.348376Z", - "iopub.status.busy": "2025-08-14T15:53:40.348021Z", - "iopub.status.idle": "2025-08-14T15:53:40.364129Z", - "shell.execute_reply": "2025-08-14T15:53:40.363204Z", - "shell.execute_reply.started": "2025-08-14T15:53:40.348351Z" - }, - "trusted": true - }, - "execution_count": null, - "outputs": [] - }, - { - "id": "26b8baba", - "cell_type": "code", - "source": [ - "# For the purposes of a demo, select only a subset of rows.\n", - "df = df.sample(n=250)\n", - "df.cache()\n", - "df.shape" - ], - "metadata": { - "execution": { - "iopub.execute_input": "2025-08-14T15:55:55.448664Z", - "iopub.status.busy": "2025-08-14T15:55:55.448310Z", - "iopub.status.idle": "2025-08-14T15:55:59.440964Z", - "shell.execute_reply": "2025-08-14T15:55:59.439988Z", - "shell.execute_reply.started": "2025-08-14T15:55:55.448637Z" - }, - "trusted": true - }, - "execution_count": null, - "outputs": [] - }, - { - "id": "af84cb21", - "cell_type": "code", - "source": [ - "# As a side effect of how I extracted the song information from the HTML DOM,\n", - "# we ended up with lists in places where we only expect one item.\n", - "#\n", - "# We can \"explode\" to flatten these lists.\n", - "flattened = df.explode([\n", - " \"Recording Repository\",\n", - " \"Recording Label\",\n", - " \"Recording Take Number\",\n", - " \"Recording Date\",\n", - " \"Recording Matrix Number\",\n", - " \"Recording Catalog Number\",\n", - " \"Media Size\",\n", - " \"Recording Location\",\n", - " \"Summary\",\n", - " \"Rights Advisory\",\n", - " \"Title\",\n", - "])\n", - "flattened.peek()" - ], - "metadata": { - "@deathbeds/jupyterlab-fonts": { - "styles": { - "": { - "body[data-jp-deck-mode='presenting'] &": { - "zoom": "161%" - } - } - } - }, - "execution": { - "iopub.execute_input": "2025-08-14T15:56:02.040804Z", - "iopub.status.busy": "2025-08-14T15:56:02.040450Z", - "iopub.status.idle": "2025-08-14T15:56:06.544384Z", - "shell.execute_reply": "2025-08-14T15:56:06.543240Z", - "shell.execute_reply.started": "2025-08-14T15:56:02.040777Z" - }, - "slideshow": { - "slide_type": "slide" - }, - "trusted": true - }, - "execution_count": null, - "outputs": [] - }, - { - "id": "085deffd", - "cell_type": "code", - "source": [ - "flattened.shape" - ], - "metadata": { - "execution": { - "iopub.execute_input": "2025-08-14T15:56:06.546531Z", - "iopub.status.busy": "2025-08-14T15:56:06.546140Z", - "iopub.status.idle": "2025-08-14T15:56:06.566005Z", - "shell.execute_reply": "2025-08-14T15:56:06.564355Z", - "shell.execute_reply.started": "2025-08-14T15:56:06.546494Z" - }, - "trusted": true - }, - "execution_count": null, - "outputs": [] - }, - { - "id": "f8e653ee", - "cell_type": "markdown", - "source": [ - "To access unstructured data from BigQuery, create a URI pointing to a file in Google Cloud Storage (GCS). Then, construct a \"blob\" (also known as an \"Object Ref\" in BigQuery terms) so that BigQuery can read from GCS." - ], - "metadata": { - "@deathbeds/jupyterlab-fonts": { - "styles": { - "": { - "body[data-jp-deck-mode='presenting'] &": { - "zoom": "216%" - } - } - } - }, - "editable": true, - "slideshow": { - "slide_type": "slide" - }, - "tags": [] - }, - "execution_count": null - }, - { - "id": "dbd1a844", - "cell_type": "code", - "source": [ - "flattened = flattened.assign(**{\\n \"GCS Prefix\": \"gs://cloud-samples-data/third-party/usa-loc-national-jukebox/\",\\n \"GCS Stub\": flattened['URL'].str.extract(r'/(jukebox-[0-9]+)/'),\\n})\\nflattened[\"GCS URI\"] = flattened[\"GCS Prefix\"] + flattened[\"GCS Stub\"] + \".mp3\"" - ], - "metadata": { - "@deathbeds/jupyterlab-fonts": { - "styles": { - "": { - "body[data-jp-deck-mode='presenting'] &": { - "zoom": "211%" - } - } - } - }, - "editable": true, - "execution": { - "iopub.execute_input": "2025-08-14T15:56:07.394879Z", - "iopub.status.busy": "2025-08-14T15:56:07.394509Z", - "iopub.status.idle": "2025-08-14T15:56:12.217017Z", - "shell.execute_reply": "2025-08-14T15:56:12.215852Z", - "shell.execute_reply.started": "2025-08-14T15:56:07.394853Z" - }, - "slideshow": { - "slide_type": "" - }, - "tags": [], - "trusted": true - }, - "execution_count": null, - "outputs": [] - }, - { - "id": "fae13ec5", - "cell_type": "markdown", - "source": [ - "BigQuery (and BigQuery DataFrames) provide access to powerful models and multimodal capabilities. Here, we transcribe audio to text." - ], - "metadata": { - "@deathbeds/jupyterlab-fonts": { - "styles": { - "": { - "body[data-jp-deck-mode='presenting'] &": { - "zoom": "317%" - } - } - } - }, - "editable": true, - "slideshow": { - "slide_type": "slide" - }, - "tags": [] - }, - "execution_count": null - }, - { - "id": "f08f92b1", - "cell_type": "code", - "source": [ - "import bigframes.bigquery as bbq\n", - "\n", - "# Replace with your own connection name.\n", - "CONNECTION_ID = 'your-project-id.your-location.your-connection' # @param {type:\"string\"}\n", - "\n", - "# Convert the audio URI to the runtime representation required by the model.\n", - "audio_ref = bbq.obj.make_ref(flattened[\"GCS URI\"], authorizer=CONNECTION_ID)\n", - "audio_metadata = bbq.obj.fetch_metadata(audio_ref)\n", - "audio_runtime = bbq.obj.get_access_url(audio_metadata, mode=\"R\")\n", - "\n", - "# Call GenAI model to perform audio transcription\n", - "raw_results = bbq.ai.generate(\n", - " prompt=(\"Transcribe the provided audio.\", audio_runtime),\n", - " endpoint=\"gemini-2.5-flash\"\n", - ")\n", - "\n", - "# Package result struct to contain 'content' and 'status' expected by downstream cells\n", - "transcription_df = bpd.DataFrame({\n", - " \"content\": raw_results.struct.field(\"result\"),\n", - " \"status\": raw_results.struct.field(\"status\")\n", - "})\n", - "flattened[\"Transcription\"] = bbq.struct(transcription_df)" - ], - "metadata": { - "editable": true, - "execution": { - "iopub.execute_input": "2025-08-14T15:56:20.908198Z", - "iopub.status.busy": "2025-08-14T15:56:20.907791Z", - "iopub.status.idle": "2025-08-14T15:58:45.909086Z", - "shell.execute_reply": "2025-08-14T15:58:45.908060Z", - "shell.execute_reply.started": "2025-08-14T15:56:20.908170Z" - }, - "slideshow": { - "slide_type": "" - }, - "tags": [], - "trusted": true - }, - "execution_count": null, - "outputs": [] - }, - { - "id": "30969ae1", - "cell_type": "markdown", - "source": [ - "Sometimes the model has transient errors. Check the status column to see if there are errors." - ], - "metadata": { - "@deathbeds/jupyterlab-fonts": { - "styles": { - "": { - "body[data-jp-deck-mode='presenting'] &": { - "zoom": "229%" - } - } - } - }, - "slideshow": { - "slide_type": "slide" - } - }, - "execution_count": null - }, - { - "id": "7d0dbc38", - "cell_type": "code", - "source": [ - "print(f\"Successful rows: {(flattened['Transcription'].struct.field('status') == '').sum()}\")\n", - "print(f\"Failed rows: {(flattened['Transcription'].struct.field('status') != '').sum()}\")\n", - "flattened.shape" - ], - "metadata": { - "@deathbeds/jupyterlab-fonts": { - "styles": { - "": { - "body[data-jp-deck-mode='presenting'] &": { - "zoom": "177%" - } - } - } - }, - "editable": true, - "execution": { - "iopub.execute_input": "2025-08-14T15:59:43.609239Z", - "iopub.status.busy": "2025-08-14T15:59:43.607976Z", - "iopub.status.idle": "2025-08-14T15:59:44.515118Z", - "shell.execute_reply": "2025-08-14T15:59:44.514275Z", - "shell.execute_reply.started": "2025-08-14T15:59:43.609201Z" - }, - "slideshow": { - "slide_type": "" - }, - "tags": [], - "trusted": true - }, - "execution_count": null, - "outputs": [] - }, - { - "id": "6cddf53b", - "cell_type": "code", - "source": [ - "# Show transcribed lyrics.\n", - "flattened[\"Transcription\"].struct.field(\"content\")" - ], - "metadata": { - "@deathbeds/jupyterlab-fonts": { - "styles": { - "": { - "body[data-jp-deck-mode='presenting'] &": { - "zoom": "141%" - } - } - } - }, - "execution": { - "iopub.execute_input": "2025-08-14T15:59:44.820256Z", - "iopub.status.busy": "2025-08-14T15:59:44.819926Z", - "iopub.status.idle": "2025-08-14T15:59:53.147159Z", - "shell.execute_reply": "2025-08-14T15:59:53.146281Z", - "shell.execute_reply.started": "2025-08-14T15:59:44.820232Z" - }, - "trusted": true - }, - "execution_count": null, - "outputs": [] - }, - { - "id": "ba0386cc", - "cell_type": "code", - "source": [ - "# Find all instrumentatal songs\n", - "instrumental = flattened[flattened[\"Transcription\"].struct.field(\"content\") == \"\"]\n", - "print(instrumental.shape)\n", - "song = instrumental.peek(1)\n", - "song" - ], - "metadata": { - "@deathbeds/jupyterlab-fonts": { - "styles": { - "": { - "body[data-jp-deck-mode='presenting'] &": { - "zoom": "152%" - } - } - } - }, - "execution": { - "iopub.execute_input": "2025-08-14T15:59:53.149222Z", - "iopub.status.busy": "2025-08-14T15:59:53.148783Z", - "iopub.status.idle": "2025-08-14T15:59:58.868959Z", - "shell.execute_reply": "2025-08-14T15:59:58.867804Z", - "shell.execute_reply.started": "2025-08-14T15:59:53.149198Z" - }, - "slideshow": { - "slide_type": "slide" - }, - "trusted": true - }, - "execution_count": null, - "outputs": [] - }, - { - "id": "61a883b2", - "cell_type": "code", - "source": [ - "import gcsfs\n", - "import IPython.display\n", - "\n", - "fs = gcsfs.GCSFileSystem(project='bigframes-dev')\n", - "with fs.open(song[\"GCS URI\"].iloc[0]) as song_file:\n", - " song_bytes = song_file.read()\n", - "\n", - "IPython.display.Audio(song_bytes)" - ], - "metadata": { - "@deathbeds/jupyterlab-fonts": { - "styles": { - "": { - "body[data-jp-deck-mode='presenting'] &": { - "zoom": "152%" - } - } - } - }, - "editable": true, - "execution": { - "iopub.execute_input": "2025-08-14T15:59:58.870143Z", - "iopub.status.busy": "2025-08-14T15:59:58.869868Z", - "iopub.status.idle": "2025-08-14T16:00:15.502470Z", - "shell.execute_reply": "2025-08-14T16:00:15.500813Z", - "shell.execute_reply.started": "2025-08-14T15:59:58.870123Z" - }, - "slideshow": { - "slide_type": "" - }, - "tags": [], - "trusted": true - }, - "execution_count": null, - "outputs": [] - }, - { - "id": "e8a25c46", - "cell_type": "markdown", - "source": [ - "## Creating a searchable index\n", - "\n", - "To be able to search by semantics rather than just text, generate embeddings and then create an index to efficiently search these.\n", - "\n", - "See also, this example: https://github.com/googleapis/python-bigquery-dataframes/blob/main/notebooks/generative_ai/bq_dataframes_llm_vector_search.ipynb" - ], - "metadata": { - "@deathbeds/jupyterlab-fonts": { - "styles": { - "": { - "body[data-jp-deck-mode='presenting'] &": { - "zoom": "181%" - } - } - } - }, - "slideshow": { - "slide_type": "slide" - } - }, - "execution_count": null - }, - { - "id": "ead0fa8c", - "cell_type": "code", - "source": [ - "from bigframes.ml.llm import TextEmbeddingGenerator\n", - "\n", - "text_model = TextEmbeddingGenerator(model_name=\"text-multilingual-embedding-002\")" - ], - "metadata": { - "@deathbeds/jupyterlab-fonts": { - "styles": { - "": { - "body[data-jp-deck-mode='presenting'] &": { - "zoom": "163%" - } - } - } - }, - "execution": { - "iopub.execute_input": "2025-08-14T16:00:15.506380Z", - "iopub.status.busy": "2025-08-14T16:00:15.505775Z", - "iopub.status.idle": "2025-08-14T16:00:25.134987Z", - "shell.execute_reply": "2025-08-14T16:00:25.134124Z", - "shell.execute_reply.started": "2025-08-14T16:00:15.506337Z" - }, - "trusted": true - }, - "execution_count": null, - "outputs": [] - }, - { - "id": "5ed7776d", - "cell_type": "code", - "source": [ - "df_to_index = (\n", - " flattened\n", - " .assign(content=flattened[\"Transcription\"].struct.field(\"content\"))\n", - " [flattened[\"Transcription\"].struct.field(\"content\") != \"\"]\n", - ")\n", - "embedding = text_model.predict(df_to_index)\n", - "embedding.peek(1)" - ], - "metadata": { - "@deathbeds/jupyterlab-fonts": { - "styles": { - "": { - "body[data-jp-deck-mode='presenting'] &": { - "zoom": "125%" - } - } - } - }, - "execution": { - "iopub.execute_input": "2025-08-14T16:00:25.136017Z", - "iopub.status.busy": "2025-08-14T16:00:25.135744Z", - "iopub.status.idle": "2025-08-14T16:00:34.860878Z", - "shell.execute_reply": "2025-08-14T16:00:34.859925Z", - "shell.execute_reply.started": "2025-08-14T16:00:25.135997Z" - }, - "trusted": true - }, - "execution_count": null, - "outputs": [] - }, - { - "id": "c96e9832", - "cell_type": "code", - "source": [ - "# Check the status column to look for errors.\n", - "print(f\"Successful rows: {(embedding['ml_generate_embedding_status'] == '').sum()}\")\n", - "print(f\"Failed rows: {(embedding['ml_generate_embedding_status'] != '').sum()}\")\n", - "embedding.shape" - ], - "metadata": { - "@deathbeds/jupyterlab-fonts": { - "styles": { - "": { - "body[data-jp-deck-mode='presenting'] &": { - "zoom": "178%" - } - } - } - }, - "editable": true, - "execution": { - "iopub.execute_input": "2025-08-14T16:01:20.816923Z", - "iopub.status.busy": "2025-08-14T16:01:20.816523Z", - "iopub.status.idle": "2025-08-14T16:01:22.480554Z", - "shell.execute_reply": "2025-08-14T16:01:22.479604Z", - "shell.execute_reply.started": "2025-08-14T16:01:20.816894Z" - }, - "slideshow": { - "slide_type": "slide" - }, - "tags": [], - "trusted": true - }, - "execution_count": null, - "outputs": [] - }, - { - "id": "0e2a5d7b", - "cell_type": "markdown", - "source": [ - "We're now ready to save this to a table." - ], - "metadata": { - "@deathbeds/jupyterlab-fonts": { - "styles": { - "": { - "body[data-jp-deck-mode='presenting'] &": { - "zoom": "224%" - } - } - } - } - }, - "execution_count": null - }, - { - "id": "51819a0c", - "cell_type": "code", - "source": [ - "embedding_table_id = f\"{bpd.options.bigquery.project}.kaggle.national_jukebox\"\n", - "embedding.to_gbq(embedding_table_id, if_exists=\"replace\")" - ], - "metadata": { - "@deathbeds/jupyterlab-fonts": { - "styles": { - "": { - "body[data-jp-deck-mode='presenting'] &": { - "zoom": "172%" - } - } - } - }, - "execution": { - "iopub.execute_input": "2025-08-14T16:03:43.611592Z", - "iopub.status.busy": "2025-08-14T16:03:43.611265Z", - "iopub.status.idle": "2025-08-14T16:03:47.459025Z", - "shell.execute_reply": "2025-08-14T16:03:47.458079Z", - "shell.execute_reply.started": "2025-08-14T16:03:43.611568Z" - }, - "trusted": true - }, - "execution_count": null, - "outputs": [] - }, - { - "id": "5e16fb14", - "cell_type": "markdown", - "source": [ - "## Searching the database\n", - "\n", - "To search by semantics, we:\n", - "\n", - "1. Turn our search string into an embedding using the same model as our index.\n", - "2. Find the closest matches to the search string." - ], - "metadata": { - "@deathbeds/jupyterlab-fonts": { - "styles": { - "": { - "body[data-jp-deck-mode='presenting'] &": { - "zoom": "183%" - } - } - } - }, - "slideshow": { - "slide_type": "slide" - } - }, - "execution_count": null - }, - { - "id": "1bad3317", - "cell_type": "code", - "source": [ - "import bigframes.pandas as bpd\n", - "\n", - "df_written = bpd.read_gbq(embedding_table_id)\n", - "df_written.peek(1)" - ], - "metadata": { - "@deathbeds/jupyterlab-fonts": { - "styles": { - "": { - "body[data-jp-deck-mode='presenting'] &": { - "zoom": "92%" - } - } - } - }, - "execution": { - "iopub.execute_input": "2025-08-14T16:03:52.674429Z", - "iopub.status.busy": "2025-08-14T16:03:52.673629Z", - "iopub.status.idle": "2025-08-14T16:03:59.962635Z", - "shell.execute_reply": "2025-08-14T16:03:59.961482Z", - "shell.execute_reply.started": "2025-08-14T16:03:52.674399Z" - }, - "slideshow": { - "slide_type": "skip" - }, - "trusted": true - }, - "execution_count": null, - "outputs": [] - }, - { - "id": "8aaaef1f", - "cell_type": "code", - "source": [ - "from bigframes.ml.llm import TextEmbeddingGenerator\n", - "\n", - "search_string = \"walking home\"\n", - "\n", - "text_model = TextEmbeddingGenerator(model_name=\"text-multilingual-embedding-002\")\n", - "search_df = bpd.DataFrame([search_string], columns=['search_string'])\n", - "search_embedding = text_model.predict(search_df)\n", - "search_embedding" - ], - "metadata": { - "@deathbeds/jupyterlab-fonts": { - "styles": { - "": { - "body[data-jp-deck-mode='presenting'] &": { - "zoom": "127%" - } - } - } - }, - "execution": { - "iopub.execute_input": "2025-08-14T16:03:59.964634Z", - "iopub.status.busy": "2025-08-14T16:03:59.964268Z", - "iopub.status.idle": "2025-08-14T16:04:55.051531Z", - "shell.execute_reply": "2025-08-14T16:04:55.050393Z", - "shell.execute_reply.started": "2025-08-14T16:03:59.964598Z" - }, - "trusted": true - }, - "execution_count": null, - "outputs": [] - }, - { - "id": "908a2340", - "cell_type": "code", - "source": [ - "import bigframes.bigquery as bbq\n", - "\n", - "vector_search_results = bbq.vector_search(\n", - " base_table=embedding_table_id,\n", - " column_to_search=\"ml_generate_embedding_result\",\n", - " query=search_embedding,\n", - " distance_type=\"COSINE\",\n", - " query_column_to_search=\"ml_generate_embedding_result\",\n", - " top_k=5,\n", - ")" - ], - "metadata": { - "@deathbeds/jupyterlab-fonts": { - "styles": { - "": { - "body[data-jp-deck-mode='presenting'] &": { - "zoom": "175%" - } - } - } - }, - "editable": true, - "execution": { - "iopub.execute_input": "2025-08-14T16:05:46.473357Z", - "iopub.status.busy": "2025-08-14T16:05:46.473056Z", - "iopub.status.idle": "2025-08-14T16:05:50.564470Z", - "shell.execute_reply": "2025-08-14T16:05:50.563277Z", - "shell.execute_reply.started": "2025-08-14T16:05:46.473336Z" - }, - "slideshow": { - "slide_type": "slide" - }, - "tags": [], - "trusted": true - }, - "execution_count": null, - "outputs": [] - }, - { - "id": "f84ebe70", - "cell_type": "code", - "source": [ - "vector_search_results.dtypes" - ], - "metadata": { - "execution": { - "iopub.execute_input": "2025-08-14T16:05:50.566930Z", - "iopub.status.busy": "2025-08-14T16:05:50.566422Z", - "iopub.status.idle": "2025-08-14T16:05:50.576293Z", - "shell.execute_reply": "2025-08-14T16:05:50.575186Z", - "shell.execute_reply.started": "2025-08-14T16:05:50.566893Z" - }, - "trusted": true - }, - "execution_count": null, - "outputs": [] - }, - { - "id": "eeff1c72", - "cell_type": "code", - "source": [ - "results = vector_search_results[[\"Title\", \"Summary\", \"Names\", \"GCS URI\", \"Transcription\", \"distance\"]].sort_values(\"distance\").to_pandas()\n", - "results" - ], - "metadata": { - "@deathbeds/jupyterlab-fonts": { - "styles": { - "": { - "body[data-jp-deck-mode='presenting'] &": { - "zoom": "158%" - } - } - } - }, - "execution": { - "iopub.execute_input": "2025-08-14T16:05:54.787080Z", - "iopub.status.busy": "2025-08-14T16:05:54.786649Z", - "iopub.status.idle": "2025-08-14T16:05:55.581285Z", - "shell.execute_reply": "2025-08-14T16:05:55.580012Z", - "shell.execute_reply.started": "2025-08-14T16:05:54.787054Z" - }, - "slideshow": { - "slide_type": "slide" - }, - "trusted": true - }, - "execution_count": null, - "outputs": [] - }, - { - "id": "7ec53675", - "cell_type": "code", - "source": [ - "print(results[\"Transcription\"].struct.field(\"content\").iloc[0])" - ], - "metadata": { - "@deathbeds/jupyterlab-fonts": { - "styles": { - "": { - "body[data-jp-deck-mode='presenting'] &": { - "zoom": "138%" - } - } - } - }, - "execution": { - "iopub.execute_input": "2025-08-14T16:05:56.142373Z", - "iopub.status.busy": "2025-08-14T16:05:56.142038Z", - "iopub.status.idle": "2025-08-14T16:05:56.149020Z", - "shell.execute_reply": "2025-08-14T16:05:56.147966Z", - "shell.execute_reply.started": "2025-08-14T16:05:56.142350Z" - }, - "trusted": true - }, - "execution_count": null, - "outputs": [] - }, - { - "id": "a96552fb", - "cell_type": "code", - "source": [ - "import gcsfs\n", - "import IPython.display\n", - "\n", - "fs = gcsfs.GCSFileSystem(project='bigframes-dev')\n", - "with fs.open(results[\"GCS URI\"].iloc[0]) as song_file:\n", - " song_bytes = song_file.read()\n", - "\n", - "IPython.display.Audio(song_bytes)" - ], - "metadata": { - "editable": true, - "execution": { - "iopub.execute_input": "2025-08-14T16:06:04.542878Z", - "iopub.status.busy": "2025-08-14T16:06:04.542537Z", - "iopub.status.idle": "2025-08-14T16:06:04.843052Z", - "shell.execute_reply": "2025-08-14T16:06:04.841220Z", - "shell.execute_reply.started": "2025-08-14T16:06:04.542854Z" - }, - "scrolled": true, - "slideshow": { - "slide_type": "" - }, - "tags": [], - "trusted": true - }, - "execution_count": null, - "outputs": [] - }, - { - "id": "72af7c7f", - "cell_type": "code", - "source": [], - "metadata": { - "trusted": true - }, - "execution_count": null, - "outputs": [] - } - ], - "metadata": { - "kaggle": { - "accelerator": "none", - "dataSources": [ - { - "databundleVersionId": 13238728, - "sourceId": 110281, - "sourceType": "competition" - } - ], - "dockerImageVersionId": 31089, - "isGpuEnabled": false, - "isInternetEnabled": true, - "language": "python", - "sourceType": "notebook" - }, - "kernelspec": { - "display_name": "Python 3", - "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.11.13" - } - }, - "nbformat_minor": 4, - "nbformat": 4 -} \ No newline at end of file diff --git a/notebooks/location/regionalized.ipynb b/notebooks/location/regionalized.ipynb index b1e9e010d48..a7ff5db84e3 100644 --- a/notebooks/location/regionalized.ipynb +++ b/notebooks/location/regionalized.ipynb @@ -7,9 +7,8 @@ "source": [ "# README\n", "\n", - "This Notebook runs requiring the following environent variable:\n", - "1. GOOGLE_CLOUD_PROJECT - The google cloud project id.\n", - "1. BIGQUERY_LOCATION - can take values as per https://cloud.google.com/bigquery/docs/locations, e.g. `us`, `asia-east1`." + "This Notebook runs differently depending on the following environent variable:\n", + "1. BIGQUERY_LOCATION - can take values as per https://cloud.google.com/bigquery/docs/locations, e.g. `us`, `asia-east1`" ] }, { @@ -17,7 +16,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## Infer location and set up data in that location if needed" + "### Infer location and set up data in that location if needed" ] }, { @@ -48,36 +47,32 @@ ], "source": [ "# Take multi-region US as the default BQ location, where most of the BQ data lies including the BQ public datasets\n", - "import os\n", - "\n", - "PROJECT_ID = os.environ.get(\"GOOGLE_CLOUD_PROJECT\")\n", - "BQ_LOCATION = os.environ.get(\"BIGQUERY_LOCATION\")\n", - "\n", - "if not PROJECT_ID:\n", - " raise ValueError(\"Project must be set via environment variable GOOGLE_CLOUD_PROJECT\")\n", - "if not BQ_LOCATION:\n", - " raise ValueError(\"BQ location must be set via environment variable BIGQUERY_LOCATION\")\n", - "\n", + "BQ_LOCATION = \"us\"\n", + "PROJECT = \"bigframes-dev\"\n", "DATASET = \"bigframes_testing\"\n", "PENGUINS_TABLE = \"bigquery-public-data.ml_datasets.penguins\"\n", "\n", "\n", "# Check for a location set in the environment and do location-specific setup if needed\n", "\n", + "import os\n", "import google.api_core.exceptions\n", "from google.cloud import bigquery\n", "import bigframes\n", + " \n", + "env_bq_location = os.getenv(\"BIGQUERY_LOCATION\")\n", + "if env_bq_location and env_bq_location != BQ_LOCATION:\n", + " BQ_LOCATION = env_bq_location.lower()\n", "\n", "client = bigquery.Client()\n", "\n", - "BQ_LOCATION = BQ_LOCATION.lower()\n", "if BQ_LOCATION != \"us\":\n", " bq_location_normalized = BQ_LOCATION.replace('-', '_')\n", "\n", " # Nominate a local penguins table\n", " penguins_table_ref = bigquery.TableReference.from_string(PENGUINS_TABLE)\n", " penguins_local_dataset_name = f\"{DATASET}_{bq_location_normalized}\"\n", - " penguins_local_dataset_ref = bigquery.DatasetReference(project=PROJECT_ID, dataset_id=penguins_local_dataset_name)\n", + " penguins_local_dataset_ref = bigquery.DatasetReference(project=PROJECT, dataset_id=penguins_local_dataset_name)\n", " penguins_local_dataset = bigquery.Dataset(penguins_local_dataset_ref)\n", " penguins_local_dataset.location = BQ_LOCATION\n", " penguins_local_table_ref= bigquery.TableReference(penguins_local_dataset, penguins_table_ref.table_id)\n", @@ -99,13 +94,13 @@ " DATASET = f\"{DATASET}_{bq_location_normalized}\"\n", "\n", "# Create the dataset to store the model if it doesn't exist \n", - "model_local_dataset = bigquery.Dataset(bigquery.DatasetReference(project=PROJECT_ID, dataset_id=DATASET))\n", + "model_local_dataset = bigquery.Dataset(bigquery.DatasetReference(project=PROJECT, dataset_id=DATASET))\n", "model_local_dataset.location = BQ_LOCATION\n", "model_dataset = client.create_dataset(model_local_dataset, exists_ok=True)\n", "\n", "# Finally log the variables driving the core notebook execution\n", "log = ('\\n'.join(f\"{name}: {str(value)}\" for name, value in {\n", - " \"BigQuery project\" : PROJECT_ID,\n", + " \"BigQuery project\" : PROJECT,\n", " \"BigQuery location\" : BQ_LOCATION,\n", " \"Penguins Table\" : PENGUINS_TABLE,\n", " \"ML Model Dataset\" : model_dataset.reference\n", @@ -126,7 +121,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## Set BigQuery DataFrames options" + "### Set BigQuery DataFrames options" ] }, { @@ -137,14 +132,7 @@ "source": [ "import bigframes.pandas\n", "\n", - "# Note: The project option is not required in all environments.\n", - "# On BigQuery Studio, the project ID is automatically detected.\n", - "bigframes.pandas.options.bigquery.project = PROJECT_ID\n", - "\n", - "# Note: The location option is not required.\n", - "# It defaults to the location of the first table or query\n", - "# passed to read_gbq(). For APIs where a location can't be\n", - "# auto-detected, the location defaults to the \"US\" location.\n", + "bigframes.pandas.options.bigquery.project = PROJECT\n", "bigframes.pandas.options.bigquery.location = BQ_LOCATION" ] }, @@ -1344,7 +1332,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## BigQuery DataFrames gives you the ability to turn your custom scalar functions into a BigQuery remote function.", + "### BigQuery DataFrames gives you the ability to turn your custom scalar functions into a BigQuery remote function.\n", "\n", "It requires the GCP project to be set up appropriately and the user having sufficient privileges to use them. One can find more details on it via `help` command." ] @@ -1421,8 +1409,8 @@ } ], "source": [ - "import bigframes.pandas as bpd\n", - "help(bpd.remote_function)" + "import bigframes.pandas as pd\n", + "help(pd.remote_function)" ] }, { @@ -1461,8 +1449,8 @@ } ], "source": [ - "@bpd.remote_function(bigquery_connection='bigframes-rf-conn', cloud_function_service_account=\"default\")\n", - "def get_bucket(num: float) -> str:\n", + "@pd.remote_function([float], str, bigquery_connection='bigframes-rf-conn')\n", + "def get_bucket(num):\n", " if not num: return \"NA\"\n", " boundary = 4000\n", " return \"at_or_above_4000\" if num >= boundary else \"below_4000\"" @@ -1643,7 +1631,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## Initialize a DataFrame from a BigQuery table" + "### Initialize a DataFrame from a BigQuery table" ] }, { @@ -2785,22 +2773,6 @@ "source": [ "model.to_gbq(f\"{DATASET}.penguins_model\", replace=True)" ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Clean Up" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "bpd.close_session()" - ] } ], "metadata": { @@ -2819,7 +2791,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.9" + "version": "3.10.12" }, "orig_nbformat": 4 }, diff --git a/notebooks/ml/bq_dataframes_ml_cross_validation.ipynb b/notebooks/ml/bq_dataframes_ml_cross_validation.ipynb deleted file mode 100644 index 3dc0eabf5a1..00000000000 --- a/notebooks/ml/bq_dataframes_ml_cross_validation.ipynb +++ /dev/null @@ -1,1013 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# BigFrames ML Cross-Vaidation\n", - "\n", - "This demo shows how to do cross validation in bigframes.ml" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## 1. Prepare Data" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [], - "source": [ - "import bigframes.pandas as bpd" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "Query job aa2b9845-0e66-4f42-a360-ffe03215caf6 is DONE. 0 Bytes processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job fe2bc354-672e-4d08-b969-bb2ede299fca is DONE. 28.9 kB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job 8d16fa20-391f-4917-86fc-1a595dba3fc6 is DONE. 33.6 kB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "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", - "
speciesislandculmen_length_mmculmen_depth_mmflipper_length_mmbody_mass_gsex
0Gentoo penguin (Pygoscelis papua)Biscoe45.216.4223.05950.0MALE
1Gentoo penguin (Pygoscelis papua)Biscoe46.514.5213.04400.0FEMALE
2Adelie Penguin (Pygoscelis adeliae)Biscoe37.716.0183.03075.0FEMALE
3Gentoo penguin (Pygoscelis papua)Biscoe46.415.6221.05000.0MALE
4Gentoo penguin (Pygoscelis papua)Biscoe46.113.2211.04500.0FEMALE
5Adelie Penguin (Pygoscelis adeliae)Torgersen43.119.2197.03500.0MALE
6Gentoo penguin (Pygoscelis papua)Biscoe45.215.8215.05300.0MALE
7Adelie Penguin (Pygoscelis adeliae)Dream36.217.3187.03300.0FEMALE
8Chinstrap penguin (Pygoscelis antarctica)Dream46.018.9195.04150.0FEMALE
9Gentoo penguin (Pygoscelis papua)Biscoe54.315.7231.05650.0MALE
11Adelie Penguin (Pygoscelis adeliae)Torgersen39.517.4186.03800.0FEMALE
12Gentoo penguin (Pygoscelis papua)Biscoe42.713.7208.03950.0FEMALE
13Adelie Penguin (Pygoscelis adeliae)Biscoe41.020.0203.04725.0MALE
14Gentoo penguin (Pygoscelis papua)Biscoe48.515.0219.04850.0FEMALE
15Chinstrap penguin (Pygoscelis antarctica)Dream49.618.2193.03775.0MALE
16Gentoo penguin (Pygoscelis papua)Biscoe50.817.3228.05600.0MALE
17Gentoo penguin (Pygoscelis papua)Biscoe46.214.1217.04375.0FEMALE
18Adelie Penguin (Pygoscelis adeliae)Biscoe38.817.2180.03800.0MALE
19Chinstrap penguin (Pygoscelis antarctica)Dream51.018.8203.04100.0MALE
20Gentoo penguin (Pygoscelis papua)Biscoe42.913.1215.05000.0FEMALE
21Gentoo penguin (Pygoscelis papua)Biscoe50.415.3224.05550.0MALE
22Gentoo penguin (Pygoscelis papua)Biscoe49.016.1216.05550.0MALE
23Gentoo penguin (Pygoscelis papua)Biscoe43.414.4218.04600.0FEMALE
24Gentoo penguin (Pygoscelis papua)Biscoe45.015.4220.05050.0MALE
25Gentoo penguin (Pygoscelis papua)Biscoe47.514.0212.04875.0FEMALE
\n", - "

25 rows × 7 columns

\n", - "
[334 rows x 7 columns in total]" - ], - "text/plain": [ - " species island culmen_length_mm \\\n", - "0 Gentoo penguin (Pygoscelis papua) Biscoe 45.2 \n", - "1 Gentoo penguin (Pygoscelis papua) Biscoe 46.5 \n", - "2 Adelie Penguin (Pygoscelis adeliae) Biscoe 37.7 \n", - "3 Gentoo penguin (Pygoscelis papua) Biscoe 46.4 \n", - "4 Gentoo penguin (Pygoscelis papua) Biscoe 46.1 \n", - "5 Adelie Penguin (Pygoscelis adeliae) Torgersen 43.1 \n", - "6 Gentoo penguin (Pygoscelis papua) Biscoe 45.2 \n", - "7 Adelie Penguin (Pygoscelis adeliae) Dream 36.2 \n", - "8 Chinstrap penguin (Pygoscelis antarctica) Dream 46.0 \n", - "9 Gentoo penguin (Pygoscelis papua) Biscoe 54.3 \n", - "11 Adelie Penguin (Pygoscelis adeliae) Torgersen 39.5 \n", - "12 Gentoo penguin (Pygoscelis papua) Biscoe 42.7 \n", - "13 Adelie Penguin (Pygoscelis adeliae) Biscoe 41.0 \n", - "14 Gentoo penguin (Pygoscelis papua) Biscoe 48.5 \n", - "15 Chinstrap penguin (Pygoscelis antarctica) Dream 49.6 \n", - "16 Gentoo penguin (Pygoscelis papua) Biscoe 50.8 \n", - "17 Gentoo penguin (Pygoscelis papua) Biscoe 46.2 \n", - "18 Adelie Penguin (Pygoscelis adeliae) Biscoe 38.8 \n", - "19 Chinstrap penguin (Pygoscelis antarctica) Dream 51.0 \n", - "20 Gentoo penguin (Pygoscelis papua) Biscoe 42.9 \n", - "21 Gentoo penguin (Pygoscelis papua) Biscoe 50.4 \n", - "22 Gentoo penguin (Pygoscelis papua) Biscoe 49.0 \n", - "23 Gentoo penguin (Pygoscelis papua) Biscoe 43.4 \n", - "24 Gentoo penguin (Pygoscelis papua) Biscoe 45.0 \n", - "25 Gentoo penguin (Pygoscelis papua) Biscoe 47.5 \n", - "\n", - " culmen_depth_mm flipper_length_mm body_mass_g sex \n", - "0 16.4 223.0 5950.0 MALE \n", - "1 14.5 213.0 4400.0 FEMALE \n", - "2 16.0 183.0 3075.0 FEMALE \n", - "3 15.6 221.0 5000.0 MALE \n", - "4 13.2 211.0 4500.0 FEMALE \n", - "5 19.2 197.0 3500.0 MALE \n", - "6 15.8 215.0 5300.0 MALE \n", - "7 17.3 187.0 3300.0 FEMALE \n", - "8 18.9 195.0 4150.0 FEMALE \n", - "9 15.7 231.0 5650.0 MALE \n", - "11 17.4 186.0 3800.0 FEMALE \n", - "12 13.7 208.0 3950.0 FEMALE \n", - "13 20.0 203.0 4725.0 MALE \n", - "14 15.0 219.0 4850.0 FEMALE \n", - "15 18.2 193.0 3775.0 MALE \n", - "16 17.3 228.0 5600.0 MALE \n", - "17 14.1 217.0 4375.0 FEMALE \n", - "18 17.2 180.0 3800.0 MALE \n", - "19 18.8 203.0 4100.0 MALE \n", - "20 13.1 215.0 5000.0 FEMALE \n", - "21 15.3 224.0 5550.0 MALE \n", - "22 16.1 216.0 5550.0 MALE \n", - "23 14.4 218.0 4600.0 FEMALE \n", - "24 15.4 220.0 5050.0 MALE \n", - "25 14.0 212.0 4875.0 FEMALE \n", - "...\n", - "\n", - "[334 rows x 7 columns]" - ] - }, - "execution_count": 2, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# read and filter out unavailable data\n", - "df = bpd.read_gbq(\"bigframes-dev.bqml_tutorial.penguins\")\n", - "df = df.dropna()\n", - "df" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [], - "source": [ - "# Select X and y from the dataset\n", - "X = df[\n", - " [\n", - " \"species\",\n", - " \"island\",\n", - " \"culmen_length_mm\",\n", - " ]\n", - " ]\n", - "y = df[\"body_mass_g\"]" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## 2.1 Define KFold class and Train/Test for Each Fold (Manual Approach)" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": {}, - "outputs": [], - "source": [ - "from bigframes.ml import model_selection, linear_model" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": {}, - "outputs": [], - "source": [ - "# Create KFold instance, n_splits defines how many folds the data will split. For example, n_split=5 will split the entire dataset into 5 pieces. \n", - "# In each fold, 4 pieces will be used for training, and the other piece will be used for evaluation. \n", - "kf = model_selection.KFold(n_splits=5)" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "Query job 9ce9fb43-306d-46e9-bbe5-d98ee55143bd is DONE. 37.0 kB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job 8c86156d-ee97-4f66-9dc1-db15ff3d8e8e is DONE. 16.4 kB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job b8f2b382-b938-4dff-8bdb-129703ade285 is DONE. 37.3 kB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - " mean_absolute_error mean_squared_error mean_squared_log_error \\\n", - "0 297.36838 148892.914876 0.009057 \n", - "\n", - " median_absolute_error r2_score explained_variance \n", - "0 238.424052 0.814613 0.816053 \n", - "\n", - "[1 rows x 6 columns]\n" - ] - }, - { - "data": { - "text/html": [ - "Query job ec2968f3-1713-4617-8a26-6fe4267f8061 is DONE. 37.0 kB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job c7a1b80f-26f5-41b1-bcdc-b276af141671 is DONE. 16.4 kB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job 82054991-c22f-41b3-9802-f16919949e26 is DONE. 37.3 kB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - " mean_absolute_error mean_squared_error mean_squared_log_error \\\n", - "0 307.6149 139013.303482 0.007907 \n", - "\n", - " median_absolute_error r2_score explained_variance \n", - "0 266.589811 0.782835 0.794297 \n", - "\n", - "[1 rows x 6 columns]\n" - ] - }, - { - "data": { - "text/html": [ - "Query job 3e5ae019-7c5b-44ea-8392-85145fdb6802 is DONE. 37.0 kB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job c35dfd28-504d-4d12-b039-da890b9cb51d is DONE. 16.5 kB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job 29ac1bb3-f864-400e-8cac-0b4c7f78ebcd is DONE. 37.3 kB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - " mean_absolute_error mean_squared_error mean_squared_log_error \\\n", - "0 348.412701 180661.063512 0.01125 \n", - "\n", - " median_absolute_error r2_score explained_variance \n", - "0 313.29406 0.744053 0.74537 \n", - "\n", - "[1 rows x 6 columns]\n" - ] - }, - { - "data": { - "text/html": [ - "Query job d90f5938-2894-4c93-8691-21162a2fca4c is DONE. 37.0 kB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job 4c6328b3-2d3f-42bb-9f83-4f8c84773c95 is DONE. 16.4 kB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job 8a885a6a-d3ad-4569-80ce-4f57d9b86105 is DONE. 37.3 kB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - " mean_absolute_error mean_squared_error mean_squared_log_error \\\n", - "0 309.991882 151820.705254 0.008898 \n", - "\n", - " median_absolute_error r2_score explained_variance \n", - "0 212.758708 0.694001 0.694287 \n", - "\n", - "[1 rows x 6 columns]\n" - ] - }, - { - "data": { - "text/html": [ - "Query job d1e60370-11c8-4f49-a8d5-85417662aa51 is DONE. 37.0 kB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job d8e8712a-6347-4725-a27d-49810d4acc1c is DONE. 16.5 kB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job 6a0ebaa6-5572-404f-a41d-b90e2c65d948 is DONE. 37.3 kB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - " mean_absolute_error mean_squared_error mean_squared_log_error \\\n", - "0 256.569216 103495.042886 0.006605 \n", - "\n", - " median_absolute_error r2_score explained_variance \n", - "0 222.940815 0.818589 0.832344 \n", - "\n", - "[1 rows x 6 columns]\n" - ] - } - ], - "source": [ - "for X_train, X_test, y_train, y_test in kf.split(X, y):\n", - " model = linear_model.LinearRegression()\n", - " model.fit(X_train, y_train)\n", - " score = model.score(X_test, y_test)\n", - "\n", - " print(score)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## 2.2 Use cross_validate Function to Do Cross Validation (Automatic Approach)" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "Query job 5bdcd65d-7d72-4094-be3a-cf67a1787cf4 is DONE. 37.0 kB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job bb0504b2-b656-4a08-9bf8-dcab0d188022 is DONE. 16.4 kB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job 8c5c4b66-9a14-455a-a3f5-99f0f522713f is DONE. 37.3 kB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job 9c9b81de-35b6-4561-8881-57da8b73cc7f is DONE. 37.0 kB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job b781f1aa-6572-49e5-ab8d-f1908b497a1c is DONE. 16.4 kB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job 41a2a58e-0289-4d58-8e39-de286f2a91fb is DONE. 37.3 kB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job 7ee839a9-f77c-49b0-844e-8eecc1647b97 is DONE. 37.0 kB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job a317d488-8589-4faa-940b-e59af91caf4d is DONE. 16.5 kB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job 2de96ea8-519a-4976-a641-eb26a4bd38fb is DONE. 37.3 kB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job 41a7d5a0-c76b-4ef3-a3da-d4d5a2ebbb0e is DONE. 37.0 kB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job 9e82ddc9-8461-4644-ba34-957a7426ff8e is DONE. 16.4 kB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job 0fa84d07-fdfa-41c9-b601-9326a94f3a09 is DONE. 37.3 kB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job d4495568-f1b5-431b-b892-4fc7dcbccfd5 is DONE. 37.0 kB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job af1e6460-3078-4a8b-8992-9e7df9dcfbb3 is DONE. 16.5 kB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job f14401bf-fd80-401a-a61d-52614fba1ca7 is DONE. 37.3 kB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/plain": [ - "{'test_score': [ mean_absolute_error mean_squared_error mean_squared_log_error \\\n", - " 0 322.341485 157616.627179 0.009137 \n", - " \n", - " median_absolute_error r2_score explained_variance \n", - " 0 269.412639 0.705594 0.724882 \n", - " \n", - " [1 rows x 6 columns],\n", - " mean_absolute_error mean_squared_error mean_squared_log_error \\\n", - " 0 289.682121 136550.318797 0.00878 \n", - " \n", - " median_absolute_error r2_score explained_variance \n", - " 0 212.874686 0.799363 0.81416 \n", - " \n", - " [1 rows x 6 columns],\n", - " mean_absolute_error mean_squared_error mean_squared_log_error \\\n", - " 0 325.358522 155218.752974 0.009606 \n", - " \n", - " median_absolute_error r2_score explained_variance \n", - " 0 267.301671 0.777174 0.7782 \n", - " \n", - " [1 rows x 6 columns],\n", - " mean_absolute_error mean_squared_error mean_squared_log_error \\\n", - " 0 286.874056 120586.575364 0.007484 \n", - " \n", - " median_absolute_error r2_score explained_variance \n", - " 0 247.656578 0.79281 0.796001 \n", - " \n", - " [1 rows x 6 columns],\n", - " mean_absolute_error mean_squared_error mean_squared_log_error \\\n", - " 0 287.989397 145947.465344 0.008447 \n", - " \n", - " median_absolute_error r2_score explained_variance \n", - " 0 186.777549 0.791452 0.798825 \n", - " \n", - " [1 rows x 6 columns]],\n", - " 'fit_time': [18.79181448201416,\n", - " 19.092008439009078,\n", - " 75.7446747609647,\n", - " 17.520530884969048,\n", - " 21.157033596013207],\n", - " 'score_time': [4.247669544012751,\n", - " 6.792615927988663,\n", - " 4.502274781989399,\n", - " 4.484583999030292,\n", - " 4.224339194013737]}" - ] - }, - "execution_count": 7, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# By using model_selection.cross_validate, the above 2.1 process is automated. The returned scores contains the evaluation results for each fold.\n", - "model = linear_model.LinearRegression()\n", - "scores = model_selection.cross_validate(model, X, y, cv=5)\n", - "scores" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "venv (3.10.14)", - "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/ml/bq_dataframes_ml_linear_regression.ipynb b/notebooks/ml/bq_dataframes_ml_linear_regression.ipynb deleted file mode 100644 index 210922eab94..00000000000 --- a/notebooks/ml/bq_dataframes_ml_linear_regression.ipynb +++ /dev/null @@ -1,760 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "ur8xi4C7S06n" - }, - "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": { - "id": "JAPoU8Sm5E6e" - }, - "source": [ - "# Train a linear regression model with BigQuery DataFrames ML", - "\n", - "\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", - " \"Vertex\n", - " Open in Vertex AI Workbench\n", - " \n", - " \n", - " \n", - " \"BQ\n", - " Open in BQ Studio\n", - " \n", - "
" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "24743cf4a1e1" - }, - "source": [ - "**_NOTE_**: This notebook has been tested in the following environment:\n", - "\n", - "* Python version = 3.10" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "tvgnzT1CKxrO" - }, - "source": [ - "## Overview\n", - "\n", - "Use this notebook to learn how to train a linear regression model using BigQuery DataFrames ML. BigQuery DataFrames ML provides a provides a scikit-learn-like API for ML powered by the BigQuery engine.\n", - "\n", - "This example is adapted from the [BQML linear regression tutorial](https://cloud.google.com/bigquery-ml/docs/linear-regression-tutorial).\n", - "\n", - "Learn more about [BigQuery DataFrames](https://cloud.google.com/python/docs/reference/bigframes/latest)." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "d975e698c9a4" - }, - "source": [ - "### Objective\n", - "\n", - "In this tutorial, you use BigQuery DataFrames to create a linear regression model that predicts the weight of an Adelie penguin based on the penguin's island of residence, culmen length and depth, flipper length, and sex.\n", - "\n", - "The steps include:\n", - "\n", - "- Creating a DataFrame from a BigQuery table.\n", - "- Cleaning and preparing data using pandas.\n", - "- Creating a linear regression model using `bigframes.ml`.\n", - "- Saving the ML model to BigQuery for future use." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "08d289fa873f" - }, - "source": [ - "### Dataset\n", - "\n", - "This tutorial uses the [```penguins``` table](https://console.cloud.google.com/bigquery?p=bigquery-public-data&d=ml_datasets&t=penguins) (a BigQuery Public Dataset) which includes data on a set of penguins including species, island of residence, weight, culmen length and depth, flipper length, and sex." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "aed92deeb4a0" - }, - "source": [ - "### Costs\n", - "\n", - "This tutorial uses billable components of Google Cloud:\n", - "\n", - "* BigQuery (compute)\n", - "* BigQuery ML\n", - "\n", - "Learn about [BigQuery compute pricing](https://cloud.google.com/bigquery/pricing#analysis_pricing_models)\n", - "and [BigQuery ML pricing](https://cloud.google.com/bigquery/pricing#bqml),\n", - "and use the [Pricing Calculator](https://cloud.google.com/products/calculator/)\n", - "to generate a cost estimate based on your projected usage." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "i7EUnXsZhAGF" - }, - "source": [ - "## Installation\n", - "\n", - "If you don't have [bigframes](https://pypi.org/project/bigframes/) package already installed, uncomment and execute the following cells to\n", - "\n", - "1. Install the package\n", - "1. Restart the notebook kernel (Jupyter or Colab) to work with the package" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "9O0Ka4W2MNF3" - }, - "outputs": [], - "source": [ - "# !pip install bigframes" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "f200f10a1da3" - }, - "outputs": [], - "source": [ - "# Automatically restart kernel after installs so that your environment can access the new packages\n", - "# import IPython\n", - "\n", - "# app = IPython.Application.instance()\n", - "# app.kernel.do_shutdown(True)" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "BF1j6f9HApxa" - }, - "source": [ - "## Before you begin\n", - "\n", - "Complete the tasks in this section to set up your environment." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "oDfTjfACBvJk" - }, - "source": [ - "### Set up your Google Cloud project\n", - "\n", - "**The following steps are required, regardless of your notebook environment.**\n", - "\n", - "1. [Select or create a Google Cloud project](https://console.cloud.google.com/cloud-resource-manager). When you first create an account, you get a $300 credit towards your compute/storage costs.\n", - "\n", - "2. [Make sure that billing is enabled for your project](https://cloud.google.com/billing/docs/how-to/modify-project).\n", - "\n", - "3. [Enable the BigQuery API](https://console.cloud.google.com/flows/enableapi?apiid=bigquery.googleapis.com).\n", - "\n", - "4. If you are running this notebook locally, install the [Cloud SDK](https://cloud.google.com/sdk)." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "WReHDGG5g0XY" - }, - "source": [ - "#### Set your project ID\n", - "\n", - "If you don't know your project ID, try the following:\n", - "* Run `gcloud config list`.\n", - "* Run `gcloud projects list`.\n", - "* See the support page: [Locate the project ID](https://support.google.com/googleapi/answer/7014113)." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "oM1iC_MfAts1" - }, - "outputs": [], - "source": [ - "PROJECT_ID = \"\" # @param {type:\"string\"}\n", - "\n", - "# Set the project id\n", - "! gcloud config set project {PROJECT_ID}" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "region" - }, - "source": [ - "#### Set the region\n", - "\n", - "You can also change the `REGION` variable used by BigQuery. Learn more about [BigQuery regions](https://cloud.google.com/bigquery/docs/locations#supported_locations)." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "eF-Twtc4XGem" - }, - "outputs": [], - "source": [ - "REGION = \"US\" # @param {type: \"string\"}" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "sBCra4QMA2wR" - }, - "source": [ - "### Authenticate your Google Cloud account\n", - "\n", - "Depending on your Jupyter environment, you might have to manually authenticate. Follow the relevant instructions below." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "74ccc9e52986" - }, - "source": [ - "**Vertex AI Workbench**\n", - "\n", - "Do nothing, you are already authenticated." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "de775a3773ba" - }, - "source": [ - "**Local JupyterLab instance**\n", - "\n", - "Uncomment and run the following cell:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "254614fa0c46" - }, - "outputs": [], - "source": [ - "# ! gcloud auth login" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "ef21552ccea8" - }, - "source": [ - "**Colab**\n", - "\n", - "Uncomment and run the following cell:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "603adbbf0532" - }, - "outputs": [], - "source": [ - "# from google.colab import auth\n", - "# auth.authenticate_user()" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "960505627ddf" - }, - "source": [ - "### Import libraries" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "PyQmSRbKA8r-" - }, - "outputs": [], - "source": [ - "import bigframes.pandas as bpd" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "init_aip:mbsdk,all" - }, - "source": [ - "### Set BigQuery DataFrames options" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "NPPMuw2PXGeo" - }, - "outputs": [], - "source": [ - "# Note: The project option is not required in all environments.\n", - "# On BigQuery Studio, the project ID is automatically detected.\n", - "bpd.options.bigquery.project = PROJECT_ID\n", - "\n", - "# Note: The location option is not required.\n", - "# It defaults to the location of the first table or query\n", - "# passed to read_gbq(). For APIs where a location can't be\n", - "# auto-detected, the location defaults to the \"US\" location.\n", - "bpd.options.bigquery.location = REGION" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "D21CoOlfFTYI" - }, - "source": [ - "If you want to reset the location of the created DataFrame or Series objects, reset the session by executing `bpd.close_session()`. After that, you can reuse `bpd.options.bigquery.location` to specify another location." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "9EMAqR37AfLS" - }, - "source": [ - "## Read a BigQuery table into a BigQuery DataFrames DataFrame\n", - "\n", - "Read the [```penguins``` table](https://console.cloud.google.com/bigquery?p=bigquery-public-data&d=ml_datasets&t=penguins) into a BigQuery DataFrames DataFrame:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "EDAaIwHpQCDZ" - }, - "outputs": [], - "source": [ - "df = bpd.read_gbq(\"bigquery-public-data.ml_datasets.penguins\")" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "DJu837YEXD7B" - }, - "source": [ - "Take a look at the DataFrame:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "_gPD0Zn1Stdb" - }, - "outputs": [], - "source": [ - "df.head()" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "rwPLjqW2Ajzh" - }, - "source": [ - "## Clean and prepare data\n", - "\n", - "You can use pandas as you normally would on the BigQuery DataFrames DataFrame, but calculations happen in the BigQuery query engine instead of your local environment.\n", - "\n", - "Because this model will focus on the Adelie Penguin species, you need to filter the data for only those rows representing Adelie penguins. Then you drop the `species` column because it is no longer needed.\n", - "\n", - "As these functions are applied, only the new DataFrame object `adelie_data` is modified. The source table and the original DataFrame object `df` don't change." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "6i6HkFJZa8na" - }, - "outputs": [], - "source": [ - "# Filter down to the data to the Adelie Penguin species\n", - "adelie_data = df[df.species == \"Adelie Penguin (Pygoscelis adeliae)\"]\n", - "\n", - "# Drop the species column\n", - "adelie_data = adelie_data.drop(columns=[\"species\"])\n", - "\n", - "# Take a look at the filtered DataFrame\n", - "adelie_data" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "jhK2OlyMbY4L" - }, - "source": [ - "Drop rows with `NULL` values in order to create a BigQuery DataFrames DataFrame for the training data:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "0am3hdlXZfxZ" - }, - "outputs": [], - "source": [ - "# Drop rows with nulls to get training data\n", - "training_data = adelie_data.dropna()\n", - "\n", - "# Take a peek at the training data\n", - "training_data" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "M_-0X7NxYK5f" - }, - "source": [ - "Specify your feature (or input) columns and the label (or output) column:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "YKwCW7Nsavap" - }, - "outputs": [], - "source": [ - "feature_columns = training_data[['island', 'culmen_length_mm', 'culmen_depth_mm', 'flipper_length_mm', 'sex']]\n", - "label_columns = training_data[['body_mass_g']]" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "CjyM7vZJZ0sQ" - }, - "source": [ - "There is a row within the `adelie_data` BigQuery DataFrames DataFrame that has a `NULL` value for the `body mass` column. `body mass` is the label column, which is the value that the model you are creating is trying to predict.\n", - "\n", - "Create a new BigQuery DataFrames DataFrame, `test_data`, for this row so that you can use it as test data on which to make a prediction later:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "wej78IDUaRW9" - }, - "outputs": [], - "source": [ - "test_data = adelie_data[adelie_data.body_mass_g.isnull()]\n", - "\n", - "test_data" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "Fx4lsNqMorJ-" - }, - "source": [ - "## Create the linear regression model\n", - "\n", - "BigQuery DataFrames ML lets you move from exploring data to creating machine learning models through its scikit-learn-like API, `bigframes.ml`. BigQuery DataFrames ML supports several types of [ML models](https://cloud.google.com/python/docs/reference/bigframes/latest#ml-capabilities).\n", - "\n", - "In this notebook, you create a linear regression model, a type of regression model that generates a continuous value from a linear combination of input features.\n", - "\n", - "When you create a model with BigQuery DataFrames ML, it is saved locally and limited to the BigQuery session. However, as you'll see in the next section, you can use `to_gbq` to save the model permanently to your BigQuery project." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "EloGtMnverFF" - }, - "source": [ - "### Create the model using `bigframes.ml`\n", - "\n", - "When you pass the feature columns without transforms, BigQuery ML uses\n", - "[automatic preprocessing](https://cloud.google.com/bigquery/docs/auto-preprocessing) to encode string values and scale numeric values.\n", - "\n", - "BigQuery ML also [automatically splits the data for training and evaluation](https://cloud.google.com/bigquery/docs/reference/standard-sql/bigqueryml-syntax-create-glm#data_split_method), although for datasets with less than 500 rows (such as this one), all rows are used for training." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "GskyyUQPowBT" - }, - "outputs": [], - "source": [ - "from bigframes.ml.linear_model import LinearRegression\n", - "\n", - "model = LinearRegression()\n", - "\n", - "model.fit(feature_columns, label_columns)" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "UGjeMPC2caKK" - }, - "source": [ - "### Score the model\n", - "\n", - "Check how the model performed by using the `score` method. More information on model scoring can be found [here](https://cloud.google.com/bigquery/docs/reference/standard-sql/bigqueryml-syntax-evaluate#mlevaluate_output)." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "kGBJKafpo0dl" - }, - "outputs": [], - "source": [ - "model.score(feature_columns, label_columns)" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "P2lUiZZ_cjri" - }, - "source": [ - "### Predict using the model\n", - "\n", - "Use the model to predict the body mass of the data row you saved earlier to the `test_data` DataFrame:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "bsQ9cmoWo0Ps" - }, - "outputs": [], - "source": [ - "model.predict(test_data)" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "GTRdUw-Ro5R1" - }, - "source": [ - "## Save the model in BigQuery\n", - "\n", - "The model is saved locally within this session. You can save the model permanently to BigQuery for use in future sessions, and to make the model sharable with others." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "K0mPaoGpcwwy" - }, - "source": [ - "Create a BigQuery dataset to house the model, adding a name for your dataset as the `DATASET_ID` variable:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "ZSP7gt13QrQt" - }, - "outputs": [], - "source": [ - "DATASET_ID = \"\" # @param {type:\"string\"}\n", - "\n", - "from google.cloud import bigquery\n", - "client = bigquery.Client(project=PROJECT_ID)\n", - "dataset = bigquery.Dataset(PROJECT_ID + \".\" + DATASET_ID)\n", - "dataset.location = REGION\n", - "dataset = client.create_dataset(dataset, exists_ok=True)\n", - "print(f\"Dataset {dataset.dataset_id} created.\")" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "zqAIWWgJczp-" - }, - "source": [ - "Save the model using the `to_gbq` method:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "QE_GD4Byo_jb" - }, - "outputs": [], - "source": [ - "model.to_gbq(DATASET_ID + \".penguin_weight\" , replace=True)" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "f7uHacAy49rT" - }, - "source": [ - "You can view the saved model in the BigQuery console under the dataset you created in the first step. Run the following cell and follow the link to view your BigQuery console:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "qDBoiA_0488Z" - }, - "outputs": [], - "source": [ - "print(f'https://console.developers.google.com/bigquery?p={PROJECT_ID}')" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "G_wjSfXpWTuy" - }, - "source": [ - "# Summary and next steps\n", - "\n", - "You've created a linear regression model using `bigframes.ml`.\n", - "\n", - "Learn more about BigQuery DataFrames in the [documentation](https://cloud.google.com/python/docs/reference/bigframes/latest) and find more sample notebooks in the [GitHub repo](https://github.com/googleapis/python-bigquery-dataframes/tree/main/notebooks)." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "TpV-iwP9qw9c" - }, - "source": [ - "## Cleaning up\n", - "\n", - "To clean up all Google Cloud resources used in this project, you can [delete the Google Cloud\n", - "project](https://cloud.google.com/resource-manager/docs/creating-managing-projects#shutting_down_projects) you used for the tutorial.\n", - "\n", - "Otherwise, you can uncomment the remaining cells and run them to delete the individual resources you created in this tutorial:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "sx_vKniMq9ZX" - }, - "outputs": [], - "source": [ - "# # Delete the BigQuery dataset and associated ML model\n", - "# from google.cloud import bigquery\n", - "# client = bigquery.Client(project=PROJECT_ID)\n", - "# client.delete_dataset(\n", - "# DATASET_ID, delete_contents=True, not_found_ok=True\n", - "# )\n", - "# print(\"Deleted dataset '{}'.\".format(DATASET_ID))" - ] - } - ], - "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.12.0" - } - }, - "nbformat": 4, - "nbformat_minor": 0 -} diff --git a/notebooks/ml/bq_dataframes_ml_linear_regression_bbq.ipynb b/notebooks/ml/bq_dataframes_ml_linear_regression_bbq.ipynb deleted file mode 100644 index 396fde5a397..00000000000 --- a/notebooks/ml/bq_dataframes_ml_linear_regression_bbq.ipynb +++ /dev/null @@ -1,2637 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 1, - "metadata": { - "id": "ur8xi4C7S06n" - }, - "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": { - "id": "JAPoU8Sm5E6e" - }, - "source": [ - "# Train a linear regression model with BigQuery DataFrames ML", - "\n", - "\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", - " \"Vertex\n", - " Open in Vertex AI Workbench\n", - " \n", - " \n", - " \n", - " \"BQ\n", - " Open in BQ Studio\n", - " \n", - "
" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "24743cf4a1e1" - }, - "source": [ - "**_NOTE_**: This notebook has been tested in the following environment:\n", - "\n", - "* Python version = 3.10" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "tvgnzT1CKxrO" - }, - "source": [ - "## Overview\n", - "\n", - "Use this notebook to learn how to train a linear regression model using BigQuery ML and the `bigframes.bigquery` module.\n", - "\n", - "This example is adapted from the [BQML linear regression tutorial](https://cloud.google.com/bigquery-ml/docs/linear-regression-tutorial).\n", - "\n", - "Learn more about [BigQuery DataFrames](https://dataframes.bigquery.dev/)." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "d975e698c9a4" - }, - "source": [ - "### Objective\n", - "\n", - "In this tutorial, you use BigQuery DataFrames to create a linear regression model that predicts the weight of an Adelie penguin based on the penguin's island of residence, culmen length and depth, flipper length, and sex.\n", - "\n", - "The steps include:\n", - "\n", - "- Creating a DataFrame from a BigQuery table.\n", - "- Cleaning and preparing data using pandas.\n", - "- Creating a linear regression model using `bigframes.ml`.\n", - "- Saving the ML model to BigQuery for future use." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "08d289fa873f" - }, - "source": [ - "### Dataset\n", - "\n", - "This tutorial uses the [```penguins``` table](https://console.cloud.google.com/bigquery?p=bigquery-public-data&d=ml_datasets&t=penguins) (a BigQuery Public Dataset) which includes data on a set of penguins including species, island of residence, weight, culmen length and depth, flipper length, and sex." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "aed92deeb4a0" - }, - "source": [ - "### Costs\n", - "\n", - "This tutorial uses billable components of Google Cloud:\n", - "\n", - "* BigQuery (compute)\n", - "* BigQuery ML\n", - "\n", - "Learn about [BigQuery compute pricing](https://cloud.google.com/bigquery/pricing#analysis_pricing_models)\n", - "and [BigQuery ML pricing](https://cloud.google.com/bigquery/pricing#bqml),\n", - "and use the [Pricing Calculator](https://cloud.google.com/products/calculator/)\n", - "to generate a cost estimate based on your projected usage." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "i7EUnXsZhAGF" - }, - "source": [ - "## Installation\n", - "\n", - "If you don't have [bigframes](https://pypi.org/project/bigframes/) package already installed, uncomment and execute the following cells to\n", - "\n", - "1. Install the package\n", - "1. Restart the notebook kernel (Jupyter or Colab) to work with the package" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": { - "id": "9O0Ka4W2MNF3" - }, - "outputs": [], - "source": [ - "# !pip install bigframes" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": { - "id": "f200f10a1da3" - }, - "outputs": [], - "source": [ - "# Automatically restart kernel after installs so that your environment can access the new packages\n", - "# import IPython\n", - "\n", - "# app = IPython.Application.instance()\n", - "# app.kernel.do_shutdown(True)" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "BF1j6f9HApxa" - }, - "source": [ - "## Before you begin\n", - "\n", - "Complete the tasks in this section to set up your environment." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "oDfTjfACBvJk" - }, - "source": [ - "### Set up your Google Cloud project\n", - "\n", - "**The following steps are required, regardless of your notebook environment.**\n", - "\n", - "1. [Select or create a Google Cloud project](https://console.cloud.google.com/cloud-resource-manager). When you first create an account, you get a $300 credit towards your compute/storage costs.\n", - "\n", - "2. [Make sure that billing is enabled for your project](https://cloud.google.com/billing/docs/how-to/modify-project).\n", - "\n", - "3. [Enable the BigQuery API](https://console.cloud.google.com/flows/enableapi?apiid=bigquery.googleapis.com).\n", - "\n", - "4. If you are running this notebook locally, install the [Cloud SDK](https://cloud.google.com/sdk)." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "WReHDGG5g0XY" - }, - "source": [ - "#### Set your project ID\n", - "\n", - "If you don't know your project ID, try the following:\n", - "* Run `gcloud config list`.\n", - "* Run `gcloud projects list`.\n", - "* See the support page: [Locate the project ID](https://support.google.com/googleapi/answer/7014113)." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "oM1iC_MfAts1" - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Updated property [core/project].\n" - ] - } - ], - "source": [ - "PROJECT_ID = \"\" # @param {type:\"string\"}\n", - "\n", - "# Set the project id\n", - "! gcloud config set project {PROJECT_ID}" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "region" - }, - "source": [ - "#### Set the region\n", - "\n", - "You can also change the `REGION` variable used by BigQuery. Learn more about [BigQuery regions](https://cloud.google.com/bigquery/docs/locations#supported_locations)." - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": { - "id": "eF-Twtc4XGem" - }, - "outputs": [], - "source": [ - "REGION = \"US\" # @param {type: \"string\"}" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "sBCra4QMA2wR" - }, - "source": [ - "### Authenticate your Google Cloud account\n", - "\n", - "Depending on your Jupyter environment, you might have to manually authenticate. Follow the relevant instructions below." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "74ccc9e52986" - }, - "source": [ - "**Vertex AI Workbench**\n", - "\n", - "Do nothing, you are already authenticated." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "de775a3773ba" - }, - "source": [ - "**Local JupyterLab instance**\n", - "\n", - "Uncomment and run the following cell:" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": { - "id": "254614fa0c46" - }, - "outputs": [], - "source": [ - "# ! gcloud auth login" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "ef21552ccea8" - }, - "source": [ - "**Colab**\n", - "\n", - "Uncomment and run the following cell:" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": { - "id": "603adbbf0532" - }, - "outputs": [], - "source": [ - "# from google.colab import auth\n", - "# auth.authenticate_user()" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "960505627ddf" - }, - "source": [ - "### Import libraries" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": { - "id": "PyQmSRbKA8r-" - }, - "outputs": [], - "source": [ - "import bigframes.pandas as bpd" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "init_aip:mbsdk,all" - }, - "source": [ - "### Set BigQuery DataFrames options" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "metadata": { - "id": "NPPMuw2PXGeo" - }, - "outputs": [], - "source": [ - "# Note: The project option is not required in all environments.\n", - "# On BigQuery Studio, the project ID is automatically detected.\n", - "bpd.options.bigquery.project = PROJECT_ID\n", - "\n", - "# Note: The location option is not required.\n", - "# It defaults to the location of the first table or query\n", - "# passed to read_gbq(). For APIs where a location can't be\n", - "# auto-detected, the location defaults to the \"US\" location.\n", - "bpd.options.bigquery.location = REGION\n", - "\n", - "# Recommended for performance. Disables pandas default ordering of all rows.\n", - "bpd.options.bigquery.ordering_mode = \"partial\"" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "D21CoOlfFTYI" - }, - "source": [ - "If you want to reset the location of the created DataFrame or Series objects, reset the session by executing `bpd.close_session()`. After that, you can reuse `bpd.options.bigquery.location` to specify another location." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "9EMAqR37AfLS" - }, - "source": [ - "## Read a BigQuery table into a BigQuery DataFrames DataFrame\n", - "\n", - "Read the [```penguins``` table](https://console.cloud.google.com/bigquery?p=bigquery-public-data&d=ml_datasets&t=penguins) into a BigQuery DataFrames DataFrame:" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "metadata": { - "id": "EDAaIwHpQCDZ" - }, - "outputs": [], - "source": [ - "df = bpd.read_gbq(\"bigquery-public-data.ml_datasets.penguins\")" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "DJu837YEXD7B" - }, - "source": [ - "Take a look at the DataFrame:" - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "metadata": { - "id": "_gPD0Zn1Stdb" - }, - "outputs": [ - { - "data": { - "text/html": [ - "✅ Completed. " - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "application/vnd.microsoft.datawrangler.viewer.v0+json": { - "columns": [ - { - "name": "index", - "rawType": "int64", - "type": "integer" - }, - { - "name": "species", - "rawType": "string", - "type": "string" - }, - { - "name": "island", - "rawType": "string", - "type": "string" - }, - { - "name": "culmen_length_mm", - "rawType": "Float64", - "type": "float" - }, - { - "name": "culmen_depth_mm", - "rawType": "Float64", - "type": "float" - }, - { - "name": "flipper_length_mm", - "rawType": "Float64", - "type": "float" - }, - { - "name": "body_mass_g", - "rawType": "Float64", - "type": "float" - }, - { - "name": "sex", - "rawType": "string", - "type": "string" - } - ], - "ref": "a652ba52-0445-4228-a2d5-baf837933515", - "rows": [ - [ - "0", - "Adelie Penguin (Pygoscelis adeliae)", - "Dream", - "36.6", - "18.4", - "184.0", - "3475.0", - "FEMALE" - ], - [ - "1", - "Adelie Penguin (Pygoscelis adeliae)", - "Dream", - "39.8", - "19.1", - "184.0", - "4650.0", - "MALE" - ], - [ - "2", - "Adelie Penguin (Pygoscelis adeliae)", - "Dream", - "40.9", - "18.9", - "184.0", - "3900.0", - "MALE" - ], - [ - "3", - "Chinstrap penguin (Pygoscelis antarctica)", - "Dream", - "46.5", - "17.9", - "192.0", - "3500.0", - "FEMALE" - ], - [ - "4", - "Adelie Penguin (Pygoscelis adeliae)", - "Dream", - "37.3", - "16.8", - "192.0", - "3000.0", - "FEMALE" - ] - ], - "shape": { - "columns": 7, - "rows": 5 - } - }, - "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", - "
speciesislandculmen_length_mmculmen_depth_mmflipper_length_mmbody_mass_gsex
0Adelie Penguin (Pygoscelis adeliae)Dream36.618.4184.03475.0FEMALE
1Adelie Penguin (Pygoscelis adeliae)Dream39.819.1184.04650.0MALE
2Adelie Penguin (Pygoscelis adeliae)Dream40.918.9184.03900.0MALE
3Chinstrap penguin (Pygoscelis antarctica)Dream46.517.9192.03500.0FEMALE
4Adelie Penguin (Pygoscelis adeliae)Dream37.316.8192.03000.0FEMALE
\n", - "
" - ], - "text/plain": [ - " species island culmen_length_mm \\\n", - "0 Adelie Penguin (Pygoscelis adeliae) Dream 36.6 \n", - "1 Adelie Penguin (Pygoscelis adeliae) Dream 39.8 \n", - "2 Adelie Penguin (Pygoscelis adeliae) Dream 40.9 \n", - "3 Chinstrap penguin (Pygoscelis antarctica) Dream 46.5 \n", - "4 Adelie Penguin (Pygoscelis adeliae) Dream 37.3 \n", - "\n", - " culmen_depth_mm flipper_length_mm body_mass_g sex \n", - "0 18.4 184.0 3475.0 FEMALE \n", - "1 19.1 184.0 4650.0 MALE \n", - "2 18.9 184.0 3900.0 MALE \n", - "3 17.9 192.0 3500.0 FEMALE \n", - "4 16.8 192.0 3000.0 FEMALE " - ] - }, - "execution_count": 11, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "df.peek()" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "rwPLjqW2Ajzh" - }, - "source": [ - "## Clean and prepare data\n", - "\n", - "You can use pandas as you normally would on the BigQuery DataFrames DataFrame, but calculations happen in the BigQuery query engine instead of your local environment.\n", - "\n", - "Because this model will focus on the Adelie Penguin species, you need to filter the data for only those rows representing Adelie penguins. Then you drop the `species` column because it is no longer needed.\n", - "\n", - "As these functions are applied, only the new DataFrame object `adelie_data` is modified. The source table and the original DataFrame object `df` don't change." - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "metadata": { - "id": "6i6HkFJZa8na" - }, - "outputs": [ - { - "data": { - "text/html": [ - "✅ Completed. \n", - " Query processed 28.9 kB in 12 seconds of slot time. [Job bigframes-dev:US.bb256e8c-f2c7-4eff-b5f3-fcc6836110cf details]\n", - " " - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "✅ Completed. \n", - " Query processed 8.4 kB in a moment of slot time.\n", - " " - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "✅ Completed. " - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "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", - "
islandculmen_length_mmculmen_depth_mmflipper_length_mmbody_mass_gsex
0Dream36.618.4184.03475.0FEMALE
1Dream39.819.1184.04650.0MALE
2Dream40.918.9184.03900.0MALE
3Dream37.316.8192.03000.0FEMALE
4Dream43.218.5192.04100.0MALE
5Dream40.220.1200.03975.0MALE
6Dream40.818.9208.04300.0MALE
7Dream39.018.7185.03650.0MALE
8Dream37.016.9185.03000.0FEMALE
9Dream34.017.1185.03400.0FEMALE
\n", - "

10 rows × 6 columns

\n", - "
[152 rows x 6 columns in total]" - ], - "text/plain": [ - "island culmen_length_mm culmen_depth_mm flipper_length_mm body_mass_g \\\n", - " Dream 36.6 18.4 184.0 3475.0 \n", - " Dream 39.8 19.1 184.0 4650.0 \n", - " Dream 40.9 18.9 184.0 3900.0 \n", - " Dream 37.3 16.8 192.0 3000.0 \n", - " Dream 43.2 18.5 192.0 4100.0 \n", - " Dream 40.2 20.1 200.0 3975.0 \n", - " Dream 40.8 18.9 208.0 4300.0 \n", - " Dream 39.0 18.7 185.0 3650.0 \n", - " Dream 37.0 16.9 185.0 3000.0 \n", - " Dream 34.0 17.1 185.0 3400.0 \n", - "\n", - " sex \n", - "FEMALE \n", - " MALE \n", - " MALE \n", - "FEMALE \n", - " MALE \n", - " MALE \n", - " MALE \n", - " MALE \n", - "FEMALE \n", - "FEMALE \n", - "...\n", - "\n", - "[152 rows x 6 columns]" - ] - }, - "execution_count": 12, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# Filter down to the data to the Adelie Penguin species\n", - "adelie_data = df[df.species == \"Adelie Penguin (Pygoscelis adeliae)\"]\n", - "\n", - "# Drop the species column\n", - "adelie_data = adelie_data.drop(columns=[\"species\"])\n", - "\n", - "# Take a look at the filtered DataFrame\n", - "adelie_data" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "jhK2OlyMbY4L" - }, - "source": [ - "Drop rows with `NULL` values in order to create a BigQuery DataFrames DataFrame for the training data:" - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "metadata": { - "id": "0am3hdlXZfxZ" - }, - "outputs": [ - { - "data": { - "text/html": [ - "Starting." - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "✅ Completed. \n", - " Query processed 8.1 kB in a moment of slot time.\n", - " " - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "✅ Completed. " - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "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", - "
islandculmen_length_mmculmen_depth_mmflipper_length_mmbody_mass_gsex
0Dream36.618.4184.03475.0FEMALE
1Dream39.819.1184.04650.0MALE
2Dream40.918.9184.03900.0MALE
3Dream37.316.8192.03000.0FEMALE
4Dream43.218.5192.04100.0MALE
5Dream40.220.1200.03975.0MALE
6Dream40.818.9208.04300.0MALE
7Dream39.018.7185.03650.0MALE
8Dream37.016.9185.03000.0FEMALE
9Dream34.017.1185.03400.0FEMALE
\n", - "

10 rows × 6 columns

\n", - "
[146 rows x 6 columns in total]" - ], - "text/plain": [ - "island culmen_length_mm culmen_depth_mm flipper_length_mm body_mass_g \\\n", - " Dream 36.6 18.4 184.0 3475.0 \n", - " Dream 39.8 19.1 184.0 4650.0 \n", - " Dream 40.9 18.9 184.0 3900.0 \n", - " Dream 37.3 16.8 192.0 3000.0 \n", - " Dream 43.2 18.5 192.0 4100.0 \n", - " Dream 40.2 20.1 200.0 3975.0 \n", - " Dream 40.8 18.9 208.0 4300.0 \n", - " Dream 39.0 18.7 185.0 3650.0 \n", - " Dream 37.0 16.9 185.0 3000.0 \n", - " Dream 34.0 17.1 185.0 3400.0 \n", - "\n", - " sex \n", - "FEMALE \n", - " MALE \n", - " MALE \n", - "FEMALE \n", - " MALE \n", - " MALE \n", - " MALE \n", - " MALE \n", - "FEMALE \n", - "FEMALE \n", - "...\n", - "\n", - "[146 rows x 6 columns]" - ] - }, - "execution_count": 13, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# Drop rows with nulls to get training data\n", - "training_data = adelie_data.dropna()\n", - "\n", - "# Take a peek at the training data\n", - "training_data" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "Fx4lsNqMorJ-" - }, - "source": [ - "## Create the linear regression model\n", - "\n", - "In this notebook, you create a linear regression model, a type of regression model that generates a continuous value from a linear combination of input features." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Create a BigQuery dataset to house the model, adding a name for your dataset as the `DATASET_ID` variable:" - ] - }, - { - "cell_type": "code", - "execution_count": 14, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Dataset bqml_tutorial created.\n" - ] - } - ], - "source": [ - "DATASET_ID = \"bqml_tutorial\" # @param {type:\"string\"}\n", - "\n", - "from google.cloud import bigquery\n", - "client = bigquery.Client(project=PROJECT_ID)\n", - "dataset = bigquery.Dataset(PROJECT_ID + \".\" + DATASET_ID)\n", - "dataset.location = REGION\n", - "dataset = client.create_dataset(dataset, exists_ok=True)\n", - "print(f\"Dataset {dataset.dataset_id} created.\")" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "EloGtMnverFF" - }, - "source": [ - "### Create the model using `bigframes.bigquery.ml.create_model`\n", - "\n", - "When you pass the feature columns without transforms, BigQuery ML uses\n", - "[automatic preprocessing](https://cloud.google.com/bigquery/docs/auto-preprocessing) to encode string values and scale numeric values.\n", - "\n", - "BigQuery ML also [automatically splits the data for training and evaluation](https://cloud.google.com/bigquery/docs/reference/standard-sql/bigqueryml-syntax-create-glm#data_split_method), although for datasets with less than 500 rows (such as this one), all rows are used for training." - ] - }, - { - "cell_type": "code", - "execution_count": 15, - "metadata": { - "id": "GskyyUQPowBT" - }, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - " Query started with request ID bigframes-dev:US.a33b3628-730b-46e8-ad17-c78bb48619ce.
SQL
CREATE OR REPLACE MODEL `bigframes-dev.bqml_tutorial.penguin_weight`\n",
-       "OPTIONS(model_type = 'LINEAR_REG')\n",
-       "AS SELECT\n",
-       "`bfuid_col_3` AS `island`,\n",
-       "`bfuid_col_4` AS `culmen_length_mm`,\n",
-       "`bfuid_col_5` AS `culmen_depth_mm`,\n",
-       "`bfuid_col_6` AS `flipper_length_mm`,\n",
-       "`bfuid_col_7` AS `label`,\n",
-       "`bfuid_col_8` AS `sex`\n",
-       "FROM\n",
-       "(SELECT\n",
-       "  `t0`.`bfuid_col_3`,\n",
-       "  `t0`.`bfuid_col_4`,\n",
-       "  `t0`.`bfuid_col_5`,\n",
-       "  `t0`.`bfuid_col_6`,\n",
-       "  `t0`.`bfuid_col_7`,\n",
-       "  `t0`.`bfuid_col_8`\n",
-       "FROM `bigframes-dev._63cfa399614a54153cc386c27d6c0c6fdb249f9e._e154f0aa_5b29_492a_b464_a77c5f5a3dbd_bqdf_60fa3196-5a3e-45ae-898e-c2b473bfa1e9` AS `t0`)\n",
-       "
\n", - " " - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "application/vnd.microsoft.datawrangler.viewer.v0+json": { - "columns": [ - { - "name": "index", - "rawType": "object", - "type": "string" - }, - { - "name": "0", - "rawType": "object", - "type": "unknown" - } - ], - "ref": "851c170c-08a5-4c06-8c0b-4547dbde3f18", - "rows": [ - [ - "etag", - "P3XS+g0ZZM19ywL+hdwUmQ==" - ], - [ - "modelReference", - "{'projectId': 'bigframes-dev', 'datasetId': 'bqml_tutorial', 'modelId': 'penguin_weight'}" - ], - [ - "creationTime", - "1764779445166" - ], - [ - "lastModifiedTime", - "1764779445237" - ], - [ - "modelType", - "LINEAR_REGRESSION" - ], - [ - "trainingRuns", - "[{'trainingOptions': {'lossType': 'MEAN_SQUARED_LOSS', 'l2Regularization': 0, 'inputLabelColumns': ['label'], 'dataSplitMethod': 'AUTO_SPLIT', 'optimizationStrategy': 'NORMAL_EQUATION', 'calculatePValues': False, 'enableGlobalExplain': False, 'categoryEncodingMethod': 'ONE_HOT_ENCODING', 'fitIntercept': True, 'standardizeFeatures': True}, 'trainingStartTime': '1764779429690', 'results': [{'index': 0, 'durationMs': '3104', 'trainingLoss': 78553.60163372214}], 'evaluationMetrics': {'regressionMetrics': {'meanAbsoluteError': 223.87876300779865, 'meanSquaredError': 78553.60163372215, 'meanSquaredLogError': 0.005614202871872688, 'medianAbsoluteError': 181.33091105963013, 'rSquared': 0.6239507555914934}}, 'startTime': '2025-12-03T16:30:29.690Z'}]" - ], - [ - "featureColumns", - "[{'name': 'island', 'type': {'typeKind': 'STRING'}}, {'name': 'culmen_length_mm', 'type': {'typeKind': 'FLOAT64'}}, {'name': 'culmen_depth_mm', 'type': {'typeKind': 'FLOAT64'}}, {'name': 'flipper_length_mm', 'type': {'typeKind': 'FLOAT64'}}, {'name': 'sex', 'type': {'typeKind': 'STRING'}}]" - ], - [ - "labelColumns", - "[{'name': 'predicted_label', 'type': {'typeKind': 'FLOAT64'}}]" - ], - [ - "location", - "US" - ] - ], - "shape": { - "columns": 1, - "rows": 9 - } - }, - "text/plain": [ - "etag P3XS+g0ZZM19ywL+hdwUmQ==\n", - "modelReference {'projectId': 'bigframes-dev', 'datasetId': 'b...\n", - "creationTime 1764779445166\n", - "lastModifiedTime 1764779445237\n", - "modelType LINEAR_REGRESSION\n", - "trainingRuns [{'trainingOptions': {'lossType': 'MEAN_SQUARE...\n", - "featureColumns [{'name': 'island', 'type': {'typeKind': 'STRI...\n", - "labelColumns [{'name': 'predicted_label', 'type': {'typeKin...\n", - "location US\n", - "dtype: object" - ] - }, - "execution_count": 15, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "import bigframes.bigquery as bbq\n", - "\n", - "model_name = f\"{PROJECT_ID}.{DATASET_ID}.penguin_weight\"\n", - "model_metadata = bbq.ml.create_model(\n", - " model_name,\n", - " replace=True,\n", - " options={\n", - " \"model_type\": \"LINEAR_REG\",\n", - " },\n", - " training_data=training_data.rename(columns={\"body_mass_g\": \"label\"})\n", - ")\n", - "model_metadata" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "GskyyUQPowBT" - }, - "source": [ - "### Evaluate the model\n", - "\n", - "Check how the model performed by using the `evalutate` function. More information on model evaluation can be found [here](https://cloud.google.com/bigquery/docs/reference/standard-sql/bigqueryml-syntax-evaluate#mlevaluate_output)." - ] - }, - { - "cell_type": "code", - "execution_count": 16, - "metadata": { - "id": "kGBJKafpo0dl" - }, - "outputs": [ - { - "data": { - "text/html": [ - "✅ Completed. \n", - " Query processed 0 Bytes in a moment of slot time.\n", - " " - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "✅ Completed. " - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "✅ Completed. " - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "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", - "
mean_absolute_errormean_squared_errormean_squared_log_errormedian_absolute_errorr2_scoreexplained_variance
0223.87876378553.6016340.005614181.3309110.6239510.623951
\n", - "

1 rows × 6 columns

\n", - "
[1 rows x 6 columns in total]" - ], - "text/plain": [ - " mean_absolute_error mean_squared_error mean_squared_log_error \\\n", - "0 223.878763 78553.601634 0.005614 \n", - "\n", - " median_absolute_error r2_score explained_variance \n", - "0 181.330911 0.623951 0.623951 \n", - "\n", - "[1 rows x 6 columns]" - ] - }, - "execution_count": 16, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "bbq.ml.evaluate(model_name)" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "P2lUiZZ_cjri" - }, - "source": [ - "### Use the model to predict outcomes\n", - "\n", - "Now that you have evaluated your model, the next step is to use it to predict an\n", - "outcome. You can run `bigframes.bigquery.ml.predict` function on the model to\n", - "predict the body mass in grams of all penguins that reside on the Biscoe\n", - "Islands." - ] - }, - { - "cell_type": "code", - "execution_count": 17, - "metadata": { - "id": "bsQ9cmoWo0Ps" - }, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/usr/local/google/home/swast/src/github.com/googleapis/python-bigquery-dataframes/bigframes/core/log_adapter.py:182: TimeTravelCacheWarning: Reading cached table from 2025-12-03 16:30:18.272882+00:00 to avoid\n", - "incompatibilies with previous reads of this table. To read the latest\n", - "version, set `use_cache=False` or close the current session with\n", - "Session.close() or bigframes.pandas.close_session().\n", - " return method(*args, **kwargs)\n" - ] - }, - { - "data": { - "text/html": [ - "✅ Completed. \n", - " Query processed 29.3 kB in a moment of slot time.\n", - " " - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "✅ Completed. " - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "✅ Completed. " - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "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", - "
predicted_labelspeciesislandculmen_length_mmculmen_depth_mmflipper_length_mmbody_mass_gsex
03945.010052Gentoo penguin (Pygoscelis papua)Biscoe<NA><NA><NA><NA><NA>
13914.916297Adelie Penguin (Pygoscelis adeliae)Biscoe39.718.9184.03550.0MALE
23278.611224Adelie Penguin (Pygoscelis adeliae)Biscoe36.417.1184.02850.0FEMALE
34006.367355Adelie Penguin (Pygoscelis adeliae)Biscoe41.618.0192.03950.0MALE
43417.610478Adelie Penguin (Pygoscelis adeliae)Biscoe35.017.9192.03725.0FEMALE
54009.612421Adelie Penguin (Pygoscelis adeliae)Biscoe41.118.2192.04050.0MALE
64231.330911Adelie Penguin (Pygoscelis adeliae)Biscoe42.019.5200.04050.0MALE
73554.308906Gentoo penguin (Pygoscelis papua)Biscoe43.813.9208.04300.0FEMALE
83550.677455Gentoo penguin (Pygoscelis papua)Biscoe43.314.0208.04575.0FEMALE
93537.882543Gentoo penguin (Pygoscelis papua)Biscoe44.013.6208.04350.0FEMALE
\n", - "

10 rows × 8 columns

\n", - "
[168 rows x 8 columns in total]" - ], - "text/plain": [ - " predicted_label species island \\\n", - "0 3945.010052 Gentoo penguin (Pygoscelis papua) Biscoe \n", - "1 3914.916297 Adelie Penguin (Pygoscelis adeliae) Biscoe \n", - "2 3278.611224 Adelie Penguin (Pygoscelis adeliae) Biscoe \n", - "3 4006.367355 Adelie Penguin (Pygoscelis adeliae) Biscoe \n", - "4 3417.610478 Adelie Penguin (Pygoscelis adeliae) Biscoe \n", - "5 4009.612421 Adelie Penguin (Pygoscelis adeliae) Biscoe \n", - "6 4231.330911 Adelie Penguin (Pygoscelis adeliae) Biscoe \n", - "7 3554.308906 Gentoo penguin (Pygoscelis papua) Biscoe \n", - "8 3550.677455 Gentoo penguin (Pygoscelis papua) Biscoe \n", - "9 3537.882543 Gentoo penguin (Pygoscelis papua) Biscoe \n", - "\n", - " culmen_length_mm culmen_depth_mm flipper_length_mm body_mass_g sex \n", - "0 \n", - "1 39.7 18.9 184.0 3550.0 MALE \n", - "2 36.4 17.1 184.0 2850.0 FEMALE \n", - "3 41.6 18.0 192.0 3950.0 MALE \n", - "4 35.0 17.9 192.0 3725.0 FEMALE \n", - "5 41.1 18.2 192.0 4050.0 MALE \n", - "6 42.0 19.5 200.0 4050.0 MALE \n", - "7 43.8 13.9 208.0 4300.0 FEMALE \n", - "8 43.3 14.0 208.0 4575.0 FEMALE \n", - "9 44.0 13.6 208.0 4350.0 FEMALE \n", - "...\n", - "\n", - "[168 rows x 8 columns]" - ] - }, - "execution_count": 17, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "df = bpd.read_gbq(\"bigquery-public-data.ml_datasets.penguins\")\n", - "biscoe = df[df[\"island\"].str.contains(\"Biscoe\")]\n", - "bbq.ml.predict(model_name, biscoe)" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "GTRdUw-Ro5R1" - }, - "source": [ - "### Explain the prediction results\n", - "\n", - "To understand why the model is generating these prediction results, you can use the `explain_predict` function." - ] - }, - { - "cell_type": "code", - "execution_count": 18, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - " Query started with request ID bigframes-dev:US.161bba69-c852-4916-a2df-bb5b309be6e4.
SQL
SELECT * FROM ML.EXPLAIN_PREDICT(MODEL `bigframes-dev.bqml_tutorial.penguin_weight`, (SELECT\n",
-       "`bfuid_col_22` AS `species`,\n",
-       "`bfuid_col_23` AS `island`,\n",
-       "`bfuid_col_24` AS `culmen_length_mm`,\n",
-       "`bfuid_col_25` AS `culmen_depth_mm`,\n",
-       "`bfuid_col_26` AS `flipper_length_mm`,\n",
-       "`bfuid_col_27` AS `body_mass_g`,\n",
-       "`bfuid_col_28` AS `sex`\n",
-       "FROM\n",
-       "(SELECT\n",
-       "  `t0`.`species`,\n",
-       "  `t0`.`island`,\n",
-       "  `t0`.`culmen_length_mm`,\n",
-       "  `t0`.`culmen_depth_mm`,\n",
-       "  `t0`.`flipper_length_mm`,\n",
-       "  `t0`.`body_mass_g`,\n",
-       "  `t0`.`sex`,\n",
-       "  `t0`.`species` AS `bfuid_col_22`,\n",
-       "  `t0`.`island` AS `bfuid_col_23`,\n",
-       "  `t0`.`culmen_length_mm` AS `bfuid_col_24`,\n",
-       "  `t0`.`culmen_depth_mm` AS `bfuid_col_25`,\n",
-       "  `t0`.`flipper_length_mm` AS `bfuid_col_26`,\n",
-       "  `t0`.`body_mass_g` AS `bfuid_col_27`,\n",
-       "  `t0`.`sex` AS `bfuid_col_28`,\n",
-       "  regexp_contains(`t0`.`island`, 'Biscoe') AS `bfuid_col_29`\n",
-       "FROM (\n",
-       "  SELECT\n",
-       "    `species`,\n",
-       "    `island`,\n",
-       "    `culmen_length_mm`,\n",
-       "    `culmen_depth_mm`,\n",
-       "    `flipper_length_mm`,\n",
-       "    `body_mass_g`,\n",
-       "    `sex`\n",
-       "  FROM `bigquery-public-data.ml_datasets.penguins` FOR SYSTEM_TIME AS OF TIMESTAMP('2025-12-03T16:30:18.272882+00:00')\n",
-       ") AS `t0`\n",
-       "WHERE\n",
-       "  regexp_contains(`t0`.`island`, 'Biscoe'))), STRUCT(3 AS top_k_features))\n",
-       "
\n", - " " - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "✅ Completed. " - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "✅ Completed. " - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "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", - "
predicted_labeltop_feature_attributionsbaseline_prediction_valueprediction_valueapproximation_errorspeciesislandculmen_length_mmculmen_depth_mmflipper_length_mmbody_mass_gsex
03945.010052[{'feature': 'island', 'attribution': 0.0}\n", - " {'...3945.0100523945.0100520.0Gentoo penguin (Pygoscelis papua)Biscoe<NA><NA><NA><NA><NA>
13914.916297[{'feature': 'flipper_length_mm', 'attribution...3945.0100523914.9162970.0Adelie Penguin (Pygoscelis adeliae)Biscoe39.718.9184.03550.0MALE
23278.611224[{'feature': 'sex', 'attribution': -443.175184...3945.0100523278.6112240.0Adelie Penguin (Pygoscelis adeliae)Biscoe36.417.1184.02850.0FEMALE
34006.367355[{'feature': 'culmen_length_mm', 'attribution'...3945.0100524006.3673550.0Adelie Penguin (Pygoscelis adeliae)Biscoe41.618.0192.03950.0MALE
43417.610478[{'feature': 'sex', 'attribution': -443.175184...3945.0100523417.6104780.0Adelie Penguin (Pygoscelis adeliae)Biscoe35.017.9192.03725.0FEMALE
54009.612421[{'feature': 'culmen_length_mm', 'attribution'...3945.0100524009.6124210.0Adelie Penguin (Pygoscelis adeliae)Biscoe41.118.2192.04050.0MALE
64231.330911[{'feature': 'flipper_length_mm', 'attribution...3945.0100524231.3309110.0Adelie Penguin (Pygoscelis adeliae)Biscoe42.019.5200.04050.0MALE
73554.308906[{'feature': 'sex', 'attribution': -443.175184...3945.0100523554.3089060.0Gentoo penguin (Pygoscelis papua)Biscoe43.813.9208.04300.0FEMALE
83550.677455[{'feature': 'sex', 'attribution': -443.175184...3945.0100523550.6774550.0Gentoo penguin (Pygoscelis papua)Biscoe43.314.0208.04575.0FEMALE
93537.882543[{'feature': 'sex', 'attribution': -443.175184...3945.0100523537.8825430.0Gentoo penguin (Pygoscelis papua)Biscoe44.013.6208.04350.0FEMALE
\n", - "

10 rows × 12 columns

\n", - "
[168 rows x 12 columns in total]" - ], - "text/plain": [ - " predicted_label top_feature_attributions \\\n", - "0 3945.010052 [{'feature': 'island', 'attribution': 0.0}\n", - " {'... \n", - "1 3914.916297 [{'feature': 'flipper_length_mm', 'attribution... \n", - "2 3278.611224 [{'feature': 'sex', 'attribution': -443.175184... \n", - "3 4006.367355 [{'feature': 'culmen_length_mm', 'attribution'... \n", - "4 3417.610478 [{'feature': 'sex', 'attribution': -443.175184... \n", - "5 4009.612421 [{'feature': 'culmen_length_mm', 'attribution'... \n", - "6 4231.330911 [{'feature': 'flipper_length_mm', 'attribution... \n", - "7 3554.308906 [{'feature': 'sex', 'attribution': -443.175184... \n", - "8 3550.677455 [{'feature': 'sex', 'attribution': -443.175184... \n", - "9 3537.882543 [{'feature': 'sex', 'attribution': -443.175184... \n", - "\n", - " baseline_prediction_value prediction_value approximation_error \\\n", - "0 3945.010052 3945.010052 0.0 \n", - "1 3945.010052 3914.916297 0.0 \n", - "2 3945.010052 3278.611224 0.0 \n", - "3 3945.010052 4006.367355 0.0 \n", - "4 3945.010052 3417.610478 0.0 \n", - "5 3945.010052 4009.612421 0.0 \n", - "6 3945.010052 4231.330911 0.0 \n", - "7 3945.010052 3554.308906 0.0 \n", - "8 3945.010052 3550.677455 0.0 \n", - "9 3945.010052 3537.882543 0.0 \n", - "\n", - " species island culmen_length_mm \\\n", - "0 Gentoo penguin (Pygoscelis papua) Biscoe \n", - "1 Adelie Penguin (Pygoscelis adeliae) Biscoe 39.7 \n", - "2 Adelie Penguin (Pygoscelis adeliae) Biscoe 36.4 \n", - "3 Adelie Penguin (Pygoscelis adeliae) Biscoe 41.6 \n", - "4 Adelie Penguin (Pygoscelis adeliae) Biscoe 35.0 \n", - "5 Adelie Penguin (Pygoscelis adeliae) Biscoe 41.1 \n", - "6 Adelie Penguin (Pygoscelis adeliae) Biscoe 42.0 \n", - "7 Gentoo penguin (Pygoscelis papua) Biscoe 43.8 \n", - "8 Gentoo penguin (Pygoscelis papua) Biscoe 43.3 \n", - "9 Gentoo penguin (Pygoscelis papua) Biscoe 44.0 \n", - "\n", - " culmen_depth_mm flipper_length_mm body_mass_g sex \n", - "0 \n", - "1 18.9 184.0 3550.0 MALE \n", - "2 17.1 184.0 2850.0 FEMALE \n", - "3 18.0 192.0 3950.0 MALE \n", - "4 17.9 192.0 3725.0 FEMALE \n", - "5 18.2 192.0 4050.0 MALE \n", - "6 19.5 200.0 4050.0 MALE \n", - "7 13.9 208.0 4300.0 FEMALE \n", - "8 14.0 208.0 4575.0 FEMALE \n", - "9 13.6 208.0 4350.0 FEMALE \n", - "...\n", - "\n", - "[168 rows x 12 columns]" - ] - }, - "execution_count": 18, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "bbq.ml.explain_predict(model_name, biscoe, top_k_features=3)" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "K0mPaoGpcwwy" - }, - "source": [] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Globally explain the model\n", - "\n", - "To know which features are generally the most important to determine penguin\n", - "weight, you can use the `global_explain` function. In order to use\n", - "`global_explain`, you must retrain the model with the `enable_global_explain`\n", - "option set to `True`." - ] - }, - { - "cell_type": "code", - "execution_count": 19, - "metadata": { - "id": "ZSP7gt13QrQt" - }, - "outputs": [ - { - "data": { - "text/html": [ - "✅ Completed. \n", - " Query processed 6.9 kB in 53 seconds of slot time. [Job bigframes-dev:US.job_welN8ErlZ_sTG7oOEULsWUgmIg7l details]\n", - " " - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "model_name = f\"{PROJECT_ID}.{DATASET_ID}.penguin_weight_with_global_explain\"\n", - "model_metadata = bbq.ml.create_model(\n", - " model_name,\n", - " replace=True,\n", - " options={\n", - " \"model_type\": \"LINEAR_REG\",\n", - " \"input_label_cols\": [\"body_mass_g\"],\n", - " \"enable_global_explain\": True,\n", - " },\n", - " training_data=training_data,\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": 20, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "✅ Completed. \n", - " Query processed 0 Bytes in a moment of slot time.\n", - " " - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "✅ Completed. " - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "✅ Completed. " - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "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", - "
featureattribution
0sex221.587592
1flipper_length_mm71.311846
2culmen_depth_mm66.17986
3culmen_length_mm45.443363
4island17.258076
\n", - "

5 rows × 2 columns

\n", - "
[5 rows x 2 columns in total]" - ], - "text/plain": [ - " feature attribution\n", - "0 sex 221.587592\n", - "1 flipper_length_mm 71.311846\n", - "2 culmen_depth_mm 66.17986\n", - "3 culmen_length_mm 45.443363\n", - "4 island 17.258076\n", - "\n", - "[5 rows x 2 columns]" - ] - }, - "execution_count": 20, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "bbq.ml.global_explain(model_name)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Compatibility with pandas\n", - "\n", - "The functions in `bigframes.bigquery.ml` can accept pandas DataFrames as well. Use the `to_pandas()` method on the results of methods like `predict()` to get a pandas DataFrame back." - ] - }, - { - "cell_type": "code", - "execution_count": 21, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - " Query started with request ID bigframes-dev:US.18d9027b-7d55-42c9-ad1b-dabccdda80dc.
SQL
SELECT * FROM ML.PREDICT(MODEL `bigframes-dev.bqml_tutorial.penguin_weight_with_global_explain`, (SELECT\n",
-       "`column_0` AS `sex`,\n",
-       "`column_1` AS `flipper_length_mm`,\n",
-       "`column_2` AS `culmen_depth_mm`,\n",
-       "`column_3` AS `culmen_length_mm`,\n",
-       "`column_4` AS `island`\n",
-       "FROM\n",
-       "(SELECT\n",
-       "  *\n",
-       "FROM (\n",
-       "  SELECT\n",
-       "    *\n",
-       "  FROM UNNEST(ARRAY<STRUCT<`column_0` STRING, `column_1` INT64, `column_2` INT64, `column_3` INT64, `column_4` STRING>>[STRUCT('MALE', 180, 15, 40, 'Biscoe'), STRUCT('FEMALE', 190, 16, 41, 'Biscoe'), STRUCT('MALE', 200, 17, 42, 'Dream'), STRUCT('FEMALE', 210, 18, 43, 'Dream')]) AS `column_0`\n",
-       ") AS `t0`)))\n",
-       "
\n", - " " - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "✅ Completed. " - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "application/vnd.microsoft.datawrangler.viewer.v0+json": { - "columns": [ - { - "name": "index", - "rawType": "Int64", - "type": "integer" - }, - { - "name": "predicted_body_mass_g", - "rawType": "Float64", - "type": "float" - }, - { - "name": "sex", - "rawType": "string", - "type": "string" - }, - { - "name": "flipper_length_mm", - "rawType": "Int64", - "type": "integer" - }, - { - "name": "culmen_depth_mm", - "rawType": "Int64", - "type": "integer" - }, - { - "name": "culmen_length_mm", - "rawType": "Int64", - "type": "integer" - }, - { - "name": "island", - "rawType": "string", - "type": "string" - } - ], - "ref": "01d67015-64b6-463e-8c16-e8ac1363ff67", - "rows": [ - [ - "0", - "3596.332210728767", - "MALE", - "180", - "15", - "40", - "Biscoe" - ], - [ - "1", - "3384.6999176328636", - "FEMALE", - "190", - "16", - "41", - "Biscoe" - ], - [ - "2", - "4049.581795919061", - "MALE", - "200", - "17", - "42", - "Dream" - ], - [ - "3", - "3837.9495028231568", - "FEMALE", - "210", - "18", - "43", - "Dream" - ] - ], - "shape": { - "columns": 6, - "rows": 4 - } - }, - "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", - "
predicted_body_mass_gsexflipper_length_mmculmen_depth_mmculmen_length_mmisland
03596.332211MALE1801540Biscoe
13384.699918FEMALE1901641Biscoe
24049.581796MALE2001742Dream
33837.949503FEMALE2101843Dream
\n", - "
" - ], - "text/plain": [ - " predicted_body_mass_g sex flipper_length_mm culmen_depth_mm \\\n", - "0 3596.332211 MALE 180 15 \n", - "1 3384.699918 FEMALE 190 16 \n", - "2 4049.581796 MALE 200 17 \n", - "3 3837.949503 FEMALE 210 18 \n", - "\n", - " culmen_length_mm island \n", - "0 40 Biscoe \n", - "1 41 Biscoe \n", - "2 42 Dream \n", - "3 43 Dream " - ] - }, - "execution_count": 21, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "import pandas as pd\n", - "\n", - "predict_df = pd.DataFrame({\n", - " \"sex\": [\"MALE\", \"FEMALE\", \"MALE\", \"FEMALE\"],\n", - " \"flipper_length_mm\": [180, 190, 200, 210],\n", - " \"culmen_depth_mm\": [15, 16, 17, 18],\n", - " \"culmen_length_mm\": [40, 41, 42, 43],\n", - " \"island\": [\"Biscoe\", \"Biscoe\", \"Dream\", \"Dream\"],\n", - "})\n", - "bbq.ml.predict(model_metadata, predict_df).to_pandas()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Compatibility with `bigframes.ml`\n", - "\n", - "The models created with `bigframes.bigquery.ml` can be used with the scikit-learn-like `bigframes.ml` modules by using the `read_gbq_model` method.\n" - ] - }, - { - "cell_type": "code", - "execution_count": 22, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "LinearRegression(enable_global_explain=True,\n", - " optimize_strategy='NORMAL_EQUATION')" - ] - }, - "execution_count": 22, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "model = bpd.read_gbq_model(model_name)\n", - "model" - ] - }, - { - "cell_type": "code", - "execution_count": 23, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "✅ Completed. \n", - " Query processed 7.3 kB in a moment of slot time. [Job bigframes-dev:US.f2f86927-bbd1-431d-b89e-3d6a064268d7 details]\n", - " " - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "✅ Completed. " - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "✅ Completed. " - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "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", - "
mean_absolute_errormean_squared_errormean_squared_log_errormedian_absolute_errorr2_scoreexplained_variance
0223.87876378553.6016340.005614181.3309110.6239510.623951
\n", - "

1 rows × 6 columns

\n", - "
[1 rows x 6 columns in total]" - ], - "text/plain": [ - " mean_absolute_error mean_squared_error mean_squared_log_error \\\n", - " 223.878763 78553.601634 0.005614 \n", - "\n", - " median_absolute_error r2_score explained_variance \n", - " 181.330911 0.623951 0.623951 \n", - "\n", - "[1 rows x 6 columns]" - ] - }, - "execution_count": 23, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "X = training_data[[\"sex\", \"flipper_length_mm\", \"culmen_depth_mm\", \"culmen_length_mm\", \"island\"]]\n", - "y = training_data[[\"body_mass_g\"]]\n", - "model.score(X, y)" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "G_wjSfXpWTuy" - }, - "source": [ - "# Summary and next steps\n", - "\n", - "You've created a linear regression model using `bigframes.bigquery.ml`.\n", - "\n", - "Learn more about BigQuery DataFrames in the [documentation](https://dataframes.bigquery.dev/) and find more sample notebooks in the [GitHub repo](https://github.com/googleapis/python-bigquery-dataframes/tree/main/notebooks)." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "TpV-iwP9qw9c" - }, - "source": [ - "## Cleaning up\n", - "\n", - "To clean up all Google Cloud resources used in this project, you can [delete the Google Cloud\n", - "project](https://cloud.google.com/resource-manager/docs/creating-managing-projects#shutting_down_projects) you used for the tutorial.\n", - "\n", - "Otherwise, you can uncomment the remaining cells and run them to delete the individual resources you created in this tutorial:" - ] - }, - { - "cell_type": "code", - "execution_count": 24, - "metadata": { - "id": "sx_vKniMq9ZX" - }, - "outputs": [], - "source": [ - "# # Delete the BigQuery dataset and associated ML model\n", - "# from google.cloud import bigquery\n", - "# client = bigquery.Client(project=PROJECT_ID)\n", - "# client.delete_dataset(\n", - "# DATASET_ID, delete_contents=True, not_found_ok=True\n", - "# )\n", - "# print(\"Deleted dataset '{}'.\".format(DATASET_ID))" - ] - } - ], - "metadata": { - "colab": { - "provenance": [], - "toc_visible": true - }, - "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.12.9" - } - }, - "nbformat": 4, - "nbformat_minor": 0 -} diff --git a/notebooks/ml/bq_dataframes_ml_linear_regression_big.ipynb b/notebooks/ml/bq_dataframes_ml_linear_regression_big.ipynb deleted file mode 100644 index d286f5ce31d..00000000000 --- a/notebooks/ml/bq_dataframes_ml_linear_regression_big.ipynb +++ /dev/null @@ -1,1064 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "ur8xi4C7S06n" - }, - "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": { - "id": "JAPoU8Sm5E6e" - }, - "source": [ - "# Train a linear regression model with BigQuery DataFrames ML", - "\n", - "\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", - " \"Vertex\n", - " Open in Vertex AI Workbench\n", - " \n", - " \n", - " \n", - " \"BQ\n", - " Open in BQ Studio\n", - " \n", - "
" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "24743cf4a1e1" - }, - "source": [ - "**_NOTE_**: This notebook has been tested in the following environment:\n", - "\n", - "* Python version = 3.11" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "tvgnzT1CKxrO" - }, - "source": [ - "## Overview\n", - "\n", - "This notebook demonstrates training a linear regression model on Big Data using BigQuery DataFrames ML. BigQuery DataFrames ML provides a provides a scikit-learn-like API for ML powered by the BigQuery engine.\n", - "\n", - "Learn more about [BigQuery DataFrames](https://cloud.google.com/python/docs/reference/bigframes/latest)." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "d975e698c9a4" - }, - "source": [ - "### Objective\n", - "\n", - "In this tutorial, we use BigQuery DataFrames to create a linear regression model that predicts the levels of Ozone in the atmosphere.\n", - "\n", - "The steps include:\n", - "\n", - "- Creating a DataFrame from the BigQuery table.\n", - "- Cleaning and preparing data using `bigframes.pandas` module.\n", - "- Creating a linear regression model using `bigframes.ml` module.\n", - "- Saving the ML model to BigQuery for future use.\n", - "\n", - "\n", - "Let's formally define our problem as: **Train a linear regression model to predict the level of ozone in the atmosphere given the measurements of other constituents and properties of the atmosphere.**" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "08d289fa873f" - }, - "source": [ - "### Dataset\n", - "\n", - "In this tutorial we are going to use the [`bigquery-public-data.epa_historical_air_quality`](https://console.cloud.google.com/marketplace/product/epa/historical-air-quality) dataset. To quote the description of the dataset:\n", - "\n", - "\"The United States Environmental Protection Agency (EPA) protects both public health and the environment by establishing the standards for national air quality. The EPA provides annual summary data as well as hourly and daily data in the categories of criteria gases, particulates, meteorological, and toxics.\"\n", - "\n", - "There are several tables capturing data about the constituents of the atmosphere, see them in the [BigQuery cloud console](https://pantheon.corp.google.com/bigquery?p=bigquery-public-data&d=epa_historical_air_quality&page=dataset). Most tables carry 10's of GBs of data, but that is not an issue with BigQuery DataFrames as the data is efficiently processed at BigQuery without transferring them to the client." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "aed92deeb4a0" - }, - "source": [ - "### Costs\n", - "\n", - "This tutorial uses billable components of Google Cloud:\n", - "\n", - "* BigQuery (compute)\n", - "* BigQuery ML\n", - "\n", - "Learn about [BigQuery compute pricing](https://cloud.google.com/bigquery/pricing#analysis_pricing_models)\n", - "and [BigQuery ML pricing](https://cloud.google.com/bigquery/pricing#bqml),\n", - "and use the [Pricing Calculator](https://cloud.google.com/products/calculator/)\n", - "to generate a cost estimate based on your projected usage." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "i7EUnXsZhAGF" - }, - "source": [ - "## Installation\n", - "\n", - "If you don't have [bigframes](https://pypi.org/project/bigframes/) package already installed, uncomment and execute the following cells to\n", - "\n", - "1. Install the package\n", - "1. Restart the notebook kernel (Jupyter or Colab) to work with the package" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "9O0Ka4W2MNF3" - }, - "outputs": [], - "source": [ - "# !pip install bigframes" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "f200f10a1da3" - }, - "outputs": [], - "source": [ - "# Automatically restart kernel after installs so that your environment can access the new packages\n", - "\n", - "# import IPython\n", - "#\n", - "# app = IPython.Application.instance()\n", - "# app.kernel.do_shutdown(True)" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "BF1j6f9HApxa" - }, - "source": [ - "## Before you begin\n", - "\n", - "Complete the tasks in this section to set up your environment." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "oDfTjfACBvJk" - }, - "source": [ - "### Set up your Google Cloud project\n", - "\n", - "**The following steps are required, regardless of your notebook environment.**\n", - "\n", - "1. [Select or create a Google Cloud project](https://console.cloud.google.com/cloud-resource-manager). When you first create an account, you get a $300 credit towards your compute/storage costs.\n", - "\n", - "2. [Make sure that billing is enabled for your project](https://cloud.google.com/billing/docs/how-to/modify-project).\n", - "\n", - "3. [Enable the BigQuery API](https://console.cloud.google.com/flows/enableapi?apiid=bigquery.googleapis.com).\n", - "\n", - "4. If you are running this notebook locally, install the [Cloud SDK](https://cloud.google.com/sdk)." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "WReHDGG5g0XY" - }, - "source": [ - "#### Set your project ID\n", - "\n", - "If you don't know your project ID, try the following:\n", - "* Run `gcloud config list`.\n", - "* Run `gcloud projects list`.\n", - "* See the support page: [Locate the project ID](https://support.google.com/googleapi/answer/7014113)." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "oM1iC_MfAts1" - }, - "outputs": [], - "source": [ - "PROJECT_ID = \"\" # @param {type:\"string\"}" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "region" - }, - "source": [ - "#### Set the BigQuery location\n", - "\n", - "You can also change the `LOCATION` variable used by BigQuery. Learn more about [BigQuery locations](https://cloud.google.com/bigquery/docs/locations#supported_locations)." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "eF-Twtc4XGem" - }, - "outputs": [], - "source": [ - "LOCATION = \"US\" # @param {type: \"string\"}" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "sBCra4QMA2wR" - }, - "source": [ - "### Set up APIs, IAM permissions and Authentication\n", - "\n", - "Follow the instructions at https://cloud.google.com/bigquery/docs/use-bigquery-dataframes#permissions.\n", - "\n", - "Depending on your notebook environment, you might have to manually authenticate. Follow the relevant instructions below." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "74ccc9e52986" - }, - "source": [ - "**Vertex AI Workbench**\n", - "\n", - "Do nothing, you are already authenticated." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "de775a3773ba" - }, - "source": [ - "**Local JupyterLab instance**\n", - "\n", - "Uncomment and run the following cell:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "254614fa0c46" - }, - "outputs": [], - "source": [ - "# ! gcloud auth login\n", - "# ! gcloud auth application-default login" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "ef21552ccea8" - }, - "source": [ - "**Colab**\n", - "\n", - "Uncomment and run the following cell:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "603adbbf0532" - }, - "outputs": [], - "source": [ - "# from google.colab import auth\n", - "# auth.authenticate_user()" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "960505627ddf" - }, - "source": [ - "### Import libraries" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "PyQmSRbKA8r-" - }, - "outputs": [], - "source": [ - "import bigframes.pandas as bpd" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "init_aip:mbsdk,all" - }, - "source": [ - "### Set BigQuery DataFrames options" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "NPPMuw2PXGeo" - }, - "outputs": [], - "source": [ - "# NOTE: The project option is not required in all environments.\n", - "# On BigQuery Studio, the project ID is automatically detected.\n", - "bpd.options.bigquery.project = PROJECT_ID\n", - "\n", - "# NOTE: The location option is not required.\n", - "# It defaults to the location of the first table or query\n", - "# passed to read_gbq(). For APIs where a location can't be\n", - "# auto-detected, the location defaults to the \"US\" location.\n", - "bpd.options.bigquery.location = LOCATION\n", - "\n", - "# NOTE: For a machine learning model the order of the data is\n", - "# not important. So let's relax the ordering_mode to accept\n", - "# partial ordering. This allows BigQuery DataFrames to run cost\n", - "# and performance optimized jobs at the BigQuery engine.\n", - "bpd.options.bigquery.ordering_mode = \"partial\"" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "D21CoOlfFTYI" - }, - "source": [ - "If you want to reset the location of the created DataFrame or Series objects, reset the session by executing `bpd.close_session()`. After that, you can reuse `bpd.options.bigquery.location` to specify another location." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "9EMAqR37AfLS" - }, - "source": [ - "## Read data in BigQuery tables as DataFrame\n", - "\n", - "Let's read the tables in the dataset to construct a BigQuery DataFrames DataFrame. We will combine measurements of various parameters of the atmosphere from multiple tables to represent a consolidated dataframe to use for our model training and prediction. We have daily and hourly versions of the data available, but since we want to create a model that is dynamic so that it can capture the variance throughout the day, we would choose the hourly version.\n", - "\n", - "Note that we would use the pandas APIs as we normally would on the BigQuery DataFrames DataFrame, but calculations happen in the BigQuery query engine instead of the local environment." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "dataset = \"bigquery-public-data.epa_historical_air_quality\"\n", - "hourly_summary_tables = [\n", - " \"co_hourly_summary\",\n", - " \"hap_hourly_summary\",\n", - " \"no2_hourly_summary\",\n", - " \"nonoxnoy_hourly_summary\",\n", - " \"o3_hourly_summary\",\n", - " \"pm10_hourly_summary\",\n", - " \"pm25_frm_hourly_summary\",\n", - " \"pm25_nonfrm_hourly_summary\",\n", - " \"pm25_speciation_hourly_summary\",\n", - " \"pressure_hourly_summary\",\n", - " \"rh_and_dp_hourly_summary\",\n", - " \"so2_hourly_summary\",\n", - " \"temperature_hourly_summary\",\n", - " \"voc_hourly_summary\",\n", - " \"wind_hourly_summary\",\n", - "]" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Let's pick index columns - to identify a measurement of the atmospheric parameter, param column - to identify which param the measurement pertains to, and value column - the column containing the measurement itself." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "index_columns = [\"state_name\", \"county_name\", \"site_num\", \"date_local\", \"time_local\"]\n", - "param_column = \"parameter_name\"\n", - "value_column = \"sample_measurement\"" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Let's observe how much data each table contains:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "for table in hourly_summary_tables:\n", - " # get the bigframes global session\n", - " bigframes_session = bpd.get_global_session()\n", - "\n", - " # get the bigquery table info\n", - " table_info = bigframes_session.bqclient.get_table(f\"{dataset}.{table}\")\n", - "\n", - " # read the table as a dataframe\n", - " df = bpd.read_gbq(f\"{dataset}.{table}\")\n", - "\n", - " # print metadata about the table\n", - " print(\n", - " f\"{table}: \"\n", - " f\"{round(table_info.num_bytes/1_000_000_000, 1)} GB, \"\n", - " f\"{round(table_info.num_rows/1_000_000, 1)} million rows, \"\n", - " f\"{df[param_column].nunique()} params\"\n", - " )" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Let's be mindful that the rows in each table may contain duplicates, which may introdude bias in any model trained on the raw data. We will make sure to drop the duplicates when we use the data for model training.\n", - "\n", - "Since we want to predict ozone level, we obviously pick the `o3` table. Let's also pick the tables about other gases - `co`, `no2` and `so2`. Let's also pick `pressure` and `temperature` tables as they seem fundamental indicators for the atmosphere. Note that each of these tables capture measurements for a single parameter (i.e. the column `parameter_name` has a single unique value).\n", - "\n", - "We are also interested in the nonoxny and wind tables, but they capture multiple parameters (i.e. the column `parameter_name` has a more than one unique values). We will include their measurements in later step, as they require extar processing to separate out the measurements for the individual parameters.\n", - "\n", - "We skip the other tables in this exercise for either they have very little or fragmented data or they seem uninteresting for the purpose of predicting ozone levels. You can take this as a separate exercise to train a linear regression model by including those parameters. " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Let's maintain an array of dtaframes, one for each parameter, and eventually combine them into a single dataframe." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "params_dfs = []" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Let's process the tables with single parameter measurements first." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "EDAaIwHpQCDZ" - }, - "outputs": [], - "source": [ - "table_param_dict = {\n", - " \"co_hourly_summary\" : \"co\",\n", - " \"no2_hourly_summary\" : \"no2\",\n", - " \"o3_hourly_summary\" : \"o3\",\n", - " \"pressure_hourly_summary\" : \"pressure\",\n", - " \"so2_hourly_summary\" : \"so2\",\n", - " \"temperature_hourly_summary\" : \"temperature\",\n", - "}\n", - "\n", - "for table, param in table_param_dict.items():\n", - " param_df = bpd.read_gbq(\n", - " f\"{dataset}.{table}\",\n", - " columns=index_columns + [value_column]\n", - " )\n", - " param_df = param_df\\\n", - " .sort_values(index_columns)\\\n", - " .drop_duplicates(index_columns)\\\n", - " .set_index(index_columns)\\\n", - " .rename(columns={value_column : param})\n", - " params_dfs.append(param_df)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The nonoxnoy table captures measurements for 3 parameters. Let's analyze how many instances of each parameter it contains." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "nonoxnoy_table = f\"{dataset}.nonoxnoy_hourly_summary\"" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "bpd.read_gbq(nonoxnoy_table, columns=[param_column]).value_counts()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We see that the NOy data is significantly sparse as compared to NO and NOx, so we skip that and include NO and NOx data." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "no_df = bpd.read_gbq(\n", - " nonoxnoy_table,\n", - " columns=index_columns + [value_column],\n", - " filters=[(param_column, \"==\", \"Nitric oxide (NO)\")]\n", - ")\n", - "no_df = no_df\\\n", - " .sort_values(index_columns)\\\n", - " .drop_duplicates(index_columns)\\\n", - " .set_index(index_columns)\\\n", - " .rename(columns={value_column: \"no_\"})\n", - "params_dfs.append(no_df)\n", - "\n", - "nox_df = bpd.read_gbq(\n", - " nonoxnoy_table,\n", - " columns=index_columns + [value_column],\n", - " filters=[(param_column, \"==\", \"Oxides of nitrogen (NOx)\")]\n", - ")\n", - "nox_df = nox_df\\\n", - " .sort_values(index_columns)\\\n", - " .drop_duplicates(index_columns)\\\n", - " .set_index(index_columns)\\\n", - " .rename(columns={value_column: \"nox\"})\n", - "params_dfs.append(nox_df)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The wind table captures measurements for 2 parameters. Let's analyze how many instances of each parameter it contains." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "wind_table = f\"{dataset}.wind_hourly_summary\"" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "bpd.read_gbq(wind_table, columns=[param_column]).value_counts()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Let's include the data for wind speed and wind direction." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "wind_speed_df = bpd.read_gbq(\n", - " wind_table,\n", - " columns=index_columns + [value_column],\n", - " filters=[(param_column, \"==\", \"Wind Speed - Resultant\")]\n", - ")\n", - "wind_speed_df = wind_speed_df\\\n", - " .sort_values(index_columns)\\\n", - " .drop_duplicates(index_columns)\\\n", - " .set_index(index_columns)\\\n", - " .rename(columns={value_column: \"wind_speed\"})\n", - "params_dfs.append(wind_speed_df)\n", - "\n", - "wind_dir_df = bpd.read_gbq(\n", - " wind_table,\n", - " columns=index_columns + [value_column],\n", - " filters=[(param_column, \"==\", \"Wind Direction - Resultant\")]\n", - ")\n", - "wind_dir_df = wind_dir_df\\\n", - " .sort_values(index_columns)\\\n", - " .drop_duplicates(index_columns)\\\n", - " .set_index(index_columns)\\\n", - " .rename(columns={value_column: \"wind_dir\"})\n", - "params_dfs.append(wind_dir_df)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Let's observe each individual parameter and number of data points for each parameter." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "for param_df in params_dfs:\n", - " print(f\"{param_df.columns.values}: {len(param_df)}\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Let's combine data from all parameters into a single DataFrame. The measurements for each parameter may not be available for every (state, county, site, date, time) identifier, we will consider only those identifiers for which measurements of all parameters are available. To achieve this we will combine the measurements via \"inner\" join.\n", - "\n", - "We will also materialize this combined data via `cache` method for efficient reuse in the subsequent steps." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "df = bpd.concat(params_dfs, axis=1, join=\"inner\").cache()\n", - "df.shape" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "rwPLjqW2Ajzh" - }, - "source": [ - "## Clean and prepare data" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Let's temporarily bring the index columns as dataframe columns for further processing on the index values for the purpose of data preparation.\n", - "We will reconstruct the index back at the time of the model training." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "df = df.reset_index()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Observe the years from which we have consolidated data so far." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "df[\"date_local\"].dt.year.value_counts().sort_index().to_pandas()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "In this tutorial we would train a model from the past data to predict ozone levels for the future data. Let's define the cut-off year as 2020. We will pretend that the data before 2020 has known ozone levels, and the 2020 onwards the ozone levels are unknown, which we will predict using our model.\n", - "\n", - "We should further separate the known data into training and test sets. The model would be trained on the training set and then evaluated on the test set to make sure the model generalizes beyond the training data. We could use [train_test_split](https://cloud.google.com/python/docs/reference/bigframes/latest/bigframes.ml.model_selection#bigframes_ml_model_selection_train_test_split) method to randomly split the training and test data, but we leave that for you to try out. In this exercise, let's split based on another cutoff year 2017 - the known data before 2017 would be training data and 2017 onwards would be the test data. This way we stay with the idea that the model is trained on past data and then used to predict the future values." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "6i6HkFJZa8na" - }, - "outputs": [], - "source": [ - "train_data_filter = (df.date_local.dt.year < 2017)\n", - "test_data_filter = (df.date_local.dt.year >= 2017) & (df.date_local.dt.year < 2020)\n", - "predict_data_filter = (df.date_local.dt.year >= 2020)\n", - "\n", - "df_train = df[train_data_filter].set_index(index_columns)\n", - "df_test = df[test_data_filter].set_index(index_columns)\n", - "df_predict = df[predict_data_filter].set_index(index_columns)\n", - "\n", - "df_train.shape, df_test.shape, df_predict.shape" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "M_-0X7NxYK5f" - }, - "source": [ - "Prepare your feature (or input) columns and the target (or output) column for the purpose of model training and evaluation:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "YKwCW7Nsavap" - }, - "outputs": [], - "source": [ - "X_train = df_train.drop(columns=\"o3\")\n", - "y_train = df_train[\"o3\"]\n", - "\n", - "X_test = df_test.drop(columns=\"o3\")\n", - "y_test = df_test[\"o3\"]" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Prepare the unknown data for prediction." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "wej78IDUaRW9" - }, - "outputs": [], - "source": [ - "X_predict = df_predict.drop(columns=\"o3\")" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "Fx4lsNqMorJ-" - }, - "source": [ - "## Create the linear regression model\n", - "\n", - "BigQuery DataFrames ML lets you seamlessly transition from exploring data to creating machine learning models through its scikit-learn-like API, `bigframes.ml`. BigQuery DataFrames ML supports several types of [ML models](https://cloud.google.com/python/docs/reference/bigframes/latest#ml-capabilities).\n", - "\n", - "In this notebook, you create a [`LinearRegression`](https://cloud.google.com/python/docs/reference/bigframes/latest/bigframes.ml.linear_model.LinearRegression) model, a type of regression model that generates a continuous value from a linear combination of input features.\n", - "\n", - "When you create a model with BigQuery DataFrames ML, it is saved in an internal location and limited to the BigQuery DataFrames session. However, as you'll see in the next section, you can use `to_gbq` to save the model permanently to your BigQuery project." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "EloGtMnverFF" - }, - "source": [ - "### Create the model using `bigframes.ml`\n", - "\n", - "Please note that BigQuery DataFrames ML is backed by BigQuery ML, which uses\n", - "[automatic preprocessing](https://cloud.google.com/bigquery/docs/auto-preprocessing) to encode string values and scale numeric values when you pass the feature columns without transforms.\n", - "\n", - "BigQuery ML also [automatically splits the data for training and evaluation](https://cloud.google.com/bigquery/docs/reference/standard-sql/bigqueryml-syntax-create-glm#data_split_method), although for datasets with less than 500 rows (such as this one), all rows are used for training." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "GskyyUQPowBT" - }, - "outputs": [], - "source": [ - "from bigframes.ml.linear_model import LinearRegression\n", - "\n", - "model = LinearRegression()\n", - "\n", - "model.fit(X_train, y_train)" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "UGjeMPC2caKK" - }, - "source": [ - "### Score the model\n", - "\n", - "Check how the model performs by using the [`score`](https://cloud.google.com/python/docs/reference/bigframes/latest/bigframes.ml.linear_model.LinearRegression#bigframes_ml_linear_model_LinearRegression_score) method. More information on BigQuery ML model scoring can be found [here](https://cloud.google.com/bigquery/docs/reference/standard-sql/bigqueryml-syntax-evaluate#mlevaluate_output)." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "kGBJKafpo0dl" - }, - "outputs": [], - "source": [ - "# On the training data\n", - "model.score(X_train, y_train)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# On the test data\n", - "model.score(X_test, y_test)" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "P2lUiZZ_cjri" - }, - "source": [ - "### Predict using the model\n", - "\n", - "Use the model to predict the levels of ozone. The predicted levels are returned in the column `predicted_o3`." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "bsQ9cmoWo0Ps" - }, - "outputs": [], - "source": [ - "df_pred = model.predict(X_predict)\n", - "df_pred.peek()" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "GTRdUw-Ro5R1" - }, - "source": [ - "## Save the model in BigQuery\n", - "\n", - "The model is saved locally within this session. You can save the model permanently to BigQuery for use in future sessions, and to make the model sharable with others." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "K0mPaoGpcwwy" - }, - "source": [ - "Create a BigQuery dataset to house the model, adding a name for your dataset as the `DATASET_ID` variable:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "ZSP7gt13QrQt" - }, - "outputs": [], - "source": [ - "DATASET_ID = \"\" # @param {type:\"string\"}\n", - "\n", - "if not DATASET_ID:\n", - " raise ValueError(\"Please define the DATASET_ID\")\n", - "\n", - "client = bpd.get_global_session().bqclient\n", - "dataset = client.create_dataset(DATASET_ID, exists_ok=True)\n", - "print(f\"Dataset {dataset.dataset_id} created.\")" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "zqAIWWgJczp-" - }, - "source": [ - "Save the model using the `to_gbq` method:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "QE_GD4Byo_jb" - }, - "outputs": [], - "source": [ - "model.to_gbq(DATASET_ID + \".o3_lr_model\" , replace=True)" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "f7uHacAy49rT" - }, - "source": [ - "You can view the saved model in the BigQuery console under the dataset you created in the first step. Run the following cell and follow the link to view your BigQuery console:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "qDBoiA_0488Z" - }, - "outputs": [], - "source": [ - "print(f'https://console.cloud.google.com/bigquery?ws=!1m5!1m4!5m3!1s{PROJECT_ID}!2s{DATASET_ID}!3so3_lr_model')" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "G_wjSfXpWTuy" - }, - "source": [ - "# Summary and next steps\n", - "\n", - "You've created a linear regression model using `bigframes.ml`.\n", - "\n", - "Learn more about BigQuery DataFrames in the [documentation](https://cloud.google.com/python/docs/reference/bigframes/latest) and find more sample notebooks in the [GitHub repo](https://github.com/googleapis/python-bigquery-dataframes/tree/main/notebooks)." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "TpV-iwP9qw9c" - }, - "source": [ - "## Cleaning up\n", - "\n", - "To clean up all Google Cloud resources used in this project, you can [delete the Google Cloud\n", - "project](https://cloud.google.com/resource-manager/docs/creating-managing-projects#shutting_down_projects) you used for the tutorial.\n", - "\n", - "Otherwise, you can uncomment the remaining cells and run them to delete the individual resources you created in this tutorial:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "sx_vKniMq9ZX" - }, - "outputs": [], - "source": [ - "# # Delete the BigQuery dataset and associated ML model\n", - "# client.delete_dataset(DATASET_ID, delete_contents=True, not_found_ok=True)" - ] - } - ], - "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.12.0" - } - }, - "nbformat": 4, - "nbformat_minor": 0 -} diff --git a/notebooks/ml/easy_linear_regression.ipynb b/notebooks/ml/easy_linear_regression.ipynb deleted file mode 100644 index 5a7258a182e..00000000000 --- a/notebooks/ml/easy_linear_regression.ipynb +++ /dev/null @@ -1,247 +0,0 @@ -{ - "cells": [ - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Using ML - Easy linear regression\n", - "\n", - "This demo shows BigQuery DataFrames ML providing an SKLearn-like experience for\n", - "training a linear regression model.\n", - "\n", - "In this \"easy\" version of linear regression, we use a couple of BQML features to simplify our code:\n", - "\n", - "- We rely on automatic preprocessing to encode string values and scale numeric values\n", - "- We rely on automatic data split & evaluation to test the model\n", - "\n", - "This example is adapted from the [BQML linear regression tutorial](https://cloud.google.com/bigquery-ml/docs/linear-regression-tutorial)." - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## 1. Init & load data" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Import `bigframes.pandas` module and get the default session" - ] - }, - { - "cell_type": "code", - "execution_count": 22, - "metadata": {}, - "outputs": [], - "source": [ - "import bigframes.pandas\n", - "session = bigframes.pandas.get_global_session()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Define a dataset for storing BQML model, and create it if it does not exist." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "dataset = f\"{session.bqclient.project}.bqml_tutorial\"\n", - "session.bqclient.create_dataset(dataset, exists_ok=True)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Define a model path" - ] - }, - { - "cell_type": "code", - "execution_count": 24, - "metadata": {}, - "outputs": [], - "source": [ - "penguins_model = f\"{dataset}.penguins_model\"" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Read the penguins data." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# read a BigQuery table to a BigQuery DataFrame\n", - "df = bigframes.pandas.read_gbq(f\"bigquery-public-data.ml_datasets.penguins\")\n", - "\n", - "# take a peek at the dataframe\n", - "df" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## 2. Data cleaning / prep" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# filter down to the data we want to analyze\n", - "adelie_data = df[df.species == \"Adelie Penguin (Pygoscelis adeliae)\"]\n", - "\n", - "# drop the columns we don't care about\n", - "adelie_data = adelie_data.drop(columns=[\"species\"])\n", - "\n", - "# drop rows with nulls to get our training data\n", - "training_data = adelie_data.dropna()\n", - "\n", - "# take a peek at the training data\n", - "training_data" - ] - }, - { - "cell_type": "code", - "execution_count": 27, - "metadata": {}, - "outputs": [], - "source": [ - "# pick feature columns and label column\n", - "feature_columns = training_data[['island', 'culmen_length_mm', 'culmen_depth_mm', 'flipper_length_mm', 'sex']]\n", - "label_columns = training_data[['body_mass_g']] \n", - "\n", - "# also get the rows that we want to make predictions for (i.e. where the feature column is null)\n", - "missing_body_mass = adelie_data[adelie_data.body_mass_g.isnull()]" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## 3. Create, score, fit, predict" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from bigframes.ml.linear_model import LinearRegression\n", - "\n", - "model = LinearRegression()\n", - "\n", - "# Here we pass the feature columns without transforms - BQML will then use\n", - "# automatic preprocessing to encode these columns\n", - "model.fit(feature_columns, label_columns)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# check how the model performed\n", - "model.score(feature_columns, label_columns)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# use the model to predict the missing labels\n", - "model.predict(missing_body_mass)" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## 4. Save in BigQuery" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# save the model to a permanent location in BigQuery, so we can use it in future sessions (and elsewhere in BQ)\n", - "model.to_gbq(penguins_model, replace=True)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## 5. Reload from BigQuery" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# WARNING - until b/281709360 is fixed & pipeline is updated, pipelines will load as models,\n", - "# and details of their transform steps will be lost (the loaded model will behave the same)\n", - "bigframes.pandas.read_gbq_model(penguins_model)" - ] - } - ], - "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.12" - }, - "orig_nbformat": 4, - "vscode": { - "interpreter": { - "hash": "a850322d07d9bdc9ec5f301d307e048bcab2390ae395e1cbce9335f4e081e5e2" - } - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/notebooks/ml/sklearn_linear_regression.ipynb b/notebooks/ml/sklearn_linear_regression.ipynb deleted file mode 100644 index 95aa314bb09..00000000000 --- a/notebooks/ml/sklearn_linear_regression.ipynb +++ /dev/null @@ -1,1343 +0,0 @@ -{ - "cells": [ - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Using ML - SKLearn linear regression\n", - "\n", - "This demo shows how we can implement a linear regression in BigQuery DataFrames ML, with API that is exactly compatible with scikit-learn." - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## 1. Init & load data" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "Query job f201b84b-5506-4038-92e6-b4a82318df8f is DONE. 0 Bytes processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job 12e0f983-695e-4903-8ff1-2f353d7e8cba is DONE. 28.9 kB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "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", - "
speciesislandculmen_length_mmculmen_depth_mmflipper_length_mmbody_mass_gsex
0Adelie Penguin (Pygoscelis adeliae)Biscoe40.118.9188.04300.0MALE
1Adelie Penguin (Pygoscelis adeliae)Torgersen39.118.7181.03750.0MALE
2Gentoo penguin (Pygoscelis papua)Biscoe47.414.6212.04725.0FEMALE
3Chinstrap penguin (Pygoscelis antarctica)Dream42.516.7187.03350.0FEMALE
4Adelie Penguin (Pygoscelis adeliae)Biscoe43.219.0197.04775.0MALE
5Gentoo penguin (Pygoscelis papua)Biscoe46.715.3219.05200.0MALE
6Adelie Penguin (Pygoscelis adeliae)Biscoe41.321.1195.04400.0MALE
7Gentoo penguin (Pygoscelis papua)Biscoe45.213.8215.04750.0FEMALE
8Gentoo penguin (Pygoscelis papua)Biscoe46.513.5210.04550.0FEMALE
9Gentoo penguin (Pygoscelis papua)Biscoe50.515.2216.05000.0FEMALE
10Gentoo penguin (Pygoscelis papua)Biscoe48.215.6221.05100.0MALE
11Adelie Penguin (Pygoscelis adeliae)Dream38.118.6190.03700.0FEMALE
12Gentoo penguin (Pygoscelis papua)Biscoe50.715.0223.05550.0MALE
13Adelie Penguin (Pygoscelis adeliae)Biscoe37.820.0190.04250.0MALE
14Adelie Penguin (Pygoscelis adeliae)Biscoe35.017.9190.03450.0FEMALE
15Gentoo penguin (Pygoscelis papua)Biscoe48.715.7208.05350.0MALE
16Adelie Penguin (Pygoscelis adeliae)Torgersen34.621.1198.04400.0MALE
17Gentoo penguin (Pygoscelis papua)Biscoe46.815.4215.05150.0MALE
18Chinstrap penguin (Pygoscelis antarctica)Dream50.320.0197.03300.0MALE
19Adelie Penguin (Pygoscelis adeliae)Dream37.218.1178.03900.0MALE
20Chinstrap penguin (Pygoscelis antarctica)Dream51.018.8203.04100.0MALE
21Adelie Penguin (Pygoscelis adeliae)Biscoe40.517.9187.03200.0FEMALE
22Gentoo penguin (Pygoscelis papua)Biscoe45.513.9210.04200.0FEMALE
23Adelie Penguin (Pygoscelis adeliae)Dream42.218.5180.03550.0FEMALE
24Chinstrap penguin (Pygoscelis antarctica)Dream51.720.3194.03775.0MALE
\n", - "

25 rows × 7 columns

\n", - "
[344 rows x 7 columns in total]" - ], - "text/plain": [ - " species island culmen_length_mm \\\n", - "0 Adelie Penguin (Pygoscelis adeliae) Biscoe 40.1 \n", - "1 Adelie Penguin (Pygoscelis adeliae) Torgersen 39.1 \n", - "2 Gentoo penguin (Pygoscelis papua) Biscoe 47.4 \n", - "3 Chinstrap penguin (Pygoscelis antarctica) Dream 42.5 \n", - "4 Adelie Penguin (Pygoscelis adeliae) Biscoe 43.2 \n", - "5 Gentoo penguin (Pygoscelis papua) Biscoe 46.7 \n", - "6 Adelie Penguin (Pygoscelis adeliae) Biscoe 41.3 \n", - "7 Gentoo penguin (Pygoscelis papua) Biscoe 45.2 \n", - "8 Gentoo penguin (Pygoscelis papua) Biscoe 46.5 \n", - "9 Gentoo penguin (Pygoscelis papua) Biscoe 50.5 \n", - "10 Gentoo penguin (Pygoscelis papua) Biscoe 48.2 \n", - "11 Adelie Penguin (Pygoscelis adeliae) Dream 38.1 \n", - "12 Gentoo penguin (Pygoscelis papua) Biscoe 50.7 \n", - "13 Adelie Penguin (Pygoscelis adeliae) Biscoe 37.8 \n", - "14 Adelie Penguin (Pygoscelis adeliae) Biscoe 35.0 \n", - "15 Gentoo penguin (Pygoscelis papua) Biscoe 48.7 \n", - "16 Adelie Penguin (Pygoscelis adeliae) Torgersen 34.6 \n", - "17 Gentoo penguin (Pygoscelis papua) Biscoe 46.8 \n", - "18 Chinstrap penguin (Pygoscelis antarctica) Dream 50.3 \n", - "19 Adelie Penguin (Pygoscelis adeliae) Dream 37.2 \n", - "20 Chinstrap penguin (Pygoscelis antarctica) Dream 51.0 \n", - "21 Adelie Penguin (Pygoscelis adeliae) Biscoe 40.5 \n", - "22 Gentoo penguin (Pygoscelis papua) Biscoe 45.5 \n", - "23 Adelie Penguin (Pygoscelis adeliae) Dream 42.2 \n", - "24 Chinstrap penguin (Pygoscelis antarctica) Dream 51.7 \n", - "\n", - " culmen_depth_mm flipper_length_mm body_mass_g sex \n", - "0 18.9 188.0 4300.0 MALE \n", - "1 18.7 181.0 3750.0 MALE \n", - "2 14.6 212.0 4725.0 FEMALE \n", - "3 16.7 187.0 3350.0 FEMALE \n", - "4 19.0 197.0 4775.0 MALE \n", - "5 15.3 219.0 5200.0 MALE \n", - "6 21.1 195.0 4400.0 MALE \n", - "7 13.8 215.0 4750.0 FEMALE \n", - "8 13.5 210.0 4550.0 FEMALE \n", - "9 15.2 216.0 5000.0 FEMALE \n", - "10 15.6 221.0 5100.0 MALE \n", - "11 18.6 190.0 3700.0 FEMALE \n", - "12 15.0 223.0 5550.0 MALE \n", - "13 20.0 190.0 4250.0 MALE \n", - "14 17.9 190.0 3450.0 FEMALE \n", - "15 15.7 208.0 5350.0 MALE \n", - "16 21.1 198.0 4400.0 MALE \n", - "17 15.4 215.0 5150.0 MALE \n", - "18 20.0 197.0 3300.0 MALE \n", - "19 18.1 178.0 3900.0 MALE \n", - "20 18.8 203.0 4100.0 MALE \n", - "21 17.9 187.0 3200.0 FEMALE \n", - "22 13.9 210.0 4200.0 FEMALE \n", - "23 18.5 180.0 3550.0 FEMALE \n", - "24 20.3 194.0 3775.0 MALE \n", - "...\n", - "\n", - "[344 rows x 7 columns]" - ] - }, - "execution_count": 1, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# Initialize BigQuery DataFrame\n", - "import bigframes.pandas\n", - "\n", - "# read a BigQuery table to a BigQuery DataFrame\n", - "df = bigframes.pandas.read_gbq(\"bigframes-dev.bqml_tutorial.penguins\")\n", - "\n", - "# take a peek at the dataframe\n", - "df" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## 2. Data cleaning / prep" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "Query job 81305962-a96a-4c86-949c-471b2ae7c86d is DONE. 28.9 kB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job 2af0b0d6-c11b-499e-8d25-a2c628b2853b is DONE. 28.9 kB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "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", - "
islandculmen_length_mmculmen_depth_mmflipper_length_mmbody_mass_gsex
0Biscoe40.118.9188.04300.0MALE
1Torgersen39.118.7181.03750.0MALE
4Biscoe43.219.0197.04775.0MALE
6Biscoe41.321.1195.04400.0MALE
11Dream38.118.6190.03700.0FEMALE
13Biscoe37.820.0190.04250.0MALE
14Biscoe35.017.9190.03450.0FEMALE
16Torgersen34.621.1198.04400.0MALE
19Dream37.218.1178.03900.0MALE
21Biscoe40.517.9187.03200.0FEMALE
23Dream42.218.5180.03550.0FEMALE
30Dream39.221.1196.04150.0MALE
32Torgersen42.917.6196.04700.0MALE
38Dream41.117.5190.03900.0MALE
40Torgersen38.621.2191.03800.0MALE
42Biscoe35.516.2195.03350.0FEMALE
44Dream39.218.6190.04250.0MALE
45Torgersen35.215.9186.03050.0FEMALE
46Dream43.218.5192.04100.0MALE
49Biscoe39.617.7186.03500.0FEMALE
53Biscoe45.620.3191.04600.0MALE
58Torgersen40.916.8191.03700.0FEMALE
60Torgersen40.318.0195.03250.0FEMALE
62Dream36.018.5186.03100.0FEMALE
63Torgersen39.320.6190.03650.0MALE
\n", - "

25 rows × 6 columns

\n", - "
[146 rows x 6 columns in total]" - ], - "text/plain": [ - " island culmen_length_mm culmen_depth_mm flipper_length_mm \\\n", - "0 Biscoe 40.1 18.9 188.0 \n", - "1 Torgersen 39.1 18.7 181.0 \n", - "4 Biscoe 43.2 19.0 197.0 \n", - "6 Biscoe 41.3 21.1 195.0 \n", - "11 Dream 38.1 18.6 190.0 \n", - "13 Biscoe 37.8 20.0 190.0 \n", - "14 Biscoe 35.0 17.9 190.0 \n", - "16 Torgersen 34.6 21.1 198.0 \n", - "19 Dream 37.2 18.1 178.0 \n", - "21 Biscoe 40.5 17.9 187.0 \n", - "23 Dream 42.2 18.5 180.0 \n", - "30 Dream 39.2 21.1 196.0 \n", - "32 Torgersen 42.9 17.6 196.0 \n", - "38 Dream 41.1 17.5 190.0 \n", - "40 Torgersen 38.6 21.2 191.0 \n", - "42 Biscoe 35.5 16.2 195.0 \n", - "44 Dream 39.2 18.6 190.0 \n", - "45 Torgersen 35.2 15.9 186.0 \n", - "46 Dream 43.2 18.5 192.0 \n", - "49 Biscoe 39.6 17.7 186.0 \n", - "53 Biscoe 45.6 20.3 191.0 \n", - "58 Torgersen 40.9 16.8 191.0 \n", - "60 Torgersen 40.3 18.0 195.0 \n", - "62 Dream 36.0 18.5 186.0 \n", - "63 Torgersen 39.3 20.6 190.0 \n", - "\n", - " body_mass_g sex \n", - "0 4300.0 MALE \n", - "1 3750.0 MALE \n", - "4 4775.0 MALE \n", - "6 4400.0 MALE \n", - "11 3700.0 FEMALE \n", - "13 4250.0 MALE \n", - "14 3450.0 FEMALE \n", - "16 4400.0 MALE \n", - "19 3900.0 MALE \n", - "21 3200.0 FEMALE \n", - "23 3550.0 FEMALE \n", - "30 4150.0 MALE \n", - "32 4700.0 MALE \n", - "38 3900.0 MALE \n", - "40 3800.0 MALE \n", - "42 3350.0 FEMALE \n", - "44 4250.0 MALE \n", - "45 3050.0 FEMALE \n", - "46 4100.0 MALE \n", - "49 3500.0 FEMALE \n", - "53 4600.0 MALE \n", - "58 3700.0 FEMALE \n", - "60 3250.0 FEMALE \n", - "62 3100.0 FEMALE \n", - "63 3650.0 MALE \n", - "...\n", - "\n", - "[146 rows x 6 columns]" - ] - }, - "execution_count": 2, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# filter down to the data we want to analyze\n", - "adelie_data = df[df.species == \"Adelie Penguin (Pygoscelis adeliae)\"]\n", - "\n", - "# drop the columns we don't care about\n", - "adelie_data = adelie_data.drop(columns=[\"species\"])\n", - "\n", - "# drop rows with nulls to get our training data\n", - "training_data = adelie_data.dropna()\n", - "\n", - "# take a peek at the training data\n", - "training_data" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## 3. Use `model_selection.train_test_split` to prepare training data" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "Query job 0808457b-a0df-4a37-b7a5-8885f4a4588c is DONE. 28.9 kB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "from bigframes.ml.model_selection import train_test_split\n", - "\n", - "feature_columns = training_data[['island', 'culmen_length_mm', 'culmen_depth_mm', 'flipper_length_mm', 'sex']]\n", - "label_columns = training_data[['body_mass_g']] \n", - "\n", - "X_train, X_test, y_train, y_test = train_test_split(\n", - " feature_columns, label_columns, test_size=0.2)" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## 4. Configure a linear regression pipeline with preprocessing" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "Pipeline(steps=[('preproc',\n", - " ColumnTransformer(transformers=[('onehot', OneHotEncoder(),\n", - " ['island', 'species', 'sex']),\n", - " ('scaler', StandardScaler(),\n", - " ['culmen_depth_mm',\n", - " 'culmen_length_mm',\n", - " 'flipper_length_mm'])])),\n", - " ('linreg', LinearRegression(fit_intercept=False))])" - ] - }, - "execution_count": 4, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "from bigframes.ml.linear_model import LinearRegression\n", - "from bigframes.ml.pipeline import Pipeline\n", - "from bigframes.ml.compose import ColumnTransformer\n", - "from bigframes.ml.preprocessing import StandardScaler, OneHotEncoder\n", - "\n", - "preprocessing = ColumnTransformer([\n", - " (\"onehot\", OneHotEncoder(), [\"island\", \"sex\"]),\n", - " (\"scaler\", StandardScaler(), [\"culmen_depth_mm\", \"culmen_length_mm\", \"flipper_length_mm\"]),\n", - "])\n", - "\n", - "model = LinearRegression(fit_intercept=False)\n", - "\n", - "pipeline = Pipeline([\n", - " ('preproc', preprocessing),\n", - " ('linreg', model)\n", - "])\n", - "\n", - "# TODO(bmil): pretty printing for pipelines\n", - "pipeline" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## 5. Fit the pipeline to the training data\n", - "\n", - "This will create a temporary BQML model in BigQuery" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "Query job e9bfa6a5-a53f-4d8b-ae8c-cc8cd55d0947 is DONE. 28.9 kB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job d8d553cf-3d36-49aa-b18b-9a05576a1fb0 is DONE. 28.9 kB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job 75ef0083-9a4f-4ffb-a6c6-d82974a1659f is DONE. 0 Bytes processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/plain": [ - "Pipeline(steps=[('preproc',\n", - " ColumnTransformer(transformers=[('onehot', OneHotEncoder(),\n", - " ['island', 'species', 'sex']),\n", - " ('scaler', StandardScaler(),\n", - " ['culmen_depth_mm',\n", - " 'culmen_length_mm',\n", - " 'flipper_length_mm'])])),\n", - " ('linreg', LinearRegression(fit_intercept=False))])" - ] - }, - "execution_count": 5, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "pipeline.fit(X_train, y_train)" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## 6. Score the pipeline on the test data with `metrics.r2_score`" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "Query job 55c5a9ce-8159-4a1a-99a4-af3a906640ba is DONE. 29.3 kB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job 3e41c470-de70-4f13-89d9-c5564d0b2836 is DONE. 232 Bytes processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job ed2f9042-a737-4d13-bd21-8c3d29cd61a2 is DONE. 28.9 kB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job 815d16b5-0a5d-42be-a766-1cff5b8f22f2 is DONE. 28.9 kB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job 37a38dc6-5073-4544-a1e3-da145a843922 is DONE. 29.4 kB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/plain": [ - "0.2655729213572775" - ] - }, - "execution_count": 6, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "from bigframes.ml.metrics import r2_score\n", - "\n", - "y_pred = pipeline.predict(X_test)[\"predicted_body_mass_g\"]\n", - "\n", - "r2_score(y_test, y_pred)" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## 5. Inference the model on new data" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "Load job 7b46750c-70b4-468d-87ba-9f84f579f2a6 is DONE. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "import pandas\n", - "\n", - "new_penguins = bigframes.pandas.read_pandas(\n", - " pandas.DataFrame(\n", - " {\n", - " \"tag_number\": [1633, 1672, 1690],\n", - " \"species\": [\n", - " \"Adelie Penguin (Pygoscelis adeliae)\",\n", - " \"Adelie Penguin (Pygoscelis adeliae)\",\n", - " \"Adelie Penguin (Pygoscelis adeliae)\",\n", - " ],\n", - " \"island\": [\"Torgersen\", \"Torgersen\", \"Dream\"],\n", - " \"culmen_length_mm\": [39.5, 38.5, 37.9],\n", - " \"culmen_depth_mm\": [18.8, 17.2, 18.1],\n", - " \"flipper_length_mm\": [196.0, 181.0, 188.0],\n", - " \"sex\": [\"MALE\", \"FEMALE\", \"FEMALE\"],\n", - " }\n", - " ).set_index(\"tag_number\")\n", - " )" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "Query job d10dd37d-5e8e-4e15-9c83-a7e9a4c592a8 is DONE. 593 Bytes processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job 207cb787-cf8a-43ea-8e73-644d3f58b11a is DONE. 24 Bytes processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job c5dc5075-cac0-4947-9e9f-06aa9cc5bd2a is DONE. 0 Bytes processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job 2ca4a569-7186-48ed-b3e4-004dca704798 is DONE. 282 Bytes processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "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", - "
predicted_body_mass_gspeciesislandculmen_length_mmculmen_depth_mmflipper_length_mmsex
tag_number
16334017.203152Adelie Penguin (Pygoscelis adeliae)Torgersen39.518.8196.0MALE
16723127.601519Adelie Penguin (Pygoscelis adeliae)Torgersen38.517.2181.0FEMALE
16903386.101231Adelie Penguin (Pygoscelis adeliae)Dream37.918.1188.0FEMALE
\n", - "

3 rows × 7 columns

\n", - "
[3 rows x 7 columns in total]" - ], - "text/plain": [ - " predicted_body_mass_g species \\\n", - "tag_number \n", - "1633 4017.203152 Adelie Penguin (Pygoscelis adeliae) \n", - "1672 3127.601519 Adelie Penguin (Pygoscelis adeliae) \n", - "1690 3386.101231 Adelie Penguin (Pygoscelis adeliae) \n", - "\n", - " island culmen_length_mm culmen_depth_mm flipper_length_mm \\\n", - "tag_number \n", - "1633 Torgersen 39.5 18.8 196.0 \n", - "1672 Torgersen 38.5 17.2 181.0 \n", - "1690 Dream 37.9 18.1 188.0 \n", - "\n", - " sex \n", - "tag_number \n", - "1633 MALE \n", - "1672 FEMALE \n", - "1690 FEMALE \n", - "\n", - "[3 rows x 7 columns]" - ] - }, - "execution_count": 8, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "pipeline.predict(new_penguins)" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## 6. Save in BigQuery" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "Copy job d1def4a4-1da1-43a9-8ae5-4459444d993d is DONE. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/plain": [ - "Pipeline(steps=[('transform',\n", - " ColumnTransformer(transformers=[('ont_hot_encoder',\n", - " OneHotEncoder(max_categories=1000001,\n", - " min_frequency=0),\n", - " 'island'),\n", - " ('standard_scaler',\n", - " StandardScaler(),\n", - " 'culmen_length_mm'),\n", - " ('standard_scaler',\n", - " StandardScaler(),\n", - " 'culmen_depth_mm'),\n", - " ('standard_scaler',\n", - " StandardScaler(),\n", - " 'flipper_length_mm'),\n", - " ('ont_hot_encoder',\n", - " OneHotEncoder(max_categories=1000001,\n", - " min_frequency=0),\n", - " 'sex')])),\n", - " ('estimator',\n", - " LinearRegression(fit_intercept=False,\n", - " optimize_strategy='NORMAL_EQUATION'))])" - ] - }, - "execution_count": 9, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "pipeline.to_gbq(\"bigframes-dev.bigframes_demo_us.penguin_model\", replace=True)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "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.9" - }, - "orig_nbformat": 4, - "vscode": { - "interpreter": { - "hash": "a850322d07d9bdc9ec5f301d307e048bcab2390ae395e1cbce9335f4e081e5e2" - } - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/notebooks/ml/timeseries_analysis.ipynb b/notebooks/ml/timeseries_analysis.ipynb deleted file mode 100644 index 3b227460230..00000000000 --- a/notebooks/ml/timeseries_analysis.ipynb +++ /dev/null @@ -1,1135 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "cf1403ce", - "metadata": {}, - "source": [ - "# Time Series Forecasting with BigFrames\n", - "\n", - "This notebook provides a comprehensive walkthrough of time series forecasting using the BigFrames library. We will explore two powerful models, TimesFM and ARIMAPlus, to predict bikeshare trip demand based on historical data from San Francisco. The process covers data loading, preprocessing, model training, and visualization of the results." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "c0b2db75", - "metadata": {}, - "outputs": [], - "source": [ - "import bigframes.pandas as bpd\n", - "from bigframes.ml import forecasting\n", - "bpd.options.display.render_mode = \"anywidget\"" - ] - }, - { - "cell_type": "markdown", - "id": "0eba46b9", - "metadata": {}, - "source": [ - "## 1. Data Loading and Preprocessing", - "\n", - "The first step is to load the San Francisco bikeshare dataset from BigQuery. We then preprocess the data by filtering for trips made by 'Subscriber' type users from 2018 onwards. This ensures we are working with a relevant and consistent subset of the data. Finally, we aggregate the trip data by the hour to create a time series of trip counts." - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "83928f4d", - "metadata": {}, - "outputs": [], - "source": [ - "df = bpd.read_gbq(\"bigquery-public-data.san_francisco_bikeshare.bikeshare_trips\")\n", - "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_grouped = df[[\"trip_hour\", \"trip_id\"]].groupby(\"trip_hour\").count().reset_index()\n", - "df_grouped = df_grouped.rename(columns={\"trip_id\": \"num_trips\"})" - ] - }, - { - "cell_type": "markdown", - "id": "c43b7e65", - "metadata": {}, - "source": [ - "### 2. Forecasting with TimesFM\n", - "\n", - "In this section, we use the TimesFM (Time Series Foundation Model) to forecast future bikeshare demand. TimesFM is a powerful model designed for a wide range of time series forecasting tasks. We will use it to predict the number of trips for the last week of our dataset." - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "1096e154", - "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/usr/local/google/home/shuowei/src/python-bigquery-dataframes/bigframes/dataframe.py:5340: FutureWarning: The 'ai' property will be removed. Please use 'bigframes.bigquery.ai'\n", - "instead.\n", - " warnings.warn(msg, category=FutureWarning)\n" - ] - }, - { - "data": { - "text/html": [ - "✅ Completed. \n", - " Query processed 58.7 MB in 19 seconds of slot time. [Job bigframes-dev:US.eb026c28-038a-4ca7-acfa-474ed0be4119 details]\n", - " " - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "✅ Completed. \n", - " Query processed 7.1 kB in a moment of slot time.\n", - " " - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "✅ Completed. \n", - " Query processed 7.1 kB in a moment of slot time.\n", - " " - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "929eda852e564b799cf76e62d9f7b46a", - "version_major": 2, - "version_minor": 1 - }, - "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", - "
forecast_timestampforecast_valueconfidence_levelprediction_interval_lower_boundprediction_interval_upper_boundai_forecast_status
02018-04-24 14:00:00+00:00126.5192110.9596.837778156.200644
12018-04-30 21:00:00+00:0082.2661970.95-7.690994172.223388
22018-04-25 14:00:00+00:00130.0572660.9578.019585182.094948
32018-04-26 06:00:00+00:0047.2352140.95-16.565634111.036063
42018-04-28 01:00:00+00:000.7611390.95-61.08053162.602809
52018-04-27 11:00:00+00:00160.4370420.9580.767928240.106157
62018-04-25 07:00:00+00:00321.4184880.95207.344246435.492729
72018-04-24 16:00:00+00:00284.6405640.95198.550187370.730941
82018-04-25 16:00:00+00:00329.6537480.95201.918472457.389023
92018-04-26 10:00:00+00:00160.9959720.9567.706721254.285223
\n", - "

10 rows × 6 columns

\n", - "
[168 rows x 6 columns in total]" - ], - "text/plain": [ - " forecast_timestamp forecast_value confidence_level \\\n", - "0 2018-04-24 14:00:00+00:00 126.519211 0.95 \n", - "1 2018-04-30 21:00:00+00:00 82.266197 0.95 \n", - "2 2018-04-25 14:00:00+00:00 130.057266 0.95 \n", - "3 2018-04-26 06:00:00+00:00 47.235214 0.95 \n", - "4 2018-04-28 01:00:00+00:00 0.761139 0.95 \n", - "5 2018-04-27 11:00:00+00:00 160.437042 0.95 \n", - "6 2018-04-25 07:00:00+00:00 321.418488 0.95 \n", - "7 2018-04-24 16:00:00+00:00 284.640564 0.95 \n", - "8 2018-04-25 16:00:00+00:00 329.653748 0.95 \n", - "9 2018-04-26 10:00:00+00:00 160.995972 0.95 \n", - "\n", - " prediction_interval_lower_bound prediction_interval_upper_bound \\\n", - "0 96.837778 156.200644 \n", - "1 -7.690994 172.223388 \n", - "2 78.019585 182.094948 \n", - "3 -16.565634 111.036063 \n", - "4 -61.080531 62.602809 \n", - "5 80.767928 240.106157 \n", - "6 207.344246 435.492729 \n", - "7 198.550187 370.730941 \n", - "8 201.918472 457.389023 \n", - "9 67.706721 254.285223 \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", - "...\n", - "\n", - "[168 rows x 6 columns]" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "result = df_grouped.head(2842-168).ai.forecast(\n", - " timestamp_column=\"trip_hour\",\n", - " data_column=\"num_trips\",\n", - " horizon=168\n", - ")\n", - "result" - ] - }, - { - "cell_type": "markdown", - "id": "90e80a82", - "metadata": {}, - "source": [ - "### 3. Forecasting with ARIMAPlus\n", - "\n", - "Next, we will use the ARIMAPlus model, which is a BigQuery ML model available through BigFrames. ARIMAPlus is an advanced forecasting model that can capture complex time series patterns. We will train it on the same historical data and use it to forecast the same period as the TimesFM model." - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "f41e1cf0", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - " Query processed 1.8 MB in 46 seconds of slot time. [Job bigframes-dev:US.ac354d97-dc91-4d01-9dca-7069db6a26a7 details]\n", - " " - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "✅ Completed. \n", - " Query processed 92.2 kB in a moment of slot time. [Job bigframes-dev:US.e61f41af-8761-4853-ae41-d38760c966ed details]\n", - " " - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "✅ Completed. \n", - " Query processed 1.3 kB in a moment of slot time.\n", - " " - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "✅ Completed. \n", - " Query processed 10.8 kB in a moment of slot time.\n", - " " - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "✅ Completed. \n", - " Query processed 0 Bytes in a moment of slot time.\n", - " " - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "0624fdda2be74b13bc6e6c30e38842b6", - "version_major": 2, - "version_minor": 1 - }, - "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", - "
forecast_timestampforecast_valuestandard_errorconfidence_levelprediction_interval_lower_boundprediction_interval_upper_boundconfidence_interval_lower_boundconfidence_interval_upper_bound
02018-04-24 00:00:00+00:0052.76833534.874520.95-15.462203120.998872-15.462203120.998872
12018-04-24 01:00:00+00:0067.328148.0752550.95-26.729122161.385322-26.729122161.385322
22018-04-24 02:00:00+00:0075.20557353.9109210.95-30.268884180.68003-30.268884180.68003
32018-04-24 03:00:00+00:0080.07092255.9940760.95-29.479141189.620985-29.479141189.620985
42018-04-24 04:00:00+00:0075.16177956.5839740.95-35.542394185.865952-35.542394185.865952
52018-04-24 05:00:00+00:0081.42843256.850870.95-29.797913192.654778-29.797913192.654778
62018-04-24 06:00:00+00:00116.98144557.1807670.955.109671228.8532185.109671228.853218
72018-04-24 07:00:00+00:00237.22236157.7703070.95124.197176350.247546124.197176350.247546
82018-04-24 08:00:00+00:00323.72257258.6816620.95208.91436438.530784208.91436438.530784
92018-04-24 09:00:00+00:00357.28895259.8069060.95240.279247474.298656240.279247474.298656
\n", - "

10 rows × 8 columns

\n", - "
[168 rows x 8 columns in total]" - ], - "text/plain": [ - " forecast_timestamp forecast_value standard_error \\\n", - "0 2018-04-24 00:00:00+00:00 52.768335 34.87452 \n", - "1 2018-04-24 01:00:00+00:00 67.3281 48.075255 \n", - "2 2018-04-24 02:00:00+00:00 75.205573 53.910921 \n", - "3 2018-04-24 03:00:00+00:00 80.070922 55.994076 \n", - "4 2018-04-24 04:00:00+00:00 75.161779 56.583974 \n", - "5 2018-04-24 05:00:00+00:00 81.428432 56.85087 \n", - "6 2018-04-24 06:00:00+00:00 116.981445 57.180767 \n", - "7 2018-04-24 07:00:00+00:00 237.222361 57.770307 \n", - "8 2018-04-24 08:00:00+00:00 323.722572 58.681662 \n", - "9 2018-04-24 09:00:00+00:00 357.288952 59.806906 \n", - "\n", - " confidence_level prediction_interval_lower_bound \\\n", - "0 0.95 -15.462203 \n", - "1 0.95 -26.729122 \n", - "2 0.95 -30.268884 \n", - "3 0.95 -29.479141 \n", - "4 0.95 -35.542394 \n", - "5 0.95 -29.797913 \n", - "6 0.95 5.109671 \n", - "7 0.95 124.197176 \n", - "8 0.95 208.91436 \n", - "9 0.95 240.279247 \n", - "\n", - " prediction_interval_upper_bound confidence_interval_lower_bound \\\n", - "0 120.998872 -15.462203 \n", - "1 161.385322 -26.729122 \n", - "2 180.68003 -30.268884 \n", - "3 189.620985 -29.479141 \n", - "4 185.865952 -35.542394 \n", - "5 192.654778 -29.797913 \n", - "6 228.853218 5.109671 \n", - "7 350.247546 124.197176 \n", - "8 438.530784 208.91436 \n", - "9 474.298656 240.279247 \n", - "\n", - " confidence_interval_upper_bound \n", - "0 120.998872 \n", - "1 161.385322 \n", - "2 180.68003 \n", - "3 189.620985 \n", - "4 185.865952 \n", - "5 192.654778 \n", - "6 228.853218 \n", - "7 350.247546 \n", - "8 438.530784 \n", - "9 474.298656 \n", - "...\n", - "\n", - "[168 rows x 8 columns]" - ] - }, - "execution_count": 4, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "model = forecasting.ARIMAPlus(\n", - " auto_arima_max_order=5, # Reduce runtime for large datasets\n", - " data_frequency=\"hourly\",\n", - " horizon=168\n", - ")\n", - "X = df_grouped.head(2842-168)[[\"trip_hour\"]]\n", - "y = df_grouped.head(2842-168)[[\"num_trips\"]]\n", - "model.fit(\n", - " X, y\n", - ")\n", - "predictions = model.predict(horizon=168, confidence_level=0.95)\n", - "predictions" - ] - }, - { - "cell_type": "markdown", - "id": "ec5a4513", - "metadata": {}, - "source": [ - "### 4. Compare and Visualize Forecasts\n", - "\n", - "Now we will visualize the forecasts from both TimesFM and ARIMAPlus against the actual historical data. This allows for a direct comparison of the two models' performance." - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "id": "7f5b5b1e", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "✅ Completed. \n", - " Query processed 31.7 MB in 11 seconds of slot time.\n", - " " - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "✅ Completed. \n", - " Query processed 58.8 MB in 12 seconds of slot time.\n", - " " - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/plain": [ - "" - ] - }, - "execution_count": 5, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAjMAAAH7CAYAAAA5AR6GAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjcsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvTLEjVAAAAAlwSFlzAAAPYQAAD2EBqD+naQABAABJREFUeJzsfXmcFMXd/tNz7M0uh8CicomoIKAGo25AMQZFBN+oGDVeaEx8o3jBKxrfGIN4kGCMV1CjPyNGY/T1TIInqGBEIIjRKCgqCotyKQILLLsz012/P2a6u6q7vtU1Mws7M9vP57Ofnemp6rO66qnne5TBGGMIESJEiBAhQoQoUkTa+wRChAgRIkSIECHyQUhmQoQIESJEiBBFjZDMhAgRIkSIECGKGiGZCREiRIgQIUIUNUIyEyJEiBAhQoQoaoRkJkSIECFChAhR1AjJTIgQIUKECBGiqBGSmRAhQoQIESJEUSMkMyFChAgRIkSIokZIZkKECMCxxx6LY489tr1PI0QIJcJ2GqIjIyQzIUoehmFo/c2fP3+PnM/s2bPJc/jFL36xR86hPfH444/jzjvvbPP9NjU14cYbb8QhhxyCmpoaVFZWYsiQIbj22muxbt26Nj9eiBAhCgdGuDZTiFLHY489Jnz/85//jLlz5+LRRx8Vth9//PHo2bOnr34ikQAAlJWVtcn5zJ49GxdeeCGmT5+O/v37C78NGTIEhx56aJscp1Axfvx4fPjhh1i9enWb7fPzzz/H6NGj0djYiB/96EcYOXIkysrK8J///Ad//etf0bVrV3zyySdtdrxCRFu30xAhigmx9j6BECF2N84991zh++LFizF37lzfdi+am5tRVVW12waHsWPH4vDDD2/z/e7cuRPV1dVtvt9CRSqVwmmnnYaNGzdi/vz5GDlypPD7Lbfcgt/+9rftdHa7H7u7nYYIUQwIzUwhQiDtbzBkyBAsW7YMxxxzDKqqqvC///u/zm+8L8L8+fNhGAaefPJJ/O///i/q6+tRXV2N//qv/8LatWvb7Jxef/11HH300aiurkbnzp3xwx/+EB999JFQZtq0aTAMAytWrMDZZ5+NLl26CIP5Y489huHDh6OyshJdu3bFWWedJT3HJUuW4KSTTkKXLl1QXV2NYcOG4a677nJ+/89//oMLLrgA++23HyoqKlBfX4+f/OQn2Lx5s7Cf7du346qrrkK/fv1QXl6OHj164Pjjj8e7774LIH0vX3jhBaxZs8YxrfXr18+pf8899+Dggw9GVVUVunTpgsMPPxyPP/648j4988wzeP/99/HLX/7SR2QAoLa2Frfccouw7amnnnLuy1577YVzzz0XX331lVDmggsuQE1NDRobGzF+/HjU1NRgn332waxZswAAH3zwAY477jhUV1ejb9++vvO0zYlvvvkm/vu//xvdunVDbW0tzj//fGzZskUo+7e//Q3jxo3D3nvvjfLycgwYMAA33XQTTNMUymXTTnXv57///W+MHTsWtbW1qKmpwQ9+8AMsXrxYei0LFy7ElClT0L17d1RXV+PUU0/F119/LXssIULsUYTKTIgQGWzevBljx47FWWedhXPPPVdqcuJxyy23wDAMXHvttdi0aRPuvPNOjB49Gu+99x4qKysDj7dt2zZ88803wra99toLADBv3jyMHTsW++23H6ZNm4Zdu3bhnnvuwYgRI/Duu+8KBAAAfvSjH2HgwIG49dZbYVuOb7nlFvzqV7/CGWecgZ/+9Kf4+uuvcc899+CYY47Bv//9b3Tu3BkAMHfuXIwfPx69evXClVdeifr6enz00UeYM2cOrrzySqfM559/jgsvvBD19fVYvnw5HnjgASxfvhyLFy+GYRgAgJ///Od4+umncdlll2Hw4MHYvHkz3nrrLXz00Uf4zne+g1/+8pfYtm0bvvzyS9xxxx0AgJqaGgDAgw8+iCuuuAKnn346rrzySrS0tOA///kPlixZgrPPPpu8j3//+98BAOedd17gPQdcM993v/tdzJgxAxs3bsRdd92FhQsXCvcFAEzTxNixY3HMMcdg5syZ+Mtf/oLLLrsM1dXV+OUvf4lzzjkHp512Gu6//36cf/75aGho8JkOL7vsMnTu3BnTpk3DypUrcd9992HNmjUOKbbPqaamBlOmTEFNTQ1ef/113HDDDWhqasJtt90m7E+3nercz+XLl+Poo49GbW0trrnmGsTjcfzxj3/EscceiwULFuDII48U9nn55ZejS5cu+PWvf43Vq1fjzjvvxGWXXYYnn3xS696HCLHbwEKE6GCYNGkS8zb9UaNGMQDs/vvv95UfNWoUGzVqlPP9jTfeYADYPvvsw5qampzt//d//8cAsLvuukt5/IcffpgBkP7ZOPTQQ1mPHj3Y5s2bnW3vv/8+i0Qi7Pzzz3e2/frXv2YA2I9//GPhGKtXr2bRaJTdcsstwvYPPviAxWIxZ3sqlWL9+/dnffv2ZVu2bBHKWpblfG5ubvZdx1//+lcGgL355pvOtrq6OjZp0iTl9Y8bN4717dvXt/2HP/whO/jgg5V1ZTjssMNYXV2dVtlEIsF69OjBhgwZwnbt2uVsnzNnDgPAbrjhBmfbxIkTGQB26623Otu2bNnCKisrmWEY7IknnnC2f/zxxwwA+/Wvf+1ss5/z8OHDWSKRcLbPnDmTAWB/+9vfnG2y+/vf//3frKqqirW0tDjbsmmnOvfzlFNOYWVlZWzVqlXOtnXr1rFOnTqxY445xncto0ePFtrF5MmTWTQaZVu3blUeJ0SI3Y3QzBQiRAbl5eW48MILtcuff/756NSpk/P99NNPR69evfDiiy9q1Z81axbmzp0r/AHA+vXr8d577+GCCy5A165dnfLDhg3D8ccfL93/z3/+c+H7s88+C8uycMYZZ+Cbb75x/urr6zFw4EC88cYbANImhi+++AJXXXWVoEgAcFQDAILS1NLSgm+++QZHHXUUADgmJADo3LkzlixZklP0UOfOnfHll19i6dKlWdVramoSnoMK77zzDjZt2oRLL70UFRUVzvZx48bhoIMOwgsvvOCr89Of/lQ4xwMPPBDV1dU444wznO0HHnggOnfujM8//9xX/+KLL0Y8Hne+X3LJJYjFYsJz5O/v9u3b8c033+Doo49Gc3MzPv74Y2F/uu006H6apolXX30Vp5xyCvbbbz9ne69evXD22WfjrbfeQlNTk+9a+HZx9NFHwzRNrFmzJvB8QoTYnQjJTIgQGeyzzz5ZOVEOHDhQ+G4YBvbff3/tKJ0jjjgCo0ePFv4AOAPDgQce6KszaNAgfPPNN9i5c6ew3Wva+PTTT8EYw8CBA9G9e3fh76OPPsKmTZsAAKtWrQKQjqJS4dtvv8WVV16Jnj17orKyEt27d3eOuW3bNqfczJkz8eGHH6J379444ogjMG3aNOkAL8O1116LmpoaHHHEERg4cCAmTZqEhQsXBtarra3F9u3btY6hurcHHXSQb1CuqKhA9+7dhW11dXXYd999hUHd3u71hQH87aSmpga9evUS2sny5ctx6qmnoq6uDrW1tejevbvjoM7fX0C/nQbdz6+//hrNzc1kO7Msy+df1adPH+F7ly5dAEB63SFC7EmEPjMhQmSg4+dSqPCeu2VZMAwDL730EqLRqK+87aeiizPOOANvv/02pk6dikMPPRQ1NTWwLAsnnngiLMsSyh199NF47rnn8Oqrr+K2227Db3/7Wzz77LMYO3as8hiDBg3CypUrMWfOHLz88st45plncO+99+KGG27AjTfeSNY76KCD8O9//xtr165F7969s7quIMjunWo7yyHTxdatWzFq1CjU1tZi+vTpGDBgACoqKvDuu+/i2muvFe4voN9Oc72fKrTldYcI0ZYIlZkQIXLEp59+KnxnjOGzzz7zOedmi759+wIAVq5c6fvt448/xl577RUYej1gwAAwxtC/f3+f+jN69GjHRDRgwAAAwIcffkjua8uWLXjttdfwi1/8AjfeeCNOPfVUHH/88YJpgkevXr1w6aWX4vnnn8cXX3yBbt26CdFEXkWDR3V1Nc4880w8/PDDaGxsxLhx43DLLbegpaWFrHPyyScD8OcTkkF1b1euXOn83pbwtpMdO3Zg/fr1TjuZP38+Nm/ejNmzZ+PKK6/E+PHjMXr0aEf1yAeq+9m9e3dUVVWR7SwSibQ5OQwRYnchJDMhQuSIP//5z4J54+mnn8b69esDFYgg9OrVC4ceeigeeeQRbN261dn+4Ycf4tVXX8VJJ50UuI/TTjsN0WgUN954o2/WzBhzQqq/853voH///rjzzjuFY9nlAHc27t2PN4uvaZo+k0iPHj2w9957o7W11dlWXV3tKwfAF+ZdVlaGwYMHgzGGZDJJXuvpp5+OoUOH4pZbbsGiRYt8v2/fvh2//OUvAQCHH344evTogfvvv184p5deegkfffQRxo0bRx4nVzzwwAPC+d93331IpVJOO5Hd30QigXvvvTev4wbdz2g0ihNOOAF/+9vfBJPXxo0b8fjjj2PkyJGora3N6xxChNhTCM1MIULkiK5du2LkyJG48MILsXHjRtx5553Yf//98bOf/Szvfd92220YO3YsGhoacNFFFzmh2XV1dZg2bVpg/QEDBuDmm2/Gddddh9WrV+OUU05Bp06d8MUXX+C5557DxRdfjKuvvhqRSAT33XcfTj75ZBx66KG48MIL0atXL3z88cdYvnw5XnnlFdTW1jqhyclkEvvssw9effVVfPHFF8Ixt2/fjn333Renn366s6TAvHnzsHTpUtx+++1OueHD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+wIAhg4dqn2dIQoTiZSFix99Bw37dcN/jxrQ3qcTosAQkpkQIXJAZTyKFdPHtNuxs8WwYcOE77169cKmTZty3kfPnj1RVVXlEBl727/+9a+sz82Lww8/PLDMIYccImRjbmhowI4dO7B27Voccsgh+MEPfoChQ4dizJgxOOGEE3D66aejS5cueZ9biPbD8+99hfkrv8b8lV+HZCaEDyGZCREiBxiGkbWppz3hXXfHMAxYloVIJO02x5tg7KUbVPswDIPcZ76orq7Oq340GsXcuXPx9ttv49VXX8U999yDX/7yl1iyZAn69++f9/mFaB/sSoRLLoSgEToAhwjRgdG9e3cAwPr1651tvDPw7kJZWVle6wG9//772LVrl/N98eLFqKmpQe/evQGkidWIESNw44034t///jfKysrw3HPP5X3eIdoP4coLIVQonqlliBAh2hyVlZU46qij8Jvf/Ab9+/fHpk2bcP311+/24/br1w9LlizB6tWrUVNTg65du2ZVP5FI4KKLLsL111+P1atX49e//jUuu+wyRCIRLFmyBK+99hpOOOEE9OjRA0uWLMHXX3+NQYMG7aarCbEnEHKZECqEykyIEB0cf/rTn5BKpTB8+HBcddVVuPnmm3f7Ma+++mpEo1EMHjwY3bt3R2NjY1b1f/CDH2DgwIE45phjcOaZZ+K//uu/nHDz2tpavPnmmzjppJNwwAEH4Prrr8ftt9+OsWPH7oYrCREiRCHAYEUYr9jU1IS6ujps27YNtbW17X06IToAWlpa8MUXX6B///6oqKho79Pp0LjggguwdetWPP/88+19KkWPYmrXjy5eg189/yEAYPVvxrXz2ZQW+v3iBefz7r63u2v8DpWZECFChAhR8AjNTLsPpeCPFJKZECFCtCkaGxtRU1ND/mVrUgoRAiiNAbdQESmBmxs6AIcIEaJNsffeeysjovbee++89j979uy86ofIHVt2JtCluqxdjm20gTbzzLIvUV0exYlDerXBGZUOIgZQ7IHvIZkJESJEmyIWi2H//fdv79MI0cZ47t9fYvKT7+OSYwfg2hMP2uPHz1c82NjUgv956n0AwBczToJRAmpEWyF9L4rOfVZAaGYKESJEiBCB+PXflgMA7pu/ql2Ony/1aNrlJoMsvrCX3YtSoHUhmQkRIkSIEAWPfIUUvr4VshkBpeAzE5KZECFChAgRiOI3y7jnX8yrQ+8OFP2jRUhmQoQIESJEESBfB+BQmaERKjMhQoQIEaJDoN3Hu3zNTNznkMuIaO9H2xYIyUyIEB0M8+fPh2EY2Lp1a7uex8KFCzF06FDE43Gccsopu+UYjDFcfPHF6Nq1KwzD2COLaIbYPch3wOXNZKEyI6LdiWobICsy069fPxiG4fubNGkSgHRq7EmTJqFbt26oqanBhAkTsHHjRmEfjY2NGDduHKqqqtCjRw9MnToVqVSq7a4oRIgQAo499lhcddVVzvfvfe97WL9+Perq6trvpABMmTIFhx56KL744ovdljvm5ZdfxuzZszFnzhysX78eQ4YM2S3H2RPo168f7rzzznY7fluMd9uak8h1BR2Vz86O1hSSpqWuz30OyYyISKT42UxWZGbp0qVYv3698zd37lwAwI9+9CMAwOTJk/GPf/wDTz31FBYsWIB169bhtNNOc+qbpolx48YhkUjg7bffxiOPPILZs2fjhhtuaMNLChEihAplZWWor69vd4fOVatW4bjjjsO+++6Lzp0777Zj9OrVC9/73vdQX1+PWCz71FqMsXDC1QZ4t3ELDpn+Ki77679zqi+aiVwysmVnAkN+/QqO//0CdX3BZyanUyhZdDifme7du6O+vt75mzNnDgYMGIBRo0Zh27ZteOihh/D73/8exx13HIYPH46HH34Yb7/9NhYvXgwAePXVV7FixQo89thjOPTQQzF27FjcdNNNmDVrFhKJxG65wBAhdgsYAxI72+cvi1nlBRdcgAULFuCuu+5ylNTZs2cLZqbZs2ejc+fOmDNnDg488EBUVVXh9NNPR3NzMx555BH069cPXbp0wRVXXAHTdPOEtra24uqrr8Y+++yD6upqHHnkkZg/f77z+5o1a3DyySejS5cuqK6uxsEHH4wXX3wRq1evhmEY2Lx5M37yk58452Sbv1555RUcdthhqKysxHHHHYdNmzbhpZdewqBBg1BbW4uzzz4bzc3NWtd++eWXo7GxEYZhoF+/fs55X3HFFejRowcqKiowcuRILF261Klnn8dLL72E4cOHo7y8HG+99RYsy8KMGTPQv39/VFZW4pBDDsHTTz8tHHP58uUYP348amtr0alTJxx99NFYtSqdl2Xp0qU4/vjjsddee6Gurg6jRo3Cu+++yzUphmnTpqFPnz4oLy/H3nvvjSuu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2aaeP/3pT+jTpw9qampw6aWXwjRNzJw5E/X19ejRowduueUW4di///3vMXToUFRXV6N379649NJLsWPHDud3+zjPP/88Bg4ciIqKCowZMwZr19L+C8ceeyyuuuoqYdspp5yCCy64wPl+7733Ovvr2bMnzjnrzMD7dMEFF+Dyyy9HY2MjDMNAv379AACtra3OCtcVFRUYOXIkli5d6tSbP38+DMPASy+9hOHDh6O8vBxvvfUWLMvCjBkz0L9/f1RWVuKQQw7B008/LRxz+fLlGD9+PGpra9GpUyccffTRzrNZunQpjj/+eOy1116oq6vDqFGj8O677zp1GWOYNm0a+vTpg/Lycuy999644oornHu0Zs0aTJ48GYZh7NZoFd1xT8cBOKnIKUWFZlMmJ199yOvz+011WDLjfi5WJbpjTB3ygGpWfsVf/42vtu7C//13g7YTW4jskW8/LL6oOexAUmdXaheOfPzInM8pHyw5ewmq4vor/u7cuRNTpkzBsGHDsGPHDtxwww049dRTBX+QX/ziF7j99ttx2GGHoaKiAq+88opvP6tWrcJLL72El19+GatWrcLpp5+Ozz//HAcccAAWLFiAt99+Gz/5yU8wevRoHHlk+t5EIhHcfffd6N+/Pz7//HNceumluOaaa3Dvvfc6+21ubsYtt9yCP//5zygrK8Oll16Ks846CwsXLszp/rzzzju44oor8Oijj+J73/sevv32W7z2xvzAenfddRcGDBiABx54AEuXLkU0GgUAXHPNNXjmmWfwyCOPoG/fvpg5cybGjBmDzz77DF27dhXu4e9+9zvst99+6NKlC2bMmIHHHnsM999/PwYOHIg333wT5557Lrp3745Ro0bhq6++wjHHHINjjz0Wr7/+Ompra7Fw4UKkUmlTx/bt2zFx4kTcc889YIzh9ttvx0knnYRPP/0UnTp1wjPPPIM77rgDTzzxBA4++GBs2LAB77//PgDg2WefxSGHHIKLL74YP/vZz3K6j15Q76HuZIOKSuTzzOSUwVcwM9HHp31u9I5fyvAu1lmMxKAYz3mPQsX6//5+ejn7/3y5FYf16bJHz6sjoS1pYi4O3cU6U7ExYcIE4fuf/vQndO/eHStWrEBNTQ0A4KqrrsJpp52m3I9lWfjTn/6ETp06YfDgwfj+97+PlStX4sUXX0QkEsGBBx6I3/72t3jj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8ggURcEzzzyDhQsXIhaL4ZVXXilyM1144YU488wzcfvtt6OhoQGpVAqXX345stms8Pk1TcP111+Puro6NDU14Zvf/Kbl/S1btuCMM85AMplEKpXC2Wefjebm5qI28Lj66qtx3HHHsf+PO+44rFixAldffTVGjBjBat5ISEgMLUgyIyEh4Yj77rsPixcvxiWXXIKdO3di586dGDduHADgxhtvxHe+8x2sX78ec+fOdTx+5cqVWL9+PV588UU8+eST+PWvf43bb79d+PyPPvooKisr8cYbb+Duu+/GHXfcgeeeew6ATnTOOOMMtLS04G9/+xuee+45bNy4Eeecc07gfj766KOIRqN49dVX8eMf/zjw8RISEoMP6WaSkJBwRHV1NaLRKCoqKtDU1AQA+PDDDwEAd9xxB0488UTP46PRKB5++GFUVFTg4IMPxh133IHrrrsO3/rWt6Cq/vuouXPn4rbbbgMATJ06FQ888ABWrlyJE088EStXrsSaNWuwadMmRrAee+wxHHzwwXjrrbdw2GGHCfdz6tSpuPvuu4U/LyEhUXqQlhkJCYnAOPTQQ30/M2/ePFRUVLD/Fy9ejK6uLmzdulXoHHaLz6hRo7B7924AwPr16zFu3DhGZABg1qxZqKmpwfr164W+n2LhwoWBPi8hIVF6kGRGQkIiMCorKwf8HJFIxPK/oijQNE34eFVVQQixvJbL5Yo+dyD6IiEhMbCQZEZCQsIV0WgUhUKhT8euXr0avb297P/XX38dyWTSYk3pK2bOnImtW7darDzr1q1DW1sbZs2aBQBoaGjAzp07LcetWrVqv88tISFRepBkRkJCwhUTJ07EG2+8gc2bN2Pv3r2BLCPZbBYXX3wx1q1bhz/96U+47bbbsGLFCiG9jB+WLVuGOXPm4Pzzz8e7776LN998ExdccAGOPfZY5gI74YQT8Pbbb+Oxxx7DJ598gttuuw0ffPDBfp9bQkKi9CDJjISEhCu+/vWvIxQKYdasWWhoaMCWLVuEj126dCmmTp2KY445Bueccw5OP/30ovDqvkJRFPzf//0famtrccwxx2DZsmWYNGkSnnrqKfaZk046Cf/+7/+O66+/Hocddhg6OztxwQUX9Mv5JSQkSgsKsTuVJSQkJPYTF154Idra2vDb3/52sJsiISFRBpCWGQkJCQkJCYkhDUlmJCQkDii2bNnCyh84/QRxZUlISEgA0s0kISFxgJHP57F582bX9ydOnIhwWObzlJCQEIckMxISEhISEhJDGtLNJCEhISEhITGkIcmMhISEhISExJCGJDMSEhISEhISQxqSzEhISEhISEgMaUgyIyEhISEhITGkIcmMhISEhISExJCGJDMSEhISEhISQxr/H1aA8JlVgtCdAAAAAElFTkSuQmCC", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "timesfm_result = result.sort_values(\"forecast_timestamp\")[[\"forecast_timestamp\", \"forecast_value\"]]\n", - "timesfm_result = timesfm_result.rename(columns={\n", - " \"forecast_timestamp\": \"trip_hour\",\n", - " \"forecast_value\": \"timesfm_forecast\"\n", - "})\n", - "arimaplus_result = predictions.sort_values(\"forecast_timestamp\")[[\"forecast_timestamp\", \"forecast_value\"]]\n", - "arimaplus_result = arimaplus_result.rename(columns={\n", - " \"forecast_timestamp\": \"trip_hour\",\n", - " \"forecast_value\": \"arimaplus_forecast\"\n", - "})\n", - "df_all = df_grouped.merge(timesfm_result, on=\"trip_hour\", how=\"left\")\n", - "df_all = df_all.merge(arimaplus_result, on=\"trip_hour\", how=\"left\")\n", - "df_all.tail(672).plot.line(\n", - " x=\"trip_hour\",\n", - " y=[\"num_trips\", \"timesfm_forecast\", \"arimaplus_forecast\"],\n", - " rot=45,\n", - " title=\"Trip Forecasts Comparison\"\n", - ")" - ] - }, - { - "cell_type": "markdown", - "id": "015804c3", - "metadata": {}, - "source": [ - "### 5. Multiple Time Series Forecasting\n", - "\n", - "This section demonstrates a more advanced capability of ARIMAPlus: forecasting multiple time series simultaneously. This is useful when you have several independent series that you want to model together, such as trip counts from different bikeshare stations. The `id_col` parameter is key here, as it is used to differentiate between the individual time series." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "6dbe6c48", - "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/usr/local/google/home/shuowei/src/python-bigquery-dataframes/bigframes/core/log_adapter.py:182: TimeTravelCacheWarning: Reading cached table from 2025-12-12 23:04:48.874384+00:00 to avoid\n", - "incompatibilies with previous reads of this table. To read the latest\n", - "version, set `use_cache=False` or close the current session with\n", - "Session.close() or bigframes.pandas.close_session().\n", - " return method(*args, **kwargs)\n" - ] - }, - { - "data": { - "text/html": [ - "✅ Completed. \n", - " Query processed 69.8 MB in a moment of slot time.\n", - " " - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Number of stations: 41\n" - ] - }, - { - "data": { - "text/html": [ - "✅ Completed. \n", - " Query processed 69.8 MB in a moment of slot time.\n", - " " - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "✅ Completed. \n", - " Query processed 69.8 MB in a moment of slot time.\n", - " " - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Date range: 2013-08-29 to 2018-04-30\n" - ] - }, - { - "data": { - "text/html": [ - "\n", - " Query processed 18.8 MB in 2 minutes of slot time. [Job bigframes-dev:US.74ada07a-98ad-4d03-90bb-2b98f1d8b558 details]\n", - " " - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "✅ Completed. \n", - " Query processed 1.4 MB in 4 seconds of slot time. [Job bigframes-dev:US.a292f715-1d9c-406d-a7d5-f99b2ba71660 details]\n", - " " - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "✅ Completed. \n", - " Query processed 4.6 kB in a moment of slot time.\n", - " " - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "✅ Completed. \n", - " Query processed 11.5 kB in a moment of slot time.\n", - " " - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "✅ Completed. \n", - " Query processed 0 Bytes in a moment of slot time.\n", - " " - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "00fc1edbf6fd40dfb949a3e3a30b6c3e", - "version_major": 2, - "version_minor": 1 - }, - "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", - "
forecast_timestampstart_station_nameforecast_valuestandard_errorconfidence_levelprediction_interval_lower_boundprediction_interval_upper_boundconfidence_interval_lower_boundconfidence_interval_upper_bound
02016-09-01 00:00:00+00:00Beale at Market27.9114173.4224340.9521.21556834.60726521.21556834.607265
12016-09-01 00:00:00+00:00Civic Center BART (7th at Market)17.094554.2662870.958.7477425.4413618.7477425.441361
22016-09-01 00:00:00+00:00Embarcadero at Bryant22.3436483.3937020.9515.70401228.98328415.70401228.983284
32016-09-01 00:00:00+00:00Embarcadero at Folsom28.253293.3821580.9521.6362434.87033921.6362434.870339
42016-09-01 00:00:00+00:00Embarcadero at Sansome52.5380836.2692910.9540.27247764.80368940.27247764.803689
52016-09-01 00:00:00+00:00Embarcadero at Vallejo16.5132332.9536830.9510.73447622.2919910.73447622.29199
62016-09-01 00:00:00+00:00Market at 10th34.0512746.2057980.9521.9098946.19265821.9098946.192658
72016-09-01 00:00:00+00:00Market at 4th25.7460294.0015920.9517.91708233.57497717.91708233.574977
82016-09-01 00:00:00+00:00Market at Sansome46.1343685.0718520.9536.21150356.05723336.21150356.057233
92016-09-01 00:00:00+00:00Mechanics Plaza (Market at Battery)23.2699413.1946750.9517.01969229.52018917.01969229.520189
\n", - "

10 rows × 9 columns

\n", - "
[123 rows x 9 columns in total]" - ], - "text/plain": [ - " forecast_timestamp start_station_name \\\n", - "0 2016-09-01 00:00:00+00:00 Beale at Market \n", - "1 2016-09-01 00:00:00+00:00 Civic Center BART (7th at Market) \n", - "2 2016-09-01 00:00:00+00:00 Embarcadero at Bryant \n", - "3 2016-09-01 00:00:00+00:00 Embarcadero at Folsom \n", - "4 2016-09-01 00:00:00+00:00 Embarcadero at Sansome \n", - "5 2016-09-01 00:00:00+00:00 Embarcadero at Vallejo \n", - "6 2016-09-01 00:00:00+00:00 Market at 10th \n", - "7 2016-09-01 00:00:00+00:00 Market at 4th \n", - "8 2016-09-01 00:00:00+00:00 Market at Sansome \n", - "9 2016-09-01 00:00:00+00:00 Mechanics Plaza (Market at Battery) \n", - "\n", - " forecast_value standard_error confidence_level \\\n", - "0 27.911417 3.422434 0.95 \n", - "1 17.09455 4.266287 0.95 \n", - "2 22.343648 3.393702 0.95 \n", - "3 28.25329 3.382158 0.95 \n", - "4 52.538083 6.269291 0.95 \n", - "5 16.513233 2.953683 0.95 \n", - "6 34.051274 6.205798 0.95 \n", - "7 25.746029 4.001592 0.95 \n", - "8 46.134368 5.071852 0.95 \n", - "9 23.269941 3.194675 0.95 \n", - "\n", - " prediction_interval_lower_bound prediction_interval_upper_bound \\\n", - "0 21.215568 34.607265 \n", - "1 8.74774 25.441361 \n", - "2 15.704012 28.983284 \n", - "3 21.63624 34.870339 \n", - "4 40.272477 64.803689 \n", - "5 10.734476 22.29199 \n", - "6 21.90989 46.192658 \n", - "7 17.917082 33.574977 \n", - "8 36.211503 56.057233 \n", - "9 17.019692 29.520189 \n", - "\n", - " confidence_interval_lower_bound confidence_interval_upper_bound \n", - "0 21.215568 34.607265 \n", - "1 8.74774 25.441361 \n", - "2 15.704012 28.983284 \n", - "3 21.63624 34.870339 \n", - "4 40.272477 64.803689 \n", - "5 10.734476 22.29199 \n", - "6 21.90989 46.192658 \n", - "7 17.917082 33.574977 \n", - "8 36.211503 56.057233 \n", - "9 17.019692 29.520189 \n", - "...\n", - "\n", - "[123 rows x 9 columns]" - ] - }, - "execution_count": 11, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "df_multi = bpd.read_gbq(\"bigquery-public-data.san_francisco_bikeshare.bikeshare_trips\")\n", - "df_multi = df_multi[df_multi[\"start_station_name\"].str.contains(\"Market|Powell|Embarcadero\")]\n", - " \n", - "# Create daily aggregation\n", - "features = bpd.DataFrame({\n", - " \"start_station_name\": df_multi[\"start_station_name\"],\n", - " \"date\": df_multi[\"start_date\"].dt.date,\n", - "})\n", - "\n", - "# Group by station and date\n", - "num_trips = features.groupby(\n", - " [\"start_station_name\", \"date\"], as_index=False\n", - ").size()\n", - "# Rename the size column to \"num_trips\"\n", - "num_trips = num_trips.rename(columns={num_trips.columns[-1]: \"num_trips\"})\n", - "\n", - "# Check data quality\n", - "print(f\"Number of stations: {num_trips['start_station_name'].nunique()}\")\n", - "print(f\"Date range: {num_trips['date'].min()} to {num_trips['date'].max()}\")\n", - "\n", - "# Use daily frequency \n", - "model = forecasting.ARIMAPlus(\n", - " data_frequency=\"daily\",\n", - " horizon=30,\n", - " auto_arima_max_order=3,\n", - " min_time_series_length=10,\n", - " time_series_length_fraction=0.8\n", - ")\n", - "\n", - "model.fit(\n", - " num_trips[[\"date\"]],\n", - " num_trips[[\"num_trips\"]],\n", - " id_col=num_trips[[\"start_station_name\"]]\n", - ")\n", - "\n", - "predictions_multi = model.predict()\n", - "predictions_multi" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "venv (3.13.0)", - "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.13.0" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/notebooks/multimodal/multimodal_dataframe.ipynb b/notebooks/multimodal/multimodal_dataframe.ipynb deleted file mode 100644 index cd363db6f36..00000000000 --- a/notebooks/multimodal/multimodal_dataframe.ipynb +++ /dev/null @@ -1,1114 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "9edad7a6", - "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": "816ab253", - "metadata": { - "id": "YOrUAvz6DMw-" - }, - "source": [ - "# BigFrames Multimodal DataFrame\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", - "id": "77d821d4", - "metadata": {}, - "source": [ - "This notebook is introducing BigFrames Multimodal features:\n", - "1. Create Multimodal DataFrame\n", - "2. Combine unstructured data with structured data\n", - "3. Conduct image transformations\n", - "4. Use LLM models to ask questions and generate embeddings on images\n", - "5. PDF chunking function\n", - "6. Transcribe audio\n", - "7. Extract EXIF metadata from images" - ] - }, - { - "cell_type": "markdown", - "id": "75ab1c13", - "metadata": { - "id": "PEAJQQ6AFg-n" - }, - "source": [ - "## Setup" - ] - }, - { - "cell_type": "markdown", - "id": "750954c4", - "metadata": {}, - "source": [ - "Install the latest bigframes package if bigframes version < 2.4.0" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "2a6fafb1", - "metadata": {}, - "outputs": [], - "source": [ - "# !pip install bigframes --upgrade" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "df561d04", - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" - }, - "id": "bGyhLnfEeB0X", - "outputId": "83ac8b64-3f44-4d43-d089-28a5026cbb42" - }, - "outputs": [], - "source": [ - "PROJECT = \"bigframes-dev\" # replace with your project. \n", - "# Refer to https://cloud.google.com/bigquery/docs/multimodal-data-dataframes-tutorial#required_roles for your required permissions\n", - "\n", - "LOCATION = \"us\" # replace with your location.\n", - "\n", - "# Dataset where the UDF will be created.\n", - "DATASET_ID = \"bigframes_samples\" # replace with your dataset ID.\n", - "\n", - "OUTPUT_BUCKET = \"bigframes_blob_test\" # replace with your GCS bucket. \n", - "# The connection (or bigframes-default-connection of the project) must have read/write permission to the bucket. \n", - "# Refer to https://cloud.google.com/bigquery/docs/multimodal-data-dataframes-tutorial#grant-permissions for setting up connection service account permissions.\n", - "# In this Notebook it uses bigframes-default-connection by default. You can also bring in your own connections in each method.\n", - "\n", - "FULL_CONNECTION_ID = f\"{PROJECT}.{LOCATION}.bigframes-default-connection\"\n", - "\n", - "import bigframes\n", - "# Setup project\n", - "bigframes.options.bigquery.project = PROJECT\n", - "bigframes.options.bigquery.location = LOCATION\n", - "\n", - "# Display options\n", - "bigframes.options.display.blob_display_width = 300\n", - "bigframes.options.display.progress_bar = None\n", - "\n", - "import bigframes.pandas as bpd\n", - "import bigframes.bigquery as bbq" - ] - }, - { - "cell_type": "code", - "execution_count": 35, - "id": "35bd6e6e", - "metadata": {}, - "outputs": [], - "source": [ - "import bigframes.bigquery as bbq\n", - "\n", - "def get_runtime_json_str(series, mode=\"R\", with_metadata=False):\n", - " \"\"\"\n", - " Get the runtime (contains signed URL to access gcs data) and apply the\n", - " ToJSONSTring transformation.\n", - " \n", - " Args:\n", - " series: bigframes.series.Series to operate on.\n", - " mode: \"R\" for read, \"RW\" for read/write.\n", - " with_metadata: Whether to fetch and include blob metadata.\n", - " \"\"\"\n", - " # 1. Optionally fetch metadata\n", - " s = (\n", - " bbq.obj.fetch_metadata(series)\n", - " if with_metadata\n", - " else series\n", - " )\n", - " \n", - " # 2. Retrieve the access URL runtime object\n", - " runtime = bbq.obj.get_access_url(s, mode=mode)\n", - " \n", - " # 3. Convert the runtime object to a JSON string\n", - " return bbq.to_json_string(runtime)\n", - "\n", - "def get_metadata(series):\n", - " # Fetch metadata and extract GCS metadata from the details JSON field\n", - " metadata_obj = bbq.obj.fetch_metadata(series)\n", - " return bbq.json_query(metadata_obj.struct.field(\"details\"), \"$.gcs_metadata\")\n", - "\n", - "def get_content_type(series):\n", - " return bbq.json_value(get_metadata(series), \"$.content_type\")\n", - "\n", - "def get_size(series):\n", - " return bbq.json_value(get_metadata(series), \"$.size\").astype(\"Int64\")\n", - "\n", - "def get_updated(series):\n", - " return bpd.to_datetime(bbq.json_value(get_metadata(series), \"$.updated\").astype(\"Int64\"), unit=\"us\", utc=True)\n", - "\n", - "from IPython.display import HTML, display\n", - "\n", - "def render_images(df):\n", - " \"\"\"Helper to display BigFrames DataFrame with rendered image previews.\"\"\"\n", - " import bigframes.pandas as bpd\n", - " import bigframes.bigquery as bbq\n", - " import bigframes\n", - " from bigframes import dtypes\n", - " import json\n", - " \n", - " if isinstance(df, bpd.Series):\n", - " df = df.to_frame()\n", - " \n", - " # 1. Auto-detect columns holding ObjectRefs\n", - " object_cols = [\n", - " col for col, dtype in zip(df.columns, df.dtypes)\n", - " if dtype == dtypes.OBJ_REF_DTYPE\n", - " ]\n", - " \n", - " if not object_cols:\n", - " display(df)\n", - " return\n", - "\n", - " limit = bigframes.options.display.max_rows or 10\n", - " view_df = df.head(limit)\n", - " \n", - " # 2. Bulk-fetch access runtime URLs ONLY (disable with_metadata to bypass potential \n", - " # race conditions on new files where BigQuery may error before async writes finalize)\n", - " runtime_cols = {\n", - " col: get_runtime_json_str(view_df[col], mode=\"R\", with_metadata=False) \n", - " for col in object_cols\n", - " }\n", - " \n", - " pandas_json_df = bpd.DataFrame(runtime_cols).to_pandas()\n", - " final_pd = view_df.to_pandas()\n", - " \n", - " width = bigframes.options.display.blob_display_width or 300\n", - " IMAGE_EXTENSIONS = (\".png\", \".jpg\", \".jpeg\", \".gif\", \".webp\")\n", - " \n", - " def format_cell_html(raw_json):\n", - " if not raw_json:\n", - " return \"\"\n", - " try:\n", - " obj_rt = json.loads(raw_json)\n", - " \n", - " if \"access_urls\" not in obj_rt:\n", - " err = obj_rt.get(\"errors\", [{\"message\": \"URL Generation Failed\"}])[0].get(\"message\")\n", - " return f'Error: {err}'\n", - " \n", - " uri = obj_rt.get(\"objectref\", {}).get(\"uri\", \"\")\n", - " url = obj_rt[\"access_urls\"][\"read_url\"]\n", - " \n", - " # Safely infer type from extension to guarantee immediate display availability\n", - " if uri and str(uri).lower().endswith(IMAGE_EXTENSIONS):\n", - " return f''\n", - " \n", - " return f'{uri if uri else \"view\"}'\n", - " except:\n", - " return \"Format Error\"\n", - "\n", - " for col in object_cols:\n", - " final_pd[col] = pandas_json_df[col].map(format_cell_html)\n", - " \n", - " display(HTML(final_pd.to_html(escape=False)))" - ] - }, - { - "cell_type": "markdown", - "id": "be9ce892", - "metadata": { - "id": "ifKOq7VZGtZy" - }, - "source": [ - "To create a Multimodal DataFrame, you can use `bigframes.bigquery.obj.make_ref` on a series of URIs. You can get the URIs from a BigQuery table or by listing them from Cloud Storage.\n", - "\n", - "In this example, we use `gcsfs` to list the files from Cloud Storage, and then use `read_gbq` to load them into a BigQuery DataFrame before creating the object reference." - ] - }, - { - "cell_type": "code", - "execution_count": 36, - "id": "871d02f4", - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" - }, - "id": "fx6YcZJbeYru", - "outputId": "d707954a-0dd0-4c50-b7bf-36b140cf76cf" - }, - "outputs": [], - "source": [ - "import gcsfs\n", - "import bigframes.bigquery as bbq\n", - "\n", - "# List files using gcsfs (public bucket)\n", - "fs = gcsfs.GCSFileSystem(anon=True)\n", - "uris = fs.glob(\"gs://cloud-samples-data/bigquery/tutorials/cymbal-pets/images/*\")\n", - "\n", - "# Ensure URIs have gs:// prefix\n", - "uris = [u if u.startswith(\"gs://\") else f\"gs://{u}\" for u in uris]\n", - "\n", - "# Read the URIs into a BigQuery DataFrame using UNNEST\n", - "# We take the first 5 for this example\n", - "df_image = bpd.read_gbq(f\"SELECT uri FROM UNNEST({uris[:5]}) as uri\")\n", - "\n", - "# Create the object reference column\n", - "df_image['image'] = bbq.obj.make_ref(df_image['uri'], authorizer=FULL_CONNECTION_ID)\n", - "df_image = df_image[['image']]" - ] - }, - { - "cell_type": "code", - "execution_count": 37, - "id": "2e0436b0", - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 487 - }, - "id": "HhCb8jRsLe9B", - "outputId": "03081cf9-3a22-42c9-b38f-649f592fdada" - }, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/usr/local/google/home/shuowei/src/google-cloud-python/google-cloud-python/packages/bigframes/bigframes/dtypes.py:1044: JSONDtypeWarning: JSON columns will be represented as pandas.ArrowDtype(pyarrow.json_())\n", - "instead of using `db_dtypes` in the future when available in pandas\n", - "(https://github.com/pandas-dev/pandas/issues/60958) and pyarrow.\n", - " warnings.warn(msg, bigframes.exceptions.JSONDtypeWarning)\n" - ] - }, - { - "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", - "
image
0
1
2
3
4
" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "# Take only the 5 images to deal with. Preview the content of the Mutimodal DataFrame\n", - "df_image = df_image.head(5)\n", - "render_images(df_image)" - ] - }, - { - "cell_type": "markdown", - "id": "429b0117", - "metadata": { - "id": "b6RRZb3qPi_T" - }, - "source": [ - "### 2. Combine unstructured data with structured data" - ] - }, - { - "cell_type": "markdown", - "id": "991fa065", - "metadata": { - "id": "4YJCdmLtR-qu" - }, - "source": [ - "Now you can put more information into the table to describe the files. Such as author info from inputs, or other metadata from the gcs object itself." - ] - }, - { - "cell_type": "code", - "execution_count": 38, - "id": "08722ec5", - "metadata": { - "id": "YYYVn7NDH0Me" - }, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/usr/local/google/home/shuowei/src/google-cloud-python/google-cloud-python/packages/bigframes/bigframes/dtypes.py:1044: JSONDtypeWarning: JSON columns will be represented as pandas.ArrowDtype(pyarrow.json_())\n", - "instead of using `db_dtypes` in the future when available in pandas\n", - "(https://github.com/pandas-dev/pandas/issues/60958) and pyarrow.\n", - " warnings.warn(msg, bigframes.exceptions.JSONDtypeWarning)\n" - ] - }, - { - "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", - "
imageauthorcontent_typesizeupdated
0aliceimage/png7157662025-03-20 17:44:38+00:00
1bobimage/png11674062025-03-20 17:44:38+00:00
2bobimage/png11508922025-03-20 17:44:39+00:00
3aliceimage/png17365332025-03-20 17:44:39+00:00
4bobimage/png4397402025-03-20 17:44:39+00:00
" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "# Combine unstructured data with structured data\n", - "df_image = df_image.head(5)\n", - "df_image[\"author\"] = [\"alice\", \"bob\", \"bob\", \"alice\", \"bob\"] # type: ignore\n", - "df_image[\"content_type\"] = get_content_type(df_image[\"image\"])\n", - "df_image[\"size\"] = get_size(df_image[\"image\"])\n", - "df_image[\"updated\"] = get_updated(df_image[\"image\"])\n", - "render_images(df_image)" - ] - }, - { - "cell_type": "markdown", - "id": "f90826f6", - "metadata": {}, - "source": [ - "### 3. Conduct image transformations" - ] - }, - { - "cell_type": "markdown", - "id": "e24c9f8c", - "metadata": {}, - "source": [ - "This section demonstrates how to perform image transformations like blur, resize, and normalize using custom BigQuery Python UDFs and the `opencv-python` library." - ] - }, - { - "cell_type": "code", - "execution_count": 39, - "id": "db665049", - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 487 - }, - "id": "HhCb8jRsLe9B", - "outputId": "03081cf9-3a22-42c9-b38f-649f592fdada" - }, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/usr/local/google/home/shuowei/src/google-cloud-python/google-cloud-python/packages/bigframes/bigframes/pandas/__init__.py:211: PreviewWarning: udf is in preview.\n", - " return global_session.with_default_session(\n", - "/usr/local/google/home/shuowei/src/google-cloud-python/google-cloud-python/packages/bigframes/bigframes/dataframe.py:4695: FunctionAxisOnePreviewWarning: DataFrame.apply with parameter axis=1 scenario is in preview.\n", - " warnings.warn(msg, category=bfe.FunctionAxisOnePreviewWarning)\n", - "/usr/local/google/home/shuowei/src/google-cloud-python/google-cloud-python/packages/bigframes/bigframes/dtypes.py:1044: JSONDtypeWarning: JSON columns will be represented as pandas.ArrowDtype(pyarrow.json_())\n", - "instead of using `db_dtypes` in the future when available in pandas\n", - "(https://github.com/pandas-dev/pandas/issues/60958) and pyarrow.\n", - " warnings.warn(msg, bigframes.exceptions.JSONDtypeWarning)\n" - ] - }, - { - "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", - "
imageblurred
0
1
2
3
4
" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "# Construct the canonical connection ID\n", - "FULL_CONNECTION_ID = f\"{PROJECT}.{LOCATION}.bigframes-default-connection\"\n", - "\n", - "@bpd.udf(\n", - " input_types=[str, str, int, int],\n", - " output_type=str,\n", - " dataset=DATASET_ID,\n", - " name=\"image_blur_v2\",\n", - " bigquery_connection=FULL_CONNECTION_ID,\n", - " packages=[\"opencv-python-headless\", \"numpy\", \"requests\"],\n", - ")\n", - "def image_blur(src_rt: str, dst_rt: str, kx: int, ky: int) -> str:\n", - " import json\n", - " import cv2 as cv\n", - " import numpy as np\n", - " import requests\n", - " import base64\n", - "\n", - " src_obj = json.loads(src_rt)\n", - " if \"access_urls\" not in src_obj:\n", - " raise ValueError(f\"Missing 'access_urls' in source object. Response: {src_obj}\")\n", - " src_url = src_obj[\"access_urls\"][\"read_url\"]\n", - " \n", - " response = requests.get(src_url, timeout=30)\n", - " response.raise_for_status()\n", - " \n", - " img = cv.imdecode(np.frombuffer(response.content, np.uint8), cv.IMREAD_UNCHANGED)\n", - " if img is None:\n", - " raise ValueError(\"cv.imdecode failed\")\n", - " \n", - " kx, ky = int(kx), int(ky)\n", - " img_blurred = cv.blur(img, ksize=(kx, ky))\n", - " \n", - " success, encoded = cv.imencode(\".jpeg\", img_blurred)\n", - " if not success:\n", - " raise ValueError(\"cv.imencode failed\")\n", - " \n", - " # Handle two output modes\n", - " if dst_rt: # GCS/Series output mode\n", - " dst_obj = json.loads(dst_rt)\n", - " if \"access_urls\" not in dst_obj:\n", - " raise ValueError(f\"Missing 'access_urls' in destination object. Verify authorizer permissions. Response: {dst_obj}\")\n", - " dst_url = dst_obj[\"access_urls\"][\"write_url\"]\n", - " \n", - " requests.put(dst_url, data=encoded.tobytes(), headers={\"Content-Type\": \"image/jpeg\"}, timeout=30).raise_for_status()\n", - " \n", - " uri = dst_obj[\"objectref\"][\"uri\"]\n", - " return uri\n", - " \n", - " else: # BigQuery bytes output mode \n", - " image_bytes = encoded.tobytes()\n", - " return base64.b64encode(image_bytes).decode()\n", - "\n", - "def apply_transformation(series, dst_folder, udf, *args, verbose=False):\n", - " import os\n", - " dst_folder = os.path.join(dst_folder, \"\")\n", - " # Fetch metadata to get the URI\n", - " metadata = bbq.obj.fetch_metadata(series)\n", - " current_uri = metadata.struct.field(\"uri\")\n", - " dst_uri = current_uri.str.replace(r\"^.*\\/(.*)$\", rf\"{dst_folder}\\1\", regex=True)\n", - " \n", - " # To avoid synchronous 404 validation checks on files that don't exist yet, \n", - " # bypass the validator by explicitly constructing an objectref JSON.\n", - " dst_blob_df = bpd.DataFrame({\"uri\": dst_uri})\n", - " dst_blob_df[\"authorizer\"] = FULL_CONNECTION_ID\n", - " dst_blob = bbq.obj.make_ref(bbq.to_json(bbq.struct(dst_blob_df)))\n", - "\n", - " df_transform = bpd.DataFrame({\n", - " \"src_rt\": get_runtime_json_str(series, mode=\"R\"),\n", - " \"dst_rt\": get_runtime_json_str(dst_blob, mode=\"RW\"),\n", - " })\n", - " res = df_transform[[\"src_rt\", \"dst_rt\"]].apply(\n", - " udf, axis=1, args=args\n", - " )\n", - " \n", - " if verbose:\n", - " return res\n", - " \n", - " # Final return MUST also use JSON bypass to eliminate temporary 404 validation \n", - " # errors from embedded ObjectRefs during fused query execution pipelines.\n", - " res_df = bpd.DataFrame({\"uri\": res})\n", - " res_df[\"authorizer\"] = FULL_CONNECTION_ID\n", - " return bbq.obj.make_ref(bbq.to_json(bbq.struct(res_df)))\n", - "\n", - "# Apply transformations\n", - "df_image[\"blurred\"] = apply_transformation(\n", - " df_image[\"image\"], f\"gs://{OUTPUT_BUCKET}/image_blur_transformed/\",\n", - " image_blur, 20, 20\n", - ")\n", - "render_images(df_image[[\"image\", \"blurred\"]])" - ] - }, - { - "cell_type": "markdown", - "id": "11fcc6ec", - "metadata": { - "id": "Euk5saeVVdTP" - }, - "source": [ - "### 4. Use LLM models to ask questions and generate embeddings on images" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "793b2f45", - "metadata": { - "id": "mRUGfcaFVW-3" - }, - "outputs": [], - "source": [ - "from bigframes.ml import llm\n", - "gemini = llm.GeminiTextGenerator()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "13d7cb93", - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 657 - }, - "id": "DNFP7CbjWdR9", - "outputId": "3f90a062-0abc-4bce-f53c-db57b06a14b9" - }, - "outputs": [], - "source": [ - "# Ask the same question on the images\n", - "answer = gemini.predict(df_image, prompt=[\"what item is it?\", \"what color is the picture?\"])\n", - "render_images(answer[[\"ml_generate_text_llm_result\", \"image\"]])" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "68857305", - "metadata": { - "id": "IG3J3HsKhyBY" - }, - "outputs": [], - "source": [ - "# Ask different questions\n", - "df_image[\"question\"] = [\n", - " \"what item is it?\",\n", - " \"what color is the picture?\",\n", - " \"what is the product name?\",\n", - " \"is it for pets?\",\n", - " \"what is the weight of the product?\",\n", - "]" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "829afc69", - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 657 - }, - "id": "qKOb765IiVuD", - "outputId": "731bafad-ea29-463f-c8c1-cb7acfd70e5d" - }, - "outputs": [], - "source": [ - "answer_alt = gemini.predict(df_image, prompt=[df_image[\"question\"], df_image[\"image\"]])\n", - "render_images(answer_alt[[\"ml_generate_text_llm_result\", \"image\"]])" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "e75df430", - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 300 - }, - "id": "KATVv2CO5RT1", - "outputId": "6ec01f27-70b6-4f69-c545-e5e3c879480c" - }, - "outputs": [], - "source": [ - "# Generate embeddings.\n", - "embed_model = llm.MultimodalEmbeddingGenerator()\n", - "embeddings = embed_model.predict(df_image[\"image\"])\n", - "embeddings" - ] - }, - { - "cell_type": "markdown", - "id": "23892b0e", - "metadata": { - "id": "iRUi8AjG7cIf" - }, - "source": [ - "### 5. PDF extraction and chunking function\n", - "\n", - "This section demonstrates how to extract text and chunk text from PDF files using custom BigQuery Python UDFs and the `pypdf` library." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "136a18b8", - "metadata": {}, - "outputs": [], - "source": [ - "# Construct the canonical connection ID\n", - "FULL_CONNECTION_ID = f\"{PROJECT}.{LOCATION}.bigframes-default-connection\"\n", - "\n", - "@bpd.udf(\n", - " input_types=[str],\n", - " output_type=str,\n", - " dataset=DATASET_ID,\n", - " name=\"pdf_extract\",\n", - " bigquery_connection=FULL_CONNECTION_ID,\n", - " packages=[\"pypdf\", \"requests\", \"cryptography\"],\n", - ")\n", - "def pdf_extract(src_obj_ref_rt: str) -> str:\n", - " import io\n", - " import json\n", - " from pypdf import PdfReader\n", - " import requests\n", - " src_obj_ref_rt_json = json.loads(src_obj_ref_rt)\n", - " src_url = src_obj_ref_rt_json[\"access_urls\"][\"read_url\"]\n", - " response = requests.get(src_url, timeout=30, stream=True)\n", - " response.raise_for_status()\n", - " pdf_bytes = response.content\n", - " pdf_file = io.BytesIO(pdf_bytes)\n", - " reader = PdfReader(pdf_file, strict=False)\n", - " all_text = \"\"\n", - " for page in reader.pages:\n", - " page_extract_text = page.extract_text()\n", - " if page_extract_text:\n", - " all_text += page_extract_text\n", - " return all_text\n", - "\n", - "@bpd.udf(\n", - " input_types=[str, int, int],\n", - " output_type=list[str],\n", - " dataset=DATASET_ID,\n", - " name=\"pdf_chunk\",\n", - " bigquery_connection=FULL_CONNECTION_ID,\n", - " packages=[\"pypdf\", \"requests\", \"cryptography\"],\n", - ")\n", - "def pdf_chunk(src_obj_ref_rt: str, chunk_size: int, overlap_size: int) -> list[str]:\n", - " import io\n", - " import json\n", - " from pypdf import PdfReader\n", - " import requests\n", - " src_obj_ref_rt_json = json.loads(src_obj_ref_rt)\n", - " src_url = src_obj_ref_rt_json[\"access_urls\"][\"read_url\"]\n", - " response = requests.get(src_url, timeout=30, stream=True)\n", - " response.raise_for_status()\n", - " pdf_bytes = response.content\n", - " pdf_file = io.BytesIO(pdf_bytes)\n", - " reader = PdfReader(pdf_file, strict=False)\n", - " all_text_chunks = []\n", - " curr_chunk = \"\"\n", - " for page in reader.pages:\n", - " page_text = page.extract_text()\n", - " if page_text:\n", - " curr_chunk += page_text\n", - " while len(curr_chunk) >= chunk_size:\n", - " split_idx = curr_chunk.rfind(\" \", 0, chunk_size)\n", - " if split_idx == -1:\n", - " split_idx = chunk_size\n", - " actual_chunk = curr_chunk[:split_idx]\n", - " all_text_chunks.append(actual_chunk)\n", - " overlap = curr_chunk[split_idx + 1 : split_idx + 1 + overlap_size]\n", - " curr_chunk = overlap + curr_chunk[split_idx + 1 + overlap_size :]\n", - " if curr_chunk:\n", - " all_text_chunks.append(curr_chunk)\n", - " return all_text_chunks" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "234a5f86", - "metadata": {}, - "outputs": [], - "source": [ - "import gcsfs\n", - "import bigframes.bigquery as bbq\n", - "\n", - "# List files using gcsfs\n", - "fs = gcsfs.GCSFileSystem(anon=True)\n", - "uris = fs.glob(\"gs://cloud-samples-data/bigquery/tutorials/cymbal-pets/documents/*\")\n", - "\n", - "# Ensure URIs have gs:// prefix\n", - "uris = [u if u.startswith(\"gs://\") else f\"gs://{u}\" for u in uris]\n", - "\n", - "# Read the URIs into a BigQuery DataFrame\n", - "df_pdf = bpd.read_gbq(f\"SELECT uri FROM UNNEST({uris[:5]}) as uri\")\n", - "\n", - "# Create the object reference column\n", - "df_pdf['pdf'] = bbq.obj.make_ref(df_pdf['uri'], authorizer=FULL_CONNECTION_ID)\n", - "df_pdf = df_pdf[['pdf']]\n", - "\n", - "# Generate a JSON string containing the runtime information (including signed read URLs)\n", - "access_urls = get_runtime_json_str(df_pdf[\"pdf\"], mode=\"R\")\n", - "\n", - "# Apply PDF extraction\n", - "df_pdf[\"extracted_text\"] = access_urls.apply(pdf_extract)\n", - "\n", - "# Apply PDF chunking\n", - "df_pdf[\"chunked\"] = access_urls.apply(pdf_chunk, args=(2000, 200))\n", - "\n", - "df_pdf[[\"extracted_text\", \"chunked\"]]" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "d80effbe", - "metadata": {}, - "outputs": [], - "source": [ - "# Explode the chunks to see each chunk as a separate row\n", - "chunked = df_pdf[\"chunked\"].explode()\n", - "chunked" - ] - }, - { - "cell_type": "markdown", - "id": "118cf1c7", - "metadata": {}, - "source": [ - "### 6. Audio transcribe" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "1794c54f", - "metadata": {}, - "outputs": [], - "source": [ - "import gcsfs\n", - "import bigframes.bigquery as bbq\n", - "\n", - "audio_gcs_path = \"gs://bigframes_blob_test/audio/*\"\n", - "\n", - "# List files using gcsfs\n", - "fs = gcsfs.GCSFileSystem()\n", - "uris = fs.glob(audio_gcs_path)\n", - "\n", - "# Ensure URIs have gs:// prefix\n", - "uris = [u if u.startswith(\"gs://\") else f\"gs://{u}\" for u in uris]\n", - "\n", - "# Read the URIs into a BigQuery DataFrame\n", - "# If the bucket is empty or doesn't exist, this will result in an empty DataFrame\n", - "if not uris:\n", - " # Fallback to a dummy list or just let it be empty\n", - " uris = [\"gs://bigframes_blob_test/audio/dummy.mp3\"]\n", - "\n", - "df = bpd.read_gbq(f\"SELECT uri FROM UNNEST({uris[:5]}) as uri\")\n", - "\n", - "# Create the object reference column\n", - "df['audio'] = bbq.obj.make_ref(df['uri'], authorizer=FULL_CONNECTION_ID)\n", - "df = df[['audio']]" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "c9f9d484", - "metadata": {}, - "outputs": [], - "source": [ - "# The audio_transcribe function is a convenience wrapper around bigframes.bigquery.ai.generate.\n", - "# Here's how to perform the same operation directly:\n", - "\n", - "audio_series = df[\"audio\"]\n", - "prompt_text = (\n", - " \"**Task:** Transcribe the provided audio. **Instructions:** - Your response \"\n", - " \"must contain only the verbatim transcription of the audio. - Do not include \"\n", - " \"any introductory text, summaries, or conversational filler in your response. \"\n", - " \"The output should begin directly with the first word of the audio.\"\n", - ")\n", - "\n", - "# Convert the audio series to the runtime representation required by the model.\n", - "# This involves fetching metadata and getting a signed access URL.\n", - "audio_metadata = bbq.obj.fetch_metadata(audio_series)\n", - "audio_runtime = bbq.obj.get_access_url(audio_metadata, mode=\"R\")\n", - "\n", - "transcribed_results = bbq.ai.generate(\n", - " prompt=(prompt_text, audio_runtime),\n", - " endpoint=\"gemini-2.5-flash\",\n", - " model_params={\"generationConfig\": {\"temperature\": 0.0}},\n", - ")\n", - "\n", - "transcribed_series = transcribed_results.struct.field(\"result\").rename(\"transcribed_content\")\n", - "transcribed_series" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "7209a62a", - "metadata": {}, - "outputs": [], - "source": [ - "# To get verbose results (including status), we can extract both fields from the result struct.\n", - "transcribed_content_series = transcribed_results.struct.field(\"result\")\n", - "transcribed_status_series = transcribed_results.struct.field(\"status\")\n", - "\n", - "transcribed_series_verbose = bpd.DataFrame(\n", - " {\n", - " \"status\": transcribed_status_series,\n", - " \"content\": transcribed_content_series,\n", - " }\n", - ")\n", - "# Package as a struct for consistent display\n", - "transcribed_series_verbose = bbq.struct(transcribed_series_verbose).rename(\"transcription_results\")\n", - "transcribed_series_verbose" - ] - }, - { - "cell_type": "markdown", - "id": "c8351cc3", - "metadata": {}, - "source": [ - "### 7. Extract EXIF metadata from images" - ] - }, - { - "cell_type": "markdown", - "id": "e59670b9", - "metadata": {}, - "source": [ - "This section demonstrates how to extract EXIF metadata from images using a custom BigQuery Python UDF and the `Pillow` library." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "fda362f4", - "metadata": {}, - "outputs": [], - "source": [ - "# Construct the canonical connection ID\n", - "FULL_CONNECTION_ID = f\"{PROJECT}.{LOCATION}.bigframes-default-connection\"\n", - "\n", - "@bpd.udf(\n", - " input_types=[str],\n", - " output_type=str,\n", - " dataset=DATASET_ID,\n", - " name=\"extract_exif\",\n", - " bigquery_connection=FULL_CONNECTION_ID,\n", - " packages=[\"pillow\", \"requests\"],\n", - " max_batching_rows=8192,\n", - " container_cpu=0.33,\n", - " container_memory=\"512Mi\"\n", - ")\n", - "def extract_exif(src_obj_ref_rt: str) -> str:\n", - " import io\n", - " import json\n", - " from PIL import ExifTags, Image\n", - " import requests\n", - " src_obj_ref_rt_json = json.loads(src_obj_ref_rt)\n", - " src_url = src_obj_ref_rt_json[\"access_urls\"][\"read_url\"]\n", - " response = requests.get(src_url, timeout=30)\n", - " bts = response.content\n", - " image = Image.open(io.BytesIO(bts))\n", - " exif_data = image.getexif()\n", - " exif_dict = {}\n", - " if exif_data:\n", - " for tag, value in exif_data.items():\n", - " tag_name = ExifTags.TAGS.get(tag, tag)\n", - " exif_dict[tag_name] = value\n", - " return json.dumps(exif_dict)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "40bb6bc9", - "metadata": {}, - "outputs": [], - "source": [ - "import gcsfs\n", - "import bigframes.bigquery as bbq\n", - "\n", - "# Create a Multimodal DataFrame from the sample image URIs\n", - "fs = gcsfs.GCSFileSystem()\n", - "uris = fs.glob(\"gs://bigframes_blob_test/images_exif/*\")\n", - "\n", - "# Ensure URIs have gs:// prefix\n", - "uris = [u if u.startswith(\"gs://\") else f\"gs://{u}\" for u in uris]\n", - "\n", - "if not uris:\n", - " uris = [\"gs://bigframes_blob_test/images_exif/dummy.jpg\"]\n", - "\n", - "exif_image_df = bpd.read_gbq(f\"SELECT uri FROM UNNEST({uris[:5]}) as uri\")\n", - "exif_image_df['blob_col'] = bbq.obj.make_ref(exif_image_df['uri'], authorizer=FULL_CONNECTION_ID)\n", - "exif_image_df = exif_image_df[['blob_col']]\n", - "\n", - "# Generate a JSON string containing the runtime information (including signed read URLs)\n", - "# This allows the UDF to download the images from Google Cloud Storage\n", - "access_urls = get_runtime_json_str(exif_image_df[\"blob_col\"], mode=\"R\")\n", - "\n", - "# Apply the BigQuery Python UDF to the runtime JSON strings\n", - "# We cast to string to ensure the input matches the UDF's signature\n", - "exif_json = access_urls.astype(str).apply(extract_exif)\n", - "\n", - "# Parse the resulting JSON strings back into a structured JSON type for easier access\n", - "exif_data = bbq.parse_json(exif_json)\n", - "\n", - "exif_data" - ] - } - ], - "metadata": { - "colab": { - "provenance": [] - }, - "kernelspec": { - "display_name": "venv (3.13.0)", - "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.13.0" - } - }, - "nbformat": 4, - "nbformat_minor": 0 -} diff --git a/notebooks/regression/bq_dataframes_ml_linear_regression.ipynb b/notebooks/regression/bq_dataframes_ml_linear_regression.ipynb new file mode 100644 index 00000000000..675416f6ea8 --- /dev/null +++ b/notebooks/regression/bq_dataframes_ml_linear_regression.ipynb @@ -0,0 +1,743 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "ur8xi4C7S06n" + }, + "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": { + "id": "JAPoU8Sm5E6e" + }, + "source": [ + "## Train a linear regression model with BigQuery DataFrames ML\n", + "\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", + " \"Vertex\n", + " Open in Vertex AI Workbench\n", + " \n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "24743cf4a1e1" + }, + "source": [ + "**_NOTE_**: This notebook has been tested in the following environment:\n", + "\n", + "* Python version = 3.10" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "tvgnzT1CKxrO" + }, + "source": [ + "## Overview\n", + "\n", + "Use this notebook to learn how to train a linear regression model by using BigQuery DataFrames ML. BigQuery DataFrames ML provides a provides a scikit-learn-like API for ML powered by the BigQuery engine.\n", + "\n", + "This example is adapted from the [BQML linear regression tutorial](https://cloud.google.com/bigquery-ml/docs/linear-regression-tutorial).\n", + "\n", + "Learn more about [BigQuery DataFrames](https://cloud.google.com/python/docs/reference/bigframes/latest)." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "d975e698c9a4" + }, + "source": [ + "### Objective\n", + "\n", + "In this tutorial, you use BigQuery DataFrames to create a linear regression model that predicts the weight of an Adelie penguin based on the penguin's island of residence, culmen length and depth, flipper length, and sex.\n", + "\n", + "The steps include:\n", + "\n", + "- Creating a DataFrame from a BigQuery table.\n", + "- Cleaning and preparing data using pandas.\n", + "- Creating a linear regression model using `bigframes.ml`.\n", + "- Saving the ML model to BigQuery for future use." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "08d289fa873f" + }, + "source": [ + "### Dataset\n", + "\n", + "This tutorial uses the [```penguins``` table](https://console.cloud.google.com/bigquery?p=bigquery-public-data&d=ml_datasets&t=penguins) (a BigQuery Public Dataset) which includes data on a set of penguins including species, island of residence, weight, culmen length and depth, flipper length, and sex." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "aed92deeb4a0" + }, + "source": [ + "### Costs\n", + "\n", + "This tutorial uses billable components of Google Cloud:\n", + "\n", + "* BigQuery (compute)\n", + "* BigQuery ML\n", + "\n", + "Learn about [BigQuery compute pricing](https://cloud.google.com/bigquery/pricing#analysis_pricing_models)\n", + "and [BigQuery ML pricing](https://cloud.google.com/bigquery/pricing#bqml),\n", + "and use the [Pricing Calculator](https://cloud.google.com/products/calculator/)\n", + "to generate a cost estimate based on your projected usage." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "i7EUnXsZhAGF" + }, + "source": [ + "## Installation\n", + "\n", + "Install the following packages, which are required to run this notebook:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "9O0Ka4W2MNF3" + }, + "outputs": [], + "source": [ + "!pip install bigframes" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "58707a750154" + }, + "source": [ + "### Colab only\n", + "\n", + "Uncomment and run the following cell to restart the kernel:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "f200f10a1da3" + }, + "outputs": [], + "source": [ + "# Automatically restart kernel after installs so that your environment can access the new packages\n", + "# import IPython\n", + "\n", + "# app = IPython.Application.instance()\n", + "# app.kernel.do_shutdown(True)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "BF1j6f9HApxa" + }, + "source": [ + "## Before you begin\n", + "\n", + "Complete the tasks in this section to set up your environment." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "oDfTjfACBvJk" + }, + "source": [ + "### Set up your Google Cloud project\n", + "\n", + "**The following steps are required, regardless of your notebook environment.**\n", + "\n", + "1. [Select or create a Google Cloud project](https://console.cloud.google.com/cloud-resource-manager). When you first create an account, you get a $300 credit towards your compute/storage costs.\n", + "\n", + "2. [Make sure that billing is enabled for your project](https://cloud.google.com/billing/docs/how-to/modify-project).\n", + "\n", + "3. [Enable the BigQuery API](https://console.cloud.google.com/flows/enableapi?apiid=bigquery.googleapis.com).\n", + "\n", + "4. If you are running this notebook locally, install the [Cloud SDK](https://cloud.google.com/sdk)." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "WReHDGG5g0XY" + }, + "source": [ + "#### Set your project ID\n", + "\n", + "If you don't know your project ID, try the following:\n", + "* Run `gcloud config list`.\n", + "* Run `gcloud projects list`.\n", + "* See the support page: [Locate the project ID](https://support.google.com/googleapi/answer/7014113)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "oM1iC_MfAts1" + }, + "outputs": [], + "source": [ + "PROJECT_ID = \"\" # @param {type:\"string\"}\n", + "\n", + "# Set the project id\n", + "! gcloud config set project {PROJECT_ID}" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "region" + }, + "source": [ + "#### Set the region\n", + "\n", + "You can also change the `REGION` variable used by BigQuery. Learn more about [BigQuery regions](https://cloud.google.com/bigquery/docs/locations#supported_locations)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "eF-Twtc4XGem" + }, + "outputs": [], + "source": [ + "REGION = \"US\" # @param {type: \"string\"}" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "sBCra4QMA2wR" + }, + "source": [ + "### Authenticate your Google Cloud account\n", + "\n", + "Depending on your Jupyter environment, you might have to manually authenticate. Follow the relevant instructions below." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "74ccc9e52986" + }, + "source": [ + "**Vertex AI Workbench**\n", + "\n", + "Do nothing, you are already authenticated." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "de775a3773ba" + }, + "source": [ + "**Local JupyterLab instance**\n", + "\n", + "Uncomment and run the following cell:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "254614fa0c46" + }, + "outputs": [], + "source": [ + "# ! gcloud auth login" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "ef21552ccea8" + }, + "source": [ + "**Colab**\n", + "\n", + "Uncomment and run the following cell:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "603adbbf0532" + }, + "outputs": [], + "source": [ + "# from google.colab import auth\n", + "# auth.authenticate_user()" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "960505627ddf" + }, + "source": [ + "### Import libraries" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "PyQmSRbKA8r-" + }, + "outputs": [], + "source": [ + "import bigframes.pandas as bf" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "init_aip:mbsdk,all" + }, + "source": [ + "### Set BigQuery DataFrames options" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "NPPMuw2PXGeo" + }, + "outputs": [], + "source": [ + "bf.options.bigquery.project = PROJECT_ID\n", + "bf.options.bigquery.location = REGION" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "D21CoOlfFTYI" + }, + "source": [ + "If you want to reset the location of the created DataFrame or Series objects, reset the session by executing `bf.close_session()`. After that, you can reuse `bf.options.bigquery.location` to specify another location." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "9EMAqR37AfLS" + }, + "source": [ + "## Read a BigQuery table into a BigQuery DataFrames DataFrame\n", + "\n", + "Read the [```penguins``` table](https://console.cloud.google.com/bigquery?p=bigquery-public-data&d=ml_datasets&t=penguins) into a BigQuery DataFrames DataFrame:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "EDAaIwHpQCDZ" + }, + "outputs": [], + "source": [ + "df = bf.read_gbq(\"bigquery-public-data.ml_datasets.penguins\")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "DJu837YEXD7B" + }, + "source": [ + "Take a look at the DataFrame:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "_gPD0Zn1Stdb" + }, + "outputs": [], + "source": [ + "df.head()" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "rwPLjqW2Ajzh" + }, + "source": [ + "## Clean and prepare data\n", + "\n", + "You can use pandas as you normally would on the BigQuery DataFrames DataFrame, but calculations happen in the BigQuery query engine instead of your local environment.\n", + "\n", + "Because this model will focus on the Adelie Penguin species, you need to filter the data for only those rows representing Adelie penguins. Then you drop the `species` column because it is no longer needed.\n", + "\n", + "As these functions are applied, only the new DataFrame object `adelie_data` is modified. The source table and the original DataFrame object `df` don't change." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "6i6HkFJZa8na" + }, + "outputs": [], + "source": [ + "# Filter down to the data to the Adelie Penguin species\n", + "adelie_data = df[df.species == \"Adelie Penguin (Pygoscelis adeliae)\"]\n", + "\n", + "# Drop the species column\n", + "adelie_data = adelie_data.drop(columns=[\"species\"])\n", + "\n", + "# Take a look at the filtered DataFrame\n", + "adelie_data" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "jhK2OlyMbY4L" + }, + "source": [ + "Drop rows with `NULL` values in order to create a BigQuery DataFrames DataFrame for the training data:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "0am3hdlXZfxZ" + }, + "outputs": [], + "source": [ + "# Drop rows with nulls to get training data\n", + "training_data = adelie_data.dropna()\n", + "\n", + "# Take a peek at the training data\n", + "training_data" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "M_-0X7NxYK5f" + }, + "source": [ + "Specify your feature (or input) columns and the label (or output) column:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "YKwCW7Nsavap" + }, + "outputs": [], + "source": [ + "feature_columns = training_data[['island', 'culmen_length_mm', 'culmen_depth_mm', 'flipper_length_mm', 'sex']]\n", + "label_columns = training_data[['body_mass_g']]" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "CjyM7vZJZ0sQ" + }, + "source": [ + "There is a row within the `adelie_data` BigQuery DataFrames DataFrame that has a `NULL` value for the `body mass` column. `body mass` is the label column, which is the value that the model you are creating is trying to predict.\n", + "\n", + "Create a new BigQuery DataFrames DataFrame, `test_data`, for this row so that you can use it as test data on which to make a prediction later:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "wej78IDUaRW9" + }, + "outputs": [], + "source": [ + "test_data = adelie_data[adelie_data.body_mass_g.isnull()]\n", + "\n", + "test_data" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "Fx4lsNqMorJ-" + }, + "source": [ + "## Create the linear regression model\n", + "\n", + "BigQuery DataFrames ML lets you move from exploring data to creating machine learning models through its scikit-learn-like API, `bigframes.ml`. BigQuery DataFrames ML supports several types of [ML models](https://cloud.google.com/python/docs/reference/bigframes/latest#ml-capabilities).\n", + "\n", + "In this notebook, you create a linear regression model, a type of regression model that generates a continuous value from a linear combination of input features.\n", + "\n", + "When you create a model with BigQuery DataFrames ML, it is saved locally and limited to the BigQuery session. However, as you'll see in the next section, you can use `to_gbq` to save the model permanently to your BigQuery project." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "EloGtMnverFF" + }, + "source": [ + "### Create the model using `bigframes.ml`\n", + "\n", + "When you pass the feature columns without transforms, BigQuery ML uses\n", + "[automatic preprocessing](https://cloud.google.com/bigquery/docs/auto-preprocessing) to encode string values and scale numeric values.\n", + "\n", + "BigQuery ML also [automatically splits the data for training and evaluation](https://cloud.google.com/bigquery/docs/reference/standard-sql/bigqueryml-syntax-create-glm#data_split_method), although for datasets with less than 500 rows (such as this one), all rows are used for training." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "GskyyUQPowBT" + }, + "outputs": [], + "source": [ + "from bigframes.ml.linear_model import LinearRegression\n", + "\n", + "model = LinearRegression()\n", + "\n", + "model.fit(feature_columns, label_columns)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "UGjeMPC2caKK" + }, + "source": [ + "### Score the model\n", + "\n", + "Check how the model performed by using the `score` method. More information on model scoring can be found [here](https://cloud.google.com/bigquery/docs/reference/standard-sql/bigqueryml-syntax-evaluate#mlevaluate_output)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "kGBJKafpo0dl" + }, + "outputs": [], + "source": [ + "model.score(feature_columns, label_columns)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "P2lUiZZ_cjri" + }, + "source": [ + "### Predict using the model\n", + "\n", + "Use the model to predict the body mass of the data row you saved earlier to the `test_data` DataFrame:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "bsQ9cmoWo0Ps" + }, + "outputs": [], + "source": [ + "model.predict(test_data)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "GTRdUw-Ro5R1" + }, + "source": [ + "## Save the model in BigQuery\n", + "\n", + "The model is saved locally within this session. You can save the model permanently to BigQuery for use in future sessions, and to make the model sharable with others." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "K0mPaoGpcwwy" + }, + "source": [ + "Create a BigQuery dataset to house the model, adding a name for your dataset as the `DATASET_ID` variable:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "ZSP7gt13QrQt" + }, + "outputs": [], + "source": [ + "DATASET_ID = \"\" # @param {type:\"string\"}\n", + "\n", + "from google.cloud import bigquery\n", + "client = bigquery.Client(project=PROJECT_ID)\n", + "dataset = bigquery.Dataset(PROJECT_ID + \".\" + DATASET_ID)\n", + "dataset.location = REGION\n", + "dataset = client.create_dataset(dataset, exists_ok=True)\n", + "print(f\"Dataset {dataset.dataset_id} created.\")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "zqAIWWgJczp-" + }, + "source": [ + "Save the model using the `to_gbq` method:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "QE_GD4Byo_jb" + }, + "outputs": [], + "source": [ + "model.to_gbq(DATASET_ID + \".penguin_weight\" , replace=True)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "f7uHacAy49rT" + }, + "source": [ + "You can view the saved model in the BigQuery console under the dataset you created in the first step. Run the following cell and follow the link to view your BigQuery console:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "qDBoiA_0488Z" + }, + "outputs": [], + "source": [ + "print(f'https://console.developers.google.com/bigquery?p={PROJECT_ID}')" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "G_wjSfXpWTuy" + }, + "source": [ + "# Summary and next steps\n", + "\n", + "You've created a linear regression model using `bigframes.ml`.\n", + "\n", + "Learn more about BigQuery DataFrames in the [documentation](https://cloud.google.com/python/docs/reference/bigframes/latest) and find more sample notebooks in the [GitHub repo](https://github.com/googleapis/python-bigquery-dataframes/tree/main/notebooks)." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "TpV-iwP9qw9c" + }, + "source": [ + "## Cleaning up\n", + "\n", + "To clean up all Google Cloud resources used in this project, you can [delete the Google Cloud\n", + "project](https://cloud.google.com/resource-manager/docs/creating-managing-projects#shutting_down_projects) you used for the tutorial.\n", + "\n", + "Otherwise, you can uncomment the remaining cells and run them to delete the individual resources you created in this tutorial:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "sx_vKniMq9ZX" + }, + "outputs": [], + "source": [ + "# # Delete the BigQuery dataset and associated ML model\n", + "# from google.cloud import bigquery\n", + "# client = bigquery.Client(project=PROJECT_ID)\n", + "# client.delete_dataset(\n", + "# DATASET_ID, delete_contents=True, not_found_ok=True\n", + "# )\n", + "# print(\"Deleted dataset '{}'.\".format(DATASET_ID))" + ] + } + ], + "metadata": { + "colab": { + "provenance": [], + "toc_visible": true + }, + "kernelspec": { + "display_name": "Python 3", + "name": "python3" + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} diff --git a/notebooks/regression/easy_linear_regression.ipynb b/notebooks/regression/easy_linear_regression.ipynb new file mode 100644 index 00000000000..c441a966ecf --- /dev/null +++ b/notebooks/regression/easy_linear_regression.ipynb @@ -0,0 +1,1270 @@ +{ + "cells": [ + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Using ML - Easy linear regression\n", + "\n", + "This demo shows BigQuery DataFrames ML providing an SKLearn-like experience for\n", + "training a linear regression model.\n", + "\n", + "In this \"easy\" version of linear regression, we use a couple of BQML features to simplify our code:\n", + "\n", + "- We rely on automatic preprocessing to encode string values and scale numeric values\n", + "- We rely on automatic data split & evaluation to test the model\n", + "\n", + "This example is adapted from the [BQML linear regression tutorial](https://cloud.google.com/bigquery-ml/docs/linear-regression-tutorial)." + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 1. Init & load data" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "ffc6d6c7815a4a92903a08a11af6db11", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "HTML(value='Query job d1e085ba-66d8-4631-bb51-50a17d0a6e51 is RUNNING. \n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
predicted_body_mass_g
2923459.735118
\n", + "

1 rows × 1 columns

\n", + "[1 rows x 1 columns in total]" + ], + "text/plain": [ + " predicted_body_mass_g\n", + "292 3459.735118\n", + "\n", + "[1 rows x 1 columns]" + ] + }, + "execution_count": 25, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# use the model to predict the missing labels\n", + "model.predict(missing_body_mass)" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 4. Save in BigQuery" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "256ff43296a9405f890e78511acc38e5", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "HTML(value='Copy job 1a273ccd-212a-4750-a3c1-615256af6d48 is RUNNING.
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
speciesislandculmen_length_mmculmen_depth_mmflipper_length_mmbody_mass_gsex
0Adelie Penguin (Pygoscelis adeliae)Dream36.618.4184.03475.0FEMALE
1Adelie Penguin (Pygoscelis adeliae)Dream39.819.1184.04650.0MALE
2Adelie Penguin (Pygoscelis adeliae)Dream40.918.9184.03900.0MALE
3Chinstrap penguin (Pygoscelis antarctica)Dream46.517.9192.03500.0FEMALE
4Adelie Penguin (Pygoscelis adeliae)Dream37.316.8192.03000.0FEMALE
5Adelie Penguin (Pygoscelis adeliae)Dream43.218.5192.04100.0MALE
6Chinstrap penguin (Pygoscelis antarctica)Dream46.916.6192.02700.0FEMALE
7Chinstrap penguin (Pygoscelis antarctica)Dream50.518.4200.03400.0FEMALE
8Chinstrap penguin (Pygoscelis antarctica)Dream49.519.0200.03800.0MALE
9Adelie Penguin (Pygoscelis adeliae)Dream40.220.1200.03975.0MALE
10Adelie Penguin (Pygoscelis adeliae)Dream40.818.9208.04300.0MALE
11Adelie Penguin (Pygoscelis adeliae)Dream39.018.7185.03650.0MALE
12Adelie Penguin (Pygoscelis adeliae)Dream37.016.9185.03000.0FEMALE
13Chinstrap penguin (Pygoscelis antarctica)Dream47.017.3185.03700.0FEMALE
14Adelie Penguin (Pygoscelis adeliae)Dream34.017.1185.03400.0FEMALE
15Adelie Penguin (Pygoscelis adeliae)Dream37.016.5185.03400.0FEMALE
16Chinstrap penguin (Pygoscelis antarctica)Dream45.717.3193.03600.0FEMALE
17Chinstrap penguin (Pygoscelis antarctica)Dream50.619.4193.03800.0MALE
18Adelie Penguin (Pygoscelis adeliae)Dream39.717.9193.04250.0MALE
19Adelie Penguin (Pygoscelis adeliae)Dream37.818.1193.03750.0MALE
20Chinstrap penguin (Pygoscelis antarctica)Dream46.617.8193.03800.0FEMALE
21Chinstrap penguin (Pygoscelis antarctica)Dream51.319.2193.03650.0MALE
22Adelie Penguin (Pygoscelis adeliae)Dream40.217.1193.03400.0FEMALE
23Adelie Penguin (Pygoscelis adeliae)Dream36.818.5193.03500.0FEMALE
24Chinstrap penguin (Pygoscelis antarctica)Dream49.618.2193.03775.0MALE
\n", + "

25 rows × 7 columns

\n", + "[344 rows x 7 columns in total]" + ], + "text/plain": [ + " species island culmen_length_mm \\\n", + "0 Adelie Penguin (Pygoscelis adeliae) Dream 36.6 \n", + "1 Adelie Penguin (Pygoscelis adeliae) Dream 39.8 \n", + "2 Adelie Penguin (Pygoscelis adeliae) Dream 40.9 \n", + "3 Chinstrap penguin (Pygoscelis antarctica) Dream 46.5 \n", + "4 Adelie Penguin (Pygoscelis adeliae) Dream 37.3 \n", + "5 Adelie Penguin (Pygoscelis adeliae) Dream 43.2 \n", + "6 Chinstrap penguin (Pygoscelis antarctica) Dream 46.9 \n", + "7 Chinstrap penguin (Pygoscelis antarctica) Dream 50.5 \n", + "8 Chinstrap penguin (Pygoscelis antarctica) Dream 49.5 \n", + "9 Adelie Penguin (Pygoscelis adeliae) Dream 40.2 \n", + "10 Adelie Penguin (Pygoscelis adeliae) Dream 40.8 \n", + "11 Adelie Penguin (Pygoscelis adeliae) Dream 39.0 \n", + "12 Adelie Penguin (Pygoscelis adeliae) Dream 37.0 \n", + "13 Chinstrap penguin (Pygoscelis antarctica) Dream 47.0 \n", + "14 Adelie Penguin (Pygoscelis adeliae) Dream 34.0 \n", + "15 Adelie Penguin (Pygoscelis adeliae) Dream 37.0 \n", + "16 Chinstrap penguin (Pygoscelis antarctica) Dream 45.7 \n", + "17 Chinstrap penguin (Pygoscelis antarctica) Dream 50.6 \n", + "18 Adelie Penguin (Pygoscelis adeliae) Dream 39.7 \n", + "19 Adelie Penguin (Pygoscelis adeliae) Dream 37.8 \n", + "20 Chinstrap penguin (Pygoscelis antarctica) Dream 46.6 \n", + "21 Chinstrap penguin (Pygoscelis antarctica) Dream 51.3 \n", + "22 Adelie Penguin (Pygoscelis adeliae) Dream 40.2 \n", + "23 Adelie Penguin (Pygoscelis adeliae) Dream 36.8 \n", + "24 Chinstrap penguin (Pygoscelis antarctica) Dream 49.6 \n", + "\n", + " culmen_depth_mm flipper_length_mm body_mass_g sex \n", + "0 18.4 184.0 3475.0 FEMALE \n", + "1 19.1 184.0 4650.0 MALE \n", + "2 18.9 184.0 3900.0 MALE \n", + "3 17.9 192.0 3500.0 FEMALE \n", + "4 16.8 192.0 3000.0 FEMALE \n", + "5 18.5 192.0 4100.0 MALE \n", + "6 16.6 192.0 2700.0 FEMALE \n", + "7 18.4 200.0 3400.0 FEMALE \n", + "8 19.0 200.0 3800.0 MALE \n", + "9 20.1 200.0 3975.0 MALE \n", + "10 18.9 208.0 4300.0 MALE \n", + "11 18.7 185.0 3650.0 MALE \n", + "12 16.9 185.0 3000.0 FEMALE \n", + "13 17.3 185.0 3700.0 FEMALE \n", + "14 17.1 185.0 3400.0 FEMALE \n", + "15 16.5 185.0 3400.0 FEMALE \n", + "16 17.3 193.0 3600.0 FEMALE \n", + "17 19.4 193.0 3800.0 MALE \n", + "18 17.9 193.0 4250.0 MALE \n", + "19 18.1 193.0 3750.0 MALE \n", + "20 17.8 193.0 3800.0 FEMALE \n", + "21 19.2 193.0 3650.0 MALE \n", + "22 17.1 193.0 3400.0 FEMALE \n", + "23 18.5 193.0 3500.0 FEMALE \n", + "24 18.2 193.0 3775.0 MALE \n", + "...\n", + "\n", + "[344 rows x 7 columns]" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Initialize BigQuery DataFrame\n", + "import bigframes.pandas\n", + "\n", + "# read a BigQuery table to a BigQuery DataFrame\n", + "df = bigframes.pandas.read_gbq(\"bigframes-dev.bqml_tutorial.penguins\")\n", + "\n", + "# take a peek at the dataframe\n", + "df" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 2. Data cleaning / prep" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "a9ad907fa6e64a61a9dce420bc7d2beb", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "HTML(value='Query job 3537a10a-641a-4d40-ae47-449c641b1bc5 is DONE. 28.9 kB processed.
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
islandculmen_length_mmculmen_depth_mmflipper_length_mmbody_mass_gsex
0Dream36.618.4184.03475.0FEMALE
1Dream39.819.1184.04650.0MALE
2Dream40.918.9184.03900.0MALE
4Dream37.316.8192.03000.0FEMALE
5Dream43.218.5192.04100.0MALE
9Dream40.220.1200.03975.0MALE
10Dream40.818.9208.04300.0MALE
11Dream39.018.7185.03650.0MALE
12Dream37.016.9185.03000.0FEMALE
14Dream34.017.1185.03400.0FEMALE
15Dream37.016.5185.03400.0FEMALE
18Dream39.717.9193.04250.0MALE
19Dream37.818.1193.03750.0MALE
22Dream40.217.1193.03400.0FEMALE
23Dream36.818.5193.03500.0FEMALE
26Dream41.518.5201.04000.0MALE
31Dream33.116.1178.02900.0FEMALE
32Dream37.218.1178.03900.0MALE
33Dream39.516.7178.03250.0FEMALE
35Dream36.018.5186.03100.0FEMALE
36Dream39.618.1186.04450.0MALE
38Dream41.320.3194.03550.0MALE
41Dream35.718.0202.03550.0FEMALE
51Dream38.117.6187.03425.0FEMALE
53Dream36.017.1187.03700.0FEMALE
\n", + "

25 rows × 6 columns

\n", + "[146 rows x 6 columns in total]" + ], + "text/plain": [ + " island culmen_length_mm culmen_depth_mm flipper_length_mm body_mass_g \\\n", + "0 Dream 36.6 18.4 184.0 3475.0 \n", + "1 Dream 39.8 19.1 184.0 4650.0 \n", + "2 Dream 40.9 18.9 184.0 3900.0 \n", + "4 Dream 37.3 16.8 192.0 3000.0 \n", + "5 Dream 43.2 18.5 192.0 4100.0 \n", + "9 Dream 40.2 20.1 200.0 3975.0 \n", + "10 Dream 40.8 18.9 208.0 4300.0 \n", + "11 Dream 39.0 18.7 185.0 3650.0 \n", + "12 Dream 37.0 16.9 185.0 3000.0 \n", + "14 Dream 34.0 17.1 185.0 3400.0 \n", + "15 Dream 37.0 16.5 185.0 3400.0 \n", + "18 Dream 39.7 17.9 193.0 4250.0 \n", + "19 Dream 37.8 18.1 193.0 3750.0 \n", + "22 Dream 40.2 17.1 193.0 3400.0 \n", + "23 Dream 36.8 18.5 193.0 3500.0 \n", + "26 Dream 41.5 18.5 201.0 4000.0 \n", + "31 Dream 33.1 16.1 178.0 2900.0 \n", + "32 Dream 37.2 18.1 178.0 3900.0 \n", + "33 Dream 39.5 16.7 178.0 3250.0 \n", + "35 Dream 36.0 18.5 186.0 3100.0 \n", + "36 Dream 39.6 18.1 186.0 4450.0 \n", + "38 Dream 41.3 20.3 194.0 3550.0 \n", + "41 Dream 35.7 18.0 202.0 3550.0 \n", + "51 Dream 38.1 17.6 187.0 3425.0 \n", + "53 Dream 36.0 17.1 187.0 3700.0 \n", + "\n", + " sex \n", + "0 FEMALE \n", + "1 MALE \n", + "2 MALE \n", + "4 FEMALE \n", + "5 MALE \n", + "9 MALE \n", + "10 MALE \n", + "11 MALE \n", + "12 FEMALE \n", + "14 FEMALE \n", + "15 FEMALE \n", + "18 MALE \n", + "19 MALE \n", + "22 FEMALE \n", + "23 FEMALE \n", + "26 MALE \n", + "31 FEMALE \n", + "32 MALE \n", + "33 FEMALE \n", + "35 FEMALE \n", + "36 MALE \n", + "38 MALE \n", + "41 FEMALE \n", + "51 FEMALE \n", + "53 FEMALE \n", + "...\n", + "\n", + "[146 rows x 6 columns]" + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# filter down to the data we want to analyze\n", + "adelie_data = df[df.species == \"Adelie Penguin (Pygoscelis adeliae)\"]\n", + "\n", + "# drop the columns we don't care about\n", + "adelie_data = adelie_data.drop(columns=[\"species\"])\n", + "\n", + "# drop rows with nulls to get our training data\n", + "training_data = adelie_data.dropna()\n", + "\n", + "# take a peek at the training data\n", + "training_data" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 3. Use `model_selection.train_test_split` to prepare training data" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "81f9aa34c7234bd88b6b7a4bc77d4b4e", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "HTML(value='Query job 288f0daa-a51e-45b4-86bf-d054467c4a99 is DONE. 28.9 kB processed.
int:\n", + "def nth_prime(n):\n", " prime_numbers = [2,3]\n", " i=3\n", " if(0Open Job" - ], + "application/vnd.jupyter.widget-view+json": { + "model_id": "0052d103678f47ffb3777ec3ac4e30f7", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "HTML(value='Query job ecaf079e-76ef-47bb-828d-a27e9552b597 is RUNNING. " + "HTML(value='Query job 4c1d9d3e-be25-4818-b74d-6214164d99ab is DONE. 0 Bytes processed. \n", " \n", " 0\n", - " 11231597\n", - " In your update, why are some of the system fun...\n", + " 11012908\n", + " you're welcome! according to the docs it shoul...\n", " 0\n", " \n", " \n", " 1\n", - " 49684807\n", - " what you have tried so far . ??\n", - " 1\n", + " 11013760\n", + " You *should* be concerned with the disk being ...\n", + " 0\n", " \n", " \n", " 2\n", - " 7623925\n", - " @Michael: It should work. Perhaps you looked i...\n", + " 11013784\n", + " have you looked at `Integrate` or `NIntegrate`?\n", " 0\n", " \n", " \n", " 3\n", - " 34046685\n", - " Will it work with SQL compact? Please excuse m...\n", + " 11015512\n", + " sorry, is a typo. The variable name is dist. (...\n", " 0\n", " \n", " \n", " 4\n", - " 6426146\n", - " do you know the equation to your pdf?\n", + " 11016238\n", + " Pfff, I'm having trouble with that formula too...\n", " 0\n", " \n", " \n", " 5\n", - " 60686114\n", - " m sorry but at least you have to think about it.\n", + " 11016276\n", + " Thanks thinksteep! Does this mean that by usin...\n", " 0\n", " \n", " \n", " 6\n", - " 16631986\n", - " i think also making disable this by only jquer...\n", + " 11016551\n", + " Jason, thanks for the reply. I've been workin...\n", " 0\n", " \n", " \n", " 7\n", - " 16498565\n", - " I am including these files on my header of the...\n", + " 11017973\n", + " I assume an `off` of 0.5 would put be exactly ...\n", " 0\n", " \n", " \n", " 8\n", - " 26601001\n", - " wrong answer, you didn't understand the logic\n", + " 11018225\n", + " Thank you very much. I do worry too much abou...\n", " 0\n", " \n", " \n", " 9\n", - " 73255842\n", - " Call the setOnClickListener before return row.\n", + " 11018370\n", + " @IanClelland, I edited my question a bit. The ...\n", " 0\n", " \n", " \n", @@ -463,21 +505,21 @@ ], "text/plain": [ " id text score\n", - "0 11231597 In your update, why are some of the system fun... 0\n", - "1 49684807 what you have tried so far . ?? 1\n", - "2 7623925 @Michael: It should work. Perhaps you looked i... 0\n", - "3 34046685 Will it work with SQL compact? Please excuse m... 0\n", - "4 6426146 do you know the equation to your pdf? 0\n", - "5 60686114 m sorry but at least you have to think about it. 0\n", - "6 16631986 i think also making disable this by only jquer... 0\n", - "7 16498565 I am including these files on my header of the... 0\n", - "8 26601001 wrong answer, you didn't understand the logic 0\n", - "9 73255842 Call the setOnClickListener before return row. 0\n", + "0 11012908 you're welcome! according to the docs it shoul... 0\n", + "1 11013760 You *should* be concerned with the disk being ... 0\n", + "2 11013784 have you looked at `Integrate` or `NIntegrate`? 0\n", + "3 11015512 sorry, is a typo. The variable name is dist. (... 0\n", + "4 11016238 Pfff, I'm having trouble with that formula too... 0\n", + "5 11016276 Thanks thinksteep! Does this mean that by usin... 0\n", + "6 11016551 Jason, thanks for the reply. I've been workin... 0\n", + "7 11017973 I assume an `off` of 0.5 would put be exactly ... 0\n", + "8 11018225 Thank you very much. I do worry too much abou... 0\n", + "9 11018370 @IanClelland, I edited my question a bit. The ... 0\n", "\n", "[10 rows x 3 columns]" ] }, - "execution_count": 23, + "execution_count": 6, "metadata": {}, "output_type": "execute_result" } @@ -497,7 +539,7 @@ }, { "cell_type": "code", - "execution_count": 24, + "execution_count": 7, "id": "55ed241e", "metadata": {}, "outputs": [ @@ -507,9 +549,8 @@ "text": [ "Help on function remote_function in module bigframes.pandas:\n", "\n", - "remote_function(input_types: 'List[type]', output_type: 'type', dataset: 'Optional[str]' = None, bigquery_connection: 'Optional[str]' = None, reuse: 'bool' = True, name: 'Optional[str]' = None, packages: 'Optional[Sequence[str]]' = None)\n", - " Decorator to turn a user defined function into a BigQuery remote function. Check out\n", - " the code samples at: https://cloud.google.com/bigquery/docs/remote-functions#bigquery-dataframes.\n", + "remote_function(input_types: 'List[type]', output_type: 'type', dataset: 'Optional[str]' = None, bigquery_connection: 'Optional[str]' = None, reuse: 'bool' = True)\n", + " Decorator to turn a user defined function into a BigQuery remote function.\n", " \n", " .. note::\n", " Please make sure following is setup before using this API:\n", @@ -535,7 +576,7 @@ " * BigQuery Data Editor (roles/bigquery.dataEditor)\n", " * BigQuery Connection Admin (roles/bigquery.connectionAdmin)\n", " * Cloud Functions Developer (roles/cloudfunctions.developer)\n", - " * Service Account User (roles/iam.serviceAccountUser) on the service account `PROJECT_NUMBER-compute@developer.gserviceaccount.com`\n", + " * Service Account User (roles/iam.serviceAccountUser)\n", " * Storage Object Viewer (roles/storage.objectViewer)\n", " * Project IAM Admin (roles/resourcemanager.projectIamAdmin) (Only required if the bigquery connection being used is not pre-created and is created dynamically with user credentials.)\n", " \n", @@ -561,25 +602,15 @@ " Name of the BigQuery connection. You should either have the\n", " connection already created in the `location` you have chosen, or\n", " you should have the Project IAM Admin role to enable the service\n", - " to create the connection for you if you need it. If this parameter is\n", + " to create the connection for you if you need it.If this parameter is\n", " not provided then the BigQuery connection from the session is used.\n", " reuse (bool, Optional):\n", " Reuse the remote function if already exists.\n", " `True` by default, which will result in reusing an existing remote\n", - " function and corresponding cloud function (if any) that was\n", - " previously created for the same udf.\n", - " Setting it to `False` would force creating a unique remote function.\n", + " function (if any) that was previously created for the same udf.\n", + " Setting it to false would force creating a unique remote function.\n", " If the required remote function does not exist then it would be\n", " created irrespective of this param.\n", - " name (str, Optional):\n", - " Explicit name of the persisted BigQuery remote function. Use it with\n", - " caution, because two users working in the same project and dataset\n", - " could overwrite each other's remote functions if they use the same\n", - " persistent name.\n", - " packages (str[], Optional):\n", - " Explicit name of the external package dependencies. Each dependency\n", - " is added to the `requirements.txt` as is, and can be of the form\n", - " supported in https://pip.pypa.io/en/stable/reference/requirements-file-format/.\n", " Returns:\n", " callable: A remote function object pointing to the cloud assets created\n", " in the background to support the remote execution. The cloud assets can be\n", @@ -600,16 +631,49 @@ }, { "cell_type": "code", - "execution_count": 25, + "execution_count": 8, + "id": "c9a8d03d", + "metadata": {}, + "outputs": [], + "source": [ + "# BigQuery DataFrames user is a data scientist and may not have privileges to\n", + "# create a BQ connector and set it up for invoking a cloud function. They\n", + "# should get such a connector created from their cloud admin and use it with\n", + "# BigQuery DataFrames remote functions. If the provided connection name does not\n", + "# exist, BigQuery DataFrames will try to create it on the fly assuming the user\n", + "# has sufficient privileges.\n", + "bq_connection_name = 'bigframes-rf-conn'" + ] + }, + { + "cell_type": "code", + "execution_count": 9, "id": "fbc27f81", "metadata": {}, "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[INFO][2023-08-18 21:23:29,687][bigframes.remote_function] Creating new cloud function: gcloud functions deploy bigframes-b0feb1fbaf8188b64d7e70118d93c5d4 --gen2 --runtime=python310 --project=bigframes-dev --region=us-central1 --source=/tmp/tmpl2ewfnue --entry-point=udf_http --trigger-http --no-allow-unauthenticated\n", + "[INFO][2023-08-18 21:24:43,689][bigframes.remote_function] Successfully created cloud function bigframes-b0feb1fbaf8188b64d7e70118d93c5d4 with uri (https://bigframes-b0feb1fbaf8188b64d7e70118d93c5d4-7krlje3eoq-uc.a.run.app)\n", + "[INFO][2023-08-18 21:24:57,348][bigframes.remote_function] Connector bigframes-rf-conn already exists\n", + "[INFO][2023-08-18 21:24:57,351][bigframes.remote_function] Creating BQ remote function: \n", + " CREATE OR REPLACE FUNCTION `bigframes-dev.bigframes_temp_us`.bigframes_b0feb1fbaf8188b64d7e70118d93c5d4(n INT64)\n", + " RETURNS INT64\n", + " REMOTE WITH CONNECTION `bigframes-dev.us.bigframes-rf-conn`\n", + " OPTIONS (\n", + " endpoint = \"https://bigframes-b0feb1fbaf8188b64d7e70118d93c5d4-7krlje3eoq-uc.a.run.app\"\n", + " )\n", + "[INFO][2023-08-18 21:24:58,300][bigframes.remote_function] Created remote function bigframes-dev.bigframes_temp_us.bigframes_b0feb1fbaf8188b64d7e70118d93c5d4\n" + ] + }, { "name": "stdout", "output_type": "stream", "text": [ "\n", - "Wall time: 76.2628 s\n" + "Wall time: 89.0601 s\n" ] } ], @@ -620,8 +684,8 @@ "\n", "# User defined function\n", "# https://www.codespeedy.com/find-nth-prime-number-in-python/\n", - "@pd.remote_function(reuse=False, cloud_function_service_account=\"default\")\n", - "def nth_prime(n: int) -> int:\n", + "@pd.remote_function([int], int, bigquery_connection=bq_connection_name)\n", + "def nth_prime(n):\n", " prime_numbers = [2,3]\n", " i=3\n", " if(0Open Job" - ], + "application/vnd.jupyter.widget-view+json": { + "model_id": "2f840ad27c514ed19c759a004b32de33", + "version_major": 2, + "version_minor": 0 + }, "text/plain": [ - "" + "HTML(value='Query job 0f421233-9d02-4746-bb39-86a3b0880aba is RUNNING. Open Job" - ], + "application/vnd.jupyter.widget-view+json": { + "model_id": "adffcff769be46b1bc6e50f9622cdd30", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "HTML(value='Query job 4f8d5734-8070-4630-8a59-c05a31d60476 is RUNNING. " + "HTML(value='Query job ec057f9e-726b-44f0-a5c0-24c05c7ecfeb is RUNNING. \n", " \n", " 0\n", - " 11231597\n", - " In your update, why are some of the system fun...\n", + " 11012908\n", + " you're welcome! according to the docs it shoul...\n", " 0\n", " -1\n", " \n", " \n", " 1\n", - " 49684807\n", - " what you have tried so far . ??\n", - " 1\n", - " 2\n", + " 11013760\n", + " You *should* be concerned with the disk being ...\n", + " 0\n", + " -1\n", " \n", " \n", " 2\n", - " 7623925\n", - " @Michael: It should work. Perhaps you looked i...\n", + " 11013784\n", + " have you looked at `Integrate` or `NIntegrate`?\n", " 0\n", " -1\n", " \n", " \n", " 3\n", - " 34046685\n", - " Will it work with SQL compact? Please excuse m...\n", + " 11015512\n", + " sorry, is a typo. The variable name is dist. (...\n", " 0\n", " -1\n", " \n", " \n", " 4\n", - " 6426146\n", - " do you know the equation to your pdf?\n", + " 11016238\n", + " Pfff, I'm having trouble with that formula too...\n", " 0\n", " -1\n", " \n", " \n", " 5\n", - " 60686114\n", - " m sorry but at least you have to think about it.\n", + " 11016276\n", + " Thanks thinksteep! Does this mean that by usin...\n", " 0\n", " -1\n", " \n", " \n", " 6\n", - " 16631986\n", - " i think also making disable this by only jquer...\n", + " 11016551\n", + " Jason, thanks for the reply. I've been workin...\n", " 0\n", " -1\n", " \n", " \n", " 7\n", - " 16498565\n", - " I am including these files on my header of the...\n", + " 11017973\n", + " I assume an `off` of 0.5 would put be exactly ...\n", " 0\n", " -1\n", " \n", " \n", " 8\n", - " 26601001\n", - " wrong answer, you didn't understand the logic\n", + " 11018225\n", + " Thank you very much. I do worry too much abou...\n", " 0\n", " -1\n", " \n", " \n", " 9\n", - " 73255842\n", - " Call the setOnClickListener before return row.\n", + " 11018370\n", + " @IanClelland, I edited my question a bit. The ...\n", " 0\n", " -1\n", " \n", @@ -789,21 +871,21 @@ ], "text/plain": [ " id text score n_prime\n", - "0 11231597 In your update, why are some of the system fun... 0 -1\n", - "1 49684807 what you have tried so far . ?? 1 2\n", - "2 7623925 @Michael: It should work. Perhaps you looked i... 0 -1\n", - "3 34046685 Will it work with SQL compact? Please excuse m... 0 -1\n", - "4 6426146 do you know the equation to your pdf? 0 -1\n", - "5 60686114 m sorry but at least you have to think about it. 0 -1\n", - "6 16631986 i think also making disable this by only jquer... 0 -1\n", - "7 16498565 I am including these files on my header of the... 0 -1\n", - "8 26601001 wrong answer, you didn't understand the logic 0 -1\n", - "9 73255842 Call the setOnClickListener before return row. 0 -1\n", + "0 11012908 you're welcome! according to the docs it shoul... 0 -1\n", + "1 11013760 You *should* be concerned with the disk being ... 0 -1\n", + "2 11013784 have you looked at `Integrate` or `NIntegrate`? 0 -1\n", + "3 11015512 sorry, is a typo. The variable name is dist. (... 0 -1\n", + "4 11016238 Pfff, I'm having trouble with that formula too... 0 -1\n", + "5 11016276 Thanks thinksteep! Does this mean that by usin... 0 -1\n", + "6 11016551 Jason, thanks for the reply. I've been workin... 0 -1\n", + "7 11017973 I assume an `off` of 0.5 would put be exactly ... 0 -1\n", + "8 11018225 Thank you very much. I do worry too much abou... 0 -1\n", + "9 11018370 @IanClelland, I edited my question a bit. The ... 0 -1\n", "\n", "[10 rows x 4 columns]" ] }, - "execution_count": 26, + "execution_count": 10, "metadata": {}, "output_type": "execute_result" } @@ -818,7 +900,7 @@ }, { "cell_type": "code", - "execution_count": 27, + "execution_count": 11, "id": "2701cb81", "metadata": {}, "outputs": [ @@ -826,8 +908,8 @@ "name": "stdout", "output_type": "stream", "text": [ - "shobs-test.bigframes_temp_us.bigframes_343b7b4bb93ca8747dae20c22bdaec8b_p27heyce\n", - "projects/shobs-test/locations/us-central1/functions/bigframes-343b7b4bb93ca8747dae20c22bdaec8b-p27heyce\n" + "bigframes-dev.bigframes_temp_us.bigframes_b0feb1fbaf8188b64d7e70118d93c5d4\n", + "projects/bigframes-dev/locations/us-central1/functions/bigframes-b0feb1fbaf8188b64d7e70118d93c5d4\n" ] } ], @@ -840,7 +922,7 @@ }, { "cell_type": "code", - "execution_count": 28, + "execution_count": 12, "id": "920fa18e", "metadata": {}, "outputs": [ @@ -855,42 +937,6 @@ " \n", " Then it can be applied to a DataFrame or Series.\n", " \n", - " .. note::\n", - " The return type of the function must be explicitly specified in the\n", - " function's original definition even if not otherwise required.\n", - " \n", - " BigQuery Utils provides many public functions under the ``bqutil`` project on Google Cloud Platform project\n", - " (See: https://github.com/GoogleCloudPlatform/bigquery-utils/tree/master/udfs#using-the-udfs).\n", - " You can checkout Community UDFs to use community-contributed functions.\n", - " (See: https://github.com/GoogleCloudPlatform/bigquery-utils/tree/master/udfs/community#community-udfs).\n", - " \n", - " **Examples:**\n", - " \n", - " Use the ``cw_lower_case_ascii_only`` function from Community UDFs.\n", - " (https://github.com/GoogleCloudPlatform/bigquery-utils/blob/master/udfs/community/cw_lower_case_ascii_only.sqlx)\n", - " \n", - " >>> import bigframes.pandas as bpd\n", - " >>> bpd.options.display.progress_bar = None\n", - " \n", - " >>> df = bpd.DataFrame({'id': [1, 2, 3], 'name': ['AURÉLIE', 'CÉLESTINE', 'DAPHNÉ']})\n", - " >>> df\n", - " id name\n", - " 0 1 AURÉLIE\n", - " 1 2 CÉLESTINE\n", - " 2 3 DAPHNÉ\n", - " \n", - " [3 rows x 2 columns]\n", - " \n", - " >>> func = bpd.read_gbq_function(\"bqutil.fn.cw_lower_case_ascii_only\")\n", - " >>> df1 = df.assign(new_name=df['name'].apply(func))\n", - " >>> df1\n", - " id name new_name\n", - " 0 1 AURÉLIE aurÉlie\n", - " 1 2 CÉLESTINE cÉlestine\n", - " 2 3 DAPHNÉ daphnÉ\n", - " \n", - " [3 rows x 3 columns]\n", - " \n", " Args:\n", " function_name (str):\n", " the function's name in BigQuery in the format\n", @@ -911,7 +957,7 @@ } ], "source": [ - "# Let's try to simulate a scenario in which user shares this remote function to\n", + "# Let's try to simulate a scenario in which user shares this remote funciton to\n", "# their colleague who simply wants to reuse it. BigFrames provides an API to do\n", "# so via `read_gbq_function`. Usage details are available via `help` command.\n", "help(pd.read_gbq_function)" @@ -919,7 +965,7 @@ }, { "cell_type": "code", - "execution_count": 29, + "execution_count": 14, "id": "a6c9da0a", "metadata": {}, "outputs": [], @@ -932,7 +978,7 @@ }, { "cell_type": "code", - "execution_count": 30, + "execution_count": 15, "id": "d7e7de7f", "metadata": {}, "outputs": [ @@ -940,17 +986,19 @@ "name": "stdout", "output_type": "stream", "text": [ - "CPU times: user 70.8 ms, sys: 3.49 ms, total: 74.3 ms\n", - "Wall time: 75.2 ms\n" + "CPU times: user 10.9 ms, sys: 0 ns, total: 10.9 ms\n", + "Wall time: 11.4 ms\n" ] }, { "data": { - "text/html": [ - "Query job f9a0e979-aeac-4ddd-a4c9-6720a5e91009 is DONE. 17.2 GB processed. Open Job" - ], + "application/vnd.jupyter.widget-view+json": { + "model_id": "73d1a73593cb4115821ab128c221a48d", + "version_major": 2, + "version_minor": 0 + }, "text/plain": [ - "" + "HTML(value='Query job bec5f7d1-3df1-4292-8c68-c396bce7dc5d is RUNNING. Open Job" - ], + "application/vnd.jupyter.widget-view+json": { + "model_id": "827770710c1549cf819fe47a5a1cd70f", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "HTML(value='Query job 02e3bf43-a387-41c7-85c7-4a5366251de7 is RUNNING. " + "HTML(value='Query job fa4329e8-2918-44c4-96c5-d8591364abc9 is RUNNING. \n", " \n", " 0\n", - " 11231597\n", - " In your update, why are some of the system fun...\n", + " 11012908\n", + " you're welcome! according to the docs it shoul...\n", " 0\n", " -1\n", " -1\n", " \n", " \n", " 1\n", - " 49684807\n", - " what you have tried so far . ??\n", - " 1\n", - " 2\n", - " 2\n", + " 11013760\n", + " You *should* be concerned with the disk being ...\n", + " 0\n", + " -1\n", + " -1\n", " \n", " \n", " 2\n", - " 7623925\n", - " @Michael: It should work. Perhaps you looked i...\n", + " 11013784\n", + " have you looked at `Integrate` or `NIntegrate`?\n", " 0\n", " -1\n", " -1\n", " \n", " \n", " 3\n", - " 34046685\n", - " Will it work with SQL compact? Please excuse m...\n", + " 11015512\n", + " sorry, is a typo. The variable name is dist. (...\n", " 0\n", " -1\n", " -1\n", " \n", " \n", " 4\n", - " 6426146\n", - " do you know the equation to your pdf?\n", + " 11016238\n", + " Pfff, I'm having trouble with that formula too...\n", " 0\n", " -1\n", " -1\n", " \n", " \n", " 5\n", - " 60686114\n", - " m sorry but at least you have to think about it.\n", + " 11016276\n", + " Thanks thinksteep! Does this mean that by usin...\n", " 0\n", " -1\n", " -1\n", " \n", " \n", " 6\n", - " 16631986\n", - " i think also making disable this by only jquer...\n", + " 11016551\n", + " Jason, thanks for the reply. I've been workin...\n", " 0\n", " -1\n", " -1\n", " \n", " \n", " 7\n", - " 16498565\n", - " I am including these files on my header of the...\n", + " 11017973\n", + " I assume an `off` of 0.5 would put be exactly ...\n", " 0\n", " -1\n", " -1\n", " \n", " \n", " 8\n", - " 26601001\n", - " wrong answer, you didn't understand the logic\n", + " 11018225\n", + " Thank you very much. I do worry too much abou...\n", " 0\n", " -1\n", " -1\n", " \n", " \n", " 9\n", - " 73255842\n", - " Call the setOnClickListener before return row.\n", + " 11018370\n", + " @IanClelland, I edited my question a bit. The ...\n", " 0\n", " -1\n", " -1\n", @@ -1084,20 +1148,20 @@ ], "text/plain": [ " id text score \\\n", - "0 11231597 In your update, why are some of the system fun... 0 \n", - "1 49684807 what you have tried so far . ?? 1 \n", - "2 7623925 @Michael: It should work. Perhaps you looked i... 0 \n", - "3 34046685 Will it work with SQL compact? Please excuse m... 0 \n", - "4 6426146 do you know the equation to your pdf? 0 \n", - "5 60686114 m sorry but at least you have to think about it. 0 \n", - "6 16631986 i think also making disable this by only jquer... 0 \n", - "7 16498565 I am including these files on my header of the... 0 \n", - "8 26601001 wrong answer, you didn't understand the logic 0 \n", - "9 73255842 Call the setOnClickListener before return row. 0 \n", + "0 11012908 you're welcome! according to the docs it shoul... 0 \n", + "1 11013760 You *should* be concerned with the disk being ... 0 \n", + "2 11013784 have you looked at `Integrate` or `NIntegrate`? 0 \n", + "3 11015512 sorry, is a typo. The variable name is dist. (... 0 \n", + "4 11016238 Pfff, I'm having trouble with that formula too... 0 \n", + "5 11016276 Thanks thinksteep! Does this mean that by usin... 0 \n", + "6 11016551 Jason, thanks for the reply. I've been workin... 0 \n", + "7 11017973 I assume an `off` of 0.5 would put be exactly ... 0 \n", + "8 11018225 Thank you very much. I do worry too much abou... 0 \n", + "9 11018370 @IanClelland, I edited my question a bit. The ... 0 \n", "\n", " n_prime n_prime_again \n", "0 -1 -1 \n", - "1 2 2 \n", + "1 -1 -1 \n", "2 -1 -1 \n", "3 -1 -1 \n", "4 -1 -1 \n", @@ -1110,7 +1174,7 @@ "[10 rows x 5 columns]" ] }, - "execution_count": 30, + "execution_count": 15, "metadata": {}, "output_type": "execute_result" } @@ -1122,38 +1186,6 @@ "df = df.assign(n_prime_again=df['score'].apply(nth_prime_existing))\n", "df.head(10)" ] - }, - { - "cell_type": "code", - "execution_count": 31, - "id": "bafab950", - "metadata": {}, - "outputs": [], - "source": [ - "# Clean up GCP assets created as part of bigframes remote_function\n", - "def cleanup_remote_function_assets(remote_udf, ignore_failures=False):\n", - " \"\"\"Clean up the GCP assets behind a bigframes remote function.\"\"\"\n", - "\n", - " session = pd.get_global_session()\n", - "\n", - " # Clean up BQ remote function\n", - " try:\n", - " session.bqclient.delete_routine(remote_udf.bigframes_remote_function)\n", - " except Exception:\n", - " # By default don't raise exception in cleanup\n", - " if not ignore_failures:\n", - " raise\n", - "\n", - " # Clean up cloud function\n", - " try:\n", - " session.cloudfunctionsclient.delete_function(name=remote_udf.bigframes_cloud_function)\n", - " except Exception:\n", - " # By default don't raise exception in cleanup\n", - " if not ignore_failures:\n", - " raise\n", - "\n", - "cleanup_remote_function_assets(nth_prime)" - ] } ], "metadata": { @@ -1172,7 +1204,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.4" + "version": "3.10.12" } }, "nbformat": 4, diff --git a/notebooks/remote_functions/remote_function_usecases.ipynb b/notebooks/remote_functions/remote_function_usecases.ipynb deleted file mode 100644 index e3a94160ad9..00000000000 --- a/notebooks/remote_functions/remote_function_usecases.ipynb +++ /dev/null @@ -1,1436 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 20, - "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": [ - "# Set Up" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": { - "id": "Y6QAttCqqMM0" - }, - "outputs": [], - "source": [ - "import bigframes.pandas as bpd" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 296 - }, - "id": "xraJ9RRzsvel", - "outputId": "6e3308cf-8de0-4b89-9128-4c6ddf3598c0" - }, - "outputs": [ - { - "data": { - "text/html": [ - "Query job 1f6094e9-1942-477c-9ce3-87a614d71294 is DONE. 0 Bytes processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job ba19f29c-33d3-4f12-9605-ddeafb74918e is DONE. 582.8 kB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job dd1ff8be-700a-4ce5-91a0-31413f70cfad is DONE. 82.0 kB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "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", - "
homeTeamNameawayTeamNameduration_minutes
88RoyalsAthletics176
106DodgersGiants216
166PhilliesRoyals162
247RangersRoyals161
374AthleticsAstros161
\n", - "
" - ], - "text/plain": [ - " homeTeamName awayTeamName duration_minutes\n", - "88 Royals Athletics 176\n", - "106 Dodgers Giants 216\n", - "166 Phillies Royals 162\n", - "247 Rangers Royals 161\n", - "374 Athletics Astros 161" - ] - }, - "execution_count": 3, - "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": 4, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 52 - }, - "id": "VoCPBJ-ZpyeG", - "outputId": "19351206-116e-4da2-8ff0-f288b7745b27" - }, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/usr/local/google/home/arwas/src1/python-bigquery-dataframes/bigframes/functions/_function_session.py:335: UserWarning: You have not explicitly set a user-managed cloud_function_service_account. Using the default compute service account, {cloud_function_service_account}. To use Bigframes 2.0, please set an explicit user-managed cloud_function_service_account or set cloud_function_service_account explicitly to `default`.See, https://cloud.google.com/functions/docs/securing/function-identity.\n", - " warnings.warn(msg, category=UserWarning)\n" - ] - }, - { - "data": { - "text/html": [ - "Query job 7c021760-59c4-4f3a-846c-9693a4d16eef 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-sessionca6012-ca541a90249f8b62951f38b7aba6a711-49to' and BQ remote function 'bigframes-dev._ed1e4d0f7d41174ba506d34d15dccf040d13f69e.bigframes_sessionca6012_ca541a90249f8b62951f38b7aba6a711_49to'.\n" - ] - } - ], - "source": [ - "@bpd.remote_function(reuse=False, cloud_function_service_account=\"default\")\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": 24, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 258 - }, - "id": "oXgDB70Lp5cG", - "outputId": "c08aade0-8b03-425b-fc26-deafd89275a4" - }, - "outputs": [ - { - "data": { - "text/html": [ - "Query job 4b116e3e-d4d3-4eb6-9764-0a29a7c5d036 is DONE. 58.3 kB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job d62ac4f0-47c9-47ae-8611-c9ecf78f20c9 is DONE. 157.2 kB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job 5f876ebb-2d95-4c68-9d84-947e02b37bad is DONE. 98.8 kB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "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", - "
homeTeamNameawayTeamNameduration_minutesduration_cat
1911DodgersAngels132medium
2365AthleticsAngels134medium
1977AthleticsAngels139medium
554CubsAngels142medium
654AstrosAngels143medium
\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": 24, - "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": 25, - "metadata": { - "id": "2UEmTbu4znyS" - }, - "outputs": [], - "source": [ - "DURATION_CATEGORY_SHORT = \"S\"\n", - "DURATION_CATEGORY_MEDIUM = \"M\"\n", - "DURATION_CATEGORY_LONG = \"L\"" - ] - }, - { - "cell_type": "code", - "execution_count": 26, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 52 - }, - "id": "G-73kpmrznHn", - "outputId": "b5923b7c-d412-43bf-9a20-3946154df81a" - }, - "outputs": [ - { - "data": { - "text/html": [ - "Query job 1909a652-5735-401b-8a77-674d8539ded0 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-session54c8b0-4191f0fce98d46cc09359de47e203236-e009' and BQ remote function 'bigframes-dev._1b6c31ff1bcd5d2f6d86833cf8268317f1b12d57.bigframes_session54c8b0_4191f0fce98d46cc09359de47e203236_e009'.\n" - ] - } - ], - "source": [ - "@bpd.remote_function(reuse=False, cloud_function_service_account=\"default\")\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": 27, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 258 - }, - "id": "DWHKsfF-z7rL", - "outputId": "c736b57f-1fcb-464a-f725-eb203265ddc2" - }, - "outputs": [ - { - "data": { - "text/html": [ - "Query job a942bdc5-6a6d-4db8-b2aa-a556197377b3 is DONE. 58.3 kB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job 175ae9d3-604f-495b-a167-8b06c0283bd2 is DONE. 147.7 kB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job d331a785-e574-45c9-86c8-d29ddd79a4d1 is DONE. 89.3 kB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "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", - "
homeTeamNameawayTeamNameduration_minutesduration_cat
1911DodgersAngels132M
2365AthleticsAngels134M
1977AthleticsAngels139M
554CubsAngels142M
654AstrosAngels143M
\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": 27, - "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": 28, - "metadata": { - "id": "zlQfhcW41uzM" - }, - "outputs": [], - "source": [ - "import math as mymath" - ] - }, - { - "cell_type": "code", - "execution_count": 29, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 52 - }, - "id": "ktADchck2mh4", - "outputId": "9aed6aea-b361-4414-a0f6-8873e8291090" - }, - "outputs": [ - { - "data": { - "text/html": [ - "Query job bbc0b78f-bc04-4bd5-b711-399786a51519 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-session54c8b0-cf31fc2d2c7fe111afa5526f5a9cdf06-gmmo' and BQ remote function 'bigframes-dev._1b6c31ff1bcd5d2f6d86833cf8268317f1b12d57.bigframes_session54c8b0_cf31fc2d2c7fe111afa5526f5a9cdf06_gmmo'.\n" - ] - } - ], - "source": [ - "@bpd.remote_function(reuse=False, cloud_function_service_account=\"default\")\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": 30, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 258 - }, - "id": "ywAtZlJU3GoB", - "outputId": "d3c93a31-3367-4ccf-bdf7-62d5bbff4461" - }, - "outputs": [ - { - "data": { - "text/html": [ - "Query job 991b54ed-9eaa-450f-9208-3e73404bb112 is DONE. 58.3 kB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job 4e464a58-ac5b-42fd-91e3-92c115bdd273 is DONE. 150.1 kB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job d340f55d-1511-431a-970d-a70ed4356935 is DONE. 91.7 kB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "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", - "
homeTeamNameawayTeamNameduration_minutesduration_cat
1911DodgersAngels1323h
2365AthleticsAngels1343h
1977AthleticsAngels1393h
554CubsAngels1423h
654AstrosAngels1433h
\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": 30, - "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": 31, - "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": 32, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 52 - }, - "id": "lQrC8T2031EJ", - "outputId": "420e7c3d-54cb-4814-f973-c7678be61caa" - }, - "outputs": [ - { - "data": { - "text/html": [ - "Query job 10d1afa3-349b-49a8-adbd-79a8309ce77c 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-session54c8b0-3c03836c2044bf625d02e25ccdbfe101-k1m4' and BQ remote function 'bigframes-dev._1b6c31ff1bcd5d2f6d86833cf8268317f1b12d57.bigframes_session54c8b0_3c03836c2044bf625d02e25ccdbfe101_k1m4'.\n" - ] - } - ], - "source": [ - "@bpd.remote_function(reuse=False, cloud_function_service_account=\"default\")\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": 33, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 258 - }, - "id": "GVyrihii4EFG", - "outputId": "e979b649-4ed4-4b82-e814-54180420e3fc" - }, - "outputs": [ - { - "data": { - "text/html": [ - "Query job 33aff336-48d6-4caa-8cae-f459d21b180e is DONE. 58.3 kB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job 561e0aa7-3962-4ef3-b308-a117a0ac3a7d is DONE. 157.4 kB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job 759dccf8-3d88-40e1-a38a-2a2064e1d269 is DONE. 99.0 kB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "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", - "
homeTeamNameawayTeamNameduration_minutesduration_cat
1911DodgersAngels1323 hrs
2365AthleticsAngels1343 hrs
1977AthleticsAngels1393 hrs
554CubsAngels1423 hrs
654AstrosAngels1433 hrs
\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": 33, - "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": 34, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 34 - }, - "id": "3EUEyNcW41_l", - "outputId": "2d09d60f-da1a-4eab-86d3-0e62390a360c" - }, - "outputs": [ - { - "data": { - "text/html": [ - "Query job e2a44878-2564-44a5-8dec-b7ea2f42afd4 is DONE. 0 Bytes processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "@bpd.remote_function(reuse=False, packages=[\"cryptography\"], cloud_function_service_account=\"default\")\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": 35, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 258 - }, - "id": "OX1Hl7bR5uyd", - "outputId": "8ac3bf28-d16d-438b-b636-74ef2371715f" - }, - "outputs": [ - { - "data": { - "text/html": [ - "Query job bcfab000-ca19-4633-bf0e-45e7d053f3eb is DONE. 60.5 kB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job 139a6449-c07e-41ff-9aed-c6fdd633740a is DONE. 388.3 kB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job 035fa2fb-0a55-4358-bb50-3ef915f5bf54 is DONE. 330.0 kB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "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", - "
homeTeamNameawayTeamNameduration_minuteshomeTeamNameRedacted
641American LeagueNational League185gAAAAABmo0n2I391cbYwIYeg8lyJq1MSFZatrtpvuUD5v-...
349AngelsAstros187gAAAAABmo0n2pX-siRwl2tIZA4m--swndC_b7vgGXrqSNM...
2349AngelsAstros160gAAAAABmo0n28Q9RwH62HvYRhTDpQ9lo8c6G8F5bnn7wgF...
557AngelsAstros166gAAAAABmo0n2YlwHlSGQ0_XvXd-QVBtB_Lq2zUifu7vKhg...
220AngelsAstros162gAAAAABmo0n2l8HMSGKYizxfEmRvGQy96mrjwx734-Rl_Z...
\n", - "
" - ], - "text/plain": [ - " homeTeamName awayTeamName duration_minutes \\\n", - "641 American League National League 185 \n", - "349 Angels Astros 187 \n", - "2349 Angels Astros 160 \n", - "557 Angels Astros 166 \n", - "220 Angels Astros 162 \n", - "\n", - " homeTeamNameRedacted \n", - "641 gAAAAABmo0n2I391cbYwIYeg8lyJq1MSFZatrtpvuUD5v-... \n", - "349 gAAAAABmo0n2pX-siRwl2tIZA4m--swndC_b7vgGXrqSNM... \n", - "2349 gAAAAABmo0n28Q9RwH62HvYRhTDpQ9lo8c6G8F5bnn7wgF... \n", - "557 gAAAAABmo0n2YlwHlSGQ0_XvXd-QVBtB_Lq2zUifu7vKhg... \n", - "220 gAAAAABmo0n2l8HMSGKYizxfEmRvGQy96mrjwx734-Rl_Z... " - ] - }, - "execution_count": 35, - "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": 36, - "metadata": {}, - "outputs": [], - "source": [ - "import datetime as dt\n", - "import humanize" - ] - }, - { - "cell_type": "code", - "execution_count": 37, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "Query job af73ab2d-8d88-4cbe-863f-d35e48af84e1 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-session54c8b0-a5e21a4ad488ce8b90de19c3c8cd33b6-0ab2' and BQ remote function 'bigframes-dev._1b6c31ff1bcd5d2f6d86833cf8268317f1b12d57.bigframes_session54c8b0_a5e21a4ad488ce8b90de19c3c8cd33b6_0ab2'.\n" - ] - } - ], - "source": [ - "@bpd.remote_function(reuse=False, packages=[\"humanize\"], cloud_function_service_account=\"default\")\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": 38, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "Query job 0a9ac329-619d-4303-8dbd-176a576d4ce8 is DONE. 58.3 kB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job 456bb9b4-0576-4c04-b707-4a04496aa538 is DONE. 162.2 kB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job 37f59939-5d2c-4fb1-839b-282ae3702d3d is DONE. 103.9 kB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "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", - "
homeTeamNameawayTeamNameduration_minutesduration_cat
1911DodgersAngels1322 hours
2365AthleticsAngels1342 hours
1977AthleticsAngels1392 hours
554CubsAngels1422 hours
654AstrosAngels1432 hours
\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": 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": {}, - "source": [ - "# Clean Up" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "bpd.close_session()" - ] - } - ], - "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.11.4" - } - }, - "nbformat": 4, - "nbformat_minor": 0 -} diff --git a/notebooks/remote_functions/remote_function_vertex_claude_model.ipynb b/notebooks/remote_functions/remote_function_vertex_claude_model.ipynb deleted file mode 100644 index dfc993072cf..00000000000 --- a/notebooks/remote_functions/remote_function_vertex_claude_model.ipynb +++ /dev/null @@ -1,482 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Use BigQuery DataFrames to run Anthropic LLM at scale\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", - "
" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Overview\n", - "\n", - "Anthropic Claude models are available as APIs on Vertex AI ([docs](https://cloud.google.com/vertex-ai/generative-ai/docs/partner-models/use-claude)).\n", - "\n", - "To run the Claude models at large scale data we can utilze the BigQuery\n", - "DataFrames remote functions ([docs](https://cloud.google.com/bigquery/docs/use-bigquery-dataframes#remote-functions)).\n", - "BigQuery DataFrames provides a simple pythonic interface `remote_function` to\n", - "deploy the user code as a BigQuery remote function and then invoke it at scale\n", - "by utilizing the parallel distributed computing architecture of BigQuery and\n", - "Google Cloud Function.\n", - "\n", - "In this notebook we showcase one such example. For the demonstration purpose we\n", - "use a small amount of data, but the example generalizes for large data. Check out\n", - "various IO APIs provided by BigQuery DataFrames [here](https://cloud.google.com/python/docs/reference/bigframes/latest/bigframes.pandas#bigframes_pandas_read_gbq)\n", - "to see how you could create a DataFrame from your Big Data sitting in a BigQuery\n", - "table or GCS bucket." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Set Up" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Set up a claude model in Vertex\n", - "\n", - "https://cloud.google.com/vertex-ai/generative-ai/docs/partner-models/use-claude#before_you_begin" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Install Anthropic with Vertex if needed\n", - "\n", - "Uncomment the following cell and run the cell to install anthropic python\n", - "package with vertex extension if you don't already have it." - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [], - "source": [ - "# !pip install anthropic[vertex] --quiet" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Define project and location for GCP integration" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "PROJECT_ID = \"bigframes-dev\" # @param {type:\"string\"}\n", - "LOCATION = \"us-east5\"" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Initialize BigQuery DataFrames dataframe\n", - "\n", - "BigQuery DataFrames is a set of open source Python libraries that let you take\n", - "advantage of BigQuery data processing by using familiar Python APIs.\n", - "See for more details https://cloud.google.com/bigquery/docs/bigquery-dataframes-introduction." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Import BigQuery DataFrames pandas module and initialize it with your project\n", - "# and location\n", - "\n", - "import bigframes.pandas as bpd\n", - "bpd.options.bigquery.project = PROJECT_ID\n", - "bpd.options.bigquery.location = LOCATION" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Let's use a DataFrame with small amount of inline data for demo purpose.\n", - "You could create a DataFrame from your own data. See APIs like `read_gbq`,\n", - "`read_csv`, `read_json` etc. at https://cloud.google.com/python/docs/reference/bigframes/latest/bigframes.pandas." - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "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", - "
questions
0What is the capital of France?
1Explain the concept of photosynthesis in simpl...
2Write a haiku about artificial intelligence.
\n", - "

3 rows × 1 columns

\n", - "
[3 rows x 1 columns in total]" - ], - "text/plain": [ - " questions\n", - "0 What is the capital of France?\n", - "1 Explain the concept of photosynthesis in simpl...\n", - "2 Write a haiku about artificial intelligence.\n", - "\n", - "[3 rows x 1 columns]" - ] - }, - "execution_count": 4, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "df = bpd.DataFrame({\"questions\": [\n", - " \"What is the capital of France?\",\n", - " \"Explain the concept of photosynthesis in simple terms.\",\n", - " \"Write a haiku about artificial intelligence.\"\n", - " ]})\n", - "df" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Use BigQuery DataFrames `remote_function`" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Let's create a remote function from a custom python function that takes a prompt\n", - "and returns the output of the claude LLM running in Vertex. We will be using\n", - "`max_batching_rows=1` to control parallelization. This ensures that a single\n", - "prompt is processed per batch in the underlying cloud function so that the batch\n", - "processing does not time out. An ideal value for `max_batching_rows` depends on\n", - "the complexity of the prompts in the real use case and should be discovered\n", - "through offline experimentation. Check out the API for other ways to control\n", - "parallelization https://cloud.google.com/python/docs/reference/bigframes/latest/bigframes.pandas#bigframes_pandas_remote_function." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - " Query processed 0 Bytes in a moment of slot time. [Job bigframes-dev:us-east5.9bc70627-6891-44a4-b7d7-8a28e213cdec details]\n", - " " - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "@bpd.remote_function(packages=[\"anthropic[vertex]\", \"google-auth[requests]\"],\n", - " max_batching_rows=1, \n", - " bigquery_connection=\"bigframes-dev.us-east5.bigframes-rf-conn\", # replace with your connection\n", - " cloud_function_service_account=\"default\",\n", - ")\n", - "def anthropic_transformer(message: str) -> str:\n", - " from anthropic import AnthropicVertex\n", - " client = AnthropicVertex(region=LOCATION, project_id=PROJECT_ID)\n", - "\n", - " message = client.messages.create(\n", - " max_tokens=1024,\n", - " messages=[\n", - " {\n", - " \"role\": \"user\",\n", - " \"content\": message,\n", - " }\n", - " ],\n", - " model=\"claude-3-haiku@20240307\",\n", - " )\n", - " content_text = message.content[0].text if message.content else \"\"\n", - " return content_text" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "'bigframes-dev._e9a5162ae4daa9f50fda3f95febaa9781131f3b8.bigframes_sessionc10c73_49262141176cbf70037559ae84e834d3'" - ] - }, - "execution_count": 6, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# Print the BigQuery remote function created\n", - "anthropic_transformer.bigframes_remote_function" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "'projects/bigframes-dev/locations/us-east5/functions/bigframes-sessionc10c73-49262141176cbf70037559ae84e834d3'" - ] - }, - "execution_count": 7, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# Print the cloud function created\n", - "anthropic_transformer.bigframes_cloud_function" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - " Query started with request ID bigframes-dev:us-east5.821579f4-63ea-4072-a3ce-318e43768432.
SQL
SELECT\n",
-       "`bfuid_col_3` AS `bfuid_col_3`,\n",
-       "`bfuid_col_4` AS `bfuid_col_4`,\n",
-       "`bfuid_col_5` AS `bfuid_col_5`\n",
-       "FROM\n",
-       "(SELECT\n",
-       "  `t1`.`bfuid_col_3`,\n",
-       "  `t1`.`bfuid_col_4`,\n",
-       "  `t1`.`bfuid_col_5`,\n",
-       "  `t1`.`bfuid_col_6` AS `bfuid_col_7`\n",
-       "FROM (\n",
-       "  SELECT\n",
-       "    `t0`.`level_0`,\n",
-       "    `t0`.`column_0`,\n",
-       "    `t0`.`bfuid_col_6`,\n",
-       "    `t0`.`level_0` AS `bfuid_col_3`,\n",
-       "    `t0`.`column_0` AS `bfuid_col_4`,\n",
-       "    `bigframes-dev._e9a5162ae4daa9f50fda3f95febaa9781131f3b8.bigframes_sessionc10c73_49262141176cbf70037559ae84e834d3`(`t0`.`column_0`) AS `bfuid_col_5`\n",
-       "  FROM (\n",
-       "    SELECT\n",
-       "      *\n",
-       "    FROM UNNEST(ARRAY<STRUCT<`level_0` INT64, `column_0` STRING, `bfuid_col_6` INT64>>[STRUCT(0, 'What is the capital of France?', 0), STRUCT(1, 'Explain the concept of photosynthesis in simple terms.', 1), STRUCT(2, 'Write a haiku about artificial intelligence.', 2)]) AS `level_0`\n",
-       "  ) AS `t0`\n",
-       ") AS `t1`)\n",
-       "ORDER BY `bfuid_col_7` ASC NULLS LAST\n",
-       "LIMIT 10
\n", - " " - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "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", - "
questionsanswers
0What is the capital of France?The capital of France is Paris.
1Explain the concept of photosynthesis in simpl...Photosynthesis is the process by which plants ...
2Write a haiku about artificial intelligence.Here is a haiku about artificial intelligence:...
\n", - "

3 rows × 2 columns

\n", - "
[3 rows x 2 columns in total]" - ], - "text/plain": [ - " questions \\\n", - "0 What is the capital of France? \n", - "1 Explain the concept of photosynthesis in simpl... \n", - "2 Write a haiku about artificial intelligence. \n", - "\n", - " answers \n", - "0 The capital of France is Paris. \n", - "1 Photosynthesis is the process by which plants ... \n", - "2 Here is a haiku about artificial intelligence:... \n", - "\n", - "[3 rows x 2 columns]" - ] - }, - "execution_count": 8, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# Apply the remote function on the user data\n", - "df[\"answers\"] = df[\"questions\"].apply(anthropic_transformer)\n", - "df" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Clean Up" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "Session sessionc10c73 closed." - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "bpd.close_session()" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "venv (3.14.2)", - "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.14.2" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/notebooks/streaming/streaming_dataframe.ipynb b/notebooks/streaming/streaming_dataframe.ipynb deleted file mode 100644 index e3dafa98195..00000000000 --- a/notebooks/streaming/streaming_dataframe.ipynb +++ /dev/null @@ -1,581 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# BigFrames StreamingDataFrame", - "bigframes.streaming.StreamingDataFrame is a special DataFrame type that allows simple operations and can create streaming jobs to process real-time data and reverse ETL output to Bigtable and Pub/Sub using [BigQuery continuous queries](https://cloud.google.com/bigquery/docs/continuous-queries-introduction).\n", - "\n", - "In this notebook, we will:\n", - "* Create a StreamingDataFrame from a BigQuery table\n", - "* Do some operations like select, filter and preview the content\n", - "* Create and manage streaming jobs to both Bigtable and Pub/Sub" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "'1.31.0'" - ] - }, - "execution_count": 1, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "import bigframes\n", - "# make sure bigframes version >= 1.12.0\n", - "bigframes.__version__" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [], - "source": [ - "import bigframes.pandas as bpd\n", - "import bigframes.streaming as bst\n", - "bigframes.options._bigquery_options.project = \"bigframes-load-testing\" # Change to your own project ID\n", - "job_id_prefix = \"test_streaming_\"" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "Query job c72abbec-0dda-49e8-8617-4d8178659ec2 is DONE. 0 Bytes processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job d55762e7-d9d4-4a79-84a4-4975e9292158 is DONE. 28.9 kB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/plain": [ - "'birds.penguins_bigtable_streaming'" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# Copy a table from the public dataset for streaming jobs. Any changes to the table can be reflected in the streaming destination.\n", - "df = bpd.read_gbq(\"bigquery-public-data.ml_datasets.penguins\")\n", - "df.to_gbq(\"birds.penguins_bigtable_streaming\", if_exists=\"replace\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Create, select, filter and preview", - "Create the StreamingDataFrame from a BigQuery table, select certain columns, filter rows and preview the output" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/usr/local/google/home/chelsealin/src/bigframes1/bigframes/session/__init__.py:604: PreviewWarning: The bigframes.streaming module is a preview feature, and subject to change.\n", - " warnings.warn(msg, stacklevel=1, category=bfe.PreviewWarning)\n", - "/usr/local/google/home/chelsealin/src/bigframes1/bigframes/core/blocks.py:141: NullIndexPreviewWarning: Creating object with Null Index. Null Index is a preview feature.\n", - " warnings.warn(msg, category=bfe.NullIndexPreviewWarning)\n" - ] - } - ], - "source": [ - "sdf = bst.read_gbq_table(\"birds.penguins_bigtable_streaming\")" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/usr/local/google/home/chelsealin/src/bigframes1/bigframes/core/blocks.py:141: NullIndexPreviewWarning: Creating object with Null Index. Null Index is a preview feature.\n", - " warnings.warn(msg, category=bfe.NullIndexPreviewWarning)\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\n" - ] - }, - { - "data": { - "text/html": [ - "Query job 2894a764-5336-492f-98e1-c865fb161ef9 is DONE. 28.9 kB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "Query job f8fb08cb-ba11-4d73-8fff-c36081d98206 is DONE. 10.4 kB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "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", - "
speciesrowkeybody_mass_g
0Adelie Penguin (Pygoscelis adeliae)Torgersen3875.0
1Adelie Penguin (Pygoscelis adeliae)Torgersen2900.0
2Adelie Penguin (Pygoscelis adeliae)Biscoe3725.0
3Adelie Penguin (Pygoscelis adeliae)Dream2975.0
4Adelie Penguin (Pygoscelis adeliae)Torgersen3050.0
5Chinstrap penguin (Pygoscelis antarctica)Dream2700.0
6Adelie Penguin (Pygoscelis adeliae)Dream3900.0
7Adelie Penguin (Pygoscelis adeliae)Biscoe3825.0
8Chinstrap penguin (Pygoscelis antarctica)Dream3775.0
9Adelie Penguin (Pygoscelis adeliae)Dream3350.0
10Adelie Penguin (Pygoscelis adeliae)Biscoe3900.0
11Adelie Penguin (Pygoscelis adeliae)Torgersen3650.0
12Adelie Penguin (Pygoscelis adeliae)Biscoe3200.0
13Chinstrap penguin (Pygoscelis antarctica)Dream3650.0
14Adelie Penguin (Pygoscelis adeliae)Dream3700.0
15Chinstrap penguin (Pygoscelis antarctica)Dream3800.0
16Chinstrap penguin (Pygoscelis antarctica)Dream3950.0
17Chinstrap penguin (Pygoscelis antarctica)Dream3350.0
18Adelie Penguin (Pygoscelis adeliae)Dream3100.0
19Chinstrap penguin (Pygoscelis antarctica)Dream3750.0
20Adelie Penguin (Pygoscelis adeliae)Biscoe3550.0
21Chinstrap penguin (Pygoscelis antarctica)Dream3400.0
22Adelie Penguin (Pygoscelis adeliae)Torgersen3450.0
23Adelie Penguin (Pygoscelis adeliae)Torgersen3600.0
24Chinstrap penguin (Pygoscelis antarctica)Dream3650.0
\n", - "

25 rows × 3 columns

\n", - "
[165 rows x 3 columns in total]" - ], - "text/plain": [ - " species rowkey body_mass_g\n", - " Adelie Penguin (Pygoscelis adeliae) Torgersen 3875.0\n", - " Adelie Penguin (Pygoscelis adeliae) Torgersen 2900.0\n", - " Adelie Penguin (Pygoscelis adeliae) Biscoe 3725.0\n", - " Adelie Penguin (Pygoscelis adeliae) Dream 2975.0\n", - " Adelie Penguin (Pygoscelis adeliae) Torgersen 3050.0\n", - "Chinstrap penguin (Pygoscelis antarctica) Dream 2700.0\n", - " Adelie Penguin (Pygoscelis adeliae) Dream 3900.0\n", - " Adelie Penguin (Pygoscelis adeliae) Biscoe 3825.0\n", - "Chinstrap penguin (Pygoscelis antarctica) Dream 3775.0\n", - " Adelie Penguin (Pygoscelis adeliae) Dream 3350.0\n", - " Adelie Penguin (Pygoscelis adeliae) Biscoe 3900.0\n", - " Adelie Penguin (Pygoscelis adeliae) Torgersen 3650.0\n", - " Adelie Penguin (Pygoscelis adeliae) Biscoe 3200.0\n", - "Chinstrap penguin (Pygoscelis antarctica) Dream 3650.0\n", - " Adelie Penguin (Pygoscelis adeliae) Dream 3700.0\n", - "Chinstrap penguin (Pygoscelis antarctica) Dream 3800.0\n", - "Chinstrap penguin (Pygoscelis antarctica) Dream 3950.0\n", - "Chinstrap penguin (Pygoscelis antarctica) Dream 3350.0\n", - " Adelie Penguin (Pygoscelis adeliae) Dream 3100.0\n", - "Chinstrap penguin (Pygoscelis antarctica) Dream 3750.0\n", - " Adelie Penguin (Pygoscelis adeliae) Biscoe 3550.0\n", - "Chinstrap penguin (Pygoscelis antarctica) Dream 3400.0\n", - " Adelie Penguin (Pygoscelis adeliae) Torgersen 3450.0\n", - " Adelie Penguin (Pygoscelis adeliae) Torgersen 3600.0\n", - "Chinstrap penguin (Pygoscelis antarctica) Dream 3650.0\n", - "...\n", - "\n", - "[165 rows x 3 columns]" - ] - }, - "execution_count": 5, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "sdf = sdf[[\"species\", \"island\", \"body_mass_g\"]]\n", - "sdf = sdf[sdf[\"body_mass_g\"] < 4000]\n", - "# BigTable needs a rowkey column\n", - "sdf = sdf.rename(columns={\"island\": \"rowkey\"})\n", - "print(type(sdf))\n", - "sdf" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### BigTable\n", - "Create BigTable streaming job" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/usr/local/google/home/chelsealin/src/bigframes1/bigframes/streaming/dataframe.py:352: PreviewWarning: The bigframes.streaming module is a preview feature, and subject to change.\n", - " warnings.warn(msg, stacklevel=1, category=bfe.PreviewWarning)\n" - ] - } - ], - "source": [ - "job = sdf.to_bigtable(instance=\"streaming-testing-instance\", # Change to your own Bigtable instance name\n", - " table=\"garrettwu-no-col-family\", # Change to your own Bigtable table name\n", - " service_account_email=\"streaming-testing-admin@bigframes-load-testing.iam.gserviceaccount.com\", # Change to your own service account\n", - " app_profile=None,\n", - " truncate=True,\n", - " overwrite=True,\n", - " auto_create_column_families=True,\n", - " bigtable_options={},\n", - " job_id=None,\n", - " job_id_prefix=job_id_prefix,)" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "True\n", - "None\n" - ] - } - ], - "source": [ - "print(job.running())\n", - "print(job.error_result)" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "True" - ] - }, - "execution_count": 8, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "job.cancel()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Pub/Sub\n", - "Create Pub/Sub streaming job" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/usr/local/google/home/chelsealin/src/bigframes1/bigframes/core/blocks.py:141: NullIndexPreviewWarning: Creating object with Null Index. Null Index is a preview feature.\n", - " warnings.warn(msg, category=bfe.NullIndexPreviewWarning)\n" - ] - } - ], - "source": [ - "# Pub/Sub requires a single column\n", - "sdf = sdf[[\"rowkey\"]]" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/usr/local/google/home/chelsealin/src/bigframes1/bigframes/streaming/dataframe.py:464: PreviewWarning: The bigframes.streaming module is a preview feature, and subject to change.\n", - " warnings.warn(msg, stacklevel=1, category=bfe.PreviewWarning)\n" - ] - } - ], - "source": [ - "job = sdf.to_pubsub(\n", - " topic=\"penguins\", # Change to your own Pub/Sub topic ID\n", - " service_account_email=\"streaming-testing@bigframes-load-testing.iam.gserviceaccount.com\", # Change to your own service account\n", - " job_id=None,\n", - " job_id_prefix=job_id_prefix,\n", - " )" - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "True\n", - "None\n" - ] - } - ], - "source": [ - "print(job.running())\n", - "print(job.error_result)" - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "True" - ] - }, - "execution_count": 12, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "job.cancel()" - ] - } - ], - "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.12.1" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/notebooks/vertex_sdk/sdk2_bigframes_pytorch.ipynb b/notebooks/vertex_sdk/sdk2_bigframes_pytorch.ipynb new file mode 100644 index 00000000000..598d958f0c3 --- /dev/null +++ b/notebooks/vertex_sdk/sdk2_bigframes_pytorch.ipynb @@ -0,0 +1,723 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "ur8xi4C7S06n" + }, + "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": { + "id": "JAPoU8Sm5E6e" + }, + "source": [ + "# Train a pytorch model with Vertex AI SDK 2.0 and Bigframes\n", + "\n", + "\n", + " \n", + " \n", + "
\n", + " \n", + " \"Colab Run in Colab\n", + " \n", + " \n", + " \n", + " \"GitHub\n", + " View on GitHub\n", + " \n", + " \n", + " \n", + " \"VertexOpen in Vertex AI Workbench\n", + " \n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "tvgnzT1CKxrO" + }, + "source": [ + "## Overview\n", + "\n", + "This tutorial demonstrates how to train a pytorch model using Vertex AI local-to-remote training with Vertex AI SDK 2.0 and BigQuery Bigframes as the data source.\n", + "\n", + "Learn more about [bigframes](https://cloud.google.com/bigquery/docs/)." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "d975e698c9a4" + }, + "source": [ + "### Objective\n", + "\n", + "In this tutorial, you learn to use `Vertex AI SDK 2.0` with Bigframes as input data source.\n", + "\n", + "\n", + "This tutorial uses the following Google Cloud ML services:\n", + "\n", + "- `Vertex AI Training`\n", + "- `Vertex AI Remote Training`\n", + "\n", + "\n", + "The steps performed include:\n", + "\n", + "- Initialize a dataframe from a BigQuery table and split the dataset\n", + "- Perform transformations as a Vertex AI remote training.\n", + "- Train the model remotely and evaluate the model locally\n", + "\n", + "**Local-to-remote training**\n", + "\n", + "```\n", + "import vertexai\n", + "from my_module import MyModelClass\n", + "\n", + "vertexai.preview.init(remote=True, project=\"my-project\", location=\"my-location\", staging_bucket=\"gs://my-bucket\")\n", + "\n", + "# Wrap the model class with `vertex_ai.preview.remote`\n", + "MyModelClass = vertexai.preview.remote(MyModelClass)\n", + "\n", + "# Instantiate the class\n", + "model = MyModelClass(...)\n", + "\n", + "# Optional set remote config\n", + "model.fit.vertex.remote_config.display_name = \"MyModelClass-remote-training\"\n", + "model.fit.vertex.remote_config.staging_bucket = \"gs://my-bucket\"\n", + "\n", + "# This `fit` call will be executed remotely\n", + "model.fit(...)\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "08d289fa873f" + }, + "source": [ + "### Dataset\n", + "\n", + "This tutorial uses the IRIS dataset, which predicts the iris species." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "aed92deeb4a0" + }, + "source": [ + "### Costs\n", + "\n", + "This tutorial uses billable components of Google Cloud:\n", + "\n", + "* Vertex AI\n", + "* BigQuery\n", + "* Cloud Storage\n", + "\n", + "Learn about [Vertex AI pricing](https://cloud.google.com/vertex-ai/pricing),\n", + "[BigQuery pricing](https://cloud.google.com/bigquery/pricing),\n", + "and [Cloud Storage pricing](https://cloud.google.com/storage/pricing), \n", + "and use the [Pricing Calculator](https://cloud.google.com/products/calculator/)\n", + "to generate a cost estimate based on your projected usage." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "i7EUnXsZhAGF" + }, + "source": [ + "## Installation\n", + "\n", + "Install the following packages required to execute this notebook. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "2b4ef9b72d43" + }, + "outputs": [], + "source": [ + "# Install the packages\n", + "! pip3 install --upgrade --quiet google-cloud-aiplatform[preview]\n", + "! pip3 install --upgrade --quiet bigframes\n", + "! pip3 install --upgrade --quiet torch" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "58707a750154" + }, + "source": [ + "### Colab only: Uncomment the following cell to restart the kernel." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "f200f10a1da3" + }, + "outputs": [], + "source": [ + "# Automatically restart kernel after installs so that your environment can access the new packages\n", + "# import IPython\n", + "\n", + "# app = IPython.Application.instance()\n", + "# app.kernel.do_shutdown(True)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "BF1j6f9HApxa" + }, + "source": [ + "## Before you begin\n", + "\n", + "### Set up your Google Cloud project\n", + "\n", + "**The following steps are required, regardless of your notebook environment.**\n", + "\n", + "1. [Select or create a Google Cloud project](https://console.cloud.google.com/cloud-resource-manager). When you first create an account, you get a $300 free credit towards your compute/storage costs.\n", + "\n", + "2. [Make sure that billing is enabled for your project](https://cloud.google.com/billing/docs/how-to/modify-project).\n", + "\n", + "3. [Enable the Vertex AI API](https://console.cloud.google.com/flows/enableapi?apiid=aiplatform.googleapis.com).\n", + "\n", + "4. If you are running this notebook locally, you need to install the [Cloud SDK](https://cloud.google.com/sdk)." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "WReHDGG5g0XY" + }, + "source": [ + "#### Set your project ID\n", + "\n", + "**If you don't know your project ID**, try the following:\n", + "* Run `gcloud config list`.\n", + "* Run `gcloud projects list`.\n", + "* See the support page: [Locate the project ID](https://support.google.com/googleapi/answer/7014113)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "oM1iC_MfAts1" + }, + "outputs": [], + "source": [ + "PROJECT_ID = \"[your-project-id]\" # @param {type:\"string\"}\n", + "\n", + "# Set the project id\n", + "! gcloud config set project {PROJECT_ID}" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "region" + }, + "source": [ + "#### Region\n", + "\n", + "You can also change the `REGION` variable used by Vertex AI. Learn more about [Vertex AI regions](https://cloud.google.com/vertex-ai/docs/general/locations)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "region" + }, + "outputs": [], + "source": [ + "REGION = \"us-central1\" # @param {type: \"string\"}" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "sBCra4QMA2wR" + }, + "source": [ + "### Authenticate your Google Cloud account\n", + "\n", + "Depending on your Jupyter environment, you may have to manually authenticate. Follow the relevant instructions below." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "74ccc9e52986" + }, + "source": [ + "**1. Vertex AI Workbench**\n", + "* Do nothing as you are already authenticated." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "de775a3773ba" + }, + "source": [ + "**2. Local JupyterLab instance, uncomment and run:**" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "254614fa0c46" + }, + "outputs": [], + "source": [ + "# ! gcloud auth login" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "ef21552ccea8" + }, + "source": [ + "**3. Colab, uncomment and run:**" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "603adbbf0532" + }, + "outputs": [], + "source": [ + "# from google.colab import auth\n", + "# auth.authenticate_user()" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "f6b2ccc891ed" + }, + "source": [ + "**4. Service account or other**\n", + "* See how to grant Cloud Storage permissions to your service account at https://cloud.google.com/storage/docs/gsutil/commands/iam#ch-examples." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "zgPO1eR3CYjk" + }, + "source": [ + "### Create a Cloud Storage bucket\n", + "\n", + "Create a storage bucket to store intermediate artifacts such as datasets." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "MzGDU7TWdts_" + }, + "outputs": [], + "source": [ + "BUCKET_URI = f\"gs://your-bucket-name-{PROJECT_ID}-unique\" # @param {type:\"string\"}" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "-EcIXiGsCePi" + }, + "source": [ + "**Only if your bucket doesn't already exist**: Run the following cell to create your Cloud Storage bucket." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "NIq7R4HZCfIc" + }, + "outputs": [], + "source": [ + "! gsutil mb -l {REGION} -p {PROJECT_ID} {BUCKET_URI}" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "960505627ddf" + }, + "source": [ + "### Import libraries and define constants" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "PyQmSRbKA8r-" + }, + "outputs": [], + "source": [ + "import bigframes.pandas as bf\n", + "import torch\n", + "import vertexai\n", + "from vertexai.preview import VertexModel\n", + "\n", + "bf.options.bigquery.location = \"us\" # Dataset is in 'us' not 'us-central1'\n", + "bf.options.bigquery.project = PROJECT_ID\n", + "\n", + "from bigframes.ml.model_selection import \\\n", + " train_test_split as bf_train_test_split" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "init_aip:mbsdk,all" + }, + "source": [ + "## Initialize Vertex AI SDK for Python\n", + "\n", + "Initialize the Vertex AI SDK for Python for your project and corresponding bucket." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "init_aip:mbsdk,all" + }, + "outputs": [], + "source": [ + "vertexai.init(\n", + " project=PROJECT_ID,\n", + " location=REGION,\n", + " staging_bucket=BUCKET_URI,\n", + ")\n", + "\n", + "REMOTE_JOB_NAME = \"sdk2-bigframes-pytorch\"\n", + "REMOTE_JOB_BUCKET = f\"{BUCKET_URI}/{REMOTE_JOB_NAME}\"" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "105334524e96" + }, + "source": [ + "## Prepare the dataset\n", + "\n", + "Now load the Iris dataset and split the data into train and test sets." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "b44cdc4e03f1" + }, + "outputs": [], + "source": [ + "df = bf.read_gbq(\"bigquery-public-data.ml_datasets.iris\")\n", + "\n", + "species_categories = {\n", + " \"versicolor\": 0,\n", + " \"virginica\": 1,\n", + " \"setosa\": 2,\n", + "}\n", + "df[\"species\"] = df[\"species\"].map(species_categories)\n", + "\n", + "# Assign an index column name\n", + "index_col = \"index\"\n", + "df.index.name = index_col" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "9cb8616b1997" + }, + "outputs": [], + "source": [ + "feature_columns = df[[\"sepal_length\", \"sepal_width\", \"petal_length\", \"petal_width\"]]\n", + "label_columns = df[[\"species\"]]\n", + "train_X, test_X, train_y, test_y = bf_train_test_split(\n", + " feature_columns, label_columns, test_size=0.2\n", + ")\n", + "\n", + "print(\"X_train size: \", train_X.size)\n", + "print(\"X_test size: \", test_X.size)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "23fe7b734b08" + }, + "outputs": [], + "source": [ + "# Switch to remote mode for training\n", + "vertexai.preview.init(remote=True)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "5904a0f1bb03" + }, + "source": [ + "## PyTorch remote training with CPU (Custom PyTorch model)\n", + "\n", + "First, train a PyTorch model as a remote training job:\n", + "\n", + "- Reinitialize Vertex AI for remote training.\n", + "- Set TorchLogisticRegression for the remote training job.\n", + "- Invoke TorchLogisticRegression locally which will launch the remote training job." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "2a1b85195a17" + }, + "outputs": [], + "source": [ + "# define the custom model\n", + "class TorchLogisticRegression(VertexModel, torch.nn.Module):\n", + " def __init__(self, input_size: int, output_size: int):\n", + " torch.nn.Module.__init__(self)\n", + " VertexModel.__init__(self)\n", + " self.linear = torch.nn.Linear(input_size, output_size)\n", + " self.softmax = torch.nn.Softmax(dim=1)\n", + "\n", + " def forward(self, x):\n", + " return self.softmax(self.linear(x))\n", + "\n", + " @vertexai.preview.developer.mark.train()\n", + " def train(self, X, y, num_epochs, lr):\n", + " X = X.to(torch.float32)\n", + " y = torch.flatten(y) # necessary to get 1D tensor\n", + " dataloader = torch.utils.data.DataLoader(\n", + " torch.utils.data.TensorDataset(X, y),\n", + " batch_size=10,\n", + " shuffle=True,\n", + " generator=torch.Generator(device=X.device),\n", + " )\n", + "\n", + " criterion = torch.nn.CrossEntropyLoss()\n", + " optimizer = torch.optim.SGD(self.parameters(), lr=lr)\n", + "\n", + " for t in range(num_epochs):\n", + " for batch, (X, y) in enumerate(dataloader):\n", + " optimizer.zero_grad()\n", + " pred = self(X)\n", + " loss = criterion(pred, y)\n", + " loss.backward()\n", + " optimizer.step()\n", + "\n", + " @vertexai.preview.developer.mark.predict()\n", + " def predict(self, X):\n", + " X = torch.tensor(X).to(torch.float32)\n", + " with torch.no_grad():\n", + " pred = torch.argmax(self(X), dim=1)\n", + " return pred" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "4e35593f520a" + }, + "outputs": [], + "source": [ + "# Switch to remote mode for training\n", + "vertexai.preview.init(remote=True)\n", + "\n", + "# Instantiate model\n", + "model = TorchLogisticRegression(4, 3)\n", + "\n", + "# Set training config\n", + "model.train.vertex.remote_config.custom_commands = [\n", + " \"pip install torchdata\",\n", + " \"pip install torcharrow\",\n", + "]\n", + "model.train.vertex.remote_config.display_name = REMOTE_JOB_NAME + \"-torch-model\"\n", + "model.train.vertex.remote_config.staging_bucket = REMOTE_JOB_BUCKET\n", + "\n", + "# Train model on Vertex\n", + "model.train(train_X, train_y, num_epochs=200, lr=0.05)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "edf4d0708f02" + }, + "source": [ + "## Remote prediction\n", + "\n", + "Obtain predictions from the trained model." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "42dfbff0ca15" + }, + "outputs": [], + "source": [ + "vertexai.preview.init(remote=True)\n", + "\n", + "# Set remote config\n", + "model.predict.vertex.remote_config.custom_commands = [\n", + " \"pip install torchdata\",\n", + " \"pip install torcharrow\",\n", + "]\n", + "model.predict.vertex.remote_config.display_name = REMOTE_JOB_NAME + \"-torch-predict\"\n", + "model.predict.vertex.remote_config.staging_bucket = REMOTE_JOB_BUCKET\n", + "\n", + "predictions = model.predict(test_X)\n", + "\n", + "print(f\"Remote predictions: {predictions}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "4340ed8316cd" + }, + "source": [ + "## Local evaluation\n", + "\n", + "Evaluate model results locally." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "eb27a31cec6f" + }, + "outputs": [], + "source": [ + "# User must convert bigframes to torch tensor for local evaluation\n", + "train_X_tensor = torch.from_numpy(\n", + " train_X.to_pandas().reset_index().drop(columns=[\"index\"]).values.astype(float)\n", + ")\n", + "train_y_tensor = torch.from_numpy(\n", + " train_y.to_pandas().reset_index().drop(columns=[\"index\"]).values.astype(float)\n", + ")\n", + "\n", + "test_X_tensor = torch.from_numpy(\n", + " test_X.to_pandas().reset_index().drop(columns=[\"index\"]).values.astype(float)\n", + ")\n", + "test_y_tensor = torch.from_numpy(\n", + " test_y.to_pandas().reset_index().drop(columns=[\"index\"]).values.astype(float)\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "7db44ad81389" + }, + "outputs": [], + "source": [ + "from sklearn.metrics import accuracy_score\n", + "\n", + "# Switch to local mode for evaluation\n", + "vertexai.preview.init(remote=False)\n", + "\n", + "# Evaluate model's accuracy score\n", + "print(\n", + " f\"Train accuracy: {accuracy_score(train_y_tensor, model.predict(train_X_tensor))}\"\n", + ")\n", + "\n", + "print(f\"Test accuracy: {accuracy_score(test_y_tensor, model.predict(test_X_tensor))}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "TpV-iwP9qw9c" + }, + "source": [ + "## Cleaning up\n", + "\n", + "To clean up all Google Cloud resources used in this project, you can [delete the Google Cloud\n", + "project](https://cloud.google.com/resource-manager/docs/creating-managing-projects#shutting_down_projects) you used for the tutorial.\n", + "\n", + "Otherwise, you can delete the individual resources you created in this tutorial:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "sx_vKniMq9ZX" + }, + "outputs": [], + "source": [ + "import os\n", + "\n", + "# Delete Cloud Storage objects that were created\n", + "delete_bucket = False\n", + "if delete_bucket or os.getenv(\"IS_TESTING\"):\n", + " ! gsutil -m rm -r $BUCKET_URI" + ] + } + ], + "metadata": { + "colab": { + "collapsed_sections": [], + "name": "sdk2_bigframes_pytorch.ipynb", + "toc_visible": true + }, + "kernelspec": { + "display_name": "Python 3", + "name": "python3" + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} diff --git a/notebooks/vertex_sdk/sdk2_bigframes_sklearn.ipynb b/notebooks/vertex_sdk/sdk2_bigframes_sklearn.ipynb new file mode 100644 index 00000000000..021c0707535 --- /dev/null +++ b/notebooks/vertex_sdk/sdk2_bigframes_sklearn.ipynb @@ -0,0 +1,727 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "ur8xi4C7S06n" + }, + "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": { + "id": "JAPoU8Sm5E6e" + }, + "source": [ + "# Train a scikit-learn model with Vertex AI SDK 2.0 and Bigframes\n", + "\n", + "\n", + " \n", + " \n", + "
\n", + " \n", + " \"Colab Run in Colab\n", + " \n", + " \n", + " \n", + " \"GitHub\n", + " View on GitHub\n", + " \n", + " \n", + " \n", + " \"VertexOpen in Vertex AI Workbench\n", + " \n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "tvgnzT1CKxrO" + }, + "source": [ + "## Overview\n", + "\n", + "This tutorial demonstrates how to train a scikit-learn model using Vertex AI local-to-remote training with Vertex AI SDK 2.0 and BigQuery Bigframes as the data source.\n", + "\n", + "Learn more about [bigframes](https://cloud.google.com/bigquery/docs/)." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "d975e698c9a4" + }, + "source": [ + "### Objective\n", + "\n", + "In this tutorial, you learn to use `Vertex AI SDK 2.0` with Bigframes as input data source.\n", + "\n", + "\n", + "This tutorial uses the following Google Cloud ML services:\n", + "\n", + "- `Vertex AI Training`\n", + "- `Vertex AI Remote Training`\n", + "\n", + "\n", + "The steps performed include:\n", + "\n", + "- Initialize a dataframe from a BigQuery table and split the dataset\n", + "- Perform transformations as a Vertex AI remote training.\n", + "- Train the model remotely and evaluate the model locally\n", + "\n", + "**Local-to-remote training**\n", + "\n", + "```\n", + "import vertexai\n", + "from my_module import MyModelClass\n", + "\n", + "vertexai.preview.init(remote=True, project=\"my-project\", location=\"my-location\", staging_bucket=\"gs://my-bucket\")\n", + "\n", + "# Wrap the model class with `vertex_ai.preview.remote`\n", + "MyModelClass = vertexai.preview.remote(MyModelClass)\n", + "\n", + "# Instantiate the class\n", + "model = MyModelClass(...)\n", + "\n", + "# Optional set remote config\n", + "model.fit.vertex.remote_config.display_name = \"MyModelClass-remote-training\"\n", + "model.fit.vertex.remote_config.staging_bucket = \"gs://my-bucket\"\n", + "\n", + "# This `fit` call will be executed remotely\n", + "model.fit(...)\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "08d289fa873f" + }, + "source": [ + "### Dataset\n", + "\n", + "This tutorial uses the IRIS dataset, which predicts the iris species." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "aed92deeb4a0" + }, + "source": [ + "### Costs\n", + "\n", + "This tutorial uses billable components of Google Cloud:\n", + "\n", + "* Vertex AI\n", + "* BigQuery\n", + "* Cloud Storage\n", + "\n", + "Learn about [Vertex AI pricing](https://cloud.google.com/vertex-ai/pricing),\n", + "[BigQuery pricing](https://cloud.google.com/bigquery/pricing),\n", + "and [Cloud Storage pricing](https://cloud.google.com/storage/pricing), \n", + "and use the [Pricing Calculator](https://cloud.google.com/products/calculator/)\n", + "to generate a cost estimate based on your projected usage." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "i7EUnXsZhAGF" + }, + "source": [ + "## Installation\n", + "\n", + "Install the following packages required to execute this notebook. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "2b4ef9b72d43" + }, + "outputs": [], + "source": [ + "# Install the packages\n", + "! pip3 install --upgrade --quiet google-cloud-aiplatform[preview]\n", + "! pip3 install --upgrade --quiet bigframes" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "58707a750154" + }, + "source": [ + "### Colab only: Uncomment the following cell to restart the kernel." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "f200f10a1da3" + }, + "outputs": [], + "source": [ + "# Automatically restart kernel after installs so that your environment can access the new packages\n", + "# import IPython\n", + "\n", + "# app = IPython.Application.instance()\n", + "# app.kernel.do_shutdown(True)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "BF1j6f9HApxa" + }, + "source": [ + "## Before you begin\n", + "\n", + "### Set up your Google Cloud project\n", + "\n", + "**The following steps are required, regardless of your notebook environment.**\n", + "\n", + "1. [Select or create a Google Cloud project](https://console.cloud.google.com/cloud-resource-manager). When you first create an account, you get a $300 free credit towards your compute/storage costs.\n", + "\n", + "2. [Make sure that billing is enabled for your project](https://cloud.google.com/billing/docs/how-to/modify-project).\n", + "\n", + "3. [Enable the Vertex AI API](https://console.cloud.google.com/flows/enableapi?apiid=aiplatform.googleapis.com).\n", + "\n", + "4. If you are running this notebook locally, you need to install the [Cloud SDK](https://cloud.google.com/sdk)." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "WReHDGG5g0XY" + }, + "source": [ + "#### Set your project ID\n", + "\n", + "**If you don't know your project ID**, try the following:\n", + "* Run `gcloud config list`.\n", + "* Run `gcloud projects list`.\n", + "* See the support page: [Locate the project ID](https://support.google.com/googleapi/answer/7014113)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "oM1iC_MfAts1" + }, + "outputs": [], + "source": [ + "PROJECT_ID = \"[your-project-id]\" # @param {type:\"string\"}\n", + "\n", + "# Set the project id\n", + "! gcloud config set project {PROJECT_ID}" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "region" + }, + "source": [ + "#### Region\n", + "\n", + "You can also change the `REGION` variable used by Vertex AI. Learn more about [Vertex AI regions](https://cloud.google.com/vertex-ai/docs/general/locations)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "region" + }, + "outputs": [], + "source": [ + "REGION = \"us-central1\" # @param {type: \"string\"}" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "sBCra4QMA2wR" + }, + "source": [ + "### Authenticate your Google Cloud account\n", + "\n", + "Depending on your Jupyter environment, you may have to manually authenticate. Follow the relevant instructions below." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "74ccc9e52986" + }, + "source": [ + "**1. Vertex AI Workbench**\n", + "* Do nothing as you are already authenticated." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "de775a3773ba" + }, + "source": [ + "**2. Local JupyterLab instance, uncomment and run:**" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "254614fa0c46" + }, + "outputs": [], + "source": [ + "# ! gcloud auth login" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "ef21552ccea8" + }, + "source": [ + "**3. Colab, uncomment and run:**" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "603adbbf0532" + }, + "outputs": [], + "source": [ + "# from google.colab import auth\n", + "# auth.authenticate_user()" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "f6b2ccc891ed" + }, + "source": [ + "**4. Service account or other**\n", + "* See how to grant Cloud Storage permissions to your service account at https://cloud.google.com/storage/docs/gsutil/commands/iam#ch-examples." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "zgPO1eR3CYjk" + }, + "source": [ + "### Create a Cloud Storage bucket\n", + "\n", + "Create a storage bucket to store intermediate artifacts such as datasets." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "MzGDU7TWdts_" + }, + "outputs": [], + "source": [ + "BUCKET_URI = f\"gs://your-bucket-name-{PROJECT_ID}-unique\" # @param {type:\"string\"}" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "-EcIXiGsCePi" + }, + "source": [ + "**Only if your bucket doesn't already exist**: Run the following cell to create your Cloud Storage bucket." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "NIq7R4HZCfIc" + }, + "outputs": [], + "source": [ + "! gsutil mb -l {REGION} -p {PROJECT_ID} {BUCKET_URI}" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "960505627ddf" + }, + "source": [ + "### Import libraries and define constants" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "PyQmSRbKA8r-" + }, + "outputs": [], + "source": [ + "import bigframes.pandas as bf\n", + "import vertexai\n", + "\n", + "bf.options.bigquery.location = \"us\" # Dataset is in 'us' not 'us-central1'\n", + "bf.options.bigquery.project = PROJECT_ID\n", + "\n", + "from bigframes.ml.model_selection import \\\n", + " train_test_split as bf_train_test_split\n", + "\n", + "REMOTE_JOB_NAME = \"sdk2-bigframes-sklearn\"\n", + "REMOTE_JOB_BUCKET = f\"{BUCKET_URI}/{REMOTE_JOB_NAME}\"" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "init_aip:mbsdk,all" + }, + "source": [ + "## Initialize Vertex AI SDK for Python\n", + "\n", + "Initialize the Vertex AI SDK for Python for your project and corresponding bucket." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "init_aip:mbsdk,all" + }, + "outputs": [], + "source": [ + "vertexai.init(\n", + " project=PROJECT_ID,\n", + " location=REGION,\n", + " staging_bucket=BUCKET_URI,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "105334524e96" + }, + "source": [ + "## Prepare the dataset\n", + "\n", + "Now load the Iris dataset and split the data into train and test sets." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "b44cdc4e03f1" + }, + "outputs": [], + "source": [ + "df = bf.read_gbq(\"bigquery-public-data.ml_datasets.iris\")\n", + "\n", + "species_categories = {\n", + " \"versicolor\": 0,\n", + " \"virginica\": 1,\n", + " \"setosa\": 2,\n", + "}\n", + "df[\"species\"] = df[\"species\"].map(species_categories)\n", + "\n", + "# Assign an index column name\n", + "index_col = \"index\"\n", + "df.index.name = index_col" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "9cb8616b1997" + }, + "outputs": [], + "source": [ + "feature_columns = df[[\"sepal_length\", \"sepal_width\", \"petal_length\", \"petal_width\"]]\n", + "label_columns = df[[\"species\"]]\n", + "train_X, test_X, train_y, test_y = bf_train_test_split(\n", + " feature_columns, label_columns, test_size=0.2\n", + ")\n", + "\n", + "print(\"X_train size: \", train_X.size)\n", + "print(\"X_test size: \", test_X.size)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "8306545fcc57" + }, + "source": [ + "## Feature transformation\n", + "\n", + "Next, you do feature transformations on the data using the Vertex AI remote training service.\n", + "\n", + "First, you re-initialize Vertex AI to enable remote training." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "55e701c31036" + }, + "outputs": [], + "source": [ + "# Switch to remote mode for training\n", + "vertexai.preview.init(remote=True)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "4a0e9d59b273" + }, + "source": [ + "### Execute remote job for fit_transform() on training data\n", + "\n", + "Next, indicate that the `StandardScalar` class is to be executed remotely. Then set up the data transform and call the `fit_transform()` method is executed remotely." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "90333089d362" + }, + "outputs": [], + "source": [ + "from sklearn.preprocessing import StandardScaler\n", + "\n", + "# Wrap classes to enable Vertex remote execution\n", + "StandardScaler = vertexai.preview.remote(StandardScaler)\n", + "\n", + "# Instantiate transformer\n", + "transformer = StandardScaler()\n", + "\n", + "# Set training config\n", + "transformer.fit_transform.vertex.remote_config.display_name = (\n", + " f\"{REMOTE_JOB_NAME}-fit-transformer-bigframes\"\n", + ")\n", + "transformer.fit_transform.vertex.remote_config.staging_bucket = REMOTE_JOB_BUCKET\n", + "\n", + "# Execute transformer on Vertex (train_X is bigframes.dataframe.DataFrame, X_train is np.array)\n", + "X_train = transformer.fit_transform(train_X)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "6bf95574c907" + }, + "source": [ + "### Remote transform on test data" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "da6eea22a89a" + }, + "outputs": [], + "source": [ + "# Transform test dataset before calculate test score\n", + "transformer.transform.vertex.remote_config.display_name = (\n", + " REMOTE_JOB_NAME + \"-transformer\"\n", + ")\n", + "transformer.transform.vertex.remote_config.staging_bucket = REMOTE_JOB_BUCKET\n", + "\n", + "# Execute transformer on Vertex (test_X is bigframes.dataframe.DataFrame, X_test is np.array)\n", + "X_test = transformer.transform(test_X)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "ddf906c886e4" + }, + "source": [ + "## Remote training\n", + "\n", + "First, train the scikit-learn model as a remote training job:\n", + "\n", + "- Set LogisticRegression for the remote training job.\n", + "- Invoke LogisticRegression locally which will launch the remote training job." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "c7b0116fa60c" + }, + "outputs": [], + "source": [ + "from sklearn.linear_model import LogisticRegression\n", + "\n", + "# Wrap classes to enable Vertex remote execution\n", + "LogisticRegression = vertexai.preview.remote(LogisticRegression)\n", + "\n", + "# Instantiate model, warm_start=True for uptraining\n", + "model = LogisticRegression(warm_start=True)\n", + "\n", + "# Set training config\n", + "model.fit.vertex.remote_config.display_name = REMOTE_JOB_NAME + \"-sklearn-model\"\n", + "model.fit.vertex.remote_config.staging_bucket = REMOTE_JOB_BUCKET\n", + "\n", + "# Train model on Vertex\n", + "model.fit(train_X, train_y)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "ffe1d5903bcb" + }, + "source": [ + "## Remote prediction\n", + "\n", + "Obtain predictions from the trained model." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "d00ce35920fa" + }, + "outputs": [], + "source": [ + "# Remote evaluation\n", + "vertexai.preview.init(remote=True)\n", + "\n", + "# Evaluate model's accuracy score\n", + "predictions = model.predict(test_X)\n", + "\n", + "print(f\"Remote predictions: {predictions}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "a8cd6cbd4403" + }, + "source": [ + "## Local evaluation\n", + "\n", + "Score model results locally." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "dc105dafdfb9" + }, + "outputs": [], + "source": [ + "# User must convert bigframes to pandas dataframe for local evaluation\n", + "train_X_pd = train_X.to_pandas().reset_index(drop=True)\n", + "train_y_pd = train_y.to_pandas().reset_index(drop=True)\n", + "\n", + "test_X_pd = test_X.to_pandas().reset_index(drop=True)\n", + "test_y_pd = test_y.to_pandas().reset_index(drop=True)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "25fec549de69" + }, + "outputs": [], + "source": [ + "# Switch to local mode for testing\n", + "vertexai.preview.init(remote=False)\n", + "\n", + "# Evaluate model's accuracy score\n", + "print(f\"Train accuracy: {model.score(train_X_pd, train_y_pd)}\")\n", + "\n", + "print(f\"Test accuracy: {model.score(test_X_pd, test_y_pd)}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "TpV-iwP9qw9c" + }, + "source": [ + "## Cleaning up\n", + "\n", + "To clean up all Google Cloud resources used in this project, you can [delete the Google Cloud\n", + "project](https://cloud.google.com/resource-manager/docs/creating-managing-projects#shutting_down_projects) you used for the tutorial.\n", + "\n", + "Otherwise, you can delete the individual resources you created in this tutorial:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "sx_vKniMq9ZX" + }, + "outputs": [], + "source": [ + "import os\n", + "\n", + "# Delete Cloud Storage objects that were created\n", + "delete_bucket = False\n", + "if delete_bucket or os.getenv(\"IS_TESTING\"):\n", + " ! gsutil -m rm -r $BUCKET_URI" + ] + } + ], + "metadata": { + "colab": { + "collapsed_sections": [], + "name": "sdk2_bigframes_sklearn.ipynb", + "toc_visible": true + }, + "kernelspec": { + "display_name": "Python 3", + "name": "python3" + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} diff --git a/notebooks/vertex_sdk/sdk2_bigframes_tensorflow.ipynb b/notebooks/vertex_sdk/sdk2_bigframes_tensorflow.ipynb new file mode 100644 index 00000000000..e6843b66b57 --- /dev/null +++ b/notebooks/vertex_sdk/sdk2_bigframes_tensorflow.ipynb @@ -0,0 +1,646 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "ur8xi4C7S06n" + }, + "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": { + "id": "JAPoU8Sm5E6e" + }, + "source": [ + "# Train a Tensorflow Keras model with Vertex AI SDK 2.0 and Bigframes \n", + "\n", + "\n", + " \n", + " \n", + "
\n", + " \n", + " \"Colab Run in Colab\n", + " \n", + " \n", + " \n", + " \"GitHub\n", + " View on GitHub\n", + " \n", + " \n", + " \n", + " \"VertexOpen in Vertex AI Workbench\n", + " \n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "tvgnzT1CKxrO" + }, + "source": [ + "## Overview\n", + "\n", + "This tutorial demonstrates how to train a tensorflow keras model using Vertex AI local-to-remote training with Vertex AI SDK 2.0 and BigQuery Bigframes as the data source.\n", + "\n", + "Learn more about [bigframes](https://cloud.google.com/bigquery/docs/)." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "d975e698c9a4" + }, + "source": [ + "### Objective\n", + "\n", + "In this tutorial, you learn to use `Vertex AI SDK 2.0` with Bigframes as input data source.\n", + "\n", + "\n", + "This tutorial uses the following Google Cloud ML services:\n", + "\n", + "- `Vertex AI Training`\n", + "- `Vertex AI Remote Training`\n", + "\n", + "\n", + "The steps performed include:\n", + "\n", + "- Initialize a dataframe from a BigQuery table and split the dataset\n", + "- Perform transformations as a Vertex AI remote training.\n", + "- Train the model remotely and evaluate the model locally\n", + "\n", + "**Local-to-remote training**\n", + "\n", + "```\n", + "import vertexai\n", + "from my_module import MyModelClass\n", + "\n", + "vertexai.preview.init(remote=True, project=\"my-project\", location=\"my-location\", staging_bucket=\"gs://my-bucket\")\n", + "\n", + "# Wrap the model class with `vertex_ai.preview.remote`\n", + "MyModelClass = vertexai.preview.remote(MyModelClass)\n", + "\n", + "# Instantiate the class\n", + "model = MyModelClass(...)\n", + "\n", + "# Optional set remote config\n", + "model.fit.vertex.remote_config.display_name = \"MyModelClass-remote-training\"\n", + "model.fit.vertex.remote_config.staging_bucket = \"gs://my-bucket\"\n", + "\n", + "# This `fit` call will be executed remotely\n", + "model.fit(...)\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "08d289fa873f" + }, + "source": [ + "### Dataset\n", + "\n", + "This tutorial uses the IRIS dataset, which predicts the iris species." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "aed92deeb4a0" + }, + "source": [ + "### Costs\n", + "\n", + "This tutorial uses billable components of Google Cloud:\n", + "\n", + "* Vertex AI\n", + "* BigQuery\n", + "* Cloud Storage\n", + "\n", + "Learn about [Vertex AI pricing](https://cloud.google.com/vertex-ai/pricing),\n", + "[BigQuery pricing](https://cloud.google.com/bigquery/pricing),\n", + "and [Cloud Storage pricing](https://cloud.google.com/storage/pricing), \n", + "and use the [Pricing Calculator](https://cloud.google.com/products/calculator/)\n", + "to generate a cost estimate based on your projected usage." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "i7EUnXsZhAGF" + }, + "source": [ + "## Installation\n", + "\n", + "Install the following packages required to execute this notebook. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "2b4ef9b72d43" + }, + "outputs": [], + "source": [ + "# Install the packages\n", + "! pip3 install --upgrade --quiet google-cloud-aiplatform[preview]\n", + "! pip3 install --upgrade --quiet bigframes\n", + "! pip3 install --upgrade --quiet tensorflow==2.12.0" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "58707a750154" + }, + "source": [ + "### Colab only: Uncomment the following cell to restart the kernel." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "f200f10a1da3" + }, + "outputs": [], + "source": [ + "# Automatically restart kernel after installs so that your environment can access the new packages\n", + "# import IPython\n", + "\n", + "# app = IPython.Application.instance()\n", + "# app.kernel.do_shutdown(True)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "BF1j6f9HApxa" + }, + "source": [ + "## Before you begin\n", + "\n", + "### Set up your Google Cloud project\n", + "\n", + "**The following steps are required, regardless of your notebook environment.**\n", + "\n", + "1. [Select or create a Google Cloud project](https://console.cloud.google.com/cloud-resource-manager). When you first create an account, you get a $300 free credit towards your compute/storage costs.\n", + "\n", + "2. [Make sure that billing is enabled for your project](https://cloud.google.com/billing/docs/how-to/modify-project).\n", + "\n", + "3. [Enable the Vertex AI API](https://console.cloud.google.com/flows/enableapi?apiid=aiplatform.googleapis.com).\n", + "\n", + "4. If you are running this notebook locally, you need to install the [Cloud SDK](https://cloud.google.com/sdk)." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "WReHDGG5g0XY" + }, + "source": [ + "#### Set your project ID\n", + "\n", + "**If you don't know your project ID**, try the following:\n", + "* Run `gcloud config list`.\n", + "* Run `gcloud projects list`.\n", + "* See the support page: [Locate the project ID](https://support.google.com/googleapi/answer/7014113)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "oM1iC_MfAts1" + }, + "outputs": [], + "source": [ + "PROJECT_ID = \"[your-project-id]\" # @param {type:\"string\"}\n", + "\n", + "# Set the project id\n", + "! gcloud config set project {PROJECT_ID}" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "region" + }, + "source": [ + "#### Region\n", + "\n", + "You can also change the `REGION` variable used by Vertex AI. Learn more about [Vertex AI regions](https://cloud.google.com/vertex-ai/docs/general/locations)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "region" + }, + "outputs": [], + "source": [ + "REGION = \"us-central1\" # @param {type: \"string\"}" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "sBCra4QMA2wR" + }, + "source": [ + "### Authenticate your Google Cloud account\n", + "\n", + "Depending on your Jupyter environment, you may have to manually authenticate. Follow the relevant instructions below." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "74ccc9e52986" + }, + "source": [ + "**1. Vertex AI Workbench**\n", + "* Do nothing as you are already authenticated." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "de775a3773ba" + }, + "source": [ + "**2. Local JupyterLab instance, uncomment and run:**" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "254614fa0c46" + }, + "outputs": [], + "source": [ + "# ! gcloud auth login" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "ef21552ccea8" + }, + "source": [ + "**3. Colab, uncomment and run:**" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "603adbbf0532" + }, + "outputs": [], + "source": [ + "# from google.colab import auth\n", + "# auth.authenticate_user()" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "f6b2ccc891ed" + }, + "source": [ + "**4. Service account or other**\n", + "* See how to grant Cloud Storage permissions to your service account at https://cloud.google.com/storage/docs/gsutil/commands/iam#ch-examples." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "zgPO1eR3CYjk" + }, + "source": [ + "### Create a Cloud Storage bucket\n", + "\n", + "Create a storage bucket to store intermediate artifacts such as datasets." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "MzGDU7TWdts_" + }, + "outputs": [], + "source": [ + "BUCKET_URI = f\"gs://your-bucket-name-{PROJECT_ID}-unique\" # @param {type:\"string\"}" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "-EcIXiGsCePi" + }, + "source": [ + "**Only if your bucket doesn't already exist**: Run the following cell to create your Cloud Storage bucket." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "NIq7R4HZCfIc" + }, + "outputs": [], + "source": [ + "! gsutil mb -l {REGION} -p {PROJECT_ID} {BUCKET_URI}" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "960505627ddf" + }, + "source": [ + "### Import libraries and define constants" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "PyQmSRbKA8r-" + }, + "outputs": [], + "source": [ + "import bigframes.pandas as bf\n", + "import tensorflow as tf\n", + "import vertexai\n", + "from tensorflow import keras\n", + "\n", + "bf.options.bigquery.location = \"us\" # Dataset is in 'us' not 'us-central1'\n", + "bf.options.bigquery.project = PROJECT_ID\n", + "\n", + "from bigframes.ml.model_selection import \\\n", + " train_test_split as bf_train_test_split" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "init_aip:mbsdk,all" + }, + "source": [ + "## Initialize Vertex AI SDK for Python\n", + "\n", + "Initialize the Vertex AI SDK for Python for your project and corresponding bucket." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "init_aip:mbsdk,all" + }, + "outputs": [], + "source": [ + "vertexai.init(\n", + " project=PROJECT_ID,\n", + " location=REGION,\n", + " staging_bucket=BUCKET_URI,\n", + ")\n", + "\n", + "REMOTE_JOB_NAME = \"sdk2-bigframes-tensorflow\"\n", + "REMOTE_JOB_BUCKET = f\"{BUCKET_URI}/{REMOTE_JOB_NAME}\"" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "105334524e96" + }, + "source": [ + "## Prepare the dataset\n", + "\n", + "Now load the Iris dataset and split the data into train and test sets." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "94576deccd8c" + }, + "outputs": [], + "source": [ + "df = bf.read_gbq(\"bigquery-public-data.ml_datasets.iris\")\n", + "\n", + "species_categories = {\n", + " \"versicolor\": 0,\n", + " \"virginica\": 1,\n", + " \"setosa\": 2,\n", + "}\n", + "df[\"target\"] = df[\"species\"].map(species_categories)\n", + "df = df.drop(columns=[\"species\"])\n", + "\n", + "train, test = bf_train_test_split(df, test_size=0.2)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "cfcbce726efa" + }, + "source": [ + "## Remote training with GPU\n", + "\n", + "First, train a TensorFlow model as a remote training job:\n", + "\n", + "- Reinitialize Vertex AI for remote training.\n", + "- Instantiate the tensorflow keras model for the remote training job.\n", + "- Invoke the tensorflow keras model.fit() locally which will launch the remote training job." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "fd865b0c4e8b" + }, + "outputs": [], + "source": [ + "# Switch to remote mode for training\n", + "vertexai.preview.init(remote=True)\n", + "\n", + "keras.Sequential = vertexai.preview.remote(keras.Sequential)\n", + "\n", + "# Instantiate model\n", + "model = keras.Sequential(\n", + " [keras.layers.Dense(5, input_shape=(4,)), keras.layers.Softmax()]\n", + ")\n", + "\n", + "# Specify optimizer and loss function\n", + "model.compile(optimizer=\"adam\", loss=\"mean_squared_error\")\n", + "\n", + "# Set training config\n", + "model.fit.vertex.remote_config.enable_cuda = True\n", + "model.fit.vertex.remote_config.display_name = REMOTE_JOB_NAME + \"-keras-model-gpu\"\n", + "model.fit.vertex.remote_config.staging_bucket = REMOTE_JOB_BUCKET\n", + "model.fit.vertex.remote_config.custom_commands = [\"pip install tensorflow-io==0.32.0\"]\n", + "\n", + "# Manually set compute resources this time\n", + "model.fit.vertex.remote_config.machine_type = \"n1-highmem-4\"\n", + "model.fit.vertex.remote_config.accelerator_type = \"NVIDIA_TESLA_K80\"\n", + "model.fit.vertex.remote_config.accelerator_count = 4\n", + "\n", + "# Train model on Vertex\n", + "model.fit(train, epochs=10)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "f1af94ac1477" + }, + "source": [ + "## Remote prediction\n", + "\n", + "Obtain predictions from the trained model." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "1d75879948b5" + }, + "outputs": [], + "source": [ + "vertexai.preview.init(remote=True)\n", + "\n", + "# Set remote config\n", + "model.predict.vertex.remote_config.enable_cuda = False\n", + "model.predict.vertex.remote_config.display_name = REMOTE_JOB_NAME + \"-keras-predict-cpu\"\n", + "model.predict.vertex.remote_config.staging_bucket = REMOTE_JOB_BUCKET\n", + "model.predict.vertex.remote_config.custom_commands = [\n", + " \"pip install tensorflow-io==0.32.0\"\n", + "]\n", + "\n", + "predictions = model.predict(train)\n", + "\n", + "print(f\"Remote predictions: {predictions}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "798b77c95067" + }, + "source": [ + "## Local evaluation\n", + "\n", + "Evaluate model results locally." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "88e734e30791" + }, + "outputs": [], + "source": [ + "# User must convert bigframes to pandas dataframe for local evaluation\n", + "feature_columns = [\"sepal_length\", \"sepal_width\", \"petal_length\", \"petal_width\"]\n", + "label_columns = [\"target\"]\n", + "\n", + "train_X_np = train[feature_columns].to_pandas().values.astype(float)\n", + "train_y_np = train[label_columns].to_pandas().values.astype(float)\n", + "train_ds = tf.data.Dataset.from_tensor_slices((train_X_np, train_y_np))\n", + "\n", + "test_X_np = test[feature_columns].to_pandas().values.astype(float)\n", + "test_y_np = test[label_columns].to_pandas().values.astype(float)\n", + "test_ds = tf.data.Dataset.from_tensor_slices((test_X_np, test_y_np))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "cb8637f783ad" + }, + "outputs": [], + "source": [ + "# Switch to local mode for evaluation\n", + "vertexai.preview.init(remote=False)\n", + "\n", + "# Evaluate model's mean square errors\n", + "print(f\"Train loss: {model.evaluate(train_ds.batch(32))}\")\n", + "\n", + "print(f\"Test loss: {model.evaluate(test_ds.batch(32))}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "TpV-iwP9qw9c" + }, + "source": [ + "## Cleaning up\n", + "\n", + "To clean up all Google Cloud resources used in this project, you can [delete the Google Cloud\n", + "project](https://cloud.google.com/resource-manager/docs/creating-managing-projects#shutting_down_projects) you used for the tutorial.\n", + "\n", + "Otherwise, you can delete the individual resources you created in this tutorial:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "sx_vKniMq9ZX" + }, + "outputs": [], + "source": [ + "import os\n", + "\n", + "# Delete Cloud Storage objects that were created\n", + "delete_bucket = False\n", + "if delete_bucket or os.getenv(\"IS_TESTING\"):\n", + " ! gsutil -m rm -r $BUCKET_URI" + ] + } + ], + "metadata": { + "colab": { + "collapsed_sections": [], + "name": "sdk2_bigframes_tensorflow.ipynb", + "toc_visible": true + }, + "kernelspec": { + "display_name": "Python 3", + "name": "python3" + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} diff --git a/notebooks/visualization/bq_dataframes_covid_line_graphs.ipynb b/notebooks/visualization/bq_dataframes_covid_line_graphs.ipynb deleted file mode 100644 index b28df7b0d7d..00000000000 --- a/notebooks/visualization/bq_dataframes_covid_line_graphs.ipynb +++ /dev/null @@ -1,648 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 1, - "metadata": { - "id": "9GIt_orUtNvA" - }, - "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": { - "id": "h7AT6h2ItNvD" - }, - "source": [ - "# Use BigQuery DataFrames to visualize COVID-19 data", - "\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", - "
" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": { - "id": "n-MFJQxLtNvE" - }, - "source": [ - "## Overview\n", - "\n", - "The goal of this notebook is to demonstrate creating line graphs from a ~20 million-row BigQuery dataset using BigQuery DataFrames. We will first create a plain line graph using matplotlip, then we will downsample and download our data to create a graph with a line of best fit using seaborn.\n", - "\n", - "If you're like me, during 2020 (and/or later years) you often found yourself looking at charts like [these](https://health.google.com/covid-19/open-data/explorer/statistics) visualizing COVID-19 cases over time. For our first graph, we're going to recreate one of those charts by filtering, summing, and then graphing COVID-19 data from the United States. BigQuery DataFrame's default integration with matplotlib will get us a satisfying result for this first graph.\n", - "\n", - "For our second graph, though, we want to use a scatterplot with a line of best fit, something that matplotlib will not do for us automatically. So, we'll demonstrate how to downsample our data and use seaborn to make our plot. Our second graph will be of symptom-related search trends against new cases of COVID-19, so we'll see if searches for things like \"cough\" and \"fever\" are more common in the places and times where more new cases of COVID-19 occur." - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": { - "id": "ffqBzbNztNvF" - }, - "source": [ - "### Dataset\n", - "\n", - "This notebook uses the [BigQuery COVID-19 Open Data](https://pantheon.corp.google.com/marketplace/product/bigquery-public-datasets/covid19-open-data). In this dataset, each row represents a new observation of the COVID-19 situation in a particular time and place. We will use the \"new_confirmed\" column, which contains the number of new COVID-19 cases at each observation, along with the \"search_trends_cough\", \"search_trends_fever\", and \"search_trends_bruise\" columns, which are [Google Trends](https://trends.google.com/trends/) data for searches related to cough, fever, and bruises. In the first section of the notebook, we will also use the \"country_code\" and \"date\" columns to compile one data point per day for a particular country." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "Nf__tMR-tNvF" - }, - "source": [ - "### Costs\n", - "\n", - "This tutorial uses billable components of Google Cloud:\n", - "\n", - "* BigQuery (compute)\n", - "\n", - "Learn about [BigQuery compute pricing](https://cloud.google.com/bigquery/pricing#analysis_pricing_models),\n", - "and use the [Pricing Calculator](https://cloud.google.com/products/calculator/)\n", - "to generate a cost estimate based on your projected usage." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "7_rsbkCktNvG" - }, - "source": [ - "## Before you begin\n", - "\n", - "### Set up your Google Cloud project\n", - "\n", - "**The following steps are required, regardless of your notebook environment.**\n", - "\n", - "1. [Select or create a Google Cloud project](https://console.cloud.google.com/cloud-resource-manager). When you first create an account, you get a $300 free credit towards your compute/storage costs.\n", - "\n", - "2. [Make sure that billing is enabled for your project](https://cloud.google.com/billing/docs/how-to/modify-project).\n", - "\n", - "3. [Enable the BigQuery API](https://console.cloud.google.com/flows/enableapi?apiid=bigquery.googleapis.com).\n", - "\n", - "4. If you are running this notebook locally, you need to install the [Cloud SDK](https://cloud.google.com/sdk)." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "XZKC6iMFxmMG" - }, - "source": [ - "#### Set your project ID\n", - "\n", - "**If you don't know your project ID**, try the following:\n", - "* Run `gcloud config list`.\n", - "* Run `gcloud projects list`.\n", - "* See the support page: [Locate the project ID](https://support.google.com/googleapi/answer/7014113)" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": { - "id": "4aooKMmnxrWF" - }, - "outputs": [], - "source": [ - "PROJECT_ID = \"\" # @param {type:\"string\"}" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "pv5A8Tm-yC1U" - }, - "source": [ - "#### Set the region\n", - "\n", - "You can also change the `REGION` variable used by BigQuery. Learn more about [BigQuery regions](https://cloud.google.com/bigquery/docs/locations#supported_locations)." - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": { - "id": "bk03Rt_HyGx-" - }, - "outputs": [], - "source": [ - "REGION = \"US\" # @param {type: \"string\"}" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "B9RWxD1btNvK" - }, - "source": [ - "Now we are ready to use BigQuery DataFrames!" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "wJ0gXezj2w1t" - }, - "source": [ - "## Visualization #1: Cases over time in the US" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": { - "id": "xckgWno6ouHY" - }, - "source": [ - "### Set up project and filter data" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": { - "id": "-uiY0hh4tNvK" - }, - "source": [ - "First, let's do project setup. We use options to tell BigQuery DataFrames what project and what region to use for our cloud computing." - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": { - "id": "R7STCS8xB5d2" - }, - "outputs": [], - "source": [ - "import bigframes.pandas as bpd\n", - "\n", - "# Note: The project option is not required in all environments.\n", - "# On BigQuery Studio, the project ID is automatically detected.\n", - "bpd.options.bigquery.project = PROJECT_ID\n", - "\n", - "# Note: The location option is not required.\n", - "# It defaults to the location of the first table or query\n", - "# passed to read_gbq(). For APIs where a location can't be\n", - "# auto-detected, the location defaults to the \"US\" location.\n", - "bpd.options.bigquery.location = REGION\n", - "# Improves performance by avoiding generating total row ordering\n", - "bpd.options.bigquery.ordering_mode = \"partial\"" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "v6FGschEowht" - }, - "source": [ - "Next, we read the data from a publicly available BigQuery dataset. This will take ~1 minute." - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": { - "id": "zDSwoBo1CU3G" - }, - "outputs": [], - "source": [ - "all_data = bpd.read_gbq(\"bigquery-public-data.covid19_open_data.covid19_open_data\")" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "9qV2y3iHp13y" - }, - "source": [ - "Using pandas syntax, we will select from our all_data input dataframe only those rows where the country_code is US. This is called row filtering." - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": { - "id": "UjMT_qhjf8Fu" - }, - "outputs": [], - "source": [ - "usa_data = all_data[all_data[\"country_code\"] == \"US\"]" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "IYCUayWkwq8c" - }, - "source": [ - "We're only concerned with the date and the total number of confirmed cases for now, so select just those two columns as well." - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": { - "id": "IaoUf57ZwrJ8" - }, - "outputs": [], - "source": [ - "usa_data = usa_data[[\"date\", \"new_confirmed\"]]" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "94oqNRnDvGkr" - }, - "source": [ - "### Sum data" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": { - "id": "TNCQWZW83U0b" - }, - "source": [ - "`usa_data.groupby(\"date\")` will give us a groupby object that lets us perform operations on groups of rows with the same date. We call sum on that object to get the sum for each day. This process might be familiar to pandas users." - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": { - "id": "tYDoaKgJChiq" - }, - "outputs": [], - "source": [ - "# numeric_only = True because we don't want to sum dates\n", - "new_cases_usa = usa_data.groupby(\"date\").sum(numeric_only = True)" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "3jcwFPgK5BLh" - }, - "source": [ - "### Line graph" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "8GvJAgnH5Nzi" - }, - "source": [ - "BigQuery DataFrames implements some plotting methods with the matplotlib backend. Use `DataFrame.plot.line()` to draw a simple line graph." - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "metadata": { - "id": "gFbCgfFC2gHw" - }, - "outputs": [ - { - "data": { - "text/plain": [ - "" - ] - }, - "execution_count": 9, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "image/png": "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", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "new_cases_usa.plot.line(\n", - " rot=45,\n", - " ylabel=\"New Cases\",\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "sM5-HFDx70RG" - }, - "source": [ - "## Visualization #2: Symptom-related searches compared to new cases" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "se1b6Vf4XB9_" - }, - "source": [ - "### Filter data" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "Wl2o-NYMoygb" - }, - "source": [ - "We're curious if searches for symptoms like \"cough\" and \"fever\" went up in the same times and places that new COVID-19 cases occured, compared to non-symptoms like \"bruise.\" Let's plot searches vs. new cases to see if it looks like there's a correlation." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "olfnCzyg8jYi" - }, - "source": [ - "First, we select the new cases column and the search trends we're interested in." - ] - }, - { - "cell_type": "code", - "execution_count": 55, - "metadata": { - "id": "LqqHzjty8jk0" - }, - "outputs": [], - "source": [ - "regional_data = all_data[all_data[\"aggregation_level\"] == 1] # get only region level data,\n", - "symptom_data = regional_data[[\"location_key\", \"new_confirmed\", \"search_trends_cough\", \"search_trends_fever\", \"search_trends_bruise\", \"population\", \"date\"]]" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "b3DlJX-k9SPk" - }, - "source": [ - "Not all rows have data for all of these columns, so let's select only the rows that do. Finally, lets add a new column capturing new confirmed cases as a percentage of area population." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "g4MeM8Oe9Q6X" - }, - "outputs": [], - "source": [ - "symptom_data = symptom_data.dropna()\n", - "symptom_data = symptom_data[symptom_data[\"new_confirmed\"] > 0]\n", - "symptom_data[\"new_cases_percent_of_pop\"] = (symptom_data[\"new_confirmed\"] / symptom_data[\"population\"]) * 100\n", - "\n", - "\n", - "# remove impossible data points\n", - "symptom_data = symptom_data[(symptom_data[\"new_cases_percent_of_pop\"] >= 0)]\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# group data up by week\n", - "weekly_data = symptom_data.groupby([symptom_data.location_key, symptom_data.date.dt.isocalendar().week]).agg({\"new_cases_percent_of_pop\": \"sum\", \"search_trends_cough\": \"mean\", \"search_trends_fever\": \"mean\", \"search_trends_bruise\": \"mean\"})" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "IlXt__om9QYI" - }, - "source": [ - "We want to use a line of best fit to make the correlation stand out. Matplotlib does not include a feature for lines of best fit, but seaborn, which is built on matplotlib, does.\n", - "\n", - "BigQuery DataFrames does not currently integrate with seaborn by default. So we will demonstrate how to downsample and download a DataFrame, and use seaborn on the downloaded data." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "T9Hub_EAXWvY" - }, - "source": [ - "### Graph with lines of best fit" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": { - "id": "hoQ9TPgUPJnN" - }, - "source": [ - "We will now use seaborn to make the plots with the lines of best fit for cough, fever, and bruise. Note that since we're working with a local pandas dataframe, you could use any other Python library or technique you're familiar with, but we'll stick to seaborn for this notebook.\n", - "\n", - "Seaborn will take a few minutes to calculate the lines. Since cough and fever are symptoms of COVID-19, but bruising isn't, we expect the slope of the line of best fit to be positive in the first two graphs, but not the third, indicating that there is a correlation between new COVID-19 cases and cough- and fever-related searches." - ] - }, - { - "cell_type": "code", - "execution_count": 59, - "metadata": { - "id": "EG7qM3R18bOb" - }, - "outputs": [ - { - "data": { - "text/plain": [ - "" - ] - }, - "execution_count": 59, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "image/png": "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N9z+2hBcuapN1XaZLoaoctRsO1O0dq100O4Avdnjder0yd2SMlQ8P+ClqSIBtDX4B2HI967neeriJC9OldZd74przSbSbKL9S+a2girLJA2FdEzb954SgUCwc9ouPt72trfxB3/wB7z22mvcd999vPDCC3zrW9/i937v91p+v23b2PYb463lcrndRwI2Tvk3REk2oXFjqUYmruIH4Z72D6zMCOSqNrmas0oknehLNk3PwjAkaahULLf5s5IksVixuZWrM1+2mS/bnOrf2JW1OS3iB4RhNJlwrDdJX0rfs+bT3WY3p0/2S9hYro8XBAxkYqRiats2zTpewFcvz/P0pSlu5errro91x/n4o2P82AODGNre91FIkkRSV5qvWSAQHHza/pv8a7/2a5TLZe6//34URcH3fX77t3+bn/mZn2n5/Z/61Kf4rd/6rXYfYx19KZ2RrlhzqqUvFW3qbIiSXNVBlSWm8iYxXSJpKIRhuGEJpp0BaOUdfMpQKZneKpEkSVLT9GyjhWqNczx+opebS9VNXVkbgmu0O7G85dZoBuatZiY2e/07fW/a+Z7u5vTJXo/VrvXqaNem2arl8cUXZvj8c9Pkas666w8MZ3jysXHedqp3X8oaoqwiEBxe2i4+nn76af77f//v/I//8T948MEHef755/nlX/5lRkZG+Lmf+7l13//JT36ST3ziE80/l8tlxsfH230sFis2l2fLVG2PpapNb1JnMBtvZh0qlstod5ybSzVML+C713NMdCc3LMHsVgBa2xfRl9JZKFvNP1cst2VQTRkqqiJjuT6j3ZHvxVZNzJK6suo5thL0N3v9d+qv2eh52vme7ub0yV6N1Xp+QKHuNrNc7WK+bPG5Z6f40otzmO56x9O3n+rlycfGeWj07qfAtosqy6RikeAQZRWB4PDSdvHxK7/yK/zar/0aP/3TPw3A2bNnuXXrFp/61Kdaig/DMDCM3U/v387XeX2xRldcY75cY6Insco+XZIkDFWmL20QhiG3l2qYKZdcNWxasa9kJ6OsWwnqa/sY1mY6RrpiLYPqdpo5135vEAR88+oSNdsjaai8896+O1rLb/b679Rfs5G4aGdQ383pk90eq/WDaGy2ZLpt9eq4tlDl6YuT/NWVhXVNpJoi8WMPDPHxC2PNnUZbJQxD8jWXuuM1S0HbyViJsopAcPRo+296vV5H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ueOOO3j00Ue7Hdaa+exnP4umaXz4wx/udigd8clPfhJN01Z87dixo9thddSRI0f4gz/4A8rlMq7rcsMNN/D00093O6yO2bRp0yl/h5qmsXv37m6HdtGSJOEv//Iv2bx5M67rsnXrVv7qr/7qvM5fWUvrIvn4l3/5Fz7ykY/wwAMP8Oyzz3LjjTfyG7/xG0xPT3c7tI7wPI8bb7yRL33pS90OZU08/vjj7N69m6eeeorHHnuMKIp4z3veg+d53Q6tI8bGxvjsZz/LM888w9NPP8073/lOfuu3fotf/epX3Q6t4/bs2cNXvvIVdu3a1e1QOur666/n2LFjy19PPvlkt0PqmIWFBd7ylrdgWRaPPvooL7zwAn/7t39Lb29vt0PrmD179qz4+3vssccAeN/73tflyC7e5z73Ob785S/zxS9+kRdffJHPfe5z/M3f/A1f+MIXuhuYWgduu+02tXv37uXvkyRRo6Oj6sEHH+xiVGsDUI888ki3w1hT09PTClCPP/54t0NZM729veof//Efux1GR9XrdXXttdeqxx57TL3jHe9Q999/f7dD6ogHHnhA3Xjjjd0OY8189KMfVW9961u7HcYldf/996utW7eqNE27HcpFu/vuu9V999234rHf+Z3fUffcc0+XImq76mc+wjDkmWee4d3vfvfyY7qu8+53v5v//d//7WJk4kJVq1UA+vr6uhxJ5yVJwje/+U08z+OOO+7odjgdtXv3bu6+++4V/y9eLV599VVGR0fZsmUL99xzDwcPHux2SB3z7//+79xyyy28733vY3BwkJtuuomvfe1r3Q5rzYRhyD//8z9z3333dfxw02648847+eEPf8grr7wCwC9+8QuefPJJ7rrrrq7GddkdLNdps7OzJEnC0NDQiseHhoZ46aWXuhSVuFBpmvLhD3+Yt7zlLezcubPb4XTMc889xx133IHv++TzeR555BHe9KY3dTusjvnmN7/Js88+y549e7odSsfdfvvtfP3rX2f79u0cO3aMT33qU7ztbW/j+eefp1AodDu8i7Z//36+/OUv85GPfIRPfOIT7Nmzhz/5kz/Btm3uvffebofXcd/5zneoVCp84AMf6HYoHfGxj32MWq3Gjh07MAyDJEn49Kc/zT333NPVuK765ENcXXbv3s3zzz9/Va2pA2zfvp29e/dSrVb513/9V+69914ef/zxqyIBOXToEPfffz+PPfYYmUym2+F03Il3kLt27eL2229nYmKCb33rW/zhH/5hFyPrjDRNueWWW/jMZz4DwE033cTzzz/PP/zDP1yVycc//dM/cddddzE6OtrtUDriW9/6Ft/4xjd4+OGHuf7669m7dy8f/vCHGR0d7erf31WffPT392MYBlNTUysen5qaYnh4uEtRiQvxoQ99iO9973s88cQTjI2NdTucjrJtm2uuuQaAN7/5zezZs4e///u/5ytf+UqXI7t4zzzzDNPT09x8883LjyVJwhNPPMEXv/hFgiDAMIwuRthZPT09bNu2jddee63boXTEyMjIKUnwddddx7/92791KaK1c+DAAX7wgx/w7W9/u9uhdMyf//mf87GPfYzf+73fA+CGG27gwIEDPPjgg11NPq76mg/btnnzm9/MD3/4w+XH0jTlhz/84VW3pn61UkrxoQ99iEceeYT//u//ZvPmzd0Oac2laUoQBN0OoyPe9a538dxzz7F3797lr1tuuYV77rmHvXv3XlWJB0Cj0WDfvn2MjIx0O5SOeMtb3nLK1vZXXnmFiYmJLkW0dh566CEGBwe5++67ux1KxzSbTXR95VBvGAZpmnYporarfuYD4CMf+Qj33nsvt9xyC7fddhuf//zn8TyPD37wg90OrSMajcaKu6zXX3+dvXv30tfXx/j4eBcj64zdu3fz8MMP893vfpdCocDk5CQApVIJ13W7HN3F+/jHP85dd93F+Pg49Xqdhx9+mB//+Md8//vf73ZoHVEoFE6pz8nlcpTL5auibufP/uzPeO9738vExARHjx7lgQcewDAMfv/3f7/boXXEn/7pn3LnnXfymc98ht/93d/lZz/7GV/96lf56le/2u3QOipNUx566CHuvfdeTPPqGRrf+9738ulPf5rx8XGuv/56fv7zn/N3f/d33Hfffd0NrKt7bS6hL3zhC2p8fFzZtq1uu+029dRTT3U7pI750Y9+pIBTvu69995uh9YRp3tvgHrooYe6HVpH3HfffWpiYkLZtq0GBgbUu971LvVf//Vf3Q5rTV1NW23f//73q5GREWXbttqwYYN6//vfr1577bVuh9VR//Ef/6F27typHMdRO3bsUF/96le7HVLHff/731eAevnll7sdSkfVajV1//33q/HxcZXJZNSWLVvUX/zFX6ggCLoal6ZUl9ucCSGEEGJdueprPoQQQghxeZHkQwghhBCXlCQfQgghhLikJPkQQgghxCUlyYcQQgghLilJPoQQQghxSUnyIYQQQohLSpIPIYQQQlxSknwIIYQQ4pKS5EMIIYQQl5QkH0IIIYS4pCT5EEIIIcQl9f8DvBq4eqmKlScAAAAASUVORK5CYII=", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "import seaborn as sns\n", - "\n", - "# draw the graph. This might take ~30 seconds.\n", - "sns.regplot(x=\"new_cases_percent_of_pop\", y=\"search_trends_cough\", data=weekly_data, scatter_kws={'alpha': 0.2, \"s\" :5})" - ] - }, - { - "cell_type": "code", - "execution_count": 62, - "metadata": { - "id": "5nVy61rEGaM4" - }, - "outputs": [ - { - "data": { - "text/plain": [ - "" - ] - }, - "execution_count": 62, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "image/png": "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99J6otlscvjwYb74xS/2eymb4qmnnuLIkSM8++yzPPnkkwRBwIc//GGazWa/l9YTY2NjfPazn+X555/n+9//Pr/4i7/IL//yL/PjH/+430vrueeee44vfelL3HTTTf1eSk/dcMMNnDp1qvvxzDPP9HtJPbO4uMidd96JZVl861vf4ic/+Ql//ud/zsDAQL+X1jPPPffcmr+/J598EoCPfexjfV7Zpfvc5z7Ho48+yhe+8AVeeeUVPve5z/Fnf/ZnfP7zn+/vwtQOcNttt6kjR450fx1FkRodHVWPPPJIH1e1OQD1xBNP9HsZm2pmZkYB6qmnnur3UjbNwMCA+tu//dt+L6On6vW6uvrqq9WTTz6pfv7nf1498MAD/V5STzz88MPq8OHD/V7Gpvn93/999b73va/fy9hSDzzwgDpw4ICK47jfS7lkd999t7rvvvvWfO5XfuVX1D333NOnFSW2febD932ef/55PvShD3U/p+s6H/rQh/if//mfPq5MXKylpSUASqVSn1fSe1EU8dWvfpVms8kdd9zR7+X01JEjR7j77rvX/L+4Xbz++uuMjo6yf/9+7rnnHo4fP97vJfXMv/7rv3LLLbfwsY99jKGhId797nfzN3/zN/1e1qbxfZ+///u/57777uv5cNN+eO9738t3vvMdXnvtNQB++MMf8swzz3DXXXf1dV2X3WC5XpubmyOKIoaHh9d8fnh4mJ/+9Kd9WpW4WHEc8+CDD3LnnXdy6NChfi+nZ1566SXuuOMOOp0O2WyWJ554guuvv77fy+qZr371q7zwwgs899xz/V5Kz91+++185Stf4dprr+XUqVP8yZ/8Ce9///t5+eWXyeVy/V7eJTt69CiPPvooDz30EH/wB3/Ac889x2//9m9j2zb33ntvv5fXc1//+tepVqt84hOf6PdSeuKTn/wktVqN6667DsMwiKKIT3/609xzzz19Xde2Dz7E9nLkyBFefvnlbXWmDnDttdfy4osvsrS0xD/90z9x77338tRTT22LAOTEiRM88MADPPnkk7iu2+/l9NzqnyBvuukmbr/9diYmJvja177Gb/3Wb/VxZb0RxzG33HILn/nMZwB497vfzcsvv8xf//Vfb8vg4+/+7u+46667GB0d7fdSeuJrX/sa//AP/8Djjz/ODTfcwIsvvsiDDz7I6OhoX//+tn3wUalUMAyD6enpNZ+fnp5m165dfVqVuBj3338/3/zmN3n66acZGxvr93J6yrZtrrrqKgBuvvlmnnvuOf7qr/6KL33pS31e2aV7/vnnmZmZ4T3veU/3c1EU8fTTT/OFL3wBz/MwDKOPK+ytYrHINddcwxtvvNHvpfTEyMjIWUHwwYMH+ed//uc+rWjzHDt2jP/8z//kX/7lX/q9lJ75vd/7PT75yU/ya7/2awDceOONHDt2jEceeaSvwce2r/mwbZubb76Z73znO93PxXHMd77znW13pr5dKaW4//77eeKJJ/jud7/Lvn37+r2kTRfHMZ7n9XsZPfHBD36Ql156iRdffLH7ccstt3DPPffw4osvbqvAA6DRaPDmm28yMjLS76X0xJ133nnW1fbXXnuNiYmJPq1o8zz22GMMDQ1x991393spPdNqtdD1tVu9YRjEcdynFSW2feYD4KGHHuLee+/llltu4bbbbuMv//IvaTab/OZv/ma/l9YTjUZjzU9Zb731Fi+++CKlUonx8fE+rqw3jhw5wuOPP843vvENcrkcp0+fBqBQKJBKpfq8ukv3qU99irvuuovx8XHq9TqPP/44//3f/823v/3tfi+tJ3K53Fn1OZlMhnK5vC3qdn73d3+Xj3zkI0xMTDA1NcXDDz+MYRj8+q//er+X1hO/8zu/w3vf+14+85nP8Ku/+qt873vf48tf/jJf/vKX+720norjmMcee4x7770X09w+W+NHPvIRPv3pTzM+Ps4NN9zAD37wA/7iL/6C++67r78L6+tdmy30+c9/Xo2PjyvbttVtt92mnn322X4vqWf+67/+SwFnfdx77739XlpPrPdsgHrsscf6vbSeuO+++9TExISybVsNDg6qD37wg+o//uM/+r2sTbWdrtp+/OMfVyMjI8q2bbV792718Y9/XL3xxhv9XlZP/du//Zs6dOiQchxHXXfdderLX/5yv5fUc9/+9rcVoF599dV+L6WnarWaeuCBB9T4+LhyXVft379f/eEf/qHyPK+v69KU6nObMyGEEELsKNu+5kMIIYQQlxcJPoQQQgixpST4EEIIIcSWkuBDCCGEEFtKgg8hhBBCbCkJPoQQQgixpST4EEIIIcSWkuBDCCGEEFtKgg8hhBBCbCkJPoQQQgixpST4EEIIIcSWkuBDCCGEEFvq/wfPqAyP2kEskwAAAABJRU5ErkJggg==", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "# similarly, for fever\n", - "sns.regplot(x=\"new_cases_percent_of_pop\", y=\"search_trends_fever\", data=weekly_data, scatter_kws={'alpha': 0.2, \"s\" :5})" - ] - }, - { - "cell_type": "code", - "execution_count": 63, - "metadata": { - "id": "-S1A9E3WGaYH" - }, - "outputs": [ - { - "data": { - "text/plain": [ - "" - ] - }, - "execution_count": 63, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "image/png": "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HN57773tDqllHnzwwfD6669vdxjr5k/+5E/Cj33sY+0O4wNz7733hjt37gyDIGh3KC1x1113hQcOHFjysU9+8pPhPffc06aIwnBTVkYcx+HVV1/l9ttvb35MVVVuv/12fvKTn7QxMnEhCoUCAJ2dnW2OpPV83+d73/selUqFffv2tTucljp48CB33XXXkv8PN5Jf/OIXDAwMsGPHDu655x5GRkbaHVLL/Pu//zs333wzn/rUp+jp6eGGG27g7/7u79od1rpwHId//Md/5MCBAy29qLWdPvrRj/Lss8/y7rvvAvCzn/2MF154gTvvvLNtMV0WF+W12szMDL7v09vbu+Tjvb29vPPOO22KSlyIIAi47777uO2227juuuvaHU7LvPnmm+zbtw/Lskgmkzz11FNcc8017Q6rZb73ve/x2muv8fLLL7c7lHVx66238vjjj3P11VczPj7OF7/4RT7+8Y/z1ltvkUql2h3eRTt27BiPPPII999/P3/6p3/Kyy+/zB/8wR9gGAb79+9vd3gt9fTTT5PP5/md3/mddofSMl/4whcoFovs2rULTdPwfZ8vfelL3HPPPW2LaVMmI2LjOHjwIG+99daG2o8HuPrqqzl06BCFQoF//ud/Zv/+/Tz33HMbIiE5deoU9957L8888wzRaLTd4ayLxe8w9+7dy6233srw8DBPPvkkv/d7v9fGyFojCAJuvvlmvvzlLwNwww038NZbb/Gtb31rwyUjf//3f8+dd97JwMBAu0NpmSeffJLvfve7PPHEE1x77bUcOnSI++67j4GBgba9fpsyGenq6kLTNCYnJ5d8fHJykr6+vjZFJc7X5z73Of7zP/+T559/nsHBwXaH01KGYXDFFVcAcNNNN/Hyyy/z9a9/nW9/+9ttjuzivfrqq0xNTXHjjTc2P+b7Ps8//zwPP/wwtm2jaVobI2y9bDbLVVddxdGjR9sdSkv09/efkRjv3r2bf/mXf2lTROvj5MmT/M///A//+q//2u5QWuqP/uiP+MIXvsBv/uZvArBnzx5OnjzJQw891LZkZFP2jBiGwU033cSzzz7b/FgQBDz77LMbbl9+IwrDkM997nM89dRT/OhHP2L79u3tDmndBUGAbdvtDqMlPvGJT/Dmm29y6NCh5p+bb76Ze+65h0OHDm24RASgXC7z3nvv0d/f3+5QWuK222474zj9u+++y/DwcJsiWh+PPfYYPT093HXXXe0OpaWq1SqqunT51zSNIAjaFNEmrYwA3H///ezfv5+bb76ZW265hb/927+lUqnwu7/7u+0OrSXK5fKSd2HHjx/n0KFDdHZ2MjQ01MbILt7Bgwd54okn+Ld/+zdSqRQTExMAZDIZYrFYm6O7eA888AB33nknQ0NDlEolnnjiCX784x/zwx/+sN2htUQqlTqjvyeRSJDL5TZM388f/uEfcvfddzM8PMzY2BgPPvggmqbxW7/1W+0OrSU+//nP89GPfpQvf/nLfPrTn+all17i0Ucf5dFHH213aC0TBAGPPfYY+/fvR9c31lJ5991386UvfYmhoSGuvfZaXn/9df76r/+aAwcOtC+otp3juQR84xvfCIeGhkLDMMJbbrklfPHFF9sdUsv87//+bwic8Wf//v3tDu2irfS8gPCxxx5rd2gtceDAgXB4eDg0DCPs7u4OP/GJT4T//d//3e6w1tVGO9r7mc98Juzv7w8Nwwi3bNkSfuYznwmPHj3a7rBa6j/+4z/C6667LjRNM9y1a1f46KOPtjuklvrhD38YAuGRI0faHUrLFYvF8N577w2HhobCaDQa7tixI/yzP/uz0LbttsWkhGEbR64JIYQQYtPblD0jQgghhLh0SDIihBBCiLaSZEQIIYQQbSXJiBBCCCHaSpIRIYQQQrSVJCNCCCGEaCtJRoQQQgjRVpKMCCGEEKKtJBkRQgghRFtJMiKEEEKItpJkRAghhBBtJcmIEEIIIdrq/wHRnL6tjOlowAAAAABJRU5ErkJggg==", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "# similarly, for bruise\n", - "sns.regplot(\n", - " x=\"new_cases_percent_of_pop\",\n", - " y=\"search_trends_bruise\",\n", - " data=weekly_data,\n", - " scatter_kws={'alpha': 0.2, \"s\" :5}\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "Hd2A8707Uhz2" - }, - "source": [ - "We see that the slope of the line is positive in the graphs for cough and fever, but flat for bruise. That means that in places with increasing new cases of COVID-19, we saw increasing searches for cough and fever, but we didn't see increasing searches for unrelated symptoms like bruises. Interesting!" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Recap" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We used matplotlib to draw a line graph of COVID-19 cases over time in the USA. Then, we used downsampling to download only a portion of the available data, used seaborn to plot lines of best fit to observe corellation between COVID-19 cases and searches for related versus unrelated symptoms.\n", - "\n", - "Thank you for using BigQuery DataFrames!" - ] - } - ], - "metadata": { - "colab": { - "provenance": [] - }, - "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.12.6" - } - }, - "nbformat": 4, - "nbformat_minor": 0 -} diff --git a/notebooks/visualization/tutorial.ipynb b/notebooks/visualization/tutorial.ipynb deleted file mode 100644 index 89a5ed87b8f..00000000000 --- a/notebooks/visualization/tutorial.ipynb +++ /dev/null @@ -1,1448 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 1, - "id": "b11d1db5", - "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": "e661697d", - "metadata": {}, - "source": [ - "# BigQuery DataFrame Visualization Tutorials", - "\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", - "
" - ] - }, - { - "cell_type": "markdown", - "id": "5e93c4c1", - "metadata": {}, - "source": [ - "This notebook provides tutorials for all plotting methods that BigQuery DataFrame offers. You will visualize different datasets with histograms, line charts, area charts, bar charts, and scatter plots." - ] - }, - { - "cell_type": "markdown", - "id": "f96c47f7", - "metadata": {}, - "source": [ - "# Before you begin" - ] - }, - { - "cell_type": "markdown", - "id": "a8dd598a", - "metadata": {}, - "source": [ - "## Set up your project ID and region" - ] - }, - { - "cell_type": "markdown", - "id": "d442ab74", - "metadata": {}, - "source": [ - "This step makes sure that you will access the target dataset with the correct auth profile." - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "7cc6237d", - "metadata": {}, - "outputs": [], - "source": [ - "PROJECT_ID = \"bigframes-dev\" # @param {type:\"string\"}\n", - "REGION = \"US\" # @param {type: \"string\"}" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "bf96593a", - "metadata": {}, - "outputs": [], - "source": [ - "import bigframes.pandas as bpd\n", - "\n", - "bpd.options.bigquery.project = PROJECT_ID\n", - "bpd.options.bigquery.location = REGION" - ] - }, - { - "cell_type": "markdown", - "id": "165fedc6", - "metadata": {}, - "source": [ - "You can also turn on the partial ordering mode for faster data processing." - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "ac5a1722", - "metadata": {}, - "outputs": [], - "source": [ - "bpd.options.bigquery.ordering_mode = 'partial'" - ] - }, - { - "cell_type": "markdown", - "id": "2ed45ca7", - "metadata": {}, - "source": [ - "# Histogram" - ] - }, - { - "cell_type": "markdown", - "id": "88837be7", - "metadata": {}, - "source": [ - "You will use the penguins public dataset in this example. First, you take a look at the shape of this data:" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "id": "fb595a8f", - "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", - "
speciesislandculmen_length_mmculmen_depth_mmflipper_length_mmbody_mass_gsex
0Adelie Penguin (Pygoscelis adeliae)Dream36.618.4184.03475.0FEMALE
1Adelie Penguin (Pygoscelis adeliae)Dream39.819.1184.04650.0MALE
2Adelie Penguin (Pygoscelis adeliae)Dream40.918.9184.03900.0MALE
3Chinstrap penguin (Pygoscelis antarctica)Dream46.517.9192.03500.0FEMALE
4Adelie Penguin (Pygoscelis adeliae)Dream37.316.8192.03000.0FEMALE
\n", - "
" - ], - "text/plain": [ - " species island culmen_length_mm \\\n", - "0 Adelie Penguin (Pygoscelis adeliae) Dream 36.6 \n", - "1 Adelie Penguin (Pygoscelis adeliae) Dream 39.8 \n", - "2 Adelie Penguin (Pygoscelis adeliae) Dream 40.9 \n", - "3 Chinstrap penguin (Pygoscelis antarctica) Dream 46.5 \n", - "4 Adelie Penguin (Pygoscelis adeliae) Dream 37.3 \n", - "\n", - " culmen_depth_mm flipper_length_mm body_mass_g sex \n", - "0 18.4 184.0 3475.0 FEMALE \n", - "1 19.1 184.0 4650.0 MALE \n", - "2 18.9 184.0 3900.0 MALE \n", - "3 17.9 192.0 3500.0 FEMALE \n", - "4 16.8 192.0 3000.0 FEMALE " - ] - }, - "execution_count": 5, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "penguins = bpd.read_gbq('bigquery-public-data.ml_datasets.penguins')\n", - "penguins.peek()" - ] - }, - { - "cell_type": "markdown", - "id": "176c12f8", - "metadata": {}, - "source": [ - "You want to draw a histogram about the distribution of culmen lengths:" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "id": "333e88a3", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "" - ] - }, - "execution_count": 6, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAjIAAAGdCAYAAAAIbpn/AAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjYsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvq6yFwwAAAAlwSFlzAAAPYQAAD2EBqD+naQAAJvBJREFUeJzt3Xt0lPWBxvFnyOQKuRggmaQEgiQBEQFFiqzBglAusqwEquAFCNLuosGK4SJovaDYIApilUpPDybQHsXSBUQpeOESagE5oEDZpVxiQkACQTQJCZKEzOwfHmYdE0IymcnML3w/58w5vO+8875P8gby8Ht/M6/F4XA4BAAAYKBWvg4AAADgLooMAAAwFkUGAAAYiyIDAACMRZEBAADGosgAAABjUWQAAICxKDIAAMBYVl8H8Da73a5Tp04pPDxcFovF13EAAEADOBwOnT9/XvHx8WrV6srjLi2+yJw6dUoJCQm+jgEAANxw4sQJdejQ4YrPt/giEx4eLun7b0RERISP0wAAgIYoKytTQkKC8/f4lbT4InP5clJERARFBgAAw1xtWgiTfQEAgLEoMgAAwFgUGQAAYKwWP0cGAFoKh8OhS5cuqaamxtdRgCYLCAiQ1Wpt8kejUGQAwABVVVUqKirShQsXfB0F8JiwsDDFxcUpKCjI7X1QZADAz9ntduXn5ysgIEDx8fEKCgriAz5hNIfDoaqqKp09e1b5+flKTk6u90Pv6kORAQA/V1VVJbvdroSEBIWFhfk6DuARoaGhCgwM1PHjx1VVVaWQkBC39sNkXwAwhLv/YwX8lSd+pvlbAQAAjEWRAQAAxmKODAAYLHHOhmY9XsGCkc16vJycHE2fPl0lJSXNelxPGDhwoHr37q0lS5Z4/VgWi0Vr167V6NGjvX4sf8OIDAAAhnjuuefUu3dvX8fwKxQZAABgLIoMAMCr7Ha7Fi5cqKSkJAUHB6tjx4568cUXtW3bNlksFpfLRvv27ZPFYlFBQUGd+7o8IvHWW2+pY8eOatOmjR555BHV1NRo4cKFstlsiomJ0YsvvujyupKSEv3yl79U+/btFRERoTvvvFP79++vtd8//elPSkxMVGRkpMaPH6/z58836GusqKjQxIkT1aZNG8XFxWnRokW1tqmsrNTMmTP1k5/8RK1bt1a/fv20bds25/M5OTmKiorSunXrlJycrJCQEA0bNkwnTpxwPj9v3jzt379fFotFFotFOTk5ztd//fXXSktLU1hYmJKTk7V+/foGZb98Hj788EPdfPPNCg0N1Z133qni4mJt3LhRN9xwgyIiInT//fe7fCDjwIED9eijj2r69Om67rrrFBsbqz/+8Y+qqKjQ5MmTFR4erqSkJG3cuLFBOdzFHBkAfqcx8z6ae84GGm/u3Ln64x//qFdffVWpqakqKirSv/71L7f3l5eXp40bN2rTpk3Ky8vTL37xC3355ZdKSUlRbm6uduzYoYceekhDhgxRv379JEn33HOPQkNDtXHjRkVGRuoPf/iDBg8erCNHjig6Otq533Xr1umDDz7Qt99+q3vvvVcLFiyoVYrqMmvWLOXm5uq9995TTEyMnnzySX3++ecul4GmTZum//3f/9WqVasUHx+vtWvXavjw4frnP/+p5ORkSdKFCxf04osvauXKlQoKCtIjjzyi8ePH6x//+IfGjRungwcPatOmTfrkk08kSZGRkc79z5s3TwsXLtTLL7+s119/XQ888ICOHz/u/Pqu5rnnntMbb7yhsLAw3Xvvvbr33nsVHByst99+W+Xl5UpLS9Prr7+uJ554wvmaFStWaPbs2dq9e7feffddPfzww1q7dq3S0tL05JNP6tVXX9WECRNUWFjotc9AYkQGAOA158+f12uvvaaFCxdq0qRJ6tKli1JTU/XLX/7S7X3a7Xa99dZb6t69u0aNGqVBgwbp8OHDWrJkibp27arJkyera9eu2rp1qyTp008/1e7du7V69WrdeuutSk5O1iuvvKKoqCj99a9/ddlvTk6OevTooQEDBmjChAnavHnzVfOUl5dr+fLleuWVVzR48GDddNNNWrFihS5duuTcprCwUNnZ2Vq9erUGDBigLl26aObMmUpNTVV2drZzu+rqar3xxhvq37+/+vTpoxUrVmjHjh3avXu3QkND1aZNG1mtVtlsNtlsNoWGhjpfm56ervvuu09JSUn67W9/q/Lycu3evbvB39f58+fr9ttv180336wpU6YoNzdXb775pm6++WYNGDBAv/jFL5zf08t69eql3/zmN0pOTtbcuXMVEhKidu3a6Ve/+pWSk5P1zDPP6Ny5czpw4ECDczQWIzIAAK85dOiQKisrNXjwYI/tMzExUeHh4c7l2NhYBQQEuHy4WmxsrIqLiyVJ+/fvV3l5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", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "penguins['culmen_depth_mm'].plot.hist(bins=40)" - ] - }, - { - "cell_type": "markdown", - "id": "9e0aa359", - "metadata": {}, - "source": [ - "# Line Chart" - ] - }, - { - "cell_type": "markdown", - "id": "b0f37913", - "metadata": {}, - "source": [ - "In this example you will use the NOAA public dataset." - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "id": "49ed2417", - "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", - "
stnwbandateyearmodatempcount_tempdewpcount_dewp...flag_minprcpflag_prcpsndpfograin_drizzlesnow_ice_pelletshailthundertornado_funnel_cloud
0010030999992021-11-102021111026.4417.94...<NA>0.0I999.9000000
1010030999992021-02-01202102018.940.54...<NA>2.76G999.9000000
2010060999992021-07-222021072234.449999.90...<NA>0.0I999.9000000
3010070999992021-04-052021040517.946.44...<NA>0.0I999.9000000
4010070999992021-02-042021020419.149999.90...<NA>0.0I999.9000000
\n", - "

5 rows × 33 columns

\n", - "
" - ], - "text/plain": [ - " stn wban date year mo da temp count_temp dewp \\\n", - "0 010030 99999 2021-11-10 2021 11 10 26.4 4 17.9 \n", - "1 010030 99999 2021-02-01 2021 02 01 8.9 4 0.5 \n", - "2 010060 99999 2021-07-22 2021 07 22 34.4 4 9999.9 \n", - "3 010070 99999 2021-04-05 2021 04 05 17.9 4 6.4 \n", - "4 010070 99999 2021-02-04 2021 02 04 19.1 4 9999.9 \n", - "\n", - " count_dewp ... flag_min prcp flag_prcp sndp fog rain_drizzle \\\n", - "0 4 ... 0.0 I 999.9 0 0 \n", - "1 4 ... 2.76 G 999.9 0 0 \n", - "2 0 ... 0.0 I 999.9 0 0 \n", - "3 4 ... 0.0 I 999.9 0 0 \n", - "4 0 ... 0.0 I 999.9 0 0 \n", - "\n", - " snow_ice_pellets hail thunder tornado_funnel_cloud \n", - "0 0 0 0 0 \n", - "1 0 0 0 0 \n", - "2 0 0 0 0 \n", - "3 0 0 0 0 \n", - "4 0 0 0 0 \n", - "\n", - "[5 rows x 33 columns]" - ] - }, - "execution_count": 7, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "noaa_surface = bpd.read_gbq(\"bigquery-public-data.noaa_gsod.gsod2021\")\n", - "noaa_surface.peek()" - ] - }, - { - "cell_type": "markdown", - "id": "239ec3d1", - "metadata": {}, - "source": [ - "You are going to plot a line chart of temperatures by date. The original dataset contains many rows for a single date, and you wan to coalesce them with their median values." - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "id": "e06afd00", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "Query job a2cee421-0f51-49a8-918a-68177b3199dc is DONE. 64.4 MB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "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", - "
temp
date
2021-02-1224.6
2021-02-1125.9
2021-02-1330.4
2021-02-1432.1
2021-01-0932.9
\n", - "
" - ], - "text/plain": [ - " temp\n", - "date \n", - "2021-02-12 24.6\n", - "2021-02-11 25.9\n", - "2021-02-13 30.4\n", - "2021-02-14 32.1\n", - "2021-01-09 32.9" - ] - }, - "execution_count": 8, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "noaa_surface_median_temps=noaa_surface[['date', 'temp']].groupby('date').median()\n", - "noaa_surface_median_temps.peek()" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "id": "68324aaf", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "" - ] - }, - "execution_count": 9, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "image/png": "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OKLpmik2bdokCoIglpWVBfQcwQj36+UN41z8OpbXat26dWJMTIz49ttvBzy2rKws8emnn1Zvs1gso1L8Ou6Byd133y2azWbxo48+Euvq6tSv7u5u9Zi77rpLLCgoED/88ENx586dYmlpqVhaWqrev3//fjE9PV28+eabfc7R2Njo81x79uwR9+zZI5aUlIg33nijuGfPHvHgwYODju+1114TjUaj+NJLL4mHDh0S77zzTjEpKUmsr69Xj6mrqxP37NkjvvDCCyIAcevWreKePXvElpYWXiNRFCsqKsQnnnhC3Llzp3jy5Enx008/FZcvXy6mpKSIDQ0NI3KNvEX69Tp27Jj42GOPiTt37hQrKyvFf/7zn+KkSZPE8847bwSvkiTSr5Xi6NGjoiAI4rvvvjsCV2Vw0XDN/vznP4vbtm0Tjx07Jv71r38VU1JSxAceeGCErpCvcL9eVqtVfRwA8de//rW4Z88e8eTJk+oxLS0t4p49e8R33nlHBCC+9tpr4p49e8S6uroRukqSsbpWr7zyiqjT6cQ1a9b4HNPe3j7o+J588kkxKSlJ/Oc//ynu27dPvOqqq8Ti4mKxp6dHPebkyZPinj17xEcffVQ0mUzqtbVarQFfh3EPTAD4/fL+JN3T0yN+5zvfEZOTk8W4uDjxmmuu8fmDeOSRR/yeo7CwcMjn6nuMP7/73e/EgoIC0WAwiGeddZa4fft2n/sHev6RygZE+jU6deqUeNlll4kZGRmiXq8X8/LyxBtvvHFUPp0N9DtE0vWqqqoSzzvvPDElJUU0Go3ilClTxAcffNBneedIifRrpVi1apWYn58vulyuUC9FwKLhmv3gBz8QMzMzRb1eL06dOlX81a9+Jbrd7uFclgGF+/VSMt19v2699Vb1mLVr1/o95pFHHhn+BRpi/KNxrc4///whf2d/3G63+JOf/ETMzMwUjUajeNFFF4nl5eU+x9x6661+z7158+aAr4MgXwwiIiKicRdWq3KIiIhoYmNgQkRERGGDgQkRERGFDQYmREREFDYYmBAREVHYYGBCREREYYOBCREREYUNBiZEREQUNhiYENGIWbJkCe67777xHgYRRTAGJkQ0Lj766CMIgoD29vbxHgoRhREGJkRERBQ2GJgQUUi6urpwyy23wGQyITs7G7/61a987v/rX/+KM844AwkJCcjKysKNN96IxsZGAMCJEydwwQUXAACSk5MhCAJuu+02AIDb7cbq1atRXFyM2NhYzJs3D2+88caY/m5ENH4YmBBRSB588EFs2bIF//znP/Hee+/ho48+wu7du9X7HQ4HHn/8cezduxdvv/02Tpw4oQYf+fn5+Mc//gEAKC8vR11dHZ599lkAwOrVq/Hyyy/j+eefx8GDB3H//ffj5ptvxpYtW8b8dySiscfdhYkoaJ2dnUhNTcXf/vY3XHfddQCA1tZW5OXl4c4778RvfvObfo/ZuXMnzjzzTFitVphMJnz00Ue44IIL0NbWhqSkJACAzWZDSkoKPvjgA5SWlqqPveOOO9Dd3Y1169aNxa9HRONIN94DIKLIU1FRAbvdjoULF6q3paSkYPr06erPu3btwk9/+lPs3bsXbW1tcLvdAICqqirMmjXL73mPHTuG7u5uXHzxxT632+12zJ8/fxR+EyIKNwxMiGjEdXV1YdmyZVi2bBleeeUVpKeno6qqCsuWLYPdbh/wcZ2dnQCAd955B7m5uT73GY3GUR0zEYUHBiZEFLTJkydDr9fj888/R0FBAQCgra0NR44cwfnnn4+ysjK0tLTgySefRH5+PgBpKsebwWAAALhcLvW2WbNmwWg0oqqqCueff/4Y/TZEFE4YmBBR0EwmE26//XY8+OCDSE1NRUZGBn784x9Do5Hq6QsKCmAwGPC73/0Od911Fw4cOIDHH3/c5xyFhYUQBAH//ve/8ZWvfAWxsbFISEjA9773Pdx///1wu90455xzYLFY8OmnnyIxMRG33nrrePy6RDSGuCqHiELy1FNP4dxzz8Xy5cuxdOlSnHPOOSgpKQEApKen46WXXsL69esxa9YsPPnkk3j66ad9Hp+bm4tHH30UP/zhD5GZmYl77rkHAPD444/jJz/5CVavXo2ZM2fi0ksvxTvvvIPi4uIx/x2JaOxxVQ4RERGFDWZMiIiIKGwwMCEiIqKwwcCEiIiIwgYDEyIiIgobDEyIiIgobDAwISIiorDBwISIiIjCBgMTIiIiChsMTIiIiChsMDAhIiKisMHAhIiIiMLG/wc+Xu84OfM/pQAAAABJRU5ErkJggg==", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "noaa_surface_median_temps.plot.line()" - ] - }, - { - "cell_type": "markdown", - "id": "5b1e75df", - "metadata": {}, - "source": [ - "# Area Chart" - ] - }, - { - "cell_type": "markdown", - "id": "544f5605", - "metadata": {}, - "source": [ - "In this example you will use the table that tracks the popularity of names in the USA." - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "id": "0c8f9726", - "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", - "
stategenderyearnamenumber
0ALF1910Sadie40
1ALF1910Mary875
2ARF1910Vera39
3ARF1910Marie78
4ARF1910Lucille66
\n", - "
" - ], - "text/plain": [ - " state gender year name number\n", - "0 AL F 1910 Sadie 40\n", - "1 AL F 1910 Mary 875\n", - "2 AR F 1910 Vera 39\n", - "3 AR F 1910 Marie 78\n", - "4 AR F 1910 Lucille 66" - ] - }, - "execution_count": 10, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "usa_names = bpd.read_gbq(\"bigquery-public-data.usa_names.usa_1910_2013\")\n", - "usa_names.peek()" - ] - }, - { - "cell_type": "markdown", - "id": "be525493", - "metadata": {}, - "source": [ - "You want to visualize the trends of the popularities of three names in US history: Mary, Emily and Lisa." - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "id": "a12cd1f5", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "Query job d1fc606b-18b0-4e7e-a669-0e505a95aa5a is DONE. 132.6 MB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "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", - "
nameEmilyLisaMary
year
19271631070864
19182353067492
19121126032375
19232047071799
19331036055769
\n", - "
" - ], - "text/plain": [ - "name Emily Lisa Mary\n", - "year \n", - "1927 1631 0 70864\n", - "1918 2353 0 67492\n", - "1912 1126 0 32375\n", - "1923 2047 0 71799\n", - "1933 1036 0 55769" - ] - }, - "execution_count": 11, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "name_counts = usa_names[usa_names['name'].isin(('Mary', 'Emily', 'Lisa'))].groupby(('year', 'name'))['number'].sum()\n", - "name_counts = name_counts.unstack(level=1).fillna(0)\n", - "name_counts.peek()\n" - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "id": "4af287bd", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "" - ] - }, - "execution_count": 12, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAjkAAAGwCAYAAABLvHTgAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjYsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvq6yFwwAAAAlwSFlzAAAPYQAAD2EBqD+naQAAsTxJREFUeJzs3Xt8XHWd+P/XOWfuk8nk0jTpvYUCbbm0UKBUhUXtUrXsVxZwvSAggits0YWuovzWB/LV/YqyCygrWEWhdYXlpiJSKJZCuZZboLRNr2nTJm3ul5nJXM/MOef3x3SGhl6TzC3J++kjD8nMZ875JG0z73w+78/7rViWZSGEEEIIMcqoxZ6AEEIIIUQ+SJAjhBBCiFFJghwhhBBCjEoS5AghhBBiVJIgRwghhBCjkgQ5QgghhBiVJMgRQgghxKhkK/YEisk0TVpbW/H5fCiKUuzpCCGEEOI4WJZFf38/EydORFWPvF4zpoOc1tZWpkyZUuxpCCGEEGIIWlpamDx58hGfH9NBjs/nA9LfpPLy8iLPRgghhBDHIxQKMWXKlOz7+JGM6SAns0VVXl4uQY4QQggxwhwr1UQSj4UQQggxKkmQI4QQQohRSYIcIYQQQoxKYzonRwghhBguwzBIJpPFnsaoYrfb0TRt2NeRIEcIIYQYAsuyaG9vJxAIFHsqo1JFRQV1dXXDqmMnQY4QQggxBJkAZ/z48Xg8HikqmyOWZRGNRuns7ARgwoQJQ76WBDlCCCHEIBmGkQ1wqquriz2dUcftdgPQ2dnJ+PHjh7x1JYnHQgghxCBlcnA8Hk+RZzJ6Zb63w8l3kiBHCCGEGCLZosqfXHxvJcgRQgghxKgkQY4QQgghRiUJcoQQQggxKkmQI4QQQohRSYIccYiEkaAr2lXsaQghhBDDIkGOOMSLzS/ywKYH+KDrg2JPRQghxrwLL7yQb3/729xyyy1UVVVRV1fH7bffnn3+7rvv5vTTT8fr9TJlyhT+5V/+hXA4nH1+xYoVVFRU8Mwzz3DKKafg8Xi4/PLLiUajrFy5kunTp1NZWcm3v/1tDMPIvi6RSPCd73yHSZMm4fV6WbBgAevWrSvgVz58EuSIARJGgl2BXWzp3sL/bv1fTMs8rteZlkl3rBvLsvI8QyGEGHtWrlyJ1+vlrbfe4s477+RHP/oRa9asAUBVVe69914aGhpYuXIlL774IrfccsuA10ejUe69914effRRVq9ezbp16/jHf/xHnn32WZ599ln+53/+h1//+tc8+eST2dfceOONrF+/nkcffZSNGzfyhS98gc985jPs3LmzoF/7cCjWGH5XCoVC+P1+gsEg5eXlxZ5OSdjZt5PHtj9GfXs9NtXGTfNv4uOTPn7M172671XebHuTC6dcyDl15xRgpkIIUTzxeJympiZmzJiBy+XK670uvPBCDMPg1VdfzT527rnn8qlPfYqf/vSnh4x/8sknuf766+nu7gbSKznXXHMNjY2NnHjiiQBcf/31/M///A8dHR2UlZUB8JnPfIbp06ezfPlympubOeGEE2hubmbixInZay9atIhzzz2Xn/zkJ/n8koGjf4+P9/1b2jqIAZqCTfTF+0BJr+o8vetpFk5ciKocedEvrIfZ1LWJhu4GQomQBDlCCJFjZ5xxxoDPJ0yYkO3t9MILL3DHHXewbds2QqEQqVSKeDxONBrNVg32eDzZAAegtraW6dOnZwOczGOZa27atAnDMDj55JMH3DeRSIyoNhYS5IgswzTYG9pLV7SLqb6p9MR72BXYxWv7X+OCyRcc8XXvd75Pe7SdvkQfCTNBb6yXKndVAWcuhBCjm91uH/C5oiiYpsmePXu4+OKLueGGG/h//+//UVVVxWuvvca1116LruvZIOdwrz/SNQHC4TCaplFfX39I36iDA6NSJ0GOyGoNt9IT7yFhJJhYNpFyRzkbujbw9K6n+fjEj6OphzZIiyajbO7eTEt/C6qiEkvGWN+2niUnLCnCVyCEEGNLfX09pmly1113oarpFffHH3982Nc988wzMQyDzs5Ozj///GFfr1gk8VhkNYWaCMQD2FQbZfYyxnvHU+GsYE9wD6/se+Wwr9nQtYGOaAexVIyTKk7CtEze63ivwDMXQoixaebMmSSTSf77v/+b3bt38z//8z8sX7582Nc9+eSTueKKK7jqqqv405/+RFNTE2+//TZ33HEHq1atysHMC0OCHAGAZVnsCe6hK9ZFhasCRVHQFI0Z5TNIGAme2f0MKSM14DWxVIxNXZto6W+h0lVJrbcWp+Zke+92wnr4CHcSQgiRK3PnzuXuu+/mZz/7GaeddhoPP/wwd9xxR06u/dBDD3HVVVfxb//2b5xyyilccsklvPPOO0ydOjUn1y8EOV0lp6sA6Ip2sbJh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+tWv8tprr7F371727t3L66+/zle/+tUBYy677DIuvfRSZs6cybx583jwwQfZtGkTW7ZsGTDupptu4tJLL2XGjBlMmDCB6667jueff562tnQds87OTp599lm+/vWvF+zrg0EGOX19fXz84x/Hbrfz3HPPsWXLFu666y4qKyuzY+68807uvfdeli9fzltvvYXX62Xx4sXE4x9mml9xxRU0NDSwZs0annnmGV555RX++Z//Oft8KBTioosuYtq0adTX1/Of//mf3H777fzmN7/JjnnjjTf48pe/zLXXXsv777/PJZdcwiWXXMLmzSOzZspghPUwbZE2AonAmM3HyXCoDmrcNUSSER7c/CA/r/85b7a9SSwVK/bUjiqTdKwoCm778QcpmaPkjYFGYskYpmWWfLXj45X5Puzr31fciRhJeOvX8M7vQOr4iFGspqaGJUuWsGLFCh566CGWLFnCuHEDTznu3LmTL3/5y5xwwgmUl5czffp0AJqbmweMO/vsswd8fu6553LqqaeycuVKAP7whz8wbdo0LrigsLmTg0o8/tnPfsaUKVMGLEHNmDEj+9+WZfHzn/+cH/zgB3z+858H4Pe//z21tbU89dRTfOlLX2Lr1q2sXr2ad955J/tN+e///m8+97nP8V//9V9MnDiRhx9+GF3XefDBB3E4HJx66qls2LCBu+++OxsM/eIXv+Azn/kM3/3udwH48Y9/zJo1a/jlL395SLb4aJPZqsJixJ0oyjVFUZhdPZsyRxm7ArvojHayM7CT6eXTufa0a6krK80gMJN07NScgzpN5NJc2aPk3fFuFEXBYRuZiccf5bF5UBWVfeEiBznhTujdDW0b4YQLofrE4s5HiDz6+te/zo033gjAfffdd8jz//AP/8C0adN44IEHmDhxIqZpctppp6HrA1fNvd5Df9m67rrruO+++/j+97/PQw89xDXXXFPwnYdBreQ8/fTTnH322XzhC19g/PjxnHnmmTzwwAPZ55uammhvb2fRokXZx/x+PwsWLGD9+vUArF+/noqKigFR36JFi1BVlbfeeis75oILLsDh+PCH9+LFi9m+fTt9fX3ZMQffJzMmc5/DSSQShEKhAR8jUXN/M4FEAJfNNWYach6NqqhMK5/GJ6d8kllVswgkArzb/i5/3PnHYk/tiHriPUSSkUEf/VcUhWpXNYZp0BfvQ0Ud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", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "name_counts.plot.area(stacked=False, alpha=0.5)" - ] - }, - { - "cell_type": "markdown", - "id": "26d14b7e", - "metadata": {}, - "source": [ - "You can also use set `subplots` to `True` to draw separate graphs for each column." - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "id": "531e20b5", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "array([, ,\n", - " ], dtype=object)" - ] - }, - "execution_count": 13, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAjkAAAGwCAYAAABLvHTgAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjYsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvq6yFwwAAAAlwSFlzAAAPYQAAD2EBqD+naQAAgvVJREFUeJzs/Xl8lPW9//8/rtmXZLKvJCzKJsgiiDG22nqkYA/2d6zaWmutUrXVoucop7XyOR6rp+3X0/ZUrRXLqT0VPWqrnlZrRXFBwVo2jQQIJIFANpJM9sxMZl+u3x9XMpCyQ5LJTF53bnMjmbnmmtdcSWae877ei6KqqooQQgghRIrRJboAIYQQQoiRICFHCCGEEClJQo4QQgghUpKEHCGEEEKkJAk5QgghhEhJEnKEEEIIkZIk5AghhBAiJRkSXUAixWIxWltbSU9PR1GURJcjhBBCiFOgqioej4fi4mJ0uuO314zrkNPa2kppaWmiyxBCCCHEGWhubqakpOS4t4/rkJOeng5oB8nhcCS4GiGEEEKcCrfbTWlpafx9/HjGdcgZPEXlcDgk5AghhBBJ5mRdTaTjsRBCCCFS0rhuyRFCCCFGkicQ5mCnl5Y+Pzl2E/MnZmI26BNd1rghIUcIIYQYRn2+EDVODwc7vRzq9dHrDdHjDRGIRDk3L40rzy9kfmkWJoOcTBlpEnJOIhaLEQqFEl1GSjEajej18klGCJE6YjGV+m4vuw71UdfeT2d/kE5PkK7+EKBiMerxBiI4XQH2d/QzszCdf5xTxPzSTJnCZARJyDmBUChEfX09sVgs0aWknMzMTAoLC+WPWwiR1EKRGLsO9VHZ3Edrn592d4CWvgCqqpJmNnJeYTq56WaMeh2qqtLY42Of08NfPQH2t/ezeFY+X7mw9JROYfX5QrT0+ZmanyanvE6RhJzjUFWVtrY29Ho9paWlJ5xsSJw6VVXx+Xx0dHQAUFRUlOCKhBDi9IWjWrj5uKGXll4fTT0+erxhTHqFSdk2JmRZMeqHvm8oisLkHDsTs23Ud3qpdrr5Y0ULrX0Bbrv0HLLtpuM+Xq83xB8+buJgp5cMq5GlswuYPzHrqMcQQ0nIOY5IJILP56O4uBibzZboclKK1WoFoKOjg/z8fDl1JYRIGtGYyu4WF9sOdtPS56ex20uPN0S6xcj80gyybKaTD2tWFM7NTyM33cz2+m421nbQ4Qlyx2XnMLXg6Hlf+oMR/vTpIWqdHqqdblBhX7uHmUUOrpxdwNySTAwSdo5JQs5xRKNRAEym4ydrceYGg2M4HJaQI4RICl39Qd7Z005dh4eDXV56+kOkWQwsnJhFhu303ysyrEYum5ZHRVMvO5v7+M/1NVx9wQQ+Nz2PdIsRgEA4yqs7WuIdmWcXZRCJxdjf3k+Hp5N9Tg/l5+bwrc9OkVadY5CQcxLSZ2RkyHEVQiSLWExlR3Mvf93fRUOXl4YuHxaTjvkTM8m0Gs/q9cxs1HPxOTnUtLk52OXl2c0N/K2uiytnF3Lh5GzW7W6jutXFvnYPE7NtlGZrHxAn5dip7/JS6/SwbncbKHDbZ89Br5PX1iNJyBFCCCGOIRSJ0e4OsOVAN7XtHmqdbjyBCFNytX41w/VhTacozCrOoNBhZXdLH5809NLY5eOdve3odQp72zzkO8yck2cfcp9z89LISzPxtwPdvLW7DatBzzcunoROgk6chJzT5A6ECYSio/Z4FpMex0CzpRBCiJETjanUdfTT3OPD6Q7gdAXwBML0+cM0dnuxGg0smpyF3Twyr8nZaSYum56H0xVgT5ubTxp6MBv1ZNuNnFfoOGaoclhNlE3OZmt9D69VtmA26vjqhaXSWj5AQs5pcAfC/GrDfnq8ozdvTrbdxN1XTEto0HnooYd47bXXqKysBOCWW26hr6+P1157LWE1CSHEcIlEY1S3efi4oYfmXh8d7iB9vhDuQJiYCka9jtIsG5Nz7ehGODwoikJRppXCDAuHen34wzHOzUs7YWjJTjOzaHI22+t7eOWTQ1iMev5p/oQRrTNZSMg5DYFQlB5vCLNBj8008p1lfQOPFwhFTznk3HLLLTz77LNHXb906VLWr19/RnV873vf4+677z6j+wohxFgVi6nsPNTHJw09tPT5aerx0eUJYdQrOKxGZhY6yLabMBt0o94yoigKpdn2k284IC/dzMJJmXzS0MsLWxuZW5LJlNxTv3+qkpBzBmwmPXbz6By6YOT0T41deeWVPPPMM0OuM5vNZ1xDWloaaWlpZ3x/IYQYizbu6+Cj/V3Ud3np6g9iMxmYOyGD7LSTDwMfiwozrEzND1PX2c9rO1q49wvTE11Swsl4sxRkNpspLCwccsnKygK0Twf//d//zVVXXYXNZuO8885jy5Yt1NXV8fnPfx673c4ll1zCgQMH4vt76KGHmD9//jEf67nnniMnJ4dgMDjk+quvvpqbbrppxJ6jEEKcjZY+P5809LK7xUV/MML80kzKpmSTk25OyoAzqCTLhkGnsOVA16h2rRirJOSMQz/60Y/45je/SWVlJTNnzuTrX/863/nOd1i1ahWffPIJqqpy1113ndK+vvKVrxCNRnn99dfj13V0dLBu3Tq+9a1vjdRTEEKIMxaJxnhvbzsNXV4iUZULJ2WTbU/ucDMozWKg0GGh1xdm3a7WRJeTcBJyUtAbb7wRP8U0ePn//r//L3778uXL+epXv8r06dP5wQ9+QENDAzfeeCNLly7lvPPO41/+5V/YuHHjKT2W1Wrl61//+pDTY88//zwTJ07k85///DA/MyGEOHvbG3o42NnPoT4/UwvsKbcaeGm2DRXYUNNB6Ay6PKQS6ZOTgi6//HJ+/etfD7kuOzs7/vXcuXPjXxcUFAAwZ86cIdcFAgHcbjcOh+Okj3f77bezaNEiWlpamDBhAmvXruWWW25JiU9FQojU0tUfZNvBHvZ39OOwGChyWBNd0rDLtpvIthtxugJ8UNvJ0tmFiS4pYSTkpCC73c7UqVOPe7vReHik1mAQOdZ1p7r6+gUXXMC8efN47rnnWLJkCXv27GHdunVnUroQQowYVVXZUN1OY7cXbzBC2ZTslPwwpijaIqGfNvXxVlUbS2YVpOTzPBUScsSwuO2223j88cdpaWlh8eLFlJaWJrokIYQYYtchF/vb+2no8jI5147VlLpvgfnpFtLMBva391PZ3McFE7MSXVJCpO5PeAT5RmnG4zN9nGAwiNPpHHKdwWAgNzd3OMo6pq9//et873vf4+mnn+a5554bsccRQogz0esN8eH+TvZ3eDAb9UwcWAMqVRn0Oibm2NjT4ub1na0ScsTJWUx6su0meryhM5q/5kxk201YTnPiwfXr11NUVDTkuhkzZlBTUzOcpQ2RkZHBtddey7p167j66qtH7HGEEOJ0RaIx1u1u42BnP72+EBdOyh7xmYvHguIMKwc6vOxo6uVQr4+SrNQOdseiqKqqJrqIRHG73WRkZOByuY7qYBsIBKivr2fKlClYLJbD95G1q47riiuuYPbs2TzxxBMn3fZ4x1cIIYbbB7UdbKzpoLK5jym5dibljJ+ZgKtbXRzs8rJsbhH3fmFGossZNid6/z6StOScJofFmDShY7T09vayceNGNm7cyFNPPZXocoQQIq6uo5/t9T1Ut7nJtBpT/jTV35uYY6ep189f93dx9QUTmJI7vmavP63JAR555BEWLVpEeno6+fn5XH311dTW1g7ZJhAIsGLFCnJyckhLS+Paa6+lvb19yDZNTU0sW7YMm81Gfn4+3//+94lEIkO22bhxIwsWLMBsNjN16lTWrl17VD2rV69m8uTJWCwWysrK2L59++k8HTFMLrjgAm655RZ++tOfMmNG6nxSEEIkN3cgzDt7nOxv9xBVVWZPyBh3o4zsZgNTcuy4/GH+d0sj4+3kzWmFnE2bNrFixQq2bt3Ku+++SzgcZsmSJXi93vg29957L3/5y1945ZVX2LRpE62trVxzzTXx26PRKMuWLSMUCrF582aeffZZ1q5dy4MPPhjfpr6+nmXLlnH55ZdTWVnJPffcw2233cbbb78d3+all15i5cqV/PCHP+TTTz9l3rx5LF26lI6OjrM5HuIMNDQ04HK5+N73vpfoUoQQAoBoTGX9bicHO/vpcAeZXZyBUZ9ak/6dqkm5NixGPZ809rKjqS/R5Yyqs+qT09nZSX5+Pps2beKyyy7D5XKRl5fHiy++yHXXXQdATU1NfH2kiy++mLfeeourrrqK1tbW+ER0a9as4Qc/+AGdnZ2YTCZ+8IMfsG7dOqqqquKP9bWvfY2+vr74StplZWUsWrSIJ598EtDmdCktLeXuu+/m/vvvP6X6T6VPzuTJk7FaU2+yqETz+/00NDRInxwhxLDzh6K8sauVqhYXOw/1UZJp49z88XWa5u81dHnZ3eJiwaRMfnbtPHS65G7ROtU+OWcVa10uF3B4Nt2KigrC4TCLFy+ObzNz5kwmTpzIli1bANiyZQtz5syJBxyApUuX4na72bNnT3ybI/cxuM3gPkKhEBUVFUO20el0LF68OL7NsQSDQdxu95DL8ej1+vhjieHn8/mAoZMQCiHE2erqD/L77U1UNPayq8VFhtXIOXnjp6Px8ZRkWXFYjextdfPBvvFzxuOMOx7HYjHuuecePvOZz3D++ecD4HQ6MZlMZGZmDtm2oKAgPm+L0+kcEnAGbx+87UTbuN1u/H4/vb29RKPRY25zomHSjzzyCA8//PApPT+DwYDNZqOzsxOj0YhONz6bOYebqqr4fD46OjrIzMyMh0khhDhbBzv7eXN3G/vaPTR0+yjOtDA9P33c9cM5FoNex/T8NCqaennl40N89txczMbUf/0945CzYsUKqqqq+Oijj4aznhG1atUqVq5cGf/e7XYfd2ZeRVEoKiqivr6exsbG0Spx3MjMzKSwcPyupyKEGF57W928ubuNGqebDk+A6fnpTBiH88KcSEGGhZw0Mw3dXv6ys5XrLkz9menPKOTcddddvPHGG3z44YeUlJTEry8sLCQUCtHX1zekNae9vT3+hlZYWHjUKKjB0VdHbvP3I7La29txOBxYrVb0ej16vf6Y25zojdNsNmM2m0/5eZpMJqZNmyanrIaZ0WiUFhwhxLDxh6J8UNtBVYsLlz/E/NIssmymRJc15ugUhen5aWw92M2fd7ayeFYBmSl+nE4r5Kiqyt13382rr77Kxo0bmTJlypDbFy5ciNFoZMOGDVx77bUA1NbW0tTURHl5OQDl5eX85Cc/oaOjg/z8fADeffddHA4Hs2bNim/z5ptvDtn3u+++G9+HyWRi4cKFbNiwIT67biwWY8OGDdx1112neQhOTKfTScdYIYQYw7bVd3Oo10e3N8iFk7JwWFP7jftsZNtNFGdaaXMFeH5rI3f9w7RElzSiTqujyYoVK3j++ed58cUXSU9Px+l04nQ68fv9gDa1/6233srKlSv54IMPqKioYPny5ZSXl3PxxRcDsGTJEmbNmsVNN93Ezp07efvtt3nggQdYsWJFvJXljjvu4ODBg9x3333U1NTw1FNP8fLLL3PvvffGa1m5ciVPP/00zz77LNXV1dx55514vV6WL18+XMdGCCHEGNfrDfFpYy8HO73k2s0ScE5CURSmF6SjUxTer+lgX7sn0SWNqNMaQn68zlvPPPMMt9xyC6ANvf7Xf/1Xfv/73xMMBlm6dClPPfXUkNNIjY2N3HnnnWzcuBG73c7NN9/Mf/7nf2IwHG5Y2rhxI/feey979+6lpKSEf//3f48/xqAnn3ySn//85zidTubPn88TTzxBWVnZKT/5Ux2CJoQQYmx6Y1crH9R0UNfRT/k5OeOiM+1w2N/uobbdQ9mUbH589ZykG1J+qu/fsnaVhBwhhEhKLX1+nt/ayLaD3RSkm5leKK/jpyocjfFRXRfRmMq/LpnOP8wsOPmdxpBRmSdHCCGESARVVfnrvk6ae3yowDl543uyv9Nl1OuYWZBOIBzl99ub8YciJ79TEpKQI4QQIunsa+/nQGc/zT0+puTYMYzTJRvORkGGhbw0M03dXl7+pDnR5YwI+a0QQgiRVIKRKB/VddHQ7cWk1zEhS5beORM6RWFmkYOYCm/tdtLm8ie6pGEnIUcIIUTSUFWV96s7qO/qp90VZNrASCFxZjKsRibl2OjqD/LC1qZElzPsJOQIIYRIGnta3VQ291HT5iE3zUxumgwZP1tTcu0Y9Tr+VtfF/o7UGlIuIUcIIURS6PQE2VDdTo3TjaLAecWyLtVwsJkMTMm14w6EeX5LI6k06FpCjhBCiDEvFInx5u42DnT20+cLM3dCBgZZOHnYTMyxYTXq2dHUS2VzX6LLGTbyGyKEEGLMe7+mg7oOD43dPqbmp5FmMSa6pJRiNug5Ny8NbyjKC9uaiMVSozVHQo4QQogxraKxl8qmXqrbPGTbTUzIlNFUI6Eky0q62Uh1m5u/1nUmupxhISFHCCHEmFXZ3Md71e1UtbpQFJhV5JB+OCPEoNcxrcBOIBTl5Y8PEY5EE13SWZOQI4QQYkza2dzHO3ucVB1y4Q9FuaA0Uyb9G2GFDitZdhMHO/v5047WpO+ELL8tQgghxpzdh1y8vcfJ7hYXvlCEBZOysJoMJ7+jOCs6ncKMgjTC0RivfnqIN3a1JXXQkZAjhBBiTNnb6mZ9VRtVLS68oQgLJmZhk4AzanLTLcwryaTDE+R/tzbw6o6WpA068lsjhBBizGh3B3h7j5OqVhf9wQgLJmZiM8tb1Wgrybah0yl82tTL77c3EY7G+MrCUnS65OoPJb85QgghxoRAOMq6XYfnwrlwUhZ2swwVT5TiTCt6BT5u7OXlT5pp7QtQ4LCg12nrXtlMBhZMyqQoY+yOdpOQI4QQIuFUVeWdve3UdXho6vFxXqFD5sIZAwoyrJRNUdje0MuG6naMeh26gZCjUxTWV5m5fGY+V8wsIMM29n5eEnKEEEIk3KdNfew+1Eet00NBupnCDEuiSxID8tIt/MOMfJyuAJFYjKiqEouByx9ib5ublj4/Ww92c+X5hVxybi4Woz7RJcdJyBFCCJFQrX1+NtV2UN3mxqDXMaPQkeiSxN+xmvRMybMfdb3LF2JXi4tPm/po6vGxsbaTpbMKWTg5a0yEHQk5QgghRkUsptLq8tPc48cXiuALRfGFInT3h6jr6Kc/EGXR5Cz0Sda5dTzLsJn47NRcOjxBqlpcbD3YzYGOfqbmp7NkVkHCw46EHCGEECMmEo3R3OunrqOfA539dHoC9HjD+EIRAuEowXCMYCRGJBZjdnGGjKRKQoqiUOCwkJ9upqXPT63Tw5aDXdR1eJhWkM53PndOwjony2+TEEKIMxaOxjDolCFLLaiqSkufn5o2D7XtHro8Qbq9QdrdQbzBCAa9glGvw2LQYTcbyEnTkZNmJstmSuAzEWdLURRKsmxMyLTS0uenus1DZXMvagIX+5SQI4QQ4oxsrO3g4/oeFEUhN91Els2EzWSgqdtLmytAV3+QNlcAXzCCUa8jO83EzMJ0MqxGWX8qhQ2GnWy7iZZeP9EEziMoIUcIIcRpa+7xsb2+h08aeugPRjDoFCxGPTazgWhUpc8fwqBTyLGbmVWUTrpFgs14oygKJkNiF1aQkCOEEOK0hCIx3t3bTn2Xl1BMZc6EDHyhKN5gBF84ismgY15JJll2EzoJNiKBJOQIIYQ4LVsOdlPf1U9Lr59ZxQ4KHDKnjRibZIFOIYQQp6zN5efj+h72tfeTZTOSn25OdElCHJeEHCGEEKckEtVOUzV0ewlGopxX7JB+NmJMk5AjhBDilGxv6OFgZz9NPT6m5aVhNiR+RlshTuS0Q86HH37Il770JYqLi1EUhddee23I7aqq8uCDD1JUVITVamXx4sXs379/yDY9PT3ceOONOBwOMjMzufXWW+nv7x+yza5du7j00kuxWCyUlpbys5/97KhaXnnlFWbOnInFYmHOnDm8+eabp/t0hBBCnIKu/iBbD3Szz9mPw2KgKHPsrjwtxKDTDjler5d58+axevXqY97+s5/9jCeeeII1a9awbds27HY7S5cuJRAIxLe58cYb2bNnD++++y5vvPEGH374Id/+9rfjt7vdbpYsWcKkSZOoqKjg5z//OQ899BC/+c1v4tts3ryZG264gVtvvZUdO3Zw9dVXc/XVV1NVVXW6T0kIIcQJqKrKhup2mnp8eEMRZhXJaSqRHBRVVc94mh5FUXj11Ve5+uqrAe0Pobi4mH/913/le9/7HgAul4uCggLWrl3L1772Naqrq5k1axYff/wxF154IQDr16/nH//xHzl06BDFxcX8+te/5t/+7d9wOp2YTNoMmPfffz+vvfYaNTU1AFx//fV4vV7eeOONeD0XX3wx8+fPZ82aNadUv9vtJiMjA5fLhcMhC8IJIcSx7D7k4s+VLXzS0MOkXDuTc45eqFGIv+cPR+lwB/jXJTMozbYN675P9f17WPvk1NfX43Q6Wbx4cfy6jIwMysrK2LJlCwBbtmwhMzMzHnAAFi9ejE6nY9u2bfFtLrvssnjAAVi6dCm1tbX09vbGtznycQa3GXycYwkGg7jd7iEXIYQQx+cNRvhwfyd1Hf2YDDomDvOblRAjaVhDjtPpBKCgoGDI9QUFBfHbnE4n+fn5Q243GAxkZ2cP2eZY+zjyMY63zeDtx/LII4+QkZERv5SWlp7uUxRCiHHlw32dNHV76fYGmV3skMn9RFIZV6OrVq1ahcvlil+am5sTXZIQQoxZDV1edh3qo67TS1GGBYdVFtAUyWVYQ05hYSEA7e3tQ65vb2+P31ZYWEhHR8eQ2yORCD09PUO2OdY+jnyM420zePuxmM1mHA7HkIsQQoijhaMx3q/poL7Li6qqTMtPT3RJQpy2YQ05U6ZMobCwkA0bNsSvc7vdbNu2jfLycgDKy8vp6+ujoqIivs37779PLBajrKwsvs2HH35IOByOb/Puu+8yY8YMsrKy4tsc+TiD2ww+jhBCiDMTisRYt6uN+q5+Wvv8TC9Ix6AfVw3/IkWc9m9tf38/lZWVVFZWAlpn48rKSpqamlAUhXvuuYcf//jHvP766+zevZtvfvObFBcXx0dgnXfeeVx55ZXcfvvtbN++nb/97W/cddddfO1rX6O4uBiAr3/965hMJm699Vb27NnDSy+9xC9/+UtWrlwZr+Nf/uVfWL9+Pb/4xS+oqanhoYce4pNPPuGuu+46+6MihBDjVCAc5U+fHqKisZc9rW5y0kyydINIWqe9QOcnn3zC5ZdfHv9+MHjcfPPNrF27lvvuuw+v18u3v/1t+vr6+OxnP8v69euxWA4v4PbCCy9w1113ccUVV6DT6bj22mt54okn4rdnZGTwzjvvsGLFChYuXEhubi4PPvjgkLl0LrnkEl588UUeeOAB/t//+39MmzaN1157jfPPP/+MDoQQQox3nkCYV3e0UN3mprrNTbbNzGxZukEksbOaJyfZyTw5Qgih6fQE+XNlCzVOD/vbPRRlWJhekC4BR5yxsTBPzmm35AghhEgNqqpyqNfPp0297G/vp6nHS0O3j0nZNqbk2iXgiKQnIUcIIcaZWExlX4eHisZemrp9tLn8NPf4iakq0/LTKMmSCf9EapCQI4QQ44SqqtR3eflbXRcN3T4O9fpo7fNj0OuYkGVhYrYdo4yiEilEQo4QQowDrX1+PtrfRV1nP83dPlpcfkx6HTML0yl0WNHp5NSUSD0ScoQQIsVtO9jNxn2dHOrx0dTjw6BTBsKNRfrdiJQmIUcIIVJYjdPNpn2dVDb1EghHOSfPTkmmTVpuxLggIUcIIVJUm8vP+ion1W1uQpEYZefkYDboE12WEKNGepgJIUQKcvnD/LmylX1ODz3eEPNKMyXgiHFHQo4QQqSYYCTK6ztb2d/u4VCvj1lFDtItxkSXJcSok9NVQgiRxALhKBWNvfT5woSiUUKRGJ5AhIYuL3Ud/UzJTSPfYTn5joRIQRJyhBAiSQUjUV7b0UJVi4tDfX7CkRjhWIxIVCUcVcl3mJmUIxP7ifFLQo4QQiShcDTGnytbqWpxUdXqxmxQsBoN2Ex6jAYdaSYDBTJEXIxzEnKEECLJRKIxXq9sZfehPqpa3eSlmTivSFYLF+LvScdjIYRIItGYyhu72th1qI89rW6ybRJwhDgeCTlCCJEkVFVlfZWTyuY+dre4cFgNzJ4gAUeI45GQI4QQSWLzgW52NPVS1dJHmtnAnOJMdBJwhDguCTlCCJEEqlpcfLS/k6oWF0aDnrklmbI0gxAnISFHCCHGuOYeH+/scbK3zU04pjK/JBO9BBwhTkpCjhBCjGE93hCv72yltt2Dyx9hfkkGJoO8dAtxKmQIuRBCjFGN3V7e3dvOvnYPba4Ac0sySJPlGYQ4ZRJyhBBijPEGI3y4r5Ndh/qo7/LR0udjekE6OXZzoksTIqlIyElBqqoSjMRw+8O4A2EC4Rhmgw6jXofJoMNs0GE3G7AYZUViIcaSaEzVOhjXddHU7aWu04uqqswuzqBA1p8S4rRJyEkiHZ4AHe4g/cEI/YEI/cEIvlCUqKqCqqICsZiKJxjBE4gQDEcJRmKEIjFQQK8o6HXaxahXSDMbyU03kWUzYdLrUAceR1VBUUAX3x70Oi0cWY16bCY9FqMek0GHooCCgqKAUafDYTXInB1CnCZPIMzuFhdVLS7a+gIc7Oqn1xemMMPCtLw0DHrpgyPEmZCQM8aFIjH2tXvY3eKisdtLd3+IYERbadgXiuIPR4lEVVRU1IGUElNVojHtG2UgqKiqSkzVPimqqKCCXqdg0CmYjXqsR7TqDIYd3UDQ0R0Rjgw6BaNeh0GvYNDp0CmAAoOxJtNmYkZhOhOzbZRm2ciwGmWYqxj3QpEYTT1eDnZ6icTUeKuqUa/Q1R9if7uHDk+Q1l4/vf4QdpOBhRMzcVhNiS5diKQmIWcM6g9GaOvz09Tjo7rNTbs7iNPtp90dBJWBgKGFk2ybCYNOpwWNgbBh1OtINxuwmvWY9LohLSuDYScQjuINaq1B3lCEUEQFVBQObxtRVWKxGLEjAlI0phJVVaJRNR6Gjty3Tqew5UA3uWkmMmwm0s0GjHodRoMWjiwGPTazFqosRj0Wow6LUY/dpC0saDUNfG3WYzbI6TSRnFRVa1E91OPnQGc/9Z39dHlD9HhDuPzh+AcGg16HArS7A0RiMTKsJhZMzCLTapQWUSGGgYScBIvGVLr6g7S5ArT1+Wnt89PpCQ6ccgrT4dFOT9lMBmbkp1GQYT2r+TEURUGvgN1swG42kH8WtcdiWtAZbBmKqird/SGcrgCH+vwc6PRi0GmnshRFi0+Dp8FMBh2mI/oIGfW6eAuRaeBrm9lAps2Aw2IkzWzEZj4chhwWIxk2I+lmw6i1FIWjMfoDEaKqSkzVnvNAg1k8YHJE65du4HnD4VDpC0XxhaJEojEtLMYOt8CZDLr4cTEbtVODVqMei0n733icUxahSIwOT4B2d4D+YBR/KII/rD1OTAXzwDE2G/SYjbr4DLmDPw+DTofFqN1uMWo16HUKekVBp9Oey2D93pD2f0xVtf0ZtFqNeh2qergVMaaq6BTtjVx7Q9eh1yvxU6YGnYJ+4GdtNujG1Bu6qqpaC2lM1YKITodBpxz39ywYieIJaKeIB/+WnS4/3f0hPIEIfb4QTneAUDQWP+UbCsfwDnxo0OsUijIsTMy2y9BwIYaZhJxRNthKo70QBmh1+XH5wwN9bML0eEN4g1FAe9PLspmYXeQYk8NGD7/oa/8bgOJMK8WZVkALBd5AhEhMJRqLEVG11ZND0RihsPa/PxzF7Q8TVVUiMVU79Tbwrn+4/5D2RmgZaP0x6hVMBj0Wgw6rSU9eulnrV3REcDINvqkPvAmbDXr0ikJsoO/SYItWTNUe88g358HbVVX7efX6QvT0h+jyhgiEtDc/Bk4PattqYSF+JJTBAKHE/4/G1Phzj0RVwrFYPBTEVC0wap/sB08N6uLfG3TaaQ2byUCW3USm1UiaxYBeUWj3aL9Hbn843kcrFIkRjGj9scKRWPwYGgeOj34weA7+9I4IHlr40MVPVQ6G0piqEo7ECEfVgecQQze4/UCtwJBjp+2beOA7cn/x63UKRp32c7SZtZ+pQa+LhyHdwLEb/P2IDf6MjmhG1OvAYtTHf97a1zrMAy2FZoMeRWHgvsR/zuFojEhM+z8UieEORHD5w/R6Q3gCYcLRGAoKOp12jAw6LZyb9XpMRh1GnYI3FMHtjxCKxAhEogTDMTzBML3eMP5wFAWwmvQUZ1gpzDBjN4+9v2MhUpmEnBFw5Ce7/oDWItPnD9N2RCtNfyBCnz9Eny9MZODTnMWgI8NmYmq+mQyr8bif3JOFUa8j0356fQpUVXtDC4Ri+EMRvOEogXCUQDiGPxSlzx+OB4ZIVI2/0ZoN2pv4YJ8ho153uBVBf8QncVUd0sFaVVViR3ytBZeh/Zu8gx25I4dP3QH8/ed6deA69RjX6gaaTQaDxJHrDekGAkdkoAatdUclEiPeqVwZCAr6gTdaq1GP1WTAH9LemGMqA89de74Wgx67zYBBryMcjWlv5FHtDXhgl/Hq1COC1mAr1eEQMVi/9vhaCNMBarwlLxbTjhMQ74iOAooK0cEwqEJsoMXv8J61749s9dLptJZG5YiQo6ISix0OJ0Nr034SBv3gz12JB129Thc/tav1Sxv6846ph0NTTIVgOEp/KII/FCU68JzUgWM/2Or1918zEL605z5wStaoIzfNRF6aGYfVKJ2GhUigpA85q1ev5uc//zlOp5N58+bxq1/9iosuuihh9aiqytMfHqTPFx7o66J9SgyEo7j8Ybwh7dOdaWAY9zl5drJtJuxmGZUEA5+YFYU0i440i4G8E2wbjkTpD0ZxB8L4QlHtk3lUO9XgCUTinawPv2Fpb04qh6f6HnxTHuxAzRG9kgbfyGxGPYUZFjIsRtIsRgx6ZUhLzaDD4UmNfz94WmswjJ3uz1gdaHEIRKIEQjG8oYHTUaEofb4QdrOeGQUOsuxGrEb9sP0ODQYfdSAUKjDQAnTm+x/c1+EWLC0ghMKxeGf68EBrTVRV4+FpMNzpBk6hDbaWxfcLhAf+zsJRFW8oiisQjv/cYwOnBBXdwEhADv9sB3/uOrSgnGkxUZJpIN1qwGLQa2EzqgXPSEwlEosRjaG1TMZUDAOjFK0mPWa9TjrZCzHGJHXIeemll1i5ciVr1qyhrKyMxx9/nKVLl1JbW0t+/tn0NjlziqKdVti0rxNvMDrwyVprks+0mZhWkBqtNGOB0aAny6An6zRbi0be8L3RKYp2as5k0DOa06QM9t0a7ucyuEv9wH7NwJj78QkhUkZSh5xHH32U22+/neXLlwOwZs0a1q1bx+9+9zvuv//+hNV1/aISDnT2A5CbJjOUCiGEEImQtCEnFApRUVHBqlWr4tfpdDoWL17Mli1bjnmfYDBIMBiMf+9yuQBwu93DWlswEiXo66c/ECHgTdpDLIQQQpyxwf6AHrcbtyEyrPsefN9WVfWE2yXtO3BXVxfRaJSCgoIh1xcUFFBTU3PM+zzyyCM8/PDDR11fWlo6IjUKIYQQ492aEdy3x+MhIyPjuLcnbcg5E6tWrWLlypXx72OxGD09PeTk5IzrTr9ut5vS0lKam5txOByJLielyLEdWXJ8R5Yc35Ejx/bsqKqKx+OhuLj4hNslbcjJzc1Fr9fT3t4+5Pr29nYKCwuPeR+z2YzZPLSPTGZm5kiVmHQcDof8sY0QObYjS47vyJLjO3Lk2J65E7XgDEraIT4mk4mFCxeyYcOG+HWxWIwNGzZQXl6ewMqEEEIIMRYkbUsOwMqVK7n55pu58MILueiii3j88cfxer3x0VZCCCGEGL+SOuRcf/31dHZ28uCDD+J0Opk/fz7r168/qjOyODGz2cwPf/jDo07libMnx3ZkyfEdWXJ8R44c29GhqCcbfyWEEEIIkYSStk+OEEIIIcSJSMgRQgghREqSkCOEEEKIlCQhRwghhBApSUKOEEIIIVKShBwhhBBCpCQJOUIIIYRISRJyhBBCCJGSJOQIIYQQIiVJyBFCCCFESpKQI4QQQoiUJCFHCCGEEClJQo4QQgghUpKEHCGEEEKkJEOiC0ikWCxGa2sr6enpKIqS6HKEEEIIcQpUVcXj8VBcXIxOd/z2mnEdclpbWyktLU10GUIIIYQ4A83NzZSUlBz39nEdctLT0wHtIDkcjgRXI4QQQohT4Xa7KS0tjb+PH8+4DjmDp6gcDoeEHCGEECLJnKyriXQ8FkKIv+frgaAn0VUIIc6ShBwhhDiSvw+2Pw0bf6p9LYRIWhJyhBDiSK07wNUMDR/BR49BLJroioQQZ2hc98kRQoghomFo2wl9TRDohYObIO8VmP+1RFcmUoCqqkQiEaJRCc4no9frMRgMZz29i4QcIYQY1FEN7hYIuKCkDNp2wI7/hYJZUDQ30dWJJBYKhWhra8Pn8yW6lKRhs9koKirCZDKd8T4k5AghBICqQssn4GoBox2yJkM0BO1V8NdfwFWPgy0r0VWKJBSLxaivr0ev11NcXIzJZJIJaE9AVVVCoRCdnZ3U19czbdq0E074dyIScoQQArQWnJ4G8LRCwfmgKJA7DXzd0FkLHz0Kix8CnT7RlYokEwqFiMVilJaWYrPZEl1OUrBarRiNRhobGwmFQlgsljPaj3Q8FkIIgJYK8LSBooe0Qu06RQcTFoDeBPUfQvVfElujSGpn2hoxXg3H8ZIjLoQYX+reg+2/hd7Gw9cF+6F9L/Q1gmPC0NYagwWKL4CwD3a8AAGZP0eIZCGnq4QQ40c0DM0fa8PD6zfB3Oth+lJoq9RacaIhyJ5y9P3SCiC9SBta/ulzcMmKUS9dpKiAC8L+0Xs8oxUsGaP3eAkmIUcIMX64msHfA/5e6G+HrU9pQ8bN6dDXDJZMreXm7ykK5M+C/g6oXQez/n+QKYv7irMUcMGmn2n9vkaLLQc+d9+wBB1FUXj11Ve5+uqrz76uESIhRwgxfvQ2arMYG61QsggOfQy1b0HWJC34lCw6/n0tGZA1BXrq4OOn4Qv/MWplixQV9msBx2AF4yh0SA77tMcL+0855Nxyyy309fXx2muvHXVbW1sbWVlje8ShhBwhxPjR16i15FgckF4I05ZC66fQtQ/seWA9yQt27jRwD8yG3PKp1ilZiLNltIE5bXQeKzJ8p8YKCwuHbV8jRToeCyHGh3BAOyXl6z48espggokXw7QroeQi7bTUiRitkDsDAm74+Ley5IMY1xRFibfwhEIh7rrrLoqKirBYLEyaNIlHHnkkvu2jjz7KnDlzsNvtlJaW8t3vfpf+/v4Rr1FCjhBifHAd0vpAqDFIyx96m8kGBvOp7SdrstbU79wNtW8Oe5lCJKMnnniC119/nZdffpna2lpeeOEFJk+eHL9dp9PxxBNPsGfPHp599lnef/997rvvvhGvS05XCSHGh74GCPSB3nzszsWnSmfQOiE3bYFP1kLONMifOUxFCpGcmpqamDZtGp/97GdRFIVJkyYNuf2ee+6Jfz158mR+/OMfc8cdd/DUU0+NaF2n1ZLz0EMPoSjKkMvMmYf/uAOBACtWrCAnJ4e0tDSuvfZa2tvbh+yjqamJZcuWYbPZyM/P5/vf/z6RSGTINhs3bmTBggWYzWamTp3K2rVrj6pl9erVTJ48GYvFQllZGdu3bz+dpyKEGG96G8HbBWbH2e8rvQhypoKrCT74iXYaTIhx7JZbbqGyspIZM2bwz//8z7zzzjtDbn/vvfe44oormDBhAunp6dx00010d3eP+Fpep326avbs2bS1tcUvH330Ufy2e++9l7/85S+88sorbNq0idbWVq655pr47dFolGXLlhEKhdi8eTPPPvssa9eu5cEHH4xvU19fz7Jly7j88suprKzknnvu4bbbbuPtt9+Ob/PSSy+xcuVKfvjDH/Lpp58yb948li5dSkdHx5keByFEKgv5tNNV/l4toJwtRYHCOeAo0ZZ82PAj8I7iMGAhxpgFCxZQX1/Pj370I/x+P1/96le57rrrAGhoaOCqq65i7ty5/PGPf6SiooLVq1cDWl+ekXTaIcdgMFBYWBi/5ObmAuByufif//kfHn30Uf7hH/6BhQsX8swzz7B582a2bt0KwDvvvMPevXt5/vnnmT9/Pl/84hf50Y9+xOrVq+NPdM2aNUyZMoVf/OIXnHfeedx1111cd911PPbYY/EaHn30UW6//XaWL1/OrFmzWLNmDTabjd/97nfDcUyEEKmmr0nrj4MK9tzh2efgkg/2XHDugvd/BEGZDVmMXw6Hg+uvv56nn36al156iT/+8Y/09PRQUVFBLBbjF7/4BRdffDHTp0+ntbV1VGo67ZCzf/9+iouLOeecc7jxxhtpamoCoKKignA4zOLFi+Pbzpw5k4kTJ7JlyxYAtmzZwpw5cygoKIhvs3TpUtxuN3v27Ilvc+Q+BrcZ3EcoFKKiomLINjqdjsWLF8e3OZ5gMIjb7R5yEUKMA32NWsjRm0+9g/Gp0BmgtAxMadqcO5t+JiOuxOkL+7SlRUb6Ej6zU0Mul4vKysohl+bmoadoH330UX7/+99TU1PDvn37eOWVVygsLCQzM5OpU6cSDof51a9+xcGDB/nf//1f1qxZMxxH7qROq+NxWVkZa9euZcaMGbS1tfHwww9z6aWXUlVVhdPpxGQykZmZOeQ+BQUFOJ1OAJxO55CAM3j74G0n2sbtduP3++nt7SUajR5zm5qamhPW/8gjj/Dwww+fzlMWQqSCvibwdmozGg83vQkmXQIHN8LBTVDzBsz6p+F/HJF6jFZtBmJf97DOX3NCthztcU/Dxo0bueCCC4Zcd+uttw75Pj09nZ/97Gfs378fvV7PokWLePPNN9HpdMybN49HH32Un/70p6xatYrLLruMRx55hG9+85tn/XRO5rRCzhe/+MX413PnzqWsrIxJkybx8ssvY7We3kFLhFWrVrFy5cr49263m9JSmZpdiJQW9ICrRWvJKZo/Mo9htGr7bt6iLeJ57hWjN7mbSF6WDG2JhTG8dtXatWuPOfgH4Le//W3869tvv53bb7/9uPu59957uffee4dcd9NNN51yHWfqrIaQZ2ZmMn36dOrq6vjCF75AKBSir69vSGtOe3t7fFbEwsLCo0ZBDY6+OnKbvx+R1d7ejsPhwGq1otfr0ev1x9zmZLMvms1mzOZhbKoWQox9fU3a0HFU7VPsSEkv1Do19zXBjv+Fi+8cuccSqcOSMa4WzBxtZzUZYH9/PwcOHKCoqIiFCxdiNBrZsGFD/Pba2lqampooLy8HoLy8nN27dw8ZBfXuu+/icDiYNWtWfJsj9zG4zeA+TCYTCxcuHLJNLBZjw4YN8W2EECKud6A/jsEKeuPIPY6iQN55gArVb2itR0KIhDqtkPO9732PTZs20dDQwObNm/nyl7+MXq/nhhtuICMjg1tvvZWVK1fywQcfUFFRwfLlyykvL+fiiy8GYMmSJcyaNYubbrqJnTt38vbbb/PAAw+wYsWKeAvLHXfcwcGDB7nvvvuoqanhqaee4uWXXx7SzLVy5Uqefvppnn32Waqrq7nzzjvxer0sX758GA+NECIl9DZo/XFOti7VcLBmajMiezth+9Mj/3hCiBM6rdNVhw4d4oYbbqC7u5u8vDw++9nPsnXrVvLy8gB47LHH0Ol0XHvttQSDQZYuXTpkNkO9Xs8bb7zBnXfeSXl5OXa7nZtvvpn/+I/Dq/lOmTKFdevWce+99/LLX/6SkpISfvvb37J06dL4Ntdffz2dnZ08+OCDOJ1O5s+fz/r164/qjCyEGOd8PdDvhKBbm5l4NOTO0FpxGv4KrTuheN7oPK4Q4iiKqqpqootIFLfbTUZGBi6XC4djGGZBFUKMHbEY7HwRGv4GndVaZ2DdKK1k01UHzp3a4p9fegJ0skzgeBYIBKivr2fy5MlJMUhnrPD7/TQ0NDBlyhQslqFLsZzq+7f85QkhUlPDX6F9D3Ttg4xJoxdwALImactHtO2EAxtOvr1IaUaj1hdspJcwSDWDx2vw+J0JWaBTCJF6euqh/kNtpXCTHfKmj+7j642QNxMObYfd/wdTF2sdk8W4pNfryczMjA+6sdlsKPL7cFyqquLz+ejo6CAzMxO9Xn/G+5KQI4RILcF+2Pu6tqZUOACTP6MtwTDa0gvBnA4d1VrYKpo7+jWIMWNwihNZY/HUZWZmnnRqmJORkCOESB2xGFS/Dt37wd0CRReAwXLy+40EvVE7beWsgr2vScgZ5xRFoaioiPz8fMLhcKLLGfOMRuNZteAMkpAjhEgdLZ9ooaKzBjImQlpeYutJnwCd+7TOz94esGcnth6RcIMT2orRIR2PhRCpw7lb62isN41+P5xjMaeBowj8vVD9WqKrEWLckZAjhEgNQQ+4DmkT8WWdk5h+OMeSMVH7f987EI0kthYhxpkx8ioghBBnqeeg1mICiT9NdSR7rjbbsqsZ6jcluhohxhUJOUKI1NBzUJvh2GjVTleNFYoOMidDJAg1byS6GiHGFQk5QojkF4tB90HobwfbGGrFGeQoAqMNWiuh+0CiqxFi3JCQI4RIfu4WrS9OxA8ZExJdzdEMZsgshVA/7Hk10dUIMW5IyBFCJL+eA1p/HJ1RW05hLMooAUUPBzdByJvoaoQYFyTkCCGSX89B8HWBxTF2l08wZ0BagdbiVL0u0dUIMS5IyBFCJLdgP/Q1g7cL0osTXc3xKQpkTgI1CrVvgqomuiIhUp6EHCFEchsydDw/sbWcTFq+djqt5wAc+iTR1QiR8iTkCCGS22DIMYyxoePHotND1mQI+7X1rIQQI0pCjhAiecViWsjxOME+BoeOH4ujWBtt1bwN3G2JrkaIlCYhRwiRvDyth4eOO8bg0PFjMdq0kVYBtwwnF2KEScgRQiSv7sGh4wZtZFWyyCjVOiLXvQeRUKKrESJlScgRQiSvnoPaqCrzGB46fizWbLDlaKfZ6t5LdDVCpCwJOUKI5BT0QF+TFnKS5VTVIEXR1rOKhqD6LzKcXIgRIiFHCJGcuvZpC3Kijv2h48eSXgimNOjYC+1Via5GiJQkIUcIkZw692mtOCb72B86fix6ozacPOSFXS8nuhohUpKEHCFE8gn7tf44/c7kO1V1pMyJYLBA42boqU90NUKkHAk5Qojk010Hvm6IRbR5Z5KV0QpZkyDohsoXE12NEClHQo4QIvl01mqnqoxWrSUkmWVN1lZPr98kkwMKMcwk5Aghkks0rM2P42mDtMJEV3P2TGnavDn+Ptj5+0RXI0RKkZAjhEgug3PjREPJ3R/nSNlTQNFpc+b4ehJdjRApQ0KOECK5dNZq/XH0Zm1kVSqwZICjRAtvMtJKiGEjIUcIkTxiUejar61ZlZafXLMcn0z2FO351L4Fwf5EVyNESpCQI4RIHn2N4O2AkE9b5DKVWLO0Pkb97VD1x0RXI0RKkJAjhEgeXfu1Pit6k7ZeVSpRFMg5F9QY7H1dWnOEGAanFXIeeeQRFi1aRHp6Ovn5+Vx99dXU1tYO2ebzn/88iqIMudxxxx1DtmlqamLZsmXYbDby8/P5/ve/TyQSGbLNxo0bWbBgAWazmalTp7J27dqj6lm9ejWTJ0/GYrFQVlbG9u3bT+fpCCGSiapq/XE8bWDLTa1TVYNsuZBeBO4WGWklxDA4rZCzadMmVqxYwdatW3n33XcJh8MsWbIEr9c7ZLvbb7+dtra2+OVnP/tZ/LZoNMqyZcsIhUJs3ryZZ599lrVr1/Lggw/Gt6mvr2fZsmVcfvnlVFZWcs8993Dbbbfx9ttvx7d56aWXWLlyJT/84Q/59NNPmTdvHkuXLqWjo+NMj4UQYixzt2irdgfdkFma6GpGhqJA7nRAheo3wNeb6IqESGqKqp758rednZ3k5+ezadMmLrvsMkBryZk/fz6PP/74Me/z1ltvcdVVV9Ha2kpBQQEAa9as4Qc/+AGdnZ2YTCZ+8IMfsG7dOqqqDi9a97WvfY2+vj7Wr18PQFlZGYsWLeLJJ58EIBaLUVpayt133839999/zMcOBoMEg8H49263m9LSUlwuFw5HijV9C5Fq9r4ONW9osx2fe4U25DoVqSq0fKKFugXfhPIVia5IiDHH7XaTkZFx0vfvs3qVcLlcAGRnZw+5/oUXXiA3N5fzzz+fVatW4fP54rdt2bKFOXPmxAMOwNKlS3G73ezZsye+zeLFi4fsc+nSpWzZsgWAUChERUXFkG10Oh2LFy+Ob3MsjzzyCBkZGfFLaWmKfhoUItX4+8C5G3obIH1C6gYcGGjNmQYoUPMm9HcmuiIhktYZv1LEYjHuuecePvOZz3D++efHr//617/O888/zwcffMCqVav43//9X77xjW/Eb3c6nUMCDhD/3ul0nnAbt9uN3++nq6uLaDR6zG0G93Esq1atwuVyxS/Nzc1n9uSFEKPr0MfgbtVmO845N9HVjDxLpnZKztsJnz6X6GqESFqGM73jihUrqKqq4qOPPhpy/be//e3413PmzKGoqIgrrriCAwcOcO65iX1xMpvNmM3mhNYghDhNIR+0fKq14qTlgWGc/A3nTIO+Q1D3Lsz7GmSkyOzOQoyiM2rJueuuu3jjjTf44IMPKCk58VwVZWVlANTV1QFQWFhIe3v7kG0Gvy8sLDzhNg6HA6vVSm5uLnq9/pjbDO5DCJEiWiq0yf9C/ZA9PdHVjB5zurZ4p68HKtZqfXWEEKfltEKOqqrcddddvPrqq7z//vtMmTLlpPeprKwEoKioCIDy8nJ27949ZBTUu+++i8PhYNasWfFtNmzYMGQ/7777LuXl5QCYTCYWLlw4ZJtYLMaGDRvi2wghUkAkBIc+gd5GsGaDOUWWcThVOeeCzgAHN0LdhpNuLoQY6rRCzooVK3j++ed58cUXSU9Px+l04nQ68fv9ABw4cIAf/ehHVFRU0NDQwOuvv843v/lNLrvsMubOnQvAkiVLmDVrFjfddBM7d+7k7bff5oEHHmDFihXxU0l33HEHBw8e5L777qOmpoannnqKl19+mXvvvTdey8qVK3n66ad59tlnqa6u5s4778Tr9bJ8+fLhOjZCiERz7tJGGfl7IXdGoqsZfSY7FM7Rnv/mX8GhikRXJERSOa0h5MpxJt965plnuOWWW2hubuYb3/gGVVVVeL1eSktL+fKXv8wDDzwwZIhXY2Mjd955Jxs3bsRut3PzzTfzn//5nxgMh7sIbdy4kXvvvZe9e/dSUlLCv//7v3PLLbcMedwnn3ySn//85zidTubPn88TTzwRPz12Kk51CJoQIgFiUdj6azi4CSJ+mPSZRFeUGKoKnTXQsReyz4UrH4GccxJdlRAJdarv32c1T06yk5AjxBjmrIIdz0PzVihaCGm5ia4ocVQVWndAXwMUnA9X/hTS8xNdlRAJMyrz5AghxIhQVWjaAn1NYLCAPSfRFSWWokDRPG0Bz4698P6PwNN+8vsJMc5JyBFCjD2dNdBTr42qypmamutUnS6dHkoWgTlDmxH5nQeg7n2IRk5+XyHGqTOeJ0cIIUaEqkLDR9qIKr0Z0osTXdHYoTfC5M9oI87aKrW1vJq3acs/yDw6QhxFQo4QYmzpqNZacdwt2sgiacUZSm+CieXa8WmtgNo3oWsfTPsC5M/SWr7MaYmuUogxQUKOEGLsiMWg8W9aK47RAulFia5obFIUyCgBez607dA6absOQXoh2HKh4DxtNXNrNlgzwZoFprShgVFVIRKEoAdCHgj2ay1FuTNAJz0ZRGqQkCOEGDsG++JIK86pMZigtAxyeqDnoLb0Rdc+bSRWWh6YHVrHbaNFm3NHZwQ1BqhayIlFIRo64hKG/PNg/te1wCREkpOQI4QYG+KtOA3SinO6bNnaBbQWmb4G8HZpK5jHwlqY0RmODo2qCgqgKqA3QNgHXbXaCK45X4Fz/0Fr3REiSUnIEUKMDZ3VWmuEu1Vacc6GOU2bS2eQqkI0CAEPqFHtOmXgdJTOAEab1iKk6LTTVy0V0LZT69TctFVr1ZERbiJJScgRQiReLAYN0hdnRCiKdsoqzXLybQ1mmHSJFjRbKmD/u9C1XxvRNfMqyCwd+XqFGEYScoQQidf6qdaK42mFgrnSapBojmKw50HHHq2Pj/sQNH8M534eZvwj2Mfx7NMiqUjIEUIkVsCtrbLdtQ+MdunwOlbojVA0Xxtt1bYT2neDqwkaN8Ocr8KUy7R+PEKMYfIbKoRIrLr3tBFV/l4oLZdWnLHGaIWJF0PQDS07oOVTcLXIJIQiKUjIEUIkTled1krQtQ8cJWBJT3RF4njMDq31xt2inV4cnIRwznVwzuVgsiW6QiGOIiFHCJEYkRDsf1sLOooCedMTXZE4mb+fhLC9Cvrb4cD7MPULMLFMm3hQiDFCQo4QIjEaPxrobNwChfO14cwiOQxOQuhxamtoNXwEHTVQ8wZM+RxMuVT6VokxQV5VhBCjz+OExi3QWQvWHEjLT3RF4kykF0LaUu3n2bEHmrdD5z6oexcmX6pNJih9dkQCScgRQoyu/g7Y+RJ012kz7E5YKJ2Nk5migKNIu/h6tLDTugO6D0D9Jm0x0amLIXOi/JzFqJOQI4QYPe422PkHcO7Slm8omK1NVCdSgy1ba8EJesC5W+tU3n1Qm+ix9CJtpfTscyTsiFEjIUcIMTpcLQMBZ7e2tlLB+eCQUxkpyZyuzZwc6tdWSG/frfW/atqitdxNW6Ktki6rnYsRJiFHCDHy+pq0U1TO3eBqhsJ50jF1PDClaXPshPzaaayOam1OpObtWsid+g9QOFdbykOIESAhRwgxsvqaBwLOTq01p3AepBckuioxmkxWKLlQW3i1sxq692unK1sqIHuK1kE5fyZYs7VWIDmdJYaJhBwhxMhxt8Kul7Q+OK4WKFoAabLu0bhlMGtLReSfDz0HtM7nrmZo36PNv2NK01ZRTy/U1s4ypx9xcWjXG8yJfhYiiUjIEUKMDI8TKn9/+BSVBBwxSG+AvBlavxxPG3TVar8nagx0em3OJKMVTHYw2rRgozdr3+fNhNxpkDVZW0hUp0/0sxFjmIQcIcTw6+/QOhm374a+Ru0UVVpeoqsSY42iaEHFUQyqCtEQBF3aoq1Bt9aXJ+CCaARiIYjF4NDH2irothztftOWQPECbYJCIf6OhBwhxPDq7xhowdkFvY1QMEc6GYuTUxStxcaQry0bcSz+Pm3trP52rXXQuRtad2r9eWb8o9bvR05niSNIyBFCDJ/eRtj9itbHorcB8mdpn7aFGA7WTO1SMBtiUW1YelcteFqhfa+2/tmUz0PxPEgvkg7MQkKOEGKYdNbCnle1eVHcLdobkcyDI0aKTq/1zcmZCr312vD0fqf2+5dRovXdmVSu9fuxZkngGack5Aghzl7Lp1Dzpnb6wNspnYzF6FEUbRblrCnaaL7u/dqyEh3V0LQZ0gq11sS8mdrSEhkl2sgtmYhwXJCQI4Q4M/4+bX2i7v3QWQNtu7QOo6UXgSUj0dWJ8UZRtMVAMyZoHZi7D2iTUPY1gc6oraNly9VOd9mytRagjFKttTGzVBvNJVKOhBwhxKkL9mufkjtrwHUI/L3g69aGi+sMUHoxmO2JrlKMd3oT5J+nXaJh7ffT4wT3IW1uHp1eW0/LmqWFHksG5Ew7PDQ9vUgLPXKKK+lJyBFCnFx/JxzarrXWeNq0gOPrBgUw2rU3hsyJ2puLEGOJ3qi11GSWat/HouDr0kYBDgb0WBgOfaINS7flaKHH4oC0goHvM8GWpc3IbMuRWZmTiIQcIcTRIiGtE6e7TevU2VmrhZveRoj4wZIFRfMG+jbIy4hIIjq9Fl7SjlhaJODRWnn627XQo8a03+sjJyU0pWlfG60DAahwIAA5tFB05O0Gy+FJDCUMJVTSvzqtXr2an//85zidTubNm8evfvUrLrrookSXJUTyUFUI9GmtM65D2sgod5s2GVuwX/vf4wRUsBdC7iLpvyBSiyUdLAOnt1QVwv6B338PhDzaauq+bq2vTyx6RACyDMzKbB+Yldk4cJtRm9XZYNX6/1gztRCkN4KiAxQt/OiM2j4MA+HIaNHurwzM+qzTa9srysB9dAMzQg8+jnSePpmkDjkvvfQSK1euZM2aNZSVlfH444+zdOlSamtryc8/zmRSQiQrVdU+Yaox7YVWjUIsMjAbbFjrexALa7PCxiKHb49Fh24fCWov1pGgdulv10ZEBVzai7qvW/taVUGvB4NNG7mSOVF7kRYilSkKmGza5ViTWEaC4HdByAVBL4S8Wif8+N9YFFAH9qU/HEqMloGJCgcCDgPhRW88HIp0xoFgMxBoODLgcPi+ik7bt96o7dNo1Za9MJgO70NVtTrUgVoGA5jedETYOvJ56w6Ht8GaFGVowBrSKnXk1+rQ6+PhbOCSOUl7fgmgqKqqnnyzsamsrIxFixbx5JNPAhCLxSgtLeXuu+/m/vvvP2r7YDBIMBiMf+9yuZg4cSLNzc04HI7hK2zPaxAODN/+ht0RP/K///GPRNPq4B/b0Ac64rGO98dy5P3PwPGey7H2d+S2f//icPQOjrjtRPUOBBJVJf4cB1+ghoSVI0LL31+vHvEY8bpiRxzTwe1jA/c/4vbBfamxofdToxCNatPkR8MQCWgXFK2Z3ezQps23ZktzuxCnS1UHPkyEIOLTlqYI+yAa0D5cxP+OGfjbHfxAEgM1MvT1UgXttePI15mB15N4EFIOh5F4qNIfcZeBx1KUw+uCDbYWxQ38jev0oDsiRA0GHBj6dfxuygleT49oebr8/2mtWcPI7XZTWlpKX18fGRnHH82ZtC05oVCIiooKVq1aFb9Op9OxePFitmzZcsz7PPLIIzz88MNHXV9aWjpidQohhBDj23+P2J49Hk9qhpyuri6i0SgFBQVDri8oKKCmpuaY91m1ahUrV66Mfx+Lxejp6SEnJwdlHH9aHUzEw96iJeTYjjA5viNLju/IkWN7dlRVxePxUFx84mVjkjbknAmz2YzZPHTxtszMzMQUMwY5HA75YxshcmxHlhzfkSXHd+TIsT1zJ2rBGZS0XbNzc3PR6/W0t7cPub69vZ3CQlnxWAghhBjvkjbkmEwmFi5cyIYNG+LXxWIxNmzYQHl5eQIrE0IIIcRYkNSnq1auXMnNN9/MhRdeyEUXXcTjjz+O1+tl+fLliS4tqZjNZn74wx8edSpPnD05tiNLju/IkuM7cuTYjo6kHkIO8OSTT8YnA5w/fz5PPPEEZWVliS5LCCGEEAmW9CFHCCGEEOJYkrZPjhBCCCHEiUjIEUIIIURKkpAjhBBCiJQkIUcIIYQQKUlCjhBCCCFSkoQcIYQQQqQkCTlCCCGESEkScoQQQgiRkiTkCCGEECIlScgRQgghREqSkCOEEEKIlCQhRwghhBApSUKOEEIIIVKSIdEFJFIsFqO1tZX09HQURUl0OUIIIYQ4Baqq4vF4KC4uRqc7fnvNuA45ra2tlJaWJroMIYQQQpyB5uZmSkpKjnv7uA456enpgHaQHA5HgqsRQgghxKlwu92UlpbG38ePZ1yHnMFTVA6HQ0KOEEIIkWRO1tVEOh4LIYQQIiVJyBFDqKpKZUclr+5/lYN9B1FV9ZTuF41F6Qn0nPL2QgghxEgb16erxFCqqrKlbQtbW7dS01PDxuaNlBeV84XJXyDHmnPc+x3yHOLDQx9yyHOImdkzWXbOMvQ6/egVLoQQQhyDhBwBaAHnry1/5WPnx1R1VeGP+HF6nXT4OtjVtYsrJl3B3Ny5ZFoyMeqMAHjDXja3bmZP1x6aPE00u5vZ3bUbd8jN9TOvj28nhBDjSTQaJRwOJ7qMpGY0GtHrz/7DsoQcQUyN8UHzB+xo30FVdxUKCuVF5YRjYfZ076Gqq4rW/lZKHaWkGdPIs+aRb8unzdtGs6eZelc9qqpSml7KQddB3qp/C3/EzzdnfxOz3pzopzdu1bvqcQVdzM2bi06RM9NCjDRVVXE6nfT19SW6lJSQmZlJYWHhWc1jJyFnnFNVlfca36Oyo5I93Xsw6AzMzZuLUWfEqDeyqHAR3f5u9vbspapLC0AGnQGL3oJRb8Qb9lJoK+SczHMw6Azk2fL42PkxG5o24I/4uW3ObdiMtuM+dnegm2xLtrwJD7OW/hbeOPgG9a56ygrLuG76dXIKUYgRNhhw8vPzsdlsMsnsGVJVFZ/PR0dHBwBFRUVnvC8JOeNcTU8Nuzp3UdVVhcVgYU7unKPeDHOsOVw64VJthsmwB1fQhTvoJkaMGVkzSDOlxbfNMGdQXlTOdud2/nror/gjfr4999tkWbKG7DMcC/Ne43vs6dpDcVoxX5v5NQk6w8QX9vFuw7sc7DtIXV8dHb4OYsT46vSvStARYoREo9F4wMnJOX4fRnFqrFYrAB0dHeTn55/xqSsJOeNYIBJgc+tm6l316BTdMQPOkRRFwWFy4DA54ATzL9lNdi3otG9ne9t2vGEvd8y9g+L04vjjvlX/FtXd1ezp3sOOjh2km9L50rlfGu6nOO6oqsqGpg3Uu+pp87YxI2sGdX11rDu4DkCCjhAjZLAPjs127JZrcfoGj2U4HD7jkCMfncex7c7tHPIcojvQzfTs6cP65mcxWigvKsdusrOrcxe/qPgFtT21eEIeXq17lV2du9jTvQejzogn5OGP+/7Ix86Ph+3xx6sdHTuo6amhrq+OQrt2GnFh4UI8IQ9vHHiDl2pfIhqLJrpMIVKWnKIaPsNxLCXkjFMdvg52duzkoOsgOZYcMs2Zw/4Yg3168m351PXV8asdv+LF6hep6qqipqeGPFseiwoXMStnFt2Bbn63+3c0uBqGvY7xoq2/jc2tm6ntqcWsNzM1cyoAOZYcFhUuoj/cz7qD6/j1zl9z0HXqcyCdLlVVaXI34Q17R2T/QghxquR0VYprcjfR5e9iZvbMeAdgVVX58NCHNHuaicQiTMuaNmKPr1f0zMubx77efdS76ukP9xOIBJiUPomJjokoikJpeinesJdGdyOrK1ez6qJVZFuzR6ymVBSMBnm38V0Oug7ii/hYmL9wSB+nbEs2FxVeREV7BZuaN7G/dz8XFV7E4kmLKbAXDGst25zb+FvL3whHw9y78F7MBhlhJ8Y3T8hDIBIYlceyGCykm068ntN4IiEnhdX21PJOwzs0ehrJNGfyuZLPsbBgIfXueupd9RzqP8QkxyRMetOI1qEoCtOzpmM32mnyNDEja8aQN1ZFUZiRPQNfxMf+3v08tfMpVi5cedxRWeOBqqqn1VRb3V1No7uR1v5WZmbNxGq0HrVNliWLz5d8nn19WuBs97Wzq2sXnyn+DGVFZcMSdqq7q9naupU9XXvwhDz8qe5P3DDzhrPerxDJyhPy8N+7/pveQO+oPF6WJYvvzP3OaQWdW265hWeffZbvfOc7rFmzZshtK1as4KmnnuLmm29m7dq1w1ztyJOQk6Kqu6t5r/E99nbvxelzoqoq9a56Pmj+gHxbPvWuesx6MyVpx1+ifjgpikJJegkl6cd+PJ2iY27uXLY5t1HZUcmanWv47vzvYjFYRqW+sWRv9142t27mvOzz+MyEz5x0e1VVqe6pxul1YjfaybfnH3dbg97ArJxZTMmYQnV3NdU91bR4WtjcupkL8i+gvLic0vRSFEUhpsYIRAIEogFiamzIfuxGO1bD0CB1yHOI95vep6anBl/ERzAaZH39esoKyzgn85wzOxhCJLlAJEBvoBeL3jLir2eDjxWIBE67Nae0tJQ//OEPPPbYY/GRTYFAgBdffJGJEyeeVV3hcBijMTGTw0rISUF7u/fGA05/uJ9Lii6hP9xPbW8tn7Z/SoY5g2A0yPy8+WOqk5xRb2Rh/kK2ObexpXULZr2Zb8/9Nkb9+Jk5eUfHDj5s/pDqnmo2t2zGrDdzYeGFJ7xPu6+d1v5WugPdnJd93ik9jtVgZUHBAtwhN7U9tezt2Uuju5GPnR8zJWMKJr2J/nA/kViESCxCVI3CEV149Do9F+RfwPz8+RTaC+kN9PJW/Vvs792PK+RiYf5C2rxt1PXV8cyeZ/hh+Q8x6OTlRoxfFoMFu9E+4o8TiJ7ZabEFCxZw4MAB/vSnP3HjjTcC8Kc//YmJEycyZcqU+Hbr16/nxz/+MVVVVej1esrLy/nlL3/JueeeC0BDQwNTpkzhD3/4A0899RTbtm3jF7/4BatWreJ3v/sd1113XXxfr732GjfeeCNOp5P09JE5xSavOilmT/ce3mt8j+ruavpD/czLm4fdZMduspNvy6fD30Gjq5GStBIcZkeiyz2K1WhlUcEitjm38eGhDzHrzdxy/i3xN0h/xE9voJdca+6In2YbTaqqsrVtK1tat7C3ey+uoItANMD/7P4f8qx5TMqYdNz71vTU0OXvQqfoTrjG2LE4TA4WFS7CH/azr3cf+/v20+huRKfoiKkx1IFk8/edlGPEqO6uZmPzRmbnzgbgQN8BnD4n5+eej91kZ7JhMu2+dmq6a1h3cB3/NPWfTu+gCCFG1be+9S2eeeaZeMj53e9+x/Lly9m4cWN8G6/Xy8qVK5k7dy79/f08+OCDfPnLX6ayshKd7nA/wPvvv59f/OIXXHDBBVgsFnbu3MkzzzwzJOQMfj9SAQck5KSUyo5KNjVvYm/PXrwhbzzgDFIUhQJbAQW24e1oOtzsJns86Gxo2oCiKExIm0CHr4PuQDfesJd8az7L5yxPifWxYmqMDw99yCfOT9jTvYeYGqO8qJyD7oM0uZt4svJJ/t9F/48sa9ZR9w1Hw+zr2Udbfxs5lpwznlDRarQyL38e50XPo8PXgYKCxWDBrDdj1BvRK4enF1BQcAVd1LnqqO6ppt5VT641l55AD+dmnku2Res0btAZOC/7PD5u/5i/HPgLiwoXUZxWfGYHSQgx4r7xjW+watUqGhsbAfjb3/7GH/7whyEh59prrx1yn9/97nfk5eWxd+9ezj///Pj199xzD9dcc038+9tuu41LLrmEtrY2ioqK6Ojo4M033+S9994b0eckIScFqKrKltYtbG3bSnV3Nf6on/n585O64266OZ0LCy9ke9t23m96n2xLdrx1IxwLY9KZsBqtfOO8b4ypU25n4sNDH7K9bTt7uveg1+lZkLcAo97IjOwZ+CN+9vfuZ/XO1Xzvwu8ddU7/oOsgnf5O/BE/s3Nmn3UtJr3puP2mjpRtzeYi60UEogEO9h2k299NcVoxE9ImHLXdRMdEGl2NPFP1DN+d/92jZr8WQowNeXl5LFu2jLVr16KqKsuWLSM3N3fINvv37+fBBx9k27ZtdHV1EYtp/fWampqGhJwLLxx6mv2iiy5i9uzZPPvss9x///08//zzTJo0icsuu2xEn5PMk5PkorEo7ze9z+bWzezu2k0oFmJB3oKkDjiDMs2ZLCpchEFnwBvxkmvNZU7uHBbkLyAYDfJ2w9tsbN6Y6DLPSkt/S3zdMJPOxPy8+fE+SHpFz9zcudiMNio7Knmm6hkisciQ+1f3VNPp69TO95tG/nz/37PoLczKmcWlJZdybua5x9xmasZUrEYrOzt38l+f/Bev7n+Vlv6WEZunRwhx5r71rW+xdu1ann32Wb71rW8ddfuXvvQlenp6ePrpp9m2bRvbtm0DIBQKDdnObj/69ei2226Lj9B65plnWL58+Yh/SJWWnCQWjoZ5u/Ftqrqq2Nu9F4NiYEH+gpTqqJtlyaKsqOyo6+fmzqWys5IXal6gKK2ImdkzE1Dd2YnGovH5iqJqlDl5c47qnGvUG7kg7wK2Orfy4aEPsRltfOO8b6DX6XEFXTS6Gmn3tzPFMeU4j5J4Rr2RC/MvZHfXbqq6qmhwNbCldQuzcmZxXs555NnyyLfmYzfak75VTohkd+WVVxIKhVAUhaVLlw65rbu7m9raWp5++mkuvfRSAD766KNT3vc3vvEN7rvvPp544gn27t3LzTffPKy1H4uEnCQVjoVZV7+OPV17qO6pxmawnXTtqVRSmFbI1MhU6nrrWLNzDf920b+RZ89LdFmnZVfXLhpdjbT0tzAja8ZxRx/ZTXYuLLiQ7c7tvN3wNnqdnhtm3EB1TzXdgW5QodBeOMrVnx67yc7FxRfjCXnY37uf2t5aGtwNfOz8mHRTOjajjVxrLosKF7GocFGiyxVi3NLr9VRXV8e/PlJWVhY5OTn85je/oaioiKamJu6///5T3ndWVhbXXHMN3//+91myZAklJSM/hYmEnCQUiUVYX7+ePV172Nu9l0xLJudlnzfuVvE+J+McPCEP9a56frnjl1xYcCEZ5gwyzBlkW7KZ5Jg0ZlsG+kP9bG/bzkHXQdKMaeTbjj+3DWiru19YcCEfOz/mrYNvYVSMBKIBnF4nGeaMpBmenW5KZ0GBdrqxwdVAT6CHDl8H4VgYo87IJ85PcFzkYEb2jESXKsSwGo0Zj4frMRyOY4+81el0/OEPf+Cf//mfOf/885kxYwZPPPEEn//8509537feeisvvvjiMU+FjYTkeGUUcdFYlHca3qGqq4rq7moyzZnMyp41Zt/MR5JO0XF+7vl87PyYPd17qHfVYzfasRvtWPQWLplwyZhd2fyj1o841H8Id8jNwvyFp/Tzy7JksbBwIZ84P+EvB//CORnn4Aq6mJc3bxQqHl5mvXlIkAlFQ9T11dHkbuL56ud5qPyhcdMqKVKbxWAhy5KlTdJ3hnPYnI4sS9ZpTzp4spmMX3vttfjXixcvZu/evUNuP7J/3eTJk0/Y366lpYWcnBz+6Z9GZ0oJCTlJJKbGeK/pPXZ1aSt4p5vSOS/nvHEZcAYZdUYuLrqYDl8HrqALb9hLu68dT8hDq7eVqZlTOS/n1CbIGy3N7mZqumuod9VTaCs8rQ7DOZYcFuYvpKKjggN9BzDqjWSYM0aw2tFh0puYnjWd3kAvNd01bGjawJLJSxJdlhBnLd2Uznfmfmfcr13l8/loa2vjP//zP/nOd76DyTQ685xJyEkiHx76kJ0dO9nbtZc0Yxqzc2ePu1NUx6JTdBTaC4f0S6nvq6emt4andz/Njy75UUJGHh2LK+jiw5YPaXRr81CcyXIHubZcLiy4kOqeaqY4pqRMyDXoDEzPmk5FewWv1r3KxUUXj8kJK4U4Xemm9DEZPEbTz372M37yk59w2WWXsWrVqlF7XHmHTBK+sI+dHTvZ070Hi8EiAeckJmVMIs+aR72rnmf2PHNU82kkFsEX9o1aPeFomK1tW3mh+gX2du2lzdvG1IypZ9yXJseaw2cnfDblJtfLteZSaC+krb+Nl2pfSnQ5Qohh8tBDDxEOh9mwYQNpaWmj9rjSkpMkWvtbcYVcBCIBFuQvGDIDrTiaTtExO3c2m1s287eWvzEndw6fK/0cvrCPPd172N25m55ADwsLFnLJhEsw682n/RiqquINe7EarMftP6KqKnV9dWxu3Uyzp5l6Vz3esJeStBLybMk1Gmw0KIrCtKxpdPm7+PDQh1w+8XKmZk5NdFlCiCQlISdJtHpb6Q/1Y9QbU2oenJFkNViZnTubHR07+H3N7/FH/dT31dPua6elv4XeQC9VXVV87PyYq8656qT9m9whN03uJrr93XQHuun0deIOubEYLJQVljE7d3Z8Ab5QNERtTy1V3VW0eFpodDfS6e8kw5zBooJFWI3W4z7OeGc32pmSMYX9vft5fu/zrCpbdUYhVIhEkEkuh89wHEsJOUmirb+N3mDvuD+ve7oKbAVMdEyk2d3MugPr8IQ8+KN+Mk2ZTMuaRl1fHVvbttLobuTCwgu5qOgiCmwFZFuy44tUNrgb2Nu9l/q+err8XfSH+/GEPHhCHsKxMAC7O3czIX0CFxZcSLopnepubSZip89Jp68Ts97M+bnnx9d1Eic20TGR1v5W9nTt4ecf/5zPFH+Gefnz5PiJMcto1D58+nw+rFb5EDMcfD6tS8HgsT0TEnKSQCCizYfiDrqTcmbfRFIUhelZ0/GH/XT4OiiwFzAnfU68JWVC2gTqXfUc6DtAV30Xuzp3kW5KJ9OcyeSMybiDbtp97XT6Omn1thJTY5j0JtKMaUx2TCbDnEFfsI8GdwMd7R3U9dZRYCugO9CNL+LDbrQzK2cWOdYzXzxzPDLqjMzNm8uOjh1UOLWRZMVNxVyQfwGfK/mcnOoTY45eryczM5OOjg4AbDZbygwKGG2qquLz+ejo6CAzM/OoSQlPh6KO47Y1t9tNRkYGLpfruJMfjQUHXQd5sfpFdnXu4pLiSzDpR2foXapRVfW4LzrBSJD9vfvpDnQTjAbR6/RY9Noq3N6wF5PeRLG9mOK04uOeLuwL9HHAdQB/xK9NRpg+acyM6kpWqqrS4evggOsAnpAHm8HGORnncMe8Oyh1lCa6PCGGUFUVp9NJX19foktJCZmZmRQWFh7zdftU378l5CRByPlby994/cDrtPS3cEnxJYkuJ+VFY1F6gj10+7qJqlEmpE0gw5whn8oSzBv2UtVZRV+oj2mZ07j7gruZlDEp0WUJcZRoNEo4HE50GUnNaDSesAXnVN+/5XRVEmj1ttIX7CPNOHrD7sYzvU5PnjWPPKucEhlL7EY7FxZdyK7OXezv28/jnz7OigtWyOgrMebo9fqzOsUihs9pdRJ46KGHUBRlyGXmzMN9RAKBACtWrCAnJ4e0tDSuvfZa2tvbh+yjqamJZcuWYbPZyM/P5/vf/z6RSGTINhs3bmTBggWYzWamTp16zCmnV69ezeTJk7FYLJSVlbF9+/bTeSpJIxQN4ex34gq65E1XjHt6Rc+8vHkU2gs54DrAE58+QW13baLLEkKMUafdE3L27Nm0tbXFL0cus37vvffyl7/8hVdeeYVNmzbR2trKNddcE789Go2ybNkyQqEQmzdv5tlnn2Xt2rU8+OCD8W3q6+tZtmwZl19+OZWVldxzzz3cdtttvP322/FtXnrpJVauXMkPf/hDPv30U+bNm8fSpUvjHb5SidPrxB1yE1NjMrJECLQ5kObkzqEkrYQGVwO/qvwVja7GRJclhBiDTqtPzkMPPcRrr71GZWXlUbe5XC7y8vJ48cUXue666wCoqanhvPPOY8uWLVx88cW89dZbXHXVVbS2tlJQUADAmjVr+MEPfkBnZycmk4kf/OAHrFu3jqqqqvi+v/a1r9HX18f69esBKCsrY9GiRTz55JMAxGIxSktLufvuu09r2fdk6JOztW0rf97/Z5o8TVxSfIn0CxFigKqq7O3eS7OnmZnZM/m3sn8jw5L863gJIU7uVN+/T7slZ//+/RQXF3POOedw44030tTUBEBFRQXhcJjFixfHt505cyYTJ05ky5YtAGzZsoU5c+bEAw7A0qVLcbvd7NmzJ77NkfsY3GZwH6FQiIqKiiHb6HQ6Fi9eHN/meILBIG63e8hlrGvtb6Uv1IfdaJeAI8QRFEVhZs5Mcqw57O/dz693/ppQNJTosoQQY8hphZyysjLWrl3L+vXr+fWvf019fT2XXnopHo8Hp9OJyWQiMzNzyH0KCgpwOp0AOJ3OIQFn8PbB2060jdvtxu/309XVRTQaPeY2g/s4nkceeYSMjIz4pbR0bA9BDcfCtPW30Rfok/44QhzDYB8ds8FMRXsFL1S/IDPOCiHiTmt01Re/+MX413PnzqWsrIxJkybx8ssvJ8UMj6tWrWLlypXx791u95gOOu3edtwhN1E1SpYlK9HlCDEmmfQmLsi7gK3OrbzX+B7FacUsnbw00WUJIcaAs5qCNTMzk+nTp1NXV0dhYSGhUOioSZDa29spLCwEoLCw8KjRVoPfn2wbh8OB1WolNzcXvV5/zG0G93E8ZrMZh8Mx5DKWtXnb8IQ8GHQGrIaxHyKFSJR0czrz8ubhCXl4qeYlqrqqTn4nIUTKO6uQ09/fz4EDBygqKmLhwoUYjUY2bNgQv722tpampibKy8sBKC8vZ/fu3UNGQb377rs4HA5mzZoV3+bIfQxuM7gPk8nEwoULh2wTi8XYsGFDfJtU0drfiivowm6Q/jhCnEy+LZ/pWdPp9Hfy9O6n6Q30JrokIUSCnVbI+d73vsemTZtoaGhg8+bNfPnLX0av13PDDTeQkZHBrbfeysqVK/nggw+oqKhg+fLllJeXc/HFFwOwZMkSZs2axU033cTOnTt5++23eeCBB1ixYgVms7bK8B133MHBgwe57777qKmp4amnnuLll1/m3nvvjdexcuVKnn76aZ599lmqq6u588478Xq9LF++fBgPTWJFYhFa+1vpDfSSY8tJdDlCJIUpGVMotBfS6GrkN7t+QyQWOfmdhBAp67T65Bw6dIgbbriB7u5u8vLy+OxnP8vWrVvJy9M6xT722GPodDquvfZagsEgS5cu5amnnorfX6/X88Ybb3DnnXdSXl6O3W7n5ptv5j/+4z/i20yZMoV169Zx77338stf/pKSkhJ++9vfsnTp4XPs119/PZ2dnTz44IM4nU7mz5/P+vXrj+qMnMw6fZ24gi4iaoQcs4QcIU6FoijMzpnN1uBWKtoreK3uNa6bfl2iyxJCJIisXTVG58nZ3LqZdQfWUe+u5zPFn5HTVUKchr5AH9vatpFpyWTlwpXMy5+X6JKEEMNoxObJESNPVVX29+6nw99BhkkWhhTidGVaMpmRM4OeQA//U/U/dPu7E12SECIBJOSMQS39Ldrw8aCbkvSSRJcjRFKalD6JInsRTe4mntzxJHW9dTKHjhDjjKxCPgbt691Hd6Abg85Apjkz0eUIkZQURWFWziy8YS87O3fSFejisgmXceWUK0k3pSe6PCHEKJCQM8aEY2Hqeuto97aTY8mRU1VCnAWT3kR5cTn7e/dT76rnNf9r7O3eyxenfJGS9BJyrDmY9eZElymEGCEScsaYRlcjXYEu/BE/s3JmJbocIZKeTtExI3sGE9ImsLNzJzs6dtDa30qeLQ+bwUaeLY/itGLKisrIteYmulwhxDCSkDPG1PbW0u3vxmwwYzfaE12OECkjzZTGJcWX0OxpptHTSGd3J6qqYtQbMevN1PbUsvLClSffkRAiaUjIGUN8YR/1rnqcXicT0ibIqSohhpmiKEx0TGSiYyKqquKL+OgN9FLdU81253b29+5nWta0RJcphBgmMrpqDDnQd4CeQA+RWISitKJElyNESlMUBbvRTkl6CedknIMv7OONA28kuiwhxDCSkDOG7OvdR6e/E7vJLp0hhRhFxWnFmPQmPmn/hBZPS6LLEUIMEwk5Y0RfoI9mTzOdvk4m2CckuhwhxhWrwUpxWjGesIe/HPxLossRQgwTCTljxP6+/fQEelBQyLPlJbocIcadkrQSDIqBra1b6fH3JLocIcQwkJAzBrhDbio7Kmn3tpNuSsegk/7gQoy2NFMahfZCeoO9rKtfl+hyhBDDQEJOgsXUGO81vkeDuwFXyMW5GecmuiQhxq3S9FIUFD489CHekDfR5QghzpKEnASraK+Iz8Zaml5KulmmmxciURwmB3nWPLp8XaxvWJ/ocoQQZ0lCTgK19reytW0r+3r3YTPYmOyYnOiShBjXBufRiRHj/ab38Yf9iS5JCHEWJOQkSCAS4L3G96h31eOP+JmdO1sm/xNiDMi2ZJNryaXV28qfD/w50eUIIc6ChJwEUFWVTYc20eBuoLW/lemZ02VeHCHGCEVRmJo1FVVVeafhHdq97YkuSQhxhiTkJEC7r5293XvZ37uffGs++fb8RJckhDhChjmDkvQSuvxd/L7m96iqmuiShBBnQEJOAuzt3kuHt4OoGpV1coQYo87JOAeT3sT2tu1UdVUluhwhxBmQkDPKgtEg+3r30eptJc+ah16nT3RJQohjsBgsnJtxLp6whz/U/IFoLJrokoQQp0lCzijb37ufLl8XgUiA0vTSRJcjhDiBkvQSHCYH+3r38X7T+4kuRwhxmiTkjLK93Xtp97VjM9qwGW2JLkcIcQJ6nZ7pWdMJRoO8VvcanpAn0SUJIU6DhJxR1OHr4JDnEB2+DmnFESJJ5FpzKbAX0NLfwm92/oZwNJzokoQQp0hCziiq7q6my9+FXqeXRTiFSBKKonBe9nmY9Ca2Obfx7J5npX+OEElCQs4oCUfD1PTU0NrfSo4lB70iHY6FSBYWg4WF+QsJx8K83/w+r9W9JsPKhUgCEnJGyf6+/XT5u/BH/ExMn5jocoQQpyndnM7C/IV4w17+fODPfND8QaJLEkKchIScUVLdXU2HrwOr0YrdZE90OUKIM5BtzWZu7lx6A728UP0COzp2JLokIcQJSMgZBR2+Dpo8TbT72ilNkw7HQiSzorQiZmbPpMPXwW92/YaDfQcTXZIQ4jgk5Iwgd8jNpuZN/F/t/9Ha34qCQr5NlnAQItlNdkxmsmMyLZ4WVleuptPXmeiShBDHYEh0AanIFXTxafun7O3ei9PnpNndTCAaYIpjisxwLEQKUBSFGdkz8Ef8HOg7wK92/Ir7Ft1Hmikt0aUJIY4gLTnDzBf28fua37OxeSPbndup663DYXJQVlhGqUNOVQmRKnSKjrl5c0k3pVPVVcV/7/pvmUNHiDFGQs4wsxltTHZMprW/FbvRzkVFFzEzZyZmgznRpQkhhplBZ2BB/gIMOgPb2rQ5dHxhX6LLEkIMkJAzAj5X8jlKHaUUpxVj1ku4ESKVmQ3mIXPo/OKTX1DZUSkTBgoxBkifnBEg/W6EGF/SzelcXHQxlR2VVLRXcKj/EAvyF7DsnGVMSJuAoiiJLlGIcUlCjhBCDIN0UzqfnfBZmj3N1PbW8l7je+zr3cfkDG0kVqG9kAJbAUVpRRh1xkSXK8S4ICFHCCGGiaIoTHRMpMheRE1PDXW9dTS6G9lm2EamOZN0UzrF9mJuOO8Gcq25iS5XiJQnIUcIIYaZUW9kTt4cZubMpNvfTU+gh95AL639rezv3U+rt5VbZt/CjOwZiS5ViJQmIUcIIUaIUWek0F5Iob0QgHAszM6Onezt3ssTnz7BV2Z8hctKLkOnyBgQIUaC/GUJIcQoMeqMLCxYyBTHFJo8TTy35zl+X/17QtFQoksTIiVJyBFCiFGkKArTs6dzQf4FdAe6WVe/jt/u/i2BSCDRpQmRciTkCCFEAhTaC7mk+BKC0SCbmjfx652/xhv2JrosIVKKhBwhhEiQdFM65UXlxIixuWUzT+54EnfIneiyhEgZEnKEECKBbEYb5YXl6HV6tju388uKX3LIcwhVVRNdmhBJT0ZXCSFEglmMFi4uupiPnR+zo2MHfRV9XJB/AZcUX8KUjCkyY7IQZ0hCjhBCjAEmvYmLii6iuruaut46DnkO8Wn7p8zInsGiwkXkWnPJteZiM9oSXaoQSUNCjhBCjBFGnZG5eXOZnjWdur46DrgO0OxpZnfXbhwmBzaDjWxrNhPSJjAhbQLFacXk2/Ix6Ib/pdwX9uH0OrEYLDhMDuxGu7QoiaQjIUcIIcYYi8HC+bnnMzNrJvXuejp8HTi9TiJqBKNixKQ3kWXJIsOUQaYlk3Myz2FC2gTybfkU2AqwG+3xfUViEbxhL6FoiAxzBia96ZiPGVNjdPg6aHI30ehppNXTiivkIhwLY9abSTOmkWfNw2F2YNQbMevMmPQmbEYbJWkl5FpzJQSJMUdCjhBCjFEGvYFpWdOYljUNgGAkSG+gl56gtkxES38LCgo72neQacmMt7jkWfPIsmTRH+7HHXITjoYJRUPodXqK7EUUpxWTa80lEovQG+ylN9BLt78bb9iLK+SiJ9BDj78HFZWoGiWqRjEoBvSKHovBglFnxKgzYtAZMOqN2Axa0JmVM4spGVMoTCuURUjFmCAhRwghkoTZYKYwrZDCNG2ZiFgsRl+wjw5fR/z/SCyCUWfEbDATjUWJxCKoqKiqSowYRp0WSjLMGZj1ZvwRP/3hfnxhH6qqYtAZSDOlMSN7BjmWHAw6A6FYiP5QP56QB3/ETygaIhQL4Yv4CEaD+CN+antq+bj9Y/KseWSaM5mQPoFiezE51hwyzBnoFB3KwD+9Tk+uNfe4rUpCDBcJOUIIkaR0Oh3Z1myyrdnx6wKRAD2BHrxhLxa9BZvRhkVvwWwwx2/rDfbS6e9Ehw6T3kS6KZ2StBLSTenH7Htj1psxW83kWHOOWUc4FsbpddLmbaO2txYFBWOHEbvRjsPswG6wo9fpAbSYoyiY9CYmOyZTml5Kkb0Ih9mBXtFj0GktRjpFR0SNEI1pLUmRWERrkYqFCEVDhGNhYmrsqFqiapSYGiMSixBTY5j0JuxGe/xiM9iwGqxyam2ckJAjhBApxGKwUJxWfMzb0kxppJnSmMjEYX1Mo85IaXoppemlqKqKJ+ShJ9BDX7CPdm87UTWKigoDU/8Mfr+zYycZ5gwyzZnYjXb0ih5FUbRWH0VBVQ+3QMXUw5fB4BNTY6ioKBwOLEe2Wg2GIJPOhFFvjP9vNVjJNGeSac7UjokxjXRTOmnGtPj3Zr1ZglAKkJAjhBBi2CiKgsPswGF2HHcbVVXpD/fT4eug299Nvas+HkhUDk+COBgyBkOMgoKiU9Cjj5/+im+vcHibgW906LQ+RWgtQdGYFq70ij7eamQ1WLEarJgN5nhnapPehMVgIcOcQYYpgzRTGlnmLDLMGWRZssgyZ2HUS5+jZCAhRwghxKhSFIV0UzrppnTOzTwXIN5KM9gKo6qq1qqD1qpz5NdnKqbGtD5EYT/eiBd/2I8v4tNGkQXCRKKReCvTkUHIrDdrp7oGTv1ZDBYcZgdZ5izSTenYjNopMLPejFk/EJR0Ju20m04XP/02uC+TzoRBZ5CWolEgIUcIIUTC6RQdOmVkVxrSKbp4y0022cfc5sgg5Iv48Ef8+CN+rWO3v4NQNKR1nlb06HV6TDqt1Wdw1Nng9QbFcPjUG4dDml6nj2+TZkwj15pLpkU7dZZhysButGM1WLEZbRh1RglCZ0lCjhBCCDHgZEFIVVW8YS/esDcegALRAH3BvvjpsJgaI6pGAYZ0jj5yPbLBUGfQGbAb7NhNdq2DuN4cH5pv0VvIsmRpI9RMWt+ldFP64dYivQmz3ixh6AQk5AghhBCnSFGUeAfuU6WqarxDdCQWIRQLEYlG8Ee14fv9oX46fZ2EY+F4UAItCOkV/dDTZQMtRjpFp81dpNPmLsqx5JBpySTdmD60M7UpbcjotvFGQo4QQggxghRloDO0AnqdHjNmALLIOmpbVdUmYAxFtXmIfGEf/eF+vGEvfcE+Imok3ndpUDwMGczxIfKDrTyD/2dbssmx5Gidwk0OrEYrJt3QbdKMaSnXoVpCjhBCCDFGKIqCQTFg0Bm0xVitR28z2Dk7Eo0QjoXxR/z4wr54Z2pPyEM0FiWshonFYloI0ukx6ozxFiGz3hzvXD3YT2jw9iyLNpLMZrBhNVqxGWxa52q9VRuKP9Cx2qg3xluVxioJOUIIIUQSURRtGL3eoLUKHe/U2eDpMV/Yhyfsifcl6vR3EosNjGRTY0TR5hyKd6hW9PH5hAZHjBl1Rgx6w1HBSKfotBm2B1qEdIouPofRYH+kb876phbYEiDpQ87q1av5+c9/jtPpZN68efzqV7/ioosuSnRZQgghREIpioJRbyRDn0GGJeO42w2GnUAkMKRDtT/q19Y+G+grFB/mP/DvyFFj8VNyR+4XFaPOyLIpyyTknImXXnqJlStXsmbNGsrKynj88cdZunQptbW15OfnJ7o8IYQQYsxTFK0Fx27SRnmdyGAgisQiRNSIttzGQAiKqBFtfqOB8BNVowQjQUjgwK+xeyLtFDz66KPcfvvtLF++nFmzZrFmzRpsNhu/+93vEl2aEEIIkXIURVtg1WzQRnxlmDPIteZSYC9gQtoEStJLtMVZ04rJs+bFT2ElStK25IRCISoqKli1alX8Op1Ox+LFi9myZcsx7xMMBgkGg/HvXS4XAG63e3hri4YIeAO4g25cBtew7lsIIYRIBpFYBACP24M7Nrzvs4Pv20eOMjuWpA05XV1dRKNRCgoKhlxfUFBATU3NMe/zyCOP8PDDDx91fWlp6YjUKIQQQox3j/P4iO3b4/GQkXH8/kZJG3LOxKpVq1i5cmX8+1gsRk9PDzk5OeN6tki3201paSnNzc04HMdfVE+cPjm2I0uO78iS4zty5NieHVVV8Xg8FBcXn3C7pA05ubm56PV62tvbh1zf3t5OYWHhMe9jNpsxm81DrsvMzBypEpOOw+GQP7YRIsd2ZMnxHVlyfEeOHNszd6IWnEFJ2/HYZDKxcOFCNmzYEL8uFouxYcMGysvLE1iZEEIIIcaCpG3JAVi5ciU333wzF154IRdddBGPP/44Xq+X5cuXJ7o0IYQQQiRYUoec66+/ns7OTh588EGcTifz589n/fr1R3VGFidmNpv54Q9/eNSpPHH25NiOLDm+I0uO78iRYzs6FPVk46+EEEIIIZJQ0vbJEUIIIYQ4EQk5QgghhEhJEnKEEEIIkZIk5AghhBAiJUnISREffvghX/rSlyguLkZRFF577bUht7e3t3PLLbdQXFyMzWbjyiuvZP/+/fHbe3p6uPvuu5kxYwZWq5WJEyfyz//8z/H1vQY1NTWxbNkybDYb+fn5fP/73ycSiYzGU0yYsz22R1JVlS9+8YvH3M94PLYwfMd3y5Yt/MM//AN2ux2Hw8Fll12G3++P397T08ONN96Iw+EgMzOTW2+9lf7+/pF+egk1HMfW6XRy0003UVhYiN1uZ8GCBfzxj38css14PLagLRW0aNEi0tPTyc/P5+qrr6a2tnbINoFAgBUrVpCTk0NaWhrXXnvtUZPYnsrf/saNG1mwYAFms5mpU6eydu3akX56KUFCTorwer3MmzeP1atXH3WbqqpcffXVHDx4kD//+c/s2LGDSZMmsXjxYrxeLwCtra20trbyX//1X1RVVbF27VrWr1/PrbfeGt9PNBpl2bJlhEIhNm/ezLPPPsvatWt58MEHR+15JsLZHtsjPf7448dcQmS8HlsYnuO7ZcsWrrzySpYsWcL27dv5+OOPueuuu9DpDr/E3XjjjezZs4d3332XN954gw8//JBvf/vbo/IcE2U4ju03v/lNamtref3119m9ezfXXHMNX/3qV9mxY0d8m/F4bAE2bdrEihUr2Lp1K++++y7hcJglS5YMOX733nsvf/nLX3jllVfYtGkTra2tXHPNNfHbT+Vvv76+nmXLlnH55ZdTWVnJPffcw2233cbbb789qs83Kaki5QDqq6++Gv++trZWBdSqqqr4ddFoVM3Ly1Offvrp4+7n5ZdfVk0mkxoOh1VVVdU333xT1el0qtPpjG/z61//WnU4HGowGBz+JzIGnc2x3bFjhzphwgS1ra3tqP3IsdWc6fEtKytTH3jggePud+/evSqgfvzxx/Hr3nrrLVVRFLWlpWV4n8QYdabH1m63q88999yQfWVnZ8e3kWN7WEdHhwqomzZtUlVVVfv6+lSj0ai+8sor8W2qq6tVQN2yZYuqqqf2t3/fffeps2fPHvJY119/vbp06dKRfkpJT1pyxoFgMAiAxWKJX6fT6TCbzXz00UfHvZ/L5cLhcGAwaHNGbtmyhTlz5gyZbHHp0qW43W727NkzQtWPbad6bH0+H1//+tdZvXr1MddWk2N7bKdyfDs6Oti2bRv5+flccsklFBQU8LnPfW7I8d+yZQuZmZlceOGF8esWL16MTqdj27Zto/RsxpZT/d295JJLeOmll+jp6SEWi/GHP/yBQCDA5z//eUCO7ZEGT+9nZ2cDUFFRQTgcZvHixfFtZs6cycSJE9myZQtwan/7W7ZsGbKPwW0G9yGOT0LOODD4R7Vq1Sp6e3sJhUL89Kc/5dChQ7S1tR3zPl1dXfzoRz8a0uTsdDqPmk168Hun0zlyT2AMO9Vje++993LJJZfwT//0T8fcjxzbYzuV43vw4EEAHnroIW6//XbWr1/PggULuOKKK+L9S5xOJ/n5+UP2bTAYyM7OHrfH91R/d19++WXC4TA5OTmYzWa+853v8OqrrzJ16lRAju2gWCzGPffcw2c+8xnOP/98QDs2JpPpqIWgCwoK4sfmVP72j7eN2+0e0u9MHE1CzjhgNBr505/+xL59+8jOzsZms/HBBx/wxS9+cUifhUFut5tly5Yxa9YsHnroodEvOImcyrF9/fXXef/993n88ccTW2wSOpXjG4vFAPjOd77D8uXLueCCC3jssceYMWMGv/vd7xJZ/ph2qq8L//7v/05fXx/vvfcen3zyCStXruSrX/0qu3fvTmD1Y8+KFSuoqqriD3/4Q6JLEUdI6rWrxKlbuHAhlZWVuFwuQqEQeXl5lJWVDWliBvB4PFx55ZWkp6fz6quvYjQa47cVFhayffv2IdsPjhI41imY8eJkx/b999/nwIEDR32au/baa7n00kvZuHGjHNsTONnxLSoqAmDWrFlD7nfeeefR1NQEaMewo6NjyO2RSISenp5xfXxPdmwPHDjAk08+SVVVFbNnzwZg3rx5/PWvf2X16tWsWbNGji1w1113xTtcl5SUxK8vLCwkFArR19c35O+/vb09fmxO5W+/sLDwqBFZ7e3tOBwOrFbrSDyllCEtOeNMRkYGeXl57N+/n08++WTI6RO3282SJUswmUy8/vrrQ87VA5SXl7N79+4hL2jvvvsuDofjqDeY8eh4x/b+++9n165dVFZWxi8Ajz32GM888wwgx/ZUHO/4Tp48meLi4qOG7u7bt49JkyYB2vHt6+ujoqIifvv7779PLBajrKxs9J7EGHW8Y+vz+QCOavHV6/XxFrTxfGxVVeWuu+7i1Vdf5f3332fKlClDbl+4cCFGo5ENGzbEr6utraWpqYny8nLg1P72y8vLh+xjcJvBfYgTSHTPZzE8PB6PumPHDnXHjh0qoD766KPqjh071MbGRlVVtZFSH3zwgXrgwAH1tddeUydNmqRec8018fu7XC61rKxMnTNnjlpXV6e2tbXFL5FIRFVVVY1EIur555+vLlmyRK2srFTXr1+v5uXlqatWrUrIcx4tZ3tsj4W/G+kyXo+tqg7P8X3sscdUh8OhvvLKK+r+/fvVBx54QLVYLGpdXV18myuvvFK94IIL1G3btqkfffSROm3aNPWGG24Y1ec62s722IZCIXXq1KnqpZdeqm7btk2tq6tT/+u//ktVFEVdt25dfLvxeGxVVVXvvPNONSMjQ924ceOQ10yfzxff5o477lAnTpyovv/+++onn3yilpeXq+Xl5fHbT+Vv/+DBg6rNZlO///3vq9XV1erq1atVvV6vrl+/flSfbzKSkJMiPvjgAxU46nLzzTerqqqqv/zlL9WSkhLVaDSqEydOVB944IEhQ5OPd39Ara+vj2/X0NCgfvGLX1StVquam5ur/uu//mt8iHmqOttjeyx/H3JUdXweW1UdvuP7yCOPqCUlJarNZlPLy8vVv/71r0Nu7+7uVm+44QY1LS1NdTgc6vLly1WPxzMaTzFhhuPY7tu3T73mmmvU/Px81WazqXPnzj1qSPl4PLaqqh73NfOZZ56Jb+P3+9Xvfve7alZWlmqz2dQvf/nLaltb25D9nMrf/gcffKDOnz9fNZlM6jnnnDPkMcTxKaqqqiPZUiSEEEIIkQjSJ0cIIYQQKUlCjhBCCCFSkoQcIYQQQqQkCTlCCCGESEkScoQQQgiRkiTkCCGEECIlScgRQgghREqSkCOEEEKIlCQhRwghhBApSUKOEEIIIVKShBwhhDhCNBqNr7AthEhuEnKEEGPWc889R05ODsFgcMj1V199NTfddBMAf/7zn1mwYAEWi4VzzjmHhx9+mEgkEt/20UcfZc6cOdjtdkpLS/nud79Lf39//Pa1a9eSmZnJ66+/zqxZszCbzTQ1NY3OExRCjCgJOUKIMesrX/kK0WiU119/PX5dR0cH69at41vf+hZ//etf+eY3v8m//Mu/sHfvXv77v/+btWvX8pOf/CS+vU6n44knnmDPnj08++yzvP/++9x3331DHsfn8/HTn/6U3/72t+zZs4f8/PxRe45CiJEjq5ALIca07373uzQ0NPDmm28CWsvM6tWrqaur4wtf+AJXXHEFq1atim///PPPc99999Ha2nrM/f3f//0fd9xxB11dXYDWkrN8+XIqKyuZN2/eyD8hIcSokZAjhBjTduzYwaJFi2hsbGTChAnMnTuXr3zlK/z7v/87eXl59Pf3o9fr49tHo1ECgQBerxebzcZ7773HI488Qk1NDW63m0gkMuT2tWvX8p3vfIdAIICiKAl8pkKI4WZIdAFCCHEiF1xwAfPmzeO5555jyZIl7Nmzh3Xr1gHQ39/Pww8/zDXXXHPU/SwWCw0NDVx11VXceeed/OQnPyE7O5uPPvqIW2+9lVAohM1mA8BqtUrAESIFScgRQox5t912G48//jgtLS0sXryY0tJSABYsWEBtbS1Tp0495v0qKiqIxWL84he/QKfTuiC+/PLLo1a3ECKxJOQIIca8r3/963zve9/j6aef5rnnnotf/+CDD3LVVVcxceJErrvuOnQ6HTt37qSqqoof//jHTJ06lXA4zK9+9Su+9KUv8be//Y01a9Yk8JkIIUaTjK4SQox5GRkZXHvttaSlpXH11VfHr1+6dClvvPEG77zzDosWLeLiiy/mscceY9KkSQDMmzePRx99lJ/+9Kecf/75vPDCCzzyyCMJehZCiNEmHY+FEEnhiiuuYPbs2TzxxBOJLkUIkSQk5AghxrTe3l42btzIddddx969e5kxY0aiSxJCJAnpkyOEGNMuuOACent7+elPfyoBRwhxWqQlRwghhBApSToeCyGEECIlScgRQgjx/2+3DmQAAAAABvlb3+MrimBJcgCAJckBAJYkBwBYkhwAYElyAIAlyQEAlgJdMWPI3mgJiAAAAABJRU5ErkJggg==", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "name_counts.plot.area(subplots=True, alpha=0.5)" - ] - }, - { - "cell_type": "markdown", - "id": "89d576a9", - "metadata": {}, - "source": [ - "# Bar Chart" - ] - }, - { - "cell_type": "markdown", - "id": "9e4c6864", - "metadata": {}, - "source": [ - "Bar Charts are suitable for analyzing categorical data. For example, you are going to check the sex distribution of the penguin data:" - ] - }, - { - "cell_type": "code", - "execution_count": 14, - "id": "e4aef1a1", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "" - ] - }, - "execution_count": 14, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "image/png": "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", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "penguin_count_by_sex = penguins[penguins['sex'].isin((\"MALE\", \"FEMALE\"))].groupby('sex')['species'].count()\n", - "penguin_count_by_sex.plot.bar()" - ] - }, - { - "cell_type": "markdown", - "id": "41f5f621", - "metadata": {}, - "source": [ - "# Scatter Plot" - ] - }, - { - "cell_type": "markdown", - "id": "d79c527a", - "metadata": {}, - "source": [ - "In this example, you will explore the relationship between NYC taxi fares and trip distances." - ] - }, - { - "cell_type": "code", - "execution_count": 15, - "id": "b6bf3f2a", - "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", - "
vendor_idpickup_datetimedropoff_datetimepassenger_counttrip_distancerate_codestore_and_fwd_flagpayment_typefare_amountextramta_taxtip_amounttolls_amountimp_surchargeairport_feetotal_amountpickup_location_iddropoff_location_iddata_file_yeardata_file_month
022021-09-19 10:25:05+00:002021-09-19 10:25:10+00:0010E-91.0N10E-90E-90E-90E-90E-90E-90E-90E-926426420219
122021-09-20 14:53:02+00:002021-09-20 14:53:23+00:0010E-91.0N10E-90E-90E-90E-90E-90E-90E-90E-919319320219
212021-09-14 12:01:02+00:002021-09-14 12:07:19+00:0010E-91.0N10E-90E-90E-90E-90E-90E-90E-90E-917017020219
322021-09-12 10:40:32+00:002021-09-12 10:41:26+00:0010E-91.0N10E-90E-90E-90E-90E-90E-90E-90E-919319320219
412021-09-25 11:57:21+00:002021-09-25 11:58:32+00:0010E-91.0N10E-90E-90E-90E-90E-90E-90E-90E-9959520219
\n", - "
" - ], - "text/plain": [ - " vendor_id pickup_datetime dropoff_datetime \\\n", - "0 2 2021-09-19 10:25:05+00:00 2021-09-19 10:25:10+00:00 \n", - "1 2 2021-09-20 14:53:02+00:00 2021-09-20 14:53:23+00:00 \n", - "2 1 2021-09-14 12:01:02+00:00 2021-09-14 12:07:19+00:00 \n", - "3 2 2021-09-12 10:40:32+00:00 2021-09-12 10:41:26+00:00 \n", - "4 1 2021-09-25 11:57:21+00:00 2021-09-25 11:58:32+00:00 \n", - "\n", - " passenger_count trip_distance rate_code store_and_fwd_flag payment_type \\\n", - "0 1 0E-9 1.0 N 1 \n", - "1 1 0E-9 1.0 N 1 \n", - "2 1 0E-9 1.0 N 1 \n", - "3 1 0E-9 1.0 N 1 \n", - "4 1 0E-9 1.0 N 1 \n", - "\n", - " fare_amount extra mta_tax tip_amount tolls_amount imp_surcharge \\\n", - "0 0E-9 0E-9 0E-9 0E-9 0E-9 0E-9 \n", - "1 0E-9 0E-9 0E-9 0E-9 0E-9 0E-9 \n", - "2 0E-9 0E-9 0E-9 0E-9 0E-9 0E-9 \n", - "3 0E-9 0E-9 0E-9 0E-9 0E-9 0E-9 \n", - "4 0E-9 0E-9 0E-9 0E-9 0E-9 0E-9 \n", - "\n", - " airport_fee total_amount pickup_location_id dropoff_location_id \\\n", - "0 0E-9 0E-9 264 264 \n", - "1 0E-9 0E-9 193 193 \n", - "2 0E-9 0E-9 170 170 \n", - "3 0E-9 0E-9 193 193 \n", - "4 0E-9 0E-9 95 95 \n", - "\n", - " data_file_year data_file_month \n", - "0 2021 9 \n", - "1 2021 9 \n", - "2 2021 9 \n", - "3 2021 9 \n", - "4 2021 9 " - ] - }, - "execution_count": 15, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "taxi_trips = bpd.read_gbq('bigquery-public-data.new_york_taxi_trips.tlc_yellow_trips_2021').dropna()\n", - "taxi_trips.peek()" - ] - }, - { - "cell_type": "markdown", - "id": "413c0f91", - "metadata": {}, - "source": [ - "First, you santize the data a bit by remove outliers and pathological datapoints:" - ] - }, - { - "cell_type": "code", - "execution_count": 16, - "id": "d4876b08", - "metadata": {}, - "outputs": [], - "source": [ - "taxi_trips = taxi_trips[taxi_trips['trip_distance'].between(0, 10, inclusive='right')]\n", - "taxi_trips = taxi_trips[taxi_trips['fare_amount'].between(0, 50, inclusive='right')]" - ] - }, - { - "cell_type": "markdown", - "id": "f1ed53f7", - "metadata": {}, - "source": [ - "You also need to sort the data before plotting if you have turned on the partial ordering mode during the setup stage." - ] - }, - { - "cell_type": "code", - "execution_count": 17, - "id": "e9ddad9b", - "metadata": {}, - "outputs": [], - "source": [ - "taxi_trips = taxi_trips.sort_values('pickup_datetime')" - ] - }, - { - "cell_type": "code", - "execution_count": 18, - "id": "e34ab06d", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "" - ] - }, - "execution_count": 18, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "image/png": "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", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "taxi_trips.plot.scatter(x='trip_distance', y='fare_amount', alpha=0.5)" - ] - }, - { - "cell_type": "markdown", - "id": "7ab4ded3", - "metadata": {}, - "source": [ - "# Advacned Plotting with Pandas/Matplotlib Parameters" - ] - }, - { - "cell_type": "markdown", - "id": "51e3b044", - "metadata": {}, - "source": [ - "Because BigQuery DataFrame's plotting library is powered by Matplotlib and Pandas, you are able to pass in more parameters to fine tune your graph like what you do with Pandas. \n", - "\n", - "In the following example, you will resuse the taxi trips dataset, except that you will rename the labels for X-axis and Y-axis, use `passenger_count` for point sizes, color points with `tip_amount`, and resize the figure. " - ] - }, - { - "cell_type": "code", - "execution_count": 19, - "id": "51c4dfc7", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "" - ] - }, - "execution_count": 19, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAABGkAAAJfCAYAAADM54shAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjYsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvq6yFwwAAAAlwSFlzAAAPYQAAD2EBqD+naQABAABJREFUeJzs3Xd4XPWV//H3nT6jKerVKu6We8UFMIZACGwIJCTAhg2QRpINmwKk7pJAEhbSiEnjt9mwQDYhjQ0lECCUYKoLLrjbsi1ZLmpWr1Pv74+xBUJukkcaSfN55Zkn6N47954ryZLmzPmeY5imaSIiIiIiIiIiIkllSXYAIiIiIiIiIiKiJI2IiIiIiIiIyIigJI2IiIiIiIiIyAigJI2IiIiIiIiIyAigJI2IiIiIiIiIyAigJI2IiIiIiIiIyAigJI2IiIiIiIiIyAigJI2IiIiIiIiIyAigJI2IiIiIiIiIyAigJI2IiIiIiIiIyAigJI2IiIiIiIiIjCkvv/wyl112GYWFhRiGwWOPPdZnv2mafOtb36KgoAC3282FF15IRUVFcoJ9ByVpRERERERERGRM6ezsZM6cOfziF7847v4f/OAH/PSnP+X//b//x5o1a0hLS+Piiy+mp6dnmCPtyzBN00xqBCIiIiIiIiIiQ8QwDB599FGuuOIKIF5FU1hYyC233MKtt94KQGtrK3l5eTz44INcc801SYvVlrQrD5NYLMbhw4fx+XwYhpHscERERERERGSMM02T9vZ2CgsLsVjG9gKWnp4eQqHQsFzLNM1+r+udTidOp3NA56msrKS2tpYLL7ywd1sgEGDx4sW88cYbStIMpcOHD1NcXJzsMERERERERCTFHDhwgHHjxiU7jCHT09NDUU4OTR0dw3I9r9dLx7uu9e1vf5vbb799QOepra0FIC8vr8/2vLy83n3JMuaTND6fD4j/4/D7/UmORkRERERERMa6trY2iouLe1+PjlWhUIimjg7+9OUv4xlgNctAdQWDXPWTn/R7bT/QKpqRbswnaY6VQvn9fiVpREREREREZNikSssNr9NJ2hAnS44tGkvEa/v8/HwA6urqKCgo6N1eV1fH3Llzz+jcZ2psL44TEREREREREXmH8ePHk5+fzwsvvNC7ra2tjTVr1rB06dIkRpYClTQiIiIiIiIiMnQsDH0FyEDP39HRwZ49e3o/rqysZNOmTWRmZlJSUsKXvvQlvve97zF58mTGjx/PbbfdRmFhYe8EqGRRkkZERERERERExpQ333yT888/v/fjm2++GYDrr7+eBx98kK9+9at0dnZy44030tLSwjnnnMMzzzyDy+VKVsiAkjRAfIxXJBIhGo0mOxQRAKxWKzabLWXWsIqIiIiIyOg1EitpVqxYgWmaJ9xvGAbf+c53+M53vnNmgSVYyidpQqEQNTU1dHV1JTsUkT48Hg8FBQU4HI5khyIiIiIiIiLDIKWTNLFYjMrKSqxWK4WFhTgcDlUuSNKZpkkoFKKhoYHKykomT56MxaIe3yIiIiIiMjJZjz6G+hqpIKWTNKFQiFgsRnFxMR6PJ9nhiPRyu93Y7Xb2799PKBRK+rpIERERERERGXopnaQ5RlUKMhLp+1JEREREREYDg6HvSZMqa170KlBEREREREREZARQJc0Z6iHMfhppoZsYJnasFJJOPn4sKZPrExERERERkVQ1Eqc7jVZK0gxSiAibOMgOamjm7clQJuA8mqiZSzFlZCUvSBEREREREREZNVIlGZVQQSI8z05eYw9BIhQSoJgMismghAz8uKmmiWfZxg5qkx2ujECGYfDYY48lOwwREREREZEzZhmmRypIlftMGBOT19jLLmrJw08WaVjf9Wl0Y6eQACbwChUcpDk5waaYaDRKLBZLdhgiIiIiIiIig6IkzQA10kkFdWTgwXmS1WIGBtmk0U2YrRzGxExoHCtWrOCmm27ipptuIhAIkJ2dzW233YZpxq/zv//7vyxcuBCfz0d+fj4f/ehHqa+v731+c3Mz1157LTk5ObjdbiZPnswDDzwAxEeT33TTTRQUFOByuSgtLeWuu+7qfW5LSwuf+tSnyMnJwe/3c8EFF/DWW2/17r/99tuZO3cu//u//0tZWRmBQIBrrrmG9vb23mPa29u59tprSUtLo6CggJ/85CesWLGCL33pS73HBINBbr31VoqKikhLS2Px4sW89NJLvfsffPBB0tPTeeKJJ5g+fTpOp5Pq6upTfu7+53/+hxkzZuB0OikoKOCmm27q3VddXc3ll1+O1+vF7/dz1VVXUVdX17v/hhtu4Iorruhzvi996UusWLGiz9fmC1/4Al/96lfJzMwkPz+f22+/vXd/WVkZAB/84AcxDKP3YxEREREREUltStIM0F4a6CKEF+cpjzUwSMfNfhppojPhsTz00EPYbDbWrl3Lvffeyz333MOvf/1rAMLhMN/97nd56623eOyxx6iqquKGG27ofe5tt93G9u3befrpp9mxYwf33Xcf2dnZAPz0pz/liSee4E9/+hO7du3id7/7XZ9Ewkc+8hHq6+t5+umnWb9+PfPnz+c973kPTU1Nvcfs3buXxx57jCeffJInn3ySVatWcffdd/fuv/nmm3nttdd44okneO6553jllVfYsGFDn/u76aabeOONN/jDH/7A5s2b+chHPsL73vc+Kioqeo/p6uri+9//Pr/+9a/Ztm0bubm5J/2c3XfffXz+85/nxhtvZMuWLTzxxBNMmjQJgFgsxuWXX05TUxOrVq3iueeeY9++fVx99dUD+8IQ/9qkpaWxZs0afvCDH/Cd73yH5557DoB169YB8MADD1BTU9P7sYiIiIiIyGhkHaZHKlDj4AHaTyNu7BinObkpDQfNdFFHO1l4ExpLcXExP/nJTzAMg6lTp7JlyxZ+8pOf8OlPf5pPfOITvcdNmDCBn/70pyxatIiOjg68Xi/V1dXMmzePhQsXAvRJwlRXVzN58mTOOeccDMOgtLS0d9+rr77K2rVrqa+vx+mMJ6p+9KMf8dhjj/HII49w4403AvGEx4MPPojP5wPgYx/7GC+88AJ33nkn7e3tPPTQQzz88MO85z3vAeIJi8LCwj4xPPDAA1RXV/duv/XWW3nmmWd44IEH+M///E8gnoz65S9/yZw5c07rc/a9732PW265hS9+8Yu92xYtWgTACy+8wJYtW6isrKS4uBiA3/zmN8yYMYN169b1Hnc6Zs+ezbe//W0AJk+ezM9//nNeeOEFLrroInJycgBIT08nPz//tM8pIiIiIiIiY5sqaQYoSKRfD5qTMY6mc8JEEx7LkiVLMIy3k0VLly6loqKCaDTK+vXrueyyyygpKcHn83HeeecB9C4H+tznPscf/vAH5s6dy1e/+lVef/313vPccMMNbNq0ialTp/KFL3yBv//977373nrrLTo6OsjKysLr9fY+Kisr2bt3b+9xZWVlvQkagIKCgt7lVvv27SMcDnPWWWf17g8EAkydOrX34y1bthCNRpkyZUqf66xatarPdRwOB7Nnzz6tz1d9fT2HDx/uTQy9244dOyguLu5N0ABMnz6d9PR0duzYcVrXOObdMb3z/kVEREQktUWI0EIzLTQTHYLXCSLDTY2DE0eVNANkw0qM0Gkfbx7tRmMbxm+pnp4eLr74Yi6++GJ+97vfkZOTQ3V1NRdffDGhUDz2Sy65hP379/O3v/2N5557jve85z18/vOf50c/+hHz58+nsrKSp59+mueff56rrrqKCy+8kEceeYSOjg4KCgr69IY5Jj09vfe/7XZ7n32GYQyoqW9HRwdWq5X169djtfYtbPN6365IcrvdfRJVJ+N2u0/7+idisVh6+/4cEw6H+x13pvcvIiIiImNPjBhVVLGH3bQR79fox89kplBG2WlX64vI2KUkzQCNI503acPEPK0fot2EcWAji7SEx7JmzZo+H69evZrJkyezc+dOGhsbufvuu3urQt58881+z8/JyeH666/n+uuv59xzz+UrX/kKP/rRjwDw+/1cffXVXH311Xz4wx/mfe97H01NTcyfP5/a2lpsNtugG95OmDABu93OunXrKCkpAaC1tZXdu3ezfPlyAObNm0c0GqW+vp5zzz13UNd5N5/PR1lZGS+88ALnn39+v/3l5eUcOHCAAwcO9H7etm/fTktLC9OnTwfin7OtW7f2ed6mTZv6JWVOxW63E43qXRMRERGRVFLBbt5iExaseI+2QmijlTdZS4Qwk5mS5AhFBmc4Kl1SpZImVe4zYSaRixMb3fSvnjieZroZRzp5+BMeS3V1NTfffDO7du3i97//PT/72c/44he/SElJCQ6Hg5/97Gfs27ePJ554gu9+97t9nvutb32Lxx9/nD179rBt2zaefPJJysvLAbjnnnv4/e9/z86dO9m9ezd//vOfyc/PJz09nQsvvJClS5dyxRVX8Pe//52qqipef/11/v3f//24iaDj8fl8XH/99XzlK1/hH//4B9u2beOTn/wkFoultypmypQpXHvttVx33XX85S9/obKykrVr13LXXXfx1FNPDfpzdvvtt/PjH/+Yn/70p1RUVLBhwwZ+9rOfAXDhhRcya9Ysrr32WjZs2MDatWu57rrrOO+883p791xwwQW8+eab/OY3v6GiooJvf/vb/ZI2p+NYsqi2tpbmZo1oFxERERnruulmN7uw4yCTTBxH/5dJFlZs7GInPfQkO0wRSTIlaQYoDz+lZNFAB5FTrB9tpRsrBtMpGJLSxeuuu47u7m7OOussPv/5z/PFL36RG2+8kZycHB588EH+/Oc/M336dO6+++7eCpljHA4H3/jGN5g9ezbLly/HarXyhz/8AYgnUX7wgx+wcOFCFi1aRFVVFX/72996kyh/+9vfWL58OR//+MeZMmUK11xzDfv37ycvL++0Y7/nnntYunQp73//+7nwwgs5++yzKS8vx+Vy9R7zwAMPcN1113HLLbcwdepUrrjiij7VN4Nx/fXXs3LlSn75y18yY8YM3v/+9/dOizIMg8cff5yMjAyWL1/OhRdeyIQJE/jjH//Y+/yLL76Y2267ja9+9assWrSI9vZ2rrvuugHH8eMf/5jnnnuO4uJi5s2bN+j7EREREZHRoYF6OunEh6/fPj9+OujgCA1JiEzkzGm6U+IY5rsbbIwxbW1tBAIBWltb8fv7VrP09PRQWVnJ+PHj+yQHTqWdHp5lO9U0kYkHL84+SZgIUZrpIkyMxYxnEaUJT9KsWLGCuXPnsnLlyoSeN1k6OzspKirixz/+MZ/85CeTHc6IMNjvTxEREREZeSrZxxpWk0v/NzZNTBqoYylnU0rZ8AcnCXey16FjybH7fPPrX8d7dPrvUOkIBll4991j/nOqnjSD4MPFJczgdfayj0aaacaOFQsGYeLNYTPxMI8SZgxRFc1ot3HjRnbu3MlZZ51Fa2sr3/nOdwC4/PLLkxyZiIiIiEji+fBjw0aQIE76vpgNEsSO47hVNiKjgXrSJI6SNIOUhpMLKaeZLvbSwBE6CBPFg4MSMiklC6c+vSf1ox/9iF27duFwOFiwYAGvvPIK2dnZZ3TOd05+erenn346YU2IRUREREQGIpNM8sjnANVkk4Pt6GuFMGFaaKaUMjLITHKUIpJsyiKcAQODTNLIHILJTadyvBHYo8m8efNYv359ws+7adOmE+4rKipK+PVERERERE6HBQvzmE+ECPXUETtagW/BQiFFzGWeKvBl1DIY+kqXVPnXoSSNjCmTJk1KdggiIiIiIsflxcs5nEsttTTTBEAWWeSR31tZIyKpTT8JgDHeO1lGKX1fioiIiIw9duwUH/2fyFihnjSJkyr3eVx2ux2Arq6uJEci0t+x78tj36ciIiIiIiIytqV0JY3VaiU9PZ36+noAPB4PhpEqK91kpDJNk66uLurr60lPT8dqtSY7JBERERERkROyHn0M9TVSQUonaQDy8/MBehM1IiNFenp67/eniIiIiIiIjH0pn6QxDIOCggJyc3MJh8PJDkcEiC9xUgWNiIiIiIiMBupJkzgpn6Q5xmq16kWxiIiIiIiIiCRNqiSjRERERERERERGNFXSiIiIiIiIiMigablT4qTKfYqIiIiIiIiIjGiqpBERERERERGRQVMlTeKkyn2KiIiIiIiIiIxoqqQRERERERERkUGzHn0M9TVSgSppRERERERERERGAFXSiIiIiIiIiMigqSdN4qTKfYqIiIiIiIiIjGiqpBERERERERGRQTMY+goQY4jPP1KokkZEREREREREZARQJY2IiIiIiIiIDJqmOyWOKmlEREREREREREYAVdKIiIiIiIiIyKBpulPipMp9ioiIiIiIiIiMaKqkEREREREREZFBsxhgGeISEEuKjHdSJY2IiIiIiIiIyAigShoRERERERERGTSLZRgqaVKkxCRFblNEREREREREZGRTJY2IiIiIiIiIDJrViD+G+hqpIKmVNPfddx+zZ8/G7/fj9/tZunQpTz/9dO/+FStWYBhGn8dnP/vZJEYsIiIiIiIiIjI0klpJM27cOO6++24mT56MaZo89NBDXH755WzcuJEZM2YA8OlPf5rvfOc7vc/xeDzJCldEREREREREZMgkNUlz2WWX9fn4zjvv5L777mP16tW9SRqPx0N+fn4ywhMRERERERGRU1Dj4MQZMbcZjUb5wx/+QGdnJ0uXLu3d/rvf/Y7s7GxmzpzJN77xDbq6uk56nmAwSFtbW5+HiIiIiIiIiMhIl/TGwVu2bGHp0qX09PTg9Xp59NFHmT59OgAf/ehHKS0tpbCwkM2bN/O1r32NXbt28Ze//OWE57vrrru44447hit8ERERERERkZRmtcQfQ32NVGCYpmkmM4BQKER1dTWtra088sgj/PrXv2bVqlW9iZp3evHFF3nPe97Dnj17mDhx4nHPFwwGCQaDvR+3tbVRXFxMa2srfr9/yO5DREREREREBOKvQwOBwJh/Hdp7n7d/Hb/LObTX6gkSuP3uMf85TXoljcPhYNKkSQAsWLCAdevWce+99/Jf//Vf/Y5dvHgxwEmTNE6nE6dzaL85REREREREROQoC0PfTCVFKmlG3G3GYrE+lTDvtGnTJgAKCgqGMSIRERERERERkaGX1Eqab3zjG1xyySWUlJTQ3t7Oww8/zEsvvcSzzz7L3r17efjhh7n00kvJyspi8+bNfPnLX2b58uXMnj07mWGLiIiIiIiIyDGqpEmYpCZp6uvrue6666ipqSEQCDB79myeffZZLrroIg4cOMDzzz/PypUr6ezspLi4mCuvvJL/+I//SGbIIiIiIiIiIiJDIqlJmvvvv/+E+4qLi1m1atUwRiMiIiIiIiIiA6ZKmoRJkdsUERERERERERnZkj7dSURERERERERGMVXSJEyK3KaIiIiIiIiIyMimShoRERERERERGTzj6GOor5ECVEkjIiIiIiIiIjICqJJGRERERERERAbPYOhLQFRJIyIiIiIiIiIiw0WVNCIiIiIiIiIyeJrulDApcpsiIiIiIiIiIiObkjQiIiIiIiIiIiOAljuJiIiIiIiIyOBpuVPCpMhtioiIiIiIiIiMbKqkEREREREREZHBUyVNwqTIbYqIiIiIiIiIjGyqpBERERERERGRwVMlTcKkyG2KiIiIiIiIiIxsqqQRERERERERkcEzjj6G+hopQJU0IiIiIiIiIiIjgCppRERERERERGTw1JMmYVLkNkVERERERERERjZV0oiIiIiIiIjI4KmSJmFS5DZFREREREREREY2VdKIiIiIiIiIyOCpkiZhlKQREREREZHkCjfHH9Y0cOSCkSKzdkVE3kVJGhERERERSY5wM9Q9Bs2rIdoJFif4ZkP+B8FdkuzoROR0GQx9pUuK5G5TpGBIRERERERGlEgn7P851D0BhgVcRWD1QPMqqFoJPYeTHaGIyLBTkkZERERERIZf61po2wTeaeDMA6sbHFngnQldlXDkhWRHKCKnyzJMjxSQIrcpIiIiIiIjSus6sDjiS5zeybCAIzuexImFkxObiEiSqCeNiIiIiIgMv2g3GI7j77M4wAzHH9iHNSwRGQRNd0qYFLlNEREREREZUdImQ7QdTLP/vlAjuErA4h7+uEREkkhJGhERERERGX7pS8CeBd2VYMbi20wTgrXxEdxZKzSKW0RSjpY7iYiIiIjI8PNMgHE3wKH/hfat8YSMGQN7OuRdGU/iiMjoYDD0I7JTJGerJI2IiIiIiCRHxtngmQxt6+NLnGw+8M0Gd5mqaEQkJSlJIyIiIiIiyePMhZxLkh2FiJwJNQ5OmBS5TRERERERERGRkU2VNCIiIiIiIiIyeKqkSZgUuU0RERERERERkZFNlTQiIiIiIiKSFCYmERqIEcJG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ZmbmVfe+HK5O3K+40nDP4krC/YorDfcsriTcr/iuUaNG6dixY5o1a5aOHj2qXr16ae3atY0mE/YmFuPHsiYYAAAAAACAF2NOGgAAAAAAAC9AkQYAAAAAAMALUKQBAAAAAADwAhRpAAAAAAAAvABFmstg0aJF6tChgxwOh5KTk7V161azIwFNevzxx9W/f38FBwerTZs2GjFihPbs2WN2LOC8PPHEE7JYLJo6darZUYBmHTp0SL/5zW8UEREhf39/9ejRQ9u2bTM7FtCI0+nUzJkzFR8fL39/fyUkJOhvf/ubWHME3mLjxo0aPny4YmNjZbFYtHr1ao/thmFo1qxZiomJkb+/v9LS0lRQUGBOWOACUKRpYStXrtS0adOUmZmpHTt2KCkpSenp6SouLjY7GtBITk6OJk2apI8//lgffPCB6uvrdeutt6qystLsaMBZffLJJ3ruuefUs2dPs6MAzSotLdXAgQPl4+Oj9957Tzt37tScOXPUqlUrs6MBjTz55JNasmSJFi5cqF27dunJJ5/UU089pWeffdbsaIAkqbKyUklJSVq0aFGT25966iktWLBAS5cu1ZYtWxQYGKj09HTV1NRc5qTAhWEJ7haWnJys/v37a+HChZIkl8uluLg4/fGPf9T06dNNTgec3bFjx9SmTRvl5OQoNTXV7DhAkyoqKtSnTx8tXrxYjzzyiHr16qV58+aZHQtoZPr06fr3v/+tTZs2mR0FOKdhw4YpKipKL774ortt5MiR8vf31yuvvGJiMqAxi8WiVatWacSIEZJOj6KJjY3Vfffdp/vvv1+SdPLkSUVFRSkrK0ujR482MS1wdoykaUF1dXXavn270tLS3G1Wq1VpaWnavHmzicmA83Py5ElJUnh4uMlJgOZNmjRJP//5zz3+rQW80TvvvKN+/frptttuU5s2bdS7d2+98MILZscCmnTDDTcoOztbe/fulSTl5+crNzdXP/3pT01OBpxbYWGhjh496vG3QWhoqJKTk3kOg9ezmx3galZSUiKn06moqCiP9qioKO3evdukVMD5cblcmjp1qgYOHKju3bubHQdo0muvvaYdO3bok08+MTsKcE5ff/21lixZomnTpukvf/mLPvnkE02ZMkW+vr7KyMgwOx7gYfr06SovL1eXLl1ks9nkdDr16KOP6s477zQ7GnBOR48elaQmn8PObAO8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", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "\n", - "taxi_trips['passenger_count_scaled'] = taxi_trips['passenger_count'] * 30\n", - "\n", - "taxi_trips.plot.scatter(\n", - " x='trip_distance', \n", - " xlabel='trip distance (miles)',\n", - " y='fare_amount', \n", - " ylabel ='fare amount (usd)',\n", - " alpha=0.5, \n", - " s='passenger_count_scaled', \n", - " label='passenger_count',\n", - " c='tip_amount',\n", - " cmap='jet',\n", - " colorbar=True,\n", - " legend=True,\n", - " figsize=(15,7),\n", - " sampling_n=1000)" - ] - }, - { - "cell_type": "markdown", - "id": "6356cdab", - "metadata": {}, - "source": [ - "# Visualize Large Dataset" - ] - }, - { - "cell_type": "markdown", - "id": "fce79ba0", - "metadata": {}, - "source": [ - "BigQuery DataFrame downloads data to your local machine for visualization. The amount of datapoints to be downloaded is capped at 1000 by default. If the amount of datapoints exceeds the cap, BigQuery DataFrame will randomly sample the amount of datapoints equal to the cap.\n", - "\n", - "You can override this cap by setting the `sampling_n` parameter when plotting graphs. For example:" - ] - }, - { - "cell_type": "code", - "execution_count": 20, - "id": "3d0ef911", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "" - ] - }, - "execution_count": 20, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "image/png": "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X34Zhw4dQmlpKSwWCwAgJycHvXv3bva4GRkZqK6uxoQJE5o8bzQaMWjQoHb4JkQkNwwmRGR3VVVVSEhIQEJCAr766isEBQUhJycHCQkJMBqNl/1cZWUlAGDTpk3o1KlTk9e0Wm271kxE8sBgQkQ269q1K9RqNfbu3YuoqCgAQGlpKdLT0zFmzBikpqaiuLgYS5YsQWRkJID6SzkX0mg0AACz2Wx9rnfv3tBqtcjJycGYMWMc9G2ISE4YTIjIZl5eXpg9ezaeeuopBAQEIDg4GM8//zwUivr19FFRUdBoNHj//ffxyCOP4OjRo3jttdeaHKNz584QBAE//vgjbrzxRri7u8Pb2xtPPvkk5s+fD4vFglGjRkGv12PXrl3w8fHBrFmzpPi6RORAvCuHiFrlX//6F6699lpMnjwZ48ePx6hRoxAXFwcACAoKwqeffoq1a9eid+/eWLJkCd58880mn+/UqRNeeeUVPPvsswgJCcHjjz8OAHjttdfw97//HYsXL0avXr1www03YNOmTejSpYvDvyMROR7vyiEiIiLZ4IwJERERyQaDCREREckGgwkRERHJBoMJERERyQaDCREREckGgwkRERHJBoMJERERyQaDCREREckGgwkRERHJBoMJERERyQaDCREREcnG/wO2QTfrb4+kxgAAAABJRU5ErkJggg==", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "noaa_surface_median_temps.plot.line(sampling_n=40)" - ] - }, - { - "cell_type": "markdown", - "id": "64d6f86d", - "metadata": {}, - "source": [ - "Note: `sampling_n` has no effect on histograms. This is because BigQuery DataFrame bucketizes the data on the server side for histograms. If your amount of bins is very large, you may encounter a \"Query too large\" error instead." - ] - } - ], - "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.16" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/noxfile.py b/noxfile.py index 839e6e0e115..34b055de445 100644 --- a/noxfile.py +++ b/noxfile.py @@ -8,7 +8,7 @@ # # https://www.apache.org/licenses/LICENSE-2.0 # -# Unless required by applicable law or agreed to in writing, software/ +# 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 @@ -16,80 +16,46 @@ from __future__ import absolute_import -import argparse -import multiprocessing +from multiprocessing import Process import os import pathlib +from pathlib import Path import re import shutil -import time from typing import Dict, List +import warnings import nox import nox.sessions -PROJECT_ID_OVERRIDE = os.getenv("BIGFRAMES_TEST_PROJECT") -ENV_OVERRIDES = ( - {"GOOGLE_CLOUD_PROJECT": PROJECT_ID_OVERRIDE} if PROJECT_ID_OVERRIDE else {} -) - -RUFF_VERSION = "ruff==0.14.14" -MYPY_VERSION = "mypy==1.15.0" - -# Notebook tests should match colab and BQ Studio. -# Check with import sys; sys.version_info -# on a fresh notebook runtime. -COLAB_AND_BQ_STUDIO_PYTHON_VERSIONS = [ - # BQ Studio - "3.10", - # colab.research.google.com - "3.11", -] - -PYTEST_VERSION = "pytest==8.4.2" +BLACK_VERSION = "black==22.3.0" +ISORT_VERSION = "isort==5.12.0" SPHINX_VERSION = "sphinx==4.5.0" -LINT_PATHS = [ - "docs", - "bigframes", - "scripts", - "tests", - "third_party", - "noxfile.py", - "setup.py", -] +LINT_PATHS = ["docs", "bigframes", "tests", "third_party", "noxfile.py", "setup.py"] -DEFAULT_PYTHON_VERSION = "3.14" +DEFAULT_PYTHON_VERSION = "3.10" -ALL_PYTHON = ["3.10", "3.11", "3.12", "3.13", "3.14", "3.15"] +UNIT_TEST_PYTHON_VERSIONS = ["3.9", "3.10", "3.11"] UNIT_TEST_STANDARD_DEPENDENCIES = [ "mock", - PYTEST_VERSION, + "asyncmock", + "pytest", "pytest-cov", - "pytest-timeout", - "pluggy", + "pytest-asyncio", + "pytest-mock", ] UNIT_TEST_EXTERNAL_DEPENDENCIES: List[str] = [] +UNIT_TEST_LOCAL_DEPENDENCIES: List[str] = [] UNIT_TEST_DEPENDENCIES: List[str] = [] -UNIT_TEST_EXTRAS: List[str] = ["tests"] -UNIT_TEST_EXTRAS_BY_PYTHON: Dict[str, List[str]] = { - "3.10": ["tests", "scikit-learn", "anywidget"], - "3.11": ["tests", "polars", "scikit-learn", "anywidget"], - # Make sure we leave some versions without "extras" so we know those - # dependencies are actually optional. - "3.13": ["tests", "polars", "scikit-learn", "anywidget"], - "3.14": ["tests", "polars", "scikit-learn", "anywidget"], -} - -# 3.11 is used by colab. -# 3.10 is needed for Windows tests as it is the only version installed in the -# bigframes-windows container image. For more information, search -# bigframes/windows-docker, internally. -SYSTEM_TEST_PYTHON_VERSIONS: List[str] = ALL_PYTHON +UNIT_TEST_EXTRAS: List[str] = [] +UNIT_TEST_EXTRAS_BY_PYTHON: Dict[str, List[str]] = {} + +SYSTEM_TEST_PYTHON_VERSIONS = ["3.9", "3.11"] SYSTEM_TEST_STANDARD_DEPENDENCIES = [ "jinja2", "mock", "openpyxl", - PYTEST_VERSION, + "pytest", "pytest-cov", "pytest-retry", "pytest-timeout", @@ -101,32 +67,28 @@ SYSTEM_TEST_EXTERNAL_DEPENDENCIES = [ "google-cloud-bigquery", ] -SYSTEM_TEST_EXTRAS: List[str] = [] -SYSTEM_TEST_EXTRAS_BY_PYTHON: Dict[str, List[str]] = { - # Make sure we leave some versions without "extras" so we know those - # dependencies are actually optional. - "3.10": ["tests", "scikit-learn", "anywidget"], - "3.12": ["tests", "scikit-learn", "polars", "anywidget"], - "3.13": ["tests", "polars", "anywidget"], - "3.14": ["tests", "polars", "anywidget"], -} - -LOGGING_NAME_ENV_VAR = "BIGFRAMES_PERFORMANCE_LOG_NAME" +SYSTEM_TEST_LOCAL_DEPENDENCIES: List[str] = [] +SYSTEM_TEST_DEPENDENCIES: List[str] = [] +SYSTEM_TEST_EXTRAS: List[str] = ["tests"] +SYSTEM_TEST_EXTRAS_BY_PYTHON: Dict[str, List[str]] = {} CURRENT_DIRECTORY = pathlib.Path(__file__).parent.absolute() # Sessions are executed in the order so putting the smaller sessions # ahead to fail fast at presubmit running. +# 'docfx' is excluded since it only needs to run in 'docs-presubmit' nox.options.sessions = [ - # Include unit_noextras to ensure at least some unit tests contribute to - # coverage. - # TODO(tswast): Consider removing this when unit_noextras and cover is run - # from GitHub actions. + "lint", + "lint_setup_py", + "mypy", + "format", + "docs", + "docfx", + "unit", "unit_noextras", - "system-3.12", # No extras. + "system", + "doctest", "cover", - # TODO(b/401609005): remove - "cleanup", ] # Error if a python version is missing @@ -140,46 +102,22 @@ def lint(session): Returns a failure if the linters find linting errors or sufficiently serious code quality issues. """ - session.install(RUFF_VERSION) - - # Check imports + session.install("flake8", BLACK_VERSION) session.run( - "ruff", - "check", - "--select", - "I,F", - f"--target-version=py{ALL_PYTHON[0].replace('.', '')}", - "--line-length=88", # Standard Black line length - *LINT_PATHS, - ) - - # Check formatting - session.run( - "ruff", - "format", + "black", "--check", - f"--target-version=py{ALL_PYTHON[0].replace('.', '')}", - "--line-length=88", *LINT_PATHS, ) + # TODO(tswast): lint all LINT_PATHS + session.run("flake8", "bigframes", "tests") -# Use a python runtime which is available in the owlbot post processor here -# https://github.com/googleapis/synthtool/blob/master/docker/owlbot/python/Dockerfile @nox.session(python=DEFAULT_PYTHON_VERSION) def blacken(session): - """(Deprecated) Legacy session. Please use 'nox -s format'.""" - session.log( - "WARNING: The 'blacken' session is deprecated and will be removed in a future release. Please use 'nox -s format' in the future." - ) - - # Just run the ruff formatter (keeping legacy behavior of only formatting, not sorting imports) - session.install(RUFF_VERSION) + """Run black. Format code to uniform standard.""" + session.install(BLACK_VERSION) session.run( - "ruff", - "format", - f"--target-version=py{ALL_PYTHON[0].replace('.', '')}", - "--line-length=88", + "black", *LINT_PATHS, ) @@ -187,31 +125,18 @@ def blacken(session): @nox.session(python=DEFAULT_PYTHON_VERSION) def format(session): """ - Run ruff to sort imports and format code. + Run isort to sort imports. Then run black + to format code to uniform standard. """ - # 1. Install ruff (skipped automatically if you run with --no-venv) - session.install(RUFF_VERSION) - - # 2. Run Ruff to fix imports - # check --select I: Enables strict import sorting - # --fix: Applies the changes automatically + session.install(BLACK_VERSION, ISORT_VERSION) + # Use the --fss option to sort imports using strict alphabetical order. + # See https://pycqa.github.io/isort/docs/configuration/options.html#force-sort-within-sections session.run( - "ruff", - "check", - "--select", - "I,F", - "--fix", - f"--target-version=py{ALL_PYTHON[0].replace('.', '')}", - "--line-length=88", # Standard Black line length + "isort", *LINT_PATHS, ) - - # 3. Run Ruff to format code session.run( - "ruff", - "format", - f"--target-version=py{ALL_PYTHON[0].replace('.', '')}", - "--line-length=88", # Standard Black line length + "black", *LINT_PATHS, ) @@ -219,33 +144,36 @@ def format(session): @nox.session(python=DEFAULT_PYTHON_VERSION) def lint_setup_py(session): """Verify that setup.py is valid (including RST check).""" - session.install("docutils", "pygments", "setuptools") + session.install("docutils", "pygments") session.run("python", "setup.py", "check", "--restructuredtext", "--strict") - session.install("twine", "wheel") - shutil.rmtree("build", ignore_errors=True) - shutil.rmtree("dist", ignore_errors=True) - session.run("python", "setup.py", "sdist") - session.run( - "python", "-m", "twine", "check", *pathlib.Path("dist").glob("*.tar.gz") - ) - def install_unittest_dependencies(session, install_test_extra, *constraints): - extras = [] - if install_test_extra: - if session.python in UNIT_TEST_EXTRAS_BY_PYTHON: - extras = UNIT_TEST_EXTRAS_BY_PYTHON[session.python] - else: - extras = UNIT_TEST_EXTRAS + standard_deps = UNIT_TEST_STANDARD_DEPENDENCIES + UNIT_TEST_DEPENDENCIES + session.install(*standard_deps, *constraints) + + if UNIT_TEST_EXTERNAL_DEPENDENCIES: + warnings.warn( + "'unit_test_external_dependencies' is deprecated. Instead, please " + "use 'unit_test_dependencies' or 'unit_test_local_dependencies'.", + DeprecationWarning, + ) + session.install(*UNIT_TEST_EXTERNAL_DEPENDENCIES, *constraints) - session.install( - *UNIT_TEST_STANDARD_DEPENDENCIES, - *UNIT_TEST_DEPENDENCIES, - "-e", - f".[{','.join(extras)}]" if extras else ".", - *constraints, - ) + if UNIT_TEST_LOCAL_DEPENDENCIES: + session.install(*UNIT_TEST_LOCAL_DEPENDENCIES, *constraints) + + if install_test_extra and UNIT_TEST_EXTRAS_BY_PYTHON: + extras = UNIT_TEST_EXTRAS_BY_PYTHON.get(session.python, []) + elif install_test_extra and UNIT_TEST_EXTRAS: + extras = UNIT_TEST_EXTRAS + else: + extras = [] + + if extras: + session.install("-e", f".[{','.join(extras)}]", *constraints) + else: + session.install("-e", ".", *constraints) def run_unit(session, install_test_extra): @@ -256,16 +184,11 @@ def run_unit(session, install_test_extra): install_unittest_dependencies(session, install_test_extra, "-c", constraints_path) # Run py.test against the unit tests. - scripts_path = "scripts" tests_path = os.path.join("tests", "unit") third_party_tests_path = os.path.join("third_party", "bigframes_vendored") session.run( "py.test", "--quiet", - # Any individual test taking longer than 1 mins will be terminated. - "--timeout=60", - # Log 20 slowest tests - "--durations=20", f"--junitxml=unit_{session.python}_sponge_log.xml", "--cov=bigframes", f"--cov={tests_path}", @@ -275,30 +198,71 @@ def run_unit(session, install_test_extra): "--cov-fail-under=0", tests_path, third_party_tests_path, - scripts_path, *session.posargs, ) -@nox.session(python=ALL_PYTHON) -@nox.parametrize("test_extra", [True, False]) -def unit(session, test_extra): - if session.python == "3.15": - session.skip( - "Skipping 3.15 until wheels are available for pyarrow. Also pyproj wheels are needed for dependency geopandas." - ) - if test_extra: - run_unit(session, install_test_extra=test_extra) - else: - unit_noextras(session) +@nox.session(python=UNIT_TEST_PYTHON_VERSIONS) +def unit(session): + run_unit(session, install_test_extra=True) -@nox.session(python=ALL_PYTHON[-1]) +@nox.session(python=UNIT_TEST_PYTHON_VERSIONS[-1]) def unit_noextras(session): run_unit(session, install_test_extra=False) +@nox.session(python=DEFAULT_PYTHON_VERSION) +def mypy(session): + """Run type checks with mypy.""" + session.install("-e", ".") + + # Just install the dependencies' type info directly, since "mypy --install-types" + # might require an additional pass. + deps = ( + set( + [ + "mypy", + "pandas-stubs", + "types-protobuf", + "types-python-dateutil", + "types-requests", + "types-setuptools", + ] + ) + | set(SYSTEM_TEST_STANDARD_DEPENDENCIES) + | set(UNIT_TEST_STANDARD_DEPENDENCIES) + ) + + session.install(*deps) + shutil.rmtree(".mypy_cache", ignore_errors=True) + session.run( + "mypy", + "bigframes", + os.path.join("tests", "system"), + os.path.join("tests", "unit"), + "--explicit-package-bases", + '--exclude="^third_party"', + ) + + def install_systemtest_dependencies(session, install_test_extra, *constraints): + # Use pre-release gRPC for system tests. + # Exclude version 1.49.0rc1 which has a known issue. + # See https://github.com/grpc/grpc/pull/30642 + session.install("--pre", "grpcio!=1.49.0rc1") + + session.install(*SYSTEM_TEST_STANDARD_DEPENDENCIES, *constraints) + + if SYSTEM_TEST_EXTERNAL_DEPENDENCIES: + session.install(*SYSTEM_TEST_EXTERNAL_DEPENDENCIES, *constraints) + + if SYSTEM_TEST_LOCAL_DEPENDENCIES: + session.install("-e", *SYSTEM_TEST_LOCAL_DEPENDENCIES, *constraints) + + if SYSTEM_TEST_DEPENDENCIES: + session.install("-e", *SYSTEM_TEST_DEPENDENCIES, *constraints) + if install_test_extra and SYSTEM_TEST_EXTRAS_BY_PYTHON: extras = SYSTEM_TEST_EXTRAS_BY_PYTHON.get(session.python, []) elif install_test_extra and SYSTEM_TEST_EXTRAS: @@ -306,19 +270,10 @@ def install_systemtest_dependencies(session, install_test_extra, *constraints): else: extras = [] - # Use pre-release gRPC for system tests. - # Exclude version 1.49.0rc1 which has a known issue. - # See https://github.com/grpc/grpc/pull/30642 - - session.install( - "--pre", - "grpcio!=1.49.0rc1", - *SYSTEM_TEST_STANDARD_DEPENDENCIES, - *SYSTEM_TEST_EXTERNAL_DEPENDENCIES, - "-e", - f".[{','.join(extras)}]" if extras else ".", - *constraints, - ) + if extras: + session.install("-e", f".[{','.join(extras)}]", *constraints) + else: + session.install("-e", ".", *constraints) def run_system( @@ -330,8 +285,6 @@ def run_system( install_test_extra=True, print_duration=False, extra_pytest_options=(), - timeout_seconds=900, - num_workers=20, ): """Run the system test suite.""" constraints_path = str( @@ -347,17 +300,13 @@ def run_system( install_systemtest_dependencies(session, install_test_extra, "-c", constraints_path) - # Print out package versions for debugging. - session.run("python", "-m", "pip", "freeze") - # Run py.test against the system tests. pytest_cmd = [ "py.test", - "-v", - f"-n={num_workers}", - "--dist=worksteal", + "--quiet", + "-n=20", # Any individual test taking longer than 15 mins will be terminated. - f"--timeout={timeout_seconds}", + "--timeout=900", # Log 20 slowest tests "--durations=20", f"--junitxml={prefix_name}_{session.python}_sponge_log.xml", @@ -381,10 +330,14 @@ def run_system( ) pytest_cmd.extend(extra_pytest_options) - session.run(*pytest_cmd, *session.posargs, test_folder, env=ENV_OVERRIDES) + session.run( + *pytest_cmd, + test_folder, + *session.posargs, + ) -@nox.session(python="3.12") +@nox.session(python=SYSTEM_TEST_PYTHON_VERSIONS) def system(session: nox.sessions.Session): """Run the system test suite.""" run_system( @@ -395,7 +348,7 @@ def system(session: nox.sessions.Session): ) -@nox.session(python=DEFAULT_PYTHON_VERSION) +@nox.session(python=SYSTEM_TEST_PYTHON_VERSIONS[-1]) def system_noextras(session: nox.sessions.Session): """Run the system test suite.""" run_system( @@ -406,46 +359,19 @@ def system_noextras(session: nox.sessions.Session): ) -@nox.session(python="3.12") +@nox.session(python=SYSTEM_TEST_PYTHON_VERSIONS[-1]) def doctest(session: nox.sessions.Session): """Run the system test suite.""" - run_system( session=session, prefix_name="doctest", - extra_pytest_options=( - "--doctest-modules", - "third_party", - "--ignore", - "third_party/bigframes_vendored/ibis", - "--ignore", - "third_party/bigframes_vendored/sqlglot", - "--ignore", - "bigframes/core/compile/polars", - "--ignore", - "bigframes/testing", - "--ignore", - "bigframes/display/anywidget.py", - "--ignore", - "bigframes/bigquery/_operations/ai.py", - "--ignore", - "bigframes/bigquery/ai.py", - "--ignore", - "bigframes/ml", - "--ignore", - "bigframes/operations/ai.py", - "--ignore", - "bigframes/operations/semantics.py", - "--ignore", - "third_party/bigframes_vendored/sklearn", - ), + extra_pytest_options=("--doctest-modules", "third_party"), test_folder="bigframes", check_cov=True, - num_workers=5, ) -@nox.session(python=DEFAULT_PYTHON_VERSION) +@nox.session(python=SYSTEM_TEST_PYTHON_VERSIONS[-1]) def e2e(session: nox.sessions.Session): """Run the large tests in system test suite.""" run_system( @@ -456,15 +382,23 @@ def e2e(session: nox.sessions.Session): ) -@nox.session(python=SYSTEM_TEST_PYTHON_VERSIONS[-1]) -def load(session: nox.sessions.Session): - """Run the very large tests in system test suite.""" - run_system( - session=session, - prefix_name="load", - test_folder=os.path.join("tests", "system", "load"), - print_duration=True, - timeout_seconds=60 * 60 * 12, +@nox.session(python=SYSTEM_TEST_PYTHON_VERSIONS) +def samples(session): + """Run the samples test suite.""" + + constraints_path = str( + CURRENT_DIRECTORY / "testing" / f"constraints-{session.python}.txt" + ) + + # TODO(swast): Use `requirements.txt` files from the samples directories to + # test samples. + install_test_extra = True + install_systemtest_dependencies(session, install_test_extra, "-c", constraints_path) + + session.run( + "py.test", + "samples", + *session.posargs, ) @@ -475,65 +409,34 @@ def cover(session): This outputs the coverage report aggregating coverage from the test runs (including system test runs), and then erases coverage data. """ - # TODO: Remove this skip when the issue is resolved. - # https://github.com/googleapis/google-cloud-python/issues/16635 - session.skip("Temporarily skip coverage session") - session.install("coverage", "pytest-cov") + session.run("coverage", "report", "--show-missing", "--fail-under=90") - # Create a coverage report that includes only the product code. - omitted_paths = [ - # non-prod, unit tested - "bigframes/core/compile/polars/*", - # untested - "bigframes/streaming/*", - # utils - "bigframes/testing/*", - ] - + # Make sure there is no dead code in our test directories. + # TODO(swast): Cleanup dead code in the system tests directory. session.run( "coverage", "report", - "--include=bigframes/*", - # Only unit tested - f"--omit={','.join(omitted_paths)}", "--show-missing", - "--fail-under=84", - ) - - # Make sure there is no dead code in our system test directories. - session.run( - "coverage", - "report", - "--show-missing", - "--include=tests/system/small/*", - # Some tests only run under old pandas, some only under new pandas version - "--fail-under=98", + "--include=tests/unit/*", + "--fail-under=100", ) session.run("coverage", "erase") -@nox.session(python="3.10") +@nox.session(python=DEFAULT_PYTHON_VERSION) def docs(session): """Build the docs for this library.""" - session.install("-e", ".[scikit-learn]") + + session.install("-e", ".") session.install( - "sphinx", - "sphinx-sitemap", - "myst-parser", - "myst-nb", - "pydata-sphinx-theme", + SPHINX_VERSION, + "alabaster", + "recommonmark", ) shutil.rmtree(os.path.join("docs", "_build"), ignore_errors=True) - session.run("python", "-m", "pip", "freeze") - - session.run( - "python", - "scripts/publish_api_coverage.py", - "docs", - ) session.run( "sphinx-build", "-W", # warnings as errors @@ -548,28 +451,19 @@ def docs(session): ) -@nox.session(python="3.10") +@nox.session(python=DEFAULT_PYTHON_VERSION) def docfx(session): """Build the docfx yaml files for this library.""" - session.install("-e", ".[scikit-learn]") + session.install("-e", ".") session.install( SPHINX_VERSION, - "sphinx-sitemap==2.9.0", - "pydata-sphinx-theme==0.13.3", - "myst-parser==0.18.1", - "myst-nb", - "gcp-sphinx-docfx-yaml==3.2.4", - "anywidget", + "alabaster", + "recommonmark", + "gcp-sphinx-docfx-yaml", ) shutil.rmtree(os.path.join("docs", "_build"), ignore_errors=True) - - session.run( - "python", - "scripts/publish_api_coverage.py", - "docs", - ) session.run( "sphinx-build", "-T", # show full traceback on exception @@ -584,7 +478,7 @@ def docfx(session): "sphinx.ext.napoleon," "sphinx.ext.todo," "sphinx.ext.viewcode," - "myst_parser" + "recommonmark" ), "-b", "html", @@ -595,41 +489,74 @@ def docfx(session): ) -def prerelease(session: nox.sessions.Session, tests_path, extra_pytest_options=()): +def prerelease(session: nox.sessions.Session, tests_path): constraints_path = str( CURRENT_DIRECTORY / "testing" / f"constraints-{session.python}.txt" ) - session.install( - *set(UNIT_TEST_STANDARD_DEPENDENCIES + SYSTEM_TEST_STANDARD_DEPENDENCIES), - "-c", - constraints_path, - "-e", - ".", - ) # PyArrow prerelease packages are published to an alternative PyPI host. # https://arrow.apache.org/docs/python/install.html#installing-nightly-packages session.install( - "--no-deps", - "--upgrade", "--extra-index-url", "https://pypi.fury.io/arrow-nightlies/", + "--prefer-binary", + "--pre", + "--upgrade", "pyarrow", - # We exclude each version individually so that we can continue to test - # some prerelease packages. See: - # https://github.com/googleapis/google-cloud-python/pull/268#discussion_r1423205172 - # "pandas!=2.1.4, !=2.2.0rc0, !=2.2.0, !=2.2.1", + ) + session.install( + "--extra-index-url", + "https://pypi.anaconda.org/scipy-wheels-nightly/simple", + "--prefer-binary", + "--pre", + "--upgrade", "pandas", - # Workaround https://github.com/googleapis/python-db-dtypes-pandas/issues/178 - "db-dtypes", - # Ensure we catch breaking changes in the client libraries early. - "git+https://github.com/googleapis/google-cloud-python.git#egg=google-cloud-bigquery&subdirectory=packages/google-cloud-bigquery", + ) + session.install( "--upgrade", - "-e", - "git+https://github.com/googleapis/google-cloud-python.git#egg=google-cloud-bigquery-storage&subdirectory=packages/google-cloud-bigquery-storage", - "git+https://github.com/googleapis/google-cloud-python.git#egg=pandas-gbq&subdirectory=packages/pandas-gbq", + "-e", # Use -e so that py.typed file is included. + "git+https://github.com/ibis-project/ibis.git#egg=ibis-framework", + ) + # Workaround https://github.com/googleapis/python-db-dtypes-pandas/issues/178 + session.install("--no-deps", "db-dtypes") + + # Workaround to install pandas-gbq >=0.15.0, which is required by test only. + session.install("--no-deps", "pandas-gbq") + + session.install( + *set(UNIT_TEST_STANDARD_DEPENDENCIES + SYSTEM_TEST_STANDARD_DEPENDENCIES), + "-c", + constraints_path, ) + # Because we test minimum dependency versions on the minimum Python + # version, the first version we test with in the unit tests sessions has a + # constraints file containing all dependencies and extras. + with open( + CURRENT_DIRECTORY + / "testing" + / f"constraints-{UNIT_TEST_PYTHON_VERSIONS[0]}.txt", + encoding="utf-8", + ) as constraints_file: + constraints_text = constraints_file.read() + + # Ignore leading whitespace and comment lines. + already_installed = frozenset( + ("db-dtypes", "pandas", "pyarrow", "ibis-framework", "pandas-gbq") + ) + deps = [ + match.group(1) + for match in re.finditer( + r"^\s*(\S+)(?===\S+)", constraints_text, flags=re.MULTILINE + ) + if match.group(1) not in already_installed + ] + + # We use --no-deps to ensure that pre-release versions aren't overwritten + # by the version ranges in setup.py. + session.install(*deps) + session.install("--no-deps", "-e", ".") + # Print out prerelease package versions. session.run("python", "-m", "pip", "freeze") @@ -648,13 +575,11 @@ def prerelease(session: nox.sessions.Session, tests_path, extra_pytest_options=( "--cov-report=term-missing", "--cov-fail-under=0", tests_path, - *extra_pytest_options, *session.posargs, - env=ENV_OVERRIDES, ) -@nox.session(python=ALL_PYTHON[-1]) +@nox.session(python=UNIT_TEST_PYTHON_VERSIONS[-1]) def unit_prerelease(session: nox.sessions.Session): """Run the unit test suite with prerelease dependencies.""" prerelease(session, os.path.join("tests", "unit")) @@ -663,101 +588,52 @@ def unit_prerelease(session: nox.sessions.Session): @nox.session(python=SYSTEM_TEST_PYTHON_VERSIONS[-1]) def system_prerelease(session: nox.sessions.Session): """Run the system test suite with prerelease dependencies.""" - small_tests_dir = os.path.join("tests", "system", "small") - - # Let's exclude remote function tests from the prerelease tests, since the - # some of the package dependencies propagate to the cloud run functions' - # requirements.txt, and the prerelease package versions may not be available - # in the standard pip install. - # This would mean that we will only rely on the standard remote function - # tests. - small_remote_function_tests = os.path.join( - small_tests_dir, "functions", "test_remote_function.py" - ) - assert os.path.exists(small_remote_function_tests) + prerelease(session, os.path.join("tests", "system", "small")) - prerelease( - session, - os.path.join("tests", "system", "small"), - (f"--ignore={small_remote_function_tests}",), - ) - - -@nox.session(python=COLAB_AND_BQ_STUDIO_PYTHON_VERSIONS) -def notebook(session: nox.Session): - google_cloud_project = PROJECT_ID_OVERRIDE or os.getenv("GOOGLE_CLOUD_PROJECT") - if not google_cloud_project: - session.error( - "Set GOOGLE_CLOUD_PROJECT environment variable to run notebook session." - ) +@nox.session(python=SYSTEM_TEST_PYTHON_VERSIONS) +def notebook(session): session.install("-e", ".[all]") - session.install( - "pytest", - "pytest-xdist", - "pytest-retry", - "nbmake", - "google-cloud-aiplatform", - "matplotlib", - "seaborn", - "anywidget", - ) + session.install("pytest", "pytest-xdist", "pytest-retry", "nbmake") + + notebooks_list = list(Path("notebooks/").glob("*/*.ipynb")) - notebooks_list = list(pathlib.Path("notebooks/").glob("*/*.ipynb")) denylist = [ # Regionalized testing is manually added later. "notebooks/location/regionalized.ipynb", # These notebooks contain special colab `param {type:"string"}` # comments, which make it easy for customers to fill in their # own information. - # - # With the notebooks_fill_params.py script, we are able to find and - # replace the PROJECT_ID parameter, but not the others. - # - # TODO(b/357904266): Test these notebooks by replacing parameters with + # TODO(ashleyxu): Test these notebooks by replacing parameters with # appropriate values and omitting cleanup logic that may break # our test infrastructure. - "notebooks/getting_started/ml_fundamentals_bq_dataframes.ipynb", # Needs DATASET. - "notebooks/ml/bq_dataframes_ml_linear_regression.ipynb", # Needs DATASET_ID. - "notebooks/ml/bq_dataframes_ml_linear_regression_big.ipynb", # Needs DATASET_ID. - "notebooks/generative_ai/bq_dataframes_ml_drug_name_generation.ipynb", # Needs CONNECTION. - # TODO(b/332737009): investigate why we get 404 errors, even though - # bq_dataframes_llm_code_generation creates a bucket in the sample. - "notebooks/generative_ai/bq_dataframes_llm_code_generation.ipynb", # Needs BUCKET_URI. - "notebooks/generative_ai/sentiment_analysis.ipynb", # Too slow - "notebooks/generative_ai/bq_dataframes_llm_vector_search.ipynb", # Limited quota for vector index ddl statements on table. - "notebooks/generative_ai/bq_dataframes_ml_drug_name_generation.ipynb", # Needs CONNECTION. - "notebooks/generative_ai/ai_movie_poster.ipynb", # Needs CONNECTION. - # TODO(b/366290533): to protect BQML quota - "notebooks/vertex_sdk/sdk2_bigframes_pytorch.ipynb", # Needs BUCKET_URI. - "notebooks/vertex_sdk/sdk2_bigframes_sklearn.ipynb", # Needs BUCKET_URI. - "notebooks/vertex_sdk/sdk2_bigframes_tensorflow.ipynb", # Needs BUCKET_URI. + "notebooks/getting_started/getting_started_bq_dataframes.ipynb", + "notebooks/generative_ai/bq_dataframes_llm_code_generation.ipynb", + "notebooks/regression/bq_dataframes_ml_linear_regression.ipynb", + "notebooks/generative_ai/bq_dataframes_ml_drug_name_generation.ipynb", + "notebooks/vertex_sdk/sdk2_bigframes_pytorch.ipynb", + "notebooks/vertex_sdk/sdk2_bigframes_sklearn.ipynb", + "notebooks/vertex_sdk/sdk2_bigframes_tensorflow.ipynb", # The experimental notebooks imagine features that don't yet # exist or only exist as temporary prototypes. - "notebooks/experimental/ai_operators.ipynb", - "notebooks/experimental/semantic_operators.ipynb", - # The notebooks that are added for more use cases, such as backing a - # blog post, which may take longer to execute and need not be - # 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", - # Needs a connection - "notebooks/remote_functions/remote_function_vertex_claude_model.ipynb", + "notebooks/experimental/longer_ml_demo.ipynb", ] - # Convert each Path notebook object to a string using a list comprehension, - # and remove tests that we choose not to test. + # Convert each Path notebook object to a string using a list comprehension. notebooks = [str(nb) for nb in notebooks_list] - notebooks = [nb for nb in notebooks if nb not in denylist and "/kaggle/" not in nb] + + # Remove tests that we choose not to test. + notebooks = list(filter(lambda nb: nb not in denylist, notebooks)) # Regionalized notebooks notebooks_reg = { "regionalized.ipynb": [ "asia-southeast1", + "eu", + "europe-west4", + "southamerica-west1", "us", + "us-central1", ] } notebooks_reg = { @@ -765,164 +641,49 @@ def notebook(session: nox.Session): for nb, regions in notebooks_reg.items() } - # The pytest --nbmake exits silently with "no tests ran" message if + # For some reason nbmake exits silently with "no tests ran" message if # one of the notebook paths supplied does not exist. Let's make sure that - # each path exists. + # each path exists for nb in notebooks + list(notebooks_reg): assert os.path.exists(nb), nb - # Determine whether to enable multi-process mode based on the environment - # variable. If BENCHMARK_AND_PUBLISH is "true", it indicates we're running - # a benchmark, so we disable multi-process mode. If BENCHMARK_AND_PUBLISH - # is "false", we enable multi-process mode for faster execution. - multi_process_mode = os.getenv("BENCHMARK_AND_PUBLISH", "false") == "false" - - try: - # Populate notebook parameters and make a backup so that the notebooks - # are runnable. - session.run( - "python", - CURRENT_DIRECTORY / "scripts" / "notebooks_fill_params.py", - *notebooks, - env=ENV_OVERRIDES, - ) + # TODO(shobs): For some reason --retries arg masks exceptions occurred in + # notebook failures, and shows unhelpful INTERNALERROR. Investigate that + # and enable retries if we can find a way to surface the real exception + # bacause the notebook is running against real GCP and something may fail + # due to transient issues. + pytest_command = [ + "py.test", + "--nbmake", + "--nbmake-timeout=600", + ] - processes = [] - for notebook in notebooks: - args = ( - "python", - "scripts/run_and_publish_benchmark.py", - "--notebook", - f"--benchmark-path={notebook}", + # Run self-contained notebooks in single session.run + # achieve parallelization via -n + session.run( + *pytest_command, + "-nauto", + *notebooks, + ) + + # Run regionalized notebooks in parallel session.run's, since each notebook + # takes a different region via env param. + processes = [] + for notebook, regions in notebooks_reg.items(): + for region in regions: + process = Process( + target=session.run, + args=(*pytest_command, notebook), + kwargs={"env": {"BIGQUERY_LOCATION": region}}, ) - if multi_process_mode: - process = multiprocessing.Process( - target=session.run, args=args, kwargs={"env": ENV_OVERRIDES} - ) - process.start() - processes.append(process) - # Adding a small delay between starting each - # process to avoid potential race conditions。 - time.sleep(1) - else: - session.run(*args, env=ENV_OVERRIDES) - - for notebook, regions in notebooks_reg.items(): - for region in regions: - region_args = ( - "python", - "scripts/run_and_publish_benchmark.py", - "--notebook", - f"--benchmark-path={notebook}", - f"--region={region}", - ) - if multi_process_mode: - process = multiprocessing.Process( - target=session.run, - args=region_args, - kwargs={"env": ENV_OVERRIDES}, - ) - process.start() - processes.append(process) - # Adding a small delay between starting each - # process to avoid potential race conditions。 - time.sleep(1) - else: - session.run(*region_args, env=ENV_OVERRIDES) - - for process in processes: - process.join() - finally: - # Prevent our notebook changes from getting checked in to git - # accidentally. - session.run( - "python", - CURRENT_DIRECTORY / "scripts" / "notebooks_restore_from_backup.py", - *notebooks, - ) - session.run( - "python", - "scripts/run_and_publish_benchmark.py", - "--notebook", - "--publish-benchmarks=notebooks/", - env=ENV_OVERRIDES, - ) - + process.start() + processes.append(process) -@nox.session(python=DEFAULT_PYTHON_VERSION) -def benchmark(session: nox.Session): - session.install("-e", ".[all]") - base_path = os.path.join("tests", "benchmark") - - parser = argparse.ArgumentParser() - parser.add_argument( - "-i", - "--iterations", - type=int, - default=1, - help="Number of iterations to run each benchmark.", - ) - parser.add_argument( - "-o", - "--output-csv", - nargs="?", - const=True, - default=False, - help=( - "Determines whether to output results to a CSV file. If no location is provided, " - "a temporary location is automatically generated." - ), - ) - parser.add_argument( - "-b", - "--benchmark-filter", - nargs="+", - help=( - "List of file or directory names to include in the benchmarks. If not provided, " - "all benchmarks are run." - ), - ) + for process in processes: + process.join() - args = parser.parse_args(session.posargs) - - benchmark_script_list: List[pathlib.Path] = [] - if args.benchmark_filter: - for filter_item in args.benchmark_filter: - full_path = os.path.join(base_path, filter_item) - if os.path.isdir(full_path): - benchmark_script_list.extend(pathlib.Path(full_path).rglob("*.py")) - elif os.path.isfile(full_path) and full_path.endswith(".py"): - benchmark_script_list.append(pathlib.Path(full_path)) - else: - raise ValueError( - f"Item {filter_item} does not match any valid file or directory" - ) - else: - benchmark_script_list = list(pathlib.Path(base_path).rglob("*.py")) - - try: - for benchmark in benchmark_script_list: - if benchmark.name in ("__init__.py", "utils.py"): - continue - session.run( - "python", - "scripts/run_and_publish_benchmark.py", - f"--benchmark-path={benchmark}", - f"--iterations={args.iterations}", - env=ENV_OVERRIDES, - ) - finally: - session.run( - "python", - "scripts/run_and_publish_benchmark.py", - f"--publish-benchmarks={base_path}", - f"--iterations={args.iterations}", - f"--output-csv={args.output_csv}", - env=ENV_OVERRIDES, - ) - -@nox.session(python=DEFAULT_PYTHON_VERSION) +@nox.session(python="3.10") def release_dry_run(session): env = {} @@ -934,159 +695,3 @@ def release_dry_run(session): ): env["PROJECT_ROOT"] = "." session.run(".kokoro/release-nightly.sh", "--dry-run", env=env) - - -@nox.session(python=DEFAULT_PYTHON_VERSION) -def cleanup(session): - """Clean up stale and/or temporary resources in the test project.""" - google_cloud_project = PROJECT_ID_OVERRIDE or os.getenv("GOOGLE_CLOUD_PROJECT") - cleanup_options = [] - if google_cloud_project: - cleanup_options.append(f"--project-id={google_cloud_project}") - - # Cleanup a few stale (more than 12 hours old) temporary cloud run - # functions created by bigframems. This will help keeping the test GCP - # project within the "Number of functions" quota - # https://cloud.google.com/functions/quotas#resource_limits - recency_cutoff_hours = 12 - cleanup_count_per_location = 40 - cleanup_options.extend( - [ - f"--recency-cutoff={recency_cutoff_hours}", - "cleanup", - f"--number={cleanup_count_per_location}", - ] - ) - - session.install("-e", ".") - - session.run("python", "scripts/manage_cloud_functions.py", *cleanup_options) - - -@nox.session(python=DEFAULT_PYTHON_VERSION) -@nox.parametrize( - "protobuf_implementation", - ["python", "upb"], -) -def core_deps_from_source(session, protobuf_implementation): - """Run all tests with core dependencies installed from source - rather than pulling the dependencies from PyPI. - """ - - # Install all dependencies - session.install("-e", ".") - - # Install dependencies for the unit test environment - unit_deps_all = UNIT_TEST_STANDARD_DEPENDENCIES + UNIT_TEST_EXTERNAL_DEPENDENCIES - session.install(*unit_deps_all) - - # Install dependencies for the system test environment - system_deps_all = ( - SYSTEM_TEST_STANDARD_DEPENDENCIES - + SYSTEM_TEST_EXTERNAL_DEPENDENCIES - + SYSTEM_TEST_EXTRAS - ) - session.install(*system_deps_all) - - # Because we test minimum dependency versions on the minimum Python - # version, the first version we test with in the unit tests sessions has a - # constraints file containing all dependencies and extras. - with open( - CURRENT_DIRECTORY / "testing" / "constraints-3.10.txt", - encoding="utf-8", - ) as constraints_file: - constraints_text = constraints_file.read() - - # Ignore leading whitespace and comment lines. - # Fiona fails to build on GitHub CI because gdal-config is missing and no Python 3.14 wheels are available. - constraints_deps = [ - match.group(1) - for match in re.finditer( - r"^\s*(\S+)(?===\S+)", constraints_text, flags=re.MULTILINE - ) - if match.group(1) != "fiona" - ] - - # Install dependencies specified in `testing/constraints-X.txt`. - session.install(*constraints_deps) - - # TODO(https://github.com/googleapis/gapic-generator-python/issues/2358): `grpcio` and - # `grpcio-status` should be added to the list below so that they are installed from source, - # rather than PyPI. - # TODO(https://github.com/googleapis/gapic-generator-python/issues/2357): `protobuf` should be - # added to the list below so that it is installed from source, rather than PyPI - # Note: If a dependency is added to the `core_dependencies_from_source` list, - # the `prerel_deps` list in the `prerelease_deps` nox session should also be updated. - core_dependencies_from_source = [ - "googleapis-common-protos @ git+https://github.com/googleapis/google-cloud-python#egg=googleapis-common-protos&subdirectory=packages/googleapis-common-protos", - "google-api-core @ git+https://github.com/googleapis/google-cloud-python#egg=google-api-core&subdirectory=packages/google-api-core", - "google-auth @ git+https://github.com/googleapis/google-cloud-python#egg=google-auth&subdirectory=packages/google-auth", - "grpc-google-iam-v1 @ git+https://github.com/googleapis/google-cloud-python#egg=grpc-google-iam-v1&subdirectory=packages/grpc-google-iam-v1", - "proto-plus @ git+https://github.com/googleapis/google-cloud-python#egg=proto-plus&subdirectory=packages/proto-plus", - ] - - for dep in core_dependencies_from_source: - session.install(dep, "--no-deps", "--ignore-installed") - print(f"Installed {dep}") - - session.run( - "py.test", - "tests/unit", - env={ - "PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION": protobuf_implementation, - }, - ) - - -@nox.session(python=ALL_PYTHON[-1]) -def prerelease_deps(session): - """Run all tests with prerelease versions of dependencies installed.""" - # TODO(https://github.com/googleapis/google-cloud-python/issues/16014): - # Add prerelease deps tests - unit_prerelease(session) - system_prerelease(session) - - -# NOTE: this is based on mypy session that came directly from the bigframes split repo -# the split repo used 3.10, the monorepo uses 3.14 -@nox.session(python="3.14") -def mypy(session): - """Run type checks with mypy.""" - # Editable mode is not compatible with mypy when there are multiple - # package directories. See: - # https://github.com/python/mypy/issues/10564#issuecomment-851687749 - session.install("--no-cache-dir", ".") - - # Just install the dependencies' type info directly, since "mypy --install-types" - # might require an additional pass. - deps = ( - set( - [ - MYPY_VERSION, - # TODO: update to latest pandas-stubs once we resolve bigframes issues. - "pandas-stubs<=2.2.3.241126", - "types-protobuf", - "types-python-dateutil", - "types-requests", - "types-setuptools", - "types-tabulate", - "types-PyYAML", - "polars", - "anywidget", - ] - ) - | set(SYSTEM_TEST_STANDARD_DEPENDENCIES) - | set(UNIT_TEST_STANDARD_DEPENDENCIES) - ) - - session.install(*deps) - shutil.rmtree(".mypy_cache", ignore_errors=True) - session.run( - "mypy", - "bigframes", - os.path.join("tests", "system"), - os.path.join("tests", "unit"), - "--check-untyped-defs", - "--explicit-package-bases", - '--exclude="^third_party"', - ) diff --git a/owlbot.py b/owlbot.py new file mode 100644 index 00000000000..be30eea5c2c --- /dev/null +++ b/owlbot.py @@ -0,0 +1,114 @@ +# Copyright 2021 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""This script is used to synthesize generated parts of this library.""" + +import pathlib +import re + +from synthtool import gcp +import synthtool as s +from synthtool.languages import python + +REPO_ROOT = pathlib.Path(__file__).parent.absolute() + +common = gcp.CommonTemplates() + +# ---------------------------------------------------------------------------- +# Add templated files +# ---------------------------------------------------------------------------- +templated_files = common.py_library( + unit_test_python_versions=["3.9", "3.10", "3.11"], + system_test_python_versions=["3.9", "3.11"], + cov_level=35, + intersphinx_dependencies={ + "pandas": "https://pandas.pydata.org/pandas-docs/stable/", + "pydata-google-auth": "https://pydata-google-auth.readthedocs.io/en/latest/", + }, +) +s.move( + templated_files, + excludes=[ + # Multi-processing note isn't relevant, as bigframes is responsible for + # creating clients, not the end user. + "docs/multiprocessing.rst", + "noxfile.py", + ".pre-commit-config.yaml", + "README.rst", + ".github/release-trigger.yml", + # BigQuery DataFrames manages its own Kokoro cluster for presubmit & continuous tests. + ".kokoro/build.sh", + ".kokoro/continuous/common.cfg", + ".kokoro/presubmit/common.cfg", + ], +) + +# ---------------------------------------------------------------------------- +# Fixup files +# ---------------------------------------------------------------------------- + +# Make sure build includes all necessary files. +s.replace( + ["MANIFEST.in"], + re.escape("recursive-include google"), + "recursive-include third_party *\nrecursive-include bigframes", +) + +# Even though BigQuery DataFrames isn't technically a client library, we are +# opting into Cloud RAD for docs hosting. +s.replace( + [".kokoro/docs/common.cfg"], + re.escape('value: "docs-staging-v2-staging"'), + 'value: "docs-staging-v2"', +) + +# Use a custom table of contents since the default one isn't organized well +# enough for the number of classes we have. +s.replace( + [".kokoro/publish-docs.sh"], + ( + re.escape("# upload docs") + + "\n" + + re.escape( + 'python3 -m docuploader upload docs/_build/html/docfx_yaml --metadata-file docs.metadata --destination-prefix docfx --staging-bucket "${V2_STAGING_BUCKET}"' + ) + ), + ( + "# Replace toc.yml template file\n" + + "mv docs/templates/toc.yml docs/_build/html/docfx_yaml/toc.yml\n\n" + + "# upload docs\n" + + 'python3 -m docuploader upload docs/_build/html/docfx_yaml --metadata-file docs.metadata --destination-prefix docfx --staging-bucket "${V2_STAGING_BUCKET}"' + ), +) + +# Fixup the documentation. +s.replace( + ["docs/conf.py"], + re.escape("Google Cloud Client Libraries for bigframes"), + "BigQuery DataFrames provides DataFrame APIs on the BigQuery engine", +) + +# ---------------------------------------------------------------------------- +# Samples templates +# ---------------------------------------------------------------------------- + +python.py_samples(skip_readmes=True) + +# ---------------------------------------------------------------------------- +# Final cleanup +# ---------------------------------------------------------------------------- + +s.shell.run(["nox", "-s", "format"], hide_output=False) +for noxfile in REPO_ROOT.glob("samples/**/noxfile.py"): + s.shell.run(["nox", "-s", "blacken"], cwd=noxfile.parent, hide_output=False) diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index 064bdaf362d..00000000000 --- a/package-lock.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "python-bigquery-dataframes", - "lockfileVersion": 3, - "requires": true, - "packages": {} -} diff --git a/pyproject.toml b/pyproject.toml deleted file mode 100644 index e7d9c326a93..00000000000 --- a/pyproject.toml +++ /dev/null @@ -1,6 +0,0 @@ -[build-system] -requires = ["setuptools"] -build-backend = "setuptools.build_meta" - -[tool.ruff.lint.isort] -known-first-party = ["bigframes"] diff --git a/pytest.ini b/pytest.ini index 512fd81a7e6..204c743bbfa 100644 --- a/pytest.ini +++ b/pytest.ini @@ -1,3 +1,4 @@ [pytest] doctest_optionflags = NORMALIZE_WHITESPACE -addopts = "--import-mode=importlib" +filterwarnings = + ignore::pandas.errors.SettingWithCopyWarning diff --git a/release-procedure.md b/release-procedure.md deleted file mode 100644 index aeb87862fe6..00000000000 --- a/release-procedure.md +++ /dev/null @@ -1,51 +0,0 @@ -# BigQuery DataFrames (bigframes) release procedure - -*(Note: bigframes releases are marked with `skip_release: true` in `librarian.yaml` and must be kicked off manually using legacylibrarian.)* - -## Setup (First Time Only) - -* Install `legacylibrarian`: - - go install github.com/googleapis/librarian/cmd/legacylibrarian@latest - -* Authenticate with GitHub CLI: - - gh auth login - -## Release Steps - -* Obtain GitHub token: - - export LIBRARIAN_GITHUB_TOKEN=$(gh auth token) - -* Stash changes (repo must be clean): - - git stash -u - -* Fetch and checkout base: - - git fetch origin main - git fetch origin --tags - git checkout origin/main - -* Check image updates: - - legacylibrarian update-image --push - -* Create release PR: - - # Option A: Push directly - legacylibrarian release stage --repo=https://github.com/googleapis/google-cloud-python --library=bigframes --library-version=X.X.X --push - - # Option B: Manual edit first (omit --push, edit files in /tmp/librarian-*, commit/push from there) - legacylibrarian release stage --repo=https://github.com/googleapis/google-cloud-python --library=bigframes --library-version=X.X.X - # In /tmp repository: - git commit -a -m "chore: create release" --no-verify # keep librarian config pristine - git push origin HEAD - gh pr create --fill --label "release:pending" - -* Post-release restore: - - # Move back any stashed/relocated files (like .vscode) - git checkout main - git stash pop diff --git a/renovate.json b/renovate.json new file mode 100644 index 00000000000..39b2a0ec929 --- /dev/null +++ b/renovate.json @@ -0,0 +1,12 @@ +{ + "extends": [ + "config:base", + "group:all", + ":preserveSemverRanges", + ":disableDependencyDashboard" + ], + "ignorePaths": [".pre-commit-config.yaml", ".kokoro/requirements.txt", "setup.py"], + "pip_requirements": { + "fileMatch": ["requirements-test.txt", "samples/[\\S/]*constraints.txt", "samples/[\\S/]*constraints-test.txt"] + } +} diff --git a/samples/dbt/.dbt.yml b/samples/dbt/.dbt.yml deleted file mode 100644 index a4301a0bab1..00000000000 --- a/samples/dbt/.dbt.yml +++ /dev/null @@ -1,27 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -dbt_sample_project: - outputs: - dev: # The target environment name (e.g., dev, prod) - compute_region: us-central1 # Region used for compute operations - dataset: dbt_sample_dateset # BigQuery dataset where dbt will create models - gcs_bucket: dbt_sample_bucket # GCS bucket to store output files - location: US # BigQuery dataset location - method: oauth # Authentication method - priority: interactive # Job priority: "interactive" or "batch" - project: bigframes-dev # GCP project ID - threads: 1 # Number of threads dbt can use for running models in parallel - type: bigquery # Specifies the dbt adapter - target: dev # The default target environment diff --git a/samples/dbt/README.md b/samples/dbt/README.md deleted file mode 100644 index 986aa2eae32..00000000000 --- a/samples/dbt/README.md +++ /dev/null @@ -1,64 +0,0 @@ -# dbt BigFrames Integration - -This repository provides simple examples of using **dbt Python models** with **BigQuery** in **BigFrames** mode. - -It includes basic configurations and sample models to help you get started quickly in a typical dbt project. - -## Highlights - -- `profiles.yml`: configures your connection to BigQuery. -- `dbt_project.yml`: configures your dbt project - **dbt_sample_project**. -- `dbt_bigframes_code_sample_1.py`: An example to read BigQuery data and perform basic transformation. -- `dbt_bigframes_code_sample_2.py`: An example to build an incremental model that leverages BigFrames UDF capabilities. -- `prepare_table.py`: An ML example to consolidate various data sources into a single, unified table for later usage. -- `prediction.py`: An ML example to train models and then generate predictions using the prepared table. - -## Requirements - -Before using this project, ensure you have: - -- A [Google Cloud account](https://cloud.google.com/free?hl=en) -- A [dbt Cloud account](https://www.getdbt.com/signup) (if using dbt Cloud) -- Python and SQL basics -- Familiarity with dbt concepts and structure - -For more, see: -- https://docs.getdbt.com/guides/dbt-python-bigframes -- https://cloud.google.com/bigquery/docs/dataframes-dbt - -## Run Locally - -Follow these steps to run the Python models using dbt Core. - -1. **Install the dbt BigQuery adapter:** - - ```bash - pip install dbt-bigquery - ``` - -2. **Initialize a dbt project (if not already done):** - - ```bash - dbt init - ``` - - Follow the prompts to complete setup. - -3. **Finish the configuration and add sample code:** - - - Edit `~/.dbt/profiles.yml` to finish the configuration. - - Replace or add code samples in `.../models/example`. - -4. **Run your dbt models:** - - To run all models: - - ```bash - dbt run - ``` - - Or run a specific model: - - ```bash - dbt run --select your_model_name - ``` \ No newline at end of file diff --git a/samples/dbt/dbt_sample_project/dbt_project.yml b/samples/dbt/dbt_sample_project/dbt_project.yml deleted file mode 100644 index 789f4d25496..00000000000 --- a/samples/dbt/dbt_sample_project/dbt_project.yml +++ /dev/null @@ -1,52 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Name your project! Project names should contain only lowercase characters -# and underscores. A good package name should reflect your organization's -# name or the intended use of these models -name: 'dbt_sample_project' -version: '1.0.0' - -# This setting configures which "profile" dbt uses for this project. -profile: 'dbt_sample_project' - -# These configurations specify where dbt should look for different types of files. -# The `model-paths` config, for example, states that models in this project can be -# found in the "models/" directory. You probably won't need to change these! -model-paths: ["models"] -analysis-paths: ["analyses"] -test-paths: ["tests"] -seed-paths: ["seeds"] -macro-paths: ["macros"] -snapshot-paths: ["snapshots"] - -clean-targets: # directories to be removed by `dbt clean` - - "target" - - "dbt_packages" - - -# Configuring models -# Full documentation: https://docs.getdbt.com/docs/configuring-models - -# In this example config, we tell dbt to build all models in the example/ -# directory as views. These settings can be overridden in the individual model -# files using the `{{ config(...) }}` macro. -models: - dbt_sample_project: - # Optional: These settings (e.g., submission_method, notebook_template_id, - # etc.) can also be defined directly in the Python model using dbt.config. - submission_method: bigframes - # Config indicated by + and applies to all files under models/example/ - example: - +materialized: view diff --git a/samples/dbt/dbt_sample_project/models/example/dbt_bigframes_code_sample_1.py b/samples/dbt/dbt_sample_project/models/example/dbt_bigframes_code_sample_1.py deleted file mode 100644 index 2e24596b794..00000000000 --- a/samples/dbt/dbt_sample_project/models/example/dbt_bigframes_code_sample_1.py +++ /dev/null @@ -1,80 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# This example demonstrates one of the most general usages of transforming raw -# BigQuery data into a processed table using a dbt Python model with BigFrames. -# See more from: https://cloud.google.com/bigquery/docs/dataframes-dbt. -# -# Key defaults when using BigFrames in a dbt Python model for BigQuery: -# - The default materialization is 'table' unless specified otherwise. This -# means dbt will create a new BigQuery table from the result of this model. -# - The default timeout for the job is 3600 seconds (60 minutes). This can be -# adjusted if your processing requires more time. -# - If no runtime template is provided, dbt will automatically create and reuse -# a default one for executing the Python code in BigQuery. -# -# BigFrames provides a pandas-like API for BigQuery data, enabling familiar -# data manipulation directly within your dbt project. This code sample -# illustrates a basic pattern for: -# 1. Reading data from an existing BigQuery dataset. -# 2. Processing it using pandas-like DataFrame operations powered by BigFrames. -# 3. Outputting a cleaned and transformed table, managed by dbt. - - -def model(dbt, session): - # Optional: Override settings from your dbt_project.yml file. - # When both are set, dbt.config takes precedence over dbt_project.yml. - # - # Use `dbt.config(submission_method="bigframes")` to tell dbt to execute - # this Python model using BigQuery DataFrames (BigFrames). This allows you - # to write pandas-like code that operates directly on BigQuery data - # without needing to pull all data into memory. - dbt.config(submission_method="bigframes") - - # Define the BigQuery table path from which to read data. - table = "bigquery-public-data.epa_historical_air_quality.temperature_hourly_summary" - - # Define the specific columns to select from the BigQuery table. - columns = [ - "state_name", - "county_name", - "date_local", - "time_local", - "sample_measurement", - ] - - # Read data from the specified BigQuery table into a BigFrames DataFrame. - df = session.read_gbq(table, columns=columns) - - # Sort the DataFrame by the specified columns. This prepares the data for - # `drop_duplicates` to ensure consistent duplicate removal. - df = df.sort_values(columns).drop_duplicates(columns) - - # Group the DataFrame by 'state_name', 'county_name', and 'date_local'. For - # each group, calculate the minimum and maximum of the 'sample_measurement' - # column. The result will be a BigFrames DataFrame with a MultiIndex. - result = df.groupby(["state_name", "county_name", "date_local"])[ - "sample_measurement" - ].agg(["min", "max"]) - - # Rename some columns and convert the MultiIndex of the 'result' DataFrame - # into regular columns. This flattens the DataFrame so 'state_name', - # 'county_name', and 'date_local' become regular columns again. - result = result.rename( - columns={"min": "min_temperature", "max": "max_temperature"} - ).reset_index() - - # Return the processed BigFrames DataFrame. - # In a dbt Python model, this DataFrame will be materialized as a table - return result diff --git a/samples/dbt/dbt_sample_project/models/example/dbt_bigframes_code_sample_2.py b/samples/dbt/dbt_sample_project/models/example/dbt_bigframes_code_sample_2.py deleted file mode 100644 index 1f060cd60bd..00000000000 --- a/samples/dbt/dbt_sample_project/models/example/dbt_bigframes_code_sample_2.py +++ /dev/null @@ -1,79 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# This example demonstrates how to build an **incremental dbt Python model** -# using BigFrames. -# -# Incremental models are essential for efficiently processing large datasets by -# only transforming new or changed data, rather than reprocessing the entire -# dataset every time. If the target table already exists, dbt will perform a -# merge based on the specified unique keys; otherwise, it will create a new -# table automatically. -# -# This model also showcases the definition and application of a **BigFrames -# User-Defined Function (UDF)** to add a descriptive summary column based on -# temperature data. BigFrames UDFs allow you to execute custom Python logic -# directly within BigQuery, leveraging BigQuery's scalability. - - -def model(dbt, session): - # Optional: override settings from dbt_project.yml. - # When both are set, dbt.config takes precedence over dbt_project.yml. - dbt.config( - # Use BigFrames mode to execute this Python model. This enables - # pandas-like operations directly on BigQuery data. - submission_method="bigframes", - # Materialize this model as an 'incremental' table. This tells dbt to - # only process new or updated data on subsequent runs. - materialized="incremental", - # Use MERGE strategy to update rows during incremental runs. - incremental_strategy="merge", - # Define the composite key that uniquely identifies a row in the - # target table. This key is used by the 'merge' strategy to match - # existing rows for updates during incremental runs. - unique_key=["state_name", "county_name", "date_local"], - ) - - # Reference an upstream dbt model or an existing BigQuery table as a - # BigFrames DataFrame. It allows you to seamlessly use the output of another - # dbt model as input to this one. - df = dbt.ref("dbt_bigframes_code_sample_1") - - # Define a BigFrames UDF to generate a temperature description. - # BigFrames UDFs allow you to define custom Python logic that executes - # directly within BigQuery. This is powerful for complex transformations. - @session.udf(dataset="dbt_sample_dataset", name="describe_udf") - def describe( - max_temperature: float, - min_temperature: float, - ) -> str: - is_hot = max_temperature > 85.0 - is_cold = min_temperature < 50.0 - - if is_hot and is_cold: - return "Expect both hot and cold conditions today." - if is_hot: - return "Overall, it's a hot day." - if is_cold: - return "Overall, it's a cold day." - return "Comfortable throughout the day." - - # Apply the UDF using combine and store the result in a column "describe". - df["describe"] = df["max_temperature"].combine(df["min_temperature"], describe) - - # Return the transformed BigFrames DataFrame. - # This DataFrame will be the final output of your incremental dbt model. - # On subsequent runs, only new or changed rows will be processed and merged - # into the target BigQuery table based on the `unique_key`. - return df diff --git a/samples/dbt/dbt_sample_project/models/ml_example/prediction.py b/samples/dbt/dbt_sample_project/models/ml_example/prediction.py deleted file mode 100644 index d2fb54b3846..00000000000 --- a/samples/dbt/dbt_sample_project/models/ml_example/prediction.py +++ /dev/null @@ -1,67 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# This DBT Python model prepares and trains a machine learning model to predict -# ozone levels. -# 1. Data Preparation: The model first gets a prepared dataset and splits it -# into three subsets based on the year: training data (before 2017), -# testing data (2017-2019), and prediction data (2020 and later). -# 2. Model Training: It then uses the LinearRegression model from BigFrames -# ML library. The model is trained on the historical data, using other -# atmospheric parameters to predict the 'o3' (ozone) levels. -# 3. Prediction: Finally, the trained model makes predictions on the most -# recent data (from 2020 onwards) and returns the resulting DataFrame of -# predicted ozone values. -# -# See more details from the related blog post: https://docs.getdbt.com/blog/train-linear-dbt-bigframes - - -def model(dbt, session): - dbt.config(submission_method="bigframes", timeout=6000) - - df = dbt.ref("prepare_table") - - # Define the rules for separating the training, test and prediction data. - train_data_filter = (df.date_local.dt.year < 2017) - test_data_filter = ( - (df.date_local.dt.year >= 2017) & (df.date_local.dt.year < 2020) - ) - predict_data_filter = (df.date_local.dt.year >= 2020) - - # Define index_columns again here in prediction. - index_columns = ["state_name", "county_name", "site_num", "date_local", "time_local"] - - # Separate the training, test and prediction data. - df_train = df[train_data_filter].set_index(index_columns) - df_test = df[test_data_filter].set_index(index_columns) - df_predict = df[predict_data_filter].set_index(index_columns) - - # Finalize the training dataframe. - X_train = df_train.drop(columns="o3") - y_train = df_train["o3"] - - # Finalize the prediction dataframe. - X_predict = df_predict.drop(columns="o3") - - # Import the LinearRegression model from bigframes.ml module. - from bigframes.ml.linear_model import LinearRegression - - # Train the model. - model = LinearRegression() - model.fit(X_train, y_train) - - # Make the prediction using the model. - df_pred = model.predict(X_predict) - - return df_pred diff --git a/samples/dbt/dbt_sample_project/models/ml_example/prepare_table.py b/samples/dbt/dbt_sample_project/models/ml_example/prepare_table.py deleted file mode 100644 index 23b54a9122d..00000000000 --- a/samples/dbt/dbt_sample_project/models/ml_example/prepare_table.py +++ /dev/null @@ -1,93 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# This DBT Python model processes EPA historical air quality data from BigQuery -# using BigFrames. The primary goal is to merge several hourly summary -# tables into a single, unified DataFrame for later prediction. It includes the -# following steps: -# 1. Reading and Cleaning: It reads individual hourly summary tables from -# BigQuery for various atmospheric parameters (like CO, O3, temperature, -# and wind speed). Each table is cleaned by sorting, removing duplicates, -# and renaming columns for clarity. -# 2. Combining Data: It then merges these cleaned tables into a single, -# comprehensive DataFrame. An inner join is used to ensure the final output -# only includes records with complete data across all parameters. -# 3. Final Output: The unified DataFrame is returned as the model's output, -# creating a corresponding BigQuery table for future use. -# -# See more details from the related blog post: https://docs.getdbt.com/blog/train-linear-dbt-bigframes - - -import bigframes.pandas as bpd - -def model(dbt, session): - # Optional: override settings from dbt_project.yml. - # When both are set, dbt.config takes precedence over dbt_project.yml. - dbt.config(submission_method="bigframes", timeout=6000) - - # Define the dataset and the columns of interest representing various parameters - # in the atmosphere. - dataset = "bigquery-public-data.epa_historical_air_quality" - index_columns = ["state_name", "county_name", "site_num", "date_local", "time_local"] - param_column = "parameter_name" - value_column = "sample_measurement" - - # Initialize a list for collecting dataframes from individual parameters. - params_dfs = [] - - # Collect dataframes from tables which contain data for single parameter. - table_param_dict = { - "co_hourly_summary" : "co", - "no2_hourly_summary" : "no2", - "o3_hourly_summary" : "o3", - "pressure_hourly_summary" : "pressure", - "so2_hourly_summary" : "so2", - "temperature_hourly_summary" : "temperature", - } - - for table, param in table_param_dict.items(): - param_df = bpd.read_gbq( - f"{dataset}.{table}", - columns=index_columns + [value_column] - ) - param_df = param_df\ - .sort_values(index_columns)\ - .drop_duplicates(index_columns)\ - .set_index(index_columns)\ - .rename(columns={value_column : param}) - params_dfs.append(param_df) - - # Collect dataframes from the table containing wind speed. - # Optionally: collect dataframes from other tables containing - # wind direction, NO, NOx, and NOy data as needed. - wind_table = f"{dataset}.wind_hourly_summary" - bpd.read_gbq(wind_table, columns=[param_column]).value_counts() - - wind_speed_df = bpd.read_gbq( - wind_table, - columns=index_columns + [value_column], - filters=[(param_column, "==", "Wind Speed - Resultant")] - ) - wind_speed_df = wind_speed_df\ - .sort_values(index_columns)\ - .drop_duplicates(index_columns)\ - .set_index(index_columns)\ - .rename(columns={value_column: "wind_speed"}) - params_dfs.append(wind_speed_df) - - # Combine data for all the selected parameters. - df = bpd.concat(params_dfs, axis=1, join="inner") - df = df.reset_index() - - return df diff --git a/samples/polars/create_polars_df_with_to_arrow_test.py b/samples/polars/create_polars_df_with_to_arrow_test.py deleted file mode 100644 index acb79f23c82..00000000000 --- a/samples/polars/create_polars_df_with_to_arrow_test.py +++ /dev/null @@ -1,40 +0,0 @@ -# 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. -# 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. - - -def test_create_polars_df() -> None: - # [START bigquery_dataframes_to_polars] - import polars - - import bigframes.enums - import bigframes.pandas as bpd - - bf_df = bpd.read_gbq_table( - "bigquery-public-data.usa_names.usa_1910_current", - # Setting index_col to either a unique column or NULL will give the - # best performance. - index_col=bigframes.enums.DefaultIndexKind.NULL, - ) - # TODO(developer): Do some analysis using BigQuery DataFrames. - # ... - - # Run the query and download the results as an Arrow table to convert into - # a Polars DataFrame. Use ordered=False if your polars analysis is OK with - # non-deterministic ordering. - arrow_table = bf_df.to_arrow(ordered=False) - polars_df = polars.from_arrow(arrow_table) - # [END bigquery_dataframes_to_polars] - - assert polars_df.shape == bf_df.shape - assert polars_df["number"].sum() == bf_df["number"].sum() diff --git a/samples/polars/noxfile.py b/samples/polars/noxfile.py deleted file mode 100644 index 63e742993f9..00000000000 --- a/samples/polars/noxfile.py +++ /dev/null @@ -1,291 +0,0 @@ -# Copyright 2019 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 print_function - -import glob -import os -from pathlib import Path -import sys -from typing import Callable, Dict, Optional - -import nox - -# WARNING - WARNING - WARNING - WARNING - WARNING -# WARNING - WARNING - WARNING - WARNING - WARNING -# DO NOT EDIT THIS FILE EVER! -# WARNING - WARNING - WARNING - WARNING - WARNING -# WARNING - WARNING - WARNING - WARNING - WARNING - -BLACK_VERSION = "black==22.3.0" -ISORT_VERSION = "isort==5.10.1" - -# Copy `noxfile_config.py` to your directory and modify it instead. - -# `TEST_CONFIG` dict is a configuration hook that allows users to -# modify the test configurations. The values here should be in sync -# with `noxfile_config.py`. Users will copy `noxfile_config.py` into -# their directory and modify it. - -TEST_CONFIG = { - # You can opt out from the test for specific Python versions. - "ignored_versions": [], - # Old samples are opted out of enforcing Python type hints - # All new samples should feature them - "enforce_type_hints": False, - # An envvar key for determining the project id to use. Change it - # to 'BUILD_SPECIFIC_GCLOUD_PROJECT' if you want to opt in using a - # build specific Cloud project. You can also use your own string - # to use your own Cloud project. - "gcloud_project_env": "GOOGLE_CLOUD_PROJECT", - # 'gcloud_project_env': 'BUILD_SPECIFIC_GCLOUD_PROJECT', - # If you need to use a specific version of pip, - # change pip_version_override to the string representation - # of the version number, for example, "20.2.4" - "pip_version_override": None, - # A dictionary you want to inject into your test. Don't put any - # secrets here. These values will override predefined values. - "envs": {}, -} - - -try: - # Ensure we can import noxfile_config in the project's directory. - sys.path.append(".") - from noxfile_config import TEST_CONFIG_OVERRIDE -except ImportError as e: - print("No user noxfile_config found: detail: {}".format(e)) - TEST_CONFIG_OVERRIDE = {} - -# Update the TEST_CONFIG with the user supplied values. -TEST_CONFIG.update(TEST_CONFIG_OVERRIDE) - - -def get_pytest_env_vars() -> Dict[str, str]: - """Returns a dict for pytest invocation.""" - ret = {} - - # Override the GCLOUD_PROJECT and the alias. - env_key = TEST_CONFIG["gcloud_project_env"] - # This should error out if not set. - ret["GOOGLE_CLOUD_PROJECT"] = os.environ[env_key] - - # Apply user supplied envs. - ret.update(TEST_CONFIG["envs"]) - return ret - - -# All versions used to test samples. -ALL_VERSIONS = ["3.10", "3.11", "3.12", "3.13", "3.14"] - -# Any default versions that should be ignored. -IGNORED_VERSIONS = TEST_CONFIG["ignored_versions"] - -TESTED_VERSIONS = sorted([v for v in ALL_VERSIONS if v not in IGNORED_VERSIONS]) - -INSTALL_LIBRARY_FROM_SOURCE = os.environ.get("INSTALL_LIBRARY_FROM_SOURCE", False) in ( - "True", - "true", -) - -# Error if a python version is missing -nox.options.error_on_missing_interpreters = True - -# -# Style Checks -# - - -# Linting with flake8. -# -# We ignore the following rules: -# E203: whitespace before ‘:’ -# E266: too many leading ‘#’ for block comment -# E501: line too long -# I202: Additional newline in a section of imports -# -# We also need to specify the rules which are ignored by default: -# ['E226', 'W504', 'E126', 'E123', 'W503', 'E24', 'E704', 'E121'] -FLAKE8_COMMON_ARGS = [ - "--show-source", - "--builtin=gettext", - "--max-complexity=20", - "--exclude=.nox,.cache,env,lib,generated_pb2,*_pb2.py,*_pb2_grpc.py", - "--ignore=E121,E123,E126,E203,E226,E24,E266,E501,E704,W503,W504,I202", - "--max-line-length=88", -] - - -@nox.session -def lint(session: nox.sessions.Session) -> None: - if not TEST_CONFIG["enforce_type_hints"]: - session.install("flake8") - else: - session.install("flake8", "flake8-annotations") - - args = FLAKE8_COMMON_ARGS + [ - ".", - ] - session.run("flake8", *args) - - -# -# Black -# - - -@nox.session -def blacken(session: nox.sessions.Session) -> None: - """Run black. Format code to uniform standard.""" - session.install(BLACK_VERSION) - python_files = [path for path in os.listdir(".") if path.endswith(".py")] - - session.run("black", *python_files) - - -# -# format = isort + black -# - - -@nox.session -def format(session: nox.sessions.Session) -> None: - """ - Run isort to sort imports. Then run black - to format code to uniform standard. - """ - session.install(BLACK_VERSION, ISORT_VERSION) - python_files = [path for path in os.listdir(".") if path.endswith(".py")] - - # Use the --fss option to sort imports using strict alphabetical order. - # See https://pycqa.github.io/isort/docs/configuration/options.html#force-sort-within-sections - session.run("isort", "--fss", *python_files) - session.run("black", *python_files) - - -# -# Sample Tests -# - - -PYTEST_COMMON_ARGS = ["--junitxml=sponge_log.xml"] - - -def _session_tests( - session: nox.sessions.Session, post_install: Callable = None -) -> None: - # check for presence of tests - test_list = glob.glob("**/*_test.py", recursive=True) + glob.glob( - "**/test_*.py", recursive=True - ) - test_list.extend(glob.glob("**/tests", recursive=True)) - - if len(test_list) == 0: - print("No tests found, skipping directory.") - return - - if TEST_CONFIG["pip_version_override"]: - pip_version = TEST_CONFIG["pip_version_override"] - session.install(f"pip=={pip_version}") - """Runs py.test for a particular project.""" - concurrent_args = [] - if os.path.exists("requirements.txt"): - if os.path.exists("constraints.txt"): - session.install("-r", "requirements.txt", "-c", "constraints.txt") - else: - session.install("-r", "requirements.txt") - with open("requirements.txt") as rfile: - packages = rfile.read() - - if os.path.exists("requirements-test.txt"): - if os.path.exists("constraints-test.txt"): - session.install("-r", "requirements-test.txt", "-c", "constraints-test.txt") - else: - session.install("-r", "requirements-test.txt") - with open("requirements-test.txt") as rtfile: - packages += rtfile.read() - - if INSTALL_LIBRARY_FROM_SOURCE: - session.install("-e", _get_repo_root()) - - if post_install: - post_install(session) - - if "pytest-parallel" in packages: - concurrent_args.extend(["--workers", "auto", "--tests-per-worker", "auto"]) - elif "pytest-xdist" in packages: - concurrent_args.extend(["-n", "auto"]) - - session.run( - "pytest", - *(PYTEST_COMMON_ARGS + session.posargs + concurrent_args), - # Pytest will return 5 when no tests are collected. This can happen - # on travis where slow and flaky tests are excluded. - # See http://doc.pytest.org/en/latest/_modules/_pytest/main.html - success_codes=[0, 5], - env=get_pytest_env_vars(), - ) - - -@nox.session(python=ALL_VERSIONS) -def py(session: nox.sessions.Session) -> None: - """Runs py.test for a sample using the specified version of Python.""" - if session.python in TESTED_VERSIONS: - _session_tests(session) - else: - session.skip( - "SKIPPED: {} tests are disabled for this sample.".format(session.python) - ) - - -# -# Readmegen -# - - -def _get_repo_root() -> Optional[str]: - """Returns the root folder of the project.""" - # Get root of this repository. Assume we don't have directories nested deeper than 10 items. - p = Path(os.getcwd()) - for i in range(10): - if p is None: - break - if Path(p / ".git").exists(): - return str(p) - # .git is not available in repos cloned via Cloud Build - # setup.py is always in the library's root, so use that instead - # https://github.com/googleapis/synthtool/issues/792 - if Path(p / "setup.py").exists(): - return str(p) - p = p.parent - raise Exception("Unable to detect repository root.") - - -GENERATED_READMES = sorted([x for x in Path(".").rglob("*.rst.in")]) - - -@nox.session -@nox.parametrize("path", GENERATED_READMES) -def readmegen(session: nox.sessions.Session, path: str) -> None: - """(Re-)generates the readme for a sample.""" - session.install("jinja2", "pyyaml") - dir_ = os.path.dirname(path) - - if os.path.exists(os.path.join(dir_, "requirements.txt")): - session.install("-r", os.path.join(dir_, "requirements.txt")) - - in_file = os.path.join(dir_, "README.rst.in") - session.run( - "python", _get_repo_root() + "/scripts/readme-gen/readme_gen.py", in_file - ) diff --git a/samples/polars/noxfile_config.py b/samples/polars/noxfile_config.py deleted file mode 100644 index 91238e9e2ff..00000000000 --- a/samples/polars/noxfile_config.py +++ /dev/null @@ -1,42 +0,0 @@ -# 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. -# 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. - -# Default TEST_CONFIG_OVERRIDE for python repos. - -# You can copy this file into your directory, then it will be inported from -# the noxfile.py. - -# The source of truth: -# https://github.com/GoogleCloudPlatform/python-docs-samples/blob/master/noxfile_config.py - -TEST_CONFIG_OVERRIDE = { - # You can opt out from the test for specific Python versions. - "ignored_versions": ["2.7", "3.7", "3.8"], - # Old samples are opted out of enforcing Python type hints - # All new samples should feature them - "enforce_type_hints": True, - # An envvar key for determining the project id to use. Change it - # to 'BUILD_SPECIFIC_GCLOUD_PROJECT' if you want to opt in using a - # build specific Cloud project. You can also use your own string - # to use your own Cloud project. - "gcloud_project_env": "GOOGLE_CLOUD_PROJECT", - # "gcloud_project_env": "BUILD_SPECIFIC_GCLOUD_PROJECT", - # If you need to use a specific version of pip, - # change pip_version_override to the string representation - # of the version number, for example, "20.2.4" - "pip_version_override": None, - # A dictionary you want to inject into your test. Don't put any - # secrets here. These values will override predefined values. - "envs": {}, -} diff --git a/samples/polars/requirements-test.txt b/samples/polars/requirements-test.txt deleted file mode 100644 index ce5e1b9e702..00000000000 --- a/samples/polars/requirements-test.txt +++ /dev/null @@ -1,3 +0,0 @@ -# samples/snippets should be runnable with no "extras" -google-cloud-testutils==1.8.0 -pytest==9.0.3 diff --git a/samples/polars/requirements.txt b/samples/polars/requirements.txt deleted file mode 100644 index 218e674b9ed..00000000000 --- a/samples/polars/requirements.txt +++ /dev/null @@ -1,3 +0,0 @@ -bigframes==2.39.0 -polars==1.40.1 -pyarrow==24.0.0 diff --git a/samples/snippets/__init__.py b/samples/snippets/__init__.py new file mode 100644 index 00000000000..1dc90d18483 --- /dev/null +++ b/samples/snippets/__init__.py @@ -0,0 +1,13 @@ +# Copyright 2023 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/samples/snippets/clustering_model_test.py b/samples/snippets/clustering_model_test.py new file mode 100644 index 00000000000..a407fc78058 --- /dev/null +++ b/samples/snippets/clustering_model_test.py @@ -0,0 +1,35 @@ +# Copyright 2023 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. + + +def test_clustering_model(): + # [START bigquery_dataframes_clustering_model] + from bigframes.ml.cluster import KMeans + import bigframes.pandas as bpd + + # Load data from BigQuery + query_or_table = "bigquery-public-data.ml_datasets.penguins" + bq_df = bpd.read_gbq(query_or_table) + + # Create the KMeans model + cluster_model = KMeans(n_clusters=10) + cluster_model.fit(bq_df["culmen_length_mm"], bq_df["sex"]) + + # Predict using the model + result = cluster_model.predict(bq_df) + # Score the model + score = cluster_model.score(bq_df) + # [END bigquery_dataframes_clustering_model] + assert result is not None + assert score is not None diff --git a/samples/snippets/gen_ai_model_test.py b/samples/snippets/gen_ai_model_test.py new file mode 100644 index 00000000000..7cbc90d4c03 --- /dev/null +++ b/samples/snippets/gen_ai_model_test.py @@ -0,0 +1,39 @@ +# Copyright 2023 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. + + +def test_llm_model(): + PROJECT_ID = "bigframes-dev" + REGION = "us" + CONN_NAME = "bigframes-ml" + # [START bigquery_dataframes_gen_ai_model] + from bigframes.ml.llm import PaLM2TextGenerator + import bigframes.pandas as bpd + + # Create the LLM model + session = bpd.get_global_session() + connection = f"{PROJECT_ID}.{REGION}.{CONN_NAME}" + model = PaLM2TextGenerator(session=session, connection_name=connection) + + df_api = bpd.read_csv("gs://cloud-samples-data/vertex-ai/bigframe/df.csv") + + # Prepare the prompts and send them to the LLM model for prediction + df_prompt_prefix = "Generate Pandas sample code for DataFrame." + df_prompt = df_prompt_prefix + df_api["API"] + + # Predict using the model + df_pred = model.predict(df_prompt.to_frame(), max_output_tokens=1024) + # [END bigquery_dataframes_gen_ai_model] + assert df_pred["ml_generate_text_llm_result"] is not None + assert df_pred["ml_generate_text_llm_result"].iloc[0] is not None diff --git a/samples/snippets/load_data_from_bigquery_test.py b/samples/snippets/load_data_from_bigquery_test.py new file mode 100644 index 00000000000..e4c65688bdd --- /dev/null +++ b/samples/snippets/load_data_from_bigquery_test.py @@ -0,0 +1,24 @@ +# Copyright 2023 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. + + +def test_bigquery_dataframes_load_data_from_bigquery(): + # [START bigquery_dataframes_load_data_from_bigquery] + # Create a DataFrame from a BigQuery table: + import bigframes.pandas as bpd + + query_or_table = "bigquery-public-data.ml_datasets.penguins" + bq_df = bpd.read_gbq(query_or_table) + # [END bigquery_dataframes_load_data_from_bigquery] + assert bq_df is not None diff --git a/samples/snippets/load_data_from_biquery_job_test.py b/samples/snippets/load_data_from_biquery_job_test.py new file mode 100644 index 00000000000..5271574a492 --- /dev/null +++ b/samples/snippets/load_data_from_biquery_job_test.py @@ -0,0 +1,51 @@ +# Copyright 2023 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. + + +def test_bigquery_dataframes_load_data_from_bigquery_job(): + from google.cloud import bigquery + + # Construct a BigQuery client object. + client = bigquery.Client(project="bigframes-dev", location="us") + + query = """ + SELECT * + FROM `bigquery-public-data.ml_datasets.penguins` + LIMIT 20 + """ + query_job = client.query(query) + JOB_ID = query_job.job_id + your_project_id = "bigframes-dev" + + # [START bigquery_dataframes_load_data_from_bigquery_job] + from google.cloud import bigquery + + import bigframes.pandas as bpd + + # Project ID inserted based on the query results selected to explore + project = your_project_id + # Location inserted based on the query results selected to explore + location = "us" + client = bigquery.Client(project=project, location=location) + + # Job ID inserted based on the query results selcted to explore + job_id = JOB_ID + job = client.get_job(job_id) + destination = str(job.destination) + + # Load data from a BigQuery table using BigFrames DataFrames: + bq_df = bpd.read_gbq_table(destination) + + # [END bigquery_dataframes_load_data_from_bigquery_job] + assert bq_df is not None diff --git a/samples/snippets/load_data_from_csv_test.py b/samples/snippets/load_data_from_csv_test.py new file mode 100644 index 00000000000..31ab9255bf4 --- /dev/null +++ b/samples/snippets/load_data_from_csv_test.py @@ -0,0 +1,25 @@ +# Copyright 2023 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. + + +def test_bigquery_dataframes_load_data_from_csv(): + # [START bigquery_dataframes_load_data_from_csv] + import bigframes.pandas as bpd + + filepath_or_buffer = "gs://cloud-samples-data/bigquery/us-states/us-states.csv" + df_from_gcs = bpd.read_csv(filepath_or_buffer) + # Display the first few rows of the DataFrame: + df_from_gcs.head() + # [END bigquery_dataframes_load_data_from_csv] + assert df_from_gcs is not None diff --git a/samples/snippets/pandas_methods_test.py b/samples/snippets/pandas_methods_test.py new file mode 100644 index 00000000000..1f472d63466 --- /dev/null +++ b/samples/snippets/pandas_methods_test.py @@ -0,0 +1,34 @@ +# Copyright 2023 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. + + +def test_bigquery_dataframes_pandas_methods(): + # [START bigquery_dataframes_pandas_methods] + import bigframes.pandas as bpd + + # Load data from BigQuery + query_or_table = "bigquery-public-data.ml_datasets.penguins" + bq_df = bpd.read_gbq(query_or_table) + + # Inspect one of the columns (or series) of the DataFrame: + bq_df["body_mass_g"].head(10) + + # Compute the mean of this series: + average_body_mass = bq_df["body_mass_g"].mean() + print(f"average_body_mass: {average_body_mass}") + + # Calculate the mean body_mass_g by species using the groupby operation: + bq_df["body_mass_g"].groupby(by=bq_df["species"]).mean().head() + # [END bigquery_dataframes_pandas_methods] + assert average_body_mass is not None diff --git a/samples/snippets/quickstart.py b/samples/snippets/quickstart.py new file mode 100644 index 00000000000..a15ea168534 --- /dev/null +++ b/samples/snippets/quickstart.py @@ -0,0 +1,71 @@ +# Copyright 2023 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. + + +def run_quickstart(project_id: str): + import bigframes + + session_options = bigframes.BigQueryOptions() + session = bigframes.connect(session_options) + + your_gcp_project_id = project_id + query_or_table = "bigquery-public-data.ml_datasets.penguins" + df_session = session.read_gbq(query_or_table) + average_body_mass = df_session["body_mass_g"].mean() + print(f"average_body_mass (df_session): {average_body_mass}") + + # [START bigquery_bigframes_quickstart] + import bigframes.pandas as bpd + + # Set BigQuery DataFrames options + bpd.options.bigquery.project = your_gcp_project_id + bpd.options.bigquery.location = "us" + + # Create a DataFrame from a BigQuery table + query_or_table = "bigquery-public-data.ml_datasets.penguins" + df = bpd.read_gbq(query_or_table) + + # Use the DataFrame just as you would a pandas DataFrame, but calculations + # happen in the BigQuery query engine instead of the local system. + average_body_mass = df["body_mass_g"].mean() + print(f"average_body_mass: {average_body_mass}") + + # Create the Linear Regression model + from bigframes.ml.linear_model import LinearRegression + + # Filter down to the data we want to analyze + adelie_data = df[df.species == "Adelie Penguin (Pygoscelis adeliae)"] + + # Drop the columns we don't care about + adelie_data = adelie_data.drop(columns=["species"]) + + # Drop rows with nulls to get our training data + training_data = adelie_data.dropna() + + # Pick feature columns and label column + X = training_data[ + [ + "island", + "culmen_length_mm", + "culmen_depth_mm", + "flipper_length_mm", + "sex", + ] + ] + y = training_data[["body_mass_g"]] + + model = LinearRegression(fit_intercept=False) + model.fit(X, y) + model.score(X, y) + # [END bigquery_bigframes_quickstart] diff --git a/samples/snippets/quickstart_test.py b/samples/snippets/quickstart_test.py new file mode 100644 index 00000000000..bbe4a8b3c49 --- /dev/null +++ b/samples/snippets/quickstart_test.py @@ -0,0 +1,31 @@ +# Copyright 2023 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 + +import bigframes.pandas + +from . import quickstart + + +def test_quickstart( + capsys: pytest.CaptureFixture[str], +) -> None: + # We need a fresh session since we're modifying connection options. + bigframes.pandas.close_session() + + # TODO(swast): Get project from environment so contributors can run tests. + quickstart.run_quickstart("bigframes-dev") + out, _ = capsys.readouterr() + assert "average_body_mass (df_session):" in out diff --git a/samples/snippets/regression_model_test.py b/samples/snippets/regression_model_test.py new file mode 100644 index 00000000000..7d1bde689cb --- /dev/null +++ b/samples/snippets/regression_model_test.py @@ -0,0 +1,57 @@ +# Copyright 2023 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. + + +def test_regression_model(): + # [START bigquery_dataframes_regression_model] + from bigframes.ml.linear_model import LinearRegression + import bigframes.pandas as bpd + + # Load data from BigQuery + query_or_table = "bigquery-public-data.ml_datasets.penguins" + bq_df = bpd.read_gbq(query_or_table) + + # Filter down to the data to the Adelie Penguin species + adelie_data = bq_df[bq_df.species == "Adelie Penguin (Pygoscelis adeliae)"] + + # Drop the species column + adelie_data = adelie_data.drop(columns=["species"]) + + # Drop rows with nulls to get training data + training_data = adelie_data.dropna() + + # Specify your feature (or input) columns and the label (or output) column: + feature_columns = training_data[ + ["island", "culmen_length_mm", "culmen_depth_mm", "flipper_length_mm", "sex"] + ] + label_columns = training_data[["body_mass_g"]] + + test_data = adelie_data[adelie_data.body_mass_g.isnull()] + + # Create the linear model + model = LinearRegression() + model.fit(feature_columns, label_columns) + + # Score the model + score = model.score(feature_columns, label_columns) + + # Predict using the model + result = model.predict(test_data) + # [END bigquery_dataframes_regression_model] + assert test_data is not None + assert feature_columns is not None + assert label_columns is not None + assert model is not None + assert score is not None + assert result is not None diff --git a/samples/snippets/remote_function.py b/samples/snippets/remote_function.py new file mode 100644 index 00000000000..646d7b0c307 --- /dev/null +++ b/samples/snippets/remote_function.py @@ -0,0 +1,163 @@ +# Copyright 2023 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. + + +def run_remote_function_and_read_gbq_function(project_id: str): + your_gcp_project_id = project_id + + # [START bigquery_dataframes_remote_function] + import bigframes.pandas as bpd + + # Set BigQuery DataFrames options + bpd.options.bigquery.project = your_gcp_project_id + bpd.options.bigquery.location = "us" + + # BigQuery DataFrames gives you the ability to turn your custom scalar + # functions into a BigQuery remote function. It requires the GCP project to + # be set up appropriately and the user having sufficient privileges to use + # them. One can find more details about the usage and the requirements via + # `help` command. + help(bpd.remote_function) + + # Read a table and inspect the column of interest. + df = bpd.read_gbq("bigquery-public-data.ml_datasets.penguins") + df["body_mass_g"].head(10) + + # Define a custom function, and specify the intent to turn it into a remote + # function. It requires a BigQuery connection. If the connection is not + # already created, BigQuery DataFrames will attempt to create one assuming + # the necessary APIs and IAM permissions are setup in the project. In our + # examples we would be using a pre-created connection named + # `bigframes-rf-conn`. We will also set `reuse=False` to make sure we don't + # step over someone else creating remote function in the same project from + # the exact same source code at the same time. Let's try a `pandas`-like use + # case in which we want to apply a user defined scalar function to every + # value in a `Series`, more specifically bucketize the `body_mass_g` value + # of the penguins, which is a real number, into a category, which is a + # string. + @bpd.remote_function( + [float], + str, + bigquery_connection="bigframes-rf-conn", + reuse=False, + ) + def get_bucket(num): + if not num: + return "NA" + boundary = 4000 + return "at_or_above_4000" if num >= boundary else "below_4000" + + # Then we can apply the remote function on the `Series`` of interest via + # `apply` API and store the result in a new column in the DataFrame. + df = df.assign(body_mass_bucket=df["body_mass_g"].apply(get_bucket)) + + # This will add a new column `body_mass_bucket` in the DataFrame. You can + # preview the original value and the bucketized value side by side. + df[["body_mass_g", "body_mass_bucket"]].head(10) + + # The above operation was possible by doing all the computation on the + # cloud. For that, there is a google cloud function deployed by serializing + # the user code, and a BigQuery remote function created to call the cloud + # function via the latter's http endpoint on the data in the DataFrame. + + # The BigQuery remote function created to support the BigQuery DataFrames + # remote function can be located via a property `bigframes_remote_function` + # set in the remote function object. + print(f"Created BQ remote function: {get_bucket.bigframes_remote_function}") + + # The cloud function can be located via another property + # `bigframes_cloud_function` set in the remote function object. + print(f"Created cloud function: {get_bucket.bigframes_cloud_function}") + + # Warning: The deployed cloud function may be visible to other users with + # sufficient privilege in the project, so the user should be careful about + # having any sensitive data in the code that will be deployed as a remote + # function. + + # Let's continue trying other potential use cases of remote functions. Let's + # say we consider the `species`, `island` and `sex` of the penguins + # sensitive information and want to redact that by replacing with their hash + # code instead. Let's define another scalar custom function and decorate it + # as a remote function. The custom function in this example has external + # package dependency, which can be specified via `packages` parameter. + @bpd.remote_function( + [str], + str, + bigquery_connection="bigframes-rf-conn", + reuse=False, + packages=["cryptography"], + ) + def get_hash(input): + from cryptography.fernet import Fernet + + # handle missing value + if input is None: + input = "" + + key = Fernet.generate_key() + f = Fernet(key) + return f.encrypt(input.encode()).decode() + + # We can use this remote function in another `pandas`-like API `map` that + # can be applied on a DataFrame + df_redacted = df[["species", "island", "sex"]].map(get_hash) + df_redacted.head(10) + + # [END bigquery_dataframes_remote_function] + + existing_get_bucket_bq_udf = get_bucket.bigframes_remote_function + + # [START bigquery_dataframes_read_gbq_function] + + # If you have already defined a custom function in BigQuery, either via the + # BigQuery Google Cloud Console or with the `remote_function` decorator, + # or otherwise, you may use it with BigQuery DataFrames with the + # `read_gbq_function` method. More details are available via the `help` + # command. + import bigframes.pandas as pd + + help(pd.read_gbq_function) + + # Here is an example of using `read_gbq_function` to load an existing + # BigQuery function. + df = pd.read_gbq("bigquery-public-data.ml_datasets.penguins") + get_bucket_function = pd.read_gbq_function(existing_get_bucket_bq_udf) + + df = df.assign(body_mass_bucket=df["body_mass_g"].apply(get_bucket_function)) + df.head(10) + + # It should be noted that if a function is created using the + # `remote_function` decorator, its created BQ remote function is accessible + # immediately afterward via the function's `bigframes_remote_function` + # attribute. The same string can be passed to `read_gbq_function` later in + # another context. + + # [END bigquery_dataframes_read_gbq_function] + + # Clean up cloud artifacts + session = bpd.get_global_session() + for function in (get_bucket, get_hash): + try: + session.bqclient.delete_routine(function.bigframes_remote_function) + except Exception: + # Ignore exception during clean-up + pass + + try: + session.cloudfunctionsclient.delete_function( + name=function.bigframes_cloud_function + ) + except Exception: + # Ignore exception during clean-up + pass diff --git a/samples/snippets/remote_function_test.py b/samples/snippets/remote_function_test.py new file mode 100644 index 00000000000..e1317c6ac08 --- /dev/null +++ b/samples/snippets/remote_function_test.py @@ -0,0 +1,32 @@ +# Copyright 2023 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 + +import bigframes.pandas + +from . import remote_function + + +def test_remote_function_and_read_gbq_function( + capsys: pytest.CaptureFixture[str], +) -> None: + # We need a fresh session since we're modifying connection options. + bigframes.pandas.close_session() + + # TODO(swast): Get project from environment so contributors can run tests. + remote_function.run_remote_function_and_read_gbq_function("bigframes-dev") + out, _ = capsys.readouterr() + assert "Created BQ remote function:" in out + assert "Created cloud function:" in out diff --git a/samples/snippets/set_options_test.py b/samples/snippets/set_options_test.py new file mode 100644 index 00000000000..ef6f41ce541 --- /dev/null +++ b/samples/snippets/set_options_test.py @@ -0,0 +1,34 @@ +# Copyright 2023 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. + + +def test_bigquery_dataframes_set_options(): + # Close the session before resetting the options + import bigframes.pandas as bpd + + bpd.close_session() + + # [START bigquery_dataframes_set_options] + import bigframes.pandas as bpd + + PROJECT_ID = "bigframes-dec" # @param {type:"string"} + REGION = "US" # @param {type:"string"} + + # Set BigQuery DataFrames options + bpd.options.bigquery.project = PROJECT_ID + bpd.options.bigquery.location = REGION + + # [END bigquery_dataframes_set_options] + assert bpd.options.bigquery.project == PROJECT_ID + assert bpd.options.bigquery.location == REGION diff --git a/scratch/.gitignore b/scratch/.gitignore deleted file mode 100644 index b813ccd98e6..00000000000 --- a/scratch/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -# Ignore all files in this directory. -* diff --git a/scripts/bigquery_generator/__init__.py b/scripts/bigquery_generator/__init__.py deleted file mode 100644 index 58d482ea386..00000000000 --- a/scripts/bigquery_generator/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -# Copyright 2026 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/scripts/bigquery_generator/constants.py b/scripts/bigquery_generator/constants.py deleted file mode 100644 index 78c3fc60c2b..00000000000 --- a/scripts/bigquery_generator/constants.py +++ /dev/null @@ -1,142 +0,0 @@ -# Copyright 2026 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import pathlib - -SCRIPTS_DIRECTORY = pathlib.Path(__file__).parent.parent.absolute() -PACKAGE_ROOT = SCRIPTS_DIRECTORY.parent -CODE_ROOT = PACKAGE_ROOT / "bigframes" -SCRIPT_PATH_RELATIVE = ( - pathlib.Path(__file__).relative_to(PACKAGE_ROOT).parent.parent - / "generate_bigframes_bigquery.py" -) - - -# Directory containing the YAML files -DATA_DIR = SCRIPTS_DIRECTORY / "data" / "sql-functions" -# Directory where the generated Python files will be placed -OUTPUT_DIR = CODE_ROOT / "operations" / "googlesql" -# Directory where the generated test files will be placed -TEST_OUTPUT_DIR = PACKAGE_ROOT / "tests" / "unit" / "bigquery" / "generated" - -PYTHON_BUILTINS = { - "abs", - "all", - "any", - "ascii", - "bin", - "bool", - "breakpoint", - "bytearray", - "bytes", - "callable", - "chr", - "classmethod", - "compile", - "complex", - "delattr", - "dict", - "dir", - "divmod", - "enumerate", - "eval", - "exec", - "filter", - "float", - "format", - "frozenset", - "getattr", - "globals", - "hasattr", - "hash", - "help", - "hex", - "id", - "input", - "int", - "isinstance", - "issubclass", - "iter", - "len", - "list", - "locals", - "map", - "max", - "memoryview", - "min", - "next", - "object", - "oct", - "open", - "ord", - "pow", - "print", - "property", - "range", - "repr", - "reversed", - "round", - "set", - "setattr", - "slice", - "sorted", - "staticmethod", - "str", - "sum", - "super", - "tuple", - "type", - "vars", - "zip", -} - -DTYPE_MAP = { - "binary": "dtypes.BYTES_DTYPE", - "string": "dtypes.STRING_DTYPE", - "int64": "dtypes.INT_DTYPE", - "i64": "dtypes.INT_DTYPE", - "float64": "dtypes.FLOAT_DTYPE", - "fp64": "dtypes.FLOAT_DTYPE", - "bool": "dtypes.BOOL_DTYPE", - "boolean": "dtypes.BOOL_DTYPE", - "geography": "dtypes.GEO_DTYPE", - "json": "dtypes.JSON_DTYPE", - "date": "dtypes.DATE_DTYPE", - "time": "dtypes.TIME_DTYPE", - "datetime": "dtypes.DATETIME_DTYPE", - "timestamp": "dtypes.TIMESTAMP_DTYPE", - "decimal<38,9>": "dtypes.NUMERIC_DTYPE", - "decimal<76,38>": "dtypes.BIGNUMERIC_DTYPE", -} - -PY_TYPE_MAP = { - "binary": "bytes", - "string": "str", - "int64": "int", - "i64": "int", - "float64": "float", - "fp64": "float", - "bool": "bool", - "boolean": "bool", - "geography": "Any", - "json": "Any", - "date": "datetime.date", - "time": "datetime.time", - "datetime": "datetime.datetime", - "timestamp": "datetime.datetime", - "struct": "dict", - "decimal<38,9>": "decimal.Decimal", - "decimal<76,38>": "decimal.Decimal", - "interval_day": "datetime.timedelta", -} diff --git a/scripts/bigquery_generator/data_models.py b/scripts/bigquery_generator/data_models.py deleted file mode 100644 index e0cffd566a0..00000000000 --- a/scripts/bigquery_generator/data_models.py +++ /dev/null @@ -1,237 +0,0 @@ -# Copyright 2026 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. - -"""Data models for BigQuery code generator. - -`BQ*` models the Substrait YAML extension structure of BigQuery SQL functions, -while `BigFrames*` models the Jinja template outputs. - -BQ* Class Relations: -==================== -+-------------------+ -| BQModule | -+-------------------+ - | - | functions: list[BQFunc] - v -+-------------------+ -| BQFunc | -+-------------------+ - | - | impls: list[BQFuncImpl] - v -+-------------------+ -| BQFuncImpl | -+-------------------+ - | - | args: list[BQFuncArg] - v -+-------------------+ -| BQFuncArg | -+-------------------+ - -BigFrames* Class Relations: -================================= - +--------------------------------+ - | Accessor |<---+ children: list[Accessor] - +--------------------------------+----+ (nested namespace hierarchy) - | - | functions: list[BigFramesFunc] - v - +--------------------------------+ - | BigFramesFunc | - +--------------------------------+ - | - | args: list[BigFramesFuncArg] - v - +--------------------------------+ - | BigFramesFuncArg | - +--------------------------------+ - - ---------------------------------- - - +--------------------------------+ - | BigFramesOp | (Standalone data model for op defs) - +--------------------------------+ -""" - -from __future__ import annotations - -import dataclasses -import pathlib - -from . import constants - - -@dataclasses.dataclass(frozen=True) -class BQFuncArg: - """ - Represents an argument of a SQL function loaded from a yaml file. - """ - - name: str - value: str # The type of the arg - optional: bool - keyword_only: bool - - -@dataclasses.dataclass(frozen=True) -class BQFuncImpl: - """ - Represents an implementation (i.e. signature) for some SQL function loaded - from a yaml file. - """ - - args: tuple[BQFuncArg, ...] - return_type: str - - @property - def requires_generic_types(self) -> bool: - if "any1" in self.return_type: - return True - - return any("any1" in arg.value for arg in self.args) - - -@dataclasses.dataclass(frozen=True) -class BQFunc: - """ - Represents a SQL function loaded from a yaml file. - """ - - name: str - description: str - impls: tuple[BQFuncImpl, ...] - series_accessor_arg: str | None - - @property - def op_base_name(self) -> str: - return self.name.split(".")[-1] - - -@dataclasses.dataclass(frozen=True) -class BQModule: - """ - Represents the data loaded from a yaml file with SQL functions info. - """ - - yaml_file: pathlib.Path - functions: tuple[BQFunc, ...] - - @property - def module_path(self) -> pathlib.Path: - return self.yaml_file.relative_to(constants.DATA_DIR).with_suffix("") - - @property - def namespace(self) -> tuple[str, ...]: - parts = self.module_path.parts - if "global_namespace" in parts: - return tuple() - return parts - - @property - def is_global(self) -> bool: - return "global_namespace" in self.module_path.parts - - -@dataclasses.dataclass(frozen=True) -class BigFramesOp: - """ - Represents a BigFrames GoogleScalarOp impl to be defined in the code base. - """ - - internal_name: str - sql_name: str - arg_specs: str - signature: str - signature_definition: str | None - - -@dataclasses.dataclass(frozen=True) -class BigFramesFuncArg: - """ - Represents an argument of a BigFrames BigQuery function to be defined in the code base. - """ - - name: str - types: frozenset[str] - optional: bool - keyword_only: bool - - @property - def type_hint(self) -> str: - types = [constants.PY_TYPE_MAP.get(t, "Any") for t in sorted(self.types)] + [ - "Literal[sentinels.Sentinel.ARGUMENT_DEFAULT]" - ] - - if len(types) > 1: - return "Union[" + ", ".join(sorted(set(types))) + "]" - - return types[0] - - @property - def default(self) -> str | None: - if self.optional: - return "sentinels.Sentinel.ARGUMENT_DEFAULT" - return None - - -@dataclasses.dataclass -class BigFramesFuncArgBuilder: - name: str - types: set[str] - optional: bool - keyword_only: bool - - def build(self) -> BigFramesFuncArg: - return BigFramesFuncArg( - name=self.name, - types=frozenset(self.types), - optional=self.optional, - keyword_only=self.keyword_only, - ) - - -@dataclasses.dataclass(frozen=True) -class BigFramesFunc: - """ - Represents a BigFrames BigQuery function to be defined in the codebase. - """ - - name: str - op_name: str - description: str - args: tuple[BigFramesFuncArg, ...] - series_accessor_arg: str | None - import_module: str | None = None - - -@dataclasses.dataclass -class Accessor: - """ - Represents the accessor extensions to be defined for pandas and BigFrames. - It consists of multiple functions bundled under the different namespaces. - - This class is designed to be mutable because it has a recursive data structure. - Mutability makes it easier to build the data structure trees from the top. - """ - - class_name: str - bigframes_class_name: str - pandas_class_name: str - is_root: bool - description: str - children: list[Accessor] - functions: list[BigFramesFunc] - prop_name: str | None = None diff --git a/scripts/bigquery_generator/file_generator.py b/scripts/bigquery_generator/file_generator.py deleted file mode 100644 index 3109afc3f8b..00000000000 --- a/scripts/bigquery_generator/file_generator.py +++ /dev/null @@ -1,132 +0,0 @@ -# Copyright 2026 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import pathlib -import subprocess -import sys -from typing import Sequence - -from . import constants, data_models, template_renderer - - -def _ensure_init_py(directory: pathlib.Path, limit_dir: pathlib.Path) -> None: - """Ensures __init__.py exists in the directory and its parents up to limit_dir.""" - curr = directory - while curr != limit_dir and curr != curr.parent: - init_file = curr / "__init__.py" - if not init_file.exists(): - print(f" Creating {init_file}") - with open(init_file, "w", encoding="utf-8") as f: - f.write(template_renderer.render_license()) - curr = curr.parent - - -def _write_file( - content: str, output_file: pathlib.Path, limit_dir: pathlib.Path -) -> None: - output_file.parent.mkdir(parents=True, exist_ok=True) - _ensure_init_py(output_file.parent, limit_dir) - - with open(output_file, "w", encoding="utf-8") as f: - f.write(content) - print(f" Generated {output_file}") - - -def _run_ruff() -> None: - targets = [ - constants.OUTPUT_DIR, - constants.TEST_OUTPUT_DIR, - constants.CODE_ROOT / "extensions", - ] - ruff_common_args = [ - "--target-version=py310", - "--line-length=88", - ] - - ruff_check_args = [ - "check", - "--select", - "I,F", - "--fix", - ] + ruff_common_args - subprocess.run( - [sys.executable, "-m", "ruff"] + ruff_check_args + targets, - check=True, - ) - - ruff_format_args = [ - "format", - ] + ruff_common_args - subprocess.run( - [sys.executable, "-m", "ruff"] + ruff_format_args + targets, - check=True, - ) - - -def _generate_op_defs(bq_module: data_models.BQModule) -> None: - if not bq_module.functions: - # If there are no function definitions, do not generate file without Python code. - return - - content = template_renderer.render_operation(bq_module) - output_file = constants.OUTPUT_DIR.joinpath(bq_module.module_path).with_suffix( - ".py" - ) - - _write_file(content, output_file, constants.OUTPUT_DIR.parent) - - -def _generate_tests(bq_module: data_models.BQModule) -> None: - if not bq_module.functions: - # If there are no function definitions, do not generate file without Python code. - return - - content = template_renderer.render_tests(bq_module) - output_file = constants.TEST_OUTPUT_DIR.joinpath( - bq_module.module_path.with_name(f"test_{bq_module.module_path.name}") - ).with_suffix(".py") - - _write_file(content, output_file, constants.TEST_OUTPUT_DIR.parent) - - -def _generate_accesor(bq_modules: Sequence[data_models.BQModule]) -> None: - (core_content, pd_content, bf_content) = template_renderer.render_accessor( - bq_modules - ) - - core_output_file = ( - constants.CODE_ROOT / "extensions" / "core" / "series_accessor.py" - ) - _write_file(core_content, core_output_file, constants.CODE_ROOT) - - pd_output_file = ( - constants.CODE_ROOT / "extensions" / "pandas" / "series_accessor.py" - ) - _write_file(pd_content, pd_output_file, constants.CODE_ROOT) - - bf_output_file = ( - constants.CODE_ROOT / "extensions" / "bigframes" / "series_accessor.py" - ) - _write_file(bf_content, bf_output_file, constants.CODE_ROOT) - - -def generate(bq_modules: Sequence[data_models.BQModule]) -> None: - for bq_module in bq_modules: - _generate_op_defs(bq_module) - _generate_tests(bq_module) - - _generate_accesor(bq_modules) - - # Ruff format - _run_ruff() diff --git a/scripts/bigquery_generator/template_renderer.py b/scripts/bigquery_generator/template_renderer.py deleted file mode 100644 index a4e8461286d..00000000000 --- a/scripts/bigquery_generator/template_renderer.py +++ /dev/null @@ -1,334 +0,0 @@ -# Copyright 2026 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. - - -""" -Renders jinja template with module data parsed from yaml. -""" - -from typing import Sequence - -import jinja2 - -from . import constants, data_models - - -def _load_templates() -> dict[str, jinja2.Template]: - env = jinja2.Environment( - loader=jinja2.FileSystemLoader(constants.SCRIPTS_DIRECTORY / "templates"), - trim_blocks=True, - lstrip_blocks=True, - ) - return { - "operation": env.get_template("operation.py.j2"), - "test_operation": env.get_template("test_operation.py.j2"), - "license": env.get_template("license.py.j2"), - "signature_def": env.get_template("signature_def.py.j2"), - "core_series_accessor": env.get_template("core_series_accessor.py.j2"), - "bigframes_series_accessor": env.get_template( - "bigframes_series_accessor.py.j2" - ), - "pandas_series_accessor": env.get_template("pandas_series_accessor.py.j2"), - } - - -TEMPLATES: dict[str, jinja2.Template] = _load_templates() - - -def _unwrap_list_type(yaml_type: str) -> str | None: - if yaml_type.startswith("list<") and yaml_type.endswith(">"): - return yaml_type[5:-1] - return None - - -def _try_get_concrete_type_expr(yaml_type: str) -> str | None: - if yaml_type in constants.DTYPE_MAP: - return constants.DTYPE_MAP[yaml_type] - inner = _unwrap_list_type(yaml_type) - if inner and inner in constants.DTYPE_MAP: - # TODO (b/540011825): Support recursive type parsing - return f"dtypes.list_type({constants.DTYPE_MAP[inner]})" - return None - - -def _get_concrete_type_expr(yaml_type: str) -> str: - expr = _try_get_concrete_type_expr(yaml_type) - if expr is None: - raise ValueError(f"Not a concrete type: {yaml_type}") - return expr - - -def _is_concrete_type(yaml_type: str) -> bool: - return _try_get_concrete_type_expr(yaml_type) is not None - - -def _validate_type(yaml_type: str) -> None: - if yaml_type in ("any1", "struct") or yaml_type in constants.DTYPE_MAP: - return - inner = _unwrap_list_type(yaml_type) - if inner is not None: - if inner == "any1" or inner in constants.DTYPE_MAP: - return - raise ValueError(f"Unsupported inner type: {inner}") - raise ValueError(f"Unsupported type: {yaml_type}") - - -def _validate_types(impls: Sequence[data_models.BQFuncImpl]) -> None: - for impl in impls: - for arg in impl.args: - _validate_type(arg.value) - _validate_type(impl.return_type) - - -def render_signature_def( - bq_func: data_models.BQFunc, -) -> tuple[str, str | None]: - """ - Returns the signature function name and its definition. - If the signature function can be inlined, the first return value is the lambda, - and the second value is None. - - Examples: - Inlined signature function: - ("lambda *args: dtypes.FLOAT64_DTYPE", None) - - Custom signature function definition: - ("_ABS_SIG", "def _ABS_SIG(*args): ...") - """ - return_types = {impl.return_type for impl in bq_func.impls} - # Optimization: if all impls return the same concrete type, - # inline the signature function as a lambda - if len(return_types) == 1: - ret_type = next(iter(return_types)) - if _is_concrete_type(ret_type): - sig_expr = f"lambda *args: {_get_concrete_type_expr(ret_type)}" - return sig_expr, None - - _validate_types(bq_func.impls) - - sig_func_name = f"_{bq_func.op_base_name.upper()}_SIG" - - max_args = max(len(impl.args) for impl in bq_func.impls) - - rendered = TEMPLATES["signature_def"].render( - func_name=sig_func_name, - max_args=max_args, - impls=bq_func.impls, - sql_name=bq_func.name, - dtype_map=constants.DTYPE_MAP, - ) - - return sig_func_name, rendered - - -def _get_bigframes_func_args( - bq_func: data_models.BQFunc, -) -> tuple[data_models.BigFramesFuncArg, ...]: - """ - Coalesces arguments from all the signatures of this function, - and return them in the order of appearance in the yaml file - """ - args_by_name: dict[str, data_models.BigFramesFuncArgBuilder] = {} - arg_order: list[str] = [] - arg_appearances: dict[str, int] = {} - for impl in bq_func.impls: - seen_in_impl = set() - for bq_func_arg in impl.args: - name = bq_func_arg.name - seen_in_impl.add(name) - if name not in args_by_name: - args_by_name[name] = data_models.BigFramesFuncArgBuilder( - name=name, - types=set(), - optional=bq_func_arg.optional, - keyword_only=bq_func_arg.keyword_only, - ) - arg_order.append(name) - else: - # If it was marked optional or keyword_only in any previous impl, keep it. - # Or if this signature marks it as optional/keyword_only, update it. - if bq_func_arg.optional: - args_by_name[name].optional = True - if bq_func_arg.keyword_only: - args_by_name[name].keyword_only = True - args_by_name[name].types.add(bq_func_arg.value) - for name in seen_in_impl: - arg_appearances[name] = arg_appearances.get(name, 0) + 1 - - # If an argument is not in all impls, it must be optional overall - num_impls = len(bq_func.impls) - for name, count in arg_appearances.items(): - if count < num_impls: - args_by_name[name].optional = True - - return tuple(args_by_name[name].build() for name in arg_order) - - -def _to_bigframes_op(bq_func: data_models.BQFunc) -> data_models.BigFramesOp: - arg_specs = [] - for bf_func_arg in _get_bigframes_func_args(bq_func): - spec = "googlesql.ArgSpec(" - if bf_func_arg.keyword_only: - spec += f'arg_name="{bf_func_arg.name}", ' - if bf_func_arg.optional: - spec += "optional=True, " - spec = spec.rstrip(", ") + ")" - arg_specs.append(spec) - - arg_specs_str = ", ".join(arg_specs) - if len(arg_specs) == 1: - arg_specs_str += "," - - (signature, signature_definition) = render_signature_def(bq_func) - - return data_models.BigFramesOp( - internal_name=f"_{bq_func.op_base_name.upper()}_OP", - sql_name=bq_func.name.upper(), - arg_specs=arg_specs_str, - signature=signature, - signature_definition=signature_definition, - ) - - -def _to_bigframes_func( - bq_func: data_models.BQFunc, import_module: str | None = None -) -> data_models.BigFramesFunc: - python_name = bq_func.op_base_name - if python_name in constants.PYTHON_BUILTINS: - python_name = python_name + "_" - - return data_models.BigFramesFunc( - name=python_name, - op_name=f"_{bq_func.op_base_name.upper()}_OP", - description=bq_func.description, - args=_get_bigframes_func_args(bq_func), - series_accessor_arg=bq_func.series_accessor_arg, - import_module=import_module, - ) - - -def render_license() -> str: - return TEMPLATES["license"].render() - - -def render_operation( - bq_module: data_models.BQModule, -) -> str: - ops: list[data_models.BigFramesOp] = [] - functions: list[data_models.BigFramesFunc] = [] - - for bq_func in bq_module.functions: - ops.append(_to_bigframes_op(bq_func)) - functions.append(_to_bigframes_func(bq_func)) - - return TEMPLATES["operation"].render( - yaml_path=bq_module.yaml_file.relative_to(constants.PACKAGE_ROOT), - script_path=constants.SCRIPT_PATH_RELATIVE, - ops=ops, - functions=functions, - ) - - -def render_tests(bq_module: data_models.BQModule) -> str: - import_path = "bigframes.operations.googlesql." + ".".join( - bq_module.module_path.parts - ) - functions: list[data_models.BigFramesFunc] = [] - for bq_func in bq_module.functions: - functions.append(_to_bigframes_func(bq_func)) - - return TEMPLATES["test_operation"].render( - yaml_path=bq_module.yaml_file.relative_to(constants.PACKAGE_ROOT), - script_path=constants.SCRIPT_PATH_RELATIVE, - import_path=import_path, - short_name=bq_module.module_path.name, - is_global=bq_module.is_global, - functions=functions, - ) - - -def _create_accessor_class_name(namespace: tuple[str, ...], prefix: str = "") -> str: - if not namespace: - return f"{prefix}BigQuerySeriesAccessor" - camel_parts = [part.capitalize() for part in namespace] - return f"{prefix}{''.join(camel_parts)}SeriesAccessor" - - -def render_accessor( - bq_modules: Sequence[data_models.BQModule], -) -> tuple[str, str, str]: - """ - Returns the content for core accessor, pandas accessor and BF accessor - """ - - namespaces: set[tuple[str, ...]] = set() - for bq_module in bq_modules: - for i in range(len(bq_module.namespace) + 1): - namespaces.add(bq_module.namespace[:i]) - - sorted_namespaces = sorted(list(namespaces), key=lambda ns: (len(ns), ns)) - - accessors: list[data_models.Accessor] = [] - accessor_lookup_table: dict[tuple[str, ...], data_models.Accessor] = {} - for namespace in sorted_namespaces: - accessor = data_models.Accessor( - class_name=_create_accessor_class_name(namespace), - bigframes_class_name=_create_accessor_class_name( - namespace, prefix="Bigframes" - ), - pandas_class_name=_create_accessor_class_name(namespace, prefix="Pandas"), - is_root=len(namespace) == 0, - description=( - f"Series accessor for BigQuery {'.'.join(namespace)} functions." - if namespace - else "Series accessor for BigQuery functions." - ), - children=[], - functions=[], - ) - accessors.append(accessor) - accessor_lookup_table[namespace] = accessor - - # Establish parent-child relations - if len(namespace) > 0: - accessor.prop_name = namespace[-1] - parent_namespace = namespace[:-1] - accessor_lookup_table[parent_namespace].children.append(accessor) - - # Arrange functions by namespaces - for bq_module in bq_modules: - module_parts = bq_module.module_path.parts - for bq_func in bq_module.functions: - if bq_func.series_accessor_arg is None: - continue - bf_func = _to_bigframes_func( - bq_func, - import_module=f"bigframes.operations.googlesql.{'.'.join(module_parts)}", - ) - accessor_lookup_table[bq_module.namespace].functions.append(bf_func) - - core_content = TEMPLATES["core_series_accessor"].render( - script_path=constants.SCRIPT_PATH_RELATIVE, - namespaces=accessors, - ) - - pandas_content = TEMPLATES["pandas_series_accessor"].render( - script_path=constants.SCRIPT_PATH_RELATIVE, namespaces=accessors - ) - - bigframes_content = TEMPLATES["bigframes_series_accessor"].render( - script_path=constants.SCRIPT_PATH_RELATIVE, namespaces=accessors - ) - - return core_content, pandas_content, bigframes_content diff --git a/scripts/bigquery_generator/yaml_parser.py b/scripts/bigquery_generator/yaml_parser.py deleted file mode 100644 index 6ef1d5d9236..00000000000 --- a/scripts/bigquery_generator/yaml_parser.py +++ /dev/null @@ -1,64 +0,0 @@ -# Copyright 2026 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - - -import pathlib -from typing import Any - -import yaml - -from . import data_models - - -def _build_func_arg_ir(arg_data: Any) -> data_models.BQFuncArg: - return data_models.BQFuncArg( - name=arg_data["name"], - value=arg_data["value"], - optional=arg_data["optional"], - keyword_only=arg_data["keyword_only"], - ) - - -def _build_func_impl_ir(impl_data: Any) -> data_models.BQFuncImpl: - return data_models.BQFuncImpl( - args=tuple(_build_func_arg_ir(arg) for arg in impl_data["args"]), - return_type=impl_data["return"], - ) - - -def _build_func_ir(func_data: Any) -> data_models.BQFunc: - return data_models.BQFunc( - name=func_data["name"], - description=func_data["description"], - impls=tuple(_build_func_impl_ir(impl) for impl in func_data["impls"]), - series_accessor_arg=func_data.get("series_accessor_arg", None), - ) - - -def parse_yaml(yaml_file: pathlib.Path) -> data_models.BQModule: - print(f"Parsing {yaml_file}...") - - with open(yaml_file, "r", encoding="utf-8") as f: - data = yaml.safe_load(f) - - functions: tuple[data_models.BQFunc, ...] = () - if isinstance(data, dict) and "scalar_functions" in data: - functions = tuple( - _build_func_ir(func_data) for func_data in data["scalar_functions"] - ) - - return data_models.BQModule( - yaml_file=yaml_file, - functions=functions, - ) diff --git a/scripts/conftest.py b/scripts/conftest.py deleted file mode 100644 index 0d55bd4b478..00000000000 --- a/scripts/conftest.py +++ /dev/null @@ -1,8 +0,0 @@ -import sys -from pathlib import Path - -# inserts scripts into path so that tests can import -project_root = Path(__file__).parent.parent -scripts_dir = project_root / "scripts" - -sys.path.insert(0, str(scripts_dir)) diff --git a/scripts/create_gcs.py b/scripts/create_gcs.py deleted file mode 100644 index bdb8a23ddc9..00000000000 --- a/scripts/create_gcs.py +++ /dev/null @@ -1,96 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# This script create the bigtable resources required for -# bigframes.streaming testing if they don't already exist - -import os -import sys -from pathlib import Path - -import google.cloud.exceptions as exceptions -import google.cloud.storage as gcs -from google.cloud.storage import transfer_manager - -PROJECT_ID = os.getenv("GOOGLE_CLOUD_PROJECT") - -if not PROJECT_ID: - print( - "Please set GOOGLE_CLOUD_PROJECT environment variable before running.", - file=sys.stderr, - ) - sys.exit(1) - - -def create_bucket(client: gcs.Client) -> gcs.Bucket: - bucket_name = "bigframes_blob_test" - - print(f"Creating bucket: {bucket_name}") - try: - bucket = client.create_bucket(bucket_name) - print(f"Bucket {bucket_name} created. ") - - except exceptions.Conflict: - print(f"Bucket {bucket_name} already exists.") - bucket = client.bucket(bucket_name) - - return bucket - - -def upload_data(bucket: gcs.Bucket): - # from https://cloud.google.com/storage/docs/samples/storage-transfer-manager-upload-directory - source_directory = "scripts/data/" - workers = 8 - - # First, recursively get all files in `directory` as Path objects. - directory_as_path_obj = Path(source_directory) - paths = directory_as_path_obj.rglob("*") - - # Filter so the list only includes files, not directories themselves. - file_paths = [path for path in paths if path.is_file()] - - # These paths are relative to the current working directory. Next, make them - # relative to `directory` - relative_paths = [path.relative_to(source_directory) for path in file_paths] - - # Finally, convert them all to strings. - string_paths = [str(path) for path in relative_paths] - - print("Found {} files.".format(len(string_paths))) - - # Start the upload. - results = transfer_manager.upload_many_from_filenames( - bucket, string_paths, source_directory=source_directory, max_workers=workers - ) - - for name, result in zip(string_paths, results): - # The results list is either `None` or an exception for each filename in - # the input list, in order. - - if isinstance(result, Exception): - print("Failed to upload {} due to exception: {}".format(name, result)) - else: - print("Uploaded {} to {}.".format(name, bucket.name)) - - -def main(): - client = gcs.Client(project=PROJECT_ID) - - bucket = create_bucket(client) - - upload_data(bucket) - - -if __name__ == "__main__": - main() diff --git a/scripts/create_load_test_tables.py b/scripts/create_load_test_tables.py deleted file mode 100644 index d94a33aa5cc..00000000000 --- a/scripts/create_load_test_tables.py +++ /dev/null @@ -1,109 +0,0 @@ -# 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. -# You may obtain a copy of the License at -# -# https://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 math -import os -import pathlib -import sys - -import google.cloud.bigquery as bigquery - -REPO_ROOT = pathlib.Path(__file__).parent.parent - -PROJECT_ID = os.getenv("GOOGLE_CLOUD_PROJECT") - -if not PROJECT_ID: - print( - "Please set GOOGLE_CLOUD_PROJECT environment variable before running.", - file=sys.stderr, - ) - sys.exit(1) - -DATASET_ID = f"{PROJECT_ID}.load_testing" -TABLE_ID = f"{DATASET_ID}.scalars" -TABLE_ID_FORMAT = f"{DATASET_ID}.scalars_{{size}}" - -KB_BYTES = 1000 -MB_BYTES = 1000 * KB_BYTES -GB_BYTES = 1000 * MB_BYTES -TB_BYTES = 1000 * GB_BYTES -SIZES = ( - ("1mb", MB_BYTES), - ("10mb", 10 * MB_BYTES), - ("100mb", 100 * MB_BYTES), - ("1gb", GB_BYTES), - ("10gb", 10 * GB_BYTES), - ("100gb", 100 * GB_BYTES), - ("1tb", TB_BYTES), -) -SCHEMA_PATH = REPO_ROOT / "tests" / "data" / "scalars_schema.json" -DATA_PATH = REPO_ROOT / "tests" / "data" / "scalars.jsonl" -BQCLIENT = bigquery.Client() - - -def create_dataset(): - dataset = bigquery.Dataset(DATASET_ID) - BQCLIENT.create_dataset(dataset, exists_ok=True) - - -def load_scalars_table(): - schema = BQCLIENT.schema_from_json(SCHEMA_PATH) - job_config = bigquery.LoadJobConfig() - job_config.schema = schema - job_config.write_disposition = bigquery.WriteDisposition.WRITE_TRUNCATE - job_config.source_format = bigquery.SourceFormat.NEWLINE_DELIMITED_JSON - - print(f"Creating {TABLE_ID}") - with open(DATA_PATH, "rb") as data_file: - BQCLIENT.load_table_from_file( - data_file, - TABLE_ID, - job_config=job_config, - ).result() - - -def multiply_table(previous_table_id, target_table_id, multiplier): - clauses = [f"SELECT * FROM `{previous_table_id}`"] * multiplier - query = " UNION ALL ".join(clauses) - job_config = bigquery.QueryJobConfig() - job_config.destination = target_table_id - job_config.write_disposition = bigquery.WriteDisposition.WRITE_TRUNCATE - print(f"Creating {target_table_id}, {multiplier} x {previous_table_id}") - BQCLIENT.query_and_wait(query, job_config=job_config) - - -def create_tables(): - base_table = BQCLIENT.get_table(TABLE_ID) - previous_bytes = base_table.num_bytes - previous_table_id = TABLE_ID - - for table_suffix, target_bytes in SIZES: - # Make sure we exceed the desired bytes by adding to the multiplier. - multiplier = math.ceil(target_bytes / previous_bytes) + 1 - target_table_id = TABLE_ID_FORMAT.format(size=table_suffix) - multiply_table(previous_table_id, target_table_id, multiplier) - - table = BQCLIENT.get_table(target_table_id) - previous_bytes = table.num_bytes - previous_table_id = target_table_id - - -def main(): - create_dataset() - load_scalars_table() - create_tables() - - -if __name__ == "__main__": - main() diff --git a/scripts/create_read_gbq_colab_benchmark_tables.py b/scripts/create_read_gbq_colab_benchmark_tables.py deleted file mode 100644 index 727a1e116ac..00000000000 --- a/scripts/create_read_gbq_colab_benchmark_tables.py +++ /dev/null @@ -1,541 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://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 argparse -import base64 -import concurrent.futures -import datetime -import json -import math -import time -from typing import Any, Iterable, MutableSequence, Sequence - -import numpy as np -from google.cloud import bigquery - -# --- Input Data --- -# Generated by querying bigquery-magics usage. See internal issue b/420984164. -TABLE_STATS: dict[str, list[float]] = { - "percentile": [9, 19, 29, 39, 49, 59, 69, 79, 89, 99], - "materialized_or_scanned_bytes": [ - 0.0, - 0.0, - 4102.0, - 76901.0, - 351693.0, - 500000.0, - 500000.0, - 1320930.0, - 17486432.0, - 1919625975.0, - ], - "avg_row_bytes": [ - 0.00014346299635435792, - 0.005370969708923197, - 0.3692756731526246, - 4.079344721151818, - 7.5418, - 12.528863516404146, - 22.686258546389798, - 48.69689224091025, - 100.90817356205852, - 2020, - ], - "materialized_mb": [ - 0.0, - 0.0, - 0.004102, - 0.076901, - 0.351693, - 0.5, - 0.5, - 1.32093, - 17.486432, - 1919.625975, - ], -} - -BIGQUERY_DATA_TYPE_SIZES = { - "BOOL": 1, - "DATE": 8, - "FLOAT64": 8, - "INT64": 8, - "DATETIME": 8, - "TIMESTAMP": 8, - "TIME": 8, - "NUMERIC": 16, - # Flexible types. - # JSON base size is its content, BYTES/STRING have 2 byte overhead + content - "JSON": 0, - "BYTES": 2, - "STRING": 2, -} -FIXED_TYPES = [ - "BOOL", - "INT64", - "FLOAT64", - "NUMERIC", - "DATE", - "DATETIME", - "TIMESTAMP", - "TIME", -] -FLEXIBLE_TYPES = ["STRING", "BYTES", "JSON"] - -JSON_CHAR_LIST = list("abcdef") -STRING_CHAR_LIST = list("abcdefghijklmnopqrstuvwxyz0123456789") - -# --- Helper Functions --- - - -def get_bq_schema(target_row_size_bytes: int) -> Sequence[tuple[str, str, int | None]]: - """ - Determines the BigQuery table schema to match the target_row_size_bytes. - Prioritizes fixed-size types for diversity, then uses flexible types. - Returns a list of tuples: (column_name, type_name, length_for_flexible_type). - Length is None for fixed-size types. - """ - schema: MutableSequence[tuple[str, str, int | None]] = [] - current_size = 0 - col_idx = 0 - - for bq_type in FIXED_TYPES: - # For simplicity, we'll allow slight overage if only fixed fields are chosen. - if current_size >= target_row_size_bytes: - break - - type_size = BIGQUERY_DATA_TYPE_SIZES[bq_type] - schema.append((f"col_{bq_type.lower()}_{col_idx}", bq_type, None)) - current_size += type_size - col_idx += 1 - - # Use flexible-size types to fill remaining space - - # Attempt to add one of each flexible type if space allows - if current_size < target_row_size_bytes: - remaining_bytes_for_content = target_row_size_bytes - current_size - - # For simplicity, divide the remaing bytes evenly across the flexible - # columns. - target_size = int(math.ceil(remaining_bytes_for_content / len(FLEXIBLE_TYPES))) - - for bq_type in FLEXIBLE_TYPES: - base_cost = BIGQUERY_DATA_TYPE_SIZES[bq_type] - min_content_size = max(0, target_size - base_cost) - - schema.append( - (f"col_{bq_type.lower()}_{col_idx}", bq_type, min_content_size) - ) - current_size += base_cost + min_content_size - col_idx += 1 - - return schema - - -def generate_bool_batch( - num_rows: int, rng: np.random.Generator, content_length: int | None = None -) -> np.ndarray: - return rng.choice([True, False], size=num_rows) - - -def generate_int64_batch( - num_rows: int, rng: np.random.Generator, content_length: int | None = None -) -> np.ndarray: - return rng.integers(-(10**18), 10**18, size=num_rows, dtype=np.int64) - - -def generate_float64_batch( - num_rows: int, rng: np.random.Generator, content_length: int | None = None -) -> np.ndarray: - return rng.random(size=num_rows) * 2 * 10**10 - 10**10 - - -def generate_numeric_batch( - num_rows: int, rng: np.random.Generator, content_length: int | None = None -) -> np.ndarray: - raw_numerics = rng.random(size=num_rows) * 2 * 10**28 - 10**28 - format_numeric_vectorized = np.vectorize(lambda x: f"{x:.9f}") - return format_numeric_vectorized(raw_numerics) - - -def generate_date_batch( - num_rows: int, rng: np.random.Generator, content_length: int | None = None -) -> np.ndarray: - start_date_ord = datetime.date(1, 1, 1).toordinal() - max_days = (datetime.date(9999, 12, 31) - datetime.date(1, 1, 1)).days - day_offsets = rng.integers(0, max_days + 1, size=num_rows) - date_ordinals = start_date_ord + day_offsets - return np.array( - [ - datetime.date.fromordinal(int(ordinal)).isoformat() - for ordinal in date_ordinals - ] - ) - - -def generate_numpy_datetimes(num_rows: int, rng: np.random.Generator) -> np.ndarray: - # Generate seconds from a broad range (e.g., year 1 to 9999) - # Note: Python's datetime.timestamp() might be limited by system's C mktime. - # For broader range with np.datetime64, it's usually fine. - # Let's generate epoch seconds relative to Unix epoch for np.datetime64 compatibility - min_epoch_seconds = int( - datetime.datetime(1, 1, 1, 0, 0, 0, tzinfo=datetime.timezone.utc).timestamp() - ) - # Max for datetime64[s] is far out, but let's bound it reasonably for BQ. - max_epoch_seconds = int( - datetime.datetime( - 9999, 12, 28, 23, 59, 59, tzinfo=datetime.timezone.utc - ).timestamp() - ) - - epoch_seconds = rng.integers( - min_epoch_seconds, - max_epoch_seconds + 1, - size=num_rows, - dtype=np.int64, - ) - microseconds_offset = rng.integers(0, 1000000, size=num_rows, dtype=np.int64) - - # Create datetime64[s] from epoch seconds and add microseconds as timedelta64[us] - np_timestamps_s = epoch_seconds.astype("datetime64[s]") - np_microseconds_td = microseconds_offset.astype("timedelta64[us]") - return np_timestamps_s + np_microseconds_td - - -def generate_datetime_batch( - num_rows: int, rng: np.random.Generator, content_length: int | None = None -) -> np.ndarray: - np_datetimes = generate_numpy_datetimes(num_rows, rng) - - # np.datetime_as_string produces 'YYYY-MM-DDTHH:MM:SS.ffffff' - # BQ DATETIME typically uses a space separator: 'YYYY-MM-DD HH:MM:SS.ffffff' - datetime_strings = np.datetime_as_string(np_datetimes, unit="us") - return np.array([s.replace("T", " ") for s in datetime_strings]) - - -def generate_timestamp_batch( - num_rows: int, rng: np.random.Generator, content_length: int | None = None -) -> np.ndarray: - np_datetimes = generate_numpy_datetimes(num_rows, rng) - - # Convert to string with UTC timezone indicator - # np.datetime_as_string with timezone='UTC' produces 'YYYY-MM-DDTHH:MM:SS.ffffffZ' - # BigQuery generally accepts this for TIMESTAMP. - return np.datetime_as_string(np_datetimes, unit="us", timezone="UTC") - - -def generate_time_batch( - num_rows: int, rng: np.random.Generator, content_length: int | None = None -) -> np.ndarray: - hours = rng.integers(0, 24, size=num_rows) - minutes = rng.integers(0, 60, size=num_rows) - seconds = rng.integers(0, 60, size=num_rows) - microseconds = rng.integers(0, 1000000, size=num_rows) - time_list = [ - datetime.time(hours[i], minutes[i], seconds[i], microseconds[i]).isoformat() - for i in range(num_rows) - ] - return np.array(time_list) - - -def generate_json_row(content_length: int, rng: np.random.Generator) -> str: - json_val_len = max(0, content_length - 5) - json_val_chars = rng.choice(JSON_CHAR_LIST, size=json_val_len) - json_obj = {"k": "".join(json_val_chars)} - return json.dumps(json_obj) - - -def generate_json_batch( - num_rows: int, rng: np.random.Generator, content_length: int | None = None -) -> np.ndarray: - content_length = content_length if content_length is not None else 10 - json_list = [ - generate_json_row(content_length=content_length, rng=rng) - for _ in range(num_rows) - ] - return np.array(json_list) - - -def generate_string_batch( - num_rows: int, rng: np.random.Generator, content_length: int | None = None -) -> np.ndarray: - content_length = content_length if content_length is not None else 1 - content_length = max(0, content_length) - chars_array = rng.choice(STRING_CHAR_LIST, size=(num_rows, content_length)) - return np.array(["".join(row_chars) for row_chars in chars_array]) - - -def generate_bytes_batch( - num_rows: int, rng: np.random.Generator, content_length: int | None = None -) -> np.ndarray: - content_length = content_length if content_length is not None else 1 - content_length = max(0, content_length) - return np.array( - [ - base64.b64encode(rng.bytes(content_length)).decode("utf-8") - for _ in range(num_rows) - ] - ) - - -BIGQUERY_DATA_TYPE_GENERATORS = { - "BOOL": generate_bool_batch, - "DATE": generate_date_batch, - "FLOAT64": generate_float64_batch, - "INT64": generate_int64_batch, - "DATETIME": generate_datetime_batch, - "TIMESTAMP": generate_timestamp_batch, - "TIME": generate_time_batch, - "NUMERIC": generate_numeric_batch, - "JSON": generate_json_batch, - "BYTES": generate_bytes_batch, - "STRING": generate_string_batch, -} - - -def generate_work_items( - table_id: str, - schema: Sequence[tuple[str, str, int | None]], - num_rows: int, - batch_size: int, -) -> Iterable[tuple[str, Sequence[tuple[str, str, int | None]], int]]: - """ - Generates work items of appropriate batch sizes. - """ - if num_rows == 0: - return - - generated_rows_total = 0 - - while generated_rows_total < num_rows: - current_batch_size = min(batch_size, num_rows - generated_rows_total) - if current_batch_size == 0: - break - - yield (table_id, schema, current_batch_size) - generated_rows_total += current_batch_size - - -def generate_batch( - schema: Sequence[tuple[str, str, int | None]], - num_rows: int, - rng: np.random.Generator, -) -> list[dict[str, Any]]: - col_names_ordered = [s[0] for s in schema] - - columns_data_batch = {} - for col_name, bq_type, length in schema: - generate_batch = BIGQUERY_DATA_TYPE_GENERATORS[bq_type] - columns_data_batch[col_name] = generate_batch( - num_rows, rng, content_length=length - ) - - # Turn numpy objects into Python objects. - # https://stackoverflow.com/a/32850511/101923 - columns_data_batch_json = {} - for column in columns_data_batch: - columns_data_batch_json[column] = columns_data_batch[column].tolist() - - # Assemble batch of rows - batch_data = [] - for i in range(num_rows): - row = { - col_name: columns_data_batch_json[col_name][i] - for col_name in col_names_ordered - } - batch_data.append(row) - - return batch_data - - -def generate_and_load_batch( - client: bigquery.Client, - table_id: str, - schema_def: Sequence[tuple[str, str, int | None]], - num_rows: int, - rng: np.random.Generator, -): - bq_schema = [] - for col_name, type_name, _ in schema_def: - bq_schema.append(bigquery.SchemaField(col_name, type_name)) - table = bigquery.Table(table_id, schema=bq_schema) - - generated_data_chunk = generate_batch(schema_def, num_rows, rng) - errors = client.insert_rows_json(table, generated_data_chunk) - if errors: - raise ValueError(f"Encountered errors while inserting sub-batch: {errors}") - - -def create_and_load_table( - client: bigquery.Client | None, - project_id: str, - dataset_id: str, - table_name: str, - schema_def: Sequence[tuple[str, str, int | None]], - num_rows: int, - executor: concurrent.futures.Executor, -): - """Creates a BigQuery table and loads data into it by consuming a data generator.""" - - if not client: - print(f"Simulating: Generated schema: {schema_def}") - return - - # BQ client library streaming insert batch size (rows per API call) - # This is different from data_gen_batch_size which is for generating data. - # We can make BQ_LOAD_BATCH_SIZE smaller than data_gen_batch_size if needed. - BQ_LOAD_BATCH_SIZE = 500 - - # Actual BigQuery operations occur here because both project_id and dataset_id are provided - print( - f"Attempting BigQuery operations for table {table_name} in project '{project_id}', dataset '{dataset_id}'." - ) - table_id = f"{project_id}.{dataset_id}.{table_name}" - - bq_schema = [] - for col_name, type_name, _ in schema_def: - bq_schema.append(bigquery.SchemaField(col_name, type_name)) - - table = bigquery.Table(table_id, schema=bq_schema) - print(f"(Re)creating table {table_id}...") - table = client.create_table(table, exists_ok=True) - print(f"Table {table_id} created successfully or already exists.") - - # Query in case there's something in the streaming buffer already. - table_rows = next( - iter(client.query_and_wait(f"SELECT COUNT(*) FROM `{table_id}`")) - )[0] - print(f"Table {table_id} has {table_rows} rows.") - num_rows = max(0, num_rows - table_rows) - - if num_rows <= 0: - print(f"No rows to load. Requested {num_rows} rows. Skipping.") - return - - print(f"Starting to load {num_rows} rows into {table_id} in batches...") - - previous_status_time = 0.0 - generated_rows_total = 0 - - for completed_rows in executor.map( - worker_process_item, - generate_work_items( - table_id, - schema_def, - num_rows, - BQ_LOAD_BATCH_SIZE, - ), - ): - generated_rows_total += completed_rows - - current_time = time.monotonic() - if current_time - previous_status_time > 5: - print(f"Wrote {generated_rows_total} out of {num_rows} rows.") - previous_status_time = current_time - - -worker_client: bigquery.Client | None = None -worker_rng: np.random.Generator | None = None - - -def worker_initializer(project_id: str | None): - global worker_client, worker_rng - - # One client per process, since multiprocessing and client connections don't - # play nicely together. - if project_id is not None: - worker_client = bigquery.Client(project=project_id) - - worker_rng = np.random.default_rng() - - -def worker_process_item( - work_item: tuple[str, Sequence[tuple[str, str, int | None]], int], -): - global worker_client, worker_rng - - if worker_client is None or worker_rng is None: - raise ValueError("Worker not initialized.") - - table_id, schema_def, num_rows = work_item - generate_and_load_batch(worker_client, table_id, schema_def, num_rows, worker_rng) - return num_rows - - -# --- Main Script Logic --- -def main(): - """Main function to create and populate BigQuery tables.""" - - parser = argparse.ArgumentParser( - description="Generate and load BigQuery benchmark tables." - ) - parser.add_argument( - "-p", - "--project_id", - type=str, - default=None, - help="Google Cloud Project ID. If not provided, script runs in simulation mode.", - ) - parser.add_argument( - "-d", - "--dataset_id", - type=str, - default=None, - help="BigQuery Dataset ID within the project. If not provided, script runs in simulation mode.", - ) - args = parser.parse_args() - - num_percentiles = len(TABLE_STATS["percentile"]) - client = None - - if args.project_id and args.dataset_id: - client = bigquery.Client(project=args.project_id) - dataset = bigquery.Dataset(f"{args.project_id}.{args.dataset_id}") - client.create_dataset(dataset, exists_ok=True) - - with concurrent.futures.ProcessPoolExecutor( - initializer=worker_initializer, initargs=(args.project_id,) - ) as executor: - for i in range(num_percentiles): - percentile = TABLE_STATS["percentile"][i] - avg_row_bytes_raw = TABLE_STATS["avg_row_bytes"][i] - table_bytes_raw = TABLE_STATS["materialized_or_scanned_bytes"][i] - - target_table_bytes = max(1, int(math.ceil(table_bytes_raw))) - target_row_bytes = max(1, int(math.ceil(avg_row_bytes_raw))) - num_rows = max(1, int(math.ceil(target_table_bytes / target_row_bytes))) - - table_name = f"percentile_{percentile:02d}" - print(f"\n--- Processing Table: {table_name} ---") - print(f"Target average row bytes (rounded up): {target_row_bytes}") - print(f"Number of rows (rounded up): {num_rows}") - - schema_definition = get_bq_schema(target_row_bytes) - print(f"Generated Schema: {schema_definition}") - - create_and_load_table( - client, - args.project_id or "", - args.dataset_id or "", - table_name, - schema_definition, - num_rows, - executor, - ) - - -if __name__ == "__main__": - main() diff --git a/scripts/create_read_gbq_colab_benchmark_tables_test.py b/scripts/create_read_gbq_colab_benchmark_tables_test.py deleted file mode 100644 index 56c9cb2bc56..00000000000 --- a/scripts/create_read_gbq_colab_benchmark_tables_test.py +++ /dev/null @@ -1,334 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://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 base64 -import datetime -import json -import math -import re - -import numpy as np -import pytest - -# Assuming the script to be tested is in the same directory or accessible via PYTHONPATH -from create_read_gbq_colab_benchmark_tables import ( - BIGQUERY_DATA_TYPE_SIZES, - generate_batch, - generate_work_items, - get_bq_schema, -) - - -# Helper function to calculate estimated row size from schema -def _calculate_row_size(schema: list[tuple[str, str, int | None]]) -> int: - """Calculates the estimated byte size of a row based on the schema. - Note: This is a simplified calculation for testing and might not perfectly - match BigQuery's internal storage, especially for complex types or NULLs. - """ - size = 0 - for _, bq_type, length in schema: - if bq_type in ["STRING", "BYTES", "JSON"]: - # Base cost (e.g., 2 bytes) + content length - size += BIGQUERY_DATA_TYPE_SIZES[bq_type] + ( - length if length is not None else 0 - ) - elif bq_type in BIGQUERY_DATA_TYPE_SIZES: - size += BIGQUERY_DATA_TYPE_SIZES[bq_type] - else: - raise AssertionError(f"Got unexpected type {bq_type}") - return size - - -# --- Tests for get_bq_schema --- - - -def test_get_bq_schema_zero_bytes(): - assert get_bq_schema(0) == [] - - -def test_get_bq_schema_one_byte(): - schema = get_bq_schema(1) - - assert len(schema) == 1 - assert schema[0][1] == "BOOL" # ('col_bool_fallback_0', 'BOOL', None) or similar - assert _calculate_row_size(schema) == 1 - - -def test_get_bq_schema_exact_fixed_fit(): - # BOOL (1) + INT64 (8) = 9 bytes - target_size = 9 - schema = get_bq_schema(target_size) - - assert len(schema) == 2 - assert schema[0][1] == "BOOL" - assert schema[1][1] == "INT64" - assert _calculate_row_size(schema) == target_size - - -def test_get_bq_schema_needs_flexible_string(): - # Sum of all fixed types: - # BOOL 1, INT64 8, FLOAT64 8, NUMERIC 16, DATE 8, DATETIME 8, TIMESTAMP 8, TIME 8 - # Total = 1+8+8+16+8+8+8+8 = 65 - target_size = 65 + 1 - schema = get_bq_schema(target_size) - - assert _calculate_row_size(schema) == 65 + 2 + 2 + 1 - - string_cols = [s for s in schema if s[1] == "STRING"] - assert len(string_cols) == 1 - assert string_cols[0][2] == 0 - - bytes_cols = [s for s in schema if s[1] == "BYTES"] - assert len(bytes_cols) == 1 - assert bytes_cols[0][2] == 0 - - json_cols = [s for s in schema if s[1] == "JSON"] - assert len(json_cols) == 1 - assert json_cols[0][2] == 1 - - -def test_get_bq_schema_flexible_expansion(): - # Sum of all fixed types: - # BOOL 1, INT64 8, FLOAT64 8, NUMERIC 16, DATE 8, DATETIME 8, TIMESTAMP 8, TIME 8 - # Total = 1+8+8+16+8+8+8+8 = 65 - target_size = 65 + 3 * 5 - schema = get_bq_schema(target_size) - - assert _calculate_row_size(schema) == target_size - - string_cols = [s for s in schema if s[1] == "STRING"] - assert len(string_cols) == 1 - assert string_cols[0][2] == 3 - - bytes_cols = [s for s in schema if s[1] == "BYTES"] - assert len(bytes_cols) == 1 - assert bytes_cols[0][2] == 3 - - json_cols = [s for s in schema if s[1] == "JSON"] - assert len(json_cols) == 1 - assert json_cols[0][2] == 5 - - -def test_get_bq_schema_all_fixed_types_possible(): - # Sum of all fixed types: - # BOOL 1, INT64 8, FLOAT64 8, NUMERIC 16, DATE 8, DATETIME 8, TIMESTAMP 8, TIME 8 - # Total = 1+8+8+16+8+8+8+8 = 65 - target_size = 65 - schema = get_bq_schema(target_size) - - expected_fixed_types = { - "BOOL", - "INT64", - "FLOAT64", - "NUMERIC", - "DATE", - "DATETIME", - "TIMESTAMP", - "TIME", - } - present_types = {s[1] for s in schema} - - assert expected_fixed_types.issubset(present_types) - - # Check if the size is close to target. - # All fixed (65) - calculated_size = _calculate_row_size(schema) - assert calculated_size == target_size - - -def test_get_bq_schema_uniqueness_of_column_names(): - target_size = 100 # A size that generates multiple columns - schema = get_bq_schema(target_size) - - column_names = [s[0] for s in schema] - assert len(column_names) == len(set(column_names)) - - -# --- Tests for generate_work_items --- - - -def test_generate_work_items_zero_rows(): - schema = [("col_int", "INT64", None)] - data_generator = generate_work_items( - "some_table", schema, num_rows=0, batch_size=10 - ) - - # Expect the generator to be exhausted - with pytest.raises(StopIteration): - next(data_generator) - - -def test_generate_work_items_basic_schema_and_batching(): - schema = [("id", "INT64", None), ("is_active", "BOOL", None)] - num_rows = 25 - batch_size = 10 - - generated_rows_count = 0 - batch_count = 0 - for work_item in generate_work_items("some_table", schema, num_rows, batch_size): - table_id, schema_def, num_rows_in_batch = work_item - assert table_id == "some_table" - assert schema_def == schema - assert num_rows_in_batch <= num_rows - assert num_rows_in_batch <= batch_size - batch_count += 1 - generated_rows_count += num_rows_in_batch - - assert generated_rows_count == num_rows - assert batch_count == math.ceil(num_rows / batch_size) # 25/10 = 2.5 -> 3 batches - - -def test_generate_work_items_batch_size_larger_than_num_rows(): - schema = [("value", "FLOAT64", None)] - num_rows = 5 - batch_size = 100 - - generated_rows_count = 0 - batch_count = 0 - for work_item in generate_work_items("some_table", schema, num_rows, batch_size): - table_id, schema_def, num_rows_in_batch = work_item - assert table_id == "some_table" - assert schema_def == schema - assert num_rows_in_batch == num_rows # Should be one batch with all rows - batch_count += 1 - generated_rows_count += num_rows_in_batch - - assert generated_rows_count == num_rows - assert batch_count == 1 - - -def test_generate_work_items_all_datatypes(rng): - schema = [ - ("c_bool", "BOOL", None), - ("c_int64", "INT64", None), - ("c_float64", "FLOAT64", None), - ("c_numeric", "NUMERIC", None), - ("c_date", "DATE", None), - ("c_datetime", "DATETIME", None), - ("c_timestamp", "TIMESTAMP", None), - ("c_time", "TIME", None), - ("c_string", "STRING", 10), - ("c_bytes", "BYTES", 5), - ("c_json", "JSON", 20), # Length for JSON is content hint - ] - num_rows = 3 - batch_size = 2 # To test multiple batches - - total_rows_processed = 0 - for work_item in generate_work_items("some_table", schema, num_rows, batch_size): - table_id, schema_def, num_rows_in_batch = work_item - assert table_id == "some_table" - assert schema_def == schema - assert num_rows_in_batch <= batch_size - assert num_rows_in_batch <= num_rows - - total_rows_processed += num_rows_in_batch - - assert total_rows_processed == num_rows - - -# --- Pytest Fixture for RNG --- -@pytest.fixture -def rng(): - return np.random.default_rng(seed=42) - - -def test_generate_batch_basic_schema(rng): - schema = [("id", "INT64", None), ("is_active", "BOOL", None)] - batch = generate_batch(schema, 5, rng) - - assert len(batch) == 5 - - for row in batch: - assert isinstance(row, dict) - assert "id" in row - assert "is_active" in row - assert isinstance(row["id"], int) - assert isinstance(row["is_active"], bool) - - -def test_generate_batch_all_datatypes(rng): - schema = [ - ("c_bool", "BOOL", None), - ("c_int64", "INT64", None), - ("c_float64", "FLOAT64", None), - ("c_numeric", "NUMERIC", None), - ("c_date", "DATE", None), - ("c_datetime", "DATETIME", None), - ("c_timestamp", "TIMESTAMP", None), - ("c_time", "TIME", None), - ("c_string", "STRING", 10), - ("c_bytes", "BYTES", 5), - ("c_json", "JSON", 20), # Length for JSON is content hint - ] - num_rows = 3 - - date_pattern = re.compile(r"^\d{4}-\d{2}-\d{2}$") - time_pattern = re.compile(r"^\d{2}:\d{2}:\d{2}(\.\d{1,6})?$") - # BQ DATETIME: YYYY-MM-DD HH:MM:SS.ffffff - datetime_pattern = re.compile(r"^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}(\.\d{1,6})?$") - # BQ TIMESTAMP (UTC 'Z'): YYYY-MM-DDTHH:MM:SS.ffffffZ - timestamp_pattern = re.compile( - r"^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{1,6})?Z$" - ) - numeric_pattern = re.compile(r"^-?\d+\.\d{9}$") - - batch = generate_batch(schema, num_rows, rng) - assert len(batch) == num_rows - - for row in batch: - assert isinstance(row["c_bool"], bool) - assert isinstance(row["c_int64"], int) - assert isinstance(row["c_float64"], float) - - assert isinstance(row["c_numeric"], str) - assert numeric_pattern.match(row["c_numeric"]) - - assert isinstance(row["c_date"], str) - assert date_pattern.match(row["c_date"]) - datetime.date.fromisoformat(row["c_date"]) # Check parsable - - assert isinstance(row["c_datetime"], str) - assert datetime_pattern.match(row["c_datetime"]) - datetime.datetime.fromisoformat(row["c_datetime"]) # Check parsable - - assert isinstance(row["c_timestamp"], str) - assert timestamp_pattern.match(row["c_timestamp"]) - # datetime.fromisoformat can parse 'Z' if Python >= 3.11, or needs replace('Z', '+00:00') - dt_obj = datetime.datetime.fromisoformat( - row["c_timestamp"].replace("Z", "+00:00") - ) - assert dt_obj.tzinfo == datetime.timezone.utc - - assert isinstance(row["c_time"], str) - assert time_pattern.match(row["c_time"]) - datetime.time.fromisoformat(row["c_time"]) # Check parsable - - assert isinstance(row["c_string"], str) - assert len(row["c_string"]) == 10 - - c_bytes = base64.b64decode(row["c_bytes"]) - assert isinstance(c_bytes, bytes) - assert len(c_bytes) == 5 - - assert isinstance(row["c_json"], str) - try: - json.loads(row["c_json"]) # Check if it's valid JSON - except json.JSONDecodeError: - pytest.fail(f"Invalid JSON string generated: {row['c_json']}") - # Note: Exact length check for JSON is hard due to content variability and escaping. - # The 'length' parameter for JSON in schema is a hint for content size. - # We are primarily testing that it's valid JSON. diff --git a/scripts/create_test_model_vertex.py b/scripts/create_test_model_vertex.py deleted file mode 100644 index 946e54773e6..00000000000 --- a/scripts/create_test_model_vertex.py +++ /dev/null @@ -1,71 +0,0 @@ -# Copyright 2023 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 argparse -import sys - -import bigframes.ml.linear_model -import bigframes.pandas - - -def create_vertex_model(vertex_model_name): - df = bigframes.pandas.read_gbq("bigquery-public-data.ml_datasets.penguins") - - # filter down to the data we want to analyze - adelie_data = df[df.species == "Adelie Penguin (Pygoscelis adeliae)"] - - # drop the columns we don't care about - adelie_data = adelie_data.drop(columns=["species"]) - - # drop rows with nulls to get our training data - training_data = adelie_data.dropna() - - feature_columns = training_data["culmen_length_mm"] - label_columns = training_data[["body_mass_g"]] - - # create model - model = bigframes.ml.linear_model.LinearRegression() - model.fit(feature_columns, label_columns) - - # register to Vertex Registry - model.register(vertex_model_name) - - -if __name__ == "__main__": - parser = argparse.ArgumentParser( - description="Get top APIs for which there are no code samples in the docstring." - ) - parser.add_argument( - "-m", - "--model-name", - type=str, - required=True, - action="store", - help="Name of the model in Vertex.", - ) - parser.add_argument( - "-p", - "--project-id", - type=str, - required=False, - action="store", - help="Project id in which the model should be created. " - "By default, a project will be resolved as per https://cloud.google.com/python/docs/reference/google-cloud-core/latest/config#overview.", - ) - - args = parser.parse_args(sys.argv[1:]) - if args.project_id: - bigframes.pandas.options.bigquery.project = args.project_id - - create_vertex_model(args.model_name) diff --git a/scripts/data/audio/audio_LJ001-0010.wav b/scripts/data/audio/audio_LJ001-0010.wav deleted file mode 100644 index 01a2e68829a..00000000000 Binary files a/scripts/data/audio/audio_LJ001-0010.wav and /dev/null differ diff --git a/scripts/data/images/img0.jpg b/scripts/data/images/img0.jpg deleted file mode 100644 index 4f9114402b4..00000000000 Binary files a/scripts/data/images/img0.jpg and /dev/null differ diff --git a/scripts/data/images/img1.jpg b/scripts/data/images/img1.jpg deleted file mode 100644 index 15c881bd1af..00000000000 Binary files a/scripts/data/images/img1.jpg and /dev/null differ diff --git a/scripts/data/images_exif/test_image_exif.jpg b/scripts/data/images_exif/test_image_exif.jpg deleted file mode 100644 index fdfdaf9ad08..00000000000 Binary files a/scripts/data/images_exif/test_image_exif.jpg and /dev/null differ diff --git a/scripts/data/pdfs/pdfs_sample-local-pdf.pdf b/scripts/data/pdfs/pdfs_sample-local-pdf.pdf deleted file mode 100644 index d162cd6877e..00000000000 Binary files a/scripts/data/pdfs/pdfs_sample-local-pdf.pdf and /dev/null differ diff --git a/scripts/data/pdfs/test-protected.pdf b/scripts/data/pdfs/test-protected.pdf deleted file mode 100644 index 0d8cd28baa4..00000000000 Binary files a/scripts/data/pdfs/test-protected.pdf and /dev/null differ diff --git a/scripts/data/sql-functions/aead.yaml b/scripts/data/sql-functions/aead.yaml deleted file mode 100644 index 198248782d7..00000000000 --- a/scripts/data/sql-functions/aead.yaml +++ /dev/null @@ -1,134 +0,0 @@ -urn: extension:google:bq_scalar_functions -scalar_functions: - - name: "aead.decrypt_bytes" - description: "Uses the matching key from keyset to decrypt ciphertext and verifies the integrity of the data using additional_data. Returns an error if decryption or verification fails." - series_accessor_arg: keyset - impls: - # Signature: aead.decrypt_bytes:vbin_vbin_vbin - - args: - - name: "keyset" - value: binary - optional: false - keyword_only: false - - name: "ciphertext" - value: binary - optional: false - keyword_only: false - - name: "additional_data" - value: binary - optional: false - keyword_only: false - return: binary - # Signature: aead.decrypt_bytes:struct_vbin_vbin - - args: - - name: "keyset" - value: struct - optional: false - keyword_only: false - - name: "ciphertext" - value: binary - optional: false - keyword_only: false - - name: "additional_data" - value: binary - optional: false - keyword_only: false - return: binary - - name: "aead.decrypt_string" - description: "Like AEAD.DECRYPT_BYTES, but where additional_data is of type STRING." - series_accessor_arg: keyset - impls: - # Signature: aead.decrypt_string:vbin_vbin_str - - args: - - name: "keyset" - value: binary - optional: false - keyword_only: false - - name: "ciphertext" - value: binary - optional: false - keyword_only: false - - name: "additional_data" - value: string - optional: false - keyword_only: false - return: string - # Signature: aead.decrypt_string:struct_vbin_str - - args: - - name: "keyset" - value: struct - optional: false - keyword_only: false - - name: "ciphertext" - value: binary - optional: false - keyword_only: false - - name: "additional_data" - value: string - optional: false - keyword_only: false - return: string - - name: "aead.encrypt" - description: "Encrypts plaintext using the primary cryptographic key in keyset. The algorithm of the primary key must be AEAD_AES_GCM_256. Binds the ciphertext to the context defined by additional_data. Returns NULL if any input is NULL." - series_accessor_arg: keyset - impls: - # Signature: aead.encrypt:vbin_str_str - - args: - - name: "keyset" - value: binary - optional: false - keyword_only: false - - name: "plaintext" - value: string - optional: false - keyword_only: false - - name: "additional_data" - value: string - optional: false - keyword_only: false - return: binary - # Signature: aead.encrypt:vbin_vbin_vbin - - args: - - name: "keyset" - value: binary - optional: false - keyword_only: false - - name: "plaintext" - value: binary - optional: false - keyword_only: false - - name: "additional_data" - value: binary - optional: false - keyword_only: false - return: binary - # Signature: aead.encrypt:struct_str_str - - args: - - name: "keyset" - value: struct - optional: false - keyword_only: false - - name: "plaintext" - value: string - optional: false - keyword_only: false - - name: "additional_data" - value: string - optional: false - keyword_only: false - return: binary - # Signature: aead.encrypt:struct_vbin_vbin - - args: - - name: "keyset" - value: struct - optional: false - keyword_only: false - - name: "plaintext" - value: binary - optional: false - keyword_only: false - - name: "additional_data" - value: binary - optional: false - keyword_only: false - return: binary diff --git a/scripts/data/sql-functions/ai.yaml b/scripts/data/sql-functions/ai.yaml deleted file mode 100644 index f3238c8178b..00000000000 --- a/scripts/data/sql-functions/ai.yaml +++ /dev/null @@ -1 +0,0 @@ -urn: extension:google:bq_scalar_functions diff --git a/scripts/data/sql-functions/global_namespace/aead_encryption.yaml b/scripts/data/sql-functions/global_namespace/aead_encryption.yaml deleted file mode 100644 index 1e62de0f2a6..00000000000 --- a/scripts/data/sql-functions/global_namespace/aead_encryption.yaml +++ /dev/null @@ -1,134 +0,0 @@ -urn: extension:google:bq_scalar_functions -scalar_functions: - - name: "deterministic_decrypt_bytes" - description: "Uses the matching key from `keyset` to decrypt `ciphertext` and verifies the integrity of the data using `additional_data`. Returns an error if decryption fails." - series_accessor_arg: keyset - impls: - # Signature: deterministic_decrypt_bytes:vbin_vbin_vbin - - args: - - name: "keyset" - value: binary - optional: false - keyword_only: false - - name: "ciphertext" - value: binary - optional: false - keyword_only: false - - name: "additional_data" - value: binary - optional: false - keyword_only: false - return: binary - # Signature: deterministic_decrypt_bytes:struct_vbin_vbin - - args: - - name: "keyset" - value: struct - optional: false - keyword_only: false - - name: "ciphertext" - value: binary - optional: false - keyword_only: false - - name: "additional_data" - value: binary - optional: false - keyword_only: false - return: binary - - name: "deterministic_decrypt_string" - description: "Like `DETERMINISTIC_DECRYPT_BYTES`, but where plaintext is of type STRING." - series_accessor_arg: keyset - impls: - # Signature: deterministic_decrypt_string:vbin_vbin_str - - args: - - name: "keyset" - value: binary - optional: false - keyword_only: false - - name: "ciphertext" - value: binary - optional: false - keyword_only: false - - name: "additional_data" - value: string - optional: false - keyword_only: false - return: string - # Signature: deterministic_decrypt_string:struct_vbin_str - - args: - - name: "keyset" - value: struct - optional: false - keyword_only: false - - name: "ciphertext" - value: binary - optional: false - keyword_only: false - - name: "additional_data" - value: string - optional: false - keyword_only: false - return: string - - name: "deterministic_encrypt" - description: "Encrypts `plaintext` using the primary cryptographic key in `keyset` using deterministic AEAD. The algorithm of the primary key must be `DETERMINISTIC_AEAD_AES_SIV_CMAC_256`. Binds the ciphertext to the context defined by `additional_data`. Returns `NULL` if any input is `NULL`." - series_accessor_arg: keyset - impls: - # Signature: deterministic_encrypt:vbin_str_str - - args: - - name: "keyset" - value: binary - optional: false - keyword_only: false - - name: "plaintext" - value: string - optional: false - keyword_only: false - - name: "additional_data" - value: string - optional: false - keyword_only: false - return: binary - # Signature: deterministic_encrypt:vbin_vbin_vbin - - args: - - name: "keyset" - value: binary - optional: false - keyword_only: false - - name: "plaintext" - value: binary - optional: false - keyword_only: false - - name: "additional_data" - value: binary - optional: false - keyword_only: false - return: binary - # Signature: deterministic_encrypt:struct_str_str - - args: - - name: "keyset" - value: struct - optional: false - keyword_only: false - - name: "plaintext" - value: string - optional: false - keyword_only: false - - name: "additional_data" - value: string - optional: false - keyword_only: false - return: binary - # Signature: deterministic_encrypt:struct_vbin_vbin - - args: - - name: "keyset" - value: struct - optional: false - keyword_only: false - - name: "plaintext" - value: binary - optional: false - keyword_only: false - - name: "additional_data" - value: binary - optional: false - keyword_only: false - return: binary diff --git a/scripts/data/sql-functions/global_namespace/array.yaml b/scripts/data/sql-functions/global_namespace/array.yaml deleted file mode 100644 index aa9230c251b..00000000000 --- a/scripts/data/sql-functions/global_namespace/array.yaml +++ /dev/null @@ -1,341 +0,0 @@ -urn: extension:google:bq_scalar_functions -scalar_functions: - - name: "array_concat" - description: "Concatenates one or more arrays with the same element type into a single array." - series_accessor_arg: array_expression_1 - impls: - # Signature: array_concat:list_list - - args: - - name: "array_expression_1" - value: list - optional: false - keyword_only: false - - name: "array_expression_2" - value: list - optional: false - keyword_only: false - return: list - - name: "array_first" - description: "Takes an array and returns the first element in the array." - series_accessor_arg: array_expression - impls: - # Signature: array_first:list - - args: - - name: "array_expression" - value: list - optional: false - keyword_only: false - return: any1 - - name: "array_first_n" - description: "Returns a prefix of `input_array` consisting of the first `n` elements." - series_accessor_arg: input_array - impls: - # Signature: array_first_n:list_i64 - - args: - - name: "input_array" - value: list - optional: false - keyword_only: false - - name: "n" - value: i64 - optional: false - keyword_only: false - return: list - - name: "array_includes" - description: "Takes an array and returns `TRUE` if there is an element in the array that is equal to the search_value." - series_accessor_arg: array_to_search - impls: - # Signature: array_includes:list_any - - args: - - name: "array_to_search" - value: list - optional: false - keyword_only: false - - name: "search_value" - value: any1 - optional: false - keyword_only: false - return: boolean - - name: "array_includes_all" - description: "Takes an array to search and an array of search values. Returns `TRUE` if all search values are in the array to search, otherwise returns `FALSE`." - series_accessor_arg: array_to_search - impls: - # Signature: array_includes_all:list_list - - args: - - name: "array_to_search" - value: list - optional: false - keyword_only: false - - name: "search_values" - value: list - optional: false - keyword_only: false - return: boolean - - name: "array_includes_any" - description: "Takes an array to search and an array of search values. Returns `TRUE` if any search values are in the array to search, otherwise returns `FALSE`." - series_accessor_arg: array_to_search - impls: - # Signature: array_includes_any:list_list - - args: - - name: "array_to_search" - value: list - optional: false - keyword_only: false - - name: "search_values" - value: list - optional: false - keyword_only: false - return: boolean - - name: "array_is_distinct" - description: "Returns `TRUE` if the array contains no repeated elements, using the same equality comparison logic as `SELECT DISTINCT`." - series_accessor_arg: array_expression - impls: - # Signature: array_is_distinct:list - - args: - - name: "array_expression" - value: list - optional: false - keyword_only: false - return: boolean - - name: "array_last" - description: "Takes an array and returns the last element in the array." - series_accessor_arg: array_expression - impls: - # Signature: array_last:list - - args: - - name: "array_expression" - value: list - optional: false - keyword_only: false - return: any1 - - name: "array_length" - description: | - Compute the length of each array element in the Series. - - **Examples:** - - >>> import bigframes.pandas as bpd - >>> import bigframes.bigquery as bbq - - >>> s = bpd.Series([[1, 2, 8, 3], [], [3, 4]]) - >>> bbq.array_length(s) - 0 4 - 1 0 - 2 2 - dtype: Int64 - - You can call this function using the Series `bigquery` accessor. - - >>> s.bigquery.array_length() - 0 4 - 1 0 - 2 2 - dtype: Int64 - - You can also use this accessor on a pandas Series after importing bigframes. - - >>> import bigframes - >>> import pandas as pd - >>> ps = pd.Series([[1, 2, 8, 3], [], [3, 4]]) - >>> ps.bigquery.array_length() - 0 4 - 1 0 - 2 2 - dtype: Int64 - - You can also apply this function directly to Series using `apply`. - - >>> s.apply(bbq.array_length, by_row=False) - 0 4 - 1 0 - 2 2 - dtype: Int64 - - Args: - series (bigframes.series.Series): A Series with array columns. - - Returns: - bigframes.series.Series: A Series of integer values indicating - the length of each element in the Series. - series_accessor_arg: series - impls: - # Signature: array_length:list - - args: - - name: "series" - value: list - optional: false - keyword_only: false - return: i64 - - name: "array_reverse" - description: "Returns the input `ARRAY` with elements in reverse order." - series_accessor_arg: value - impls: - # Signature: array_reverse:list - - args: - - name: "value" - value: list - optional: false - keyword_only: false - return: list - - name: "array_slice" - description: "Returns an array containing zero or more consecutive elements from the input array." - series_accessor_arg: array_to_slice - impls: - # Signature: array_slice:list_i64_i64 - - args: - - name: "array_to_slice" - value: list - optional: false - keyword_only: false - - name: "start_offset" - value: i64 - optional: false - keyword_only: false - - name: "end_offset" - value: i64 - optional: false - keyword_only: false - return: list - - name: "array_to_string" - description: | - Converts array elements within a Series into delimited strings. - - **Examples:** - - >>> import bigframes.pandas as bpd - >>> import bigframes.bigquery as bbq - - >>> s = bpd.Series([["H", "i", "!"], ["Hello", "World"], np.nan, [], ["Hi"]]) - >>> bbq.array_to_string(s, delimiter=", ") - 0 H, i, ! - 1 Hello, World - 2 - 3 - 4 Hi - dtype: string - - You can call this function using the Series `bigquery` accessor. - - >>> s.bigquery.array_to_string(delimiter=", ") - 0 H, i, ! - 1 Hello, World - 2 - 3 - 4 Hi - dtype: string - - You can also use this accessor on a pandas Series after importing bigframes. - - >>> import bigframes - >>> import pandas as pd - >>> ps = pd.Series([["H", "i", "!"], ["Hello", "World"], None, [], ["Hi"]]) - >>> ps.bigquery.array_to_string(delimiter=", ") - 0 H, i, ! - 1 Hello, World - 2 - 3 - 4 Hi - dtype: string - - Args: - series (bigframes.series.Series): A Series containing arrays. - delimiter (str): The string used to separate array elements. - null_text (str, optional): The string to replace any NULL values in the array with. - - Returns: - bigframes.series.Series: A Series containing delimited strings. - series_accessor_arg: series - impls: - # Signature: array_to_string:list_str_str - - args: - - name: "series" - value: list - optional: false - keyword_only: false - - name: "delimiter" - value: string - optional: false - keyword_only: false - - name: "null_text" - value: string - optional: true - keyword_only: false - return: string - # Signature: array_to_string:list_vbin_vbin - - args: - - name: "series" - value: list - optional: false - keyword_only: false - - name: "delimiter" - value: binary - optional: false - keyword_only: false - - name: "null_text" - value: binary - optional: true - keyword_only: false - return: binary - - name: "flatten" - description: "Takes an array of nested data and flattens a specific part of it into a single, flat array with the [array elements field access operator][array-el-field-operator]. Returns `NULL` if the input value is `NULL`." - series_accessor_arg: array_to_flatten - impls: - # Signature: flatten:list_i64 - - args: - - name: "array_to_flatten" - value: list - optional: false - keyword_only: false - - name: "depth" - value: i64 - optional: true - keyword_only: true - return: list - - name: "generate_array" - description: "Returns an array of values. The `start_expression` and `end_expression` parameters determine the inclusive start and end of the array." - impls: - # Signature: generate_array:i64_i64_i64 - - args: - - name: "start_expression" - value: i64 - optional: false - keyword_only: false - - name: "end_expression" - value: i64 - optional: false - keyword_only: false - - name: "step_expression" - value: i64 - optional: true - keyword_only: false - return: list - # Signature: generate_array:dec_dec_dec - - args: - - name: "start_expression" - value: decimal<38,9> - optional: false - keyword_only: false - - name: "end_expression" - value: decimal<38,9> - optional: false - keyword_only: false - - name: "step_expression" - value: decimal<38,9> - optional: true - keyword_only: false - return: list> - # Signature: generate_array:fp64_fp64_fp64 - - args: - - name: "start_expression" - value: fp64 - optional: false - keyword_only: false - - name: "end_expression" - value: fp64 - optional: false - keyword_only: false - - name: "step_expression" - value: fp64 - optional: true - keyword_only: false - return: list diff --git a/scripts/data/sql-functions/global_namespace/bit.yaml b/scripts/data/sql-functions/global_namespace/bit.yaml deleted file mode 100644 index fe14eae7b64..00000000000 --- a/scripts/data/sql-functions/global_namespace/bit.yaml +++ /dev/null @@ -1,27 +0,0 @@ -urn: extension:google:bq_scalar_functions -scalar_functions: - - name: "bit_count" - description: "The input, `expression`, must be an integer or `BYTES`. Returns the number of bits that are set in the input expression. For signed integers, this is the number of bits in two's complement form." - series_accessor_arg: expression - impls: - # Signature: bit_count:i32 - - args: - - name: "expression" - value: i32 - optional: false - keyword_only: false - return: i64 - # Signature: bit_count:i64 - - args: - - name: "expression" - value: i64 - optional: false - keyword_only: false - return: i64 - # Signature: bit_count:vbin - - args: - - name: "expression" - value: binary - optional: false - keyword_only: false - return: i64 diff --git a/scripts/data/sql-functions/global_namespace/conversion.yaml b/scripts/data/sql-functions/global_namespace/conversion.yaml deleted file mode 100644 index c39724427de..00000000000 --- a/scripts/data/sql-functions/global_namespace/conversion.yaml +++ /dev/null @@ -1,119 +0,0 @@ -urn: extension:google:bq_scalar_functions -scalar_functions: - - name: "bool" - description: "Converts a JSON boolean to a SQL BOOL value." - series_accessor_arg: json_string_expression - impls: - # Signature: bool:str - - args: - - name: "json_string_expression" - value: string - optional: false - keyword_only: false - return: boolean - - name: "double" - description: "Converts a JSON number to a SQL FLOAT64 value." - series_accessor_arg: json_string_expression - impls: - # Signature: double:str_str - - args: - - name: "json_string_expression" - value: string - optional: false - keyword_only: false - - name: "wide_number_mode" - value: string - optional: true - keyword_only: true - return: fp64 - - name: "float64" - description: "Converts a JSON number to a SQL FLOAT64 value." - series_accessor_arg: json_string_expression - impls: - # Signature: float64:str_str - - args: - - name: "json_string_expression" - value: string - optional: false - keyword_only: false - - name: "wide_number_mode" - value: string - optional: true - keyword_only: true - return: fp64 - - name: "int64" - description: "Converts a JSON number to a SQL INT64 value." - series_accessor_arg: json_string_expression - impls: - # Signature: int64:str - - args: - - name: "json_string_expression" - value: string - optional: false - keyword_only: false - return: i64 - - name: "parse_bignumeric" - description: "Converts a STRING to a BIGNUMERIC value." - series_accessor_arg: string_expression - impls: - # Signature: parse_bignumeric:str - - args: - - name: "string_expression" - value: string - optional: false - keyword_only: false - return: decimal<76,38> - - name: "parse_numeric" - description: "Converts a STRING to a NUMERIC value." - series_accessor_arg: string_expression - impls: - # Signature: parse_numeric:str - - args: - - name: "string_expression" - value: string - optional: false - keyword_only: false - return: decimal<38,9> - - name: "string" - description: "Converts a value to a STRING value." - series_accessor_arg: expression - impls: - # Signature: string:pts_str - - args: - - name: "expression" - value: timestamp - optional: false - keyword_only: false - - name: "timezone" - value: string - optional: true - keyword_only: false - return: string - # Signature: string:date - - args: - - name: "expression" - value: date - optional: false - keyword_only: false - return: string - # Signature: string:pt - - args: - - name: "expression" - value: time - optional: false - keyword_only: false - return: string - # Signature: string:pts - - args: - - name: "expression" - value: timestamp - optional: false - keyword_only: false - return: string - # Signature: string:str - - args: - - name: "expression" - value: string - optional: false - keyword_only: false - return: string diff --git a/scripts/data/sql-functions/global_namespace/date.yaml b/scripts/data/sql-functions/global_namespace/date.yaml deleted file mode 100644 index 8d1dfc95284..00000000000 --- a/scripts/data/sql-functions/global_namespace/date.yaml +++ /dev/null @@ -1,277 +0,0 @@ -urn: extension:google:bq_scalar_functions -scalar_functions: - - name: "current_date" - description: "Returns the current date as a DATE object. Parentheses are optional when called with no arguments." - impls: - # Signature: current_date:str - - args: - - name: "time_zone_expression" - value: string - optional: true - keyword_only: false - return: date - - name: "date" - description: "Constructs or extracts a date." - series_accessor_arg: expression - impls: - # Signature: date:pts_str - - args: - - name: "expression" - value: timestamp - optional: false - keyword_only: false - - name: "time_zone_expression" - value: string - optional: true - keyword_only: false - return: date - # Signature: date:pts - - args: - - name: "expression" - value: timestamp - optional: false - keyword_only: false - return: date - # Signature: date:i64_i64_i64 - - args: - - name: "year" - value: i64 - optional: false - keyword_only: false - - name: "month" - value: i64 - optional: false - keyword_only: false - - name: "day" - value: i64 - optional: false - keyword_only: false - return: date - # Signature: date:date - - args: - - name: "expression" - value: date - optional: false - keyword_only: false - return: date - # Signature: date:str - - args: - - name: "expression" - value: string - optional: false - keyword_only: false - return: date - - name: "date_add" - description: "Adds a specified time interval to a DATE." - series_accessor_arg: date_expression - impls: - # Signature: date_add:date_i64_any - - args: - - name: "date_expression" - value: date - optional: false - keyword_only: false - - name: "int64_expression" - value: i64 - optional: false - keyword_only: false - - name: "date_part" - value: any1 - optional: false - keyword_only: false - return: date - # TODO(b/527093666): add support for date_bucket when we add an INTERVAL dtype - - name: "date_diff" - description: "Gets the number of unit boundaries between two DATE values (end_date - start_date) at a particular time granularity." - series_accessor_arg: end_date - impls: - # Signature: date_diff:date_date_any - - args: - - name: "end_date" - value: date - optional: false - keyword_only: false - - name: "start_date" - value: date - optional: false - keyword_only: false - - name: "granularity" - value: any1 - optional: false - keyword_only: false - return: i64 - - name: "date_from_unix_date" - description: "Interprets an INT64 expression as the number of days since 1970-01-01." - series_accessor_arg: int64_expression - impls: - # Signature: date_from_unix_date:i64 - - args: - - name: "int64_expression" - value: i64 - optional: false - keyword_only: false - return: date - - name: "date_sub" - description: "Subtracts a specified time interval from a DATE." - series_accessor_arg: date_expression - impls: - # Signature: date_sub:date_i64_any - - args: - - name: "date_expression" - value: date - optional: false - keyword_only: false - - name: "int64_expression" - value: i64 - optional: false - keyword_only: false - - name: "date_part" - value: any1 - optional: false - keyword_only: false - return: date - - name: "date_trunc" - description: "Truncates a DATE, DATETIME, or TIMESTAMP value at a particular granularity." - series_accessor_arg: date_value - impls: - # Signature: date_trunc:date_any - - args: - - name: "date_value" - value: date - optional: false - keyword_only: false - - name: "granularity" - value: any1 - optional: false - keyword_only: false - return: date - - name: "extract" - description: "Returns the value corresponding to the specified date part." - series_accessor_arg: date_expression - impls: - # Signature: extract:date_any - - args: - - name: "date_expression" - value: date - optional: false - keyword_only: false - - name: "part" - value: any1 - optional: false - keyword_only: false - return: i64 - # Signature: extract:pts_any_str - - args: - - name: "date_expression" - value: timestamp - optional: false - keyword_only: false - - name: "part" - value: any1 - optional: false - keyword_only: false - - name: "time_zone" - value: string - optional: true - keyword_only: false - return: i64 - # Signature: extract:pts_any - - args: - - name: "date_expression" - value: timestamp - optional: false - keyword_only: false - - name: "part" - value: any1 - optional: false - keyword_only: false - return: i64 - # Signature: extract:pt_any - - args: - - name: "date_expression" - value: time - optional: false - keyword_only: false - - name: "part" - value: any1 - optional: false - keyword_only: false - return: i64 - - name: "format_date" - description: "Formats a DATE value according to a specified format string." - series_accessor_arg: date_expr - impls: - # Signature: format_date:str_date - - args: - - name: "format_string" - value: string - optional: false - keyword_only: false - - name: "date_expr" - value: date - optional: false - keyword_only: false - return: string - - name: "generate_date_array" - description: "Generates an array of dates in a range." - impls: - # Signature: generate_date_array:date_date_i64_any - - args: - - name: "start_date" - value: date - optional: false - keyword_only: false - - name: "end_date" - value: date - optional: false - keyword_only: false - - name: "int64_expression" - value: i64 - optional: true - keyword_only: false - - name: "date_part" - value: any1 - optional: true - keyword_only: false - return: list - - name: "last_day" - description: "Returns the last day from a date expression. This is commonly used to return the last day of the month." - series_accessor_arg: date_expression - impls: - # Signature: last_day:date_any - - args: - - name: "date_expression" - value: date - optional: false - keyword_only: false - - name: "date_part" - value: any1 - optional: true - keyword_only: false - return: date - - name: "parse_date" - description: "Converts a STRING value to a DATE value." - series_accessor_arg: date_string - impls: - # Signature: parse_date:str_str - - args: - - name: "format_string" - value: string - optional: false - keyword_only: false - - name: "date_string" - value: string - optional: false - keyword_only: false - return: date - - name: "unix_date" - description: "Returns the number of days since 1970-01-01." - series_accessor_arg: date_expression - impls: - # Signature: unix_date:date - - args: - - name: "date_expression" - value: date - optional: false - keyword_only: false - return: i64 diff --git a/scripts/decrypt-secrets.sh b/scripts/decrypt-secrets.sh new file mode 100755 index 00000000000..0018b421ddf --- /dev/null +++ b/scripts/decrypt-secrets.sh @@ -0,0 +1,46 @@ +#!/bin/bash + +# Copyright 2023 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. +# 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. + +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +ROOT=$( dirname "$DIR" ) + +# Work from the project root. +cd $ROOT + +# Prevent it from overriding files. +# We recommend that sample authors use their own service account files and cloud project. +# In that case, they are supposed to prepare these files by themselves. +if [[ -f "testing/test-env.sh" ]] || \ + [[ -f "testing/service-account.json" ]] || \ + [[ -f "testing/client-secrets.json" ]]; then + echo "One or more target files exist, aborting." + exit 1 +fi + +# Use SECRET_MANAGER_PROJECT if set, fallback to cloud-devrel-kokoro-resources. +PROJECT_ID="${SECRET_MANAGER_PROJECT:-cloud-devrel-kokoro-resources}" + +gcloud secrets versions access latest --secret="python-docs-samples-test-env" \ + --project="${PROJECT_ID}" \ + > testing/test-env.sh +gcloud secrets versions access latest \ + --secret="python-docs-samples-service-account" \ + --project="${PROJECT_ID}" \ + > testing/service-account.json +gcloud secrets versions access latest \ + --secret="python-docs-samples-client-secrets" \ + --project="${PROJECT_ID}" \ + > testing/client-secrets.json diff --git a/scripts/dev-utils/tpcds_upload_helper.py b/scripts/dev-utils/tpcds_upload_helper.py deleted file mode 100644 index dec5b39768f..00000000000 --- a/scripts/dev-utils/tpcds_upload_helper.py +++ /dev/null @@ -1,596 +0,0 @@ -import argparse -import csv -import os -import sys - -import google.api_core.exceptions -from google.cloud import bigquery - - -def preprocess_csv(input_file_path, output_file_path): - try: - with ( - open(input_file_path, mode="r", newline="", encoding="utf-8") as infile, - open(output_file_path, mode="w", newline="", encoding="utf-8") as outfile, - ): - reader = csv.reader(infile, delimiter="|") - writer = csv.writer(outfile, delimiter="|") - - for row in reader: - writer.writerow(row[:-1]) - except Exception as e: - print(f"An error occurred: {e}") - - -def get_schema(table_name): - schema = { - "customer_address": [ - bigquery.SchemaField("ca_address_sk", "INTEGER", mode="REQUIRED"), - bigquery.SchemaField("ca_address_id", "STRING", mode="REQUIRED"), - bigquery.SchemaField("ca_street_number", "STRING", mode="NULLABLE"), - bigquery.SchemaField("ca_street_name", "STRING", mode="NULLABLE"), - bigquery.SchemaField("ca_street_type", "STRING", mode="NULLABLE"), - bigquery.SchemaField("ca_suite_number", "STRING", mode="NULLABLE"), - bigquery.SchemaField("ca_city", "STRING", mode="NULLABLE"), - bigquery.SchemaField("ca_county", "STRING", mode="NULLABLE"), - bigquery.SchemaField("ca_state", "STRING", mode="NULLABLE"), - bigquery.SchemaField("ca_zip", "STRING", mode="NULLABLE"), - bigquery.SchemaField("ca_country", "STRING", mode="NULLABLE"), - bigquery.SchemaField("ca_gmt_offset", "FLOAT", mode="NULLABLE"), - bigquery.SchemaField("ca_location_type", "STRING", mode="NULLABLE"), - ], - "customer_demographics": [ - bigquery.SchemaField("cd_demo_sk", "INTEGER", mode="REQUIRED"), - bigquery.SchemaField("cd_gender", "STRING", mode="NULLABLE"), - bigquery.SchemaField("cd_marital_status", "STRING", mode="NULLABLE"), - bigquery.SchemaField("cd_education_status", "STRING", mode="NULLABLE"), - bigquery.SchemaField("cd_purchase_estimate", "INTEGER", mode="NULLABLE"), - bigquery.SchemaField("cd_credit_rating", "STRING", mode="NULLABLE"), - bigquery.SchemaField("cd_dep_count", "INTEGER", mode="NULLABLE"), - bigquery.SchemaField("cd_dep_employed_count", "INTEGER", mode="NULLABLE"), - bigquery.SchemaField("cd_dep_college_count", "INTEGER", mode="NULLABLE"), - ], - "date_dim": [ - bigquery.SchemaField("d_date_sk", "INTEGER", mode="REQUIRED"), - bigquery.SchemaField("d_date_id", "STRING", mode="REQUIRED"), - bigquery.SchemaField("d_date", "DATE", mode="NULLABLE"), - bigquery.SchemaField("d_month_seq", "INTEGER", mode="NULLABLE"), - bigquery.SchemaField("d_week_seq", "INTEGER", mode="NULLABLE"), - bigquery.SchemaField("d_quarter_seq", "INTEGER", mode="NULLABLE"), - bigquery.SchemaField("d_year", "INTEGER", mode="NULLABLE"), - bigquery.SchemaField("d_dow", "INTEGER", mode="NULLABLE"), - bigquery.SchemaField("d_moy", "INTEGER", mode="NULLABLE"), - bigquery.SchemaField("d_dom", "INTEGER", mode="NULLABLE"), - bigquery.SchemaField("d_qoy", "INTEGER", mode="NULLABLE"), - bigquery.SchemaField("d_fy_year", "INTEGER", mode="NULLABLE"), - bigquery.SchemaField("d_fy_quarter_seq", "INTEGER", mode="NULLABLE"), - bigquery.SchemaField("d_fy_week_seq", "INTEGER", mode="NULLABLE"), - bigquery.SchemaField("d_day_name", "STRING", mode="NULLABLE"), - bigquery.SchemaField("d_quarter_name", "STRING", mode="NULLABLE"), - bigquery.SchemaField("d_holiday", "STRING", mode="NULLABLE"), - bigquery.SchemaField("d_weekend", "STRING", mode="NULLABLE"), - bigquery.SchemaField("d_following_holiday", "STRING", mode="NULLABLE"), - bigquery.SchemaField("d_first_dom", "INTEGER", mode="NULLABLE"), - bigquery.SchemaField("d_last_dom", "INTEGER", mode="NULLABLE"), - bigquery.SchemaField("d_same_day_ly", "INTEGER", mode="NULLABLE"), - bigquery.SchemaField("d_same_day_lq", "INTEGER", mode="NULLABLE"), - bigquery.SchemaField("d_current_day", "STRING", mode="NULLABLE"), - bigquery.SchemaField("d_current_week", "STRING", mode="NULLABLE"), - bigquery.SchemaField("d_current_month", "STRING", mode="NULLABLE"), - bigquery.SchemaField("d_current_quarter", "STRING", mode="NULLABLE"), - bigquery.SchemaField("d_current_year", "STRING", mode="NULLABLE"), - ], - "warehouse": [ - bigquery.SchemaField("w_warehouse_sk", "INTEGER", mode="REQUIRED"), - bigquery.SchemaField("w_warehouse_id", "STRING", mode="REQUIRED"), - bigquery.SchemaField("w_warehouse_name", "STRING", mode="NULLABLE"), - bigquery.SchemaField("w_warehouse_sq_ft", "INTEGER", mode="NULLABLE"), - bigquery.SchemaField("w_street_number", "STRING", mode="NULLABLE"), - bigquery.SchemaField("w_street_name", "STRING", mode="NULLABLE"), - bigquery.SchemaField("w_street_type", "STRING", mode="NULLABLE"), - bigquery.SchemaField("w_suite_number", "STRING", mode="NULLABLE"), - bigquery.SchemaField("w_city", "STRING", mode="NULLABLE"), - bigquery.SchemaField("w_county", "STRING", mode="NULLABLE"), - bigquery.SchemaField("w_state", "STRING", mode="NULLABLE"), - bigquery.SchemaField("w_zip", "STRING", mode="NULLABLE"), - bigquery.SchemaField("w_country", "STRING", mode="NULLABLE"), - bigquery.SchemaField("w_gmt_offset", "FLOAT", mode="NULLABLE"), - ], - "ship_mode": [ - bigquery.SchemaField("sm_ship_mode_sk", "INTEGER", mode="REQUIRED"), - bigquery.SchemaField("sm_ship_mode_id", "STRING", mode="REQUIRED"), - bigquery.SchemaField("sm_type", "STRING", mode="NULLABLE"), - bigquery.SchemaField("sm_code", "STRING", mode="NULLABLE"), - bigquery.SchemaField("sm_carrier", "STRING", mode="NULLABLE"), - bigquery.SchemaField("sm_contract", "STRING", mode="NULLABLE"), - ], - "time_dim": [ - bigquery.SchemaField("t_time_sk", "INTEGER", mode="REQUIRED"), - bigquery.SchemaField("t_time_id", "STRING", mode="REQUIRED"), - bigquery.SchemaField("t_time", "INTEGER", mode="NULLABLE"), - bigquery.SchemaField("t_hour", "INTEGER", mode="NULLABLE"), - bigquery.SchemaField("t_minute", "INTEGER", mode="NULLABLE"), - bigquery.SchemaField("t_second", "INTEGER", mode="NULLABLE"), - bigquery.SchemaField("t_am_pm", "STRING", mode="NULLABLE"), - bigquery.SchemaField("t_shift", "STRING", mode="NULLABLE"), - bigquery.SchemaField("t_sub_shift", "STRING", mode="NULLABLE"), - bigquery.SchemaField("t_meal_time", "STRING", mode="NULLABLE"), - ], - "reason": [ - bigquery.SchemaField("r_reason_sk", "INTEGER", mode="REQUIRED"), - bigquery.SchemaField("r_reason_id", "STRING", mode="REQUIRED"), - bigquery.SchemaField("r_reason_desc", "STRING", mode="NULLABLE"), - ], - "income_band": [ - bigquery.SchemaField("ib_income_band_sk", "INTEGER", mode="REQUIRED"), - bigquery.SchemaField("ib_lower_bound", "INTEGER", mode="NULLABLE"), - bigquery.SchemaField("ib_upper_bound", "INTEGER", mode="NULLABLE"), - ], - "item": [ - bigquery.SchemaField("i_item_sk", "INTEGER", mode="REQUIRED"), - bigquery.SchemaField("i_item_id", "STRING", mode="REQUIRED"), - bigquery.SchemaField("i_rec_start_date", "DATE", mode="NULLABLE"), - bigquery.SchemaField("i_rec_end_date", "DATE", mode="NULLABLE"), - bigquery.SchemaField("i_item_desc", "STRING", mode="NULLABLE"), - bigquery.SchemaField("i_current_price", "FLOAT", mode="NULLABLE"), - bigquery.SchemaField("i_wholesale_cost", "FLOAT", mode="NULLABLE"), - bigquery.SchemaField("i_brand_id", "INTEGER", mode="NULLABLE"), - bigquery.SchemaField("i_brand", "STRING", mode="NULLABLE"), - bigquery.SchemaField("i_class_id", "INTEGER", mode="NULLABLE"), - bigquery.SchemaField("i_class", "STRING", mode="NULLABLE"), - bigquery.SchemaField("i_category_id", "INTEGER", mode="NULLABLE"), - bigquery.SchemaField("i_category", "STRING", mode="NULLABLE"), - bigquery.SchemaField("i_manufact_id", "INTEGER", mode="NULLABLE"), - bigquery.SchemaField("i_manufact", "STRING", mode="NULLABLE"), - bigquery.SchemaField("i_size", "STRING", mode="NULLABLE"), - bigquery.SchemaField("i_formulation", "STRING", mode="NULLABLE"), - bigquery.SchemaField("i_color", "STRING", mode="NULLABLE"), - bigquery.SchemaField("i_units", "STRING", mode="NULLABLE"), - bigquery.SchemaField("i_container", "STRING", mode="NULLABLE"), - bigquery.SchemaField("i_manager_id", "INTEGER", mode="NULLABLE"), - bigquery.SchemaField("i_product_name", "STRING", mode="NULLABLE"), - ], - "store": [ - bigquery.SchemaField("s_store_sk", "INTEGER", mode="REQUIRED"), - bigquery.SchemaField("s_store_id", "STRING", mode="REQUIRED"), - bigquery.SchemaField("s_rec_start_date", "DATE", mode="NULLABLE"), - bigquery.SchemaField("s_rec_end_date", "DATE", mode="NULLABLE"), - bigquery.SchemaField("s_closed_date_sk", "INTEGER", mode="NULLABLE"), - bigquery.SchemaField("s_store_name", "STRING", mode="NULLABLE"), - bigquery.SchemaField("s_number_employees", "INTEGER", mode="NULLABLE"), - bigquery.SchemaField("s_floor_space", "INTEGER", mode="NULLABLE"), - bigquery.SchemaField("s_hours", "STRING", mode="NULLABLE"), - bigquery.SchemaField("s_manager", "STRING", mode="NULLABLE"), - bigquery.SchemaField("s_market_id", "INTEGER", mode="NULLABLE"), - bigquery.SchemaField("s_geography_class", "STRING", mode="NULLABLE"), - bigquery.SchemaField("s_market_desc", "STRING", mode="NULLABLE"), - bigquery.SchemaField("s_market_manager", "STRING", mode="NULLABLE"), - bigquery.SchemaField("s_division_id", "INTEGER", mode="NULLABLE"), - bigquery.SchemaField("s_division_name", "STRING", mode="NULLABLE"), - bigquery.SchemaField("s_company_id", "INTEGER", mode="NULLABLE"), - bigquery.SchemaField("s_company_name", "STRING", mode="NULLABLE"), - bigquery.SchemaField("s_street_number", "STRING", mode="NULLABLE"), - bigquery.SchemaField("s_street_name", "STRING", mode="NULLABLE"), - bigquery.SchemaField("s_street_type", "STRING", mode="NULLABLE"), - bigquery.SchemaField("s_suite_number", "STRING", mode="NULLABLE"), - bigquery.SchemaField("s_city", "STRING", mode="NULLABLE"), - bigquery.SchemaField("s_county", "STRING", mode="NULLABLE"), - bigquery.SchemaField("s_state", "STRING", mode="NULLABLE"), - bigquery.SchemaField("s_zip", "STRING", mode="NULLABLE"), - bigquery.SchemaField("s_country", "STRING", mode="NULLABLE"), - bigquery.SchemaField("s_gmt_offset", "FLOAT", mode="NULLABLE"), - bigquery.SchemaField("s_tax_precentage", "FLOAT", mode="NULLABLE"), - ], - "call_center": [ - bigquery.SchemaField("cc_call_center_sk", "INTEGER", mode="REQUIRED"), - bigquery.SchemaField("cc_call_center_id", "STRING", mode="REQUIRED"), - bigquery.SchemaField("cc_rec_start_date", "DATE", mode="NULLABLE"), - bigquery.SchemaField("cc_rec_end_date", "DATE", mode="NULLABLE"), - bigquery.SchemaField("cc_closed_date_sk", "INTEGER", mode="NULLABLE"), - bigquery.SchemaField("cc_open_date_sk", "INTEGER", mode="NULLABLE"), - bigquery.SchemaField("cc_name", "STRING", mode="NULLABLE"), - bigquery.SchemaField("cc_class", "STRING", mode="NULLABLE"), - bigquery.SchemaField("cc_employees", "INTEGER", mode="NULLABLE"), - bigquery.SchemaField("cc_sq_ft", "INTEGER", mode="NULLABLE"), - bigquery.SchemaField("cc_hours", "STRING", mode="NULLABLE"), - bigquery.SchemaField("cc_manager", "STRING", mode="NULLABLE"), - bigquery.SchemaField("cc_mkt_id", "INTEGER", mode="NULLABLE"), - bigquery.SchemaField("cc_mkt_class", "STRING", mode="NULLABLE"), - bigquery.SchemaField("cc_mkt_desc", "STRING", mode="NULLABLE"), - bigquery.SchemaField("cc_market_manager", "STRING", mode="NULLABLE"), - bigquery.SchemaField("cc_division", "INTEGER", mode="NULLABLE"), - bigquery.SchemaField("cc_division_name", "STRING", mode="NULLABLE"), - bigquery.SchemaField("cc_company", "INTEGER", mode="NULLABLE"), - bigquery.SchemaField("cc_company_name", "STRING", mode="NULLABLE"), - bigquery.SchemaField("cc_street_number", "STRING", mode="NULLABLE"), - bigquery.SchemaField("cc_street_name", "STRING", mode="NULLABLE"), - bigquery.SchemaField("cc_street_type", "STRING", mode="NULLABLE"), - bigquery.SchemaField("cc_suite_number", "STRING", mode="NULLABLE"), - bigquery.SchemaField("cc_city", "STRING", mode="NULLABLE"), - bigquery.SchemaField("cc_county", "STRING", mode="NULLABLE"), - bigquery.SchemaField("cc_state", "STRING", mode="NULLABLE"), - bigquery.SchemaField("cc_zip", "STRING", mode="NULLABLE"), - bigquery.SchemaField("cc_country", "STRING", mode="NULLABLE"), - bigquery.SchemaField("cc_gmt_offset", "FLOAT", mode="NULLABLE"), - bigquery.SchemaField("cc_tax_percentage", "FLOAT", mode="NULLABLE"), - ], - "customer": [ - bigquery.SchemaField("c_customer_sk", "INTEGER", mode="REQUIRED"), - bigquery.SchemaField("c_customer_id", "STRING", mode="REQUIRED"), - bigquery.SchemaField("c_current_cdemo_sk", "INTEGER", mode="NULLABLE"), - bigquery.SchemaField("c_current_hdemo_sk", "INTEGER", mode="NULLABLE"), - bigquery.SchemaField("c_current_addr_sk", "INTEGER", mode="NULLABLE"), - bigquery.SchemaField("c_first_shipto_date_sk", "INTEGER", mode="NULLABLE"), - bigquery.SchemaField("c_first_sales_date_sk", "INTEGER", mode="NULLABLE"), - bigquery.SchemaField("c_salutation", "STRING", mode="NULLABLE"), - bigquery.SchemaField("c_first_name", "STRING", mode="NULLABLE"), - bigquery.SchemaField("c_last_name", "STRING", mode="NULLABLE"), - bigquery.SchemaField("c_preferred_cust_flag", "STRING", mode="NULLABLE"), - bigquery.SchemaField("c_birth_day", "INTEGER", mode="NULLABLE"), - bigquery.SchemaField("c_birth_month", "INTEGER", mode="NULLABLE"), - bigquery.SchemaField("c_birth_year", "INTEGER", mode="NULLABLE"), - bigquery.SchemaField("c_birth_country", "STRING", mode="NULLABLE"), - bigquery.SchemaField("c_login", "STRING", mode="NULLABLE"), - bigquery.SchemaField("c_email_address", "STRING", mode="NULLABLE"), - bigquery.SchemaField("c_last_review_date_sk", "STRING", mode="NULLABLE"), - ], - "web_site": [ - bigquery.SchemaField("web_site_sk", "INTEGER", mode="REQUIRED"), - bigquery.SchemaField("web_site_id", "STRING", mode="REQUIRED"), - bigquery.SchemaField("web_rec_start_date", "DATE", mode="NULLABLE"), - bigquery.SchemaField("web_rec_end_date", "DATE", mode="NULLABLE"), - bigquery.SchemaField("web_name", "STRING", mode="NULLABLE"), - bigquery.SchemaField("web_open_date_sk", "INTEGER", mode="NULLABLE"), - bigquery.SchemaField("web_close_date_sk", "INTEGER", mode="NULLABLE"), - bigquery.SchemaField("web_class", "STRING", mode="NULLABLE"), - bigquery.SchemaField("web_manager", "STRING", mode="NULLABLE"), - bigquery.SchemaField("web_mkt_id", "INTEGER", mode="NULLABLE"), - bigquery.SchemaField("web_mkt_class", "STRING", mode="NULLABLE"), - bigquery.SchemaField("web_mkt_desc", "STRING", mode="NULLABLE"), - bigquery.SchemaField("web_market_manager", "STRING", mode="NULLABLE"), - bigquery.SchemaField("web_company_id", "INTEGER", mode="NULLABLE"), - bigquery.SchemaField("web_company_name", "STRING", mode="NULLABLE"), - bigquery.SchemaField("web_street_number", "STRING", mode="NULLABLE"), - bigquery.SchemaField("web_street_name", "STRING", mode="NULLABLE"), - bigquery.SchemaField("web_street_type", "STRING", mode="NULLABLE"), - bigquery.SchemaField("web_suite_number", "STRING", mode="NULLABLE"), - bigquery.SchemaField("web_city", "STRING", mode="NULLABLE"), - bigquery.SchemaField("web_county", "STRING", mode="NULLABLE"), - bigquery.SchemaField("web_state", "STRING", mode="NULLABLE"), - bigquery.SchemaField("web_zip", "STRING", mode="NULLABLE"), - bigquery.SchemaField("web_country", "STRING", mode="NULLABLE"), - bigquery.SchemaField("web_gmt_offset", "FLOAT", mode="NULLABLE"), - bigquery.SchemaField("web_tax_percentage", "FLOAT", mode="NULLABLE"), - ], - "store_returns": [ - bigquery.SchemaField("sr_returned_date_sk", "INTEGER", mode="NULLABLE"), - bigquery.SchemaField("sr_return_time_sk", "INTEGER", mode="NULLABLE"), - bigquery.SchemaField("sr_item_sk", "INTEGER", mode="REQUIRED"), - bigquery.SchemaField("sr_customer_sk", "INTEGER", mode="NULLABLE"), - bigquery.SchemaField("sr_cdemo_sk", "INTEGER", mode="NULLABLE"), - bigquery.SchemaField("sr_hdemo_sk", "INTEGER", mode="NULLABLE"), - bigquery.SchemaField("sr_addr_sk", "INTEGER", mode="NULLABLE"), - bigquery.SchemaField("sr_store_sk", "INTEGER", mode="NULLABLE"), - bigquery.SchemaField("sr_reason_sk", "INTEGER", mode="NULLABLE"), - bigquery.SchemaField("sr_ticket_number", "INTEGER", mode="REQUIRED"), - bigquery.SchemaField("sr_return_quantity", "INTEGER", mode="NULLABLE"), - bigquery.SchemaField("sr_return_amt", "FLOAT", mode="NULLABLE"), - bigquery.SchemaField("sr_return_tax", "FLOAT", mode="NULLABLE"), - bigquery.SchemaField("sr_return_amt_inc_tax", "FLOAT", mode="NULLABLE"), - bigquery.SchemaField("sr_fee", "FLOAT", mode="NULLABLE"), - bigquery.SchemaField("sr_return_ship_cost", "FLOAT", mode="NULLABLE"), - bigquery.SchemaField("sr_refunded_cash", "FLOAT", mode="NULLABLE"), - bigquery.SchemaField("sr_reversed_charge", "FLOAT", mode="NULLABLE"), - bigquery.SchemaField("sr_store_credit", "FLOAT", mode="NULLABLE"), - bigquery.SchemaField("sr_net_loss", "FLOAT", mode="NULLABLE"), - ], - "household_demographics": [ - bigquery.SchemaField("hd_demo_sk", "INTEGER", mode="REQUIRED"), - bigquery.SchemaField("hd_income_band_sk", "INTEGER", mode="NULLABLE"), - bigquery.SchemaField("hd_buy_potential", "STRING", mode="NULLABLE"), - bigquery.SchemaField("hd_dep_count", "INTEGER", mode="NULLABLE"), - bigquery.SchemaField("hd_vehicle_count", "INTEGER", mode="NULLABLE"), - ], - "web_page": [ - bigquery.SchemaField("wp_web_page_sk", "INTEGER", mode="REQUIRED"), - bigquery.SchemaField("wp_web_page_id", "STRING", mode="REQUIRED"), - bigquery.SchemaField("wp_rec_start_date", "DATE", mode="NULLABLE"), - bigquery.SchemaField("wp_rec_end_date", "DATE", mode="NULLABLE"), - bigquery.SchemaField("wp_creation_date_sk", "INTEGER", mode="NULLABLE"), - bigquery.SchemaField("wp_access_date_sk", "INTEGER", mode="NULLABLE"), - bigquery.SchemaField("wp_autogen_flag", "STRING", mode="NULLABLE"), - bigquery.SchemaField("wp_customer_sk", "INTEGER", mode="NULLABLE"), - bigquery.SchemaField("wp_url", "STRING", mode="NULLABLE"), - bigquery.SchemaField("wp_type", "STRING", mode="NULLABLE"), - bigquery.SchemaField("wp_char_count", "INTEGER", mode="NULLABLE"), - bigquery.SchemaField("wp_link_count", "INTEGER", mode="NULLABLE"), - bigquery.SchemaField("wp_image_count", "INTEGER", mode="NULLABLE"), - bigquery.SchemaField("wp_max_ad_count", "INTEGER", mode="NULLABLE"), - ], - "promotion": [ - bigquery.SchemaField("p_promo_sk", "INTEGER", mode="REQUIRED"), - bigquery.SchemaField("p_promo_id", "STRING", mode="REQUIRED"), - bigquery.SchemaField("p_start_date_sk", "INTEGER", mode="NULLABLE"), - bigquery.SchemaField("p_end_date_sk", "INTEGER", mode="NULLABLE"), - bigquery.SchemaField("p_item_sk", "INTEGER", mode="NULLABLE"), - bigquery.SchemaField("p_cost", "FLOAT", mode="NULLABLE"), - bigquery.SchemaField("p_response_target", "INTEGER", mode="NULLABLE"), - bigquery.SchemaField("p_promo_name", "STRING", mode="NULLABLE"), - bigquery.SchemaField("p_channel_dmail", "STRING", mode="NULLABLE"), - bigquery.SchemaField("p_channel_email", "STRING", mode="NULLABLE"), - bigquery.SchemaField("p_channel_catalog", "STRING", mode="NULLABLE"), - bigquery.SchemaField("p_channel_tv", "STRING", mode="NULLABLE"), - bigquery.SchemaField("p_channel_radio", "STRING", mode="NULLABLE"), - bigquery.SchemaField("p_channel_press", "STRING", mode="NULLABLE"), - bigquery.SchemaField("p_channel_event", "STRING", mode="NULLABLE"), - bigquery.SchemaField("p_channel_demo", "STRING", mode="NULLABLE"), - bigquery.SchemaField("p_channel_details", "STRING", mode="NULLABLE"), - bigquery.SchemaField("p_purpose", "STRING", mode="NULLABLE"), - bigquery.SchemaField("p_discount_active", "STRING", mode="NULLABLE"), - ], - "catalog_page": [ - bigquery.SchemaField("cp_catalog_page_sk", "INTEGER", mode="REQUIRED"), - bigquery.SchemaField("cp_catalog_page_id", "STRING", mode="REQUIRED"), - bigquery.SchemaField("cp_start_date_sk", "INTEGER", mode="NULLABLE"), - bigquery.SchemaField("cp_end_date_sk", "INTEGER", mode="NULLABLE"), - bigquery.SchemaField("cp_department", "STRING", mode="NULLABLE"), - bigquery.SchemaField("cp_catalog_number", "INTEGER", mode="NULLABLE"), - bigquery.SchemaField("cp_catalog_page_number", "INTEGER", mode="NULLABLE"), - bigquery.SchemaField("cp_description", "STRING", mode="NULLABLE"), - bigquery.SchemaField("cp_type", "STRING", mode="NULLABLE"), - ], - "inventory": [ - bigquery.SchemaField("inv_date_sk", "INTEGER", mode="REQUIRED"), - bigquery.SchemaField("inv_item_sk", "INTEGER", mode="REQUIRED"), - bigquery.SchemaField("inv_warehouse_sk", "INTEGER", mode="REQUIRED"), - bigquery.SchemaField("inv_quantity_on_hand", "INTEGER", mode="NULLABLE"), - ], - "catalog_returns": [ - bigquery.SchemaField("cr_returned_date_sk", "INTEGER", mode="NULLABLE"), - bigquery.SchemaField("cr_returned_time_sk", "INTEGER", mode="NULLABLE"), - bigquery.SchemaField("cr_item_sk", "INTEGER", mode="REQUIRED"), - bigquery.SchemaField("cr_refunded_customer_sk", "INTEGER", mode="NULLABLE"), - bigquery.SchemaField("cr_refunded_cdemo_sk", "INTEGER", mode="NULLABLE"), - bigquery.SchemaField("cr_refunded_hdemo_sk", "INTEGER", mode="NULLABLE"), - bigquery.SchemaField("cr_refunded_addr_sk", "INTEGER", mode="NULLABLE"), - bigquery.SchemaField( - "cr_returning_customer_sk", "INTEGER", mode="NULLABLE" - ), - bigquery.SchemaField("cr_returning_cdemo_sk", "INTEGER", mode="NULLABLE"), - bigquery.SchemaField("cr_returning_hdemo_sk", "INTEGER", mode="NULLABLE"), - bigquery.SchemaField("cr_returning_addr_sk", "INTEGER", mode="NULLABLE"), - bigquery.SchemaField("cr_call_center_sk", "INTEGER", mode="NULLABLE"), - bigquery.SchemaField("cr_catalog_page_sk", "INTEGER", mode="NULLABLE"), - bigquery.SchemaField("cr_ship_mode_sk", "INTEGER", mode="NULLABLE"), - bigquery.SchemaField("cr_warehouse_sk", "INTEGER", mode="NULLABLE"), - bigquery.SchemaField("cr_reason_sk", "INTEGER", mode="NULLABLE"), - bigquery.SchemaField("cr_order_number", "INTEGER", mode="REQUIRED"), - bigquery.SchemaField("cr_return_quantity", "INTEGER", mode="NULLABLE"), - bigquery.SchemaField("cr_return_amount", "FLOAT", mode="NULLABLE"), - bigquery.SchemaField("cr_return_tax", "FLOAT", mode="NULLABLE"), - bigquery.SchemaField("cr_return_amt_inc_tax", "FLOAT", mode="NULLABLE"), - bigquery.SchemaField("cr_fee", "FLOAT", mode="NULLABLE"), - bigquery.SchemaField("cr_return_ship_cost", "FLOAT", mode="NULLABLE"), - bigquery.SchemaField("cr_refunded_cash", "FLOAT", mode="NULLABLE"), - bigquery.SchemaField("cr_reversed_charge", "FLOAT", mode="NULLABLE"), - bigquery.SchemaField("cr_store_credit", "FLOAT", mode="NULLABLE"), - bigquery.SchemaField("cr_net_loss", "FLOAT", mode="NULLABLE"), - ], - "web_returns": [ - bigquery.SchemaField("wr_returned_date_sk", "INTEGER", mode="NULLABLE"), - bigquery.SchemaField("wr_returned_time_sk", "INTEGER", mode="NULLABLE"), - bigquery.SchemaField("wr_item_sk", "INTEGER", mode="REQUIRED"), - bigquery.SchemaField("wr_refunded_customer_sk", "INTEGER", mode="NULLABLE"), - bigquery.SchemaField("wr_refunded_cdemo_sk", "INTEGER", mode="NULLABLE"), - bigquery.SchemaField("wr_refunded_hdemo_sk", "INTEGER", mode="NULLABLE"), - bigquery.SchemaField("wr_refunded_addr_sk", "INTEGER", mode="NULLABLE"), - bigquery.SchemaField( - "wr_returning_customer_sk", "INTEGER", mode="NULLABLE" - ), - bigquery.SchemaField("wr_returning_cdemo_sk", "INTEGER", mode="NULLABLE"), - bigquery.SchemaField("wr_returning_hdemo_sk", "INTEGER", mode="NULLABLE"), - bigquery.SchemaField("wr_returning_addr_sk", "INTEGER", mode="NULLABLE"), - bigquery.SchemaField("wr_web_page_sk", "INTEGER", mode="NULLABLE"), - bigquery.SchemaField("wr_reason_sk", "INTEGER", mode="NULLABLE"), - bigquery.SchemaField("wr_order_number", "INTEGER", mode="REQUIRED"), - bigquery.SchemaField("wr_return_quantity", "INTEGER", mode="NULLABLE"), - bigquery.SchemaField("wr_return_amt", "FLOAT", mode="NULLABLE"), - bigquery.SchemaField("wr_return_tax", "FLOAT", mode="NULLABLE"), - bigquery.SchemaField("wr_return_amt_inc_tax", "FLOAT", mode="NULLABLE"), - bigquery.SchemaField("wr_fee", "FLOAT", mode="NULLABLE"), - bigquery.SchemaField("wr_return_ship_cost", "FLOAT", mode="NULLABLE"), - bigquery.SchemaField("wr_refunded_cash", "FLOAT", mode="NULLABLE"), - bigquery.SchemaField("wr_reversed_charge", "FLOAT", mode="NULLABLE"), - bigquery.SchemaField("wr_account_credit", "FLOAT", mode="NULLABLE"), - bigquery.SchemaField("wr_net_loss", "FLOAT", mode="NULLABLE"), - ], - "web_sales": [ - bigquery.SchemaField("ws_sold_date_sk", "INTEGER", mode="NULLABLE"), - bigquery.SchemaField("ws_sold_time_sk", "INTEGER", mode="NULLABLE"), - bigquery.SchemaField("ws_ship_date_sk", "INTEGER", mode="NULLABLE"), - bigquery.SchemaField("ws_item_sk", "INTEGER", mode="REQUIRED"), - bigquery.SchemaField("ws_bill_customer_sk", "INTEGER", mode="NULLABLE"), - bigquery.SchemaField("ws_bill_cdemo_sk", "INTEGER", mode="NULLABLE"), - bigquery.SchemaField("ws_bill_hdemo_sk", "INTEGER", mode="NULLABLE"), - bigquery.SchemaField("ws_bill_addr_sk", "INTEGER", mode="NULLABLE"), - bigquery.SchemaField("ws_ship_customer_sk", "INTEGER", mode="NULLABLE"), - bigquery.SchemaField("ws_ship_cdemo_sk", "INTEGER", mode="NULLABLE"), - bigquery.SchemaField("ws_ship_hdemo_sk", "INTEGER", mode="NULLABLE"), - bigquery.SchemaField("ws_ship_addr_sk", "INTEGER", mode="NULLABLE"), - bigquery.SchemaField("ws_web_page_sk", "INTEGER", mode="NULLABLE"), - bigquery.SchemaField("ws_web_site_sk", "INTEGER", mode="NULLABLE"), - bigquery.SchemaField("ws_ship_mode_sk", "INTEGER", mode="NULLABLE"), - bigquery.SchemaField("ws_warehouse_sk", "INTEGER", mode="NULLABLE"), - bigquery.SchemaField("ws_promo_sk", "INTEGER", mode="NULLABLE"), - bigquery.SchemaField("ws_order_number", "INTEGER", mode="REQUIRED"), - bigquery.SchemaField("ws_quantity", "INTEGER", mode="NULLABLE"), - bigquery.SchemaField("ws_wholesale_cost", "FLOAT", mode="NULLABLE"), - bigquery.SchemaField("ws_list_price", "FLOAT", mode="NULLABLE"), - bigquery.SchemaField("ws_sales_price", "FLOAT", mode="NULLABLE"), - bigquery.SchemaField("ws_ext_discount_amt", "FLOAT", mode="NULLABLE"), - bigquery.SchemaField("ws_ext_sales_price", "FLOAT", mode="NULLABLE"), - bigquery.SchemaField("ws_ext_wholesale_cost", "FLOAT", mode="NULLABLE"), - bigquery.SchemaField("ws_ext_list_price", "FLOAT", mode="NULLABLE"), - bigquery.SchemaField("ws_ext_tax", "FLOAT", mode="NULLABLE"), - bigquery.SchemaField("ws_coupon_amt", "FLOAT", mode="NULLABLE"), - bigquery.SchemaField("ws_ext_ship_cost", "FLOAT", mode="NULLABLE"), - bigquery.SchemaField("ws_net_paid", "FLOAT", mode="NULLABLE"), - bigquery.SchemaField("ws_net_paid_inc_tax", "FLOAT", mode="NULLABLE"), - bigquery.SchemaField("ws_net_paid_inc_ship", "FLOAT", mode="NULLABLE"), - bigquery.SchemaField("ws_net_paid_inc_ship_tax", "FLOAT", mode="NULLABLE"), - bigquery.SchemaField("ws_net_profit", "FLOAT", mode="NULLABLE"), - ], - "catalog_sales": [ - bigquery.SchemaField("cs_sold_date_sk", "INTEGER", mode="NULLABLE"), - bigquery.SchemaField("cs_sold_time_sk", "INTEGER", mode="NULLABLE"), - bigquery.SchemaField("cs_ship_date_sk", "INTEGER", mode="NULLABLE"), - bigquery.SchemaField("cs_bill_customer_sk", "INTEGER", mode="NULLABLE"), - bigquery.SchemaField("cs_bill_cdemo_sk", "INTEGER", mode="NULLABLE"), - bigquery.SchemaField("cs_bill_hdemo_sk", "INTEGER", mode="NULLABLE"), - bigquery.SchemaField("cs_bill_addr_sk", "INTEGER", mode="NULLABLE"), - bigquery.SchemaField("cs_ship_customer_sk", "INTEGER", mode="NULLABLE"), - bigquery.SchemaField("cs_ship_cdemo_sk", "INTEGER", mode="NULLABLE"), - bigquery.SchemaField("cs_ship_hdemo_sk", "INTEGER", mode="NULLABLE"), - bigquery.SchemaField("cs_ship_addr_sk", "INTEGER", mode="NULLABLE"), - bigquery.SchemaField("cs_call_center_sk", "INTEGER", mode="NULLABLE"), - bigquery.SchemaField("cs_catalog_page_sk", "INTEGER", mode="NULLABLE"), - bigquery.SchemaField("cs_ship_mode_sk", "INTEGER", mode="NULLABLE"), - bigquery.SchemaField("cs_warehouse_sk", "INTEGER", mode="NULLABLE"), - bigquery.SchemaField("cs_item_sk", "INTEGER", mode="REQUIRED"), - bigquery.SchemaField("cs_promo_sk", "INTEGER", mode="NULLABLE"), - bigquery.SchemaField("cs_order_number", "INTEGER", mode="REQUIRED"), - bigquery.SchemaField("cs_quantity", "INTEGER", mode="NULLABLE"), - bigquery.SchemaField("cs_wholesale_cost", "FLOAT", mode="NULLABLE"), - bigquery.SchemaField("cs_list_price", "FLOAT", mode="NULLABLE"), - bigquery.SchemaField("cs_sales_price", "FLOAT", mode="NULLABLE"), - bigquery.SchemaField("cs_ext_discount_amt", "FLOAT", mode="NULLABLE"), - bigquery.SchemaField("cs_ext_sales_price", "FLOAT", mode="NULLABLE"), - bigquery.SchemaField("cs_ext_wholesale_cost", "FLOAT", mode="NULLABLE"), - bigquery.SchemaField("cs_ext_list_price", "FLOAT", mode="NULLABLE"), - bigquery.SchemaField("cs_ext_tax", "FLOAT", mode="NULLABLE"), - bigquery.SchemaField("cs_coupon_amt", "FLOAT", mode="NULLABLE"), - bigquery.SchemaField("cs_ext_ship_cost", "FLOAT", mode="NULLABLE"), - bigquery.SchemaField("cs_net_paid", "FLOAT", mode="NULLABLE"), - bigquery.SchemaField("cs_net_paid_inc_tax", "FLOAT", mode="NULLABLE"), - bigquery.SchemaField("cs_net_paid_inc_ship", "FLOAT", mode="NULLABLE"), - bigquery.SchemaField("cs_net_paid_inc_ship_tax", "FLOAT", mode="NULLABLE"), - bigquery.SchemaField("cs_net_profit", "FLOAT", mode="NULLABLE"), - ], - "store_sales": [ - bigquery.SchemaField("ss_sold_date_sk", "INTEGER", mode="NULLABLE"), - bigquery.SchemaField("ss_sold_time_sk", "INTEGER", mode="NULLABLE"), - bigquery.SchemaField("ss_item_sk", "INTEGER", mode="REQUIRED"), - bigquery.SchemaField("ss_customer_sk", "INTEGER", mode="NULLABLE"), - bigquery.SchemaField("ss_cdemo_sk", "INTEGER", mode="NULLABLE"), - bigquery.SchemaField("ss_hdemo_sk", "INTEGER", mode="NULLABLE"), - bigquery.SchemaField("ss_addr_sk", "INTEGER", mode="NULLABLE"), - bigquery.SchemaField("ss_store_sk", "INTEGER", mode="NULLABLE"), - bigquery.SchemaField("ss_promo_sk", "INTEGER", mode="NULLABLE"), - bigquery.SchemaField("ss_ticket_number", "INTEGER", mode="REQUIRED"), - bigquery.SchemaField("ss_quantity", "INTEGER", mode="NULLABLE"), - bigquery.SchemaField("ss_wholesale_cost", "FLOAT", mode="NULLABLE"), - bigquery.SchemaField("ss_list_price", "FLOAT", mode="NULLABLE"), - bigquery.SchemaField("ss_sales_price", "FLOAT", mode="NULLABLE"), - bigquery.SchemaField("ss_ext_discount_amt", "FLOAT", mode="NULLABLE"), - bigquery.SchemaField("ss_ext_sales_price", "FLOAT", mode="NULLABLE"), - bigquery.SchemaField("ss_ext_wholesale_cost", "FLOAT", mode="NULLABLE"), - bigquery.SchemaField("ss_ext_list_price", "FLOAT", mode="NULLABLE"), - bigquery.SchemaField("ss_ext_tax", "FLOAT", mode="NULLABLE"), - bigquery.SchemaField("ss_coupon_amt", "FLOAT", mode="NULLABLE"), - bigquery.SchemaField("ss_net_paid", "FLOAT", mode="NULLABLE"), - bigquery.SchemaField("ss_net_paid_inc_tax", "FLOAT", mode="NULLABLE"), - bigquery.SchemaField("ss_net_profit", "FLOAT", mode="NULLABLE"), - ], - } - - return schema[table_name] - - -def load_data_to_bigquery(table_name, file_paths, client, dataset_ref, temp_file): - """Loads data from a list of files into a BigQuery table.""" - job_config = bigquery.LoadJobConfig( - source_format=bigquery.SourceFormat.CSV, - skip_leading_rows=0, # No header in .dat files - field_delimiter="|", - schema=get_schema(table_name), - ) - - table_ref = dataset_ref.table(table_name) - table = bigquery.Table(table_ref) - client.create_table(table) - - # Load data from each file - for file_path in sorted(file_paths): - preprocess_csv(file_path, temp_file) - with open(temp_file, "rb") as source_file: - job = client.load_table_from_file( - source_file, table_ref, job_config=job_config - ) - job.result() - print( - f"Loaded data from {file_path} into table {project_id}:{dataset_id}.{table_name}" - ) - - -if __name__ == "__main__": - """ - Loads TPC-DS data to BigQuery. - - This script loads TPC-DS data generated with source code from - https://www.tpc.org/tpc_documents_current_versions/current_specifications5.asp - into BigQuery. - - Note: If the dataset already exists, the script will exit without uploading data. - - Usage: - python tpcds_upload_helper.py --project_id --dataset_id --ds_path - python tpcds_upload_helper.py -d -p -s - """ - parser = argparse.ArgumentParser(description="Load TPC-DS data to BigQuery") - parser.add_argument( - "--project_id", "-p", required=True, help="Google Cloud project ID" - ) - parser.add_argument("--dataset_id", "-d", required=True, help="BigQuery dataset ID") - parser.add_argument( - "--ds_path", "-s", required=True, help="Path to the TPC-DS data directory" - ) - args = parser.parse_args() - - project_id = args.project_id - dataset_id = args.dataset_id - ds_path = args.ds_path - temp_file = "temp.csv" - - # Initialize BigQuery client - client = bigquery.Client(project=project_id) - dataset_ref = client.dataset(dataset_id) - try: - # Quit if dataset exists - client.get_dataset(dataset_ref) - print(f"Dataset {project_id}:{dataset_id} already exists. Skipping.") - sys.exit(1) - except google.api_core.exceptions.NotFound: - # Create the dataset if it doesn't exist - dataset = bigquery.Dataset(dataset_ref) - client.create_dataset(dataset) - print(f"Created dataset {project_id}:{dataset_id}") - - # Iterate through the folders - for table_name in sorted(os.listdir(ds_path)): - table_path = os.path.join(ds_path, table_name) - table_name = table_name.split(".")[0] - if os.path.isdir(table_path): - file_paths = [ - os.path.join(table_path, f) - for f in os.listdir(table_path) - if f.endswith(".dat") - ] - load_data_to_bigquery( - table_name, file_paths, client, dataset_ref, temp_file - ) - - try: - os.remove(temp_file) - print("Removed temporary file: temp.csv") - except FileNotFoundError: - print("Temporary file not found.") diff --git a/scripts/generate_bigframes_bigquery.py b/scripts/generate_bigframes_bigquery.py deleted file mode 100755 index fe90dc43472..00000000000 --- a/scripts/generate_bigframes_bigquery.py +++ /dev/null @@ -1,45 +0,0 @@ -#!/usr/bin/env -S uv run --active --script -# -# /// script -# dependencies = [ -# "jinja2", -# "pyyaml", -# "ruff==0.14.14", -# ] -# /// -# -# Copyright 2026 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import pathlib -import sys - -scripts_dir = pathlib.Path(__file__).parent -if str(scripts_dir) not in sys.path: - sys.path.insert(0, str(scripts_dir)) - -from bigquery_generator import constants, file_generator, yaml_parser # noqa: E402 - - -def main() -> None: - modules = [] - - for yaml_file in sorted(constants.DATA_DIR.glob("**/*.yaml")): - modules.append(yaml_parser.parse_yaml(yaml_file)) - - file_generator.generate(modules) - - -if __name__ == "__main__": - main() diff --git a/scripts/get_documentation_coverage.py b/scripts/get_documentation_coverage.py deleted file mode 100755 index a6566cafab0..00000000000 --- a/scripts/get_documentation_coverage.py +++ /dev/null @@ -1,172 +0,0 @@ -# Copyright 2023 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 argparse -import importlib -import inspect -import sys -import typing - -import bigframes -import bigframes.pandas as bpd - -PRESENT = "present" -NOT_PRESENT = "not_present" - -CLASSES = [ - bpd.DataFrame, - bpd.Series, - bpd.Index, - bigframes.session.Session, - bigframes.operations.strings.StringMethods, - bigframes.operations.datetimes.DatetimeMethods, - bigframes.operations.structs.StructAccessor, -] - -ML_MODULE_NAMES = [ - "cluster", - "compose", - "decomposition", - "ensemble", - "linear_model", - "metrics", - "model_selection", - "pipeline", - "preprocessing", - "llm", - "forecasting", - "imported", - "remote", -] - -COVERAGE_GENERATORS = { - "documentation": lambda docstr: docstr, - "code samples": lambda docstr: docstr and "**Examples:**" in docstr, -} - -for module_name in ML_MODULE_NAMES: - module = importlib.import_module(f"bigframes.ml.{module_name}") - classes_ = [ - class_ for _, class_ in inspect.getmembers(module, predicate=inspect.isclass) - ] - CLASSES.extend(classes_) - - -def get_coverage_summary( - func: typing.Callable, -) -> typing.Dict[str, typing.Dict[str, typing.List[str]]]: - """Get Summary of the code samples coverage in BigFrames APIs. - - Args: - func (callable): - Function to accept documentation and return whether it satisfies - coverage. - Returns: - Summary: A dictionary of the format - { - class_1: { - "present": [method1, method2, ...], - "not_present": [method3, method4, ...] - }, - class_2: { - ... - } - } - """ - summary: typing.Dict[str, typing.Dict[str, typing.List[str]]] = dict() - - for class_ in CLASSES: - class_key = f"{class_.__module__}.{class_.__name__}" - summary[class_key] = {PRESENT: [], NOT_PRESENT: []} - - members = inspect.getmembers(class_) - - for name, obj in members: - # ignore private methods - if name.startswith("_") and not name.startswith("__"): - continue - - # ignore constructor - if name == "__init__": - continue - - def predicate(impl): - return ( - # This includes class methods like `from_dict`, `from_records` - inspect.ismethod(impl) - # This includes instance methods like `dropna`, join` - or inspect.isfunction(impl) - # This includes properties like `shape`, `values` but not - # generic properties like `__weakref__` - or (inspect.isdatadescriptor(impl) and not name.startswith("__")) - ) - - if not predicate(obj): - continue - - # At this point we have a property or a public method - impl = getattr(class_, name) - - docstr = inspect.getdoc(impl) - coverage_present = func(docstr) - key = PRESENT if coverage_present else NOT_PRESENT - summary[class_key][key].append(name) - - return summary - - -if __name__ == "__main__": - parser = argparse.ArgumentParser( - description="Get a summary of documentation coverage in BigFrames APIs." - ) - parser.add_argument( - "-c", - "--code-samples", - type=bool, - action=argparse.BooleanOptionalAction, - default=False, - help="Whether to calculate code samples coverage. By default the tool" - " calculates the documentation (docstring) coverage.", - ) - parser.add_argument( - "-d", - "--details", - type=bool, - action=argparse.BooleanOptionalAction, - default=False, - help="Whether to print APIs with and without the coverage.", - ) - - args = parser.parse_args(sys.argv[1:]) - - scenario = "code samples" if args.code_samples else "documentation" - summary = get_coverage_summary(COVERAGE_GENERATORS[scenario]) - - total_with_code_samples = 0 - total = 0 - for class_, class_summary in summary.items(): - apis_with_code_samples = len(class_summary[PRESENT]) - total_with_code_samples += apis_with_code_samples - - apis_total = len(class_summary[PRESENT]) + len(class_summary[NOT_PRESENT]) - total += apis_total - - coverage = 100 * apis_with_code_samples / apis_total - print(f"{class_}: {coverage:.1f}% ({apis_with_code_samples}/{apis_total})") - if args.details: - print(f"===> APIs WITH {scenario}: {class_summary[PRESENT]}") - print(f"===> APIs WITHOUT {scenario}: {class_summary[NOT_PRESENT]}") - - coverage = 100 * total_with_code_samples / total - print(f"Total: {coverage:.1f}% ({total_with_code_samples}/{total})") diff --git a/scripts/manage_cloud_functions.py b/scripts/manage_cloud_functions.py deleted file mode 100644 index c92be4ebadb..00000000000 --- a/scripts/manage_cloud_functions.py +++ /dev/null @@ -1,229 +0,0 @@ -# Copyright 2023 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 argparse -import datetime as dt -import sys -import time - -import google.api_core.exceptions -from google.cloud import functions_v2 - -GCF_REGIONS_ALL = [ - "asia-east1", - "asia-east2", - "asia-northeast1", - "asia-northeast2", - "europe-north1", - "europe-southwest1", - "europe-west1", - "europe-west2", - "europe-west4", - "europe-west8", - "europe-west9", - "us-central1", - "us-east1", - "us-east4", - "us-east5", - "us-south1", - "us-west1", - "asia-east2", - "asia-northeast3", - "asia-southeast1", - "asia-southeast2", - "asia-south1", - "asia-south2", - "australia-southeast1", - "australia-southeast2", - "europe-central2", - "europe-west2", - "europe-west3", - "europe-west6", - "northamerica-northeast1", - "northamerica-northeast2", - "southamerica-east1", - "southamerica-west1", - "us-west2", - "us-west3", - "us-west4", -] - -GCF_CLIENT = functions_v2.FunctionServiceClient() - - -def get_bigframes_functions(project, region): - parent = f"projects/{project}/locations/{region}" - functions = GCF_CLIENT.list_functions( - functions_v2.ListFunctionsRequest(parent=parent) - ) - - # Filter bigframes created functions - functions = [ - function - for function in functions - if function.name.startswith( - f"projects/{project}/locations/{region}/functions/bigframes-" - ) - ] - - return functions - - -def summarize_gcfs(args): - """Summarize number of bigframes cloud functions in various regions.""" - - region_counts = {} - for region in args.regions: - functions = get_bigframes_functions(args.project_id, region) - functions_count = len(functions) - - # Exclude reporting regions with 0 bigframes GCFs - if functions_count == 0: - continue - - # Count how many GCFs are newer than a day - recent = 0 - for f in functions: - age = dt.datetime.now() - dt.datetime.fromtimestamp( - f.update_time.timestamp() - ) - if age.total_seconds() < args.recency_cutoff: - recent += 1 - - region_counts[region] = (functions_count, recent) - - for item in sorted( - region_counts.items(), key=lambda item: item[1][0], reverse=True - ): - region = item[0] - count, recent = item[1] - print( - "{}: Total={}, Recent={}, Older={}".format( - region, count, recent, count - recent - ) - ) - - -def cleanup_gcfs(args): - """Clean-up bigframes cloud functions in the given regions.""" - max_delete_per_region = args.number - - for region in args.regions: - functions = get_bigframes_functions(args.project_id, region) - count = 0 - for f in functions: - age = dt.datetime.now() - dt.datetime.fromtimestamp( - f.update_time.timestamp() - ) - if age.total_seconds() >= args.recency_cutoff: - try: - count += 1 - GCF_CLIENT.delete_function(name=f.name) - print( - f"[{region}]: deleted [{count}] {f.name} last updated on {f.update_time}" - ) - if count >= max_delete_per_region: - break - # Mostly there is a 60 mutations per minute quota, we want to use 10% of - # that for this clean-up, i.e. 6 mutations per minute. So wait for - # 60/6 = 10 seconds - time.sleep(10) - except google.api_core.exceptions.NotFound: - # Most likely the function was deleted otherwise - pass - except google.api_core.exceptions.ResourceExhausted: - # Stop deleting in this region for now - print( - f"Failed to delete function in region {region} due to quota exhaustion. Pausing for 2 minutes." - ) - time.sleep(120) - - -def list_str(values): - return [val for val in values.split(",") if val] - - -def get_project_from_environment(): - from google.cloud import bigquery - - return bigquery.Client().project - - -if __name__ == "__main__": - parser = argparse.ArgumentParser( - description="Manage cloud functions created to serve bigframes remote functions." - ) - parser.add_argument( - "-p", - "--project-id", - type=str, - required=False, - action="store", - help="GCP project-id. If not provided, the project-id resolved by the" - " BigQuery client from the user environment would be used.", - ) - parser.add_argument( - "-r", - "--regions", - type=list_str, - required=False, - default=GCF_REGIONS_ALL, - action="store", - help="Cloud functions region(s). If multiple regions, Specify comma separated (e.g. region1,region2)", - ) - - def hours_to_timedelta(hrs): - return dt.timedelta(hours=int(hrs)).total_seconds() - - parser.add_argument( - "-c", - "--recency-cutoff", - type=hours_to_timedelta, - required=False, - default=hours_to_timedelta("24"), - action="store", - help="Number of hours, cloud functions older than which should be considered stale (worthy of cleanup).", - ) - - subparsers = parser.add_subparsers(title="subcommands", required=True) - parser_summary = subparsers.add_parser( - "summary", - help="BigFrames cloud functions summary.", - description="Show the bigframes cloud functions summary.", - ) - parser_summary.set_defaults(func=summarize_gcfs) - parser_cleanup = subparsers.add_parser( - "cleanup", - help="BigFrames cloud functions clean up.", - description="Delete the stale bigframes cloud functions.", - ) - parser_cleanup.add_argument( - "-n", - "--number", - type=int, - required=False, - default=100, - action="store", - help="Number of stale (more than a day old) cloud functions to clean up.", - ) - parser_cleanup.set_defaults(func=cleanup_gcfs) - - args = parser.parse_args(sys.argv[1:]) - if args.project_id is None: - args.project_id = get_project_from_environment() - if args.project_id is None: - raise ValueError( - "Could not resolve a project. Plese set it via --project-id option." - ) - args.func(args) diff --git a/scripts/notebooks_fill_params.py b/scripts/notebooks_fill_params.py deleted file mode 100644 index e0f7c8d687a..00000000000 --- a/scripts/notebooks_fill_params.py +++ /dev/null @@ -1,65 +0,0 @@ -# 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. -# 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 json -import os -import re -import shutil -import sys - -GOOGLE_CLOUD_PROJECT = os.environ["GOOGLE_CLOUD_PROJECT"] - - -def make_backup(notebook_path: str): - shutil.copy( - notebook_path, - f"{notebook_path}.backup", - ) - - -def replace_project(line): - """ - Notebooks contain special colab `param {type:"string"}` - comments, which make it easy for customers to fill in their - own information. - """ - # Make sure we're robust to whitespace differences. - cleaned = re.sub(r"\s", "", line) - if cleaned == 'PROJECT_ID=""#@param{type:"string"}': - return f'PROJECT_ID = "{GOOGLE_CLOUD_PROJECT}" # @param {{type:"string"}}\n' - else: - return line - - -def replace_params(notebook_path: str): - with open(notebook_path, "r", encoding="utf-8") as notebook_file: - notebook_json = json.load(notebook_file) - - for cell in notebook_json["cells"]: - lines = cell.get("source", []) - new_lines = [replace_project(line) for line in lines] - cell["source"] = new_lines - - with open(notebook_path, "w", encoding="utf-8") as notebook_file: - json.dump(notebook_json, notebook_file, indent=2, ensure_ascii=False) - - -def main(notebook_paths): - for notebook_path in notebook_paths: - make_backup(notebook_path) - replace_params(notebook_path) - - -if __name__ == "__main__": - main(sys.argv[1:]) diff --git a/scripts/notebooks_restore_from_backup.py b/scripts/notebooks_restore_from_backup.py deleted file mode 100644 index 4d3e0333e39..00000000000 --- a/scripts/notebooks_restore_from_backup.py +++ /dev/null @@ -1,35 +0,0 @@ -# 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. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import pathlib -import shutil -import sys - - -def restore_from_backup(notebook_path): - backup_path = pathlib.Path(f"{notebook_path}.backup") - if backup_path.exists(): - shutil.move( - backup_path, - notebook_path, - ) - - -def main(notebook_paths): - for notebook_path in notebook_paths: - restore_from_backup(notebook_path) - - -if __name__ == "__main__": - main(sys.argv[1:]) diff --git a/scripts/publish_api_coverage.py b/scripts/publish_api_coverage.py index f94cd7e6d7f..856307e4407 100644 --- a/scripts/publish_api_coverage.py +++ b/scripts/publish_api_coverage.py @@ -17,112 +17,29 @@ import argparse import inspect -import pathlib -import sys import pandas as pd -import pandas.core.groupby -import pandas.core.indexes.accessors -import pandas.core.strings.accessor -import pandas.core.window.rolling -import bigframes -import bigframes.core.groupby -import bigframes.core.window -import bigframes.operations.datetimes -import bigframes.operations.strings import bigframes.pandas as bpd -REPO_ROOT = pathlib.Path(__file__).parent.parent - -BIGFRAMES_OBJECT = { - "pandas": "bigframes.pandas", - "dataframe": "bigframes.pandas.DataFrame", - "dataframegroupby": "bigframes.pandas.api.typing.DataFrameGroupBy", - "index": "bigframes.pandas.Index", - "series": "bigframes.pandas.Series", - "seriesgroupby": "bigframes.pandas.api.typing.SeriesGroupBy", - "datetimemethods": "bigframes.pandas.api.typing.DatetimeMethods", - "stringmethods": "bigframes.pandas.api.typing.StringMethods", - "window": "bigframes.pandas.api.typing.Window", -} - - -PANDAS_TARGETS = [ - ("pandas", pd, bpd), - ("dataframe", pd.DataFrame, bpd.DataFrame), - ( - "dataframegroupby", - pandas.core.groupby.DataFrameGroupBy, - bigframes.core.groupby.DataFrameGroupBy, - ), - ("series", pd.Series, bpd.Series), - ( - "seriesgroupby", - pandas.core.groupby.DataFrameGroupBy, - bigframes.core.groupby.DataFrameGroupBy, - ), - ( - "datetimemethods", - pandas.core.indexes.accessors.CombinedDatetimelikeProperties, - bigframes.operations.datetimes.DatetimeMethods, - ), - ( - "stringmethods", - pandas.core.strings.accessor.StringMethods, - bigframes.operations.strings.StringMethods, - ), - ( - "window", - pandas.core.window.rolling.Rolling, - bigframes.core.window.Window, - ), - ("index", pd.Index, bpd.Index), -] - - -def names_from_signature(signature): - """Extract the names of parameters from signature - - See: https://docs.python.org/3/library/inspect.html#inspect.signature - """ - return frozenset({parameter for parameter in signature.parameters}) - - -def calculate_missing_parameters(bigframes_function, target_function): - # Some built-in functions can't be inspected. These raise a ValueError. - try: - bigframes_signature = inspect.signature(bigframes_function) - target_signature = inspect.signature(target_function) - except ValueError: - return {} - - bigframes_params = names_from_signature(bigframes_signature) - target_params = names_from_signature(target_signature) - return target_params - bigframes_params - def generate_pandas_api_coverage(): """Inspect all our pandas objects, and compare with the real pandas objects, to see which methods we implement. For each, generate a regex that can be used to check if its present in a notebook""" - header = [ - "api", - "pattern", - "kind", - "is_in_bigframes", - "missing_parameters", - "requires_index", - "requires_ordering", - ] + header = ["api", "pattern", "kind", "is_in_bigframes"] api_patterns = [] + targets = [ + ("pandas", pd, bpd), + ("dataframe", pd.DataFrame, bpd.DataFrame), + ("series", pd.Series, bpd.Series), + ("index", pd.Index, bpd.Index), + ] indexers = ["loc", "iloc", "iat", "ix", "at"] - for name, pandas_obj, bigframes_obj in PANDAS_TARGETS: + for name, pandas_obj, bigframes_obj in targets: for member in dir(pandas_obj): - missing_parameters = "" - # skip private functions and properties - if member[0] == "_": + if member[0] == "_" and member[1] != "_": continue # skip members that are also common python methods @@ -133,17 +50,6 @@ def generate_pandas_api_coverage(): # Function, match .member( token = f"\\.{member}\\(" token_type = "function" - - if hasattr(bigframes_obj, member): - bigframes_function = getattr(bigframes_obj, member) - pandas_function = getattr(pandas_obj, member) - missing_parameters = ", ".join( - sorted( - calculate_missing_parameters( - bigframes_function, pandas_function - ) - ) - ) elif member in indexers: # Indexer, match .indexer[ token = f"\\.{member}\\[" @@ -154,31 +60,9 @@ def generate_pandas_api_coverage(): token_type = "property" is_in_bigframes = hasattr(bigframes_obj, member) - requires_index = "" - requires_ordering = "" - - if is_in_bigframes: - attr = getattr(bigframes_obj, member) - - # TODO(b/361101138): Add check/documentation for partial - # support (e.g. with some parameters). - requires_index = ( - "Y" if hasattr(attr, "_validations_requires_index") else "" - ) - requires_ordering = ( - "Y" if hasattr(attr, "_validations_requires_ordering") else "" - ) api_patterns.append( - [ - f"{name}.{member}", - token, - token_type, - is_in_bigframes, - missing_parameters, - requires_index, - requires_ordering, - ] + [f"{name}.{member}", token, token_type, is_in_bigframes] ) return pd.DataFrame(api_patterns, columns=header) @@ -187,9 +71,6 @@ def generate_pandas_api_coverage(): def generate_sklearn_api_coverage(): """Explore all SKLearn modules, and for each item contained generate a regex to detect it being imported, and record whether we implement it""" - - import sklearn # noqa - sklearn_modules = [ "sklearn", "sklearn.model_selection", @@ -279,131 +160,19 @@ def build_api_coverage_table(bigframes_version: str, release_version: str): sklearn_cov_df["module"] = "bigframes.ml" combined_df = pd.concat([pandas_cov_df, sklearn_cov_df]) combined_df["timestamp"] = pd.Timestamp.now() - # BigQuery only supports microsecond precision timestamps. - combined_df["timestamp"] = combined_df["timestamp"].astype("datetime64[us]") combined_df["bigframes_version"] = bigframes_version combined_df["release_version"] = release_version - combined_df = combined_df.infer_objects().convert_dtypes() - return combined_df - - -def format_api(api_names, is_in_bigframes, api_prefix): - api_names = api_names.str.slice(start=len(f"{api_prefix}.")) - formatted = "" + api_names + "" - bigframes_object = BIGFRAMES_OBJECT.get(api_prefix) - if bigframes_object is None: - return formatted - - linked = ( - '' - + formatted - + "" - ) - return formatted.mask(is_in_bigframes, linked) - - -def generate_api_coverage(df, api_prefix): - dataframe_apis = df.loc[df["api"].str.startswith(f"{api_prefix}.")] - fully_implemented = ( - dataframe_apis["missing_parameters"].str.len() == 0 - ) & dataframe_apis["is_in_bigframes"] - partial_implemented = ( - dataframe_apis["missing_parameters"].str.len() != 0 - ) & dataframe_apis["is_in_bigframes"] - not_implemented = ~dataframe_apis["is_in_bigframes"] - - dataframe_table = pd.DataFrame( - { - "API": format_api( - dataframe_apis["api"], - dataframe_apis["is_in_bigframes"], - api_prefix, - ), - "Implemented": "", - "Requires index": dataframe_apis["requires_index"], - "Requires ordering": dataframe_apis["requires_ordering"], - "Missing parameters": dataframe_apis["missing_parameters"], - } - ) - dataframe_table.loc[fully_implemented, "Implemented"] = "Y" - dataframe_table.loc[partial_implemented, "Implemented"] = "P" - dataframe_table.loc[not_implemented, "Implemented"] = "N" - return dataframe_table - - -def generate_api_coverage_doc(df, api_prefix): - dataframe_table = generate_api_coverage(df, api_prefix) - dataframe_table = dataframe_table.loc[~(dataframe_table["Implemented"] == "N")] - dataframe_table["Implemented"] = dataframe_table["Implemented"].map( - { - "Y": "Y", - "P": "P", - } - ) - - with open( - REPO_ROOT / "docs" / "supported_pandas_apis" / f"bf_{api_prefix}.html", - "w", - ) as html_file: - dataframe_table.to_html( - html_file, index=False, header=True, escape=False, border=0, col_space="8em" - ) - - -def generate_api_coverage_docs(df): - for target in PANDAS_TARGETS: - api_prefix = target[0] - generate_api_coverage_doc(df, api_prefix) - - -def print_api_coverage_summary(df, api_prefix): - dataframe_table = generate_api_coverage(df, api_prefix) - - print(api_prefix) - print(dataframe_table[["Implemented", "API"]].groupby(["Implemented"]).count()) - print(f"{api_prefix} APIs: {dataframe_table.shape[0]}\n") - - -def print_api_coverage_summaries(df): - for target in PANDAS_TARGETS: - api_prefix = target[0] - print_api_coverage_summary(df, api_prefix) - - print(f"\nAll APIs: {len(df.index)}") - fully_implemented = (df["missing_parameters"].str.len() == 0) & df[ - "is_in_bigframes" - ] - print(f"Y: {fully_implemented.sum()}") - partial_implemented = (df["missing_parameters"].str.len() != 0) & df[ - "is_in_bigframes" - ] - print(f"P: {partial_implemented.sum()}") - not_implemented = ~df["is_in_bigframes"] - print(f"N: {not_implemented.sum()}") + return combined_df.infer_objects().convert_dtypes() def main(): parser = argparse.ArgumentParser() - parser.add_argument("output_type") - parser.add_argument("--bigframes_version", default=bigframes.__version__) - parser.add_argument("--release_version", default="") + parser.add_argument("--bigframes_version") + parser.add_argument("--release_version") parser.add_argument("--bigquery_table_name") args = parser.parse_args() df = build_api_coverage_table(args.bigframes_version, args.release_version) - - if args.output_type == "bigquery": - df.to_gbq(args.bigquery_table_name, if_exists="append") - elif args.output_type == "docs": - generate_api_coverage_docs(df) - elif args.output_type == "summary": - print_api_coverage_summaries(df) - else: - print(f"Unexpected output_type {repr(args.output_type)}") - sys.exit(1) + df.to_gbq(args.bigquery_table_name, if_exists="append") if __name__ == "__main__": diff --git a/scripts/readme-gen/readme_gen.py b/scripts/readme-gen/readme_gen.py new file mode 100644 index 00000000000..1acc119835b --- /dev/null +++ b/scripts/readme-gen/readme_gen.py @@ -0,0 +1,69 @@ +#!/usr/bin/env python + +# Copyright 2023 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. + +"""Generates READMEs using configuration defined in yaml.""" + +import argparse +import io +import os +import subprocess + +import jinja2 +import yaml + + +jinja_env = jinja2.Environment( + trim_blocks=True, + loader=jinja2.FileSystemLoader( + os.path.abspath(os.path.join(os.path.dirname(__file__), "templates")) + ), + autoescape=True, +) + +README_TMPL = jinja_env.get_template("README.tmpl.rst") + + +def get_help(file): + return subprocess.check_output(["python", file, "--help"]).decode() + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument("source") + parser.add_argument("--destination", default="README.rst") + + args = parser.parse_args() + + source = os.path.abspath(args.source) + root = os.path.dirname(source) + destination = os.path.join(root, args.destination) + + jinja_env.globals["get_help"] = get_help + + with io.open(source, "r") as f: + config = yaml.load(f) + + # This allows get_help to execute in the right directory. + os.chdir(root) + + output = README_TMPL.render(config) + + with io.open(destination, "w") as f: + f.write(output) + + +if __name__ == "__main__": + main() diff --git a/scripts/readme-gen/templates/README.tmpl.rst b/scripts/readme-gen/templates/README.tmpl.rst new file mode 100644 index 00000000000..4fd239765b0 --- /dev/null +++ b/scripts/readme-gen/templates/README.tmpl.rst @@ -0,0 +1,87 @@ +{# The following line is a lie. BUT! Once jinja2 is done with it, it will + become truth! #} +.. This file is automatically generated. Do not edit this file directly. + +{{product.name}} Python Samples +=============================================================================== + +.. image:: https://gstatic.com/cloudssh/images/open-btn.png + :target: https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/GoogleCloudPlatform/python-docs-samples&page=editor&open_in_editor={{folder}}/README.rst + + +This directory contains samples for {{product.name}}. {{product.description}} + +{{description}} + +.. _{{product.name}}: {{product.url}} + +{% if required_api_url %} +To run the sample, you need to enable the API at: {{required_api_url}} +{% endif %} + +{% if required_role %} +To run the sample, you need to have `{{required_role}}` role. +{% endif %} + +{{other_required_steps}} + +{% if setup %} +Setup +------------------------------------------------------------------------------- + +{% for section in setup %} + +{% include section + '.tmpl.rst' %} + +{% endfor %} +{% endif %} + +{% if samples %} +Samples +------------------------------------------------------------------------------- + +{% for sample in samples %} +{{sample.name}} ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +{% if not sample.hide_cloudshell_button %} +.. image:: https://gstatic.com/cloudssh/images/open-btn.png + :target: https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/GoogleCloudPlatform/python-docs-samples&page=editor&open_in_editor={{folder}}/{{sample.file}},{{folder}}/README.rst +{% endif %} + + +{{sample.description}} + +To run this sample: + +.. code-block:: bash + + $ python {{sample.file}} +{% if sample.show_help %} + + {{get_help(sample.file)|indent}} +{% endif %} + + +{% endfor %} +{% endif %} + +{% if cloud_client_library %} + +The client library +------------------------------------------------------------------------------- + +This sample uses the `Google Cloud Client Library for Python`_. +You can read the documentation for more details on API usage and use GitHub +to `browse the source`_ and `report issues`_. + +.. _Google Cloud Client Library for Python: + https://googlecloudplatform.github.io/google-cloud-python/ +.. _browse the source: + https://github.com/GoogleCloudPlatform/google-cloud-python +.. _report issues: + https://github.com/GoogleCloudPlatform/google-cloud-python/issues + +{% endif %} + +.. _Google Cloud SDK: https://cloud.google.com/sdk/ \ No newline at end of file diff --git a/scripts/readme-gen/templates/auth.tmpl.rst b/scripts/readme-gen/templates/auth.tmpl.rst new file mode 100644 index 00000000000..1446b94a5e3 --- /dev/null +++ b/scripts/readme-gen/templates/auth.tmpl.rst @@ -0,0 +1,9 @@ +Authentication +++++++++++++++ + +This sample requires you to have authentication setup. Refer to the +`Authentication Getting Started Guide`_ for instructions on setting up +credentials for applications. + +.. _Authentication Getting Started Guide: + https://cloud.google.com/docs/authentication/getting-started diff --git a/scripts/readme-gen/templates/auth_api_key.tmpl.rst b/scripts/readme-gen/templates/auth_api_key.tmpl.rst new file mode 100644 index 00000000000..11957ce2714 --- /dev/null +++ b/scripts/readme-gen/templates/auth_api_key.tmpl.rst @@ -0,0 +1,14 @@ +Authentication +++++++++++++++ + +Authentication for this service is done via an `API Key`_. To obtain an API +Key: + +1. Open the `Cloud Platform Console`_ +2. Make sure that billing is enabled for your project. +3. From the **Credentials** page, create a new **API Key** or use an existing + one for your project. + +.. _API Key: + https://developers.google.com/api-client-library/python/guide/aaa_apikeys +.. _Cloud Console: https://console.cloud.google.com/project?_ diff --git a/scripts/readme-gen/templates/install_deps.tmpl.rst b/scripts/readme-gen/templates/install_deps.tmpl.rst new file mode 100644 index 00000000000..6f069c6c87a --- /dev/null +++ b/scripts/readme-gen/templates/install_deps.tmpl.rst @@ -0,0 +1,29 @@ +Install Dependencies +++++++++++++++++++++ + +#. Clone python-docs-samples and change directory to the sample directory you want to use. + + .. code-block:: bash + + $ git clone https://github.com/GoogleCloudPlatform/python-docs-samples.git + +#. Install `pip`_ and `virtualenv`_ if you do not already have them. You may want to refer to the `Python Development Environment Setup Guide`_ for Google Cloud Platform for instructions. + + .. _Python Development Environment Setup Guide: + https://cloud.google.com/python/setup + +#. Create a virtualenv. Samples are compatible with Python 3.7+. + + .. code-block:: bash + + $ virtualenv env + $ source env/bin/activate + +#. Install the dependencies needed to run the samples. + + .. code-block:: bash + + $ pip install -r requirements.txt + +.. _pip: https://pip.pypa.io/ +.. _virtualenv: https://virtualenv.pypa.io/ diff --git a/scripts/readme-gen/templates/install_portaudio.tmpl.rst b/scripts/readme-gen/templates/install_portaudio.tmpl.rst new file mode 100644 index 00000000000..5ea33d18c00 --- /dev/null +++ b/scripts/readme-gen/templates/install_portaudio.tmpl.rst @@ -0,0 +1,35 @@ +Install PortAudio ++++++++++++++++++ + +Install `PortAudio`_. This is required by the `PyAudio`_ library to stream +audio from your computer's microphone. PyAudio depends on PortAudio for cross-platform compatibility, and is installed differently depending on the +platform. + +* For Mac OS X, you can use `Homebrew`_:: + + brew install portaudio + + **Note**: if you encounter an error when running `pip install` that indicates + it can't find `portaudio.h`, try running `pip install` with the following + flags:: + + pip install --global-option='build_ext' \ + --global-option='-I/usr/local/include' \ + --global-option='-L/usr/local/lib' \ + pyaudio + +* For Debian / Ubuntu Linux:: + + apt-get install portaudio19-dev python-all-dev + +* Windows may work without having to install PortAudio explicitly (it will get + installed with PyAudio). + +For more details, see the `PyAudio installation`_ page. + + +.. _PyAudio: https://people.csail.mit.edu/hubert/pyaudio/ +.. _PortAudio: http://www.portaudio.com/ +.. _PyAudio installation: + https://people.csail.mit.edu/hubert/pyaudio/#downloads +.. _Homebrew: http://brew.sh diff --git a/scripts/run_and_publish_benchmark.py b/scripts/run_and_publish_benchmark.py deleted file mode 100644 index 859d68e60ed..00000000000 --- a/scripts/run_and_publish_benchmark.py +++ /dev/null @@ -1,481 +0,0 @@ -# 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. -# 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 argparse -import datetime -import json -import os -import pathlib -import re -import subprocess -import sys -import tempfile -from typing import Dict, List, Tuple, Union - -import numpy as np -import pandas as pd -import pandas_gbq - -LOGGING_NAME_ENV_VAR = "BIGFRAMES_PERFORMANCE_LOG_NAME" -CURRENT_DIRECTORY = pathlib.Path(__file__).parent.absolute() - - -def run_benchmark_subprocess(args, log_env_name_var, file_path=None, region=None): - """ - Runs a benchmark subprocess with configured environment variables. Adjusts PYTHONPATH, - sets region-specific BigQuery location, and logs environment variables. - - This function terminates the benchmark session if the subprocess exits with an error, - due to `check=True` in subprocess.run, which raises CalledProcessError on non-zero - exit status. - """ - env = os.environ.copy() - current_pythonpath = env.get("PYTHONPATH", "") - env["PYTHONPATH"] = ( - os.path.join(os.getcwd(), "tests") + os.pathsep + current_pythonpath - ) - - if region: - env["BIGQUERY_LOCATION"] = region - env[LOGGING_NAME_ENV_VAR] = log_env_name_var - try: - if file_path: # Notebooks - duration_pattern = re.compile(r"(\d+\.\d+)s call") - process = subprocess.Popen(args, env=env, stdout=subprocess.PIPE, text=True) - assert process.stdout is not None - for line in process.stdout: - print(line, end="") - match = duration_pattern.search(line) - if match: - duration = match.group(1) - with open(f"{file_path}.local_exec_time_seconds", "w") as f: - f.write(f"{duration}\n") - process.wait() - if process.returncode != 0: - raise subprocess.CalledProcessError(process.returncode, args) - else: # Benchmarks - file_path = log_env_name_var - subprocess.run(args, env=env, check=True) - except Exception: - directory = pathlib.Path(file_path).parent - for file in directory.glob(f"{pathlib.Path(file_path).name}.*"): - if file.suffix != ".backup": - print(f"Benchmark failed, deleting: {file}") - file.unlink() - error_file = directory / f"{pathlib.Path(file_path).name}.error" - error_file.touch() - - -def collect_benchmark_result( - benchmark_path: str, iterations: int -) -> Tuple[pd.DataFrame, Union[str, None]]: - """Generate a DataFrame report on HTTP queries, bytes processed, slot time and execution time from log files.""" - path = pathlib.Path(benchmark_path) - try: - results_dict: Dict[str, List[Union[int, float, None]]] = {} - # Use local_seconds_files as the baseline - local_seconds_files = sorted(path.rglob("*.local_exec_time_seconds")) - error_files = sorted(path.rglob("*.error")) - benchmarks_with_missing_files = [] - - for local_seconds_file in local_seconds_files: - base_name = local_seconds_file.name.removesuffix(".local_exec_time_seconds") - base_path = local_seconds_file.parent / base_name - filename = base_path.relative_to(path) - - # Construct paths for other metric files - bytes_file = pathlib.Path(f"{base_path}.bytesprocessed") - millis_file = pathlib.Path(f"{base_path}.slotmillis") - bq_seconds_file = pathlib.Path(f"{base_path}.bq_exec_time_seconds") - query_char_count_file = pathlib.Path(f"{base_path}.query_char_count") - - # Check if all corresponding files exist - missing_files = [] - if not bytes_file.exists(): - missing_files.append(bytes_file.name) - if not millis_file.exists(): - missing_files.append(millis_file.name) - if not bq_seconds_file.exists(): - missing_files.append(bq_seconds_file.name) - if not query_char_count_file.exists(): - missing_files.append(query_char_count_file.name) - - if missing_files: - benchmarks_with_missing_files.append((str(filename), missing_files)) - continue - - with open(query_char_count_file, "r") as file: - lines = file.read().splitlines() - query_char_count = sum(int(line) for line in lines) / iterations - query_count = len(lines) / iterations - - with open(local_seconds_file, "r") as file: - lines = file.read().splitlines() - local_seconds = sum(float(line) for line in lines) / iterations - - with open(bytes_file, "r") as file: - lines = file.read().splitlines() - total_bytes = sum(int(line) for line in lines) / iterations - - with open(millis_file, "r") as file: - lines = file.read().splitlines() - total_slot_millis = sum(int(line) for line in lines) / iterations - - with open(bq_seconds_file, "r") as file: - lines = file.read().splitlines() - bq_seconds = sum(float(line) for line in lines) / iterations - - results_dict[str(filename)] = [ - query_count, - total_bytes, - total_slot_millis, - local_seconds, - bq_seconds, - query_char_count, - ] - finally: - for files_to_remove in ( - path.rglob("*.bytesprocessed"), - path.rglob("*.slotmillis"), - path.rglob("*.local_exec_time_seconds"), - path.rglob("*.bq_exec_time_seconds"), - path.rglob("*.query_char_count"), - path.rglob("*.error"), - ): - for log_file in files_to_remove: - log_file.unlink() - - columns = [ - "Query_Count", - "Bytes_Processed", - "Slot_Millis", - "Local_Execution_Time_Sec", - "BigQuery_Execution_Time_Sec", - "Query_Char_Count", - ] - - benchmark_metrics = pd.DataFrame.from_dict( - results_dict, - orient="index", - columns=columns, - ) - - report_title = ( - "---BIGQUERY USAGE REPORT---" - if iterations == 1 - else f"---BIGQUERY USAGE REPORT (Averages over {iterations} Iterations)---" - ) - print(report_title) - for index, row in benchmark_metrics.iterrows(): - formatted_local_exec_time = ( - f"{round(row['Local_Execution_Time_Sec'], 1)} seconds" - if not pd.isna(row["Local_Execution_Time_Sec"]) - else "N/A" - ) - print( - f"{index} - query count: {row['Query_Count']}," - + f" query char count: {row['Query_Char_Count']}," - + f" bytes processed sum: {row['Bytes_Processed']}," - + f" slot millis sum: {row['Slot_Millis']}," - + f" local execution time: {formatted_local_exec_time}" - + f", bigquery execution time: {round(row['BigQuery_Execution_Time_Sec'], 1)} seconds" - ) - - geometric_mean_queries = geometric_mean_excluding_zeros( - benchmark_metrics["Query_Count"] - ) - geometric_mean_query_char_count = geometric_mean_excluding_zeros( - benchmark_metrics["Query_Char_Count"] - ) - geometric_mean_bytes = geometric_mean_excluding_zeros( - benchmark_metrics["Bytes_Processed"] - ) - geometric_mean_slot_millis = geometric_mean_excluding_zeros( - benchmark_metrics["Slot_Millis"] - ) - geometric_mean_local_seconds = geometric_mean_excluding_zeros( - benchmark_metrics["Local_Execution_Time_Sec"] - ) - geometric_mean_bq_seconds = geometric_mean_excluding_zeros( - benchmark_metrics["BigQuery_Execution_Time_Sec"] - ) - - print( - f"---Geometric mean of queries: {geometric_mean_queries}," - + f" Geometric mean of queries char counts: {geometric_mean_query_char_count}," - + f" Geometric mean of bytes processed: {geometric_mean_bytes}," - + f" Geometric mean of slot millis: {geometric_mean_slot_millis}," - + f" Geometric mean of local execution time: {geometric_mean_local_seconds} seconds" - + f", Geometric mean of BigQuery execution time: {geometric_mean_bq_seconds} seconds---" - ) - - all_errors: List[str] = [] - if error_files: - all_errors.extend( - f"Failed: {error_file.relative_to(path).with_suffix('')}" - for error_file in error_files - ) - if ( - benchmarks_with_missing_files - and os.getenv("BENCHMARK_AND_PUBLISH", "false") == "true" - ): - all_errors.extend( - f"Missing files for benchmark '{name}': {files}" - for name, files in benchmarks_with_missing_files - ) - error_message = "\n" + "\n".join(all_errors) if all_errors else None - return ( - benchmark_metrics.reset_index().rename(columns={"index": "Benchmark_Name"}), - error_message, - ) - - -def geometric_mean_excluding_zeros(data): - """ - Calculate the geometric mean of a dataset, excluding any zero values. - Returns NaN if the dataset is empty, contains only NaN values, or if - all non-NaN values are zeros. - - The result is rounded to one decimal place. - """ - data = data.dropna() - data = data[data != 0] - if len(data) == 0: - return np.nan - log_data = np.log(data) - return round(np.exp(log_data.mean()), 1) - - -def get_repository_status(): - current_directory = os.getcwd() - subprocess.run( - ["git", "config", "--global", "--add", "safe.directory", current_directory], - check=True, - ) - - git_hash = subprocess.check_output( - ["git", "rev-parse", "--short", "HEAD"], text=True - ).strip() - bigframes_version = subprocess.check_output( - ["python", "-c", "import bigframes; print(bigframes.__version__)"], text=True - ).strip() - release_version = ( - f"{bigframes_version}dev{datetime.datetime.now().strftime('%Y%m%d')}+{git_hash}" - ) - - return { - "benchmark_start_time": datetime.datetime.now().isoformat(), - "git_hash": git_hash, - "bigframes_version": bigframes_version, - "release_version": release_version, - "python_version": sys.version, - } - - -def find_config(start_path): - """ - Searches for a 'config.jsonl' file starting from the given path and moving up to parent - directories. - - This function ascends from the initial directory specified by `start_path` up to 3 - levels or until it reaches a directory named 'benchmark'. The search moves upwards - because if there are multiple 'config.jsonl' files in the path hierarchy, the closest - configuration to the starting directory (the lowest level) is expected to take effect. - It checks each directory for the presence of 'config.jsonl'. If found, it returns the - path to the configuration file. If not found within the limit or upon reaching - the 'benchmark' directory, it returns None. - """ - target_file = "config.jsonl" - current_path = pathlib.Path(start_path).resolve() - if current_path.is_file(): - current_path = current_path.parent - - levels_checked = 0 - while current_path.name != "benchmark" and levels_checked < 3: - config_path = current_path / target_file - if config_path.exists(): - return config_path - if current_path.parent == current_path: - break - current_path = current_path.parent - levels_checked += 1 - - return None - - -def publish_to_bigquery(dataframe, notebook, project_name="bigframes-metrics"): - bigquery_table = ( - f"{project_name}.benchmark_report.notebook_benchmark" - if notebook - else f"{project_name}.benchmark_report.benchmark" - ) - - repo_status = get_repository_status() - for idx, col in enumerate(repo_status.keys()): - dataframe.insert(idx, col, repo_status[col]) - - pandas_gbq.to_gbq( - dataframe=dataframe, - destination_table=bigquery_table, - if_exists="append", - ) - print(f"Results have been successfully uploaded to {bigquery_table}.") - - -def run_benchmark_from_config(benchmark: str, iterations: int): - print(benchmark) - config_path = find_config(benchmark) - - if config_path: - benchmark_configs = [] - with open(config_path, "r") as f: - for line in f: - if line.strip(): - config = json.loads(line) - python_args = [f"--{key}={value}" for key, value in config.items()] - suffix = ( - config["benchmark_suffix"] - if "benchmark_suffix" in config - else "_".join(f"{key}_{value}" for key, value in config.items()) - ) - benchmark_configs.append((suffix, python_args)) - else: - benchmark_configs = [(None, [])] - - for _ in range(iterations): - for benchmark_config in benchmark_configs: - args = ["python", str(benchmark)] - args.extend(benchmark_config[1]) - log_env_name_var = str(benchmark) - if benchmark_config[0] is not None: - log_env_name_var += f"_{benchmark_config[0]}" - run_benchmark_subprocess(args=args, log_env_name_var=log_env_name_var) - - -def run_notebook_benchmark(benchmark_file: str, region: str): - export_file = f"{benchmark_file}_{region}" if region else benchmark_file - log_env_name_var = os.path.basename(export_file) - # TODO(shobs): For some reason --retries arg masks exceptions occurred in - # notebook failures, and shows unhelpful INTERNALERROR. Investigate that - # and enable retries if we can find a way to surface the real exception - # bacause the notebook is running against real GCP and something may fail - # due to transient issues. - pytest_command = [ - "py.test", - "--nbmake", - "--nbmake-timeout=900", # 15 minutes - "--durations=0", - "--color=yes", - ] - benchmark_args = (*pytest_command, benchmark_file) - - run_benchmark_subprocess( - args=benchmark_args, - log_env_name_var=log_env_name_var, - file_path=export_file, - region=region, - ) - - -def parse_arguments(): - parser = argparse.ArgumentParser( - description="Run benchmarks for different scenarios." - ) - parser.add_argument( - "--notebook", - action="store_true", - help="Set this flag to run the benchmark as a notebook. If not set, it assumes a Python (.py) file.", - ) - - parser.add_argument( - "--benchmark-path", - type=str, - default=None, - help="Specify the file path to the benchmark script, either a Jupyter notebook or a Python script.", - ) - - parser.add_argument( - "--region", - type=str, - default=None, - help="Specify the region where the benchmark will be executed or where the data resides. This parameter is optional.", - ) - - parser.add_argument( - "--publish-benchmarks", - type=str, - default=None, - help="Set the benchmarks to be published to BigQuery.", - ) - - parser.add_argument( - "--iterations", - type=int, - default=1, - help="Number of iterations to run each benchmark.", - ) - parser.add_argument( - "--output-csv", - type=str, - default=None, - help="Determines whether to output results to a CSV file. If no location is provided, a temporary location is automatically generated.", - ) - - return parser.parse_args() - - -def main(): - args = parse_arguments() - - if args.publish_benchmarks: - benchmark_metrics, error_message = collect_benchmark_result( - args.publish_benchmarks, args.iterations - ) - # Output results to CSV without specifying a location - if args.output_csv == "True": - current_time = datetime.datetime.now().strftime("%Y%m%d-%H%M%S") - temp_file = tempfile.NamedTemporaryFile( - prefix=f"benchmark_{current_time}_", delete=False, suffix=".csv" - ) - benchmark_metrics.to_csv(temp_file.name, index=False) - print( - f"Benchmark result is saved to a temporary location: {temp_file.name}" - ) - temp_file.close() - # Output results to CSV with specified a custom location - elif args.output_csv != "False": - benchmark_metrics.to_csv(args.output_csv, index=False) - print(f"Benchmark result is saved to: {args.output_csv}") - - # Publish the benchmark metrics to BigQuery under the 'bigframes-metrics' project. - # The 'BENCHMARK_AND_PUBLISH' environment variable should be set to 'true' only - # in specific Kokoro sessions. - if os.getenv("BENCHMARK_AND_PUBLISH", "false") == "true": - publish_to_bigquery(benchmark_metrics, args.notebook) - # If the 'GCLOUD_BENCH_PUBLISH_PROJECT' environment variable is set, publish the - # benchmark metrics to a specified BigQuery table in the provided project. This is - # intended for local testing where the default behavior is not to publish results. - elif project := os.getenv("GCLOUD_BENCH_PUBLISH_PROJECT", ""): - publish_to_bigquery(benchmark_metrics, args.notebook, project) - - if error_message: - raise Exception(error_message) - elif args.notebook: - run_notebook_benchmark(args.benchmark_path, args.region) - else: - run_benchmark_from_config(args.benchmark_path, args.iterations) - - -if __name__ == "__main__": - main() diff --git a/scripts/run_doctest.sh b/scripts/run_doctest.sh deleted file mode 100755 index d5fd7256ece..00000000000 --- a/scripts/run_doctest.sh +++ /dev/null @@ -1,49 +0,0 @@ -#!/bin/bash -set -eo pipefail - -# Disable buffering, so that the logs stream through. -export PYTHONUNBUFFERED=1 - -# Assume we are running from the repo root or we need to find it. -# If this script is in packages/bigframes/scripts/run_doctest.sh, -# then repo root is 3 levels up. -export PROJECT_ROOT=$(realpath "$(dirname "${BASH_SOURCE[0]}")/../../..") -cd "$PROJECT_ROOT" - -git config --global --add safe.directory "$(realpath .)" - -package_name="bigframes" -package_path="packages/${package_name}" -files_to_check="${package_path}" - -# Use the IF block to handle the case where KOKORO vars are missing -# (e.g. local testing) -if [[ -n "${KOKORO_GITHUB_PULL_REQUEST_TARGET_BRANCH}" && -n "${KOKORO_GITHUB_PULL_REQUEST_COMMIT}" ]]; then - echo "checking changes with 'git diff ${KOKORO_GITHUB_PULL_REQUEST_TARGET_BRANCH}...${KOKORO_GITHUB_PULL_REQUEST_COMMIT} -- ${files_to_check}'" - - package_modified=$(git diff "${KOKORO_GITHUB_PULL_REQUEST_TARGET_BRANCH}...${KOKORO_GITHUB_PULL_REQUEST_COMMIT}" -- "${files_to_check}" | wc -l) -else - # If not a PR (like a local run or a different CI trigger), - # we treat it as 0 so it falls through to the "continuous" check. - package_modified=0 -fi - -# Check if modified OR if it's a continuous build -if [[ "${package_modified}" -gt 0 || "$KOKORO_BUILD_ARTIFACTS_SUBDIR" == *"continuous"* ]]; then - echo "------------------------------------------------------------" - echo "Running doctest for: ${package_name}" - echo "------------------------------------------------------------" - - # Ensure credentials are set for system tests in Kokoro - if [[ -z "${GOOGLE_APPLICATION_CREDENTIALS}" && -f "${KOKORO_GFILE_DIR}/service-account.json" ]]; then - export GOOGLE_APPLICATION_CREDENTIALS="${KOKORO_GFILE_DIR}/service-account.json" - fi - - export GOOGLE_CLOUD_PROJECT="bigframes-testing" - NOX_SESSION=("cleanup" "doctest") - - cd "${package_path}" - python3 -m nox -s "${NOX_SESSION[@]}" -else - echo "No changes in ${package_name} and not a continuous build, skipping." -fi \ No newline at end of file diff --git a/scripts/setup-project-for-testing.sh b/scripts/setup-project-for-testing.sh deleted file mode 100755 index df9cea46a4b..00000000000 --- a/scripts/setup-project-for-testing.sh +++ /dev/null @@ -1,259 +0,0 @@ -#!/bin/bash - -# Copyright 2023 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. - - -if [ $# -lt 1 ]; then - echo "USAGE: `basename $0` []" - echo "EXAMPLES:" - echo " `basename $0` my-project" - echo " `basename $0` my-project user:user_id@example.com" - echo " `basename $0` my-project group:group_id@example.com" - echo " `basename $0` my-project serviceAccount:service_account_id@example.com" - exit 1 -fi - -PROJECT_ID=$1 -PRINCIPAL=$2 -BIGFRAMES_DEFAULT_CONNECTION_NAME=bigframes-default-connection -BIGFRAMES_RF_CONNECTION_NAME=bigframes-rf-conn - -if [ "$PRINCIPAL" != "" ]; then - echo $PRINCIPAL | grep -E "(user|group|serviceAccount):" >/dev/null - if [ $? -ne 0 ]; then - echo "principal must have prefix 'user:', 'group:' or 'serviceAccount:'" - exit 1 - fi -fi - -if ! test `which gcloud`; then - echo "gcloud CLI is not installed. Install it from https://cloud.google.com/sdk/docs/install." >&2 - exit 1 -fi - -################################################################################ -# Log and execute a command -################################################################################ -function log_and_execute() { - echo Running command: $* - $* -} - - -################################################################################ -# Enable APIs -################################################################################ -function enable_apis() { - for service in aiplatform.googleapis.com \ - artifactregistry.googleapis.com \ - bigquery.googleapis.com \ - bigqueryconnection.googleapis.com \ - bigquerystorage.googleapis.com \ - cloudbuild.googleapis.com \ - cloudfunctions.googleapis.com \ - cloudresourcemanager.googleapis.com \ - compute.googleapis.com \ - run.googleapis.com \ - ; do - log_and_execute gcloud --project=$PROJECT_ID services enable $service - if [ $? -ne 0 ]; then - echo "Failed to enable service $service, exiting..." - exit 1 - fi - done -} - - -################################################################################ -# Ensure a BQ connection exists with desired IAM rols -################################################################################ -function ensure_bq_connection_with_iam() { - if [ $# -ne 2 ]; then - echo "USAGE: `basename $0` " - echo "EXAMPLES:" - echo " `basename $0` my-project my-connection" - exit 1 - fi - - location=$1 - connection_name=$2 - - log_and_execute bq show \ - --connection \ - --project_id=$PROJECT_ID \ - --location=$location \ - $connection_name 2>&1 >/dev/null - if [ $? -ne 0 ]; then - echo "Connection $connection_name doesn't exists in location \"$location\", creating..." - log_and_execute bq mk \ - --connection \ - --project_id=$PROJECT_ID \ - --location=$location \ - --connection_type=CLOUD_RESOURCE \ - $connection_name - if [ $? -ne 0 ]; then - echo "Failed creating connection, exiting." - exit 1 - fi - else - echo "Connection $connection_name already exists in location $location." - fi - - compact_json_info_cmd="bq show --connection \ - --project_id=$PROJECT_ID \ - --location=$location \ - --format=json \ - $connection_name" - compact_json_info_cmd_output=`$compact_json_info_cmd` - if [ $? -ne 0 ]; then - echo "Failed to fetch connection info: $compact_json_info_cmd_output" - exit 1 - fi - - connection_service_account=`echo $compact_json_info_cmd_output | sed -e 's/.*"cloudResource":{"serviceAccountId":"//' -e 's/".*//'` - - # Configure roles for the service accounts associated with the connection - for role in run.invoker aiplatform.user; do - log_and_execute gcloud projects add-iam-policy-binding $PROJECT_ID \ - --member=serviceAccount:$connection_service_account \ - --role=roles/$role - if [ $? -ne 0 ]; then - echo "Failed to set IAM, exiting..." - exit 1 - fi - done -} - - -################################################################################ -# Create the default BQ connection in US location -################################################################################ -function ensure_bq_connections_with_iam() { - ensure_bq_connection_with_iam "us" "$BIGFRAMES_DEFAULT_CONNECTION_NAME" - - # Create commonly used BQ connection in various locations - for location in asia-southeast1 \ - eu \ - europe-west4 \ - southamerica-west1 \ - us \ - us-central1 \ - us-east5 \ - ; do - ensure_bq_connection_with_iam "$location" "$BIGFRAMES_RF_CONNECTION_NAME" - done -} - - -################################################################################ -# Set up IAM roles for principal -################################################################################ -function setup_iam_roles () { - if [ "$PRINCIPAL" != "" ]; then - for role in aiplatform.user \ - bigquery.user \ - bigquery.connectionAdmin \ - bigquery.dataEditor \ - browser \ - cloudfunctions.developer \ - iam.serviceAccountUser \ - ; do - log_and_execute gcloud projects add-iam-policy-binding $PROJECT_ID \ - --member=$PRINCIPAL \ - --role=roles/$role - if [ $? -ne 0 ]; then - echo "Failed to set IAM, exiting..." - exit 1 - fi - done - fi -} - - -################################################################################ -# Create vertex endpoint for test ML model -################################################################################ -function create_bq_model_vertex_endpoint () { - vertex_region=us-central1 - model_name=bigframes-test-linreg2 - endpoint_name=$model_name-endpoint - - # Create vertex model - log_and_execute python scripts/create_test_model_vertex.py \ - -m $model_name \ - -p $PROJECT_ID - if [ $? -ne 0 ]; then - echo "Failed to create model, exiting..." - exit 1 - fi - - # Create vertex endpoint - log_and_execute gcloud ai endpoints create \ - --project=$PROJECT_ID \ - --region=$vertex_region \ - --display-name=$endpoint_name - if [ $? -ne 0 ]; then - echo "Failed to create vertex endpoint, exiting..." - exit 1 - fi - - # Fetch endpoint id - endpoint_id=`gcloud ai endpoints list \ - --project=$PROJECT_ID \ - --region=$vertex_region \ - --filter=display_name=$endpoint_name 2>/dev/null \ - | tail -n1 | cut -d' ' -f 1` - if [ "$endpoint_id" = "" ]; then - echo "Failed to fetch vertex endpoint id, exiting..." - exit 1 - fi - - # Deploy the model to the vertex endpoint - log_and_execute gcloud ai endpoints deploy-model $endpoint_id \ - --project=$PROJECT_ID \ - --region=$vertex_region \ - --model=$model_name \ - --display-name=$model_name - if [ $? -ne 0 ]; then - echo "Failed to deploy model to vertex endpoint, exiting..." - exit 1 - fi - - # Form the endpoint - endpoint_rel_path=`gcloud ai endpoints describe \ - --project=$PROJECT_ID \ - --region=us-central1 \ - $endpoint_id 2>/dev/null \ - | grep "^name:" | cut -d' ' -f2` - if [ "$endpoint_rel_path" = "" ]; then - echo "Failed to fetch vertex endpoint relativr path, exiting..." - exit 1 - fi - endpoint_path=https://$vertex_region-aiplatform.googleapis.com/v1/$endpoint_rel_path - - # Print the endpoint configuration to be used in tests - echo - echo Run following command to set test model vertex endpoint: - echo export BIGFRAMES_TEST_MODEL_VERTEX_ENDPOINT=$endpoint_path -} - - -################################################################################ -# Set the things up -################################################################################ -enable_apis -ensure_bq_connections_with_iam -setup_iam_roles -create_bq_model_vertex_endpoint diff --git a/scripts/templates/bigframes_series_accessor.py.j2 b/scripts/templates/bigframes_series_accessor.py.j2 deleted file mode 100644 index 8ce37d67321..00000000000 --- a/scripts/templates/bigframes_series_accessor.py.j2 +++ /dev/null @@ -1,44 +0,0 @@ -{% include 'license.py.j2' %} - -# -# DO NOT MODIFY THIS FILE DIRECTLY. -# This file was generated by the script: {{ script_path }} -# - -from __future__ import annotations - -from typing import cast, Optional, TypeVar - -from bigframes.core.logging import log_adapter -from bigframes.extensions.core import series_accessor as core_accessor -from bigframes import series, dataframe, session - -T = TypeVar("T", bound="dataframe.DataFrame") -S = TypeVar("S", bound="series.Series") - - -{% for ns in namespaces %} -@log_adapter.class_logger -class {{ ns.bigframes_class_name }}(core_accessor.{{ ns.class_name }}[T, S]): - def __init__(self, bf_obj: S): - super().__init__(bf_obj) - - def _bf_from_series( - self, session: Optional[session.Session] = None - ) -> series.Series: - return self._obj - - def _to_dataframe(self, bf_df: dataframe.DataFrame) -> T: - return cast(T, bf_df) - - def _to_series(self, bf_series: series.Series) -> S: - return cast(S, bf_series) - - {% for child in ns.children %} - @property - def {{ child.prop_name }}(self) -> {{ child.bigframes_class_name }}[T, S]: - return {{ child.bigframes_class_name }}(self._obj) - - {% endfor %} - -{% endfor %} diff --git a/scripts/templates/core_series_accessor.py.j2 b/scripts/templates/core_series_accessor.py.j2 deleted file mode 100644 index 89decdcbe1e..00000000000 --- a/scripts/templates/core_series_accessor.py.j2 +++ /dev/null @@ -1,81 +0,0 @@ -{% include 'license.py.j2' %} - -# -# DO NOT MODIFY THIS FILE DIRECTLY. -# This file was generated by the script: {{ script_path }} -# - -from __future__ import annotations - -import abc -import datetime -from typing import ( - Any, - Literal, - Optional, - TypeVar, - Union, - cast, -) - -from bigframes import series, session -from bigframes.core import col, sentinels -from bigframes.extensions.core import abstract_series_accessor, series_mixins - -T = TypeVar("T") -S = TypeVar("S") - - -{% for ns in namespaces %} -{% if ns.class_name == "AiSeriesAccessor" %} -class {{ ns.class_name }}(series_mixins.AIMixin[T, S]): -{% else %} -class {{ ns.class_name }}(abstract_series_accessor.AbstractBigQuerySeriesAccessor[T, S]): -{% endif %} - """{{ ns.description }}""" - - {% for child in ns.children %} - @property - @abc.abstractmethod - def {{ child.prop_name }}(self) -> {{ child.class_name }}[T, S]: - """Accessor for BigQuery {{ child.prop_name }} functions.""" - - {% endfor %} - {% for func in ns.functions %} - def {{ func.name }}( - self, - {% for arg in func.args if arg.name != func.series_accessor_arg %} - {{ arg.name }}: Union[series.Series, col.Expression, {{ arg.type_hint }}]{% if arg.default %} = {{ arg.default }}{% endif %}, - {% endfor %} - *, - session: Optional[session.Session] = None, - ) -> S: - """{{ func.description | indent(8) }}""" - from {{ func.import_module }} import {{ func.name }} as {{ func.name }}_impl - {% if func.args | length > 1 %} - - # Resolve session from other arguments if not passed - if session is None: - from bigframes.core import googlesql - session = googlesql._find_session( - {% for arg in func.args if arg.name != func.series_accessor_arg %} - {{ arg.name }}, - {% endfor %} - ) - {% endif %} - - bf_series = self._bf_from_series(session) - result = {{ func.name }}_impl( - {% for arg in func.args %} - {% if arg.name == func.series_accessor_arg %} - bf_series, - {% else %} - {{ arg.name }}, - {% endif %} - {% endfor %} - ) - return self._to_series(cast(series.Series, result)) - - {% endfor %} - -{% endfor %} diff --git a/scripts/templates/license.py.j2 b/scripts/templates/license.py.j2 deleted file mode 100644 index 58d482ea386..00000000000 --- a/scripts/templates/license.py.j2 +++ /dev/null @@ -1,13 +0,0 @@ -# Copyright 2026 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/scripts/templates/operation.py.j2 b/scripts/templates/operation.py.j2 deleted file mode 100644 index 720d867986e..00000000000 --- a/scripts/templates/operation.py.j2 +++ /dev/null @@ -1,50 +0,0 @@ -{% include 'license.py.j2' %} - -# -# DO NOT MODIFY THIS FILE DIRECTLY. -# This file was generated from: {{ yaml_path }} -# by the script: {{ script_path }} - -from __future__ import annotations - -import datetime -import decimal -from typing import Any, Literal, Optional, TypeVar, Union - -from bigframes import dtypes -import bigframes.core.col -import bigframes.core.expression as ex -import bigframes.core.googlesql -import bigframes.core.sentinels as sentinels -from bigframes.operations import googlesql -import bigframes.operations as ops -import bigframes.series as series - -{% for op in ops %} -{% if op.signature_definition %} -{{ op.signature_definition }} - - -{% endif %} -{{ op.internal_name }} = googlesql.GoogleSqlScalarOp( - "{{ op.sql_name }}", - args=({{ op.arg_specs }}), - signature={{ op.signature }}, -) -{% endfor %} -{% for func in functions %} - - -def {{ func.name }}( -{% for arg in func.args %} - {{ arg.name }}: Union[series.Series, bigframes.core.col.Expression, {{ arg.type_hint }}]{% if arg.default %} = {{ arg.default }}{% endif %}, -{% endfor %} -) -> Union[series.Series, bigframes.core.col.Expression]: - """{{ func.description | indent(4) }}""" - return bigframes.core.googlesql.apply_googlesql_scalar_op( - {{ func.op_name }}, -{% for arg in func.args %} - {{ arg.name }}, -{% endfor %} - ) -{% endfor %} diff --git a/scripts/templates/pandas_series_accessor.py.j2 b/scripts/templates/pandas_series_accessor.py.j2 deleted file mode 100644 index 15054665561..00000000000 --- a/scripts/templates/pandas_series_accessor.py.j2 +++ /dev/null @@ -1,53 +0,0 @@ -{% include 'license.py.j2' %} - -# -# DO NOT MODIFY THIS FILE DIRECTLY. -# This file was generated by the script: {{ script_path }} -# - -from __future__ import annotations - -from typing import cast, Optional, TypeVar - -import pandas -import pandas.api.extensions - -from bigframes import dataframe, series, session -from bigframes.core import global_session as bf_session -from bigframes.core.logging import log_adapter -from bigframes.extensions.core import series_accessor as core_accessor - -T = TypeVar("T", bound="pandas.DataFrame") -S = TypeVar("S", bound="pandas.Series") - - -{% for ns in namespaces %} -{% if ns.is_root %} -@pandas.api.extensions.register_series_accessor("bigquery") -{% endif %} -@log_adapter.class_logger -class {{ ns.pandas_class_name }}(core_accessor.{{ ns.class_name }}[T, S]): - def __init__(self, pandas_obj: S): - super().__init__(pandas_obj) - - def _bf_from_series( - self, session: Optional[session.Session] = None - ) -> series.Series: - if session is None: - session = bf_session.get_global_session() - return cast(series.Series, session.read_pandas(self._obj)) - - def _to_dataframe(self, bf_df: dataframe.DataFrame) -> T: - return cast(T, bf_df.to_pandas(ordered=True)) - - def _to_series(self, bf_series: series.Series) -> S: - return cast(S, bf_series.to_pandas(ordered=True)) - - {% for child in ns.children %} - @property - def {{ child.prop_name }}(self) -> {{ child.pandas_class_name }}[T, S]: - return {{ child.pandas_class_name }}(self._obj) - - {% endfor %} - -{% endfor %} diff --git a/scripts/templates/signature_def.py.j2 b/scripts/templates/signature_def.py.j2 deleted file mode 100644 index b00c95e3383..00000000000 --- a/scripts/templates/signature_def.py.j2 +++ /dev/null @@ -1,76 +0,0 @@ -def {{ func_name }}(*args): - # Pad args with None to match max expected args - args = args + (None,) * ({{ max_args }} - len(args)) - {% for impl in impls %} - # Try matching impl {{ loop.index0 }} - {% if impl.requires_generic_types %} - any1_val = None - {% endif %} - match_ok = True - {% for arg in impl.args %} - {% set idx = loop.index0 %} - if match_ok and args[{{ idx }}] is not None: - {% if arg.value == "any1" %} - if any1_val is not None: - try: - any1_val = dtypes.coerce_to_common(any1_val, args[{{ idx }}]) - except TypeError: - match_ok = False - else: - any1_val = args[{{ idx }}] - {% elif arg.value.startswith("list<") and arg.value.endswith(">") %} - {% set inner_type = arg.value[5:-1] %} - if not dtypes.is_array_like(args[{{ idx }}]): - match_ok = False - else: - inner = dtypes.get_array_inner_type(args[{{ idx }}]) - {% if inner_type == "any1" %} - if any1_val is not None: - try: - any1_val = dtypes.coerce_to_common(any1_val, inner) - except TypeError: - match_ok = False - else: - any1_val = inner - {% else %} - {% set dtype_expr = dtype_map[inner_type] %} - try: - if dtypes.coerce_to_common(inner, {{ dtype_expr }}) != {{ dtype_expr }}: - match_ok = False - except TypeError: - match_ok = False - {% endif %} - {% elif arg.value == "struct" %} - if not dtypes.is_struct_like(args[{{ idx }}]): - match_ok = False - {% else %} - {% set dtype_expr = dtype_map[arg.value] %} - try: - if dtypes.coerce_to_common(args[{{ idx }}], {{ dtype_expr }}) != {{ dtype_expr }}: - match_ok = False - except TypeError: - match_ok = False - {% endif %} - {% endfor %} - if match_ok: - {% set return_type_yaml = impl.return_type %} - {% if return_type_yaml == "any1" %} - return any1_val - {% elif return_type_yaml.startswith("list<") and return_type_yaml.endswith(">") %} - {% set inner_type = return_type_yaml[5:-1] %} - {% if inner_type == "any1" %} - if any1_val is not None: - return dtypes.list_type(any1_val) - else: - return None - {% else %} - {% set dtype_expr = dtype_map[inner_type] %} - return dtypes.list_type({{ dtype_expr }}) - {% endif %} - {% else %} - {% set dtype_expr = dtype_map[return_type_yaml] %} - return {{ dtype_expr }} - {% endif %} - - {% endfor %} - raise TypeError(f"Could not find matching signature for {{ sql_name }} with argument types: {[str(t) for t in args]}") diff --git a/scripts/templates/test_operation.py.j2 b/scripts/templates/test_operation.py.j2 deleted file mode 100644 index 6aee365cded..00000000000 --- a/scripts/templates/test_operation.py.j2 +++ /dev/null @@ -1,44 +0,0 @@ -{% include 'license.py.j2' %} - -# -# DO NOT MODIFY THIS FILE DIRECTLY. -# This file was generated from: {{ yaml_path }} -# by the script: {{ script_path }} - -import bigframes.core.col -import bigframes.core.expression as ex -import bigframes.pandas as bpd -import {{ import_path }} as {{ short_name }}_op -import bigframes.bigquery as bbq - - -{% for func in functions %} -def test_{{ func.name }}_expression(): - # Call the function with col() expressions -{% if is_global %} - result = bbq.{{ func.name }}( -{% else %} - result = bbq.{{ short_name }}.{{ func.name }}( -{% endif %} - {% for arg in func.args %} - bpd.col("{{ arg.name }}"), - {% endfor %} - ) - - # Verify result is a col Expression - assert isinstance(result, bigframes.core.col.Expression) - - # Verify the internal expression structure - expr = result._value - assert isinstance(expr, ex.OpExpression) - assert expr.op == {{ short_name }}_op.{{ func.op_name }} - - # Verify arguments are free variables matching the names - assert len(expr.inputs) == {{ func.args | length }} - {% for arg in func.args %} - assert isinstance(expr.inputs[{{ loop.index0 }}], ex.UnboundVariableExpression) - assert expr.inputs[{{ loop.index0 }}].id == "{{ arg.name }}" - {% endfor %} - - -{% endfor %} diff --git a/scripts/test_publish_api_coverage.py b/scripts/test_publish_api_coverage.py index 167cf5917b0..96b2d1bb48d 100644 --- a/scripts/test_publish_api_coverage.py +++ b/scripts/test_publish_api_coverage.py @@ -12,57 +12,34 @@ # See the License for the specific language governing permissions and # limitations under the License. -import sys - import pandas -import pytest -from publish_api_coverage import build_api_coverage_table - -pytest.importorskip("sklearn") - - -@pytest.fixture -def api_coverage_df(): - return build_api_coverage_table("my_bf_ver", "my_release_ver") +import publish_api_coverage -@pytest.mark.skipif( - sys.version_info >= (3, 13), - reason="Issues with installing sklearn for this test in python 3.13", -) -def test_api_coverage_produces_expected_schema(api_coverage_df): - # Older pandas has different timestamp default precision - pytest.importorskip("pandas", minversion="2.0.0") - +def test_api_coverage_produces_expected_schema(): + df = publish_api_coverage.build_api_coverage_table("my_bf_ver", "my_release_ver") pandas.testing.assert_series_equal( - api_coverage_df.dtypes, + df.dtypes, pandas.Series( - data={ - # Note to developer: if you update this test, you will also - # need to update schema of the API coverage BigQuery table in - # the bigframes-metrics project. - "api": "string", - "pattern": "string", - "kind": "string", - "is_in_bigframes": "boolean", - "missing_parameters": "string", - "requires_index": "string", - "requires_ordering": "string", - "module": "string", - "timestamp": "datetime64[us]", - "bigframes_version": "string", - "release_version": "string", - }, + data=[ + "string", + "string", + "string", + "boolean", + "string", + "datetime64[ns]", + "string", + "string", + ], + index=[ + "api", + "pattern", + "kind", + "is_in_bigframes", + "module", + "timestamp", + "bigframes_version", + "release_version", + ], ), - # String dtype behavior not consistent across pandas versions - check_dtype=False, ) - - -@pytest.mark.skipif( - sys.version_info >= (3, 13), - reason="Issues with installing sklearn for this test in python 3.13", -) -def test_api_coverage_produces_missing_parameters(api_coverage_df): - """Make sure at least some functions have reported missing parameters.""" - assert (api_coverage_df["missing_parameters"].str.len() > 0).any() diff --git a/scripts/windows/build.bat b/scripts/windows/build.bat deleted file mode 100644 index d599702c98e..00000000000 --- a/scripts/windows/build.bat +++ /dev/null @@ -1,38 +0,0 @@ -@rem Copyright 2024 Google LLC -@rem -@rem Licensed under the Apache License, Version 2.0 (the "License"); -@rem you may not use this file except in compliance with the License. -@rem You may obtain a copy of the License at -@rem -@rem http://www.apache.org/licenses/LICENSE-2.0 -@rem -@rem Unless required by applicable law or agreed to in writing, software -@rem distributed under the License is distributed on an "AS IS" BASIS, -@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -@rem See the License for the specific language governing permissions and -@rem limitations under the License. - -:; Change directory to repo root. -SET script_dir="%~dp0" -cd "%~dp0"\..\.. - -echo "Listing available Python versions' -py -0 || goto :error - -py -3.10 -m pip install --upgrade pip || goto :error -py -3.10 -m pip install --upgrade pip setuptools wheel || goto :error - -echo "Building Wheel" -py -3.10 -m pip wheel . --wheel-dir wheels || goto :error/ - -echo "Built wheel, now running tests." -call "%script_dir%"/test.bat 3.10 || goto :error - -echo "Windows build has completed successfully" - -:; https://stackoverflow.com/a/46813196/101923 -:; exit 0 -exit /b 0 - -:error -exit /b %errorlevel% diff --git a/scripts/windows/test.bat b/scripts/windows/test.bat deleted file mode 100644 index bcd605bd129..00000000000 --- a/scripts/windows/test.bat +++ /dev/null @@ -1,40 +0,0 @@ -@rem Copyright 2024 Google LLC -@rem -@rem Licensed under the Apache License, Version 2.0 (the "License"); -@rem you may not use this file except in compliance with the License. -@rem You may obtain a copy of the License at -@rem -@rem http://www.apache.org/licenses/LICENSE-2.0 -@rem -@rem Unless required by applicable law or agreed to in writing, software -@rem distributed under the License is distributed on an "AS IS" BASIS, -@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -@rem See the License for the specific language governing permissions and -@rem limitations under the License. - -@rem This test file runs for one Python version at a time, and is intended to -@rem be called from within the build loop. - -:; Change directory to repo root. -SET script_dir="%~dp0" -cd "%~dp0"\..\.. - -set PYTHON_VERSION=%1 -if "%PYTHON_VERSION%"=="" ( - echo "Python version was not provided, using Python 3.10" - set PYTHON_VERSION=3.10 -) - -py -%PYTHON_VERSION%-64 -m pip install nox || goto :error - -py -%PYTHON_VERSION%-64 -m nox -s unit-"%PYTHON_VERSION%" || goto :error - -:; TODO(b/358148440): enable system tests on windows -:; py -%PYTHON_VERSION%-64 -m nox -s system-"%PYTHON_VERSION%" || goto :error - -:; https://stackoverflow.com/a/46813196/101923 -:; exit 0 -exit /b 0 - -:error -exit /b %errorlevel% diff --git a/setup.py b/setup.py index e2717fbe5e4..29eacb74a9a 100644 --- a/setup.py +++ b/setup.py @@ -12,7 +12,6 @@ # See the License for the specific language governing permissions and # limitations under the License. - import io import itertools import os @@ -31,67 +30,36 @@ # 'Development Status :: 3 - Alpha' # 'Development Status :: 4 - Beta' # 'Development Status :: 5 - Production/Stable' -release_status = "Development Status :: 5 - Production/Stable" +release_status = "Development Status :: 3 - Alpha" dependencies = [ - # please keep these in sync with the minimum versions in testing/constraints-3.10.txt "cloudpickle >= 2.0.0", "fsspec >=2023.3.0", - "gcsfs >=2023.3.0, !=2025.5.0, !=2026.2.0, !=2026.3.0", + "gcsfs >=2023.3.0", "geopandas >=0.12.2", - "google-auth[pyopenssl] >=2.15.0,<3.0", - "google-cloud-bigquery[bqstorage,pandas] >=3.36.0", - # 2.30 needed for arrow support. - "google-cloud-bigquery-storage >= 2.30.0, < 3.0.0", - "google-cloud-functions >=1.20.2", - "google-cloud-bigquery-connection >=1.18.2", - "google-cloud-resource-manager >=1.14.2", + "google-auth >2.14.1,<3.0dev", + "google-cloud-bigquery[bqstorage,pandas] >=3.10.0", + "google-cloud-functions >=1.10.1", + "google-cloud-bigquery-connection >=1.12.0", + "google-cloud-iam >=2.12.1", + "google-cloud-resource-manager >=1.10.3", "google-cloud-storage >=2.0.0", - "google-crc32c >=1.0.0,<2.0.0", - "grpc-google-iam-v1 >= 0.14.2", - "numpy >=1.24.0", - "pandas >=1.5.3", - "pandas-gbq >=0.26.1", - "pyarrow >=23.0.1", + # TODO: Relax upper bound once we have fixed `system_prerelease` tests. + "ibis-framework[bigquery] >=6.2.0,<7.0.0dev", + "pandas >=1.5.0", "pydata-google-auth >=1.8.2", "requests >=2.27.1", - "shapely >=1.8.5", - "tabulate >=0.9", - "humanize >=4.6.0", - "matplotlib >=3.7.1", - "db-dtypes >=1.4.2", - "pyiceberg >= 0.7.1", - # For vendored ibis-framework. - "atpublic>=2.3,<6", - "python-dateutil>=2.8.2,<3", - "pytz>=2022.7", - "toolz>=0.11,<2", - "typing-extensions>=4.5.0,<5", - "rich>=12.4.4,<14", + "scikit-learn >=1.2.2", + "sqlalchemy >=1.4,<3.0dev", + "ipywidgets >=7.7.1", + "humanize >= 4.6.0", ] extras = { # Optional test dependencies packages. If they're missed, may skip some tests. "tests": [ - "freezegun", - "pytest-snapshot", - "google-cloud-bigtable >=2.30.0", - "google-cloud-pubsub >=2.29.0", - "tzdata", + "pandas-gbq >=0.19.0", ], - # used for local engine - "polars": ["polars >= 1.21.0"], - "scikit-learn": ["scikit-learn>=1.2.2"], # Packages required for basic development flow. - "dev": [ - "pytest", - "pre-commit", - "nox", - "google-cloud-testutils", - ], - # install anywidget for SQL - "anywidget": [ - "anywidget>=0.9.18", - "traitlets>=5.0.0", - ], + "dev": ["pytest", "pytest-mock", "pre-commit", "nox", "google-cloud-testutils"], } extras["all"] = list(sorted(frozenset(itertools.chain.from_iterable(extras.values())))) @@ -112,53 +80,36 @@ # benchmarks, etc. packages = [ package - for package in setuptools.find_namespace_packages() - if package.startswith("bigframes") -] + [ - package - for package in setuptools.find_namespace_packages("third_party") - if package.startswith("bigframes_vendored") + for package in setuptools.PEP420PackageFinder.find() + if package.startswith("bigframes") or package.startswith("third_party") ] setuptools.setup( name=name, version=version_id, description=description, - download_url="https://github.com/googleapis/google-cloud-python/tree/main/packages/bigframes/releases", long_description=readme, - long_description_content_type="text/x-rst", author="Google LLC", author_email="bigframes-feedback@google.com", license="Apache 2.0", - url="https://dataframes.bigquery.dev", - project_urls={ - "Source": "https://github.com/googleapis/google-cloud-python/tree/main/packages/bigframes", - "Changelog": "https://dataframes.bigquery.dev/changelog.html", - "Issues": "https://github.com/googleapis/google-cloud-python/tree/main/packages/bigframes/issues", - }, + url="https://github.com/googleapis/python-bigquery-dataframes", classifiers=[ release_status, "Intended Audience :: Developers", "License :: OSI Approved :: Apache Software License", "Programming Language :: Python", "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", - "Programming Language :: Python :: 3.12", - "Programming Language :: Python :: 3.13", - "Programming Language :: Python :: 3.14", "Operating System :: OS Independent", "Topic :: Internet", ], install_requires=dependencies, extras_require=extras, platforms="Posix; MacOS X; Windows", - package_dir={ - "bigframes": "bigframes", - "bigframes_vendored": "third_party/bigframes_vendored", - }, packages=packages, - python_requires=">=3.10", + python_requires=">=3.9", include_package_data=True, zip_safe=False, ) diff --git a/specs/2025-08-04-geoseries-scalars.md b/specs/2025-08-04-geoseries-scalars.md deleted file mode 100644 index e7bc6c61e19..00000000000 --- a/specs/2025-08-04-geoseries-scalars.md +++ /dev/null @@ -1,317 +0,0 @@ -# Implementing GeoSeries scalar operators - -This project is to implement all GeoSeries scalar properties and methods in the -`bigframes.geopandas.GeoSeries` class. Likewise, all BigQuery GEOGRAPHY -functions should be exposed in the `bigframes.bigquery` module. - -## Background - -*Explain the context and why this change is necessary.* -*Include links to relevant issues or documentation.* - -* https://geopandas.org/en/stable/docs/reference/geoseries.html -* https://cloud.google.com/bigquery/docs/reference/standard-sql/geography_functions - -## Acceptance Criteria - -*Define the specific, measurable outcomes that indicate the task is complete.* -*Use a checklist format for clarity.* - -### GeoSeries methods and properties - -- [x] Constructor -- [x] GeoSeries.area -- [x] GeoSeries.boundary -- [ ] GeoSeries.bounds -- [ ] GeoSeries.total_bounds -- [x] GeoSeries.length -- [ ] GeoSeries.geom_type -- [ ] GeoSeries.offset_curve -- [x] GeoSeries.distance -- [ ] GeoSeries.hausdorff_distance -- [ ] GeoSeries.frechet_distance -- [ ] GeoSeries.representative_point -- [ ] GeoSeries.exterior -- [ ] GeoSeries.interiors -- [ ] GeoSeries.minimum_bounding_radius -- [ ] GeoSeries.minimum_clearance -- [x] GeoSeries.x -- [x] GeoSeries.y -- [ ] GeoSeries.z -- [ ] GeoSeries.m -- [ ] GeoSeries.get_coordinates -- [ ] GeoSeries.count_coordinates -- [ ] GeoSeries.count_geometries -- [ ] GeoSeries.count_interior_rings -- [ ] GeoSeries.set_precision -- [ ] GeoSeries.get_precision -- [ ] GeoSeries.get_geometry -- [x] GeoSeries.is_closed -- [ ] GeoSeries.is_empty -- [ ] GeoSeries.is_ring -- [ ] GeoSeries.is_simple -- [ ] GeoSeries.is_valid -- [ ] GeoSeries.is_valid_reason -- [ ] GeoSeries.is_valid_coverage -- [ ] GeoSeries.invalid_coverage_edges -- [ ] GeoSeries.has_m -- [ ] GeoSeries.has_z -- [ ] GeoSeries.is_ccw -- [ ] GeoSeries.contains -- [ ] GeoSeries.contains_properly -- [ ] GeoSeries.crosses -- [ ] GeoSeries.disjoint -- [ ] GeoSeries.dwithin -- [ ] GeoSeries.geom_equals -- [ ] GeoSeries.geom_equals_exact -- [ ] GeoSeries.geom_equals_identical -- [ ] GeoSeries.intersects -- [ ] GeoSeries.overlaps -- [ ] GeoSeries.touches -- [ ] GeoSeries.within -- [ ] GeoSeries.covers -- [ ] GeoSeries.covered_by -- [ ] GeoSeries.relate -- [ ] GeoSeries.relate_pattern -- [ ] GeoSeries.clip_by_rect -- [x] GeoSeries.difference -- [x] GeoSeries.intersection -- [ ] GeoSeries.symmetric_difference -- [ ] GeoSeries.union -- [x] GeoSeries.boundary -- [x] GeoSeries.buffer -- [x] GeoSeries.centroid -- [ ] GeoSeries.concave_hull -- [x] GeoSeries.convex_hull -- [ ] GeoSeries.envelope -- [ ] GeoSeries.extract_unique_points -- [ ] GeoSeries.force_2d -- [ ] GeoSeries.force_3d -- [ ] GeoSeries.make_valid -- [ ] GeoSeries.minimum_bounding_circle -- [ ] GeoSeries.maximum_inscribed_circle -- [ ] GeoSeries.minimum_clearance -- [ ] GeoSeries.minimum_clearance_line -- [ ] GeoSeries.minimum_rotated_rectangle -- [ ] GeoSeries.normalize -- [ ] GeoSeries.orient_polygons -- [ ] GeoSeries.remove_repeated_points -- [ ] GeoSeries.reverse -- [ ] GeoSeries.sample_points -- [ ] GeoSeries.segmentize -- [ ] GeoSeries.shortest_line -- [ ] GeoSeries.simplify -- [ ] GeoSeries.simplify_coverage -- [ ] GeoSeries.snap -- [ ] GeoSeries.transform -- [ ] GeoSeries.affine_transform -- [ ] GeoSeries.rotate -- [ ] GeoSeries.scale -- [ ] GeoSeries.skew -- [ ] GeoSeries.translate -- [ ] GeoSeries.interpolate -- [ ] GeoSeries.line_merge -- [ ] GeoSeries.project -- [ ] GeoSeries.shared_paths -- [ ] GeoSeries.build_area -- [ ] GeoSeries.constrained_delaunay_triangles -- [ ] GeoSeries.delaunay_triangles -- [ ] GeoSeries.explode -- [ ] GeoSeries.intersection_all -- [ ] GeoSeries.polygonize -- [ ] GeoSeries.union_all -- [ ] GeoSeries.voronoi_polygons -- [ ] GeoSeries.from_arrow -- [ ] GeoSeries.from_file -- [ ] GeoSeries.from_wkb -- [x] GeoSeries.from_wkt -- [x] GeoSeries.from_xy -- [ ] GeoSeries.to_arrow -- [ ] GeoSeries.to_file -- [ ] GeoSeries.to_json -- [ ] GeoSeries.to_wkb -- [x] GeoSeries.to_wkt -- [ ] GeoSeries.crs -- [ ] GeoSeries.set_crs -- [ ] GeoSeries.to_crs -- [ ] GeoSeries.estimate_utm_crs -- [ ] GeoSeries.fillna -- [ ] GeoSeries.isna -- [ ] GeoSeries.notna -- [ ] GeoSeries.clip -- [ ] GeoSeries.plot -- [ ] GeoSeries.explore -- [ ] GeoSeries.sindex -- [ ] GeoSeries.has_sindex -- [ ] GeoSeries.cx -- [ ] GeoSeries.__geo_interface__ - -### `bigframes.pandas` methods - -Constructors: Functions that build new geography values from coordinates or -existing geographies. - -- [x] ST_GEOGPOINT -- [ ] ST_MAKELINE -- [ ] ST_MAKEPOLYGON -- [ ] ST_MAKEPOLYGONORIENTED - -Parsers ST_GEOGFROM: Functions that create geographies from an external format -such as WKT and GeoJSON. - -- [ ] ST_GEOGFROMGEOJSON -- [x] ST_GEOGFROMTEXT -- [ ] ST_GEOGFROMWKB -- [ ] ST_GEOGPOINTFROMGEOHASH - -Formatters: Functions that export geographies to an external format such as WKT. - -- [ ] ST_ASBINARY -- [ ] ST_ASGEOJSON -- [x] ST_ASTEXT -- [ ] ST_GEOHASH - -Transformations: Functions that generate a new geography based on input. - -- [x] ST_BOUNDARY -- [x] ST_BUFFER -- [ ] ST_BUFFERWITHTOLERANCE -- [x] ST_CENTROID -- [ ] ST_CENTROID_AGG (Aggregate) -- [ ] ST_CLOSESTPOINT -- [x] ST_CONVEXHULL -- [x] ST_DIFFERENCE -- [ ] ST_EXTERIORRING -- [ ] ST_INTERIORRINGS -- [x] ST_INTERSECTION -- [ ] ST_LINEINTERPOLATEPOINT -- [ ] ST_LINESUBSTRING -- [ ] ST_SIMPLIFY -- [ ] ST_SNAPTOGRID -- [ ] ST_UNION -- [ ] ST_UNION_AGG (Aggregate) - -Accessors: Functions that provide access to properties of a geography without -side-effects. - -- [ ] ST_DIMENSION -- [ ] ST_DUMP -- [ ] ST_ENDPOINT -- [ ] ST_GEOMETRYTYPE -- [x] ST_ISCLOSED -- [ ] ST_ISCOLLECTION -- [ ] ST_ISEMPTY -- [ ] ST_ISRING -- [ ] ST_NPOINTS -- [ ] ST_NUMGEOMETRIES -- [ ] ST_NUMPOINTS -- [ ] ST_POINTN -- [ ] ST_STARTPOINT -- [x] ST_X -- [x] ST_Y - -Predicates: Functions that return TRUE or FALSE for some spatial relationship -between two geographies or some property of a geography. These functions are -commonly used in filter clauses. - -- [ ] ST_CONTAINS -- [ ] ST_COVEREDBY -- [ ] ST_COVERS -- [ ] ST_DISJOINT -- [ ] ST_DWITHIN -- [ ] ST_EQUALS -- [ ] ST_HAUSDORFFDWITHIN -- [ ] ST_INTERSECTS -- [ ] ST_INTERSECTSBOX -- [ ] ST_TOUCHES -- [ ] ST_WITHIN - -Measures: Functions that compute measurements of one or more geographies. - -- [ ] ST_ANGLE -- [x] ST_AREA -- [ ] ST_AZIMUTH -- [ ] ST_BOUNDINGBOX -- [x] ST_DISTANCE -- [ ] ST_EXTENT (Aggregate) -- [ ] ST_HAUSDORFFDISTANCE -- [ ] ST_LINELOCATEPOINT -- [x] ST_LENGTH -- [ ] ST_MAXDISTANCE -- [ ] ST_PERIMETER - -Clustering: Functions that perform clustering on geographies. - -- [ ] ST_CLUSTERDBSCAN - -S2 functions: Functions for working with S2 cell coverings of GEOGRAPHY. - -- [ ] S2_CELLIDFROMPOINT -- [ ] S2_COVERINGCELLIDS - -Raster functions: Functions for analyzing geospatial rasters using geographies. - -- [ ] ST_REGIONSTATS - -## Detailed Steps - -*Break down the implementation into small, actionable steps.* -*This section will guide the development process.* - -### Implementing a new scalar geography operation - -- [ ] **Define the operation dataclass:** - - [ ] In `bigframes/operations/geo_ops.py`, create a new dataclass - inheriting from `base_ops.UnaryOp` or `base_ops.BinaryOp`. Note that - BinaryOp is for methods that take two **columns**. Any literal values can - be passed as parameters to a UnaryOp. - - [ ] Define the `name` of the operation and any parameters it requires. - - [ ] Implement the `output_type` method to specify the data type of the result. -- [ ] **Export the new operation:** - - [ ] In `bigframes/operations/__init__.py`, import your new operation dataclass and add it to the `__all__` list. -- [ ] **Implement the compilation logic:** - - [ ] In `bigframes/core/compile/ibis_compiler/operations/geo_ops.py`: - - [ ] If the BigQuery function has a direct equivalent in Ibis, you can often reuse an existing Ibis method. - - [ ] If not, define a new Ibis UDF using `@ibis_udf.scalar.builtin` to map to the specific BigQuery function signature. - - [ ] Create a new compiler implementation function (e.g., `geo_length_op_impl`). - - [ ] Register this function to your operation dataclass using `@register_unary_op` or `@register_binary_op`. - - [ ] In `bigframes/core/compile/sqlglot/expressions/geo_ops.py`: - - [ ] Create a new compiler implementation function that generates the appropriate `sqlglot.exp` expression. - - [ ] Register this function to your operation dataclass using `@register_unary_op` or `@register_binary_op`. -- [ ] **Implement the user-facing function or property:** - - [ ] For a `bigframes.bigquery` function: - - [ ] In `bigframes/bigquery/_operations/geo.py`, create the user-facing function (e.g., `st_length`). - - [ ] The function should take a `Series` and any other parameters. - - [ ] Inside the function, call `series._apply_unary_op` or `series._apply_binary_op`, passing the operation dataclass you created. - - [ ] Add a comprehensive docstring with examples. - - [ ] In `bigframes/bigquery/__init__.py`, import your new user-facing function and add it to the `__all__` list. - - [ ] For a `GeoSeries` property or method: - - [ ] In `bigframes/geopandas/geoseries.py`, create the property or - method. Omit the docstring. - - [ ] If the operation is not possible to be supported, such as if the - geopandas method returns values in units corresponding to the - coordinate system rather than meters that BigQuery uses, raise a - `NotImplementedError` with a helpful message. Likewise, if a - required parameter takes a value in terms of the coordinate - system, but BigQuery uses meters, raise a `NotImplementedError`. - - [ ] Otherwise, call `series._apply_unary_op` or `series._apply_binary_op`, passing the operation dataclass. - - [ ] Add a comprehensive docstring with examples to the superclass in - `third_party/bigframes_vendored/geopandas/geoseries.py`. -- [ ] **Add Tests:** - - [ ] Add system tests in `tests/system/small/bigquery/test_geo.py` or `tests/system/small/geopandas/test_geoseries.py` to verify the end-to-end functionality. Test various inputs, including edge cases and `NULL` values. - - [ ] If you are overriding a pandas or GeoPandas property and raising `NotImplementedError`, add a unit test to ensure the correct error is raised. - -## Verification - -*Specify the commands to run to verify the changes.* - -- [ ] The `nox -r -s format lint lint_setup_py` linter should pass. -- [ ] The `nox -r -s mypy` static type checker should pass. -- [ ] The `nox -r -s docs docfx` docs should successfully build and include relevant docs in the output. -- [ ] All new and existing unit tests `pytest tests/unit` should pass. -- [ ] Identify all related system tests in the `tests/system` directories. -- [ ] All related system tests `pytest tests/system/small/path_to_relevant_test.py::test_name` should pass. - -## Constraints - -Follow the guidelines listed in GEMINI.md at the root of the repository. diff --git a/specs/2025-08-11-anywidget-align-text.md b/specs/2025-08-11-anywidget-align-text.md deleted file mode 100644 index 03305538dc6..00000000000 --- a/specs/2025-08-11-anywidget-align-text.md +++ /dev/null @@ -1,132 +0,0 @@ -# Anywidget: align text left and numerics right - -The "anywidget" rendering mode outputs an HTML table per page right now, but -the values need to be aligned according to their data type. - -## Background - -Anywidget currently renders pages like the following: - -```html - - - - - - - - - - - - - - - - - - - - - - - - - - - -
stategenderyearnamenumber
VAM1930Pat6
TXM1968Kennith18
-``` - -* This change fixes internal issue b/437697339. -* Numeric data should be right aligned so that it is easier to compare numbers, - especially if they all are rounded to the same precision. -* Text data is better left aligned, since many languages read left to right. - -## Acceptance Criteria - -- [ ] Header cells should align left. -- [ ] Header cells should use the resize CSS property to allow resizing. -- [ ] STRING columns are left-aligned in the output of `TableWidget` in - `bigframes/display/anywidget.py`. -- [ ] Numeric columns (INT64, FLOAT64, NUMERIC, BIGNUMERIC) are right-aligned - in the output of `TableWidget` in `bigframes/display/anywidget.py`. -- [ ] Create option `DisplayOptions.precision` in - `bigframes/_config/display_options.py` that can override the output - precision (defaults to 6, just like `pandas.options.display.precision`). -- [ ] All other non-numeric column types, including BYTES, BOOLEAN, TIMESTAMP, - and more, are left-aligned in the output of `TableWidget` in - `bigframes/display/anywidget.py`. -- [ ] There are parameterized unit tests verifying the alignment is set - correctly. - -## Detailed Steps - -### 1. Create Display Precision Configuration - -- [ ] In `bigframes/_config/display_options.py`, add a new `precision` attribute to the `DisplayOptions` dataclass. -- [ ] Set the default value to `6`. -- [ ] Add precision to the items in `def pandas_repr` that get passed to the pandas options context. -- [ ] Add a docstring explaining that it controls the floating point output precision, similar to `pandas.options.display.precision`. -- [ ] Check these items off with `[x]` as they are completed. - -### 2. Improve the headers - -- [ ] Create `bigframes/display/html.py`. -- [ ] In `bigframes/display/html.py`, create a `def render_html(*, dataframe: pandas.DataFrame, table_id: str)` method. -- [ ] Loop through the column names to create the table head. -- [ ] Apply the `text-align: left` style to the header. -- [ ] Wrap the cell text in a resizable `div`. -- [ ] Check these items off with `[x]` as they are completed. - -### 3. Implement Alignment and Precision Logic in TableWidget - -- [ ] Create a helper function `_is_dtype_numeric(dtype)` that takes a pandas - dtype returns True for types that that should be right-aligned. These - dtypes should correspond to the BigQuery data types: `INT64`, `FLOAT64`, - `NUMERIC`, `BIGNUMERIC`. Use the `bigframes.dtypes` module to map from - pandas type to BigQuery type. -- [ ] In the loop that generates the table rows (`` elements), add a function to determine the style based on the column's `dtype`. -- [ ] If the column's `dtype` is in the numeric set, apply the CSS style `text-align: right`. -- [ ] For all other `dtypes` (including `STRING`, `BYTES`, `BOOLEAN`, `TIMESTAMP`, etc.), apply `text-align: left`. -- [ ] When formatting floating-point numbers for display, use the `bigframes.options.display.precision` value. -- [ ] In `bigframes/display/anywidget.py`, modify the `_set_table_html` method of the `TableWidget` class to call `bigframes.display.html.render_html(...)`. -- [ ] Render the notebook at `notebooks/dataframes/anywidget_mode.ipynb` with - the `jupyter nbconvert --to notebook --execute notebooks/dataframes/anywidget_mode.ipynb` - command and validate that the rendered notebook includes the desired - changes to the HTML tables. -- [ ] Check these items off with `[x]` as they are completed. - -### 4. Add Parameterized Unit Tests - -- [ ] Create a new test file: `tests/unit/display/test_html.py`. -- [ ] Create a parameterized test method, e.g., `test_render_html_alignment_and_precision`. -- [ ] Use `@pytest.mark.parametrize` to test various scenarios. -- [ ] **Scenario 1: Alignment.** - - Create a sample `bigframes.dataframe.DataFrame` with columns of different types: a string, an integer, a float, and a boolean. - - Render the `pandas.DataFrame` to HTML. - - Assert that the integer and float column headers and data cells (`` and ``) have `style="text-align: right;"`. - - Assert that the string and boolean columns have `style="text-align: left;"`. -- [ ] **Scenario 2: Precision.** - - Create a `bigframes.dataframe.DataFrame` with a `FLOAT64` column containing a number with many decimal places (e.g., `3.14159265`). - - Set `bigframes.options.display.precision = 4`. - - Render the `pandas.DataFrame` to HTML. - - Assert that the output string contains the number formatted to 4 decimal places (e.g., `3.1416`). - - Remember to reset the option value after the test to avoid side effects. -- [ ] Check these items off with `[x]` as they are completed. - -## Verification - -*Specify the commands to run to verify the changes.* - -- [ ] The `nox -r -s format lint lint_setup_py` linter should pass. -- [ ] The `nox -r -s mypy` static type checker should pass. -- [ ] The `nox -r -s docs docfx` docs should successfully build and include relevant docs in the output. -- [ ] All new and existing unit tests `pytest tests/unit` should pass. -- [ ] Identify all related system tests in the `tests/system` directories. -- [ ] All related system tests `pytest tests/system/small/path_to_relevant_test.py::test_name` should pass. -- [ ] Check these items off with `[x]` as they are completed. - -## Constraints - -Follow the guidelines listed in GEMINI.md at the root of the repository. diff --git a/specs/TEMPLATE.md b/specs/TEMPLATE.md deleted file mode 100644 index 0d93035dcc2..00000000000 --- a/specs/TEMPLATE.md +++ /dev/null @@ -1,47 +0,0 @@ -# Title of the Specification - -*Provide a brief overview of the feature or bug.* - -## Background - -*Explain the context and why this change is necessary.* -*Include links to relevant issues or documentation.* - -## Acceptance Criteria - -*Define the specific, measurable outcomes that indicate the task is complete.* -*Use a checklist format for clarity.* - -- [ ] Criterion 1 -- [ ] Criterion 2 -- [ ] Criterion 3 - -## Detailed Steps - -*Break down the implementation into small, actionable steps.* -*This section will guide the development process.* - -### 1. Step One - -- [ ] Action 1.1 -- [ ] Action 1.2 - -### 2. Step Two - -- [ ] Action 2.1 -- [ ] Action 2.2 - -## Verification - -*Specify the commands to run to verify the changes.* - -- [ ] The `nox -r -s format lint lint_setup_py` linter should pass. -- [ ] The `nox -r -s mypy` static type checker should pass. -- [ ] The `nox -r -s docs docfx` docs should successfully build and include relevant docs in the output. -- [ ] All new and existing unit tests `pytest tests/unit` should pass. -- [ ] Identify all related system tests in the `tests/system` directories. -- [ ] All related system tests `pytest tests/system/small/path_to_relevant_test.py::test_name` should pass. - -## Constraints - -Follow the guidelines listed in GEMINI.md at the root of the repository. diff --git a/specs/bigframes-bigquery-contributing.md b/specs/bigframes-bigquery-contributing.md deleted file mode 100644 index 10931af0755..00000000000 --- a/specs/bigframes-bigquery-contributing.md +++ /dev/null @@ -1,501 +0,0 @@ -# bigframes.bigquery inputs and outputs policies - -The goal of the [bigframes.bigquery -APIs](https://dataframes.bigquery.dev/reference/api/bigframes.bigquery.html#module-bigframes.bigquery) -is to provide the simplest possible mapping from BigQuery (GoogleSQL) -[functions](https://docs.cloud.google.com/bigquery/docs/reference/standard-sql/functions-all) -and -[operations](https://docs.cloud.google.com/bigquery/docs/reference/standard-sql/query-syntax) -to Python. "Simplest" is somewhat ambiguous though, when it comes to the types -involved and behaviors, so this document aims to expand on that vision with -specific examples. - -## SQL and BigFrames expression types - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
SQL expression type(s) - Python type(s) - Notes - Examples -
Column expression (usable in a SELECT clause) - - - Both Python Series and column expression should be supported as inputs, - with the output reflecting the users input. Use a TypeVar - rather than directly using union types to make type checking easier. -

-Special considerations for Series inputs: -

-If an input and output are both a Series with the same number of rows, make sure -the output Series is implicitly (row identity) alignable with the original -input. In other words, don't generate a table expression. -

-If there are multiple Series inputs, they should be implicitly aligned if -possible so as not to generate unnecessary table expressions. -

Most scalar functions accept one or more column expressions as input. -
Scalar values - - - Theoretically, we could try to get the type system to help the user - disambiguate between this case and the "Column expression" case, but I think - that's more trouble than it it's worth with regards to the expectations of - Python users. - - -
Table expression - bpd.DataFrame -

-All columns are included as normal columns in the input table expression, -including named index columns. If column names aren't unique or contain -characters not compatible with BigQuery flexible column names, raise an error. -

-Outputs are unordered and unindexed to allow for cleaner mapping with SQL. -

Most APIs that take a table expression as input, also output a table - expression with the same number of rows and passing through all unused - columns. - -

This should be used to pass through any index or ordering columns (as well - as all other columns, if that's the SQL behavior), to allow for easy joining - with the original input DataFrame. -

Same number of rows as the input, so we should preserve index and ordering: - - - -

- Different number of rows in output, so no need to preserve index or ordering. - Default index / ordering should be specified with the Session's - configuration: - -

- -

- Possible to have the same number of rows as the input, but joining with the original goes against the purpose of the feature: - -

- -
Table name - string (referring to fully-qualified table ID, e.g. project.dataset.table / project.catalog.namespace.table) - Some SQL APIs do not support or have limitations with arbitrary table expressions, instead taking in a table ID, such as TABLESAMPLE expression. -

-Also, SEARCH and VECTOR_SEARCH, if you want the indexes attached to the table to actually apply. -

-For outputs, it might be preferable to output a table ID instead of a DataFrame, if the user is explicitly creating a table. For example, to_gbq() returns a string with the table name, which is useful for the case where BigFrame generates the table ID for the user. -

All of the items from the "Table expression" row above. APIs that require a table expression, but don't take a table ID can trivially take a table ID through a (SELECT * FROM table) subquery. -

-Some APIs only take a table ID and not an arbitrary table expression:

- -
Aggregated table expression - DataFrameGroupBy - - - -
Analytic table expression -
    - -
  • DataFrameGroupBy - feasibility TBD -
  • Deferred column Expression with a Window applied.
- -
- - -
Column name (unqualified*) \ - \ -*I've only encountered examples where the table name / table expression is passed in separately. - string, -

-For cases where the column name is used as an alias and we aren't using named Series: -

-dict[str, Expression] -

Often a table expression input is paired with a column name input, as is the case with the CREATE MODEL and VECTOR_SEARCH APIs -

-If SQL expects a column name rather than a column expression, do not attempt to change this in Python. For example, don't allow a Series as a substitute for DataFrames + Column name. \ - \ -If the associated table expression is input as a DataFrame, validate that these map cleanly to SQL and raise a ValueError if not. For example: \ -

    - -
  • Duplicate column names (excluding unnamed index columns). -
  • Column names that are some hashable value other than integer (which maps cleanly to a column name) or string. -
  • Any column name containing a punctuation mark that is not allowed by BigQuery flexible column names, such as ! or $.
- -
- -
Literal values - corresponding literal Python value (e.g. int, float, string) - For cases where scalar values are also supported, it should be safe to start with this and then expand to support expressions without a breaking change, as is done in https://github.com/googleapis/google-cloud-python/pull/16606. - Most scalar functions accept one or more literal values as input. -
Scalar subqueries - Not supported yet, except implicitly in some aggregation use cases. -

-Would need some sort of bigframes deferred expression that can be tied to a table expression. -

-(Possibly DataFrame with 1 column?) -

- -
- -## Python policies - -### Naming - -Take the SQL function name, keyword name (used as a function name in Python), or argument name and transform them to lower_snake_case to reflect Python conventions. - -### Internal expressions - -Prefer creating deferred BigFrames expression objects where feasible. For -example, all scalar outputting functions should return a -`bigframes.pandas.Series` or `bigframes.core.col.Expression` that wraps a -`bigframes.core.expression.Expression`. - -Prefer returning a `bigframes.pandas.DataFrame` that wraps a -`bigframes.bigframes.core.bigframe_node.BigFrameNode`. See `from_bq_data_source` in -`bigframes.core.array_value.ArrayValue`, as an example. - -Exceptions to this are cases where the output schema is likely to evolve or -differ in ways that are difficult to model, such as the `ML.PREDICT` SQL -function, where output columns differ based on the model type and support for -model types are frequently added to BigQuery. In these exceptional cases, the -generated query should run immediately and the returned value should wrap the -results. - -### Argument syntax details - -Arguments in Python can be one of: - -* Positional - * Supported by `*args` in Python, but not recommended. Positional arguments in SQL should map to named positional or keyword arguments in Python. -* Positional or keyword - * Required positional arguments should be positional, just like they are in SQL. -* Keyword-only - * All other arguments should be keyword-only. Use `, * ,` Python syntax to achieve this. - -For optional parameters, use an optional sentinel (see: ) and omit the value from the generated SQL if the user doesn't explicitly provide one. This ensures that an explicit NULL / None value can be passed in. - -``` - -from enum import Enum - -class Default(Enum): - token = 0 - -DEFAULT = Default.token - -def spam(*, ham: list[str] | None | Default = DEFAULT): - op_kwargs = {} - - if ham is not DEFAULT: - op_kwargs['ham'] = "prosciutto" - - ... - -``` - -### Scalar operations types policies - -Many operations output a table expression. For these, the output type is always a DataFrame, regardless of the input types. - -For scalar operations, there are three cases to consider when determining the output types: - - - - - - - - - - - - - - - - - - -
Scalar ops - Input type(s) - Scalar ops - Output type -
Expression - Expression -
Series / DataFrame - Series / DataFrame -

-Preserve ordering and index(es). Join inputs as needed before applying the operation. -

Mix of Expression and Series / DataFrame - Series / DataFrame -

-Preserve ordering and index(es). Join inputs as needed before applying the operation. -

- -## Examples - -### PIVOT SQL operator - -SQL syntax ([docs](https://docs.cloud.google.com/bigquery/docs/reference/standard-sql/query-syntax#pivot_operator)): - -``` -FROM from_item[, ...] pivot_operator - -pivot_operator: - PIVOT( - aggregate_function_call [as_alias][, ...] - FOR input_column - IN ( pivot_column [as_alias][, ...] ) - ) [AS alias] - -as_alias: - [AS] alias - -``` - -SQL example: - -``` -WITH Produce AS ( - SELECT 'Kale' as product, 51 as sales, 'Q1' as quarter, 2020 as year UNION ALL - SELECT 'Kale', 23, 'Q2', 2020 UNION ALL - SELECT 'Kale', 45, 'Q3', 2020 UNION ALL - SELECT 'Kale', 3, 'Q4', 2020 UNION ALL - SELECT 'Kale', 70, 'Q1', 2021 UNION ALL - SELECT 'Kale', 85, 'Q2', 2021 UNION ALL - SELECT 'Apple', 77, 'Q1', 2020 UNION ALL - SELECT 'Apple', 0, 'Q2', 2020 UNION ALL - SELECT 'Apple', 1, 'Q1', 2021) -SELECT * FROM Produce - -/*---------+-------+---------+------+ - | product | sales | quarter | year | - +---------+-------+---------+------| - | Kale | 51 | Q1 | 2020 | - | Kale | 23 | Q2 | 2020 | - | Kale | 45 | Q3 | 2020 | - | Kale | 3 | Q4 | 2020 | - | Kale | 70 | Q1 | 2021 | - | Kale | 85 | Q2 | 2021 | - | Apple | 77 | Q1 | 2020 | - | Apple | 0 | Q2 | 2020 | - | Apple | 1 | Q1 | 2021 | - +---------+-------+---------+------*/ - - -SELECT * FROM - Produce - PIVOT(SUM(sales) FOR quarter IN ('Q1', 'Q2', 'Q3', 'Q4')) - -/*---------+------+----+------+------+------+ - | product | year | Q1 | Q2 | Q3 | Q4 | - +---------+------+----+------+------+------+ - | Apple | 2020 | 77 | 0 | NULL | NULL | - | Apple | 2021 | 1 | NULL | NULL | NULL | - | Kale | 2020 | 51 | 23 | 45 | 3 | - | Kale | 2021 | 70 | 85 | NULL | NULL | - +---------+------+----+------+------+------*/ - -``` - -Python definition: - -``` -def pivot( - table_expression: bpd.DataFrame, - *, - aggregation: Expression | dict[str, Expression], - input_column: str, - pivot_columns: dict[str, float | str | ...] | Sequence[float | str | ...], -) -> bpd.DataFrame: - ... -``` - -Since pivot creates a table expression, we run immediately. - - \ -Python usage: - -``` -pivotted = bbq.pivot( - my_produce_dataframe, - aggregation=bpd.col("sales").sum(), - input_column="quarter", - pivot_columns=["Q1", "Q2", "Q3", "Q4"], -) -``` - -### UNPIVOT SQL operator - -SQL syntax ([docs](https://docs.cloud.google.com/bigquery/docs/reference/standard-sql/query-syntax#unpivot_operator)): - -``` -FROM from_item[, ...] unpivot_operator - -unpivot_operator: - UNPIVOT [ { INCLUDE NULLS | EXCLUDE NULLS } ] ( - { single_column_unpivot | multi_column_unpivot } - ) [unpivot_alias] - -single_column_unpivot: - values_column - FOR name_column - IN (columns_to_unpivot) - -multi_column_unpivot: - values_column_set - FOR name_column - IN (column_sets_to_unpivot) - -values_column_set: - (values_column[, ...]) - -columns_to_unpivot: - unpivot_column [row_value_alias][, ...] - -column_sets_to_unpivot: - (unpivot_column [row_value_alias][, ...]) - -unpivot_alias and row_value_alias: - [AS] alias -``` - -SQL example: - -``` -WITH Produce AS ( - SELECT 'Kale' as product, 51 as Q1, 23 as Q2, 45 as Q3, 3 as Q4 UNION ALL - SELECT 'Apple', 77, 0, 25, 2) - --- SELECT * FROM Produce -/*---------+----+----+----+----+ - | product | Q1 | Q2 | Q3 | Q4 | - +---------+----+----+----+----+ - | Kale | 51 | 23 | 45 | 3 | - | Apple | 77 | 0 | 25 | 2 | - +---------+----+----+----+----*/ - -SELECT * FROM Produce -UNPIVOT(sales FOR quarter IN (Q1, Q2, Q3, Q4)) -- single_column_unpivot - -/*---------+-------+---------+ - | product | sales | quarter | - +---------+-------+---------+ - | Kale | 51 | Q1 | - | Kale | 23 | Q2 | - | Kale | 45 | Q3 | - | Kale | 3 | Q4 | - | Apple | 77 | Q1 | - | Apple | 0 | Q2 | - | Apple | 25 | Q3 | - | Apple | 2 | Q4 | - +---------+-------+---------*/ -``` - -Python definition: - -``` -def unpivot( - table_expression: bpd.DataFrame, - *, - exclude_nulls: bool = True, - values_column: str | Sequence[str], - name_column: str, - columns_to_unpivot: dict[str, str | int] | Sequence[str], -) -> bpd.DataFrame: - ... -``` - -Since unpivot creates a table expression, we run immediately. - - \ -Python usage: - -``` -unpivotted = bbq.unpivot( - my_produce_dataframe, - values_column="sales", - name_column="quarter", - columns_to_unpivot=["Q1", "Q2", "Q3", "Q4"], -) -``` diff --git a/specs/bigframes-bigquery-generator.md b/specs/bigframes-bigquery-generator.md deleted file mode 100644 index 1078bbd05a3..00000000000 --- a/specs/bigframes-bigquery-generator.md +++ /dev/null @@ -1,100 +0,0 @@ -# Code generation for bigframes.bigquery - -This document describes code generation for the `bigframes.bigquery` modules. -For detailed specifications on input and output types, refer to -[Contributing to bigframes.bigquery](./bigframes-bigquery-contributing.md). - -## Overview - -The script at `packages/bigframes/scripts/generate_bigframes_bigquery.py` -generates python submodules for the `bigframes.bigquery` module. When run -without any arguments, it iterates through all yaml files at -`packages/bigframes/scripts/data/sql-functions/**/*.yaml` to generate the code. - -The script also generates a unit test that verifies that the functions have been -included in the `bigframes.bigquery` module, which is important to check, as the -`__init__.py` file requires manual updates. - -## Running the generator - -Since the dependencies for the script differ from that of bigframes -and its test suite, use the self-contained Python script technique described at -https://docs.astral.sh/uv/guides/scripts/ -to automatically manage dependencies using `uv`. Therefore, the header of the -script will look something like: - -```python -#!/usr/bin/env -S uv run --script -# -# /// script -# dependencies = [ -# "jinja2", -# "pyyaml", -# ] -# /// -# -# Copyright 2026 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# ... -``` - -To run the script: - -```bash -cd packages/bigframes -uv run scripts/generate_bigframes_bigquery.py -``` - -To improve reproducibility, we also check in the uv lock file generated by -running `uv lock --script scripts/generate_bigframes_bigquery.py`. - -## Generated code organization - -The `generate_bigframes_bigquery.py` script generates submodules of -`bigframes.bigquery._operations`, with the full path reflecting the organization -of the YAML files. For example, a YAML file at -`packages/bigframes/scripts/data/sql-functions/aead.yaml` corresponds to a -generated Python module at `bigframes.bigquery._operations.aead`. Likewise, -`packages/bigframes/scripts/data/sql-functions/builtins/bit.yaml` corresponds -to the `bigframes.bigquery._operations.builtins.bit` submodule. - -## Generated module implementation - -Each generated module has all functions defined in the YAML file converted to -the equivalent Python definition, including keyword arguments and docstrings. - -### Code generation - -The code will be templated using the jinja2 template engine. This allows -proposed changes to the templated code to be reviewed more easily. - -### Handling optional arguments - -When the user calls a Python function without specifying the optional -argument, that argument is omitted from the SQL text. To allow for explicit -NULL values to be passed in (None in Python), the default value is specified -to be a default sentinel value enum `bigframes.core.sentinels.DEFAULT`. For -example: - -```python -import bigframes.core.sentinels - -def current_date( - time_zone_expression: str | bigframes.core.sentinels.Default = bigframes.core.sentinels.DEFAULT, -): - ... -``` - -### Input and output types - -Refer to the table in -[Contributing to bigframes.bigquery](./bigframes-bigquery-contributing.md). - -### Internal bigframes operator - -Scalar functions should generate an expression using the `GoogleSqlScalarOp`. -This keeps the implementation as scalar SQL functions consistent. - -Aggregate, analytic, and table-valued functions currently require custom ops. As -such, those functions are currently out of scope for this generator. diff --git a/testing/constraints-3.10.txt b/testing/constraints-3.10.txt index 1dcdd64baa0..9f0786f47e6 100644 --- a/testing/constraints-3.10.txt +++ b/testing/constraints-3.10.txt @@ -1,124 +1,15 @@ -# Please keep these in sync with the minimum versions in setup.py -cloudpickle==2.0.0 -fsspec==2023.3.0 -gcsfs==2023.3.0 -geopandas==0.12.2 -google-auth==2.15.0 -google-cloud-bigtable==2.30.0 -google-cloud-pubsub==2.29.0 -google-cloud-bigquery==3.36.0 -google-cloud-functions==1.20.2 -google-cloud-bigquery-connection==1.18.2 -google-cloud-iam==2.18.2 -google-cloud-resource-manager==1.14.2 -google-cloud-storage==2.0.0 -grpc-google-iam-v1==0.14.2 -numpy==1.24.0 +# Keep in sync with colab/containers/requirements.core.in image +google-auth==2.17.3 +ipykernel==5.5.6 +ipython==7.34.0 +notebook==6.4.8 pandas==1.5.3 -pandas-gbq==0.26.1 -pyarrow==23.0.1 -pydata-google-auth==1.8.2 -pyiceberg==0.7.1 +portpicker==1.3.9 requests==2.27.1 -scikit-learn==1.2.2 -shapely==1.8.5 -tabulate==0.9 -humanize==4.6.0 +tornado==6.3.1 +absl-py==1.4.0 +debugpy==1.6.6 +ipywidgets==7.7.1 matplotlib==3.7.1 -db-dtypes==1.4.2 -# For vendored ibis-framework. -atpublic==2.3 -python-dateutil==2.8.2 -pytz==2022.7 -toolz==0.11 -typing-extensions==4.6.1 -rich==12.4.4 -# For anywidget mode -anywidget>=0.9.18 -traitlets==5.0.0 -# constrained dependencies to give pip a helping hand -aiohappyeyeballs==2.6.1 -aiohttp==3.13.3 -aiosignal==1.4.0 -anywidget==0.9.21 -asttokens==3.0.1 -async-timeout==5.0.1 -attrs==25.4.0 -cachetools==5.5.2 -certifi==2026.1.4 -charset-normalizer==2.0.12 -click==8.3.1 -click-plugins==1.1.1.2 -cligj==0.7.2 -comm==0.2.3 -commonmark==0.9.1 -contourpy==1.3.2 -coverage==7.13.3 -cycler==0.12.1 -db-dtypes==1.4.2 -decorator==5.2.1 -exceptiongroup==1.2.2 -executing==2.2.1 -fiona==1.10.1 -fonttools==4.61.1 -freezegun==1.5.5 -frozenlist==1.8.0 -google-api-core==2.29.0 -google-auth-oauthlib==1.2.4 -google-cloud-bigquery-storage==2.36.0 -google-cloud-core==2.5.0 -google-crc32c==1.8.0 -google-resumable-media==2.8.0 -googleapis-common-protos==1.72.0 -grpc-google-iam-v1==0.14.2 -grpcio==1.74.0 -grpcio-status==1.62.3 -idna==3.11 -iniconfig2.3.0 -ipython==8.21.0 -ipython-genutils==0.2.0 -ipywidgets==8.1.8 -jedi==0.19.2 -joblib==1.5.3 -jupyterlab_widgets==3.0.16 -kiwisolver==1.4.9 -matplotlib-inline==0.2.1 -mock==5.2.0 -moc==5.2.0 -multidict==6.7.1 -oauthlib==3.3.1 -packaging==26.0 -parso==0.8.5 -pexpect==4.9.0 -pillow==12.1.0 -pluggy==1.6.0 -prompt_toolkit==3.0.52 -propcache==0.4.1 -proto-plus==1.27.1 -protobuf==6.33.5 -psygnal==0.15.1 -ptyprocess==0.7.0 -pure_eval==0.2.3 -pyasn1==0.6.2 -pyasn1_modules==0.4.2 -Pygments==2.19.2 -pyparsing==3.3.2 -pyproj==3.7.1 -pytest==8.4.2 -pytest-cov==7.0.0 -pytest-snapshot==0.9.0 -pytest-timeout==2.4.0 -python-dateutil==2.8.2 -requests-oauthlib==2.0.0 -rsa==4.9.1 -scipy==1.15.3 -setuptools==80.9.0 -six==1.17.0 -stack-data==0.6.3 -threadpoolctl==3.6.0 -tomli==2.4.0 -urllib3==1.26.20 -wcwidth==0.6.0 -wheel==0.45.1 -widgetsnbextension==4.0.15 -yarl==1.22.0 +psutil==5.9.5 +traitlets==5.7.1 diff --git a/testing/constraints-3.11.txt b/testing/constraints-3.11.txt index 17854fda96f..e69de29bb2d 100644 --- a/testing/constraints-3.11.txt +++ b/testing/constraints-3.11.txt @@ -1,620 +0,0 @@ -# Keep in sync with %pip freeze in colab. -# Note: These are just constraints, so it's ok to have extra packages we -# aren't installing, except in the version that gets used for prerelease -# tests. -absl-py==1.4.0 -accelerate==1.9.0 -aiofiles==24.1.0 -aiohappyeyeballs==2.6.1 -aiohttp==3.12.15 -aiosignal==1.4.0 -alabaster==1.0.0 -albucore==0.0.24 -albumentations==2.0.8 -ale-py==0.11.2 -altair==5.5.0 -annotated-types==0.7.0 -antlr4-python3-runtime==4.9.3 -anyio==4.10.0 -anywidget==0.9.18 -argon2-cffi==25.1.0 -argon2-cffi-bindings==25.1.0 -array_record==0.7.2 -arviz==0.22.0 -astropy==7.1.0 -astropy-iers-data==0.2025.8.4.0.42.59 -astunparse==1.6.3 -atpublic==5.1 -attrs==25.3.0 -audioread==3.0.1 -autograd==1.8.0 -babel==2.17.0 -backcall==0.2.0 -backports.tarfile==1.2.0 -beautifulsoup4==4.13.4 -betterproto==2.0.0b6 -bigquery-magics==0.10.2 -bleach==6.2.0 -blinker==1.9.0 -blis==1.3.0 -blobfile==3.0.0 -blosc2==3.6.1 -bokeh==3.7.3 -Bottleneck==1.4.2 -bqplot==0.12.45 -branca==0.8.1 -Brotli==1.1.0 -build==1.3.0 -CacheControl==0.14.3 -cachetools==5.5.2 -catalogue==2.0.10 -certifi==2025.8.3 -cffi==1.17.1 -chardet==5.2.0 -charset-normalizer==3.4.2 -chex==0.1.90 -clarabel==0.11.1 -click==8.2.1 -cloudpathlib==0.21.1 -cloudpickle==3.1.1 -cmake==3.31.6 -cmdstanpy==1.2.5 -colorcet==3.1.0 -colorlover==0.3.0 -colour==0.1.5 -community==1.0.0b1 -confection==0.1.5 -cons==0.4.7 -contourpy==1.3.3 -cramjam==2.11.0 -cryptography==43.0.3 -cuda-python==12.6.2.post1 -cudf-polars-cu12==25.6.0 -cufflinks==0.17.3 -cuml-cu12==25.6.0 -cupy-cuda12x==13.3.0 -curl_cffi==0.12.0 -cuvs-cu12==25.6.1 -cvxopt==1.3.2 -cvxpy==1.6.7 -cycler==0.12.1 -cyipopt==1.5.0 -cymem==2.0.11 -Cython==3.0.12 -dask==2025.5.0 -dask-cuda==25.6.0 -dask-cudf-cu12==25.6.0 -dataproc-spark-connect==0.8.3 -datasets==4.0.0 -db-dtypes==1.4.3 -dbus-python==1.2.18 -debugpy==1.8.15 -decorator==4.4.2 -defusedxml==0.7.1 -diffusers==0.34.0 -dill==0.3.8 -distributed==2025.5.0 -distributed-ucxx-cu12==0.44.0 -distro==1.9.0 -dlib==19.24.6 -dm-tree==0.1.9 -docstring_parser==0.17.0 -docutils==0.21.2 -dopamine_rl==4.1.2 -duckdb==1.3.2 -earthengine-api==1.5.24 -easydict==1.13 -editdistance==0.8.1 -eerepr==0.1.2 -einops==0.8.1 -entrypoints==0.4 -et_xmlfile==2.0.0 -etils==1.13.0 -etuples==0.3.10 -Farama-Notifications==0.0.4 -fastai==2.7.19 -fastapi==0.116.1 -fastcore==1.7.29 -fastdownload==0.0.7 -fastjsonschema==2.21.1 -fastprogress==1.0.3 -fastrlock==0.8.3 -ffmpy==0.6.1 -filelock==3.18.0 -firebase-admin==6.9.0 -Flask==3.1.1 -flatbuffers==25.2.10 -flax==0.10.6 -folium==0.20.0 -fonttools==4.59.0 -frozendict==2.4.6 -frozenlist==1.7.0 -fsspec==2025.3.0 -future==1.0.0 -gast==0.6.0 -gcsfs==2025.3.0 -GDAL==3.13.1 -gdown==5.2.0 -geemap==0.35.3 -geocoder==1.38.1 -geographiclib==2.0 -geopandas==1.1.1 -geopy==2.4.1 -gin-config==0.5.0 -gitdb==4.0.12 -GitPython==3.1.45 -glob2==0.7 -google==2.0.3 -google-ai-generativelanguage==0.6.17 -google-api-core==2.25.1 -google-api-python-client==2.177.0 -google-auth==2.38.0 -google-auth-httplib2==0.2.0 -google-auth-oauthlib==1.2.2 -google-cloud-aiplatform==1.106.0 -google-cloud-bigquery==3.36.0 -google-cloud-bigquery-connection==1.18.3 -google-cloud-bigquery-storage==2.32.0 -google-cloud-core==2.4.3 -google-cloud-dataproc==5.21.0 -google-cloud-datastore==2.21.0 -google-cloud-firestore==2.21.0 -google-cloud-functions==1.20.4 -google-cloud-language==2.17.2 -google-cloud-resource-manager==1.14.2 -google-cloud-spanner==3.56.0 -google-cloud-storage==2.19.0 -google-cloud-translate==3.21.1 -google-crc32c==1.7.1 -google-genai==1.28.0 -google-generativeai==0.8.5 -google-pasta==0.2.0 -google-resumable-media==2.7.2 -googleapis-common-protos==1.70.0 -googledrivedownloader==1.1.0 -gradio==6.15.1 -gradio_client==1.11.0 -graphviz==0.21 -greenlet==3.2.3 -groovy==0.1.2 -grpc-google-iam-v1==0.14.2 -grpc-interceptor==0.15.4 -grpcio==1.74.0 -grpcio-status==1.72.1 -grpclib==0.4.8 -gspread==6.2.1 -gspread-dataframe==4.0.0 -gym==0.25.2 -gym-notices==0.1.0 -gymnasium==1.2.0 -h11==0.16.0 -h2==4.2.0 -h5netcdf==1.6.3 -h5py==3.14.0 -hdbscan==0.8.40 -hf-xet==1.1.5 -hf_transfer==0.1.9 -highspy==1.11.0 -holidays==0.78 -holoviews==1.21.0 -hpack==4.1.0 -html5lib==1.1 -httpcore==1.0.9 -httpimport==1.4.1 -httplib2==0.22.0 -httpx==0.28.1 -huggingface-hub==0.34.3 -humanize==4.12.3 -hyperframe==6.1.0 -hyperopt==0.2.7 -ibis-framework==9.5.0 -idna==3.10 -imageio==2.37.0 -imageio-ffmpeg==0.6.0 -imagesize==1.4.1 -imbalanced-learn==0.13.0 -immutabledict==4.2.1 -importlib_metadata==8.7.0 -importlib_resources==6.5.2 -imutils==0.5.4 -inflect==7.5.0 -iniconfig==2.1.0 -intel-cmplr-lib-ur==2025.2.0 -intel-openmp==2025.2.0 -ipyevents==2.0.2 -ipyfilechooser==0.6.0 -ipykernel==6.17.1 -ipyleaflet==0.20.0 -ipyparallel==8.8.0 -ipython==7.34.0 -ipython-genutils==0.2.0 -ipython-sql==0.5.0 -ipytree==0.2.2 -ipywidgets==7.7.1 -itsdangerous==2.2.0 -jaraco.classes==3.4.0 -jaraco.context==6.0.1 -jaraco.functools==4.2.1 -jax==0.5.3 -jax-cuda12-pjrt==0.5.3 -jax-cuda12-plugin==0.5.3 -jaxlib==0.5.3 -jeepney==0.9.0 -jieba==0.42.1 -Jinja2==3.1.6 -jiter==0.10.0 -joblib==1.5.1 -jsonpatch==1.33 -jsonpickle==4.1.1 -jsonpointer==3.0.0 -jsonschema==4.25.0 -jsonschema-specifications==2025.4.1 -jupyter-client==6.1.12 -jupyter-console==6.1.0 -jupyter-leaflet==0.20.0 -jupyter-server==1.16.0 -jupyter_core==5.8.1 -jupyterlab_pygments==0.3.0 -jupyterlab_widgets==3.0.15 -jupytext==1.17.2 -kaggle==1.7.4.5 -kagglehub==0.3.12 -keras==3.10.0 -keras-hub==0.21.1 -keras-nlp==0.21.1 -keyring==25.6.0 -keyrings.google-artifactregistry-auth==1.1.2 -kiwisolver==1.4.8 -langchain==0.3.27 -langchain-core==0.3.72 -langchain-text-splitters==0.3.9 -langcodes==3.5.0 -langsmith==0.8.18 -language_data==1.3.0 -launchpadlib==1.10.16 -lazr.restfulclient==0.14.4 -lazr.uri==1.0.6 -lazy_loader==0.4 -libclang==18.1.1 -libcugraph-cu12==25.6.0 -libcuml-cu12==25.6.0 -libcuvs-cu12==25.6.1 -libkvikio-cu12==25.6.0 -libpysal==4.13.0 -libraft-cu12==25.6.0 -librmm-cu12==25.6.0 -librosa==0.11.0 -libucx-cu12==1.18.1 -libucxx-cu12==0.44.0 -linkify-it-py==2.0.3 -llvmlite==0.43.0 -locket==1.0.0 -logical-unification==0.4.6 -lxml==5.4.0 -Mako==1.1.3 -marisa-trie==1.2.1 -Markdown==3.8.2 -markdown-it-py==3.0.0 -MarkupSafe==3.0.2 -matplotlib==3.10.0 -matplotlib-inline==0.1.7 -matplotlib-venn==1.1.2 -mdit-py-plugins==0.4.2 -mdurl==0.1.2 -miniKanren==1.0.5 -missingno==0.5.2 -mistune==3.3.0 -mizani==0.13.5 -mkl==2025.2.0 -ml_dtypes==0.5.3 -mlxtend==0.23.4 -more-itertools==10.7.0 -moviepy==1.0.3 -mpmath==1.3.0 -msgpack==1.2.1 -multidict==6.6.3 -multipledispatch==1.0.0 -multiprocess==0.70.16 -multitasking==0.0.12 -murmurhash==1.0.13 -music21==9.3.0 -namex==0.1.0 -narwhals==2.0.1 -natsort==8.4.0 -nbclassic==1.3.1 -nbclient==0.10.2 -nbconvert==7.16.6 -nbformat==5.10.4 -ndindex==1.10.0 -nest-asyncio==1.6.0 -networkx==3.5 -nibabel==5.3.2 -nltk==3.9.1 -notebook==6.5.7 -notebook_shim==0.2.4 -numba==0.60.0 -numba-cuda==0.11.0 -numexpr==2.11.0 -numpy==2.0.2 -nvidia-cublas-cu12==12.5.3.2 -nvidia-cuda-cupti-cu12==12.5.82 -nvidia-cuda-nvcc-cu12==12.5.82 -nvidia-cuda-nvrtc-cu12==12.5.82 -nvidia-cuda-runtime-cu12==12.5.82 -nvidia-cudnn-cu12==9.3.0.75 -nvidia-cufft-cu12==11.2.3.61 -nvidia-curand-cu12==10.3.6.82 -nvidia-cusolver-cu12==11.6.3.83 -nvidia-cusparse-cu12==12.5.1.3 -nvidia-cusparselt-cu12==0.6.2 -nvidia-ml-py==12.575.51 -nvidia-nccl-cu12==2.23.4 -nvidia-nvjitlink-cu12==12.5.82 -nvidia-nvtx-cu12==12.4.127 -nvtx==0.2.13 -oauth2client==4.1.3 -oauthlib==3.3.1 -omegaconf==2.3.0 -openai==1.98.0 -opencv-contrib-python==4.12.0.88 -opencv-python==4.12.0.88 -opencv-python-headless==4.12.0.88 -openpyxl==3.1.5 -opt_einsum==3.4.0 -optax==0.2.5 -optree==0.17.0 -orbax-checkpoint==0.11.20 -orjson==3.11.1 -osqp==1.0.4 -packaging==25.0 -pandas==2.2.2 -pandas-datareader==0.10.0 -pandas-gbq==0.29.2 -pandas-stubs==2.2.2.240909 -pandocfilters==1.5.1 -panel==1.7.5 -param==2.2.1 -parso==0.8.4 -parsy==2.1 -partd==1.4.2 -patsy==1.0.1 -peewee==3.18.2 -peft==0.17.0 -pexpect==4.9.0 -pickleshare==0.7.5 -pillow==11.3.0 -platformdirs==4.3.8 -plotly==5.24.1 -plotnine==0.14.5 -pluggy==1.6.0 -ply==3.11 -polars==1.25.2 -pooch==1.8.2 -portpicker==1.5.2 -preshed==3.0.10 -prettytable==3.16.0 -proglog==0.1.12 -progressbar2==4.5.0 -prometheus_client==0.22.1 -promise==2.3 -prompt_toolkit==3.0.51 -propcache==0.3.2 -prophet==1.1.7 -proto-plus==1.26.1 -protobuf==6.33.5 -psutil==5.9.5 -psycopg2==2.9.10 -psygnal==0.14.0 -ptyprocess==0.7.0 -py-cpuinfo==9.0.0 -py4j==0.10.9.7 -pyarrow==23.0.1 -pyasn1==0.6.1 -pyasn1_modules==0.4.2 -pycairo==1.28.0 -pycocotools==2.0.10 -pycparser==2.22 -pycryptodomex==3.23.0 -pydantic==2.11.7 -pydantic_core==2.33.2 -pydata-google-auth==1.9.1 -pydot==3.0.4 -pydotplus==2.0.2 -PyDrive2==1.21.3 -pydub==0.25.1 -pyerfa==2.0.1.5 -pygame==2.6.1 -pygit2==1.18.1 -Pygments==2.19.2 -PyGObject==3.42.0 -PyJWT==2.10.1 -pylibcugraph-cu12==25.6.0 -pylibraft-cu12==25.6.0 -pymc==5.25.1 -pynndescent==0.5.13 -pynvjitlink-cu12==0.7.0 -pynvml==12.0.0 -pyogrio==0.11.1 -pyomo==6.9.2 -PyOpenGL==3.1.9 -pyOpenSSL==24.2.1 -pyparsing==3.2.3 -pyperclip==1.9.0 -pyproj==3.7.1 -pyproject_hooks==1.2.0 -pyshp==2.3.1 -PySocks==1.7.1 -pyspark==3.5.2 -pytensor==2.31.7 -python-apt==0.0.0 -python-box==7.3.2 -python-dateutil==2.9.0.post0 -python-louvain==0.16 -python-multipart==0.0.20 -python-slugify==8.0.4 -python-snappy==0.7.3 -python-utils==3.9.1 -pytz==2025.2 -pyviz_comms==3.0.6 -PyWavelets==1.9.0 -PyYAML==6.0.2 -pyzmq==26.2.1 -raft-dask-cu12==25.6.0 -rapids-dask-dependency==25.6.0 -rapids-logger==0.1.1 -ratelim==0.1.6 -referencing==0.36.2 -regex==2024.11.6 -requests==2.32.3 -requests-oauthlib==2.0.0 -requests-toolbelt==1.0.0 -requirements-parser==0.9.0 -rich==13.9.4 -rmm-cu12==25.6.0 -roman-numerals-py==3.1.0 -rpds-py==0.26.0 -rpy2==3.5.17 -rsa==4.9.1 -ruff==0.12.7 -safehttpx==0.1.6 -safetensors==0.5.3 -scikit-image==0.25.2 -scikit-learn==1.6.1 -scipy==1.16.1 -scooby==0.10.1 -scs==3.2.7.post2 -seaborn==0.13.2 -SecretStorage==3.3.3 -semantic-version==2.10.0 -Send2Trash==1.8.3 -sentence-transformers==4.1.0 -sentencepiece==0.2.0 -sentry-sdk==2.34.1 -shap==0.48.0 -shapely==2.1.1 -shellingham==1.5.4 -simple-parsing==0.1.7 -simplejson==3.20.1 -simsimd==6.5.0 -six==1.17.0 -sklearn-compat==0.1.3 -sklearn-pandas==2.2.0 -slicer==0.0.8 -smart_open==7.3.0.post1 -smmap==5.0.2 -sniffio==1.3.1 -snowballstemmer==3.0.1 -sortedcontainers==2.4.0 -soundfile==0.13.1 -soupsieve==2.8.4 -soxr==0.5.0.post1 -spacy==3.8.7 -spacy-legacy==3.0.12 -spacy-loggers==1.0.5 -spanner-graph-notebook==1.1.7 -Sphinx==8.2.3 -sphinxcontrib-applehelp==2.0.0 -sphinxcontrib-devhelp==2.0.0 -sphinxcontrib-htmlhelp==2.1.0 -sphinxcontrib-jsmath==1.0.1 -sphinxcontrib-qthelp==2.0.0 -sphinxcontrib-serializinghtml==2.0.0 -SQLAlchemy==2.0.42 -sqlparse==0.5.3 -srsly==2.5.1 -stanio==0.5.1 -starlette==0.47.2 -statsmodels==0.14.5 -stringzilla==3.12.5 -stumpy==1.13.0 -sympy==1.13.1 -tables==3.10.2 -tabulate==0.9.0 -tbb==2022.2.0 -tblib==3.1.0 -tcmlib==1.4.0 -tenacity==8.5.0 -tensorboard==2.19.0 -tensorboard-data-server==0.7.2 -tensorflow==2.19.0 -tensorflow-datasets==4.9.9 -tensorflow-hub==0.16.1 -tensorflow-io-gcs-filesystem==0.37.1 -tensorflow-metadata==1.17.2 -tensorflow-probability==0.25.0 -tensorflow-text==2.19.0 -tensorflow_decision_forests==1.12.0 -tensorstore==0.1.76 -termcolor==3.1.0 -terminado==0.18.1 -text-unidecode==1.3 -textblob==0.19.0 -tf-slim==1.1.0 -tf_keras==2.19.0 -thinc==8.3.6 -threadpoolctl==3.6.0 -tifffile==2025.6.11 -tiktoken==0.9.0 -timm==1.0.19 -tinycss2==1.4.0 -tokenizers==0.21.4 -toml==0.10.2 -tomlkit==0.13.3 -toolz==0.12.1 -torchao==0.10.0 -torchdata==0.11.0 -torchsummary==1.5.1 -torchtune==0.6.1 -tornado==6.4.2 -tqdm==4.67.1 -traitlets==5.7.1 -traittypes==0.2.1 -transformers==5.5.0 -treelite==4.4.1 -treescope==0.1.9 -triton==3.2.0 -tsfresh==0.21.0 -tweepy==4.16.0 -typeguard==4.4.4 -typer==0.16.0 -types-pytz==2025.2.0.20250516 -types-setuptools==80.9.0.20250801 -typing-inspection==0.4.1 -typing_extensions==4.14.1 -tzdata==2025.2 -tzlocal==5.3.1 -uc-micro-py==1.0.3 -ucx-py-cu12==0.44.0 -ucxx-cu12==0.44.0 -umap-learn==0.5.9.post2 -umf==0.11.0 -uritemplate==4.2.0 -urllib3==2.5.0 -uvicorn==0.35.0 -vega-datasets==0.9.0 -wadllib==1.3.6 -wandb==0.21.0 -wasabi==1.1.3 -wcwidth==0.2.13 -weasel==0.4.1 -webcolors==24.11.1 -webencodings==0.5.1 -websocket-client==1.8.0 -websockets==15.0.1 -Werkzeug==3.1.3 -widgetsnbextension==3.6.10 -wordcloud==1.9.4 -wrapt==1.17.2 -wurlitzer==3.1.1 -xarray==2025.7.1 -xarray-einstats==0.9.1 -xgboost==3.0.3 -xlrd==2.0.2 -xxhash==3.5.0 -xyzservices==2025.4.0 -yarl==1.20.1 -ydf==0.13.0 -yellowbrick==1.5 -yfinance==0.2.65 -zict==3.0.0 -zipp==3.23.0 diff --git a/testing/constraints-3.12.txt b/testing/constraints-3.12.txt deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/testing/constraints-3.13.txt b/testing/constraints-3.13.txt deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/testing/constraints-3.14.txt b/testing/constraints-3.14.txt deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/testing/constraints-3.15.txt b/testing/constraints-3.15.txt deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/testing/constraints-3.9.txt b/testing/constraints-3.9.txt new file mode 100644 index 00000000000..f43d3b4ca01 --- /dev/null +++ b/testing/constraints-3.9.txt @@ -0,0 +1,121 @@ +argcomplete==2.1.2 +asyncmock==0.4.2 +atpublic==3.1.1 +attrs==22.2.0 +bidict==0.22.1 +black==23.3.0 +bleach==6.0.0 +cachetools==5.3.0 +certifi==2022.12.7 +cffi==1.15.1 +cfgv==3.3.1 +charset-normalizer==2.0.0 +click==8.1.3 +cloudpickle==2.0.0 +colorlog==6.7.0 +coverage==7.2.2 +cryptography==40.0.1 +distlib==0.3.6 +docstring-inheritance==2.0.0 +docutils==0.19 +exceptiongroup==1.1.1 +execnet==1.9.0 +filelock==3.10.7 +fsspec==2023.3.0 +gcp-docuploader==0.6.5 +gcp-releasetool==1.11.0 +gcsfs==2023.3.0 +geopandas==0.12.2 +google-api-core==2.11.0 +google-auth==2.17.0 +google-auth-oauthlib==1.0.0 +google-cloud-bigquery==3.10.0 +google-cloud-bigquery-connection==1.12.0 +google-cloud-bigquery-storage==2.19.1 +google-cloud-core==2.3.2 +google-cloud-functions==1.10.1 +google-cloud-iam==2.12.1 +google-cloud-resource-manager==1.10.3 +google-cloud-storage==2.0.0 +google-cloud-testutils==1.3.3 +google-crc32c==1.5.0 +google-resumable-media==2.4.1 +googleapis-common-protos==1.59.0 +greenlet==2.0.2 +grpc-google-iam-v1==0.12.6 +grpcio==1.53.0 +grpcio-status==1.48.2 +ibis-framework==6.2.0 +humanize==4.6.0 +identify==2.5.22 +idna==3.4 +importlib-metadata==6.1.0 +iniconfig==2.0.0 +ipywidgets==7.7.1 +jaraco.classes==3.2.3 +jeepney==0.8.0 +Jinja2==3.1.2 +keyring==23.13.1 +markdown-it-py==2.2.0 +MarkupSafe==2.1.2 +mdurl==0.1.2 +mock==5.0.1 +more-itertools==9.1.0 +multipledispatch==0.6.0 +mypy-extensions==1.0.0 +nodeenv==1.7.0 +nox==2022.11.21 +numpy==1.24.2 +oauthlib==3.2.2 +packaging==23.0 +pandas==1.5.0 +pandas-gbq==0.19.0 +parsy==2.1 +pathspec==0.11.1 +pkginfo==1.9.6 +platformdirs==3.2.0 +pluggy==1.0.0 +pooch==1.7.0 +pre-commit==3.2.1 +proto-plus==1.22.2 +protobuf==3.20.3 +pyarrow==11.0.0 +pyasn1==0.4.8 +pyasn1-modules==0.2.8 +pycparser==2.21 +pydata-google-auth==1.8.2 +Pygments==2.14.0 +PyJWT==2.6.0 +pyperclip==1.8.2 +pytest==7.2.2 +pytest-asyncio==0.21.0 +pytest-cov==4.0.0 +pytest-mock==3.11.1 +pytest-retry==1.1.0 +pytest-xdist==3.2.1 +python-dateutil==2.8.2 +pytz==2023.3 +PyYAML==6.0 +readme-renderer==37.3 +requests==2.27.1 +requests-oauthlib==1.3.1 +requests-toolbelt==0.10.1 +rfc3986==2.0.0 +rich==13.3.3 +rsa==4.9 +scikit-learn==1.2.2 +SecretStorage==3.3.3 +six==1.16.0 +SQLAlchemy==1.4.0 +sqlglot==10.6.4 +tomli==2.0.1 +toolz==0.12.0 +tqdm==4.65.0 +twine==4.0.2 +typing_extensions==4.5.0 +tzdata==2023.3 +urllib3==1.26.15 +virtualenv==20.21.0 +webencodings==0.5.1 +xxhash==3.2.0 +zipp==3.15.0 diff --git a/tests/benchmark/.gitignore b/tests/benchmark/.gitignore deleted file mode 100644 index f1bf042bf75..00000000000 --- a/tests/benchmark/.gitignore +++ /dev/null @@ -1,6 +0,0 @@ -*.bytesprocessed -*.bq_exec_time_seconds -*.error -*.local_exec_time_seconds -*.query_char_count -*.slotmillis diff --git a/tests/benchmark/README.md b/tests/benchmark/README.md deleted file mode 100644 index e5b75855143..00000000000 --- a/tests/benchmark/README.md +++ /dev/null @@ -1,83 +0,0 @@ -# BigFrames Benchmarking -## Overview -This directory contains scripts for performance benchmarking of various components of BigFrames. - -## Why Separate Processes? -Each benchmark is executed in a separate process to mitigate the effects of any residual caching or settings that may persist in BigFrames, ensuring that each test is conducted in a clean state. - -## Available Benchmarks -This section lists the benchmarks currently available, with descriptions and links to their sources: -- **DB Benchmark**: This benchmark is adapted from DuckDB Labs and is designed to assess database performance. More information can be found on the [official DB Benchmark GitHub page](https://github.com/duckdblabs/db-benchmark). -- **TPC-H Benchmark**: Based on the TPC-H standards, this benchmark evaluates transaction processing capabilities. It is adapted from code found in the Polars repository, specifically tailored to test and compare these capabilities. Details are available on the [Polars Benchmark GitHub repository](https://github.com/pola-rs/polars-benchmark). -- **Notebooks**: These Jupyter notebooks showcase BigFrames' key features and patterns, and also enable performance benchmarking. Explore them at the [BigFrames Notebooks repository](https://github.com/googleapis/python-bigquery-dataframes/tree/main/notebooks). - -## Benchmark Configuration Using `config.jsonl` Files - -For each benchmark, a corresponding `config.jsonl` file exists in the same folder or its parent folder. These configuration files allow users to control various benchmark parameters without modifying the code directly. By updating the relevant `config.jsonl` file in the specific benchmark's folder, you can easily configure settings such as: -- **benchmark_suffix**: A suffix appended to the benchmark name for identification purposes. -- **ordered**: Controls the mode for BigFrames, specifying whether to use ordered (`true`) or unordered mode (`false`). -- **project_id**: The Google Cloud project ID where the benchmark dataset or table is located. -- **dataset_id**: The dataset ID for querying during the benchmark. -- **table_id**: This is **required** for benchmarks like `dbbenchmark` that target a specific table, but is **not configurable** for benchmarks like `TPC-H`, which use multiple tables with fixed names. - -### Example `config.jsonl` Files - -#### `dbbenchmark` Example -```jsonl -{"benchmark_suffix": "50g_ordered", "project_id": "your-google-cloud-project", "dataset_id": "dbbenchmark", "table_id": "G1_1e9_1e2_5_0", "ordered": true} -{"benchmark_suffix": "50g_unordered", "project_id": "your-google-cloud-project", "dataset_id": "dbbenchmark", "table_id": "G1_1e9_1e2_5_0", "ordered": false} -``` - -#### `TPC-H` Example -```jsonl -{"benchmark_suffix": "10t_unordered", "project_id": "your-google-cloud-project", "dataset_id": "tpch_0010t", "ordered": false} -``` - -## Usage Examples -Our benchmarking process runs internally on a daily basis to continuously monitor the performance of BigFrames. However, there are occasions when you might need to conduct benchmarking locally to test specific changes or new features. - -Here's how you can run benchmarks locally: - -- **Running Notebook Benchmarks**: To execute all notebook benchmarks, use the following command: - ```bash - nox -r -s notebook - ``` - - This command runs all the Jupyter notebooks in the repository as benchmarks. -- **Running Pure Benchmarks**: For executing more traditional benchmarks that do not involve notebooks, use: - ```bash - nox -r -s benchmark - ``` - This will run all the non-notebook benchmarks specified in the repository. - -- **Saving Results**: By default, when run locally, each benchmark concludes by printing a summary of the results, which are not saved automatically. To save the results to a CSV file, you can use the --output-csv or -o option followed by a specific path. If no path is specified, the results will be saved to a temporary location, and the path to this location will be printed at the end of the benchmark. - ```bash - nox -r -s benchmark -- --output-csv path/to/your/results.csv - nox -r -s benchmark -- --output-csv - ``` - -- **Running Multiple Iterations**: To run a benchmark multiple times and obtain an average result, use the -i or --iterations option followed by the number of iterations: - ```bash - nox -r -s benchmark -- --iterations 5 - ``` - -- **Filtering Benchmarks**: If you want to run only specific benchmarks, such as TPC-H, or specific queries within a benchmark, like tpch/q1, you can use the --benchmark-filter or -b option followed by the folder, file name, or both: - ```bash - # Runs all benchmarks in the 'tpch' directory - nox -r -s benchmark -- --benchmark-filter tpch - - # Runs all benchmarks in 'db_benchmark' and specific queries q1 and q2 from TPC-H - nox -r -s benchmark -- --benchmark-filter db_benchmark tpch/q1.py tpch/q2.py - ``` -- **Uploading Results to BigQuery**: To upload benchmark results to BigQuery, set the environment variable GCLOUD_BENCH_PUBLISH_PROJECT to the Google Cloud project where you want to store the results. This enables automatic uploading of the benchmark data to your specified project in BigQuery: - ```bash - export GCLOUD_BENCH_PUBLISH_PROJECT='your-google-cloud-project-id' - - # Run all non-notebook benchmarks and uploads the results to - # your-google-cloud-project-id.benchmark_report.benchmark - nox -r -s benchmark - - # Run all notebook benchmarks and uploads the results to - # your-google-cloud-project-id.benchmark_report.notebook_benchmark - nox -r -s notebook - ``` diff --git a/tests/benchmark/__init__.py b/tests/benchmark/__init__.py deleted file mode 100644 index 6d5e14bcf4a..00000000000 --- a/tests/benchmark/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -# 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. -# 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/benchmark/db_benchmark/groupby/config.jsonl b/tests/benchmark/db_benchmark/groupby/config.jsonl deleted file mode 100644 index b6f23ebbf78..00000000000 --- a/tests/benchmark/db_benchmark/groupby/config.jsonl +++ /dev/null @@ -1,2 +0,0 @@ -{"benchmark_suffix": "50g_ordered", "project_id": "bigframes-dev-perf", "dataset_id": "dbbenchmark", "table_id": "G1_1e9_1e2_5_0", "ordered": true} -{"benchmark_suffix": "50g_unordered", "project_id": "bigframes-dev-perf", "dataset_id": "dbbenchmark", "table_id": "G1_1e9_1e2_5_0", "ordered": false} diff --git a/tests/benchmark/db_benchmark/groupby/q1.py b/tests/benchmark/db_benchmark/groupby/q1.py deleted file mode 100644 index 0051ed5b59c..00000000000 --- a/tests/benchmark/db_benchmark/groupby/q1.py +++ /dev/null @@ -1,32 +0,0 @@ -# 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. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import pathlib - -import benchmark.utils as utils -import bigframes_vendored.db_benchmark.groupby_queries as vendored_dbbenchmark_groupby_queries - -if __name__ == "__main__": - config = utils.get_configuration(include_table_id=True) - current_path = pathlib.Path(__file__).absolute() - - utils.get_execution_time( - vendored_dbbenchmark_groupby_queries.q1, - current_path, - config.benchmark_suffix, - config.project_id, - config.dataset_id, - config.table_id, - config.session, - ) diff --git a/tests/benchmark/db_benchmark/groupby/q10.py b/tests/benchmark/db_benchmark/groupby/q10.py deleted file mode 100644 index 08ca9a7fe48..00000000000 --- a/tests/benchmark/db_benchmark/groupby/q10.py +++ /dev/null @@ -1,32 +0,0 @@ -# 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. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import pathlib - -import benchmark.utils as utils -import bigframes_vendored.db_benchmark.groupby_queries as vendored_dbbenchmark_groupby_queries - -if __name__ == "__main__": - config = utils.get_configuration(include_table_id=True) - current_path = pathlib.Path(__file__).absolute() - - utils.get_execution_time( - vendored_dbbenchmark_groupby_queries.q10, - current_path, - config.benchmark_suffix, - config.project_id, - config.dataset_id, - config.table_id, - config.session, - ) diff --git a/tests/benchmark/db_benchmark/groupby/q2.py b/tests/benchmark/db_benchmark/groupby/q2.py deleted file mode 100644 index 5b3b6839310..00000000000 --- a/tests/benchmark/db_benchmark/groupby/q2.py +++ /dev/null @@ -1,32 +0,0 @@ -# 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. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import pathlib - -import benchmark.utils as utils -import bigframes_vendored.db_benchmark.groupby_queries as vendored_dbbenchmark_groupby_queries - -if __name__ == "__main__": - config = utils.get_configuration(include_table_id=True) - current_path = pathlib.Path(__file__).absolute() - - utils.get_execution_time( - vendored_dbbenchmark_groupby_queries.q2, - current_path, - config.benchmark_suffix, - config.project_id, - config.dataset_id, - config.table_id, - config.session, - ) diff --git a/tests/benchmark/db_benchmark/groupby/q3.py b/tests/benchmark/db_benchmark/groupby/q3.py deleted file mode 100644 index 97d005fbf47..00000000000 --- a/tests/benchmark/db_benchmark/groupby/q3.py +++ /dev/null @@ -1,32 +0,0 @@ -# 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. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import pathlib - -import benchmark.utils as utils -import bigframes_vendored.db_benchmark.groupby_queries as vendored_dbbenchmark_groupby_queries - -if __name__ == "__main__": - config = utils.get_configuration(include_table_id=True) - current_path = pathlib.Path(__file__).absolute() - - utils.get_execution_time( - vendored_dbbenchmark_groupby_queries.q3, - current_path, - config.benchmark_suffix, - config.project_id, - config.dataset_id, - config.table_id, - config.session, - ) diff --git a/tests/benchmark/db_benchmark/groupby/q4.py b/tests/benchmark/db_benchmark/groupby/q4.py deleted file mode 100644 index 709b2107d2d..00000000000 --- a/tests/benchmark/db_benchmark/groupby/q4.py +++ /dev/null @@ -1,32 +0,0 @@ -# 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. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import pathlib - -import benchmark.utils as utils -import bigframes_vendored.db_benchmark.groupby_queries as vendored_dbbenchmark_groupby_queries - -if __name__ == "__main__": - config = utils.get_configuration(include_table_id=True) - current_path = pathlib.Path(__file__).absolute() - - utils.get_execution_time( - vendored_dbbenchmark_groupby_queries.q4, - current_path, - config.benchmark_suffix, - config.project_id, - config.dataset_id, - config.table_id, - config.session, - ) diff --git a/tests/benchmark/db_benchmark/groupby/q5.py b/tests/benchmark/db_benchmark/groupby/q5.py deleted file mode 100644 index 3d870b0598c..00000000000 --- a/tests/benchmark/db_benchmark/groupby/q5.py +++ /dev/null @@ -1,32 +0,0 @@ -# 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. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import pathlib - -import benchmark.utils as utils -import bigframes_vendored.db_benchmark.groupby_queries as vendored_dbbenchmark_groupby_queries - -if __name__ == "__main__": - config = utils.get_configuration(include_table_id=True) - current_path = pathlib.Path(__file__).absolute() - - utils.get_execution_time( - vendored_dbbenchmark_groupby_queries.q5, - current_path, - config.benchmark_suffix, - config.project_id, - config.dataset_id, - config.table_id, - config.session, - ) diff --git a/tests/benchmark/db_benchmark/groupby/q6.py b/tests/benchmark/db_benchmark/groupby/q6.py deleted file mode 100644 index bceb5599b2d..00000000000 --- a/tests/benchmark/db_benchmark/groupby/q6.py +++ /dev/null @@ -1,32 +0,0 @@ -# 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. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import pathlib - -import benchmark.utils as utils -import bigframes_vendored.db_benchmark.groupby_queries as vendored_dbbenchmark_groupby_queries - -if __name__ == "__main__": - config = utils.get_configuration(include_table_id=True) - current_path = pathlib.Path(__file__).absolute() - - utils.get_execution_time( - vendored_dbbenchmark_groupby_queries.q6, - current_path, - config.benchmark_suffix, - config.project_id, - config.dataset_id, - config.table_id, - config.session, - ) diff --git a/tests/benchmark/db_benchmark/groupby/q7.py b/tests/benchmark/db_benchmark/groupby/q7.py deleted file mode 100644 index 600e26bf160..00000000000 --- a/tests/benchmark/db_benchmark/groupby/q7.py +++ /dev/null @@ -1,32 +0,0 @@ -# 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. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import pathlib - -import benchmark.utils as utils -import bigframes_vendored.db_benchmark.groupby_queries as vendored_dbbenchmark_groupby_queries - -if __name__ == "__main__": - config = utils.get_configuration(include_table_id=True) - current_path = pathlib.Path(__file__).absolute() - - utils.get_execution_time( - vendored_dbbenchmark_groupby_queries.q7, - current_path, - config.benchmark_suffix, - config.project_id, - config.dataset_id, - config.table_id, - config.session, - ) diff --git a/tests/benchmark/db_benchmark/groupby/q8.py b/tests/benchmark/db_benchmark/groupby/q8.py deleted file mode 100644 index 82082bc7e5a..00000000000 --- a/tests/benchmark/db_benchmark/groupby/q8.py +++ /dev/null @@ -1,32 +0,0 @@ -# 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. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import pathlib - -import benchmark.utils as utils -import bigframes_vendored.db_benchmark.groupby_queries as vendored_dbbenchmark_groupby_queries - -if __name__ == "__main__": - config = utils.get_configuration(include_table_id=True) - current_path = pathlib.Path(__file__).absolute() - - utils.get_execution_time( - vendored_dbbenchmark_groupby_queries.q8, - current_path, - config.benchmark_suffix, - config.project_id, - config.dataset_id, - config.table_id, - config.session, - ) diff --git a/tests/benchmark/db_benchmark/join/config.jsonl b/tests/benchmark/db_benchmark/join/config.jsonl deleted file mode 100644 index e709281137d..00000000000 --- a/tests/benchmark/db_benchmark/join/config.jsonl +++ /dev/null @@ -1,2 +0,0 @@ -{"benchmark_suffix": "50g_ordered", "project_id": "bigframes-dev-perf", "dataset_id": "dbbenchmark", "table_id": "J1_1e9_NA_0_0", "ordered": true} -{"benchmark_suffix": "50g_unordered", "project_id": "bigframes-dev-perf", "dataset_id": "dbbenchmark", "table_id": "J1_1e9_NA_0_0", "ordered": false} diff --git a/tests/benchmark/db_benchmark/join/q1.py b/tests/benchmark/db_benchmark/join/q1.py deleted file mode 100644 index e9e3c2fad0d..00000000000 --- a/tests/benchmark/db_benchmark/join/q1.py +++ /dev/null @@ -1,33 +0,0 @@ -# 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. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import pathlib - -import benchmark.utils as utils -import bigframes_vendored.db_benchmark.join_queries as vendored_dbbenchmark_join_queries - -if __name__ == "__main__": - config = utils.get_configuration(include_table_id=True) - - current_path = pathlib.Path(__file__).absolute() - - utils.get_execution_time( - vendored_dbbenchmark_join_queries.q1, - current_path, - config.benchmark_suffix, - config.project_id, - config.dataset_id, - config.table_id, - config.session, - ) diff --git a/tests/benchmark/db_benchmark/join/q2.py b/tests/benchmark/db_benchmark/join/q2.py deleted file mode 100644 index f4b9f67def4..00000000000 --- a/tests/benchmark/db_benchmark/join/q2.py +++ /dev/null @@ -1,33 +0,0 @@ -# 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. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import pathlib - -import benchmark.utils as utils -import bigframes_vendored.db_benchmark.join_queries as vendored_dbbenchmark_join_queries - -if __name__ == "__main__": - config = utils.get_configuration(include_table_id=True) - - current_path = pathlib.Path(__file__).absolute() - - utils.get_execution_time( - vendored_dbbenchmark_join_queries.q2, - current_path, - config.benchmark_suffix, - config.project_id, - config.dataset_id, - config.table_id, - config.session, - ) diff --git a/tests/benchmark/db_benchmark/join/q3.py b/tests/benchmark/db_benchmark/join/q3.py deleted file mode 100644 index 83be831a46e..00000000000 --- a/tests/benchmark/db_benchmark/join/q3.py +++ /dev/null @@ -1,33 +0,0 @@ -# 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. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import pathlib - -import benchmark.utils as utils -import bigframes_vendored.db_benchmark.join_queries as vendored_dbbenchmark_join_queries - -if __name__ == "__main__": - config = utils.get_configuration(include_table_id=True) - - current_path = pathlib.Path(__file__).absolute() - - utils.get_execution_time( - vendored_dbbenchmark_join_queries.q3, - current_path, - config.benchmark_suffix, - config.project_id, - config.dataset_id, - config.table_id, - config.session, - ) diff --git a/tests/benchmark/db_benchmark/join/q4.py b/tests/benchmark/db_benchmark/join/q4.py deleted file mode 100644 index 6399683472b..00000000000 --- a/tests/benchmark/db_benchmark/join/q4.py +++ /dev/null @@ -1,33 +0,0 @@ -# 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. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import pathlib - -import benchmark.utils as utils -import bigframes_vendored.db_benchmark.join_queries as vendored_dbbenchmark_join_queries - -if __name__ == "__main__": - config = utils.get_configuration(include_table_id=True) - - current_path = pathlib.Path(__file__).absolute() - - utils.get_execution_time( - vendored_dbbenchmark_join_queries.q4, - current_path, - config.benchmark_suffix, - config.project_id, - config.dataset_id, - config.table_id, - config.session, - ) diff --git a/tests/benchmark/db_benchmark/join/q5.py b/tests/benchmark/db_benchmark/join/q5.py deleted file mode 100644 index b0b26f93652..00000000000 --- a/tests/benchmark/db_benchmark/join/q5.py +++ /dev/null @@ -1,33 +0,0 @@ -# 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. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import pathlib - -import benchmark.utils as utils -import bigframes_vendored.db_benchmark.join_queries as vendored_dbbenchmark_join_queries - -if __name__ == "__main__": - config = utils.get_configuration(include_table_id=True) - - current_path = pathlib.Path(__file__).absolute() - - utils.get_execution_time( - vendored_dbbenchmark_join_queries.q5, - current_path, - config.benchmark_suffix, - config.project_id, - config.dataset_id, - config.table_id, - config.session, - ) diff --git a/tests/benchmark/db_benchmark/sort/config.jsonl b/tests/benchmark/db_benchmark/sort/config.jsonl deleted file mode 100644 index e709281137d..00000000000 --- a/tests/benchmark/db_benchmark/sort/config.jsonl +++ /dev/null @@ -1,2 +0,0 @@ -{"benchmark_suffix": "50g_ordered", "project_id": "bigframes-dev-perf", "dataset_id": "dbbenchmark", "table_id": "J1_1e9_NA_0_0", "ordered": true} -{"benchmark_suffix": "50g_unordered", "project_id": "bigframes-dev-perf", "dataset_id": "dbbenchmark", "table_id": "J1_1e9_NA_0_0", "ordered": false} diff --git a/tests/benchmark/db_benchmark/sort/q1.py b/tests/benchmark/db_benchmark/sort/q1.py deleted file mode 100644 index d73fe28e30f..00000000000 --- a/tests/benchmark/db_benchmark/sort/q1.py +++ /dev/null @@ -1,32 +0,0 @@ -# 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. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import pathlib - -import benchmark.utils as utils -import bigframes_vendored.db_benchmark.sort_queries as vendored_dbbenchmark_sort_queries - -if __name__ == "__main__": - config = utils.get_configuration(include_table_id=True) - current_path = pathlib.Path(__file__).absolute() - - utils.get_execution_time( - vendored_dbbenchmark_sort_queries.q1, - current_path, - config.benchmark_suffix, - config.project_id, - config.dataset_id, - config.table_id, - config.session, - ) diff --git a/tests/benchmark/read_gbq_colab/aggregate_output.py b/tests/benchmark/read_gbq_colab/aggregate_output.py deleted file mode 100644 index e5620d8e16c..00000000000 --- a/tests/benchmark/read_gbq_colab/aggregate_output.py +++ /dev/null @@ -1,63 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -import pathlib - -import benchmark.utils as utils - -import bigframes.pandas as bpd - -PAGE_SIZE = utils.READ_GBQ_COLAB_PAGE_SIZE - - -def aggregate_output(*, project_id, dataset_id, table_id): - # TODO(tswast): Support alternative query if table_id is a local DataFrame, - # e.g. "{local_inline}" or "{local_large}" - df = bpd._read_gbq_colab(f"SELECT * FROM `{project_id}`.{dataset_id}.{table_id}") - - # Simulate getting the first page, since we'll always do that first in the UI. - batches = df._to_pandas_batches(page_size=PAGE_SIZE) - assert (tr := batches.total_rows) is not None and tr >= 0 - next(iter(batches)) - - # To simulate very small rows that can only fit a boolean, - # some tables don't have an integer column. If an integer column is available, - # we prefer to group by that to get a more realistic number of groups. - group_column = "col_int64_1" - if group_column not in df.columns: - group_column = "col_bool_0" - - # Simulate the user aggregating by a column and visualizing those results - df_aggregated = ( - df.assign(rounded=df[group_column].astype("Int64").round(-9)) - .groupby("rounded") - .sum(numeric_only=True) - ) - - batches = df_aggregated._to_pandas_batches(page_size=PAGE_SIZE) - assert (tr := batches.total_rows) is not None and tr >= 0 - next(iter(batches)) - - -if __name__ == "__main__": - config = utils.get_configuration(include_table_id=True, start_session=False) - current_path = pathlib.Path(__file__).absolute() - - utils.get_execution_time( - aggregate_output, - current_path, - config.benchmark_suffix, - project_id=config.project_id, - dataset_id=config.dataset_id, - table_id=config.table_id, - ) diff --git a/tests/benchmark/read_gbq_colab/config.jsonl b/tests/benchmark/read_gbq_colab/config.jsonl deleted file mode 100644 index 6f1ddf4a5f7..00000000000 --- a/tests/benchmark/read_gbq_colab/config.jsonl +++ /dev/null @@ -1,10 +0,0 @@ -{"benchmark_suffix": "percentile_09", "project_id": "bigframes-dev-perf", "dataset_id": "read_gbq_colab_benchmark", "table_id": "percentile_09", "ordered": false} -{"benchmark_suffix": "percentile_19", "project_id": "bigframes-dev-perf", "dataset_id": "read_gbq_colab_benchmark", "table_id": "percentile_19", "ordered": false} -{"benchmark_suffix": "percentile_29", "project_id": "bigframes-dev-perf", "dataset_id": "read_gbq_colab_benchmark", "table_id": "percentile_29", "ordered": false} -{"benchmark_suffix": "percentile_39", "project_id": "bigframes-dev-perf", "dataset_id": "read_gbq_colab_benchmark", "table_id": "percentile_39", "ordered": false} -{"benchmark_suffix": "percentile_49", "project_id": "bigframes-dev-perf", "dataset_id": "read_gbq_colab_benchmark", "table_id": "percentile_49", "ordered": false} -{"benchmark_suffix": "percentile_59", "project_id": "bigframes-dev-perf", "dataset_id": "read_gbq_colab_benchmark", "table_id": "percentile_59", "ordered": false} -{"benchmark_suffix": "percentile_69", "project_id": "bigframes-dev-perf", "dataset_id": "read_gbq_colab_benchmark", "table_id": "percentile_69", "ordered": false} -{"benchmark_suffix": "percentile_79", "project_id": "bigframes-dev-perf", "dataset_id": "read_gbq_colab_benchmark", "table_id": "percentile_79", "ordered": false} -{"benchmark_suffix": "percentile_89", "project_id": "bigframes-dev-perf", "dataset_id": "read_gbq_colab_benchmark", "table_id": "percentile_89", "ordered": false} -{"benchmark_suffix": "percentile_99", "project_id": "bigframes-dev-perf", "dataset_id": "read_gbq_colab_benchmark", "table_id": "percentile_99", "ordered": false} diff --git a/tests/benchmark/read_gbq_colab/dry_run.py b/tests/benchmark/read_gbq_colab/dry_run.py deleted file mode 100644 index 6caf08be72d..00000000000 --- a/tests/benchmark/read_gbq_colab/dry_run.py +++ /dev/null @@ -1,41 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -import pathlib - -import benchmark.utils as utils - -import bigframes.pandas - - -def dry_run(*, project_id, dataset_id, table_id): - # TODO(tswast): Support alternative query if table_id is a local DataFrame, - # e.g. "{local_inline}" or "{local_large}" - bigframes.pandas._read_gbq_colab( - f"SELECT * FROM `{project_id}`.{dataset_id}.{table_id}", - dry_run=True, - ) - - -if __name__ == "__main__": - config = utils.get_configuration(include_table_id=True, start_session=False) - current_path = pathlib.Path(__file__).absolute() - - utils.get_execution_time( - dry_run, - current_path, - config.benchmark_suffix, - project_id=config.project_id, - dataset_id=config.dataset_id, - table_id=config.table_id, - ) diff --git a/tests/benchmark/read_gbq_colab/filter_output.py b/tests/benchmark/read_gbq_colab/filter_output.py deleted file mode 100644 index dc88d313662..00000000000 --- a/tests/benchmark/read_gbq_colab/filter_output.py +++ /dev/null @@ -1,60 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -import pathlib - -import benchmark.utils as utils - -import bigframes.pandas as bpd - -PAGE_SIZE = utils.READ_GBQ_COLAB_PAGE_SIZE - - -def filter_output( - *, - project_id, - dataset_id, - table_id, -): - # TODO(tswast): Support alternative query if table_id is a local DataFrame, - # e.g. "{local_inline}" or "{local_large}" - df = bpd._read_gbq_colab(f"SELECT * FROM `{project_id}`.{dataset_id}.{table_id}") - - # Simulate getting the first page, since we'll always do that first in the UI. - batches = df._to_pandas_batches(page_size=PAGE_SIZE) - assert (tr := batches.total_rows) is not None and tr >= 0 - next(iter(batches)) - - # Simulate the user filtering by a column and visualizing those results - df_filtered = df[df["col_bool_0"]] - batches = df_filtered._to_pandas_batches(page_size=PAGE_SIZE) - assert (tr := batches.total_rows) is not None and tr >= 0 - first_page = next(iter(batches)) - - # It's possible we don't have any pages at all, since we filtered out all - # matching rows. - assert len(first_page.index) <= tr - - -if __name__ == "__main__": - config = utils.get_configuration(include_table_id=True) - current_path = pathlib.Path(__file__).absolute() - - utils.get_execution_time( - filter_output, - current_path, - config.benchmark_suffix, - project_id=config.project_id, - dataset_id=config.dataset_id, - table_id=config.table_id, - ) diff --git a/tests/benchmark/read_gbq_colab/first_page.py b/tests/benchmark/read_gbq_colab/first_page.py deleted file mode 100644 index 33e2a24bd7b..00000000000 --- a/tests/benchmark/read_gbq_colab/first_page.py +++ /dev/null @@ -1,47 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -import pathlib - -import benchmark.utils as utils - -import bigframes.pandas - -PAGE_SIZE = utils.READ_GBQ_COLAB_PAGE_SIZE - - -def first_page(*, project_id, dataset_id, table_id): - # TODO(tswast): Support alternative query if table_id is a local DataFrame, - # e.g. "{local_inline}" or "{local_large}" - df = bigframes.pandas._read_gbq_colab( - f"SELECT * FROM `{project_id}`.{dataset_id}.{table_id}" - ) - - # Get number of rows (to calculate number of pages) and the first page. - batches = df._to_pandas_batches(page_size=PAGE_SIZE) - assert (tr := batches.total_rows) is not None and tr >= 0 - next(iter(batches)) - - -if __name__ == "__main__": - config = utils.get_configuration(include_table_id=True, start_session=False) - current_path = pathlib.Path(__file__).absolute() - - utils.get_execution_time( - first_page, - current_path, - config.benchmark_suffix, - project_id=config.project_id, - dataset_id=config.dataset_id, - table_id=config.table_id, - ) diff --git a/tests/benchmark/read_gbq_colab/last_page.py b/tests/benchmark/read_gbq_colab/last_page.py deleted file mode 100644 index 2e485a070a8..00000000000 --- a/tests/benchmark/read_gbq_colab/last_page.py +++ /dev/null @@ -1,48 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -import pathlib - -import benchmark.utils as utils - -import bigframes.pandas - -PAGE_SIZE = utils.READ_GBQ_COLAB_PAGE_SIZE - - -def last_page(*, project_id, dataset_id, table_id): - # TODO(tswast): Support alternative query if table_id is a local DataFrame, - # e.g. "{local_inline}" or "{local_large}" - df = bigframes.pandas._read_gbq_colab( - f"SELECT * FROM `{project_id}`.{dataset_id}.{table_id}" - ) - - # Get number of rows (to calculate number of pages) and then all pages. - batches = df._to_pandas_batches(page_size=PAGE_SIZE) - assert (tr := batches.total_rows) is not None and tr >= 0 - for _ in batches: - pass - - -if __name__ == "__main__": - config = utils.get_configuration(include_table_id=True, start_session=False) - current_path = pathlib.Path(__file__).absolute() - - utils.get_execution_time( - last_page, - current_path, - config.benchmark_suffix, - project_id=config.project_id, - dataset_id=config.dataset_id, - table_id=config.table_id, - ) diff --git a/tests/benchmark/read_gbq_colab/sort_output.py b/tests/benchmark/read_gbq_colab/sort_output.py deleted file mode 100644 index 3044e0c2a32..00000000000 --- a/tests/benchmark/read_gbq_colab/sort_output.py +++ /dev/null @@ -1,57 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -import pathlib - -import benchmark.utils as utils - -import bigframes.pandas - -PAGE_SIZE = utils.READ_GBQ_COLAB_PAGE_SIZE - - -def sort_output(*, project_id, dataset_id, table_id): - # TODO(tswast): Support alternative query if table_id is a local DataFrame, - # e.g. "{local_inline}" or "{local_large}" - df = bigframes.pandas._read_gbq_colab( - f"SELECT * FROM `{project_id}`.{dataset_id}.{table_id}" - ) - - # Simulate getting the first page, since we'll always do that first in the UI. - batches = df._to_pandas_batches(page_size=PAGE_SIZE) - assert (tr := batches.total_rows) is not None and tr >= 0 - next(iter(batches)) - - # Simulate the user sorting by a column and visualizing those results - sort_column = "col_int64_1" - if sort_column not in df.columns: - sort_column = "col_bool_0" - - df_sorted = df.sort_values(sort_column) - batches = df_sorted._to_pandas_batches(page_size=PAGE_SIZE) - assert (tr := batches.total_rows) is not None and tr >= 0 - next(iter(batches)) - - -if __name__ == "__main__": - config = utils.get_configuration(include_table_id=True, start_session=False) - current_path = pathlib.Path(__file__).absolute() - - utils.get_execution_time( - sort_output, - current_path, - config.benchmark_suffix, - project_id=config.project_id, - dataset_id=config.dataset_id, - table_id=config.table_id, - ) diff --git a/tests/benchmark/tpch/config.jsonl b/tests/benchmark/tpch/config.jsonl deleted file mode 100644 index e6f7a444f65..00000000000 --- a/tests/benchmark/tpch/config.jsonl +++ /dev/null @@ -1,10 +0,0 @@ -{"benchmark_suffix": "1g_ordered", "project_id": "bigframes-dev-perf", "dataset_id": "tpch_0001g", "ordered": true} -{"benchmark_suffix": "1g_unordered", "project_id": "bigframes-dev-perf", "dataset_id": "tpch_0001g", "ordered": false} -{"benchmark_suffix": "10g_ordered", "project_id": "bigframes-dev-perf", "dataset_id": "tpch_0010g", "ordered": true} -{"benchmark_suffix": "10g_unordered", "project_id": "bigframes-dev-perf", "dataset_id": "tpch_0010g", "ordered": false} -{"benchmark_suffix": "100g_ordered", "project_id": "bigframes-dev-perf", "dataset_id": "tpch_0100g", "ordered": true} -{"benchmark_suffix": "100g_unordered", "project_id": "bigframes-dev-perf", "dataset_id": "tpch_0100g", "ordered": false} -{"benchmark_suffix": "1t_ordered", "project_id": "bigframes-dev-perf", "dataset_id": "tpch_0001t", "ordered": true} -{"benchmark_suffix": "1t_unordered", "project_id": "bigframes-dev-perf", "dataset_id": "tpch_0001t", "ordered": false} -{"benchmark_suffix": "10t_ordered", "project_id": "bigframes-dev-perf", "dataset_id": "tpch_0010t", "ordered": true} -{"benchmark_suffix": "10t_unordered", "project_id": "bigframes-dev-perf", "dataset_id": "tpch_0010t", "ordered": false} diff --git a/tests/benchmark/tpch/q1.py b/tests/benchmark/tpch/q1.py deleted file mode 100644 index beacaa436bf..00000000000 --- a/tests/benchmark/tpch/q1.py +++ /dev/null @@ -1,30 +0,0 @@ -# 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. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -import pathlib - -import benchmark.utils as utils -import bigframes_vendored.tpch.queries.q1 as vendored_tpch_q1 - -if __name__ == "__main__": - config = utils.get_configuration() - current_path = pathlib.Path(__file__).absolute() - - utils.get_execution_time( - vendored_tpch_q1.q, - current_path, - config.benchmark_suffix, - config.project_id, - config.dataset_id, - config.session, - ) diff --git a/tests/benchmark/tpch/q10.py b/tests/benchmark/tpch/q10.py deleted file mode 100644 index 27262ff2103..00000000000 --- a/tests/benchmark/tpch/q10.py +++ /dev/null @@ -1,30 +0,0 @@ -# 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. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -import pathlib - -import benchmark.utils as utils -import bigframes_vendored.tpch.queries.q10 as vendored_tpch_q10 - -if __name__ == "__main__": - config = utils.get_configuration() - current_path = pathlib.Path(__file__).absolute() - - utils.get_execution_time( - vendored_tpch_q10.q, - current_path, - config.benchmark_suffix, - config.project_id, - config.dataset_id, - config.session, - ) diff --git a/tests/benchmark/tpch/q11.py b/tests/benchmark/tpch/q11.py deleted file mode 100644 index 45a0168bb14..00000000000 --- a/tests/benchmark/tpch/q11.py +++ /dev/null @@ -1,30 +0,0 @@ -# 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. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -import pathlib - -import benchmark.utils as utils -import bigframes_vendored.tpch.queries.q11 as vendored_tpch_q11 - -if __name__ == "__main__": - config = utils.get_configuration() - current_path = pathlib.Path(__file__).absolute() - - utils.get_execution_time( - vendored_tpch_q11.q, - current_path, - config.benchmark_suffix, - config.project_id, - config.dataset_id, - config.session, - ) diff --git a/tests/benchmark/tpch/q12.py b/tests/benchmark/tpch/q12.py deleted file mode 100644 index d055cd1c0b7..00000000000 --- a/tests/benchmark/tpch/q12.py +++ /dev/null @@ -1,30 +0,0 @@ -# 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. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -import pathlib - -import benchmark.utils as utils -import bigframes_vendored.tpch.queries.q12 as vendored_tpch_q12 - -if __name__ == "__main__": - config = utils.get_configuration() - current_path = pathlib.Path(__file__).absolute() - - utils.get_execution_time( - vendored_tpch_q12.q, - current_path, - config.benchmark_suffix, - config.project_id, - config.dataset_id, - config.session, - ) diff --git a/tests/benchmark/tpch/q13.py b/tests/benchmark/tpch/q13.py deleted file mode 100644 index f74ef26448d..00000000000 --- a/tests/benchmark/tpch/q13.py +++ /dev/null @@ -1,30 +0,0 @@ -# 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. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -import pathlib - -import benchmark.utils as utils -import bigframes_vendored.tpch.queries.q13 as vendored_tpch_q13 - -if __name__ == "__main__": - config = utils.get_configuration() - current_path = pathlib.Path(__file__).absolute() - - utils.get_execution_time( - vendored_tpch_q13.q, - current_path, - config.benchmark_suffix, - config.project_id, - config.dataset_id, - config.session, - ) diff --git a/tests/benchmark/tpch/q14.py b/tests/benchmark/tpch/q14.py deleted file mode 100644 index 01ee0add398..00000000000 --- a/tests/benchmark/tpch/q14.py +++ /dev/null @@ -1,30 +0,0 @@ -# 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. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -import pathlib - -import benchmark.utils as utils -import bigframes_vendored.tpch.queries.q14 as vendored_tpch_q14 - -if __name__ == "__main__": - config = utils.get_configuration() - current_path = pathlib.Path(__file__).absolute() - - utils.get_execution_time( - vendored_tpch_q14.q, - current_path, - config.benchmark_suffix, - config.project_id, - config.dataset_id, - config.session, - ) diff --git a/tests/benchmark/tpch/q15.py b/tests/benchmark/tpch/q15.py deleted file mode 100644 index b19141797ac..00000000000 --- a/tests/benchmark/tpch/q15.py +++ /dev/null @@ -1,30 +0,0 @@ -# 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. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -import pathlib - -import benchmark.utils as utils -import bigframes_vendored.tpch.queries.q15 as vendored_tpch_q15 - -if __name__ == "__main__": - config = utils.get_configuration() - current_path = pathlib.Path(__file__).absolute() - - utils.get_execution_time( - vendored_tpch_q15.q, - current_path, - config.benchmark_suffix, - config.project_id, - config.dataset_id, - config.session, - ) diff --git a/tests/benchmark/tpch/q16.py b/tests/benchmark/tpch/q16.py deleted file mode 100644 index 5947bb6ed12..00000000000 --- a/tests/benchmark/tpch/q16.py +++ /dev/null @@ -1,30 +0,0 @@ -# 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. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -import pathlib - -import benchmark.utils as utils -import bigframes_vendored.tpch.queries.q16 as vendored_tpch_q16 - -if __name__ == "__main__": - config = utils.get_configuration() - current_path = pathlib.Path(__file__).absolute() - - utils.get_execution_time( - vendored_tpch_q16.q, - current_path, - config.benchmark_suffix, - config.project_id, - config.dataset_id, - config.session, - ) diff --git a/tests/benchmark/tpch/q17.py b/tests/benchmark/tpch/q17.py deleted file mode 100644 index e80f7b23f97..00000000000 --- a/tests/benchmark/tpch/q17.py +++ /dev/null @@ -1,30 +0,0 @@ -# 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. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -import pathlib - -import benchmark.utils as utils -import bigframes_vendored.tpch.queries.q17 as vendored_tpch_q17 - -if __name__ == "__main__": - config = utils.get_configuration() - current_path = pathlib.Path(__file__).absolute() - - utils.get_execution_time( - vendored_tpch_q17.q, - current_path, - config.benchmark_suffix, - config.project_id, - config.dataset_id, - config.session, - ) diff --git a/tests/benchmark/tpch/q18.py b/tests/benchmark/tpch/q18.py deleted file mode 100644 index 7e9d6c00c4c..00000000000 --- a/tests/benchmark/tpch/q18.py +++ /dev/null @@ -1,30 +0,0 @@ -# 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. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -import pathlib - -import benchmark.utils as utils -import bigframes_vendored.tpch.queries.q18 as vendored_tpch_q18 - -if __name__ == "__main__": - config = utils.get_configuration() - current_path = pathlib.Path(__file__).absolute() - - utils.get_execution_time( - vendored_tpch_q18.q, - current_path, - config.benchmark_suffix, - config.project_id, - config.dataset_id, - config.session, - ) diff --git a/tests/benchmark/tpch/q19.py b/tests/benchmark/tpch/q19.py deleted file mode 100644 index f2c1cfc6237..00000000000 --- a/tests/benchmark/tpch/q19.py +++ /dev/null @@ -1,30 +0,0 @@ -# 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. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -import pathlib - -import benchmark.utils as utils -import bigframes_vendored.tpch.queries.q19 as vendored_tpch_q19 - -if __name__ == "__main__": - config = utils.get_configuration() - current_path = pathlib.Path(__file__).absolute() - - utils.get_execution_time( - vendored_tpch_q19.q, - current_path, - config.benchmark_suffix, - config.project_id, - config.dataset_id, - config.session, - ) diff --git a/tests/benchmark/tpch/q2.py b/tests/benchmark/tpch/q2.py deleted file mode 100644 index 64907d0d258..00000000000 --- a/tests/benchmark/tpch/q2.py +++ /dev/null @@ -1,30 +0,0 @@ -# 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. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -import pathlib - -import benchmark.utils as utils -import bigframes_vendored.tpch.queries.q2 as vendored_tpch_q2 - -if __name__ == "__main__": - config = utils.get_configuration() - current_path = pathlib.Path(__file__).absolute() - - utils.get_execution_time( - vendored_tpch_q2.q, - current_path, - config.benchmark_suffix, - config.project_id, - config.dataset_id, - config.session, - ) diff --git a/tests/benchmark/tpch/q20.py b/tests/benchmark/tpch/q20.py deleted file mode 100644 index 8a405280ef0..00000000000 --- a/tests/benchmark/tpch/q20.py +++ /dev/null @@ -1,30 +0,0 @@ -# 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. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -import pathlib - -import benchmark.utils as utils -import bigframes_vendored.tpch.queries.q20 as vendored_tpch_q20 - -if __name__ == "__main__": - config = utils.get_configuration() - current_path = pathlib.Path(__file__).absolute() - - utils.get_execution_time( - vendored_tpch_q20.q, - current_path, - config.benchmark_suffix, - config.project_id, - config.dataset_id, - config.session, - ) diff --git a/tests/benchmark/tpch/q21.py b/tests/benchmark/tpch/q21.py deleted file mode 100644 index 29b364b3874..00000000000 --- a/tests/benchmark/tpch/q21.py +++ /dev/null @@ -1,30 +0,0 @@ -# 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. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -import pathlib - -import benchmark.utils as utils -import bigframes_vendored.tpch.queries.q21 as vendored_tpch_q21 - -if __name__ == "__main__": - config = utils.get_configuration() - current_path = pathlib.Path(__file__).absolute() - - utils.get_execution_time( - vendored_tpch_q21.q, - current_path, - config.benchmark_suffix, - config.project_id, - config.dataset_id, - config.session, - ) diff --git a/tests/benchmark/tpch/q22.py b/tests/benchmark/tpch/q22.py deleted file mode 100644 index 9147115097f..00000000000 --- a/tests/benchmark/tpch/q22.py +++ /dev/null @@ -1,30 +0,0 @@ -# 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. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -import pathlib - -import benchmark.utils as utils -import bigframes_vendored.tpch.queries.q22 as vendored_tpch_q22 - -if __name__ == "__main__": - config = utils.get_configuration() - current_path = pathlib.Path(__file__).absolute() - - utils.get_execution_time( - vendored_tpch_q22.q, - current_path, - config.benchmark_suffix, - config.project_id, - config.dataset_id, - config.session, - ) diff --git a/tests/benchmark/tpch/q3.py b/tests/benchmark/tpch/q3.py deleted file mode 100644 index e4eee0630bd..00000000000 --- a/tests/benchmark/tpch/q3.py +++ /dev/null @@ -1,30 +0,0 @@ -# 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. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -import pathlib - -import benchmark.utils as utils -import bigframes_vendored.tpch.queries.q3 as vendored_tpch_q3 - -if __name__ == "__main__": - config = utils.get_configuration() - current_path = pathlib.Path(__file__).absolute() - - utils.get_execution_time( - vendored_tpch_q3.q, - current_path, - config.benchmark_suffix, - config.project_id, - config.dataset_id, - config.session, - ) diff --git a/tests/benchmark/tpch/q4.py b/tests/benchmark/tpch/q4.py deleted file mode 100644 index f0aa3b77a0f..00000000000 --- a/tests/benchmark/tpch/q4.py +++ /dev/null @@ -1,30 +0,0 @@ -# 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. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -import pathlib - -import benchmark.utils as utils -import bigframes_vendored.tpch.queries.q4 as vendored_tpch_q4 - -if __name__ == "__main__": - config = utils.get_configuration() - current_path = pathlib.Path(__file__).absolute() - - utils.get_execution_time( - vendored_tpch_q4.q, - current_path, - config.benchmark_suffix, - config.project_id, - config.dataset_id, - config.session, - ) diff --git a/tests/benchmark/tpch/q5.py b/tests/benchmark/tpch/q5.py deleted file mode 100644 index 5f82638278d..00000000000 --- a/tests/benchmark/tpch/q5.py +++ /dev/null @@ -1,30 +0,0 @@ -# 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. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -import pathlib - -import benchmark.utils as utils -import bigframes_vendored.tpch.queries.q5 as vendored_tpch_q5 - -if __name__ == "__main__": - config = utils.get_configuration() - current_path = pathlib.Path(__file__).absolute() - - utils.get_execution_time( - vendored_tpch_q5.q, - current_path, - config.benchmark_suffix, - config.project_id, - config.dataset_id, - config.session, - ) diff --git a/tests/benchmark/tpch/q6.py b/tests/benchmark/tpch/q6.py deleted file mode 100644 index bf06f8d31c8..00000000000 --- a/tests/benchmark/tpch/q6.py +++ /dev/null @@ -1,30 +0,0 @@ -# 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. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -import pathlib - -import benchmark.utils as utils -import bigframes_vendored.tpch.queries.q6 as vendored_tpch_q6 - -if __name__ == "__main__": - config = utils.get_configuration() - current_path = pathlib.Path(__file__).absolute() - - utils.get_execution_time( - vendored_tpch_q6.q, - current_path, - config.benchmark_suffix, - config.project_id, - config.dataset_id, - config.session, - ) diff --git a/tests/benchmark/tpch/q7.py b/tests/benchmark/tpch/q7.py deleted file mode 100644 index f9575dd4d6b..00000000000 --- a/tests/benchmark/tpch/q7.py +++ /dev/null @@ -1,30 +0,0 @@ -# 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. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -import pathlib - -import benchmark.utils as utils -import bigframes_vendored.tpch.queries.q7 as vendored_tpch_q7 - -if __name__ == "__main__": - config = utils.get_configuration() - current_path = pathlib.Path(__file__).absolute() - - utils.get_execution_time( - vendored_tpch_q7.q, - current_path, - config.benchmark_suffix, - config.project_id, - config.dataset_id, - config.session, - ) diff --git a/tests/benchmark/tpch/q8.py b/tests/benchmark/tpch/q8.py deleted file mode 100644 index 0af13eaeeb0..00000000000 --- a/tests/benchmark/tpch/q8.py +++ /dev/null @@ -1,30 +0,0 @@ -# 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. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -import pathlib - -import benchmark.utils as utils -import bigframes_vendored.tpch.queries.q8 as vendored_tpch_q8 - -if __name__ == "__main__": - config = utils.get_configuration() - current_path = pathlib.Path(__file__).absolute() - - utils.get_execution_time( - vendored_tpch_q8.q, - current_path, - config.benchmark_suffix, - config.project_id, - config.dataset_id, - config.session, - ) diff --git a/tests/benchmark/tpch/q9.py b/tests/benchmark/tpch/q9.py deleted file mode 100644 index 61a319377a0..00000000000 --- a/tests/benchmark/tpch/q9.py +++ /dev/null @@ -1,30 +0,0 @@ -# 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. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -import pathlib - -import benchmark.utils as utils -import bigframes_vendored.tpch.queries.q9 as vendored_tpch_q9 - -if __name__ == "__main__": - config = utils.get_configuration() - current_path = pathlib.Path(__file__).absolute() - - utils.get_execution_time( - vendored_tpch_q9.q, - current_path, - config.benchmark_suffix, - config.project_id, - config.dataset_id, - config.session, - ) diff --git a/tests/benchmark/utils.py b/tests/benchmark/utils.py deleted file mode 100644 index 9690e0a3bd5..00000000000 --- a/tests/benchmark/utils.py +++ /dev/null @@ -1,107 +0,0 @@ -# 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. -# You may obtain a copy of the License at -# -# https://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 argparse -import dataclasses -import time - -import bigframes - -READ_GBQ_COLAB_PAGE_SIZE = 100 - - -@dataclasses.dataclass(frozen=True) -class BenchmarkConfig: - project_id: str - dataset_id: str - session: bigframes.Session | None - benchmark_suffix: str | None - table_id: str | None = None - - -def get_configuration(include_table_id=False, start_session=True) -> BenchmarkConfig: - parser = argparse.ArgumentParser() - parser.add_argument( - "--project_id", - type=str, - required=True, - help="The BigQuery project ID.", - ) - parser.add_argument( - "--dataset_id", - type=str, - required=True, - help="The BigQuery dataset ID.", - ) - - if include_table_id: - parser.add_argument( - "--table_id", - type=str, - required=True, - help="The BigQuery table ID to query.", - ) - - parser.add_argument( - "--ordered", - type=str, - help="Set to True (default) to have an ordered session, or False for an unordered session.", - ) - parser.add_argument( - "--benchmark_suffix", - type=str, - help="Suffix to append to benchmark names for identification purposes.", - ) - - args = parser.parse_args() - session = _initialize_session(_str_to_bool(args.ordered)) if start_session else None - - return BenchmarkConfig( - project_id=args.project_id, - dataset_id=args.dataset_id, - table_id=args.table_id if include_table_id else None, - session=session, - benchmark_suffix=args.benchmark_suffix, - ) - - -def get_execution_time(func, current_path, suffix, *args, **kwargs): - start_time = time.perf_counter() - func(*args, **kwargs) - end_time = time.perf_counter() - runtime = end_time - start_time - - clock_time_file_path = f"{current_path}_{suffix}.local_exec_time_seconds" - - with open(clock_time_file_path, "a") as log_file: - log_file.write(f"{runtime}\n") - - -def _str_to_bool(value): - if value == "True": - return True - elif value == "False": - return False - else: - raise argparse.ArgumentTypeError('Only "True" or "False" expected.') - - -def _initialize_session(ordered: bool): - # TODO(tswast): add a flag to enable the polars semi-executor. - context = bigframes.BigQueryOptions( - location="US", ordering_mode="strict" if ordered else "partial" - ) - session = bigframes.Session(context=context) - print(f"Initialized {'ordered' if ordered else 'unordered'} session.") - return session diff --git a/tests/data/json.jsonl b/tests/data/json.jsonl deleted file mode 100644 index 1abdcc9d565..00000000000 --- a/tests/data/json.jsonl +++ /dev/null @@ -1,16 +0,0 @@ -{"rowindex": 0, "json_col": null} -{"rowindex": 1, "json_col": true} -{"rowindex": 2, "json_col": 100} -{"rowindex": 3, "json_col": 0.98} -{"rowindex": 4, "json_col": "a string"} -{"rowindex": 5, "json_col": []} -{"rowindex": 6, "json_col": [1, 2, 3]} -{"rowindex": 7, "json_col": [{"a": 1}, {"a": 2}, {"a": null}, {}]} -{"rowindex": 8, "json_col": "100"} -{"rowindex": 9, "json_col": {"folat_num": 3.14159}} -{"rowindex": 10, "json_col": {"date": "2024-07-16"}} -{"rowindex": 11, "json_col": 100} -{"rowindex": 12, "json_col": {"int_value": 2, "null_filed": null}} -{"rowindex": 13, "json_col": {"list_data": [10, 20, 30]}} -{"rowindex": 14, "json_col": {"person": {"name": "Alice", "age": 35}}} -{"rowindex": 15, "json_col": {"order": {"items": ["book", "pen"], "total": 15.99}}} diff --git a/tests/data/json_schema.json b/tests/data/json_schema.json deleted file mode 100644 index 6bbbf5ca55c..00000000000 --- a/tests/data/json_schema.json +++ /dev/null @@ -1,12 +0,0 @@ -[ - { - "name": "rowindex", - "type": "INTEGER", - "mode": "REQUIRED" - }, - { - "name": "json_col", - "type": "JSON", - "mode": "NULLABLE" - } -] diff --git a/tests/data/nested.jsonl b/tests/data/nested.jsonl deleted file mode 100644 index 751ad0df78b..00000000000 --- a/tests/data/nested.jsonl +++ /dev/null @@ -1,100 +0,0 @@ -{"rowindex":0,"customer_id":"jkl","day":"2023-12-18","flag":1,"label":{"key": "my-key","value":"my-value"},"event_sequence":[{"category":"B","timestamp":"2023-12-18 03:43:58","data":[{"key":"x","value":20.2533015856},{"key":"y","value":42.8363462389}]},{"category":"D","timestamp":"2023-12-18 07:15:37","data":[{"key":"x","value":62.0762664928},{"key":"z","value":83.6655402432}]}],"address":{"street":"123 Test Lane","city":"Testerchon"}} -{"rowindex":1,"customer_id":"def","day":"2023-12-18","flag":2,"event_sequence":[{"category":"D","timestamp":"2023-12-18 23:11:11","data":[{"key":"w","value":36.1388065179}]},{"category":"B","timestamp":"2023-12-18 07:12:50","data":[{"key":"z","value":68.7673488304}]},{"category":"D","timestamp":"2023-12-18 09:09:03","data":[{"key":"x","value":57.4139647019}]},{"category":"C","timestamp":"2023-12-18 13:05:30","data":[{"key":"z","value":36.087871201}]}]} -{"rowindex":2,"customer_id":"abc","day":"2023-12-6","flag":0,"event_sequence":[{"category":"C","timestamp":"2023-12-06 10:37:11","data":[]},{"category":"A","timestamp":"2023-12-06 03:35:44","data":[]},{"category":"D","timestamp":"2023-12-06 13:10:57","data":[{"key":"z","value":21.8487807658}]},{"category":"B","timestamp":"2023-12-06 01:39:16","data":[{"key":"y","value":1.6380505139}]}]} -{"rowindex":3,"customer_id":"mno","day":"2023-12-16","flag":2,"event_sequence":[]} -{"rowindex":4,"customer_id":"jkl","day":"2023-12-1","flag":1,"event_sequence":[{"category":"C","timestamp":"2023-12-01 22:29:35","data":[]}]} -{"rowindex":5,"customer_id":"mno","day":"2023-12-8","flag":2,"event_sequence":[{"category":"C","timestamp":"2023-12-08 19:56:43","data":[{"key":"z","value":64.0025360397}]},{"category":"A","timestamp":"2023-12-08 00:43:53","data":[{"key":"z","value":62.5030923507},{"key":"y","value":67.4517590972}]}]} -{"rowindex":6,"customer_id":"abc","day":"2023-12-3","flag":1,"event_sequence":[{"category":"D","timestamp":"2023-12-03 10:04:48","data":[{"key":"x","value":73.0494929425},{"key":"z","value":81.1761568104}]}]} -{"rowindex":7,"customer_id":"abc","day":"2023-12-6","flag":1,"event_sequence":[{"category":"B","timestamp":"2023-12-06 16:50:15","data":[{"key":"w","value":46.395435162},{"key":"y","value":7.8421775851}]},{"category":"A","timestamp":"2023-12-06 05:55:01","data":[]},{"category":"B","timestamp":"2023-12-06 15:24:08","data":[{"key":"x","value":37.5351196265},{"key":"w","value":65.4896295524}]}]} -{"rowindex":8,"customer_id":"jkl","day":"2023-12-8","flag":2,"event_sequence":[{"category":"A","timestamp":"2023-12-08 00:21:23","data":[{"key":"w","value":42.4467608939},{"key":"x","value":81.083558253}]},{"category":"A","timestamp":"2023-12-08 09:31:05","data":[]},{"category":"C","timestamp":"2023-12-08 01:42:37","data":[{"key":"y","value":55.1881250973}]},{"category":"C","timestamp":"2023-12-08 21:14:46","data":[{"key":"z","value":12.0833253151}]},{"category":"D","timestamp":"2023-12-08 21:38:25","data":[{"key":"y","value":59.9482432021}]}]} -{"rowindex":9,"customer_id":"jkl","day":"2023-12-5","flag":1,"event_sequence":[{"category":"B","timestamp":"2023-12-05 09:46:09","data":[{"key":"w","value":48.5204042398}]},{"category":"C","timestamp":"2023-12-05 03:44:30","data":[{"key":"y","value":49.3712140658}]}]} -{"rowindex":10,"customer_id":"mno","day":"2023-12-1","flag":1,"event_sequence":[{"category":"B","timestamp":"2023-12-01 00:53:03","data":[{"key":"w","value":19.1753301515},{"key":"z","value":90.1966084522}]},{"category":"B","timestamp":"2023-12-01 15:18:15","data":[{"key":"w","value":28.4831052842},{"key":"y","value":74.3676328239}]},{"category":"D","timestamp":"2023-12-01 18:35:06","data":[{"key":"w","value":50.9000130431}]},{"category":"A","timestamp":"2023-12-01 19:10:15","data":[{"key":"x","value":36.4073472229},{"key":"y","value":2.5800142072}]}]} -{"rowindex":11,"customer_id":"abc","day":"2023-12-7","flag":1,"event_sequence":[{"category":"B","timestamp":"2023-12-07 03:28:37","data":[]},{"category":"D","timestamp":"2023-12-07 03:00:47","data":[{"key":"z","value":42.5078083149},{"key":"w","value":0.3430387149}]}]} -{"rowindex":12,"customer_id":"jkl","day":"2023-12-16","flag":0,"event_sequence":[{"category":"C","timestamp":"2023-12-16 20:28:48","data":[{"key":"y","value":99.4511527722}]}]} -{"rowindex":13,"customer_id":"ghi","day":"2023-12-18","flag":1,"event_sequence":[{"category":"D","timestamp":"2023-12-18 00:35:24","data":[{"key":"w","value":30.3520969504}]},{"category":"B","timestamp":"2023-12-18 10:45:35","data":[]},{"category":"C","timestamp":"2023-12-18 18:39:11","data":[{"key":"z","value":93.486287241}]},{"category":"C","timestamp":"2023-12-18 18:55:30","data":[{"key":"y","value":20.2247125873}]}]} -{"rowindex":14,"customer_id":"abc","day":"2023-12-14","flag":2,"event_sequence":[{"category":"A","timestamp":"2023-12-14 04:48:13","data":[]},{"category":"B","timestamp":"2023-12-14 07:39:40","data":[]},{"category":"D","timestamp":"2023-12-14 22:08:13","data":[{"key":"x","value":31.3054147446},{"key":"y","value":32.9881809276}]},{"category":"A","timestamp":"2023-12-14 23:02:18","data":[{"key":"x","value":41.4514710087},{"key":"w","value":71.0759384863}]}]} -{"rowindex":15,"customer_id":"def","day":"2023-12-14","flag":0,"event_sequence":[{"category":"D","timestamp":"2023-12-14 18:34:07","data":[{"key":"w","value":82.4015077053},{"key":"x","value":80.8508070787}]},{"category":"B","timestamp":"2023-12-14 10:08:52","data":[{"key":"y","value":91.3558143519},{"key":"w","value":42.8103570355}]}]} -{"rowindex":16,"customer_id":"mno","day":"2023-12-7","flag":0,"event_sequence":[{"category":"C","timestamp":"2023-12-07 07:07:38","data":[]},{"category":"A","timestamp":"2023-12-07 03:39:27","data":[{"key":"w","value":25.6141348288}]}]} -{"rowindex":17,"customer_id":"mno","day":"2023-12-18","flag":2,"event_sequence":[{"category":"D","timestamp":"2023-12-18 22:24:48","data":[{"key":"y","value":81.207759202}]}]} -{"rowindex":18,"customer_id":"ghi","day":"2023-12-13","flag":2,"event_sequence":[{"category":"A","timestamp":"2023-12-13 16:26:05","data":[{"key":"y","value":30.6921921236}]},{"category":"C","timestamp":"2023-12-13 15:00:10","data":[{"key":"x","value":73.8609954622}]}]} -{"rowindex":19,"customer_id":"abc","day":"2023-12-7","flag":2,"event_sequence":[]} -{"rowindex":20,"customer_id":"jkl","day":"2023-12-17","flag":0,"event_sequence":[]} -{"rowindex":21,"customer_id":"mno","day":"2023-12-14","flag":1,"event_sequence":[{"category":"D","timestamp":"2023-12-14 04:13:58","data":[{"key":"w","value":86.1548312989}]},{"category":"D","timestamp":"2023-12-14 15:39:43","data":[{"key":"w","value":40.0214161212}]},{"category":"B","timestamp":"2023-12-14 19:35:33","data":[{"key":"z","value":67.4152417129}]},{"category":"D","timestamp":"2023-12-14 17:20:20","data":[]},{"category":"C","timestamp":"2023-12-14 00:10:29","data":[{"key":"z","value":56.6529579965},{"key":"y","value":52.1273353535}]}]} -{"rowindex":22,"customer_id":"mno","day":"2023-12-8","flag":0,"event_sequence":[{"category":"D","timestamp":"2023-12-08 13:34:42","data":[{"key":"w","value":95.9950956489},{"key":"y","value":73.9478601628}]}]} -{"rowindex":23,"customer_id":"def","day":"2023-12-17","flag":1,"event_sequence":[{"category":"A","timestamp":"2023-12-17 10:07:16","data":[{"key":"x","value":66.1044798274}]},{"category":"B","timestamp":"2023-12-17 14:33:42","data":[{"key":"z","value":77.4267396836}]},{"category":"B","timestamp":"2023-12-17 11:54:45","data":[]}]} -{"rowindex":24,"customer_id":"def","day":"2023-12-17","flag":2,"event_sequence":[{"category":"B","timestamp":"2023-12-17 21:02:08","data":[{"key":"y","value":70.9945354474}]}]} -{"rowindex":25,"customer_id":"ghi","day":"2023-12-2","flag":0,"event_sequence":[{"category":"D","timestamp":"2023-12-02 01:17:39","data":[]},{"category":"B","timestamp":"2023-12-02 13:54:33","data":[{"key":"w","value":49.7485944905},{"key":"x","value":12.3938168348}]},{"category":"B","timestamp":"2023-12-02 02:30:14","data":[]},{"category":"C","timestamp":"2023-12-02 13:16:54","data":[{"key":"x","value":52.0455905555},{"key":"y","value":13.1107332474}]},{"category":"A","timestamp":"2023-12-02 23:10:23","data":[{"key":"w","value":73.5827155332}]}]} -{"rowindex":26,"customer_id":"def","day":"2023-12-1","flag":1,"event_sequence":[{"category":"D","timestamp":"2023-12-01 10:01:13","data":[]}]} -{"rowindex":27,"customer_id":"mno","day":"2023-12-10","flag":1,"event_sequence":[{"category":"C","timestamp":"2023-12-10 11:07:58","data":[{"key":"y","value":41.8327013256},{"key":"w","value":59.4445826737}]},{"category":"C","timestamp":"2023-12-10 01:35:25","data":[{"key":"z","value":98.4395840749}]}]} -{"rowindex":28,"customer_id":"def","day":"2023-12-4","flag":0,"event_sequence":[{"category":"B","timestamp":"2023-12-04 13:27:56","data":[]},{"category":"D","timestamp":"2023-12-04 07:29:29","data":[]},{"category":"C","timestamp":"2023-12-04 15:50:42","data":[]},{"category":"C","timestamp":"2023-12-04 21:14:39","data":[{"key":"x","value":87.2090409333},{"key":"z","value":67.873124445}]},{"category":"A","timestamp":"2023-12-04 10:22:07","data":[]}]} -{"rowindex":29,"customer_id":"abc","day":"2023-12-6","flag":0,"event_sequence":[{"category":"D","timestamp":"2023-12-06 13:03:19","data":[{"key":"y","value":64.2584716378},{"key":"w","value":17.4653120122}]},{"category":"A","timestamp":"2023-12-06 06:10:03","data":[{"key":"w","value":93.696003482},{"key":"y","value":0.675474038}]},{"category":"B","timestamp":"2023-12-06 10:10:08","data":[]},{"category":"C","timestamp":"2023-12-06 06:48:30","data":[]},{"category":"B","timestamp":"2023-12-06 23:00:42","data":[{"key":"x","value":65.1766190228}]}]} -{"rowindex":30,"customer_id":"abc","day":"2023-12-1","flag":1,"event_sequence":[{"category":"C","timestamp":"2023-12-01 03:17:48","data":[]},{"category":"A","timestamp":"2023-12-01 19:59:32","data":[]},{"category":"C","timestamp":"2023-12-01 02:16:52","data":[]}]} -{"rowindex":31,"customer_id":"jkl","day":"2023-12-2","flag":0,"event_sequence":[{"category":"A","timestamp":"2023-12-02 13:13:21","data":[{"key":"y","value":85.6919195342}]},{"category":"C","timestamp":"2023-12-02 06:32:12","data":[{"key":"y","value":72.2526437761},{"key":"x","value":62.1668944755}]},{"category":"D","timestamp":"2023-12-02 01:49:25","data":[{"key":"z","value":13.5820871569}]},{"category":"A","timestamp":"2023-12-02 21:30:07","data":[{"key":"x","value":33.6063239173},{"key":"z","value":93.896859174}]},{"category":"C","timestamp":"2023-12-02 07:03:10","data":[{"key":"w","value":95.2222323306},{"key":"x","value":8.4438153156}]}]} -{"rowindex":32,"customer_id":"def","day":"2023-12-9","flag":1,"event_sequence":[]} -{"rowindex":33,"customer_id":"def","day":"2023-12-4","flag":2,"event_sequence":[{"category":"B","timestamp":"2023-12-04 18:03:13","data":[{"key":"x","value":87.3759936085}]},{"category":"C","timestamp":"2023-12-04 12:23:33","data":[{"key":"x","value":7.6663438235}]},{"category":"D","timestamp":"2023-12-04 23:16:12","data":[{"key":"x","value":42.6682526335}]}]} -{"rowindex":34,"customer_id":"ghi","day":"2023-12-2","flag":2,"event_sequence":[{"category":"D","timestamp":"2023-12-02 22:48:04","data":[]},{"category":"A","timestamp":"2023-12-02 06:52:49","data":[{"key":"x","value":53.7008853605}]},{"category":"D","timestamp":"2023-12-02 21:35:43","data":[{"key":"w","value":65.7972882681}]},{"category":"D","timestamp":"2023-12-02 04:22:32","data":[{"key":"x","value":8.0812633272}]},{"category":"D","timestamp":"2023-12-02 04:53:36","data":[]}]} -{"rowindex":35,"customer_id":"ghi","day":"2023-12-18","flag":1,"event_sequence":[{"category":"D","timestamp":"2023-12-18 07:51:07","data":[]},{"category":"C","timestamp":"2023-12-18 23:09:23","data":[{"key":"x","value":36.7126625188},{"key":"z","value":7.3234058497}]}]} -{"rowindex":36,"customer_id":"ghi","day":"2023-12-11","flag":2,"event_sequence":[{"category":"D","timestamp":"2023-12-11 17:36:44","data":[{"key":"y","value":72.5462499934},{"key":"x","value":40.7042156894}]},{"category":"C","timestamp":"2023-12-11 19:58:01","data":[{"key":"x","value":88.553115143},{"key":"w","value":16.5083749137}]},{"category":"C","timestamp":"2023-12-11 00:22:58","data":[{"key":"y","value":13.7684351079}]},{"category":"A","timestamp":"2023-12-11 06:52:46","data":[{"key":"x","value":82.6970048317}]}]} -{"rowindex":37,"customer_id":"jkl","day":"2023-12-1","flag":2,"event_sequence":[{"category":"B","timestamp":"2023-12-01 12:41:41","data":[]},{"category":"D","timestamp":"2023-12-01 05:37:51","data":[]},{"category":"C","timestamp":"2023-12-01 07:50:54","data":[{"key":"y","value":79.7821140254},{"key":"w","value":55.1183743775}]},{"category":"A","timestamp":"2023-12-01 16:23:25","data":[]}]} -{"rowindex":38,"customer_id":"abc","day":"2023-12-15","flag":0,"event_sequence":[{"category":"B","timestamp":"2023-12-15 15:45:21","data":[]},{"category":"D","timestamp":"2023-12-15 05:40:05","data":[{"key":"z","value":84.4372711239}]},{"category":"C","timestamp":"2023-12-15 18:54:07","data":[]},{"category":"C","timestamp":"2023-12-15 01:34:35","data":[{"key":"x","value":57.6043137776},{"key":"y","value":2.0915421039}]}]} -{"rowindex":39,"customer_id":"ghi","day":"2023-12-2","flag":0,"event_sequence":[{"category":"C","timestamp":"2023-12-02 17:31:07","data":[]},{"category":"A","timestamp":"2023-12-02 14:09:19","data":[]},{"category":"A","timestamp":"2023-12-02 19:47:26","data":[{"key":"y","value":40.4981578761}]}]} -{"rowindex":40,"customer_id":"abc","day":"2023-12-17","flag":0,"event_sequence":[{"category":"D","timestamp":"2023-12-17 14:54:26","data":[]}]} -{"rowindex":41,"customer_id":"def","day":"2023-12-8","flag":0,"event_sequence":[{"category":"C","timestamp":"2023-12-08 03:29:13","data":[{"key":"w","value":22.3551385464}]},{"category":"A","timestamp":"2023-12-08 18:11:55","data":[]}]} -{"rowindex":42,"customer_id":"ghi","day":"2023-12-2","flag":1,"event_sequence":[{"category":"C","timestamp":"2023-12-02 13:34:00","data":[{"key":"w","value":32.2914731904},{"key":"z","value":1.667821995}]},{"category":"C","timestamp":"2023-12-02 16:27:30","data":[]},{"category":"D","timestamp":"2023-12-02 05:53:11","data":[]},{"category":"C","timestamp":"2023-12-02 06:36:55","data":[{"key":"z","value":17.1648556861},{"key":"y","value":68.34850499}]}]} -{"rowindex":43,"customer_id":"ghi","day":"2023-12-11","flag":2,"event_sequence":[{"category":"B","timestamp":"2023-12-11 08:23:53","data":[{"key":"x","value":44.2005886027}]},{"category":"B","timestamp":"2023-12-11 07:45:41","data":[{"key":"w","value":77.6941452877},{"key":"z","value":51.1968046092}]},{"category":"B","timestamp":"2023-12-11 11:58:25","data":[{"key":"y","value":68.1363704094}]},{"category":"C","timestamp":"2023-12-11 22:13:57","data":[{"key":"z","value":58.1763854177}]},{"category":"C","timestamp":"2023-12-11 09:13:08","data":[]}]} -{"rowindex":44,"customer_id":"def","day":"2023-12-12","flag":2,"event_sequence":[{"category":"C","timestamp":"2023-12-12 11:38:27","data":[{"key":"y","value":89.3301425129},{"key":"w","value":39.419946238}]}]} -{"rowindex":45,"customer_id":"mno","day":"2023-12-14","flag":2,"event_sequence":[{"category":"A","timestamp":"2023-12-14 13:26:53","data":[{"key":"z","value":76.4355996198}]},{"category":"D","timestamp":"2023-12-14 02:51:25","data":[]},{"category":"D","timestamp":"2023-12-14 16:06:20","data":[]}]} -{"rowindex":46,"customer_id":"mno","day":"2023-12-18","flag":0,"event_sequence":[{"category":"D","timestamp":"2023-12-18 16:52:35","data":[{"key":"y","value":92.8314533492}]},{"category":"A","timestamp":"2023-12-18 18:55:16","data":[]},{"category":"A","timestamp":"2023-12-18 11:48:11","data":[]}]} -{"rowindex":47,"customer_id":"ghi","day":"2023-12-5","flag":2,"event_sequence":[{"category":"D","timestamp":"2023-12-05 18:00:29","data":[{"key":"w","value":4.1194443596},{"key":"y","value":90.9907980881}]},{"category":"C","timestamp":"2023-12-05 18:28:30","data":[]},{"category":"C","timestamp":"2023-12-05 01:23:53","data":[]},{"category":"B","timestamp":"2023-12-05 09:30:53","data":[]}]} -{"rowindex":48,"customer_id":"jkl","day":"2023-12-4","flag":1,"event_sequence":[{"category":"C","timestamp":"2023-12-04 00:00:57","data":[{"key":"x","value":54.1860622721},{"key":"z","value":21.9039040875}]},{"category":"C","timestamp":"2023-12-04 03:47:29","data":[{"key":"z","value":10.1626962952},{"key":"y","value":80.2137857017}]},{"category":"C","timestamp":"2023-12-04 09:38:59","data":[{"key":"y","value":41.4002343854},{"key":"x","value":2.5915025309}]},{"category":"D","timestamp":"2023-12-04 10:26:10","data":[{"key":"y","value":78.3790791291},{"key":"z","value":21.0205345948}]}]} -{"rowindex":49,"customer_id":"jkl","day":"2023-12-11","flag":0,"event_sequence":[{"category":"C","timestamp":"2023-12-11 00:56:27","data":[]},{"category":"C","timestamp":"2023-12-11 00:00:49","data":[]},{"category":"A","timestamp":"2023-12-11 06:51:01","data":[]},{"category":"B","timestamp":"2023-12-11 15:03:31","data":[{"key":"w","value":11.4068443366}]},{"category":"A","timestamp":"2023-12-11 06:51:26","data":[{"key":"x","value":16.6716464506},{"key":"w","value":12.3375298466}]}]} -{"rowindex":50,"customer_id":"jkl","day":"2023-12-7","flag":0,"event_sequence":[]} -{"rowindex":51,"customer_id":"jkl","day":"2023-12-16","flag":1,"event_sequence":[]} -{"rowindex":52,"customer_id":"mno","day":"2023-12-8","flag":0,"event_sequence":[{"category":"C","timestamp":"2023-12-08 13:38:34","data":[{"key":"y","value":89.16823262}]},{"category":"B","timestamp":"2023-12-08 21:42:37","data":[{"key":"z","value":49.2264719354},{"key":"w","value":71.3471924749}]},{"category":"B","timestamp":"2023-12-08 11:20:22","data":[]}]} -{"rowindex":53,"customer_id":"ghi","day":"2023-12-18","flag":0,"event_sequence":[]} -{"rowindex":54,"customer_id":"def","day":"2023-12-14","flag":2,"event_sequence":[{"category":"B","timestamp":"2023-12-14 15:18:52","data":[{"key":"x","value":10.7255724898}]},{"category":"C","timestamp":"2023-12-14 00:16:13","data":[{"key":"x","value":81.6578442509},{"key":"z","value":97.6343706241}]},{"category":"A","timestamp":"2023-12-14 15:17:47","data":[{"key":"z","value":61.0727156569},{"key":"y","value":68.5047229429}]}]} -{"rowindex":55,"customer_id":"def","day":"2023-12-17","flag":0,"event_sequence":[{"category":"A","timestamp":"2023-12-17 08:09:37","data":[{"key":"x","value":96.7880530276}]},{"category":"C","timestamp":"2023-12-17 17:45:03","data":[{"key":"x","value":89.261752039}]},{"category":"B","timestamp":"2023-12-17 23:34:55","data":[{"key":"x","value":56.6947696032},{"key":"y","value":39.2160698568}]}]} -{"rowindex":56,"customer_id":"abc","day":"2023-12-3","flag":1,"event_sequence":[{"category":"C","timestamp":"2023-12-03 16:36:33","data":[{"key":"w","value":31.3842474288},{"key":"y","value":70.0883222713}]},{"category":"A","timestamp":"2023-12-03 23:14:03","data":[{"key":"z","value":2.241181478},{"key":"x","value":33.4155024672}]},{"category":"C","timestamp":"2023-12-03 02:59:20","data":[{"key":"w","value":30.325598456},{"key":"y","value":43.6801994079}]},{"category":"A","timestamp":"2023-12-03 17:25:12","data":[]}]} -{"rowindex":57,"customer_id":"jkl","day":"2023-12-18","flag":0,"event_sequence":[{"category":"B","timestamp":"2023-12-18 02:36:06","data":[{"key":"y","value":59.5978119693},{"key":"w","value":50.0596752663}]},{"category":"A","timestamp":"2023-12-18 22:15:26","data":[{"key":"y","value":46.7811589523},{"key":"z","value":17.5305458954}]},{"category":"B","timestamp":"2023-12-18 10:46:35","data":[{"key":"y","value":17.5499211188}]}]} -{"rowindex":58,"customer_id":"jkl","day":"2023-12-11","flag":1,"event_sequence":[{"category":"A","timestamp":"2023-12-11 08:08:24","data":[]},{"category":"A","timestamp":"2023-12-11 14:37:12","data":[{"key":"z","value":85.2678327892}]},{"category":"A","timestamp":"2023-12-11 14:11:26","data":[]},{"category":"A","timestamp":"2023-12-11 09:15:19","data":[]},{"category":"A","timestamp":"2023-12-11 13:29:27","data":[]}]} -{"rowindex":59,"customer_id":"mno","day":"2023-12-18","flag":2,"event_sequence":[]} -{"rowindex":60,"customer_id":"def","day":"2023-12-15","flag":0,"event_sequence":[{"category":"C","timestamp":"2023-12-15 22:31:56","data":[{"key":"x","value":69.3286635086},{"key":"z","value":41.2999550449}]},{"category":"D","timestamp":"2023-12-15 22:30:05","data":[]},{"category":"B","timestamp":"2023-12-15 13:52:17","data":[{"key":"z","value":37.8991532333},{"key":"y","value":69.1381526165}]}]} -{"rowindex":61,"customer_id":"jkl","day":"2023-12-6","flag":2,"event_sequence":[{"category":"C","timestamp":"2023-12-06 16:19:12","data":[{"key":"w","value":83.7533903572},{"key":"x","value":72.0796689391}]}]} -{"rowindex":62,"customer_id":"ghi","day":"2023-12-13","flag":1,"event_sequence":[{"category":"D","timestamp":"2023-12-13 19:35:45","data":[{"key":"y","value":9.7338091747}]},{"category":"B","timestamp":"2023-12-13 04:27:13","data":[{"key":"x","value":77.5851696223},{"key":"y","value":44.6396928116}]},{"category":"B","timestamp":"2023-12-13 14:21:37","data":[{"key":"z","value":62.6243288556}]},{"category":"C","timestamp":"2023-12-13 09:43:52","data":[{"key":"y","value":96.4384908625}]}]} -{"rowindex":63,"customer_id":"def","day":"2023-12-14","flag":1,"event_sequence":[{"category":"A","timestamp":"2023-12-14 10:49:52","data":[{"key":"x","value":47.2768901655},{"key":"y","value":31.4990167429}]},{"category":"B","timestamp":"2023-12-14 13:00:17","data":[{"key":"y","value":47.1290340032},{"key":"x","value":63.4631919376}]},{"category":"A","timestamp":"2023-12-14 22:12:52","data":[]},{"category":"A","timestamp":"2023-12-14 06:31:57","data":[]},{"category":"A","timestamp":"2023-12-14 03:46:03","data":[]}]} -{"rowindex":64,"customer_id":"mno","day":"2023-12-9","flag":1,"event_sequence":[{"category":"B","timestamp":"2023-12-09 10:04:27","data":[{"key":"y","value":67.6773976982},{"key":"w","value":30.3681543638}]},{"category":"D","timestamp":"2023-12-09 06:31:47","data":[]}]} -{"rowindex":65,"customer_id":"mno","day":"2023-12-4","flag":2,"event_sequence":[{"category":"D","timestamp":"2023-12-04 03:30:32","data":[]},{"category":"B","timestamp":"2023-12-04 05:04:06","data":[{"key":"x","value":21.382181381}]}]} -{"rowindex":66,"customer_id":"mno","day":"2023-12-9","flag":0,"event_sequence":[]} -{"rowindex":67,"customer_id":"def","day":"2023-12-18","flag":0,"event_sequence":[{"category":"D","timestamp":"2023-12-18 15:06:18","data":[{"key":"w","value":22.8608042274}]}]} -{"rowindex":68,"customer_id":"mno","day":"2023-12-2","flag":0,"event_sequence":[{"category":"A","timestamp":"2023-12-02 20:31:02","data":[{"key":"z","value":91.6471682783}]}]} -{"rowindex":69,"customer_id":"def","day":"2023-12-1","flag":2,"event_sequence":[{"category":"A","timestamp":"2023-12-01 18:23:24","data":[]},{"category":"B","timestamp":"2023-12-01 03:38:19","data":[{"key":"z","value":77.6426948721}]},{"category":"D","timestamp":"2023-12-01 02:53:39","data":[]},{"category":"D","timestamp":"2023-12-01 01:16:05","data":[{"key":"x","value":4.1829224252}]}]} -{"rowindex":70,"customer_id":"ghi","day":"2023-12-2","flag":1,"event_sequence":[{"category":"A","timestamp":"2023-12-02 18:53:51","data":[]},{"category":"A","timestamp":"2023-12-02 11:05:50","data":[{"key":"z","value":41.8070964998}]},{"category":"B","timestamp":"2023-12-02 06:32:35","data":[]},{"category":"B","timestamp":"2023-12-02 07:03:09","data":[{"key":"x","value":73.1611243111}]}]} -{"rowindex":71,"customer_id":"ghi","day":"2023-12-9","flag":2,"event_sequence":[{"category":"C","timestamp":"2023-12-09 04:54:59","data":[{"key":"x","value":85.2320581103}]},{"category":"B","timestamp":"2023-12-09 15:11:55","data":[]},{"category":"D","timestamp":"2023-12-09 16:21:45","data":[]},{"category":"B","timestamp":"2023-12-09 06:03:32","data":[{"key":"w","value":69.0663696235}]},{"category":"C","timestamp":"2023-12-09 02:48:41","data":[{"key":"y","value":13.3980977494}]}]} -{"rowindex":72,"customer_id":"abc","day":"2023-12-14","flag":0,"event_sequence":[{"category":"C","timestamp":"2023-12-14 15:56:22","data":[]},{"category":"D","timestamp":"2023-12-14 06:48:33","data":[{"key":"y","value":36.2141968443},{"key":"z","value":95.4467019984}]}]} -{"rowindex":73,"customer_id":"mno","day":"2023-12-13","flag":1,"event_sequence":[{"category":"D","timestamp":"2023-12-13 10:19:12","data":[]}]} -{"rowindex":74,"customer_id":"def","day":"2023-12-3","flag":0,"event_sequence":[]} -{"rowindex":75,"customer_id":"abc","day":"2023-12-13","flag":2,"event_sequence":[{"category":"D","timestamp":"2023-12-13 19:07:09","data":[{"key":"w","value":18.8470628926},{"key":"z","value":88.20939594}]}]} -{"rowindex":76,"customer_id":"ghi","day":"2023-12-8","flag":0,"event_sequence":[{"category":"D","timestamp":"2023-12-08 15:22:08","data":[]},{"category":"C","timestamp":"2023-12-08 16:51:43","data":[{"key":"w","value":79.5244986146}]},{"category":"C","timestamp":"2023-12-08 03:12:25","data":[{"key":"w","value":56.6377952915},{"key":"z","value":42.3533060413}]}]} -{"rowindex":77,"customer_id":"jkl","day":"2023-12-13","flag":2,"event_sequence":[{"category":"C","timestamp":"2023-12-13 12:14:14","data":[{"key":"w","value":35.2592201371},{"key":"y","value":13.5684896571}]}]} -{"rowindex":78,"customer_id":"abc","day":"2023-12-5","flag":2,"event_sequence":[{"category":"B","timestamp":"2023-12-05 19:22:58","data":[{"key":"z","value":66.2843566224}]},{"category":"B","timestamp":"2023-12-05 19:39:08","data":[{"key":"w","value":34.080531438}]},{"category":"C","timestamp":"2023-12-05 02:53:05","data":[{"key":"z","value":33.991374759},{"key":"x","value":80.0208062703}]},{"category":"D","timestamp":"2023-12-05 13:30:43","data":[{"key":"y","value":67.1306733907}]},{"category":"A","timestamp":"2023-12-05 00:51:36","data":[{"key":"w","value":17.3844088301}]}]} -{"rowindex":79,"customer_id":"mno","day":"2023-12-9","flag":1,"event_sequence":[{"category":"D","timestamp":"2023-12-09 10:36:18","data":[{"key":"y","value":17.9861379377},{"key":"x","value":31.1422706226}]},{"category":"A","timestamp":"2023-12-09 19:04:16","data":[]},{"category":"C","timestamp":"2023-12-09 23:46:25","data":[]},{"category":"B","timestamp":"2023-12-09 15:08:37","data":[]}]} -{"rowindex":80,"customer_id":"mno","day":"2023-12-11","flag":0,"event_sequence":[{"category":"C","timestamp":"2023-12-11 23:50:20","data":[]},{"category":"A","timestamp":"2023-12-11 13:45:37","data":[{"key":"y","value":34.1896555846},{"key":"z","value":54.8455987136}]},{"category":"D","timestamp":"2023-12-11 05:27:06","data":[{"key":"z","value":8.6439113664},{"key":"w","value":57.8679152847}]},{"category":"A","timestamp":"2023-12-11 22:56:07","data":[]},{"category":"D","timestamp":"2023-12-11 01:09:13","data":[{"key":"x","value":94.8088772326},{"key":"y","value":92.9817038325}]}]} -{"rowindex":81,"customer_id":"mno","day":"2023-12-2","flag":2,"event_sequence":[{"category":"D","timestamp":"2023-12-02 19:13:55","data":[{"key":"x","value":92.6140550812},{"key":"y","value":21.6844233156}]},{"category":"A","timestamp":"2023-12-02 10:19:54","data":[{"key":"z","value":96.1332346043},{"key":"y","value":12.3365763983}]},{"category":"C","timestamp":"2023-12-02 23:15:36","data":[]}]} -{"rowindex":82,"customer_id":"def","day":"2023-12-8","flag":2,"event_sequence":[{"category":"C","timestamp":"2023-12-08 03:43:45","data":[{"key":"z","value":39.7558930693}]},{"category":"A","timestamp":"2023-12-08 01:35:47","data":[]},{"category":"D","timestamp":"2023-12-08 04:53:02","data":[{"key":"x","value":3.1323563783}]},{"category":"B","timestamp":"2023-12-08 01:12:21","data":[{"key":"w","value":21.6503102051},{"key":"y","value":43.4536696853}]},{"category":"B","timestamp":"2023-12-08 01:57:25","data":[{"key":"z","value":11.3705979892},{"key":"y","value":85.3671308445}]}]} -{"rowindex":83,"customer_id":"mno","day":"2023-12-16","flag":0,"event_sequence":[]} -{"rowindex":84,"customer_id":"def","day":"2023-12-13","flag":0,"event_sequence":[]} -{"rowindex":85,"customer_id":"jkl","day":"2023-12-6","flag":2,"event_sequence":[{"category":"D","timestamp":"2023-12-06 18:06:14","data":[{"key":"w","value":75.6475285669},{"key":"y","value":92.2341481081}]},{"category":"B","timestamp":"2023-12-06 15:28:32","data":[]},{"category":"B","timestamp":"2023-12-06 19:45:52","data":[]},{"category":"C","timestamp":"2023-12-06 08:32:52","data":[]},{"category":"A","timestamp":"2023-12-06 17:32:37","data":[{"key":"y","value":80.2305875735}]}]} -{"rowindex":86,"customer_id":"abc","day":"2023-12-10","flag":2,"event_sequence":[{"category":"A","timestamp":"2023-12-10 09:34:20","data":[{"key":"y","value":10.7693525828},{"key":"w","value":81.4922282197}]},{"category":"C","timestamp":"2023-12-10 03:58:48","data":[{"key":"y","value":75.2926863125},{"key":"x","value":14.3834415502}]},{"category":"A","timestamp":"2023-12-10 09:09:24","data":[{"key":"z","value":26.1964055176},{"key":"w","value":33.2590307936}]},{"category":"C","timestamp":"2023-12-10 07:53:33","data":[{"key":"z","value":23.2141532358}]}]} -{"rowindex":87,"customer_id":"ghi","day":"2023-12-6","flag":1,"event_sequence":[{"category":"B","timestamp":"2023-12-06 17:59:34","data":[]},{"category":"B","timestamp":"2023-12-06 15:30:32","data":[{"key":"z","value":65.5093670838}]},{"category":"C","timestamp":"2023-12-06 11:40:36","data":[{"key":"z","value":19.0969232242}]},{"category":"C","timestamp":"2023-12-06 23:24:48","data":[{"key":"w","value":41.7328593069}]}]} -{"rowindex":88,"customer_id":"mno","day":"2023-12-4","flag":2,"event_sequence":[{"category":"D","timestamp":"2023-12-04 09:37:49","data":[{"key":"z","value":73.115183578},{"key":"w","value":55.409641057}]},{"category":"A","timestamp":"2023-12-04 20:25:06","data":[{"key":"x","value":68.225517069}]},{"category":"C","timestamp":"2023-12-04 02:46:08","data":[]},{"category":"A","timestamp":"2023-12-04 06:18:04","data":[{"key":"x","value":95.7957065313},{"key":"y","value":68.2634789529}]}]} -{"rowindex":89,"customer_id":"ghi","day":"2023-12-3","flag":2,"event_sequence":[{"category":"B","timestamp":"2023-12-03 07:02:27","data":[{"key":"w","value":17.0165951832}]},{"category":"D","timestamp":"2023-12-03 19:06:20","data":[]},{"category":"A","timestamp":"2023-12-03 17:50:14","data":[{"key":"x","value":4.3834633659},{"key":"z","value":84.6024255445}]},{"category":"C","timestamp":"2023-12-03 06:51:03","data":[]}]} -{"rowindex":90,"customer_id":"mno","day":"2023-12-12","flag":2,"event_sequence":[{"category":"B","timestamp":"2023-12-12 21:57:25","data":[{"key":"y","value":95.5058021347}]},{"category":"C","timestamp":"2023-12-12 07:24:27","data":[{"key":"z","value":17.9587475242}]},{"category":"A","timestamp":"2023-12-12 10:35:52","data":[{"key":"z","value":55.194876676}]},{"category":"D","timestamp":"2023-12-12 23:44:14","data":[{"key":"w","value":24.6177835891}]},{"category":"D","timestamp":"2023-12-12 16:09:40","data":[{"key":"y","value":32.2627525342},{"key":"x","value":77.4276051497}]}]} -{"rowindex":91,"customer_id":"abc","day":"2023-12-18","flag":0,"event_sequence":[]} -{"rowindex":92,"customer_id":"jkl","day":"2023-12-7","flag":0,"event_sequence":[{"category":"C","timestamp":"2023-12-07 11:36:31","data":[{"key":"w","value":70.4689420724}]},{"category":"A","timestamp":"2023-12-07 09:18:26","data":[{"key":"z","value":31.0551928628},{"key":"y","value":4.7472634353}]},{"category":"D","timestamp":"2023-12-07 05:44:09","data":[{"key":"z","value":37.7906214595},{"key":"w","value":38.618192046}]},{"category":"B","timestamp":"2023-12-07 16:30:31","data":[{"key":"y","value":92.4389663402}]},{"category":"A","timestamp":"2023-12-07 16:35:58","data":[{"key":"x","value":63.8398372162},{"key":"z","value":90.1325261576}]}]} -{"rowindex":93,"customer_id":"abc","day":"2023-12-15","flag":0,"event_sequence":[]} -{"rowindex":94,"customer_id":"mno","day":"2023-12-7","flag":0,"event_sequence":[]} -{"rowindex":95,"customer_id":"ghi","day":"2023-12-14","flag":2,"event_sequence":[{"category":"C","timestamp":"2023-12-14 22:37:13","data":[{"key":"x","value":55.3895966386}]},{"category":"B","timestamp":"2023-12-14 15:56:30","data":[{"key":"y","value":87.7140820119},{"key":"x","value":48.3079555774}]},{"category":"D","timestamp":"2023-12-14 06:35:41","data":[{"key":"y","value":60.4608873685},{"key":"x","value":74.6169412477}]}]} -{"rowindex":96,"customer_id":"def","day":"2023-12-1","flag":1,"event_sequence":[{"category":"D","timestamp":"2023-12-01 07:57:31","data":[{"key":"w","value":83.8985453363},{"key":"x","value":37.6937609678}]},{"category":"A","timestamp":"2023-12-01 00:44:54","data":[{"key":"w","value":65.3980461559}]},{"category":"D","timestamp":"2023-12-01 17:43:00","data":[]},{"category":"A","timestamp":"2023-12-01 02:48:33","data":[{"key":"z","value":23.8579933054}]},{"category":"B","timestamp":"2023-12-01 07:36:21","data":[{"key":"y","value":53.0811307247}]}]} -{"rowindex":97,"customer_id":"ghi","day":"2023-12-5","flag":0,"event_sequence":[{"category":"B","timestamp":"2023-12-05 09:23:03","data":[]},{"category":"C","timestamp":"2023-12-05 01:22:08","data":[]}]} -{"rowindex":98,"customer_id":"ghi","day":"2023-12-14","flag":2,"event_sequence":[]} -{"rowindex":99,"customer_id":"ghi","day":"2023-12-14","flag":1,"event_sequence":[{"category":"B","timestamp":"2023-12-14 15:46:06","data":[{"key":"z","value":48.2733214833}]},{"category":"D","timestamp":"2023-12-14 15:39:56","data":[]},{"category":"D","timestamp":"2023-12-14 17:18:14","data":[]},{"category":"D","timestamp":"2023-12-14 02:41:54","data":[{"key":"z","value":98.7008514491},{"key":"x","value":55.3757151027}]},{"category":"C","timestamp":"2023-12-14 07:54:49","data":[{"key":"z","value":69.8181005179}]}]} diff --git a/tests/data/nested_schema.json b/tests/data/nested_schema.json deleted file mode 100644 index 2b843bb395d..00000000000 --- a/tests/data/nested_schema.json +++ /dev/null @@ -1,84 +0,0 @@ -[ - { - "mode": "REQUIRED", - "name": "rowindex", - "type": "INTEGER" - }, - { - "mode": "NULLABLE", - "name": "customer_id", - "type": "STRING" - }, - { - "mode": "NULLABLE", - "name": "day", - "type": "DATE" - }, - { - "mode": "NULLABLE", - "name": "flag", - "type": "INTEGER" - }, - { - "fields": [ - { - "name": "key", - "type": "STRING" - }, - { - "name": "value", - "type": "STRING" - } - ], - "name": "label", - "type": "RECORD" - }, - { - "fields": [ - { - "fields": [ - { - "mode": "NULLABLE", - "name": "value", - "type": "FLOAT" - }, - { - "mode": "NULLABLE", - "name": "key", - "type": "STRING" - } - ], - "mode": "REPEATED", - "name": "data", - "type": "RECORD" - }, - { - "mode": "NULLABLE", - "name": "timestamp", - "type": "TIMESTAMP" - }, - { - "mode": "NULLABLE", - "name": "category", - "type": "STRING" - } - ], - "mode": "REPEATED", - "name": "event_sequence", - "type": "RECORD" - }, - { - "fields": [ - { - "name": "street", - "type": "STRING" - }, - { - "name": "city", - "type": "STRING" - } - ], - "name": "address", - "type": "RECORD" - } -] diff --git a/tests/data/nested_structs.jsonl b/tests/data/nested_structs.jsonl deleted file mode 100644 index 97e230c9197..00000000000 --- a/tests/data/nested_structs.jsonl +++ /dev/null @@ -1,6 +0,0 @@ -{"id": 1, "person": {"name": "Alice", "age": 30, "address": {"city": "New York", "country": "USA"}}, "bool_col": true, "int64_col": "123456789", "float64_col": 1.25, "string_col": "Hello World", "json_col": {"a": 1, "b": [1, 2]}, "date_col": "2026-06-24", "time_col": "12:34:56.789012", "datetime_col": "2026-06-24 12:34:56.789012", "timestamp_col": "2026-06-24T12:34:56.789012Z", "bytes_col": "SGVsbG8=", "numeric_col": "123456.789", "bignumeric_col": "123456.7890123456789", "geography_col": "POINT(30 10)", "duration_col": "1000"} -{"id": 2, "person": {"name": "", "age": -1, "address": {"city": "", "country": ""}}, "bool_col": false, "int64_col": "-9223372036854775808", "float64_col": "-Infinity", "string_col": "", "json_col": {}, "date_col": "0001-01-01", "time_col": "00:00:00", "datetime_col": "0001-01-02 00:00:00", "timestamp_col": "0001-01-02T00:00:00Z", "bytes_col": "", "numeric_col": "-99999999999999999999999999999.999999999", "bignumeric_col": "-99999999999999999999999999999999999999.99999999999999999999999999999999999999", "geography_col": "POINT(0 0)", "duration_col": "-9223372036854775"} -{"id": 3, "person": {"name": "Very Long Name...", "age": 150, "address": {"city": "City", "country": "Country"}}, "bool_col": true, "int64_col": "9223372036854775807", "float64_col": "Infinity", "string_col": "Unicode: 🚀 Spark ✨", "json_col": {"max": true, "nested": {"val": 999}}, "date_col": "9999-12-31", "time_col": "23:59:59.999999", "datetime_col": "9999-12-31 23:59:59.999999", "timestamp_col": "9999-12-31T23:59:59.999999Z", "bytes_col": "dmVyeSBsb25nIGJ5dGVzIHZhbHVl", "numeric_col": "99999999999999999999999999999.999999999", "bignumeric_col": "99999999999999999999999999999999999999.99999999999999999999999999999999999999", "geography_col": "POLYGON((0 0, 10 0, 10 10, 0 10, 0 0))", "duration_col": "9223372036854775"} -{"id": 4, "person": null, "bool_col": null, "int64_col": null, "float64_col": null, "string_col": null, "date_col": null, "time_col": null, "datetime_col": null, "timestamp_col": null, "bytes_col": null, "numeric_col": null, "bignumeric_col": null, "geography_col": null, "duration_col": null} -{"id": 5, "person": {"name": "Bob", "age": 0, "address": null}, "bool_col": false, "int64_col": "0", "float64_col": "NaN", "string_col": "Line 1\nLine 2\n\"Quotes\"", "json_col": [1, "two", null], "date_col": "1970-01-01", "time_col": "12:00:00", "datetime_col": "1970-01-01 12:00:00", "timestamp_col": "1970-01-01T12:00:00Z", "bytes_col": "AA==", "numeric_col": "0", "bignumeric_col": "0", "geography_col": "LINESTRING(0 0, 1 1, 2 2)", "duration_col": "0"} -{"id": 6, "person": null, "bool_col": null, "int64_col": null, "float64_col": null, "string_col": null, "json_col": null, "date_col": null, "time_col": null, "datetime_col": null, "timestamp_col": null, "bytes_col": null, "numeric_col": null, "bignumeric_col": null, "geography_col": null, "duration_col": null} diff --git a/tests/data/nested_structs_schema.json b/tests/data/nested_structs_schema.json deleted file mode 100644 index 06e4a3e5275..00000000000 --- a/tests/data/nested_structs_schema.json +++ /dev/null @@ -1,112 +0,0 @@ -[ - { - "name": "id", - "type": "INTEGER", - "mode": "REQUIRED" - }, - { - "name": "person", - "type": "RECORD", - "mode": "NULLABLE", - "fields": [ - { - "name": "name", - "type": "STRING", - "mode": "NULLABLE" - }, - { - "name": "age", - "type": "INTEGER", - "mode": "NULLABLE" - }, - { - "name": "address", - "type": "RECORD", - "mode": "NULLABLE", - "fields": [ - { - "name": "city", - "type": "STRING", - "mode": "NULLABLE" - }, - { - "name": "country", - "type": "STRING", - "mode": "NULLABLE" - } - ] - } - ] - }, - { - "name": "bool_col", - "type": "BOOLEAN", - "mode": "NULLABLE" - }, - { - "name": "int64_col", - "type": "INTEGER", - "mode": "NULLABLE" - }, - { - "name": "float64_col", - "type": "FLOAT", - "mode": "NULLABLE" - }, - { - "name": "string_col", - "type": "STRING", - "mode": "NULLABLE" - }, - { - "name": "json_col", - "type": "JSON", - "mode": "NULLABLE" - }, - { - "name": "date_col", - "type": "DATE", - "mode": "NULLABLE" - }, - { - "name": "time_col", - "type": "TIME", - "mode": "NULLABLE" - }, - { - "name": "datetime_col", - "type": "DATETIME", - "mode": "NULLABLE" - }, - { - "name": "timestamp_col", - "type": "TIMESTAMP", - "mode": "NULLABLE" - }, - { - "name": "bytes_col", - "type": "BYTES", - "mode": "NULLABLE" - }, - { - "name": "numeric_col", - "type": "NUMERIC", - "mode": "NULLABLE" - }, - { - "name": "bignumeric_col", - "type": "BIGNUMERIC", - "mode": "NULLABLE" - }, - { - "name": "geography_col", - "type": "GEOGRAPHY", - "mode": "NULLABLE" - }, - { - "name": "duration_col", - "type": "INTEGER", - "mode": "NULLABLE", - "description": "#microseconds" - } -] diff --git a/tests/data/people.csv b/tests/data/people.csv deleted file mode 100644 index f5f9998b82c..00000000000 --- a/tests/data/people.csv +++ /dev/null @@ -1,4 +0,0 @@ -Name,Age,City -Alice,25,New York -Bob,30,London -Charlie,22,Paris diff --git a/tests/data/ratings.jsonl b/tests/data/ratings.jsonl deleted file mode 100644 index b7cd350d085..00000000000 --- a/tests/data/ratings.jsonl +++ /dev/null @@ -1,20 +0,0 @@ -{"user_id": 1, "item_id": 2, "rating": 4.0} -{"user_id": 1, "item_id": 5, "rating": 3.0} -{"user_id": 2, "item_id": 1, "rating": 5.0} -{"user_id": 2, "item_id": 3, "rating": 2.0} -{"user_id": 3, "item_id": 4, "rating": 4.5} -{"user_id": 3, "item_id": 7, "rating": 3.5} -{"user_id": 4, "item_id": 2, "rating": 1.0} -{"user_id": 4, "item_id": 8, "rating": 5.0} -{"user_id": 5, "item_id": 3, "rating": 4.0} -{"user_id": 5, "item_id": 9, "rating": 2.5} -{"user_id": 6, "item_id": 1, "rating": 3.0} -{"user_id": 6, "item_id": 6, "rating": 4.5} -{"user_id": 7, "item_id": 5, "rating": 5.0} -{"user_id": 7, "item_id": 10, "rating": 1.5} -{"user_id": 8, "item_id": 4, "rating": 2.0} -{"user_id": 8, "item_id": 7, "rating": 4.0} -{"user_id": 9, "item_id": 2, "rating": 3.5} -{"user_id": 9, "item_id": 9, "rating": 5.0} -{"user_id": 10, "item_id": 3, "rating": 4.5} -{"user_id": 10, "item_id": 8, "rating": 2.5} diff --git a/tests/data/ratings_schema.json b/tests/data/ratings_schema.json deleted file mode 100644 index 9fd0101ec80..00000000000 --- a/tests/data/ratings_schema.json +++ /dev/null @@ -1,17 +0,0 @@ -[ - { - "mode": "NULLABLE", - "name": "user_id", - "type": "STRING" - }, - { - "mode": "NULLABLE", - "name": "item_id", - "type": "INT64" - }, - { - "mode": "NULLABLE", - "name": "rating", - "type": "FLOAT" - } -] diff --git a/tests/data/repeated.jsonl b/tests/data/repeated.jsonl deleted file mode 100644 index b3c47772f6d..00000000000 --- a/tests/data/repeated.jsonl +++ /dev/null @@ -1,3 +0,0 @@ -{"rowindex": 0, "int_list_col": [1], "bool_list_col": [true], "float_list_col": [1.2, 2.3], "date_list_col": ["2021-07-21"], "date_time_list_col": ["2021-07-21 11:39:45"], "numeric_list_col": [1.2, 2.3, 3.4], "string_list_col": ["abc", "de", "f"]} -{"rowindex": 1, "int_list_col": [1,2], "bool_list_col": [true, false], "float_list_col": [1.1], "date_list_col": ["2021-07-21", "1987-03-28"], "date_time_list_col": ["1999-03-14 17:22:00"], "numeric_list_col": [5.5, 2.3], "string_list_col": ["a", "bc", "de"]} -{"rowindex": 2, "int_list_col": [1,2,3], "bool_list_col": [true], "float_list_col": [0.5, -1.9, 2.3], "date_list_col": ["2017-08-01", "2004-11-22"], "date_time_list_col": ["1979-06-03 03:20:45"], "numeric_list_col": [1.7], "string_list_col": ["", "a"]} diff --git a/tests/data/repeated_schema.json b/tests/data/repeated_schema.json deleted file mode 100644 index 300f32c994c..00000000000 --- a/tests/data/repeated_schema.json +++ /dev/null @@ -1,42 +0,0 @@ -[ - { - "name": "rowindex", - "type": "INTEGER", - "mode": "REQUIRED" - }, - { - "name": "int_list_col", - "type": "INTEGER", - "mode": "REPEATED" - }, - { - "name": "bool_list_col", - "type": "BOOLEAN", - "mode": "REPEATED" - }, - { - "name": "float_list_col", - "type": "FLOAT", - "mode": "REPEATED" - }, - { - "name": "date_list_col", - "type": "DATE", - "mode": "REPEATED" - }, - { - "name": "date_time_list_col", - "type": "DATETIME", - "mode": "REPEATED" - }, - { - "name": "numeric_list_col", - "type": "NUMERIC", - "mode": "REPEATED" - }, - { - "name": "string_list_col", - "type": "STRING", - "mode": "REPEATED" - } -] diff --git a/tests/data/scalars.jsonl b/tests/data/scalars.jsonl index 6e591cfa728..172a55ec11a 100644 --- a/tests/data/scalars.jsonl +++ b/tests/data/scalars.jsonl @@ -1,9 +1,9 @@ -{"bool_col": true, "bytes_col": "SGVsbG8sIFdvcmxkIQ==", "date_col": "2021-07-21", "datetime_col": "2021-07-21 11:39:45", "geography_col": "POINT(-122.0838511 37.3860517)", "int64_col": "123456789", "int64_too": "0", "numeric_col": "1.23456789", "float64_col": "1.25", "rowindex": 0, "rowindex_2": 0, "string_col": "Hello, World!", "time_col": "11:41:43.076160", "timestamp_col": "2021-07-21T17:43:43.945289Z", "duration_col": 4} -{"bool_col": false, "bytes_col": "44GT44KT44Gr44Gh44Gv", "date_col": "1991-02-03", "datetime_col": "1991-01-02 03:45:06", "geography_col": "POINT(-71.104 42.315)", "int64_col": "-987654321", "int64_too": "1", "numeric_col": "1.23456789", "float64_col": "2.51", "rowindex": 1, "rowindex_2": 1, "string_col": "こんにちは", "time_col": "11:14:34.701606", "timestamp_col": "2021-07-21T17:43:43.945289Z", "duration_col": -1000000} -{"bool_col": true, "bytes_col": "wqFIb2xhIE11bmRvIQ==", "date_col": "2023-03-01", "datetime_col": "2023-03-01 10:55:13", "geography_col": "POINT(-0.124474760143016 51.5007826749545)", "int64_col": "314159", "int64_too": "0", "numeric_col": "101.1010101", "float64_col": "2.5e10", "rowindex": 2, "rowindex_2": 2, "string_col": " ¡Hola Mundo! ", "time_col": "23:59:59.999999", "timestamp_col": "2023-03-01T10:55:13.250125Z", "duration_col": 0} -{"bool_col": null, "bytes_col": null, "date_col": null, "datetime_col": null, "geography_col": null, "int64_col": null, "int64_too": "1", "numeric_col": null, "float64_col": null, "rowindex": 3, "rowindex_2": 3, "string_col": null, "time_col": null, "timestamp_col": null, "duration_col": null} -{"bool_col": false, "bytes_col": "44GT44KT44Gr44Gh44Gv", "date_col": "2021-07-21", "datetime_col": null, "geography_col": null, "int64_col": "-234892", "int64_too": "-2345", "numeric_col": null, "float64_col": null, "rowindex": 4, "rowindex_2": 4, "string_col": "Hello, World!", "time_col": null, "timestamp_col": null, "duration_col": 31540000000000} -{"bool_col": false, "bytes_col": "R8O8dGVuIFRhZw==", "date_col": "1980-03-14", "datetime_col": "1980-03-14 15:16:17", "geography_col": null, "int64_col": "55555", "int64_too": "0", "numeric_col": "5.555555", "float64_col": "555.555", "rowindex": 5, "rowindex_2": 5, "string_col": "Güten Tag!", "time_col": "15:16:17.181921", "timestamp_col": "1980-03-14T15:16:17.181921Z", "duration_col": 4} -{"bool_col": true, "bytes_col": "SGVsbG8JQmlnRnJhbWVzIQc=", "date_col": "2023-05-23", "datetime_col": "2023-05-23 11:37:01", "geography_col": "LINESTRING(-0.127959 51.507728, -0.127026 51.507473)", "int64_col": "101202303", "int64_too": "2", "numeric_col": "-10.090807", "float64_col": "-123.456", "rowindex": 6, "rowindex_2": 6, "string_col": "capitalize, This ", "time_col": "01:02:03.456789", "timestamp_col": "2023-05-23T11:42:55.000001Z", "duration_col": null} -{"bool_col": true, "bytes_col": null, "date_col": "2038-01-20", "datetime_col": "2038-01-19 03:14:08", "geography_col": null, "int64_col": "-214748367", "int64_too": "2", "numeric_col": "11111111.1", "float64_col": "42.42", "rowindex": 7, "rowindex_2": 7, "string_col": " سلام", "time_col": "12:00:00.000001", "timestamp_col": "2038-01-19T03:14:17.999999Z", "duration_col": 4} -{"bool_col": false, "bytes_col": null, "date_col": null, "datetime_col": null, "geography_col": null, "int64_col": "2", "int64_too": "1", "numeric_col": null, "float64_col": "6.87", "rowindex": 8, "rowindex_2": 8, "string_col": "T", "time_col": null, "timestamp_col": null, "duration_col": 432000000000} +{"bool_col": true, "bytes_col": "SGVsbG8sIFdvcmxkIQ==", "date_col": "2021-07-21", "datetime_col": "2021-07-21 11:39:45", "geography_col": "POINT(-122.0838511 37.3860517)", "int64_col": "123456789", "int64_too": "0", "numeric_col": "1.23456789", "float64_col": "1.25", "rowindex": 0, "rowindex_2": 0, "string_col": "Hello, World!", "time_col": "11:41:43.076160", "timestamp_col": "2021-07-21T17:43:43.945289Z"} +{"bool_col": false, "bytes_col": "44GT44KT44Gr44Gh44Gv", "date_col": "1991-02-03", "datetime_col": "1991-01-02 03:45:06", "geography_col": "POINT(-71.104 42.315)", "int64_col": "-987654321", "int64_too": "1", "numeric_col": "1.23456789", "float64_col": "2.51", "rowindex": 1, "rowindex_2": 1, "string_col": "こんにちは", "time_col": "11:14:34.701606", "timestamp_col": "2021-07-21T17:43:43.945289Z"} +{"bool_col": true, "bytes_col": "wqFIb2xhIE11bmRvIQ==", "date_col": "2023-03-01", "datetime_col": "2023-03-01 10:55:13", "geography_col": "POINT(-0.124474760143016 51.5007826749545)", "int64_col": "314159", "int64_too": "0", "numeric_col": "101.1010101", "float64_col": "2.5e10", "rowindex": 2, "rowindex_2": 2, "string_col": " ¡Hola Mundo! ", "time_col": "23:59:59.999999", "timestamp_col": "2023-03-01T10:55:13.250125Z"} +{"bool_col": null, "bytes_col": null, "date_col": null, "datetime_col": null, "geography_col": null, "int64_col": null, "int64_too": "1", "numeric_col": null, "float64_col": null, "rowindex": 3, "rowindex_2": 3, "string_col": null, "time_col": null, "timestamp_col": null} +{"bool_col": false, "bytes_col": "44GT44KT44Gr44Gh44Gv", "date_col": "2021-07-21", "datetime_col": null, "geography_col": null, "int64_col": "-234892", "int64_too": "-2345", "numeric_col": null, "float64_col": null, "rowindex": 4, "rowindex_2": 4, "string_col": "Hello, World!", "time_col": null, "timestamp_col": null} +{"bool_col": false, "bytes_col": "R8O8dGVuIFRhZw==", "date_col": "1980-03-14", "datetime_col": "1980-03-14 15:16:17", "geography_col": null, "int64_col": "55555", "int64_too": "0", "numeric_col": "5.555555", "float64_col": "555.555", "rowindex": 5, "rowindex_2": 5, "string_col": "Güten Tag!", "time_col": "15:16:17.181921", "timestamp_col": "1980-03-14T15:16:17.181921Z"} +{"bool_col": true, "bytes_col": "SGVsbG8JQmlnRnJhbWVzIQc=", "date_col": "2023-05-23", "datetime_col": "2023-05-23 11:37:01", "geography_col": "MULTIPOINT (20 20, 10 40, 40 30, 30 10)", "int64_col": "101202303", "int64_too": "2", "numeric_col": "-10.090807", "float64_col": "-123.456", "rowindex": 6, "rowindex_2": 6, "string_col": "capitalize, This ", "time_col": "01:02:03.456789", "timestamp_col": "2023-05-23T11:42:55.000001Z"} +{"bool_col": true, "bytes_col": null, "date_col": "2038-01-20", "datetime_col": "2038-01-19 03:14:08", "geography_col": null, "int64_col": "-214748367", "int64_too": "2", "numeric_col": "11111111.1", "float64_col": "42.42", "rowindex": 7, "rowindex_2": 7, "string_col": " سلام", "time_col": "12:00:00.000001", "timestamp_col": "2038-01-19T03:14:17.999999Z"} +{"bool_col": false, "bytes_col": null, "date_col": null, "datetime_col": null, "geography_col": null, "int64_col": "2", "int64_too": "1", "numeric_col": null, "float64_col": "6.87", "rowindex": 8, "rowindex_2": 8, "string_col": "T", "time_col": null, "timestamp_col": null} diff --git a/tests/data/scalars_schema.json b/tests/data/scalars_schema.json index 8be4e952288..1f5d8cdb650 100644 --- a/tests/data/scalars_schema.json +++ b/tests/data/scalars_schema.json @@ -71,11 +71,5 @@ "mode": "NULLABLE", "name": "timestamp_col", "type": "TIMESTAMP" - }, - { - "mode": "NULLABLE", - "name": "duration_col", - "type": "INTEGER", - "description": "#microseconds" } ] diff --git a/tests/data/time_series.jsonl b/tests/data/time_series.jsonl index 329e5a8b61f..e0f9ca7ae25 100644 --- a/tests/data/time_series.jsonl +++ b/tests/data/time_series.jsonl @@ -1,732 +1,366 @@ -{"parsed_date":"2017-07-01 00:00:00 UTC","id":"1","total_visits":"2048"} -{"parsed_date":"2016-09-07 00:00:00 UTC","id":"1","total_visits":"2562"} -{"parsed_date":"2016-10-25 00:00:00 UTC","id":"1","total_visits":"3842"} -{"parsed_date":"2017-04-10 00:00:00 UTC","id":"1","total_visits":"2563"} -{"parsed_date":"2017-01-09 00:00:00 UTC","id":"1","total_visits":"2308"} -{"parsed_date":"2017-05-02 00:00:00 UTC","id":"1","total_visits":"2564"} -{"parsed_date":"2016-11-11 00:00:00 UTC","id":"1","total_visits":"3588"} -{"parsed_date":"2017-07-30 00:00:00 UTC","id":"1","total_visits":"1799"} -{"parsed_date":"2017-06-10 00:00:00 UTC","id":"1","total_visits":"1545"} -{"parsed_date":"2016-08-14 00:00:00 UTC","id":"1","total_visits":"1801"} -{"parsed_date":"2017-05-14 00:00:00 UTC","id":"1","total_visits":"1290"} -{"parsed_date":"2017-02-08 00:00:00 UTC","id":"1","total_visits":"2570"} -{"parsed_date":"2017-06-01 00:00:00 UTC","id":"1","total_visits":"2826"} -{"parsed_date":"2017-04-23 00:00:00 UTC","id":"1","total_visits":"1548"} -{"parsed_date":"2016-11-04 00:00:00 UTC","id":"1","total_visits":"3596"} -{"parsed_date":"2017-02-04 00:00:00 UTC","id":"1","total_visits":"1549"} -{"parsed_date":"2016-12-09 00:00:00 UTC","id":"1","total_visits":"2830"} -{"parsed_date":"2016-10-30 00:00:00 UTC","id":"1","total_visits":"3086"} -{"parsed_date":"2017-03-28 00:00:00 UTC","id":"1","total_visits":"2577"} -{"parsed_date":"2017-06-11 00:00:00 UTC","id":"1","total_visits":"1555"} -{"parsed_date":"2016-12-17 00:00:00 UTC","id":"1","total_visits":"2324"} -{"parsed_date":"2016-09-22 00:00:00 UTC","id":"1","total_visits":"2581"} -{"parsed_date":"2017-01-29 00:00:00 UTC","id":"1","total_visits":"1814"} -{"parsed_date":"2017-03-22 00:00:00 UTC","id":"1","total_visits":"2582"} -{"parsed_date":"2017-02-21 00:00:00 UTC","id":"1","total_visits":"2582"} -{"parsed_date":"2016-10-14 00:00:00 UTC","id":"1","total_visits":"2838"} -{"parsed_date":"2017-04-27 00:00:00 UTC","id":"1","total_visits":"2838"} -{"parsed_date":"2016-10-26 00:00:00 UTC","id":"1","total_visits":"4375"} -{"parsed_date":"2016-08-22 00:00:00 UTC","id":"1","total_visits":"2584"} -{"parsed_date":"2016-12-07 00:00:00 UTC","id":"1","total_visits":"2840"} -{"parsed_date":"2017-01-20 00:00:00 UTC","id":"1","total_visits":"2074"} -{"parsed_date":"2017-03-07 00:00:00 UTC","id":"1","total_visits":"2586"} -{"parsed_date":"2017-05-16 00:00:00 UTC","id":"1","total_visits":"3098"} -{"parsed_date":"2017-05-03 00:00:00 UTC","id":"1","total_visits":"2588"} -{"parsed_date":"2017-05-01 00:00:00 UTC","id":"1","total_visits":"2588"} -{"parsed_date":"2016-11-27 00:00:00 UTC","id":"1","total_visits":"3356"} -{"parsed_date":"2017-04-29 00:00:00 UTC","id":"1","total_visits":"1566"} -{"parsed_date":"2016-09-18 00:00:00 UTC","id":"1","total_visits":"1822"} -{"parsed_date":"2017-03-23 00:00:00 UTC","id":"1","total_visits":"2847"} -{"parsed_date":"2017-03-14 00:00:00 UTC","id":"1","total_visits":"2338"} -{"parsed_date":"2016-12-21 00:00:00 UTC","id":"1","total_visits":"2594"} -{"parsed_date":"2016-10-11 00:00:00 UTC","id":"1","total_visits":"2850"} -{"parsed_date":"2017-01-24 00:00:00 UTC","id":"1","total_visits":"3618"} -{"parsed_date":"2017-03-05 00:00:00 UTC","id":"1","total_visits":"1827"} -{"parsed_date":"2017-01-19 00:00:00 UTC","id":"1","total_visits":"2083"} -{"parsed_date":"2016-08-09 00:00:00 UTC","id":"1","total_visits":"2851"} -{"parsed_date":"2017-04-08 00:00:00 UTC","id":"1","total_visits":"1829"} -{"parsed_date":"2017-04-12 00:00:00 UTC","id":"1","total_visits":"2341"} -{"parsed_date":"2016-09-29 00:00:00 UTC","id":"1","total_visits":"2597"} -{"parsed_date":"2016-12-20 00:00:00 UTC","id":"1","total_visits":"3110"} -{"parsed_date":"2017-01-15 00:00:00 UTC","id":"1","total_visits":"1576"} -{"parsed_date":"2017-04-14 00:00:00 UTC","id":"1","total_visits":"1834"} -{"parsed_date":"2017-02-28 00:00:00 UTC","id":"1","total_visits":"2347"} -{"parsed_date":"2016-09-16 00:00:00 UTC","id":"1","total_visits":"2603"} -{"parsed_date":"2016-10-18 00:00:00 UTC","id":"1","total_visits":"3628"} -{"parsed_date":"2017-02-24 00:00:00 UTC","id":"1","total_visits":"2093"} -{"parsed_date":"2017-05-17 00:00:00 UTC","id":"1","total_visits":"3117"} -{"parsed_date":"2017-06-23 00:00:00 UTC","id":"1","total_visits":"2095"} -{"parsed_date":"2016-11-12 00:00:00 UTC","id":"1","total_visits":"3119"} -{"parsed_date":"2016-11-21 00:00:00 UTC","id":"1","total_visits":"4143"} -{"parsed_date":"2017-02-27 00:00:00 UTC","id":"1","total_visits":"2352"} -{"parsed_date":"2016-12-26 00:00:00 UTC","id":"1","total_visits":"1586"} -{"parsed_date":"2017-04-25 00:00:00 UTC","id":"1","total_visits":"2354"} -{"parsed_date":"2017-03-21 00:00:00 UTC","id":"1","total_visits":"2611"} -{"parsed_date":"2016-12-22 00:00:00 UTC","id":"1","total_visits":"2100"} -{"parsed_date":"2016-10-01 00:00:00 UTC","id":"1","total_visits":"1589"} -{"parsed_date":"2016-09-24 00:00:00 UTC","id":"1","total_visits":"1845"} -{"parsed_date":"2017-06-21 00:00:00 UTC","id":"1","total_visits":"2357"} -{"parsed_date":"2016-09-02 00:00:00 UTC","id":"1","total_visits":"2613"} -{"parsed_date":"2016-08-26 00:00:00 UTC","id":"1","total_visits":"2359"} -{"parsed_date":"2016-10-12 00:00:00 UTC","id":"1","total_visits":"2871"} -{"parsed_date":"2017-05-15 00:00:00 UTC","id":"1","total_visits":"2360"} -{"parsed_date":"2017-06-12 00:00:00 UTC","id":"1","total_visits":"2361"} -{"parsed_date":"2016-08-16 00:00:00 UTC","id":"1","total_visits":"2873"} -{"parsed_date":"2017-04-30 00:00:00 UTC","id":"1","total_visits":"1594"} -{"parsed_date":"2017-04-05 00:00:00 UTC","id":"1","total_visits":"2619"} -{"parsed_date":"2016-08-12 00:00:00 UTC","id":"1","total_visits":"2619"} -{"parsed_date":"2016-11-08 00:00:00 UTC","id":"1","total_visits":"3899"} -{"parsed_date":"2016-08-13 00:00:00 UTC","id":"1","total_visits":"1596"} -{"parsed_date":"2017-05-09 00:00:00 UTC","id":"1","total_visits":"2108"} -{"parsed_date":"2017-02-23 00:00:00 UTC","id":"1","total_visits":"2364"} -{"parsed_date":"2017-07-31 00:00:00 UTC","id":"1","total_visits":"2620"} -{"parsed_date":"2017-06-25 00:00:00 UTC","id":"1","total_visits":"1597"} -{"parsed_date":"2017-07-29 00:00:00 UTC","id":"1","total_visits":"1597"} -{"parsed_date":"2016-09-17 00:00:00 UTC","id":"1","total_visits":"1853"} -{"parsed_date":"2016-12-27 00:00:00 UTC","id":"1","total_visits":"1855"} -{"parsed_date":"2017-05-20 00:00:00 UTC","id":"1","total_visits":"1855"} -{"parsed_date":"2016-10-08 00:00:00 UTC","id":"1","total_visits":"2114"} -{"parsed_date":"2016-10-27 00:00:00 UTC","id":"1","total_visits":"4162"} -{"parsed_date":"2017-07-08 00:00:00 UTC","id":"1","total_visits":"1859"} -{"parsed_date":"2016-08-24 00:00:00 UTC","id":"1","total_visits":"2627"} -{"parsed_date":"2016-12-23 00:00:00 UTC","id":"1","total_visits":"1604"} -{"parsed_date":"2017-02-02 00:00:00 UTC","id":"1","total_visits":"2372"} -{"parsed_date":"2016-09-08 00:00:00 UTC","id":"1","total_visits":"2628"} -{"parsed_date":"2017-04-02 00:00:00 UTC","id":"1","total_visits":"1861"} -{"parsed_date":"2017-02-15 00:00:00 UTC","id":"1","total_visits":"2629"} -{"parsed_date":"2017-07-05 00:00:00 UTC","id":"1","total_visits":"2885"} -{"parsed_date":"2016-10-17 00:00:00 UTC","id":"1","total_visits":"3397"} -{"parsed_date":"2017-02-20 00:00:00 UTC","id":"1","total_visits":"2374"} -{"parsed_date":"2017-03-24 00:00:00 UTC","id":"1","total_visits":"2374"} -{"parsed_date":"2017-04-20 00:00:00 UTC","id":"1","total_visits":"2374"} -{"parsed_date":"2016-11-18 00:00:00 UTC","id":"1","total_visits":"3654"} -{"parsed_date":"2017-07-25 00:00:00 UTC","id":"1","total_visits":"2631"} -{"parsed_date":"2016-11-13 00:00:00 UTC","id":"1","total_visits":"3144"} -{"parsed_date":"2017-03-18 00:00:00 UTC","id":"1","total_visits":"1610"} -{"parsed_date":"2016-08-03 00:00:00 UTC","id":"1","total_visits":"2890"} -{"parsed_date":"2016-08-19 00:00:00 UTC","id":"1","total_visits":"2379"} -{"parsed_date":"2017-02-14 00:00:00 UTC","id":"1","total_visits":"2379"} -{"parsed_date":"2017-07-11 00:00:00 UTC","id":"1","total_visits":"2635"} -{"parsed_date":"2017-04-22 00:00:00 UTC","id":"1","total_visits":"1612"} -{"parsed_date":"2016-10-07 00:00:00 UTC","id":"1","total_visits":"2892"} -{"parsed_date":"2016-09-05 00:00:00 UTC","id":"1","total_visits":"2125"} -{"parsed_date":"2016-09-23 00:00:00 UTC","id":"1","total_visits":"2381"} -{"parsed_date":"2016-11-15 00:00:00 UTC","id":"1","total_visits":"4685"} -{"parsed_date":"2017-01-28 00:00:00 UTC","id":"1","total_visits":"1614"} -{"parsed_date":"2017-07-14 00:00:00 UTC","id":"1","total_visits":"2382"} -{"parsed_date":"2017-01-07 00:00:00 UTC","id":"1","total_visits":"1615"} -{"parsed_date":"2017-04-03 00:00:00 UTC","id":"1","total_visits":"2383"} -{"parsed_date":"2017-03-20 00:00:00 UTC","id":"1","total_visits":"2383"} -{"parsed_date":"2016-12-18 00:00:00 UTC","id":"1","total_visits":"2128"} -{"parsed_date":"2017-03-17 00:00:00 UTC","id":"1","total_visits":"2129"} -{"parsed_date":"2017-05-23 00:00:00 UTC","id":"1","total_visits":"2129"} -{"parsed_date":"2016-11-30 00:00:00 UTC","id":"1","total_visits":"4435"} -{"parsed_date":"2017-01-01 00:00:00 UTC","id":"1","total_visits":"1364"} -{"parsed_date":"2017-01-02 00:00:00 UTC","id":"1","total_visits":"1620"} -{"parsed_date":"2016-09-25 00:00:00 UTC","id":"1","total_visits":"1877"} -{"parsed_date":"2016-08-07 00:00:00 UTC","id":"1","total_visits":"1622"} -{"parsed_date":"2016-10-09 00:00:00 UTC","id":"1","total_visits":"2134"} -{"parsed_date":"2017-03-01 00:00:00 UTC","id":"1","total_visits":"2390"} -{"parsed_date":"2017-01-04 00:00:00 UTC","id":"1","total_visits":"2390"} -{"parsed_date":"2017-06-06 00:00:00 UTC","id":"1","total_visits":"2391"} -{"parsed_date":"2017-04-18 00:00:00 UTC","id":"1","total_visits":"2391"} -{"parsed_date":"2017-04-06 00:00:00 UTC","id":"1","total_visits":"2647"} -{"parsed_date":"2017-01-30 00:00:00 UTC","id":"1","total_visits":"2392"} -{"parsed_date":"2016-10-16 00:00:00 UTC","id":"1","total_visits":"2649"} -{"parsed_date":"2016-08-04 00:00:00 UTC","id":"1","total_visits":"3161"} -{"parsed_date":"2016-10-21 00:00:00 UTC","id":"1","total_visits":"3419"} -{"parsed_date":"2016-08-02 00:00:00 UTC","id":"1","total_visits":"2140"} -{"parsed_date":"2017-03-06 00:00:00 UTC","id":"1","total_visits":"2396"} -{"parsed_date":"2016-09-13 00:00:00 UTC","id":"1","total_visits":"2396"} -{"parsed_date":"2016-09-14 00:00:00 UTC","id":"1","total_visits":"2652"} -{"parsed_date":"2017-04-19 00:00:00 UTC","id":"1","total_visits":"2397"} -{"parsed_date":"2017-06-19 00:00:00 UTC","id":"1","total_visits":"2142"} -{"parsed_date":"2016-12-13 00:00:00 UTC","id":"1","total_visits":"3166"} -{"parsed_date":"2017-06-20 00:00:00 UTC","id":"1","total_visits":"2143"} -{"parsed_date":"2016-10-10 00:00:00 UTC","id":"1","total_visits":"2911"} -{"parsed_date":"2017-07-06 00:00:00 UTC","id":"1","total_visits":"2658"} -{"parsed_date":"2017-01-03 00:00:00 UTC","id":"1","total_visits":"2403"} -{"parsed_date":"2017-01-08 00:00:00 UTC","id":"1","total_visits":"1637"} -{"parsed_date":"2017-02-25 00:00:00 UTC","id":"1","total_visits":"1638"} -{"parsed_date":"2017-05-24 00:00:00 UTC","id":"1","total_visits":"2406"} -{"parsed_date":"2016-11-22 00:00:00 UTC","id":"1","total_visits":"3942"} -{"parsed_date":"2017-05-06 00:00:00 UTC","id":"1","total_visits":"1383"} -{"parsed_date":"2017-07-02 00:00:00 UTC","id":"1","total_visits":"1895"} -{"parsed_date":"2016-12-01 00:00:00 UTC","id":"1","total_visits":"4200"} -{"parsed_date":"2017-03-16 00:00:00 UTC","id":"1","total_visits":"2409"} -{"parsed_date":"2016-12-12 00:00:00 UTC","id":"1","total_visits":"3433"} -{"parsed_date":"2016-12-25 00:00:00 UTC","id":"1","total_visits":"1386"} -{"parsed_date":"2017-02-26 00:00:00 UTC","id":"1","total_visits":"1643"} -{"parsed_date":"2017-04-28 00:00:00 UTC","id":"1","total_visits":"2411"} -{"parsed_date":"2016-08-11 00:00:00 UTC","id":"1","total_visits":"2667"} -{"parsed_date":"2017-07-20 00:00:00 UTC","id":"1","total_visits":"2668"} -{"parsed_date":"2017-05-21 00:00:00 UTC","id":"1","total_visits":"1645"} -{"parsed_date":"2017-06-17 00:00:00 UTC","id":"1","total_visits":"1391"} -{"parsed_date":"2016-12-29 00:00:00 UTC","id":"1","total_visits":"1647"} -{"parsed_date":"2017-07-17 00:00:00 UTC","id":"1","total_visits":"2671"} -{"parsed_date":"2017-01-16 00:00:00 UTC","id":"1","total_visits":"1906"} -{"parsed_date":"2017-03-03 00:00:00 UTC","id":"1","total_visits":"2162"} -{"parsed_date":"2016-11-14 00:00:00 UTC","id":"1","total_visits":"4466"} -{"parsed_date":"2016-08-30 00:00:00 UTC","id":"1","total_visits":"2675"} -{"parsed_date":"2016-08-27 00:00:00 UTC","id":"1","total_visits":"1654"} -{"parsed_date":"2017-02-09 00:00:00 UTC","id":"1","total_visits":"2678"} -{"parsed_date":"2017-06-03 00:00:00 UTC","id":"1","total_visits":"1399"} -{"parsed_date":"2017-05-07 00:00:00 UTC","id":"1","total_visits":"1400"} -{"parsed_date":"2016-11-02 00:00:00 UTC","id":"1","total_visits":"3960"} -{"parsed_date":"2016-12-15 00:00:00 UTC","id":"1","total_visits":"2937"} -{"parsed_date":"2017-04-01 00:00:00 UTC","id":"1","total_visits":"2170"} -{"parsed_date":"2017-07-21 00:00:00 UTC","id":"1","total_visits":"2427"} -{"parsed_date":"2016-08-06 00:00:00 UTC","id":"1","total_visits":"1663"} -{"parsed_date":"2016-09-01 00:00:00 UTC","id":"1","total_visits":"2687"} -{"parsed_date":"2017-06-28 00:00:00 UTC","id":"1","total_visits":"2687"} -{"parsed_date":"2016-08-20 00:00:00 UTC","id":"1","total_visits":"1664"} -{"parsed_date":"2017-04-26 00:00:00 UTC","id":"1","total_visits":"4224"} -{"parsed_date":"2017-07-09 00:00:00 UTC","id":"1","total_visits":"1921"} -{"parsed_date":"2017-07-28 00:00:00 UTC","id":"1","total_visits":"2433"} -{"parsed_date":"2016-09-19 00:00:00 UTC","id":"1","total_visits":"2689"} -{"parsed_date":"2017-07-24 00:00:00 UTC","id":"1","total_visits":"2436"} -{"parsed_date":"2017-06-13 00:00:00 UTC","id":"1","total_visits":"2181"} -{"parsed_date":"2016-09-15 00:00:00 UTC","id":"1","total_visits":"2949"} -{"parsed_date":"2017-02-03 00:00:00 UTC","id":"1","total_visits":"2182"} -{"parsed_date":"2016-09-10 00:00:00 UTC","id":"1","total_visits":"1671"} -{"parsed_date":"2017-06-09 00:00:00 UTC","id":"1","total_visits":"1927"} -{"parsed_date":"2017-01-11 00:00:00 UTC","id":"1","total_visits":"2185"} -{"parsed_date":"2017-02-19 00:00:00 UTC","id":"1","total_visits":"2187"} -{"parsed_date":"2017-01-17 00:00:00 UTC","id":"1","total_visits":"2443"} -{"parsed_date":"2017-05-12 00:00:00 UTC","id":"1","total_visits":"1932"} -{"parsed_date":"2016-12-16 00:00:00 UTC","id":"1","total_visits":"2956"} -{"parsed_date":"2017-02-01 00:00:00 UTC","id":"1","total_visits":"2445"} -{"parsed_date":"2016-11-26 00:00:00 UTC","id":"1","total_visits":"3213"} -{"parsed_date":"2017-06-02 00:00:00 UTC","id":"1","total_visits":"2190"} -{"parsed_date":"2016-08-05 00:00:00 UTC","id":"1","total_visits":"2702"} -{"parsed_date":"2016-11-01 00:00:00 UTC","id":"1","total_visits":"3728"} -{"parsed_date":"2017-01-05 00:00:00 UTC","id":"1","total_visits":"2193"} -{"parsed_date":"2017-03-08 00:00:00 UTC","id":"1","total_visits":"2449"} -{"parsed_date":"2016-08-28 00:00:00 UTC","id":"1","total_visits":"1682"} -{"parsed_date":"2017-07-04 00:00:00 UTC","id":"1","total_visits":"1938"} -{"parsed_date":"2017-03-10 00:00:00 UTC","id":"1","total_visits":"2194"} -{"parsed_date":"2017-07-07 00:00:00 UTC","id":"1","total_visits":"2450"} -{"parsed_date":"2016-10-29 00:00:00 UTC","id":"1","total_visits":"2964"} -{"parsed_date":"2016-10-13 00:00:00 UTC","id":"1","total_visits":"2964"} -{"parsed_date":"2016-12-04 00:00:00 UTC","id":"1","total_visits":"3220"} -{"parsed_date":"2017-01-21 00:00:00 UTC","id":"1","total_visits":"1685"} -{"parsed_date":"2017-06-29 00:00:00 UTC","id":"1","total_visits":"2709"} -{"parsed_date":"2016-08-29 00:00:00 UTC","id":"1","total_visits":"2454"} -{"parsed_date":"2016-12-19 00:00:00 UTC","id":"1","total_visits":"3222"} -{"parsed_date":"2017-05-30 00:00:00 UTC","id":"1","total_visits":"2199"} -{"parsed_date":"2017-02-10 00:00:00 UTC","id":"1","total_visits":"2199"} -{"parsed_date":"2016-08-31 00:00:00 UTC","id":"1","total_visits":"3223"} -{"parsed_date":"2017-06-18 00:00:00 UTC","id":"1","total_visits":"1432"} -{"parsed_date":"2017-01-12 00:00:00 UTC","id":"1","total_visits":"2203"} -{"parsed_date":"2017-05-18 00:00:00 UTC","id":"1","total_visits":"2715"} -{"parsed_date":"2016-10-23 00:00:00 UTC","id":"1","total_visits":"2971"} -{"parsed_date":"2016-09-04 00:00:00 UTC","id":"1","total_visits":"1692"} -{"parsed_date":"2016-12-10 00:00:00 UTC","id":"1","total_visits":"2207"} -{"parsed_date":"2016-12-11 00:00:00 UTC","id":"1","total_visits":"2208"} -{"parsed_date":"2017-04-11 00:00:00 UTC","id":"1","total_visits":"2464"} -{"parsed_date":"2016-09-21 00:00:00 UTC","id":"1","total_visits":"2720"} -{"parsed_date":"2016-11-06 00:00:00 UTC","id":"1","total_visits":"3232"} -{"parsed_date":"2017-01-26 00:00:00 UTC","id":"1","total_visits":"2209"} -{"parsed_date":"2016-09-12 00:00:00 UTC","id":"1","total_visits":"2465"} -{"parsed_date":"2017-04-21 00:00:00 UTC","id":"1","total_visits":"2210"} -{"parsed_date":"2017-01-06 00:00:00 UTC","id":"1","total_visits":"2210"} -{"parsed_date":"2017-04-04 00:00:00 UTC","id":"1","total_visits":"2978"} -{"parsed_date":"2017-01-22 00:00:00 UTC","id":"1","total_visits":"1700"} -{"parsed_date":"2017-07-26 00:00:00 UTC","id":"1","total_visits":"2725"} -{"parsed_date":"2016-08-18 00:00:00 UTC","id":"1","total_visits":"2725"} -{"parsed_date":"2016-09-27 00:00:00 UTC","id":"1","total_visits":"2727"} -{"parsed_date":"2016-12-02 00:00:00 UTC","id":"1","total_visits":"3751"} -{"parsed_date":"2017-05-05 00:00:00 UTC","id":"1","total_visits":"1960"} -{"parsed_date":"2016-11-19 00:00:00 UTC","id":"1","total_visits":"2984"} -{"parsed_date":"2016-11-09 00:00:00 UTC","id":"1","total_visits":"3752"} -{"parsed_date":"2016-12-05 00:00:00 UTC","id":"1","total_visits":"4265"} -{"parsed_date":"2017-05-11 00:00:00 UTC","id":"1","total_visits":"2218"} -{"parsed_date":"2017-01-25 00:00:00 UTC","id":"1","total_visits":"2986"} -{"parsed_date":"2017-03-11 00:00:00 UTC","id":"1","total_visits":"1707"} -{"parsed_date":"2017-03-30 00:00:00 UTC","id":"1","total_visits":"2731"} -{"parsed_date":"2016-10-20 00:00:00 UTC","id":"1","total_visits":"3755"} -{"parsed_date":"2017-02-07 00:00:00 UTC","id":"1","total_visits":"2476"} -{"parsed_date":"2017-02-22 00:00:00 UTC","id":"1","total_visits":"2477"} -{"parsed_date":"2017-07-23 00:00:00 UTC","id":"1","total_visits":"1966"} -{"parsed_date":"2016-11-03 00:00:00 UTC","id":"1","total_visits":"4014"} -{"parsed_date":"2016-08-01 00:00:00 UTC","id":"1","total_visits":"1711"} -{"parsed_date":"2017-01-13 00:00:00 UTC","id":"1","total_visits":"1967"} -{"parsed_date":"2017-05-19 00:00:00 UTC","id":"1","total_visits":"2223"} -{"parsed_date":"2016-11-20 00:00:00 UTC","id":"1","total_visits":"3247"} -{"parsed_date":"2016-11-25 00:00:00 UTC","id":"1","total_visits":"3759"} -{"parsed_date":"2017-03-25 00:00:00 UTC","id":"1","total_visits":"1712"} -{"parsed_date":"2017-01-27 00:00:00 UTC","id":"1","total_visits":"1969"} -{"parsed_date":"2017-06-26 00:00:00 UTC","id":"1","total_visits":"2226"} -{"parsed_date":"2017-05-25 00:00:00 UTC","id":"1","total_visits":"2228"} -{"parsed_date":"2017-01-31 00:00:00 UTC","id":"1","total_visits":"2229"} -{"parsed_date":"2017-07-13 00:00:00 UTC","id":"1","total_visits":"2741"} -{"parsed_date":"2017-03-15 00:00:00 UTC","id":"1","total_visits":"2486"} -{"parsed_date":"2017-05-28 00:00:00 UTC","id":"1","total_visits":"1463"} -{"parsed_date":"2017-03-09 00:00:00 UTC","id":"1","total_visits":"2231"} -{"parsed_date":"2017-07-15 00:00:00 UTC","id":"1","total_visits":"1721"} -{"parsed_date":"2016-11-24 00:00:00 UTC","id":"1","total_visits":"3770"} -{"parsed_date":"2016-10-05 00:00:00 UTC","id":"1","total_visits":"3770"} -{"parsed_date":"2016-12-31 00:00:00 UTC","id":"1","total_visits":"1211"} -{"parsed_date":"2016-10-02 00:00:00 UTC","id":"1","total_visits":"1724"} -{"parsed_date":"2017-07-22 00:00:00 UTC","id":"1","total_visits":"1724"} -{"parsed_date":"2016-09-11 00:00:00 UTC","id":"1","total_visits":"1725"} -{"parsed_date":"2017-06-15 00:00:00 UTC","id":"1","total_visits":"2237"} -{"parsed_date":"2017-06-05 00:00:00 UTC","id":"1","total_visits":"2493"} -{"parsed_date":"2017-02-06 00:00:00 UTC","id":"1","total_visits":"2238"} -{"parsed_date":"2016-10-15 00:00:00 UTC","id":"1","total_visits":"2495"} -{"parsed_date":"2016-08-21 00:00:00 UTC","id":"1","total_visits":"1730"} -{"parsed_date":"2016-08-23 00:00:00 UTC","id":"1","total_visits":"2754"} -{"parsed_date":"2017-06-30 00:00:00 UTC","id":"1","total_visits":"2499"} -{"parsed_date":"2017-01-18 00:00:00 UTC","id":"1","total_visits":"2245"} -{"parsed_date":"2016-08-10 00:00:00 UTC","id":"1","total_visits":"2757"} -{"parsed_date":"2016-12-08 00:00:00 UTC","id":"1","total_visits":"3013"} -{"parsed_date":"2016-11-28 00:00:00 UTC","id":"1","total_visits":"4807"} -{"parsed_date":"2017-05-22 00:00:00 UTC","id":"1","total_visits":"2248"} -{"parsed_date":"2016-09-20 00:00:00 UTC","id":"1","total_visits":"2760"} -{"parsed_date":"2016-10-06 00:00:00 UTC","id":"1","total_visits":"3016"} -{"parsed_date":"2016-09-06 00:00:00 UTC","id":"1","total_visits":"2508"} -{"parsed_date":"2016-09-03 00:00:00 UTC","id":"1","total_visits":"1741"} -{"parsed_date":"2016-12-06 00:00:00 UTC","id":"1","total_visits":"3021"} -{"parsed_date":"2016-12-24 00:00:00 UTC","id":"1","total_visits":"1231"} -{"parsed_date":"2016-10-28 00:00:00 UTC","id":"1","total_visits":"3791"} -{"parsed_date":"2016-12-30 00:00:00 UTC","id":"1","total_visits":"1232"} -{"parsed_date":"2017-05-29 00:00:00 UTC","id":"1","total_visits":"1745"} -{"parsed_date":"2017-07-10 00:00:00 UTC","id":"1","total_visits":"2769"} -{"parsed_date":"2017-06-22 00:00:00 UTC","id":"1","total_visits":"2258"} -{"parsed_date":"2017-07-19 00:00:00 UTC","id":"1","total_visits":"2514"} -{"parsed_date":"2016-10-03 00:00:00 UTC","id":"1","total_visits":"2514"} -{"parsed_date":"2017-06-14 00:00:00 UTC","id":"1","total_visits":"2517"} -{"parsed_date":"2016-10-22 00:00:00 UTC","id":"1","total_visits":"3029"} -{"parsed_date":"2017-01-23 00:00:00 UTC","id":"1","total_visits":"2262"} -{"parsed_date":"2017-04-24 00:00:00 UTC","id":"1","total_visits":"2263"} -{"parsed_date":"2016-11-10 00:00:00 UTC","id":"1","total_visits":"4055"} -{"parsed_date":"2016-09-26 00:00:00 UTC","id":"1","total_visits":"2776"} -{"parsed_date":"2016-10-19 00:00:00 UTC","id":"1","total_visits":"3544"} -{"parsed_date":"2017-03-04 00:00:00 UTC","id":"1","total_visits":"1753"} -{"parsed_date":"2017-05-26 00:00:00 UTC","id":"1","total_visits":"2009"} -{"parsed_date":"2017-02-13 00:00:00 UTC","id":"1","total_visits":"2266"} -{"parsed_date":"2017-02-18 00:00:00 UTC","id":"1","total_visits":"1755"} -{"parsed_date":"2017-03-02 00:00:00 UTC","id":"1","total_visits":"2267"} -{"parsed_date":"2017-03-31 00:00:00 UTC","id":"1","total_visits":"2268"} -{"parsed_date":"2017-01-10 00:00:00 UTC","id":"1","total_visits":"2268"} -{"parsed_date":"2017-03-29 00:00:00 UTC","id":"1","total_visits":"2525"} -{"parsed_date":"2017-03-27 00:00:00 UTC","id":"1","total_visits":"2525"} -{"parsed_date":"2016-11-23 00:00:00 UTC","id":"1","total_visits":"3805"} -{"parsed_date":"2017-05-27 00:00:00 UTC","id":"1","total_visits":"1502"} -{"parsed_date":"2016-10-24 00:00:00 UTC","id":"1","total_visits":"4063"} -{"parsed_date":"2016-12-14 00:00:00 UTC","id":"1","total_visits":"3040"} -{"parsed_date":"2017-02-11 00:00:00 UTC","id":"1","total_visits":"1761"} -{"parsed_date":"2017-07-27 00:00:00 UTC","id":"1","total_visits":"2529"} -{"parsed_date":"2017-02-17 00:00:00 UTC","id":"1","total_visits":"2785"} -{"parsed_date":"2017-04-15 00:00:00 UTC","id":"1","total_visits":"1506"} -{"parsed_date":"2016-11-05 00:00:00 UTC","id":"1","total_visits":"3042"} -{"parsed_date":"2016-10-04 00:00:00 UTC","id":"1","total_visits":"4322"} -{"parsed_date":"2017-05-13 00:00:00 UTC","id":"1","total_visits":"1251"} -{"parsed_date":"2017-04-16 00:00:00 UTC","id":"1","total_visits":"1507"} -{"parsed_date":"2016-12-28 00:00:00 UTC","id":"1","total_visits":"1763"} -{"parsed_date":"2016-08-15 00:00:00 UTC","id":"1","total_visits":"3043"} -{"parsed_date":"2016-12-03 00:00:00 UTC","id":"1","total_visits":"3044"} -{"parsed_date":"2017-06-27 00:00:00 UTC","id":"1","total_visits":"2789"} -{"parsed_date":"2017-06-24 00:00:00 UTC","id":"1","total_visits":"1510"} -{"parsed_date":"2017-07-16 00:00:00 UTC","id":"1","total_visits":"1766"} -{"parsed_date":"2017-04-09 00:00:00 UTC","id":"1","total_visits":"1766"} -{"parsed_date":"2017-06-07 00:00:00 UTC","id":"1","total_visits":"2279"} -{"parsed_date":"2017-04-17 00:00:00 UTC","id":"1","total_visits":"2279"} -{"parsed_date":"2016-09-28 00:00:00 UTC","id":"1","total_visits":"2535"} -{"parsed_date":"2017-03-26 00:00:00 UTC","id":"1","total_visits":"1768"} -{"parsed_date":"2017-05-10 00:00:00 UTC","id":"1","total_visits":"2024"} -{"parsed_date":"2017-06-08 00:00:00 UTC","id":"1","total_visits":"2280"} -{"parsed_date":"2017-05-08 00:00:00 UTC","id":"1","total_visits":"2025"} -{"parsed_date":"2017-03-13 00:00:00 UTC","id":"1","total_visits":"2537"} -{"parsed_date":"2016-11-17 00:00:00 UTC","id":"1","total_visits":"4074"} -{"parsed_date":"2016-08-25 00:00:00 UTC","id":"1","total_visits":"2539"} -{"parsed_date":"2017-02-16 00:00:00 UTC","id":"1","total_visits":"2539"} -{"parsed_date":"2017-06-16 00:00:00 UTC","id":"1","total_visits":"2028"} -{"parsed_date":"2016-11-16 00:00:00 UTC","id":"1","total_visits":"4334"} -{"parsed_date":"2016-08-17 00:00:00 UTC","id":"1","total_visits":"2800"} -{"parsed_date":"2017-03-19 00:00:00 UTC","id":"1","total_visits":"1776"} -{"parsed_date":"2016-11-29 00:00:00 UTC","id":"1","total_visits":"4337"} -{"parsed_date":"2017-02-05 00:00:00 UTC","id":"1","total_visits":"1522"} -{"parsed_date":"2016-10-31 00:00:00 UTC","id":"1","total_visits":"3827"} -{"parsed_date":"2017-05-31 00:00:00 UTC","id":"1","total_visits":"2292"} -{"parsed_date":"2017-07-18 00:00:00 UTC","id":"1","total_visits":"2804"} -{"parsed_date":"2017-03-12 00:00:00 UTC","id":"1","total_visits":"1781"} -{"parsed_date":"2016-09-09 00:00:00 UTC","id":"1","total_visits":"2549"} -{"parsed_date":"2017-01-14 00:00:00 UTC","id":"1","total_visits":"1526"} -{"parsed_date":"2017-05-04 00:00:00 UTC","id":"1","total_visits":"2806"} -{"parsed_date":"2016-11-07 00:00:00 UTC","id":"1","total_visits":"3832"} -{"parsed_date":"2017-04-07 00:00:00 UTC","id":"1","total_visits":"2297"} -{"parsed_date":"2017-07-12 00:00:00 UTC","id":"1","total_visits":"2554"} -{"parsed_date":"2017-04-13 00:00:00 UTC","id":"1","total_visits":"2300"} -{"parsed_date":"2017-08-01 00:00:00 UTC","id":"1","total_visits":"2556"} -{"parsed_date":"2017-06-04 00:00:00 UTC","id":"1","total_visits":"1534"} -{"parsed_date":"2017-02-12 00:00:00 UTC","id":"1","total_visits":"1790"} -{"parsed_date":"2017-07-03 00:00:00 UTC","id":"1","total_visits":"2046"} -{"parsed_date":"2016-09-30 00:00:00 UTC","id":"1","total_visits":"2303"} -{"parsed_date":"2016-08-08 00:00:00 UTC","id":"1","total_visits":"2815"} -{"parsed_date":"2017-07-01 00:00:00 UTC","id":"2","total_visits":"2048"} -{"parsed_date":"2016-09-07 00:00:00 UTC","id":"2","total_visits":"2562"} -{"parsed_date":"2016-10-25 00:00:00 UTC","id":"2","total_visits":"3842"} -{"parsed_date":"2017-04-10 00:00:00 UTC","id":"2","total_visits":"2563"} -{"parsed_date":"2017-01-09 00:00:00 UTC","id":"2","total_visits":"2308"} -{"parsed_date":"2017-05-02 00:00:00 UTC","id":"2","total_visits":"2564"} -{"parsed_date":"2016-11-11 00:00:00 UTC","id":"2","total_visits":"3588"} -{"parsed_date":"2017-07-30 00:00:00 UTC","id":"2","total_visits":"1799"} -{"parsed_date":"2017-06-10 00:00:00 UTC","id":"2","total_visits":"1545"} -{"parsed_date":"2016-08-14 00:00:00 UTC","id":"2","total_visits":"1801"} -{"parsed_date":"2017-05-14 00:00:00 UTC","id":"2","total_visits":"1290"} -{"parsed_date":"2017-02-08 00:00:00 UTC","id":"2","total_visits":"2570"} -{"parsed_date":"2017-06-01 00:00:00 UTC","id":"2","total_visits":"2826"} -{"parsed_date":"2017-04-23 00:00:00 UTC","id":"2","total_visits":"1548"} -{"parsed_date":"2016-11-04 00:00:00 UTC","id":"2","total_visits":"3596"} -{"parsed_date":"2017-02-04 00:00:00 UTC","id":"2","total_visits":"1549"} -{"parsed_date":"2016-12-09 00:00:00 UTC","id":"2","total_visits":"2830"} -{"parsed_date":"2016-10-30 00:00:00 UTC","id":"2","total_visits":"3086"} -{"parsed_date":"2017-03-28 00:00:00 UTC","id":"2","total_visits":"2577"} -{"parsed_date":"2017-06-11 00:00:00 UTC","id":"2","total_visits":"1555"} -{"parsed_date":"2016-12-17 00:00:00 UTC","id":"2","total_visits":"2324"} -{"parsed_date":"2016-09-22 00:00:00 UTC","id":"2","total_visits":"2581"} -{"parsed_date":"2017-01-29 00:00:00 UTC","id":"2","total_visits":"1814"} -{"parsed_date":"2017-03-22 00:00:00 UTC","id":"2","total_visits":"2582"} -{"parsed_date":"2017-02-21 00:00:00 UTC","id":"2","total_visits":"2582"} -{"parsed_date":"2016-10-14 00:00:00 UTC","id":"2","total_visits":"2838"} -{"parsed_date":"2017-04-27 00:00:00 UTC","id":"2","total_visits":"2838"} -{"parsed_date":"2016-10-26 00:00:00 UTC","id":"2","total_visits":"4375"} -{"parsed_date":"2016-08-22 00:00:00 UTC","id":"2","total_visits":"2584"} -{"parsed_date":"2016-12-07 00:00:00 UTC","id":"2","total_visits":"2840"} -{"parsed_date":"2017-01-20 00:00:00 UTC","id":"2","total_visits":"2074"} -{"parsed_date":"2017-03-07 00:00:00 UTC","id":"2","total_visits":"2586"} -{"parsed_date":"2017-05-16 00:00:00 UTC","id":"2","total_visits":"3098"} -{"parsed_date":"2017-05-03 00:00:00 UTC","id":"2","total_visits":"2588"} -{"parsed_date":"2017-05-01 00:00:00 UTC","id":"2","total_visits":"2588"} -{"parsed_date":"2016-11-27 00:00:00 UTC","id":"2","total_visits":"3356"} -{"parsed_date":"2017-04-29 00:00:00 UTC","id":"2","total_visits":"1566"} -{"parsed_date":"2016-09-18 00:00:00 UTC","id":"2","total_visits":"1822"} -{"parsed_date":"2017-03-23 00:00:00 UTC","id":"2","total_visits":"2847"} -{"parsed_date":"2017-03-14 00:00:00 UTC","id":"2","total_visits":"2338"} -{"parsed_date":"2016-12-21 00:00:00 UTC","id":"2","total_visits":"2594"} -{"parsed_date":"2016-10-11 00:00:00 UTC","id":"2","total_visits":"2850"} -{"parsed_date":"2017-01-24 00:00:00 UTC","id":"2","total_visits":"3618"} -{"parsed_date":"2017-03-05 00:00:00 UTC","id":"2","total_visits":"1827"} -{"parsed_date":"2017-01-19 00:00:00 UTC","id":"2","total_visits":"2083"} -{"parsed_date":"2016-08-09 00:00:00 UTC","id":"2","total_visits":"2851"} -{"parsed_date":"2017-04-08 00:00:00 UTC","id":"2","total_visits":"1829"} -{"parsed_date":"2017-04-12 00:00:00 UTC","id":"2","total_visits":"2341"} -{"parsed_date":"2016-09-29 00:00:00 UTC","id":"2","total_visits":"2597"} -{"parsed_date":"2016-12-20 00:00:00 UTC","id":"2","total_visits":"3110"} -{"parsed_date":"2017-01-15 00:00:00 UTC","id":"2","total_visits":"1576"} -{"parsed_date":"2017-04-14 00:00:00 UTC","id":"2","total_visits":"1834"} -{"parsed_date":"2017-02-28 00:00:00 UTC","id":"2","total_visits":"2347"} -{"parsed_date":"2016-09-16 00:00:00 UTC","id":"2","total_visits":"2603"} -{"parsed_date":"2016-10-18 00:00:00 UTC","id":"2","total_visits":"3628"} -{"parsed_date":"2017-02-24 00:00:00 UTC","id":"2","total_visits":"2093"} -{"parsed_date":"2017-05-17 00:00:00 UTC","id":"2","total_visits":"3117"} -{"parsed_date":"2017-06-23 00:00:00 UTC","id":"2","total_visits":"2095"} -{"parsed_date":"2016-11-12 00:00:00 UTC","id":"2","total_visits":"3119"} -{"parsed_date":"2016-11-21 00:00:00 UTC","id":"2","total_visits":"4143"} -{"parsed_date":"2017-02-27 00:00:00 UTC","id":"2","total_visits":"2352"} -{"parsed_date":"2016-12-26 00:00:00 UTC","id":"2","total_visits":"1586"} -{"parsed_date":"2017-04-25 00:00:00 UTC","id":"2","total_visits":"2354"} -{"parsed_date":"2017-03-21 00:00:00 UTC","id":"2","total_visits":"2611"} -{"parsed_date":"2016-12-22 00:00:00 UTC","id":"2","total_visits":"2100"} -{"parsed_date":"2016-10-01 00:00:00 UTC","id":"2","total_visits":"1589"} -{"parsed_date":"2016-09-24 00:00:00 UTC","id":"2","total_visits":"1845"} -{"parsed_date":"2017-06-21 00:00:00 UTC","id":"2","total_visits":"2357"} -{"parsed_date":"2016-09-02 00:00:00 UTC","id":"2","total_visits":"2613"} -{"parsed_date":"2016-08-26 00:00:00 UTC","id":"2","total_visits":"2359"} -{"parsed_date":"2016-10-12 00:00:00 UTC","id":"2","total_visits":"2871"} -{"parsed_date":"2017-05-15 00:00:00 UTC","id":"2","total_visits":"2360"} -{"parsed_date":"2017-06-12 00:00:00 UTC","id":"2","total_visits":"2361"} -{"parsed_date":"2016-08-16 00:00:00 UTC","id":"2","total_visits":"2873"} -{"parsed_date":"2017-04-30 00:00:00 UTC","id":"2","total_visits":"1594"} -{"parsed_date":"2017-04-05 00:00:00 UTC","id":"2","total_visits":"2619"} -{"parsed_date":"2016-08-12 00:00:00 UTC","id":"2","total_visits":"2619"} -{"parsed_date":"2016-11-08 00:00:00 UTC","id":"2","total_visits":"3899"} -{"parsed_date":"2016-08-13 00:00:00 UTC","id":"2","total_visits":"1596"} -{"parsed_date":"2017-05-09 00:00:00 UTC","id":"2","total_visits":"2108"} -{"parsed_date":"2017-02-23 00:00:00 UTC","id":"2","total_visits":"2364"} -{"parsed_date":"2017-07-31 00:00:00 UTC","id":"2","total_visits":"2620"} -{"parsed_date":"2017-06-25 00:00:00 UTC","id":"2","total_visits":"1597"} -{"parsed_date":"2017-07-29 00:00:00 UTC","id":"2","total_visits":"1597"} -{"parsed_date":"2016-09-17 00:00:00 UTC","id":"2","total_visits":"1853"} -{"parsed_date":"2016-12-27 00:00:00 UTC","id":"2","total_visits":"1855"} -{"parsed_date":"2017-05-20 00:00:00 UTC","id":"2","total_visits":"1855"} -{"parsed_date":"2016-10-08 00:00:00 UTC","id":"2","total_visits":"2114"} -{"parsed_date":"2016-10-27 00:00:00 UTC","id":"2","total_visits":"4162"} -{"parsed_date":"2017-07-08 00:00:00 UTC","id":"2","total_visits":"1859"} -{"parsed_date":"2016-08-24 00:00:00 UTC","id":"2","total_visits":"2627"} -{"parsed_date":"2016-12-23 00:00:00 UTC","id":"2","total_visits":"1604"} -{"parsed_date":"2017-02-02 00:00:00 UTC","id":"2","total_visits":"2372"} -{"parsed_date":"2016-09-08 00:00:00 UTC","id":"2","total_visits":"2628"} -{"parsed_date":"2017-04-02 00:00:00 UTC","id":"2","total_visits":"1861"} -{"parsed_date":"2017-02-15 00:00:00 UTC","id":"2","total_visits":"2629"} -{"parsed_date":"2017-07-05 00:00:00 UTC","id":"2","total_visits":"2885"} -{"parsed_date":"2016-10-17 00:00:00 UTC","id":"2","total_visits":"3397"} -{"parsed_date":"2017-02-20 00:00:00 UTC","id":"2","total_visits":"2374"} -{"parsed_date":"2017-03-24 00:00:00 UTC","id":"2","total_visits":"2374"} -{"parsed_date":"2017-04-20 00:00:00 UTC","id":"2","total_visits":"2374"} -{"parsed_date":"2016-11-18 00:00:00 UTC","id":"2","total_visits":"3654"} -{"parsed_date":"2017-07-25 00:00:00 UTC","id":"2","total_visits":"2631"} -{"parsed_date":"2016-11-13 00:00:00 UTC","id":"2","total_visits":"3144"} -{"parsed_date":"2017-03-18 00:00:00 UTC","id":"2","total_visits":"1610"} -{"parsed_date":"2016-08-03 00:00:00 UTC","id":"2","total_visits":"2890"} -{"parsed_date":"2016-08-19 00:00:00 UTC","id":"2","total_visits":"2379"} -{"parsed_date":"2017-02-14 00:00:00 UTC","id":"2","total_visits":"2379"} -{"parsed_date":"2017-07-11 00:00:00 UTC","id":"2","total_visits":"2635"} -{"parsed_date":"2017-04-22 00:00:00 UTC","id":"2","total_visits":"1612"} -{"parsed_date":"2016-10-07 00:00:00 UTC","id":"2","total_visits":"2892"} -{"parsed_date":"2016-09-05 00:00:00 UTC","id":"2","total_visits":"2125"} -{"parsed_date":"2016-09-23 00:00:00 UTC","id":"2","total_visits":"2381"} -{"parsed_date":"2016-11-15 00:00:00 UTC","id":"2","total_visits":"4685"} -{"parsed_date":"2017-01-28 00:00:00 UTC","id":"2","total_visits":"1614"} -{"parsed_date":"2017-07-14 00:00:00 UTC","id":"2","total_visits":"2382"} -{"parsed_date":"2017-01-07 00:00:00 UTC","id":"2","total_visits":"1615"} -{"parsed_date":"2017-04-03 00:00:00 UTC","id":"2","total_visits":"2383"} -{"parsed_date":"2017-03-20 00:00:00 UTC","id":"2","total_visits":"2383"} -{"parsed_date":"2016-12-18 00:00:00 UTC","id":"2","total_visits":"2128"} -{"parsed_date":"2017-03-17 00:00:00 UTC","id":"2","total_visits":"2129"} -{"parsed_date":"2017-05-23 00:00:00 UTC","id":"2","total_visits":"2129"} -{"parsed_date":"2016-11-30 00:00:00 UTC","id":"2","total_visits":"4435"} -{"parsed_date":"2017-01-01 00:00:00 UTC","id":"2","total_visits":"1364"} -{"parsed_date":"2017-01-02 00:00:00 UTC","id":"2","total_visits":"1620"} -{"parsed_date":"2016-09-25 00:00:00 UTC","id":"2","total_visits":"1877"} -{"parsed_date":"2016-08-07 00:00:00 UTC","id":"2","total_visits":"1622"} -{"parsed_date":"2016-10-09 00:00:00 UTC","id":"2","total_visits":"2134"} -{"parsed_date":"2017-03-01 00:00:00 UTC","id":"2","total_visits":"2390"} -{"parsed_date":"2017-01-04 00:00:00 UTC","id":"2","total_visits":"2390"} -{"parsed_date":"2017-06-06 00:00:00 UTC","id":"2","total_visits":"2391"} -{"parsed_date":"2017-04-18 00:00:00 UTC","id":"2","total_visits":"2391"} -{"parsed_date":"2017-04-06 00:00:00 UTC","id":"2","total_visits":"2647"} -{"parsed_date":"2017-01-30 00:00:00 UTC","id":"2","total_visits":"2392"} -{"parsed_date":"2016-10-16 00:00:00 UTC","id":"2","total_visits":"2649"} -{"parsed_date":"2016-08-04 00:00:00 UTC","id":"2","total_visits":"3161"} -{"parsed_date":"2016-10-21 00:00:00 UTC","id":"2","total_visits":"3419"} -{"parsed_date":"2016-08-02 00:00:00 UTC","id":"2","total_visits":"2140"} -{"parsed_date":"2017-03-06 00:00:00 UTC","id":"2","total_visits":"2396"} -{"parsed_date":"2016-09-13 00:00:00 UTC","id":"2","total_visits":"2396"} -{"parsed_date":"2016-09-14 00:00:00 UTC","id":"2","total_visits":"2652"} -{"parsed_date":"2017-04-19 00:00:00 UTC","id":"2","total_visits":"2397"} -{"parsed_date":"2017-06-19 00:00:00 UTC","id":"2","total_visits":"2142"} -{"parsed_date":"2016-12-13 00:00:00 UTC","id":"2","total_visits":"3166"} -{"parsed_date":"2017-06-20 00:00:00 UTC","id":"2","total_visits":"2143"} -{"parsed_date":"2016-10-10 00:00:00 UTC","id":"2","total_visits":"2911"} -{"parsed_date":"2017-07-06 00:00:00 UTC","id":"2","total_visits":"2658"} -{"parsed_date":"2017-01-03 00:00:00 UTC","id":"2","total_visits":"2403"} -{"parsed_date":"2017-01-08 00:00:00 UTC","id":"2","total_visits":"1637"} -{"parsed_date":"2017-02-25 00:00:00 UTC","id":"2","total_visits":"1638"} -{"parsed_date":"2017-05-24 00:00:00 UTC","id":"2","total_visits":"2406"} -{"parsed_date":"2016-11-22 00:00:00 UTC","id":"2","total_visits":"3942"} -{"parsed_date":"2017-05-06 00:00:00 UTC","id":"2","total_visits":"1383"} -{"parsed_date":"2017-07-02 00:00:00 UTC","id":"2","total_visits":"1895"} -{"parsed_date":"2016-12-01 00:00:00 UTC","id":"2","total_visits":"4200"} -{"parsed_date":"2017-03-16 00:00:00 UTC","id":"2","total_visits":"2409"} -{"parsed_date":"2016-12-12 00:00:00 UTC","id":"2","total_visits":"3433"} -{"parsed_date":"2016-12-25 00:00:00 UTC","id":"2","total_visits":"1386"} -{"parsed_date":"2017-02-26 00:00:00 UTC","id":"2","total_visits":"1643"} -{"parsed_date":"2017-04-28 00:00:00 UTC","id":"2","total_visits":"2411"} -{"parsed_date":"2016-08-11 00:00:00 UTC","id":"2","total_visits":"2667"} -{"parsed_date":"2017-07-20 00:00:00 UTC","id":"2","total_visits":"2668"} -{"parsed_date":"2017-05-21 00:00:00 UTC","id":"2","total_visits":"1645"} -{"parsed_date":"2017-06-17 00:00:00 UTC","id":"2","total_visits":"1391"} -{"parsed_date":"2016-12-29 00:00:00 UTC","id":"2","total_visits":"1647"} -{"parsed_date":"2017-07-17 00:00:00 UTC","id":"2","total_visits":"2671"} -{"parsed_date":"2017-01-16 00:00:00 UTC","id":"2","total_visits":"1906"} -{"parsed_date":"2017-03-03 00:00:00 UTC","id":"2","total_visits":"2162"} -{"parsed_date":"2016-11-14 00:00:00 UTC","id":"2","total_visits":"4466"} -{"parsed_date":"2016-08-30 00:00:00 UTC","id":"2","total_visits":"2675"} -{"parsed_date":"2016-08-27 00:00:00 UTC","id":"2","total_visits":"1654"} -{"parsed_date":"2017-02-09 00:00:00 UTC","id":"2","total_visits":"2678"} -{"parsed_date":"2017-06-03 00:00:00 UTC","id":"2","total_visits":"1399"} -{"parsed_date":"2017-05-07 00:00:00 UTC","id":"2","total_visits":"1400"} -{"parsed_date":"2016-11-02 00:00:00 UTC","id":"2","total_visits":"3960"} -{"parsed_date":"2016-12-15 00:00:00 UTC","id":"2","total_visits":"2937"} -{"parsed_date":"2017-04-01 00:00:00 UTC","id":"2","total_visits":"2170"} -{"parsed_date":"2017-07-21 00:00:00 UTC","id":"2","total_visits":"2427"} -{"parsed_date":"2016-08-06 00:00:00 UTC","id":"2","total_visits":"1663"} -{"parsed_date":"2016-09-01 00:00:00 UTC","id":"2","total_visits":"2687"} -{"parsed_date":"2017-06-28 00:00:00 UTC","id":"2","total_visits":"2687"} -{"parsed_date":"2016-08-20 00:00:00 UTC","id":"2","total_visits":"1664"} -{"parsed_date":"2017-04-26 00:00:00 UTC","id":"2","total_visits":"4224"} -{"parsed_date":"2017-07-09 00:00:00 UTC","id":"2","total_visits":"1921"} -{"parsed_date":"2017-07-28 00:00:00 UTC","id":"2","total_visits":"2433"} -{"parsed_date":"2016-09-19 00:00:00 UTC","id":"2","total_visits":"2689"} -{"parsed_date":"2017-07-24 00:00:00 UTC","id":"2","total_visits":"2436"} -{"parsed_date":"2017-06-13 00:00:00 UTC","id":"2","total_visits":"2181"} -{"parsed_date":"2016-09-15 00:00:00 UTC","id":"2","total_visits":"2949"} -{"parsed_date":"2017-02-03 00:00:00 UTC","id":"2","total_visits":"2182"} -{"parsed_date":"2016-09-10 00:00:00 UTC","id":"2","total_visits":"1671"} -{"parsed_date":"2017-06-09 00:00:00 UTC","id":"2","total_visits":"1927"} -{"parsed_date":"2017-01-11 00:00:00 UTC","id":"2","total_visits":"2185"} -{"parsed_date":"2017-02-19 00:00:00 UTC","id":"2","total_visits":"2187"} -{"parsed_date":"2017-01-17 00:00:00 UTC","id":"2","total_visits":"2443"} -{"parsed_date":"2017-05-12 00:00:00 UTC","id":"2","total_visits":"1932"} -{"parsed_date":"2016-12-16 00:00:00 UTC","id":"2","total_visits":"2956"} -{"parsed_date":"2017-02-01 00:00:00 UTC","id":"2","total_visits":"2445"} -{"parsed_date":"2016-11-26 00:00:00 UTC","id":"2","total_visits":"3213"} -{"parsed_date":"2017-06-02 00:00:00 UTC","id":"2","total_visits":"2190"} -{"parsed_date":"2016-08-05 00:00:00 UTC","id":"2","total_visits":"2702"} -{"parsed_date":"2016-11-01 00:00:00 UTC","id":"2","total_visits":"3728"} -{"parsed_date":"2017-01-05 00:00:00 UTC","id":"2","total_visits":"2193"} -{"parsed_date":"2017-03-08 00:00:00 UTC","id":"2","total_visits":"2449"} -{"parsed_date":"2016-08-28 00:00:00 UTC","id":"2","total_visits":"1682"} -{"parsed_date":"2017-07-04 00:00:00 UTC","id":"2","total_visits":"1938"} -{"parsed_date":"2017-03-10 00:00:00 UTC","id":"2","total_visits":"2194"} -{"parsed_date":"2017-07-07 00:00:00 UTC","id":"2","total_visits":"2450"} -{"parsed_date":"2016-10-29 00:00:00 UTC","id":"2","total_visits":"2964"} -{"parsed_date":"2016-10-13 00:00:00 UTC","id":"2","total_visits":"2964"} -{"parsed_date":"2016-12-04 00:00:00 UTC","id":"2","total_visits":"3220"} -{"parsed_date":"2017-01-21 00:00:00 UTC","id":"2","total_visits":"1685"} -{"parsed_date":"2017-06-29 00:00:00 UTC","id":"2","total_visits":"2709"} -{"parsed_date":"2016-08-29 00:00:00 UTC","id":"2","total_visits":"2454"} -{"parsed_date":"2016-12-19 00:00:00 UTC","id":"2","total_visits":"3222"} -{"parsed_date":"2017-05-30 00:00:00 UTC","id":"2","total_visits":"2199"} -{"parsed_date":"2017-02-10 00:00:00 UTC","id":"2","total_visits":"2199"} -{"parsed_date":"2016-08-31 00:00:00 UTC","id":"2","total_visits":"3223"} -{"parsed_date":"2017-06-18 00:00:00 UTC","id":"2","total_visits":"1432"} -{"parsed_date":"2017-01-12 00:00:00 UTC","id":"2","total_visits":"2203"} -{"parsed_date":"2017-05-18 00:00:00 UTC","id":"2","total_visits":"2715"} -{"parsed_date":"2016-10-23 00:00:00 UTC","id":"2","total_visits":"2971"} -{"parsed_date":"2016-09-04 00:00:00 UTC","id":"2","total_visits":"1692"} -{"parsed_date":"2016-12-10 00:00:00 UTC","id":"2","total_visits":"2207"} -{"parsed_date":"2016-12-11 00:00:00 UTC","id":"2","total_visits":"2208"} -{"parsed_date":"2017-04-11 00:00:00 UTC","id":"2","total_visits":"2464"} -{"parsed_date":"2016-09-21 00:00:00 UTC","id":"2","total_visits":"2720"} -{"parsed_date":"2016-11-06 00:00:00 UTC","id":"2","total_visits":"3232"} -{"parsed_date":"2017-01-26 00:00:00 UTC","id":"2","total_visits":"2209"} -{"parsed_date":"2016-09-12 00:00:00 UTC","id":"2","total_visits":"2465"} -{"parsed_date":"2017-04-21 00:00:00 UTC","id":"2","total_visits":"2210"} -{"parsed_date":"2017-01-06 00:00:00 UTC","id":"2","total_visits":"2210"} -{"parsed_date":"2017-04-04 00:00:00 UTC","id":"2","total_visits":"2978"} -{"parsed_date":"2017-01-22 00:00:00 UTC","id":"2","total_visits":"1700"} -{"parsed_date":"2017-07-26 00:00:00 UTC","id":"2","total_visits":"2725"} -{"parsed_date":"2016-08-18 00:00:00 UTC","id":"2","total_visits":"2725"} -{"parsed_date":"2016-09-27 00:00:00 UTC","id":"2","total_visits":"2727"} -{"parsed_date":"2016-12-02 00:00:00 UTC","id":"2","total_visits":"3751"} -{"parsed_date":"2017-05-05 00:00:00 UTC","id":"2","total_visits":"1960"} -{"parsed_date":"2016-11-19 00:00:00 UTC","id":"2","total_visits":"2984"} -{"parsed_date":"2016-11-09 00:00:00 UTC","id":"2","total_visits":"3752"} -{"parsed_date":"2016-12-05 00:00:00 UTC","id":"2","total_visits":"4265"} -{"parsed_date":"2017-05-11 00:00:00 UTC","id":"2","total_visits":"2218"} -{"parsed_date":"2017-01-25 00:00:00 UTC","id":"2","total_visits":"2986"} -{"parsed_date":"2017-03-11 00:00:00 UTC","id":"2","total_visits":"1707"} -{"parsed_date":"2017-03-30 00:00:00 UTC","id":"2","total_visits":"2731"} -{"parsed_date":"2016-10-20 00:00:00 UTC","id":"2","total_visits":"3755"} -{"parsed_date":"2017-02-07 00:00:00 UTC","id":"2","total_visits":"2476"} -{"parsed_date":"2017-02-22 00:00:00 UTC","id":"2","total_visits":"2477"} -{"parsed_date":"2017-07-23 00:00:00 UTC","id":"2","total_visits":"1966"} -{"parsed_date":"2016-11-03 00:00:00 UTC","id":"2","total_visits":"4014"} -{"parsed_date":"2016-08-01 00:00:00 UTC","id":"2","total_visits":"1711"} -{"parsed_date":"2017-01-13 00:00:00 UTC","id":"2","total_visits":"1967"} -{"parsed_date":"2017-05-19 00:00:00 UTC","id":"2","total_visits":"2223"} -{"parsed_date":"2016-11-20 00:00:00 UTC","id":"2","total_visits":"3247"} -{"parsed_date":"2016-11-25 00:00:00 UTC","id":"2","total_visits":"3759"} -{"parsed_date":"2017-03-25 00:00:00 UTC","id":"2","total_visits":"1712"} -{"parsed_date":"2017-01-27 00:00:00 UTC","id":"2","total_visits":"1969"} -{"parsed_date":"2017-06-26 00:00:00 UTC","id":"2","total_visits":"2226"} -{"parsed_date":"2017-05-25 00:00:00 UTC","id":"2","total_visits":"2228"} -{"parsed_date":"2017-01-31 00:00:00 UTC","id":"2","total_visits":"2229"} -{"parsed_date":"2017-07-13 00:00:00 UTC","id":"2","total_visits":"2741"} -{"parsed_date":"2017-03-15 00:00:00 UTC","id":"2","total_visits":"2486"} -{"parsed_date":"2017-05-28 00:00:00 UTC","id":"2","total_visits":"1463"} -{"parsed_date":"2017-03-09 00:00:00 UTC","id":"2","total_visits":"2231"} -{"parsed_date":"2017-07-15 00:00:00 UTC","id":"2","total_visits":"1721"} -{"parsed_date":"2016-11-24 00:00:00 UTC","id":"2","total_visits":"3770"} -{"parsed_date":"2016-10-05 00:00:00 UTC","id":"2","total_visits":"3770"} -{"parsed_date":"2016-12-31 00:00:00 UTC","id":"2","total_visits":"1211"} -{"parsed_date":"2016-10-02 00:00:00 UTC","id":"2","total_visits":"1724"} -{"parsed_date":"2017-07-22 00:00:00 UTC","id":"2","total_visits":"1724"} -{"parsed_date":"2016-09-11 00:00:00 UTC","id":"2","total_visits":"1725"} -{"parsed_date":"2017-06-15 00:00:00 UTC","id":"2","total_visits":"2237"} -{"parsed_date":"2017-06-05 00:00:00 UTC","id":"2","total_visits":"2493"} -{"parsed_date":"2017-02-06 00:00:00 UTC","id":"2","total_visits":"2238"} -{"parsed_date":"2016-10-15 00:00:00 UTC","id":"2","total_visits":"2495"} -{"parsed_date":"2016-08-21 00:00:00 UTC","id":"2","total_visits":"1730"} -{"parsed_date":"2016-08-23 00:00:00 UTC","id":"2","total_visits":"2754"} -{"parsed_date":"2017-06-30 00:00:00 UTC","id":"2","total_visits":"2499"} -{"parsed_date":"2017-01-18 00:00:00 UTC","id":"2","total_visits":"2245"} -{"parsed_date":"2016-08-10 00:00:00 UTC","id":"2","total_visits":"2757"} -{"parsed_date":"2016-12-08 00:00:00 UTC","id":"2","total_visits":"3013"} -{"parsed_date":"2016-11-28 00:00:00 UTC","id":"2","total_visits":"4807"} -{"parsed_date":"2017-05-22 00:00:00 UTC","id":"2","total_visits":"2248"} -{"parsed_date":"2016-09-20 00:00:00 UTC","id":"2","total_visits":"2760"} -{"parsed_date":"2016-10-06 00:00:00 UTC","id":"2","total_visits":"3016"} -{"parsed_date":"2016-09-06 00:00:00 UTC","id":"2","total_visits":"2508"} -{"parsed_date":"2016-09-03 00:00:00 UTC","id":"2","total_visits":"1741"} -{"parsed_date":"2016-12-06 00:00:00 UTC","id":"2","total_visits":"3021"} -{"parsed_date":"2016-12-24 00:00:00 UTC","id":"2","total_visits":"1231"} -{"parsed_date":"2016-10-28 00:00:00 UTC","id":"2","total_visits":"3791"} -{"parsed_date":"2016-12-30 00:00:00 UTC","id":"2","total_visits":"1232"} -{"parsed_date":"2017-05-29 00:00:00 UTC","id":"2","total_visits":"1745"} -{"parsed_date":"2017-07-10 00:00:00 UTC","id":"2","total_visits":"2769"} -{"parsed_date":"2017-06-22 00:00:00 UTC","id":"2","total_visits":"2258"} -{"parsed_date":"2017-07-19 00:00:00 UTC","id":"2","total_visits":"2514"} -{"parsed_date":"2016-10-03 00:00:00 UTC","id":"2","total_visits":"2514"} -{"parsed_date":"2017-06-14 00:00:00 UTC","id":"2","total_visits":"2517"} -{"parsed_date":"2016-10-22 00:00:00 UTC","id":"2","total_visits":"3029"} -{"parsed_date":"2017-01-23 00:00:00 UTC","id":"2","total_visits":"2262"} -{"parsed_date":"2017-04-24 00:00:00 UTC","id":"2","total_visits":"2263"} -{"parsed_date":"2016-11-10 00:00:00 UTC","id":"2","total_visits":"4055"} -{"parsed_date":"2016-09-26 00:00:00 UTC","id":"2","total_visits":"2776"} -{"parsed_date":"2016-10-19 00:00:00 UTC","id":"2","total_visits":"3544"} -{"parsed_date":"2017-03-04 00:00:00 UTC","id":"2","total_visits":"1753"} -{"parsed_date":"2017-05-26 00:00:00 UTC","id":"2","total_visits":"2009"} -{"parsed_date":"2017-02-13 00:00:00 UTC","id":"2","total_visits":"2266"} -{"parsed_date":"2017-02-18 00:00:00 UTC","id":"2","total_visits":"1755"} -{"parsed_date":"2017-03-02 00:00:00 UTC","id":"2","total_visits":"2267"} -{"parsed_date":"2017-03-31 00:00:00 UTC","id":"2","total_visits":"2268"} -{"parsed_date":"2017-01-10 00:00:00 UTC","id":"2","total_visits":"2268"} -{"parsed_date":"2017-03-29 00:00:00 UTC","id":"2","total_visits":"2525"} -{"parsed_date":"2017-03-27 00:00:00 UTC","id":"2","total_visits":"2525"} -{"parsed_date":"2016-11-23 00:00:00 UTC","id":"2","total_visits":"3805"} -{"parsed_date":"2017-05-27 00:00:00 UTC","id":"2","total_visits":"1502"} -{"parsed_date":"2016-10-24 00:00:00 UTC","id":"2","total_visits":"4063"} -{"parsed_date":"2016-12-14 00:00:00 UTC","id":"2","total_visits":"3040"} -{"parsed_date":"2017-02-11 00:00:00 UTC","id":"2","total_visits":"1761"} -{"parsed_date":"2017-07-27 00:00:00 UTC","id":"2","total_visits":"2529"} -{"parsed_date":"2017-02-17 00:00:00 UTC","id":"2","total_visits":"2785"} -{"parsed_date":"2017-04-15 00:00:00 UTC","id":"2","total_visits":"1506"} -{"parsed_date":"2016-11-05 00:00:00 UTC","id":"2","total_visits":"3042"} -{"parsed_date":"2016-10-04 00:00:00 UTC","id":"2","total_visits":"4322"} -{"parsed_date":"2017-05-13 00:00:00 UTC","id":"2","total_visits":"1251"} -{"parsed_date":"2017-04-16 00:00:00 UTC","id":"2","total_visits":"1507"} -{"parsed_date":"2016-12-28 00:00:00 UTC","id":"2","total_visits":"1763"} -{"parsed_date":"2016-08-15 00:00:00 UTC","id":"2","total_visits":"3043"} -{"parsed_date":"2016-12-03 00:00:00 UTC","id":"2","total_visits":"3044"} -{"parsed_date":"2017-06-27 00:00:00 UTC","id":"2","total_visits":"2789"} -{"parsed_date":"2017-06-24 00:00:00 UTC","id":"2","total_visits":"1510"} -{"parsed_date":"2017-07-16 00:00:00 UTC","id":"2","total_visits":"1766"} -{"parsed_date":"2017-04-09 00:00:00 UTC","id":"2","total_visits":"1766"} -{"parsed_date":"2017-06-07 00:00:00 UTC","id":"2","total_visits":"2279"} -{"parsed_date":"2017-04-17 00:00:00 UTC","id":"2","total_visits":"2279"} -{"parsed_date":"2016-09-28 00:00:00 UTC","id":"2","total_visits":"2535"} -{"parsed_date":"2017-03-26 00:00:00 UTC","id":"2","total_visits":"1768"} -{"parsed_date":"2017-05-10 00:00:00 UTC","id":"2","total_visits":"2024"} -{"parsed_date":"2017-06-08 00:00:00 UTC","id":"2","total_visits":"2280"} -{"parsed_date":"2017-05-08 00:00:00 UTC","id":"2","total_visits":"2025"} -{"parsed_date":"2017-03-13 00:00:00 UTC","id":"2","total_visits":"2537"} -{"parsed_date":"2016-11-17 00:00:00 UTC","id":"2","total_visits":"4074"} -{"parsed_date":"2016-08-25 00:00:00 UTC","id":"2","total_visits":"2539"} -{"parsed_date":"2017-02-16 00:00:00 UTC","id":"2","total_visits":"2539"} -{"parsed_date":"2017-06-16 00:00:00 UTC","id":"2","total_visits":"2028"} -{"parsed_date":"2016-11-16 00:00:00 UTC","id":"2","total_visits":"4334"} -{"parsed_date":"2016-08-17 00:00:00 UTC","id":"2","total_visits":"2800"} -{"parsed_date":"2017-03-19 00:00:00 UTC","id":"2","total_visits":"1776"} -{"parsed_date":"2016-11-29 00:00:00 UTC","id":"2","total_visits":"4337"} -{"parsed_date":"2017-02-05 00:00:00 UTC","id":"2","total_visits":"1522"} -{"parsed_date":"2016-10-31 00:00:00 UTC","id":"2","total_visits":"3827"} -{"parsed_date":"2017-05-31 00:00:00 UTC","id":"2","total_visits":"2292"} -{"parsed_date":"2017-07-18 00:00:00 UTC","id":"2","total_visits":"2804"} -{"parsed_date":"2017-03-12 00:00:00 UTC","id":"2","total_visits":"1781"} -{"parsed_date":"2016-09-09 00:00:00 UTC","id":"2","total_visits":"2549"} -{"parsed_date":"2017-01-14 00:00:00 UTC","id":"2","total_visits":"1526"} -{"parsed_date":"2017-05-04 00:00:00 UTC","id":"2","total_visits":"2806"} -{"parsed_date":"2016-11-07 00:00:00 UTC","id":"2","total_visits":"3832"} -{"parsed_date":"2017-04-07 00:00:00 UTC","id":"2","total_visits":"2297"} -{"parsed_date":"2017-07-12 00:00:00 UTC","id":"2","total_visits":"2554"} -{"parsed_date":"2017-04-13 00:00:00 UTC","id":"2","total_visits":"2300"} -{"parsed_date":"2017-08-01 00:00:00 UTC","id":"2","total_visits":"2556"} -{"parsed_date":"2017-06-04 00:00:00 UTC","id":"2","total_visits":"1534"} -{"parsed_date":"2017-02-12 00:00:00 UTC","id":"2","total_visits":"1790"} -{"parsed_date":"2017-07-03 00:00:00 UTC","id":"2","total_visits":"2046"} -{"parsed_date":"2016-09-30 00:00:00 UTC","id":"2","total_visits":"2303"} -{"parsed_date":"2016-08-08 00:00:00 UTC","id":"2","total_visits":"2815"} +{"parsed_date":"2017-07-01 00:00:00 UTC","total_visits":"2048"} +{"parsed_date":"2016-09-07 00:00:00 UTC","total_visits":"2562"} +{"parsed_date":"2016-10-25 00:00:00 UTC","total_visits":"3842"} +{"parsed_date":"2017-04-10 00:00:00 UTC","total_visits":"2563"} +{"parsed_date":"2017-01-09 00:00:00 UTC","total_visits":"2308"} +{"parsed_date":"2017-05-02 00:00:00 UTC","total_visits":"2564"} +{"parsed_date":"2016-11-11 00:00:00 UTC","total_visits":"3588"} +{"parsed_date":"2017-07-30 00:00:00 UTC","total_visits":"1799"} +{"parsed_date":"2017-06-10 00:00:00 UTC","total_visits":"1545"} +{"parsed_date":"2016-08-14 00:00:00 UTC","total_visits":"1801"} +{"parsed_date":"2017-05-14 00:00:00 UTC","total_visits":"1290"} +{"parsed_date":"2017-02-08 00:00:00 UTC","total_visits":"2570"} +{"parsed_date":"2017-06-01 00:00:00 UTC","total_visits":"2826"} +{"parsed_date":"2017-04-23 00:00:00 UTC","total_visits":"1548"} +{"parsed_date":"2016-11-04 00:00:00 UTC","total_visits":"3596"} +{"parsed_date":"2017-02-04 00:00:00 UTC","total_visits":"1549"} +{"parsed_date":"2016-12-09 00:00:00 UTC","total_visits":"2830"} +{"parsed_date":"2016-10-30 00:00:00 UTC","total_visits":"3086"} +{"parsed_date":"2017-03-28 00:00:00 UTC","total_visits":"2577"} +{"parsed_date":"2017-06-11 00:00:00 UTC","total_visits":"1555"} +{"parsed_date":"2016-12-17 00:00:00 UTC","total_visits":"2324"} +{"parsed_date":"2016-09-22 00:00:00 UTC","total_visits":"2581"} +{"parsed_date":"2017-01-29 00:00:00 UTC","total_visits":"1814"} +{"parsed_date":"2017-03-22 00:00:00 UTC","total_visits":"2582"} +{"parsed_date":"2017-02-21 00:00:00 UTC","total_visits":"2582"} +{"parsed_date":"2016-10-14 00:00:00 UTC","total_visits":"2838"} +{"parsed_date":"2017-04-27 00:00:00 UTC","total_visits":"2838"} +{"parsed_date":"2016-10-26 00:00:00 UTC","total_visits":"4375"} +{"parsed_date":"2016-08-22 00:00:00 UTC","total_visits":"2584"} +{"parsed_date":"2016-12-07 00:00:00 UTC","total_visits":"2840"} +{"parsed_date":"2017-01-20 00:00:00 UTC","total_visits":"2074"} +{"parsed_date":"2017-03-07 00:00:00 UTC","total_visits":"2586"} +{"parsed_date":"2017-05-16 00:00:00 UTC","total_visits":"3098"} +{"parsed_date":"2017-05-03 00:00:00 UTC","total_visits":"2588"} +{"parsed_date":"2017-05-01 00:00:00 UTC","total_visits":"2588"} +{"parsed_date":"2016-11-27 00:00:00 UTC","total_visits":"3356"} +{"parsed_date":"2017-04-29 00:00:00 UTC","total_visits":"1566"} +{"parsed_date":"2016-09-18 00:00:00 UTC","total_visits":"1822"} +{"parsed_date":"2017-03-23 00:00:00 UTC","total_visits":"2847"} +{"parsed_date":"2017-03-14 00:00:00 UTC","total_visits":"2338"} +{"parsed_date":"2016-12-21 00:00:00 UTC","total_visits":"2594"} +{"parsed_date":"2016-10-11 00:00:00 UTC","total_visits":"2850"} +{"parsed_date":"2017-01-24 00:00:00 UTC","total_visits":"3618"} +{"parsed_date":"2017-03-05 00:00:00 UTC","total_visits":"1827"} +{"parsed_date":"2017-01-19 00:00:00 UTC","total_visits":"2083"} +{"parsed_date":"2016-08-09 00:00:00 UTC","total_visits":"2851"} +{"parsed_date":"2017-04-08 00:00:00 UTC","total_visits":"1829"} +{"parsed_date":"2017-04-12 00:00:00 UTC","total_visits":"2341"} +{"parsed_date":"2016-09-29 00:00:00 UTC","total_visits":"2597"} +{"parsed_date":"2016-12-20 00:00:00 UTC","total_visits":"3110"} +{"parsed_date":"2017-01-15 00:00:00 UTC","total_visits":"1576"} +{"parsed_date":"2017-04-14 00:00:00 UTC","total_visits":"1834"} +{"parsed_date":"2017-02-28 00:00:00 UTC","total_visits":"2347"} +{"parsed_date":"2016-09-16 00:00:00 UTC","total_visits":"2603"} +{"parsed_date":"2016-10-18 00:00:00 UTC","total_visits":"3628"} +{"parsed_date":"2017-02-24 00:00:00 UTC","total_visits":"2093"} +{"parsed_date":"2017-05-17 00:00:00 UTC","total_visits":"3117"} +{"parsed_date":"2017-06-23 00:00:00 UTC","total_visits":"2095"} +{"parsed_date":"2016-11-12 00:00:00 UTC","total_visits":"3119"} +{"parsed_date":"2016-11-21 00:00:00 UTC","total_visits":"4143"} +{"parsed_date":"2017-02-27 00:00:00 UTC","total_visits":"2352"} +{"parsed_date":"2016-12-26 00:00:00 UTC","total_visits":"1586"} +{"parsed_date":"2017-04-25 00:00:00 UTC","total_visits":"2354"} +{"parsed_date":"2017-03-21 00:00:00 UTC","total_visits":"2611"} +{"parsed_date":"2016-12-22 00:00:00 UTC","total_visits":"2100"} +{"parsed_date":"2016-10-01 00:00:00 UTC","total_visits":"1589"} +{"parsed_date":"2016-09-24 00:00:00 UTC","total_visits":"1845"} +{"parsed_date":"2017-06-21 00:00:00 UTC","total_visits":"2357"} +{"parsed_date":"2016-09-02 00:00:00 UTC","total_visits":"2613"} +{"parsed_date":"2016-08-26 00:00:00 UTC","total_visits":"2359"} +{"parsed_date":"2016-10-12 00:00:00 UTC","total_visits":"2871"} +{"parsed_date":"2017-05-15 00:00:00 UTC","total_visits":"2360"} +{"parsed_date":"2017-06-12 00:00:00 UTC","total_visits":"2361"} +{"parsed_date":"2016-08-16 00:00:00 UTC","total_visits":"2873"} +{"parsed_date":"2017-04-30 00:00:00 UTC","total_visits":"1594"} +{"parsed_date":"2017-04-05 00:00:00 UTC","total_visits":"2619"} +{"parsed_date":"2016-08-12 00:00:00 UTC","total_visits":"2619"} +{"parsed_date":"2016-11-08 00:00:00 UTC","total_visits":"3899"} +{"parsed_date":"2016-08-13 00:00:00 UTC","total_visits":"1596"} +{"parsed_date":"2017-05-09 00:00:00 UTC","total_visits":"2108"} +{"parsed_date":"2017-02-23 00:00:00 UTC","total_visits":"2364"} +{"parsed_date":"2017-07-31 00:00:00 UTC","total_visits":"2620"} +{"parsed_date":"2017-06-25 00:00:00 UTC","total_visits":"1597"} +{"parsed_date":"2017-07-29 00:00:00 UTC","total_visits":"1597"} +{"parsed_date":"2016-09-17 00:00:00 UTC","total_visits":"1853"} +{"parsed_date":"2016-12-27 00:00:00 UTC","total_visits":"1855"} +{"parsed_date":"2017-05-20 00:00:00 UTC","total_visits":"1855"} +{"parsed_date":"2016-10-08 00:00:00 UTC","total_visits":"2114"} +{"parsed_date":"2016-10-27 00:00:00 UTC","total_visits":"4162"} +{"parsed_date":"2017-07-08 00:00:00 UTC","total_visits":"1859"} +{"parsed_date":"2016-08-24 00:00:00 UTC","total_visits":"2627"} +{"parsed_date":"2016-12-23 00:00:00 UTC","total_visits":"1604"} +{"parsed_date":"2017-02-02 00:00:00 UTC","total_visits":"2372"} +{"parsed_date":"2016-09-08 00:00:00 UTC","total_visits":"2628"} +{"parsed_date":"2017-04-02 00:00:00 UTC","total_visits":"1861"} +{"parsed_date":"2017-02-15 00:00:00 UTC","total_visits":"2629"} +{"parsed_date":"2017-07-05 00:00:00 UTC","total_visits":"2885"} +{"parsed_date":"2016-10-17 00:00:00 UTC","total_visits":"3397"} +{"parsed_date":"2017-02-20 00:00:00 UTC","total_visits":"2374"} +{"parsed_date":"2017-03-24 00:00:00 UTC","total_visits":"2374"} +{"parsed_date":"2017-04-20 00:00:00 UTC","total_visits":"2374"} +{"parsed_date":"2016-11-18 00:00:00 UTC","total_visits":"3654"} +{"parsed_date":"2017-07-25 00:00:00 UTC","total_visits":"2631"} +{"parsed_date":"2016-11-13 00:00:00 UTC","total_visits":"3144"} +{"parsed_date":"2017-03-18 00:00:00 UTC","total_visits":"1610"} +{"parsed_date":"2016-08-03 00:00:00 UTC","total_visits":"2890"} +{"parsed_date":"2016-08-19 00:00:00 UTC","total_visits":"2379"} +{"parsed_date":"2017-02-14 00:00:00 UTC","total_visits":"2379"} +{"parsed_date":"2017-07-11 00:00:00 UTC","total_visits":"2635"} +{"parsed_date":"2017-04-22 00:00:00 UTC","total_visits":"1612"} +{"parsed_date":"2016-10-07 00:00:00 UTC","total_visits":"2892"} +{"parsed_date":"2016-09-05 00:00:00 UTC","total_visits":"2125"} +{"parsed_date":"2016-09-23 00:00:00 UTC","total_visits":"2381"} +{"parsed_date":"2016-11-15 00:00:00 UTC","total_visits":"4685"} +{"parsed_date":"2017-01-28 00:00:00 UTC","total_visits":"1614"} +{"parsed_date":"2017-07-14 00:00:00 UTC","total_visits":"2382"} +{"parsed_date":"2017-01-07 00:00:00 UTC","total_visits":"1615"} +{"parsed_date":"2017-04-03 00:00:00 UTC","total_visits":"2383"} +{"parsed_date":"2017-03-20 00:00:00 UTC","total_visits":"2383"} +{"parsed_date":"2016-12-18 00:00:00 UTC","total_visits":"2128"} +{"parsed_date":"2017-03-17 00:00:00 UTC","total_visits":"2129"} +{"parsed_date":"2017-05-23 00:00:00 UTC","total_visits":"2129"} +{"parsed_date":"2016-11-30 00:00:00 UTC","total_visits":"4435"} +{"parsed_date":"2017-01-01 00:00:00 UTC","total_visits":"1364"} +{"parsed_date":"2017-01-02 00:00:00 UTC","total_visits":"1620"} +{"parsed_date":"2016-09-25 00:00:00 UTC","total_visits":"1877"} +{"parsed_date":"2016-08-07 00:00:00 UTC","total_visits":"1622"} +{"parsed_date":"2016-10-09 00:00:00 UTC","total_visits":"2134"} +{"parsed_date":"2017-03-01 00:00:00 UTC","total_visits":"2390"} +{"parsed_date":"2017-01-04 00:00:00 UTC","total_visits":"2390"} +{"parsed_date":"2017-06-06 00:00:00 UTC","total_visits":"2391"} +{"parsed_date":"2017-04-18 00:00:00 UTC","total_visits":"2391"} +{"parsed_date":"2017-04-06 00:00:00 UTC","total_visits":"2647"} +{"parsed_date":"2017-01-30 00:00:00 UTC","total_visits":"2392"} +{"parsed_date":"2016-10-16 00:00:00 UTC","total_visits":"2649"} +{"parsed_date":"2016-08-04 00:00:00 UTC","total_visits":"3161"} +{"parsed_date":"2016-10-21 00:00:00 UTC","total_visits":"3419"} +{"parsed_date":"2016-08-02 00:00:00 UTC","total_visits":"2140"} +{"parsed_date":"2017-03-06 00:00:00 UTC","total_visits":"2396"} +{"parsed_date":"2016-09-13 00:00:00 UTC","total_visits":"2396"} +{"parsed_date":"2016-09-14 00:00:00 UTC","total_visits":"2652"} +{"parsed_date":"2017-04-19 00:00:00 UTC","total_visits":"2397"} +{"parsed_date":"2017-06-19 00:00:00 UTC","total_visits":"2142"} +{"parsed_date":"2016-12-13 00:00:00 UTC","total_visits":"3166"} +{"parsed_date":"2017-06-20 00:00:00 UTC","total_visits":"2143"} +{"parsed_date":"2016-10-10 00:00:00 UTC","total_visits":"2911"} +{"parsed_date":"2017-07-06 00:00:00 UTC","total_visits":"2658"} +{"parsed_date":"2017-01-03 00:00:00 UTC","total_visits":"2403"} +{"parsed_date":"2017-01-08 00:00:00 UTC","total_visits":"1637"} +{"parsed_date":"2017-02-25 00:00:00 UTC","total_visits":"1638"} +{"parsed_date":"2017-05-24 00:00:00 UTC","total_visits":"2406"} +{"parsed_date":"2016-11-22 00:00:00 UTC","total_visits":"3942"} +{"parsed_date":"2017-05-06 00:00:00 UTC","total_visits":"1383"} +{"parsed_date":"2017-07-02 00:00:00 UTC","total_visits":"1895"} +{"parsed_date":"2016-12-01 00:00:00 UTC","total_visits":"4200"} +{"parsed_date":"2017-03-16 00:00:00 UTC","total_visits":"2409"} +{"parsed_date":"2016-12-12 00:00:00 UTC","total_visits":"3433"} +{"parsed_date":"2016-12-25 00:00:00 UTC","total_visits":"1386"} +{"parsed_date":"2017-02-26 00:00:00 UTC","total_visits":"1643"} +{"parsed_date":"2017-04-28 00:00:00 UTC","total_visits":"2411"} +{"parsed_date":"2016-08-11 00:00:00 UTC","total_visits":"2667"} +{"parsed_date":"2017-07-20 00:00:00 UTC","total_visits":"2668"} +{"parsed_date":"2017-05-21 00:00:00 UTC","total_visits":"1645"} +{"parsed_date":"2017-06-17 00:00:00 UTC","total_visits":"1391"} +{"parsed_date":"2016-12-29 00:00:00 UTC","total_visits":"1647"} +{"parsed_date":"2017-07-17 00:00:00 UTC","total_visits":"2671"} +{"parsed_date":"2017-01-16 00:00:00 UTC","total_visits":"1906"} +{"parsed_date":"2017-03-03 00:00:00 UTC","total_visits":"2162"} +{"parsed_date":"2016-11-14 00:00:00 UTC","total_visits":"4466"} +{"parsed_date":"2016-08-30 00:00:00 UTC","total_visits":"2675"} +{"parsed_date":"2016-08-27 00:00:00 UTC","total_visits":"1654"} +{"parsed_date":"2017-02-09 00:00:00 UTC","total_visits":"2678"} +{"parsed_date":"2017-06-03 00:00:00 UTC","total_visits":"1399"} +{"parsed_date":"2017-05-07 00:00:00 UTC","total_visits":"1400"} +{"parsed_date":"2016-11-02 00:00:00 UTC","total_visits":"3960"} +{"parsed_date":"2016-12-15 00:00:00 UTC","total_visits":"2937"} +{"parsed_date":"2017-04-01 00:00:00 UTC","total_visits":"2170"} +{"parsed_date":"2017-07-21 00:00:00 UTC","total_visits":"2427"} +{"parsed_date":"2016-08-06 00:00:00 UTC","total_visits":"1663"} +{"parsed_date":"2016-09-01 00:00:00 UTC","total_visits":"2687"} +{"parsed_date":"2017-06-28 00:00:00 UTC","total_visits":"2687"} +{"parsed_date":"2016-08-20 00:00:00 UTC","total_visits":"1664"} +{"parsed_date":"2017-04-26 00:00:00 UTC","total_visits":"4224"} +{"parsed_date":"2017-07-09 00:00:00 UTC","total_visits":"1921"} +{"parsed_date":"2017-07-28 00:00:00 UTC","total_visits":"2433"} +{"parsed_date":"2016-09-19 00:00:00 UTC","total_visits":"2689"} +{"parsed_date":"2017-07-24 00:00:00 UTC","total_visits":"2436"} +{"parsed_date":"2017-06-13 00:00:00 UTC","total_visits":"2181"} +{"parsed_date":"2016-09-15 00:00:00 UTC","total_visits":"2949"} +{"parsed_date":"2017-02-03 00:00:00 UTC","total_visits":"2182"} +{"parsed_date":"2016-09-10 00:00:00 UTC","total_visits":"1671"} +{"parsed_date":"2017-06-09 00:00:00 UTC","total_visits":"1927"} +{"parsed_date":"2017-01-11 00:00:00 UTC","total_visits":"2185"} +{"parsed_date":"2017-02-19 00:00:00 UTC","total_visits":"2187"} +{"parsed_date":"2017-01-17 00:00:00 UTC","total_visits":"2443"} +{"parsed_date":"2017-05-12 00:00:00 UTC","total_visits":"1932"} +{"parsed_date":"2016-12-16 00:00:00 UTC","total_visits":"2956"} +{"parsed_date":"2017-02-01 00:00:00 UTC","total_visits":"2445"} +{"parsed_date":"2016-11-26 00:00:00 UTC","total_visits":"3213"} +{"parsed_date":"2017-06-02 00:00:00 UTC","total_visits":"2190"} +{"parsed_date":"2016-08-05 00:00:00 UTC","total_visits":"2702"} +{"parsed_date":"2016-11-01 00:00:00 UTC","total_visits":"3728"} +{"parsed_date":"2017-01-05 00:00:00 UTC","total_visits":"2193"} +{"parsed_date":"2017-03-08 00:00:00 UTC","total_visits":"2449"} +{"parsed_date":"2016-08-28 00:00:00 UTC","total_visits":"1682"} +{"parsed_date":"2017-07-04 00:00:00 UTC","total_visits":"1938"} +{"parsed_date":"2017-03-10 00:00:00 UTC","total_visits":"2194"} +{"parsed_date":"2017-07-07 00:00:00 UTC","total_visits":"2450"} +{"parsed_date":"2016-10-29 00:00:00 UTC","total_visits":"2964"} +{"parsed_date":"2016-10-13 00:00:00 UTC","total_visits":"2964"} +{"parsed_date":"2016-12-04 00:00:00 UTC","total_visits":"3220"} +{"parsed_date":"2017-01-21 00:00:00 UTC","total_visits":"1685"} +{"parsed_date":"2017-06-29 00:00:00 UTC","total_visits":"2709"} +{"parsed_date":"2016-08-29 00:00:00 UTC","total_visits":"2454"} +{"parsed_date":"2016-12-19 00:00:00 UTC","total_visits":"3222"} +{"parsed_date":"2017-05-30 00:00:00 UTC","total_visits":"2199"} +{"parsed_date":"2017-02-10 00:00:00 UTC","total_visits":"2199"} +{"parsed_date":"2016-08-31 00:00:00 UTC","total_visits":"3223"} +{"parsed_date":"2017-06-18 00:00:00 UTC","total_visits":"1432"} +{"parsed_date":"2017-01-12 00:00:00 UTC","total_visits":"2203"} +{"parsed_date":"2017-05-18 00:00:00 UTC","total_visits":"2715"} +{"parsed_date":"2016-10-23 00:00:00 UTC","total_visits":"2971"} +{"parsed_date":"2016-09-04 00:00:00 UTC","total_visits":"1692"} +{"parsed_date":"2016-12-10 00:00:00 UTC","total_visits":"2207"} +{"parsed_date":"2016-12-11 00:00:00 UTC","total_visits":"2208"} +{"parsed_date":"2017-04-11 00:00:00 UTC","total_visits":"2464"} +{"parsed_date":"2016-09-21 00:00:00 UTC","total_visits":"2720"} +{"parsed_date":"2016-11-06 00:00:00 UTC","total_visits":"3232"} +{"parsed_date":"2017-01-26 00:00:00 UTC","total_visits":"2209"} +{"parsed_date":"2016-09-12 00:00:00 UTC","total_visits":"2465"} +{"parsed_date":"2017-04-21 00:00:00 UTC","total_visits":"2210"} +{"parsed_date":"2017-01-06 00:00:00 UTC","total_visits":"2210"} +{"parsed_date":"2017-04-04 00:00:00 UTC","total_visits":"2978"} +{"parsed_date":"2017-01-22 00:00:00 UTC","total_visits":"1700"} +{"parsed_date":"2017-07-26 00:00:00 UTC","total_visits":"2725"} +{"parsed_date":"2016-08-18 00:00:00 UTC","total_visits":"2725"} +{"parsed_date":"2016-09-27 00:00:00 UTC","total_visits":"2727"} +{"parsed_date":"2016-12-02 00:00:00 UTC","total_visits":"3751"} +{"parsed_date":"2017-05-05 00:00:00 UTC","total_visits":"1960"} +{"parsed_date":"2016-11-19 00:00:00 UTC","total_visits":"2984"} +{"parsed_date":"2016-11-09 00:00:00 UTC","total_visits":"3752"} +{"parsed_date":"2016-12-05 00:00:00 UTC","total_visits":"4265"} +{"parsed_date":"2017-05-11 00:00:00 UTC","total_visits":"2218"} +{"parsed_date":"2017-01-25 00:00:00 UTC","total_visits":"2986"} +{"parsed_date":"2017-03-11 00:00:00 UTC","total_visits":"1707"} +{"parsed_date":"2017-03-30 00:00:00 UTC","total_visits":"2731"} +{"parsed_date":"2016-10-20 00:00:00 UTC","total_visits":"3755"} +{"parsed_date":"2017-02-07 00:00:00 UTC","total_visits":"2476"} +{"parsed_date":"2017-02-22 00:00:00 UTC","total_visits":"2477"} +{"parsed_date":"2017-07-23 00:00:00 UTC","total_visits":"1966"} +{"parsed_date":"2016-11-03 00:00:00 UTC","total_visits":"4014"} +{"parsed_date":"2016-08-01 00:00:00 UTC","total_visits":"1711"} +{"parsed_date":"2017-01-13 00:00:00 UTC","total_visits":"1967"} +{"parsed_date":"2017-05-19 00:00:00 UTC","total_visits":"2223"} +{"parsed_date":"2016-11-20 00:00:00 UTC","total_visits":"3247"} +{"parsed_date":"2016-11-25 00:00:00 UTC","total_visits":"3759"} +{"parsed_date":"2017-03-25 00:00:00 UTC","total_visits":"1712"} +{"parsed_date":"2017-01-27 00:00:00 UTC","total_visits":"1969"} +{"parsed_date":"2017-06-26 00:00:00 UTC","total_visits":"2226"} +{"parsed_date":"2017-05-25 00:00:00 UTC","total_visits":"2228"} +{"parsed_date":"2017-01-31 00:00:00 UTC","total_visits":"2229"} +{"parsed_date":"2017-07-13 00:00:00 UTC","total_visits":"2741"} +{"parsed_date":"2017-03-15 00:00:00 UTC","total_visits":"2486"} +{"parsed_date":"2017-05-28 00:00:00 UTC","total_visits":"1463"} +{"parsed_date":"2017-03-09 00:00:00 UTC","total_visits":"2231"} +{"parsed_date":"2017-07-15 00:00:00 UTC","total_visits":"1721"} +{"parsed_date":"2016-11-24 00:00:00 UTC","total_visits":"3770"} +{"parsed_date":"2016-10-05 00:00:00 UTC","total_visits":"3770"} +{"parsed_date":"2016-12-31 00:00:00 UTC","total_visits":"1211"} +{"parsed_date":"2016-10-02 00:00:00 UTC","total_visits":"1724"} +{"parsed_date":"2017-07-22 00:00:00 UTC","total_visits":"1724"} +{"parsed_date":"2016-09-11 00:00:00 UTC","total_visits":"1725"} +{"parsed_date":"2017-06-15 00:00:00 UTC","total_visits":"2237"} +{"parsed_date":"2017-06-05 00:00:00 UTC","total_visits":"2493"} +{"parsed_date":"2017-02-06 00:00:00 UTC","total_visits":"2238"} +{"parsed_date":"2016-10-15 00:00:00 UTC","total_visits":"2495"} +{"parsed_date":"2016-08-21 00:00:00 UTC","total_visits":"1730"} +{"parsed_date":"2016-08-23 00:00:00 UTC","total_visits":"2754"} +{"parsed_date":"2017-06-30 00:00:00 UTC","total_visits":"2499"} +{"parsed_date":"2017-01-18 00:00:00 UTC","total_visits":"2245"} +{"parsed_date":"2016-08-10 00:00:00 UTC","total_visits":"2757"} +{"parsed_date":"2016-12-08 00:00:00 UTC","total_visits":"3013"} +{"parsed_date":"2016-11-28 00:00:00 UTC","total_visits":"4807"} +{"parsed_date":"2017-05-22 00:00:00 UTC","total_visits":"2248"} +{"parsed_date":"2016-09-20 00:00:00 UTC","total_visits":"2760"} +{"parsed_date":"2016-10-06 00:00:00 UTC","total_visits":"3016"} +{"parsed_date":"2016-09-06 00:00:00 UTC","total_visits":"2508"} +{"parsed_date":"2016-09-03 00:00:00 UTC","total_visits":"1741"} +{"parsed_date":"2016-12-06 00:00:00 UTC","total_visits":"3021"} +{"parsed_date":"2016-12-24 00:00:00 UTC","total_visits":"1231"} +{"parsed_date":"2016-10-28 00:00:00 UTC","total_visits":"3791"} +{"parsed_date":"2016-12-30 00:00:00 UTC","total_visits":"1232"} +{"parsed_date":"2017-05-29 00:00:00 UTC","total_visits":"1745"} +{"parsed_date":"2017-07-10 00:00:00 UTC","total_visits":"2769"} +{"parsed_date":"2017-06-22 00:00:00 UTC","total_visits":"2258"} +{"parsed_date":"2017-07-19 00:00:00 UTC","total_visits":"2514"} +{"parsed_date":"2016-10-03 00:00:00 UTC","total_visits":"2514"} +{"parsed_date":"2017-06-14 00:00:00 UTC","total_visits":"2517"} +{"parsed_date":"2016-10-22 00:00:00 UTC","total_visits":"3029"} +{"parsed_date":"2017-01-23 00:00:00 UTC","total_visits":"2262"} +{"parsed_date":"2017-04-24 00:00:00 UTC","total_visits":"2263"} +{"parsed_date":"2016-11-10 00:00:00 UTC","total_visits":"4055"} +{"parsed_date":"2016-09-26 00:00:00 UTC","total_visits":"2776"} +{"parsed_date":"2016-10-19 00:00:00 UTC","total_visits":"3544"} +{"parsed_date":"2017-03-04 00:00:00 UTC","total_visits":"1753"} +{"parsed_date":"2017-05-26 00:00:00 UTC","total_visits":"2009"} +{"parsed_date":"2017-02-13 00:00:00 UTC","total_visits":"2266"} +{"parsed_date":"2017-02-18 00:00:00 UTC","total_visits":"1755"} +{"parsed_date":"2017-03-02 00:00:00 UTC","total_visits":"2267"} +{"parsed_date":"2017-03-31 00:00:00 UTC","total_visits":"2268"} +{"parsed_date":"2017-01-10 00:00:00 UTC","total_visits":"2268"} +{"parsed_date":"2017-03-29 00:00:00 UTC","total_visits":"2525"} +{"parsed_date":"2017-03-27 00:00:00 UTC","total_visits":"2525"} +{"parsed_date":"2016-11-23 00:00:00 UTC","total_visits":"3805"} +{"parsed_date":"2017-05-27 00:00:00 UTC","total_visits":"1502"} +{"parsed_date":"2016-10-24 00:00:00 UTC","total_visits":"4063"} +{"parsed_date":"2016-12-14 00:00:00 UTC","total_visits":"3040"} +{"parsed_date":"2017-02-11 00:00:00 UTC","total_visits":"1761"} +{"parsed_date":"2017-07-27 00:00:00 UTC","total_visits":"2529"} +{"parsed_date":"2017-02-17 00:00:00 UTC","total_visits":"2785"} +{"parsed_date":"2017-04-15 00:00:00 UTC","total_visits":"1506"} +{"parsed_date":"2016-11-05 00:00:00 UTC","total_visits":"3042"} +{"parsed_date":"2016-10-04 00:00:00 UTC","total_visits":"4322"} +{"parsed_date":"2017-05-13 00:00:00 UTC","total_visits":"1251"} +{"parsed_date":"2017-04-16 00:00:00 UTC","total_visits":"1507"} +{"parsed_date":"2016-12-28 00:00:00 UTC","total_visits":"1763"} +{"parsed_date":"2016-08-15 00:00:00 UTC","total_visits":"3043"} +{"parsed_date":"2016-12-03 00:00:00 UTC","total_visits":"3044"} +{"parsed_date":"2017-06-27 00:00:00 UTC","total_visits":"2789"} +{"parsed_date":"2017-06-24 00:00:00 UTC","total_visits":"1510"} +{"parsed_date":"2017-07-16 00:00:00 UTC","total_visits":"1766"} +{"parsed_date":"2017-04-09 00:00:00 UTC","total_visits":"1766"} +{"parsed_date":"2017-06-07 00:00:00 UTC","total_visits":"2279"} +{"parsed_date":"2017-04-17 00:00:00 UTC","total_visits":"2279"} +{"parsed_date":"2016-09-28 00:00:00 UTC","total_visits":"2535"} +{"parsed_date":"2017-03-26 00:00:00 UTC","total_visits":"1768"} +{"parsed_date":"2017-05-10 00:00:00 UTC","total_visits":"2024"} +{"parsed_date":"2017-06-08 00:00:00 UTC","total_visits":"2280"} +{"parsed_date":"2017-05-08 00:00:00 UTC","total_visits":"2025"} +{"parsed_date":"2017-03-13 00:00:00 UTC","total_visits":"2537"} +{"parsed_date":"2016-11-17 00:00:00 UTC","total_visits":"4074"} +{"parsed_date":"2016-08-25 00:00:00 UTC","total_visits":"2539"} +{"parsed_date":"2017-02-16 00:00:00 UTC","total_visits":"2539"} +{"parsed_date":"2017-06-16 00:00:00 UTC","total_visits":"2028"} +{"parsed_date":"2016-11-16 00:00:00 UTC","total_visits":"4334"} +{"parsed_date":"2016-08-17 00:00:00 UTC","total_visits":"2799"} +{"parsed_date":"2017-03-19 00:00:00 UTC","total_visits":"1776"} +{"parsed_date":"2016-11-29 00:00:00 UTC","total_visits":"4337"} +{"parsed_date":"2017-02-05 00:00:00 UTC","total_visits":"1522"} +{"parsed_date":"2016-10-31 00:00:00 UTC","total_visits":"3827"} +{"parsed_date":"2017-05-31 00:00:00 UTC","total_visits":"2292"} +{"parsed_date":"2017-07-18 00:00:00 UTC","total_visits":"2804"} +{"parsed_date":"2017-03-12 00:00:00 UTC","total_visits":"1781"} +{"parsed_date":"2016-09-09 00:00:00 UTC","total_visits":"2549"} +{"parsed_date":"2017-01-14 00:00:00 UTC","total_visits":"1526"} +{"parsed_date":"2017-05-04 00:00:00 UTC","total_visits":"2806"} +{"parsed_date":"2016-11-07 00:00:00 UTC","total_visits":"3832"} +{"parsed_date":"2017-04-07 00:00:00 UTC","total_visits":"2297"} +{"parsed_date":"2017-07-12 00:00:00 UTC","total_visits":"2554"} +{"parsed_date":"2017-04-13 00:00:00 UTC","total_visits":"2300"} +{"parsed_date":"2017-08-01 00:00:00 UTC","total_visits":"2556"} +{"parsed_date":"2017-06-04 00:00:00 UTC","total_visits":"1534"} +{"parsed_date":"2017-02-12 00:00:00 UTC","total_visits":"1790"} +{"parsed_date":"2017-07-03 00:00:00 UTC","total_visits":"2046"} +{"parsed_date":"2016-09-30 00:00:00 UTC","total_visits":"2303"} +{"parsed_date":"2016-08-08 00:00:00 UTC","total_visits":"2815"} diff --git a/tests/data/time_series_schema.json b/tests/data/time_series_schema.json index 35473dc0e36..857595b9e64 100644 --- a/tests/data/time_series_schema.json +++ b/tests/data/time_series_schema.json @@ -4,11 +4,6 @@ "name": "parsed_date", "type": "TIMESTAMP" }, - { - "mode": "NULLABLE", - "name": "id", - "type": "STRING" - }, { "mode": "NULLABLE", "name": "total_visits", diff --git a/tests/data/urban_areas.jsonl b/tests/data/urban_areas.jsonl deleted file mode 100644 index 3bc2fbf38ec..00000000000 --- a/tests/data/urban_areas.jsonl +++ /dev/null @@ -1,22 +0,0 @@ -{"name":"unknown", "geo_id":"0"} -{"name":"atlantis", "geo_id":"7", "internal_point_geom":"POINT(0 0)"} -{"geo_id":"69184","urban_area_code":"69184","name":"Phoenix--Mesa, AZ","lsad_name":"Phoenix--Mesa, AZ Urbanized Area","area_lsad_code":"75","mtfcc_feature_class_code":"G3500","type":"U","functional_status":"S","area_land_meters":2968773027,"area_water_meters":12679933,"internal_point_lon":-111.9700406,"internal_point_lat":33.4940669,"internal_point_geom":"POINT(-111.9700406 33.4940669)","urban_area_geom":"MULTIPOLYGON(((-111.471305 33.373296, -111.471432 33.373286, -111.471801 33.37332, -111.47239 33.373455, -111.472618 33.373523, -111.472816 33.373559, -111.473002 33.373537, -111.473195 33.373455, -111.473692 33.373103, -111.474124 33.372902, -111.474146 33.372895, -111.47435 33.372835, -111.474957 33.372739, -111.47527 33.3727, -111.475475 33.372656, -111.47566 33.372574, -111.47585 33.372476, -111.475961 33.372397, -111.47608 33.372284, -111.47618 33.372149, -111.476287 33.371977, -111.476342 33.371837, -111.47637 33.371599, -111.47637 33.371088, -111.476369 33.370699, -111.476319 33.370376, -111.476254 33.37022, -111.476206 33.370102, -111.476006 33.36974, -111.475788 33.369369, -111.475695 33.369248, -111.47565 33.369251, -111.475501 33.36928, -111.475226 33.369411, -111.474795 33.369598, -111.47472 33.369639, -111.474485 33.369374, -111.474285 33.369151, -111.474158 33.369239, -111.474076 33.369154, -111.474201 33.369064, -111.475273 33.36829, -111.475361 33.368226, -111.475371 33.36822, -111.475541 33.368105, -111.475676 33.368012, -111.475808 33.367922, -111.475922 33.367845, -111.476052 33.367758, -111.476191 33.367663, -111.476275 33.367606, -111.476402 33.36752, -111.476239 33.367365, -111.47614 33.367288, -111.475672 33.366923, -111.475915 33.366757, -111.476544 33.366328, -111.476886 33.366094, -111.477022 33.366, -111.477229 33.366213, -111.47712 33.365668, -111.476886 33.36543, -111.476783 33.365326, -111.476235 33.36477, -111.475986 33.364517, -111.475721 33.364249, -111.47536 33.363875, -111.475198 33.363708, -111.474797 33.363292, -111.47405 33.362574, -111.47322 33.361697, -111.472607 33.361148, -111.471888 33.360537, -111.471755 33.360441, -111.471349 33.360148, -111.471227 33.360065, -111.471558 33.359737, -111.47161 33.359674, -111.471659 33.359585, -111.471581 33.359557, -111.471457 33.359506, -111.47142 33.35949, -111.471384 33.359468, -111.471354 33.359442, -111.471327 33.359411, -111.471305 33.359377, -111.471288 33.35934, -111.471277 33.3593, -111.471272 33.35926, -111.471272 33.359148, -111.471775 33.359146, -111.47434 33.359135, -111.474339 33.358746, -111.474338 33.358343, -111.474517 33.357523, -111.474704 33.357229, -111.474778 33.357425, -111.474839 33.357757, -111.474842 33.357824, -111.474965 33.35781, -111.475046 33.357754, -111.475106 33.357742, -111.475274 33.357644, -111.475326 33.357564, -111.475164 33.357476, -111.475133 33.357453, -111.475055 33.357394, -111.474896 33.357121, -111.474776 33.357053, -111.474656 33.357036, -111.474649 33.356983, -111.474665 33.356724, -111.474717 33.356499, -111.474754 33.356312, -111.474758 33.35626, -111.474529 33.355793, -111.474579 33.353276, -111.474445 33.352803, -111.474148 33.352716, -111.473214 33.352728, -111.473219 33.352421, -111.472954 33.352423, -111.472816 33.352423, -111.472678 33.352421, -111.472403 33.352421, -111.472134 33.352421, -111.471937 33.352422, -111.471741 33.352424, -111.471614 33.352426, -111.471234 33.352429, -111.47111 33.352431, -111.470744 33.352435, -111.470626 33.352437, -111.470285 33.352438, -111.4702 33.352437, -111.470199 33.352393, -111.470201 33.352334, -111.4702 33.352272, -111.470197 33.352209, -111.470195 33.352086, -111.470194 33.352036, -111.470193 33.351968, -111.470186 33.351898, -111.469634 33.351893, -111.469557 33.351895, -111.469503 33.351906, -111.469441 33.351931, -111.469389 33.351956, -111.469343 33.351982, -111.469295 33.352018, -111.469258 33.352061, -111.469226 33.352109, -111.469199 33.352162, -111.469176 33.352212, -111.46916 33.352262, -111.469147 33.352317, -111.469143 33.352332, -111.469054 33.352302, -111.468776 33.35222, -111.468697 33.352195, -111.468585 33.352158, -111.468447 33.352093, -111.46835 33.352036, -111.468242 33.35197, -111.468189 33.351927, -111.468139 33.351891, -111.4681 33.351858, -111.468051 33.351819, -111.468011 33.351788, -111.467943 33.351743, -111.467903 33.351709, -111.467853 33.351685, -111.467806 33.351654, -111.46776 33.351624, -111.467718 33.351593, -111.467676 33.351564, -111.467627 33.351536, -111.467578 33.351506, -111.46753 33.351478, -111.467485 33.35145, -111.467439 33.351422, -111.467391 33.351397, -111.46733 33.351371, -111.467282 33.351349, -111.46723 33.351329, -111.467152 33.351304, -111.467102 33.351286, -111.467039 33.35127, -111.466973 33.351253, -111.466918 33.351244, -111.466863 33.351235, -111.466798 33.351225, -111.466734 33.351221, -111.466658 33.351215, -111.466598 33.351211, -111.466534 33.35121, -111.466468 33.351204, -111.466437 33.351203, -111.466414 33.351203, -111.46636 33.351203, -111.466306 33.351204, -111.466239 33.351205, -111.466173 33.351208, -111.466119 33.351207, -111.466052 33.351205, -111.465977 33.351202, -111.465912 33.351197, -111.465848 33.351197, -111.465779 33.351203, -111.465717 33.351208, -111.465677 33.351197, -111.463375 33.351203, -111.463193 33.351232, -111.463089 33.351281, -111.463006 33.35141, -111.462975 33.351509, -111.462807 33.351472, -111.46256 33.35148, -111.462543 33.351501, -111.462528 33.351871, -111.462546 33.352103, -111.462608 33.3522, -111.462741 33.352285, -111.462926 33.352308, -111.463024 33.352283, -111.463127 33.35222, -111.463345 33.352379, -111.463669 33.352598, -111.464071 33.352871, -111.463656 33.353301, -111.463295 33.353062, -111.463077 33.353301, -111.462982 33.353377, -111.462826 33.353422, -111.461865 33.353614, -111.460546 33.352722, -111.459707 33.352144, -111.458504 33.35117, -111.458178 33.351481, -111.457276 33.350862, -111.456683 33.35044, -111.456572 33.350361, -111.455212 33.349359, -111.454206 33.348584, -111.453007 33.347596, -111.452934 33.347535, -111.451904 33.346641, -111.450403 33.345365, -111.450279 33.345247, -111.449536 33.344549, -111.448211 33.34335, -111.447638 33.342824, -111.446946 33.342191, -111.446045 33.341365, -111.445058 33.34046, -111.444198 33.339664, -111.443398 33.33896, -111.441744 33.337451, -111.441028 33.336773, -111.440725 33.336633, -111.440499 33.336544, -111.440244 33.336506, -111.440029 33.336514, -111.439815 33.336551, -111.439646 33.33661, -111.43945 33.336669, -111.439073 33.336969, -111.438985 33.337038, -111.438097 33.337719, -111.437913 33.337839, -111.437675 33.337964, -111.437388 33.33809, -111.437309 33.337916, -111.43726 33.33777, -111.437091 33.337836, -111.437009 33.337652, -111.436874 33.337468, -111.436749 33.337341, -111.436618 33.337272, -111.436532 33.337232, -111.436262 33.337266, -111.43621 33.337283, -111.43602 33.337347, -111.435721 33.337427, -111.43554 33.337448, -111.435308 33.337466, -111.435104 33.337497, -111.434917 33.337569, -111.43466 33.33768, -111.43447 33.337743, -111.434418 33.33775, -111.434298 33.337767, -111.434011 33.337747, -111.43361 33.337696, -111.433361 33.337646, -111.433256 33.337618, -111.433224 33.337609, -111.433144 33.337566, -111.433016 33.337427, -111.432823 33.337148, -111.43269 33.336957, -111.432612 33.336868, -111.432535 33.336793, -111.432407 33.336737, -111.432271 33.336725, -111.432078 33.336734, -111.431903 33.336788, -111.431527 33.336989, -111.43143 33.337079, -111.431392 33.337128, -111.431214 33.337038, -111.43109 33.336989, -111.43098 33.336958, -111.430739 33.336947, -111.429173 33.336951, -111.428842 33.336962, -111.428641 33.336959, -111.428626 33.337074, -111.428573 33.337166, -111.428454 33.337308, -111.428316 33.337421, -111.428124 33.337564, -111.428037 33.337677, -111.427977 33.337787, -111.42794 33.337902, -111.427932 33.338071, -111.427967 33.338251, -111.427995 33.338296, -111.42804 33.338368, -111.428091 33.338479, -111.428146 33.338715, -111.428214 33.339108, -111.427964 33.339132, -111.427546 33.339218, -111.427315 33.339241, -111.427146 33.339297, -111.427095 33.339469, -111.427055 33.339654, -111.426987 33.339806, -111.426974 33.339834, -111.426894 33.33995, -111.42675 33.340057, -111.426614 33.340151, -111.426371 33.340318, -111.42611 33.340493, -111.425989 33.340573, -111.425804 33.340697, -111.42554 33.340902, -111.425439 33.341049, -111.425398 33.341231, -111.425398 33.341246, -111.425387 33.341588, -111.425387 33.342022, -111.425387 33.342095, -111.425474 33.342624, -111.425518 33.342837, -111.425425 33.343119, -111.425378 33.343379, -111.425367 33.343642, -111.425379 33.343644, -111.42565 33.343688, -111.425957 33.343702, -111.426156 33.343688, -111.426352 33.343608, -111.426387 33.343626, -111.426448 33.343626, -111.4265 33.343604, -111.426519 33.343569, -111.426509 33.343542, -111.426703 33.343516, -111.426851 33.343521, -111.426926 33.343535, -111.42699 33.343428, -111.427084 33.343352, -111.427265 33.343285, -111.427462 33.34328, -111.42758 33.343304, -111.427671 33.343353, -111.427795 33.343435, -111.428029 33.343229, -111.428289 33.34301, -111.428357 33.343151, -111.428557 33.343338, -111.428741 33.34339, -111.429765 33.343395, -111.429993 33.343362, -111.43015 33.343297, -111.430259 33.343204, -111.430412 33.343024, -111.430638 33.343148, -111.431123 33.343395, -111.431295 33.343407, -111.431841 33.343403, -111.432125 33.343355, -111.432239 33.343306, -111.432279 33.343289, -111.432566 33.343111, -111.432682 33.343046, -111.43493 33.343954, -111.435385 33.344219, -111.436448 33.344819, -111.437788 33.345607, -111.440843 33.347403, -111.446368 33.350593, -111.447865 33.3514, -111.447932 33.351385, -111.448248 33.351377, -111.448689 33.351376, -111.448716 33.351518, -111.448783 33.351654, -111.44892 33.351782, -111.449203 33.351956, -111.449957 33.352387, -111.44988 33.352487, -111.451576 33.3534, -111.452787 33.354053, -111.453483 33.354365, -111.454501 33.354819, -111.454164 33.355128, -111.454014 33.355264, -111.453501 33.354961, -111.453496 33.355055, -111.453505 33.355321, -111.453764 33.355491, -111.453406 33.355812, -111.453127 33.355665, -111.452475 33.356208, -111.452742 33.356425, -111.452689 33.356471, -111.452558 33.356586, -111.451999 33.357081, -111.451895 33.357177, -111.451765 33.3573, -111.451671 33.357388, -111.451622 33.357433, -111.451562 33.357491, -111.451493 33.357556, -111.45132 33.357405, -111.45122 33.35732, -111.451027 33.357152, -111.450868 33.357014, -111.450711 33.356869, -111.450297 33.356484, -111.450212 33.356423, -111.450142 33.356374, -111.449851 33.356192, -111.449159 33.355777, -111.448875 33.355615, -111.448099 33.355173, -111.447817 33.35501, -111.446983 33.354526, -111.445172 33.353468, -111.444113 33.35286, -111.443991 33.352797, -111.443701 33.352646, -111.44344 33.352559, -111.443201 33.352534, -111.442989 33.35253, -111.442517 33.35252, -111.442222 33.352521, -111.442074 33.352521, -111.441866 33.352521, -111.441649 33.352521, -111.441425 33.352451, -111.441216 33.352344, -111.441053 33.352261, -111.440446 33.351894, -111.440103 33.351698, -111.439868 33.351646, -111.439123 33.351609, -111.437517 33.351626, -111.435685 33.351627, -111.43499 33.351629, -111.43399 33.351627, -111.431531 33.351658, -111.429323 33.351655, -111.425762 33.351681, -111.425735 33.352964, -111.425731 33.353139, -111.425737 33.354287, -111.425734 33.354543, -111.425722 33.355413, -111.425731 33.355883, -111.426587 33.35588, -111.426905 33.355879, -111.427105 33.355871, -111.427252 33.355866, -111.427318 33.355863, -111.427535 33.355825, -111.427776 33.355728, -111.428408 33.355378, -111.429524 33.354792, -111.430071 33.354508, -111.430693 33.354215, -111.430786 33.35437, -111.431069 33.354718, -111.431725 33.355352, -111.432222 33.355786, -111.432778 33.356271, -111.432935 33.356409, -111.433281 33.356772, -111.43332 33.356828, -111.433475 33.357051, -111.433533 33.357217, -111.433562 33.357359, -111.433563 33.357445, -111.433562 33.358233, -111.433564 33.358621, -111.433564 33.358739, -111.433567 33.359005, -111.433571 33.359364, -111.43294 33.359357, -111.432443 33.359408, -111.432389 33.359413, -111.432238 33.35945, -111.431935 33.359526, -111.431717 33.359603, -111.431692 33.359611, -111.431505 33.359726, -111.43145 33.359769, -111.431318 33.359873, -111.431104 33.360047, -111.430842 33.360267, -111.430411 33.360622, -111.430303 33.360709, -111.430223 33.360754, -111.429989 33.36088, -111.42962 33.360985, -111.429061 33.361046, -111.428799 33.361074, -111.4276 33.361202, -111.427241 33.36124, -111.426883 33.361278, -111.426787 33.361295, -111.426349 33.361375, -111.426311 33.362347, -111.426309 33.362592, -111.426305 33.363045, -111.426309 33.363295, -111.426309 33.363335, -111.426319 33.363953, -111.426593 33.36394, -111.42681 33.363931, -111.42765 33.363895, -111.428034 33.363889, -111.429029 33.363849, -111.429413 33.363836, -111.429985 33.363821, -111.430296 33.363816, -111.430673 33.363782, -111.43084 33.363768, -111.430979 33.363768, -111.431224 33.36377, -111.431612 33.363768, -111.431754 33.36378, -111.431937 33.363794, -111.432113 33.363824, -111.432172 33.363833, -111.432573 33.3639, -111.433352 33.364064, -111.43318 33.364779, -111.433091 33.365152, -111.433071 33.365234, -111.432989 33.365575, -111.432898 33.365958, -111.432882 33.366025, -111.432676 33.366891, -111.432628 33.36717, -111.432614 33.367434, -111.432596 33.367862, -111.432576 33.368442, -111.432553 33.368874, -111.432508 33.369718, -111.432505 33.369795, -111.432491 33.370111, -111.432476 33.370444, -111.43247 33.370922, -111.432469 33.371015, -111.432463 33.371428, -111.432443 33.372197, -111.432416 33.373236, -111.432404 33.373686, -111.432397 33.373936, -111.432402 33.374008, -111.432411 33.374155, -111.432437 33.374298, -111.43249 33.37446, -111.432518 33.374502, -111.432559 33.374566, -111.432628 33.374657, -111.432701 33.374713, -111.432748 33.374751, -111.432855 33.374833, -111.433161 33.375024, -111.433399 33.375245, -111.43351 33.375407, -111.433555 33.375549, -111.433586 33.375822, -111.433609 33.375977, -111.433572 33.377014, -111.433453 33.37702, -111.433237 33.37702, -111.433068 33.377009, -111.432921 33.376994, -111.432772 33.376959, -111.432735 33.377048, -111.432801 33.377068, -111.433197 33.377133, -111.433332 33.377121, -111.43357 33.377085, -111.433546 33.378065, -111.433517 33.378338, -111.433446 33.378822, -111.43414 33.378808, -111.435041 33.378791, -111.435726 33.378785, -111.436128 33.37878, -111.436046 33.3789, -111.435905 33.378988, -111.43572 33.379035, -111.435563 33.37912, -111.434976 33.379399, -111.434683 33.379568, -111.43464 33.379629, -111.434626 33.379658, -111.434619 33.379713, -111.434634 33.379779, -111.434706 33.379821, -111.434814 33.379844, -111.434964 33.379832, -111.435201 33.379801, -111.435459 33.379793, -111.436047 33.379821, -111.436355 33.379797, -111.436519 33.379719, -111.436666 33.379689, -111.436846 33.379688, -111.437222 33.379694, -111.437653 33.379694, -111.43765 33.380435, -111.440694 33.380448, -111.44073 33.379996, -111.440743 33.379742, -111.44075 33.379713, -111.440866 33.379264, -111.440921 33.379047, -111.440907 33.378869, -111.440848 33.378783, -111.440885 33.378698, -111.440914 33.378649, -111.440949 33.378626, -111.440972 33.378615, -111.441021 33.378597, -111.441079 33.378607, -111.441167 33.378659, -111.441251 33.378675, -111.441417 33.378693, -111.441566 33.378717, -111.441686 33.378729, -111.441806 33.378722, -111.441871 33.378694, -111.441945 33.378656, -111.44199 33.378599, -111.442025 33.378424, -111.442057 33.378321, -111.442102 33.378225, -111.442114 33.378163, -111.443139 33.378148, -111.443173 33.378465, -111.443263 33.378778, -111.443276 33.378825, -111.443286 33.378854, -111.443396 33.37915, -111.443566 33.379418, -111.443994 33.379956, -111.445124 33.381245, -111.44519 33.381321, -111.445277 33.38142, -111.445447 33.38158, -111.445571 33.381661, -111.445691 33.38174, -111.4461 33.381943, -111.446399 33.382061, -111.446491 33.38208, -111.446401 33.382307, -111.446383 33.382466, -111.446363 33.382529, -111.446321 33.382584, -111.446138 33.382743, -111.445903 33.382926, -111.445865 33.383068, -111.445844 33.38315, -111.445816 33.383262, -111.445802 33.383366, -111.445816 33.383507, -111.445851 33.383687, -111.445858 33.383829, -111.445858 33.383929, -111.445834 33.384012, -111.445768 33.384182, -111.446162 33.384375, -111.446356 33.384528, -111.446521 33.38472, -111.446729 33.384936, -111.446937 33.385102, -111.447158 33.385233, -111.447554 33.385453, -111.447629 33.385506, -111.448033 33.385824, -111.448135 33.385961, -111.448212 33.386108, -111.448265 33.386285, -111.448361 33.386527, -111.448467 33.386699, -111.448572 33.386806, -111.448697 33.386899, -111.448891 33.386984, -111.449029 33.387033, -111.449193 33.387051, -111.449343 33.387053, -111.449474 33.387042, -111.449625 33.387011, -111.449746 33.386969, -111.449906 33.386876, -111.450762 33.386297, -111.450869 33.386256, -111.451 33.386226, -111.451134 33.386206, -111.451234 33.386203, -111.451351 33.38621, -111.451387 33.386212, -111.451571 33.386262, -111.451774 33.386341, -111.452213 33.386571, -111.452269 33.3866, -111.452881 33.386854, -111.45315 33.386992, -111.453075 33.387056, -111.453475 33.387282, -111.453804 33.387467, -111.453926 33.387535, -111.454491 33.38787, -111.454894 33.388115, -111.455057 33.388253, -111.455169 33.388379, -111.455278 33.38853, -111.455356 33.388711, -111.455568 33.389319, -111.455606 33.389395, -111.455658 33.389502, -111.455679 33.389545, -111.455789 33.38951, -111.455943 33.389443, -111.456091 33.389361, -111.456197 33.389308, -111.456162 33.389235, -111.455719 33.389367, -111.455688 33.389303, -111.455449 33.388636, -111.455386 33.388502, -111.455303 33.388373, -111.455221 33.388263, -111.455119 33.388162, -111.455007 33.388066, -111.454842 33.387954, -111.454507 33.387752, -111.453884 33.387388, -111.45362 33.387233, -111.453545 33.387194, -111.453769 33.386922, -111.453821 33.386858, -111.453863 33.386816, -111.453908 33.386783, -111.453959 33.386754, -111.454009 33.386733, -111.454071 33.386716, -111.454151 33.386706, -111.45423 33.386707, -111.454304 33.3867, -111.454409 33.386672, -111.454472 33.386646, -111.454564 33.386591, -111.454635 33.386521, -111.45496 33.386117, -111.455082 33.385967, -111.455182 33.385843, -111.455208 33.385813, -111.455237 33.385786, -111.455269 33.385762, -111.45532 33.385732, -111.455368 33.385708, -111.455411 33.385692, -111.455465 33.385682, -111.455496 33.385678, -111.455568 33.385676, -111.456048 33.385684, -111.456173 33.385681, -111.456287 33.38568, -111.456347 33.385671, -111.456407 33.385658, -111.456465 33.38564, -111.456621 33.385574, -111.456714 33.385542, -111.456814 33.385523, -111.456922 33.385509, -111.457028 33.385496, -111.457251 33.385469, -111.457385 33.385465, -111.45753 33.385484, -111.457584 33.385487, -111.457644 33.385482, -111.45772 33.385471, -111.457857 33.385435, -111.457925 33.3854, -111.457984 33.38536, -111.458053 33.385296, -111.458107 33.385222, -111.458144 33.385142, -111.45816 33.385074, -111.458161 33.385006, -111.458165 33.384938, -111.458172 33.38487, -111.458203 33.384779, -111.458243 33.384716, -111.45829 33.384665, -111.458437 33.384561, -111.458614 33.384653, -111.458631 33.384707, -111.458673 33.384763, -111.458787 33.384824, -111.458956 33.384926, -111.459063 33.384967, -111.459177 33.384968, -111.459309 33.384966, -111.459429 33.384932, -111.459517 33.384894, -111.459542 33.384847, -111.459539 33.384788, -111.459516 33.384723, -111.459463 33.384639, -111.459404 33.384528, -111.459495 33.384484, -111.459624 33.384438, -111.459604 33.384365, -111.459598 33.384267, -111.459619 33.384213, -111.45965 33.384162, -111.459729 33.384073, -111.459793 33.384024, -111.459879 33.383977, -111.459936 33.383958, -111.460053 33.383938, -111.460172 33.383945, -111.460293 33.383973, -111.460422 33.383981, -111.46055 33.38396, -111.460663 33.383918, -111.460774 33.38387, -111.460883 33.383818, -111.460989 33.38376, -111.461192 33.38363, -111.46129 33.383559, -111.461704 33.383251, -111.461918 33.383126, -111.462141 33.383021, -111.462522 33.382869, -111.462714 33.382771, -111.462897 33.382655, -111.463101 33.382491, -111.463371 33.382261, -111.463464 33.382182, -111.463616 33.382094, -111.463694 33.38206, -111.46386 33.382009, -111.464032 33.381983, -111.464206 33.381984, -111.464378 33.382011, -111.464462 33.382034, -111.464544 33.382064, -111.464825 33.382177, -111.46486 33.382111, -111.464993 33.382174, -111.465032 33.382109, -111.465159 33.381964, -111.465324 33.381819, -111.465479 33.381725, -111.465637 33.381653, -111.465849 33.381571, -111.466109 33.381515, -111.466635 33.381425, -111.467398 33.381289, -111.467347 33.381146, -111.467443 33.38113, -111.467496 33.381121, -111.468899 33.380879, -111.469226 33.380812, -111.469449 33.38075, -111.469661 33.380643, -111.469857 33.380521, -111.469997 33.3804, -111.470134 33.380237, -111.470248 33.380046, -111.470309 33.379879, -111.470341 33.379669, -111.470335 33.379435, -111.470405 33.379429, -111.47047 33.379427, -111.470447 33.379254, -111.470378 33.379028, -111.470353 33.378967, -111.470108 33.378385, -111.469982 33.378086, -111.469883 33.377842, -111.470031 33.377803, -111.470136 33.377761, -111.47019 33.37771, -111.470472 33.377629, -111.470662 33.377573, -111.470891 33.377536, -111.471035 33.377533, -111.471103 33.37753, -111.471299 33.377542, -111.471539 33.377589, -111.471807 33.377685, -111.47197 33.377786, -111.472057 33.37784, -111.472233 33.378, -111.472385 33.378199, -111.472484 33.37839, -111.47327 33.378372, -111.473907 33.378377, -111.473958 33.378355, -111.473992 33.378304, -111.474008 33.378072, -111.474065 33.377833, -111.474149 33.377623, -111.474295 33.377349, -111.474376 33.377116, -111.47443 33.376877, -111.474439 33.376711, -111.474417 33.376385, -111.474347 33.376112, -111.47428 33.375909, -111.474263 33.375802, -111.474295 33.375432, -111.474332 33.375231, -111.47431 33.37502, -111.474287 33.374927, -111.47402 33.374339, -111.474008 33.374312, -111.473554 33.374464, -111.47332 33.374524, -111.473115 33.374556, -111.472572 33.374611, -111.472013 33.374651, -111.471864 33.374682, -111.471727 33.374746, -111.471593 33.374845, -111.471509 33.374947, -111.471445 33.375025, -111.471219 33.375278, -111.471075 33.375403, -111.470934 33.375514, -111.470839 33.375608, -111.470578 33.376018, -111.470414 33.376185, -111.470266 33.376286, -111.470049 33.37639, -111.469714 33.376497, -111.469585 33.376495, -111.46948 33.376514, -111.469358 33.376545, -111.46926 33.376288, -111.468932 33.375504, -111.468879 33.375307, -111.468856 33.375222, -111.468855 33.375138, -111.468852 33.374966, -111.468859 33.374243, -111.468952 33.37425, -111.469183 33.374252, -111.469332 33.374229, -111.469481 33.374184, -111.469681 33.374072, -111.469709 33.374048, -111.469915 33.373865, -111.470219 33.373651, -111.470438 33.373513, -111.470643 33.373427, -111.470847 33.373354, -111.471082 33.373313, -111.471305 33.373296)), ((-111.828898 33.710544, -111.828744 33.710476, -111.828688 33.710448, -111.828197 33.710201, -111.828008 33.710103, -111.827736 33.709963, -111.827647 33.709922, -111.827499 33.709878, -111.827371 33.709853, -111.827262 33.709844, -111.827081 33.709853, -111.826954 33.709871, -111.826853 33.709897, -111.826742 33.709938, -111.826496 33.710047, -111.826395 33.710098, -111.826231 33.7102, -111.825986 33.710396, -111.825832 33.71052, -111.825762 33.710595, -111.825696 33.710693, -111.825584 33.710851, -111.825505 33.710983, -111.825443 33.71111, -111.825402 33.711212, -111.825326 33.711428, -111.825322 33.711449, -111.825256 33.711999, -111.825227 33.712336, -111.825224 33.712427, -111.825203 33.712472, -111.825217 33.712888, -111.825226 33.713164, -111.825107 33.713568, -111.825621 33.713635, -111.825809 33.713709, -111.825994 33.71387, -111.826085 33.713949, -111.826292 33.714073, -111.826673 33.71419, -111.827539 33.712531, -111.82756 33.712456, -111.827595 33.71233, -111.827634 33.712213, -111.827664 33.712124, -111.827715 33.712034, -111.827788 33.711927, -111.827893 33.711801, -111.827938 33.711758, -111.82801 33.711687, -111.828196 33.711478, -111.828373 33.711265, -111.828541 33.711048, -111.828898 33.710544)), ((-112.130589 33.814859, -112.130594 33.815014, -112.1306 33.815205, -112.130604 33.81532, -112.130603 33.815356, -112.1314 33.815359, -112.132796 33.815292, -112.132843 33.815593, -112.133251 33.815578, -112.133249 33.814889, -112.133248 33.814847, -112.133131 33.814847, -112.131063 33.814842, -112.130589 33.814859)), ((-111.643568 33.277836, -111.643563 33.277712, -111.640731 33.277641, -111.634869 33.277617, -111.634871 33.277755, -111.641363 33.277815, -111.643543 33.277836, -111.643568 33.277836)), ((-111.572975 33.292819, -111.576741 33.303516, -111.577583 33.305906, -111.577919 33.306803, -111.579272 33.306812, -111.57932 33.306813, -111.57996 33.306896, -111.58055 33.307026, -111.580681 33.307042, -111.58075 33.307112, -111.580789 33.307147, -111.58085 33.307168, -111.580924 33.307177, -111.581193 33.307177, -111.582221 33.30719, -111.583093 33.30722, -111.5834 33.307182, -111.583392 33.306297, -111.583382 33.30498, -111.583369 33.303891, -111.583344 33.301262, -111.58334 33.300852, -111.583164 33.300231, -111.580655 33.300179, -111.580658 33.299292, -111.580678 33.2961, -111.58063 33.293888, -111.580628 33.292547, -111.572975 33.292819)), ((-111.928123 33.849924, -111.92775 33.849943, -111.927326 33.849948, -111.927026 33.849945, -111.926575 33.849932, -111.926011 33.849921, -111.925994 33.850797, -111.926002 33.851059, -111.925991 33.851539, -111.926006 33.851841, -111.925994 33.852533, -111.926007 33.852884, -111.925983 33.853401, -111.925999 33.853454, -111.926026 33.853495, -111.92607 33.853531, -111.926095 33.853545, -111.926127 33.853545, -111.926214 33.853526, -111.926307 33.853519, -111.926539 33.853527, -111.927081 33.853557, -111.927092 33.853256, -111.927091 33.852984, -111.927077 33.852464, -111.927074 33.852339, -111.927068 33.85192, -111.927071 33.851895, -111.927086 33.85186, -111.927113 33.851828, -111.927128 33.851816, -111.927178 33.85179, -111.927235 33.851778, -111.927462 33.851786, -111.92764 33.851798, -111.927903 33.851831, -111.927944 33.851832, -111.928002 33.851819, -111.92815 33.851744, -111.928183 33.851714, -111.928202 33.851677, -111.928205 33.851629, -111.928191 33.851547, -111.928181 33.85147, -111.928188 33.851233, -111.928216 33.851034, -111.928216 33.850972, -111.92819 33.850885, -111.928182 33.850847, -111.928175 33.850811, -111.928175 33.850739, -111.928205 33.850496, -111.928204 33.850437, -111.928166 33.850278, -111.928151 33.850164, -111.928152 33.85003, -111.928148 33.849998, -111.928123 33.849924)), ((-112.409656 33.686299, -112.409229 33.686665, -112.409138 33.686717, -112.409138 33.687296, -112.409128 33.694925, -112.408732 33.694932, -112.407007 33.694913, -112.406216 33.694912, -112.405424 33.694924, -112.403869 33.694904, -112.403094 33.694904, -112.402348 33.69492, -112.400722 33.694945, -112.399965 33.694945, -112.399175 33.694957, -112.399133 33.694973, -112.399106 33.695002, -112.399097 33.695036, -112.399125 33.695662, -112.399136 33.696762, -112.400316 33.696757, -112.401801 33.696728, -112.403292 33.696722, -112.406395 33.696744, -112.407369 33.696739, -112.408264 33.696753, -112.409123 33.69674, -112.409309 33.696737, -112.409317 33.69565, -112.409323 33.695323, -112.409323 33.694922, -112.409326 33.693669, -112.409338 33.693339, -112.409359 33.692351, -112.409388 33.691698, -112.409425 33.691375, -112.409487 33.691061, -112.409535 33.690868, -112.409436 33.690865, -112.409406 33.690861, -112.409371 33.690846, -112.409339 33.690818, -112.40932 33.690781, -112.40932 33.690046, -112.409326 33.689698, -112.409321 33.689005, -112.409324 33.688657, -112.409319 33.68831, -112.409332 33.68805, -112.409362 33.687824, -112.409405 33.68764, -112.409458 33.687485, -112.409508 33.687367, -112.409621 33.687132, -112.409681 33.687031, -112.409861 33.68678, -112.409964 33.686664, -112.409984 33.686637, -112.409992 33.686615, -112.409994 33.686586, -112.409984 33.686546, -112.409904 33.686473, -112.409768 33.686381, -112.409656 33.686299)), ((-111.501758 33.192177, -111.496628 33.192211, -111.49666 33.192445, -111.496745 33.192856, -111.496836 33.193143, -111.496469 33.193229, -111.49581 33.193455, -111.495014 33.19368, -111.494149 33.193869, -111.493772 33.193911, -111.493676 33.193922, -111.493164 33.193942, -111.492857 33.193931, -111.492865 33.194291, -111.492878 33.195416, -111.492886 33.196108, -111.492911 33.196863, -111.492956 33.198242, -111.492977 33.198346, -111.493009 33.19839, -111.493062 33.198416, -111.493132 33.198422, -111.493871 33.198433, -111.495776 33.198388, -111.496484 33.198408, -111.496722 33.198354, -111.496845 33.198332, -111.497079 33.198332, -111.497265 33.198372, -111.497388 33.198393, -111.497623 33.198392, -111.497941 33.198379, -111.498021 33.198379, -111.499098 33.198386, -111.500015 33.198397, -111.501872 33.198358, -111.501861 33.197488, -111.501853 33.196894, -111.502023 33.196892, -111.502286 33.196894, -111.502826 33.196886, -111.503044 33.196883, -111.50323 33.196886, -111.503929 33.196899, -111.504809 33.196883, -111.50481 33.197663, -111.504832 33.19833, -111.505385 33.198343, -111.507753 33.198336, -111.508385 33.197976, -111.508426 33.198107, -111.50847 33.198249, -111.508482 33.198289, -111.508989 33.198302, -111.509362 33.198302, -111.509621 33.198306, -111.509816 33.198302, -111.50999 33.198277, -111.510125 33.198234, -111.51029 33.198148, -111.510812 33.198465, -111.510943 33.198544, -111.510943 33.198587, -111.510945 33.199416, -111.510967 33.199921, -111.511014 33.201016, -111.511023 33.201393, -111.511047 33.202347, -111.511074 33.204298, -111.511109 33.205359, -111.511024 33.205646, -111.510924 33.205879, -111.510914 33.205985, -111.510987 33.206462, -111.511213 33.207472, -111.511384 33.208475, -111.51139 33.208547, -111.511437 33.209094, -111.511432 33.209621, -111.511392 33.210161, -111.511425 33.212769, -111.511431 33.213258, -111.511422 33.213749, -111.511403 33.215198, -111.511398 33.215549, -111.511397 33.217048, -111.511398 33.21732, -111.511384 33.218621, -111.511471 33.220139, -111.511308 33.220599, -111.511308 33.224252, -111.511308 33.224727, -111.511384 33.22488, -111.511639 33.225183, -111.511678 33.23096, -111.511735 33.230963, -111.513105 33.231197, -111.513612 33.231206, -111.528737 33.231172, -111.52872 33.228811, -111.528719 33.227803, -111.528719 33.227711, -111.528718 33.227293, -111.528733 33.225104, -111.528742 33.223996, -111.528746 33.222212, -111.528741 33.22009, -111.527898 33.220095, -111.526064 33.220126, -111.520577 33.220137, -111.518972 33.220115, -111.516569 33.220125, -111.515822 33.220118, -111.515823 33.219213, -111.51582 33.217712, -111.515823 33.217388, -111.515837 33.215592, -111.51584 33.215054, -111.515827 33.21441, -111.515817 33.213928, -111.515807 33.213786, -111.515804 33.213744, -111.515789 33.213524, -111.515821 33.212815, -111.51702 33.212791, -111.518283 33.212781, -111.518777 33.212775, -111.519075 33.212775, -111.52011 33.212775, -111.520701 33.212763, -111.521118 33.212753, -111.52186 33.212769, -111.522071 33.212792, -111.52211 33.212794, -111.522403 33.212801, -111.522808 33.212809, -111.523076 33.212806, -111.523537 33.212798, -111.524081 33.21279, -111.524416 33.212784, -111.525968 33.212794, -111.528085 33.212806, -111.528225 33.212794, -111.528271 33.212778, -111.528311 33.212764, -111.528412 33.212721, -111.528456 33.212688, -111.528495 33.212668, -111.528545 33.212648, -111.528602 33.212643, -111.52867 33.212642, -111.528741 33.212646, -111.528747 33.211261, -111.528737 33.209587, -111.528731 33.20734, -111.528743 33.205542, -111.528464 33.205554, -111.528037 33.205576, -111.527413 33.205588, -111.527423 33.207355, -111.527423 33.209553, -111.526304 33.209553, -111.525531 33.209556, -111.525357 33.209556, -111.525331 33.209353, -111.52533 33.20919, -111.525292 33.209111, -111.525202 33.209063, -111.525087 33.209055, -111.52499 33.209074, -111.524951 33.209108, -111.524944 33.209358, -111.524951 33.209559, -111.524855 33.209559, -111.524652 33.209572, -111.524463 33.209601, -111.524425 33.209601, -111.523133 33.209595, -111.522779 33.209595, -111.522111 33.209597, -111.52114 33.209598, -111.520087 33.209605, -111.520089 33.20557, -111.520106 33.204544, -111.520109 33.203364, -111.520099 33.202798, -111.52009 33.202285, -111.520092 33.200953, -111.520089 33.200521, -111.520077 33.199143, -111.520045 33.198116, -111.52099 33.198093, -111.522189 33.198072, -111.52404 33.198045, -111.524438 33.19804, -111.526323 33.198031, -111.528792 33.198, -111.528723 33.195892, -111.528664 33.194207, -111.528606 33.192684, -111.528578 33.191737, -111.528562 33.191589, -111.528568 33.191509, -111.528553 33.191316, -111.528525 33.191189, -111.528489 33.191128, -111.528463 33.191099, -111.528397 33.191025, -111.528338 33.190913, -111.528344 33.190858, -111.528379 33.190804, -111.528437 33.190741, -111.528542 33.190643, -111.528599 33.190517, -111.528607 33.190075, -111.528503 33.187526, -111.528354 33.183695, -111.52812 33.17769, -111.528049 33.176334, -111.528038 33.176105, -111.528013 33.175624, -111.527896 33.17337, -111.527837 33.171795, -111.527563 33.166044, -111.527514 33.164637, -111.527381 33.162098, -111.52737 33.161893, -111.512589 33.161938, -111.511228 33.161938, -111.509797 33.161935, -111.510315 33.1755, -111.510332 33.175947, -111.510334 33.175998, -111.510344 33.176409, -111.510347 33.176491, -111.510349 33.176524, -111.510389 33.177061, -111.510462 33.179176, -111.510748 33.187511, -111.510788 33.18902, -111.510837 33.190579, -111.510881 33.191041, -111.510597 33.191041, -111.50959 33.191041, -111.507695 33.191043, -111.506017 33.191035, -111.50173 33.191024, -111.501758 33.192177)), ((-111.563382 33.440813, -111.563381 33.442689, -111.563381 33.443635, -111.563379 33.449607, -111.563427 33.451003, -111.561222 33.451011, -111.561222 33.451096, -111.561255 33.451341, -111.560467 33.451348, -111.55914 33.451345, -111.557565 33.451345, -111.556866 33.451345, -111.556374 33.451347, -111.556069 33.451348, -111.555459 33.451349, -111.554734 33.451352, -111.554389 33.451355, -111.553601 33.451352, -111.553005 33.451349, -111.552564 33.451348, -111.550872 33.451347, -111.550619 33.451346, -111.550421 33.451346, -111.549825 33.45134, -111.549798 33.451528, -111.54976 33.45167, -111.549729 33.451718, -111.549605 33.451735, -111.549417 33.451725, -111.549385 33.451684, -111.549373 33.451583, -111.54939 33.451335, -111.548801 33.45133, -111.548365 33.451324, -111.547171 33.451324, -111.547092 33.451514, -111.54697 33.451701, -111.546835 33.451849, -111.546748 33.451901, -111.546632 33.451926, -111.546647 33.452021, -111.546636 33.452083, -111.546597 33.452126, -111.546543 33.452145, -111.546427 33.452139, -111.546244 33.452078, -111.54613 33.45203, -111.546125 33.452599, -111.546121 33.45309, -111.54627 33.453113, -111.546764 33.453136, -111.546792 33.453307, -111.546798 33.453424, -111.546785 33.453485, -111.546756 33.453541, -111.546725 33.453572, -111.546615 33.453575, -111.546116 33.453566, -111.546115 33.45405, -111.546112 33.4549, -111.544847 33.454904, -111.544009 33.454906, -111.543658 33.454907, -111.543456 33.455191, -111.543224 33.455483, -111.543177 33.455505, -111.543125 33.455499, -111.543088 33.455413, -111.543069 33.455196, -111.543093 33.454908, -111.541761 33.45491, -111.541771 33.453212, -111.541768 33.452137, -111.541779 33.451337, -111.541169 33.451335, -111.539649 33.451327, -111.539401 33.451326, -111.537474 33.451337, -111.537474 33.45236, -111.537477 33.453108, -111.537477 33.453189, -111.537232 33.453187, -111.536811 33.453223, -111.536417 33.453218, -111.53555 33.453193, -111.535431 33.453183, -111.535361 33.45317, -111.535297 33.453161, -111.534987 33.453162, -111.53444 33.453155, -111.534466 33.453049, -111.534468 33.452944, -111.534423 33.452835, -111.53434 33.452665, -111.53418 33.452439, -111.534098 33.452356, -111.534014 33.452323, -111.533893 33.452294, -111.533304 33.452256, -111.533169 33.452252, -111.533177 33.451341, -111.532144 33.451343, -111.531112 33.451346, -111.528868 33.451351, -111.528853 33.452291, -111.528853 33.452322, -111.528846 33.453187, -111.528841 33.453984, -111.528853 33.454976, -111.528854 33.45501, -111.528853 33.45592, -111.528851 33.456733, -111.52885 33.456798, -111.528846 33.45825, -111.528804 33.458568, -111.528789 33.458799, -111.528791 33.459487, -111.529038 33.459466, -111.529195 33.459439, -111.529411 33.459423, -111.529652 33.459454, -111.529834 33.459465, -111.529925 33.459449, -111.529987 33.459409, -111.530066 33.459198, -111.530086 33.459099, -111.530114 33.459037, -111.530166 33.458975, -111.530236 33.458958, -111.53033 33.458954, -111.530439 33.459001, -111.530512 33.459035, -111.530761 33.459218, -111.530948 33.459344, -111.53094 33.459396, -111.530938 33.459715, -111.530926 33.459919, -111.530934 33.460116, -111.530966 33.460332, -111.530995 33.460445, -111.530198 33.460447, -111.528793 33.46045, -111.528795 33.461042, -111.528793 33.462145, -111.528796 33.462265, -111.528734 33.462334, -111.528727 33.462742, -111.528729 33.463141, -111.528729 33.463455, -111.52872 33.464061, -111.528741 33.464767, -111.528792 33.465114, -111.528811 33.465438, -111.528824 33.465635, -111.528838 33.465687, -111.528833 33.465712, -111.528905 33.465872, -111.529857 33.465871, -111.530808 33.465871, -111.530944 33.465871, -111.531154 33.46585, -111.531713 33.465791, -111.532386 33.465791, -111.53306 33.465791, -111.533074 33.465654, -111.53484 33.46546, -111.53573 33.465495, -111.536149 33.465511, -111.536163 33.465432, -111.536187 33.465291, -111.536162 33.46519, -111.53611 33.465094, -111.535996 33.465023, -111.535872 33.464984, -111.535779 33.464967, -111.5357 33.464964, -111.535318 33.464948, -111.534937 33.464988, -111.534713 33.464986, -111.534596 33.464975, -111.53461 33.46492, -111.534637 33.464871, -111.534702 33.464798, -111.534804 33.46474, -111.535286 33.464492, -111.535279 33.464357, -111.535267 33.4643, -111.535237 33.464053, -111.535285 33.464053, -111.53545 33.46405, -111.53668 33.464073, -111.537438 33.464073, -111.537432 33.464134, -111.537438 33.464457, -111.53749 33.464827, -111.5375 33.464962, -111.537502 33.46499, -111.537508 33.465072, -111.537496 33.465246, -111.537431 33.465563, -111.537453 33.465733, -111.537516 33.465868, -111.538215 33.465867, -111.538305 33.465799, -111.538328 33.465723, -111.538259 33.465408, -111.538392 33.465341, -111.538466 33.465299, -111.538535 33.465214, -111.538533 33.465155, -111.538517 33.465107, -111.539597 33.464655, -111.539568 33.464446, -111.539564 33.464281, -111.539556 33.464166, -111.539573 33.4641, -111.53961 33.464048, -111.539571 33.463815, -111.539539 33.463482, -111.539493 33.46342, -111.539457 33.463341, -111.539453 33.46325, -111.539488 33.463177, -111.539563 33.463102, -111.539572 33.463065, -111.539575 33.462884, -111.539594 33.46277, -111.539598 33.462728, -111.539594 33.462686, -111.539583 33.462652, -111.53956 33.462599, -111.539561 33.462289, -111.539661 33.46228, -111.540012 33.462254, -111.540225 33.462244, -111.540311 33.462215, -111.540386 33.462176, -111.540496 33.462151, -111.540671 33.46215, -111.540819 33.462158, -111.540971 33.462183, -111.541263 33.462251, -111.541398 33.462271, -111.541528 33.462266, -111.541662 33.462249, -111.541765 33.462214, -111.541771 33.462722, -111.541774 33.462964, -111.541776 33.463171, -111.541967 33.463144, -111.542287 33.463146, -111.542431 33.463171, -111.542538 33.463237, -111.542594 33.463375, -111.542595 33.463485, -111.54251 33.463644, -111.542442 33.463794, -111.542271 33.463901, -111.542103 33.463988, -111.542 33.464022, -111.541859 33.464035, -111.541786 33.464019, -111.542025 33.465594, -111.543989 33.465589, -111.546074 33.465584, -111.546112 33.465577, -111.54764 33.465294, -111.547687 33.465173, -111.547745 33.465066, -111.547796 33.464998, -111.547872 33.464969, -111.548033 33.464962, -111.548281 33.464958, -111.548281 33.464892, -111.548279 33.464641, -111.548302 33.464136, -111.548308 33.464005, -111.548837 33.464036, -111.550791 33.464149, -111.552688 33.463952, -111.552784 33.464008, -111.55286 33.464028, -111.552977 33.464035, -111.553229 33.464036, -111.553521 33.464069, -111.553849 33.464042, -111.554017 33.464042, -111.554342 33.464064, -111.554455 33.464059, -111.554667 33.464039, -111.554814 33.464044, -111.554914 33.464048, -111.555611 33.464101, -111.555813 33.464105, -111.556919 33.464126, -111.55805 33.464046, -111.557977 33.464095, -111.557941 33.464149, -111.557927 33.464224, -111.557938 33.464278, -111.557983 33.464391, -111.557994 33.464477, -111.557961 33.464967, -111.557951 33.465242, -111.557883 33.465322, -111.557775 33.46566, -111.558761 33.465651, -111.559118 33.465653, -111.560286 33.46566, -111.561438 33.465669, -111.562765 33.465814, -111.563324 33.465798, -111.563553 33.465837, -111.567665 33.465837, -111.568286 33.46584, -111.568907 33.465843, -111.569471 33.465846, -111.57362 33.465867, -111.578352 33.46589, -111.57859 33.465891, -111.578846 33.465893, -111.580626 33.465902, -111.580634 33.4658, -111.580633 33.465005, -111.580633 33.46485, -111.580036 33.464867, -111.579552 33.464881, -111.579244 33.464868, -111.579146 33.46476, -111.579033 33.464655, -111.578962 33.464562, -111.578949 33.464376, -111.578952 33.464239, -111.578969 33.464188, -111.57911 33.46412, -111.579259 33.46406, -111.57946 33.464036, -111.579783 33.464061, -111.579848 33.46407, -111.579988 33.464094, -111.580185 33.464093, -111.580433 33.464054, -111.580633 33.464052, -111.580633 33.463994, -111.580632 33.462339, -111.580632 33.462275, -111.580632 33.462221, -111.582778 33.462226, -111.582791 33.461255, -111.582907 33.461257, -111.583773 33.461197, -111.583971 33.461206, -111.584409 33.461289, -111.584459 33.461297, -111.584948 33.461374, -111.585164 33.461379, -111.585735 33.461301, -111.586161 33.46129, -111.586155 33.463583, -111.587056 33.463582, -111.587085 33.463464, -111.587093 33.463406, -111.587091 33.463306, -111.587073 33.46322, -111.587065 33.46311, -111.587084 33.462496, -111.587101 33.462228, -111.587099 33.462201, -111.587101 33.462086, -111.587096 33.461841, -111.587106 33.461677, -111.587095 33.461402, -111.58709 33.4613, -111.587076 33.460974, -111.587094 33.460586, -111.587115 33.460352, -111.587113 33.460323, -111.587121 33.460214, -111.587123 33.460135, -111.587109 33.459967, -111.587118 33.45948, -111.587102 33.459201, -111.587093 33.458928, -111.587091 33.458875, -111.587097 33.458829, -111.587103 33.458745, -111.587097 33.458719, -111.587076 33.458683, -111.587039 33.458652, -111.587002 33.458633, -111.586941 33.458616, -111.586851 33.458613, -111.586468 33.458627, -111.586262 33.45862, -111.585503 33.458615, -111.585248 33.458624, -111.585123 33.458626, -111.584977 33.458646, -111.584892 33.458653, -111.584809 33.458651, -111.584658 33.45863, -111.584467 33.458621, -111.584353 33.458607, -111.584298 33.458589, -111.584205 33.458534, -111.584145 33.458513, -111.584065 33.458498, -111.583983 33.458492, -111.583718 33.458503, -111.58383 33.458694, -111.583841 33.458745, -111.583848 33.458813, -111.583853 33.458942, -111.58385 33.459328, -111.583855 33.459519, -111.583847 33.45988, -111.583832 33.459913, -111.583752 33.459999, -111.583679 33.460059, -111.58363 33.460085, -111.58348 33.46013, -111.583409 33.460144, -111.5832 33.460168, -111.583084 33.460169, -111.58306 33.460161, -111.583044 33.460147, -111.582975 33.460073, -111.582912 33.460019, -111.582871 33.460003, -111.582713 33.459987, -111.582625 33.459989, -111.582545 33.460005, -111.582509 33.460017, -111.582325 33.460116, -111.582191 33.460175, -111.58213 33.460216, -111.582079 33.460265, -111.58203 33.460326, -111.581957 33.460391, -111.581913 33.460416, -111.581888 33.46042, -111.581688 33.460431, -111.580631 33.460416, -111.580466 33.460447, -111.580266 33.460475, -111.580054 33.460461, -111.579652 33.460419, -111.579221 33.460391, -111.579113 33.460368, -111.579091 33.46034, -111.578947 33.460335, -111.578816 33.460341, -111.578497 33.460379, -111.578497 33.460296, -111.578495 33.459964, -111.578492 33.459085, -111.578496 33.458606, -111.578604 33.458605, -111.578928 33.45859, -111.57928 33.458573, -111.579572 33.45857, -111.579637 33.45857, -111.579998 33.458585, -111.580633 33.458619, -111.580638 33.457684, -111.58064 33.457438, -111.580639 33.457183, -111.580637 33.456736, -111.580636 33.456291, -111.580634 33.454915, -111.580634 33.454701, -111.580635 33.454529, -111.580629 33.454495, -111.580636 33.45382, -111.580637 33.453151, -111.580638 33.451915, -111.580638 33.451334, -111.580221 33.451345, -111.579984 33.451332, -111.579503 33.451333, -111.578566 33.451334, -111.576949 33.451337, -111.576547 33.451338, -111.575702 33.451338, -111.575615 33.451339, -111.575646 33.451147, -111.575742 33.450939, -111.575954 33.450554, -111.576258 33.450203, -111.576601 33.449962, -111.576998 33.449735, -111.577318 33.449369, -111.577651 33.448834, -111.577689 33.448682, -111.577668 33.44845, -111.577673 33.448274, -111.577623 33.447946, -111.577655 33.447815, -111.577871 33.447498, -111.578066 33.447233, -111.578471 33.446945, -111.578998 33.445931, -111.579124 33.445776, -111.579178 33.445777, -111.579224 33.445805, -111.579281 33.445856, -111.579351 33.445867, -111.579454 33.445861, -111.579583 33.445819, -111.57978 33.445691, -111.580003 33.445476, -111.580213 33.445317, -111.580359 33.445239, -111.580489 33.445198, -111.580636 33.445219, -111.580636 33.445059, -111.580636 33.445026, -111.579352 33.444891, -111.575798 33.444466, -111.575371 33.444422, -111.574228 33.444305, -111.572191 33.444065, -111.571766 33.444018, -111.569944 33.443821, -111.568992 33.443705, -111.568531 33.443629, -111.568191 33.443551, -111.567736 33.443403, -111.567274 33.443238, -111.566786 33.44303, -111.5666 33.442932, -111.566404 33.442828, -111.565721 33.442406, -111.565433 33.442211, -111.564893 33.441847, -111.563382 33.440813)), ((-112.126189 33.834946, -112.126196 33.833525, -112.126184 33.833093, -112.12619 33.832818, -112.126179 33.83228, -112.126168 33.831788, -112.12618 33.831345, -112.126186 33.83093, -112.126176 33.830489, -112.126175 33.830438, -112.126171 33.830344, -112.126172 33.830137, -112.126165 33.829636, -112.126175 33.829522, -112.126194 33.829293, -112.126201 33.82912, -112.12619 33.828875, -112.126189 33.828625, -112.126178 33.828438, -112.126179 33.828294, -112.126185 33.828242, -112.126208 33.82806, -112.126219 33.827879, -112.12622 33.827734, -112.126039 33.827713, -112.126023 33.827711, -112.125909 33.827705, -112.1254 33.827699, -112.125312 33.827695, -112.125181 33.82769, -112.124586 33.827687, -112.124101 33.827685, -112.122998 33.82769, -112.121964 33.827699, -112.121892 33.827694, -112.121898 33.827465, -112.12191 33.827022, -112.121899 33.826868, -112.121899 33.826304, -112.121891 33.825874, -112.1219 33.825806, -112.121934 33.825788, -112.121954 33.825781, -112.12203 33.825792, -112.122284 33.825805, -112.122635 33.825812, -112.123156 33.825799, -112.123251 33.825781, -112.1233 33.825754, -112.123315 33.825741, -112.123332 33.825719, -112.123339 33.82571, -112.123352 33.825672, -112.12336 33.825519, -112.123358 33.824794, -112.123345 33.824646, -112.123322 33.82458, -112.12328 33.824499, -112.123237 33.824438, -112.123168 33.824369, -112.122972 33.824238, -112.12288 33.824185, -112.122839 33.824168, -112.122766 33.824138, -112.122697 33.824123, -112.12259 33.824117, -112.122213 33.824092, -112.12171 33.824065, -112.121336 33.824043, -112.1213 33.824041, -112.121 33.824031, -112.120867 33.824031, -112.120528 33.824032, -112.119815 33.82404, -112.117601 33.824045, -112.117492 33.824049, -112.117493 33.823984, -112.117508 33.821063, -112.117511 33.820449, -112.117496 33.820604, -112.117484 33.820716, -112.117443 33.820848, -112.11739 33.820973, -112.117331 33.821071, -112.117202 33.821253, -112.11698 33.821554, -112.116921 33.821647, -112.116801 33.821833, -112.11665 33.822111, -112.116545 33.822353, -112.116486 33.822519, -112.116446 33.822634, -112.116412 33.822784, -112.116399 33.822895, -112.116392 33.823118, -112.116406 33.823251, -112.116439 33.823384, -112.116485 33.823518, -112.116577 33.823719, -112.116663 33.823873, -112.116696 33.823926, -112.116742 33.824001, -112.116806 33.824104, -112.116949 33.824368, -112.117147 33.824702, -112.117228 33.824872, -112.117345 33.825157, -112.117401 33.825324, -112.117455 33.825545, -112.117483 33.825706, -112.117498 33.825886, -112.117503 33.826511, -112.117501 33.827681, -112.117315 33.827685, -112.115969 33.827681, -112.114857 33.827668, -112.114069 33.827674, -112.113522 33.827673, -112.113155 33.827681, -112.112725 33.827675, -112.112393 33.827671, -112.111742 33.827667, -112.110846 33.827687, -112.110727 33.82768, -112.110511 33.827675, -112.110077 33.827694, -112.109448 33.827684, -112.109155 33.827671, -112.109073 33.827678, -112.108722 33.827675, -112.108714 33.827653, -112.108638 33.827652, -112.107657 33.8276, -112.107086 33.827569, -112.106859 33.82753, -112.106158 33.827526, -112.105875 33.827648, -112.105303 33.828252, -112.104633 33.829089, -112.104486 33.829174, -112.104485 33.829132, -112.104488 33.828433, -112.104511 33.828045, -112.104515 33.827909, -112.104513 33.82784, -112.104508 33.827804, -112.104485 33.827745, -112.104452 33.827701, -112.10439 33.827654, -112.104125 33.827653, -112.103856 33.827652, -112.103162 33.827665, -112.1019 33.827675, -112.100525 33.82767, -112.100154 33.82768, -112.100145 33.827472, -112.10015 33.82742, -112.100146 33.826018, -112.100147 33.825901, -112.100154 33.824928, -112.100149 33.824763, -112.100155 33.824256, -112.100148 33.823021, -112.100138 33.822794, -112.10012 33.822765, -112.100069 33.821251, -112.100062 33.820862, -112.100086 33.820839, -112.10011 33.820802, -112.100122 33.820758, -112.100117 33.82059, -112.100118 33.820455, -112.100133 33.820363, -112.100144 33.820205, -112.10014 33.819873, -112.100155 33.819572, -112.100163 33.819423, -112.100153 33.819237, -112.100156 33.819079, -112.100155 33.819064, -112.100151 33.818672, -112.100157 33.818052, -112.100142 33.817152, -112.100142 33.816793, -112.100158 33.816409, -112.100157 33.816272, -112.100156 33.816071, -112.100154 33.815585, -112.100165 33.813145, -112.100164 33.808344, -112.099129 33.80841, -112.099027 33.808415, -112.097966 33.808472, -112.09785 33.808491, -112.097766 33.808511, -112.097708 33.808518, -112.097568 33.808526, -112.096977 33.808535, -112.096353 33.80855, -112.095856 33.808562, -112.095852 33.808363, -112.095844 33.807532, -112.095841 33.807144, -112.095826 33.806663, -112.095818 33.806408, -112.095817 33.806231, -112.095815 33.805848, -112.095798 33.805807, -112.095763 33.805768, -112.095714 33.805742, -112.095661 33.80573, -112.095581 33.805724, -112.095328 33.805724, -112.094535 33.805768, -112.094258 33.805782, -112.093742 33.805809, -112.091489 33.805925, -112.090502 33.805968, -112.090328 33.805973, -112.089701 33.806008, -112.088573 33.806088, -112.087928 33.806106, -112.087557 33.806109, -112.087172 33.806086, -112.086589 33.806089, -112.085572 33.806126, -112.085475 33.80613, -112.084868 33.806163, -112.083906 33.806215, -112.082749 33.806282, -112.082741 33.805608, -112.082732 33.804827, -112.082708 33.802702, -112.082691 33.801268, -112.082677 33.799964, -112.08269 33.79949, -112.082694 33.79915, -112.082718 33.79906, -112.082721 33.799018, -112.082726 33.798806, -112.082741 33.798084, -112.082756 33.797945, -112.082788 33.79781, -112.082823 33.797712, -112.082872 33.797611, -112.082887 33.796922, -112.082902 33.796224, -112.082973 33.791751, -112.082699 33.791733, -112.082405 33.791715, -112.078362 33.791756, -112.078187 33.791758, -112.078181 33.791252, -112.078169 33.790731, -112.076851 33.790619, -112.076427 33.790582, -112.075918 33.790175, -112.075622 33.789939, -112.075437 33.789946, -112.074571 33.789534, -112.074407 33.789144, -112.074341 33.788886, -112.074296 33.788745, -112.074262 33.788632, -112.074084 33.788072, -112.074089 33.788123, -112.074095 33.788232, -112.074084 33.788397, -112.074088 33.788735, -112.074071 33.789263, -112.074048 33.789461, -112.074039 33.789721, -112.07404 33.789955, -112.074042 33.790266, -112.074058 33.790435, -112.074046 33.790764, -112.074044 33.790986, -112.074051 33.791183, -112.074017 33.791418, -112.074009 33.791507, -112.074007 33.791734, -112.074001 33.791788, -112.074002 33.79182, -112.072657 33.791834, -112.0723 33.791838, -112.070636 33.79185, -112.069765 33.791858, -112.069725 33.79185, -112.06968 33.791833, -112.069421 33.791833, -112.068844 33.791851, -112.068561 33.791851, -112.068109 33.791845, -112.067558 33.791863, -112.066855 33.791904, -112.066697 33.791901, -112.066303 33.791869, -112.066153 33.791861, -112.065792 33.791871, -112.065599 33.79188, -112.065552 33.791886, -112.065496 33.791892, -112.065397 33.791917, -112.065254 33.791962, -112.065243 33.791873, -112.06486 33.791875, -112.064395 33.791887, -112.064105 33.791879, -112.063738 33.79188, -112.063582 33.791872, -112.0627 33.791859, -112.062592 33.791845, -112.06257 33.791838, -112.062488 33.791831, -112.062391 33.791844, -112.062351 33.791852, -112.061856 33.791853, -112.061736 33.791844, -112.061701 33.791834, -112.06168 33.791824, -112.06107 33.791885, -112.061065 33.791826, -112.061023 33.791546, -112.061015 33.791425, -112.061025 33.791354, -112.061027 33.791294, -112.061037 33.791223, -112.061047 33.791145, -112.061064 33.790756, -112.061062 33.790253, -112.061047 33.789753, -112.061046 33.789715, -112.061042 33.789048, -112.061041 33.788867, -112.061029 33.788638, -112.061031 33.788173, -112.061027 33.788103, -112.061 33.788032, -112.060981 33.788006, -112.060923 33.787948, -112.060914 33.787938, -112.060832 33.787847, -112.060807 33.787791, -112.060802 33.787726, -112.060804 33.787688, -112.060812 33.787647, -112.060851 33.787569, -112.060912 33.787446, -112.060934 33.787387, -112.060946 33.787332, -112.060975 33.787139, -112.061 33.786864, -112.061 33.786829, -112.061003 33.786685, -112.060982 33.786046, -112.060947 33.78569, -112.060932 33.785356, -112.060966 33.784688, -112.060963 33.784627, -112.060955 33.784607, -112.060912 33.78456, -112.060842 33.784533, -112.060364 33.784527, -112.060065 33.784524, -112.059714 33.784533, -112.059272 33.784524, -112.058352 33.784537, -112.057399 33.784539, -112.057083 33.784549, -112.056685 33.784549, -112.056542 33.784556, -112.054573 33.784598, -112.053882 33.784613, -112.052476 33.784637, -112.052426 33.784657, -112.052396 33.784689, -112.052382 33.784723, -112.052372 33.784745, -112.052369 33.784777, -112.052362 33.786381, -112.052365 33.786961, -112.052367 33.787212, -112.052369 33.787554, -112.052368 33.787744, -112.052367 33.788073, -112.052367 33.788166, -112.052366 33.78849, -112.052363 33.789116, -112.052362 33.789673, -112.052376 33.79005, -112.052378 33.790268, -112.05239 33.791522, -112.052387 33.791846, -112.054479 33.791857, -112.056744 33.791868, -112.056741 33.793207, -112.05674 33.793587, -112.05674 33.794086, -112.05674 33.794288, -112.05674 33.794539, -112.056746 33.7963, -112.056748 33.796879, -112.056734 33.79813, -112.056703 33.79863, -112.056687 33.799026, -112.056692 33.799169, -112.054569 33.79917, -112.053134 33.799175, -112.052395 33.799175, -112.05236 33.799246, -112.05235 33.799289, -112.05235 33.799421, -112.052406 33.799842, -112.052426 33.800203, -112.052431 33.800535, -112.052425 33.80155, -112.052393 33.802368, -112.052378 33.802734, -112.05238 33.80281, -112.052402 33.803367, -112.052396 33.804377, -112.052387 33.80483, -112.05237 33.805674, -112.052346 33.806265, -112.052342 33.806448, -112.052032 33.806438, -112.051455 33.806428, -112.050428 33.806439, -112.05011 33.806437, -112.049607 33.806434, -112.049002 33.806441, -112.048121 33.806418, -112.048116 33.807223, -112.048098 33.807703, -112.048089 33.808238, -112.048073 33.809382, -112.048059 33.810011, -112.04805 33.810404, -112.048047 33.810525, -112.048028 33.811419, -112.048002 33.81275, -112.047993 33.813016, -112.047951 33.813718, -112.04793 33.814175, -112.047916 33.81498, -112.047904 33.816853, -112.047905 33.817038, -112.047906 33.817195, -112.047906 33.81722, -112.047911 33.817754, -112.04791 33.817881, -112.047908 33.818099, -112.047907 33.818485, -112.047907 33.81911, -112.047225 33.819115, -112.04648 33.819115, -112.045878 33.819109, -112.045668 33.819107, -112.04542 33.819108, -112.044911 33.819111, -112.044448 33.819113, -112.044245 33.819115, -112.043643 33.81911, -112.043497 33.819109, -112.0435 33.819859, -112.043486 33.819905, -112.04346 33.819942, -112.043443 33.819958, -112.043403 33.819983, -112.043382 33.819988, -112.043223 33.819998, -112.042911 33.819999, -112.042462 33.82, -112.042069 33.82, -112.04162 33.820004, -112.040807 33.820012, -112.039168 33.820005, -112.039179 33.819353, -112.039172 33.819259, -112.039146 33.819154, -112.039128 33.819056, -112.038665 33.81906, -112.038552 33.819058, -112.039489 33.817014, -112.040219 33.815565, -112.040933 33.81372, -112.039459 33.813721, -112.039044 33.813721, -112.037742 33.813723, -112.03549 33.813721, -112.034717 33.813721, -112.032636 33.813721, -112.03187 33.813717, -112.030731 33.813711, -112.030467 33.813694, -112.029487 33.813629, -112.028622 33.813612, -112.028567 33.813611, -112.026153 33.813605, -112.025547 33.813603, -112.024163 33.813598, -112.023952 33.813597, -112.022762 33.813597, -112.022149 33.813597, -112.022059 33.813596, -112.021738 33.813594, -112.021583 33.813592, -112.021586 33.815782, -112.021609 33.81723, -112.021735 33.817227, -112.021738 33.818436, -112.021628 33.818403, -112.021594 33.820826, -112.021744 33.820828, -112.021775 33.820829, -112.02181 33.820855, -112.021861 33.820874, -112.02193 33.820884, -112.022004 33.820885, -112.021745 33.821118, -112.021594 33.821254, -112.021597 33.824439, -112.021599 33.82565, -112.021753 33.825215, -112.022025 33.824443, -112.022865 33.822199, -112.023356 33.82089, -112.023403 33.82089, -112.023465 33.82089, -112.023869 33.820892, -112.025217 33.820888, -112.026141 33.820886, -112.026134 33.821552, -112.026092 33.823449, -112.026085 33.824182, -112.02607 33.82447, -112.026068 33.824509, -112.025338 33.824495, -112.025123 33.824479, -112.025092 33.824479, -112.025025 33.824633, -112.025014 33.824928, -112.024971 33.825218, -112.024912 33.825825, -112.024874 33.826206, -112.024799 33.826625, -112.024751 33.826969, -112.024762 33.827345, -112.024815 33.827619, -112.024907 33.827855, -112.025052 33.828027, -112.025159 33.828091, -112.025293 33.828157, -112.026086 33.828102, -112.027535 33.828117, -112.02779 33.828178, -112.02854 33.828171, -112.029414 33.828145, -112.030465 33.828132, -112.031274 33.828142, -112.032105 33.828143, -112.032287 33.828144, -112.033596 33.828153, -112.034242 33.828146, -112.034784 33.82815, -112.035785 33.828155, -112.036955 33.828153, -112.038351 33.828157, -112.03878 33.828151, -112.039143 33.828156, -112.039224 33.828157, -112.039235 33.82845, -112.039243 33.828796, -112.039244 33.829865, -112.039234 33.830083, -112.039235 33.830566, -112.039242 33.830817, -112.039247 33.831017, -112.039242 33.83132, -112.039251 33.831797, -112.039253 33.832094, -112.039246 33.832518, -112.039252 33.832739, -112.03925 33.832921, -112.039247 33.832992, -112.03924 33.833152, -112.039234 33.83348, -112.03925 33.833832, -112.03925 33.83394, -112.039249 33.834172, -112.039234 33.834611, -112.039226 33.834865, -112.03921 33.835191, -112.039218 33.83522, -112.039238 33.835254, -112.039283 33.835304, -112.039321 33.835326, -112.039366 33.835337, -112.040337 33.835349, -112.040459 33.835351, -112.04058 33.835352, -112.041731 33.835351, -112.042178 33.835356, -112.042482 33.835366, -112.042745 33.835375, -112.043328 33.835395, -112.043628 33.835396, -112.043797 33.8354, -112.04413 33.835407, -112.044667 33.835394, -112.045054 33.83538, -112.045579 33.835366, -112.045626 33.835365, -112.04601 33.835362, -112.046212 33.835366, -112.046726 33.835377, -112.04672 33.835529, -112.046717 33.835979, -112.046708 33.836312, -112.046709 33.836626, -112.046722 33.836893, -112.046715 33.837157, -112.046708 33.837241, -112.046702 33.837736, -112.046707 33.838483, -112.046702 33.838796, -112.046709 33.839076, -112.046935 33.839064, -112.04736 33.83903, -112.047776 33.839009, -112.0478 33.839002, -112.047827 33.838981, -112.047836 33.838952, -112.047866 33.835409, -112.047868 33.835381, -112.047894 33.835229, -112.047906 33.834796, -112.0479 33.834523, -112.047901 33.834396, -112.047901 33.834327, -112.047904 33.833708, -112.047897 33.833441, -112.047901 33.832502, -112.047892 33.832152, -112.047884 33.83187, -112.047882 33.831792, -112.047879 33.831657, -112.047873 33.831414, -112.047872 33.83135, -112.04787 33.831278, -112.047871 33.83111, -112.047874 33.830798, -112.047872 33.830241, -112.047876 33.829694, -112.047867 33.829226, -112.047877 33.828833, -112.047884 33.82853, -112.047898 33.828251, -112.047898 33.828073, -112.048426 33.82811, -112.048869 33.828141, -112.049658 33.828151, -112.050099 33.828152, -112.050744 33.828155, -112.051292 33.828153, -112.051542 33.828154, -112.05232 33.828157, -112.05405 33.82816, -112.054653 33.828156, -112.055945 33.828156, -112.05663 33.828167, -112.056641 33.828755, -112.056651 33.829067, -112.056656 33.829617, -112.056666 33.830137, -112.056667 33.830549, -112.056685 33.830966, -112.056679 33.831358, -112.056693 33.832025, -112.056691 33.83224, -112.056684 33.833003, -112.056686 33.834175, -112.056679 33.834431, -112.05666 33.835093, -112.056661 33.835394, -112.05664 33.835618, -112.05663 33.835967, -112.056634 33.836429, -112.056642 33.836764, -112.056628 33.837359, -112.05662 33.837568, -112.056628 33.838059, -112.05663 33.838532, -112.056631 33.838859, -112.056631 33.83894, -112.056673 33.839734, -112.056671 33.839921, -112.056642 33.840267, -112.056621 33.840407, -112.056614 33.840726, -112.056618 33.841169, -112.056628 33.841377, -112.05663 33.841416, -112.056629 33.841733, -112.056629 33.841827, -112.056622 33.842021, -112.056633 33.84249, -112.056632 33.842674, -112.056642 33.843285, -112.056639 33.843683, -112.056645 33.844118, -112.056644 33.844356, -112.056635 33.844432, -112.056618 33.844807, -112.056623 33.845111, -112.056636 33.845394, -112.056625 33.845697, -112.056615 33.845806, -112.056613 33.845831, -112.056602 33.845877, -112.056582 33.84593, -112.056576 33.84596, -112.05657 33.846061, -112.056561 33.846088, -112.056559 33.846116, -112.056574 33.846179, -112.056603 33.846243, -112.056607 33.846292, -112.056619 33.84692, -112.056621 33.847693, -112.056638 33.848217, -112.056635 33.848311, -112.056617 33.848452, -112.056613 33.848938, -112.05662 33.84972, -112.056624 33.849794, -112.056634 33.849841, -112.056653 33.849899, -112.056656 33.849922, -112.057198 33.849911, -112.058783 33.849897, -112.059141 33.849906, -112.060063 33.849917, -112.060994 33.84992, -112.062612 33.849915, -112.062691 33.849903, -112.062687 33.850697, -112.062673 33.851836, -112.062654 33.852296, -112.062633 33.852581, -112.062593 33.852931, -112.062557 33.853156, -112.062492 33.853478, -112.063074 33.853532, -112.063549 33.853561, -112.064289 33.853555, -112.064723 33.853562, -112.065347 33.853561, -112.065346 33.853037, -112.06536 33.852559, -112.065364 33.851833, -112.065354 33.851672, -112.067027 33.85356, -112.068048 33.855409, -112.068349 33.855109, -112.068323 33.854696, -112.068316 33.854587, -112.068336 33.854235, -112.068402 33.854109, -112.068553 33.853999, -112.068771 33.853966, -112.069008 33.853971, -112.069384 33.853878, -112.069687 33.853856, -112.070477 33.853702, -112.070728 33.853685, -112.070899 33.853641, -112.071663 33.853729, -112.072098 33.853636, -112.07227 33.853625, -112.073139 33.853647, -112.073719 33.85352, -112.073811 33.8533, -112.074072 33.853087, -112.074061 33.853446, -112.07406 33.8535, -112.074082 33.853888, -112.074082 33.85395, -112.074082 33.853991, -112.074083 33.854424, -112.074077 33.854804, -112.07407 33.854913, -112.074056 33.855149, -112.074066 33.855529, -112.074066 33.856467, -112.074071 33.856879, -112.074069 33.857024, -112.074082 33.857083, -112.074118 33.857142, -112.074155 33.857172, -112.074194 33.857187, -112.074226 33.857191, -112.074304 33.857191, -112.074332 33.857191, -112.074981 33.857191, -112.075494 33.857196, -112.081317 33.857274, -112.082716 33.857291, -112.082665 33.864913, -112.082653 33.866772, -112.082643 33.868174, -112.083249 33.868173, -112.083925 33.868172, -112.084335 33.870067, -112.084457 33.870372, -112.084992 33.871704, -112.085066 33.871702, -112.086101 33.871671, -112.086277 33.871666, -112.08685 33.871665, -112.086901 33.871667, -112.087116 33.871666, -112.095544 33.871646, -112.098888 33.871607, -112.100031 33.871602, -112.100287 33.871601, -112.100088 33.871161, -112.100044 33.871064, -112.108743 33.871047, -112.111249 33.871041, -112.117595 33.871039, -112.123668 33.871018, -112.129233 33.871007, -112.134685 33.870996, -112.134668 33.872409, -112.134685 33.878389, -112.140124 33.878361, -112.144364 33.878424, -112.144429 33.878423, -112.144827 33.878422, -112.144873 33.878422, -112.145595 33.878421, -112.145706 33.878421, -112.146645 33.87842, -112.146847 33.878384, -112.146835 33.878281, -112.146801 33.877992, -112.146797 33.877955, -112.146835 33.877801, -112.147037 33.876984, -112.147189 33.876367, -112.152285 33.876058, -112.152298 33.876672, -112.152303 33.876934, -112.151858 33.876898, -112.151143 33.876938, -112.150522 33.877088, -112.150589 33.8772, -112.150727 33.87738, -112.150908 33.877519, -112.151791 33.877687, -112.152187 33.877565, -112.152304 33.877351, -112.152303 33.877882, -112.152302 33.878125, -112.152301 33.878959, -112.152302 33.880689, -112.152303 33.881179, -112.152307 33.882298, -112.152308 33.882748, -112.152298 33.883809, -112.152311 33.883809, -112.152946 33.8838, -112.152943 33.883932, -112.152934 33.884428, -112.152943 33.884595, -112.152948 33.884659, -112.152972 33.884743, -112.153014 33.884837, -112.153084 33.884929, -112.153173 33.885018, -112.153282 33.885084, -112.153358 33.885119, -112.15343 33.885143, -112.153544 33.88517, -112.153622 33.885178, -112.153811 33.885175, -112.153974 33.885183, -112.154088 33.885168, -112.154114 33.885028, -112.154147 33.884921, -112.154286 33.884475, -112.15432 33.884366, -112.154357 33.88429, -112.154427 33.884168, -112.154598 33.883895, -112.154671 33.883742, -112.154725 33.883608, -112.155001 33.882782, -112.155242 33.882033, -112.15491 33.881932, -112.155107 33.881146, -112.155506 33.88121, -112.155715 33.880572, -112.155726 33.880452, -112.155725 33.880091, -112.155844 33.879287, -112.155799 33.879102, -112.155443 33.87859, -112.155573 33.878518, -112.155745 33.87841, -112.155962 33.87825, -112.156206 33.878024, -112.156491 33.877668, -112.156605 33.877481, -112.156745 33.877143, -112.156804 33.876935, -112.156847 33.876658, -112.15685 33.876383, -112.156815 33.876147, -112.156734 33.87582, -112.156643 33.875595, -112.156533 33.875359, -112.156697 33.875321, -112.156852 33.875274, -112.15724 33.875218, -112.157594 33.875162, -112.157849 33.875078, -112.158081 33.874967, -112.158468 33.874661, -112.158579 33.874522, -112.158678 33.874374, -112.158757 33.874182, -112.159244 33.874066, -112.161194 33.873065, -112.161977 33.871812, -112.162313 33.871276, -112.164247 33.869208, -112.164272 33.86908, -112.158604 33.87101, -112.158537 33.870464, -112.158766 33.87044, -112.159106 33.870392, -112.15952 33.870315, -112.159703 33.870285, -112.159979 33.870248, -112.160349 33.870218, -112.160351 33.869345, -112.160149 33.869352, -112.159948 33.869374, -112.159789 33.869402, -112.159473 33.869467, -112.159201 33.86951, -112.158925 33.869542, -112.158833 33.869549, -112.158723 33.868758, -112.159003 33.868727, -112.159281 33.868687, -112.159558 33.86864, -112.160089 33.868528, -112.160355 33.868483, -112.160315 33.868292, -112.160263 33.86812, -112.160194 33.867953, -112.160126 33.867822, -112.160058 33.867712, -112.160016 33.867651, -112.159853 33.867435, -112.159603 33.867103, -112.159509 33.866951, -112.15943 33.866792, -112.159369 33.866629, -112.159325 33.866461, -112.159298 33.866291, -112.15929 33.86612, -112.159294 33.865609, -112.15931 33.865449, -112.15934 33.865291, -112.159375 33.865159, -112.159467 33.864897, -112.159517 33.864713, -112.159547 33.864522, -112.158381 33.864518, -112.157816 33.864516, -112.157818 33.863923, -112.155719 33.863918, -112.155489 33.863927, -112.155261 33.863951, -112.155091 33.863977, -112.155003 33.863993, -112.154782 33.864044, -112.154565 33.864107, -112.154354 33.864184, -112.1542 33.864248, -112.15405 33.86432, -112.153621 33.864552, -112.153152 33.864806, -112.152899 33.86492, -112.152689 33.864998, -112.152473 33.865063, -112.152252 33.865116, -112.152237 33.865119, -112.152027 33.865156, -112.151799 33.865182, -112.150352 33.865333, -112.150313 33.865278, -112.150274 33.865247, -112.150225 33.865223, -112.150169 33.86521, -112.150115 33.865208, -112.149357 33.865215, -112.14804 33.865209, -112.147757 33.865202, -112.147694 33.865211, -112.147638 33.865235, -112.147607 33.865258, -112.147546 33.865106, -112.147553 33.864957, -112.147432 33.864727, -112.147289 33.864472, -112.147178 33.864303, -112.147109 33.864026, -112.145875 33.863941, -112.145699 33.863929, -112.145575 33.863921, -112.145534 33.863918, -112.145179 33.863893, -112.144854 33.861056, -112.144654 33.859319, -112.144159 33.855001, -112.143727 33.851298, -112.143709 33.851138, -112.143526 33.849548, -112.143442 33.848761, -112.143384 33.848278, -112.143287 33.847321, -112.14326 33.84681, -112.143247 33.846345, -112.143245 33.845766, -112.143253 33.845425, -112.143254 33.845384, -112.143265 33.845148, -112.143281 33.844839, -112.143316 33.844496, -112.143362 33.843978, -112.143453 33.843323, -112.14349 33.843109, -112.14362 33.842343, -112.143656 33.842158, -112.143663 33.842116, -112.143755 33.841589, -112.143775 33.841476, -112.143797 33.841352, -112.143821 33.841218, -112.144033 33.840043, -112.144047 33.839964, -112.144151 33.83935, -112.144268 33.838723, -112.14438 33.838053, -112.144448 33.837584, -112.144518 33.837062, -112.144566 33.836636, -112.144621 33.836025, -112.144659 33.835418, -112.144684 33.834831, -112.144698 33.834287, -112.144698 33.833594, -112.144685 33.833098, -112.14466 33.832519, -112.144615 33.831861, -112.144606 33.831756, -112.144563 33.831267, -112.144451 33.83036, -112.144397 33.829991, -112.144297 33.829373, -112.144144 33.828532, -112.144063 33.828226, -112.143981 33.827869, -112.143944 33.827707, -112.143907 33.827562, -112.143333 33.827556, -112.143226 33.827556, -112.143214 33.827489, -112.142935 33.827553, -112.142651 33.827597, -112.142256 33.827569, -112.142141 33.827546, -112.134958 33.827477, -112.134958 33.834628, -112.134932 33.837022, -112.134884 33.837015, -112.134866 33.838325, -112.134863 33.839161, -112.134878 33.839421, -112.132708 33.839451, -112.132707 33.839564, -112.130526 33.839589, -112.13053 33.838838, -112.130538 33.838379, -112.130542 33.838129, -112.130544 33.837604, -112.130546 33.83729, -112.130539 33.83701, -112.130537 33.83694, -112.130529 33.836645, -112.130528 33.836605, -112.130529 33.836006, -112.130532 33.835746, -112.130536 33.83533, -112.13054 33.834917, -112.130345 33.83492, -112.130266 33.834921, -112.129616 33.834918, -112.12942 33.834922, -112.128821 33.834933, -112.127692 33.834944, -112.127317 33.834944, -112.126189 33.834946)), ((-112.110277 33.761933, -112.110177 33.762395, -112.110098 33.762587, -112.109913 33.762884, -112.109835 33.763093, -112.109769 33.763099, -112.109512 33.763209, -112.109486 33.763247, -112.109005 33.763429, -112.108611 33.763633, -112.108525 33.763677, -112.108031 33.763886, -112.107742 33.763979, -112.107722 33.764007, -112.107104 33.764172, -112.106906 33.764282, -112.106702 33.764568, -112.106544 33.764738, -112.106235 33.764975, -112.105136 33.765464, -112.104504 33.765663, -112.104155 33.765696, -112.104068 33.765716, -112.10382 33.765773, -112.103227 33.766037, -112.103037 33.766169, -112.102668 33.766565, -112.102412 33.7669, -112.102247 33.76717, -112.102122 33.767351, -112.101912 33.767928, -112.101807 33.768093, -112.101734 33.768176, -112.101432 33.768605, -112.100938 33.768957, -112.100385 33.769116, -112.100043 33.769144, -112.099595 33.769056, -112.098621 33.769199, -112.097884 33.769381, -112.097779 33.769469, -112.097417 33.769519, -112.097153 33.769739, -112.096627 33.769744, -112.096304 33.769502, -112.095935 33.769112, -112.095553 33.768172, -112.09549 33.768183, -112.094844 33.768227, -112.093703 33.769943, -112.093353 33.770772, -112.093097 33.771378, -112.092654 33.771942, -112.092413 33.772249, -112.091761 33.77225, -112.091775 33.773061, -112.091776 33.773136, -112.092431 33.773135, -112.092441 33.773697, -112.092777 33.773697, -112.093285 33.773747, -112.093773 33.77395, -112.094193 33.774256, -112.094262 33.774306, -112.094845 33.774699, -112.094531 33.775095, -112.094753 33.775236, -112.094911 33.775403, -112.094954 33.775561, -112.094965 33.775728, -112.094912 33.775869, -112.094628 33.776257, -112.094082 33.777015, -112.093448 33.776699, -112.093154 33.777105, -112.092986 33.777669, -112.092923 33.777818, -112.092682 33.778329, -112.092629 33.778488, -112.092582 33.778934, -112.094211 33.778956, -112.094231 33.778956, -112.095085 33.779073, -112.095959 33.779446, -112.09631 33.779616, -112.096395 33.779657, -112.09718 33.780038, -112.097892 33.780155, -112.098036 33.780157, -112.098015 33.78073, -112.097457 33.780664, -112.09703 33.780552, -112.096909 33.780514, -112.096488 33.780346, -112.09644 33.780317, -112.096189 33.78067, -112.09609 33.780892, -112.096082 33.781104, -112.096533 33.781289, -112.097218 33.781483, -112.097566 33.781531, -112.098028 33.781577, -112.098045 33.782399, -112.097629 33.782363, -112.097639 33.783229, -112.097247 33.783234, -112.097274 33.783924, -112.099579 33.783738, -112.099866 33.782479, -112.100367 33.780148, -112.100399 33.779747, -112.100379 33.779559, -112.100353 33.779403, -112.100311 33.779196, -112.100244 33.778998, -112.100135 33.77877, -112.099958 33.778411, -112.09941 33.777217, -112.099225 33.776556, -112.099628 33.776542, -112.101924 33.776459, -112.104297 33.776479, -112.104655 33.776482, -112.104684 33.776295, -112.104672 33.775919, -112.104616 33.775355, -112.104503 33.774734, -112.104372 33.774152, -112.1043 33.773791, -112.104292 33.773481, -112.104304 33.773259, -112.10436 33.773075, -112.104454 33.77286, -112.104822 33.77245, -112.105193 33.77222, -112.105575 33.772088, -112.106369 33.77203, -112.106806 33.772079, -112.1073 33.772135, -112.108016 33.772265, -112.107775 33.772776, -112.107668 33.773001, -112.107561 33.77356, -112.10889 33.774099, -112.108091 33.774969, -112.107878 33.775326, -112.107621 33.776399, -112.107562 33.776614, -112.107545 33.776426, -112.107452 33.776052, -112.107294 33.775755, -112.107156 33.77559, -112.107129 33.775618, -112.107129 33.775722, -112.107182 33.776057, -112.107104 33.776684, -112.10713 33.776789, -112.107255 33.776959, -112.107275 33.777047, -112.107223 33.777349, -112.10715 33.777514, -112.107107 33.777763, -112.107058 33.778059, -112.107006 33.778169, -112.106565 33.778779, -112.106335 33.778966, -112.105624 33.779675, -112.105492 33.779862, -112.10544 33.780143, -112.105644 33.780907, -112.105598 33.781237, -112.105526 33.781479, -112.105408 33.781748, -112.105237 33.781946, -112.105092 33.782243, -112.10496 33.78243, -112.104572 33.783238, -112.104441 33.783453, -112.104178 33.783733, -112.104155 33.783817, -112.104125 33.783931, -112.104125 33.784123, -112.10427 33.78447, -112.104283 33.784585, -112.104275 33.784624, -112.104231 33.784855, -112.104093 33.784998, -112.103474 33.785262, -112.103402 33.785328, -112.103211 33.785597, -112.103053 33.786048, -112.103099 33.786284, -112.103172 33.786422, -112.103152 33.786614, -112.103034 33.786752, -112.103007 33.786774, -112.10283 33.786999, -112.102777 33.787109, -112.102692 33.787807, -112.102586 33.78795, -112.102396 33.788104, -112.102402 33.788418, -112.102528 33.788681, -112.102712 33.788934, -112.102778 33.789072, -112.102765 33.789165, -112.102423 33.789506, -112.10216 33.789941, -112.102094 33.790309, -112.102087 33.790886, -112.101574 33.791694, -112.10143 33.7922, -112.101344 33.792393, -112.10118 33.792569, -112.10085 33.79275, -112.100614 33.792915, -112.100251 33.793036, -112.100093 33.793119, -112.099975 33.793256, -112.0996 33.793482, -112.099488 33.793625, -112.099481 33.793823, -112.099541 33.794147, -112.099515 33.794273, -112.099449 33.794356, -112.099251 33.794466, -112.099179 33.794543, -112.099153 33.794609, -112.099159 33.794999, -112.099113 33.795203, -112.098982 33.795395, -112.098982 33.795615, -112.098949 33.79573, -112.098653 33.796027, -112.098515 33.796429, -112.098311 33.796511, -112.098087 33.796566, -112.097823 33.796687, -112.097507 33.796896, -112.097178 33.797226, -112.096862 33.797485, -112.096823 33.797661, -112.096836 33.797925, -112.096757 33.798106, -112.096593 33.798205, -112.096204 33.798249, -112.09604 33.798293, -112.095882 33.798436, -112.095763 33.798717, -112.095592 33.799002, -112.095546 33.799041, -112.095507 33.799261, -112.09527 33.799525, -112.095224 33.799613, -112.095145 33.799899, -112.095204 33.800244, -112.096136 33.799671, -112.096297 33.799572, -112.096704 33.799331, -112.09684 33.799256, -112.097127 33.799111, -112.097347 33.799014, -112.097435 33.798975, -112.097704 33.798872, -112.098132 33.798729, -112.098253 33.798694, -112.098776 33.798572, -112.098849 33.798559, -112.099092 33.798515, -112.099424 33.798471, -112.099761 33.798441, -112.1001 33.798428, -112.101786 33.798424, -112.102097 33.798422, -112.105107 33.798402, -112.105717 33.798398, -112.106275 33.798397, -112.107034 33.798396, -112.108364 33.798385, -112.109773 33.798383, -112.109889 33.798381, -112.109828 33.798506, -112.110055 33.798505, -112.110069 33.799008, -112.11007 33.799189, -112.110073 33.799552, -112.110091 33.799696, -112.110124 33.799825, -112.110156 33.79991, -112.110236 33.800066, -112.110327 33.800203, -112.110391 33.800279, -112.110434 33.800331, -112.110464 33.800362, -112.110484 33.800394, -112.110238 33.800619, -112.110166 33.800686, -112.11008 33.800799, -112.110017 33.800922, -112.109977 33.801056, -112.109961 33.801247, -112.109965 33.801313, -112.109996 33.801466, -112.110049 33.801574, -112.110247 33.80188, -112.11063 33.802479, -112.110668 33.802529, -112.110704 33.802565, -112.110743 33.802577, -112.110803 33.80257, -112.110906 33.80252, -112.111244 33.802364, -112.111436 33.802239, -112.111612 33.802107, -112.111763 33.801963, -112.111936 33.801759, -112.11207 33.801538, -112.112147 33.801344, -112.112176 33.801226, -112.112205 33.801065, -112.112477 33.801142, -112.112568 33.801173, -112.112747 33.801244, -112.1129 33.801323, -112.113014 33.801397, -112.113069 33.801438, -112.113184 33.801539, -112.113284 33.801649, -112.113343 33.801727, -112.113511 33.802028, -112.113528 33.802058, -112.113591 33.802181, -112.11322 33.802339, -112.113052 33.802449, -112.112771 33.802684, -112.112383 33.803142, -112.11313 33.803643, -112.113485 33.803208, -112.113756 33.80302, -112.114 33.802911, -112.11427 33.803395, -112.114551 33.803876, -112.114746 33.804201, -112.115079 33.804779, -112.115118 33.804872, -112.11516 33.805029, -112.115174 33.805155, -112.115173 33.805275, -112.115067 33.80619, -112.115069 33.806989, -112.115057 33.807862, -112.115024 33.808187, -112.115008 33.808342, -112.114972 33.808864, -112.114835 33.809083, -112.114598 33.809323, -112.114473 33.80949, -112.11443 33.809529, -112.114264 33.809677, -112.114196 33.809893, -112.114093 33.810287, -112.113996 33.810651, -112.11373 33.811135, -112.113893 33.811294, -112.11409 33.811371, -112.114466 33.811574, -112.114795 33.81164, -112.114868 33.811717, -112.114881 33.812008, -112.114973 33.812063, -112.115125 33.81209, -112.115467 33.812057, -112.115645 33.812096, -112.115731 33.812211, -112.115863 33.812321, -112.115948 33.812409, -112.116264 33.812469, -112.116554 33.812733, -112.116633 33.812755, -112.11695 33.812705, -112.117424 33.812502, -112.117457 33.813249, -112.117525 33.812964, -112.119093 33.812942, -112.120233 33.812929, -112.120297 33.81293, -112.12112 33.812917, -112.121853 33.812908, -112.123424 33.812888, -112.124044 33.81288, -112.125232 33.812868, -112.126193 33.812868, -112.126214 33.813011, -112.126232 33.813212, -112.126216 33.813803, -112.126214 33.814339, -112.126209 33.815237, -112.126208 33.8154, -112.127576 33.815416, -112.128065 33.815075, -112.128379 33.814991, -112.128394 33.81491, -112.128428 33.814886, -112.128455 33.814866, -112.128558 33.814859, -112.128619 33.814858, -112.128681 33.814858, -112.128741 33.814857, -112.128798 33.814858, -112.128903 33.814861, -112.128999 33.814865, -112.129061 33.81486, -112.129507 33.814871, -112.130589 33.814859, -112.130582 33.814691, -112.130582 33.813846, -112.130588 33.813142, -112.130597 33.812972, -112.130603 33.812855, -112.130673 33.812855, -112.131023 33.812853, -112.132808 33.812845, -112.134292 33.812839, -112.13444 33.812838, -112.134562 33.812838, -112.134967 33.81282, -112.136402 33.812797, -112.138034 33.81276, -112.137816 33.812166, -112.137192 33.810463, -112.136951 33.809294, -112.13686 33.808898, -112.136104 33.805585, -112.13595 33.80514, -112.135741 33.804537, -112.135358 33.803952, -112.134979 33.803593, -112.134147 33.803139, -112.132639 33.802686, -112.131351 33.80227, -112.130927 33.80212, -112.130628 33.801953, -112.130418 33.801793, -112.130169 33.801608, -112.129916 33.801376, -112.129682 33.801074, -112.129514 33.800764, -112.129425 33.80054, -112.129395 33.800393, -112.129263 33.799634, -112.129225 33.798386, -112.129426 33.798387, -112.130619 33.798389, -112.131494 33.798353, -112.132481 33.798303, -112.132559 33.798306, -112.133115 33.798321, -112.13359 33.798323, -112.134217 33.798315, -112.134732 33.798299, -112.135055 33.798297, -112.135277 33.798297, -112.135247 33.798157, -112.135025 33.79816, -112.134911 33.798163, -112.134529 33.798158, -112.133121 33.798182, -112.132594 33.79819, -112.132352 33.798193, -112.132139 33.798188, -112.131172 33.798194, -112.131241 33.798093, -112.131297 33.797866, -112.131722 33.797361, -112.132106 33.796936, -112.132753 33.796491, -112.133217 33.79637, -112.133107 33.793266, -112.132748 33.791136, -112.132264 33.790737, -112.131598 33.790189, -112.131433 33.789826, -112.131213 33.788422, -112.13113 33.788354, -112.131093 33.787499, -112.131074 33.787062, -112.130991 33.786881, -112.130745 33.784932, -112.130608 33.784457, -112.130551 33.78374, -112.130443 33.782395, -112.13014 33.780403, -112.128515 33.780765, -112.126916 33.781311, -112.125615 33.781491, -112.125127 33.781652, -112.125031 33.781766, -112.124687 33.782188, -112.124225 33.782515, -112.123762 33.78262, -112.123491 33.782683, -112.122759 33.783172, -112.121671 33.783688, -112.121276 33.783748, -112.119524 33.783737, -112.117496 33.783696, -112.117469 33.783381, -112.117404 33.783058, -112.117265 33.782623, -112.117145 33.782364, -112.117024 33.782114, -112.116895 33.781892, -112.116608 33.781494, -112.116219 33.781096, -112.115784 33.780744, -112.115257 33.780346, -112.115118 33.780235, -112.114628 33.779791, -112.114369 33.779514, -112.114174 33.77931, -112.11398 33.779079, -112.113703 33.778746, -112.113379 33.778292, -112.11311 33.777857, -112.112907 33.777506, -112.112545 33.776683, -112.112426 33.776412, -112.112252 33.775973, -112.11207 33.775518, -112.111821 33.774872, -112.111581 33.774218, -112.111449 33.773821, -112.111391 33.773211, -112.111424 33.772919, -112.111482 33.77267, -112.11159 33.772339, -112.111739 33.772041, -112.111912 33.771777, -112.112078 33.77152, -112.112293 33.77123, -112.112542 33.770866, -112.11274 33.770543, -112.112864 33.770221, -112.112947 33.769774, -112.112968 33.769372, -112.112823 33.768209, -112.112804 33.766529, -112.112798 33.765966, -112.112798 33.765677, -112.112791 33.765205, -112.11279 33.765147, -112.112782 33.764717, -112.112782 33.764419, -112.112757 33.764187, -112.112729 33.764077, -112.112699 33.763955, -112.112624 33.763773, -112.11255 33.763525, -112.112368 33.763252, -112.112177 33.763012, -112.111987 33.762813, -112.111697 33.762565, -112.111234 33.7623, -112.110721 33.762109, -112.110277 33.761933)), ((-112.144364 33.878424, -112.143531 33.878426, -112.143525 33.881687, -112.143523 33.882479, -112.143521 33.883845, -112.144737 33.883855, -112.144955 33.883856, -112.144956 33.883231, -112.14482 33.882478, -112.14474 33.882434, -112.144697 33.882006, -112.144617 33.881962, -112.144546 33.88169, -112.144246 33.880542, -112.144126 33.878965, -112.144364 33.878424)), ((-111.586647 33.143576, -111.586685 33.143431, -111.586682 33.143344, -111.58666 33.143279, -111.586613 33.1432, -111.586008 33.143169, -111.585842 33.143169, -111.585779 33.143199, -111.585747 33.143233, -111.585724 33.143288, -111.585732 33.143451, -111.585743 33.143521, -111.585874 33.143521, -111.58616 33.143521, -111.586357 33.143543, -111.586578 33.14362, -111.586646 33.143651, -111.586647 33.143576)), ((-112.012996 33.799345, -112.012996 33.799277, -112.011349 33.799278, -112.007676 33.799284, -112.006884 33.799281, -112.00422 33.799285, -112.003728 33.799285, -112.002901 33.799275, -112.00202 33.799264, -111.99967 33.799253, -111.999019 33.799246, -111.997966 33.799226, -111.997639 33.799222, -111.997644 33.799314, -112.002761 33.799367, -112.004222 33.799378, -112.007044 33.7994, -112.012996 33.79937, -112.012996 33.799345)), ((-111.572975 33.292819, -111.572479 33.291299, -111.571703 33.289084, -111.568975 33.2813, -111.568198 33.279, -111.567923 33.278187, -111.567873 33.278038, -111.564175 33.278039, -111.5634 33.27804, -111.563266 33.278039, -111.563275 33.278189, -111.563335 33.278993, -111.563339 33.279176, -111.563339 33.279335, -111.56335 33.287391, -111.563356 33.292049, -111.563366 33.292785, -111.563437 33.292785, -111.566367 33.292797, -111.56656 33.292797, -111.572975 33.292819)), ((-111.87401 33.476868, -111.872622 33.476861, -111.870456 33.476877, -111.869631 33.476878, -111.86823 33.476882, -111.867963 33.476886, -111.86761 33.476883, -111.866437 33.476896, -111.866148 33.4769, -111.865423 33.476906, -111.865383 33.476899, -111.865331 33.476875, -111.865332 33.477946, -111.865327 33.479529, -111.865339 33.480503, -111.86534 33.482284, -111.86535 33.483078, -111.865357 33.483674, -111.865357 33.483699, -111.865348 33.484126, -111.865345 33.484243, -111.865345 33.484321, -111.865353 33.485909, -111.865353 33.48602, -111.865352 33.48632, -111.865358 33.486857, -111.865364 33.487313, -111.865368 33.487582, -111.865358 33.487755, -111.865451 33.487769, -111.865548 33.487777, -111.865931 33.487769, -111.870162 33.487747, -111.87209 33.487732, -111.874024 33.487724, -111.874008 33.485092, -111.874012 33.483986, -111.874018 33.482729, -111.874011 33.480462, -111.874015 33.47929, -111.87401 33.476868)), ((-112.306758 33.550823, -112.307641 33.550818, -112.30853 33.550823, -112.308558 33.550824, -112.308973 33.550832, -112.309144 33.550837, -112.309571 33.550852, -112.309628 33.550852, -112.309926 33.550857, -112.31098 33.550874, -112.311315 33.550879, -112.312089 33.550892, -112.312362 33.550897, -112.315104 33.550923, -112.315667 33.550929, -112.316024 33.550932, -112.317816 33.550949, -112.317998 33.55095, -112.319053 33.550961, -112.320108 33.55097, -112.322814 33.551001, -112.324365 33.551015, -112.324363 33.550935, -112.324362 33.550905, -112.324355 33.55063, -112.318259 33.550601, -112.317961 33.5506, -112.315919 33.550598, -112.315099 33.550598, -112.312032 33.550599, -112.311397 33.550599, -112.311309 33.550599, -112.311204 33.550599, -112.310968 33.550599, -112.309769 33.5506, -112.309461 33.5506, -112.308425 33.5506, -112.306793 33.550589, -112.306784 33.55065, -112.306762 33.550717, -112.306762 33.550728, -112.306758 33.550823)), ((-111.556919 33.464126, -111.556585 33.464346, -111.556319 33.464506, -111.556258 33.464579, -111.556229 33.464678, -111.556226 33.46476, -111.556297 33.464834, -111.556365 33.464882, -111.556237 33.464821, -111.556109 33.464813, -111.555968 33.464831, -111.555651 33.464897, -111.554822 33.464992, -111.55613 33.465318, -111.556797 33.465353, -111.556788 33.465299, -111.556747 33.46517, -111.556742 33.465076, -111.55683 33.465009, -111.556961 33.464928, -111.556886 33.464689, -111.556919 33.464126)), ((-111.477229 33.366213, -111.477357 33.366855, -111.477654 33.366644, -111.477441 33.366431, -111.477229 33.366213)), ((-111.956883 33.280932, -111.956717 33.277302, -111.952804 33.277305, -111.952826 33.280898, -111.952841 33.280921, -111.952871 33.280937, -111.956883 33.280932)), ((-112.272147 33.391101, -112.272208 33.390447, -112.272077 33.390449, -112.272011 33.389121, -112.269986 33.389121, -112.269964 33.389249, -112.268904 33.389284, -112.270252 33.38947, -112.270435 33.389508, -112.27054 33.389564, -112.270894 33.389882, -112.272147 33.391101)), ((-112.012996 33.79937, -112.012996 33.799387, -112.012996 33.799404, -112.012997 33.799499, -112.013007 33.802125, -112.013004 33.802548, -112.013009 33.802904, -112.013012 33.804019, -112.013015 33.80427, -112.013016 33.804314, -112.013017 33.804374, -112.013013 33.805014, -112.013019 33.806144, -112.013016 33.806598, -112.013018 33.80702, -112.013026 33.808289, -112.013025 33.808741, -112.01303 33.809077, -112.013042 33.809328, -112.013069 33.80963, -112.013098 33.809862, -112.013105 33.809899, -112.013138 33.810086, -112.013263 33.81064, -112.01341 33.811271, -112.013453 33.811482, -112.0135 33.811763, -112.013545 33.812084, -112.013571 33.812384, -112.013579 33.812878, -112.013585 33.813557, -112.01363 33.813558, -112.014408 33.81357, -112.014923 33.813571, -112.015632 33.813573, -112.01795 33.813582, -112.021077 33.813588, -112.021583 33.813592, -112.021594 33.812253, -112.021639 33.807253, -112.021609 33.801966, -112.02013 33.800309, -112.019354 33.799376, -112.019344 33.799365, -112.012996 33.79937)), ((-111.669406 33.277896, -111.669065 33.277893, -111.660698 33.277844, -111.656497 33.277819, -111.652129 33.277793, -111.65213 33.277911, -111.649434 33.277884, -111.648531 33.277873, -111.64671 33.277862, -111.644357 33.277839, -111.643568 33.277836, -111.643569 33.278007, -111.643577 33.278869, -111.643593 33.280644, -111.643608 33.282426, -111.648459 33.282465, -111.648483 33.282459, -111.648517 33.282444, -111.64855 33.282414, -111.648568 33.282372, -111.648555 33.280689, -111.648539 33.278913, -111.648532 33.278022, -111.652215 33.278045, -111.653361 33.278053, -111.653366 33.277921, -111.656497 33.277935, -111.660699 33.277956, -111.667493 33.27799, -111.667647 33.277994, -111.66785 33.278011, -111.668069 33.278045, -111.668162 33.278066, -111.668283 33.278093, -111.668986 33.278298, -111.669061 33.278312, -111.669124 33.278314, -111.669197 33.278306, -111.66926 33.27829, -111.669319 33.278253, -111.669358 33.278214, -111.669383 33.278174, -111.669392 33.278159, -111.669399 33.278132, -111.66941 33.278109, -111.66941 33.278089, -111.669406 33.277896)), ((-111.574557 33.14725, -111.574615 33.148283, -111.574661 33.148938, -111.574702 33.149524, -111.574743 33.150081, -111.574771 33.150415, -111.574839 33.151227, -111.574877 33.151682, -111.574885 33.151774, -111.575021 33.153409, -111.575048 33.153728, -111.575067 33.154072, -111.575089 33.154473, -111.575967 33.154477, -111.576078 33.154477, -111.57657 33.154479, -111.578837 33.154462, -111.579143 33.154463, -111.579092 33.15366, -111.579058 33.153115, -111.579044 33.152892, -111.579016 33.152439, -111.578995 33.15209, -111.578924 33.15083, -111.578854 33.149662, -111.578814 33.149022, -111.58059 33.149003, -111.58102 33.149, -111.581377 33.149003, -111.58177 33.149008, -111.58179 33.148933, -111.581882 33.148792, -111.581924 33.148766, -111.582201 33.148771, -111.582246 33.148795, -111.582271 33.148831, -111.582282 33.149014, -111.582675 33.149019, -111.584235 33.149012, -111.585035 33.149009, -111.585042 33.149195, -111.585072 33.149391, -111.58508 33.149439, -111.585108 33.149507, -111.585157 33.149585, -111.585233 33.149635, -111.585298 33.149648, -111.585357 33.149641, -111.585422 33.149622, -111.585464 33.149555, -111.585508 33.149448, -111.585511 33.149167, -111.585513 33.149006, -111.58585 33.149005, -111.586464 33.149001, -111.587994 33.149003, -111.588644 33.149003, -111.588687 33.149168, -111.58872 33.149346, -111.588766 33.149437, -111.58877 33.149493, -111.588763 33.149549, -111.588725 33.149608, -111.588564 33.149755, -111.588102 33.150132, -111.588004 33.150249, -111.587962 33.150326, -111.587853 33.150798, -111.588017 33.150798, -111.58814 33.150798, -111.588262 33.150799, -111.589196 33.150801, -111.589257 33.150801, -111.5898 33.150799, -111.5899 33.150799, -111.590923 33.150796, -111.591813 33.150795, -111.592304 33.150793, -111.592496 33.15059, -111.592547 33.150792, -111.593562 33.150788, -111.594978 33.150782, -111.595318 33.150779, -111.596309 33.150776, -111.59737 33.150779, -111.598239 33.150782, -111.598439 33.150782, -111.599246 33.150787, -111.59956 33.150781, -111.599941 33.150774, -111.600169 33.150781, -111.600628 33.150793, -111.600803 33.150798, -111.601137 33.150801, -111.601696 33.150805, -111.602194 33.150782, -111.602659 33.150781, -111.603027 33.150748, -111.603065 33.150733, -111.603098 33.150719, -111.603192 33.150652, -111.603265 33.150599, -111.603655 33.150337, -111.603701 33.15028, -111.603746 33.149968, -111.603754 33.149838, -111.60312 33.149731, -111.602078 33.149518, -111.601077 33.149378, -111.600867 33.149313, -111.600054 33.148992, -111.599908 33.148953, -111.599821 33.148938, -111.59978 33.14893, -111.59957 33.148938, -111.598908 33.148964, -111.598176 33.148971, -111.597408 33.148977, -111.597417 33.14923, -111.597414 33.149262, -111.597407 33.149327, -111.597369 33.149411, -111.597253 33.149524, -111.597106 33.149608, -111.596983 33.149625, -111.596842 33.149588, -111.596721 33.149509, -111.59664 33.149414, -111.596622 33.149355, -111.596607 33.149305, -111.596591 33.14898, -111.594594 33.148984, -111.593672 33.148986, -111.593705 33.148715, -111.593701 33.148598, -111.593566 33.14835, -111.593536 33.148227, -111.593536 33.14809, -111.593556 33.147913, -111.593568 33.147646, -111.593563 33.147412, -111.59354 33.147191, -111.594149 33.147193, -111.595267 33.147195, -111.595243 33.145842, -111.595227 33.144623, -111.595233 33.144031, -111.595235 33.143848, -111.595236 33.143759, -111.594839 33.143819, -111.594651 33.143844, -111.594564 33.143856, -111.59441 33.143855, -111.594309 33.143822, -111.594292 33.143813, -111.594261 33.143797, -111.594085 33.143746, -111.59387 33.143706, -111.593607 33.143684, -111.593039 33.14371, -111.589086 33.143721, -111.588862 33.143718, -111.587743 33.143707, -111.586878 33.143704, -111.586721 33.143686, -111.586646 33.143651, -111.586645 33.144183, -111.586647 33.144317, -111.586658 33.14519, -111.586662 33.145439, -111.586663 33.147174, -111.586438 33.147174, -111.582464 33.147181, -111.580671 33.147185, -111.579052 33.147195, -111.578871 33.147208, -111.578719 33.147229, -111.578499 33.147253, -111.577236 33.14726, -111.576553 33.147258, -111.576257 33.147257, -111.574557 33.14725)), ((-111.432518 33.376983, -111.432269 33.376852, -111.432057 33.376717, -111.43209 33.376682, -111.431797 33.3765, -111.431549 33.376328, -111.431317 33.376205, -111.431158 33.376451, -111.430944 33.376818, -111.430903 33.377029, -111.430877 33.377257, -111.430794 33.37748, -111.43065 33.377918, -111.430639 33.378017, -111.430637 33.378041, -111.430658 33.37811, -111.430707 33.37814, -111.432134 33.378146, -111.432189 33.37814, -111.432222 33.378118, -111.432252 33.378099, -111.432297 33.378033, -111.432314 33.378011, -111.432527 33.377513, -111.432735 33.377048, -111.432518 33.376983)), ((-111.499521 33.090567, -111.500013 33.091062, -111.501885 33.093083, -111.501191 33.093555, -111.501212 33.09359, -111.501273 33.093705, -111.501346 33.093837, -111.501448 33.093998, -111.501507 33.094081, -111.501723 33.094329, -111.501796 33.094414, -111.501946 33.094585, -111.501994 33.09464, -111.502021 33.094671, -111.502095 33.094755, -111.502239 33.094921, -111.50231 33.095001, -111.50238 33.095079, -111.502497 33.09522, -111.502513 33.095239, -111.502579 33.095321, -111.502651 33.095402, -111.502733 33.09548, -111.503028 33.095678, -111.503129 33.095737, -111.503242 33.095804, -111.503328 33.095855, -111.503519 33.095974, -111.503609 33.096031, -111.503695 33.096083, -111.50378 33.096136, -111.503794 33.096146, -111.503861 33.096097, -111.503917 33.096055, -111.503991 33.095999, -111.504035 33.095971, -111.504081 33.095939, -111.504126 33.095909, -111.504236 33.095838, -111.504284 33.095807, -111.504372 33.09573, -111.504732 33.09614, -111.505082 33.09652, -111.505525 33.097, -111.506352 33.097886, -111.507766 33.099418, -111.507758 33.096671, -111.507735 33.089419, -111.505526 33.089409, -111.501319 33.089393, -111.499479 33.08943, -111.499521 33.090567)), ((-111.563261 33.277611, -111.563141 33.277611, -111.562596 33.27761, -111.5626 33.277018, -111.562604 33.276456, -111.562605 33.275892, -111.562606 33.275706, -111.562575 33.275289, -111.56257 33.27506, -111.562563 33.274704, -111.562558 33.274461, -111.561144 33.274459, -111.559445 33.274469, -111.559194 33.274473, -111.559102 33.274486, -111.558993 33.274523, -111.558947 33.274549, -111.558934 33.274392, -111.558946 33.273828, -111.558958 33.273274, -111.558965 33.273021, -111.558966 33.272995, -111.558973 33.27275, -111.558986 33.272287, -111.558985 33.272207, -111.558982 33.272049, -111.558977 33.271836, -111.558974 33.271693, -111.558965 33.271261, -111.558964 33.271183, -111.558958 33.270778, -111.55824 33.270783, -111.557876 33.270784, -111.55687 33.270788, -111.556731 33.270788, -111.555732 33.270789, -111.554687 33.270788, -111.554617 33.270788, -111.55252 33.270798, -111.552457 33.270798, -111.551836 33.270801, -111.551466 33.270802, -111.551019 33.270804, -111.551007 33.271054, -111.550998 33.271126, -111.550965 33.271163, -111.550835 33.271192, -111.550774 33.271172, -111.550714 33.271107, -111.550697 33.271006, -111.550705 33.270806, -111.550333 33.270808, -111.550329 33.271979, -111.550328 33.272177, -111.550327 33.272604, -111.550325 33.273241, -111.550328 33.273463, -111.550332 33.273764, -111.550356 33.275405, -111.549852 33.275393, -111.549437 33.275382, -111.547246 33.275402, -111.546653 33.275394, -111.546654 33.274431, -111.546062 33.274437, -111.546053 33.275179, -111.546054 33.275385, -111.546057 33.276392, -111.54609 33.277546, -111.546082 33.278097, -111.546647 33.278161, -111.549813 33.278145, -111.550044 33.277887, -111.550135 33.277842, -111.55018 33.277758, -111.550217 33.2776, -111.550201 33.277467, -111.550162 33.277313, -111.550163 33.277227, -111.550177 33.276618, -111.550187 33.276457, -111.550229 33.276396, -111.550316 33.276353, -111.550369 33.276351, -111.550372 33.277136, -111.550375 33.277726, -111.550363 33.278153, -111.553558 33.278087, -111.553682 33.278081, -111.553847 33.278072, -111.554635 33.278063, -111.55575 33.278064, -111.555945 33.278066, -111.557218 33.278045, -111.557417 33.278042, -111.557859 33.278046, -111.558512 33.27805, -111.5595 33.278056, -111.561261 33.278046, -111.562219 33.278028, -111.563149 33.278039, -111.563266 33.278039, -111.563261 33.277611)), ((-111.943282 33.847171, -111.94422 33.847175, -111.944641 33.847187, -111.945202 33.847184, -111.946085 33.847185, -111.946948 33.847198, -111.947501 33.847213, -111.947546 33.84722, -111.947749 33.847038, -111.947763 33.847016, -111.947771 33.84698, -111.947727 33.846732, -111.947723 33.846598, -111.947728 33.846572, -111.947752 33.846541, -111.947776 33.846526, -111.947913 33.84649, -111.948216 33.846427, -111.948286 33.846408, -111.94838 33.846383, -111.94873 33.846315, -111.949011 33.846279, -111.949255 33.846235, -111.949334 33.846233, -111.949415 33.846244, -111.949922 33.846401, -111.949953 33.846407, -111.950014 33.846412, -111.950074 33.846406, -111.950222 33.846362, -111.950336 33.846346, -111.951131 33.846341, -111.951354 33.846346, -111.951483 33.846338, -111.951841 33.846272, -111.951892 33.846251, -111.951935 33.846224, -111.951977 33.846185, -111.952054 33.846126, -111.952163 33.846068, -111.952274 33.846031, -111.952338 33.846019, -111.952416 33.845997, -111.952599 33.845908, -111.952392 33.845662, -111.952252 33.845505, -111.951955 33.845196, -111.951776 33.845, -111.951692 33.844898, -111.951599 33.844769, -111.951507 33.844628, -111.951456 33.844533, -111.951352 33.844301, -111.951316 33.844201, -111.951274 33.844062, -111.951222 33.84383, -111.95121 33.843745, -111.951208 33.843655, -111.951215 33.843543, -111.951231 33.843454, -111.951271 33.843302, -111.95132 33.843179, -111.95139 33.84303, -111.951539 33.842746, -111.951741 33.842336, -111.951797 33.8422, -111.95188 33.841959, -111.951942 33.841747, -111.951992 33.841548, -111.952032 33.841364, -111.95211 33.841366, -111.952225 33.84136, -111.952339 33.841343, -111.952742 33.841246, -111.953446 33.84109, -111.953856 33.840982, -111.954505 33.840865, -111.95459 33.840838, -111.954713 33.840789, -111.954826 33.840731, -111.955006 33.84061, -111.955334 33.840372, -111.955535 33.840236, -111.955769 33.840064, -111.955955 33.839921, -111.956198 33.839746, -111.95627 33.839699, -111.956345 33.839658, -111.956553 33.839565, -111.956793 33.83947, -111.957141 33.839342, -111.957488 33.839229, -111.957557 33.839209, -111.957657 33.83918, -111.958 33.839097, -111.958098 33.839082, -111.958225 33.839073, -111.958861 33.839071, -111.959444 33.839075, -111.959704 33.839077, -111.961285 33.839071, -111.961459 33.83907, -111.961579 33.839078, -111.961701 33.839106, -111.962008 33.839227, -111.962136 33.839256, -111.962301 33.839283, -111.962504 33.839315, -111.962983 33.839372, -111.963003 33.839098, -111.963053 33.838746, -111.96318 33.838538, -111.963305 33.838077, -111.963461 33.837645, -111.963558 33.83757, -111.963741 33.83746, -111.963942 33.837298, -111.964094 33.837151, -111.964285 33.836843, -111.964327 33.836752, -111.964473 33.836255, -111.964575 33.83574, -111.964701 33.835414, -111.964936 33.835088, -111.965095 33.834879, -111.965245 33.834698, -111.965308 33.83458, -111.965349 33.834363, -111.965421 33.834065, -111.965505 33.833866, -111.965685 33.833522, -111.966003 33.832987, -111.966259 33.832688, -111.966366 33.832524, -111.966482 33.832334, -111.966675 33.832125, -111.96677 33.831935, -111.966887 33.83179, -111.967231 33.831616, -111.967402 33.831488, -111.967572 33.831252, -111.967635 33.83108, -111.96764 33.830947, -111.967601 33.830882, -111.967501 33.830685, -111.967519 33.830306, -111.967678 33.830025, -111.967739 33.829727, -111.96792 33.829446, -111.96825 33.829001, -111.968282 33.828965, -111.968377 33.828784, -111.968503 33.82844, -111.968607 33.828078, -111.968646 33.827781, -111.966911 33.828191, -111.966651 33.828157, -111.966349 33.828132, -111.966178 33.828296, -111.965974 33.828478, -111.965824 33.828569, -111.965585 33.828601, -111.965584 33.828644, -111.96557 33.828737, -111.965545 33.828767, -111.965519 33.82878, -111.96541 33.82881, -111.965302 33.828826, -111.965221 33.828851, -111.96512 33.828905, -111.96501 33.828982, -111.964956 33.829032, -111.964909 33.829089, -111.964853 33.829162, -111.964788 33.829225, -111.96467 33.829305, -111.964567 33.829361, -111.96446 33.829407, -111.964206 33.829498, -111.964001 33.829559, -111.963888 33.829577, -111.963269 33.829606, -111.963198 33.829613, -111.963142 33.829619, -111.963044 33.829642, -111.962969 33.82967, -111.962831 33.829737, -111.962715 33.829808, -111.96268 33.829844, -111.962652 33.829893, -111.962646 33.829911, -111.962639 33.83021, -111.962644 33.830886, -111.962657 33.831078, -111.962653 33.831103, -111.962642 33.831126, -111.962618 33.831157, -111.962579 33.831183, -111.962533 33.831198, -111.962497 33.831202, -111.96222 33.831197, -111.962118 33.831211, -111.96168 33.831329, -111.960661 33.831659, -111.960411 33.831722, -111.96034 33.831741, -111.960039 33.831824, -111.959986 33.831683, -111.959947 33.83155, -111.959924 33.831405, -111.959917 33.83126, -111.959964 33.830679, -111.95996 33.830639, -111.959941 33.830599, -111.959911 33.830568, -111.959869 33.830545, -111.95984 33.830536, -111.959563 33.830492, -111.959147 33.830402, -111.958651 33.830245, -111.958575 33.830217, -111.958434 33.830165, -111.958353 33.830147, -111.958221 33.830139, -111.958072 33.830144, -111.957943 33.830163, -111.957849 33.830187, -111.957682 33.830241, -111.957525 33.830291, -111.957219 33.830429, -111.957063 33.830513, -111.957013 33.830543, -111.956897 33.830645, -111.956785 33.830761, -111.956717 33.830839, -111.956665 33.830923, -111.956602 33.831047, -111.956468 33.831279, -111.956429 33.831367, -111.956373 33.831571, -111.956351 33.831715, -111.956339 33.831955, -111.956356 33.832272, -111.956078 33.832283, -111.95574 33.832274, -111.955416 33.832255, -111.955114 33.832209, -111.955068 33.832199, -111.954933 33.832532, -111.95504 33.832193, -111.95479 33.83214, -111.954151 33.83195, -111.954264 33.831743, -111.954378 33.831428, -111.9545 33.831008, -111.954657 33.830514, -111.954728 33.830378, -111.954807 33.830106, -111.954973 33.829572, -111.955017 33.82928, -111.955122 33.82858, -111.955122 33.828259, -111.955113 33.827944, -111.955069 33.827235, -111.955025 33.826638, -111.955646 33.826638, -111.955741 33.826638, -111.955906 33.826635, -111.956104 33.826621, -111.95613 33.826606, -111.95614 33.826601, -111.956173 33.826563, -111.956184 33.826526, -111.956182 33.826259, -111.956169 33.825672, -111.95616 33.824458, -111.956155 33.824413, -111.956135 33.824356, -111.956098 33.824311, -111.956069 33.824289, -111.956032 33.824275, -111.956006 33.82427, -111.955807 33.82426, -111.955646 33.824255, -111.955633 33.824255, -111.955409 33.824258, -111.954711 33.824279, -111.954562 33.823413, -111.955292 33.823419, -111.955451 33.823407, -111.955506 33.823399, -111.955592 33.823371, -111.955643 33.823337, -111.955673 33.823301, -111.955685 33.823276, -111.955695 33.823238, -111.955709 33.823108, -111.955725 33.822913, -111.955728 33.822762, -111.955733 33.822678, -111.955733 33.822395, -111.955722 33.822231, -111.955725 33.822035, -111.955731 33.821897, -111.9557 33.82091, -111.955362 33.820913, -111.954791 33.820909, -111.954464 33.820936, -111.954465 33.820782, -111.9545 33.820546, -111.95457 33.819828, -111.95464 33.819268, -111.954707 33.818818, -111.95471 33.818497, -111.954702 33.818209, -111.95469 33.818071, -111.9545 33.81678, -111.954432 33.816397, -111.954409 33.816173, -111.954395 33.815962, -111.954395 33.815734, -111.95439 33.81553, -111.954413 33.815443, -111.954413 33.815295, -111.95445 33.814979, -111.95451 33.814663, -111.954715 33.81402, -111.954587 33.813987, -111.954548 33.813978, -111.954474 33.81396, -111.953667 33.81377, -111.953441 33.813735, -111.953301 33.813723, -111.952195 33.813728, -111.952065 33.813742, -111.952058 33.813715, -111.952049 33.813653, -111.952045 33.81362, -111.952045 33.81356, -111.952045 33.813278, -111.952034 33.813207, -111.952014 33.81316, -111.951983 33.813111, -111.951925 33.813052, -111.951858 33.813007, -111.951799 33.812981, -111.951222 33.812839, -111.951108 33.812811, -111.950933 33.812759, -111.950835 33.812717, -111.950748 33.812666, -111.950676 33.81261, -111.950582 33.812515, -111.950516 33.812425, -111.950322 33.812012, -111.949856 33.81097, -111.949771 33.810797, -111.949735 33.810689, -111.949731 33.810667, -111.949715 33.810576, -111.949713 33.810365, -111.949727 33.810269, -111.949753 33.810181, -111.94979 33.810101, -111.949868 33.809976, -111.950103 33.809639, -111.95027 33.809414, -111.95053 33.809056, -111.950673 33.80888, -111.950766 33.80879, -111.950885 33.8087, -111.951482 33.808329, -111.951639 33.808233, -111.951846 33.8081, -111.952556 33.807671, -111.952666 33.807792, -111.952739 33.807886, -111.952782 33.807933, -111.952848 33.807986, -111.952899 33.808018, -111.952975 33.808053, -111.953081 33.808083, -111.953209 33.8081, -111.953316 33.8081, -111.953959 33.808037, -111.954279 33.808011, -111.954719 33.807996, -111.955294 33.808022, -111.955776 33.808059, -111.95619 33.808069, -111.956188 33.808327, -111.956172 33.808459, -111.95614 33.80859, -111.956092 33.808718, -111.956021 33.808871, -111.955928 33.809037, -111.955907 33.809074, -111.95718 33.809595, -111.957244 33.809656, -111.957215 33.809489, -111.957802 33.808605, -111.957893 33.808452, -111.957943 33.808368, -111.958293 33.807806, -111.958701 33.80715, -111.959969 33.805107, -111.960225 33.804692, -111.96041 33.804641, -111.960567 33.804597, -111.960459 33.804563, -111.960329 33.804522, -111.960707 33.803925, -111.960484 33.803808, -111.96036 33.803759, -111.960175 33.803701, -111.960025 33.803667, -111.959733 33.803612, -111.959536 33.803586, -111.959483 33.803575, -111.959589 33.803565, -111.959787 33.803565, -111.95998 33.803572, -111.960163 33.80359, -111.960345 33.803613, -111.960844 33.803702, -111.961298 33.802964, -111.961076 33.802887, -111.960992 33.802864, -111.960884 33.802848, -111.960854 33.80283, -111.960832 33.802803, -111.960826 33.802782, -111.960804 33.801783, -111.960814 33.801349, -111.960793 33.800726, -111.960778 33.800533, -111.960775 33.800496, -111.960768 33.80047, -111.960772 33.800431, -111.960789 33.800395, -111.960805 33.800339, -111.960822 33.800097, -111.960821 33.799642, -111.960813 33.799479, -111.960801 33.799403, -111.960774 33.799335, -111.960703 33.799226, -111.960665 33.799189, -111.960816 33.799176, -111.961439 33.799206, -111.96205 33.799219, -111.962766 33.799263, -111.962948 33.799271, -111.963092 33.799272, -111.963589 33.799275, -111.963742 33.799275, -111.964416 33.799277, -111.96485 33.799282, -111.965124 33.799285, -111.965373 33.799288, -111.966324 33.799287, -111.967264 33.799292, -111.967269 33.799148, -111.968361 33.799145, -111.969504 33.799146, -111.970992 33.799146, -111.971182 33.799145, -111.971615 33.799144, -111.971735 33.799144, -111.972888 33.799141, -111.973804 33.799142, -111.974775 33.799144, -111.975503 33.799134, -111.975933 33.799133, -111.976107 33.799133, -111.977244 33.799134, -111.978147 33.79913, -111.978282 33.799129, -111.978389 33.799129, -111.97995 33.799132, -111.982446 33.799123, -111.984171 33.799115, -111.984213 33.799255, -111.984598 33.799252, -111.985397 33.799252, -111.986552 33.799247, -111.986786 33.799246, -111.986917 33.799245, -111.988337 33.79924, -111.990721 33.799231, -111.991349 33.799232, -111.992519 33.799227, -111.993301 33.799216, -111.994224 33.799208, -111.995012 33.799208, -111.995122 33.799209, -111.99568 33.799213, -111.995909 33.799214, -111.996918 33.799214, -111.997223 33.799217, -111.997639 33.799222, -111.997631 33.799094, -111.997313 33.799094, -111.997394 33.799, -111.996044 33.798993, -111.995696 33.798995, -111.995713 33.798858, -111.995732 33.798688, -111.995737 33.798639, -111.995624 33.798284, -111.995696 33.797808, -111.995721 33.797139, -111.995726 33.796566, -111.995665 33.795862, -111.995656 33.795752, -111.995697 33.795502, -111.995902 33.795229, -111.99574 33.794962, -111.995681 33.794764, -111.995627 33.791919, -111.995555 33.786098, -111.995534 33.784358, -111.995526 33.783714, -111.995535 33.783663, -111.995553 33.783406, -111.995551 33.783216, -111.995543 33.783151, -111.995512 33.782984, -111.995515 33.782959, -111.995516 33.782346, -111.99551 33.782309, -111.995487 33.782267, -111.995444 33.782227, -111.995414 33.782214, -111.995428 33.782199, -111.99546 33.782149, -111.995474 33.782096, -111.995473 33.782055, -111.995453 33.781983, -111.995391 33.7818, -111.99538 33.781692, -111.99537 33.781391, -111.99535 33.781028, -111.995361 33.780983, -111.995372 33.780966, -111.995384 33.780948, -111.995437 33.780903, -111.995461 33.780865, -111.995482 33.780778, -111.995498 33.780661, -111.995521 33.780385, -111.995539 33.779931, -111.995532 33.779182, -111.995531 33.779015, -111.995528 33.778736, -111.995523 33.778245, -111.995521 33.778028, -111.995514 33.777351, -111.995519 33.777066, -111.995513 33.775603, -111.995527 33.774687, -111.995511 33.774051, -111.995508 33.773697, -111.995509 33.773638, -111.995509 33.773627, -111.995509 33.773584, -111.99551 33.773522, -111.995514 33.773033, -111.995512 33.772409, -111.995513 33.772032, -111.995515 33.771395, -111.995518 33.770046, -111.995526 33.7699, -111.995534 33.769137, -111.995528 33.768478, -111.995525 33.768101, -111.995531 33.768, -111.995529 33.7661, -111.995524 33.765533, -111.995523 33.763833, -111.995523 33.762766, -111.995523 33.762502, -111.99552 33.760967, -111.995534 33.759729, -111.995521 33.759454, -111.995524 33.758334, -111.995524 33.758159, -111.995519 33.757899, -111.995506 33.75788, -111.995501 33.757824, -111.995467 33.757754, -111.995453 33.757732, -111.99542 33.757691, -111.995361 33.757636, -111.995289 33.75759, -111.995089 33.757505, -111.994979 33.757462, -111.995072 33.757302, -111.995101 33.757229, -111.995114 33.757145, -111.995108 33.756995, -111.995107 33.75694, -111.995112 33.756162, -111.9951 33.756025, -111.99508 33.755993, -111.995045 33.755968, -111.995006 33.755957, -111.994846 33.755956, -111.994364 33.755953, -111.993741 33.755961, -111.993693 33.755978, -111.993662 33.756007, -111.993647 33.756044, -111.993651 33.756455, -111.99366 33.756489, -111.993708 33.75658, -111.993726 33.756644, -111.993718 33.756717, -111.993706 33.756759, -111.993678 33.756818, -111.99361 33.756921, -111.992575 33.756515, -111.991759 33.756182, -111.991179 33.75594, -111.9907 33.755754, -111.991129 33.755041, -111.991846 33.753829, -111.992313 33.75305, -111.994325 33.749755, -111.994533 33.749423, -111.994725 33.749099, -111.995196 33.748334, -111.995435 33.74794, -111.995441 33.747686, -111.995542 33.747521, -111.996467 33.746, -111.998933 33.741966, -111.999252 33.741461, -111.999497 33.741056, -111.99782 33.741037, -111.997233 33.741051, -111.995517 33.741046, -111.995504 33.73987, -111.995505 33.739101, -111.995506 33.737031, -111.995514 33.736032, -111.995513 33.735818, -111.995504 33.734306, -111.995493 33.733956, -111.995546 33.733248, -111.995641 33.731985, -111.995689 33.731807, -111.995679 33.730423, -111.995664 33.730223, -111.995645 33.730007, -111.995707 33.72959, -111.995801 33.729333, -111.995847 33.729061, -111.995845 33.728772, -111.995796 33.728644, -111.995795 33.728532, -111.99565 33.728437, -111.995502 33.728183, -111.995443 33.727834, -111.995512 33.727564, -111.995532 33.72731, -111.99553 33.727199, -111.995684 33.727038, -111.995879 33.72676, -111.996166 33.726638, -111.996458 33.726573, -111.996538 33.726536, -111.996459 33.72653, -111.996464 33.726394, -111.996475 33.726085, -111.996489 33.725704, -111.996513 33.724589, -111.996527 33.723922, -111.99654 33.722916, -111.995848 33.722904, -111.995709 33.722908, -111.995693 33.722908, -111.995398 33.722929, -111.995205 33.722952, -111.994919 33.723001, -111.99482 33.722652, -111.994809 33.722571, -111.994803 33.722347, -111.995022 33.722344, -111.995644 33.722347, -111.995693 33.722357, -111.995748 33.722378, -111.995799 33.7224, -111.995841 33.722402, -111.995879 33.722394, -111.995912 33.722375, -111.995936 33.722348, -111.995949 33.722314, -111.995946 33.722274, -111.995956 33.721605, -111.995974 33.720934, -111.995968 33.720902, -111.995948 33.720874, -111.995917 33.720854, -111.99588 33.720845, -111.995602 33.720858, -111.994767 33.720865, -111.993721 33.720873, -111.993644 33.720888, -111.993569 33.720913, -111.993546 33.720924, -111.993465 33.720974, -111.993424 33.721012, -111.99338 33.721078, -111.993363 33.721126, -111.993299 33.721306, -111.99322 33.721611, -111.993119 33.721564, -111.992508 33.721453, -111.992655 33.720546, -111.991091 33.720441, -111.990735 33.720567, -111.990071 33.720528, -111.990022 33.720508, -111.989968 33.720501, -111.989126 33.720504, -111.988904 33.720505, -111.988646 33.720506, -111.988292 33.720507, -111.987728 33.720509, -111.986825 33.720512, -111.985358 33.720517, -111.985 33.720519, -111.983582 33.720355, -111.979286 33.72052, -111.979288 33.720882, -111.978259 33.720884, -111.978221 33.720892, -111.978188 33.720908, -111.97815 33.720944, -111.978131 33.720986, -111.978135 33.721285, -111.977496 33.721286, -111.977496 33.722951, -111.977496 33.724013, -111.977501 33.724587, -111.977495 33.725671, -111.977499 33.726188, -111.9775 33.726289, -111.977498 33.726411, -111.977497 33.726513, -111.977496 33.726576, -111.977491 33.727222, -111.977488 33.727618, -111.977468 33.728309, -111.977456 33.72905, -111.977479 33.729477, -111.977513 33.72984, -111.97754 33.729997, -111.977674 33.730773, -111.977751 33.731221, -111.977818 33.731597, -111.977916 33.732112, -111.977989 33.732575, -111.97804 33.732931, -111.978103 33.733505, -111.978129 33.733835, -111.97814 33.734311, -111.978151 33.735134, -111.978151 33.735381, -111.978155 33.737483, -111.978158 33.738116, -111.978134 33.739424, -111.978128 33.740794, -111.97773 33.741136, -111.97672 33.741111, -111.976182 33.741111, -111.975252 33.741126, -111.974585 33.74112, -111.973419 33.741125, -111.973337 33.741126, -111.970229 33.741126, -111.969853 33.74113, -111.969586 33.741129, -111.969522 33.741128, -111.969491 33.741128, -111.969432 33.741128, -111.969363 33.741128, -111.968942 33.741126, -111.968572 33.741121, -111.967662 33.741123, -111.967274 33.741119, -111.966995 33.741115, -111.96584 33.741116, -111.965094 33.741121, -111.963539 33.741125, -111.962941 33.741121, -111.961743 33.74111, -111.960735 33.741115, -111.960704 33.741115, -111.960622 33.741116, -111.960628 33.740681, -111.960636 33.740059, -111.960634 33.739782, -111.96063 33.739012, -111.960634 33.738475, -111.96063 33.737245, -111.960634 33.736422, -111.960167 33.736421, -111.960078 33.736408, -111.959983 33.736384, -111.959891 33.736369, -111.96007 33.736341, -111.960178 33.73633, -111.960634 33.736329, -111.960633 33.735234, -111.960639 33.733831, -111.960639 33.733526, -111.960631 33.732916, -111.960614 33.732542, -111.960624 33.732152, -111.960463 33.732107, -111.960305 33.732068, -111.960229 33.732057, -111.96001 33.732037, -111.959834 33.732025, -111.959696 33.732029, -111.959573 33.732042, -111.959259 33.732089, -111.959169 33.732098, -111.959084 33.732097, -111.958971 33.732088, -111.958847 33.732071, -111.958613 33.732028, -111.958469 33.732011, -111.958513 33.731795, -111.958527 33.73169, -111.958533 33.731608, -111.958539 33.731452, -111.958534 33.731348, -111.958528 33.731303, -111.958504 33.731222, -111.958486 33.73116, -111.958462 33.731031, -111.958474 33.730906, -111.958513 33.730718, -111.958511 33.730509, -111.958499 33.730404, -111.958485 33.730345, -111.958465 33.730304, -111.958581 33.730302, -111.95858 33.730014, -111.958578 33.729412, -111.958581 33.729314, -111.958413 33.729125, -111.958331 33.729033, -111.958397 33.728998, -111.958431 33.728972, -111.958466 33.728933, -111.958495 33.728885, -111.958528 33.728811, -111.958524 33.728746, -111.958531 33.728648, -111.958533 33.728566, -111.958522 33.728496, -111.958577 33.728481, -111.958692 33.728458, -111.958871 33.728443, -111.959047 33.72844, -111.959202 33.72843, -111.959462 33.728397, -111.959558 33.728396, -111.959677 33.72841, -111.959789 33.728425, -111.959913 33.728433, -111.960078 33.728424, -111.9603 33.728418, -111.960458 33.728424, -111.960604 33.728439, -111.960572 33.727789, -111.960574 33.727711, -111.960625 33.727338, -111.960631 33.727249, -111.960623 33.727082, -111.960624 33.727004, -111.960624 33.726985, -111.960633 33.726925, -111.960645 33.726883, -111.960669 33.726756, -111.960669 33.726717, -111.960649 33.726595, -111.960627 33.726508, -111.960413 33.72649, -111.960097 33.72648, -111.959979 33.726456, -111.959901 33.726445, -111.959619 33.726432, -111.959496 33.726437, -111.959436 33.726448, -111.959379 33.726464, -111.959304 33.72647, -111.959087 33.726462, -111.958777 33.726458, -111.958642 33.726459, -111.958462 33.72645, -111.957776 33.726468, -111.956352 33.726481, -111.956302 33.726491, -111.956271 33.726504, -111.956186 33.726566, -111.956134 33.726604, -111.956088 33.726623, -111.955418 33.726708, -111.955286 33.726725, -111.955047 33.726742, -111.955019 33.726743, -111.954906 33.726746, -111.954637 33.726742, -111.954154 33.726721, -111.95387 33.726703, -111.953152 33.726638, -111.952089 33.726585, -111.952024 33.726582, -111.951795 33.726569, -111.951116 33.726549, -111.950525 33.726547, -111.950294 33.726556, -111.950097 33.726564, -111.949836 33.726585, -111.949812 33.726587, -111.949624 33.726596, -111.949402 33.72662, -111.949249 33.726647, -111.94896 33.726689, -111.948853 33.726695, -111.948729 33.726688, -111.948186 33.72659, -111.948043 33.726576, -111.947939 33.726574, -111.947912 33.726574, -111.947781 33.726582, -111.947652 33.726599, -111.947452 33.72664, -111.947206 33.726674, -111.946935 33.726698, -111.946666 33.72671, -111.946485 33.72671, -111.943358 33.726668, -111.943375 33.726732, -111.943381 33.726814, -111.943376 33.72687, -111.943376 33.727554, -111.943376 33.727612, -111.943378 33.728516, -111.94337 33.729577, -111.943367 33.73001, -111.943345 33.730283, -111.942335 33.730268, -111.94221 33.730271, -111.941696 33.730269, -111.941578 33.73027, -111.941161 33.73028, -111.941021 33.730286, -111.940856 33.730286, -111.940804 33.730281, -111.940775 33.730279, -111.940506 33.73023, -111.940365 33.730222, -111.9402 33.730226, -111.940049 33.730237, -111.939761 33.730242, -111.939207 33.730259, -111.938929 33.73024, -111.93891 33.73024, -111.938706 33.730237, -111.938543 33.730241, -111.938375 33.730246, -111.93814 33.730246, -111.937843 33.730254, -111.937574 33.730258, -111.937178 33.730254, -111.936422 33.730248, -111.936112 33.730253, -111.935975 33.730265, -111.935898 33.730276, -111.93553 33.730298, -111.93526 33.730303, -111.934539 33.7303, -111.93452 33.730106, -111.93452 33.729961, -111.934561 33.72961, -111.934551 33.729041, -111.934553 33.728764, -111.934541 33.728616, -111.934541 33.728536, -111.934565 33.728362, -111.934577 33.72819, -111.934567 33.728034, -111.934569 33.727041, -111.934586 33.726639, -111.933752 33.726644, -111.932475 33.726643, -111.931876 33.726641, -111.930632 33.726637, -111.93049 33.726644, -111.930293 33.726664, -111.930244 33.726671, -111.930103 33.726701, -111.929933 33.726717, -111.929447 33.726719, -111.928162 33.726723, -111.927942 33.726721, -111.927257 33.726714, -111.92574 33.726699, -111.92561 33.7267, -111.923964 33.726705, -111.92156 33.726706, -111.920463 33.726706, -111.917325 33.726707, -111.916248 33.726708, -111.914994 33.726709, -111.912867 33.726713, -111.910374 33.726715, -111.90863 33.726717, -111.907903 33.726718, -111.905798 33.726719, -111.903823 33.726723, -111.902282 33.726726, -111.901424 33.726727, -111.901424 33.725967, -111.901508 33.725962, -111.901631 33.725939, -111.90171 33.725914, -111.90178 33.725884, -111.901864 33.725831, -111.901943 33.725761, -111.902008 33.725679, -111.902049 33.725603, -111.902308 33.724926, -111.902915 33.723364, -111.902924 33.723333, -111.90294 33.723239, -111.902941 33.723143, -111.902931 33.723059, -111.902893 33.72295, -111.902841 33.722853, -111.902785 33.722789, -111.902703 33.722711, -111.902597 33.722627, -111.902015 33.722246, -111.902676 33.721547, -111.902722 33.721489, -111.902782 33.721397, -111.902831 33.7213, -111.902866 33.721205, -111.902891 33.721088, -111.9029 33.720959, -111.902897 33.720917, -111.902889 33.720817, -111.90287 33.720724, -111.902829 33.72058, -111.902785 33.720466, -111.902722 33.720284, -111.902706 33.7202, -111.902695 33.72008, -111.902698 33.719961, -111.902699 33.719199, -111.902701 33.717932, -111.901496 33.717937, -111.901347 33.717926, -111.901238 33.717909, -111.901098 33.717875, -111.900978 33.717837, -111.900749 33.717742, -111.900079 33.717466, -111.89995 33.717423, -111.899822 33.717393, -111.899691 33.71738, -111.899303 33.717389, -111.899305 33.717003, -111.899295 33.716847, -111.899268 33.7167, -111.899237 33.716591, -111.899183 33.716451, -111.898992 33.716125, -111.898941 33.716004, -111.898911 33.715878, -111.898904 33.715793, -111.898909 33.715513, -111.898916 33.715409, -111.898937 33.715312, -111.898964 33.715238, -111.898996 33.71517, -111.899058 33.715075, -111.899131 33.714989, -111.899711 33.71448, -111.901348 33.715706, -111.902514 33.714647, -111.902831 33.714338, -111.902846 33.714317, -111.902908 33.714221, -111.902962 33.714101, -111.902987 33.714016, -111.902996 33.713975, -111.903006 33.713885, -111.902998 33.713209, -111.904756 33.713209, -111.904829 33.713208, -111.906471 33.71318, -111.907418 33.713181, -111.907915 33.713176, -111.909367 33.713154, -111.910211 33.713141, -111.910919 33.71314, -111.9129 33.713168, -111.915137 33.713168, -111.915723 33.71316, -111.916609 33.713153, -111.916661 33.713089, -111.916661 33.713153, -111.917491 33.713147, -111.919754 33.71315, -111.922425 33.713159, -111.922893 33.713159, -111.925331 33.713158, -111.925331 33.711373, -111.925331 33.711325, -111.925354 33.709632, -111.925351 33.708879, -111.925337 33.708223, -111.925318 33.706887, -111.925303 33.70602, -111.925302 33.705919, -111.9253 33.705761, -111.925291 33.704963, -111.925297 33.704111, -111.925295 33.703164, -111.925292 33.701658, -111.925302 33.701089, -111.925312 33.700346, -111.925316 33.699741, -111.925312 33.699039, -111.925319 33.698715, -111.925338 33.696973, -111.925334 33.695764, -111.925334 33.694839, -111.925336 33.692907, -111.925333 33.691517, -111.925337 33.69143, -111.925343 33.691291, -111.925353 33.690974, -111.925367 33.690693, -111.92537 33.690586, -111.925412 33.689318, -111.925417 33.688463, -111.925413 33.687835, -111.925412 33.68725, -111.925401 33.686046, -111.925396 33.685461, -111.925397 33.684803, -111.925397 33.684491, -111.925397 33.684215, -111.925396 33.683988, -111.925393 33.683075, -111.925395 33.680488, -111.925392 33.679978, -111.925391 33.679831, -111.925409 33.677213, -111.925404 33.676939, -111.925396 33.676479, -111.925375 33.675755, -111.925367 33.674959, -111.92537 33.674438, -111.925364 33.673949, -111.925366 33.673555, -111.92539 33.673286, -111.925388 33.672253, -111.925394 33.671635, -111.925401 33.669818, -111.925401 33.669571, -111.925383 33.667358, -111.925381 33.664868, -111.925379 33.664598, -111.925378 33.664425, -111.925377 33.66414, -111.925379 33.663486, -111.925348 33.661486, -111.925333 33.660048, -111.925335 33.65885, -111.925335 33.658324, -111.92545 33.658348, -111.925563 33.658371, -111.926359 33.658538, -111.926359 33.658281, -111.926358 33.657891, -111.926357 33.657019, -111.926354 33.655256, -111.927776 33.655261, -111.92813 33.655262, -111.929683 33.655262, -111.934132 33.655264, -111.934177 33.655264, -111.934701 33.655258, -111.935159 33.655262, -111.935542 33.655265, -111.935869 33.655277, -111.936211 33.655302, -111.936806 33.655365, -111.93796 33.655535, -111.938502 33.655626, -111.938886 33.655699, -111.939522 33.65582, -111.939918 33.655904, -111.940359 33.656008, -111.940743 33.656098, -111.941685 33.656341, -111.942064 33.656454, -111.942697 33.656639, -111.943514 33.656903, -111.943982 33.657062, -111.944507 33.657252, -111.945054 33.657466, -111.945678 33.657724, -111.946419 33.658042, -111.946827 33.658228, -111.947247 33.658431, -111.948056 33.65884, -111.948516 33.659082, -111.948875 33.659283, -111.949184 33.659456, -111.949399 33.659572, -111.949782 33.659807, -111.950754 33.66042, -111.951556 33.660968, -111.952719 33.661789, -111.953009 33.662017, -111.95303 33.662034, -111.953374 33.662293, -111.953601 33.662479, -111.953847 33.662651, -111.954021 33.662759, -111.954163 33.662839, -111.954475 33.662994, -111.954776 33.663121, -111.955108 33.663239, -111.955272 33.663292, -111.955429 33.663335, -111.955674 33.663389, -111.955739 33.663166, -111.955772 33.663047, -111.955783 33.662942, -111.955781 33.662589, -111.955766 33.662353, -111.955762 33.662125, -111.955763 33.660989, -111.955764 33.660485, -111.956016 33.660569, -111.956996 33.660924, -111.957085 33.660933, -111.95715 33.660916, -111.957191 33.660908, -111.957256 33.660876, -111.957305 33.660843, -111.957329 33.660794, -111.957378 33.660746, -111.958061 33.659346, -111.959179 33.659348, -111.959932 33.659345, -111.960095 33.659344, -111.960095 33.659235, -111.960094 33.658993, -111.960093 33.658448, -111.960091 33.657353, -111.960088 33.656854, -111.960091 33.65679, -111.960092 33.656629, -111.960082 33.656418, -111.960062 33.656254, -111.960052 33.656178, -111.960007 33.655946, -111.959944 33.655713, -111.959849 33.655445, -111.959785 33.655296, -111.959709 33.655142, -111.959597 33.654939, -111.959297 33.65445, -111.959096 33.654149, -111.958899 33.65382, -111.958741 33.653476, -111.958653 33.653235, -111.958589 33.653018, -111.95853 33.652757, -111.958495 33.652522, -111.958488 33.652288, -111.958494 33.652068, -111.958519 33.651788, -111.958544 33.651631, -111.958594 33.651395, -111.958679 33.651096, -111.958758 33.65087, -111.958855 33.650651, -111.958942 33.650463, -111.95902 33.650516, -111.959658 33.650796, -111.960761 33.651211, -111.961952 33.651659, -111.963615 33.652065, -111.963697 33.652091, -111.966778 33.653076, -111.968157 33.653485, -111.968271 33.653519, -111.976883 33.656075, -111.977389 33.656264, -111.978442 33.656657, -111.9814 33.65758, -111.983327 33.658244, -111.987016 33.659386, -111.990554 33.660611, -111.993065 33.661368, -111.993539 33.661511, -111.993978 33.661653, -111.994407 33.661791, -111.994519 33.661845, -111.994624 33.661895, -111.995661 33.662191, -111.995866 33.66225, -111.996202 33.662346, -111.996284 33.662375, -111.998925 33.663295, -111.999506 33.663525, -111.999497 33.663812, -111.999496 33.663853, -111.999529 33.66576, -111.999431 33.666637, -111.999221 33.668489, -111.999147 33.668958, -111.99906 33.669353, -111.9989 33.669785, -111.998616 33.670217, -111.998295 33.670538, -111.998011 33.670822, -111.997554 33.671094, -111.997011 33.671378, -111.99648 33.671538, -111.995623 33.671671, -111.995616 33.671929, -111.995612 33.67204, -111.995611 33.672108, -111.99561 33.672165, -111.995606 33.672326, -111.995594 33.672775, -111.99552 33.675639, -111.995475 33.676974, -111.995755 33.677054, -111.9957 33.677153, -111.995517 33.677457, -111.995456 33.677547, -111.995386 33.67765, -111.9953 33.677765, -111.995 33.678113, -111.994801 33.67832, -111.99457 33.678539, -111.993394 33.679568, -111.991919 33.680859, -111.991786 33.680977, -111.991383 33.681356, -111.991206 33.681533, -111.991047 33.681699, -111.990806 33.681962, -111.990565 33.682252, -111.990421 33.682426, -111.990213 33.6827, -111.989983 33.683022, -111.989802 33.683296, -111.989263 33.68412, -111.988897 33.684694, -111.98885 33.68477, -111.988783 33.684867, -111.988176 33.684544, -111.987629 33.684259, -111.987106 33.683999, -111.986699 33.683801, -111.986149 33.683543, -111.985749 33.683367, -111.985006 33.683046, -111.984507 33.682841, -111.984286 33.682753, -111.984079 33.68267, -111.983283 33.682367, -111.983015 33.682272, -111.982635 33.682136, -111.982177 33.681981, -111.981398 33.681726, -111.980912 33.681579, -111.980402 33.68143, -111.97987 33.68128, -111.979699 33.681235, -111.979079 33.681064, -111.978811 33.680991, -111.978714 33.680962, -111.978727 33.680921, -111.978842 33.680547, -111.978973 33.680392, -111.979019 33.680121, -111.979108 33.67901, -111.979112 33.678767, -111.978017 33.678757, -111.978104 33.678374, -111.978087 33.678358, -111.978044 33.678338, -111.977369 33.678319, -111.977374 33.678169, -111.977382 33.677644, -111.9774 33.677114, -111.977397 33.67677, -111.975253 33.676752, -111.974706 33.676747, -111.974715 33.676675, -111.974712 33.67658, -111.974694 33.67648, -111.974655 33.676371, -111.974617 33.676301, -111.974547 33.676206, -111.974458 33.676119, -111.974391 33.676068, -111.974279 33.676004, -111.974159 33.675957, -111.974052 33.675931, -111.973942 33.675917, -111.973822 33.67591, -111.973122 33.675829, -111.972938 33.675794, -111.972823 33.675765, -111.97058 33.675054, -111.970422 33.675016, -111.970129 33.674967, -111.970087 33.674974, -111.970067 33.674986, -111.969996 33.675131, -111.969919 33.675323, -111.969799 33.675722, -111.969701 33.675996, -111.969696 33.676028, -111.969611 33.676058, -111.96946 33.676051, -111.969399 33.676497, -111.969363 33.676837, -111.969324 33.677268, -111.969273 33.67754, -111.969102 33.678105, -111.968961 33.678054, -111.968468 33.677875, -111.967808 33.677668, -111.967588 33.6776, -111.966724 33.677333, -111.966666 33.677315, -111.964748 33.676725, -111.964571 33.676669, -111.964364 33.676604, -111.963706 33.676408, -111.963452 33.676342, -111.963122 33.676274, -111.962821 33.676223, -111.962646 33.676199, -111.96247 33.67618, -111.960162 33.676117, -111.959958 33.676112, -111.959978 33.677036, -111.959943 33.678225, -111.959892 33.678433, -111.959678 33.679076, -111.959561 33.679369, -111.959518 33.679466, -111.9591 33.680192, -111.958983 33.680357, -111.958858 33.680498, -111.958549 33.680828, -111.958473 33.68091, -111.95814 33.681256, -111.957754 33.681708, -111.956888 33.68261, -111.956463 33.683064, -111.95593 33.683634, -111.955189 33.684445, -111.954324 33.685372, -111.953535 33.686203, -111.953035 33.686736, -111.952768 33.687023, -111.952529 33.687271, -111.952436 33.687379, -111.952069 33.68781, -111.951849 33.688092, -111.951608 33.688418, -111.951475 33.688624, -111.951348 33.688848, -111.951246 33.689055, -111.950988 33.689638, -111.950839 33.690116, -111.950734 33.690564, -111.950689 33.690826, -111.950661 33.691051, -111.950657 33.691111, -111.950633 33.691458, -111.950633 33.691735, -111.9507 33.692377, -111.950751 33.692689, -111.950838 33.693081, -111.950936 33.693533, -111.951026 33.693874, -111.951189 33.694692, -111.951249 33.695066, -111.951288 33.695497, -111.951321 33.696077, -111.951332 33.696571, -111.951327 33.696789, -111.95133 33.697092, -111.951343 33.697526, -111.951313 33.698732, -111.953965 33.698731, -111.95427 33.6984, -111.954227 33.698731, -111.955934 33.69873, -111.960024 33.698731, -111.963696 33.698726, -111.964508 33.698729, -111.965377 33.698733, -111.968609 33.698734, -111.968725 33.698729, -111.968813 33.698726, -111.969354 33.698729, -111.971297 33.698722, -111.97161 33.698721, -111.973102 33.698731, -111.974745 33.698725, -111.975723 33.698725, -111.976178 33.698726, -111.977729 33.698722, -111.979443 33.698728, -111.979817 33.698728, -111.980093 33.698727, -111.981458 33.698726, -111.98267 33.698726, -111.982851 33.698726, -111.984442 33.698731, -111.985701 33.698727, -111.986609 33.698731, -111.987822 33.698736, -111.991755 33.698742, -111.992803 33.698743, -111.99618 33.698749, -111.996671 33.698734, -111.99791 33.698711, -111.99828 33.698711, -111.998635 33.698711, -111.99873 33.698712, -111.998952 33.698714, -111.999978 33.69871, -112.000707 33.698717, -112.00147 33.698745, -112.002781 33.698747, -112.003578 33.698751, -112.006749 33.698755, -112.007142 33.698754, -112.008064 33.69875, -112.009521 33.698751, -112.010776 33.698746, -112.013882 33.698743, -112.01388 33.698397, -112.013896 33.698286, -112.013905 33.698146, -112.013908 33.697928, -112.013907 33.696581, -112.013891 33.695602, -112.013876 33.695007, -112.013864 33.694743, -112.013859 33.693946, -112.013849 33.693381, -112.013833 33.69292, -112.013832 33.692221, -112.013845 33.691927, -112.013839 33.691725, -112.013796 33.69036, -112.013937 33.688616, -112.014592 33.687928, -112.014847 33.687038, -112.014592 33.686043, -112.013835 33.685068, -112.013796 33.684087, -112.016554 33.68419, -112.016637 33.684192, -112.017438 33.684213, -112.017841 33.684234, -112.018325 33.684276, -112.018827 33.684356, -112.019329 33.684468, -112.020247 33.684759, -112.020693 33.684956, -112.021216 33.685226, -112.022203 33.685777, -112.022497 33.685946, -112.02289 33.686171, -112.023318 33.686403, -112.023734 33.686602, -112.024293 33.686829, -112.025058 33.687083, -112.025578 33.687208, -112.025889 33.687285, -112.026677 33.687419, -112.027733 33.6875, -112.028354 33.6875, -112.029355 33.687489, -112.031132 33.687532, -112.031134 33.687658, -112.031154 33.689194, -112.031143 33.689747, -112.031094 33.690218, -112.031047 33.690539, -112.030999 33.690785, -112.030949 33.691003, -112.030861 33.691319, -112.030786 33.691535, -112.030751 33.691634, -112.030587 33.692053, -112.030381 33.692478, -112.030108 33.692955, -112.02974 33.693509, -112.028991 33.694594, -112.028035 33.695972, -112.026927 33.697575, -112.025721 33.69932, -112.025688 33.699368, -112.025036 33.700306, -112.024336 33.701323, -112.023638 33.702327, -112.023725 33.702408, -112.023335 33.702971, -112.022529 33.704141, -112.022224 33.704585, -112.021687 33.705346, -112.021232 33.706014, -112.020879 33.706545, -112.020322 33.707408, -112.019066 33.709426, -112.017723 33.711583, -112.017606 33.71177, -112.015567 33.715051, -112.015334 33.715422, -112.015275 33.715516, -112.013649 33.718142, -112.012353 33.720254, -112.011634 33.721418, -112.011707 33.721444, -112.011821 33.72147, -112.0119 33.721478, -112.012503 33.721473, -112.015621 33.721479, -112.018738 33.721471, -112.019662 33.721463, -112.020561 33.721461, -112.021806 33.721469, -112.024291 33.721463, -112.025042 33.721468, -112.024734 33.721056, -112.024441 33.720627, -112.023922 33.719885, -112.023402 33.719095, -112.023076 33.718437, -112.022782 33.717812, -112.022584 33.717171, -112.022531 33.716646, -112.022607 33.716137, -112.022735 33.715472, -112.02279 33.715184, -112.023181 33.714214, -112.023828 33.713405, -112.02481 33.712575, -112.025856 33.711907, -112.02676 33.711388, -112.028028 33.710504, -112.028724 33.709858, -112.029024 33.709511, -112.030926 33.709456, -112.030952 33.709455, -112.031216 33.709447, -112.034772 33.709574, -112.044293 33.709915, -112.044414 33.709942, -112.04454 33.70996, -112.044738 33.709975, -112.044973 33.70998, -112.045171 33.709973, -112.045357 33.709952, -112.045755 33.709867, -112.045883 33.709853, -112.045893 33.709649, -112.045998 33.709474, -112.046123 33.709391, -112.046399 33.709314, -112.046472 33.709276, -112.046735 33.708995, -112.046847 33.708435, -112.046952 33.708215, -112.046899 33.707956, -112.046952 33.707385, -112.046933 33.70717, -112.046985 33.707033, -112.047012 33.706725, -112.04709 33.706433, -112.047222 33.705999, -112.047696 33.705263, -112.047696 33.705224, -112.048027 33.704832, -112.048049 33.698473, -112.048047 33.698252, -112.048223 33.694011, -112.048229 33.693876, -112.048241 33.693691, -112.048458 33.690197, -112.048472 33.689967, -112.048462 33.689638, -112.048477 33.688598, -112.048477 33.688156, -112.048477 33.687845, -112.048498 33.687037, -112.048501 33.686958, -112.048485 33.686212, -112.048487 33.68619, -112.048505 33.686099, -112.048547 33.68599, -112.048525 33.685792, -112.048526 33.685411, -112.048518 33.685359, -112.048496 33.685309, -112.048463 33.685267, -112.048529 33.685279, -112.048633 33.685298, -112.048865 33.68536, -112.048932 33.685381, -112.048988 33.685415, -112.049124 33.685536, -112.049147 33.685529, -112.04925 33.685509, -112.049333 33.685507, -112.049437 33.685519, -112.049524 33.685545, -112.050033 33.685703, -112.05009 33.685717, -112.05011 33.685722, -112.050192 33.685727, -112.050259 33.685721, -112.05034 33.685701, -112.050418 33.68567, -112.050488 33.685627, -112.050533 33.68559, -112.050667 33.685437, -112.050753 33.685353, -112.050869 33.685259, -112.050997 33.685174, -112.051127 33.685116, -112.05133 33.685038, -112.05154 33.684973, -112.051794 33.684857, -112.052207 33.684651, -112.052535 33.684499, -112.052651 33.684458, -112.052892 33.684386, -112.053135 33.684329, -112.055343 33.684221, -112.055701 33.684155, -112.056402 33.684103, -112.057888 33.684098, -112.061731 33.683977, -112.064896 33.683899, -112.065644 33.683889, -112.065614 33.684573, -112.065566 33.685414, -112.065537 33.686172, -112.065506 33.68654, -112.065426 33.687123, -112.065411 33.687262, -112.065397 33.687551, -112.065399 33.687644, -112.065432 33.687977, -112.065503 33.688354, -112.065547 33.688683, -112.06557 33.688929, -112.065578 33.689413, -112.065577 33.690585, -112.065577 33.691124, -112.065564 33.692429, -112.06555 33.693286, -112.065528 33.694045, -112.065515 33.694239, -112.065495 33.694382, -112.065442 33.694636, -112.06541 33.694777, -112.06508 33.696072, -112.065021 33.696274, -112.06499 33.69638, -112.064937 33.696565, -112.064913 33.69667, -112.065057 33.696793, -112.0651 33.696842, -112.06517 33.69694, -112.065189 33.696978, -112.065194 33.697021, -112.06519 33.697054, -112.065102 33.697311, -112.065077 33.697421, -112.065073 33.697509, -112.065081 33.697581, -112.065105 33.697667, -112.065146 33.697764, -112.065236 33.697935, -112.065436 33.698208, -112.065506 33.698276, -112.065591 33.698333, -112.065654 33.69836, -112.065714 33.698378, -112.065776 33.698397, -112.065895 33.69842, -112.066014 33.698429, -112.066672 33.698414, -112.067079 33.698412, -112.068119 33.6984, -112.068159 33.698372, -112.0682 33.698341, -112.076249 33.698222, -112.079751 33.698171, -112.079969 33.698184, -112.08007 33.698164, -112.080294 33.698071, -112.080879 33.697933, -112.081537 33.697856, -112.081728 33.697867, -112.082168 33.697817, -112.083127 33.697855, -112.083109 33.697832, -112.083074 33.697812, -112.083055 33.697797, -112.083042 33.697775, -112.083012 33.697533, -112.082993 33.697166, -112.082988 33.696678, -112.082994 33.696368, -112.083022 33.69598, -112.083024 33.695626, -112.083012 33.69504, -112.083011 33.694605, -112.082994 33.694115, -112.082995 33.69337, -112.082983 33.692814, -112.082972 33.690931, -112.091743 33.691094, -112.091729 33.691138, -112.091725 33.691177, -112.09174 33.692626, -112.091756 33.693774, -112.091763 33.694588, -112.091743 33.694802, -112.091735 33.695165, -112.091736 33.695298, -112.093451 33.695318, -112.094224 33.695318, -112.095029 33.695315, -112.096578 33.695318, -112.097909 33.695321, -112.09799 33.695318, -112.098111 33.695302, -112.098426 33.695231, -112.099055 33.694977, -112.099199 33.69493, -112.099359 33.694893, -112.099528 33.694867, -112.09974 33.69485, -112.099906 33.694849, -112.099898 33.695044, -112.099901 33.695427, -112.099902 33.695637, -112.099906 33.695752, -112.099939 33.696641, -112.09994 33.696798, -112.099954 33.698099, -112.09996 33.698175, -112.099966 33.698248, -112.09998 33.6983, -112.099989 33.698353, -112.09998 33.698464, -112.099977 33.701424, -112.099977 33.701826, -112.099977 33.702011, -112.099982 33.70352, -112.099982 33.703557, -112.099958 33.704593, -112.09996 33.704756, -112.099942 33.70507, -112.099951 33.705501, -112.099953 33.705584, -112.099961 33.706021, -112.099963 33.706284, -112.099972 33.707397, -112.099978 33.707957, -112.099978 33.709321, -112.099978 33.709526, -112.099984 33.71098, -112.099776 33.710985, -112.099555 33.710989, -112.099181 33.710998, -112.098408 33.711015, -112.098265 33.711018, -112.096556 33.711032, -112.095944 33.711027, -112.09594 33.711226, -112.095914 33.712571, -112.095923 33.712756, -112.095929 33.712877, -112.095827 33.712878, -112.095464 33.712882, -112.094494 33.712894, -112.093832 33.712903, -112.093769 33.712904, -112.093516 33.712907, -112.092857 33.712914, -112.092273 33.712921, -112.091267 33.712933, -112.09129 33.713018, -112.091292 33.713041, -112.091302 33.713147, -112.091303 33.713172, -112.091318 33.713559, -112.091331 33.713901, -112.091365 33.714427, -112.091323 33.714605, -112.091262 33.714744, -112.090158 33.714754, -112.090115 33.714759, -112.090165 33.71566, -112.090169 33.715724, -112.090495 33.715699, -112.091122 33.715651, -112.091367 33.715632, -112.091418 33.716919, -112.091478 33.718165, -112.091489 33.718384, -112.091339 33.71839, -112.091208 33.718404, -112.090927 33.718406, -112.090912 33.718406, -112.090248 33.718414, -112.089877 33.718414, -112.089345 33.718413, -112.089317 33.71756, -112.089313 33.717478, -112.089306 33.717314, -112.089226 33.717437, -112.087256 33.717466, -112.087107 33.717354, -112.08708 33.7166, -112.087693 33.716593, -112.089253 33.716579, -112.089245 33.716455, -112.089245 33.716229, -112.089236 33.715925, -112.089238 33.715809, -112.08924 33.715798, -112.089075 33.715779, -112.088875 33.715775, -112.088721 33.715837, -112.088601 33.715903, -112.088397 33.715945, -112.088196 33.715957, -112.087959 33.715934, -112.087824 33.715901, -112.087722 33.715877, -112.087411 33.715862, -112.087023 33.715933, -112.087025 33.715508, -112.087002 33.714795, -112.086961 33.713874, -112.086945 33.713413, -112.086943 33.713181, -112.086944 33.713125, -112.086946 33.713087, -112.086946 33.713073, -112.08566 33.713077, -112.084787 33.713087, -112.082666 33.713103, -112.082545 33.713101, -112.08254 33.713197, -112.082511 33.71378, -112.082487 33.713756, -112.08242 33.713685, -112.082329 33.713568, -112.082225 33.713399, -112.082165 33.713328, -112.082138 33.713303, -112.081891 33.713481, -112.081467 33.713003, -112.080968 33.713, -112.080801 33.712999, -112.080578 33.713174, -112.080427 33.713293, -112.08031 33.713374, -112.080361 33.713504, -112.080393 33.713562, -112.080461 33.713647, -112.080633 33.713816, -112.081049 33.714186, -112.081417 33.714534, -112.081478 33.714596, -112.081751 33.714872, -112.081992 33.715153, -112.082195 33.715414, -112.082407 33.715703, -112.08243 33.715741, -112.082533 33.715909, -112.082586 33.716004, -112.082691 33.716191, -112.082703 33.716212, -112.082738 33.716306, -112.082756 33.716354, -112.08278 33.716447, -112.08279 33.71656, -112.082786 33.716761, -112.082793 33.717282, -112.082804 33.717696, -112.082837 33.718173, -112.082859 33.719088, -112.082899 33.720172, -112.082904 33.72031, -112.082436 33.720327, -112.081918 33.720312, -112.081723 33.720302, -112.081439 33.720301, -112.080578 33.720288, -112.080076 33.72028, -112.079293 33.720274, -112.079077 33.720269, -112.07843 33.720256, -112.078053 33.720253, -112.077834 33.720252, -112.077069 33.720253, -112.075451 33.720218, -112.07464 33.720218, -112.07436 33.720214, -112.072841 33.720186, -112.072805 33.720186, -112.072232 33.720182, -112.071989 33.720179, -112.071571 33.720175, -112.070779 33.720166, -112.07013 33.720167, -112.070128 33.721227, -112.070128 33.721278, -112.070125 33.721328, -112.070122 33.721374, -112.070122 33.721426, -112.070121 33.721478, -112.070119 33.721524, -112.07012 33.721572, -112.070118 33.721622, -112.070117 33.721673, -112.070122 33.721718, -112.070127 33.721768, -112.070131 33.721815, -112.07012 33.721861, -112.070094 33.721903, -112.070064 33.721944, -112.070032 33.722029, -112.069687 33.722025, -112.069402 33.722025, -112.06883 33.722026, -112.068045 33.722017, -112.068032 33.722101, -112.068032 33.722197, -112.068037 33.72229, -112.068028 33.722395, -112.068009 33.722507, -112.06799 33.722586, -112.067985 33.722968, -112.067968 33.72335, -112.067971 33.723653, -112.067987 33.723761, -112.068016 33.723869, -112.068011 33.723907, -112.068 33.72393, -112.067978 33.723936, -112.067966 33.72395, -112.068553 33.723933, -112.070943 33.723866, -112.070903 33.723897, -112.070886 33.723941, -112.070878 33.72404, -112.070886 33.724197, -112.070929 33.724521, -112.070946 33.724626, -112.070953 33.724929, -112.070982 33.725213, -112.070982 33.725316, -112.070965 33.725396, -112.070946 33.725446, -112.070913 33.725492, -112.070879 33.725522, -112.07084 33.725538, -112.070509 33.725535, -112.070474 33.725545, -112.070429 33.725576, -112.070395 33.725618, -112.070382 33.725646, -112.070326 33.725818, -112.070302 33.725894, -112.070252 33.72602, -112.070235 33.726037, -112.070206 33.72605, -112.070181 33.726054, -112.069535 33.726058, -112.06948 33.726136, -112.071289 33.72605, -112.072386 33.726017, -112.07337 33.725952, -112.074287 33.725776, -112.07518 33.725606, -112.076259 33.725635, -112.078663 33.725677, -112.080847 33.725746, -112.080842 33.725966, -112.081061 33.725966, -112.081773 33.725965, -112.082701 33.725988, -112.083 33.72597, -112.083045 33.72595, -112.083075 33.725923, -112.083094 33.725888, -112.083098 33.725837, -112.083098 33.725788, -112.083098 33.725715, -112.083099 33.725524, -112.083091 33.725363, -112.083273 33.725363, -112.083626 33.72536, -112.085233 33.725346, -112.085235 33.725398, -112.085239 33.725649, -112.085239 33.725715, -112.085263 33.725716, -112.086102 33.725707, -112.086351 33.725703, -112.08656 33.725702, -112.08659 33.725704, -112.087443 33.725693, -112.087443 33.725739, -112.087444 33.725799, -112.087459 33.725844, -112.087479 33.725869, -112.087557 33.725911, -112.087805 33.725913, -112.088568 33.725899, -112.088943 33.725893, -112.089093 33.725891, -112.089544 33.725888, -112.089979 33.725885, -112.090046 33.725885, -112.090567 33.72588, -112.090583 33.725899, -112.091716 33.725887, -112.094098 33.72583, -112.09489 33.725775, -112.095189 33.725754, -112.095451 33.72574, -112.095952 33.726107, -112.096219 33.726302, -112.097648 33.727386, -112.098576 33.728094, -112.09898 33.728403, -112.099395 33.728734, -112.099538 33.728861, -112.099589 33.728906, -112.099772 33.729086, -112.099949 33.729281, -112.100649 33.730101, -112.101658 33.731303, -112.102485 33.732288, -112.102665 33.732502, -112.103385 33.73336, -112.103396 33.733373, -112.104777 33.735029, -112.106584 33.737217, -112.10697 33.737684, -112.10806 33.738974, -112.108852 33.73993, -112.109172 33.740275, -112.108787 33.740283, -112.10867 33.740286, -112.10849 33.740278, -112.108121 33.740261, -112.107885 33.740297, -112.107609 33.740351, -112.107261 33.740484, -112.10695 33.740699, -112.106793 33.740868, -112.106723 33.740963, -112.106611 33.741117, -112.107029 33.741348, -112.107404 33.741624, -112.107689 33.741901, -112.107813 33.742014, -112.108128 33.742535, -112.108214 33.742863, -112.108259 33.743085, -112.108294 33.743353, -112.108294 33.743647, -112.108294 33.743869, -112.107914 33.743899, -112.106942 33.743892, -112.106521 33.743892, -112.105268 33.743871, -112.10464 33.743694, -112.103954 33.743641, -112.103541 33.743654, -112.103165 33.743626, -112.103132 33.744168, -112.103146 33.744439, -112.103141 33.744564, -112.103118 33.744768, -112.102859 33.745321, -112.102615 33.745859, -112.102635 33.746205, -112.102647 33.746342, -112.103019 33.746896, -112.103505 33.74729, -112.103813 33.747533, -112.104001 33.747711, -112.10415 33.747912, -112.104255 33.748132, -112.104314 33.748364, -112.104344 33.748538, -112.104304 33.748746, -112.103643 33.748684, -112.103643 33.748561, -112.103627 33.748457, -112.103616 33.748409, -112.103602 33.748361, -112.103538 33.748235, -112.103495 33.74817, -112.103462 33.748126, -112.103431 33.748089, -112.103381 33.748033, -112.103345 33.747981, -112.103238 33.748006, -112.103103 33.748087, -112.102977 33.748169, -112.102934 33.748198, -112.102757 33.748315, -112.10271 33.748344, -112.102666 33.748372, -112.102619 33.748401, -112.102572 33.748429, -112.102527 33.748456, -112.102481 33.748482, -112.102436 33.748508, -112.102389 33.748533, -112.102342 33.748556, -112.102293 33.748577, -112.102243 33.748596, -112.102088 33.748647, -112.101979 33.748674, -112.101922 33.748686, -112.101865 33.748697, -112.101816 33.748704, -112.101628 33.74873, -112.101507 33.748734, -112.101446 33.748732, -112.101325 33.748721, -112.101265 33.748714, -112.101091 33.7487, -112.100984 33.748698, -112.100885 33.748701, -112.100778 33.748723, -112.100753 33.748763, -112.100825 33.749082, -112.100895 33.749633, -112.100943 33.750013, -112.10096 33.750262, -112.100885 33.750695, -112.101361 33.750762, -112.101572 33.750791, -112.101707 33.750772, -112.10187 33.750728, -112.101954 33.75067, -112.102072 33.750579, -112.102314 33.750722, -112.102463 33.750782, -112.102542 33.750799, -112.102641 33.750813, -112.102708 33.750814, -112.103448 33.750835, -112.103582 33.750823, -112.103681 33.750787, -112.103739 33.750751, -112.103791 33.750704, -112.103892 33.750575, -112.103974 33.750435, -112.104075 33.750306, -112.104204 33.750176, -112.10431 33.750094, -112.104473 33.749975, -112.104772 33.750282, -112.104927 33.750382, -112.105194 33.750507, -112.10504 33.750829, -112.104944 33.751026, -112.10521 33.751097, -112.105471 33.751139, -112.105726 33.751198, -112.10602 33.75123, -112.106462 33.751252, -112.106454 33.751075, -112.10638 33.750563, -112.106826 33.750506, -112.107116 33.750449, -112.107192 33.75042, -112.10768 33.750367, -112.108296 33.750352, -112.10832 33.752021, -112.10832 33.752914, -112.108295 33.754256, -112.108283 33.75477, -112.108319 33.755158, -112.108336 33.755507, -112.108378 33.756341, -112.10837 33.757847, -112.108362 33.759237, -112.10837 33.759593, -112.108378 33.759742, -112.10842 33.759883, -112.108494 33.760189, -112.108585 33.760404, -112.108693 33.760636, -112.108867 33.760884, -112.109049 33.761124, -112.109347 33.761398, -112.109669 33.761646, -112.110067 33.761828, -112.110277 33.761933, -112.110293 33.761859, -112.110301 33.761823, -112.110294 33.760839, -112.11034 33.759871, -112.110267 33.759173, -112.110141 33.758503, -112.110102 33.758354, -112.10995 33.758074, -112.109871 33.757799, -112.109858 33.757563, -112.109943 33.757073, -112.110009 33.756919, -112.110193 33.756628, -112.110398 33.7564, -112.109888 33.756408, -112.109986 33.75588, -112.110179 33.7553, -112.110352 33.754821, -112.110443 33.754568, -112.11051 33.754383, -112.110784 33.753948, -112.111003 33.753719, -112.111283 33.753271, -112.111332 33.753192, -112.111469 33.752001, -112.111715 33.751497, -112.111907 33.751359, -112.112417 33.750037, -112.112452 33.749829, -112.112612 33.749653, -112.112692 33.749477, -112.11274 33.749221, -112.11274 33.748965, -112.112772 33.748645, -112.112868 33.748277, -112.112996 33.747909, -112.113172 33.747525, -112.113316 33.746981, -112.113412 33.746645, -112.113438 33.746525, -112.113425 33.746375, -112.113457 33.746172, -112.113488 33.746008, -112.113499 33.745951, -112.114119 33.746697, -112.114757 33.747274, -112.115317 33.747565, -112.115949 33.747801, -112.116344 33.747911, -112.117087 33.747949, -112.121411 33.747342, -112.124248 33.746898, -112.124452 33.746866, -112.12477 33.746816, -112.124977 33.746783, -112.125098 33.747342, -112.125223 33.747321, -112.125277 33.747305, -112.125349 33.747271, -112.125415 33.747219, -112.125655 33.746932, -112.125701 33.746889, -112.12574 33.746866, -112.125808 33.746841, -112.126238 33.746763, -112.126659 33.746652, -112.12689 33.746583, -112.127102 33.746506, -112.127309 33.746418, -112.127452 33.746348, -112.127659 33.746232, -112.128281 33.745838, -112.128954 33.745262, -112.129493 33.744801, -112.131001 33.743539, -112.131383 33.743249, -112.131595 33.743121, -112.132 33.742899, -112.132048 33.742877, -112.132352 33.742755, -112.132612 33.742668, -112.132875 33.742594, -112.133781 33.742392, -112.134741 33.742177, -112.135502 33.74202, -112.136608 33.741798, -112.137683 33.74157, -112.140974 33.740894, -112.145886 33.739872, -112.148542 33.739329, -112.149155 33.73921, -112.149498 33.739144, -112.151071 33.738811, -112.15281 33.738461, -112.154315 33.738142, -112.155233 33.737969, -112.155535 33.73793, -112.155755 33.737912, -112.156031 33.737904, -112.156304 33.737912, -112.156653 33.737939, -112.156961 33.73798, -112.159023 33.73823, -112.159794 33.73833, -112.159872 33.738337, -112.160186 33.738374, -112.160516 33.738425, -112.160738 33.73845, -112.161034 33.738471, -112.161302 33.73848, -112.161613 33.738476, -112.161884 33.73846, -112.162193 33.738428, -112.162448 33.738384, -112.1627 33.738329, -112.162917 33.738272, -112.163161 33.738197, -112.165477 33.737347, -112.166197 33.737091, -112.167423 33.736664, -112.168558 33.736254, -112.16864 33.736218, -112.168748 33.736186, -112.168904 33.736165, -112.169148 33.736123, -112.169341 33.736079, -112.169693 33.73598, -112.169891 33.735918, -112.169885 33.735622, -112.172684 33.734628, -112.172737 33.734601, -112.173073 33.734447, -112.173678 33.734287, -112.17502 33.734211, -112.176264 33.734574, -112.176711 33.734788, -112.178639 33.735431, -112.182238 33.736718, -112.183699 33.737262, -112.186643 33.738358, -112.188746 33.739142, -112.190569 33.739719, -112.191286 33.740082, -112.191576 33.740302, -112.198295 33.746969, -112.199932 33.74845, -112.201792 33.749999, -112.202605 33.750489, -112.202417 33.750558, -112.202328 33.750591, -112.202384 33.750769, -112.202482 33.751078, -112.203006 33.751367, -112.203143 33.751693, -112.203155 33.751692, -112.203364 33.75168, -112.20354 33.7519, -112.203552 33.751809, -112.203551 33.751573, -112.203551 33.751448, -112.203547 33.751338, -112.20359 33.751377, -112.203601 33.751386, -112.203624 33.751407, -112.203815 33.751577, -112.2046 33.752277, -112.205654 33.753208, -112.206735 33.754173, -112.207677 33.755007, -112.20859 33.75582, -112.208787 33.755996, -112.209408 33.756549, -112.209634 33.756751, -112.209908 33.756995, -112.210952 33.757905, -112.212027 33.758874, -112.213088 33.759806, -112.214165 33.760766, -112.215409 33.761892, -112.215735 33.762205, -112.215792 33.76226, -112.216163 33.762637, -112.21649 33.762986, -112.216741 33.763269, -112.216986 33.763558, -112.217289 33.763939, -112.217525 33.764255, -112.217672 33.764464, -112.21773 33.764547, -112.217871 33.764747, -112.217936 33.764839, -112.217943 33.76485, -112.218202 33.765247, -112.218285 33.765381, -112.218934 33.766538, -112.219551 33.767662, -112.220188 33.768851, -112.221 33.770279, -112.221019 33.770318, -112.221042 33.770406, -112.221042 33.770495, -112.221022 33.77058, -112.221089 33.770502, -112.221153 33.770448, -112.221168 33.770435, -112.221258 33.770379, -112.22136 33.770334, -112.221479 33.770301, -112.221599 33.770285, -112.221689 33.770283, -112.221813 33.770296, -112.221921 33.770321, -112.222046 33.770367, -112.222345 33.770488, -112.22281 33.770703, -112.223101 33.770828, -112.223219 33.770878, -112.223615 33.771034, -112.223478 33.769463, -112.223506 33.764362, -112.223827 33.762393, -112.223055 33.762424, -112.223247 33.76191, -112.223945 33.761626, -112.22408 33.760113, -112.224245 33.759585, -112.224408 33.758141, -112.225172 33.755113, -112.225556 33.754339, -112.226608 33.752217, -112.227199 33.751032, -112.227352 33.750059, -112.227348 33.750031, -112.227285 33.749507, -112.227328 33.749179, -112.227541 33.748406, -112.228023 33.747549, -112.228383 33.747003, -112.228601 33.746354, -112.228717 33.745575, -112.228768 33.744951, -112.228868 33.744635, -112.229242 33.744396, -112.229989 33.744106, -112.230125 33.744004, -112.229758 33.743726, -112.229825 33.741962, -112.229861 33.741014, -112.230948 33.74115, -112.230669 33.740654, -112.230349 33.7402, -112.230164 33.739674, -112.2301 33.739503, -112.230116 33.739493, -112.230314 33.739242, -112.230197 33.73922, -112.229721 33.73913, -112.22957 33.738971, -112.229565 33.738701, -112.229475 33.738532, -112.229289 33.738474, -112.228931 33.738393, -112.228732 33.738227, -112.228483 33.738144, -112.228297 33.738068, -112.228689 33.738185, -112.22932 33.738408, -112.229746 33.738584, -112.230467 33.738741, -112.231081 33.738875, -112.231974 33.739, -112.232231 33.739046, -112.232554 33.73893, -112.232859 33.738816, -112.233042 33.738811, -112.233448 33.73882, -112.232536 33.736263, -112.232244 33.735569, -112.232009 33.735012, -112.232217 33.735188, -112.232521 33.735588, -112.233209 33.736004, -112.233545 33.736404, -112.233913 33.736964, -112.23403 33.737153, -112.234185 33.737412, -112.234057 33.737988, -112.234137 33.738324, -112.234793 33.73906, -112.235161 33.739684, -112.235545 33.740804, -112.235753 33.741188, -112.236281 33.741508, -112.236665 33.741956, -112.236969 33.742052, -112.237114 33.742388, -112.237226 33.742916, -112.23745 33.743108, -112.238122 33.743335, -112.23849 33.74346, -112.238864 33.744149, -112.239098 33.74458, -112.239178 33.74538, -112.239418 33.746148, -112.24017 33.747124, -112.240906 33.748228, -112.241258 33.74954, -112.241428 33.750036, -112.241558 33.751312, -112.241834 33.75216, -112.241799 33.753289, -112.242547 33.754471, -112.24444 33.754311, -112.247144 33.754203, -112.249372 33.754235, -112.250714 33.754254, -112.251869 33.754171, -112.253035 33.754124, -112.253439 33.753209, -112.253997 33.751874, -112.254509 33.750685, -112.255001 33.749877, -112.255764 33.748625, -112.255812 33.748547, -112.256126 33.748147, -112.256465 33.747729, -112.257066 33.747003, -112.257128 33.74693, -112.25785 33.746019, -112.258462 33.745327, -112.259103 33.744642, -112.259555 33.744081, -112.260021 33.74344, -112.260481 33.742735, -112.260968 33.741888, -112.261245 33.741349, -112.261493 33.740817, -112.261744 33.740149, -112.262384 33.738496, -112.262464 33.738277, -112.262574 33.737976, -112.262905 33.737067, -112.263271 33.736063, -112.263839 33.734504, -112.264247 33.733372, -112.266584 33.733618, -112.266359 33.734201, -112.266351 33.734859, -112.264468 33.734882, -112.264478 33.735894, -112.265011 33.735891, -112.26634 33.735899, -112.266342 33.736417, -112.266344 33.737013, -112.26443 33.737013, -112.264434 33.738007, -112.266389 33.737996, -112.266528 33.737996, -112.266523 33.739056, -112.266762 33.739051, -112.267232 33.739041, -112.268023 33.739025, -112.268332 33.73899, -112.268587 33.738889, -112.268842 33.738719, -112.269803 33.738037, -112.270136 33.737807, -112.270252 33.737726, -112.270644 33.737455, -112.270822 33.737628, -112.271529 33.738319, -112.270958 33.738744, -112.27095 33.738794, -112.270974 33.738849, -112.271493 33.739476, -112.271553 33.739517, -112.271678 33.739521, -112.271799 33.739509, -112.272836 33.738752, -112.272961 33.738676, -112.273078 33.738632, -112.273166 33.738619, -112.273287 33.738619, -112.273467 33.738609, -112.273446 33.737846, -112.273429 33.737196, -112.273354 33.734446, -112.273339 33.733885, -112.273305 33.73263, -112.273295 33.732439, -112.27328 33.732121, -112.273279 33.731857, -112.273267 33.731588, -112.273196 33.730808, -112.273161 33.730301, -112.273159 33.730106, -112.273159 33.730078, -112.273151 33.729052, -112.27315 33.72878, -112.273138 33.728371, -112.273081 33.726421, -112.273068 33.725962, -112.273038 33.724928, -112.275042 33.724927, -112.275165 33.724927, -112.27521 33.724927, -112.275205 33.724845, -112.276194 33.724834, -112.277362 33.724822, -112.277889 33.724823, -112.278728 33.724825, -112.279109 33.724824, -112.279518 33.724824, -112.280633 33.724831, -112.280847 33.724831, -112.281131 33.7248, -112.281411 33.724746, -112.281693 33.724676, -112.281691 33.724615, -112.281686 33.724482, -112.281651 33.723166, -112.281646 33.72297, -112.281604 33.721771, -112.281598 33.721598, -112.28146 33.71793, -112.281517 33.717929, -112.281502 33.71749, -112.281492 33.717535, -112.281393 33.714217, -112.281327 33.714216, -112.281325 33.714145, -112.281325 33.714135, -112.281312 33.713743, -112.28135 33.713751, -112.281449 33.713772, -112.282254 33.713864, -112.282911 33.713888, -112.285188 33.713874, -112.285173 33.713958, -112.285177 33.714526, -112.285194 33.716818, -112.285906 33.716868, -112.287869 33.717001, -112.287977 33.716105, -112.288038 33.715496, -112.28804 33.715483, -112.288051 33.715318, -112.288055 33.715216, -112.288067 33.714544, -112.288311 33.714542, -112.292105 33.714522, -112.292111 33.715296, -112.292113 33.715531, -112.292109 33.715596, -112.292096 33.71566, -112.292074 33.715721, -112.292044 33.71578, -112.292005 33.715836, -112.29196 33.715888, -112.291899 33.715941, -112.291848 33.715976, -112.291784 33.716012, -112.291716 33.716041, -112.291644 33.716063, -112.291569 33.716078, -112.291492 33.716086, -112.291456 33.716087, -112.291111 33.716089, -112.291117 33.716862, -112.29014 33.716854, -112.290129 33.716898, -112.29013 33.717536, -112.290126 33.717607, -112.290115 33.717674, -112.290098 33.717751, -112.290068 33.717847, -112.290142 33.717863, -112.290265 33.717899, -112.290364 33.717933, -112.29053 33.717995, -112.290625 33.718021, -112.290679 33.718035, -112.2907 33.718048, -112.29072 33.718078, -112.290726 33.718109, -112.290718 33.718134, -112.290696 33.718166, -112.290668 33.718202, -112.290199 33.718869, -112.289711 33.719593, -112.289622 33.719712, -112.289517 33.719853, -112.289423 33.719993, -112.289363 33.72006, -112.289294 33.720118, -112.289223 33.720165, -112.289127 33.720207, -112.289056 33.720231, -112.288958 33.720249, -112.288876 33.720254, -112.288695 33.720245, -112.288573 33.720242, -112.28857 33.720284, -112.288553 33.720428, -112.28852 33.720552, -112.288453 33.720676, -112.288286 33.720957, -112.287978 33.721422, -112.288446 33.721656, -112.288021 33.722305, -112.287536 33.723002, -112.287322 33.72347, -112.287219 33.723725, -112.287008 33.723978, -112.287029 33.724003, -112.287108 33.724121, -112.287153 33.724202, -112.287192 33.724286, -112.28736 33.72508, -112.287499 33.725321, -112.287628 33.725657, -112.287785 33.72535, -112.287975 33.72495, -112.288513 33.723672, -112.28875 33.723017, -112.289162 33.722394, -112.289384 33.721995, -112.28959 33.721628, -112.290545 33.720957, -112.290818 33.71924, -112.29217 33.718684, -112.293153 33.71805, -112.293654 33.717737, -112.29376 33.717815, -112.294356 33.71764, -112.294585 33.717528, -112.294587 33.71748, -112.294512 33.714309, -112.294513 33.713879, -112.294505 33.713793, -112.29226 33.713809, -112.291355 33.71379, -112.290725 33.71376, -112.290369 33.713735, -112.289917 33.713705, -112.289618 33.713693, -112.288361 33.713693, -112.285589 33.713715, -112.285224 33.713709, -112.285174 33.713709, -112.282257 33.713701, -112.281636 33.713623, -112.28131 33.713574, -112.281301 33.71346, -112.281233 33.713458, -112.281218 33.712995, -112.281188 33.712456, -112.281172 33.712161, -112.28114 33.711149, -112.281124 33.710653, -112.281125 33.710578, -112.281132 33.710055, -112.281148 33.708925, -112.281114 33.707122, -112.281113 33.707057, -112.281105 33.706652, -112.281099 33.705882, -112.281093 33.705015, -112.281055 33.704117, -112.281017 33.703507, -112.283291 33.703511, -112.283266 33.701706, -112.281643 33.701691, -112.281376 33.701697, -112.281026 33.701704, -112.281018 33.700913, -112.281034 33.699953, -112.281021 33.699891, -112.281119 33.699901, -112.282044 33.699912, -112.282651 33.69992, -112.283222 33.699904, -112.283344 33.699904, -112.283563 33.699902, -112.283603 33.699905, -112.283699 33.699933, -112.283787 33.699949, -112.284397 33.699962, -112.285509 33.699975, -112.285849 33.699979, -112.286338 33.699979, -112.286347 33.699561, -112.286357 33.699087, -112.286368 33.698637, -112.286379 33.698155, -112.286313 33.698156, -112.286259 33.698156, -112.286203 33.698155, -112.286145 33.698154, -112.286084 33.698153, -112.286021 33.698152, -112.285956 33.698152, -112.285893 33.69815, -112.285831 33.698148, -112.285769 33.698147, -112.285709 33.698146, -112.285649 33.698146, -112.285587 33.698145, -112.285535 33.698145, -112.28548 33.698144, -112.285426 33.698143, -112.285371 33.698142, -112.285352 33.698142, -112.28527 33.69814, -112.284982 33.698122, -112.284847 33.698114, -112.284692 33.6981, -112.283176 33.698067, -112.283177 33.69778, -112.283171 33.697317, -112.283166 33.697205, -112.283137 33.696969, -112.283113 33.696792, -112.283117 33.696638, -112.283154 33.696469, -112.283208 33.696297, -112.283382 33.696299, -112.283425 33.696294, -112.2836 33.696307, -112.283739 33.696307, -112.284069 33.696316, -112.284192 33.696332, -112.284275 33.696355, -112.284416 33.696439, -112.284469 33.696458, -112.28453 33.696469, -112.284601 33.696467, -112.284669 33.69645, -112.284746 33.696416, -112.284802 33.696403, -112.285174 33.696399, -112.285326 33.696394, -112.285351 33.696393, -112.285484 33.696379, -112.285578 33.696363, -112.285603 33.696359, -112.285737 33.696335, -112.28586 33.696326, -112.286588 33.696338, -112.286617 33.696342, -112.286772 33.69635, -112.286809 33.696348, -112.287006 33.696338, -112.287067 33.696326, -112.287211 33.696308, -112.287496 33.6963, -112.287883 33.696313, -112.288066 33.696316, -112.288442 33.696319, -112.288589 33.696321, -112.288673 33.696319, -112.288749 33.696308, -112.288804 33.69629, -112.288884 33.69626, -112.288957 33.696241, -112.289058 33.696222, -112.289118 33.696216, -112.289299 33.696203, -112.289457 33.696202, -112.289531 33.69491, -112.289534 33.694875, -112.28962 33.69458, -112.289665 33.694532, -112.289665 33.69459, -112.289666 33.694914, -112.289669 33.695371, -112.289676 33.695502, -112.289684 33.695606, -112.289676 33.695747, -112.289671 33.695866, -112.289671 33.695916, -112.289673 33.696168, -112.289671 33.696203, -112.289751 33.696412, -112.289801 33.696567, -112.289811 33.696608, -112.289801 33.697604, -112.289796 33.698675, -112.289793 33.69955, -112.289793 33.699656, -112.289791 33.70027, -112.289768 33.700328, -112.289783 33.700805, -112.289797 33.701865, -112.289803 33.702295, -112.289798 33.703087, -112.289799 33.703575, -112.289775 33.703801, -112.289773 33.70385, -112.289795 33.704473, -112.289805 33.704759, -112.289849 33.705303, -112.289856 33.705497, -112.29341 33.705371, -112.300602 33.705119, -112.300558 33.704847, -112.30043 33.704468, -112.300112 33.703803, -112.299952 33.703509, -112.299506 33.702814, -112.299427 33.702728, -112.299355 33.702598, -112.299333 33.702568, -112.29927 33.702519, -112.299249 33.702488, -112.299227 33.702439, -112.298977 33.702186, -112.298931 33.702159, -112.295268 33.699932, -112.29461 33.698904, -112.294466 33.698569, -112.294574 33.697241, -112.294597 33.697172, -112.294588 33.697138, -112.294615 33.696962, -112.294622 33.696891, -112.29462 33.696857, -112.294614 33.696713, -112.294562 33.696432, -112.294526 33.696329, -112.294499 33.696188, -112.294544 33.695637, -112.294572 33.695295, -112.294606 33.695118, -112.294777 33.694638, -112.294911 33.694335, -112.295125 33.693983, -112.295545 33.693198, -112.295594 33.693098, -112.29565 33.692963, -112.29604 33.691904, -112.296068 33.691836, -112.296076 33.691821, -112.296133 33.691696, -112.296197 33.691514, -112.296215 33.691479, -112.296224 33.691442, -112.296221 33.691355, -112.296169 33.691092, -112.29592 33.690172, -112.295732 33.689853, -112.295493 33.689629, -112.295228 33.689381, -112.295499 33.688733, -112.29568 33.688487, -112.295942 33.687915, -112.29605 33.687781, -112.296095 33.687748, -112.29635 33.687532, -112.296552 33.687213, -112.296647 33.687062, -112.29686 33.686843, -112.297095 33.686711, -112.297396 33.68662, -112.297782 33.686494, -112.29803 33.686372, -112.298618 33.685919, -112.298759 33.68576, -112.298795 33.685552, -112.299268 33.684177, -112.299426 33.683897, -112.299584 33.683628, -112.299637 33.683485, -112.299716 33.683391, -112.299716 33.683331, -112.299801 33.683237, -112.299906 33.682973, -112.299933 33.682682, -112.299867 33.68249, -112.29969 33.681539, -112.299591 33.681352, -112.299473 33.681275, -112.299118 33.680879, -112.298986 33.680599, -112.299052 33.680351, -112.299131 33.680219, -112.299506 33.679824, -112.299788 33.679609, -112.300589 33.678711, -112.300808 33.678466, -112.301945 33.677586, -112.302182 33.677306, -112.30249 33.6768, -112.302557 33.67669, -112.302734 33.67647, -112.302885 33.676354, -112.302939 33.681408, -112.306005 33.681325, -112.306065 33.681323, -112.307742 33.681278, -112.307743 33.681413, -112.30775 33.682221, -112.308122 33.682222, -112.314948 33.682233, -112.316564 33.682174, -112.316578 33.683523, -112.316578 33.685428, -112.316552 33.685801, -112.316481 33.687025, -112.315504 33.687057, -112.315518 33.686283, -112.315199 33.686282, -112.314532 33.686343, -112.313645 33.686471, -112.313388 33.686485, -112.31336 33.687246, -112.313461 33.687246, -112.314048 33.687199, -112.314245 33.687163, -112.314435 33.687902, -112.314161 33.687946, -112.313625 33.688006, -112.313512 33.688007, -112.313508 33.688777, -112.313614 33.68877, -112.314243 33.688703, -112.314311 33.689027, -112.31431 33.689126, -112.314296 33.689444, -112.312295 33.689515, -112.312277 33.691455, -112.312322 33.69301, -112.312317 33.695313, -112.312317 33.695326, -112.312324 33.6967, -112.312346 33.698279, -112.312352 33.698385, -112.312375 33.700241, -112.31237 33.700896, -112.312371 33.701883, -112.312371 33.702091, -112.31239 33.702836, -112.31239 33.703579, -112.31237 33.704449, -112.312252 33.704962, -112.312092 33.705404, -112.311813 33.705862, -112.311495 33.706349, -112.311119 33.706927, -112.311877 33.706959, -112.312671 33.706992, -112.313364 33.707017, -112.315437 33.703137, -112.315706 33.702715, -112.31589 33.702446, -112.316085 33.702267, -112.316634 33.701562, -112.316981 33.701214, -112.317528 33.700787, -112.317982 33.7004, -112.318261 33.700221, -112.31854 33.700043, -112.318672 33.69996, -112.31897 33.699798, -112.319396 33.699566, -112.320154 33.69922, -112.320942 33.698924, -112.321758 33.698683, -112.322592 33.698494, -112.323442 33.698361, -112.3243 33.698285, -112.324561 33.698278, -112.325065 33.698278, -112.32494 33.696617, -112.327384 33.696584, -112.329506 33.696485, -112.329742 33.696444, -112.329929 33.696355, -112.330312 33.696318, -112.330498 33.696353, -112.33067 33.696404, -112.331457 33.696593, -112.333843 33.696622, -112.333897 33.696621, -112.33412 33.696618, -112.339177 33.696547, -112.342653 33.696452, -112.350846 33.696484, -112.351054 33.696356, -112.35115 33.696276, -112.351214 33.696196, -112.35122 33.696151, -112.351257 33.695841, -112.351814 33.695818, -112.351863 33.695816, -112.351856 33.696214, -112.352266 33.69622, -112.35276 33.696221, -112.352827 33.696219, -112.353213 33.69622, -112.353323 33.696216, -112.353397 33.696205, -112.353493 33.696177, -112.353568 33.696147, -112.353657 33.696101, -112.353769 33.696025, -112.353796 33.696004, -112.353844 33.695956, -112.353893 33.695897, -112.354003 33.69573, -112.355219 33.695681, -112.355341 33.695665, -112.357098 33.695434, -112.357146 33.695424, -112.357199 33.695412, -112.357666 33.695483, -112.358101 33.695513, -112.358385 33.695463, -112.358816 33.695336, -112.359148 33.69522, -112.359358 33.695357, -112.359711 33.695586, -112.359709 33.696645, -112.359709 33.696713, -112.359708 33.696824, -112.359708 33.696873, -112.359702 33.701003, -112.359932 33.700946, -112.360185 33.700885, -112.360527 33.700802, -112.361109 33.700671, -112.362836 33.700271, -112.363416 33.70013, -112.366322 33.699439, -112.366895 33.699296, -112.367464 33.699163, -112.36803 33.699023, -112.368609 33.698891, -112.369766 33.698602, -112.370904 33.698329, -112.371124 33.698275, -112.371542 33.698173, -112.372059 33.698033, -112.372636 33.697899, -112.373215 33.697759, -112.37437 33.69749, -112.374948 33.697348, -112.375528 33.697214, -112.376692 33.696931, -112.377272 33.696797, -112.37843 33.696513, -112.381317 33.695831, -112.383061 33.695408, -112.384217 33.69512, -112.384798 33.694979, -112.38711 33.69441, -112.388821 33.693999, -112.389354 33.693858, -112.389683 33.693764, -112.390216 33.69359, -112.390488 33.693486, -112.39215 33.692757, -112.394359 33.691769, -112.394427 33.691737, -112.394912 33.691517, -112.39603 33.691018, -112.396583 33.690765, -112.397129 33.69051, -112.398249 33.690009, -112.398831 33.68976, -112.400507 33.689033, -112.40163 33.688517, -112.402285 33.688224, -112.40294 33.687921, -112.403373 33.687734, -112.403931 33.687472, -112.404385 33.687267, -112.405593 33.68672, -112.405909 33.686584, -112.406316 33.686426, -112.40677 33.686267, -112.407329 33.686084, -112.407594 33.686034, -112.408055 33.685937, -112.408198 33.685912, -112.40825 33.685906, -112.40831 33.6859, -112.408504 33.68589, -112.408631 33.685894, -112.408738 33.68591, -112.408861 33.685948, -112.409042 33.686033, -112.409141 33.686064, -112.40914 33.686515, -112.409199 33.686475, -112.409534 33.686209, -112.409656 33.686299, -112.409709 33.686253, -112.410005 33.686009, -112.411645 33.687304, -112.415179 33.690062, -112.422943 33.696147, -112.425581 33.698206, -112.427534 33.699736, -112.429488 33.701265, -112.431642 33.702937, -112.434816 33.705374, -112.436884 33.707023, -112.442308 33.711282, -112.442782 33.711302, -112.443085 33.711316, -112.443625 33.711336, -112.443839 33.711518, -112.444219 33.711826, -112.444312 33.711905, -112.444766 33.712276, -112.444994 33.712035, -112.444954 33.711384, -112.445102 33.711279, -112.445126 33.71127, -112.445167 33.711262, -112.445189 33.711262, -112.445369 33.711282, -112.445835 33.711314, -112.445874 33.71129, -112.445944 33.711321, -112.446348 33.711334, -112.447349 33.711334, -112.448275 33.711334, -112.448421 33.711328, -112.449246 33.711331, -112.449285 33.711321, -112.449328 33.711295, -112.449352 33.711268, -112.44937 33.711226, -112.449386 33.710893, -112.449382 33.710669, -112.449387 33.710446, -112.449386 33.709999, -112.449389 33.709533, -112.449385 33.709345, -112.44938 33.709101, -112.449377 33.708198, -112.449378 33.707669, -112.449382 33.707508, -112.449372 33.706565, -112.449384 33.705846, -112.449378 33.705714, -112.449374 33.705616, -112.449372 33.704918, -112.449378 33.704472, -112.449375 33.70425, -112.449371 33.704196, -112.449361 33.70415, -112.449336 33.704113, -112.449292 33.704081, -112.44925 33.704066, -112.449082 33.704057, -112.448875 33.704053, -112.448298 33.704052, -112.447755 33.704058, -112.447007 33.70405, -112.44645 33.704053, -112.446161 33.704045, -112.445865 33.704046, -112.445599 33.704052, -112.445334 33.704051, -112.445167 33.704054, -112.444792 33.704048, -112.444278 33.704053, -112.443985 33.70406, -112.443207 33.704039, -112.442873 33.704036, -112.442548 33.704058, -112.44229 33.704068, -112.441657 33.704065, -112.44144 33.704047, -112.441328 33.704038, -112.440835 33.704042, -112.440271 33.704061, -112.440182 33.704069, -112.440126 33.704085, -112.43958 33.704132, -112.439319 33.704144, -112.439173 33.704151, -112.437844 33.704173, -112.43809 33.704539, -112.438143 33.704594, -112.438367 33.704788, -112.438592 33.704996, -112.438213 33.704999, -112.437822 33.705005, -112.437106 33.705018, -112.437039 33.705021, -112.436976 33.704999, -112.436855 33.704926, -112.436489 33.704636, -112.436298 33.704582, -112.435623 33.70456, -112.435285 33.704882, -112.43512 33.704732, -112.433903 33.703769, -112.432883 33.702971, -112.431213 33.701673, -112.430562 33.701156, -112.429773 33.700543, -112.429565 33.700383, -112.428744 33.69974, -112.427278 33.698595, -112.426952 33.698345, -112.426308 33.697837, -112.426014 33.697608, -112.425901 33.69752, -112.425 33.696802, -112.423467 33.695607, -112.422649 33.694974, -112.42099 33.693664, -112.419312 33.692359, -112.417971 33.691309, -112.417804 33.691181, -112.417307 33.6908, -112.416657 33.690283, -112.414301 33.688448, -112.41396 33.688186, -112.413256 33.687665, -112.41291 33.687403, -112.412049 33.686741, -112.411869 33.686609, -112.411533 33.686348, -112.41098 33.685905, -112.410543 33.685566, -112.410623 33.6855, -112.410866 33.685313, -112.411103 33.685175, -112.414325 33.683433, -112.41478 33.683194, -112.415282 33.682929, -112.415453 33.682839, -112.417614 33.681702, -112.42004 33.679482, -112.420435 33.679114, -112.421683 33.677968, -112.421755 33.677902, -112.42201 33.677784, -112.422229 33.677761, -112.422516 33.677734, -112.422653 33.677677, -112.422748 33.677582, -112.422809 33.677448, -112.422829 33.67736, -112.423104 33.676759, -112.423219 33.676595, -112.423253 33.676565, -112.42534 33.674689, -112.425899 33.674167, -112.426103 33.673925, -112.426537 33.673309, -112.426827 33.672809, -112.426938 33.672556, -112.429089 33.669896, -112.429516 33.669533, -112.431521 33.667988, -112.431703 33.667848, -112.432783 33.667026, -112.432922 33.666861, -112.434105 33.661963, -112.434335 33.661425, -112.434506 33.661144, -112.435308 33.660237, -112.435479 33.660061, -112.435683 33.659836, -112.436228 33.65922, -112.436984 33.658654, -112.437471 33.658363, -112.440093 33.657236, -112.441099 33.656873, -112.441789 33.656719, -112.442322 33.656642, -112.445964 33.656428, -112.446364 33.656362, -112.44691 33.656191, -112.447692 33.655801, -112.449297 33.654858, -112.449657 33.654646, -112.450157 33.654212, -112.450479 33.653799, -112.450558 33.653717, -112.450767 33.653404, -112.450829 33.653286, -112.450963 33.653031, -112.451169 33.65264, -112.451175 33.65209, -112.451123 33.651843, -112.450893 33.651254, -112.450117 33.6501, -112.449749 33.649435, -112.449479 33.648775, -112.449288 33.648033, -112.449209 33.647484, -112.449183 33.647132, -112.449203 33.646829, -112.449211 33.646771, -112.449255 33.646489, -112.449413 33.646049, -112.449426 33.646027, -112.449492 33.645917, -112.449643 33.64573, -112.449879 33.645515, -112.450037 33.645406, -112.450405 33.64523, -112.451056 33.645048, -112.451227 33.644971, -112.451772 33.644663, -112.452048 33.644438, -112.452172 33.644262, -112.452889 33.645021, -112.452905 33.645038, -112.452862 33.645119, -112.452871 33.645775, -112.452876 33.646494, -112.452695 33.646505, -112.452766 33.64798, -112.452768 33.648324, -112.454207 33.649072, -112.458217 33.643207, -112.46033 33.638445, -112.460388 33.638294, -112.459718 33.638293, -112.458865 33.6383, -112.457847 33.638313, -112.45699 33.638314, -112.455967 33.638308, -112.455377 33.638309, -112.455059 33.63831, -112.455071 33.638279, -112.455199 33.638026, -112.455285 33.637875, -112.455375 33.637742, -112.455427 33.63768, -112.455531 33.637574, -112.455619 33.637494, -112.455711 33.637424, -112.455826 33.637357, -112.455947 33.637296, -112.456058 33.637251, -112.456289 33.63717, -112.456438 33.637135, -112.457154 33.636993, -112.458063 33.636778, -112.458174 33.636743, -112.458254 33.636703, -112.458361 33.636641, -112.458413 33.636597, -112.458482 33.636519, -112.458528 33.636453, -112.458553 33.636381, -112.458568 33.636319, -112.45858 33.636224, -112.458577 33.636125, -112.458548 33.635936, -112.458439 33.63541, -112.4582 33.634339, -112.458003 33.633331, -112.457814 33.632434, -112.457801 33.632268, -112.457802 33.632192, -112.457826 33.632003, -112.457902 33.631712, -112.457999 33.631447, -112.458083 33.631275, -112.458258 33.631013, -112.458355 33.630876, -112.45924 33.629728, -112.459283 33.629667, -112.459339 33.629569, -112.459384 33.629461, -112.459419 33.629328, -112.459432 33.629218, -112.459428 33.629079, -112.45941 33.628375, -112.459398 33.628271, -112.459366 33.628126, -112.459332 33.628025, -112.459279 33.627907, -112.459189 33.627756, -112.459077 33.627616, -112.458287 33.626826, -112.458192 33.62671, -112.458134 33.626618, -112.458077 33.626496, -112.458041 33.626382, -112.458022 33.626279, -112.458016 33.626197, -112.458019 33.626091, -112.45833 33.624144, -112.458484 33.624141, -112.458494 33.624088, -112.458507 33.623906, -112.458518 33.623873, -112.45855 33.62371, -112.458674 33.623076, -112.458672 33.62303, -112.458694 33.622401, -112.458701 33.62236, -112.458249 33.619289, -112.458105 33.618678, -112.457986 33.618327, -112.457489 33.617205, -112.45746 33.617139, -112.457427 33.61698, -112.457427 33.616755, -112.457339 33.616756, -112.457366 33.616623, -112.457408 33.616481, -112.457465 33.616341, -112.457532 33.616213, -112.458095 33.615322, -112.458414 33.614809, -112.458753 33.614278, -112.458869 33.614074, -112.458958 33.613888, -112.45904 33.613675, -112.459122 33.613395, -112.459177 33.613171, -112.459242 33.612855, -112.459259 33.612678, -112.459225 33.609659, -112.458255 33.60967, -112.457562 33.609679, -112.456113 33.609681, -112.453979 33.609678, -112.452865 33.609677, -112.452883 33.60953, -112.452867 33.609433, -112.452874 33.608542, -112.452816 33.608542, -112.452614 33.608542, -112.452614 33.608522, -112.452418 33.608542, -112.448427 33.608545, -112.448433 33.609444, -112.448433 33.609682, -112.448069 33.609683, -112.446128 33.60969, -112.444249 33.609696, -112.44419 33.609696, -112.444103 33.609477, -112.444177 33.605405, -112.444186 33.602314, -112.443868 33.602314, -112.443799 33.602313, -112.443731 33.60231, -112.443663 33.602305, -112.443596 33.602298, -112.443528 33.602289, -112.443462 33.602278, -112.443395 33.602265, -112.44333 33.60225, -112.443265 33.602234, -112.443201 33.602216, -112.443138 33.602195, -112.443076 33.602174, -112.443015 33.60215, -112.442954 33.602124, -112.442849 33.60208, -112.442741 33.602038, -112.442632 33.602, -112.442521 33.601965, -112.442409 33.601933, -112.442295 33.601904, -112.442181 33.601879, -112.442264 33.601594, -112.442347 33.601309, -112.442217 33.601286, -112.442086 33.601265, -112.441954 33.601249, -112.441822 33.601236, -112.441689 33.601227, -112.441556 33.601221, -112.441423 33.601219, -112.441015 33.601218, -112.440607 33.601217, -112.440199 33.601217, -112.440199 33.60094, -112.4402 33.600664, -112.440201 33.600388, -112.440607 33.600389, -112.441013 33.600389, -112.441419 33.60039, -112.441995 33.600419, -112.442033 33.600143, -112.442072 33.599868, -112.44211 33.599593, -112.442318 33.599616, -112.442525 33.599646, -112.44273 33.599681, -112.443302 33.599789, -112.443359 33.599655, -112.443367 33.599621, -112.443374 33.599587, -112.44338 33.599553, -112.443385 33.599519, -112.443388 33.599485, -112.44339 33.59945, -112.443391 33.599416, -112.443505 33.599416, -112.443981 33.599417, -112.444158 33.599417, -112.444196 33.599416, -112.44421 33.595292, -112.441472 33.595298, -112.438413 33.595305, -112.436063 33.595312, -112.435999 33.595313, -112.435999 33.595186, -112.435542 33.595188, -112.435525 33.595188, -112.435465 33.595188, -112.431958 33.595201, -112.427931 33.595217, -112.427726 33.595218, -112.427103 33.595225, -112.427021 33.59523, -112.426949 33.595235, -112.426907 33.59524, -112.426856 33.595245, -112.425875 33.595243, -112.422484 33.595239, -112.420101 33.595213, -112.418353 33.595194, -112.418353 33.595022, -112.418317 33.593007, -112.418309 33.591458, -112.418301 33.589354, -112.418301 33.587886, -112.418304 33.586769, -112.418309 33.585273, -112.418336 33.580844, -112.418337 33.580734, -112.418335 33.580691, -112.418337 33.580667, -112.418339 33.580639, -112.418196 33.580641, -112.418002 33.58064, -112.417937 33.580641, -112.41775 33.580643, -112.417424 33.580644, -112.417302 33.580595, -112.41723 33.580581, -112.414886 33.580563, -112.414038 33.580556, -112.414105 33.580657, -112.414113 33.580669, -112.414156 33.580733, -112.414154 33.581098, -112.414151 33.581713, -112.414137 33.585295, -112.413768 33.585272, -112.413152 33.585276, -112.412491 33.585285, -112.411863 33.585298, -112.411092 33.585299, -112.41106 33.585339, -112.411037 33.58552, -112.411039 33.586093, -112.411016 33.586094, -112.410828 33.586095, -112.41063 33.586097, -112.41057 33.586111, -112.410548 33.586116, -112.410535 33.58624, -112.410534 33.586283, -112.410533 33.586347, -112.41053 33.586543, -112.410529 33.586629, -112.410527 33.586777, -112.410525 33.586894, -112.410521 33.587024, -112.410516 33.587371, -112.410595 33.587437, -112.410859 33.587426, -112.410927 33.587427, -112.410929 33.587965, -112.410858 33.587965, -112.409607 33.587975, -112.409614 33.586055, -112.409622 33.584243, -112.409618 33.581686, -112.409618 33.581096, -112.409617 33.580732, -112.409583 33.580731, -112.408891 33.580721, -112.40812 33.580711, -112.406966 33.580696, -112.405828 33.580681, -112.4047 33.580666, -112.403501 33.58065, -112.40237 33.580635, -112.402393 33.580573, -112.402398 33.58056, -112.402401 33.580543, -112.401953 33.58054, -112.401209 33.58053, -112.40097 33.580528, -112.400121 33.580518, -112.399938 33.580516, -112.398997 33.580505, -112.398908 33.580504, -112.397966 33.580492, -112.397852 33.580491, -112.396921 33.58048, -112.396834 33.580479, -112.395833 33.580466, -112.395757 33.580466, -112.394757 33.580454, -112.39428 33.58045, -112.393859 33.580443, -112.393676 33.58032, -112.393675 33.580228, -112.393673 33.57975, -112.380644 33.579662, -112.376407 33.579633, -112.373323 33.579612, -112.371288 33.579595, -112.370983 33.579593, -112.37059 33.57959, -112.367637 33.579567, -112.367928 33.579646, -112.367949 33.579717, -112.36794 33.579836, -112.367926 33.580009, -112.367913 33.58017, -112.368372 33.580172, -112.368552 33.580174, -112.368933 33.580176, -112.369347 33.580179, -112.369478 33.580179, -112.370041 33.580183, -112.370596 33.580187, -112.37065 33.580187, -112.371005 33.580189, -112.371717 33.580193, -112.372091 33.580195, -112.372424 33.580197, -112.373157 33.580202, -112.373227 33.580202, -112.373859 33.580206, -112.37431 33.580208, -112.374566 33.58021, -112.375309 33.580214, -112.375406 33.580214, -112.375975 33.580218, -112.376382 33.580222, -112.377108 33.58023, -112.377452 33.580234, -112.378257 33.580245, -112.378528 33.580248, -112.379278 33.580258, -112.37954 33.580261, -112.380299 33.580271, -112.380641 33.580275, -112.381314 33.580284, -112.381716 33.580289, -112.382431 33.580298, -112.382806 33.580302, -112.383359 33.58031, -112.38387 33.580316, -112.384465 33.580324, -112.384886 33.580329, -112.385401 33.580336, -112.38602 33.580343, -112.386504 33.58035, -112.387072 33.580356, -112.387402 33.580361, -112.388177 33.580371, -112.388505 33.580376, -112.389276 33.580385, -112.389456 33.580388, -112.390338 33.580399, -112.390562 33.580402, -112.391417 33.580412, -112.391504 33.580414, -112.392527 33.580426, -112.392621 33.580428, -112.39326 33.580438, -112.393676 33.580441, -112.393676 33.580469, -112.393677 33.580564, -112.392527 33.580546, -112.391416 33.58053, -112.390336 33.580515, -112.389275 33.5805, -112.388175 33.580485, -112.387071 33.58047, -112.386019 33.580456, -112.384886 33.580441, -112.383868 33.580426, -112.382804 33.580412, -112.381715 33.580397, -112.38064 33.580383, -112.379538 33.580369, -112.378528 33.580356, -112.378106 33.580351, -112.377451 33.580346, -112.376364 33.580339, -112.375403 33.58033, -112.374309 33.580321, -112.373228 33.580313, -112.373102 33.580312, -112.372093 33.580304, -112.371009 33.580296, -112.370591 33.580293, -112.370057 33.58029, -112.369477 33.580284, -112.368932 33.58028, -112.36837 33.580276, -112.367989 33.580274, -112.3679 33.580274, -112.367773 33.580273, -112.367339 33.580269, -112.366784 33.580265, -112.366221 33.580262, -112.366142 33.580262, -112.365929 33.58026, -112.365371 33.580255, -112.364872 33.580251, -112.36436 33.580247, -112.363839 33.580243, -112.36332 33.580239, -112.36281 33.580235, -112.362298 33.580231, -112.361789 33.580228, -112.361272 33.580225, -112.360768 33.580222, -112.360284 33.580219, -112.35974 33.580216, -112.359411 33.580214, -112.359169 33.580213, -112.359036 33.580224, -112.359038 33.580645, -112.359056 33.583947, -112.359016 33.590383, -112.359097 33.591421, -112.3591 33.592327, -112.359112 33.594598, -112.359112 33.594697, -112.35913 33.598227, -112.359789 33.598249, -112.360172 33.598182, -112.360627 33.598025, -112.361073 33.597974, -112.361451 33.598005, -112.361385 33.598816, -112.361375 33.599097, -112.36149 33.599777, -112.362096 33.599724, -112.36219 33.600382, -112.362209 33.600555, -112.362213 33.600793, -112.362216 33.601244, -112.362229 33.601303, -112.362253 33.601359, -112.362288 33.601409, -112.362346 33.601463, -112.362388 33.601493, -112.36245 33.601524, -112.362517 33.601545, -112.362588 33.601556, -112.364478 33.601574, -112.365417 33.601557, -112.365419 33.601895, -112.367341 33.601917, -112.367346 33.601131, -112.367341 33.60035, -112.367341 33.59958, -112.367341 33.598827, -112.365775 33.5988, -112.36569 33.598792, -112.365606 33.598771, -112.365089 33.598601, -112.364957 33.598571, -112.364999 33.598422, -112.365012 33.59833, -112.365017 33.598237, -112.365015 33.597769, -112.365152 33.597768, -112.365738 33.597744, -112.365962 33.597742, -112.366454 33.597749, -112.366648 33.597744, -112.366842 33.597725, -112.367341 33.597631, -112.367331 33.597531, -112.367312 33.597432, -112.367282 33.597335, -112.367244 33.59724, -112.367196 33.597148, -112.367138 33.597059, -112.367074 33.596976, -112.367042 33.596944, -112.367367 33.596719, -112.367546 33.596634, -112.367701 33.596601, -112.367869 33.596586, -112.367873 33.594767, -112.368027 33.594768, -112.372173 33.5948, -112.374002 33.594815, -112.376385 33.594843, -112.376414 33.594871, -112.376431 33.594904, -112.376452 33.59501, -112.376463 33.596424, -112.376474 33.598169, -112.376484 33.599622, -112.376501 33.602061, -112.376509 33.60277, -112.376509 33.603161, -112.37651 33.603995, -112.37653 33.60541, -112.376517 33.60569, -112.376508 33.60592, -112.376517 33.606942, -112.376493 33.609301, -112.375972 33.609293, -112.372438 33.609271, -112.368003 33.609243, -112.363341 33.609213, -112.361264 33.6092, -112.359188 33.609186, -112.354905 33.609158, -112.354852 33.609157, -112.350593 33.609126, -112.350531 33.609126, -112.350465 33.609126, -112.347648 33.609148, -112.346449 33.609147, -112.346271 33.609144, -112.346202 33.609142, -112.342655 33.609082, -112.342592 33.609081, -112.342578 33.609081, -112.342538 33.609081, -112.342141 33.609079, -112.342062 33.609079, -112.34195 33.609078, -112.341943 33.608702, -112.341935 33.607519, -112.341939 33.607422, -112.341944 33.607029, -112.34191 33.605993, -112.341897 33.605251, -112.341891 33.603503, -112.341888 33.602672, -112.341887 33.602519, -112.341894 33.60181, -112.341899 33.601423, -112.341901 33.599949, -112.341897 33.598449, -112.341903 33.596042, -112.341902 33.595958, -112.341902 33.594557, -112.341904 33.594427, -112.341857 33.593926, -112.341844 33.593659, -112.341849 33.593246, -112.341867 33.592846, -112.341878 33.592311, -112.341891 33.591936, -112.341894 33.59188, -112.341901 33.591564, -112.341896 33.591217, -112.341876 33.587557, -112.341872 33.587369, -112.341871 33.587297, -112.337519 33.587285, -112.337521 33.585475, -112.340787 33.585488, -112.341834 33.585501, -112.34181 33.583729, -112.341788 33.581898, -112.341778 33.580078, -112.340776 33.580076, -112.339707 33.580067, -112.338449 33.580043, -112.337491 33.580039, -112.337388 33.580038, -112.337275 33.580037, -112.335308 33.580016, -112.333123 33.58, -112.328872 33.579988, -112.328898 33.580313, -112.328921 33.580456, -112.328962 33.58061, -112.329015 33.580756, -112.329103 33.580942, -112.329131 33.581037, -112.329142 33.58113, -112.329123 33.583649, -112.328682 33.583644, -112.327384 33.583642, -112.324528 33.583592, -112.324497 33.5803, -112.324495 33.580129, -112.32451 33.579961, -112.322282 33.579936, -112.320469 33.579941, -112.31787 33.579945, -112.315953 33.579949, -112.315869 33.579949, -112.315766 33.579949, -112.315256 33.57995, -112.314156 33.579952, -112.313559 33.579953, -112.313118 33.579954, -112.313314 33.579639, -112.312214 33.579639, -112.312187 33.578686, -112.312186 33.57864, -112.311988 33.571773, -112.311888 33.568265, -112.311836 33.566458, -112.311807 33.565435, -112.311497 33.565435, -112.311497 33.565386, -112.311219 33.565384, -112.310178 33.565375, -112.309063 33.565374, -112.307528 33.565371, -112.307244 33.565371, -112.307179 33.565372, -112.307175 33.565238, -112.306745 33.551116, -112.306758 33.550823, -112.304939 33.550832, -112.302581 33.550866, -112.302121 33.550869, -112.301109 33.550876, -112.301105 33.550777, -112.301091 33.550699, -112.300379 33.550712, -112.298432 33.550744, -112.297156 33.55075, -112.296778 33.550749, -112.295918 33.550748, -112.295519 33.550747, -112.294631 33.550745, -112.294353 33.550734, -112.294355 33.550696, -112.294374 33.550587, -112.2944 33.550502, -112.294414 33.550477, -112.297562 33.550525, -112.298431 33.550516, -112.29844 33.546709, -112.298448 33.54369, -112.297658 33.543691, -112.296821 33.543191, -112.296823 33.542231, -112.296836 33.537566, -112.296828 33.537529, -112.296805 33.537497, -112.296764 33.537472, -112.296714 33.537461, -112.294229 33.537456, -112.294185 33.538572, -112.294168 33.538905, -112.294135 33.539242, -112.294123 33.539341, -112.294077 33.539488, -112.294029 33.53961, -112.293937 33.539792, -112.293828 33.539963, -112.293698 33.540128, -112.293603 33.540228, -112.293445 33.540374, -112.293323 33.54047, -112.293129 33.540599, -112.292961 33.540691, -112.292795 33.540768, -112.292221 33.540967, -112.292588 33.541666, -112.292668 33.541759, -112.292777 33.541862, -112.292897 33.541953, -112.293031 33.542034, -112.293134 33.542085, -112.293209 33.542116, -112.292969 33.542547, -112.292927 33.542661, -112.292899 33.542778, -112.292888 33.542901, -112.292882 33.543191, -112.293091 33.543704, -112.289901 33.543712, -112.289865 33.543723, -112.289847 33.543736, -112.289833 33.543753, -112.287534 33.543733, -112.287474 33.543725, -112.287444 33.543717, -112.287345 33.543701, -112.28726 33.543698, -112.28643 33.543709, -112.285154 33.543681, -112.285058 33.543666, -112.284991 33.543644, -112.284914 33.543602, -112.284858 33.54356, -112.284829 33.543531, -112.284783 33.543467, -112.284753 33.543397, -112.284739 33.543323, -112.284805 33.541646, -112.284813 33.541556, -112.284803 33.541473, -112.284782 33.541403, -112.284746 33.541328, -112.284664 33.54122, -112.28464 33.541165, -112.284634 33.54111, -112.284635 33.540865, -112.284668 33.540693, -112.285429 33.539251, -112.285555 33.539005, -112.286312 33.537745, -112.28651 33.537385, -112.286666 33.537069, -112.286686 33.537005, -112.286696 33.536929, -112.286689 33.536851, -112.286658 33.536747, -112.286596 33.53655, -112.282787 33.536693, -112.28215 33.536717, -112.282084 33.53672, -112.282019 33.536721, -112.281863 33.536727, -112.281505 33.53674, -112.277999 33.536876, -112.276715 33.536926, -112.275124 33.536987, -112.272571 33.537008, -112.272442 33.53701, -112.271318 33.53702, -112.271243 33.53703, -112.269862 33.537078, -112.269595 33.537087, -112.268585 33.537118, -112.268445 33.537126, -112.267948 33.537149, -112.266991 33.537194, -112.264896 33.537264, -112.263767 33.537296, -112.263718 33.537298, -112.263752 33.537072, -112.263794 33.536832, -112.263848 33.536603, -112.263974 33.536362, -112.264131 33.536176, -112.264245 33.536062, -112.264365 33.535941, -112.264648 33.535646, -112.264811 33.535436, -112.264919 33.535153, -112.264937 33.534876, -112.264725 33.53303, -112.264699 33.531773, -112.264723 33.530407, -112.264734 33.530175, -112.264876 33.530163, -112.268721 33.530096, -112.269074 33.530084, -112.268976 33.528602, -112.268959 33.528412, -112.268928 33.527907, -112.268916 33.527436, -112.268904 33.527019, -112.268886 33.52629, -112.268881 33.525706, -112.268892 33.524411, -112.268889 33.523016, -112.269167 33.523002, -112.269168 33.522928, -112.26917 33.522718, -112.269634 33.52268, -112.272387 33.522482, -112.272388 33.522773, -112.272388 33.522843, -112.272495 33.522838, -112.272537 33.522826, -112.272537 33.52284, -112.272537 33.52286, -112.272557 33.522934, -112.272695 33.522968, -112.272892 33.522969, -112.274821 33.522875, -112.275839 33.522848, -112.279204 33.522701, -112.279232 33.5227, -112.279245 33.5227, -112.279613 33.522712, -112.279777 33.522712, -112.279836 33.522689, -112.279889 33.522696, -112.279967 33.522756, -112.279981 33.522784, -112.283756 33.522368, -112.285165 33.522365, -112.288045 33.522387, -112.289647 33.522394, -112.289788 33.522395, -112.289873 33.522362, -112.289885 33.522349, -112.289914 33.522308, -112.28993 33.522261, -112.289931 33.52223, -112.289943 33.521971, -112.289947 33.52188, -112.289961 33.521566, -112.289969 33.521257, -112.289967 33.520086, -112.289979 33.51791, -112.289987 33.516632, -112.289994 33.515343, -112.289965 33.515107, -112.289948 33.514867, -112.289941 33.514415, -112.289946 33.511798, -112.289948 33.510067, -112.289987 33.50818, -112.29857 33.507817, -112.30022 33.507748, -112.300274 33.507747, -112.301731 33.507987, -112.304261 33.507936, -112.304352 33.507934, -112.304762 33.507925, -112.30642 33.507892, -112.306412 33.507637, -112.306409 33.507542, -112.306577 33.507539, -112.306764 33.507535, -112.3086 33.507506, -112.310672 33.507486, -112.312618 33.507489, -112.315853 33.507494, -112.316217 33.507495, -112.319435 33.5075, -112.323403 33.507506, -112.323922 33.507506, -112.323946 33.507507, -112.324056 33.507509, -112.324041 33.507304, -112.324031 33.507154, -112.323988 33.503925, -112.323901 33.503925, -112.323842 33.501156, -112.323807 33.498395, -112.32379 33.497079, -112.32367 33.496928, -112.323607 33.496705, -112.323523 33.496705, -112.323524 33.496687, -112.323276 33.496686, -112.323228 33.496687, -112.322684 33.496681, -112.31961 33.496687, -112.319598 33.496334, -112.319582 33.493887, -112.319582 33.493414, -112.317835 33.493403, -112.316259 33.49339, -112.314614 33.493366, -112.312737 33.493343, -112.312788 33.493226, -112.312825 33.493133, -112.311695 33.493122, -112.31082 33.493116, -112.309338 33.493107, -112.308924 33.493055, -112.308612 33.493065, -112.307561 33.493094, -112.306797 33.493088, -112.306231 33.493097, -112.302506 33.493235, -112.30175 33.493263, -112.298518 33.493355, -112.296011 33.49344, -112.293329 33.493539, -112.291837 33.493594, -112.290218 33.493653, -112.289547 33.49366, -112.286864 33.493685, -112.286324 33.493684, -112.285645 33.493678, -112.28494 33.493679, -112.282709 33.493699, -112.281046 33.493727, -112.279406 33.493752, -112.278327 33.49376, -112.277241 33.493768, -112.275839 33.493772, -112.275369 33.493769, -112.274274 33.493775, -112.272767 33.493792, -112.272646 33.493795, -112.272551 33.493797, -112.272444 33.493799, -112.27232 33.493802, -112.272286 33.481022, -112.272347 33.479818, -112.272361 33.47936, -112.272361 33.479323, -112.272473 33.47932, -112.272474 33.479279, -112.272709 33.479273, -112.272855 33.47927, -112.273219 33.479262, -112.27323 33.475632, -112.273232 33.474811, -112.272868 33.474814, -112.272871 33.47401, -112.272873 33.47334, -112.272874 33.473234, -112.272822 33.473231, -112.272853 33.47213, -112.272858 33.471958, -112.272836 33.468573, -112.272835 33.468435, -112.272835 33.468413, -112.272871 33.46473, -112.272873 33.464494, -112.272874 33.464463, -112.272904 33.462255, -112.272909 33.461931, -112.272914 33.461734, -112.272922 33.461435, -112.272891 33.46107, -112.272891 33.461031, -112.272892 33.460838, -112.272896 33.459364, -112.27292 33.457866, -112.272922 33.457606, -112.272925 33.457347, -112.272956 33.453851, -112.272986 33.450471, -112.276784 33.45037, -112.281021 33.450257, -112.281178 33.450255, -112.284004 33.450226, -112.285515 33.450188, -112.289784 33.450079, -112.289784 33.449936, -112.289902 33.449935, -112.289895 33.446835, -112.289893 33.446313, -112.28984 33.440632, -112.289825 33.439097, -112.289807 33.437196, -112.289802 33.436319, -112.289805 33.435639, -112.289811 33.435446, -112.28978 33.43282, -112.289789 33.432076, -112.289799 33.430932, -112.289784 33.430294, -112.289774 33.429589, -112.289765 33.428168, -112.289744 33.426105, -112.289733 33.425214, -112.289714 33.423586, -112.289687 33.420983, -112.289686 33.420821, -112.289686 33.420745, -112.289652 33.418582, -112.289646 33.418241, -112.289632 33.417352, -112.289574 33.414657, -112.289464 33.414659, -112.28945 33.41358, -112.289448 33.413506, -112.289445 33.413346, -112.289413 33.411792, -112.289406 33.411467, -112.289384 33.410422, -112.289355 33.40902, -112.289326 33.406998, -112.289314 33.406546, -112.28931 33.406374, -112.289421 33.406373, -112.289561 33.406373, -112.289929 33.406371, -112.290751 33.406368, -112.291444 33.40636, -112.292565 33.40636, -112.293587 33.406346, -112.294283 33.406338, -112.297086 33.406308, -112.297087 33.407863, -112.297873 33.40787, -112.297872 33.407697, -112.297882 33.406746, -112.297883 33.4063, -112.297771 33.402681, -112.2979 33.402681, -112.298095 33.402676, -112.299014 33.402686, -112.299616 33.402676, -112.299782 33.402667, -112.299877 33.402654, -112.299932 33.402642, -112.300056 33.402603, -112.300143 33.402586, -112.300258 33.402577, -112.301863 33.402572, -112.302047 33.402578, -112.301991 33.397168, -112.303351 33.397157, -112.303379 33.397351, -112.303383 33.397716, -112.303379 33.398111, -112.303401 33.398902, -112.304545 33.398901, -112.305537 33.398885, -112.306165 33.398862, -112.306161 33.397137, -112.306149 33.395958, -112.306146 33.39578, -112.306141 33.395286, -112.306141 33.395184, -112.306227 33.39518, -112.307014 33.39517, -112.307901 33.395178, -112.308801 33.395175, -112.309676 33.395184, -112.311553 33.395192, -112.312494 33.395193, -112.314364 33.395209, -112.317136 33.395215, -112.318071 33.395204, -112.319024 33.395203, -112.319884 33.395211, -112.320018 33.395223, -112.320116 33.39524, -112.320241 33.395273, -112.320367 33.395319, -112.320507 33.395363, -112.320631 33.395389, -112.320754 33.395401, -112.321188 33.395408, -112.321931 33.395408, -112.322671 33.395412, -112.32327 33.395421, -112.324154 33.395415, -112.325641 33.395414, -112.326395 33.395409, -112.327189 33.395412, -112.328745 33.395412, -112.329534 33.395415, -112.329877 33.395412, -112.330328 33.395418, -112.331102 33.395412, -112.331883 33.395415, -112.333373 33.395406, -112.334082 33.395409, -112.336399 33.395401, -112.337098 33.395404, -112.338508 33.395401, -112.339958 33.39539, -112.340445 33.395379, -112.340439 33.3952, -112.340438 33.39416, -112.340435 33.39389, -112.340443 33.392007, -112.34044 33.39183, -112.34052 33.391595, -112.323248 33.391534, -112.32323 33.391084, -112.323141 33.388323, -112.323096 33.387023, -112.323095 33.386834, -112.323064 33.386054, -112.323044 33.38525, -112.323022 33.384749, -112.32097 33.384769, -112.319006 33.384981, -112.319028 33.385607, -112.319043 33.386848, -112.319043 33.387464, -112.31906 33.388157, -112.319074 33.390687, -112.319089 33.391421, -112.319089 33.391451, -112.319091 33.391831, -112.318514 33.39183, -112.31582 33.39181, -112.314682 33.391803, -112.312026 33.391796, -112.310698 33.391789, -112.310705 33.391536, -112.306919 33.391499, -112.306868 33.391779, -112.306664 33.391779, -112.306327 33.391777, -112.306131 33.391776, -112.306129 33.391498, -112.293297 33.39161, -112.293287 33.390942, -112.289427 33.391039, -112.28911 33.391047, -112.28907 33.391049, -112.288958 33.391053, -112.288964 33.391413, -112.288976 33.391703, -112.28898 33.39181, -112.28898 33.392014, -112.288868 33.392015, -112.288823 33.392016, -112.288757 33.392017, -112.288327 33.392021, -112.286471 33.392049, -112.284314 33.392094, -112.283045 33.392124, -112.281707 33.392147, -112.280496 33.392176, -112.27918 33.3922, -112.278863 33.392203, -112.277894 33.392214, -112.277416 33.392223, -112.277285 33.392225, -112.277188 33.392227, -112.277083 33.392229, -112.277081 33.392119, -112.277079 33.39197, -112.277009 33.391971, -112.273193 33.39206, -112.273101 33.392027, -112.272147 33.391101, -112.272131 33.391416, -112.272084 33.391326, -112.272002 33.391213, -112.271551 33.390729, -112.271264 33.390434, -112.270845 33.390072, -112.270501 33.389826, -112.270365 33.389749, -112.270214 33.389681, -112.270049 33.389623, -112.269861 33.389577, -112.268866 33.389404, -112.268202 33.389306, -112.267519 33.389207, -112.266619 33.389065, -112.266164 33.389007, -112.265321 33.388906, -112.26513 33.388897, -112.264125 33.38882, -112.263794 33.388785, -112.263713 33.388779, -112.263186 33.388762, -112.263118 33.388773, -112.263079 33.388785, -112.262945 33.388835, -112.26252 33.388952, -112.262267 33.389011, -112.261904 33.389094, -112.26172 33.389145, -112.261552 33.389176, -112.259596 33.389597, -112.259309 33.389646, -112.259083 33.389676, -112.258989 33.389688, -112.258683 33.389729, -112.25845 33.389774, -112.25821 33.389837, -112.258113 33.389876, -112.258035 33.389922, -112.257982 33.389964, -112.257726 33.390129, -112.256559 33.390848, -112.256409 33.390928, -112.256281 33.390985, -112.255703 33.391225, -112.254728 33.391634, -112.254644 33.391669, -112.254581 33.391705, -112.25454 33.391739, -112.254519 33.391748, -112.254486 33.391751, -112.254287 33.391751, -112.254284 33.391097, -112.254277 33.38944, -112.254279 33.389007, -112.25428 33.388858, -112.25428 33.388708, -112.254284 33.387343, -112.254289 33.385908, -112.254294 33.384317, -112.254295 33.38384, -112.254295 33.383756, -112.253901 33.383708, -112.253474 33.383618, -112.253103 33.383512, -112.252832 33.383424, -112.252469 33.38331, -112.252152 33.383211, -112.25181 33.3831, -112.251627 33.383038, -112.237643 33.388111, -112.237122 33.388404, -112.236762 33.388427, -112.234948 33.38897, -112.234754 33.388762, -112.234559 33.388551, -112.233808 33.387743, -112.230408 33.38451, -112.229072 33.38324, -112.229224 33.380932, -112.229232 33.380817, -112.229458 33.377583, -112.229474 33.377318, -112.229118 33.377319, -112.229118 33.377148, -112.229119 33.375311, -112.229112 33.373965, -112.225803 33.373931, -112.225528 33.373928, -112.22554 33.373945, -112.2262 33.374772, -112.226282 33.374896, -112.22637 33.375059, -112.226407 33.375148, -112.226457 33.375312, -112.226491 33.375475, -112.22651 33.375638, -112.226577 33.376629, -112.226626 33.377324, -112.223941 33.377329, -112.222944 33.37733, -112.222898 33.37733, -112.221539 33.375974, -112.22154 33.375864, -112.2216 33.370271, -112.221603 33.370002, -112.22071 33.37001, -112.219917 33.370013, -112.217536 33.370037, -112.215775 33.370023, -112.215606 33.369848, -112.215407 33.369644, -112.212124 33.366737, -112.211068 33.365833, -112.210354 33.365139, -112.209302 33.364131, -112.208775 33.363627, -112.208104 33.362963, -112.208076 33.362917, -112.208054 33.362833, -112.208007 33.362749, -112.207892 33.362749, -112.207727 33.362749, -112.204174 33.359209, -112.203202 33.358286, -112.200541 33.3557, -112.195908 33.351011, -112.194067 33.349151, -112.193729 33.348808, -112.193424 33.348498, -112.193372 33.348444, -112.189217 33.344462, -112.189123 33.344418, -112.18638 33.34178, -112.186354 33.341717, -112.186306 33.341672, -112.185815 33.341208, -112.182311 33.3412, -112.181993 33.341211, -112.181822 33.34122, -112.181336 33.341217, -112.177789 33.341197, -112.176111 33.341188, -112.17463 33.34118, -112.17368 33.341175, -112.173594 33.341175, -112.173339 33.341168, -112.172004 33.341156, -112.170437 33.341144, -112.169341 33.341133, -112.169252 33.33391, -112.169236 33.33391, -112.169241 33.333718, -112.169204 33.327699, -112.169201 33.32532, -112.169197 33.32523, -112.169195 33.32505, -112.16921 33.325065, -112.169813 33.325746, -112.170382 33.325276, -112.171692 33.324252, -112.173309 33.323019, -112.173399 33.322921, -112.174225 33.321638, -112.174408 33.321243, -112.174781 33.320017, -112.175011 33.319439, -112.175022 33.319423, -112.175698 33.318516, -112.176176 33.317994, -112.176602 33.31713, -112.176995 33.316482, -112.177061 33.316218, -112.177107 33.315272, -112.177303 33.313496, -112.17727 33.313414, -112.176629 33.313429, -112.17638 33.313485, -112.176347 33.31344, -112.175908 33.313485, -112.175365 33.313429, -112.175214 33.313468, -112.175005 33.31344, -112.174821 33.313468, -112.174789 33.313513, -112.174494 33.313468, -112.173885 33.31355, -112.173512 33.31354, -112.173185 33.313359, -112.17289 33.313073, -112.172674 33.312935, -112.172497 33.312859, -112.172314 33.312781, -112.172065 33.312742, -112.171738 33.31277, -112.171181 33.312725, -112.170408 33.312589, -112.170277 33.312551, -112.169996 33.312468, -112.169786 33.312451, -112.16921 33.312247, -112.169102 33.312212, -112.169105 33.313606, -112.169105 33.315119, -112.169107 33.316656, -112.169109 33.317909, -112.169111 33.319346, -112.169116 33.320211, -112.169119 33.323537, -112.169124 33.324979, -112.163557 33.319545, -112.163455 33.319446, -112.161245 33.317253, -112.159697 33.315815, -112.158791 33.314901, -112.158664 33.31478, -112.157771 33.313924, -112.155372 33.311625, -112.153092 33.309441, -112.152095 33.308487, -112.151753 33.308152, -112.151752 33.30822, -112.151722 33.309694, -112.151718 33.313483, -112.151719 33.315865, -112.152075 33.315843, -112.15322 33.315828, -112.153386 33.315824, -112.153592 33.315821, -112.153922 33.315818, -112.155106 33.315818, -112.15557 33.31582, -112.156045 33.315801, -112.15605 33.31602, -112.156062 33.316786, -112.156083 33.31746, -112.156087 33.317545, -112.156094 33.317556, -112.156114 33.317566, -112.15614 33.317572, -112.156459 33.317597, -112.156726 33.317608, -112.157012 33.31761, -112.157464 33.317603, -112.158066 33.31758, -112.159302 33.317583, -112.15963 33.317577, -112.160153 33.317542, -112.160319 33.317521, -112.160496 33.317484, -112.160936 33.317358, -112.161954 33.318343, -112.163027 33.319373, -112.163202 33.31954, -112.164575 33.320856, -112.165631 33.32189, -112.166407 33.322662, -112.166575 33.322825, -112.167332 33.323556, -112.167465 33.32367, -112.167535 33.323724, -112.168831 33.324983, -112.169125 33.325257, -112.169139 33.327312, -112.16914 33.327919, -112.169142 33.329402, -112.169147 33.33014, -112.169151 33.333716, -112.169151 33.333807, -112.169153 33.333911, -112.169044 33.333917, -112.169014 33.333917, -112.168976 33.333917, -112.165075 33.333917, -112.160477 33.333917, -112.160466 33.333826, -112.160466 33.33372, -112.16048 33.328542, -112.160474 33.328501, -112.160456 33.328471, -112.160445 33.328452, -112.160429 33.328436, -112.160355 33.328356, -112.160286 33.32828, -112.160271 33.328245, -112.160262 33.327967, -112.160259 33.327948, -112.160236 33.327894, -112.160209 33.327857, -112.160017 33.327671, -112.159938 33.327612, -112.15986 33.327565, -112.15978 33.327531, -112.160505 33.327345, -112.158805 33.327244, -112.158805 33.326645, -112.158263 33.326635, -112.155401 33.326939, -112.154976 33.328671, -112.155306 33.329656, -112.154405 33.330545, -112.154297 33.330409, -112.154239 33.330207, -112.154171 33.330167, -112.154028 33.330188, -112.15387 33.330281, -112.153654 33.330436, -112.153617 33.330523, -112.153612 33.330695, -112.153633 33.330801, -112.153712 33.330827, -112.153805 33.330844, -112.154017 33.330933, -112.154452 33.330668, -112.154471 33.330717, -112.154513 33.330799, -112.154604 33.330969, -112.15563 33.330922, -112.15579 33.330903, -112.155947 33.330869, -112.157295 33.330486, -112.157403 33.330447, -112.157507 33.330398, -112.157585 33.330363, -112.157596 33.330472, -112.157592 33.330587, -112.157569 33.330788, -112.15757 33.331488, -112.157551 33.332487, -112.157548 33.333917, -112.156158 33.333917, -112.153879 33.333917, -112.151831 33.333917, -112.151794 33.333919, -112.151771 33.333928, -112.151748 33.333936, -112.151722 33.333956, -112.151713 33.333969, -112.151701 33.333999, -112.151695 33.33543, -112.151693 33.335805, -112.151692 33.336436, -112.15169 33.337497, -112.15169 33.337525, -112.15169 33.33769, -112.148875 33.33769, -112.148795 33.337697, -112.148719 33.337711, -112.148407 33.337798, -112.148278 33.337835, -112.148221 33.337868, -112.148201 33.337896, -112.147589 33.339405, -112.147507 33.339609, -112.147459 33.339698, -112.147388 33.339796, -112.147301 33.339888, -112.146641 33.340446, -112.146017 33.341008, -112.145837 33.341173, -112.145765 33.341093, -112.145778 33.34101, -112.145857 33.340938, -112.145878 33.340855, -112.145895 33.340642, -112.145222 33.341196, -112.145163 33.3412, -112.145079 33.3412, -112.14499 33.341195, -112.144798 33.341198, -112.144694 33.3412, -112.144591 33.341202, -112.144383 33.341203, -112.14428 33.341205, -112.144177 33.341208, -112.144075 33.341209, -112.143978 33.341206, -112.143891 33.341199, -112.143817 33.341175, -112.143734 33.341174, -112.143677 33.341126, -112.143637 33.341065, -112.143667 33.342154, -112.143744 33.342339, -112.142298 33.343257, -112.142152 33.343342, -112.142014 33.343145, -112.141693 33.343337, -112.141661 33.343375, -112.140799 33.343895, -112.139675 33.344572, -112.139597 33.344645, -112.139384 33.345239, -112.139369 33.345212, -112.139358 33.345191, -112.139355 33.345177, -112.139345 33.345142, -112.13935 33.344876, -112.139352 33.344765, -112.139354 33.344394, -112.139341 33.344327, -112.139332 33.344188, -112.139301 33.344087, -112.139298 33.344031, -112.139318 33.34394, -112.13935 33.343774, -112.13936 33.342954, -112.137226 33.342953, -112.135079 33.342953, -112.135083 33.342713, -112.135086 33.342491, -112.135102 33.3422, -112.135098 33.342128, -112.135079 33.342097, -112.135049 33.342079, -112.134979 33.342058, -112.134628 33.34114, -112.134604 33.341144, -112.133802 33.341144, -112.130704 33.341144, -112.130134 33.341144, -112.129543 33.341144, -112.127107 33.341144, -112.126502 33.341144, -112.126117 33.341144, -112.12611 33.342064, -112.12608 33.343457, -112.126076 33.343645, -112.125904 33.343645, -112.125904 33.343457, -112.125904 33.343245, -112.125517 33.343459, -112.124282 33.344144, -112.122589 33.344138, -112.122554 33.343108, -112.122527 33.342295, -112.122129 33.342286, -112.121854 33.34228, -112.121679 33.34227, -112.121494 33.342621, -112.121039 33.343483, -112.120939 33.342926, -112.120657 33.342944, -112.121206 33.342439, -112.121148 33.34189, -112.120797 33.341326, -112.119988 33.341336, -112.119279 33.341346, -112.117839 33.341367, -112.117831 33.343541, -112.117822 33.344979, -112.117715 33.34498, -112.117673 33.344952, -112.116367 33.344845, -112.10046 33.344858, -112.100464 33.348315, -112.100465 33.34886, -112.100471 33.349521, -112.098323 33.349401, -112.096359 33.349382, -112.096362 33.349327, -112.091673 33.349296, -112.091647 33.35006, -112.091703 33.351745, -112.091752 33.351771, -112.091775 33.355133, -112.091743 33.355837, -112.091445 33.355839, -112.090321 33.355845, -112.090155 33.355837, -112.08972 33.355863, -112.089459 33.355869, -112.088704 33.355884, -112.088619 33.355886, -112.087513 33.355888, -112.086867 33.355874, -112.086008 33.355874, -112.085421 33.355876, -112.083967 33.355878, -112.083724 33.355879, -112.08355 33.355909, -112.083491 33.355935, -112.083412 33.355998, -112.08325 33.355996, -112.082892 33.355993, -112.082694 33.355992, -112.082685 33.356249, -112.082492 33.356243, -112.078602 33.356135, -112.078102 33.356122, -112.078104 33.355963, -112.078115 33.355286, -112.075515 33.355514, -112.075086 33.356097, -112.07486 33.356037, -112.074263 33.356251, -112.073991 33.356611, -112.073894 33.356945, -112.073781 33.357312, -112.073193 33.357358, -112.072975 33.357376, -112.072954 33.357326, -112.072849 33.357126, -112.072778 33.357017, -112.072325 33.356243, -112.072228 33.356088, -112.072208 33.356064, -112.072172 33.356018, -112.072096 33.356034, -112.072031 33.356048, -112.071873 33.356068, -112.071495 33.356099, -112.071096 33.356105, -112.070869 33.356104, -112.070676 33.356104, -112.070134 33.356096, -112.06951 33.356095, -112.06886 33.356117, -112.068454 33.354984, -112.068327 33.354804, -112.065967 33.354768, -112.065975 33.356181, -112.065791 33.356046, -112.065476 33.35606, -112.06502 33.356052, -112.064785 33.356062, -112.064635 33.356077, -112.064395 33.356108, -112.064153 33.356127, -112.064069 33.356132, -112.06387 33.356144, -112.06367 33.356143, -112.063408 33.35613, -112.063229 33.356132, -112.063141 33.356138, -112.062979 33.356164, -112.06291 33.356181, -112.062814 33.357013, -112.061282 33.35701, -112.060293 33.357009, -112.059575 33.357008, -112.058295 33.356591, -112.057722 33.356405, -112.057709 33.356506, -112.05768 33.35674, -112.057669 33.356831, -112.057656 33.35694, -112.058033 33.35701, -112.05809 33.35702, -112.058453 33.357089, -112.058451 33.357329, -112.058449 33.35764, -112.058439 33.359322, -112.057677 33.359375, -112.057679 33.358463, -112.055969 33.358508, -112.055873 33.358213, -112.055138 33.358141, -112.054915 33.358495, -112.054804 33.358674, -112.05455 33.358658, -112.053648 33.358601, -112.053554 33.359132, -112.053082 33.35915, -112.050219 33.359265, -112.050192 33.359651, -112.050115 33.360745, -112.049858 33.360951, -112.049558 33.361193, -112.049257 33.361434, -112.048889 33.361731, -112.048881 33.361862, -112.048818 33.362894, -112.048818 33.362908, -112.048796 33.363255, -112.047104 33.363245, -112.046727 33.363245, -112.046134 33.36325, -112.044657 33.36325, -112.044485 33.36325, -112.044497 33.363301, -112.044494 33.364566, -112.044487 33.36659, -112.044299 33.366591, -112.044115 33.366607, -112.044006 33.366619, -112.043945 33.366635, -112.043895 33.366659, -112.043837 33.366703, -112.043804 33.366742, -112.043762 33.366808, -112.043724 33.366841, -112.043665 33.366867, -112.043605 33.366877, -112.04274 33.366875, -112.042401 33.366875, -112.042334 33.366881, -112.042326 33.365698, -112.042329 33.36538, -112.042336 33.364463, -112.04235 33.363782, -112.04235 33.363453, -112.042341 33.363388, -112.042316 33.363326, -112.042309 33.363281, -112.041363 33.363289, -112.040628 33.36329, -112.040195 33.363294, -112.038193 33.363314, -112.038185 33.363556, -112.036741 33.364524, -112.03674 33.364539, -112.035928 33.3645, -112.032835 33.364851, -112.032536 33.365509, -112.032165 33.366326, -112.031745 33.366565, -112.031519 33.366695, -112.030684 33.366553, -112.030123 33.366476, -112.029643 33.366425, -112.029131 33.366377, -112.02881 33.366335, -112.028489 33.366286, -112.028423 33.366289, -112.028354 33.366307, -112.028313 33.366328, -112.028296 33.36634, -112.027805 33.366861, -112.027775 33.366898, -112.027651 33.367047, -112.027559 33.367139, -112.02726 33.367403, -112.027185 33.367452, -112.026853 33.367629, -112.026795 33.367683, -112.026754 33.367744, -112.026732 33.367803, -112.026724 33.367865, -112.026754 33.368814, -112.0257 33.368828, -112.024473 33.368845, -112.023243 33.368864, -112.023181 33.368878, -112.023132 33.368904, -112.023119 33.368915, -112.023082 33.368961, -112.023066 33.369003, -112.023053 33.369222, -112.023051 33.369374, -112.023065 33.369765, -112.023097 33.370606, -112.023101 33.370706, -112.022937 33.370714, -112.021404 33.370731, -112.02093 33.370738, -112.020702 33.370747, -112.020221 33.370755, -112.019205 33.370772, -112.018428 33.370783, -112.018095 33.370795, -112.018032 33.370789, -112.017826 33.370739, -112.01773 33.370723, -112.017677 33.37072, -112.017157 33.370733, -112.016858 33.370742, -112.016591 33.370742, -112.016347 33.370803, -112.014406 33.371278, -112.01435 33.371948, -112.014344 33.371879, -112.01434 33.371834, -112.014341 33.37176, -112.014332 33.37174, -112.014312 33.371715, -112.014276 33.371685, -112.014222 33.371634, -112.014188 33.371584, -112.014158 33.371509, -112.014119 33.37142, -112.014078 33.371359, -112.011314 33.37146, -112.010777 33.371328, -112.009981 33.371884, -112.005755 33.371885, -112.005102 33.371885, -112.004318 33.373112, -112.000915 33.373117, -111.999631 33.371788, -111.999238 33.373117, -111.997883 33.373776, -111.997372 33.374074, -111.99685 33.374302, -111.996 33.374346, -111.9942 33.374346, -111.994157 33.374827, -111.994152 33.375352, -111.993492 33.375357, -111.993229 33.375359, -111.992277 33.375366, -111.992284 33.374589, -111.989841 33.374558, -111.989713 33.373948, -111.988365 33.373932, -111.988329 33.374719, -111.984485 33.374505, -111.984357 33.371675, -111.984322 33.370911, -111.980691 33.370966, -111.976439 33.370911, -111.976309 33.368705, -111.976147 33.365938, -111.976104 33.364272, -111.976103 33.364109, -111.976227 33.364094, -111.976462 33.364078, -111.976673 33.364078, -111.976842 33.364086, -111.977057 33.364109, -111.97836 33.364235, -111.978467 33.364241, -111.978623 33.364241, -111.978735 33.364233, -111.978814 33.364226, -111.97929 33.364142, -111.979445 33.364131, -111.979692 33.364125, -111.979843 33.364111, -111.980009 33.36408, -111.980132 33.364046, -111.980509 33.363869, -111.980793 33.363722, -111.980965 33.363644, -111.981079 33.363593, -111.981129 33.363573, -111.980906 33.363568, -111.980569 33.363562, -111.979886 33.363572, -111.979881 33.362829, -111.980331 33.362828, -111.980535 33.362826, -111.980532 33.362695, -111.982389 33.362696, -111.983154 33.362056, -111.98376 33.361514, -111.983748 33.359898, -111.984372 33.359622, -111.985912 33.358944, -111.986204 33.358699, -111.986665 33.358294, -111.987205 33.357828, -111.987775 33.357336, -111.988321 33.356865, -111.988612 33.356613, -111.989127 33.356168, -111.990148 33.356156, -111.991271 33.356145, -111.991641 33.355827, -111.991414 33.35528, -111.991977 33.355539, -111.99235 33.355218, -111.99464 33.355203, -111.995004 33.355196, -111.994738 33.354705, -111.994709 33.353817, -111.995231 33.353354, -111.99434 33.353368, -111.9934 33.353554, -111.994337 33.353027, -111.994333 33.352461, -111.997158 33.352429, -111.997169 33.35153, -111.99718 33.350573, -111.997193 33.349448, -111.997201 33.348763, -111.997199 33.346133, -111.997187 33.342969, -111.997095 33.341495, -111.997097 33.341399, -111.9971 33.341246, -111.998021 33.341492, -111.998684 33.341492, -112.004251 33.341488, -112.013849 33.341482, -112.013822 33.340631, -112.013771 33.339048, -112.01383 33.338233, -112.013839 33.338116, -112.013886 33.337477, -112.013868 33.337418, -112.013857 33.337345, -112.013868 33.336081, -112.013872 33.335613, -112.013879 33.334902, -112.014491 33.33477, -112.014495 33.334429, -112.014494 33.334212, -112.014492 33.333615, -112.014494 33.330482, -112.01449 33.328772, -112.014507 33.326838, -112.014489 33.324157, -112.014493 33.323334, -112.015421 33.322977, -112.016196 33.32312, -112.01733 33.323328, -112.02238 33.323317, -112.031439 33.323303, -112.031709 33.323301, -112.031807 33.323304, -112.031821 33.322353, -112.031821 33.320458, -112.031824 33.319673, -112.036208 33.317263, -112.041689 33.314489, -112.04127 33.316311, -112.040814 33.318295, -112.040989 33.318689, -112.041273 33.318852, -112.04133 33.318886, -112.041045 33.319042, -112.040929 33.319116, -112.040665 33.319315, -112.040554 33.319459, -112.040414 33.319642, -112.043961 33.319629, -112.048508 33.319613, -112.049112 33.31961, -112.051432 33.319617, -112.052375 33.319619, -112.052989 33.319621, -112.05374 33.319623, -112.054183 33.319093, -112.054216 33.319114, -112.054262 33.319125, -112.055318 33.319125, -112.055416 33.319113, -112.055481 33.319096, -112.055279 33.319628, -112.055825 33.319627, -112.056692 33.319626, -112.058193 33.319636, -112.06513 33.319655, -112.068988 33.319665, -112.069288 33.319153, -112.069402 33.31915, -112.069495 33.31915, -112.070394 33.319669, -112.08362 33.319706, -112.083618 33.316045, -112.083618 33.313879, -112.083618 33.313246, -112.082927 33.312355, -112.082982 33.31011, -112.083617 33.309731, -112.083616 33.309547, -112.083616 33.309303, -112.083616 33.308931, -112.082629 33.308911, -112.08255 33.308976, -112.08259 33.309268, -112.082609 33.309372, -112.082505 33.309372, -112.082347 33.30924, -112.082158 33.309202, -112.081869 33.309227, -112.081588 33.309252, -112.081038 33.309538, -112.08073 33.309784, -112.080468 33.310115, -112.080311 33.310142, -112.080102 33.310077, -112.080122 33.309944, -112.080128 33.309901, -112.080193 33.309799, -112.080309 33.309689, -112.08045 33.309563, -112.080621 33.309424, -112.080635 33.309412, -112.080886 33.309296, -112.081712 33.308916, -112.081948 33.308762, -112.081968 33.308563, -112.081732 33.308432, -112.082039 33.307993, -112.082066 33.307817, -112.082268 33.307597, -112.082475 33.307718, -112.083615 33.307623, -112.083615 33.306579, -112.083615 33.30524, -112.083615 33.305183, -112.083615 33.305142, -112.085158 33.305137, -112.085353 33.305136, -112.087636 33.305128, -112.089855 33.305118, -112.091222 33.305111, -112.09241 33.305105, -112.092984 33.305103, -112.09465 33.305093, -112.100358 33.305067, -112.100437 33.305066, -112.101333 33.305058, -112.106209 33.305014, -112.124827 33.304851, -112.137698 33.304737, -112.14299 33.30469, -112.147914 33.304647, -112.144482 33.301799, -112.143293 33.300819, -112.143127 33.30068, -112.142751 33.300292, -112.141936 33.299433, -112.139439 33.296807, -112.138747 33.296081, -112.137377 33.294644, -112.136336 33.293552, -112.134611 33.291744, -112.134077 33.291241, -112.133424 33.290629, -112.133789 33.29026, -112.133066 33.290292, -112.129597 33.290446, -112.128684 33.290486, -112.117252 33.290992, -112.117116 33.290998, -112.117037 33.290995, -112.114971 33.291016, -112.113188 33.291021, -112.111188 33.291032, -112.107016 33.291054, -112.105891 33.291054, -112.105269 33.291054, -112.103905 33.291064, -112.103181 33.291058, -112.102356 33.291035, -112.101682 33.291012, -112.101225 33.291006, -112.101156 33.291006, -112.100399 33.291003, -112.100337 33.291003, -112.099723 33.291005, -112.097073 33.291016, -112.096589 33.291016, -112.094368 33.291021, -112.093575 33.291023, -112.093198 33.291024, -112.092587 33.29103, -112.088269 33.291036, -112.087723 33.291035, -112.086834 33.29104, -112.085347 33.291033, -112.085142 33.291034, -112.083538 33.291043, -112.082977 33.291043, -112.082144 33.291038, -112.081612 33.291041, -112.080601 33.29105, -112.07889 33.291052, -112.078398 33.291052, -112.074479 33.291065, -112.070237 33.291074, -112.067118 33.291081, -112.064713 33.291083, -112.063419 33.291089, -112.06105 33.291096, -112.05785 33.291103, -112.05342 33.291106, -112.050962 33.291111, -112.048147 33.291117, -112.040298 33.291122, -112.033987 33.291131, -112.032157 33.291133, -112.031952 33.291131, -112.031722 33.291128, -112.029748 33.291128, -112.027296 33.291128, -112.023504 33.291124, -112.018388 33.29113, -112.014486 33.291142, -112.012294 33.291142, -112.011778 33.29115, -112.008845 33.291251, -112.00545 33.291362, -112.005318 33.291367, -112.003604 33.291421, -112.002931 33.291444, -112.002841 33.291448, -112.000984 33.291528, -112.000128 33.291549, -111.997413 33.291562, -111.997224 33.291569, -111.997071 33.291578, -111.995381 33.291584, -111.992036 33.291597, -111.989813 33.291421, -111.9898 33.291421, -111.987151 33.290841, -111.986225 33.290825, -111.981192 33.290742, -111.98042 33.290681, -111.980167 33.290681, -111.979987 33.290697, -111.97978 33.290732, -111.979613 33.290743, -111.977973 33.29074, -111.975912 33.290748, -111.973933 33.290749, -111.972455 33.290737, -111.972209 33.290756, -111.972121 33.290763, -111.971867 33.290765, -111.97155 33.29077, -111.971364 33.289983, -111.971246 33.289485, -111.971155 33.289127, -111.970946 33.288441, -111.970602 33.287399, -111.970472 33.287079, -111.970292 33.286589, -111.9701 33.286111, -111.970088 33.285972, -111.970075 33.285894, -111.970006 33.285719, -111.96991 33.285493, -111.969764 33.285533, -111.96971 33.285448, -111.969626 33.285333, -111.969516 33.285199, -111.969367 33.285041, -111.969271 33.284954, -111.969131 33.284843, -111.969035 33.284776, -111.968797 33.284633, -111.968625 33.284546, -111.968409 33.284453, -111.968324 33.284421, -111.968225 33.284385, -111.968064 33.284334, -111.967826 33.284273, -111.967219 33.284139, -111.967272 33.28398, -111.967808 33.282455, -111.967938 33.282076, -111.968039 33.281784, -111.967843 33.281461, -111.967749 33.281317, -111.967712 33.28126, -111.967584 33.2816, -111.966486 33.284543, -111.965369 33.284577, -111.965341 33.28456, -111.965308 33.284554, -111.961537 33.284557, -111.96153 33.280869, -111.956883 33.280932, -111.95705 33.284562, -111.95314 33.284565, -111.952945 33.28457, -111.952929 33.284574, -111.952883 33.284596, -111.952855 33.284629, -111.952845 33.284666, -111.952851 33.285798, -111.952864 33.288189, -111.945862 33.288198, -111.945813 33.288207, -111.945766 33.288224, -111.945711 33.28826, -111.945689 33.28828, -111.945648 33.288345, -111.945635 33.288395, -111.945639 33.288791, -111.945648 33.289797, -111.945657 33.289906, -111.94567 33.290063, -111.945666 33.290308, -111.945661 33.290611, -111.945662 33.29073, -111.945665 33.290871, -111.945556 33.290869, -111.9453 33.290723, -111.940016 33.290705, -111.937684 33.290533, -111.935996 33.290587, -111.935806 33.290593, -111.934475 33.290636, -111.930561 33.290636, -111.929889 33.290636, -111.929256 33.290636, -111.927834 33.290695, -111.926412 33.290782, -111.925354 33.290782, -111.923917 33.290899, -111.922625 33.290905, -111.922022 33.290913, -111.920367 33.290906, -111.919955 33.290909, -111.919611 33.290902, -111.919391 33.290897, -111.91908 33.290916, -111.917845 33.290914, -111.916423 33.290987, -111.915521 33.291001, -111.914966 33.291001, -111.913007 33.29102, -111.911112 33.291038, -111.910586 33.291041, -111.893596 33.291148, -111.893449 33.283244, -111.893469 33.279364, -111.893454 33.276029, -111.893455 33.27592, -111.893431 33.268656, -111.893328 33.263899, -111.893303 33.262737, -111.893276 33.261406, -111.893276 33.261387, -111.893229 33.261377, -111.893076 33.261363, -111.892821 33.261358, -111.891812 33.26135, -111.890697 33.261344, -111.888741 33.261384, -111.888706 33.259908, -111.888693 33.259602, -111.888675 33.259362, -111.888644 33.259158, -111.888597 33.25896, -111.88854 33.258783, -111.888466 33.258608, -111.888369 33.258426, -111.888193 33.258133, -111.888022 33.25788, -111.887887 33.257703, -111.887699 33.257489, -111.887598 33.257392, -111.887477 33.257289, -111.887289 33.257156, -111.88706 33.25701, -111.886775 33.256846, -111.886529 33.25672, -111.886291 33.256619, -111.886153 33.25657, -111.885959 33.256511, -111.885759 33.256462, -111.885415 33.256394, -111.884418 33.256247, -111.884301 33.256217, -111.884179 33.256191, -111.884201 33.25612, -111.884216 33.256022, -111.8843 33.255465, -111.88431 33.255105, -111.884315 33.254928, -111.884308 33.254593, -111.884264 33.254239, -111.884255 33.254167, -111.884146 33.253286, -111.883844 33.250666, -111.883837 33.250525, -111.883833 33.250428, -111.883774 33.249893, -111.883725 33.249558, -111.883706 33.249401, -111.883658 33.249114, -111.883595 33.248835, -111.883514 33.248553, -111.883493 33.248502, -111.883457 33.248413, -111.883336 33.24814, -111.883205 33.247869, -111.882992 33.247467, -111.8829 33.247326, -111.882726 33.247109, -111.882513 33.246874, -111.882744 33.246723, -111.88283 33.246661, -111.882905 33.246589, -111.882961 33.246519, -111.883014 33.246432, -111.883048 33.246353, -111.883072 33.24626, -111.883082 33.246164, -111.883076 33.24607, -111.883071 33.245907, -111.883315 33.245908, -111.883434 33.245914, -111.883554 33.245935, -111.883682 33.245972, -111.883802 33.246024, -111.88391 33.246087, -111.883998 33.246155, -111.884084 33.246242, -111.884135 33.246308, -111.884334 33.246622, -111.884516 33.246912, -111.884553 33.246972, -111.884563 33.246982, -111.884592 33.247009, -111.884626 33.247028, -111.884677 33.247044, -111.884722 33.247048, -111.886987 33.247006, -111.88702 33.246997, -111.887059 33.246966, -111.887085 33.246924, -111.887094 33.246899, -111.887093 33.246886, -111.892952 33.24682, -111.892906 33.246504, -111.892896 33.24636, -111.892791 33.242167, -111.892781 33.242027, -111.892751 33.241874, -111.8927 33.241711, -111.892652 33.24154, -111.892624 33.241397, -111.892606 33.241255, -111.89256 33.23959, -111.892482 33.236567, -111.892477 33.236288, -111.892859 33.232827, -111.892885 33.232386, -111.893116 33.228495, -111.893075 33.225569, -111.893084 33.222215, -111.893098 33.219248, -111.893101 33.218573, -111.893092 33.217801, -111.893081 33.21688, -111.893073 33.214834, -111.893066 33.212792, -111.893073 33.212564, -111.893067 33.212529, -111.893049 33.212491, -111.893084 33.212452, -111.893116 33.212395, -111.893132 33.21233, -111.89312 33.212021, -111.893109 33.211756, -111.89309 33.207076, -111.893084 33.205517, -111.893085 33.204811, -111.893085 33.204709, -111.893085 33.204645, -111.893085 33.204409, -111.89357 33.204433, -111.894832 33.204445, -111.894904 33.204433, -111.894938 33.2044, -111.894941 33.204347, -111.894927 33.203797, -111.893911 33.203801, -111.893624 33.203791, -111.893514 33.20375, -111.893407 33.203634, -111.893369 33.203564, -111.893307 33.203485, -111.893228 33.203432, -111.893197 33.203412, -111.893053 33.203347, -111.892899 33.203326, -111.892554 33.203282, -111.891975 33.203336, -111.890604 33.203357, -111.889313 33.203377, -111.8889 33.203381, -111.888302 33.203389, -111.886788 33.203409, -111.884346 33.203432, -111.884108 33.203435, -111.881584 33.203459, -111.87988 33.203471, -111.876104 33.20349, -111.876032 33.20349, -111.875996 33.20349, -111.87594 33.203505, -111.875889 33.203533, -111.875847 33.203545, -111.875804 33.203547, -111.875734 33.203541, -111.875641 33.203525, -111.87563 33.203525, -111.875564 33.203525, -111.875473 33.203525, -111.875273 33.203528, -111.872373 33.203558, -111.871143 33.203571, -111.871118 33.203196, -111.87112 33.202971, -111.871107 33.202927, -111.871097 33.202909, -111.871038 33.202889, -111.870964 33.20288, -111.870218 33.202864, -111.869739 33.202872, -111.869198 33.202869, -111.868558 33.202872, -111.8668 33.202841, -111.866802 33.201993, -111.861544 33.202002, -111.860505 33.202005, -111.858409 33.202039, -111.858161 33.202044, -111.858211 33.202644, -111.858247 33.202866, -111.858269 33.202995, -111.858351 33.203306, -111.858398 33.203511, -111.858422 33.203775, -111.858286 33.203778, -111.857724 33.203788, -111.857198 33.203797, -111.857184 33.203797, -111.855618 33.203814, -111.854698 33.203826, -111.853964 33.203837, -111.850605 33.203884, -111.850487 33.203884, -111.849764 33.20389, -111.849562 33.203892, -111.849466 33.203894, -111.849232 33.203898, -111.848792 33.203906, -111.848468 33.20391, -111.847742 33.203918, -111.847436 33.203922, -111.846936 33.203928, -111.846444 33.203933, -111.84619 33.203936, -111.84544 33.203944, -111.845367 33.203946, -111.845293 33.203947, -111.844433 33.203955, -111.844179 33.203958, -111.842678 33.203975, -111.842655 33.203975, -111.841507 33.203991, -111.84122 33.203994, -111.841176 33.203995, -111.841132 33.203996, -111.841119 33.203996, -111.841074 33.203997, -111.841025 33.203466, -111.840973 33.202683, -111.840971 33.202267, -111.840966 33.201504, -111.840964 33.201236, -111.840962 33.201017, -111.840959 33.200642, -111.846594 33.200657, -111.846682 33.200263, -111.846742 33.199883, -111.846771 33.199301, -111.84677 33.198704, -111.846741 33.198582, -111.846626 33.198152, -111.846342 33.197815, -111.845989 33.197578, -111.845696 33.197463, -111.845238 33.197504, -111.843768 33.197518, -111.841618 33.197483, -111.841401 33.197468, -111.841271 33.197446, -111.841185 33.197432, -111.840945 33.197437, -111.840944 33.198983, -111.840954 33.199993, -111.840904 33.200322, -111.840876 33.201017, -111.840863 33.201369, -111.84086 33.202005, -111.840852 33.202552, -111.840886 33.203351, -111.840939 33.203999, -111.840708 33.204003, -111.84017 33.204013, -111.840068 33.204016, -111.840029 33.204018, -111.839965 33.20402, -111.839954 33.20402, -111.839942 33.20402, -111.83986 33.204021, -111.839455 33.20403, -111.838513 33.204042, -111.837576 33.204054, -111.836639 33.204068, -111.834826 33.204092, -111.833014 33.204115, -111.832803 33.204118, -111.832533 33.204121, -111.831779 33.204133, -111.831488 33.204138, -111.831196 33.204141, -111.830556 33.204152, -111.829074 33.204167, -111.828135 33.20418, -111.827869 33.204184, -111.827634 33.204187, -111.827411 33.20419, -111.826908 33.204197, -111.826449 33.2042, -111.825974 33.204207, -111.825353 33.204217, -111.825208 33.204218, -111.824508 33.204224, -111.824091 33.204232, -111.823848 33.204236, -111.823691 33.204239, -111.823451 33.204242, -111.822172 33.204257, -111.820665 33.204284, -111.819871 33.204312, -111.819497 33.204322, -111.81905 33.204335, -111.818745 33.204343, -111.817726 33.204367, -111.817413 33.204374, -111.81738 33.204374, -111.81654 33.204385, -111.816368 33.204386, -111.815407 33.204399, -111.815295 33.2044, -111.815074 33.204407, -111.814249 33.204434, -111.81216 33.204501, -111.811873 33.20451, -111.811681 33.204517, -111.811199 33.204534, -111.810056 33.204573, -111.807889 33.204645, -111.806905 33.204673, -111.806458 33.204687, -111.806371 33.204686, -111.806293 33.204688, -111.806052 33.204694, -111.805715 33.204696, -111.802549 33.204706, -111.802153 33.204711, -111.801937 33.204711, -111.801843 33.204711, -111.800434 33.204713, -111.797936 33.20472, -111.797635 33.204721, -111.796022 33.204726, -111.793825 33.204735, -111.793454 33.204735, -111.789206 33.204729, -111.789143 33.204723, -111.7884 33.204729, -111.787703 33.204714, -111.787428 33.204698, -111.786838 33.204698, -111.785155 33.204694, -111.783347 33.20469, -111.781375 33.204689, -111.780846 33.204689, -111.77943 33.204687, -111.777711 33.204684, -111.776653 33.204687, -111.776564 33.204687, -111.775039 33.204687, -111.774207 33.204683, -111.773889 33.204681, -111.772702 33.204681, -111.772668 33.204682, -111.772587 33.204684, -111.772501 33.204698, -111.772399 33.20472, -111.771982 33.20474, -111.770468 33.204738, -111.768884 33.204741, -111.768855 33.204754, -111.768841 33.204756, -111.768411 33.204747, -111.768344 33.204746, -111.768157 33.204742, -111.76617 33.204737, -111.764406 33.204735, -111.764147 33.204726, -111.764066 33.204735, -111.764034 33.204743, -111.764008 33.204749, -111.76396 33.204759, -111.76381 33.20478, -111.763687 33.204786, -111.763665 33.204787, -111.763545 33.204787, -111.761382 33.204777, -111.760327 33.20478, -111.759927 33.204782, -111.758304 33.204783, -111.757844 33.204781, -111.756201 33.204772, -111.755426 33.204782, -111.755428 33.204863, -111.755417 33.204949, -111.75539 33.208533, -111.755455 33.209624, -111.755454 33.209653, -111.755448 33.209872, -111.755441 33.210577, -111.755432 33.211437, -111.755426 33.211965, -111.755431 33.212097, -111.755432 33.212123, -111.755449 33.215463, -111.755463 33.217401, -111.755449 33.217931, -111.755422 33.218587, -111.755422 33.218629, -111.755418 33.219086, -111.755418 33.219104, -111.755418 33.219172, -111.755417 33.219251, -111.754892 33.219267, -111.75302 33.219282, -111.751092 33.219292, -111.749388 33.219304, -111.746746 33.219316, -111.74307 33.219327, -111.74168 33.219335, -111.741145 33.219338, -111.741068 33.219338, -111.74096 33.219338, -111.74075 33.21934, -111.740342 33.219341, -111.739699 33.219346, -111.737861 33.219347, -111.737491 33.219344, -111.734723 33.21933, -111.734103 33.219327, -111.733506 33.219326, -111.731765 33.219316, -111.731478 33.219313, -111.731478 33.219414, -111.731479 33.219793, -111.731479 33.219906, -111.729961 33.219906, -111.729805 33.21978, -111.72931 33.219304, -111.724859 33.219282, -111.721282 33.219251, -111.720468 33.219254, -111.720473 33.219116, -111.720478 33.218973, -111.720481 33.218496, -111.720473 33.217695, -111.720472 33.216309, -111.72047 33.215915, -111.720468 33.215589, -111.72506 33.215293, -111.725937 33.215037, -111.725938 33.214785, -111.725939 33.214066, -111.72593 33.212967, -111.725909 33.211944, -111.726298 33.211964, -111.726888 33.211979, -111.727227 33.212001, -111.728704 33.212001, -111.728927 33.211992, -111.728989 33.211995, -111.729049 33.212013, -111.729082 33.212034, -111.729137 33.212096, -111.729143 33.212057, -111.729156 33.212018, -111.729197 33.211972, -111.729217 33.211933, -111.729234 33.211866, -111.729229 33.211464, -111.729224 33.210749, -111.729212 33.210177, -111.729209 33.210133, -111.728584 33.21011, -111.728395 33.210112, -111.727035 33.21013, -111.723469 33.210122, -111.722075 33.210119, -111.721447 33.210117, -111.720456 33.210151, -111.720452 33.209651, -111.720452 33.209159, -111.720452 33.208291, -111.720452 33.208234, -111.720453 33.208115, -111.720453 33.207394, -111.720453 33.207172, -111.720453 33.207093, -111.720453 33.20703, -111.720453 33.206968, -111.720433 33.206615, -111.720417 33.206504, -111.720479 33.206479, -111.720503 33.206474, -111.720657 33.20646, -111.725084 33.206471, -111.725329 33.206471, -111.726419 33.206473, -111.726713 33.206474, -111.72745 33.20648, -111.727655 33.206482, -111.727956 33.206484, -111.728312 33.206479, -111.728345 33.206479, -111.728379 33.206477, -111.728518 33.20647, -111.728842 33.206482, -111.729145 33.206498, -111.729136 33.205931, -111.729126 33.205664, -111.729115 33.205376, -111.729114 33.205336, -111.729108 33.20483, -111.729127 33.204794, -111.729154 33.204769, -111.729191 33.204752, -111.729427 33.204721, -111.72992 33.204707, -111.730097 33.204706, -111.730426 33.204701, -111.730682 33.204707, -111.730734 33.204712, -111.731074 33.204743, -111.731578 33.20476, -111.733159 33.204763, -111.734609 33.204752, -111.736451 33.204748, -111.735038 33.204679, -111.730734 33.204678, -111.727612 33.204677, -111.717026 33.204673, -111.71701 33.20467, -111.716787 33.204647, -111.716556 33.204638, -111.716222 33.204638, -111.716111 33.204638, -111.714901 33.204639, -111.714136 33.20464, -111.713232 33.204641, -111.712134 33.204639, -111.712031 33.204639, -111.711827 33.204638, -111.711714 33.204638, -111.711428 33.204638, -111.711061 33.204638, -111.710711 33.204638, -111.709722 33.204638, -111.707785 33.204638, -111.707504 33.204638, -111.70345 33.204636, -111.703185 33.204636, -111.702323 33.204636, -111.701031 33.204635, -111.699064 33.204634, -111.698817 33.204634, -111.698287 33.204635, -111.697703 33.204636, -111.697442 33.204637, -111.696611 33.204635, -111.69661 33.205247, -111.695908 33.205244, -111.69591 33.204634, -111.695221 33.204634, -111.694676 33.204635, -111.694532 33.204635, -111.69418 33.204637, -111.694111 33.204637, -111.693966 33.204636, -111.693747 33.204635, -111.693232 33.204633, -111.692831 33.204631, -111.692788 33.204631, -111.692069 33.204633, -111.691838 33.204634, -111.691422 33.204635, -111.691 33.204636, -111.690448 33.204638, -111.690212 33.204639, -111.690233 33.20497, -111.690242 33.205525, -111.690243 33.206545, -111.687178 33.206536, -111.686466 33.206527, -111.685904 33.206525, -111.685908 33.205637, -111.685911 33.204637, -111.684839 33.204641, -111.683667 33.204645, -111.681683 33.204656, -111.67974 33.204662, -111.679609 33.204662, -111.679415 33.204664, -111.678785 33.20467, -111.677626 33.204679, -111.677438 33.204681, -111.677449 33.205039, -111.677449 33.205073, -111.677462 33.206657, -111.677464 33.206998, -111.677465 33.207058, -111.677467 33.207608, -111.67747 33.208354, -111.67713 33.208374, -111.676181 33.208379, -111.675639 33.208367, -111.675284 33.208388, -111.675165 33.208399, -111.674123 33.208401, -111.67349 33.208416, -111.673339 33.208433, -111.673266 33.208427, -111.673242 33.208422, -111.673233 33.207484, -111.673237 33.206642, -111.673232 33.206183, -111.673231 33.206076, -111.673225 33.20496, -111.673224 33.204713, -111.672189 33.204721, -111.670819 33.204731, -111.669438 33.204742, -111.669087 33.204745, -111.668981 33.204746, -111.66879 33.204748, -111.668759 33.204748, -111.668625 33.204749, -111.668607 33.204595, -111.668623 33.204063, -111.668623 33.203339, -111.66863 33.202925, -111.668638 33.202646, -111.668624 33.202384, -111.668621 33.202323, -111.668622 33.202275, -111.668629 33.201971, -111.66852 33.201977, -111.668397 33.20197, -111.668243 33.201936, -111.668161 33.201825, -111.668149 33.20171, -111.6682 33.201621, -111.668261 33.201575, -111.66838 33.201566, -111.668582 33.201619, -111.668633 33.20162, -111.668632 33.200964, -111.668633 33.200602, -111.668639 33.200328, -111.668641 33.199829, -111.668652 33.199311, -111.668651 33.199169, -111.668651 33.199081, -111.668646 33.198256, -111.668652 33.197884, -111.668656 33.197654, -111.668652 33.197167, -111.668541 33.197165, -111.668386 33.197131, -111.667943 33.196923, -111.667808 33.196876, -111.667668 33.196863, -111.667555 33.196917, -111.667496 33.197007, -111.667461 33.197105, -111.667483 33.197204, -111.667588 33.197298, -111.667726 33.197405, -111.667816 33.197502, -111.667881 33.197771, -111.667893 33.197862, -111.667372 33.197866, -111.667149 33.19787, -111.666927 33.197868, -111.666475 33.197868, -111.666213 33.197868, -111.666218 33.198542, -111.666226 33.199161, -111.663921 33.199161, -111.663923 33.200342, -111.663735 33.200344, -111.66342 33.200347, -111.662844 33.200344, -111.662694 33.200342, -111.662034 33.20035, -111.661886 33.200352, -111.661623 33.20035, -111.661337 33.200349, -111.660405 33.200339, -111.66039 33.200429, -111.660394 33.200482, -111.660354 33.200539, -111.660322 33.200569, -111.660295 33.200592, -111.660233 33.200626, -111.660093 33.200643, -111.659918 33.200653, -111.659743 33.200654, -111.659618 33.200633, -111.659565 33.200573, -111.659549 33.200508, -111.659551 33.200345, -111.658217 33.200356, -111.657881 33.200352, -111.657469 33.200353, -111.65738 33.200353, -111.657055 33.200357, -111.657032 33.200357, -111.656896 33.200359, -111.656825 33.200358, -111.656825 33.200153, -111.656767 33.199148, -111.656862 33.199149, -111.657031 33.199148, -111.660472 33.199156, -111.66068 33.199156, -111.660657 33.198402, -111.660642 33.197871, -111.660481 33.197872, -111.660329 33.197873, -111.66028 33.197873, -111.660236 33.197996, -111.660161 33.198104, -111.660074 33.198181, -111.65996 33.198228, -111.659862 33.198231, -111.659787 33.198206, -111.659708 33.19815, -111.659652 33.198068, -111.659619 33.197989, -111.659596 33.197915, -111.659589 33.197868, -111.65953 33.197868, -111.659269 33.19787, -111.65925 33.198012, -111.65917 33.198118, -111.659081 33.198168, -111.658949 33.198195, -111.65875 33.198211, -111.658608 33.198174, -111.658508 33.198105, -111.658481 33.198043, -111.658461 33.197876, -111.658201 33.197874, -111.657642 33.197878, -111.657652 33.19803, -111.657654 33.198338, -111.657625 33.1984, -111.657562 33.198424, -111.657494 33.198422, -111.657425 33.198394, -111.657368 33.19836, -111.657356 33.19832, -111.657337 33.1982, -111.657308 33.197957, -111.657308 33.197885, -111.657142 33.197889, -111.656713 33.197884, -111.656588 33.197888, -111.656363 33.197893, -111.656096 33.197895, -111.655923 33.197901, -111.655799 33.197901, -111.655805 33.198206, -111.655807 33.198363, -111.655793 33.199692, -111.655815 33.200135, -111.655814 33.200358, -111.655786 33.201022, -111.655778 33.201584, -111.651559 33.201599, -111.65156 33.20202, -111.651568 33.203692, -111.651573 33.204917, -111.651558 33.204918, -111.651426 33.204918, -111.651402 33.204804, -111.651399 33.204741, -111.651385 33.204667, -111.651379 33.20457, -111.651386 33.203674, -111.650821 33.203606, -111.649706 33.203499, -111.64971 33.20355, -111.649737 33.203597, -111.649789 33.203643, -111.649844 33.203684, -111.649882 33.203736, -111.649909 33.203805, -111.649916 33.203837, -111.64993 33.203898, -111.649966 33.204112, -111.649956 33.204304, -111.64994 33.204455, -111.649943 33.20451, -111.649967 33.204776, -111.64999 33.204927, -111.649772 33.204929, -111.648964 33.204935, -111.648442 33.204938, -111.64738 33.204945, -111.646648 33.204951, -111.646532 33.204952, -111.646012 33.204956, -111.645198 33.204963, -111.644684 33.204966, -111.644445 33.204968, -111.644164 33.20497, -111.643693 33.204974, -111.643536 33.204975, -111.643419 33.204976, -111.643145 33.204977, -111.642533 33.204984, -111.641921 33.20499, -111.640937 33.204996, -111.640342 33.205, -111.639972 33.205003, -111.639382 33.205007, -111.638832 33.205011, -111.638762 33.205011, -111.638755 33.205466, -111.63875 33.205813, -111.638762 33.207289, -111.638768 33.208202, -111.638772 33.208509, -111.638795 33.20863, -111.638796 33.208655, -111.638639 33.208691, -111.638541 33.208702, -111.638465 33.2087, -111.63835 33.208686, -111.638182 33.208682, -111.637897 33.208692, -111.63767 33.208692, -111.636326 33.208695, -111.635392 33.208689, -111.63477 33.208695, -111.634475 33.208692, -111.634474 33.207101, -111.634457 33.206246, -111.634453 33.205835, -111.634449 33.205491, -111.634445 33.205067, -111.634382 33.204013, -111.634304 33.203698, -111.634186 33.203423, -111.633963 33.20299, -111.633517 33.202453, -111.633111 33.202112, -111.632692 33.201837, -111.632207 33.201614, -111.631722 33.201444, -111.631325 33.201348, -111.629924 33.201138, -111.629896 33.201134, -111.629525 33.201082, -111.627576 33.200812, -111.627238 33.200756, -111.626683 33.200682, -111.626189 33.200615, -111.625632 33.20053, -111.625283 33.200477, -111.625215 33.200467, -111.625093 33.200452, -111.62502 33.200449, -111.624675 33.200407, -111.624227 33.200347, -111.624012 33.200317, -111.623244 33.199305, -111.622853 33.198867, -111.622389 33.19836, -111.622261 33.198203, -111.622084 33.198076, -111.621933 33.197968, -111.621213 33.19726, -111.620994 33.197046, -111.620077 33.196157, -111.619518 33.195562, -111.618904 33.194985, -111.618218 33.194322, -111.6177 33.193835, -111.617423 33.193514, -111.617256 33.193226, -111.61719 33.193002, -111.617178 33.192884, -111.617141 33.192498, -111.617105 33.192014, -111.617057 33.191758, -111.616919 33.191586, -111.616917 33.190984, -111.616916 33.190654, -111.616917 33.189821, -111.616917 33.189054, -111.61691 33.187511, -111.616879 33.18642, -111.61688 33.185158, -111.616881 33.184624, -111.616874 33.183411, -111.616869 33.182381, -111.616879 33.180377, -111.616878 33.176395, -111.616875 33.176124, -111.616099 33.176114, -111.614652 33.176113, -111.614402 33.176112, -111.613319 33.17611, -111.612778 33.176108, -111.61208 33.176112, -111.611761 33.176114, -111.61004 33.176108, -111.60985 33.176108, -111.608746 33.176101, -111.608259 33.176105, -111.607523 33.176111, -111.606661 33.176119, -111.60634 33.176118, -111.605207 33.176116, -111.604403 33.176117, -111.604279 33.176118, -111.604008 33.17613, -111.602627 33.176192, -111.602327 33.176177, -111.602094 33.176057, -111.601869 33.175899, -111.601613 33.175734, -111.601446 33.175613, -111.601396 33.175576, -111.60111 33.175404, -111.600877 33.175268, -111.600667 33.175126, -111.600404 33.174953, -111.600171 33.174833, -111.599893 33.174637, -111.599781 33.17454, -111.599743 33.173729, -111.599687 33.173492, -111.599646 33.173322, -111.599641 33.173302, -111.599636 33.17311, -111.599622 33.172559, -111.600309 33.172572, -111.600908 33.17257, -111.601852 33.172569, -111.60186 33.172707, -111.601849 33.172818, -111.601822 33.172938, -111.601828 33.17309, -111.601858 33.173175, -111.601886 33.173198, -111.601937 33.173201, -111.602065 33.173177, -111.602137 33.173155, -111.602171 33.173116, -111.602201 33.173051, -111.602233 33.172963, -111.60224 33.172919, -111.602206 33.172801, -111.602211 33.172752, -111.602256 33.172635, -111.602288 33.172568, -111.602836 33.172568, -111.603828 33.172567, -111.603949 33.172567, -111.603959 33.1723, -111.603976 33.172008, -111.604015 33.17192, -111.604078 33.17187, -111.604205 33.171854, -111.604324 33.171872, -111.60444 33.171944, -111.604559 33.172071, -111.604587 33.172189, -111.604588 33.172344, -111.604577 33.172566, -111.604662 33.172565, -111.605515 33.172565, -111.606647 33.172562, -111.608182 33.172556, -111.608275 33.172556, -111.609366 33.172553, -111.610368 33.172559, -111.611175 33.172564, -111.611495 33.172564, -111.6116 33.172562, -111.612624 33.172558, -111.612615 33.171331, -111.612615 33.168882, -111.611481 33.168886, -111.610359 33.168891, -111.609496 33.168884, -111.60922 33.168885, -111.608244 33.168885, -111.608179 33.168886, -111.607646 33.168883, -111.607246 33.16888, -111.606791 33.168875, -111.605375 33.168881, -111.604946 33.168883, -111.605005 33.168675, -111.605069 33.168473, -111.605159 33.168148, -111.60526 33.16794, -111.605307 33.167808, -111.605305 33.167446, -111.605343 33.167046, -111.603763 33.167048, -111.603758 33.167686, -111.603711 33.168891, -111.603549 33.168892, -111.602246 33.168889, -111.601669 33.168891, -111.601042 33.168894, -111.5998 33.168897, -111.599658 33.168892, -111.599281 33.168878, -111.597408 33.168867, -111.596536 33.168872, -111.595526 33.168878, -111.594857 33.168888, -111.594676 33.168889, -111.59346 33.168885, -111.593176 33.168886, -111.592276 33.168888, -111.591966 33.168889, -111.58811 33.168898, -111.586984 33.168926, -111.586772 33.168914, -111.586693 33.168894, -111.58664 33.168841, -111.586624 33.168763, -111.586614 33.168234, -111.58662 33.167928, -111.586643 33.166791, -111.586649 33.166432, -111.586647 33.165355, -111.586649 33.164496, -111.586647 33.163798, -111.586618 33.163688, -111.586593 33.163642, -111.586574 33.163606, -111.586485 33.163491, -111.586405 33.163517, -111.586283 33.163531, -111.586084 33.163534, -111.585189 33.163535, -111.584475 33.163528, -111.584101 33.163536, -111.583642 33.163534, -111.583191 33.163525, -111.58287 33.163527, -111.5821 33.163525, -111.581877 33.163507, -111.581765 33.163518, -111.581633 33.163541, -111.581548 33.163551, -111.581402 33.163534, -111.581118 33.163511, -111.58086 33.163507, -111.580582 33.163514, -111.580361 33.163538, -111.580141 33.163545, -111.579767 33.163534, -111.579705 33.162667, -111.57964 33.161659, -111.579151 33.161655, -111.575969 33.161673, -111.575837 33.161665, -111.575797 33.161651, -111.575756 33.161639, -111.575648 33.161554, -111.575582 33.161414, -111.575528 33.160559, -111.575455 33.159654, -111.575408 33.158791, -111.575372 33.158289, -111.575354 33.158048, -111.572721 33.158057, -111.571689 33.158065, -111.571119 33.15806, -111.571026 33.158031, -111.571002 33.157998, -111.57098 33.15795, -111.570952 33.157691, -111.570927 33.157459, -111.570924 33.157422, -111.5709 33.156985, -111.570885 33.156674, -111.570856 33.156205, -111.570717 33.154445, -111.570661 33.153503, -111.570621 33.152818, -111.57053 33.151553, -111.570778 33.151527, -111.570957 33.151521, -111.571135 33.151546, -111.571262 33.151538, -111.57156 33.151493, -111.571671 33.151452, -111.571775 33.151387, -111.571922 33.151249, -111.571989 33.151182, -111.57201 33.151131, -111.57201 33.151071, -111.57197 33.151018, -111.571917 33.150986, -111.571738 33.15095, -111.571507 33.150959, -111.571168 33.15094, -111.571045 33.150964, -111.570883 33.151068, -111.57069 33.151212, -111.570616 33.151282, -111.570515 33.151353, -111.57045 33.150415, -111.57023 33.147295, -111.570455 33.147238, -111.57065 33.147229, -111.5716 33.147244, -111.572887 33.14725, -111.574557 33.14725, -111.574533 33.14678, -111.574517 33.146482, -111.574498 33.145899, -111.574466 33.14537, -111.57442 33.144899, -111.574409 33.144784, -111.574403 33.144726, -111.574396 33.144644, -111.574356 33.144207, -111.57435 33.143934, -111.574342 33.143631, -111.574315 33.143527, -111.57353 33.143509, -111.573167 33.143507, -111.572566 33.143507, -111.570342 33.143501, -111.570162 33.143506, -111.57003 33.143532, -111.569982 33.143541, -111.56997 33.143378, -111.569947 33.143009, -111.569872 33.141778, -111.569867 33.141707, -111.56985 33.141405, -111.569804 33.140799, -111.569742 33.139997, -111.570718 33.139972, -111.571321 33.139958, -111.572715 33.139918, -111.573947 33.139922, -111.575686 33.139935, -111.577143 33.139919, -111.578191 33.139922, -111.578056 33.138115, -111.578025 33.137648, -111.577937 33.136318, -111.577908 33.135886, -111.577871 33.135402, -111.577818 33.134715, -111.577809 33.134604, -111.577797 33.134441, -111.57766 33.132601, -111.577581 33.131211, -111.57751 33.130386, -111.577495 33.130007, -111.577468 33.129368, -111.577479 33.129317, -111.577474 33.129047, -111.577545 33.129069, -111.577767 33.129066, -111.577682 33.127257, -111.577525 33.12395, -111.577507 33.123286, -111.577405 33.123272, -111.576989 33.123258, -111.577001 33.123789, -111.577005 33.123959, -111.5771 33.125013, -111.577153 33.125501, -111.577286 33.126714, -111.577312 33.127049, -111.576911 33.127094, -111.576814 33.127103, -111.576743 33.127108, -111.576464 33.127093, -111.576132 33.127079, -111.574352 33.127084, -111.573441 33.127107, -111.573305 33.127122, -111.573266 33.127153, -111.573238 33.127198, -111.573224 33.127308, -111.573276 33.128138, -111.573337 33.128679, -111.573354 33.129019, -111.573413 33.130018, -111.573463 33.130741, -111.573464 33.130823, -111.573471 33.131345, -111.573545 33.132482, -111.573513 33.132661, -111.572498 33.132683, -111.571934 33.132687, -111.57161 33.13269, -111.571349 33.132693, -111.570227 33.132702, -111.569512 33.132715, -111.569293 33.13272, -111.566178 33.133467, -111.565914 33.13348, -111.565466 33.133451, -111.565278 33.13348, -111.565105 33.133451, -111.564744 33.133523, -111.564238 33.133725, -111.564051 33.133884, -111.563819 33.134, -111.563632 33.133913, -111.563444 33.133682, -111.563227 33.133552, -111.562718 33.133443, -111.562303 33.133494, -111.561061 33.133443, -111.559314 33.133497, -111.559082 33.133501, -111.558981 33.133558, -111.556222 33.13277, -111.553354 33.132565, -111.553328 33.132565, -111.552619 33.132563, -111.553182 33.132746, -111.553252 33.132766, -111.553202 33.132858, -111.5531 33.133034, -111.552951 33.132978, -111.552648 33.132848, -111.552016 33.132829, -111.551921 33.132828, -111.548529 33.132804, -111.546304 33.132812, -111.543885 33.132822, -111.543415 33.132824, -111.543238 33.132824, -111.54318 33.132825, -111.542735 33.132825, -111.541789 33.132828, -111.540804 33.132831, -111.540155 33.132832, -111.539756 33.132852, -111.539734 33.132858, -111.539415 33.132942, -111.539105 33.133091, -111.539024 33.133164, -111.538858 33.133175, -111.538747 33.133234, -111.538707 33.133255, -111.538552 33.133362, -111.537877 33.132585, -111.537758 33.132606, -111.536348 33.132638, -111.535669 33.132634, -111.535088 33.132638, -111.534208 33.132602, -111.533798 33.132609, -111.533495 33.132632, -111.533329 33.132655, -111.53326 33.132666, -111.533045 33.132691, -111.531203 33.132702, -111.530352 33.132697, -111.52698 33.13271, -111.526333 33.132725, -111.524754 33.132717, -111.523419 33.132722, -111.521804 33.132744, -111.520792 33.132742, -111.518767 33.132776, -111.512101 33.132815, -111.511932 33.132804, -111.511012 33.13281, -111.508975 33.132799, -111.508448 33.132804, -111.506414 33.132824, -111.505904 33.132821, -111.505761 33.132832, -111.50491 33.132618, -111.50413 33.132669, -111.503671 33.132713, -111.503198 33.132801, -111.502699 33.132944, -111.5022 33.133119, -111.501832 33.133195, -111.50149 33.133229, -111.493912 33.125484, -111.491509 33.12286, -111.491053 33.121824, -111.490998 33.121382, -111.490982 33.118275, -111.491014 33.118274, -111.491261 33.118273, -111.493376 33.118268, -111.494837 33.118283, -111.495111 33.118282, -111.495091 33.117618, -111.495084 33.116948, -111.495087 33.116457, -111.495088 33.116345, -111.495072 33.114628, -111.495052 33.111964, -111.49504 33.11114, -111.495031 33.110433, -111.495028 33.108724, -111.494989 33.107072, -111.494992 33.106582, -111.494993 33.105703, -111.494986 33.10447, -111.494971 33.103918, -111.495419 33.103918, -111.496549 33.103915, -111.497113 33.103915, -111.497209 33.103915, -111.498247 33.103893, -111.499444 33.103897, -111.499598 33.102438, -111.499521 33.090567, -111.49934 33.090386, -111.498704 33.089709, -111.498182 33.090069, -111.498069 33.090002, -111.498037 33.08999, -111.497842 33.089913, -111.497722 33.089874, -111.497643 33.089858, -111.49751 33.089844, -111.497271 33.089841, -111.49214 33.089841, -111.49202 33.089854, -111.491938 33.089874, -111.491732 33.089988, -111.491655 33.090047, -111.491601 33.09011, -111.491551 33.090179, -111.491465 33.090435, -111.491236 33.091242, -111.491218 33.091282, -111.491091 33.09167, -111.491077 33.091761, -111.49108 33.091932, -111.491071 33.093076, -111.491085 33.093716, -111.491086 33.095654, -111.491102 33.098284, -111.49111 33.098357, -111.491138 33.098427, -111.491885 33.099558, -111.491895 33.099573, -111.491923 33.099643, -111.49195 33.099791, -111.491946 33.100206, -111.490608 33.100226, -111.490619 33.10221, -111.490655 33.103367, -111.490638 33.10396, -111.490649 33.104165, -111.490665 33.105272, -111.490692 33.106596, -111.490736 33.109313, -111.490746 33.111126, -111.490751 33.112006, -111.490765 33.114596, -111.490769 33.11525, -111.490774 33.115456, -111.490775 33.115513, -111.490801 33.116582, -111.490844 33.117599, -111.49083 33.11788, -111.490854 33.118015, -111.49084 33.118277, -111.489034 33.118305, -111.48768 33.118342, -111.486806 33.11836, -111.487111 33.118682, -111.487365 33.119247, -111.488974 33.120926, -111.490962 33.123029, -111.498823 33.130854, -111.500128 33.132548, -111.500682 33.132863, -111.501754 33.133592, -111.501918 33.133769, -111.503275 33.13523, -111.50394 33.136193, -111.503995 33.136252, -111.504215 33.136501, -111.504285 33.13658, -111.510813 33.14396, -111.514058 33.147269, -111.513657 33.147288, -111.52737 33.161893, -111.528397 33.161887, -111.528682 33.161884, -111.528847 33.161879, -111.530287 33.161834, -111.530329 33.161833, -111.532192 33.161819, -111.535278 33.161805, -111.536045 33.1618, -111.537491 33.161794, -111.539755 33.161785, -111.541796 33.161769, -111.542159 33.161763, -111.543764 33.161778, -111.544836 33.161783, -111.544846 33.162369, -111.544997 33.165467, -111.545458 33.174891, -111.545514 33.176277, -111.545517 33.176334, -111.545534 33.176335, -111.545651 33.176334, -111.550741 33.176315, -111.550819 33.176347, -111.551342 33.176326, -111.551506 33.176342, -111.5518 33.176319, -111.551963 33.176319, -111.552146 33.176336, -111.552565 33.176293, -111.552663 33.176287, -111.552738 33.176299, -111.552853 33.176319, -111.553166 33.176315, -111.553441 33.176342, -111.553722 33.176309, -111.554036 33.176326, -111.554291 33.176265, -111.554409 33.176254, -111.554572 33.176293, -111.554997 33.176266, -111.555141 33.176282, -111.555494 33.176266, -111.555684 33.176237, -111.563268 33.176347, -111.563101 33.173392, -111.56312 33.173338, -111.563165 33.173293, -111.563237 33.17328, -111.563924 33.173287, -111.566777 33.173274, -111.566929 33.173235, -111.567322 33.173012, -111.567579 33.172894, -111.567682 33.172874, -111.567793 33.172882, -111.567852 33.172886, -111.568035 33.172898, -111.569014 33.172925, -111.569735 33.17293, -111.570264 33.172932, -111.570501 33.172933, -111.572137 33.172952, -111.573632 33.172933, -111.574448 33.172945, -111.574884 33.172933, -111.575313 33.17286, -111.575549 33.17303, -111.577417 33.17438, -111.577564 33.174487, -111.578235 33.174979, -111.579042 33.175559, -111.579844 33.176157, -111.580337 33.176512, -111.579282 33.177559, -111.579256 33.17765, -111.579039 33.177871, -111.578711 33.178286, -111.578436 33.178665, -111.578229 33.179094, -111.578001 33.17966, -111.577886 33.180037, -111.577801 33.180394, -111.577696 33.180352, -111.577257 33.18033, -111.576879 33.180306, -111.576388 33.180272, -111.576386 33.180228, -111.576383 33.180171, -111.576378 33.180099, -111.57637 33.180018, -111.576361 33.179945, -111.57636 33.179886, -111.576358 33.17976, -111.57638 33.179761, -111.576464 33.17976, -111.576628 33.179758, -111.576705 33.17976, -111.576775 33.179761, -111.576833 33.179761, -111.576919 33.179766, -111.57699 33.179767, -111.577044 33.179752, -111.577035 33.179705, -111.577047 33.179661, -111.577065 33.179599, -111.577119 33.17943, -111.577192 33.179218, -111.577235 33.179107, -111.577281 33.178998, -111.5773 33.178955, -111.577382 33.178781, -111.577434 33.178675, -111.57754 33.17848, -111.57759 33.178395, -111.577638 33.178314, -111.577704 33.178213, -111.577791 33.178083, -111.577843 33.178007, -111.577994 33.17777, -111.578044 33.177692, -111.57809 33.177612, -111.578156 33.17745, -111.578181 33.177363, -111.578198 33.177282, -111.578213 33.177121, -111.578215 33.177038, -111.578214 33.176955, -111.578211 33.176875, -111.578207 33.176809, -111.578203 33.176758, -111.578196 33.176688, -111.578187 33.176662, -111.578183 33.17665, -111.578156 33.17665, -111.578061 33.176652, -111.577852 33.176656, -111.577617 33.176662, -111.577492 33.176663, -111.577363 33.176662, -111.577098 33.176664, -111.576963 33.176665, -111.576827 33.176667, -111.576555 33.176671, -111.57642 33.17667, -111.576288 33.176666, -111.576052 33.176661, -111.575952 33.17666, -111.575862 33.176662, -111.575702 33.176666, -111.575635 33.17666, -111.575544 33.176669, -111.575538 33.176737, -111.575541 33.176787, -111.575544 33.176845, -111.575547 33.176904, -111.57555 33.176962, -111.575552 33.177027, -111.575553 33.177062, -111.575443 33.177061, -111.575277 33.17707, -111.575187 33.177071, -111.575087 33.17707, -111.574986 33.177067, -111.574819 33.177062, -111.574763 33.177061, -111.574627 33.177059, -111.57463 33.177013, -111.574634 33.176935, -111.574636 33.176876, -111.574636 33.176792, -111.574637 33.176703, -111.57459 33.176704, -111.574513 33.176699, -111.574455 33.176699, -111.574379 33.176698, -111.574282 33.176699, -111.574169 33.176701, -111.574044 33.176701, -111.573912 33.176699, -111.573777 33.176699, -111.573643 33.176699, -111.573402 33.176699, -111.573301 33.176699, -111.573215 33.176696, -111.573018 33.176692, -111.572931 33.176687, -111.572872 33.176682, -111.57283 33.176692, -111.572812 33.176744, -111.572815 33.176837, -111.572822 33.176901, -111.572829 33.176974, -111.572837 33.177052, -111.572845 33.177124, -111.572847 33.177253, -111.57284 33.177357, -111.572835 33.177415, -111.572839 33.177487, -111.572886 33.177522, -111.572945 33.177535, -111.573029 33.177543, -111.573088 33.177546, -111.573168 33.177551, -111.573266 33.177554, -111.573607 33.177554, -111.573707 33.177554, -111.573791 33.177554, -111.573861 33.177554, -111.57397 33.177553, -111.574067 33.177557, -111.574129 33.177557, -111.574196 33.177559, -111.574263 33.177559, -111.574384 33.177557, -111.574485 33.177557, -111.574624 33.177558, -111.574625 33.177574, -111.574627 33.17763, -111.574626 33.17769, -111.574625 33.177751, -111.574623 33.17788, -111.574623 33.17795, -111.574626 33.178098, -111.574627 33.178173, -111.574627 33.178249, -111.574627 33.178264, -111.574516 33.178266, -111.574396 33.178277, -111.574326 33.178278, -111.574239 33.178278, -111.574137 33.178277, -111.574025 33.178275, -111.57391 33.178277, -111.573697 33.178283, -111.573602 33.178288, -111.573513 33.178291, -111.573361 33.178297, -111.573297 33.178299, -111.573239 33.178302, -111.573148 33.1783, -111.573047 33.178283, -111.572975 33.17828, -111.572961 33.178286, -111.572967 33.178375, -111.572969 33.178451, -111.572973 33.178511, -111.572977 33.178584, -111.572991 33.178755, -111.573011 33.178948, -111.573019 33.179049, -111.57302 33.179075, -111.573031 33.17927, -111.573038 33.179389, -111.573054 33.179749, -111.573065 33.179965, -111.573074 33.180151, -111.573079 33.180241, -111.573084 33.180326, -111.573087 33.1804, -111.573093 33.18051, -111.573086 33.180577, -111.57308 33.180631, -111.573062 33.180664, -111.573067 33.180677, -111.573086 33.181393, -111.573738 33.181379, -111.574417 33.181413, -111.57507 33.181458, -111.576632 33.181645, -111.576645 33.18242, -111.574972 33.182219, -111.574466 33.182184, -111.573674 33.182164, -111.57365 33.182919, -111.573334 33.182917, -111.573217 33.182941, -111.573207 33.182958, -111.573195 33.18298, -111.573217 33.183503, -111.573222 33.183611, -111.573247 33.18366, -111.573317 33.183684, -111.573636 33.183686, -111.574169 33.183701, -111.5743 33.183705, -111.574838 33.183754, -111.574825 33.18387, -111.574813 33.18397, -111.574805 33.184077, -111.574801 33.184125, -111.574791 33.184196, -111.574784 33.184257, -111.574779 33.184303, -111.574775 33.184331, -111.574771 33.184364, -111.574761 33.184438, -111.57475 33.184517, -111.574739 33.18459, -111.574723 33.184694, -111.574711 33.18475, -111.574703 33.184786, -111.574472 33.184773, -111.574348 33.184767, -111.574224 33.184762, -111.573996 33.18475, -111.573891 33.184742, -111.573792 33.184731, -111.573621 33.184714, -111.573553 33.184708, -111.573497 33.184706, -111.573416 33.18471, -111.573361 33.18473, -111.573328 33.184754, -111.573295 33.1848, -111.573288 33.184858, -111.573297 33.18493, -111.573306 33.185017, -111.573314 33.185117, -111.573324 33.185226, -111.573345 33.185459, -111.573355 33.185575, -111.573372 33.185806, -111.573378 33.185918, -111.573392 33.186121, -111.5734 33.186211, -111.573407 33.186298, -111.573414 33.186366, -111.573417 33.186441, -111.573415 33.186521, -111.573413 33.186608, -111.573415 33.186695, -111.573419 33.186774, -111.573423 33.186844, -111.573425 33.186903, -111.573424 33.186998, -111.573461 33.187077, -111.573515 33.187114, -111.573596 33.187112, -111.573692 33.187114, -111.573748 33.187112, -111.573803 33.187112, -111.573862 33.187114, -111.573922 33.187119, -111.573983 33.187123, -111.574042 33.187126, -111.574101 33.187133, -111.57416 33.187136, -111.574221 33.18714, -111.574282 33.187144, -111.574406 33.187154, -111.574533 33.187168, -111.574596 33.187175, -111.574659 33.187184, -111.574723 33.187193, -111.574788 33.187205, -111.574852 33.187216, -111.574916 33.187227, -111.574982 33.18724, -111.575046 33.18725, -111.575112 33.187267, -111.575177 33.187283, -111.575306 33.187306, -111.575432 33.187329, -111.575491 33.187345, -111.575549 33.187361, -111.575606 33.187375, -111.575719 33.187406, -111.575949 33.187466, -111.57607 33.187497, -111.576133 33.187519, -111.576254 33.187545, -111.576315 33.187554, -111.576376 33.187561, -111.576437 33.187562, -111.576497 33.187562, -111.576557 33.187559, -111.576591 33.187557, -111.576593 33.187587, -111.576603 33.187674, -111.576618 33.187787, -111.576625 33.18786, -111.576646 33.188029, -111.576669 33.1882, -111.57668 33.188285, -111.576637 33.18828, -111.576506 33.188304, -111.576362 33.188321, -111.576285 33.188316, -111.576192 33.188308, -111.576086 33.188294, -111.575859 33.188249, -111.575747 33.188222, -111.575636 33.188194, -111.5753 33.188102, -111.575181 33.18807, -111.574947 33.188015, -111.57483 33.187992, -111.574712 33.18797, -111.574466 33.187928, -111.574343 33.187911, -111.574222 33.187899, -111.574005 33.18788, -111.573917 33.187873, -111.573845 33.187868, -111.573785 33.187863, -111.573698 33.187857, -111.573641 33.187848, -111.573612 33.187846, -111.573561 33.187841, -111.57354 33.187845, -111.573517 33.187855, -111.573525 33.187956, -111.573535 33.188039, -111.573543 33.188136, -111.573552 33.18828, -111.573554 33.188318, -111.573557 33.188357, -111.573561 33.188429, -111.57357 33.188563, -111.573573 33.188633, -111.573574 33.188715, -111.573574 33.188809, -111.573578 33.188918, -111.573584 33.189027, -111.573591 33.189132, -111.573594 33.189223, -111.573604 33.189355, -111.573667 33.189418, -111.573753 33.189462, -111.573826 33.189476, -111.573922 33.189487, -111.574032 33.189498, -111.574286 33.189529, -111.574439 33.189549, -111.574583 33.189577, -111.574879 33.18965, -111.57503 33.189692, -111.575349 33.18977, -111.575507 33.189802, -111.575803 33.189847, -111.575939 33.18986, -111.576273 33.189875, -111.576358 33.189874, -111.576433 33.189873, -111.576632 33.18989, -111.576732 33.189901, -111.5768 33.189891, -111.576822 33.189861, -111.576829 33.189854, -111.576835 33.189836, -111.576834 33.189816, -111.576832 33.189712, -111.576825 33.189653, -111.576818 33.189588, -111.57681 33.189518, -111.576805 33.189472, -111.577748 33.189429, -111.577802 33.190064, -111.57782 33.190783, -111.575516 33.190799, -111.573026 33.190851, -111.573168 33.19335, -111.572983 33.193332, -111.572957 33.191782, -111.572957 33.19085, -111.566195 33.190856, -111.564345 33.190837, -111.564209 33.190828, -111.56413 33.190824, -111.564139 33.191267, -111.564404 33.196018, -111.564476 33.196382, -111.564499 33.196727, -111.564465 33.196803, -111.564451 33.196817, -111.564317 33.196955, -111.564394 33.197256, -111.564494 33.197696, -111.564523 33.198017, -111.56473 33.198016, -111.565441 33.198016, -111.565639 33.198039, -111.565445 33.198086, -111.564735 33.198101, -111.564529 33.198105, -111.564589 33.199441, -111.564613 33.19989, -111.56468 33.201117, -111.564773 33.202593, -111.564819 33.203004, -111.564905 33.203373, -111.56506 33.203865, -111.565231 33.204431, -111.565298 33.20477, -111.565348 33.205173, -111.565347 33.205254, -111.565347 33.205486, -111.563705 33.205509, -111.563161 33.205517, -111.562973 33.205424, -111.562712 33.205332, -111.56251 33.205283, -111.56222 33.205249, -111.558976 33.205264, -111.54651 33.20532, -111.546044 33.205298, -111.546065 33.206811, -111.546083 33.207983, -111.546082 33.208742, -111.54608 33.209766, -111.54607 33.210666, -111.546052 33.211942, -111.546056 33.212361, -111.546064 33.213253, -111.546074 33.214588, -111.546074 33.214804, -111.546073 33.215467, -111.546073 33.215988, -111.546072 33.218811, -111.546042 33.220005, -111.545972 33.220005, -111.546033 33.222, -111.545993 33.223947, -111.546013 33.225476, -111.546041 33.227586, -111.546039 33.22788, -111.54603 33.229408, -111.546055 33.22983, -111.546109 33.23013, -111.546158 33.230302, -111.546162 33.230395, -111.546172 33.23069, -111.546182 33.232231, -111.546159 33.232712, -111.546147 33.233265, -111.54615 33.233334, -111.546152 33.233393, -111.546171 33.233494, -111.546201 33.233539, -111.546231 33.233603, -111.546235 33.233643, -111.546224 33.23369, -111.546184 33.233734, -111.546139 33.233775, -111.546095 33.233831, -111.546084 33.233894, -111.546088 33.234317, -111.546093 33.234676, -111.5461 33.235329, -111.546064 33.236657, -111.546019 33.236635, -111.545936 33.236611, -111.543095 33.236618, -111.541692 33.236606, -111.541384 33.236607, -111.541391 33.237699, -111.541403 33.238334, -111.541412 33.23884, -111.541388 33.239739, -111.54141 33.240916, -111.541394 33.241816, -111.541089 33.241828, -111.539512 33.24185, -111.539179 33.241855, -111.538532 33.241853, -111.537365 33.24185, -111.537386 33.24286, -111.537386 33.243302, -111.537387 33.244149, -111.53739 33.244732, -111.537394 33.245265, -111.537394 33.246473, -111.53738 33.247025, -111.537364 33.247656, -111.537358 33.249071, -111.536865 33.249073, -111.535559 33.249091, -111.534187 33.249104, -111.534104 33.249104, -111.533211 33.249111, -111.53199 33.249119, -111.530287 33.249108, -111.529769 33.249101, -111.528743 33.248723, -111.528731 33.247872, -111.528736 33.246229, -111.528735 33.244825, -111.528734 33.243727, -111.528718 33.241878, -111.528716 33.241675, -111.528737 33.240287, -111.528731 33.240032, -111.528753 33.238974, -111.528756 33.238816, -111.528753 33.238304, -111.528766 33.237724, -111.528756 33.237488, -111.528754 33.236632, -111.528736 33.236316, -111.528741 33.236068, -111.526428 33.23605, -111.525623 33.236058, -111.523974 33.236055, -111.523051 33.236065, -111.522036 33.236052, -111.520301 33.236049, -111.519829 33.236063, -111.51858 33.2361, -111.517441 33.236094, -111.516558 33.236108, -111.515635 33.236096, -111.514234 33.2361, -111.513815 33.236131, -111.513606 33.236153, -111.513482 33.236198, -111.513351 33.236269, -111.51326 33.236347, -111.51311 33.236519, -111.512845 33.237119, -111.512632 33.237527, -111.51249 33.237929, -111.512297 33.238529, -111.512052 33.239081, -111.511852 33.239604, -111.511651 33.240034, -111.511594 33.240221, -111.511555 33.240375, -111.511555 33.240609, -111.511559 33.241874, -111.51156 33.241933, -111.511568 33.243943, -111.511545 33.246738, -111.511562 33.249111, -111.513288 33.249122, -111.515353 33.249121, -111.515958 33.249114, -111.517093 33.249101, -111.519084 33.249093, -111.52334 33.2491, -111.524372 33.249096, -111.527182 33.249077, -111.528792 33.249088, -111.528797 33.250149, -111.528801 33.252208, -111.530076 33.252223, -111.530067 33.251835, -111.530961 33.251838, -111.53096 33.252214, -111.532056 33.252203, -111.532674 33.252201, -111.533068 33.252201, -111.533222 33.252201, -111.533231 33.252563, -111.533203 33.253746, -111.533198 33.255192, -111.533375 33.255193, -111.533555 33.255194, -111.537407 33.255139, -111.538351 33.25515, -111.539509 33.255156, -111.540242 33.255162, -111.541656 33.255125, -111.541677 33.255465, -111.541778 33.256222, -111.541785 33.256276, -111.541892 33.257207, -111.542005 33.258091, -111.542118 33.258907, -111.542275 33.259983, -111.54239 33.260821, -111.542461 33.261311, -111.542578 33.262096, -111.542652 33.262625, -111.542692 33.262984, -111.542765 33.263633, -111.546027 33.263599, -111.548262 33.263571, -111.548368 33.263568, -111.550376 33.263543, -111.550716 33.263542, -111.550688 33.26271, -111.55065 33.26183, -111.550544 33.26018, -111.552342 33.260177, -111.55286 33.260185, -111.553423 33.260185, -111.553922 33.260133, -111.554192 33.260109, -111.55484 33.260123, -111.554851 33.260913, -111.554874 33.261823, -111.554885 33.262251, -111.554886 33.263529, -111.555721 33.263527, -111.556743 33.263523, -111.558277 33.263517, -111.558595 33.263515, -111.558837 33.263514, -111.56014 33.263508, -111.560236 33.263508, -111.560254 33.263748, -111.560277 33.263854, -111.560305 33.263921, -111.560399 33.26397, -111.560553 33.263989, -111.560715 33.263984, -111.560797 33.263921, -111.560829 33.263804, -111.560824 33.263506, -111.563117 33.263495, -111.563166 33.263495, -111.563255 33.263495, -111.56325 33.261964, -111.563257 33.258809, -111.563257 33.258241, -111.563443 33.258241, -111.564476 33.258268, -111.56514 33.25832, -111.565695 33.258487, -111.566028 33.258642, -111.566383 33.258797, -111.566497 33.258846, -111.56739 33.259234, -111.568152 33.259373, -111.568813 33.259377, -111.569567 33.259396, -111.570277 33.259406, -111.571452 33.259422, -111.571764 33.25941, -111.572098 33.259369, -111.572402 33.259296, -111.572765 33.259039, -111.57337 33.25956, -111.573683 33.259584, -111.573852 33.259597, -111.574919 33.259679, -111.576068 33.259784, -111.57618 33.259794, -111.576161 33.260223, -111.583028 33.260367, -111.583013 33.258207, -111.582999 33.256409, -111.582995 33.255869, -111.582987 33.254698, -111.582953 33.250087, -111.582918 33.248961, -111.583178 33.248961, -111.583363 33.248961, -111.583442 33.248955, -111.583587 33.248944, -111.583841 33.248922, -111.583811 33.246133, -111.584174 33.246131, -111.587404 33.246131, -111.588511 33.246138, -111.589182 33.246133, -111.590181 33.246139, -111.591441 33.246153, -111.591443 33.246225, -111.591444 33.246288, -111.591451 33.246557, -111.591475 33.246861, -111.591466 33.247543, -111.591454 33.248579, -111.591573 33.249119, -111.591596 33.249947, -111.591599 33.250053, -111.591605 33.250511, -111.591608 33.25075, -111.591616 33.251303, -111.591632 33.252473, -111.591665 33.25483, -111.591679 33.255828, -111.596826 33.255888, -111.59998 33.255925, -111.600146 33.255927, -111.600157 33.257135, -111.600165 33.257899, -111.607884 33.257958, -111.608834 33.257965, -111.609159 33.257968, -111.609098 33.255782, -111.608876 33.255843, -111.608848 33.255845, -111.608848 33.255803, -111.608846 33.25304, -111.608846 33.252214, -111.609462 33.252209, -111.609728 33.252203, -111.609847 33.25219, -111.609965 33.252167, -111.610334 33.252087, -111.610472 33.252062, -111.610612 33.252046, -111.610752 33.252042, -111.61204 33.252045, -111.612195 33.252035, -111.612272 33.252021, -111.612347 33.252004, -111.612428 33.251979, -111.612506 33.251947, -111.61258 33.25191, -111.612651 33.251866, -111.612716 33.25182, -111.612777 33.251767, -111.612831 33.251711, -111.61288 33.251651, -111.612921 33.251587, -111.612957 33.25152, -111.612984 33.251451, -111.613005 33.251381, -111.613018 33.251309, -111.613024 33.251236, -111.613024 33.250529, -111.613728 33.250529, -111.613728 33.250957, -111.613727 33.251755, -111.616628 33.251759, -111.616634 33.252226, -111.616627 33.252634, -111.616493 33.25263, -111.616482 33.25263, -111.616159 33.252629, -111.616041 33.252633, -111.615931 33.252645, -111.615868 33.252656, -111.615783 33.25267, -111.615699 33.252679, -111.615614 33.252684, -111.615566 33.252685, -111.61529 33.252684, -111.614963 33.252682, -111.613982 33.252681, -111.613716 33.252681, -111.61357 33.252679, -111.61357 33.252803, -111.613569 33.253079, -111.613569 33.2532, -111.613566 33.25327, -111.613562 33.253304, -111.613528 33.253476, -111.613702 33.253478, -111.613979 33.253478, -111.614961 33.25348, -111.615243 33.253481, -111.615284 33.253482, -111.615389 33.253487, -111.615492 33.253501, -111.615593 33.253521, -111.615701 33.253551, -111.615806 33.253579, -111.615917 33.2536, -111.61603 33.253613, -111.61615 33.253617, -111.616189 33.253617, -111.616284 33.253617, -111.616624 33.253618, -111.616624 33.253496, -111.616625 33.253304, -111.6171 33.253299, -111.617061 33.250965, -111.61714 33.250965, -111.617354 33.250967, -111.617386 33.253297, -111.617426 33.256119, -111.617427 33.256196, -111.617439 33.257035, -111.613991 33.257028, -111.613986 33.258569, -111.617461 33.258584, -111.617526 33.26299, -111.617527 33.263028, -111.617528 33.263123, -111.617634 33.263123, -111.622494 33.263149, -111.623876 33.263157, -111.62568 33.263168, -111.626045 33.26317, -111.634707 33.263216, -111.634704 33.262982, -111.634872 33.262984, -111.634894 33.262985, -111.634893 33.262954, -111.634832 33.260721, -111.634791 33.260283, -111.634772 33.259455, -111.634742 33.257508, -111.634699 33.2565, -111.634712 33.256507, -111.634722 33.256514, -111.634813 33.256571, -111.638002 33.258582, -111.638112 33.258397, -111.63813 33.258351, -111.63819 33.258204, -111.639313 33.258906, -111.645385 33.262681, -111.645941 33.263033, -111.646548 33.263415, -111.646347 33.263402, -111.648734 33.264894, -111.650999 33.266303, -111.651637 33.266681, -111.652075 33.266953, -111.655536 33.269127, -111.657911 33.270605, -111.657963 33.270637, -111.658114 33.270615, -111.658146 33.270635, -111.660747 33.272254, -111.660996 33.272409, -111.661121 33.272486, -111.662897 33.273591, -111.663158 33.273753, -111.663459 33.27394, -111.663876 33.274199, -111.664241 33.274426, -111.66499 33.27489, -111.66685 33.276045, -111.669403 33.277645, -111.669404 33.277723, -111.669406 33.277896, -111.669634 33.277919, -111.669889 33.277946, -111.675906 33.281692, -111.677793 33.282863, -111.678675 33.283413, -111.678772 33.283589, -111.679244 33.283883, -111.682059 33.285633, -111.683783 33.286719, -111.683782 33.286734, -111.684072 33.2869, -111.686079 33.288163, -111.686385 33.28846, -111.686392 33.288682, -111.686392 33.288694, -111.686425 33.289029, -111.686449 33.289209, -111.686481 33.289505, -111.686517 33.289839, -111.686526 33.29003, -111.686565 33.292628, -111.686566 33.292713, -111.68646 33.292702, -111.686442 33.2927, -111.686406 33.29271, -111.686379 33.29273, -111.686365 33.292757, -111.686373 33.292872, -111.686394 33.293176, -111.686389 33.293355, -111.686372 33.293507, -111.685807 33.295765, -111.685511 33.296953, -111.684918 33.296836, -111.684231 33.296698, -111.684062 33.296681, -111.683162 33.296686, -111.683165 33.296646, -111.683169 33.296284, -111.683111 33.296281, -111.682593 33.296127, -111.682529 33.29612, -111.681949 33.296118, -111.681891 33.296125, -111.681572 33.296208, -111.681354 33.296267, -111.681327 33.29627, -111.681319 33.296667, -111.680615 33.296657, -111.680341 33.296649, -111.680202 33.296656, -111.680122 33.296669, -111.680037 33.296697, -111.679959 33.296736, -111.67989 33.296785, -111.679831 33.296847, -111.679796 33.296899, -111.679763 33.296971, -111.679634 33.296944, -111.679544 33.296914, -111.679434 33.29686, -111.67938 33.296826, -111.679315 33.296774, -111.679213 33.296716, -111.679129 33.296683, -111.679013 33.296655, -111.678906 33.296644, -111.678506 33.296635, -111.677763 33.296627, -111.676879 33.296618, -111.676612 33.296616, -111.676503 33.29663, -111.676407 33.296655, -111.676321 33.296689, -111.67602 33.296861, -111.675814 33.296981, -111.675786 33.297016, -111.675755 33.297114, -111.675741 33.297229, -111.675739 33.297421, -111.675728 33.298558, -111.675723 33.299107, -111.675718 33.299754, -111.675711 33.300612, -111.675487 33.300528, -111.675538 33.300255, -111.675506 33.299971, -111.675198 33.299854, -111.674609 33.299835, -111.674128 33.299919, -111.674187 33.300532, -111.673464 33.300552, -111.673385 33.300441, -111.673248 33.30026, -111.673169 33.300248, -111.673016 33.300337, -111.672976 33.300271, -111.672868 33.30014, -111.672587 33.299865, -111.672065 33.299424, -111.67136 33.298811, -111.671093 33.298576, -111.67085 33.298363, -111.670786 33.298361, -111.66964 33.298341, -111.669595 33.296317, -111.669558 33.294582, -111.669537 33.293742, -111.669506 33.292864, -111.669498 33.292501, -111.669466 33.292551, -111.669269 33.292861, -111.665485 33.292803, -111.665059 33.292814, -111.663861 33.292793, -111.663828 33.292749, -111.663651 33.292738, -111.663619 33.292776, -111.662604 33.292765, -111.662375 33.292721, -111.662342 33.292765, -111.662139 33.292721, -111.662008 33.292765, -111.661124 33.292754, -111.660915 33.292699, -111.660882 33.292754, -111.660685 33.292699, -111.659304 33.292737, -111.659271 33.292682, -111.658767 33.292709, -111.657255 33.29267, -111.657058 33.292713, -111.657026 33.292659, -111.655467 33.292659, -111.655206 33.292696, -111.653791 33.292637, -111.653097 33.29262, -111.650033 33.29253, -111.649955 33.292493, -111.643815 33.292485, -111.635525 33.292476, -111.635523 33.295946, -111.635523 33.29599, -111.635504 33.296115, -111.635554 33.300086, -111.635573 33.300894, -111.635637 33.302643, -111.635655 33.30538, -111.635727 33.306404, -111.635759 33.307035, -111.635733 33.307701, -111.635784 33.30945, -111.635862 33.310484, -111.636071 33.310682, -111.63939 33.313437, -111.639508 33.313511, -111.639737 33.313651, -111.640575 33.313647, -111.641728 33.313679, -111.642271 33.313719, -111.643024 33.313692, -111.643221 33.31373, -111.643437 33.313675, -111.643961 33.31373, -111.644321 33.31394, -111.644419 33.314104, -111.644994 33.316167, -111.645158 33.316403, -111.645472 33.316706, -111.646618 33.317723, -111.646618 33.317762, -111.646768 33.317844, -111.64739 33.318438, -111.647521 33.318503, -111.647704 33.318641, -111.64815 33.319027, -111.648556 33.319373, -111.649164 33.319753, -111.649544 33.319918, -111.650042 33.32005, -111.650618 33.320177, -111.65162 33.320468, -111.65236 33.320634, -111.652753 33.320782, -111.653264 33.320892, -111.65386 33.32103, -111.654397 33.32121, -111.654606 33.321305, -111.655977 33.321351, -111.663487 33.321405, -111.66989 33.32145, -111.669896 33.321514, -111.669964 33.322265, -111.66999 33.32255, -111.670015 33.322306, -111.670103 33.321533, -111.670098 33.32152, -111.670147 33.321522, -111.671562 33.321737, -111.67984 33.321738, -111.679871 33.321734, -111.679813 33.321675, -111.679314 33.321147, -111.67928 33.321103, -111.679153 33.320918, -111.679084 33.320794, -111.679002 33.320622, -111.678936 33.320441, -111.678888 33.320256, -111.678858 33.320071, -111.678846 33.319913, -111.678901 33.315584, -111.678905 33.315095, -111.678889 33.314898, -111.678883 33.314861, -111.679226 33.314406, -111.679255 33.314425, -111.679299 33.314444, -111.679368 33.314455, -111.680265 33.314462, -111.681378 33.31447, -111.681844 33.314473, -111.681885 33.314469, -111.681925 33.314455, -111.681968 33.314425, -111.681991 33.314394, -111.682003 33.314362, -111.68201 33.313809, -111.682014 33.313273, -111.68202 33.312581, -111.68203 33.311694, -111.68204 33.310768, -111.682044 33.310288, -111.682058 33.310187, -111.682091 33.310088, -111.682124 33.310023, -111.682146 33.309989, -111.682373 33.309644, -111.682524 33.309413, -111.683021 33.308653, -111.683052 33.308611, -111.683082 33.308557, -111.683113 33.308476, -111.683126 33.30841, -111.683141 33.308103, -111.683127 33.308064, -111.683902 33.308017, -111.684758 33.30814, -111.685445 33.308183, -111.686665 33.308119, -111.686617 33.307198, -111.68672 33.307198, -111.68728 33.3072, -111.687493 33.307198, -111.687444 33.307986, -111.687378 33.308234, -111.686805 33.309637, -111.686769 33.309724, -111.686691 33.309807, -111.686621 33.30995, -111.686213 33.31078, -111.686232 33.310834, -111.685872 33.311582, -111.685302 33.31338, -111.684647 33.315063, -111.684189 33.316454, -111.68417 33.31653, -111.684038 33.317059, -111.682977 33.320209, -111.682932 33.320303, -111.682827 33.320726, -111.682786 33.320867, -111.682679 33.321237, -111.68265 33.321336, -111.682671 33.321415, -111.682715 33.321584, -111.682741 33.321634, -111.682785 33.321719, -111.683017 33.321682, -111.68309 33.321674, -111.683302 33.321652, -111.683334 33.321731, -111.683394 33.321861, -111.68502 33.321852, -111.686927 33.321852, -111.686929 33.322004, -111.686938 33.322302, -111.686945 33.322524, -111.686964 33.323954, -111.686977 33.324424, -111.686991 33.325366, -111.687031 33.327771, -111.687038 33.328183, -111.687042 33.328436, -111.687043 33.32846, -111.687048 33.328842, -111.686928 33.328842, -111.686929 33.32905, -111.686945 33.329929, -111.686993 33.332482, -111.687014 33.333775, -111.687024 33.334328, -111.687046 33.336084, -111.68705 33.336307, -111.687088 33.337919, -111.687097 33.338964, -111.687144 33.341238, -111.687152 33.341905, -111.687182 33.343323, -111.687184 33.343572, -111.687315 33.350411, -111.687313 33.350568, -111.682516 33.350536, -111.682311 33.350535, -111.682072 33.350533, -111.681873 33.350532, -111.68073 33.350529, -111.679739 33.350526, -111.679424 33.350523, -111.679024 33.350519, -111.673564 33.350468, -111.670581 33.350442, -111.670183 33.350439, -111.668306 33.350422, -111.667264 33.350411, -111.666748 33.350406, -111.66618 33.350402, -111.6644 33.350391, -111.663346 33.350382, -111.661825 33.350369, -111.661206 33.350364, -111.660586 33.350361, -111.659604 33.350353, -111.658538 33.350344, -111.657473 33.350337, -111.653104 33.350302, -111.651564 33.350288, -111.650024 33.350276, -111.647698 33.350257, -111.645334 33.350237, -111.644566 33.350227, -111.644422 33.350226, -111.644136 33.350229, -111.644027 33.350228, -111.643903 33.350227, -111.643659 33.350225, -111.643163 33.350221, -111.635723 33.350164, -111.635346 33.350164, -111.632092 33.35016, -111.630626 33.350146, -111.628521 33.350127, -111.62709 33.350119, -111.62672 33.350116, -111.625326 33.350104, -111.623895 33.350091, -111.620072 33.350066, -111.618573 33.350054, -111.618474 33.350054, -111.617485 33.350047, -111.609763 33.349987, -111.607491 33.349977, -111.602789 33.349947, -111.602508 33.349945, -111.602508 33.34974, -111.602558 33.336349, -111.602561 33.335476, -111.602561 33.335356, -111.602561 33.335188, -111.602559 33.334152, -111.602558 33.333303, -111.602557 33.332479, -111.602556 33.331632, -111.602555 33.330793, -111.602553 33.329964, -111.602553 33.329444, -111.602552 33.328925, -111.602551 33.328078, -111.60255 33.327245, -111.602549 33.326403, -111.602548 33.325552, -111.602547 33.324732, -111.602546 33.323923, -111.602545 33.323164, -111.602544 33.322399, -111.602543 33.321822, -111.602542 33.321046, -111.602542 33.320965, -111.601311 33.321036, -111.601088 33.32105, -111.600901 33.321058, -111.600745 33.321064, -111.60067 33.321067, -111.600627 33.321069, -111.599776 33.321106, -111.599472 33.3211, -111.598772 33.321086, -111.597415 33.32106, -111.59738 33.321059, -111.59656 33.321043, -111.596427 33.321268, -111.595463 33.321007, -111.595128 33.32095, -111.594581 33.320901, -111.593977 33.320889, -111.593328 33.320876, -111.592218 33.320864, -111.592131 33.320855, -111.592115 33.319253, -111.592108 33.318568, -111.592107 33.31852, -111.592098 33.317522, -111.592095 33.317255, -111.592091 33.316897, -111.592085 33.316296, -111.592056 33.313571, -111.592178 33.313605, -111.593292 33.313617, -111.59494 33.313631, -111.594937 33.313527, -111.59493 33.312774, -111.594927 33.312398, -111.594918 33.311612, -111.594885 33.308452, -111.594865 33.306522, -111.600552 33.306571, -111.600552 33.306424, -111.597654 33.306401, -111.594901 33.306379, -111.592139 33.306357, -111.59205 33.306357, -111.591992 33.306359, -111.591813 33.306376, -111.588754 33.306347, -111.587095 33.306331, -111.583759 33.306299, -111.5834 33.307182, -111.583505 33.307234, -111.583578 33.307288, -111.583583 33.307371, -111.583559 33.307653, -111.583556 33.307847, -111.583567 33.308068, -111.58359 33.30831, -111.583596 33.308531, -111.583593 33.30861, -111.583583 33.308916, -111.583573 33.309238, -111.583581 33.309908, -111.583432 33.31054, -111.583449 33.312365, -111.583458 33.313332, -111.583517 33.313496, -111.583592 33.313707, -111.583617 33.314397, -111.583622 33.314551, -111.58362 33.314591, -111.583617 33.314633, -111.583573 33.314968, -111.583566 33.31508, -111.583557 33.315452, -111.583552 33.315617, -111.583552 33.315647, -111.583557 33.315778, -111.583558 33.31595, -111.583585 33.316134, -111.583582 33.316161, -111.583578 33.316195, -111.583554 33.316252, -111.583514 33.316308, -111.583474 33.316353, -111.583467 33.316384, -111.583474 33.316412, -111.583527 33.316553, -111.583541 33.316615, -111.583553 33.316668, -111.583557 33.316744, -111.583554 33.31682, -111.583565 33.316884, -111.583592 33.316972, -111.583594 33.317009, -111.583595 33.317033, -111.583585 33.317186, -111.583571 33.317407, -111.583569 33.31748, -111.583565 33.317687, -111.583549 33.317755, -111.583502 33.317899, -111.583492 33.317928, -111.583487 33.317985, -111.5835 33.318216, -111.583523 33.318288, -111.583553 33.318354, -111.583568 33.31842, -111.5836 33.318936, -111.583595 33.319029, -111.58358 33.319145, -111.583578 33.319288, -111.583612 33.319616, -111.583609 33.319715, -111.583603 33.319768, -111.583611 33.320439, -111.583612 33.32058, -111.583613 33.320659, -111.583616 33.320871, -111.583617 33.320949, -111.583637 33.322475, -111.583655 33.323757, -111.583668 33.324736, -111.583669 33.324827, -111.583701 33.326213, -111.583709 33.330091, -111.583711 33.330465, -111.583712 33.330533, -111.583712 33.330603, -111.583726 33.332074, -111.583726 33.333728, -111.583731 33.334523, -111.583737 33.335322, -111.58374 33.336626, -111.583741 33.337314, -111.583741 33.338006, -111.58375 33.338867, -111.583793 33.341205, -111.583795 33.341619, -111.583796 33.342041, -111.583797 33.342056, -111.583798 33.342293, -111.583798 33.342399, -111.583799 33.342494, -111.583799 33.342557, -111.583793 33.342616, -111.583799 33.342629, -111.583791 33.343019, -111.583817 33.346093, -111.58383 33.347496, -111.583836 33.348194, -111.583848 33.349584, -111.58385 33.349764, -111.583854 33.350116, -111.583857 33.350409, -111.583863 33.350912, -111.583874 33.351847, -111.583875 33.351917, -111.583879 33.35233, -111.583879 33.352367, -111.583884 33.352877, -111.583886 33.353108, -111.583894 33.353848, -111.583894 33.353869, -111.583896 33.354154, -111.5839 33.354598, -111.5839 33.354646, -111.583907 33.355364, -111.583907 33.355378, -111.583911 33.355835, -111.583915 33.356305, -111.583919 33.356779, -111.583923 33.357003, -111.583877 33.357512, -111.58388 33.357827, -111.584356 33.357819, -111.584405 33.361792, -111.583918 33.361793, -111.583929 33.362908, -111.583932 33.363154, -111.58394 33.364135, -111.583953 33.365423, -111.583967 33.366955, -111.583994 33.369914, -111.58401 33.371366, -111.584011 33.371548, -111.58402 33.372552, -111.584079 33.378649, -111.580688 33.378649, -111.580696 33.37874, -111.578703 33.37876, -111.577812 33.378763, -111.576333 33.378773, -111.575738 33.378777, -111.575594 33.378779, -111.573753 33.37878, -111.572833 33.37878, -111.572617 33.378784, -111.572542 33.378785, -111.572468 33.378787, -111.571464 33.378804, -111.57086 33.378814, -111.57034 33.378822, -111.569861 33.378822, -111.567818 33.378822, -111.565375 33.378825, -111.564451 33.378822, -111.563484 33.378814, -111.563434 33.378814, -111.562519 33.378805, -111.560816 33.378822, -111.557535 33.378853, -111.55621 33.378836, -111.555594 33.378839, -111.554534 33.378843, -111.554113 33.378845, -111.546146 33.378755, -111.545722 33.378755, -111.544829 33.378754, -111.542349 33.378768, -111.541689 33.378782, -111.541555 33.378785, -111.540366 33.37881, -111.539039 33.378805, -111.538748 33.378805, -111.538204 33.378804, -111.5378 33.378819, -111.537292 33.378841, -111.53721 33.378839, -111.537185 33.378838, -111.536791 33.378825, -111.536547 33.378808, -111.536243 33.378794, -111.535964 33.37878, -111.535599 33.378752, -111.535242 33.378743, -111.535184 33.378742, -111.534643 33.37876, -111.533255 33.378766, -111.533071 33.378763, -111.531912 33.378757, -111.531351 33.378753, -111.5292 33.378759, -111.528767 33.378763, -111.528485 33.378771, -111.52814 33.37878, -111.527965 33.378791, -111.527658 33.378825, -111.527257 33.378842, -111.524858 33.37886, -111.524422 33.378863, -111.524279 33.378864, -111.521806 33.37886, -111.520081 33.378858, -111.51983 33.378858, -111.51759 33.378858, -111.516398 33.378855, -111.515366 33.378859, -111.513484 33.378867, -111.511961 33.378873, -111.511599 33.37885, -111.511535 33.378878, -111.511516 33.378895, -111.511485 33.378923, -111.511458 33.378954, -111.511429 33.379116, -111.511418 33.380279, -111.511422 33.381482, -111.511024 33.381476, -111.510692 33.381484, -111.510435 33.381468, -111.510043 33.381408, -111.509979 33.381386, -111.509726 33.381349, -111.50956 33.381309, -111.509532 33.381591, -111.509461 33.3818, -111.509448 33.381849, -111.508017 33.381828, -111.50691 33.381827, -111.506542 33.381827, -111.506254 33.381847, -111.505971 33.381874, -111.505768 33.381909, -111.50532 33.381997, -111.504789 33.382075, -111.504681 33.382106, -111.504642 33.382121, -111.504657 33.382661, -111.50464 33.382865, -111.504587 33.383004, -111.504525 33.383152, -111.504432 33.383405, -111.504428 33.383526, -111.504443 33.383662, -111.504479 33.383775, -111.50453 33.384076, -111.504576 33.38416, -111.504603 33.384189, -111.504684 33.3842, -111.505269 33.384195, -111.505744 33.384173, -111.506128 33.384026, -111.50636 33.383922, -111.506664 33.384366, -111.507109 33.385016, -111.507254 33.385258, -111.507489 33.38537, -111.507656 33.38541, -111.507984 33.385429, -111.508423 33.38542, -111.5087 33.385415, -111.508965 33.38541, -111.509203 33.385371, -111.509571 33.385264, -111.509984 33.385095, -111.510248 33.384951, -111.510289 33.384929, -111.510563 33.384856, -111.510842 33.384842, -111.511278 33.384849, -111.511438 33.384856, -111.511438 33.38517, -111.51144 33.385592, -111.511438 33.3861, -111.511438 33.386423, -111.511288 33.386425, -111.511096 33.386457, -111.509547 33.386671, -111.508409 33.386811, -111.507718 33.38691, -111.507439 33.386957, -111.507017 33.387076, -111.505889 33.38713, -111.504785 33.38711, -111.504439 33.387071, -111.504335 33.387054, -111.503996 33.387012, -111.503646 33.386954, -111.502896 33.386808, -111.502863 33.386802, -111.502307 33.386657, -111.501817 33.386487, -111.501456 33.38635, -111.501011 33.386177, -111.50056 33.38595, -111.500425 33.385874, -111.499335 33.385263, -111.499211 33.385194, -111.497689 33.384304, -111.496254 33.383491, -111.495782 33.383218, -111.495167 33.38286, -111.494778 33.382634, -111.493809 33.382044, -111.493213 33.381676, -111.492922 33.381484, -111.4925 33.381176, -111.492228 33.380941, -111.491378 33.380124, -111.490902 33.379631, -111.490729 33.379452, -111.490521 33.379237, -111.490436 33.379149, -111.490267 33.378974, -111.488128 33.376817, -111.486554 33.375197, -111.486341 33.375011, -111.48492 33.373578, -111.484538 33.373188, -111.484147 33.37281, -111.483066 33.371687, -111.482964 33.371584, -111.482432 33.371049, -111.482088 33.370711, -111.47964 33.368228, -111.478854 33.367452, -111.478023 33.366598, -111.477955 33.366529, -111.477899 33.36647, -111.477654 33.366644, -111.477773 33.366762, -111.478048 33.367037, -111.478136 33.367125, -111.478518 33.367525, -111.479257 33.368302, -111.479799 33.368845, -111.480414 33.369464, -111.481509 33.370579, -111.482092 33.371166, -111.48256 33.371634, -111.483496 33.372574, -111.483705 33.372784, -111.483994 33.373075, -111.485373 33.374221, -111.487181 33.37614, -111.49019 33.379145, -111.490011 33.379142, -111.490299 33.379436, -111.48999 33.379589, -111.489863 33.379675, -111.489798 33.379768, -111.489772 33.379865, -111.48976 33.379994, -111.48976 33.380162, -111.489762 33.381023, -111.489792 33.382292, -111.489796 33.382467, -111.489801 33.382657, -111.489788 33.384256, -111.489786 33.384704, -111.489782 33.385465, -111.489771 33.386073, -111.491425 33.38608, -111.493119 33.386071, -111.49312 33.386733, -111.493127 33.386861, -111.493165 33.386893, -111.493379 33.386893, -111.493788 33.386884, -111.494114 33.386882, -111.494119 33.387591, -111.494117 33.387859, -111.494112 33.388359, -111.494109 33.388652, -111.494105 33.389141, -111.494109 33.38973, -111.493714 33.389729, -111.493022 33.389726, -111.492009 33.389722, -111.491829 33.389721, -111.490983 33.389715, -111.490018 33.389708, -111.489762 33.389713, -111.48976 33.389933, -111.489791 33.391275, -111.489791 33.391519, -111.48979 33.393361, -111.489237 33.393362, -111.487619 33.393363, -111.487237 33.393363, -111.485471 33.393366, -111.481117 33.393382, -111.480411 33.393385, -111.477762 33.393377, -111.476843 33.393376, -111.476858 33.394244, -111.476856 33.394289, -111.476826 33.395133, -111.476811 33.39556, -111.476801 33.396608, -111.4768 33.396729, -111.476835 33.396922, -111.476834 33.396953, -111.476829 33.397084, -111.476842 33.398756, -111.476859 33.40062, -111.476973 33.400968, -111.477014 33.401134, -111.476809 33.401963, -111.476807 33.402095, -111.476786 33.402185, -111.476746 33.402276, -111.476685 33.402357, -111.476643 33.402395, -111.476603 33.402431, -111.476521 33.40249, -111.476425 33.40254, -111.476299 33.402574, -111.476172 33.402586, -111.475683 33.402582, -111.475291 33.402578, -111.474668 33.402592, -111.474503 33.402619, -111.474384 33.402656, -111.474271 33.402723, -111.474073 33.402921, -111.47397 33.402853, -111.473738 33.402742, -111.473497 33.40267, -111.473225 33.402623, -111.472979 33.402607, -111.472727 33.402619, -111.472508 33.402647, -111.47205 33.402777, -111.471369 33.402948, -111.470832 33.403036, -111.469987 33.403187, -111.469804 33.403257, -111.469572 33.403373, -111.469418 33.403478, -111.469383 33.403528, -111.469269 33.403668, -111.469175 33.40382, -111.469094 33.404054, -111.469079 33.404325, -111.469072 33.404798, -111.469076 33.404977, -111.469093 33.405104, -111.469127 33.405219, -111.4692 33.405378, -111.469325 33.405544, -111.46945 33.405662, -111.469531 33.405716, -111.469631 33.40578, -111.469825 33.405873, -111.470087 33.405935, -111.470314 33.405954, -111.470527 33.405952, -111.470825 33.405968, -111.470816 33.406244, -111.470811 33.406397, -111.470805 33.406461, -111.470764 33.406941, -111.47084 33.406948, -111.471126 33.407009, -111.471496 33.407125, -111.471785 33.407201, -111.472075 33.407221, -111.472377 33.407223, -111.472641 33.40719, -111.472932 33.407137, -111.473187 33.407052, -111.473409 33.406992, -111.473599 33.406955, -111.473881 33.406913, -111.474454 33.406903, -111.474677 33.406894, -111.474874 33.406868, -111.475122 33.406801, -111.475438 33.406678, -111.475753 33.407249, -111.475839 33.407386, -111.475947 33.407488, -111.476082 33.40759, -111.47624 33.407691, -111.476389 33.407744, -111.476404 33.407758, -111.476604 33.4078, -111.476912 33.407804, -111.477275 33.407809, -111.477953 33.407823, -111.477952 33.408046, -111.477952 33.40855, -111.477947 33.409734, -111.477947 33.409765, -111.477946 33.409951, -111.477426 33.409889, -111.477205 33.409906, -111.47711 33.409923, -111.477093 33.409944, -111.477091 33.409961, -111.477091 33.409996, -111.477117 33.41005, -111.477174 33.410082, -111.476892 33.411219, -111.477448 33.411551, -111.477362 33.412008, -111.477013 33.413172, -111.476786 33.41508, -111.476994 33.422207, -111.477485 33.423229, -111.477471 33.423175, -111.477456 33.423067, -111.477491 33.423025, -111.477534 33.422989, -111.477634 33.42297, -111.47774 33.42297, -111.477812 33.422987, -111.477862 33.423029, -111.477862 33.423251, -111.477859 33.423688, -111.477854 33.424137, -111.477849 33.424647, -111.477016 33.424659, -111.476967 33.425942, -111.477233 33.426859, -111.477431 33.427793, -111.476633 33.428882, -111.477906 33.428885, -111.477914 33.428427, -111.477918 33.428207, -111.47794 33.428174, -111.477925 33.427802, -111.478558 33.427816, -111.478767 33.427815, -111.479052 33.427815, -111.479087 33.428418, -111.479087 33.428488, -111.479106 33.42853, -111.480015 33.428521, -111.480054 33.429651, -111.479715 33.429654, -111.479178 33.429658, -111.479106 33.429997, -111.478999 33.430498, -111.479102 33.43092, -111.479056 33.431199, -111.479045 33.431359, -111.479046 33.431485, -111.479004 33.431534, -111.478958 33.43184, -111.479 33.431905, -111.47903 33.43198, -111.479055 33.43219, -111.479034 33.432366, -111.478794 33.432416, -111.478574 33.432471, -111.478466 33.432471, -111.478246 33.432437, -111.478247 33.432617, -111.47794 33.433325, -111.476738 33.433586, -111.476714 33.434137, -111.477319 33.435257, -111.476871 33.43591, -111.477127 33.436188, -111.47714 33.436421, -111.477148 33.436542, -111.477148 33.438611, -111.4776 33.438544, -111.477658 33.438567, -111.477822 33.438678, -111.477915 33.438713, -111.478011 33.438716, -111.478193 33.438696, -111.478683 33.438682, -111.479085 33.438682, -111.479089 33.43842, -111.479247 33.438353, -111.479343 33.438305, -111.479442 33.438283, -111.479544 33.438271, -111.47973 33.43819, -111.479906 33.438164, -111.480135 33.438138, -111.480127 33.437576, -111.48012 33.437095, -111.480137 33.43696, -111.480173 33.436895, -111.480232 33.436866, -111.480363 33.43685, -111.48062 33.436849, -111.48099 33.436847, -111.48112 33.436858, -111.481127 33.437511, -111.481139 33.437764, -111.481141 33.43781, -111.481165 33.43833, -111.481166 33.438361, -111.481161 33.438647, -111.481142 33.439661, -111.479189 33.439647, -111.479093 33.439673, -111.479031 33.439718, -111.478935 33.439857, -111.478893 33.439951, -111.478874 33.440048, -111.478863 33.440105, -111.478855 33.440238, -111.478867 33.44033, -111.478916 33.440437, -111.478938 33.440486, -111.478495 33.440477, -111.477936 33.440502, -111.477919 33.440663, -111.477928 33.440939, -111.477882 33.441083, -111.477874 33.441245, -111.477873 33.441608, -111.47784 33.441722, -111.477783 33.441775, -111.477708 33.441795, -111.477624 33.441778, -111.477531 33.441753, -111.477423 33.441769, -111.477299 33.441802, -111.477164 33.44183, -111.476735 33.44214, -111.476728 33.442673, -111.476843 33.444038, -111.476932 33.445101, -111.476917 33.445793, -111.476918 33.445985, -111.476921 33.446829, -111.479032 33.446858, -111.479 33.447783, -111.478137 33.447747, -111.476925 33.447719, -111.476929 33.448538, -111.47693 33.448611, -111.476944 33.449561, -111.476839 33.451009, -111.476843 33.451352, -111.477015 33.451549, -111.477122 33.451531, -111.477255 33.451553, -111.47737 33.451536, -111.477484 33.451499, -111.477552 33.451461, -111.47781 33.451481, -111.47808 33.451501, -111.478269 33.451513, -111.478389 33.451499, -111.478414 33.451489, -111.478465 33.451453, -111.478555 33.451405, -111.47864 33.451383, -111.478976 33.451366, -111.479354 33.451366, -111.479557 33.45136, -111.479766 33.451338, -111.479863 33.451344, -111.479963 33.451363, -111.480026 33.451396, -111.480087 33.451414, -111.480159 33.451411, -111.480231 33.451393, -111.4802 33.450539, -111.480166 33.449562, -111.480293 33.449567, -111.480758 33.449584, -111.481167 33.449585, -111.482369 33.449592, -111.482367 33.450135, -111.482378 33.451225, -111.482384 33.451346, -111.48268 33.45134, -111.483336 33.451358, -111.483607 33.451365, -111.484085 33.451363, -111.484301 33.451372, -111.484581 33.451386, -111.484869 33.451403, -111.485275 33.451427, -111.485696 33.451438, -111.485949 33.451419, -111.486235 33.451383, -111.486421 33.451358, -111.486975 33.451346, -111.48727 33.451348, -111.487705 33.451349, -111.488495 33.451358, -111.489835 33.451343, -111.490826 33.451346, -111.491075 33.451347, -111.492093 33.451338, -111.492541 33.451335, -111.492928 33.451337, -111.493111 33.451338, -111.493211 33.451338, -111.493552 33.45134, -111.494221 33.451285, -111.494205 33.451247, -111.494199 33.451102, -111.494212 33.450477, -111.494225 33.449897, -111.494212 33.449751, -111.494169 33.449646, -111.494231 33.449455, -111.494249 33.449119, -111.494249 33.448684, -111.494247 33.447678, -111.494243 33.447316, -111.49424 33.446747, -111.49424 33.44635, -111.494241 33.445929, -111.49424 33.445081, -111.494239 33.4442, -111.494239 33.444151, -111.494239 33.443989, -111.494239 33.443678, -111.494262 33.443588, -111.49422 33.44337, -111.494194 33.443253, -111.493909 33.442104, -111.49418 33.441378, -111.494178 33.441253, -111.494192 33.440517, -111.493935 33.44051, -111.492154 33.440491, -111.491955 33.440489, -111.491075 33.440489, -111.490178 33.440489, -111.489846 33.440494, -111.489839 33.439503, -111.489846 33.438736, -111.489846 33.438679, -111.489847 33.438612, -111.489854 33.437894, -111.489841 33.437503, -111.489868 33.437208, -111.489878 33.43694, -111.490218 33.436937, -111.490235 33.436937, -111.490914 33.436905, -111.490938 33.436904, -111.491332 33.436874, -111.49169 33.436846, -111.491961 33.436843, -111.492387 33.436838, -111.492509 33.436848, -111.492971 33.436886, -111.493617 33.436939, -111.49387 33.436937, -111.494171 33.436935, -111.494163 33.436802, -111.494184 33.435558, -111.494184 33.432954, -111.494185 33.432162, -111.494183 33.4318, -111.494154 33.431725, -111.494158 33.431441, -111.494163 33.430535, -111.494168 33.429755, -111.494168 33.429667, -111.494274 33.429668, -111.495083 33.42967, -111.496442 33.429673, -111.498071 33.429665, -111.500687 33.429675, -111.502761 33.429678, -111.502878 33.429679, -111.503295 33.429681, -111.503344 33.429602, -111.503345 33.429527, -111.502906 33.429546, -111.502758 33.429552, -111.502755 33.429125, -111.502754 33.428737, -111.502749 33.427883, -111.502753 33.427355, -111.502763 33.426048, -111.502745 33.424705, -111.502747 33.424225, -111.50275 33.423713, -111.502751 33.42358, -111.502752 33.423385, -111.502749 33.423066, -111.502742 33.422372, -111.503454 33.422373, -111.505097 33.422376, -111.506117 33.422378, -111.511484 33.422387, -111.511484 33.423377, -111.511481 33.424165, -111.511479 33.424996, -111.511484 33.425865, -111.511486 33.426698, -111.511486 33.427431, -111.511491 33.427827, -111.512119 33.427858, -111.512176 33.427818, -111.51219 33.427749, -111.512192 33.427422, -111.513653 33.427571, -111.513615 33.426937, -111.51363 33.425956, -111.513804 33.426054, -111.513966 33.426084, -111.514153 33.426099, -111.514272 33.426119, -111.514308 33.426175, -111.514334 33.426264, -111.514355 33.426685, -111.514403 33.426935, -111.514677 33.426934, -111.515297 33.426933, -111.515755 33.426918, -111.515756 33.426986, -111.515773 33.427788, -111.515779 33.428066, -111.515812 33.429634, -111.514656 33.429639, -111.513663 33.429645, -111.513608 33.429646, -111.513591 33.430346, -111.513597 33.430535, -111.513515 33.430535, -111.513454 33.430526, -111.513399 33.430522, -111.513297 33.430514, -111.512838 33.430504, -111.512212 33.43049, -111.511734 33.430486, -111.51148 33.430493, -111.511488 33.431334, -111.511488 33.432564, -111.511496 33.433267, -111.512143 33.433267, -111.512597 33.433273, -111.513094 33.43328, -111.513601 33.433287, -111.514672 33.433284, -111.515257 33.433284, -111.515758 33.433282, -111.516397 33.433279, -111.516323 33.432578, -111.51629 33.432129, -111.516308 33.431446, -111.516872 33.43146, -111.516872 33.431288, -111.516873 33.430613, -111.517157 33.430585, -111.517593 33.430585, -111.517912 33.430593, -111.517914 33.430481, -111.51763 33.430506, -111.517439 33.430497, -111.517385 33.430462, -111.517373 33.430376, -111.51738 33.430256, -111.517391 33.430222, -111.51744 33.43018, -111.517631 33.430118, -111.51792 33.43012, -111.517928 33.429627, -111.518473 33.429623, -111.519359 33.429619, -111.519466 33.429617, -111.519664 33.429617, -111.520124 33.429617, -111.520123 33.430371, -111.520122 33.431787, -111.52012 33.432026, -111.520115 33.432658, -111.520115 33.432898, -111.52012 33.433295, -111.520652 33.4333, -111.522358 33.433276, -111.522707 33.433276, -111.524515 33.433282, -111.524952 33.433282, -111.526262 33.433281, -111.5266 33.433041, -111.526706 33.432966, -111.527115 33.432679, -111.528735 33.431514, -111.528743 33.431407, -111.528716 33.430523, -111.528712 33.430264, -111.528722 33.429617, -111.528745 33.42816, -111.528743 33.426916, -111.528742 33.426786, -111.528742 33.426484, -111.528737 33.423833, -111.528765 33.422362, -111.529831 33.422365, -111.533142 33.422353, -111.533509 33.422351, -111.533523 33.422188, -111.533556 33.422131, -111.533687 33.422058, -111.534026 33.421903, -111.534299 33.421864, -111.534396 33.421874, -111.534475 33.421967, -111.534521 33.42219, -111.534526 33.422348, -111.534758 33.422348, -111.534783 33.421587, -111.534816 33.421521, -111.534861 33.42148, -111.534936 33.42147, -111.535085 33.421478, -111.535216 33.421494, -111.535246 33.421503, -111.53527 33.422347, -111.536022 33.422345, -111.536403 33.422345, -111.537462 33.422345, -111.53745 33.420986, -111.537445 33.420502, -111.537436 33.418702, -111.539078 33.418706, -111.539802 33.418705, -111.541064 33.418702, -111.541766 33.418705, -111.541768 33.418556, -111.541772 33.418186, -111.541779 33.417549, -111.54178 33.417386, -111.54215 33.417378, -111.542307 33.417368, -111.542385 33.41733, -111.542425 33.417254, -111.542447 33.417082, -111.542427 33.416978, -111.54239 33.41695, -111.54221 33.416929, -111.541785 33.41695, -111.541774 33.415073, -111.542818 33.415072, -111.543873 33.41507, -111.546109 33.415081, -111.546115 33.416061, -111.546114 33.416289, -111.546112 33.417306, -111.546117 33.418143, -111.546034 33.418241, -111.546041 33.419086, -111.54565 33.419441, -111.545482 33.419562, -111.54542 33.419607, -111.544822 33.420042, -111.544549 33.420237, -111.543835 33.420733, -111.543481 33.421, -111.542873 33.421428, -111.542494 33.4217, -111.542258 33.42188, -111.541877 33.422147, -111.541633 33.422347, -111.541952 33.422368, -111.542515 33.422367, -111.542506 33.422607, -111.542354 33.422655, -111.542215 33.422756, -111.542061 33.422919, -111.541926 33.423119, -111.541868 33.423266, -111.541856 33.423332, -111.541866 33.42338, -111.541905 33.423399, -111.542116 33.423428, -111.542271 33.423422, -111.542336 33.4234, -111.542394 33.423326, -111.542503 33.423174, -111.542652 33.423042, -111.54281 33.42299, -111.543027 33.422932, -111.543355 33.422914, -111.54341 33.42289, -111.543445 33.422846, -111.543457 33.422753, -111.543421 33.422652, -111.543371 33.422587, -111.543349 33.422555, -111.543336 33.422496, -111.543348 33.422331, -111.543726 33.422325, -111.544204 33.422323, -111.544446 33.422323, -111.544888 33.42232, -111.544869 33.422853, -111.544888 33.42325, -111.544876 33.423906, -111.544859 33.424124, -111.54482 33.424129, -111.544767 33.424182, -111.544771 33.424297, -111.544821 33.424349, -111.544936 33.424371, -111.545088 33.424379, -111.545437 33.424342, -111.545519 33.424314, -111.545695 33.424239, -111.545726 33.42427, -111.545808 33.424298, -111.546098 33.424332, -111.546098 33.424917, -111.546098 33.425438, -111.546098 33.425485, -111.546107 33.425961, -111.546108 33.425984, -111.54611 33.426125, -111.546115 33.426395, -111.546116 33.426466, -111.546116 33.426485, -111.546125 33.426888, -111.54613 33.427138, -111.54611 33.428241, -111.546088 33.429308, -111.546083 33.429558, -111.546082 33.429603, -111.546073 33.43004, -111.546084 33.430371, -111.546104 33.43101, -111.546117 33.43139, -111.546122 33.431541, -111.546131 33.431814, -111.546127 33.432125, -111.546119 33.432781, -111.546118 33.432868, -111.546113 33.433196, -111.546111 33.433712, -111.546115 33.434134, -111.546125 33.435009, -111.546126 33.435141, -111.546153 33.436467, -111.546153 33.436867, -111.546154 33.437244, -111.54635 33.437295, -111.546564 33.437415, -111.546677 33.437486, -111.546782 33.437514, -111.546916 33.437596, -111.548046 33.438431, -111.549131 33.439239, -111.549754 33.439684, -111.549898 33.439769, -111.549944 33.439796, -111.550072 33.439832, -111.550404 33.439898, -111.550503 33.439906, -111.550609 33.439906, -111.550689 33.4399, -111.550889 33.439861, -111.551003 33.439861, -111.551127 33.439881, -111.551266 33.439943, -111.551764 33.440271, -111.552303 33.440672, -111.552387 33.440767, -111.552413 33.440797, -111.552432 33.440868, -111.552403 33.440935, -111.552301 33.441018, -111.552127 33.441135, -111.552025 33.441243, -111.552026 33.441288, -111.552048 33.441316, -111.552095 33.441325, -111.552376 33.441284, -111.55243 33.44127, -111.553902 33.4409, -111.55506 33.440624, -111.559541 33.439551, -111.560765 33.439234, -111.560911 33.439199, -111.561024 33.439196, -111.561166 33.439247, -111.562343 33.440077, -111.563107 33.440624, -111.563382 33.440813, -111.563388 33.439529, -111.563391 33.438733, -111.563383 33.436829, -111.567507 33.436833, -111.567682 33.436833, -111.568463 33.436836, -111.569806 33.436833, -111.570499 33.436832, -111.572068 33.436822, -111.573079 33.436815, -111.574156 33.436814, -111.575125 33.436814, -111.576384 33.436802, -111.577338 33.436794, -111.578055 33.436799, -111.58039 33.436816, -111.580672 33.436818, -111.580762 33.436819, -111.580786 33.436819, -111.581751 33.436826, -111.582943 33.436826, -111.583903 33.436826, -111.584983 33.436827, -111.585012 33.436827, -111.585166 33.436827, -111.585304 33.436827, -111.585457 33.436827, -111.585984 33.436827, -111.588008 33.436827, -111.588235 33.436827, -111.589305 33.436827, -111.591478 33.436828, -111.59364 33.436829, -111.594282 33.43683, -111.595738 33.43683, -111.597966 33.436829, -111.597962 33.437775, -111.597962 33.438834, -111.597962 33.440592, -111.597963 33.440793, -111.597963 33.441695, -111.597966 33.443821, -111.598078 33.443818, -111.598078 33.443962, -111.598079 33.444084, -111.59808 33.444281, -111.598052 33.447707, -111.598038 33.449635, -111.598031 33.450159, -111.598037 33.450274, -111.598103 33.45048, -111.598146 33.450533, -111.598204 33.450623, -111.598249 33.450738, -111.598216 33.450742, -111.598158 33.45082, -111.598264 33.450776, -111.598361 33.451045, -111.600309 33.451123, -111.601772 33.451141, -111.602328 33.451134, -111.604363 33.451141, -111.604448 33.451141, -111.606677 33.451131, -111.607996 33.451125, -111.608837 33.451133, -111.611072 33.451126, -111.611591 33.451194, -111.612007 33.451199, -111.612807 33.451193, -111.613757 33.451123, -111.615132 33.451121, -111.615137 33.451039, -111.615272 33.450643, -111.615271 33.450981, -111.615271 33.451101, -111.615276 33.451121, -111.615287 33.451161, -111.615304 33.45119, -111.615322 33.451222, -111.615361 33.451262, -111.615375 33.451273, -111.615268 33.451326, -111.615247 33.451341, -111.61535 33.45134, -111.615452 33.451338, -111.615551 33.451338, -111.619257 33.451347, -111.619644 33.451348, -111.619649 33.451275, -111.619646 33.451255, -111.619643 33.451234, -111.619639 33.451202, -111.619631 33.451175, -111.621746 33.451185, -111.621751 33.451204, -111.621755 33.451222, -111.621762 33.451254, -111.621771 33.451293, -111.621785 33.451352, -111.622352 33.451352, -111.62292 33.451355, -111.622981 33.451355, -111.623399 33.451355, -111.623799 33.451355, -111.624071 33.451358, -111.62409 33.451358, -111.624104 33.451358, -111.624147 33.455564, -111.624168 33.457119, -111.624167 33.457208, -111.624027 33.465941, -111.626824 33.465971, -111.627096 33.465967, -111.628833 33.466056, -111.629185 33.466074, -111.630376 33.466175, -111.630389 33.46611, -111.630399 33.466064, -111.630409 33.466017, -111.630472 33.465761, -111.630572 33.465418, -111.630744 33.464796, -111.630783 33.464667, -111.630944 33.464212, -111.631067 33.463819, -111.631171 33.463486, -111.631379 33.462881, -111.631494 33.462573, -111.631542 33.462443, -111.631686 33.461981, -111.632048 33.462057, -111.632511 33.462138, -111.632628 33.462158, -111.632679 33.462167, -111.632766 33.462182, -111.632919 33.462204, -111.633072 33.462229, -111.633728 33.462328, -111.633672 33.462472, -111.633663 33.46253, -111.633666 33.4626, -111.633673 33.462671, -111.633676 33.462792, -111.633675 33.462823, -111.633485 33.462829, -111.633106 33.46287, -111.632961 33.462887, -111.632587 33.462899, -111.632555 33.462899, -111.632556 33.462946, -111.632544 33.463417, -111.632541 33.464219, -111.632487 33.465756, -111.632448 33.465797, -111.63241 33.465868, -111.632403 33.466081, -111.632393 33.466443, -111.632391 33.466827, -111.632437 33.466943, -111.632441 33.466954, -111.632464 33.467045, -111.632479 33.46717, -111.632482 33.467394, -111.632483 33.46744, -111.63248 33.467889, -111.632484 33.468023, -111.632482 33.468683, -111.632482 33.468749, -111.632489 33.469307, -111.632487 33.469656, -111.632586 33.469662, -111.632686 33.469667, -111.632945 33.469659, -111.63364 33.469662, -111.633896 33.469672, -111.633976 33.469676, -111.634065 33.469687, -111.634151 33.469689, -111.634284 33.469681, -111.633991 33.473374, -111.634209 33.473371, -111.634423 33.473377, -111.634672 33.473374, -111.634694 33.474065, -111.634678 33.474524, -111.634677 33.474683, -111.634658 33.476658, -111.634636 33.476999, -111.63449 33.476995, -111.633991 33.476979, -111.633708 33.476987, -111.633674 33.476993, -111.633641 33.477007, -111.633608 33.477038, -111.633592 33.477072, -111.633579 33.47739, -111.633588 33.477604, -111.633605 33.477812, -111.633602 33.477956, -111.633586 33.478294, -111.633594 33.478568, -111.63361 33.478645, -111.633634 33.478688, -111.633669 33.478726, -111.633731 33.478763, -111.633816 33.478783, -111.633848 33.478789, -111.634171 33.478808, -111.632843 33.478796, -111.63282 33.478797, -111.632694 33.478802, -111.632723 33.480733, -111.632831 33.480734, -111.635588 33.480719, -111.635588 33.478825, -111.635676 33.478825, -111.636063 33.478819, -111.636479 33.478823, -111.636898 33.478818, -111.636941 33.478817, -111.637144 33.478821, -111.637733 33.478834, -111.638202 33.47885, -111.638368 33.478851, -111.638749 33.478837, -111.639567 33.478825, -111.640041 33.478826, -111.640577 33.478828, -111.640828 33.478829, -111.640831 33.476995, -111.642038 33.476995, -111.647998 33.477055, -111.649853 33.477077, -111.649866 33.478451, -111.649878 33.47882, -111.649883 33.479595, -111.649883 33.479643, -111.649883 33.480597, -111.649878 33.480621, -111.649858 33.480691, -111.649853 33.480714, -111.649849 33.481254, -111.649834 33.482459, -111.649823 33.484285, -111.649834 33.484792, -111.649832 33.485393, -111.649829 33.485661, -111.649828 33.486404, -111.649824 33.486995, -111.649822 33.487301, -111.649821 33.487459, -111.649845 33.487639, -111.649887 33.487811, -111.649944 33.487978, -111.649958 33.488011, -111.649971 33.488038, -111.650022 33.488114, -111.650099 33.488197, -111.650184 33.488263, -111.651053 33.488782, -111.650479 33.492146, -111.650045 33.494695, -111.649948 33.495044, -111.651878 33.49505, -111.653809 33.495055, -111.667698 33.495092, -111.667875 33.495092, -111.680797 33.495128, -111.681588 33.495131, -111.681957 33.495131, -111.682326 33.495133, -111.682446 33.495133, -111.682475 33.495133, -111.682622 33.495133, -111.682521 33.495431, -111.682376 33.495728, -111.682287 33.495874, -111.682169 33.496025, -111.682119 33.49608, -111.681962 33.49626, -111.68162 33.496558, -111.680679 33.497159, -111.680287 33.497377, -111.679926 33.497703, -111.679657 33.49797, -111.679511 33.498175, -111.679358 33.498418, -111.679278 33.498773, -111.67927 33.499088, -111.679385 33.499873, -111.67943 33.500042, -111.67946 33.500292, -111.679509 33.500504, -111.679607 33.500805, -111.679681 33.501063, -111.679898 33.501519, -111.680013 33.501747, -111.680262 33.502057, -111.680352 33.502249, -111.680382 33.502364, -111.680348 33.502412, -111.680334 33.502431, -111.68037 33.502792, -111.680381 33.503369, -111.680429 33.503753, -111.681134 33.503946, -111.681466 33.504062, -111.682237 33.50437, -111.68231 33.504404, -111.682392 33.504451, -111.682621 33.504626, -111.682713 33.504687, -111.682865 33.504771, -111.683007 33.504831, -111.683144 33.504875, -111.683485 33.50496, -111.68373 33.505026, -111.684042 33.505125, -111.68404 33.505018, -111.684037 33.504835, -111.684036 33.504754, -111.684034 33.504631, -111.683842 33.502601, -111.683831 33.502481, -111.684083 33.502482, -111.684653 33.502474, -111.696653 33.502052, -111.696651 33.501253, -111.696648 33.499735, -111.69907 33.499709, -111.701279 33.499679, -111.701254 33.497745, -111.701805 33.497749, -111.701784 33.497658, -111.701671 33.497186, -111.70148 33.497003, -111.701225 33.496887, -111.700955 33.496764, -111.700495 33.496613, -111.700182 33.496409, -111.700047 33.496293, -111.699929 33.496189, -111.699269 33.495541, -111.699 33.495227, -111.69877 33.494833, -111.698716 33.49475, -111.698767 33.494593, -111.699325 33.493195, -111.699451 33.493231, -111.699627 33.493274, -111.699836 33.493311, -111.699985 33.493324, -111.700764 33.493326, -111.700763 33.492551, -111.700762 33.492371, -111.700761 33.491937, -111.700752 33.491911, -111.700728 33.491881, -111.700694 33.49186, -111.700647 33.49185, -111.700243 33.491847, -111.700044 33.491836, -111.699825 33.491808, -111.699832 33.491746, -111.699834 33.491621, -111.699819 33.491494, -111.699788 33.491369, -111.699751 33.491273, -111.699697 33.491166, -111.699392 33.490704, -111.699777 33.490524, -111.699877 33.490473, -111.700051 33.490381, -111.700226 33.490281, -111.700405 33.490161, -111.700468 33.490121, -111.700554 33.490049, -111.700625 33.489967, -111.700681 33.489876, -111.700693 33.489846, -111.701298 33.490083, -111.701683 33.488968, -111.700743 33.488663, -111.700743 33.488397, -111.700735 33.488281, -111.700719 33.48819, -111.700677 33.488052, -111.700614 33.48792, -111.700559 33.487831, -111.700311 33.487524, -111.70041 33.487465, -111.70055 33.487385, -111.70077 33.487257, -111.700973 33.487126, -111.701062 33.487063, -111.70118 33.486963, -111.701303 33.486842, -111.701444 33.486674, -111.701552 33.486517, -111.701637 33.486362, -111.701709 33.486203, -111.702168 33.485152, -111.702238 33.484961, -111.702276 33.484814, -111.702305 33.484645, -111.702318 33.484451, -111.702312 33.484276, -111.702285 33.484096, -111.702239 33.483917, -111.702115 33.483576, -111.701677 33.482371, -111.701502 33.481893, -111.701316 33.481386, -111.701276 33.481237, -111.701252 33.481082, -111.70125 33.480885, -111.70125 33.480826, -111.701253 33.480317, -111.701249 33.480143, -111.701245 33.479939, -111.701224 33.479359, -111.701198 33.478502, -111.701191 33.477694, -111.701196 33.477221, -111.701203 33.476638, -111.701201 33.475839, -111.7012 33.475385, -111.701199 33.475206, -111.701199 33.474782, -111.701199 33.474655, -111.7012 33.471865, -111.701209 33.471514, -111.701209 33.471268, -111.701209 33.471142, -111.701209 33.4705, -111.701204 33.469678, -111.701227 33.468775, -111.701236 33.468165, -111.701246 33.468099, -111.701286 33.467973, -111.701296 33.467827, -111.701317 33.466518, -111.701314 33.466465, -111.701294 33.466392, -111.701258 33.466324, -111.702864 33.466326, -111.703314 33.466326, -111.704358 33.466327, -111.704612 33.46631, -111.705673 33.466312, -111.70585 33.466312, -111.707737 33.466316, -111.709383 33.466319, -111.709584 33.466321, -111.709916 33.466324, -111.710904 33.466334, -111.712034 33.466352, -111.713834 33.466341, -111.714609 33.466343, -111.716202 33.466344, -111.718556 33.466349, -111.71856 33.467003, -111.718544 33.468852, -111.718542 33.469129, -111.718534 33.47019, -111.718533 33.470332, -111.7185 33.473014, -111.718496 33.47368, -111.718476 33.475551, -111.71847 33.47607, -111.71846 33.476997, -111.718457 33.477283, -111.717507 33.477683, -111.717235 33.477731, -111.717144 33.477717, -111.716996 33.477693, -111.716647 33.477848, -111.716589 33.477874, -111.716401 33.47782, -111.716245 33.477764, -111.716214 33.478205, -111.716197 33.478508, -111.716145 33.479416, -111.716128 33.479677, -111.71614 33.479867, -111.716161 33.480171, -111.716185 33.480497, -111.716207 33.480795, -111.716723 33.480895, -111.717191 33.480992, -111.718093 33.481202, -111.71822 33.481226, -111.718375 33.481239, -111.718423 33.481248, -111.718482 33.481274, -111.718517 33.481301, -111.718528 33.481502, -111.718528 33.481566, -111.719795 33.481704, -111.720557 33.481807, -111.721689 33.481843, -111.722854 33.481814, -111.724723 33.481695, -111.726055 33.481538, -111.726363 33.481503, -111.726472 33.48149, -111.728334 33.481197, -111.72894 33.481088, -111.72982 33.481049, -111.730729 33.480996, -111.731529 33.480976, -111.732291 33.481005, -111.73314 33.481054, -111.734291 33.48108, -111.735404 33.481154, -111.735687 33.481109, -111.735706 33.482545, -111.735677 33.482753, -111.735206 33.484189, -111.735206 33.485124, -111.73521 33.485259, -111.735413 33.485132, -111.735751 33.484924, -111.736055 33.484828, -111.736614 33.484842, -111.737563 33.484898, -111.737794 33.484831, -111.737841 33.484773, -111.738069 33.484502, -111.738314 33.4842, -111.73849 33.483696, -111.738499 33.483668, -111.738653 33.483174, -111.738766 33.482692, -111.73894 33.482312, -111.73931 33.482104, -111.739753 33.481999, -111.740382 33.481803, -111.740987 33.481716, -111.741519 33.481795, -111.742107 33.481896, -111.742588 33.482014, -111.743175 33.481965, -111.744972 33.481885, -111.745995 33.481793, -111.746369 33.481763, -111.7466 33.481706, -111.746899 33.481544, -111.747327 33.481116, -111.747661 33.480709, -111.747737 33.480615, -111.747837 33.480496, -111.74789 33.480436, -111.747952 33.480369, -111.748421 33.479855, -111.748658 33.479596, -111.749466 33.47867, -111.750546 33.477477, -111.750692 33.477317, -111.751217 33.476622, -111.75169 33.476309, -111.753239 33.475347, -111.753351 33.475301, -111.756822 33.473854, -111.758528 33.473266, -111.759184 33.47292, -111.759912 33.472663, -111.760306 33.472524, -111.76194 33.471996, -111.765936 33.470706, -111.766087 33.470663, -111.766494 33.470526, -111.767124 33.470334, -111.768961 33.469735, -111.769505 33.469444, -111.769998 33.469334, -111.770063 33.469307, -111.770457 33.469223, -111.771756 33.468873, -111.772281 33.46868, -111.773317 33.468241, -111.773744 33.468252, -111.774971 33.468049, -111.775502 33.467939, -111.776073 33.467709, -111.776115 33.467703, -111.776781 33.46761, -111.777118 33.46751, -111.777155 33.4675, -111.777523 33.467208, -111.777562 33.467208, -111.778139 33.466873, -111.778165 33.466887, -111.778234 33.466928, -111.778322 33.466979, -111.778462 33.467059, -111.778504 33.467084, -111.778594 33.467125, -111.778407 33.467213, -111.778347 33.467228, -111.778226 33.467238, -111.778132 33.467256, -111.778042 33.467286, -111.778022 33.467292, -111.777756 33.467435, -111.777667 33.467495, -111.777575 33.467577, -111.77751 33.467652, -111.777468 33.467703, -111.777387 33.467765, -111.777298 33.467807, -111.777216 33.467832, -111.776764 33.467912, -111.776404 33.467959, -111.776125 33.46799, -111.775999 33.468019, -111.775877 33.468058, -111.775617 33.468173, -111.775414 33.468243, -111.775137 33.468313, -111.77472 33.468398, -111.77492 33.471082, -111.77503 33.471978, -111.77505 33.472172, -111.775214 33.471973, -111.775452 33.471686, -111.775721 33.471364, -111.775998 33.471013, -111.776918 33.46978, -111.776959 33.469728, -111.777092 33.469572, -111.777236 33.469423, -111.777361 33.469307, -111.777546 33.469155, -111.777713 33.469034, -111.777889 33.468922, -111.778437 33.46862, -111.778587 33.468522, -111.778701 33.468432, -111.779039 33.468128, -111.779123 33.468038, -111.779206 33.467926, -111.779238 33.467872, -111.77945 33.467509, -111.779587 33.467565, -111.779597 33.46757, -111.779793 33.467639, -111.780039 33.467711, -111.780472 33.46781, -111.780605 33.467838, -111.780811 33.467869, -111.78102 33.467888, -111.781511 33.467901, -111.781653 33.467905, -111.782448 33.467928, -111.782586 33.46793, -111.782875 33.467939, -111.782349 33.468728, -111.782134 33.469062, -111.781956 33.469346, -111.779986 33.472499, -111.779314 33.473369, -111.779157 33.473574, -111.779103 33.473644, -111.778425 33.474396, -111.778428 33.474778, -111.778428 33.474852, -111.778435 33.47542, -111.778447 33.476675, -111.778455 33.477463, -111.778888 33.477339, -111.77888 33.477736, -111.778844 33.479495, -111.7788 33.481074, -111.778798 33.481135, -111.781126 33.48111, -111.787299 33.480908, -111.78732 33.480302, -111.787366 33.477322, -111.787362 33.476097, -111.787022 33.476097, -111.787021 33.476036, -111.787024 33.475988, -111.787025 33.475938, -111.787025 33.475889, -111.787025 33.475841, -111.787024 33.475785, -111.787023 33.47573, -111.787022 33.475676, -111.787024 33.475623, -111.787023 33.475569, -111.787023 33.475515, -111.787023 33.475459, -111.787022 33.475403, -111.787022 33.47535, -111.787021 33.475295, -111.78702 33.475242, -111.787022 33.475187, -111.787021 33.475133, -111.787022 33.475082, -111.787021 33.475028, -111.787021 33.474977, -111.787021 33.474925, -111.78702 33.47486, -111.787022 33.474807, -111.787019 33.474753, -111.787022 33.474697, -111.787019 33.474652, -111.787018 33.474597, -111.787014 33.474545, -111.787362 33.474545, -111.787362 33.473894, -111.787362 33.473653, -111.787362 33.471875, -111.78734 33.471145, -111.78734 33.470404, -111.78734 33.469994, -111.78734 33.469109, -111.787284 33.466403, -111.787423 33.466395, -111.788238 33.466405, -111.789068 33.466411, -111.796102 33.466467, -111.797576 33.466479, -111.798445 33.466486, -111.800495 33.46651, -111.802575 33.466538, -111.802796 33.466541, -111.804355 33.466561, -111.80488 33.466563, -111.804875 33.466659, -111.804779 33.467205, -111.804769 33.467388, -111.804774 33.467508, -111.804802 33.469966, -111.804789 33.47357, -111.804844 33.473565, -111.806897 33.473565, -111.810083 33.473565, -111.810198 33.473564, -111.810313 33.473563, -111.810892 33.473557, -111.813435 33.473536, -111.813423 33.475794, -111.813433 33.477093, -111.813431 33.477316, -111.81343 33.477536, -111.813426 33.478122, -111.813425 33.479605, -111.813434 33.48077, -111.813814 33.480753, -111.814184 33.48084, -111.814596 33.480744, -111.816385 33.482309, -111.816693 33.482579, -111.817796 33.483544, -111.819507 33.48574, -111.819161 33.485981, -111.819295 33.48615, -111.819328 33.486204, -111.819359 33.486283, -111.819375 33.486373, -111.819378 33.487316, -111.819374 33.487357, -111.819352 33.487397, -111.819309 33.487428, -111.819269 33.48744, -111.817907 33.487532, -111.817846 33.487543, -111.817779 33.487564, -111.817711 33.487603, -111.817706 33.487387, -111.817698 33.487341, -111.817674 33.487287, -111.817521 33.487102, -111.815676 33.488365, -111.814003 33.489528, -111.812678 33.49045, -111.813219 33.490884, -111.813295 33.490962, -111.813357 33.491045, -111.813407 33.491137, -111.813441 33.491231, -111.813461 33.491327, -111.813465 33.49145, -111.813479 33.493786, -111.813474 33.495283, -111.813569 33.495241, -111.81429 33.495258, -111.814288 33.495277, -111.814892 33.495272, -111.816584 33.49526, -111.81769 33.495255, -111.819882 33.495249, -111.822073 33.495241, -111.822074 33.496927, -111.822074 33.497945, -111.822075 33.498801, -111.822075 33.498911, -111.823713 33.498886, -111.826247 33.498859, -111.828819 33.498837, -111.829684 33.49884, -111.829782 33.498835, -111.829963 33.498818, -111.830141 33.498813, -111.830302 33.498821, -111.83074 33.498817, -111.832188 33.498807, -111.835268 33.498801, -111.839384 33.498768, -111.839385 33.495168, -111.839384 33.49281, -111.839384 33.492182, -111.839383 33.491555, -111.839514 33.491605, -111.839539 33.491612, -111.83963 33.491622, -111.839706 33.491616, -111.839888 33.491577, -111.839964 33.491566, -111.840045 33.49157, -111.840211 33.491608, -111.840278 33.491619, -111.840352 33.491618, -111.840581 33.491582, -111.8408 33.491567, -111.840913 33.491555, -111.841013 33.491537, -111.841183 33.491518, -111.841358 33.491509, -111.842961 33.491501, -111.843124 33.491498, -111.843216 33.491495, -111.844999 33.491495, -111.846246 33.491487, -111.847058 33.49149, -111.847564 33.49147, -111.847661 33.491455, -111.847753 33.491428, -111.847797 33.491419, -111.847839 33.491422, -111.847883 33.491439, -111.847958 33.4915, -111.848012 33.491532, -111.84809 33.49156, -111.848098 33.490634, -111.848082 33.489801, -111.848078 33.489691, -111.848068 33.489406, -111.848081 33.487803, -111.850161 33.487823, -111.852292 33.48782, -111.852344 33.487808, -111.852367 33.487796, -111.852401 33.48776, -111.852414 33.487727, -111.852388 33.486629, -111.852397 33.485487, -111.852404 33.485066, -111.852394 33.484612, -111.852373 33.484223, -111.85237 33.484139, -111.852382 33.483998, -111.852439 33.483718, -111.852461 33.483583, -111.852479 33.483403, -111.852485 33.483171, -111.852454 33.48187, -111.852406 33.480569, -111.856709 33.480544, -111.856706 33.476982, -111.856706 33.476861, -111.856523 33.476852, -111.854584 33.476869, -111.854372 33.476884, -111.854225 33.476903, -111.854112 33.476903, -111.853641 33.476871, -111.853454 33.476866, -111.852752 33.47686, -111.852684 33.476867, -111.852587 33.476891, -111.852443 33.476953, -111.852391 33.476968, -111.852336 33.476979, -111.852356 33.476602, -111.852349 33.476349, -111.852362 33.476233, -111.85238 33.476168, -111.852408 33.476048, -111.85248 33.475018, -111.85248 33.474762, -111.852382 33.473314, -111.848071 33.473334, -111.848045 33.47157, -111.848038 33.469687, -111.848044 33.467142, -111.848036 33.465986, -111.846537 33.466017, -111.845866 33.466023, -111.844484 33.466001, -111.843262 33.465932, -111.843299 33.465503, -111.843324 33.465142, -111.84335 33.464782, -111.84337 33.464565, -111.843389 33.464182, -111.843389 33.463856, -111.843359 33.463606, -111.843332 33.463451, -111.843264 33.46318, -111.843193 33.46297, -111.843084 33.462704, -111.842994 33.462524, -111.842865 33.462304, -111.842715 33.462072, -111.842577 33.461878, -111.842265 33.461425, -111.841744 33.460667, -111.841369 33.460116, -111.841303 33.460019, -111.841268 33.458908, -111.841179 33.457887, -111.841198 33.457828, -111.841811 33.455995, -111.842078 33.455198, -111.842096 33.455145, -111.842258 33.455036, -111.842949 33.454684, -111.847755 33.451979, -111.847858 33.452014, -111.847944 33.452045, -111.847873 33.452533, -111.847805 33.453001, -111.848249 33.453549, -111.849478 33.453771, -111.850208 33.453902, -111.850265 33.453913, -111.851495 33.453977, -111.852779 33.454763, -111.852853 33.454805, -111.852948 33.454859, -111.853207 33.454997, -111.853548 33.455162, -111.853753 33.45525, -111.854111 33.45539, -111.854373 33.455481, -111.854769 33.455599, -111.855074 33.45568, -111.855391 33.45575, -111.855874 33.455826, -111.856105 33.455857, -111.856405 33.455886, -111.856709 33.455905, -111.856959 33.455911, -111.85731 33.455909, -111.857453 33.455903, -111.857876 33.455877, -111.85814 33.455849, -111.85816 33.455848, -111.858512 33.4558, -111.858712 33.455765, -111.85898 33.455711, -111.85928 33.455638, -111.859634 33.455539, -111.862731 33.454551, -111.865981 33.453496, -111.866255 33.453409, -111.866398 33.453359, -111.868477 33.452692, -111.871315 33.451783, -111.871718 33.451673, -111.872143 33.451574, -111.872542 33.451497, -111.872836 33.451452, -111.873242 33.451405, -111.873762 33.451355, -111.874067 33.451341, -111.874596 33.451335, -111.874668 33.451335, -111.874656 33.452382, -111.874645 33.453412, -111.874634 33.454918, -111.874619 33.456809, -111.874605 33.458589, -111.874576 33.462203, -111.874573 33.462683, -111.874572 33.462773, -111.874572 33.462862, -111.874568 33.463413, -111.874563 33.463963, -111.874549 33.465806, -111.878236 33.465814, -111.878363 33.465814, -111.878363 33.466941, -111.878329 33.469544, -111.878274 33.469538, -111.878256 33.469541, -111.87815 33.469557, -111.87812 33.46956, -111.876606 33.469552, -111.876241 33.469561, -111.875924 33.469575, -111.875693 33.469571, -111.874155 33.469566, -111.874 33.469561, -111.873785 33.469555, -111.872486 33.469535, -111.872261 33.469546, -111.871933 33.469583, -111.871637 33.4696, -111.871376 33.469603, -111.871087 33.469595, -111.869776 33.469592, -111.869712 33.469642, -111.86968 33.46969, -111.869667 33.469744, -111.869668 33.469869, -111.869674 33.470729, -111.869674 33.472233, -111.86971 33.473132, -111.869701 33.473155, -111.869685 33.473174, -111.869616 33.473232, -111.872826 33.473211, -111.874005 33.473208, -111.874011 33.474275, -111.87401 33.476868, -111.874664 33.476849, -111.875719 33.476849, -111.875843 33.47563, -111.875905 33.47413, -111.875929 33.473193, -111.87711 33.473191, -111.878322 33.473183, -111.878371 33.473183, -111.880738 33.473166, -111.882668 33.473157, -111.882657 33.471417, -111.882655 33.469538, -111.882655 33.469456, -111.882655 33.467637, -111.882655 33.465823, -111.883201 33.465825, -111.885752 33.465829, -111.887724 33.465859, -111.887841 33.465857, -111.888325 33.465856, -111.888575 33.465854, -111.888251 33.468308, -111.888204 33.468767, -111.888167 33.469279, -111.888162 33.469417, -111.888317 33.469416, -111.888341 33.469217, -111.888413 33.468767, -111.888515 33.468248, -111.888565 33.46802, -111.888625 33.467776, -111.888864 33.466752, -111.889056 33.465852, -111.891669 33.465843, -111.891828 33.465842, -111.891909 33.467017, -111.891906 33.467303, -111.891898 33.467385, -111.89187 33.467565, -111.891859 33.469456, -111.89185 33.471511, -111.891836 33.47312, -111.891818 33.4767, -111.891816 33.477576, -111.8918 33.479529, -111.891795 33.480289, -111.891804 33.480389, -111.891819 33.480556, -111.891803 33.481493, -111.891785 33.48182, -111.891729 33.484214, -111.891731 33.484422, -111.891739 33.485129, -111.891737 33.485569, -111.891732 33.486961, -111.891729 33.487642, -111.891724 33.48904, -111.891722 33.489663, -111.891719 33.490395, -111.891716 33.491127, -111.891713 33.491858, -111.891697 33.493668, -111.891688 33.494389, -111.891684 33.49476, -111.891692 33.494875, -111.8917 33.49501, -111.891689 33.496951, -111.891679 33.498728, -111.891671 33.499622, -111.891664 33.500355, -111.891654 33.502045, -111.891654 33.502105, -111.887941 33.501981, -111.887939 33.502077, -111.887946 33.502491, -111.887939 33.503636, -111.88796 33.505025, -111.888 33.50634, -111.888028 33.506994, -111.888044 33.507571, -111.888052 33.508149, -111.888057 33.509253, -111.888058 33.509391, -111.888063 33.509765, -111.888049 33.510931, -111.887978 33.515019, -111.887977 33.515524, -111.887959 33.51654, -111.887956 33.516722, -111.887916 33.518532, -111.887901 33.519439, -111.887952 33.519875, -111.888024 33.520909, -111.888093 33.521818, -111.888112 33.522125, -111.888118 33.522624, -111.888112 33.523121, -111.888088 33.523719, -111.888064 33.523864, -111.88889 33.523879, -111.890482 33.523871, -111.891523 33.523851, -111.891479 33.526271, -111.8915 33.527723, -111.891507 33.528224, -111.891509 33.528529, -111.891515 33.529279, -111.891521 33.530061, -111.891522 33.530144, -111.891519 33.530819, -111.891518 33.530896, -111.891515 33.53152, -111.891515 33.531588, -111.891514 33.531692, -111.891514 33.531791, -111.891513 33.531917, -111.891383 33.531838, -111.891248 33.531739, -111.889986 33.530737, -111.889341 33.530229, -111.889102 33.530082, -111.888829 33.529936, -111.888618 33.529837, -111.888327 33.529719, -111.888027 33.529617, -111.887748 33.529538, -111.887518 33.529483, -111.885595 33.529215, -111.884699 33.531467, -111.884361 33.532327, -111.883818 33.533707, -111.883014 33.535778, -111.882425 33.537253, -111.882034 33.538158, -111.881933 33.538437, -111.882545 33.538432, -111.882982 33.538449, -111.884121 33.538446, -111.88418 33.538443, -111.886743 33.538424, -111.88674 33.538656, -111.886722 33.538752, -111.886693 33.538839, -111.886436 33.539369, -111.885898 33.540633, -111.885883 33.540673, -111.885888 33.540722, -111.885912 33.540762, -111.885946 33.540788, -111.885986 33.540802, -111.886502 33.540964, -111.887647 33.541306, -111.887778 33.541325, -111.887871 33.541328, -111.887985 33.541319, -111.888056 33.541305, -111.888155 33.541275, -111.88855 33.541085, -111.888623 33.541052, -111.888775 33.540998, -111.888935 33.540954, -111.889081 33.540926, -111.889245 33.540908, -111.889423 33.540902, -111.890616 33.540824, -111.891203 33.540798, -111.891448 33.540787, -111.891445 33.541506, -111.891443 33.541849, -111.891462 33.543737, -111.891456 33.544606, -111.891458 33.545302, -111.891461 33.545851, -111.891466 33.546008, -111.891499 33.546962, -111.891473 33.547958, -111.891473 33.54923, -111.891451 33.5503, -111.891434 33.551676, -111.891433 33.552953, -111.891414 33.55437, -111.891432 33.556089, -111.89143 33.556597, -111.891426 33.558073, -111.891401 33.560233, -111.891387 33.561444, -111.891387 33.561528, -111.891361 33.561775, -111.891418 33.563311, -111.891417 33.563635, -111.891413 33.565697, -111.891399 33.566652, -111.891416 33.566988, -111.891416 33.56716, -111.891217 33.567423, -111.891028 33.56743, -111.890751 33.567441, -111.890544 33.567449, -111.886986 33.567588, -111.883153 33.567639, -111.874245 33.567756, -111.874224 33.567982, -111.874004 33.567982, -111.873758 33.567981, -111.870638 33.567979, -111.870346 33.567998, -111.87035 33.567979, -111.867241 33.56798, -111.862294 33.567983, -111.85985 33.567977, -111.856578 33.567971, -111.849246 33.567955, -111.848 33.567953, -111.843543 33.567937, -111.839641 33.567923, -111.839364 33.567922, -111.838899 33.567922, -111.832769 33.567923, -111.826345 33.567924, -111.82407 33.567906, -111.822022 33.56789, -111.821685 33.56789, -111.817689 33.567891, -111.81765 33.567891, -111.817468 33.567891, -111.813923 33.567895, -111.812668 33.567897, -111.807758 33.567883, -111.807388 33.567882, -111.804731 33.567875, -111.801939 33.567886, -111.801677 33.567887, -111.80144 33.567888, -111.799974 33.567894, -111.800487 33.56833, -111.800641 33.57169, -111.798219 33.57175, -111.79822 33.572236, -111.796769 33.572239, -111.796724 33.571612, -111.796634 33.571612, -111.796036 33.571528, -111.795417 33.571527, -111.787658 33.571523, -111.787198 33.571523, -111.787199 33.571586, -111.787001 33.571585, -111.786998 33.571714, -111.787177 33.571934, -111.787401 33.572209, -111.787434 33.572386, -111.787388 33.573079, -111.787268 33.574183, -111.787249 33.574568, -111.787252 33.574926, -111.787257 33.575004, -111.787276 33.575329, -111.787368 33.576113, -111.787401 33.576477, -111.787435 33.577028, -111.78745 33.577571, -111.787451 33.578167, -111.78745 33.578342, -111.787008 33.578522, -111.78685 33.578639, -111.786535 33.579036, -111.78624 33.579585, -111.786003 33.579892, -111.78573 33.580261, -111.785625 33.580404, -111.785546 33.580549, -111.785484 33.581107, -111.785392 33.58125, -111.785254 33.581366, -111.784998 33.581437, -111.78484 33.581453, -111.784704 33.581467, -111.784295 33.581509, -111.78426 33.581527, -111.783957 33.581486, -111.783701 33.581443, -111.783447 33.581394, -111.782945 33.581272, -111.782699 33.5812, -111.782455 33.581121, -111.782215 33.581035, -111.781979 33.580941, -111.781747 33.580841, -111.78157 33.580759, -111.781296 33.580621, -111.781078 33.580501, -111.780659 33.580242, -111.779876 33.579727, -111.779123 33.579232, -111.778779 33.579025, -111.778292 33.578761, -111.778055 33.57865, -111.777451 33.578387, -111.777257 33.578147, -111.777183 33.578089, -111.777092 33.578, -111.77694 33.577821, -111.776797 33.577711, -111.776748 33.577665, -111.776545 33.577571, -111.776395 33.577434, -111.776285 33.577327, -111.776207 33.577217, -111.776151 33.577171, -111.776057 33.577129, -111.775855 33.577081, -111.775828 33.577063, -111.775517 33.576677, -111.775268 33.576152, -111.776102 33.576052, -111.776348 33.576006, -111.776403 33.575968, -111.776445 33.575928, -111.77649 33.575871, -111.776518 33.575793, -111.776518 33.575731, -111.776512 33.575694, -111.776879 33.5755, -111.776589 33.575117, -111.776215 33.575315, -111.776205 33.57548, -111.776194 33.5757, -111.776282 33.575816, -111.776041 33.575865, -111.775015 33.576015, -111.774788 33.576054, -111.774683 33.576082, -111.774592 33.576123, -111.774482 33.576196, -111.77443 33.57625, -111.774418 33.576264, -111.774418 33.575144, -111.774312 33.574301, -111.771304 33.574276, -111.770105 33.574265, -111.770106 33.570664, -111.770107 33.567908, -111.766254 33.567906, -111.764015 33.567905, -111.76145 33.567904, -111.760449 33.56791, -111.758676 33.567921, -111.757849 33.567906, -111.751449 33.567905, -111.749705 33.56791, -111.735443 33.567902, -111.735243 33.567904, -111.72852 33.567933, -111.72856 33.567546, -111.727949 33.567546, -111.726941 33.567547, -111.726553 33.567549, -111.726567 33.56794, -111.721019 33.567921, -111.718109 33.567912, -111.717838 33.568038, -111.717232 33.568017, -111.716909 33.567908, -111.713011 33.567895, -111.709376 33.567881, -111.708813 33.567881, -111.708432 33.567902, -111.708326 33.568684, -111.708163 33.570308, -111.708122 33.570712, -111.707614 33.57576, -111.707376 33.578131, -111.707074 33.581131, -111.706962 33.582245, -111.706326 33.583599, -111.70631 33.583772, -111.706277 33.58404, -111.705911 33.586992, -111.705694 33.588744, -111.705688 33.589025, -111.705679 33.589385, -111.705634 33.591363, -111.705616 33.591889, -111.705592 33.59261, -111.70547 33.593785, -111.704697 33.593939, -111.704554 33.595172, -111.704534 33.595342, -111.704515 33.595513, -111.70414 33.598742, -111.704032 33.5998, -111.702993 33.607976, -111.702601 33.611049, -111.702469 33.612087, -111.702458 33.612245, -111.702392 33.613143, -111.702277 33.613926, -111.702116 33.615245, -111.701618 33.619314, -111.701503 33.619746, -111.701326 33.620406, -111.701206 33.620767, -111.701141 33.620973, -111.700994 33.621443, -111.700589 33.624357, -111.700524 33.624825, -111.700343 33.626122, -111.700266 33.626678, -111.699866 33.629564, -111.699467 33.632451, -111.69926 33.633946, -111.698908 33.636609, -111.69862 33.639347, -111.69855 33.640491, -111.698789 33.640486, -111.699732 33.640459, -111.70007 33.639753, -111.700283 33.639308, -111.700347 33.639166, -111.700487 33.63878, -111.700553 33.638548, -111.7006 33.638317, -111.700657 33.637945, -111.700979 33.635117, -111.701104 33.63402, -111.701592 33.629735, -111.701629 33.62951, -111.701675 33.629333, -111.701734 33.629158, -111.701806 33.628988, -111.70189 33.628826, -111.701995 33.628653, -111.702107 33.628493, -111.702214 33.628359, -111.702312 33.62825, -111.702457 33.628106, -111.702616 33.627967, -111.702736 33.627875, -111.702913 33.627753, -111.703097 33.627643, -111.703317 33.627529, -111.703491 33.627453, -111.703562 33.627425, -111.703771 33.627352, -111.703982 33.627291, -111.704796 33.627108, -111.705947 33.62685, -111.707156 33.626587, -111.707261 33.626567, -111.707414 33.626538, -111.707639 33.626507, -111.707701 33.626499, -111.707964 33.626476, -111.708078 33.626471, -111.708467 33.626456, -111.708545 33.626457, -111.708912 33.626467, -111.709329 33.6265, -111.709593 33.626531, -111.709842 33.626569, -111.711332 33.626784, -111.712499 33.626961, -111.712624 33.62698, -111.715881 33.627447, -111.716741 33.627566, -111.716902 33.627578, -111.717086 33.627578, -111.717227 33.627567, -111.717406 33.627541, -111.717593 33.627496, -111.717772 33.627436, -111.717905 33.627379, -111.718071 33.627291, -111.718094 33.627276, -111.718189 33.627214, -111.718305 33.627126, -111.718434 33.627007, -111.718574 33.626842, -111.718653 33.626729, -111.718716 33.626617, -111.718774 33.626484, -111.71882 33.626333, -111.718849 33.626163, -111.718883 33.62596, -111.719088 33.624674, -111.719803 33.624753, -111.719909 33.624762, -111.720185 33.624776, -111.720437 33.624775, -111.720691 33.624763, -111.720787 33.624755, -111.721066 33.624721, -111.721343 33.624672, -111.722057 33.624507, -111.722359 33.624433, -111.722597 33.624375, -111.72411 33.623993, -111.724462 33.624967, -111.724519 33.62514, -111.724604 33.625339, -111.724639 33.625406, -111.724943 33.62594, -111.724989 33.626004, -111.728468 33.625819, -111.730099 33.625813, -111.732814 33.625716, -111.734846 33.625643, -111.734904 33.625649, -111.735113 33.628196, -111.735395 33.631632, -111.735592 33.634038, -111.735515 33.63716, -111.7355 33.637798, -111.735484 33.638435, -111.735552 33.639286, -111.735625 33.640193, -111.736048 33.640192, -111.73647 33.64019, -111.741593 33.640171, -111.748995 33.640143, -111.749125 33.640142, -111.749256 33.640142, -111.753453 33.640131, -111.756503 33.640123, -111.763336 33.640105, -111.78188 33.640055, -111.783198 33.640051, -111.782357 33.639205, -111.783379 33.638303, -111.783287 33.638209, -111.782296 33.637452, -111.781214 33.637252, -111.780793 33.636751, -111.780974 33.6356, -111.780192 33.635349, -111.779448 33.634853, -111.77929 33.634748, -111.778689 33.633847, -111.778263 33.633776, -111.778169 33.633822, -111.778122 33.633843, -111.777922 33.633931, -111.77757 33.634112, -111.777839 33.634341, -111.778029 33.634551, -111.778082 33.634705, -111.77804 33.634873, -111.777923 33.635014, -111.777746 33.635195, -111.777621 33.635355, -111.777516 33.635571, -111.777427 33.635718, -111.777354 33.635892, -111.777316 33.636027, -111.77732 33.636165, -111.777362 33.636322, -111.777819 33.636327, -111.778347 33.636365, -111.778606 33.636376, -111.77889 33.636294, -111.77908 33.636255, -111.779447 33.636263, -111.779693 33.636292, -111.779833 33.636367, -111.779954 33.636503, -111.780025 33.636704, -111.78015 33.637109, -111.78024 33.637424, -111.780216 33.637589, -111.780081 33.637769, -111.779841 33.637931, -111.779566 33.6381, -111.779355 33.638291, -111.779186 33.638566, -111.779097 33.63885, -111.778974 33.639031, -111.778911 33.639158, -111.778887 33.63936, -111.778383 33.639318, -111.778001 33.639322, -111.777642 33.639397, -111.777378 33.639426, -111.777099 33.639358, -111.776817 33.639297, -111.776484 33.639363, -111.776099 33.639429, -111.775754 33.639433, -111.775349 33.639542, -111.774856 33.639749, -111.774603 33.639815, -111.774315 33.639813, -111.774037 33.639715, -111.773753 33.639471, -111.773598 33.639244, -111.773354 33.63871, -111.773312 33.638536, -111.7733 33.638399, -111.773088 33.63844, -111.772574 33.638394, -111.772105 33.638352, -111.771635 33.638311, -111.771065 33.63826, -111.770773 33.63817, -111.770666 33.638048, -111.770403 33.637749, -111.77034 33.637464, -111.770404 33.63694, -111.770426 33.636752, -111.770278 33.63636, -111.76981 33.636021, -111.769296 33.635946, -111.768851 33.635882, -111.768289 33.635774, -111.76798 33.635668, -111.767713 33.63556, -111.767301 33.635366, -111.767089 33.635287, -111.766935 33.635242, -111.766803 33.635211, -111.766679 33.635188, -111.766259 33.635158, -111.76588 33.635142, -111.765484 33.635139, -111.765074 33.635121, -111.764844 33.635099, -111.764686 33.635075, -111.764504 33.63503, -111.764356 33.63498, -111.764137 33.634885, -111.763837 33.634743, -111.763364 33.634502, -111.763004 33.634336, -111.762899 33.634289, -111.76249 33.634152, -111.76185 33.633957, -111.761605 33.633892, -111.761479 33.633862, -111.761369 33.633846, -111.761125 33.633832, -111.760915 33.633836, -111.760716 33.633856, -111.760666 33.633863, -111.760599 33.633613, -111.760573 33.63356, -111.760131 33.631963, -111.759689 33.630361, -111.759516 33.629732, -111.759484 33.629618, -111.759463 33.629558, -111.759425 33.629476, -111.759372 33.629392, -111.759312 33.62932, -111.759037 33.629056, -111.758947 33.628975, -111.758709 33.62876, -111.758575 33.628672, -111.758432 33.628599, -111.758276 33.62854, -111.758172 33.628511, -111.758022 33.628481, -111.757398 33.628403, -111.757315 33.628385, -111.757205 33.628348, -111.7571 33.628298, -111.757003 33.628233, -111.756919 33.628157, -111.756863 33.628092, -111.756804 33.627997, -111.756776 33.627932, -111.756747 33.627828, -111.756678 33.627223, -111.756626 33.626769, -111.756541 33.626019, -111.757402 33.625949, -111.757504 33.625944, -111.757612 33.625954, -111.757716 33.625979, -111.757815 33.626018, -111.757904 33.62607, -111.757981 33.626134, -111.758539 33.626756, -111.758782 33.626619, -111.758885 33.626565, -111.759348 33.626396, -111.759457 33.626348, -111.759576 33.62628, -111.759731 33.626159, -111.759651 33.626092, -111.759608 33.626049, -111.759548 33.625967, -111.75952 33.62591, -111.759305 33.625258, -111.759275 33.625197, -111.759219 33.62512, -111.759151 33.625053, -111.759074 33.624998, -111.758977 33.624949, -111.758722 33.624862, -111.758461 33.624773, -111.758356 33.624722, -111.75827 33.624664, -111.758179 33.624578, -111.758143 33.624532, -111.757878 33.624116, -111.757823 33.624049, -111.757753 33.623984, -111.757373 33.623709, -111.757485 33.623601, -111.757856 33.623243, -111.758683 33.622446, -111.758888 33.622249, -111.758974 33.622154, -111.759082 33.622011, -111.75931 33.62164, -111.759452 33.621409, -111.759773 33.620895, -111.75982 33.620801, -111.759856 33.620703, -111.759879 33.620614, -111.759896 33.6205, -111.759901 33.620374, -111.759885 33.620214, -111.759857 33.620101, -111.759797 33.619953, -111.759753 33.619873, -111.759746 33.61986, -111.75967 33.61975, -111.759578 33.619645, -111.759465 33.619544, -111.759392 33.619491, -111.759272 33.619417, -111.759177 33.619353, -111.759049 33.619267, -111.758551 33.61893, -111.758517 33.618907, -111.758294 33.618757, -111.757498 33.618259, -111.757384 33.618198, -111.75727 33.618154, -111.75712 33.618106, -111.756877 33.618047, -111.756502 33.617966, -111.755904 33.617834, -111.755764 33.617794, -111.755602 33.617735, -111.755447 33.617663, -111.755176 33.6175, -111.755089 33.617429, -111.75472 33.617111, -111.754636 33.617033, -111.754518 33.616905, -111.754398 33.616749, -111.754317 33.616624, -111.75483 33.616427, -111.754937 33.616383, -111.754912 33.616339, -111.754847 33.616201, -111.754605 33.615426, -111.754551 33.615318, -111.753918 33.615608, -111.753853 33.615522, -111.75381 33.615474, -111.753766 33.615425, -111.75362 33.615289, -111.753345 33.615026, -111.753186 33.614855, -111.753048 33.614683, -111.752984 33.61457, -111.75291 33.614422, -111.752834 33.614239, -111.752802 33.614147, -111.752712 33.613851, -111.752672 33.613674, -111.752659 33.613514, -111.752666 33.613387, -111.752686 33.613274, -111.752957 33.612179, -111.753051 33.611789, -111.753101 33.611602, -111.753144 33.611497, -111.753216 33.611367, -111.753809 33.610488, -111.753909 33.610345, -111.75396 33.610256, -111.754032 33.610155, -111.754146 33.610028, -111.754317 33.609852, -111.754784 33.609405, -111.754835 33.609351, -111.754936 33.609228, -111.75502 33.609102, -111.755116 33.608914, -111.755326 33.608425, -111.755379 33.608329, -111.755454 33.608223, -111.75558 33.60808, -111.755697 33.607974, -111.755795 33.607901, -111.755905 33.607832, -111.756086 33.60774, -111.756276 33.607667, -111.756766 33.607474, -111.756897 33.607413, -111.757065 33.607323, -111.757173 33.60725, -111.757299 33.607145, -111.757311 33.607135, -111.757437 33.607011, -111.757468 33.606976, -111.75757 33.606832, -111.757661 33.606673, -111.757734 33.606507, -111.757787 33.606342, -111.757823 33.606173, -111.757838 33.606032, -111.757842 33.605888, -111.757844 33.605868, -111.757872 33.605575, -111.757956 33.604646, -111.758013 33.604197, -111.758035 33.603949, -111.758054 33.60357, -111.758034 33.603394, -111.757995 33.603221, -111.757976 33.603163, -111.757957 33.603105, -111.757886 33.602937, -111.757819 33.602821, -111.757738 33.602712, -111.757915 33.602583, -111.758305 33.602288, -111.758338 33.602307, -111.75843 33.602369, -111.758527 33.602452, -111.75892 33.602886, -111.759028 33.602995, -111.759108 33.603051, -111.759203 33.603087, -111.759313 33.603121, -111.759538 33.603167, -111.759648 33.603199, -111.759756 33.603243, -111.759884 33.603316, -111.759973 33.603383, -111.760061 33.603471, -111.760132 33.60356, -111.760174 33.603602, -111.760277 33.603685, -111.760356 33.603738, -111.76071 33.603929, -111.760805 33.603992, -111.760897 33.604069, -111.760976 33.604153, -111.761021 33.604227, -111.761053 33.604212, -111.761118 33.604176, -111.76119 33.604122, -111.76125 33.60406, -111.761284 33.60401, -111.761296 33.603992, -111.761332 33.603898, -111.761394 33.603776, -111.761472 33.603665, -111.761511 33.603591, -111.761561 33.603538, -111.761617 33.6035, -111.761637 33.603492, -111.761687 33.603472, -111.761762 33.603452, -111.761866 33.603425, -111.762009 33.603403, -111.762166 33.603395, -111.762376 33.603403, -111.762507 33.603396, -111.762655 33.603368, -111.762697 33.603355, -111.762741 33.603434, -111.762839 33.603471, -111.763308 33.604467, -111.763495 33.604659, -111.763512 33.604738, -111.763553 33.605026, -111.763853 33.605027, -111.764976 33.605033, -111.765735 33.605036, -111.765782 33.605278, -111.7658 33.605409, -111.76717 33.605591, -111.769169 33.606778, -111.769702 33.606104, -111.76969 33.606173, -111.770116 33.605629, -111.770112 33.609433, -111.770109 33.609572, -111.770189 33.610516, -111.770158 33.611425, -111.77077 33.611425, -111.78731 33.611369, -111.787319 33.611567, -111.787379 33.612754, -111.788493 33.612752, -111.788302 33.605929, -111.788253 33.604168, -111.788201 33.602329, -111.78818 33.601602, -111.788196 33.600794, -111.788226 33.600105, -111.788682 33.600326, -111.78886 33.600403, -111.789123 33.600499, -111.789296 33.600549, -111.789457 33.600581, -111.789651 33.60062, -111.789733 33.600633, -111.790028 33.600663, -111.790183 33.600669, -111.790511 33.600662, -111.790642 33.60065, -111.790879 33.60062, -111.791079 33.600585, -111.791281 33.600534, -111.791476 33.600472, -111.791743 33.600369, -111.791988 33.600261, -111.792124 33.600189, -111.792212 33.600135, -111.792461 33.599984, -111.792971 33.599682, -111.793422 33.599426, -111.793529 33.599362, -111.793589 33.599331, -111.793726 33.599285, -111.793829 33.59926, -111.793901 33.599252, -111.794006 33.59925, -111.794116 33.599262, -111.794645 33.59939, -111.794779 33.599417, -111.794911 33.599435, -111.795096 33.599448, -111.795277 33.599447, -111.795456 33.599432, -111.795653 33.599403, -111.795912 33.599368, -111.796162 33.599325, -111.796453 33.599295, -111.79661 33.599291, -111.796764 33.599303, -111.796911 33.599322, -111.797045 33.599348, -111.797215 33.599392, -111.797368 33.599446, -111.797533 33.599517, -111.797957 33.599703, -111.7981 33.599769, -111.798189 33.599805, -111.798408 33.599885, -111.798552 33.599928, -111.798869 33.599996, -111.798987 33.60001, -111.79919 33.600009, -111.799331 33.600002, -111.79947 33.599981, -111.799717 33.599922, -111.799795 33.599898, -111.800018 33.59981, -111.8003 33.599685, -111.800493 33.599604, -111.800576 33.599569, -111.800788 33.599511, -111.800891 33.599483, -111.801061 33.59945, -111.801131 33.599442, -111.801258 33.59944, -111.801472 33.599432, -111.801752 33.599426, -111.802003 33.599406, -111.802511 33.599332, -111.802683 33.59931, -111.802869 33.599299, -111.802991 33.599299, -111.803167 33.59931, -111.803377 33.599337, -111.803567 33.599353, -111.8038 33.599369, -111.804056 33.599379, -111.804576 33.599383, -111.804599 33.599379, -111.804628 33.599363, -111.804639 33.599353, -111.804653 33.599323, -111.804665 33.5983, -111.804669 33.597591, -111.804671 33.597149, -111.804687 33.596924, -111.805225 33.596926, -111.805677 33.596919, -111.806665 33.596924, -111.807064 33.596923, -111.807333 33.596922, -111.807333 33.596376, -111.809377 33.596368, -111.809507 33.596353, -111.809596 33.596332, -111.809682 33.596302, -111.809785 33.596254, -111.809884 33.59619, -111.809983 33.596108, -111.81006 33.596028, -111.810301 33.595798, -111.810469 33.595633, -111.810523 33.595562, -111.810572 33.595472, -111.810604 33.595382, -111.810624 33.595289, -111.810627 33.594873, -111.810625 33.594326, -111.810633 33.594217, -111.81065 33.594133, -111.810668 33.594072, -111.810758 33.593921, -111.810872 33.593775, -111.810931 33.593682, -111.810973 33.593575, -111.810994 33.593488, -111.810998 33.593362, -111.810999 33.591915, -111.810997 33.591174, -111.811 33.59049, -111.810991 33.590455, -111.810968 33.590427, -111.810935 33.590406, -111.810896 33.590397, -111.810154 33.59037, -111.809034 33.590369, -111.809033 33.589681, -111.813056 33.589681, -111.813362 33.589681, -111.813361 33.591205, -111.812923 33.591198, -111.812667 33.591173, -111.812637 33.591069, -111.812637 33.590443, -111.812622 33.590413, -111.81256 33.590383, -111.811747 33.590385, -111.811683 33.590443, -111.811701 33.59149, -111.811701 33.591912, -111.812593 33.591913, -111.813359 33.591913, -111.813359 33.592554, -111.813362 33.593838, -111.813362 33.594325, -111.813362 33.595171, -111.813361 33.595613, -111.813361 33.596712, -111.813382 33.596802, -111.813409 33.596866, -111.813427 33.596943, -111.81343 33.596992, -111.813429 33.597201, -111.813424 33.597646, -111.813424 33.597674, -111.813444 33.597897, -111.813449 33.597975, -111.813467 33.59804, -111.81348 33.598061, -111.813497 33.598079, -111.813521 33.598097, -111.813573 33.598118, -111.81367 33.598135, -111.81377 33.598135, -111.814041 33.598095, -111.814567 33.597996, -111.815931 33.59771, -111.816706 33.597526, -111.816746 33.597655, -111.816797 33.597782, -111.816851 33.597892, -111.816858 33.597905, -111.816945 33.598051, -111.817043 33.598185, -111.817173 33.598328, -111.81729 33.598438, -111.817319 33.598463, -111.817437 33.598553, -111.817587 33.59865, -111.817674 33.598699, -111.817822 33.598772, -111.817913 33.598809, -111.81801 33.598844, -111.819498 33.599297, -111.819745 33.599365, -111.819888 33.599395, -111.820041 33.599414, -111.820417 33.599433, -111.820975 33.599451, -111.820981 33.599787, -111.820998 33.59989, -111.821028 33.599988, -111.821076 33.600116, -111.821107 33.600242, -111.821112 33.600294, -111.821113 33.600518, -111.82197 33.600508, -111.822172 33.600516, -111.822352 33.600538, -111.822505 33.60057, -111.822818 33.600659, -111.822526 33.601375, -111.822526 33.60141, -111.822536 33.601436, -111.822579 33.601476, -111.822717 33.601518, -111.822813 33.601539, -111.822928 33.601547, -111.823033 33.601539, -111.823167 33.60151, -111.823277 33.601489, -111.823404 33.601473, -111.823429 33.601473, -111.823539 33.601473, -111.823679 33.601493, -111.82384 33.601528, -111.823943 33.601543, -111.824056 33.601546, -111.824284 33.601521, -111.824443 33.601494, -111.824562 33.601476, -111.824842 33.601457, -111.824913 33.601456, -111.825055 33.60146, -111.825295 33.601486, -111.825787 33.601559, -111.825882 33.60159, -111.825983 33.601634, -111.826036 33.601665, -111.826063 33.601681, -111.826127 33.601735, -111.826243 33.601871, -111.827125 33.601114, -111.827046 33.601018, -111.827017 33.600967, -111.826985 33.600912, -111.826941 33.600799, -111.826924 33.600721, -111.826915 33.60061, -111.826921 33.600535, -111.82695 33.600374, -111.827015 33.600059, -111.827037 33.599942, -111.827058 33.599792, -111.827066 33.599615, -111.827059 33.599438, -111.827033 33.598821, -111.827025 33.598546, -111.827027 33.598237, -111.827034 33.598095, -111.827098 33.597573, -111.827379 33.597602, -111.827491 33.597602, -111.827617 33.597591, -111.827885 33.597546, -111.828011 33.59753, -111.828439 33.597506, -111.828609 33.597504, -111.829261 33.597551, -111.829499 33.597562, -111.829703 33.597557, -111.829913 33.597538, -111.830244 33.597485, -111.830804 33.597369, -111.83102 33.597323, -111.831237 33.597263, -111.831299 33.597378, -111.831338 33.597483, -111.831383 33.597701, -111.831389 33.597816, -111.83135 33.598323, -111.831267 33.5993, -111.831258 33.5996, -111.831257 33.599645, -111.831267 33.599794, -111.831308 33.600038, -111.831411 33.600522, -111.831448 33.600709, -111.831529 33.601115, -111.831548 33.601278, -111.831562 33.601476, -111.831563 33.601798, -111.831547 33.602211, -111.831507 33.602452, -111.831463 33.60259, -111.831372 33.602793, -111.831276 33.602988, -111.831223 33.603127, -111.831204 33.603217, -111.831194 33.60343, -111.831194 33.603915, -111.831195 33.604264, -111.831209 33.604488, -111.831234 33.604567, -111.831315 33.604745, -111.831356 33.604807, -111.831522 33.604981, -111.831595 33.605041, -111.831767 33.605147, -111.831821 33.605176, -111.831866 33.605195, -111.831978 33.605234, -111.832124 33.605263, -111.832297 33.605282, -111.832913 33.605318, -111.833098 33.605333, -111.833575 33.605371, -111.83376 33.60538, -111.834131 33.605378, -111.834323 33.605364, -111.834654 33.605315, -111.834848 33.605278, -111.835058 33.605222, -111.83581 33.604987, -111.835882 33.604957, -111.835973 33.604904, -111.836045 33.604847, -111.836116 33.604762, -111.836168 33.604786, -111.836249 33.604814, -111.836366 33.604834, -111.836457 33.604834, -111.836547 33.604822, -111.836637 33.604797, -111.836717 33.604761, -111.836932 33.604616, -111.836865 33.604452, -111.836891 33.604276, -111.836864 33.603874, -111.836832 33.603764, -111.836654 33.603627, -111.836595 33.603533, -111.836595 33.603407, -111.836628 33.603325, -111.836864 33.603077, -111.836918 33.602949, -111.836943 33.60289, -111.836854 33.602745, -111.83702 33.602538, -111.838218 33.600984, -111.838301 33.6009, -111.838383 33.600834, -111.838473 33.600782, -111.840518 33.599901, -111.840718 33.599827, -111.840884 33.599782, -111.841449 33.599687, -111.841581 33.599656, -111.841665 33.599627, -111.841783 33.599567, -111.841832 33.599532, -111.84188 33.599485, -111.842092 33.599166, -111.841282 33.598798, -111.841217 33.598755, -111.841163 33.598703, -111.841128 33.598655, -111.841098 33.598595, -111.841078 33.598529, -111.841069 33.598441, -111.841071 33.598419, -111.841076 33.598376, -111.841095 33.59832, -111.841186 33.598166, -111.841295 33.598008, -111.841624 33.598154, -111.841961 33.598294, -111.842474 33.59853, -111.843632 33.599049, -111.844287 33.599353, -111.844425 33.599431, -111.84449 33.599474, -111.844623 33.59958, -111.844717 33.599637, -111.844791 33.599673, -111.845014 33.599782, -111.846173 33.600297, -111.846398 33.600402, -111.847134 33.600745, -111.847236 33.600784, -111.847562 33.600909, -111.848084 33.601108, -111.84849 33.601277, -111.849764 33.601862, -111.85049 33.602203, -111.851274 33.602557, -111.851879 33.602842, -111.852487 33.603119, -111.852862 33.603309, -111.852972 33.603369, -111.853151 33.603486, -111.853335 33.603626, -111.853438 33.603718, -111.853591 33.603875, -111.853636 33.603927, -111.85381 33.604156, -111.853864 33.604244, -111.854105 33.604375, -111.855394 33.606601, -111.855651 33.607047, -111.858294 33.611098, -111.858572 33.611527, -111.858421 33.611528, -111.85688 33.61154, -111.85551 33.61153, -111.844864 33.611464, -111.844601 33.611564, -111.844423 33.611729, -111.844049 33.611944, -111.843924 33.611993, -111.843885 33.611997, -111.843766 33.61201, -111.843346 33.611977, -111.843109 33.612092, -111.842801 33.612318, -111.842218 33.612647, -111.842111 33.612708, -111.841887 33.612791, -111.841565 33.612802, -111.840422 33.61294, -111.840126 33.612901, -111.83985 33.612918, -111.839691 33.612972, -111.839797 33.614803, -111.840236 33.61595, -111.840651 33.616232, -111.841261 33.616648, -111.841104 33.616763, -111.841004 33.616851, -111.84087 33.616995, -111.840806 33.617078, -111.840714 33.617227, -111.840641 33.617314, -111.840556 33.617391, -111.840482 33.617443, -111.840375 33.617497, -111.840299 33.617524, -111.840192 33.617549, -111.840058 33.617566, -111.839681 33.617596, -111.839448 33.617603, -111.839203 33.617597, -111.838866 33.617578, -111.838697 33.617581, -111.838525 33.617599, -111.838389 33.617623, -111.838206 33.617673, -111.837878 33.61779, -111.836953 33.618062, -111.836728 33.61812, -111.836572 33.618147, -111.836416 33.618162, -111.836276 33.618164, -111.836001 33.61815, -111.835813 33.618122, -111.835654 33.618084, -111.835479 33.618029, -111.835304 33.617962, -111.835105 33.617856, -111.834899 33.617721, -111.834757 33.617643, -111.834627 33.617585, -111.834506 33.617547, -111.834375 33.617514, -111.834236 33.617492, -111.834121 33.617484, -111.833646 33.617488, -111.83286 33.617488, -111.832664 33.617489, -111.832551 33.617501, -111.832469 33.617521, -111.832369 33.617561, -111.832301 33.6176, -111.832226 33.617658, -111.831575 33.618202, -111.831274 33.618462, -111.831119 33.618618, -111.830989 33.61877, -111.830875 33.61892, -111.830772 33.619086, -111.830664 33.619291, -111.830543 33.619541, -111.830424 33.619741, -111.830372 33.619797, -111.830306 33.619845, -111.830229 33.61988, -111.82922 33.620238, -111.829247 33.620694, -111.829043 33.621057, -111.828879 33.621431, -111.828649 33.621821, -111.82834 33.622613, -111.828301 33.622866, -111.82832 33.623009, -111.828261 33.623207, -111.827998 33.623608, -111.827394 33.624153, -111.827223 33.624362, -111.827144 33.624565, -111.827171 33.624906, -111.827092 33.625042, -111.82652 33.625918, -111.826111 33.626804, -111.825959 33.627387, -111.82596 33.62755, -111.825951 33.627658, -111.825952 33.627784, -111.825889 33.627956, -111.825719 33.628219, -111.825623 33.628391, -111.825369 33.628807, -111.82523 33.628989, -111.825028 33.629315, -111.824977 33.629522, -111.824989 33.629685, -111.825055 33.629828, -111.825143 33.629972, -111.825209 33.630152, -111.82521 33.630296, -111.825125 33.630423, -111.824975 33.630542, -111.824783 33.63066, -111.824579 33.630833, -111.82444 33.630969, -111.824259 33.631133, -111.824088 33.631269, -111.823884 33.631388, -111.823701 33.631479, -111.823454 33.631571, -111.823306 33.63161, -111.823535 33.631914, -111.826107 33.631814, -111.826137 33.631892, -111.826527 33.632911, -111.826406 33.634305, -111.827363 33.634803, -111.828801 33.634555, -111.829817 33.634754, -111.830895 33.635402, -111.830954 33.636697, -111.830346 33.637406, -111.830274 33.638306, -111.830272 33.638533, -111.830307 33.638717, -111.830548 33.63864, -111.830675 33.638328, -111.830958 33.638249, -111.831014 33.638289, -111.831612 33.638192, -111.832053 33.637303, -111.833228 33.636797, -111.834138 33.637176, -111.834903 33.637495, -111.834876 33.637132, -111.834843 33.636697, -111.835202 33.635601, -111.835561 33.635169, -111.835704 33.635051, -111.835741 33.634953, -111.836279 33.634605, -111.836758 33.634605, -111.837356 33.634754, -111.837895 33.634106, -111.838103 33.63401, -111.838553 33.633807, -111.839095 33.633807, -111.83951 33.633807, -111.840288 33.634007, -111.840803 33.634651, -111.841006 33.634904, -111.840707 33.635551, -111.83957 33.637345, -111.838858 33.638456, -111.838613 33.63884, -111.83725 33.639042, -111.836937 33.639089, -111.836237 33.639213, -111.8361 33.639238, -111.83616 33.639388, -111.836457 33.639607, -111.837246 33.639598, -111.839881 33.639568, -111.840843 33.640614, -111.843988 33.640187, -111.84582 33.639939, -111.845834 33.639954, -111.845989 33.639991, -111.84622 33.640048, -111.846458 33.640095, -111.846696 33.640129, -111.846935 33.640151, -111.847176 33.640162, -111.848262 33.640164, -111.849261 33.640165, -111.849626 33.640158, -111.851776 33.640164, -111.852002 33.64016, -111.852449 33.640159, -111.853129 33.640162, -111.85386 33.640156, -111.854012 33.640158, -111.854322 33.640161, -111.855718 33.640155, -111.85671 33.640158, -111.858594 33.640156, -111.860129 33.640156, -111.860401 33.640145, -111.860719 33.640141, -111.86094 33.640146, -111.861087 33.640153, -111.861216 33.640158, -111.861172 33.643235, -111.861266 33.64365, -111.861279 33.643988, -111.861635 33.645509, -111.86179 33.645997, -111.862173 33.646965, -111.862573 33.647762, -111.862956 33.648393, -111.863791 33.649821, -111.864627 33.651223, -111.864943 33.651794, -111.865187 33.652214, -111.865544 33.652994, -111.865571 33.653053, -111.865915 33.653953, -111.866219 33.654706, -111.865995 33.654705, -111.865935 33.654705, -111.864998 33.654702, -111.866426 33.655937, -111.866547 33.656042, -111.866728 33.656188, -111.866798 33.65636, -111.866925 33.656403, -111.867098 33.65694, -111.867275 33.657444, -111.867357 33.65792, -111.867343 33.658329, -111.867289 33.658614, -111.86722 33.658968, -111.867098 33.659227, -111.866862 33.659812, -111.866752 33.66004, -111.866605 33.660335, -111.866398 33.660788, -111.866191 33.661192, -111.865857 33.661644, -111.865581 33.661989, -111.865328 33.66223, -111.864964 33.662731, -111.864398 33.66328, -111.863752 33.664181, -111.863589 33.664575, -111.863404 33.665062, -111.863304 33.665433, -111.863242 33.666012, -111.863083 33.667305, -111.863083 33.66737, -111.8631 33.667449, -111.863127 33.667508, -111.863184 33.667714, -111.862623 33.66784, -111.862072 33.667972, -111.861809 33.668008, -111.86164 33.668041, -111.861546 33.668069, -111.861454 33.668103, -111.861327 33.668167, -111.861192 33.668246, -111.861065 33.668306, -111.860885 33.668081, -111.86069 33.667952, -111.860804 33.667768, -111.860942 33.667659, -111.861105 33.667591, -111.861244 33.667496, -111.861553 33.667108, -111.861822 33.666761, -111.862442 33.665937, -111.862548 33.665801, -111.862719 33.665433, -111.862532 33.665358, -111.862361 33.66527, -111.862231 33.665181, -111.86206 33.665051, -111.861432 33.665453, -111.86118 33.665678, -111.861074 33.665834, -111.861017 33.666113, -111.860992 33.666365, -111.860984 33.666447, -111.860943 33.666515, -111.860666 33.666399, -111.860593 33.666406, -111.86052 33.666474, -111.860438 33.666515, -111.859729 33.666794, -111.859379 33.666405, -111.858499 33.666997, -111.858768 33.667236, -111.858866 33.667365, -111.858955 33.667549, -111.858979 33.667747, -111.858971 33.66791, -111.858979 33.668196, -111.859003 33.668346, -111.859089 33.668577, -111.858894 33.668625, -111.85874 33.668686, -111.858591 33.668759, -111.858429 33.66886, -111.858071 33.669109, -111.857947 33.668943, -111.857886 33.668824, -111.857866 33.668705, -111.857876 33.668603, -111.857347 33.668483, -111.857205 33.668492, -111.857184 33.668679, -111.857164 33.668772, -111.856827 33.669266, -111.856787 33.669394, -111.856684 33.669828, -111.856694 33.669955, -111.856827 33.670041, -111.856979 33.670109, -111.857168 33.670204, -111.857376 33.670305, -111.857468 33.670339, -111.85756 33.670339, -111.858415 33.670212, -111.858639 33.670169, -111.85873 33.670127, -111.858822 33.670059, -111.858914 33.670084, -111.859016 33.670101, -111.859158 33.670076, -111.859973 33.669948, -111.860441 33.669898, -111.860492 33.669974, -111.860532 33.670008, -111.860624 33.670042, -111.860706 33.670017, -111.860756 33.669991, -111.860807 33.669923, -111.860818 33.669847, -111.861093 33.669864, -111.861235 33.669923, -111.861459 33.670094, -111.861683 33.670273, -111.861611 33.6704, -111.861642 33.670596, -111.86174 33.670846, -111.861791 33.670977, -111.861855 33.671141, -111.861865 33.671286, -111.861824 33.671498, -111.861733 33.671643, -111.861509 33.671915, -111.861458 33.672077, -111.861457 33.672494, -111.86105 33.672503, -111.860826 33.672562, -111.859166 33.673327, -111.859085 33.673387, -111.859003 33.673498, -111.858937 33.673624, -111.858832 33.673826, -111.859023 33.673889, -111.859123 33.673968, -111.859186 33.674017, -111.859278 33.674077, -111.85942 33.674145, -111.859736 33.674213, -111.859949 33.674179, -111.859981 33.674171, -111.860955 33.674673, -111.863496 33.675984, -111.863533 33.676087, -111.863589 33.676267, -111.863644 33.676356, -111.863764 33.6765, -111.863862 33.67658, -111.864112 33.676768, -111.864221 33.676875, -111.864308 33.677037, -111.864365 33.677325, -111.8644 33.677587, -111.864413 33.677794, -111.864437 33.678037, -111.864516 33.67838, -111.864745 33.678585, -111.865039 33.678854, -111.865267 33.679078, -111.865344 33.679203, -111.865367 33.67933, -111.865478 33.679617, -111.865554 33.679734, -111.865772 33.679895, -111.865924 33.679975, -111.866075 33.680046, -111.8664 33.680161, -111.866606 33.68025, -111.86679 33.680348, -111.866921 33.680437, -111.867029 33.680517, -111.867095 33.680616, -111.867158 33.680757, -111.867172 33.680787, -111.867371 33.681192, -111.867482 33.681579, -111.867659 33.681965, -111.867725 33.6821, -111.867845 33.682252, -111.867933 33.682378, -111.86801 33.682531, -111.86804 33.682612, -111.868574 33.682449, -111.868665 33.682596, -111.869173 33.683322, -111.869411 33.683758, -111.869782 33.684438, -111.869779 33.685978, -111.869779 33.686584, -111.869933 33.68658, -111.871586 33.686579, -111.871587 33.686119, -111.871591 33.686068, -111.871615 33.685921, -111.871661 33.685758, -111.871762 33.685357, -111.871765 33.68528, -111.871762 33.684699, -111.871767 33.684436, -111.872214 33.684437, -111.873337 33.684435, -111.874745 33.684435, -111.874905 33.684449, -111.875075 33.684478, -111.875205 33.684512, -111.875332 33.684554, -111.875447 33.684601, -111.875582 33.684669, -111.875708 33.684748, -111.87584 33.684849, -111.875926 33.68493, -111.876071 33.685089, -111.876172 33.685179, -111.876386 33.685329, -111.877969 33.68641, -111.878197 33.686566, -111.878433 33.686754, -111.878593 33.686895, -111.879028 33.687301, -111.878489 33.6877, -111.878324 33.687806, -111.878148 33.687905, -111.877737 33.688101, -111.877271 33.688318, -111.877131 33.688398, -111.876996 33.688494, -111.876875 33.688602, -111.876791 33.688694, -111.876704 33.688811, -111.876621 33.688951, -111.876567 33.68907, -111.876537 33.689151, -111.876507 33.689285, -111.876486 33.689464, -111.876484 33.689647, -111.876493 33.690187, -111.878319 33.690188, -111.880969 33.690183, -111.880979 33.691073, -111.88098 33.691154, -111.881003 33.691294, -111.881034 33.691431, -111.880697 33.691493, -111.880514 33.691524, -111.880362 33.691537, -111.880203 33.691536, -111.880119 33.69153, -111.87981 33.691532, -111.87834 33.691534, -111.877182 33.691535, -111.877002 33.691554, -111.876826 33.691586, -111.87665 33.691631, -111.876483 33.691687, -111.876313 33.691759, -111.876188 33.691826, -111.876026 33.691932, -111.875879 33.69205, -111.87578 33.692145, -111.875663 33.692277, -111.875121 33.692986, -111.874806 33.69347, -111.874554 33.69384, -111.874517 33.693895, -111.874514 33.693932, -111.874524 33.693961, -111.874544 33.693987, -111.875492 33.694548, -111.875415 33.694629, -111.875286 33.694755, -111.875163 33.694864, -111.875 33.694989, -111.874808 33.695114, -111.874697 33.695175, -111.874498 33.695268, -111.874162 33.695396, -111.873599 33.69561, -111.872712 33.695955, -111.872802 33.696116, -111.872876 33.696287, -111.872922 33.696424, -111.872999 33.696744, -111.873026 33.696922, -111.873036 33.697107, -111.873034 33.697228, -111.87303 33.698117, -111.873126 33.698109, -111.873413 33.698095, -111.873518 33.698094, -111.874058 33.698091, -111.874465 33.698088, -111.875615 33.698091, -111.876583 33.698087, -111.879663 33.698085, -111.882057 33.698084, -111.882767 33.698084, -111.884725 33.698083, -111.884955 33.698083, -111.885048 33.698084, -111.887312 33.69813, -111.887563 33.698145, -111.887764 33.698169, -111.88797 33.698205, -111.888205 33.698263, -111.888401 33.698324, -111.888589 33.698395, -111.88907 33.698548, -111.889227 33.698588, -111.889522 33.698647, -111.889757 33.69868, -111.890001 33.698701, -111.890226 33.698708, -111.890603 33.698703, -111.890611 33.704265, -111.890611 33.704917, -111.890612 33.705121, -111.890608 33.707714, -111.890609 33.710178, -111.890609 33.711021, -111.890612 33.711386, -111.890622 33.712631, -111.890625 33.713108, -111.890626 33.713197, -111.89064 33.715072, -111.890624 33.716808, -111.890801 33.716844, -111.890871 33.71685, -111.891087 33.71686, -111.891138 33.71686, -111.891784 33.716859, -111.894873 33.716855, -111.894906 33.716857, -111.894939 33.716869, -111.894964 33.716889, -111.894981 33.716923, -111.894983 33.717504, -111.894861 33.717914, -111.894834 33.718159, -111.89484 33.718199, -111.89486 33.718245, -111.894908 33.718328, -111.894936 33.718414, -111.89497 33.718597, -111.894963 33.71862, -111.894924 33.718665, -111.894908 33.718705, -111.894892 33.71879, -111.894884 33.718912, -111.894876 33.718981, -111.894885 33.719043, -111.894904 33.719129, -111.894892 33.719699, -111.894903 33.719964, -111.894887 33.720081, -111.894894 33.720491, -111.893155 33.720486, -111.891253 33.72048, -111.891012 33.720478, -111.890619 33.720474, -111.889884 33.720485, -111.889554 33.720474, -111.88924 33.720433, -111.889161 33.720408, -111.888962 33.720358, -111.888744 33.720285, -111.888548 33.720202, -111.888372 33.720113, -111.88824 33.720033, -111.888197 33.720024, -111.888089 33.719993, -111.887836 33.719948, -111.88752 33.719906, -111.887204 33.719877, -111.887077 33.71987, -111.886828 33.719883, -111.886372 33.719891, -111.886257 33.7199, -111.886143 33.719917, -111.8861 33.719741, -111.886081 33.719684, -111.886053 33.719554, -111.886043 33.719423, -111.886072 33.718924, -111.886095 33.718572, -111.886095 33.718449, -111.886078 33.71831, -111.88604 33.718163, -111.886 33.718063, -111.885943 33.717953, -111.885875 33.717851, -111.885797 33.717756, -111.88571 33.717669, -111.885661 33.717622, -111.885518 33.71752, -111.885362 33.717432, -111.885239 33.717379, -111.885092 33.717332, -111.884946 33.717298, -111.884756 33.717271, -111.884133 33.717231, -111.883865 33.717205, -111.883555 33.717153, -111.883501 33.717142, -111.883321 33.717105, -111.882724 33.716969, -111.882589 33.716951, -111.882458 33.716947, -111.88205 33.716942, -111.881939 33.716938, -111.881831 33.716947, -111.881732 33.716965, -111.881627 33.716996, -111.881558 33.717028, -111.881519 33.717052, -111.881447 33.717105, -111.881379 33.717172, -111.88132 33.717258, -111.881276 33.717353, -111.881254 33.717432, -111.881245 33.717529, -111.881255 33.717617, -111.881281 33.717701, -111.881329 33.717792, -111.881403 33.717879, -111.881494 33.717955, -111.881576 33.718006, -111.881666 33.718047, -111.882524 33.718324, -111.882761 33.718413, -111.882852 33.718454, -111.882916 33.718483, -111.883124 33.718591, -111.883311 33.718704, -111.883542 33.718875, -111.883708 33.719018, -111.884626 33.719918, -111.884772 33.72004, -111.884878 33.720118, -111.885258 33.720374, -111.885149 33.720498, -111.884858 33.720798, -111.884734 33.720909, -111.884595 33.721019, -111.884434 33.721129, -111.884263 33.721229, -111.884037 33.721338, -111.88389 33.721397, -111.88368 33.721469, -111.883518 33.721516, -111.883476 33.721421, -111.883411 33.72131, -111.883303 33.72118, -111.883235 33.721121, -111.883121 33.721041, -111.882998 33.720971, -111.882863 33.720913, -111.882766 33.72088, -111.88262 33.720843, -111.88222 33.720767, -111.881915 33.720695, -111.881706 33.720638, -111.881407 33.720537, -111.881124 33.72042, -111.880903 33.720314, -111.880683 33.720198, -111.880444 33.720054, -111.88025 33.719916, -111.880217 33.719893, -111.880034 33.719748, -111.879858 33.719593, -111.879696 33.719435, -111.879164 33.718855, -111.879021 33.718699, -111.879282 33.718572, -111.879357 33.718521, -111.879402 33.718474, -111.87943 33.718422, -111.879444 33.71836, -111.879435 33.718287, -111.879322 33.717912, -111.879287 33.717843, -111.879242 33.71779, -111.879174 33.717737, -111.879096 33.717699, -111.878626 33.717559, -111.878509 33.717538, -111.878419 33.717532, -111.878325 33.717537, -111.878239 33.717554, -111.878157 33.717582, -111.878064 33.717632, -111.877981 33.717699, -111.877928 33.717762, -111.877818 33.717965, -111.877637 33.717937, -111.877498 33.717924, -111.877475 33.717922, -111.87734 33.71792, -111.877205 33.717927, -111.877084 33.717943, -111.876713 33.718, -111.876089 33.718105, -111.875951 33.71814, -111.875809 33.71819, -111.875662 33.71826, -111.875495 33.718376, -111.875433 33.718317, -111.875267 33.718144, -111.875155 33.718007, -111.874761 33.71744, -111.874642 33.717288, -111.874497 33.717132, -111.874356 33.717005, -111.874245 33.716918, -111.874101 33.71682, -111.873946 33.716729, -111.873703 33.717023, -111.873635 33.717081, -111.873545 33.717133, -111.873463 33.717166, -111.873361 33.717189, -111.873254 33.717197, -111.873142 33.717192, -111.873044 33.717177, -111.872918 33.717162, -111.872611 33.717126, -111.872503 33.717123, -111.872383 33.717131, -111.872245 33.717156, -111.871454 33.717348, -111.871324 33.717366, -111.871196 33.717369, -111.871068 33.717357, -111.870946 33.717331, -111.870848 33.717299, -111.870824 33.71729, -111.870701 33.717221, -111.87059 33.717139, -111.870515 33.717069, -111.870432 33.71697, -111.870384 33.716869, -111.870351 33.716767, -111.870334 33.716662, -111.870333 33.716557, -111.870169 33.716537, -111.870009 33.716504, -111.869861 33.716459, -111.869715 33.716402, -111.869587 33.716341, -111.868787 33.715881, -111.868678 33.715823, -111.868583 33.715778, -111.868396 33.715707, -111.868201 33.715652, -111.868004 33.715614, -111.867854 33.715597, -111.867702 33.715589, -111.867511 33.715597, -111.867319 33.715619, -111.86713 33.715656, -111.86694 33.715708, -111.866795 33.71576, -111.866589 33.715857, -111.866547 33.715775, -111.866501 33.71566, -111.86647 33.715544, -111.866453 33.715426, -111.86645 33.715305, -111.866461 33.715186, -111.866486 33.715066, -111.866505 33.715009, -111.866526 33.714949, -111.866578 33.714837, -111.86666 33.714694, -111.866741 33.714579, -111.86685 33.714448, -111.866988 33.714313, -111.867076 33.714239, -111.867231 33.714077, -111.867313 33.71398, -111.867403 33.713851, -111.867469 33.713732, -111.867522 33.71361, -111.867569 33.713457, -111.867589 33.713348, -111.867614 33.71311, -111.867623 33.712873, -111.867616 33.712639, -111.867544 33.712639, -111.867361 33.712639, -111.865982 33.712641, -111.862295 33.712646, -111.861501 33.712647, -111.861118 33.712648, -111.859112 33.71265, -111.858985 33.712653, -111.858544 33.712654, -111.858212 33.712648, -111.857519 33.712663, -111.857444 33.712662, -111.857057 33.712658, -111.856777 33.712646, -111.856352 33.712624, -111.856221 33.712622, -111.856009 33.712604, -111.855828 33.712581, -111.855573 33.712553, -111.855353 33.71252, -111.855118 33.712476, -111.855059 33.712463, -111.85493 33.712431, -111.854729 33.712381, -111.854405 33.712288, -111.85419 33.712218, -111.854006 33.712147, -111.853721 33.712037, -111.85357 33.711975, -111.853155 33.711775, -111.852883 33.71162, -111.852653 33.711475, -111.851839 33.710931, -111.851307 33.71059, -111.851177 33.710507, -111.850939 33.710343, -111.850511 33.710063, -111.850338 33.709948, -111.848855 33.708966, -111.848922 33.708899, -111.848965 33.708856, -111.849185 33.708622, -111.849317 33.708463, -111.849388 33.708351, -111.849435 33.70825, -111.849473 33.708134, -111.849516 33.707954, -111.849524 33.707842, -111.849513 33.707728, -111.849836 33.707667, -111.850012 33.707649, -111.850223 33.707647, -111.850219 33.708053, -111.850218 33.708184, -111.850231 33.70829, -111.850247 33.708347, -111.850273 33.708391, -111.850312 33.708433, -111.850421 33.708507, -111.850512 33.70855, -111.850754 33.708603, -111.850974 33.708665, -111.851308 33.708751, -111.851854 33.708893, -111.851941 33.70893, -111.852022 33.70898, -111.852073 33.70903, -111.852209 33.709147, -111.852318 33.70925, -111.852415 33.709332, -111.852432 33.709346, -111.852485 33.709379, -111.852805 33.709539, -111.852928 33.709608, -111.853019 33.70968, -111.853071 33.709739, -111.853168 33.709867, -111.853278 33.709999, -111.853601 33.710415, -111.853647 33.710457, -111.853691 33.710485, -111.853762 33.710516, -111.853821 33.710527, -111.853883 33.710526, -111.853965 33.710513, -111.854025 33.710494, -111.854093 33.710459, -111.854142 33.710413, -111.854179 33.710357, -111.854213 33.710253, -111.854311 33.709982, -111.854362 33.709913, -111.854439 33.709831, -111.85462 33.709686, -111.854662 33.709647, -111.854721 33.709573, -111.854759 33.709501, -111.854783 33.709408, -111.854821 33.709212, -111.854869 33.709048, -111.854948 33.708846, -111.855007 33.708729, -111.85503 33.708683, -111.855206 33.708392, -111.855283 33.708262, -111.855316 33.708173, -111.855336 33.708071, -111.855338 33.707961, -111.855323 33.707869, -111.855294 33.707779, -111.855241 33.707674, -111.855175 33.707589, -111.85512 33.707535, -111.855041 33.707476, -111.854779 33.70732, -111.854672 33.707253, -111.85458 33.707194, -111.854324 33.707042, -111.854145 33.706928, -111.853994 33.706832, -111.853929 33.70679, -111.853525 33.706554, -111.853434 33.706506, -111.853313 33.706443, -111.853173 33.706372, -111.852988 33.706294, -111.852688 33.706188, -111.852464 33.706125, -111.852098 33.706044, -111.851704 33.705971, -111.851489 33.705923, -111.851303 33.705889, -111.851211 33.705884, -111.850605 33.705896, -111.8504 33.705909, -111.850283 33.705933, -111.850163 33.705973, -111.850092 33.70601, -111.849907 33.706127, -111.849815 33.706225, -111.849695 33.706379, -111.849618 33.706452, -111.84952 33.706523, -111.849451 33.706562, -111.849303 33.706641, -111.849198 33.706713, -111.849105 33.706789, -111.849013 33.70688, -111.848888 33.707017, -111.848665 33.70691, -111.848121 33.706661, -111.846902 33.706102, -111.846596 33.705965, -111.846068 33.705715, -111.845976 33.705657, -111.845898 33.705589, -111.84582 33.705502, -111.845753 33.705405, -111.84572 33.705344, -111.845683 33.705287, -111.845217 33.704566, -111.845172 33.704511, -111.845046 33.704566, -111.844888 33.704623, -111.844689 33.70468, -111.844483 33.704723, -111.844087 33.704786, -111.844031 33.704797, -111.843926 33.704818, -111.843726 33.704876, -111.84358 33.704927, -111.843442 33.704985, -111.843298 33.705064, -111.843202 33.705128, -111.843079 33.705221, -111.842965 33.705319, -111.84286 33.705428, -111.842752 33.705561, -111.842659 33.705705, -111.842636 33.705747, -111.842274 33.705568, -111.841907 33.705404, -111.841857 33.705383, -111.841801 33.70536, -111.841549 33.705237, -111.841291 33.705123, -111.840881 33.704955, -111.840593 33.704852, -111.840292 33.704762, -111.840078 33.704706, -111.83981 33.704647, -111.83946 33.704589, -111.839201 33.704556, -111.838862 33.704528, -111.838774 33.704523, -111.838829 33.703988, -111.838854 33.703775, -111.838863 33.703603, -111.838854 33.703477, -111.838829 33.703345, -111.838787 33.703208, -111.838775 33.703183, -111.838749 33.703107, -111.838737 33.702997, -111.838741 33.702875, -111.838749 33.702828, -111.83875 33.702737, -111.838732 33.702625, -111.838698 33.702525, -111.838645 33.702415, -111.838595 33.702355, -111.838482 33.702261, -111.838318 33.702159, -111.838265 33.702117, -111.838199 33.702051, -111.838142 33.701973, -111.83811 33.701909, -111.838071 33.701766, -111.83806 33.701658, -111.838082 33.701279, -111.838081 33.701207, -111.838063 33.701096, -111.838027 33.700989, -111.837986 33.700911, -111.837928 33.700831, -111.837858 33.700759, -111.8378 33.700712, -111.83771 33.700659, -111.837618 33.700614, -111.837504 33.700578, -111.837361 33.700547, -111.836948 33.700463, -111.836968 33.700373, -111.837012 33.70026, -111.837025 33.700206, -111.837038 33.700109, -111.837036 33.700053, -111.837035 33.700013, -111.837015 33.699909, -111.836994 33.699842, -111.836948 33.699755, -111.836872 33.699654, -111.836801 33.699538, -111.836763 33.699422, -111.836752 33.699351, -111.836727 33.699277, -111.836686 33.699197, -111.836644 33.699127, -111.836586 33.699061, -111.836481 33.698976, -111.836368 33.698904, -111.836288 33.698865, -111.836173 33.698824, -111.836171 33.698794, -111.836173 33.698642, -111.836127 33.698629, -111.835832 33.698536, -111.835702 33.698438, -111.835529 33.698376, -111.835377 33.698332, -111.835182 33.698252, -111.835051 33.698166, -111.833315 33.698166, -111.833174 33.698158, -111.822692 33.698149, -111.822708 33.6982, -111.822785 33.698436, -111.822828 33.698637, -111.822839 33.698753, -111.82285 33.698936, -111.822857 33.700349, -111.822862 33.700564, -111.82286 33.700873, -111.822859 33.701106, -111.822825 33.701499, -111.822822 33.701635, -111.822831 33.701887, -111.822856 33.702328, -111.822869 33.702618, -111.822871 33.702799, -111.822877 33.703408, -111.822872 33.703692, -111.822873 33.704282, -111.822872 33.704754, -111.822866 33.705371, -111.82287 33.705428, -111.822873 33.705442, -111.822881 33.705466, -111.822911 33.705519, -111.822957 33.70557, -111.823004 33.705605, -111.823072 33.705639, -111.823131 33.705655, -111.823946 33.705755, -111.824167 33.705787, -111.824256 33.705797, -111.824437 33.705807, -111.824685 33.705801, -111.824797 33.705794, -111.825726 33.70574, -111.825896 33.70573, -111.825967 33.705704, -111.826494 33.705658, -111.826784 33.705647, -111.82711 33.705659, -111.827134 33.705515, -111.827155 33.705452, -111.827191 33.705347, -111.82729 33.705131, -111.827465 33.70518, -111.827759 33.705248, -111.827937 33.705277, -111.828314 33.705314, -111.82846 33.705324, -111.828721 33.705356, -111.828826 33.705376, -111.828953 33.705401, -111.82924 33.705454, -111.829422 33.705481, -111.82994 33.705513, -111.83006 33.705528, -111.8302 33.705559, -111.830333 33.705604, -111.8305 33.705672, -111.830591 33.705719, -111.830783 33.705848, -111.831027 33.705999, -111.831295 33.706133, -111.831415 33.706176, -111.831634 33.706241, -111.831889 33.706319, -111.831973 33.706348, -111.83213 33.706431, -111.832184 33.70647, -111.832229 33.706503, -111.832299 33.706577, -111.832331 33.706611, -111.832416 33.706693, -111.832475 33.706764, -111.832199 33.706877, -111.831858 33.707016, -111.831665 33.70711, -111.831499 33.707201, -111.831341 33.707301, -111.831106 33.707472, -111.830913 33.707629, -111.830704 33.707822, -111.830512 33.708028, -111.830332 33.70825, -111.830208 33.708422, -111.830167 33.708483, -111.829683 33.709316, -111.829525 33.709598, -111.829293 33.70998, -111.829152 33.710195, -111.828898 33.710544, -111.828937 33.710558, -111.829093 33.71061, -111.82924 33.710644, -111.829387 33.710663, -111.829497 33.710666, -111.829622 33.71066, -111.829728 33.710643, -111.829851 33.710612, -111.830042 33.710539, -111.830139 33.710501, -111.830225 33.710476, -111.830375 33.71045, -111.830463 33.710445, -111.830794 33.710454, -111.831144 33.710475, -111.831136 33.710609, -111.831149 33.710856, -111.831183 33.711049, -111.831191 33.711153, -111.831189 33.71128, -111.831178 33.711343, -111.831148 33.71143, -111.831045 33.711662, -111.831005 33.7118, -111.830986 33.711901, -111.83096 33.712192, -111.831028 33.712196, -111.831844 33.712201, -111.831827 33.712306, -111.831822 33.712409, -111.831801 33.712539, -111.83178 33.712631, -111.83177 33.712751, -111.83178 33.712881, -111.831802 33.712991, -111.831837 33.713099, -111.831921 33.713244, -111.83198 33.713327, -111.832053 33.713463, -111.832094 33.713547, -111.832159 33.713648, -111.832254 33.713767, -111.832333 33.713837, -111.832425 33.713911, -111.832513 33.714006, -111.832574 33.714094, -111.832592 33.71413, -111.832629 33.714239, -111.832674 33.714326, -111.832728 33.714404, -111.832799 33.714483, -111.83291 33.714573, -111.833202 33.714772, -111.833091 33.714876, -111.832997 33.714952, -111.832893 33.715025, -111.83252 33.715247, -111.832313 33.715392, -111.832126 33.715552, -111.832033 33.715635, -111.831849 33.715811, -111.831543 33.716119, -111.831484 33.716197, -111.831438 33.716285, -111.831419 33.716364, -111.831417 33.716442, -111.831432 33.716522, -111.831463 33.716592, -111.831528 33.716689, -111.831585 33.716745, -111.831672 33.716808, -111.831706 33.716827, -111.832008 33.716951, -111.832113 33.717004, -111.832155 33.717029, -111.832294 33.717125, -111.832546 33.717295, -111.832663 33.717372, -111.832932 33.717537, -111.83309 33.71763, -111.833378 33.717808, -111.833503 33.717876, -111.833373 33.717995, -111.833212 33.718114, -111.833033 33.718221, -111.832823 33.718322, -111.832688 33.718372, -111.832639 33.71839, -111.832463 33.718443, -111.832328 33.718486, -111.832172 33.718551, -111.832063 33.718612, -111.831974 33.718676, -111.831883 33.718758, -111.831825 33.718818, -111.831802 33.718837, -111.831752 33.718898, -111.831693 33.718953, -111.831569 33.719042, -111.83139 33.719141, -111.831282 33.719204, -111.831161 33.719288, -111.831086 33.719353, -111.831031 33.719411, -111.830944 33.719531, -111.830867 33.719668, -111.83084 33.719747, -111.830811 33.719865, -111.830792 33.720025, -111.830796 33.720377, -111.830799 33.722761, -111.830801 33.723651, -111.830801 33.724919, -111.830801 33.725218, -111.830804 33.725891, -111.830801 33.72682, -111.830149 33.726832, -111.829585 33.72685, -111.828863 33.726862, -111.827734 33.726866, -111.826738 33.726865, -111.82655 33.726855, -111.826589 33.726951, -111.826578 33.726984, -111.826556 33.727379, -111.82651 33.727952, -111.826469 33.72825, -111.82639 33.728759, -111.826161 33.730336, -111.826148 33.730465, -111.826291 33.730475, -111.826419 33.730468, -111.826681 33.73047, -111.82687 33.730488, -111.826957 33.730484, -111.827107 33.730459, -111.827235 33.730426, -111.827358 33.730405, -111.827582 33.730379, -111.827698 33.730373, -111.827783 33.730378, -111.827992 33.730418, -111.828237 33.730455, -111.828472 33.730475, -111.828654 33.730463, -111.828815 33.730466, -111.828941 33.730438, -111.829007 33.730419, -111.829093 33.730384, -111.829175 33.730337, -111.829208 33.730322, -111.829262 33.730313, -111.829609 33.730346, -111.829709 33.730372, -111.829863 33.730424, -111.830292 33.73044, -111.831094 33.730431, -111.831665 33.730402, -111.831782 33.730402, -111.832545 33.730402, -111.832615 33.730386, -111.832676 33.730359, -111.832726 33.730323, -111.832767 33.730276, -111.832788 33.730256, -111.832857 33.730192, -111.832882 33.730171, -111.833046 33.730028, -111.8331 33.729968, -111.833136 33.729915, -111.833188 33.729808, -111.833224 33.729709, -111.83325 33.729583, -111.833252 33.729562, -111.833253 33.729458, -111.833236 33.729344, -111.833183 33.729129, -111.833336 33.7291, -111.833465 33.729053, -111.833579 33.728999, -111.833685 33.728935, -111.834072 33.728672, -111.834105 33.728645, -111.834193 33.728574, -111.834318 33.728451, -111.834437 33.728307, -111.834544 33.728153, -111.834629 33.728018, -111.834722 33.727847, -111.834855 33.727607, -111.834578 33.72751, -111.834454 33.727476, -111.834361 33.727455, -111.834152 33.727427, -111.834009 33.727414, -111.833538 33.727413, -111.833434 33.727418, -111.83335 33.727429, -111.833347 33.727325, -111.833361 33.727114, -111.833361 33.72697, -111.833342 33.72681, -111.834396 33.726808, -111.83551 33.726808, -111.836152 33.726812, -111.837024 33.726806, -111.837119 33.726806, -111.837507 33.726792, -111.838453 33.726775, -111.838812 33.726759, -111.839033 33.726756, -111.839022 33.727041, -111.839008 33.727083, -111.838985 33.727118, -111.838922 33.727184, -111.838888 33.727241, -111.838882 33.727265, -111.838892 33.727299, -111.838909 33.727316, -111.838991 33.727372, -111.839021 33.727414, -111.839028 33.727456, -111.839024 33.727477, -111.839025 33.728002, -111.839026 33.728054, -111.839029 33.72824, -111.838997 33.728353, -111.838982 33.728395, -111.838977 33.728408, -111.83898 33.728456, -111.839005 33.728532, -111.839014 33.72864, -111.83902 33.728661, -111.839024 33.728839, -111.839017 33.729091, -111.839026 33.729559, -111.839016 33.730017, -111.839016 33.730354, -111.839012 33.73045, -111.838978 33.730642, -111.838966 33.730865, -111.838971 33.730951, -111.839004 33.731016, -111.839024 33.731101, -111.839024 33.731162, -111.839021 33.731192, -111.839021 33.731221, -111.839018 33.731449, -111.839026 33.731753, -111.83902 33.73192, -111.839 33.732131, -111.839006 33.732185, -111.839017 33.732238, -111.839025 33.732428, -111.839026 33.73335, -111.83903 33.733756, -111.839031 33.733902, -111.839009 33.733982, -111.838968 33.734056, -111.838951 33.734078, -111.839232 33.734086, -111.837848 33.735328, -111.838281 33.73762, -111.838252 33.737663, -111.838188 33.737724, -111.837209 33.736789, -111.837097 33.736665, -111.836901 33.736563, -111.836687 33.736516, -111.836444 33.736485, -111.836295 33.736422, -111.836099 33.736235, -111.835679 33.736461, -111.835455 33.736765, -111.835258 33.736983, -111.835072 33.737053, -111.834708 33.737045, -111.834713 33.737365, -111.834718 33.737671, -111.834717 33.737917, -111.834715 33.738254, -111.83436 33.738258, -111.83415 33.73827, -111.833922 33.738295, -111.833706 33.738331, -111.83349 33.738379, -111.833286 33.738438, -111.83309 33.738508, -111.832913 33.738585, -111.832702 33.738691, -111.832445 33.738839, -111.832384 33.738761, -111.832321 33.738666, -111.832287 33.738599, -111.832248 33.738495, -111.832204 33.738425, -111.832146 33.738361, -111.832076 33.738309, -111.832002 33.738273, -111.831907 33.738244, -111.831805 33.73823, -111.8317 33.738232, -111.831611 33.738248, -111.831538 33.738273, -111.831407 33.738341, -111.83134 33.738391, -111.831275 33.73846, -111.831233 33.738524, -111.831206 33.738594, -111.831196 33.738658, -111.831199 33.738715, -111.831221 33.738801, -111.831269 33.738907, -111.831328 33.738996, -111.831399 33.739077, -111.831837 33.739662, -111.831901 33.739753, -111.832014 33.739924, -111.832063 33.740031, -111.832087 33.740112, -111.832108 33.740209, -111.832123 33.74034, -111.832132 33.740503, -111.832303 33.740491, -111.8325 33.740495, -111.832651 33.74051, -111.833197 33.740568, -111.833255 33.740573, -111.833316 33.740564, -111.833361 33.740538, -111.833382 33.740519, -111.833404 33.740486, -111.833415 33.740449, -111.833414 33.740405, -111.833421 33.740367, -111.833431 33.740249, -111.83401 33.740245, -111.83429 33.740246, -111.834711 33.740248, -111.834714 33.741238, -111.834668 33.741325, -111.834636 33.74142, -111.83463 33.741453, -111.834622 33.741518, -111.834625 33.741608, -111.834646 33.741843, -111.834666 33.741966, -111.834666 33.742067, -111.834652 33.74226, -111.834652 33.742295, -111.83466 33.742412, -111.834739 33.742923, -111.834751 33.743069, -111.834747 33.743275, -111.834732 33.743559, -111.83469 33.744038, -111.834646 33.74436, -111.834637 33.744456, -111.83462 33.744724, -111.834628 33.744939, -111.834657 33.745272, -111.834692 33.745556, -111.834739 33.745752, -111.834773 33.74586, -111.834794 33.745976, -111.834786 33.746083, -111.834764 33.746187, -111.834711 33.746378, -111.834678 33.746531, -111.834666 33.74668, -111.834667 33.746948, -111.834664 33.746986, -111.834658 33.747065, -111.834619 33.747344, -111.8346 33.747641, -111.834599 33.747695, -111.83463 33.748033, -111.83465 33.748174, -111.834732 33.748557, -111.834765 33.748667, -111.834786 33.748793, -111.834788 33.74892, -111.834773 33.749073, -111.83475 33.749163, -111.834741 33.749264, -111.834692 33.749538, -111.834655 33.74967, -111.834586 33.749918, -111.83454 33.750062, -111.83452 33.750195, -111.834521 33.750342, -111.834543 33.750596, -111.834567 33.750731, -111.834631 33.750986, -111.834686 33.751192, -111.834757 33.751514, -111.834766 33.75163, -111.834801 33.751848, -111.834801 33.751931, -111.834801 33.752034, -111.834786 33.752112, -111.834777 33.75221, -111.834718 33.752594, -111.834656 33.752926, -111.834603 33.754048, -111.834608 33.754149, -111.834616 33.754235, -111.834598 33.754446, -111.834577 33.754577, -111.834545 33.754669, -111.834516 33.75473, -111.834419 33.755007, -111.834395 33.755064, -111.834378 33.755172, -111.834386 33.755298, -111.834408 33.755385, -111.834449 33.75548, -111.834518 33.755594, -111.834561 33.755639, -111.834622 33.755677, -111.834706 33.755704, -111.834811 33.755726, -111.834942 33.755741, -111.835362 33.75575, -111.835993 33.755752, -111.836025 33.755749, -111.836114 33.755733, -111.836154 33.755716, -111.83621 33.75569, -111.83627 33.755678, -111.836342 33.75568, -111.836393 33.755689, -111.836469 33.755715, -111.836585 33.75577, -111.836655 33.755786, -111.836726 33.755788, -111.836803 33.755777, -111.836865 33.75575, -111.836931 33.755729, -111.837063 33.755719, -111.83719 33.755723, -111.837312 33.755728, -111.837926 33.755757, -111.838166 33.755744, -111.838378 33.755743, -111.838942 33.755754, -111.838971 33.755764, -111.839004 33.755784, -111.839042 33.755822, -111.839072 33.755838, -111.83914 33.755844, -111.839218 33.755836, -111.839464 33.755827, -111.839645 33.755832, -111.839693 33.75585, -111.839756 33.755896, -111.839793 33.755901, -111.839823 33.755894, -111.839879 33.755866, -111.83995 33.75584, -111.840036 33.75582, -111.840118 33.755816, -111.840284 33.755844, -111.840371 33.755844, -111.840441 33.755832, -111.840518 33.755831, -111.840651 33.755843, -111.840752 33.755865, -111.840953 33.75597, -111.840975 33.755974, -111.841016 33.755974, -111.841074 33.755948, -111.841157 33.755845, -111.841188 33.755829, -111.841258 33.755828, -111.842082 33.755823, -111.842225 33.755822, -111.843273 33.755828, -111.843743 33.755822, -111.843917 33.755833, -111.84394 33.755619, -111.843963 33.75548, -111.844003 33.755161, -111.844016 33.754973, -111.8441 33.754071, -111.844115 33.753918, -111.844173 33.753923, -111.844357 33.75393, -111.84476 33.753914, -111.844895 33.753919, -111.845018 33.753949, -111.845117 33.753987, -111.845183 33.753991, -111.845308 33.754003, -111.84546 33.754045, -111.845561 33.754083, -111.845656 33.754127, -111.845749 33.754182, -111.845826 33.754235, -111.845925 33.754323, -111.846268 33.754725, -111.846374 33.75483, -111.846493 33.754923, -111.846585 33.754981, -111.846716 33.755043, -111.846838 33.755088, -111.846948 33.755117, -111.847065 33.755135, -111.847207 33.755141, -111.847766 33.755122, -111.848007 33.755117, -111.848297 33.755125, -111.848728 33.755156, -111.848902 33.755159, -111.849039 33.755154, -111.849371 33.755125, -111.849639 33.755107, -111.849955 33.755096, -111.850297 33.755097, -111.850663 33.755111, -111.851304 33.755159, -111.851531 33.755166, -111.851914 33.755174, -111.85237 33.755168, -111.852534 33.755176, -111.852711 33.755199, -111.852875 33.755229, -111.852931 33.755241, -111.853172 33.755309, -111.853621 33.755482, -111.853761 33.755526, -111.854056 33.755602, -111.854231 33.755636, -111.85446 33.755667, -111.854683 33.755684, -111.855158 33.755688, -111.855682 33.755679, -111.856419 33.755678, -111.856908 33.75569, -111.857476 33.755716, -111.85801 33.755729, -111.858419 33.755729, -111.859013 33.755714, -111.859231 33.755701, -111.859649 33.755686, -111.859966 33.755685, -111.860262 33.755692, -111.860371 33.755697, -111.860702 33.755719, -111.860827 33.755721, -111.861182 33.755719, -111.861509 33.755702, -111.86174 33.755691, -111.862956 33.755688, -111.863059 33.755684, -111.863199 33.755667, -111.863328 33.75564, -111.863474 33.755595, -111.8636 33.75554, -111.863708 33.755479, -111.863752 33.755452, -111.863832 33.755394, -111.863946 33.755297, -111.864043 33.755192, -111.864097 33.755118, -111.864164 33.755, -111.864211 33.754883, -111.864235 33.754791, -111.86426 33.754611, -111.864337 33.753487, -111.864368 33.753045, -111.86438 33.75291, -111.864405 33.752801, -111.864455 33.75267, -111.864522 33.752545, -111.864587 33.752454, -111.864612 33.752424, -111.864695 33.752339, -111.864789 33.752262, -111.864898 33.752187, -111.865016 33.752121, -111.865138 33.752066, -111.865273 33.752019, -111.865429 33.751985, -111.865553 33.751969, -111.865679 33.751964, -111.867817 33.752057, -111.868189 33.752059, -111.868392 33.75204, -111.868566 33.752012, -111.868752 33.751968, -111.868956 33.751899, -111.869141 33.751824, -111.869439 33.75168, -111.869822 33.751588, -111.869899 33.751572, -111.870054 33.751552, -111.870176 33.751548, -111.870296 33.751553, -111.870452 33.751574, -111.870599 33.751593, -111.870682 33.751592, -111.870759 33.751578, -111.870827 33.751555, -111.870897 33.751517, -111.87099 33.751438, -111.87106 33.751364, -111.871134 33.75133, -111.87121 33.751312, -111.871325 33.751308, -111.871383 33.7513, -111.871446 33.751279, -111.8716 33.751193, -111.871761 33.751113, -111.87182 33.751091, -111.871945 33.751056, -111.872018 33.751222, -111.87207 33.751354, -111.872104 33.751503, -111.872112 33.751593, -111.872215 33.751591, -111.872472 33.751608, -111.872613 33.751603, -111.872737 33.751586, -111.872843 33.751569, -111.872974 33.751566, -111.873163 33.751579, -111.873217 33.751573, -111.873298 33.751553, -111.873374 33.751517, -111.873428 33.751479, -111.873477 33.751431, -111.873521 33.751364, -111.873542 33.751307, -111.87355 33.751237, -111.873541 33.751171, -111.873519 33.751085, -111.87347 33.750962, -111.873534 33.750947, -111.873661 33.750905, -111.873765 33.750858, -111.874045 33.750699, -111.874209 33.750621, -111.874411 33.750541, -111.874534 33.750504, -111.87483 33.750425, -111.874931 33.750391, -111.87512 33.750318, -111.875271 33.750248, -111.875515 33.750114, -111.87567 33.750036, -111.87583 33.749971, -111.875996 33.749918, -111.876509 33.749792, -111.8768 33.749727, -111.876941 33.749683, -111.877056 33.749636, -111.877175 33.749577, -111.877279 33.749512, -111.877377 33.749436, -111.877465 33.749352, -111.877768 33.749016, -111.87798 33.74881, -111.878079 33.748739, -111.878213 33.748662, -111.878331 33.74861, -111.878463 33.74856, -111.8785 33.748532, -111.878524 33.748502, -111.878538 33.74847, -111.878547 33.748394, -111.878573 33.748271, -111.878627 33.748122, -111.87869 33.748005, -111.878761 33.747906, -111.879057 33.747568, -111.879216 33.747386, -111.879393 33.747194, -111.879487 33.7471, -111.879671 33.746933, -111.879945 33.746708, -111.880102 33.746567, -111.880188 33.74647, -111.880296 33.746327, -111.880417 33.746127, -111.880684 33.745688, -111.880861 33.745376, -111.880887 33.745309, -111.880914 33.745201, -111.880926 33.745091, -111.880927 33.745059, -111.880918 33.744968, -111.88088 33.74481, -111.880833 33.744678, -111.880803 33.744568, -111.880786 33.744441, -111.880797 33.744348, -111.880835 33.744218, -111.880883 33.74412, -111.881 33.743949, -111.881063 33.743841, -111.881124 33.743703, -111.881144 33.743625, -111.881151 33.743536, -111.881144 33.743443, -111.881121 33.743355, -111.881094 33.74329, -111.881022 33.743164, -111.88097 33.743067, -111.880824 33.742772, -111.880751 33.742577, -111.880696 33.742379, -111.880685 33.742354, -111.88052 33.74168, -111.880493 33.741597, -111.881217 33.741504, -111.881539 33.741467, -111.881866 33.741441, -111.882371 33.741422, -111.88495 33.741422, -111.885505 33.741422, -111.887103 33.741423, -111.887531 33.74141, -111.887864 33.74139, -111.888821 33.741302, -111.889392 33.741259, -111.889853 33.74124, -111.890003 33.741238, -111.890304 33.741234, -111.890732 33.741234, -111.891157 33.741249, -111.894994 33.741247, -111.897037 33.741246, -111.898014 33.741244, -111.899793 33.741239, -111.900507 33.741239, -111.900757 33.741235, -111.90084 33.741236, -111.90115 33.74124, -111.904214 33.741238, -111.908195 33.741238, -111.908491 33.741238, -111.908476 33.742214, -111.908473 33.742489, -111.908495 33.742951, -111.908504 33.743494, -111.908504 33.743631, -111.90849 33.743929, -111.908492 33.744255, -111.908482 33.744659, -111.908492 33.74482, -111.908501 33.744892, -111.908583 33.74488, -111.908767 33.744872, -111.90913 33.744879, -111.909478 33.744899, -111.909648 33.744897, -111.909697 33.744894, -111.909896 33.744893, -111.910078 33.744904, -111.910267 33.744901, -111.910452 33.744883, -111.910654 33.744867, -111.910658 33.745664, -111.910648 33.746332, -111.910658 33.746461, -111.910665 33.746543, -111.91068 33.746695, -111.91069 33.74698, -111.910687 33.747202, -111.910654 33.747496, -111.910633 33.747906, -111.910612 33.748109, -111.910605 33.748305, -111.910616 33.748381, -111.910641 33.748457, -111.910666 33.748504, -111.911388 33.748488, -111.911495 33.748488, -111.912641 33.748483, -111.912911 33.748488, -111.912897 33.748576, -111.91289 33.748724, -111.912899 33.749063, -111.912894 33.749435, -111.9129 33.749738, -111.91289 33.75031, -111.91289 33.750359, -111.912897 33.750809, -111.912884 33.751393, -111.912896 33.751936, -111.912896 33.752095, -111.912896 33.75215, -111.912894 33.752517, -111.912896 33.752829, -111.91289 33.753305, -111.912883 33.753406, -111.912883 33.753861, -111.912886 33.754053, -111.912892 33.754497, -111.912896 33.754755, -111.912895 33.75524, -111.912913 33.75582, -111.912871 33.75582, -111.910937 33.755819, -111.910014 33.755819, -111.908486 33.755821, -111.907524 33.755827, -111.906618 33.755824, -111.904211 33.755831, -111.900971 33.755835, -111.899847 33.755836, -111.896168 33.755824, -111.895852 33.755823, -111.895544 33.75582, -111.891185 33.755811, -111.891215 33.75734, -111.891222 33.759384, -111.891228 33.760733, -111.891241 33.764034, -111.89124 33.764337, -111.891238 33.765028, -111.891242 33.765855, -111.891256 33.768936, -111.891257 33.769315, -111.892492 33.769111, -111.892598 33.769102, -111.892941 33.769065, -111.893006 33.769047, -111.893088 33.76901, -111.893149 33.768969, -111.893387 33.768733, -111.893417 33.768752, -111.893565 33.768834, -111.893815 33.768945, -111.893891 33.768972, -111.893986 33.768996, -111.894359 33.769044, -111.894421 33.769053, -111.894559 33.769062, -111.894706 33.769057, -111.894825 33.769043, -111.894995 33.769027, -111.895271 33.769018, -111.895486 33.769026, -111.895696 33.769049, -111.896231 33.769143, -111.896176 33.769295, -111.896149 33.769396, -111.896124 33.769552, -111.896116 33.769697, -111.896122 33.770151, -111.896442 33.770152, -111.897286 33.770167, -111.898382 33.770204, -111.899864 33.770253, -111.900062 33.770257, -111.900709 33.770264, -111.900842 33.770265, -111.901427 33.77026, -111.904154 33.770257, -111.904261 33.770257, -111.905069 33.770257, -111.905681 33.770254, -111.907089 33.770248, -111.908284 33.770235, -111.908366 33.770235, -111.90851 33.770236, -111.910376 33.770245, -111.913587 33.770256, -111.914773 33.770252, -111.917192 33.770251, -111.917635 33.770252, -111.919004 33.770246, -111.919447 33.770244, -111.920839 33.770245, -111.921251 33.770253, -111.922217 33.770262, -111.923653 33.770257, -111.924889 33.770261, -111.925881 33.77026, -111.925882 33.770798, -111.925879 33.771083, -111.92587 33.771901, -111.925855 33.772783, -111.925855 33.773675, -111.925855 33.773864, -111.925855 33.773935, -111.92586 33.774559, -111.925846 33.775745, -111.925835 33.776508, -111.925832 33.777522, -111.92583 33.77875, -111.925824 33.780474, -111.925825 33.781325, -111.925832 33.781852, -111.925848 33.783083, -111.92585 33.783217, -111.925854 33.783394, -111.925867 33.78392, -111.925867 33.78406, -111.925865 33.784818, -111.923762 33.784837, -111.923545 33.784838, -111.923419 33.784857, -111.923324 33.784884, -111.923259 33.784909, -111.923165 33.784953, -111.923079 33.784989, -111.922982 33.785045, -111.922916 33.785094, -111.922814 33.785231, -111.922669 33.785455, -111.922518 33.785666, -111.922439 33.785762, -111.922314 33.785874, -111.922061 33.786072, -111.921975 33.786136, -111.921831 33.786234, -111.921672 33.786357, -111.921523 33.786489, -111.921441 33.786576, -111.921406 33.786623, -111.921355 33.786713, -111.921321 33.786806, -111.921168 33.787595, -111.921123 33.787789, -111.921088 33.787884, -111.921074 33.787921, -111.921064 33.787942, -111.921004 33.788053, -111.920933 33.788156, -111.92083 33.78828, -111.920727 33.788381, -111.920619 33.788468, -111.920502 33.788546, -111.920368 33.788618, -111.920246 33.788671, -111.919772 33.788856, -111.919525 33.788953, -111.919401 33.789017, -111.919278 33.789101, -111.919186 33.789181, -111.919105 33.789269, -111.919035 33.789367, -111.918967 33.789483, -111.918894 33.789589, -111.918816 33.789678, -111.918727 33.789759, -111.918482 33.789932, -111.918379 33.790004, -111.918307 33.790069, -111.918244 33.790138, -111.918169 33.790245, -111.918138 33.7903, -111.918086 33.79042, -111.918052 33.790548, -111.918043 33.790618, -111.918039 33.790644, -111.918037 33.790742, -111.918042 33.790809, -111.917761 33.790836, -111.917391 33.790859, -111.91726 33.790853, -111.917004 33.790807, -111.916847 33.79079, -111.916684 33.790784, -111.916561 33.790788, -111.916398 33.790804, -111.916087 33.790852, -111.915962 33.790861, -111.914485 33.790869, -111.914386 33.790855, -111.914292 33.790824, -111.91426 33.790808, -111.914201 33.790771, -111.914124 33.790703, -111.914076 33.790642, -111.914012 33.790521, -111.913958 33.790443, -111.913917 33.790405, -111.913858 33.790365, -111.913783 33.790329, -111.913485 33.790243, -111.91334 33.790193, -111.91286 33.78998, -111.912388 33.789767, -111.912276 33.789732, -111.912131 33.789708, -111.912032 33.789704, -111.911845 33.789707, -111.911667 33.789702, -111.911537 33.789683, -111.911449 33.789664, -111.911293 33.789613, -111.911193 33.789562, -111.911098 33.789504, -111.911012 33.789443, -111.91072 33.78925, -111.910629 33.789205, -111.910534 33.789172, -111.910435 33.789149, -111.910311 33.789138, -111.91026 33.789138, -111.910138 33.789154, -111.910057 33.789175, -111.909951 33.789215, -111.90988 33.789252, -111.909783 33.789318, -111.909717 33.789381, -111.909702 33.789395, -111.90963 33.789488, -111.909318 33.790112, -111.909275 33.79024, -111.90925 33.790363, -111.909245 33.790545, -111.90926 33.791164, -111.909272 33.79121, -111.909311 33.791277, -111.909326 33.791293, -111.909342 33.791311, -111.909406 33.791355, -111.909467 33.791382, -111.909529 33.791395, -111.909597 33.791398, -111.909795 33.791382, -111.909938 33.791385, -111.910056 33.791404, -111.910299 33.79146, -111.910457 33.791508, -111.910616 33.791545, -111.910759 33.791565, -111.911136 33.791586, -111.911074 33.792167, -111.909099 33.792041, -111.908603 33.792019, -111.908517 33.792019, -111.908525 33.791771, -111.908518 33.790526, -111.90853 33.790333, -111.908558 33.790162, -111.908571 33.790025, -111.908569 33.78989, -111.908567 33.789416, -111.908564 33.788642, -111.908397 33.788572, -111.908302 33.78855, -111.908241 33.788546, -111.908225 33.788544, -111.908111 33.78854, -111.907995 33.78852, -111.907903 33.788492, -111.907781 33.788442, -111.907686 33.788415, -111.907568 33.788397, -111.907454 33.788395, -111.906904 33.788405, -111.906649 33.788388, -111.906468 33.788368, -111.906366 33.788355, -111.90635 33.788335, -111.906334 33.788301, -111.90633 33.788256, -111.906358 33.788147, -111.906377 33.788019, -111.906372 33.787776, -111.906395 33.787402, -111.906395 33.787178, -111.90638 33.786979, -111.906373 33.786807, -111.906369 33.786519, -111.906369 33.786478, -111.906362 33.786422, -111.906353 33.786259, -111.906359 33.785975, -111.90635 33.785867, -111.906325 33.785752, -111.906319 33.785708, -111.906322 33.785616, -111.906354 33.785498, -111.906366 33.785424, -111.906367 33.785328, -111.906356 33.785273, -111.906347 33.785198, -111.906354 33.785108, -111.906383 33.784995, -111.906381 33.784943, -111.906367 33.784899, -111.906337 33.784859, -111.9063 33.784831, -111.906254 33.784812, -111.906198 33.784804, -111.906056 33.784801, -111.905972 33.784809, -111.905808 33.784832, -111.905627 33.784844, -111.905429 33.784841, -111.905182 33.784817, -111.905029 33.78481, -111.904851 33.784811, -111.904664 33.784813, -111.9044 33.784819, -111.904245 33.784817, -111.904217 33.784908, -111.904209 33.78498, -111.904214 33.785224, -111.904223 33.785358, -111.904224 33.785556, -111.904238 33.785703, -111.904258 33.785813, -111.904272 33.785898, -111.904269 33.785957, -111.904239 33.786081, -111.904226 33.786505, -111.904203 33.786592, -111.904163 33.786675, -111.904153 33.786725, -111.904141 33.786977, -111.904159 33.787881, -111.904163 33.787931, -111.904176 33.788, -111.904173 33.788074, -111.904158 33.788134, -111.904148 33.788167, -111.904146 33.788208, -111.90416 33.788259, -111.904185 33.788297, -111.904228 33.788335, -111.904249 33.788347, -111.904113 33.788376, -111.903895 33.788461, -111.903798 33.788484, -111.90371 33.788495, -111.903609 33.788495, -111.903506 33.788481, -111.903303 33.788429, -111.903136 33.7884, -111.903017 33.788386, -111.902808 33.788377, -111.902586 33.788384, -111.902495 33.788391, -111.90237 33.788408, -111.902219 33.788411, -111.902146 33.788406, -111.902023 33.788389, -111.901988 33.788386, -111.901831 33.788374, -111.901754 33.788379, -111.901676 33.788396, -111.901571 33.788425, -111.901464 33.788443, -111.901349 33.788447, -111.900907 33.788414, -111.900534 33.788404, -111.899935 33.788422, -111.899825 33.788423, -111.899828 33.788975, -111.899831 33.789443, -111.899814 33.789579, -111.899812 33.789595, -111.899787 33.78973, -111.899781 33.789854, -111.899793 33.789988, -111.899824 33.790119, -111.899853 33.790213, -111.899817 33.790318, -111.899833 33.791338, -111.899841 33.79186, -111.899868 33.791919, -111.899871 33.792059, -111.899779 33.792083, -111.899684 33.792091, -111.899168 33.792072, -111.898667 33.792071, -111.898223 33.792086, -111.897698 33.792096, -111.896745 33.792107, -111.895578 33.792109, -111.895113 33.792108, -111.893479 33.792106, -111.892153 33.792107, -111.89194 33.792096, -111.891232 33.792093, -111.891232 33.792044, -111.890663 33.792052, -111.890015 33.792045, -111.889786 33.792036, -111.88956 33.792018, -111.889295 33.791987, -111.889 33.791938, -111.888635 33.79186, -111.888264 33.791763, -111.888112 33.791721, -111.887805 33.791654, -111.887547 33.791611, -111.887367 33.791589, -111.887229 33.791572, -111.88702 33.791553, -111.886669 33.791537, -111.886391 33.791537, -111.886197 33.791544, -111.885827 33.79157, -111.885406 33.791614, -111.885229 33.791663, -111.884933 33.79173, -111.884553 33.791786, -111.884211 33.791823, -111.883862 33.791848, -111.883706 33.791855, -111.880496 33.79186, -111.88032 33.791872, -111.880118 33.791898, -111.879903 33.791939, -111.879671 33.792, -111.879513 33.792053, -111.879258 33.792148, -111.87902 33.792223, -111.878829 33.792342, -111.878684 33.792447, -111.87855 33.792559, -111.878455 33.792647, -111.878193 33.792911, -111.878002 33.793125, -111.87786 33.793304, -111.877765 33.793431, -111.877637 33.793697, -111.877609 33.793767, -111.877259 33.793672, -111.877147 33.793653, -111.876937 33.793588, -111.876846 33.793519, -111.876809 33.793483, -111.876767 33.793441, -111.876299 33.79301, -111.876222 33.792961, -111.876147 33.792929, -111.876054 33.792905, -111.875977 33.792897, -111.875905 33.792898, -111.87549 33.792885, -111.875344 33.792868, -111.875243 33.792845, -111.874843 33.792748, -111.874667 33.792722, -111.874593 33.792715, -111.874395 33.792713, -111.874252 33.792721, -111.874233 33.792724, -111.874097 33.792742, -111.873929 33.792777, -111.873819 33.792807, -111.872937 33.79307, -111.872836 33.793095, -111.872757 33.793107, -111.872617 33.793112, -111.872485 33.793099, -111.872403 33.793079, -111.872195 33.793011, -111.87201 33.792943, -111.871789 33.792874, -111.871551 33.79281, -111.871075 33.792695, -111.870953 33.792681, -111.870874 33.792679, -111.87079 33.792685, -111.870724 33.792694, -111.870659 33.792708, -111.870594 33.792727, -111.870494 33.792766, -111.870402 33.792814, -111.870342 33.792853, -111.870236 33.792944, -111.870171 33.793018, -111.870142 33.793057, -111.870115 33.79311, -111.870074 33.793206, -111.870049 33.793307, -111.869964 33.793905, -111.869933 33.794026, -111.869891 33.794137, -111.869838 33.794245, -111.869756 33.794373, -111.869674 33.794475, -111.869598 33.794554, -111.869504 33.794637, -111.86939 33.794718, -111.869281 33.794783, -111.868806 33.795006, -111.868444 33.795167, -111.868274 33.795228, -111.86737 33.795476, -111.867272 33.795505, -111.867179 33.795548, -111.867149 33.795568, -111.867103 33.795598, -111.867028 33.795668, -111.866696 33.796112, -111.866492 33.796391, -111.866425 33.796462, -111.866322 33.796543, -111.866215 33.796606, -111.866098 33.796662, -111.865971 33.796742, -111.86588 33.796815, -111.865811 33.796891, -111.865747 33.796974, -111.865689 33.797077, -111.865662 33.79714, -111.865646 33.797206, -111.865623 33.797402, -111.86563 33.79776, -111.865625 33.797957, -111.865635 33.798191, -111.865695 33.79873, -111.865712 33.798839, -111.865713 33.799008, -111.865707 33.799157, -111.865688 33.799279, -111.865668 33.799448, -111.865644 33.799592, -111.865637 33.799819, -111.865644 33.801202, -111.865644 33.801285, -111.865664 33.801651, -111.865672 33.801727, -111.865687 33.802102, -111.865688 33.802217, -111.865697 33.803055, -111.865697 33.803102, -111.865717 33.803168, -111.865748 33.803214, -111.86579 33.803254, -111.865845 33.803288, -111.865907 33.803312, -111.865928 33.803318, -111.865953 33.803323, -111.866048 33.803333, -111.866204 33.803357, -111.866383 33.803402, -111.866556 33.803464, -111.866664 33.803513, -111.866782 33.803579, -111.866906 33.803659, -111.866992 33.803731, -111.867171 33.803907, -111.867502 33.804251, -111.867863 33.804628, -111.867985 33.80474, -111.868069 33.804803, -111.868197 33.80488, -111.868348 33.804947, -111.868647 33.80508, -111.869077 33.805253, -111.869244 33.805317, -111.869388 33.805379, -111.869554 33.80546, -111.869642 33.80551, -111.869702 33.805549, -111.869858 33.805633, -111.869997 33.805694, -111.87012 33.805735, -111.870316 33.805783, -111.870431 33.805801, -111.870797 33.805837, -111.870971 33.805784, -111.871019 33.80576, -111.871167 33.805656, -111.871206 33.80564, -111.871484 33.80555, -111.871546 33.805641, -111.871573 33.805679, -111.87168 33.805818, -111.871793 33.805937, -111.871983 33.806114, -111.872161 33.806259, -111.872244 33.806319, -111.872307 33.806364, -111.872727 33.806634, -111.873769 33.807279, -111.874139 33.807526, -111.874311 33.807667, -111.874485 33.807829, -111.874643 33.807996, -111.87477 33.808146, -111.874906 33.80833, -111.87501 33.808493, -111.87504 33.808545, -111.875212 33.808918, -111.875377 33.809314, -111.875492 33.809549, -111.875577 33.809702, -111.875668 33.80983, -111.875786 33.809969, -111.875906 33.810089, -111.876017 33.810185, -111.876149 33.810286, -111.876323 33.810398, -111.876483 33.810484, -111.876689 33.810575, -111.876876 33.810643, -111.877088 33.810699, -111.877324 33.81074, -111.87755 33.810766, -111.877742 33.810778, -111.877904 33.81078, -111.877951 33.81078, -111.878697 33.81081, -111.878892 33.810833, -111.879115 33.810876, -111.879283 33.810922, -111.879406 33.810966, -111.879591 33.811048, -111.879754 33.811131, -111.879909 33.811224, -111.880037 33.811317, -111.880167 33.811428, -111.880285 33.811549, -111.880395 33.811679, -111.880497 33.811821, -111.880584 33.811969, -111.880641 33.812088, -111.880703 33.812287, -111.880743 33.81246, -111.880757 33.812546, -111.880767 33.812865, -111.880774 33.813352, -111.880759 33.813889, -111.878887 33.813885, -111.878238 33.813887, -111.877544 33.813894, -111.876903 33.813888, -111.875633 33.813892, -111.875245 33.813864, -111.874828 33.813856, -111.874617 33.813856, -111.874134 33.813855, -111.874017 33.813859, -111.873844 33.813865, -111.873665 33.813872, -111.873284 33.813877, -111.872688 33.813885, -111.871918 33.813906, -111.871734 33.813904, -111.87074 33.813897, -111.867732 33.813895, -111.866865 33.813897, -111.865848 33.813899, -111.864336 33.813911, -111.863298 33.813902, -111.862899 33.813906, -111.861472 33.813918, -111.860005 33.813913, -111.859045 33.813919, -111.858753 33.813925, -111.857668 33.813929, -111.856938 33.813932, -111.85682 33.813932, -111.854204 33.813917, -111.852124 33.813942, -111.847817 33.813922, -111.844963 33.813871, -111.842488 33.813827, -111.842472 33.814089, -111.842464 33.814518, -111.842446 33.814607, -111.842409 33.814698, -111.842352 33.814782, -111.84228 33.814854, -111.842149 33.814963, -111.841752 33.815253, -111.841055 33.815736, -111.840969 33.815808, -111.840892 33.815886, -111.840689 33.816118, -111.840614 33.816242, -111.840541 33.816386, -111.840507 33.816488, -111.840489 33.816592, -111.840487 33.816697, -111.840483 33.81678, -111.840462 33.816855, -111.84042 33.816959, -111.840409 33.817027, -111.840412 33.817104, -111.840427 33.817192, -111.840423 33.817275, -111.840404 33.817352, -111.840356 33.817468, -111.840122 33.817931, -111.839997 33.818172, -111.839973 33.818247, -111.839965 33.818328, -111.839973 33.818475, -111.840037 33.818786, -111.840073 33.818864, -111.840105 33.818909, -111.840198 33.819024, -111.840218 33.819073, -111.840228 33.819199, -111.840257 33.819281, -111.840305 33.819356, -111.840361 33.819428, -111.840372 33.819476, -111.840369 33.819539, -111.840307 33.819764, -111.840271 33.819863, -111.84021 33.81998, -111.840007 33.820285, -111.839956 33.820389, -111.8399 33.820459, -111.839845 33.820555, -111.839813 33.820636, -111.839798 33.820709, -111.839798 33.820795, -111.839808 33.820931, -111.839804 33.821158, -111.839799 33.821759, -111.839813 33.82184, -111.839853 33.821959, -111.839905 33.822064, -111.840048 33.822325, -111.840099 33.822441, -111.840123 33.822516, -111.840166 33.822691, -111.840172 33.82276, -111.840169 33.822817, -111.840119 33.82322, -111.840094 33.823284, -111.84003 33.823387, -111.840008 33.823437, -111.839916 33.823686, -111.839864 33.823808, -111.839764 33.824011, -111.839721 33.824121, -111.839706 33.824198, -111.839704 33.824295, -111.839718 33.824427, -111.839738 33.824497, -111.839755 33.824555, -111.839791 33.824639, -111.840133 33.825347, -111.84021 33.825501, -111.840246 33.825616, -111.84026 33.8257, -111.840265 33.825796, -111.840252 33.825918, -111.840233 33.825996, -111.840022 33.826706, -111.839976 33.826883, -111.83995 33.827062, -111.839943 33.827194, -111.839944 33.827548, -111.839945 33.829198, -111.839948 33.829304, -111.839968 33.829409, -111.84 33.829501, -111.840088 33.829705, -111.840119 33.829818, -111.840131 33.829903, -111.840142 33.830174, -111.84013 33.830352, -111.840093 33.830678, -111.840031 33.831123, -111.840008 33.831263, -111.840003 33.831474, -111.839998 33.832205, -111.840013 33.832332, -111.840052 33.832491, -111.840113 33.832662, -111.840182 33.832814, -111.840526 33.833449, -111.840585 33.833595, -111.840612 33.833685, -111.840618 33.833704, -111.840639 33.833822, -111.840642 33.833929, -111.840627 33.834048, -111.840592 33.834184, -111.840544 33.834304, -111.84047 33.834436, -111.840389 33.83454, -111.84012 33.834811, -111.840066 33.834877, -111.84 33.83498, -111.839951 33.835087, -111.839921 33.835187, -111.839904 33.835308, -111.839899 33.835671, -111.839894 33.836383, -111.839905 33.836509, -111.839939 33.83674, -111.839943 33.836897, -111.839932 33.837022, -111.83992 33.837106, -111.839893 33.837271, -111.839872 33.837403, -111.839874 33.837483, -111.839887 33.837549, -111.839947 33.837779, -111.839959 33.837888, -111.839955 33.837983, -111.83994 33.83807, -111.839917 33.83819, -111.839914 33.838292, -111.83992 33.838358, -111.839961 33.838598, -111.839965 33.838708, -111.839954 33.838803, -111.839925 33.838915, -111.839898 33.839024, -111.839885 33.839135, -111.839884 33.839234, -111.839907 33.839728, -111.839931 33.840511, -111.839923 33.840603, -111.839896 33.840734, -111.839869 33.84083, -111.839845 33.840914, -111.839823 33.841036, -111.839819 33.84113, -111.839831 33.841244, -111.839881 33.841499, -111.839887 33.84154, -111.839902 33.841706, -111.839901 33.841873, -111.839867 33.842255, -111.839838 33.842457, -111.839841 33.842559, -111.839859 33.842643, -111.839888 33.842698, -111.839902 33.842715, -111.839953 33.842757, -111.839986 33.842772, -111.840063 33.842788, -111.840214 33.842799, -111.840301 33.842795, -111.840974 33.842809, -111.841074 33.842817, -111.841234 33.842821, -111.841709 33.842804, -111.841876 33.842786, -111.842081 33.842774, -111.842236 33.842777, -111.842418 33.84279, -111.842505 33.842802, -111.842668 33.842846, -111.843144 33.842181, -111.84331 33.841972, -111.843398 33.841874, -111.843507 33.841771, -111.843679 33.841632, -111.843892 33.841485, -111.844128 33.841347, -111.844231 33.841293, -111.844775 33.841022, -111.845567 33.840653, -111.845756 33.840571, -111.846442 33.840274, -111.846653 33.840186, -111.847171 33.839962, -111.847628 33.83975, -111.847874 33.839622, -111.848543 33.839226, -111.850163 33.838284, -111.85031 33.838188, -111.850581 33.837981, -111.850904 33.837703, -111.85155 33.837133, -111.851982 33.836762, -111.852097 33.836657, -111.852745 33.837087, -111.852537 33.837322, -111.852582 33.837345, -111.853091 33.837675, -111.853128 33.837689, -111.853176 33.837695, -111.853224 33.837688, -111.85327 33.837668, -111.85329 33.837654, -111.853418 33.837548, -111.853667 33.837732, -111.853766 33.83782, -111.853854 33.837917, -111.85393 33.838022, -111.853943 33.838043, -111.854157 33.838384, -111.854296 33.8386, -111.854386 33.838739, -111.854775 33.839316, -111.85481 33.839364, -111.854845 33.839443, -111.854856 33.839467, -111.85489 33.839518, -111.855048 33.839722, -111.855137 33.839854, -111.855267 33.840048, -111.855345 33.840166, -111.85548 33.840346, -111.855538 33.840444, -111.855772 33.840865, -111.855992 33.841289, -111.856074 33.841435, -111.856147 33.841551, -111.856264 33.84169, -111.856458 33.84186, -111.856667 33.841639, -111.856712 33.841609, -111.856777 33.841584, -111.856839 33.841575, -111.856902 33.841577, -111.856965 33.841591, -111.857119 33.841656, -111.857033 33.84164, -111.856962 33.841645, -111.856896 33.841664, -111.856832 33.841699, -111.856789 33.841739, -111.856726 33.84183, -111.8566 33.841984, -111.856948 33.842278, -111.856991 33.842308, -111.857066 33.842361, -111.857242 33.842455, -111.857337 33.842497, -111.85746 33.842539, -111.857572 33.842566, -111.857736 33.842589, -111.857889 33.842595, -111.858034 33.842588, -111.859043 33.842452, -111.859223 33.842435, -111.859394 33.842413, -111.859954 33.842329, -111.860108 33.842315, -111.860253 33.842315, -111.860412 33.842331, -111.860646 33.842373, -111.860652 33.842339, -111.860679 33.842265, -111.860716 33.842208, -111.86079 33.842131, -111.860842 33.842093, -111.860916 33.842054, -111.861041 33.842015, -111.861237 33.841972, -111.861312 33.841963, -111.861513 33.841945, -111.861635 33.841948, -111.861717 33.841957, -111.861859 33.841877, -111.86203 33.841749, -111.862201 33.841586, -111.86234 33.841441, -111.862501 33.841241, -111.862747 33.841059, -111.862831 33.84077, -111.862981 33.840651, -111.863433 33.840432, -111.863723 33.840276, -111.863959 33.840112, -111.864163 33.839949, -111.864275 33.839846, -111.864269 33.83983, -111.864189 33.839491, -111.864188 33.839462, -111.864196 33.83942, -111.864217 33.839382, -111.864325 33.839265, -111.86441 33.839146, -111.86463 33.83868, -111.864791 33.838389, -111.864672 33.838268, -111.864461 33.838087, -111.864307 33.837961, -111.864155 33.837815, -111.864043 33.83768, -111.864008 33.837622, -111.863949 33.837543, -111.863899 33.83749, -111.863799 33.837409, -111.86373 33.837364, -111.863655 33.837327, -111.863548 33.837288, -111.863442 33.837265, -111.863365 33.837256, -111.863241 33.83723, -111.863161 33.837204, -111.863058 33.837157, -111.862948 33.837087, -111.862796 33.836961, -111.862722 33.836907, -111.862615 33.836845, -111.862711 33.836719, -111.862788 33.836632, -111.862868 33.836531, -111.86298 33.836365, -111.863258 33.835964, -111.86332 33.835875, -111.863413 33.835751, -111.863555 33.835591, -111.863667 33.835486, -111.863831 33.835359, -111.863997 33.835253, -111.864166 33.835166, -111.864327 33.835099, -111.864502 33.835041, -111.864709 33.834991, -111.865228 33.834909, -111.865297 33.834901, -111.865498 33.834873, -111.865621 33.834864, -111.865746 33.834853, -111.865992 33.834808, -111.866096 33.834784, -111.866163 33.834757, -111.866351 33.834696, -111.866494 33.834644, -111.866679 33.834565, -111.866892 33.834455, -111.867107 33.834323, -111.86727 33.834208, -111.867702 33.833869, -111.868015 33.833621, -111.868102 33.833563, -111.868265 33.833467, -111.868408 33.833396, -111.868524 33.833358, -111.868686 33.833317, -111.868755 33.833295, -111.8689 33.833259, -111.869057 33.83323, -111.869252 33.833208, -111.869566 33.8332, -111.870129 33.833196, -111.87039 33.833195, -111.870636 33.833184, -111.870893 33.833155, -111.871103 33.83312, -111.871285 33.833081, -111.871473 33.833028, -111.87165 33.832965, -111.871816 33.832896, -111.87189 33.832866, -111.872072 33.832776, -111.872245 33.832674, -111.872445 33.832538, -111.872629 33.832391, -111.872739 33.832287, -111.872851 33.832171, -111.87302 33.831975, -111.873151 33.831781, -111.873254 33.831603, -111.873342 33.831419, -111.873402 33.831257, -111.87345 33.831086, -111.873484 33.830911, -111.873599 33.829939, -111.873648 33.8296, -111.873713 33.829344, -111.873784 33.829139, -111.873848 33.828985, -111.873942 33.828787, -111.874102 33.828515, -111.87441 33.828039, -111.874503 33.827917, -111.874657 33.827751, -111.874902 33.82751, -111.875045 33.827362, -111.875153 33.82726, -111.875188 33.827226, -111.875287 33.827117, -111.875404 33.826994, -111.875462 33.826914, -111.875547 33.826766, -111.875586 33.826677, -111.875628 33.826524, -111.875689 33.826174, -111.875711 33.826035, -111.875609 33.825773, -111.875557 33.825627, -111.875524 33.825535, -111.875516 33.825499, -111.875505 33.825408, -111.875513 33.825034, -111.875777 33.825011, -111.876131 33.824979, -111.876549 33.824924, -111.876832 33.824853, -111.877265 33.824727, -111.877706 33.824593, -111.878218 33.824389, -111.878493 33.824271, -111.879092 33.824066, -111.87932 33.823979, -111.879627 33.823861, -111.880115 33.82368, -111.880903 33.823373, -111.881281 33.823223, -111.881627 33.823082, -111.881958 33.82294, -111.882147 33.822845, -111.882383 33.822719, -111.882572 33.822609, -111.882714 33.822507, -111.882863 33.822405, -111.883123 33.822192, -111.883359 33.821979, -111.883611 33.821727, -111.88391 33.821397, -111.884273 33.821027, -111.88443 33.820853, -111.884753 33.820562, -111.885052 33.82031, -111.885543 33.819986, -111.885795 33.820315, -111.88591 33.820464, -111.886253 33.820936, -111.88629 33.820972, -111.88633 33.820993, -111.886381 33.821008, -111.886565 33.821014, -111.887083 33.821014, -111.887525 33.82101, -111.887618 33.821001, -111.887668 33.820991, -111.887681 33.820986, -111.891245 33.820989, -111.891243 33.821115, -111.891245 33.821403, -111.891256 33.82179, -111.89125 33.821978, -111.891254 33.822271, -111.891243 33.823071, -111.89125 33.823431, -111.891252 33.823582, -111.891264 33.82448, -111.891264 33.825079, -111.891265 33.826392, -111.891269 33.826521, -111.891273 33.826742, -111.891278 33.827077, -111.891277 33.827304, -111.891273 33.827418, -111.891257 33.827533, -111.891252 33.827568, -111.891236 33.827625, -111.891211 33.827679, -111.89124 33.827723, -111.891262 33.827792, -111.891264 33.828006, -111.891257 33.828213, -111.891276 33.828511, -111.891219 33.831353, -111.891202 33.832682, -111.891245 33.832732, -111.891237 33.832553, -111.891249 33.831697, -111.891257 33.83166, -111.891282 33.831623, -111.891386 33.831554, -111.891412 33.831542, -111.891819 33.83135, -111.891964 33.831277, -111.892112 33.831217, -111.892359 33.831129, -111.892685 33.831029, -111.89286 33.830986, -111.893407 33.830855, -111.894123 33.83068, -111.894232 33.830647, -111.894443 33.830567, -111.894583 33.830505, -111.894866 33.830365, -111.895105 33.830263, -111.895396 33.830167, -111.895513 33.830114, -111.895606 33.830058, -111.895746 33.829944, -111.895805 33.829887, -111.895893 33.829816, -111.895981 33.829772, -111.896076 33.82974, -111.896242 33.829707, -111.896981 33.829608, -111.897092 33.829579, -111.897237 33.829528, -111.89738 33.829461, -111.897528 33.829371, -111.89769 33.829278, -111.897874 33.82919, -111.897994 33.829143, -111.898122 33.829106, -111.898396 33.829056, -111.8987 33.828992, -111.898848 33.828965, -111.898988 33.828957, -111.899234 33.828965, -111.89941 33.828986, -111.899835 33.829071, -111.89984 33.828738, -111.899836 33.828692, -111.899838 33.828404, -111.899831 33.827675, -111.899935 33.82766, -111.90008 33.827624, -111.900221 33.827572, -111.900337 33.827536, -111.900442 33.827516, -111.900597 33.827503, -111.900695 33.827486, -111.900793 33.827458, -111.901287 33.827257, -111.901469 33.827172, -111.901697 33.827065, -111.901838 33.827008, -111.901965 33.826973, -111.902099 33.826949, -111.902241 33.826937, -111.902316 33.826944, -111.902415 33.82697, -111.902534 33.827012, -111.902667 33.827077, -111.902762 33.827139, -111.902855 33.827214, -111.902944 33.827306, -111.903076 33.827543, -111.903094 33.827567, -111.903153 33.827625, -111.903214 33.827663, -111.9033 33.827697, -111.903388 33.827718, -111.903473 33.827726, -111.903573 33.82772, -111.903924 33.827646, -111.904062 33.82764, -111.904197 33.827651, -111.904346 33.827683, -111.904668 33.827762, -111.904756 33.827766, -111.904949 33.827738, -111.905161 33.82769, -111.905305 33.827685, -111.905669 33.827709, -111.90583 33.827712, -111.906274 33.827738, -111.906456 33.827744, -111.906602 33.827732, -111.9068 33.827703, -111.906975 33.827667, -111.907132 33.827637, -111.907219 33.827625, -111.907327 33.827626, -111.90741 33.827637, -111.907522 33.827666, -111.907604 33.827686, -111.907689 33.827695, -111.907795 33.827693, -111.907883 33.827675, -111.907975 33.827641, -111.908044 33.827602, -111.908099 33.82756, -111.908176 33.827513, -111.908259 33.827479, -111.908345 33.827458, -111.908414 33.827452, -111.908562 33.827452, -111.908717 33.827453, -111.909003 33.827464, -111.910827 33.827452, -111.911191 33.827447, -111.911541 33.827449, -111.911875 33.827457, -111.912229 33.827474, -111.912351 33.827474, -111.91241 33.82747, -111.912443 33.827464, -111.912505 33.827442, -111.912662 33.827343, -111.912584 33.827227, -111.912459 33.82707, -111.912219 33.826709, -111.912174 33.826628, -111.912157 33.826578, -111.912133 33.826421, -111.91213 33.826335, -111.912124 33.826114, -111.912424 33.826133, -111.912808 33.826171, -111.912978 33.826191, -111.913098 33.826211, -111.913256 33.826248, -111.913481 33.826308, -111.913621 33.826353, -111.914011 33.826489, -111.91452 33.826675, -111.914728 33.826743, -111.914868 33.82678, -111.914929 33.826791, -111.915025 33.826793, -111.915089 33.826786, -111.915223 33.826756, -111.915318 33.82672, -111.91541 33.826671, -111.915458 33.826635, -111.915525 33.826568, -111.915575 33.826492, -111.915607 33.826412, -111.915641 33.826252, -111.915659 33.82609, -111.915664 33.825702, -111.915655 33.825535, -111.915837 33.8256, -111.916089 33.825699, -111.916521 33.825854, -111.916675 33.825897, -111.916827 33.825931, -111.916945 33.82595, -111.917284 33.825977, -111.917279 33.826907, -111.91728 33.827034, -111.917279 33.827255, -111.917278 33.827533, -111.917274 33.827753, -111.917272 33.827866, -111.917265 33.827997, -111.917237 33.828174, -111.917274 33.828185, -111.917383 33.828209, -111.917494 33.828221, -111.918444 33.828215, -111.918691 33.828209, -111.920355 33.828205, -111.921208 33.828203, -111.921519 33.828182, -111.921537 33.82818, -111.921741 33.828152, -111.921919 33.828117, -111.922265 33.828024, -111.922407 33.827973, -111.922712 33.827832, -111.922757 33.827806, -111.922979 33.827675, -111.923118 33.827579, -111.923462 33.827344, -111.923594 33.827435, -111.92381 33.827622, -111.924099 33.827837, -111.924275 33.827945, -111.924383 33.828008, -111.924582 33.828081, -111.924797 33.828149, -111.92503 33.828218, -111.925319 33.828246, -111.925438 33.828269, -111.92567 33.828289, -111.925818 33.828291, -111.926108 33.828297, -111.926098 33.828733, -111.926112 33.829879, -111.92624 33.831475, -111.926212 33.831526, -111.92618 33.831608, -111.926102 33.831713, -111.926049 33.831806, -111.926009 33.831906, -111.925983 33.832015, -111.925979 33.832061, -111.925975 33.832113, -111.92598 33.8322, -111.926004 33.832352, -111.926014 33.832701, -111.926004 33.832928, -111.925986 33.83301, -111.925967 33.833057, -111.925929 33.833125, -111.925909 33.833194, -111.925905 33.833224, -111.925916 33.833445, -111.926166 33.833602, -111.926283 33.833651, -111.926294 33.833654, -111.926398 33.833686, -111.926618 33.833723, -111.92685 33.833758, -111.926942 33.833784, -111.927018 33.833815, -111.927089 33.833855, -111.927207 33.833978, -111.927259 33.834048, -111.927309 33.834114, -111.927361 33.834186, -111.927387 33.834233, -111.927402 33.834287, -111.927408 33.834585, -111.928264 33.83458, -111.928264 33.834548, -111.928304 33.834194, -111.928307 33.834082, -111.928307 33.833963, -111.92833 33.833285, -111.928347 33.832937, -111.928353 33.832824, -111.928369 33.832696, -111.928408 33.832506, -111.928462 33.832326, -111.928532 33.83214, -111.928563 33.832043, -111.928662 33.831787, -111.9287 33.831676, -111.928754 33.831516, -111.928768 33.831456, -111.928771 33.831381, -111.928759 33.831301, -111.928731 33.831224, -111.928686 33.831149, -111.928633 33.831089, -111.928556 33.831027, -111.928416 33.830945, -111.928289 33.830885, -111.928147 33.830833, -111.928052 33.830793, -111.927927 33.830726, -111.927801 33.830643, -111.927684 33.830543, -111.927563 33.830397, -111.92745 33.830232, -111.927394 33.830093, -111.927374 33.829953, -111.927355 33.829729, -111.92735 33.829525, -111.927356 33.82924, -111.92737 33.829032, -111.927387 33.828765, -111.927412 33.828654, -111.927472 33.828495, -111.927657 33.828518, -111.928111 33.828643, -111.928508 33.828779, -111.928678 33.828825, -111.928933 33.82891, -111.92903 33.828932, -111.929495 33.829091, -111.929858 33.82921, -111.93021 33.82933, -111.930635 33.829534, -111.930948 33.829681, -111.931365 33.829886, -111.931488 33.829951, -111.93193 33.830165, -111.932298 33.830358, -111.932499 33.830441, -111.932845 33.830612, -111.933134 33.830765, -111.933436 33.830914, -111.933703 33.831037, -111.933891 33.831137, -111.934078 33.831257, -111.934208 33.831349, -111.934281 33.831417, -111.934412 33.831566, -111.934574 33.831785, -111.934688 33.832013, -111.934798 33.832228, -111.934933 33.832429, -111.935108 33.832652, -111.93531 33.832831, -111.935485 33.832971, -111.935725 33.833103, -111.935909 33.833186, -111.936133 33.833269, -111.936325 33.833313, -111.93654 33.833348, -111.936745 33.83337, -111.936942 33.83337, -111.937161 33.833357, -111.937331 33.833329, -111.937735 33.833291, -111.938063 33.833252, -111.938558 33.833195, -111.938979 33.833138, -111.93984 33.833046, -111.940304 33.83298, -111.940405 33.832954, -111.940558 33.832923, -111.940751 33.832879, -111.94096 33.832809, -111.94095 33.833501, -111.940968 33.83356, -111.940987 33.833592, -111.941028 33.833627, -111.941076 33.83365, -111.941104 33.833658, -111.941157 33.833665, -111.941219 33.833657, -111.941316 33.833623, -111.941383 33.833615, -111.941603 33.833628, -111.941632 33.833639, -111.941676 33.833681, -111.941722 33.833707, -111.941773 33.833719, -111.941913 33.833721, -111.941939 33.833713, -111.941954 33.8337, -111.941966 33.833668, -111.941971 33.83343, -111.941908 33.833337, -111.941892 33.833289, -111.941895 33.832415, -111.942033 33.832363, -111.942182 33.832315, -111.942405 33.832245, -111.942729 33.832157, -111.943058 33.832105, -111.94329 33.8321, -111.943293 33.832247, -111.943281 33.833129, -111.943272 33.833842, -111.943268 33.834118, -111.943266 33.834871, -111.943276 33.835763, -111.943276 33.836103, -111.943276 33.83643, -111.943276 33.837026, -111.943276 33.837116, -111.943276 33.837805, -111.943277 33.838299, -111.943279 33.839008, -111.943279 33.839123, -111.943279 33.839522, -111.943279 33.839921, -111.943272 33.840314, -111.94328 33.841205, -111.943281 33.841874, -111.943281 33.842148, -111.943277 33.842586, -111.943273 33.843016, -111.943274 33.843163, -111.943278 33.844307, -111.943675 33.844311, -111.943947 33.84424, -111.94552 33.84355, -111.945854 33.843446, -111.946171 33.844367, -111.9462 33.84456, -111.946155 33.844759, -111.946075 33.844952, -111.945459 33.845675, -111.945368 33.84585, -111.945329 33.846053, -111.945328 33.84607, -111.944689 33.846142, -111.943283 33.846284, -111.943197 33.846261, -111.940788 33.84628, -111.939086 33.846276, -111.938939 33.846295, -111.938942 33.846369, -111.938937 33.846552, -111.938947 33.848074, -111.938537 33.848081, -111.93801 33.848085, -111.937572 33.848081, -111.936278 33.84809, -111.935836 33.848096, -111.93464 33.848084, -111.934629 33.848033, -111.934589 33.847927, -111.934577 33.847881, -111.93457 33.847798, -111.934578 33.847739, -111.934614 33.847638, -111.934637 33.847548, -111.934641 33.847471, -111.934625 33.847325, -111.934613 33.847217, -111.93461 33.846748, -111.934615 33.846451, -111.934602 33.846407, -111.934563 33.846355, -111.934515 33.846323, -111.934467 33.846307, -111.933861 33.846309, -111.933814 33.846309, -111.933612 33.846302, -111.933438 33.846308, -111.933132 33.846328, -111.932901 33.846334, -111.93277 33.84633, -111.932691 33.846322, -111.932566 33.846315, -111.932301 33.846309, -111.930643 33.846318, -111.930395 33.846323, -111.929658 33.846316, -111.929257 33.846327, -111.92915 33.846328, -111.928828 33.846325, -111.928555 33.84671, -111.928692 33.846826, -111.928779 33.846799, -111.928763 33.846828, -111.928766 33.846874, -111.928783 33.846922, -111.928797 33.846968, -111.928822 33.847027, -111.928886 33.847084, -111.928974 33.847122, -111.929062 33.847154, -111.929126 33.847188, -111.929137 33.847249, -111.929085 33.847295, -111.929011 33.847299, -111.92895 33.847275, -111.928871 33.847222, -111.928798 33.84721, -111.928719 33.847219, -111.92865 33.84723, -111.928584 33.847247, -111.928487 33.847272, -111.92842 33.847277, -111.928346 33.847278, -111.928271 33.84729, -111.92821 33.847313, -111.928186 33.847325, -111.928146 33.847348, -111.928065 33.847318, -111.92805 33.847468, -111.928101 33.848135, -111.928134 33.848748, -111.928107 33.848997, -111.928107 33.849094, -111.928118 33.849398, -111.928118 33.849897, -111.928123 33.849924, -111.928498 33.849931, -111.929695 33.84994, -111.930266 33.84994, -111.930243 33.850232, -111.93025 33.850334, -111.93028 33.850519, -111.930283 33.85067, -111.930289 33.850818, -111.930308 33.850947, -111.930314 33.851055, -111.930307 33.851177, -111.930311 33.851568, -111.930303 33.851766, -111.930291 33.851922, -111.93029 33.852565, -111.930276 33.852602, -111.930252 33.85263, -111.93021 33.852653, -111.930168 33.852662, -111.929748 33.85264, -111.929711 33.85264, -111.929676 33.853088, -111.930751 33.852832, -111.930795 33.852804, -111.930848 33.852792, -111.930888 33.852794, -111.930989 33.852826, -111.931043 33.852826, -111.931132 33.852808, -111.93128 33.852762, -111.931316 33.852745, -111.931365 33.852702, -111.931375 33.852686, -111.931387 33.852651, -111.931397 33.85259, -111.931416 33.85228, -111.931381 33.852153, -111.931381 33.852124, -111.931403 33.851922, -111.931423 33.851665, -111.931412 33.851376, -111.931413 33.851183, -111.931368 33.850541, -111.931366 33.850336, -111.93139 33.849936, -111.932269 33.84993, -111.932507 33.849929, -111.933999 33.849927, -111.934619 33.849918, -111.934635 33.849962, -111.934641 33.850008, -111.934617 33.850219, -111.934612 33.850592, -111.934614 33.850979, -111.934622 33.851197, -111.934617 33.85166, -111.934622 33.852618, -111.934625 33.853137, -111.934639 33.853401, -111.934642 33.853567, -111.935157 33.853552, -111.935675 33.853554, -111.936294 33.85355, -111.936708 33.853548, -111.938789 33.853552, -111.939308 33.853546, -111.939531 33.853546, -111.939821 33.853545, -111.939844 33.853545, -111.940345 33.853542, -111.940866 33.853534, -111.941385 33.853534, -111.941734 33.853528, -111.942318 33.853526, -111.942601 33.853246, -111.942843 33.853034, -111.943007 33.852903, -111.943126 33.85282, -111.943274 33.852708, -111.943277 33.85166, -111.943283 33.850275, -111.943284 33.850165, -111.94328 33.849886, -111.943271 33.84903, -111.943267 33.84836, -111.943272 33.848063, -111.943281 33.847532, -111.943282 33.847171), (-111.565347 33.205486, -111.565554 33.205483, -111.567397 33.205457, -111.568028 33.205449, -111.569891 33.205425, -111.573788 33.209536, -111.576432 33.212403, -111.57675 33.212313, -111.575923 33.211429, -111.574867 33.21035, -111.57365 33.209089, -111.573575 33.20901, -111.573697 33.208852, -111.573746 33.208901, -111.573837 33.209013, -111.573932 33.209111, -111.573968 33.209148, -111.574005 33.209184, -111.574044 33.209221, -111.574378 33.20948, -111.574434 33.209516, -111.574545 33.209581, -111.574842 33.209719, -111.574903 33.209744, -111.575229 33.209835, -111.575369 33.209854, -111.575822 33.209874, -111.576046 33.20987, -111.576277 33.209873, -111.576604 33.209873, -111.576773 33.209874, -111.576857 33.209876, -111.577115 33.209877, -111.577202 33.209877, -111.577375 33.209877, -111.577639 33.209868, -111.577758 33.209865, -111.577984 33.20986, -111.578068 33.20986, -111.578153 33.20986, -111.578497 33.20986, -111.578755 33.20986, -111.578924 33.209857, -111.579359 33.209853, -111.57962 33.209853, -111.57979 33.209851, -111.580205 33.209854, -111.580288 33.209856, -111.58037 33.209857, -111.580593 33.209871, -111.580646 33.209881, -111.580726 33.209878, -111.580787 33.209877, -111.580841 33.209877, -111.580978 33.209874, -111.581049 33.209876, -111.581132 33.209877, -111.581283 33.209876, -111.581379 33.209877, -111.581448 33.209878, -111.581513 33.209877, -111.58157 33.209879, -111.581645 33.209881, -111.581726 33.209922, -111.581951 33.209921, -111.582103 33.20993, -111.582273 33.209931, -111.582597 33.209932, -111.582663 33.209933, -111.582631 33.205408, -111.583057 33.205405, -111.585645 33.205387, -111.591133 33.205349, -111.592886 33.205337, -111.595398 33.20532, -111.595409 33.204827, -111.595422 33.204268, -111.595414 33.203656, -111.595417 33.203426, -111.595424 33.203369, -111.59538 33.203079, -111.595393 33.201566, -111.595351 33.199339, -111.595358 33.194786, -111.595365 33.190776, -111.595366 33.189284, -111.595372 33.188482, -111.595457 33.187516, -111.595468 33.18744, -111.595562 33.187511, -111.597044 33.188577, -111.597764 33.189095, -111.598717 33.189797, -111.599857 33.190609, -111.599966 33.1907, -111.600059 33.190768, -111.601272 33.191665, -111.603057 33.192966, -111.603978 33.193632, -111.606408 33.195414, -111.606503 33.195484, -111.607734 33.19636, -111.608826 33.197178, -111.609302 33.197508, -111.610017 33.197927, -111.61013 33.197978, -111.61035 33.198079, -111.610755 33.198237, -111.611099 33.198355, -111.611616 33.198492, -111.612796 33.19868, -111.613862 33.198833, -111.614909 33.19899, -111.615649 33.199094, -111.617008 33.199298, -111.617791 33.19941, -111.618346 33.199484, -111.619212 33.199623, -111.620551 33.199816, -111.621324 33.199922, -111.621292 33.200024, -111.621288 33.200248, -111.621326 33.20033, -111.62143 33.200471, -111.621493 33.200578, -111.621517 33.201029, -111.621516 33.201337, -111.621548 33.201402, -111.621623 33.201485, -111.621669 33.201543, -111.62169 33.201634, -111.621687 33.201732, -111.621677 33.20181, -111.621656 33.202466, -111.622395 33.202463, -111.623946 33.202455, -111.624384 33.202455, -111.624813 33.202464, -111.624827 33.203169, -111.62482 33.203321, -111.624803 33.203393, -111.624755 33.203518, -111.624609 33.203846, -111.624491 33.204126, -111.623707 33.204135, -111.623126 33.204137, -111.623183 33.205181, -111.625703 33.20516, -111.625703 33.205247, -111.628764 33.205228, -111.629291 33.205225, -111.634225 33.205192, -111.634233 33.205492, -111.634244 33.205826, -111.634278 33.208691, -111.633267 33.208681, -111.633103 33.208669, -111.632856 33.208641, -111.631794 33.208452, -111.631536 33.208401, -111.631308 33.20837, -111.631065 33.208351, -111.630866 33.208347, -111.63067 33.208351, -111.630458 33.208368, -111.630199 33.208402, -111.629975 33.20845, -111.629708 33.208523, -111.629513 33.208593, -111.629323 33.208675, -111.629152 33.20876, -111.629049 33.208813, -111.628827 33.208944, -111.628679 33.209041, -111.628119 33.209384, -111.627911 33.209488, -111.627662 33.209595, -111.627506 33.20965, -111.627292 33.209708, -111.62703 33.209761, -111.62685 33.209788, -111.626669 33.209801, -111.625794 33.209815, -111.625792 33.210479, -111.625781 33.210538, -111.625744 33.210659, -111.625732 33.210721, -111.625726 33.210819, -111.625722 33.211008, -111.625725 33.211492, -111.625734 33.211595, -111.625728 33.21162, -111.62571 33.211647, -111.625684 33.211669, -111.625644 33.211686, -111.625614 33.211691, -111.625359 33.211688, -111.622721 33.211705, -111.621548 33.211709, -111.618965 33.211729, -111.616939 33.211737, -111.616878 33.211737, -111.617395 33.212488, -111.617306 33.213276, -111.61748 33.2156, -111.617696 33.21575, -111.617798 33.215829, -111.617781 33.216036, -111.617729 33.216707, -111.617811 33.217484, -111.617817 33.217534, -111.617879 33.218192, -111.617703 33.218606, -111.617561 33.218895, -111.617329 33.219817, -111.617386 33.219977, -111.617406 33.22007, -111.617411 33.220247, -111.617393 33.222451, -111.617392 33.222699, -111.61738 33.222911, -111.617355 33.223171, -111.617352 33.223239, -111.617348 33.223368, -111.617369 33.223702, -111.617368 33.224762, -111.617354 33.225994, -111.617357 33.226441, -111.617359 33.226681, -111.617348 33.226745, -111.6173 33.226864, -111.617291 33.226908, -111.617191 33.226907, -111.615428 33.226906, -111.615006 33.226903, -111.614688 33.226902, -111.612753 33.226908, -111.609019 33.22692, -111.60861 33.226922, -111.607428 33.226925, -111.605366 33.226928, -111.605384 33.226814, -111.605366 33.225266, -111.605158 33.224246, -111.60425 33.224249, -111.604262 33.224803, -111.604259 33.225102, -111.602167 33.225107, -111.601454 33.225113, -111.600002 33.225116, -111.600012 33.226044, -111.600038 33.226782, -111.600043 33.22694, -111.598642 33.226939, -111.59573 33.226947, -111.594945 33.226947, -111.591621 33.226948, -111.591117 33.226945, -111.591216 33.227062, -111.591498 33.227398, -111.591657 33.227578, -111.591979 33.227944, -111.592443 33.228448, -111.593086 33.229125, -111.593486 33.229534, -111.593965 33.230023, -111.594101 33.230161, -111.593508 33.230171, -111.593226 33.230263, -111.591752 33.228733, -111.590944 33.227794, -111.590869 33.227712, -111.590441 33.227247, -111.590158 33.227, -111.589742 33.226636, -111.586747 33.223554, -111.58321 33.219915, -111.583141 33.219924, -111.583095 33.219927, -111.583095 33.219964, -111.582736 33.219978, -111.582736 33.220052, -111.582737 33.220138, -111.582745 33.221174, -111.582729 33.221828, -111.582762 33.223847, -111.582812 33.226911, -111.582817 33.227204, -111.582632 33.227198, -111.582489 33.227213, -111.581856 33.227216, -111.581099 33.227221, -111.580877 33.227089, -111.580676 33.226995, -111.580513 33.226971, -111.580359 33.226976, -111.580249 33.227002, -111.58022 33.227026, -111.58018 33.227083, -111.580172 33.22721, -111.579863 33.227206, -111.579292 33.227199, -111.578402 33.227194, -111.577344 33.22719, -111.576886 33.227175, -111.576505 33.227144, -111.576377 33.227134, -111.576236 33.227125, -111.576121 33.227119, -111.575962 33.22711, -111.574686 33.22712, -111.573988 33.22711, -111.572715 33.227093, -111.57159 33.227114, -111.571227 33.227122, -111.570054 33.227127, -111.569456 33.22712, -111.568537 33.227109, -111.567519 33.227125, -111.567433 33.227235, -111.567431 33.227269, -111.566685 33.227266, -111.566162 33.227265, -111.565574 33.227263, -111.565113 33.227257, -111.564826 33.227254, -111.564082 33.227272, -111.563889 33.227288, -111.5634 33.227434, -111.563241 33.227468, -111.563242 33.227311, -111.563265 33.226622, -111.563242 33.22542, -111.563235 33.225082, -111.563216 33.223855, -111.563202 33.222611, -111.563211 33.220853, -111.563222 33.219932, -111.563188 33.219347, -111.563181 33.21887, -111.563172 33.21835, -111.56317 33.217512, -111.56317 33.217291, -111.56316 33.214539, -111.56316 33.214516, -111.56316 33.214331, -111.563171 33.213876, -111.563209 33.213476, -111.563308 33.213037, -111.563436 33.212652, -111.563583 33.212321, -111.563712 33.212096, -111.563818 33.21191, -111.563852 33.21185, -111.564262 33.211275, -111.564575 33.210873, -111.564816 33.210479, -111.565031 33.210029, -111.565138 33.209778, -111.565238 33.209454, -111.565297 33.209069, -111.565338 33.208706, -111.565347 33.205486), (-111.80693 33.282599, -111.803271 33.282678, -111.800347 33.282702, -111.795395 33.282738, -111.795122 33.282743, -111.789673 33.282845, -111.789673 33.282728, -111.789674 33.282496, -111.789673 33.282438, -111.789674 33.28218, -111.789674 33.277473, -111.789674 33.277363, -111.78955 33.277365, -111.789361 33.277368, -111.789341 33.277368, -111.788949 33.277374, -111.785509 33.277427, -111.782669 33.277457, -111.782363 33.277459, -111.781456 33.277465, -111.780404 33.277473, -111.778578 33.277498, -111.777024 33.277521, -111.776409 33.277529, -111.77578 33.277538, -111.774972 33.27755, -111.774815 33.277552, -111.774725 33.277552, -111.774666 33.277554, -111.774585 33.277555, -111.773467 33.277571, -111.772727 33.277578, -111.772727 33.277703, -111.772731 33.279058, -111.77273 33.279798, -111.77273 33.280051, -111.772733 33.280232, -111.772738 33.280503, -111.772761 33.280844, -111.772802 33.281188, -111.772882 33.28165, -111.772975 33.282074, -111.773014 33.28221, -111.773012 33.282037, -111.773011 33.281937, -111.773011 33.281913, -111.773007 33.281521, -111.773275 33.282418, -111.77328 33.282435, -111.773449 33.283001, -111.77356 33.283223, -111.773605 33.283307, -111.773608 33.283573, -111.773602 33.283602, -111.773588 33.283681, -111.773578 33.283724, -111.773605 33.283791, -111.773684 33.283987, -111.773763 33.284182, -111.773791 33.28425, -111.773917 33.284603, -111.77396 33.284724, -111.773988 33.284802, -111.774093 33.285196, -111.774163 33.285507, -111.774195 33.285685, -111.774232 33.28588, -111.774279 33.286211, -111.774313 33.286599, -111.774321 33.286837, -111.774325 33.286951, -111.774324 33.287502, -111.774324 33.288399, -111.774213 33.2884, -111.774215 33.28751, -111.773969 33.287512, -111.773495 33.287518, -111.773159 33.287484, -111.772942 33.287936, -111.772909 33.28803, -111.772884 33.288131, -111.772741 33.28814, -111.772756 33.288085, -111.772766 33.288033, -111.772791 33.287945, -111.772898 33.287687, -111.773016 33.287449, -111.773265 33.286976, -111.773357 33.286817, -111.773427 33.286681, -111.773497 33.286498, -111.773531 33.286372, -111.773559 33.286211, -111.773569 33.286065, -111.773562 33.285603, -111.773557 33.285265, -111.773561 33.285004, -111.773576 33.284813, -111.772732 33.284852, -111.772733 33.284934, -111.77274 33.285434, -111.772738 33.2856, -111.772732 33.286234, -111.772721 33.28752, -111.772705 33.28814, -111.772701 33.288299, -111.772757 33.288325, -111.772892 33.288376, -111.772906 33.288382, -111.772928 33.288376, -111.772937 33.288426, -111.77293 33.288458, -111.772899 33.288549, -111.772888 33.288618, -111.772886 33.288946, -111.77289 33.291722, -111.772912 33.291791, -111.772913 33.291927, -111.772897 33.291964, -111.772897 33.292029, -111.772735 33.292032, -111.772732 33.291928, -111.772728 33.291721, -111.772715 33.291143, -111.772711 33.290948, -111.772542 33.290948, -111.771847 33.290952, -111.771839 33.291896, -111.771668 33.291897, -111.771673 33.292046, -111.771115 33.292054, -111.770018 33.292064, -111.768455 33.292076, -111.767375 33.292087, -111.76737 33.291758, -111.767319 33.288497, -111.768432 33.288474, -111.768418 33.286644, -111.768397 33.284886, -111.767579 33.284895, -111.767321 33.284896, -111.767323 33.286643, -111.767335 33.288486, -111.766236 33.288499, -111.764117 33.288504, -111.764124 33.28893, -111.764128 33.289119, -111.764125 33.289727, -111.764125 33.289817, -111.764137 33.290132, -111.764128 33.290537, -111.764123 33.290796, -111.764129 33.290912, -111.764152 33.291309, -111.764159 33.29141, -111.764171 33.29199, -111.764174 33.29211, -111.764089 33.292102, -111.763835 33.292144, -111.76305 33.292271, -111.763021 33.292282, -111.762442 33.292506, -111.761581 33.29309, -111.760059 33.294221, -111.75962 33.294531, -111.759549 33.29458, -111.758777 33.295128, -111.757694 33.295475, -111.755533 33.295496, -111.755548 33.293297, -111.755538 33.292374, -111.755536 33.292175, -111.755089 33.292243, -111.751755 33.292267, -111.751131 33.292217, -111.748922 33.292235, -111.748826 33.292236, -111.748419 33.29224, -111.746937 33.292253, -111.746883 33.292265, -111.745198 33.292265, -111.74383 33.292413, -111.74523 33.29223, -111.745404 33.292105, -111.745611 33.291968, -111.745829 33.291849, -111.746063 33.291763, -111.746488 33.291635, -111.746923 33.291511, -111.747217 33.291402, -111.747422 33.291306, -111.747605 33.291203, -111.747816 33.291046, -111.748014 33.290855, -111.748148 33.290682, -111.748276 33.290477, -111.748356 33.290269, -111.748718 33.288819, -111.748742 33.288747, -111.748756 33.288702, -111.749075 33.287245, -111.749084 33.287205, -111.749105 33.287145, -111.749417 33.286605, -111.749841 33.286142, -111.750104 33.285925, -111.750267 33.286216, -111.750447 33.286531, -111.750767 33.286934, -111.750842 33.287028, -111.750914 33.287106, -111.750982 33.287157, -111.751052 33.287182, -111.751165 33.287147, -111.751215 33.287094, -111.751394 33.28693, -111.751445 33.286867, -111.751496 33.286754, -111.751514 33.286666, -111.751534 33.286485, -111.751542 33.286417, -111.75155 33.286352, -111.751559 33.286234, -111.751575 33.286046, -111.751575 33.285941, -111.751581 33.285867, -111.751597 33.285773, -111.75162 33.285663, -111.751644 33.285601, -111.751663 33.28553, -111.751747 33.285316, -111.752068 33.285279, -111.753276 33.285258, -111.755153 33.285273, -111.755391 33.285265, -111.755388 33.285103, -111.755386 33.284998, -111.755295 33.281354, -111.75525 33.279516, -111.755209 33.277808, -111.755208 33.277749, -111.755206 33.277673, -111.757549 33.277651, -111.757618 33.277651, -111.759595 33.277644, -111.759688 33.277643, -111.759919 33.277642, -111.762678 33.277627, -111.763048 33.277625, -111.763962 33.277622, -111.763942 33.277566, -111.76393 33.277493, -111.76394 33.277167, -111.763942 33.276804, -111.763931 33.274962, -111.763934 33.273955, -111.763937 33.273215, -111.763937 33.272986, -111.763941 33.270578, -111.763933 33.270498, -111.76391 33.27042, -111.764122 33.270418, -111.765728 33.270404, -111.766644 33.270401, -111.767698 33.270387, -111.768834 33.270392, -111.769572 33.270386, -111.770382 33.270373, -111.770882 33.270372, -111.771419 33.270363, -111.772261 33.270355, -111.772532 33.270347, -111.772687 33.270342, -111.772689 33.270732, -111.772693 33.271403, -111.772701 33.272719, -111.772701 33.272747, -111.772705 33.273446, -111.772705 33.27394, -111.772705 33.274225, -111.774387 33.274231, -111.774422 33.274231, -111.774567 33.274229, -111.775398 33.27423, -111.780565 33.274233, -111.781416 33.274233, -111.782668 33.274234, -111.789365 33.274242, -111.789388 33.274241, -111.789463 33.274242, -111.789628 33.274244, -111.789626 33.274052, -111.789625 33.273755, -111.789617 33.272706, -111.789607 33.271947, -111.789606 33.271887, -111.789595 33.270175, -111.789565 33.266728, -111.789558 33.266213, -111.789551 33.265695, -111.789544 33.264688, -111.789543 33.264491, -111.789528 33.263141, -111.789525 33.262853, -111.789889 33.262847, -111.791672 33.262819, -111.791991 33.262814, -111.793709 33.262786, -111.79381 33.262784, -111.798145 33.262718, -111.804449 33.262647, -111.80505 33.262641, -111.806406 33.262622, -111.806807 33.262617, -111.806981 33.262614, -111.809112 33.262586, -111.811097 33.262559, -111.811689 33.262551, -111.811886 33.262548, -111.812156 33.262545, -111.812168 33.262545, -111.812204 33.262544, -111.814362 33.262514, -111.81538 33.2625, -111.818175 33.262462, -111.820199 33.26243, -111.821664 33.262414, -111.822163 33.262408, -111.822085 33.262588, -111.822061 33.26267, -111.821957 33.263106, -111.821932 33.263266, -111.821921 33.263371, -111.821855 33.264345, -111.821858 33.264379, -111.821831 33.264544, -111.821684 33.265189, -111.821655 33.265369, -111.821637 33.265573, -111.821637 33.265783, -111.821649 33.265935, -111.821729 33.266668, -111.821741 33.266792, -111.821743 33.266917, -111.821731 33.267063, -111.821709 33.267186, -111.821652 33.267401, -111.821728 33.26742, -111.821834 33.267442, -111.821857 33.267367, -111.821888 33.267223, -111.821907 33.267097, -111.821922 33.266903, -111.821917 33.266734, -111.821841 33.266011, -111.82183 33.265777, -111.821839 33.265542, -111.821866 33.26531, -111.822045 33.264483, -111.822065 33.264335, -111.822119 33.263385, -111.822126 33.263323, -111.822152 33.263199, -111.822205 33.263078, -111.822229 33.262998, -111.822282 33.262628, -111.822298 33.262566, -111.82237 33.262406, -111.824122 33.262378, -111.824343 33.262378, -111.825172 33.262377, -111.825427 33.262372, -111.826424 33.262363, -111.828006 33.262341, -111.829297 33.262307, -111.832433 33.262258, -111.83273 33.262254, -111.832701 33.258904, -111.832892 33.258631, -111.832947 33.255066, -111.832669 33.255069, -111.830505 33.255083, -111.830504 33.255069, -111.830502 33.254999, -111.8302 33.255004, -111.829534 33.255015, -111.825351 33.255099, -111.825316 33.255147, -111.825295 33.255179, -111.825228 33.255274, -111.825148 33.255412, -111.825041 33.255626, -111.824916 33.255879, -111.824833 33.256088, -111.824724 33.256433, -111.824663 33.256672, -111.824624 33.25689, -111.824601 33.257109, -111.824597 33.25719, -111.824526 33.257877, -111.824488 33.258088, -111.824353 33.258667, -111.8243 33.258817, -111.824098 33.259256, -111.824098 33.259119, -111.824097 33.258676, -111.824088 33.257171, -111.824082 33.25506, -111.824062 33.251438, -111.82406 33.249868, -111.824047 33.248972, -111.824052 33.247819, -111.824236 33.247818, -111.824352 33.247816, -111.826245 33.247797, -111.82827 33.247776, -111.829417 33.247765, -111.829476 33.247763, -111.829501 33.247763, -111.829554 33.247763, -111.829611 33.247763, -111.830072 33.247758, -111.830844 33.247749, -111.832823 33.247729, -111.833703 33.247721, -111.83393 33.247719, -111.833984 33.247718, -111.835158 33.247706, -111.836853 33.247681, -111.836914 33.24768, -111.836969 33.247679, -111.836967 33.24787, -111.836992 33.24787, -111.83959 33.247842, -111.84114 33.247814, -111.841287 33.24782, -111.841293 33.248425, -111.841309 33.249256, -111.84132 33.250049, -111.841319 33.251258, -111.84132 33.252119, -111.84134 33.253047, -111.84134 33.254013, -111.841334 33.254669, -111.841322 33.254838, -111.841345 33.255427, -111.841367 33.256676, -111.841371 33.257143, -111.841391 33.258547, -111.841394 33.258922, -111.841394 33.260333, -111.841394 33.260455, -111.841414 33.262136, -111.841414 33.262278, -111.841454 33.26575, -111.841454 33.265936, -111.84146 33.266726, -111.841478 33.267918, -111.841488 33.26845, -111.841494 33.268782, -111.841501 33.269364, -111.841501 33.269432, -111.841347 33.269435, -111.839175 33.269473, -111.837454 33.269485, -111.836909 33.269481, -111.836915 33.272792, -111.836916 33.273118, -111.836919 33.274811, -111.83674 33.274807, -111.834999 33.274769, -111.835003 33.274972, -111.835019 33.275799, -111.834284 33.275793, -111.834224 33.275793, -111.834262 33.276715, -111.833001 33.276733, -111.832636 33.276739, -111.83042 33.276774, -111.829853 33.27677, -111.828521 33.276728, -111.828128 33.276725, -111.826666 33.276744, -111.825929 33.276755, -111.824197 33.276781, -111.820497 33.276822, -111.820083 33.276846, -111.820016 33.276851, -111.819618 33.276885, -111.818758 33.276977, -111.818294 33.277011, -111.817828 33.27703, -111.815811 33.277053, -111.815691 33.277054, -111.815628 33.277054, -111.815603 33.277054, -111.815548 33.277231, -111.815601 33.27723, -111.815619 33.27723, -111.815757 33.277229, -111.815299 33.278993, -111.815276 33.279145, -111.815268 33.279276, -111.815271 33.279407, -111.815287 33.279536, -111.815313 33.279649, -111.815351 33.279761, -111.815419 33.279908, -111.815481 33.280012, -111.815795 33.280437, -111.815913 33.280611, -111.815969 33.280714, -111.816025 33.280856, -111.816055 33.280968, -111.816116 33.281394, -111.816147 33.281525, -111.816192 33.281653, -111.816252 33.281776, -111.816388 33.282026, -111.816511 33.282291, -111.816544 33.282375, -111.816557 33.282406, -111.816586 33.282506, -111.816612 33.282594, -111.816615 33.282613, -111.816645 33.282758, -111.816578 33.282769, -111.816562 33.282613, -111.816551 33.282507, -111.816383 33.282491, -111.815588 33.282505, -111.815071 33.282524, -111.813915 33.282535, -111.80693 33.282599)), ((-111.57675 33.212313, -111.577994 33.213626, -111.579386 33.215045, -111.580326 33.216013, -111.580984 33.216707, -111.582596 33.218407, -111.582676 33.218491, -111.582726 33.218537, -111.582723 33.218158, -111.582736 33.218064, -111.582724 33.217032, -111.582736 33.216223, -111.582723 33.215876, -111.58272 33.215147, -111.582705 33.214183, -111.58268 33.213136, -111.582677 33.212745, -111.582321 33.212501, -111.582231 33.212432, -111.582039 33.212332, -111.58191 33.212302, -111.577564 33.2123, -111.57675 33.212313)), ((-111.830502 33.254999, -111.832103 33.254973, -111.832668 33.254976, -111.832954 33.254973, -111.832964 33.254646, -111.830504 33.254658, -111.830502 33.254999)), ((-111.821834 33.267442, -111.821796 33.267564, -111.82179 33.267581, -111.821749 33.267685, -111.821668 33.267836, -111.821572 33.26798, -111.82145 33.268132, -111.821301 33.268295, -111.821197 33.268389, -111.821084 33.268476, -111.820939 33.268571, -111.82068 33.268717, -111.820409 33.268847, -111.819883 33.26909, -111.819516 33.26926, -111.819316 33.269364, -111.819141 33.269466, -111.818982 33.269571, -111.818791 33.269714, -111.818613 33.269874, -111.818463 33.270032, -111.818324 33.270206, -111.818212 33.270369, -111.818112 33.270541, -111.81802 33.270733, -111.817956 33.270896, -111.817722 33.271662, -111.823108 33.271657, -111.823107 33.270813, -111.823425 33.270809, -111.823466 33.270809, -111.823955 33.270803, -111.824163 33.270801, -111.824157 33.269744, -111.824156 33.269658, -111.824154 33.268912, -111.824152 33.268725, -111.824148 33.268115, -111.823938 33.268128, -111.823878 33.268099, -111.823855 33.268083, -111.823724 33.267992, -111.822932 33.267761, -111.821834 33.267442)))"} -{"geo_id":"09298","urban_area_code":"09298","name":"Boulder, CO","lsad_name":"Boulder, CO Urbanized Area","area_lsad_code":"75","mtfcc_feature_class_code":"G3500","type":"U","functional_status":"S","area_land_meters":84039209,"area_water_meters":697531,"internal_point_lon":-105.2390499,"internal_point_lat":40.027135,"internal_point_geom":"POINT(-105.2390499 40.027135)","urban_area_geom":"MULTIPOLYGON(((-105.215986 40.021871, -105.216112 40.021735, -105.215752 40.021737, -105.215791 40.021947, -105.2159 40.021921, -105.215974 40.021878, -105.215986 40.021871)), ((-105.18363 40.094358, -105.183476 40.094354, -105.179335 40.094345, -105.178346 40.094344, -105.177356 40.094352, -105.17657 40.094338, -105.174496 40.094344, -105.173851 40.094352, -105.173328 40.09434, -105.172658 40.094346, -105.172487 40.094347, -105.171925 40.094355, -105.169062 40.094337, -105.169053 40.093825, -105.169068 40.093276, -105.169078 40.092368, -105.169065 40.091451, -105.169082 40.088765, -105.169084 40.088546, -105.169073 40.087607, -105.169071 40.087419, -105.16907 40.087091, -105.169042 40.087091, -105.168714 40.087411, -105.1685 40.087664, -105.168321 40.087851, -105.16817 40.088037, -105.167663 40.08846, -105.167562 40.088614, -105.167534 40.088724, -105.167584 40.088921, -105.167863 40.089289, -105.168184 40.089674, -105.168191 40.089784, -105.168048 40.089866, -105.167748 40.089965, -105.167483 40.090075, -105.16729 40.090234, -105.167254 40.090355, -105.16739 40.090553, -105.167562 40.09069, -105.167712 40.090882, -105.16774 40.090992, -105.167626 40.091096, -105.167376 40.091206, -105.166861 40.091365, -105.166081 40.091464, -105.165788 40.091546, -105.165666 40.091629, -105.165638 40.091684, -105.165666 40.091816, -105.165866 40.091997, -105.166124 40.092074, -105.166391 40.092163, -105.165383 40.092307, -105.165094 40.09235, -105.164705 40.092384, -105.16444 40.092388, -105.16422 40.092362, -105.164061 40.092322, -105.16384 40.092236, -105.163613 40.092117, -105.163409 40.091975, -105.163386 40.091971, -105.163304 40.09197, -105.163243 40.091986, -105.162906 40.092163, -105.162724 40.092278, -105.162451 40.092386, -105.162072 40.092503, -105.161895 40.092541, -105.161487 40.092596, -105.161323 40.092609, -105.160638 40.092598, -105.159387 40.092625, -105.158865 40.09259, -105.157978 40.0926, -105.157091 40.092591, -105.157045 40.092148, -105.156998 40.092022, -105.15692 40.091906, -105.156844 40.091827, -105.156696 40.091731, -105.156527 40.091659, -105.156355 40.091618, -105.156189 40.091605, -105.155455 40.09167, -105.155254 40.091698, -105.15424 40.091799, -105.153969 40.09185, -105.153463 40.091983, -105.153177 40.092082, -105.152246 40.092462, -105.152151 40.092165, -105.152022 40.091875, -105.151921 40.091747, -105.151791 40.091635, -105.151716 40.091586, -105.152204 40.091115, -105.152284 40.091004, -105.152342 40.090885, -105.15238 40.090735, -105.152381 40.090633, -105.152353 40.090521, -105.152275 40.090377, -105.152193 40.090274, -105.152086 40.090186, -105.151992 40.090132, -105.151131 40.089793, -105.150984 40.089761, -105.150832 40.089751, -105.150242 40.089738, -105.150267 40.089089, -105.150292 40.088906, -105.150438 40.088362, -105.150539 40.087934, -105.150559 40.087755, -105.150548 40.087319, -105.150321 40.08732, -105.149709 40.087312, -105.149045 40.087303, -105.146584 40.087295, -105.144292 40.087296, -105.142199 40.087294, -105.141994 40.087301, -105.141366 40.087303, -105.141224 40.087304, -105.140978 40.087305, -105.138254 40.087317, -105.137297 40.087318, -105.134898 40.087328, -105.131459 40.087359, -105.131493 40.087912, -105.131503 40.088602, -105.131548 40.090736, -105.131556 40.091213, -105.131562 40.092671, -105.131526 40.094416, -105.131532 40.094899, -105.131494 40.098358, -105.131487 40.098495, -105.131466 40.099607, -105.131465 40.099828, -105.131599 40.099834, -105.13192 40.099938, -105.132585 40.100268, -105.133 40.100565, -105.133293 40.100807, -105.133793 40.10112, -105.133936 40.101246, -105.134194 40.101356, -105.134294 40.101433, -105.134615 40.101456, -105.135009 40.101445, -105.135502 40.101439, -105.135998 40.101479, -105.136253 40.1015, -105.136604 40.101456, -105.136797 40.10138, -105.137162 40.101155, -105.137312 40.100962, -105.13782 40.100743, -105.137992 40.100606, -105.138178 40.100578, -105.138428 40.100611, -105.138778 40.100716, -105.13885 40.100782, -105.1389 40.101067, -105.139036 40.101304, -105.139171 40.101364, -105.139257 40.101408, -105.139379 40.101578, -105.139513 40.101695, -105.13965 40.101815, -105.140186 40.102084, -105.140573 40.102249, -105.140801 40.102375, -105.141173 40.102606, -105.141817 40.103134, -105.142281 40.103485, -105.14251 40.103694, -105.142867 40.103936, -105.143525 40.104403, -105.144047 40.104766, -105.144483 40.104936, -105.144734 40.104958, -105.145141 40.104904, -105.145492 40.104761, -105.145935 40.104454, -105.146048 40.104346, -105.146164 40.104234, -105.146386 40.104086, -105.146844 40.103839, -105.14708 40.10363, -105.147435 40.103384, -105.147456 40.103409, -105.147594 40.103521, -105.147742 40.103601, -105.147888 40.103652, -105.147917 40.103659, -105.148187 40.103715, -105.148152 40.103831, -105.148094 40.103942, -105.148014 40.104044, -105.147913 40.104135, -105.147683 40.104282, -105.14743 40.104406, -105.147389 40.104428, -105.146263 40.105038, -105.146139 40.105135, -105.146017 40.105272, -105.145976 40.105344, -105.145931 40.105493, -105.145919 40.105606, -105.145928 40.106738, -105.146062 40.106721, -105.146322 40.106668, -105.146452 40.10663, -105.146695 40.106536, -105.146717 40.106569, -105.14678 40.106623, -105.146865 40.106656, -105.146985 40.10667, -105.147058 40.106657, -105.14728 40.106516, -105.147362 40.106479, -105.147436 40.106452, -105.147556 40.106426, -105.14769 40.106387, -105.147778 40.106339, -105.14779 40.10632, -105.14782 40.106217, -105.147811 40.106132, -105.147712 40.105979, -105.148517 40.105536, -105.148641 40.105473, -105.148906 40.105367, -105.149186 40.10529, -105.149478 40.105244, -105.150613 40.105218, -105.151348 40.105207, -105.151353 40.105163, -105.151395 40.104982, -105.151446 40.104862, -105.151547 40.104704, -105.151643 40.104603, -105.151758 40.104515, -105.151935 40.104419, -105.152226 40.10431, -105.152434 40.104266, -105.152649 40.104253, -105.15288 40.104205, -105.1531 40.104132, -105.153295 40.104041, -105.153459 40.10393, -105.153527 40.103858, -105.153815 40.103584, -105.153895 40.103515, -105.154227 40.103295, -105.154362 40.10324, -105.154471 40.103159, -105.154548 40.103057, -105.154585 40.102949, -105.154592 40.102838, -105.154531 40.102533, -105.154538 40.102469, -105.154579 40.102345, -105.154654 40.10223, -105.154736 40.102076, -105.15478 40.101913, -105.154791 40.101603, -105.15943 40.10161, -105.159437 40.102631, -105.159422 40.103158, -105.159433 40.103985, -105.159246 40.103974, -105.159068 40.103996, -105.158953 40.104095, -105.158831 40.10426, -105.15881 40.104342, -105.158896 40.104502, -105.158889 40.104578, -105.158791 40.104673, -105.15871 40.104754, -105.158488 40.104765, -105.158066 40.104946, -105.157658 40.105072, -105.157222 40.105292, -105.157114 40.105369, -105.157114 40.105457, -105.157243 40.105622, -105.157229 40.105704, -105.157128 40.105786, -105.156985 40.105814, -105.156799 40.105923, -105.156406 40.106127, -105.155705 40.106209, -105.155362 40.106302, -105.155168 40.106379, -105.155097 40.106478, -105.154975 40.106577, -105.154653 40.10667, -105.154368 40.106686, -105.154337 40.106684, -105.153838 40.106637, -105.153673 40.106692, -105.153523 40.10679, -105.153394 40.106829, -105.153208 40.106796, -105.152951 40.106774, -105.152815 40.106812, -105.152815 40.106889, -105.152736 40.107081, -105.152779 40.107158, -105.153072 40.107241, -105.153187 40.107301, -105.153194 40.107416, -105.153158 40.107521, -105.153 40.107636, -105.152686 40.10774, -105.152228 40.107795, -105.152106 40.10785, -105.152042 40.108086, -105.151798 40.108438, -105.151791 40.108542, -105.151905 40.108619, -105.152206 40.108668, -105.152242 40.108718, -105.152234 40.108828, -105.152177 40.108861, -105.152048 40.108987, -105.157988 40.109181, -105.159452 40.109229, -105.159472 40.112058, -105.159466 40.115959, -105.159466 40.1161, -105.159479 40.116137, -105.159652 40.116025, -105.159985 40.115791, -105.160304 40.115547, -105.160609 40.115292, -105.162733 40.113343, -105.165269 40.111014, -105.166529 40.109856, -105.168733 40.107833, -105.169775 40.106893, -105.173192 40.103812, -105.173702 40.104136, -105.172978 40.104786, -105.172841 40.104908, -105.173457 40.105348, -105.17366 40.105492, -105.173727 40.105577, -105.173763 40.105683, -105.17378 40.10586, -105.173784 40.106307, -105.173783 40.106359, -105.173844 40.10635, -105.174845 40.106218, -105.175189 40.106224, -105.175604 40.106185, -105.177156 40.106087, -105.177499 40.106054, -105.177807 40.106054, -105.178393 40.106015, -105.178765 40.106054, -105.179387 40.106175, -105.180096 40.10623, -105.18104 40.106191, -105.181312 40.106186, -105.183151 40.105995, -105.183271 40.105983, -105.183529 40.105917, -105.184015 40.105724, -105.184287 40.105697, -105.184859 40.105735, -105.185174 40.105697, -105.185496 40.105604, -105.185868 40.105505, -105.186125 40.105368, -105.187313 40.104961, -105.187506 40.104934, -105.187971 40.104983, -105.188085 40.105038, -105.188114 40.105203, -105.188192 40.105258, -105.188414 40.105274, -105.188965 40.105263, -105.18943 40.105285, -105.189687 40.105252, -105.191089 40.105247, -105.191239 40.105252, -105.191425 40.105252, -105.19169 40.105225, -105.191869 40.105247, -105.192048 40.10528, -105.192362 40.105307, -105.192677 40.105417, -105.19282 40.105543, -105.193035 40.105966, -105.193256 40.106208, -105.193464 40.106334, -105.194494 40.106762, -105.194837 40.106933, -105.195295 40.107059, -105.195624 40.107064, -105.195896 40.107284, -105.19614 40.107458, -105.196711 40.107734, -105.19699 40.107938, -105.197176 40.108026, -105.197427 40.108058, -105.197935 40.108036, -105.198779 40.1078, -105.199465 40.107635, -105.199794 40.107613, -105.200838 40.107447, -105.200841 40.107554, -105.20083 40.107938, -105.200837 40.108084, -105.201658 40.108079, -105.201846 40.108055, -105.204794 40.10734, -105.204905 40.107309, -105.205037 40.107244, -105.205407 40.106936, -105.205582 40.107005, -105.205675 40.107028, -105.205959 40.107085, -105.206114 40.107094, -105.206901 40.107091, -105.206903 40.107353, -105.206903 40.107624, -105.206902 40.107781, -105.208482 40.107376, -105.209185 40.107165, -105.209555 40.1072, -105.210146 40.107296, -105.210698 40.107395, -105.210645 40.105429, -105.211639 40.104661, -105.212604 40.104387, -105.212969 40.104354, -105.213412 40.104327, -105.213684 40.104233, -105.21432 40.103865, -105.214571 40.103805, -105.214792 40.103772, -105.215035 40.103667, -105.21535 40.103475, -105.215851 40.10331, -105.21633 40.103206, -105.216312 40.102582, -105.21628 40.101669, -105.214723 40.101669, -105.21333 40.101675, -105.212683 40.101672, -105.211648 40.101681, -105.210113 40.101688, -105.208526 40.101681, -105.206883 40.101648, -105.204006 40.101659, -105.200819 40.101677, -105.199823 40.101684, -105.198568 40.101691, -105.196375 40.101705, -105.194049 40.101704, -105.192824 40.101702, -105.190419 40.101706, -105.189928 40.101732, -105.189639 40.101775, -105.189428 40.101825, -105.188771 40.102047, -105.18779 40.102425, -105.187185 40.102633, -105.186535 40.102825, -105.184957 40.103277, -105.184237 40.103449, -105.183507 40.103593, -105.183139 40.103655, -105.183111 40.103157, -105.1831 40.102449, -105.183116 40.10173, -105.181791 40.101729, -105.179786 40.101702, -105.178415 40.101693, -105.177952 40.101696, -105.177029 40.101675, -105.176231 40.101662, -105.175714 40.101673, -105.175572 40.101666, -105.178383 40.099132, -105.178843 40.098712, -105.18363 40.094358)), ((-105.18363 40.094358, -105.183704 40.094359, -105.184222 40.094371, -105.185173 40.094356, -105.186483 40.094365, -105.186924 40.094368, -105.187849 40.094367, -105.187862 40.093847, -105.187867 40.093538, -105.187869 40.093395, -105.187877 40.092881, -105.187884 40.092564, -105.187883 40.09238, -105.187885 40.092214, -105.187886 40.092036, -105.187869 40.091945, -105.187839 40.091872, -105.187825 40.091846, -105.187793 40.091804, -105.188006 40.091612, -105.18902 40.0907, -105.190312 40.089538, -105.190693 40.089196, -105.190734 40.089159, -105.190845 40.089082, -105.191274 40.089081, -105.191792 40.089072, -105.192077 40.089086, -105.193593 40.089226, -105.194165 40.089275, -105.194169 40.089441, -105.19417 40.089509, -105.194175 40.090823, -105.194186 40.094011, -105.194184 40.094226, -105.194186 40.094259, -105.194182 40.094384, -105.19615 40.094384, -105.196151 40.094265, -105.196152 40.094202, -105.196152 40.09414, -105.196154 40.094009, -105.196407 40.094009, -105.197612 40.093993, -105.197768 40.093995, -105.197875 40.094023, -105.198131 40.094115, -105.198327 40.094138, -105.198731 40.09414, -105.199047 40.094111, -105.199943 40.094003, -105.199941 40.094222, -105.199935 40.094267, -105.199939 40.094393, -105.201299 40.094411, -105.201716 40.094406, -105.202659 40.094396, -105.203433 40.094404, -105.204604 40.094398, -105.204599 40.094273, -105.204597 40.094228, -105.204592 40.0941, -105.204593 40.093609, -105.204585 40.09301, -105.204591 40.091761, -105.204552 40.091654, -105.204484 40.091554, -105.20423 40.091243, -105.204141 40.09109, -105.204008 40.090836, -105.203902 40.090698, -105.203805 40.090603, -105.20369 40.090561, -105.203562 40.09055, -105.203041 40.090541, -105.202911 40.09057, -105.202824 40.090609, -105.202928 40.090456, -105.203003 40.090296, -105.203044 40.090132, -105.203049 40.090038, -105.202968 40.089898, -105.20293 40.089687, -105.202885 40.089629, -105.202812 40.089593, -105.202765 40.089588, -105.202641 40.089576, -105.202648 40.087146, -105.202636 40.085708, -105.202624 40.084277, -105.202591 40.084155, -105.202524 40.084041, -105.202426 40.083941, -105.2023 40.083865, -105.202156 40.083811, -105.202001 40.083782, -105.201896 40.083777, -105.201267 40.083771, -105.200513 40.083774, -105.200053 40.083768, -105.199938 40.083766, -105.198521 40.083765, -105.198303 40.083788, -105.198091 40.083835, -105.19788 40.083904, -105.197731 40.083973, -105.197636 40.084031, -105.197523 40.084121, -105.197479 40.084156, -105.196392 40.085139, -105.19624 40.08524, -105.196232 40.085148, -105.196248 40.084393, -105.196252 40.084258, -105.196282 40.083465, -105.196284 40.083398, -105.197216 40.082544, -105.200358 40.079694, -105.201985 40.078218, -105.203106 40.077182, -105.203798 40.076547, -105.203926 40.076432, -105.205675 40.074861, -105.205843 40.074704, -105.20664 40.073954, -105.206714 40.073888, -105.206969 40.073664, -105.206995 40.073637, -105.207008 40.073624, -105.207706 40.073001, -105.208077 40.072673, -105.208264 40.072507, -105.20964 40.071255, -105.211346 40.069711, -105.211706 40.069384, -105.212108 40.069013, -105.212608 40.068543, -105.213566 40.06768, -105.213633 40.067614, -105.213686 40.067568, -105.21382 40.067451, -105.215946 40.065521, -105.216285 40.065205, -105.216687 40.064832, -105.219697 40.062101, -105.223018 40.059062, -105.223772 40.058399, -105.22386 40.058327, -105.223879 40.058311, -105.224713 40.057621, -105.225534 40.056965, -105.225735 40.056807, -105.226309 40.056355, -105.226743 40.056014, -105.227393 40.055483, -105.228044 40.054972, -105.228152 40.054888, -105.228833 40.054324, -105.230819 40.052734, -105.23102 40.05288, -105.231057 40.0529, -105.231168 40.052878, -105.231359 40.0528, -105.232123 40.052763, -105.232999 40.052279, -105.233518 40.051951, -105.233887 40.051688, -105.234123 40.051505, -105.234521 40.051137, -105.234566 40.051095, -105.234867 40.05109, -105.235751 40.051077, -105.236532 40.05107, -105.23751 40.051072, -105.242149 40.051079, -105.242842 40.051072, -105.244243 40.051073, -105.244277 40.051073, -105.244278 40.051142, -105.244285 40.051609, -105.244282 40.05233, -105.244478 40.052328, -105.245453 40.052321, -105.245776 40.052296, -105.246094 40.052252, -105.246899 40.052104, -105.247069 40.05209, -105.247755 40.052155, -105.248003 40.052201, -105.24818 40.052258, -105.248346 40.052333, -105.248793 40.052612, -105.249324 40.052987, -105.249885 40.053369, -105.250087 40.053468, -105.250261 40.053527, -105.25044 40.053566, -105.250631 40.053588, -105.251718 40.053586, -105.252836 40.053581, -105.252971 40.05359, -105.253168 40.05362, -105.253358 40.053668, -105.253597 40.053761, -105.253717 40.053612, -105.253916 40.053422, -105.254169 40.053238, -105.254489 40.05301, -105.254627 40.052881, -105.254701 40.052796, -105.254804 40.052647, -105.254828 40.052605, -105.254895 40.052452, -105.254938 40.052294, -105.254952 40.052132, -105.254953 40.051738, -105.254953 40.051275, -105.254953 40.05104, -105.256044 40.051033, -105.256681 40.051032, -105.257231 40.05103, -105.257384 40.05103, -105.258498 40.051028, -105.258729 40.051026, -105.258977 40.051027, -105.259004 40.051027, -105.259032 40.051028, -105.25918 40.051029, -105.259225 40.051029, -105.259225 40.05114, -105.259967 40.051181, -105.260246 40.051456, -105.260372 40.05158, -105.260495 40.051701, -105.260648 40.05163, -105.260738 40.05172, -105.260823 40.051804, -105.260976 40.051946, -105.261084 40.052039, -105.261245 40.052173, -105.261493 40.052371, -105.261766 40.052576, -105.262003 40.052745, -105.262336 40.05297, -105.262508 40.053078, -105.262746 40.053226, -105.262812 40.053268, -105.263669 40.0538, -105.264371 40.054233, -105.265113 40.054702, -105.265431 40.054897, -105.265652 40.055032, -105.265757 40.055098, -105.266875 40.0558, -105.268224 40.056628, -105.269614 40.057495, -105.27048 40.05801, -105.270802 40.058204, -105.271391 40.058578, -105.273072 40.05964, -105.276746 40.061929, -105.279457 40.063606, -105.279975 40.063936, -105.280042 40.063983, -105.279982 40.064187, -105.279955 40.064241, -105.279882 40.064345, -105.279887 40.064486, -105.279887 40.064513, -105.279887 40.064681, -105.279914 40.064713, -105.28005 40.064876, -105.280173 40.064999, -105.280213 40.065112, -105.280275 40.065397, -105.280318 40.0657, -105.28033 40.065754, -105.280413 40.065835, -105.280507 40.065853, -105.280777 40.065879, -105.28087 40.065861, -105.281104 40.065779, -105.281175 40.065779, -105.281245 40.065797, -105.281292 40.065824, -105.281304 40.065905, -105.281235 40.066085, -105.281083 40.066158, -105.280884 40.06623, -105.280778 40.066312, -105.280743 40.066366, -105.280744 40.066528, -105.280815 40.066672, -105.280945 40.066816, -105.281098 40.067005, -105.281345 40.067247, -105.281568 40.067409, -105.281662 40.067436, -105.281721 40.067436, -105.282002 40.067381, -105.282117 40.067375, -105.282116 40.067328, -105.282143 40.067266, -105.282172 40.067224, -105.282209 40.067186, -105.282253 40.067154, -105.282536 40.06701, -105.282671 40.06696, -105.282622 40.066796, -105.282561 40.066635, -105.282451 40.066394, -105.282345 40.066184, -105.282231 40.065975, -105.282112 40.065769, -105.281994 40.0656, -105.282642 40.065555, -105.282818 40.065555, -105.283927 40.065557, -105.284032 40.065557, -105.2841 40.065561, -105.284199 40.065581, -105.284245 40.065597, -105.284303 40.065624, -105.284429 40.065716, -105.285534 40.066588, -105.285599 40.066633, -105.285686 40.066676, -105.285766 40.066703, -105.28585 40.066721, -105.285971 40.066731, -105.286121 40.066731, -105.286117 40.067095, -105.285481 40.067094, -105.28441 40.06676, -105.284009 40.066704, -105.283452 40.066391, -105.283087 40.066453, -105.283042 40.066501, -105.283182 40.066914, -105.283689 40.067211, -105.284227 40.067304, -105.285322 40.067643, -105.286111 40.06765, -105.286198 40.067695, -105.286244 40.067714, -105.286341 40.067744, -105.286408 40.067759, -105.286494 40.067772, -105.286563 40.067777, -105.286654 40.067777, -105.286823 40.067759, -105.286905 40.067741, -105.286985 40.067718, -105.287062 40.06769, -105.287154 40.067647, -105.287222 40.067608, -105.287244 40.067648, -105.287262 40.067709, -105.287286 40.067769, -105.287347 40.067881, -105.287394 40.067943, -105.287453 40.067998, -105.287602 40.068083, -105.287688 40.06811, -105.287776 40.068126, -105.287864 40.068131, -105.28803 40.068113, -105.288103 40.068097, -105.288219 40.068069, -105.288239 40.068063, -105.288291 40.068051, -105.288358 40.06803, -105.28848 40.067997, -105.288643 40.067954, -105.288725 40.067932, -105.288803 40.067912, -105.288991 40.067867, -105.289038 40.067854, -105.28908 40.067942, -105.289113 40.068016, -105.289144 40.068089, -105.289207 40.068231, -105.289237 40.068298, -105.289265 40.068362, -105.289288 40.068417, -105.289306 40.068465, -105.28932 40.068534, -105.289319 40.068586, -105.289306 40.068635, -105.289274 40.068689, -105.289361 40.068713, -105.289444 40.068736, -105.289521 40.068756, -105.289587 40.068772, -105.289712 40.068751, -105.289722 40.068722, -105.289764 40.068624, -105.289794 40.068557, -105.289858 40.068404, -105.289891 40.068325, -105.289921 40.068249, -105.289945 40.068176, -105.289953 40.0681, -105.289928 40.06795, -105.289902 40.067881, -105.289877 40.067816, -105.289832 40.067706, -105.289819 40.067659, -105.289815 40.067635, -105.289833 40.067578, -105.289848 40.067534, -105.289871 40.067485, -105.289903 40.067425, -105.289939 40.067361, -105.28998 40.067294, -105.290023 40.067227, -105.290069 40.067154, -105.290115 40.067081, -105.290169 40.067001, -105.290222 40.066923, -105.290276 40.066843, -105.29033 40.066764, -105.29038 40.066683, -105.290428 40.066604, -105.290465 40.066546, -105.290641 40.066314, -105.290839 40.066016, -105.290954 40.065843, -105.290981 40.065766, -105.291004 40.065689, -105.291013 40.06561, -105.291008 40.065543, -105.290988 40.065502, -105.290959 40.065477, -105.290916 40.065468, -105.290848 40.065455, -105.290408 40.065447, -105.290028 40.065446, -105.289917 40.065446, -105.289792 40.065443, -105.289666 40.065438, -105.289542 40.065436, -105.289418 40.065436, -105.289295 40.065438, -105.289176 40.06544, -105.288945 40.065441, -105.288836 40.065441, -105.288733 40.065441, -105.288637 40.065442, -105.288469 40.065451, -105.288399 40.065454, -105.288365 40.065452, -105.288364 40.065339, -105.288388 40.065295, -105.28837 40.064809, -105.288352 40.064256, -105.288352 40.064031, -105.28836 40.063894, -105.288359 40.063847, -105.288362 40.06378, -105.288352 40.063613, -105.288343 40.063462, -105.288347 40.063386, -105.288436 40.063135, -105.288476 40.063077, -105.288527 40.063035, -105.288561 40.062994, -105.288607 40.062923, -105.28872 40.062981, -105.290436 40.063814, -105.290757 40.063968, -105.290958 40.064029, -105.291195 40.064072, -105.291854 40.064125, -105.292711 40.064183, -105.292968 40.06418, -105.293222 40.064149, -105.293981 40.063987, -105.294541 40.063857, -105.29501 40.063737, -105.295095 40.063289, -105.295227 40.06281, -105.29546 40.06152, -105.295596 40.059768, -105.295613 40.058201, -105.295621 40.057496, -105.29567 40.056232, -105.295674 40.055137, -105.295527 40.054009, -105.29543 40.05345, -105.295226 40.052869, -105.294886 40.052275, -105.294576 40.05185, -105.294452 40.051522, -105.295317 40.047875, -105.295546 40.047285, -105.295614 40.046983, -105.297005 40.043429, -105.296651 40.043118, -105.296516 40.042999, -105.296571 40.042671, -105.295833 40.040816, -105.296084 40.039732, -105.296387 40.03737, -105.296421 40.037047, -105.296499 40.036306, -105.296529 40.036022, -105.296569 40.035653, -105.29657 40.035415, -105.296573 40.034832, -105.296576 40.034412, -105.296564 40.032896, -105.296232 40.032464, -105.295618 40.031666, -105.294702 40.030475, -105.293784 40.029282, -105.29468 40.027669, -105.294885 40.027298, -105.295481 40.026226, -105.295907 40.025458, -105.295943 40.025318, -105.296101 40.024075, -105.296137 40.023796, -105.296138 40.023502, -105.296077 40.023217, -105.296029 40.023105, -105.296001 40.023049, -105.296036 40.023008, -105.296153 40.022927, -105.296199 40.02281, -105.296211 40.022683, -105.296245 40.022431, -105.296244 40.02226, -105.296302 40.022052, -105.296453 40.021791, -105.296517 40.021732, -105.296545 40.021705, -105.296606 40.021648, -105.296731 40.021535, -105.296712 40.021526, -105.296638 40.021481, -105.296579 40.021465, -105.296375 40.021464, -105.296065 40.021448, -105.295847 40.021399, -105.295293 40.021233, -105.295156 40.021044, -105.294711 40.020787, -105.29452 40.02067, -105.295098 40.020695, -105.295323 40.020687, -105.295544 40.020661, -105.296063 40.020551, -105.296234 40.020399, -105.296096 40.020224, -105.295292 40.019296, -105.295193 40.018089, -105.294374 40.017359, -105.294058 40.017076, -105.294018 40.016613, -105.29394 40.015707, -105.294059 40.015606, -105.294087 40.015569, -105.294188 40.015439, -105.294416 40.015313, -105.294685 40.015147, -105.294743 40.015059, -105.294731 40.014944, -105.294743 40.014848, -105.294848 40.014646, -105.294939 40.014531, -105.295026 40.01442, -105.295048 40.014392, -105.295065 40.014371, -105.295248 40.014265, -105.296169 40.01374, -105.296224 40.013718, -105.296353 40.01367, -105.296527 40.013616, -105.29675 40.013561, -105.296888 40.013536, -105.297075 40.01351, -105.297265 40.013496, -105.297504 40.013491, -105.297644 40.013496, -105.297786 40.013506, -105.297972 40.013529, -105.298156 40.013561, -105.298335 40.013603, -105.298519 40.013658, -105.299025 40.013843, -105.298939 40.013964, -105.299735 40.014284, -105.299796 40.014309, -105.300134 40.014445, -105.300841 40.014445, -105.301001 40.014428, -105.301519 40.014371, -105.301504 40.014134, -105.302633 40.012903, -105.305259 40.012549, -105.305991 40.012451, -105.30613 40.012386, -105.306231 40.012368, -105.306477 40.012268, -105.307506 40.011797, -105.308056 40.011669, -105.308255 40.01166, -105.308399 40.011701, -105.308502 40.011731, -105.308748 40.011875, -105.308996 40.012126, -105.309031 40.012243, -105.309032 40.012297, -105.308985 40.012424, -105.308858 40.01273, -105.3088 40.012974, -105.308778 40.013226, -105.308826 40.013469, -105.308956 40.013658, -105.309108 40.013766, -105.309214 40.013793, -105.309343 40.013783, -105.30946 40.013738, -105.309647 40.013647, -105.309822 40.013611, -105.309998 40.013637, -105.310233 40.013709, -105.310444 40.013825, -105.310691 40.013996, -105.31095 40.014148, -105.311267 40.014427, -105.31135 40.014562, -105.311339 40.01467, -105.311281 40.014832, -105.311165 40.01504, -105.311142 40.015094, -105.311177 40.015229, -105.311295 40.0154, -105.311425 40.015499, -105.311507 40.015498, -105.311858 40.015434, -105.312314 40.015217, -105.312443 40.015199, -105.312982 40.015233, -105.313685 40.015276, -105.314212 40.015257, -105.314294 40.01523, -105.314364 40.015148, -105.314375 40.015013, -105.31457 40.014139, -105.314675 40.013886, -105.314896 40.013633, -105.315375 40.013199, -105.315573 40.01301, -105.315725 40.012901, -105.3159 40.012838, -105.316111 40.012801, -105.316322 40.0128, -105.316545 40.012845, -105.317096 40.013051, -105.317437 40.013212, -105.31773 40.013328, -105.31793 40.013364, -105.318164 40.013399, -105.318469 40.013488, -105.31861 40.013551, -105.318799 40.013704, -105.319081 40.014009, -105.31912 40.014066, -105.319199 40.01418, -105.319411 40.014459, -105.319518 40.014666, -105.319565 40.014792, -105.319565 40.014837, -105.319578 40.014918, -105.319672 40.014999, -105.319766 40.015043, -105.319977 40.015115, -105.320469 40.015168, -105.320809 40.015158, -105.320926 40.015121, -105.320984 40.015049, -105.321007 40.014905, -105.321019 40.014774, -105.321028 40.014727, -105.321315 40.014647, -105.321891 40.015364, -105.321919 40.015403, -105.321957 40.015435, -105.322087 40.015492, -105.322149 40.015535, -105.322201 40.015597, -105.322252 40.01567, -105.322293 40.015744, -105.32231 40.015789, -105.322303 40.015819, -105.322422 40.016059, -105.32282 40.016189, -105.32319 40.016183, -105.323725 40.016183, -105.324016 40.016194, -105.324204 40.016417, -105.324261 40.016485, -105.324382 40.016547, -105.324382 40.016494, -105.324373 40.016442, -105.324363 40.016386, -105.324344 40.016261, -105.324314 40.016127, -105.324293 40.016066, -105.324273 40.015925, -105.324126 40.015872, -105.324086 40.015853, -105.324002 40.015833, -105.323529 40.015727, -105.323425 40.015698, -105.323332 40.015612, -105.323363 40.015571, -105.32339 40.015559, -105.32361 40.015613, -105.323836 40.015615, -105.323922 40.015604, -105.324256 40.015478, -105.324165 40.015381, -105.324053 40.015299, -105.324291 40.015328, -105.324432 40.015341, -105.3247 40.015348, -105.325 40.015333, -105.325145 40.015319, -105.325847 40.01522, -105.326211 40.01515, -105.326378 40.0151, -105.326474 40.015059, -105.326554 40.015009, -105.326627 40.014951, -105.326722 40.014854, -105.326765 40.014787, -105.326789 40.014749, -105.326838 40.014658, -105.32689 40.014543, -105.326913 40.014459, -105.326921 40.014351, -105.326911 40.014248, -105.326876 40.014131, -105.32682 40.014005, -105.326713 40.013855, -105.326615 40.013757, -105.326508 40.013678, -105.326366 40.01359, -105.32625 40.013544, -105.325811 40.01345, -105.325575 40.013405, -105.325348 40.01334, -105.325148 40.013247, -105.324945 40.013104, -105.324835 40.012982, -105.324742 40.012832, -105.324697 40.012596, -105.324705 40.012474, -105.324746 40.012307, -105.324807 40.012189, -105.324864 40.012108, -105.324933 40.012023, -105.325075 40.011905, -105.325356 40.011718, -105.325544 40.011594, -105.326002 40.011323, -105.326189 40.011205, -105.326287 40.011108, -105.326397 40.010941, -105.326482 40.010766, -105.326511 40.010579, -105.326482 40.010388, -105.326438 40.010282, -105.326279 40.009892, -105.326125 40.009572, -105.326062 40.009385, -105.326019 40.009111, -105.325704 40.009073, -105.325683 40.009071, -105.325447 40.009086, -105.325024 40.009116, -105.324886 40.008962, -105.324924 40.008871, -105.324949 40.008755, -105.324913 40.008579, -105.324903 40.008491, -105.324771 40.008166, -105.324359 40.007595, -105.324032 40.007394, -105.323922 40.007336, -105.323741 40.007262, -105.323492 40.007226, -105.323034 40.007197, -105.322842 40.007212, -105.322707 40.007212, -105.322671 40.007204, -105.322595 40.007188, -105.322547 40.007145, -105.322643 40.007059, -105.32281 40.00701, -105.323074 40.006962, -105.323504 40.006864, -105.323616 40.006833, -105.323743 40.006784, -105.323807 40.006736, -105.323791 40.006589, -105.323799 40.006436, -105.323775 40.006338, -105.323696 40.00618, -105.323608 40.006088, -105.323568 40.006106, -105.323496 40.006167, -105.323488 40.006277, -105.323504 40.006467, -105.323472 40.006534, -105.323369 40.006595, -105.323281 40.006601, -105.323169 40.006552, -105.323097 40.00651, -105.322954 40.006448, -105.322802 40.006412, -105.322675 40.0064, -105.322539 40.006424, -105.322388 40.006473, -105.322284 40.006577, -105.322125 40.006687, -105.321997 40.006754, -105.321821 40.006803, -105.321718 40.006803, -105.321518 40.006748, -105.321343 40.006687, -105.321207 40.006668, -105.321008 40.006668, -105.320825 40.006699, -105.320697 40.006772, -105.320585 40.006797, -105.320402 40.006784, -105.320346 40.00676, -105.320362 40.006711, -105.320474 40.006632, -105.320617 40.006577, -105.320729 40.00651, -105.32108 40.006418, -105.321215 40.00643, -105.321335 40.006424, -105.321447 40.006381, -105.321518 40.006296, -105.321534 40.006186, -105.321574 40.006039, -105.321613 40.005846, -105.321774 40.005618, -105.321861 40.005514, -105.321932 40.005235, -105.321919 40.004959, -105.321885 40.004873, -105.321763 40.004663, -105.321679 40.004611, -105.321427 40.004519, -105.321125 40.004341, -105.320885 40.004036, -105.320556 40.003709, -105.319766 40.003359, -105.319214 40.003224, -105.318684 40.003132, -105.31807 40.002947, -105.317665 40.002732, -105.317183 40.002321, -105.316889 40.002003, -105.316603 40.001841, -105.316266 40.001711, -105.315905 40.001701, -105.315508 40.001803, -105.315147 40.002051, -105.314947 40.002125, -105.31476 40.002074, -105.314585 40.001971, -105.314369 40.001702, -105.313635 40.001477, -105.313256 40.000979, -105.313174 40.000755, -105.313114 40.000718, -105.313018 40.000711, -105.312938 40.000765, -105.312958 40.000916, -105.313058 40.001247, -105.313122 40.001609, -105.313121 40.00179, -105.313192 40.001923, -105.313327 40.001976, -105.313683 40.00198, -105.313774 40.002051, -105.313748 40.002166, -105.313631 40.002204, -105.31337 40.002168, -105.313138 40.002079, -105.312912 40.001984, -105.312743 40.001837, -105.312667 40.00155, -105.312532 40.001282, -105.312388 40.000952, -105.31227 40.00074, -105.312134 40.000666, -105.311976 40.000598, -105.311742 40.000509, -105.311636 40.000427, -105.311572 40.000287, -105.311558 40.000219, -105.311559 40.000185, -105.311564 40.000165, -105.311601 40.000002, -105.311605 39.999865, -105.311531 39.999436, -105.311471 39.999229, -105.311449 39.99907, -105.31145 39.99891, -105.311466 39.998789, -105.311495 39.998752, -105.311566 39.99872, -105.311651 39.998725, -105.311702 39.998748, -105.311899 39.998969, -105.311954 39.998997, -105.312017 39.999009, -105.312113 39.998994, -105.312166 39.998964, -105.312202 39.998923, -105.312277 39.998719, -105.312287 39.998612, -105.312275 39.998505, -105.312139 39.997968, -105.312142 39.99792, -105.312167 39.997876, -105.312211 39.997842, -105.312268 39.997822, -105.312477 39.997819, -105.312672 39.997852, -105.312794 39.997896, -105.313238 39.998115, -105.313415 39.998183, -105.313636 39.99825, -105.313715 39.998296, -105.313779 39.998356, -105.314117 39.998939, -105.314205 39.999055, -105.314311 39.999161, -105.314476 39.999283, -105.314559 39.999319, -105.314651 39.99934, -105.314746 39.999343, -105.315167 39.999268, -105.315228 39.999224, -105.315246 39.999182, -105.315241 39.999139, -105.315215 39.999101, -105.315171 39.999074, -105.315116 39.999062, -105.314985 39.99908, -105.314779 39.999126, -105.314704 39.999128, -105.314633 39.999113, -105.31457 39.999081, -105.314523 39.999037, -105.314495 39.998983, -105.31444 39.998786, -105.314279 39.998381, -105.314174 39.998144, -105.314076 39.997991, -105.31402 39.997938, -105.313947 39.997897, -105.313617 39.997786, -105.313502 39.997731, -105.313401 39.997662, -105.313317 39.997581, -105.313213 39.997465, -105.31307 39.997372, -105.312685 39.997247, -105.312552 39.997191, -105.31241 39.997151, -105.312336 39.997138, -105.311766 39.997124, -105.311627 39.997135, -105.311492 39.997164, -105.311365 39.997208, -105.311111 39.997346, -105.311017 39.997374, -105.310918 39.997385, -105.310649 39.997375, -105.310325 39.997421, -105.310153 39.997422, -105.309983 39.997405, -105.309817 39.99737, -105.309658 39.997319, -105.309339 39.997193, -105.309229 39.99714, -105.309056 39.99711, -105.309007 39.997077, -105.308884 39.997193, -105.308756 39.99727, -105.308445 39.997547, -105.308274 39.997648, -105.307958 39.997721, -105.307729 39.997739, -105.307383 39.997793, -105.307059 39.997915, -105.306793 39.998112, -105.306619 39.998257, -105.306333 39.998477, -105.306114 39.99861, -105.305859 39.998788, -105.305173 39.99919, -105.304923 39.999274, -105.304738 39.999362, -105.304578 39.9995, -105.304333 39.999722, -105.304179 39.99983, -105.304014 39.999879, -105.303703 39.999954, -105.30327 40.000012, -105.303062 40.000067, -105.302673 40.000199, -105.302497 40.000273, -105.302345 40.000371, -105.302232 40.000493, -105.302155 40.000635, -105.302078 40.000664, -105.301909 40.000701, -105.301726 40.000693, -105.301443 40.000647, -105.301248 40.00063, -105.30106 40.000643, -105.300793 40.000701, -105.300634 40.000761, -105.300415 40.000874, -105.300092 40.00108, -105.299933 40.001229, -105.299817 40.001408, -105.299755 40.001619, -105.299761 40.001837, -105.299848 40.0022, -105.299815 40.002336, -105.29973 40.002445, -105.299662 40.002472, -105.299257 40.002741, -105.298828 40.00305, -105.298718 40.003147, -105.298608 40.003142, -105.298568 40.003103, -105.298577 40.003085, -105.298562 40.002964, -105.298675 40.002899, -105.298807 40.002798, -105.298942 40.002676, -105.299027 40.00266, -105.299147 40.002538, -105.299271 40.002423, -105.299292 40.002361, -105.299298 40.002299, -105.299278 40.002161, -105.299226 40.002011, -105.299145 40.001613, -105.299107 40.001373, -105.299119 40.001306, -105.299155 40.001179, -105.299221 40.000995, -105.299248 40.000794, -105.299302 40.000677, -105.299552 40.000361, -105.299801 40.000073, -105.299893 39.999984, -105.299945 39.999948, -105.300049 39.999827, -105.300048 39.999747, -105.300015 39.999718, -105.299967 39.999703, -105.299914 39.999704, -105.29986 39.999719, -105.299764 39.999785, -105.29967 39.999876, -105.299482 40.000085, -105.299429 40.000136, -105.299304 40.00023, -105.29902 40.000404, -105.298886 40.0005, -105.298774 40.000608, -105.298732 40.00067, -105.298688 40.000809, -105.298652 40.000956, -105.298617 40.001018, -105.298532 40.001144, -105.29838 40.001333, -105.298274 40.001562, -105.298143 40.001742, -105.298083 40.001787, -105.297937 40.001854, -105.297591 40.001812, -105.297306 40.001807, -105.297118 40.001824, -105.296859 40.001842, -105.296591 40.001819, -105.296436 40.001753, -105.296317 40.001653, -105.29617 40.001454, -105.296051 40.001272, -105.295831 40.001146, -105.295709 40.001183, -105.29567 40.001218, -105.295656 40.001293, -105.295668 40.001341, -105.295699 40.001381, -105.295985 40.001583, -105.296167 40.001771, -105.296341 40.002033, -105.296382 40.002157, -105.296363 40.002296, -105.296278 40.00243, -105.296155 40.00255, -105.296026 40.002666, -105.295896 40.002768, -105.295737 40.002829, -105.295415 40.002917, -105.295211 40.002944, -105.295112 40.002901, -105.295091 40.002849, -105.295095 40.002792, -105.295199 40.002718, -105.295276 40.002686, -105.295364 40.002658, -105.295465 40.002662, -105.295568 40.002645, -105.295668 40.002614, -105.295755 40.002562, -105.295897 40.002433, -105.295954 40.002364, -105.295972 40.002291, -105.29597 40.002213, -105.295865 40.002101, -105.295785 40.002062, -105.295575 40.002008, -105.295251 40.001948, -105.295022 40.001941, -105.294814 40.001922, -105.294709 40.001902, -105.294475 40.001821, -105.294419 40.001731, -105.294386 40.001641, -105.294405 40.001583, -105.294473 40.00145, -105.294597 40.00134, -105.294732 40.001256, -105.294834 40.001177, -105.294867 40.001134, -105.294866 40.00109, -105.294788 40.001024, -105.294729 40.001036, -105.294673 40.001058, -105.294622 40.001095, -105.294386 40.001298, -105.29427 40.001418, -105.294226 40.001489, -105.294142 40.001641, -105.294021 40.001988, -105.294032 40.002169, -105.294083 40.002608, -105.294096 40.002786, -105.2941 40.002956, -105.29412 40.003125, -105.294151 40.003205, -105.294203 40.003277, -105.294274 40.003338, -105.294458 40.003428, -105.295116 40.003659, -105.29532 40.003746, -105.295481 40.003866, -105.295592 40.004023, -105.295642 40.004198, -105.295635 40.004396, -105.295525 40.004547, -105.295396 40.004705, -105.295241 40.004841, -105.295005 40.004998, -105.294969 40.005082, -105.29491 40.005388, -105.294883 40.005722, -105.294843 40.005928, -105.294813 40.006159, -105.294734 40.006341, -105.294664 40.006509, -105.294613 40.006616, -105.294539 40.006718, -105.29438 40.00685, -105.294256 40.006899, -105.29406 40.006947, -105.293794 40.006929, -105.293663 40.006863, -105.29361 40.006818, -105.293515 40.00672, -105.293337 40.006492, -105.293079 40.006202, -105.293024 40.006098, -105.292992 40.005892, -105.293072 40.005685, -105.293103 40.005528, -105.293099 40.005205, -105.293087 40.005013, -105.29312 40.004944, -105.293192 40.004822, -105.293187 40.00474, -105.293143 40.004673, -105.293066 40.004627, -105.292664 40.004549, -105.292383 40.004447, -105.292304 40.004397, -105.292205 40.004262, -105.292174 40.004092, -105.29219 40.004004, -105.292254 40.003826, -105.292338 40.003644, -105.292383 40.003443, -105.29238 40.003242, -105.29236 40.00306, -105.292282 40.002903, -105.292126 40.002774, -105.291676 40.002443, -105.291485 40.002239, -105.291393 40.002103, -105.291336 40.002, -105.291307 40.001946, -105.291243 40.00179, -105.291129 40.001496, -105.291065 40.0014, -105.290982 40.001313, -105.290944 40.001284, -105.290886 40.001241, -105.290763 40.001171, -105.290743 40.00116, -105.29064 40.001086, -105.290553 40.001001, -105.290486 40.000905, -105.290439 40.000803, -105.290415 40.000703, -105.29041 40.000642, -105.290419 40.000533, -105.290483 40.000305, -105.290522 40.000212, -105.290561 40.00012, -105.290576 40.000084, -105.290586 40.000061, -105.290617 39.99999, -105.29067 39.999863, -105.290712 39.999725, -105.290719 39.9997, -105.290759 39.999419, -105.290747 39.999363, -105.29071 39.999299, -105.290655 39.999251, -105.290586 39.999217, -105.290555 39.999209, -105.290517 39.9992, -105.290466 39.999195, -105.290345 39.999213, -105.290089 39.999315, -105.289722 39.9995, -105.289558 39.999566, -105.289386 39.999616, -105.288955 39.999691, -105.28874 39.999746, -105.28827 39.999914, -105.28805 39.999999, -105.287907 40.000037, -105.287773 40.000056, -105.287662 40.000062, -105.286756 40.000061, -105.286647 40.000061, -105.286644 39.999961, -105.285868 39.999959, -105.285421 39.999959, -105.284256 39.99996, -105.284008 39.999961, -105.283445 39.999962, -105.283446 40.000051, -105.283237 40.000047, -105.283025 40.000044, -105.283003 39.999916, -105.28299 39.999871, -105.282964 39.999784, -105.28301 39.999707, -105.283018 39.999656, -105.282943 39.999442, -105.282922 39.999313, -105.282873 39.998608, -105.282828 39.998262, -105.282793 39.997824, -105.282668 39.996569, -105.282619 39.996337, -105.282541 39.995779, -105.282558 39.995343, -105.282607 39.99513, -105.282659 39.994998, -105.282737 39.99489, -105.282796 39.994825, -105.282742 39.994317, -105.28266 39.99353, -105.282579 39.993633, -105.282463 39.993823, -105.28237 39.993958, -105.282207 39.994175, -105.282067 39.9944, -105.281939 39.99459, -105.281852 39.994748, -105.2818 39.994842, -105.281704 39.994991, -105.281692 39.994984, -105.281571 39.994953, -105.281517 39.994957, -105.281278 39.995047, -105.281028 39.995118, -105.280903 39.995184, -105.280803 39.99527, -105.280776 39.995303, -105.280638 39.995387, -105.280429 39.995467, -105.280261 39.995503, -105.279517 39.995557, -105.279201 39.995606, -105.278815 39.995695, -105.278679 39.995693, -105.278554 39.995659, -105.278481 39.995617, -105.278359 39.995491, -105.278247 39.995327, -105.278165 39.995154, -105.278127 39.994999, -105.278141 39.994896, -105.278188 39.994799, -105.278267 39.994714, -105.278626 39.994436, -105.278979 39.994181, -105.279184 39.994006, -105.279453 39.993756, -105.279641 39.993625, -105.279845 39.993507, -105.280063 39.993406, -105.280418 39.993266, -105.280708 39.993171, -105.280974 39.993103, -105.281072 39.993047, -105.281142 39.992969, -105.281224 39.992843, -105.281266 39.992782, -105.27987 39.992824, -105.27749 39.992885, -105.277488 39.992791, -105.277146 39.992786, -105.273803 39.992873, -105.273729 39.99487, -105.273723 39.995372, -105.273589 39.995414, -105.273603 39.995464, -105.273602 39.995759, -105.27361 39.995796, -105.273609 39.996092, -105.273712 39.996095, -105.273718 39.996302, -105.272502 39.996216, -105.271245 39.996205, -105.271218 39.996161, -105.271176 39.996142, -105.270787 39.99615, -105.270766 39.995641, -105.270767 39.995267, -105.270776 39.992796, -105.270445 39.992785, -105.267251 39.992813, -105.264531 39.992837, -105.264284 39.992479, -105.264638 39.992299, -105.264762 39.992266, -105.264893 39.992255, -105.265044 39.992273, -105.265222 39.992321, -105.265319 39.99236, -105.265643 39.992532, -105.265794 39.992573, -105.266005 39.9926, -105.266338 39.992601, -105.266745 39.992574, -105.26691 39.992537, -105.267061 39.992476, -105.267187 39.992392, -105.26729 39.99229, -105.267348 39.992205, -105.267367 39.99204, -105.267349 39.991876, -105.267296 39.991764, -105.267212 39.991665, -105.266718 39.991257, -105.266085 39.990767, -105.26596 39.990695, -105.265774 39.990623, -105.265523 39.990569, -105.265315 39.990555, -105.265109 39.990575, -105.264842 39.990646, -105.264714 39.990693, -105.264492 39.990829, -105.264294 39.990968, -105.263811 39.991285, -105.263691 39.991374, -105.263678 39.991397, -105.26366 39.991441, -105.263654 39.991512, -105.263678 39.99159, -105.263561 39.991461, -105.261714 39.989439, -105.260628 39.989413, -105.260626 39.989339, -105.269135 39.989277, -105.270103 39.98998, -105.270969 39.989084, -105.271398 39.989099, -105.271911 39.989117, -105.276919 39.98928, -105.277045 39.989285, -105.276853 39.98582, -105.276847 39.985714, -105.276845 39.985685, -105.269559 39.985118, -105.269425 39.984875, -105.269313 39.984874, -105.269205 39.984874, -105.268967 39.984859, -105.268596 39.984808, -105.268231 39.984724, -105.267933 39.984632, -105.267683 39.984532, -105.267273 39.984336, -105.267173 39.984283, -105.266313 39.983825, -105.266786 39.983265, -105.26686 39.983132, -105.266885 39.983063, -105.266896 39.982567, -105.266886 39.982376, -105.266854 39.982279, -105.266799 39.982189, -105.266459 39.981861, -105.266198 39.981636, -105.264899 39.982475, -105.264576 39.982693, -105.264425 39.982779, -105.263861 39.982257, -105.264786 39.98165, -105.265473 39.981212, -105.265555 39.981128, -105.265614 39.981018, -105.265662 39.980698, -105.264725 39.980633, -105.264727 39.980606, -105.264713 39.980502, -105.264666 39.980403, -105.26444 39.980133, -105.263624 39.979247, -105.263515 39.979122, -105.2634 39.97899, -105.263374 39.978916, -105.263372 39.978839, -105.263396 39.978764, -105.263424 39.978717, -105.263485 39.978656, -105.263996 39.978388, -105.264738 39.977993, -105.264966 39.977874, -105.265139 39.977815, -105.265185 39.977805, -105.265404 39.977754, -105.266061 39.977667, -105.266536 39.977586, -105.266637 39.977545, -105.266724 39.977487, -105.266792 39.977416, -105.266838 39.977336, -105.26686 39.97725, -105.266866 39.97709, -105.266866 39.976649, -105.266866 39.976405, -105.267311 39.97634, -105.267403 39.976193, -105.267462 39.976023, -105.267897 39.975746, -105.269297 39.975581, -105.27061 39.974877, -105.2713 39.973496, -105.271315 39.973466, -105.271587 39.973228, -105.271332 39.9729, -105.270386 39.972568, -105.270243 39.972509, -105.270015 39.972417, -105.269663 39.972296, -105.269507 39.972231, -105.269192 39.971996, -105.268945 39.971768, -105.268818 39.971674, -105.268718 39.971577, -105.268648 39.971508, -105.268559 39.971421, -105.26817 39.971414, -105.266721 39.971389, -105.266544 39.971267, -105.266135 39.97091, -105.26579 39.970639, -105.265479 39.970416, -105.265137 39.970148, -105.264767 39.969854, -105.264704 39.969805, -105.264386 39.96956, -105.264064 39.969296, -105.264005 39.969248, -105.263923 39.969183, -105.263692 39.968999, -105.26347 39.968822, -105.263362 39.968737, -105.263239 39.968639, -105.263117 39.968541, -105.262903 39.968371, -105.262496 39.968047, -105.262168 39.967787, -105.262015 39.967665, -105.261502 39.967265, -105.260843 39.967263, -105.260089 39.967261, -105.260088 39.966873, -105.260088 39.966368, -105.260086 39.965637, -105.260083 39.964452, -105.259676 39.964448, -105.254938 39.964397, -105.251747 39.964364, -105.248881 39.964351, -105.248526 39.965496, -105.248111 39.966483, -105.247965 39.966602, -105.247526 39.967675, -105.247445 39.967874, -105.246299 39.968058, -105.246195 39.968172, -105.246085 39.968291, -105.247067 39.968784, -105.246435 39.970297, -105.246382 39.970417, -105.246313 39.97058, -105.244774 39.970574, -105.244452 39.970574, -105.243439 39.970573, -105.243145 39.970572, -105.241425 39.97057, -105.238604 39.970565, -105.238504 39.970581, -105.238293 39.97063, -105.237949 39.970721, -105.237414 39.970872, -105.237388 39.970879, -105.237324 39.970884, -105.23727 39.970869, -105.237217 39.970826, -105.237167 39.970662, -105.237174 39.970336, -105.237207 39.970023, -105.237221 39.969856, -105.237361 39.969178, -105.237505 39.968614, -105.237488 39.968558, -105.237453 39.968536, -105.23735 39.968507, -105.237547 39.967718, -105.237575 39.967604, -105.237604 39.967477, -105.237614 39.967432, -105.237733 39.966911, -105.237871 39.966215, -105.23791 39.96583, -105.237924 39.965575, -105.237925 39.965189, -105.237903 39.964805, -105.237894 39.964706, -105.237869 39.964445, -105.237837 39.96422, -105.237798 39.963992, -105.237719 39.963655, -105.237657 39.963432, -105.237586 39.96321, -105.237488 39.962963, -105.237451 39.962869, -105.237239 39.962412, -105.237122 39.962187, -105.236932 39.961852, -105.236766 39.961604, -105.236561 39.961327, -105.236094 39.96075, -105.236006 39.960658, -105.235904 39.960552, -105.23575 39.960406, -105.235589 39.960253, -105.235258 39.959962, -105.234912 39.959683, -105.23392 39.958939, -105.233573 39.95869, -105.233716 39.95862, -105.234037 39.958482, -105.234458 39.958312, -105.235243 39.958235, -105.235507 39.958108, -105.235778 39.957888, -105.235878 39.957559, -105.236006 39.957377, -105.236135 39.957268, -105.23632 39.957196, -105.236698 39.957064, -105.237019 39.956987, -105.237298 39.956817, -105.237397 39.956701, -105.237396 39.956658, -105.237394 39.956588, -105.237383 39.956213, -105.237461 39.956042, -105.237647 39.955779, -105.237882 39.955581, -105.238132 39.955498, -105.238267 39.955443, -105.238401 39.955406, -105.23856 39.955361, -105.239223 39.95519, -105.239323 39.955069, -105.239459 39.954696, -105.239694 39.953987, -105.239801 39.953822, -105.23995 39.953729, -105.240069 39.953691, -105.240471 39.953564, -105.241271 39.953373, -105.241577 39.953217, -105.241734 39.953008, -105.241905 39.95286, -105.241991 39.952816, -105.242233 39.952525, -105.242547 39.951975, -105.242547 39.951789, -105.242439 39.951635, -105.242304 39.951404, -105.242139 39.951097, -105.242125 39.95102, -105.242189 39.950833, -105.24236 39.950454, -105.24251 39.950311, -105.243102 39.950014, -105.243401 39.949888, -105.243665 39.949729, -105.243729 39.949652, -105.243915 39.949294, -105.244022 39.949179, -105.245242 39.949118, -105.245413 39.949058, -105.245955 39.9487, -105.246226 39.948574, -105.24689 39.94837, -105.247018 39.948227, -105.247196 39.948041, -105.247396 39.947947, -105.247674 39.947843, -105.24786 39.947562, -105.247845 39.947134, -105.247973 39.94692, -105.248116 39.946804, -105.248601 39.94659, -105.248729 39.94648, -105.248857 39.946233, -105.249014 39.945738, -105.249249 39.945529, -105.249613 39.94543, -105.250576 39.945408, -105.250799 39.945422, -105.251068 39.945394, -105.251213 39.945372, -105.251372 39.945348, -105.251443 39.945325, -105.251629 39.945267, -105.25191 39.945149, -105.252143 39.94504, -105.252587 39.944751, -105.252727 39.944634, -105.252844 39.944498, -105.252914 39.944354, -105.252924 39.944156, -105.2529 39.943948, -105.2529 39.943759, -105.252923 39.943669, -105.252993 39.943597, -105.253086 39.943552, -105.253227 39.943533, -105.253718 39.943595, -105.253906 39.943631, -105.254035 39.94364, -105.254175 39.943693, -105.254351 39.943738, -105.25448 39.943783, -105.254632 39.943828, -105.25482 39.943863, -105.254972 39.943872, -105.255101 39.943854, -105.255241 39.94379, -105.255322 39.943682, -105.255345 39.94352, -105.255356 39.943357, -105.255262 39.94315, -105.255214 39.942988, -105.255014 39.942646, -105.254979 39.942493, -105.254966 39.942367, -105.254978 39.942295, -105.255036 39.942187, -105.255153 39.942097, -105.255293 39.942033, -105.255456 39.94197, -105.255655 39.94187, -105.256204 39.941572, -105.256368 39.941508, -105.256473 39.941481, -105.256613 39.941481, -105.256813 39.941507, -105.256988 39.941516, -105.257211 39.941542, -105.257398 39.941533, -105.257585 39.941496, -105.25776 39.941415, -105.257889 39.941289, -105.258005 39.941135, -105.258098 39.940964, -105.258203 39.940747, -105.258319 39.94054, -105.258458 39.940233, -105.258575 39.940116, -105.25868 39.940025, -105.258948 39.939872, -105.259252 39.939664, -105.259392 39.939492, -105.259485 39.939357, -105.259578 39.939167, -105.259683 39.939014, -105.259811 39.938888, -105.259928 39.938815, -105.260033 39.938761, -105.260173 39.938779, -105.260396 39.938841, -105.260712 39.938913, -105.261192 39.938966, -105.261485 39.938974, -105.261672 39.938956, -105.261801 39.938892, -105.261917 39.938811, -105.261976 39.938721, -105.262033 39.938549, -105.262103 39.938414, -105.262184 39.938216, -105.262312 39.937945, -105.26258 39.937521, -105.26272 39.937358, -105.262813 39.937277, -105.26307 39.937159, -105.263269 39.93715, -105.26348 39.937177, -105.263714 39.937221, -105.263878 39.937302, -105.264054 39.937382, -105.264594 39.937796, -105.264735 39.937876, -105.26491 39.93793, -105.265297 39.937929, -105.265484 39.937893, -105.265648 39.937847, -105.265835 39.937784, -105.266022 39.937711, -105.266279 39.937585, -105.266571 39.937467, -105.266793 39.937403, -105.267085 39.937303, -105.267448 39.937221, -105.267763 39.937158, -105.268149 39.937094, -105.26843 39.93703, -105.268676 39.937002, -105.268933 39.936984, -105.269073 39.936956, -105.269225 39.936884, -105.269365 39.936803, -105.269482 39.93673, -105.269576 39.936703, -105.269716 39.936694, -105.269892 39.936675, -105.270055 39.936639, -105.270254 39.936548, -105.270441 39.936386, -105.270709 39.936196, -105.270724 39.936177, -105.27086 39.936006, -105.270977 39.935763, -105.271116 39.93551, -105.271337 39.935149, -105.271675 39.934869, -105.271885 39.934742, -105.272072 39.934661, -105.272365 39.934543, -105.272732 39.934417, -105.27282 39.934397, -105.272898 39.934359, -105.273056 39.934281, -105.273468 39.933988, -105.273738 39.933766, -105.274238 39.933429, -105.274447 39.933298, -105.274566 39.933264, -105.274586 39.933226, -105.274716 39.933173, -105.274986 39.933134, -105.275773 39.933097, -105.276215 39.933046, -105.276637 39.932972, -105.276886 39.932906, -105.277134 39.932818, -105.277298 39.932762, -105.27751 39.932654, -105.277675 39.932592, -105.277844 39.932547, -105.278199 39.932476, -105.278435 39.932434, -105.27861 39.932426, -105.278671 39.932426, -105.27874 39.932438, -105.2789 39.93248, -105.27926 39.932628, -105.279339 39.93266, -105.279579 39.932731, -105.27963 39.932747, -105.279668 39.932751, -105.279742 39.932744, -105.279857 39.932738, -105.279924 39.932735, -105.279973 39.932722, -105.280003 39.932703, -105.280019 39.932681, -105.280028 39.932648, -105.280044 39.932556, -105.280059 39.932492, -105.280058 39.932414, -105.280047 39.932331, -105.280143 39.932341, -105.280192 39.932335, -105.280286 39.9323, -105.280326 39.932281, -105.279962 39.93211, -105.279833 39.932039, -105.279669 39.93194, -105.279423 39.931868, -105.279247 39.93177, -105.278801 39.9316, -105.278591 39.931528, -105.27811 39.931475, -105.277923 39.931458, -105.277455 39.931432, -105.277338 39.931459, -105.277198 39.931495, -105.277046 39.931586, -105.276929 39.931694, -105.276801 39.931821, -105.276685 39.931956, -105.276545 39.932083, -105.276381 39.932191, -105.276218 39.932245, -105.275914 39.932336, -105.27582 39.932346, -105.275621 39.932346, -105.27534 39.93232, -105.275071 39.932284, -105.274919 39.932285, -105.274685 39.932303, -105.274439 39.932286, -105.274228 39.932322, -105.274041 39.932359, -105.273831 39.932395, -105.27362 39.932378, -105.273371 39.932342, -105.273058 39.932298, -105.272929 39.932316, -105.271094 39.932879, -105.269931 39.933237, -105.269916 39.933289, -105.269895 39.933359, -105.269878 39.933425, -105.269873 39.933487, -105.269881 39.933551, -105.269893 39.933598, -105.269911 39.933651, -105.269925 39.933713, -105.269933 39.933793, -105.269933 39.933843, -105.269928 39.933896, -105.269898 39.93406, -105.269885 39.934115, -105.26987 39.93417, -105.26983 39.93428, -105.269806 39.934336, -105.269795 39.934358, -105.26978 39.934391, -105.269752 39.934446, -105.269725 39.934501, -105.269666 39.934609, -105.269635 39.934663, -105.269606 39.934717, -105.269581 39.934773, -105.269543 39.934881, -105.26953 39.934935, -105.269509 39.935094, -105.269495 39.935193, -105.269487 39.935238, -105.269472 39.935321, -105.269461 39.935387, -105.269469 39.935485, -105.267787 39.935715, -105.266767 39.935838, -105.265969 39.935938, -105.265644 39.936009, -105.265012 39.936175, -105.264721 39.936262, -105.263711 39.936559, -105.262685 39.93687, -105.259652 39.937754, -105.257964 39.938262, -105.257552 39.938401, -105.257301 39.938519, -105.257074 39.938662, -105.25692 39.938784, -105.256817 39.938877, -105.256348 39.939386, -105.256162 39.939574, -105.255029 39.940785, -105.254866 39.940923, -105.25481 39.940961, -105.254609 39.941096, -105.254365 39.941231, -105.253883 39.941436, -105.252403 39.942004, -105.251942 39.942192, -105.251033 39.942535, -105.250551 39.942731, -105.250101 39.942901, -105.249278 39.943231, -105.248917 39.943361, -105.248162 39.943687, -105.247756 39.943898, -105.247441 39.944093, -105.247189 39.94427, -105.246862 39.944513, -105.246422 39.944864, -105.245934 39.945237, -105.24541 39.945647, -105.245045 39.945915, -105.244183 39.946577, -105.244093 39.946639, -105.243875 39.946814, -105.243464 39.947123, -105.242995 39.947493, -105.242605 39.947783, -105.242194 39.948106, -105.241423 39.948668, -105.241116 39.94885, -105.240716 39.949039, -105.239885 39.949406, -105.239256 39.949666, -105.238401 39.950036, -105.236887 39.950684, -105.237054 39.950896, -105.237138 39.951026, -105.237228 39.951248, -105.237246 39.95148, -105.237416 39.951903, -105.237441 39.952003, -105.237508 39.9522, -105.237504 39.952268, -105.237564 39.952408, -105.237581 39.95243, -105.236945 39.952582, -105.236338 39.952758, -105.235618 39.952951, -105.235125 39.953154, -105.234747 39.953407, -105.234155 39.953978, -105.233706 39.95455, -105.233178 39.955138, -105.233121 39.955171, -105.233057 39.955248, -105.232808 39.955462, -105.232287 39.955819, -105.232137 39.955946, -105.231787 39.956259, -105.231772 39.956271, -105.231536 39.956463, -105.231484 39.956505, -105.231354 39.956107, -105.231319 39.95573, -105.231322 39.955504, -105.231342 39.955278, -105.231366 39.95513, -105.231431 39.954879, -105.231521 39.954633, -105.231571 39.954526, -105.231629 39.954391, -105.231797 39.954056, -105.231941 39.953807, -105.232046 39.953642, -105.231993 39.953635, -105.231893 39.953624, -105.231465 39.953542, -105.231242 39.953554, -105.231083 39.953603, -105.230961 39.953705, -105.230851 39.953876, -105.230746 39.954229, -105.230709 39.954331, -105.230659 39.954453, -105.230457 39.954742, -105.230274 39.954933, -105.229635 39.955535, -105.229294 39.955893, -105.229095 39.956128, -105.228629 39.955966, -105.228175 39.955786, -105.227135 39.955305, -105.226864 39.955203, -105.226826 39.955189, -105.226516 39.955109, -105.226107 39.955046, -105.22579 39.955007, -105.225319 39.954951, -105.224713 39.954892, -105.224348 39.954852, -105.224113 39.954826, -105.223743 39.954786, -105.223548 39.954772, -105.223535 39.954863, -105.223547 39.954931, -105.223578 39.954996, -105.223664 39.955141, -105.22373 39.955292, -105.223763 39.955394, -105.223781 39.955371, -105.223872 39.955289, -105.223958 39.955242, -105.224089 39.955203, -105.224399 39.955316, -105.224776 39.955467, -105.224989 39.955519, -105.225182 39.955567, -105.225732 39.955676, -105.227162 39.955947, -105.227357 39.955994, -105.227388 39.956003, -105.227663 39.956094, -105.227934 39.956214, -105.228633 39.956625, -105.228714 39.956694, -105.22873 39.956707, -105.228868 39.956826, -105.228963 39.956907, -105.229678 39.957538, -105.229789 39.957636, -105.230826 39.958656, -105.231532 39.959321, -105.231899 39.959638, -105.23218 39.959881, -105.232351 39.960026, -105.232318 39.96007, -105.232026 39.960175, -105.231933 39.960307, -105.231833 39.960554, -105.231876 39.96085, -105.231826 39.961125, -105.231764 39.961251, -105.231741 39.96129, -105.231541 39.961696, -105.23147 39.961933, -105.231456 39.962163, -105.231727 39.962641, -105.231742 39.962756, -105.231706 39.962861, -105.231528 39.962987, -105.231385 39.963042, -105.231242 39.963048, -105.230735 39.962938, -105.230585 39.962916, -105.230443 39.962955, -105.230321 39.963064, -105.230257 39.963262, -105.230293 39.963454, -105.2303 39.963685, -105.230108 39.963888, -105.229801 39.964097, -105.229616 39.964328, -105.229551 39.964498, -105.229602 39.964745, -105.229694 39.964872, -105.229645 39.964949, -105.229673 39.965036, -105.229766 39.965086, -105.229909 39.965047, -105.230009 39.965058, -105.230044 39.965113, -105.230037 39.965196, -105.22988 39.965349, -105.229581 39.965437, -105.229174 39.965454, -105.228831 39.965432, -105.228303 39.965443, -105.228153 39.965482, -105.228039 39.965586, -105.227739 39.965921, -105.227311 39.96641, -105.227247 39.966531, -105.227204 39.966691, -105.227254 39.966877, -105.227269 39.967124, -105.227233 39.967399, -105.227141 39.967751, -105.226934 39.968097, -105.226813 39.968239, -105.22667 39.968388, -105.226527 39.968514, -105.22642 39.968663, -105.226506 39.968849, -105.226756 39.969239, -105.226835 39.969552, -105.226856 39.969794, -105.226807 39.969898, -105.226414 39.970151, -105.226007 39.970321, -105.225451 39.970624, -105.225101 39.970981, -105.224494 39.971338, -105.224073 39.971651, -105.223752 39.971882, -105.223567 39.972074, -105.22346 39.972393, -105.223431 39.972624, -105.223489 39.973475, -105.223475 39.973925, -105.223368 39.974381, -105.22314 39.975145, -105.222969 39.97632, -105.222784 39.977034, -105.222684 39.977309, -105.22257 39.977715, -105.22242 39.978056, -105.222278 39.978314, -105.221871 39.978776, -105.221586 39.979523, -105.221401 39.979957, -105.221214 39.9803, -105.220645 39.980037, -105.219933 39.979707, -105.218737 39.979153, -105.217754 39.978703, -105.217557 39.978613, -105.215036 39.977444, -105.213476 39.976721, -105.212833 39.976424, -105.212831 39.976562, -105.212824 39.977016, -105.212823 39.977222, -105.21282 39.978082, -105.21282 39.978267, -105.212833 39.980126, -105.212833 39.98092, -105.21283 39.983446, -105.212828 39.983766, -105.212824 39.98448, -105.212821 39.985105, -105.212839 39.986036, -105.212841 39.986144, -105.21282 39.987145, -105.21282 39.987441, -105.212821 39.987494, -105.212821 39.987944, -105.212823 39.989729, -105.21279 39.99033, -105.212725 39.990993, -105.212689 39.991397, -105.212654 39.991782, -105.212478 39.993489, -105.212457 39.993701, -105.212448 39.99408, -105.212475 39.994505, -105.212498 39.994991, -105.212482 39.995829, -105.21253 39.996143, -105.212562 39.996268, -105.212704 39.996713, -105.212744 39.996927, -105.212747 39.997163, -105.212739 39.998786, -105.212533 39.998864, -105.212469 39.998924, -105.212419 39.999281, -105.212426 39.999633, -105.212448 39.999792, -105.212433 40.000014, -105.212431 40.000067, -105.212424 40.00022, -105.212333 40.000985, -105.21194 40.001545, -105.211768 40.001957, -105.211665 40.002208, -105.211281 40.002362, -105.210988 40.002818, -105.211091 40.003351, -105.210826 40.00362, -105.210618 40.003622, -105.208079 40.003626, -105.206471 40.00363, -105.206359 40.003631, -105.205568 40.003639, -105.204293 40.00364, -105.203962 40.003619, -105.203798 40.0036, -105.20346 40.003543, -105.203295 40.003504, -105.202835 40.003361, -105.201867 40.00305, -105.201286 40.002873, -105.198875 40.0021, -105.198562 40.001965, -105.198341 40.001847, -105.197744 40.001444, -105.197126 40.001856, -105.196477 40.0023, -105.196251 40.00247, -105.196165 40.00258, -105.196114 40.002702, -105.196103 40.002819, -105.196111 40.002935, -105.196132 40.002994, -105.196199 40.003105, -105.196297 40.003201, -105.19642 40.003278, -105.196563 40.003331, -105.19672 40.003358, -105.196922 40.003372, -105.198484 40.003363, -105.198532 40.003739, -105.198578 40.004719, -105.198562 40.004905, -105.198512 40.005087, -105.19845 40.005206, -105.198398 40.005277, -105.198086 40.005591, -105.19797 40.005742, -105.197876 40.005902, -105.197788 40.006125, -105.197642 40.006671, -105.197485 40.007195, -105.197414 40.007395, -105.197306 40.007588, -105.197191 40.007738, -105.196945 40.007976, -105.196711 40.008143, -105.196477 40.008262, -105.195828 40.00856, -105.195423 40.008763, -105.194781 40.009056, -105.194641 40.009139, -105.194574 40.009215, -105.194546 40.009293, -105.194553 40.00938, -105.194583 40.009441, -105.19474 40.00967, -105.194766 40.009777, -105.194754 40.009899, -105.194767 40.010046, -105.194811 40.010119, -105.194844 40.010148, -105.194888 40.010173, -105.19496 40.010196, -105.195039 40.010201, -105.195216 40.010169, -105.19536 40.010125, -105.195766 40.009934, -105.196154 40.009764, -105.196544 40.009571, -105.196929 40.009408, -105.197039 40.009393, -105.197149 40.009408, -105.197244 40.009452, -105.197304 40.009505, -105.19689 40.00991, -105.19667 40.010171, -105.19648 40.010446, -105.195749 40.011661, -105.195581 40.011874, -105.195381 40.01207, -105.195127 40.012287, -105.194995 40.01244, -105.194921 40.012566, -105.194818 40.012795, -105.19464 40.013123, -105.194483 40.013471, -105.194325 40.013792, -105.194247 40.014026, -105.194217 40.014206, -105.194212 40.014266, -105.194233 40.014581, -105.194243 40.014654, -105.195178 40.014659, -105.196636 40.014667, -105.196783 40.014668, -105.198306 40.014663, -105.19886 40.014679, -105.200616 40.01468, -105.20093 40.014684, -105.20128 40.014687, -105.202799 40.014678, -105.203831 40.014677, -105.204644 40.014676, -105.205032 40.014679, -105.205213 40.01468, -105.20551 40.014685, -105.205509 40.014767, -105.205509 40.014779, -105.20548 40.01488, -105.205345 40.015351, -105.205141 40.016353, -105.206265 40.016474, -105.206328 40.017374, -105.206328 40.017459, -105.206431 40.019406, -105.206464 40.020047, -105.206555 40.02001, -105.206601 40.019991, -105.206625 40.019981, -105.206685 40.019962, -105.207875 40.019574, -105.208539 40.019327, -105.210625 40.018563, -105.211039 40.018338, -105.211581 40.017926, -105.211682 40.017849, -105.211708 40.017833, -105.211782 40.017786, -105.211848 40.017744, -105.212005 40.017646, -105.212067 40.017607, -105.212129 40.017551, -105.212282 40.017413, -105.212388 40.017318, -105.212446 40.017266, -105.212575 40.017171, -105.212668 40.017103, -105.21276 40.017034, -105.212781 40.017019, -105.213253 40.016574, -105.213738 40.016107, -105.213988 40.015783, -105.214138 40.015519, -105.214231 40.015508, -105.214181 40.015635, -105.214181 40.015761, -105.214245 40.015871, -105.214074 40.015981, -105.213888 40.016118, -105.213517 40.016525, -105.213117 40.016931, -105.212982 40.01706, -105.213173 40.017089, -105.213613 40.017136, -105.213874 40.017164, -105.214345 40.017214, -105.214754 40.017258, -105.215308 40.017317, -105.215726 40.01736, -105.215837 40.017373, -105.21588 40.017379, -105.216011 40.017384, -105.216925 40.017495, -105.217177 40.017525, -105.217551 40.017565, -105.217919 40.017605, -105.218316 40.017647, -105.218313 40.017689, -105.218314 40.017774, -105.218314 40.017824, -105.217817 40.017767, -105.21716 40.017749, -105.217104 40.017765, -105.216914 40.017743, -105.216851 40.017736, -105.216798 40.01775, -105.216745 40.017756, -105.216623 40.017787, -105.21654 40.017821, -105.216473 40.017857, -105.216406 40.017904, -105.216365 40.017941, -105.216344 40.017964, -105.21632 40.017993, -105.216276 40.018057, -105.21626 40.018088, -105.216245 40.018131, -105.216229 40.018178, -105.216227 40.018195, -105.216224 40.018214, -105.216223 40.018246, -105.216222 40.018286, -105.216225 40.018322, -105.216227 40.018417, -105.216229 40.018571, -105.216235 40.019096, -105.216237 40.019222, -105.216249 40.020304, -105.21625 40.020431, -105.216172 40.020418, -105.216277 40.021146, -105.216338 40.021601, -105.216388 40.021973, -105.216419 40.022143, -105.21648 40.022481, -105.216535 40.022784, -105.216583 40.023049, -105.216694 40.023657, -105.216803 40.024258, -105.21692 40.024872, -105.216943 40.024963, -105.216974 40.025051, -105.217014 40.025138, -105.217076 40.025242, -105.217771 40.025852, -105.217975 40.025966, -105.218191 40.026126, -105.218353 40.026298, -105.218419 40.026381, -105.218716 40.026852, -105.218926 40.027029, -105.219225 40.027198, -105.219436 40.027249, -105.21966 40.027212, -105.219843 40.027095, -105.220053 40.026884, -105.220203 40.026767, -105.220469 40.026604, -105.220774 40.026383, -105.220893 40.026331, -105.221102 40.02622, -105.22131 40.026121, -105.221415 40.0261, -105.221514 40.026127, -105.22164 40.026206, -105.221698 40.026267, -105.221652 40.026343, -105.221552 40.0264, -105.22151 40.026425, -105.221194 40.026562, -105.220924 40.026656, -105.220878 40.026675, -105.220828 40.026697, -105.220746 40.026732, -105.220627 40.026785, -105.220527 40.026905, -105.220459 40.02709, -105.220459 40.027111, -105.220456 40.027247, -105.220453 40.027281, -105.220503 40.027842, -105.220513 40.027953, -105.220512 40.028121, -105.220512 40.02822, -105.22049 40.028464, -105.220426 40.028558, -105.220283 40.028611, -105.220092 40.028626, -105.21971 40.028621, -105.219441 40.028646, -105.21918 40.028665, -105.218973 40.028717, -105.21883 40.028774, -105.218692 40.028782, -105.218464 40.028749, -105.218369 40.028675, -105.218399 40.029087, -105.218427 40.029158, -105.218466 40.029256, -105.218669 40.029254, -105.22023 40.029248, -105.220409 40.029247, -105.220685 40.029247, -105.220726 40.029247, -105.220767 40.029247, -105.220798 40.029247, -105.22091 40.029247, -105.221295 40.029246, -105.221364 40.029246, -105.221514 40.029237, -105.221658 40.029222, -105.222037 40.029154, -105.222143 40.029129, -105.222398 40.029054, -105.222567 40.028993, -105.222739 40.028928, -105.222811 40.028888, -105.223124 40.028732, -105.223292 40.028644, -105.223308 40.028636, -105.223349 40.028613, -105.223407 40.028582, -105.224053 40.029231, -105.224415 40.029596, -105.224439 40.029598, -105.224545 40.029622, -105.224747 40.029748, -105.224683 40.02981, -105.224294 40.03038, -105.22424 40.030503, -105.223969 40.031328, -105.223929 40.031444, -105.223677 40.032022, -105.223611 40.032114, -105.223509 40.032211, -105.223301 40.032375, -105.223219 40.03246, -105.223167 40.03255, -105.223139 40.032645, -105.223135 40.032781, -105.223138 40.03306, -105.223142 40.033493, -105.223148 40.033675, -105.223178 40.033767, -105.223185 40.033787, -105.223275 40.033932, -105.223182 40.033975, -105.22305 40.034037, -105.222805 40.034152, -105.223009 40.034449, -105.223021 40.034479, -105.22304 40.034529, -105.222934 40.034576, -105.222891 40.0346, -105.222732 40.034689, -105.222491 40.034823, -105.22193 40.035382, -105.221884 40.035428, -105.221041 40.036081, -105.220855 40.03634, -105.220797 40.0365, -105.220773 40.036564, -105.220693 40.036785, -105.220574 40.037113, -105.220506 40.037301, -105.220384 40.037499, -105.220134 40.037658, -105.220043 40.037702, -105.219698 40.037867, -105.21962 40.03785, -105.218791 40.038158, -105.218291 40.038334, -105.217791 40.038493, -105.217342 40.038687, -105.216669 40.038977, -105.216226 40.039219, -105.215723 40.039562, -105.215655 40.039609, -105.215122 40.040091, -105.21509 40.04012, -105.214754 40.040362, -105.21464 40.040482, -105.214555 40.040735, -105.214462 40.040818, -105.214112 40.040922, -105.214112 40.041004, -105.214262 40.041087, -105.215076 40.041312, -105.215312 40.041432, -105.21597 40.041888, -105.21627 40.042064, -105.216402 40.042127, -105.216411 40.042056, -105.216435 40.041874, -105.21652 40.041715, -105.216618 40.041679, -105.21672 40.041715, -105.216939 40.041914, -105.217018 40.041962, -105.217184 40.041946, -105.217225 40.041916, -105.217567 40.042216, -105.218131 40.042685, -105.218591 40.043059, -105.218888 40.043298, -105.219105 40.043424, -105.219222 40.043497, -105.219501 40.043581, -105.220656 40.043569, -105.22081 40.043554, -105.220801 40.041818, -105.220799 40.040669, -105.220799 40.040594, -105.220807 40.040306, -105.220816 40.040267, -105.220849 40.040213, -105.220903 40.04017, -105.220972 40.040142, -105.221049 40.040134, -105.225466 40.040154, -105.229394 40.040157, -105.23291 40.040147, -105.233536 40.040146, -105.233679 40.040146, -105.233949 40.040146, -105.235588 40.040144, -105.235785 40.040143, -105.240026 40.040128, -105.241768 40.040136, -105.242198 40.040395, -105.24228 40.040421, -105.242432 40.040475, -105.2429 40.040257, -105.243076 40.040237, -105.244192 40.040218, -105.244192 40.040253, -105.244192 40.040359, -105.244193 40.040834, -105.244196 40.041745, -105.244197 40.041872, -105.244198 40.042167, -105.244199 40.042501, -105.244199 40.042625, -105.2442 40.042767, -105.244199 40.04288, -105.244196 40.043136, -105.244196 40.043182, -105.244189 40.043308, -105.244177 40.04353, -105.244177 40.043977, -105.244189 40.044362, -105.2442 40.044854, -105.244206 40.045365, -105.244082 40.045349, -105.243999 40.045338, -105.243699 40.045361, -105.243456 40.04541, -105.243356 40.045454, -105.242956 40.045476, -105.242563 40.045482, -105.242256 40.045466, -105.241341 40.045565, -105.24047 40.045561, -105.24026 40.045734, -105.23887 40.046835, -105.237779 40.047726, -105.237597 40.047896, -105.23673 40.048711, -105.236454 40.048998, -105.236392 40.049063, -105.235687 40.04986, -105.23551 40.04902, -105.235069 40.049373, -105.234865 40.049529, -105.234504 40.049809, -105.234033 40.050175, -105.233275 40.050765, -105.233014 40.050977, -105.232858 40.051104, -105.232499 40.051099, -105.232259 40.051095, -105.232204 40.051093, -105.231331 40.051059, -105.228252 40.05105, -105.225502 40.051042, -105.223961 40.051039, -105.22384 40.051038, -105.220856 40.051023, -105.219542 40.051009, -105.218558 40.050988, -105.218133 40.050981, -105.216128 40.051017, -105.214272 40.051003, -105.212985 40.050978, -105.209024 40.050951, -105.208198 40.050951, -105.206851 40.050964, -105.206855 40.05062, -105.206554 40.050623, -105.205725 40.050426, -105.205432 40.050395, -105.20526 40.050376, -105.205074 40.05042, -105.204938 40.050503, -105.20481 40.050678, -105.204798 40.050952, -105.204153 40.050955, -105.20215 40.050959, -105.200387 40.050971, -105.198662 40.050967, -105.197078 40.050985, -105.195315 40.050983, -105.193556 40.050995, -105.190541 40.050993, -105.189823 40.051, -105.187641 40.051011, -105.18747 40.051009, -105.187439 40.05101, -105.187114 40.051022, -105.186974 40.05104, -105.186762 40.051067, -105.186589 40.051101, -105.186074 40.051283, -105.185632 40.05149, -105.184595 40.052016, -105.184111 40.052214, -105.183897 40.052287, -105.183745 40.052338, -105.18348 40.052414, -105.183324 40.052452, -105.182081 40.05276, -105.181634 40.052872, -105.18109 40.052959, -105.18085 40.052971, -105.180748 40.052967, -105.179279 40.052914, -105.17862 40.052902, -105.178315 40.052897, -105.178326 40.054786, -105.177172 40.054776, -105.17698 40.054776, -105.176266 40.054776, -105.176125 40.054776, -105.175759 40.054769, -105.175547 40.054791, -105.175339 40.05483, -105.175115 40.054892, -105.174806 40.055029, -105.174551 40.055176, -105.173851 40.055646, -105.173582 40.055809, -105.173297 40.055955, -105.173094 40.056043, -105.171335 40.056817, -105.170696 40.057105, -105.170016 40.05741, -105.169699 40.057585, -105.169528 40.057723, -105.169383 40.057877, -105.169259 40.058051, -105.169157 40.058243, -105.169086 40.058443, -105.168296 40.058427, -105.167292 40.058425, -105.166172 40.058425, -105.165847 40.058428, -105.165524 40.058462, -105.165287 40.058506, -105.165055 40.058567, -105.164441 40.058687, -105.164116 40.058726, -105.164035 40.058731, -105.163047 40.058723, -105.160973 40.058658, -105.160701 40.058683, -105.160563 40.058719, -105.160435 40.058772, -105.160328 40.058841, -105.160213 40.05895, -105.160127 40.059074, -105.160074 40.059208, -105.160059 40.059353, -105.160081 40.059496, -105.160139 40.059634, -105.16018 40.059699, -105.160324 40.059824, -105.160491 40.059931, -105.160677 40.060017, -105.162265 40.060447, -105.16316 40.06073, -105.164075 40.061084, -105.164429 40.06125, -105.164548 40.061299, -105.164717 40.061344, -105.164893 40.061366, -105.16507 40.061363, -105.165243 40.061333, -105.165375 40.061286, -105.165811 40.061092, -105.16606 40.061275, -105.166199 40.061333, -105.166547 40.061395, -105.167549 40.061521, -105.16774 40.061552, -105.167889 40.061552, -105.168055 40.061531, -105.168233 40.061484, -105.168398 40.061416, -105.168498 40.061342, -105.168605 40.06122, -105.168698 40.061074, -105.168743 40.060958, -105.168862 40.060703, -105.169017 40.06046, -105.169093 40.060388, -105.169186 40.060329, -105.169331 40.060274, -105.169482 40.060467, -105.169573 40.060562, -105.169917 40.060866, -105.169479 40.061268, -105.16939 40.061389, -105.169261 40.061571, -105.169188 40.061724, -105.16914 40.061882, -105.169117 40.062043, -105.169102 40.062501, -105.169114 40.063383, -105.169123 40.064028, -105.169139 40.064851, -105.169154 40.065347, -105.169182 40.065385, -105.169227 40.06541, -105.169263 40.065418, -105.171153 40.065409, -105.173307 40.065415, -105.173292 40.064882, -105.173287 40.064111, -105.173278 40.063148, -105.173242 40.063004, -105.173175 40.062863, -105.173099 40.062765, -105.172967 40.062652, -105.173147 40.062428, -105.173207 40.062259, -105.173234 40.062084, -105.173216 40.061304, -105.174231 40.0613, -105.175237 40.061311, -105.175671 40.061286, -105.175875 40.061244, -105.176071 40.061183, -105.176447 40.061007, -105.176547 40.060976, -105.176811 40.060916, -105.176986 40.060896, -105.177221 40.060896, -105.178399 40.060889, -105.178439 40.062509, -105.178467 40.064897, -105.178479 40.065439, -105.17847 40.066561, -105.178481 40.067256, -105.178488 40.067745, -105.178494 40.069009, -105.178495 40.069124, -105.1785 40.069434, -105.178505 40.06976, -105.178506 40.069849, -105.178515 40.070475, -105.178517 40.070584, -105.178501 40.072401, -105.178499 40.072509, -105.178496 40.072722, -105.178493 40.072894, -105.178493 40.072904, -105.178489 40.073218, -105.17848 40.073969, -105.178404 40.079772, -105.178371 40.08236, -105.178812 40.082239, -105.179205 40.082173, -105.179233 40.082172, -105.179977 40.082156, -105.18012 40.082118, -105.180185 40.082052, -105.180185 40.081915, -105.18002 40.081519, -105.179992 40.081376, -105.179927 40.081184, -105.180006 40.080937, -105.180163 40.080712, -105.180507 40.080366, -105.18085 40.080069, -105.181114 40.07991, -105.181822 40.079619, -105.182387 40.079284, -105.182673 40.07902, -105.183145 40.07857, -105.183481 40.078361, -105.183781 40.078229, -105.183804 40.078224, -105.184525 40.078081, -105.185297 40.077988, -105.185855 40.077971, -105.186384 40.077977, -105.186577 40.0779, -105.186627 40.077796, -105.18657 40.077373, -105.186534 40.076845, -105.186663 40.076554, -105.186777 40.0764, -105.187006 40.076181, -105.187256 40.076016, -105.187559 40.07586, -105.187671 40.075802, -105.187842 40.075755, -105.187959 40.075722, -105.187974 40.075913, -105.188074 40.075889, -105.188074 40.075984, -105.188073 40.076072, -105.188072 40.076111, -105.188071 40.076163, -105.18807 40.076207, -105.188069 40.076321, -105.188052 40.077331, -105.18804 40.079042, -105.188012 40.081341, -105.18801 40.081538, -105.187985 40.083378, -105.187983 40.084209, -105.187953 40.086016, -105.187932 40.087085, -105.187929 40.087259, -105.187924 40.087336, -105.187912 40.087501, -105.188264 40.087656, -105.188493 40.087765, -105.188717 40.08788, -105.188938 40.088, -105.189388 40.088275, -105.189677 40.088466, -105.18977 40.088528, -105.189922 40.088631, -105.188773 40.089677, -105.188465 40.089957, -105.187775 40.090585, -105.18363 40.094358)))"} -{"geo_id":"36514","urban_area_code":"36514","name":"Hammond, LA","lsad_name":"Hammond, LA Urbanized Area","area_lsad_code":"75","mtfcc_feature_class_code":"G3500","type":"U","functional_status":"S","area_land_meters":198495100,"area_water_meters":755580,"internal_point_lon":-90.4816249,"internal_point_lat":30.4927098,"internal_point_geom":"POINT(-90.4816249 30.4927098)","urban_area_geom":"MULTIPOLYGON(((-90.547281 30.432013, -90.54727 30.43206, -90.54719 30.43235, -90.547061 30.43263, -90.547126 30.4332, -90.547179 30.433349, -90.547264 30.43359, -90.54836 30.43479, -90.54871 30.43525, -90.54882 30.43538, -90.54903 30.4355, -90.5493 30.43561, -90.54971 30.43582, -90.54994 30.43593, -90.55017 30.43605, -90.550197 30.436068, -90.55093 30.43655, -90.55122 30.43676, -90.55133 30.43687, -90.55142 30.43686, -90.55153 30.43684, -90.55163 30.43685, -90.55177 30.43687, -90.551921 30.436896, -90.55194 30.4369, -90.55213 30.43695, -90.55237 30.43704, -90.55329 30.43741, -90.553863 30.437649, -90.553858 30.437674, -90.553293 30.440695, -90.553105 30.441702, -90.553172 30.441727, -90.55331 30.44178, -90.553379 30.441794, -90.55345 30.44181, -90.55369 30.44185, -90.55385 30.44187, -90.55398 30.4419, -90.554037 30.441902, -90.55453 30.44192, -90.55529 30.4419, -90.555824 30.4419, -90.55611 30.4419, -90.55642 30.44188, -90.55652 30.44188, -90.556577 30.441882, -90.55672 30.44189, -90.55703 30.44194, -90.557046 30.441944, -90.55713 30.44197, -90.55718 30.44202, -90.557261 30.442101, -90.55729 30.44213, -90.557536 30.442306, -90.55763 30.442373, -90.557675 30.442405, -90.55771 30.44243, -90.557806 30.442509, -90.557849 30.442545, -90.558 30.44267, -90.55832 30.44293, -90.558328 30.442936, -90.55881 30.44332, -90.55921 30.44367, -90.55935 30.44378, -90.5596 30.444, -90.559768 30.444107, -90.55985 30.44416, -90.55996 30.44423, -90.56029 30.44439, -90.560314 30.444398, -90.560508 30.444457, -90.561269 30.444628, -90.561705 30.444732, -90.562508 30.444887, -90.562939 30.444939, -90.56304 30.444951, -90.56417 30.44507, -90.56437 30.44509, -90.564385 30.44509, -90.565034 30.445124, -90.56514 30.44513, -90.56525 30.44514, -90.56521 30.44547, -90.56519 30.44555, -90.565183 30.445562, -90.56516 30.44561, -90.56515 30.44569, -90.56519 30.44579, -90.5652 30.44589, -90.56518 30.4461, -90.56514 30.44626, -90.5651 30.44635, -90.56508 30.44649, -90.565051 30.446819, -90.56503 30.44706, -90.56503 30.447248, -90.56503 30.448078, -90.56503 30.44883, -90.56502 30.44946, -90.56503 30.45034, -90.565042 30.45057, -90.56505 30.45071, -90.56502 30.45133, -90.56502 30.4514, -90.565031 30.451644, -90.56504 30.45181, -90.56507 30.4522, -90.565097 30.452377, -90.56512 30.45252, -90.56513 30.45262, -90.56511 30.453, -90.56511 30.453106, -90.56511 30.45441, -90.565104 30.454568, -90.56509 30.45495, -90.5651 30.45498, -90.56513 30.45501, -90.56516 30.45502, -90.56527 30.45504, -90.565294 30.45504, -90.565703 30.45504, -90.56584 30.45504, -90.566035 30.45504, -90.56642 30.45504, -90.56651 30.45502, -90.5666 30.45499, -90.566613 30.454984, -90.56667 30.45496, -90.56679 30.4549, -90.566801 30.455009, -90.56683 30.45528, -90.566853 30.455404, -90.567673 30.454292, -90.567746 30.454193, -90.568115 30.453806, -90.568634 30.45322, -90.568665 30.45322, -90.568758 30.45322, -90.56879 30.45322, -90.568911 30.453219, -90.57075 30.45321, -90.57101 30.45319, -90.57112 30.45317, -90.57135 30.4531, -90.57182 30.45293, -90.57221 30.45277, -90.5721 30.45264, -90.57149 30.4518, -90.57147 30.45178, -90.571113 30.451308, -90.57097 30.45112, -90.57092 30.45106, -90.570878 30.451004, -90.57077 30.45086, -90.57063 30.45063, -90.57057 30.4505, -90.5705 30.45037, -90.57038 30.45012, -90.570309 30.44994, -90.570215 30.44969, -90.570093 30.449351, -90.57 30.449093, -90.569596 30.448017, -90.569539 30.447858, -90.569134 30.446871, -90.569078 30.446692, -90.568852 30.446094, -90.568757 30.445925, -90.56866 30.44591, -90.568123 30.445706, -90.56784 30.445526, -90.56776 30.445321, -90.56755 30.445157, -90.567246 30.445069, -90.566719 30.44514, -90.566415 30.445113, -90.566155 30.444942, -90.565952 30.444629, -90.565863 30.444381, -90.56592 30.443931, -90.565907 30.443579, -90.565653 30.443205, -90.565565 30.442914, -90.565476 30.442683, -90.565533 30.44238, -90.565438 30.442232, -90.565343 30.442188, -90.565228 30.442144, -90.565158 30.442147, -90.5652 30.44206, -90.565229 30.441999, -90.565271 30.441915, -90.565579 30.441662, -90.565707 30.441558, -90.565667 30.441506, -90.565549 30.441351, -90.56551 30.4413, -90.565014 30.440641, -90.56475 30.44029, -90.56429 30.4397, -90.56364 30.43886, -90.563505 30.438681, -90.56327 30.43837, -90.563 30.43803, -90.562902 30.437903, -90.56277 30.43773, -90.56267 30.4376, -90.56262 30.43755, -90.562598 30.437533, -90.56257 30.43751, -90.56255 30.43747, -90.5625 30.43741, -90.562461 30.437398, -90.562347 30.437362, -90.56231 30.43735, -90.5622 30.43731, -90.562096 30.437289, -90.56175 30.43722, -90.56146 30.43716, -90.56144 30.437156, -90.56122 30.43712, -90.561226 30.43704, -90.561245 30.436803, -90.56125 30.43674, -90.561252 30.436724, -90.561155 30.436724, -90.560867 30.436724, -90.560772 30.436725, -90.560773 30.436653, -90.560778 30.436441, -90.56078 30.43638, -90.56078 30.43637, -90.56078 30.436353, -90.56078 30.436302, -90.56078 30.436286, -90.56078 30.4361, -90.560776 30.435938, -90.56077 30.43566, -90.560763 30.434897, -90.56076 30.43455, -90.56076 30.434308, -90.56076 30.433582, -90.56076 30.43334, -90.56077 30.43276, -90.56077 30.4321, -90.56079 30.43176, -90.56025 30.43178, -90.55989 30.4318, -90.55981 30.4318, -90.5594 30.43182, -90.559178 30.431877, -90.55898 30.43197, -90.55887 30.43203, -90.55857 30.432231, -90.55858 30.432416, -90.55895 30.43278, -90.559315 30.433136, -90.55937 30.43319, -90.5597 30.43345, -90.55983 30.43351, -90.55983 30.433575, -90.55983 30.43365, -90.559832 30.43377, -90.559834 30.433835, -90.559837 30.43398, -90.559846 30.434415, -90.55985 30.43456, -90.55942 30.43456, -90.55922 30.43453, -90.55902 30.43448, -90.558735 30.434708, -90.558253 30.435018, -90.557877 30.435272, -90.55749 30.4349, -90.557218 30.436134, -90.55721 30.43617, -90.55712 30.43663, -90.55702 30.43713, -90.55702 30.43715, -90.55703 30.43718, -90.55707 30.43722, -90.55714 30.43725, -90.55723 30.43728, -90.55731 30.43729, -90.557371 30.437302, -90.55741 30.43731, -90.55795 30.43738, -90.55793 30.43751, -90.55792 30.43754, -90.557916 30.43756, -90.557819 30.438103, -90.55781 30.43816, -90.557789 30.438285, -90.557127 30.438218, -90.556843 30.438185, -90.556702 30.438169, -90.556372 30.437999, -90.556048 30.437663, -90.555718 30.437278, -90.555376 30.436904, -90.555192 30.43674, -90.554963 30.436415, -90.554839 30.43624, -90.554798 30.436181, -90.554703 30.436047, -90.554226 30.435515, -90.554196 30.435481, -90.553536 30.435239, -90.553181 30.435195, -90.552807 30.434969, -90.552756 30.434885, -90.552553 30.434551, -90.552058 30.433815, -90.551678 30.433446, -90.551373 30.433314, -90.54987 30.43321, -90.549527 30.433138, -90.548829 30.432753, -90.548706 30.432721, -90.548322 30.432621, -90.548056 30.432473, -90.547871 30.432231, -90.547516 30.43206, -90.547446 30.432044, -90.547281 30.432013)), ((-90.530381 30.544554, -90.53021 30.544553, -90.529473 30.544563, -90.529312 30.544565, -90.529306 30.545911, -90.529293 30.546542, -90.5293 30.54723, -90.52929 30.548397, -90.52929 30.548414, -90.528571 30.548422, -90.52819 30.548425, -90.527197 30.548433, -90.526776 30.548447, -90.526158 30.548409, -90.525614 30.548393, -90.525406 30.54838, -90.525311 30.548367, -90.525212 30.548352, -90.525029 30.548404, -90.524979 30.548462, -90.52492 30.548517, -90.524846 30.548548, -90.524698 30.5486, -90.524541 30.548648, -90.524486 30.548682, -90.52445 30.548729, -90.524425 30.54881, -90.524412 30.548893, -90.524405 30.548953, -90.524429 30.549012, -90.524482 30.549062, -90.524626 30.549128, -90.524685 30.549152, -90.524755 30.549185, -90.524849 30.549253, -90.524894 30.549315, -90.524922 30.549391, -90.524945 30.549473, -90.524959 30.549559, -90.524965 30.549596, -90.524979 30.549731, -90.524983 30.54979, -90.524987 30.54988, -90.524988 30.549927, -90.524984 30.550008, -90.52496 30.550459, -90.52496 30.55119, -90.524967 30.551437, -90.524958 30.551512, -90.52494 30.551581, -90.524923 30.55166, -90.524912 30.551766, -90.524929 30.551828, -90.524968 30.551884, -90.525053 30.551884, -90.528377 30.551909, -90.529165 30.551915, -90.529243 30.551899, -90.529285 30.55186, -90.52929 30.548509, -90.529494 30.548513, -90.530118 30.548515, -90.531415 30.548497, -90.531702 30.548496, -90.530381 30.544554)), ((-90.458784 30.425522, -90.458061 30.425628, -90.45713 30.425764, -90.456336 30.425985, -90.456328 30.426029, -90.456316 30.426063, -90.456292 30.426167, -90.456277 30.426274, -90.456262 30.426453, -90.455013 30.426246, -90.454968 30.426242, -90.454878 30.426238, -90.454476 30.426225, -90.454464 30.426225, -90.454342 30.426224, -90.454343 30.426487, -90.454344 30.426666, -90.454345 30.426851, -90.454346 30.427037, -90.454346 30.427276, -90.454348 30.42754, -90.454149 30.427536, -90.454077 30.427535, -90.453789 30.427537, -90.453553 30.427542, -90.453501 30.427544, -90.453356 30.427557, -90.453555 30.427839, -90.454152 30.428685, -90.454352 30.428968, -90.454876 30.429711, -90.455066 30.429981, -90.45645 30.431942, -90.456975 30.432686, -90.457032 30.432661, -90.457178 30.432523, -90.45733 30.432364, -90.457419 30.432177, -90.457514 30.431957, -90.457609 30.431704, -90.457617 30.431683, -90.457768 30.431303, -90.457759 30.431267, -90.457757 30.431233, -90.457764 30.4312, -90.457762 30.431128, -90.457753 30.431057, -90.457733 30.430808, -90.45772 30.430701, -90.457695 30.43056, -90.457688 30.43049, -90.45765 30.430319, -90.4576 30.430145, -90.457567 30.430042, -90.457508 30.429909, -90.457163 30.429508, -90.457031 30.429325, -90.456955 30.429199, -90.456909 30.4291, -90.456857 30.428966, -90.456784 30.428686, -90.456723 30.428477, -90.456681 30.428316, -90.456614 30.428056, -90.456602 30.428022, -90.456545 30.427633, -90.456514 30.427492, -90.456484 30.427315, -90.456461 30.42721, -90.456444 30.427177, -90.456414 30.42713, -90.456492 30.42708, -90.456635 30.426988, -90.456755 30.426913, -90.45705 30.42675, -90.457207 30.426686, -90.457342 30.42665, -90.457365 30.426644, -90.457599 30.426595, -90.457726 30.426568, -90.457746 30.426564, -90.457958 30.426526, -90.458111 30.426499, -90.45824 30.426477, -90.45836 30.426455, -90.45872 30.426391, -90.458841 30.42637, -90.458893 30.426361, -90.458784 30.425522)), ((-90.452875 30.570355, -90.452873 30.570392, -90.452868 30.570504, -90.452867 30.570542, -90.452866 30.570744, -90.452862 30.571352, -90.452862 30.571555, -90.452861 30.571667, -90.452859 30.572004, -90.452859 30.572117, -90.452852 30.572382, -90.452831 30.573179, -90.452825 30.573445, -90.452824 30.573482, -90.452822 30.573595, -90.452822 30.573633, -90.452808 30.574398, -90.452766 30.576693, -90.452753 30.577459, -90.452766 30.577794, -90.452775 30.578178, -90.452778 30.578298, -90.452773 30.579517, -90.452792 30.580336, -90.452809 30.581056, -90.452811 30.581322, -90.45283 30.581796, -90.452861 30.582515, -90.452878 30.584019, -90.452885 30.584623, -90.45289 30.584761, -90.453583 30.584771, -90.454917 30.584793, -90.455664 30.584794, -90.456358 30.584795, -90.456974 30.584818, -90.457527 30.584818, -90.45764 30.58481, -90.457932 30.584772, -90.45811 30.58474, -90.458688 30.584589, -90.458796 30.584554, -90.459299 30.584393, -90.459381 30.584358, -90.459829 30.584196, -90.461176 30.583713, -90.461626 30.583552, -90.461625 30.583377, -90.461623 30.582854, -90.461623 30.58268, -90.461621 30.582359, -90.461618 30.581399, -90.461617 30.581079, -90.46162 30.580876, -90.461631 30.580268, -90.461635 30.580066, -90.461641 30.579682, -90.461663 30.578531, -90.46167 30.578148, -90.461679 30.577577, -90.46171 30.575864, -90.461717 30.575515, -90.461718 30.575294, -90.46165 30.575271, -90.461447 30.575205, -90.46138 30.575184, -90.461164 30.574975, -90.4611 30.574893, -90.460903 30.574811, -90.46071 30.574746, -90.460465 30.57475, -90.46016 30.574794, -90.459601 30.574954, -90.459436 30.575042, -90.458991 30.575185, -90.458553 30.57536, -90.458361 30.575425, -90.458248 30.575465, -90.45807 30.575492, -90.457861 30.575509, -90.457721 30.575498, -90.457556 30.575454, -90.457314 30.575328, -90.457105 30.575168, -90.456895 30.574805, -90.456768 30.574377, -90.456743 30.57404, -90.456742 30.57402, -90.456806 30.573701, -90.456977 30.573261, -90.457085 30.572992, -90.457111 30.572667, -90.457155 30.572365, -90.457161 30.572107, -90.457053 30.571782, -90.456831 30.571458, -90.456621 30.571161, -90.456329 30.571007, -90.456094 30.57093, -90.455662 30.570892, -90.455052 30.570842, -90.454556 30.570785, -90.454525 30.570782, -90.454201 30.570727, -90.45394 30.570683, -90.453311 30.570502, -90.452886 30.570359, -90.452875 30.570355)), ((-90.458679 30.424695, -90.458784 30.425522, -90.460051 30.425298, -90.459947 30.42445, -90.459807 30.423631, -90.458519 30.423846, -90.457628 30.424006, -90.45736 30.424082, -90.457347 30.424189, -90.457339 30.424224, -90.457272 30.424314, -90.457201 30.424401, -90.457049 30.42457, -90.457001 30.424619, -90.457062 30.424677, -90.457368 30.424969, -90.457531 30.424893, -90.458679 30.424695)), ((-90.454342 30.426224, -90.45434 30.425841, -90.454337 30.424692, -90.454336 30.424309, -90.454308 30.424312, -90.454226 30.424324, -90.454199 30.424328, -90.454063 30.424311, -90.453587 30.424295, -90.453573 30.424294, -90.452994 30.424286, -90.451862 30.424289, -90.451692 30.424291, -90.451323 30.424298, -90.451066 30.424297, -90.451355 30.42471, -90.451859 30.42543, -90.452152 30.42586, -90.452219 30.425952, -90.452515 30.426361, -90.452711 30.4263, -90.452819 30.426279, -90.452871 30.426275, -90.452942 30.426272, -90.45319 30.426282, -90.453398 30.426276, -90.453711 30.426255, -90.453974 30.42623, -90.454026 30.426226, -90.454135 30.426222, -90.454342 30.426224)), ((-90.380114 30.434364, -90.38011 30.433818, -90.380101 30.432183, -90.380099 30.431663, -90.380099 30.431638, -90.380098 30.431396, -90.380096 30.430799, -90.380092 30.428792, -90.380101 30.428284, -90.380118 30.427446, -90.379855 30.42743, -90.379423 30.427405, -90.379069 30.42738, -90.378808 30.427363, -90.37872 30.427485, -90.378637 30.427683, -90.378612 30.427847, -90.378542 30.428172, -90.378548 30.428425, -90.378529 30.428578, -90.378516 30.428672, -90.378427 30.428809, -90.378294 30.428985, -90.378267 30.429007, -90.378154 30.429106, -90.378008 30.42921, -90.377881 30.429287, -90.377761 30.429353, -90.377526 30.429452, -90.37738 30.429595, -90.37724 30.42976, -90.377177 30.429897, -90.377088 30.430155, -90.377043 30.430375, -90.376967 30.43054, -90.376865 30.430749, -90.376789 30.430853, -90.376656 30.430974, -90.376453 30.431172, -90.376294 30.431386, -90.376224 30.431595, -90.376192 30.431793, -90.376205 30.431985, -90.376205 30.432277, -90.376166 30.432452, -90.376077 30.432628, -90.375849 30.433068, -90.375747 30.433255, -90.375741 30.433398, -90.37576 30.433662, -90.375836 30.433914, -90.37588 30.434041, -90.375981 30.434266, -90.376203 30.434613, -90.376355 30.434805, -90.376507 30.43497, -90.377209 30.434715, -90.378258 30.434337, -90.379381 30.434246, -90.379473 30.434239, -90.379835 30.434283, -90.379929 30.43431, -90.380114 30.434364)), ((-90.550088 30.505206, -90.550087 30.505291, -90.550102 30.506847, -90.550119 30.508551, -90.550116 30.508969, -90.550131 30.509286, -90.550136 30.5096, -90.55016 30.509691, -90.550192 30.509747, -90.550248 30.509796, -90.550283 30.509814, -90.550353 30.509835, -90.550428 30.509846, -90.550516 30.509851, -90.552177 30.509855, -90.552532 30.509856, -90.553819 30.509839, -90.553894 30.509838, -90.553984 30.509837, -90.554119 30.509838, -90.554194 30.50984, -90.554194 30.510103, -90.554204 30.510374, -90.554263 30.511977, -90.554283 30.512512, -90.554287 30.51261, -90.5543 30.512904, -90.554305 30.513002, -90.554303 30.513073, -90.554298 30.513288, -90.554297 30.51336, -90.554175 30.513436, -90.554075 30.513466, -90.554037 30.513478, -90.553837 30.5135, -90.553584 30.513534, -90.553451 30.513536, -90.553338 30.51353, -90.553317 30.51353, -90.553093 30.513504, -90.553029 30.513496, -90.553014 30.513495, -90.552981 30.513495, -90.55293 30.513506, -90.552882 30.513525, -90.552849 30.513544, -90.552836 30.513553, -90.552802 30.513587, -90.552621 30.513795, -90.552078 30.514419, -90.551897 30.514627, -90.551908 30.514699, -90.551916 30.514708, -90.551945 30.51474, -90.55202 30.514781, -90.552099 30.514799, -90.552136 30.514825, -90.552189 30.514863, -90.552205 30.514877, -90.55219 30.514996, -90.552158 30.515035, -90.552079 30.515107, -90.552042 30.51513, -90.552016 30.515134, -90.551979 30.515162, -90.55191 30.515226, -90.551921 30.515345, -90.551948 30.515377, -90.551959 30.515405, -90.552089 30.51553, -90.552102 30.515542, -90.552118 30.515583, -90.552129 30.515643, -90.552119 30.515785, -90.552119 30.515831, -90.552136 30.516, -90.552162 30.516087, -90.552189 30.516156, -90.552216 30.516183, -90.552248 30.516243, -90.552386 30.516345, -90.552476 30.516389, -90.552598 30.51642, -90.552638 30.516443, -90.552789 30.516557, -90.553477 30.517151, -90.553554 30.517206, -90.553575 30.51721, -90.553612 30.517233, -90.553713 30.517283, -90.553726 30.517285, -90.553755 30.517292, -90.553776 30.517306, -90.553845 30.517324, -90.553946 30.517411, -90.553989 30.517459, -90.554061 30.51753, -90.554116 30.51761, -90.554254 30.517721, -90.554297 30.517749, -90.554345 30.517785, -90.554383 30.517818, -90.554493 30.517913, -90.554541 30.517968, -90.554584 30.518023, -90.554594 30.518046, -90.554674 30.518119, -90.554738 30.518206, -90.554759 30.518247, -90.554787 30.518288, -90.554791 30.518311, -90.554823 30.518348, -90.554877 30.518535, -90.554904 30.518563, -90.554914 30.518604, -90.554975 30.518677, -90.554989 30.518695, -90.554999 30.518718, -90.555015 30.518732, -90.555079 30.5188, -90.555148 30.518869, -90.555223 30.518974, -90.555255 30.519038, -90.555287 30.519121, -90.555303 30.519134, -90.555308 30.519152, -90.555335 30.519176, -90.555414 30.519228, -90.555457 30.519248, -90.555579 30.519285, -90.555658 30.519298, -90.555687 30.519319, -90.555716 30.519326, -90.555764 30.519351, -90.555817 30.519371, -90.555891 30.519389, -90.555902 30.519393, -90.555936 30.519405, -90.556064 30.519414, -90.556146 30.519407, -90.556286 30.519367, -90.556468 30.519337, -90.556558 30.519332, -90.556696 30.519341, -90.556781 30.519373, -90.556945 30.519514, -90.557004 30.519574, -90.557093 30.519696, -90.5571 30.519713, -90.557116 30.519729, -90.557121 30.519757, -90.557144 30.519784, -90.557148 30.519802, -90.557174 30.51983, -90.55719 30.519866, -90.557206 30.519926, -90.557249 30.520045, -90.557313 30.520191, -90.557329 30.520214, -90.557383 30.520342, -90.557409 30.52037, -90.557436 30.52042, -90.557452 30.520438, -90.557473 30.52048, -90.557489 30.520498, -90.5575 30.52053, -90.557523 30.520559, -90.557532 30.520576, -90.557596 30.520658, -90.557708 30.520855, -90.557734 30.520896, -90.557745 30.520928, -90.557772 30.52096, -90.557777 30.520981, -90.557793 30.521006, -90.557804 30.521029, -90.557809 30.521047, -90.55783 30.521093, -90.557849 30.521145, -90.557879 30.521303, -90.55789 30.521326, -90.557911 30.521395, -90.557962 30.52153, -90.55797 30.521569, -90.557983 30.521608, -90.557994 30.521649, -90.558024 30.521793, -90.558069 30.521931, -90.558141 30.522109, -90.558157 30.522123, -90.558173 30.522146, -90.558184 30.522178, -90.558211 30.522233, -90.558269 30.522297, -90.558296 30.522333, -90.55832 30.52239, -90.558355 30.522571, -90.558411 30.52275, -90.558425 30.522782, -90.558462 30.522819, -90.558478 30.522839, -90.55852 30.522878, -90.558547 30.522915, -90.558568 30.522956, -90.558595 30.523024, -90.558681 30.523304, -90.5587 30.523356, -90.558716 30.52343, -90.558735 30.523537, -90.558757 30.523748, -90.558752 30.523863, -90.558763 30.523954, -90.558795 30.523982, -90.558837 30.524005, -90.558922 30.524027, -90.559049 30.524068, -90.559076 30.524086, -90.559102 30.524095, -90.559193 30.524173, -90.55923 30.524219, -90.55924 30.524242, -90.559246 30.524306, -90.559236 30.524388, -90.559199 30.524476, -90.559183 30.524494, -90.559173 30.524526, -90.559157 30.524558, -90.559136 30.524577, -90.559104 30.524622, -90.559036 30.524682, -90.558978 30.524715, -90.558919 30.524728, -90.558898 30.524742, -90.558861 30.524752, -90.55884 30.524765, -90.558774 30.524798, -90.558455 30.525065, -90.558434 30.525074, -90.558355 30.525134, -90.558289 30.525166, -90.558241 30.525198, -90.55807 30.525329, -90.557959 30.525442, -90.557912 30.525502, -90.557875 30.525557, -90.557851 30.525619, -90.557841 30.525798, -90.557909 30.526171, -90.557923 30.526211, -90.558011 30.5262, -90.558185 30.526183, -90.558338 30.526175, -90.558491 30.526173, -90.558879 30.526175, -90.560418 30.526184, -90.561754 30.526178, -90.562713 30.526174, -90.563095 30.526171, -90.564245 30.526162, -90.564628 30.52616, -90.565085 30.526153, -90.566463 30.526135, -90.5669 30.52613, -90.566903 30.526243, -90.566917 30.526584, -90.566923 30.526698, -90.566922 30.52699, -90.566922 30.5275, -90.566936 30.527868, -90.566948 30.528161, -90.566951 30.528257, -90.566954 30.528316, -90.566957 30.528545, -90.566959 30.528642, -90.566961 30.528825, -90.566965 30.529052, -90.566975 30.529376, -90.566981 30.52956, -90.566982 30.529611, -90.566988 30.529767, -90.56699 30.529819, -90.566993 30.529919, -90.566991 30.530162, -90.56699 30.530498, -90.566981 30.530808, -90.566983 30.531193, -90.566985 30.531537, -90.566986 30.531563, -90.566988 30.531604, -90.566987 30.531641, -90.566987 30.531668, -90.568066 30.531685, -90.568585 30.531669, -90.568924 30.531628, -90.569732 30.531574, -90.57046 30.531542, -90.57119 30.53153, -90.57119 30.531199, -90.57119 30.53111, -90.57118 30.53025, -90.57118 30.53021, -90.57118 30.52988, -90.57118 30.529818, -90.57118 30.52973, -90.57118 30.529632, -90.57118 30.52957, -90.57118 30.529398, -90.57118 30.52918, -90.57118 30.52905, -90.57183 30.52905, -90.57183 30.5289, -90.57179 30.52885, -90.57174 30.52883, -90.57158 30.52883, -90.57118 30.52882, -90.57118 30.52871, -90.57118 30.528692, -90.57118 30.528638, -90.57118 30.52862, -90.571178 30.52848, -90.571172 30.52806, -90.57117 30.52792, -90.57117 30.527826, -90.57117 30.527544, -90.57117 30.52745, -90.57117 30.52728, -90.57117 30.527181, -90.57117 30.52702, -90.571155 30.526377, -90.57115 30.52611, -90.57115 30.52591, -90.57115 30.525409, -90.57115 30.52527, -90.57114 30.52492, -90.57114 30.52331, -90.57114 30.52319, -90.57114 30.52261, -90.57114 30.522541, -90.57114 30.522334, -90.57114 30.522266, -90.57114 30.52207, -90.57114 30.521485, -90.57114 30.52129, -90.571139 30.52124, -90.571137 30.521091, -90.571137 30.521042, -90.571131 30.520591, -90.571115 30.51924, -90.57111 30.51879, -90.57151 30.51881, -90.571961 30.518819, -90.57243 30.51883, -90.57334 30.51885, -90.574516 30.51885, -90.57462 30.51885, -90.575367 30.518802, -90.57536 30.51809, -90.575361 30.517997, -90.57537 30.5172, -90.57537 30.51667, -90.57536 30.51598, -90.57536 30.515584, -90.57536 30.51534, -90.57535 30.51478, -90.57535 30.514751, -90.575451 30.514769, -90.57686 30.514781, -90.577021 30.514772, -90.577116 30.514747, -90.577311 30.514652, -90.577412 30.514597, -90.577491 30.514575, -90.577683 30.514617, -90.579747 30.514192, -90.579694 30.514146, -90.579683 30.514119, -90.579657 30.514082, -90.57963 30.514055, -90.579619 30.514013, -90.579604 30.513995, -90.579529 30.513872, -90.579481 30.513757, -90.579465 30.513734, -90.579454 30.513713, -90.579443 30.513689, -90.579428 30.51367, -90.579417 30.513634, -90.579401 30.513615, -90.579385 30.513583, -90.579348 30.513538, -90.579344 30.51352, -90.579294 30.51346, -90.579284 30.513432, -90.579236 30.513373, -90.57923 30.513355, -90.579214 30.513341, -90.579204 30.513314, -90.579188 30.513295, -90.579172 30.513263, -90.579134 30.513218, -90.579124 30.513195, -90.579108 30.513176, -90.579087 30.513126, -90.579022 30.513007, -90.579006 30.512966, -90.57899 30.512952, -90.578985 30.512925, -90.578957 30.512887, -90.578942 30.512838, -90.57891 30.512801, -90.578905 30.512778, -90.578884 30.512751, -90.578852 30.512691, -90.578782 30.512536, -90.578771 30.512495, -90.578756 30.512481, -90.57874 30.512435, -90.578723 30.512415, -90.578717 30.512397, -90.578681 30.512357, -90.57867 30.51233, -90.578612 30.512257, -90.57859 30.512229, -90.578564 30.512156, -90.5785 30.512088, -90.578505 30.512074, -90.578505 30.512042, -90.578477 30.512004, -90.578462 30.511955, -90.578441 30.511932, -90.57843 30.511891, -90.578404 30.511859, -90.578367 30.511764, -90.579357 30.511761, -90.582329 30.511752, -90.58332 30.51175, -90.584598 30.511747, -90.588433 30.51174, -90.589712 30.511739, -90.589705 30.511723, -90.589686 30.511675, -90.58968 30.51166, -90.589666 30.511619, -90.589624 30.5115, -90.58961 30.51146, -90.58953 30.51126, -90.58941 30.510842, -90.58939 30.51077, -90.58932 30.51062, -90.58917 30.51037, -90.58892 30.51004, -90.58869 30.50976, -90.58858 30.50955, -90.588486 30.509159, -90.58847 30.50909, -90.588355 30.508524, -90.588931 30.508571, -90.589359 30.508607, -90.589421 30.508612, -90.59066 30.508713, -90.591237 30.508761, -90.592022 30.508635, -90.592081 30.508648, -90.592117 30.508663, -90.59215 30.508679, -90.592181 30.508707, -90.59225 30.50882, -90.592123 30.507554, -90.592105 30.505295, -90.59212 30.5052, -90.59213 30.505146, -90.59216 30.505, -90.59225 30.5049, -90.592416 30.504872, -90.59249 30.50486, -90.592497 30.504784, -90.592501 30.504729, -90.592515 30.504564, -90.59252 30.50451, -90.592488 30.504507, -90.592395 30.504499, -90.592364 30.504497, -90.592273 30.504489, -90.592137 30.504477, -90.592 30.504466, -90.591909 30.504459, -90.591581 30.504431, -90.59108 30.50439, -90.590598 30.50436, -90.59027 30.50434, -90.58998 30.50432, -90.58982 30.50431, -90.589109 30.504271, -90.5889 30.50426, -90.58882 30.50425, -90.58884 30.5038, -90.58884 30.50376, -90.58884 30.50295, -90.588817 30.502289, -90.5888 30.5018, -90.588487 30.5018, -90.58764 30.5018, -90.587551 30.501796, -90.58737 30.50179, -90.58724 30.50179, -90.58727 30.50173, -90.587285 30.501562, -90.58729 30.50152, -90.58729 30.5009, -90.587288 30.500855, -90.58728 30.50062, -90.587293 30.500341, -90.5873 30.5002, -90.58729 30.50009, -90.58724 30.50004, -90.586918 30.500024, -90.586726 30.500013, -90.58665 30.50001, -90.58654 30.50002, -90.58645 30.50004, -90.586412 30.499854, -90.58641 30.49984, -90.58642 30.49971, -90.58641 30.49964, -90.58637 30.49948, -90.58634 30.49933, -90.586329 30.499295, -90.58631 30.49923, -90.58626 30.49912, -90.58621 30.49914, -90.58615 30.49915, -90.586075 30.499152, -90.58588 30.49916, -90.585508 30.49916, -90.58532 30.49916, -90.585191 30.49916, -90.58508 30.49916, -90.584804 30.499179, -90.584676 30.499188, -90.58449 30.4992, -90.58422 30.49922, -90.583935 30.499201, -90.58375 30.49919, -90.58375 30.49909, -90.583742 30.499051, -90.58373 30.49898, -90.58371 30.4989, -90.583683 30.498638, -90.58367 30.4985, -90.583835 30.498489, -90.584332 30.498457, -90.584498 30.498447, -90.584436 30.498326, -90.584283 30.497798, -90.584294 30.497597, -90.584315 30.497194, -90.584296 30.496506, -90.584442 30.496177, -90.584879 30.495699, -90.584844 30.495667, -90.58477 30.4956, -90.584752 30.495589, -90.58462 30.49551, -90.584302 30.495368, -90.58415 30.4953, -90.584429 30.495289, -90.58495 30.49527, -90.585266 30.495247, -90.585545 30.495227, -90.585654 30.495154, -90.585997 30.495009, -90.586149 30.494945, -90.586429 30.494544, -90.586517 30.494131, -90.58653 30.49384, -90.586523 30.493697, -90.586505 30.493318, -90.586502 30.493194, -90.586498 30.492971, -90.586559 30.492816, -90.586803 30.492207, -90.587145 30.491866, -90.587171 30.491853, -90.587521 30.491682, -90.58755 30.49153, -90.587574 30.491411, -90.587601 30.49107, -90.587614 30.490916, -90.58773 30.491021, -90.587988 30.491256, -90.588098 30.491312, -90.588239 30.491384, -90.588335 30.491345, -90.588504 30.491278, -90.588628 30.491241, -90.588728 30.491213, -90.589767 30.490909, -90.590643 30.490794, -90.591303 30.490502, -90.592154 30.489996, -90.592611 30.489815, -90.592704 30.489796, -90.592641 30.489666, -90.592555 30.489443, -90.592246 30.489421, -90.59179 30.489519, -90.59123 30.48964, -90.59028 30.48966, -90.59001 30.48967, -90.589475 30.489665, -90.5887 30.48966, -90.588625 30.48966, -90.588404 30.489662, -90.58833 30.489663, -90.587558 30.489668, -90.585901 30.489698, -90.585402 30.489703, -90.585292 30.489694, -90.584063 30.489708, -90.582753 30.489702, -90.582576 30.489705, -90.581839 30.489721, -90.581735 30.489724, -90.581732 30.489579, -90.581654 30.48917, -90.581645 30.489098, -90.581635 30.48906, -90.581614 30.489, -90.58159 30.48894, -90.58139 30.48828, -90.58122 30.48787, -90.58097 30.48719, -90.580883 30.487192, -90.57969 30.48722, -90.57963 30.487223, -90.5795 30.48723, -90.578432 30.487265, -90.57837 30.487268, -90.57831 30.48727, -90.57761 30.48731, -90.57712 30.48732, -90.57698 30.48732, -90.57677 30.48732, -90.5765 30.48731, -90.576451 30.488197, -90.57643 30.4886, -90.57641 30.48864, -90.576 30.48915, -90.57596 30.48918, -90.5758 30.4892, -90.575632 30.489233, -90.5755 30.48926, -90.57537 30.48928, -90.57522 30.48928, -90.57514 30.4893, -90.57505 30.48933, -90.574535 30.489653, -90.574553 30.489666, -90.574639 30.489741, -90.574691 30.489794, -90.57471 30.489825, -90.574853 30.489993, -90.574906 30.490047, -90.575028 30.490188, -90.575072 30.490247, -90.575194 30.490389, -90.575335 30.490564, -90.575388 30.490622, -90.575442 30.490704, -90.575554 30.490874, -90.575749 30.491168, -90.576115 30.491645, -90.576171 30.491741, -90.576405 30.492192, -90.576484 30.492394, -90.576558 30.492635, -90.576584 30.492703, -90.576621 30.492842, -90.576646 30.49291, -90.576676 30.493013, -90.57671 30.493189, -90.576713 30.493209, -90.576739 30.493368, -90.576811 30.493532, -90.576873 30.493625, -90.57689 30.493657, -90.576935 30.493717, -90.57709 30.493831, -90.577154 30.493871, -90.5712 30.495969, -90.569999 30.495974, -90.569956 30.494768, -90.569699 30.494749, -90.56972 30.49518, -90.56974 30.49589, -90.56975 30.4961, -90.569756 30.496349, -90.56976 30.49648, -90.56976 30.49679, -90.56976 30.49698, -90.56977 30.49715, -90.569775 30.497758, -90.56978 30.49836, -90.56979 30.49887, -90.56979 30.5, -90.569794 30.500095, -90.56982 30.50058, -90.569911 30.500859, -90.56999 30.5011, -90.57003 30.50132, -90.57003 30.50151, -90.570027 30.50154, -90.57001 30.50173, -90.56997 30.50221, -90.56995 30.50268, -90.56995 30.50305, -90.569963 30.503631, -90.56997 30.50393, -90.56997 30.50433, -90.56944 30.50433, -90.569379 30.50433, -90.567606 30.50433, -90.567126 30.50433, -90.567016 30.50433, -90.567016 30.504351, -90.567017 30.504489, -90.56702 30.504906, -90.567021 30.505045, -90.56702 30.505082, -90.56702 30.505196, -90.56702 30.505235, -90.566725 30.505244, -90.56623 30.50526, -90.56584 30.505249, -90.565546 30.505241, -90.565336 30.505235, -90.565324 30.505235, -90.564864 30.505241, -90.564707 30.505225, -90.564691 30.505224, -90.564644 30.505201, -90.564616 30.505143, -90.564615 30.505067, -90.564386 30.505069, -90.5637 30.505076, -90.563472 30.505079, -90.563281 30.505081, -90.56271 30.505089, -90.56252 30.505093, -90.560033 30.505115, -90.552574 30.505183, -90.550088 30.505206)), ((-90.452875 30.570355, -90.452892 30.569968, -90.452907 30.569621, -90.452926 30.5692, -90.45295 30.567417, -90.45296 30.566683, -90.453001 30.565241, -90.453017 30.56445, -90.453026 30.564035, -90.453047 30.563045, -90.453053 30.559641, -90.453046 30.556559, -90.453043 30.556091, -90.453028 30.553443, -90.455231 30.55344, -90.455493 30.552922, -90.455827 30.552495, -90.456177 30.552049, -90.45639 30.551716, -90.456456 30.551435, -90.456406 30.551018, -90.456385 30.550822, -90.457633 30.548799, -90.457801 30.548504, -90.461748 30.548474, -90.461754 30.548938, -90.461772 30.550332, -90.461779 30.550797, -90.462398 30.55079, -90.462588 30.55079, -90.463545 30.550791, -90.464731 30.550787, -90.465016 30.550784, -90.465719 30.550778, -90.465825 30.550762, -90.467087 30.550404, -90.467213 30.551031, -90.467307 30.551502, -90.468747 30.550622, -90.468997 30.55054, -90.469502 30.550415, -90.469636 30.550392, -90.469763 30.550355, -90.469779 30.550351, -90.469927 30.550292, -90.470001 30.550245, -90.470173 30.550199, -90.47029 30.550168, -90.470689 30.550056, -90.470862 30.550008, -90.471076 30.549955, -90.471552 30.54984, -90.471718 30.549799, -90.471932 30.549747, -90.471975 30.549737, -90.472115 30.549708, -90.472449 30.549642, -90.472667 30.549607, -90.472853 30.549579, -90.472965 30.549561, -90.47314 30.54953, -90.473816 30.549414, -90.474001 30.549379, -90.474183 30.549346, -90.474285 30.549314, -90.474255 30.549165, -90.474118 30.548744, -90.473569 30.547049, -90.473387 30.546484, -90.473469 30.546466, -90.473717 30.546414, -90.473789 30.546399, -90.4738 30.546397, -90.473941 30.546368, -90.474365 30.546284, -90.474507 30.546256, -90.474789 30.546201, -90.474891 30.546179, -90.475946 30.545957, -90.47604 30.545938, -90.476232 30.545901, -90.476426 30.545868, -90.476483 30.54597, -90.476699 30.546454, -90.476988 30.547063, -90.47703 30.547152, -90.477068 30.54726, -90.477265 30.547812, -90.477672 30.548818, -90.477977 30.549493, -90.478155 30.549878, -90.47839 30.550378, -90.479051 30.551397, -90.479185 30.551604, -90.479884 30.552687, -90.480621 30.55378, -90.48068 30.553875, -90.481174 30.554665, -90.481301 30.5551, -90.481276 30.556116, -90.481213 30.558006, -90.481169 30.559349, -90.481169 30.559436, -90.481169 30.559866, -90.48117 30.560223, -90.481214 30.560344, -90.481418 30.560486, -90.481879 30.5608, -90.482047 30.560915, -90.482225 30.561058, -90.482221 30.561925, -90.482211 30.564526, -90.482208 30.565394, -90.482207 30.565714, -90.482205 30.565838, -90.482194 30.56717, -90.482191 30.567615, -90.482189 30.567853, -90.482183 30.568569, -90.482182 30.568808, -90.482176 30.569515, -90.482165 30.570843, -90.482127 30.571282, -90.482095 30.57159, -90.482075 30.571628, -90.481981 30.571821, -90.48188 30.571997, -90.481733 30.572107, -90.48164 30.572158, -90.481897 30.573014, -90.482671 30.575585, -90.482929 30.576442, -90.482917 30.576445, -90.482862 30.57645, -90.48266 30.576471, -90.482593 30.576479, -90.48233 30.576508, -90.482261 30.576514, -90.481266 30.576604, -90.481225 30.576608, -90.480936 30.576648, -90.480946 30.576784, -90.480948 30.576807, -90.480998 30.57706, -90.481024 30.577187, -90.481053 30.577321, -90.481062 30.577362, -90.481503 30.57879, -90.481671 30.579334, -90.481673 30.579352, -90.482816 30.58321, -90.482856 30.583343, -90.483085 30.584211, -90.483206 30.584698, -90.483593 30.584697, -90.484756 30.584697, -90.485144 30.584697, -90.485171 30.584773, -90.485292 30.585151, -90.485406 30.585504, -90.485564 30.585973, -90.485717 30.586519, -90.485847 30.58698, -90.485945 30.587326, -90.48598 30.587449, -90.486258 30.58836, -90.486275 30.588413, -90.486367 30.588704, -90.486079 30.588761, -90.485218 30.588934, -90.485131 30.588951, -90.485227 30.589025, -90.485284 30.589069, -90.485427 30.589198, -90.485438 30.589208, -90.485496 30.589275, -90.485541 30.589353, -90.485577 30.589435, -90.485654 30.589656, -90.485685 30.589743, -90.485757 30.589916, -90.485833 30.590036, -90.485865 30.590071, -90.485903 30.590099, -90.485931 30.590108, -90.486005 30.590132, -90.486101 30.590136, -90.486132 30.590138, -90.486355 30.590117, -90.48662 30.590073, -90.486698 30.590061, -90.486782 30.590015, -90.486829 30.590034, -90.48684 30.590038, -90.486911 30.590068, -90.487024 30.59006, -90.487055 30.590058, -90.487087 30.590053, -90.487619 30.591807, -90.488 30.593064, -90.489215 30.597069, -90.489748 30.598824, -90.489976 30.599569, -90.492085 30.606651, -90.494836 30.6159, -90.494786 30.615902, -90.494639 30.615911, -90.49459 30.615914, -90.494362 30.615927, -90.494147 30.615931, -90.49282 30.61596, -90.492378 30.61597, -90.492673 30.616999, -90.492683 30.617033, -90.493625 30.620214, -90.493939 30.621275, -90.49407 30.621689, -90.494444 30.622866, -90.494467 30.622933, -90.494611 30.623344, -90.494444 30.623395, -90.493859 30.62353, -90.4938 30.623545, -90.491867 30.624068, -90.491664 30.62411, -90.491605 30.624115, -90.491501 30.624105, -90.491458 30.62409, -90.4914 30.624026, -90.491377 30.624001, -90.491219 30.623768, -90.491003 30.62338, -90.490971 30.623312, -90.490659 30.623329, -90.489723 30.62338, -90.489412 30.623398, -90.489417 30.623727, -90.489432 30.624714, -90.489437 30.625043, -90.489212 30.625045, -90.489263 30.625181, -90.489568 30.626333, -90.490127 30.628444, -90.490834 30.630847, -90.491275 30.632347, -90.491352 30.63259, -90.491584 30.633318, -90.491662 30.633562, -90.491862 30.633517, -90.492462 30.633385, -90.492663 30.633342, -90.492763 30.633718, -90.49281 30.633892, -90.493082 30.634842, -90.49319 30.635217, -90.492997 30.635267, -90.492951 30.635279, -90.492413 30.635397, -90.492219 30.635441, -90.492036 30.635481, -90.491803 30.635533, -90.491488 30.635598, -90.491404 30.635616, -90.491304 30.635629, -90.491315 30.635854, -90.491327 30.63607, -90.491323 30.63653, -90.491322 30.636756, -90.49158 30.636755, -90.492357 30.636755, -90.492616 30.636755, -90.492741 30.637177, -90.493117 30.638444, -90.493243 30.638867, -90.493414 30.638868, -90.493478 30.638876, -90.493515 30.638886, -90.493571 30.638912, -90.493591 30.638928, -90.493622 30.638962, -90.493639 30.639002, -90.493646 30.639092, -90.493645 30.639161, -90.493632 30.639275, -90.493598 30.6395, -90.493586 30.639675, -90.493569 30.639957, -90.493566 30.639993, -90.493559 30.640103, -90.493558 30.64014, -90.49355 30.640264, -90.493536 30.640498, -90.493526 30.640639, -90.493519 30.640764, -90.493503 30.64102, -90.493495 30.641193, -90.493479 30.641569, -90.493493 30.642482, -90.493495 30.642562, -90.493491 30.64272, -90.493489 30.642791, -90.493491 30.642912, -90.493054 30.642927, -90.492021 30.642963, -90.491907 30.642979, -90.491785 30.643009, -90.491752 30.64302, -90.49144 30.643134, -90.491347 30.643181, -90.491324 30.643228, -90.49133 30.644008, -90.491329 30.644038, -90.491312 30.644844, -90.491267 30.645627, -90.491246 30.646338, -90.491225 30.646623, -90.491181 30.647267, -90.491174 30.647485, -90.491155 30.648001, -90.491152 30.648116, -90.491144 30.649404, -90.491148 30.649551, -90.491155 30.64981, -90.491175 30.649904, -90.49122 30.65001, -90.491242 30.650052, -90.491448 30.650111, -90.491772 30.650151, -90.492024 30.650172, -90.492245 30.650175, -90.492651 30.650162, -90.492688 30.650161, -90.495776 30.650147, -90.496916 30.650147, -90.49789 30.650148, -90.498286 30.650144, -90.498338 30.650139, -90.49837 30.650226, -90.498394 30.650284, -90.498418 30.65041, -90.498395 30.650728, -90.49829 30.651548, -90.498258 30.651757, -90.498242 30.651791, -90.498203 30.652027, -90.498193 30.652105, -90.498174 30.652153, -90.498166 30.652201, -90.498153 30.652231, -90.498129 30.652408, -90.498143 30.652634, -90.49817 30.65279, -90.498183 30.652852, -90.49821 30.652879, -90.498226 30.652911, -90.498257 30.652953, -90.498305 30.653008, -90.498345 30.653067, -90.49848 30.653227, -90.498491 30.653255, -90.498528 30.653296, -90.498547 30.653333, -90.49856 30.653351, -90.498592 30.653411, -90.498603 30.653438, -90.498629 30.653465, -90.498645 30.653507, -90.498714 30.653754, -90.498736 30.653855, -90.498752 30.653905, -90.498837 30.654121, -90.498948 30.654267, -90.49907 30.654359, -90.499121 30.654371, -90.499139 30.654382, -90.499163 30.654388, -90.499291 30.654393, -90.499277 30.654566, -90.499274 30.654624, -90.499256 30.654842, -90.499207 30.655419, -90.499188 30.655721, -90.499146 30.656184, -90.499072 30.657017, -90.499022 30.657576, -90.498981 30.65804, -90.498977 30.658108, -90.498973 30.658196, -90.498964 30.658312, -90.498959 30.658381, -90.498947 30.658526, -90.498916 30.658927, -90.498911 30.658963, -90.498893 30.659109, -90.498864 30.659342, -90.498777 30.660041, -90.498748 30.660274, -90.498738 30.66037, -90.498707 30.66066, -90.498698 30.660757, -90.498685 30.660874, -90.498649 30.661227, -90.498641 30.661308, -90.498635 30.661345, -90.49861 30.661528, -90.498535 30.66208, -90.498516 30.662265, -90.498494 30.662471, -90.498482 30.662704, -90.498471 30.662958, -90.49847 30.663494, -90.498472 30.663654, -90.498487 30.664027, -90.498505 30.664468, -90.498509 30.664584, -90.498512 30.664655, -90.498532 30.664935, -90.498541 30.665052, -90.498545 30.665108, -90.498557 30.665276, -90.498562 30.665332, -90.498567 30.665404, -90.498586 30.665566, -90.498616 30.665733, -90.498654 30.665939, -90.49867 30.665991, -90.498698 30.666064, -90.498957 30.666851, -90.498968 30.666895, -90.499001 30.667022, -90.499072 30.667164, -90.499117 30.667221, -90.499154 30.667244, -90.499246 30.667148, -90.499348 30.667207, -90.499935 30.667107, -90.499961 30.667305, -90.500873 30.667305, -90.501359 30.667313, -90.501761 30.667319, -90.501947 30.667323, -90.502703 30.667322, -90.50287 30.667331, -90.502969 30.667342, -90.503048 30.667352, -90.503363 30.667425, -90.50335 30.667626, -90.503349 30.66768, -90.503344 30.667718, -90.503342 30.667757, -90.503342 30.667858, -90.503389 30.668093, -90.5034 30.668182, -90.503397 30.668228, -90.503386 30.66843, -90.503382 30.668499, -90.50337 30.668705, -90.503367 30.668775, -90.503209 30.668773, -90.502812 30.66877, -90.502736 30.668773, -90.50271 30.668775, -90.502644 30.668787, -90.502584 30.668809, -90.50254 30.668845, -90.502506 30.66889, -90.502478 30.668945, -90.502458 30.669012, -90.502448 30.669048, -90.502434 30.669186, -90.502438 30.669296, -90.502461 30.669522, -90.502468 30.669638, -90.502462 30.669744, -90.502454 30.669927, -90.502457 30.669959, -90.502467 30.669988, -90.502491 30.670005, -90.50252 30.670018, -90.502598 30.670041, -90.502632 30.670045, -90.502793 30.670065, -90.503051 30.670089, -90.503163 30.670095, -90.503254 30.6701, -90.50334 30.670109, -90.503339 30.670656, -90.503336 30.672298, -90.503336 30.672846, -90.503334 30.672954, -90.503328 30.673281, -90.503327 30.67339, -90.503325 30.673599, -90.503323 30.67418, -90.503329 30.674226, -90.503333 30.674251, -90.503369 30.67432, -90.50345 30.67439, -90.503554 30.674433, -90.50381 30.674523, -90.503904 30.674556, -90.504065 30.674589, -90.504222 30.674604, -90.50459 30.674609, -90.504954 30.674592, -90.505181 30.674583, -90.505338 30.674568, -90.505467 30.67457, -90.505797 30.674556, -90.505968 30.674549, -90.506091 30.674538, -90.506217 30.674512, -90.506465 30.67444, -90.506921 30.674319, -90.507142 30.674263, -90.507589 30.674152, -90.507641 30.67415, -90.507799 30.674146, -90.507852 30.674145, -90.507901 30.674143, -90.508049 30.67414, -90.508099 30.674139, -90.50808 30.673222, -90.508073 30.672814, -90.50804 30.670811, -90.508005 30.668839, -90.507982 30.667515, -90.507972 30.66712, -90.50795 30.666227, -90.507947 30.665937, -90.507943 30.665543, -90.508051 30.665543, -90.508139 30.665543, -90.508375 30.665538, -90.508483 30.665537, -90.508538 30.665535, -90.508704 30.665533, -90.50876 30.665533, -90.509088 30.665527, -90.509197 30.665526, -90.510508 30.665526, -90.510946 30.665526, -90.511066 30.665526, -90.511429 30.665526, -90.51155 30.665526, -90.51145 30.665412, -90.511421 30.665373, -90.511302 30.665217, -90.511146 30.665026, -90.51104 30.664912, -90.510905 30.664766, -90.510848 30.664704, -90.510807 30.664659, -90.510683 30.664515, -90.510629 30.664452, -90.510535 30.664335, -90.510455 30.664221, -90.51037 30.664064, -90.510355 30.664033, -90.510293 30.663903, -90.510167 30.663664, -90.51013 30.663583, -90.510038 30.663326, -90.50996 30.663134, -90.509872 30.662977, -90.509776 30.662822, -90.509695 30.662682, -90.509674 30.662646, -90.509577 30.662468, -90.509472 30.662259, -90.509463 30.662238, -90.509379 30.662046, -90.509303 30.661832, -90.509271 30.661731, -90.509191 30.661476, -90.509085 30.661114, -90.508984 30.660662, -90.508828 30.660168, -90.508819 30.66014, -90.508681 30.659647, -90.508662 30.659564, -90.508659 30.65952, -90.508645 30.659309, -90.508649 30.659134, -90.508653 30.659022, -90.508653 30.659006, -90.50764 30.659, -90.507396 30.658984, -90.507105 30.656802, -90.505231 30.650086, -90.504733 30.648267, -90.504662 30.648013, -90.504589 30.647771, -90.504146 30.64629, -90.503999 30.645797, -90.504036 30.645798, -90.504148 30.645803, -90.504186 30.645805, -90.504128 30.645606, -90.504078 30.645434, -90.503947 30.645014, -90.503886 30.644818, -90.504346 30.644758, -90.505729 30.644582, -90.50619 30.644523, -90.506234 30.644568, -90.506366 30.644705, -90.50641 30.644751, -90.506488 30.644843, -90.506555 30.644937, -90.506645 30.645063, -90.506716 30.645191, -90.506866 30.645569, -90.506953 30.645789, -90.507751 30.645794, -90.510146 30.645809, -90.510945 30.645814, -90.511041 30.645817, -90.511332 30.645828, -90.511429 30.645832, -90.511637 30.645908, -90.512256 30.646152, -90.51233 30.646181, -90.51246 30.646241, -90.512665 30.646349, -90.51275 30.646381, -90.512799 30.6464, -90.512928 30.646431, -90.513059 30.646452, -90.513223 30.646463, -90.51363 30.646471, -90.513709 30.646471, -90.514032 30.646471, -90.514194 30.64647, -90.515317 30.646482, -90.516294 30.646494, -90.516408 30.646505, -90.516499 30.646526, -90.516588 30.646596, -90.516612 30.646681, -90.516617 30.646751, -90.516603 30.647, -90.51658 30.647388, -90.51658 30.647428, -90.516583 30.647578, -90.516557 30.649151, -90.51655 30.649577, -90.516552 30.649859, -90.51656 30.649921, -90.516587 30.649987, -90.516654 30.650077, -90.516787 30.650181, -90.516911 30.650252, -90.516928 30.650263, -90.51696 30.650189, -90.516978 30.650153, -90.516986 30.65013, -90.517007 30.650102, -90.517028 30.650052, -90.517047 30.650024, -90.517076 30.649992, -90.517139 30.649859, -90.517149 30.649831, -90.517176 30.649804, -90.517212 30.649717, -90.517286 30.649565, -90.517312 30.649492, -90.517344 30.64945, -90.517365 30.6494, -90.517381 30.649379, -90.517391 30.649368, -90.517449 30.649239, -90.517465 30.649212, -90.517492 30.649184, -90.517502 30.649148, -90.517518 30.649134, -90.517523 30.649106, -90.51755 30.649079, -90.517555 30.649056, -90.517572 30.649036, -90.517592 30.648992, -90.517618 30.648959, -90.517649 30.648877, -90.517671 30.648849, -90.517681 30.648826, -90.517697 30.648803, -90.517739 30.648707, -90.517755 30.648684, -90.517781 30.648657, -90.517785 30.648639, -90.517855 30.64851, -90.517952 30.648317, -90.517976 30.648253, -90.517992 30.64823, -90.518018 30.648202, -90.518022 30.648184, -90.518047 30.648138, -90.51815 30.6479, -90.518207 30.647753, -90.518213 30.647734, -90.518234 30.647707, -90.518244 30.647684, -90.518265 30.647656, -90.518286 30.647615, -90.518328 30.6475, -90.518349 30.647477, -90.518465 30.647202, -90.518586 30.646895, -90.518581 30.646872, -90.518554 30.646808, -90.518525 30.646767, -90.518478 30.646707, -90.518442 30.646661, -90.51841 30.646602, -90.518394 30.646584, -90.518335 30.646474, -90.518293 30.646419, -90.518277 30.646387, -90.518224 30.646314, -90.518197 30.646286, -90.518186 30.646259, -90.51812 30.646167, -90.518059 30.646106, -90.518052 30.646089, -90.518029 30.646058, -90.517925 30.64593, -90.517907 30.645893, -90.518574 30.645899, -90.520486 30.645919, -90.520465 30.64583, -90.520319 30.645179, -90.520095 30.644322, -90.519423 30.641752, -90.5192 30.640896, -90.519246 30.640902, -90.519281 30.640913, -90.519347 30.640924, -90.519371 30.640931, -90.519392 30.640945, -90.51944 30.640958, -90.519477 30.640981, -90.51952 30.64099, -90.519546 30.641004, -90.519663 30.641013, -90.519684 30.641026, -90.51971 30.641026, -90.51979 30.641049, -90.519811 30.641063, -90.519928 30.641053, -90.520081 30.641034, -90.520155 30.640993, -90.520177 30.640988, -90.52023 30.640961, -90.52033 30.640919, -90.520351 30.640916, -90.520552 30.64085, -90.520577 30.640842, -90.520608 30.640822, -90.520695 30.640772, -90.520851 30.64063, -90.520859 30.640619, -90.52088 30.640588, -90.520915 30.640554, -90.520959 30.640512, -90.520996 30.640464, -90.521001 30.640441, -90.521022 30.640418, -90.521033 30.64039, -90.521063 30.640347, -90.521075 30.640331, -90.52108 30.640308, -90.521103 30.640272, -90.52101 30.640232, -90.520893 30.640181, -90.520733 30.640112, -90.520641 30.640072, -90.52065 30.640054, -90.520629 30.640045, -90.520568 30.64002, -90.520548 30.640012, -90.520527 30.640003, -90.520464 30.639977, -90.520444 30.639969, -90.520427 30.639962, -90.520376 30.639941, -90.52036 30.639935, -90.520343 30.639928, -90.520295 30.639908, -90.520279 30.639902, -90.520127 30.639844, -90.519673 30.639672, -90.519642 30.63966, -90.519523 30.639614, -90.519435 30.639579, -90.519171 30.639477, -90.519084 30.639443, -90.519025 30.639421, -90.51885 30.639355, -90.518792 30.639333, -90.518377 30.639176, -90.517229 30.638742, -90.517136 30.638704, -90.516725 30.63854, -90.516389 30.638406, -90.516267 30.63836, -90.515602 30.638108, -90.514892 30.637828, -90.514788 30.637787, -90.51443 30.637662, -90.514391 30.637648, -90.514073 30.637547, -90.513989 30.63752, -90.51377 30.637456, -90.51355 30.637397, -90.513231 30.637325, -90.512984 30.637278, -90.512617 30.63721, -90.512315 30.637162, -90.510027 30.636726, -90.510028 30.636716, -90.509993 30.636603, -90.50995 30.636489, -90.50994 30.636466, -90.509924 30.636443, -90.509908 30.636407, -90.509892 30.636384, -90.509876 30.636347, -90.50986 30.636329, -90.509812 30.636201, -90.509763 30.636031, -90.50964 30.635706, -90.509598 30.635601, -90.509582 30.635583, -90.509571 30.635541, -90.509548 30.635514, -90.509544 30.635496, -90.509528 30.635477, -90.509512 30.635445, -90.509448 30.635299, -90.509422 30.635226, -90.509358 30.635143, -90.50931 30.635093, -90.509304 30.635075, -90.509292 30.635059, -90.509249 30.635024, -90.509209 30.635002, -90.509124 30.634942, -90.509124 30.634919, -90.508837 30.634783, -90.508816 30.634764, -90.50879 30.634764, -90.508763 30.634751, -90.508646 30.634714, -90.508555 30.6347, -90.50853 30.634687, -90.507999 30.634537, -90.507967 30.634519, -90.507925 30.634506, -90.507877 30.634478, -90.507845 30.634469, -90.507787 30.634433, -90.507771 30.634419, -90.507628 30.634419, -90.507601 30.634401, -90.507596 30.634383, -90.507551 30.634353, -90.507479 30.634326, -90.507437 30.634305, -90.507392 30.634269, -90.507148 30.634144, -90.507124 30.634132, -90.507049 30.634091, -90.506761 30.633854, -90.506688 30.633794, -90.506582 30.633696, -90.506539 30.633648, -90.506473 30.633593, -90.506433 30.63357, -90.50638 30.633525, -90.506228 30.633379, -90.505833 30.633038, -90.505753 30.63296, -90.505726 30.632926, -90.505682 30.632883, -90.505466 30.632668, -90.505397 30.632574, -90.505381 30.632549, -90.505364 30.632521, -90.506042 30.632369, -90.506249 30.632323, -90.507553 30.632032, -90.507835 30.63195, -90.507982 30.631885, -90.508053 30.631841, -90.508064 30.631835, -90.508139 30.631774, -90.508262 30.631631, -90.508377 30.631437, -90.508454 30.631283, -90.508427 30.631203, -90.5084 30.631079, -90.508393 30.630746, -90.508385 30.630294, -90.508395 30.629113, -90.5084 30.628569, -90.508402 30.628238, -90.508412 30.627245, -90.508415 30.626914, -90.508418 30.626533, -90.508428 30.625392, -90.508432 30.625012, -90.508033 30.625014, -90.507761 30.625017, -90.505748 30.62504, -90.505078 30.625049, -90.505097 30.625145, -90.505112 30.625188, -90.505025 30.625248, -90.505002 30.625262, -90.504924 30.625298, -90.504906 30.625308, -90.504743 30.625414, -90.504662 30.625475, -90.504573 30.625542, -90.504505 30.625561, -90.504359 30.625586, -90.504229 30.62558, -90.504197 30.625562, -90.504102 30.625479, -90.504033 30.625411, -90.50402 30.625364, -90.504011 30.625329, -90.50399 30.625301, -90.503979 30.625269, -90.503963 30.625253, -90.503957 30.625235, -90.503925 30.625188, -90.503903 30.625141, -90.503863 30.625086, -90.503839 30.625057, -90.503653 30.625059, -90.503097 30.625067, -90.502912 30.625071, -90.502815 30.624891, -90.502524 30.624352, -90.502428 30.624173, -90.502297 30.623956, -90.502249 30.623858, -90.502105 30.623559, -90.501821 30.623175, -90.501749 30.622909, -90.501721 30.622804, -90.501679 30.622555, -90.501731 30.622543, -90.501891 30.622508, -90.501944 30.622497, -90.501901 30.62241, -90.501805 30.622154, -90.501714 30.621879, -90.501612 30.621595, -90.50158 30.62149, -90.501537 30.621394, -90.501527 30.621362, -90.501511 30.621339, -90.501457 30.62117, -90.501425 30.621087, -90.501409 30.621055, -90.501409 30.621014, -90.501387 30.620872, -90.501366 30.620849, -90.501296 30.620629, -90.501221 30.620419, -90.501157 30.620213, -90.501114 30.620061, -90.501098 30.620025, -90.501082 30.620006, -90.501072 30.619984, -90.50104 30.619942, -90.501025 30.619915, -90.499546 30.620243, -90.49939 30.620286, -90.499239 30.620338, -90.499047 30.620427, -90.498971 30.620478, -90.498838 30.620544, -90.498726 30.620576, -90.49813 30.620707, -90.497497 30.620861, -90.496924 30.620976, -90.496656 30.621035, -90.496616 30.620912, -90.496577 30.620763, -90.496237 30.619671, -90.496045 30.619056, -90.495993 30.618881, -90.496304 30.61881, -90.497238 30.618599, -90.49755 30.618529, -90.497499 30.618345, -90.497349 30.617793, -90.497299 30.61761, -90.496981 30.617675, -90.496027 30.617872, -90.49571 30.617939, -90.495584 30.617525, -90.495209 30.616286, -90.495084 30.615873, -90.494601 30.614276, -90.494031 30.612387, -90.493153 30.609486, -90.492671 30.60789, -90.492654 30.607836, -90.492606 30.607676, -90.49259 30.607623, -90.492567 30.607546, -90.492533 30.607428, -90.492368 30.606844, -90.492314 30.60665, -90.492121 30.60589, -90.491888 30.604969, -90.491484 30.603628, -90.491258 30.602878, -90.491178 30.602635, -90.491032 30.602159, -90.490887 30.601687, -90.490585 30.600646, -90.490391 30.599994, -90.490276 30.599606, -90.490169 30.599275, -90.490137 30.599174, -90.490043 30.598874, -90.490012 30.598774, -90.489849 30.598225, -90.489724 30.597799, -90.489353 30.596582, -90.489187 30.596035, -90.489158 30.595941, -90.489073 30.59566, -90.489045 30.595567, -90.488826 30.594849, -90.488708 30.59445, -90.487875 30.591637, -90.48771 30.591098, -90.487369 30.589984, -90.48716 30.589296, -90.48676 30.587978, -90.486536 30.587232, -90.486331 30.586544, -90.486503 30.586502, -90.486568 30.586487, -90.486684 30.586379, -90.486988 30.586342, -90.487165 30.586322, -90.487276 30.58631, -90.48748 30.586253, -90.487574 30.586228, -90.487704 30.586154, -90.48776 30.586017, -90.48776 30.585817, -90.487659 30.585452, -90.487574 30.58514, -90.487626 30.58511, -90.487785 30.585022, -90.487839 30.584994, -90.487869 30.585082, -90.487959 30.585336, -90.487962 30.585346, -90.487991 30.585435, -90.488241 30.58541, -90.488639 30.585371, -90.488993 30.585337, -90.489244 30.585313, -90.489685 30.585275, -90.491012 30.585162, -90.491454 30.585125, -90.491451 30.585035, -90.491443 30.584766, -90.491441 30.584677, -90.491439 30.584642, -90.491436 30.584537, -90.491436 30.584503, -90.49143 30.584134, -90.491416 30.583141, -90.491412 30.583029, -90.4914 30.582661, -90.491397 30.582476, -90.491395 30.582321, -90.491397 30.581985, -90.4914 30.581924, -90.491406 30.581821, -90.491418 30.581787, -90.491447 30.581754, -90.491588 30.581748, -90.492257 30.581737, -90.49251 30.581734, -90.494 30.581727, -90.49469 30.581736, -90.495134 30.581743, -90.495265 30.581734, -90.4955 30.58171, -90.49551 30.581535, -90.495492 30.580646, -90.495488 30.579914, -90.495482 30.578971, -90.495495 30.577339, -90.4955 30.577174, -90.493795 30.57718, -90.493675 30.577181, -90.493358 30.57718, -90.492698 30.577178, -90.492407 30.577178, -90.492091 30.577178, -90.491946 30.577178, -90.491511 30.577179, -90.491367 30.57718, -90.491199 30.57718, -90.490986 30.577181, -90.490698 30.577182, -90.490531 30.577183, -90.490487 30.577183, -90.49018 30.577183, -90.489129 30.577183, -90.488779 30.577183, -90.488733 30.577183, -90.488595 30.577183, -90.48855 30.577183, -90.488533 30.576931, -90.488495 30.576695, -90.488448 30.576619, -90.488209 30.576233, -90.487594 30.575101, -90.487318 30.574591, -90.486809 30.573653, -90.485283 30.57084, -90.484941 30.57021, -90.484841 30.569875, -90.484803 30.569747, -90.484769 30.569632, -90.484708 30.569359, -90.48468 30.56923, -90.484635 30.56903, -90.484503 30.56843, -90.484459 30.568231, -90.484454 30.568208, -90.484401 30.56797, -90.484228 30.567188, -90.484171 30.566928, -90.484014 30.566134, -90.483545 30.563754, -90.483538 30.563718, -90.483477 30.563405, -90.483383 30.562962, -90.483768 30.562955, -90.484622 30.562965, -90.484999 30.562959, -90.485095 30.562958, -90.485195 30.562957, -90.486553 30.562954, -90.48726 30.562948, -90.490232 30.562924, -90.491142 30.562917, -90.491945 30.562923, -90.492671 30.562927, -90.492841 30.562929, -90.494851 30.562933, -90.495578 30.562935, -90.495574 30.56312, -90.495575 30.56324, -90.495587 30.564156, -90.495592 30.564462, -90.495594 30.564639, -90.495596 30.56477, -90.49565 30.564774, -90.495732 30.564774, -90.495833 30.564773, -90.495886 30.564772, -90.49599 30.564771, -90.496237 30.564768, -90.496327 30.564771, -90.496421 30.564769, -90.496512 30.56477, -90.496607 30.564768, -90.496702 30.564768, -90.496798 30.564771, -90.496901 30.564772, -90.497005 30.564771, -90.497105 30.564769, -90.497203 30.564767, -90.497298 30.564767, -90.497394 30.564767, -90.497486 30.564766, -90.497574 30.564765, -90.497746 30.564761, -90.497829 30.564758, -90.497911 30.564757, -90.497994 30.564756, -90.498075 30.564757, -90.498152 30.56476, -90.498229 30.564762, -90.498305 30.564763, -90.498379 30.564763, -90.498454 30.564762, -90.498528 30.564759, -90.498602 30.564755, -90.498675 30.564752, -90.498745 30.564751, -90.498814 30.564751, -90.498882 30.564752, -90.498948 30.564753, -90.499017 30.564752, -90.499091 30.564749, -90.499193 30.564746, -90.499252 30.564745, -90.499307 30.564746, -90.49936 30.564748, -90.499417 30.564749, -90.499459 30.56475, -90.499552 30.564161, -90.499713 30.563417, -90.499824 30.562908, -90.5001 30.562904, -90.500931 30.562894, -90.501209 30.562891, -90.501834 30.562889, -90.503712 30.562884, -90.504062 30.562884, -90.50426 30.562884, -90.504338 30.562887, -90.504487 30.562891, -90.504858 30.562884, -90.506421 30.562859, -90.506943 30.562851, -90.506992 30.56285, -90.507052 30.562849, -90.507142 30.562848, -90.507193 30.562848, -90.507609 30.562844, -90.507993 30.562842, -90.508049 30.562842, -90.508857 30.562852, -90.509196 30.562857, -90.509274 30.562857, -90.510162 30.562856, -90.51066 30.562865, -90.512292 30.562839, -90.512573 30.562842, -90.513747 30.562852, -90.515148 30.562838, -90.516109 30.562843, -90.516187 30.562836, -90.516312 30.562814, -90.516395 30.562783, -90.516495 30.562733, -90.51667 30.562739, -90.516684 30.561717, -90.516689 30.559175, -90.516727 30.559174, -90.516632 30.555464, -90.516621 30.555027, -90.516637 30.554606, -90.516637 30.554457, -90.516638 30.553944, -90.516641 30.552867, -90.516627 30.552407, -90.516612 30.551895, -90.51661 30.551539, -90.51661 30.551518, -90.516619 30.550986, -90.516616 30.55039, -90.516615 30.550014, -90.516611 30.549833, -90.516602 30.54929, -90.5166 30.54911, -90.516603 30.548924, -90.516613 30.548368, -90.516617 30.548183, -90.516617 30.547875, -90.516619 30.547237, -90.516618 30.547115, -90.516617 30.546951, -90.516616 30.546644, -90.516613 30.54625, -90.516611 30.545661, -90.516601 30.545071, -90.516605 30.544678, -90.51713 30.544672, -90.518685 30.544657, -90.518708 30.544656, -90.519159 30.544655, -90.519234 30.544653, -90.519505 30.544646, -90.519624 30.544645, -90.520105 30.544642, -90.520795 30.544639, -90.521186 30.544637, -90.521919 30.544633, -90.521934 30.542689, -90.52196 30.541326, -90.525326 30.5415, -90.52534 30.541151, -90.525367 30.541128, -90.525491 30.541127, -90.525859 30.541127, -90.52588 30.54114, -90.525901 30.541126, -90.526584 30.541129, -90.526603 30.541118, -90.526645 30.541113, -90.52678 30.541115, -90.526807 30.54111, -90.526955 30.54111, -90.527071 30.541118, -90.527162 30.541196, -90.527172 30.541212, -90.527178 30.54126, -90.527178 30.541596, -90.529453 30.541713, -90.529496 30.541844, -90.530038 30.543508, -90.530192 30.543983, -90.530343 30.544445, -90.530381 30.544554, -90.530623 30.544556, -90.532 30.544533, -90.532053 30.544532, -90.533382 30.544525, -90.535787 30.544503, -90.535823 30.544502, -90.536045 30.5445, -90.536822 30.544494, -90.537082 30.544493, -90.538278 30.544485, -90.539964 30.54446, -90.540063 30.544457, -90.541012 30.54444, -90.542105 30.544429, -90.542091 30.545575, -90.542019 30.548332, -90.542025 30.549399, -90.542028 30.549891, -90.542013 30.55212, -90.54201 30.5526, -90.542003 30.553667, -90.542 30.554035, -90.541993 30.55514, -90.541991 30.555509, -90.541992 30.55578, -90.541998 30.556593, -90.542 30.556865, -90.541998 30.556948, -90.541995 30.557198, -90.541995 30.557282, -90.541986 30.557951, -90.54198 30.558471, -90.541975 30.558963, -90.541994 30.559033, -90.542019 30.559066, -90.542059 30.559084, -90.542149 30.559096, -90.54381 30.559075, -90.544978 30.559088, -90.546168 30.559102, -90.546319 30.559102, -90.546772 30.559105, -90.546924 30.559107, -90.546938 30.559283, -90.546953 30.559296, -90.546939 30.559539, -90.546934 30.559718, -90.54694 30.559915, -90.546932 30.559995, -90.546899 30.560268, -90.54687 30.560344, -90.546855 30.560433, -90.546844 30.560454, -90.546829 30.560516, -90.546786 30.560592, -90.546784 30.560617, -90.546789 30.560635, -90.546742 30.560741, -90.546732 30.560782, -90.546708 30.56084, -90.546692 30.560881, -90.546635 30.561234, -90.546626 30.561277, -90.546556 30.561605, -90.546537 30.561697, -90.546521 30.561761, -90.546515 30.561791, -90.546461 30.562043, -90.546406 30.562174, -90.546406 30.562229, -90.546383 30.562346, -90.546354 30.562513, -90.546338 30.562586, -90.546297 30.562784, -90.546267 30.562915, -90.546253 30.562973, -90.546216 30.563139, -90.546213 30.563149, -90.546202 30.563209, -90.546164 30.563392, -90.546144 30.563474, -90.546107 30.563685, -90.546073 30.56377, -90.546071 30.563804, -90.546087 30.563818, -90.546076 30.563878, -90.546063 30.563908, -90.546056 30.563956, -90.546032 30.564013, -90.54602 30.564075, -90.546019 30.564093, -90.546003 30.564144, -90.545902 30.564641, -90.54585 30.56488, -90.545832 30.564983, -90.545811 30.565157, -90.545812 30.565231, -90.545831 30.565389, -90.545854 30.56555, -90.545866 30.565602, -90.545879 30.565636, -90.545906 30.56581, -90.545915 30.565849, -90.545923 30.56587, -90.545931 30.565909, -90.545944 30.565947, -90.545952 30.565986, -90.54596 30.566007, -90.545976 30.566085, -90.545986 30.566536, -90.546473 30.564798, -90.546512 30.564723, -90.546599 30.564659, -90.546695 30.564616, -90.546764 30.564597, -90.546955 30.564571, -90.547205 30.564566, -90.547311 30.564564, -90.547818 30.564567, -90.548077 30.564547, -90.548313 30.564505, -90.548392 30.564495, -90.54855 30.564489, -90.548708 30.564495, -90.548866 30.564516, -90.549169 30.564567, -90.549314 30.564588, -90.549607 30.564594, -90.549632 30.564593, -90.550321 30.564581, -90.550445 30.564573, -90.550448 30.56427, -90.550457 30.56341, -90.550458 30.563363, -90.550467 30.563061, -90.550468 30.562941, -90.55047 30.562751, -90.550469 30.562581, -90.550469 30.562462, -90.550468 30.562251, -90.550466 30.561621, -90.550466 30.561411, -90.550465 30.56104, -90.550462 30.55993, -90.550461 30.55956, -90.550463 30.55947, -90.55047 30.559203, -90.550473 30.559114, -90.550472 30.55903, -90.550472 30.55878, -90.550472 30.558697, -90.55047 30.55855, -90.550465 30.558112, -90.550464 30.557966, -90.550433 30.555322, -90.550416 30.554823, -90.550402 30.554507, -90.550394 30.554208, -90.550375 30.5534, -90.550371 30.552364, -90.55037 30.55175, -90.550369 30.551729, -90.550366 30.551669, -90.550366 30.551649, -90.550364 30.55145, -90.550358 30.550857, -90.550356 30.550659, -90.550354 30.550532, -90.55035 30.550152, -90.550349 30.550026, -90.550349 30.549998, -90.55035 30.549665, -90.550357 30.548585, -90.55036 30.548225, -90.550178 30.548223, -90.54983 30.548228, -90.549527 30.548234, -90.54878 30.548231, -90.548359 30.548247, -90.54826 30.548258, -90.548243 30.548263, -90.548225 30.54827, -90.548203 30.54829, -90.548175 30.548346, -90.548165 30.548442, -90.548181 30.548558, -90.548203 30.548589, -90.548233 30.548616, -90.548273 30.548639, -90.548352 30.548642, -90.548201 30.548668, -90.547751 30.548745, -90.547602 30.548772, -90.547574 30.548703, -90.547435 30.548272, -90.547426 30.548133, -90.547421 30.548045, -90.547404 30.547867, -90.547414 30.547729, -90.547391 30.54722, -90.547386 30.547176, -90.548795 30.547219, -90.54911 30.547224, -90.550055 30.547241, -90.55037 30.547247, -90.55037 30.546667, -90.550372 30.544927, -90.550373 30.544348, -90.549898 30.544354, -90.548476 30.544374, -90.548002 30.544381, -90.547997 30.544356, -90.548139 30.544182, -90.548155 30.544159, -90.548335 30.54398, -90.54835 30.543966, -90.548447 30.543892, -90.548556 30.543832, -90.548587 30.543804, -90.548794 30.54374, -90.548923 30.543728, -90.548947 30.543721, -90.549235 30.543667, -90.549259 30.54366, -90.549286 30.543665, -90.549307 30.543665, -90.549413 30.543646, -90.549487 30.543628, -90.549518 30.543605, -90.549542 30.543602, -90.549603 30.543581, -90.549637 30.543579, -90.549682 30.543595, -90.549733 30.543588, -90.549772 30.543574, -90.549834 30.543565, -90.549944 30.543551, -90.550148 30.543493, -90.550185 30.543483, -90.550255 30.543473, -90.550351 30.543445, -90.550444 30.543418, -90.550587 30.543386, -90.550608 30.543372, -90.550629 30.543367, -90.550677 30.54334, -90.550703 30.543335, -90.550735 30.543319, -90.550777 30.543294, -90.550946 30.543146, -90.551052 30.543041, -90.551057 30.543018, -90.551059 30.543, -90.551072 30.542972, -90.551088 30.542954, -90.55112 30.542894, -90.551146 30.542857, -90.551148 30.542807, -90.551156 30.542669, -90.551121 30.542516, -90.551102 30.542458, -90.551094 30.54241, -90.551067 30.542287, -90.551016 30.542138, -90.550933 30.541863, -90.550871 30.54168, -90.550863 30.541641, -90.550839 30.541566, -90.550812 30.541442, -90.550791 30.541414, -90.55075 30.541036, -90.55073 30.540764, -90.550695 30.540551, -90.550678 30.540372, -90.550685 30.540012, -90.550703 30.539923, -90.550714 30.539902, -90.550732 30.539824, -90.550784 30.539691, -90.550787 30.53967, -90.550804 30.539621, -90.550837 30.539535, -90.550886 30.539276, -90.550972 30.539058, -90.551025 30.539003, -90.551035 30.538957, -90.551057 30.538934, -90.551062 30.538911, -90.551099 30.538874, -90.551125 30.538801, -90.551151 30.538773, -90.551162 30.53875, -90.551173 30.538703, -90.551168 30.538625, -90.551267 30.53861, -90.551418 30.538572, -90.551583 30.538511, -90.551681 30.538447, -90.551807 30.538332, -90.551844 30.53828, -90.551871 30.538243, -90.551897 30.538146, -90.551895 30.538084, -90.551882 30.538023, -90.551853 30.537952, -90.551767 30.537813, -90.551558 30.53752, -90.551466 30.537368, -90.551402 30.537238, -90.551038 30.536358, -90.550954 30.536172, -90.550845 30.535927, -90.550645 30.535453, -90.550762 30.535411, -90.551356 30.535236, -90.551588 30.535169, -90.551765 30.535129, -90.551944 30.535095, -90.552124 30.53507, -90.552328 30.535052, -90.552722 30.535037, -90.553031 30.535036, -90.553392 30.535042, -90.553565 30.535039, -90.554309 30.535029, -90.554309 30.534838, -90.55431 30.534562, -90.554298 30.534266, -90.554291 30.534076, -90.554247 30.532655, -90.554217 30.531851, -90.554193 30.531048, -90.554169 30.529691, -90.55415 30.529286, -90.554114 30.528526, -90.554101 30.527891, -90.554091 30.526459, -90.554102 30.526272, -90.553812 30.526279, -90.553293 30.526302, -90.552093 30.526299, -90.550504 30.526309, -90.550405 30.526324, -90.550334 30.526347, -90.550304 30.526363, -90.550281 30.526395, -90.550244 30.526506, -90.55023 30.526586, -90.550236 30.526758, -90.55024 30.527376, -90.550261 30.528143, -90.550268 30.528332, -90.550281 30.528501, -90.550272 30.528729, -90.550288 30.52937, -90.550299 30.530253, -90.550335 30.531152, -90.550354 30.532604, -90.550391 30.533356, -90.550391 30.533378, -90.550388 30.533482, -90.550381 30.533797, -90.55038 30.533903, -90.550154 30.533898, -90.549476 30.533883, -90.54925 30.533878, -90.548403 30.533861, -90.545863 30.533812, -90.545017 30.533796, -90.545035 30.533684, -90.545065 30.533598, -90.545082 30.533528, -90.545101 30.533494, -90.545111 30.533411, -90.545127 30.533374, -90.545129 30.533349, -90.545155 30.533225, -90.545171 30.53312, -90.545199 30.532863, -90.545204 30.532819, -90.545188 30.532746, -90.545166 30.532691, -90.54515 30.532659, -90.545113 30.532614, -90.544885 30.532417, -90.544693 30.532266, -90.544537 30.532126, -90.544497 30.532075, -90.54447 30.532024, -90.544454 30.532006, -90.544427 30.531942, -90.544405 30.531849, -90.544416 30.531667, -90.544444 30.531499, -90.544457 30.531465, -90.54447 30.531403, -90.544504 30.531309, -90.544515 30.531268, -90.544531 30.531254, -90.544536 30.531227, -90.544588 30.531153, -90.544799 30.530939, -90.54495 30.530822, -90.544989 30.530799, -90.545082 30.53073, -90.545111 30.530716, -90.545204 30.530662, -90.545248 30.530638, -90.545539 30.530408, -90.545723 30.530247, -90.545755 30.530205, -90.545765 30.530182, -90.545781 30.530164, -90.545792 30.530141, -90.545807 30.530123, -90.545823 30.530095, -90.545834 30.530026, -90.54586 30.529999, -90.545849 30.529985, -90.545838 30.529907, -90.545819 30.529813, -90.545781 30.529658, -90.545763 30.529568, -90.54572 30.529518, -90.545733 30.529479, -90.545708 30.529138, -90.545678 30.528911, -90.545649 30.528817, -90.545627 30.528762, -90.545598 30.528636, -90.545565 30.528489, -90.545525 30.528359, -90.545502 30.528299, -90.545493 30.528276, -90.545449 30.528115, -90.545437 30.528059, -90.54541 30.527903, -90.545395 30.527635, -90.5454 30.527566, -90.545416 30.527461, -90.545434 30.527367, -90.545463 30.527287, -90.545484 30.527218, -90.545552 30.527126, -90.545666 30.527017, -90.545689 30.526983, -90.545742 30.526928, -90.545773 30.526864, -90.545879 30.526763, -90.545982 30.526675, -90.546011 30.526662, -90.546082 30.526611, -90.546111 30.526597, -90.546343 30.526404, -90.546491 30.526275, -90.546515 30.526251, -90.546776 30.526004, -90.546792 30.525972, -90.546992 30.525767, -90.54705 30.525691, -90.547129 30.525567, -90.5472 30.525466, -90.547229 30.525434, -90.5473 30.525296, -90.547355 30.525168, -90.547371 30.525154, -90.547381 30.525117, -90.547394 30.525102, -90.547439 30.525035, -90.547508 30.524961, -90.5477 30.524809, -90.547748 30.524777, -90.547824 30.524736, -90.547861 30.524713, -90.547919 30.524694, -90.547983 30.524685, -90.548009 30.524676, -90.548769 30.524593, -90.548906 30.52457, -90.548956 30.524553, -90.54906 30.524537, -90.549099 30.524525, -90.549176 30.524514, -90.549268 30.524484, -90.549425 30.524458, -90.549496 30.524433, -90.54967 30.5244, -90.549758 30.524378, -90.549779 30.524374, -90.549799 30.524366, -90.549919 30.524335, -90.549961 30.524312, -90.55009 30.524194, -90.550119 30.52416, -90.550123 30.524142, -90.550138 30.524114, -90.550161 30.523991, -90.55015 30.523936, -90.550124 30.523867, -90.550097 30.523817, -90.550081 30.523798, -90.550038 30.52372, -90.550022 30.523702, -90.549982 30.523601, -90.549972 30.523558, -90.549952 30.523327, -90.549957 30.523212, -90.54997 30.523127, -90.549993 30.523065, -90.550004 30.523015, -90.549993 30.522996, -90.550051 30.522948, -90.550088 30.5229, -90.550106 30.522889, -90.55018 30.522826, -90.550236 30.522796, -90.550307 30.522748, -90.550368 30.522716, -90.550479 30.522617, -90.550526 30.522559, -90.550537 30.522532, -90.550558 30.522509, -90.550563 30.522486, -90.550579 30.522468, -90.550592 30.522392, -90.550631 30.522293, -90.550642 30.522232, -90.550652 30.522156, -90.550638 30.522089, -90.550624 30.521904, -90.550642 30.521778, -90.550634 30.521642, -90.550598 30.521429, -90.550592 30.521369, -90.55056 30.521017, -90.550567 30.520763, -90.550585 30.520673, -90.550606 30.52062, -90.550621 30.520526, -90.550702 30.520322, -90.550729 30.520295, -90.550734 30.520272, -90.550813 30.520171, -90.55085 30.520136, -90.550887 30.520079, -90.550934 30.52003, -90.550971 30.519982, -90.551029 30.519929, -90.551235 30.519752, -90.551353 30.519643, -90.551435 30.519532, -90.551461 30.519504, -90.551464 30.519486, -90.551482 30.519458, -90.551556 30.519256, -90.551563 30.519189, -90.551082 30.519148, -90.550815 30.519127, -90.549581 30.519035, -90.549348 30.519027, -90.548927 30.519019, -90.54857 30.519019, -90.548365 30.519019, -90.547821 30.519009, -90.547496 30.519007, -90.546524 30.519002, -90.5462 30.519001, -90.545941 30.51901, -90.545351 30.519006, -90.545338 30.519006, -90.543418 30.519015, -90.542859 30.519012, -90.542805 30.519012, -90.542227 30.519013, -90.541957 30.519011, -90.541942 30.51786, -90.541915 30.51571, -90.541925 30.515168, -90.541921 30.514407, -90.541916 30.513256, -90.541915 30.51319, -90.541913 30.512994, -90.541913 30.512974, -90.541913 30.512929, -90.541911 30.512884, -90.54191 30.512764, -90.541909 30.512735, -90.5419 30.512468, -90.541901 30.512155, -90.541903 30.511962, -90.541903 30.511778, -90.541907 30.511228, -90.541908 30.511045, -90.541902 30.510816, -90.541899 30.510656, -90.541896 30.510129, -90.541895 30.5099, -90.541793 30.509892, -90.541524 30.509883, -90.541173 30.509883, -90.540969 30.509884, -90.540333 30.509894, -90.539006 30.509887, -90.538869 30.509887, -90.538284 30.509898, -90.537978 30.509903, -90.537332 30.509907, -90.53694 30.509911, -90.534479 30.509911, -90.533528 30.509911, -90.533527 30.509866, -90.533524 30.509733, -90.533524 30.509689, -90.533512 30.509035, -90.533479 30.507075, -90.533469 30.506422, -90.533463 30.506239, -90.533448 30.50569, -90.533444 30.505507, -90.533444 30.505482, -90.533444 30.505407, -90.533444 30.505383, -90.534549 30.505367, -90.537301 30.50533, -90.537864 30.505324, -90.53897 30.505314, -90.540373 30.5053, -90.544582 30.505262, -90.545986 30.505249, -90.546806 30.50524, -90.549267 30.505214, -90.550088 30.505206, -90.550089 30.505047, -90.550094 30.504573, -90.550096 30.504416, -90.549872 30.504423, -90.54939 30.504426, -90.549054 30.504429, -90.548025 30.504432, -90.547271 30.504448, -90.546777 30.50446, -90.546566 30.504468, -90.54641 30.504474, -90.546317 30.504478, -90.546078 30.504495, -90.545943 30.504508, -90.545788 30.504523, -90.544951 30.504546, -90.544774 30.504551, -90.543726 30.504572, -90.54332 30.504577, -90.542566 30.504573, -90.542443 30.504572, -90.542036 30.504569, -90.541607 30.50456, -90.54161 30.503881, -90.541614 30.503227, -90.541686 30.502104, -90.541705 30.501848, -90.541757 30.501172, -90.541794 30.500888, -90.541914 30.500042, -90.541928 30.49995, -90.541963 30.49961, -90.541968 30.49948, -90.541936 30.499211, -90.541839 30.498774, -90.541812 30.498588, -90.541803 30.498425, -90.541801 30.496637, -90.5418 30.495497, -90.5418 30.495397, -90.541802 30.495101, -90.541803 30.495002, -90.5418 30.494683, -90.541792 30.493822, -90.541788 30.493722, -90.541775 30.493408, -90.541755 30.492241, -90.541739 30.491217, -90.541727 30.489945, -90.541722 30.488741, -90.541718 30.487575, -90.541716 30.487284, -90.541713 30.486413, -90.541712 30.486123, -90.5416 30.486121, -90.540878 30.486132, -90.539167 30.486161, -90.538376 30.486168, -90.537817 30.486173, -90.537542 30.486173, -90.537249 30.486173, -90.536373 30.486173, -90.536299 30.486173, -90.536081 30.486171, -90.53606 30.48617, -90.535998 30.48617, -90.535978 30.48617, -90.535742 30.486167, -90.535337 30.486163, -90.535037 30.486169, -90.534856 30.486174, -90.534803 30.486184, -90.534732 30.486184, -90.53452 30.486184, -90.53445 30.486184, -90.535299 30.485432, -90.537575 30.483419, -90.537699 30.483234, -90.537778 30.483117, -90.53826 30.482402, -90.538279 30.482131, -90.537417 30.482132, -90.537299 30.482133, -90.537325 30.481149, -90.53803 30.48116, -90.53803 30.480383, -90.53803 30.479797, -90.538305 30.479813, -90.538306 30.479678, -90.538308 30.479532, -90.538309 30.479395, -90.538353 30.479394, -90.542248 30.47935, -90.542243 30.479105, -90.542248 30.478559, -90.542251 30.478275, -90.542244 30.477509, -90.542232 30.476922, -90.542221 30.476377, -90.542222 30.476273, -90.542227 30.475965, -90.542229 30.475862, -90.542402 30.475858, -90.54292 30.475846, -90.543094 30.475843, -90.54299 30.475724, -90.542946 30.475677, -90.542668 30.475373, -90.542542 30.475265, -90.542482 30.475243, -90.54246 30.475237, -90.542418 30.475226, -90.542333 30.475222, -90.54224 30.475223, -90.542239 30.475046, -90.542239 30.474697, -90.542241 30.474518, -90.542243 30.474342, -90.542733 30.474344, -90.542943 30.474345, -90.543387 30.474343, -90.544135 30.474328, -90.544206 30.474327, -90.544697 30.474322, -90.544784 30.474321, -90.54481 30.474321, -90.545151 30.474322, -90.545265 30.474323, -90.54569 30.474325, -90.545747 30.474325, -90.546142 30.474323, -90.546286 30.47431, -90.546315 30.474299, -90.546363 30.47427, -90.546395 30.474331, -90.546442 30.474567, -90.546469 30.474643, -90.546475 30.474653, -90.546489 30.474674, -90.546517 30.474698, -90.546579 30.474731, -90.546611 30.474741, -90.546664 30.474747, -90.546793 30.474746, -90.54763 30.474706, -90.547618 30.474398, -90.547618 30.474082, -90.548541 30.474087, -90.548747 30.474086, -90.54898 30.474085, -90.549681 30.474083, -90.549852 30.474083, -90.549915 30.47408, -90.549916 30.474017, -90.549898 30.47386, -90.549888 30.473676, -90.549882 30.473304, -90.549891 30.472928, -90.549883 30.472704, -90.549857 30.472612, -90.549707 30.472314, -90.549682 30.472232, -90.549673 30.472173, -90.549682 30.471727, -90.549676 30.471431, -90.549687 30.471048, -90.549694 30.471017, -90.549716 30.470967, -90.549764 30.470915, -90.55028 30.470519, -90.551128 30.469268, -90.551029 30.469203, -90.550934 30.469134, -90.550892 30.469089, -90.550728 30.46891, -90.550719 30.468899, -90.55068 30.468852, -90.550601 30.468769, -90.550482 30.468624, -90.550429 30.468569, -90.550357 30.468482, -90.550226 30.468301, -90.55012 30.468147, -90.550099 30.46809, -90.550033 30.467909, -90.550012 30.467802, -90.550017 30.467767, -90.550044 30.467662, -90.550105 30.46753, -90.550133 30.467462, -90.550176 30.467401, -90.550202 30.467373, -90.550296 30.467256, -90.550394 30.467189, -90.550469 30.467159, -90.550579 30.467108, -90.550612 30.467087, -90.550737 30.466993, -90.550802 30.466949, -90.550862 30.466899, -90.550969 30.466746, -90.550982 30.466711, -90.551 30.466498, -90.550999 30.466462, -90.550989 30.466428, -90.550853 30.466167, -90.550739 30.466018, -90.550643 30.465901, -90.550574 30.465811, -90.550532 30.465749, -90.550491 30.465688, -90.550459 30.465622, -90.550437 30.465553, -90.550422 30.465483, -90.550408 30.46545, -90.550394 30.46538, -90.550417 30.465095, -90.550431 30.46505, -90.550439 30.465027, -90.550538 30.464829, -90.550562 30.4648, -90.550721 30.464634, -90.550867 30.464508, -90.550912 30.464472, -90.550928 30.46446, -90.55098 30.464404, -90.55109 30.464254, -90.55117 30.464129, -90.551234 30.463996, -90.551264 30.463893, -90.551271 30.463858, -90.551292 30.46379, -90.551302 30.463648, -90.551307 30.463613, -90.551313 30.463469, -90.55131 30.463217, -90.551317 30.463182, -90.551351 30.463117, -90.551371 30.463085, -90.551424 30.46303, -90.551458 30.463011, -90.551617 30.46297, -90.551658 30.462963, -90.551781 30.46296, -90.551987 30.462969, -90.552112 30.462984, -90.552276 30.463005, -90.552359 30.463009, -90.552483 30.46301, -90.552566 30.463006, -90.55269 30.463006, -90.552727 30.462991, -90.552854 30.462921, -90.55295 30.462803, -90.552963 30.46277, -90.552979 30.462663, -90.552985 30.462557, -90.552978 30.462521, -90.552974 30.46245, -90.552963 30.462379, -90.552912 30.462169, -90.552891 30.462101, -90.552861 30.462034, -90.552776 30.461759, -90.552752 30.461546, -90.552749 30.461474, -90.552752 30.461403, -90.552749 30.461367, -90.552763 30.46126, -90.552773 30.461226, -90.552792 30.461193, -90.552819 30.461126, -90.552846 30.461099, -90.552894 30.46104, -90.552925 30.461016, -90.553002 30.46099, -90.553119 30.460957, -90.553199 30.460937, -90.553239 30.46093, -90.553484 30.460905, -90.553608 30.460901, -90.553686 30.460874, -90.553689 30.460839, -90.55369 30.460767, -90.553683 30.460731, -90.553638 30.460631, -90.553599 30.460568, -90.553534 30.460476, -90.553356 30.460236, -90.553308 30.460178, -90.553276 30.460111, -90.553253 30.460043, -90.553235 30.459973, -90.553172 30.459765, -90.553109 30.459595, -90.553066 30.459495, -90.552944 30.459266, -90.552846 30.459031, -90.552835 30.458997, -90.552816 30.458782, -90.552822 30.458746, -90.55289 30.458615, -90.55291 30.458583, -90.552937 30.458556, -90.553137 30.458427, -90.553249 30.458321, -90.553274 30.458292, -90.553289 30.458259, -90.553321 30.458155, -90.553326 30.458119, -90.553283 30.458018, -90.553258 30.45799, -90.553158 30.457924, -90.553084 30.457892, -90.553044 30.457884, -90.552515 30.457969, -90.552393 30.457982, -90.55231 30.457985, -90.552186 30.45798, -90.552105 30.457965, -90.55207 30.457945, -90.551946 30.45785, -90.551911 30.45783, -90.551881 30.457805, -90.551834 30.457746, -90.551815 30.457714, -90.551804 30.457679, -90.551821 30.457536, -90.551829 30.457502, -90.551852 30.457471, -90.551937 30.457393, -90.55201 30.457306, -90.552054 30.457246, -90.552185 30.457109, -90.552207 30.457078, -90.552228 30.457009, -90.552232 30.456974, -90.55223 30.456938, -90.552221 30.456903, -90.552159 30.45681, -90.552103 30.456757, -90.552068 30.456738, -90.551918 30.456678, -90.551836 30.456664, -90.551795 30.456661, -90.55167 30.456659, -90.551547 30.456644, -90.551498 30.456635, -90.55134 30.456587, -90.551304 30.456569, -90.551126 30.456418, -90.551101 30.45639, -90.551083 30.456357, -90.551071 30.456323, -90.551059 30.456216, -90.551058 30.45618, -90.551061 30.456144, -90.551071 30.456109, -90.551225 30.45594, -90.551265 30.455877, -90.551396 30.455696, -90.551436 30.455633, -90.551474 30.455569, -90.551521 30.455469, -90.551582 30.455336, -90.551592 30.455301, -90.551599 30.455266, -90.551606 30.455195, -90.551607 30.455161, -90.551614 30.454944, -90.551625 30.454766, -90.551625 30.454658, -90.551618 30.454515, -90.551609 30.454444, -90.551581 30.454122, -90.551561 30.454091, -90.551482 30.454007, -90.551389 30.453937, -90.551352 30.453921, -90.551237 30.453881, -90.551158 30.453861, -90.55104 30.453828, -90.551001 30.453815, -90.550643 30.453725, -90.550483 30.453688, -90.550406 30.453659, -90.550369 30.453643, -90.549992 30.453422, -90.54997 30.453401, -90.54989 30.453404, -90.549809 30.453406, -90.549524 30.453398, -90.549286 30.453424, -90.54908 30.453448, -90.548747 30.453585, -90.548361 30.453983, -90.548184 30.454273, -90.547985 30.454502, -90.547767 30.454598, -90.547716 30.4546, -90.547609 30.454607, -90.547425 30.454512, -90.547371 30.454344, -90.547397 30.454152, -90.547344 30.454169, -90.547293 30.454178, -90.547089 30.454192, -90.546603 30.454187, -90.546303 30.454187, -90.545899 30.454187, -90.5458 30.454201, -90.545725 30.454218, -90.545601 30.4543, -90.545558 30.454356, -90.545515 30.454423, -90.54544 30.454619, -90.54538 30.454697, -90.545328 30.454749, -90.545281 30.454786, -90.545203 30.454826, -90.545095 30.454844, -90.544355 30.454863, -90.543853 30.454866, -90.543322 30.454875, -90.542225 30.454894, -90.542226 30.454519, -90.542229 30.453617, -90.542226 30.453395, -90.542223 30.453021, -90.54245 30.453018, -90.542789 30.453015, -90.543115 30.453008, -90.543133 30.453007, -90.543361 30.453007, -90.543313 30.452699, -90.543116 30.452446, -90.542716 30.452248, -90.542549 30.452235, -90.542239 30.452211, -90.542238 30.452001, -90.542238 30.451645, -90.542231 30.451373, -90.542227 30.451164, -90.541916 30.451168, -90.541417 30.451176, -90.540983 30.451175, -90.540672 30.451175, -90.540657 30.451175, -90.540612 30.451175, -90.540598 30.451175, -90.540106 30.451174, -90.538495 30.451185, -90.537513 30.451186, -90.536224 30.451199, -90.536091 30.4512, -90.535692 30.451203, -90.53556 30.451205, -90.535149 30.45121, -90.534494 30.45122, -90.533919 30.451241, -90.53351 30.451256, -90.533375 30.451263, -90.533045 30.451272, -90.53286 30.451278, -90.532346 30.451301, -90.531703 30.45134, -90.531652 30.451342, -90.531403 30.451352, -90.531188 30.451356, -90.531193 30.451336, -90.531383 30.450924, -90.531637 30.450665, -90.531834 30.450533, -90.53198 30.450429, -90.531967 30.450259, -90.531954 30.450158, -90.531891 30.449659, -90.531676 30.449099, -90.531606 30.448417, -90.531619 30.448049, -90.531695 30.447532, -90.531784 30.447389, -90.531835 30.447114, -90.532134 30.4469, -90.532628 30.446779, -90.53292 30.446801, -90.53318 30.44679, -90.533194 30.44678, -90.533352 30.44668, -90.533694 30.446504, -90.534361 30.446131, -90.534534 30.446055, -90.534684 30.445988, -90.535008 30.445939, -90.5353 30.445966, -90.535471 30.44595, -90.535706 30.445862, -90.536004 30.445829, -90.536141 30.445829, -90.536835 30.445829, -90.537876 30.445813, -90.538383 30.445802, -90.538726 30.445736, -90.539031 30.445538, -90.539462 30.445285, -90.539843 30.444884, -90.539995 30.444703, -90.540211 30.444598, -90.540543 30.444294, -90.540668 30.444181, -90.540992 30.44384, -90.541258 30.443516, -90.541453 30.443002, -90.541473 30.44297, -90.541483 30.442829, -90.541505 30.44255, -90.541506 30.442534, -90.541547 30.442415, -90.541593 30.442282, -90.541734 30.442198, -90.541858 30.442126, -90.542041 30.441968, -90.54211 30.441883, -90.542216 30.441757, -90.542216 30.441857, -90.542216 30.442159, -90.542217 30.44226, -90.54235 30.442246, -90.542941 30.442351, -90.543029 30.442367, -90.543492 30.442472, -90.544209 30.442631, -90.544697 30.442714, -90.545106 30.442755, -90.545395 30.442692, -90.545725 30.442401, -90.546201 30.442098, -90.546317 30.441918, -90.546461 30.441697, -90.546493 30.441219, -90.54636 30.440779, -90.546392 30.440647, -90.546417 30.44057, -90.546538 30.440466, -90.547033 30.440086, -90.547137 30.440004, -90.547635 30.439614, -90.547703 30.439588, -90.547771 30.439562, -90.547719 30.439477, -90.547676 30.439416, -90.547554 30.439256, -90.547289 30.438908, -90.547201 30.438832, -90.547049 30.43871, -90.546786 30.438445, -90.546778 30.438437, -90.546654 30.438342, -90.54653 30.438177, -90.546518 30.438161, -90.546478 30.438098, -90.54642 30.437955, -90.546341 30.437722, -90.546299 30.437583, -90.546261 30.437481, -90.546231 30.437415, -90.546166 30.437255, -90.546095 30.437077, -90.546072 30.437026, -90.54605 30.436977, -90.545937 30.436746, -90.545924 30.436712, -90.545924 30.436675, -90.54594 30.436643, -90.545999 30.436592, -90.546091 30.43652, -90.546128 30.436502, -90.546166 30.436488, -90.546274 30.436435, -90.546344 30.436397, -90.546373 30.436377, -90.54641 30.436353, -90.546545 30.436171, -90.546562 30.436139, -90.546568 30.436103, -90.546579 30.435996, -90.546579 30.435961, -90.546564 30.435929, -90.546512 30.435874, -90.546481 30.43585, -90.546364 30.435747, -90.546341 30.435717, -90.546309 30.435651, -90.546319 30.435622, -90.546371 30.435565, -90.546454 30.435441, -90.546492 30.435377, -90.5465 30.435342, -90.5465 30.435306, -90.546493 30.43527, -90.546478 30.435237, -90.546423 30.435183, -90.54636 30.435137, -90.546324 30.435118, -90.546284 30.435109, -90.546207 30.435084, -90.54617 30.435067, -90.546137 30.435045, -90.546081 30.434992, -90.546004 30.434908, -90.545988 30.434875, -90.545979 30.43484, -90.545977 30.434804, -90.546045 30.434597, -90.54608 30.434532, -90.546127 30.434433, -90.54615 30.434402, -90.546178 30.434376, -90.546206 30.434136, -90.546212 30.43409, -90.546207 30.433839, -90.546219 30.433768, -90.546279 30.433449, -90.546345 30.433221, -90.546379 30.433102, -90.546386 30.433066, -90.546416 30.433, -90.546432 30.432955, -90.546441 30.432932, -90.546457 30.432899, -90.546648 30.432428, -90.546729 30.432264, -90.54677 30.432186, -90.546869 30.432003, -90.546889 30.431934, -90.546962 30.43195, -90.547034 30.431967, -90.547202 30.431998, -90.547281 30.432013, -90.54732 30.43184, -90.54732 30.43174, -90.54732 30.43161, -90.54757 30.43137, -90.54778 30.43115, -90.547812 30.431111, -90.5479 30.43101, -90.54793 30.43087, -90.548176 30.431012, -90.54831 30.43109, -90.548921 30.431431, -90.54917 30.43157, -90.549308 30.431581, -90.549472 30.431595, -90.549696 30.431482, -90.54982 30.43142, -90.550036 30.431516, -90.55036 30.43166, -90.55041 30.43163, -90.55051 30.4315, -90.55058 30.431387, -90.55066 30.43126, -90.55071 30.43119, -90.550799 30.431052, -90.55084 30.43099, -90.55089 30.43088, -90.5509 30.43082, -90.5509 30.43075, -90.55092 30.43065, -90.550957 30.430595, -90.55105 30.43046, -90.55083 30.43035, -90.550802 30.430334, -90.55072 30.43029, -90.55031 30.43002, -90.550097 30.429892, -90.54986 30.42975, -90.549981 30.429591, -90.550348 30.429118, -90.55047 30.42896, -90.55062 30.42901, -90.55076 30.42904, -90.55093 30.42903, -90.550984 30.42902, -90.55117 30.42899, -90.5517 30.42889, -90.552548 30.428787, -90.55269 30.42877, -90.55307 30.42871, -90.553205 30.428687, -90.5535 30.42864, -90.553613 30.428626, -90.55375 30.42861, -90.553913 30.428585, -90.55421 30.42854, -90.554405 30.428523, -90.55457 30.42851, -90.554691 30.4285, -90.555058 30.42847, -90.55518 30.42846, -90.555246 30.428453, -90.555445 30.428433, -90.555512 30.428427, -90.555573 30.428185, -90.555758 30.427461, -90.55582 30.42722, -90.556029 30.426261, -90.556157 30.425681, -90.554532 30.423983, -90.553854 30.423275, -90.553655 30.423419, -90.55316 30.42378, -90.553064 30.423855, -90.552872 30.424008, -90.552597 30.424226, -90.55258 30.42424, -90.551795 30.424909, -90.55163 30.42505, -90.55157 30.4251, -90.551525 30.425133, -90.55134 30.42527, -90.55118 30.42537, -90.551009 30.425455, -90.55094 30.42549, -90.54996 30.42594, -90.549341 30.426206, -90.54931 30.42622, -90.54896 30.42638, -90.548787 30.426461, -90.548711 30.426496, -90.54862 30.42654, -90.548491 30.426616, -90.54842 30.42666, -90.548092 30.42646, -90.54778 30.42627, -90.5471 30.425875, -90.54704 30.42584, -90.546922 30.42577, -90.54677 30.42568, -90.546729 30.425655, -90.54672 30.42565, -90.54665 30.42563, -90.54662 30.42561, -90.546601 30.4256, -90.54656 30.42558, -90.54648 30.42567, -90.546432 30.425731, -90.54607 30.426202, -90.54595 30.42636, -90.54587 30.42647, -90.54583 30.42652, -90.54546 30.42699, -90.54539 30.42708, -90.54534 30.42715, -90.545232 30.427284, -90.544911 30.427688, -90.544804 30.427823, -90.544842 30.427932, -90.544895 30.427962, -90.545356 30.428223, -90.54551 30.42831, -90.545724 30.428444, -90.546365 30.428846, -90.54658 30.42898, -90.54675 30.429087, -90.54704 30.42927, -90.547272 30.429394, -90.54745 30.42949, -90.547633 30.429592, -90.548185 30.429898, -90.54837 30.43, -90.54783 30.43064, -90.54773 30.43075, -90.547519 30.430979, -90.547018 30.431474, -90.54695 30.431541, -90.546882 30.431609, -90.54679 30.431693, -90.546725 30.431754, -90.546713 30.431766, -90.54642 30.432068, -90.545638 30.432826, -90.545459 30.433015, -90.545287 30.43321, -90.545123 30.433407, -90.54507 30.433477, -90.544775 30.433875, -90.544679 30.433998, -90.544556 30.434153, -90.54452 30.434194, -90.544258 30.434501, -90.54408 30.43469, -90.544012 30.434753, -90.543828 30.434925, -90.54375 30.434981, -90.543511 30.435168, -90.543458 30.435211, -90.543153 30.43543, -90.542772 30.435666, -90.542619 30.435748, -90.5425 30.435807, -90.542227 30.435944, -90.542139 30.435975, -90.541751 30.436117, -90.541634 30.436161, -90.541192 30.436307, -90.540792 30.436417, -90.540563 30.436463, -90.540226 30.436533, -90.540159 30.436544, -90.539998 30.436571, -90.539512 30.436661, -90.539492 30.436664, -90.538941 30.436758, -90.537492 30.437019, -90.536826 30.43714, -90.536749 30.437154, -90.536518 30.437196, -90.536441 30.43721, -90.536313 30.436918, -90.536221 30.436735, -90.536204 30.4367, -90.53549 30.435345, -90.535247 30.434882, -90.534991 30.434388, -90.534812 30.434043, -90.534759 30.433951, -90.534738 30.433907, -90.53471 30.433864, -90.534682 30.433807, -90.53439 30.433287, -90.534202 30.432919, -90.534157 30.432831, -90.533942 30.432428, -90.534146 30.432141, -90.534243 30.432006, -90.534531 30.431301, -90.534635 30.431223, -90.534903 30.431023, -90.532321 30.428543, -90.525199 30.430568, -90.525155 30.430769, -90.525066 30.431005, -90.524844 30.431187, -90.524736 30.431494, -90.524666 30.431753, -90.524843 30.43205, -90.524818 30.432231, -90.524691 30.432412, -90.52464 30.432577, -90.524634 30.432781, -90.524678 30.432946, -90.524627 30.433111, -90.524475 30.433275, -90.524412 30.433402, -90.524273 30.433839, -90.524279 30.43408, -90.524284 30.43421, -90.524419 30.434788, -90.524474 30.435024, -90.5244 30.435252, -90.524385 30.435298, -90.524163 30.435568, -90.523865 30.435711, -90.523497 30.435815, -90.523053 30.435787, -90.522717 30.435655, -90.522399 30.435573, -90.522317 30.435573, -90.522 30.435528, -90.52181 30.435451, -90.521581 30.435297, -90.521238 30.435201, -90.521209 30.435012, -90.521178 30.4348, -90.521126 30.434448, -90.521099 30.43426, -90.521049 30.43392, -90.521025 30.43375, -90.520945 30.43321, -90.520902 30.432902, -90.520855 30.432563, -90.520778 30.432062, -90.520704 30.431573, -90.520562 30.430559, -90.520492 30.430058, -90.52046 30.429862, -90.520402 30.4295, -90.520354 30.429277, -90.520314 30.429084, -90.520305 30.42904, -90.520264 30.428804, -90.520192 30.428152, -90.520188 30.428112, -90.520017 30.426865, -90.519911 30.425846, -90.519901 30.425638, -90.51988 30.425561, -90.519859 30.425526, -90.519802 30.425473, -90.519779 30.425415, -90.519778 30.425389, -90.519777 30.425286, -90.519708 30.424738, -90.519667 30.424574, -90.519602 30.42448, -90.519522 30.424419, -90.519443 30.42439, -90.519312 30.424371, -90.518748 30.42437, -90.516605 30.424369, -90.516108 30.424372, -90.515228 30.424378, -90.515049 30.424378, -90.514512 30.424381, -90.514334 30.424383, -90.514281 30.424383, -90.51422 30.424384, -90.514122 30.424383, -90.514069 30.424383, -90.513847 30.424382, -90.513567 30.424368, -90.513446 30.424362, -90.513181 30.424325, -90.512848 30.424222, -90.512133 30.423939, -90.511667 30.423755, -90.511615 30.423734, -90.511005 30.423506, -90.51091 30.423471, -90.510814 30.423429, -90.510616 30.423366, -90.510396 30.423331, -90.510168 30.423317, -90.508916 30.423335, -90.50821 30.423346, -90.50818 30.423346, -90.507887 30.423353, -90.507256 30.42337, -90.506919 30.423374, -90.506597 30.423379, -90.506204 30.423385, -90.505374 30.423409, -90.505041 30.423413, -90.504652 30.423419, -90.504079 30.423421, -90.504035 30.423422, -90.503396 30.42344, -90.502361 30.423458, -90.502037 30.423464, -90.501873 30.423479, -90.501792 30.423497, -90.501741 30.423508, -90.50171 30.423516, -90.501594 30.423554, -90.501557 30.423567, -90.501546 30.423572, -90.501418 30.423627, -90.50129 30.423706, -90.501215 30.423767, -90.501091 30.423895, -90.5008 30.424289, -90.500449 30.424815, -90.500319 30.424968, -90.500179 30.425102, -90.50003 30.425224, -90.499731 30.425423, -90.499615 30.425502, -90.499275 30.425702, -90.498971 30.425902, -90.498755 30.426001, -90.498663 30.426024, -90.498473 30.426048, -90.498381 30.426047, -90.49831 30.42604, -90.498197 30.426029, -90.498064 30.426008, -90.497672 30.425914, -90.497278 30.425833, -90.497118 30.4258, -90.496641 30.425701, -90.496561 30.425685, -90.496484 30.425662, -90.496296 30.425616, -90.496066 30.425561, -90.495888 30.425523, -90.495729 30.425494, -90.495539 30.42546, -90.495283 30.425414, -90.49522 30.425403, -90.495091 30.425384, -90.494885 30.425354, -90.494331 30.425253, -90.494145 30.425236, -90.494068 30.425243, -90.493748 30.425297, -90.493431 30.425351, -90.4933 30.425363, -90.49324 30.425367, -90.493061 30.425357, -90.493004 30.425351, -90.493018 30.42531, -90.492859 30.425095, -90.492799 30.42507, -90.492522 30.424956, -90.49223 30.424971, -90.491722 30.424887, -90.491709 30.424877, -90.491562 30.424763, -90.491533 30.424741, -90.491522 30.42459, -90.491471 30.424636, -90.491342 30.424757, -90.491321 30.424776, -90.491272 30.424823, -90.491284 30.424856, -90.491286 30.424868, -90.491296 30.424928, -90.491289 30.425008, -90.491285 30.425056, -90.491179 30.425019, -90.491113 30.424993, -90.490813 30.424874, -90.490776 30.424844, -90.490717 30.424798, -90.490563 30.424618, -90.490544 30.424567, -90.490537 30.424508, -90.490569 30.424394, -90.490659 30.424201, -90.490751 30.423983, -90.490804 30.423814, -90.490823 30.423711, -90.49083 30.423531, -90.490802 30.423329, -90.490798 30.423299, -90.490765 30.423146, -90.490685 30.422942, -90.490637 30.42289, -90.490606 30.422879, -90.490563 30.422878, -90.490537 30.422877, -90.490252 30.422939, -90.489854 30.423027, -90.489754 30.423044, -90.489318 30.423123, -90.489292 30.423128, -90.489008 30.423187, -90.488836 30.423216, -90.488474 30.423304, -90.488437 30.423314, -90.48801 30.42344, -90.486909 30.423798, -90.486783 30.42384, -90.486389 30.423965, -90.486147 30.424042, -90.485764 30.424165, -90.485419 30.424257, -90.485175 30.424323, -90.485092 30.424345, -90.484907 30.424377, -90.484728 30.424393, -90.484149 30.424412, -90.484095 30.424414, -90.483582 30.424442, -90.482684 30.424458, -90.482333 30.424474, -90.482138 30.424495, -90.481492 30.424581, -90.481364 30.424602, -90.481071 30.424671, -90.480621 30.424779, -90.480175 30.424871, -90.480066 30.424904, -90.479742 30.424968, -90.479532 30.425015, -90.479408 30.425043, -90.479243 30.425087, -90.478861 30.425205, -90.478646 30.425284, -90.478434 30.425382, -90.478023 30.425601, -90.477525 30.425846, -90.477321 30.425947, -90.477211 30.426004, -90.476278 30.426493, -90.476075 30.4266, -90.475969 30.42666, -90.475883 30.426704, -90.475627 30.426838, -90.475542 30.426883, -90.475497 30.426782, -90.47537 30.426402, -90.475322 30.426248, -90.475251 30.42602, -90.47496 30.424992, -90.474949 30.424967, -90.474928 30.424951, -90.474902 30.42494, -90.474871 30.424937, -90.474807 30.42494, -90.474661 30.424963, -90.474487 30.425002, -90.4744 30.425016, -90.474365 30.425018, -90.474331 30.425015, -90.474287 30.424997, -90.474246 30.424965, -90.474204 30.424907, -90.474184 30.424896, -90.474146 30.424884, -90.473854 30.424892, -90.473759 30.424899, -90.4737 30.424909, -90.473666 30.424924, -90.472648 30.425726, -90.472769 30.425792, -90.473003 30.425918, -90.47327 30.426012, -90.47353 30.426083, -90.473777 30.426116, -90.474114 30.426072, -90.474195 30.426072, -90.474418 30.426072, -90.474589 30.426121, -90.474742 30.426204, -90.475313 30.426742, -90.475389 30.426852, -90.475408 30.42695, -90.475129 30.427082, -90.474833 30.427233, -90.474181 30.427569, -90.473272 30.428022, -90.473119 30.428104, -90.472709 30.428328, -90.472556 30.428409, -90.47227 30.42856, -90.472158 30.42862, -90.47141 30.429005, -90.471124 30.429153, -90.469991 30.429736, -90.468623 30.430441, -90.468275 30.430621, -90.468228 30.430641, -90.468185 30.430667, -90.466595 30.43149, -90.465464 30.432076, -90.465438 30.432093, -90.465082 30.432276, -90.464798 30.432423, -90.464179 30.432726, -90.463922 30.432846, -90.463797 30.432906, -90.463531 30.433026, -90.463734 30.437161, -90.464024 30.437754, -90.464111 30.437824, -90.465283 30.438546, -90.464113 30.438472, -90.463262 30.438418, -90.46326 30.438269, -90.463243 30.438182, -90.463229 30.43815, -90.463212 30.438132, -90.463111 30.43808, -90.463078 30.438073, -90.462938 30.438064, -90.462871 30.438053, -90.46279 30.438026, -90.462715 30.437992, -90.462657 30.437946, -90.46261 30.437891, -90.462591 30.437866, -90.462523 30.437773, -90.46243 30.437664, -90.462331 30.437561, -90.462218 30.437469, -90.462095 30.437388, -90.462008 30.43734, -90.461858 30.437275, -90.461795 30.437253, -90.461667 30.437219, -90.461537 30.437192, -90.4614 30.437171, -90.461263 30.437159, -90.46119 30.437157, -90.460973 30.437168, -90.460829 30.437187, -90.460545 30.437251, -90.460445 30.437265, -90.460344 30.43727, -90.460289 30.437268, -90.460237 30.437259, -90.460197 30.437236, -90.460162 30.437203, -90.460102 30.437134, -90.460025 30.437009, -90.459991 30.43696, -90.459775 30.436646, -90.459416 30.436146, -90.458927 30.435453, -90.457463 30.433377, -90.456975 30.432686, -90.45693 30.432706, -90.456794 30.432766, -90.45675 30.432787, -90.456663 30.432825, -90.45655 30.432877, -90.456404 30.432942, -90.456318 30.432981, -90.456124 30.432706, -90.455545 30.431884, -90.455394 30.431669, -90.455352 30.43161, -90.455181 30.431369, -90.45467 30.430648, -90.454596 30.430544, -90.454494 30.430413, -90.454465 30.430377, -90.45444 30.430346, -90.454405 30.430293, -90.454386 30.430263, -90.454361 30.430225, -90.454348 30.430205, -90.45431 30.430146, -90.454298 30.430127, -90.45402 30.429734, -90.453189 30.428555, -90.452912 30.428163, -90.452617 30.427742, -90.452132 30.427039, -90.45093 30.425329, -90.450516 30.42474, -90.45031 30.424447, -90.449675 30.42355, -90.449579 30.423425, -90.449309 30.423095, -90.449232 30.423011, -90.449043 30.422805, -90.448988 30.422756, -90.448847 30.422645, -90.448792 30.422619, -90.44874 30.422608, -90.448668 30.422607, -90.448629 30.422645, -90.448584 30.422732, -90.448573 30.422772, -90.448568 30.422814, -90.448564 30.42296, -90.44857 30.423022, -90.448595 30.423266, -90.448596 30.423401, -90.448602 30.423941, -90.448611 30.424356, -90.448618 30.42461, -90.448634 30.424801, -90.448347 30.424806, -90.447951 30.424806, -90.445905 30.424806, -90.445259 30.424806, -90.445223 30.424805, -90.4451 30.4248, -90.44505 30.424799, -90.444885 30.424798, -90.444731 30.424796, -90.444609 30.424796, -90.444557 30.424796, -90.444487 30.424797, -90.444122 30.424805, -90.444001 30.424808, -90.44392 30.42481, -90.443636 30.424811, -90.442543 30.424816, -90.442179 30.424819, -90.442179 30.4249, -90.442182 30.425146, -90.442183 30.425228, -90.442183 30.425254, -90.442184 30.425317, -90.442184 30.425332, -90.442185 30.425359, -90.442192 30.425559, -90.442192 30.425709, -90.442199 30.42676, -90.442202 30.427111, -90.442177 30.427245, -90.442098 30.427292, -90.442082 30.427303, -90.442019 30.427311, -90.441939 30.427322, -90.441086 30.4273, -90.440776 30.427292, -90.440755 30.427291, -90.44026 30.427305, -90.439543 30.427327, -90.439232 30.427365, -90.438824 30.427437, -90.438725 30.427455, -90.438219 30.427552, -90.438122 30.427227, -90.437862 30.426347, -90.437835 30.426254, -90.437743 30.425929, -90.437689 30.425757, -90.43753 30.425242, -90.437512 30.425183, -90.43748 30.42507, -90.437467 30.425023, -90.437428 30.424885, -90.437416 30.424839, -90.437363 30.424682, -90.437315 30.42452, -90.437035 30.423557, -90.436954 30.423276, -90.436942 30.423237, -90.436831 30.422884, -90.436499 30.421827, -90.436389 30.421475, -90.436361 30.421385, -90.436277 30.421116, -90.436249 30.421027, -90.436185 30.421025, -90.435995 30.421021, -90.435932 30.42102, -90.434836 30.421011, -90.434298 30.421515, -90.432578 30.423128, -90.431491 30.423447, -90.431371 30.42426, -90.430286 30.425276, -90.42895 30.426531, -90.428918 30.426562, -90.428826 30.426655, -90.428796 30.426687, -90.42874 30.426603, -90.428696 30.426512, -90.428671 30.426434, -90.428665 30.426413, -90.42864 30.426306, -90.428621 30.426245, -90.428486 30.425884, -90.428385 30.425633, -90.428343 30.425526, -90.428276 30.425372, -90.428211 30.425221, -90.428016 30.42477, -90.427952 30.42462, -90.427861 30.424408, -90.427785 30.424241, -90.427701 30.424071, -90.427557 30.423779, -90.426984 30.422689, -90.426888 30.422522, -90.426855 30.42247, -90.426743 30.422289, -90.426525 30.421966, -90.426164 30.421945, -90.426077 30.421941, -90.42532 30.421907, -90.425083 30.421897, -90.424723 30.421883, -90.424605 30.421878, -90.424335 30.421867, -90.424095 30.421857, -90.423933 30.42185, -90.423172 30.421822, -90.422785 30.421807, -90.422766 30.421806, -90.422708 30.421803, -90.42269 30.421803, -90.422492 30.421795, -90.421923 30.421773, -90.421899 30.42177, -90.421703 30.421753, -90.421706 30.421618, -90.421718 30.421398, -90.421724 30.421043, -90.421716 30.420689, -90.421715 30.420638, -90.421733 30.4202, -90.421732 30.419823, -90.42175 30.419373, -90.421756 30.418901, -90.421751 30.418841, -90.421738 30.418783, -90.421699 30.41871, -90.421679 30.418654, -90.421666 30.418596, -90.421667 30.418528, -90.421669 30.418459, -90.421663 30.418415, -90.421643 30.418338, -90.421622 30.418316, -90.421594 30.418303, -90.421562 30.418304, -90.421505 30.418323, -90.421483 30.418336, -90.421449 30.418371, -90.421389 30.41845, -90.421305 30.418549, -90.421121 30.418786, -90.42108 30.418834, -90.421025 30.4189, -90.420988 30.418962, -90.420974 30.419001, -90.420949 30.419124, -90.420926 30.419189, -90.420911 30.419255, -90.420926 30.419385, -90.420922 30.419421, -90.420909 30.41944, -90.420849 30.419492, -90.420824 30.419543, -90.420789 30.419648, -90.420777 30.4197, -90.420771 30.419812, -90.420767 30.420198, -90.420757 30.421357, -90.420755 30.421744, -90.42066 30.421744, -90.420379 30.421746, -90.420285 30.421747, -90.419755 30.42175, -90.419123 30.421755, -90.418168 30.421764, -90.417639 30.42177, -90.416891 30.42178, -90.416846 30.42178, -90.416223 30.421783, -90.415956 30.421773, -90.415736 30.42174, -90.41547 30.421641, -90.414893 30.421344, -90.414605 30.421175, -90.414538 30.421136, -90.414097 30.420944, -90.413958 30.420895, -90.413877 30.420867, -90.41365 30.421191, -90.41356 30.42131, -90.41335 30.421618, -90.4133 30.421674, -90.413281 30.421706, -90.413232 30.421763, -90.413125 30.421915, -90.413033 30.422076, -90.412981 30.422174, -90.412941 30.422275, -90.412919 30.422343, -90.412911 30.422414, -90.412909 30.422521, -90.412914 30.422628, -90.412934 30.42284, -90.412945 30.422911, -90.412969 30.423121, -90.412982 30.423239, -90.413014 30.423514, -90.413022 30.423656, -90.413026 30.423834, -90.413013 30.423941, -90.412988 30.424083, -90.412976 30.424117, -90.412932 30.424217, -90.412914 30.42425, -90.412868 30.424349, -90.412804 30.424441, -90.412754 30.424498, -90.412665 30.424611, -90.412617 30.424674, -90.412498 30.424819, -90.412454 30.424879, -90.412384 30.424967, -90.412266 30.425113, -90.412216 30.425169, -90.412124 30.425288, -90.41205 30.425374, -90.411898 30.425543, -90.411818 30.425625, -90.411789 30.42565, -90.411664 30.425745, -90.411568 30.425813, -90.411458 30.425862, -90.411307 30.425923, -90.411191 30.425958, -90.4104 30.426155, -90.41032 30.42617, -90.409529 30.426373, -90.409451 30.426396, -90.408663 30.4266, -90.408583 30.426617, -90.408346 30.426679, -90.408001 30.426805, -90.407738 30.426911, -90.407634 30.42697, -90.407366 30.427135, -90.407333 30.427157, -90.407044 30.42736, -90.406854 30.427498, -90.40651 30.427758, -90.40592 30.428213, -90.405859 30.428262, -90.405801 30.428313, -90.405769 30.428333, -90.405696 30.428396, -90.405385 30.428715, -90.405379 30.428724, -90.405315 30.428704, -90.405244 30.428689, -90.405116 30.428662, -90.404997 30.428645, -90.404897 30.428639, -90.404829 30.428639, -90.404796 30.428639, -90.404693 30.428664, -90.404318 30.428813, -90.404257 30.428847, -90.404163 30.42891, -90.404004 30.42901, -90.403813 30.429122, -90.40368 30.429189, -90.403535 30.429251, -90.40346 30.429274, -90.403447 30.429277, -90.403398 30.429285, -90.403191 30.429298, -90.403016 30.4293, -90.401856 30.429251, -90.401765 30.429239, -90.401493 30.429203, -90.401403 30.429192, -90.401311 30.42918, -90.401069 30.429145, -90.400759 30.429102, -90.400286 30.429054, -90.400066 30.429026, -90.399733 30.428984, -90.399633 30.428971, -90.399468 30.42895, -90.399334 30.428935, -90.399235 30.428924, -90.399149 30.428914, -90.398893 30.428886, -90.398808 30.428877, -90.398499 30.428842, -90.398264 30.428816, -90.397797 30.428774, -90.397571 30.428758, -90.39733 30.428742, -90.397262 30.42874, -90.397263 30.429235, -90.397268 30.430724, -90.39727 30.43122, -90.39727 30.431252, -90.39727 30.431303, -90.39727 30.431554, -90.39727 30.431638, -90.397269 30.43175, -90.397269 30.432085, -90.397269 30.432198, -90.39579 30.432034, -90.395601 30.432013, -90.395538 30.432, -90.394985 30.431888, -90.394808 30.431852, -90.394599 30.43187, -90.394333 30.431893, -90.394015 30.431921, -90.392348 30.432376, -90.391687 30.432353, -90.391476 30.432307, -90.391417 30.432297, -90.38995 30.432054, -90.389254 30.431947, -90.388737 30.431951, -90.387404 30.431964, -90.387281 30.432003, -90.38676 30.432166, -90.386583 30.432224, -90.385484 30.432798, -90.385419 30.432833, -90.385276 30.432892, -90.384229 30.433327, -90.384148 30.433359, -90.383925 30.433442, -90.383301 30.433677, -90.383257 30.433688, -90.383029 30.433751, -90.382537 30.433889, -90.382421 30.433921, -90.38238 30.433933, -90.382339 30.43396, -90.381478 30.43452, -90.381261 30.434567, -90.381069 30.434609, -90.380879 30.434585, -90.38072 30.434539, -90.380114 30.434364, -90.380114 30.43439, -90.380114 30.434406, -90.38011 30.434621, -90.380098 30.435394, -90.380095 30.435652, -90.380094 30.435746, -90.380093 30.435793, -90.380089 30.436216, -90.380088 30.436358, -90.38008 30.43713, -90.380066 30.438691, -90.380061 30.439446, -90.380057 30.440219, -90.380045 30.44043, -90.380044 30.440471, -90.380038 30.44123, -90.380037 30.441484, -90.380036 30.441558, -90.380032 30.441951, -90.380021 30.443353, -90.380018 30.443821, -90.380017 30.44395, -90.380016 30.444142, -90.380015 30.445107, -90.380015 30.445137, -90.380012 30.445429, -90.380011 30.445542, -90.380007 30.445884, -90.380007 30.445999, -90.380006 30.44606, -90.380004 30.446246, -90.380004 30.446308, -90.380004 30.446489, -90.380006 30.447006, -90.380004 30.447032, -90.379997 30.447213, -90.379996 30.447304, -90.379995 30.447579, -90.379995 30.447671, -90.380086 30.447672, -90.380158 30.447674, -90.380232 30.447683, -90.380278 30.447696, -90.380334 30.447718, -90.380354 30.447726, -90.380414 30.447751, -90.38044 30.447758, -90.380438 30.447955, -90.380431 30.448549, -90.38043 30.448748, -90.38043 30.450589, -90.38031 30.450589, -90.380225 30.450589, -90.380017 30.450581, -90.380038 30.452252, -90.380043 30.45366, -90.380044 30.454269, -90.380108 30.454275, -90.380084 30.45526, -90.380047 30.456787, -90.380066 30.45748, -90.380066 30.457545, -90.380069 30.459315, -90.380076 30.459775, -90.380097 30.461157, -90.380103 30.461546, -90.380105 30.461618, -90.380108 30.461771, -90.380118 30.462231, -90.380122 30.462385, -90.380126 30.46259, -90.380128 30.462696, -90.38014 30.463208, -90.380145 30.463414, -90.380152 30.463733, -90.380159 30.464041, -90.380179 30.46463, -90.380181 30.464692, -90.380193 30.465012, -90.380193 30.465024, -90.380194 30.465062, -90.380195 30.465075, -90.380202 30.46529, -90.380216 30.465682, -90.380226 30.465936, -90.380236 30.466152, -90.380249 30.46647, -90.380254 30.466649, -90.380279 30.46737, -90.380298 30.468141, -90.380302 30.468269, -90.380309 30.468639, -90.380319 30.469185, -90.380331 30.469807, -90.380364 30.470825, -90.380383 30.471372, -90.380381 30.471477, -90.380383 30.471545, -90.380402 30.472067, -90.380407 30.472198, -90.380409 30.472241, -90.380425 30.473033, -90.380436 30.473329, -90.38046 30.473913, -90.380492 30.474626, -90.380493 30.474658, -90.3805 30.474982, -90.380499 30.475338, -90.380495 30.475478, -90.380506 30.475611, -90.380531 30.475721, -90.380542 30.475754, -90.380559 30.475784, -90.380585 30.475816, -90.380617 30.475843, -90.380677 30.475867, -90.380742 30.475882, -90.380869 30.475902, -90.381328 30.475924, -90.381447 30.47593, -90.381767 30.475937, -90.382101 30.475935, -90.382417 30.475929, -90.382736 30.475923, -90.383065 30.475916, -90.384294 30.475894, -90.384917 30.475894, -90.385013 30.475894, -90.385541 30.4759, -90.385662 30.475893, -90.385664 30.475848, -90.385667 30.475748, -90.385682 30.475313, -90.385684 30.475258, -90.385684 30.475169, -90.385924 30.475291, -90.38636 30.475513, -90.386639 30.475673, -90.386773 30.47575, -90.38685 30.475838, -90.387368 30.475822, -90.387944 30.475812, -90.388487 30.475803, -90.389998 30.475794, -90.390699 30.47579, -90.390758 30.47579, -90.390861 30.47579, -90.390954 30.47579, -90.391228 30.475789, -90.392121 30.475785, -90.392323 30.475803, -90.392041 30.476177, -90.391831 30.476458, -90.391233 30.477329, -90.390968 30.477716, -90.390896 30.477816, -90.390783 30.477952, -90.390741 30.477993, -90.390658 30.478079, -90.390569 30.478161, -90.390475 30.478238, -90.390373 30.478306, -90.39027 30.478358, -90.390161 30.478402, -90.390049 30.47844, -90.389904 30.47848, -90.389816 30.478495, -90.389756 30.478507, -90.389644 30.478513, -90.389459 30.478515, -90.389459 30.478575, -90.389462 30.478703, -90.389473 30.478754, -90.389487 30.478813, -90.389679 30.478813, -90.390129 30.478819, -90.391087 30.478818, -90.39589 30.478817, -90.397492 30.478817, -90.397609 30.478817, -90.397961 30.478817, -90.398079 30.478817, -90.398095 30.478782, -90.39822 30.478256, -90.398232 30.478196, -90.398455 30.477178, -90.398563 30.476788, -90.39869 30.476497, -90.39878 30.47637, -90.398887 30.476222, -90.399198 30.475887, -90.399605 30.47559, -90.39996 30.475332, -90.400201 30.475145, -90.400366 30.474997, -90.400474 30.474821, -90.400582 30.474606, -90.40069 30.474321, -90.400703 30.474264, -90.400824 30.473766, -90.400875 30.473612, -90.400875 30.473485, -90.40085 30.473309, -90.400793 30.473139, -90.400685 30.472908, -90.400533 30.472617, -90.400463 30.472413, -90.400482 30.472232, -90.400533 30.471957, -90.400692 30.471528, -90.400927 30.470973, -90.401035 30.470759, -90.401066 30.470583, -90.401092 30.470116, -90.401118 30.469693, -90.401162 30.469319, -90.401219 30.469148, -90.401417 30.468181, -90.401499 30.467873, -90.401572 30.467702, -90.401582 30.467681, -90.401715 30.467555, -90.401817 30.467461, -90.401969 30.46739, -90.402293 30.467329, -90.402762 30.467252, -90.403003 30.46722, -90.403232 30.467176, -90.403587 30.467033, -90.403682 30.466994, -90.403944 30.466885, -90.404038 30.466846, -90.404451 30.466615, -90.404635 30.466495, -90.404677 30.466457, -90.404793 30.466357, -90.404895 30.466275, -90.404927 30.466357, -90.405034 30.466495, -90.405028 30.466621, -90.405002 30.466791, -90.404964 30.466885, -90.405021 30.466962, -90.405129 30.466978, -90.405504 30.466863, -90.405631 30.466863, -90.405751 30.466946, -90.40577 30.467023, -90.405764 30.467193, -90.405776 30.46738, -90.405865 30.467523, -90.405986 30.467693, -90.406195 30.467809, -90.406423 30.467902, -90.406728 30.46799, -90.406975 30.46804, -90.40735 30.468111, -90.407597 30.468194, -90.407794 30.468337, -90.407862 30.468424, -90.41244 30.468385, -90.412778 30.468382, -90.413793 30.468375, -90.414058 30.468373, -90.414132 30.468373, -90.414132 30.468245, -90.414132 30.467861, -90.414133 30.467734, -90.414581 30.467745, -90.415926 30.467779, -90.416375 30.467791, -90.421229 30.467796, -90.421245 30.46974, -90.422433 30.46974, -90.422429 30.474744, -90.422312 30.474761, -90.42202 30.474821, -90.421753 30.474865, -90.421517 30.474909, -90.421271 30.474997, -90.418783 30.475947, -90.417659 30.476392, -90.417145 30.476568, -90.416714 30.476716, -90.416447 30.476815, -90.41618 30.47687, -90.415831 30.476854, -90.415419 30.476809, -90.414987 30.476694, -90.414391 30.476507, -90.414293 30.476483, -90.4143 30.476601, -90.414321 30.476956, -90.414329 30.477075, -90.414329 30.477092, -90.41433 30.477143, -90.414331 30.47716, -90.414334 30.477335, -90.414345 30.47786, -90.414347 30.477925, -90.414353 30.478036, -90.414478 30.478034, -90.414751 30.478057, -90.414971 30.478092, -90.415044 30.478108, -90.415402 30.478192, -90.415778 30.4783, -90.416148 30.478422, -90.416521 30.478539, -90.416945 30.478655, -90.417052 30.478682, -90.417373 30.478765, -90.41773 30.478847, -90.417776 30.478845, -90.417914 30.478841, -90.417961 30.47884, -90.418536 30.478824, -90.419071 30.478829, -90.42002 30.478838, -90.420176 30.478837, -90.426825 30.478836, -90.429012 30.478836, -90.429042 30.478836, -90.429051 30.4789, -90.429078 30.479093, -90.429087 30.479159, -90.429083 30.479206, -90.429073 30.479347, -90.429071 30.479395, -90.429262 30.479396, -90.429837 30.479401, -90.430029 30.479403, -90.429923 30.47966, -90.429802 30.479956, -90.429707 30.480096, -90.429489 30.480116, -90.429367 30.480157, -90.429242 30.480201, -90.429109 30.48026, -90.42913 30.480753, -90.429151 30.481341, -90.429159 30.481565, -90.429168 30.48235, -90.429158 30.482707, -90.429127 30.482803, -90.428918 30.483027, -90.428676 30.48331, -90.428203 30.483751, -90.427943 30.483943, -90.427827 30.484008, -90.427631 30.484121, -90.427299 30.484288, -90.427244 30.484341, -90.427228 30.484424, -90.427224 30.484671, -90.427219 30.484747, -90.426774 30.484747, -90.426395 30.484747, -90.425442 30.484765, -90.424999 30.484774, -90.425027 30.485477, -90.425058 30.486509, -90.425061 30.486597, -90.425142 30.488844, -90.425168 30.489695, -90.425172 30.490791, -90.425152 30.49154, -90.425168 30.492069, -90.425189 30.492727, -90.425203 30.493894, -90.424803 30.493894, -90.424018 30.493894, -90.423605 30.493907, -90.423587 30.493908, -90.423206 30.493909, -90.422855 30.49391, -90.422014 30.493915, -90.421805 30.493916, -90.421456 30.49392, -90.421394 30.49392, -90.421311 30.493922, -90.421208 30.493922, -90.421146 30.493923, -90.420873 30.493925, -90.420574 30.493929, -90.420057 30.493932, -90.419785 30.493934, -90.418975 30.493938, -90.418726 30.49394, -90.417703 30.493939, -90.416547 30.493956, -90.415738 30.493954, -90.415602 30.493953, -90.415324 30.493953, -90.415197 30.493949, -90.415062 30.493946, -90.414787 30.493949, -90.413962 30.493959, -90.413688 30.493963, -90.413586 30.493964, -90.413122 30.493967, -90.411998 30.493977, -90.411425 30.493979, -90.41086 30.493981, -90.410601 30.493983, -90.409828 30.493991, -90.40957 30.493994, -90.408973 30.494, -90.408838 30.494002, -90.407182 30.494013, -90.406586 30.494018, -90.406095 30.49402, -90.405037 30.494025, -90.405044 30.494116, -90.405044 30.49423, -90.405205 30.49423, -90.405499 30.494232, -90.405791 30.49477, -90.40609 30.495298, -90.406428 30.495895, -90.406476 30.49598, -90.407567 30.497926, -90.407674 30.498223, -90.407834 30.498512, -90.408036 30.498877, -90.408252 30.499196, -90.408493 30.499537, -90.408816 30.499996, -90.409292 30.500507, -90.409495 30.500689, -90.409685 30.500903, -90.409939 30.501255, -90.40999 30.501324, -90.41002 30.501361, -90.410046 30.5014, -90.410077 30.501442, -90.410254 30.501685, -90.410314 30.501766, -90.410453 30.501921, -90.410561 30.502041, -90.410783 30.502366, -90.410818 30.502427, -90.410924 30.502607, -90.410925 30.503056, -90.410929 30.503971, -90.410928 30.504406, -90.410928 30.504856, -90.41093 30.505072, -90.410937 30.50572, -90.41094 30.505936, -90.41094 30.505999, -90.410943 30.506192, -90.410944 30.506256, -90.41112 30.506264, -90.411234 30.506264, -90.412106 30.506268, -90.412397 30.50627, -90.412458 30.506956, -90.412468 30.507133, -90.412483 30.507374, -90.412493 30.507706, -90.41314 30.507712, -90.414875 30.507718, -90.415206 30.507724, -90.415191 30.50899, -90.415184 30.509638, -90.415168 30.511104, -90.415169 30.511258, -90.415612 30.51126, -90.416486 30.511266, -90.416494 30.511724, -90.416502 30.512168, -90.417645 30.512165, -90.41868 30.512164, -90.420531 30.51215, -90.421063 30.512125, -90.421075 30.512124, -90.421237 30.512121, -90.42154 30.512092, -90.422209 30.511987, -90.422247 30.511981, -90.422416 30.511947, -90.422976 30.511837, -90.423039 30.51182, -90.423244 30.511768, -90.4233 30.511754, -90.423471 30.511713, -90.423528 30.5117, -90.423542 30.511687, -90.423589 30.511684, -90.423772 30.511635, -90.423834 30.511619, -90.424542 30.511432, -90.42472 30.511386, -90.425213 30.511251, -90.426665 30.510858, -90.427064 30.510751, -90.427372 30.510663, -90.42759 30.510605, -90.428248 30.510433, -90.428467 30.510376, -90.428766 30.510298, -90.428867 30.51027, -90.430067 30.509947, -90.430449 30.509844, -90.430467 30.509839, -90.430672 30.509785, -90.431287 30.509626, -90.431493 30.509574, -90.431515 30.509712, -90.431547 30.51008, -90.431548 30.510137, -90.431553 30.51041, -90.43155 30.511838, -90.43155 30.512246, -90.43155 30.512405, -90.43155 30.512534, -90.431549 30.512817, -90.431547 30.513258, -90.431538 30.515817, -90.431538 30.51604, -90.431532 30.516671, -90.43153 30.516999, -90.431532 30.517175, -90.431545 30.518197, -90.431544 30.518362, -90.431544 30.518688, -90.431544 30.518817, -90.43153 30.519076, -90.431549 30.519105, -90.431626 30.519077, -90.431615 30.521701, -90.431583 30.529574, -90.431573 30.532199, -90.431572 30.53257, -90.43157 30.533685, -90.43157 30.534057, -90.431825 30.534048, -90.431956 30.534049, -90.432593 30.534066, -90.43285 30.534073, -90.432886 30.533725, -90.432923 30.533373, -90.433 30.532682, -90.433007 30.532627, -90.433063 30.532339, -90.433148 30.531897, -90.433212 30.531572, -90.433469 30.530585, -90.433583 30.53015, -90.433609 30.530051, -90.433689 30.529755, -90.433716 30.529657, -90.433873 30.529062, -90.434315 30.5274, -90.434347 30.527279, -90.434506 30.526685, -90.434605 30.526312, -90.434903 30.525194, -90.435003 30.524822, -90.435261 30.523825, -90.435383 30.523357, -90.435756 30.521983, -90.436048 30.520838, -90.436061 30.520789, -90.436313 30.519844, -90.436429 30.519406, -90.436779 30.518092, -90.436802 30.518007, -90.436924 30.517664, -90.437249 30.517166, -90.437316 30.517068, -90.437531 30.516759, -90.437993 30.516177, -90.438141 30.516012, -90.43839 30.515795, -90.438752 30.515498, -90.439305 30.515047, -90.439509 30.514889, -90.439778 30.514683, -90.440127 30.51442, -90.440334 30.514265, -90.440405 30.514204, -90.440622 30.514024, -90.440694 30.513964, -90.440997 30.513727, -90.441706 30.513176, -90.441905 30.513018, -90.442207 30.51278, -90.442282 30.512719, -90.44251 30.51254, -90.442586 30.51248, -90.445186 30.511885, -90.445165 30.511999, -90.445168 30.512868, -90.445169 30.513154, -90.445171 30.513615, -90.445168 30.514012, -90.445166 30.514299, -90.445165 30.514363, -90.445108 30.516619, -90.445102 30.516858, -90.445057 30.51782, -90.445038 30.518018, -90.445019 30.518194, -90.444949 30.518381, -90.444867 30.518683, -90.444765 30.519029, -90.444746 30.519189, -90.444676 30.519469, -90.444524 30.519975, -90.444454 30.520255, -90.444276 30.520651, -90.443991 30.521261, -90.443864 30.521525, -90.443673 30.521921, -90.443603 30.522075, -90.443451 30.522383, -90.443311 30.522718, -90.443134 30.523042, -90.443084 30.523194, -90.443064 30.523257, -90.443096 30.523614, -90.443127 30.524043, -90.443197 30.524427, -90.44328 30.524829, -90.443343 30.525246, -90.443358 30.52549, -90.443362 30.525549, -90.443367 30.525702, -90.443375 30.525925, -90.443356 30.52617, -90.443342 30.526337, -90.443325 30.526549, -90.444218 30.526563, -90.4469 30.526606, -90.447794 30.526621, -90.447918 30.526623, -90.448293 30.526632, -90.448418 30.526635, -90.448419 30.526678, -90.448414 30.526737, -90.448402 30.526794, -90.448384 30.526844, -90.448369 30.526874, -90.44836 30.526893, -90.448275 30.52702, -90.448184 30.527115, -90.44809 30.527241, -90.448055 30.527303, -90.448018 30.5274, -90.448002 30.527504, -90.447987 30.527715, -90.447987 30.527752, -90.447989 30.527998, -90.447978 30.528591, -90.447978 30.529033, -90.447978 30.529604, -90.447982 30.529629, -90.448 30.529655, -90.448054 30.529696, -90.448088 30.529711, -90.448204 30.529728, -90.448278 30.529743, -90.448343 30.529767, -90.448362 30.529784, -90.448389 30.529827, -90.448391 30.52985, -90.44837 30.530102, -90.44836 30.530164, -90.448345 30.530206, -90.448288 30.530246, -90.448001 30.530374, -90.447941 30.530424, -90.44795 30.530642, -90.448008 30.531086, -90.448056 30.531287, -90.448047 30.531624, -90.448128 30.531985, -90.448198 30.532237, -90.448217 30.532324, -90.448232 30.532392, -90.448223 30.532509, -90.448199 30.532658, -90.448226 30.533048, -90.448273 30.533406, -90.448303 30.533596, -90.448349 30.534148, -90.448544 30.534146, -90.449457 30.53415, -90.449516 30.534151, -90.450068 30.534201, -90.450487 30.534332, -90.450767 30.534437, -90.451065 30.534591, -90.451345 30.534739, -90.451487 30.534847, -90.451493 30.535972, -90.451494 30.536098, -90.451439 30.536623, -90.451405 30.536734, -90.451361 30.536804, -90.451281 30.53689, -90.451166 30.536974, -90.450941 30.537141, -90.450885 30.537205, -90.450845 30.537274, -90.45083 30.537351, -90.450791 30.538168, -90.45078 30.540165, -90.450775 30.541132, -90.450777 30.541278, -90.450745 30.541278, -90.450652 30.541279, -90.450621 30.54128, -90.450169 30.541284, -90.450071 30.541285, -90.448813 30.541298, -90.448362 30.541303, -90.448134 30.541307, -90.448007 30.541307, -90.446943 30.541307, -90.446589 30.541308, -90.446602 30.541424, -90.446614 30.541814, -90.446691 30.542254, -90.446697 30.542825, -90.446699 30.543049, -90.446703 30.543287, -90.446741 30.543672, -90.446862 30.543903, -90.447046 30.544205, -90.447091 30.54426, -90.447237 30.544463, -90.447504 30.544832, -90.447539 30.544904, -90.447612 30.545052, -90.447853 30.545458, -90.448056 30.545783, -90.448215 30.546079, -90.448431 30.546426, -90.448672 30.546712, -90.448926 30.546915, -90.449238 30.547085, -90.449527 30.547245, -90.449676 30.547327, -90.45007 30.547536, -90.450228 30.547651, -90.450298 30.547816, -90.450451 30.548141, -90.450584 30.54847, -90.450591 30.548514, -90.450177 30.548514, -90.448935 30.548517, -90.448522 30.548519, -90.448494 30.549107, -90.448491 30.549263, -90.448484 30.549825, -90.448502 30.550337, -90.448508 30.551497, -90.448513 30.552242, -90.448517 30.552943, -90.448516 30.552968, -90.448512 30.555147, -90.448512 30.555402, -90.448512 30.555874, -90.448513 30.556224, -90.448518 30.557274, -90.44852 30.557625, -90.44852 30.557984, -90.448522 30.558762, -90.448516 30.559063, -90.44851 30.559423, -90.448509 30.559471, -90.448509 30.559527, -90.448507 30.559842, -90.448507 30.559948, -90.448507 30.559987, -90.448507 30.560105, -90.448507 30.560145, -90.448506 30.560246, -90.448504 30.560549, -90.448504 30.560651, -90.448498 30.561848, -90.448492 30.563198, -90.448493 30.565439, -90.448495 30.566637, -90.449268 30.566644, -90.45159 30.566665, -90.451792 30.566667, -90.452364 30.566675, -90.45239 30.56678, -90.452403 30.566984, -90.452352 30.567319, -90.452285 30.567456, -90.452187 30.56766, -90.45206 30.567979, -90.452053 30.568182, -90.45218 30.568798, -90.452352 30.569738, -90.45237 30.56979, -90.45246 30.570046, -90.452632 30.570254, -90.452875 30.570355)))"} -{"geo_id":"29818","urban_area_code":"29818","name":"Flagstaff, AZ","lsad_name":"Flagstaff, AZ Urbanized Area","area_lsad_code":"75","mtfcc_feature_class_code":"G3500","type":"U","functional_status":"S","area_land_meters":90137027,"area_water_meters":59774,"internal_point_lon":-111.6239942,"internal_point_lat":35.1995,"internal_point_geom":"POINT(-111.6239942 35.1995)","urban_area_geom":"MULTIPOLYGON(((-111.503454 35.250453, -111.503322 35.250398, -111.503056 35.25034, -111.502746 35.250341, -111.502547 35.250365, -111.502381 35.250345, -111.502811 35.251228, -111.503175 35.251525, -111.503191 35.251242, -111.503196 35.251157, -111.503222 35.251017, -111.503306 35.25079, -111.503417 35.250561, -111.503454 35.250453)), ((-111.503454 35.250453, -111.503948 35.250483, -111.504277 35.2505, -111.504764 35.250521, -111.505043 35.250458, -111.50507 35.250168, -111.505534 35.25017, -111.505871 35.250172, -111.506271 35.25019, -111.506318 35.250192, -111.506555 35.250268, -111.506753 35.250326, -111.506808 35.250343, -111.506924 35.250365, -111.507514 35.250365, -111.50812 35.250363, -111.508139 35.250026, -111.508117 35.249746, -111.508051 35.249581, -111.507998 35.249449, -111.507714 35.249044, -111.50739 35.248539, -111.507275 35.248366, -111.50707 35.248067, -111.506932 35.247929, -111.506911 35.247908, -111.506717 35.247855, -111.505584 35.247864, -111.503532 35.247864, -111.503537 35.248688, -111.50353 35.250025, -111.503517 35.250162, -111.503512 35.250217, -111.503486 35.25036, -111.503454 35.250453)), ((-111.59179 35.185283, -111.592394 35.185544, -111.592542 35.185638, -111.592581 35.185663, -111.592824 35.185836, -111.593016 35.186007, -111.593161 35.186179, -111.593254 35.186303, -111.593351 35.186485, -111.593461 35.186613, -111.593571 35.186767, -111.593708 35.186919, -111.59396 35.187169, -111.594083 35.187074, -111.594283 35.186905, -111.594455 35.186735, -111.594557 35.186599, -111.59468 35.186372, -111.594769 35.186153, -111.594815 35.185937, -111.594827 35.185722, -111.594817 35.185445, -111.594807 35.182607, -111.594797 35.182438, -111.594676 35.18244, -111.594495 35.182443, -111.594328 35.18242, -111.594113 35.182371, -111.593897 35.182309, -111.593732 35.182288, -111.593558 35.182288, -111.593369 35.182289, -111.593143 35.182306, -111.593157 35.182354, -111.593173 35.182461, -111.59318 35.182596, -111.593181 35.182759, -111.593169 35.182901, -111.593138 35.183001, -111.593064 35.183152, -111.592852 35.1835, -111.592603 35.183922, -111.59196 35.184973, -111.591816 35.185235, -111.591752 35.185204, -111.591608 35.185083, -111.591504 35.185026, -111.591401 35.185088, -111.591344 35.185173, -111.591334 35.185291, -111.591363 35.18539, -111.591427 35.185456, -111.59149 35.185512, -111.591561 35.18555, -111.59179 35.185283)), ((-111.643128 35.24032, -111.644806 35.240248, -111.645528 35.240248, -111.651755 35.240536, -111.652154 35.240555, -111.652194 35.240526, -111.652966 35.240093, -111.653031 35.240068, -111.653189 35.240007, -111.653566 35.239862, -111.653645 35.239836, -111.653744 35.239814, -111.653931 35.239788, -111.654252 35.239767, -111.654579 35.239757, -111.655034 35.239742, -111.655645 35.239723, -111.655783 35.239722, -111.656141 35.239722, -111.657419 35.239722, -111.657728 35.239723, -111.657729 35.239709, -111.657725 35.239673, -111.662315 35.239587, -111.66425 35.239661, -111.666806 35.239759, -111.666807 35.239656, -111.666833 35.236628, -111.666887 35.236675, -111.66693 35.236712, -111.667208 35.236953, -111.668114 35.237816, -111.668276 35.237973, -111.668551 35.238241, -111.66887 35.238491, -111.66901 35.238575, -111.66927 35.238732, -111.669833 35.239038, -111.670201 35.239198, -111.670661 35.23936, -111.671109 35.239476, -111.671441 35.239541, -111.671819 35.239597, -111.672312 35.239629, -111.673731 35.239631, -111.674422 35.239618, -111.675271 35.239602, -111.67587 35.239591, -111.677652 35.239576, -111.679517 35.239566, -111.679768 35.239559, -111.680197 35.239547, -111.680574 35.239553, -111.680654 35.239558, -111.680817 35.239569, -111.681142 35.239605, -111.681382 35.239647, -111.681802 35.23975, -111.682136 35.239744, -111.682324 35.239733, -111.682183 35.239692, -111.681975 35.23963, -111.68164 35.239546, -111.6816 35.239536, -111.681238 35.239465, -111.681392 35.239229, -111.681533 35.238888, -111.681594 35.238668, -111.68166 35.238514, -111.681788 35.238334, -111.682 35.238321, -111.682168 35.238287, -111.682478 35.238162, -111.682744 35.238023, -111.68286 35.237929, -111.682928 35.237779, -111.682919 35.237614, -111.682874 35.237461, -111.682802 35.23737, -111.682782 35.237345, -111.682695 35.237278, -111.682609 35.237214, -111.682455 35.23713, -111.682047 35.236938, -111.681952 35.236875, -111.681801 35.236738, -111.681743 35.236624, -111.681719 35.236577, -111.681702 35.23654, -111.681685 35.236501, -111.681626 35.236364, -111.681581 35.236288, -111.681399 35.235981, -111.681189 35.235722, -111.680927 35.235483, -111.680703 35.235367, -111.680515 35.235299, -111.680274 35.235211, -111.679556 35.235077, -111.67916 35.234968, -111.679028 35.234906, -111.67894 35.23484, -111.678866 35.234757, -111.678792 35.234652, -111.678729 35.234487, -111.678709 35.234377, -111.678714 35.234065, -111.678694 35.233882, -111.678674 35.233795, -111.678668 35.233771, -111.678475 35.233484, -111.678455 35.233423, -111.678437 35.233364, -111.678415 35.233292, -111.678389 35.233187, -111.678373 35.233011, -111.678425 35.232653, -111.678426 35.232359, -111.678384 35.232209, -111.678344 35.232156, -111.678296 35.232108, -111.678218 35.232048, -111.678017 35.231963, -111.677896 35.23193, -111.677753 35.231901, -111.677398 35.231843, -111.677104 35.231728, -111.67666 35.231469, -111.676571 35.231383, -111.676453 35.231248, -111.676261 35.23106, -111.676111 35.230965, -111.67559 35.230779, -111.675111 35.230725, -111.675068 35.229661, -111.675065 35.229594, -111.675022 35.229324, -111.674969 35.229118, -111.674924 35.228998, -111.674847 35.228896, -111.674611 35.228694, -111.673989 35.228202, -111.673783 35.228066, -111.67375 35.228044, -111.67367 35.228012, -111.673534 35.227968, -111.67342 35.227953, -111.673296 35.227954, -111.673124 35.22797, -111.672679 35.228074, -111.67219 35.228167, -111.671916 35.228223, -111.671778 35.228277, -111.671756 35.228292, -111.670882 35.229506, -111.670721 35.229458, -111.667073 35.229218, -111.665764 35.229378, -111.665749 35.229364, -111.665701 35.229336, -111.665664 35.229331, -111.665626 35.229331, -111.665538 35.22936, -111.66541 35.229407, -111.665374 35.229423, -111.665198 35.229511, -111.664899 35.229653, -111.66479 35.229742, -111.664707 35.229827, -111.664586 35.229761, -111.664511 35.229715, -111.664364 35.229561, -111.664356 35.229549, -111.664278 35.229427, -111.664131 35.229177, -111.663938 35.228872, -111.663911 35.228771, -111.663908 35.227981, -111.663943 35.22753, -111.663742 35.227612, -111.663682 35.227627, -111.663527 35.227667, -111.662713 35.227854, -111.662768 35.227913, -111.662838 35.227947, -111.662889 35.22796, -111.662901 35.228389, -111.663159 35.228755, -111.663304 35.228989, -111.6637 35.229629, -111.663717 35.229657, -111.663975 35.23004, -111.664191 35.230415, -111.663727 35.230934, -111.663674 35.231035, -111.663659 35.231108, -111.663659 35.23118, -111.663693 35.231433, -111.663687 35.231526, -111.663663 35.231596, -111.663641 35.231638, -111.663594 35.231679, -111.663547 35.231708, -111.663515 35.231723, -111.663293 35.231828, -111.663101 35.231855, -111.663016 35.23185, -111.662934 35.231833, -111.662344 35.231734, -111.662316 35.231479, -111.662301 35.231458, -111.662264 35.231434, -111.662202 35.231419, -111.662136 35.231423, -111.661906 35.231475, -111.661926 35.231595, -111.661926 35.231694, -111.66192 35.231757, -111.661658 35.231835, -111.661526 35.23189, -111.661387 35.231985, -111.661244 35.231772, -111.66099 35.23129, -111.660929 35.231142, -111.66076 35.230736, -111.660187 35.22925, -111.659921 35.228569, -111.659813 35.228326, -111.659752 35.228189, -111.659731 35.228142, -111.659538 35.227793, -111.659304 35.227428, -111.659231 35.227313, -111.659105 35.227122, -111.659077 35.227079, -111.658958 35.226901, -111.658942 35.226877, -111.658879 35.226797, -111.658319 35.226085, -111.657751 35.22535, -111.657644 35.225216, -111.657621 35.225187, -111.657947 35.225183, -111.658239 35.225183, -111.658302 35.225206, -111.658424 35.225231, -111.658537 35.225237, -111.659549 35.225223, -111.659512 35.225177, -111.6592 35.224798, -111.658925 35.224786, -111.658814 35.224756, -111.658763 35.224736, -111.658648 35.224658, -111.658554 35.224531, -111.658425 35.224317, -111.658317 35.224182, -111.658258 35.224137, -111.658162 35.224106, -111.657979 35.224076, -111.657827 35.224063, -111.657459 35.224076, -111.6573 35.224111, -111.657204 35.224143, -111.656862 35.22433, -111.656787 35.224258, -111.656615 35.224093, -111.656423 35.223924, -111.65615 35.223705, -111.655817 35.223464, -111.655566 35.223309, -111.655716 35.222851, -111.655875 35.222915, -111.656094 35.223064, -111.65618 35.223122, -111.656551 35.223397, -111.65663 35.223451, -111.656703 35.223476, -111.656744 35.223478, -111.656806 35.223467, -111.656923 35.223421, -111.657086 35.223347, -111.657215 35.223289, -111.657369 35.223226, -111.657427 35.223166, -111.657451 35.223081, -111.657441 35.222321, -111.657443 35.221997, -111.657445 35.221768, -111.657446 35.221677, -111.657439 35.221636, -111.65741 35.221468, -111.657879 35.221349, -111.657851 35.220942, -111.657866 35.220414, -111.65789 35.219626, -111.657888 35.21957, -111.657866 35.218895, -111.657878 35.218545, -111.657894 35.218341, -111.657853 35.218182, -111.657764 35.217985, -111.65773 35.217908, -111.65778 35.217831, -111.657822 35.217637, -111.65783 35.217526, -111.657835 35.217459, -111.658991 35.217282, -111.659002 35.217238, -111.659146 35.21662, -111.65873 35.216381, -111.658279 35.216243, -111.658075 35.216224, -111.657941 35.216219, -111.657814 35.216231, -111.657757 35.21541, -111.657785 35.215348, -111.657826 35.215287, -111.657853 35.215225, -111.657863 35.215122, -111.657852 35.214346, -111.657845 35.213695, -111.65781 35.21242, -111.657805 35.211359, -111.657813 35.211181, -111.657847 35.210517, -111.657864 35.210079, -111.657912 35.209701, -111.657926 35.2096, -111.658455 35.207662, -111.65869 35.206763, -111.658802 35.20631, -111.658803 35.206145, -111.658802 35.205901, -111.658726 35.205556, -111.658532 35.204785, -111.658445 35.204439, -111.658406 35.20428, -111.658367 35.203978, -111.658317 35.203595, -111.658264 35.203158, -111.658384 35.203156, -111.658441 35.203149, -111.658476 35.203141, -111.6585 35.203124, -111.658511 35.203087, -111.65851 35.202619, -111.658184 35.202521, -111.658228 35.20233, -111.658557 35.201593, -111.658643 35.201401, -111.659025 35.20066, -111.659561 35.200831, -111.660015 35.200727, -111.660029 35.200683, -111.660025 35.20061, -111.659745 35.199787, -111.659418 35.19881, -111.65911 35.197893, -111.657653 35.198219, -111.657613 35.198102, -111.657606 35.19805, -111.657626 35.198017, -111.657656 35.197992, -111.657709 35.197968, -111.65843 35.197691, -111.659077 35.19747, -111.659688 35.197261, -111.659704 35.197256, -111.660211 35.19706, -111.660684 35.196878, -111.661092 35.196742, -111.662832 35.19615, -111.663176 35.196006, -111.663497 35.195844, -111.663583 35.196088, -111.664807 35.195544, -111.664635 35.19534, -111.664569 35.195255, -111.664443 35.195092, -111.664395 35.195029, -111.665221 35.19437, -111.666445 35.193652, -111.666533 35.1936, -111.667931 35.192849, -111.668827 35.192561, -111.669802 35.192559, -111.670584 35.192782, -111.671063 35.193052, -111.671294 35.193279, -111.671372 35.193786, -111.671426 35.193816, -111.671621 35.193925, -111.672235 35.193836, -111.67242 35.194013, -111.672566 35.194158, -111.672697 35.194247, -111.672864 35.194347, -111.67299 35.194416, -111.673057 35.194485, -111.67318 35.194513, -111.67469 35.194861, -111.674938 35.194866, -111.67779 35.194924, -111.67873 35.19502, -111.680141 35.195533, -111.681339 35.196078, -111.681772 35.196253, -111.682584 35.196388, -111.683801 35.196591, -111.683837 35.196597, -111.684975 35.196385, -111.686345 35.195778, -111.688887 35.194582, -111.689348 35.194433, -111.69003 35.194215, -111.690468 35.194234, -111.69056 35.194238, -111.692656 35.194539, -111.693741 35.194696, -111.694857 35.194627, -111.696029 35.194375, -111.696781 35.19413, -111.697063 35.194039, -111.697369 35.19394, -111.698338 35.193511, -111.699226 35.193117, -111.70027 35.192656, -111.701319 35.192409, -111.701442 35.192381, -111.702111 35.192335, -111.703479 35.19277, -111.705376 35.193571, -111.706325 35.19419, -111.706743 35.194648, -111.707102 35.194484, -111.707071 35.194435, -111.707517 35.192406, -111.70756 35.192303, -111.707801 35.191653, -111.707838 35.191563, -111.70814 35.190811, -111.708759 35.189199, -111.709126 35.188246, -111.709215 35.187942, -111.709241 35.187851, -111.709247 35.187838, -111.709295 35.187511, -111.709361 35.187031, -111.709379 35.186825, -111.709422 35.186663, -111.709507 35.186392, -111.709435 35.18637, -111.709615 35.185987, -111.709747 35.186025, -111.709976 35.185507, -111.70986 35.185471, -111.710081 35.185003, -111.709402 35.184878, -111.708297 35.18471, -111.708021 35.184656, -111.707796 35.184607, -111.707448 35.184531, -111.706935 35.18441, -111.706427 35.184278, -111.705745 35.184088, -111.704627 35.183763, -111.704495 35.183721, -111.703574 35.183486, -111.703163 35.183338, -111.702622 35.183167, -111.701924 35.182957, -111.701263 35.182799, -111.701191 35.182781, -111.699219 35.18229, -111.69583 35.181499, -111.695767 35.181486, -111.695139 35.181355, -111.694811 35.181273, -111.695643 35.180574, -111.695712 35.180516, -111.696609 35.179747, -111.696682 35.179685, -111.69735 35.179137, -111.697599 35.178916, -111.697777 35.178758, -111.697948 35.178573, -111.69811 35.178361, -111.698248 35.178115, -111.698381 35.177826, -111.698784 35.176891, -111.698925 35.176502, -111.698971 35.176376, -111.699288 35.175726, -111.699495 35.175264, -111.699976 35.174156, -111.700282 35.173478, -111.700482 35.173079, -111.700782 35.172374, -111.700914 35.172064, -111.701004 35.171738, -111.701067 35.171295, -111.701059 35.171005, -111.700985 35.170699, -111.70095 35.170477, -111.70097 35.17026, -111.701179 35.168927, -111.701184 35.168894, -111.701375 35.168048, -111.70139 35.167889, -111.701378 35.167447, -111.701373 35.167275, -111.701451 35.166334, -111.701488 35.165895, -111.701555 35.165519, -111.701562 35.165479, -111.701575 35.165433, -111.701589 35.165418, -111.701659 35.165261, -111.701806 35.165026, -111.702441 35.164082, -111.702528 35.163937, -111.701987 35.163759, -111.701766 35.163665, -111.701663 35.163609, -111.701555 35.163551, -111.701476 35.163508, -111.701147 35.16333, -111.700107 35.162685, -111.699908 35.162582, -111.69972 35.162538, -111.699472 35.162535, -111.698308 35.162781, -111.697835 35.162906, -111.697478 35.163007, -111.69716 35.163064, -111.69656 35.163085, -111.696163 35.163083, -111.695141 35.163242, -111.694569 35.163259, -111.694189 35.163248, -111.693776 35.163191, -111.693577 35.163116, -111.693074 35.162847, -111.69267 35.162691, -111.692301 35.162599, -111.691804 35.162452, -111.691225 35.162261, -111.690915 35.162073, -111.690406 35.161737, -111.690146 35.161612, -111.689683 35.161511, -111.689422 35.161304, -111.688911 35.160769, -111.688628 35.160509, -111.688147 35.160254, -111.687665 35.160005, -111.68755 35.159945, -111.687161 35.159676, -111.686303 35.159085, -111.686095 35.158941, -111.686068 35.158914, -111.685906 35.158751, -111.685768 35.158528, -111.685731 35.158329, -111.685904 35.157796, -111.685997 35.157497, -111.686142 35.157115, -111.686359 35.156634, -111.686627 35.156204, -111.687065 35.155647, -111.687368 35.155235, -111.687521 35.155028, -111.687715 35.154818, -111.68814 35.154459, -111.688444 35.154188, -111.688723 35.153856, -111.688018 35.153677, -111.685839 35.153177, -111.685819 35.153065, -111.685783 35.152996, -111.685559 35.152573, -111.684971 35.151608, -111.684662 35.151102, -111.684639 35.151063, -111.683886 35.149802, -111.683312 35.14893, -111.683071 35.148657, -111.682969 35.148574, -111.682774 35.148487, -111.68319 35.147394, -111.6837 35.146029, -111.685085 35.142371, -111.685146 35.14221, -111.685307 35.141816, -111.68551 35.141253, -111.685701 35.14067, -111.685768 35.140441, -111.685781 35.140357, -111.685857 35.139883, -111.685863 35.139758, -111.685738 35.139754, -111.685508 35.139748, -111.685305 35.139735, -111.684825 35.139701, -111.684847 35.139085, -111.684903 35.138137, -111.684944 35.137216, -111.684975 35.137021, -111.684974 35.136477, -111.684992 35.134504, -111.685082 35.131503, -111.685049 35.130654, -111.684999 35.130258, -111.684818 35.129511, -111.684668 35.129003, -111.684494 35.128629, -111.684086 35.127866, -111.683848 35.127488, -111.683675 35.127268, -111.68351 35.127019, -111.683481 35.126975, -111.683468 35.126958, -111.682639 35.125884, -111.682284 35.125356, -111.682086 35.125026, -111.681972 35.124851, -111.681869 35.124614, -111.68155 35.12397, -111.681313 35.123422, -111.681161 35.123001, -111.681047 35.122683, -111.680974 35.122427, -111.680941 35.122313, -111.680898 35.122163, -111.68072 35.121435, -111.680639 35.120944, -111.680612 35.12078, -111.680546 35.120034, -111.680539 35.119192, -111.680548 35.118992, -111.68058 35.118252, -111.680633 35.117804, -111.680743 35.117126, -111.68093 35.116232, -111.681027 35.115696, -111.681171 35.115166, -111.681276 35.114741, -111.681348 35.114506, -111.681439 35.114002, -111.681588 35.113328, -111.681605 35.113134, -111.681701 35.112069, -111.681722 35.111638, -111.681714 35.111117, -111.681654 35.110247, -111.681588 35.109473, -111.681483 35.10869, -111.681349 35.107785, -111.681291 35.106768, -111.681291 35.105912, -111.681339 35.105359, -111.681392 35.104827, -111.684407 35.104821, -111.685448 35.104696, -111.685412 35.105824, -111.687298 35.105159, -111.68734 35.10524, -111.687444 35.105617, -111.687454 35.105844, -111.68762 35.106227, -111.687756 35.106369, -111.687917 35.106502, -111.688126 35.106643, -111.688564 35.106806, -111.68895 35.106872, -111.689467 35.10688, -111.689702 35.106882, -111.690156 35.106845, -111.690357 35.106803, -111.690236 35.106314, -111.690478 35.105221, -111.690865 35.105401, -111.691706 35.105602, -111.691719 35.10553, -111.691929 35.104964, -111.692065 35.10462, -111.691915 35.104554, -111.691222 35.10439, -111.690714 35.104157, -111.690813 35.103715, -111.691151 35.10297, -111.690782 35.102574, -111.690822 35.102199, -111.691418 35.102155, -111.691563 35.102741, -111.691756 35.103145, -111.691852 35.103308, -111.691929 35.103389, -111.692215 35.103536, -111.692505 35.103698, -111.69262 35.103568, -111.692732 35.103455, -111.692907 35.103303, -111.693023 35.103228, -111.693141 35.103161, -111.693338 35.10305, -111.694655 35.102411, -111.694765 35.101707, -111.694381 35.101323, -111.694765 35.101032, -111.695064 35.10082, -111.695447 35.100467, -111.695834 35.100405, -111.696132 35.100345, -111.696913 35.10029, -111.696915 35.100699, -111.698548 35.100973, -111.699076 35.101692, -111.699336 35.102055, -111.699792 35.101654, -111.70006 35.101895, -111.700216 35.101875, -111.700782 35.101316, -111.700772 35.099573, -111.702025 35.097702, -111.701771 35.097413, -111.701037 35.096578, -111.699062 35.096071, -111.698905 35.095448, -111.699736 35.095218, -111.700367 35.094661, -111.700542 35.094645, -111.700798 35.094567, -111.700862 35.094528, -111.701123 35.094369, -111.701336 35.094176, -111.70147 35.09403, -111.701585 35.093833, -111.701667 35.093572, -111.701707 35.093238, -111.701727 35.093073, -111.701779 35.092615, -111.701793 35.092493, -111.701782 35.092282, -111.701734 35.092167, -111.701649 35.092037, -111.701624 35.092014, -111.701536 35.091934, -111.701452 35.091897, -111.701308 35.091833, -111.701389 35.091746, -111.701436 35.091695, -111.701529 35.091638, -111.701599 35.09162, -111.701779 35.091593, -111.701997 35.091586, -111.702075 35.091595, -111.702243 35.091613, -111.702365 35.091673, -111.702494 35.091783, -111.702592 35.091899, -111.702635 35.092027, -111.702688 35.092149, -111.702741 35.092226, -111.702793 35.092277, -111.702891 35.092339, -111.702995 35.092364, -111.703127 35.092363, -111.703275 35.092314, -111.703396 35.092228, -111.703437 35.092148, -111.703453 35.092054, -111.703449 35.091963, -111.70341 35.091877, -111.703323 35.091759, -111.704032 35.091583, -111.703639 35.085297, -111.695762 35.085585, -111.689708 35.085806, -111.689069 35.085923, -111.689043 35.085919, -111.688972 35.085882, -111.688691 35.085628, -111.688514 35.085488, -111.688385 35.085417, -111.688224 35.085364, -111.688068 35.085422, -111.687981 35.085601, -111.687885 35.085953, -111.687732 35.08648, -111.687578 35.086927, -111.687386 35.087528, -111.687355 35.087654, -111.687262 35.088039, -111.687119 35.088542, -111.687081 35.088761, -111.687038 35.088984, -111.686968 35.089345, -111.687002 35.090162, -111.687367 35.09014, -111.687517 35.090099, -111.687763 35.090122, -111.688013 35.090111, -111.688147 35.090101, -111.688176 35.090174, -111.688219 35.090236, -111.688233 35.090279, -111.688261 35.09046, -111.688296 35.090632, -111.688313 35.090789, -111.688312 35.090948, -111.688287 35.091143, -111.688232 35.091334, -111.688101 35.091733, -111.688068 35.091861, -111.688025 35.092093, -111.688027 35.092156, -111.688034 35.092211, -111.688066 35.09232, -111.688103 35.092411, -111.688145 35.092481, -111.688309 35.092659, -111.688395 35.092722, -111.688477 35.092766, -111.688677 35.092861, -111.688837 35.092899, -111.689211 35.092924, -111.689952 35.092912, -111.690113 35.092919, -111.690198 35.09293, -111.690346 35.092979, -111.690535 35.09306, -111.690592 35.093098, -111.69045 35.093192, -111.690322 35.093301, -111.690222 35.093428, -111.690149 35.093565, -111.690129 35.093664, -111.690115 35.093763, -111.690129 35.093862, -111.690149 35.09395, -111.690149 35.094071, -111.690155 35.09417, -111.690129 35.09423, -111.690055 35.09434, -111.689968 35.094423, -111.689761 35.094582, -111.6896 35.094731, -111.68942 35.09467, -111.689232 35.094643, -111.689072 35.094588, -111.688951 35.094571, -111.688798 35.094571, -111.688664 35.094582, -111.68835 35.094698, -111.688129 35.094775, -111.687908 35.094857, -111.687801 35.09489, -111.687634 35.094923, -111.68744 35.094978, -111.687286 35.094995, -111.687119 35.094995, -111.686945 35.095044, -111.686785 35.095116, -111.686651 35.095204, -111.686424 35.095341, -111.68627 35.095445, -111.686189 35.095495, -111.686082 35.095544, -111.685995 35.095569, -111.685855 35.09561, -111.685581 35.095654, -111.685347 35.095676, -111.685206 35.095665, -111.685119 35.095676, -111.685019 35.095731, -111.684932 35.095819, -111.684825 35.095995, -111.684758 35.096072, -111.68472 35.096092, -111.684651 35.096127, -111.684511 35.096149, -111.68431 35.096155, -111.68406 35.096133, -111.683869 35.096105, -111.683795 35.096082, -111.683311 35.097569, -111.682205 35.100794, -111.681742 35.102193, -111.681424 35.103326, -111.681197 35.104365, -111.681127 35.104827, -111.6811 35.105005, -111.681019 35.10585, -111.681012 35.10663, -111.681018 35.107001, -111.681069 35.10755, -111.681283 35.10924, -111.681375 35.110133, -111.681446 35.11107, -111.681455 35.111747, -111.681417 35.112195, -111.681378 35.112548, -111.681334 35.113138, -111.681333 35.113156, -111.68129 35.113474, -111.68114 35.114079, -111.680869 35.115246, -111.680435 35.117322, -111.680348 35.117817, -111.680279 35.118393, -111.680264 35.118712, -111.680234 35.11935, -111.680257 35.120041, -111.680316 35.120732, -111.680356 35.120975, -111.680434 35.121448, -111.680599 35.122165, -111.680645 35.122313, -111.680681 35.12243, -111.679972 35.12243, -111.677962 35.122439, -111.677951 35.125053, -111.677949 35.125361, -111.677922 35.128914, -111.677428 35.128625, -111.677214 35.128535, -111.677134 35.128534, -111.677064 35.128533, -111.676616 35.128581, -111.676326 35.128586, -111.675932 35.128514, -111.675825 35.128473, -111.67573 35.128382, -111.67564 35.128319, -111.675388 35.128324, -111.675213 35.128374, -111.675043 35.128463, -111.674979 35.128488, -111.674869 35.128671, -111.674637 35.128853, -111.674256 35.129123, -111.673856 35.129387, -111.673605 35.129503, -111.673521 35.129511, -111.673376 35.129466, -111.672997 35.129384, -111.672548 35.129302, -111.672328 35.129226, -111.671517 35.128839, -111.671372 35.128825, -111.671068 35.128819, -111.670769 35.128767, -111.670502 35.128668, -111.670239 35.12852, -111.670045 35.128299, -111.66989 35.128146, -111.669829 35.128108, -111.669792 35.128108, -111.669521 35.128186, -111.66926 35.128226, -111.66898 35.128232, -111.668952 35.128236, -111.668635 35.128283, -111.668528 35.128338, -111.668393 35.128465, -111.668352 35.128561, -111.668354 35.128761, -111.668299 35.128965, -111.668207 35.129111, -111.668193 35.12912, -111.667975 35.129262, -111.667458 35.129495, -111.666955 35.129666, -111.666671 35.129703, -111.66633 35.129701, -111.665866 35.129611, -111.665562 35.129505, -111.665295 35.129392, -111.664887 35.129187, -111.664675 35.12905, -111.664422 35.128928, -111.663907 35.128774, -111.662961 35.128426, -111.662235 35.128242, -111.661726 35.128164, -111.661361 35.128055, -111.66162 35.128569, -111.661772 35.129146, -111.66183 35.129759, -111.661865 35.129852, -111.661904 35.129956, -111.662214 35.130312, -111.662438 35.130505, -111.662708 35.130748, -111.663096 35.130886, -111.663422 35.130954, -111.663817 35.131075, -111.664035 35.131158, -111.663935 35.131158, -111.663741 35.131185, -111.663607 35.131224, -111.663399 35.131295, -111.663138 35.131416, -111.662884 35.131553, -111.66269 35.131663, -111.662242 35.131933, -111.661967 35.13213, -111.661639 35.132306, -111.661378 35.132438, -111.661291 35.13246, -111.661224 35.132487, -111.661137 35.132542, -111.66107 35.13257, -111.661017 35.132619, -111.66091 35.132663, -111.660763 35.132696, -111.660738 35.1327, -111.660622 35.132718, -111.660247 35.132718, -111.65994 35.132713, -111.659732 35.13274, -111.659527 35.13278, -111.65925 35.132833, -111.658976 35.13291, -111.658822 35.132938, -111.658642 35.132976, -111.658461 35.132998, -111.658394 35.132998, -111.658334 35.133004, -111.658274 35.132992, -111.658213 35.132965, -111.65818 35.132938, -111.6581 35.132921, -111.657966 35.132921, -111.657805 35.132932, -111.657584 35.132998, -111.657323 35.133251, -111.657189 35.133333, -111.656975 35.133415, -111.656842 35.133443, -111.656373 35.133569, -111.655965 35.133706, -111.655777 35.133783, -111.655537 35.13391, -111.655289 35.134009, -111.655068 35.134119, -111.654787 35.134311, -111.654466 35.13452, -111.654285 35.134674, -111.654118 35.134844, -111.653957 35.135069, -111.65383 35.135234, -111.653676 35.135404, -111.653448 35.135696, -111.653407 35.135761, -111.653352 35.135868, -111.653083 35.13644, -111.653065 35.136507, -111.65304 35.136646, -111.652986 35.136778, -111.652917 35.136934, -111.652897 35.136976, -111.652915 35.137124, -111.653 35.137364, -111.653118 35.138032, -111.653259 35.138309, -111.653402 35.138533, -111.653604 35.138762, -111.653673 35.138897, -111.653676 35.138911, -111.653699 35.13901, -111.653678 35.139129, -111.653621 35.139236, -111.653528 35.139342, -111.653453 35.139438, -111.653318 35.139613, -111.653069 35.139892, -111.65299 35.140038, -111.652961 35.140115, -111.652943 35.140234, -111.652887 35.140448, -111.652808 35.140671, -111.652735 35.140796, -111.652658 35.140867, -111.652133 35.141296, -111.651409 35.141749, -111.651102 35.14201, -111.650883 35.14217, -111.649944 35.142855, -111.649521 35.143143, -111.649294 35.143326, -111.649169 35.143433, -111.649577 35.144071, -111.650017 35.14476, -111.650718 35.145834, -111.651154 35.146539, -111.651646 35.147288, -111.65198 35.147842, -111.653138 35.149547, -111.653576 35.150089, -111.653894 35.150474, -111.654227 35.150839, -111.654869 35.151496, -111.656429 35.153062, -111.657257 35.153938, -111.657565 35.154282, -111.657715 35.15445, -111.657995 35.154808, -111.658336 35.155244, -111.658489 35.155455, -111.658552 35.155543, -111.658749 35.155814, -111.658994 35.156138, -111.659116 35.15633, -111.659349 35.156731, -111.659189 35.156762, -111.65893 35.156811, -111.658383 35.156852, -111.657531 35.155696, -111.657149 35.155169, -111.656527 35.154454, -111.656421 35.154529, -111.656396 35.154644, -111.654924 35.155802, -111.654186 35.156411, -111.653779 35.156768, -111.653162 35.157309, -111.652329 35.158014, -111.650251 35.159804, -111.648716 35.161138, -111.648234 35.161566, -111.648116 35.161671, -111.647852 35.161914, -111.64771 35.1621, -111.647623 35.162215, -111.647523 35.162346, -111.647512 35.162366, -111.647503 35.162382, -111.64747 35.16244, -111.647361 35.162666, -111.647361 35.162681, -111.647248 35.163022, -111.647018 35.163919, -111.646922 35.164205, -111.646793 35.164457, -111.646506 35.164824, -111.64619 35.165152, -111.646152 35.165178, -111.645698 35.165503, -111.645668 35.165521, -111.645391 35.165685, -111.64567 35.166071, -111.645853 35.166305, -111.646 35.166492, -111.64669 35.167374, -111.647006 35.167797, -111.647048 35.167854, -111.647557 35.168537, -111.648033 35.169213, -111.648153 35.16947, -111.6482 35.169692, -111.648218 35.169927, -111.648214 35.170927, -111.648224 35.172597, -111.647524 35.172886, -111.646384 35.173447, -111.645285 35.174081, -111.644411 35.174636, -111.643574 35.175244, -111.643154 35.175591, -111.642952 35.175758, -111.641958 35.176609, -111.641632 35.176878, -111.641322 35.177134, -111.640898 35.177484, -111.64051 35.177786, -111.639911 35.178296, -111.639577 35.178585, -111.639062 35.178827, -111.633442 35.18147, -111.633075 35.181479, -111.632804 35.181487, -111.625773 35.181696, -111.623352 35.181762, -111.623198 35.181762, -111.620921 35.181838, -111.619859 35.181847, -111.609297 35.181869, -111.60849 35.181852, -111.608412 35.181892, -111.607843 35.182018, -111.607556 35.182095, -111.607087 35.182249, -111.606866 35.182315, -111.606705 35.182398, -111.606619 35.182486, -111.606565 35.18264, -111.606565 35.182893, -111.606512 35.183102, -111.606458 35.183189, -111.606311 35.183299, -111.606204 35.183426, -111.60615 35.18358, -111.606077 35.18369, -111.605943 35.183816, -111.605849 35.183976, -111.605803 35.184196, -111.605783 35.184383, -111.605796 35.18458, -111.605763 35.184674, -111.605696 35.184789, -111.605582 35.184883, -111.605415 35.184982, -111.605221 35.185048, -111.605006 35.185108, -111.604732 35.185158, -111.604484 35.185174, -111.604317 35.185219, -111.604109 35.185296, -111.603982 35.185356, -111.603915 35.185406, -111.603848 35.185603, -111.603782 35.185719, -111.603721 35.185774, -111.603607 35.18579, -111.603447 35.18584, -111.603326 35.185955, -111.603126 35.186093, -111.602811 35.186236, -111.602637 35.186291, -111.602396 35.186313, -111.602074 35.186385, -111.601847 35.186489, -111.601593 35.186533, -111.601398 35.186588, -111.601191 35.186709, -111.601017 35.186885, -111.600863 35.187089, -111.600749 35.187215, -111.600535 35.187369, -111.600515 35.187528, -111.600435 35.187682, -111.600027 35.188298, -111.599926 35.188408, -111.599886 35.188595, -111.599819 35.18887, -111.599801 35.18892, -111.599748 35.189068, -111.599732 35.189112, -111.599639 35.189321, -111.599626 35.189661, -111.599699 35.189953, -111.59986 35.189989, -111.599943 35.190013, -111.6001 35.190079, -111.600182 35.190105, -111.600216 35.190125, -111.600411 35.190446, -111.600531 35.190595, -111.6007 35.19076, -111.600743 35.190823, -111.600795 35.190959, -111.600807 35.19103, -111.600812 35.191208, -111.600796 35.191457, -111.600774 35.191481, -111.600732 35.191492, -111.600687 35.191497, -111.600645 35.191507, -111.6005 35.191122, -111.600443 35.19124, -111.600383 35.191315, -111.600314 35.191406, -111.600262 35.191532, -111.60025 35.191614, -111.600214 35.191731, -111.600072 35.191801, -111.599879 35.191807, -111.599708 35.191791, -111.598892 35.192061, -111.598896 35.19201, -111.598903 35.191912, -111.598925 35.191615, -111.598108 35.191376, -111.597521 35.191206, -111.59741 35.191416, -111.597217 35.191644, -111.597032 35.191813, -111.596779 35.191986, -111.596617 35.192037, -111.596559 35.192055, -111.596316 35.192105, -111.595811 35.192117, -111.595355 35.192092, -111.595145 35.19207, -111.595112 35.192066, -111.594908 35.191988, -111.59467 35.191839, -111.594508 35.1917, -111.594499 35.19168, -111.594417 35.191581, -111.594311 35.191333, -111.59421 35.191093, -111.594179 35.191005, -111.59411 35.190815, -111.594037 35.19061, -111.593892 35.190239, -111.593817 35.190044, -111.593661 35.1896, -111.593634 35.189522, -111.593571 35.189371, -111.593521 35.189253, -111.593406 35.189005, -111.593303 35.188841, -111.593254 35.188786, -111.59388 35.188805, -111.594958 35.188837, -111.59485 35.188493, -111.594728 35.188129, -111.594653 35.18793, -111.594623 35.187794, -111.594562 35.187691, -111.59454 35.187669, -111.594394 35.187522, -111.59396 35.187169, -111.593497 35.187526, -111.593133 35.187737, -111.59258 35.188177, -111.592443 35.188256, -111.592125 35.188516, -111.591741 35.188816, -111.588279 35.18903, -111.588096 35.188934, -111.58802 35.188888, -111.587863 35.189019, -111.587729 35.189101, -111.587661 35.189134, -111.58764 35.189055, -111.587547 35.188867, -111.587302 35.188791, -111.586662 35.188898, -111.586425 35.188938, -111.58598 35.188965, -111.585806 35.188946, -111.585064 35.188864, -111.583747 35.188718, -111.583748 35.188667, -111.583748 35.188619, -111.583754 35.188543, -111.583783 35.188442, -111.583842 35.188359, -111.583924 35.188301, -111.583991 35.188254, -111.584114 35.188198, -111.584465 35.18815, -111.584648 35.188088, -111.584796 35.187998, -111.584981 35.187831, -111.585056 35.187682, -111.585109 35.187463, -111.584861 35.187422, -111.584476 35.187419, -111.584177 35.187477, -111.583845 35.187573, -111.583288 35.187719, -111.582818 35.187841, -111.582493 35.187927, -111.582359 35.187962, -111.581937 35.188116, -111.581847 35.187908, -111.58182 35.1878, -111.581875 35.187522, -111.581935 35.187389, -111.582006 35.1873, -111.582105 35.187206, -111.58227 35.187067, -111.582391 35.186967, -111.582518 35.186833, -111.583213 35.185816, -111.583717 35.185094, -111.583807 35.184948, -111.583917 35.184721, -111.584118 35.184238, -111.584271 35.183919, -111.584548 35.18341, -111.58512 35.18233, -111.585164 35.182221, -111.585159 35.182167, -111.585135 35.182129, -111.585093 35.182102, -111.585019 35.182073, -111.584772 35.182071, -111.583523 35.182077, -111.582418 35.182097, -111.582109 35.182098, -111.581216 35.182099, -111.580182 35.182127, -111.580174 35.182259, -111.580158 35.182393, -111.580124 35.182565, -111.580071 35.182762, -111.579999 35.18296, -111.579911 35.183175, -111.579746 35.183417, -111.579381 35.183951, -111.578862 35.184711, -111.578757 35.184901, -111.57869 35.185038, -111.57859 35.18529, -111.578526 35.185515, -111.578487 35.185715, -111.578479 35.187521, -111.57843 35.188367, -111.578431 35.188415, -111.578432 35.1886, -111.573943 35.18853, -111.567127 35.188556, -111.566372 35.188559, -111.563712 35.188483, -111.562857 35.196252, -111.562522 35.196257, -111.562387 35.196256, -111.562303 35.196298, -111.561918 35.196376, -111.561319 35.196594, -111.561486 35.196917, -111.562085 35.198, -111.562341 35.198381, -111.562371 35.198412, -111.562422 35.198465, -111.562524 35.198573, -111.56284 35.199016, -111.562857 35.199039, -111.563255 35.199482, -111.563403 35.199587, -111.563871 35.199749, -111.564358 35.199876, -111.565938 35.200404, -111.566076 35.200469, -111.566186 35.20052, -111.567267 35.201121, -111.567836 35.201443, -111.567999 35.201587, -111.568147 35.201754, -111.568763 35.20267, -111.569114 35.203157, -111.569201 35.203203, -111.569301 35.203219, -111.569839 35.203212, -111.570134 35.2032, -111.570847 35.203171, -111.571253 35.203155, -111.57226 35.203182, -111.572872 35.203187, -111.573179 35.203204, -111.573373 35.203232, -111.573739 35.203339, -111.573982 35.203415, -111.574484 35.203543, -111.574983 35.203659, -111.575135 35.203693, -111.575224 35.203713, -111.575371 35.203744, -111.575713 35.203819, -111.576046 35.203891, -111.576933 35.204089, -111.577244 35.204172, -111.577413 35.204195, -111.577716 35.204203, -111.577628 35.205357, -111.577626 35.205568, -111.577621 35.206199, -111.577643 35.206945, -111.577646 35.207031, -111.577659 35.208003, -111.577024 35.20801, -111.575847 35.208058, -111.575723 35.208089, -111.575676 35.208136, -111.575641 35.208294, -111.575633 35.208607, -111.575649 35.20876, -111.576087 35.208763, -111.576681 35.208777, -111.577191 35.208766, -111.577394 35.208756, -111.577441 35.208743, -111.577513 35.208683, -111.577606 35.208559, -111.577703 35.208503, -111.577754 35.208768, -111.577774 35.208873, -111.577811 35.209473, -111.577819 35.209806, -111.577831 35.210323, -111.577525 35.210337, -111.577374 35.210359, -111.577218 35.210373, -111.575759 35.210333, -111.575766 35.210542, -111.575769 35.210649, -111.575616 35.210641, -111.575522 35.210672, -111.575361 35.210754, -111.574952 35.21105, -111.574714 35.211214, -111.574641 35.211313, -111.57458 35.21151, -111.574503 35.211698, -111.5744 35.211728, -111.574191 35.211712, -111.574014 35.21173, -111.573952 35.211795, -111.573917 35.211979, -111.573925 35.212406, -111.573962 35.21253, -111.574046 35.212572, -111.574296 35.212614, -111.575614 35.212637, -111.575739 35.21265, -111.575923 35.212683, -111.575952 35.212542, -111.576015 35.212409, -111.576144 35.212494, -111.576067 35.212663, -111.576034 35.212802, -111.57603 35.212887, -111.575896 35.212881, -111.575642 35.212868, -111.57473 35.212821, -111.574543 35.212822, -111.573871 35.2128, -111.57389 35.213804, -111.573894 35.214005, -111.573912 35.214932, -111.574874 35.214961, -111.576027 35.214996, -111.576263 35.215003, -111.576246 35.215217, -111.575945 35.215207, -111.574575 35.215164, -111.573825 35.215163, -111.573377 35.215149, -111.573322 35.215147, -111.572814 35.21513, -111.572705 35.215135, -111.572637 35.215186, -111.572503 35.215375, -111.571917 35.216468, -111.571887 35.216583, -111.571903 35.216643, -111.572023 35.216711, -111.572122 35.216715, -111.57284 35.216591, -111.573658 35.216442, -111.576134 35.215991, -111.576349 35.215952, -111.576418 35.215941, -111.576484 35.21593, -111.576379 35.215428, -111.576365 35.215278, -111.576381 35.215122, -111.576404 35.215, -111.576584 35.214996, -111.576657 35.215, -111.57699 35.215016, -111.57761 35.215039, -111.577709 35.215008, -111.577745 35.214914, -111.577744 35.214392, -111.577742 35.213803, -111.577674 35.213765, -111.577138 35.213724, -111.576684 35.213726, -111.576446 35.213768, -111.576234 35.213822, -111.57615 35.213555, -111.576272 35.213532, -111.576397 35.213523, -111.576699 35.213551, -111.577308 35.213587, -111.577558 35.213598, -111.577683 35.213547, -111.577719 35.213474, -111.577723 35.213264, -111.577627 35.212914, -111.577484 35.212641, -111.577495 35.211972, -111.576812 35.211985, -111.576801 35.211905, -111.576843 35.21189, -111.577068 35.211826, -111.577342 35.211772, -111.577532 35.211758, -111.577799 35.211774, -111.577767 35.212216, -111.57779 35.21252, -111.577862 35.212736, -111.577913 35.212887, -111.578155 35.21322, -111.578722 35.21365, -111.579875 35.214432, -111.580671 35.215, -111.581049 35.21527, -111.581547 35.21573, -111.581419 35.215775, -111.581237 35.215896, -111.580582 35.216269, -111.580255 35.216445, -111.579689 35.21674, -111.579484 35.216822, -111.579265 35.216902, -111.579122 35.216948, -111.57872 35.217056, -111.578125 35.217191, -111.576973 35.217434, -111.576695 35.217484, -111.576588 35.217515, -111.576493 35.217575, -111.574508 35.217864, -111.572723 35.218044, -111.571773 35.218123, -111.570829 35.21819, -111.569853 35.218243, -111.568619 35.21827, -111.567865 35.218281, -111.567824 35.218338, -111.567737 35.218365, -111.567596 35.218349, -111.567556 35.218349, -111.567449 35.218338, -111.567408 35.218281, -111.566677 35.218275, -111.565673 35.218258, -111.564535 35.21821, -111.56351 35.218153, -111.563211 35.218124, -111.562527 35.218057, -111.562369 35.218034, -111.561743 35.217961, -111.561337 35.217913, -111.560454 35.217832, -111.560301 35.21781, -111.560304 35.218387, -111.560321 35.218599, -111.561782 35.218733, -111.561798 35.218959, -111.561803 35.219025, -111.561844 35.219525, -111.561862 35.219742, -111.562526 35.219842, -111.562686 35.219912, -111.563511 35.22005, -111.563897 35.22011, -111.566141 35.220459, -111.566955 35.220597, -111.567008 35.220602, -111.566993 35.220641, -111.567027 35.220685, -111.567027 35.22074, -111.56694 35.22085, -111.566585 35.221059, -111.566525 35.221185, -111.566478 35.221427, -111.566384 35.22157, -111.566243 35.221724, -111.566076 35.221949, -111.565955 35.222054, -111.565768 35.222114, -111.56552 35.222169, -111.565319 35.222153, -111.565064 35.222054, -111.564957 35.221982, -111.564703 35.221724, -111.564448 35.22151, -111.564147 35.221323, -111.563986 35.221174, -111.563919 35.221053, -111.563832 35.220982, -111.563738 35.220971, -111.563651 35.220982, -111.563597 35.221026, -111.563557 35.221136, -111.563544 35.221279, -111.563591 35.221477, -111.563711 35.221702, -111.563825 35.2219, -111.563845 35.222015, -111.563825 35.222059, -111.563738 35.222098, -111.563618 35.222164, -111.563718 35.222252, -111.563731 35.222307, -111.563805 35.222521, -111.563892 35.222647, -111.564073 35.222763, -111.564549 35.222906, -111.56479 35.223038, -111.564944 35.223175, -111.565051 35.223389, -111.565064 35.223697, -111.565038 35.223912, -111.564984 35.224049, -111.564763 35.224203, -111.564602 35.224296, -111.564522 35.224384, -111.564448 35.224527, -111.564415 35.224626, -111.564274 35.224808, -111.564227 35.224901, -111.564207 35.225055, -111.564187 35.225143, -111.564013 35.225269, -111.563981 35.225318, -111.56374 35.225311, -111.563674 35.225417, -111.563525 35.225629, -111.563396 35.225799, -111.563291 35.225897, -111.563099 35.226008, -111.562927 35.226092, -111.56262 35.226198, -111.56252 35.226211, -111.562263 35.22623, -111.562295 35.226407, -111.562346 35.22669, -111.562406 35.226802, -111.562425 35.226836, -111.562521 35.226886, -111.562562 35.226915, -111.562707 35.226968, -111.562852 35.227003, -111.562899 35.227026, -111.562935 35.227085, -111.562998 35.227218, -111.563126 35.227514, -111.563218 35.227706, -111.563291 35.227801, -111.563356 35.227845, -111.56349 35.227874, -111.563627 35.227894, -111.564355 35.227864, -111.564333 35.228233, -111.564329 35.228731, -111.564338 35.228924, -111.564382 35.229042, -111.564401 35.229071, -111.564477 35.229107, -111.564538 35.229121, -111.564603 35.229127, -111.564619 35.229832, -111.564624 35.230536, -111.564624 35.230565, -111.564643 35.230748, -111.564662 35.230793, -111.564698 35.230831, -111.564724 35.230846, -111.564771 35.230851, -111.56541 35.230848, -111.566693 35.230831, -111.567268 35.230835, -111.567914 35.23084, -111.568219 35.230839, -111.568476 35.230837, -111.568792 35.230836, -111.568989 35.230835, -111.568995 35.231137, -111.568984 35.231452, -111.568676 35.231439, -111.568231 35.231352, -111.56797 35.231279, -111.567807 35.231259, -111.567652 35.231251, -111.567536 35.231252, -111.567417 35.231273, -111.567363 35.231303, -111.567327 35.231333, -111.56731 35.231416, -111.56731 35.231505, -111.567331 35.231983, -111.567377 35.232299, -111.567418 35.232676, -111.567458 35.232829, -111.567551 35.233122, -111.567634 35.233341, -111.567718 35.233653, -111.567753 35.234013, -111.567755 35.234307, -111.567732 35.234684, -111.567638 35.235197, -111.567627 35.235393, -111.567623 35.235472, -111.567631 35.235647, -111.567631 35.235659, -111.567658 35.235758, -111.56778 35.236024, -111.567873 35.236264, -111.568013 35.236731, -111.568083 35.23703, -111.568124 35.237238, -111.568125 35.237425, -111.568081 35.237671, -111.56806 35.237853, -111.568083 35.237946, -111.568116 35.238, -111.568156 35.238053, -111.568205 35.238093, -111.568247 35.238111, -111.568303 35.238124, -111.568514 35.238158, -111.568694 35.238167, -111.568705 35.238215, -111.568738 35.238265, -111.568765 35.238285, -111.568786 35.238301, -111.568823 35.238317, -111.568913 35.23833, -111.569305 35.238345, -111.569592 35.238341, -111.569904 35.238337, -111.570317 35.238345, -111.570365 35.238354, -111.570427 35.23838, -111.570184 35.238734, -111.570101 35.238884, -111.570004 35.238988, -111.569878 35.239108, -111.569748 35.239254, -111.56965 35.239351, -111.569505 35.239462, -111.569382 35.239566, -111.569326 35.239628, -111.569304 35.239781, -111.569292 35.239865, -111.564118 35.239884, -111.562754 35.239892, -111.562471 35.239891, -111.560252 35.239902, -111.556497 35.239967, -111.55573 35.239981, -111.55475 35.239999, -111.553799 35.240015, -111.553095 35.240028, -111.552366 35.240036, -111.54865 35.240077, -111.548019 35.24009, -111.545801 35.240112, -111.544692 35.240122, -111.542779 35.240141, -111.542746 35.240345, -111.542698 35.240431, -111.542613 35.240519, -111.542591 35.240542, -111.542431 35.240644, -111.542278 35.240705, -111.542211 35.240788, -111.54216 35.24093, -111.542159 35.24191, -111.542257 35.24209, -111.543239 35.242046, -111.543528 35.242039, -111.543877 35.242031, -111.544456 35.242018, -111.54566 35.241996, -111.546335 35.241988, -111.548126 35.241975, -111.548955 35.241992, -111.54961 35.241968, -111.550332 35.24194, -111.550592 35.241937, -111.550993 35.241932, -111.551644 35.241915, -111.552311 35.241895, -111.553535 35.241913, -111.553571 35.242223, -111.553576 35.242927, -111.553526 35.243304, -111.553515 35.243441, -111.553502 35.243601, -111.553513 35.244064, -111.553518 35.244269, -111.55356 35.24464, -111.553565 35.244683, -111.553553 35.245125, -111.553502 35.246146, -111.552808 35.246044, -111.551788 35.245893, -111.551356 35.245824, -111.551311 35.246498, -111.551274 35.247064, -111.551499 35.247261, -111.551805 35.24755, -111.551828 35.247572, -111.551862 35.247633, -111.551879 35.247745, -111.551869 35.248105, -111.551835 35.248605, -111.551836 35.248644, -111.551846 35.248999, -111.551857 35.249413, -111.551156 35.24942, -111.551105 35.250013, -111.551098 35.250141, -111.551088 35.250322, -111.551081 35.251002, -111.551076 35.25115, -111.551043 35.252016, -111.55104 35.252232, -111.55103 35.252967, -111.551017 35.253882, -111.551034 35.254951, -111.549547 35.254949, -111.548738 35.254937, -111.54843 35.254933, -111.547717 35.254937, -111.546927 35.254942, -111.54667 35.254942, -111.546154 35.254942, -111.545148 35.254941, -111.544344 35.254938, -111.543792 35.254937, -111.543267 35.254938, -111.542398 35.254939, -111.542255 35.254932, -111.542253 35.254845, -111.542239 35.25433, -111.542217 35.253913, -111.542213 35.253835, -111.542209 35.253168, -111.542205 35.252618, -111.542217 35.252321, -111.542247 35.25161, -111.542239 35.251459, -111.54217 35.251305, -111.542145 35.251258, -111.54212 35.251212, -111.542108 35.251082, -111.542115 35.250956, -111.54215 35.250514, -111.542102 35.250394, -111.5414 35.250357, -111.541243 35.250357, -111.541161 35.250344, -111.541085 35.250291, -111.540987 35.250238, -111.540868 35.250217, -111.540711 35.250213, -111.540516 35.250218, -111.540337 35.250232, -111.540175 35.250264, -111.540138 35.250376, -111.540147 35.250553, -111.540172 35.2507, -111.54019 35.250913, -111.54012 35.251056, -111.540012 35.251105, -111.539905 35.251111, -111.539851 35.251374, -111.539773 35.251689, -111.539706 35.251853, -111.539623 35.251981, -111.539548 35.252057, -111.539472 35.25212, -111.539464 35.252225, -111.539476 35.252334, -111.539473 35.252423, -111.539465 35.252711, -111.539461 35.253356, -111.539451 35.254355, -111.539454 35.254734, -111.539454 35.254782, -111.538855 35.254743, -111.538623 35.254728, -111.538131 35.254699, -111.537106 35.254646, -111.536789 35.254631, -111.536639 35.254619, -111.535996 35.254569, -111.535648 35.254551, -111.534821 35.254511, -111.533497 35.254425, -111.533497 35.254558, -111.533498 35.255387, -111.533508 35.257861, -111.533405 35.257901, -111.533296 35.257924, -111.533239 35.257921, -111.533175 35.257899, -111.533088 35.257863, -111.532986 35.257838, -111.532911 35.257842, -111.532847 35.257861, -111.532806 35.257916, -111.532784 35.258006, -111.532803 35.258108, -111.532876 35.258197, -111.532955 35.258228, -111.533068 35.258237, -111.533256 35.258214, -111.533509 35.258173, -111.533504 35.260142, -111.532424 35.260107, -111.53052 35.260037, -111.529174 35.259995, -111.528569 35.259976, -111.52632 35.259873, -111.524705 35.25982, -111.524705 35.26015, -111.524692 35.261526, -111.524689 35.261796, -111.523591 35.2613, -111.521045 35.259958, -111.519189 35.2594, -111.519109 35.258937, -111.519056 35.258887, -111.519077 35.257807, -111.518469 35.257808, -111.517888 35.257809, -111.51719 35.257822, -111.516922 35.257843, -111.516838 35.257846, -111.516832 35.25752, -111.516933 35.256277, -111.516326 35.255205, -111.510575 35.254814, -111.509245 35.253925, -111.508042 35.25325, -111.506188 35.252548, -111.505753 35.2524, -111.503175 35.251525, -111.503161 35.251759, -111.503118 35.252402, -111.503088 35.252546, -111.503033 35.252685, -111.502849 35.253142, -111.502652 35.253633, -111.502373 35.254392, -111.50244 35.254449, -111.502427 35.256172, -111.502421 35.256401, -111.502424 35.25727, -111.502426 35.257897, -111.502427 35.258072, -111.50241 35.25811, -111.50349 35.258048, -111.503533 35.258046, -111.5054 35.257926, -111.505776 35.257912, -111.505755 35.25887, -111.505743 35.260274, -111.505742 35.260411, -111.505718 35.261214, -111.505704 35.261663, -111.505703 35.261717, -111.505738 35.261755, -111.5058 35.261777, -111.505905 35.261789, -111.506886 35.261776, -111.506877 35.262632, -111.50686 35.26356, -111.506858 35.263623, -111.506848 35.26445, -111.506842 35.264956, -111.506838 35.265423, -111.506835 35.266341, -111.506831 35.26675, -111.506826 35.267222, -111.506823 35.26741, -111.506796 35.26793, -111.5068 35.268622, -111.50682 35.269339, -111.506855 35.269564, -111.506855 35.27072, -111.506859 35.271375, -111.506477 35.271383, -111.505674 35.27136, -111.505425 35.271354, -111.50483 35.271347, -111.503379 35.271359, -111.501954 35.271331, -111.501332 35.271331, -111.501261 35.271332, -111.500778 35.271331, -111.500172 35.271322, -111.500172 35.271282, -111.50018 35.270738, -111.500041 35.270728, -111.499774 35.27072, -111.49965 35.270723, -111.499364 35.270755, -111.499187 35.270781, -111.499161 35.271319, -111.498501 35.271319, -111.498111 35.271318, -111.498007 35.271318, -111.497778 35.271318, -111.496919 35.271311, -111.49522 35.271291, -111.494645 35.271297, -111.493541 35.271309, -111.493381 35.27131, -111.492374 35.271299, -111.491677 35.271291, -111.490801 35.271281, -111.490258 35.271285, -111.489038 35.271294, -111.48904 35.271425, -111.489066 35.273818, -111.489084 35.276266, -111.48909 35.27672, -111.489488 35.276749, -111.491349 35.276774, -111.491318 35.277395, -111.491305 35.280465, -111.491297 35.28313, -111.492855 35.283162, -111.493068 35.283165, -111.494475 35.283185, -111.494685 35.283188, -111.495667 35.283226, -111.495964 35.283231, -111.496734 35.283245, -111.49717 35.283253, -111.497437 35.283257, -111.497954 35.283276, -111.498244 35.283287, -111.499288 35.283295, -111.500328 35.283339, -111.500946 35.283378, -111.501042 35.28338, -111.501554 35.283394, -111.502126 35.283313, -111.502679 35.283326, -111.503795 35.283377, -111.506206 35.283423, -111.50644 35.283419, -111.506862 35.283413, -111.506864 35.283709, -111.506847 35.283962, -111.506814 35.284068, -111.50669 35.284221, -111.506586 35.284328, -111.506566 35.284384, -111.506558 35.284406, -111.506542 35.284451, -111.506526 35.284584, -111.506523 35.284949, -111.506533 35.285319, -111.506567 35.286192, -111.506603 35.287016, -111.506632 35.287553, -111.506647 35.287669, -111.506677 35.287909, -111.506748 35.288364, -111.506778 35.28858, -111.506801 35.289062, -111.506806 35.289261, -111.506267 35.289349, -111.506205 35.289368, -111.506176 35.289405, -111.506167 35.289459, -111.506177 35.289506, -111.5062 35.289533, -111.506291 35.289551, -111.506813 35.289528, -111.506815 35.289602, -111.506825 35.289933, -111.506483 35.289951, -111.506447 35.289961, -111.506423 35.289997, -111.506408 35.290079, -111.506412 35.290171, -111.506449 35.290204, -111.506837 35.290211, -111.506859 35.290506, -111.50566 35.290507, -111.505628 35.291005, -111.505575 35.29141, -111.506093 35.291424, -111.506607 35.291444, -111.506906 35.291406, -111.50893 35.291392, -111.509121 35.291391, -111.509896 35.291386, -111.51024 35.291379, -111.510246 35.292266, -111.511339 35.292241, -111.511338 35.291719, -111.511338 35.291358, -111.512244 35.291347, -111.513492 35.291352, -111.513482 35.290935, -111.513449 35.289678, -111.513446 35.289611, -111.513444 35.289554, -111.513434 35.289331, -111.513514 35.289054, -111.513513 35.288943, -111.51349 35.288799, -111.513462 35.288738, -111.513419 35.288579, -111.513405 35.288108, -111.513399 35.287919, -111.513394 35.287777, -111.51381 35.28778, -111.515582 35.287783, -111.51673 35.287774, -111.517306 35.287753, -111.517636 35.287737, -111.51767 35.287731, -111.517791 35.28771, -111.517891 35.287679, -111.518166 35.287742, -111.518354 35.287771, -111.518537 35.287778, -111.518999 35.287746, -111.519025 35.287743, -111.519323 35.287711, -111.519758 35.287778, -111.519781 35.287739, -111.519711 35.287585, -111.519546 35.287455, -111.519344 35.287313, -111.519247 35.287197, -111.519191 35.287032, -111.519176 35.286852, -111.519142 35.286579, -111.519054 35.286358, -111.518875 35.285988, -111.51855 35.285801, -111.518132 35.285535, -111.517918 35.285399, -111.516554 35.284556, -111.515844 35.284119, -111.515659 35.284058, -111.515516 35.284046, -111.514883 35.284064, -111.513477 35.284092, -111.513316 35.284108, -111.51331 35.283986, -111.513307 35.282835, -111.513834 35.282834, -111.51434 35.282833, -111.514345 35.282739, -111.514769 35.282747, -111.514763 35.282841, -111.514895 35.282844, -111.515127 35.282876, -111.515223 35.282898, -111.515312 35.282918, -111.515386 35.282941, -111.515519 35.282984, -111.517464 35.284151, -111.518813 35.284939, -111.519004 35.285014, -111.519128 35.285048, -111.519262 35.285051, -111.519396 35.285034, -111.519571 35.28497, -111.51971 35.284906, -111.519782 35.284834, -111.519812 35.284804, -111.519899 35.284689, -111.51995 35.284575, -111.51997 35.284448, -111.519922 35.281857, -111.519915 35.281567, -111.519929 35.28139, -111.519954 35.281187, -111.520029 35.280908, -111.52046 35.278946, -111.520615 35.278282, -111.520628 35.278181, -111.52062 35.278086, -111.52061 35.278068, -111.520559 35.277981, -111.520463 35.277886, -111.520362 35.277828, -111.52025 35.277785, -111.52015 35.277763, -111.520042 35.277767, -111.519894 35.2778, -111.519371 35.277884, -111.518436 35.278065, -111.517816 35.278166, -111.517481 35.278191, -111.515677 35.278284, -111.515662 35.277743, -111.515667 35.276724, -111.516384 35.276728, -111.517205 35.276734, -111.51797 35.276742, -111.519248 35.276751, -111.519429 35.27674, -111.519636 35.276691, -111.519766 35.27662, -111.519882 35.276505, -111.519932 35.276412, -111.519967 35.276346, -111.519998 35.276238, -111.520014 35.275987, -111.520017 35.275497, -111.520022 35.274749, -111.520031 35.273248, -111.520043 35.27067, -111.520054 35.26957, -111.518978 35.269565, -111.517139 35.269573, -111.515466 35.269572, -111.515476 35.26844, -111.515484 35.267751, -111.515481 35.266325, -111.51548 35.265929, -111.515483 35.265792, -111.515144 35.265243, -111.514927 35.264907, -111.514844 35.264816, -111.515103 35.264597, -111.515425 35.264346, -111.515469 35.264304, -111.515579 35.264389, -111.515868 35.264506, -111.516193 35.26456, -111.517382 35.264548, -111.517616 35.264517, -111.518088 35.264393, -111.518412 35.264314, -111.5187 35.264283, -111.519084 35.264287, -111.519094 35.264639, -111.519215 35.264668, -111.519306 35.264671, -111.519365 35.264655, -111.519423 35.264601, -111.519524 35.264481, -111.5196 35.264292, -111.520088 35.264296, -111.522075 35.264291, -111.522283 35.264293, -111.522294 35.264506, -111.52232 35.264638, -111.522434 35.26468, -111.52261 35.264594, -111.522712 35.264479, -111.522773 35.264297, -111.52357 35.264303, -111.524658 35.264321, -111.524661 35.264988, -111.524662 35.265499, -111.524657 35.266135, -111.52463 35.268173, -111.524613 35.269303, -111.524614 35.269564, -111.524863 35.269565, -111.525662 35.269556, -111.526315 35.269549, -111.526752 35.269552, -111.527705 35.269559, -111.5281 35.269575, -111.52847 35.269611, -111.528565 35.269614, -111.528877 35.269625, -111.530101 35.269554, -111.531137 35.269547, -111.531899 35.26954, -111.533429 35.269569, -111.5339 35.269578, -111.534311 35.269585, -111.535144 35.269595, -111.537013 35.269599, -111.538948 35.269617, -111.539751 35.269629, -111.540474 35.26964, -111.542288 35.269639, -111.543676 35.269635, -111.543675 35.269908, -111.544398 35.269894, -111.54461 35.269897, -111.544667 35.269923, -111.544672 35.269965, -111.544662 35.270054, -111.544617 35.270245, -111.544465 35.270674, -111.544897 35.270842, -111.544513 35.271669, -111.544189 35.272369, -111.543919 35.272946, -111.543787 35.27329, -111.543733 35.273444, -111.543634 35.273724, -111.543487 35.274296, -111.54343 35.274545, -111.543388 35.274729, -111.543371 35.274853, -111.543299 35.275361, -111.5432 35.275984, -111.543052 35.277041, -111.542953 35.2777, -111.545937 35.278386, -111.546235 35.278419, -111.546605 35.278454, -111.547223 35.278479, -111.548007 35.278482, -111.549115 35.27844, -111.549793 35.27844, -111.550246 35.278478, -111.550551 35.278526, -111.55071 35.278538, -111.551147 35.27856, -111.552018 35.27863, -111.55238 35.278666, -111.552847 35.27867, -111.553041 35.278669, -111.553567 35.278667, -111.554675 35.278674, -111.555475 35.278671, -111.553948 35.276694, -111.554282 35.276753, -111.554383 35.27673, -111.554392 35.275988, -111.554393 35.275893, -111.5544 35.275275, -111.554395 35.275062, -111.554389 35.27482, -111.554324 35.274676, -111.554262 35.274696, -111.554023 35.274697, -111.552733 35.274685, -111.552229 35.27468, -111.551646 35.274674, -111.551234 35.274674, -111.55029 35.274675, -111.55019 35.274675, -111.550142 35.274675, -111.550076 35.274658, -111.550027 35.274573, -111.55003 35.274559, -111.550026 35.27426, -111.550011 35.273095, -111.550009 35.272783, -111.549206 35.27278, -111.548475 35.272779, -111.547755 35.272779, -111.547203 35.272779, -111.5471 35.272759, -111.547038 35.272695, -111.547029 35.272621, -111.547069 35.272492, -111.54713 35.272362, -111.547529 35.271513, -111.547723 35.271085, -111.547907 35.270681, -111.547987 35.270504, -111.548101 35.270227, -111.548123 35.270142, -111.548133 35.270101, -111.54815 35.26988, -111.548161 35.269586, -111.548157 35.269333, -111.548149 35.268877, -111.548145 35.268616, -111.547819 35.268604, -111.547568 35.268613, -111.547296 35.268622, -111.546677 35.268643, -111.546331 35.268662, -111.546204 35.268669, -111.546101 35.268651, -111.545942 35.268597, -111.546096 35.268268, -111.546402 35.267605, -111.546691 35.266981, -111.546796 35.266754, -111.547352 35.265553, -111.547582 35.265058, -111.547593 35.265035, -111.547801 35.264583, -111.548188 35.263709, -111.548758 35.262535, -111.548764 35.262523, -111.548958 35.262196, -111.549239 35.261746, -111.549334 35.261623, -111.549506 35.261401, -111.549854 35.261001, -111.550228 35.26056, -111.550772 35.259946, -111.551244 35.259465, -111.551681 35.258984, -111.551894 35.258739, -111.552188 35.258401, -111.552983 35.257537, -111.553501 35.256992, -111.553861 35.256601, -111.55461 35.255783, -111.554813 35.255555, -111.55533 35.254974, -111.555825 35.254434, -111.556049 35.254182, -111.556627 35.253544, -111.556854 35.253294, -111.557381 35.252716, -111.557553 35.252527, -111.557947 35.252077, -111.558748 35.251163, -111.559105 35.25076, -111.559764 35.25002, -111.559859 35.249945, -111.55994 35.249864, -111.560233 35.249572, -111.560856 35.248951, -111.560921 35.248886, -111.561936 35.247949, -111.562523 35.247407, -111.563145 35.246817, -111.563914 35.246122, -111.564602 35.24547, -111.564844 35.245247, -111.566972 35.243287, -111.567295 35.242985, -111.568088 35.242242, -111.568247 35.242094, -111.568401 35.242194, -111.56854 35.242314, -111.568648 35.242432, -111.568741 35.242495, -111.568864 35.242557, -111.568965 35.242589, -111.569025 35.242589, -111.569202 35.242523, -111.569333 35.242414, -111.56945 35.242298, -111.56954 35.242196, -111.56961 35.242083, -111.569953 35.241704, -111.570106 35.241737, -111.570404 35.241876, -111.570429 35.241836, -111.570458 35.24174, -111.570456 35.241602, -111.570451 35.241504, -111.57044 35.241454, -111.570426 35.241391, -111.570382 35.241288, -111.570331 35.241216, -111.570272 35.241199, -111.570102 35.241168, -111.569715 35.241141, -111.569577 35.241055, -111.56946 35.240982, -111.569759 35.240711, -111.570498 35.240044, -111.570699 35.239863, -111.570824 35.239751, -111.57151 35.239801, -111.575876 35.239713, -111.577844 35.239684, -111.577878 35.239544, -111.577892 35.239485, -111.577902 35.238825, -111.577938 35.23648, -111.577942 35.236195, -111.577958 35.235147, -111.578059 35.235175, -111.578169 35.235179, -111.5783 35.235171, -111.578435 35.235168, -111.578514 35.235158, -111.578627 35.235133, -111.578676 35.235116, -111.578746 35.235091, -111.57882 35.235059, -111.57888 35.235022, -111.5789 35.235004, -111.578915 35.234943, -111.578972 35.234203, -111.578951 35.234098, -111.578936 35.234069, -111.578906 35.234032, -111.578867 35.234008, -111.578792 35.233989, -111.578699 35.233974, -111.578643 35.233965, -111.57844 35.233957, -111.578241 35.233962, -111.57819 35.233968, -111.578065 35.233983, -111.577988 35.233993, -111.577976 35.233993, -111.577744 35.234001, -111.577074 35.233919, -111.576943 35.233941, -111.576841 35.233971, -111.576674 35.234001, -111.576615 35.234023, -111.576589 35.234071, -111.576488 35.234023, -111.576412 35.233969, -111.576289 35.233738, -111.576251 35.233592, -111.576117 35.233451, -111.576027 35.233381, -111.576368 35.232894, -111.576726 35.232382, -111.577386 35.231438, -111.57769 35.23136, -111.577754 35.231344, -111.577914 35.231258, -111.578033 35.23097, -111.578073 35.230969, -111.578071 35.230881, -111.578054 35.230492, -111.578105 35.230419, -111.578153 35.230351, -111.578365 35.23005, -111.578725 35.229541, -111.57906 35.229052, -111.579973 35.229129, -111.580577 35.229154, -111.58068 35.229045, -111.58081 35.228983, -111.580798 35.22896, -111.580751 35.228832, -111.580735 35.228658, -111.580733 35.228636, -111.580779 35.228294, -111.580876 35.227547, -111.580917 35.227298, -111.580944 35.227183, -111.58103 35.227211, -111.581086 35.227234, -111.581199 35.227252, -111.581397 35.227259, -111.581528 35.227263, -111.582203 35.227298, -111.58264 35.227294, -111.5828 35.227284, -111.582963 35.227268, -111.583118 35.227243, -111.583278 35.227225, -111.583413 35.227204, -111.583525 35.227193, -111.58376 35.227187, -111.583976 35.227186, -111.584133 35.227162, -111.584238 35.227146, -111.584529 35.227107, -111.584683 35.227083, -111.585129 35.227014, -111.585781 35.22695, -111.585876 35.226927, -111.585878 35.227002, -111.586808 35.226969, -111.586827 35.227112, -111.586897 35.227112, -111.586895 35.227209, -111.586941 35.227207, -111.587277 35.227194, -111.587264 35.227138, -111.587021 35.226075, -111.587463 35.226068, -111.587668 35.22607, -111.587854 35.226071, -111.588056 35.226072, -111.588438 35.226075, -111.588626 35.226163, -111.588867 35.226223, -111.589019 35.226241, -111.589428 35.226278, -111.589431 35.226239, -111.589452 35.226148, -111.589485 35.226054, -111.589561 35.226052, -111.590114 35.226195, -111.590797 35.226261, -111.591682 35.226228, -111.592475 35.226151, -111.593426 35.225962, -111.594721 35.225648, -111.595271 35.225568, -111.595529 35.225534, -111.596483 35.225526, -111.597195 35.225666, -111.597702 35.225792, -111.597878 35.225833, -111.598505 35.226067, -111.599011 35.22622, -111.599067 35.226212, -111.599509 35.22615, -111.602674 35.225887, -111.60326 35.225839, -111.60437 35.225745, -111.605685 35.225612, -111.606528 35.225528, -111.607538 35.225424, -111.608163 35.225362, -111.60884 35.225315, -111.608934 35.225307, -111.609461 35.225263, -111.609836 35.225232, -111.610872 35.225105, -111.611869 35.224967, -111.612831 35.224895, -111.613617 35.224861, -111.613895 35.224848, -111.614495 35.224754, -111.614877 35.224695, -111.615653 35.22466, -111.616701 35.224765, -111.617297 35.224885, -111.617933 35.225122, -111.618408 35.225361, -111.618515 35.225415, -111.619003 35.22577, -111.619455 35.226136, -111.620014 35.226735, -111.620744 35.227438, -111.621741 35.228512, -111.622663 35.229374, -111.623828 35.230398, -111.62445 35.230975, -111.624739 35.231208, -111.624821 35.231275, -111.625216 35.231727, -111.625264 35.231781, -111.625781 35.232535, -111.625811 35.232579, -111.625919 35.232733, -111.626159 35.233065, -111.626424 35.233381, -111.626883 35.233918, -111.627225 35.234372, -111.627607 35.234857, -111.627898 35.2352, -111.62816 35.235491, -111.628371 35.235818, -111.6286 35.236139, -111.62891 35.236448, -111.629103 35.236772, -111.629368 35.237155, -111.629675 35.237505, -111.629895 35.237811, -111.630061 35.23808, -111.630119 35.238225, -111.630265 35.238566, -111.63049 35.238921, -111.630691 35.23922, -111.630738 35.239262, -111.630925 35.240432, -111.63267 35.240428, -111.632636 35.240466, -111.63257 35.240549, -111.632423 35.240711, -111.632379 35.240784, -111.63239 35.240845, -111.632443 35.240897, -111.63248 35.24097, -111.632549 35.241146, -111.632539 35.241289, -111.63251 35.24138, -111.632426 35.241542, -111.632315 35.241704, -111.632294 35.241774, -111.632301 35.241826, -111.632328 35.241884, -111.632358 35.241926, -111.632403 35.241966, -111.632455 35.241999, -111.632511 35.242014, -111.632592 35.242016, -111.632675 35.24201, -111.632711 35.242007, -111.633168 35.241936, -111.633113 35.242026, -111.632837 35.242446, -111.632517 35.24286, -111.632093 35.243423, -111.632566 35.243468, -111.633041 35.243457, -111.633647 35.243443, -111.63428 35.243459, -111.634823 35.243491, -111.63494 35.243498, -111.63536 35.243464, -111.635975 35.243413, -111.637223 35.243415, -111.638043 35.243417, -111.638866 35.243428, -111.639093 35.243163, -111.639476 35.242765, -111.639891 35.242367, -111.640099 35.242167, -111.640279 35.242031, -111.640553 35.241823, -111.640716 35.24173, -111.640918 35.241616, -111.641274 35.241465, -111.641535 35.241382, -111.641951 35.241289, -111.642215 35.241262, -111.642418 35.241219, -111.643068 35.24108, -111.643397 35.240992, -111.643519 35.240968, -111.643729 35.240956, -111.643826 35.240959, -111.643267 35.240557, -111.643201 35.240451, -111.643128 35.24032)))"} -{"geo_id":"37162","urban_area_code":"37162","name":"Harrisonburg, VA","lsad_name":"Harrisonburg, VA Urbanized Area","area_lsad_code":"75","mtfcc_feature_class_code":"G3500","type":"U","functional_status":"S","area_land_meters":84233479,"area_water_meters":256962,"internal_point_lon":-78.8861996,"internal_point_lat":38.4238197,"internal_point_geom":"POINT(-78.8861996 38.4238197)","urban_area_geom":"MULTIPOLYGON(((-78.882172 38.39397, -78.882033 38.393678, -78.881851 38.393345, -78.88154 38.392773, -78.881352 38.393011, -78.881417 38.39338, -78.881815 38.393807, -78.882172 38.39397)), ((-78.81424 38.450237, -78.814509 38.450385, -78.814515 38.45029, -78.814544 38.449781, -78.814505 38.449144, -78.814418 38.449038, -78.814392 38.448995, -78.814325 38.448935, -78.814202 38.448843, -78.814144 38.448813, -78.813992 38.448747, -78.81386 38.448701, -78.813588 38.448599, -78.813418 38.448548, -78.813258 38.448491, -78.813041 38.448424, -78.812759 38.44831, -78.812504 38.448213, -78.81229 38.448133, -78.812183 38.448216, -78.812103 38.448292, -78.812034 38.448373, -78.811903 38.448566, -78.811895 38.448601, -78.811791 38.449166, -78.81276 38.449576, -78.813494 38.449906, -78.81424 38.450237)), ((-78.979656 38.379364, -78.979801 38.379224, -78.980079 38.378954, -78.98023 38.37905, -78.980365 38.379164, -78.980417 38.379223, -78.980514 38.379407, -78.980602 38.379538, -78.980644 38.379588, -78.980727 38.379665, -78.981121 38.379946, -78.98134 38.380084, -78.981327 38.380035, -78.981337 38.379994, -78.98137 38.379951, -78.98199 38.379488, -78.982166 38.379347, -78.982211 38.379291, -78.982244 38.379222, -78.982095 38.379167, -78.981904 38.379071, -78.981759 38.378985, -78.981341 38.378713, -78.981188 38.378609, -78.980787 38.378334, -78.980675 38.378417, -78.980374 38.378097, -78.980254 38.377994, -78.980139 38.377912, -78.980087 38.377889, -78.980029 38.37788, -78.979969 38.377886, -78.979915 38.377905, -78.979841 38.377955, -78.979771 38.37802, -78.979741 38.378061, -78.979719 38.378131, -78.979709 38.378242, -78.979683 38.378307, -78.979638 38.378359, -78.979477 38.378521, -78.979437 38.37855, -78.97939 38.378571, -78.979325 38.378587, -78.979265 38.378579, -78.97921 38.378557, -78.978978 38.378395, -78.978778 38.378235, -78.978702 38.378168, -78.978225 38.377813, -78.977783 38.377459, -78.977496 38.377215, -78.97721 38.376948, -78.977124 38.376883, -78.976979 38.3768, -78.976875 38.376756, -78.976459 38.37661, -78.976211 38.376527, -78.976024 38.376453, -78.975811 38.376348, -78.975594 38.376263, -78.97488 38.375984, -78.974345 38.375775, -78.974576 38.375908, -78.975097 38.376389, -78.975435 38.376599, -78.975538 38.376614, -78.97569 38.376717, -78.976605 38.376872, -78.976695 38.376939, -78.977023 38.377039, -78.977191 38.377226, -78.977148 38.37741, -78.977263 38.377671, -78.977762 38.378094, -78.977865 38.378109, -78.97792 38.378171, -78.978147 38.378257, -78.978675 38.378711, -78.97932 38.379264, -78.979559 38.379297, -78.979656 38.379364)), ((-78.948365 38.405768, -78.948556 38.405905, -78.948593 38.405949, -78.94984 38.404816, -78.95045 38.404275, -78.950731 38.404054, -78.951684 38.403329, -78.952396 38.402801, -78.953136 38.402294, -78.953379 38.402129, -78.953522 38.402034, -78.953866 38.401795, -78.954293 38.401472, -78.95447 38.401326, -78.954732 38.401113, -78.955111 38.400784, -78.955449 38.400468, -78.955694 38.400224, -78.95585 38.400062, -78.955991 38.399907, -78.95616 38.399722, -78.956293 38.399569, -78.956452 38.399385, -78.956679 38.399099, -78.956942 38.398752, -78.95749 38.397985, -78.957521 38.398, -78.957682 38.398094, -78.957718 38.398055, -78.958746 38.398454, -78.959663 38.39881, -78.960332 38.398602, -78.960673 38.398666, -78.961667 38.398842, -78.963306 38.396455, -78.963474 38.396213, -78.963725 38.396245, -78.965472 38.396472, -78.965663 38.396497, -78.967167 38.396692, -78.96786 38.396782, -78.967505 38.397464, -78.967136 38.398165, -78.966739 38.398922, -78.966699 38.399093, -78.967067 38.399179, -78.967165 38.399198, -78.967098 38.399232, -78.967029 38.399278, -78.966936 38.399359, -78.966872 38.399441, -78.966829 38.399522, -78.966817 38.399578, -78.966825 38.399634, -78.966865 38.399709, -78.966991 38.399876, -78.967024 38.399958, -78.967035 38.400027, -78.967035 38.400065, -78.967096 38.400066, -78.967146 38.400066, -78.967199 38.400069, -78.967838 38.400081, -78.968149 38.400089, -78.968919 38.400089, -78.969081 38.400081, -78.969829 38.400104, -78.969894 38.400106, -78.970098 38.400223, -78.970258 38.400315, -78.97074 38.400293, -78.97073 38.400193, -78.970742 38.400193, -78.972736 38.400247, -78.972747 38.399752, -78.972746 38.399301, -78.972751 38.398917, -78.972749 38.398632, -78.972564 38.398568, -78.971311 38.398175, -78.971171 38.398133, -78.971223 38.398011, -78.971406 38.397662, -78.971525 38.397425, -78.971635 38.397188, -78.971884 38.396672, -78.972083 38.39626, -78.97221 38.395996, -78.972359 38.395635, -78.972443 38.3955, -78.9751 38.39632, -78.977434 38.397104, -78.977559 38.396975, -78.977559 38.396947, -78.977615 38.396898, -78.977768 38.396645, -78.977789 38.396594, -78.977827 38.3965, -78.977873 38.396387, -78.978033 38.395393, -78.978046 38.39514, -78.978011 38.394816, -78.978088 38.394525, -78.978179 38.394388, -78.978318 38.394262, -78.978324 38.394212, -78.978332 38.394146, -78.978374 38.393998, -78.978437 38.393861, -78.978513 38.39374, -78.978583 38.393723, -78.978803 38.393677, -78.979078 38.393569, -78.979246 38.393459, -78.979325 38.393357, -78.979336 38.393344, -78.979406 38.393201, -78.979497 38.393036, -78.979838 38.392658, -78.979964 38.392575, -78.979999 38.39252, -78.980243 38.392289, -78.980299 38.392201, -78.980389 38.392114, -78.980465 38.391985, -78.980585 38.391782, -78.98071 38.39157, -78.980849 38.391405, -78.980984 38.391295, -78.981028 38.391055, -78.981648 38.390823, -78.981836 38.390753, -78.982529 38.390414, -78.982653 38.390354, -78.982733 38.390315, -78.982908 38.390229, -78.983345 38.390017, -78.983489 38.389949, -78.98426 38.389602, -78.984614 38.389419, -78.984998 38.389223, -78.985467 38.388981, -78.9859 38.388786, -78.986519 38.388478, -78.986836 38.388319, -78.986968 38.388253, -78.987429 38.387971, -78.987814 38.387734, -78.988037 38.387598, -78.988547 38.387226, -78.989033 38.386855, -78.989441 38.386674, -78.989729 38.386355, -78.989899 38.386167, -78.989916 38.386031, -78.989935 38.385876, -78.98995 38.385759, -78.989974 38.385559, -78.989838 38.385467, -78.989292 38.385113, -78.990003 38.385244, -78.989947 38.385182, -78.989215 38.384699, -78.988768 38.384501, -78.988222 38.384425, -78.988202 38.384411, -78.98807 38.384322, -78.987793 38.384245, -78.987474 38.384157, -78.987273 38.383966, -78.986854 38.383798, -78.98641 38.383437, -78.985588 38.383186, -78.985318 38.382985, -78.983704 38.382053, -78.983621 38.381959, -78.982814 38.381493, -78.98273 38.3814, -78.981889 38.380929, -78.981715 38.380768, -78.981415 38.380699, -78.98129 38.380627, -78.980983 38.380285, -78.979656 38.379364, -78.979371 38.379629, -78.979297 38.379698, -78.978339 38.37901, -78.978159 38.378883, -78.977919 38.378901, -78.977626 38.378929, -78.977596 38.378928, -78.977566 38.37893, -78.977042 38.379046, -78.976705 38.378934, -78.976451 38.378849, -78.976177 38.378616, -78.976049 38.378512, -78.975499 38.378324, -78.974688 38.377686, -78.97347 38.376882, -78.972561 38.376384, -78.971596 38.376037, -78.970926 38.375744, -78.97015 38.375166, -78.969999 38.375053, -78.969931 38.374824, -78.969903 38.374728, -78.969886 38.374671, -78.969901 38.374587, -78.96993 38.374434, -78.969971 38.374221, -78.969991 38.374114, -78.97002 38.374085, -78.97017 38.373936, -78.970423 38.373684, -78.970622 38.37334, -78.970746 38.373126, -78.970896 38.37274, -78.97096 38.372569, -78.970959 38.372557, -78.970916 38.372062, -78.970901 38.371884, -78.970895 38.371871, -78.970625 38.371286, -78.970547 38.371085, -78.97046 38.370861, -78.970455 38.370173, -78.970353 38.369776, -78.970326 38.369678, -78.970303 38.369589, -78.970287 38.369532, -78.970187 38.369506, -78.969574 38.369348, -78.96947 38.369321, -78.968921 38.369306, -78.968514 38.369295, -78.96811 38.369283, -78.96735 38.369372, -78.966739 38.36956, -78.966644 38.369589, -78.966445 38.3696, -78.965983 38.369624, -78.965887 38.369629, -78.965774 38.369635, -78.965067 38.369638, -78.964469 38.369641, -78.963926 38.369814, -78.963279 38.370587, -78.963242 38.370614, -78.962927 38.370846, -78.962674 38.371031, -78.962575 38.371104, -78.962385 38.371133, -78.962333 38.371141, -78.961981 38.371192, -78.961865 38.371152, -78.961395 38.371127, -78.961251 38.371119, -78.96112 38.371104, -78.961 38.371092, -78.960808 38.371061, -78.960476 38.370978, -78.960428 38.370966, -78.9598 38.370752, -78.959472 38.370664, -78.959151 38.370609, -78.958879 38.370543, -78.958314 38.370445, -78.958342 38.370703, -78.958405 38.370922, -78.958482 38.371136, -78.9586 38.371384, -78.958859 38.371817, -78.959187 38.372405, -78.95934 38.372718, -78.959368 38.372982, -78.95934 38.373141, -78.959271 38.373278, -78.959014 38.373545, -78.959006 38.373597, -78.958968 38.374116, -78.958951 38.374229, -78.958918 38.374335, -78.958836 38.374516, -78.958731 38.374731, -78.958639 38.374955, -78.958624 38.375008, -78.958608 38.375062, -78.958597 38.375154, -78.958595 38.375175, -78.958526 38.375188, -78.957992 38.375232, -78.957871 38.375243, -78.957563 38.375276, -78.956555 38.375381, -78.956395 38.375396, -78.956197 38.375401, -78.956056 38.375406, -78.956022 38.375408, -78.955869 38.37541, -78.955642 38.375395, -78.955509 38.375386, -78.955507 38.37542, -78.955504 38.375462, -78.955225 38.37543, -78.954967 38.375386, -78.954701 38.375326, -78.954421 38.375245, -78.954137 38.375148, -78.953949 38.375064, -78.953667 38.374925, -78.95345 38.374798, -78.953401 38.374852, -78.953375 38.374882, -78.953341 38.37492, -78.953313 38.374976, -78.953303 38.375035, -78.953312 38.375095, -78.953359 38.37517, -78.953451 38.375278, -78.953536 38.375379, -78.954102 38.376006, -78.954704 38.37669, -78.955005 38.377033, -78.95507 38.377106, -78.955423 38.377513, -78.95596 38.37814, -78.95622 38.378431, -78.956433 38.378681, -78.956569 38.37884, -78.956682 38.378967, -78.956733 38.379025, -78.956863 38.379178, -78.957128 38.379492, -78.958315 38.380844, -78.95887 38.381489, -78.959117 38.381767, -78.95916 38.381801, -78.95924 38.38185, -78.95933 38.381886, -78.959975 38.382031, -78.960751 38.382206, -78.960956 38.382257, -78.961339 38.382353, -78.961708 38.382446, -78.962046 38.382531, -78.962288 38.382593, -78.96248 38.382652, -78.962578 38.382688, -78.962698 38.382753, -78.962806 38.382829, -78.962894 38.382911, -78.963397 38.383442, -78.963566 38.383617, -78.963756 38.383814, -78.963877 38.383938, -78.964398 38.384484, -78.96368 38.385783, -78.963563 38.385682, -78.963109 38.385287, -78.96283 38.385037, -78.962484 38.385656, -78.95999 38.385746, -78.959739 38.385751, -78.959614 38.385758, -78.958233 38.38518, -78.957406 38.386816, -78.957247 38.386755, -78.956853 38.386601, -78.953575 38.385314, -78.953193 38.38546, -78.951957 38.385933, -78.95023 38.386118, -78.95005 38.386138, -78.949494 38.387621, -78.948741 38.38841, -78.949229 38.388726, -78.951008 38.389859, -78.95154 38.390213, -78.951615 38.390569, -78.951424 38.390599, -78.95128 38.390621, -78.951116 38.390646, -78.951016 38.390659, -78.951042 38.390718, -78.951718 38.393094, -78.951923 38.39385, -78.952144 38.394677, -78.95233 38.395371, -78.953037 38.395213, -78.954236 38.394945, -78.954279 38.3952, -78.954325 38.395466, -78.95437 38.39573, -78.954812 38.398345, -78.954533 38.398439, -78.954712 38.399495, -78.954727 38.39958, -78.954729 38.399591, -78.954747 38.399685, -78.954694 38.399708, -78.954536 38.399777, -78.954441 38.399828, -78.954358 38.399872, -78.954241 38.399948, -78.954156 38.400017, -78.954063 38.400111, -78.954002 38.400191, -78.953945 38.400293, -78.953905 38.400401, -78.953885 38.400514, -78.953884 38.400607, -78.953883 38.400642, -78.953925 38.400988, -78.953954 38.401146, -78.953975 38.40119, -78.953986 38.401215, -78.954036 38.401277, -78.954119 38.401342, -78.954143 38.401341, -78.954131 38.401351, -78.954013 38.401439, -78.953661 38.401702, -78.953368 38.401909, -78.953173 38.402041, -78.952955 38.402183, -78.952216 38.402666, -78.95189 38.402883, -78.951142 38.403414, -78.950386 38.403996, -78.949859 38.40443, -78.949332 38.404893, -78.94868 38.405483, -78.948365 38.405768)), ((-78.862659 38.404791, -78.862752 38.404743, -78.862817 38.404704, -78.865409 38.404838, -78.865293 38.404506, -78.865273 38.404455, -78.865248 38.404394, -78.865136 38.404267, -78.86506 38.404156, -78.864984 38.404039, -78.864954 38.403963, -78.864923 38.403867, -78.864927 38.403678, -78.864951 38.40355, -78.865061 38.403318, -78.865176 38.403197, -78.865281 38.403088, -78.865835 38.402731, -78.866158 38.402513, -78.866224 38.40247, -78.866396 38.402362, -78.864631 38.401479, -78.861757 38.400136, -78.860655 38.399282, -78.859519 38.398342, -78.859387 38.398233, -78.859174 38.398073, -78.859113 38.397945, -78.858868 38.397454, -78.858532 38.396764, -78.858371 38.396428, -78.85777 38.395192, -78.85762 38.394884, -78.857569 38.394778, -78.857443 38.394526, -78.857085 38.393812, -78.856855 38.393384, -78.856725 38.393167, -78.856492 38.39278, -78.8554 38.391082, -78.854981 38.390427, -78.854689 38.389969, -78.854425 38.389565, -78.8543 38.389382, -78.853973 38.388946, -78.85382 38.388766, -78.853563 38.388476, -78.853411 38.388319, -78.852917 38.387847, -78.852009 38.386993, -78.851972 38.386956, -78.851742 38.38673, -78.851626 38.386601, -78.851379 38.38629, -78.851259 38.386104, -78.85113 38.385861, -78.851006 38.385597, -78.850945 38.385445, -78.850875 38.385217, -78.850791 38.384843, -78.850458 38.383204, -78.850426 38.383043, -78.850376 38.382794, -78.850305 38.382445, -78.850149 38.381688, -78.850081 38.381366, -78.850021 38.381129, -78.849928 38.380792, -78.849751 38.38023, -78.849359 38.379091, -78.849121 38.378456, -78.84897 38.378089, -78.848702 38.377507, -78.84856 38.377226, -78.848354 38.376816, -78.848109 38.376329, -78.847487 38.375091, -78.846628 38.375895, -78.846253 38.376245, -78.845722 38.376778, -78.844522 38.377968, -78.843392 38.379095, -78.842994 38.379492, -78.842616 38.379871, -78.842462 38.380037, -78.84232 38.380218, -78.842261 38.380314, -78.842195 38.380457, -78.842142 38.380613, -78.84211 38.380761, -78.842109 38.380808, -78.842096 38.381431, -78.842079 38.381535, -78.842042 38.381636, -78.841999 38.381713, -78.841941 38.381773, -78.841868 38.381822, -78.841802 38.381853, -78.841699 38.381883, -78.840589 38.382078, -78.840355 38.382125, -78.840201 38.382171, -78.840055 38.382231, -78.839972 38.382279, -78.8399 38.382337, -78.839833 38.382419, -78.839767 38.382542, -78.839683 38.382803, -78.839652 38.382927, -78.839603 38.383128, -78.839515 38.383529, -78.839406 38.384022, -78.83927 38.384574, -78.839265 38.384597, -78.839255 38.384637, -78.839213 38.384898, -78.839167 38.385119, -78.83908 38.385541, -78.83905 38.38569, -78.839021 38.38584, -78.838787 38.387035, -78.838587 38.388032, -78.838565 38.38819, -78.83854 38.388375, -78.838275 38.38833, -78.837591 38.388238, -78.834968 38.387898, -78.834301 38.387812, -78.833289 38.387666, -78.833015 38.387612, -78.8328 38.387559, -78.832498 38.387467, -78.832068 38.387335, -78.831812 38.387248, -78.831529 38.387136, -78.831367 38.387063, -78.831114 38.386931, -78.830857 38.386778, -78.830643 38.386639, -78.83037 38.386458, -78.830098 38.386276, -78.830103 38.3858, -78.830125 38.384994, -78.830147 38.384167, -78.830001 38.384089, -78.829351 38.383755, -78.827036 38.382566, -78.824891 38.381429, -78.824872 38.38144, -78.8248 38.381505, -78.824741 38.381576, -78.824666 38.381736, -78.824638 38.38182, -78.824611 38.381907, -78.824586 38.381989, -78.824563 38.382062, -78.824523 38.382184, -78.824508 38.382233, -78.824474 38.382358, -78.824438 38.382424, -78.824381 38.382508, -78.824454 38.382518, -78.824532 38.382529, -78.824618 38.382543, -78.824707 38.382561, -78.824801 38.382584, -78.824897 38.382609, -78.825091 38.382665, -78.825294 38.382727, -78.825399 38.382759, -78.825505 38.382791, -78.825613 38.382824, -78.825721 38.382856, -78.825934 38.382923, -78.826039 38.382959, -78.826141 38.382994, -78.826241 38.383029, -78.826338 38.383064, -78.826434 38.383098, -78.826528 38.383131, -78.826623 38.383163, -78.82672 38.383194, -78.826815 38.383222, -78.826939 38.383252, -78.827008 38.383269, -78.827106 38.383287, -78.827205 38.383306, -78.827307 38.383325, -78.82733 38.38333, -78.82741 38.383347, -78.827514 38.383373, -78.827619 38.383406, -78.827722 38.383447, -78.827822 38.383497, -78.827918 38.383559, -78.828011 38.383627, -78.8281 38.383701, -78.828186 38.383779, -78.82827 38.383859, -78.828356 38.383938, -78.828444 38.384017, -78.82854 38.38409, -78.828641 38.384158, -78.828743 38.384225, -78.828842 38.38429, -78.828939 38.384354, -78.829031 38.384418, -78.829092 38.384461, -78.82912 38.384481, -78.829207 38.384544, -78.829291 38.384605, -78.829374 38.384664, -78.829447 38.38473, -78.829543 38.384891, -78.829563 38.384981, -78.829567 38.385073, -78.829537 38.385256, -78.829505 38.385336, -78.829464 38.385405, -78.829416 38.385466, -78.829372 38.385517, -78.829308 38.38558, -78.829269 38.385614, -78.829215 38.385662, -78.829074 38.385564, -78.828805 38.385382, -78.8285 38.385189, -78.828278 38.38507, -78.828113 38.384993, -78.82774 38.384836, -78.82723 38.384642, -78.826871 38.38452, -78.826598 38.38444, -78.826451 38.384401, -78.826148 38.384334, -78.825601 38.384229, -78.824757 38.384076, -78.824333 38.383991, -78.824097 38.383933, -78.823913 38.383878, -78.823697 38.383802, -78.823376 38.383678, -78.82325 38.384169, -78.823204 38.384278, -78.823142 38.384382, -78.823051 38.384494, -78.822589 38.38493, -78.82241 38.385086, -78.82225 38.385218, -78.822171 38.385278, -78.82174 38.385536, -78.821455 38.385685, -78.821088 38.385866, -78.820422 38.386205, -78.820066 38.386394, -78.819757 38.386564, -78.819218 38.38687, -78.819199 38.386797, -78.819192 38.386737, -78.819167 38.3867, -78.819137 38.386677, -78.819083 38.386649, -78.818225 38.386386, -78.818147 38.386374, -78.818067 38.386376, -78.818021 38.386384, -78.816248 38.386687, -78.816159 38.386645, -78.815866 38.386501, -78.815822 38.386568, -78.815692 38.386744, -78.8155 38.386982, -78.815584 38.387026, -78.815648 38.387059, -78.81573 38.387097, -78.815809 38.387121, -78.81574 38.387136, -78.815835 38.387135, -78.815961 38.387158, -78.816175 38.387221, -78.816247 38.387251, -78.816267 38.387259, -78.816321 38.387282, -78.816482 38.387358, -78.816655 38.387439, -78.816742 38.387482, -78.81667 38.387573, -78.816619 38.387635, -78.816563 38.387711, -78.816534 38.387752, -78.816506 38.387792, -78.816476 38.387831, -78.816444 38.387871, -78.816378 38.387955, -78.816345 38.387998, -78.816311 38.388042, -78.81624 38.388132, -78.816169 38.388221, -78.816134 38.388263, -78.816101 38.388304, -78.816041 38.388375, -78.815985 38.388434, -78.815842 38.388532, -78.815796 38.38866, -78.815748 38.388726, -78.815716 38.388767, -78.815603 38.388906, -78.81552 38.38901, -78.815478 38.389063, -78.815299 38.389281, -78.815257 38.389334, -78.815217 38.389383, -78.815152 38.389462, -78.815079 38.389508, -78.814993 38.389471, -78.814767 38.389359, -78.814698 38.389319, -78.814583 38.38922, -78.814541 38.389164, -78.814507 38.389106, -78.81448 38.38905, -78.814456 38.388996, -78.814431 38.388942, -78.8144 38.388891, -78.814363 38.388847, -78.814349 38.388834, -78.814285 38.388777, -78.814244 38.388734, -78.814139 38.388629, -78.81325 38.389705, -78.812928 38.390096, -78.812397 38.390746, -78.812126 38.391086, -78.811904 38.391348, -78.811743 38.391537, -78.811504 38.391803, -78.811249 38.392059, -78.811017 38.392273, -78.810701 38.39255, -78.810644 38.392601, -78.810614 38.392623, -78.810452 38.392746, -78.810206 38.392932, -78.810016 38.393056, -78.809717 38.393266, -78.809528 38.39343, -78.809386 38.393577, -78.809337 38.393643, -78.809585 38.393755, -78.80968 38.393813, -78.809763 38.393882, -78.80982 38.393948, -78.809905 38.394083, -78.809955 38.394138, -78.810002 38.394175, -78.810053 38.394194, -78.810097 38.394199, -78.81012 38.394198, -78.811615 38.394133, -78.812823 38.395583, -78.813392 38.39627, -78.813609 38.396552, -78.81395 38.397038, -78.81407 38.397214, -78.814289 38.397515, -78.814494 38.397782, -78.814813 38.398137, -78.815002 38.398332, -78.815286 38.398602, -78.81542 38.398722, -78.815542 38.398827, -78.81557 38.39885, -78.81582 38.399055, -78.815993 38.399189, -78.815809 38.399335, -78.815774 38.399381, -78.815753 38.399449, -78.815748 38.399533, -78.815765 38.399646, -78.815753 38.399694, -78.815711 38.399765, -78.815696 38.399841, -78.815266 38.400198, -78.815874 38.400699, -78.815947 38.400725, -78.816025 38.400738, -78.816105 38.400737, -78.816197 38.400717, -78.81624 38.4007, -78.816398 38.400599, -78.816494 38.400523, -78.816622 38.400398, -78.817037 38.399999, -78.817105 38.400052, -78.817923 38.400676, -78.818078 38.400795, -78.818449 38.401081, -78.819204 38.401656, -78.819337 38.401758, -78.820293 38.402491, -78.821256 38.403243, -78.821585 38.403495, -78.821977 38.403804, -78.822975 38.404585, -78.82435 38.405669, -78.82431 38.405708, -78.824268 38.405732, -78.824277 38.40577, -78.82429 38.405785, -78.82446 38.405924, -78.824792 38.406206, -78.824993 38.406371, -78.82512 38.406504, -78.825291 38.40671, -78.825401 38.406867, -78.825482 38.407001, -78.825543 38.407134, -78.825656 38.407497, -78.825679 38.407444, -78.825712 38.407393, -78.825751 38.407356, -78.825988 38.407229, -78.826249 38.407056, -78.826691 38.407319, -78.827044 38.407545, -78.827263 38.407697, -78.827641 38.407978, -78.827994 38.408247, -78.828152 38.408368, -78.828676 38.408767, -78.828804 38.408622, -78.829213 38.408916, -78.829586 38.409238, -78.829854 38.40947, -78.830157 38.409724, -78.830502 38.410036, -78.830846 38.410359, -78.831 38.410515, -78.831603 38.411133, -78.832021 38.411574, -78.8325 38.412101, -78.832687 38.412318, -78.832762 38.412405, -78.832806 38.412456, -78.832952 38.412626, -78.833081 38.412777, -78.834395 38.41431, -78.835311 38.415361, -78.835039 38.415489, -78.835205 38.415687, -78.835593 38.416136, -78.83579 38.416365, -78.836275 38.416943, -78.836335 38.41702, -78.836583 38.417343, -78.836688 38.417497, -78.836856 38.417778, -78.836688 38.417824, -78.834911 38.418326, -78.833458 38.418736, -78.832908 38.418889, -78.830577 38.419553, -78.829868 38.419751, -78.828435 38.420155, -78.827316 38.420474, -78.827031 38.420552, -78.82773 38.420937, -78.828619 38.421424, -78.830252 38.422601, -78.827439 38.429523, -78.827325 38.429505, -78.826485 38.429364, -78.826239 38.430018, -78.82568 38.431484, -78.825432 38.432136, -78.82432 38.435079, -78.824367 38.435169, -78.824391 38.435211, -78.824836 38.436041, -78.824993 38.436149, -78.82501 38.436163, -78.825511 38.436481, -78.825191 38.437945, -78.825163 38.438279, -78.825806 38.439543, -78.826693 38.440306, -78.82672 38.440324, -78.828839 38.441758, -78.829507 38.442557, -78.829864 38.44279, -78.830295 38.44307, -78.830844 38.443143, -78.830902 38.443161, -78.831887 38.443295, -78.831936 38.443302, -78.833313 38.4435, -78.83406 38.443581, -78.834083 38.443616, -78.833113 38.444234, -78.832196 38.445117, -78.830839 38.446479, -78.83047 38.446842, -78.829559 38.44756, -78.828449 38.448317, -78.826381 38.449633, -78.825257 38.450323, -78.824733 38.45067, -78.824323 38.45088, -78.823674 38.451127, -78.823189 38.451225, -78.822478 38.451227, -78.822124 38.451179, -78.820469 38.450733, -78.819932 38.450597, -78.819644 38.450592, -78.818916 38.450573, -78.818373 38.450619, -78.81805 38.450669, -78.817162 38.450766, -78.816621 38.450823, -78.816277 38.450828, -78.815858 38.450788, -78.815402 38.450698, -78.814509 38.450385, -78.814501 38.450528, -78.814502 38.450549, -78.814511 38.450806, -78.814512 38.451156, -78.814547 38.451806, -78.814602 38.452168, -78.814822 38.452152, -78.816102 38.452113, -78.816431 38.452099, -78.816759 38.452075, -78.817135 38.452025, -78.817507 38.451959, -78.818385 38.451762, -78.818645 38.451697, -78.818908 38.451642, -78.819241 38.451585, -78.819573 38.451543, -78.819838 38.451521, -78.819983 38.451514, -78.82017 38.451506, -78.820563 38.451512, -78.820889 38.451533, -78.82116 38.451565, -78.821495 38.45162, -78.822328 38.451808, -78.823195 38.452013, -78.823225 38.45195, -78.82327 38.4519, -78.823315 38.451866, -78.823395 38.451829, -78.823533 38.451792, -78.823643 38.451747, -78.823907 38.451598, -78.82408 38.451482, -78.824255 38.451352, -78.824387 38.451244, -78.824458 38.451171, -78.824585 38.451073, -78.82475 38.450956, -78.824963 38.450817, -78.825213 38.450663, -78.82528 38.450614, -78.825355 38.450551, -78.825579 38.450386, -78.825674 38.450327, -78.825764 38.450264, -78.825885 38.450163, -78.825954 38.450116, -78.826371 38.449855, -78.826823 38.449583, -78.826945 38.449515, -78.82751 38.449125, -78.827557 38.449083, -78.827696 38.448943, -78.827744 38.448903, -78.827875 38.448824, -78.828193 38.44866, -78.82839 38.448579, -78.82866 38.448457, -78.828883 38.44842, -78.828917 38.448408, -78.828942 38.44839, -78.828965 38.448367, -78.828988 38.448332, -78.829077 38.448147, -78.82913 38.448049, -78.829167 38.448007, -78.829418 38.447851, -78.829572 38.44777, -78.829692 38.447701, -78.829775 38.447646, -78.829872 38.447571, -78.829996 38.447442, -78.830073 38.447377, -78.830185 38.447294, -78.830554 38.446974, -78.830768 38.44678, -78.830793 38.446746, -78.830823 38.446694, -78.830845 38.446641, -78.830869 38.446516, -78.830876 38.446581, -78.830867 38.446662, -78.830839 38.44674, -78.830828 38.4468, -78.830835 38.44686, -78.83086 38.446917, -78.830959 38.447035, -78.831038 38.447113, -78.831116 38.447178, -78.831406 38.447382, -78.831455 38.447438, -78.831488 38.447501, -78.831544 38.447726, -78.831674 38.448134, -78.83174 38.448249, -78.831888 38.448329, -78.832082 38.448357, -78.83215 38.448358, -78.832594 38.448568, -78.832742 38.448568, -78.833215 38.448676, -78.833271 38.44871, -78.833286 38.448724, -78.833321 38.448765, -78.833299 38.449036, -78.833294 38.449099, -78.833283 38.449212, -78.833246 38.449676, -78.833258 38.44977, -78.833317 38.450284, -78.833341 38.450871, -78.833351 38.451455, -78.833686 38.452567, -78.833918 38.453352, -78.833968 38.453494, -78.833991 38.453551, -78.834579 38.45554, -78.835451 38.455516, -78.838248 38.455443, -78.839601 38.455141, -78.839648 38.455131, -78.840765 38.454881, -78.844267 38.454101, -78.844752 38.453993, -78.845567 38.453628, -78.84582 38.453516, -78.847656 38.452695, -78.847936 38.45257, -78.848589 38.452278, -78.84853 38.452536, -78.848449 38.452887, -78.848124 38.454311, -78.847818 38.455647, -78.847428 38.457349, -78.846892 38.459696, -78.849852 38.463116, -78.849838 38.463122, -78.849747 38.463159, -78.848665 38.463602, -78.848297 38.463749, -78.848038 38.463855, -78.847659 38.464008, -78.847571 38.464043, -78.846513 38.464505, -78.846232 38.464614, -78.846059 38.464684, -78.845276 38.465004, -78.844153 38.465471, -78.844134 38.465479, -78.843721 38.465651, -78.843396 38.465779, -78.843351 38.465795, -78.843035 38.465904, -78.842786 38.465976, -78.842712 38.465998, -78.842358 38.466082, -78.842332 38.466088, -78.842091 38.466129, -78.841779 38.466171, -78.841406 38.466205, -78.841031 38.466222, -78.840808 38.466225, -78.840298 38.466237, -78.839575 38.466242, -78.839362 38.466246, -78.838608 38.466273, -78.837886 38.466301, -78.836483 38.465579, -78.83655 38.466349, -78.836221 38.466377, -78.836002 38.466402, -78.835793 38.466431, -78.835627 38.46646, -78.835339 38.466523, -78.835054 38.466597, -78.834685 38.466713, -78.83448 38.466787, -78.83399 38.466994, -78.83378 38.467098, -78.833737 38.467119, -78.833494 38.467257, -78.833307 38.467374, -78.833183 38.467461, -78.833077 38.467536, -78.833028 38.467571, -78.8328 38.467755, -78.832429 38.468075, -78.831643 38.468783, -78.831674 38.468895, -78.831486 38.469059, -78.8312 38.469373, -78.831083 38.46948, -78.831008 38.469564, -78.830978 38.469607, -78.83109 38.469805, -78.831261 38.470085, -78.831327 38.470186, -78.831427 38.470336, -78.831504 38.470429, -78.831743 38.470785, -78.831862 38.470957, -78.832028 38.471196, -78.832118 38.471157, -78.832374 38.470986, -78.832613 38.470783, -78.833302 38.470232, -78.833335 38.470189, -78.833352 38.470141, -78.833353 38.470112, -78.833342 38.470063, -78.833314 38.470019, -78.833175 38.469902, -78.833022 38.469774, -78.832883 38.469658, -78.832711 38.469524, -78.83244 38.469311, -78.832169 38.469096, -78.832026 38.468983, -78.832003 38.468952, -78.831998 38.468916, -78.832012 38.468882, -78.832358 38.468552, -78.832509 38.468399, -78.832606 38.468425, -78.832668 38.468432, -78.83274 38.468429, -78.832783 38.468417, -78.832829 38.468389, -78.832993 38.46813, -78.833012 38.468091, -78.833044 38.468054, -78.833062 38.468041, -78.833138 38.468008, -78.833217 38.467991, -78.833333 38.467987, -78.833458 38.467998, -78.83358 38.468023, -78.834005 38.468164, -78.834112 38.468189, -78.834179 38.468204, -78.834286 38.468221, -78.834577 38.468258, -78.835472 38.468417, -78.836143 38.468552, -78.836867 38.468668, -78.837165 38.468707, -78.837424 38.468732, -78.837751 38.468756, -78.838004 38.468787, -78.838128 38.468816, -78.838408 38.468908, -78.838684 38.469004, -78.83893 38.469082, -78.83915 38.469133, -78.839355 38.469164, -78.839506 38.469178, -78.839707 38.469184, -78.839916 38.469179, -78.840156 38.469181, -78.840262 38.469196, -78.840537 38.469265, -78.840679 38.469289, -78.840785 38.469315, -78.84087 38.469341, -78.840914 38.469359, -78.841041 38.469429, -78.84128 38.469618, -78.84145 38.469769, -78.841482 38.46979, -78.841573 38.469827, -78.841674 38.469879, -78.841708 38.469904, -78.841796 38.469994, -78.84221 38.470471, -78.842345 38.470621, -78.84241 38.470702, -78.842451 38.470768, -78.842567 38.471007, -78.842608 38.471052, -78.84267 38.471099, -78.84275 38.471152, -78.842803 38.471207, -78.842819 38.471231, -78.842969 38.471548, -78.843034 38.471656, -78.843049 38.471689, -78.843149 38.471913, -78.843254 38.472115, -78.84333 38.472208, -78.843477 38.472341, -78.843579 38.472413, -78.843758 38.472515, -78.844085 38.472743, -78.844148 38.472797, -78.844216 38.472855, -78.844354 38.472993, -78.844475 38.473135, -78.844563 38.47311, -78.844642 38.473079, -78.844689 38.473057, -78.84477 38.473013, -78.844894 38.472919, -78.845094 38.472747, -78.845104 38.472586, -78.845154 38.472484, -78.845217 38.472386, -78.845303 38.472232, -78.84544 38.47203, -78.845476 38.471942, -78.845503 38.471858, -78.84554 38.471796, -78.845571 38.471763, -78.845654 38.471723, -78.845744 38.471691, -78.845937 38.471566, -78.845994 38.471515, -78.846043 38.471454, -78.846126 38.47133, -78.846436 38.470923, -78.84653 38.470781, -78.846543 38.470728, -78.846595 38.470427, -78.846626 38.470334, -78.845553 38.469802, -78.84522 38.469657, -78.845063 38.469589, -78.845051 38.469494, -78.845053 38.469402, -78.845079 38.469298, -78.845213 38.468945, -78.845284 38.468779, -78.845296 38.468763, -78.845415 38.468523, -78.845512 38.468307, -78.845638 38.467987, -78.845719 38.467798, -78.84575 38.467743, -78.845775 38.467709, -78.845984 38.467499, -78.846187 38.46728, -78.8467 38.466782, -78.847073 38.466442, -78.847203 38.466308, -78.847352 38.466163, -78.847588 38.46595, -78.847739 38.465795, -78.84793 38.465885, -78.847971 38.46591, -78.848016 38.465965, -78.848033 38.465992, -78.848049 38.466006, -78.848075 38.466014, -78.848319 38.465667, -78.848563 38.465326, -78.848901 38.464816, -78.850818 38.467071, -78.852452 38.468994, -78.852586 38.469155, -78.857805 38.475296, -78.858039 38.475237, -78.860431 38.475212, -78.861164 38.476558, -78.864193 38.477601, -78.865818 38.478168, -78.865899 38.478197, -78.865987 38.478228, -78.866354 38.478355, -78.86774 38.478832, -78.86788 38.478882, -78.866394 38.480386, -78.866975 38.480695, -78.866937 38.480761, -78.866925 38.480806, -78.866931 38.480839, -78.866954 38.480876, -78.866999 38.480909, -78.867256 38.481025, -78.867305 38.481043, -78.867412 38.481074, -78.867362 38.481176, -78.867152 38.481519, -78.866924 38.481946, -78.866844 38.482088, -78.866776 38.482222, -78.866699 38.482361, -78.866691 38.482412, -78.866696 38.482444, -78.866783 38.482432, -78.867139 38.482411, -78.867212 38.482411, -78.867395 38.48242, -78.86763 38.482451, -78.868057 38.482529, -78.86836 38.482576, -78.868485 38.482596, -78.868611 38.482615, -78.870419 38.482927, -78.870625 38.482934, -78.87053 38.48408, -78.870516 38.484257, -78.870501 38.484437, -78.870472 38.484798, -78.870443 38.485158, -78.870435 38.48525, -78.870427 38.485343, -78.870335 38.48643, -78.870317 38.48676, -78.870314 38.486934, -78.870318 38.487066, -78.87033 38.487241, -78.870347 38.487389, -78.870376 38.487563, -78.870438 38.487825, -78.87053 38.488097, -78.870577 38.488217, -78.870692 38.488461, -78.870789 38.488638, -78.871011 38.488991, -78.871147 38.489219, -78.871246 38.489338, -78.871361 38.489518, -78.872151 38.488999, -78.87219 38.488974, -78.872718 38.488617, -78.872988 38.488504, -78.873704 38.487987, -78.874301 38.487446, -78.874701 38.487084, -78.875108 38.486715, -78.875389 38.486446, -78.876061 38.485017, -78.878697 38.486229, -78.878872 38.486114, -78.879159 38.485924, -78.879308 38.485803, -78.879417 38.485698, -78.879922 38.485272, -78.880217 38.484915, -78.880532 38.484552, -78.880746 38.484293, -78.880963 38.484044, -78.881361 38.483612, -78.881632 38.483337, -78.881868 38.483116, -78.881981 38.482998, -78.882118 38.482829, -78.882371 38.482494, -78.882536 38.482291, -78.883081 38.481587, -78.883356 38.481222, -78.883495 38.481072, -78.883603 38.480976, -78.883926 38.480715, -78.884004 38.480652, -78.884121 38.480537, -78.884171 38.480478, -78.884232 38.480407, -78.884409 38.480177, -78.884482 38.480103, -78.884552 38.480052, -78.884651 38.479998, -78.884838 38.479928, -78.884986 38.479881, -78.885355 38.479749, -78.885467 38.479704, -78.8858 38.479568, -78.885923 38.479527, -78.885967 38.479518, -78.886066 38.479497, -78.886213 38.479482, -78.886482 38.479477, -78.886759 38.479477, -78.886932 38.479464, -78.887465 38.479411, -78.887637 38.479409, -78.887786 38.479421, -78.887903 38.47944, -78.888704 38.479625, -78.889359 38.479792, -78.889737 38.479883, -78.889861 38.479566, -78.889894 38.479466, -78.889956 38.479273, -78.890004 38.479084, -78.890044 38.478856, -78.890073 38.478547, -78.890076 38.478515, -78.890107 38.478001, -78.890133 38.477578, -78.890161 38.477218, -78.890237 38.476724, -78.890299 38.476431, -78.890361 38.476196, -78.890411 38.476009, -78.890429 38.475894, -78.890427 38.475799, -78.890406 38.475679, -78.890361 38.475538, -78.89035 38.475506, -78.889988 38.474671, -78.88995 38.474574, -78.889711 38.473971, -78.889562 38.473537, -78.889828 38.473277, -78.889746 38.473236, -78.889587 38.473146, -78.889388 38.473034, -78.889187 38.47291, -78.888814 38.472665, -78.888663 38.472574, -78.888466 38.472466, -78.888399 38.472429, -78.888176 38.47232, -78.887358 38.471962, -78.887022 38.471811, -78.886859 38.471726, -78.886804 38.471695, -78.886603 38.471568, -78.886513 38.471504, -78.886036 38.47112, -78.88585 38.470999, -78.885702 38.470919, -78.88521 38.47069, -78.884961 38.470565, -78.884836 38.470498, -78.884748 38.470451, -78.884392 38.470247, -78.884097 38.47007, -78.883822 38.469904, -78.883498 38.469713, -78.883335 38.469615, -78.883873 38.468748, -78.885652 38.465891, -78.886154 38.465084, -78.886605 38.463934, -78.886667 38.463778, -78.886776 38.46338, -78.888709 38.456322, -78.888823 38.455905, -78.888876 38.455716, -78.888973 38.455357, -78.890082 38.455857, -78.890406 38.455933, -78.890994 38.456072, -78.893038 38.456553, -78.894366 38.456865, -78.894792 38.456966, -78.895237 38.457066, -78.895569 38.457148, -78.897253 38.457847, -78.89808 38.458183, -78.900589 38.459217, -78.902113 38.457156, -78.902371 38.456802, -78.902659 38.456417, -78.902763 38.456276, -78.902815 38.456274, -78.903027 38.456266, -78.903649 38.456181, -78.903867 38.456165, -78.903963 38.456286, -78.904125 38.456521, -78.904353 38.456824, -78.904404 38.456918, -78.904437 38.457017, -78.904464 38.457225, -78.904476 38.457372, -78.904529 38.45783, -78.904593 38.458269, -78.904649 38.458795, -78.904692 38.459056, -78.90474 38.459441, -78.904807 38.459792, -78.904803 38.459828, -78.904792 38.459861, -78.904773 38.45989, -78.904714 38.459944, -78.904762 38.459999, -78.904792 38.460023, -78.90484 38.460042, -78.904905 38.46005, -78.904969 38.46007, -78.905028 38.46011, -78.905044 38.460127, -78.905058 38.460161, -78.905057 38.46019, -78.90504 38.460224, -78.904881 38.460352, -78.904826 38.460413, -78.904787 38.46048, -78.904691 38.460715, -78.904684 38.460777, -78.904692 38.460826, -78.904711 38.460874, -78.904787 38.460967, -78.904888 38.46106, -78.904926 38.461106, -78.904946 38.461145, -78.904954 38.461178, -78.904956 38.461314, -78.90494 38.461439, -78.904921 38.461824, -78.904885 38.461967, -78.904869 38.462055, -78.904859 38.462174, -78.904875 38.462688, -78.904885 38.46276, -78.904908 38.46283, -78.905173 38.463304, -78.90523 38.463373, -78.905472 38.463582, -78.905531 38.463659, -78.905574 38.463699, -78.90567 38.463737, -78.906208 38.463915, -78.906408 38.463991, -78.906525 38.464053, -78.90661 38.464112, -78.90725 38.464741, -78.907412 38.464918, -78.907489 38.465019, -78.90759 38.465204, -78.907665 38.46533, -78.907753 38.465451, -78.907879 38.465594, -78.908187 38.465573, -78.908579 38.465552, -78.908727 38.46555, -78.909265 38.465541, -78.909375 38.465546, -78.909414 38.465549, -78.909494 38.465557, -78.909903 38.465616, -78.910343 38.465688, -78.910463 38.465712, -78.910778 38.465774, -78.911112 38.465833, -78.911841 38.465986, -78.912107 38.466029, -78.912327 38.466051, -78.913111 38.466082, -78.913436 38.46609, -78.913925 38.466098, -78.914228 38.466096, -78.914234 38.466015, -78.914245 38.465985, -78.914278 38.465938, -78.914306 38.465917, -78.914327 38.465901, -78.914416 38.465861, -78.914483 38.465822, -78.914538 38.465776, -78.914578 38.465726, -78.914609 38.465672, -78.914634 38.465598, -78.914648 38.46551, -78.914643 38.465389, -78.914629 38.465261, -78.914633 38.465191, -78.914669 38.465048, -78.914683 38.464937, -78.914678 38.464863, -78.91465 38.464752, -78.914628 38.464694, -78.914516 38.464443, -78.914476 38.464329, -78.914462 38.464263, -78.914472 38.464232, -78.914494 38.464193, -78.914615 38.464053, -78.914755 38.463872, -78.914836 38.4638, -78.914917 38.463751, -78.915008 38.463715, -78.915082 38.463707, -78.91517 38.463717, -78.915496 38.463826, -78.915548 38.463837, -78.915616 38.463838, -78.915704 38.463816, -78.9158 38.463771, -78.915867 38.46372, -78.915929 38.463643, -78.915975 38.46356, -78.916004 38.463471, -78.916013 38.463389, -78.916034 38.463311, -78.916063 38.463252, -78.91626 38.462995, -78.91638 38.462851, -78.916955 38.463178, -78.91758 38.463532, -78.918471 38.464044, -78.918901 38.4643, -78.919137 38.464463, -78.919204 38.464531, -78.919254 38.464607, -78.919276 38.464657, -78.919291 38.464728, -78.919295 38.464819, -78.919284 38.464895, -78.919232 38.465077, -78.919106 38.465407, -78.918877 38.46593, -78.918519 38.466716, -78.918488 38.466825, -78.918481 38.466887, -78.918487 38.466974, -78.918496 38.467016, -78.918532 38.467107, -78.918606 38.467208, -78.918653 38.467257, -78.918694 38.4673, -78.918751 38.46735, -78.91919 38.467735, -78.919934 38.468379, -78.920215 38.468636, -78.920566 38.468966, -78.92118 38.469561, -78.921291 38.469485, -78.921609 38.469193, -78.921684 38.469145, -78.921746 38.469121, -78.921843 38.469095, -78.921945 38.469082, -78.922307 38.469063, -78.922399 38.469046, -78.922486 38.469016, -78.922545 38.468984, -78.922582 38.468942, -78.922595 38.4689, -78.922594 38.468876, -78.922628 38.468871, -78.922669 38.468857, -78.922704 38.468835, -78.922953 38.46858, -78.92298 38.468562, -78.923022 38.468552, -78.923235 38.468568, -78.923314 38.468559, -78.923363 38.468543, -78.923462 38.468485, -78.923518 38.468438, -78.923806 38.468119, -78.924101 38.467767, -78.924165 38.467678, -78.924213 38.467583, -78.924244 38.467484, -78.924472 38.466106, -78.92458 38.465522, -78.924638 38.465264, -78.924743 38.464893, -78.924785 38.464791, -78.924836 38.464707, -78.924931 38.464599, -78.924974 38.464505, -78.925112 38.464167, -78.925243 38.463911, -78.925657 38.463198, -78.925769 38.462974, -78.925939 38.462587, -78.926094 38.462293, -78.926149 38.462204, -78.926221 38.462131, -78.92628 38.462091, -78.926386 38.462042, -78.926517 38.462002, -78.926586 38.461973, -78.926629 38.461936, -78.926662 38.461882, -78.926938 38.461346, -78.926959 38.461316, -78.927027 38.461222, -78.927129 38.461161, -78.927201 38.461128, -78.92726 38.461109, -78.927311 38.4611, -78.927365 38.4611, -78.927517 38.461117, -78.927914 38.46122, -78.928346 38.461318, -78.928471 38.460733, -78.928605 38.460207, -78.928659 38.460031, -78.928733 38.459868, -78.928803 38.459747, -78.928937 38.459567, -78.92902 38.459486, -78.929876 38.458788, -78.930064 38.458633, -78.930176 38.458518, -78.930272 38.458395, -78.930352 38.458265, -78.930675 38.457582, -78.930764 38.457379, -78.930831 38.457207, -78.93084 38.457178, -78.930873 38.457067, -78.930898 38.456908, -78.930902 38.456854, -78.930887 38.456454, -78.930859 38.455719, -78.930872 38.455577, -78.930892 38.455493, -78.930947 38.455357, -78.931149 38.454992, -78.931419 38.454482, -78.931458 38.4544, -78.931593 38.454295, -78.931674 38.454597, -78.937262 38.455743, -78.943988 38.457125, -78.943986 38.457277, -78.943983 38.457553, -78.94396 38.458852, -78.94394 38.45894, -78.943903 38.459025, -78.943848 38.459103, -78.94365 38.459308, -78.943683 38.459323, -78.943795 38.459339, -78.944018 38.459328, -78.944947 38.459416, -78.945192 38.459411, -78.945457 38.45935, -78.945646 38.459268, -78.945953 38.459087, -78.946407 38.458719, -78.946756 38.458548, -78.947364 38.458378, -78.947678 38.458323, -78.947573 38.458191, -78.94765 38.458114, -78.947818 38.458026, -78.948139 38.4579, -78.948278 38.457873, -78.948369 38.457917, -78.948423 38.457889, -78.948433 38.457912, -78.948452 38.457992, -78.948454 38.458074, -78.948381 38.458258, -78.948509 38.458191, -78.948624 38.45813, -78.948707 38.458003, -78.948698 38.45784, -78.948844 38.457851, -78.949326 38.457955, -78.949613 38.458054, -78.949871 38.458312, -78.950367 38.458691, -78.9508 38.458916, -78.951099 38.45919, -78.951149 38.459222, -78.951224 38.459115, -78.95133 38.458935, -78.951437 38.458716, -78.951491 38.458638, -78.951562 38.458569, -78.951625 38.458526, -78.951712 38.458486, -78.951899 38.458433, -78.95211 38.458361, -78.952313 38.458276, -78.952521 38.45817, -78.952646 38.458091, -78.952737 38.45802, -78.952839 38.457923, -78.952224 38.457752, -78.951727 38.457626, -78.951495 38.457557, -78.95076 38.457378, -78.950099 38.457235, -78.94958 38.457144, -78.949225 38.457096, -78.948859 38.457054, -78.948676 38.457033, -78.94832 38.457011, -78.947924 38.457004, -78.947858 38.457004, -78.947443 38.45701, -78.946377 38.457045, -78.946356 38.456869, -78.946317 38.456705, -78.946277 38.456484, -78.946274 38.456352, -78.946243 38.456365, -78.946164 38.456389, -78.945915 38.456437, -78.945651 38.45647, -78.945061 38.456503, -78.944997 38.456511, -78.944895 38.456535, -78.9448 38.456573, -78.944754 38.456608, -78.944689 38.456672, -78.94468 38.456692, -78.944673 38.45674, -78.944676 38.456774, -78.944028 38.456558, -78.943809 38.456483, -78.943004 38.456195, -78.942681 38.45607, -78.94236 38.455935, -78.942056 38.455797, -78.941649 38.455603, -78.941311 38.455429, -78.940881 38.455195, -78.940621 38.455042, -78.939855 38.45457, -78.939588 38.454426, -78.939394 38.454339, -78.939191 38.454265, -78.938941 38.454194, -78.938774 38.454159, -78.938562 38.454128, -78.938234 38.454105, -78.937397 38.454087, -78.937157 38.454081, -78.935667 38.454044, -78.934222 38.454013, -78.932703 38.453981, -78.932082 38.453963, -78.932025 38.453962, -78.931507 38.453953, -78.931429 38.453919, -78.931377 38.453882, -78.931156 38.453594, -78.93164 38.453315, -78.93172 38.45328, -78.931776 38.453264, -78.931866 38.453249, -78.931998 38.453216, -78.932088 38.453179, -78.932182 38.45312, -78.932248 38.453062, -78.932318 38.452984, -78.932352 38.452936, -78.932369 38.452901, -78.932378 38.452865, -78.932379 38.452805, -78.932363 38.452746, -78.932329 38.452691, -78.932269 38.452636, -78.932219 38.452608, -78.932076 38.452556, -78.931858 38.452491, -78.931399 38.452335, -78.931103 38.452647, -78.930144 38.452883, -78.92999 38.452838, -78.929453 38.452692, -78.929135 38.452612, -78.929206 38.452513, -78.929157 38.452342, -78.929164 38.452249, -78.929289 38.452051, -78.929317 38.451897, -78.929282 38.451766, -78.929031 38.451458, -78.928919 38.451217, -78.928731 38.450991, -78.928459 38.450783, -78.928382 38.450656, -78.928354 38.450546, -78.928403 38.450409, -78.928549 38.45031, -78.928717 38.450233, -78.92885 38.450124, -78.928955 38.449992, -78.929059 38.449767, -78.929094 38.44952, -78.929052 38.449377, -78.928983 38.449272, -78.928668 38.448998, -78.928277 38.448515, -78.928026 38.448267, -78.927614 38.447971, -78.927342 38.447877, -78.9273 38.447801, -78.927307 38.447317, -78.927337 38.447224, -78.927377 38.447098, -78.927475 38.446916, -78.927593 38.446505, -78.927656 38.446181, -78.927782 38.445857, -78.927999 38.445533, -78.928313 38.445225, -78.928397 38.44517, -78.928543 38.445038, -78.928557 38.445005, -78.928648 38.44494, -78.928655 38.444912, -78.928718 38.444879, -78.929144 38.444445, -78.929235 38.444308, -78.929319 38.444253, -78.929493 38.444061, -78.929517 38.444039, -78.929828 38.443764, -78.930129 38.443528, -78.93052 38.443292, -78.930617 38.443199, -78.930932 38.442985, -78.930973 38.442947, -78.926915 38.439152, -78.924781 38.437279, -78.924661 38.437151, -78.9246 38.437073, -78.924473 38.437105, -78.924446 38.437116, -78.924387 38.437152, -78.92434 38.437198, -78.924305 38.437263, -78.924173 38.437805, -78.924117 38.438084, -78.924095 38.438136, -78.924065 38.438174, -78.923999 38.438225, -78.923943 38.43828, -78.923812 38.438463, -78.923713 38.438655, -78.923674 38.438719, -78.923596 38.438819, -78.923523 38.438881, -78.92342 38.438948, -78.92318 38.439066, -78.9229 38.439213, -78.922563 38.439382, -78.921723 38.439821, -78.921366 38.439999, -78.92105 38.44017, -78.921019 38.440179, -78.920975 38.440192, -78.920894 38.440199, -78.920716 38.440183, -78.920538 38.440176, -78.920481 38.440182, -78.920422 38.440195, -78.920249 38.440224, -78.920194 38.44023, -78.92011 38.440233, -78.920007 38.44023, -78.919802 38.440213, -78.919624 38.440216, -78.919449 38.440228, -78.919354 38.440239, -78.919072 38.440282, -78.91894 38.440287, -78.918834 38.440282, -78.918495 38.440227, -78.918369 38.440215, -78.918167 38.440202, -78.917107 38.440159, -78.91654 38.440141, -78.916127 38.440148, -78.915803 38.439382, -78.915732 38.439224, -78.915626 38.439029, -78.915477 38.438817, -78.915325 38.438645, -78.915161 38.438489, -78.915119 38.438454, -78.91507 38.438413, -78.915001 38.43836, -78.914944 38.438319, -78.914907 38.438294, -78.915443 38.437455, -78.913784 38.436852, -78.912446 38.43621, -78.912628 38.435938, -78.913167 38.435126, -78.914259 38.433479, -78.914399 38.433255, -78.914538 38.433028, -78.914674 38.432792, -78.915909 38.430577, -78.915969 38.430461, -78.916065 38.430229, -78.916108 38.430093, -78.916118 38.430048, -78.916137 38.429912, -78.916142 38.429836, -78.916321 38.42983, -78.9164 38.429815, -78.916494 38.429783, -78.916634 38.429708, -78.917548 38.42918, -78.917942 38.428947, -78.918386 38.428697, -78.918428 38.428656, -78.918465 38.428595, -78.918479 38.428538, -78.918475 38.42848, -78.91841 38.428334, -78.918775 38.428238, -78.919292 38.428093, -78.919557 38.428002, -78.919656 38.427968, -78.920002 38.427841, -78.920324 38.427701, -78.920463 38.427635, -78.920515 38.42761, -78.920585 38.427576, -78.920848 38.427439, -78.921156 38.427267, -78.921231 38.427223, -78.921818 38.426881, -78.921963 38.427018, -78.922256 38.427266, -78.922305 38.427307, -78.92254 38.427499, -78.923262 38.428104, -78.923445 38.428257, -78.923825 38.428576, -78.924004 38.428719, -78.924155 38.42883, -78.924334 38.428947, -78.924618 38.429132, -78.924908 38.429325, -78.925748 38.428455, -78.926471 38.427731, -78.927482 38.426698, -78.928203 38.425962, -78.928803 38.42535, -78.928859 38.425287, -78.928833 38.425253, -78.928809 38.425223, -78.928757 38.425156, -78.928685 38.425081, -78.928155 38.42456, -78.928097 38.424502, -78.927827 38.424214, -78.927715 38.424086, -78.927476 38.423815, -78.927426 38.423747, -78.927386 38.423682, -78.92736 38.423629, -78.927711 38.423426, -78.928156 38.423162, -78.928781 38.422784, -78.929108 38.422558, -78.929207 38.422486, -78.929406 38.422341, -78.929601 38.422184, -78.929877 38.421953, -78.930143 38.421705, -78.930938 38.420915, -78.931082 38.420996, -78.931171 38.421046, -78.931234 38.421063, -78.931281 38.421064, -78.931338 38.421053, -78.931418 38.42102, -78.933021 38.420074, -78.933232 38.419939, -78.933385 38.419836, -78.933643 38.419651, -78.933892 38.419454, -78.93417 38.419223, -78.93453 38.418891, -78.934945 38.418495, -78.935571 38.417891, -78.93657 38.416927, -78.936543 38.417106, -78.936533 38.41718, -78.936491 38.417334, -78.936337 38.417669, -78.936226 38.417888, -78.936212 38.417932, -78.93617 38.418135, -78.936184 38.41819, -78.936351 38.418426, -78.936351 38.418558, -78.936379 38.418668, -78.9364 38.418696, -78.936526 38.418739, -78.936582 38.418783, -78.936658 38.419124, -78.936756 38.419256, -78.937091 38.419344, -78.937287 38.419327, -78.937615 38.419261, -78.937761 38.419278, -78.93788 38.419344, -78.938009 38.419369, -78.938159 38.419399, -78.93816 38.419514, -78.938175 38.41962, -78.938211 38.419753, -78.938368 38.420073, -78.938663 38.420708, -78.938676 38.420731, -78.938684 38.420746, -78.938739 38.420844, -78.938921 38.421114, -78.939099 38.42135, -78.939265 38.421532, -78.939409 38.42168, -78.93952 38.421807, -78.939574 38.42187, -78.939645 38.421981, -78.939685 38.422062, -78.939741 38.422056, -78.939816 38.422062, -78.939865 38.422082, -78.940008 38.42214, -78.940066 38.422154, -78.940124 38.422155, -78.939988 38.421804, -78.939967 38.421656, -78.939227 38.420865, -78.938961 38.42065, -78.938965 38.420621, -78.938969 38.420596, -78.938978 38.420533, -78.938991 38.420437, -78.939039 38.420095, -78.939078 38.419814, -78.940361 38.420384, -78.940802 38.420569, -78.941259 38.420761, -78.941365 38.420812, -78.941353 38.420904, -78.941588 38.421125, -78.941588 38.420918, -78.94179 38.420942, -78.94198 38.420977, -78.941986 38.420926, -78.942006 38.420729, -78.942046 38.420733, -78.94235 38.42076, -78.942587 38.420789, -78.942599 38.420871, -78.942717 38.420889, -78.94296 38.420914, -78.943185 38.420942, -78.943233 38.420948, -78.943345 38.42048, -78.943527 38.420469, -78.943554 38.420284, -78.943545 38.419864, -78.944421 38.419762, -78.944859 38.419719, -78.945084 38.419692, -78.945201 38.419677, -78.94521 38.419823, -78.945219 38.419952, -78.945225 38.420055, -78.946286 38.420218, -78.946255 38.420436, -78.946173 38.421033, -78.946332 38.421049, -78.946353 38.421051, -78.947424 38.421163, -78.947179 38.420526, -78.94707 38.420217, -78.946997 38.420008, -78.947246 38.420115, -78.947333 38.420156, -78.947519 38.420243, -78.947726 38.420352, -78.947884 38.420447, -78.948051 38.420561, -78.949615 38.420568, -78.95295 38.420583, -78.955684 38.420982, -78.956139 38.419547, -78.956253 38.419176, -78.956357 38.418833, -78.956462 38.418522, -78.956527 38.418277, -78.956638 38.417912, -78.955962 38.417868, -78.955618 38.417846, -78.955318 38.417826, -78.955018 38.417807, -78.954719 38.417787, -78.95442 38.417768, -78.954328 38.417762, -78.954128 38.417761, -78.953823 38.417728, -78.953681 38.417719, -78.953633 38.41728, -78.953555 38.416428, -78.953549 38.416358, -78.953532 38.416205, -78.95356 38.415989, -78.953576 38.41585, -78.953584 38.415789, -78.953593 38.415712, -78.95363 38.415414, -78.953664 38.415138, -78.953698 38.414861, -78.953732 38.414585, -78.953767 38.414308, -78.953788 38.414143, -78.953816 38.41391, -78.953829 38.413805, -78.953844 38.413686, -78.953873 38.413449, -78.95397 38.412667, -78.95398 38.412558, -78.954115 38.412588, -78.954177 38.412602, -78.95444 38.412654, -78.954473 38.412659, -78.954654 38.412686, -78.954873 38.41271, -78.955023 38.412717, -78.955221 38.412713, -78.955441 38.412693, -78.9556 38.412671, -78.955755 38.412639, -78.955907 38.412598, -78.956138 38.412517, -78.956331 38.412434, -78.956551 38.412318, -78.956939 38.412073, -78.957537 38.411676, -78.95791 38.411429, -78.958425 38.411107, -78.95879 38.410887, -78.95901 38.410761, -78.958722 38.410669, -78.958147 38.410444, -78.957355 38.410116, -78.957074 38.410005, -78.956464 38.409743, -78.956225 38.409649, -78.956118 38.409598, -78.956006 38.409529, -78.955936 38.409465, -78.955896 38.409476, -78.955847 38.409474, -78.955757 38.409441, -78.955589 38.409358, -78.95485 38.409047, -78.953872 38.408613, -78.953842 38.408584, -78.95383 38.408555, -78.953688 38.408641, -78.95318 38.408992, -78.952926 38.409194, -78.952652 38.409477, -78.952212 38.40998, -78.951954 38.410294, -78.951865 38.410367, -78.951425 38.410793, -78.951248 38.410936, -78.951138 38.411013, -78.950991 38.411118, -78.950674 38.411334, -78.950497 38.411438, -78.950123 38.411639, -78.95008 38.411685, -78.950066 38.411627, -78.950045 38.411598, -78.950009 38.411576, -78.949869 38.41166, -78.949705 38.411757, -78.948278 38.41212, -78.948198 38.412141, -78.947134 38.412198, -78.94409 38.411911, -78.942227 38.411611, -78.942113 38.411609, -78.941784 38.411525, -78.941816 38.411489, -78.941888 38.411402, -78.941961 38.411316, -78.941948 38.411248, -78.941925 38.411213, -78.941861 38.411158, -78.941925 38.411107, -78.942543 38.410544, -78.943105 38.410014, -78.943529 38.409603, -78.943722 38.409417, -78.943922 38.409561, -78.944073 38.409688, -78.944214 38.409798, -78.944449 38.409958, -78.944879 38.410191, -78.945057 38.410274, -78.945202 38.410331, -78.945334 38.410367, -78.945447 38.410385, -78.9456 38.410404, -78.945606 38.410286, -78.945605 38.410149, -78.945566 38.40984, -78.94558 38.409758, -78.945611 38.409679, -78.945659 38.409606, -78.945787 38.409502, -78.945891 38.409402, -78.945918 38.409352, -78.945928 38.409298, -78.945921 38.409244, -78.945903 38.409203, -78.945853 38.409141, -78.945806 38.409057, -78.945782 38.408985, -78.945744 38.408822, -78.945792 38.408838, -78.945837 38.408861, -78.945979 38.408922, -78.946342 38.409088, -78.946786 38.409291, -78.946912 38.409349, -78.94712 38.409434, -78.94727 38.409514, -78.947387 38.409576, -78.947671 38.409737, -78.947686 38.409746, -78.948137 38.40925, -78.947954 38.409142, -78.946802 38.40844, -78.946355 38.408155, -78.946279 38.40771, -78.94625 38.407519, -78.946888 38.407176, -78.94731 38.406932, -78.947517 38.406804, -78.947669 38.406705, -78.94782 38.406596, -78.947602 38.406452, -78.947978 38.406121, -78.948365 38.405768, -78.948153 38.405636, -78.947262 38.405151, -78.946389 38.404668, -78.945733 38.404318, -78.945658 38.404291, -78.945578 38.404278, -78.945519 38.404279, -78.945367 38.4043, -78.945245 38.404311, -78.945101 38.4043, -78.945073 38.404295, -78.944635 38.404479, -78.944325 38.4046, -78.944388 38.404701, -78.944613 38.405094, -78.94482 38.405437, -78.945099 38.405912, -78.945215 38.406076, -78.945304 38.406171, -78.945406 38.406258, -78.945794 38.406537, -78.94625 38.406881, -78.946403 38.406988, -78.946521 38.407082, -78.946623 38.407156, -78.946405 38.407287, -78.94623 38.40738, -78.946216 38.407284, -78.94546 38.407648, -78.945057 38.407894, -78.944969 38.407948, -78.944892 38.407902, -78.944821 38.407879, -78.944774 38.407959, -78.944751 38.407984, -78.944654 38.40806, -78.944354 38.408256, -78.94432 38.408293, -78.944301 38.408336, -78.944302 38.408391, -78.944322 38.408434, -78.944347 38.408457, -78.944094 38.408673, -78.943909 38.408838, -78.943694 38.409024, -78.943496 38.409187, -78.943301 38.409374, -78.943204 38.409474, -78.943081 38.409422, -78.942667 38.409814, -78.942442 38.410032, -78.941904 38.410576, -78.941752 38.410706, -78.941658 38.410798, -78.941628 38.410838, -78.941616 38.410885, -78.941624 38.410932, -78.941632 38.410945, -78.9415 38.411058, -78.94123 38.411282, -78.940835 38.411571, -78.940488 38.411609, -78.940355 38.411677, -78.938757 38.410609, -78.938046 38.411344, -78.937718 38.411697, -78.937307 38.412142, -78.93622 38.413115, -78.936146 38.413101, -78.936003 38.413074, -78.935761 38.413029, -78.935306 38.41293, -78.934997 38.412854, -78.93494 38.41284, -78.934259 38.412679, -78.933905 38.412611, -78.933156 38.412498, -78.932885 38.412464, -78.932429 38.412396, -78.932175 38.412346, -78.931974 38.412297, -78.93173 38.412223, -78.931439 38.412109, -78.931309 38.412053, -78.931067 38.411932, -78.930721 38.411739, -78.930412 38.411553, -78.930136 38.4114, -78.929903 38.411291, -78.929658 38.411194, -78.929444 38.411119, -78.929281 38.411069, -78.928978 38.410994, -78.928782 38.410957, -78.928552 38.410921, -78.928693 38.41035, -78.928725 38.410142, -78.928733 38.41002, -78.928724 38.409901, -78.928693 38.409757, -78.928592 38.409496, -78.928466 38.409227, -78.92839 38.409107, -78.928297 38.408996, -78.928174 38.408877, -78.928083 38.408808, -78.927959 38.408732, -78.927843 38.408678, -78.92778 38.408651, -78.927402 38.408513, -78.927249 38.40844, -78.927106 38.408355, -78.92689 38.408198, -78.926432 38.407806, -78.926285 38.407673, -78.925548 38.407005, -78.925314 38.406789, -78.924861 38.406376, -78.924742 38.406281, -78.924639 38.406211, -78.924507 38.406135, -78.924393 38.406083, -78.924234 38.406027, -78.924137 38.406, -78.923909 38.40595, -78.923168 38.405807, -78.922665 38.405705, -78.922397 38.405636, -78.92221 38.405571, -78.922128 38.405539, -78.92184 38.4054, -78.92167 38.405306, -78.921452 38.405176, -78.921239 38.405037, -78.921213 38.405018, -78.921081 38.404923, -78.920956 38.404822, -78.920856 38.404732, -78.920487 38.404379, -78.920213 38.404136, -78.919967 38.403936, -78.919633 38.403674, -78.919095 38.403254, -78.91852 38.402817, -78.918364 38.402689, -78.918411 38.40258, -78.918442 38.40254, -78.918535 38.402448, -78.91931 38.401742, -78.919352 38.40171, -78.919527 38.401606, -78.919583 38.401562, -78.919611 38.401535, -78.919772 38.401377, -78.919885 38.401245, -78.919896 38.401208, -78.919889 38.40117, -78.919857 38.401126, -78.919817 38.40109, -78.919779 38.401069, -78.919704 38.401048, -78.91961 38.401042, -78.919375 38.401074, -78.919203 38.401081, -78.919094 38.40107, -78.918988 38.401045, -78.918924 38.401019, -78.918455 38.400741, -78.918126 38.400552, -78.917933 38.400454, -78.917804 38.400413, -78.918227 38.398358, -78.918253 38.398175, -78.918257 38.398067, -78.918247 38.397955, -78.918219 38.397795, -78.91816 38.397615, -78.918015 38.39727, -78.917629 38.396422, -78.917537 38.396224, -78.917443 38.396021, -78.917275 38.395451, -78.917553 38.394842, -78.917617 38.394803, -78.917686 38.394761, -78.917871 38.394153, -78.917825 38.394124, -78.916949 38.393522, -78.916852 38.393414, -78.916942 38.393287, -78.917056 38.393131, -78.917926 38.391906, -78.918646 38.390844, -78.91928 38.389908, -78.919786 38.389163, -78.920001 38.388855, -78.920093 38.388717, -78.920556 38.388028, -78.920813 38.387653, -78.921372 38.386815, -78.922205 38.385592, -78.922286 38.385628, -78.922492 38.385732, -78.922645 38.385823, -78.922805 38.385943, -78.923399 38.386472, -78.923611 38.386654, -78.923665 38.386693, -78.92371 38.386652, -78.92384 38.386519, -78.924045 38.386252, -78.924136 38.38616, -78.92446 38.385915, -78.924756 38.385712, -78.924817 38.385679, -78.924901 38.38565, -78.924969 38.385644, -78.925007 38.385594, -78.92507 38.385549, -78.926095 38.384993, -78.927002 38.384477, -78.92743 38.384239, -78.927745 38.384063, -78.928017 38.383933, -78.928095 38.383911, -78.928177 38.383903, -78.928259 38.383911, -78.928384 38.383946, -78.928502 38.383994, -78.928591 38.384039, -78.928665 38.384089, -78.92876 38.384176, -78.928999 38.384462, -78.929608 38.38522, -78.930275 38.386067, -78.930766 38.386704, -78.931005 38.387007, -78.931594 38.387743, -78.931678 38.387698, -78.931711 38.387675, -78.931761 38.387617, -78.931827 38.38757, -78.931893 38.387542, -78.932131 38.387462, -78.932249 38.387412, -78.932524 38.387281, -78.93285 38.387115, -78.933195 38.386951, -78.933391 38.38685, -78.933597 38.387132, -78.934147 38.387843, -78.934444 38.388243, -78.934507 38.388294, -78.934582 38.388333, -78.934749 38.38837, -78.934776 38.388316, -78.934811 38.388273, -78.934908 38.388189, -78.934967 38.388151, -78.935641 38.387765, -78.935973 38.387579, -78.936909 38.387096, -78.937035 38.387016, -78.937148 38.386925, -78.937275 38.3868, -78.937342 38.38671, -78.937479 38.386444, -78.937555 38.386312, -78.937586 38.386222, -78.9376 38.386131, -78.937598 38.386035, -78.937578 38.385916, -78.937538 38.385801, -78.937503 38.385736, -78.93773 38.385649, -78.93788 38.385579, -78.937969 38.385525, -78.938068 38.385447, -78.938154 38.38536, -78.938235 38.385256, -78.938286 38.385165, -78.938314 38.385088, -78.938391 38.384674, -78.938417 38.384578, -78.938499 38.384379, -78.938779 38.383838, -78.938983 38.383426, -78.939013 38.383328, -78.939024 38.383248, -78.93901 38.383097, -78.938999 38.383037, -78.938905 38.382667, -78.938428 38.382746, -78.938296 38.382783, -78.938172 38.382834, -78.938084 38.382886, -78.938005 38.382945, -78.937917 38.383027, -78.937844 38.383118, -78.93775 38.383282, -78.937407 38.38393, -78.937022 38.384674, -78.93699 38.384757, -78.936665 38.384719, -78.936467 38.384709, -78.936375 38.384714, -78.936248 38.384735, -78.936125 38.38477, -78.93601 38.384819, -78.935655 38.385001, -78.935563 38.385053, -78.935197 38.384583, -78.93516 38.384535, -78.934787 38.384045, -78.934634 38.383843, -78.934172 38.383209, -78.934113 38.383127, -78.933715 38.382572, -78.933106 38.381721, -78.932706 38.381172, -78.931847 38.379971, -78.931312 38.379241, -78.931003 38.37882, -78.930852 38.378607, -78.930677 38.37836, -78.930558 38.378208, -78.930478 38.378122, -78.930364 38.378027, -78.930728 38.377573, -78.931032 38.377207, -78.931281 38.376923, -78.93137 38.376831, -78.931606 38.376587, -78.931867 38.376338, -78.932386 38.375868, -78.933871 38.37453, -78.933984 38.374428, -78.934329 38.374122, -78.93446 38.373983, -78.934576 38.373837, -78.934679 38.373683, -78.934764 38.373546, -78.934879 38.373329, -78.934995 38.372999, -78.935016 38.372939, -78.935243 38.372016, -78.935391 38.371326, -78.935633 38.370353, -78.935712 38.370063, -78.936035 38.368695, -78.936153 38.368215, -78.936329 38.367596, -78.936424 38.366931, -78.936747 38.36697, -78.936786 38.366964, -78.936829 38.366944, -78.936859 38.366912, -78.936982 38.366428, -78.937002 38.366399, -78.937029 38.366382, -78.93707 38.366372, -78.937273 38.366397, -78.937332 38.366391, -78.937385 38.366372, -78.937428 38.36634, -78.937453 38.366309, -78.93761 38.36567, -78.938303 38.365822, -78.938731 38.365907, -78.939077 38.365976, -78.939399 38.366034, -78.939697 38.366087, -78.940332 38.364437, -78.938801 38.364099, -78.938754 38.363997, -78.938875 38.363944, -78.940394 38.364334, -78.94051 38.363557, -78.940549 38.36355, -78.943259 38.362315, -78.943157 38.362032, -78.944798 38.3613, -78.946425 38.360462, -78.946392 38.360417, -78.946015 38.359726, -78.945855 38.359314, -78.945702 38.358863, -78.945639 38.358495, -78.945576 38.357793, -78.945513 38.357403, -78.94536 38.356837, -78.945262 38.356337, -78.945192 38.356057, -78.945053 38.35564, -78.944899 38.35508, -78.944878 38.35486, -78.944864 38.354228, -78.944878 38.354146, -78.945032 38.353773, -78.94506 38.353663, -78.945094 38.353339, -78.945122 38.353185, -78.945136 38.353142, -78.945157 38.353075, -78.945206 38.352971, -78.945569 38.352317, -78.945778 38.352015, -78.945834 38.351883, -78.945882 38.35173, -78.94591 38.351576, -78.94591 38.351472, -78.945861 38.351186, -78.945861 38.351076, -78.945882 38.351003, -78.945815 38.350699, -78.945223 38.35082, -78.94458 38.350936, -78.944678 38.351122, -78.944839 38.351496, -78.944964 38.351823, -78.945017 38.352014, -78.945053 38.352207, -78.945054 38.352356, -78.945035 38.352504, -78.944998 38.35265, -78.944862 38.353076, -78.944794 38.353267, -78.944678 38.353554, -78.944508 38.35391, -78.944411 38.354069, -78.944298 38.35422, -78.944222 38.354307, -78.943647 38.354873, -78.943507 38.354775, -78.94328 38.354633, -78.943004 38.354481, -78.942965 38.35448, -78.942931 38.354494, -78.942723 38.354691, -78.942314 38.355112, -78.941915 38.355483, -78.941764 38.355634, -78.941428 38.355956, -78.941231 38.356163, -78.940845 38.356534, -78.940284 38.357099, -78.939981 38.357435, -78.939808 38.357642, -78.939769 38.357671, -78.939719 38.357714, -78.93966 38.357777, -78.939097 38.358499, -78.938893 38.35874, -78.938767 38.3589, -78.938534 38.3592, -78.938368 38.359402, -78.938173 38.359661, -78.938064 38.359828, -78.937972 38.359946, -78.93785 38.360124, -78.93848 38.360321, -78.938625 38.36037, -78.938142 38.361086, -78.938083 38.361185, -78.937987 38.361348, -78.937908 38.361499, -78.937785 38.361801, -78.937721 38.361994, -78.937638 38.36233, -78.936887 38.362211, -78.936656 38.36218, -78.935761 38.362059, -78.935571 38.362018, -78.935424 38.361975, -78.935275 38.361918, -78.935097 38.361835, -78.934969 38.361761, -78.93483 38.36166, -78.934704 38.361548, -78.93463 38.361464, -78.934591 38.36141, -78.932877 38.362022, -78.932524 38.361549, -78.932668 38.361479, -78.93278 38.361432, -78.932855 38.361389, -78.932906 38.361353, -78.932979 38.361285, -78.933094 38.361144, -78.933165 38.361088, -78.933224 38.361067, -78.933276 38.361061, -78.933341 38.361066, -78.93352 38.361135, -78.933581 38.361141, -78.933642 38.361134, -78.934009 38.360998, -78.934262 38.360889, -78.934074 38.36062, -78.933733 38.360615, -78.933642 38.360598, -78.933384 38.360478, -78.933133 38.360291, -78.933091 38.360274, -78.932903 38.360269, -78.93277 38.360274, -78.932436 38.36034, -78.931778 38.360538, -78.931633 38.360582, -78.931382 38.360697, -78.931187 38.360802, -78.931089 38.360873, -78.931034 38.360928, -78.93102 38.360983, -78.931027 38.361093, -78.931047 38.361175, -78.931152 38.361406, -78.93118 38.36151, -78.931201 38.361697, -78.931229 38.36179, -78.931389 38.362125, -78.931424 38.362257, -78.931396 38.362411, -78.931333 38.362597, -78.931284 38.36268, -78.930936 38.362993, -78.930803 38.36307, -78.930601 38.363157, -78.93044 38.363207, -78.930259 38.363245, -78.929994 38.363284, -78.929659 38.363306, -78.929359 38.363311, -78.92892 38.363294, -78.928717 38.363305, -78.92848 38.363333, -78.928166 38.363421, -78.92809 38.36347, -78.928027 38.363542, -78.928006 38.363591, -78.928003 38.363672, -78.927999 38.363789, -78.928013 38.364157, -78.927999 38.36425, -78.927824 38.364524, -78.927727 38.364629, -78.92749 38.364826, -78.927441 38.364898, -78.92742 38.36498, -78.927427 38.365013, -78.927462 38.365068, -78.92751 38.365123, -78.927643 38.365216, -78.927845 38.365255, -78.928613 38.365266, -78.928961 38.365315, -78.929143 38.365354, -78.929268 38.365387, -78.929436 38.365453, -78.929575 38.365524, -78.929861 38.365711, -78.930105 38.365909, -78.930496 38.366277, -78.930565 38.366387, -78.930593 38.366474, -78.930607 38.36671, -78.930621 38.366749, -78.930789 38.366936, -78.93083 38.367007, -78.930928 38.367271, -78.930935 38.367386, -78.930914 38.36743, -78.93074 38.367529, -78.930691 38.367584, -78.930677 38.367617, -78.930705 38.367814, -78.93072 38.367865, -78.93074 38.36793, -78.930663 38.368039, -78.930656 38.368094, -78.930691 38.36821, -78.930767 38.368396, -78.930837 38.368655, -78.930983 38.369577, -78.931053 38.369863, -78.931144 38.370148, -78.931155 38.370231, -78.931213 38.370346, -78.931283 38.370461, -78.931318 38.370582, -78.931353 38.370846, -78.931388 38.37089, -78.931416 38.370901, -78.931548 38.370912, -78.931744 38.370989, -78.931974 38.371159, -78.932057 38.371263, -78.932162 38.371444, -78.932274 38.371697, -78.93249 38.371933, -78.93265 38.372065, -78.932797 38.372164, -78.932866 38.372197, -78.933222 38.372296, -78.933243 38.372318, -78.933257 38.372362, -78.933376 38.372395, -78.933459 38.372516, -78.933487 38.372576, -78.933508 38.372708, -78.933508 38.372796, -78.933459 38.37301, -78.933403 38.373125, -78.933334 38.37323, -78.933117 38.373433, -78.932999 38.37351, -78.932692 38.37368, -78.932134 38.374031, -78.931694 38.374427, -78.931527 38.374597, -78.931373 38.374822, -78.931108 38.375124, -78.931092 38.375174, -78.930904 38.375361, -78.930736 38.375481, -78.93066 38.375586, -78.930597 38.375805, -78.93059 38.375899, -78.930673 38.376162, -78.930722 38.376481, -78.930673 38.37669, -78.930534 38.376937, -78.930018 38.377623, -78.929922 38.37774, -78.929875 38.377767, -78.929856 38.377821, -78.92939 38.378386, -78.929341 38.378469, -78.929243 38.378557, -78.929152 38.378612, -78.929041 38.378639, -78.928887 38.378628, -78.928706 38.378584, -78.928357 38.378425, -78.928238 38.378403, -78.928022 38.378447, -78.927949 38.378479, -78.927904 38.378389, -78.927671 38.377944, -78.927585 38.377811, -78.927518 38.377723, -78.927406 38.377611, -78.927269 38.377503, -78.927179 38.377444, -78.927086 38.377383, -78.926985 38.377332, -78.926836 38.377271, -78.926675 38.377222, -78.926542 38.377193, -78.926173 38.377131, -78.924756 38.376938, -78.924471 38.376908, -78.924108 38.376884, -78.923878 38.37688, -78.923675 38.376887, -78.922419 38.376997, -78.922224 38.377009, -78.921865 38.377023, -78.920841 38.377076, -78.919918 38.377171, -78.919614 38.37719, -78.919366 38.377188, -78.919167 38.377176, -78.918611 38.377106, -78.91836 38.377073, -78.918108 38.377041, -78.917787 38.376999, -78.917541 38.376973, -78.917346 38.376964, -78.917193 38.376965, -78.917022 38.376981, -78.916855 38.377011, -78.916654 38.377062, -78.91649 38.377118, -78.916299 38.377202, -78.916149 38.377287, -78.916092 38.377324, -78.915879 38.377498, -78.915837 38.377535, -78.915358 38.377959, -78.915226 38.37806, -78.915379 38.378401, -78.915443 38.378608, -78.915457 38.378806, -78.915421 38.378932, -78.915395 38.379024, -78.914978 38.380866, -78.914892 38.381104, -78.91473 38.381342, -78.914542 38.38153, -78.914216 38.38169, -78.913279 38.381952, -78.911962 38.382333, -78.911493 38.382421, -78.911397 38.381707, -78.91136 38.381582, -78.911325 38.3815, -78.911253 38.38137, -78.910566 38.380334, -78.910213 38.379798, -78.909936 38.379891, -78.909646 38.379987, -78.909569 38.37987, -78.909476 38.37976, -78.909313 38.379607, -78.909223 38.379513, -78.909135 38.37943, -78.909076 38.379391, -78.909042 38.379385, -78.908999 38.37939, -78.908948 38.37942, -78.908937 38.379436, -78.906976 38.380429, -78.906907 38.381293, -78.906291 38.381114, -78.905985 38.381082, -78.905201 38.381326, -78.904939 38.381423, -78.904802 38.381481, -78.90393 38.381852, -78.903735 38.381921, -78.903576 38.381966, -78.903743 38.382323, -78.903873 38.382569, -78.904019 38.382794, -78.904186 38.38301, -78.904375 38.383296, -78.904579 38.383585, -78.904669 38.383693, -78.904728 38.383745, -78.904784 38.383786, -78.904935 38.38388, -78.905119 38.383974, -78.905202 38.384003, -78.905054 38.384499, -78.904559 38.386236, -78.90443 38.386622, -78.904259 38.387013, -78.904168 38.387174, -78.903872 38.387621, -78.903838 38.387663, -78.903778 38.387738, -78.90356 38.387982, -78.903253 38.388278, -78.903145 38.388373, -78.902914 38.388553, -78.902719 38.388689, -78.902464 38.388849, -78.902342 38.388918, -78.902583 38.389008, -78.90277 38.389089, -78.902995 38.3892, -78.903136 38.389279, -78.903215 38.38934, -78.90328 38.389411, -78.90345 38.389655, -78.903505 38.389704, -78.903572 38.389741, -78.903649 38.389766, -78.903915 38.389808, -78.903976 38.389833, -78.904026 38.389871, -78.904042 38.389889, -78.904094 38.389924, -78.904121 38.389964, -78.90413 38.39, -78.904135 38.390053, -78.904158 38.390104, -78.904263 38.390226, -78.904312 38.390311, -78.904402 38.390531, -78.904494 38.39067, -78.904618 38.390829, -78.904734 38.390946, -78.905115 38.391256, -78.905189 38.391297, -78.905272 38.391326, -78.905361 38.39134, -78.905397 38.391342, -78.907148 38.391437, -78.907422 38.391548, -78.907748 38.391681, -78.905734 38.392718, -78.904472 38.392248, -78.903108 38.391736, -78.902746 38.391603, -78.90007 38.391437, -78.900197 38.391086, -78.900312 38.390731, -78.900351 38.390567, -78.900379 38.390369, -78.900378 38.390216, -78.900355 38.390057, -78.900322 38.389928, -78.900222 38.389695, -78.899947 38.389285, -78.899621 38.388892, -78.899343 38.388587, -78.899007 38.388275, -78.898957 38.388163, -78.898794 38.388012, -78.898243 38.387559, -78.898848 38.387109, -78.898922 38.387054, -78.899595 38.386557, -78.899491 38.386394, -78.899062 38.385766, -78.899017 38.385714, -78.898982 38.3857, -78.898942 38.385699, -78.898906 38.385713, -78.898178 38.38635, -78.898135 38.386381, -78.898073 38.386413, -78.898027 38.386424, -78.897956 38.386422, -78.8979 38.386407, -78.897776 38.386329, -78.897569 38.386181, -78.89759 38.385902, -78.897624 38.385722, -78.897724 38.385349, -78.897745 38.38523, -78.897736 38.385176, -78.897708 38.385125, -78.897678 38.385097, -78.897407 38.386008, -78.897322 38.38725, -78.89732 38.387281, -78.897299 38.387591, -78.89707 38.389065, -78.896734 38.391235, -78.891556 38.39091, -78.891118 38.390882, -78.890952 38.390873, -78.890002 38.390814, -78.889827 38.394666, -78.889809 38.394644, -78.889763 38.394606, -78.889726 38.394581, -78.889678 38.394551, -78.889652 38.394532, -78.889626 38.39451, -78.889611 38.394489, -78.889602 38.394465, -78.889601 38.394448, -78.889608 38.394366, -78.889608 38.394317, -78.889607 38.394304, -78.889601 38.394279, -78.889592 38.394254, -78.889581 38.39423, -78.889566 38.394208, -78.889558 38.394195, -78.88954 38.394174, -78.889509 38.394145, -78.889486 38.394127, -78.889447 38.394104, -78.889433 38.394097, -78.889289 38.394035, -78.889224 38.394008, -78.889092 38.393956, -78.888959 38.393907, -78.888806 38.393853, -78.888656 38.393794, -78.888614 38.393775, -78.888553 38.39375, -78.887902 38.393455, -78.887291 38.393209, -78.88707 38.393142, -78.886674 38.393012, -78.886574 38.392975, -78.886446 38.392918, -78.886294 38.392837, -78.886201 38.392771, -78.886035 38.392663, -78.885916 38.392602, -78.885788 38.392555, -78.885551 38.392496, -78.88543 38.392459, -78.885396 38.392443, -78.885253 38.392377, -78.88479 38.392086, -78.884719 38.392048, -78.884639 38.392023, -78.884571 38.392013, -78.884484 38.392013, -78.884413 38.392025, -78.884345 38.392044, -78.884274 38.392059, -78.884188 38.392068, -78.884141 38.392065, -78.883995 38.392004, -78.883918 38.391979, -78.883884 38.392448, -78.883851 38.392918, -78.883801 38.393604, -78.883651 38.393594, -78.883207 38.393563, -78.883126 38.393552, -78.88303 38.393524, -78.882933 38.39349, -78.88285 38.393473, -78.882741 38.393464, -78.882666 38.393528, -78.882617 38.393581, -78.882593 38.39362, -78.882571 38.393655, -78.882542 38.393734, -78.882532 38.39382, -78.882509 38.393817, -78.882454 38.393819, -78.88241 38.393832, -78.882286 38.393899, -78.882172 38.39397, -78.882226 38.394117, -78.882278 38.394309, -78.882316 38.394503, -78.882347 38.394809, -78.882383 38.395607, -78.882381 38.395738, -78.882369 38.395904, -78.882322 38.396125, -78.882277 38.396264, -78.881749 38.397613, -78.881565 38.398115, -78.881487 38.398328, -78.881247 38.398958, -78.881827 38.399125, -78.881839 38.399183, -78.881882 38.399247, -78.881943 38.399314, -78.88201 38.399372, -78.88211 38.399427, -78.882486 38.399564, -78.882642 38.399624, -78.88244 38.402276, -78.88294 38.40249, -78.882853 38.403173, -78.88283 38.403338, -78.882765 38.403813, -78.882641 38.404766, -78.882392 38.406671, -78.882325 38.407169, -78.882014 38.4095, -78.881992 38.409669, -78.882049 38.409798, -78.88229 38.410348, -78.883033 38.411037, -78.883527 38.411497, -78.88182 38.412128, -78.881621 38.411793, -78.878687 38.412117, -78.878562 38.41213, -78.878521 38.412135, -78.878075 38.412184, -78.877691 38.412228, -78.8775 38.412248, -78.877203 38.412278, -78.876634 38.412133, -78.876283 38.412048, -78.876154 38.412016, -78.875817 38.411927, -78.875603 38.411875, -78.875376 38.411821, -78.875302 38.411803, -78.875213 38.411934, -78.875241 38.411938, -78.875292 38.411974, -78.875318 38.412049, -78.875282 38.41218, -78.875249 38.412176, -78.875119 38.412084, -78.874806 38.412516, -78.872828 38.412596, -78.865338 38.412904, -78.865186 38.41291, -78.865167 38.412822, -78.865165 38.41278, -78.865161 38.412755, -78.865145 38.41265, -78.865112 38.412342, -78.865102 38.412059, -78.865107 38.411654, -78.865138 38.410484, -78.865152 38.409786, -78.865269 38.409734, -78.865368 38.409683, -78.865404 38.409003, -78.86509 38.409018, -78.865069 38.408915, -78.864998 38.408633, -78.86491 38.40834, -78.86488 38.408276, -78.864866 38.408227, -78.864722 38.407853, -78.864676 38.407746, -78.86445 38.407368, -78.86429 38.4071, -78.864191 38.406941, -78.864136 38.406887, -78.863967 38.406647, -78.863723 38.40629, -78.863352 38.405822, -78.862936 38.405229, -78.862659 38.404791)))"} -{"geo_id":"22096","urban_area_code":"22096","name":"Danbury, CT--NY","lsad_name":"Danbury, CT--NY Urbanized Area","area_lsad_code":"75","mtfcc_feature_class_code":"G3500","type":"U","functional_status":"S","area_land_meters":341052773,"area_water_meters":9949125,"internal_point_lon":-73.4266233,"internal_point_lat":41.4589603,"internal_point_geom":"POINT(-73.4266233 41.4589603)","urban_area_geom":"MULTIPOLYGON(((-73.576273 41.447334, -73.576305 41.447778, -73.576375 41.448219, -73.576468 41.448554, -73.576608 41.44895, -73.577143 41.450317, -73.578459 41.453692, -73.578509 41.453826, -73.578646 41.454235, -73.578721 41.454552, -73.57879 41.455026, -73.578971 41.45704, -73.579022 41.457697, -73.579035 41.457865, -73.579216 41.458166, -73.579557 41.458775, -73.57971 41.458997, -73.579958 41.459266, -73.580689 41.459953, -73.581729 41.4609, -73.58202 41.461121, -73.58239 41.461398, -73.582627 41.461554, -73.581838 41.46223, -73.581728 41.462352, -73.581632 41.462503, -73.581573 41.462652, -73.58149 41.4629, -73.58134 41.463514, -73.581233 41.463888, -73.581234 41.463988, -73.581287 41.464139, -73.581316 41.464174, -73.581561 41.464381, -73.581612 41.464465, -73.58165 41.464611, -73.581624 41.465152, -73.581843 41.465172, -73.583496 41.465201, -73.584311 41.46521, -73.584836 41.465195, -73.584766 41.464306, -73.584665 41.463442, -73.584598 41.46324, -73.584494 41.463047, -73.584363 41.462866, -73.584133 41.46262, -73.58378 41.46234, -73.583356 41.462042, -73.583632 41.461796, -73.58371 41.461695, -73.583741 41.461597, -73.583752 41.461459, -73.583738 41.461301, -73.583703 41.460863, -73.583721 41.460724, -73.583801 41.460508, -73.583909 41.460298, -73.583983 41.460202, -73.58408 41.460112, -73.584197 41.460032, -73.584533 41.459853, -73.584674 41.459701, -73.584731 41.459595, -73.584756 41.459482, -73.584759 41.459417, -73.584821 41.458636, -73.584816 41.458245, -73.584741 41.458115, -73.584532 41.457953, -73.58382 41.457441, -73.583709 41.457318, -73.583658 41.457203, -73.583628 41.457066, -73.583615 41.456887, -73.58378 41.455474, -73.583159 41.455382, -73.582971 41.455373, -73.582835 41.45539, -73.582421 41.455535, -73.580775 41.456099, -73.580736 41.455778, -73.580702 41.455563, -73.580601 41.455127, -73.580487 41.45479, -73.580383 41.454635, -73.580237 41.454504, -73.580038 41.454375, -73.579781 41.454242, -73.579535 41.454137, -73.579566 41.453581, -73.579556 41.453439, -73.579502 41.45327, -73.579515 41.453203, -73.579569 41.453176, -73.579691 41.453055, -73.579826 41.452997, -73.579951 41.452899, -73.580059 41.452791, -73.580096 41.452734, -73.580123 41.452572, -73.580156 41.452545, -73.58019 41.452532, -73.580362 41.452505, -73.58046 41.452407, -73.580507 41.452383, -73.580538 41.452353, -73.580568 41.452289, -73.580683 41.452222, -73.580723 41.452141, -73.580744 41.452012, -73.580744 41.451938, -73.580724 41.451816, -73.580784 41.451648, -73.580906 41.45152, -73.581416 41.451087, -73.581606 41.450897, -73.581725 41.450695, -73.581844 41.450422, -73.581915 41.449995, -73.581927 41.449734, -73.581927 41.449318, -73.581891 41.449033, -73.581868 41.448725, -73.581594 41.447971, -73.581547 41.447757, -73.581547 41.447556, -73.581535 41.447401, -73.581499 41.447211, -73.581393 41.447045, -73.581253 41.446766, -73.576273 41.447334)), ((-73.44155 41.332132, -73.441737 41.33231, -73.441893 41.332481, -73.441976 41.332571, -73.441997 41.332622, -73.44213 41.33265, -73.442517 41.332733, -73.44253 41.332737, -73.44266 41.332781, -73.442731 41.332805, -73.442946 41.332877, -73.443018 41.332902, -73.443074 41.332809, -73.443199 41.332726, -73.443351 41.332697, -73.443524 41.332669, -73.443576 41.332661, -73.443856 41.332633, -73.444047 41.33261, -73.444213 41.332567, -73.444409 41.332506, -73.444689 41.332341, -73.444779 41.332253, -73.445084 41.332027, -73.445114 41.332013, -73.445355 41.331913, -73.445533 41.331867, -73.445676 41.331845, -73.445202 41.3317, -73.445045 41.331652, -73.444611 41.33154, -73.443352 41.331784, -73.44155 41.332132)), ((-73.538128 41.430025, -73.538404 41.427842, -73.537735 41.42822, -73.537648 41.428706, -73.537633 41.429727, -73.537863 41.430284, -73.538066 41.430511, -73.538128 41.430025)), ((-73.44155 41.332132, -73.441521 41.332105, -73.441393 41.331984, -73.441241 41.332015, -73.441038 41.332075, -73.440835 41.332155, -73.440716 41.332247, -73.440668 41.332303, -73.44155 41.332132)), ((-73.384612 41.646985, -73.384634 41.646918, -73.384692 41.64675, -73.384779 41.646518, -73.384974 41.646115, -73.385032 41.646009, -73.385123 41.645844, -73.385148 41.645801, -73.385327 41.645507, -73.385207 41.645538, -73.384538 41.645237, -73.384413 41.645476, -73.384292 41.645716, -73.384854 41.645978, -73.384612 41.646587, -73.384433 41.647133, -73.384524 41.647297, -73.384612 41.646985)), ((-73.375358 41.369623, -73.375304 41.369514, -73.375245 41.369394, -73.375212 41.369263, -73.375155 41.369186, -73.375084 41.369088, -73.374896 41.369067, -73.374558 41.369036, -73.374519 41.369031, -73.374138 41.368992, -73.374041 41.368956, -73.373771 41.368737, -73.373616 41.368496, -73.373474 41.368106, -73.373438 41.367942, -73.373381 41.367673, -73.373281 41.367399, -73.373074 41.366684, -73.373 41.366429, -73.372847 41.36606, -73.372765 41.36586, -73.372481 41.365466, -73.372262 41.365271, -73.37224 41.365259, -73.371978 41.365119, -73.371767 41.36509, -73.371732 41.36509, -73.371432 41.365099, -73.371271 41.36513, -73.370997 41.365184, -73.370979 41.365188, -73.37072 41.365273, -73.370381 41.365464, -73.370302 41.36553, -73.37022 41.365616, -73.370172 41.36571, -73.370143 41.36579, -73.37014 41.3658, -73.370078 41.365921, -73.370066 41.365992, -73.370124 41.366198, -73.370229 41.366419, -73.370425 41.367068, -73.370548 41.367298, -73.370784 41.367572, -73.371054 41.367744, -73.371207 41.367799, -73.371366 41.367844, -73.37165 41.367853, -73.37195 41.367808, -73.372013 41.367788, -73.372271 41.367708, -73.372677 41.367555, -73.37303 41.36757, -73.373096 41.367587, -73.373129 41.367698, -73.373333 41.367972, -73.373336 41.368093, -73.373348 41.368493, -73.373384 41.368598, -73.373603 41.368872, -73.373939 41.369092, -73.374106 41.369141, -73.374238 41.369267, -73.374456 41.369377, -73.374566 41.369377, -73.374699 41.36948, -73.374857 41.369602, -73.374999 41.369632, -73.375152 41.369671, -73.375312 41.369643, -73.375358 41.369623)), ((-73.292994 41.402306, -73.292896 41.401451, -73.292631 41.399988, -73.292443 41.399083, -73.292227 41.398685, -73.292074 41.398433, -73.29183 41.398112, -73.291697 41.397952, -73.291524 41.397798, -73.291202 41.397519, -73.28871 41.395182, -73.286964 41.395745, -73.286413 41.395819, -73.286036 41.395871, -73.285115 41.396029, -73.28477 41.396095, -73.284677 41.396114, -73.284223 41.396187, -73.284508 41.39661, -73.284719 41.396859, -73.284859 41.396991, -73.284978 41.397082, -73.285166 41.397174, -73.285466 41.397217, -73.286037 41.397262, -73.286416 41.397347, -73.286848 41.397531, -73.287665 41.397859, -73.288163 41.398025, -73.288401 41.398085, -73.2889 41.398238, -73.289066 41.398306, -73.289261 41.398396, -73.289471 41.398487, -73.289136 41.398906, -73.289123 41.398923, -73.289082 41.398971, -73.288748 41.399367, -73.288269 41.40003, -73.287851 41.400557, -73.28779 41.400636, -73.287426 41.401074, -73.287223 41.400983, -73.286882 41.40083, -73.286614 41.400715, -73.28641 41.400629, -73.286158 41.40052, -73.285746 41.400343, -73.285406 41.400191, -73.285157 41.40008, -73.284999 41.40031, -73.284858 41.400487, -73.284657 41.400385, -73.284531 41.400371, -73.2844 41.400384, -73.284292 41.400496, -73.284149 41.400696, -73.284122 41.400766, -73.284162 41.400821, -73.284283 41.400901, -73.284472 41.400988, -73.284276 41.401222, -73.284154 41.401362, -73.283989 41.401332, -73.283926 41.40139, -73.28378 41.401567, -73.283592 41.401844, -73.283576 41.401966, -73.283605 41.402133, -73.283653 41.402304, -73.283757 41.402507, -73.283942 41.402648, -73.284077 41.402697, -73.284257 41.402702, -73.284498 41.402674, -73.28476 41.402614, -73.284901 41.402569, -73.285018 41.402491, -73.285278 41.402253, -73.285519 41.40199, -73.285582 41.402012, -73.286194 41.402267, -73.286394 41.402363, -73.28626 41.402547, -73.286091 41.402783, -73.285854 41.403094, -73.285717 41.403276, -73.28563 41.403363, -73.285529 41.403466, -73.285376 41.403579, -73.285346 41.403593, -73.285236 41.403648, -73.285142 41.403664, -73.285036 41.403687, -73.28485 41.403685, -73.284621 41.403626, -73.284556 41.40361, -73.283549 41.403302, -73.282819 41.403107, -73.282215 41.402946, -73.282542 41.403669, -73.282588 41.403771, -73.283 41.404681, -73.283593 41.405896, -73.283783 41.40621, -73.284145 41.406807, -73.284248 41.406989, -73.284443 41.407129, -73.284848 41.407421, -73.285052 41.407509, -73.285273 41.407606, -73.285577 41.407412, -73.286492 41.406834, -73.286797 41.406641, -73.286949 41.406545, -73.287196 41.406379, -73.28747 41.406186, -73.287709 41.406052, -73.287804 41.406005, -73.287985 41.405877, -73.288053 41.405835, -73.288145 41.40578, -73.288303 41.405754, -73.288873 41.405596, -73.289454 41.405448, -73.289859 41.405308, -73.290253 41.405152, -73.290553 41.404973, -73.290841 41.404821, -73.291072 41.404669, -73.291094 41.404651, -73.291257 41.40452, -73.291459 41.404306, -73.29168 41.404092, -73.291832 41.403874, -73.29189 41.403784, -73.291975 41.403656, -73.292128 41.403392, -73.292233 41.403104, -73.292291 41.402894, -73.292309 41.40283, -73.292318 41.402563, -73.292307 41.402493, -73.292283 41.402389, -73.292424 41.402363, -73.292775 41.402299, -73.29285 41.402301, -73.292994 41.402306)), ((-73.385161 41.363264, -73.385149 41.363231, -73.38509 41.363209, -73.385047 41.363132, -73.384887 41.362978, -73.384879 41.362847, -73.384888 41.362841, -73.38493 41.362814, -73.384807 41.362638, -73.384639 41.362457, -73.384224 41.36205, -73.38405 41.361853, -73.383828 41.361657, -73.383715 41.361556, -73.383489 41.361419, -73.383398 41.361321, -73.383336 41.361254, -73.383227 41.361001, -73.383271 41.360952, -73.383365 41.360946, -73.38354 41.360903, -73.383563 41.360909, -73.383693 41.360947, -73.38373 41.360914, -73.383737 41.360859, -73.383679 41.360782, -73.383679 41.360678, -73.383752 41.360612, -73.383694 41.360529, -73.38365 41.360507, -73.383643 41.360458, -73.383694 41.360354, -73.383796 41.360238, -73.383956 41.360123, -73.384219 41.360025, -73.384539 41.359959, -73.384671 41.359981, -73.384773 41.359926, -73.384852 41.359821, -73.384911 41.359745, -73.384926 41.35963, -73.385013 41.359547, -73.385064 41.359531, -73.385166 41.359531, -73.385356 41.359378, -73.385266 41.359396, -73.385079 41.359434, -73.384899 41.359445, -73.384407 41.359477, -73.38352 41.359586, -73.383498 41.359589, -73.383278 41.359613, -73.38306 41.35962, -73.382789 41.35967, -73.382579 41.35971, -73.381978 41.359821, -73.381708 41.359872, -73.381815 41.359973, -73.381944 41.360147, -73.381974 41.360183, -73.382648 41.36101, -73.382748 41.361144, -73.382996 41.361473, -73.383074 41.361583, -73.383348 41.361926, -73.383861 41.362566, -73.384401 41.362905, -73.384627 41.363005, -73.384644 41.363014, -73.38469 41.36304, -73.384875 41.363145, -73.385121 41.363251, -73.385161 41.363264)), ((-73.467272 41.573588, -73.467652 41.574413, -73.467967 41.575094, -73.468727 41.576918, -73.468967 41.577494, -73.469106 41.577743, -73.469607 41.577258, -73.469908 41.576967, -73.470206 41.576592, -73.470364 41.576362, -73.470702 41.57551, -73.470711 41.575489, -73.470839 41.574828, -73.470647 41.57477, -73.470115 41.5746, -73.469136 41.574288, -73.468739 41.57418, -73.468602 41.574144, -73.467968 41.573879, -73.467272 41.573588)), ((-73.341626 41.317012, -73.341452 41.316807, -73.341274 41.316724, -73.341079 41.316711, -73.34084 41.316748, -73.34059 41.316731, -73.340319 41.316623, -73.340101 41.316423, -73.340005 41.316308, -73.339841 41.316279, -73.339807 41.316246, -73.339752 41.316231, -73.339588 41.316297, -73.340261 41.316839, -73.340724 41.317158, -73.340816 41.317159, -73.340929 41.31717, -73.341111 41.317151, -73.341424 41.317072, -73.341597 41.317021, -73.341626 41.317012)), ((-73.294642 41.402259, -73.294556 41.402268, -73.294141 41.402324, -73.293895 41.402344, -73.293693 41.40234, -73.293522 41.402336, -73.293204 41.402312, -73.292994 41.402306, -73.293168 41.403014, -73.293326 41.404202, -73.29334 41.404922, -73.293261 41.405447, -73.293118 41.405874, -73.29328 41.405892, -73.293433 41.40593, -73.293513 41.406017, -73.293594 41.406388, -73.293673 41.406497, -73.293871 41.406513, -73.294141 41.40645, -73.294345 41.406313, -73.294643 41.406183, -73.294855 41.405978, -73.294964 41.405737, -73.29495 41.405403, -73.294892 41.405209, -73.294709 41.404828, -73.29449 41.404518, -73.294469 41.404446, -73.294513 41.404298, -73.294477 41.404185, -73.294382 41.404121, -73.294178 41.404005, -73.294119 41.4039, -73.294134 41.403725, -73.294331 41.403368, -73.29447 41.403185, -73.294521 41.403069, -73.294616 41.402638, -73.294601 41.402593, -73.294652 41.40235, -73.294642 41.402259)), ((-73.304151 41.371822, -73.304783 41.372324, -73.305403 41.372248, -73.305457 41.372254, -73.305536 41.372315, -73.305587 41.372445, -73.305688 41.372547, -73.305698 41.372585, -73.305724 41.372604, -73.305869 41.372644, -73.306016 41.372748, -73.306142 41.372768, -73.306182 41.372671, -73.306264 41.372621, -73.306489 41.372597, -73.306772 41.372483, -73.306933 41.372459, -73.306996 41.372398, -73.307055 41.372405, -73.307308 41.372325, -73.307361 41.372332, -73.307448 41.372374, -73.307631 41.372289, -73.307757 41.372274, -73.307824 41.372292, -73.30798 41.372422, -73.308086 41.372422, -73.308225 41.372365, -73.308353 41.372262, -73.308474 41.372251, -73.308546 41.372224, -73.308739 41.372077, -73.30885 41.372038, -73.308957 41.371954, -73.309202 41.371805, -73.309255 41.371714, -73.309471 41.371574, -73.309618 41.371485, -73.30973 41.371308, -73.309849 41.371223, -73.310035 41.371173, -73.310049 41.371137, -73.309881 41.370994, -73.309743 41.370928, -73.309459 41.370835, -73.309255 41.370692, -73.30916 41.370539, -73.309153 41.370407, -73.30924 41.370187, -73.30924 41.370077, -73.309269 41.369935, -73.309379 41.369748, -73.30943 41.369578, -73.309423 41.369215, -73.309532 41.368903, -73.309597 41.368831, -73.309874 41.368716, -73.310224 41.368634, -73.310363 41.368628, -73.311026 41.368683, -73.31115 41.368683, -73.311458 41.368595, -73.311541 41.368786, -73.311621 41.368891, -73.311961 41.369145, -73.312257 41.369328, -73.312375 41.369407, -73.312454 41.369452, -73.312572 41.369532, -73.312739 41.369668, -73.312873 41.369755, -73.313017 41.369877, -73.313104 41.370194, -73.313201 41.370442, -73.313304 41.370577, -73.313452 41.370735, -73.313899 41.371153, -73.314324 41.371507, -73.314748 41.371839, -73.315244 41.37219, -73.315869 41.372632, -73.315931 41.372673, -73.316158 41.372824, -73.316313 41.372987, -73.316425 41.373106, -73.316592 41.373337, -73.316616 41.373369, -73.316765 41.373593, -73.316898 41.373835, -73.317221 41.374193, -73.317401 41.374408, -73.317526 41.37455, -73.317671 41.374767, -73.318457 41.374216, -73.318559 41.374145, -73.319254 41.373669, -73.319443 41.373511, -73.319767 41.373223, -73.320267 41.372767, -73.320667 41.372386, -73.320757 41.372314, -73.320807 41.372235, -73.320835 41.372167, -73.320838 41.37206, -73.320821 41.371949, -73.320777 41.371645, -73.320736 41.371121, -73.320749 41.370995, -73.32078 41.370913, -73.3208 41.370883, -73.32082 41.370855, -73.320877 41.370795, -73.320969 41.370721, -73.321032 41.370678, -73.321084 41.370644, -73.321576 41.370488, -73.32192 41.370389, -73.32222 41.370303, -73.322618 41.370193, -73.32291 41.370114, -73.323547 41.36997, -73.323828 41.369932, -73.324011 41.369908, -73.324239 41.369892, -73.324468 41.369876, -73.325509 41.36983, -73.325838 41.369833, -73.326443 41.369841, -73.326848 41.369849, -73.327073 41.369853, -73.327607 41.369936, -73.327943 41.369958, -73.328363 41.36995, -73.328738 41.369926, -73.329108 41.369873, -73.329368 41.36983, -73.329566 41.369787, -73.329748 41.369729, -73.329918 41.369661, -73.330313 41.369515, -73.330552 41.369407, -73.330649 41.369364, -73.331006 41.369192, -73.331404 41.369027, -73.331691 41.368827, -73.3319 41.368587, -73.331296 41.368296, -73.330992 41.36815, -73.330604 41.367929, -73.330507 41.367841, -73.330426 41.367712, -73.330387 41.367589, -73.330253 41.366902, -73.33021 41.366797, -73.330205 41.366784, -73.330138 41.366662, -73.330016 41.366482, -73.329833 41.366246, -73.329594 41.366001, -73.329378 41.365769, -73.32918 41.365439, -73.32903 41.365044, -73.329021 41.365027, -73.328689 41.364375, -73.328371 41.363957, -73.328088 41.36355, -73.328111 41.363319, -73.328131 41.363211, -73.32808 41.363096, -73.328012 41.363029, -73.327921 41.36293, -73.327813 41.362852, -73.327613 41.362764, -73.327453 41.362737, -73.327358 41.362604, -73.3272 41.362408, -73.327171 41.362379, -73.326596 41.361797, -73.326338 41.361495, -73.326052 41.36116, -73.325706 41.360696, -73.325419 41.36033, -73.325373 41.360271, -73.325279 41.360106, -73.325169 41.359875, -73.325161 41.359857, -73.324992 41.359453, -73.324862 41.359033, -73.324796 41.35885, -73.324753 41.35866, -73.324722 41.358312, -73.324714 41.358216, -73.324607 41.357787, -73.324 41.357968, -73.323721 41.35804, -73.323385 41.35811, -73.323121 41.358158, -73.323018 41.358178, -73.322642 41.358235, -73.32182 41.358376, -73.321002 41.358506, -73.320839 41.358564, -73.320726 41.358596, -73.320679 41.358644, -73.320667 41.358742, -73.320683 41.358832, -73.320743 41.358995, -73.320784 41.359168, -73.320744 41.35928, -73.320685 41.359348, -73.320209 41.359526, -73.319957 41.359601, -73.319556 41.359696, -73.319404 41.359741, -73.319229 41.359794, -73.319086 41.359874, -73.319015 41.359944, -73.318898 41.360112, -73.318872 41.36019, -73.318863 41.360302, -73.318871 41.360444, -73.318871 41.36057, -73.318882 41.36071, -73.318903 41.360848, -73.318957 41.360975, -73.318975 41.361003, -73.319099 41.361151, -73.319141 41.361308, -73.319148 41.3614, -73.319172 41.361678, -73.319156 41.361845, -73.319132 41.362009, -73.319038 41.362264, -73.318875 41.362443, -73.318844 41.362545, -73.318808 41.36273, -73.318805 41.362754, -73.31878 41.363025, -73.318788 41.363113, -73.318811 41.363233, -73.318711 41.36323, -73.318577 41.363253, -73.318412 41.363314, -73.318204 41.363429, -73.318022 41.363536, -73.317461 41.363733, -73.316471 41.364083, -73.31489 41.364625, -73.314474 41.364756, -73.314073 41.364901, -73.31332 41.365109, -73.313142 41.365159, -73.312787 41.365304, -73.312513 41.365395, -73.312243 41.36546, -73.312004 41.365492, -73.311925 41.365488, -73.311548 41.36547, -73.311223 41.365455, -73.310464 41.365465, -73.310418 41.365468, -73.310241 41.365483, -73.310044 41.36546, -73.309842 41.365702, -73.309676 41.365909, -73.309521 41.366142, -73.309373 41.366483, -73.309341 41.366524, -73.309156 41.366687, -73.308947 41.366751, -73.308794 41.366828, -73.308618 41.366925, -73.3084 41.367094, -73.30823 41.367232, -73.307868 41.367618, -73.307565 41.368024, -73.307178 41.368361, -73.306951 41.36851, -73.306726 41.368642, -73.306437 41.368759, -73.306192 41.368833, -73.30597 41.368913, -73.305875 41.368984, -73.305687 41.369277, -73.305532 41.369462, -73.305401 41.369629, -73.305325 41.369735, -73.305217 41.369915, -73.30511 41.370156, -73.305039 41.370362, -73.305005 41.370555, -73.305008 41.370775, -73.305106 41.370926, -73.305206 41.371116, -73.305223 41.37139, -73.305211 41.371682, -73.305117 41.371734, -73.304996 41.371779, -73.304837 41.371793, -73.304365 41.37181, -73.304179 41.371813, -73.304151 41.371822)), ((-73.37107 41.662121, -73.371735 41.661742, -73.372377 41.661378, -73.373081 41.660941, -73.373668 41.660507, -73.373689 41.660492, -73.373942 41.660272, -73.37424 41.659999, -73.374353 41.66005, -73.3744 41.660071, -73.374591 41.660111, -73.374635 41.660116, -73.374718 41.66012, -73.374843 41.660126, -73.374873 41.660128, -73.375172 41.660126, -73.375387 41.660112, -73.375494 41.66009, -73.375539 41.660081, -73.37575 41.660014, -73.37604 41.659889, -73.376368 41.659717, -73.376587 41.65962, -73.376917 41.65949, -73.3773 41.659312, -73.377567 41.659159, -73.377858 41.658984, -73.378002 41.658887, -73.37813 41.658791, -73.378201 41.658747, -73.378258 41.658728, -73.378356 41.658711, -73.378409 41.658703, -73.378496 41.658694, -73.378612 41.658715, -73.378781 41.658768, -73.378991 41.65885, -73.379253 41.658944, -73.379352 41.658969, -73.37953 41.658989, -73.379877 41.659005, -73.38004 41.659, -73.380247 41.658995, -73.38044 41.659002, -73.380615 41.658975, -73.380628 41.658988, -73.38133 41.659309, -73.38143 41.659356, -73.382447 41.659669, -73.383148 41.6599, -73.383199 41.659913, -73.383584 41.660037, -73.384038 41.660184, -73.384147 41.660233, -73.384308 41.660342, -73.38434 41.660282, -73.384358 41.660188, -73.384357 41.659978, -73.38431 41.659633, -73.384321 41.659517, -73.384348 41.659422, -73.38439 41.659348, -73.384528 41.659237, -73.384868 41.659007, -73.385236 41.658751, -73.385371 41.658617, -73.385484 41.658477, -73.385554 41.658332, -73.385587 41.658171, -73.38559 41.657994, -73.385559 41.657608, -73.385526 41.657434, -73.385438 41.657245, -73.385224 41.656925, -73.3848 41.65629, -73.384742 41.656178, -73.384697 41.656055, -73.384617 41.655852, -73.384553 41.65573, -73.384541 41.655706, -73.384416 41.655491, -73.384155 41.655083, -73.383974 41.654871, -73.383952 41.654853, -73.383918 41.654824, -73.383741 41.654569, -73.383537 41.654206, -73.383471 41.654044, -73.383464 41.654005, -73.383446 41.653901, -73.383439 41.653744, -73.383422 41.653634, -73.383375 41.653508, -73.383263 41.653297, -73.383219 41.653164, -73.3832 41.653048, -73.383185 41.652824, -73.383173 41.652739, -73.383131 41.652626, -73.383027 41.652438, -73.382906 41.65224, -73.382856 41.65211, -73.382812 41.652022, -73.382749 41.65196, -73.382664 41.65191, -73.382463 41.651839, -73.382638 41.65151, -73.382706 41.651385, -73.38303 41.650798, -73.383456 41.650035, -73.383524 41.649915, -73.383709 41.649587, -73.383753 41.649517, -73.383766 41.649491, -73.383828 41.649382, -73.383912 41.649191, -73.383958 41.64909, -73.384094 41.648739, -73.384157 41.648579, -73.38442 41.647658, -73.384524 41.647297, -73.384246 41.647218, -73.383955 41.647144, -73.383847 41.647127, -73.383713 41.647114, -73.383558 41.647115, -73.383404 41.647156, -73.383375 41.647164, -73.383195 41.64724, -73.38303 41.647326, -73.382905 41.647417, -73.382812 41.647505, -73.382321 41.647973, -73.382221 41.648072, -73.382165 41.648128, -73.382023 41.648258, -73.381877 41.648361, -73.381712 41.648457, -73.381534 41.648527, -73.381314 41.648601, -73.38112 41.648643, -73.380443 41.648794, -73.37991 41.648907, -73.379649 41.648981, -73.379524 41.649034, -73.379408 41.649092, -73.379295 41.649157, -73.37852 41.649758, -73.378379 41.649869, -73.37808 41.650085, -73.377919 41.650218, -73.377758 41.650326, -73.377652 41.650379, -73.377547 41.65042, -73.377528 41.650428, -73.37659 41.650769, -73.375971 41.650982, -73.375754 41.65105, -73.37543 41.651154, -73.375149 41.651235, -73.375108 41.651246, -73.374985 41.651281, -73.374945 41.651293, -73.374714 41.651368, -73.374616 41.651394, -73.37449 41.65143, -73.374123 41.651561, -73.37397 41.651636, -73.373825 41.651738, -73.373763 41.651817, -73.373734 41.651873, -73.373681 41.651976, -73.373613 41.652194, -73.37357 41.65233, -73.373516 41.652502, -73.373473 41.652595, -73.373396 41.65272, -73.373373 41.65276, -73.373308 41.652832, -73.373214 41.652949, -73.373112 41.653101, -73.373007 41.653236, -73.372857 41.653438, -73.372722 41.65362, -73.372609 41.653754, -73.372493 41.653892, -73.372432 41.653969, -73.372308 41.654131, -73.372189 41.654258, -73.372055 41.65437, -73.371971 41.654426, -73.37187 41.654481, -73.371671 41.654576, -73.371332 41.654727, -73.37121 41.654776, -73.371121 41.654811, -73.371042 41.654844, -73.370722 41.655003, -73.370622 41.655076, -73.370482 41.655202, -73.370467 41.655218, -73.370425 41.655268, -73.370412 41.655286, -73.371487 41.655905, -73.37276 41.656637, -73.374194 41.657461, -73.374769 41.65764, -73.375954 41.65801, -73.375608 41.658171, -73.375542 41.658202, -73.375344 41.658248, -73.375 41.658253, -73.374777 41.658254, -73.374564 41.658336, -73.374493 41.658349, -73.374455 41.658357, -73.374287 41.658445, -73.374236 41.658589, -73.374258 41.659206, -73.374339 41.659518, -73.374337 41.659648, -73.374337 41.659765, -73.374242 41.659931, -73.373963 41.660197, -73.373569 41.660507, -73.373122 41.660847, -73.372969 41.660921, -73.372734 41.660986, -73.372324 41.660989, -73.372039 41.661021, -73.371717 41.661147, -73.371388 41.661228, -73.371138 41.661193, -73.370963 41.66115, -73.370838 41.661157, -73.370736 41.661209, -73.370736 41.661268, -73.370487 41.661292, -73.370325 41.661387, -73.370237 41.661439, -73.370207 41.66145, -73.370143 41.661475, -73.37007 41.661484, -73.370005 41.661528, -73.369705 41.661699, -73.369251 41.662008, -73.369106 41.662141, -73.368811 41.662413, -73.368717 41.6625, -73.368585 41.662571, -73.368292 41.662572, -73.368146 41.662573, -73.367751 41.662496, -73.367494 41.662366, -73.367208 41.662069, -73.367003 41.661976, -73.366733 41.661941, -73.365656 41.661966, -73.365407 41.661949, -73.36518 41.661964, -73.364829 41.661954, -73.364675 41.661988, -73.364427 41.661926, -73.364258 41.661712, -73.364039 41.661668, -73.363636 41.661563, -73.363409 41.661529, -73.36327 41.661437, -73.363189 41.661327, -73.363029 41.661292, -73.362473 41.661305, -73.362286 41.6613, -73.362392 41.661442, -73.362421 41.661516, -73.362423 41.661572, -73.362417 41.661625, -73.362397 41.661706, -73.362272 41.662028, -73.362133 41.66228, -73.362041 41.662466, -73.362009 41.662534, -73.361644 41.66322, -73.361557 41.663371, -73.361499 41.663474, -73.361436 41.663575, -73.361345 41.663742, -73.361124 41.664279, -73.361062 41.66445, -73.360979 41.664734, -73.360911 41.664919, -73.360789 41.66517, -73.360729 41.665295, -73.360484 41.665732, -73.360406 41.665883, -73.360291 41.666165, -73.360248 41.666271, -73.360194 41.666462, -73.360137 41.66661, -73.360033 41.666819, -73.359999 41.666872, -73.359769 41.667192, -73.359607 41.667473, -73.359602 41.667482, -73.359464 41.667798, -73.359335 41.668127, -73.359219 41.668394, -73.359021 41.668804, -73.358805 41.66922, -73.358583 41.669707, -73.358546 41.669804, -73.358542 41.669849, -73.358559 41.669904, -73.358616 41.669969, -73.358654 41.669993, -73.360809 41.668608, -73.362159 41.667736, -73.363437 41.666911, -73.364476 41.66624, -73.365749 41.665419, -73.368853 41.663405, -73.369049 41.663678, -73.369143 41.663623, -73.369332 41.66354, -73.369582 41.663535, -73.369948 41.663491, -73.370043 41.663451, -73.370247 41.663274, -73.370394 41.663164, -73.370781 41.662818, -73.371038 41.662506, -73.371074 41.662151, -73.37107 41.662121)), ((-73.574578 41.484432, -73.574554 41.484501, -73.574446 41.484737, -73.574212 41.484764, -73.573977 41.484722, -73.573167 41.484491, -73.572761 41.484492, -73.572132 41.484612, -73.571641 41.484811, -73.571557 41.484986, -73.57145 41.485321, -73.571407 41.485429, -73.571099 41.485656, -73.570907 41.485723, -73.570876 41.486152, -73.570705 41.486179, -73.568677 41.493773, -73.569408 41.497022, -73.567914 41.497447, -73.566965 41.499041, -73.565845 41.500484, -73.565679 41.501771, -73.565875 41.502011, -73.565595 41.502094, -73.563129 41.501921, -73.562968 41.506607, -73.566375 41.50689, -73.56732 41.506952, -73.567965 41.50714, -73.56791 41.507705, -73.567378 41.508499, -73.566034 41.508874, -73.565587 41.510589, -73.566767 41.510733, -73.566377 41.512177, -73.563233 41.511902, -73.561963 41.519626, -73.562253 41.519787, -73.562541 41.519919, -73.562962 41.520143, -73.563509 41.520493, -73.563579 41.520534, -73.563766 41.520618, -73.564006 41.520674, -73.564387 41.520704, -73.565055 41.52071, -73.566103 41.520609, -73.5663 41.520567, -73.566469 41.520507, -73.566995 41.520269, -73.567102 41.520232, -73.567543 41.520204, -73.568004 41.520217, -73.568462 41.520231, -73.568606 41.520219, -73.568695 41.520203, -73.568913 41.520125, -73.569162 41.520012, -73.569283 41.519938, -73.569587 41.519723, -73.569823 41.519595, -73.570138 41.519479, -73.570411 41.519395, -73.570565 41.519381, -73.570767 41.519383, -73.571125 41.519423, -73.571378 41.519452, -73.571454 41.519453, -73.571517 41.519444, -73.571674 41.519384, -73.571805 41.519311, -73.572035 41.519156, -73.572531 41.518783, -73.572924 41.518435, -73.573133 41.518228, -73.573336 41.518, -73.57409 41.517051, -73.574568 41.516549, -73.574735 41.516359, -73.574815 41.516227, -73.574882 41.516072, -73.575031 41.515662, -73.575478 41.514259, -73.57557 41.5141, -73.575778 41.513863, -73.576489 41.513206, -73.576559 41.513203, -73.576801 41.513176, -73.577282 41.513036, -73.577449 41.512963, -73.577745 41.512759, -73.578188 41.512507, -73.578314 41.512464, -73.578621 41.512397, -73.57898 41.512347, -73.579549 41.512289, -73.579853 41.512299, -73.580223 41.512365, -73.583406 41.513238, -73.583748 41.513359, -73.584024 41.5129, -73.58409 41.512761, -73.584112 41.512666, -73.584103 41.512524, -73.584049 41.512404, -73.583931 41.512226, -73.583804 41.512103, -73.583583 41.511976, -73.583428 41.511921, -73.582315 41.511619, -73.582183 41.511594, -73.581699 41.511521, -73.581249 41.511501, -73.579112 41.511389, -73.578943 41.511353, -73.578868 41.511313, -73.578806 41.511261, -73.579634 41.511024, -73.579876 41.510937, -73.580118 41.510831, -73.580559 41.510673, -73.581055 41.510551, -73.581334 41.510502, -73.581486 41.510495, -73.581767 41.510524, -73.581979 41.510519, -73.582133 41.510498, -73.582243 41.510459, -73.583116 41.509956, -73.583351 41.50978, -73.583811 41.509399, -73.584104 41.509204, -73.584378 41.509059, -73.584816 41.508861, -73.585107 41.508758, -73.585205 41.508741, -73.585375 41.508736, -73.585504 41.508757, -73.585749 41.508836, -73.585964 41.508919, -73.586258 41.509036, -73.586828 41.509271, -73.587395 41.509494, -73.587558 41.509582, -73.587624 41.509624, -73.587715 41.509621, -73.587799 41.509594, -73.587814 41.509535, -73.587779 41.509435, -73.587867 41.509425, -73.587449 41.508795, -73.587116 41.508298, -73.58666 41.50762, -73.586584 41.507515, -73.586251 41.50707, -73.584688 41.505128, -73.584589 41.504991, -73.584374 41.504692, -73.58418 41.504394, -73.58381 41.503775, -73.583242 41.502795, -73.58306 41.502446, -73.582786 41.501848, -73.582328 41.500789, -73.58241 41.500572, -73.582511 41.500371, -73.582722 41.499899, -73.582811 41.499564, -73.582659 41.499334, -73.582083 41.498812, -73.581915 41.498721, -73.581727 41.498717, -73.581401 41.49865, -73.581074 41.497897, -73.580767 41.497288, -73.580236 41.496319, -73.579793 41.495487, -73.579708 41.495279, -73.579375 41.494151, -73.579278 41.493856, -73.578924 41.492926, -73.57859 41.492144, -73.578108 41.490928, -73.578078 41.490854, -73.577566 41.489545, -73.57726 41.488757, -73.577324 41.488707, -73.577342 41.488681, -73.57735 41.488627, -73.577258 41.488217, -73.577122 41.487755, -73.577058 41.487587, -73.577 41.487497, -73.576932 41.487457, -73.576814 41.487428, -73.576521 41.486482, -73.576497 41.486399, -73.576352 41.485908, -73.576156 41.485181, -73.576144 41.485138, -73.576128 41.485079, -73.576035 41.484739, -73.575901 41.484241, -73.575311 41.48435, -73.575219 41.484365, -73.575139 41.484377, -73.574813 41.484421, -73.574714 41.484431, -73.574639 41.484434, -73.574578 41.484432)), ((-73.46641 41.57172, -73.463824 41.566116, -73.462962 41.564249, -73.461367 41.560794, -73.461333 41.560719, -73.461291 41.560818, -73.461203 41.561107, -73.461103 41.561388, -73.461033 41.561543, -73.460867 41.561739, -73.460493 41.562184, -73.460424 41.562301, -73.460403 41.562391, -73.46041 41.56256, -73.460456 41.562709, -73.460664 41.563135, -73.461157 41.564086, -73.461541 41.564881, -73.461729 41.565275, -73.461954 41.565746, -73.462215 41.566187, -73.462231 41.566214, -73.46292 41.567578, -73.463031 41.567807, -73.463244 41.568246, -73.463539 41.568875, -73.463837 41.56947, -73.464139 41.570057, -73.464366 41.570519, -73.464909 41.571571, -73.46493 41.571618, -73.465126 41.572054, -73.465345 41.572388, -73.46544 41.572516, -73.465497 41.572579, -73.465547 41.572619, -73.465611 41.57267, -73.466197 41.573026, -73.466362 41.573149, -73.466543 41.573274, -73.466791 41.573411, -73.467052 41.573541, -73.467088 41.573571, -73.467124 41.573574, -73.467235 41.573584, -73.467272 41.573588, -73.46641 41.57172)), ((-73.424117 41.321817, -73.424125 41.321732, -73.424062 41.321813, -73.423927 41.321922, -73.423746 41.321994, -73.423655 41.32201, -73.423609 41.32199, -73.423388 41.321817, -73.423154 41.321738, -73.423075 41.321735, -73.423027 41.321766, -73.42301 41.321828, -73.42301 41.321977, -73.423036 41.322039, -73.423098 41.32227, -73.42308 41.32232, -73.422883 41.322467, -73.422827 41.322624, -73.422833 41.322832, -73.422921 41.322865, -73.423066 41.322876, -73.423197 41.322865, -73.423423 41.322755, -73.423685 41.322563, -73.423824 41.322541, -73.423983 41.322582, -73.424117 41.321817)), ((-73.521381 41.417979, -73.52237 41.418978, -73.52341 41.419866, -73.524157 41.419396, -73.524537 41.419113, -73.524683 41.418987, -73.524701 41.418863, -73.52466 41.418697, -73.52373 41.417555, -73.523613 41.417461, -73.523518 41.417445, -73.521615 41.417884, -73.521381 41.417979)), ((-73.386405 41.643953, -73.386258 41.643979, -73.386028 41.644291, -73.385589 41.644019, -73.385134 41.644559, -73.38562 41.644848, -73.385459 41.645069, -73.385359 41.645196, -73.385221 41.645374, -73.385327 41.645507, -73.385343 41.64548, -73.385508 41.645239, -73.385539 41.645194, -73.386034 41.644476, -73.386186 41.644261, -73.386405 41.643953)), ((-73.537093 41.439706, -73.537117 41.439786, -73.537164 41.43965, -73.537228 41.439523, -73.537284 41.439365, -73.53733 41.439024, -73.537369 41.438363, -73.537377 41.43834, -73.537367 41.438013, -73.537352 41.437612, -73.53739 41.436811, -73.538392 41.436915, -73.5386 41.436915, -73.538781 41.436887, -73.538931 41.436843, -73.539016 41.436805, -73.539147 41.43673, -73.539486 41.436467, -73.539744 41.436307, -73.54059 41.435977, -73.541405 41.435707, -73.541959 41.43549, -73.542183 41.435436, -73.542561 41.435387, -73.542643 41.435692, -73.542716 41.435811, -73.542805 41.435909, -73.542905 41.435983, -73.543068 41.436059, -73.543158 41.43609, -73.54335 41.43614, -73.54388 41.436253, -73.544064 41.436323, -73.544325 41.436472, -73.544481 41.436573, -73.544606 41.436637, -73.5447 41.436674, -73.544863 41.436709, -73.545016 41.436713, -73.54519 41.436687, -73.54533 41.436647, -73.545489 41.436561, -73.545665 41.436398, -73.546309 41.43564, -73.546399 41.435512, -73.546469 41.435347, -73.54651 41.435125, -73.546539 41.434254, -73.546522 41.434032, -73.54648 41.433723, -73.54632 41.432378, -73.546268 41.431962, -73.546203 41.431809, -73.546084 41.431685, -73.545934 41.431582, -73.545782 41.431519, -73.545623 41.431482, -73.545532 41.431474, -73.545345 41.431483, -73.54497 41.431549, -73.543932 41.431747, -73.543734 41.431797, -73.543492 41.431903, -73.543357 41.431994, -73.543159 41.432171, -73.543118 41.432211, -73.54304 41.432172, -73.542772 41.432046, -73.542371 41.431876, -73.542379 41.431613, -73.542204 41.431395, -73.542257 41.431177, -73.542458 41.430606, -73.542707 41.429968, -73.542724 41.429397, -73.543152 41.428992, -73.543516 41.428656, -73.544009 41.428078, -73.544547 41.427599, -73.544987 41.427345, -73.545349 41.427077, -73.545642 41.426925, -73.546059 41.426543, -73.546204 41.426137, -73.546068 41.426321, -73.545939 41.426442, -73.545824 41.426525, -73.545411 41.426751, -73.545287 41.426816, -73.544949 41.426992, -73.544803 41.427047, -73.544539 41.427102, -73.54441 41.427109, -73.544245 41.427107, -73.543709 41.427044, -73.543324 41.426999, -73.543169 41.427006, -73.54303 41.427034, -73.542838 41.427084, -73.54266 41.427157, -73.542347 41.427323, -73.542104 41.427459, -73.541796 41.427703, -73.541694 41.427837, -73.541559 41.42814, -73.541462 41.428306, -73.541213 41.42863, -73.541059 41.428807, -73.541051 41.428821, -73.54096 41.428937, -73.540437 41.429426, -73.539843 41.429932, -73.539454 41.430267, -73.539083 41.430576, -73.538954 41.430681, -73.538835 41.430761, -73.538699 41.430834, -73.53856 41.43088, -73.538368 41.430972, -73.538137 41.430623, -73.538077 41.430532, -73.538066 41.430511, -73.537888 41.431885, -73.537742 41.433255, -73.537731 41.433361, -73.537673 41.433905, -73.537632 41.434301, -73.537509 41.435493, -73.537469 41.43589, -73.537261 41.43791, -73.537093 41.439706)), ((-73.552831 41.45493, -73.552591 41.45624, -73.55251 41.456685, -73.552352 41.457571, -73.552344 41.457707, -73.552371 41.457831, -73.552457 41.458027, -73.552559 41.458158, -73.55268 41.45826, -73.552973 41.458444, -73.553148 41.45858, -73.553313 41.458528, -73.553536 41.458489, -73.554019 41.458406, -73.554377 41.458362, -73.554585 41.458365, -73.554835 41.458411, -73.554991 41.458469, -73.555168 41.458564, -73.555424 41.458762, -73.556129 41.459384, -73.556548 41.459708, -73.556665 41.459797, -73.556809 41.459732, -73.557021 41.459641, -73.55726 41.459577, -73.557376 41.459677, -73.557319 41.45977, -73.556869 41.459951, -73.557043 41.460084, -73.557203 41.460228, -73.557235 41.460257, -73.557374 41.460413, -73.557469 41.460549, -73.557495 41.460587, -73.557551 41.460689, -73.557565 41.460731, -73.557649 41.460984, -73.557714 41.461392, -73.557782 41.461815, -73.557796 41.461901, -73.557882 41.462508, -73.557936 41.462731, -73.558036 41.462969, -73.558138 41.463126, -73.5582 41.463203, -73.558308 41.463319, -73.558837 41.463754, -73.55945 41.464345, -73.559874 41.464685, -73.560511 41.465069, -73.560921 41.465291, -73.561433 41.465519, -73.562602 41.465957, -73.562905 41.4661, -73.562959 41.466132, -73.563142 41.466243, -73.563399 41.466449, -73.563564 41.466623, -73.563737 41.466867, -73.563895 41.46717, -73.564145 41.467742, -73.564227 41.468052, -73.564336 41.46895, -73.564425 41.4693, -73.564622 41.469775, -73.56479 41.470146, -73.565171 41.470989, -73.565239 41.47114, -73.565356 41.4714, -73.565726 41.472221, -73.565959 41.472666, -73.5662 41.473075, -73.566824 41.474086, -73.567125 41.474562, -73.567408 41.47505, -73.567548 41.475363, -73.568491 41.477822, -73.568531 41.477972, -73.568544 41.478119, -73.568511 41.478351, -73.568416 41.478746, -73.568405 41.47888, -73.5684 41.478933, -73.568513 41.479742, -73.568688 41.480667, -73.568782 41.480986, -73.568875 41.481218, -73.56911 41.481696, -73.569652 41.482697, -73.570171 41.483487, -73.570665 41.484227, -73.57089 41.48456, -73.571343 41.484494, -73.571683 41.484408, -73.572244 41.484213, -73.572461 41.484156, -73.572755 41.484114, -73.572944 41.484108, -73.573145 41.484123, -73.573409 41.484171, -73.573848 41.484298, -73.574251 41.484397, -73.574436 41.484427, -73.57446 41.484428, -73.574578 41.484432, -73.574877 41.483769, -73.574601 41.483712, -73.574651 41.483174, -73.573947 41.483044, -73.574185 41.482482, -73.574712 41.481066, -73.575289 41.481079, -73.575311 41.481078, -73.57551 41.481203, -73.575636 41.481195, -73.575658 41.481193, -73.575763 41.481126, -73.575813 41.481065, -73.575831 41.481006, -73.575844 41.480964, -73.575927 41.48078, -73.575928 41.480633, -73.575908 41.480616, -73.57578 41.480593, -73.575739 41.480572, -73.575716 41.480529, -73.575719 41.480424, -73.575773 41.480346, -73.575813 41.480326, -73.575891 41.480336, -73.575937 41.480386, -73.576022 41.480401, -73.57608 41.480394, -73.576127 41.480404, -73.576401 41.480549, -73.576441 41.480589, -73.576512 41.480603, -73.576556 41.480599, -73.576627 41.480572, -73.576661 41.480505, -73.576661 41.48037, -73.576617 41.480218, -73.576613 41.480002, -73.576634 41.479945, -73.576671 41.479935, -73.576779 41.479928, -73.576944 41.479962, -73.577059 41.479959, -73.577072 41.479932, -73.577076 41.479851, -73.577062 41.479706, -73.577079 41.479665, -73.577103 41.479655, -73.577224 41.479648, -73.577219 41.479621, -73.577248 41.47953, -73.577292 41.4795, -73.57745 41.479486, -73.577481 41.47947, -73.577518 41.479429, -73.577555 41.479352, -73.577614 41.479081, -73.57764 41.479042, -73.577717 41.478984, -73.577808 41.478943, -73.577933 41.478842, -73.577997 41.478768, -73.578108 41.478572, -73.578193 41.478293, -73.57824 41.478218, -73.578348 41.478122, -73.578399 41.478046, -73.578345 41.478024, -73.577849 41.477825, -73.577687 41.477717, -73.577552 41.477663, -73.577471 41.477569, -73.577336 41.477447, -73.577336 41.477285, -73.577417 41.477204, -73.577673 41.477016, -73.577984 41.476651, -73.578105 41.476557, -73.578132 41.476422, -73.578105 41.476388, -73.578011 41.476375, -73.577937 41.476354, -73.577862 41.476321, -73.577734 41.476213, -73.5777 41.476166, -73.577619 41.476091, -73.577559 41.476004, -73.577538 41.475903, -73.577572 41.475849, -73.577613 41.475842, -73.577741 41.475788, -73.577775 41.475754, -73.577795 41.47568, -73.577775 41.475606, -73.577748 41.475565, -73.577694 41.475525, -73.577613 41.475491, -73.577545 41.475484, -73.577484 41.475457, -73.577397 41.475403, -73.577289 41.475356, -73.577221 41.475295, -73.577154 41.475215, -73.577052 41.475059, -73.576971 41.475005, -73.576803 41.475005, -73.576755 41.475019, -73.576654 41.475089, -73.576512 41.475275, -73.576465 41.475316, -73.576303 41.475356, -73.576215 41.475356, -73.576094 41.475343, -73.575966 41.475282, -73.575959 41.475161, -73.576006 41.474985, -73.576013 41.474884, -73.576067 41.474702, -73.576067 41.474661, -73.576053 41.474634, -73.575979 41.474601, -73.575945 41.474601, -73.575736 41.474547, -73.575621 41.474493, -73.575523 41.474462, -73.575466 41.474432, -73.575432 41.474432, -73.575304 41.474614, -73.575216 41.474682, -73.575169 41.474682, -73.575095 41.474641, -73.574919 41.474466, -73.574879 41.474351, -73.574814 41.474241, -73.574692 41.474119, -73.574568 41.474048, -73.57442 41.474034, -73.574302 41.474002, -73.574177 41.473987, -73.573974 41.473946, -73.57386 41.473973, -73.57361 41.474077, -73.573367 41.474108, -73.573306 41.474088, -73.573232 41.474034, -73.573185 41.473973, -73.573151 41.473899, -73.573077 41.473818, -73.573036 41.473791, -73.572813 41.473717, -73.572496 41.473677, -73.572321 41.473683, -73.572233 41.473669, -73.572159 41.473643, -73.572051 41.473589, -73.57199 41.473542, -73.57197 41.473461, -73.57197 41.473387, -73.57201 41.473285, -73.57199 41.473218, -73.571949 41.473171, -73.57174 41.473083, -73.571673 41.473029, -73.571605 41.472948, -73.571546 41.47283, -73.571437 41.472714, -73.571349 41.472486, -73.571244 41.472267, -73.571201 41.472222, -73.571166 41.472139, -73.571166 41.472051, -73.571139 41.471932, -73.571146 41.471862, -73.571116 41.471795, -73.571079 41.471768, -73.570799 41.471761, -73.570721 41.471727, -73.570583 41.471636, -73.570491 41.471505, -73.570397 41.471407, -73.570353 41.47138, -73.570215 41.471349, -73.569611 41.4714, -73.569449 41.471306, -73.569327 41.471211, -73.569239 41.471073, -73.569212 41.470911, -73.56927 41.470729, -73.569266 41.470688, -73.569253 41.470672, -73.569162 41.470645, -73.569094 41.470641, -73.568895 41.470665, -73.568801 41.470719, -73.56876 41.470776, -73.568753 41.470867, -73.56877 41.470921, -73.568872 41.471043, -73.568885 41.471086, -73.568875 41.471164, -73.568753 41.471268, -73.568636 41.471311, -73.568586 41.471296, -73.568559 41.471269, -73.568539 41.470952, -73.568498 41.470807, -73.568424 41.470645, -73.568218 41.470402, -73.568026 41.470243, -73.567979 41.470142, -73.567989 41.470079, -73.568074 41.46998, -73.568168 41.469926, -73.568201 41.469879, -73.568208 41.469818, -73.568168 41.46971, -73.567938 41.469602, -73.567904 41.469596, -73.567817 41.469528, -73.567722 41.469387, -73.567655 41.469171, -73.567628 41.468975, -73.567722 41.468739, -73.56779 41.468658, -73.568255 41.468678, -73.568429 41.468657, -73.568505 41.468611, -73.568539 41.468577, -73.568559 41.468523, -73.568573 41.468415, -73.568438 41.46822, -73.568444 41.468011, -73.568465 41.467984, -73.568775 41.467822, -73.569123 41.467552, -73.569254 41.467417, -73.569389 41.467221, -73.56943 41.467134, -73.569437 41.466985, -73.56943 41.466884, -73.569376 41.466763, -73.569257 41.466639, -73.569092 41.466527, -73.568883 41.466412, -73.568492 41.466243, -73.567992 41.465994, -73.567837 41.465859, -73.567816 41.465775, -73.567743 41.465675, -73.567688 41.465427, -73.567533 41.465049, -73.56752 41.464995, -73.567493 41.464948, -73.567378 41.464854, -73.56729 41.464807, -73.567223 41.4648, -73.567162 41.46478, -73.566993 41.464773, -73.566919 41.464759, -73.566831 41.464766, -73.566771 41.464793, -73.566534 41.464854, -73.56644 41.464908, -73.565691 41.465225, -73.565468 41.465225, -73.565414 41.465178, -73.565353 41.465097, -73.565313 41.46482, -73.565326 41.464753, -73.565421 41.464672, -73.56565 41.46451, -73.565873 41.464287, -73.566048 41.464071, -73.566096 41.46399, -73.566109 41.463869, -73.566096 41.463801, -73.566001 41.463673, -73.565508 41.463221, -73.565218 41.462898, -73.56513 41.462823, -73.565049 41.462769, -73.564968 41.462736, -73.564874 41.462715, -73.564793 41.462715, -73.564658 41.462749, -73.564604 41.462783, -73.564388 41.462871, -73.56428 41.462938, -73.564057 41.463127, -73.563801 41.46337, -73.563747 41.46341, -73.563639 41.463457, -73.563477 41.463471, -73.563348 41.463471, -73.563105 41.46343, -73.563018 41.463397, -73.562446 41.463098, -73.562384 41.462929, -73.562334 41.462648, -73.562334 41.462383, -73.562323 41.462261, -73.562168 41.46181, -73.561985 41.461426, -73.561924 41.461344, -73.561837 41.461229, -73.561499 41.460588, -73.561478 41.460384, -73.561492 41.460257, -73.56152 41.460151, -73.561576 41.460046, -73.561774 41.459806, -73.561943 41.459537, -73.562041 41.459427, -73.562067 41.459291, -73.562073 41.459193, -73.562034 41.459051, -73.56193 41.458756, -73.561748 41.457988, -73.561642 41.457256, -73.561602 41.45688, -73.561538 41.45666, -73.561474 41.45651, -73.561382 41.456383, -73.561247 41.45624, -73.561112 41.456134, -73.560799 41.455935, -73.559757 41.455388, -73.559466 41.455267, -73.559281 41.455203, -73.558911 41.455139, -73.558584 41.455132, -73.558349 41.455153, -73.558186 41.455246, -73.558129 41.455295, -73.558108 41.455345, -73.558051 41.455573, -73.557973 41.455743, -73.557909 41.455807, -73.557852 41.455828, -73.557677 41.45585, -73.557528 41.455821, -73.557457 41.455786, -73.557329 41.455686, -73.557279 41.455658, -73.557144 41.455651, -73.557066 41.455672, -73.556945 41.455743, -73.556874 41.455743, -73.556803 41.455729, -73.555772 41.455011, -73.555349 41.454663, -73.555278 41.454585, -73.555249 41.454521, -73.555235 41.454287, -73.555199 41.454216, -73.555157 41.454187, -73.5551 41.45418, -73.554979 41.454216, -73.554908 41.454216, -73.554844 41.45418, -73.554594 41.45378, -73.552977 41.454859, -73.552831 41.45493)), ((-73.554984 41.449365, -73.557497 41.449179, -73.574555 41.447491, -73.574971 41.447465, -73.575408 41.447409, -73.576273 41.447334, -73.576258 41.447093, -73.57619 41.445967, -73.576144 41.445683, -73.576013 41.445153, -73.575477 41.443702, -73.574889 41.442142, -73.574715 41.441637, -73.574601 41.441207, -73.57455 41.440851, -73.574532 41.440624, -73.57454 41.440218, -73.574573 41.439896, -73.574597 41.439725, -73.574943 41.437797, -73.574983 41.43757, -73.575309 41.435718, -73.57538 41.435058, -73.575452 41.434263, -73.575478 41.433925, -73.575508 41.433551, -73.575584 41.432914, -73.575637 41.432671, -73.575669 41.43252, -73.575852 41.431959, -73.576784 41.429668, -73.57684 41.429526, -73.577218 41.42857, -73.577346 41.428595, -73.57757 41.428587, -73.577901 41.428541, -73.57842 41.428416, -73.578676 41.428389, -73.578925 41.4284, -73.579214 41.428447, -73.579551 41.42853, -73.579557 41.428335, -73.579405 41.428337, -73.579128 41.428211, -73.578684 41.428042, -73.578248 41.427922, -73.577861 41.42778, -73.577604 41.427621, -73.577726 41.427198, -73.577769 41.426934, -73.577801 41.426599, -73.577804 41.426542, -73.577791 41.426071, -73.577636 41.424667, -73.577555 41.423857, -73.577475 41.423188, -73.577438 41.422899, -73.577363 41.422586, -73.577266 41.422289, -73.576976 41.421593, -73.576829 41.421242, -73.576477 41.420397, -73.576283 41.419904, -73.5759 41.418911, -73.575794 41.418687, -73.575622 41.418425, -73.575055 41.417611, -73.57494 41.417417, -73.574833 41.417191, -73.574567 41.416475, -73.57449 41.416184, -73.574461 41.415939, -73.574479 41.41572, -73.574597 41.415298, -73.574022 41.415123, -73.573017 41.414847, -73.572661 41.414755, -73.572334 41.414634, -73.572159 41.414536, -73.571948 41.414375, -73.571241 41.413794, -73.570418 41.413156, -73.56893 41.412007, -73.568555 41.411756, -73.566305 41.410438, -73.566131 41.410354, -73.566076 41.41009, -73.566129 41.40987, -73.566173 41.409429, -73.566353 41.407999, -73.56647 41.40691, -73.566495 41.406819, -73.56653 41.406792, -73.56678 41.406674, -73.566018 41.406629, -73.565722 41.406648, -73.56554 41.406672, -73.564646 41.406848, -73.564436 41.406927, -73.564309 41.406993, -73.563981 41.407201, -73.563631 41.40745, -73.563325 41.407734, -73.563164 41.407913, -73.56305 41.408095, -73.562628 41.408996, -73.562543 41.409244, -73.562481 41.409589, -73.562409 41.409886, -73.562326 41.410171, -73.562317 41.410244, -73.56213 41.410281, -73.562152 41.410365, -73.562176 41.410406, -73.56229 41.410507, -73.562665 41.411018, -73.562813 41.411095, -73.562831 41.411149, -73.562834 41.411208, -73.562902 41.411356, -73.562985 41.411386, -73.56306 41.411553, -73.563289 41.411646, -73.56351 41.411692, -73.563753 41.411727, -73.563904 41.411855, -73.564009 41.411971, -73.564102 41.412151, -73.564136 41.412302, -73.564113 41.412429, -73.564113 41.412615, -73.564291 41.41294, -73.564403 41.413148, -73.564409 41.413171, -73.564415 41.413195, -73.564403 41.413415, -73.564322 41.413647, -73.564287 41.413856, -73.564299 41.414111, -73.564345 41.414285, -73.564415 41.414436, -73.564427 41.414622, -73.564519 41.414998, -73.564577 41.41509, -73.564693 41.415241, -73.564774 41.415369, -73.564751 41.415473, -73.564623 41.415554, -73.564507 41.415566, -73.564333 41.415543, -73.564171 41.415473, -73.563985 41.415415, -73.563845 41.415427, -73.563834 41.415531, -73.56388 41.415705, -73.56395 41.415891, -73.56402 41.415995, -73.564147 41.416088, -73.564252 41.416216, -73.564298 41.416366, -73.564368 41.416529, -73.564321 41.416656, -73.564205 41.416761, -73.564078 41.416854, -73.563962 41.416958, -73.563834 41.41719, -73.563741 41.417248, -73.563578 41.417329, -73.563451 41.417422, -73.563381 41.417503, -73.563346 41.417677, -73.563358 41.41777, -73.563532 41.417979, -73.563683 41.418095, -73.563764 41.418222, -73.563799 41.418361, -73.563753 41.418419, -73.563648 41.418461, -73.563427 41.418472, -73.563265 41.418507, -73.563241 41.418588, -73.563276 41.418704, -73.563404 41.418728, -73.563566 41.41882, -73.563593 41.418867, -73.563613 41.418902, -73.563613 41.418983, -73.56352 41.419099, -73.563415 41.419134, -73.563218 41.41918, -73.562974 41.419203, -73.5628 41.419284, -73.562812 41.419435, -73.562858 41.419516, -73.562881 41.419598, -73.562916 41.419667, -73.562986 41.41969, -73.563253 41.419574, -73.563485 41.419505, -73.563648 41.419505, -73.563764 41.419632, -73.563822 41.419772, -73.563729 41.419922, -73.563439 41.420062, -73.563415 41.420189, -73.563474 41.420328, -73.56359 41.420456, -73.56381 41.420502, -73.564077 41.420491, -73.564309 41.420456, -73.56446 41.420444, -73.564576 41.420468, -73.564762 41.420595, -73.564809 41.420769, -73.564704 41.420885, -73.564495 41.420897, -73.564367 41.420932, -73.564321 41.420978, -73.564251 41.421117, -73.564228 41.421361, -73.564228 41.421593, -73.564251 41.42179, -73.564309 41.421941, -73.564391 41.422068, -73.564553 41.422498, -73.564542 41.422625, -73.564494 41.422713, -73.564309 41.422887, -73.564111 41.423096, -73.563856 41.423293, -73.563601 41.423572, -73.563275 41.42378, -73.563055 41.423827, -73.562904 41.423954, -73.562892 41.424082, -73.562916 41.424163, -73.563055 41.424244, -73.563208 41.424262, -73.563496 41.424314, -73.563647 41.424407, -73.563728 41.424523, -73.563763 41.424662, -73.563693 41.424766, -73.563508 41.42479, -73.56345 41.424766, -73.563322 41.424662, -73.563194 41.424592, -73.56302 41.424569, -73.56266 41.42465, -73.562544 41.424732, -73.562556 41.424859, -73.562614 41.424987, -73.562707 41.425045, -73.562846 41.42508, -73.563322 41.42508, -73.563496 41.425091, -73.563612 41.425184, -73.563659 41.425288, -73.563635 41.42537, -73.563496 41.425451, -73.563299 41.42552, -73.563066 41.425567, -73.562823 41.425544, -73.562637 41.425555, -73.562553 41.425588, -73.562373 41.426117, -73.562325 41.426183, -73.562231 41.426249, -73.561968 41.426304, -73.561902 41.426328, -73.561778 41.426408, -73.561535 41.426619, -73.561435 41.426667, -73.561352 41.426695, -73.560857 41.426735, -73.560753 41.426759, -73.56068 41.426787, -73.56059 41.426835, -73.560445 41.426953, -73.560392 41.427015, -73.560088 41.427543, -73.560029 41.427602, -73.559942 41.427664, -73.559793 41.427716, -73.558432 41.428313, -73.558336 41.428337, -73.558166 41.428354, -73.558097 41.428375, -73.558055 41.428399, -73.557979 41.428482, -73.557754 41.429014, -73.557688 41.429086, -73.557574 41.429176, -73.557359 41.429452, -73.557328 41.429517, -73.55731 41.429597, -73.557297 41.429846, -73.55731 41.42996, -73.557671 41.43042, -73.557723 41.430552, -73.557709 41.430843, -73.557681 41.430929, -73.55766 41.430943, -73.557626 41.430946, -73.557304 41.43087, -73.557231 41.43086, -73.557144 41.43086, -73.557106 41.430884, -73.557054 41.430943, -73.557033 41.430998, -73.557061 41.431637, -73.556985 41.432023, -73.556755 41.432857, -73.556379 41.433652, -73.555841 41.434565, -73.555742 41.43468, -73.555664 41.434732, -73.555471 41.4348, -73.555309 41.43482, -73.555116 41.434805, -73.554826 41.434745, -73.554481 41.434694, -73.554229 41.43467, -73.554062 41.434697, -73.553959 41.434739, -73.553862 41.434815, -73.553799 41.434898, -73.553765 41.434988, -73.553751 41.435106, -73.553772 41.435493, -73.553862 41.435652, -73.553929 41.435913, -73.554131 41.436447, -73.554217 41.436555, -73.554269 41.4366, -73.554362 41.436652, -73.554421 41.436703, -73.554538 41.436876, -73.554673 41.437326, -73.554787 41.437646, -73.554794 41.437774, -73.55478 41.437817, -73.554652 41.437959, -73.554624 41.43803, -73.554624 41.438094, -73.554638 41.438137, -73.554688 41.438172, -73.555164 41.4383, -73.555199 41.438357, -73.555214 41.438428, -73.555157 41.438584, -73.555093 41.438648, -73.554901 41.438741, -73.554318 41.438897, -73.55404 41.439004, -73.554012 41.439039, -73.554019 41.439117, -73.554083 41.439139, -73.554528 41.439188, -73.554577 41.43921, -73.554613 41.439252, -73.55462 41.439288, -73.554492 41.439473, -73.554449 41.439501, -73.554385 41.439515, -73.554165 41.439409, -73.554094 41.439416, -73.554008 41.439437, -73.553959 41.439458, -73.553895 41.439515, -73.553888 41.439615, -73.553916 41.439671, -73.554023 41.439764, -73.554087 41.439835, -73.554115 41.43992, -73.554101 41.439991, -73.554072 41.440041, -73.55403 41.440069, -73.553952 41.440069, -73.553745 41.440041, -73.553617 41.440041, -73.553568 41.440062, -73.553525 41.440105, -73.553433 41.440268, -73.553433 41.440311, -73.553447 41.440353, -73.553511 41.440389, -73.553966 41.440432, -73.554051 41.440481, -73.554129 41.440567, -73.554172 41.440631, -73.554172 41.440716, -73.554151 41.440737, -73.554087 41.440758, -73.553856 41.44073, -73.553756 41.440744, -73.553699 41.440766, -73.553649 41.44083, -73.553529 41.441028, -73.553429 41.441163, -73.553415 41.44122, -73.553443 41.441398, -73.553486 41.441441, -73.55355 41.441448, -73.553728 41.44137, -73.553848 41.441355, -73.55392 41.441355, -73.553976 41.441384, -73.553991 41.441448, -73.553962 41.441497, -73.553877 41.44154, -73.553557 41.441654, -73.553422 41.441796, -73.553358 41.441909, -73.55328 41.442151, -73.55328 41.442251, -73.553308 41.442322, -73.553386 41.44235, -73.55345 41.442314, -73.553806 41.441988, -73.553884 41.441931, -73.553955 41.441909, -73.554112 41.441902, -73.554161 41.441924, -73.554275 41.442116, -73.554318 41.442151, -73.554382 41.442151, -73.554723 41.442066, -73.554808 41.442066, -73.55488 41.442094, -73.554951 41.442137, -73.555022 41.442208, -73.555043 41.442279, -73.555036 41.442357, -73.554993 41.442457, -73.554936 41.442513, -73.554814 41.442595, -73.554659 41.442698, -73.554527 41.442833, -73.554456 41.442961, -73.554407 41.443082, -73.554364 41.443281, -73.554357 41.443394, -73.554335 41.443515, -73.554279 41.443622, -73.554207 41.443721, -73.554129 41.443792, -73.55408 41.443878, -73.55408 41.443949, -73.554108 41.444027, -73.554207 41.444077, -73.554471 41.444126, -73.555111 41.444197, -73.555182 41.44424, -73.555203 41.444268, -73.555217 41.444318, -73.55521 41.444389, -73.555175 41.444432, -73.55505 41.444496, -73.554993 41.444553, -73.554972 41.444588, -73.554951 41.444659, -73.554958 41.444801, -73.554986 41.444865, -73.555071 41.444965, -73.555334 41.445185, -73.55542 41.445291, -73.555619 41.445583, -73.555718 41.445767, -73.556398 41.447189, -73.55645 41.447261, -73.556067 41.447851, -73.55517 41.449103, -73.554984 41.449365)), ((-73.371634 41.344876, -73.371902 41.344748, -73.372062 41.344633, -73.372252 41.344354, -73.37249 41.344149, -73.372582 41.344072, -73.372822 41.343953, -73.373089 41.343794, -73.373138 41.343773, -73.37344 41.343646, -73.373795 41.343489, -73.374206 41.343355, -73.374603 41.343244, -73.374861 41.343119, -73.37507 41.342996, -73.37529 41.342817, -73.375411 41.342577, -73.375577 41.342033, -73.375713 41.341468, -73.375825 41.340781, -73.375717 41.340528, -73.375507 41.340346, -73.375435 41.34021, -73.375418 41.339876, -73.375425 41.33971, -73.375432 41.339583, -73.375501 41.339263, -73.37577 41.338818, -73.375903 41.338614, -73.375968 41.338526, -73.376039 41.3384, -73.376173 41.338255, -73.376221 41.338206, -73.376309 41.338117, -73.376341 41.338104, -73.376589 41.338005, -73.376643 41.337992, -73.3767 41.337837, -73.378245 41.336878, -73.378138 41.336766, -73.377787 41.336183, -73.377572 41.335762, -73.376918 41.334631, -73.376738 41.334229, -73.376179 41.333387, -73.375854 41.332973, -73.375505 41.332594, -73.375215 41.332256, -73.374956 41.331996, -73.37485 41.33189, -73.374643 41.331717, -73.374561 41.331634, -73.374108 41.331174, -73.373638 41.330708, -73.373246 41.330297, -73.372604 41.329625, -73.372455 41.329468, -73.372397 41.32941, -73.371989 41.329002, -73.371788 41.328818, -73.371756 41.328788, -73.371537 41.328587, -73.371013 41.328173, -73.370602 41.327826, -73.370506 41.327745, -73.370235 41.327445, -73.370005 41.327211, -73.369919 41.327056, -73.369838 41.326862, -73.36974 41.326341, -73.369666 41.325796, -73.369645 41.325713, -73.36959 41.325608, -73.36945 41.325452, -73.368917 41.325073, -73.36873 41.324951, -73.367977 41.324461, -73.367714 41.324303, -73.367414 41.324305, -73.366514 41.324313, -73.366215 41.324316, -73.366075 41.324301, -73.365915 41.324291, -73.365787 41.324251, -73.365689 41.324221, -73.365326 41.324013, -73.364864 41.323664, -73.364696 41.323547, -73.36434 41.323299, -73.363996 41.323007, -73.363787 41.322892, -73.363529 41.322759, -73.363212 41.322659, -73.363006 41.322641, -73.362985 41.32264, -73.362748 41.322639, -73.36113 41.322677, -73.359115 41.322644, -73.35845 41.322643, -73.357131 41.322643, -73.356933 41.322623, -73.356869 41.322607, -73.356788 41.322587, -73.356681 41.322555, -73.356619 41.322537, -73.356379 41.322379, -73.356246 41.322278, -73.355898 41.321796, -73.355614 41.321403, -73.354969 41.320499, -73.35447 41.319744, -73.354104 41.319213, -73.353942 41.318977, -73.353703 41.318658, -73.353511 41.31835, -73.352947 41.317499, -73.352733 41.317175, -73.35279 41.31713, -73.352926 41.316996, -73.352977 41.316924, -73.353065 41.316816, -73.353162 41.316739, -73.353168 41.316636, -73.353109 41.316543, -73.353007 41.316477, -73.352855 41.316428, -73.352649 41.316398, -73.352404 41.316391, -73.352179 41.316377, -73.351702 41.315697, -73.351212 41.31498, -73.350898 41.31452, -73.350652 41.314129, -73.35058 41.313991, -73.350455 41.313782, -73.349844 41.312754, -73.349733 41.312566, -73.349624 41.312423, -73.34928 41.311972, -73.349108 41.311745, -73.348566 41.310969, -73.348396 41.310713, -73.348339 41.310563, -73.34828 41.310407, -73.348151 41.310872, -73.348029 41.311344, -73.347855 41.311496, -73.347242 41.31171, -73.346668 41.311884, -73.34635 41.312004, -73.346248 41.312041, -73.346226 41.31205, -73.346121 41.31209, -73.346107 41.312099, -73.345314 41.312601, -73.344439 41.313029, -73.344135 41.312948, -73.344237 41.313161, -73.344399 41.313281, -73.344578 41.313335, -73.344766 41.313389, -73.344856 41.31342, -73.344994 41.31351, -73.345126 41.313588, -73.345263 41.313723, -73.345298 41.31379, -73.345324 41.313866, -73.345342 41.314045, -73.34537 41.314171, -73.345452 41.314236, -73.345495 41.31432, -73.345582 41.314528, -73.345585 41.314676, -73.345605 41.314757, -73.345653 41.315178, -73.345775 41.315474, -73.345942 41.315681, -73.345785 41.315736, -73.345546 41.315821, -73.345192 41.31593, -73.344971 41.316011, -73.344665 41.316087, -73.344613 41.316098, -73.34452 41.316119, -73.344299 41.316091, -73.344176 41.316053, -73.344046 41.316112, -73.343979 41.316204, -73.343953 41.316273, -73.343842 41.316299, -73.343516 41.316334, -73.343329 41.316393, -73.343148 41.316466, -73.342966 41.316545, -73.342413 41.316786, -73.342028 41.31688, -73.341785 41.316965, -73.341667 41.317, -73.341626 41.317012, -73.341825 41.317274, -73.342119 41.31759, -73.342217 41.317625, -73.342212 41.31769, -73.342369 41.317965, -73.342305 41.318108, -73.341955 41.318259, -73.341944 41.318376, -73.341982 41.318442, -73.342167 41.318764, -73.342247 41.319006, -73.342221 41.319364, -73.342025 41.319609, -73.341919 41.319821, -73.341992 41.320134, -73.342116 41.320348, -73.342218 41.320584, -73.342262 41.320634, -73.342597 41.320705, -73.342837 41.320886, -73.342932 41.321012, -73.342954 41.321111, -73.342947 41.321221, -73.343034 41.32127, -73.343187 41.321298, -73.343402 41.321347, -73.343358 41.321426, -73.343296 41.321539, -73.343347 41.321643, -73.343435 41.321748, -73.343865 41.321879, -73.343996 41.321951, -73.344178 41.322192, -73.344571 41.322505, -73.344746 41.322554, -73.344983 41.322554, -73.345619 41.322554, -73.345802 41.322554, -73.345897 41.322604, -73.346152 41.322724, -73.346356 41.322768, -73.346487 41.322779, -73.346552 41.322806, -73.346916 41.323064, -73.347157 41.323306, -73.347456 41.323728, -73.347652 41.324118, -73.347762 41.32436, -73.347842 41.324442, -73.348432 41.324815, -73.348804 41.325101, -73.348898 41.325507, -73.348942 41.325556, -73.349343 41.325765, -73.349459 41.32588, -73.349591 41.326138, -73.349787 41.326253, -73.349897 41.326341, -73.349977 41.32644, -73.350021 41.326549, -73.349933 41.326742, -73.349431 41.327351, -73.34938 41.327466, -73.349373 41.327703, -73.34939 41.327739, -73.349424 41.327812, -73.349584 41.328015, -73.349949 41.328416, -73.349985 41.328504, -73.349978 41.328597, -73.349701 41.328927, -73.349585 41.329168, -73.349585 41.329371, -73.349694 41.329481, -73.349811 41.329509, -73.350066 41.329509, -73.350265 41.329564, -73.350326 41.329582, -73.350683 41.329088, -73.350834 41.328848, -73.350969 41.328594, -73.351061 41.328424, -73.351184 41.328126, -73.351349 41.327756, -73.35146 41.327637, -73.351586 41.327533, -73.352503 41.326964, -73.352675 41.326868, -73.352888 41.326719, -73.353122 41.326458, -73.35334 41.326115, -73.353408 41.32601, -73.353636 41.325543, -73.353933 41.325098, -73.354474 41.32499, -73.354684 41.32496, -73.355569 41.32486, -73.355747 41.324882, -73.355878 41.324919, -73.355999 41.324963, -73.356105 41.324996, -73.356126 41.325003, -73.356623 41.32519, -73.356477 41.32528, -73.356429 41.325319, -73.356405 41.325414, -73.356448 41.325438, -73.356551 41.325463, -73.356732 41.325496, -73.357035 41.325543, -73.357211 41.325585, -73.357247 41.325594, -73.357456 41.325637, -73.357555 41.325678, -73.357633 41.325703, -73.35776 41.325776, -73.357841 41.325801, -73.357875 41.325919, -73.357879 41.326162, -73.357864 41.326778, -73.357838 41.327403, -73.357816 41.327788, -73.357842 41.32812, -73.357865 41.328345, -73.357956 41.328573, -73.358048 41.32878, -73.35832 41.329265, -73.358493 41.329608, -73.358759 41.330123, -73.358829 41.330259, -73.359206 41.330891, -73.35945 41.331398, -73.359588 41.331653, -73.359672 41.331808, -73.359768 41.331956, -73.359892 41.332147, -73.36027 41.332755, -73.36036 41.332898, -73.3608 41.333725, -73.361192 41.334328, -73.361352 41.33461, -73.361551 41.334959, -73.361728 41.335221, -73.362191 41.336033, -73.362452 41.336489, -73.363193 41.337744, -73.363584 41.338319, -73.363654 41.338425, -73.363963 41.33889, -73.364103 41.33924, -73.364177 41.339615, -73.364312 41.340286, -73.364357 41.340747, -73.364382 41.341003, -73.364388 41.341129, -73.36439 41.342057, -73.364391 41.342093, -73.364338 41.343038, -73.36416 41.343014, -73.363964 41.342997, -73.363958 41.343135, -73.363963 41.343309, -73.363907 41.343385, -73.363773 41.343447, -73.36353 41.343532, -73.363277 41.343641, -73.363353 41.343761, -73.363457 41.343913, -73.363667 41.344264, -73.363877 41.344592, -73.364037 41.34479, -73.364123 41.344882, -73.364193 41.344997, -73.364254 41.345158, -73.364228 41.345351, -73.364094 41.345494, -73.364002 41.345514, -73.363918 41.34554, -73.363794 41.345522, -73.363519 41.345445, -73.363415 41.345311, -73.363207 41.34516, -73.363026 41.34495, -73.362534 41.344618, -73.362019 41.344196, -73.361979 41.344274, -73.361953 41.3444, -73.361951 41.344505, -73.362024 41.344606, -73.36209 41.344741, -73.362185 41.345, -73.362239 41.345241, -73.362334 41.345515, -73.362419 41.345645, -73.362477 41.345765, -73.362526 41.345828, -73.362549 41.345883, -73.362981 41.345837, -73.363146 41.345849, -73.363382 41.345881, -73.363881 41.34594, -73.364293 41.345835, -73.36456 41.345768, -73.365456 41.345508, -73.365501 41.345606, -73.365531 41.34567, -73.365592 41.345781, -73.365666 41.345875, -73.365754 41.345956, -73.365867 41.346059, -73.36593 41.346117, -73.366049 41.346229, -73.366146 41.346292, -73.366423 41.346492, -73.366508 41.346538, -73.366719 41.346653, -73.366921 41.346821, -73.367216 41.347065, -73.367598 41.347507, -73.367664 41.347583, -73.36857 41.348853, -73.369202 41.349706, -73.369556 41.350183, -73.369769 41.350491, -73.370033 41.350873, -73.370355 41.351149, -73.370637 41.351344, -73.370982 41.351582, -73.371585 41.351957, -73.37243 41.352483, -73.37266 41.352626, -73.373765 41.353313, -73.374153 41.353606, -73.374366 41.353867, -73.374434 41.35395, -73.374744 41.354337, -73.375059 41.354761, -73.375363 41.355191, -73.37537 41.355201, -73.375718 41.355674, -73.376189 41.356241, -73.376342 41.356435, -73.37667 41.356849, -73.37681 41.356982, -73.37696 41.357126, -73.377238 41.357377, -73.377382 41.357507, -73.378231 41.358235, -73.378813 41.358702, -73.37939 41.359172, -73.379574 41.35936, -73.379745 41.359544, -73.379889 41.359731, -73.37994 41.359814, -73.380124 41.359835, -73.380414 41.359911, -73.380988 41.360112, -73.38125 41.360151, -73.381456 41.360176, -73.381764 41.360195, -73.381751 41.359972, -73.381708 41.359872, -73.381616 41.359759, -73.381452 41.359474, -73.381331 41.359189, -73.381295 41.359097, -73.381168 41.358774, -73.381151 41.358599, -73.381114 41.358369, -73.381086 41.358154, -73.381052 41.35781, -73.38103 41.356735, -73.381026 41.356482, -73.381017 41.355819, -73.38104 41.355601, -73.380979 41.355052, -73.380977 41.35485, -73.380972 41.354486, -73.380973 41.35445, -73.380992 41.354108, -73.381004 41.353908, -73.381023 41.353306, -73.381016 41.352635, -73.380903 41.352105, -73.380845 41.351653, -73.380874 41.351005, -73.380891 41.35065, -73.380888 41.350405, -73.380954 41.349856, -73.380993 41.349527, -73.381056 41.349008, -73.381133 41.348543, -73.381188 41.348217, -73.381224 41.347861, -73.381246 41.347687, -73.381267 41.347534, -73.381274 41.347054, -73.381291 41.346756, -73.3813 41.346604, -73.381354 41.346094, -73.381384 41.34582, -73.381412 41.345565, -73.381491 41.345045, -73.381511 41.344919, -73.381577 41.344252, -73.381282 41.34423, -73.380953 41.344153, -73.380938 41.34415, -73.380764 41.344085, -73.380674 41.344017, -73.380525 41.343817, -73.380446 41.343644, -73.380354 41.343464, -73.380314 41.343305, -73.380309 41.343279, -73.378924 41.343533, -73.373456 41.34454, -73.371634 41.344876)), ((-73.393124 41.629554, -73.393251 41.62885, -73.393465 41.62769, -73.393649 41.626421, -73.393778 41.625495, -73.393803 41.625312, -73.393857 41.625091, -73.393897 41.624968, -73.393944 41.624847, -73.394066 41.624612, -73.394145 41.624491, -73.394265 41.624338, -73.394448 41.624374, -73.394564 41.624391, -73.394699 41.624376, -73.394843 41.624342, -73.395061 41.624273, -73.395237 41.624195, -73.395363 41.62414, -73.395849 41.623904, -73.396005 41.623848, -73.39628 41.623776, -73.396401 41.623762, -73.396615 41.623761, -73.396823 41.623793, -73.397754 41.624007, -73.398031 41.624039, -73.39815 41.62402, -73.398241 41.623998, -73.39834 41.62394, -73.39857 41.623759, -73.398872 41.623508, -73.398966 41.62344, -73.399137 41.623314, -73.399449 41.623131, -73.399629 41.623012, -73.399657 41.622998, -73.399844 41.622912, -73.399862 41.622914, -73.400145 41.622802, -73.400548 41.622629, -73.400666 41.622579, -73.400849 41.622471, -73.400989 41.622364, -73.401123 41.622217, -73.401251 41.621983, -73.401265 41.621925, -73.401403 41.621388, -73.401482 41.621233, -73.401566 41.621113, -73.401709 41.620982, -73.401971 41.620776, -73.40251 41.620389, -73.402901 41.620165, -73.404286 41.619432, -73.404582 41.619227, -73.404965 41.618933, -73.405326 41.618625, -73.405654 41.618348, -73.406132 41.617877, -73.406428 41.617545, -73.406551 41.617408, -73.406591 41.617349, -73.406612 41.617313, -73.406672 41.617162, -73.406741 41.616926, -73.406832 41.616654, -73.406896 41.616507, -73.406979 41.616346, -73.407228 41.615985, -73.407319 41.615862, -73.408132 41.614774, -73.40835 41.614537, -73.408646 41.614253, -73.408794 41.614126, -73.408944 41.613974, -73.409033 41.613833, -73.409079 41.613732, -73.409107 41.613623, -73.409121 41.613432, -73.409081 41.613137, -73.408998 41.612836, -73.409 41.612608, -73.40902 41.61249, -73.409051 41.612418, -73.409153 41.612106, -73.409224 41.611947, -73.409317 41.611808, -73.4094 41.611732, -73.409489 41.61167, -73.409673 41.611546, -73.410148 41.611265, -73.410382 41.611124, -73.41053 41.611023, -73.410776 41.61084, -73.410879 41.61075, -73.411086 41.610638, -73.411155 41.610587, -73.411201 41.610534, -73.411268 41.610436, -73.41135 41.610287, -73.411426 41.610152, -73.411585 41.609832, -73.411803 41.609449, -73.411927 41.609177, -73.412036 41.608827, -73.412087 41.608693, -73.412155 41.608551, -73.412229 41.608449, -73.412526 41.608106, -73.412645 41.607922, -73.412713 41.607814, -73.412734 41.607783, -73.412835 41.607597, -73.412855 41.607528, -73.412858 41.607467, -73.412865 41.60734, -73.412871 41.607302, -73.412872 41.607219, -73.412851 41.607105, -73.412822 41.606998, -73.412818 41.606981, -73.412709 41.606681, -73.412605 41.606461, -73.412581 41.606339, -73.412579 41.606197, -73.412604 41.606001, -73.412607 41.605985, -73.412669 41.605716, -73.412677 41.605661, -73.412727 41.605595, -73.412848 41.605329, -73.412998 41.605039, -73.413168 41.604778, -73.41321 41.604716, -73.41337 41.604525, -73.41352 41.604405, -73.413755 41.604263, -73.41401 41.604139, -73.414316 41.603981, -73.414512 41.603901, -73.414664 41.603867, -73.414829 41.603855, -73.415045 41.603864, -73.415305 41.603915, -73.415418 41.603924, -73.415536 41.603911, -73.415637 41.603879, -73.415755 41.603807, -73.415853 41.603721, -73.415903 41.603679, -73.416473 41.602994, -73.416503 41.602946, -73.416534 41.602784, -73.416587 41.602515, -73.41662 41.602297, -73.416645 41.602135, -73.416724 41.602149, -73.416961 41.602194, -73.417041 41.602209, -73.417264 41.60225, -73.417521 41.602304, -73.417544 41.602311, -73.417657 41.602345, -73.417831 41.602407, -73.418005 41.6025, -73.418162 41.602603, -73.418288 41.602695, -73.418446 41.602853, -73.41852 41.602952, -73.418588 41.603072, -73.418639 41.60318, -73.418671 41.603268, -73.418681 41.603295, -73.4187 41.60337, -73.418714 41.603462, -73.418738 41.603776, -73.418744 41.603817, -73.418748 41.60389, -73.418768 41.604234, -73.418775 41.604349, -73.418788 41.604569, -73.418791 41.604663, -73.418808 41.605086, -73.418814 41.605222, -73.418828 41.605453, -73.41886 41.605603, -73.41891 41.605835, -73.418941 41.605907, -73.418969 41.605974, -73.418997 41.606027, -73.419043 41.606112, -73.419148 41.60628, -73.419215 41.606359, -73.419254 41.606405, -73.419278 41.606433, -73.419307 41.606454, -73.419427 41.606555, -73.41969 41.606776, -73.419726 41.606806, -73.419846 41.606907, -73.42003 41.607061, -73.420054 41.607082, -73.420378 41.60737, -73.420881 41.60797, -73.421234 41.608391, -73.421371 41.608556, -73.421625 41.608862, -73.421767 41.609066, -73.421891 41.609243, -73.422102 41.609199, -73.422231 41.609173, -73.422314 41.609156, -73.422512 41.609106, -73.422673 41.609054, -73.422725 41.60903, -73.422796 41.608999, -73.422921 41.608939, -73.422948 41.608982, -73.422955 41.609298, -73.422882 41.609544, -73.422845 41.609601, -73.422736 41.609772, -73.422582 41.610072, -73.422369 41.610397, -73.421909 41.611206, -73.42185 41.611414, -73.421791 41.611623, -73.421922 41.612006, -73.421943 41.612073, -73.421926 41.612066, -73.4219 41.612037, -73.421852 41.612018, -73.421828 41.612012, -73.421593 41.61196, -73.421451 41.611898, -73.421383 41.611869, -73.421328 41.611851, -73.42115 41.612108, -73.420962 41.612356, -73.42085 41.612566, -73.420792 41.612705, -73.420782 41.61273, -73.420727 41.612902, -73.420504 41.613978, -73.420478 41.614136, -73.420496 41.6143, -73.420525 41.614409, -73.4208 41.614951, -73.420913 41.615224, -73.420978 41.615395, -73.421015 41.615518, -73.421023 41.615598, -73.421031 41.615676, -73.421057 41.616048, -73.421086 41.616293, -73.421121 41.616436, -73.421169 41.616559, -73.421188 41.61659, -73.421215 41.616636, -73.421299 41.616774, -73.421318 41.616804, -73.421331 41.616818, -73.421552 41.617209, -73.421618 41.617361, -73.421654 41.617505, -73.421672 41.617628, -73.421668 41.617782, -73.42164 41.617902, -73.421589 41.618042, -73.421491 41.618199, -73.421368 41.61834, -73.420995 41.618704, -73.42094 41.618784, -73.420876 41.618905, -73.420823 41.61905, -73.420798 41.619165, -73.420805 41.619278, -73.420818 41.619345, -73.420858 41.619437, -73.421002 41.619671, -73.42138 41.620367, -73.421417 41.620469, -73.421563 41.620866, -73.421642 41.621122, -73.421692 41.621405, -73.421706 41.621468, -73.421723 41.621504, -73.421834 41.621763, -73.421931 41.62204, -73.421972 41.622221, -73.421993 41.622459, -73.421999 41.622522, -73.422018 41.622656, -73.422044 41.622721, -73.42211 41.62284, -73.422263 41.623017, -73.422591 41.623285, -73.422855 41.623518, -73.422996 41.62365, -73.423114 41.623724, -73.423289 41.623807, -73.423443 41.623864, -73.423603 41.623895, -73.423866 41.623912, -73.424419 41.623986, -73.42445 41.62399, -73.4245 41.623998, -73.424918 41.62406, -73.425478 41.624151, -73.425736 41.624193, -73.426091 41.624237, -73.426141 41.624239, -73.426164 41.624239, -73.426402 41.624245, -73.426817 41.62422, -73.427135 41.624188, -73.427358 41.624177, -73.427439 41.624182, -73.427499 41.624198, -73.42756 41.624226, -73.427677 41.624299, -73.427947 41.624561, -73.428067 41.62466, -73.428317 41.624868, -73.428424 41.62498, -73.428489 41.625057, -73.428554 41.625149, -73.428546 41.625053, -73.42855 41.62492, -73.428552 41.62486, -73.428565 41.624732, -73.428597 41.624594, -73.42865 41.624502, -73.428785 41.624319, -73.428803 41.624304, -73.428851 41.624266, -73.428963 41.624196, -73.428995 41.62418, -73.428975 41.62416, -73.428845 41.624095, -73.42867 41.623926, -73.428444 41.623833, -73.428144 41.623726, -73.428013 41.62367, -73.427889 41.62351, -73.427845 41.623406, -73.427853 41.62319, -73.427933 41.622998, -73.427925 41.622755, -73.427919 41.622543, -73.427853 41.622416, -73.427758 41.622374, -73.427722 41.622378, -73.427678 41.622566, -73.427656 41.622602, -73.427546 41.622628, -73.427399 41.622526, -73.427341 41.622201, -73.427298 41.621714, -73.427298 41.621624, -73.427436 41.621383, -73.427546 41.621241, -73.427564 41.621193, -73.427996 41.621126, -73.42828 41.621109, -73.428375 41.621097, -73.428447 41.621074, -73.428491 41.621039, -73.428536 41.620952, -73.428551 41.620874, -73.42855 41.620841, -73.42855 41.620621, -73.428541 41.620406, -73.428556 41.620313, -73.428587 41.620254, -73.428626 41.620212, -73.428689 41.620161, -73.42878 41.620108, -73.428864 41.620072, -73.428976 41.620036, -73.429142 41.620004, -73.429633 41.619934, -73.430057 41.619862, -73.430076 41.619858, -73.430355 41.619817, -73.430793 41.619771, -73.43075 41.619601, -73.430622 41.619094, -73.43058 41.618925, -73.430553 41.618815, -73.430475 41.618488, -73.430449 41.618379, -73.430415 41.618235, -73.43038 41.618127, -73.430143 41.61738, -73.430064 41.617131, -73.430053 41.617087, -73.43002 41.616956, -73.43001 41.616913, -73.429968 41.61648, -73.42993 41.616084, -73.429927 41.61521, -73.429927 41.615178, -73.429929 41.614991, -73.42993 41.614904, -73.429934 41.614744, -73.429941 41.614427, -73.429945 41.614305, -73.430028 41.613845, -73.430108 41.613495, -73.430179 41.613187, -73.430234 41.613028, -73.4304 41.612554, -73.430438 41.612391, -73.430519 41.612069, -73.430529 41.612034, -73.430579 41.61178, -73.430583 41.61162, -73.430574 41.611343, -73.430565 41.611082, -73.430561 41.610961, -73.430543 41.610752, -73.430665 41.610755, -73.430792 41.610751, -73.431062 41.610699, -73.431368 41.610641, -73.431491 41.610629, -73.431597 41.61063, -73.431751 41.610643, -73.431903 41.610662, -73.432278 41.610729, -73.43261 41.610816, -73.432813 41.61087, -73.433077 41.610965, -73.433108 41.61098, -73.433189 41.611035, -73.433432 41.6112, -73.433513 41.611256, -73.43328 41.611484, -73.433111 41.611663, -73.433072 41.611758, -73.433059 41.611837, -73.433062 41.611914, -73.433087 41.611982, -73.433148 41.612087, -73.433233 41.612199, -73.433962 41.613019, -73.434311 41.613428, -73.43456 41.613695, -73.434692 41.613799, -73.434765 41.613847, -73.434865 41.613876, -73.434952 41.613879, -73.435136 41.613863, -73.435438 41.613801, -73.435611 41.613766, -73.436407 41.613574, -73.436437 41.613564, -73.4366 41.613783, -73.43709 41.61444, -73.437254 41.61466, -73.437289 41.614707, -73.437531 41.615076, -73.437746 41.615435, -73.438244 41.616287, -73.438328 41.616486, -73.438384 41.616616, -73.43853 41.616878, -73.438697 41.617103, -73.438804 41.617205, -73.438873 41.617253, -73.438919 41.617271, -73.438991 41.61728, -73.439128 41.617286, -73.43939 41.617272, -73.43959 41.617287, -73.439661 41.617307, -73.439721 41.61734, -73.439748 41.617376, -73.439772 41.617418, -73.439832 41.617714, -73.439956 41.618664, -73.440334 41.620133, -73.440409 41.620386, -73.44057 41.621183, -73.440597 41.621446, -73.440613 41.621781, -73.44062 41.621903, -73.440601 41.622584, -73.440586 41.622914, -73.440548 41.623349, -73.440531 41.623713, -73.44051 41.623898, -73.451017 41.619727, -73.450881 41.619571, -73.450555 41.619245, -73.450099 41.618985, -73.448698 41.619897, -73.448209 41.61993, -73.447655 41.619865, -73.447362 41.619799, -73.446906 41.619767, -73.446417 41.619669, -73.445863 41.619213, -73.445765 41.618789, -73.445765 41.618105, -73.445863 41.617519, -73.446124 41.6169, -73.446239 41.615902, -73.446402 41.615872, -73.446767 41.615839, -73.447072 41.615791, -73.447387 41.615705, -73.447662 41.615637, -73.44774 41.61562, -73.447891 41.615587, -73.448107 41.615549, -73.448172 41.615545, -73.448225 41.615551, -73.448282 41.615571, -73.448349 41.615621, -73.448471 41.6158, -73.448551 41.615876, -73.448634 41.615891, -73.448677 41.615879, -73.448959 41.61557, -73.449183 41.615312, -73.449346 41.61517, -73.449503 41.615056, -73.449816 41.614878, -73.449866 41.614827, -73.449654 41.614747, -73.4494 41.614613, -73.449208 41.614458, -73.44902 41.614279, -73.448896 41.614148, -73.448879 41.61413, -73.448746 41.613924, -73.448649 41.613749, -73.44856 41.613547, -73.448241 41.612507, -73.448154 41.612174, -73.448106 41.611929, -73.448059 41.611466, -73.448069 41.611347, -73.448294 41.611426, -73.44841 41.611435, -73.448571 41.611448, -73.448688 41.611057, -73.448878 41.610758, -73.448974 41.610368, -73.449164 41.609992, -73.449195 41.609909, -73.448907 41.609776, -73.448942 41.609705, -73.449017 41.609514, -73.449124 41.609261, -73.449213 41.609035, -73.449377 41.60852, -73.449785 41.607846, -73.450027 41.607488, -73.45012 41.607337, -73.450247 41.607154, -73.450485 41.606833, -73.450654 41.606562, -73.451103 41.605954, -73.451524 41.605753, -73.451917 41.605549, -73.452247 41.605347, -73.452476 41.605187, -73.452798 41.6049, -73.452955 41.604701, -73.453109 41.604467, -73.453286 41.604154, -73.453483 41.603803, -73.453645 41.603406, -73.453713 41.603121, -73.453739 41.602804, -73.453739 41.602464, -73.453685 41.602018, -73.453568 41.601627, -73.453448 41.601169, -73.453332 41.600714, -73.453042 41.59978, -73.452744 41.598662, -73.452521 41.598066, -73.4519 41.596998, -73.451081 41.595633, -73.450261 41.59449, -73.449313 41.593443, -73.449452 41.593363, -73.449581 41.593289, -73.449792 41.593168, -73.44985 41.593135, -73.450024 41.593036, -73.450082 41.593003, -73.450224 41.593285, -73.450543 41.593687, -73.451196 41.594466, -73.451534 41.594723, -73.451951 41.594943, -73.452769 41.595306, -73.453203 41.595549, -73.453551 41.595788, -73.453823 41.595947, -73.453836 41.595958, -73.454116 41.59619, -73.454779 41.596905, -73.454864 41.597016, -73.454822 41.597166, -73.454626 41.597528, -73.454607 41.597619, -73.455108 41.597874, -73.455388 41.597936, -73.455688 41.59788, -73.456224 41.597617, -73.456485 41.597439, -73.456976 41.5972, -73.457198 41.597159, -73.457712 41.597187, -73.457923 41.597165, -73.458501 41.59713, -73.458704 41.597087, -73.458918 41.597041, -73.459042 41.596916, -73.459123 41.596796, -73.459401 41.596714, -73.459467 41.596638, -73.459532 41.596627, -73.459596 41.596618, -73.459578 41.596705, -73.459556 41.596851, -73.459563 41.596976, -73.459587 41.597096, -73.460015 41.597101, -73.461081 41.596713, -73.461003 41.596167, -73.461059 41.59609, -73.461107 41.596035, -73.461179 41.595936, -73.461209 41.595859, -73.461225 41.595778, -73.461227 41.595707, -73.461219 41.595672, -73.461277 41.595657, -73.461604 41.595616, -73.461757 41.595596, -73.461885 41.595581, -73.462018 41.59557, -73.462602 41.595549, -73.462877 41.595536, -73.463042 41.595515, -73.463141 41.595495, -73.463198 41.595477, -73.46326 41.595459, -73.463361 41.595423, -73.463475 41.595368, -73.463602 41.595289, -73.463682 41.595224, -73.463788 41.595109, -73.463836 41.59505, -73.463878 41.594974, -73.463888 41.594945, -73.463948 41.594777, -73.464023 41.59443, -73.464085 41.594177, -73.464118 41.594069, -73.464184 41.593859, -73.46431 41.593498, -73.464334 41.593431, -73.464429 41.593188, -73.464502 41.593029, -73.464593 41.592871, -73.464672 41.592748, -73.464945 41.592403, -73.464952 41.592394, -73.465418 41.591824, -73.465723 41.591466, -73.465994 41.591166, -73.466144 41.591016, -73.466228 41.590966, -73.466351 41.590917, -73.466472 41.590874, -73.466625 41.590828, -73.466726 41.590807, -73.466839 41.590791, -73.466992 41.590775, -73.467124 41.59077, -73.467288 41.590778, -73.467336 41.590783, -73.467388 41.590785, -73.467443 41.590799, -73.467513 41.590812, -73.467729 41.590878, -73.46798 41.590988, -73.468086 41.591051, -73.46843 41.591255, -73.469188 41.591716, -73.469429 41.591857, -73.469556 41.591932, -73.470162 41.592266, -73.470408 41.592401, -73.470432 41.592417, -73.470466 41.592435, -73.470647 41.59253, -73.470708 41.592562, -73.470875 41.592649, -73.470989 41.592709, -73.471098 41.592743, -73.471347 41.592794, -73.471418 41.5928, -73.471459 41.592805, -73.471607 41.592801, -73.471696 41.5928, -73.471888 41.592768, -73.472022 41.592727, -73.472121 41.592688, -73.472178 41.592658, -73.472261 41.592616, -73.472354 41.592552, -73.472461 41.59246, -73.472531 41.592377, -73.472576 41.592315, -73.472628 41.592214, -73.472665 41.592104, -73.472678 41.592006, -73.472671 41.591831, -73.472611 41.591082, -73.472608 41.59104, -73.472598 41.590987, -73.47257 41.590672, -73.472546 41.590491, -73.472527 41.590347, -73.472516 41.590256, -73.472445 41.589923, -73.472415 41.589782, -73.47241 41.589758, -73.472321 41.589377, -73.47231 41.589322, -73.472277 41.589158, -73.472269 41.588949, -73.472284 41.588843, -73.472307 41.588778, -73.472357 41.588672, -73.472419 41.588587, -73.472489 41.588501, -73.472579 41.58842, -73.472667 41.588357, -73.472752 41.588309, -73.472838 41.588274, -73.472941 41.58824, -73.473014 41.588217, -73.47314 41.588185, -73.473245 41.588176, -73.473405 41.588177, -73.473487 41.588181, -73.473506 41.588183, -73.473607 41.588199, -73.47373 41.588228, -73.473809 41.588254, -73.473849 41.588268, -73.473869 41.588275, -73.473966 41.588322, -73.474005 41.588341, -73.473071 41.586295, -73.470272 41.58016, -73.469467 41.578394, -73.469318 41.578126, -73.469275 41.578049, -73.469148 41.577819, -73.469106 41.577743, -73.468879 41.578144, -73.468865 41.578165, -73.468681 41.578463, -73.468328 41.578902, -73.467955 41.579302, -73.467856 41.57941, -73.467608 41.579643, -73.46749 41.579755, -73.467188 41.580053, -73.466815 41.580492, -73.466645 41.580695, -73.46641 41.58098, -73.466281 41.581162, -73.465781 41.581818, -73.465752 41.581851, -73.465476 41.582165, -73.465247 41.582312, -73.464141 41.582811, -73.463653 41.583005, -73.463294 41.583168, -73.463099 41.583258, -73.462628 41.583456, -73.462248 41.583597, -73.46202 41.583646, -73.461957 41.583655, -73.461819 41.583955, -73.461814 41.583965, -73.461697 41.584185, -73.461651 41.584275, -73.461566 41.584461, -73.461476 41.584719, -73.461349 41.585042, -73.461168 41.585529, -73.46108 41.585817, -73.461074 41.585843, -73.461059 41.585923, -73.461045 41.586044, -73.461045 41.586174, -73.46106 41.586309, -73.461087 41.586428, -73.46111 41.586512, -73.461372 41.587087, -73.461438 41.587301, -73.461482 41.587491, -73.461498 41.58765, -73.461498 41.587848, -73.461481 41.587984, -73.46128 41.588692, -73.461187 41.589053, -73.461137 41.589343, -73.461107 41.58983, -73.461104 41.590102, -73.46101 41.590677, -73.460935 41.591222, -73.460894 41.591468, -73.460845 41.591772, -73.460795 41.592202, -73.460758 41.592471, -73.460703 41.592748, -73.460626 41.593098, -73.460537 41.593509, -73.460478 41.593775, -73.460415 41.594065, -73.4604 41.594148, -73.46034 41.594502, -73.46026 41.594833, -73.460189 41.595128, -73.460119 41.595338, -73.460045 41.595478, -73.459944 41.595591, -73.459804 41.595716, -73.459789 41.595731, -73.459531 41.595919, -73.459508 41.595936, -73.45947 41.595964, -73.459288 41.596102, -73.459228 41.596148, -73.459077 41.596136, -73.458976 41.59612, -73.458836 41.596079, -73.458442 41.595933, -73.458429 41.595928, -73.458203 41.595823, -73.458015 41.595722, -73.457882 41.595628, -73.457796 41.595559, -73.457716 41.595478, -73.457538 41.595273, -73.457312 41.595034, -73.457289 41.595006, -73.457226 41.594929, -73.457169 41.59485, -73.456792 41.594451, -73.456614 41.594339, -73.456567 41.594309, -73.456418 41.594252, -73.456256 41.594211, -73.456036 41.594193, -73.455969 41.594198, -73.455903 41.594218, -73.455835 41.594258, -73.455672 41.594225, -73.455473 41.594202, -73.455445 41.594201, -73.45499 41.594191, -73.454262 41.594188, -73.454099 41.594188, -73.453868 41.594185, -73.453627 41.594181, -73.453353 41.594159, -73.45314 41.594124, -73.452849 41.594058, -73.45248 41.593958, -73.452041 41.593813, -73.451848 41.593729, -73.451661 41.59364, -73.451393 41.593484, -73.45116 41.593328, -73.451128 41.5933, -73.450884 41.593088, -73.450795 41.592983, -73.450733 41.592891, -73.450652 41.592755, -73.450579 41.592633, -73.450158 41.591804, -73.450085 41.591687, -73.449879 41.591408, -73.449714 41.591216, -73.449551 41.591041, -73.44907 41.590521, -73.448975 41.590428, -73.448595 41.590027, -73.448198 41.589608, -73.446832 41.588171, -73.445642 41.586911, -73.445326 41.586579, -73.445237 41.586486, -73.444907 41.586139, -73.443919 41.585092, -73.443543 41.584693, -73.442857 41.583967, -73.442579 41.583681, -73.442395 41.583519, -73.442338 41.583468, -73.442094 41.5833, -73.441942 41.583215, -73.441876 41.583177, -73.441753 41.583113, -73.441688 41.58308, -73.441451 41.582981, -73.441235 41.582896, -73.441159 41.582868, -73.440958 41.582796, -73.440764 41.58273, -73.440325 41.582583, -73.440184 41.58254, -73.439985 41.582496, -73.439662 41.582459, -73.439431 41.582443, -73.439029 41.582452, -73.438799 41.582464, -73.438665 41.582475, -73.438503 41.582497, -73.43812 41.58258, -73.437653 41.582689, -73.437377 41.582745, -73.437181 41.582764, -73.436872 41.582773, -73.436603 41.582759, -73.436384 41.582741, -73.436106 41.582694, -73.435312 41.582488, -73.435073 41.582436, -73.434947 41.582413, -73.434848 41.582395, -73.434554 41.582365, -73.43398 41.582334, -73.433052 41.58229, -73.43239 41.582253, -73.432188 41.582237, -73.431948 41.582209, -73.43172 41.582167, -73.431398 41.582087, -73.431012 41.581964, -73.430499 41.58179, -73.430418 41.581758, -73.430522 41.581622, -73.430621 41.581511, -73.43103 41.58106, -73.431277 41.580812, -73.431503 41.580587, -73.431531 41.580559, -73.431956 41.580152, -73.432095 41.579988, -73.432156 41.579918, -73.432204 41.5798, -73.432251 41.579658, -73.432302 41.579344, -73.432352 41.579114, -73.432399 41.578951, -73.432525 41.578721, -73.432597 41.578605, -73.432696 41.57845, -73.433136 41.577817, -73.433182 41.577748, -73.43366 41.577054, -73.43394 41.576645, -73.434187 41.576287, -73.434302 41.576119, -73.434778 41.575415, -73.435056 41.575005, -73.435417 41.574481, -73.436373 41.573098, -73.436502 41.572911, -73.436866 41.572389, -73.437426 41.571554, -73.437846 41.570939, -73.438044 41.57062, -73.438521 41.569975, -73.439806 41.568074, -73.439875 41.568121, -73.440049 41.56827, -73.440225 41.568411, -73.440424 41.56854, -73.440613 41.568644, -73.440787 41.568699, -73.440949 41.568739, -73.441123 41.568758, -73.441324 41.568753, -73.442049 41.568671, -73.442142 41.568651, -73.442227 41.56994, -73.442314 41.569936, -73.442575 41.569926, -73.442663 41.569924, -73.442659 41.56976, -73.442585 41.569602, -73.442498 41.569447, -73.442563 41.569047, -73.442564 41.568633, -73.442476 41.567979, -73.442451 41.567897, -73.442402 41.567735, -73.442176 41.567367, -73.441467 41.566447, -73.441219 41.566124, -73.440641 41.56518, -73.440502 41.564872, -73.440378 41.564285, -73.440356 41.5636, -73.440359 41.562257, -73.440363 41.561091, -73.440466 41.560778, -73.440459 41.560674, -73.4404 41.560448, -73.440429 41.560345, -73.440405 41.560207, -73.440397 41.560163, -73.440386 41.560101, -73.440364 41.560036, -73.440351 41.559995, -73.440225 41.559619, -73.44021 41.559572, -73.440064 41.559322, -73.439844 41.558707, -73.439808 41.558463, -73.439815 41.558044, -73.439763 41.557503, -73.439702 41.557242, -73.439581 41.556726, -73.439544 41.556361, -73.439555 41.555915, -73.439566 41.55546, -73.439545 41.555199, -73.439501 41.554869, -73.439515 41.55468, -73.439661 41.554273, -73.439691 41.554075, -73.439662 41.553728, -73.439515 41.553298, -73.43953 41.552654, -73.439566 41.552438, -73.439639 41.551642, -73.439866 41.550961, -73.439983 41.550769, -73.44018 41.550627, -73.440378 41.550571, -73.440494 41.550573, -73.440714 41.550666, -73.440875 41.550749, -73.441101 41.550847, -73.441196 41.550925, -73.441291 41.551088, -73.441419 41.551498, -73.441459 41.551626, -73.441533 41.551839, -73.4417 41.551981, -73.442029 41.552197, -73.442212 41.552303, -73.442489 41.552559, -73.442643 41.552723, -73.442892 41.552907, -73.443206 41.553006, -73.443308 41.553016, -73.443528 41.552983, -73.443636 41.552942, -73.443725 41.552909, -73.4439 41.552767, -73.444054 41.55258, -73.444149 41.552338, -73.44417 41.552213, -73.444156 41.552064, -73.444024 41.551706, -73.443886 41.550907, -73.44382 41.550127, -73.443659 41.549643, -73.443425 41.549183, -73.443365 41.549065, -73.443045 41.548437, -73.442168 41.547069, -73.442131 41.546947, -73.44219 41.546777, -73.442256 41.546138, -73.442328 41.545757, -73.442562 41.54535, -73.442694 41.545203, -73.442752 41.545105, -73.442825 41.544858, -73.442862 41.544647, -73.44284 41.544178, -73.442782 41.543858, -73.442803 41.54343, -73.442833 41.543417, -73.442935 41.54341, -73.443257 41.543144, -73.443395 41.542956, -73.443483 41.542809, -73.443564 41.542716, -73.443593 41.542648, -73.443607 41.542518, -73.443585 41.542103, -73.443621 41.541771, -73.4436 41.541613, -73.443402 41.5412, -73.443395 41.541069, -73.44322 41.540639, -73.443146 41.540273, -73.44303 41.539907, -73.442993 41.539555, -73.442978 41.539334, -73.442928 41.539055, -73.442795 41.538771, -73.442635 41.538429, -73.442423 41.538007, -73.442299 41.537853, -73.442101 41.53749, -73.441956 41.537276, -73.441517 41.536811, -73.441057 41.53621, -73.440691 41.535953, -73.440355 41.535683, -73.440063 41.535512, -73.439873 41.535469, -73.439756 41.535463, -73.439459 41.535516, -73.439025 41.535593, -73.439 41.535588, -73.438887 41.535568, -73.438777 41.535477, -73.438748 41.535323, -73.438777 41.534427, -73.438771 41.534341, -73.438748 41.533958, -73.43873 41.533879, -73.438682 41.533669, -73.438689 41.533512, -73.43866 41.53334, -73.438485 41.532996, -73.438492 41.532879, -73.438551 41.532654, -73.43858 41.532168, -73.438543 41.531749, -73.438689 41.531494, -73.438711 41.531269, -73.438777 41.531072, -73.43877 41.530716, -73.438864 41.530141, -73.43885 41.52997, -73.438799 41.529816, -73.438711 41.529689, -73.43858 41.529547, -73.438514 41.52942, -73.438492 41.52919, -73.438405 41.528977, -73.438419 41.528262, -73.438288 41.527972, -73.438244 41.527872, -73.43812 41.527429, -73.438112 41.527078, -73.438222 41.526399, -73.438171 41.526308, -73.438039 41.526198, -73.437981 41.526198, -73.437937 41.526274, -73.437922 41.526516, -73.437725 41.527014, -73.437654 41.527086, -73.437411 41.527338, -73.43717 41.527763, -73.43706 41.527779, -73.436966 41.527733, -73.436936 41.527679, -73.4369 41.527363, -73.4369 41.526827, -73.436819 41.525993, -73.436702 41.525581, -73.43671 41.52532, -73.436659 41.524644, -73.436659 41.524392, -73.436447 41.52383, -73.43641 41.523069, -73.436067 41.522168, -73.436009 41.522145, -73.435906 41.52217, -73.435833 41.522246, -73.435775 41.522367, -73.435687 41.522419, -73.435584 41.522409, -73.435563 41.522337, -73.435628 41.522013, -73.435621 41.521491, -73.435585 41.521261, -73.435563 41.52081, -73.43549 41.520471, -73.435417 41.520286, -73.435212 41.52004, -73.43514 41.519922, -73.435081 41.519538, -73.435096 41.519259, -73.435183 41.519134, -73.435929 41.518914, -73.436279 41.518699, -73.436498 41.518494, -73.436666 41.518506, -73.437097 41.518584, -73.437199 41.518621, -73.437309 41.518695, -73.43747 41.518931, -73.437703 41.519389, -73.437827 41.519377, -73.437908 41.519306, -73.437995 41.519064, -73.438003 41.518871, -73.438105 41.518557, -73.438163 41.518188, -73.438244 41.518077, -73.438357 41.517975, -73.438404 41.517935, -73.438492 41.517666, -73.438587 41.517501, -73.438697 41.51743, -73.439149 41.516945, -73.439573 41.516735, -73.439822 41.51659, -73.440055 41.516381, -73.440253 41.515893, -73.44034 41.515777, -73.440464 41.515702, -73.440567 41.51569, -73.440741 41.515773, -73.440844 41.515761, -73.441012 41.515709, -73.441362 41.515647, -73.441436 41.515589, -73.441399 41.515341, -73.441416 41.515207, -73.441429 41.515107, -73.441341 41.514836, -73.441362 41.514773, -73.441524 41.514708, -73.44156 41.514663, -73.441399 41.514012, -73.441446 41.513829, -73.441516 41.513555, -73.441392 41.513017, -73.441363 41.512985, -73.441275 41.513011, -73.441187 41.513176, -73.441107 41.513432, -73.440888 41.513605, -73.44061 41.513709, -73.440281 41.51375, -73.439996 41.513894, -73.439814 41.513937, -73.439616 41.513871, -73.439456 41.513752, -73.439339 41.513507, -73.439288 41.512998, -73.439252 41.512443, -73.439164 41.512217, -73.439164 41.511843, -73.439106 41.511477, -73.439032 41.511157, -73.439032 41.510814, -73.439044 41.510684, -73.439061 41.510513, -73.439025 41.510206, -73.438865 41.509538, -73.43882 41.509123, -73.438769 41.508915, -73.438747 41.508352, -73.438755 41.507789, -73.438748 41.507554, -73.438645 41.507193, -73.438433 41.506803, -73.438199 41.506092, -73.438119 41.505681, -73.438119 41.505438, -73.438003 41.505049, -73.437812 41.50447, -73.437775 41.504294, -73.437805 41.503772, -73.437885 41.503377, -73.437973 41.503184, -73.438155 41.502935, -73.438411 41.502785, -73.438587 41.502751, -73.439587 41.502833, -73.439864 41.502863, -73.439989 41.502906, -73.440178 41.503034, -73.440478 41.503286, -73.441508 41.50403, -73.441807 41.504178, -73.442136 41.504304, -73.442194 41.504309, -73.442311 41.504243, -73.442529 41.503926, -73.442595 41.503684, -73.44272 41.502893, -73.442712 41.502195, -73.442807 41.501854, -73.442822 41.501381, -73.44263 41.500329, -73.442628 41.500095, -73.442526 41.499972, -73.442453 41.49976, -73.442343 41.499236, -73.442277 41.499082, -73.442161 41.498661, -73.442183 41.498016, -73.442191 41.497815, -73.442189 41.497558, -73.442051 41.497052, -73.442116 41.496828, -73.442058 41.496696, -73.44189 41.496172, -73.441898 41.49605, -73.441745 41.495602, -73.441664 41.495489, -73.441628 41.495375, -73.441598 41.495024, -73.441437 41.49463, -73.441342 41.494471, -73.441131 41.49422, -73.440897 41.493713, -73.440488 41.493104, -73.440299 41.492894, -73.440219 41.492767, -73.43992 41.492511, -73.439752 41.492401, -73.439561 41.492276, -73.43919 41.491947, -73.438458 41.491387, -73.438447 41.491374, -73.438247 41.491137, -73.437699 41.49067, -73.43748 41.490469, -73.437403 41.490304, -73.437298 41.490079, -73.437152 41.489928, -73.436985 41.489638, -73.436889 41.489366, -73.436714 41.489157, -73.436437 41.488811, -73.436078 41.488387, -73.435933 41.488196, -73.435582 41.487872, -73.435509 41.487758, -73.435429 41.48768, -73.435289 41.487604, -73.435173 41.487542, -73.434948 41.487277, -73.4344 41.486838, -73.434115 41.486699, -73.43402 41.486567, -73.433991 41.486499, -73.434035 41.486328, -73.43429 41.486192, -73.434538 41.486128, -73.434633 41.486053, -73.43464 41.485963, -73.434589 41.485867, -73.43459 41.485732, -73.434692 41.485473, -73.43486 41.485268, -73.434743 41.485081, -73.434808 41.484826, -73.434829 41.484796, -73.434977 41.484594, -73.435159 41.484195, -73.435159 41.483858, -73.435093 41.483591, -73.435327 41.483171, -73.435349 41.483068, -73.435335 41.482707, -73.435225 41.482255, -73.435225 41.482102, -73.435196 41.481539, -73.435115 41.481299, -73.435064 41.481253, -73.434999 41.481234, -73.43494 41.481247, -73.434904 41.481283, -73.43486 41.480971, -73.434846 41.480791, -73.434648 41.480433, -73.434495 41.480039, -73.434451 41.479696, -73.434305 41.479324, -73.434196 41.479188, -73.434014 41.478776, -73.433875 41.478576, -73.433848 41.478511, -73.433692 41.478136, -73.433305 41.477545, -73.433225 41.477306, -73.433188 41.47699, -73.432955 41.476744, -73.432919 41.47663, -73.432918 41.476458, -73.432918 41.476387, -73.43294 41.476356, -73.433013 41.476299, -73.433386 41.476178, -73.433576 41.476162, -73.433758 41.476574, -73.433992 41.476893, -73.434036 41.47715, -73.434106 41.477393, -73.434349 41.477749, -73.434626 41.478257, -73.434634 41.478415, -73.434882 41.47885, -73.434977 41.478879, -73.435218 41.479449, -73.43532 41.479613, -73.435408 41.479731, -73.435554 41.479756, -73.435604 41.479797, -73.435823 41.480313, -73.435802 41.480435, -73.435824 41.480588, -73.435962 41.480896, -73.436115 41.481124, -73.436115 41.481313, -73.436349 41.481591, -73.436692 41.482257, -73.437007 41.482721, -73.437121 41.482862, -73.437393 41.483199, -73.437539 41.483386, -73.438269 41.483832, -73.438532 41.483958, -73.438776 41.484178, -73.43884 41.484236, -73.438897 41.484309, -73.439276 41.48454, -73.439547 41.484606, -73.439846 41.484642, -73.439956 41.484769, -73.440196 41.484831, -73.440642 41.485063, -73.44081 41.485155, -73.440941 41.48522, -73.441138 41.485249, -73.441197 41.485277, -73.441286 41.485313, -73.441621 41.48545, -73.441846 41.485516, -73.442095 41.485573, -73.442292 41.485567, -73.44262 41.48545, -73.442715 41.485383, -73.442883 41.48521, -73.443051 41.484807, -73.443124 41.484745, -73.443219 41.484584, -73.443263 41.484368, -73.443394 41.484023, -73.443394 41.483879, -73.443335 41.483721, -73.443351 41.483401, -73.44335 41.483176, -73.443211 41.482958, -73.443059 41.482461, -73.443058 41.482344, -73.44316 41.482151, -73.443226 41.481932, -73.443248 41.481486, -73.443416 41.481132, -73.443459 41.480917, -73.443365 41.480438, -73.44324 41.480247, -73.443146 41.48003, -73.44297 41.479307, -73.442986 41.4791, -73.443022 41.478979, -73.44307 41.478883, -73.443138 41.478751, -73.443204 41.478671, -73.443416 41.478529, -73.443643 41.478478, -73.443759 41.478408, -73.44392 41.478171, -73.444174 41.478031, -73.444313 41.477893, -73.444409 41.477561, -73.444387 41.477282, -73.444344 41.477159, -73.444241 41.476802, -73.44425 41.476758, -73.444284 41.4766, -73.444131 41.476305, -73.444183 41.476063, -73.444189 41.475631, -73.444153 41.475482, -73.444036 41.475201, -73.443934 41.475064, -73.443861 41.474901, -73.443831 41.474257, -73.443693 41.474039, -73.443467 41.473792, -73.443335 41.473597, -73.443306 41.473286, -73.443262 41.473128, -73.443324 41.472667, -73.443335 41.472593, -73.443277 41.472407, -73.443189 41.47228, -73.442685 41.471814, -73.44251 41.471519, -73.442415 41.471261, -73.442269 41.470948, -73.442145 41.470591, -73.442065 41.47049, -73.441868 41.470393, -73.441109 41.47023, -73.440876 41.47029, -73.440766 41.470288, -73.440708 41.470238, -73.440518 41.470154, -73.44016 41.469825, -73.440036 41.469423, -73.439963 41.469277, -73.439678 41.46867, -73.43943 41.468302, -73.439364 41.468148, -73.439284 41.468048, -73.438795 41.467609, -73.43862 41.467426, -73.438504 41.467208, -73.43824 41.46689, -73.437904 41.466228, -73.437788 41.466037, -73.437635 41.465733, -73.43754 41.465601, -73.437438 41.465501, -73.437335 41.465459, -73.437065 41.465397, -73.436985 41.465422, -73.436818 41.465515, -73.4367 41.465599, -73.436642 41.465634, -73.436306 41.465769, -73.43616 41.465902, -73.435905 41.466029, -73.43562 41.466233, -73.435518 41.46642, -73.435496 41.466555, -73.435504 41.466753, -73.435503 41.466943, -73.435357 41.467229, -73.435236 41.467364, -73.435036 41.467589, -73.434846 41.467938, -73.434773 41.468239, -73.434752 41.468491, -73.434766 41.46863, -73.43478 41.468937, -73.434927 41.46952, -73.43505 41.469814, -73.435189 41.469987, -73.435255 41.470128, -73.435372 41.470638, -73.43543 41.470864, -73.435481 41.470914, -73.435649 41.47102, -73.435913 41.471269, -73.435955 41.471308, -73.435976 41.471329, -73.436161 41.471514, -73.436223 41.471576, -73.436343 41.471696, -73.436518 41.471789, -73.436715 41.472066, -73.436948 41.472155, -73.43732 41.472439, -73.437805 41.472749, -73.43789 41.472803, -73.437999 41.472836, -73.438254 41.473001, -73.438503 41.473275, -73.438547 41.473478, -73.438532 41.473599, -73.438496 41.473612, -73.438452 41.473589, -73.438379 41.473638, -73.438364 41.473696, -73.438401 41.47376, -73.438408 41.473836, -73.438342 41.473885, -73.438291 41.47406, -73.438267 41.474058, -73.438087 41.473927, -73.437758 41.473566, -73.4377 41.473449, -73.437474 41.473306, -73.436956 41.473065, -73.436912 41.472938, -73.436634 41.472763, -73.436394 41.472665, -73.436079 41.472593, -73.435999 41.472511, -73.435525 41.472253, -73.434934 41.471898, -73.43489 41.471892, -73.43462 41.471857, -73.434452 41.471882, -73.434387 41.471868, -73.434175 41.471901, -73.433985 41.471957, -73.433824 41.472094, -73.433715 41.472133, -73.433649 41.472132, -73.433379 41.472111, -73.433174 41.472004, -73.433033 41.471884, -73.433007 41.471862, -73.432978 41.471749, -73.432955 41.471611, -73.432919 41.471384, -73.432839 41.471162, -73.432693 41.470926, -73.432699 41.470799, -73.432713 41.47051, -73.432715 41.470458, -73.432686 41.470381, -73.432569 41.470262, -73.432401 41.469981, -73.432219 41.469761, -73.432197 41.469735, -73.432007 41.46957, -73.43173 41.469206, -73.431533 41.469014, -73.431285 41.468826, -73.431146 41.468775, -73.430949 41.46884, -73.430846 41.469005, -73.430693 41.469025, -73.430416 41.469107, -73.43038 41.469124, -73.430256 41.469208, -73.430168 41.46941, -73.430204 41.469649, -73.430051 41.469908, -73.429919 41.46983, -73.429716 41.469735, -73.429679 41.469718, -73.429328 41.469506, -73.429161 41.469333, -73.429 41.469214, -73.428869 41.469144, -73.428402 41.468778, -73.428154 41.468693, -73.427861 41.468554, -73.427723 41.468516, -73.42765 41.468529, -73.427467 41.468508, -73.427348 41.468559, -73.427256 41.4686, -73.427175 41.468608, -73.425964 41.468591, -73.425789 41.468647, -73.425708 41.4687, -73.425643 41.468776, -73.425548 41.468815, -73.425409 41.468759, -73.425146 41.46858, -73.425066 41.468345, -73.424921 41.468037, -73.424767 41.467818, -73.424512 41.467576, -73.424212 41.467248, -73.424162 41.467076, -73.424139 41.466882, -73.424096 41.466823, -73.423884 41.466676, -73.423818 41.46658, -73.423855 41.466576, -73.424038 41.466669, -73.424432 41.466778, -73.424578 41.466739, -73.424665 41.466673, -73.42468 41.466565, -73.424658 41.46634, -73.424614 41.466226, -73.424556 41.46587, -73.424373 41.465165, -73.424198 41.46491, -73.424075 41.464742, -73.423987 41.464493, -73.423994 41.464403, -73.424041 41.46431, -73.424075 41.464247, -73.424228 41.46415, -73.424447 41.464049, -73.424557 41.463969, -73.42468 41.463827, -73.424776 41.463387, -73.424834 41.46332, -73.424943 41.463272, -73.425206 41.463199, -73.425344 41.463201, -73.42544 41.463229, -73.425586 41.463317, -73.425717 41.463458, -73.425958 41.46361, -73.426063 41.463659, -73.426233 41.463738, -73.426388 41.46381, -73.426731 41.463891, -73.426771 41.46389, -73.426855 41.463888, -73.426921 41.463858, -73.426942 41.463834, -73.426965 41.463809, -73.42702 41.463677, -73.427045 41.463621, -73.427089 41.463576, -73.427359 41.463454, -73.42745 41.463396, -73.427505 41.463361, -73.427534 41.463299, -73.427542 41.463257, -73.427585 41.463047, -73.427586 41.462498, -73.427629 41.462386, -73.427636 41.462201, -73.427578 41.462097, -73.427571 41.462016, -73.427644 41.461823, -73.427855 41.461524, -73.428038 41.461189, -73.428067 41.460964, -73.427965 41.460665, -73.427805 41.46033, -73.427724 41.460036, -73.427746 41.459874, -73.427834 41.459718, -73.427892 41.459498, -73.42797 41.459297, -73.432472 41.461874, -73.433561 41.461476, -73.435145 41.461276, -73.43405 41.457813, -73.43385 41.455932, -73.433354 41.45534, -73.431294 41.450524, -73.434515 41.454566, -73.435485 41.455851, -73.438395 41.459708, -73.439366 41.460994, -73.441065 41.460693, -73.44191 41.460545, -73.444455 41.460096, -73.445051 41.45999, -73.44568 41.461949, -73.445597 41.461909, -73.445495 41.461917, -73.445436 41.461975, -73.445407 41.4621, -73.445437 41.46224, -73.4454 41.462348, -73.445371 41.462676, -73.445444 41.463047, -73.445473 41.463317, -73.445597 41.463819, -73.44567 41.463937, -73.445655 41.464013, -73.445627 41.464216, -73.4457 41.464505, -73.445838 41.464732, -73.44594 41.464819, -73.445999 41.464901, -73.445955 41.465017, -73.445933 41.465183, -73.445962 41.465418, -73.446123 41.465857, -73.446167 41.466078, -73.446077 41.466252, -73.446043 41.46632, -73.446021 41.466419, -73.446298 41.466729, -73.446335 41.467004, -73.446481 41.467244, -73.446554 41.467489, -73.446641 41.467733, -73.446707 41.467824, -73.44686 41.467966, -73.447062 41.468086, -73.44713 41.468127, -73.447262 41.468255, -73.447663 41.468553, -73.44824 41.46889, -73.448364 41.468986, -73.448437 41.469117, -73.448437 41.469288, -73.448459 41.469338, -73.448503 41.469361, -73.448611 41.469343, -73.448656 41.469336, -73.448766 41.46936, -73.448787 41.469487, -73.44886 41.469636, -73.449116 41.469951, -73.44924 41.470078, -73.449539 41.47024, -73.449642 41.470341, -73.449758 41.470482, -73.450204 41.470726, -73.450226 41.470776, -73.450204 41.471033, -73.450204 41.471168, -73.450393 41.47154, -73.450371 41.471697, -73.450335 41.471746, -73.450233 41.471857, -73.450218 41.471997, -73.450263 41.472119, -73.450204 41.47233, -73.450321 41.472543, -73.450591 41.47283, -73.45062 41.472916, -73.450635 41.473119, -73.450708 41.473323, -73.45092 41.473636, -73.450919 41.473735, -73.451036 41.474413, -73.451023 41.47453, -73.451 41.474754, -73.451022 41.475259, -73.451146 41.475828, -73.451227 41.475987, -73.451277 41.47624, -73.451278 41.476614, -73.451278 41.476654, -73.451504 41.476941, -73.451584 41.47714, -73.451818 41.477535, -73.451796 41.477656, -73.45184 41.477792, -73.451841 41.478085, -73.451911 41.478286, -73.451972 41.47846, -73.452153 41.47881, -73.45238 41.479128, -73.452446 41.479358, -73.452519 41.479539, -73.452542 41.479855, -73.452673 41.480546, -73.452717 41.480821, -73.452833 41.481169, -73.452929 41.48136, -73.452943 41.48145, -73.452972 41.482104, -73.452921 41.48254, -73.452994 41.482892, -73.452904 41.483614, -73.452899 41.483656, -73.452842 41.483899, -73.452863 41.48425, -73.452725 41.484582, -73.452623 41.485071, -73.452611 41.48542, -73.452608 41.485521, -73.452484 41.485889, -73.452457 41.486136, -73.452455 41.486159, -73.452492 41.48633, -73.452491 41.486443, -73.452586 41.486769, -73.452586 41.486877, -73.452711 41.487149, -73.452827 41.487465, -73.452834 41.48774, -73.452725 41.488112, -73.452725 41.488234, -73.452714 41.488266, -73.452682 41.488364, -73.452681 41.488603, -73.452733 41.488996, -73.452769 41.489266, -73.452835 41.48947, -73.453011 41.489715, -73.453142 41.490014, -73.453244 41.490223, -73.453842 41.49087, -73.454573 41.49151, -73.454814 41.491644, -73.454965 41.491646, -73.454989 41.491647, -73.455267 41.491691, -73.455646 41.491863, -73.456027 41.492097, -73.456588 41.492398, -73.457129 41.492554, -73.457443 41.492526, -73.45764 41.492642, -73.457691 41.492633, -73.457859 41.492577, -73.458042 41.492458, -73.458107 41.49236, -73.45807 41.492224, -73.458165 41.491978, -73.458333 41.491462, -73.45834 41.491309, -73.458377 41.491089, -73.45834 41.490683, -73.458312 41.490183, -73.45837 41.489499, -73.458544 41.489177, -73.458676 41.489039, -73.458844 41.488956, -73.459034 41.488819, -73.459187 41.488582, -73.45923 41.488317, -73.459187 41.488019, -73.459157 41.487667, -73.459041 41.487405, -73.459011 41.487265, -73.459106 41.486856, -73.459098 41.486779, -73.458959 41.486494, -73.458967 41.486287, -73.459194 41.486015, -73.459215 41.485867, -73.459143 41.485668, -73.459117 41.485535, -73.459156 41.485515, -73.459565 41.485538, -73.45966 41.485576, -73.459967 41.485746, -73.459792 41.485834, -73.459792 41.485861, -73.460134 41.48596, -73.460222 41.486038, -73.460384 41.486441, -73.460376 41.486576, -73.460266 41.486741, -73.46031 41.486782, -73.46069 41.486936, -73.460799 41.486973, -73.461454 41.487121, -73.461603 41.487155, -73.461712 41.487206, -73.461778 41.487189, -73.461942 41.487195, -73.461969 41.487196, -73.462194 41.487253, -73.462363 41.487233, -73.462421 41.487238, -73.462603 41.487205, -73.462786 41.487185, -73.463064 41.487238, -73.463224 41.487339, -73.463305 41.487448, -73.463343 41.487482, -73.463523 41.487645, -73.463714 41.487827, -73.464064 41.488161, -73.464129 41.488315, -73.464487 41.488766, -73.464546 41.488906, -73.464677 41.489115, -73.464867 41.489352, -73.465262 41.489523, -73.465233 41.489739, -73.465261 41.489798, -73.465584 41.490255, -73.465891 41.490689, -73.466051 41.490849, -73.46611 41.490926, -73.466175 41.491288, -73.466373 41.491466, -73.466409 41.491678, -73.466519 41.491878, -73.466767 41.492169, -73.467132 41.492552, -73.467213 41.49281, -73.467352 41.493082, -73.467593 41.493437, -73.467907 41.49377, -73.46855 41.494683, -73.468696 41.495046, -73.468893 41.4958, -73.468967 41.496234, -73.468879 41.49675, -73.468901 41.496827, -73.469106 41.497222, -73.469106 41.497325, -73.469157 41.497794, -73.469274 41.497981, -73.469186 41.498101, -73.469311 41.498184, -73.469238 41.498205, -73.469157 41.498294, -73.46918 41.498452, -73.469169 41.49853, -73.469121 41.498879, -73.469165 41.499019, -73.469392 41.499342, -73.469435 41.499518, -73.469384 41.499675, -73.469414 41.500095, -73.469507 41.500348, -73.469505 41.500452, -73.469383 41.500554, -73.469258 41.500709, -73.469149 41.500622, -73.468944 41.5003, -73.468757 41.500095, -73.468752 41.500084, -73.468719 41.499999, -73.468695 41.499995, -73.46861 41.499935, -73.468479 41.499794, -73.468413 41.499748, -73.468391 41.499698, -73.468384 41.499347, -73.468303 41.499057, -73.468233 41.498964, -73.46804 41.498707, -73.468142 41.497709, -73.468193 41.497538, -73.468164 41.497277, -73.468127 41.497195, -73.468055 41.497136, -73.467967 41.496738, -73.467777 41.496434, -73.467477 41.496133, -73.467338 41.496032, -73.466753 41.495792, -73.466667 41.495757, -73.466483 41.495714, -73.466403 41.495718, -73.466133 41.495836, -73.465805 41.495845, -73.465586 41.495896, -73.465489 41.495934, -73.465418 41.495961, -73.465382 41.495956, -73.465242 41.496017, -73.465133 41.495984, -73.465031 41.495933, -73.464585 41.495995, -73.464461 41.495989, -73.46442 41.496005, -73.46425 41.496076, -73.463957 41.496248, -73.463556 41.496369, -73.46349 41.496399, -73.463344 41.496586, -73.46333 41.496924, -73.463359 41.497109, -73.463301 41.497414, -73.463373 41.497758, -73.463462 41.497962, -73.463469 41.498079, -73.463483 41.498277, -73.463593 41.49854, -73.463639 41.498593, -73.463688 41.498649, -73.463791 41.498921, -73.463805 41.499218, -73.463805 41.499416, -73.463914 41.499643, -73.463995 41.499811, -73.464069 41.500095, -73.464072 41.500175, -73.464075 41.500217, -73.464241 41.500701, -73.464444 41.501082, -73.464566 41.50139, -73.464706 41.501728, -73.464921 41.502247, -73.464944 41.502304, -73.464993 41.502422, -73.465019 41.502472, -73.465049 41.502527, -73.465085 41.502596, -73.465195 41.502803, -73.465212 41.502835, -73.465231 41.502873, -73.465424 41.503257, -73.465507 41.503565, -73.46568 41.504206, -73.46576 41.50436, -73.465907 41.504514, -73.466024 41.504638, -73.466126 41.504802, -73.466279 41.504993, -73.466382 41.505148, -73.466597 41.505491, -73.46693 41.506024, -73.467009 41.506117, -73.467078 41.506198, -73.467285 41.506443, -73.467347 41.506516, -73.467356 41.506524, -73.467584 41.506726, -73.467967 41.507065, -73.468053 41.507204, -73.468181 41.507408, -73.468344 41.507667, -73.468362 41.507696, -73.468618 41.507983, -73.468943 41.508244, -73.469063 41.50834, -73.469202 41.508459, -73.469297 41.508627, -73.469429 41.509142, -73.469553 41.509261, -73.46978 41.50953, -73.469882 41.50963, -73.470218 41.509779, -73.470496 41.509963, -73.470569 41.509993, -73.470642 41.510023, -73.470868 41.510161, -73.470942 41.510243, -73.470723 41.510556, -73.470255 41.511094, -73.469992 41.510776, -73.46973 41.510556, -73.469591 41.510478, -73.469196 41.510355, -73.469057 41.510272, -73.468962 41.510181, -73.468613 41.509948, -73.468597 41.509937, -73.468393 41.509637, -73.467581 41.509073, -73.467107 41.508823, -73.466515 41.508577, -73.46642 41.508445, -73.466354 41.508291, -73.466288 41.508209, -73.46605 41.508007, -73.465879 41.507861, -73.465813 41.507766, -73.46574 41.507481, -73.46563 41.507331, -73.465448 41.507234, -73.465214 41.507195, -73.465069 41.507089, -73.464761 41.506806, -73.464477 41.50659, -73.464096 41.506432, -73.463965 41.5063, -73.463907 41.506209, -73.463856 41.506028, -73.463665 41.505778, -73.463592 41.505704, -73.463198 41.505303, -73.463088 41.505266, -73.462832 41.505217, -73.46276 41.50518, -73.462709 41.505036, -73.46265 41.504784, -73.462635 41.504719, -73.462446 41.504469, -73.462219 41.504277, -73.462065 41.504054, -73.462 41.504004, -73.461817 41.503461, -73.461664 41.503279, -73.461386 41.503054, -73.461218 41.50284, -73.461072 41.502753, -73.460962 41.502735, -73.460801 41.502754, -73.460751 41.502776, -73.460685 41.502874, -73.460626 41.50304, -73.460627 41.503211, -73.460461 41.503577, -73.460437 41.503632, -73.460364 41.503905, -73.460371 41.504131, -73.460451 41.504442, -73.460678 41.504824, -73.46062 41.505359, -73.460621 41.505683, -73.460605 41.506043, -73.460635 41.50653, -73.460576 41.506731, -73.460452 41.507158, -73.460482 41.507393, -73.460524 41.507794, -73.460533 41.507884, -73.460519 41.508298, -73.460613 41.508538, -73.46076 41.508797, -73.461052 41.509058, -73.461176 41.509199, -73.461286 41.50929, -73.461724 41.50967, -73.461768 41.509864, -73.461929 41.510195, -73.4622 41.510505, -73.462565 41.511046, -73.462681 41.511313, -73.462813 41.511653, -73.462814 41.511927, -73.462799 41.512274, -73.462814 41.512463, -73.462711 41.512773, -73.462514 41.51309, -73.462485 41.513306, -73.462675 41.514357, -73.46269 41.514862, -73.462778 41.515259, -73.463012 41.51569, -73.463071 41.516065, -73.46318 41.516292, -73.463246 41.516549, -73.463334 41.516812, -73.463444 41.517169, -73.463546 41.517521, -73.463688 41.517741, -73.463787 41.517894, -73.464101 41.518344, -73.464379 41.518613, -73.464744 41.518821, -73.464839 41.518885, -73.46508 41.519136, -73.465205 41.519341, -73.465293 41.519693, -73.465286 41.520017, -73.46522 41.520151, -73.465212 41.520241, -73.465249 41.52039, -73.465431 41.520546, -73.465644 41.520661, -73.465753 41.520744, -73.466009 41.521067, -73.466148 41.521339, -73.466185 41.521565, -73.466119 41.521879, -73.466031 41.522, -73.465944 41.522025, -73.465688 41.521905, -73.46541 41.521856, -73.465221 41.52189, -73.465001 41.522004, -73.464862 41.522182, -73.464819 41.52233, -73.464877 41.522633, -73.465038 41.522982, -73.465185 41.523186, -73.465353 41.523369, -73.46555 41.523484, -73.465776 41.523554, -73.466112 41.523613, -73.466273 41.523669, -73.466354 41.52376, -73.466376 41.523909, -73.466434 41.524108, -73.466427 41.524203, -73.466091 41.524811, -73.466018 41.525026, -73.466025 41.525305, -73.465982 41.525579, -73.466165 41.526581, -73.466216 41.526996, -73.466151 41.527041, -73.466099 41.527035, -73.465938 41.526826, -73.465434 41.52645, -73.465149 41.526293, -73.464623 41.526138, -73.46436 41.526017, -73.464134 41.525816, -73.464053 41.525657, -73.464068 41.52554, -73.464236 41.525376, -73.464418 41.52523, -73.464491 41.525141, -73.464521 41.525037, -73.464542 41.52479, -73.46441 41.524284, -73.46433 41.523918, -73.464279 41.523296, -73.464206 41.522862, -73.464191 41.522561, -73.46411 41.522249, -73.463905 41.521818, -73.463847 41.521588, -73.463752 41.521501, -73.463606 41.521445, -73.463394 41.521447, -73.463226 41.521471, -73.462963 41.521445, -73.462372 41.521307, -73.462188 41.521286, -73.461597 41.521319, -73.461305 41.521247, -73.461144 41.521182, -73.460662 41.521113, -73.460428 41.521123, -73.460275 41.521184, -73.460187 41.521332, -73.460348 41.521901, -73.46048 41.522236, -73.460575 41.5224, -73.460641 41.522617, -73.460604 41.52272, -73.460585 41.522724, -73.460363 41.522775, -73.460092 41.52278, -73.459925 41.522747, -73.459786 41.522659, -73.45953 41.522363, -73.459325 41.522027, -73.459157 41.521574, -73.458894 41.521337, -73.458661 41.521086, -73.458449 41.520934, -73.458208 41.520913, -73.458098 41.520943, -73.458003 41.521001, -73.457908 41.521107, -73.457821 41.521327, -73.457806 41.521462, -73.457842 41.521638, -73.457996 41.521924, -73.458099 41.522231, -73.458164 41.522669, -73.458215 41.522899, -73.458311 41.523675, -73.458464 41.524272, -73.458691 41.525076, -73.458895 41.52546, -73.458932 41.52553, -73.459013 41.525945, -73.459049 41.526027, -73.459247 41.526534, -73.459758 41.527361, -73.459846 41.527443, -73.460525 41.528181, -73.460906 41.528529, -73.461147 41.528699, -73.461497 41.528933, -73.461848 41.529145, -73.462199 41.529379, -73.462308 41.529417, -73.462593 41.529407, -73.462988 41.529435, -73.463463 41.529401, -73.463624 41.529416, -73.463748 41.529467, -73.464004 41.529638, -73.464091 41.529756, -73.464143 41.530036, -73.464267 41.530258, -73.464486 41.530482, -73.464742 41.530782, -73.464932 41.530996, -73.465013 41.531029, -73.465465 41.531089, -73.465546 41.531131, -73.46575 41.531359, -73.466021 41.531497, -73.466115 41.531697, -73.466108 41.531877, -73.466014 41.532146, -73.466021 41.532353, -73.466034 41.532388, -73.466044 41.532416, -73.466077 41.532503, -73.466088 41.532533, -73.466548 41.533765, -73.466672 41.533988, -73.46695 41.534428, -73.46725 41.534972, -73.467849 41.535967, -73.468259 41.536504, -73.468486 41.536753, -73.469195 41.537534, -73.469428 41.53774, -73.469786 41.53815, -73.470101 41.538442, -73.470751 41.538825, -73.471066 41.539067, -73.471096 41.539117, -73.471146 41.539429, -73.471285 41.539818, -73.471431 41.540081, -73.471637 41.5403, -73.471702 41.540449, -73.471731 41.540603, -73.471944 41.541286, -73.472229 41.541835, -73.472434 41.542337, -73.4725 41.542581, -73.472558 41.542947, -73.472537 41.543347, -73.472566 41.544077, -73.472684 41.544565, -73.47283 41.5449, -73.472939 41.545334, -73.473334 41.546159, -73.474198 41.547247, -73.474446 41.548016, -73.474746 41.548276, -73.474885 41.54835, -73.474972 41.548424, -73.475031 41.54851, -73.475119 41.548799, -73.475236 41.54899, -73.475319 41.549052, -73.475412 41.549123, -73.475573 41.549193, -73.475719 41.54928, -73.475821 41.549408, -73.47599 41.549689, -73.476319 41.55, -73.476728 41.550266, -73.47743 41.550847, -73.477949 41.551372, -73.478117 41.551505, -73.478636 41.551832, -73.479031 41.552044, -73.479096 41.55206, -73.479184 41.552082, -73.479521 41.552082, -73.47966 41.552133, -73.479783 41.55223, -73.479864 41.552339, -73.479937 41.552488, -73.480032 41.552832, -73.480142 41.553441, -73.480128 41.553788, -73.480158 41.553946, -73.480183 41.554023, -73.480246 41.554217, -73.480385 41.554453, -73.480656 41.554682, -73.480802 41.554936, -73.480846 41.555198, -73.480978 41.555537, -73.481102 41.555769, -73.481314 41.555983, -73.481431 41.556048, -73.481621 41.556339, -73.482112 41.556692, -73.482608 41.557014, -73.483055 41.557245, -73.483318 41.55741, -73.483691 41.557577, -73.484655 41.557869, -73.485014 41.558036, -73.48519 41.558074, -73.485211 41.558228, -73.485131 41.558411, -73.485037 41.558527, -73.484788 41.558682, -73.484596 41.55873, -73.484167 41.55884, -73.483897 41.558846, -73.483531 41.558755, -73.483304 41.558626, -73.483019 41.558388, -73.482887 41.558328, -73.482616 41.55827, -73.482448 41.558309, -73.482207 41.558427, -73.482203 41.55844, -73.482142 41.558624, -73.482179 41.558913, -73.482304 41.559248, -73.482457 41.559529, -73.48283 41.559809, -73.483093 41.560159, -73.483173 41.560219, -73.483395 41.560297, -73.483517 41.56034, -73.484468 41.560893, -73.484709 41.560977, -73.485038 41.561072, -73.48525 41.561169, -73.485411 41.561251, -73.485506 41.561299, -73.485681 41.561369, -73.486157 41.561235, -73.486471 41.561262, -73.486523 41.561285, -73.486748 41.561383, -73.486885 41.561479, -73.486931 41.561511, -73.487487 41.561969, -73.487524 41.56205, -73.487422 41.562107, -73.487443 41.562144, -73.487645 41.562219, -73.487729 41.562251, -73.487992 41.562399, -73.48797 41.562403, -73.487502 41.562311, -73.487407 41.562328, -73.487195 41.562469, -73.487158 41.562536, -73.487137 41.562748, -73.487145 41.562806, -73.487195 41.562879, -73.48781 41.563401, -73.487869 41.563541, -73.488007 41.563804, -73.48811 41.563895, -73.488424 41.564111, -73.488469 41.564427, -73.488545 41.564447, -73.488666 41.564479, -73.488761 41.564539, -73.488908 41.564514, -73.488988 41.564501, -73.489039 41.56452, -73.489368 41.564871, -73.489419 41.564993, -73.48942 41.565115, -73.489442 41.565184, -73.489463 41.565246, -73.489661 41.565591, -73.489895 41.566013, -73.490254 41.566531, -73.490276 41.566608, -73.490217 41.566684, -73.490086 41.566871, -73.490079 41.567047, -73.490248 41.56754, -73.490489 41.568061, -73.490643 41.568441, -73.490628 41.568684, -73.490599 41.568855, -73.490687 41.569162, -73.490958 41.56972, -73.491023 41.5698, -73.490994 41.569506, -73.490984 41.569407, -73.490886 41.568406, -73.490845 41.568245, -73.490833 41.568194, -73.490688 41.567557, -73.490615 41.567028, -73.490763 41.567003, -73.490902 41.566944, -73.49108 41.566885, -73.491252 41.5668, -73.491371 41.566691, -73.491399 41.566588, -73.491391 41.566445, -73.491502 41.566262, -73.491926 41.566474, -73.491959 41.566476, -73.492033 41.566483, -73.492096 41.566509, -73.492281 41.566152, -73.492355 41.565903, -73.492295 41.565749, -73.492198 41.565593, -73.491966 41.565319, -73.491831 41.565159, -73.491917 41.564966, -73.492182 41.5643, -73.492195 41.564257, -73.49227 41.564007, -73.492278 41.563862, -73.492242 41.563655, -73.492176 41.563474, -73.491956 41.56326, -73.49175 41.563069, -73.49164 41.562967, -73.491498 41.562857, -73.491428 41.562716, -73.491221 41.562538, -73.491156 41.5625, -73.490931 41.562367, -73.490806 41.562254, -73.490792 41.562237, -73.49063 41.562042, -73.490344 41.561586, -73.49017 41.561227, -73.490071 41.561183, -73.490024 41.561153, -73.489641 41.560915, -73.489606 41.560893, -73.489499 41.560819, -73.489195 41.560432, -73.489159 41.560386, -73.489111 41.56029, -73.488984 41.560035, -73.488924 41.559914, -73.488843 41.559248, -73.488857 41.559203, -73.488898 41.559077, -73.488995 41.558961, -73.48933 41.559096, -73.490335 41.559503, -73.490671 41.559639, -73.490256 41.558918, -73.49015 41.558733, -73.489815 41.557926, -73.489437 41.556968, -73.489285 41.556625, -73.489131 41.556275, -73.48905 41.556105, -73.488904 41.555891, -73.488632 41.555494, -73.488611 41.555472, -73.488293 41.55513, -73.487968 41.554858, -73.487462 41.554562, -73.487411 41.554541, -73.486938 41.554348, -73.486519 41.554179, -73.486144 41.554029, -73.485255 41.553694, -73.484833 41.553536, -73.484371 41.553382, -73.483939 41.553194, -73.483709 41.553094, -73.483139 41.552775, -73.482938 41.552622, -73.48248 41.552137, -73.482274 41.551685, -73.482109 41.551157, -73.482105 41.551143, -73.481973 41.55021, -73.482092 41.550127, -73.482238 41.550028, -73.48239 41.549942, -73.482466 41.549909, -73.4826 41.549852, -73.482453 41.549407, -73.482438 41.549321, -73.482322 41.548621, -73.482336 41.548429, -73.482466 41.548259, -73.482586 41.54812, -73.48276 41.548032, -73.482916 41.547959, -73.48293 41.547953, -73.483089 41.5479, -73.483451 41.54781, -73.484424 41.54719, -73.484393 41.54708, -73.484398 41.546958, -73.484356 41.546882, -73.484364 41.546842, -73.484436 41.546779, -73.484486 41.546518, -73.484493 41.546154, -73.484567 41.54561, -73.484529 41.545397, -73.484376 41.545098, -73.484478 41.545046, -73.484434 41.544977, -73.484332 41.544917, -73.484156 41.544767, -73.48393 41.544448, -73.483791 41.544334, -73.4836 41.544084, -73.483542 41.543957, -73.483542 41.543669, -73.483614 41.543309, -73.483622 41.543219, -73.483549 41.543052, -73.48354 41.54276, -73.483519 41.542047, -73.483504 41.541966, -73.483467 41.54192, -73.48346 41.54179, -73.483233 41.541575, -73.483241 41.541233, -73.483204 41.541084, -73.483058 41.540942, -73.483014 41.540869, -73.482902 41.540295, -73.482897 41.540269, -73.483057 41.539627, -73.483005 41.538825, -73.482924 41.538436, -73.482936 41.538317, -73.482954 41.538149, -73.48299 41.538036, -73.482887 41.537747, -73.482903 41.537674, -73.482938 41.537509, -73.482726 41.537227, -73.482705 41.537173, -73.482748 41.536993, -73.482777 41.536723, -73.482952 41.536302, -73.482989 41.536146, -73.483047 41.535912, -73.483091 41.535809, -73.483098 41.535633, -73.483067 41.535503, -73.483039 41.535385, -73.483075 41.535182, -73.483022 41.534937, -73.48301 41.534884, -73.483046 41.534628, -73.483032 41.533941, -73.483031 41.533889, -73.482979 41.5332, -73.482935 41.532974, -73.482927 41.53273, -73.48281 41.532364, -73.482811 41.532053, -73.482767 41.531769, -73.482737 41.531571, -73.482751 41.531485, -73.482801 41.531412, -73.482832 41.531369, -73.482839 41.531041, -73.482835 41.530998, -73.482773 41.530184, -73.482781 41.530001, -73.48279 41.529787, -73.482801 41.529549, -73.48286 41.529176, -73.482857 41.529149, -73.482839 41.528936, -73.48283 41.528838, -73.48283 41.528822, -73.482837 41.528573, -73.482825 41.52848, -73.482822 41.528451, -73.482792 41.528372, -73.482727 41.528197, -73.48272 41.527828, -73.482661 41.527548, -73.482661 41.527354, -73.482676 41.527255, -73.482639 41.527106, -73.482531 41.526905, -73.482471 41.526793, -73.482368 41.52649, -73.482265 41.526309, -73.482141 41.525929, -73.482141 41.525762, -73.482082 41.525514, -73.482038 41.525162, -73.481906 41.52462, -73.481819 41.524168, -73.481768 41.523992, -73.481117 41.523159, -73.481029 41.52296, -73.480977 41.522896, -73.480847 41.522805, -73.480656 41.522671, -73.480437 41.522335, -73.479929 41.521614, -73.479918 41.521599, -73.479676 41.521451, -73.479633 41.521388, -73.479523 41.521093, -73.479435 41.520809, -73.479318 41.520618, -73.478886 41.52013, -73.478543 41.519653, -73.478515 41.519606, -73.478361 41.519307, -73.478237 41.518998, -73.478023 41.518552, -73.477957 41.518395, -73.477914 41.518295, -73.477871 41.518233, -73.47781 41.51819, -73.477676 41.51809, -73.476679 41.517285, -73.476584 41.517213, -73.476657 41.517196, -73.476686 41.517165, -73.476547 41.51696, -73.47646 41.51695, -73.476372 41.516972, -73.476262 41.516952, -73.476145 41.516892, -73.475743 41.51649, -73.475524 41.516226, -73.475422 41.516135, -73.475298 41.516061, -73.475021 41.515869, -73.474947 41.515737, -73.47491 41.515493, -73.474786 41.515262, -73.474472 41.514785, -73.474392 41.514703, -73.474238 41.514669, -73.474063 41.514662, -73.473939 41.514629, -73.473873 41.514592, -73.473814 41.514533, -73.473799 41.514343, -73.473829 41.51424, -73.473682 41.51413, -73.473602 41.514021, -73.473405 41.513487, -73.473054 41.513104, -73.472908 41.5128, -73.472864 41.512669, -73.472805 41.512592, -73.472608 41.512495, -73.472584 41.512501, -73.472403 41.512555, -73.472302 41.512491, -73.472213 41.512386, -73.472009 41.512284, -73.471475 41.5118, -73.47081 41.511408, -73.470592 41.511256, -73.470942 41.51068, -73.470917 41.510666, -73.470845 41.510624, -73.470821 41.510611, -73.470845 41.510577, -73.471075 41.510268, -73.471346 41.509908, -73.471516 41.509572, -73.471515 41.509481, -73.471495 41.509359, -73.471361 41.509162, -73.471122 41.508809, -73.471268 41.508841, -73.471412 41.508904, -73.472049 41.509181, -73.472258 41.509272, -73.47254 41.509395, -73.472645 41.509344, -73.472856 41.509305, -73.473015 41.509252, -73.473031 41.509231, -73.473155 41.509082, -73.473332 41.508574, -73.473405 41.508482, -73.473682 41.508289, -73.473912 41.508234, -73.474062 41.508253, -73.474251 41.508277, -73.474569 41.508382, -73.474705 41.508452, -73.474868 41.508536, -73.4751 41.508691, -73.475229 41.508777, -73.475733 41.508636, -73.476083 41.508571, -73.476362 41.50852, -73.476697 41.508514, -73.477156 41.50846, -73.477542 41.508293, -73.478546 41.507755, -73.478589 41.507733, -73.479184 41.507465, -73.479337 41.507371, -73.479408 41.507307, -73.479755 41.506915, -73.47981 41.506724, -73.479862 41.506264, -73.479882 41.506114, -73.479914 41.50588, -73.480013 41.505637, -73.480623 41.504663, -73.480971 41.504224, -73.481289 41.503896, -73.481571 41.503578, -73.481629 41.503452, -73.481585 41.503155, -73.481563 41.50275, -73.481641 41.502469, -73.481678 41.502414, -73.481707 41.502374, -73.481856 41.502183, -73.481988 41.501988, -73.482004 41.501586, -73.482041 41.501422, -73.482165 41.501112, -73.482304 41.501155, -73.482612 41.501297, -73.482792 41.501358, -73.482856 41.50138, -73.483606 41.501393, -73.484391 41.501321, -73.484627 41.501334, -73.484801 41.501394, -73.485038 41.501477, -73.485399 41.501701, -73.485444 41.50169, -73.485579 41.501659, -73.485625 41.501649, -73.485093 41.501293, -73.484796 41.501172, -73.484352 41.500846, -73.483947 41.500451, -73.4838 41.500308, -73.483683 41.500185, -73.483508 41.500092, -73.483454 41.500001, -73.483178 41.499534, -73.483171 41.499327, -73.483134 41.499141, -73.483164 41.498791, -73.483243 41.498427, -73.483295 41.49836, -73.483535 41.49789, -73.483717 41.497654, -73.484294 41.497405, -73.48444 41.497218, -73.484842 41.49698, -73.485141 41.49684, -73.485564 41.49657, -73.485987 41.496148, -73.486159 41.49602, -73.486586 41.495706, -73.486732 41.49555, -73.486769 41.495487, -73.486929 41.495039, -73.487133 41.494767, -73.487339 41.494374, -73.487476 41.494114, -73.487527 41.493894, -73.487541 41.49343, -73.487614 41.493211, -73.487672 41.493126, -73.487847 41.492989, -73.488037 41.492604, -73.488095 41.49242, -73.488219 41.492106, -73.488307 41.492017, -73.48854 41.491881, -73.488861 41.491737, -73.489052 41.491631, -73.489161 41.491506, -73.489175 41.491448, -73.489131 41.4912, -73.488911 41.490602, -73.488773 41.490164, -73.488772 41.489925, -73.48886 41.489701, -73.489261 41.489386, -73.489356 41.489266, -73.489364 41.489235, -73.489393 41.489131, -73.489538 41.488917, -73.489903 41.488647, -73.490268 41.488472, -73.490523 41.488345, -73.490721 41.488172, -73.490793 41.488037, -73.490823 41.487799, -73.490764 41.487587, -73.490779 41.487258, -73.490917 41.487093, -73.490924 41.486521, -73.490865 41.48635, -73.490872 41.486197, -73.490959 41.486009, -73.491346 41.485469, -73.491412 41.485303, -73.491448 41.48493, -73.491674 41.484694, -73.491755 41.484614, -73.491974 41.484342, -73.492031 41.484235, -73.492003 41.483996, -73.492024 41.483847, -73.492111 41.483704, -73.492119 41.483533, -73.492017 41.483356, -73.491965 41.483216, -73.491899 41.482891, -73.491928 41.482837, -73.492045 41.482677, -73.492344 41.482559, -73.492534 41.482512, -73.49287 41.482485, -73.493147 41.482434, -73.493293 41.482319, -73.493381 41.482122, -73.493373 41.482068, -73.493402 41.481906, -73.493483 41.481795, -73.493658 41.481734, -73.494176 41.481732, -73.494307 41.481661, -73.494358 41.48159, -73.494351 41.481401, -73.494168 41.481106, -73.49397 41.480837, -73.493547 41.480472, -73.493262 41.480387, -73.49308 41.480258, -73.492963 41.480239, -73.492751 41.479993, -73.492496 41.479841, -73.492415 41.479691, -73.492334 41.479627, -73.49221 41.47945, -73.492144 41.479273, -73.491991 41.479109, -73.491823 41.479003, -73.491517 41.478869, -73.491188 41.478716, -73.490829 41.478445, -73.490727 41.478331, -73.49053 41.478171, -73.490304 41.477921, -73.489573 41.477303, -73.489237 41.477087, -73.489215 41.476979, -73.489311 41.476777, -73.489273 41.476669, -73.489156 41.476613, -73.488894 41.476515, -73.488841 41.476479, -73.488785 41.476441, -73.488609 41.476295, -73.488492 41.476208, -73.48839 41.476166, -73.488288 41.476075, -73.488273 41.475921, -73.488149 41.475753, -73.487981 41.475629, -73.487879 41.475583, -73.487791 41.475415, -73.487711 41.475396, -73.48763 41.475296, -73.487666 41.475251, -73.487761 41.475226, -73.487959 41.475224, -73.488024 41.475193, -73.488039 41.475144, -73.48798 41.475021, -73.487921 41.474962, -73.487885 41.474844, -73.487842 41.474821, -73.487717 41.47482, -73.487667 41.474783, -73.487557 41.474818, -73.487454 41.474708, -73.487425 41.474514, -73.487447 41.474465, -73.487542 41.474412, -73.487513 41.474232, -73.487542 41.473939, -73.487483 41.473763, -73.487505 41.473506, -73.48749 41.473457, -73.487395 41.473334, -73.487387 41.473208, -73.487409 41.47314, -73.487315 41.472977, -73.487176 41.472759, -73.487139 41.472722, -73.486884 41.472615, -73.486804 41.472533, -73.486789 41.472452, -73.486818 41.472344, -73.486796 41.472304, -73.486551 41.472125, -73.486525 41.472106, -73.486445 41.471885, -73.486306 41.471761, -73.486291 41.471531, -73.486239 41.471468, -73.485933 41.471103, -73.485911 41.470815, -73.485729 41.470695, -73.485648 41.470519, -73.485604 41.470464, -73.48551 41.470404, -73.485399 41.470376, -73.48529 41.470302, -73.485261 41.470198, -73.485276 41.470167, -73.485202 41.470076, -73.485056 41.469975, -73.484884 41.46979, -73.484611 41.469496, -73.484494 41.469432, -73.484298 41.469312, -73.484622 41.469236, -73.484887 41.469118, -73.484929 41.469082, -73.484977 41.468925, -73.48499 41.468895, -73.485202 41.46878, -73.48544 41.468732, -73.485535 41.468726, -73.486036 41.468668, -73.486286 41.468683, -73.486386 41.468724, -73.486474 41.468734, -73.486672 41.46871, -73.486827 41.468652, -73.487038 41.468557, -73.487566 41.468062, -73.487667 41.467894, -73.488241 41.468016, -73.488744 41.46841, -73.489041 41.468611, -73.489896 41.46917, -73.490746 41.469712, -73.490931 41.469852, -73.490702 41.469911, -73.490635 41.469949, -73.490562 41.469992, -73.490497 41.470112, -73.490431 41.470236, -73.490366 41.470292, -73.490117 41.470381, -73.490001 41.470404, -73.489974 41.470404, -73.48987 41.470487, -73.489829 41.470527, -73.489777 41.470612, -73.48974 41.470703, -73.489728 41.470798, -73.489731 41.470855, -73.489729 41.470906, -73.489736 41.470963, -73.489746 41.471017, -73.489766 41.471124, -73.489774 41.471171, -73.489784 41.471248, -73.489789 41.471299, -73.489801 41.471357, -73.489802 41.471407, -73.489813 41.471471, -73.489761 41.471623, -73.489754 41.471687, -73.489747 41.471751, -73.489738 41.471815, -73.489728 41.471878, -73.489719 41.471941, -73.489719 41.472008, -73.489718 41.472061, -73.489725 41.472112, -73.489733 41.472161, -73.489751 41.472221, -73.48977 41.472276, -73.489794 41.472328, -73.489842 41.472416, -73.489895 41.472503, -73.489909 41.472523, -73.489945 41.472576, -73.489987 41.472641, -73.490046 41.47272, -73.490076 41.472764, -73.490105 41.472809, -73.490134 41.472855, -73.490163 41.4729, -73.490226 41.472988, -73.490259 41.473032, -73.490294 41.473078, -73.490331 41.473125, -73.490369 41.473172, -73.49041 41.473219, -73.490491 41.473309, -73.490531 41.473352, -73.490572 41.473396, -73.490615 41.473442, -73.490661 41.473488, -73.490711 41.473533, -73.490766 41.473573, -73.490827 41.473607, -73.490899 41.473629, -73.490971 41.473643, -73.491033 41.473648, -73.491139 41.473636, -73.49121 41.473622, -73.491278 41.473624, -73.491355 41.473664, -73.491394 41.473702, -73.491427 41.47375, -73.491451 41.4738, -73.491461 41.473846, -73.49147 41.473917, -73.491482 41.474024, -73.491258 41.474086, -73.491119 41.474153, -73.490899 41.474341, -73.490708 41.474605, -73.490576 41.474918, -73.490481 41.475236, -73.490444 41.475713, -73.490543 41.47603, -73.490787 41.476382, -73.491102 41.476649, -73.491253 41.476771, -73.491371 41.477004, -73.491483 41.477468, -73.491595 41.477663, -73.491939 41.47805, -73.492609 41.47847, -73.493759 41.479089, -73.494386 41.479427, -73.494663 41.479492, -73.496807 41.479057, -73.496682 41.479316, -73.496606 41.479623, -73.496571 41.479816, -73.496542 41.480153, -73.496543 41.480165, -73.496587 41.48045, -73.496708 41.480876, -73.49686 41.481212, -73.497088 41.481595, -73.498184 41.483436, -73.498574 41.48384, -73.499152 41.48424, -73.499405 41.484368, -73.499482 41.484403, -73.499819 41.484557, -73.500307 41.484753, -73.500652 41.484863, -73.500761 41.485035, -73.50082 41.485128, -73.50088 41.485221, -73.500988 41.485391, -73.501998 41.486975, -73.502081 41.487106, -73.50237 41.487479, -73.502499 41.487288, -73.502602 41.487058, -73.502949 41.486293, -73.50328 41.485782, -73.503543 41.485378, -73.503676 41.485403, -73.504076 41.485479, -73.50415 41.485493, -73.504209 41.48551, -73.504273 41.485527, -73.504416 41.485566, -73.504464 41.485586, -73.504526 41.485612, -73.504557 41.485535, -73.504565 41.485518, -73.504683 41.485299, -73.504697 41.485246, -73.504724 41.485149, -73.504865 41.485013, -73.505026 41.48486, -73.505307 41.484627, -73.505458 41.484503, -73.505506 41.484464, -73.505667 41.484341, -73.505925 41.4841, -73.506344 41.483607, -73.506474 41.483457, -73.506743 41.483126, -73.507072 41.482669, -73.507432 41.482173, -73.507501 41.482335, -73.507508 41.482427, -73.507523 41.482617, -73.507458 41.482897, -73.507515 41.483027, -73.507662 41.483144, -73.507692 41.483168, -73.507896 41.483277, -73.507883 41.483483, -73.507836 41.483538, -73.507722 41.483675, -73.5077 41.483741, -73.507911 41.484118, -73.507986 41.484252, -73.508122 41.484496, -73.508224 41.484679, -73.508327 41.484862, -73.508364 41.484927, -73.508371 41.484971, -73.508422 41.48518, -73.508473 41.485218, -73.508612 41.485268, -73.50878 41.4854, -73.50878 41.485422, -73.508845 41.485812, -73.508896 41.48591, -73.508998 41.485998, -73.509575 41.486306, -73.509783 41.486435, -73.509903 41.486509, -73.510122 41.486619, -73.51029 41.486746, -73.510363 41.486757, -73.51056 41.486751, -73.510713 41.486729, -73.511056 41.486713, -73.511421 41.486763, -73.51229 41.486675, -73.512728 41.486747, -73.512903 41.486686, -73.513444 41.486423, -73.513824 41.486363, -73.51413 41.48639, -73.514868 41.486336, -73.515335 41.48632, -73.51557 41.486406, -73.516087 41.486594, -73.516313 41.486611, -73.516612 41.48665, -73.516854 41.486628, -73.517087 41.486529, -73.517609 41.486122, -73.517715 41.486041, -73.518029 41.485892, -73.51819 41.485915, -73.518256 41.485898, -73.51849 41.485679, -73.518942 41.485476, -73.519074 41.485481, -73.519709 41.485663, -73.520129 41.485684, -73.520453 41.485701, -73.520694 41.485701, -73.520913 41.48568, -73.521512 41.485636, -73.521724 41.485565, -73.522133 41.485329, -73.522272 41.485301, -73.522746 41.485049, -73.522878 41.484956, -73.523243 41.484797, -73.523674 41.48472, -73.52401 41.48472, -73.524455 41.484841, -73.52471 41.484951, -73.524871 41.485181, -73.525112 41.485374, -73.525177 41.485396, -73.525345 41.48539, -73.525623 41.485264, -73.52574 41.485149, -73.525849 41.485, -73.526003 41.48488, -73.526039 41.484863, -73.526149 41.484896, -73.526244 41.484979, -73.526316 41.485116, -73.526294 41.485336, -73.526214 41.485456, -73.526207 41.485517, -73.526258 41.485588, -73.526404 41.485643, -73.526499 41.485791, -73.526499 41.486005, -73.526491 41.486088, -73.526425 41.486154, -73.526491 41.486357, -73.526352 41.486516, -73.526345 41.486582, -73.52641 41.486714, -73.526578 41.486867, -73.526835 41.487209, -73.526673 41.487323, -73.52656 41.48745, -73.526504 41.487719, -73.526603 41.488016, -73.52673 41.488314, -73.526956 41.488724, -73.527254 41.489135, -73.52891 41.490762, -73.529065 41.49089, -73.529349 41.490975, -73.529832 41.491173, -73.529912 41.491233, -73.53 41.491398, -73.530036 41.491535, -73.53 41.492029, -73.529963 41.492584, -73.529882 41.493133, -73.529904 41.493242, -73.529963 41.493305, -73.530007 41.493257, -73.530049 41.49321, -73.530329 41.493009, -73.530534 41.492888, -73.530728 41.492761, -73.531113 41.49251, -73.53205 41.491952, -73.532181 41.491856, -73.532347 41.491735, -73.532795 41.49129, -73.533114 41.490841, -73.533222 41.490463, -73.533269 41.490384, -73.533384 41.490287, -73.533692 41.490073, -73.533932 41.489937, -73.534386 41.489723, -73.534569 41.489593, -73.534678 41.489483, -73.534753 41.489373, -73.534794 41.489213, -73.534896 41.488637, -73.53498 41.488353, -73.535182 41.487952, -73.53528 41.48781, -73.535349 41.487752, -73.53553 41.487666, -73.53572 41.487606, -73.535881 41.487572, -73.53651 41.487472, -73.536776 41.487421, -73.536934 41.487373, -73.537148 41.487267, -73.537397 41.487105, -73.537751 41.486828, -73.538269 41.486384, -73.538615 41.486101, -73.53886 41.485947, -73.539062 41.485876, -73.539197 41.485857, -73.539543 41.485853, -73.539687 41.48584, -73.54016 41.485751, -73.540348 41.485736, -73.54043 41.485181, -73.540586 41.484365, -73.540713 41.483937, -73.540765 41.48313, -73.540842 41.482683, -73.540961 41.482265, -73.540991 41.482165, -73.541072 41.481829, -73.541149 41.481349, -73.541107 41.480982, -73.54136 41.480433, -73.541377 41.480397, -73.541458 41.480217, -73.541679 41.479737, -73.541744 41.479562, -73.541919 41.479095, -73.542276 41.479137, -73.542841 41.479167, -73.542589 41.479845, -73.542557 41.479912, -73.542531 41.479955, -73.542507 41.480088, -73.542512 41.480107, -73.542523 41.480162, -73.542561 41.480337, -73.542609 41.480551, -73.5428 41.48045, -73.543017 41.480321, -73.543217 41.480198, -73.543537 41.479912, -73.543697 41.479454, -73.543717 41.479398, -73.543747 41.479252, -73.543752 41.479226, -73.543935 41.479225, -73.544074 41.479214, -73.544421 41.479156, -73.544504 41.479105, -73.544546 41.479069, -73.54478 41.478951, -73.545342 41.478742, -73.545504 41.4787, -73.545685 41.47867, -73.546018 41.478693, -73.546095 41.478687, -73.546165 41.478665, -73.54619 41.478629, -73.546199 41.478045, -73.546189 41.477373, -73.546192 41.477318, -73.546221 41.476831, -73.546214 41.476719, -73.546199 41.476668, -73.546155 41.476643, -73.54604 41.476621, -73.545835 41.476615, -73.545126 41.476552, -73.545218 41.476507, -73.545344 41.476426, -73.546448 41.475534, -73.546736 41.475313, -73.54685 41.475252, -73.546959 41.475229, -73.547065 41.475238, -73.547381 41.475359, -73.547423 41.475382, -73.547442 41.475427, -73.547506 41.475695, -73.547543 41.475808, -73.547608 41.475907, -73.547736 41.47602, -73.548241 41.476339, -73.548392 41.476424, -73.548545 41.476478, -73.548453 41.476609, -73.548159 41.47696, -73.548072 41.477088, -73.548019 41.477319, -73.547998 41.477638, -73.548007 41.477964, -73.548057 41.479092, -73.548049 41.479471, -73.548224 41.479766, -73.548404 41.480018, -73.548478 41.480092, -73.548545 41.480125, -73.548655 41.48015, -73.548724 41.480154, -73.548761 41.480148, -73.548844 41.480131, -73.548921 41.480088, -73.548957 41.480027, -73.548965 41.479929, -73.548959 41.479503, -73.548932 41.479018, -73.549038 41.478999, -73.549652 41.478997, -73.549882 41.479, -73.549887 41.478683, -73.549875 41.47833, -73.549852 41.47762, -73.550577 41.477607, -73.550677 41.47758, -73.550685 41.477573, -73.550738 41.477532, -73.550767 41.477482, -73.550773 41.477358, -73.550779 41.477237, -73.550758 41.476981, -73.550748 41.476364, -73.550874 41.476357, -73.551164 41.476317, -73.551293 41.476274, -73.551221 41.476151, -73.551122 41.47601, -73.550822 41.475663, -73.550754 41.475568, -73.55062 41.475333, -73.550584 41.475249, -73.55056 41.475099, -73.550591 41.475, -73.550807 41.474792, -73.550908 41.474664, -73.550935 41.474618, -73.550981 41.47448, -73.550995 41.474276, -73.551173 41.474294, -73.551233 41.474308, -73.551375 41.47435, -73.551485 41.474406, -73.551537 41.474474, -73.551622 41.474703, -73.551647 41.474741, -73.551728 41.47481, -73.551793 41.474846, -73.55186 41.47488, -73.552005 41.47493, -73.552338 41.475, -73.552683 41.47505, -73.55305 41.475077, -73.553147 41.475057, -73.553172 41.475043, -73.553211 41.475001, -73.553271 41.47489, -73.553365 41.474555, -73.553457 41.474087, -73.553544 41.473749, -73.55359 41.473513, -73.553592 41.473315, -73.55358 41.473097, -73.553578 41.473051, -73.55349 41.472232, -73.553447 41.4721, -73.553404 41.472047, -73.553386 41.472032, -73.553338 41.472019, -73.552935 41.472044, -73.552517 41.472094, -73.552379 41.471043, -73.552171 41.469393, -73.552169 41.469217, -73.552151 41.469094, -73.552143 41.469086, -73.552132 41.469075, -73.552073 41.469062, -73.551767 41.469065, -73.551337 41.468936, -73.551004 41.468837, -73.55094 41.468794, -73.550975 41.468739, -73.55102 41.468592, -73.55107 41.467567, -73.551181 41.467032, -73.551685 41.467041, -73.551695 41.46695, -73.551678 41.466809, -73.551616 41.466669, -73.551399 41.465979, -73.551256 41.465649, -73.551184 41.46548, -73.551237 41.465414, -73.55126 41.465341, -73.551264 41.464865, -73.551249 41.464736, -73.551204 41.464637, -73.55116 41.464584, -73.551096 41.464524, -73.550988 41.464466, -73.55088 41.464437, -73.550151 41.464331, -73.549863 41.464342, -73.549812 41.464165, -73.549782 41.464096, -73.549724 41.464009, -73.549639 41.463944, -73.549518 41.463879, -73.548812 41.463618, -73.548894 41.463488, -73.548925 41.463401, -73.548932 41.463312, -73.548924 41.463152, -73.548897 41.463062, -73.548396 41.462369, -73.548292 41.462259, -73.54801 41.462037, -73.547999 41.461979, -73.548024 41.461907, -73.548239 41.461969, -73.548988 41.462228, -73.548837 41.461922, -73.548718 41.461605, -73.548676 41.461476, -73.548659 41.461405, -73.548653 41.461333, -73.548611 41.461271, -73.548581 41.461227, -73.548544 41.461207, -73.54848 41.461202, -73.548385 41.461204, -73.548208 41.461222, -73.548144 41.461216, -73.54808 41.461193, -73.548016 41.46116, -73.547891 41.461119, -73.54732 41.460986, -73.547231 41.460963, -73.547329 41.460426, -73.547652 41.460294, -73.547569 41.460247, -73.547398 41.460113, -73.547368 41.460083, -73.547195 41.459908, -73.547003 41.459662, -73.546845 41.459418, -73.546825 41.459388, -73.546667 41.459217, -73.54648 41.459083, -73.545894 41.458719, -73.545755 41.458623, -73.545448 41.458367, -73.545321 41.458288, -73.544844 41.458113, -73.544565 41.457964, -73.544276 41.457778, -73.544093 41.457688, -73.54387 41.457616, -73.543787 41.457595, -73.543615 41.45757, -73.543605 41.4574, -73.543555 41.457285, -73.543365 41.457032, -73.543107 41.456731, -73.543076 41.456646, -73.543061 41.456437, -73.543089 41.456208, -73.543116 41.456155, -73.54317 41.456093, -73.543315 41.455709, -73.54358 41.455285, -73.543615 41.455214, -73.543709 41.454765, -73.543783 41.454613, -73.543813 41.454518, -73.543811 41.454462, -73.543777 41.454356, -73.54363 41.454025, -73.543623 41.453933, -73.543751 41.453519, -73.543784 41.453439, -73.54385 41.453318, -73.543887 41.453284, -73.544011 41.453205, -73.544095 41.453168, -73.544442 41.453047, -73.544636 41.452998, -73.544908 41.452946, -73.545227 41.452927, -73.545649 41.452902, -73.546107 41.452892, -73.546176 41.452904, -73.546315 41.452942, -73.546511 41.45303, -73.546596 41.453082, -73.546815 41.453241, -73.546948 41.453363, -73.547196 41.45365, -73.547473 41.454046, -73.547572 41.454242, -73.547624 41.454398, -73.547646 41.455108, -73.547682 41.45558, -73.547735 41.455739, -73.5478 41.455863, -73.547918 41.456017, -73.54806 41.456129, -73.548227 41.456225, -73.548417 41.456312, -73.549748 41.456839, -73.550068 41.456975, -73.550289 41.457048, -73.550302 41.456944, -73.55034 41.456795, -73.550401 41.456669, -73.550498 41.45651, -73.550726 41.456264, -73.550927 41.456098, -73.552054 41.455286, -73.552238 41.455189, -73.552491 41.455096, -73.552831 41.45493, -73.552849 41.454834, -73.553053 41.45376, -73.553289 41.452694, -73.553372 41.452339, -73.553422 41.452128, -73.553563 41.45167, -73.553783 41.451158, -73.55399 41.450789, -73.554195 41.450474, -73.554984 41.449365, -73.550619 41.449785, -73.545868 41.450278, -73.545521 41.450096, -73.54534 41.449964, -73.544826 41.449936, -73.543767 41.450002, -73.542784 41.450105, -73.542149 41.450088, -73.542018 41.450125, -73.541905 41.450143, -73.541522 41.450177, -73.541198 41.450196, -73.541111 41.450207, -73.540842 41.450301, -73.540485 41.450448, -73.540294 41.450491, -73.540168 41.450505, -73.540021 41.450505, -73.539889 41.450494, -73.539629 41.450494, -73.539561 41.450502, -73.539451 41.450541, -73.539405 41.450572, -73.539341 41.450652, -73.539203 41.450923, -73.53915 41.451032, -73.536036 41.451338, -73.536046 41.450978, -73.536055 41.450662, -73.536064 41.45019, -73.536287 41.447213, -73.536334 41.446632, -73.536406 41.446066, -73.536583 41.44492, -73.536732 41.443606, -73.536813 41.442827, -73.537026 41.440487, -73.537093 41.439706, -73.536924 41.440015, -73.536743 41.4403, -73.53633 41.440529, -73.536149 41.440584, -73.536027 41.440626, -73.535583 41.440724, -73.534495 41.440926, -73.534137 41.440992, -73.533063 41.441192, -73.532706 41.441259, -73.532202 41.441363, -73.531608 41.441487, -73.53143 41.441531, -73.530691 41.44168, -73.530525 41.441714, -73.5302 41.441827, -73.528403 41.44226, -73.523012 41.44356, -73.521216 41.443994, -73.521161 41.443976, -73.521041 41.443954, -73.520559 41.443868, -73.52051 41.44386, -73.520431 41.443849, -73.520332 41.443854, -73.520355 41.443763, -73.520413 41.443655, -73.520468 41.443555, -73.520633 41.443381, -73.520883 41.443229, -73.520936 41.443198, -73.521073 41.443127, -73.521045 41.442867, -73.520961 41.442089, -73.520947 41.441954, -73.520964 41.44192, -73.520934 41.44183, -73.521376 41.441596, -73.521431 41.441514, -73.521595 41.441276, -73.52167 41.441054, -73.521692 41.440887, -73.521695 41.440556, -73.521616 41.440356, -73.521489 41.440211, -73.521215 41.43997, -73.521131 41.439896, -73.520691 41.439689, -73.520839 41.439488, -73.520863 41.439421, -73.521007 41.43903, -73.521007 41.438807, -73.520917 41.438499, -73.520874 41.438352, -73.520799 41.438203, -73.520798 41.438005, -73.520797 41.437645, -73.520889 41.43743, -73.520898 41.437412, -73.520982 41.437256, -73.52108 41.437089, -73.521287 41.436739, -73.521345 41.436576, -73.52138 41.43653, -73.521786 41.43658, -73.522294 41.436508, -73.523326 41.436216, -73.523515 41.436163, -73.524524 41.435893, -73.525205 41.435738, -73.524872 41.434683, -73.524605 41.433907, -73.524429 41.433384, -73.524327 41.433271, -73.524196 41.433167, -73.523816 41.432886, -73.523428 41.432613, -73.523359 41.432571, -73.522922 41.432112, -73.522784 41.431967, -73.522432 41.43154, -73.522047 41.430927, -73.521879 41.430453, -73.521834 41.430298, -73.521787 41.430132, -73.521752 41.429815, -73.521639 41.429624, -73.521542 41.429389, -73.521385 41.429033, -73.521288 41.42881, -73.521172 41.42839, -73.521112 41.427976, -73.52117 41.427434, -73.521215 41.427152, -73.521233 41.427047, -73.521435 41.426551, -73.52148 41.426511, -73.52156 41.426385, -73.521724 41.426132, -73.521782 41.425996, -73.521817 41.42582, -73.521859 41.425615, -73.521811 41.425552, -73.521502 41.425269, -73.521102 41.424893, -73.520653 41.424495, -73.520385 41.424274, -73.520318 41.424218, -73.519978 41.423988, -73.519601 41.423752, -73.51925 41.423484, -73.518705 41.423067, -73.517713 41.422488, -73.517388 41.422306, -73.517269 41.422263, -73.517021 41.422056, -73.516893 41.42195, -73.516798 41.421909, -73.516642 41.421795, -73.516429 41.421593, -73.516384 41.421473, -73.516289 41.420962, -73.51618 41.419583, -73.516208 41.419402, -73.516297 41.419101, -73.516441 41.418822, -73.51655 41.418677, -73.516776 41.418468, -73.516982 41.418377, -73.517267 41.418287, -73.517816 41.418229, -73.518006 41.418171, -73.518514 41.417894, -73.518852 41.417796, -73.519473 41.417699, -73.520094 41.417655, -73.521381 41.417979, -73.521161 41.417789, -73.520955 41.417717, -73.520595 41.417582, -73.520477 41.417575, -73.520379 41.417594, -73.52032 41.417465, -73.520173 41.416748, -73.520055 41.416757, -73.519974 41.416774, -73.519537 41.416777, -73.51926 41.416751, -73.519128 41.416718, -73.518954 41.416635, -73.518268 41.416063, -73.518006 41.41574, -73.517926 41.415572, -73.518014 41.415551, -73.518152 41.415539, -73.518232 41.415481, -73.518269 41.415324, -73.518218 41.41513, -73.518174 41.415066, -73.518123 41.415066, -73.517962 41.415235, -73.517911 41.415263, -73.517853 41.415296, -73.51778 41.415295, -73.51778 41.415232, -73.518036 41.415028, -73.51816 41.414895, -73.518167 41.414868, -73.518014 41.414708, -73.517992 41.414515, -73.517672 41.413961, -73.517483 41.413648, -73.517366 41.413511, -73.517293 41.413326, -73.517132 41.413058, -73.517038 41.412768, -73.51695 41.412621, -73.516841 41.412437, -73.516747 41.412094, -73.516536 41.411803, -73.516638 41.411655, -73.516799 41.41163, -73.516999 41.411622, -73.51744 41.411607, -73.517637 41.411565, -73.518002 41.411371, -73.518028 41.411347, -73.518299 41.411105, -73.518389 41.411025, -73.518629 41.410888, -73.518753 41.410831, -73.518782 41.410817, -73.519607 41.410451, -73.519848 41.410269, -73.520256 41.410081, -73.520288 41.410054, -73.520322 41.410027, -73.520584 41.4099, -73.520769 41.409761, -73.520913 41.409652, -73.521049 41.40951, -73.521182 41.409372, -73.521409 41.409078, -73.521467 41.408952, -73.521584 41.408756, -73.521641 41.408563, -73.521737 41.408249, -73.521737 41.408201, -73.521737 41.408046, -73.521993 41.407608, -73.522212 41.407408, -73.522263 41.407382, -73.522298 41.407375, -73.522468 41.407344, -73.522832 41.407403, -73.523029 41.407455, -73.523568 41.407574, -73.523977 41.40766, -73.524269 41.407709, -73.524749 41.407679, -73.525121 41.407567, -73.52537 41.407444, -73.525399 41.407431, -73.52564 41.407173, -73.52588 41.406978, -73.526183 41.406786, -73.526174 41.406776, -73.52615 41.406748, -73.526142 41.406739, -73.526294 41.406654, -73.526477 41.406553, -73.526731 41.406366, -73.526873 41.406263, -73.527283 41.405994, -73.527524 41.405865, -73.527716 41.405718, -73.527737 41.405689, -73.528029 41.405291, -73.528323 41.404946, -73.528725 41.404774, -73.529056 41.404696, -73.529367 41.404553, -73.529647 41.404364, -73.529883 41.404157, -73.530205 41.403912, -73.53037 41.403803, -73.530723 41.403515, -73.531014 41.403258, -73.530859 41.403101, -73.530814 41.403063, -73.530518 41.402812, -73.53035 41.402656, -73.53018 41.402515, -73.529966 41.402337, -73.529371 41.401838, -73.529176 41.401695, -73.528365 41.4011, -73.527565 41.400562, -73.527139 41.400297, -73.526965 41.400184, -73.52673 41.40007, -73.526623 41.400028, -73.526134 41.399835, -73.526027 41.399801, -73.525708 41.399595, -73.525693 41.39958, -73.525581 41.399467, -73.525556 41.399431, -73.525426 41.39924, -73.525206 41.398935, -73.525125 41.398823, -73.525083 41.398775, -73.524716 41.39836, -73.524306 41.398085, -73.524426 41.397909, -73.524488 41.397866, -73.524556 41.397821, -73.524643 41.397762, -73.524741 41.397708, -73.524798 41.397681, -73.524865 41.397658, -73.524938 41.397645, -73.525021 41.397641, -73.525206 41.397672, -73.525303 41.397708, -73.525393 41.397745, -73.525427 41.397759, -73.525441 41.397704, -73.525594 41.397444, -73.525631 41.3974, -73.525676 41.397348, -73.525762 41.397248, -73.525857 41.397088, -73.525944 41.396791, -73.526018 41.396468, -73.526149 41.396096, -73.526222 41.395953, -73.526273 41.395904, -73.526452 41.39537, -73.526478 41.395294, -73.52655 41.395124, -73.526887 41.394305, -73.525649 41.394236, -73.525002 41.394243, -73.522709 41.39427, -73.520046 41.39461, -73.51937 41.394679, -73.51881 41.394737, -73.518101 41.394675, -73.517605 41.394572, -73.517502 41.394553, -73.5176 41.39443, -73.517707 41.3943, -73.517867 41.394042, -73.51795 41.393909, -73.51825 41.393519, -73.520462 41.393874, -73.523135 41.393797, -73.525051 41.393741, -73.530304 41.393629, -73.532118 41.393414, -73.533906 41.393017, -73.53894 41.391672, -73.538873 41.391529, -73.538917 41.391426, -73.539063 41.39132, -73.539143 41.39124, -73.539248 41.391153, -73.540523 41.390745, -73.540456 41.390433, -73.540381 41.390082, -73.540369 41.390038, -73.540276 41.389706, -73.540251 41.389517, -73.54024 41.389408, -73.540122 41.388858, -73.540604 41.388842, -73.540456 41.388451, -73.540442 41.388158, -73.540325 41.387621, -73.540297 41.387337, -73.540202 41.387097, -73.540165 41.386925, -73.540013 41.386594, -73.539736 41.386312, -73.539561 41.386053, -73.539473 41.385705, -73.539466 41.385619, -73.539444 41.385497, -73.539401 41.38538, -73.5394 41.3851, -73.539335 41.384505, -73.539226 41.384175, -73.539116 41.38375, -73.538774 41.382944, -73.538279 41.38197, -73.538192 41.381482, -73.538207 41.381221, -73.538105 41.381238, -73.537784 41.381216, -73.53761 41.381173, -73.537565 41.381136, -73.537602 41.381065, -73.538105 41.380941, -73.538119 41.380846, -73.538083 41.380774, -73.537923 41.380686, -73.537886 41.380551, -73.537806 41.380469, -73.537711 41.380467, -73.537449 41.380595, -73.53734 41.38062, -73.537305 41.380592, -73.537266 41.380561, -73.537332 41.380467, -73.537368 41.380382, -73.537303 41.380232, -73.537222 41.380177, -73.537186 41.3801, -73.537019 41.379972, -73.536852 41.379889, -73.536778 41.37982, -73.536706 41.379626, -73.536647 41.379409, -73.536632 41.379334, -73.536622 41.379276, -73.536596 41.379143, -73.536582 41.37889, -73.536655 41.378684, -73.537355 41.37799, -73.537581 41.377597, -73.537654 41.377233, -73.537676 41.376684, -73.537675 41.376382, -73.537688 41.376261, -73.537673 41.37613, -73.537682 41.375912, -73.53743 41.375717, -73.537297 41.375676, -73.536718 41.375659, -73.534924 41.375604, -73.535152 41.375564, -73.535414 41.375517, -73.535741 41.375516, -73.535907 41.375515, -73.536131 41.375517, -73.53642 41.375519, -73.536917 41.375525, -73.537303 41.375532, -73.537694 41.375546, -73.537722 41.375547, -73.537802 41.375563, -73.538079 41.375621, -73.538125 41.375633, -73.538232 41.375662, -73.538475 41.375726, -73.538608 41.375701, -73.538704 41.37562, -73.539 41.375375, -73.539048 41.375324, -73.53938 41.375028, -73.539492 41.374954, -73.539627 41.3749, -73.539887 41.374799, -73.539965 41.37479, -73.54016 41.37477, -73.540425 41.374788, -73.540475 41.3748, -73.54059 41.374828, -73.541114 41.375038, -73.54116 41.375057, -73.541357 41.375113, -73.541754 41.375177, -73.54189 41.375199, -73.542187 41.375247, -73.542173 41.376201, -73.54217 41.376421, -73.542177 41.376659, -73.542145 41.376742, -73.542049 41.37684, -73.542322 41.376822, -73.543142 41.376771, -73.543416 41.376754, -73.543425 41.376622, -73.543471 41.376258, -73.543619 41.375094, -73.543659 41.374775, -73.543722 41.374281, -73.543727 41.374236, -73.543744 41.374101, -73.54375 41.374057, -73.543945 41.37252, -73.544532 41.367911, -73.544728 41.366375, -73.544806 41.365758, -73.545042 41.36391, -73.545121 41.363294, -73.545155 41.362938, -73.545258 41.361871, -73.545293 41.361516, -73.542578 41.361594, -73.537339 41.361747, -73.534437 41.361931, -73.533839 41.361969, -73.533782 41.361986, -73.53363 41.361976, -73.532755 41.361799, -73.531759 41.361622, -73.530336 41.361395, -73.530189 41.361372, -73.528357 41.361127, -73.527608 41.361002, -73.526894 41.360895, -73.526054 41.360809, -73.525777 41.360781, -73.525349 41.360771, -73.524615 41.360755, -73.524312 41.360795, -73.523404 41.360919, -73.523102 41.360961, -73.522504 41.361026, -73.522316 41.361047, -73.521751 41.361095, -73.521541 41.361113, -73.51974 41.361323, -73.518089 41.361566, -73.517715 41.361626, -73.517501 41.361662, -73.517188 41.361713, -73.516374 41.36183, -73.516239 41.360863, -73.516189 41.360497, -73.516002 41.359273, -73.515881 41.358725, -73.515769 41.358184, -73.515742 41.357979, -73.515728 41.357868, -73.515753 41.357483, -73.51579 41.357248, -73.515794 41.357008, -73.515396 41.356941, -73.51525 41.356917, -73.514915 41.356896, -73.514193 41.356932, -73.513791 41.356953, -73.513073 41.357046, -73.512892 41.357104, -73.512732 41.35718, -73.512613 41.357247, -73.512457 41.357301, -73.512341 41.35737, -73.51224 41.357407, -73.511993 41.357467, -73.511688 41.357549, -73.511642 41.357366, -73.511607 41.357272, -73.511547 41.357149, -73.511398 41.356932, -73.511037 41.356617, -73.510729 41.356368, -73.510674 41.356279, -73.510643 41.356172, -73.510629 41.356044, -73.510556 41.355723, -73.510482 41.355549, -73.510424 41.355367, -73.51028 41.354997, -73.510248 41.35489, -73.510244 41.354788, -73.510229 41.354699, -73.510223 41.354532, -73.510275 41.354315, -73.510296 41.353663, -73.510114 41.353214, -73.509922 41.353011, -73.509717 41.352817, -73.509536 41.352936, -73.509477 41.353004, -73.509375 41.353092, -73.509299 41.353172, -73.509141 41.353309, -73.508965 41.353441, -73.508792 41.353496, -73.508624 41.353556, -73.508472 41.353592, -73.508254 41.353708, -73.508067 41.35391, -73.508028 41.354002, -73.507984 41.354248, -73.507988 41.354293, -73.508135 41.354497, -73.50833 41.354653, -73.508574 41.354779, -73.508681 41.354867, -73.508732 41.354987, -73.508742 41.355177, -73.50875 41.355884, -73.508751 41.355958, -73.508852 41.356223, -73.509257 41.356932, -73.509443 41.357287, -73.509472 41.3575, -73.509511 41.3577, -73.509548 41.358328, -73.509526 41.358847, -73.509437 41.359054, -73.509157 41.35947, -73.508917 41.359873, -73.508905 41.360081, -73.508954 41.360175, -73.50912 41.360523, -73.509151 41.360673, -73.50916 41.360715, -73.509171 41.360822, -73.509166 41.360951, -73.509141 41.361113, -73.509111 41.361248, -73.50909 41.361434, -73.509087 41.361548, -73.509107 41.361705, -73.509147 41.361866, -73.509222 41.362045, -73.509259 41.362109, -73.509329 41.362231, -73.509351 41.36227, -73.509443 41.362471, -73.509616 41.362683, -73.508851 41.362774, -73.508411 41.362828, -73.508393 41.36283, -73.506746 41.3631, -73.506569 41.363127, -73.505809 41.363246, -73.505644 41.363137, -73.505438 41.363068, -73.505185 41.362983, -73.505016 41.36297, -73.50458 41.362939, -73.50431 41.362939, -73.504143 41.362961, -73.503953 41.362941, -73.503898 41.362935, -73.503669 41.363004, -73.503472 41.36296, -73.503152 41.362785, -73.50289 41.362697, -73.502584 41.362647, -73.502545 41.362648, -73.502336 41.362658, -73.502234 41.36268, -73.501855 41.362976, -73.501805 41.363, -73.501783 41.3629, -73.501719 41.362603, -73.501698 41.362504, -73.501608 41.362138, -73.501573 41.361995, -73.500833 41.361747, -73.497729 41.360711, -73.497122 41.360508, -73.495885 41.360095, -73.495868 41.360119, -73.495934 41.360165, -73.495919 41.360245, -73.495879 41.360293, -73.495812 41.360319, -73.495737 41.360309, -73.495625 41.360497, -73.495501 41.360708, -73.4953 41.361066, -73.495279 41.361104, -73.49517 41.361241, -73.495096 41.361335, -73.494842 41.361549, -73.494806 41.361571, -73.494604 41.361702, -73.494532 41.361733, -73.494437 41.36176, -73.494373 41.361839, -73.494205 41.362206, -73.494165 41.362355, -73.49417 41.362452, -73.494205 41.362528, -73.494246 41.362586, -73.494331 41.362687, -73.494341 41.362702, -73.494387 41.362773, -73.494662 41.363076, -73.494425 41.363224, -73.494246 41.363337, -73.494053 41.363428, -73.493888 41.363482, -73.493708 41.363511, -73.493649 41.363514, -73.493576 41.363519, -73.49337 41.363514, -73.493351 41.36346, -73.493146 41.363145, -73.493003 41.362994, -73.492892 41.362906, -73.49288 41.362894, -73.492562 41.362573, -73.492339 41.362301, -73.492021 41.361819, -73.491798 41.361414, -73.491634 41.361105, -73.491621 41.361073, -73.491557 41.360914, -73.49154 41.360871, -73.491313 41.360154, -73.491185 41.359834, -73.49107 41.359627, -73.491029 41.35951, -73.490959 41.359421, -73.490787 41.359286, -73.490636 41.359167, -73.489745 41.358618, -73.489264 41.358431, -73.48821 41.358226, -73.487939 41.358145, -73.487845 41.358117, -73.487626 41.358027, -73.487466 41.357976, -73.487078 41.357932, -73.486936 41.357946, -73.48689 41.35795, -73.486768 41.357963, -73.486755 41.357967, -73.486712 41.357982, -73.486207 41.358153, -73.486005 41.358222, -73.485168 41.358357, -73.485139 41.358432, -73.485142 41.358888, -73.485143 41.358973, -73.485199 41.359418, -73.484998 41.359442, -73.484728 41.359451, -73.484586 41.359457, -73.484269 41.3594, -73.483959 41.359339, -73.483727 41.359235, -73.483391 41.35905, -73.483283 41.358991, -73.482974 41.35883, -73.482827 41.358765, -73.482711 41.358759, -73.482579 41.358757, -73.482454 41.358743, -73.482255 41.358695, -73.482074 41.358643, -73.481852 41.35867, -73.481647 41.358667, -73.481413 41.358732, -73.481247 41.358772, -73.481087 41.358827, -73.480922 41.358905, -73.480906 41.358908, -73.480639 41.358976, -73.480402 41.359078, -73.480422 41.359459, -73.480437 41.359719, -73.480431 41.359976, -73.480413 41.360146, -73.480356 41.360345, -73.480319 41.360494, -73.480308 41.360591, -73.480303 41.360649, -73.480311 41.360973, -73.480327 41.36103, -73.480331 41.361291, -73.480348 41.36154, -73.480364 41.361621, -73.480498 41.362292, -73.480609 41.362699, -73.480863 41.363513, -73.48093 41.363728, -73.480968 41.363911, -73.481034 41.364143, -73.481078 41.364329, -73.481089 41.364375, -73.48116 41.364636, -73.481217 41.364783, -73.481274 41.364865, -73.481316 41.364924, -73.481403 41.365007, -73.481681 41.365262, -73.481894 41.365442, -73.481962 41.3655, -73.482108 41.365641, -73.482435 41.366005, -73.482464 41.366042, -73.482758 41.366579, -73.482577 41.366524, -73.48242 41.366486, -73.482176 41.366489, -73.482047 41.366491, -73.481497 41.366613, -73.481354 41.366648, -73.480775 41.366794, -73.480431 41.366859, -73.479756 41.367006, -73.479382 41.367125, -73.479248 41.367179, -73.479016 41.367356, -73.47898 41.367389, -73.478769 41.36759, -73.478516 41.367807, -73.47835 41.367942, -73.478241 41.368029, -73.478127 41.368122, -73.47791 41.368287, -73.477799 41.368372, -73.477638 41.368359, -73.477478 41.368335, -73.47733 41.368256, -73.477213 41.368166, -73.47714 41.368094, -73.477047 41.368033, -73.476908 41.367921, -73.476767 41.367786, -73.476471 41.36767, -73.476146 41.367555, -73.475969 41.367506, -73.47584 41.367457, -73.475685 41.367409, -73.475567 41.367366, -73.475481 41.367309, -73.475395 41.367261, -73.475041 41.36698, -73.47486 41.366788, -73.474703 41.366589, -73.474492 41.366277, -73.474308 41.365949, -73.474223 41.365767, -73.474181 41.365658, -73.474107 41.365432, -73.474041 41.36518, -73.474005 41.364892, -73.473971 41.364448, -73.474 41.364139, -73.473998 41.363915, -73.473962 41.363658, -73.47372 41.363144, -73.473501 41.362842, -73.473424 41.362766, -73.473237 41.362595, -73.473931 41.36568, -73.474061 41.366243, -73.474283 41.366883, -73.474514 41.367587, -73.474674 41.367998, -73.474775 41.368366, -73.474859 41.368725, -73.474857 41.369317, -73.474879 41.369611, -73.474951 41.369891, -73.475067 41.370178, -73.475225 41.370442, -73.475417 41.370789, -73.474911 41.370958, -73.474256 41.371169, -73.473989 41.371236, -73.473921 41.371262, -73.473629 41.371375, -73.473517 41.371419, -73.473173 41.371542, -73.473159 41.371546, -73.472699 41.371679, -73.472344 41.371829, -73.472034 41.37192, -73.47162 41.372047, -73.471448 41.372092, -73.471035 41.372235, -73.470975 41.372255, -73.470673 41.372354, -73.470436 41.372448, -73.470116 41.372497, -73.469879 41.3726, -73.469707 41.372732, -73.469541 41.372846, -73.469493 41.372933, -73.469372 41.373099, -73.469335 41.37323, -73.469301 41.373386, -73.469277 41.373526, -73.469207 41.373755, -73.469119 41.373912, -73.469036 41.374021, -73.468965 41.374102, -73.468507 41.374429, -73.468291 41.374574, -73.468155 41.374648, -73.467935 41.374727, -73.467635 41.374836, -73.467274 41.374959, -73.467053 41.375035, -73.466848 41.375142, -73.466788 41.375175, -73.466476 41.37535, -73.466344 41.375461, -73.466247 41.375616, -73.466183 41.375788, -73.466165 41.375838, -73.466126 41.376081, -73.466118 41.376127, -73.466088 41.376182, -73.466017 41.376314, -73.465916 41.376431, -73.465904 41.376451, -73.465883 41.376491, -73.465835 41.376533, -73.46562 41.376423, -73.465287 41.376253, -73.464992 41.376068, -73.464788 41.375941, -73.464794 41.37587, -73.464815 41.37566, -73.464822 41.37559, -73.464868 41.375116, -73.464869 41.375069, -73.464877 41.374768, -73.464908 41.374507, -73.464917 41.374387, -73.464804 41.374133, -73.464679 41.374042, -73.464475 41.374034, -73.46423 41.374052, -73.464133 41.37406, -73.463712 41.374118, -73.463496 41.374126, -73.463087 41.374144, -73.462857 41.374201, -73.462648 41.374253, -73.462755 41.374714, -73.462758 41.374726, -73.462798 41.375096, -73.462862 41.375504, -73.462934 41.375647, -73.4632 41.376025, -73.463206 41.376033, -73.463465 41.376418, -73.463651 41.376688, -73.463676 41.376725, -73.463886 41.377015, -73.464116 41.377275, -73.464279 41.377446, -73.464506 41.377684, -73.464359 41.377856, -73.464272 41.378, -73.464115 41.37829, -73.46388 41.378957, -73.463824 41.379093, -73.463794 41.379168, -73.46374 41.379336, -73.46367 41.379455, -73.463597 41.379435, -73.463432 41.379352, -73.463036 41.379168, -73.462981 41.379137, -73.462631 41.378946, -73.461085 41.37797, -73.461035 41.377956, -73.460713 41.377867, -73.460599 41.377842, -73.46047 41.377824, -73.460296 41.377784, -73.460209 41.377774, -73.460008 41.377753, -73.459949 41.377738, -73.459865 41.377717, -73.459599 41.377675, -73.459496 41.37766, -73.459167 41.377619, -73.459094 41.37765, -73.459007 41.377661, -73.458823 41.377748, -73.458796 41.377762, -73.458569 41.377836, -73.458493 41.377756, -73.458268 41.377518, -73.458259 41.377508, -73.458192 41.377441, -73.458338 41.377345, -73.458351 41.377337, -73.458599 41.37689, -73.458685 41.376738, -73.458533 41.376695, -73.458547 41.376425, -73.458437 41.376122, -73.458189 41.37575, -73.458007 41.375585, -73.457643 41.375414, -73.457249 41.3753, -73.457147 41.375222, -73.45711 41.375096, -73.457205 41.375029, -73.457215 41.375012, -73.457232 41.374998, -73.457249 41.374965, -73.457249 41.374888, -73.4573 41.374717, -73.457256 41.374608, -73.456826 41.374207, -73.456819 41.374092, -73.456724 41.373905, -73.456608 41.373867, -73.456542 41.373861, -73.455944 41.373988, -73.455687 41.373403, -73.455669 41.374101, -73.456166 41.37456, -73.456278 41.374686, -73.456581 41.375028, -73.456725 41.375241, -73.45685 41.375394, -73.45697 41.375597, -73.456986 41.375723, -73.45701 41.375859, -73.457102 41.376065, -73.457661 41.376487, -73.457833 41.376588, -73.458066 41.376725, -73.458143 41.376803, -73.458169 41.376883, -73.458109 41.376964, -73.457976 41.37707, -73.457884 41.377132, -73.457696 41.377227, -73.457574 41.377276, -73.457526 41.377296, -73.456721 41.377678, -73.456653 41.377726, -73.456374 41.377924, -73.45652 41.378056, -73.456811 41.37832, -73.456966 41.378448, -73.457119 41.378575, -73.456981 41.378671, -73.456868 41.378751, -73.456733 41.378852, -73.456609 41.378931, -73.45658 41.378969, -73.456481 41.379105, -73.456203 41.37936, -73.455796 41.379737, -73.455382 41.380139, -73.455112 41.380403, -73.4551 41.380417, -73.455065 41.380462, -73.455054 41.380477, -73.454902 41.380649, -73.454683 41.3809, -73.454462 41.381176, -73.454384 41.381275, -73.454338 41.381367, -73.454151 41.38124, -73.453809 41.381007, -73.453586 41.38087, -73.453395 41.380752, -73.453366 41.380733, -73.453276 41.380673, -73.453259 41.38065, -73.453314 41.380513, -73.453323 41.38039, -73.453243 41.380267, -73.45311 41.380138, -73.452948 41.38001, -73.452774 41.379884, -73.452638 41.379771, -73.452534 41.379674, -73.452438 41.379611, -73.452325 41.379524, -73.45225 41.379449, -73.452206 41.379411, -73.452144 41.379347, -73.452044 41.379206, -73.452026 41.379096, -73.4521 41.378618, -73.452067 41.378373, -73.451956 41.378226, -73.45178 41.378148, -73.451546 41.378137, -73.451325 41.37817, -73.451235 41.378268, -73.451198 41.378362, -73.451157 41.378439, -73.450957 41.378942, -73.450913 41.379135, -73.450961 41.379422, -73.450911 41.379583, -73.450861 41.379644, -73.450654 41.379735, -73.449689 41.38003, -73.448723 41.380346, -73.448399 41.380421, -73.448102 41.380534, -73.44775 41.380603, -73.447489 41.380684, -73.447019 41.380784, -73.446638 41.380841, -73.446424 41.380896, -73.446234 41.380992, -73.446101 41.381092, -73.445927 41.381226, -73.445868 41.381273, -73.445673 41.381439, -73.445592 41.381549, -73.445455 41.381681, -73.445446 41.381691, -73.445326 41.381756, -73.445257 41.381744, -73.445201 41.381713, -73.445146 41.381632, -73.445112 41.381503, -73.445079 41.381348, -73.445054 41.380844, -73.445051 41.380821, -73.444966 41.380118, -73.444888 41.379836, -73.444786 41.379665, -73.444552 41.379428, -73.444347 41.379306, -73.443311 41.378761, -73.442924 41.378558, -73.442463 41.37827, -73.442156 41.378021, -73.44209 41.377968, -73.441596 41.377624, -73.44124 41.377359, -73.441197 41.377326, -73.440885 41.377085, -73.440596 41.37679, -73.44038 41.376561, -73.440312 41.376488, -73.440128 41.376202, -73.43981 41.375068, -73.439696 41.374719, -73.439619 41.374538, -73.439559 41.374396, -73.439485 41.374294, -73.439318 41.374162, -73.439109 41.374059, -73.438786 41.373959, -73.438712 41.373936, -73.438223 41.373865, -73.438102 41.373754, -73.437995 41.373498, -73.437982 41.373467, -73.437904 41.373174, -73.437814 41.373205, -73.437748 41.373263, -73.437716 41.373334, -73.437694 41.373402, -73.437664 41.373638, -73.437613 41.37371, -73.437321 41.37377, -73.437191 41.373805, -73.43716 41.373814, -73.436992 41.373929, -73.436904 41.374049, -73.436606 41.374334, -73.436183 41.374608, -73.435979 41.374797, -73.435759 41.375065, -73.435597 41.375601, -73.435495 41.375694, -73.435297 41.375766, -73.435049 41.375782, -73.434865 41.375765, -73.434353 41.375661, -73.434251 41.375659, -73.434162 41.375686, -73.434089 41.375714, -73.434016 41.375748, -73.433947 41.375795, -73.433845 41.375888, -73.433932 41.375509, -73.434196 41.374372, -73.434284 41.373994, -73.434569 41.373375, -73.435021 41.372399, -73.434769 41.371463, -73.434593 41.370806, -73.434397 41.369657, -73.434229 41.368318, -73.433704 41.367366, -73.434183 41.367222, -73.434564 41.36713, -73.434413 41.36684, -73.434372 41.366762, -73.434268 41.366577, -73.433335 41.364987, -73.433083 41.364602, -73.432989 41.364276, -73.432829 41.363507, -73.432679 41.362959, -73.432608 41.362943, -73.432398 41.362895, -73.432329 41.36288, -73.43168 41.360231, -73.430577 41.355726, -73.429574 41.352328, -73.428802 41.349713, -73.427968 41.346806, -73.42779 41.346186, -73.42558 41.338487, -73.425517 41.338267, -73.425591 41.338261, -73.425672 41.33827, -73.42574 41.338294, -73.42576 41.338371, -73.425752 41.338391, -73.425729 41.338447, -73.425667 41.338496, -73.425818 41.338855, -73.425876 41.338993, -73.425978 41.339098, -73.426394 41.339266, -73.426568 41.339366, -73.42672 41.339473, -73.426795 41.33954, -73.426905 41.339758, -73.42709 41.340007, -73.427162 41.340072, -73.427249 41.340142, -73.427348 41.340199, -73.427658 41.340379, -73.427817 41.34045, -73.427967 41.340532, -73.42814 41.34028, -73.428155 41.340194, -73.42817 41.340117, -73.428245 41.339541, -73.428282 41.339023, -73.428285 41.338986, -73.428289 41.338804, -73.428354 41.338644, -73.428507 41.338667, -73.428579 41.338655, -73.428654 41.338643, -73.428774 41.338611, -73.429193 41.338447, -73.429226 41.338439, -73.429449 41.338388, -73.429795 41.33839, -73.429915 41.338392, -73.42995 41.338392, -73.430345 41.33836, -73.430577 41.338311, -73.430799 41.338243, -73.430817 41.338236, -73.431144 41.338119, -73.431256 41.338015, -73.431338 41.337927, -73.431426 41.337787, -73.431468 41.337723, -73.431541 41.337521, -73.431583 41.337341, -73.431748 41.337126, -73.432148 41.336724, -73.432218 41.336655, -73.43247 41.336427, -73.432667 41.336282, -73.432852 41.336138, -73.433129 41.335949, -73.433335 41.33581, -73.434066 41.335374, -73.434672 41.335102, -73.435177 41.334946, -73.43532 41.334904, -73.43562 41.334817, -73.436109 41.334702, -73.435986 41.334468, -73.435979 41.334458, -73.43587 41.334293, -73.435784 41.334147, -73.435685 41.333997, -73.435612 41.333871, -73.435563 41.333743, -73.43553 41.333655, -73.435508 41.333475, -73.435511 41.333405, -73.435515 41.333299, -73.43552 41.333195, -73.435523 41.333125, -73.435532 41.332922, -73.435533 41.332857, -73.435477 41.332726, -73.435428 41.332633, -73.435322 41.332562, -73.435224 41.332505, -73.435066 41.332456, -73.434686 41.332459, -73.434264 41.332481, -73.434062 41.332448, -73.433878 41.332381, -73.433401 41.33211, -73.433174 41.332034, -73.432509 41.331946, -73.432127 41.331912, -73.431857 41.331841, -73.431651 41.331766, -73.431531 41.331672, -73.431444 41.331579, -73.431373 41.331005, -73.431349 41.330615, -73.4313 41.330193, -73.431523 41.330149, -73.432008 41.330109, -73.432497 41.330056, -73.432872 41.329984, -73.433192 41.329868, -73.433529 41.329837, -73.433696 41.329849, -73.433846 41.329855, -73.434068 41.329855, -73.434374 41.329818, -73.434725 41.329729, -73.435074 41.32963, -73.435404 41.329524, -73.435888 41.329438, -73.436383 41.329462, -73.436898 41.329495, -73.437062 41.329482, -73.437622 41.329438, -73.438159 41.329391, -73.43826 41.329364, -73.438361 41.329432, -73.438558 41.329544, -73.438753 41.329617, -73.439508 41.32995, -73.439794 41.330082, -73.440022 41.330217, -73.440082 41.330285, -73.440112 41.330343, -73.440455 41.33083, -73.440794 41.33133, -73.4409 41.33147, -73.441 41.331587, -73.441322 41.331908, -73.441393 41.331984, -73.441515 41.331951, -73.441705 41.331918, -73.441768 41.331904, -73.442023 41.331849, -73.442369 41.331743, -73.442881 41.331604, -73.443125 41.331539, -73.443255 41.331517, -73.443013 41.330854, -73.443013 41.330706, -73.44313 41.330442, -73.443093 41.330354, -73.443108 41.330327, -73.442984 41.330277, -73.442846 41.330201, -73.442868 41.329986, -73.442846 41.329937, -73.442744 41.329838, -73.442642 41.329805, -73.442315 41.329779, -73.442219 41.329772, -73.442176 41.329734, -73.442052 41.329525, -73.442001 41.329377, -73.441921 41.329317, -73.441236 41.329234, -73.441083 41.32913, -73.44085 41.329064, -73.440734 41.329053, -73.440442 41.328751, -73.44034 41.328757, -73.440289 41.328812, -73.440224 41.328784, -73.440071 41.328729, -73.440027 41.328773, -73.440005 41.328872, -73.439903 41.328883, -73.439743 41.328806, -73.439707 41.328735, -73.43943 41.328389, -73.439328 41.328285, -73.439233 41.328246, -73.438913 41.328208, -73.438257 41.328092, -73.437806 41.327928, -73.436976 41.327412, -73.436859 41.327274, -73.436837 41.327165, -73.436706 41.326775, -73.436713 41.326687, -73.436691 41.326643, -73.436546 41.326347, -73.436764 41.326281, -73.436823 41.326209, -73.43683 41.326006, -73.436932 41.325957, -73.437012 41.325891, -73.437107 41.325715, -73.437121 41.325545, -73.437063 41.324875, -73.437005 41.324771, -73.436954 41.324606, -73.436735 41.324562, -73.436629 41.324562, -73.43659 41.324562, -73.436226 41.324749, -73.435781 41.324716, -73.435689 41.324702, -73.435526 41.324678, -73.435397 41.324577, -73.437396 41.32334, -73.437298 41.323267, -73.436666 41.323647, -73.436018 41.323999, -73.43526 41.324455, -73.435177 41.324376, -73.435131 41.324338, -73.435104 41.324315, -73.434937 41.324255, -73.434689 41.324282, -73.434633 41.324266, -73.434578 41.324252, -73.434466 41.324221, -73.434276 41.324195, -73.434186 41.324101, -73.434092 41.324046, -73.433983 41.324035, -73.433906 41.32408, -73.433852 41.324155, -73.433807 41.324616, -73.43369 41.324709, -73.433543 41.324741, -73.433529 41.324743, -73.433457 41.324759, -73.433283 41.324729, -73.433181 41.324721, -73.433109 41.32471, -73.432744 41.324535, -73.432366 41.324419, -73.432206 41.324408, -73.431841 41.324518, -73.431681 41.324469, -73.431412 41.324463, -73.431215 41.324469, -73.430938 41.324381, -73.430567 41.324106, -73.430327 41.324002, -73.430094 41.323881, -73.429955 41.323722, -73.429912 41.323601, -73.429824 41.323442, -73.429686 41.323255, -73.429638 41.323209, -73.429577 41.323151, -73.429264 41.323096, -73.428878 41.323008, -73.428608 41.32297, -73.428455 41.323069, -73.428268 41.323229, -73.428251 41.323244, -73.428018 41.323425, -73.427873 41.323497, -73.427756 41.32359, -73.427639 41.323629, -73.427421 41.323634, -73.427188 41.323628, -73.427057 41.323568, -73.426751 41.323277, -73.426285 41.322673, -73.426016 41.322399, -73.425579 41.321921, -73.425557 41.321751, -73.425572 41.321663, -73.425557 41.321608, -73.425506 41.321564, -73.425382 41.321553, -73.425149 41.32158, -73.424836 41.321756, -73.424705 41.321855, -73.424676 41.321948, -73.424661 41.322206, -73.424756 41.322387, -73.424778 41.322503, -73.424705 41.322569, -73.424472 41.322662, -73.424333 41.322673, -73.423983 41.322582, -73.423836 41.323427, -73.423603 41.323405, -73.423476 41.323394, -73.423155 41.32336, -73.422904 41.323365, -73.422789 41.323368, -73.422671 41.323372, -73.422687 41.323507, -73.422651 41.323716, -73.422549 41.32382, -73.422476 41.323974, -73.422381 41.324089, -73.422192 41.324215, -73.42209 41.324243, -73.421931 41.324353, -73.421777 41.324462, -73.421733 41.324555, -73.421711 41.324704, -73.421748 41.324742, -73.421849 41.324748, -73.42193 41.324792, -73.422053 41.324934, -73.422279 41.325396, -73.422359 41.325687, -73.422221 41.325791, -73.422068 41.325862, -73.421703 41.326159, -73.421499 41.326477, -73.421478 41.326543, -73.421448 41.326609, -73.421419 41.326839, -73.421376 41.326933, -73.421332 41.327103, -73.42123 41.327262, -73.421114 41.327531, -73.421018 41.327756, -73.420953 41.328036, -73.420814 41.328332, -73.420625 41.328634, -73.420596 41.328755, -73.420486 41.328799, -73.420384 41.328816, -73.419955 41.328936, -73.419867 41.328925, -73.419776 41.32894, -73.419678 41.329057, -73.419612 41.329084, -73.419503 41.329101, -73.419452 41.329139, -73.419467 41.329222, -73.419467 41.329348, -73.419383 41.329568, -73.419317 41.329587, -73.419161 41.329633, -73.419015 41.329683, -73.418716 41.329886, -73.418556 41.330061, -73.418439 41.330391, -73.418323 41.330715, -73.418315 41.330729, -73.418272 41.330813, -73.418148 41.330907, -73.418068 41.331027, -73.417834 41.331357, -73.417837 41.331406, -73.417842 41.331505, -73.417725 41.331659, -73.417412 41.33195, -73.41739 41.331999, -73.417477 41.332103, -73.417601 41.332114, -73.41779 41.332157, -73.417841 41.332169, -73.417892 41.332263, -73.418002 41.332345, -73.418162 41.332526, -73.418249 41.332713, -73.418169 41.333064, -73.418242 41.33324, -73.418244 41.333279, -73.418249 41.333394, -73.418118 41.333701, -73.417899 41.333948, -73.417841 41.334058, -73.417724 41.334151, -73.417396 41.334299, -73.417236 41.334393, -73.417134 41.334508, -73.417047 41.334706, -73.416894 41.334947, -73.416825 41.335015, -73.41666 41.335183, -73.416595 41.335265, -73.41626 41.335573, -73.41618 41.335694, -73.416209 41.335792, -73.416218 41.335977, -73.416223 41.336094, -73.416107 41.336149, -73.415964 41.336247, -73.415924 41.336275, -73.415465 41.336314, -73.415036 41.336484, -73.415007 41.336528, -73.414897 41.336583, -73.41481 41.336647, -73.414693 41.336736, -73.414465 41.336883, -73.414454 41.336898, -73.414394 41.336942, -73.414329 41.337, -73.414257 41.337175, -73.414222 41.337261, -73.414276 41.337269, -73.414297 41.337269, -73.414525 41.337279, -73.414601 41.337283, -73.414783 41.337291, -73.415055 41.337324, -73.41553 41.337382, -73.416191 41.337488, -73.416407 41.337535, -73.416538 41.337564, -73.416859 41.3376, -73.416968 41.337368, -73.417069 41.337156, -73.417264 41.336705, -73.417282 41.336669, -73.4174 41.336443, -73.41741 41.336404, -73.417441 41.336287, -73.417452 41.336249, -73.423045 41.335499, -73.421801 41.337471, -73.421229 41.338705, -73.421 41.339385, -73.420576 41.343661, -73.42034 41.344578, -73.419432 41.347699, -73.418702 41.34959, -73.418461 41.350607, -73.418579 41.355401, -73.418536 41.35657, -73.418401 41.357404, -73.418312 41.357316, -73.418222 41.357243, -73.418068 41.357096, -73.417831 41.356884, -73.417345 41.356449, -73.417125 41.356247, -73.416891 41.356034, -73.416788 41.35594, -73.41648 41.355659, -73.41644 41.355622, -73.416369 41.355577, -73.416347 41.355556, -73.416283 41.355494, -73.416262 41.355474, -73.415832 41.355043, -73.415413 41.354604, -73.415362 41.354542, -73.415035 41.354149, -73.414937 41.35401, -73.414806 41.353823, -73.414597 41.353497, -73.414591 41.353487, -73.41431 41.352926, -73.414191 41.352607, -73.414094 41.352035, -73.413964 41.351014, -73.413865 41.350224, -73.41381 41.349728, -73.413746 41.349774, -73.413706 41.349854, -73.413686 41.35022, -73.413716 41.350389, -73.413728 41.35056, -73.41372 41.350659, -73.413713 41.350743, -73.413701 41.350913, -73.413645 41.351336, -73.413554 41.351764, -73.413504 41.351923, -73.413352 41.352685, -73.413245 41.353039, -73.413159 41.353263, -73.413015 41.35348, -73.41296 41.353565, -73.412583 41.353927, -73.412571 41.353939, -73.41243 41.354027, -73.412281 41.354111, -73.412225 41.354152, -73.412057 41.354277, -73.412002 41.354319, -73.41168 41.354549, -73.411613 41.354598, -73.411234 41.355031, -73.41112 41.355288, -73.411033 41.355516, -73.410953 41.355707, -73.410867 41.355875, -73.410812 41.356018, -73.410731 41.356232, -73.410694 41.356381, -73.410675 41.356457, -73.410639 41.356607, -73.410479 41.357219, -73.410386 41.357655, -73.410367 41.357745, -73.41035 41.357959, -73.410357 41.358146, -73.410389 41.358495, -73.41044 41.359433, -73.410423 41.359898, -73.41019 41.360856, -73.410164 41.360964, -73.409992 41.361754, -73.409884 41.36174, -73.409803 41.361706, -73.409774 41.361679, -73.409758 41.36164, -73.409746 41.361568, -73.409719 41.361321, -73.409673 41.361103, -73.409612 41.360713, -73.409606 41.36067, -73.409539 41.360546, -73.409425 41.360463, -73.40866 41.360197, -73.408481 41.36002, -73.408274 41.359759, -73.408078 41.359548, -73.407746 41.359233, -73.40763 41.359097, -73.407572 41.358997, -73.407413 41.358534, -73.407338 41.35837, -73.407227 41.358239, -73.407071 41.358163, -73.406989 41.35814, -73.406713 41.358063, -73.406503 41.358017, -73.405918 41.358006, -73.405725 41.357954, -73.405457 41.357783, -73.405361 41.357721, -73.405021 41.357406, -73.404728 41.357175, -73.404695 41.357149, -73.404486 41.356971, -73.404423 41.356907, -73.404382 41.356865, -73.404277 41.356768, -73.40422 41.356734, -73.404144 41.356688, -73.403963 41.356622, -73.403784 41.356566, -73.402974 41.356429, -73.402661 41.356391, -73.402521 41.356349, -73.402351 41.356284, -73.40217 41.35611, -73.401906 41.355784, -73.401225 41.35476, -73.400826 41.354297, -73.400524 41.353965, -73.400391 41.353818, -73.400201 41.353624, -73.400105 41.353526, -73.399725 41.353139, -73.399689 41.353103, -73.399576 41.35299, -73.399239 41.352654, -73.399127 41.352542, -73.398914 41.352319, -73.398737 41.352149, -73.398696 41.35211, -73.398123 41.351745, -73.397477 41.351388, -73.397318 41.351297, -73.397173 41.351214, -73.396819 41.351061, -73.39688 41.351176, -73.397039 41.351386, -73.397107 41.351498, -73.397175 41.351611, -73.397934 41.352967, -73.398712 41.354481, -73.398847 41.354743, -73.399048 41.355076, -73.399232 41.355381, -73.399163 41.355452, -73.399105 41.355511, -73.399054 41.355536, -73.398991 41.355563, -73.39892 41.355596, -73.398854 41.355637, -73.398676 41.355714, -73.398479 41.355792, -73.398381 41.35583, -73.395073 41.361073, -73.393306 41.363814, -73.392649 41.364539, -73.392196 41.364833, -73.390156 41.366477, -73.390079 41.366438, -73.38998 41.366405, -73.389953 41.3664, -73.389838 41.366381, -73.389711 41.36637, -73.389611 41.366372, -73.389498 41.366393, -73.389317 41.366466, -73.3893 41.366474, -73.389108 41.366526, -73.388842 41.366275, -73.388587 41.365972, -73.388547 41.365931, -73.387707 41.365075, -73.38719 41.364503, -73.386841 41.364178, -73.386507 41.363956, -73.386157 41.363712, -73.385965 41.363604, -73.385593 41.363397, -73.385369 41.363333, -73.385347 41.363327, -73.385161 41.363264, -73.385229 41.363456, -73.385367 41.363561, -73.385622 41.363681, -73.385855 41.363868, -73.385845 41.363885, -73.385811 41.363951, -73.385877 41.364005, -73.385906 41.36406, -73.385804 41.364148, -73.385796 41.364181, -73.385826 41.36422, -73.385818 41.364242, -73.385665 41.364263, -73.385687 41.364368, -73.385658 41.364412, -73.385556 41.364434, -73.385578 41.364532, -73.385687 41.364648, -73.385716 41.364714, -73.385694 41.364889, -73.385628 41.365142, -73.385606 41.365532, -73.385489 41.365526, -73.385431 41.36557, -73.385438 41.365652, -73.385533 41.365707, -73.385569 41.365812, -73.38554 41.365976, -73.385562 41.366086, -73.38563 41.366181, -73.385736 41.366328, -73.385678 41.36647, -73.385772 41.366624, -73.385663 41.367074, -73.38551 41.367113, -73.385488 41.367206, -73.385422 41.367255, -73.385255 41.367244, -73.385102 41.367206, -73.385087 41.367145, -73.384912 41.367085, -73.384927 41.366964, -73.384869 41.366931, -73.384879 41.366896, -73.384898 41.366832, -73.384869 41.366805, -73.384563 41.366711, -73.384395 41.366706, -73.38406 41.366804, -73.383805 41.36681, -73.383579 41.366722, -73.383521 41.36665, -73.383492 41.366585, -73.383535 41.366436, -73.383506 41.366409, -73.383463 41.366403, -73.382974 41.366491, -73.38277 41.366474, -73.382472 41.366496, -73.382238 41.366502, -73.382012 41.366397, -73.381942 41.366379, -73.381838 41.366353, -73.381787 41.366243, -73.381699 41.366166, -73.381588 41.366089, -73.381535 41.365988, -73.381365 41.365661, -73.381228 41.365561, -73.381001 41.365824, -73.380676 41.366108, -73.380655 41.366172, -73.380698 41.366345, -73.380843 41.366675, -73.380878 41.366957, -73.380781 41.367417, -73.380823 41.367613, -73.380842 41.367656, -73.38114 41.368321, -73.381452 41.369036, -73.381009 41.369147, -73.380371 41.369309, -73.379674 41.369448, -73.379227 41.369538, -73.378175 41.370038, -73.378074 41.369952, -73.37795 41.369897, -73.377753 41.370029, -73.377527 41.370139, -73.377141 41.3701, -73.377229 41.37004, -73.377228 41.370018, -73.377141 41.370018, -73.377097 41.370007, -73.377076 41.36993, -73.377097 41.369902, -73.377032 41.369765, -73.376952 41.369804, -73.376835 41.369842, -73.376755 41.369825, -73.37669 41.369727, -73.376415 41.369708, -73.376281 41.369699, -73.376092 41.369704, -73.375742 41.36966, -73.375502 41.369561, -73.375358 41.369623, -73.375369 41.369646, -73.375527 41.369882, -73.375612 41.370009, -73.375703 41.370131, -73.375927 41.370472, -73.376014 41.370672, -73.376089 41.370845, -73.376128 41.370961, -73.375782 41.371004, -73.375579 41.371065, -73.375213 41.371194, -73.375086 41.371246, -73.375069 41.371257, -73.375009 41.371296, -73.374873 41.371402, -73.374793 41.371539, -73.374706 41.37172, -73.374546 41.371947, -73.374285 41.372144, -73.374204 41.372195, -73.374076 41.372247, -73.373934 41.372276, -73.373726 41.372301, -73.373541 41.372298, -73.373367 41.37229, -73.37321 41.372263, -73.373059 41.3722, -73.372935 41.372116, -73.372844 41.372046, -73.372744 41.371912, -73.372626 41.3718, -73.372502 41.371722, -73.372407 41.371693, -73.372363 41.37168, -73.372253 41.371681, -73.372072 41.371703, -73.371773 41.371753, -73.371678 41.37177, -73.371311 41.371797, -73.371068 41.371834, -73.370874 41.371886, -73.370676 41.371941, -73.370315 41.372132, -73.370177 41.372225, -73.369981 41.372418, -73.36986 41.372512, -73.369751 41.372581, -73.369739 41.37259, -73.369658 41.372657, -73.369559 41.372727, -73.369382 41.37283, -73.36902 41.372958, -73.368366 41.373141, -73.367915 41.373276, -73.367879 41.373288, -73.367545 41.373375, -73.367373 41.373421, -73.367356 41.373426, -73.3672 41.373449, -73.367029 41.373469, -73.366843 41.373473, -73.366755 41.373475, -73.366666 41.373481, -73.366097 41.373503, -73.366049 41.373505, -73.365717 41.373542, -73.365262 41.373574, -73.364623 41.373576, -73.364392 41.373568, -73.36431 41.373566, -73.364001 41.373551, -73.363912 41.373554, -73.363824 41.373551, -73.363547 41.373553, -73.363514 41.373558, -73.363239 41.373601, -73.362904 41.373658, -73.362597 41.373714, -73.362293 41.37377, -73.362135 41.37357, -73.361882 41.373288, -73.361808 41.373198, -73.361311 41.372597, -73.360992 41.372101, -73.360652 41.37164, -73.360493 41.371377, -73.360371 41.371175, -73.360058 41.370768, -73.35983 41.370431, -73.359688 41.37022, -73.359316 41.36969, -73.359217 41.369503, -73.359157 41.369415, -73.359067 41.369282, -73.359006 41.369177, -73.358968 41.36906, -73.358913 41.369066, -73.35875 41.369051, -73.358615 41.369043, -73.358493 41.369043, -73.358468 41.369045, -73.358344 41.36906, -73.357497 41.369193, -73.356984 41.36928, -73.356634 41.369341, -73.356507 41.369333, -73.356489 41.369329, -73.356378 41.369304, -73.356369 41.369298, -73.356286 41.369243, -73.356199 41.369171, -73.356086 41.369062, -73.356078 41.369054, -73.356002 41.368972, -73.355773 41.368724, -73.355618 41.368556, -73.355082 41.367988, -73.354975 41.367875, -73.354858 41.367738, -73.354764 41.367729, -73.35401 41.367672, -73.353756 41.367658, -73.353518 41.367651, -73.353315 41.367666, -73.353141 41.367712, -73.352929 41.367788, -73.352637 41.36795, -73.352427 41.368149, -73.352178 41.368293, -73.351378 41.368697, -73.351144 41.368815, -73.350486 41.369152, -73.350232 41.369241, -73.349974 41.369358, -73.349706 41.369425, -73.349444 41.369505, -73.349288 41.369542, -73.349133 41.369572, -73.348982 41.369607, -73.349196 41.36934, -73.349219 41.369255, -73.349236 41.369141, -73.34924 41.369068, -73.349249 41.368928, -73.349226 41.368818, -73.349201 41.368663, -73.348932 41.367735, -73.348784 41.367278, -73.348595 41.36669, -73.348472 41.366277, -73.348437 41.366173, -73.348386 41.366019, -73.348193 41.365448, -73.348089 41.365172, -73.34804 41.365063, -73.347979 41.36486, -73.347988 41.364633, -73.347991 41.364565, -73.347995 41.364316, -73.348007 41.364206, -73.348004 41.364094, -73.348087 41.363689, -73.348089 41.36368, -73.348126 41.363327, -73.348168 41.36268, -73.348194 41.362454, -73.348243 41.362044, -73.348321 41.360484, -73.348064 41.360406, -73.347765 41.360285, -73.347677 41.36023, -73.347612 41.360164, -73.347575 41.359989, -73.347568 41.35984, -73.347451 41.359758, -73.34724 41.359725, -73.346978 41.359621, -73.346839 41.359533, -73.346482 41.359226, -73.346268 41.359091, -73.34603 41.358941, -73.345746 41.35871, -73.345498 41.358595, -73.345301 41.358529, -73.344779 41.358046, -73.34447 41.357761, -73.344392 41.357728, -73.344376 41.357722, -73.344303 41.357706, -73.344004 41.357744, -73.343858 41.357728, -73.343596 41.357613, -73.343224 41.35741, -73.343057 41.357349, -73.343042 41.357393, -73.343039 41.357429, -73.34302 41.357794, -73.343029 41.357933, -73.341677 41.357772, -73.341371 41.357736, -73.341282 41.357758, -73.341248 41.357821, -73.341218 41.357901, -73.341187 41.357982, -73.341155 41.358063, -73.341124 41.358144, -73.341091 41.358225, -73.341056 41.358303, -73.341013 41.358376, -73.34096 41.358446, -73.340897 41.358512, -73.340822 41.358571, -73.340741 41.35863, -73.340657 41.358692, -73.340573 41.358757, -73.340489 41.35882, -73.34041 41.358881, -73.340328 41.358938, -73.340251 41.358992, -73.340174 41.359049, -73.340095 41.359106, -73.340018 41.359161, -73.339942 41.359214, -73.339708 41.35934, -73.339629 41.359363, -73.339545 41.35938, -73.339455 41.359393, -73.339359 41.359403, -73.33926 41.359408, -73.339158 41.35941, -73.339054 41.35941, -73.338946 41.35941, -73.338727 41.359408, -73.338698 41.359407, -73.338366 41.359736, -73.338155 41.359945, -73.337134 41.360244, -73.337285 41.360435, -73.337526 41.360673, -73.337776 41.360959, -73.337925 41.36111, -73.337988 41.361173, -73.338229 41.361363, -73.338414 41.361508, -73.338606 41.361623, -73.339141 41.361915, -73.339294 41.362007, -73.339509 41.362151, -73.339847 41.362509, -73.340046 41.362663, -73.340232 41.362819, -73.340455 41.362967, -73.341268 41.363539, -73.341734 41.363907, -73.342031 41.36412, -73.342185 41.364231, -73.342433 41.364356, -73.3427 41.364457, -73.343049 41.364572, -73.343207 41.364631, -73.343477 41.364715, -73.343564 41.364878, -73.343699 41.365103, -73.344133 41.365855, -73.344168 41.365923, -73.3445 41.366578, -73.34462 41.36695, -73.344688 41.367381, -73.34472 41.367495, -73.344761 41.367699, -73.344822 41.367817, -73.344932 41.367954, -73.345121 41.368279, -73.345317 41.36854, -73.345607 41.368873, -73.345946 41.369195, -73.345967 41.369212, -73.346122 41.369337, -73.346244 41.369414, -73.346313 41.369459, -73.346599 41.369592, -73.346785 41.369684, -73.346923 41.369752, -73.347285 41.369973, -73.347446 41.370108, -73.347597 41.37034, -73.347634 41.370401, -73.347788 41.370653, -73.348019 41.371016, -73.348215 41.371268, -73.348232 41.371289, -73.348438 41.371537, -73.348371 41.371907, -73.348353 41.37201, -73.348204 41.372307, -73.348046 41.372651, -73.347862 41.372907, -73.347643 41.373214, -73.347574 41.373181, -73.347341 41.373027, -73.347122 41.372945, -73.346875 41.372895, -73.34651 41.372851, -73.344539 41.375016, -73.344315 41.374956, -73.343825 41.374762, -73.343394 41.374581, -73.342644 41.374315, -73.342044 41.374103, -73.340713 41.373607, -73.340313 41.373409, -73.340093 41.373273, -73.339896 41.373054, -73.339674 41.372816, -73.339146 41.372117, -73.338586 41.371476, -73.3382 41.371144, -73.337829 41.370898, -73.337314 41.370557, -73.336814 41.370332, -73.336413 41.370201, -73.336004 41.370062, -73.335906 41.370043, -73.335613 41.369988, -73.335516 41.36997, -73.335104 41.3705, -73.334655 41.371032, -73.33454 41.371199, -73.334525 41.371266, -73.334501 41.371384, -73.334562 41.371561, -73.33468 41.371779, -73.334858 41.371968, -73.334965 41.372158, -73.335014 41.372355, -73.335014 41.372518, -73.334925 41.372722, -73.334677 41.373095, -73.334259 41.373802, -73.334081 41.374206, -73.333888 41.374589, -73.333782 41.374837, -73.333687 41.375082, -73.333588 41.375174, -73.333487 41.375344, -73.33344 41.375512, -73.333447 41.375629, -73.333467 41.375912, -73.33368 41.377251, -73.333732 41.378121, -73.333758 41.378548, -73.33376 41.378707, -73.333775 41.37975, -73.333443 41.380652, -73.333433 41.380676, -73.333112 41.381488, -73.333021 41.381541, -73.332985 41.381567, -73.332861 41.381662, -73.332658 41.38187, -73.332554 41.381978, -73.331984 41.382716, -73.331815 41.382965, -73.331735 41.383168, -73.331516 41.38398, -73.331346 41.38479, -73.331278 41.385156, -73.331172 41.385548, -73.330913 41.386396, -73.330878 41.386503, -73.330499 41.387668, -73.330485 41.387706, -73.330444 41.387821, -73.330431 41.38786, -73.33032 41.388172, -73.329994 41.389093, -73.329987 41.389107, -73.32985 41.389409, -73.329801 41.389539, -73.329758 41.389652, -73.32972 41.38973, -73.329669 41.389836, -73.329653 41.38987, -73.329624 41.38993, -73.329543 41.39013, -73.32931 41.390599, -73.32918 41.390838, -73.32906 41.390985, -73.32906 41.391095, -73.329117 41.391377, -73.329099 41.391596, -73.328915 41.39186, -73.328763 41.392048, -73.328809 41.392168, -73.328845 41.392261, -73.328916 41.392498, -73.329064 41.392816, -73.329116 41.393061, -73.32914 41.39324, -73.329146 41.393657, -73.329189 41.393939, -73.329187 41.39419, -73.329175 41.394303, -73.329138 41.394443, -73.329076 41.394602, -73.329066 41.394652, -73.329032 41.394692, -73.329019 41.394724, -73.328993 41.394767, -73.328907 41.394857, -73.328691 41.395016, -73.328048 41.395446, -73.32782 41.395565, -73.327564 41.395639, -73.3273 41.395739, -73.326935 41.395871, -73.326724 41.395962, -73.326267 41.396253, -73.325865 41.396536, -73.325372 41.397059, -73.325024 41.397417, -73.324763 41.397675, -73.324303 41.398082, -73.323841 41.398562, -73.323617 41.398891, -73.323298 41.399273, -73.323123 41.399499, -73.322701 41.399965, -73.322544 41.400205, -73.322405 41.400386, -73.322342 41.400507, -73.322253 41.400629, -73.322081 41.401206, -73.322061 41.401232, -73.322022 41.401283, -73.321885 41.401445, -73.321811 41.401496, -73.321689 41.401526, -73.32154 41.401546, -73.32108 41.401577, -73.320759 41.401606, -73.320657 41.401615, -73.320533 41.401622, -73.320451 41.401627, -73.320217 41.401631, -73.320028 41.401601, -73.319217 41.401314, -73.318842 41.40119, -73.318531 41.401042, -73.318233 41.400887, -73.317757 41.400724, -73.317609 41.400689, -73.317434 41.400687, -73.316581 41.400774, -73.315761 41.400867, -73.315546 41.400892, -73.314702 41.400959, -73.314395 41.400973, -73.314114 41.400991, -73.314343 41.40075, -73.314542 41.400582, -73.314901 41.400214, -73.315063 41.400061, -73.315306 41.399834, -73.315759 41.399412, -73.316257 41.398935, -73.317777 41.397521, -73.3174 41.397384, -73.316693 41.397104, -73.316489 41.397069, -73.315833 41.397132, -73.315024 41.397255, -73.314709 41.397273, -73.314491 41.397319, -73.313595 41.397473, -73.31331 41.397572, -73.313172 41.397665, -73.313099 41.397844, -73.313091 41.398091, -73.313179 41.398408, -73.313099 41.398938, -73.312705 41.39986, -73.312443 41.400113, -73.31234 41.400265, -73.312326 41.400418, -73.312369 41.40067, -73.312566 41.400993, -73.312778 41.401221, -73.312822 41.40165, -73.3128 41.401731, -73.3128 41.402073, -73.312778 41.402226, -73.31269 41.402319, -73.312516 41.402384, -73.312279 41.402423, -73.31223 41.402264, -73.31211 41.401957, -73.312062 41.401875, -73.311803 41.401432, -73.311727 41.401209, -73.311548 41.400413, -73.311494 41.400214, -73.31134 41.399645, -73.31133 41.399496, -73.311326 41.399139, -73.311328 41.399085, -73.311341 41.398764, -73.311401 41.398189, -73.311454 41.397614, -73.311486 41.397417, -73.311554 41.397265, -73.311807 41.396963, -73.311982 41.396809, -73.312001 41.396775, -73.312136 41.396549, -73.312243 41.396187, -73.312296 41.396019, -73.31238 41.395761, -73.312303 41.395665, -73.31224 41.395532, -73.312184 41.395411, -73.312109 41.395107, -73.312044 41.394751, -73.312025 41.394648, -73.312008 41.394485, -73.311771 41.394494, -73.311647 41.394484, -73.311517 41.394474, -73.310955 41.39446, -73.310567 41.394431, -73.310414 41.39442, -73.310209 41.394391, -73.309941 41.394335, -73.309726 41.394297, -73.309558 41.394315, -73.309461 41.394397, -73.309382 41.394534, -73.309204 41.394817, -73.309067 41.394947, -73.308907 41.395083, -73.308891 41.395101, -73.308785 41.395228, -73.308674 41.395474, -73.308589 41.396053, -73.308515 41.396626, -73.308597 41.396841, -73.308702 41.396984, -73.308884 41.397203, -73.308961 41.397446, -73.308966 41.397565, -73.308958 41.3977, -73.308882 41.398244, -73.308768 41.398917, -73.308634 41.399989, -73.30856 41.400586, -73.308532 41.400861, -73.308493 41.401077, -73.308499 41.401292, -73.308493 41.401384, -73.308546 41.401463, -73.308635 41.401529, -73.308724 41.40156, -73.308711 41.401829, -73.308673 41.402636, -73.308661 41.402906, -73.308111 41.4029, -73.307798 41.402995, -73.307659 41.402993, -73.307564 41.40296, -73.307516 41.402915, -73.3072 41.402621, -73.306471 41.402106, -73.305684 41.401856, -73.305406 41.401779, -73.30467 41.401467, -73.30437 41.401408, -73.30432 41.401399, -73.30392 41.401208, -73.303714 41.401056, -73.303663 41.400912, -73.303577 41.400847, -73.303413 41.4008, -73.30311 41.400714, -73.303008 41.400731, -73.302862 41.400805, -73.302738 41.40092, -73.3026 41.400936, -73.302315 41.400914, -73.302205 41.400864, -73.302089 41.400812, -73.302039 41.400748, -73.302031 41.400644, -73.302089 41.400524, -73.302024 41.400419, -73.301937 41.400332, -73.301936 41.400107, -73.301782 41.399902, -73.301688 41.399721, -73.301462 41.3996, -73.301287 41.399417, -73.301266 41.399219, -73.301332 41.399103, -73.301543 41.398966, -73.301572 41.398886, -73.301565 41.398773, -73.301587 41.398661, -73.301244 41.398507, -73.301157 41.398416, -73.301113 41.398221, -73.301055 41.398072, -73.300885 41.397898, -73.300828 41.397839, -73.300632 41.397696, -73.300151 41.397482, -73.299822 41.397599, -73.299775 41.397594, -73.299727 41.397591, -73.299913 41.397413, -73.300064 41.397298, -73.30028 41.397072, -73.300355 41.396962, -73.300403 41.396855, -73.300418 41.396824, -73.300441 41.396712, -73.300479 41.396616, -73.30028 41.396284, -73.300135 41.395982, -73.299906 41.395531, -73.29912 41.393986, -73.299013 41.393723, -73.298945 41.393371, -73.298902 41.392762, -73.298909 41.392682, -73.29896 41.392591, -73.299018 41.392468, -73.299085 41.392345, -73.299156 41.392184, -73.299175 41.392129, -73.299209 41.39204, -73.29922 41.391932, -73.299251 41.391818, -73.299201 41.391652, -73.299011 41.391315, -73.298865 41.391003, -73.298785 41.390648, -73.298782 41.390633, -73.298827 41.390259, -73.29885 41.389822, -73.298829 41.389562, -73.29882 41.389434, -73.298771 41.389205, -73.29876 41.38914, -73.298756 41.389116, -73.298752 41.388946, -73.298751 41.388881, -73.298769 41.388844, -73.298777 41.388819, -73.298802 41.388745, -73.298828 41.388624, -73.298843 41.388559, -73.298961 41.388323, -73.298982 41.388273, -73.299064 41.388087, -73.299212 41.387873, -73.299357 41.387676, -73.299514 41.387547, -73.299551 41.387529, -73.299839 41.387394, -73.299856 41.387153, -73.299861 41.38711, -73.299892 41.386886, -73.299806 41.386373, -73.299812 41.386268, -73.299825 41.386078, -73.299845 41.385987, -73.299857 41.385933, -73.299874 41.385765, -73.299955 41.385401, -73.300041 41.385184, -73.300118 41.384992, -73.300187 41.384802, -73.300223 41.384597, -73.300276 41.384128, -73.300292 41.383905, -73.3003 41.383673, -73.300252 41.382894, -73.300213 41.382733, -73.300167 41.382538, -73.300164 41.382523, -73.300109 41.382224, -73.300058 41.381918, -73.300043 41.381829, -73.30004 41.381738, -73.299949 41.381174, -73.299944 41.381141, -73.299912 41.380903, -73.29991 41.380676, -73.299927 41.380499, -73.300094 41.380367, -73.300528 41.380118, -73.300738 41.380022, -73.301191 41.379815, -73.301431 41.379692, -73.301806 41.379502, -73.302134 41.379341, -73.302525 41.379193, -73.302943 41.379036, -73.303297 41.378942, -73.303571 41.378892, -73.303733 41.378859, -73.303881 41.37883, -73.304015 41.378809, -73.304147 41.378803, -73.304166 41.378623, -73.304193 41.378497, -73.304203 41.378451, -73.304211 41.37826, -73.304245 41.378048, -73.30431 41.37785, -73.304362 41.377749, -73.3044 41.377611, -73.304396 41.3776, -73.30437 41.377522, -73.304356 41.37736, -73.304343 41.377297, -73.304286 41.377019, -73.304247 41.376871, -73.304202 41.3767, -73.304057 41.376313, -73.303861 41.37579, -73.303818 41.375655, -73.303819 41.375638, -73.303832 41.375499, -73.303845 41.375204, -73.303904 41.375174, -73.304029 41.375084, -73.304117 41.375002, -73.304192 41.374869, -73.30427 41.374751, -73.304368 41.374575, -73.304414 41.374433, -73.304592 41.374208, -73.304681 41.37406, -73.304685 41.373945, -73.304667 41.373573, -73.304623 41.373458, -73.304308 41.372994, -73.304179 41.372758, -73.303998 41.372549, -73.303793 41.372109, -73.303815 41.372024, -73.303989 41.371929, -73.304028 41.371884, -73.304104 41.371838, -73.304151 41.371822, -73.303854 41.371532, -73.303753 41.371521, -73.303621 41.37151, -73.303519 41.371372, -73.303483 41.371372, -73.303432 41.371367, -73.303264 41.371274, -73.303097 41.371241, -73.303024 41.371186, -73.302922 41.371048, -73.30274 41.370983, -73.302485 41.370939, -73.302186 41.370812, -73.30196 41.370895, -73.301778 41.370982, -73.301668 41.370993, -73.301537 41.370911, -73.301421 41.37073, -73.301042 41.370543, -73.300947 41.370395, -73.300874 41.370351, -73.300772 41.370318, -73.300648 41.370334, -73.300408 41.370444, -73.300211 41.370477, -73.300065 41.370455, -73.300007 41.370417, -73.299917 41.370347, -73.299893 41.370359, -73.29983 41.370308, -73.299766 41.370257, -73.299781 41.370093, -73.29985 41.369739, -73.29994 41.369279, -73.300078 41.36882, -73.300236 41.368645, -73.300372 41.368567, -73.300401 41.368133, -73.300241 41.367776, -73.300168 41.367513, -73.300219 41.367145, -73.300154 41.366865, -73.300168 41.366585, -73.300241 41.366283, -73.300234 41.366107, -73.300285 41.365789, -73.300387 41.365547, -73.300525 41.365454, -73.300556 41.365449, -73.300587 41.365447, -73.300381 41.36532, -73.299905 41.364971, -73.299426 41.364613, -73.29923 41.364476, -73.298901 41.36427, -73.298839 41.364213, -73.298741 41.364176, -73.298741 41.364189, -73.298719 41.364256, -73.298485 41.365165, -73.298346 41.366353, -73.298301 41.366466, -73.29822 41.366595, -73.297971 41.366812, -73.297705 41.366986, -73.297453 41.367208, -73.297386 41.36735, -73.297396 41.367491, -73.297494 41.368345, -73.297634 41.36931, -73.297677 41.369449, -73.297756 41.369514, -73.297884 41.369593, -73.298134 41.369735, -73.298994 41.370164, -73.299112 41.370216, -73.299185 41.370268, -73.299408 41.370364, -73.299905 41.370582, -73.299723 41.370938, -73.29973 41.371114, -73.299774 41.371185, -73.299774 41.371334, -73.299773 41.371427, -73.29973 41.37152, -73.299496 41.371767, -73.299489 41.371817, -73.299445 41.371915, -73.299154 41.372146, -73.299074 41.372283, -73.298971 41.372409, -73.298527 41.372525, -73.298359 41.37259, -73.298301 41.372579, -73.298206 41.372667, -73.298024 41.372739, -73.29798 41.372805, -73.29798 41.372958, -73.298009 41.373101, -73.297915 41.373238, -73.297681 41.373353, -73.297616 41.373414, -73.297543 41.373568, -73.297535 41.373622, -73.29739 41.373798, -73.297084 41.374083, -73.296821 41.374451, -73.296384 41.374621, -73.296303 41.374742, -73.295917 41.374973, -73.295895 41.375, -73.29583 41.375094, -73.296188 41.375419, -73.295994 41.375556, -73.295855 41.375671, -73.295709 41.375696, -73.295279 41.375676, -73.295076 41.375745, -73.294907 41.375868, -73.29479 41.375904, -73.294608 41.375959, -73.294446 41.375982, -73.294359 41.375995, -73.293958 41.376147, -73.29365 41.376093, -73.293567 41.376083, -73.293463 41.376107, -73.293437 41.376139, -73.293389 41.376203, -73.293347 41.376307, -73.293287 41.376457, -73.293254 41.376482, -73.293158 41.376559, -73.293126 41.376585, -73.29291 41.376756, -73.292509 41.377076, -73.292349 41.377173, -73.292233 41.377217, -73.292056 41.377285, -73.291986 41.377334, -73.291857 41.377311, -73.29177 41.377231, -73.291633 41.377166, -73.291532 41.377115, -73.290968 41.376834, -73.290384 41.376474, -73.289595 41.377287, -73.28958 41.377304, -73.289397 41.377527, -73.289377 41.377731, -73.289368 41.377756, -73.289317 41.377894, -73.289289 41.377971, -73.28921 41.378323, -73.289178 41.378467, -73.289155 41.378513, -73.289052 41.37863, -73.289018 41.378669, -73.288893 41.378737, -73.288817 41.378755, -73.288427 41.378852, -73.288176 41.378909, -73.287962 41.378959, -73.287699 41.37902, -73.287016 41.379203, -73.286878 41.379241, -73.286717 41.379262, -73.286043 41.379293, -73.28534 41.379397, -73.28523 41.379451, -73.284618 41.379809, -73.28442 41.379882, -73.28424 41.379959, -73.28399 41.380067, -73.283699 41.38031, -73.283672 41.380333, -73.283586 41.380447, -73.283493 41.38056, -73.283307 41.38062, -73.283152 41.380624, -73.283 41.380636, -73.283231 41.380789, -73.283331 41.380885, -73.283432 41.381004, -73.283513 41.381085, -73.283794 41.381446, -73.28409 41.381813, -73.284383 41.382267, -73.284524 41.382557, -73.284614 41.382788, -73.284665 41.382976, -73.284755 41.383204, -73.284794 41.383356, -73.284839 41.383619, -73.284883 41.38401, -73.284905 41.384425, -73.284932 41.384779, -73.284923 41.385148, -73.284939 41.385313, -73.284949 41.38548, -73.285021 41.385811, -73.285231 41.386347, -73.285525 41.38698, -73.285711 41.387343, -73.285851 41.387647, -73.286 41.38786, -73.286089 41.388021, -73.286259 41.388124, -73.286412 41.388263, -73.286704 41.388459, -73.288 41.389272, -73.288047 41.389303, -73.287529 41.389721, -73.285977 41.390976, -73.28546 41.391395, -73.285475 41.391479, -73.28552 41.391731, -73.285535 41.391816, -73.285692 41.392045, -73.28571 41.392071, -73.285812 41.392144, -73.285877 41.392204, -73.28595 41.392335, -73.285987 41.392404, -73.286088 41.392491, -73.286299 41.392582, -73.286555 41.392694, -73.286613 41.392719, -73.286671 41.392774, -73.286839 41.393128, -73.28695 41.393284, -73.287087 41.393478, -73.287225 41.393624, -73.287266 41.39364, -73.287284 41.393648, -73.287452 41.393695, -73.287597 41.393689, -73.287619 41.393657, -73.287663 41.393654, -73.287794 41.393737, -73.288137 41.393823, -73.288552 41.393919, -73.288595 41.393924, -73.288858 41.394005, -73.289026 41.394102, -73.289091 41.394148, -73.289419 41.394409, -73.28947 41.394482, -73.28971 41.394931, -73.289776 41.395252, -73.289809 41.395584, -73.28982 41.39569, -73.289936 41.395926, -73.29006 41.39604, -73.290381 41.396392, -73.290469 41.396537, -73.290487 41.396556, -73.290914 41.397003, -73.291518 41.397489, -73.291737 41.397664, -73.29197 41.397843, -73.292022 41.398055, -73.292079 41.398142, -73.292145 41.398161, -73.292276 41.398239, -73.292269 41.398338, -73.292291 41.398528, -73.292575 41.39882, -73.29278 41.398954, -73.292839 41.398993, -73.292989 41.399091, -73.292988 41.399113, -73.292985 41.399212, -73.292925 41.399325, -73.292787 41.399395, -73.292728 41.399457, -73.292692 41.399677, -73.292742 41.399912, -73.293103 41.400251, -73.29318 41.400448, -73.293119 41.400607, -73.293153 41.400701, -73.293229 41.400769, -73.293347 41.400825, -73.293632 41.400886, -73.29392 41.400947, -73.29428 41.40107, -73.294331 41.401161, -73.294368 41.401346, -73.294623 41.402075, -73.294642 41.402259, -73.294788 41.402243, -73.294883 41.402214, -73.294961 41.402463, -73.295048 41.40265, -73.295091 41.402725, -73.29518 41.402854, -73.295481 41.403194, -73.295746 41.403517, -73.296236 41.404188, -73.296626 41.40466, -73.296731 41.404801, -73.296826 41.404898, -73.296868 41.404961, -73.296966 41.405071, -73.297203 41.405327, -73.297386 41.405545, -73.297884 41.40609, -73.298351 41.406593, -73.298516 41.406833, -73.298669 41.40702, -73.298912 41.407412, -73.298971 41.407508, -73.298993 41.407545, -73.299095 41.407672, -73.299164 41.407788, -73.299222 41.407886, -73.298991 41.408, -73.298785 41.408089, -73.298519 41.408155, -73.298283 41.408174, -73.298129 41.408187, -73.297562 41.408115, -73.297296 41.408131, -73.296771 41.408217, -73.296387 41.408267, -73.295708 41.408373, -73.295333 41.408431, -73.295148 41.40846, -73.294354 41.408589, -73.293914 41.408671, -73.293111 41.408821, -73.292595 41.408913, -73.292155 41.408993, -73.292004 41.409019, -73.291904 41.409038, -73.291553 41.409105, -73.291504 41.409115, -73.291404 41.409136, -73.291146 41.409936, -73.291033 41.410747, -73.291014 41.41089, -73.291091 41.412054, -73.291104 41.412094, -73.291335 41.412807, -73.291516 41.413276, -73.29199 41.414051, -73.292513 41.414735, -73.292924 41.41518, -73.293112 41.415383, -73.294268 41.416144, -73.294653 41.416324, -73.295811 41.416865, -73.296197 41.417046, -73.296183 41.417052, -73.296128 41.417084, -73.295976 41.417158, -73.295778 41.417233, -73.295734 41.417251, -73.29564 41.417271, -73.295369 41.417358, -73.295201 41.417392, -73.295031 41.417415, -73.294824 41.417431, -73.294556 41.417433, -73.29415 41.417413, -73.294082 41.417258, -73.293999 41.417101, -73.293787 41.416837, -73.293567 41.416601, -73.293357 41.41636, -73.293184 41.416011, -73.293114 41.415795, -73.292984 41.41564, -73.292834 41.415483, -73.292123 41.414836, -73.291767 41.414403, -73.291735 41.414262, -73.291663 41.414161, -73.291488 41.41386, -73.291339 41.413672, -73.291243 41.41361, -73.291059 41.413522, -73.290824 41.413471, -73.290584 41.413512, -73.290365 41.413587, -73.290093 41.413676, -73.28954 41.413857, -73.289101 41.413903, -73.288568 41.414006, -73.288279 41.414128, -73.28783 41.414362, -73.287488 41.414525, -73.287081 41.414728, -73.286915 41.414817, -73.286809 41.414641, -73.286321 41.414769, -73.28616 41.414757, -73.286124 41.414671, -73.286117 41.41409, -73.285964 41.413903, -73.285825 41.413906, -73.285366 41.414025, -73.285132 41.414102, -73.284629 41.414271, -73.284228 41.414355, -73.283834 41.414497, -73.283593 41.414638, -73.283397 41.414768, -73.283375 41.414783, -73.283245 41.414813, -73.283016 41.414868, -73.282774 41.414877, -73.282696 41.414881, -73.282689 41.414962, -73.282616 41.414866, -73.282565 41.414761, -73.282556 41.414613, -73.282554 41.414564, -73.282399 41.414296, -73.282183 41.414172, -73.282089 41.414105, -73.28204 41.414085, -73.282055 41.414034, -73.282033 41.414006, -73.281941 41.413855, -73.281894 41.413751, -73.28187 41.413698, -73.281872 41.413613, -73.28193 41.413462, -73.281966 41.413391, -73.282042 41.413242, -73.282072 41.413138, -73.281953 41.412891, -73.281665 41.412477, -73.281672 41.412401, -73.281783 41.412242, -73.281817 41.412162, -73.281789 41.412001, -73.281685 41.411907, -73.281128 41.411798, -73.280969 41.411688, -73.280939 41.411667, -73.28092 41.41147, -73.280836 41.411432, -73.280637 41.411436, -73.280617 41.411437, -73.280537 41.411412, -73.280454 41.411338, -73.280363 41.41114, -73.280255 41.410805, -73.280193 41.410728, -73.279981 41.410652, -73.279911 41.410471, -73.279854 41.410409, -73.279718 41.410319, -73.279661 41.410242, -73.279606 41.409916, -73.279506 41.409854, -73.2794 41.40984, -73.279276 41.409867, -73.279237 41.409922, -73.279244 41.410079, -73.279158 41.41011, -73.279019 41.410088, -73.278901 41.410026, -73.278752 41.40981, -73.278627 41.40968, -73.278608 41.409599, -73.278664 41.409488, -73.278751 41.409429, -73.278811 41.409414, -73.278828 41.409354, -73.278787 41.409309, -73.278751 41.409224, -73.278802 41.409132, -73.278793 41.409048, -73.278662 41.408981, -73.278509 41.408923, -73.278453 41.408609, -73.278367 41.408443, -73.278203 41.408356, -73.277927 41.408308, -73.277162 41.407999, -73.276683 41.407642, -73.276151 41.40755, -73.275704 41.407326, -73.275566 41.407288, -73.275394 41.407334, -73.27521 41.407419, -73.275136 41.407414, -73.275047 41.40734, -73.274965 41.407002, -73.274919 41.406971, -73.274871 41.40694, -73.274353 41.406848, -73.27423 41.406846, -73.274101 41.406884, -73.274015 41.406935, -73.273876 41.406929, -73.273664 41.40687, -73.273416 41.406766, -73.27319 41.406826, -73.273081 41.406925, -73.272947 41.406931, -73.272804 41.406921, -73.27263 41.406818, -73.272499 41.406728, -73.272272 41.40664, -73.272133 41.406545, -73.272074 41.406571, -73.272035 41.406588, -73.271959 41.406621, -73.271776 41.406691, -73.271137 41.406938, -73.270687 41.407078, -73.27032 41.407193, -73.270143 41.407236, -73.270037 41.407257, -73.269958 41.407273, -73.269772 41.407323, -73.269653 41.407342, -73.269471 41.407418, -73.269322 41.407508, -73.269055 41.407282, -73.268899 41.407189, -73.26872 41.407101, -73.268274 41.406958, -73.268125 41.406923, -73.267958 41.40691, -73.267786 41.406891, -73.267423 41.406927, -73.267242 41.406949, -73.267092 41.406962, -73.266936 41.407027, -73.266784 41.407098, -73.266529 41.407227, -73.266204 41.407518, -73.266088 41.407732, -73.265989 41.407989, -73.266001 41.408182, -73.266058 41.408362, -73.266182 41.408676, -73.266239 41.408878, -73.2663 41.409013, -73.266546 41.409626, -73.266659 41.409949, -73.266872 41.410449, -73.266945 41.410695, -73.266983 41.411013, -73.266999 41.411236, -73.267027 41.411392, -73.267009 41.411673, -73.267006 41.411967, -73.266916 41.412398, -73.266794 41.412817, -73.266709 41.413014, -73.266383 41.413585, -73.266132 41.413895, -73.265877 41.414177, -73.265585 41.41445, -73.265389 41.414664, -73.26528 41.414799, -73.26514 41.414946, -73.265233 41.415019, -73.265325 41.415095, -73.2652 41.41518, -73.265154 41.41522, -73.26495 41.415384, -73.264398 41.415775, -73.264084 41.416143, -73.264004 41.416226, -73.263462 41.4168, -73.261838 41.418572, -73.260433 41.420059, -73.260155 41.420374, -73.259982 41.420554, -73.259542 41.421066, -73.259248 41.421357, -73.258652 41.421948, -73.257586 41.423062, -73.257551 41.4231, -73.256816 41.423806, -73.25485 41.425717, -73.254239 41.426298, -73.253639 41.426899, -73.252447 41.428025, -73.252127 41.428329, -73.251948 41.428512, -73.251781 41.428692, -73.251371 41.429047, -73.251008 41.429398, -73.25093 41.429484, -73.250867 41.429532, -73.250733 41.429675, -73.250704 41.429727, -73.250681 41.42977, -73.250592 41.429866, -73.250552 41.429911, -73.250428 41.430022, -73.250296 41.430143, -73.250076 41.430375, -73.249963 41.430497, -73.249874 41.43059, -73.249818 41.430651, -73.249697 41.430769, -73.249599 41.430861, -73.249506 41.43095, -73.249361 41.431088, -73.249286 41.431164, -73.249045 41.431412, -73.248632 41.43181, -73.248412 41.432023, -73.24833 41.432101, -73.248087 41.432336, -73.248006 41.432415, -73.247905 41.432551, -73.247884 41.43258, -73.247611 41.4329, -73.24758 41.432942, -73.247482 41.433081, -73.247374 41.433232, -73.247263 41.43344, -73.247057 41.433831, -73.24683 41.43439, -73.24676 41.4346, -73.246689 41.434815, -73.246637 41.435004, -73.246587 41.435182, -73.246546 41.435333, -73.246475 41.435662, -73.246467 41.435723, -73.246446 41.435907, -73.246424 41.436234, -73.246431 41.436388, -73.246469 41.436711, -73.246518 41.436974, -73.246581 41.43724, -73.246669 41.437517, -73.246764 41.437766, -73.246904 41.438043, -73.247027 41.43823, -73.247122 41.438191, -73.247285 41.438126, -73.2474 41.438058, -73.247489 41.438006, -73.247591 41.437946, -73.24776 41.437789, -73.24824 41.437348, -73.248503 41.437149, -73.248569 41.437139, -73.248619 41.437133, -73.248685 41.437161, -73.248758 41.437171, -73.248824 41.437122, -73.248848 41.437054, -73.248867 41.437001, -73.248962 41.436949, -73.249137 41.436911, -73.24956 41.436922, -73.249758 41.436916, -73.249928 41.43694, -73.25 41.436951, -73.250304 41.436992, -73.250393 41.436994, -73.250865 41.437005, -73.251332 41.436904, -73.251711 41.436756, -73.252448 41.436349, -73.252872 41.436044, -73.253069 41.43594, -73.253156 41.435896, -73.253471 41.435621, -73.253725 41.435242, -73.253784 41.435087, -73.253865 41.434875, -73.254542 41.434391, -73.254785 41.434218, -73.255383 41.433903, -73.255704 41.433773, -73.256121 41.433675, -73.256449 41.433608, -73.256653 41.433503, -73.257083 41.433366, -73.257482 41.433256, -73.258315 41.433029, -73.258508 41.432993, -73.258587 41.432979, -73.258958 41.432935, -73.25952 41.433011, -73.260432 41.433254, -73.260694 41.433299, -73.261387 41.433471, -73.261817 41.433545, -73.262146 41.433632, -73.262823 41.433885, -73.263444 41.434187, -73.264013 41.434511, -73.264319 41.434664, -73.264596 41.434835, -73.264771 41.434968, -73.264916 41.435101, -73.264996 41.435237, -73.265208 41.435443, -73.265331 41.435792, -73.265412 41.43587, -73.265747 41.436059, -73.266097 41.436506, -73.266192 41.436692, -73.266155 41.436718, -73.265834 41.436691, -73.265791 41.436731, -73.265807 41.436826, -73.265868 41.437059, -73.266015 41.437216, -73.266113 41.437252, -73.266363 41.437288, -73.266554 41.437332, -73.266787 41.437387, -73.266978 41.437395, -73.267263 41.437364, -73.267405 41.437359, -73.267544 41.43735, -73.267731 41.437413, -73.267896 41.437462, -73.268003 41.437555, -73.268172 41.437662, -73.268199 41.43788, -73.268257 41.438228, -73.268613 41.438442, -73.268814 41.438487, -73.269001 41.43838, -73.269233 41.438375, -73.269393 41.438391, -73.270227 41.437958, -73.270291 41.438014, -73.270304 41.438026, -73.27042 41.438129, -73.270538 41.438281, -73.270569 41.438322, -73.270655 41.438432, -73.270722 41.438526, -73.270821 41.438663, -73.270662 41.438749, -73.270561 41.438805, -73.270479 41.438823, -73.270396 41.438741, -73.270348 41.438966, -73.270282 41.439028, -73.270122 41.439003, -73.269502 41.438791, -73.269239 41.438746, -73.269079 41.438766, -73.269014 41.438829, -73.269247 41.439053, -73.269137 41.439186, -73.269042 41.439239, -73.268802 41.439195, -73.268707 41.439265, -73.268714 41.439558, -73.268794 41.439636, -73.26894 41.439661, -73.269101 41.439663, -73.26913 41.439699, -73.2691 41.439731, -73.26867 41.43972, -73.268415 41.439626, -73.268371 41.439535, -73.268342 41.439296, -73.268218 41.439163, -73.267955 41.439047, -73.267802 41.438999, -73.267546 41.438982, -73.267416 41.438908, -73.26728 41.438805, -73.267131 41.438692, -73.267087 41.438687, -73.267 41.438749, -73.267 41.438906, -73.26692 41.439139, -73.267175 41.440017, -73.267378 41.441186, -73.267603 41.442225, -73.267836 41.442918, -73.267916 41.44328, -73.268033 41.443533, -73.268171 41.443675, -73.268645 41.444011, -73.268827 41.444086, -73.269739 41.444527, -73.270184 41.444863, -73.27065 41.445311, -73.270869 41.445441, -73.270979 41.445519, -73.271 41.445578, -73.270913 41.445784, -73.270935 41.445878, -73.271168 41.446071, -73.271263 41.446109, -73.271365 41.44607, -73.271431 41.44608, -73.271584 41.446343, -73.271759 41.446796, -73.271977 41.44725, -73.272217 41.447789, -73.272342 41.447949, -73.272407 41.447981, -73.272524 41.447987, -73.272538 41.448015, -73.272516 41.448109, -73.2724 41.448179, -73.272268 41.44815, -73.272137 41.448054, -73.271729 41.447381, -73.271663 41.447376, -73.27162 41.447492, -73.271568 41.447739, -73.271517 41.447788, -73.271452 41.447796, -73.271342 41.447524, -73.271335 41.447163, -73.27132 41.447037, -73.271102 41.446669, -73.271073 41.446547, -73.271066 41.446268, -73.271029 41.446258, -73.270861 41.446359, -73.270628 41.446576, -73.270562 41.446562, -73.270518 41.446399, -73.270444 41.445811, -73.270329 41.445572, -73.270176 41.445435, -73.270001 41.445337, -73.269899 41.445345, -73.269833 41.445398, -73.269818 41.445569, -73.270379 41.446582, -73.270707 41.447217, -73.271152 41.447922, -73.271655 41.448632, -73.272298 41.449487, -73.272829 41.450194, -73.273404 41.450865, -73.273716 41.451117, -73.274112 41.451438, -73.274412 41.451601, -73.274783 41.451741, -73.276184 41.452028, -73.276323 41.452066, -73.276717 41.452149, -73.278111 41.452232, -73.279745 41.452221, -73.279997 41.452201, -73.280424 41.452168, -73.280737 41.452132, -73.281299 41.452023, -73.281547 41.451996, -73.282131 41.45184, -73.282328 41.451787, -73.282942 41.451668, -73.283269 41.451535, -73.283634 41.45141, -73.284656 41.451168, -73.285116 41.450977, -73.285481 41.450879, -73.285941 41.450625, -73.287429 41.449967, -73.287904 41.44978, -73.288662 41.449449, -73.289998 41.448744, -73.29018 41.448607, -73.290677 41.448092, -73.291115 41.447716, -73.291261 41.447547, -73.291407 41.447423, -73.291771 41.447244, -73.292034 41.447157, -73.292304 41.447107, -73.292822 41.44703, -73.293691 41.447033, -73.294332 41.447092, -73.294704 41.447175, -73.294807 41.447235, -73.29491 41.447409, -73.295974 41.4475, -73.296036 41.44722, -73.296098 41.446939, -73.300423 41.44695, -73.300339 41.446345, -73.30085 41.446057, -73.301245 41.446368, -73.302052 41.446289, -73.302063 41.445886, -73.30369 41.445225, -73.303716 41.444322, -73.303606 41.444293, -73.303701 41.444106, -73.303715 41.444069, -73.304491 41.444259, -73.304675 41.444333, -73.305116 41.444762, -73.305468 41.445104, -73.305532 41.445137, -73.305718 41.445285, -73.306047 41.445614, -73.306558 41.446069, -73.306816 41.44641, -73.306852 41.446457, -73.307039 41.446713, -73.307315 41.446974, -73.307431 41.447066, -73.307516 41.447108, -73.307804 41.447059, -73.308012 41.447025, -73.308671 41.446938, -73.308748 41.446928, -73.308949 41.446851, -73.308946 41.446942, -73.308945 41.447004, -73.308907 41.447214, -73.308891 41.447305, -73.308961 41.447421, -73.309089 41.447499, -73.309898 41.447677, -73.310224 41.447853, -73.310658 41.447972, -73.311109 41.448155, -73.311435 41.448451, -73.31153 41.448572, -73.311775 41.448738, -73.311848 41.448777, -73.312044 41.448716, -73.312134 41.44868, -73.312374 41.448584, -73.312571 41.44859, -73.312998 41.448771, -73.313192 41.448901, -73.313324 41.448929, -73.313582 41.448931, -73.313964 41.449045, -73.314056 41.44914, -73.314236 41.449225, -73.314547 41.449237, -73.314961 41.449066, -73.315322 41.448878, -73.315775 41.448579, -73.31581 41.448566, -73.316175 41.448432, -73.316885 41.448273, -73.317028 41.448216, -73.317579 41.447854, -73.317582 41.447767, -73.317588 41.447626, -73.317531 41.447519, -73.317491 41.447443, -73.31763 41.447543, -73.31797 41.447787, -73.318036 41.447859, -73.318153 41.447986, -73.318375 41.448169, -73.318483 41.4482, -73.318627 41.448241, -73.318772 41.448268, -73.318857 41.448264, -73.319335 41.44819, -73.319545 41.448196, -73.319669 41.448221, -73.319801 41.448248, -73.320056 41.448325, -73.32118 41.449412, -73.321344 41.449571, -73.322277 41.450473, -73.322386 41.450581, -73.322879 41.451069, -73.323155 41.451355, -73.323821 41.452048, -73.324043 41.452352, -73.324213 41.452725, -73.32437 41.453221, -73.324386 41.453269, -73.324426 41.453463, -73.324433 41.453743, -73.324434 41.453784, -73.324436 41.453822, -73.324417 41.454134, -73.324495 41.454554, -73.324528 41.454821, -73.324544 41.454946, -73.324703 41.455156, -73.32494 41.455424, -73.325196 41.45575, -73.325376 41.455978, -73.325429 41.456062, -73.325474 41.456133, -73.325527 41.456277, -73.325473 41.456659, -73.325354 41.456929, -73.325235 41.457215, -73.325232 41.457383, -73.325337 41.457811, -73.325417 41.457966, -73.325519 41.458164, -73.325644 41.458296, -73.325678 41.458332, -73.325887 41.458489, -73.326179 41.4587, -73.326215 41.458742, -73.326266 41.458802, -73.3263 41.458842, -73.326377 41.458933, -73.326401 41.45897, -73.32668 41.459401, -73.326721 41.459529, -73.32676 41.459652, -73.326778 41.459738, -73.326754 41.459831, -73.326661 41.460009, -73.326551 41.460288, -73.326529 41.460404, -73.326601 41.460546, -73.32672 41.460639, -73.32696 41.46073, -73.327214 41.460839, -73.326952 41.460849, -73.326139 41.460869, -73.325858 41.460903, -73.325707 41.460921, -73.325508 41.460985, -73.325265 41.46115, -73.325127 41.461305, -73.325078 41.461444, -73.325018 41.461815, -73.324983 41.462428, -73.325013 41.462644, -73.325183 41.463378, -73.32554 41.464418, -73.325887 41.465576, -73.326103 41.466092, -73.32627 41.466325, -73.326538 41.466622, -73.32781 41.467627, -73.328107 41.467898, -73.328303 41.46792, -73.3285 41.467844, -73.328731 41.467763, -73.329515 41.467445, -73.329675 41.467541, -73.329692 41.467586, -73.329746 41.467662, -73.329816 41.467778, -73.329825 41.467798, -73.330711 41.468893, -73.333063 41.471218, -73.332872 41.471422, -73.333132 41.471503, -73.333227 41.471553, -73.333454 41.471674, -73.333571 41.471581, -73.333644 41.471555, -73.33368 41.471588, -73.334053 41.471863, -73.334381 41.472003, -73.334746 41.472184, -73.33503 41.472499, -73.335213 41.4726, -73.335323 41.472697, -73.335718 41.472928, -73.335743 41.472958, -73.335798 41.473023, -73.336031 41.473189, -73.336103 41.473231, -73.336309 41.47335, -73.33663 41.473639, -73.336732 41.473703, -73.33736 41.474023, -73.337529 41.474156, -73.337784 41.474331, -73.338053 41.47442, -73.338404 41.474466, -73.338798 41.47449, -73.338805 41.474544, -73.338696 41.47465, -73.338696 41.474691, -73.338748 41.474768, -73.338944 41.474807, -73.339376 41.474921, -73.339613 41.475027, -73.339011 41.474157, -73.338564 41.473897, -73.338342 41.473781, -73.338072 41.47364, -73.337245 41.473182, -73.33676 41.472889, -73.336532 41.472674, -73.336406 41.472561, -73.336109 41.472296, -73.335864 41.472022, -73.336175 41.472013, -73.336513 41.47204, -73.337084 41.472088, -73.33727 41.472082, -73.337342 41.472058, -73.337352 41.471908, -73.337344 41.471821, -73.337098 41.471528, -73.336988 41.471422, -73.336941 41.471357, -73.336935 41.471313, -73.336953 41.471225, -73.337022 41.471189, -73.337073 41.471181, -73.337554 41.471116, -73.33772 41.471108, -73.338029 41.471192, -73.338168 41.471241, -73.338704 41.471432, -73.338908 41.47153, -73.339351 41.471862, -73.339393 41.471896, -73.339759 41.472188, -73.339984 41.472014, -73.340069 41.471912, -73.340129 41.471845, -73.340165 41.471806, -73.3403 41.471582, -73.340441 41.471325, -73.340441 41.471165, -73.340413 41.47101, -73.340392 41.470853, -73.340696 41.470541, -73.341051 41.470179, -73.340789 41.470047, -73.34035 41.469758, -73.340292 41.4697, -73.340044 41.469455, -73.339682 41.469242, -73.339083 41.468859, -73.338923 41.468684, -73.338634 41.468464, -73.338386 41.468297, -73.338427 41.468274, -73.338464 41.468137, -73.338275 41.467888, -73.337995 41.467678, -73.337896 41.467566, -73.33785 41.467431, -73.337745 41.467212, -73.337621 41.467087, -73.337576 41.467017, -73.337403 41.466617, -73.337387 41.466589, -73.337325 41.466483, -73.337308 41.466454, -73.337175 41.466224, -73.336306 41.467207, -73.336006 41.466947, -73.335694 41.46662, -73.335678 41.466603, -73.335092 41.46584, -73.334885 41.465399, -73.334346 41.464975, -73.333901 41.464656, -73.333487 41.464329, -73.333324 41.464235, -73.333006 41.464053, -73.332934 41.46399, -73.332618 41.46371, -73.332441 41.463525, -73.332411 41.463488, -73.332321 41.463377, -73.332291 41.46334, -73.332025 41.463044, -73.331747 41.462721, -73.330888 41.462041, -73.330098 41.461464, -73.329729 41.461223, -73.329595 41.461121, -73.329701 41.461098, -73.329771 41.461129, -73.329861 41.461159, -73.32992 41.461176, -73.330123 41.461208, -73.329868 41.461012, -73.329677 41.460876, -73.329488 41.460741, -73.329393 41.460528, -73.329306 41.460306, -73.329196 41.460174, -73.329182 41.460038, -73.329021 41.459883, -73.329269 41.459553, -73.329896 41.459098, -73.330028 41.458934, -73.330105 41.458784, -73.33013 41.458737, -73.330225 41.458693, -73.330333 41.458679, -73.330477 41.458693, -73.330721 41.458746, -73.331213 41.458861, -73.33148 41.458896, -73.331731 41.45891, -73.331823 41.458915, -73.332078 41.4589, -73.332436 41.458802, -73.33275 41.458599, -73.333043 41.458267, -73.333212 41.458175, -73.333363 41.458136, -73.333501 41.458092, -73.333619 41.457999, -73.333764 41.457885, -73.333844 41.45776, -73.333858 41.457601, -73.334029 41.457355, -73.334059 41.457332, -73.334122 41.457288, -73.334391 41.45716, -73.334515 41.457067, -73.334588 41.456978, -73.334698 41.456732, -73.334756 41.456566, -73.33488 41.456324, -73.33488 41.45614, -73.334871 41.456122, -73.334811 41.455664, -73.3348 41.45558, -73.334768 41.455532, -73.334762 41.455495, -73.334423 41.454923, -73.334338 41.454829, -73.33431 41.454767, -73.33404 41.454398, -73.334047 41.454281, -73.334113 41.45408, -73.334167 41.453932, -73.334222 41.453784, -73.334216 41.453555, -73.334215 41.453509, -73.334135 41.453386, -73.333806 41.45317, -73.333702 41.453107, -73.333492 41.452981, -73.33342 41.452867, -73.333295 41.452473, -73.333098 41.452146, -73.333011 41.45203, -73.332886 41.451864, -73.332792 41.451579, -73.332791 41.451394, -73.332828 41.451287, -73.333017 41.451015, -73.333032 41.450763, -73.333266 41.450424, -73.333265 41.450208, -73.333448 41.450134, -73.333528 41.450054, -73.333835 41.449973, -73.334294 41.449975, -73.334477 41.449892, -73.334579 41.449785, -73.334586 41.449632, -73.334644 41.449624, -73.334933 41.449643, -73.335082 41.449653, -73.335192 41.449812, -73.335425 41.449928, -73.335601 41.449971, -73.335681 41.450036, -73.335856 41.45034, -73.335924 41.450385, -73.335987 41.450427, -73.336279 41.450688, -73.336644 41.451171, -73.336842 41.451282, -73.336994 41.451307, -73.337096 41.451312, -73.337673 41.451343, -73.338096 41.451453, -73.338352 41.451466, -73.339023 41.451318, -73.33938 41.451274, -73.339913 41.451115, -73.340205 41.451069, -73.340519 41.451083, -73.340949 41.451089, -73.341183 41.450989, -73.341234 41.450492, -73.341248 41.450364, -73.341328 41.449897, -73.341328 41.449775, -73.341379 41.449528, -73.34145 41.448953, -73.341414 41.448957, -73.341326 41.449004, -73.341105 41.449125, -73.34101 41.449246, -73.340927 41.449353, -73.340899 41.449454, -73.34084 41.449605, -73.340768 41.449701, -73.340677 41.449777, -73.34056 41.449761, -73.340504 41.449723, -73.340153 41.448739, -73.340015 41.448351, -73.33985 41.447986, -73.33919 41.446847, -73.338994 41.446642, -73.338616 41.446349, -73.338022 41.4458, -73.337826 41.44549, -73.337844 41.445315, -73.337939 41.44506, -73.338054 41.444726, -73.337993 41.44444, -73.337959 41.444411, -73.337694 41.444187, -73.337326 41.443943, -73.337231 41.443855, -73.337143 41.4438, -73.336902 41.443696, -73.336924 41.443526, -73.337 41.443358, -73.336739 41.443466, -73.336688 41.443478, -73.336536 41.443513, -73.336486 41.443526, -73.336091 41.443549, -73.335831 41.443565, -73.335685 41.443591, -73.335217 41.443917, -73.335021 41.443979, -73.334645 41.444099, -73.334592 41.444116, -73.334372 41.444196, -73.334191 41.444263, -73.333712 41.444445, -73.333567 41.444523, -73.333313 41.444663, -73.332418 41.443977, -73.332192 41.443804, -73.32958 41.442146, -73.328629 41.441543, -73.328038 41.441143, -73.327978 41.441063, -73.327931 41.440945, -73.327914 41.440902, -73.327803 41.440696, -73.327632 41.440425, -73.327567 41.440321, -73.327556 41.439544, -73.32751 41.439455, -73.327377 41.439362, -73.327074 41.43927, -73.326771 41.439171, -73.326468 41.438922, -73.326446 41.438902, -73.326037 41.438522, -73.325795 41.438374, -73.325702 41.438346, -73.325579 41.438285, -73.325551 41.438228, -73.325334 41.437776, -73.325268 41.437705, -73.325229 41.437664, -73.325225 41.437619, -73.325296 41.437553, -73.325358 41.437438, -73.325479 41.43717, -73.325501 41.437018, -73.32557 41.436462, -73.325606 41.436391, -73.326015 41.435853, -73.326044 41.435765, -73.326031 41.43571, -73.325975 41.435639, -73.325876 41.435557, -73.325837 41.435525, -73.325551 41.435291, -73.325441 41.435115, -73.327062 41.434919, -73.328518 41.434744, -73.328939 41.434694, -73.331726 41.434359, -73.331927 41.434334, -73.333549 41.434139, -73.33358 41.434024, -73.333559 41.433787, -73.333556 41.433752, -73.33355 41.433677, -73.333556 41.433003, -73.333568 41.432578, -73.33358 41.432187, -73.333793 41.432189, -73.333858 41.432189, -73.33392 41.432191, -73.334012 41.432194, -73.334123 41.43221, -73.334405 41.43228, -73.334533 41.432334, -73.33464 41.432372, -73.334877 41.432465, -73.334895 41.432473, -73.335205 41.432616, -73.335248 41.43254, -73.335274 41.432507, -73.335315 41.43247, -73.33539 41.432311, -73.335393 41.432298, -73.33542 41.432192, -73.335463 41.432087, -73.335531 41.431834, -73.335626 41.431708, -73.33576 41.431458, -73.335813 41.431274, -73.335835 41.431202, -73.335881 41.430908, -73.334751 41.43058, -73.333873 41.430325, -73.332446 41.429911, -73.331363 41.429597, -73.331066 41.429511, -73.330483 41.429342, -73.330235 41.429266, -73.330093 41.429218, -73.329855 41.429078, -73.329658 41.428977, -73.329535 41.4289, -73.329403 41.428828, -73.329037 41.428629, -73.328542 41.428348, -73.328142 41.428134, -73.327868 41.428032, -73.327706 41.427973, -73.327364 41.427876, -73.32704 41.427766, -73.326821 41.427708, -73.326502 41.427624, -73.325902 41.427502, -73.325759 41.427502, -73.325612 41.427485, -73.325301 41.427488, -73.325111 41.42749, -73.324634 41.427478, -73.324269 41.427499, -73.324011 41.427482, -73.323812 41.427457, -73.323608 41.427426, -73.323505 41.427401, -73.32341 41.427378, -73.323256 41.427327, -73.323132 41.427289, -73.322927 41.427237, -73.32282 41.427206, -73.322715 41.427176, -73.322508 41.427097, -73.322405 41.427058, -73.322091 41.426957, -73.321919 41.426883, -73.321318 41.426628, -73.321314 41.426422, -73.321107 41.426343, -73.321011 41.426298, -73.320927 41.426268, -73.320909 41.426264, -73.320882 41.426261, -73.320863 41.426261, -73.320843 41.426264, -73.320824 41.426268, -73.320723 41.426308, -73.320639 41.426328, -73.320507 41.426248, -73.320359 41.42616, -73.320208 41.426058, -73.320074 41.425968, -73.319976 41.42588, -73.319733 41.425574, -73.319658 41.42546, -73.319311 41.424934, -73.318899 41.424288, -73.318644 41.423966, -73.318519 41.423854, -73.318222 41.423589, -73.318011 41.423437, -73.317599 41.423198, -73.317538 41.423168, -73.31733 41.423064, -73.317027 41.422954, -73.316796 41.422895, -73.316716 41.422857, -73.316646 41.422802, -73.316403 41.422584, -73.316194 41.422325, -73.316157 41.422279, -73.315899 41.421869, -73.316553 41.421728, -73.316721 41.421693, -73.317332 41.421596, -73.317622 41.421568, -73.317859 41.421497, -73.318152 41.421333, -73.318264 41.421311, -73.318491 41.421316, -73.318727 41.421322, -73.319086 41.421339, -73.31916 41.421339, -73.319901 41.421237, -73.320132 41.421159, -73.321062 41.42066, -73.32133 41.420503, -73.321558 41.420369, -73.321727 41.420199, -73.322145 41.420133, -73.322546 41.420066, -73.322784 41.420043, -73.322944 41.420025, -73.323214 41.419959, -73.323372 41.419923, -73.323451 41.419891, -73.323546 41.41983, -73.323608 41.419789, -73.323655 41.419742, -73.323716 41.419647, -73.323832 41.419479, -73.324023 41.419258, -73.324312 41.419068, -73.324445 41.418937, -73.324471 41.418883, -73.325009 41.419011, -73.326623 41.419396, -73.327161 41.419525, -73.327253 41.419548, -73.327529 41.419618, -73.327621 41.419642, -73.328084 41.419727, -73.328146 41.419738, -73.329725 41.420027, -73.330251 41.420124, -73.330412 41.419807, -73.330715 41.419966, -73.331219 41.420688, -73.330781 41.42083, -73.3307 41.420859, -73.330606 41.420877, -73.33049 41.420889, -73.329985 41.420914, -73.33 41.421077, -73.330021 41.421196, -73.330079 41.421531, -73.330138 41.421853, -73.330194 41.422185, -73.330238 41.422418, -73.330267 41.42257, -73.330298 41.422681, -73.330347 41.422818, -73.330396 41.422924, -73.33044 41.423003, -73.330513 41.423115, -73.330604 41.423244, -73.330705 41.423357, -73.330827 41.423477, -73.330941 41.423574, -73.331053 41.423658, -73.331175 41.423736, -73.331324 41.42382, -73.33149 41.423902, -73.331627 41.423962, -73.331769 41.424015, -73.331942 41.424067, -73.332081 41.4241, -73.332206 41.424124, -73.332509 41.424176, -73.333186 41.424284, -73.333463 41.424326, -73.333656 41.424347, -73.333889 41.424345, -73.334056 41.424338, -73.334191 41.42431, -73.334338 41.424268, -73.334461 41.424226, -73.334575 41.424169, -73.334708 41.424093, -73.334807 41.424011, -73.334938 41.423876, -73.335014 41.423783, -73.335077 41.423675, -73.335119 41.423572, -73.335139 41.423475, -73.335147 41.423348, -73.335149 41.423257, -73.335132 41.423151, -73.335115 41.423067, -73.335081 41.422959, -73.335048 41.422879, -73.33498 41.422744, -73.334891 41.422584, -73.334714 41.422348, -73.334622 41.422235, -73.334508 41.422125, -73.334321 41.421976, -73.334188 41.421894, -73.333961 41.421777, -73.333754 41.421694, -73.333324 41.421535, -73.332952 41.421395, -73.33282 41.421338, -73.332695 41.421271, -73.332537 41.421164, -73.332323 41.420966, -73.332153 41.420773, -73.332035 41.420642, -73.331858 41.420429, -73.331964 41.420304, -73.33199 41.420264, -73.332009 41.420226, -73.332017 41.420199, -73.332027 41.420136, -73.332042 41.420044, -73.332073 41.419815, -73.332091 41.419696, -73.33211 41.419613, -73.332137 41.419524, -73.33216 41.419442, -73.332187 41.419365, -73.332218 41.419299, -73.332249 41.419248, -73.332306 41.419156, -73.332365 41.419195, -73.332719 41.419363, -73.333249 41.419494, -73.33356 41.41958, -73.333894 41.419617, -73.33397 41.41969, -73.334206 41.419947, -73.334464 41.420149, -73.334402 41.420047, -73.334335 41.419949, -73.334282 41.419766, -73.334249 41.419602, -73.334607 41.419575, -73.335654 41.419304, -73.33639 41.419082, -73.336765 41.418916, -73.336817 41.418928, -73.337087 41.419143, -73.337167 41.419186, -73.337363 41.419288, -73.337681 41.41938, -73.337849 41.419324, -73.33798 41.419275, -73.338522 41.41919, -73.338642 41.419159, -73.338684 41.419081, -73.338738 41.419088, -73.338805 41.419082, -73.339458 41.419158, -73.339758 41.419193, -73.339816 41.419197, -73.340702 41.419263, -73.340838 41.419279, -73.341775 41.419397, -73.342829 41.419477, -73.343415 41.419539, -73.344821 41.41966, -73.345316 41.419692, -73.345681 41.419732, -73.346045 41.419767, -73.346521 41.419813, -73.347114 41.419938, -73.347342 41.420001, -73.347686 41.420078, -73.348237 41.420208, -73.34861 41.420289, -73.348801 41.420317, -73.349643 41.420483, -73.349899 41.420532, -73.350168 41.420543, -73.350723 41.420531, -73.351427 41.420432, -73.351456 41.42042, -73.351546 41.420386, -73.351576 41.420376, -73.3518 41.419829, -73.351932 41.419651, -73.352094 41.41945, -73.352175 41.41917, -73.352118 41.41902, -73.352038 41.41891, -73.351958 41.418815, -73.352177 41.418886, -73.352384 41.418929, -73.35254 41.418957, -73.353306 41.418946, -73.354016 41.418957, -73.354095 41.418917, -73.354267 41.418831, -73.354329 41.418793, -73.354406 41.418749, -73.35453 41.418675, -73.354618 41.418834, -73.354643 41.41893, -73.354659 41.419326, -73.354678 41.419611, -73.354682 41.419665, -73.354701 41.41981, -73.354741 41.420097, -73.354808 41.420368, -73.354928 41.42074, -73.35499 41.420931, -73.355025 41.42104, -73.355141 41.421367, -73.355181 41.421505, -73.355239 41.421699, -73.355377 41.422099, -73.355397 41.422157, -73.355544 41.422648, -73.35567 41.423228, -73.355678 41.423332, -73.355692 41.42349, -73.355694 41.423637, -73.355712 41.423754, -73.355724 41.423913, -73.355722 41.423932, -73.355716 41.424011, -73.355709 41.424277, -73.355698 41.424469, -73.355697 41.424495, -73.355692 41.424648, -73.355679 41.424773, -73.355671 41.424857, -73.355652 41.425054, -73.355644 41.42536, -73.355619 41.425484, -73.35558 41.425691, -73.355567 41.425751, -73.35553 41.425932, -73.355519 41.425993, -73.355484 41.426147, -73.355426 41.426409, -73.355356 41.426604, -73.355303 41.426754, -73.355244 41.426909, -73.355069 41.427377, -73.355012 41.427533, -73.35497 41.427664, -73.354961 41.427692, -73.354896 41.427898, -73.354815 41.428172, -73.354769 41.428333, -73.354741 41.428415, -73.35473 41.428451, -73.35466 41.428663, -73.354634 41.428746, -73.354971 41.428635, -73.35503 41.428615, -73.355867 41.428339, -73.356209 41.428195, -73.356594 41.428035, -73.357365 41.427712, -73.358165 41.427378, -73.358794 41.427058, -73.359552 41.426748, -73.359666 41.426715, -73.360471 41.426487, -73.360482 41.426518, -73.360549 41.426648, -73.36058 41.426692, -73.360665 41.426732, -73.360956 41.426795, -73.361282 41.426838, -73.361656 41.426951, -73.361717 41.42697, -73.362446 41.427266, -73.362601 41.427391, -73.363508 41.428362, -73.363725 41.42869, -73.363876 41.428977, -73.363798 41.429702, -73.363848 41.429905, -73.36386 41.429953, -73.364072 41.430477, -73.364285 41.430823, -73.364329 41.430968, -73.36436 41.431138, -73.364416 41.431295, -73.364424 41.431448, -73.364612 41.432431, -73.364622 41.432483, -73.364711 41.43305, -73.364731 41.433566, -73.364697 41.433721, -73.364603 41.433963, -73.364497 41.434204, -73.364404 41.434592, -73.364385 41.434796, -73.364393 41.435, -73.364471 41.435236, -73.364478 41.435452, -73.364439 41.435616, -73.364303 41.435808, -73.36402 41.436176, -73.363991 41.436215, -73.36384 41.436418, -73.36362 41.436714, -73.363436 41.436993, -73.363271 41.437263, -73.363426 41.437318, -73.363597 41.437379, -73.363885 41.437501, -73.363896 41.437506, -73.364032 41.437577, -73.364249 41.43769, -73.364351 41.437748, -73.364384 41.437767, -73.364311 41.437857, -73.360833 41.441569, -73.359859 41.44261, -73.358844 41.443694, -73.354846 41.447961, -73.354574 41.447825, -73.354424 41.44775, -73.354405 41.447793, -73.35426 41.448008, -73.353924 41.448194, -73.35347 41.448446, -73.352296 41.449101, -73.352144 41.449217, -73.352123 41.449232, -73.352093 41.449255, -73.35206 41.449313, -73.352009 41.449381, -73.351353 41.449679, -73.351475 41.449711, -73.351649 41.449758, -73.35177 41.449824, -73.351939 41.449897, -73.352171 41.449998, -73.35229 41.450103, -73.352397 41.450199, -73.352437 41.450235, -73.352531 41.450327, -73.352577 41.450387, -73.352647 41.450477, -73.353037 41.451126, -73.35312 41.451286, -73.353363 41.451752, -73.353529 41.452086, -73.353565 41.452157, -73.353615 41.452452, -73.353708 41.452626, -73.353892 41.452893, -73.354513 41.45379, -73.354795 41.454184, -73.355225 41.454784, -73.355491 41.455082, -73.35589 41.455595, -73.356259 41.456132, -73.356319 41.456219, -73.356464 41.456453, -73.356683 41.456768, -73.356941 41.45704, -73.357112 41.457212, -73.357476 41.457498, -73.357572 41.457555, -73.358449 41.458221, -73.358701 41.458509, -73.358934 41.458912, -73.359422 41.459756, -73.359539 41.460062, -73.359587 41.460369, -73.359637 41.460682, -73.35969 41.460921, -73.359794 41.46128, -73.359881 41.46158, -73.360047 41.462181, -73.360051 41.462195, -73.360489 41.463519, -73.360602 41.463968, -73.360715 41.464418, -73.360754 41.464574, -73.360246 41.464594, -73.359864 41.464631, -73.359621 41.464656, -73.359288 41.46461, -73.359112 41.464582, -73.358969 41.464565, -73.358772 41.46454, -73.358325 41.464401, -73.357305 41.464168, -73.357251 41.464139, -73.357089 41.464053, -73.356941 41.463974, -73.356435 41.463783, -73.356196 41.463675, -73.355566 41.46339, -73.355482 41.463351, -73.355246 41.463241, -73.354901 41.463081, -73.354304 41.462761, -73.353535 41.462349, -73.353012 41.462629, -73.35275 41.462765, -73.352524 41.462878, -73.352242 41.463026, -73.352045 41.463096, -73.351805 41.46316, -73.351685 41.463167, -73.350949 41.462847, -73.350714 41.462807, -73.350498 41.462803, -73.350181 41.462914, -73.34971 41.463099, -73.349401 41.463272, -73.349189 41.463487, -73.349041 41.463721, -73.348751 41.464132, -73.34824 41.464706, -73.348573 41.464919, -73.348742 41.465027, -73.348951 41.465183, -73.349449 41.465709, -73.349479 41.465741, -73.34973 41.465988, -73.349919 41.466073, -73.350033 41.466092, -73.350883 41.466234, -73.350987 41.466258, -73.351302 41.466332, -73.351306 41.46658, -73.351419 41.466774, -73.351702 41.467118, -73.351949 41.467299, -73.352268 41.4674, -73.352799 41.467476, -73.35323 41.467402, -73.354021 41.467221, -73.35429 41.467203, -73.354588 41.467259, -73.354851 41.467388, -73.35506 41.467497, -73.355289 41.467568, -73.356118 41.467823, -73.35662 41.468039, -73.356802 41.468189, -73.35695 41.468581, -73.356911 41.468803, -73.35689 41.469142, -73.356903 41.469349, -73.357054 41.469656, -73.357113 41.469683, -73.357121 41.469692, -73.357744 41.469807, -73.357796 41.469817, -73.357836 41.469822, -73.357796 41.469845, -73.357752 41.469903, -73.357758 41.469921, -73.357657 41.470045, -73.357671 41.470392, -73.357634 41.470464, -73.357482 41.470547, -73.357205 41.470719, -73.357066 41.470753, -73.356927 41.470841, -73.356716 41.47091, -73.356343 41.470945, -73.356219 41.470984, -73.356109 41.4711, -73.355993 41.471121, -73.355913 41.471196, -73.355927 41.471318, -73.35592 41.471462, -73.355903 41.471476, -73.356115 41.471455, -73.356313 41.471517, -73.356525 41.471584, -73.356656 41.471684, -73.356768 41.471822, -73.356827 41.471965, -73.356815 41.472098, -73.356775 41.472374, -73.356739 41.472574, -73.356713 41.472741, -73.356673 41.472855, -73.356637 41.472955, -73.356618 41.473153, -73.356711 41.473147, -73.356683 41.473293, -73.35669 41.47341, -73.356812 41.473769, -73.356917 41.473885, -73.357072 41.474056, -73.357741 41.474775, -73.358337 41.475513, -73.358501 41.475694, -73.358831 41.476057, -73.35886 41.476136, -73.358906 41.476176, -73.358982 41.476195, -73.359069 41.476176, -73.35915 41.476158, -73.359321 41.476106, -73.359432 41.476073, -73.359892 41.475919, -73.36007 41.475864, -73.360109 41.475853, -73.360327 41.475808, -73.360423 41.475603, -73.360442 41.475287, -73.360426 41.475253, -73.360358 41.475106, -73.360043 41.474832, -73.359962 41.474576, -73.359884 41.474362, -73.359842 41.474116, -73.359905 41.473705, -73.359945 41.473447, -73.360148 41.473201, -73.360892 41.473281, -73.361209 41.473286, -73.361706 41.473294, -73.361988 41.473295, -73.362229 41.473296, -73.36281 41.473211, -73.363435 41.473086, -73.36436 41.472852, -73.364436 41.472833, -73.364634 41.472765, -73.36532 41.472396, -73.36553 41.472831, -73.366075 41.47396, -73.366163 41.474138, -73.366378 41.474572, -73.366446 41.474728, -73.36665 41.475197, -73.366719 41.475354, -73.366871 41.475674, -73.36704 41.476029, -73.367138 41.476235, -73.367233 41.476387, -73.367327 41.476462, -73.367456 41.476521, -73.367558 41.476569, -73.367744 41.476724, -73.367899 41.476853, -73.367997 41.476902, -73.368253 41.47703, -73.368585 41.477196, -73.368731 41.477254, -73.368997 41.477361, -73.369159 41.477393, -73.36931 41.477398, -73.36959 41.47731, -73.369777 41.4773, -73.369957 41.477337, -73.369974 41.477341, -73.370221 41.477412, -73.370506 41.477568, -73.370685 41.477318, -73.370751 41.477227, -73.370983 41.476831, -73.37098 41.476566, -73.370959 41.476484, -73.370944 41.476424, -73.370927 41.476275, -73.37089 41.476188, -73.370931 41.476206, -73.371005 41.47627, -73.371288 41.47664, -73.37137 41.476748, -73.371414 41.476884, -73.371472 41.47697, -73.371764 41.477132, -73.371903 41.477246, -73.371962 41.477342, -73.371969 41.47754, -73.372137 41.477745, -73.372342 41.477874, -73.372398 41.477973, -73.372422 41.478015, -73.372349 41.47805, -73.372306 41.47818, -73.372204 41.478422, -73.372245 41.478492, -73.372276 41.478544, -73.372313 41.478648, -73.372226 41.478886, -73.372153 41.479178, -73.372087 41.47928, -73.371963 41.479589, -73.371905 41.479651, -73.371854 41.47975, -73.371869 41.480047, -73.371818 41.4802, -73.37176 41.480658, -73.371775 41.480811, -73.371826 41.481051, -73.371987 41.481314, -73.371965 41.48149, -73.371856 41.481578, -73.371535 41.4817, -73.371535 41.482011, -73.371462 41.482095, -73.371455 41.482153, -73.371594 41.482435, -73.371601 41.482588, -73.371427 41.483031, -73.371405 41.48327, -73.371442 41.483626, -73.371427 41.483806, -73.371457 41.483901, -73.371676 41.484345, -73.371727 41.484558, -73.371662 41.484827, -73.37156 41.485857, -73.371481 41.486171, -73.371656 41.486264, -73.371896 41.486578, -73.372101 41.48672, -73.372211 41.486753, -73.372437 41.48677, -73.372503 41.486753, -73.372773 41.486617, -73.372919 41.486714, -73.373357 41.487116, -73.373672 41.487314, -73.374564 41.487705, -73.374703 41.487833, -73.375088 41.488073, -73.37567 41.488284, -73.37597 41.488441, -73.376291 41.488468, -73.376386 41.488569, -73.376663 41.488717, -73.376743 41.488848, -73.376933 41.489045, -73.376954 41.489144, -73.376918 41.489189, -73.376794 41.489381, -73.376794 41.489489, -73.37691 41.489873, -73.377114 41.490214, -73.377267 41.49054, -73.37734 41.490766, -73.377573 41.491261, -73.377712 41.491533, -73.377872 41.491742, -73.37866 41.49228, -73.378959 41.492455, -73.379135 41.492507, -73.379288 41.492496, -73.379346 41.492447, -73.379404 41.492318, -73.379434 41.491994, -73.379492 41.491968, -73.379756 41.492016, -73.380025 41.492124, -73.380259 41.492127, -73.380412 41.492219, -73.380682 41.492322, -73.381062 41.492382, -73.381164 41.492351, -73.381303 41.492281, -73.381449 41.492135, -73.381588 41.491848, -73.381603 41.491371, -73.381567 41.491123, -73.381509 41.490915, -73.381494 41.49078, -73.381276 41.490416, -73.381304 41.490399, -73.381517 41.49051, -73.381779 41.490559, -73.381882 41.490614, -73.38202 41.49067, -73.382297 41.490728, -73.382502 41.490717, -73.382604 41.490674, -73.382699 41.490585, -73.382772 41.490415, -73.382824 41.490371, -73.382962 41.490323, -73.383254 41.490305, -73.383474 41.490182, -73.383693 41.489919, -73.384168 41.489165, -73.384226 41.48912, -73.384303 41.4891, -73.384438 41.489077, -73.384372 41.489199, -73.384269 41.489468, -73.384327 41.489851, -73.384663 41.490248, -73.384678 41.490311, -73.384656 41.490347, -73.384377 41.490253, -73.384144 41.490322, -73.384063 41.490388, -73.383808 41.490731, -73.383618 41.491175, -73.383617 41.491674, -73.383572 41.492156, -73.383456 41.49242, -73.383456 41.492501, -73.383587 41.492804, -73.383535 41.492984, -73.383237 41.492831, -73.383054 41.492869, -73.382849 41.493015, -73.382725 41.493193, -73.382645 41.49339, -73.382659 41.49353, -73.382622 41.493552, -73.382352 41.493607, -73.382228 41.493659, -73.382126 41.493739, -73.382053 41.493846, -73.381958 41.493912, -73.381921 41.493902, -73.381804 41.493838, -73.381308 41.493709, -73.380929 41.493636, -73.380702 41.493633, -73.38049 41.493648, -73.380388 41.493687, -73.380336 41.493712, -73.380154 41.493801, -73.380103 41.4938, -73.380023 41.493767, -73.379709 41.493718, -73.379483 41.493647, -73.379307 41.493523, -73.378936 41.493153, -73.378644 41.492996, -73.378388 41.492916, -73.378074 41.49292, -73.377782 41.492961, -73.377476 41.493065, -73.37738 41.49319, -73.377285 41.493553, -73.377256 41.493981, -73.377321 41.494184, -73.377321 41.494306, -73.377553 41.494922, -73.37821 41.49576, -73.378407 41.495969, -73.378495 41.49611, -73.378698 41.497077, -73.378683 41.497171, -73.378596 41.497467, -73.378566 41.49767, -73.378573 41.498224, -73.378623 41.498684, -73.378601 41.498782, -73.378433 41.498902, -73.378389 41.498987, -73.378506 41.499317, -73.378542 41.499615, -73.3786 41.499805, -73.378564 41.500097, -73.378621 41.500165, -73.378823 41.500353, -73.379138 41.500501, -73.379337 41.500563, -73.379386 41.500595, -73.379622 41.500945, -73.380002 41.501414, -73.380363 41.501743, -73.380669 41.50195, -73.380859 41.502012, -73.381326 41.502054, -73.381998 41.502424, -73.38234 41.502699, -73.382552 41.502846, -73.383085 41.503115, -73.383165 41.503193, -73.383289 41.503424, -73.383654 41.503677, -73.384158 41.503864, -73.384399 41.503912, -73.385042 41.504016, -73.385669 41.504196, -73.38594 41.504222, -73.386297 41.504308, -73.387466 41.504861, -73.387714 41.504963, -73.38797 41.505079, -73.388481 41.505397, -73.389116 41.505983, -73.389298 41.506129, -73.389649 41.506472, -73.389904 41.506768, -73.390035 41.507076, -73.390072 41.507225, -73.390079 41.507807, -73.390042 41.507991, -73.390064 41.508167, -73.389997 41.508476, -73.389968 41.508697, -73.389933 41.508897, -73.389902 41.509079, -73.38988 41.509231, -73.389909 41.509376, -73.389697 41.509494, -73.389625 41.509651, -73.389529 41.510141, -73.389411 41.510634, -73.389353 41.510697, -73.389215 41.510803, -73.388594 41.511064, -73.388382 41.511237, -73.388119 41.511364, -73.38787 41.511392, -73.387249 41.51132, -73.386927 41.511329, -73.386744 41.511385, -73.386518 41.511549, -73.386452 41.511642, -73.386408 41.511804, -73.386342 41.512087, -73.386342 41.512546, -73.386422 41.512853, -73.386561 41.513148, -73.386735 41.513407, -73.386845 41.513503, -73.387005 41.513541, -73.387298 41.513528, -73.387349 41.51356, -73.387385 41.51374, -73.38748 41.513872, -73.387816 41.51412, -73.387976 41.514199, -73.388129 41.514295, -73.388437 41.514489, -73.389109 41.514773, -73.389263 41.514879, -73.389569 41.515091, -73.390212 41.515415, -73.391293 41.515813, -73.391723 41.515846, -73.392074 41.516031, -73.392172 41.51611, -73.392271 41.516191, -73.392395 41.516242, -73.392772 41.516316, -73.392789 41.51632, -73.392979 41.516444, -73.393498 41.516717, -73.393921 41.516873, -73.394064 41.516926, -73.394493 41.517085, -73.394637 41.517138, -73.394937 41.517219, -73.395419 41.517487, -73.39617 41.517754, -73.396279 41.517771, -73.396295 41.517774, -73.396623 41.517774, -73.396937 41.51772, -73.397193 41.517633, -73.397347 41.517519, -73.397441 41.517529, -73.397676 41.51751, -73.397712 41.517726, -73.39777 41.517912, -73.398222 41.518355, -73.398536 41.518499, -73.398741 41.518659, -73.399012 41.518762, -73.39915 41.518841, -73.399274 41.518968, -73.399683 41.519528, -73.400005 41.519857, -73.400326 41.520113, -73.400653 41.520285, -73.400836 41.520346, -73.401385 41.520421, -73.401275 41.520636, -73.401246 41.520847, -73.401376 41.521267, -73.401953 41.522235, -73.402463 41.523264, -73.402631 41.523591, -73.403004 41.524343, -73.403193 41.524747, -73.403346 41.525177, -73.403427 41.525511, -73.403558 41.525851, -73.403835 41.526445, -73.404419 41.527367, -73.404799 41.528043, -73.405135 41.528895, -73.40563 41.529856, -73.406207 41.531675, -73.406229 41.53186, -73.406389 41.53242, -73.40655 41.532846, -73.406674 41.53337, -73.40674 41.533848, -73.406629 41.535711, -73.406534 41.536322, -73.406417 41.536636, -73.406307 41.537116, -73.406176 41.537515, -73.406157 41.537554, -73.405897 41.53811, -73.405839 41.538407, -73.405795 41.538514, -73.405605 41.538791, -73.405276 41.539768, -73.405063 41.540301, -73.405063 41.540395, -73.405202 41.540537, -73.406034 41.54162, -73.406181 41.542037, -73.406341 41.542868, -73.406486 41.543221, -73.406508 41.543379, -73.406516 41.543519, -73.40653 41.543571, -73.406545 41.543627, -73.406676 41.543786, -73.406969 41.544092, -73.407023 41.544135, -73.406654 41.544336, -73.406801 41.544915, -73.406831 41.544882, -73.406952 41.544782, -73.407081 41.544699, -73.406419 41.548118, -73.405991 41.549429, -73.405764 41.549753, -73.405565 41.550081, -73.405566 41.550272, -73.405587 41.550596, -73.405496 41.550594, -73.405357 41.550576, -73.40527 41.550545, -73.405161 41.550471, -73.404968 41.550313, -73.404793 41.550203, -73.404676 41.550144, -73.404519 41.550079, -73.404406 41.550041, -73.404288 41.550013, -73.40416 41.550004, -73.404096 41.55, -73.403904 41.549998, -73.402818 41.550043, -73.402567 41.550042, -73.402383 41.550031, -73.402249 41.550012, -73.402117 41.549986, -73.401858 41.549909, -73.401645 41.549825, -73.401128 41.549596, -73.400952 41.549528, -73.40075 41.54946, -73.400591 41.549416, -73.400429 41.549378, -73.400314 41.549357, -73.399776 41.549296, -73.399541 41.549281, -73.399492 41.549278, -73.398731 41.549263, -73.398417 41.549267, -73.398102 41.549278, -73.397962 41.549289, -73.397689 41.549329, -73.397641 41.54934, -73.397577 41.549355, -73.39741 41.54941, -73.397287 41.549476, -73.397193 41.549548, -73.397136 41.549614, -73.397091 41.549685, -73.39698 41.549941, -73.396968 41.549962, -73.396817 41.55025, -73.396743 41.550358, -73.396534 41.550544, -73.396426 41.550621, -73.3961 41.550859, -73.395821 41.551071, -73.395144 41.551625, -73.394958 41.551779, -73.394836 41.55187, -73.394786 41.551907, -73.394711 41.551951, -73.394693 41.551962, -73.394606 41.552, -73.394448 41.552052, -73.394245 41.552096, -73.394124 41.552129, -73.394088 41.552143, -73.393971 41.552216, -73.393848 41.552324, -73.393712 41.552419, -73.393269 41.552685, -73.393111 41.552786, -73.393037 41.552839, -73.392935 41.552938, -73.392866 41.553038, -73.392834 41.553105, -73.392802 41.553233, -73.3928 41.553353, -73.392836 41.553595, -73.392888 41.554072, -73.392965 41.554673, -73.393025 41.555272, -73.393039 41.555367, -73.393128 41.555836, -73.393198 41.556365, -73.39323 41.556572, -73.393276 41.556793, -73.39334 41.556941, -73.393233 41.556947, -73.393165 41.556945, -73.392944 41.556899, -73.392486 41.55678, -73.391797 41.556591, -73.391498 41.556505, -73.391283 41.556434, -73.391079 41.556374, -73.391052 41.556431, -73.390964 41.556637, -73.390978 41.556727, -73.391052 41.556913, -73.391014 41.557146, -73.391087 41.55726, -73.391205 41.557325, -73.391533 41.557442, -73.392 41.557975, -73.392628 41.558822, -73.39284 41.559198, -73.393235 41.559825, -73.393286 41.55997, -73.393424 41.560211, -73.393387 41.560345, -73.393249 41.560461, -73.393226 41.560514, -73.393256 41.560605, -73.393402 41.560764, -73.393431 41.56085, -73.393562 41.561145, -73.393672 41.561444, -73.393862 41.562162, -73.393927 41.562244, -73.393927 41.56251, -73.393883 41.562712, -73.393875 41.563059, -73.393721 41.563313, -73.393451 41.563521, -73.393217 41.56377, -73.393169 41.563932, -73.393119 41.563888, -73.392617 41.563347, -73.392369 41.563046, -73.392201 41.562801, -73.392139 41.562699, -73.39208 41.5626, -73.391413 41.561334, -73.391144 41.560866, -73.390869 41.560457, -73.390593 41.560082, -73.390278 41.559694, -73.389166 41.558464, -73.388344 41.557578, -73.379074 41.562143, -73.378864 41.562231, -73.372047 41.565105, -73.364635 41.564886, -73.362905 41.564912, -73.362592 41.564938, -73.361654 41.565018, -73.361342 41.565045, -73.358959 41.565172, -73.353014 41.565489, -73.353757 41.567527, -73.351878 41.56808, -73.352113 41.568345, -73.352142 41.568485, -73.352149 41.568584, -73.352281 41.56877, -73.352442 41.568908, -73.352749 41.569146, -73.352969 41.569424, -73.353145 41.569742, -73.353276 41.570095, -73.353459 41.570287, -73.353796 41.570539, -73.354307 41.57093, -73.354586 41.571208, -73.354834 41.571415, -73.355237 41.571889, -73.355435 41.572387, -73.355625 41.572669, -73.355873 41.572866, -73.356356 41.573107, -73.356653 41.573239, -73.356543 41.573376, -73.356208 41.573561, -73.356091 41.573712, -73.356128 41.573736, -73.356543 41.574048, -73.356642 41.574103, -73.356892 41.574206, -73.356941 41.57422, -73.357024 41.574237, -73.357111 41.574247, -73.357209 41.574251, -73.35729 41.574252, -73.357349 41.574253, -73.357492 41.574235, -73.357534 41.574225, -73.357614 41.574208, -73.357629 41.574205, -73.35775 41.574166, -73.357832 41.574131, -73.357866 41.574118, -73.358102 41.573986, -73.358443 41.573796, -73.358646 41.573684, -73.35871 41.573648, -73.358822 41.573586, -73.358907 41.573546, -73.358974 41.573516, -73.359105 41.573456, -73.35931 41.57339, -73.359455 41.573354, -73.359754 41.573282, -73.35999 41.57324, -73.360179 41.573217, -73.360672 41.573179, -73.360966 41.573171, -73.361325 41.573163, -73.361475 41.57317, -73.361584 41.573175, -73.361777 41.573203, -73.362126 41.573292, -73.362749 41.573488, -73.363702 41.573788, -73.36387 41.573857, -73.363993 41.573917, -73.364172 41.57402, -73.364343 41.57414, -73.364485 41.574266, -73.364729 41.574535, -73.365036 41.574925, -73.365555 41.575555, -73.365776 41.575831, -73.36613 41.576272, -73.366514 41.576785, -73.366586 41.576871, -73.366695 41.577014, -73.367023 41.577443, -73.367133 41.577586, -73.367331 41.57784, -73.367622 41.578211, -73.367731 41.578332, -73.367851 41.578448, -73.367935 41.578521, -73.367979 41.578556, -73.368233 41.578756, -73.368202 41.578807, -73.368177 41.578872, -73.368169 41.578913, -73.368163 41.578993, -73.368165 41.579033, -73.368188 41.579161, -73.368212 41.579352, -73.368216 41.579376, -73.368289 41.579835, -73.368322 41.580161, -73.368328 41.580446, -73.368308 41.581163, -73.368305 41.581187, -73.36829 41.581342, -73.368246 41.581504, -73.36821 41.58161, -73.368166 41.581715, -73.368134 41.581772, -73.368322 41.581713, -73.368738 41.581583, -73.36889 41.581537, -73.369079 41.581479, -73.369271 41.581419, -73.369443 41.581368, -73.369871 41.581244, -73.37017 41.581167, -73.37055 41.581077, -73.370644 41.581055, -73.370784 41.581029, -73.370925 41.581009, -73.370993 41.581178, -73.371532 41.581358, -73.371939 41.581992, -73.373423 41.582551, -73.374091 41.582894, -73.373754 41.58354, -73.374344 41.583812, -73.374813 41.583211, -73.376105 41.583619, -73.376154 41.584937, -73.378209 41.585839, -73.379551 41.585559, -73.379959 41.585114, -73.381709 41.584835, -73.383373 41.584162, -73.384256 41.583958, -73.384953 41.583234, -73.384988 41.582791, -73.384962 41.582712, -73.384872 41.582027, -73.385045 41.582016, -73.385236 41.581848, -73.385522 41.581954, -73.385699 41.582038, -73.385924 41.582159, -73.386012 41.582221, -73.386101 41.582292, -73.386166 41.582363, -73.386194 41.582401, -73.386319 41.582573, -73.386604 41.583012, -73.386983 41.583661, -73.38705 41.583729, -73.387106 41.583779, -73.387257 41.583861, -73.387493 41.583931, -73.387731 41.584017, -73.387909 41.584127, -73.388259 41.584371, -73.388583 41.584608, -73.388946 41.584874, -73.389436 41.58528, -73.389461 41.585306, -73.389219 41.585531, -73.388977 41.58579, -73.388941 41.585822, -73.38858 41.586149, -73.388433 41.586283, -73.388249 41.586455, -73.388155 41.586531, -73.388061 41.586572, -73.387883 41.586614, -73.387612 41.586643, -73.387029 41.58667, -73.386876 41.586677, -73.386714 41.586685, -73.38653 41.586701, -73.3863 41.58674, -73.386097 41.586774, -73.385765 41.586825, -73.385651 41.586843, -73.385223 41.586915, -73.385126 41.586937, -73.385 41.586968, -73.384649 41.587081, -73.384293 41.587213, -73.384204 41.587253, -73.383968 41.587362, -73.38379 41.587465, -73.383729 41.58751, -73.383652 41.587566, -73.383586 41.587643, -73.383408 41.587856, -73.383221 41.588105, -73.383104 41.588263, -73.382687 41.588351, -73.382264 41.58842, -73.38223 41.588424, -73.381861 41.588471, -73.381681 41.588506, -73.381502 41.588555, -73.381253 41.588632, -73.380876 41.588768, -73.380522 41.58888, -73.379885 41.589089, -73.379749 41.589144, -73.379683 41.589178, -73.379534 41.589257, -73.379218 41.589507, -73.379085 41.589588, -73.378941 41.58966, -73.378879 41.589692, -73.378773 41.589737, -73.378624 41.589788, -73.378471 41.589834, -73.378363 41.589863, -73.378163 41.589918, -73.377787 41.590063, -73.377641 41.590129, -73.377596 41.590155, -73.377488 41.590233, -73.377386 41.590319, -73.377252 41.590447, -73.377152 41.590533, -73.377046 41.590614, -73.376873 41.590722, -73.376785 41.590764, -73.376692 41.59081, -73.376567 41.590858, -73.376359 41.590925, -73.376287 41.590944, -73.376206 41.59096, -73.375939 41.591015, -73.375809 41.591045, -73.375455 41.591147, -73.375182 41.591226, -73.374549 41.591395, -73.374495 41.591406, -73.374045 41.59154, -73.373312 41.591792, -73.373234 41.591822, -73.372776 41.592, -73.372566 41.592107, -73.372522 41.592123, -73.37248 41.592188, -73.372287 41.592417, -73.372261 41.592445, -73.372216 41.592495, -73.372093 41.592618, -73.37196 41.592733, -73.371864 41.592804, -73.371764 41.592871, -73.371645 41.592939, -73.371581 41.592973, -73.371247 41.59315, -73.371226 41.593162, -73.370885 41.593352, -73.37079 41.593407, -73.370699 41.593466, -73.370657 41.593502, -73.370608 41.593559, -73.370571 41.593621, -73.370547 41.593672, -73.37053 41.593724, -73.370518 41.593783, -73.370515 41.593843, -73.370528 41.593909, -73.370593 41.594078, -73.370768 41.59439, -73.371169 41.595135, -73.371483 41.595695, -73.37155 41.5958, -73.371742 41.596053, -73.372091 41.596464, -73.372467 41.596921, -73.372745 41.597242, -73.372931 41.597472, -73.373099 41.597701, -73.373302 41.598024, -73.373341 41.598098, -73.373406 41.598248, -73.373463 41.598399, -73.373692 41.599074, -73.373866 41.599612, -73.37398 41.599992, -73.374028 41.600215, -73.37407 41.600484, -73.374077 41.600582, -73.374061 41.600617, -73.374036 41.600649, -73.373986 41.600693, -73.373927 41.60073, -73.373821 41.600784, -73.373576 41.600881, -73.37344 41.600927, -73.373018 41.601055, -73.37274 41.601148, -73.372561 41.601212, -73.372281 41.601325, -73.372066 41.601426, -73.371956 41.601483, -73.371834 41.601563, -73.371543 41.60177, -73.370854 41.602364, -73.370573 41.60259, -73.370499 41.602661, -73.370453 41.60272, -73.370436 41.602755, -73.37043 41.602788, -73.370493 41.602896, -73.370498 41.602922, -73.370417 41.60283, -73.370384 41.602791, -73.370347 41.602745, -73.370311 41.602699, -73.37027 41.602656, -73.370223 41.602605, -73.370122 41.602494, -73.369979 41.602332, -73.369899 41.60224, -73.369744 41.602059, -73.369676 41.601974, -73.369615 41.601895, -73.36956 41.60182, -73.369506 41.601752, -73.36945 41.601689, -73.369394 41.601627, -73.369339 41.601573, -73.369289 41.601534, -73.369284 41.601524, -73.369212 41.601512, -73.369133 41.601516, -73.369102 41.601519, -73.369029 41.601527, -73.368976 41.601534, -73.368961 41.601537, -73.368879 41.601546, -73.368785 41.601557, -73.368682 41.60157, -73.368572 41.601583, -73.368457 41.601596, -73.368116 41.601632, -73.368006 41.601642, -73.367897 41.601653, -73.367789 41.601664, -73.367681 41.601676, -73.367573 41.601688, -73.367465 41.601701, -73.367357 41.601713, -73.367043 41.601749, -73.36694 41.601761, -73.366838 41.601772, -73.366777 41.601777, -73.36755 41.602916, -73.367494 41.602935, -73.367259 41.604521, -73.366132 41.605805, -73.365894 41.606598, -73.36596 41.607135, -73.366252 41.607079, -73.366384 41.607065, -73.366594 41.607057, -73.366911 41.607065, -73.367164 41.607058, -73.367719 41.606994, -73.368208 41.606968, -73.368467 41.606969, -73.369318 41.606989, -73.369541 41.607014, -73.369739 41.607065, -73.369931 41.607138, -73.370582 41.607468, -73.371063 41.60773, -73.371493 41.607986, -73.372388 41.608594, -73.37254 41.608684, -73.372746 41.60878, -73.372706 41.608812, -73.372668 41.60885, -73.372637 41.608892, -73.37236 41.609361, -73.372133 41.609831, -73.372068 41.610015, -73.372048 41.610166, -73.372041 41.610295, -73.372056 41.610585, -73.37206 41.610618, -73.37208 41.610779, -73.372154 41.611236, -73.372202 41.611466, -73.372212 41.61151, -73.372251 41.61207, -73.37228 41.612614, -73.372284 41.612692, -73.372319 41.613094, -73.372355 41.613336, -73.372458 41.613664, -73.372837 41.614643, -73.372846 41.614666, -73.372909 41.614944, -73.372961 41.615275, -73.373025 41.615679, -73.373049 41.615806, -73.373208 41.616645, -73.373312 41.617241, -73.373315 41.617254, -73.373441 41.617791, -73.37368 41.61845, -73.374018 41.61927, -73.374178 41.619641, -73.374379 41.620027, -73.374516 41.620349, -73.374627 41.620664, -73.374683 41.620811, -73.374746 41.620918, -73.374837 41.620997, -73.374971 41.62107, -73.375028 41.621033, -73.375184 41.621238, -73.376379 41.622749, -73.376629 41.623052, -73.37687 41.623374, -73.377257 41.623837, -73.377363 41.623936, -73.377431 41.623993, -73.377643 41.624277, -73.37815 41.625031, -73.378195 41.62511, -73.378483 41.625739, -73.378606 41.626039, -73.378821 41.626565, -73.378949 41.626868, -73.37905 41.627136, -73.379311 41.62775, -73.379671 41.62866, -73.3798 41.628942, -73.379969 41.629363, -73.380058 41.629635, -73.380133 41.629789, -73.380389 41.630156, -73.38066 41.630583, -73.380909 41.631019, -73.380941 41.631098, -73.381023 41.631295, -73.381445 41.632126, -73.381612 41.632463, -73.381698 41.632636, -73.381854 41.633014, -73.381935 41.633251, -73.382023 41.633483, -73.382139 41.63368, -73.382393 41.633968, -73.382509 41.634047, -73.382565 41.634085, -73.382757 41.634197, -73.382963 41.634293, -73.383088 41.634372, -73.383167 41.634472, -73.383215 41.634555, -73.383246 41.634639, -73.38329 41.634941, -73.383319 41.635135, -73.383361 41.635494, -73.383401 41.636079, -73.383485 41.636676, -73.38354 41.637021, -73.383548 41.637226, -73.383549 41.637242, -73.383541 41.637399, -73.383528 41.637515, -73.383389 41.637972, -73.383373 41.638077, -73.383345 41.63827, -73.383316 41.638393, -73.383292 41.638498, -73.383694 41.63866, -73.384215 41.638888, -73.384577 41.639033, -73.384788 41.639098, -73.384881 41.639119, -73.384991 41.639144, -73.385141 41.639165, -73.38527 41.639161, -73.385304 41.639157, -73.385342 41.639233, -73.385544 41.639776, -73.385643 41.639996, -73.385681 41.640102, -73.385697 41.640209, -73.38569 41.640277, -73.385665 41.640372, -73.385621 41.6405, -73.385447 41.640766, -73.385404 41.640853, -73.385385 41.640955, -73.385383 41.640998, -73.385397 41.641072, -73.385447 41.641189, -73.385598 41.641654, -73.385585 41.64171, -73.385549 41.641931, -73.385529 41.642009, -73.385476 41.642213, -73.385449 41.642656, -73.385431 41.642999, -73.385444 41.64324, -73.385487 41.643314, -73.385539 41.643381, -73.385698 41.643522, -73.385973 41.643694, -73.386108 41.643798, -73.386343 41.643929, -73.386405 41.643953, -73.386724 41.643551, -73.387068 41.643131, -73.387809 41.64223, -73.387914 41.642103, -73.388584 41.64128, -73.388971 41.64081, -73.389077 41.64068, -73.389247 41.640475, -73.38974 41.639857, -73.389658 41.639834, -73.389577 41.639812, -73.389413 41.639771, -73.389332 41.639751, -73.389449 41.639537, -73.3895 41.639479, -73.390072 41.639105, -73.390445 41.638605, -73.390496 41.638444, -73.390424 41.63838, -73.390175 41.638399, -73.389984 41.638437, -73.389889 41.638431, -73.389735 41.638388, -73.389655 41.638212, -73.389626 41.638108, -73.389655 41.637878, -73.389714 41.637821, -73.38978 41.63779, -73.390029 41.637776, -73.390161 41.637678, -73.390226 41.637589, -73.390271 41.637383, -73.390191 41.637253, -73.390139 41.637169, -73.389663 41.636784, -73.389561 41.636679, -73.389503 41.636575, -73.389495 41.636363, -73.389532 41.636053, -73.389518 41.635899, -73.389598 41.635783, -73.38976 41.635691, -73.389994 41.635667, -73.39036 41.635646, -73.390718 41.635511, -73.390952 41.635388, -73.391106 41.63535, -73.391223 41.63527, -73.39126 41.635221, -73.391267 41.635131, -73.391194 41.63504, -73.391011 41.634903, -73.390946 41.634834, -73.39096 41.634704, -73.391129 41.634472, -73.391143 41.634409, -73.391107 41.634346, -73.390997 41.634321, -73.390573 41.634343, -73.390521 41.634315, -73.390448 41.634206, -73.390675 41.63388, -73.391004 41.633705, -73.391195 41.633563, -73.391269 41.633465, -73.391305 41.633321, -73.391313 41.633141, -73.39143 41.633013, -73.391532 41.632933, -73.391803 41.632788, -73.391978 41.632678, -73.39222 41.632371, -73.392355 41.632089, -73.392463 41.632118, -73.39254 41.63213, -73.392642 41.631882, -73.392744 41.63158, -73.393038 41.630066, -73.393089 41.629762, -73.393124 41.629554)))"} -{"geo_id":"15724","urban_area_code":"15724","name":"Charlottesville, VA","lsad_name":"Charlottesville, VA Urbanized Area","area_lsad_code":"75","mtfcc_feature_class_code":"G3500","type":"U","functional_status":"S","area_land_meters":89567887,"area_water_meters":812935,"internal_point_lon":-78.4838373,"internal_point_lat":38.0575424,"internal_point_geom":"POINT(-78.4838373 38.0575424)","urban_area_geom":"MULTIPOLYGON(((-78.531157 38.02626, -78.53132 38.026219, -78.531797 38.02608, -78.531996 38.026012, -78.532236 38.025913, -78.532391 38.02584, -78.532313 38.025733, -78.532296 38.025683, -78.532295 38.02564, -78.532309 38.025589, -78.532339 38.025542, -78.532384 38.025504, -78.5325 38.025441, -78.532605 38.025367, -78.532993 38.025033, -78.533041 38.02497, -78.533074 38.024902, -78.53309 38.024829, -78.533088 38.024756, -78.532906 38.024124, -78.532856 38.024, -78.532825 38.023969, -78.532781 38.023947, -78.532721 38.023939, -78.532681 38.023945, -78.532146 38.024157, -78.531757 38.024335, -78.530125 38.025297, -78.530181 38.025349, -78.530306 38.025492, -78.530327 38.025509, -78.530376 38.025564, -78.530639 38.025833, -78.530778 38.025964, -78.531157 38.02626)), ((-78.567403 38.059618, -78.567391 38.059717, -78.567411 38.059865, -78.567432 38.059948, -78.567536 38.060101, -78.567606 38.060173, -78.567723 38.060265, -78.567773 38.060304, -78.567912 38.060398, -78.568057 38.060519, -78.568224 38.060678, -78.568314 38.060848, -78.56837 38.061062, -78.568377 38.061106, -78.568356 38.061315, -78.568231 38.061902, -78.568223 38.061919, -78.568189 38.062078, -78.568127 38.062232, -78.568086 38.062357, -78.568071 38.062402, -78.56803 38.062605, -78.568021 38.062622, -78.567953 38.06283, -78.567898 38.06305, -78.567856 38.063292, -78.567838 38.063508, -78.567835 38.063544, -78.56787 38.063731, -78.567932 38.063896, -78.567981 38.06411, -78.567953 38.064313, -78.567877 38.064758, -78.567842 38.064895, -78.567766 38.065104, -78.567727 38.065193, -78.567956 38.065131, -78.56798 38.065097, -78.567988 38.065079, -78.568049 38.064839, -78.56812 38.064658, -78.568147 38.064537, -78.568153 38.064449, -78.56815 38.064357, -78.568113 38.064067, -78.568073 38.063857, -78.567987 38.063486, -78.567982 38.063454, -78.567977 38.06333, -78.567993 38.063227, -78.568011 38.063167, -78.568306 38.062415, -78.568441 38.062054, -78.568583 38.061725, -78.568631 38.061604, -78.568662 38.061479, -78.568682 38.061286, -78.568679 38.061209, -78.568646 38.061068, -78.56833 38.060258, -78.568302 38.060203, -78.568246 38.060131, -78.568154 38.060054, -78.568064 38.060002, -78.567631 38.059812, -78.567543 38.059758, -78.567453 38.059679, -78.567403 38.059618)), ((-78.567956 38.065131, -78.567923 38.065177, -78.567858 38.065235, -78.567787 38.06528, -78.567572 38.065415, -78.5675 38.065476, -78.567422 38.065561, -78.567359 38.065653, -78.567313 38.065751, -78.567265 38.065919, -78.567201 38.066102, -78.567124 38.066281, -78.567037 38.066455, -78.566973 38.066558, -78.566884 38.066671, -78.566716 38.066841, -78.566528 38.067015, -78.566327 38.067179, -78.566257 38.067245, -78.566153 38.067362, -78.566065 38.067488, -78.565729 38.06811, -78.565688 38.068225, -78.565664 38.068343, -78.565658 38.068494, -78.565668 38.068631, -78.565804 38.06932, -78.565849 38.06954, -78.565931 38.070022, -78.566023 38.070499, -78.566042 38.070572, -78.566078 38.070667, -78.566137 38.070783, -78.566164 38.070826, -78.566228 38.070912, -78.56683 38.071783, -78.56711 38.072188, -78.567585 38.072859, -78.568422 38.074064, -78.568809 38.074605, -78.568883 38.074689, -78.568966 38.074768, -78.569081 38.074859, -78.569208 38.074939, -78.569746 38.075211, -78.570499 38.075647, -78.570822 38.075831, -78.571108 38.076001, -78.571189 38.076049, -78.571931 38.076494, -78.572055 38.076569, -78.572246 38.07668, -78.572778 38.076974, -78.573143 38.077151, -78.573392 38.077254, -78.573838 38.077413, -78.573994 38.077468, -78.574898 38.077766, -78.575051 38.077805, -78.575989 38.077967, -78.576318 38.078014, -78.57673 38.078058, -78.577328 38.078103, -78.578617 38.078184, -78.579388 38.078221, -78.579481 38.078235, -78.579569 38.078263, -78.579634 38.078295, -78.579832 38.078436, -78.580127 38.078648, -78.580458 38.078901, -78.580778 38.079163, -78.581002 38.079338, -78.581454 38.079674, -78.58166 38.079841, -78.581852 38.080018, -78.582063 38.08025, -78.582258 38.080491, -78.582321 38.080594, -78.582367 38.080702, -78.582396 38.080813, -78.582407 38.080904, -78.5824 38.081235, -78.582405 38.081439, -78.582426 38.081632, -78.582438 38.081677, -78.582471 38.08175, -78.582521 38.081817, -78.582155 38.081621, -78.581941 38.081499, -78.58109 38.08096, -78.580809 38.080802, -78.58051 38.080622, -78.580428 38.080581, -78.580384 38.080581, -78.580353 38.080596, -78.580323 38.080631, -78.580316 38.080666, -78.58033 38.080738, -78.580344 38.080896, -78.580345 38.080964, -78.580321 38.0817, -78.580297 38.082008, -78.580292 38.082317, -78.580295 38.082441, -78.58027 38.082835, -78.580256 38.08306, -78.580259 38.083347, -78.580246 38.08345, -78.580224 38.083481, -78.580188 38.083503, -78.580144 38.083511, -78.579899 38.083513, -78.579861 38.083525, -78.579841 38.08354, -78.579824 38.083568, -78.57982 38.083586, -78.579788 38.08379, -78.579809 38.083788, -78.579874 38.083776, -78.579954 38.083766, -78.580023 38.083761, -78.580088 38.083746, -78.58017 38.083731, -78.58023 38.083718, -78.580299 38.083702, -78.580366 38.083692, -78.58043 38.083691, -78.580488 38.083688, -78.580558 38.08369, -78.580627 38.083687, -78.580684 38.083685, -78.580754 38.083685, -78.580838 38.083688, -78.580908 38.083687, -78.580978 38.083693, -78.581072 38.083708, -78.581142 38.083717, -78.581205 38.08372, -78.581265 38.083724, -78.581324 38.083725, -78.581399 38.083733, -78.581504 38.083747, -78.581573 38.083753, -78.581634 38.083757, -78.581707 38.083768, -78.58177 38.083773, -78.581838 38.08378, -78.581904 38.083784, -78.581997 38.083799, -78.582069 38.083806, -78.582128 38.083812, -78.582198 38.083811, -78.582267 38.083814, -78.582336 38.083814, -78.582405 38.08382, -78.582476 38.083818, -78.582551 38.083829, -78.582617 38.083831, -78.582687 38.083843, -78.582756 38.083848, -78.582816 38.083851, -78.582888 38.083859, -78.582988 38.083865, -78.583047 38.083871, -78.583108 38.083871, -78.583139 38.083871, -78.583163 38.08382, -78.583185 38.083771, -78.583199 38.083724, -78.58321 38.083672, -78.583212 38.083605, -78.583224 38.083557, -78.583235 38.083501, -78.583258 38.083458, -78.583273 38.083412, -78.58329 38.083366, -78.583313 38.083316, -78.583348 38.083272, -78.583378 38.083226, -78.583422 38.083185, -78.583457 38.083141, -78.583492 38.0831, -78.583535 38.083066, -78.58357 38.083017, -78.583608 38.082979, -78.583659 38.082941, -78.583702 38.082898, -78.58375 38.08286, -78.583794 38.082824, -78.583842 38.082783, -78.583903 38.082744, -78.583938 38.082702, -78.584712 38.083176, -78.585542 38.083662, -78.585907 38.083885, -78.586228 38.084085, -78.586589 38.084299, -78.58691 38.084475, -78.587069 38.084191, -78.587226 38.083933, -78.587382 38.083695, -78.587504 38.083529, -78.587642 38.083371, -78.587816 38.083201, -78.588246 38.08286, -78.588839 38.082416, -78.589109 38.082233, -78.589181 38.082196, -78.589294 38.082146, -78.589454 38.082088, -78.589639 38.082044, -78.589941 38.081985, -78.590251 38.081938, -78.590574 38.081906, -78.590979 38.081879, -78.592084 38.081835, -78.592179 38.081831, -78.59309 38.081798, -78.593699 38.081775, -78.594146 38.081754, -78.594295 38.081737, -78.594478 38.081705, -78.594657 38.081659, -78.594846 38.081593, -78.595 38.081531, -78.595138 38.081473, -78.595808 38.081163, -78.597024 38.08062, -78.597165 38.080542, -78.597335 38.080425, -78.597478 38.080296, -78.597816 38.079966, -78.598005 38.079793, -78.598454 38.079381, -78.59862 38.079219, -78.598662 38.07917, -78.598738 38.079059, -78.598788 38.078966, -78.598827 38.07887, -78.598864 38.078722, -78.598894 38.078385, -78.598895 38.078304, -78.598901 38.078022, -78.598893 38.07787, -78.598867 38.077681, -78.598813 38.077457, -78.598734 38.077238, -78.598605 38.076959, -78.598489 38.076762, -78.598368 38.076593, -78.598262 38.076468, -78.598167 38.076374, -78.598039 38.076269, -78.597846 38.076138, -78.597651 38.076024, -78.597488 38.075941, -78.597362 38.075884, -78.597238 38.075833, -78.59532 38.075683, -78.595331 38.075652, -78.595323 38.07562, -78.5953 38.075594, -78.595272 38.07558, -78.595002 38.075514, -78.594715 38.075462, -78.594689 38.075453, -78.594629 38.075423, -78.594589 38.075391, -78.594552 38.075343, -78.594673 38.075208, -78.594774 38.075059, -78.594843 38.074919, -78.594338 38.07472, -78.59418 38.074652, -78.593974 38.074547, -78.593726 38.074406, -78.59351 38.074267, -78.593394 38.074172, -78.593297 38.074074, -78.593208 38.073961, -78.593149 38.073866, -78.593093 38.073748, -78.593012 38.073529, -78.592958 38.073333, -78.592915 38.073104, -78.592872 38.072879, -78.592823 38.072604, -78.592782 38.072386, -78.592595 38.071288, -78.592563 38.071099, -78.592497 38.070719, -78.595824 38.069429, -78.596203 38.069871, -78.596238 38.069904, -78.596384 38.070091, -78.596453 38.070102, -78.596625 38.070121, -78.596603 38.07006, -78.59659 38.069917, -78.596565 38.069855, -78.596512 38.069789, -78.59649 38.06977, -78.596369 38.069689, -78.59626 38.069597, -78.596149 38.069475, -78.596027 38.069351, -78.595945 38.069267, -78.595886 38.069184, -78.595842 38.069094, -78.595808 38.06899, -78.595767 38.068918, -78.595723 38.068866, -78.59567 38.06882, -78.595557 38.068746, -78.595503 38.068693, -78.595465 38.068632, -78.595435 38.068555, -78.595373 38.068447, -78.595304 38.068365, -78.595221 38.068291, -78.595115 38.068208, -78.595042 38.068128, -78.594756 38.067721, -78.594597 38.067519, -78.594441 38.067351, -78.59427 38.067191, -78.594161 38.0671, -78.593778 38.066813, -78.593681 38.066717, -78.593598 38.06661, -78.593556 38.066528, -78.593531 38.066441, -78.593518 38.066297, -78.593336 38.06634, -78.59321 38.066382, -78.593072 38.066448, -78.592992 38.066469, -78.592907 38.066476, -78.592821 38.066468, -78.592726 38.066448, -78.592638 38.066415, -78.592559 38.066369, -78.59243 38.066262, -78.59218 38.066067, -78.592102 38.066001, -78.592016 38.065895, -78.591962 38.065805, -78.591923 38.065713, -78.591891 38.065595, -78.591907 38.065451, -78.591935 38.065346, -78.591939 38.065292, -78.591925 38.065241, -78.591899 38.065206, -78.591867 38.065185, -78.591605 38.065098, -78.591787 38.06477, -78.591997 38.06439, -78.59208 38.064235, -78.592339 38.063783, -78.592458 38.063597, -78.592611 38.063397, -78.592658 38.063344, -78.592747 38.063264, -78.592828 38.063207, -78.592915 38.063156, -78.593058 38.063094, -78.593184 38.063057, -78.593369 38.06303, -78.593498 38.063021, -78.593674 38.063021, -78.594042 38.063052, -78.594232 38.06306, -78.594423 38.063053, -78.594725 38.063021, -78.595479 38.062907, -78.595988 38.062824, -78.596226 38.062768, -78.596295 38.062742, -78.596398 38.062688, -78.596477 38.062627, -78.596535 38.062567, -78.596592 38.062481, -78.59665 38.062337, -78.596816 38.061923, -78.596915 38.061703, -78.597057 38.061433, -78.597284 38.06106, -78.597422 38.060793, -78.597464 38.060684, -78.597502 38.060543, -78.597518 38.060428, -78.597521 38.060283, -78.59751 38.060166, -78.597488 38.060045, -78.59745 38.059932, -78.59734 38.059722, -78.597202 38.059484, -78.597149 38.059382, -78.597072 38.059211, -78.597014 38.059059, -78.596912 38.058792, -78.596867 38.058692, -78.596609 38.058224, -78.596585 38.058147, -78.596575 38.058047, -78.596586 38.057958, -78.596615 38.057872, -78.59666 38.057793, -78.596719 38.057723, -78.596793 38.057661, -78.596879 38.05761, -78.597028 38.057541, -78.597182 38.057487, -78.59743 38.057411, -78.59764 38.057348, -78.597726 38.05731, -78.597825 38.057251, -78.597403 38.057026, -78.597583 38.056879, -78.597683 38.056797, -78.597817 38.056725, -78.597913 38.056688, -78.598041 38.056653, -78.599384 38.056449, -78.599665 38.056407, -78.60015 38.056334, -78.600247 38.056324, -78.600415 38.056315, -78.600514 38.056318, -78.602019 38.056467, -78.603115 38.056564, -78.603846 38.056632, -78.60587 38.056823, -78.606561 38.056882, -78.607065 38.056929, -78.607745 38.056984, -78.607716 38.056869, -78.607662 38.056734, -78.607598 38.056625, -78.607557 38.056573, -78.607488 38.056498, -78.607275 38.056255, -78.607215 38.056163, -78.606989 38.05596, -78.606274 38.055144, -78.606002 38.055155, -78.605045 38.055247, -78.604334 38.05535, -78.603442 38.055515, -78.602653 38.055669, -78.601632 38.055857, -78.600706 38.056024, -78.60009 38.056112, -78.599925 38.056136, -78.599613 38.056195, -78.599582 38.056111, -78.599563 38.056099, -78.59954 38.056099, -78.599482 38.056127, -78.599364 38.056144, -78.599232 38.056151, -78.598949 38.056143, -78.598576 38.056115, -78.598405 38.056096, -78.598442 38.055987, -78.5985 38.055867, -78.598563 38.055758, -78.598618 38.055664, -78.598686 38.055526, -78.5987 38.055484, -78.598701 38.055429, -78.598676 38.055359, -78.598561 38.055139, -78.598438 38.054949, -78.598364 38.054833, -78.59829 38.054681, -78.598267 38.054588, -78.598238 38.054321, -78.598207 38.054194, -78.598174 38.054114, -78.597863 38.053589, -78.597774 38.053475, -78.597718 38.053424, -78.597632 38.053375, -78.597551 38.053348, -78.597465 38.053333, -78.597207 38.05333, -78.596984 38.053344, -78.596585 38.053391, -78.596351 38.053427, -78.596026 38.053498, -78.595622 38.053599, -78.595474 38.053627, -78.595129 38.053673, -78.595044 38.053679, -78.594958 38.053793, -78.594831 38.05389, -78.594498 38.054081, -78.594103 38.054299, -78.593867 38.054404, -78.593718 38.054454, -78.592478 38.054783, -78.591029 38.055145, -78.590737 38.055223, -78.590552 38.055283, -78.59012 38.055443, -78.589941 38.055576, -78.59012 38.055584, -78.590271 38.055585, -78.590573 38.05558, -78.590844 38.055567, -78.59224 38.055516, -78.592794 38.055511, -78.593095 38.055523, -78.593394 38.05555, -78.593812 38.05561, -78.594179 38.055677, -78.594462 38.055737, -78.594674 38.055794, -78.594803 38.055828, -78.594989 38.055885, -78.595357 38.056015, -78.59555 38.05609, -78.59592 38.056249, -78.596157 38.056361, -78.596245 38.056405, -78.59673 38.056665, -78.596175 38.056748, -78.595852 38.056789, -78.595516 38.056835, -78.594876 38.057004, -78.593848 38.057349, -78.59293 38.057683, -78.59218 38.057986, -78.592097 38.058016, -78.591411 38.058268, -78.590952 38.058402, -78.59037 38.05853, -78.589806 38.058637, -78.589788 38.058825, -78.589774 38.058896, -78.58974 38.058962, -78.589684 38.059033, -78.589587 38.059099, -78.589538 38.059127, -78.58942 38.059165, -78.589247 38.059231, -78.589059 38.059347, -78.589045 38.059369, -78.589018 38.05939, -78.588972 38.05945, -78.588916 38.059416, -78.588814 38.059371, -78.588749 38.059351, -78.588641 38.059331, -78.588534 38.059327, -78.588447 38.059339, -78.58838 38.059359, -78.588278 38.059407, -78.588217 38.05945, -78.587935 38.059678, -78.587776 38.059779, -78.58773 38.059799, -78.587606 38.059838, -78.587485 38.05986, -78.587325 38.059877, -78.587003 38.059882, -78.586542 38.059857, -78.586265 38.059866, -78.58615 38.059885, -78.585973 38.059935, -78.58584 38.05999, -78.585706 38.060062, -78.585616 38.060134, -78.585458 38.060309, -78.585249 38.060613, -78.585143 38.060735, -78.585034 38.060834, -78.584879 38.060947, -78.584773 38.06101, -78.584733 38.061025, -78.584656 38.061044, -78.584306 38.061161, -78.583954 38.061295, -78.58374 38.061397, -78.583577 38.061487, -78.583383 38.06161, -78.583351 38.061645, -78.583337 38.061687, -78.583336 38.061755, -78.583317 38.061806, -78.583282 38.061853, -78.582977 38.062139, -78.582877 38.062228, -78.582618 38.062459, -78.582397 38.062669, -78.582209 38.062576, -78.582069 38.062495, -78.581958 38.062424, -78.581963 38.062495, -78.581991 38.062585, -78.58202 38.062703, -78.582032 38.062822, -78.582015 38.063026, -78.582026 38.063085, -78.582072 38.063164, -78.582404 38.063878, -78.581284 38.064038, -78.581212 38.063944, -78.581087 38.063713, -78.580976 38.06345, -78.580941 38.063378, -78.580907 38.063291, -78.580858 38.063104, -78.580837 38.06284, -78.580816 38.062741, -78.58077 38.062641, -78.580615 38.062775, -78.580524 38.06283, -78.580418 38.062874, -78.580315 38.062902, -78.580202 38.062918, -78.57961 38.06295, -78.579608 38.062913, -78.579541 38.062437, -78.579538 38.062383, -78.579522 38.062274, -78.579487 38.06214, -78.579435 38.06201, -78.579352 38.061871, -78.579165 38.061591, -78.579086 38.061482, -78.578663 38.060941, -78.57861 38.060859, -78.578558 38.060734, -78.578548 38.060662, -78.578557 38.06059, -78.578585 38.060502, -78.578623 38.06043, -78.578706 38.060325, -78.578732 38.060298, -78.578489 38.06032, -78.57801 38.060282, -78.577537 38.060265, -78.577385 38.060249, -78.57728 38.060227, -78.577155 38.060172, -78.577114 38.060222, -78.57703 38.060298, -78.576954 38.060337, -78.576829 38.060359, -78.57678 38.060353, -78.576607 38.060315, -78.576537 38.060309, -78.576454 38.06032, -78.576343 38.060348, -78.576141 38.060419, -78.576065 38.060441, -78.575898 38.060469, -78.575801 38.060463, -78.575711 38.060469, -78.575412 38.060546, -78.575301 38.060557, -78.575204 38.060546, -78.575176 38.060524, -78.575155 38.06043, -78.575134 38.060408, -78.5751 38.060397, -78.574982 38.060452, -78.574884 38.060485, -78.574655 38.060513, -78.574551 38.060546, -78.574475 38.060584, -78.574412 38.060645, -78.574343 38.060804, -78.574287 38.06087, -78.574211 38.06093, -78.574121 38.06098, -78.57403 38.060996, -78.573829 38.061023, -78.573739 38.061062, -78.573648 38.061227, -78.5736 38.06126, -78.573551 38.061282, -78.573475 38.061293, -78.573315 38.061309, -78.573183 38.061358, -78.573058 38.061452, -78.572961 38.061512, -78.572829 38.061556, -78.572606 38.0616, -78.572412 38.061622, -78.572238 38.061666, -78.572134 38.06171, -78.572044 38.061759, -78.571877 38.061858, -78.571544 38.062144, -78.571356 38.062336, -78.570947 38.06277, -78.570662 38.063066, -78.570523 38.06322, -78.570329 38.063401, -78.570224 38.063517, -78.570079 38.063687, -78.570058 38.063698, -78.569981 38.063791, -78.569613 38.064148, -78.569391 38.064329, -78.569196 38.0645, -78.56887 38.064741, -78.568787 38.064796, -78.568578 38.064911, -78.568391 38.064994, -78.56812 38.065087, -78.567956 38.065131)), ((-78.474651 38.096861, -78.474687 38.097067, -78.474733 38.097337, -78.47512 38.096964, -78.47647 38.096008, -78.476937 38.095693, -78.477001 38.095837, -78.477108 38.096076, -78.477317 38.096544, -78.47761 38.097197, -78.477631 38.097235, -78.477664 38.097258, -78.477706 38.097268, -78.478089 38.097236, -78.478253 38.097214, -78.478667 38.097131, -78.478793 38.097111, -78.478967 38.0971, -78.479008 38.097094, -78.479071 38.097072, -78.479098 38.097054, -78.479111 38.09702, -78.479107 38.096986, -78.479084 38.096955, -78.478962 38.09688, -78.478922 38.096845, -78.478906 38.096819, -78.478892 38.096773, -78.478897 38.096725, -78.478947 38.09664, -78.479003 38.09658, -78.479074 38.09653, -78.479315 38.096418, -78.479352 38.096415, -78.479394 38.096396, -78.479474 38.096342, -78.479527 38.096306, -78.479622 38.096257, -78.479858 38.096192, -78.479963 38.096176, -78.480096 38.09617, -78.480229 38.09618, -78.480575 38.096262, -78.480769 38.096304, -78.481031 38.096351, -78.48131 38.096381, -78.481485 38.0964, -78.482006 38.096433, -78.482147 38.096445, -78.482196 38.096449, -78.482367 38.096474, -78.482534 38.096514, -78.482654 38.096558, -78.482797 38.096623, -78.482911 38.096689, -78.483469 38.097084, -78.483624 38.097212, -78.483813 38.097388, -78.483906 38.097336, -78.484003 38.097268, -78.484071 38.097212, -78.484154 38.097132, -78.484268 38.096994, -78.484355 38.096866, -78.484488 38.096628, -78.484786 38.096037, -78.485232 38.095139, -78.485581 38.094466, -78.485718 38.094254, -78.485835 38.094108, -78.485995 38.093934, -78.486132 38.093804, -78.486276 38.093692, -78.486487 38.093549, -78.48661 38.093478, -78.486739 38.093415, -78.487095 38.093273, -78.487263 38.093218, -78.487406 38.093181, -78.487967 38.093072, -78.488439 38.092981, -78.488126 38.09363, -78.488148 38.093735, -78.488187 38.093928, -78.4883 38.094212, -78.488356 38.094239, -78.488344 38.094268, -78.488405 38.094478, -78.488482 38.094565, -78.488833 38.094958, -78.488908 38.095044, -78.488925 38.094934, -78.48897 38.094741, -78.489043 38.094529, -78.489029 38.094503, -78.489015 38.094431, -78.489021 38.094267, -78.489056 38.094107, -78.489167 38.093899, -78.48959 38.093229, -78.489708 38.09302, -78.489743 38.092894, -78.489708 38.092877, -78.489514 38.092888, -78.489424 38.09291, -78.489382 38.092817, -78.489378 38.09279, -78.489468 38.092771, -78.489741 38.092699, -78.48999 38.092618, -78.490238 38.092525, -78.490384 38.092454, -78.490544 38.092364, -78.490683 38.092269, -78.490881 38.092113, -78.491033 38.091972, -78.491142 38.091853, -78.491262 38.091703, -78.491366 38.091546, -78.491449 38.09139, -78.491519 38.091233, -78.491574 38.091061, -78.491619 38.09084, -78.491641 38.090619, -78.49164 38.090487, -78.491613 38.090286, -78.491576 38.090128, -78.49149 38.089869, -78.491415 38.08971, -78.491091 38.089136, -78.491033 38.089025, -78.490945 38.088828, -78.4909 38.088685, -78.490877 38.088574, -78.490835 38.088311, -78.490747 38.087323, -78.490661 38.086432, -78.490651 38.086186, -78.490653 38.085991, -78.49072 38.085309, -78.490764 38.084781, -78.490774 38.084473, -78.490779 38.084001, -78.490759 38.083435, -78.490708 38.083134, -78.49066 38.082952, -78.49059 38.082762, -78.491099 38.082443, -78.49149 38.082174, -78.491968 38.081828, -78.492688 38.081262, -78.492832 38.081144, -78.494061 38.080171, -78.494335 38.079955, -78.494538 38.079794, -78.494789 38.079585, -78.49503 38.079372, -78.495348 38.079061, -78.495489 38.078918, -78.495624 38.078766, -78.495952 38.078381, -78.496091 38.078196, -78.496716 38.077341, -78.49727 38.076575, -78.497395 38.076398, -78.498103 38.075427, -78.498675 38.074639, -78.498803 38.074436, -78.498867 38.074324, -78.498949 38.074158, -78.499104 38.074275, -78.499523 38.074622, -78.499588 38.074693, -78.499655 38.074754, -78.499725 38.074799, -78.499796 38.07483, -78.499834 38.074837, -78.499882 38.074832, -78.500229 38.074662, -78.500277 38.074635, -78.500306 38.074605, -78.500359 38.074497, -78.500306 38.074473, -78.500185 38.074321, -78.500153 38.074273, -78.500121 38.074203, -78.500092 38.074102, -78.500081 38.073988, -78.500089 38.07391, -78.500204 38.073595, -78.500219 38.073514, -78.500218 38.073449, -78.500203 38.073374, -78.500158 38.073245, -78.500023 38.072998, -78.499958 38.072916, -78.499888 38.072857, -78.499794 38.072805, -78.49964 38.072741, -78.499538 38.07271, -78.499446 38.072697, -78.499323 38.072698, -78.499328 38.072586, -78.499321 38.072347, -78.499316 38.072222, -78.499274 38.071925, -78.499087 38.071202, -78.499154 38.071172, -78.499274 38.071107, -78.499381 38.07103, -78.499547 38.070881, -78.500039 38.070402, -78.500172 38.070272, -78.500437 38.069979, -78.500746 38.069651, -78.500819 38.069571, -78.501158 38.069225, -78.501447 38.068978, -78.50175 38.06874, -78.502287 38.068342, -78.502498 38.06819, -78.502863 38.067917, -78.503018 38.067798, -78.503176 38.06767, -78.50337 38.067513, -78.503444 38.067439, -78.50362 38.067231, -78.503735 38.067076, -78.503839 38.066912, -78.503952 38.066698, -78.504002 38.06658, -78.504062 38.066395, -78.504125 38.066092, -78.504323 38.066119, -78.504423 38.066143, -78.504615 38.0662, -78.504808 38.06625, -78.505066 38.0663, -78.506351 38.066484, -78.506743 38.06653, -78.508427 38.066756, -78.508902 38.066827, -78.509065 38.066869, -78.509221 38.066924, -78.510076 38.067283, -78.510131 38.067295, -78.51025 38.067287, -78.51036 38.067264, -78.51043 38.06724, -78.51117 38.06753, -78.512353 38.068025, -78.512418 38.067995, -78.512491 38.067977, -78.512566 38.067974, -78.51264 38.067985, -78.512855 38.068063, -78.513232 38.068181, -78.513373 38.068235, -78.513463 38.068288, -78.513541 38.068352, -78.513747 38.068585, -78.513825 38.068653, -78.513878 38.068689, -78.51416 38.068837, -78.514216 38.068766, -78.514362 38.068603, -78.514592 38.068324, -78.514731 38.068127, -78.515167 38.067565, -78.515349 38.067261, -78.515489 38.066988, -78.515556 38.066843, -78.515664 38.066576, -78.515778 38.066322, -78.515921 38.066006, -78.515813 38.06592, -78.515669 38.065787, -78.515541 38.065644, -78.515426 38.065484, -78.515329 38.065317, -78.515242 38.065122, -78.515203 38.065014, -78.514995 38.064371, -78.514904 38.064172, -78.514811 38.064012, -78.514716 38.063877, -78.515055 38.063676, -78.515159 38.063622, -78.515324 38.063557, -78.515862 38.063408, -78.515914 38.063389, -78.515994 38.063346, -78.516368 38.063045, -78.516604 38.062846, -78.516825 38.062637, -78.516979 38.062507, -78.517112 38.06241, -78.517176 38.062375, -78.517346 38.062299, -78.517533 38.062243, -78.517861 38.062163, -78.51828 38.062049, -78.518384 38.062026, -78.518492 38.062012, -78.518608 38.062008, -78.518676 38.062016, -78.518771 38.062041, -78.518853 38.062082, -78.518917 38.062139, -78.518954 38.062196, -78.518992 38.062303, -78.519001 38.062376, -78.518992 38.06245, -78.518965 38.062517, -78.518932 38.062551, -78.518886 38.062575, -78.518598 38.062633, -78.518299 38.062702, -78.518215 38.062732, -78.518156 38.062771, -78.51811 38.062819, -78.517915 38.062985, -78.517786 38.063102, -78.517757 38.063137, -78.517722 38.063201, -78.517703 38.06327, -78.517702 38.06335, -78.517719 38.063408, -78.517751 38.063451, -78.517787 38.063479, -78.517841 38.063505, -78.517926 38.063522, -78.518047 38.063534, -78.518273 38.063541, -78.518573 38.063532, -78.518807 38.063507, -78.518885 38.063492, -78.519035 38.063448, -78.519186 38.063386, -78.519446 38.063288, -78.519646 38.063184, -78.519719 38.063138, -78.519796 38.063069, -78.519953 38.06287, -78.520007 38.062771, -78.520071 38.062573, -78.520273 38.062141, -78.520398 38.061854, -78.520708 38.061304, -78.520956 38.060711, -78.520988 38.060652, -78.521044 38.060587, -78.521265 38.060409, -78.521386 38.060291, -78.521543 38.060102, -78.521627 38.059988, -78.521694 38.059868, -78.521776 38.059591, -78.521866 38.059254, -78.521871 38.059174, -78.521862 38.05911, -78.521805 38.058936, -78.521804 38.058876, -78.521822 38.058817, -78.521856 38.058763, -78.521906 38.058718, -78.52207 38.058622, -78.52222 38.058545, -78.52232 38.05848, -78.522388 38.058414, -78.522441 38.058334, -78.522482 38.05823, -78.522559 38.057753, -78.522583 38.057535, -78.522582 38.057452, -78.522562 38.057372, -78.522497 38.057238, -78.522466 38.057158, -78.522444 38.057055, -78.522436 38.056829, -78.522456 38.056513, -78.522489 38.056434, -78.522534 38.056375, -78.522636 38.056286, -78.522681 38.056255, -78.522737 38.056207, -78.522783 38.056154, -78.522827 38.056086, -78.522853 38.05602, -78.522886 38.055863, -78.5229 38.055658, -78.522893 38.055605, -78.52282 38.055427, -78.522812 38.055415, -78.522759 38.055336, -78.522699 38.055282, -78.522564 38.055205, -78.522366 38.055083, -78.522334 38.055055, -78.522296 38.054971, -78.522355 38.054962, -78.522408 38.054937, -78.52248 38.054883, -78.522741 38.054574, -78.522789 38.054503, -78.522827 38.054418, -78.522839 38.054341, -78.522843 38.054206, -78.522854 38.054141, -78.522887 38.054076, -78.52294 38.054016, -78.522996 38.053973, -78.523396 38.053744, -78.523509 38.053664, -78.52365 38.053542, -78.523807 38.053395, -78.524106 38.053165, -78.524207 38.053063, -78.524257 38.052995, -78.524304 38.052905, -78.524357 38.052737, -78.524453 38.052324, -78.524564 38.051763, -78.524693 38.050959, -78.525095 38.05074, -78.525597 38.050459, -78.525835 38.050337, -78.525955 38.050285, -78.526182 38.050196, -78.526401 38.050115, -78.528316 38.051777, -78.529296 38.052592, -78.529846 38.052988, -78.530552 38.05338, -78.531526 38.053798, -78.532357 38.054103, -78.532965 38.054324, -78.533543 38.054537, -78.533616 38.054564, -78.534305 38.054675, -78.534927 38.054729, -78.535509 38.054698, -78.53598 38.054604, -78.536641 38.054408, -78.537235 38.054154, -78.538053 38.053669, -78.538943 38.053171, -78.53993 38.052675, -78.540032 38.05264, -78.54077 38.052329, -78.541212 38.052156, -78.5417 38.051978, -78.542333 38.051786, -78.542938 38.051644, -78.543543 38.051528, -78.544215 38.051421, -78.544865 38.051368, -78.545531 38.051381, -78.545969 38.051432, -78.544496 38.051923, -78.544324 38.051968, -78.543889 38.052069, -78.543483 38.052139, -78.54315 38.052181, -78.54294 38.052228, -78.542561 38.052302, -78.542407 38.052313, -78.541889 38.052372, -78.541198 38.052441, -78.540926 38.052491, -78.540676 38.05252, -78.540402 38.052571, -78.540299 38.052603, -78.540218 38.052641, -78.54017 38.052679, -78.540132 38.052735, -78.54012 38.052775, -78.540261 38.052982, -78.540518 38.053387, -78.540857 38.053938, -78.541079 38.054391, -78.54155 38.054272, -78.541824 38.054199, -78.542497 38.054046, -78.542568 38.054024, -78.542672 38.053985, -78.542822 38.053918, -78.542915 38.053975, -78.543037 38.054033, -78.543096 38.054052, -78.543149 38.054052, -78.5432 38.054039, -78.54336 38.05394, -78.543385 38.053907, -78.543392 38.05387, -78.543381 38.053832, -78.543275 38.053694, -78.543245 38.053628, -78.543406 38.053662, -78.54347 38.05367, -78.543536 38.053668, -78.54363 38.05365, -78.54369 38.05362, -78.543739 38.053579, -78.543779 38.05352, -78.543805 38.053468, -78.543812 38.053412, -78.5438 38.053358, -78.543755 38.053282, -78.543803 38.053253, -78.544386 38.052965, -78.544422 38.052952, -78.544718 38.052868, -78.544885 38.052846, -78.544971 38.052845, -78.545079 38.052861, -78.545148 38.052883, -78.545229 38.052929, -78.545298 38.052988, -78.545353 38.053056, -78.54536 38.05308, -78.545397 38.053222, -78.545401 38.053285, -78.545389 38.053362, -78.545359 38.053437, -78.545025 38.053868, -78.544966 38.053917, -78.544591 38.054146, -78.544362 38.054297, -78.544007 38.054564, -78.544285 38.054565, -78.544418 38.054582, -78.544551 38.054615, -78.54471 38.054669, -78.544829 38.054724, -78.544963 38.054804, -78.545085 38.054896, -78.545136 38.054942, -78.545312 38.055121, -78.545448 38.055284, -78.545546 38.055376, -78.545747 38.055533, -78.545817 38.055582, -78.546044 38.055694, -78.546222 38.05576, -78.546356 38.055797, -78.546461 38.055813, -78.546632 38.055821, -78.546718 38.055812, -78.546869 38.055769, -78.546972 38.055727, -78.547151 38.055618, -78.547285 38.055522, -78.547481 38.05537, -78.548495 38.055556, -78.549022 38.055661, -78.549104 38.055674, -78.549298 38.055685, -78.549425 38.055674, -78.549473 38.055666, -78.549529 38.05565, -78.549703 38.055585, -78.549801 38.055538, -78.549838 38.055519, -78.549937 38.055457, -78.55004 38.055373, -78.550115 38.05528, -78.550204 38.055151, -78.550301 38.054983, -78.550538 38.054497, -78.550565 38.054453, -78.550637 38.054372, -78.550762 38.054263, -78.550834 38.054212, -78.550934 38.05416, -78.551042 38.054119, -78.551152 38.054086, -78.551267 38.054068, -78.551444 38.05406, -78.551835 38.054079, -78.552326 38.054134, -78.552493 38.054139, -78.552689 38.054128, -78.552997 38.054095, -78.553174 38.054045, -78.553383 38.053972, -78.553501 38.053919, -78.553794 38.053799, -78.553998 38.053726, -78.554208 38.053663, -78.554451 38.053606, -78.554569 38.053589, -78.554767 38.053572, -78.554993 38.053572, -78.555056 38.053586, -78.555104 38.053614, -78.555263 38.053772, -78.555313 38.053792, -78.555377 38.053794, -78.555433 38.053804, -78.555529 38.053831, -78.555663 38.053887, -78.555819 38.053916, -78.555967 38.053967, -78.555956 38.054071, -78.555967 38.054119, -78.55604 38.054235, -78.556108 38.054309, -78.556184 38.054368, -78.556583 38.054655, -78.556653 38.054726, -78.556709 38.054805, -78.556779 38.054943, -78.556821 38.055057, -78.556842 38.05515, -78.556854 38.055264, -78.556848 38.055373, -78.556823 38.05548, -78.55679 38.055563, -78.556725 38.055678, -78.556592 38.05583, -78.556471 38.055944, -78.556398 38.056007, -78.556991 38.056325, -78.55721 38.056437, -78.55767 38.056721, -78.557736 38.056763, -78.558066 38.057024, -78.558486 38.057321, -78.558826 38.057587, -78.559003 38.05771, -78.559044 38.057745, -78.559155 38.057824, -78.559209 38.057856, -78.559641 38.058091, -78.559764 38.058139, -78.560106 38.058289, -78.560314 38.058367, -78.560408 38.058406, -78.560557 38.05848, -78.560631 38.058544, -78.560804 38.058712, -78.560951 38.059006, -78.560993 38.059078, -78.56106 38.059162, -78.561156 38.059249, -78.561229 38.059309, -78.561296 38.059349, -78.56137 38.059382, -78.561468 38.059411, -78.561551 38.059425, -78.561678 38.059432, -78.561812 38.059424, -78.561977 38.059397, -78.56212 38.059383, -78.562288 38.059383, -78.562404 38.059401, -78.562494 38.059431, -78.56256 38.059464, -78.562613 38.059504, -78.562663 38.059561, -78.562702 38.059639, -78.562714 38.059694, -78.562714 38.059999, -78.562727 38.060157, -78.562762 38.06036, -78.562799 38.060462, -78.562889 38.060633, -78.563255 38.06039, -78.563406 38.060265, -78.563681 38.060016, -78.563891 38.059811, -78.56427 38.059373, -78.564319 38.059293, -78.564366 38.059162, -78.56439 38.059071, -78.564403 38.058955, -78.56439 38.058757, -78.564359 38.058599, -78.564309 38.058445, -78.564273 38.058369, -78.5642 38.058259, -78.564113 38.058157, -78.56394 38.057998, -78.563527 38.05767, -78.563383 38.057557, -78.563084 38.057345, -78.563537 38.057056, -78.563791 38.056907, -78.564015 38.056752, -78.564096 38.056685, -78.564245 38.056528, -78.564329 38.056467, -78.564547 38.056356, -78.564913 38.056198, -78.565179 38.05608, -78.565315 38.056012, -78.565384 38.05597, -78.565459 38.055907, -78.565484 38.055875, -78.565518 38.056077, -78.565578 38.056237, -78.565669 38.056419, -78.565737 38.056529, -78.565815 38.056635, -78.566097 38.056948, -78.566444 38.057391, -78.566558 38.057563, -78.566594 38.057616, -78.566619 38.057679, -78.566676 38.058054, -78.566686 38.05824, -78.566708 38.058407, -78.566754 38.058544, -78.56679 38.058618, -78.566843 38.058687, -78.567026 38.058849, -78.567108 38.058958, -78.5672 38.059112, -78.567257 38.059246, -78.567351 38.059526, -78.567363 38.059553, -78.56738 38.059583, -78.567403 38.059618, -78.567412 38.059555, -78.567502 38.059256, -78.567502 38.059195, -78.567481 38.059107, -78.567377 38.058943, -78.567259 38.058745, -78.567092 38.058569, -78.567036 38.058492, -78.567015 38.05841, -78.567002 38.058322, -78.567029 38.058114, -78.567022 38.058042, -78.567009 38.057976, -78.566918 38.057795, -78.566912 38.057648, -78.566911 38.057636, -78.566745 38.057455, -78.566703 38.057295, -78.566356 38.056905, -78.566265 38.056834, -78.566196 38.056763, -78.566154 38.056713, -78.566078 38.056593, -78.56605 38.056538, -78.565939 38.056389, -78.565849 38.056203, -78.565793 38.056, -78.565814 38.055807, -78.565835 38.055752, -78.565876 38.05567, -78.565988 38.055522, -78.56605 38.055428, -78.566189 38.055269, -78.56637 38.055082, -78.566432 38.055028, -78.566536 38.054918, -78.566675 38.054808, -78.566765 38.054713, -78.566225 38.054341, -78.565777 38.053994, -78.565273 38.053469, -78.564847 38.053077, -78.564276 38.052685, -78.563659 38.052383, -78.562976 38.052089, -78.562494 38.051929, -78.561542 38.051768, -78.560702 38.051742, -78.559984 38.051795, -78.559379 38.05192, -78.558371 38.052142, -78.557419 38.052347, -78.556657 38.052507, -78.555832 38.052702, -78.555307 38.052792, -78.554679 38.052846, -78.554046 38.052859, -78.553273 38.052855, -78.552506 38.052855, -78.550937 38.052846, -78.550404 38.052787, -78.550531 38.052184, -78.550541 38.052146, -78.550554 38.052048, -78.550549 38.05195, -78.550459 38.051551, -78.550377 38.051064, -78.550373 38.050832, -78.550228 38.050821, -78.54866 38.050737, -78.548058 38.050699, -78.547016 38.050654, -78.546469 38.050653, -78.546068 38.050668, -78.545698 38.050688, -78.545217 38.050735, -78.544878 38.050783, -78.544368 38.050872, -78.544029 38.050941, -78.543631 38.051036, -78.543309 38.051126, -78.542865 38.051275, -78.54237 38.051455, -78.541789 38.051674, -78.541575 38.051763, -78.541015 38.051993, -78.5399 38.052437, -78.539724 38.052506, -78.539342 38.052664, -78.539048 38.052768, -78.538748 38.052858, -78.538516 38.052909, -78.538305 38.052942, -78.538085 38.05296, -78.537863 38.052964, -78.537642 38.052954, -78.537394 38.052925, -78.537142 38.05288, -78.537042 38.052858, -78.536818 38.052792, -78.536531 38.052692, -78.536244 38.052579, -78.536103 38.052523, -78.535301 38.052204, -78.534587 38.051931, -78.53446 38.051883, -78.534082 38.051761, -78.533583 38.051615, -78.533096 38.051488, -78.533062 38.05148, -78.532735 38.051406, -78.532628 38.051382, -78.532295 38.051316, -78.531815 38.051221, -78.531356 38.051138, -78.529336 38.050754, -78.528998 38.050688, -78.528567 38.050588, -78.528194 38.050489, -78.528142 38.050475, -78.527785 38.05035, -78.527574 38.050269, -78.527157 38.050081, -78.527271 38.049943, -78.527354 38.049866, -78.527452 38.049802, -78.527576 38.049747, -78.527665 38.04972, -78.527735 38.049706, -78.527798 38.049698, -78.52798 38.049682, -78.528135 38.049676, -78.528294 38.049651, -78.528328 38.049644, -78.52847 38.049593, -78.528603 38.049528, -78.52873 38.049445, -78.528835 38.049349, -78.528899 38.049268, -78.529053 38.048985, -78.529163 38.048743, -78.529259 38.04849, -78.529535 38.047648, -78.529711 38.047082, -78.529741 38.046931, -78.529746 38.046883, -78.529775 38.046655, -78.529783 38.046438, -78.529778 38.046171, -78.529788 38.045917, -78.529793 38.04588, -78.529826 38.045739, -78.529849 38.045644, -78.530005 38.045253, -78.530069 38.045156, -78.530486 38.044685, -78.530572 38.044595, -78.530655 38.044508, -78.530929 38.044222, -78.531231 38.043905, -78.531885 38.043215, -78.532111 38.042972, -78.532505 38.042544, -78.532595 38.042432, -78.532842 38.042124, -78.532936 38.04198, -78.533078 38.041743, -78.533161 38.04157, -78.533268 38.041279, -78.533328 38.041071, -78.533355 38.040953, -78.533437 38.040598, -78.533469 38.040459, -78.533532 38.040224, -78.533556 38.040133, -78.533628 38.039862, -78.533674 38.039659, -78.533727 38.039481, -78.533825 38.0392, -78.533997 38.038776, -78.534122 38.038461, -78.534148 38.038395, -78.534263 38.038105, -78.534458 38.037611, -78.534623 38.037652, -78.534781 38.037707, -78.534895 38.037755, -78.534975 38.03778, -78.53507 38.03759, -78.535175 38.037332, -78.535355 38.036915, -78.535487 38.036558, -78.535522 38.036426, -78.535543 38.036261, -78.535522 38.036091, -78.535474 38.035932, -78.535453 38.035904, -78.535425 38.035877, -78.53539 38.035855, -78.535342 38.035833, -78.535265 38.035811, -78.535049 38.035787, -78.534995 38.035778, -78.534953 38.03575, -78.534918 38.035717, -78.534898 38.035652, -78.534912 38.03558, -78.534939 38.035525, -78.535036 38.035405, -78.535162 38.035262, -78.535231 38.035174, -78.535488 38.034889, -78.535627 38.034691, -78.535849 38.034296, -78.535919 38.034136, -78.535988 38.033944, -78.536024 38.033767, -78.536051 38.033379, -78.536065 38.033269, -78.5361 38.033159, -78.53619 38.032978, -78.536308 38.032775, -78.536371 38.032615, -78.536496 38.032231, -78.536551 38.031935, -78.536572 38.031693, -78.536579 38.031418, -78.536572 38.031182, -78.536559 38.031073, -78.53651 38.030886, -78.536455 38.030721, -78.536281 38.030408, -78.536253 38.030342, -78.53617 38.03032, -78.536101 38.030271, -78.535948 38.030117, -78.535823 38.030007, -78.535594 38.029848, -78.535476 38.029749, -78.535289 38.029623, -78.535115 38.029562, -78.534949 38.029535, -78.534837 38.029529, -78.53472 38.029535, -78.534581 38.029551, -78.534497 38.029535, -78.534324 38.029425, -78.534268 38.029403, -78.534178 38.029381, -78.534046 38.029375, -78.533914 38.029381, -78.533755 38.029425, -78.533553 38.029501, -78.533199 38.029693, -78.533125 38.029704, -78.533102 38.029704, -78.533081 38.029661, -78.533067 38.029617, -78.533081 38.029562, -78.533102 38.029523, -78.533539 38.029068, -78.53363 38.028936, -78.533665 38.028826, -78.533734 38.028519, -78.533748 38.028425, -78.533644 38.028392, -78.533436 38.028266, -78.533325 38.028167, -78.5332 38.028024, -78.533137 38.027931, -78.532999 38.027689, -78.532922 38.027541, -78.532784 38.027343, -78.532749 38.027299, -78.532645 38.02736, -78.532527 38.027415, -78.532478 38.02742, -78.532367 38.027393, -78.532304 38.027365, -78.53227 38.027349, -78.532069 38.027233, -78.531992 38.027167, -78.531923 38.027063, -78.531916 38.026986, -78.531923 38.026915, -78.532006 38.026777, -78.531923 38.026755, -78.531854 38.026728, -78.531722 38.026657, -78.531618 38.026591, -78.531157 38.02626, -78.530573 38.026405, -78.529756 38.026612, -78.529669 38.026639, -78.529499 38.02668, -78.529222 38.026735, -78.528978 38.026767, -78.528826 38.026782, -78.528742 38.026785, -78.52851 38.026794, -78.52861 38.026566, -78.528672 38.026426, -78.5288 38.026176, -78.528945 38.025933, -78.52916 38.025608, -78.529327 38.025386, -78.529598 38.025065, -78.529758 38.024894, -78.529798 38.024854, -78.52985 38.024806, -78.529861 38.024706, -78.529875 38.024661, -78.52998 38.024372, -78.529994 38.024268, -78.52998 38.024136, -78.529953 38.024042, -78.529918 38.023971, -78.529821 38.023834, -78.529698 38.023721, -78.529301 38.023268, -78.529127 38.023032, -78.528947 38.022812, -78.528877 38.022746, -78.528808 38.022691, -78.528725 38.022636, -78.528572 38.022559, -78.528426 38.02245, -78.528302 38.022345, -78.528191 38.022219, -78.528135 38.022142, -78.528086 38.022027, -78.528052 38.0219, -78.528017 38.021719, -78.52799 38.021521, -78.527948 38.021362, -78.527934 38.02134, -78.527816 38.021082, -78.527573 38.021153, -78.526583 38.021459, -78.526559 38.021381, -78.52655 38.02129, -78.526693 38.021138, -78.526815 38.021018, -78.526924 38.020889, -78.526951 38.020831, -78.52696 38.020769, -78.526951 38.020707, -78.526832 38.020314, -78.526831 38.02024, -78.526856 38.020169, -78.526894 38.020115, -78.527041 38.019979, -78.52716 38.019848, -78.527203 38.019822, -78.527244 38.019811, -78.527276 38.019809, -78.527383 38.019817, -78.527432 38.019819, -78.527532 38.01966, -78.527574 38.01955, -78.527588 38.019501, -78.527553 38.019402, -78.527512 38.019363, -78.527484 38.019347, -78.527449 38.019347, -78.527401 38.019358, -78.527165 38.019435, -78.526963 38.019468, -78.526817 38.019478, -78.526568 38.019462, -78.526394 38.019423, -78.526262 38.019379, -78.526165 38.01933, -78.526116 38.019281, -78.526019 38.019165, -78.525978 38.019083, -78.525832 38.018698, -78.5257 38.018517, -78.52543 38.018308, -78.52536 38.018232, -78.52534 38.018193, -78.525326 38.01799, -78.525305 38.017957, -78.525222 38.017864, -78.525187 38.017803, -78.525159 38.017743, -78.525153 38.017671, -78.52516 38.017518, -78.525153 38.017397, -78.525125 38.017314, -78.52509 38.017254, -78.525021 38.017166, -78.524882 38.017062, -78.524743 38.01699, -78.524591 38.016864, -78.524438 38.016754, -78.524362 38.016754, -78.524265 38.016765, -78.52407 38.01682, -78.52389 38.016897, -78.523842 38.016924, -78.523738 38.016877, -78.523682 38.016838, -78.523897 38.016712, -78.524207 38.016569, -78.524277 38.016529, -78.524353 38.016469, -78.524416 38.0164, -78.524491 38.016293, -78.52471 38.015988, -78.525064 38.01557, -78.525376 38.015187, -78.52557 38.01496, -78.52579 38.014684, -78.525944 38.014466, -78.526088 38.014224, -78.526149 38.014104, -78.526336 38.013538, -78.526384 38.013353, -78.526402 38.013238, -78.526442 38.012951, -78.526511 38.012355, -78.526667 38.012398, -78.526763 38.012411, -78.527336 38.012449, -78.527737 38.012492, -78.527834 38.012506, -78.528005 38.012546, -78.528286 38.012611, -78.528421 38.012631, -78.528586 38.012637, -78.528724 38.012627, -78.528916 38.012592, -78.52905 38.012551, -78.529176 38.012497, -78.529247 38.012458, -78.529456 38.012297, -78.529637 38.012148, -78.529743 38.012066, -78.52986 38.011996, -78.529992 38.011932, -78.530122 38.01189, -78.530231 38.011867, -78.530363 38.011854, -78.530718 38.011858, -78.530851 38.011856, -78.530999 38.011841, -78.531143 38.011811, -78.531254 38.011777, -78.53143 38.011702, -78.531613 38.011608, -78.531573 38.011547, -78.531461 38.011352, -78.531354 38.011102, -78.531271 38.010846, -78.531063 38.009697, -78.530881 38.008679, -78.530831 38.008401, -78.530806 38.008299, -78.53076 38.008175, -78.530681 38.008032, -78.530594 38.007913, -78.530489 38.007797, -78.53037 38.00769, -78.530237 38.007593, -78.530122 38.007525, -78.529969 38.007453, -78.52984 38.007404, -78.529672 38.007356, -78.529494 38.00732, -78.529447 38.007311, -78.529298 38.007297, -78.529149 38.007298, -78.528959 38.007313, -78.528797 38.007338, -78.528578 38.007392, -78.528358 38.007462, -78.528205 38.007499, -78.528039 38.007526, -78.5279 38.00754, -78.527674 38.007543, -78.527475 38.00753, -78.527348 38.00749, -78.527538 38.006974, -78.527752 38.006271, -78.527803 38.006327, -78.527856 38.006365, -78.527897 38.006382, -78.527921 38.006389, -78.528233 38.00644, -78.528768 38.006507, -78.528952 38.006525, -78.529092 38.006532, -78.529772 38.006538, -78.529984 38.006546, -78.530513 38.006604, -78.530625 38.006599, -78.530672 38.006588, -78.530912 38.006629, -78.531199 38.006669, -78.531458 38.006675, -78.531537 38.006667, -78.531633 38.006644, -78.531741 38.006599, -78.532039 38.006367, -78.532109 38.006286, -78.532173 38.006179, -78.532195 38.006123, -78.532544 38.005396, -78.532641 38.005229, -78.532757 38.005069, -78.532807 38.005007, -78.533035 38.004792, -78.533178 38.004668, -78.5336 38.004334, -78.533697 38.004278, -78.533803 38.004234, -78.533956 38.004188, -78.534085 38.004136, -78.534278 38.004027, -78.534392 38.003944, -78.534493 38.003851, -78.534526 38.003809, -78.534581 38.003718, -78.534682 38.003499, -78.534776 38.003336, -78.534917 38.003131, -78.535031 38.002996, -78.535133 38.002894, -78.535332 38.002765, -78.535658 38.002564, -78.53594 38.002364, -78.536144 38.002243, -78.536393 38.002119, -78.536243 38.002062, -78.535876 38.001925, -78.535728 38.00187, -78.535399 38.001801, -78.535235 38.00178, -78.534818 38.001789, -78.534728 38.001798, -78.534575 38.001813, -78.534242 38.001839, -78.533922 38.001831, -78.533817 38.001818, -78.533315 38.001681, -78.533277 38.001647, -78.533227 38.001621, -78.533175 38.001584, -78.533096 38.001505, -78.533036 38.001453, -78.532978 38.001391, -78.532943 38.001354, -78.532907 38.001308, -78.532869 38.001256, -78.53283 38.001199, -78.532755 38.001071, -78.532717 38.001001, -78.53268 38.00093, -78.532642 38.000859, -78.532588 38.000761, -78.532562 38.000713, -78.532518 38.00064, -78.532469 38.000568, -78.532417 38.000496, -78.532362 38.000422, -78.532303 38.000352, -78.532241 38.00028, -78.532172 38.000221, -78.5321 38.000165, -78.532028 38.000115, -78.531955 38.000068, -78.531884 38.000018, -78.531819 37.999971, -78.531755 37.999926, -78.531637 37.999848, -78.531586 37.999817, -78.531539 37.999789, -78.531445 37.999741, -78.531413 37.999727, -78.531681 37.999407, -78.531739 37.999353, -78.531783 37.999319, -78.531826 37.999277, -78.53192 37.9992, -78.532024 37.999116, -78.532079 37.999072, -78.532137 37.999027, -78.5322 37.998988, -78.532265 37.998949, -78.532331 37.998904, -78.532465 37.998824, -78.532538 37.998783, -78.532613 37.998749, -78.532691 37.998711, -78.532769 37.998675, -78.532849 37.998644, -78.533003 37.998578, -78.533079 37.998539, -78.533157 37.998499, -78.533234 37.998456, -78.533384 37.998363, -78.53353 37.998263, -78.533601 37.998211, -78.533668 37.998152, -78.533791 37.998031, -78.533847 37.997969, -78.533902 37.997906, -78.533954 37.997839, -78.534046 37.997702, -78.534083 37.997632, -78.534119 37.997563, -78.534188 37.997417, -78.534239 37.997275, -78.534261 37.997199, -78.53428 37.997119, -78.534302 37.99687, -78.534293 37.996785, -78.534278 37.9967, -78.534262 37.996616, -78.534213 37.99645, -78.534097 37.996212, -78.534049 37.996136, -78.533899 37.99591, -78.533842 37.995848, -78.533782 37.995787, -78.533659 37.995675, -78.533596 37.995619, -78.533531 37.995564, -78.533462 37.995514, -78.533387 37.995464, -78.533219 37.995368, -78.533132 37.995323, -78.533042 37.995278, -78.532948 37.99523, -78.532758 37.99513, -78.532665 37.995077, -78.532492 37.994968, -78.532327 37.994851, -78.532251 37.994792, -78.53219 37.994744, -78.532176 37.994734, -78.532105 37.994676, -78.532036 37.994619, -78.531898 37.99449, -78.531824 37.99444, -78.531749 37.994381, -78.531669 37.994322, -78.531428 37.994147, -78.531345 37.994092, -78.531189 37.993985, -78.531005 37.993847, -78.530955 37.993815, -78.530887 37.993769, -78.530806 37.993708, -78.530678 37.993826, -78.530506 37.993957, -78.53035 37.994054, -78.530094 37.994185, -78.529891 37.99428, -78.529665 37.994387, -78.529516 37.994468, -78.52938 37.994561, -78.529246 37.994686, -78.529093 37.994852, -78.528869 37.995162, -78.528658 37.995504, -78.528654 37.995478, -78.528649 37.995375, -78.528656 37.99526, -78.528788 37.994876, -78.52885 37.994733, -78.529031 37.994436, -78.529329 37.993997, -78.529191 37.993816, -78.529031 37.99369, -78.528761 37.993558, -78.528525 37.993377, -78.528462 37.993344, -78.528379 37.993322, -78.527935 37.993349, -78.527782 37.993376, -78.527383 37.993547, -78.527297 37.993585, -78.526922 37.993826, -78.526741 37.993958, -78.526353 37.994304, -78.525957 37.994573, -78.525249 37.994803, -78.52511 37.994814, -78.524805 37.994775, -78.524513 37.994764, -78.524465 37.994748, -78.524229 37.994605, -78.524139 37.994563, -78.524007 37.994501, -78.523792 37.994347, -78.523744 37.99433, -78.523584 37.994314, -78.523362 37.994226, -78.523279 37.994204, -78.523084 37.994182, -78.522911 37.994187, -78.522835 37.994407, -78.522744 37.994533, -78.522668 37.994703, -78.522612 37.994758, -78.522508 37.994824, -78.52239 37.994873, -78.522325 37.994886, -78.522307 37.99489, -78.522196 37.99489, -78.521925 37.994857, -78.521884 37.994862, -78.521863 37.99489, -78.521849 37.994939, -78.521904 37.995098, -78.521897 37.995186, -78.521876 37.995247, -78.521856 37.995258, -78.521661 37.995186, -78.521523 37.995164, -78.52137 37.995202, -78.521314 37.995296, -78.52137 37.995472, -78.52137 37.995504, -78.521356 37.995532, -78.521161 37.995708, -78.520925 37.996092, -78.520773 37.996256, -78.52037 37.996654, -78.520349 37.996674, -78.520286 37.996761, -78.520245 37.996987, -78.520203 37.997047, -78.520134 37.997091, -78.519884 37.99714, -78.51987 37.997162, -78.519856 37.997277, -78.519821 37.997404, -78.519849 37.997536, -78.519828 37.997618, -78.519814 37.997634, -78.519724 37.997667, -78.519467 37.997695, -78.519391 37.997722, -78.519342 37.99775, -78.519314 37.997777, -78.51928 37.997854, -78.519196 37.998134, -78.519161 37.998189, -78.519016 37.99832, -78.519029 37.998419, -78.519106 37.998551, -78.519113 37.998617, -78.519085 37.998727, -78.51906 37.998784, -78.519036 37.998837, -78.518967 37.998924, -78.518904 37.998985, -78.518668 37.999122, -78.518404 37.999232, -78.517884 37.999512, -78.517335 37.999742, -78.51703 37.999813, -78.516815 37.999835, -78.516662 37.999829, -78.516565 37.999807, -78.516253 37.999643, -78.515892 37.999544, -78.515726 37.999522, -78.515601 37.999494, -78.515524 37.999466, -78.515277 37.999299, -78.515143 37.999208, -78.514852 37.999065, -78.514442 37.998911, -78.514123 37.998829, -78.513964 37.998763, -78.513547 37.998516, -78.513381 37.998439, -78.513235 37.998389, -78.513138 37.998373, -78.512985 37.998367, -78.512992 37.998312, -78.513077 37.998139, -78.513131 37.998032, -78.513173 37.997857, -78.513187 37.997703, -78.51318 37.997626, -78.513166 37.997598, -78.513076 37.997565, -78.513007 37.997554, -78.512896 37.99751, -78.512625 37.997368, -78.512501 37.997324, -78.512327 37.997302, -78.512056 37.997307, -78.512005 37.997289, -78.511696 37.99718, -78.511612 37.997164, -78.511175 37.997164, -78.510592 37.997185, -78.510502 37.997174, -78.510398 37.997136, -78.510322 37.997086, -78.51028 37.997031, -78.510274 37.996839, -78.51026 37.996806, -78.510183 37.996751, -78.509642 37.996526, -78.509365 37.996432, -78.508775 37.996267, -78.508727 37.996234, -78.508643 37.996383, -78.508615 37.996399, -78.508574 37.996405, -78.508477 37.996421, -78.508255 37.996536, -78.508192 37.996586, -78.508039 37.996772, -78.508018 37.996783, -78.507998 37.996767, -78.507991 37.996728, -78.508012 37.996558, -78.507998 37.996459, -78.507956 37.996382, -78.507873 37.996283, -78.507804 37.996223, -78.507721 37.996168, -78.507616 37.996119, -78.507477 37.996098, -78.507394 37.996086, -78.507311 37.996091, -78.5072 37.996118, -78.507172 37.996113, -78.507138 37.99602, -78.507119 37.995687, -78.507117 37.995652, -78.50709 37.995553, -78.507027 37.995443, -78.506937 37.995317, -78.506847 37.995212, -78.506722 37.995091, -78.506632 37.995064, -78.506597 37.995042, -78.506618 37.995009, -78.506986 37.994916, -78.507118 37.994894, -78.507382 37.994872, -78.507506 37.994812, -78.50759 37.994751, -78.507659 37.994664, -78.507812 37.994323, -78.507972 37.994115, -78.508167 37.993741, -78.508264 37.993659, -78.508403 37.993368, -78.508444 37.993302, -78.508535 37.99322, -78.508583 37.993198, -78.50954 37.992791, -78.509725 37.992734, -78.509829 37.992714, -78.50997 37.992697, -78.510033 37.992695, -78.510171 37.992697, -78.510701 37.99273, -78.511229 37.992762, -78.511367 37.992754, -78.511503 37.99273, -78.511787 37.992647, -78.51189 37.992621, -78.511939 37.992617, -78.512063 37.992615, -78.512249 37.992632, -78.512264 37.992562, -78.512292 37.992489, -78.512343 37.992402, -78.51244 37.992286, -78.51256 37.992177, -78.512803 37.992005, -78.513081 37.991819, -78.513633 37.99143, -78.513913 37.991215, -78.514 37.991139, -78.514095 37.991036, -78.514154 37.990952, -78.514211 37.990852, -78.514262 37.990732, -78.514293 37.990615, -78.514304 37.990495, -78.514297 37.99034, -78.514265 37.990184, -78.514227 37.990074, -78.514172 37.989969, -78.514073 37.989829, -78.513972 37.989718, -78.513883 37.989642, -78.513761 37.989558, -78.513655 37.989499, -78.513481 37.989423, -78.512502 37.989082, -78.511607 37.988782, -78.511368 37.988722, -78.511202 37.988692, -78.510973 37.988663, -78.510811 37.988636, -78.510719 37.988611, -78.510604 37.988572, -78.510467 37.988509, -78.510212 37.988363, -78.510093 37.988294, -78.509848 37.988161, -78.509753 37.988117, -78.509668 37.98808, -78.509478 37.98801, -78.509373 37.987981, -78.509232 37.987954, -78.509144 37.987945, -78.509088 37.987942, -78.508925 37.987946, -78.508735 37.987966, -78.508549 37.988002, -78.508381 37.988061, -78.508218 37.988129, -78.507737 37.988374, -78.507411 37.988541, -78.507117 37.988692, -78.506659 37.988926, -78.506476 37.989016, -78.506344 37.989072, -78.506153 37.989135, -78.505994 37.989171, -78.50584 37.989191, -78.505716 37.989197, -78.505622 37.989196, -78.505468 37.989177, -78.505222 37.989121, -78.505356 37.988895, -78.505441 37.988727, -78.505515 37.988555, -78.505566 37.98839, -78.505587 37.988289, -78.50561 37.988113, -78.505634 37.987779, -78.505637 37.987666, -78.505658 37.986666, -78.505658 37.98644, -78.505655 37.986257, -78.50563 37.985972, -78.505555 37.985428, -78.505542 37.985236, -78.505547 37.985045, -78.505575 37.98473, -78.505602 37.984502, -78.505648 37.984122, -78.505714 37.983598, -78.50576 37.98322, -78.505795 37.983023, -78.505841 37.982881, -78.505904 37.982744, -78.505984 37.982612, -78.506083 37.982479, -78.506199 37.982352, -78.506303 37.982258, -78.506466 37.982099, -78.506507 37.982037, -78.506531 37.981969, -78.506536 37.981913, -78.506519 37.981851, -78.506485 37.981794, -78.506436 37.981745, -78.506333 37.981678, -78.505961 37.982055, -78.505354 37.982691, -78.505132 37.98294, -78.504709 37.983436, -78.503922 37.984363, -78.503672 37.984663, -78.503522 37.984846, -78.503305 37.985128, -78.503113 37.985392, -78.502425 37.98639, -78.502173 37.986752, -78.502073 37.986887, -78.501995 37.986984, -78.501905 37.987097, -78.501679 37.987354, -78.500994 37.988102, -78.500854 37.988256, -78.500412 37.988732, -78.50026 37.988876, -78.500054 37.98905, -78.499916 37.989153, -78.499756 37.98926, -78.499064 37.98969, -78.498964 37.989752, -78.49866 37.989964, -78.498394 37.990164, -78.498066 37.990424, -78.497698 37.990692, -78.49739 37.990897, -78.497208 37.991001, -78.497006 37.991096, -78.496912 37.991136, -78.496804 37.991178, -78.496545 37.991269, -78.496431 37.991302, -78.495479 37.991576, -78.49502 37.991703, -78.494826 37.991755, -78.494523 37.991826, -78.494257 37.991881, -78.493521 37.992023, -78.492663 37.992201, -78.491103 37.992559, -78.490679 37.992666, -78.490304 37.992778, -78.489948 37.992915, -78.489687 37.993026, -78.489398 37.993172, -78.488955 37.993433, -78.488926 37.99345, -78.488366 37.993797, -78.48808 37.993966, -78.487191 37.99448, -78.486994 37.994615, -78.48686 37.994726, -78.486741 37.994846, -78.486676 37.994923, -78.48659 37.99504, -78.486547 37.995111, -78.48647 37.995235, -78.486343 37.995485, -78.486088 37.996025, -78.485399 37.997494, -78.485064 37.998198, -78.485041 37.998241, -78.485026 37.998269, -78.485001 37.998312, -78.484927 37.998431, -78.484837 37.998545, -78.484731 37.99865, -78.484604 37.998749, -78.484395 37.998891, -78.484146 37.999039, -78.48354 37.999367, -78.483398 37.99946, -78.483311 37.999525, -78.483191 37.999634, -78.483099 37.99974, -78.482989 37.999895, -78.482914 38.000023, -78.482624 38.000675, -78.482524 38.000874, -78.482482 38.000958, -78.482297 38.00129, -78.48198 38.001862, -78.481769 38.002258, -78.481619 38.002554, -78.481396 38.002935, -78.481282 38.003154, -78.481161 38.003333, -78.480922 38.003756, -78.480833 38.003915, -78.480676 38.00418, -78.480328 38.004785, -78.480186 38.005045, -78.479978 38.005411, -78.479667 38.005933, -78.479001 38.007015, -78.478889 38.007202, -78.478575 38.007722, -78.476636 38.010967, -78.476567 38.010987, -78.476358 38.011041, -78.47628 38.011062, -78.476049 38.011121, -78.474709 38.011507, -78.472415 38.012156, -78.472231 38.012208, -78.471996 38.012274, -78.470174 38.011908, -78.469504 38.011745, -78.468992 38.013122, -78.468821 38.01317, -78.468398 38.013293, -78.467603 38.013514, -78.467344 38.013591, -78.466519 38.013819, -78.466114 38.013938, -78.464937 38.014269, -78.463856 38.014551, -78.46359 38.014613, -78.463008 38.014752, -78.461601 38.015053, -78.460524 38.015257, -78.459333 38.015449, -78.45787 38.015649, -78.457178 38.015736, -78.456374 38.015845, -78.455799 38.015916, -78.453421 38.01623, -78.451605 38.016462, -78.451333 38.0165, -78.450766 38.016572, -78.449964 38.01668, -78.450744 38.016876, -78.45097 38.016999, -78.451309 38.017168, -78.451656 38.017368, -78.452022 38.017714, -78.452372 38.018084, -78.45249 38.01821, -78.452895 38.018582, -78.453241 38.01884, -78.453648 38.01909, -78.454078 38.019357, -78.453906 38.019425, -78.45385 38.019431, -78.453627 38.019413, -78.453469 38.019359, -78.452358 38.018679, -78.451977 38.018415, -78.45147 38.018168, -78.451151 38.017949, -78.451012 38.017894, -78.450769 38.01785, -78.450554 38.01785, -78.450096 38.017888, -78.449839 38.017888, -78.449624 38.017855, -78.449325 38.017795, -78.449047 38.017663, -78.448798 38.017482, -78.448624 38.017319, -78.448402 38.017349, -78.44814 38.017385, -78.447697 38.017445, -78.44775 38.017526, -78.447794 38.017622, -78.44782 38.017722, -78.44788 38.018197, -78.447926 38.018373, -78.44799 38.018546, -78.448097 38.01876, -78.448214 38.018956, -78.448408 38.019237, -78.448481 38.019381, -78.448514 38.019476, -78.448514 38.019529, -78.448491 38.01959, -78.448445 38.019641, -78.448392 38.019674, -78.448343 38.019691, -78.447435 38.019862, -78.447347 38.019887, -78.447244 38.019931, -78.447013 38.020065, -78.446217 38.020576, -78.443804 38.019471, -78.442219 38.021667, -78.442114 38.021777, -78.441839 38.021743, -78.441287 38.021619, -78.441201 38.021606, -78.440479 38.021526, -78.440222 38.021597, -78.44009 38.021648, -78.439915 38.021737, -78.439797 38.021809, -78.43965 38.021922, -78.439467 38.022081, -78.439089 38.022399, -78.438859 38.022617, -78.438728 38.02273, -78.438568 38.022844, -78.438469 38.022901, -78.438304 38.022979, -78.438155 38.023033, -78.438031 38.023066, -78.437984 38.023074, -78.437837 38.0231, -78.437737 38.023111, -78.437569 38.023118, -78.4374 38.02311, -78.437311 38.023097, -78.436945 38.023047, -78.436662 38.023018, -78.436484 38.023007, -78.43634 38.023008, -78.436189 38.023018, -78.436124 38.023026, -78.435944 38.023058, -78.435749 38.02311, -78.435599 38.023162, -78.43545 38.023225, -78.435346 38.023282, -78.435172 38.023403, -78.435043 38.023508, -78.434698 38.02379, -78.434771 38.023837, -78.434628 38.023953, -78.434395 38.024149, -78.43415 38.024413, -78.434072 38.024511, -78.434039 38.024571, -78.434027 38.02462, -78.434028 38.024672, -78.434053 38.024765, -78.434133 38.024992, -78.434204 38.025248, -78.434227 38.025395, -78.434224 38.02555, -78.434206 38.025661, -78.434123 38.025939, -78.434075 38.026029, -78.434025 38.026096, -78.433667 38.026444, -78.433548 38.026549, -78.433498 38.026576, -78.433446 38.026588, -78.433413 38.026582, -78.433401 38.026606, -78.433366 38.02665, -78.433328 38.026679, -78.433263 38.026709, -78.433174 38.026733, -78.432987 38.026752, -78.432827 38.026757, -78.432511 38.026759, -78.432465 38.026763, -78.432404 38.026777, -78.432335 38.026805, -78.432139 38.026926, -78.432039 38.02697, -78.431953 38.026995, -78.431471 38.027086, -78.431066 38.027177, -78.430491 38.028127, -78.4296 38.027495, -78.428009 38.026367, -78.425294 38.024279, -78.425246 38.024464, -78.425231 38.024541, -78.425169 38.024986, -78.425101 38.025307, -78.424989 38.025756, -78.424969 38.025885, -78.424963 38.026007, -78.424977 38.026124, -78.425009 38.026253, -78.425133 38.02661, -78.425356 38.027212, -78.425485 38.027558, -78.425547 38.027737, -78.424505 38.028143, -78.423856 38.028401, -78.423175 38.028679, -78.422329 38.028994, -78.421362 38.029384, -78.421178 38.029463, -78.421178 38.029616, -78.421254 38.030319, -78.42122 38.030434, -78.42097 38.030752, -78.420858 38.030923, -78.420817 38.03112, -78.420775 38.031406, -78.420754 38.031675, -78.42065 38.032317, -78.420566 38.032504, -78.420546 38.03269, -78.420504 38.032921, -78.420518 38.033093, -78.420629 38.033404, -78.420656 38.033657, -78.420566 38.033849, -78.420441 38.033992, -78.420288 38.034113, -78.420115 38.034272, -78.420011 38.034321, -78.419629 38.034612, -78.419497 38.034788, -78.419441 38.034925, -78.419344 38.035227, -78.41933 38.035403, -78.419365 38.035683, -78.419427 38.035957, -78.419469 38.036281, -78.419454 38.036551, -78.419318 38.036762, -78.419142 38.036993, -78.418919 38.0371, -78.418763 38.037158, -78.418656 38.037267, -78.416286 38.040134, -78.417296 38.040196, -78.418043 38.040264, -78.418304 38.040287, -78.419 38.040133, -78.419434 38.040035, -78.420488 38.039861, -78.421128 38.039708, -78.421316 38.039698, -78.421348 38.039666, -78.421354 38.039644, -78.42135 38.039618, -78.421267 38.039435, -78.421252 38.039357, -78.421252 38.03931, -78.421311 38.039031, -78.421401 38.038787, -78.421507 38.038596, -78.421587 38.038492, -78.421676 38.038405, -78.421778 38.03832, -78.421833 38.03828, -78.422076 38.038134, -78.422193 38.038082, -78.422267 38.038056, -78.422595 38.037959, -78.422749 38.037908, -78.422815 38.037882, -78.422892 38.037852, -78.423113 38.037752, -78.423306 38.037637, -78.42358 38.037446, -78.423635 38.037406, -78.423818 38.037252, -78.423941 38.03713, -78.424037 38.037028, -78.424086 38.036975, -78.424106 38.036955, -78.424211 38.036821, -78.424309 38.036666, -78.424384 38.036522, -78.424509 38.036214, -78.424536 38.036109, -78.424553 38.035981, -78.42455 38.035846, -78.424549 38.035769, -78.42451 38.035489, -78.424147 38.034282, -78.424087 38.034046, -78.424073 38.033918, -78.424073 38.033833, -78.424093 38.033668, -78.424106 38.033616, -78.424124 38.033565, -78.424177 38.033457, -78.424329 38.033206, -78.424528 38.033244, -78.42465 38.033254, -78.424752 38.03325, -78.424831 38.033235, -78.425241 38.033124, -78.425362 38.0331, -78.425453 38.033089, -78.425524 38.033088, -78.425713 38.033094, -78.425897 38.033116, -78.426534 38.03324, -78.426677 38.033258, -78.426785 38.033264, -78.427091 38.033249, -78.427301 38.033224, -78.427411 38.033197, -78.427716 38.033084, -78.427958 38.032977, -78.428376 38.032771, -78.42854 38.032702, -78.428757 38.032618, -78.429229 38.032477, -78.429471 38.032426, -78.429618 38.032402, -78.43001 38.032374, -78.430256 38.03239, -78.430349 38.032397, -78.431083 38.032497, -78.431205 38.032508, -78.431696 38.032511, -78.43171 38.032511, -78.431961 38.032496, -78.43238 38.032449, -78.432693 38.032404, -78.433228 38.032335, -78.433657 38.032287, -78.433848 38.032253, -78.433996 38.032215, -78.43417 38.032154, -78.434303 38.032097, -78.434457 38.032013, -78.43468 38.031862, -78.434743 38.031813, -78.434786 38.031801, -78.434821 38.0318, -78.434844 38.031804, -78.434965 38.031665, -78.43537 38.031992, -78.436915 38.033111, -78.437083 38.033217, -78.435853 38.033866, -78.431782 38.036017, -78.431875 38.036057, -78.432033 38.036157, -78.434714 38.036179, -78.435867 38.036424, -78.43611 38.036617, -78.436458 38.03679, -78.436699 38.036851, -78.437206 38.036881, -78.439083 38.037922, -78.439274 38.03812, -78.439517 38.038378, -78.440124 38.039483, -78.440953 38.039618, -78.442057 38.039508, -78.443188 38.039059, -78.443284 38.039019, -78.444114 38.038677, -78.444766 38.03877, -78.44607 38.038972, -78.446525 38.039042, -78.447452 38.039435, -78.452261 38.041139, -78.452288 38.041038, -78.452303 38.040922, -78.452297 38.040823, -78.452272 38.040727, -78.452231 38.040635, -78.452173 38.040548, -78.452074 38.040443, -78.451978 38.040371, -78.451905 38.040332, -78.451295 38.040106, -78.450181 38.039709, -78.449874 38.039593, -78.4485 38.039103, -78.448317 38.039028, -78.448098 38.03892, -78.448312 38.038787, -78.448927 38.038449, -78.45013 38.037816, -78.452402 38.036645, -78.452871 38.036403, -78.453212 38.036214, -78.45342 38.036079, -78.453616 38.035932, -78.453837 38.035742, -78.454008 38.035573, -78.454206 38.035356, -78.454359 38.035165, -78.454497 38.034967, -78.454605 38.034787, -78.455049 38.033945, -78.455066 38.03392, -78.455096 38.033851, -78.455292 38.033853, -78.456084 38.033849, -78.456394 38.033867, -78.456678 38.033899, -78.456946 38.033945, -78.457169 38.033992, -78.457386 38.034054, -78.457588 38.034129, -78.457921 38.034259, -78.458132 38.034359, -78.458294 38.034436, -78.458344 38.034458, -78.458489 38.034533, -78.458535 38.034559, -78.457815 38.034935, -78.457239 38.035206, -78.456492 38.035904, -78.456392 38.035991, -78.455949 38.036419, -78.455495 38.036862, -78.45512 38.037269, -78.454662 38.037829, -78.454401 38.038356, -78.454092 38.039243, -78.453978 38.040141, -78.454015 38.041048, -78.454141 38.041787, -78.454241 38.041972, -78.45441 38.042568, -78.454631 38.043084, -78.454795 38.043394, -78.45497 38.043686, -78.455067 38.043869, -78.455102 38.044148, -78.454989 38.044611, -78.45487 38.045105, -78.454835 38.045232, -78.454738 38.045473, -78.454488 38.045682, -78.454406 38.045723, -78.454307 38.045775, -78.453933 38.045907, -78.453467 38.045968, -78.452259 38.045946, -78.451773 38.045924, -78.451426 38.04588, -78.450947 38.045792, -78.450301 38.045628, -78.449551 38.045419, -78.449294 38.045381, -78.449072 38.045386, -78.448649 38.045534, -78.447899 38.045853, -78.4476 38.046062, -78.447468 38.046248, -78.447274 38.046704, -78.447225 38.046891, -78.446962 38.047747, -78.446781 38.048252, -78.446531 38.049153, -78.446517 38.049345, -78.446531 38.049576, -78.446698 38.04985, -78.446858 38.050059, -78.447024 38.050355, -78.447288 38.050855, -78.447337 38.05102, -78.447358 38.051657, -78.447413 38.052036, -78.447504 38.052277, -78.447525 38.052436, -78.447545 38.053145, -78.44758 38.053342, -78.447684 38.053688, -78.447733 38.053891, -78.447754 38.054029, -78.447743 38.054182, -78.44774 38.054226, -78.447747 38.054512, -78.447733 38.055253, -78.447685 38.055972, -78.447775 38.056571, -78.447858 38.056862, -78.448004 38.057208, -78.448122 38.057416, -78.448276 38.057645, -78.44838 38.057792, -78.44866 38.05809, -78.449038 38.058484, -78.449168 38.058645, -78.449326 38.058851, -78.449228 38.058919, -78.449214 38.058929, -78.449415 38.059236, -78.449526 38.059397, -78.450123 38.060028, -78.450762 38.060484, -78.450945 38.060527, -78.451161 38.060578, -78.451571 38.060819, -78.451738 38.061097, -78.45162 38.061588, -78.451495 38.061715, -78.451366 38.06206, -78.451407 38.062127, -78.451449 38.062193, -78.45113 38.062446, -78.450762 38.062769, -78.450505 38.062995, -78.450004 38.063501, -78.449908 38.063599, -78.448818 38.064768, -78.448505 38.065065, -78.448262 38.065274, -78.447748 38.065625, -78.447491 38.065768, -78.447186 38.065911, -78.446033 38.066372, -78.445338 38.066679, -78.445095 38.066817, -78.443366 38.067366, -78.443171 38.067476, -78.443025 38.067596, -78.4429 38.067882, -78.442866 38.068091, -78.442852 38.068283, -78.4429 38.068662, -78.442906 38.068685, -78.442984 38.068964, -78.443081 38.069238, -78.443227 38.069562, -78.443262 38.06976, -78.443157 38.069968, -78.443042 38.070126, -78.443005 38.070177, -78.442762 38.070331, -78.441914 38.070765, -78.440838 38.071385, -78.440518 38.071528, -78.440289 38.07166, -78.439858 38.072049, -78.43974 38.072203, -78.439698 38.072472, -78.439705 38.072774, -78.439678 38.073285, -78.439754 38.073433, -78.440101 38.073938, -78.440449 38.074355, -78.440622 38.074432, -78.440796 38.074427, -78.440983 38.074465, -78.441456 38.074723, -78.441845 38.074883, -78.44256 38.075267, -78.442977 38.075476, -78.443887 38.076003, -78.444394 38.076283, -78.444679 38.076458, -78.445026 38.076656, -78.445512 38.076975, -78.445992 38.077255, -78.446207 38.077397, -78.446429 38.077573, -78.446659 38.077809, -78.446804 38.078029, -78.44695 38.078287, -78.447103 38.07871, -78.447173 38.079434, -78.447145 38.079775, -78.447082 38.080011, -78.446881 38.080357, -78.446645 38.080637, -78.446402 38.080977, -78.4462 38.081515, -78.44602 38.081894, -78.445895 38.082103, -78.445249 38.083195, -78.445242 38.083498, -78.445263 38.083717, -78.445207 38.083926, -78.444797 38.084475, -78.444721 38.084607, -78.444471 38.084848, -78.444241 38.084991, -78.443825 38.085277, -78.443047 38.085727, -78.442553 38.085875, -78.442303 38.086007, -78.442123 38.086155, -78.442025 38.086292, -78.441991 38.086424, -78.442011 38.086682, -78.442053 38.086907, -78.442296 38.087352, -78.442338 38.087445, -78.442594 38.087714, -78.442651 38.087775, -78.442928 38.088005, -78.443063 38.08813, -78.443022 38.088188, -78.442982 38.088247, -78.443212 38.088507, -78.443616 38.088873, -78.443589 38.089032, -78.443672 38.089329, -78.44372 38.089554, -78.443776 38.089998, -78.443894 38.090548, -78.443943 38.090894, -78.443943 38.091201, -78.443971 38.091519, -78.443943 38.091778, -78.443887 38.092019, -78.443727 38.092409, -78.443686 38.092568, -78.443735 38.092711, -78.443818 38.092892, -78.443978 38.093172, -78.444096 38.09332, -78.444214 38.093502, -78.444457 38.09382, -78.444853 38.094232, -78.444943 38.094353, -78.445367 38.095099, -78.445409 38.095242, -78.445381 38.095571, -78.445249 38.095912, -78.445221 38.096027, -78.445221 38.09634, -78.445326 38.096714, -78.445451 38.09695, -78.445562 38.097109, -78.445784 38.097372, -78.446173 38.097713, -78.446264 38.097823, -78.446457 38.098005, -78.446625 38.098163, -78.446952 38.098509, -78.44725 38.098707, -78.447549 38.098877, -78.447806 38.098948, -78.44807 38.098987, -78.448355 38.099003, -78.448564 38.09897, -78.448723 38.09891, -78.448925 38.098772, -78.449015 38.098751, -78.449043 38.098772, -78.448953 38.098877, -78.448939 38.098954, -78.449029 38.098981, -78.449418 38.098899, -78.449641 38.098772, -78.449995 38.098416, -78.45003 38.09835, -78.44996 38.098229, -78.449849 38.098185, -78.449807 38.098135, -78.449835 38.098064, -78.450127 38.097938, -78.450586 38.097696, -78.450947 38.097531, -78.451774 38.097246, -78.452114 38.097114, -78.45242 38.097059, -78.452927 38.09707, -78.453538 38.097185, -78.453872 38.097262, -78.454351 38.097465, -78.454796 38.097685, -78.455352 38.097932, -78.455811 38.098113, -78.456158 38.098261, -78.456387 38.098382, -78.45677 38.098552, -78.457235 38.098788, -78.459007 38.099787, -78.459264 38.099914, -78.460696 38.10055, -78.461349 38.100867, -78.461235 38.101011, -78.461138 38.101133, -78.460544 38.101882, -78.460009 38.102564, -78.459568 38.103126, -78.458603 38.10429, -78.457804 38.105297, -78.45751 38.105695, -78.4569 38.106619, -78.456598 38.107068, -78.456347 38.10716, -78.456129 38.107251, -78.455712 38.107404, -78.455663 38.107418, -78.455595 38.107429, -78.45549 38.107429, -78.455405 38.107414, -78.455341 38.107393, -78.455269 38.107355, -78.454821 38.106985, -78.454693 38.106859, -78.454581 38.106725, -78.454162 38.106115, -78.454094 38.10603, -78.454016 38.105914, -78.453951 38.10577, -78.453944 38.105723, -78.453946 38.105643, -78.453973 38.105541, -78.454028 38.1054, -78.454262 38.104996, -78.454284 38.104948, -78.454306 38.104864, -78.454312 38.104699, -78.454312 38.104666, -78.45432 38.10462, -78.454348 38.104545, -78.454419 38.104437, -78.45447 38.104335, -78.454507 38.104217, -78.454576 38.104039, -78.454621 38.103984, -78.454686 38.103943, -78.454784 38.103913, -78.455092 38.103849, -78.455156 38.103829, -78.455212 38.103798, -78.455264 38.103747, -78.455339 38.103618, -78.455457 38.103461, -78.455482 38.103406, -78.455484 38.103344, -78.455501 38.103324, -78.455523 38.103317, -78.455671 38.10335, -78.455713 38.103347, -78.455749 38.103331, -78.455774 38.103305, -78.455858 38.103087, -78.455938 38.102919, -78.456123 38.102608, -78.456247 38.102379, -78.456398 38.102079, -78.456125 38.102051, -78.455914 38.102019, -78.455297 38.101899, -78.45465 38.101764, -78.45441 38.101697, -78.454224 38.101633, -78.454037 38.101547, -78.453873 38.101455, -78.453781 38.101395, -78.453235 38.101015, -78.453054 38.100912, -78.452911 38.100847, -78.45274 38.100784, -78.452527 38.100721, -78.452309 38.100672, -78.45211 38.100639, -78.451856 38.100613, -78.451556 38.100598, -78.451001 38.100593, -78.450722 38.100594, -78.449872 38.100624, -78.449825 38.100626, -78.449478 38.100621, -78.449308 38.100611, -78.449174 38.1006, -78.448884 38.100563, -78.448305 38.100461, -78.4479 38.100385, -78.447345 38.100266, -78.44684 38.10014, -78.446411 38.100079, -78.445871 38.099907, -78.44555 38.099738, -78.445379 38.099669, -78.445225 38.099592, -78.445094 38.099512, -78.444952 38.09941, -78.444796 38.099275, -78.445114 38.099, -78.445265 38.098859, -78.445126 38.098745, -78.444896 38.098601, -78.44469 38.098483, -78.444497 38.098344, -78.444408 38.098299, -78.44433 38.098273, -78.444176 38.098234, -78.443854 38.098185, -78.443714 38.09815, -78.443477 38.098077, -78.443419 38.098074, -78.443363 38.098084, -78.443322 38.098101, -78.443296 38.098119, -78.443223 38.098193, -78.442938 38.097981, -78.442795 38.097894, -78.442642 38.097819, -78.44248 38.097756, -78.44164 38.0975, -78.441347 38.09742, -78.441143 38.097369, -78.441002 38.097346, -78.440819 38.09733, -78.440698 38.097329, -78.440644 38.097328, -78.440156 38.097351, -78.439771 38.097357, -78.439376 38.097353, -78.439145 38.097351, -78.438946 38.097366, -78.438788 38.097387, -78.438595 38.097426, -78.438253 38.097523, -78.438153 38.097551, -78.438 38.098057, -78.437833 38.098487, -78.437683 38.098813, -78.437661 38.098906, -78.437656 38.099, -78.437686 38.099173, -78.43769 38.099233, -78.437684 38.099314, -78.437661 38.099414, -78.437586 38.099593, -78.437499 38.09972, -78.43747 38.099796, -78.437459 38.099859, -78.437468 38.100007, -78.437489 38.100131, -78.437499 38.100288, -78.437489 38.100444, -78.43746 38.100544, -78.437414 38.100641, -78.437351 38.100731, -78.437274 38.100813, -78.437183 38.100886, -78.43708 38.100949, -78.436838 38.101058, -78.436675 38.101132, -78.436512 38.101193, -78.43631 38.101253, -78.436241 38.101263, -78.436079 38.101233, -78.436002 38.10123, -78.435889 38.101242, -78.435799 38.101268, -78.435716 38.101306, -78.435658 38.101228, -78.435598 38.101168, -78.435512 38.101101, -78.435137 38.1009, -78.435063 38.100847, -78.435006 38.100805, -78.43491 38.100722, -78.434834 38.100646, -78.435649 38.100295, -78.436638 38.099769, -78.436633 38.099706, -78.436557 38.099316, -78.43655 38.099151, -78.436508 38.098899, -78.436504 38.098888, -78.43646 38.098734, -78.436376 38.098613, -78.436251 38.098492, -78.436085 38.098361, -78.435966 38.09813, -78.435966 38.097993, -78.435883 38.097855, -78.435716 38.097647, -78.43564 38.097581, -78.435626 38.097488, -78.43566 38.097214, -78.435666 38.097191, -78.435599 38.097214, -78.435505 38.097241, -78.435338 38.097277, -78.434923 38.097328, -78.43484 38.097353, -78.434767 38.097391, -78.434704 38.09744, -78.434665 38.097486, -78.434558 38.097808, -78.434522 38.097859, -78.434479 38.097895, -78.434421 38.097924, -78.434357 38.097941, -78.43402 38.098015, -78.433124 38.098255, -78.432679 38.099318, -78.432613 38.099483, -78.432459 38.099872, -78.432249 38.100407, -78.431986 38.100966, -78.431805 38.101429, -78.431579 38.101925, -78.43134 38.102404, -78.431093 38.10278, -78.430813 38.103123, -78.430593 38.103379, -78.430138 38.103874, -78.429602 38.104385, -78.428948 38.105064, -78.42878 38.105235, -78.427998 38.107147, -78.428425 38.107265, -78.429633 38.108143, -78.429935 38.108362, -78.430538 38.10899, -78.430288 38.109116, -78.4301 38.109193, -78.429968 38.109325, -78.429933 38.109429, -78.429905 38.109687, -78.42994 38.109874, -78.430031 38.110094, -78.430058 38.110193, -78.430093 38.110604, -78.430086 38.110774, -78.42994 38.111027, -78.429884 38.111148, -78.429884 38.111219, -78.429927 38.111317, -78.42994 38.111346, -78.429933 38.11151, -78.429863 38.111669, -78.42983 38.111829, -78.429808 38.111938, -78.429842 38.112213, -78.430106 38.112921, -78.430085 38.113064, -78.430113 38.113141, -78.430363 38.113262, -78.430433 38.113322, -78.430419 38.113514, -78.43044 38.113597, -78.430468 38.113646, -78.430558 38.113723, -78.430544 38.113866, -78.430495 38.113921, -78.430085 38.11425, -78.429842 38.114459, -78.429766 38.114557, -78.429522 38.114684, -78.429314 38.114766, -78.428848 38.115205, -78.428688 38.115282, -78.428549 38.115381, -78.428431 38.11548, -78.428362 38.115513, -78.428097 38.115754, -78.427889 38.115979, -78.427423 38.116578, -78.427347 38.116743, -78.427173 38.116913, -78.427048 38.117066, -78.426923 38.117138, -78.426742 38.117308, -78.426603 38.117374, -78.426457 38.117659, -78.426443 38.117769, -78.426457 38.117868, -78.426366 38.117945, -78.426269 38.118066, -78.426186 38.118148, -78.426033 38.11845, -78.426012 38.118543, -78.426053 38.118818, -78.426005 38.118884, -78.425747 38.119158, -78.425621 38.119515, -78.425448 38.119905, -78.425365 38.120185, -78.425268 38.120361, -78.425163 38.120492, -78.425017 38.120706, -78.424926 38.12092, -78.423209 38.121863, -78.423081 38.121921, -78.422944 38.121964, -78.422801 38.121994, -78.422655 38.122006, -78.422435 38.122006, -78.421935 38.121969, -78.421394 38.121886, -78.42118 38.121846, -78.421014 38.121801, -78.420793 38.121726, -78.420081 38.121409, -78.420002 38.121491, -78.419876 38.121599, -78.419208 38.122213, -78.418843 38.122561, -78.418644 38.122752, -78.418521 38.122877, -78.418177 38.123202, -78.418091 38.12328, -78.417897 38.123445, -78.417348 38.123883, -78.416788 38.124338, -78.416395 38.124664, -78.41562 38.125268, -78.415524 38.125364, -78.415463 38.125447, -78.415446 38.125473, -78.415408 38.125601, -78.4154 38.12567, -78.415407 38.125765, -78.415433 38.125858, -78.415487 38.125946, -78.41551 38.125975, -78.41556 38.12603, -78.415647 38.126105, -78.415759 38.126177, -78.415908 38.126256, -78.415968 38.126285, -78.416115 38.126346, -78.416673 38.126517, -78.416954 38.126616, -78.417172 38.126705, -78.41737 38.126798, -78.417943 38.127082, -78.418341 38.127297, -78.418572 38.127445, -78.418912 38.127672, -78.41915 38.127854, -78.419277 38.127972, -78.419415 38.12813, -78.419966 38.128852, -78.42006 38.128971, -78.420198 38.129148, -78.420327 38.129302, -78.4205 38.129486, -78.420603 38.129572, -78.420755 38.129673, -78.420915 38.129757, -78.421095 38.129838, -78.4213 38.129917, -78.421378 38.129944, -78.421522 38.129984, -78.421887 38.130076, -78.422052 38.13011, -78.422508 38.130187, -78.423435 38.13031, -78.423822 38.130367, -78.42403 38.130415, -78.424277 38.130489, -78.424635 38.130622, -78.425617 38.131018, -78.425721 38.13105, -78.425842 38.131079, -78.426009 38.131106, -78.426178 38.131119, -78.426314 38.13112, -78.426665 38.131095, -78.426765 38.131088, -78.427037 38.131074, -78.427407 38.131065, -78.427882 38.131071, -78.428498 38.131093, -78.428711 38.131102, -78.429448 38.131137, -78.429644 38.131147, -78.430305 38.131191, -78.430464 38.131203, -78.430717 38.13123, -78.430924 38.131262, -78.431166 38.131302, -78.433125 38.131673, -78.433803 38.131787, -78.433676 38.132288, -78.433678 38.13236, -78.433706 38.132449, -78.433739 38.132498, -78.433774 38.132529, -78.433829 38.132558, -78.433972 38.132593, -78.433859 38.132993, -78.433794 38.133202, -78.43357 38.134058, -78.433416 38.134269, -78.433195 38.134572, -78.432173 38.13616, -78.430917 38.138131, -78.430468 38.138847, -78.429836 38.139833, -78.429464 38.140424, -78.428859 38.141359, -78.428348 38.142178, -78.428275 38.142291, -78.427887 38.14289, -78.427641 38.143278, -78.427277 38.143851, -78.427263 38.143873, -78.425718 38.146293, -78.4249 38.147586, -78.423982 38.149017, -78.423879 38.149181, -78.423584 38.149071, -78.423218 38.148934, -78.422961 38.149156, -78.422789 38.149245, -78.422682 38.149292, -78.422438 38.149381, -78.422155 38.149503, -78.420274 38.150457, -78.420241 38.150499, -78.42022 38.150526, -78.420192 38.150549, -78.420171 38.150576, -78.420025 38.150702, -78.419831 38.150833, -78.41974 38.150894, -78.419337 38.151103, -78.419163 38.15118, -78.418885 38.151268, -78.418593 38.151339, -78.418512 38.151353, -78.418468 38.151361, -78.418336 38.151372, -78.417897 38.151322, -78.417828 38.151328, -78.417515 38.151399, -78.417188 38.151465, -78.416903 38.151536, -78.416771 38.151564, -78.416604 38.151591, -78.416493 38.151602, -78.416166 38.151586, -78.415804 38.151602, -78.415617 38.151646, -78.415457 38.151717, -78.415387 38.151778, -78.415311 38.151876, -78.415262 38.15197, -78.415199 38.152195, -78.41513 38.152563, -78.415053 38.153315, -78.41506 38.153408, -78.415074 38.153485, -78.415227 38.153743, -78.415296 38.153886, -78.415352 38.154034, -78.415366 38.154199, -78.415393 38.154331, -78.415442 38.154451, -78.415491 38.15455, -78.415546 38.154611, -78.415581 38.154638, -78.41565 38.154666, -78.415859 38.154737, -78.416081 38.154836, -78.416248 38.154918, -78.41636 38.154968, -78.416415 38.154984, -78.416527 38.155034, -78.416575 38.155051, -78.41661 38.155045, -78.416652 38.155023, -78.416721 38.154951, -78.416749 38.154907, -78.41686 38.154605, -78.41687 38.154579, -78.41695 38.15462, -78.417017 38.154416, -78.417167 38.154184, -78.417953 38.153402, -78.418092 38.153265, -78.418126 38.153125, -78.418296 38.153004, -78.41833 38.152864, -78.418438 38.152807, -78.419136 38.151855, -78.419405 38.151712, -78.420028 38.151657, -78.421016 38.151837, -78.421472 38.152085, -78.421861 38.152379, -78.421798 38.152482, -78.421742 38.152438, -78.421728 38.152421, -78.421659 38.152372, -78.421353 38.152135, -78.421151 38.151998, -78.421061 38.151954, -78.420706 38.151811, -78.420581 38.151789, -78.419962 38.151756, -78.419663 38.151767, -78.419455 38.151789, -78.419357 38.151811, -78.419225 38.151883, -78.419135 38.151948, -78.41901 38.152091, -78.418898 38.152256, -78.418745 38.152453, -78.418669 38.15258, -78.418251 38.153123, -78.418071 38.153348, -78.418001 38.153425, -78.41789 38.153551, -78.417577 38.15387, -78.417375 38.154051, -78.41734 38.154095, -78.417153 38.154298, -78.417104 38.154386, -78.417069 38.154468, -78.417006 38.154648, -78.416965 38.15477, -78.416921 38.154876, -78.416895 38.15494, -78.416812 38.155094, -78.416742 38.155138, -78.416818 38.155231, -78.416916 38.15538, -78.41702 38.155506, -78.41709 38.155572, -78.417152 38.155649, -78.417159 38.155726, -78.417152 38.155901, -78.417159 38.156017, -78.417194 38.156099, -78.417242 38.156159, -78.417312 38.15622, -78.417368 38.156253, -78.417541 38.156335, -78.417583 38.156363, -78.417625 38.156418, -78.417632 38.156483, -78.417625 38.156555, -78.417583 38.156626, -78.417409 38.156873, -78.417367 38.156967, -78.417298 38.157197, -78.417291 38.157268, -78.417298 38.157345, -78.417312 38.157378, -78.417367 38.157455, -78.41743 38.157483, -78.417458 38.15751, -78.417492 38.157598, -78.417555 38.157686, -78.417687 38.157818, -78.417694 38.157873, -78.417687 38.157933, -78.41761 38.158114, -78.41745 38.158422, -78.417429 38.158449, -78.417374 38.158482, -78.417297 38.158504, -78.417084 38.158548, -78.417133 38.158601, -78.417279 38.158728, -78.417457 38.158859, -78.41753 38.158901, -78.417659 38.158962, -78.415009 38.163121, -78.414626 38.163716, -78.414541 38.163851, -78.414293 38.164245, -78.413059 38.166178, -78.412771 38.16663, -78.41231 38.167345, -78.411781 38.168185, -78.411662 38.168371, -78.410521 38.170164, -78.410064 38.170881, -78.410306 38.171, -78.41066 38.171142, -78.410918 38.17123, -78.411129 38.17129, -78.411268 38.171323, -78.411408 38.171342, -78.411553 38.171348, -78.411726 38.171336, -78.411866 38.17131, -78.411975 38.171279, -78.412188 38.171201, -78.412417 38.17111, -78.412539 38.17105, -78.412709 38.170956, -78.412801 38.170896, -78.412921 38.17079, -78.413068 38.170619, -78.41335 38.170158, -78.413415 38.170072, -78.41349 38.16999, -78.413619 38.169872, -78.413769 38.169758, -78.413906 38.169673, -78.414056 38.169604, -78.414137 38.169574, -78.414237 38.169546, -78.41434 38.169527, -78.414439 38.169514, -78.414782 38.169485, -78.415603 38.169418, -78.415908 38.169372, -78.41623 38.169305, -78.41664 38.1692, -78.416727 38.169174, -78.416764 38.169161, -78.416902 38.169115, -78.417157 38.169022, -78.417995 38.16868, -78.418684 38.168383, -78.418919 38.168265, -78.419169 38.168123, -78.419276 38.168046, -78.419375 38.167961, -78.419484 38.167843, -78.41964 38.16764, -78.419749 38.167475, -78.419872 38.167222, -78.419932 38.167052, -78.419993 38.166819, -78.420032 38.166647, -78.420112 38.166351, -78.420385 38.165586, -78.420424 38.165515, -78.420486 38.165433, -78.420564 38.165356, -78.4209 38.165083, -78.420989 38.165028, -78.421109 38.164978, -78.421268 38.164924, -78.421434 38.164884, -78.421747 38.16483, -78.421898 38.164821, -78.421989 38.164822, -78.422475 38.164893, -78.422598 38.164898, -78.423127 38.164852, -78.423385 38.164747, -78.42364 38.164623, -78.423724 38.16457, -78.423793 38.164507, -78.423827 38.164466, -78.424256 38.163852, -78.424428 38.16363, -78.424621 38.163413, -78.424591 38.163403, -78.42441 38.163359, -78.424313 38.16332, -78.424208 38.16326, -78.424076 38.163194, -78.423875 38.163079, -78.423805 38.163035, -78.423729 38.162963, -78.423666 38.162892, -78.423631 38.162788, -78.423638 38.162738, -78.423673 38.162645, -78.423708 38.162595, -78.423771 38.16248, -78.423805 38.162431, -78.42423 38.162013, -78.424292 38.161942, -78.42439 38.16175, -78.42448 38.161525, -78.424536 38.161289, -78.42464 38.160937, -78.424724 38.16074, -78.424814 38.160613, -78.42487 38.160542, -78.424967 38.160443, -78.425357 38.16013, -78.425496 38.160026, -78.425607 38.159949, -78.425753 38.159867, -78.425913 38.159795, -78.426066 38.159713, -78.426296 38.159537, -78.426532 38.159301, -78.42665 38.159142, -78.426713 38.159049, -78.426748 38.158983, -78.426796 38.158725, -78.426859 38.158483, -78.426977 38.158104, -78.427061 38.157769, -78.427089 38.157715, -78.427137 38.157665, -78.427339 38.157539, -78.427415 38.157484, -78.427527 38.157385, -78.427556 38.157347, -78.427617 38.15727, -78.427652 38.157182, -78.427687 38.157017, -78.427666 38.156924, -78.427638 38.156891, -78.427555 38.156831, -78.42745 38.15677, -78.427137 38.156611, -78.427075 38.156584, -78.427012 38.156545, -78.42688 38.156397, -78.426818 38.156314, -78.426776 38.156243, -78.426407 38.155754, -78.426206 38.155507, -78.425997 38.155332, -78.425879 38.155255, -78.425636 38.15515, -78.425309 38.155035, -78.425177 38.155013, -78.424593 38.154799, -78.424009 38.154607, -78.423765 38.154519, -78.423341 38.154326, -78.423049 38.154304, -78.422952 38.154288, -78.422841 38.15425, -78.422722 38.154173, -78.422611 38.15409, -78.422493 38.153964, -78.422423 38.153898, -78.422271 38.153635, -78.422236 38.153558, -78.422187 38.153382, -78.42218 38.153316, -78.422187 38.153256, -78.422215 38.15319, -78.422354 38.153014, -78.422361 38.152965, -78.422354 38.152921, -78.422257 38.152838, -78.422034 38.152674, -78.421994 38.152641, -78.422064 38.152532, -78.422097 38.152482, -78.422119 38.152447, -78.422603 38.151682, -78.423512 38.150243, -78.423679 38.149982, -78.42419 38.149184, -78.424237 38.149108, -78.424271 38.149054, -78.424458 38.148754, -78.425735 38.146759, -78.425986 38.146364, -78.427503 38.143987, -78.427714 38.143662, -78.427836 38.143463, -78.428484 38.142448, -78.428576 38.142303, -78.429366 38.141062, -78.429718 38.140517, -78.429938 38.140173, -78.430217 38.139721, -78.43064 38.139062, -78.430821 38.13878, -78.431436 38.137809, -78.431762 38.137317, -78.43197 38.136964, -78.432144 38.136687, -78.432386 38.136334, -78.432427 38.136267, -78.432565 38.136303, -78.432911 38.136351, -78.433585 38.136491, -78.433621 38.136506, -78.433718 38.136563, -78.433785 38.136623, -78.433946 38.13683, -78.434412 38.1375, -78.434513 38.137659, -78.434539 38.137742, -78.434548 38.137827, -78.43454 38.137912, -78.434453 38.138194, -78.43438 38.138612, -78.434369 38.13871, -78.434334 38.138873, -78.434284 38.139001, -78.434168 38.139237, -78.433918 38.139589, -78.433903 38.139637, -78.433906 38.139687, -78.433922 38.139726, -78.433958 38.139776, -78.43402 38.139824, -78.434069 38.139847, -78.434236 38.139906, -78.43429 38.13992, -78.434384 38.139931, -78.434606 38.139943, -78.434695 38.139937, -78.434803 38.139917, -78.434906 38.139883, -78.435364 38.139658, -78.435664 38.139462, -78.435787 38.139357, -78.435876 38.139267, -78.435913 38.139202, -78.435946 38.139116, -78.436 38.138817, -78.436011 38.138715, -78.436039 38.138615, -78.436167 38.138353, -78.436205 38.138291, -78.436283 38.138193, -78.436403 38.138082, -78.436659 38.13786, -78.436761 38.137752, -78.436871 38.137603, -78.436917 38.137519, -78.436963 38.137399, -78.436977 38.137316, -78.436973 38.137232, -78.436951 38.13715, -78.43673 38.136692, -78.436681 38.13658, -78.436617 38.136388, -78.436615 38.13632, -78.436622 38.136228, -78.436759 38.135632, -78.436783 38.135464, -78.436784 38.135377, -78.436766 38.135291, -78.43673 38.135208, -78.436692 38.135156, -78.436644 38.13511, -78.436572 38.13506, -78.436249 38.134904, -78.435664 38.134657, -78.43542 38.134558, -78.435372 38.134542, -78.435246 38.134513, -78.435086 38.134496, -78.434952 38.134493, -78.43472 38.134507, -78.434405 38.134528, -78.434309 38.134528, -78.434191 38.134514, -78.43399 38.134465, -78.433665 38.134358, -78.434013 38.133897, -78.434421 38.133409, -78.434899 38.132866, -78.4355 38.13225, -78.435553 38.132202, -78.435723 38.132286, -78.436114 38.1325, -78.435992 38.132694, -78.435906 38.132839, -78.436014 38.132922, -78.436134 38.132988, -78.436216 38.133017, -78.436313 38.133032, -78.436363 38.13303, -78.436408 38.133013, -78.436481 38.132953, -78.436657 38.132798, -78.437171 38.133083, -78.43727 38.133143, -78.437425 38.133252, -78.437626 38.133424, -78.437792 38.133578, -78.438016 38.133833, -78.438271 38.134153, -78.438415 38.134315, -78.438574 38.134468, -78.438618 38.1345, -78.438737 38.134574, -78.438866 38.134636, -78.439072 38.134705, -78.439353 38.134787, -78.439593 38.13499, -78.439746 38.135015, -78.439969 38.135057, -78.440243 38.135123, -78.440511 38.135221, -78.441052 38.135444, -78.44135 38.135583, -78.441591 38.135676, -78.441839 38.135757, -78.442123 38.135833, -78.442156 38.135842, -78.442969 38.136112, -78.44377 38.136364, -78.444089 38.136417, -78.444701 38.136503, -78.444721 38.13651, -78.444762 38.136524, -78.444844 38.136553, -78.445403 38.136753, -78.446003 38.137028, -78.446877 38.137431, -78.447042 38.137521, -78.44712 38.137631, -78.447047 38.137843, -78.446817 38.138157, -78.446683 38.138341, -78.446598 38.138474, -78.446508 38.138646, -78.446422 38.138856, -78.446378 38.138995, -78.446329 38.13922, -78.44614 38.140242, -78.446089 38.140496, -78.446055 38.140751, -78.446035 38.140978, -78.446027 38.141169, -78.446033 38.14132, -78.446076 38.14162, -78.446097 38.141711, -78.446165 38.141927, -78.446237 38.14209, -78.446326 38.142249, -78.446527 38.142562, -78.446719 38.142794, -78.446886 38.142975, -78.447279 38.143375, -78.447396 38.143515, -78.447477 38.143633, -78.447546 38.143753, -78.447595 38.143864, -78.447642 38.144007, -78.447666 38.144125, -78.447677 38.144277, -78.44767 38.144421, -78.447648 38.144552, -78.447612 38.14468, -78.447564 38.144806, -78.447492 38.144953, -78.447367 38.145162, -78.44718 38.145428, -78.447037 38.145601, -78.446881 38.145764, -78.447096 38.145932, -78.447248 38.146051, -78.447351 38.146148, -78.447551 38.146338, -78.447585 38.146393, -78.447601 38.146453, -78.4476 38.146515, -78.447582 38.14658, -78.44756 38.146628, -78.44732 38.147052, -78.446761 38.148086, -78.446679 38.148285, -78.446668 38.148372, -78.446679 38.148477, -78.446707 38.148561, -78.446773 38.148638, -78.446843 38.148693, -78.446891 38.14872, -78.447235 38.148874, -78.447653 38.149034, -78.448535 38.149306, -78.449007 38.149437, -78.449383 38.149178, -78.449814 38.149001, -78.44999 38.148632, -78.450183 38.148402, -78.450359 38.148286, -78.450644 38.148271, -78.451274 38.147118, -78.452526 38.14512, -78.454056 38.142531, -78.455254 38.140586, -78.455915 38.139649, -78.457345 38.137215, -78.456647 38.136811, -78.457169 38.135843, -78.457336 38.135547, -78.45743 38.135375, -78.458294 38.133856, -78.458313 38.133777, -78.458358 38.133655, -78.458485 38.133384, -78.458715 38.132926, -78.458897 38.13257, -78.459058 38.132269, -78.459162 38.132048, -78.459249 38.131822, -78.459276 38.131718, -78.459306 38.131542, -78.459318 38.131363, -78.459734 38.129975, -78.459912 38.12906, -78.46009 38.128069, -78.460041 38.128069, -78.459944 38.128006, -78.45988 38.127879, -78.45988 38.12779, -78.460025 38.127676, -78.46017 38.127638, -78.460267 38.12765, -78.460509 38.126482, -78.460606 38.126202, -78.460735 38.125872, -78.460978 38.125466, -78.461284 38.125072, -78.461779 38.124587, -78.461301 38.124182, -78.46098 38.12389, -78.460715 38.123618, -78.460511 38.123245, -78.460256 38.122807, -78.459655 38.121781, -78.459349 38.121212, -78.459041 38.120561, -78.459014 38.120491, -78.458879 38.120087, -78.458841 38.119937, -78.458777 38.119643, -78.458757 38.119426, -78.458755 38.119269, -78.45877 38.119102, -78.458788 38.118967, -78.45884 38.11875, -78.45891 38.118534, -78.459052 38.117975, -78.459083 38.117853, -78.459155 38.117353, -78.459164 38.117177, -78.459183 38.116817, -78.459187 38.116408, -78.459188 38.116081, -78.459188 38.115968, -78.459203 38.115757, -78.459236 38.115547, -78.459288 38.11534, -78.459449 38.114876, -78.459646 38.114315, -78.459857 38.113546, -78.459875 38.113431, -78.460039 38.112334, -78.460071 38.112195, -78.460109 38.112077, -78.460128 38.112025, -78.460538 38.11109, -78.460629 38.110857, -78.460724 38.110668, -78.460838 38.110482, -78.461346 38.109771, -78.461733 38.109203, -78.461799 38.109094, -78.461849 38.108979, -78.46188 38.108861, -78.46193 38.108482, -78.461959 38.108392, -78.462005 38.108306, -78.462054 38.108243, -78.462183 38.108051, -78.46223 38.107972, -78.462366 38.107711, -78.462417 38.107645, -78.462482 38.107587, -78.462555 38.107542, -78.462638 38.10751, -78.462708 38.107495, -78.462818 38.107485, -78.462894 38.10747, -78.462978 38.107435, -78.463056 38.107386, -78.46312 38.107327, -78.463292 38.107104, -78.463365 38.107024, -78.463451 38.106952, -78.463571 38.106881, -78.463903 38.106742, -78.464301 38.106553, -78.464241 38.106447, -78.46413 38.106287, -78.464081 38.1062, -78.464095 38.106051, -78.46413 38.106019, -78.46429 38.105936, -78.464616 38.105881, -78.464721 38.105832, -78.464867 38.105722, -78.465172 38.105464, -78.465311 38.105283, -78.465603 38.105041, -78.465797 38.10486, -78.465832 38.104706, -78.465829 38.104666, -78.465601 38.1046, -78.46531 38.104488, -78.464748 38.104302, -78.463851 38.103984, -78.463289 38.103777, -78.462637 38.103551, -78.46256 38.103529, -78.462372 38.103464, -78.462192 38.103386, -78.46202 38.103297, -78.461675 38.103083, -78.461577 38.103028, -78.461446 38.10297, -78.461147 38.102872, -78.460672 38.102729, -78.460421 38.102659, -78.460241 38.102618, -78.460378 38.102424, -78.461334 38.101219, -78.461416 38.101116, -78.461539 38.100961, -78.462232 38.10133, -78.462697 38.101593, -78.462819 38.101645, -78.462955 38.101703, -78.463601 38.102071, -78.464393 38.102554, -78.465338 38.10307, -78.465894 38.103344, -78.466165 38.103448, -78.466436 38.103536, -78.466728 38.103646, -78.466909 38.10375, -78.467305 38.104178, -78.467757 38.104453, -78.467903 38.104519, -78.468028 38.104563, -78.468241 38.104089, -78.468649 38.103884, -78.468757 38.103882, -78.468927 38.103853, -78.468982 38.103846, -78.469013 38.103838, -78.469112 38.103821, -78.469689 38.103684, -78.470043 38.103497, -78.470633 38.103107, -78.470751 38.102788, -78.470807 38.102536, -78.470772 38.102448, -78.470682 38.102355, -78.470633 38.102234, -78.470626 38.10213, -78.470793 38.102009, -78.470911 38.101893, -78.471071 38.101515, -78.471126 38.101416, -78.471327 38.10113, -78.471397 38.100987, -78.471418 38.1009, -78.471432 38.100658, -78.471397 38.10051, -78.471348 38.10035, -78.471278 38.100191, -78.471209 38.100114, -78.471174 38.099988, -78.471237 38.099922, -78.471563 38.099862, -78.47166 38.099807, -78.47173 38.09973, -78.471779 38.099587, -78.471737 38.099351, -78.471535 38.09889, -78.471466 38.09867, -78.471472 38.098599, -78.471514 38.098549, -78.471604 38.098555, -78.471757 38.098692, -78.471952 38.09884, -78.472063 38.09889, -78.472237 38.098939, -78.472452 38.09895, -78.472563 38.098912, -78.472702 38.098824, -78.473154 38.098296, -78.473362 38.098088, -78.473765 38.097593, -78.473932 38.09744, -78.47407 38.097291, -78.474279 38.096891, -78.474411 38.096869, -78.474612 38.096874, -78.474651 38.096861)))"} -{"geo_id":"84630","urban_area_code":"84630","name":"Staunton--Waynesboro, VA","lsad_name":"Staunton--Waynesboro, VA Urbanized Area","area_lsad_code":"75","mtfcc_feature_class_code":"G3500","type":"U","functional_status":"S","area_land_meters":98793096,"area_water_meters":451490,"internal_point_lon":-78.9912736,"internal_point_lat":38.1190385,"internal_point_geom":"POINT(-78.9912736 38.1190385)","urban_area_geom":"MULTIPOLYGON(((-79.134783 38.152163, -79.134836 38.151279, -79.134864 38.150572, -79.134868 38.150456, -79.134873 38.150341, -79.1349 38.149663, -79.134939 38.148905, -79.134954 38.14859, -79.134968 38.148217, -79.135012 38.14723, -79.13506 38.146479, -79.135095 38.145816, -79.135082 38.14573, -79.13505 38.145646, -79.135106 38.145618, -79.135235 38.145541, -79.135351 38.145454, -79.135441 38.14537, -79.135562 38.145233, -79.135667 38.145089, -79.135757 38.144938, -79.13583 38.144782, -79.135853 38.144722, -79.135889 38.144614, -79.135925 38.144468, -79.135949 38.144319, -79.136021 38.143606, -79.136056 38.143294, -79.136092 38.142966, -79.136119 38.142709, -79.136163 38.142357, -79.13622 38.141785, -79.136011 38.141744, -79.135863 38.141725, -79.135794 38.14173, -79.135742 38.141745, -79.135707 38.141762, -79.135672 38.141794, -79.135647 38.141833, -79.135601 38.141962, -79.135581 38.142075, -79.135576 38.142177, -79.135571 38.143601, -79.134325 38.143531, -79.131719 38.143274, -79.131379 38.143253, -79.124494 38.142829, -79.123247 38.142753, -79.123051 38.142741, -79.122344 38.142707, -79.12233 38.142798, -79.122332 38.142831, -79.122349 38.142873, -79.122382 38.142907, -79.122427 38.142932, -79.122541 38.142956, -79.123201 38.143061, -79.123305 38.143082, -79.123605 38.143142, -79.123659 38.143167, -79.123702 38.143203, -79.12373 38.143247, -79.12374 38.143296, -79.123693 38.143614, -79.123572 38.144426, -79.123551 38.144648, -79.122977 38.144759, -79.122077 38.14493, -79.121484 38.145054, -79.121489 38.145222, -79.121505 38.145279, -79.121448 38.145337, -79.12139 38.145385, -79.121308 38.145436, -79.121205 38.145479, -79.121119 38.145499, -79.12103 38.145504, -79.120969 38.145496, -79.120894 38.145495, -79.120822 38.145514, -79.12074 38.145589, -79.120416 38.145949, -79.120344 38.146009, -79.120259 38.146058, -79.120074 38.146127, -79.119891 38.146206, -79.119718 38.146297, -79.119555 38.1464, -79.119218 38.14666, -79.118857 38.146968, -79.118708 38.147112, -79.118566 38.147285, -79.118457 38.147459, -79.11815 38.147968, -79.11808 38.148104, -79.118036 38.148216, -79.117999 38.148351, -79.117989 38.148455, -79.117994 38.14854, -79.118016 38.148643, -79.118081 38.148808, -79.118356 38.149369, -79.118925 38.150365, -79.119081 38.150536, -79.119254 38.150669, -79.119477 38.150783, -79.119371 38.151238, -79.119235 38.151994, -79.120538 38.152174, -79.12077 38.152211, -79.121206 38.152296, -79.122014 38.152469, -79.124402 38.15301, -79.124701 38.153078, -79.125411 38.153238, -79.125909 38.153354, -79.126715 38.153534, -79.126801 38.153553, -79.127128 38.153627, -79.127642 38.153742, -79.128155 38.153858, -79.129389 38.15413, -79.129377 38.154151, -79.12933 38.154193, -79.129307 38.154219, -79.129154 38.154669, -79.128993 38.155085, -79.128995 38.155121, -79.129016 38.155153, -79.129043 38.155173, -79.129275 38.155243, -79.129796 38.155357, -79.130431 38.155507, -79.130894 38.155605, -79.131256 38.15567, -79.131156 38.156, -79.13103 38.156344, -79.130967 38.156499, -79.130842 38.156764, -79.130768 38.157034, -79.130714 38.157178, -79.130638 38.15732, -79.130553 38.157445, -79.13042 38.157605, -79.130274 38.157798, -79.130244 38.157851, -79.130232 38.157908, -79.130236 38.157966, -79.130258 38.158022, -79.130297 38.158072, -79.130343 38.158102, -79.130398 38.158124, -79.130471 38.158139, -79.130645 38.158141, -79.130788 38.158125, -79.13088 38.158103, -79.130987 38.158065, -79.13108 38.158015, -79.131175 38.157942, -79.131244 38.157865, -79.131286 38.15784, -79.131335 38.157827, -79.131643 38.157836, -79.131786 38.157854, -79.131822 38.157864, -79.131858 38.157878, -79.131951 38.157913, -79.13203 38.157926, -79.132109 38.157924, -79.132588 38.157838, -79.13282 38.157803, -79.133208 38.157778, -79.133654 38.157721, -79.133988 38.15767, -79.134577 38.157587, -79.135136 38.1575, -79.135372 38.157473, -79.135414 38.157475, -79.135483 38.157492, -79.135564 38.157396, -79.135849 38.157077, -79.136516 38.156352, -79.136619 38.156228, -79.136656 38.156173, -79.136688 38.156098, -79.136701 38.156021, -79.136511 38.156006, -79.136135 38.15595, -79.13568 38.155841, -79.134627 38.155536, -79.134603 38.155486, -79.134595 38.155435, -79.13462 38.155199, -79.134642 38.154639, -79.134693 38.153732, -79.134701 38.153578, -79.134708 38.153423, -79.134726 38.153094, -79.134783 38.152163)), ((-78.985676 38.189392, -78.993281 38.179373, -78.992701 38.178041, -78.992502 38.177605, -78.992337 38.177269, -78.992129 38.176903, -78.99205 38.176779, -78.991744 38.176339, -78.991715 38.176299, -78.99161 38.176336, -78.991555 38.176348, -78.991441 38.176358, -78.990936 38.176364, -78.990872 38.17638, -78.990816 38.176407, -78.990771 38.176445, -78.990739 38.176491, -78.990723 38.176542, -78.990722 38.176569, -78.990739 38.176654, -78.990762 38.176739, -78.990793 38.176829, -78.990639 38.176867, -78.99048 38.17689, -78.990318 38.176899, -78.990237 38.176898, -78.989558 38.17694, -78.989063 38.176962, -78.98891 38.17695, -78.98885 38.176921, -78.988801 38.176881, -78.988765 38.176833, -78.988059 38.175737, -78.987315 38.175782, -78.986551 38.175816, -78.986405 38.175849, -78.986266 38.175897, -78.986137 38.17596, -78.986062 38.176008, -78.98578 38.17625, -78.985462 38.176541, -78.985236 38.176698, -78.985136 38.176759, -78.984998 38.176832, -78.984446 38.17711, -78.984384 38.177147, -78.984128 38.177301, -78.982031 38.178624, -78.98183 38.178758, -78.981542 38.17895, -78.981242 38.179157, -78.981119 38.179242, -78.980977 38.179352, -78.980847 38.179471, -78.98068 38.179658, -78.980589 38.179781, -78.980515 38.179912, -78.980449 38.180045, -78.980335 38.180317, -78.980286 38.180456, -78.980245 38.180603, -78.980195 38.180788, -78.980172 38.180846, -78.98011 38.180959, -78.980029 38.181063, -78.979931 38.181159, -78.979712 38.181338, -78.979193 38.181812, -78.978855 38.182131, -78.97871 38.182244, -78.978637 38.182293, -78.9785 38.182364, -78.977986 38.182586, -78.977574 38.182769, -78.977401 38.182827, -78.977311 38.182852, -78.976744 38.182984, -78.976606 38.183026, -78.976523 38.183077, -78.97648 38.183113, -78.976407 38.18319, -78.976679 38.183294, -78.977291 38.183534, -78.977975 38.183818, -78.978688 38.184129, -78.979303 38.184406, -78.979332 38.184419, -78.980236 38.184823, -78.981674 38.185472, -78.981838 38.185566, -78.98193 38.18562, -78.982105 38.185738, -78.982246 38.185843, -78.982427 38.185995, -78.982796 38.186371, -78.983465 38.187097, -78.984085 38.18776, -78.985188 38.188943, -78.985442 38.189199, -78.985611 38.189344, -78.985676 38.189392)), ((-79.004985 38.21177, -79.00502 38.211716, -79.005054 38.211661, -79.004721 38.211292, -79.004568 38.211089, -79.004359 38.210776, -79.003956 38.210078, -79.003907 38.210007, -79.003859 38.209957, -79.003546 38.209468, -79.003483 38.209408, -79.003421 38.209364, -79.003184 38.20926, -79.003115 38.209216, -79.003052 38.209139, -79.003275 38.209111, -79.00329 38.209096, -79.003296 38.209051, -79.003191 38.208787, -79.003087 38.208425, -79.003039 38.208342, -79.00299 38.208287, -79.002941 38.208254, -79.002872 38.208232, -79.002694 38.208189, -79.0026 38.208166, -79.002489 38.2081, -79.002412 38.20804, -79.002197 38.207815, -79.001807 38.207502, -79.001647 38.207386, -79.00157 38.207348, -79.0012 38.207635, -79.001084 38.207724, -79.000969 38.207814, -78.999784 38.2087, -78.997465 38.210692, -78.996228 38.211756, -78.994883 38.212878, -78.994443 38.213246, -78.992454 38.214908, -78.992159 38.215249, -78.991161 38.216405, -78.99002 38.217858, -78.991534 38.219034, -78.991868 38.219264, -78.992498 38.219663, -78.992604 38.219724, -78.993051 38.219933, -78.993166 38.219992, -78.993275 38.220055, -78.993427 38.220166, -78.993513 38.220217, -78.993627 38.220277, -78.994144 38.220528, -78.994177 38.220538, -78.994222 38.220537, -78.99427 38.220519, -78.994288 38.220504, -78.994445 38.220251, -78.994537 38.220066, -78.994583 38.219996, -78.994636 38.219955, -78.994684 38.219933, -78.9948 38.219863, -78.994927 38.2198, -78.994987 38.219782, -78.995074 38.219771, -78.995161 38.219775, -78.995246 38.219794, -78.995755 38.218782, -78.996039 38.21824, -78.996304 38.217726, -78.996475 38.217389, -78.997733 38.218019, -78.997903 38.217899, -79.000643 38.215946, -79.000505 38.215925, -79.000401 38.215898, -79.000373 38.215881, -79.000401 38.215843, -79.000624 38.215684, -79.000721 38.215645, -79.00093 38.215602, -79.001021 38.215679, -79.001132 38.215596, -79.001251 38.215481, -79.001794 38.214916, -79.001968 38.214718, -79.002163 38.214581, -79.002289 38.214515, -79.002755 38.214153, -79.00295 38.214037, -79.002999 38.213999, -79.003354 38.213516, -79.003689 38.213209, -79.003738 38.21317, -79.003974 38.213061, -79.004155 38.212945, -79.00426 38.212852, -79.004385 38.212715, -79.004926 38.211863, -79.004953 38.21184, -79.004955 38.211817, -79.004985 38.21177)), ((-78.967153 38.097533, -78.966609 38.097275, -78.966091 38.097075, -78.965398 38.09682, -78.964943 38.096662, -78.963951 38.096299, -78.963923 38.096289, -78.96234 38.095678, -78.961785 38.09546, -78.958758 38.094294, -78.958575 38.094223, -78.955345 38.092963, -78.954849 38.092764, -78.95473 38.092716, -78.95361 38.092266, -78.953039 38.092028, -78.952938 38.091987, -78.952835 38.091941, -78.951833 38.091498, -78.951426 38.091326, -78.950877 38.091103, -78.950758 38.091055, -78.950182 38.090822, -78.950019 38.090756, -78.949769 38.090655, -78.947489 38.089735, -78.947355 38.089681, -78.946536 38.089351, -78.945236 38.088828, -78.944988 38.088728, -78.945045 38.08858, -78.945092 38.088186, -78.945097 38.087997, -78.945136 38.086281, -78.945274 38.086243, -78.945079 38.085765, -78.945003 38.085505, -78.944988 38.085397, -78.945004 38.085349, -78.945035 38.085316, -78.9451 38.085262, -78.945107 38.085223, -78.94515 38.084286, -78.945131 38.084252, -78.945102 38.084229, -78.945039 38.084199, -78.944876 38.084188, -78.94492 38.083589, -78.944929 38.083391, -78.944308 38.083369, -78.94431 38.08331, -78.944293 38.083255, -78.944285 38.083184, -78.944267 38.083085, -78.944254 38.083038, -78.944239 38.082987, -78.94422 38.082934, -78.944198 38.082879, -78.944155 38.082768, -78.944135 38.082711, -78.944114 38.082652, -78.944096 38.082592, -78.944062 38.082467, -78.944059 38.082455, -78.944048 38.082401, -78.944036 38.082335, -78.944027 38.082272, -78.944021 38.082211, -78.944019 38.082175, -78.944103 38.082187, -78.94417 38.082196, -78.944241 38.082207, -78.944332 38.082217, -78.944427 38.082226, -78.94452 38.082238, -78.944532 38.08224, -78.944595 38.082175, -78.944709 38.081953, -78.94481 38.081729, -78.944881 38.081592, -78.944929 38.081532, -78.94502 38.081442, -78.94528 38.081252, -78.945303 38.081198, -78.945305 38.081155, -78.945279 38.081113, -78.945252 38.081083, -78.945185 38.081032, -78.945134 38.080955, -78.945049 38.080733, -78.944978 38.080489, -78.944953 38.080338, -78.94522 38.080289, -78.945524 38.080211, -78.94577 38.08013, -78.946008 38.080036, -78.946238 38.079928, -78.946271 38.079899, -78.946288 38.079855, -78.946281 38.079816, -78.94627 38.079799, -78.945982 38.079471, -78.945868 38.079518, -78.945663 38.07961, -78.945451 38.07969, -78.945329 38.079729, -78.945121 38.079787, -78.944894 38.079836, -78.944683 38.079871, -78.944488 38.079889, -78.944235 38.079893, -78.944072 38.079893, -78.943738 38.079866, -78.943507 38.079832, -78.943235 38.079772, -78.942967 38.079691, -78.942701 38.07959, -78.94253 38.079512, -78.94184 38.07922, -78.94181 38.079262, -78.941718 38.079373, -78.94159 38.079504, -78.941147 38.07989, -78.940731 38.080244, -78.940601 38.080333, -78.940488 38.080396, -78.940369 38.080451, -78.940309 38.080469, -78.940204 38.080486, -78.940097 38.080489, -78.939769 38.080451, -78.939502 38.080411, -78.939233 38.080385, -78.938962 38.080375, -78.938802 38.080387, -78.938605 38.080415, -78.938444 38.080451, -78.938225 38.080515, -78.937986 38.080608, -78.937812 38.080694, -78.937704 38.080582, -78.93741 38.080322, -78.937236 38.080193, -78.937372 38.080082, -78.9375 38.080003, -78.937672 38.079919, -78.937797 38.079871, -78.937863 38.079851, -78.938068 38.079788, -78.938238 38.079722, -78.938366 38.079658, -78.938538 38.079548, -78.93809 38.079154, -78.93794 38.07905, -78.937778 38.078959, -78.937458 38.078802, -78.936882 38.078535, -78.935481 38.077879, -78.935369 38.07784, -78.935253 38.077814, -78.935313 38.077647, -78.935376 38.077515, -78.935429 38.077426, -78.935526 38.07731, -78.935614 38.077223, -78.935735 38.077123, -78.935898 38.077017, -78.936218 38.076866, -78.936233 38.076843, -78.936314 38.076811, -78.936409 38.076771, -78.936461 38.07675, -78.936517 38.076726, -78.936636 38.076674, -78.936703 38.076646, -78.936776 38.076617, -78.936851 38.076587, -78.936925 38.076556, -78.937002 38.076526, -78.93708 38.076495, -78.93716 38.076464, -78.937243 38.076432, -78.937327 38.076398, -78.937412 38.076364, -78.937497 38.076329, -78.937581 38.076293, -78.937662 38.076258, -78.937742 38.076224, -78.937819 38.076192, -78.937891 38.076161, -78.937959 38.076133, -78.938022 38.076105, -78.938082 38.076077, -78.93814 38.076051, -78.938193 38.076026, -78.938289 38.075978, -78.93837 38.075934, -78.938441 38.075909, -78.938553 38.075851, -78.938843 38.0757, -78.939189 38.075521, -78.940094 38.074905, -78.941225 38.074262, -78.94155 38.074573, -78.944893 38.072409, -78.945097 38.072277, -78.945972 38.071733, -78.946589 38.072119, -78.947277 38.07163, -78.94733 38.07165, -78.947423 38.071565, -78.947574 38.071455, -78.947751 38.071344, -78.94788 38.07124, -78.94816 38.071038, -78.948398 38.070858, -78.948588 38.070737, -78.948851 38.070557, -78.948973 38.070465, -78.949197 38.070286, -78.949681 38.069972, -78.950066 38.069748, -78.950159 38.069675, -78.950292 38.06955, -78.950248 38.069509, -78.950174 38.069441, -78.95014 38.069396, -78.950119 38.069347, -78.950111 38.069286, -78.949106 38.068302, -78.948509 38.068104, -78.947212 38.067703, -78.946827 38.06756, -78.946435 38.067406, -78.945075 38.066808, -78.944688 38.066632, -78.94431 38.066464, -78.944034 38.066346, -78.942961 38.065877, -78.942262 38.065562, -78.9416 38.06527, -78.941062 38.065015, -78.940961 38.064963, -78.94069 38.064803, -78.940556 38.06471, -78.940313 38.064504, -78.940041 38.064255, -78.939836 38.064038, -78.939664 38.063881, -78.939593 38.06383, -78.939528 38.063915, -78.939502 38.063933, -78.937927 38.064808, -78.937861 38.064712, -78.937605 38.064382, -78.937119 38.063798, -78.936823 38.063442, -78.936722 38.063334, -78.93702 38.063194, -78.938233 38.062587, -78.938499 38.062429, -78.938641 38.062352, -78.939672 38.061879, -78.9397 38.061922, -78.93978 38.061988, -78.939861 38.06203, -78.939951 38.062059, -78.940115 38.062085, -78.940452 38.0621, -78.94065 38.0621, -78.940694 38.0621, -78.940798 38.062097, -78.941194 38.062069, -78.94201 38.062001, -78.942476 38.061974, -78.942739 38.061972, -78.942997 38.061984, -78.943198 38.06202, -78.944197 38.062203, -78.946664 38.062959, -78.947451 38.063192, -78.947922 38.063329, -78.948516 38.063503, -78.949423 38.063778, -78.951231 38.064336, -78.951556 38.064434, -78.952659 38.064768, -78.963477 38.068066, -78.96378 38.067809, -78.964152 38.067925, -78.965069 38.068225, -78.965548 38.068027, -78.965677 38.067984, -78.96593 38.067884, -78.966296 38.067751, -78.966403 38.067693, -78.966574 38.067587, -78.966732 38.067471, -78.966609 38.067375, -78.966409 38.067204, -78.96632 38.06712, -78.96621 38.067008, -78.966193 38.06699, -78.965944 38.066727, -78.965805 38.066595, -78.965618 38.066432, -78.965516 38.066354, -78.965341 38.066226, -78.965056 38.066031, -78.964852 38.065904, -78.96473 38.065834, -78.964379 38.065642, -78.964086 38.065494, -78.963646 38.065293, -78.963502 38.065232, -78.963188 38.065099, -78.963046 38.065038, -78.962903 38.064978, -78.962563 38.064836, -78.962404 38.06477, -78.962113 38.064657, -78.961776 38.064542, -78.961242 38.064377, -78.960896 38.064264, -78.960547 38.064127, -78.960307 38.06402, -78.960087 38.063908, -78.959942 38.063823, -78.959798 38.063732, -78.959597 38.063598, -78.959448 38.063492, -78.959031 38.063211, -78.958921 38.063136, -78.958677 38.062968, -78.957857 38.062417, -78.957016 38.061844, -78.956758 38.061673, -78.956404 38.061454, -78.956193 38.061314, -78.956044 38.061201, -78.955933 38.061101, -78.95586 38.061017, -78.955708 38.060775, -78.955358 38.060056, -78.955269 38.059865, -78.955089 38.059501, -78.954902 38.059157, -78.954799 38.058956, -78.954716 38.058809, -78.954011 38.057467, -78.953738 38.056969, -78.953646 38.056812, -78.95354 38.056618, -78.953304 38.056189, -78.953188 38.055989, -78.952004 38.056446, -78.951474 38.056647, -78.951182 38.056741, -78.951087 38.056772, -78.950267 38.057002, -78.950024 38.057073, -78.949602 38.057212, -78.949568 38.057183, -78.949379 38.057272, -78.949191 38.05736, -78.949106 38.057388, -78.948695 38.057551, -78.948386 38.057691, -78.948235 38.057759, -78.947852 38.057935, -78.946833 38.058406, -78.945498 38.059023, -78.94492 38.059286, -78.944589 38.059423, -78.944268 38.059566, -78.943841 38.059769, -78.943591 38.059888, -78.9431 38.060113, -78.942996 38.059929, -78.942869 38.05974, -78.942736 38.059556, -78.942628 38.059417, -78.942566 38.059349, -78.942475 38.059272, -78.942371 38.059207, -78.942255 38.059152, -78.942149 38.059112, -78.942011 38.059075, -78.941868 38.059052, -78.94162 38.059037, -78.941455 38.05904, -78.94126 38.059061, -78.941093 38.059097, -78.940889 38.059163, -78.940717 38.059238, -78.94055 38.059321, -78.94037 38.059425, -78.939968 38.059646, -78.939766 38.059733, -78.939527 38.059811, -78.939392 38.059843, -78.939284 38.059864, -78.939109 38.059886, -78.938999 38.059893, -78.938748 38.059893, -78.938491 38.059867, -78.938087 38.0598, -78.93774 38.059732, -78.937075 38.05957, -78.936585 38.059427, -78.936079 38.059265, -78.935836 38.059192, -78.935266 38.059021, -78.934977 38.058928, -78.934618 38.05882, -78.933969 38.058633, -78.932208 38.058094, -78.927437 38.056629, -78.926882 38.056455, -78.926481 38.056334, -78.92598 38.056177, -78.925466 38.056022, -78.925134 38.055919, -78.925745 38.05581, -78.925874 38.055774, -78.925916 38.055763, -78.926287 38.055688, -78.926759 38.055551, -78.92719 38.055403, -78.92769 38.055156, -78.928461 38.054727, -78.928628 38.054673, -78.928774 38.054662, -78.92917 38.054722, -78.929322 38.054717, -78.929538 38.054689, -78.929816 38.054618, -78.930066 38.05453, -78.930475 38.054316, -78.930913 38.054047, -78.931108 38.053964, -78.931212 38.053953, -78.931329 38.053969, -78.931429 38.053906, -78.931503 38.053871, -78.931766 38.053773, -78.931946 38.053666, -78.932174 38.05352, -78.932237 38.053469, -78.932303 38.053397, -78.932347 38.053315, -78.932456 38.052975, -78.932487 38.05291, -78.932612 38.052713, -78.932538 38.052694, -78.932506 38.052682, -78.932235 38.052549, -78.932122 38.052508, -78.931915 38.052457, -78.931812 38.052428, -78.931429 38.052286, -78.931279 38.052217, -78.931219 38.052181, -78.931067 38.052028, -78.931009 38.05197, -78.931008 38.051951, -78.930994 38.051904, -78.930781 38.051595, -78.930509 38.051199, -78.930405 38.051047, -78.930224 38.050768, -78.930035 38.050494, -78.929873 38.050241, -78.929713 38.050023, -78.929497 38.049716, -78.929853 38.049639, -78.93023 38.049548, -78.930903 38.049385, -78.931648 38.049215, -78.933487 38.048733, -78.933638 38.048627, -78.934062 38.048536, -78.934259 38.048495, -78.935062 38.048331, -78.935184 38.048336, -78.935239 38.048338, -78.935432 38.048345, -78.935792 38.048263, -78.935956 38.048231, -78.936055 38.048216, -78.936177 38.048198, -78.936309 38.048186, -78.937902 38.048083, -78.938974 38.048015, -78.939177 38.048008, -78.939273 38.048005, -78.939442 38.048007, -78.939547 38.048014, -78.939692 38.048033, -78.939928 38.048087, -78.940336 38.048194, -78.941826 38.04859, -78.942196 38.047283, -78.942237 38.047136, -78.942471 38.046304, -78.942495 38.04622, -78.942518 38.046135, -78.942574 38.045935, -78.942895 38.044794, -78.943001 38.044417, -78.942895 38.044408, -78.942822 38.044417, -78.942643 38.044455, -78.942193 38.044588, -78.941829 38.044707, -78.941337 38.044845, -78.941228 38.04488, -78.941119 38.044928, -78.941049 38.044988, -78.940561 38.04553, -78.939845 38.045523, -78.93915 38.045527, -78.939133 38.045603, -78.939123 38.04565, -78.938761 38.045666, -78.938005 38.045941, -78.937838 38.045968, -78.937685 38.04593, -78.937449 38.045804, -78.936914 38.045435, -78.936185 38.044952, -78.936123 38.044842, -78.936143 38.044749, -78.936282 38.044634, -78.936504 38.044408, -78.936685 38.044194, -78.936831 38.043953, -78.936977 38.043623, -78.937178 38.043283, -78.937331 38.043112, -78.937671 38.042843, -78.937761 38.042723, -78.937879 38.042333, -78.937964 38.042185, -78.93741 38.041849, -78.937263 38.041779, -78.937157 38.041755, -78.937068 38.041761, -78.936946 38.041807, -78.936803 38.041883, -78.936692 38.041932, -78.936555 38.041982, -78.936371 38.042012, -78.936211 38.042011, -78.936098 38.041994, -78.935981 38.04196, -78.935868 38.041911, -78.935699 38.042077, -78.935153 38.042615, -78.935058 38.042709, -78.934929 38.042816, -78.934757 38.042933, -78.934525 38.043041, -78.934331 38.043125, -78.934309 38.043134, -78.93363 38.043513, -78.933386 38.043665, -78.932979 38.043918, -78.932951 38.043844, -78.932924 38.043804, -78.93232 38.043188, -78.932154 38.043012, -78.931877 38.042681, -78.931652 38.042492, -78.930882 38.041662, -78.92997 38.041876, -78.92947 38.042052, -78.928345 38.042365, -78.927921 38.042499, -78.92764 38.042627, -78.927451 38.042695, -78.927287 38.042813, -78.926935 38.043095, -78.926823 38.043173, -78.926735 38.043297, -78.926851 38.043593, -78.92672 38.043731, -78.926718 38.043813, -78.926471 38.043899, -78.926508 38.043956, -78.926428 38.044041, -78.926324 38.044101, -78.92624 38.044107, -78.926011 38.044167, -78.925532 38.044656, -78.925379 38.044722, -78.925226 38.044716, -78.924899 38.044727, -78.92451 38.044853, -78.924336 38.045002, -78.924267 38.045112, -78.924121 38.045606, -78.923955 38.045776, -78.923775 38.04593, -78.923565 38.046046, -78.923322 38.046205, -78.922989 38.046344, -78.922523 38.046503, -78.922211 38.046574, -78.921947 38.046596, -78.921753 38.046673, -78.921551 38.046838, -78.920734 38.047564, -78.919656 38.048521, -78.918829 38.049175, -78.917982 38.049801, -78.917801 38.049943, -78.91762 38.050235, -78.917426 38.050581, -78.917196 38.05085, -78.91705 38.050993, -78.916891 38.051123, -78.916384 38.051282, -78.916113 38.05131, -78.915849 38.051299, -78.9153 38.051194, -78.914752 38.051029, -78.914432 38.050881, -78.914217 38.050705, -78.914016 38.050496, -78.913759 38.050354, -78.913516 38.050343, -78.913231 38.050359, -78.912981 38.050408, -78.912797 38.05043, -78.912681 38.050368, -78.912545 38.050297, -78.911284 38.049631, -78.911052 38.049499, -78.910275 38.04909, -78.909926 38.0489, -78.909636 38.048747, -78.9092 38.048945, -78.90872 38.04915, -78.907324 38.049595, -78.907204 38.049634, -78.906385 38.05007, -78.906014 38.050225, -78.904979 38.050662, -78.904653 38.050897, -78.904564 38.050963, -78.903742 38.051797, -78.903359 38.051646, -78.903361 38.051626, -78.903348 38.0516, -78.902586 38.050883, -78.902161 38.050503, -78.901806 38.050746, -78.90057 38.051636, -78.900383 38.051756, -78.900346 38.051781, -78.900111 38.051915, -78.899866 38.052038, -78.899683 38.052117, -78.899612 38.052149, -78.899404 38.052229, -78.899136 38.052317, -78.8974 38.052849, -78.897261 38.052569, -78.897105 38.052284, -78.896987 38.05209, -78.896943 38.052046, -78.89691 38.052031, -78.896833 38.052028, -78.896327 38.052107, -78.895784 38.052203, -78.895601 38.052248, -78.895559 38.052263, -78.895513 38.052285, -78.895463 38.05232, -78.895434 38.05234, -78.895352 38.052147, -78.895286 38.052026, -78.895253 38.052003, -78.895211 38.051991, -78.895167 38.051994, -78.894675 38.052161, -78.893388 38.052558, -78.893597 38.052977, -78.893242 38.053083, -78.891585 38.053596, -78.891529 38.053627, -78.891509 38.053657, -78.891505 38.053691, -78.891666 38.054054, -78.891628 38.054065, -78.891334 38.054147, -78.890144 38.054525, -78.889527 38.054705, -78.889503 38.054713, -78.888852 38.054918, -78.887771 38.055242, -78.887568 38.055307, -78.887502 38.055345, -78.887473 38.055372, -78.887457 38.055421, -78.887463 38.055474, -78.88749 38.055559, -78.88755 38.055693, -78.887624 38.055842, -78.887478 38.055884, -78.886967 38.056047, -78.886631 38.05616, -78.886168 38.056343, -78.885738 38.056556, -78.885384 38.056771, -78.885268 38.056848, -78.885191 38.0569, -78.88508 38.056974, -78.884239 38.057586, -78.88397 38.057799, -78.883209 38.058362, -78.881475 38.05966, -78.881313 38.059793, -78.881087 38.060004, -78.880915 38.06019, -78.880757 38.060385, -78.880393 38.060911, -78.880192 38.060808, -78.880074 38.060715, -78.880018 38.06068, -78.879511 38.060493, -78.879451 38.060479, -78.879397 38.060477, -78.879374 38.060482, -78.879321 38.060514, -78.879279 38.060553, -78.879141 38.060766, -78.879031 38.060951, -78.879 38.060999, -78.878376 38.061982, -78.8783 38.062094, -78.878205 38.062235, -78.877902 38.062734, -78.877525 38.063327, -78.877286 38.063723, -78.876752 38.063517, -78.876295 38.063339, -78.875542 38.06304, -78.875229 38.062912, -78.874202 38.062504, -78.873773 38.062315, -78.873228 38.062109, -78.873187 38.062112, -78.873169 38.062124, -78.873103 38.062194, -78.873037 38.062294, -78.872889 38.062531, -78.872845 38.062601, -78.872703 38.062525, -78.872537 38.062418, -78.872478 38.062361, -78.872442 38.06231, -78.872316 38.062078, -78.872273 38.06199, -78.872166 38.061732, -78.872021 38.061444, -78.871955 38.061327, -78.871662 38.060704, -78.871204 38.059767, -78.871151 38.059667, -78.871029 38.059389, -78.870942 38.059219, -78.870889 38.059116, -78.870821 38.058998, -78.87063 38.059058, -78.870288 38.059165, -78.870125 38.058837, -78.86996 38.058479, -78.869854 38.058171, -78.869566 38.057159, -78.869485 38.056902, -78.869434 38.056706, -78.869565 38.05581, -78.869857 38.055567, -78.870066 38.05573, -78.869959 38.055391, -78.869779 38.054817, -78.869295 38.054569, -78.868898 38.054366, -78.868901 38.053831, -78.868929 38.053378, -78.868951 38.052477, -78.868938 38.052039, -78.868889 38.051606, -78.86886 38.051436, -78.868845 38.051352, -78.868727 38.050807, -78.868583 38.050227, -78.868562 38.0501, -78.86852 38.049888, -78.868432 38.049519, -78.868329 38.049239, -78.868176 38.048819, -78.8681 38.048681, -78.867927 38.048431, -78.867641 38.04803, -78.867471 38.047852, -78.867454 38.047834, -78.867442 38.04784, -78.867348 38.047875, -78.86711 38.047964, -78.866778 38.048088, -78.866473 38.048202, -78.866324 38.048257, -78.866168 38.048315, -78.865737 38.048439, -78.865706 38.048448, -78.865382 38.048614, -78.86502 38.048701, -78.864793 38.04882, -78.864934 38.049024, -78.865271 38.049511, -78.865698 38.050463, -78.866172 38.051093, -78.866198 38.051128, -78.866352 38.051332, -78.86682 38.053599, -78.866998 38.05525, -78.867026 38.056796, -78.866485 38.058967, -78.866389 38.05935, -78.866307 38.059683, -78.866173 38.060221, -78.866127 38.060405, -78.865985 38.060976, -78.865851 38.061515, -78.865827 38.061612, -78.865805 38.061698, -78.86566 38.062283, -78.865107 38.062592, -78.864914 38.0627, -78.864396 38.062989, -78.864313 38.063036, -78.863746 38.063353, -78.863241 38.063634, -78.863077 38.063732, -78.862957 38.063802, -78.862301 38.064191, -78.862101 38.064308, -78.861659 38.064627, -78.861624 38.064652, -78.861145 38.064997, -78.86097 38.065122, -78.860425 38.065514, -78.860222 38.06566, -78.859933 38.065901, -78.85977 38.066036, -78.859262 38.066458, -78.858801 38.066841, -78.858783 38.066855, -78.858621 38.066991, -78.858113 38.067412, -78.858064 38.067453, -78.858095 38.067498, -78.858159 38.067563, -78.858221 38.067607, -78.858644 38.067765, -78.859058 38.067937, -78.859454 38.068077, -78.85991 38.068249, -78.860218 38.068383, -78.860518 38.068529, -78.860558 38.068559, -78.860651 38.068642, -78.860671 38.068665, -78.86073 38.068733, -78.860788 38.068821, -78.860798 38.068874, -78.860789 38.068928, -78.860623 38.06916, -78.860494 38.069316, -78.860381 38.069471, -78.860236 38.06972, -78.8602 38.069777, -78.859516 38.069519, -78.857363 38.068708, -78.857094 38.069153, -78.857034 38.069111, -78.856753 38.068851, -78.856627 38.06878, -78.85658 38.068756, -78.856551 38.068742, -78.856135 38.069108, -78.856229 38.069195, -78.85626 38.069291, -78.856864 38.069563, -78.857069 38.069655, -78.857247 38.069734, -78.857713 38.069938, -78.858321 38.070203, -78.85843 38.070251, -78.858564 38.070309, -78.858782 38.070429, -78.858433 38.070905, -78.858216 38.07105, -78.858094 38.071295, -78.857505 38.072094, -78.857152 38.07256, -78.857065 38.072675, -78.856982 38.072785, -78.856949 38.072828, -78.856913 38.072875, -78.856864 38.072939, -78.856753 38.073084, -78.856747 38.0731, -78.856731 38.073118, -78.855796 38.074375, -78.855616 38.074538, -78.855731 38.074568, -78.855918 38.074652, -78.858274 38.075711, -78.858544 38.075833, -78.859029 38.076041, -78.85929 38.076179, -78.859666 38.076354, -78.86005 38.076536, -78.860418 38.076716, -78.860935 38.076961, -78.861142 38.077066, -78.861421 38.077198, -78.861743 38.077371, -78.862259 38.077614, -78.862445 38.077717, -78.862586 38.077796, -78.862105 38.078421, -78.861407 38.07947, -78.861449 38.079498, -78.86151 38.079529, -78.861599 38.079584, -78.861675 38.07965, -78.861785 38.07979, -78.861634 38.079832, -78.861517 38.079873, -78.861366 38.079925, -78.86102 38.080053, -78.86197 38.080448, -78.86343 38.081055, -78.863792 38.081205, -78.864268 38.081402, -78.865123 38.081768, -78.865027 38.082037, -78.86492 38.082382, -78.864431 38.083962, -78.864255 38.084499, -78.864079 38.085036, -78.86399 38.085329, -78.863901 38.085622, -78.863832 38.085592, -78.863727 38.085512, -78.863656 38.085476, -78.86361 38.085461, -78.862977 38.0853, -78.862683 38.085191, -78.862616 38.085152, -78.862562 38.085103, -78.862517 38.085048, -78.862476 38.085019, -78.862425 38.085002, -78.862052 38.084943, -78.861467 38.084823, -78.860959 38.084693, -78.860589 38.084645, -78.860329 38.084571, -78.860295 38.084571, -78.86022 38.084585, -78.860046 38.084678, -78.860066 38.084603, -78.860066 38.084526, -78.860049 38.084451, -78.860014 38.08438, -78.859974 38.084327, -78.859891 38.084234, -78.859835 38.084146, -78.859797 38.084052, -78.859768 38.083921, -78.859734 38.083837, -78.859684 38.083758, -78.859305 38.083295, -78.859306 38.083259, -78.859292 38.083202, -78.859193 38.083033, -78.859135 38.082911, -78.859156 38.08286, -78.8592 38.08279, -78.859283 38.082719, -78.859343 38.082692, -78.859452 38.082677, -78.859524 38.082676, -78.859462 38.082656, -78.859306 38.08263, -78.85919 38.082602, -78.859068 38.082541, -78.858915 38.082436, -78.858884 38.082426, -78.858795 38.082343, -78.858733 38.082298, -78.858687 38.082274, -78.85859 38.082239, -78.858542 38.082214, -78.858512 38.082191, -78.858599 38.08188, -78.858231 38.081226, -78.858019 38.081116, -78.857883 38.08117, -78.857755 38.081205, -78.857621 38.081225, -78.857547 38.08123, -78.857394 38.081223, -78.857194 38.081199, -78.857138 38.081192, -78.856828 38.081145, -78.856634 38.081116, -78.85642 38.081089, -78.856182 38.081063, -78.856157 38.081063, -78.855966 38.081065, -78.855788 38.081082, -78.855259 38.081159, -78.854323 38.081276, -78.853962 38.081306, -78.853628 38.081327, -78.853456 38.081339, -78.853323 38.082047, -78.853279 38.082064, -78.853245 38.082093, -78.853166 38.082267, -78.853078 38.082498, -78.853022 38.082687, -78.852915 38.083006, -78.852848 38.083228, -78.85279 38.083365, -78.852714 38.083497, -78.852647 38.083885, -78.852612 38.08399, -78.852583 38.084051, -78.852534 38.084225, -78.852472 38.084397, -78.852405 38.084545, -78.852366 38.084676, -78.852311 38.084959, -78.852283 38.085035, -78.852277 38.085067, -78.852238 38.085165, -78.852205 38.085278, -78.852169 38.085484, -78.852147 38.085544, -78.852067 38.08566, -78.852043 38.085722, -78.85202 38.085893, -78.852006 38.08596, -78.851931 38.086072, -78.851752 38.086607, -78.85164 38.086871, -78.8516 38.086996, -78.851526 38.087189, -78.851232 38.088104, -78.851107 38.088414, -78.851017 38.088711, -78.850963 38.088914, -78.850899 38.089096, -78.850848 38.089239, -78.850583 38.090046, -78.85048 38.09039, -78.850288 38.090891, -78.85023 38.091042, -78.850045 38.091606, -78.849991 38.091732, -78.849924 38.091927, -78.849779 38.092207, -78.849641 38.092661, -78.84953 38.093097, -78.849454 38.093528, -78.849418 38.093781, -78.849344 38.094085, -78.849309 38.094249, -78.849282 38.094409, -78.849263 38.094574, -78.849214 38.094874, -78.848611 38.094714, -78.848235 38.094619, -78.848135 38.094586, -78.848044 38.094552, -78.847862 38.094472, -78.84766 38.094362, -78.847316 38.094109, -78.846566 38.093509, -78.84607 38.093109, -78.845384 38.092596, -78.845115 38.092401, -78.845031 38.09248, -78.844984 38.092544, -78.844876 38.09276, -78.844841 38.092898, -78.844833 38.092971, -78.844842 38.093087, -78.844902 38.09405, -78.844967 38.094117, -78.845008 38.094183, -78.845123 38.094413, -78.845123 38.094439, -78.84514 38.094462, -78.845162 38.094527, -78.845287 38.094897, -78.845273 38.094941, -78.845203 38.095095, -78.845155 38.095259, -78.845175 38.095325, -78.845189 38.095353, -78.845328 38.095446, -78.845377 38.095474, -78.845516 38.095572, -78.845606 38.09566, -78.845655 38.095721, -78.845857 38.096028, -78.84594 38.096138, -78.846031 38.09622, -78.846163 38.096352, -78.846281 38.0965, -78.846406 38.096599, -78.846851 38.096978, -78.84699 38.097104, -78.847067 38.097159, -78.847157 38.097203, -78.847324 38.097241, -78.847463 38.097252, -78.847789 38.097236, -78.847942 38.097241, -78.848172 38.097274, -78.848714 38.09734, -78.849019 38.097411, -78.849151 38.09745, -78.849353 38.097521, -78.849589 38.097576, -78.849798 38.097647, -78.849839 38.097642, -78.849979 38.097653, -78.850076 38.097675, -78.850208 38.097718, -78.850764 38.097949, -78.850889 38.098009, -78.851049 38.09807, -78.851243 38.09813, -78.851501 38.098185, -78.851626 38.098185, -78.851862 38.098174, -78.852077 38.098146, -78.852244 38.098113, -78.852592 38.098058, -78.85287 38.098009, -78.853078 38.097981, -78.853273 38.097998, -78.853558 38.09803, -78.853655 38.098036, -78.853738 38.098025, -78.853912 38.097986, -78.85403 38.097964, -78.854343 38.097942, -78.854683 38.097986, -78.854871 38.098019, -78.855177 38.098123, -78.855274 38.098145, -78.855441 38.098222, -78.855622 38.098321, -78.855761 38.098409, -78.855942 38.098535, -78.856199 38.098754, -78.856535 38.098926, -78.856574 38.098946, -78.856811 38.099051, -78.856991 38.09915, -78.857151 38.099254, -78.857269 38.099347, -78.857346 38.099402, -78.857541 38.0996, -78.857728 38.099781, -78.857874 38.099946, -78.857923 38.100017, -78.857993 38.100094, -78.858083 38.100171, -78.858154 38.100221, -78.85834 38.100352, -78.85846 38.100441, -78.85857 38.100522, -78.858799 38.100763, -78.858931 38.100912, -78.859105 38.10106, -78.859258 38.101153, -78.859355 38.101192, -78.859675 38.10129, -78.859911 38.101334, -78.86005 38.101384, -78.86019 38.101455, -78.860347 38.101573, -78.860495 38.101685, -78.860648 38.101784, -78.860548 38.102014, -78.860406 38.102322, -78.860321 38.102523, -78.860239 38.102693, -78.859452 38.104445, -78.859092 38.105266, -78.858422 38.106757, -78.857673 38.106645, -78.85748 38.106608, -78.856898 38.106526, -78.856845 38.106677, -78.856779 38.106963, -78.856728 38.107095, -78.856687 38.107179, -78.858085 38.107509, -78.857974 38.107757, -78.857922 38.107865, -78.857783 38.108194, -78.857619 38.108614, -78.857561 38.108801, -78.857518 38.108959, -78.857427 38.109503, -78.857324 38.109809, -78.85716 38.110543, -78.857066 38.11101, -78.85671 38.112577, -78.8566 38.113036, -78.856451 38.113726, -78.856191 38.114935, -78.855866 38.116378, -78.855824 38.11659, -78.855716 38.117035, -78.855637 38.117325, -78.855457 38.117877, -78.855298 38.118328, -78.85491 38.119426, -78.854903 38.119444, -78.856403 38.119471, -78.85803 38.113876, -78.858278 38.113035, -78.859137 38.110123, -78.859527 38.10879, -78.859786 38.107944, -78.861376 38.108335, -78.861611 38.108387, -78.861582 38.108459, -78.861524 38.108623, -78.861476 38.108822, -78.861453 38.108952, -78.861441 38.109115, -78.861446 38.109329, -78.861471 38.109513, -78.861585 38.109816, -78.861736 38.110152, -78.861863 38.110461, -78.861947 38.110693, -78.86198 38.110728, -78.862015 38.11075, -78.862066 38.110766, -78.862117 38.110767, -78.862009 38.110472, -78.861926 38.110208, -78.861793 38.109884, -78.861668 38.109637, -78.861585 38.109445, -78.861557 38.109352, -78.861529 38.109192, -78.861522 38.109066, -78.861529 38.108962, -78.86157 38.108769, -78.861633 38.108594, -78.861709 38.108434, -78.861723 38.108412, -78.861903 38.108121, -78.862285 38.107649, -78.862561 38.107385, -78.862137 38.10732, -78.861968 38.107294, -78.860208 38.107022, -78.861157 38.103551, -78.861649 38.102108, -78.861755 38.101774, -78.862241 38.100359, -78.862736 38.098634, -78.862858 38.098193, -78.863349 38.097231, -78.864147 38.096268, -78.864385 38.095611, -78.866882 38.092545, -78.867098 38.091932, -78.867436 38.091825, -78.867487 38.091777, -78.867936 38.091354, -78.86906 38.092044, -78.869816 38.092419, -78.870319 38.092678, -78.870576 38.092797, -78.871215 38.093119, -78.871252 38.093133, -78.871317 38.093145, -78.871371 38.093144, -78.871435 38.093131, -78.871454 38.093122, -78.871499 38.0931, -78.871582 38.093042, -78.871652 38.092975, -78.871822 38.092751, -78.871993 38.092491, -78.872019 38.092431, -78.872132 38.092166, -78.872175 38.092065, -78.872249 38.092062, -78.872277 38.092003, -78.872394 38.090805, -78.872397 38.090723, -78.872393 38.09056, -78.87238 38.090477, -78.872179 38.089842, -78.872168 38.089762, -78.8723 38.089781, -78.872468 38.089791, -78.872636 38.089786, -78.872694 38.089789, -78.872766 38.089781, -78.872832 38.089757, -78.872888 38.089721, -78.872923 38.089684, -78.87298 38.089562, -78.873021 38.089436, -78.873035 38.089417, -78.873058 38.089404, -78.873092 38.089399, -78.873168 38.089419, -78.873294 38.089442, -78.873422 38.089451, -78.873551 38.089446, -78.873677 38.089427, -78.873799 38.089395, -78.87385 38.089369, -78.873927 38.089318, -78.87399 38.089256, -78.874144 38.089046, -78.874316 38.088914, -78.874396 38.088897, -78.874206 38.090021, -78.874107 38.0906, -78.874108 38.090677, -78.874128 38.090753, -78.874187 38.090865, -78.874219 38.09089, -78.874243 38.090899, -78.874296 38.090907, -78.874332 38.090904, -78.874373 38.090886, -78.874426 38.090837, -78.874478 38.090808, -78.874539 38.090791, -78.874619 38.090793, -78.874679 38.090788, -78.874829 38.090752, -78.875036 38.090687, -78.875153 38.090639, -78.875227 38.09062, -78.875342 38.090614, -78.875402 38.090626, -78.875882 38.090829, -78.875928 38.090845, -78.876056 38.090879, -78.876102 38.090885, -78.876181 38.090879, -78.87626 38.090851, -78.876298 38.090819, -78.876328 38.090768, -78.876364 38.090721, -78.876443 38.090662, -78.87649 38.09062, -78.876525 38.090573, -78.876564 38.090465, -78.876618 38.090176, -78.876599 38.089862, -78.876605 38.089733, -78.87658 38.089474, -78.87657 38.08941, -78.87649 38.089169, -78.876425 38.089056, -78.876412 38.089041, -78.876379 38.089019, -78.876312 38.089, -78.876166 38.088978, -78.875923 38.088954, -78.875765 38.08893, -78.875725 38.088911, -78.875695 38.08888, -78.875684 38.088841, -78.875715 38.088646, -78.87669 38.088754, -78.876831 38.088768, -78.876905 38.088777, -78.876979 38.088784, -78.877043 38.089739, -78.877029 38.089871, -78.876994 38.089975, -78.877063 38.090173, -78.877083 38.090387, -78.877108 38.091175, -78.877132 38.091908, -78.877717 38.091413, -78.878001 38.091173, -78.878343 38.091368, -78.878437 38.091305, -78.878478 38.091462, -78.878521 38.091464, -78.878529 38.091633, -78.878568 38.092104, -78.878562 38.092218, -78.878552 38.092298, -78.878482 38.092483, -78.878451 38.092545, -78.878104 38.093088, -78.878061 38.093171, -78.87803 38.093256, -78.877843 38.093991, -78.877808 38.094161, -78.878036 38.094207, -78.878293 38.094279, -78.878386 38.09432, -78.878472 38.094369, -78.878537 38.094417, -78.878598 38.094461, -78.878636 38.094496, -78.878833 38.094744, -78.87889 38.094799, -78.878971 38.094862, -78.879038 38.094895, -78.879161 38.094946, -78.87927 38.094978, -78.879347 38.094991, -78.879387 38.095008, -78.879472 38.095071, -78.879566 38.095153, -78.879632 38.095191, -78.879722 38.095222, -78.879869 38.095288, -78.879904 38.095308, -78.879969 38.095364, -78.879999 38.095407, -78.880031 38.09547, -78.880087 38.095531, -78.880123 38.09555, -78.880183 38.095565, -78.880277 38.095574, -78.880335 38.095569, -78.880391 38.095554, -78.880434 38.095532, -78.880475 38.095491, -78.880498 38.095441, -78.880511 38.095383, -78.880506 38.09534, -78.880472 38.095297, -78.880451 38.095277, -78.8803 38.095166, -78.880253 38.09511, -78.880216 38.095032, -78.880208 38.094987, -78.880213 38.094711, -78.880225 38.094109, -78.880363 38.094312, -78.880608 38.094599, -78.880863 38.094808, -78.881428 38.095089, -78.881958 38.095247, -78.882083 38.095273, -78.882225 38.095288, -78.882361 38.095289, -78.882494 38.095276, -78.882599 38.095255, -78.882735 38.095216, -78.882841 38.095175, -78.882966 38.095113, -78.883206 38.094962, -78.883384 38.094837, -78.88351 38.094754, -78.883653 38.094676, -78.8837 38.09466, -78.883726 38.094651, -78.884444 38.094866, -78.885049 38.095195, -78.885495 38.095448, -78.886683 38.096134, -78.887901 38.096855, -78.887979 38.096899, -78.888255 38.097055, -78.888687 38.097283, -78.889363 38.097661, -78.890212 38.098134, -78.890246 38.098155, -78.890344 38.098216, -78.890373 38.098234, -78.890457 38.09837, -78.890526 38.098483, -78.890515 38.098528, -78.89049 38.098577, -78.890218 38.098979, -78.891203 38.099358, -78.891619 38.099518, -78.892389 38.099795, -78.892808 38.099957, -78.893258 38.100122, -78.89472 38.100675, -78.895314 38.100893, -78.896014 38.101159, -78.896436 38.10132, -78.897019 38.101529, -78.897487 38.101708, -78.898134 38.101952, -78.899248 38.102362, -78.899302 38.102367, -78.899352 38.102358, -78.899489 38.102573, -78.900072 38.103467, -78.900292 38.1038, -78.900458 38.104066, -78.900797 38.104647, -78.900834 38.104711, -78.901605 38.106019, -78.901682 38.106119, -78.901774 38.106211, -78.901811 38.106243, -78.901918 38.106319, -78.902022 38.106373, -78.902135 38.106414, -78.902254 38.106443, -78.902336 38.106454, -78.902445 38.106453, -78.902436 38.106099, -78.902419 38.105456, -78.902403 38.105196, -78.902365 38.104816, -78.902316 38.104514, -78.902233 38.104143, -78.902142 38.103814, -78.902071 38.103598, -78.901941 38.103243, -78.901791 38.102904, -78.90177 38.102863, -78.901661 38.102649, -78.901537 38.102426, -78.901406 38.102209, -78.901284 38.102017, -78.901012 38.101631, -78.900872 38.101442, -78.900731 38.101254, -78.900407 38.100824, -78.900118 38.100441, -78.89987 38.100109, -78.899517 38.099638, -78.899346 38.0994, -78.89913 38.099079, -78.898978 38.098819, -78.898907 38.098679, -78.898778 38.098362, -78.89873 38.098214, -78.898685 38.098046, -78.898649 38.097912, -78.899368 38.09813, -78.902231 38.098483, -78.902327 38.098494, -78.902595 38.098733, -78.903534 38.099978, -78.903795 38.099802, -78.906499 38.097438, -78.912479 38.092106, -78.912753 38.092237, -78.9128 38.09226, -78.913048 38.09238, -78.913794 38.092847, -78.915057 38.09192, -78.915077 38.091905, -78.915428 38.091647, -78.915469 38.091675, -78.915592 38.091559, -78.915706 38.091446, -78.916286 38.090869, -78.916365 38.090917, -78.917024 38.091447, -78.917033 38.091454, -78.918867 38.092506, -78.919092 38.092645, -78.919797 38.093079, -78.922339 38.094869, -78.922793 38.095189, -78.923349 38.095491, -78.92425 38.095766, -78.926215 38.096367, -78.926964 38.096574, -78.929719 38.097228, -78.932104 38.097987, -78.935313 38.09939, -78.936305 38.099926, -78.937106 38.10034, -78.940357 38.101835, -78.941491 38.102131, -78.942796 38.10231, -78.942844 38.10212, -78.942921 38.102046, -78.9433 38.10191, -78.943439 38.101853, -78.943604 38.101769, -78.943746 38.101711, -78.943975 38.101602, -78.944124 38.10154, -78.944302 38.101451, -78.944396 38.10139, -78.944477 38.101318, -78.944565 38.101208, -78.944648 38.101074, -78.944839 38.100709, -78.94516 38.100176, -78.94531 38.099955, -78.945383 38.099838, -78.94544 38.099703, -78.945517 38.099552, -78.945713 38.099242, -78.945824 38.099026, -78.945882 38.09895, -78.945955 38.098909, -78.946033 38.098894, -78.946122 38.098893, -78.946223 38.098933, -78.946636 38.099226, -78.946882 38.09939, -78.947127 38.099577, -78.947229 38.099647, -78.947302 38.099659, -78.947365 38.099655, -78.94743 38.099631, -78.947566 38.099559, -78.947786 38.099454, -78.94803 38.099352, -78.948181 38.099275, -78.948345 38.099176, -78.948397 38.099145, -78.948455 38.099139, -78.948525 38.099151, -78.948724 38.099233, -78.948961 38.099361, -78.949078 38.099412, -78.949167 38.099452, -78.949397 38.099546, -78.949518 38.099618, -78.949553 38.099638, -78.949705 38.099751, -78.94983 38.099876, -78.949855 38.099884, -78.949921 38.099921, -78.95027 38.100106, -78.951062 38.100437, -78.951099 38.100443, -78.951186 38.100457, -78.951283 38.100466, -78.951368 38.100454, -78.951484 38.100413, -78.952148 38.100215, -78.953215 38.099911, -78.953369 38.099871, -78.953493 38.099852, -78.953586 38.099806, -78.954061 38.099617, -78.954814 38.099382, -78.955305 38.099248, -78.955807 38.099136, -78.956217 38.099052, -78.957013 38.098926, -78.957833 38.09885, -78.958255 38.098824, -78.959128 38.098761, -78.959795 38.098695, -78.961122 38.098598, -78.961882 38.098594, -78.962322 38.098553, -78.96264 38.098518, -78.962953 38.098493, -78.964269 38.098367, -78.964702 38.098315, -78.964831 38.098282, -78.964813 38.098256, -78.964796 38.098215, -78.964791 38.09816, -78.964808 38.098108, -78.964843 38.098062, -78.964896 38.098025, -78.964969 38.097994, -78.965048 38.097975, -78.965134 38.097969, -78.965268 38.097974, -78.9654 38.097995, -78.965527 38.09803, -78.965736 38.098112, -78.965822 38.098163, -78.966129 38.098163, -78.966199 38.098151, -78.966274 38.098121, -78.966334 38.098074, -78.966359 38.098038, -78.966363 38.098002, -78.966349 38.097967, -78.966282 38.097919, -78.966521 38.097833, -78.966852 38.097703, -78.966944 38.097663, -78.967097 38.09758, -78.967153 38.097533)), ((-78.967153 38.097533, -78.967205 38.097557, -78.96746 38.097701, -78.967719 38.097869, -78.968075 38.09811, -78.968263 38.098244, -78.966824 38.098325, -78.966853 38.098346, -78.967564 38.098834, -78.967682 38.098919, -78.967797 38.099003, -78.968426 38.099453, -78.968603 38.099566, -78.967747 38.100218, -78.966882 38.100865, -78.96531 38.102041, -78.965203 38.102121, -78.964462 38.102677, -78.964123 38.102927, -78.964057 38.10288, -78.964023 38.102877, -78.963961 38.102876, -78.9639 38.10284, -78.963539 38.102542, -78.963524 38.102523, -78.96329 38.102309, -78.963214 38.102357, -78.96316 38.102392, -78.963115 38.10242, -78.963068 38.102451, -78.963001 38.102497, -78.962947 38.102536, -78.962904 38.102571, -78.962862 38.102604, -78.962818 38.102637, -78.962773 38.10267, -78.96273 38.102704, -78.962687 38.102739, -78.962643 38.102773, -78.962601 38.102805, -78.962561 38.102839, -78.962511 38.102878, -78.962465 38.102919, -78.962423 38.10295, -78.962375 38.102989, -78.962336 38.103025, -78.962246 38.103107, -78.96283 38.103618, -78.962897 38.103678, -78.962912 38.103692, -78.962995 38.103765, -78.962853 38.103869, -78.962598 38.104068, -78.96255 38.104111, -78.962507 38.104157, -78.962436 38.104256, -78.962421 38.104336, -78.96242 38.104386, -78.962171 38.104485, -78.96192 38.104565, -78.961797 38.104598, -78.961495 38.104647, -78.960268 38.104793, -78.959303 38.104913, -78.958718 38.104996, -78.958386 38.10506, -78.95739 38.105293, -78.957158 38.105356, -78.956714 38.10549, -78.956289 38.105625, -78.956358 38.105857, -78.956406 38.106101, -78.956422 38.10634, -78.956249 38.10636, -78.956157 38.106381, -78.956068 38.10641, -78.956004 38.106436, -78.955915 38.106486, -78.955824 38.10655, -78.955761 38.106612, -78.955173 38.107396, -78.954936 38.107723, -78.954556 38.108219, -78.95446 38.108371, -78.954385 38.108515, -78.954231 38.108779, -78.953904 38.109229, -78.953238 38.110107, -78.95285 38.110625, -78.952618 38.110945, -78.95218 38.111535, -78.952094 38.111679, -78.952031 38.111811, -78.95198 38.111955, -78.951935 38.112167, -78.951925 38.112246, -78.951924 38.112326, -78.951937 38.112415, -78.951956 38.11248, -78.952 38.112573, -78.952097 38.112723, -78.952188 38.112837, -78.952286 38.112926, -78.952378 38.112982, -78.952523 38.113055, -78.952579 38.113075, -78.952732 38.113116, -78.952929 38.113148, -78.953049 38.11315, -78.953408 38.11314, -78.953454 38.113143, -78.95357 38.113151, -78.953659 38.113165, -78.953749 38.113188, -78.953959 38.113262, -78.954079 38.113314, -78.954054 38.113347, -78.953804 38.113631, -78.953697 38.113763, -78.95366 38.113827, -78.953642 38.113875, -78.953626 38.113966, -78.953624 38.114069, -78.953649 38.114224, -78.953684 38.114326, -78.953748 38.114433, -78.953797 38.114491, -78.953952 38.114645, -78.954084 38.114786, -78.954163 38.114906, -78.954274 38.114865, -78.954372 38.114823, -78.954536 38.114743, -78.954737 38.114629, -78.954913 38.114536, -78.955165 38.114417, -78.955459 38.114288, -78.955956 38.114109, -78.956389 38.113966, -78.956491 38.113936, -78.957263 38.113707, -78.957594 38.113631, -78.957673 38.113619, -78.957758 38.113614, -78.957821 38.113615, -78.957989 38.113633, -78.958065 38.113649, -78.958229 38.113695, -78.958353 38.11375, -78.958443 38.113802, -78.958579 38.113889, -78.958642 38.113941, -78.958715 38.114013, -78.958853 38.113945, -78.959226 38.113786, -78.959636 38.1136, -78.959762 38.113535, -78.959964 38.113418, -78.960154 38.113282, -78.960321 38.113144, -78.960469 38.112992, -78.960579 38.112858, -78.960671 38.11273, -78.960771 38.112565, -78.960966 38.112135, -78.960985 38.112147, -78.961031 38.112187, -78.961058 38.11223, -78.961134 38.112411, -78.961162 38.112464, -78.961248 38.112569, -78.961343 38.112672, -78.961462 38.112788, -78.961428 38.112858, -78.961372 38.112941, -78.96124 38.11299, -78.961074 38.113133, -78.961018 38.11316, -78.960935 38.113166, -78.960844 38.113122, -78.960768 38.113138, -78.960664 38.113177, -78.960344 38.113479, -78.960024 38.113721, -78.959545 38.113962, -78.959162 38.114105, -78.959016 38.114177, -78.958905 38.114243, -78.958801 38.11432, -78.958745 38.114347, -78.958558 38.114413, -78.958349 38.114517, -78.958182 38.114627, -78.958036 38.114693, -78.957946 38.114781, -78.957897 38.114847, -78.95764 38.114929, -78.95755 38.115045, -78.95748 38.1151, -78.956959 38.115177, -78.956751 38.115242, -78.956674 38.11527, -78.956507 38.115418, -78.956368 38.115451, -78.956299 38.11549, -78.956257 38.115534, -78.956285 38.115665, -78.956285 38.115715, -78.956194 38.115858, -78.956201 38.116022, -78.95616 38.116176, -78.95609 38.116292, -78.956021 38.116357, -78.955993 38.116456, -78.955986 38.116528, -78.95593 38.116599, -78.955757 38.116742, -78.955541 38.116868, -78.955326 38.116973, -78.955012 38.117013, -78.954887 38.117262, -78.954797 38.117408, -78.954706 38.117526, -78.954568 38.117678, -78.95419 38.117993, -78.953696 38.118387, -78.951322 38.120316, -78.951068 38.120527, -78.950615 38.120891, -78.950061 38.12136, -78.949921 38.121503, -78.949591 38.121908, -78.948611 38.123245, -78.948504 38.123409, -78.948437 38.123541, -78.94839 38.123679, -78.948354 38.123871, -78.948313 38.124178, -78.948287 38.124465, -78.948209 38.125138, -78.94818 38.125304, -78.948136 38.125427, -78.948065 38.125576, -78.947978 38.12572, -78.947801 38.125977, -78.947707 38.126122, -78.947521 38.126412, -78.947424 38.126598, -78.947353 38.126766, -78.947279 38.126998, -78.947136 38.127508, -78.947068 38.127683, -78.947 38.127813, -78.946827 38.12806, -78.946687 38.128276, -78.946885 38.128489, -78.947608 38.129353, -78.947896 38.12968, -78.948698 38.130549, -78.953894 38.131891, -78.954979 38.132173, -78.955976 38.13243, -78.956017 38.132286, -78.956128 38.131898, -78.956233 38.131405, -78.95629 38.131165, -78.95637 38.130863, -78.95645 38.130485, -78.956603 38.129847, -78.956685 38.129561, -78.956704 38.129516, -78.956764 38.129408, -78.957025 38.129063, -78.957301 38.128726, -78.957481 38.128465, -78.957631 38.128264, -78.957793 38.128073, -78.958086 38.127788, -78.958441 38.127454, -78.959152 38.126832, -78.959329 38.126634, -78.959542 38.126339, -78.959724 38.126109, -78.960234 38.125473, -78.960424 38.125266, -78.960606 38.125094, -78.960867 38.124862, -78.96102 38.124741, -78.961424 38.124461, -78.962044 38.124044, -78.962192 38.123961, -78.962829 38.123568, -78.963274 38.123281, -78.964142 38.122731, -78.964352 38.12259, -78.96444 38.122514, -78.964545 38.122406, -78.964658 38.122251, -78.964735 38.122134, -78.964814 38.121987, -78.964879 38.121885, -78.964991 38.12171, -78.965221 38.121411, -78.965792 38.120772, -78.965924 38.120643, -78.966071 38.120525, -78.966233 38.120418, -78.966635 38.120254, -78.966801 38.120169, -78.966957 38.120071, -78.967095 38.119968, -78.967218 38.119855, -78.967325 38.119732, -78.967372 38.119667, -78.967451 38.119536, -78.967591 38.119223, -78.96761 38.119167, -78.967661 38.119059, -78.967728 38.118957, -78.96781 38.118861, -78.968589 38.118134, -78.968853 38.117893, -78.969096 38.117678, -78.969651 38.117241, -78.970371 38.116726, -78.970622 38.116529, -78.970854 38.116346, -78.97091 38.116297, -78.971011 38.116193, -78.971138 38.116035, -78.971233 38.115941, -78.971342 38.115856, -78.971462 38.115782, -78.971762 38.115638, -78.971977 38.115523, -78.972212 38.115364, -78.972305 38.115301, -78.972872 38.114865, -78.972925 38.114824, -78.973597 38.114379, -78.973835 38.114215, -78.974255 38.113915, -78.974342 38.113839, -78.974598 38.113573, -78.974652 38.113509, -78.975302 38.112736, -78.975754 38.112158, -78.975977 38.111894, -78.976262 38.111595, -78.976673 38.111212, -78.977174 38.110784, -78.977772 38.11023, -78.978122 38.109897, -78.97857 38.109504, -78.978934 38.109224, -78.979325 38.108945, -78.980275 38.108287, -78.98046 38.108144, -78.980558 38.108046, -78.980689 38.107877, -78.981138 38.107162, -78.981253 38.106984, -78.981074 38.106913, -78.980782 38.106869, -78.980574 38.106871, -78.979974 38.10692, -78.979703 38.106947, -78.979587 38.106963, -78.979355 38.107003, -78.979241 38.10703, -78.979129 38.107065, -78.978894 38.107169, -78.978598 38.106798, -78.978467 38.106544, -78.978442 38.106354, -78.97848 38.106159, -78.978563 38.105989, -78.978733 38.10579, -78.979759 38.105041, -78.979894 38.105102, -78.981483 38.105753, -78.981707 38.105845, -78.982018 38.105973, -78.982424 38.106136, -78.982542 38.106182, -78.982887 38.106301, -78.983712 38.106557, -78.9848 38.106883, -78.984941 38.106925, -78.985714 38.107198, -78.98592 38.107275, -78.986189 38.107375, -78.986526 38.1075, -78.986988 38.107676, -78.987534 38.107873, -78.987649 38.107911, -78.98797 38.108019, -78.988544 38.108183, -78.988644 38.108212, -78.989387 38.10841, -78.990389 38.108675, -78.990488 38.1087, -78.991141 38.10887, -78.990899 38.10925, -78.990816 38.109476, -78.990782 38.10962, -78.990769 38.109763, -78.991006 38.109817, -78.991055 38.109705, -78.991147 38.109516, -78.991293 38.10914, -78.991359 38.108926, -78.991915 38.109071, -78.992034 38.109098, -78.992838 38.109278, -78.992762 38.109506, -78.992687 38.109735, -78.992508 38.110298, -78.992347 38.110832, -78.992146 38.111636, -78.992106 38.111797, -78.991769 38.11315, -78.99137 38.114721, -78.991184 38.115447, -78.991003 38.115579, -78.990459 38.115955, -78.990123 38.116186, -78.990054 38.116157, -78.989984 38.116144, -78.989934 38.116146, -78.989889 38.116164, -78.989583 38.116373, -78.989003 38.116759, -78.98866 38.116979, -78.988786 38.117096, -78.988504 38.117287, -78.987412 38.118033, -78.987194 38.118155, -78.987058 38.118219, -78.986834 38.118326, -78.986374 38.118594, -78.985837 38.118943, -78.985752 38.118998, -78.985595 38.119102, -78.985303 38.119298, -78.985275 38.119321, -78.985213 38.119384, -78.985173 38.119443, -78.985161 38.1195, -78.985169 38.119554, -78.985196 38.119601, -78.985578 38.119964, -78.985515 38.120002, -78.985147 38.120246, -78.985109 38.12028, -78.98508 38.120319, -78.985068 38.120363, -78.985072 38.120398, -78.985092 38.12044, -78.985178 38.120526, -78.985591 38.120896, -78.985979 38.121254, -78.986179 38.121426, -78.986747 38.121033, -78.986873 38.121147, -78.987525 38.121742, -78.988254 38.122416, -78.988377 38.122514, -78.988418 38.122541, -78.988462 38.122556, -78.988509 38.122561, -78.988569 38.122556, -78.988624 38.122537, -78.988937 38.122331, -78.989643 38.121845, -78.990482 38.121274, -78.991132 38.12083, -78.991781 38.120385, -78.991844 38.120448, -78.991908 38.12051, -78.992007 38.120609, -78.99225 38.120837, -78.992187 38.120884, -78.992111 38.120955, -78.991922 38.121151, -78.991887 38.121199, -78.991868 38.121264, -78.991871 38.121304, -78.991886 38.121338, -78.991919 38.121375, -78.991965 38.121402, -78.992019 38.12141, -78.992095 38.121411, -78.99241 38.121361, -78.992891 38.121302, -78.993054 38.121276, -78.993371 38.121238, -78.993537 38.121209, -78.993723 38.121166, -78.993822 38.12113, -78.993923 38.121075, -78.993967 38.121032, -78.993994 38.120982, -78.994016 38.120884, -78.994021 38.120752, -78.994005 38.120647, -78.993986 38.120595, -78.993929 38.120508, -78.993849 38.120433, -78.993784 38.120387, -78.993708 38.120353, -78.993716 38.12028, -78.993805 38.119755, -78.993624 38.11963, -78.993444 38.119504, -78.993487 38.119486, -78.993536 38.119476, -78.993591 38.119477, -78.993672 38.119497, -78.993746 38.11953, -78.99381 38.119575, -78.994188 38.119814, -78.994242 38.119841, -78.994316 38.119865, -78.994411 38.119874, -78.994486 38.119867, -78.994581 38.119844, -78.994669 38.119808, -78.994875 38.11969, -78.995036 38.119578, -78.995157 38.119481, -78.995294 38.119351, -78.995417 38.119212, -78.995458 38.119159, -78.995474 38.119113, -78.995472 38.119066, -78.995451 38.119022, -78.995118 38.118743, -78.995082 38.118703, -78.995045 38.118644, -78.995029 38.118573, -78.995021 38.118452, -78.995007 38.118395, -78.994978 38.118353, -78.994945 38.118325, -78.994888 38.118297, -78.994822 38.11828, -78.994768 38.118277, -78.99471 38.118298, -78.994647 38.118336, -78.994565 38.118414, -78.994516 38.118451, -78.994386 38.118536, -78.994259 38.118594, -78.994016 38.118723, -78.993876 38.118813, -78.993809 38.118874, -78.993799 38.118856, -78.99366 38.118721, -78.992747 38.117901, -78.99169 38.116936, -78.991801 38.116859, -78.991877 38.116787, -78.991995 38.116606, -78.992036 38.116552, -78.992299 38.116299, -78.992682 38.115956, -78.993658 38.1151, -78.993909 38.114872, -78.994087 38.114732, -78.994198 38.114747, -78.994333 38.114781, -78.994635 38.114887, -78.994715 38.114899, -78.994812 38.114896, -78.994902 38.114874, -78.995046 38.114815, -78.995208 38.114725, -78.995322 38.114678, -78.995442 38.114643, -78.995607 38.114604, -78.995769 38.114552, -78.995793 38.114551, -78.995834 38.114558, -78.995864 38.114573, -78.9959 38.114606, -78.995962 38.114683, -78.996008 38.114766, -78.996037 38.114854, -78.996095 38.114993, -78.996183 38.115163, -78.996291 38.115308, -78.99644 38.115472, -78.996529 38.115583, -78.996615 38.115725, -78.996677 38.115818, -78.996749 38.115907, -78.997005 38.116172, -78.997124 38.116339, -78.997298 38.116659, -78.997376 38.11685, -78.997501 38.117048, -78.99764 38.117367, -78.997773 38.117713, -78.997919 38.117927, -78.998044 38.118333, -78.998163 38.118586, -78.998594 38.11936, -78.998713 38.119634, -78.998998 38.119936, -78.999235 38.120277, -78.999256 38.120474, -78.999207 38.120738, -78.999187 38.121067, -78.999199 38.121165, -78.999215 38.121282, -78.999417 38.121941, -78.999556 38.122188, -78.99966 38.122457, -78.999744 38.122391, -78.999948 38.1223, -79.000087 38.122251, -79.000303 38.122141, -79.000421 38.122092, -79.00063 38.122174, -79.001012 38.122367, -79.001276 38.122449, -79.001554 38.122576, -79.001828 38.122733, -79.00185 38.122753, -79.001886 38.122766, -79.001985 38.122823, -79.002318 38.12307, -79.002561 38.123219, -79.002839 38.123427, -79.003131 38.123538, -79.003409 38.123604, -79.003639 38.123604, -79.003799 38.123576, -79.004056 38.123467, -79.004459 38.123346, -79.004633 38.123319, -79.005029 38.12333, -79.005168 38.123346, -79.005307 38.123385, -79.005745 38.123577, -79.005954 38.123649, -79.006162 38.12366, -79.006315 38.123649, -79.006489 38.123622, -79.006649 38.123512, -79.00708 38.123194, -79.007359 38.123002, -79.007505 38.122848, -79.007588 38.122683, -79.007609 38.122491, -79.007533 38.122271, -79.007394 38.121985, -79.007151 38.121639, -79.006721 38.121145, -79.006596 38.121018, -79.006443 38.120788, -79.006339 38.120524, -79.006075 38.120019, -79.005992 38.119788, -79.005957 38.119629, -79.005971 38.119508, -79.006041 38.119178, -79.006113 38.11897, -79.006167 38.118657, -79.006201 38.118492, -79.006306 38.118355, -79.006355 38.118256, -79.00632 38.118146, -79.00625 38.118009, -79.00625 38.117926, -79.006306 38.117855, -79.006459 38.117789, -79.006689 38.117712, -79.006862 38.117718, -79.007592 38.117872, -79.007905 38.117955, -79.008114 38.117982, -79.008614 38.118103, -79.009052 38.118241, -79.009469 38.118323, -79.009837 38.118367, -79.010115 38.118368, -79.010268 38.118351, -79.010338 38.118296, -79.010373 38.118175, -79.010394 38.117967, -79.010429 38.11739, -79.010408 38.117214, -79.010277 38.116731, -79.01027 38.116605, -79.010345 38.116373, -79.01037 38.116333, -79.010395 38.11622, -79.010465 38.115962, -79.010584 38.115314, -79.010688 38.114831, -79.010654 38.114683, -79.010668 38.114161, -79.010703 38.113897, -79.010793 38.113771, -79.010877 38.113678, -79.011051 38.11354, -79.011245 38.113414, -79.011364 38.113359, -79.011472 38.113234, -79.011564 38.113275, -79.011635 38.113306, -79.011954 38.113449, -79.012393 38.113649, -79.012251 38.113859, -79.012209 38.113908, -79.012122 38.113994, -79.012081 38.114063, -79.012073 38.114118, -79.012083 38.114173, -79.01211 38.114224, -79.012186 38.114309, -79.012285 38.114396, -79.012367 38.114452, -79.012397 38.114463, -79.01254 38.114472, -79.014687 38.114635, -79.014751 38.114661, -79.015113 38.114821, -79.015458 38.114993, -79.015515 38.115027, -79.015781 38.115183, -79.01601 38.115334, -79.016189 38.115467, -79.016406 38.115628, -79.017705 38.11668, -79.018512 38.117332, -79.01978 38.118358, -79.019615 38.11851, -79.01953 38.11863, -79.019474 38.11878, -79.019411 38.119016, -79.019408 38.119105, -79.019465 38.119231, -79.019526 38.119337, -79.019587 38.119429, -79.019644 38.11948, -79.019769 38.119558, -79.01983 38.119626, -79.019862 38.119681, -79.019853 38.119751, -79.019841 38.119787, -79.019805 38.119834, -79.019737 38.11992, -79.019686 38.120029, -79.019692 38.120139, -79.019714 38.120224, -79.019836 38.12036, -79.020084 38.120615, -79.020319 38.120812, -79.020984 38.1214, -79.021058 38.121495, -79.021053 38.121557, -79.021139 38.121602, -79.02123 38.121558, -79.021264 38.121558, -79.021313 38.121585, -79.021438 38.121684, -79.021612 38.121849, -79.021702 38.12197, -79.021716 38.122041, -79.021816 38.12212, -79.021828 38.121975, -79.021826 38.121896, -79.021794 38.121608, -79.021813 38.121527, -79.02183 38.121515, -79.021879 38.1215, -79.021937 38.121488, -79.021996 38.121485, -79.022044 38.121489, -79.022163 38.121544, -79.02252 38.121085, -79.022571 38.120996, -79.022588 38.120942, -79.022592 38.12088, -79.022558 38.12079, -79.0226 38.120778, -79.022669 38.12073, -79.022707 38.120692, -79.022742 38.120647, -79.022742 38.120613, -79.022768 38.12063, -79.023286 38.121154, -79.023377 38.121208, -79.023424 38.121221, -79.023658 38.121224, -79.023861 38.121206, -79.023951 38.121219, -79.024172 38.121297, -79.024215 38.121334, -79.024189 38.121338, -79.024159 38.121352, -79.024121 38.121383, -79.023752 38.121743, -79.023653 38.121849, -79.023632 38.121904, -79.023624 38.121969, -79.023641 38.122037, -79.023659 38.122074, -79.023703 38.122125, -79.023737 38.122139, -79.023824 38.122159, -79.023893 38.122166, -79.023988 38.122182, -79.02404 38.122203, -79.024149 38.122267, -79.024192 38.122318, -79.024206 38.122366, -79.024207 38.122509, -79.024215 38.122536, -79.02425 38.122557, -79.024319 38.122567, -79.024406 38.122587, -79.024462 38.122634, -79.02448 38.122658, -79.024515 38.122682, -79.024619 38.122652, -79.024992 38.122753, -79.025021 38.122724, -79.025083 38.12264, -79.025252 38.122467, -79.025334 38.122391, -79.025427 38.122331, -79.025468 38.122309, -79.025512 38.122292, -79.025557 38.12228, -79.025664 38.122272, -79.025908 38.12229, -79.025994 38.122289, -79.026067 38.122281, -79.026145 38.122254, -79.026256 38.122208, -79.026547 38.122368, -79.026983 38.122596, -79.027361 38.122771, -79.027847 38.122975, -79.029356 38.12358, -79.029544 38.124706, -79.029564 38.124805, -79.029597 38.124932, -79.02963 38.125027, -79.029766 38.125338, -79.029781 38.125359, -79.029892 38.125456, -79.030022 38.125561, -79.030105 38.125606, -79.030176 38.125627, -79.030326 38.125663, -79.030495 38.12569, -79.030645 38.12573, -79.030709 38.125785, -79.030724 38.125822, -79.030733 38.125863, -79.030664 38.126202, -79.030626 38.126345, -79.030553 38.126557, -79.030508 38.126812, -79.030367 38.127503, -79.030306 38.127701, -79.030294 38.12779, -79.030311 38.127852, -79.030345 38.12791, -79.030378 38.127945, -79.030459 38.127989, -79.03054 38.12802, -79.030646 38.128047, -79.030852 38.128078, -79.030865 38.128021, -79.030971 38.127698, -79.031083 38.127434, -79.031203 38.127173, -79.031272 38.127038, -79.031307 38.12697, -79.031405 38.126818, -79.031479 38.126705, -79.031585 38.126564, -79.031933 38.126037, -79.03201 38.125927, -79.032292 38.125498, -79.032576 38.125683, -79.032703 38.125765, -79.033652 38.126416, -79.034502 38.127005, -79.034754 38.12718, -79.035115 38.127443, -79.035493 38.12774, -79.036167 38.128281, -79.036353 38.128446, -79.036545 38.128616, -79.036784 38.128826, -79.03726 38.129248, -79.037804 38.129749, -79.038096 38.130024, -79.038284 38.1302, -79.038934 38.130809, -79.038777 38.130918, -79.03874 38.130951, -79.038728 38.130963, -79.038708 38.130987, -79.03869 38.131013, -79.038675 38.13104, -79.038663 38.131068, -79.038633 38.131152, -79.038399 38.131802, -79.038328 38.132001, -79.038107 38.13262, -79.037936 38.133064, -79.037879 38.13322, -79.03786 38.133274, -79.037672 38.133792, -79.037448 38.133743, -79.037426 38.133739, -79.037403 38.133737, -79.037368 38.133738, -79.037345 38.133742, -79.037323 38.133747, -79.037301 38.133754, -79.037261 38.133774, -79.037236 38.133793, -79.037223 38.133808, -79.037207 38.133833, -79.037194 38.133853, -79.03717 38.133894, -79.037149 38.133936, -79.037123 38.134, -79.037116 38.134022, -79.037089 38.134121, -79.037061 38.134231, -79.037026 38.134397, -79.037008 38.134496, -79.036989 38.134558, -79.036973 38.134598, -79.036925 38.134696, -79.036846 38.13485, -79.036827 38.134879, -79.036783 38.134941, -79.036692 38.135065, -79.036598 38.135187, -79.036581 38.135206, -79.036546 38.135242, -79.036489 38.135293, -79.036448 38.135325, -79.036426 38.13534, -79.036356 38.135391, -79.036278 38.13544, -79.036224 38.13547, -79.036145 38.135523, -79.035988 38.135632, -79.035968 38.135644, -79.03593 38.13567, -79.035877 38.135712, -79.035845 38.135742, -79.03579 38.135784, -79.035732 38.135824, -79.035695 38.135847, -79.035645 38.135878, -79.035569 38.13593, -79.035521 38.135967, -79.035498 38.135986, -79.035431 38.136046, -79.03541 38.136067, -79.034811 38.136599, -79.034752 38.136646, -79.034738 38.13666, -79.034762 38.136678, -79.034774 38.136689, -79.034785 38.136701, -79.034815 38.13674, -79.034838 38.136772, -79.034885 38.136845, -79.034921 38.136896, -79.034981 38.13697, -79.035024 38.137018, -79.03507 38.137064, -79.035128 38.137106, -79.035148 38.137119, -79.03519 38.137144, -79.035256 38.137177, -79.035302 38.137197, -79.035349 38.137215, -79.035373 38.137223, -79.035805 38.13739, -79.03584 38.137401, -79.035673 38.137936, -79.035142 38.139717, -79.035093 38.139915, -79.035072 38.140007, -79.035065 38.140051, -79.035061 38.140095, -79.03506 38.140161, -79.035066 38.140228, -79.035074 38.140272, -79.035079 38.140294, -79.035105 38.140373, -79.035139 38.14045, -79.035164 38.140501, -79.035192 38.14055, -79.035223 38.140599, -79.035283 38.140669, -79.035326 38.140713, -79.035395 38.140777, -79.035419 38.140798, -79.035469 38.140838, -79.035548 38.140894, -79.035598 38.140926, -79.035644 38.140952, -79.035692 38.140976, -79.035766 38.141009, -79.035868 38.141046, -79.03592 38.141062, -79.035946 38.141069, -79.036027 38.141087, -79.036109 38.1411, -79.036164 38.141106, -79.036247 38.14111, -79.036308 38.14111, -79.036373 38.141108, -79.03647 38.1411, -79.036503 38.141096, -79.036599 38.141081, -79.036662 38.141068, -79.036725 38.141053, -79.036867 38.141013, -79.036916 38.140997, -79.03694 38.140988, -79.036983 38.14097, -79.037046 38.141062, -79.037083 38.141125, -79.037107 38.141168, -79.037133 38.141209, -79.037163 38.141249, -79.037179 38.141269, -79.037193 38.141284, -79.037211 38.141299, -79.037271 38.141341, -79.037311 38.141367, -79.03736 38.14139, -79.037439 38.141417, -79.037503 38.14144, -79.037538 38.141457, -79.037586 38.141485, -79.037631 38.141517, -79.037671 38.141553, -79.037695 38.141578, -79.037726 38.141619, -79.037744 38.141647, -79.037759 38.141677, -79.037771 38.141707, -79.037776 38.141722, -79.037913 38.14202, -79.038044 38.14235, -79.038132 38.142593, -79.038039 38.142611, -79.037956 38.142623, -79.037475 38.142665, -79.037216 38.142686, -79.036973 38.142713, -79.036762 38.14274, -79.036652 38.142759, -79.036517 38.142788, -79.036284 38.142843, -79.036037 38.142915, -79.035868 38.14297, -79.035778 38.143006, -79.035625 38.143076, -79.035454 38.143167, -79.035241 38.143298, -79.035211 38.143316, -79.03508 38.143404, -79.034804 38.143591, -79.034479 38.143811, -79.0344 38.143865, -79.033943 38.144184, -79.033348 38.144594, -79.032962 38.144866, -79.032482 38.145232, -79.032202 38.145474, -79.032142 38.145526, -79.031724 38.145913, -79.031662 38.145972, -79.031561 38.146064, -79.031417 38.146208, -79.031324 38.146307, -79.031232 38.146413, -79.031153 38.146507, -79.031066 38.146613, -79.030992 38.146698, -79.030938 38.146764, -79.029634 38.146713, -79.028316 38.146478, -79.019746 38.14495, -79.019719 38.144945, -79.019241 38.14486, -79.018608 38.144747, -79.018579 38.144802, -79.018355 38.145268, -79.018222 38.145578, -79.018113 38.14585, -79.017965 38.146263, -79.017726 38.14704, -79.017639 38.147358, -79.017387 38.148236, -79.017859 38.148188, -79.01867 38.148107, -79.01899 38.148082, -79.019172 38.148074, -79.019319 38.148071, -79.019344 38.148071, -79.019482 38.148077, -79.019593 38.148084, -79.019708 38.148094, -79.019816 38.148106, -79.020038 38.148137, -79.020174 38.148166, -79.020603 38.148264, -79.020696 38.148282, -79.020783 38.1483, -79.020915 38.14832, -79.021074 38.148339, -79.02124 38.148356, -79.021403 38.148365, -79.021608 38.148371, -79.021778 38.148373, -79.021982 38.148368, -79.022161 38.148359, -79.022327 38.148347, -79.022489 38.148332, -79.02265 38.148317, -79.02288 38.148292, -79.022917 38.148288, -79.023056 38.148271, -79.023263 38.148245, -79.023476 38.148218, -79.023721 38.148189, -79.02385 38.148174, -79.024057 38.148155, -79.02452 38.148109, -79.024988 38.148057, -79.025143 38.148042, -79.025404 38.148015, -79.025893 38.147951, -79.026168 38.147916, -79.026367 38.147889, -79.026708 38.147839, -79.027249 38.147743, -79.02751 38.147695, -79.027705 38.147663, -79.028 38.147611, -79.028445 38.147541, -79.028663 38.147518, -79.028771 38.147512, -79.028847 38.14751, -79.029094 38.147512, -79.029349 38.147524, -79.029581 38.147538, -79.02974 38.147552, -79.029831 38.147564, -79.029938 38.147587, -79.030069 38.147619, -79.030216 38.147661, -79.030239 38.14767, -79.030434 38.147741, -79.031054 38.147985, -79.031505 38.148167, -79.032171 38.148432, -79.032304 38.148482, -79.032413 38.148519, -79.0325 38.148545, -79.032631 38.148578, -79.032815 38.148616, -79.03317 38.148687, -79.033666 38.148783, -79.034299 38.1489, -79.035078 38.14842, -79.03608 38.147599, -79.036115 38.147571, -79.036544 38.147734, -79.036498 38.14736, -79.036518 38.147294, -79.036532 38.147283, -79.036553 38.147283, -79.03713 38.147827, -79.037269 38.147975, -79.037298 38.14802, -79.041717 38.149698, -79.047282 38.149862, -79.047587 38.150585, -79.047594 38.150599, -79.047619 38.150641, -79.047629 38.150654, -79.047663 38.150692, -79.047676 38.150703, -79.04772 38.150733, -79.047743 38.150747, -79.04779 38.150773, -79.047863 38.150809, -79.047999 38.150866, -79.048066 38.150896, -79.048198 38.150957, -79.048261 38.150998, -79.048352 38.151066, -79.04846 38.151136, -79.048567 38.151199, -79.048628 38.151237, -79.048586 38.151269, -79.048542 38.151314, -79.048517 38.151361, -79.048575 38.151395, -79.048632 38.151431, -79.048713 38.151487, -79.048789 38.151547, -79.049157 38.151826, -79.049287 38.151921, -79.049363 38.151972, -79.049415 38.152003, -79.049442 38.152018, -79.049526 38.15206, -79.049584 38.152085, -79.049671 38.152116, -79.049759 38.152144, -79.049893 38.152183, -79.049983 38.152205, -79.050373 38.152296, -79.050602 38.152352, -79.050716 38.152381, -79.051643 38.152641, -79.052007 38.152747, -79.052091 38.152769, -79.052176 38.152789, -79.052262 38.152807, -79.052305 38.152815, -79.052337 38.15282, -79.052651 38.15286, -79.052808 38.152882, -79.052903 38.152892, -79.053092 38.152914, -79.053409 38.152955, -79.053473 38.152962, -79.053667 38.15298, -79.053748 38.152984, -79.053868 38.152986, -79.053909 38.152986, -79.054029 38.152981, -79.054137 38.152972, -79.054208 38.152963, -79.054279 38.152951, -79.054349 38.152937, -79.054418 38.152921, -79.054485 38.152903, -79.054519 38.152893, -79.054618 38.15286, -79.054714 38.152822, -79.054826 38.152764, -79.055144 38.152877, -79.055394 38.152966, -79.056139 38.153227, -79.056546 38.153372, -79.056653 38.153411, -79.055838 38.153562, -79.055646 38.153616, -79.05184 38.154704, -79.051268 38.154982, -79.050729 38.155295, -79.050083 38.156065, -79.049779 38.156716, -79.049487 38.157878, -79.049104 38.158835, -79.048611 38.160065, -79.048471 38.160368, -79.04885 38.160501, -79.048631 38.160846, -79.048386 38.161195, -79.048141 38.161527, -79.047813 38.161935, -79.047404 38.162417, -79.047029 38.162835, -79.046945 38.162922, -79.046748 38.163125, -79.04648 38.163388, -79.046535 38.163666, -79.046549 38.163698, -79.04656 38.163712, -79.046605 38.163754, -79.046632 38.163774, -79.04666 38.163792, -79.046728 38.163827, -79.046768 38.163851, -79.046807 38.163877, -79.046843 38.163905, -79.046878 38.163935, -79.046894 38.16395, -79.046909 38.163966, -79.047032 38.164085, -79.046808 38.164188, -79.046661 38.164259, -79.046443 38.16437, -79.046349 38.164419, -79.046328 38.164432, -79.046301 38.164454, -79.046279 38.16448, -79.046262 38.164508, -79.046254 38.164528, -79.046247 38.164559, -79.046255 38.164621, -79.046263 38.164663, -79.046281 38.164725, -79.046297 38.164765, -79.046306 38.164785, -79.046322 38.164813, -79.046335 38.164842, -79.046345 38.164873, -79.046353 38.164903, -79.046357 38.164934, -79.046359 38.164965, -79.046357 38.164996, -79.046326 38.16508, -79.046298 38.165165, -79.046285 38.165208, -79.046263 38.165293, -79.046258 38.165307, -79.04624 38.165345, -79.046224 38.16537, -79.046196 38.165405, -79.046186 38.165416, -79.046129 38.165464, -79.046032 38.165551, -79.04597 38.16561, -79.045882 38.165702, -79.045853 38.165734, -79.045587 38.166008, -79.045298 38.165594, -79.045254 38.165528, -79.045233 38.165494, -79.045174 38.165392, -79.045156 38.165357, -79.045122 38.165287, -79.04509 38.165217, -79.044988 38.164967, -79.04494 38.164844, -79.044411 38.16534, -79.043654 38.166054, -79.043309 38.166378, -79.042968 38.166698, -79.042755 38.166559, -79.042719 38.166538, -79.04271 38.166532, -79.04262 38.166614, -79.041261 38.168035, -79.039762 38.169431, -79.039354 38.169811, -79.039049 38.170097, -79.037923 38.171588, -79.036665 38.174133, -79.034884 38.176655, -79.033165 38.178591, -79.03219 38.179697, -79.031454 38.180621, -79.030902 38.180252, -79.030492 38.179959, -79.03019 38.179751, -79.02983 38.179508, -79.029389 38.179206, -79.029045 38.178974, -79.02876 38.17878, -79.028673 38.178722, -79.027795 38.178171, -79.027504 38.177985, -79.026593 38.177411, -79.026298 38.17722, -79.026003 38.177037, -79.025774 38.176891, -79.025441 38.176564, -79.024655 38.176038, -79.024304 38.176508, -79.02412 38.176745, -79.023937 38.176983, -79.023754 38.17722, -79.023387 38.177695, -79.02315 38.178005, -79.022913 38.178316, -79.022706 38.178593, -79.0225 38.178871, -79.022294 38.179149, -79.022074 38.179433, -79.021855 38.179717, -79.021636 38.180002, -79.021423 38.180277, -79.021216 38.180546, -79.021015 38.180809, -79.020814 38.181071, -79.020613 38.181334, -79.020413 38.181597, -79.020096 38.181481, -79.019779 38.181364, -79.019462 38.181247, -79.018779 38.181225, -79.018466 38.181246, -79.018257 38.181279, -79.017972 38.181356, -79.017854 38.181396, -79.017728 38.181438, -79.017554 38.181477, -79.017387 38.181488, -79.017213 38.181488, -79.016928 38.181455, -79.01674 38.18141, -79.016671 38.181383, -79.016637 38.181359, -79.016511 38.181273, -79.016399 38.181224, -79.016281 38.181185, -79.016149 38.181169, -79.015968 38.181163, -79.01585 38.181174, -79.015479 38.181233, -79.015335 38.181256, -79.015116 38.181271, -79.014792 38.181294, -79.014593 38.181328, -79.014559 38.181334, -79.014535 38.181338, -79.014437 38.181333, -79.014215 38.181289, -79.014083 38.181245, -79.013644 38.181173, -79.013556 38.181168, -79.013136 38.18114, -79.012705 38.181255, -79.012308 38.18126, -79.012197 38.181271, -79.0121 38.181293, -79.011856 38.181392, -79.011759 38.181419, -79.011313 38.18148, -79.011548 38.181696, -79.01307 38.183098, -79.013895 38.183858, -79.014596 38.184456, -79.015288 38.185041, -79.015341 38.185086, -79.015406 38.185151, -79.016402 38.186141, -79.016418 38.18621, -79.016265 38.186071, -79.016221 38.18605, -79.016164 38.186015, -79.01605 38.185927, -79.015744 38.185644, -79.015283 38.185236, -79.015239 38.185204, -79.015116 38.18512, -79.014881 38.185073, -79.014543 38.185031, -79.014127 38.184906, -79.013901 38.184858, -79.013569 38.184799, -79.013362 38.184766, -79.013245 38.184723, -79.013055 38.184701, -79.012982 38.184707, -79.012921 38.184722, -79.012798 38.184788, -79.012718 38.184953, -79.012436 38.185653, -79.012296 38.185961, -79.012095 38.186296, -79.012007 38.186388, -79.011947 38.186442, -79.011872 38.186484, -79.011804 38.186512, -79.011168 38.186696, -79.010863 38.18681, -79.010674 38.186891, -79.010381 38.187051, -79.010217 38.18712, -79.009767 38.187286, -79.008883 38.187414, -79.008788 38.187436, -79.0088 38.18734, -79.008796 38.187318, -79.008784 38.187295, -79.008668 38.187167, -79.008429 38.186814, -79.00839 38.186728, -79.008385 38.186703, -79.008391 38.186583, -79.008388 38.186559, -79.008369 38.186513, -79.008351 38.186491, -79.008285 38.186445, -79.008178 38.186359, -79.008137 38.186311, -79.008063 38.186198, -79.00801 38.186088, -79.007921 38.185931, -79.007841 38.185771, -79.007705 38.18544, -79.007584 38.185105, -79.007516 38.184941, -79.007416 38.184739, -79.007175 38.184406, -79.007077 38.184286, -79.006991 38.184166, -79.006957 38.184176, -79.006901 38.18418, -79.006674 38.184175, -79.006408 38.184136, -79.006081 38.184104, -79.005992 38.184108, -79.005816 38.184131, -79.005279 38.184275, -79.004831 38.184412, -79.004771 38.184418, -79.004397 38.18443, -79.003754 38.184539, -79.003095 38.184728, -79.00283 38.184826, -79.002601 38.184885, -79.002299 38.184929, -79.001906 38.18494, -79.001324 38.184977, -79.00106 38.184987, -79.000756 38.185006, -79.000416 38.185045, -78.999872 38.18507, -78.999635 38.185093, -78.999394 38.185083, -78.998939 38.18506, -78.998301 38.18503, -78.998031 38.18613, -78.997857 38.186835, -78.997651 38.187677, -78.997586 38.187766, -78.997555 38.187834, -78.997518 38.187956, -78.997474 38.188152, -78.997252 38.189055, -78.997177 38.189301, -78.997131 38.189423, -78.997075 38.189541, -78.996886 38.189859, -78.996669 38.190191, -78.996549 38.190335, -78.996339 38.190569, -78.996171 38.190739, -78.996104 38.190798, -78.995991 38.190899, -78.995688 38.191131, -78.99523 38.191456, -78.995169 38.191505, -78.995079 38.191595, -78.995004 38.191693, -78.99483 38.19199, -78.994353 38.191824, -78.993739 38.191599, -78.993432 38.191493, -78.993134 38.191403, -78.992978 38.191367, -78.992689 38.191297, -78.99248 38.191256, -78.992215 38.191219, -78.991834 38.191188, -78.99132 38.19112, -78.990807 38.191051, -78.990567 38.191019, -78.990076 38.190947, -78.98985 38.190902, -78.989673 38.190856, -78.989483 38.190796, -78.989231 38.190699, -78.989037 38.190612, -78.988483 38.190346, -78.988251 38.190254, -78.988012 38.190175, -78.987769 38.190117, -78.986939 38.189941, -78.986673 38.189875, -78.986582 38.189846, -78.986374 38.18978, -78.986228 38.189721, -78.985991 38.189596, -78.985792 38.189478, -78.985676 38.189392, -78.984392 38.191066, -78.983747 38.191903, -78.983835 38.191941, -78.984364 38.192202, -78.984478 38.192233, -78.984564 38.192239, -78.984929 38.192203, -78.985021 38.192181, -78.985031 38.192198, -78.985048 38.192213, -78.985095 38.192235, -78.985141 38.192272, -78.985159 38.192302, -78.985178 38.192426, -78.985187 38.192456, -78.985218 38.192515, -78.985568 38.192888, -78.985644 38.193027, -78.985675 38.193067, -78.985658 38.193094, -78.985614 38.193419, -78.985607 38.193502, -78.985589 38.193582, -78.985545 38.193671, -78.985443 38.193799, -78.985384 38.193846, -78.985329 38.193877, -78.985276 38.19389, -78.985196 38.193886, -78.98515 38.193874, -78.98476 38.193685, -78.9846 38.193595, -78.984534 38.193563, -78.983964 38.193355, -78.983584 38.193192, -78.983263 38.193034, -78.983185 38.193007, -78.983014 38.192975, -78.982928 38.192967, -78.982823 38.193103, -78.983226 38.193226, -78.983908 38.193451, -78.984548 38.19372, -78.98514 38.194022, -78.98617 38.194504, -78.986894 38.194889, -78.987556 38.195273, -78.988461 38.195723, -78.988697 38.195904, -78.988871 38.196058, -78.988969 38.196206, -78.988997 38.196305, -78.988976 38.196431, -78.988893 38.196585, -78.988823 38.196788, -78.988816 38.197008, -78.988851 38.197194, -78.988942 38.197474, -78.989102 38.197749, -78.989311 38.198034, -78.989576 38.198342, -78.989799 38.198512, -78.989931 38.19854, -78.990174 38.198539, -78.990418 38.198583, -78.990662 38.19866, -78.991184 38.198858, -78.991622 38.199006, -78.992381 38.199296, -78.992639 38.199423, -78.992882 38.199598, -78.992945 38.199779, -78.992928 38.199971, -78.992913 38.200058, -78.992855 38.200263, -78.992772 38.200433, -78.992633 38.200543, -78.992556 38.200572, -78.992523 38.200591, -78.992491 38.200596, -78.992278 38.200675, -78.992166 38.200735, -78.992076 38.200895, -78.992048 38.201065, -78.992035 38.201784, -78.992056 38.202097, -78.992147 38.202273, -78.992237 38.202361, -78.992871 38.202619, -78.993309 38.202756, -78.99363 38.202909, -78.993868 38.203013, -78.994173 38.203173, -78.99426 38.203223, -78.994409 38.20331, -78.994758 38.203557, -78.99512 38.203831, -78.996666 38.20511, -78.997195 38.205473, -78.997634 38.205791, -78.997968 38.205972, -78.998295 38.206082, -78.998734 38.206191, -78.99975 38.206344, -79.000006 38.206375, -79.000221 38.20643, -79.000388 38.20649, -79.000688 38.206628, -79.001355 38.207007, -79.001759 38.207216, -79.00219 38.20759, -79.002295 38.207639, -79.002517 38.207705, -79.002677 38.207766, -79.002761 38.207831, -79.002939 38.207944, -79.003367 38.207515, -79.003449 38.207431, -79.004102 38.207797, -79.004154 38.207831, -79.004278 38.207925, -79.004389 38.208028, -79.004498 38.208158, -79.004535 38.208211, -79.004852 38.208777, -79.004944 38.208907, -79.005023 38.208994, -79.005153 38.209108, -79.005177 38.209125, -79.00525 38.209176, -79.005381 38.209251, -79.005521 38.209315, -79.005647 38.20936, -79.005533 38.209515, -79.005494 38.209581, -79.005466 38.209651, -79.005447 38.20974, -79.005446 38.209831, -79.005458 38.209904, -79.005489 38.209991, -79.005631 38.210231, -79.005683 38.210303, -79.005763 38.210385, -79.005856 38.210459, -79.00596 38.210522, -79.006075 38.210573, -79.006243 38.210626, -79.006467 38.21071, -79.006683 38.210806, -79.007174 38.211062, -79.007778 38.21137, -79.007877 38.211407, -79.007997 38.211428, -79.008078 38.211432, -79.0082 38.21142, -79.00828 38.211401, -79.008359 38.211525, -79.008406 38.211581, -79.008495 38.211653, -79.00871 38.211785, -79.008741 38.21182, -79.008756 38.211862, -79.008752 38.211906, -79.008741 38.21193, -79.008677 38.212024, -79.008673 38.212075, -79.008687 38.212124, -79.00872 38.212169, -79.008767 38.212204, -79.008859 38.21224, -79.008968 38.212267, -79.00908 38.21228, -79.009148 38.212281, -79.009375 38.212271, -79.009694 38.212249, -79.009736 38.212149, -79.009768 38.212117, -79.009831 38.212072, -79.009906 38.212041, -79.010078 38.212002, -79.010383 38.211943, -79.010493 38.211926, -79.011018 38.211874, -79.011188 38.211862, -79.011248 38.211861, -79.011432 38.21187, -79.011506 38.211868, -79.011586 38.211852, -79.011658 38.211819, -79.012042 38.211491, -79.012138 38.211486, -79.012232 38.211466, -79.012368 38.211415, -79.012855 38.211189, -79.013182 38.211043, -79.013774 38.210771, -79.013859 38.210741, -79.014006 38.210704, -79.014158 38.210681, -79.014312 38.210673, -79.01461 38.210683, -79.015344 38.210727, -79.015703 38.210732, -79.015929 38.21072, -79.01604 38.210703, -79.016148 38.210678, -79.016277 38.210635, -79.016398 38.210579, -79.016629 38.210448, -79.016787 38.210346, -79.016892 38.210266, -79.016964 38.21019, -79.017006 38.210127, -79.017022 38.210057, -79.017019 38.209977, -79.016998 38.209899, -79.016967 38.209844, -79.016907 38.209765, -79.016587 38.209377, -79.016331 38.209064, -79.016138 38.208813, -79.016092 38.208762, -79.016002 38.208683, -79.015899 38.208615, -79.015704 38.208522, -79.01556 38.208466, -79.015549 38.207416, -79.015548 38.207386, -79.015531 38.207338, -79.015301 38.207029, -79.015162 38.206893, -79.015089 38.206811, -79.015066 38.206754, -79.015061 38.206694, -79.015081 38.206607, -79.015166 38.206371, -79.015552 38.206422, -79.015831 38.206454, -79.016238 38.206507, -79.016554 38.206538, -79.016835 38.206575, -79.016997 38.206607, -79.017123 38.206643, -79.017274 38.2067, -79.017363 38.206755, -79.017502 38.206857, -79.017603 38.206946, -79.017719 38.20707, -79.017791 38.207175, -79.017822 38.20724, -79.017853 38.207325, -79.017974 38.207327, -79.018372 38.207376, -79.018527 38.20736, -79.018679 38.207329, -79.018909 38.207258, -79.019072 38.207231, -79.019117 38.207208, -79.01915 38.207174, -79.019165 38.207143, -79.019168 38.207125, -79.019357 38.205822, -79.019295 38.205794, -79.019119 38.205726, -79.018892 38.205653, -79.018647 38.20559, -79.018336 38.20553, -79.018009 38.205488, -79.016595 38.205392, -79.015304 38.205305, -79.015322 38.205197, -79.015358 38.205092, -79.015378 38.205052, -79.015463 38.204931, -79.015565 38.204818, -79.015632 38.204755, -79.015909 38.204515, -79.016064 38.204386, -79.016274 38.204197, -79.016573 38.203942, -79.016441 38.203853, -79.015691 38.203296, -79.015338 38.203035, -79.01483 38.202677, -79.014746 38.202751, -79.014301 38.203122, -79.013851 38.203509, -79.013331 38.203955, -79.013228 38.204039, -79.013033 38.203867, -79.012044 38.202999, -79.011878 38.20287, -79.012367 38.202114, -79.01289 38.201396, -79.012901 38.201363, -79.012899 38.201326, -79.01298 38.201331, -79.013017 38.20134, -79.0131 38.201372, -79.013251 38.201452, -79.01327 38.201485, -79.013271 38.201522, -79.013263 38.201543, -79.012693 38.202316, -79.013937 38.201781, -79.014044 38.201649, -79.014199 38.201487, -79.014313 38.20138, -79.01436 38.201325, -79.014311 38.201322, -79.014284 38.201314, -79.013488 38.200924, -79.013166 38.200757, -79.013707 38.200333, -79.013839 38.200222, -79.014026 38.200056, -79.014364 38.199778, -79.014195 38.199698, -79.013685 38.199426, -79.013269 38.199172, -79.012598 38.198703, -79.012889 38.198465, -79.013043 38.198339, -79.014072 38.197455, -79.014923 38.196733, -79.015058 38.196618, -79.015737 38.19604, -79.016107 38.195735, -79.016407 38.195487, -79.017663 38.194451, -79.017839 38.194306, -79.017895 38.19426, -79.018161 38.19404, -79.018379 38.193861, -79.018505 38.193758, -79.018987 38.193364, -79.019468 38.192969, -79.019545 38.192906, -79.019756 38.192732, -79.021141 38.191587, -79.022035 38.190821, -79.022162 38.190705, -79.022198 38.190672, -79.022848 38.190078, -79.022902 38.19003, -79.022992 38.189947, -79.023516 38.189437, -79.023849 38.18911, -79.023985 38.188978, -79.024809 38.188174, -79.025883 38.187124, -79.026353 38.186665, -79.027926 38.185115, -79.028324 38.18472, -79.029367 38.183686, -79.030431 38.182621, -79.030462 38.182595, -79.030645 38.182422, -79.030816 38.182252, -79.030993 38.182081, -79.031166 38.181917, -79.031344 38.181752, -79.031427 38.18167, -79.03159 38.181506, -79.031751 38.18134, -79.031909 38.181173, -79.031987 38.181088, -79.032039 38.181024, -79.032142 38.180854, -79.032308 38.180663, -79.032501 38.180439, -79.032647 38.180257, -79.032699 38.18019, -79.032814 38.180035, -79.032918 38.179901, -79.033059 38.179722, -79.03313 38.17963, -79.033281 38.179719, -79.033667 38.179958, -79.03383 38.180065, -79.033859 38.180081, -79.034051 38.180163, -79.034192 38.18021, -79.034277 38.180247, -79.034019 38.179906, -79.033938 38.179803, -79.03391 38.179777, -79.033886 38.17976, -79.033516 38.179552, -79.033274 38.179434, -79.033342 38.17934, -79.033659 38.178923, -79.033872 38.178639, -79.034239 38.178141, -79.034485 38.177804, -79.034755 38.177432, -79.034898 38.177232, -79.035234 38.176752, -79.035665 38.176163, -79.035869 38.175889, -79.036001 38.17571, -79.036307 38.17528, -79.03662 38.174785, -79.036813 38.1743, -79.036961 38.173987, -79.037312 38.173301, -79.037445 38.173033, -79.037652 38.172613, -79.037894 38.172135, -79.038023 38.171892, -79.038093 38.171764, -79.038309 38.171391, -79.038462 38.17116, -79.038567 38.171014, -79.038786 38.170732, -79.038856 38.170648, -79.039052 38.170424, -79.039244 38.170213, -79.039363 38.170095, -79.039528 38.169931, -79.03985 38.169627, -79.039936 38.169544, -79.040266 38.169231, -79.0414 38.168166, -79.042 38.168548, -79.042234 38.168712, -79.042411 38.168836, -79.04258 38.168938, -79.042844 38.169114, -79.042995 38.169206, -79.043012 38.169211, -79.043045 38.169213, -79.043073 38.169209, -79.043107 38.169197, -79.043131 38.169178, -79.043485 38.168831, -79.043511 38.168807, -79.043593 38.168738, -79.043622 38.168716, -79.04368 38.168674, -79.04374 38.168633, -79.043803 38.168593, -79.043867 38.168556, -79.043932 38.16852, -79.043966 38.168503, -79.044069 38.168455, -79.044139 38.168425, -79.044175 38.168411, -79.044283 38.168372, -79.044357 38.168349, -79.044479 38.168319, -79.04452 38.16831, -79.044602 38.168294, -79.044685 38.16828, -79.044726 38.168274, -79.044852 38.168258, -79.044936 38.168251, -79.045013 38.168249, -79.045117 38.168256, -79.045151 38.16826, -79.045473 38.168326, -79.045525 38.16834, -79.045735 38.168393, -79.045788 38.168404, -79.045871 38.168433, -79.045952 38.168464, -79.046071 38.168515, -79.046149 38.168552, -79.046229 38.168593, -79.046259 38.168605, -79.046291 38.168615, -79.046326 38.168623, -79.04657 38.169135, -79.046666 38.169334, -79.046692 38.169379, -79.046713 38.169407, -79.046737 38.169435, -79.046763 38.169461, -79.046806 38.169497, -79.046822 38.169509, -79.046854 38.16953, -79.046871 38.16954, -79.047091 38.169658, -79.047401 38.169805, -79.047479 38.169853, -79.047514 38.16988, -79.047548 38.169908, -79.04785 38.170219, -79.047661 38.170336, -79.047541 38.170405, -79.04732 38.170523, -79.04695 38.170704, -79.046434 38.170931, -79.046979 38.170784, -79.047151 38.170739, -79.04752 38.170639, -79.047874 38.170538, -79.048057 38.170489, -79.048586 38.170341, -79.048725 38.170297, -79.049009 38.17062, -79.0492 38.17082, -79.049301 38.170928, -79.049429 38.171087, -79.049519 38.171205, -79.049581 38.17132, -79.04961 38.171416, -79.049621 38.171653, -79.049603 38.171798, -79.049628 38.171928, -79.049661 38.172013, -79.049699 38.172079, -79.049725 38.172107, -79.049521 38.172242, -79.049312 38.172385, -79.048907 38.172588, -79.048315 38.172876, -79.048021 38.173032, -79.047983 38.173055, -79.047929 38.173093, -79.04788 38.173135, -79.047836 38.17318, -79.047809 38.173212, -79.047786 38.173245, -79.047757 38.173294, -79.047742 38.173328, -79.047723 38.173379, -79.047718 38.173396, -79.047708 38.173449, -79.047705 38.173485, -79.047705 38.173521, -79.04771 38.173574, -79.047711 38.173613, -79.047715 38.173651, -79.047727 38.173708, -79.047739 38.173745, -79.047753 38.173782, -79.047771 38.173818, -79.047802 38.173871, -79.047826 38.173904, -79.047867 38.173952, -79.047898 38.173982, -79.048066 38.174164, -79.048094 38.174194, -79.048426 38.174534, -79.048468 38.174586, -79.048492 38.174622, -79.048514 38.174659, -79.048524 38.174678, -79.048549 38.174736, -79.048562 38.174776, -79.048572 38.174816, -79.048581 38.174877, -79.048626 38.175153, -79.048405 38.175186, -79.047923 38.175267, -79.047839 38.175288, -79.047669 38.175345, -79.04748 38.17542, -79.047173 38.175543, -79.047142 38.175559, -79.04707 38.175605, -79.047058 38.175617, -79.04704 38.175643, -79.047028 38.175671, -79.047025 38.17568, -79.047022 38.1757, -79.047022 38.175719, -79.047024 38.175739, -79.04703 38.175758, -79.047038 38.175777, -79.047043 38.175786, -79.047062 38.175811, -79.047069 38.175819, -79.047361 38.176076, -79.047397 38.176106, -79.047446 38.176154, -79.047489 38.176205, -79.047503 38.176223, -79.047539 38.176277, -79.047569 38.176334, -79.047593 38.176393, -79.047605 38.176433, -79.04761 38.176453, -79.047623 38.176495, -79.047636 38.17656, -79.047643 38.176625, -79.047643 38.176691, -79.047639 38.176734, -79.047628 38.176799, -79.047617 38.176842, -79.047594 38.176905, -79.047576 38.176946, -79.047555 38.176987, -79.0475 38.17707, -79.047464 38.177116, -79.047456 38.177127, -79.047358 38.177243, -79.047336 38.177267, -79.046963 38.177747, -79.046658 38.17815, -79.046559 38.178266, -79.046594 38.17828, -79.046895 38.178394, -79.046971 38.178415, -79.04703 38.178425, -79.04707 38.178429, -79.047131 38.178431, -79.047171 38.178429, -79.047212 38.178425, -79.047309 38.178406, -79.047367 38.178396, -79.047426 38.178388, -79.047486 38.178383, -79.047536 38.178381, -79.047576 38.178381, -79.047616 38.178383, -79.047675 38.178391, -79.047713 38.178399, -79.047751 38.17841, -79.047788 38.178422, -79.04784 38.178445, -79.047889 38.178472, -79.047919 38.178493, -79.047947 38.178515, -79.047976 38.17854, -79.048018 38.178582, -79.048055 38.178627, -79.048066 38.178642, -79.048086 38.178674, -79.048104 38.178707, -79.048128 38.17876, -79.04815 38.178798, -79.048175 38.178836, -79.048202 38.178872, -79.048232 38.178907, -79.048281 38.178957, -79.048298 38.178973, -79.048354 38.179018, -79.048392 38.179042, -79.048432 38.179064, -79.048495 38.179094, -79.048539 38.179111, -79.048607 38.179133, -79.048653 38.179145, -79.0487 38.179155, -79.048748 38.179163, -79.048797 38.179169, -79.048821 38.179171, -79.048887 38.179171, -79.048985 38.179166, -79.049051 38.17916, -79.049083 38.179155, -79.049116 38.179151, -79.04918 38.17914, -79.049244 38.179126, -79.049275 38.179118, -79.049315 38.179106, -79.049364 38.179241, -79.049544 38.179707, -79.049742 38.180162, -79.050242 38.180048, -79.050587 38.179967, -79.050644 38.179951, -79.050699 38.179933, -79.050753 38.179912, -79.050806 38.179889, -79.050856 38.179863, -79.050905 38.179835, -79.050951 38.179805, -79.051443 38.179308, -79.051545 38.179209, -79.051408 38.179124, -79.050915 38.178808, -79.050812 38.178718, -79.050844 38.178704, -79.050898 38.178676, -79.050949 38.178646, -79.050998 38.178614, -79.051044 38.178579, -79.051087 38.178542, -79.051127 38.178503, -79.051164 38.178462, -79.051197 38.178419, -79.051227 38.178374, -79.051451 38.177874, -79.051712 38.177935, -79.051881 38.177959, -79.052186 38.177998, -79.052678 38.178049, -79.052891 38.178068, -79.053329 38.17811, -79.053521 38.178115, -79.053619 38.178111, -79.053748 38.178089, -79.053849 38.178055, -79.054134 38.177939, -79.054492 38.177789, -79.054633 38.17772, -79.05475 38.177646, -79.054866 38.177546, -79.054955 38.177453, -79.055045 38.177341, -79.055098 38.177218, -79.055121 38.177109, -79.055131 38.176927, -79.05513 38.17681, -79.05511 38.176714, -79.055046 38.176551, -79.054918 38.176367, -79.05452 38.175793, -79.054132 38.175208, -79.05399 38.175011, -79.053907 38.174907, -79.053781 38.174796, -79.053332 38.17444, -79.053238 38.174344, -79.052977 38.174037, -79.05265 38.173628, -79.052914 38.173551, -79.053425 38.173334, -79.053784 38.173152, -79.053974 38.173059, -79.053989 38.173046, -79.054051 38.17297, -79.054117 38.173113, -79.054274 38.173385, -79.054443 38.173623, -79.054608 38.173779, -79.054772 38.173892, -79.054939 38.173977, -79.055146 38.174059, -79.055329 38.174104, -79.05566 38.174161, -79.056009 38.174177, -79.056281 38.174194, -79.056323 38.174194, -79.056546 38.17419, -79.056648 38.174184, -79.056635 38.174265, -79.056577 38.174391, -79.056414 38.174703, -79.05607 38.175256, -79.056012 38.175387, -79.056012 38.175434, -79.056029 38.175502, -79.056056 38.175556, -79.056116 38.175634, -79.056166 38.175681, -79.056424 38.175849, -79.056669 38.176006, -79.056779 38.176094, -79.056905 38.176215, -79.056985 38.176132, -79.05719 38.175938, -79.057533 38.175653, -79.057747 38.1755, -79.05796 38.175366, -79.058093 38.17526, -79.058214 38.175145, -79.058328 38.17504, -79.058373 38.174969, -79.058408 38.174882, -79.058428 38.174685, -79.058433 38.174522, -79.058464 38.174414, -79.058508 38.174092, -79.058502 38.174032, -79.058497 38.174006, -79.058409 38.173871, -79.058387 38.173835, -79.058378 38.173801, -79.058366 38.173382, -79.058209 38.173374, -79.057645 38.173331, -79.057646 38.17332, -79.057641 38.17316, -79.057513 38.17274, -79.057395 38.172477, -79.05734 38.172364, -79.057092 38.172006, -79.056856 38.171658, -79.056782 38.171535, -79.05675 38.171468, -79.056734 38.171379, -79.056721 38.171247, -79.056724 38.171134, -79.056754 38.170937, -79.056792 38.170808, -79.056849 38.170682, -79.056865 38.170659, -79.057014 38.170457, -79.057572 38.169709, -79.057772 38.169407, -79.057812 38.169354, -79.057839 38.169313, -79.057848 38.169299, -79.057881 38.169243, -79.057911 38.169185, -79.057936 38.169127, -79.057959 38.169068, -79.057977 38.169007, -79.057992 38.168947, -79.058003 38.168885, -79.058011 38.168824, -79.058014 38.168762, -79.058007 38.168651, -79.057994 38.168484, -79.057966 38.168275, -79.057953 38.168147, -79.057951 38.168125, -79.057945 38.167731, -79.05795 38.167625, -79.058154 38.167579, -79.059354 38.167307, -79.059686 38.167234, -79.05969 38.167375, -79.05969 38.167583, -79.059683 38.168017, -79.059661 38.168754, -79.059622 38.169872, -79.059581 38.171693, -79.059584 38.171714, -79.059592 38.171735, -79.059603 38.171755, -79.059617 38.171774, -79.059634 38.17179, -79.059655 38.171805, -79.059677 38.171818, -79.059701 38.171828, -79.059727 38.171835, -79.059754 38.171839, -79.060044 38.171893, -79.060064 38.171898, -79.060398 38.171983, -79.060899 38.172157, -79.061037 38.172209, -79.061135 38.172028, -79.061142 38.172006, -79.061147 38.17198, -79.06115 38.171763, -79.061146 38.171717, -79.061146 38.171671, -79.061148 38.171625, -79.061156 38.171557, -79.061171 38.171489, -79.061192 38.171422, -79.061204 38.171394, -79.061226 38.171337, -79.061244 38.171279, -79.06126 38.171221, -79.061277 38.171133, -79.061284 38.171073, -79.061275 38.170701, -79.061239 38.170099, -79.061215 38.169989, -79.061935 38.169837, -79.061976 38.169829, -79.062058 38.169815, -79.06214 38.169803, -79.062182 38.169798, -79.062307 38.169786, -79.06239 38.169781, -79.062516 38.169778, -79.062599 38.169779, -79.062767 38.169788, -79.062808 38.169792, -79.062792 38.169712, -79.062785 38.169686, -79.06276 38.169608, -79.06274 38.169556, -79.062705 38.16948, -79.062675 38.169402, -79.062651 38.169323, -79.062639 38.169269, -79.062629 38.169216, -79.062622 38.169162, -79.06262 38.169135, -79.062618 38.169053, -79.062641 38.168823, -79.06265 38.16874, -79.062664 38.168524, -79.062662 38.168491, -79.06266 38.168475, -79.062653 38.168442, -79.062643 38.16841, -79.062637 38.168395, -79.062618 38.168356, -79.062596 38.168318, -79.062572 38.168281, -79.062545 38.168245, -79.062516 38.16821, -79.062484 38.168177, -79.062451 38.168145, -79.062433 38.168129, -79.062377 38.168085, -79.062357 38.168071, -79.062299 38.168034, -79.062265 38.168011, -79.062234 38.167987, -79.06219 38.167947, -79.062164 38.167918, -79.06213 38.167873, -79.06211 38.167842, -79.062086 38.167792, -79.062074 38.167759, -79.06206 38.167707, -79.062025 38.167516, -79.061993 38.167255, -79.061958 38.166847, -79.061942 38.16668, -79.062239 38.166571, -79.062829 38.16632, -79.063068 38.166194, -79.06339 38.166008, -79.063505 38.165942, -79.063735 38.165803, -79.06349 38.167391, -79.063488 38.167402, -79.063486 38.167425, -79.063489 38.16746, -79.063498 38.167494, -79.063508 38.167516, -79.06352 38.167537, -79.063534 38.167557, -79.063561 38.167585, -79.063581 38.167602, -79.063615 38.167624, -79.063652 38.167642, -79.06368 38.167652, -79.063712 38.167661, -79.063776 38.167672, -79.063825 38.167674, -79.064384 38.167638, -79.064723 38.167603, -79.064745 38.167596, -79.064765 38.167587, -79.064784 38.167577, -79.064793 38.167571, -79.064816 38.16755, -79.064829 38.167534, -79.064839 38.167518, -79.064843 38.167509, -79.064849 38.167491, -79.064852 38.167473, -79.064852 38.167455, -79.06485 38.167436, -79.06478 38.167209, -79.064764 38.167066, -79.064764 38.167042, -79.064767 38.166994, -79.064773 38.166947, -79.064781 38.1669, -79.064792 38.166853, -79.064799 38.166829, -79.064814 38.166783, -79.064832 38.166737, -79.064864 38.16667, -79.06506 38.166334, -79.065072 38.166315, -79.065098 38.166278, -79.065127 38.166242, -79.065143 38.166224, -79.065176 38.16619, -79.065229 38.166142, -79.065248 38.166127, -79.065288 38.166098, -79.065351 38.166057, -79.065537 38.165953, -79.065541 38.166007, -79.065541 38.16609, -79.065537 38.166146, -79.065531 38.166202, -79.065527 38.166229, -79.065516 38.166285, -79.065495 38.166367, -79.065477 38.166421, -79.065457 38.166474, -79.065445 38.166501, -79.065319 38.166749, -79.065211 38.166986, -79.065204 38.167003, -79.065194 38.167037, -79.065187 38.167071, -79.065183 38.167106, -79.065181 38.167141, -79.065184 38.167194, -79.06519 38.167229, -79.065204 38.16728, -79.065216 38.167311, -79.065239 38.167384, -79.065257 38.167449, -79.065394 38.1675, -79.065552 38.167568, -79.065793 38.167681, -79.065942 38.167753, -79.06599 38.167773, -79.06604 38.167791, -79.066091 38.167807, -79.066142 38.167821, -79.066168 38.167827, -79.066221 38.167837, -79.0663 38.167849, -79.06667 38.167897, -79.066758 38.167918, -79.066816 38.167944, -79.067044 38.168139, -79.067062 38.168167, -79.067085 38.16821, -79.067091 38.168225, -79.067101 38.168255, -79.067109 38.168286, -79.067113 38.168317, -79.067136 38.168544, -79.06715 38.168641, -79.067167 38.168718, -79.06722 38.168879, -79.067303 38.169153, -79.067309 38.169176, -79.06741 38.169569, -79.067428 38.169645, -79.067502 38.169949, -79.06757 38.170267, -79.067583 38.170332, -79.067613 38.170478, -79.067621 38.170533, -79.067624 38.17056, -79.067627 38.170615, -79.067636 38.170641, -79.067649 38.170659, -79.067672 38.170675, -79.068021 38.170661, -79.068375 38.170648, -79.068411 38.170353, -79.0685 38.170284, -79.068686 38.170287, -79.06883 38.170279, -79.070492 38.170198, -79.074727 38.16999, -79.074769 38.17022, -79.074557 38.170223, -79.074368 38.170223, -79.07433 38.170226, -79.074318 38.170228, -79.074293 38.170234, -79.074258 38.170247, -79.074237 38.170258, -79.074208 38.170277, -79.073903 38.170538, -79.073879 38.170566, -79.073867 38.170586, -79.073857 38.170606, -79.073853 38.170617, -79.073847 38.170638, -79.073836 38.170701, -79.073807 38.170889, -79.073797 38.170916, -79.073784 38.170942, -79.073768 38.170968, -79.07374 38.171004, -79.073686 38.171059, -79.073668 38.171083, -79.073653 38.171108, -79.07364 38.171134, -79.07363 38.171161, -79.073626 38.171175, -79.073599 38.171326, -79.073585 38.171427, -79.073579 38.171478, -79.073545 38.1718, -79.07355 38.172083, -79.07355 38.172244, -79.073548 38.172272, -79.07355 38.172313, -79.073559 38.172354, -79.073569 38.17238, -79.073582 38.172406, -79.073597 38.17243, -79.073615 38.172454, -79.073646 38.172487, -79.073679 38.172507, -79.073691 38.172513, -79.073715 38.172523, -79.073741 38.172531, -79.073767 38.172537, -79.073808 38.172542, -79.073836 38.172543, -79.073868 38.17254, -79.073921 38.172529, -79.073956 38.172519, -79.07399 38.172507, -79.074022 38.172493, -79.074067 38.172467, -79.074111 38.172436, -79.07416 38.172409, -79.074194 38.172393, -79.074212 38.172386, -79.074267 38.172368, -79.074286 38.172363, -79.074334 38.172363, -79.074367 38.172366, -79.074398 38.172371, -79.07443 38.172378, -79.074389 38.172516, -79.074206 38.173151, -79.074013 38.173796, -79.073872 38.17426, -79.073783 38.174559, -79.073672 38.174938, -79.073618 38.175121, -79.073393 38.175855, -79.073318 38.176099, -79.073283 38.176196, -79.073256 38.176259, -79.073222 38.176324, -79.073196 38.176366, -79.073168 38.176407, -79.073137 38.176447, -79.073104 38.176486, -79.073069 38.176524, -79.073012 38.176577, -79.072971 38.176611, -79.072928 38.176644, -79.07279 38.176761, -79.072515 38.176995, -79.072314 38.177169, -79.072034 38.177411, -79.071482 38.177893, -79.071095 38.178236, -79.071007 38.178307, -79.070872 38.17841, -79.07074 38.178507, -79.070703 38.17853, -79.070589 38.178595, -79.070471 38.178657, -79.070391 38.178696, -79.070267 38.17875, -79.069909 38.178892, -79.069466 38.179056, -79.069038 38.179208, -79.068891 38.179262, -79.068672 38.179342, -79.068596 38.179371, -79.068254 38.179498, -79.068101 38.179556, -79.067782 38.179673, -79.067649 38.179721, -79.067362 38.179828, -79.067255 38.179864, -79.067185 38.179891, -79.067106 38.179925, -79.06701 38.179972, -79.066972 38.179995, -79.066918 38.180033, -79.066885 38.180061, -79.066823 38.180122, -79.066766 38.180187, -79.0667 38.18027, -79.066602 38.180398, -79.066443 38.1806, -79.066259 38.180836, -79.066198 38.180912, -79.066121 38.181008, -79.066081 38.181051, -79.066038 38.181092, -79.065991 38.181131, -79.065942 38.181167, -79.06589 38.181201, -79.065835 38.181233, -79.065778 38.181261, -79.065639 38.181325, -79.065453 38.181405, -79.065195 38.181506, -79.064815 38.181658, -79.064126 38.181935, -79.064033 38.181972, -79.063774 38.182084, -79.063695 38.182122, -79.063617 38.182163, -79.063524 38.182222, -79.063433 38.182284, -79.063346 38.182349, -79.063261 38.182416, -79.0632 38.182467, -79.063054 38.182597, -79.062959 38.182686, -79.062791 38.182849, -79.062673 38.182968, -79.062557 38.183089, -79.0625 38.18315, -79.061796 38.183868, -79.061513 38.184163, -79.061335 38.18435, -79.061244 38.184443, -79.061113 38.184584, -79.06105 38.184653, -79.060998 38.184706, -79.060849 38.184869, -79.060715 38.18502, -79.060661 38.185085, -79.060635 38.185118, -79.060577 38.185201, -79.060559 38.185229, -79.06051 38.185315, -79.060482 38.18537, -79.060458 38.185415, -79.06044 38.185451, -79.060408 38.185523, -79.060378 38.185595, -79.06035 38.185668, -79.060338 38.185705, -79.060313 38.185769, -79.060282 38.185865, -79.060266 38.185926, -79.060251 38.185997, -79.060245 38.186029, -79.06024 38.186062, -79.060234 38.186121, -79.060206 38.186335, -79.060159 38.186714, -79.060144 38.18684, -79.060124 38.187024, -79.060985 38.187149, -79.061596 38.187225, -79.062141 38.187275, -79.062605 38.18729, -79.062953 38.187295, -79.06339 38.187284, -79.063677 38.187268, -79.063827 38.18726, -79.064313 38.187214, -79.064796 38.187154, -79.065506 38.18704, -79.065799 38.186987, -79.066823 38.186804, -79.067406 38.186706, -79.068012 38.186604, -79.069959 38.186261, -79.069962 38.186231, -79.06999 38.186187, -79.070032 38.186159, -79.070101 38.186143, -79.070157 38.186154, -79.07022 38.186187, -79.070247 38.186209, -79.071709 38.185955, -79.072885 38.185751, -79.073568 38.185641, -79.073657 38.18563, -79.074252 38.185563, -79.074469 38.185549, -79.075048 38.185518, -79.075519 38.185518, -79.075853 38.185524, -79.076127 38.18553, -79.07702 38.185559, -79.077533 38.185587, -79.078256 38.185609, -79.079348 38.185647, -79.080007 38.185669, -79.080188 38.185677, -79.08084 38.185709, -79.081129 38.185716, -79.081532 38.185735, -79.081906 38.185739, -79.081978 38.185741, -79.082121 38.185742, -79.082263 38.185741, -79.082406 38.185737, -79.082556 38.185731, -79.082729 38.185721, -79.082947 38.185707, -79.083164 38.18569, -79.083382 38.18567, -79.083598 38.185648, -79.083815 38.185624, -79.083923 38.18561, -79.084204 38.185586, -79.08438 38.185565, -79.08466 38.185533, -79.084799 38.185516, -79.08506 38.18549, -79.08545 38.185447, -79.085905 38.185392, -79.086027 38.185379, -79.086239 38.185352, -79.086373 38.185339, -79.086641 38.185309, -79.086909 38.185276, -79.087523 38.18521, -79.087679 38.185194, -79.087835 38.185176, -79.08799 38.185155, -79.088241 38.185116, -79.088402 38.185088, -79.088722 38.18503, -79.088902 38.18499, -79.088991 38.184969, -79.08917 38.184925, -79.089474 38.184845, -79.089679 38.184794, -79.090153 38.184655, -79.090367 38.184583, -79.090794 38.184437, -79.090997 38.184359, -79.091199 38.184279, -79.091299 38.184238, -79.091454 38.184172, -79.091531 38.184138, -79.09176 38.184034, -79.091937 38.183949, -79.092113 38.183862, -79.092287 38.183773, -79.092505 38.18368, -79.092658 38.183597, -79.092929 38.183441, -79.093128 38.183329, -79.093194 38.183418, -79.093353 38.183357, -79.09343 38.183329, -79.093584 38.183276, -79.094137 38.183096, -79.094506 38.182974, -79.094638 38.182924, -79.094703 38.182898, -79.094833 38.182844, -79.094925 38.182801, -79.095107 38.182713, -79.095197 38.182668, -79.095256 38.182637, -79.095314 38.182605, -79.095428 38.182539, -79.095596 38.182436, -79.09565 38.182401, -79.095997 38.182152, -79.096153 38.182035, -79.09652 38.181751, -79.096627 38.18168, -79.096835 38.181513, -79.097127 38.181749, -79.097535 38.18207, -79.097798 38.182277, -79.09782 38.182294, -79.097872 38.182337, -79.098203 38.182606, -79.098403 38.182775, -79.098415 38.182785, -79.098631 38.182967, -79.098719 38.182866, -79.101033 38.179987, -79.101021 38.179817, -79.101209 38.179568, -79.101322 38.179419, -79.101395 38.179294, -79.101419 38.179256, -79.101613 38.179039, -79.102512 38.177877, -79.103234 38.176944, -79.103746 38.17607, -79.104269 38.175177, -79.104338 38.174809, -79.104357 38.174774, -79.105113 38.173404, -79.105774 38.172205, -79.105977 38.171835, -79.106435 38.171006, -79.106665 38.170591, -79.107139 38.169546, -79.108268 38.16706, -79.108472 38.166612, -79.108796 38.165677, -79.109295 38.164238, -79.109395 38.163949, -79.10945 38.163799, -79.109329 38.16447, -79.110299 38.162101, -79.110389 38.161891, -79.110125 38.161859, -79.110066 38.161852, -79.110005 38.161844, -79.109982 38.161841, -79.11 38.161809, -79.110143 38.161556, -79.110287 38.161302, -79.11036 38.161172, -79.11043 38.161048, -79.110574 38.160794, -79.110717 38.16054, -79.110861 38.160287, -79.111041 38.15996, -79.111148 38.159779, -79.111291 38.159525, -79.111434 38.159272, -79.111578 38.159018, -79.111721 38.158764, -79.111865 38.15851, -79.112008 38.158257, -79.112152 38.158003, -79.112295 38.157749, -79.112503 38.157423, -79.112672 38.157161, -79.11284 38.1569, -79.113009 38.156639, -79.113178 38.156378, -79.113264 38.156239, -79.113345 38.156098, -79.11342 38.155956, -79.11349 38.155811, -79.113554 38.155665, -79.113612 38.155518, -79.113657 38.155391, -79.114473 38.156495, -79.114665 38.156311, -79.114873 38.156114, -79.115657 38.155312, -79.115875 38.155029, -79.116163 38.154654, -79.116307 38.154447, -79.116728 38.153695, -79.116933 38.153216, -79.117209 38.152498, -79.117347 38.151753, -79.117155 38.151729, -79.116879 38.151696, -79.116691 38.151675, -79.11647 38.151666, -79.116315 38.151669, -79.116082 38.151685, -79.116008 38.151695, -79.115904 38.15171, -79.115078 38.151863, -79.114994 38.15188, -79.11416 38.152047, -79.1136 38.152162, -79.113317 38.15222, -79.113167 38.15225, -79.113097 38.152265, -79.113042 38.152277, -79.112936 38.152299, -79.112742 38.151982, -79.112716 38.151926, -79.112706 38.151908, -79.112684 38.151872, -79.112649 38.151822, -79.112625 38.151792, -79.112599 38.151763, -79.11257 38.151736, -79.112538 38.151711, -79.112515 38.151681, -79.112495 38.151649, -79.112477 38.151617, -79.112372 38.151374, -79.112302 38.151193, -79.112254 38.15108, -79.112236 38.151041, -79.112203 38.150965, -79.112173 38.150887, -79.112159 38.150848, -79.112133 38.150754, -79.112113 38.15069, -79.112109 38.150669, -79.112099 38.150648, -79.112089 38.150636, -79.110283 38.147163, -79.110185 38.146979, -79.110138 38.146885, -79.109828 38.146288, -79.108339 38.143853, -79.107928 38.14318, -79.107656 38.142745, -79.107415 38.14234, -79.106061 38.140125, -79.106082 38.140097, -79.106142 38.140016, -79.106223 38.139905, -79.106385 38.139721, -79.106407 38.139693, -79.106494 38.139569, -79.106566 38.139458, -79.106572 38.139438, -79.106576 38.139418, -79.106577 38.139407, -79.106577 38.139387, -79.106571 38.139357, -79.106563 38.139337, -79.106543 38.139308, -79.106523 38.139282, -79.106476 38.13923, -79.106419 38.139174, -79.106395 38.139147, -79.106373 38.139117, -79.106354 38.139087, -79.106332 38.139058, -79.10629 38.138998, -79.10625 38.138937, -79.106232 38.138906, -79.106218 38.138874, -79.106206 38.138841, -79.106198 38.138807, -79.106195 38.13879, -79.10619 38.138739, -79.106176 38.138688, -79.106131 38.138611, -79.105979 38.138456, -79.105665 38.138186, -79.105449 38.138043, -79.105268 38.137937, -79.105032 38.137814, -79.104887 38.137746, -79.104738 38.137683, -79.104726 38.137643, -79.104696 38.137603, -79.104652 38.137572, -79.104538 38.137525, -79.104232 38.137419, -79.10407 38.137327, -79.103639 38.13708, -79.103574 38.137035, -79.103493 38.136955, -79.103443 38.13688, -79.103344 38.13664, -79.103302 38.136564, -79.103245 38.136494, -79.103158 38.136422, -79.103055 38.136365, -79.102979 38.136337, -79.102854 38.136306, -79.102723 38.136283, -79.102557 38.136265, -79.102389 38.136261, -79.10225 38.136269, -79.102221 38.136271, -79.101745 38.136512, -79.101395 38.13669, -79.101372 38.136701, -79.101232 38.13676, -79.101084 38.136816, -79.100934 38.136878, -79.100903 38.13689, -79.10087 38.136903, -79.100663 38.136996, -79.10061 38.13697, -79.100794 38.136841, -79.100392 38.13666, -79.100071 38.136517, -79.09975 38.136375, -79.099429 38.136232, -79.099108 38.13609, -79.098788 38.135947, -79.098467 38.135805, -79.098146 38.135662, -79.097825 38.13552, -79.097504 38.135377, -79.097183 38.135234, -79.096833 38.135044, -79.096482 38.134854, -79.096216 38.134672, -79.09595 38.134491, -79.095684 38.134309, -79.095589 38.134244, -79.095418 38.134127, -79.095152 38.133946, -79.094886 38.133764, -79.09462 38.133583, -79.094354 38.133401, -79.094088 38.133219, -79.093822 38.133038, -79.093556 38.132856, -79.09329 38.132675, -79.091944 38.132528, -79.091728 38.132228, -79.091666 38.132142, -79.09172 38.132148, -79.091769 38.132154, -79.091933 38.132172, -79.092057 38.132186, -79.092602 38.13224, -79.092669 38.132243, -79.093131 38.132267, -79.093338 38.132273, -79.093611 38.132272, -79.093967 38.132252, -79.094366 38.132213, -79.094626 38.132182, -79.0947 38.132173, -79.094671 38.131599, -79.094575 38.131364, -79.09448 38.131236, -79.094286 38.131097, -79.094011 38.130924, -79.093799 38.130836, -79.093396 38.130694, -79.092952 38.130613, -79.092525 38.130521, -79.092249 38.130443, -79.09192 38.130289, -79.091586 38.130052, -79.091194 38.129708, -79.090931 38.129388, -79.090633 38.129062, -79.090477 38.1289, -79.090412 38.128841, -79.090314 38.12875, -79.090116 38.128569, -79.089934 38.128414, -79.089575 38.128127, -79.089353 38.127963, -79.089174 38.127837, -79.088676 38.127521, -79.088381 38.12735, -79.088022 38.127159, -79.087713 38.127007, -79.087399 38.126867, -79.08705 38.126728, -79.086898 38.126672, -79.086585 38.126571, -79.086202 38.126456, -79.085973 38.126393, -79.085397 38.126249, -79.085337 38.126223, -79.085179 38.126169, -79.085015 38.126129, -79.084681 38.126064, -79.084199 38.125995, -79.083899 38.125956, -79.083596 38.125926, -79.083146 38.125896, -79.082698 38.125884, -79.082269 38.125885, -79.08155 38.125904, -79.081423 38.125883, -79.081215 38.125868, -79.080981 38.12584, -79.080713 38.125791, -79.080551 38.125751, -79.080354 38.125689, -79.080071 38.125578, -79.079213 38.125196, -79.078737 38.124991, -79.078562 38.124905, -79.078393 38.124803, -79.07859 38.124555, -79.078707 38.12439, -79.078772 38.124268, -79.078855 38.124069, -79.078929 38.12386, -79.079006 38.123567, -79.079136 38.123145, -79.07929 38.122719, -79.079391 38.122502, -79.079672 38.122077, -79.079891 38.121763, -79.079929 38.121698, -79.080073 38.121454, -79.080545 38.120707, -79.080645 38.120566, -79.080828 38.120308, -79.081555 38.119437, -79.081819 38.119082, -79.082251 38.118541, -79.082491 38.118232, -79.082958 38.117636, -79.083215 38.117359, -79.083542 38.117026, -79.083654 38.116911, -79.083837 38.116733, -79.084118 38.116438, -79.08453 38.116044, -79.084681 38.115903, -79.084924 38.1157, -79.085396 38.115321, -79.084982 38.115016, -79.085131 38.114887, -79.085645 38.114412, -79.085815 38.114239, -79.085825 38.114196, -79.085818 38.114153, -79.085794 38.114114, -79.085755 38.114083, -79.085343 38.113847, -79.085031 38.113675, -79.084833 38.113564, -79.084239 38.113232, -79.083874 38.113027, -79.083064 38.112575, -79.082635 38.113115, -79.082468 38.113297, -79.082305 38.113459, -79.083065 38.113901, -79.083238 38.114001, -79.083445 38.114109, -79.083633 38.114207, -79.083927 38.114368, -79.084124 38.114481, -79.084238 38.114547, -79.084339 38.114605, -79.084569 38.114751, -79.084477 38.114839, -79.084026 38.115245, -79.083644 38.11561, -79.083223 38.11599, -79.082596 38.115548, -79.082451 38.115407, -79.082225 38.115287, -79.081989 38.115189, -79.081273 38.114842, -79.080986 38.114704, -79.080432 38.11446, -79.080369 38.114437, -79.079335 38.114683, -79.079041 38.114778, -79.078254 38.115078, -79.077763 38.115292, -79.077299 38.115536, -79.076992 38.115846, -79.076786 38.116133, -79.076602 38.116605, -79.076476 38.11701, -79.076472 38.117028, -79.076469 38.117115, -79.076484 38.117201, -79.076555 38.117383, -79.07676 38.117717, -79.076809 38.117774, -79.076894 38.117858, -79.076972 38.117958, -79.077012 38.118031, -79.077045 38.118144, -79.077078 38.118204, -79.077244 38.118372, -79.07743 38.118552, -79.077502 38.118599, -79.077564 38.118616, -79.077509 38.118655, -79.07747 38.118703, -79.077463 38.118757, -79.077473 38.118809, -79.077501 38.118858, -79.077519 38.118879, -79.077685 38.118892, -79.077769 38.118915, -79.077829 38.118944, -79.077877 38.119001, -79.078092 38.119217, -79.078116 38.119252, -79.0782 38.119373, -79.078279 38.119525, -79.078345 38.119631, -79.078456 38.119768, -79.078543 38.119857, -79.078429 38.119926, -79.078117 38.120142, -79.078006 38.12023, -79.07788 38.120349, -79.077789 38.120454, -79.07769 38.120605, -79.077516 38.120942, -79.076889 38.122223, -79.076739 38.122511, -79.076531 38.122938, -79.076449 38.123092, -79.076417 38.123186, -79.076402 38.123283, -79.076405 38.12338, -79.076429 38.123489, -79.076471 38.123594, -79.076527 38.123686, -79.076623 38.123794, -79.076716 38.123869, -79.077071 38.124081, -79.077292 38.124218, -79.077646 38.124427, -79.078335 38.124871, -79.078125 38.125093, -79.077989 38.125215, -79.077808 38.125359, -79.077683 38.125447, -79.077467 38.125584, -79.077279 38.125684, -79.077121 38.125755, -79.076681 38.125922, -79.076626 38.125943, -79.076473 38.125928, -79.076321 38.125912, -79.075974 38.125876, -79.07555 38.125818, -79.075307 38.125779, -79.075074 38.125738, -79.074315 38.125583, -79.074237 38.125564, -79.074031 38.125514, -79.073681 38.125417, -79.073386 38.12533, -79.072999 38.125209, -79.072518 38.125042, -79.072105 38.124881, -79.071295 38.124528, -79.070943 38.124351, -79.070479 38.124107, -79.070197 38.123946, -79.06992 38.123768, -79.069509 38.123504, -79.070235 38.122014, -79.071176 38.120846, -79.071512 38.120327, -79.07189 38.119674, -79.0725 38.118831, -79.072642 38.118474, -79.072746 38.118146, -79.073276 38.117436, -79.074448 38.116152, -79.07527 38.115177, -79.0756 38.114883, -79.076315 38.113824, -79.076414 38.113763, -79.076597 38.113601, -79.076894 38.113326, -79.076943 38.1133, -79.077258 38.113035, -79.077576 38.112683, -79.078087 38.112108, -79.078293 38.111807, -79.07877 38.111275, -79.079417 38.11058, -79.079823 38.110203, -79.080027 38.110136, -79.080426 38.109926, -79.080629 38.109764, -79.081077 38.109475, -79.081261 38.109252, -79.08131 38.109282, -79.081415 38.109359, -79.081505 38.109412, -79.081605 38.109453, -79.082378 38.108699, -79.082762 38.108313, -79.083206 38.107899, -79.083702 38.107401, -79.08382 38.10727, -79.083932 38.107131, -79.083959 38.107072, -79.083966 38.107009, -79.083961 38.106971, -79.083861 38.106759, -79.083806 38.106684, -79.083778 38.106654, -79.083739 38.106624, -79.083651 38.106591, -79.083571 38.106572, -79.0835 38.106563, -79.083346 38.106563, -79.082879 38.106615, -79.082287 38.10669, -79.080078 38.107062, -79.079975 38.107071, -79.079886 38.107066, -79.0798 38.107048, -79.07972 38.107016, -79.079673 38.10699, -79.079613 38.106943, -79.079563 38.106885, -79.079528 38.10682, -79.079509 38.10675, -79.079507 38.106683, -79.079522 38.106626, -79.079553 38.106574, -79.079609 38.10652, -79.079893 38.106299, -79.080084 38.106162, -79.080363 38.105968, -79.080484 38.10587, -79.080624 38.105739, -79.080808 38.105533, -79.080937 38.10538, -79.081109 38.105153, -79.081265 38.104919, -79.081323 38.104823, -79.081421 38.104848, -79.081545 38.104846, -79.08211 38.104775, -79.082627 38.104724, -79.081878 38.103648, -79.081128 38.102573, -79.080654 38.101927, -79.080228 38.099958, -79.080351 38.099694, -79.080032 38.099857, -79.079666 38.100045, -79.07896 38.100418, -79.077534 38.101156, -79.076668 38.101607, -79.076316 38.10178, -79.075692 38.102057, -79.0753 38.102229, -79.074865 38.102426, -79.074676 38.102522, -79.074371 38.102701, -79.074173 38.102834, -79.073964 38.102991, -79.073772 38.103154, -79.07362 38.103298, -79.073415 38.103518, -79.073261 38.103714, -79.073149 38.103878, -79.073047 38.104046, -79.072934 38.104261, -79.072857 38.104436, -79.072765 38.104694, -79.072703 38.104936, -79.072665 38.105163, -79.072644 38.105394, -79.07264 38.105626, -79.072655 38.105858, -79.07275 38.106364, -79.072818 38.106671, -79.072865 38.106868, -79.073027 38.107469, -79.073102 38.107713, -79.073148 38.107879, -79.073234 38.108241, -79.073299 38.108596, -79.073334 38.108851, -79.073345 38.108968, -79.073363 38.109284, -79.073357 38.109666, -79.073327 38.110011, -79.073282 38.110304, -79.073225 38.110573, -79.073158 38.110826, -79.073081 38.111064, -79.072942 38.11142, -79.072847 38.111628, -79.072733 38.111852, -79.072641 38.112017, -79.072288 38.112596, -79.071712 38.113526, -79.071504 38.113433, -79.071481 38.11343, -79.071452 38.11344, -79.071373 38.113516, -79.071328 38.113535, -79.071278 38.11354, -79.071238 38.113535, -79.070813 38.113356, -79.070718 38.113326, -79.070602 38.113306, -79.070536 38.1133, -79.070454 38.113307, -79.070417 38.113319, -79.070321 38.113371, -79.070235 38.113434, -79.070177 38.113497, -79.069862 38.113935, -79.069858 38.113975, -79.069871 38.114013, -79.069899 38.114045, -79.070032 38.114116, -79.070252 38.114217, -79.071055 38.114519, -79.070817 38.114859, -79.070545 38.115219, -79.070419 38.115377, -79.070361 38.11545, -79.069945 38.115943, -79.069327 38.116615, -79.068883 38.11709, -79.068577 38.117408, -79.06815 38.117866, -79.067742 38.118296, -79.067659 38.118387, -79.067607 38.118445, -79.067528 38.118462, -79.067412 38.118499, -79.067326 38.118539, -79.067191 38.118621, -79.067136 38.11864, -79.067039 38.11866, -79.066937 38.118666, -79.066851 38.118663, -79.066766 38.118651, -79.066629 38.118616, -79.066516 38.118571, -79.066287 38.118445, -79.066161 38.118368, -79.066026 38.118273, -79.065859 38.118138, -79.065684 38.11797, -79.065568 38.117831, -79.065409 38.117598, -79.065331 38.117449, -79.065257 38.117263, -79.065215 38.11714, -79.065165 38.117152, -79.065068 38.116702, -79.064671 38.114879, -79.064044 38.112004, -79.063946 38.111522, -79.063882 38.111145, -79.06378 38.110755, -79.063662 38.110417, -79.063495 38.110055, -79.063376 38.109868, -79.063192 38.109621, -79.062929 38.109308, -79.062761 38.109136, -79.062512 38.108906, -79.062064 38.108523, -79.061731 38.10825, -79.061666 38.10831, -79.060029 38.109835, -79.059765 38.110134, -79.059418 38.110431, -79.058865 38.110922, -79.05842 38.111328, -79.056954 38.112691, -79.057069 38.112961, -79.057208 38.113326, -79.057363 38.113713, -79.057417 38.113844, -79.05762 38.114301, -79.057896 38.114956, -79.058239 38.115792, -79.05831 38.11598, -79.058345 38.116112, -79.058362 38.116246, -79.058361 38.116381, -79.058342 38.116514, -79.058313 38.116621, -79.058252 38.116768, -79.058185 38.116878, -79.058121 38.11696, -79.058028 38.117057, -79.057967 38.117109, -79.057821 38.117217, -79.057784 38.117244, -79.057659 38.117318, -79.057532 38.117387, -79.057353 38.117488, -79.057233 38.117573, -79.057173 38.117623, -79.057057 38.117737, -79.056985 38.117825, -79.056892 38.117964, -79.056741 38.118201, -79.05668 38.118307, -79.056633 38.118387, -79.05649 38.118662, -79.056442 38.118781, -79.056425 38.118824, -79.056355 38.119085, -79.056346 38.119126, -79.056286 38.119429, -79.056246 38.119681, -79.05624 38.119809, -79.056252 38.119938, -79.056269 38.120002, -79.056303 38.120078, -79.056359 38.120168, -79.056447 38.120267, -79.0568 38.120585, -79.056954 38.120738, -79.057162 38.12097, -79.057893 38.121816, -79.058099 38.122032, -79.058514 38.122502, -79.058695 38.122719, -79.058878 38.122951, -79.059299 38.123478, -79.059465 38.123687, -79.060218 38.124653, -79.06097 38.12562, -79.061788 38.126681, -79.061919 38.126863, -79.062015 38.127025, -79.06205 38.127102, -79.06207 38.127161, -79.062084 38.127226, -79.061801 38.127232, -79.061548 38.127251, -79.06144 38.127264, -79.061387 38.127279, -79.060027 38.126592, -79.057562 38.125201, -79.057499 38.125165, -79.055619 38.124119, -79.054069 38.123257, -79.053983 38.123215, -79.053681 38.123069, -79.053614 38.123035, -79.05357 38.123014, -79.053379 38.122922, -79.053077 38.122775, -79.052775 38.122628, -79.052473 38.122481, -79.052171 38.122335, -79.051869 38.122188, -79.051567 38.122041, -79.051352 38.121935, -79.051046 38.121783, -79.050739 38.121631, -79.050433 38.12148, -79.050126 38.121328, -79.049819 38.121176, -79.049522 38.121436, -79.049224 38.121696, -79.048926 38.121956, -79.048755 38.122104, -79.04866 38.122022, -79.048455 38.121845, -79.048267 38.121642, -79.048087 38.121389, -79.047533 38.121439, -79.047186 38.121367, -79.047229 38.12133, -79.047435 38.121142, -79.047426 38.121123, -79.047263 38.121116, -79.046975 38.121088, -79.046689 38.121045, -79.046464 38.121, -79.046175 38.120924, -79.045791 38.120807, -79.045415 38.120677, -79.042031 38.119463, -79.041829 38.119391, -79.041038 38.11911, -79.040281 38.118849, -79.038214 38.118118, -79.03702 38.117682, -79.036468 38.117453, -79.036085 38.117309, -79.035603 38.11711, -79.034502 38.116641, -79.034005 38.116418, -79.033161 38.116017, -79.032673 38.115776, -79.031503 38.115169, -79.031154 38.114977, -79.030749 38.114768, -79.02932 38.11401, -79.02894 38.113813, -79.028967 38.113786, -79.029018 38.113764, -79.029112 38.113744, -79.029183 38.113736, -79.02929 38.113731, -79.029772 38.11374, -79.029913 38.113733, -79.023011 38.109971, -79.021962 38.109395, -79.021499 38.109146, -79.019769 38.108201, -79.019182 38.10787, -79.019038 38.107793, -79.018882 38.107958, -79.018763 38.108162, -79.018712 38.108304, -79.018673 38.108414, -79.018672 38.109276, -79.018693 38.109622, -79.018637 38.109924, -79.018567 38.110161, -79.018463 38.110435, -79.018115 38.110957, -79.017997 38.111231, -79.017718 38.111544, -79.016814 38.112373, -79.016411 38.112604, -79.016167 38.11273, -79.015945 38.112812, -79.015632 38.112949, -79.015041 38.113279, -79.014869 38.11339, -79.014846 38.113405, -79.014686 38.113569, -79.014547 38.113663, -79.014394 38.1138, -79.014373 38.113899, -79.014338 38.113992, -79.014269 38.114064, -79.014151 38.114091, -79.014012 38.114091, -79.013852 38.114074, -79.013678 38.114047, -79.01349 38.113992, -79.013316 38.113909, -79.013178 38.113827, -79.012795 38.113678, -79.012683 38.113599, -79.012718 38.113544, -79.012771 38.113475, -79.0129 38.113497, -79.013525 38.113349, -79.013678 38.113349, -79.013817 38.11336, -79.014012 38.113432, -79.014165 38.113443, -79.014234 38.113416, -79.014373 38.113443, -79.014499 38.113443, -79.014617 38.113416, -79.014742 38.11335, -79.014773 38.113324, -79.014204 38.112896, -79.013634 38.112469, -79.013143 38.113034, -79.013086 38.113013, -79.012991 38.112948, -79.012691 38.112768, -79.012392 38.112634, -79.012239 38.112576, -79.012161 38.112526, -79.012139 38.1125, -79.012136 38.112425, -79.012152 38.112362, -79.012174 38.112305, -79.012182 38.112244, -79.012167 38.112202, -79.012137 38.112176, -79.012065 38.112146, -79.011624 38.112001, -79.01135 38.111917, -79.01121 38.111856, -79.011279 38.111775, -79.011396 38.111619, -79.010584 38.111168, -79.01054 38.111144, -79.010492 38.111122, -79.010448 38.111112, -79.010392 38.111111, -79.010303 38.111124, -79.009593 38.111401, -79.009504 38.111423, -79.009535 38.111387, -79.010169 38.110835, -79.010359 38.110686, -79.010499 38.110617, -79.010623 38.110571, -79.010753 38.110563, -79.010865 38.110576, -79.010964 38.110608, -79.011108 38.110683, -79.011217 38.11078, -79.01128 38.110849, -79.011349 38.110895, -79.011393 38.110912, -79.011463 38.110928, -79.011601 38.110948, -79.01154 38.110715, -79.011381 38.110246, -79.011269 38.109994, -79.010911 38.109309, -79.010794 38.109057, -79.010718 38.108903, -79.010638 38.108742, -79.010523 38.108483, -79.010453 38.108332, -79.010099 38.107573, -79.009982 38.107363, -79.009861 38.107158, -79.009671 38.10689, -79.009653 38.106864, -79.009109 38.106069, -79.0089 38.105767, -79.008648 38.105459, -79.008413 38.105207, -79.008125 38.104912, -79.00787 38.10467, -79.007679 38.104496, -79.007468 38.104317, -79.00691 38.1039, -79.006799 38.103827, -79.006529 38.103651, -79.005837 38.103263, -79.005282 38.102981, -79.004006 38.102413, -79.003063 38.101988, -79.002806 38.101873, -79.001919 38.101489, -79.001317 38.101215, -79.000883 38.10104, -79.000371 38.100864, -79.000005 38.100759, -78.999905 38.100743, -78.999804 38.100742, -78.999704 38.100754, -78.999608 38.100781, -78.999563 38.100799, -78.99908 38.101073, -78.998987 38.101117, -78.998885 38.101148, -78.998776 38.101166, -78.998724 38.101169, -78.998539 38.101149, -78.998449 38.101131, -78.998018 38.10102, -78.9974 38.100843, -78.996932 38.100716, -78.996871 38.100696, -78.996693 38.100623, -78.996393 38.100473, -78.996326 38.100431, -78.996279 38.100391, -78.996213 38.10031, -78.996163 38.100221, -78.996062 38.099968, -78.995977 38.099823, -78.995909 38.099745, -78.99586 38.099702, -78.995765 38.09965, -78.995663 38.099607, -78.995597 38.099585, -78.995433 38.099544, -78.995097 38.099425, -78.995058 38.0994, -78.99504 38.099377, -78.995025 38.099357, -78.994952 38.099354, -78.99489 38.099344, -78.994132 38.099183, -78.994081 38.099181, -78.994033 38.099194, -78.994 38.099213, -78.993966 38.099244, -78.993948 38.099269, -78.992526 38.099152, -78.991729 38.099087, -78.990557 38.099158, -78.986379 38.09936, -78.985962 38.099301, -78.982306 38.098783, -78.98149 38.098686, -78.981501 38.098597, -78.981494 38.098494, -78.98147 38.098338, -78.981448 38.097993, -78.98144 38.097515, -78.981406 38.096232, -78.981385 38.095882, -78.981365 38.09553, -78.981339 38.095277, -78.981303 38.095076, -78.981177 38.094684, -78.981083 38.094391, -78.980905 38.093863, -78.980783 38.093517, -78.980746 38.093387, -78.980642 38.093063, -78.980589 38.092932, -78.980504 38.092791, -78.980436 38.092705, -78.980264 38.09253, -78.980158 38.092427, -78.979745 38.092048, -78.979636 38.091954, -78.979441 38.091786, -78.979306 38.091692, -78.979175 38.091611, -78.979009 38.091834, -78.978868 38.09199, -78.978791 38.092064, -78.978737 38.092116, -78.978483 38.092331, -78.978234 38.092517, -78.977682 38.092919, -78.977326 38.093178, -78.977187 38.093276, -78.976975 38.093427, -78.976527 38.093743, -78.976014 38.094095, -78.975578 38.094403, -78.975131 38.094726, -78.973906 38.09563, -78.973352 38.09604, -78.972797 38.096449, -78.972614 38.09658, -78.972432 38.096712, -78.972377 38.09675, -78.972169 38.096875, -78.971887 38.097011, -78.971753 38.097065, -78.971577 38.09712, -78.971359 38.097167, -78.971174 38.097196, -78.970753 38.097236, -78.970015 38.097301, -78.969867 38.096212, -78.969832 38.096007, -78.968094 38.096161, -78.968114 38.096323, -78.968132 38.096535, -78.968222 38.09725, -78.968227 38.097389, -78.967671 38.097334, -78.967545 38.097329, -78.96743 38.097339, -78.967389 38.097349, -78.967326 38.097375, -78.967247 38.097433, -78.967153 38.097533)))"} -{"geo_id":"08434","urban_area_code":"08434","name":"Bloomsburg--Berwick, PA","lsad_name":"Bloomsburg--Berwick, PA Urbanized Area","area_lsad_code":"75","mtfcc_feature_class_code":"G3500","type":"U","functional_status":"S","area_land_meters":84724852,"area_water_meters":3051795,"internal_point_lon":-76.41213,"internal_point_lat":41.016958,"internal_point_geom":"POINT(-76.41213 41.016958)","urban_area_geom":"MULTIPOLYGON(((-76.277562 41.054267, -76.278156 41.054427, -76.278491 41.054517, -76.279892 41.054875, -76.281675 41.055348, -76.28141 41.055428, -76.281256 41.055474, -76.280564 41.055679, -76.280526 41.055696, -76.280134 41.055604, -76.279389 41.055442, -76.279329 41.055436, -76.279277 41.05544, -76.279197 41.055458, -76.27914 41.055481, -76.27909 41.055516, -76.279038 41.055577, -76.279024 41.055602, -76.279001 41.055644, -76.278973 41.055732, -76.278969 41.055786, -76.27898 41.055836, -76.279015 41.055886, -76.279071 41.055931, -76.279178 41.055994, -76.279268 41.056031, -76.279328 41.056045, -76.279378 41.056057, -76.279535 41.056095, -76.281644 41.056657, -76.280801 41.056816, -76.279968 41.05695, -76.279889 41.056969, -76.279858 41.05699, -76.279834 41.057038, -76.279832 41.057159, -76.279859 41.057377, -76.279966 41.057945, -76.279979 41.057969, -76.280002 41.057986, -76.28004 41.057996, -76.280944 41.057848, -76.28107 41.057827, -76.281587 41.057749, -76.283612 41.057485, -76.283686 41.05745, -76.283742 41.057407, -76.283782 41.057356, -76.283805 41.057295, -76.283807 41.057194, -76.283793 41.056686, -76.28377 41.056638, -76.283728 41.056598, -76.283692 41.056578, -76.281561 41.056009, -76.281249 41.055917, -76.280948 41.055809, -76.281062 41.055821, -76.281285 41.055821, -76.281349 41.055824, -76.281486 41.055838, -76.281804 41.055884, -76.282089 41.055907, -76.282208 41.055909, -76.282362 41.055895, -76.282489 41.055872, -76.282578 41.055845, -76.282653 41.055813, -76.282747 41.055759, -76.282801 41.055716, -76.282843 41.055658, -76.284838 41.056185, -76.287002 41.056756, -76.28871 41.05721, -76.291981 41.058079, -76.292814 41.0583, -76.294128 41.05864, -76.295853 41.059098, -76.297383 41.059495, -76.29936 41.060031, -76.301064 41.060476, -76.301686 41.060639, -76.302182 41.06076, -76.302197 41.060774, -76.304085 41.061281, -76.304112 41.061394, -76.304435 41.061736, -76.304951 41.062052, -76.304899 41.062077, -76.304872 41.062109, -76.304867 41.062169, -76.304847 41.062197, -76.304716 41.062239, -76.304633 41.06223, -76.304587 41.062213, -76.304467 41.062199, -76.303939 41.062526, -76.303826 41.062614, -76.303734 41.06285, -76.303695 41.062978, -76.303686 41.063039, -76.303627 41.063107, -76.303603 41.063163, -76.303608 41.063204, -76.303705 41.063334, -76.303729 41.063476, -76.303751 41.063538, -76.303826 41.063586, -76.303849 41.063635, -76.303839 41.063795, -76.303795 41.064192, -76.303819 41.064308, -76.303813 41.064409, -76.30377 41.064489, -76.30373 41.064584, -76.303709 41.064687, -76.303704 41.064748, -76.303672 41.064816, -76.303654 41.064939, -76.303682 41.064997, -76.303762 41.065097, -76.303803 41.065158, -76.303823 41.0653, -76.30401 41.065468, -76.304057 41.065518, -76.304099 41.065628, -76.304113 41.065665, -76.30414 41.065706, -76.304159 41.065717, -76.304241 41.065719, -76.304319 41.06571, -76.304416 41.065734, -76.304427 41.065705, -76.304462 41.065588, -76.304578 41.065272, -76.304613 41.065132, -76.304625 41.065025, -76.304627 41.064711, -76.30462 41.064521, -76.304632 41.064103, -76.304684 41.0633, -76.304732 41.062665, -76.304765 41.062521, -76.304818 41.062415, -76.304887 41.06232, -76.304955 41.062253, -76.305089 41.062143, -76.305188 41.062062, -76.305319 41.06197, -76.30549 41.061818, -76.305546 41.061748, -76.305595 41.061676, -76.305421 41.061631, -76.305447 41.061591, -76.305462 41.061568, -76.305533 41.06152, -76.305553 41.061499, -76.305625 41.061446, -76.305722 41.061397, -76.305976 41.061251, -76.30605 41.061216, -76.3061 41.061167, -76.306131 41.061086, -76.306142 41.061021, -76.306108 41.06086, -76.306097 41.060756, -76.306076 41.060653, -76.306075 41.060561, -76.306121 41.060447, -76.306099 41.060365, -76.306035 41.060327, -76.305989 41.060309, -76.305848 41.060238, -76.305755 41.060195, -76.305589 41.06009, -76.30552 41.060027, -76.305471 41.059957, -76.305367 41.059851, -76.305313 41.059808, -76.305229 41.059759, -76.305138 41.059717, -76.305039 41.059698, -76.304966 41.0597, -76.304889 41.059679, -76.304832 41.05964, -76.304717 41.059597, -76.304394 41.059578, -76.304312 41.059567, -76.304157 41.059526, -76.304089 41.059498, -76.303987 41.059485, -76.303854 41.059479, -76.303742 41.059454, -76.303661 41.059486, -76.303595 41.059547, -76.303533 41.059685, -76.303443 41.05969, -76.303365 41.059688, -76.303256 41.059665, -76.30319 41.059641, -76.303114 41.059586, -76.303092 41.05952, -76.303105 41.059465, -76.303143 41.059384, -76.303148 41.059306, -76.303096 41.059254, -76.303002 41.059253, -76.302922 41.059259, -76.302848 41.059251, -76.302783 41.059192, -76.302756 41.059138, -76.302722 41.059085, -76.302671 41.05904, -76.3026 41.059016, -76.302577 41.058995, -76.302548 41.058968, -76.302487 41.058928, -76.302398 41.05889, -76.302356 41.058842, -76.302321 41.058719, -76.302329 41.058649, -76.302328 41.058598, -76.30232 41.058577, -76.302286 41.058532, -76.302235 41.058583, -76.30201 41.058821, -76.301964 41.058889, -76.301582 41.058514, -76.301452 41.058369, -76.301408 41.058332, -76.301312 41.058268, -76.301209 41.058219, -76.301161 41.058205, -76.301002 41.058174, -76.300253 41.058088, -76.298849 41.057956, -76.298726 41.057937, -76.298604 41.057906, -76.298546 41.057886, -76.29846 41.05784, -76.298384 41.057778, -76.298266 41.057629, -76.298142 41.057446, -76.297966 41.057168, -76.297604 41.056637, -76.296931 41.055651, -76.296832 41.055526, -76.296809 41.055498, -76.29671 41.055404, -76.296542 41.055275, -76.296434 41.0552, -76.296205 41.055061, -76.295965 41.054928, -76.295837 41.054875, -76.295758 41.054852, -76.295626 41.054833, -76.295483 41.05483, -76.29539 41.054838, -76.295104 41.054887, -76.294961 41.054919, -76.293754 41.055261, -76.293208 41.05541, -76.293017 41.055454, -76.292771 41.055486, -76.29215 41.055504, -76.291991 41.055488, -76.291869 41.055463, -76.291788 41.055435, -76.29172 41.055403, -76.291612 41.05534, -76.291505 41.055269, -76.291422 41.055236, -76.291322 41.055214, -76.291268 41.055202, -76.291117 41.055184, -76.290981 41.05518, -76.290837 41.055192, -76.290476 41.055248, -76.29028 41.055286, -76.290019 41.055345, -76.289749 41.055413, -76.28949 41.055457, -76.28942 41.055465, -76.28921 41.055489, -76.289067 41.055498, -76.288829 41.055513, -76.288511 41.055524, -76.288297 41.055519, -76.287735 41.055479, -76.287319 41.055441, -76.286748 41.055375, -76.286338 41.055342, -76.285974 41.055329, -76.284795 41.05533, -76.284554 41.055329, -76.284171 41.055327, -76.28316 41.055347, -76.282845 41.055345, -76.282556 41.055324, -76.282291 41.055289, -76.282096 41.055253, -76.28206 41.055244, -76.282036 41.055238, -76.281746 41.055168, -76.281612 41.055121, -76.281537 41.055088, -76.28149 41.055058, -76.281414 41.054984, -76.281503 41.054959, -76.281625 41.054906, -76.281741 41.05484, -76.281785 41.054808, -76.281851 41.054742, -76.281952 41.054595, -76.281997 41.054506, -76.282029 41.054403, -76.282041 41.054319, -76.282041 41.054295, -76.277953 41.05424, -76.277562 41.054267)), ((-76.632518 40.986132, -76.632672 40.986173, -76.632542 40.986047, -76.632465 40.985982, -76.632372 40.985894, -76.632305 40.985821, -76.632242 40.985767, -76.632226 40.985838, -76.632366 40.985963, -76.632418 40.986021, -76.632506 40.98611, -76.632518 40.986132)), ((-76.632518 40.986132, -76.632403 40.986088, -76.632377 40.986083, -76.632265 40.986044, -76.632186 40.986021, -76.632148 40.98601, -76.63206 40.986003, -76.631994 40.986008, -76.63195 40.986018, -76.63169 40.986119, -76.631734 40.986169, -76.631994 40.986076, -76.632054 40.986068, -76.632153 40.986067, -76.632204 40.986075, -76.632244 40.986074, -76.6324 40.986102, -76.632518 40.986132)), ((-76.613566 41.001818, -76.61334 41.002142, -76.613173 41.002405, -76.613013 41.002671, -76.612874 41.002929, -76.612723 41.003231, -76.612545 41.003622, -76.612382 41.004032, -76.612335 41.004175, -76.613114 41.004227, -76.613468 41.00424, -76.613666 41.004257, -76.614043 41.004318, -76.614244 41.004362, -76.614718 41.004485, -76.615233 41.004634, -76.615833 41.004798, -76.61672 41.005022, -76.617293 41.00517, -76.617638 41.005268, -76.617743 41.005275, -76.617801 41.005267, -76.617826 41.005256, -76.617863 41.005211, -76.617943 41.004955, -76.618197 41.004282, -76.618417 41.003687, -76.618678 41.00296, -76.619006 41.002071, -76.619035 41.001975, -76.61905 41.001812, -76.618708 41.001811, -76.617482 41.001815, -76.617019 41.001819, -76.61614 41.001817, -76.614555 41.001826, -76.614214 41.001822, -76.613566 41.001818)), ((-76.45497 40.94632, -76.454684 40.946327, -76.454475 40.946327, -76.454224 40.946321, -76.454073 40.946329, -76.453904 40.946356, -76.453351 40.946472, -76.453213 40.946497, -76.452876 40.94655, -76.45246 40.946622, -76.451457 40.946776, -76.450629 40.946922, -76.450029 40.947016, -76.449819 40.947033, -76.44974 40.947046, -76.44966 40.947305, -76.449578 40.947704, -76.449475 40.94806, -76.449445 40.948239, -76.449443 40.948329, -76.449457 40.948404, -76.449472 40.948479, -76.449498 40.94852, -76.449748 40.948796, -76.449899 40.948983, -76.449956 40.949087, -76.449977 40.949159, -76.449978 40.949195, -76.449954 40.949264, -76.44992 40.949319, -76.449861 40.949372, -76.449774 40.949432, -76.449626 40.949543, -76.448888 40.950063, -76.448567 40.950277, -76.448422 40.950382, -76.448274 40.950497, -76.448199 40.950569, -76.448149 40.950631, -76.448042 40.950781, -76.448482 40.950492, -76.448857 40.950222, -76.449125 40.950047, -76.449359 40.949905, -76.449677 40.949729, -76.449927 40.949602, -76.450303 40.949422, -76.45102 40.949096, -76.451233 40.948982, -76.45167 40.948747, -76.45183 40.948653, -76.452101 40.94848, -76.452269 40.948362, -76.452461 40.948708, -76.452531 40.948841, -76.452583 40.948928, -76.452667 40.948872, -76.452753 40.948815, -76.45299 40.948673, -76.453169 40.948522, -76.453452 40.948296, -76.45366 40.948115, -76.45391 40.947969, -76.454084 40.947772, -76.454213 40.947657, -76.454307 40.947551, -76.454424 40.947428, -76.454536 40.947338, -76.454644 40.947264, -76.454788 40.947173, -76.454939 40.947112, -76.455176 40.947043, -76.455303 40.946991, -76.455503 40.946925, -76.455627 40.946806, -76.45567 40.946777, -76.4559 40.946781, -76.456034 40.946757, -76.456139 40.946688, -76.456211 40.946626, -76.456186 40.946151, -76.456186 40.946064, -76.456196 40.945827, -76.456157 40.945829, -76.455749 40.945965, -76.455563 40.946017, -76.455313 40.946117, -76.455127 40.946203, -76.45497 40.94632)), ((-76.655849 40.954629, -76.655682 40.95476, -76.655283 40.955051, -76.654988 40.955257, -76.654481 40.955567, -76.654141 40.95575, -76.653906 40.955876, -76.652953 40.956324, -76.652081 40.956719, -76.650425 40.957468, -76.64998 40.957681, -76.649694 40.957819, -76.649368 40.957975, -76.648658 40.958315, -76.647704 40.958678, -76.647509 40.958742, -76.647216 40.958838, -76.64677 40.958971, -76.646394 40.959057, -76.645811 40.959176, -76.642032 40.959713, -76.64123 40.959817, -76.641208 40.959636, -76.641062 40.958426, -76.640942 40.957904, -76.640784 40.957284, -76.640723 40.957277, -76.640592 40.957293, -76.640226 40.957357, -76.639238 40.957513, -76.638925 40.956316, -76.638578 40.955008, -76.638476 40.954715, -76.636906 40.954996, -76.636348 40.955096, -76.63552 40.955233, -76.635351 40.955252, -76.635295 40.955254, -76.634559 40.955238, -76.634338 40.954413, -76.634821 40.954288, -76.634949 40.954231, -76.635126 40.954127, -76.635428 40.953867, -76.635493 40.953789, -76.635559 40.95371, -76.635639 40.953579, -76.635665 40.953505, -76.635662 40.953357, -76.635604 40.953282, -76.635443 40.953174, -76.635326 40.95312, -76.635247 40.953113, -76.635067 40.953121, -76.634903 40.953143, -76.634029 40.953265, -76.633705 40.952065, -76.63337 40.950825, -76.633128 40.94993, -76.63288 40.94893, -76.632771 40.948488, -76.632672 40.948174, -76.632601 40.947947, -76.632526 40.947751, -76.632435 40.947514, -76.63221 40.947138, -76.632165 40.947062, -76.631916 40.946646, -76.63204 40.946474, -76.632318 40.94596, -76.632499 40.945472, -76.632632 40.945148, -76.632682 40.94496, -76.632727 40.944792, -76.632854 40.944457, -76.632977 40.944171, -76.633038 40.943992, -76.633118 40.943827, -76.633139 40.943794, -76.629676 40.944287, -76.628963 40.944389, -76.627658 40.944602, -76.624807 40.945083, -76.623675 40.945273, -76.620626 40.945786, -76.618734 40.945689, -76.617928 40.945647, -76.617958 40.945711, -76.617892 40.94652, -76.617665 40.946845, -76.617482 40.947032, -76.616962 40.947563, -76.616499 40.948408, -76.615908 40.949006, -76.615697 40.949222, -76.615547 40.949685, -76.615492 40.949749, -76.61514 40.950159, -76.615235 40.950263, -76.61529 40.950323, -76.615193 40.950379, -76.615248 40.950427, -76.615731 40.950904, -76.616807 40.951634, -76.616864 40.951662, -76.617539 40.952091, -76.619114 40.953054, -76.61916 40.953076, -76.621258 40.954361, -76.622237 40.954947, -76.622296 40.954987, -76.622687 40.955226, -76.623293 40.955596, -76.623327 40.955617, -76.623202 40.95588, -76.622917 40.956243, -76.622768 40.956427, -76.622862 40.956515, -76.623128 40.956824, -76.623162 40.956853, -76.623586 40.957217, -76.624678 40.95777, -76.625745 40.958598, -76.627905 40.959765, -76.629073 40.960288, -76.629469 40.960513, -76.629684 40.9607, -76.629897 40.960963, -76.630324 40.96123, -76.631224 40.9616, -76.632903 40.962302, -76.633926 40.962684, -76.634543 40.962928, -76.635123 40.963046, -76.635839 40.96313, -76.636908 40.963221, -76.638526 40.963382, -76.639685 40.963431, -76.640831 40.963359, -76.641952 40.963244, -76.642723 40.963191, -76.643767 40.963145, -76.643856 40.963128, -76.643707 40.962804, -76.644037 40.962769, -76.644119 40.962761, -76.644707 40.962661, -76.645386 40.962497, -76.645576 40.962469, -76.645941 40.962402, -76.646321 40.96232, -76.646624 40.962193, -76.646799 40.962125, -76.647246 40.96199, -76.648308 40.96161, -76.64901 40.961322, -76.649787 40.960971, -76.650561 40.960608, -76.65147 40.960134, -76.652547 40.959422, -76.65296 40.959138, -76.652785 40.958995, -76.65272 40.958942, -76.652673 40.958828, -76.653739 40.957918, -76.656204 40.955807, -76.655878 40.954724, -76.655849 40.954629)), ((-76.632672 40.986173, -76.632773 40.986271, -76.632836 40.986324, -76.632939 40.986369, -76.632958 40.986438, -76.633191 40.986663, -76.633258 40.986691, -76.63333 40.986705, -76.633375 40.986726, -76.633449 40.98677, -76.633509 40.986822, -76.6336 40.986911, -76.633734 40.987104, -76.63386 40.987296, -76.633919 40.98736, -76.634057 40.987461, -76.634187 40.98757, -76.634277 40.987618, -76.63438 40.987691, -76.634483 40.987773, -76.634617 40.987904, -76.634649 40.987955, -76.634671 40.988001, -76.634722 40.988067, -76.634767 40.988106, -76.634823 40.988142, -76.635037 40.988234, -76.635107 40.98828, -76.635215 40.988414, -76.635249 40.988479, -76.635259 40.988526, -76.635361 40.988695, -76.635407 40.988755, -76.635443 40.988794, -76.635517 40.98883, -76.635585 40.988841, -76.635717 40.988923, -76.63585 40.989038, -76.635947 40.989153, -76.635948 40.989202, -76.635974 40.989248, -76.636029 40.989329, -76.636067 40.989426, -76.636114 40.989445, -76.63612 40.989488, -76.636321 40.989874, -76.636398 40.98998, -76.636505 40.990093, -76.636597 40.990166, -76.636737 40.990329, -76.636777 40.99039, -76.636846 40.990533, -76.636879 40.990638, -76.63691 40.990801, -76.636942 40.990948, -76.636945 40.991009, -76.63699 40.991113, -76.637138 40.991334, -76.637211 40.991399, -76.637272 40.991446, -76.637316 40.991499, -76.637464 40.991518, -76.637622 40.991495, -76.637709 40.991495, -76.63785 40.991518, -76.637919 40.991578, -76.637952 40.991627, -76.637974 40.991686, -76.638015 40.991734, -76.638024 40.99175, -76.638058 40.991808, -76.638077 40.991875, -76.638082 40.99193, -76.638073 40.992096, -76.638075 40.992148, -76.638069 40.992184, -76.638057 40.992364, -76.638066 40.992429, -76.638094 40.992469, -76.638189 40.992528, -76.638336 40.99253, -76.638486 40.992537, -76.638537 40.992555, -76.638603 40.992596, -76.638679 40.992626, -76.638723 40.992657, -76.63875 40.99269, -76.638792 40.992718, -76.638832 40.992757, -76.63886 40.992803, -76.63894 40.992964, -76.638955 40.993018, -76.639022 40.993074, -76.639059 40.993093, -76.639134 40.993116, -76.639186 40.993142, -76.639224 40.993181, -76.639272 40.993251, -76.639318 40.99334, -76.639322 40.993384, -76.639339 40.993442, -76.639401 40.993472, -76.639492 40.993535, -76.639537 40.993575, -76.63958 40.993606, -76.639639 40.993638, -76.639729 40.993706, -76.639782 40.993762, -76.639801 40.993797, -76.639809 40.993838, -76.639809 40.99387, -76.6398 40.9939, -76.63977 40.993943, -76.639786 40.993992, -76.639872 40.994066, -76.639917 40.994073, -76.640108 40.994181, -76.640216 40.994207, -76.640274 40.994237, -76.640399 40.994311, -76.640458 40.994323, -76.640561 40.994375, -76.640563 40.994306, -76.640561 40.994085, -76.640564 40.993945, -76.640569 40.99369, -76.640554 40.9935, -76.640522 40.993402, -76.640468 40.993287, -76.640453 40.993264, -76.640077 40.992717, -76.63977 40.992215, -76.639708 40.992113, -76.639404 40.99151, -76.639376 40.991455, -76.63929 40.991275, -76.639191 40.991082, -76.639092 40.990914, -76.639 40.990785, -76.638921 40.990695, -76.638796 40.990568, -76.638658 40.990463, -76.638497 40.99036, -76.638347 40.990275, -76.63789 40.990052, -76.637408 40.989797, -76.637224 40.989683, -76.637091 40.989591, -76.636967 40.989499, -76.636754 40.989321, -76.636668 40.989236, -76.636459 40.989007, -76.636244 40.988742, -76.636099 40.988533, -76.635944 40.988294, -76.6357 40.98788, -76.635622 40.987755, -76.635552 40.987669, -76.635466 40.987577, -76.635325 40.987459, -76.635441 40.987416, -76.635574 40.987377, -76.635847 40.98735, -76.636641 40.987296, -76.63784 40.987174, -76.63813 40.987154, -76.638254 40.987142, -76.638262 40.987125, -76.638259 40.987021, -76.638237 40.986883, -76.637685 40.98684, -76.636803 40.986797, -76.636034 40.986765, -76.635958 40.986762, -76.635388 40.986721, -76.635398 40.986629, -76.635399 40.986425, -76.635401 40.986374, -76.635409 40.986246, -76.635433 40.986057, -76.635449 40.985846, -76.634945 40.985761, -76.634705 40.985722, -76.634074 40.985601, -76.634049 40.985599, -76.634013 40.985607, -76.633969 40.985636, -76.633928 40.985679, -76.633917 40.985702, -76.63391 40.985776, -76.63392 40.986037, -76.633926 40.98634, -76.633907 40.986496, -76.633603 40.986423, -76.633431 40.986378, -76.633271 40.986335, -76.632946 40.986247, -76.632752 40.986194, -76.632672 40.986173)), ((-76.655849 40.954629, -76.655974 40.95453, -76.656163 40.954369, -76.656613 40.953959, -76.657591 40.953003, -76.658423 40.952217, -76.658363 40.95191, -76.658282 40.951492, -76.658228 40.951346, -76.658065 40.95072, -76.657946 40.950348, -76.657926 40.950233, -76.657776 40.949917, -76.65771 40.949827, -76.657677 40.949692, -76.657595 40.94929, -76.657514 40.949057, -76.657536 40.94901, -76.657477 40.948831, -76.657239 40.948155, -76.657284 40.948124, -76.657676 40.947904, -76.658314 40.947526, -76.659268 40.946948, -76.659559 40.946735, -76.659896 40.946424, -76.65995 40.946501, -76.660393 40.946906, -76.66082 40.947112, -76.661324 40.947234, -76.661599 40.947417, -76.661957 40.947466, -76.662537 40.947524, -76.662979 40.947634, -76.663047 40.947706, -76.663112 40.947775, -76.663623 40.947272, -76.663708 40.947166, -76.663598 40.947141, -76.663434 40.947116, -76.662828 40.947164, -76.662381 40.947159, -76.662047 40.947135, -76.661792 40.947106, -76.66158 40.947027, -76.661286 40.946933, -76.660878 40.946706, -76.660023 40.946295, -76.660087 40.946225, -76.659165 40.945504, -76.658688 40.945122, -76.658359 40.944891, -76.65823 40.944837, -76.658157 40.94481, -76.658053 40.944772, -76.657994 40.944754, -76.65801 40.944737, -76.658092 40.94465, -76.658134 40.944587, -76.658143 40.944513, -76.658119 40.944424, -76.65799 40.944114, -76.657937 40.943947, -76.657888 40.943748, -76.65785 40.94357, -76.657762 40.943159, -76.657759 40.943011, -76.657752 40.942978, -76.656159 40.943127, -76.656094 40.943133, -76.656105 40.943526, -76.655492 40.943687, -76.654863 40.943853, -76.654692 40.943904, -76.654576 40.943934, -76.654543 40.943968, -76.654544 40.944019, -76.65457 40.944092, -76.654605 40.944135, -76.654668 40.944148, -76.655548 40.944008, -76.655603 40.944346, -76.655494 40.94435, -76.65525 40.944346, -76.655091 40.944361, -76.654945 40.944522, -76.654746 40.944689, -76.654527 40.944815, -76.654244 40.944865, -76.653923 40.944888, -76.653619 40.944873, -76.653489 40.944747, -76.653466 40.944602, -76.653505 40.944529, -76.653351 40.944575, -76.653145 40.944655, -76.652862 40.944601, -76.652947 40.944891, -76.65405 40.948621, -76.654176 40.949048, -76.654402 40.94981, -76.654894 40.951444, -76.655814 40.954528, -76.655849 40.954629)), ((-76.567395 40.968408, -76.567061 40.968447, -76.567078 40.968513, -76.567143 40.968656, -76.567202 40.968736, -76.567316 40.96893, -76.567371 40.969044, -76.567397 40.96915, -76.567412 40.969176, -76.567431 40.969266, -76.567445 40.969409, -76.56744 40.969592, -76.567404 40.969869, -76.56737 40.970011, -76.567351 40.970062, -76.567308 40.970115, -76.567255 40.970164, -76.567145 40.970229, -76.567047 40.970266, -76.566946 40.970313, -76.566881 40.970363, -76.56685 40.970409, -76.566835 40.970466, -76.566833 40.970507, -76.566852 40.970562, -76.567478 40.971636, -76.567536 40.971717, -76.567695 40.971877, -76.567753 40.971927, -76.567811 40.971989, -76.567863 40.972054, -76.568059 40.972359, -76.568173 40.972548, -76.568231 40.972656, -76.568257 40.972719, -76.568282 40.972829, -76.568306 40.972979, -76.568337 40.97309, -76.56843 40.973268, -76.568499 40.97336, -76.568569 40.973489, -76.568657 40.973626, -76.568675 40.973654, -76.568733 40.973773, -76.568921 40.973467, -76.567607 40.969159, -76.567395 40.968408)), ((-76.462295 40.957592, -76.462183 40.957572, -76.461942 40.957568, -76.461864 40.957571, -76.461817 40.957579, -76.461745 40.957598, -76.461647 40.957632, -76.461406 40.957779, -76.461274 40.957899, -76.461219 40.957942, -76.461151 40.957983, -76.46094 40.95803, -76.460753 40.958129, -76.460723 40.958151, -76.460713 40.958168, -76.460674 40.958245, -76.460598 40.95836, -76.460597 40.958487, -76.460612 40.958766, -76.460609 40.958991, -76.460653 40.959131, -76.460675 40.959155, -76.460731 40.95927, -76.460732 40.959337, -76.460718 40.959538, -76.460719 40.95976, -76.460738 40.959987, -76.460753 40.960079, -76.460732 40.960289, -76.46067 40.960745, -76.460674 40.960952, -76.460672 40.961149, -76.460675 40.961167, -76.46087 40.961251, -76.460868 40.961208, -76.46088 40.961058, -76.460985 40.960577, -76.460991 40.960552, -76.461095 40.959974, -76.461101 40.959824, -76.461031 40.959256, -76.460997 40.959111, -76.460898 40.958836, -76.460877 40.95874, -76.460878 40.958647, -76.460902 40.958578, -76.460956 40.958491, -76.461023 40.958415, -76.461107 40.958348, -76.461216 40.958281, -76.461385 40.958202, -76.461646 40.958103, -76.462095 40.957961, -76.462155 40.957931, -76.462212 40.95789, -76.462241 40.957853, -76.462268 40.957772, -76.462295 40.957592)), ((-76.395145 41.071436, -76.395275 41.071509, -76.39564 41.07169, -76.39601 41.071856, -76.396537 41.07207, -76.39694 41.072241, -76.397612 41.072518, -76.397833 41.072617, -76.398131 41.072742, -76.398588 41.072943, -76.398924 41.073076, -76.399512 41.073346, -76.39962 41.073378, -76.399741 41.073402, -76.399849 41.073408, -76.400057 41.073396, -76.400274 41.073352, -76.400538 41.073288, -76.400587 41.073273, -76.400764 41.073245, -76.400871 41.073241, -76.400952 41.073258, -76.400999 41.073278, -76.401047 41.073308, -76.401079 41.073327, -76.401169 41.073416, -76.401288 41.073567, -76.40169 41.074138, -76.401732 41.074188, -76.401777 41.074233, -76.40184 41.074297, -76.401954 41.074385, -76.402081 41.074456, -76.402336 41.074559, -76.402501 41.074616, -76.402954 41.074748, -76.403225 41.074838, -76.40336 41.074893, -76.403505 41.074969, -76.403602 41.075039, -76.403639 41.075074, -76.403652 41.075087, -76.40373 41.075193, -76.403792 41.075316, -76.403852 41.075512, -76.403858 41.075536, -76.403934 41.075819, -76.403996 41.076003, -76.404049 41.076115, -76.404068 41.076141, -76.404166 41.076227, -76.404248 41.076264, -76.40433 41.076289, -76.404417 41.076301, -76.404646 41.076303, -76.404729 41.076297, -76.404806 41.076298, -76.404895 41.076299, -76.405155 41.076328, -76.405261 41.076346, -76.405622 41.07643, -76.406422 41.076608, -76.407186 41.076751, -76.407817 41.076884, -76.407901 41.076919, -76.407984 41.076981, -76.407497 41.077065, -76.406933 41.077183, -76.406556 41.077275, -76.406363 41.077318, -76.405713 41.077491, -76.404788 41.07775, -76.404532 41.077821, -76.404294 41.077884, -76.40387 41.078004, -76.403962 41.079119, -76.406052 41.078886, -76.409813 41.078422, -76.410134 41.078567, -76.411094 41.079002, -76.411181 41.079774, -76.411271 41.080807, -76.411295 41.081091, -76.411485 41.082549, -76.410933 41.08318, -76.409598 41.084468, -76.409504 41.084551, -76.409999 41.084865, -76.410055 41.0849, -76.410097 41.084864, -76.410462 41.084532, -76.410689 41.084339, -76.411256 41.083833, -76.411487 41.083635, -76.411806 41.083337, -76.412 41.083121, -76.412147 41.08292, -76.412355 41.082599, -76.41242 41.082475, -76.41252 41.082234, -76.412649 41.081852, -76.412683 41.081738, -76.412885 41.081129, -76.413076 41.080625, -76.4131 41.080578, -76.413356 41.080671, -76.413727 41.080831, -76.413874 41.080904, -76.414055 41.080973, -76.414159 41.080997, -76.41422 41.080997, -76.414244 41.080987, -76.414316 41.080917, -76.414451 41.08074, -76.41461 41.080815, -76.414647 41.080828, -76.414703 41.080846, -76.414764 41.08085, -76.414824 41.080835, -76.414872 41.080807, -76.415092 41.080633, -76.415211 41.080552, -76.41533 41.080459, -76.415581 41.08028, -76.415683 41.080212, -76.415889 41.080061, -76.415973 41.079999, -76.416043 41.079933, -76.416106 41.079836, -76.416355 41.079311, -76.416379 41.079246, -76.416417 41.079172, -76.41646 41.079088, -76.416485 41.079016, -76.416475 41.078964, -76.416453 41.078926, -76.416406 41.078896, -76.416211 41.078819, -76.415982 41.078739, -76.415696 41.078639, -76.415612 41.078605, -76.416194 41.077803, -76.416239 41.077803, -76.416396 41.077834, -76.416658 41.077859, -76.416982 41.077877, -76.417389 41.077914, -76.417539 41.077941, -76.417857 41.078011, -76.417971 41.078041, -76.418093 41.078073, -76.418155 41.078086, -76.418264 41.078108, -76.418316 41.078119, -76.418448 41.078136, -76.418565 41.078139, -76.41834 41.077914, -76.418895 41.076523, -76.418807 41.076492, -76.418816 41.076473, -76.418846 41.076381, -76.418862 41.07626, -76.418871 41.076146, -76.418872 41.076052, -76.418909 41.075908, -76.418955 41.075817, -76.41896 41.075761, -76.41902 41.07567, -76.419731 41.075644, -76.420656 41.075611, -76.420573 41.075457, -76.420279 41.074912, -76.419812 41.074043, -76.419502 41.073469, -76.419494 41.073453, -76.418025 41.073915, -76.417531 41.074071, -76.417227 41.074315, -76.416934 41.074562, -76.416011 41.074514, -76.41593 41.074519, -76.415951 41.074255, -76.415961 41.074125, -76.416003 41.073768, -76.416009 41.073683, -76.416053 41.073161, -76.416072 41.072984, -76.416082 41.07284, -76.416158 41.072135, -76.416164 41.072009, -76.416175 41.071898, -76.416197 41.071685, -76.415836 41.070798, -76.415761 41.070655, -76.415745 41.070615, -76.415564 41.070604, -76.415406 41.070607, -76.415167 41.070591, -76.415031 41.070562, -76.414905 41.070516, -76.414391 41.070254, -76.41424 41.070188, -76.414143 41.070152, -76.414044 41.07011, -76.413848 41.070039, -76.41357 41.069952, -76.413331 41.069894, -76.413139 41.069863, -76.412849 41.06984, -76.412703 41.069842, -76.412495 41.069861, -76.412322 41.06989, -76.412191 41.069924, -76.411463 41.070139, -76.411203 41.070237, -76.410692 41.070403, -76.410216 41.070551, -76.410003 41.0706, -76.409768 41.070638, -76.409566 41.070661, -76.409401 41.070665, -76.409123 41.070653, -76.408773 41.070606, -76.408345 41.070501, -76.408125 41.07043, -76.408006 41.070382, -76.40779 41.070272, -76.407735 41.070235, -76.407648 41.070157, -76.407583 41.070075, -76.407489 41.069903, -76.407455 41.069818, -76.407427 41.069694, -76.406529 41.069819, -76.400087 41.070716, -76.395145 41.071436)), ((-76.618997 40.994681, -76.619561 40.993933, -76.620313 40.992947, -76.620738 40.992397, -76.6208 40.992309, -76.621444 40.991472, -76.621593 40.991268, -76.622716 40.989801, -76.622739 40.98977, -76.623136 40.989238, -76.623547 40.988655, -76.624223 40.987636, -76.625054 40.986388, -76.625245 40.986089, -76.625402 40.9858, -76.625477 40.98563, -76.625537 40.985478, -76.625587 40.985343, -76.625482 40.985374, -76.625179 40.985482, -76.624947 40.98559, -76.624746 40.985717, -76.624514 40.98588, -76.624232 40.986116, -76.624161 40.986198, -76.624066 40.986345, -76.623912 40.98663, -76.62386 40.986753, -76.623855 40.98681, -76.623856 40.986936, -76.62389 40.987116, -76.623975 40.987294, -76.624018 40.987469, -76.624001 40.987585, -76.623933 40.987704, -76.623805 40.987799, -76.623591 40.987974, -76.623306 40.988247, -76.623113 40.988376, -76.622875 40.988508, -76.6227 40.98857, -76.62256 40.988597, -76.622392 40.988689, -76.622316 40.988752, -76.622311 40.988785, -76.622313 40.988852, -76.622342 40.988927, -76.62238 40.989005, -76.622403 40.989078, -76.622432 40.989125, -76.622487 40.989199, -76.622572 40.989378, -76.622551 40.989461, -76.622521 40.989543, -76.622464 40.989634, -76.622445 40.989651, -76.622319 40.989768, -76.6222 40.989822, -76.622098 40.989885, -76.622044 40.989908, -76.621978 40.989954, -76.621883 40.990131, -76.621793 40.990268, -76.62169 40.990389, -76.621587 40.990482, -76.621532 40.990518, -76.621466 40.990544, -76.621363 40.990567, -76.621114 40.990583, -76.62097 40.990581, -76.620853 40.990541, -76.620766 40.99049, -76.620706 40.990469, -76.620602 40.990473, -76.620555 40.990481, -76.620376 40.99055, -76.62016 40.990663, -76.619906 40.990783, -76.619787 40.990859, -76.619746 40.990912, -76.619691 40.991, -76.619641 40.991134, -76.619597 40.991223, -76.619538 40.991305, -76.619129 40.9918, -76.619115 40.991842, -76.619092 40.991955, -76.618934 40.992367, -76.618913 40.992498, -76.618914 40.992597, -76.61895 40.992676, -76.619023 40.992747, -76.619074 40.992789, -76.619144 40.992835, -76.619203 40.992885, -76.619277 40.992984, -76.619285 40.993012, -76.619286 40.993135, -76.619272 40.993182, -76.619237 40.993251, -76.619191 40.993324, -76.619054 40.99344, -76.618933 40.993494, -76.618865 40.993515, -76.618819 40.993522, -76.618773 40.99352, -76.618698 40.993518, -76.618506 40.993505, -76.618437 40.993511, -76.618288 40.993532, -76.618032 40.993552, -76.617728 40.993717, -76.617347 40.994082, -76.617142 40.994237, -76.616991 40.994295, -76.616028 40.994912, -76.615844 40.99504, -76.615676 40.99513, -76.615556 40.995167, -76.615472 40.995172, -76.615393 40.995158, -76.615317 40.995125, -76.61528 40.995097, -76.615203 40.995071, -76.615082 40.995078, -76.615011 40.995087, -76.61493 40.99511, -76.614903 40.995137, -76.614881 40.995167, -76.614881 40.995214, -76.614868 40.995243, -76.614788 40.995321, -76.614673 40.995388, -76.614559 40.995425, -76.614419 40.995445, -76.614068 40.995478, -76.613771 40.995486, -76.613543 40.995468, -76.613287 40.99543, -76.612945 40.995368, -76.612682 40.995325, -76.612116 40.995225, -76.611582 40.995066, -76.611483 40.995033, -76.611425 40.994987, -76.611387 40.994915, -76.611386 40.994845, -76.611402 40.994789, -76.61143 40.994746, -76.611466 40.99471, -76.611529 40.99467, -76.61159 40.994625, -76.611656 40.994507, -76.611661 40.994313, -76.611675 40.994233, -76.611675 40.994127, -76.61166 40.994051, -76.611577 40.993973, -76.611527 40.993945, -76.611461 40.993898, -76.611357 40.993811, -76.611257 40.993712, -76.611065 40.993442, -76.611057 40.993384, -76.611067 40.993311, -76.611066 40.993245, -76.611003 40.99319, -76.610937 40.993156, -76.610884 40.993142, -76.610761 40.993151, -76.610715 40.993162, -76.610624 40.993184, -76.610443 40.993378, -76.610344 40.993466, -76.610283 40.993508, -76.610167 40.993575, -76.610124 40.993591, -76.610036 40.993596, -76.609954 40.993591, -76.609881 40.993578, -76.609719 40.993581, -76.609641 40.993591, -76.609531 40.993616, -76.609498 40.993631, -76.609466 40.993644, -76.609269 40.993747, -76.609221 40.99378, -76.609171 40.993831, -76.60914 40.993871, -76.609119 40.99391, -76.609038 40.99399, -76.608836 40.994145, -76.608781 40.994201, -76.60877 40.994235, -76.608768 40.994267, -76.608771 40.994299, -76.608794 40.994353, -76.608827 40.994382, -76.608898 40.994461, -76.60895 40.994539, -76.609011 40.994647, -76.60904 40.994767, -76.609045 40.994828, -76.609056 40.994879, -76.609083 40.994943, -76.609098 40.995001, -76.609133 40.99509, -76.609273 40.995322, -76.60932 40.995431, -76.609364 40.995507, -76.609422 40.995583, -76.609448 40.995655, -76.609467 40.995738, -76.609456 40.995824, -76.609429 40.995919, -76.609421 40.995976, -76.609414 40.996027, -76.609423 40.996502, -76.609414 40.996556, -76.609394 40.996619, -76.609369 40.996731, -76.609333 40.996794, -76.609314 40.996848, -76.609287 40.996976, -76.609282 40.997066, -76.60929 40.997344, -76.609342 40.997546, -76.609393 40.997647, -76.609394 40.997725, -76.609366 40.99779, -76.609356 40.997922, -76.609363 40.998018, -76.609372 40.998053, -76.609391 40.998089, -76.609459 40.998149, -76.609573 40.998206, -76.60969 40.998279, -76.609761 40.99835, -76.609792 40.998402, -76.609844 40.998463, -76.610037 40.998622, -76.610179 40.998711, -76.610295 40.998815, -76.610747 40.999369, -76.610764 40.9994, -76.610799 40.999651, -76.610861 40.999849, -76.610867 40.999887, -76.61089 41.000031, -76.610887 41.000085, -76.610881 41.000217, -76.610867 41.000299, -76.610856 41.000363, -76.610823 41.00047, -76.610807 41.000532, -76.610788 41.000609, -76.61072 41.000772, -76.610683 41.000861, -76.610575 41.001056, -76.610515 41.00124, -76.610473 41.001483, -76.610449 41.001658, -76.610447 41.001774, -76.610459 41.001882, -76.610465 41.001915, -76.610568 41.00188, -76.61081 41.001809, -76.611179 41.001737, -76.611519 41.00171, -76.611782 41.00171, -76.611898 41.001719, -76.612364 41.001778, -76.612719 41.001817, -76.612913 41.00182, -76.613115 41.001834, -76.613317 41.001836, -76.613483 41.00183, -76.613566 41.001818, -76.613678 41.001662, -76.613809 41.00149, -76.613978 41.001267, -76.614366 41.000758, -76.614522 41.000554, -76.614708 41.000309, -76.614821 41.00016, -76.61491 41.000045, -76.615062 40.99985, -76.615211 40.999648, -76.615471 40.999311, -76.615579 40.999166, -76.616303 40.998214, -76.61677 40.9976, -76.618922 40.994779, -76.618997 40.994681)), ((-76.228578 41.079845, -76.228969 41.07987, -76.230005 41.079979, -76.230577 41.080025, -76.23082 41.080053, -76.231118 41.080098, -76.231364 41.080144, -76.231428 41.080163, -76.231614 41.080257, -76.231675 41.080292, -76.232089 41.08061, -76.232218 41.080733, -76.2324 41.080922, -76.232521 41.081013, -76.232415 41.081152, -76.232503 41.08124, -76.232563 41.081306, -76.232582 41.081327, -76.232805 41.0815, -76.232978 41.081606, -76.233093 41.081669, -76.233138 41.081694, -76.2332 41.081712, -76.233271 41.081717, -76.233354 41.081708, -76.233395 41.081699, -76.233471 41.081668, -76.233554 41.081621, -76.233693 41.081534, -76.233854 41.081443, -76.234071 41.081295, -76.234303 41.081151, -76.234924 41.0808, -76.235129 41.080668, -76.235308 41.080547, -76.235427 41.08048, -76.235563 41.080423, -76.235624 41.080405, -76.235705 41.080395, -76.235836 41.080398, -76.235903 41.080405, -76.23607 41.080433, -76.236295 41.080459, -76.236526 41.080492, -76.236786 41.080519, -76.236853 41.080523, -76.237085 41.080512, -76.237132 41.080503, -76.237271 41.08046, -76.236347 41.079448, -76.235985 41.079065, -76.235868 41.078912, -76.235775 41.07877, -76.235757 41.078735, -76.235809 41.078702, -76.235858 41.078678, -76.23585 41.078609, -76.235866 41.078435, -76.235872 41.078397, -76.235883 41.078262, -76.235913 41.078196, -76.235945 41.078101, -76.235976 41.077886, -76.235986 41.077655, -76.235938 41.077556, -76.235921 41.077499, -76.235906 41.077475, -76.235878 41.077418, -76.235877 41.077362, -76.23589 41.077297, -76.235919 41.077213, -76.235936 41.077132, -76.235937 41.077063, -76.235929 41.076961, -76.235912 41.076894, -76.23592 41.076741, -76.235931 41.076663, -76.235977 41.076492, -76.235967 41.076462, -76.235662 41.076404, -76.235457 41.076341, -76.235363 41.076283, -76.235312 41.076237, -76.235191 41.076186, -76.235104 41.076189, -76.234732 41.076167, -76.23465 41.076153, -76.234293 41.076112, -76.23422 41.076112, -76.234079 41.076119, -76.233991 41.076135, -76.233626 41.076159, -76.23341 41.076182, -76.233198 41.076229, -76.23307 41.07627, -76.232975 41.076315, -76.232865 41.076354, -76.232786 41.076397, -76.232729 41.076429, -76.232404 41.076624, -76.232324 41.076656, -76.232291 41.076662, -76.232152 41.076659, -76.232094 41.076648, -76.231977 41.076585, -76.231933 41.076553, -76.231909 41.076523, -76.231853 41.076395, -76.231824 41.076355, -76.231805 41.076308, -76.231764 41.076272, -76.231602 41.076172, -76.231494 41.076096, -76.231408 41.076002, -76.23138 41.075944, -76.231373 41.075902, -76.231386 41.075792, -76.23141 41.0757, -76.231412 41.07565, -76.231383 41.075562, -76.231301 41.075413, -76.231274 41.075327, -76.231258 41.07519, -76.231259 41.075077, -76.231253 41.074966, -76.231261 41.074946, -76.231377 41.074833, -76.231418 41.074812, -76.231479 41.074788, -76.231585 41.074731, -76.231684 41.074667, -76.23182 41.074605, -76.231921 41.074582, -76.232009 41.074582, -76.23213 41.074594, -76.232151 41.074603, -76.232176 41.074623, -76.232242 41.07465, -76.232301 41.074657, -76.232384 41.074655, -76.232564 41.074628, -76.232666 41.074628, -76.232729 41.074608, -76.232863 41.074521, -76.232995 41.074482, -76.23309 41.074439, -76.233129 41.074412, -76.233172 41.074354, -76.233236 41.074302, -76.233253 41.074282, -76.233277 41.074234, -76.233304 41.074211, -76.233366 41.074182, -76.233644 41.074035, -76.233776 41.07398, -76.233876 41.073951, -76.23394 41.073948, -76.233994 41.073958, -76.234159 41.074004, -76.234291 41.074029, -76.234364 41.074029, -76.234417 41.07402, -76.234494 41.073996, -76.234541 41.073994, -76.234603 41.074002, -76.234694 41.074034, -76.234747 41.074042, -76.235053 41.074055, -76.235186 41.074065, -76.23537 41.074066, -76.235489 41.074058, -76.235613 41.074038, -76.23574 41.074002, -76.235788 41.073998, -76.235836 41.074014, -76.235886 41.07405, -76.235953 41.074074, -76.236029 41.074088, -76.236166 41.074099, -76.23644 41.074098, -76.236673 41.073913, -76.236995 41.073659, -76.237198 41.073495, -76.237728 41.074206, -76.237827 41.074339, -76.238034 41.07437, -76.239555 41.074596, -76.240206 41.074693, -76.240545 41.074749, -76.240679 41.074764, -76.241038 41.074589, -76.242162 41.074013, -76.243642 41.07325, -76.244501 41.072807, -76.244665 41.072722, -76.244796 41.072654, -76.245322 41.072382, -76.245885 41.072093, -76.246622 41.071713, -76.246706 41.07167, -76.249204 41.070382, -76.250626 41.069647, -76.250598 41.069235, -76.250585 41.069053, -76.250521 41.069074, -76.250343 41.069137, -76.25011 41.069211, -76.250033 41.069229, -76.249899 41.069261, -76.249385 41.069362, -76.249185 41.069436, -76.24902 41.069486, -76.248936 41.069503, -76.248863 41.06948, -76.249021 41.069426, -76.249174 41.069365, -76.249344 41.069308, -76.249626 41.069252, -76.249665 41.069247, -76.249814 41.069226, -76.249896 41.069208, -76.250062 41.069161, -76.250342 41.069081, -76.250497 41.069018, -76.250572 41.068977, -76.250854 41.068877, -76.250978 41.068806, -76.251079 41.068729, -76.251099 41.068715, -76.251156 41.06868, -76.251259 41.06864, -76.251318 41.068631, -76.251356 41.068631, -76.251378 41.068638, -76.251346 41.068695, -76.251413 41.068717, -76.251509 41.068785, -76.251633 41.068805, -76.251749 41.068806, -76.251869 41.068775, -76.252089 41.068694, -76.252205 41.068646, -76.252443 41.068594, -76.252622 41.068583, -76.252762 41.068534, -76.252882 41.068455, -76.252916 41.068409, -76.252931 41.068354, -76.253023 41.068261, -76.253181 41.068165, -76.253287 41.068089, -76.253486 41.06797, -76.253561 41.067932, -76.253606 41.067907, -76.253751 41.067933, -76.25493 41.068154, -76.255496 41.068265, -76.255397 41.068502, -76.255316 41.068653, -76.255253 41.068753, -76.255186 41.068831, -76.255129 41.068885, -76.255049 41.068943, -76.254999 41.068971, -76.254701 41.069114, -76.254631 41.069141, -76.254355 41.069216, -76.254213 41.069267, -76.254139 41.069302, -76.254086 41.069335, -76.254013 41.069391, -76.253948 41.069458, -76.25389 41.069532, -76.253769 41.069685, -76.253641 41.069854, -76.253471 41.070102, -76.253105 41.070643, -76.253063 41.070683, -76.25304 41.070695, -76.252977 41.070728, -76.252821 41.07078, -76.252715 41.070806, -76.252553 41.070839, -76.252153 41.070937, -76.25131 41.071124, -76.251226 41.071155, -76.251176 41.071182, -76.251136 41.071224, -76.251119 41.071258, -76.251111 41.071319, -76.251115 41.071398, -76.251134 41.071555, -76.251151 41.071723, -76.251261 41.072563, -76.251344 41.073226, -76.251353 41.073442, -76.25134 41.073538, -76.250993 41.073473, -76.250908 41.073469, -76.25086 41.073482, -76.250823 41.073511, -76.250804 41.073546, -76.250798 41.07362, -76.250806 41.073991, -76.250856 41.074913, -76.250859 41.075112, -76.250894 41.075891, -76.25092 41.076255, -76.250957 41.076488, -76.250963 41.076526, -76.250993 41.07685, -76.250999 41.077078, -76.250998 41.077099, -76.250994 41.077152, -76.250988 41.077317, -76.250992 41.077443, -76.251007 41.077496, -76.251034 41.077544, -76.251071 41.077586, -76.251113 41.077616, -76.251159 41.077638, -76.251221 41.077653, -76.251373 41.077658, -76.251875 41.077637, -76.252477 41.07762, -76.252763 41.077601, -76.252937 41.077594, -76.252994 41.077592, -76.253098 41.07758, -76.253239 41.077564, -76.253772 41.077464, -76.253877 41.077449, -76.254288 41.077406, -76.254519 41.077396, -76.254827 41.077397, -76.255214 41.07741, -76.255881 41.077449, -76.256012 41.077453, -76.256025 41.077333, -76.25604 41.076648, -76.256366 41.076683, -76.25679 41.076738, -76.257043 41.076759, -76.257226 41.07676, -76.257397 41.076748, -76.257548 41.076724, -76.257748 41.076678, -76.258438 41.07649, -76.258745 41.076415, -76.258945 41.076372, -76.259271 41.076309, -76.25966 41.076222, -76.259714 41.07621, -76.259835 41.076163, -76.259878 41.076132, -76.25992 41.076082, -76.259946 41.076026, -76.259947 41.075886, -76.259886 41.075701, -76.259879 41.075635, -76.259881 41.075617, -76.259895 41.075585, -76.259938 41.075543, -76.259981 41.075519, -76.260081 41.075494, -76.260349 41.075436, -76.260742 41.075345, -76.261211 41.075249, -76.262795 41.074892, -76.263048 41.074828, -76.263269 41.074752, -76.263623 41.074593, -76.263715 41.074561, -76.263807 41.074538, -76.263909 41.074525, -76.264259 41.074503, -76.264851 41.074491, -76.265194 41.074471, -76.265347 41.07445, -76.265629 41.074399, -76.265712 41.074381, -76.266714 41.074163, -76.266959 41.074101, -76.266999 41.074227, -76.267208 41.074894, -76.267611 41.076101, -76.26774 41.076528, -76.267781 41.076724, -76.267787 41.076838, -76.267783 41.076898, -76.267743 41.077194, -76.267736 41.077311, -76.267743 41.077379, -76.267775 41.077481, -76.267824 41.077598, -76.267881 41.077701, -76.268798 41.07917, -76.268867 41.079303, -76.268907 41.079437, -76.268976 41.079592, -76.269048 41.0798, -76.269126 41.079768, -76.269184 41.079706, -76.269206 41.079661, -76.269202 41.079615, -76.269125 41.079561, -76.269101 41.079526, -76.269038 41.079413, -76.268997 41.079326, -76.268966 41.079215, -76.268948 41.079106, -76.268954 41.078877, -76.268989 41.078609, -76.269011 41.078533, -76.269066 41.078201, -76.269094 41.078097, -76.269152 41.077958, -76.269247 41.077808, -76.269267 41.077766, -76.269274 41.077699, -76.269237 41.077624, -76.269238 41.077528, -76.269304 41.077449, -76.269361 41.077358, -76.269428 41.077231, -76.269437 41.077173, -76.269439 41.077074, -76.269437 41.077008, -76.269402 41.076892, -76.269387 41.076817, -76.269386 41.076748, -76.269372 41.076689, -76.269345 41.076609, -76.269342 41.076567, -76.269357 41.07648, -76.269359 41.076362, -76.269335 41.076335, -76.269324 41.076298, -76.269322 41.07625, -76.269266 41.076072, -76.269113 41.075841, -76.269108 41.075789, -76.26909 41.07575, -76.268972 41.075613, -76.268898 41.075511, -76.268832 41.075439, -76.268774 41.07536, -76.268759 41.075318, -76.268747 41.075265, -76.268674 41.075172, -76.268658 41.075118, -76.268692 41.075051, -76.26876 41.074938, -76.26877 41.074871, -76.268754 41.074799, -76.268636 41.074682, -76.268552 41.074605, -76.268448 41.074523, -76.26818 41.074415, -76.268017 41.074273, -76.267991 41.074235, -76.267812 41.074038, -76.2678 41.074018, -76.26779 41.074001, -76.267795 41.073836, -76.26775 41.073784, -76.267575 41.073784, -76.267517 41.073757, -76.267514 41.073666, -76.267548 41.073607, -76.267555 41.073506, -76.267557 41.073342, -76.267545 41.073274, -76.267457 41.073034, -76.267465 41.072984, -76.267495 41.072896, -76.267537 41.072689, -76.267542 41.072668, -76.267548 41.072643, -76.267591 41.072517, -76.26761 41.072403, -76.267663 41.072319, -76.267751 41.072134, -76.267781 41.072037, -76.267785 41.07198, -76.267809 41.071891, -76.26783 41.071837, -76.267908 41.071717, -76.268039 41.071555, -76.268061 41.071503, -76.268086 41.071414, -76.268116 41.071342, -76.268156 41.071299, -76.268274 41.071233, -76.268373 41.071135, -76.268465 41.071024, -76.268516 41.070988, -76.268594 41.070968, -76.268653 41.070978, -76.268707 41.070999, -76.26877 41.070973, -76.268759 41.070919, -76.268764 41.070857, -76.268819 41.070807, -76.268902 41.070804, -76.268935 41.070762, -76.268951 41.07073, -76.269023 41.070672, -76.269163 41.070607, -76.269231 41.070557, -76.269379 41.070448, -76.269519 41.070337, -76.269614 41.070255, -76.269866 41.070085, -76.269979 41.07, -76.270047 41.069954, -76.27029 41.069892, -76.270365 41.069876, -76.270443 41.069831, -76.270463 41.069758, -76.270597 41.069672, -76.270689 41.069652, -76.270875 41.069579, -76.270988 41.069506, -76.271111 41.069452, -76.27125 41.069414, -76.271369 41.069362, -76.271477 41.069295, -76.271593 41.069263, -76.271925 41.069145, -76.272049 41.06909, -76.272145 41.069067, -76.272251 41.069094, -76.272286 41.069159, -76.272322 41.069207, -76.272498 41.06919, -76.272599 41.069152, -76.272738 41.0691, -76.272523 41.069023, -76.27208 41.068857, -76.271797 41.068756, -76.271542 41.068683, -76.271276 41.068626, -76.27098 41.068586, -76.270813 41.068573, -76.27069 41.068569, -76.269038 41.068563, -76.267469 41.068589, -76.266951 41.068593, -76.266109 41.068621, -76.263885 41.068718, -76.262944 41.068771, -76.262124 41.068817, -76.261298 41.068853, -76.260839 41.068883, -76.260503 41.068905, -76.259763 41.068944, -76.259676 41.068949, -76.259322 41.068956, -76.259018 41.06895, -76.258834 41.068937, -76.258717 41.068928, -76.258561 41.068907, -76.258225 41.068841, -76.257634 41.068714, -76.257314 41.068643, -76.257024 41.068584, -76.256907 41.068392, -76.25639 41.067684, -76.256247 41.067469, -76.255571 41.066524, -76.255496 41.066428, -76.255463 41.066385, -76.255224 41.066039, -76.255007 41.065748, -76.254934 41.065643, -76.255031 41.065618, -76.256493 41.065139, -76.256972 41.06498, -76.257344 41.064856, -76.257654 41.064675, -76.258366 41.064438, -76.269016 41.060877, -76.269008 41.060752, -76.268982 41.060617, -76.268959 41.060553, -76.268896 41.060432, -76.268756 41.060237, -76.268653 41.060074, -76.2686 41.059876, -76.268585 41.059782, -76.268578 41.059702, -76.268631 41.059209, -76.268635 41.059017, -76.268623 41.058863, -76.268571 41.058568, -76.268539 41.058418, -76.268514 41.058341, -76.268462 41.058342, -76.268248 41.058352, -76.268142 41.058374, -76.268004 41.058409, -76.267816 41.05845, -76.267733 41.058444, -76.267658 41.058452, -76.267568 41.058483, -76.267479 41.058505, -76.267395 41.058518, -76.267293 41.058526, -76.267088 41.058556, -76.266885 41.058619, -76.266762 41.058625, -76.266704 41.058611, -76.266491 41.05864, -76.26627 41.058685, -76.266189 41.058682, -76.26602 41.058683, -76.265947 41.058692, -76.265893 41.058732, -76.265801 41.058757, -76.265702 41.058738, -76.265653 41.058694, -76.265605 41.058609, -76.265508 41.058546, -76.265466 41.058491, -76.265428 41.058428, -76.265377 41.058369, -76.26535 41.058345, -76.265414 41.058329, -76.266658 41.058017, -76.266707 41.058004, -76.267351 41.057843, -76.267901 41.057704, -76.268196 41.057632, -76.26841 41.057576, -76.269285 41.057356, -76.269376 41.057337, -76.269434 41.057321, -76.269465 41.057312, -76.269907 41.0572, -76.270365 41.057084, -76.270655 41.05701, -76.271032 41.056916, -76.27103 41.056867, -76.27102 41.056545, -76.270989 41.055661, -76.271084 41.055631, -76.272101 41.055297, -76.273353 41.054876, -76.273423 41.054852, -76.273795 41.054738, -76.274719 41.054479, -76.274909 41.054433, -76.27512 41.054399, -76.275573 41.054351, -76.275742 41.05434, -76.276581 41.05431, -76.27665 41.054309, -76.276807 41.054306, -76.276885 41.054305, -76.277198 41.054293, -76.277562 41.054267, -76.277174 41.054163, -76.277042 41.05413, -76.277144 41.054084, -76.277211 41.054057, -76.277479 41.053903, -76.277518 41.053882, -76.277569 41.053843, -76.277697 41.053767, -76.277776 41.053727, -76.277947 41.053651, -76.278175 41.053517, -76.2783 41.053452, -76.278431 41.053419, -76.27853 41.053399, -76.27862 41.053388, -76.278701 41.053383, -76.278797 41.053372, -76.27896 41.053287, -76.279041 41.053266, -76.279105 41.05323, -76.279164 41.053182, -76.279215 41.053049, -76.279444 41.052981, -76.27949 41.052937, -76.279545 41.052895, -76.279545 41.052839, -76.27951 41.05275, -76.279539 41.052689, -76.279572 41.052633, -76.279641 41.052595, -76.279648 41.052532, -76.279694 41.052472, -76.279749 41.052376, -76.279773 41.052289, -76.279785 41.052184, -76.279779 41.051995, -76.279782 41.051836, -76.279773 41.05163, -76.279748 41.051447, -76.279754 41.051162, -76.279752 41.050924, -76.27977 41.050705, -76.279807 41.050629, -76.279868 41.050547, -76.279897 41.05049, -76.279948 41.050438, -76.280028 41.050377, -76.280332 41.050204, -76.280517 41.050115, -76.280595 41.050066, -76.280651 41.05, -76.280677 41.049695, -76.28074 41.049621, -76.280884 41.049547, -76.280952 41.049508, -76.281042 41.049445, -76.281135 41.049417, -76.281221 41.049406, -76.28133 41.049413, -76.281471 41.049474, -76.281542 41.049499, -76.281675 41.049534, -76.281768 41.049526, -76.281861 41.049482, -76.28192 41.049448, -76.28203 41.049363, -76.282045 41.049307, -76.282091 41.04926, -76.282246 41.049174, -76.282296 41.049129, -76.282438 41.049017, -76.282485 41.048933, -76.282606 41.048787, -76.282661 41.048685, -76.282673 41.048624, -76.282675 41.048561, -76.282671 41.048496, -76.282621 41.048384, -76.282603 41.04833, -76.282552 41.048214, -76.282542 41.048152, -76.282544 41.048059, -76.282557 41.047971, -76.282586 41.047881, -76.282724 41.047681, -76.282749 41.04763, -76.2828 41.047583, -76.282872 41.047492, -76.282916 41.047445, -76.282998 41.047437, -76.283099 41.047437, -76.283189 41.04745, -76.283269 41.047443, -76.283355 41.047473, -76.283462 41.047504, -76.283537 41.047499, -76.283625 41.047453, -76.283716 41.04737, -76.28387 41.047206, -76.283947 41.047057, -76.283969 41.046992, -76.283938 41.046722, -76.28395 41.046241, -76.28398 41.046151, -76.284043 41.046102, -76.284204 41.046051, -76.284282 41.046019, -76.284567 41.045879, -76.284606 41.045859, -76.284655 41.045821, -76.284752 41.045746, -76.284805 41.04569, -76.284851 41.045623, -76.284972 41.045483, -76.285031 41.045407, -76.285069 41.045255, -76.28507 41.045235, -76.2837 41.045067, -76.282656 41.044935, -76.280395 41.044649, -76.277039 41.044231, -76.273595 41.043781, -76.272915 41.043692, -76.270748 41.043422, -76.270642 41.043408, -76.270616 41.042604, -76.270613 41.042099, -76.270549 41.039858, -76.270537 41.039486, -76.270104 41.039472, -76.269866 41.039458, -76.269657 41.039421, -76.269487 41.03938, -76.269328 41.039349, -76.269135 41.039347, -76.268947 41.03936, -76.268674 41.039357, -76.268483 41.039359, -76.267959 41.039342, -76.267789 41.039323, -76.267613 41.03932, -76.267468 41.039332, -76.267345 41.039329, -76.267217 41.039315, -76.267092 41.039317, -76.266849 41.039342, -76.266488 41.039365, -76.266394 41.039375, -76.266181 41.039387, -76.265958 41.039411, -76.265734 41.039453, -76.265626 41.039462, -76.265551 41.039473, -76.265389 41.039508, -76.265144 41.039549, -76.26501 41.039565, -76.264759 41.039582, -76.264231 41.039643, -76.263919 41.039664, -76.263572 41.039701, -76.263463 41.039705, -76.263181 41.039736, -76.263098 41.039749, -76.26299 41.039772, -76.262917 41.039783, -76.262753 41.039799, -76.262661 41.039813, -76.262532 41.039827, -76.262392 41.039847, -76.262238 41.039861, -76.26193 41.039867, -76.26179 41.039873, -76.261553 41.039873, -76.261329 41.039927, -76.261213 41.039947, -76.260922 41.03997, -76.260827 41.039984, -76.260726 41.040015, -76.260603 41.040018, -76.260441 41.040064, -76.260327 41.040092, -76.260249 41.040103, -76.260166 41.04011, -76.259858 41.040195, -76.259645 41.040246, -76.259489 41.040275, -76.25908 41.040353, -76.258949 41.040372, -76.258754 41.040394, -76.258625 41.040417, -76.258077 41.040544, -76.257825 41.040622, -76.257746 41.040642, -76.257518 41.040681, -76.257417 41.040703, -76.257192 41.040769, -76.256781 41.040869, -76.256394 41.040977, -76.256319 41.041002, -76.256163 41.041065, -76.256056 41.04108, -76.255863 41.041093, -76.255792 41.041105, -76.25573 41.041147, -76.255676 41.041206, -76.255592 41.04128, -76.25552 41.041334, -76.255385 41.041404, -76.255019 41.041548, -76.254873 41.04161, -76.254775 41.041667, -76.254471 41.041901, -76.254257 41.042043, -76.25406 41.042158, -76.253813 41.042286, -76.253465 41.042452, -76.253238 41.042566, -76.253135 41.042625, -76.253035 41.042672, -76.252899 41.042721, -76.252754 41.042786, -76.252596 41.042873, -76.252453 41.043007, -76.252236 41.043152, -76.252057 41.043285, -76.251887 41.043379, -76.251677 41.043508, -76.251623 41.043547, -76.251218 41.043774, -76.251117 41.043826, -76.251025 41.043851, -76.250906 41.043889, -76.25078 41.043966, -76.25073 41.044005, -76.25066 41.044043, -76.250564 41.044102, -76.250505 41.044162, -76.250376 41.044264, -76.250197 41.044446, -76.250136 41.044502, -76.250062 41.044554, -76.249928 41.04464, -76.249794 41.044726, -76.249447 41.044912, -76.249091 41.045145, -76.249004 41.045196, -76.248805 41.045302, -76.248605 41.045422, -76.248471 41.045513, -76.2484 41.045554, -76.248325 41.045604, -76.248236 41.045653, -76.24811 41.045702, -76.247979 41.045759, -76.2478 41.045854, -76.247559 41.04599, -76.247493 41.046032, -76.247205 41.046188, -76.247132 41.046217, -76.246944 41.046306, -76.246871 41.046347, -76.246813 41.046386, -76.246742 41.046421, -76.246557 41.046495, -76.246331 41.046621, -76.246151 41.046748, -76.245895 41.046895, -76.245829 41.046927, -76.245539 41.047084, -76.245468 41.047126, -76.24541 41.047168, -76.245298 41.047239, -76.245134 41.047333, -76.244628 41.047587, -76.244407 41.047671, -76.244248 41.047742, -76.244075 41.047837, -76.243915 41.047918, -76.243812 41.047957, -76.243683 41.048015, -76.243603 41.048066, -76.24352 41.048128, -76.243292 41.048257, -76.243124 41.048366, -76.242934 41.048465, -76.242796 41.048545, -76.242395 41.048747, -76.242266 41.048831, -76.242032 41.04895, -76.241902 41.049022, -76.241688 41.049167, -76.241486 41.049277, -76.241076 41.049476, -76.240964 41.049536, -76.240884 41.049571, -76.240548 41.049749, -76.240362 41.049858, -76.240195 41.049945, -76.240105 41.049985, -76.239819 41.050116, -76.239309 41.050375, -76.239163 41.050454, -76.238823 41.0506, -76.238743 41.050638, -76.238675 41.050682, -76.238608 41.050718, -76.238524 41.050754, -76.238431 41.050804, -76.238335 41.050864, -76.238252 41.050909, -76.238058 41.051001, -76.237877 41.051095, -76.237711 41.051154, -76.237658 41.051195, -76.237544 41.051256, -76.237401 41.051324, -76.237163 41.051453, -76.237103 41.051504, -76.236978 41.051583, -76.236876 41.05162, -76.236805 41.051637, -76.236498 41.051776, -76.236418 41.051802, -76.23625 41.051897, -76.236168 41.051937, -76.236109 41.051972, -76.235963 41.052027, -76.235871 41.052069, -76.235599 41.052206, -76.235412 41.052305, -76.235355 41.05234, -76.235243 41.052384, -76.235169 41.052393, -76.235041 41.052484, -76.234914 41.052539, -76.234821 41.052556, -76.234677 41.052644, -76.2346 41.052683, -76.234575 41.052696, -76.234473 41.052734, -76.234382 41.052778, -76.234173 41.052903, -76.233993 41.05302, -76.233748 41.053142, -76.233663 41.053173, -76.233538 41.053202, -76.233251 41.053317, -76.233105 41.053351, -76.232697 41.053529, -76.232607 41.053562, -76.231913 41.052757, -76.231534 41.052315, -76.231301 41.052004, -76.230977 41.051705, -76.23061 41.051425, -76.230177 41.051228, -76.231337 41.050735, -76.231438 41.050693, -76.231438 41.050672, -76.231627 41.050291, -76.231616 41.050259, -76.231519 41.050234, -76.230796 41.050087, -76.230509 41.05008, -76.23035 41.050182, -76.229805 41.050315, -76.229568 41.050319, -76.22944 41.050289, -76.229159 41.050226, -76.228227 41.05019, -76.227435 41.050058, -76.226575 41.049756, -76.226165 41.049574, -76.2252 41.048991, -76.224558 41.048518, -76.224038 41.048208, -76.223143 41.047995, -76.222727 41.047897, -76.222166 41.047812, -76.221733 41.047666, -76.221332 41.047313, -76.221147 41.047089, -76.221036 41.047145, -76.220951 41.047187, -76.22044 41.047443, -76.220361 41.04748, -76.220163 41.047574, -76.220155 41.047551, -76.220133 41.047485, -76.220126 41.047463, -76.220051 41.047279, -76.219934 41.046967, -76.21986 41.046769, -76.219794 41.046654, -76.219752 41.046592, -76.2197 41.046533, -76.219599 41.046443, -76.219483 41.04635, -76.219402 41.046276, -76.219336 41.046209, -76.21927 41.046079, -76.219244 41.045978, -76.219237 41.045906, -76.219245 41.045825, -76.21926 41.04575, -76.219309 41.045631, -76.219314 41.04562, -76.219467 41.045342, -76.219541 41.04524, -76.21959 41.045183, -76.216227 41.045729, -76.215862 41.045788, -76.215015 41.046098, -76.214399 41.046327, -76.214137 41.046426, -76.213345 41.046683, -76.212911 41.046833, -76.212768 41.046877, -76.212625 41.046912, -76.212528 41.046943, -76.212492 41.046956, -76.212344 41.04702, -76.212233 41.047063, -76.212116 41.047103, -76.211909 41.04716, -76.211692 41.047215, -76.21116 41.047335, -76.210875 41.047388, -76.210564 41.04748, -76.210447 41.047522, -76.210346 41.047569, -76.210221 41.047629, -76.209651 41.048012, -76.209212 41.048323, -76.209078 41.048447, -76.208873 41.048627, -76.208538 41.048897, -76.208448 41.048976, -76.208258 41.049127, -76.208067 41.049253, -76.207865 41.049374, -76.207401 41.049597, -76.206815 41.049864, -76.20651 41.049995, -76.206247 41.05012, -76.205654 41.050404, -76.205016 41.050689, -76.204891 41.050741, -76.20483 41.050782, -76.204801 41.050827, -76.204791 41.050849, -76.204785 41.050885, -76.20479 41.050937, -76.205013 41.051313, -76.205295 41.051787, -76.205686 41.052462, -76.205809 41.052662, -76.20585 41.052736, -76.206118 41.053217, -76.205883 41.053279, -76.205781 41.053307, -76.205567 41.053373, -76.205328 41.05344, -76.205181 41.053474, -76.205075 41.053499, -76.204948 41.05354, -76.204937 41.053561, -76.204937 41.053606, -76.204956 41.053638, -76.205117 41.053914, -76.205171 41.054006, -76.205413 41.053942, -76.206142 41.053751, -76.206385 41.053688, -76.206633 41.053624, -76.20692 41.053552, -76.207376 41.053429, -76.207624 41.053363, -76.207883 41.053291, -76.208422 41.053144, -76.208664 41.053084, -76.208722 41.053071, -76.208925 41.053017, -76.209082 41.052975, -76.209556 41.05285, -76.209714 41.052809, -76.20983 41.052777, -76.21018 41.052685, -76.210297 41.052655, -76.210331 41.052646, -76.210435 41.05262, -76.21047 41.052612, -76.210253 41.052813, -76.210167 41.052895, -76.209824 41.053292, -76.209675 41.053485, -76.209496 41.05372, -76.209469 41.05372, -76.209294 41.053945, -76.209244 41.054008, -76.209126 41.054161, -76.209101 41.054202, -76.20906 41.054272, -76.208951 41.054419, -76.208883 41.054512, -76.208681 41.054795, -76.208633 41.054865, -76.208531 41.055017, -76.208204 41.055525, -76.207868 41.055981, -76.207653 41.056275, -76.207465 41.056529, -76.207385 41.05662, -76.20732 41.056696, -76.207178 41.056847, -76.207056 41.05697, -76.206888 41.057123, -76.206751 41.057248, -76.206554 41.057402, -76.206354 41.057546, -76.206105 41.057706, -76.205948 41.057805, -76.205699 41.057941, -76.205423 41.05809, -76.20522 41.058182, -76.204968 41.0583, -76.204695 41.058419, -76.2044 41.058533, -76.204089 41.058631, -76.203715 41.058732, -76.203272 41.058835, -76.202819 41.058907, -76.202291 41.05898, -76.202123 41.059004, -76.201622 41.059078, -76.201289 41.059117, -76.201448 41.059352, -76.201598 41.05961, -76.201696 41.059777, -76.201925 41.060169, -76.202647 41.061395, -76.20112 41.06168, -76.200572 41.061756, -76.198927 41.061987, -76.19838 41.062064, -76.198523 41.062315, -76.198954 41.063071, -76.199098 41.063323, -76.19878 41.063313, -76.197953 41.063351, -76.197823 41.063357, -76.197756 41.063357, -76.197039 41.063357, -76.195407 41.063346, -76.194863 41.063373, -76.194711 41.063401, -76.194595 41.063428, -76.194508 41.063461, -76.194479 41.063439, -76.194537 41.063373, -76.194566 41.063313, -76.194573 41.063258, -76.194478 41.063225, -76.194174 41.063242, -76.194064 41.063273, -76.193985 41.063297, -76.193898 41.063346, -76.193775 41.063368, -76.19339 41.063533, -76.192817 41.063758, -76.192164 41.064065, -76.191889 41.064164, -76.191744 41.064203, -76.19171 41.064209, -76.191424 41.064269, -76.191374 41.064274, -76.190895 41.064389, -76.190851 41.06445, -76.191113 41.064422, -76.191693 41.064329, -76.191816 41.064313, -76.191906 41.064334, -76.191925 41.064345, -76.191896 41.064395, -76.191033 41.064576, -76.190416 41.064686, -76.190389 41.064692, -76.189937 41.064796, -76.189734 41.064873, -76.189546 41.064971, -76.189488 41.065021, -76.189444 41.065076, -76.189408 41.065142, -76.189379 41.065219, -76.189378 41.065109, -76.189377 41.064999, -76.189373 41.064632, -76.188834 41.064779, -76.186259 41.065485, -76.185598 41.065755, -76.185551 41.065773, -76.18521 41.065914, -76.184728 41.066262, -76.184701 41.066441, -76.184879 41.066649, -76.184525 41.066762, -76.184221 41.066816, -76.183909 41.066899, -76.183855 41.066909, -76.183604 41.066959, -76.183169 41.06697, -76.18277 41.066948, -76.182458 41.066915, -76.182218 41.066822, -76.182146 41.066772, -76.181885 41.066739, -76.181282 41.066646, -76.180941 41.06663, -76.18076 41.066586, -76.180738 41.066578, -76.180608 41.066531, -76.180528 41.066608, -76.18047 41.066597, -76.18047 41.066531, -76.18039 41.066503, -76.180049 41.06641, -76.180013 41.066404, -76.179977 41.06641, -76.179911 41.066437, -76.179831 41.066498, -76.179832 41.066443, -76.179817 41.066399, -76.179788 41.066339, -76.179744 41.066306, -76.179628 41.06624, -76.179519 41.066185, -76.179345 41.066113, -76.179186 41.066064, -76.17883 41.066053, -76.178605 41.066053, -76.178185 41.066152, -76.177713 41.066273, -76.177249 41.066322, -76.176958 41.066328, -76.176755 41.066333, -76.175979 41.066311, -76.174956 41.066245, -76.174659 41.066206, -76.173716 41.066146, -76.171945 41.066135, -76.171242 41.066069, -76.170335 41.066063, -76.168877 41.066167, -76.168238 41.066261, -76.167711 41.066313, -76.167585 41.066326, -76.167194 41.066348, -76.166381 41.066293, -76.1653 41.066183, -76.164632 41.066127, -76.164248 41.066095, -76.163777 41.066013, -76.1634 41.065925, -76.162703 41.065738, -76.162217 41.065661, -76.161419 41.065606, -76.161035 41.065584, -76.160913 41.065565, -76.160396 41.065485, -76.159961 41.065386, -76.159293 41.065342, -76.15843 41.06538, -76.157857 41.065374, -76.157364 41.065402, -76.155862 41.065544, -76.153903 41.065873, -76.152336 41.066246, -76.15169 41.066378, -76.150464 41.066685, -76.149774 41.06687, -76.14955 41.066931, -76.148381 41.067354, -76.147881 41.067529, -76.147409 41.067809, -76.146567 41.068347, -76.146386 41.068469, -76.146444 41.068551, -76.14662 41.068797, -76.146679 41.068879, -76.147336 41.068607, -76.14759 41.06849, -76.147674 41.068468, -76.147773 41.068463, -76.147911 41.068476, -76.147942 41.068476, -76.148054 41.068448, -76.148142 41.068407, -76.148195 41.068368, -76.148407 41.068168, -76.148442 41.068136, -76.148484 41.068113, -76.148636 41.068051, -76.149097 41.067876, -76.149195 41.067845, -76.14971 41.0677, -76.150753 41.067436, -76.150952 41.06738, -76.151556 41.067229, -76.152012 41.067153, -76.15271 41.067005, -76.153228 41.06689, -76.153666 41.0668, -76.153929 41.066735, -76.153955 41.066728, -76.154589 41.066583, -76.155051 41.066483, -76.155403 41.066387, -76.15582 41.066289, -76.155905 41.066268, -76.155971 41.066253, -76.156086 41.066238, -76.156164 41.06623, -76.156252 41.066223, -76.156246 41.066234, -76.156247 41.066272, -76.156248 41.066288, -76.156277 41.066373, -76.156305 41.066478, -76.156309 41.066491, -76.156328 41.066541, -76.156787 41.066462, -76.157345 41.066383, -76.157433 41.066375, -76.157708 41.066352, -76.158335 41.066347, -76.159249 41.066352, -76.159727 41.066373, -76.160326 41.066446, -76.160775 41.066515, -76.161113 41.066567, -76.161703 41.066708, -76.161868 41.066747, -76.161889 41.06678, -76.161952 41.066882, -76.161973 41.066916, -76.162242 41.066975, -76.163049 41.067153, -76.163199 41.067187, -76.163319 41.067209, -76.163316 41.067228, -76.163307 41.067285, -76.163304 41.067304, -76.163297 41.067353, -76.163297 41.067409, -76.163309 41.067608, -76.163327 41.06788, -76.163335 41.068163, -76.163351 41.068464, -76.163355 41.068523, -76.163379 41.068828, -76.16299 41.068913, -76.162666 41.068999, -76.162455 41.069065, -76.162212 41.06915, -76.161943 41.069252, -76.161877 41.069278, -76.16073 41.069744, -76.160305 41.069907, -76.159742 41.070093, -76.158874 41.070371, -76.158635 41.07047, -76.158437 41.070564, -76.158193 41.070714, -76.157814 41.070981, -76.157724 41.071046, -76.157258 41.071389, -76.157158 41.071459, -76.15698 41.071583, -76.156599 41.071859, -76.156539 41.071902, -76.156546 41.072336, -76.156543 41.072419, -76.157053 41.072243, -76.157078 41.072234, -76.158639 41.071707, -76.159714 41.071335, -76.160831 41.070957, -76.1615 41.070753, -76.161539 41.070741, -76.161694 41.070694, -76.162277 41.07052, -76.162472 41.070462, -76.162792 41.070371, -76.162992 41.070317, -76.163381 41.070212, -76.163937 41.070069, -76.164561 41.069919, -76.165087 41.069794, -76.165127 41.069784, -76.16541 41.06972, -76.165702 41.069655, -76.166385 41.069516, -76.166711 41.069451, -76.16696 41.069401, -76.167417 41.069316, -76.167655 41.069272, -76.168957 41.069062, -76.169549 41.068975, -76.169603 41.068968, -76.170263 41.068881, -76.170936 41.068795, -76.171704 41.068698, -76.172199 41.068641, -76.172742 41.068586, -76.17296 41.068566, -76.173637 41.068507, -76.174093 41.068465, -76.175462 41.068343, -76.175919 41.068303, -76.176102 41.068287, -76.176655 41.06824, -76.176839 41.068225, -76.176881 41.068221, -76.177007 41.06821, -76.17705 41.068207, -76.178056 41.068124, -76.178633 41.068077, -76.180365 41.06793, -76.181073 41.067858, -76.181375 41.067828, -76.182075 41.067734, -76.182215 41.067714, -76.182883 41.067611, -76.183704 41.067476, -76.186063 41.067089, -76.187805 41.06679, -76.188166 41.066726, -76.189622 41.06647, -76.189652 41.066465, -76.190089 41.066393, -76.190168 41.06638, -76.190363 41.066344, -76.190888 41.06625, -76.191497 41.066148, -76.192245 41.066016, -76.192496 41.065983, -76.192518 41.065981, -76.192851 41.065949, -76.193215 41.065921, -76.193202 41.066009, -76.193173 41.066124, -76.193122 41.06641, -76.193122 41.066498, -76.193137 41.06658, -76.193159 41.06664, -76.193195 41.066706, -76.19326 41.066778, -76.193369 41.066871, -76.193616 41.067041, -76.193674 41.067091, -76.193848 41.067222, -76.193913 41.067261, -76.194007 41.067338, -76.194116 41.067415, -76.194283 41.067519, -76.194317 41.067543, -76.194392 41.067596, -76.194421 41.067634, -76.194443 41.067777, -76.194414 41.067914, -76.194385 41.068019, -76.19437 41.068052, -76.194291 41.068112, -76.194283 41.068238, -76.194312 41.068354, -76.194428 41.068436, -76.194465 41.068474, -76.194479 41.068573, -76.194494 41.068612, -76.194515 41.06871, -76.194523 41.068798, -76.194552 41.068881, -76.194675 41.069051, -76.194733 41.069166, -76.19474 41.069199, -76.19474 41.069237, -76.194682 41.069358, -76.194624 41.06943, -76.194581 41.069468, -76.194487 41.069523, -76.194349 41.069578, -76.194283 41.069622, -76.194269 41.069677, -76.194262 41.069759, -76.194262 41.069847, -76.194254 41.069962, -76.194218 41.070259, -76.194196 41.070336, -76.194167 41.070506, -76.194175 41.070544, -76.194211 41.070583, -76.194385 41.070714, -76.194421 41.070764, -76.194443 41.070857, -76.194429 41.071044, -76.194414 41.071115, -76.194385 41.071176, -76.194356 41.071214, -76.194327 41.071242, -76.194298 41.07128, -76.194262 41.071351, -76.194233 41.071456, -76.194218 41.071494, -76.194226 41.071538, -76.194255 41.071593, -76.194422 41.071813, -76.194472 41.071868, -76.194588 41.071928, -76.194719 41.071983, -76.194763 41.07201, -76.194777 41.072038, -76.194835 41.072202, -76.194857 41.072224, -76.194886 41.072263, -76.194922 41.072323, -76.194958 41.072406, -76.194973 41.072471, -76.194973 41.072576, -76.194988 41.072603, -76.195031 41.072642, -76.195278 41.072828, -76.195307 41.072856, -76.195343 41.072883, -76.195372 41.072911, -76.195455 41.072974, -76.195503 41.07301, -76.195546 41.073037, -76.195641 41.073042, -76.195749 41.073042, -76.195916 41.073059, -76.19596 41.073081, -76.196032 41.073169, -76.196062 41.073218, -76.196091 41.073251, -76.196127 41.073278, -76.196221 41.073322, -76.196359 41.073438, -76.196417 41.07352, -76.196439 41.073564, -76.196584 41.073767, -76.196678 41.07391, -76.196758 41.073992, -76.19678 41.074146, -76.19678 41.074267, -76.196787 41.074305, -76.196816 41.074333, -76.196853 41.074349, -76.196901 41.074381, -76.19692 41.074341, -76.196946 41.07426, -76.196948 41.074211, -76.196941 41.074135, -76.196922 41.074055, -76.196893 41.073965, -76.196839 41.073856, -76.196786 41.073748, -76.196613 41.073458, -76.19652 41.073314, -76.19645 41.073228, -76.196349 41.073127, -76.196239 41.073026, -76.196002 41.072822, -76.195865 41.072693, -76.195785 41.072595, -76.195759 41.072563, -76.195712 41.07248, -76.195668 41.072373, -76.195644 41.07223, -76.195629 41.072076, -76.195624 41.07193, -76.195629 41.071693, -76.195633 41.071544, -76.195656 41.071073, -76.195683 41.07094, -76.195715 41.070815, -76.195779 41.070643, -76.195814 41.070573, -76.19593 41.070349, -76.195999 41.070238, -76.19603 41.070189, -76.196122 41.070073, -76.196154 41.070038, -76.196259 41.069927, -76.196401 41.069765, -76.196447 41.069696, -76.196502 41.069549, -76.196505 41.069396, -76.196509 41.069242, -76.196505 41.069143, -76.196485 41.06864, -76.196484 41.068616, -76.196457 41.067361, -76.196444 41.067137, -76.196446 41.067037, -76.19645 41.066887, -76.196465 41.066819, -76.196526 41.066681, -76.19659 41.066599, -76.196629 41.066566, -76.196767 41.06646, -76.196809 41.066419, -76.196833 41.066396, -76.196907 41.066314, -76.197141 41.065928, -76.197187 41.065835, -76.197201 41.065809, -76.197251 41.065612, -76.197818 41.065526, -76.198716 41.065392, -76.199385 41.0653, -76.199521 41.065277, -76.199577 41.065269, -76.200092 41.065212, -76.200682 41.065146, -76.202342 41.064974, -76.202636 41.064934, -76.202748 41.06492, -76.202982 41.064877, -76.203238 41.064823, -76.203466 41.064767, -76.203906 41.064643, -76.204296 41.064517, -76.204818 41.064337, -76.205421 41.064143, -76.205841 41.064022, -76.206227 41.063929, -76.206608 41.063849, -76.207278 41.06373, -76.208182 41.063599, -76.210122 41.063309, -76.211397 41.063119, -76.211792 41.063053, -76.212222 41.062974, -76.212646 41.062887, -76.212657 41.06304, -76.21267 41.063195, -76.212687 41.063499, -76.212697 41.063653, -76.212725 41.064078, -76.212728 41.064183, -76.212765 41.065314, -76.212779 41.065522, -76.212789 41.065775, -76.212811 41.066306, -76.212817 41.066475, -76.212835 41.066985, -76.212841 41.067155, -76.212848 41.067366, -76.212872 41.068, -76.212881 41.068212, -76.212882 41.068252, -76.212886 41.068372, -76.212888 41.068413, -76.212901 41.068762, -76.212934 41.069474, -76.213018 41.071286, -76.213052 41.072185, -76.213074 41.072657, -76.213099 41.073151, -76.213116 41.073719, -76.213129 41.073755, -76.213156 41.073796, -76.213197 41.073824, -76.213259 41.073849, -76.213336 41.073858, -76.213403 41.07386, -76.213474 41.073855, -76.214021 41.07374, -76.215004 41.073535, -76.215131 41.073516, -76.215287 41.073518, -76.215364 41.07353, -76.215387 41.073536, -76.215411 41.073543, -76.215444 41.073562, -76.215487 41.073608, -76.215514 41.073647, -76.21554 41.073709, -76.215556 41.073819, -76.215585 41.074392, -76.215633 41.07501, -76.215681 41.075608, -76.215707 41.075987, -76.215565 41.076017, -76.21532 41.076051, -76.214774 41.076129, -76.214304 41.076189, -76.214155 41.076212, -76.214021 41.076233, -76.213767 41.076265, -76.21377 41.076454, -76.213771 41.076502, -76.213798 41.076631, -76.213879 41.076889, -76.213921 41.076999, -76.213989 41.077176, -76.21434 41.077126, -76.214468 41.077108, -76.214705 41.07708, -76.215393 41.076981, -76.215745 41.076932, -76.215921 41.076906, -76.216057 41.076887, -76.216449 41.076817, -76.216625 41.076786, -76.216748 41.076563, -76.21708 41.075965, -76.217152 41.075929, -76.217381 41.075817, -76.217403 41.075821, -76.217438 41.075839, -76.217459 41.075866, -76.217532 41.075938, -76.217533 41.075949, -76.217539 41.075998, -76.217561 41.076031, -76.217604 41.076053, -76.217648 41.076064, -76.217692 41.076086, -76.217713 41.076124, -76.217713 41.076229, -76.217742 41.076239, -76.217859 41.076256, -76.217924 41.076289, -76.217935 41.07632, -76.217953 41.076371, -76.217953 41.076529, -76.218006 41.076519, -76.218378 41.076455, -76.218717 41.076411, -76.218789 41.076403, -76.219074 41.076373, -76.219309 41.076358, -76.219899 41.076362, -76.220537 41.076392, -76.220866 41.076411, -76.221068 41.076415, -76.221326 41.076411, -76.221622 41.076407, -76.221957 41.076394, -76.222059 41.076393, -76.222149 41.0764, -76.222172 41.076405, -76.222321 41.076435, -76.222399 41.076444, -76.222807 41.076455, -76.22293 41.076454, -76.223541 41.076452, -76.223625 41.076459, -76.223703 41.07648, -76.223752 41.076508, -76.223791 41.076546, -76.223816 41.076592, -76.223837 41.076654, -76.223865 41.077093, -76.22391 41.077934, -76.223923 41.078155, -76.223942 41.078335, -76.223977 41.078553, -76.22401 41.078625, -76.224044 41.078675, -76.224081 41.078729, -76.224145 41.078784, -76.224207 41.07883, -76.224313 41.078894, -76.224808 41.079117, -76.22487 41.079146, -76.22534 41.07937, -76.225998 41.07969, -76.226151 41.079747, -76.226354 41.079804, -76.226522 41.079831, -76.226718 41.079839, -76.227139 41.079849, -76.227615 41.079843, -76.228016 41.079838, -76.228322 41.079839, -76.228578 41.079845)), ((-76.462295 40.957592, -76.462427 40.957634, -76.462494 40.957666, -76.46256 40.957689, -76.462623 40.957717, -76.462687 40.957757, -76.46281 40.957825, -76.462884 40.95785, -76.463041 40.957886, -76.463124 40.957906, -76.463157 40.957903, -76.463281 40.957893, -76.46345 40.957911, -76.46354 40.957912, -76.463613 40.957923, -76.463682 40.957944, -76.463815 40.958012, -76.463935 40.958015, -76.463956 40.957892, -76.464025 40.957622, -76.464099 40.957402, -76.464137 40.95729, -76.464377 40.956629, -76.46456 40.955956, -76.46474 40.955248, -76.464824 40.954932, -76.464966 40.95447, -76.465089 40.95413, -76.465277 40.953672, -76.465332 40.953555, -76.466112 40.953685, -76.466071 40.953739, -76.465961 40.953893, -76.465932 40.953963, -76.465851 40.954116, -76.465761 40.95434, -76.465722 40.954425, -76.465643 40.954641, -76.465595 40.954748, -76.465549 40.954833, -76.465445 40.955028, -76.465371 40.955279, -76.465347 40.955442, -76.465333 40.955576, -76.4652 40.955871, -76.465193 40.956004, -76.465168 40.956137, -76.465139 40.956226, -76.465118 40.956367, -76.465115 40.956423, -76.465123 40.956521, -76.465108 40.956648, -76.465071 40.956733, -76.465031 40.956847, -76.464998 40.956984, -76.464975 40.957199, -76.464894 40.957339, -76.464874 40.957429, -76.464856 40.957578, -76.464853 40.95766, -76.46486 40.957701, -76.464916 40.957725, -76.464979 40.95772, -76.465069 40.95768, -76.465166 40.957628, -76.465205 40.957616, -76.465211 40.957661, -76.465195 40.957695, -76.465086 40.957837, -76.464948 40.957988, -76.464873 40.958084, -76.464793 40.958156, -76.464712 40.958222, -76.464598 40.958399, -76.464586 40.958425, -76.464547 40.958549, -76.464522 40.958666, -76.464476 40.958973, -76.464449 40.959208, -76.464414 40.959425, -76.464397 40.959628, -76.464375 40.96023, -76.464343 40.960504, -76.464524 40.961132, -76.464598 40.961526, -76.464643 40.96168, -76.464677 40.961774, -76.46476 40.961949, -76.467373 40.961951, -76.467028 40.960612, -76.466991 40.958556, -76.467526 40.956148, -76.468175 40.954336, -76.468405 40.954077, -76.469789 40.954309, -76.470005 40.954364, -76.470133 40.954407, -76.470176 40.954424, -76.470088 40.954522, -76.47003 40.954602, -76.469994 40.95463, -76.469895 40.954744, -76.46985 40.954823, -76.469789 40.954904, -76.469756 40.95499, -76.469759 40.955072, -76.469716 40.955239, -76.469658 40.955414, -76.469646 40.95549, -76.4696 40.955577, -76.469527 40.955871, -76.469406 40.956107, -76.469358 40.956336, -76.469334 40.956405, -76.46933 40.956439, -76.469304 40.956499, -76.469294 40.956542, -76.469268 40.956605, -76.469256 40.956695, -76.469252 40.956798, -76.469231 40.956857, -76.469216 40.95693, -76.469187 40.957033, -76.469168 40.957145, -76.469191 40.957199, -76.469202 40.957265, -76.469148 40.957355, -76.469071 40.957367, -76.46902 40.957355, -76.469003 40.957326, -76.469006 40.957237, -76.469016 40.957147, -76.469039 40.957062, -76.469074 40.956966, -76.469104 40.956899, -76.469128 40.95681, -76.469142 40.956734, -76.469102 40.956702, -76.469084 40.956727, -76.468993 40.956925, -76.468917 40.957076, -76.468858 40.957255, -76.468815 40.957426, -76.468785 40.9576, -76.468781 40.957684, -76.468805 40.957906, -76.468843 40.958082, -76.468867 40.958222, -76.46885 40.958495, -76.468838 40.958538, -76.468824 40.958631, -76.468819 40.958884, -76.468794 40.959174, -76.46877 40.959287, -76.468768 40.959408, -76.468774 40.959494, -76.468788 40.959561, -76.468836 40.959692, -76.468861 40.95973, -76.468955 40.959829, -76.468995 40.959862, -76.469031 40.959904, -76.469055 40.95995, -76.469075 40.959998, -76.469096 40.960091, -76.469125 40.960141, -76.469124 40.960167, -76.469175 40.960272, -76.469215 40.960333, -76.469216 40.960366, -76.469237 40.960435, -76.469264 40.960495, -76.469329 40.960668, -76.469345 40.960852, -76.469365 40.960925, -76.469417 40.961012, -76.46943 40.961045, -76.469439 40.961096, -76.469471 40.961212, -76.469491 40.961316, -76.469534 40.961426, -76.469527 40.961468, -76.469516 40.961484, -76.469497 40.961495, -76.469487 40.961531, -76.46952 40.961581, -76.469574 40.961637, -76.469614 40.961671, -76.469663 40.961705, -76.469698 40.961737, -76.469808 40.961889, -76.469843 40.961993, -76.469863 40.962081, -76.469911 40.962206, -76.469947 40.962334, -76.469969 40.962377, -76.47 40.962413, -76.470033 40.962516, -76.470038 40.962596, -76.470063 40.962676, -76.470074 40.962747, -76.470093 40.962811, -76.470152 40.962956, -76.470212 40.963046, -76.47025 40.963127, -76.470271 40.963192, -76.470268 40.963226, -76.470242 40.963246, -76.470204 40.963256, -76.470185 40.963279, -76.470188 40.963302, -76.470317 40.963477, -76.47035 40.963514, -76.470403 40.96356, -76.470447 40.963605, -76.470476 40.963644, -76.470518 40.963716, -76.470538 40.963827, -76.470559 40.963895, -76.470601 40.96397, -76.470597 40.964011, -76.470566 40.964019, -76.470553 40.964061, -76.470552 40.964085, -76.470572 40.964125, -76.470632 40.96418, -76.470687 40.96421, -76.470773 40.964376, -76.470844 40.964484, -76.470881 40.964555, -76.470926 40.964652, -76.470949 40.964733, -76.471016 40.964907, -76.471075 40.965171, -76.471093 40.965277, -76.471125 40.965388, -76.47115 40.965457, -76.471183 40.965616, -76.471193 40.965688, -76.471287 40.966095, -76.471333 40.966258, -76.471395 40.966564, -76.471475 40.966816, -76.471505 40.966935, -76.471517 40.967042, -76.471512 40.967199, -76.471505 40.967247, -76.471483 40.967292, -76.471181 40.967694, -76.471103 40.967754, -76.471043 40.967794, -76.470977 40.967854, -76.470944 40.967903, -76.470937 40.967921, -76.470915 40.967998, -76.470864 40.968067, -76.470684 40.96824, -76.470483 40.968381, -76.470394 40.968439, -76.470231 40.968586, -76.470101 40.968722, -76.470055 40.968784, -76.470021 40.968858, -76.469974 40.969002, -76.469941 40.969143, -76.469809 40.969343, -76.469774 40.969405, -76.469744 40.969498, -76.469731 40.969569, -76.469638 40.969904, -76.469591 40.970104, -76.469589 40.970174, -76.469612 40.970229, -76.469573 40.970371, -76.469542 40.970421, -76.46952 40.970476, -76.469502 40.9706, -76.469467 40.970757, -76.469461 40.970998, -76.469406 40.971093, -76.469389 40.97125, -76.46937 40.971345, -76.469361 40.971432, -76.469343 40.971515, -76.469317 40.971601, -76.469318 40.971681, -76.469368 40.97174, -76.469438 40.971792, -76.469487 40.971818, -76.469541 40.971816, -76.469554 40.971785, -76.469573 40.971692, -76.469591 40.971631, -76.469615 40.971478, -76.469623 40.971382, -76.46962 40.971173, -76.469656 40.971125, -76.469689 40.971132, -76.469723 40.971205, -76.469734 40.971275, -76.469728 40.971888, -76.469692 40.972086, -76.469673 40.972234, -76.469657 40.972424, -76.469622 40.972584, -76.469615 40.97265, -76.469584 40.972831, -76.469541 40.972953, -76.46951 40.973111, -76.469416 40.973279, -76.469857 40.973572, -76.471526 40.974841, -76.471652 40.975004, -76.47183 40.975214, -76.472075 40.975563, -76.472105 40.97559, -76.472127 40.975699, -76.47216 40.975834, -76.472275 40.976201, -76.472227 40.976314, -76.472197 40.976402, -76.472172 40.976456, -76.472152 40.976531, -76.472128 40.976764, -76.472098 40.976891, -76.471966 40.977318, -76.471855 40.977677, -76.471795 40.977848, -76.471702 40.97816, -76.471658 40.978311, -76.471632 40.97846, -76.471628 40.978681, -76.471645 40.978914, -76.471673 40.979008, -76.471707 40.979216, -76.471736 40.979382, -76.47179 40.979543, -76.471811 40.97961, -76.471847 40.979753, -76.471966 40.980011, -76.472093 40.980342, -76.472193 40.980547, -76.472257 40.980625, -76.472302 40.98066, -76.472499 40.980917, -76.472607 40.98102, -76.472645 40.981074, -76.472657 40.98111, -76.472642 40.98115, -76.472668 40.981201, -76.472639 40.981407, -76.472601 40.981683, -76.472571 40.981675, -76.472548 40.981659, -76.472468 40.981621, -76.472439 40.981614, -76.472283 40.981595, -76.472206 40.981593, -76.47199 40.981539, -76.471906 40.981529, -76.471826 40.981515, -76.471601 40.98149, -76.47144 40.981496, -76.471282 40.98151, -76.4712 40.981509, -76.471129 40.981499, -76.471057 40.981477, -76.470924 40.981422, -76.470865 40.981388, -76.470676 40.981291, -76.470616 40.981254, -76.470552 40.981225, -76.470475 40.981208, -76.470355 40.981142, -76.470315 40.98119, -76.470344 40.98124, -76.470385 40.981296, -76.470418 40.981354, -76.470496 40.981519, -76.470526 40.981569, -76.470565 40.98169, -76.470615 40.981737, -76.470641 40.981793, -76.470629 40.981848, -76.470887 40.982155, -76.47091 40.982188, -76.470914 40.982245, -76.470958 40.982351, -76.471008 40.982436, -76.471166 40.982656, -76.471194 40.982711, -76.471219 40.982744, -76.471267 40.982793, -76.471291 40.982807, -76.471351 40.982831, -76.471358 40.982851, -76.471434 40.982937, -76.471391 40.982948, -76.471318 40.982875, -76.471254 40.982842, -76.471171 40.982745, -76.471141 40.982692, -76.471065 40.982594, -76.470961 40.982434, -76.470937 40.982382, -76.470905 40.982327, -76.470864 40.982281, -76.470841 40.982226, -76.470841 40.982199, -76.470584 40.981893, -76.470517 40.981839, -76.470476 40.981789, -76.470468 40.98177, -76.47048 40.981716, -76.470457 40.981663, -76.470419 40.981606, -76.470391 40.981554, -76.470353 40.981436, -76.470295 40.981332, -76.470273 40.981275, -76.470242 40.98116, -76.470215 40.981108, -76.4701 40.981032, -76.47004 40.980998, -76.469975 40.98097, -76.469901 40.980947, -76.469834 40.980911, -76.469777 40.980868, -76.46964 40.980726, -76.469602 40.980675, -76.469557 40.980626, -76.4695 40.98058, -76.469406 40.980481, -76.469351 40.980432, -76.469222 40.980338, -76.469164 40.980281, -76.469076 40.980233, -76.469045 40.980209, -76.468986 40.980148, -76.468946 40.980118, -76.468893 40.980051, -76.46884 40.980009, -76.468811 40.979978, -76.468781 40.979926, -76.46875 40.979901, -76.468726 40.979875, -76.468718 40.979857, -76.46867 40.9798, -76.468632 40.97977, -76.468586 40.97974, -76.468552 40.979699, -76.46837 40.979513, -76.468347 40.979474, -76.468294 40.97941, -76.468256 40.979384, -76.468144 40.979239, -76.468052 40.979097, -76.467973 40.978993, -76.467899 40.978918, -76.467872 40.978879, -76.467855 40.978831, -76.467819 40.978778, -76.467773 40.978723, -76.467736 40.978697, -76.467722 40.978677, -76.467742 40.978666, -76.467751 40.978649, -76.467726 40.978636, -76.467719 40.978589, -76.467692 40.978581, -76.467654 40.978584, -76.467629 40.97859, -76.467606 40.978585, -76.467589 40.978572, -76.467534 40.978499, -76.467444 40.978342, -76.46732 40.978146, -76.467276 40.978125, -76.467226 40.978152, -76.467202 40.978153, -76.467182 40.97814, -76.467173 40.97812, -76.467177 40.978097, -76.467204 40.978059, -76.467214 40.978033, -76.467211 40.977996, -76.467181 40.977963, -76.467141 40.977963, -76.467101 40.977979, -76.467046 40.978015, -76.467008 40.978032, -76.466984 40.97803, -76.466961 40.978036, -76.466942 40.978054, -76.4669 40.978109, -76.466872 40.978136, -76.466789 40.978198, -76.466761 40.978224, -76.466687 40.978312, -76.466629 40.978359, -76.466531 40.978457, -76.466485 40.978518, -76.466447 40.978557, -76.466402 40.978593, -76.466374 40.978625, -76.466311 40.978716, -76.466276 40.978757, -76.46617 40.978856, -76.46613 40.978909, -76.46591 40.97912, -76.465817 40.979247, -76.465732 40.979337, -76.465714 40.979364, -76.465626 40.979444, -76.465616 40.979465, -76.465553 40.979551, -76.465506 40.979602, -76.465449 40.979653, -76.465407 40.979678, -76.465328 40.97974, -76.465318 40.979764, -76.465297 40.979779, -76.465237 40.979809, -76.465182 40.979865, -76.465039 40.979997, -76.464913 40.980143, -76.464808 40.980217, -76.464791 40.980239, -76.46474 40.980287, -76.464675 40.980357, -76.464668 40.980383, -76.464648 40.980415, -76.464585 40.980463, -76.464521 40.98054, -76.464472 40.980586, -76.464416 40.980612, -76.46441 40.980641, -76.464399 40.980661, -76.464296 40.98075, -76.464262 40.980799, -76.464161 40.980904, -76.464104 40.980954, -76.464054 40.981005, -76.463989 40.981063, -76.46393 40.981121, -76.463862 40.981199, -76.463795 40.98125, -76.463686 40.981348, -76.463471 40.98157, -76.463368 40.981661, -76.463295 40.981746, -76.463042 40.982006, -76.462838 40.982185, -76.462703 40.982324, -76.462611 40.982399, -76.462504 40.982494, -76.462267 40.982732, -76.462161 40.982824, -76.462039 40.982902, -76.461958 40.982964, -76.46183 40.983053, -76.461722 40.983163, -76.461657 40.983211, -76.461439 40.983397, -76.461388 40.983434, -76.461329 40.983487, -76.461127 40.983615, -76.460991 40.983721, -76.460891 40.983805, -76.460784 40.983866, -76.460685 40.983947, -76.460558 40.984027, -76.460379 40.984154, -76.460214 40.98426, -76.460102 40.984345, -76.459871 40.984492, -76.459835 40.984515, -76.459424 40.984744, -76.459352 40.98478, -76.459123 40.984922, -76.458715 40.985132, -76.458639 40.985159, -76.458473 40.985248, -76.458448 40.985263, -76.458376 40.985289, -76.458302 40.98531, -76.458234 40.985343, -76.458175 40.985381, -76.458115 40.985413, -76.458062 40.985456, -76.457999 40.98549, -76.457932 40.985513, -76.457869 40.985543, -76.457807 40.985578, -76.457736 40.985604, -76.45768 40.98564, -76.457633 40.985685, -76.457566 40.985713, -76.457504 40.985751, -76.457437 40.985774, -76.457374 40.985803, -76.457252 40.985873, -76.457113 40.985918, -76.45699 40.985985, -76.456853 40.986043, -76.456788 40.986077, -76.45672 40.986102, -76.456649 40.986119, -76.456581 40.986145, -76.45646 40.986219, -76.456408 40.986259, -76.456285 40.986324, -76.456093 40.986417, -76.456035 40.986454, -76.456001 40.986497, -76.455977 40.986497, -76.455916 40.986508, -76.455861 40.986548, -76.455736 40.986608, -76.455401 40.986748, -76.454869 40.986986, -76.454798 40.987012, -76.454734 40.987049, -76.454601 40.987107, -76.454176 40.987337, -76.453975 40.987411, -76.453849 40.98747, -76.453644 40.987538, -76.453581 40.987571, -76.453317 40.98769, -76.453123 40.987771, -76.45292 40.98785, -76.45285 40.987873, -76.452727 40.987943, -76.452658 40.987967, -76.452595 40.987998, -76.452534 40.988035, -76.452461 40.988049, -76.452391 40.988077, -76.45233 40.988112, -76.45226 40.988133, -76.452202 40.988166, -76.452218 40.988223, -76.452244 40.98828, -76.452207 40.988301, -76.45216 40.988255, -76.452089 40.98824, -76.452047 40.988241, -76.451994 40.988283, -76.451859 40.988338, -76.451741 40.988407, -76.45161 40.988462, -76.45154 40.988485, -76.45148 40.988519, -76.45141 40.988544, -76.451346 40.988575, -76.451275 40.988594, -76.451217 40.988632, -76.451024 40.988731, -76.450896 40.98879, -76.450839 40.988827, -76.450708 40.98888, -76.450589 40.988951, -76.450537 40.98899, -76.45047 40.989015, -76.449907 40.989322, -76.449771 40.98938, -76.44966 40.989434, -76.449415 40.989561, -76.449346 40.989593, -76.449291 40.989638, -76.449267 40.989651, -76.448539 40.98832, -76.446646 40.989211, -76.439402 40.992468, -76.439265 40.992525, -76.438304 40.991364, -76.437939 40.990922, -76.437607 40.99052, -76.437361 40.990248, -76.437225 40.990121, -76.437044 40.989981, -76.436848 40.989847, -76.436584 40.989694, -76.436378 40.989594, -76.436261 40.989544, -76.436196 40.98962, -76.436137 40.989688, -76.436091 40.989771, -76.43602 40.989961, -76.435929 40.990109, -76.43583 40.990226, -76.435753 40.990291, -76.435625 40.990371, -76.435472 40.990444, -76.435332 40.990494, -76.43503 40.990581, -76.434008 40.990817, -76.433549 40.990922, -76.432874 40.991077, -76.431994 40.991278, -76.431539 40.991359, -76.429751 40.991658, -76.426723 40.992137, -76.425622 40.992342, -76.425152 40.992416, -76.424892 40.992461, -76.424777 40.992539, -76.424704 40.992577, -76.424581 40.992621, -76.424406 40.992672, -76.424171 40.992732, -76.4235 40.992948, -76.423237 40.993047, -76.423037 40.993132, -76.422779 40.993251, -76.422241 40.993476, -76.421647 40.993742, -76.421496 40.99381, -76.421218 40.993946, -76.42096 40.994094, -76.420789 40.994196, -76.420169 40.994566, -76.420046 40.994643, -76.419539 40.99497, -76.419239 40.995163, -76.418661 40.995553, -76.418336 40.99576, -76.418164 40.995854, -76.41805 40.995917, -76.417849 40.996016, -76.417453 40.996186, -76.417238 40.996268, -76.416188 40.996609, -76.415729 40.996751, -76.415185 40.996926, -76.414569 40.997105, -76.414216 40.997212, -76.413663 40.997372, -76.412975 40.997581, -76.412563 40.997727, -76.412048 40.997885, -76.411766 40.997987, -76.411259 40.998194, -76.41113 40.998247, -76.410783 40.998408, -76.410707 40.998451, -76.410675 40.99848, -76.410666 40.998509, -76.410686 40.998556, -76.410704 40.998569, -76.410806 40.998589, -76.410958 40.998594, -76.411622 40.998634, -76.412087 40.998652, -76.412281 40.998651, -76.412634 40.998625, -76.412822 40.998599, -76.412996 40.998563, -76.413458 40.998454, -76.413705 40.998385, -76.414379 40.998182, -76.414806 40.998042, -76.41523 40.997895, -76.415771 40.997723, -76.416076 40.997616, -76.416497 40.99748, -76.416738 40.997396, -76.41749 40.997152, -76.417855 40.997038, -76.418159 40.996919, -76.418327 40.99684, -76.418608 40.996696, -76.418718 40.996629, -76.418828 40.996545, -76.41899 40.996407, -76.419356 40.996007, -76.419437 40.996037, -76.419531 40.996066, -76.419708 40.996106, -76.419949 40.996145, -76.420267 40.996174, -76.420434 40.996168, -76.420645 40.996131, -76.421247 40.996008, -76.421705 40.995914, -76.422011 40.995835, -76.421894 40.996232, -76.421882 40.996431, -76.421871 40.996627, -76.421821 40.997516, -76.421753 40.998362, -76.4276 40.996704, -76.428854 40.998093, -76.42881 40.998104, -76.428638 40.998185, -76.428594 40.998201, -76.428526 40.998233, -76.4285 40.998251, -76.428471 40.998263, -76.428371 40.998271, -76.428289 40.998299, -76.428254 40.998316, -76.427929 40.998371, -76.427424 40.998493, -76.427354 40.998514, -76.427206 40.998581, -76.427124 40.998611, -76.426889 40.998681, -76.426701 40.998726, -76.425919 40.9989, -76.425124 40.999115, -76.423875 40.999411, -76.423536 40.999505, -76.423067 40.999629, -76.422366 40.9998, -76.422037 40.999884, -76.421768 40.999989, -76.421543 41.000069, -76.42076 41.000305, -76.419956 41.000556, -76.419602 41.000646, -76.419292 41.000743, -76.418355 41.001013, -76.418 41.001124, -76.417604 41.001234, -76.416781 41.001483, -76.415939 41.001761, -76.415283 41.002021, -76.414872 41.002169, -76.414115 41.002516, -76.413777 41.002653, -76.413389 41.002867, -76.41336 41.002883, -76.413038 41.003076, -76.412578 41.003275, -76.411778 41.003682, -76.411223 41.003936, -76.410911 41.004093, -76.410812 41.004143, -76.410321 41.004339, -76.409924 41.004524, -76.409681 41.004654, -76.409119 41.004944, -76.408963 41.005018, -76.408734 41.005126, -76.408415 41.005265, -76.408119 41.005414, -76.407827 41.00558, -76.407617 41.005686, -76.407369 41.005796, -76.407076 41.005926, -76.406903 41.006012, -76.406679 41.006107, -76.406403 41.006183, -76.406217 41.006258, -76.405802 41.006491, -76.405626 41.006565, -76.405374 41.00665, -76.405214 41.006696, -76.403595 41.007295, -76.402508 41.007697, -76.401574 41.008128, -76.401524 41.008139, -76.401008 41.008247, -76.400421 41.008466, -76.399648 41.008732, -76.398884 41.008908, -76.397605 41.009163, -76.396382 41.009435, -76.395365 41.00973, -76.394343 41.009968, -76.390411 41.010706, -76.38807 41.011117, -76.387018 41.011358, -76.386 41.011638, -76.385764 41.011703, -76.385399 41.011808, -76.385233 41.011837, -76.385081 41.011836, -76.384371 41.012067, -76.383659 41.012285, -76.38288 41.012562, -76.380171 41.013321, -76.379149 41.013607, -76.378776 41.013805, -76.378053 41.013977, -76.377395 41.014048, -76.376622 41.014166, -76.376102 41.014301, -76.375505 41.014467, -76.374731 41.014675, -76.374703 41.014683, -76.37401 41.01493, -76.373914 41.014965, -76.373367 41.01511, -76.373119 41.015187, -76.372777 41.015293, -76.371402 41.015659, -76.370944 41.015791, -76.370845 41.01582, -76.370536 41.01592, -76.370222 41.016022, -76.369919 41.016069, -76.369481 41.016279, -76.369605 41.016095, -76.369339 41.016174, -76.369071 41.016361, -76.368927 41.016405, -76.368613 41.016501, -76.367949 41.016723, -76.367337 41.01688, -76.366604 41.017092, -76.366087 41.017178, -76.365083 41.017418, -76.364278 41.017615, -76.363197 41.017893, -76.361832 41.018193, -76.360858 41.018432, -76.360083 41.018544, -76.35943 41.01869, -76.359337 41.01869, -76.358992 41.01879, -76.35729 41.019111, -76.356695 41.019262, -76.355325 41.019561, -76.355168 41.019591, -76.354752 41.019646, -76.354469 41.019668, -76.354167 41.019724, -76.353161 41.0199, -76.353097 41.019914, -76.352568 41.020034, -76.351805 41.02019, -76.35069 41.020361, -76.349883 41.020455, -76.349221 41.020577, -76.348277 41.020675, -76.347603 41.02078, -76.346839 41.020965, -76.346426 41.021073, -76.346124 41.021107, -76.345609 41.021195, -76.345055 41.021291, -76.34407 41.021472, -76.343021 41.021774, -76.341881 41.022198, -76.341434 41.022388, -76.340799 41.022625, -76.340325 41.022826, -76.340186 41.022886, -76.339551 41.023116, -76.339045 41.023313, -76.338514 41.023589, -76.337843 41.02393, -76.337403 41.024179, -76.336972 41.024412, -76.336511 41.024706, -76.336276 41.024887, -76.335991 41.025057, -76.335742 41.025184, -76.335673 41.025244, -76.335437 41.025451, -76.335283 41.025447, -76.33513 41.025566, -76.334954 41.025703, -76.334572 41.025975, -76.334 41.026308, -76.333489 41.0266, -76.332792 41.026972, -76.332297 41.027334, -76.33196 41.027621, -76.331787 41.02776, -76.331721 41.027808, -76.33166 41.027879, -76.331463 41.028083, -76.331322 41.028213, -76.331264 41.028257, -76.33096 41.028438, -76.330889 41.028472, -76.330775 41.028515, -76.330566 41.028621, -76.330252 41.028738, -76.330154 41.028786, -76.33008 41.02884, -76.329993 41.028893, -76.329838 41.028933, -76.329756 41.028959, -76.329482 41.029034, -76.329365 41.029058, -76.329164 41.02913, -76.329019 41.02919, -76.328919 41.02922, -76.32884 41.029237, -76.328664 41.029295, -76.328395 41.029409, -76.328345 41.029436, -76.32829 41.029466, -76.328134 41.029574, -76.327991 41.029616, -76.327935 41.029634, -76.327862 41.029658, -76.327618 41.029816, -76.327267 41.03, -76.327178 41.030033, -76.327075 41.030092, -76.326977 41.030135, -76.32689 41.030192, -76.326787 41.030234, -76.326498 41.030373, -76.326245 41.030527, -76.326141 41.030584, -76.325883 41.030703, -76.325077 41.031244, -76.324638 41.031523, -76.323841 41.032021, -76.323305 41.03243, -76.322764 41.032794, -76.322396 41.03305, -76.321883 41.033268, -76.321454 41.033419, -76.320734 41.033582, -76.320094 41.033782, -76.31972 41.033976, -76.318638 41.034436, -76.318011 41.034763, -76.317434 41.035051, -76.316969 41.035326, -76.31634 41.035573, -76.315534 41.035956, -76.314836 41.036251, -76.314285 41.03634, -76.313363 41.036577, -76.31323 41.036654, -76.312811 41.036751, -76.312538 41.036814, -76.312317 41.036875, -76.310907 41.035468, -76.309612 41.034177, -76.309388 41.033953, -76.309461 41.033921, -76.309975 41.033687, -76.310052 41.033649, -76.310301 41.033525, -76.310648 41.033339, -76.311724 41.032703, -76.315699 41.03028, -76.31703 41.029463, -76.318614 41.028471, -76.318919 41.028293, -76.319597 41.027896, -76.320457 41.02737, -76.32104 41.027019, -76.32125 41.026889, -76.32198 41.026439, -76.32137 41.026112, -76.321118 41.026037, -76.320904 41.025944, -76.320602 41.025822, -76.32036 41.025735, -76.320074 41.025649, -76.319825 41.025585, -76.319554 41.025529, -76.319254 41.025459, -76.318758 41.025359, -76.318608 41.02534, -76.318561 41.025328, -76.318423 41.025266, -76.318365 41.025229, -76.318282 41.025147, -76.318161 41.025485, -76.317959 41.026105, -76.317571 41.027189, -76.317429 41.0275, -76.317343 41.02763, -76.317327 41.02765, -76.317252 41.027742, -76.317034 41.027972, -76.31689 41.028107, -76.316757 41.02821, -76.316716 41.028237, -76.316366 41.028437, -76.316035 41.028592, -76.315877 41.028398, -76.315411 41.027787, -76.315318 41.027829, -76.314548 41.028145, -76.314092 41.028337, -76.312779 41.028907, -76.312747 41.028916, -76.312699 41.028853, -76.312255 41.028274, -76.31264 41.027982, -76.313278 41.027728, -76.314565 41.027175, -76.314875 41.027052, -76.31434 41.026362, -76.314148 41.0261, -76.313821 41.025654, -76.313492 41.025204, -76.312744 41.024207, -76.312544 41.023931, -76.312428 41.023771, -76.312203 41.023461, -76.312015 41.023206, -76.311616 41.022667, -76.311242 41.02216, -76.311056 41.021908, -76.310549 41.021206, -76.310466 41.021096, -76.310261 41.020826, -76.310201 41.020739, -76.310141 41.020675, -76.310104 41.020649, -76.310051 41.02064, -76.310023 41.020641, -76.309914 41.020673, -76.309754 41.020732, -76.309465 41.020862, -76.309069 41.021023, -76.307984 41.021476, -76.307708 41.021596, -76.307403 41.021722, -76.306826 41.021955, -76.305533 41.022493, -76.305233 41.022622, -76.304875 41.022776, -76.304635 41.022885, -76.304522 41.022934, -76.304449 41.022808, -76.304262 41.022584, -76.304201 41.022415, -76.304183 41.022348, -76.304237 41.022227, -76.304274 41.02216, -76.304336 41.022123, -76.304326 41.022117, -76.304154 41.022021, -76.304018 41.021922, -76.303948 41.021862, -76.303876 41.021788, -76.303848 41.021724, -76.30373 41.021594, -76.303598 41.021476, -76.303522 41.021439, -76.30343 41.021428, -76.303341 41.021386, -76.303188 41.021272, -76.303089 41.021217, -76.303005 41.021184, -76.30279 41.021157, -76.302715 41.021135, -76.302627 41.021095, -76.302551 41.021039, -76.302497 41.021012, -76.302453 41.021001, -76.302144 41.020986, -76.302099 41.020976, -76.302046 41.020945, -76.30202 41.020917, -76.301984 41.020864, -76.301922 41.020837, -76.301748 41.020814, -76.301459 41.020809, -76.301177 41.020826, -76.300983 41.020815, -76.300795 41.020726, -76.300737 41.020688, -76.300618 41.020652, -76.300516 41.020641, -76.300429 41.020667, -76.300339 41.02072, -76.300255 41.020753, -76.300185 41.020762, -76.300109 41.020744, -76.299946 41.021175, -76.299916 41.021272, -76.299905 41.021395, -76.299909 41.021448, -76.299924 41.021498, -76.299968 41.021605, -76.300085 41.021827, -76.300239 41.022086, -76.300306 41.022179, -76.300424 41.022318, -76.300889 41.022814, -76.301166 41.023135, -76.301586 41.023694, -76.301827 41.024025, -76.301786 41.024049, -76.301472 41.024169, -76.301296 41.024243, -76.300889 41.024424, -76.300154 41.024731, -76.299776 41.024905, -76.299264 41.025117, -76.299014 41.025226, -76.298674 41.025368, -76.298299 41.025515, -76.297237 41.025951, -76.296671 41.026178, -76.296288 41.026337, -76.295023 41.026825, -76.293771 41.027333, -76.293542 41.027433, -76.293616 41.027525, -76.29419 41.028324, -76.296299 41.031137, -76.296752 41.031767, -76.29738 41.032626, -76.297853 41.033251, -76.298117 41.033611, -76.298345 41.033943, -76.298358 41.033962, -76.29884 41.034678, -76.298537 41.034798, -76.297684 41.035124, -76.297164 41.035337, -76.296261 41.035719, -76.295308 41.036519, -76.295394 41.036515, -76.296411 41.036455, -76.296615 41.03643, -76.296747 41.036678, -76.296795 41.036746, -76.296906 41.036876, -76.297083 41.037068, -76.297135 41.037134, -76.298252 41.037107, -76.298796 41.037095, -76.299171 41.037082, -76.299528 41.037061, -76.299884 41.037026, -76.300167 41.036985, -76.300493 41.036922, -76.300588 41.036899, -76.30079 41.037122, -76.301433 41.03817, -76.302453 41.037935, -76.303405 41.037715, -76.303474 41.037905, -76.303897 41.039044, -76.304662 41.039084, -76.304898 41.039124, -76.304912 41.039166, -76.304835 41.039284, -76.304724 41.03942, -76.304357 41.039727, -76.30418 41.039806, -76.304408 41.040419, -76.304145 41.040556, -76.303961 41.040651, -76.303344 41.040917, -76.302516 41.04119, -76.301661 41.041417, -76.301594 41.041447, -76.301493 41.041471, -76.301378 41.04149, -76.301238 41.041527, -76.301132 41.041561, -76.300897 41.041626, -76.300822 41.041658, -76.300567 41.041751, -76.300298 41.041832, -76.300222 41.041848, -76.300139 41.04188, -76.300045 41.041903, -76.299952 41.041918, -76.299566 41.042032, -76.299435 41.042084, -76.299332 41.042116, -76.299181 41.042135, -76.299028 41.04216, -76.29877 41.042191, -76.298655 41.042196, -76.298549 41.042206, -76.298263 41.042268, -76.297995 41.042291, -76.297727 41.042289, -76.297382 41.042278, -76.296907 41.042246, -76.296716 41.04224, -76.296574 41.042224, -76.296241 41.042209, -76.296037 41.04219, -76.295946 41.042176, -76.295752 41.04213, -76.295653 41.042113, -76.295415 41.042054, -76.295182 41.041987, -76.294891 41.041889, -76.294663 41.041772, -76.294502 41.0417, -76.294122 41.041473, -76.294039 41.041427, -76.293878 41.041349, -76.293614 41.041198, -76.293444 41.041082, -76.293392 41.041036, -76.293341 41.04096, -76.293254 41.040889, -76.293123 41.040762, -76.293066 41.040715, -76.292869 41.040593, -76.292682 41.040509, -76.292577 41.040469, -76.292472 41.040449, -76.29225 41.04043, -76.291895 41.040424, -76.291808 41.040431, -76.291724 41.040445, -76.291502 41.040444, -76.291186 41.040452, -76.29052 41.04044, -76.290374 41.040441, -76.29025 41.040448, -76.290132 41.04046, -76.290008 41.040459, -76.289893 41.040448, -76.289813 41.040447, -76.289732 41.040463, -76.289643 41.0405, -76.289524 41.04047, -76.289383 41.040458, -76.289269 41.040461, -76.289107 41.040458, -76.289063 41.040479, -76.289011 41.040519, -76.288941 41.040631, -76.288885 41.040695, -76.288822 41.040736, -76.288743 41.040779, -76.288663 41.040806, -76.288565 41.04082, -76.288421 41.040813, -76.288197 41.040786, -76.288116 41.040751, -76.287981 41.040703, -76.287848 41.040702, -76.287776 41.040728, -76.287716 41.040764, -76.287549 41.040893, -76.287365 41.041047, -76.287294 41.041086, -76.287209 41.041253, -76.287149 41.041335, -76.287141 41.041342, -76.286974 41.041531, -76.286682 41.041816, -76.286577 41.041882, -76.286478 41.042009, -76.286443 41.042071, -76.286308 41.042192, -76.286207 41.042389, -76.286167 41.042552, -76.286161 41.042611, -76.286142 41.042706, -76.286082 41.042855, -76.286039 41.04294, -76.286006 41.043083, -76.285976 41.043142, -76.285919 41.043235, -76.285902 41.043303, -76.285853 41.043387, -76.285794 41.043473, -76.285785 41.043545, -76.285814 41.043622, -76.28588 41.043685, -76.285942 41.043729, -76.286048 41.043783, -76.286029 41.043849, -76.285991 41.043906, -76.285854 41.044146, -76.285788 41.044203, -76.285638 41.044289, -76.285466 41.044364, -76.285391 41.044401, -76.285215 41.0445, -76.285139 41.044547, -76.285096 41.044614, -76.285101 41.04469, -76.285135 41.044745, -76.285222 41.044839, -76.285244 41.044923, -76.285241 41.045089, -76.285234 41.045191, -76.28521 41.045253, -76.285203 41.045273, -76.285175 41.045369, -76.285131 41.045469, -76.28505 41.045591, -76.284884 41.045761, -76.284819 41.045837, -76.285417 41.045898, -76.286421 41.04602, -76.286327 41.046856, -76.28672 41.046923, -76.287383 41.047056, -76.287624 41.046757, -76.287533 41.046711, -76.287471 41.046137, -76.287578 41.046147, -76.287817 41.046157, -76.288117 41.046157, -76.288296 41.04615, -76.288518 41.046132, -76.288883 41.046082, -76.28889 41.046109, -76.288924 41.046233, -76.288974 41.04631, -76.289016 41.046352, -76.28906 41.046378, -76.289127 41.0464, -76.289264 41.046434, -76.289837 41.046505, -76.29013 41.046525, -76.290279 41.046527, -76.290453 41.046517, -76.290739 41.046482, -76.291088 41.046422, -76.291619 41.046191, -76.291905 41.046044, -76.292061 41.04596, -76.293307 41.045292, -76.293851 41.04497, -76.293955 41.044899, -76.29603 41.044364, -76.297877 41.043888, -76.297836 41.044551, -76.297715 41.04478, -76.297715 41.045123, -76.297654 41.045489, -76.297775 41.045603, -76.299165 41.045375, -76.299679 41.045237, -76.299739 41.045054, -76.299558 41.044871, -76.299739 41.044711, -76.299739 41.044437, -76.299558 41.044254, -76.299801 41.04392, -76.29994 41.043355, -76.304696 41.042118, -76.305006 41.042037, -76.305423 41.041929, -76.306481 41.041654, -76.306645 41.041611, -76.307639 41.041357, -76.307745 41.041326, -76.30944 41.040829, -76.310442 41.040511, -76.31437 41.039254, -76.315657 41.038841, -76.316432 41.038607, -76.316554 41.03857, -76.316664 41.038659, -76.316782 41.038745, -76.316988 41.038867, -76.317089 41.038947, -76.31721 41.039066, -76.317293 41.039165, -76.317386 41.039299, -76.317421 41.039369, -76.317462 41.039504, -76.317499 41.039606, -76.317515 41.039666, -76.316906 41.039851, -76.315875 41.040175, -76.315493 41.040274, -76.315345 41.04032, -76.315206 41.040376, -76.315066 41.04045, -76.31498 41.040505, -76.314946 41.040546, -76.314923 41.04061, -76.31491 41.040712, -76.314908 41.040812, -76.314923 41.040913, -76.314952 41.041014, -76.314994 41.041112, -76.315048 41.041205, -76.315164 41.041364, -76.315225 41.041426, -76.315273 41.041459, -76.315326 41.041481, -76.315388 41.041494, -76.31549 41.041501, -76.315663 41.041492, -76.315762 41.041478, -76.315858 41.041453, -76.316095 41.041369, -76.316643 41.041193, -76.317785 41.040861, -76.317873 41.04113, -76.318038 41.041699, -76.318145 41.042039, -76.318229 41.042262, -76.318317 41.042466, -76.318371 41.042562, -76.318404 41.042603, -76.318462 41.042647, -76.31853 41.042683, -76.318629 41.04272, -76.319153 41.042939, -76.3192 41.042959, -76.319378 41.043022, -76.320609 41.043422, -76.320744 41.04347, -76.320989 41.043575, -76.321072 41.043622, -76.321128 41.043663, -76.321279 41.043798, -76.321643 41.0442, -76.321856 41.044469, -76.322097 41.044751, -76.322256 41.044914, -76.325643 41.043972, -76.326223 41.043976, -76.326247 41.043965, -76.326605 41.043779, -76.326709 41.043714, -76.326829 41.043626, -76.326934 41.043528, -76.327086 41.043361, -76.327212 41.043197, -76.32727 41.043105, -76.327328 41.043014, -76.327461 41.042765, -76.327498 41.042671, -76.327558 41.042461, -76.327573 41.042333, -76.327559 41.042047, -76.327544 41.041959, -76.3274 41.041484, -76.327377 41.041369, -76.327372 41.041235, -76.327387 41.041176, -76.327434 41.041076, -76.327506 41.040943, -76.327592 41.040825, -76.327712 41.040688, -76.327757 41.040647, -76.327872 41.040566, -76.328056 41.040455, -76.328409 41.040231, -76.328693 41.04004, -76.328939 41.039898, -76.329102 41.039821, -76.329666 41.039611, -76.329838 41.039542, -76.329978 41.039476, -76.330426 41.039246, -76.330474 41.03922, -76.330588 41.039142, -76.330743 41.039018, -76.331165 41.038694, -76.332437 41.037671, -76.332736 41.037399, -76.333095 41.037027, -76.333281 41.036839, -76.333466 41.036676, -76.333601 41.036573, -76.333717 41.036507, -76.333787 41.036475, -76.333954 41.036411, -76.334166 41.036347, -76.334335 41.036286, -76.33452 41.036192, -76.334544 41.036184, -76.334585 41.036182, -76.334625 41.036192, -76.334654 41.036207, -76.334687 41.036237, -76.334715 41.036277, -76.334729 41.036321, -76.334736 41.036473, -76.334732 41.036535, -76.334681 41.036913, -76.33468 41.036995, -76.33469 41.037126, -76.334705 41.037203, -76.334744 41.037315, -76.334807 41.037435, -76.334864 41.037514, -76.335079 41.037741, -76.335117 41.037694, -76.335137 41.037628, -76.335069 41.037573, -76.334995 41.037543, -76.334915 41.037479, -76.334822 41.037352, -76.334785 41.037293, -76.334759 41.037147, -76.334753 41.036985, -76.334758 41.036928, -76.334751 41.036862, -76.334752 41.036798, -76.334763 41.036725, -76.334786 41.036664, -76.334818 41.036614, -76.334871 41.036567, -76.334843 41.03655, -76.334811 41.036496, -76.334801 41.036439, -76.334788 41.036232, -76.334743 41.036186, -76.334689 41.03614, -76.334713 41.036087, -76.334687 41.036006, -76.334709 41.035947, -76.334708 41.035918, -76.334699 41.035785, -76.334667 41.035711, -76.334549 41.035615, -76.334528 41.035525, -76.334523 41.035432, -76.334499 41.035375, -76.334445 41.035275, -76.334434 41.03521, -76.334394 41.035127, -76.334384 41.035089, -76.334349 41.03498, -76.334211 41.034956, -76.333794 41.034897, -76.333381 41.034852, -76.332827 41.034812, -76.332797 41.034808, -76.332175 41.034785, -76.331838 41.034787, -76.331127 41.034824, -76.330676 41.034859, -76.330235 41.034907, -76.329052 41.035076, -76.329329 41.034983, -76.329472 41.034928, -76.32989 41.034803, -76.331492 41.034352, -76.332271 41.034126, -76.332966 41.033925, -76.333871 41.033665, -76.334729 41.033425, -76.334889 41.03338, -76.334978 41.033427, -76.335179 41.033535, -76.33523 41.03358, -76.335691 41.033792, -76.335766 41.033826, -76.336238 41.034082, -76.337452 41.03473, -76.337748 41.034885, -76.337993 41.035013, -76.33812 41.035071, -76.338272 41.035141, -76.338502 41.035247, -76.338731 41.035346, -76.338783 41.035361, -76.339223 41.035527, -76.339823 41.035742, -76.34046 41.035955, -76.34088 41.03606, -76.341229 41.036152, -76.341686 41.036266, -76.342069 41.036353, -76.343845 41.036712, -76.344511 41.036835, -76.354409 41.038901, -76.354572 41.03893, -76.354487 41.039078, -76.354412 41.03921, -76.354331 41.039352, -76.354497 41.039406, -76.354677 41.039454, -76.354863 41.039493, -76.355521 41.039605, -76.359039 41.040307, -76.359324 41.040361, -76.359676 41.040422, -76.359907 41.040445, -76.360124 41.040452, -76.360341 41.040448, -76.36059 41.040428, -76.360721 41.0404, -76.360757 41.040404, -76.361001 41.040362, -76.361223 41.040311, -76.361854 41.040141, -76.362645 41.039928, -76.364486 41.03947, -76.365614 41.039172, -76.366415 41.038983, -76.367218 41.038777, -76.367997 41.038578, -76.36799 41.038696, -76.367976 41.038794, -76.367945 41.038878, -76.367635 41.039445, -76.367521 41.039701, -76.367494 41.039786, -76.36749 41.039805, -76.367463 41.039947, -76.367458 41.040102, -76.367466 41.040152, -76.367511 41.040304, -76.367587 41.040517, -76.367654 41.040671, -76.367922 41.041349, -76.368209 41.042046, -76.368401 41.042491, -76.368802 41.043422, -76.369441 41.044947, -76.369659 41.045429, -76.369778 41.045706, -76.369933 41.046117, -76.369746 41.046156, -76.369265 41.04627, -76.368474 41.046432, -76.367577 41.046615, -76.367201 41.046698, -76.36641 41.046883, -76.364802 41.047228, -76.362736 41.047637, -76.361842 41.047821, -76.360855 41.048006, -76.359716 41.048236, -76.359294 41.048313, -76.3579 41.048559, -76.357562 41.048631, -76.357605 41.048856, -76.357656 41.049267, -76.357692 41.049767, -76.357772 41.050444, -76.357857 41.051493, -76.358003 41.052983, -76.358053 41.053774, -76.358155 41.053726, -76.358216 41.053676, -76.358342 41.053587, -76.358415 41.053519, -76.358482 41.053472, -76.358556 41.053437, -76.358618 41.0534, -76.358654 41.053346, -76.358729 41.053315, -76.358838 41.053319, -76.358928 41.053307, -76.359032 41.053222, -76.359071 41.053152, -76.35911 41.053099, -76.359164 41.053056, -76.359352 41.053003, -76.359425 41.052977, -76.359563 41.05291, -76.359647 41.052877, -76.35976 41.05282, -76.359839 41.052793, -76.359946 41.052749, -76.360039 41.052723, -76.360099 41.052682, -76.360259 41.052536, -76.360367 41.052497, -76.36051 41.052379, -76.360604 41.052391, -76.360667 41.052346, -76.360716 41.052296, -76.360734 41.052235, -76.360808 41.052212, -76.360896 41.052212, -76.360908 41.052156, -76.360943 41.052103, -76.361057 41.052082, -76.361139 41.052081, -76.361195 41.052036, -76.361311 41.051917, -76.361406 41.051737, -76.361415 41.05168, -76.361414 41.051611, -76.361391 41.051558, -76.361435 41.051513, -76.361553 41.051473, -76.361697 41.051365, -76.361763 41.051339, -76.361873 41.051276, -76.361935 41.051228, -76.362001 41.051203, -76.362245 41.051174, -76.362368 41.051062, -76.362543 41.051005, -76.362617 41.050992, -76.362695 41.050964, -76.362778 41.050923, -76.362869 41.050853, -76.362948 41.050815, -76.363196 41.050719, -76.36327 41.050698, -76.363393 41.050681, -76.363468 41.050652, -76.363576 41.050619, -76.363649 41.050583, -76.363776 41.050504, -76.363845 41.050495, -76.363996 41.050474, -76.364084 41.050448, -76.364148 41.050413, -76.364346 41.05047, -76.364425 41.050456, -76.364529 41.050521, -76.364571 41.050573, -76.364659 41.050604, -76.364775 41.050596, -76.364851 41.050619, -76.36489 41.05064, -76.364908 41.050713, -76.364992 41.050743, -76.365061 41.050776, -76.365117 41.050815, -76.365325 41.050767, -76.36541 41.05079, -76.365485 41.0508, -76.365698 41.05085, -76.365792 41.050812, -76.365829 41.050761, -76.365911 41.050729, -76.36599 41.050717, -76.366071 41.050721, -76.366142 41.050706, -76.366223 41.050702, -76.36628 41.050746, -76.36627 41.050823, -76.366347 41.050952, -76.366458 41.050985, -76.366542 41.050979, -76.366687 41.050935, -76.366807 41.050928, -76.366905 41.050894, -76.367015 41.050892, -76.367102 41.05088, -76.367176 41.050839, -76.367227 41.050798, -76.367314 41.05077, -76.367392 41.05077, -76.367498 41.050757, -76.367565 41.050731, -76.36764 41.050679, -76.36771 41.0507, -76.36777 41.050759, -76.367812 41.050828, -76.367885 41.050834, -76.36795 41.050803, -76.368042 41.050799, -76.368117 41.050823, -76.368181 41.050777, -76.36821 41.050726, -76.368259 41.050668, -76.368269 41.05061, -76.368293 41.050585, -76.368357 41.050558, -76.368422 41.050511, -76.368463 41.050456, -76.368534 41.050445, -76.368733 41.050404, -76.368846 41.050406, -76.369176 41.050421, -76.369266 41.050366, -76.369354 41.050335, -76.369525 41.050303, -76.369604 41.050329, -76.369695 41.050336, -76.369769 41.050356, -76.369838 41.050389, -76.369911 41.050391, -76.369994 41.050387, -76.370065 41.050421, -76.370142 41.050445, -76.370186 41.050443, -76.370269 41.050414, -76.370356 41.050398, -76.370432 41.050434, -76.370516 41.050438, -76.370584 41.050407, -76.370659 41.050409, -76.370734 41.050426, -76.370882 41.050392, -76.370972 41.050416, -76.371056 41.05045, -76.371131 41.050451, -76.371206 41.050445, -76.371244 41.050446, -76.371215 41.050062, -76.371145 41.049001, -76.370952 41.046846, -76.370949 41.046808, -76.370921 41.0463, -76.370895 41.046106, -76.370868 41.045956, -76.371928 41.045772, -76.372377 41.045684, -76.372811 41.045586, -76.373165 41.045495, -76.373606 41.045374, -76.373777 41.045319, -76.373729 41.045253, -76.373709 41.045217, -76.373605 41.044966, -76.373531 41.04482, -76.373472 41.044738, -76.373403 41.044662, -76.373299 41.044567, -76.373149 41.044461, -76.373055 41.044405, -76.372805 41.044281, -76.372569 41.044174, -76.37245 41.044089, -76.372379 41.044017, -76.372346 41.043966, -76.372294 41.043799, -76.37224 41.043675, -76.37221 41.043629, -76.372136 41.043549, -76.372076 41.04351, -76.372023 41.04349, -76.371937 41.04347, -76.371849 41.043464, -76.37164 41.043483, -76.371423 41.043521, -76.371369 41.043524, -76.371268 41.043513, -76.37124 41.043503, -76.371187 41.043457, -76.371182 41.043438, -76.371131 41.043334, -76.370844 41.042618, -76.370692 41.042252, -76.370647 41.042103, -76.370611 41.041929, -76.370595 41.041887, -76.370485 41.041705, -76.370392 41.041509, -76.370235 41.041108, -76.37004 41.040653, -76.369801 41.040134, -76.370049 41.040062, -76.370217 41.040026, -76.370559 41.039972, -76.371172 41.039866, -76.371266 41.03984, -76.371294 41.039823, -76.371309 41.0398, -76.371322 41.03973, -76.371323 41.03966, -76.371314 41.039617, -76.37127 41.039525, -76.37121 41.039422, -76.37118 41.039354, -76.371153 41.039215, -76.371105 41.039058, -76.370932 41.038675, -76.370736 41.038202, -76.370627 41.037956, -76.372198 41.037576, -76.373029 41.03735, -76.373439 41.037254, -76.373673 41.037213, -76.373966 41.037174, -76.374096 41.037158, -76.374145 41.037155, -76.37441 41.037158, -76.374583 41.037171, -76.374758 41.037194, -76.374995 41.037233, -76.375216 41.037282, -76.375251 41.037292, -76.375959 41.037504, -76.37623 41.037408, -76.377136 41.037052, -76.377332 41.036983, -76.377775 41.036801, -76.377852 41.036757, -76.377903 41.036711, -76.377754 41.036177, -76.377501 41.035596, -76.380234 41.034379, -76.380687 41.03418, -76.381234 41.033933, -76.381359 41.033908, -76.381866 41.033805, -76.382175 41.033744, -76.382518 41.033673, -76.383385 41.033508, -76.384039 41.033393, -76.384636 41.033277, -76.386025 41.033025, -76.3863 41.032964, -76.386681 41.032866, -76.386941 41.032788, -76.38702 41.032758, -76.38713 41.032702, -76.387508 41.032473, -76.387677 41.032392, -76.387831 41.032333, -76.387984 41.032288, -76.388377 41.032187, -76.389013 41.032034, -76.389891 41.031813, -76.390644 41.031641, -76.392294 41.031247, -76.392708 41.031141, -76.393718 41.030916, -76.394382 41.030774, -76.395301 41.030567, -76.395506 41.03052, -76.396077 41.030392, -76.397583 41.030044, -76.398268 41.029892, -76.398834 41.029756, -76.399244 41.029644, -76.399595 41.029538, -76.400664 41.029201, -76.401134 41.029048, -76.401673 41.028892, -76.403062 41.028531, -76.403202 41.028493, -76.404223 41.028215, -76.408924 41.026893, -76.409537 41.026733, -76.40993 41.026621, -76.410261 41.027406, -76.41038 41.027674, -76.411669 41.030769, -76.412405 41.032456, -76.412497 41.032699, -76.413118 41.03415, -76.413161 41.03427, -76.413166 41.03431, -76.413178 41.034348, -76.413178 41.034394, -76.413141 41.034492, -76.413103 41.034559, -76.412963 41.034687, -76.412844 41.034766, -76.412757 41.034843, -76.412634 41.034987, -76.41255 41.035124, -76.41251 41.035244, -76.412495 41.035289, -76.412433 41.035594, -76.412393 41.035878, -76.41237 41.036097, -76.412786 41.036122, -76.412876 41.036129, -76.413048 41.036142, -76.413482 41.036153, -76.413633 41.036145, -76.413791 41.036127, -76.413955 41.036102, -76.414434 41.036001, -76.41457 41.035966, -76.414838 41.035896, -76.414906 41.035881, -76.414571 41.038138, -76.414642 41.038298, -76.414725 41.038502, -76.414848 41.038829, -76.414913 41.038966, -76.415058 41.039311, -76.41512 41.03947, -76.41486 41.039548, -76.414769 41.039575, -76.414575 41.039617, -76.414337 41.039681, -76.414267 41.039706, -76.414145 41.03976, -76.413538 41.039999, -76.413196 41.040139, -76.412812 41.040287, -76.412891 41.040563, -76.413114 41.041259, -76.413195 41.041492, -76.413271 41.041739, -76.413387 41.042076, -76.413448 41.042237, -76.41367 41.042916, -76.413702 41.042979, -76.41376 41.04304, -76.41373 41.043061, -76.41353 41.043204, -76.41265 41.043838, -76.4126 41.043874, -76.412066 41.044245, -76.411242 41.044843, -76.411017 41.044996, -76.410766 41.045158, -76.410299 41.045479, -76.410086 41.045615, -76.409626 41.045927, -76.409477 41.046018, -76.409066 41.0463, -76.408606 41.046593, -76.408358 41.046762, -76.407682 41.047207, -76.406985 41.047661, -76.406551 41.047944, -76.40598 41.048332, -76.405608 41.048553, -76.40532 41.048691, -76.405212 41.048738, -76.404614 41.049, -76.40401 41.049271, -76.403558 41.049445, -76.402538 41.049819, -76.402333 41.049902, -76.402243 41.049935, -76.401922 41.050053, -76.401194 41.050345, -76.399978 41.050862, -76.398487 41.051526, -76.398231 41.051647, -76.397216 41.052111, -76.396999 41.052206, -76.395968 41.052684, -76.39562 41.052824, -76.39557 41.052844, -76.395457 41.052877, -76.395044 41.052962, -76.394517 41.053042, -76.393829 41.053117, -76.393515 41.053144, -76.391851 41.053224, -76.390888 41.053286, -76.390536 41.053315, -76.389871 41.05334, -76.389701 41.05335, -76.389756 41.053719, -76.389824 41.054265, -76.389886 41.054678, -76.389894 41.054855, -76.389888 41.054891, -76.389882 41.054927, -76.389881 41.054939, -76.389843 41.055033, -76.389816 41.055077, -76.389662 41.055242, -76.38954 41.055345, -76.389321 41.055501, -76.389136 41.055632, -76.389095 41.055661, -76.388618 41.055969, -76.388471 41.056055, -76.388182 41.056233, -76.388015 41.056351, -76.387923 41.056432, -76.387865 41.056509, -76.387834 41.056577, -76.3878 41.056696, -76.387759 41.057073, -76.387735 41.057205, -76.387722 41.057323, -76.387715 41.05739, -76.387718 41.057497, -76.387736 41.05759, -76.387852 41.058019, -76.387967 41.058491, -76.388025 41.058679, -76.388085 41.058827, -76.388165 41.058988, -76.388259 41.059193, -76.388341 41.059339, -76.388477 41.05962, -76.388578 41.059857, -76.38864 41.059982, -76.38754 41.060232, -76.387004 41.060362, -76.386392 41.060499, -76.386179 41.060555, -76.386065 41.060591, -76.385916 41.060648, -76.386047 41.061012, -76.386606 41.062492, -76.38678 41.062924, -76.386917 41.063293, -76.387337 41.064348, -76.387477 41.064688, -76.387536 41.064874, -76.387564 41.064997, -76.38757 41.065109, -76.387567 41.065138, -76.388113 41.066445, -76.389066 41.068786, -76.389084 41.068829, -76.3891 41.068875, -76.389521 41.070061, -76.389655 41.070419, -76.389889 41.071068, -76.390142 41.071732, -76.390292 41.07212, -76.390469 41.072095, -76.394357 41.071547, -76.395145 41.071436, -76.395016 41.071356, -76.394855 41.071263, -76.394703 41.071154, -76.394534 41.071043, -76.394135 41.070749, -76.394079 41.070703, -76.393862 41.070492, -76.39361 41.070193, -76.393547 41.070071, -76.393527 41.070015, -76.393518 41.06999, -76.393477 41.069828, -76.393468 41.069705, -76.393473 41.069634, -76.393504 41.069486, -76.39355 41.069372, -76.393611 41.069242, -76.393701 41.069112, -76.393876 41.068888, -76.393993 41.068729, -76.39423 41.068446, -76.394424 41.068249, -76.394595 41.068095, -76.394827 41.067877, -76.395621 41.067151, -76.395717 41.067046, -76.39582 41.066892, -76.395886 41.066719, -76.395905 41.066631, -76.39592 41.066445, -76.395915 41.066321, -76.395815 41.065791, -76.395811 41.065567, -76.395828 41.065395, -76.395851 41.065036, -76.395883 41.06475, -76.395893 41.064615, -76.395943 41.064177, -76.395971 41.063966, -76.39599 41.063773, -76.396005 41.063503, -76.396017 41.063081, -76.396013 41.062828, -76.395992 41.062518, -76.395965 41.062236, -76.395897 41.0618, -76.395845 41.061396, -76.395815 41.061037, -76.395799 41.060663, -76.395801 41.059909, -76.395781 41.059546, -76.395777 41.059343, -76.395766 41.059144, -76.395769 41.058951, -76.395766 41.058853, -76.395753 41.058345, -76.395735 41.058034, -76.395731 41.057869, -76.395715 41.057761, -76.395709 41.057645, -76.395679 41.057368, -76.395666 41.0572, -76.395628 41.05673, -76.395628 41.056603, -76.395633 41.056546, -76.39571 41.056059, -76.395722 41.055938, -76.39571 41.05585, -76.395693 41.055806, -76.395608 41.055674, -76.395555 41.055609, -76.395404 41.055384, -76.395251 41.055048, -76.395196 41.054895, -76.395185 41.054864, -76.395101 41.054528, -76.395044 41.054265, -76.395024 41.054203, -76.39499 41.054138, -76.394946 41.054089, -76.394891 41.054046, -76.394823 41.053993, -76.394701 41.053912, -76.394588 41.053854, -76.394452 41.053794, -76.394227 41.053684, -76.394146 41.053623, -76.394256 41.053582, -76.39564 41.053176, -76.396463 41.052925, -76.400565 41.05166, -76.405687 41.050189, -76.406456 41.049969, -76.407603 41.049638, -76.408092 41.049498, -76.409953 41.048962, -76.410615 41.048773, -76.411301 41.048573, -76.411919 41.048405, -76.412615 41.048219, -76.413713 41.047925, -76.414735 41.047652, -76.415341 41.047485, -76.415379 41.047531, -76.415427 41.047573, -76.415437 41.04758, -76.416324 41.047512, -76.416471 41.047456, -76.416561 41.047365, -76.416584 41.047297, -76.416608 41.047151, -76.416732 41.047118, -76.417615 41.046881, -76.418129 41.046745, -76.41827 41.046707, -76.418758 41.046575, -76.419679 41.046328, -76.419891 41.046271, -76.420148 41.046805, -76.420477 41.047782, -76.424295 41.047466, -76.423944 41.046106, -76.423873 41.04583, -76.423776 41.045455, -76.423723 41.045247, -76.423787 41.045232, -76.423835 41.045211, -76.423966 41.04517, -76.423997 41.045167, -76.424023 41.04516, -76.424086 41.045143, -76.424356 41.045072, -76.424447 41.045046, -76.424528 41.045024, -76.424572 41.04501, -76.424731 41.04497, -76.424795 41.044952, -76.424912 41.044921, -76.425051 41.044883, -76.425284 41.044821, -76.425442 41.044778, -76.425552 41.044748, -76.425514 41.044675, -76.425477 41.044596, -76.425179 41.043952, -76.425125 41.043836, -76.424872 41.043325, -76.42473 41.04307, -76.42453 41.042752, -76.424424 41.042599, -76.42409 41.04215, -76.424307 41.042073, -76.424357 41.042046, -76.424409 41.042005, -76.424472 41.041933, -76.42451 41.041866, -76.424529 41.041808, -76.424532 41.04174, -76.424511 41.041612, -76.424394 41.041177, -76.424322 41.040987, -76.423986 41.040189, -76.424324 41.040159, -76.424625 41.040143, -76.424836 41.040141, -76.425158 41.040158, -76.426356 41.040309, -76.426729 41.040359, -76.426987 41.040393, -76.426983 41.040331, -76.426969 41.040219, -76.426951 41.040189, -76.426912 41.040163, -76.426923 41.040146, -76.426939 41.040132, -76.426983 41.040113, -76.427022 41.040086, -76.427045 41.040057, -76.427047 41.040038, -76.427032 41.039993, -76.427021 41.039864, -76.42703 41.039759, -76.427062 41.039655, -76.427105 41.039545, -76.427123 41.039471, -76.427123 41.039413, -76.427105 41.039355, -76.427074 41.039284, -76.427038 41.039155, -76.427035 41.039118, -76.427076 41.039003, -76.427185 41.038629, -76.427241 41.038503, -76.427391 41.038232, -76.427702 41.037627, -76.427795 41.037486, -76.427879 41.037381, -76.427936 41.037323, -76.428115 41.037167, -76.428226 41.037079, -76.428383 41.036971, -76.428529 41.036886, -76.428727 41.03679, -76.428867 41.036733, -76.429137 41.036648, -76.429211 41.036626, -76.429275 41.036599, -76.429457 41.036494, -76.429513 41.036475, -76.429581 41.036468, -76.429675 41.036473, -76.429716 41.036485, -76.42978 41.036511, -76.429835 41.036518, -76.429867 41.036493, -76.429884 41.036463, -76.429927 41.036442, -76.429952 41.036439, -76.430048 41.036439, -76.430208 41.036454, -76.430458 41.036489, -76.430965 41.036585, -76.431174 41.036634, -76.431421 41.036705, -76.431782 41.036826, -76.43203 41.03689, -76.432308 41.036945, -76.432821 41.037021, -76.432887 41.037042, -76.433009 41.037093, -76.433359 41.037188, -76.433451 41.037197, -76.433557 41.037195, -76.433746 41.037223, -76.433867 41.03723, -76.433967 41.037227, -76.434181 41.037203, -76.434404 41.037167, -76.434763 41.037139, -76.434934 41.037143, -76.435009 41.037134, -76.435083 41.037117, -76.435418 41.037014, -76.435589 41.036953, -76.435821 41.036857, -76.435927 41.036798, -76.435994 41.036749, -76.436084 41.036667, -76.436142 41.036591, -76.43619 41.036504, -76.436238 41.036403, -76.43627 41.036316, -76.436302 41.03627, -76.436359 41.036227, -76.436425 41.036189, -76.436508 41.036134, -76.436538 41.036109, -76.4365 41.036093, -76.436389 41.036135, -76.436367 41.036115, -76.436381 41.036099, -76.436447 41.036053, -76.436582 41.035997, -76.436841 41.035871, -76.437039 41.035752, -76.437163 41.035696, -76.437496 41.035573, -76.437867 41.03544, -76.438223 41.035284, -76.438548 41.035132, -76.438798 41.035056, -76.438884 41.035039, -76.439034 41.034989, -76.439126 41.034977, -76.439216 41.034956, -76.439387 41.034925, -76.439462 41.03492, -76.43964 41.03492, -76.4398 41.034933, -76.439902 41.034936, -76.440053 41.034896, -76.440156 41.034877, -76.440347 41.034833, -76.44038 41.034826, -76.440394 41.034826, -76.440433 41.034813, -76.440475 41.034808, -76.440665 41.034765, -76.440793 41.034713, -76.440928 41.034652, -76.441096 41.034521, -76.441287 41.03433, -76.441375 41.034207, -76.441379 41.034167, -76.441337 41.034124, -76.441346 41.034072, -76.441375 41.034028, -76.441405 41.033957, -76.441427 41.033881, -76.441659 41.033548, -76.441718 41.033449, -76.441836 41.033319, -76.442124 41.03306, -76.442289 41.032919, -76.442426 41.032811, -76.442603 41.032689, -76.442805 41.032565, -76.443137 41.032385, -76.443175 41.032362, -76.443242 41.032338, -76.443302 41.032307, -76.443584 41.032216, -76.443664 41.032206, -76.443998 41.032115, -76.444077 41.032097, -76.444155 41.032074, -76.444226 41.032048, -76.444305 41.032031, -76.444467 41.032023, -76.444556 41.032006, -76.444631 41.031976, -76.444774 41.031898, -76.444846 41.031872, -76.445013 41.031864, -76.445027 41.031809, -76.444964 41.031783, -76.44489 41.031784, -76.444821 41.03176, -76.444828 41.031704, -76.445036 41.031604, -76.44517 41.031526, -76.445229 41.03148, -76.445264 41.031431, -76.445293 41.031366, -76.445327 41.031304, -76.445373 41.031253, -76.445434 41.031205, -76.445619 41.031018, -76.445667 41.030899, -76.445706 41.030831, -76.445735 41.030768, -76.445752 41.030713, -76.445751 41.030648, -76.445756 41.030584, -76.445741 41.030525, -76.44575 41.030468, -76.44575 41.030412, -76.445731 41.030352, -76.445664 41.030313, -76.445648 41.030277, -76.445664 41.030221, -76.445738 41.030047, -76.445786 41.02987, -76.445809 41.029745, -76.445815 41.029603, -76.44587 41.029311, -76.445873 41.029243, -76.445898 41.029176, -76.446073 41.029056, -76.446117 41.029009, -76.446071 41.028967, -76.446098 41.028913, -76.446166 41.028883, -76.446196 41.028828, -76.44625 41.028782, -76.446276 41.028721, -76.446275 41.028656, -76.446262 41.028601, -76.446258 41.028467, -76.44627 41.028325, -76.446292 41.028229, -76.446302 41.028185, -76.446404 41.027878, -76.446428 41.027817, -76.446444 41.027789, -76.446488 41.027719, -76.446573 41.027581, -76.446633 41.027457, -76.446672 41.027408, -76.446803 41.027243, -76.446885 41.027133, -76.447137 41.026871, -76.447191 41.026829, -76.447352 41.026672, -76.447524 41.026531, -76.447557 41.026492, -76.44761 41.026431, -76.447744 41.026362, -76.447877 41.026279, -76.44798 41.026198, -76.44811 41.026123, -76.448176 41.02608, -76.448376 41.025966, -76.448545 41.025916, -76.448631 41.025883, -76.448674 41.025833, -76.448728 41.025793, -76.4488 41.025776, -76.448878 41.025763, -76.449178 41.025686, -76.449242 41.025655, -76.449637 41.025551, -76.449872 41.025501, -76.450031 41.025472, -76.450247 41.025437, -76.450328 41.025417, -76.450473 41.025389, -76.450545 41.02538, -76.450623 41.02536, -76.450933 41.025329, -76.451012 41.025315, -76.451089 41.025291, -76.451163 41.025295, -76.451236 41.025281, -76.451309 41.025272, -76.451454 41.025243, -76.451603 41.025227, -76.451827 41.025183, -76.45198 41.025168, -76.452197 41.02512, -76.452276 41.02512, -76.452357 41.02511, -76.452426 41.025091, -76.452497 41.025082, -76.452573 41.025063, -76.45265 41.025056, -76.452724 41.02506, -76.452805 41.025047, -76.452879 41.025048, -76.453034 41.025085, -76.453104 41.025113, -76.453176 41.025126, -76.453251 41.025118, -76.453467 41.025056, -76.45353 41.025028, -76.453604 41.025014, -76.453681 41.025004, -76.453963 41.024949, -76.454118 41.024926, -76.454352 41.024902, -76.454517 41.024893, -76.454655 41.024836, -76.454731 41.024829, -76.454809 41.024828, -76.454954 41.024803, -76.455023 41.024785, -76.455252 41.024745, -76.455324 41.024717, -76.455389 41.02468, -76.455499 41.024604, -76.455653 41.024575, -76.455737 41.024571, -76.45581 41.024563, -76.45589 41.024536, -76.455967 41.024518, -76.456044 41.02451, -76.456188 41.024481, -76.456266 41.024471, -76.456349 41.024466, -76.456419 41.024449, -76.456725 41.024397, -76.456805 41.024388, -76.456957 41.024359, -76.457032 41.024351, -76.457182 41.024322, -76.457254 41.024316, -76.45748 41.024279, -76.457552 41.024259, -76.457635 41.024247, -76.45772 41.02424, -76.45794 41.024204, -76.45817 41.024192, -76.4584 41.02419, -76.458477 41.024185, -76.458552 41.024188, -76.458627 41.024183, -76.458859 41.024206, -76.458938 41.02422, -76.459093 41.02422, -76.459171 41.024234, -76.459242 41.02426, -76.459324 41.024265, -76.459477 41.024282, -76.459553 41.024274, -76.45964 41.024279, -76.459716 41.024307, -76.459781 41.024359, -76.459822 41.024408, -76.459961 41.024449, -76.460024 41.024491, -76.46009 41.02452, -76.460175 41.024529, -76.460252 41.02451, -76.460328 41.024504, -76.460408 41.024513, -76.460476 41.024536, -76.460554 41.024555, -76.46063 41.024582, -76.46071 41.024586, -76.46078 41.024603, -76.461054 41.024656, -76.461262 41.024704, -76.461557 41.024757, -76.461632 41.024766, -76.461711 41.024781, -76.462104 41.024804, -76.46242 41.024822, -76.462489 41.02482, -76.462395 41.024794, -76.462233 41.024769, -76.462092 41.024734, -76.462029 41.024714, -76.461815 41.024646, -76.461745 41.024614, -76.46168 41.024579, -76.461506 41.024466, -76.461454 41.024419, -76.461439 41.024364, -76.461335 41.024285, -76.461292 41.024236, -76.461255 41.024185, -76.461231 41.024133, -76.46115 41.024132, -76.461075 41.024121, -76.460926 41.024071, -76.460917 41.024047, -76.460979 41.024017, -76.461052 41.024003, -76.461122 41.024004, -76.461187 41.024028, -76.461266 41.024031, -76.461303 41.023981, -76.461303 41.02392, -76.461325 41.023738, -76.461315 41.02368, -76.461279 41.023629, -76.461325 41.023609, -76.461337 41.023585, -76.4613 41.02345, -76.46134 41.023443, -76.461361 41.023596, -76.461498 41.024081, -76.461695 41.024328, -76.461659 41.024411, -76.461698 41.024463, -76.461777 41.024476, -76.461825 41.024492, -76.461861 41.024504, -76.461914 41.024543, -76.462185 41.024655, -76.46226 41.024666, -76.46234 41.02467, -76.462578 41.024708, -76.462724 41.024741, -76.462806 41.024747, -76.462881 41.02476, -76.462954 41.024778, -76.463029 41.024789, -76.463178 41.024794, -76.463339 41.024807, -76.46341 41.024819, -76.463492 41.024822, -76.463569 41.024812, -76.463635 41.024783, -76.463656 41.024749, -76.463688 41.024726, -76.463713 41.024756, -76.463725 41.024785, -76.463747 41.024794, -76.463783 41.024795, -76.463838 41.024756, -76.463813 41.024742, -76.46388 41.024737, -76.463939 41.024776, -76.463992 41.024817, -76.464136 41.024847, -76.464219 41.02485, -76.464375 41.024866, -76.464526 41.024877, -76.464609 41.024874, -76.464686 41.02486, -76.46476 41.024864, -76.464838 41.024874, -76.464915 41.024889, -76.465064 41.024907, -76.465214 41.024903, -76.465364 41.024917, -76.465442 41.024914, -76.46567 41.024924, -76.465905 41.024908, -76.46598 41.024897, -76.466044 41.024869, -76.466078 41.024794, -76.466092 41.024773, -76.466105 41.024817, -76.466103 41.024847, -76.466167 41.024876, -76.46624 41.024878, -76.466474 41.024862, -76.466771 41.024855, -76.46692 41.024864, -76.467073 41.024864, -76.467148 41.024876, -76.467283 41.02492, -76.467353 41.024953, -76.467419 41.024976, -76.467567 41.025008, -76.467626 41.025004, -76.467814 41.025007, -76.467889 41.024998, -76.46805 41.024993, -76.468146 41.024985, -76.468306 41.024948, -76.468445 41.024906, -76.468473 41.024893, -76.468204 41.024898, -76.468019 41.024914, -76.467982 41.024905, -76.467946 41.02487, -76.467914 41.024859, -76.467875 41.024874, -76.46786 41.024918, -76.467819 41.024937, -76.467754 41.024926, -76.467678 41.024909, -76.467643 41.024892, -76.467499 41.024862, -76.467442 41.024828, -76.46737 41.024814, -76.467297 41.024808, -76.467227 41.02479, -76.467162 41.024764, -76.467183 41.024754, -76.46727 41.024727, -76.467234 41.024717, -76.467135 41.024754, -76.46717 41.0247, -76.467193 41.024682, -76.467149 41.024687, -76.467095 41.024707, -76.467018 41.024675, -76.46688 41.024641, -76.466802 41.024636, -76.466428 41.024592, -76.466271 41.024596, -76.466193 41.024594, -76.466116 41.0246, -76.466037 41.024601, -76.465964 41.024613, -76.465888 41.024618, -76.465811 41.024617, -76.465735 41.024628, -76.465587 41.024627, -76.465516 41.024651, -76.465442 41.02465, -76.465304 41.024694, -76.465223 41.024708, -76.465068 41.024728, -76.464926 41.024762, -76.464852 41.024775, -76.464778 41.024767, -76.464746 41.024739, -76.464725 41.024729, -76.464573 41.024715, -76.464426 41.024719, -76.464346 41.024726, -76.464269 41.024738, -76.464193 41.024741, -76.464136 41.024706, -76.464174 41.024659, -76.464249 41.024645, -76.464322 41.024639, -76.464399 41.024639, -76.464558 41.024647, -76.464713 41.024643, -76.46494 41.024625, -76.46502 41.024624, -76.465099 41.024609, -76.465177 41.024616, -76.465318 41.024575, -76.465466 41.024547, -76.465539 41.024556, -76.465616 41.024533, -76.465696 41.024521, -76.465849 41.024513, -76.46608 41.024491, -76.466382 41.024484, -76.466456 41.024488, -76.46653 41.024498, -76.466693 41.024511, -76.466768 41.024512, -76.466915 41.024527, -76.466993 41.024525, -76.46707 41.024539, -76.467134 41.02457, -76.467208 41.024593, -76.467271 41.024621, -76.467324 41.02466, -76.467402 41.024668, -76.467472 41.024699, -76.467546 41.024712, -76.467699 41.024747, -76.467851 41.024775, -76.467972 41.024793, -76.468048 41.024803, -76.468132 41.024808, -76.468286 41.02483, -76.468598 41.02483, -76.468674 41.024822, -76.468755 41.024803, -76.468835 41.0248, -76.468914 41.024785, -76.469076 41.024745, -76.469145 41.024721, -76.469309 41.024678, -76.469554 41.024571, -76.469628 41.024547, -76.469823 41.024495, -76.469898 41.024444, -76.46995 41.024392, -76.470108 41.02432, -76.470177 41.02428, -76.470603 41.024069, -76.470683 41.024022, -76.470884 41.023887, -76.470958 41.023845, -76.471063 41.0238, -76.471103 41.023785, -76.471125 41.02377, -76.471213 41.023755, -76.471234 41.023734, -76.471256 41.02372, -76.47129 41.023719, -76.471333 41.023702, -76.47134 41.023674, -76.471381 41.023645, -76.471441 41.023629, -76.471504 41.023633, -76.471521 41.023601, -76.47155 41.023581, -76.471606 41.02356, -76.471649 41.023531, -76.471687 41.023522, -76.471727 41.023488, -76.471793 41.023449, -76.471907 41.023395, -76.472025 41.023358, -76.472068 41.02335, -76.472127 41.023317, -76.472167 41.023312, -76.472201 41.023286, -76.472388 41.023184, -76.472468 41.023157, -76.472557 41.023097, -76.472828 41.022943, -76.472876 41.02293, -76.472919 41.02291, -76.472934 41.022896, -76.473007 41.022854, -76.473113 41.02284, -76.473226 41.022784, -76.473263 41.022759, -76.473279 41.022735, -76.473286 41.022705, -76.473255 41.022716, -76.473269 41.022681, -76.473316 41.022613, -76.473361 41.022571, -76.473424 41.022479, -76.473428 41.022421, -76.47346 41.022355, -76.473494 41.022312, -76.473511 41.022271, -76.473498 41.022255, -76.473447 41.022261, -76.473468 41.022192, -76.47349 41.022154, -76.473509 41.022116, -76.473534 41.022063, -76.473545 41.022035, -76.473562 41.022022, -76.473586 41.021988, -76.47363 41.021905, -76.473679 41.021756, -76.473744 41.021603, -76.473792 41.021415, -76.473825 41.021337, -76.473893 41.021276, -76.473906 41.021255, -76.473908 41.021206, -76.473934 41.021173, -76.473942 41.021153, -76.473928 41.021131, -76.473918 41.021066, -76.473943 41.021013, -76.473971 41.020936, -76.473981 41.020831, -76.474014 41.020754, -76.474045 41.020695, -76.474104 41.020621, -76.474132 41.020604, -76.474223 41.020493, -76.474246 41.020451, -76.474263 41.020438, -76.474294 41.0204, -76.47435 41.020268, -76.474406 41.020211, -76.474424 41.020165, -76.474472 41.020092, -76.474515 41.020005, -76.474584 41.019892, -76.474605 41.019878, -76.474641 41.019832, -76.474658 41.019819, -76.474696 41.019763, -76.474703 41.019733, -76.47472 41.019698, -76.474748 41.019611, -76.474785 41.019579, -76.474846 41.019461, -76.474861 41.019447, -76.474877 41.019406, -76.47491 41.019356, -76.474944 41.019292, -76.474976 41.019264, -76.475004 41.019216, -76.475029 41.019153, -76.475033 41.019117, -76.475076 41.019091, -76.475093 41.019067, -76.475109 41.01903, -76.475123 41.019012, -76.475135 41.01898, -76.475149 41.018965, -76.475219 41.018853, -76.475269 41.01879, -76.475295 41.018709, -76.475367 41.018583, -76.475387 41.018567, -76.475413 41.018537, -76.475433 41.018491, -76.475464 41.018474, -76.47548 41.018443, -76.475496 41.018388, -76.47551 41.018365, -76.475539 41.018291, -76.475563 41.018228, -76.475574 41.01819, -76.475582 41.018064, -76.475579 41.018002, -76.475576 41.017949, -76.475565 41.017889, -76.47553 41.017784, -76.475513 41.017761, -76.475498 41.017643, -76.475454 41.017544, -76.475448 41.017489, -76.475452 41.017449, -76.475444 41.017382, -76.475447 41.017357, -76.475443 41.017317, -76.475408 41.017266, -76.475407 41.017216, -76.475414 41.017193, -76.475415 41.017098, -76.475359 41.016916, -76.475306 41.016806, -76.475271 41.016724, -76.475237 41.016612, -76.475126 41.016381, -76.475091 41.016327, -76.475041 41.016217, -76.475009 41.016164, -76.47497 41.016117, -76.474895 41.016009, -76.474813 41.01585, -76.474793 41.015797, -76.474762 41.015745, -76.474737 41.015692, -76.474702 41.015639, -76.474676 41.015586, -76.474528 41.015375, -76.474463 41.015264, -76.474381 41.015156, -76.47438 41.015093, -76.474369 41.015038, -76.47426 41.014879, -76.474142 41.014734, -76.474088 41.014695, -76.474054 41.014646, -76.473923 41.014508, -76.473874 41.014466, -76.473761 41.014318, -76.473712 41.014272, -76.473535 41.014021, -76.473506 41.013965, -76.473465 41.013914, -76.473398 41.013809, -76.473325 41.013712, -76.4732 41.013639, -76.47313 41.013617, -76.473013 41.013542, -76.472962 41.013493, -76.472851 41.013414, -76.472802 41.013371, -76.47268 41.013283, -76.4725 41.013169, -76.472367 41.013102, -76.472151 41.013016, -76.472 41.012964, -76.47193 41.012935, -76.471794 41.012907, -76.471725 41.012907, -76.471641 41.012906, -76.471423 41.012923, -76.471283 41.012927, -76.471193 41.012925, -76.471167 41.012931, -76.471139 41.012948, -76.471125 41.012969, -76.471135 41.012986, -76.471171 41.013017, -76.471214 41.013036, -76.47123 41.01305, -76.471232 41.013078, -76.471141 41.013067, -76.471101 41.013078, -76.471161 41.013102, -76.471289 41.013127, -76.471234 41.01317, -76.47122 41.013189, -76.471227 41.013207, -76.471263 41.013227, -76.471309 41.013243, -76.471339 41.013273, -76.471343 41.013307, -76.471311 41.013319, -76.471287 41.013317, -76.471244 41.013327, -76.471204 41.013326, -76.471158 41.013337, -76.471124 41.013336, -76.471087 41.013335, -76.471049 41.013348, -76.471024 41.01335, -76.471002 41.013341, -76.470934 41.013292, -76.470846 41.013221, -76.470622 41.013054, -76.470579 41.013013, -76.470546 41.012988, -76.470457 41.012945, -76.47044 41.012924, -76.470394 41.012907, -76.470329 41.012898, -76.470206 41.012804, -76.470117 41.012709, -76.47014 41.012699, -76.470133 41.012673, -76.470101 41.012673, -76.470024 41.012614, -76.469974 41.012593, -76.46991 41.01255, -76.469868 41.012513, -76.469856 41.012489, -76.469798 41.012431, -76.469789 41.012397, -76.469758 41.012363, -76.469754 41.012345, -76.469733 41.012327, -76.469696 41.012321, -76.469677 41.012309, -76.469643 41.012231, -76.469597 41.012173, -76.469584 41.012146, -76.469587 41.012094, -76.469646 41.012045, -76.469641 41.012027, -76.46964 41.011989, -76.469626 41.011964, -76.469629 41.011931, -76.469659 41.011873, -76.469668 41.011848, -76.469671 41.011787, -76.469691 41.011759, -76.469737 41.011638, -76.469741 41.011583, -76.46975 41.011551, -76.469805 41.011466, -76.469821 41.011413, -76.469838 41.0114, -76.469845 41.011355, -76.469888 41.011299, -76.469927 41.011264, -76.469967 41.011198, -76.47003 41.011161, -76.470045 41.011138, -76.470094 41.011092, -76.470113 41.011081, -76.470154 41.01108, -76.470184 41.011067, -76.470192 41.011037, -76.470231 41.011015, -76.47028 41.010964, -76.470299 41.010935, -76.470334 41.010899, -76.470352 41.010886, -76.470394 41.010878, -76.470436 41.010852, -76.470469 41.01084, -76.470506 41.010839, -76.470529 41.01083, -76.47057 41.010779, -76.470578 41.010757, -76.470587 41.010701, -76.470643 41.010624, -76.47067 41.010601, -76.470716 41.010485, -76.470715 41.010445, -76.470704 41.010386, -76.470694 41.010194, -76.47065 41.010084, -76.470625 41.010052, -76.470593 41.010031, -76.470563 41.009993, -76.470438 41.009866, -76.470355 41.009801, -76.470322 41.009761, -76.470267 41.009719, -76.470243 41.009714, -76.470135 41.009611, -76.470104 41.009602, -76.470074 41.009635, -76.470119 41.009665, -76.470135 41.00969, -76.470102 41.009689, -76.470085 41.009672, -76.470049 41.009655, -76.470011 41.009625, -76.470001 41.009599, -76.469975 41.009574, -76.469895 41.009523, -76.469735 41.009461, -76.469675 41.00944, -76.469621 41.009399, -76.469563 41.009362, -76.469493 41.009338, -76.469445 41.009294, -76.469337 41.009213, -76.469276 41.009175, -76.469145 41.009105, -76.469037 41.009028, -76.468978 41.008994, -76.468916 41.008952, -76.468849 41.008922, -76.468787 41.008889, -76.468744 41.008845, -76.468683 41.008806, -76.468226 41.008568, -76.468101 41.008511, -76.468016 41.008473, -76.467943 41.008447, -76.467479 41.00821, -76.467329 41.008157, -76.467258 41.008141, -76.467116 41.008081, -76.46706 41.008042, -76.466997 41.008012, -76.466848 41.00796, -76.466783 41.007933, -76.466431 41.007804, -76.466205 41.007748, -76.465912 41.007647, -76.465546 41.00754, -76.465602 41.007569, -76.46577 41.007688, -76.465819 41.007737, -76.46588 41.007771, -76.465937 41.007814, -76.465939 41.007873, -76.466059 41.007954, -76.466109 41.007995, -76.466063 41.007989, -76.466001 41.007952, -76.465942 41.007905, -76.465866 41.007876, -76.465743 41.007789, -76.465691 41.007743, -76.465625 41.007701, -76.465555 41.007682, -76.465484 41.007654, -76.465405 41.007634, -76.465342 41.007605, -76.465299 41.007554, -76.465349 41.00751, -76.465427 41.007516, -76.465504 41.007527, -76.465484 41.007513, -76.465221 41.007424, -76.465076 41.007387, -76.464944 41.007318, -76.464824 41.007235, -76.464682 41.007169, -76.464606 41.007143, -76.464583 41.007136, -76.464379 41.007116, -76.464325 41.007089, -76.46428 41.007089, -76.464235 41.007067, -76.464088 41.007028, -76.463973 41.00698, -76.463908 41.006953, -76.463773 41.00688, -76.463737 41.006871, -76.46352 41.006861, -76.463421 41.006847, -76.463153 41.006862, -76.463091 41.006874, -76.463048 41.006845, -76.462995 41.006824, -76.462862 41.006811, -76.46271 41.006789, -76.462544 41.006733, -76.462434 41.006668, -76.46233 41.0066, -76.462311 41.006581, -76.462301 41.006547, -76.462285 41.00653, -76.462233 41.006502, -76.462214 41.006474, -76.462178 41.006464, -76.461978 41.006335, -76.461849 41.006233, -76.461835 41.006218, -76.461836 41.006196, -76.461802 41.006189, -76.461738 41.006159, -76.461697 41.006119, -76.461688 41.00609, -76.461653 41.006081, -76.461535 41.005976, -76.461537 41.005954, -76.461515 41.005947, -76.461494 41.005919, -76.461414 41.005847, -76.461371 41.005782, -76.461141 41.00553, -76.461089 41.005485, -76.460983 41.005456, -76.460949 41.005453, -76.460926 41.005445, -76.460857 41.005371, -76.460651 41.005107, -76.460591 41.005038, -76.460567 41.005024, -76.460551 41.005004, -76.460552 41.004981, -76.460554 41.004954, -76.460532 41.004923, -76.460522 41.004906, -76.460515 41.004878, -76.460488 41.004864, -76.460484 41.004829, -76.460488 41.004772, -76.4605 41.004717, -76.460658 41.004457, -76.460677 41.004404, -76.460704 41.004351, -76.460742 41.004303, -76.460775 41.004239, -76.460885 41.004112, -76.461065 41.00393, -76.461215 41.003798, -76.461301 41.003694, -76.461463 41.00357, -76.461638 41.00338, -76.46172 41.003283, -76.461781 41.003239, -76.461844 41.003204, -76.461979 41.003139, -76.462095 41.003067, -76.462209 41.002979, -76.462253 41.002935, -76.462291 41.002881, -76.462441 41.002727, -76.4625 41.00265, -76.46261 41.002533, -76.46268 41.002427, -76.462766 41.002366, -76.462801 41.002317, -76.462949 41.002523, -76.46305 41.002664, -76.463078 41.002704, -76.463042 41.002743, -76.463017 41.002785, -76.462975 41.00283, -76.462938 41.002883, -76.462891 41.002937, -76.462845 41.002981, -76.462777 41.003032, -76.462659 41.003112, -76.462578 41.003156, -76.462359 41.003255, -76.462257 41.003323, -76.462184 41.003358, -76.462074 41.003401, -76.462012 41.003434, -76.461947 41.003483, -76.461905 41.003529, -76.461862 41.003587, -76.461787 41.003716, -76.461555 41.004023, -76.461495 41.00405, -76.461493 41.003975, -76.461499 41.003872, -76.461442 41.003924, -76.461345 41.004039, -76.461281 41.004089, -76.461235 41.004135, -76.461091 41.004298, -76.460958 41.004591, -76.460923 41.004704, -76.460922 41.004763, -76.460931 41.00479, -76.460943 41.004827, -76.460999 41.004958, -76.461169 41.005199, -76.461211 41.005247, -76.461449 41.005456, -76.461708 41.005695, -76.461781 41.005749, -76.461821 41.005784, -76.461891 41.005832, -76.462009 41.005927, -76.462054 41.005975, -76.462109 41.006014, -76.46239 41.006194, -76.462466 41.006218, -76.462541 41.006236, -76.462626 41.006248, -76.4627 41.006247, -76.462798 41.00624, -76.463159 41.006242, -76.463236 41.006249, -76.46333 41.006249, -76.463434 41.006261, -76.463613 41.006298, -76.463861 41.006369, -76.464022 41.006432, -76.464092 41.006471, -76.464189 41.006518, -76.4642 41.006523, -76.464363 41.006597, -76.464521 41.00668, -76.46474 41.006804, -76.464958 41.006901, -76.465189 41.007034, -76.46548 41.007131, -76.465656 41.007193, -76.465906 41.007269, -76.466112 41.007337, -76.466216 41.007379, -76.466347 41.007426, -76.466622 41.007512, -76.466837 41.007597, -76.467331 41.007771, -76.467676 41.007907, -76.467736 41.007945, -76.467939 41.008034, -76.468442 41.008313, -76.468595 41.008394, -76.46876 41.008355, -76.470712 41.007907, -76.471957 41.007801, -76.471718 41.007119, -76.471707 41.007103, -76.471631 41.007028, -76.471618 41.007005, -76.471622 41.006968, -76.47164 41.006938, -76.472215 41.006624, -76.473497 41.005977, -76.473895 41.00576, -76.474004 41.005687, -76.474052 41.005646, -76.474063 41.005612, -76.474058 41.005576, -76.474029 41.005536, -76.473517 41.005009, -76.472948 41.004384, -76.472732 41.004132, -76.472499 41.003814, -76.472114 41.00334, -76.472019 41.003246, -76.471939 41.003194, -76.471873 41.003167, -76.471799 41.003152, -76.471679 41.003144, -76.471549 41.003156, -76.471043 41.003234, -76.470168 41.003346, -76.469558 41.003431, -76.468088 41.003616, -76.467871 41.003638, -76.467244 41.003652, -76.467087 41.003647, -76.4668 41.003612, -76.466063 41.003509, -76.465498 41.00342, -76.465187 41.003379, -76.464989 41.003363, -76.465348 41.003161, -76.4656 41.003006, -76.466074 41.002766, -76.466213 41.002687, -76.466311 41.00261, -76.466383 41.002532, -76.466577 41.002278, -76.466838 41.001898, -76.466956 41.001764, -76.467083 41.001649, -76.467346 41.00143, -76.467686 41.001184, -76.467817 41.001089, -76.468232 41.00077, -76.468621 41.000454, -76.468886 41.000246, -76.469211 41.000037, -76.469343 40.999967, -76.46938 40.99995, -76.469531 40.999886, -76.469822 40.999783, -76.470052 40.99971, -76.470121 40.999682, -76.470331 40.999576, -76.470812 40.999301, -76.471165 40.999068, -76.471329 40.998971, -76.471516 40.998836, -76.471652 40.998725, -76.471756 40.998621, -76.471806 40.998554, -76.471853 40.998464, -76.471889 40.99834, -76.471917 40.998145, -76.471944 40.997971, -76.471957 40.997744, -76.471977 40.997572, -76.471997 40.997489, -76.472036 40.997403, -76.472123 40.99727, -76.472212 40.997155, -76.472428 40.996919, -76.473199 40.996168, -76.473509 40.99586, -76.473745 40.995599, -76.473849 40.995477, -76.473913 40.995394, -76.473952 40.995404, -76.47454 40.995499, -76.474716 40.995535, -76.474971 40.995608, -76.475166 40.995681, -76.475343 40.995762, -76.47551 40.995856, -76.475701 40.995978, -76.475887 40.996105, -76.476005 40.996179, -76.476141 40.996245, -76.476309 40.996308, -76.47646 40.996347, -76.476599 40.996368, -76.476787 40.996377, -76.477141 40.996366, -76.477315 40.996372, -76.477399 40.996385, -76.477696 40.996467, -76.477869 40.996525, -76.478598 40.996822, -76.478753 40.996897, -76.478893 40.996981, -76.479241 40.997248, -76.479424 40.997355, -76.479669 40.997484, -76.47982 40.997548, -76.479959 40.997591, -76.480149 40.997632, -76.480333 40.997657, -76.480518 40.99767, -76.480953 40.997674, -76.481094 40.99768, -76.481188 40.997684, -76.481304 40.997699, -76.481484 40.997736, -76.48238 40.997994, -76.482625 40.997575, -76.482814 40.997252, -76.482904 40.99712, -76.487643 40.998693, -76.487894 40.998786, -76.488214 40.998917, -76.488513 40.999057, -76.488811 40.999215, -76.489099 40.999392, -76.489363 40.999574, -76.489606 40.999762, -76.48982 40.999949, -76.490066 41.00019, -76.490258 41.000407, -76.490431 41.000635, -76.490594 41.000881, -76.490731 41.00112, -76.490853 41.001368, -76.49095 41.001604, -76.491017 41.001807, -76.491079 41.002056, -76.49098 41.002044, -76.490774 41.002018, -76.490689 41.001992, -76.490619 41.001957, -76.490542 41.0019, -76.490175 41.001488, -76.489977 41.001298, -76.489783 41.001136, -76.489597 41.001002, -76.489381 41.000861, -76.489044 41.000654, -76.488937 41.000576, -76.488895 41.00054, -76.489006 41.000846, -76.489144 41.001188, -76.489242 41.001397, -76.489503 41.001839, -76.489585 41.00202, -76.489658 41.002244, -76.489815 41.002795, -76.489916 41.003085, -76.489979 41.003226, -76.488662 41.006095, -76.488648 41.006148, -76.488578 41.006395, -76.488483 41.006506, -76.488342 41.006644, -76.488167 41.006733, -76.487976 41.006767, -76.487771 41.006774, -76.487491 41.006787, -76.487367 41.00679, -76.487155 41.006813, -76.487023 41.006854, -76.486845 41.006947, -76.486745 41.007025, -76.486648 41.007133, -76.486573 41.007244, -76.486565 41.007331, -76.486571 41.007349, -76.486659 41.007382, -76.486717 41.0074, -76.486779 41.007422, -76.486844 41.007449, -76.486911 41.007477, -76.486982 41.007505, -76.487054 41.007535, -76.487126 41.007567, -76.48719 41.007602, -76.487247 41.007641, -76.4873 41.007683, -76.487343 41.007729, -76.487395 41.007779, -76.487439 41.007829, -76.487477 41.007884, -76.487509 41.00794, -76.487535 41.007997, -76.487555 41.008053, -76.487571 41.008111, -76.487573 41.008131, -76.487579 41.008171, -76.48758 41.008232, -76.487574 41.008296, -76.487561 41.00836, -76.487542 41.008425, -76.48752 41.00849, -76.487495 41.008555, -76.487469 41.00862, -76.487442 41.008685, -76.487414 41.008752, -76.487387 41.008819, -76.487359 41.008886, -76.487331 41.008952, -76.487274 41.009083, -76.487247 41.009149, -76.48722 41.009213, -76.487193 41.009277, -76.487167 41.009341, -76.48714 41.009405, -76.487113 41.009468, -76.487086 41.009532, -76.487058 41.009595, -76.487032 41.009657, -76.487005 41.009719, -76.486979 41.009783, -76.486953 41.009847, -76.486926 41.009913, -76.486899 41.00998, -76.486871 41.010048, -76.486842 41.010116, -76.486813 41.010183, -76.486783 41.010248, -76.486754 41.010312, -76.486701 41.010429, -76.48666 41.010524, -76.486635 41.010581, -76.486607 41.010644, -76.486592 41.01068, -76.486571 41.010729, -76.486525 41.010842, -76.4865 41.010906, -76.486474 41.01097, -76.486448 41.011036, -76.486442 41.01105, -76.486421 41.011104, -76.486394 41.011172, -76.486364 41.01124, -76.486334 41.011308, -76.486303 41.011374, -76.486239 41.011495, -76.486205 41.011553, -76.486168 41.01161, -76.486129 41.011667, -76.486092 41.011724, -76.486024 41.01183, -76.485981 41.011894, -76.48595 41.011943, -76.485986 41.011964, -76.4861 41.012004, -76.486191 41.012033, -76.486288 41.012066, -76.486376 41.012103, -76.48645 41.012143, -76.486505 41.012182, -76.486555 41.012227, -76.486593 41.012271, -76.486646 41.012342, -76.487139 41.012047, -76.487541 41.011815, -76.487802 41.011578, -76.488044 41.011377, -76.488419 41.011124, -76.488837 41.010932, -76.489129 41.01082, -76.489265 41.010962, -76.489657 41.011753, -76.48983 41.012116, -76.490171 41.012808, -76.490283 41.01304, -76.490306 41.013085, -76.490336 41.013147, -76.490574 41.013636, -76.490736 41.013987, -76.490843 41.014263, -76.490905 41.014452, -76.490944 41.014582, -76.491004 41.014783, -76.491041 41.014924, -76.491077 41.01511, -76.491086 41.015182, -76.491118 41.015414, -76.491126 41.015787, -76.491101 41.015994, -76.491069 41.016255, -76.49097 41.016791, -76.490847 41.017152, -76.490649 41.017571, -76.490553 41.017748, -76.490272 41.017636, -76.49005 41.017401, -76.489819 41.017112, -76.48979 41.017049, -76.489841 41.016924, -76.489852 41.016831, -76.489847 41.016769, -76.489825 41.016701, -76.489636 41.016327, -76.489557 41.016223, -76.48949 41.016165, -76.489381 41.01609, -76.489281 41.016043, -76.489038 41.015958, -76.488787 41.015887, -76.488293 41.015734, -76.487974 41.015671, -76.487832 41.015622, -76.487735 41.015596, -76.487617 41.015588, -76.487501 41.015591, -76.487355 41.015607, -76.487251 41.015641, -76.487136 41.015691, -76.486896 41.015852, -76.486808 41.015887, -76.486699 41.015911, -76.486425 41.015981, -76.485905 41.016082, -76.485724 41.016124, -76.485635 41.016133, -76.485505 41.016131, -76.485353 41.016118, -76.485029 41.016073, -76.484878 41.016042, -76.484792 41.016033, -76.48466 41.016033, -76.484559 41.016046, -76.484469 41.016074, -76.484387 41.016115, -76.484292 41.016182, -76.484251 41.016221, -76.484085 41.016407, -76.483823 41.016751, -76.48379 41.016809, -76.483772 41.016884, -76.483775 41.016996, -76.483791 41.017093, -76.48385 41.01729, -76.483907 41.017513, -76.483989 41.017775, -76.484186 41.018282, -76.484228 41.018355, -76.484287 41.018438, -76.484335 41.018484, -76.484389 41.018521, -76.484484 41.018571, -76.484567 41.018601, -76.484689 41.018629, -76.484851 41.018651, -76.485813 41.018732, -76.486421 41.018792, -76.486668 41.018803, -76.486862 41.018798, -76.487087 41.018765, -76.488306 41.018507, -76.4886 41.01843, -76.488935 41.018605, -76.48895 41.018823, -76.489202 41.01895, -76.489406 41.018994, -76.489726 41.019077, -76.489222 41.019818, -76.488983 41.020177, -76.489265 41.019843, -76.489572 41.019406, -76.490006 41.018773, -76.490553 41.017958, -76.490823 41.017453, -76.490872 41.017336, -76.490944 41.017354, -76.49121 41.017416, -76.491307 41.017441, -76.491412 41.017463, -76.491514 41.017487, -76.491615 41.017515, -76.491933 41.017589, -76.492035 41.017613, -76.492139 41.017632, -76.492157 41.017586, -76.492176 41.017543, -76.492211 41.017464, -76.492239 41.017413, -76.492266 41.01737, -76.492305 41.017319, -76.492443 41.017134, -76.49248 41.017064, -76.492514 41.01699, -76.492609 41.016756, -76.49264 41.016675, -76.492671 41.016595, -76.492702 41.016518, -76.492733 41.016439, -76.492763 41.01636, -76.492793 41.016283, -76.492821 41.016206, -76.49285 41.01613, -76.492878 41.016054, -76.492905 41.015979, -76.492934 41.015903, -76.492986 41.015759, -76.493024 41.015634, -76.493036 41.015582, -76.493064 41.015495, -76.49313 41.015444, -76.494734 41.015736, -76.495491 41.015874, -76.496508 41.016064, -76.496765 41.016118, -76.497149 41.016229, -76.497252 41.016268, -76.497546 41.016406, -76.497755 41.016516, -76.497789 41.016547, -76.497996 41.016704, -76.498199 41.016869, -76.498355 41.017029, -76.498609 41.01726, -76.499469 41.018063, -76.499753 41.018333, -76.500191 41.018739, -76.500338 41.018876, -76.500587 41.019133, -76.500638 41.019202, -76.500422 41.019359, -76.4999 41.019702, -76.499659 41.019854, -76.499416 41.019982, -76.49926 41.020065, -76.498814 41.020299, -76.497923 41.02077, -76.497373 41.021081, -76.496795 41.021432, -76.496473 41.021623, -76.496298 41.021744, -76.496269 41.021764, -76.496132 41.021894, -76.496028 41.022004, -76.495769 41.022281, -76.495303 41.02281, -76.495042 41.023183, -76.494968 41.023274, -76.49492 41.023334, -76.494844 41.023409, -76.494688 41.023533, -76.494552 41.023626, -76.49418 41.02385, -76.493971 41.023958, -76.493888 41.023995, -76.493893 41.02406, -76.494014 41.024555, -76.494577 41.026548, -76.494623 41.026669, -76.494702 41.026811, -76.494834 41.02678, -76.494936 41.02678, -76.495002 41.02678, -76.495067 41.026802, -76.495118 41.026831, -76.495176 41.02686, -76.495198 41.026867, -76.49534 41.026879, -76.495338 41.026832, -76.495403 41.026797, -76.495373 41.026713, -76.495325 41.026651, -76.495327 41.026604, -76.495291 41.026562, -76.495297 41.026516, -76.495315 41.02647, -76.495316 41.026423, -76.495286 41.02624, -76.495257 41.026163, -76.495269 41.025912, -76.495288 41.025888, -76.495316 41.025868, -76.495335 41.02584, -76.495345 41.025738, -76.495353 41.025714, -76.495375 41.025684, -76.495405 41.025602, -76.495415 41.025531, -76.495468 41.025412, -76.495473 41.025385, -76.49549 41.025352, -76.495561 41.025326, -76.495588 41.025294, -76.495677 41.025162, -76.495743 41.025098, -76.495792 41.025098, -76.49582 41.025102, -76.495859 41.025101, -76.495879 41.025083, -76.495914 41.025021, -76.495941 41.024906, -76.495959 41.024882, -76.496011 41.024844, -76.496249 41.024689, -76.496299 41.024636, -76.496329 41.024592, -76.496456 41.024435, -76.496594 41.024353, -76.496712 41.02425, -76.496809 41.024153, -76.496883 41.024102, -76.496968 41.024027, -76.497051 41.023941, -76.497191 41.023819, -76.497245 41.023763, -76.497326 41.023694, -76.4974 41.02362, -76.497501 41.023559, -76.497555 41.023492, -76.497765 41.023298, -76.49787 41.023242, -76.497905 41.023162, -76.498016 41.023093, -76.498069 41.023045, -76.498141 41.023, -76.498276 41.022925, -76.498318 41.022895, -76.498524 41.02281, -76.498585 41.022763, -76.498848 41.022592, -76.498971 41.022484, -76.499046 41.022438, -76.499052 41.022393, -76.499086 41.022359, -76.499138 41.022324, -76.499204 41.022235, -76.499272 41.022166, -76.499382 41.022069, -76.499477 41.021999, -76.499627 41.021913, -76.499776 41.021812, -76.499875 41.021762, -76.500055 41.021717, -76.500122 41.021671, -76.500166 41.021647, -76.50019 41.021643, -76.500271 41.02161, -76.500363 41.02159, -76.500473 41.021547, -76.500562 41.021493, -76.500673 41.021413, -76.501015 41.021198, -76.501101 41.02115, -76.501223 41.02109, -76.501364 41.02104, -76.501458 41.021011, -76.501644 41.020935, -76.501847 41.021272, -76.502139 41.021743, -76.502435 41.022152, -76.502588 41.022312, -76.502964 41.022759, -76.503201 41.023023, -76.503514 41.022915, -76.503947 41.022748, -76.504213 41.022639, -76.504479 41.022534, -76.504875 41.022377, -76.505077 41.022309, -76.505316 41.022238, -76.50541 41.022218, -76.505551 41.022208, -76.505753 41.022217, -76.505924 41.022239, -76.506117 41.02228, -76.507347 41.022607, -76.508091 41.022816, -76.50854 41.022931, -76.508916 41.022991, -76.509788 41.023099, -76.510015 41.023147, -76.510159 41.023187, -76.510272 41.023213, -76.510503 41.023281, -76.510645 41.023329, -76.510765 41.023323, -76.510904 41.023311, -76.51096 41.0233, -76.511013 41.023279, -76.51097 41.023235, -76.510823 41.023134, -76.510711 41.023051, -76.510581 41.022965, -76.510408 41.02284, -76.510271 41.022722, -76.51017 41.022604, -76.510107 41.022519, -76.510025 41.022467, -76.509928 41.022443, -76.509603 41.022415, -76.509473 41.02239, -76.509238 41.022369, -76.508293 41.022343, -76.508093 41.022299, -76.507897 41.022262, -76.507753 41.02222, -76.507563 41.022153, -76.507359 41.022065, -76.50725 41.022036, -76.507117 41.022023, -76.50697 41.02198, -76.506861 41.021963, -76.50678 41.021939, -76.506722 41.021901, -76.506692 41.021849, -76.506656 41.02162, -76.506632 41.021552, -76.506547 41.021436, -76.506469 41.021386, -76.506362 41.021307, -76.506276 41.021214, -76.506181 41.021085, -76.506092 41.020981, -76.506015 41.02094, -76.505845 41.020865, -76.505838 41.020847, -76.505809 41.020819, -76.505744 41.02072, -76.505657 41.020655, -76.505448 41.020569, -76.505265 41.020384, -76.505247 41.019968, -76.505252 41.019781, -76.505272 41.019641, -76.505358 41.019467, -76.505492 41.019344, -76.505656 41.019278, -76.505819 41.019221, -76.505956 41.019159, -76.506028 41.019081, -76.506032 41.018985, -76.506028 41.01874, -76.506031 41.018584, -76.506056 41.018401, -76.506055 41.018306, -76.505985 41.018197, -76.505873 41.018088, -76.505765 41.01799, -76.505559 41.01777, -76.505477 41.017748, -76.505376 41.017754, -76.505327 41.017768, -76.505249 41.017773, -76.505106 41.017814, -76.505024 41.017845, -76.50489 41.017864, -76.504712 41.017823, -76.504611 41.017758, -76.504356 41.017688, -76.504253 41.017635, -76.504177 41.017559, -76.504031 41.017369, -76.504013 41.017337, -76.504046 41.017325, -76.504285 41.017229, -76.504407 41.017166, -76.504534 41.017073, -76.504612 41.017149, -76.50468 41.017227, -76.504777 41.017366, -76.504857 41.01745, -76.504927 41.017505, -76.505031 41.017554, -76.505105 41.017582, -76.505201 41.0176, -76.505377 41.017612, -76.505539 41.017614, -76.505903 41.017599, -76.506138 41.017564, -76.506432 41.017535, -76.506538 41.017517, -76.506652 41.017483, -76.506828 41.017431, -76.506927 41.01741, -76.507012 41.017407, -76.507099 41.017414, -76.507174 41.017432, -76.507551 41.017588, -76.50806 41.017791, -76.508169 41.017827, -76.508287 41.017853, -76.508393 41.017865, -76.508482 41.017865, -76.508667 41.017849, -76.50886 41.017824, -76.509538 41.01771, -76.509941 41.017652, -76.510602 41.017545, -76.510833 41.01751, -76.51158 41.017372, -76.512296 41.017248, -76.513149 41.017093, -76.513725 41.01698, -76.514257 41.016862, -76.514446 41.016826, -76.514662 41.016787, -76.515134 41.016691, -76.515266 41.016648, -76.515532 41.016515, -76.515581 41.016491, -76.516162 41.016224, -76.516941 41.015882, -76.517194 41.015794, -76.517241 41.015771, -76.517291 41.01573, -76.517316 41.015691, -76.517319 41.015672, -76.51614 41.015488, -76.511462 41.01476, -76.510312 41.014581, -76.508915 41.01027, -76.508793 41.009881, -76.50673 41.010082, -76.506547 41.010095, -76.506389 41.010114, -76.506431 41.009352, -76.506446 41.009119, -76.506483 41.008559, -76.506495 41.00827, -76.506353 41.008262, -76.506206 41.008242, -76.505904 41.008192, -76.505721 41.008152, -76.5055 41.008088, -76.504597 41.007795, -76.504392 41.007737, -76.503938 41.007628, -76.503765 41.007597, -76.50328 41.007547, -76.502458 41.007494, -76.50187 41.007451, -76.50164 41.007423, -76.501611 41.00742, -76.50144 41.007388, -76.501515 41.007248, -76.501592 41.007131, -76.501927 41.006722, -76.502312 41.006294, -76.502402 41.006201, -76.502644 41.005981, -76.502719 41.005923, -76.503005 41.005743, -76.503428 41.005507, -76.503499 41.005474, -76.503544 41.00543, -76.503573 41.00538, -76.503592 41.005313, -76.503599 41.005232, -76.503586 41.004983, -76.50359 41.004916, -76.503607 41.004853, -76.503696 41.004715, -76.50379 41.00454, -76.503846 41.004418, -76.503875 41.00433, -76.503891 41.004255, -76.503935 41.004127, -76.503945 41.004108, -76.501477 41.004903, -76.499582 41.00506, -76.496752 41.005397, -76.496589 41.005219, -76.496528 41.005166, -76.496392 41.005084, -76.496271 41.005026, -76.496186 41.004999, -76.495952 41.004949, -76.495472 41.004875, -76.49524 41.004832, -76.495122 41.004816, -76.494872 41.004792, -76.494746 41.004791, -76.494528 41.004809, -76.494152 41.004858, -76.494066 41.004866, -76.493994 41.004872, -76.493964 41.004841, -76.49394 41.004772, -76.49394 41.004739, -76.493942 41.004252, -76.493947 41.004158, -76.493959 41.003826, -76.49395 41.003628, -76.493775 41.003213, -76.493705 41.002887, -76.49365 41.002767, -76.493513 41.002571, -76.493432 41.002433, -76.493382 41.002255, -76.493353 41.00217, -76.49326 41.002044, -76.493181 41.001972, -76.493117 41.001907, -76.49292 41.001755, -76.492599 41.001596, -76.492418 41.001379, -76.492373 41.001331, -76.492318 41.001284, -76.492077 41.001119, -76.491983 41.001004, -76.491767 41.000791, -76.491719 41.000739, -76.491611 41.000644, -76.491534 41.000593, -76.491178 41.00033, -76.490993 41.000172, -76.490714 40.999892, -76.490629 40.999788, -76.490617 40.999733, -76.490591 40.999675, -76.49054 40.999626, -76.490304 40.999435, -76.490185 40.99935, -76.490123 40.999313, -76.490066 40.999265, -76.489926 40.999091, -76.489849 40.998984, -76.489686 40.998624, -76.48965 40.998467, -76.4896 40.99821, -76.489579 40.998148, -76.489499 40.998012, -76.489378 40.997832, -76.489339 40.997782, -76.489236 40.997677, -76.489117 40.99752, -76.489066 40.997465, -76.489008 40.997414, -76.488878 40.997317, -76.488752 40.997233, -76.488675 40.997195, -76.488517 40.997156, -76.488431 40.997122, -76.488362 40.997088, -76.488304 40.997054, -76.488188 40.996966, -76.488109 40.996801, -76.488073 40.99675, -76.488045 40.996685, -76.487943 40.996476, -76.487889 40.996421, -76.487823 40.996381, -76.487751 40.996347, -76.48745 40.996259, -76.487309 40.996222, -76.487152 40.996159, -76.487071 40.996137, -76.486839 40.996086, -76.486682 40.996045, -76.486464 40.995956, -76.486405 40.99592, -76.486337 40.995894, -76.486254 40.995878, -76.486084 40.995833, -76.486032 40.995815, -76.485962 40.995788, -76.48581 40.99574, -76.485733 40.99571, -76.485601 40.995618, -76.485548 40.995576, -76.485486 40.995535, -76.485437 40.99549, -76.48537 40.995462, -76.48521 40.995471, -76.484686 40.995522, -76.48441 40.995545, -76.484227 40.995569, -76.484047 40.995577, -76.48397 40.995584, -76.483441 40.995672, -76.483274 40.995708, -76.483104 40.995753, -76.482944 40.995809, -76.482635 40.995868, -76.482545 40.995891, -76.482465 40.995924, -76.482184 40.996071, -76.48212 40.99611, -76.48198 40.996184, -76.481729 40.99636, -76.481605 40.996431, -76.481447 40.996507, -76.481386 40.996552, -76.481331 40.996599, -76.481267 40.996576, -76.481214 40.996558, -76.480772 40.996401, -76.480474 40.996285, -76.480099 40.99612, -76.479887 40.996016, -76.480109 40.995646, -76.480681 40.995152, -76.481442 40.994448, -76.482528 40.995142, -76.482636 40.995211, -76.4827 40.995226, -76.482757 40.995223, -76.482814 40.995207, -76.482857 40.995181, -76.482949 40.99511, -76.483059 40.994994, -76.48324 40.994762, -76.483284 40.994688, -76.483331 40.994559, -76.48337 40.994393, -76.483408 40.994124, -76.483438 40.993788, -76.483461 40.993664, -76.483508 40.993528, -76.483592 40.993364, -76.483752 40.993108, -76.484241 40.99244, -76.484306 40.992326, -76.484341 40.992229, -76.484354 40.992122, -76.484348 40.992021, -76.484325 40.991915, -76.48424 40.991912, -76.484136 40.991924, -76.484063 40.991943, -76.483934 40.992004, -76.483572 40.992197, -76.483416 40.992299, -76.483293 40.992398, -76.4832 40.992463, -76.4831 40.992516, -76.483014 40.992544, -76.48283 40.992571, -76.482612 40.99258, -76.481948 40.992625, -76.481721 40.992631, -76.481573 40.992624, -76.481308 40.99259, -76.480453 40.992445, -76.480307 40.992429, -76.480195 40.992424, -76.480061 40.992434, -76.479608 40.992538, -76.479253 40.992636, -76.479014 40.992721, -76.478835 40.99278, -76.478666 40.992675, -76.478674 40.992474, -76.478662 40.992286, -76.478634 40.992127, -76.478576 40.991915, -76.478022 40.990266, -76.477949 40.989983, -76.477845 40.989212, -76.478732 40.988385, -76.478782 40.988338, -76.478704 40.988277, -76.478589 40.988185, -76.480525 40.986178, -76.481301 40.985383, -76.481757 40.984944, -76.482094 40.984641, -76.482461 40.984361, -76.482784 40.984099, -76.482684 40.984217, -76.482519 40.984384, -76.482591 40.984341, -76.482879 40.984167, -76.483132 40.98402, -76.483687 40.983726, -76.484371 40.983387, -76.485623 40.982766, -76.486075 40.982552, -76.486421 40.982403, -76.486541 40.982357, -76.486734 40.982283, -76.48689 40.982305, -76.487366 40.982351, -76.487564 40.982362, -76.487741 40.982358, -76.48792 40.982329, -76.488546 40.98219, -76.4889 40.982126, -76.48923 40.98206, -76.489631 40.981976, -76.489949 40.98191, -76.490477 40.981794, -76.491147 40.981655, -76.491293 40.981621, -76.491771 40.981522, -76.492093 40.981453, -76.492978 40.981244, -76.493441 40.98115, -76.49319 40.980502, -76.495016 40.98001, -76.495585 40.979869, -76.495705 40.979839, -76.496022 40.979775, -76.496404 40.979715, -76.497007 40.979638, -76.497315 40.979605, -76.497485 40.979587, -76.498199 40.979512, -76.498736 40.979446, -76.49906 40.979411, -76.499562 40.979357, -76.499925 40.979307, -76.50011 40.979274, -76.500365 40.979215, -76.500877 40.979069, -76.501193 40.978976, -76.501706 40.978815, -76.50294 40.97844, -76.503502 40.978273, -76.50387 40.978159, -76.504196 40.978065, -76.504783 40.977881, -76.505354 40.97772, -76.50555 40.977673, -76.506001 40.977581, -76.506267 40.977533, -76.506378 40.977516, -76.506739 40.977461, -76.50667 40.977276, -76.506617 40.977111, -76.506403 40.976537, -76.510193 40.97574, -76.513008 40.975148, -76.514444 40.974846, -76.515 40.974733, -76.517636 40.974171, -76.518242 40.974052, -76.52156 40.973352, -76.522143 40.974387, -76.522892 40.974214, -76.52331 40.974128, -76.523606 40.974075, -76.52463 40.973907, -76.525695 40.973732, -76.527218 40.973473, -76.527797 40.97337, -76.528355 40.973257, -76.529019 40.973084, -76.529005 40.973069, -76.528881 40.972888, -76.528635 40.972503, -76.528415 40.972158, -76.528366 40.972082, -76.528315 40.97198, -76.52829 40.97193, -76.529206 40.971731, -76.530304 40.971494, -76.532023 40.971136, -76.53216 40.971104, -76.533238 40.970881, -76.5347 40.970567, -76.535484 40.970408, -76.536221 40.970253, -76.539721 40.969513, -76.539865 40.969483, -76.540365 40.969372, -76.541396 40.969159, -76.54463 40.968474, -76.545074 40.968372, -76.545438 40.968288, -76.545584 40.968252, -76.546569 40.96801, -76.548643 40.967474, -76.550511 40.966993, -76.551525 40.966732, -76.551648 40.9667, -76.553303 40.966278, -76.554647 40.965941, -76.557518 40.965197, -76.558607 40.964919, -76.559246 40.964766, -76.55982 40.964628, -76.560024 40.964579, -76.560344 40.964511, -76.560846 40.964404, -76.561503 40.964271, -76.562995 40.964004, -76.563478 40.963918, -76.564085 40.963803, -76.564134 40.963925, -76.564168 40.964039, -76.564158 40.964136, -76.564122 40.964349, -76.564101 40.964438, -76.564098 40.964496, -76.564135 40.96455, -76.564223 40.964629, -76.564243 40.96467, -76.564251 40.964701, -76.564245 40.964737, -76.564252 40.964755, -76.564275 40.96476, -76.56432 40.964757, -76.564341 40.96473, -76.564364 40.964721, -76.564449 40.964705, -76.564482 40.964694, -76.564503 40.964681, -76.564609 40.964662, -76.564633 40.964666, -76.564782 40.964748, -76.564873 40.964815, -76.564911 40.964861, -76.56494 40.964881, -76.565075 40.964888, -76.565177 40.964917, -76.565242 40.964957, -76.565269 40.964993, -76.565275 40.965034, -76.565268 40.965087, -76.565271 40.965122, -76.565287 40.965149, -76.565334 40.965186, -76.565392 40.965242, -76.565437 40.965279, -76.565474 40.965292, -76.565545 40.965286, -76.565583 40.965355, -76.565845 40.965833, -76.565968 40.966073, -76.566062 40.966229, -76.566115 40.966288, -76.566196 40.966334, -76.566296 40.966359, -76.566367 40.966361, -76.566533 40.966358, -76.566765 40.966328, -76.566764 40.966376, -76.56717 40.967686, -76.567395 40.968408, -76.567523 40.968393, -76.567832 40.96834, -76.56806 40.968289, -76.568607 40.968144, -76.569308 40.967961, -76.569618 40.967891, -76.569875 40.96786, -76.570059 40.96785, -76.571203 40.967828, -76.571857 40.967818, -76.572877 40.967788, -76.572958 40.967786, -76.573244 40.967768, -76.573485 40.967751, -76.57378 40.96773, -76.574127 40.96771, -76.574592 40.967703, -76.575039 40.967711, -76.576302 40.967737, -76.576785 40.967747, -76.577462 40.967745, -76.57806 40.967723, -76.578311 40.96771, -76.578329 40.967823, -76.578345 40.967873, -76.578529 40.968163, -76.579113 40.969214, -76.57986 40.970622, -76.58019 40.971216, -76.580276 40.971395, -76.580384 40.971695, -76.580406 40.971782, -76.580416 40.971873, -76.580418 40.971888, -76.580411 40.972085, -76.58038 40.972271, -76.580317 40.972563, -76.58017 40.973184, -76.579931 40.973135, -76.579492 40.973073, -76.57936 40.973058, -76.579191 40.973048, -76.57913 40.973049, -76.57901 40.973063, -76.578885 40.973094, -76.578763 40.973141, -76.578659 40.973196, -76.57858 40.973255, -76.578503 40.973331, -76.578461 40.973398, -76.578387 40.973587, -76.578298 40.973964, -76.578267 40.974102, -76.578193 40.974381, -76.578132 40.974643, -76.578057 40.974904, -76.578011 40.975092, -76.577992 40.97517, -76.57798 40.975245, -76.577974 40.975423, -76.577999 40.975583, -76.578032 40.975717, -76.578166 40.975985, -76.57821 40.976084, -76.577954 40.976136, -76.57739 40.976274, -76.57725 40.976308, -76.576879 40.976409, -76.576392 40.976548, -76.576162 40.976627, -76.576071 40.976668, -76.575893 40.976733, -76.57565 40.976852, -76.575494 40.976952, -76.57533 40.977093, -76.575217 40.977209, -76.574991 40.977526, -76.57495 40.977591, -76.574904 40.977681, -76.574886 40.977727, -76.574862 40.977786, -76.574757 40.978147, -76.574716 40.978252, -76.574675 40.978378, -76.574637 40.978445, -76.574511 40.978516, -76.574267 40.978599, -76.574146 40.978634, -76.574024 40.978678, -76.573646 40.9788, -76.573081 40.979005, -76.572831 40.979089, -76.572611 40.979158, -76.572486 40.979182, -76.572096 40.979216, -76.572006 40.979236, -76.571721 40.97929, -76.571587 40.979322, -76.57138 40.979376, -76.570884 40.979491, -76.570753 40.979524, -76.572313 40.984666, -76.572411 40.985021, -76.582983 40.984458, -76.582824 40.984173, -76.582163 40.983027, -76.581919 40.982627, -76.581857 40.982525, -76.581754 40.982371, -76.581624 40.982212, -76.581512 40.982084, -76.581373 40.98195, -76.58119 40.981778, -76.581 40.981631, -76.580884 40.981552, -76.580774 40.981465, -76.580697 40.981381, -76.580633 40.981303, -76.580546 40.981158, -76.580469 40.98101, -76.580441 40.980956, -76.580215 40.980455, -76.580147 40.980254, -76.580108 40.979948, -76.580087 40.979724, -76.58006 40.97937, -76.580054 40.979279, -76.580038 40.978686, -76.580029 40.977726, -76.580019 40.977473, -76.579986 40.977148, -76.579958 40.976998, -76.579882 40.976671, -76.579831 40.976494, -76.579663 40.976012, -76.579633 40.975903, -76.579908 40.975862, -76.580058 40.975841, -76.580316 40.975797, -76.580438 40.975781, -76.580481 40.975775, -76.580931 40.975706, -76.581497 40.975648, -76.581824 40.975629, -76.582204 40.975623, -76.582594 40.975623, -76.582771 40.975628, -76.583446 40.97564, -76.583877 40.975647, -76.584251 40.975652, -76.584415 40.975653, -76.584693 40.975663, -76.585063 40.975668, -76.585549 40.975678, -76.586112 40.975689, -76.5864 40.975691, -76.586996 40.975709, -76.587814 40.975728, -76.588582 40.975717, -76.588779 40.975701, -76.588826 40.975697, -76.588916 40.975687, -76.589451 40.975625, -76.589825 40.97557, -76.590444 40.975488, -76.590753 40.975462, -76.590802 40.97546, -76.590854 40.975457, -76.591251 40.975457, -76.591382 40.975459, -76.591659 40.975465, -76.591942 40.975479, -76.59212 40.975482, -76.592466 40.975475, -76.592564 40.975469, -76.592702 40.97545, -76.592814 40.975428, -76.59298 40.975386, -76.593133 40.975328, -76.593253 40.975262, -76.593377 40.975181, -76.593565 40.975034, -76.593729 40.974886, -76.594024 40.974603, -76.594175 40.974483, -76.594306 40.97439, -76.594469 40.974298, -76.59459 40.974237, -76.594701 40.974182, -76.595277 40.973982, -76.595432 40.973937, -76.595896 40.973801, -76.596226 40.973699, -76.59642 40.973645, -76.596533 40.973613, -76.596605 40.973596, -76.596781 40.973554, -76.596792 40.973687, -76.596782 40.974079, -76.596781 40.9744, -76.596786 40.974433, -76.596812 40.974514, -76.596851 40.974583, -76.596894 40.974634, -76.596961 40.974685, -76.597029 40.974717, -76.597087 40.974739, -76.597161 40.974753, -76.597306 40.974751, -76.597432 40.974733, -76.598766 40.974451, -76.599264 40.974352, -76.59969 40.974252, -76.599845 40.974206, -76.599951 40.97415, -76.600039 40.974054, -76.600082 40.973946, -76.600134 40.973448, -76.600187 40.972831, -76.600684 40.972669, -76.600898 40.972591, -76.601141 40.972486, -76.601238 40.972439, -76.601329 40.972381, -76.601508 40.972246, -76.601601 40.97217, -76.601903 40.971916, -76.602221 40.971636, -76.6023 40.971567, -76.602455 40.971459, -76.602527 40.971416, -76.602597 40.972115, -76.602633 40.972571, -76.602646 40.972742, -76.602677 40.972923, -76.602701 40.973122, -76.602721 40.973572, -76.60272 40.97376, -76.602695 40.973906, -76.60265 40.974117, -76.602589 40.974346, -76.602563 40.974466, -76.602551 40.974621, -76.602554 40.974697, -76.602897 40.974435, -76.602882 40.974423, -76.602886 40.974405, -76.602901 40.974385, -76.602975 40.974352, -76.602988 40.974336, -76.603024 40.97427, -76.60306 40.974247, -76.603103 40.974205, -76.603123 40.974168, -76.603154 40.974131, -76.603191 40.974097, -76.603193 40.974039, -76.60313 40.974008, -76.603124 40.97399, -76.603132 40.973961, -76.603147 40.973947, -76.603199 40.973916, -76.603277 40.973889, -76.603324 40.973865, -76.603346 40.973844, -76.603349 40.973813, -76.603361 40.973774, -76.603378 40.973743, -76.60338 40.973713, -76.603375 40.973695, -76.603336 40.973691, -76.603297 40.973671, -76.603289 40.973624, -76.603298 40.973607, -76.60333 40.973587, -76.603368 40.973576, -76.6034 40.973556, -76.603428 40.973505, -76.603439 40.973458, -76.603452 40.973428, -76.603467 40.973341, -76.603514 40.973254, -76.603533 40.973227, -76.603557 40.973205, -76.60358 40.973172, -76.603579 40.973085, -76.603609 40.973048, -76.603629 40.973032, -76.603655 40.973019, -76.603682 40.972997, -76.603709 40.972956, -76.603746 40.972872, -76.603766 40.972769, -76.603806 40.972613, -76.604261 40.972472, -76.604405 40.972446, -76.606258 40.972232, -76.606614 40.972186, -76.606738 40.97216, -76.606842 40.972133, -76.606915 40.972105, -76.607026 40.972054, -76.607216 40.971952, -76.607355 40.971885, -76.607713 40.971686, -76.608157 40.972184, -76.608414 40.972066, -76.60891 40.971809, -76.608922 40.971803, -76.60915 40.97168, -76.60937 40.971561, -76.60951 40.971494, -76.609575 40.971874, -76.609674 40.972603, -76.609825 40.972536, -76.610188 40.972356, -76.610424 40.972221, -76.611346 40.971747, -76.611936 40.971451, -76.611948 40.971442, -76.613083 40.972772, -76.613177 40.972883, -76.613275 40.972842, -76.613869 40.972553, -76.613938 40.972632, -76.614478 40.973237, -76.614555 40.973198, -76.615086 40.972931, -76.615074 40.973035, -76.614883 40.974649, -76.612473 40.976324, -76.613449 40.979022, -76.613461 40.979058, -76.614037 40.980651, -76.617375 40.979204, -76.616598 40.978666, -76.615412 40.977876, -76.616843 40.977108, -76.616625 40.976753, -76.617487 40.976288, -76.61675 40.975137, -76.617 40.974996, -76.617682 40.974609, -76.617795 40.974536, -76.617355 40.973398, -76.616891 40.972495, -76.616975 40.972585, -76.617261 40.972892, -76.617669 40.97328, -76.617607 40.973356, -76.617585 40.973396, -76.617575 40.973438, -76.617571 40.973488, -76.617576 40.973531, -76.617663 40.973743, -76.617827 40.97406, -76.617872 40.974205, -76.617886 40.974272, -76.617889 40.974346, -76.617883 40.97442, -76.617867 40.974492, -76.617824 40.97462, -76.617802 40.974707, -76.6178 40.974791, -76.617809 40.974864, -76.617823 40.974922, -76.617847 40.974971, -76.617909 40.975062, -76.617989 40.975149, -76.618207 40.975359, -76.618664 40.975874, -76.618683 40.975889, -76.618873 40.976043, -76.619206 40.97639, -76.619488 40.97666, -76.619722 40.976856, -76.620535 40.976385, -76.620827 40.976216, -76.620836 40.976223, -76.621038 40.976364, -76.621249 40.976505, -76.62174 40.976813, -76.621845 40.976894, -76.622064 40.977108, -76.622245 40.977278, -76.622345 40.977362, -76.622394 40.977393, -76.622466 40.97741, -76.622536 40.977412, -76.622621 40.977404, -76.622687 40.977385, -76.62274 40.977364, -76.62276 40.977352, -76.622871 40.977313, -76.623229 40.977619, -76.6236 40.977959, -76.623968 40.978319, -76.624381 40.978753, -76.624545 40.978937, -76.625174 40.97967, -76.625478 40.980033, -76.62579 40.980395, -76.626162 40.980833, -76.62682 40.981609, -76.627349 40.98221, -76.627573 40.982447, -76.627637 40.982514, -76.627439 40.982618, -76.627117 40.982795, -76.62702 40.982833, -76.626854 40.982976, -76.62676 40.983069, -76.626679 40.983165, -76.626524 40.983378, -76.626489 40.983426, -76.62646 40.983466, -76.626305 40.983693, -76.626161 40.983921, -76.626107 40.984018, -76.626039 40.984157, -76.625879 40.984547, -76.625678 40.985088, -76.625614 40.985271, -76.625587 40.985343, -76.625661 40.985321, -76.625772 40.98528, -76.625886 40.985256, -76.62603 40.985215, -76.626583 40.984987, -76.626824 40.984837, -76.62705 40.984655, -76.62721 40.98451, -76.627319 40.984422, -76.627473 40.984351, -76.627654 40.984289, -76.627745 40.984279, -76.627836 40.984269, -76.628007 40.984262, -76.628121 40.984262, -76.628232 40.984245, -76.628335 40.984197, -76.628422 40.984119, -76.628506 40.984029, -76.628544 40.983933, -76.628561 40.983823, -76.628571 40.983689, -76.628598 40.98362, -76.628686 40.983521, -76.62878 40.9836, -76.630124 40.984756, -76.630589 40.985176, -76.630817 40.985393, -76.630946 40.985528, -76.631117 40.985707, -76.631554 40.986182, -76.63169 40.986119, -76.630996 40.985351, -76.630664 40.985008, -76.630288 40.984628, -76.630144 40.98447, -76.629924 40.984251, -76.629343 40.983705, -76.628934 40.983301, -76.628878 40.98325, -76.628831 40.983206, -76.628764 40.983147, -76.628356 40.982744, -76.627997 40.982366, -76.627922 40.982279, -76.627777 40.982109, -76.627372 40.981675, -76.626533 40.980793, -76.62442 40.978572, -76.62418 40.978332, -76.624062 40.978229, -76.62386 40.978042, -76.623483 40.977679, -76.623382 40.977567, -76.622997 40.977233, -76.621401 40.975884, -76.619947 40.974655, -76.619472 40.974256, -76.618116 40.973117, -76.617735 40.972789, -76.617539 40.972612, -76.617376 40.972443, -76.617215 40.972262, -76.617018 40.971976, -76.616907 40.97177, -76.616817 40.971565, -76.616761 40.971403, -76.616728 40.971299, -76.616691 40.971133, -76.616634 40.970799, -76.616752 40.970841, -76.61746 40.971094, -76.617182 40.971452, -76.617788 40.971662, -76.618165 40.971793, -76.618331 40.971941, -76.61849 40.972083, -76.619316 40.972815, -76.620764 40.972157, -76.620779 40.972102, -76.620794 40.971997, -76.620799 40.971794, -76.620792 40.971776, -76.620708 40.971717, -76.620624 40.971626, -76.620597 40.97159, -76.620549 40.971543, -76.620427 40.971463, -76.620076 40.971191, -76.619836 40.970998, -76.619649 40.970848, -76.619393 40.970651, -76.619197 40.970532, -76.619093 40.970494, -76.619059 40.970495, -76.619037 40.970502, -76.619008 40.970503, -76.61897 40.970495, -76.618951 40.970474, -76.618942 40.970454, -76.618911 40.97043, -76.61884 40.970397, -76.618723 40.970335, -76.618624 40.970268, -76.618494 40.970182, -76.618429 40.970131, -76.618574 40.970126, -76.61872 40.97012, -76.620797 40.970041, -76.623285 40.966592, -76.624615 40.966873, -76.624677 40.966871, -76.628286 40.967725, -76.630911 40.968236, -76.631411 40.968282, -76.633249 40.968661, -76.63326 40.968409, -76.633259 40.968288, -76.633248 40.968114, -76.633267 40.967975, -76.633276 40.967905, -76.633289 40.967807, -76.633375 40.966749, -76.633345 40.966721, -76.633213 40.966613, -76.633121 40.966527, -76.633047 40.966473, -76.63291 40.966397, -76.632807 40.966333, -76.632674 40.966216, -76.632342 40.96601, -76.632166 40.965915, -76.632097 40.965897, -76.632054 40.965893, -76.631973 40.965875, -76.63195 40.965863, -76.631925 40.96583, -76.631906 40.965795, -76.631812 40.965701, -76.631748 40.965663, -76.63165 40.965591, -76.631567 40.965498, -76.631508 40.965396, -76.631489 40.965326, -76.63147 40.965211, -76.631472 40.965188, -76.631437 40.965121, -76.631385 40.964955, -76.631337 40.964924, -76.63111 40.964818, -76.631056 40.964805, -76.630996 40.964777, -76.630745 40.964675, -76.630465 40.964574, -76.630356 40.964525, -76.630222 40.964492, -76.630021 40.964458, -76.629834 40.964406, -76.629643 40.96436, -76.629339 40.96425, -76.629172 40.964202, -76.628917 40.964107, -76.628708 40.963998, -76.628485 40.963888, -76.628185 40.963705, -76.627872 40.963506, -76.627658 40.963359, -76.627526 40.963286, -76.627316 40.963206, -76.627106 40.963141, -76.626891 40.96304, -76.626637 40.962909, -76.626346 40.962743, -76.626054 40.962589, -76.625499 40.96224, -76.62513 40.962068, -76.624861 40.961919, -76.624496 40.961742, -76.62406 40.961498, -76.623779 40.961346, -76.623752 40.961321, -76.623686 40.961286, -76.623352 40.961084, -76.623154 40.960986, -76.62301 40.960937, -76.622843 40.960857, -76.62267 40.960788, -76.622646 40.960775, -76.622363 40.960627, -76.622189 40.96053, -76.621649 40.960142, -76.62126 40.959906, -76.621065 40.95976, -76.62097 40.959703, -76.620764 40.959636, -76.620718 40.959621, -76.620691 40.959612, -76.620639 40.959598, -76.620594 40.95958, -76.620426 40.959477, -76.620333 40.959406, -76.620245 40.959368, -76.620114 40.959312, -76.62003 40.959271, -76.619952 40.959241, -76.619886 40.959222, -76.619807 40.959174, -76.619719 40.959143, -76.619663 40.959105, -76.619643 40.959086, -76.619586 40.959033, -76.619419 40.958899, -76.619352 40.958856, -76.619135 40.958719, -76.619004 40.958619, -76.618917 40.958552, -76.618786 40.958438, -76.618681 40.958328, -76.618256 40.957994, -76.618065 40.957858, -76.617866 40.957727, -76.617716 40.957648, -76.617587 40.957573, -76.61745 40.957471, -76.617248 40.957311, -76.617133 40.957198, -76.616965 40.957094, -76.616865 40.957005, -76.616643 40.956781, -76.616596 40.956701, -76.616525 40.956619, -76.616416 40.956536, -76.616239 40.956373, -76.616149 40.956308, -76.616122 40.956301, -76.616068 40.956269, -76.615999 40.956246, -76.615907 40.956178, -76.615825 40.956098, -76.615671 40.95596, -76.615305 40.955598, -76.615053 40.955401, -76.614882 40.95523, -76.614734 40.955058, -76.614542 40.954873, -76.614428 40.954791, -76.614079 40.95451, -76.613905 40.95437, -76.613441 40.953968, -76.612944 40.953492, -76.612832 40.95336, -76.612734 40.953259, -76.612652 40.953161, -76.612548 40.953061, -76.612275 40.952818, -76.612051 40.952607, -76.611918 40.952501, -76.611873 40.952449, -76.611839 40.952401, -76.611796 40.952358, -76.611726 40.952305, -76.611664 40.952284, -76.611584 40.952274, -76.61155 40.952245, -76.611516 40.952134, -76.611318 40.951928, -76.61082 40.951452, -76.610713 40.951331, -76.610612 40.951206, -76.610555 40.951159, -76.61051 40.951128, -76.6104 40.951035, -76.610291 40.950918, -76.610198 40.95085, -76.610108 40.950756, -76.609997 40.950678, -76.609932 40.950608, -76.609719 40.950405, -76.609667 40.950382, -76.609627 40.950344, -76.609575 40.950278, -76.609449 40.950147, -76.609371 40.950102, -76.609316 40.950055, -76.609237 40.949976, -76.609066 40.949775, -76.609009 40.9497, -76.608908 40.949632, -76.60868 40.949457, -76.608567 40.949353, -76.608518 40.949313, -76.60848 40.949275, -76.608402 40.949181, -76.608353 40.949136, -76.608281 40.94908, -76.608221 40.949044, -76.608085 40.94894, -76.607992 40.948875, -76.607895 40.9488, -76.60772 40.948674, -76.607646 40.948627, -76.607455 40.948476, -76.607389 40.9484, -76.607333 40.948348, -76.60717 40.948214, -76.607115 40.948163, -76.606922 40.948026, -76.606784 40.947915, -76.606654 40.947787, -76.606439 40.947637, -76.606376 40.947602, -76.606305 40.947576, -76.606274 40.947572, -76.606157 40.947522, -76.605994 40.947418, -76.605837 40.947296, -76.605721 40.947223, -76.605645 40.947168, -76.605556 40.947082, -76.6054 40.946952, -76.60527 40.946821, -76.605147 40.946712, -76.604779 40.946408, -76.604689 40.946321, -76.604618 40.946408, -76.603669 40.947373, -76.602785 40.948272, -76.602757 40.948318, -76.602708 40.948362, -76.602463 40.948715, -76.602003 40.948353, -76.601865 40.948244, -76.600469 40.947133, -76.599972 40.946736, -76.599754 40.946552, -76.599491 40.94632, -76.59914 40.945995, -76.59879 40.94568, -76.59797 40.94498, -76.597518 40.944619, -76.597348 40.944483, -76.597258 40.944417, -76.59717 40.944466, -76.596888 40.944605, -76.596495 40.944786, -76.596636 40.944975, -76.596757 40.945122, -76.596777 40.945155, -76.596815 40.945217, -76.596842 40.945276, -76.596854 40.945323, -76.596854 40.945374, -76.596844 40.945402, -76.596804 40.945443, -76.596703 40.945521, -76.59637 40.945738, -76.595939 40.946009, -76.595886 40.946037, -76.595376 40.946303, -76.595241 40.946353, -76.594665 40.946501, -76.594245 40.946617, -76.593766 40.946744, -76.593503 40.946806, -76.593107 40.946921, -76.592964 40.946966, -76.592887 40.94701, -76.592861 40.947058, -76.592855 40.947116, -76.592876 40.947193, -76.593066 40.947465, -76.593098 40.947534, -76.592881 40.94759, -76.592434 40.947744, -76.592094 40.947845, -76.591895 40.947919, -76.591842 40.947949, -76.591816 40.947978, -76.591809 40.948002, -76.591846 40.948093, -76.591935 40.94825, -76.59244 40.949084, -76.593188 40.950264, -76.593396 40.950629, -76.59379 40.950501, -76.594809 40.950146, -76.594853 40.950215, -76.594987 40.950435, -76.595073 40.950546, -76.59514 40.950623, -76.595247 40.950729, -76.595357 40.950822, -76.595499 40.950901, -76.59559 40.950935, -76.595657 40.95096, -76.595811 40.950989, -76.596006 40.951012, -76.596227 40.951003, -76.596353 40.951003, -76.596534 40.950989, -76.596879 40.950937, -76.597392 40.950844, -76.597825 40.950771, -76.598033 40.950749, -76.598043 40.950604, -76.598051 40.950499, -76.598043 40.950382, -76.59802 40.950213, -76.597983 40.950039, -76.597935 40.949847, -76.597822 40.949455, -76.597735 40.949198, -76.59767 40.949052, -76.597597 40.948963, -76.597686 40.948974, -76.597835 40.948975, -76.598027 40.948965, -76.598162 40.948943, -76.598323 40.948903, -76.599089 40.948695, -76.599235 40.948678, -76.599405 40.948677, -76.599817 40.948698, -76.599906 40.948691, -76.599991 40.948665, -76.60008 40.948614, -76.600134 40.94857, -76.600185 40.948496, -76.60021 40.948441, -76.600236 40.948312, -76.600223 40.94817, -76.600201 40.948038, -76.60011 40.947712, -76.600039 40.947493, -76.600038 40.947444, -76.600541 40.948117, -76.600738 40.948368, -76.600981 40.948688, -76.601319 40.949148, -76.60163 40.949562, -76.601835 40.949843, -76.601551 40.950313, -76.601498 40.950412, -76.601224 40.950877, -76.600831 40.950821, -76.600621 40.951125, -76.600295 40.951601, -76.599328 40.953006, -76.599014 40.953466, -76.59844 40.954339, -76.598208 40.954339, -76.597965 40.954342, -76.597635 40.954334, -76.59717 40.95433, -76.596865 40.954323, -76.596604 40.954332, -76.59669 40.955389, -76.596312 40.95574, -76.596154 40.955906, -76.596104 40.955993, -76.595078 40.955952, -76.592559 40.959235, -76.592809 40.959296, -76.592721 40.959296, -76.591934 40.959313, -76.591649 40.95932, -76.590341 40.959344, -76.590039 40.959344, -76.589064 40.959366, -76.588801 40.959377, -76.588061 40.959439, -76.587725 40.959484, -76.587111 40.959587, -76.586812 40.959645, -76.586268 40.959759, -76.586155 40.959779, -76.585813 40.959841, -76.585503 40.959905, -76.585167 40.959964, -76.584974 40.959996, -76.584736 40.960036, -76.584334 40.960083, -76.583899 40.960111, -76.583624 40.960118, -76.583427 40.960117, -76.58296 40.9601, -76.582664 40.960081, -76.581736 40.960005, -76.580838 40.959925, -76.580725 40.959818, -76.580692 40.959794, -76.58039 40.959739, -76.580224 40.959681, -76.580163 40.959652, -76.579895 40.959584, -76.579753 40.959559, -76.57961 40.959491, -76.579478 40.959422, -76.579241 40.959339, -76.578962 40.959258, -76.578896 40.959243, -76.578845 40.959222, -76.578789 40.959178, -76.57869 40.959086, -76.578614 40.959037, -76.578411 40.958925, -76.578376 40.958889, -76.578354 40.958856, -76.578263 40.958775, -76.578226 40.958754, -76.578156 40.95875, -76.578099 40.958752, -76.578019 40.958761, -76.577794 40.958758, -76.5777 40.958748, -76.57739 40.958675, -76.577271 40.958637, -76.57709 40.958603, -76.577032 40.958606, -76.576956 40.958662, -76.576826 40.958776, -76.576612 40.958928, -76.576476 40.959044, -76.576377 40.959141, -76.576273 40.959256, -76.576202 40.959313, -76.576105 40.959363, -76.575968 40.959425, -76.575652 40.959611, -76.575496 40.959729, -76.575357 40.959869, -76.575275 40.959964, -76.575245 40.960003, -76.575211 40.96004, -76.57516 40.960098, -76.575087 40.960144, -76.574976 40.960199, -76.574919 40.960216, -76.574806 40.960238, -76.574729 40.960244, -76.574559 40.960275, -76.574388 40.960363, -76.574289 40.960425, -76.574216 40.960478, -76.574172 40.960505, -76.574139 40.96052, -76.574034 40.960542, -76.573944 40.960571, -76.573895 40.960583, -76.573871 40.96058, -76.573786 40.960508, -76.573707 40.960478, -76.573653 40.960481, -76.573557 40.960509, -76.573461 40.960543, -76.573411 40.960577, -76.573396 40.960603, -76.57335 40.960652, -76.573306 40.960669, -76.573264 40.960671, -76.573104 40.960691, -76.573024 40.960724, -76.572898 40.960758, -76.572734 40.960787, -76.572655 40.960806, -76.572487 40.960886, -76.572293 40.960951, -76.572168 40.960999, -76.572053 40.961072, -76.571907 40.961094, -76.571776 40.961106, -76.571701 40.961128, -76.571634 40.96116, -76.571595 40.961188, -76.571577 40.96121, -76.571563 40.961243, -76.571534 40.96127, -76.571418 40.961336, -76.571259 40.961366, -76.57109 40.961423, -76.57105 40.961442, -76.57098 40.961511, -76.570771 40.961607, -76.570629 40.961656, -76.570516 40.961669, -76.570356 40.961703, -76.570199 40.961747, -76.569989 40.961828, -76.569919 40.961875, -76.569854 40.961937, -76.569778 40.962034, -76.569681 40.96213, -76.569611 40.962182, -76.569418 40.962288, -76.569398 40.96231, -76.569387 40.962383, -76.569423 40.962437, -76.569512 40.962498, -76.569538 40.96253, -76.569542 40.96261, -76.569516 40.962653, -76.56949 40.962666, -76.569448 40.96268, -76.569383 40.962677, -76.569302 40.962659, -76.569226 40.962649, -76.569162 40.962616, -76.569112 40.962585, -76.569075 40.962572, -76.56903 40.962571, -76.568994 40.962589, -76.568981 40.962616, -76.568976 40.962694, -76.569015 40.962863, -76.569018 40.963082, -76.568974 40.963445, -76.568953 40.963493, -76.568927 40.963655, -76.568935 40.963704, -76.568957 40.963745, -76.568979 40.963771, -76.568992 40.963807, -76.568915 40.963911, -76.568761 40.964038, -76.568691 40.964111, -76.568641 40.964189, -76.568592 40.964235, -76.568526 40.964265, -76.568487 40.964276, -76.568463 40.964287, -76.568448 40.964301, -76.56844 40.964321, -76.568429 40.964395, -76.568405 40.964432, -76.56838 40.964458, -76.568346 40.964481, -76.568274 40.964508, -76.568128 40.964512, -76.567931 40.964494, -76.567909 40.964485, -76.567908 40.964455, -76.567896 40.964414, -76.567858 40.964407, -76.567821 40.964409, -76.567784 40.964417, -76.567741 40.964436, -76.567715 40.964456, -76.567678 40.964505, -76.567659 40.964577, -76.567656 40.964608, -76.56766 40.964658, -76.56769 40.964678, -76.567783 40.964683, -76.567812 40.964692, -76.567832 40.964703, -76.567841 40.96472, -76.567841 40.96487, -76.567831 40.964926, -76.567817 40.964948, -76.567785 40.964965, -76.567743 40.964981, -76.567691 40.964992, -76.567639 40.965009, -76.567471 40.965075, -76.567433 40.965102, -76.567381 40.965114, -76.566771 40.965187, -76.566515 40.965195, -76.566402 40.965201, -76.566222 40.964612, -76.565835 40.963331, -76.565493 40.963446, -76.565092 40.963569, -76.56463 40.963689, -76.564412 40.963319, -76.564361 40.963233, -76.563936 40.962483, -76.563865 40.96238, -76.563797 40.962308, -76.563718 40.962249, -76.563653 40.962217, -76.563321 40.963037, -76.563397 40.963052, -76.563447 40.96307, -76.563485 40.963104, -76.563593 40.963172, -76.563756 40.963219, -76.563893 40.963292, -76.563903 40.963356, -76.563922 40.963415, -76.562157 40.963759, -76.559155 40.963878, -76.558817 40.963895, -76.555742 40.964136, -76.553776 40.964302, -76.552895 40.96436, -76.551205 40.964677, -76.551134 40.964702, -76.550149 40.965041, -76.548607 40.965884, -76.547465 40.966615, -76.547218 40.966773, -76.545496 40.96764, -76.543797 40.968328, -76.542563 40.968602, -76.542502 40.968612, -76.542426 40.968625, -76.541153 40.968658, -76.540897 40.968666, -76.539436 40.968945, -76.53791 40.969244, -76.535532 40.969542, -76.534464 40.969832, -76.534412 40.969857, -76.533886 40.970005, -76.532439 40.970268, -76.530522 40.97062, -76.527981 40.97094, -76.527068 40.9711, -76.526221 40.971231, -76.525568 40.971337, -76.52507 40.971419, -76.524427 40.971518, -76.52382 40.971606, -76.521879 40.972125, -76.521106 40.972366, -76.520903 40.972452, -76.519412 40.972929, -76.518964 40.973096, -76.517014 40.973756, -76.515369 40.974319, -76.51484 40.974466, -76.514269 40.974568, -76.513662 40.974631, -76.512923 40.974655, -76.504707 40.974664, -76.504586 40.974668, -76.503376 40.974891, -76.50306 40.974934, -76.5006 40.975809, -76.499687 40.976117, -76.498213 40.97667, -76.498106 40.976695, -76.497958 40.976795, -76.497655 40.976937, -76.497486 40.977003, -76.497101 40.977144, -76.496983 40.977179, -76.496674 40.977285, -76.496463 40.977345, -76.496285 40.977386, -76.496826 40.978174, -76.496742 40.978233, -76.49665 40.978253, -76.496498 40.978265, -76.496416 40.978287, -76.496253 40.978295, -76.496093 40.978357, -76.495933 40.978426, -76.495852 40.978454, -76.495764 40.978476, -76.495602 40.978504, -76.495385 40.97857, -76.495224 40.978606, -76.495049 40.97863, -76.494852 40.97864, -76.494696 40.978641, -76.494456 40.978641, -76.494356 40.978632, -76.494333 40.978577, -76.494352 40.978494, -76.494382 40.978447, -76.494377 40.97842, -76.494331 40.97842, -76.494266 40.97845, -76.49418 40.978498, -76.494072 40.978582, -76.493959 40.978686, -76.493912 40.978738, -76.493851 40.978788, -76.493792 40.978824, -76.493707 40.978831, -76.493552 40.9788, -76.493483 40.978778, -76.493404 40.978775, -76.493329 40.978782, -76.493253 40.978811, -76.493225 40.978835, -76.493196 40.97887, -76.493123 40.978855, -76.493078 40.978798, -76.493014 40.97878, -76.492893 40.978824, -76.492785 40.978869, -76.492733 40.978914, -76.492615 40.978954, -76.492565 40.978967, -76.492523 40.978978, -76.492428 40.978976, -76.492338 40.978991, -76.492256 40.979013, -76.492078 40.979041, -76.492005 40.979045, -76.49191 40.979056, -76.491844 40.979079, -76.491786 40.979125, -76.49172 40.979153, -76.491483 40.979152, -76.491439 40.979164, -76.491346 40.979203, -76.491239 40.979209, -76.49113 40.979256, -76.49107 40.979288, -76.491022 40.979308, -76.490924 40.979299, -76.490857 40.979254, -76.490667 40.979157, -76.490446 40.979143, -76.490378 40.979114, -76.490306 40.979098, -76.490216 40.97909, -76.490135 40.979114, -76.490061 40.979142, -76.489981 40.979145, -76.489831 40.979054, -76.489759 40.979039, -76.489708 40.979044, -76.489629 40.979093, -76.489595 40.979127, -76.489519 40.979131, -76.489448 40.979108, -76.489121 40.97892, -76.489066 40.978908, -76.488973 40.978923, -76.488883 40.978924, -76.48879 40.978963, -76.488769 40.979004, -76.488787 40.979026, -76.488825 40.979043, -76.488858 40.979072, -76.488855 40.97911, -76.488834 40.979135, -76.488739 40.979184, -76.488583 40.979228, -76.488501 40.979232, -76.488427 40.979271, -76.488375 40.979319, -76.488321 40.979357, -76.488264 40.979367, -76.488082 40.979335, -76.488019 40.979369, -76.487982 40.979492, -76.487947 40.979543, -76.487874 40.979586, -76.487807 40.979583, -76.487729 40.97957, -76.487644 40.979571, -76.487583 40.979586, -76.487475 40.979629, -76.487436 40.979646, -76.487368 40.979687, -76.487321 40.979732, -76.48714 40.979936, -76.487012 40.980022, -76.48695 40.980069, -76.486899 40.980125, -76.486776 40.980365, -76.486712 40.980407, -76.486642 40.98044, -76.486574 40.980483, -76.486524 40.980534, -76.486475 40.980582, -76.486402 40.980622, -76.486255 40.980686, -76.486194 40.980726, -76.486149 40.980783, -76.486089 40.980825, -76.485956 40.980892, -76.485892 40.980917, -76.485865 40.980957, -76.485831 40.981091, -76.485824 40.981152, -76.485813 40.981186, -76.48574 40.981249, -76.485637 40.981299, -76.485584 40.98135, -76.485542 40.981404, -76.485521 40.981419, -76.485451 40.981501, -76.48539 40.981592, -76.485261 40.981668, -76.4852 40.981679, -76.485143 40.98169, -76.484864 40.981688, -76.484807 40.981765, -76.484756 40.981807, -76.484372 40.981911, -76.48422 40.981956, -76.484049 40.98202, -76.484 40.982104, -76.483909 40.982178, -76.483798 40.982204, -76.483726 40.982136, -76.483709 40.982074, -76.483686 40.98205, -76.483601 40.98203, -76.483497 40.982149, -76.483374 40.982281, -76.483315 40.982357, -76.483244 40.982382, -76.483152 40.982383, -76.483044 40.982427, -76.482947 40.982492, -76.482603 40.982685, -76.482557 40.98272, -76.482526 40.982788, -76.482305 40.982968, -76.482046 40.983152, -76.481982 40.983209, -76.481923 40.983252, -76.481852 40.983277, -76.481762 40.983303, -76.481691 40.983336, -76.481633 40.983374, -76.481525 40.983464, -76.481474 40.983514, -76.481418 40.983558, -76.481353 40.983597, -76.481266 40.983627, -76.481106 40.983665, -76.481027 40.983688, -76.480958 40.983715, -76.480903 40.983764, -76.480821 40.983884, -76.480755 40.983995, -76.480598 40.984152, -76.480539 40.984198, -76.480471 40.984233, -76.480425 40.984245, -76.480287 40.984248, -76.480067 40.984245, -76.479977 40.984249, -76.479893 40.984261, -76.479737 40.984294, -76.479665 40.984314, -76.479596 40.984348, -76.47954 40.984387, -76.479482 40.984434, -76.479424 40.984469, -76.47937 40.984508, -76.479296 40.98455, -76.479174 40.98458, -76.479046 40.98462, -76.47883 40.984717, -76.478718 40.984755, -76.478657 40.984755, -76.478592 40.984737, -76.478529 40.984755, -76.478362 40.984871, -76.478298 40.984901, -76.478227 40.98494, -76.478088 40.985003, -76.478013 40.985042, -76.477936 40.985105, -76.477894 40.985159, -76.477845 40.985212, -76.477682 40.985328, -76.47758 40.985435, -76.477499 40.985549, -76.477479 40.985614, -76.47744 40.985662, -76.477405 40.98568, -76.477016 40.985187, -76.476696 40.984802, -76.47656 40.984799, -76.476126 40.984831, -76.476015 40.983697, -76.47596 40.983104, -76.475945 40.982974, -76.475932 40.982854, -76.475909 40.982644, -76.475868 40.982417, -76.475828 40.982284, -76.475771 40.98215, -76.475692 40.982005, -76.475572 40.981826, -76.475207 40.981366, -76.475097 40.981205, -76.474989 40.981009, -76.474882 40.980772, -76.474304 40.979485, -76.474911 40.979332, -76.475493 40.979184, -76.475568 40.979168, -76.475618 40.979149, -76.475647 40.979124, -76.475645 40.979099, -76.475608 40.979036, -76.475437 40.978868, -76.475315 40.978735, -76.475127 40.97854, -76.475041 40.97846, -76.474979 40.978416, -76.474919 40.978391, -76.474869 40.978384, -76.474797 40.978389, -76.474503 40.978487, -76.473902 40.978657, -76.473414 40.977706, -76.472471 40.975788, -76.472344 40.975502, -76.472262 40.975264, -76.472223 40.975097, -76.472199 40.974813, -76.472195 40.974533, -76.472212 40.97407, -76.472265 40.972598, -76.472272 40.972046, -76.472258 40.971834, -76.472229 40.971658, -76.472182 40.971423, -76.472147 40.97113, -76.472139 40.970924, -76.472154 40.970653, -76.472303 40.969525, -76.472339 40.969119, -76.472341 40.968826, -76.472311 40.968429, -76.472281 40.968202, -76.472278 40.968176, -76.472205 40.967831, -76.472135 40.967581, -76.471657 40.966223, -76.471508 40.965498, -76.471351 40.96491, -76.471174 40.964405, -76.470707 40.963273, -76.469535 40.96046, -76.469405 40.960126, -76.469332 40.959877, -76.469281 40.959627, -76.469256 40.959442, -76.469251 40.959261, -76.469275 40.958964, -76.469307 40.958747, -76.469359 40.95853, -76.469442 40.958278, -76.469536 40.958062, -76.469651 40.957834, -76.470287 40.958109, -76.470923 40.957996, -76.47153 40.957909, -76.472112 40.957812, -76.4726 40.957741, -76.473225 40.957644, -76.473371 40.95763, -76.473282 40.95753, -76.473135 40.9574, -76.472617 40.957022, -76.472481 40.956933, -76.472342 40.956862, -76.472235 40.956823, -76.471927 40.95673, -76.471765 40.956669, -76.471651 40.956615, -76.471571 40.956573, -76.47143 40.956489, -76.471196 40.956331, -76.470941 40.956142, -76.47085 40.956081, -76.47087 40.956051, -76.470958 40.955869, -76.470986 40.955777, -76.471149 40.955793, -76.471246 40.955795, -76.471309 40.955785, -76.471346 40.955771, -76.471415 40.955724, -76.471486 40.955657, -76.471633 40.955539, -76.471775 40.955441, -76.472018 40.955318, -76.473102 40.954815, -76.473653 40.954551, -76.474551 40.954138, -76.4748 40.954046, -76.47502 40.953977, -76.475288 40.953908, -76.476035 40.953751, -76.477047 40.953562, -76.478112 40.953343, -76.478232 40.953322, -76.47843 40.953286, -76.478711 40.95326, -76.479127 40.953241, -76.480604 40.953192, -76.4809 40.953196, -76.48133 40.953221, -76.48138 40.953227, -76.481529 40.953243, -76.481789 40.953281, -76.482127 40.953345, -76.482762 40.953504, -76.483184 40.95362, -76.48332 40.953667, -76.483563 40.953751, -76.483914 40.953891, -76.484314 40.954021, -76.484598 40.954103, -76.485168 40.95425, -76.486313 40.954531, -76.485708 40.952907, -76.485416 40.952059, -76.485198 40.95156, -76.48466 40.950318, -76.484434 40.950296, -76.480562 40.95002, -76.477941 40.950019, -76.475713 40.950268, -76.473581 40.950678, -76.471681 40.951281, -76.47062 40.951853, -76.470069 40.952182, -76.469372 40.952633, -76.466846 40.95311, -76.467059 40.952965, -76.467278 40.952795, -76.467413 40.952677, -76.467554 40.952564, -76.467614 40.952491, -76.467602 40.95241, -76.467505 40.952311, -76.467379 40.952208, -76.467167 40.952047, -76.466956 40.951857, -76.466672 40.95163, -76.466653 40.951615, -76.466635 40.9516, -76.466292 40.951288, -76.466019 40.951077, -76.465696 40.950802, -76.465534 40.950645, -76.465151 40.950348, -76.465036 40.950209, -76.464968 40.950138, -76.464853 40.950015, -76.464637 40.949853, -76.464558 40.949796, -76.464358 40.949671, -76.463941 40.949301, -76.463812 40.94922, -76.463651 40.94911, -76.463488 40.948984, -76.463375 40.948889, -76.463056 40.94866, -76.462597 40.948377, -76.462081 40.947994, -76.461831 40.947845, -76.461626 40.947706, -76.461449 40.947551, -76.461248 40.9474, -76.461156 40.947295, -76.461054 40.947192, -76.460322 40.946802, -76.460138 40.946695, -76.459867 40.946522, -76.458922 40.946209, -76.458727 40.946087, -76.458556 40.945993, -76.458284 40.945911, -76.458146 40.945887, -76.457955 40.945839, -76.457844 40.945828, -76.457776 40.945844, -76.457649 40.945885, -76.457528 40.945954, -76.457412 40.946028, -76.457263 40.946152, -76.457016 40.946341, -76.456884 40.946433, -76.456815 40.946486, -76.456697 40.94655, -76.456503 40.946677, -76.456437 40.946757, -76.456382 40.946838, -76.456285 40.946923, -76.456229 40.946977, -76.456156 40.947047, -76.456058 40.947132, -76.455974 40.947173, -76.455882 40.947258, -76.455679 40.947332, -76.455506 40.947409, -76.455442 40.947431, -76.455345 40.947485, -76.455231 40.947591, -76.455129 40.947666, -76.455087 40.947705, -76.454908 40.947823, -76.454703 40.948029, -76.454493 40.948232, -76.454363 40.948381, -76.454245 40.948581, -76.454115 40.948759, -76.454059 40.948821, -76.453958 40.948916, -76.453882 40.948989, -76.453595 40.949233, -76.453317 40.949424, -76.453071 40.949545, -76.452953 40.949591, -76.452979 40.949637, -76.453043 40.949758, -76.453285 40.949596, -76.453947 40.949161, -76.454033 40.949096, -76.454519 40.950263, -76.454694 40.950488, -76.455103 40.951044, -76.454589 40.951251, -76.454135 40.951419, -76.454108 40.951428, -76.453986 40.95147, -76.453964 40.951478, -76.453808 40.951532, -76.453536 40.951643, -76.453239 40.951749, -76.45304 40.951826, -76.452999 40.95185, -76.452942 40.951924, -76.453163 40.952003, -76.453314 40.952044, -76.453432 40.952067, -76.453569 40.95208, -76.453719 40.95208, -76.453854 40.952064, -76.454045 40.95203, -76.454175 40.951988, -76.45423 40.951966, -76.454254 40.951956, -76.454777 40.952904, -76.45552 40.954253, -76.4557 40.954582, -76.455671 40.954601, -76.456181 40.9555, -76.456905 40.955203, -76.457131 40.955091, -76.457494 40.95489, -76.457938 40.955025, -76.458232 40.955106, -76.458425 40.955165, -76.458096 40.955787, -76.458899 40.956047, -76.459727 40.956293, -76.460543 40.956542, -76.461362 40.956809, -76.461441 40.956835, -76.462319 40.957381, -76.46231 40.957494, -76.462295 40.957592)))"} -{"geo_id":"03763","urban_area_code":"03763","name":"Athens-Clarke County, GA","lsad_name":"Athens-Clarke County, GA Urbanized Area","area_lsad_code":"75","mtfcc_feature_class_code":"G3500","type":"U","functional_status":"S","area_land_meters":254875194,"area_water_meters":2512432,"internal_point_lon":-83.3982719,"internal_point_lat":33.9420889,"internal_point_geom":"POINT(-83.3982719 33.9420889)","urban_area_geom":"MULTIPOLYGON(((-83.329152 33.975119, -83.329123 33.975183, -83.328961 33.975461, -83.328172 33.976674, -83.327953 33.977034, -83.32774 33.977452, -83.327713 33.977505, -83.327679 33.977717, -83.327667 33.977855, -83.327702 33.978256, -83.327779 33.978458, -83.327895 33.978686, -83.328308 33.97933, -83.328897 33.980072, -83.328927 33.980102, -83.329181 33.98035, -83.329617 33.98071, -83.32981 33.980838, -83.330097 33.980938, -83.330471 33.981023, -83.330694 33.981104, -83.330927 33.981225, -83.331265 33.981455, -83.331539 33.981654, -83.331838 33.981902, -83.331944 33.982038, -83.332125 33.982285, -83.332241 33.982487, -83.332308 33.98263, -83.332359 33.982774, -83.332436 33.982963, -83.332471 33.983088, -83.332516 33.983317, -83.332517 33.983626, -83.332533 33.983922, -83.332467 33.984291, -83.33305 33.984417, -83.333466 33.984508, -83.333629 33.984543, -83.33376 33.984571, -83.333899 33.984572, -83.334013 33.984525, -83.33412 33.984438, -83.334211 33.984384, -83.334326 33.984357, -83.334473 33.984358, -83.334685 33.984414, -83.335353 33.984616, -83.33554 33.984651, -83.335736 33.984666, -83.335891 33.984661, -83.336007 33.984587, -83.336081 33.984444, -83.33663 33.983474, -83.33668 33.983345, -83.336674 33.983209, -83.336602 33.983032, -83.336425 33.982745, -83.336353 33.982601, -83.336338 33.982479, -83.336363 33.982356, -83.336413 33.982241, -83.336521 33.982112, -83.336669 33.982025, -83.336833 33.981924, -83.336989 33.981864, -83.337153 33.981817, -83.337284 33.981757, -83.337383 33.981662, -83.33745 33.98154, -83.33775 33.980943, -83.3379 33.980611, -83.337942 33.980488, -83.337927 33.980379, -83.337863 33.980256, -83.337717 33.98014, -83.337522 33.980009, -83.3374 33.97994, -83.337368 33.979851, -83.337402 33.97975, -83.337568 33.979424, -83.337827 33.978867, -83.337902 33.978691, -83.337967 33.978524, -83.334933 33.978261, -83.334172 33.97814, -83.333669 33.978037, -83.333094 33.977789, -83.332504 33.977484, -83.332008 33.977156, -83.331898 33.977059, -83.331651 33.97683, -83.33135 33.976456, -83.331049 33.976116, -83.330721 33.975844, -83.330313 33.975617, -83.329483 33.975242, -83.329152 33.975119)), ((-83.360029 33.907131, -83.360224 33.906944, -83.361694 33.905127, -83.362262 33.904256, -83.361513 33.904074, -83.361217 33.904375, -83.360856 33.904734, -83.360513 33.905163, -83.35935 33.906563, -83.359652 33.906889, -83.360029 33.907131)), ((-83.268836 33.958728, -83.268724 33.958636, -83.2683 33.958316, -83.267953 33.958029, -83.267527 33.957783, -83.267306 33.95765, -83.26701 33.957509, -83.266427 33.957284, -83.266006 33.957117, -83.26519 33.956839, -83.264708 33.956674, -83.263542 33.95746, -83.267219 33.958999, -83.268054 33.959349, -83.268251 33.959445, -83.268563 33.959044, -83.268836 33.958728)), ((-83.416856 33.848881, -83.416874 33.848851, -83.416901 33.848808, -83.416915 33.848784, -83.416923 33.848771, -83.416946 33.848734, -83.417148 33.848411, -83.418076 33.846876, -83.417911 33.847116, -83.417787 33.847299, -83.417621 33.847542, -83.417017 33.848111, -83.416744 33.848319, -83.416287 33.848402, -83.41451 33.847832, -83.415374 33.848178, -83.416856 33.848881)), ((-83.346906 34.035202, -83.346944 34.035326, -83.346971 34.035494, -83.347047 34.035882, -83.347084 34.035983, -83.347125 34.036053, -83.347176 34.036112, -83.34727 34.036171, -83.347291 34.03618, -83.348575 34.036751, -83.349211 34.037034, -83.350769 34.037673, -83.351755 34.038082, -83.352236 34.038282, -83.352675 34.038462, -83.35347 34.038787, -83.353668 34.038573, -83.353838 34.038371, -83.354002 34.03814, -83.354147 34.037898, -83.352882 34.037427, -83.350181 34.036421, -83.349874 34.036306, -83.349568 34.036192, -83.346906 34.035202)), ((-83.407067 34.010882, -83.406543 34.010764, -83.405406 34.01023, -83.403819 34.009863, -83.403422 34.009494, -83.403155 34.008752, -83.402942 34.00848, -83.40256 34.008136, -83.402217 34.007891, -83.402079 34.007839, -83.401835 34.007839, -83.401324 34.008045, -83.400546 34.008497, -83.400134 34.008788, -83.399935 34.008978, -83.400157 34.009143, -83.400126 34.010129, -83.400409 34.011504, -83.40095 34.012488, -83.401332 34.012832, -83.402041 34.013816, -83.402049 34.014036, -83.402446 34.014449, -83.402629 34.015422, -83.402644 34.015886, -83.40253 34.016394, -83.402499 34.016765, -83.402545 34.016969, -83.40314 34.017588, -83.403239 34.018137, -83.403212 34.018225, -83.403056 34.018181, -83.402698 34.018096, -83.402343 34.018068, -83.402079 34.01809, -83.401672 34.01822, -83.401109 34.018486, -83.400751 34.018664, -83.400524 34.018752, -83.40033 34.018848, -83.399244 34.019241, -83.398721 34.019471, -83.398556 34.019592, -83.398436 34.019725, -83.398378 34.019867, -83.398363 34.019942, -83.398344 34.020036, -83.39836 34.020204, -83.398385 34.020316, -83.39845 34.020413, -83.398565 34.020519, -83.398663 34.020601, -83.400042 34.021432, -83.400181 34.02156, -83.400263 34.021666, -83.400328 34.021782, -83.400413 34.02201, -83.401824 34.02157, -83.401933 34.021547, -83.402939 34.021191, -83.403214 34.02107, -83.403508 34.020907, -83.40372 34.02077, -83.403911 34.020621, -83.404073 34.020433, -83.404213 34.020227, -83.404311 34.020017, -83.404375 34.019819, -83.404563 34.018841, -83.40467 34.018434, -83.404819 34.017927, -83.404994 34.017333, -83.405099 34.017117, -83.405258 34.016704, -83.405365 34.016503, -83.405579 34.016098, -83.405642 34.015877, -83.405669 34.015373, -83.40559 34.014771, -83.405584 34.014568, -83.405609 34.014452, -83.405646 34.014383, -83.405705 34.014279, -83.406099 34.013715, -83.406576 34.013011, -83.406888 34.012451, -83.406927 34.012357, -83.406934 34.01234, -83.406961 34.012229, -83.407113 34.011288, -83.407105 34.011037, -83.407067 34.010882)), ((-83.293083 34.013195, -83.293541 34.014117, -83.293725 34.014488, -83.29381 34.014625, -83.293952 34.014855, -83.295077 34.017099, -83.29543 34.017817, -83.295617 34.018137, -83.295794 34.018354, -83.296003 34.018589, -83.29629 34.018862, -83.296627 34.019143, -83.296655 34.019166, -83.297074 34.019488, -83.297111 34.019517, -83.297516 34.019828, -83.297533 34.019849, -83.297704 34.019994, -83.297559 34.020036, -83.297433 34.02004, -83.297375 34.020025, -83.297206 34.020029, -83.297179 34.020036, -83.296967 34.020079, -83.296821 34.020152, -83.296779 34.020194, -83.296752 34.020259, -83.296686 34.020336, -83.29666 34.020359, -83.296625 34.020394, -83.296579 34.020509, -83.296525 34.020582, -83.296487 34.02064, -83.296401 34.020764, -83.296304 34.020856, -83.296105 34.021084, -83.296057 34.021218, -83.295981 34.02137, -83.295895 34.021465, -83.295819 34.021598, -83.2958 34.021617, -83.295667 34.02176, -83.295543 34.021883, -83.295401 34.022007, -83.295115 34.02214, -83.294963 34.022283, -83.294868 34.022359, -83.294763 34.022511, -83.294697 34.022597, -83.294564 34.022692, -83.294449 34.022787, -83.294326 34.022844, -83.294202 34.022958, -83.293983 34.023044, -83.293936 34.023168, -83.293974 34.023301, -83.293993 34.023405, -83.293898 34.023615, -83.293746 34.023805, -83.293641 34.024033, -83.293622 34.024262, -83.293536 34.024585, -83.29347 34.024661, -83.293441 34.024994, -83.29347 34.025184, -83.293479 34.025337, -83.293574 34.025441, -83.293546 34.025679, -83.293555 34.025774, -83.293565 34.025898, -83.293543 34.026116, -83.29347 34.026259, -83.293327 34.02643, -83.293239 34.026571, -83.292937 34.026828, -83.292723 34.026985, -83.292556 34.026994, -83.2924 34.026946, -83.292171 34.026904, -83.291952 34.026937, -83.291776 34.027018, -83.291705 34.027099, -83.291648 34.027194, -83.291524 34.027294, -83.29132 34.027322, -83.291063 34.027313, -83.290939 34.027246, -83.290678 34.027184, -83.290488 34.027132, -83.290221 34.027141, -83.289898 34.02727, -83.289693 34.027365, -83.289541 34.027417, -83.289113 34.027498, -83.288565 34.027526, -83.28739 34.027488, -83.28662 34.027377, -83.286307 34.027376, -83.285982 34.027421, -83.286178 34.028223, -83.286338 34.0288, -83.286366 34.028998, -83.286376 34.029192, -83.286358 34.029402, -83.286303 34.029758, -83.286189 34.030028, -83.285808 34.030605, -83.285556 34.03093, -83.285342 34.03114, -83.285008 34.031379, -83.284562 34.031589, -83.283717 34.031882, -83.283261 34.032032, -83.283331 34.032122, -83.283368 34.032329, -83.283389 34.032555, -83.283487 34.032734, -83.283678 34.03317, -83.28374 34.03346, -83.283787 34.033764, -83.283768 34.034087, -83.283716 34.034463, -83.283566 34.034958, -83.28351 34.035166, -83.283499 34.03536, -83.283536 34.035566, -83.283618 34.03574, -83.283825 34.035917, -83.284038 34.036016, -83.284272 34.036057, -83.284497 34.03606, -83.284699 34.036043, -83.285031 34.035991, -83.285519 34.035931, -83.285962 34.035929, -83.286375 34.03596, -83.286805 34.036075, -83.287152 34.036256, -83.28742 34.036406, -83.287658 34.036577, -83.287809 34.036703, -83.288069 34.037001, -83.288142 34.037014, -83.288302 34.037118, -83.288489 34.037198, -83.28879 34.037263, -83.288962 34.037272, -83.289162 34.03728, -83.289548 34.037332, -83.289949 34.037372, -83.290506 34.037421, -83.29051 34.037576, -83.290518 34.037884, -83.290461 34.038396, -83.290443 34.0388, -83.290429 34.038854, -83.290392 34.039002, -83.290241 34.039219, -83.290089 34.0394, -83.289935 34.039533, -83.289753 34.039644, -83.289569 34.039695, -83.289355 34.03971, -83.28907 34.039716, -83.287589 34.039803, -83.285595 34.039887, -83.285487 34.039897, -83.285154 34.039931, -83.284942 34.039994, -83.284758 34.040045, -83.284631 34.040107, -83.28452 34.040216, -83.284452 34.040324, -83.284427 34.040455, -83.284374 34.040634, -83.284329 34.041188, -83.284866 34.041264, -83.285598 34.041368, -83.28591 34.041397, -83.286118 34.041393, -83.286375 34.041377, -83.286651 34.041336, -83.288104 34.040976, -83.288416 34.040902, -83.288573 34.040873, -83.288705 34.04086, -83.288856 34.040863, -83.28917 34.040862, -83.289637 34.040911, -83.290085 34.040944, -83.290286 34.040946, -83.290462 34.040932, -83.290749 34.040864, -83.291023 34.040754, -83.2911 34.040709, -83.291154 34.040759, -83.291399 34.040842, -83.29153 34.040871, -83.291674 34.040889, -83.291823 34.040886, -83.292034 34.040872, -83.292269 34.040847, -83.292456 34.040843, -83.292612 34.040906, -83.292684 34.04101, -83.292719 34.04118, -83.292728 34.041273, -83.292701 34.041418, -83.292418 34.04222, -83.29223 34.042839, -83.29215 34.043105, -83.292 34.043563, -83.291979 34.043687, -83.291976 34.043796, -83.292034 34.043888, -83.29213 34.04399, -83.292232 34.044086, -83.29231 34.044204, -83.292389 34.044611, -83.292453 34.044682, -83.292548 34.044732, -83.292642 34.044762, -83.293194 34.044715, -83.293375 34.044738, -83.293461 34.044758, -83.293494 34.044766, -83.293758 34.044866, -83.294078 34.044988, -83.29429 34.045026, -83.294552 34.045083, -83.294747 34.045152, -83.294867 34.045232, -83.295059 34.045415, -83.2952 34.045519, -83.295408 34.045111, -83.295542 34.044858, -83.295653 34.044979, -83.295757 34.04507, -83.29584 34.045136, -83.296095 34.045165, -83.296269 34.045238, -83.296363 34.045337, -83.296458 34.045471, -83.296544 34.045621, -83.296558 34.045773, -83.296552 34.045863, -83.296536 34.046095, -83.296552 34.046314, -83.296589 34.04655, -83.296655 34.046735, -83.296667 34.046811, -83.29664 34.046921, -83.296616 34.046962, -83.297334 34.047077, -83.297967 34.047192, -83.298545 34.047324, -83.298625 34.047121, -83.299004 34.0461, -83.299092 34.04583, -83.299601 34.045929, -83.30012 34.046023, -83.300369 34.046082, -83.300659 34.04618, -83.304483 34.047364, -83.304416 34.047534, -83.304156 34.048109, -83.304097 34.04828, -83.304062 34.048444, -83.304015 34.04873, -83.303948 34.049212, -83.303924 34.049386, -83.303923 34.0494, -83.303926 34.049461, -83.303937 34.049629, -83.303973 34.049799, -83.304064 34.049948, -83.304477 34.050395, -83.304697 34.05066, -83.304842 34.050819, -83.304973 34.050963, -83.305176 34.051188, -83.305292 34.051331, -83.305586 34.051236, -83.306164 34.051091, -83.30732 34.050682, -83.308259 34.050273, -83.309042 34.049947, -83.309246 34.049863, -83.309487 34.049671, -83.30968 34.049406, -83.309787 34.049011, -83.30984 34.048816, -83.309896 34.048611, -83.309872 34.047937, -83.30968 34.047431, -83.309572 34.047208, -83.309342 34.046733, -83.30908 34.046354, -83.308596 34.045842, -83.308306 34.04559, -83.307849 34.045191, -83.306885 34.044652, -83.306304 34.044328, -83.306309 34.044227, -83.306322 34.044017, -83.306322 34.043869, -83.306323 34.043223, -83.306317 34.042989, -83.306272 34.042778, -83.306264 34.042758, -83.306174 34.042535, -83.306033 34.042288, -83.305882 34.042101, -83.305093 34.041125, -83.304911 34.040914, -83.304833 34.040769, -83.304902 34.040734, -83.305693 34.04041, -83.306799 34.039946, -83.307375 34.039686, -83.307578 34.039601, -83.309185 34.038926, -83.309821 34.038659, -83.309935 34.038611, -83.310435 34.038403, -83.310913 34.038169, -83.311092 34.038081, -83.311244 34.037995, -83.311489 34.037856, -83.311661 34.037744, -83.311855 34.037618, -83.311931 34.037548, -83.31204 34.037461, -83.312304 34.037231, -83.312432 34.037086, -83.312676 34.03681, -83.312944 34.03642, -83.313515 34.03559, -83.313599 34.035445, -83.315655 34.03195, -83.316773 34.030068, -83.317422 34.028857, -83.317719 34.02879, -83.317912 34.028721, -83.318316 34.028518, -83.319485 34.027978, -83.320639 34.027438, -83.321465 34.027097, -83.321913 34.026831, -83.323596 34.027461, -83.323675 34.027491, -83.326761 34.028582, -83.326723 34.028664, -83.326644 34.028856, -83.326604 34.028953, -83.326549 34.029086, -83.326423 34.029363, -83.326327 34.029696, -83.326181 34.03031, -83.325944 34.031314, -83.325824 34.031893, -83.325583 34.033061, -83.325503 34.033448, -83.325397 34.033912, -83.325252 34.034552, -83.324982 34.035738, -83.324687 34.037037, -83.324558 34.037604, -83.325293 34.037582, -83.325966 34.037542, -83.326245 34.03749, -83.32659 34.037352, -83.326778 34.037246, -83.327028 34.036951, -83.327286 34.036526, -83.327292 34.036506, -83.328874 34.035793, -83.329408 34.035612, -83.329642 34.03551, -83.32972 34.03559, -83.329798 34.03561, -83.330057 34.035573, -83.330468 34.035468, -83.330737 34.035355, -83.330839 34.035288, -83.330877 34.035271, -83.330903 34.035255, -83.331069 34.035155, -83.33131 34.034978, -83.331417 34.034892, -83.332466 34.034429, -83.332849 34.034155, -83.332902 34.034138, -83.333757 34.033854, -83.333986 34.033689, -83.334223 34.033442, -83.334513 34.033415, -83.335161 34.033468, -83.335443 34.033537, -83.335543 34.033622, -83.335771 34.033991, -83.336038 34.034197, -83.336382 34.034387, -83.336893 34.034509, -83.337557 34.034578, -83.339087 34.034523, -83.339899 34.034494, -83.340783 34.034591, -83.341051 34.034399, -83.342058 34.034125, -83.342814 34.033835, -83.342935 34.033735, -83.345833 34.034801, -83.346877 34.035191, -83.346906 34.035202, -83.346802 34.034503, -83.346741 34.03385, -83.346621 34.033167, -83.346526 34.032734, -83.346467 34.032691, -83.346014 34.032305, -83.34548 34.03168, -83.346612 34.031212, -83.346815 34.030794, -83.346849 34.030298, -83.347221 34.029485, -83.347413 34.028718, -83.348115 34.026784, -83.348443 34.026633, -83.349084 34.026549, -83.349736 34.02634, -83.350022 34.026248, -83.350617 34.025877, -83.351258 34.025331, -83.351327 34.025273, -83.352052 34.024381, -83.352494 34.023711, -83.352723 34.023038, -83.353051 34.022489, -83.353479 34.021173, -83.353646 34.020857, -83.353959 34.020473, -83.354982 34.019966, -83.355592 34.019826, -83.356515 34.019868, -83.357454 34.020045, -83.35782 34.020002, -83.35795 34.01984, -83.35795 34.019634, -83.357751 34.019235, -83.357881 34.01865, -83.357866 34.018416, -83.357171 34.017291, -83.357126 34.017168, -83.357118 34.016893, -83.35724 34.016467, -83.35753 34.016113, -83.357881 34.015564, -83.35811 34.015314, -83.359231 34.014394, -83.359887 34.014258, -83.360795 34.014204, -83.361391 34.014009, -83.361481 34.01386, -83.36153 34.013777, -83.361589 34.013682, -83.36197 34.013683, -83.362016 34.013545, -83.36197 34.013174, -83.362199 34.01279, -83.362115 34.012545, -83.362115 34.012367, -83.362245 34.012117, -83.362329 34.011762, -83.362291 34.011078, -83.362344 34.010954, -83.362802 34.010526, -83.363 34.009567, -83.363229 34.009364, -83.363542 34.00921, -83.364076 34.009239, -83.364404 34.009143, -83.364534 34.009047, -83.364778 34.008607, -83.365015 34.007662, -83.365119 34.007596, -83.365082 34.007491, -83.364866 34.007028, -83.364436 34.006133, -83.364046 34.005282, -83.363952 34.005036, -83.363925 34.004775, -83.363901 34.00444, -83.363922 34.004161, -83.364032 34.002686, -83.364139 34.000533, -83.364347 34.00054, -83.364479 34.000538, -83.364553 34.000517, -83.3646 34.000492, -83.364646 34.000452, -83.36471 34.000388, -83.364831 34.000303, -83.365104 34.000133, -83.365341 34.00001, -83.365486 33.999949, -83.365643 33.999928, -83.366162 33.999918, -83.366294 33.999935, -83.36658 34.000036, -83.366694 34.000061, -83.366831 34.000062, -83.366986 34.000048, -83.367122 34.000025, -83.367332 34.000004, -83.367834 34.000034, -83.36806 34.000072, -83.36823 34.000081, -83.368348 34.000066, -83.368531 34.000035, -83.368685 34.000001, -83.368731 33.999986, -83.368959 33.999754, -83.369249 33.999249, -83.369501 33.999095, -83.369776 33.998763, -83.370424 33.998558, -83.37105 33.998336, -83.371431 33.998089, -83.371538 33.99802, -83.371992 33.997551, -83.372019 33.997523, -83.372362 33.997032, -83.372675 33.99682, -83.372873 33.99651, -83.372789 33.996012, -83.372667 33.995589, -83.372705 33.995204, -83.373209 33.995023, -83.373506 33.994793, -83.373545 33.995474, -83.37369 33.995903, -83.374071 33.996547, -83.374407 33.997297, -83.374559 33.997598, -83.374803 33.998081, -83.374898 33.998675, -83.374929 33.999197, -83.375146 33.9997, -83.374541 34.000116, -83.374423 34.000263, -83.374462 34.000538, -83.374267 34.000765, -83.374007 34.000941, -83.374058 34.001337, -83.374017 34.001529, -83.374146 34.001823, -83.375155 34.002509, -83.375417 34.00258, -83.375575 34.00299, -83.375491 34.003267, -83.375648 34.00346, -83.375577 34.0036, -83.375295 34.0036, -83.375133 34.003693, -83.375169 34.003935, -83.375132 34.004097, -83.374953 34.004265, -83.374625 34.004305, -83.37461 34.004487, -83.374724 34.004825, -83.374942 34.0051, -83.375369 34.005361, -83.375784 34.005741, -83.376039 34.005882, -83.376296 34.005994, -83.376221 34.005536, -83.377119 34.004543, -83.37757 34.004059, -83.377664 34.003942, -83.37778 34.003724, -83.377927 34.003405, -83.37833 34.003492, -83.378467 34.003507, -83.378629 34.003518, -83.378892 34.003517, -83.379074 34.003498, -83.379305 34.003453, -83.379464 34.003414, -83.379633 34.003363, -83.379841 34.003229, -83.379876 34.003219, -83.380459 34.002842, -83.380859 34.002654, -83.381046 34.002572, -83.381131 34.002546, -83.381849 34.00226, -83.382091 34.002149, -83.382244 34.002067, -83.382435 34.001947, -83.38279 34.001695, -83.382967 34.001583, -83.383164 34.001415, -83.383273 34.001293, -83.383457 34.000979, -83.383515 34.000792, -83.383425 34.000748, -83.381715 33.999859, -83.38154 33.999751, -83.381455 33.999688, -83.38129 33.999553, -83.381129 33.999366, -83.380783 33.999019, -83.380282 33.998415, -83.380091 33.99816, -83.380008 33.997995, -83.379872 33.997723, -83.379834 33.99759, -83.37997 33.996715, -83.38002 33.996531, -83.380187 33.995911, -83.380285 33.995548, -83.380419 33.994843, -83.380434 33.994784, -83.380528 33.994407, -83.380969 33.992633, -83.381537 33.99017, -83.381504 33.990142, -83.378445 33.989556, -83.378188 33.989482, -83.377916 33.988574, -83.377528 33.987026, -83.377429 33.986631, -83.377337 33.986248, -83.377233 33.98581, -83.377197 33.985607, -83.377179 33.985505, -83.377051 33.985047, -83.376981 33.984589, -83.376949 33.984292, -83.376945 33.983717, -83.376935 33.983372, -83.376959 33.982958, -83.376528 33.983155, -83.376357 33.983224, -83.376193 33.98328, -83.375965 33.983317, -83.375563 33.983338, -83.374317 33.983368, -83.374088 33.983379, -83.373185 33.983525, -83.37301 33.983495, -83.372794 33.983381, -83.372044 33.98279, -83.371003 33.98197, -83.370407 33.981537, -83.370141 33.981325, -83.369956 33.981204, -83.36958 33.981027, -83.369142 33.980891, -83.368665 33.980756, -83.368521 33.980686, -83.367046 33.979857, -83.366565 33.979609, -83.366317 33.979456, -83.366052 33.979317, -83.365938 33.979279, -83.365845 33.979249, -83.365656 33.979259, -83.365468 33.979308, -83.365359 33.97937, -83.364138 33.980106, -83.364112 33.980129, -83.364027 33.980182, -83.363817 33.980432, -83.363515 33.980972, -83.363807 33.981277, -83.363903 33.982025, -83.363854 33.982018, -83.363735 33.982021, -83.36266 33.98193, -83.362495 33.981939, -83.362218 33.981925, -83.362075 33.981901, -83.361815 33.98176, -83.361883 33.981639, -83.362521 33.980682, -83.362705 33.980536, -83.362891 33.980249, -83.362942 33.980156, -83.363226 33.979627, -83.363382 33.979336, -83.363489 33.979122, -83.363455 33.979403, -83.363466 33.979505, -83.363511 33.979601, -83.363579 33.979663, -83.363686 33.979742, -83.36381 33.979798, -83.363895 33.979821, -83.36403 33.979815, -83.364109 33.979809, -83.36421 33.979787, -83.364295 33.979747, -83.36465 33.979527, -83.364825 33.979415, -83.364926 33.97933, -83.364988 33.979246, -83.365016 33.979184, -83.365033 33.979082, -83.365028 33.978992, -83.365005 33.978913, -83.364937 33.978817, -83.364853 33.978738, -83.36474 33.978671, -83.364633 33.978637, -83.364549 33.97862, -83.364369 33.978598, -83.364261 33.978574, -83.364098 33.978372, -83.365084 33.978394, -83.365422 33.9784, -83.366984 33.978366, -83.368162 33.978304, -83.369323 33.978293, -83.369605 33.978287, -83.370665 33.978242, -83.372581 33.978175, -83.374016 33.978134, -83.374777 33.978111, -83.374543 33.978299, -83.375755 33.978397, -83.377689 33.978483, -83.378167 33.978504, -83.378306 33.978009, -83.378357 33.977787, -83.378882 33.977673, -83.379187 33.977592, -83.379796 33.977426, -83.380391 33.97722, -83.380736 33.977077, -83.381226 33.976889, -83.381282 33.976856, -83.381652 33.976709, -83.381828 33.97664, -83.381915 33.976603, -83.38243 33.976387, -83.382677 33.976283, -83.383266 33.976066, -83.383493 33.975982, -83.383592 33.976075, -83.383646 33.976126, -83.384051 33.976508, -83.38468 33.976965, -83.384976 33.97718, -83.385832 33.977801, -83.385988 33.977905, -83.386537 33.978321, -83.386952 33.978694, -83.387371 33.979181, -83.387667 33.979652, -83.387951 33.980105, -83.388502 33.981074, -83.388721 33.98146, -83.389056 33.982012, -83.389354 33.982617, -83.389453 33.982909, -83.389566 33.983435, -83.389727 33.984381, -83.389797 33.984866, -83.389913 33.985662, -83.390056 33.986584, -83.390158 33.987129, -83.390605 33.989525, -83.390678 33.990074, -83.390666 33.990395, -83.390623 33.990697, -83.390516 33.991254, -83.389874 33.993378, -83.38944 33.994836, -83.389765 33.994995, -83.390063 33.99502, -83.390437 33.99491, -83.391016 33.994636, -83.391299 33.994647, -83.391695 33.994774, -83.39197 33.995005, -83.392222 33.995291, -83.392268 33.995582, -83.392237 33.995909, -83.392382 33.996157, -83.39268 33.996459, -83.392817 33.996649, -83.392848 33.997034, -83.393076 33.997474, -83.393076 33.997776, -83.393 33.997966, -83.392764 33.998172, -83.392207 33.998476, -83.392092 33.998762, -83.392237 33.998875, -83.39242 33.998847, -83.39326 33.998403, -83.393542 33.998323, -83.393824 33.998434, -83.393939 33.998599, -83.393786 33.999049, -83.393885 33.999407, -83.394038 33.999736, -83.394027 33.999774, -83.394049 34.000116, -83.393889 34.000292, -83.393673 34.000309, -83.393291 34.000304, -83.3932 34.000469, -83.393303 34.000577, -83.393462 34.00064, -83.393946 34.000704, -83.394499 34.0006, -83.394812 34.000623, -83.395074 34.000776, -83.395142 34.000993, -83.395166 34.001414, -83.395649 34.002258, -83.396156 34.002405, -83.39657 34.002711, -83.396618 34.002729, -83.397564 34.003972, -83.397802 34.004117, -83.398109 34.004211, -83.398312 34.004195, -83.398793 34.004, -83.398976 34.003984, -83.399205 34.00411, -83.399273 34.004217, -83.399304 34.004371, -83.399189 34.004577, -83.398876 34.004921, -83.398792 34.005454, -83.398464 34.006088, -83.398434 34.006347, -83.398518 34.006663, -83.398487 34.007006, -83.398487 34.00724, -83.398632 34.00754, -83.398731 34.007883, -83.398685 34.008117, -83.398883 34.008375, -83.399295 34.008703, -83.399546 34.008868, -83.399745 34.008829, -83.400119 34.008552, -83.400645 34.008223, -83.40111 34.007989, -83.401606 34.007701, -83.401767 34.007644, -83.402118 34.007658, -83.402591 34.00793, -83.403056 34.008367, -83.403285 34.008642, -83.403621 34.009467, -83.403964 34.009712, -83.405284 34.010054, -83.405848 34.010312, -83.407019 34.010686, -83.407042 34.010782, -83.407067 34.010882, -83.407863 34.011061, -83.40829 34.011172, -83.408656 34.011241, -83.409312 34.011472, -83.410159 34.011802, -83.410785 34.012031, -83.411197 34.012056, -83.411662 34.01196, -83.412418 34.011672, -83.41283 34.01148, -83.413425 34.011148, -83.413822 34.01086, -83.41402 34.01075, -83.41418 34.010775, -83.414363 34.010942, -83.414577 34.011272, -83.414829 34.011709, -83.415058 34.012218, -83.415172 34.012435, -83.415355 34.012683, -83.415676 34.013216, -83.41579 34.013491, -83.415752 34.013749, -83.416022 34.013711, -83.416019 34.013667, -83.415851 34.013134, -83.415553 34.01271, -83.415271 34.012064, -83.415553 34.011954, -83.415736 34.011968, -83.415782 34.011996, -83.415805 34.012133, -83.415935 34.012161, -83.416393 34.012048, -83.416561 34.012049, -83.416759 34.012145, -83.416988 34.012145, -83.417156 34.012087, -83.417484 34.012101, -83.417667 34.012071, -83.417964 34.011992, -83.418178 34.011951, -83.418308 34.011934, -83.418491 34.01177, -83.418689 34.011728, -83.419048 34.011839, -83.419368 34.011729, -83.419696 34.011768, -83.42004 34.012029, -83.420375 34.012057, -83.420848 34.01256, -83.421367 34.012851, -83.421741 34.012783, -83.422138 34.012475, -83.42316 34.011981, -83.423671 34.011896, -83.424282 34.011954, -83.424465 34.012018, -83.424808 34.012447, -83.425602 34.013019, -83.42612 34.013635, -83.426177 34.013661, -83.426228 34.013776, -83.42635 34.013993, -83.426499 34.014345, -83.426788 34.015069, -83.426811 34.015132, -83.426869 34.015289, -83.426892 34.01535, -83.427033 34.015674, -83.427109 34.015794, -83.427203 34.015941, -83.427302 34.016108, -83.427415 34.016281, -83.427507 34.016403, -83.427652 34.016524, -83.427791 34.016623, -83.427939 34.01672, -83.430006 34.018128, -83.43093 34.01873, -83.432058 34.019491, -83.432464 34.019748, -83.433023 34.020131, -83.433508 34.020527, -83.434145 34.021151, -83.434617 34.021604, -83.436971 34.023861, -83.437558 34.023588, -83.437639 34.023537, -83.437751 34.023466, -83.438071 34.023243, -83.438632 34.022853, -83.438682 34.022818, -83.438925 34.022648, -83.439079 34.022566, -83.439186 34.022525, -83.439266 34.022506, -83.439388 34.022504, -83.439622 34.022527, -83.440411 34.022647, -83.440573 34.022638, -83.440694 34.022596, -83.4408 34.022521, -83.440904 34.022372, -83.441077 34.022031, -83.44129 34.021435, -83.441435 34.02097, -83.441663 34.02016, -83.441708 34.01986, -83.441695 34.019646, -83.441663 34.019455, -83.441617 34.019276, -83.441544 34.019091, -83.441441 34.018854, -83.441225 34.018407, -83.441217 34.018126, -83.441241 34.017759, -83.441284 34.017518, -83.441306 34.017095, -83.441296 34.016743, -83.441241 34.016346, -83.441161 34.016024, -83.440986 34.015468, -83.440901 34.015234, -83.440771 34.01496, -83.440472 34.014548, -83.440089 34.014189, -83.439403 34.013479, -83.439141 34.012983, -83.439077 34.012519, -83.43912 34.012236, -83.439283 34.012022, -83.439633 34.011818, -83.440135 34.011639, -83.440255 34.011593, -83.440603 34.01146, -83.44087 34.011328, -83.441136 34.011168, -83.441618 34.010849, -83.441983 34.010602, -83.442871 34.009725, -83.443231 34.009295, -83.443513 34.008688, -83.45261 34.005283, -83.453657 34.004903, -83.453862 34.004872, -83.454025 34.004809, -83.454347 34.004685, -83.459385 34.002738, -83.45906 34.002641, -83.458165 34.002289, -83.457976 34.00218, -83.457802 34.002014, -83.457482 34.001485, -83.457267 34.001052, -83.457171 34.000631, -83.456912 33.999283, -83.456992 33.999126, -83.457373 33.998823, -83.457838 33.998518, -83.458004 33.99836, -83.458033 33.998204, -83.458027 33.99723, -83.458086 33.997103, -83.458249 33.99686, -83.45875 33.996458, -83.459013 33.996366, -83.459009 33.996211, -83.45911 33.996087, -83.459358 33.995974, -83.459996 33.995748, -83.460453 33.995652, -83.460825 33.995466, -83.461045 33.995263, -83.461243 33.994969, -83.461378 33.994568, -83.461345 33.994224, -83.461153 33.993852, -83.46091 33.993598, -83.460526 33.993366, -83.45993 33.993128, -83.458617 33.991304, -83.456038 33.993529, -83.454714 33.994041, -83.454616 33.993685, -83.454609 33.993448, -83.454861 33.992306, -83.454907 33.9922, -83.454995 33.992006, -83.455121 33.991855, -83.455329 33.991653, -83.455862 33.991168, -83.456057 33.990936, -83.456121 33.990688, -83.456102 33.990451, -83.456069 33.990023, -83.456172 33.989994, -83.45627 33.989956, -83.456364 33.989912, -83.456454 33.989862, -83.456539 33.989805, -83.456617 33.989744, -83.457084 33.989315, -83.457767 33.988689, -83.458203 33.98833, -83.458307 33.988251, -83.458476 33.98814, -83.458657 33.988041, -83.458848 33.987956, -83.459046 33.987884, -83.45925 33.987823, -83.459663 33.987714, -83.46008 33.98762, -83.461662 33.98723, -83.462731 33.986984, -83.462831 33.986961, -83.463624 33.986779, -83.464768 33.986559, -83.465027 33.986529, -83.465065 33.986525, -83.465208 33.986991, -83.465354 33.987609, -83.465404 33.98772, -83.465443 33.987853, -83.465482 33.987987, -83.465521 33.98812, -83.465574 33.988302, -83.4656 33.988387, -83.46564 33.98852, -83.46568 33.988654, -83.465711 33.988757, -83.46575 33.988903, -83.465802 33.989008, -83.465869 33.989103, -83.465913 33.98914, -83.466 33.989205, -83.46605 33.989245, -83.46616 33.989326, -83.466294 33.98944, -83.466386 33.989546, -83.466455 33.989655, -83.466499 33.98977, -83.466575 33.990036, -83.466715 33.990449, -83.466866 33.991129, -83.46693 33.991223, -83.46702 33.991307, -83.467066 33.991332, -83.467137 33.991365, -83.467271 33.991402, -83.467384 33.99141, -83.467543 33.991404, -83.467822 33.99138, -83.468007 33.991361, -83.468238 33.991346, -83.469005 33.991307, -83.469312 33.991323, -83.469378 33.99132, -83.46956 33.991343, -83.469672 33.991358, -83.469943 33.991336, -83.470158 33.991332, -83.470379 33.991308, -83.470479 33.991272, -83.470527 33.99124, -83.470558 33.991222, -83.470584 33.991153, -83.470577 33.990948, -83.47054 33.9906, -83.470522 33.990341, -83.470502 33.990052, -83.470498 33.989984, -83.470483 33.989769, -83.470461 33.98964, -83.470451 33.989579, -83.470439 33.989504, -83.470414 33.989196, -83.470398 33.989044, -83.470505 33.988637, -83.47052 33.988556, -83.470546 33.988421, -83.47058 33.988172, -83.470571 33.988038, -83.47054 33.987856, -83.470485 33.987521, -83.470477 33.987259, -83.471158 33.987689, -83.471164 33.987513, -83.471142 33.986351, -83.471148 33.986174, -83.471146 33.986109, -83.471266 33.986023, -83.471664 33.985832, -83.472323 33.985518, -83.472491 33.985418, -83.472729 33.985278, -83.473036 33.985104, -83.473039 33.983551, -83.473395 33.983545, -83.473383 33.982544, -83.473663 33.982539, -83.474264 33.981508, -83.474346 33.981252, -83.474713 33.980212, -83.474861 33.979917, -83.475183 33.979462, -83.475399 33.9791, -83.475546 33.978824, -83.475561 33.978512, -83.475591 33.978219, -83.475717 33.977983, -83.475815 33.977851, -83.475704 33.977794, -83.473708 33.977831, -83.472483 33.977688, -83.471159 33.977401, -83.470316 33.977169, -83.470081 33.977077, -83.469761 33.97691, -83.469591 33.976776, -83.469371 33.976635, -83.469218 33.976527, -83.469009 33.976469, -83.468818 33.976439, -83.46866 33.976442, -83.468423 33.976557, -83.468264 33.976648, -83.468235 33.976665, -83.467964 33.976884, -83.467686 33.977035, -83.467488 33.977117, -83.466985 33.977362, -83.4634 33.978708, -83.462445 33.979074, -83.462098 33.979159, -83.461628 33.979243, -83.461136 33.978595, -83.460366 33.977883, -83.459751 33.977427, -83.459005 33.976989, -83.456638 33.975725, -83.456321 33.975556, -83.456868 33.974797, -83.456296 33.974353, -83.456166 33.974178, -83.456131 33.973968, -83.456253 33.973603, -83.45634 33.973332, -83.456625 33.97292, -83.456805 33.972683, -83.457233 33.972096, -83.457274 33.972057, -83.457489 33.971848, -83.457633 33.971703, -83.457082 33.971296, -83.456459 33.970904, -83.456094 33.97095, -83.45532 33.970762, -83.455411 33.970494, -83.455602 33.970054, -83.455937 33.969738, -83.456265 33.969669, -83.456609 33.969768, -83.456853 33.969796, -83.457334 33.969659, -83.457662 33.969629, -83.458226 33.969684, -83.458737 33.969849, -83.459424 33.970256, -83.459806 33.970187, -83.460065 33.969956, -83.460332 33.969421, -83.460759 33.969157, -83.461225 33.968841, -83.460233 33.968187, -83.459539 33.967705, -83.458928 33.967227, -83.458402 33.966831, -83.456868 33.965938, -83.456327 33.9655, -83.456174 33.965146, -83.456227 33.964734, -83.456487 33.964431, -83.456784 33.964264, -83.458051 33.96458, -83.458577 33.964523, -83.459287 33.964265, -83.460355 33.963715, -83.460935 33.963372, -83.461606 33.962916, -83.461835 33.962353, -83.46185 33.961998, -83.461591 33.961242, -83.461514 33.96082, -83.461686 33.96082, -83.4619 33.960819, -83.462189 33.960818, -83.464755 33.960863, -83.465773 33.960868, -83.465981 33.959552, -83.466591 33.959598, -83.466644 33.959588, -83.466737 33.959587, -83.466919 33.959585, -83.468103 33.959591, -83.46827 33.959593, -83.469439 33.959604, -83.469744 33.959571, -83.469987 33.959463, -83.470224 33.959305, -83.470382 33.959142, -83.470534 33.958927, -83.470602 33.958718, -83.470613 33.958459, -83.470613 33.958374, -83.470653 33.957923, -83.470619 33.95759, -83.470529 33.957336, -83.47041 33.957119, -83.470201 33.956802, -83.470077 33.956615, -83.469854 33.956186, -83.469601 33.955592, -83.469525 33.955403, -83.46931 33.954644, -83.469198 33.954192, -83.469659 33.954173, -83.470049 33.954135, -83.470695 33.953984, -83.47078 33.953961, -83.472245 33.953559, -83.473723 33.953119, -83.472138 33.950065, -83.471938 33.9493, -83.471882 33.948811, -83.471835 33.94839, -83.471824 33.948019, -83.471824 33.947996, -83.471915 33.947481, -83.472042 33.946966, -83.472301 33.946507, -83.472789 33.945834, -83.47313 33.94601, -83.473681 33.946255, -83.474398 33.946487, -83.474921 33.946585, -83.475324 33.946607, -83.475367 33.946842, -83.475398 33.947098, -83.475532 33.947243, -83.475725 33.947387, -83.475954 33.947501, -83.476227 33.947555, -83.476489 33.947599, -83.476892 33.94763, -83.477199 33.947614, -83.477494 33.947579, -83.477727 33.947446, -83.477792 33.947392, -83.4779 33.947304, -83.478002 33.947144, -83.477994 33.946859, -83.477991 33.946366, -83.477844 33.94415, -83.477828 33.943174, -83.478464 33.943072, -83.479625 33.942733, -83.479943 33.942688, -83.480436 33.942735, -83.481009 33.942715, -83.480952 33.94403, -83.480915 33.944306, -83.480709 33.944948, -83.48052 33.945398, -83.486496 33.946105, -83.486654 33.946122, -83.487298 33.946179, -83.487915 33.946266, -83.488687 33.946226, -83.488598 33.945348, -83.488597 33.94524, -83.488596 33.945075, -83.488595 33.944869, -83.488431 33.943309, -83.48834 33.943071, -83.487915 33.942252, -83.487801 33.941936, -83.487781 33.941827, -83.487779 33.941662, -83.4878 33.941363, -83.487813 33.94031, -83.488133 33.940315, -83.488902 33.940327, -83.489766 33.94025, -83.490536 33.940125, -83.491899 33.939942, -83.492307 33.939892, -83.493028 33.939806, -83.493382 33.939785, -83.494059 33.939828, -83.494415 33.939835, -83.494829 33.939961, -83.495048 33.940041, -83.495458 33.94018, -83.496004 33.940354, -83.49594 33.94048, -83.495578 33.941479, -83.495531 33.94156, -83.495518 33.941649, -83.495538 33.941737, -83.495598 33.94181, -83.495751 33.94187, -83.496028 33.941957, -83.495974 33.942102, -83.495707 33.942896, -83.495635 33.94316, -83.495574 33.943333, -83.495486 33.94354, -83.495643 33.943593, -83.496393 33.943675, -83.497469 33.94377, -83.497813 33.943814, -83.498237 33.94389, -83.499028 33.943953, -83.499155 33.943945, -83.499282 33.943915, -83.500073 33.943557, -83.500143 33.943525, -83.500278 33.943494, -83.500646 33.943362, -83.500772 33.943331, -83.500974 33.943267, -83.501072 33.943228, -83.501167 33.943185, -83.501257 33.943136, -83.501342 33.94308, -83.501379 33.943051, -83.501764 33.942072, -83.501807 33.942083, -83.501957 33.942119, -83.502018 33.942133, -83.502334 33.942218, -83.504753 33.942822, -83.505844 33.943087, -83.507731 33.943601, -83.510396 33.944423, -83.510771 33.944522, -83.510985 33.944583, -83.510915 33.944708, -83.510418 33.945355, -83.509571 33.946485, -83.509328 33.946834, -83.509072 33.947231, -83.508831 33.947634, -83.508723 33.947839, -83.508687 33.947924, -83.508656 33.948011, -83.508609 33.948186, -83.508582 33.948365, -83.508576 33.948545, -83.508495 33.949175, -83.508473 33.949394, -83.509084 33.949385, -83.510266 33.949341, -83.51075 33.949371, -83.511097 33.949397, -83.511331 33.949433, -83.511875 33.949596, -83.512315 33.949755, -83.512834 33.949899, -83.513347 33.950083, -83.513858 33.950213, -83.514312 33.950311, -83.514804 33.950355, -83.515231 33.950373, -83.515577 33.950353, -83.517166 33.950193, -83.517977 33.950116, -83.519302 33.950001, -83.520378 33.949899, -83.520546 33.949888, -83.52125 33.949816, -83.521497 33.950084, -83.521775 33.95036, -83.521847 33.950426, -83.522138 33.950694, -83.522278 33.950792, -83.522486 33.950956, -83.522803 33.951218, -83.522804 33.954665, -83.522803 33.956089, -83.52305 33.956124, -83.523093 33.956129, -83.523337 33.956178, -83.523387 33.956182, -83.523417 33.956185, -83.523445 33.956187, -83.523475 33.956189, -83.523504 33.95619, -83.523533 33.956191, -83.523561 33.956191, -83.52359 33.956191, -83.523619 33.95619, -83.523647 33.95619, -83.523676 33.956188, -83.523705 33.956187, -83.523734 33.956184, -83.523762 33.956182, -83.523791 33.956178, -83.523835 33.956174, -83.523863 33.956169, -83.523891 33.956165, -83.52392 33.95616, -83.523948 33.956155, -83.523976 33.956149, -83.524004 33.956143, -83.524032 33.956136, -83.524059 33.95613, -83.524086 33.956122, -83.52411 33.956116, -83.524133 33.956109, -83.524599 33.955968, -83.524708 33.955935, -83.524737 33.955927, -83.524765 33.955919, -83.524793 33.955913, -83.524821 33.955905, -83.524849 33.9559, -83.524877 33.955893, -83.524905 33.955888, -83.524933 33.955882, -83.524961 33.955878, -83.52499 33.955873, -83.525018 33.95587, -83.525047 33.955866, -83.525075 33.955863, -83.525104 33.95586, -83.525133 33.955859, -83.525161 33.955856, -83.52519 33.955855, -83.525219 33.955854, -83.525248 33.955854, -83.525477 33.955851, -83.525558 33.955851, -83.52555 33.956229, -83.525487 33.956586, -83.525093 33.956732, -83.523567 33.957742, -83.523328 33.957903, -83.523127 33.958315, -83.524541 33.958636, -83.52457 33.958579, -83.524601 33.958516, -83.524665 33.958339, -83.524876 33.958222, -83.524971 33.958179, -83.525392 33.957896, -83.526104 33.957386, -83.526683 33.95697, -83.527146 33.956616, -83.527294 33.956493, -83.527424 33.956356, -83.527538 33.956209, -83.527632 33.956054, -83.52775 33.955808, -83.527835 33.955648, -83.527978 33.955414, -83.528136 33.955188, -83.528277 33.955003, -83.528355 33.954925, -83.529035 33.95575, -83.529716 33.956574, -83.529782 33.956654, -83.529849 33.956735, -83.52994 33.956844, -83.53003 33.956953, -83.530328 33.95731, -83.53053 33.957552, -83.530625 33.957667, -83.530663 33.957713, -83.530943 33.958034, -83.531182 33.958331, -83.531632 33.958868, -83.531749 33.959007, -83.536704 33.959266, -83.538095 33.959335, -83.538021 33.959199, -83.537774 33.958754, -83.537628 33.958447, -83.537544 33.958239, -83.537509 33.958072, -83.537478 33.957854, -83.537477 33.957689, -83.53749 33.957525, -83.537514 33.957389, -83.537519 33.957304, -83.537539 33.957168, -83.537694 33.956517, -83.537859 33.95567, -83.537969 33.954855, -83.53799 33.954654, -83.538139 33.953625, -83.538152 33.953378, -83.538151 33.953131, -83.538131 33.952967, -83.538074 33.952752, -83.538005 33.952558, -83.537822 33.951987, -83.537704 33.951673, -83.537609 33.951353, -83.537407 33.950545, -83.53763 33.950498, -83.538063 33.950405, -83.538525 33.950307, -83.539221 33.950136, -83.539439 33.950072, -83.539834 33.949934, -83.540093 33.949832, -83.54082 33.949513, -83.541334 33.94926, -83.541783 33.949584, -83.542181 33.949828, -83.542404 33.949946, -83.542634 33.950058, -83.543064 33.950285, -83.543112 33.95031, -83.544066 33.950829, -83.545249 33.951488, -83.545551 33.951483, -83.54565 33.951715, -83.546175 33.952037, -83.546316 33.95211, -83.546366 33.952132, -83.546461 33.952174, -83.546643 33.95224, -83.54686 33.952297, -83.546926 33.952314, -83.547607 33.952478, -83.547661 33.952491, -83.547739 33.95251, -83.548437 33.952678, -83.548587 33.95239, -83.548738 33.952162, -83.548733 33.951833, -83.548724 33.951301, -83.546996 33.951101, -83.547007 33.94932, -83.547759 33.949559, -83.548028 33.949644, -83.548194 33.949685, -83.548352 33.949708, -83.548506 33.949715, -83.548655 33.949709, -83.548813 33.949688, -83.548956 33.949653, -83.549116 33.949592, -83.549776 33.949195, -83.548912 33.94825, -83.548842 33.948178, -83.548756 33.948111, -83.54865 33.948049, -83.54854 33.948003, -83.548423 33.94797, -83.548295 33.94795, -83.548172 33.947942, -83.548077 33.947943, -83.547765 33.947969, -83.547662 33.947977, -83.54704 33.94804, -83.54706 33.947248, -83.547768 33.947176, -83.55058 33.94689, -83.551729 33.946773, -83.552777 33.946692, -83.553466 33.946639, -83.555324 33.946607, -83.55646 33.94671, -83.557801 33.946908, -83.559284 33.947328, -83.559949 33.947569, -83.56 33.947425, -83.557973 33.94661, -83.558119 33.946157, -83.558215 33.945926, -83.558387 33.945486, -83.558477 33.945282, -83.558572 33.94511, -83.558655 33.944994, -83.558769 33.944861, -83.558888 33.944732, -83.558976 33.944653, -83.559123 33.944546, -83.559304 33.94443, -83.560134 33.943993, -83.560952 33.943571, -83.562858 33.942578, -83.56294 33.942534, -83.563078 33.94246, -83.564273 33.941817, -83.565765 33.940999, -83.566443 33.940638, -83.566703 33.940469, -83.566844 33.940354, -83.566996 33.940209, -83.567173 33.94001, -83.567252 33.939885, -83.567347 33.93968, -83.567414 33.939496, -83.567456 33.939335, -83.56748 33.939171, -83.567489 33.939006, -83.567478 33.938869, -83.567414 33.938488, -83.567391 33.938408, -83.567318 33.938255, -83.56722 33.938081, -83.567105 33.937914, -83.566948 33.937731, -83.566649 33.937419, -83.566299 33.937096, -83.565798 33.936602, -83.565126 33.935887, -83.564902 33.935615, -83.564814 33.935498, -83.56475 33.935372, -83.564664 33.935223, -83.564587 33.935071, -83.564474 33.934813, -83.564386 33.934577, -83.564348 33.934439, -83.564305 33.934282, -83.564147 33.933572, -83.564087 33.933254, -83.564021 33.932906, -83.563879 33.932134, -83.563832 33.931877, -83.563712 33.931288, -83.56361 33.930875, -83.563527 33.930627, -83.563393 33.930287, -83.563105 33.929649, -83.563004 33.929465, -83.562912 33.929286, -83.562832 33.929132, -83.562543 33.92865, -83.562238 33.928177, -83.562054 33.927891, -83.561807 33.927527, -83.561139 33.926586, -83.560482 33.925648, -83.560365 33.925451, -83.560296 33.925296, -83.560226 33.925313, -83.560135 33.925343, -83.55992 33.925385, -83.559827 33.925391, -83.559735 33.92539, -83.559456 33.925343, -83.559363 33.925312, -83.559275 33.925275, -83.55903 33.925133, -83.558941 33.925083, -83.558854 33.925034, -83.558769 33.924986, -83.558685 33.924938, -83.558522 33.924846, -83.558445 33.924803, -83.558371 33.924762, -83.5583 33.924722, -83.558244 33.924686, -83.558189 33.924652, -83.55814 33.924623, -83.558073 33.924574, -83.55802 33.924529, -83.557916 33.924467, -83.557951 33.924368, -83.557951 33.924302, -83.55795 33.924172, -83.55795 33.924066, -83.557947 33.924008, -83.557932 33.92389, -83.557905 33.923773, -83.557889 33.923714, -83.55787 33.923655, -83.557848 33.923598, -83.557825 33.923541, -83.557775 33.92343, -83.55775 33.923375, -83.557725 33.92332, -83.557699 33.923265, -83.557673 33.923211, -83.557619 33.923101, -83.557592 33.923046, -83.557565 33.92299, -83.557538 33.922936, -83.557515 33.922874, -83.557477 33.922752, -83.557462 33.922689, -83.557449 33.922625, -83.557436 33.922561, -83.55741 33.922431, -83.557395 33.922364, -83.557381 33.922296, -83.557369 33.922249, -83.557332 33.922091, -83.557317 33.922022, -83.5573 33.921951, -83.557285 33.92188, -83.557246 33.921666, -83.557233 33.921593, -83.55722 33.92152, -83.557179 33.921299, -83.557165 33.921224, -83.557151 33.921149, -83.557118 33.920997, -83.557101 33.92092, -83.557061 33.920768, -83.557035 33.920693, -83.557003 33.92062, -83.556928 33.920477, -83.556884 33.920409, -83.556788 33.920278, -83.556676 33.920154, -83.556614 33.920098, -83.55655 33.920045, -83.556483 33.919995, -83.556413 33.919948, -83.556381 33.919926, -83.55627 33.919859, -83.556198 33.919815, -83.556126 33.919772, -83.556054 33.919728, -83.555912 33.919641, -83.555698 33.919509, -83.555627 33.919465, -83.555557 33.919421, -83.555489 33.919377, -83.555422 33.919333, -83.555359 33.919287, -83.555242 33.919191, -83.55521 33.919156, -83.555189 33.919142, -83.555139 33.919096, -83.555096 33.919055, -83.555014 33.918981, -83.554913 33.919053, -83.554854 33.919102, -83.554815 33.919138, -83.554769 33.919182, -83.554719 33.919235, -83.554665 33.919294, -83.554608 33.919358, -83.554551 33.919428, -83.554493 33.919502, -83.554439 33.91958, -83.554386 33.919662, -83.554336 33.919747, -83.554287 33.919834, -83.55424 33.919921, -83.554193 33.920008, -83.554153 33.920083, -83.553706 33.919886, -83.552675 33.919434, -83.552608 33.919431, -83.55254 33.919428, -83.551751 33.919394, -83.550665 33.919462, -83.550373 33.919423, -83.550025 33.919322, -83.550055 33.919104, -83.550082 33.918692, -83.550128 33.917655, -83.550183 33.917648, -83.550339 33.917627, -83.550416 33.917615, -83.550502 33.917601, -83.550597 33.917587, -83.5507 33.917572, -83.550924 33.917541, -83.551045 33.917529, -83.551172 33.917515, -83.551303 33.9175, -83.551375 33.917489, -83.551146 33.91706, -83.550837 33.91648, -83.550133 33.916749, -83.550119 33.916182, -83.550094 33.915161, -83.550067 33.91435, -83.55005 33.913967, -83.550055 33.913691, -83.550074 33.913508, -83.550112 33.913329, -83.550172 33.913156, -83.550209 33.913071, -83.550251 33.912988, -83.550404 33.912737, -83.550433 33.912695, -83.550805 33.912175, -83.551007 33.911914, -83.55116 33.911718, -83.551714 33.910944, -83.552098 33.910335, -83.552221 33.910107, -83.552598 33.909352, -83.552839 33.908765, -83.552934 33.908511, -83.552963 33.908404, -83.55297 33.908321, -83.552963 33.908264, -83.552899 33.908251, -83.5528 33.908254, -83.551912 33.908442, -83.550355 33.908803, -83.550008 33.908893, -83.54973 33.908979, -83.549522 33.909062, -83.549479 33.90909, -83.549369 33.909149, -83.54903 33.909369, -83.548471 33.909794, -83.548015 33.910156, -83.547809 33.910331, -83.547374 33.910662, -83.547145 33.910819, -83.546955 33.910927, -83.546729 33.911041, -83.546582 33.911103, -83.546426 33.911148, -83.546173 33.91121, -83.546044 33.91123, -83.545879 33.911241, -83.545649 33.911232, -83.545386 33.911214, -83.54519 33.911191, -83.544485 33.911051, -83.543465 33.910813, -83.543074 33.910718, -83.54185 33.910458, -83.54133 33.910356, -83.541155 33.910325, -83.540465 33.910201, -83.541001 33.912103, -83.541943 33.915389, -83.541685 33.915612, -83.541444 33.915789, -83.541109 33.915934, -83.540473 33.916158, -83.540222 33.916218, -83.540155 33.91632, -83.540101 33.916386, -83.540049 33.91642, -83.540019 33.916432, -83.539993 33.916449, -83.539973 33.916471, -83.539929 33.916544, -83.539745 33.91719, -83.539715 33.917239, -83.539673 33.917281, -83.539516 33.917382, -83.53943 33.917423, -83.539398 33.917428, -83.539332 33.917424, -83.539301 33.917415, -83.539225 33.917362, -83.539196 33.91735, -83.539163 33.917346, -83.5391 33.917362, -83.539075 33.917379, -83.539056 33.917402, -83.538822 33.917768, -83.538802 33.917799, -83.538705 33.917956, -83.538542 33.918232, -83.538481 33.918379, -83.538468 33.91844, -83.538461 33.918563, -83.538475 33.918763, -83.538292 33.918771, -83.537789 33.918815, -83.537613 33.918831, -83.536704 33.91892, -83.536489 33.918916, -83.535504 33.918895, -83.535177 33.918891, -83.534593 33.918986, -83.534011 33.919082, -83.533912 33.918892, -83.533416 33.917945, -83.533472 33.917487, -83.533647 33.91675, -83.53403 33.915914, -83.534095 33.915664, -83.534006 33.915168, -83.534062 33.914862, -83.534305 33.914483, -83.534324 33.913584, -83.53423 33.912836, -83.534279 33.912379, -83.534421 33.91203, -83.534676 33.911475, -83.534844 33.911076, -83.534989 33.910634, -83.534974 33.910376, -83.534943 33.910282, -83.534914 33.910175, -83.533056 33.910439, -83.532559 33.910504, -83.531766 33.910618, -83.530606 33.910779, -83.529486 33.910932, -83.529135 33.910975, -83.528015 33.911143, -83.527858 33.91118, -83.527739 33.911223, -83.527653 33.911261, -83.527521 33.911341, -83.527447 33.911393, -83.527381 33.911453, -83.527308 33.911543, -83.527236 33.911664, -83.527183 33.911792, -83.527147 33.911924, -83.527087 33.912294, -83.526955 33.912934, -83.526781 33.913607, -83.526702 33.913846, -83.526566 33.914225, -83.526459 33.914502, -83.526328 33.914794, -83.526125 33.915299, -83.526043 33.915523, -83.52596 33.91573, -83.525903 33.9159, -83.525859 33.916094, -83.525795 33.916316, -83.525725 33.916632, -83.525587 33.917377, -83.525533 33.917625, -83.525453 33.918056, -83.525 33.917982, -83.524641 33.917935, -83.52438 33.917919, -83.523952 33.917913, -83.523359 33.917925, -83.522801 33.917952, -83.52244 33.917981, -83.521985 33.918038, -83.521403 33.918126, -83.520426 33.918293, -83.520109 33.918344, -83.51899 33.918506, -83.518552 33.918578, -83.518218 33.918646, -83.517789 33.918753, -83.517464 33.918847, -83.51721 33.918935, -83.516838 33.919085, -83.516552 33.919227, -83.516356 33.919333, -83.516037 33.919534, -83.515833 33.919678, -83.515644 33.919836, -83.515405 33.920068, -83.515136 33.920352, -83.514851 33.920678, -83.51473 33.920811, -83.514606 33.920976, -83.514431 33.921245, -83.514092 33.921813, -83.513537 33.922693, -83.51339 33.922927, -83.513018 33.923546, -83.51225 33.923347, -83.512002 33.923298, -83.511696 33.923254, -83.511546 33.92324, -83.510991 33.923214, -83.510181 33.923197, -83.509535 33.923178, -83.508313 33.923142, -83.507946 33.923137, -83.507532 33.923114, -83.507139 33.923076, -83.506861 33.923037, -83.506521 33.922973, -83.506248 33.922911, -83.505413 33.922752, -83.5053 33.92273, -83.50406 33.922494, -83.502282 33.921998, -83.500268 33.921434, -83.50031 33.921303, -83.500316 33.921285, -83.50038 33.921136, -83.500437 33.921037, -83.500578 33.920819, -83.500755 33.920587, -83.500808 33.920487, -83.50085 33.920382, -83.500879 33.920247, -83.500899 33.920083, -83.5009 33.919953, -83.500905 33.919466, -83.500909 33.919007, -83.500929 33.918144, -83.50094 33.917897, -83.50106 33.917273, -83.501146 33.916979, -83.50122 33.916768, -83.501302 33.916588, -83.501476 33.916175, -83.501625 33.915838, -83.501765 33.915495, -83.50184 33.915325, -83.50156 33.915243, -83.501442 33.915214, -83.500169 33.9149, -83.499542 33.914742, -83.499065 33.914623, -83.49931 33.913757, -83.499607 33.912615, -83.499795 33.912028, -83.499897 33.911686, -83.500144 33.910864, -83.500149 33.910792, -83.500138 33.910733, -83.500153 33.910649, -83.500148 33.910577, -83.500221 33.910378, -83.500259 33.91032, -83.500265 33.910262, -83.500255 33.910194, -83.500239 33.91013, -83.500237 33.909952, -83.500241 33.909867, -83.500292 33.909774, -83.500367 33.909675, -83.500398 33.909672, -83.500613 33.909757, -83.500956 33.909031, -83.501216 33.908501, -83.501336 33.908496, -83.501924 33.908586, -83.502 33.908359, -83.502091 33.908073, -83.502199 33.907792, -83.502311 33.907551, -83.502382 33.907481, -83.502491 33.907416, -83.502624 33.90741, -83.502767 33.907442, -83.50299 33.907539, -83.503287 33.907675, -83.503439 33.907814, -83.503582 33.907961, -83.503671 33.908107, -83.503754 33.908305, -83.503813 33.908571, -83.503888 33.908951, -83.503966 33.909471, -83.503985 33.909593, -83.504199 33.909562, -83.504424 33.909465, -83.504667 33.90937, -83.504877 33.909249, -83.505288 33.909084, -83.505428 33.909029, -83.505873 33.908922, -83.506153 33.908841, -83.506334 33.908776, -83.506426 33.908707, -83.506391 33.908536, -83.506312 33.908046, -83.506242 33.9075, -83.506174 33.906925, -83.505977 33.905063, -83.505941 33.904789, -83.505892 33.904546, -83.505833 33.90436, -83.505807 33.904263, -83.505724 33.904026, -83.505562 33.903695, -83.505381 33.903402, -83.505249 33.903212, -83.50492 33.9028, -83.50471 33.902488, -83.504607 33.902316, -83.504407 33.901909, -83.504341 33.901732, -83.504008 33.900922, -83.503783 33.900395, -83.502944 33.898347, -83.502744 33.897875, -83.502617 33.897534, -83.502538 33.897295, -83.502489 33.897079, -83.502455 33.896861, -83.502436 33.896587, -83.502435 33.896395, -83.502452 33.89612, -83.502507 33.895794, -83.502126 33.896165, -83.501221 33.897065, -83.500234 33.898069, -83.499592 33.898707, -83.499349 33.898935, -83.498861 33.899344, -83.498652 33.899478, -83.49845 33.89957, -83.498301 33.899629, -83.498084 33.899694, -83.497737 33.899782, -83.497366 33.899868, -83.497041 33.899931, -83.496258 33.900103, -83.496106 33.90013, -83.496002 33.900152, -83.494808 33.900409, -83.494594 33.900452, -83.494329 33.900511, -83.494047 33.900573, -83.493416 33.9007, -83.493337 33.900717, -83.492594 33.90088, -83.492049 33.900991, -83.491686 33.901071, -83.491525 33.901106, -83.49084 33.901257, -83.490235 33.901409, -83.489661 33.901569, -83.489105 33.901743, -83.488935 33.901802, -83.48802 33.902094, -83.487285 33.90233, -83.484731 33.903128, -83.481432 33.904159, -83.478796 33.904919, -83.478162 33.905093, -83.47808 33.905116, -83.477038 33.905417, -83.476949 33.905445, -83.476804 33.905494, -83.47657 33.905595, -83.476513 33.905623, -83.47453 33.905879, -83.473846 33.899701, -83.474684 33.899934, -83.474997 33.899866, -83.47521 33.899607, -83.475134 33.899316, -83.475332 33.899247, -83.475691 33.899305, -83.475874 33.89947, -83.476187 33.899935, -83.47653 33.900127, -83.476958 33.900196, -83.477537 33.90021, -83.478079 33.900289, -83.47869 33.900512, -83.479353 33.900633, -83.479544 33.900729, -83.479643 33.900949, -83.479765 33.901125, -83.479864 33.901153, -83.480193 33.901084, -83.480437 33.900809, -83.480467 33.900548, -83.480406 33.900262, -83.480269 33.899957, -83.480307 33.899372, -83.480483 33.899053, -83.480833 33.898765, -83.481078 33.898726, -83.481673 33.898833, -83.482085 33.898858, -83.482199 33.898834, -83.482344 33.89879, -83.482512 33.898572, -83.482687 33.898449, -83.48271 33.898229, -83.482489 33.897819, -83.482603 33.897586, -83.482703 33.897157, -83.482604 33.896503, -83.482588 33.895926, -83.482588 33.895417, -83.482787 33.895239, -83.482977 33.895, -83.483046 33.894906, -83.482718 33.894675, -83.482703 33.894414, -83.48239 33.894167, -83.482306 33.893909, -83.482192 33.893634, -83.482916 33.893513, -83.483191 33.893375, -83.483542 33.893439, -83.483702 33.89337, -83.483511 33.89263, -83.483335 33.891781, -83.483301 33.891172, -83.483264 33.891036, -83.483036 33.890295, -83.482569 33.889537, -83.481309 33.889008, -83.479894 33.890045, -83.479759 33.890147, -83.479276 33.890498, -83.478927 33.890759, -83.478276 33.891277, -83.478147 33.891384, -83.477936 33.891559, -83.476954 33.892358, -83.476102 33.893042, -83.475766 33.893287, -83.474759 33.894001, -83.474362 33.894277, -83.474233 33.894367, -83.473963 33.89455, -83.473659 33.894757, -83.473168 33.895047, -83.473148 33.895059, -83.472788 33.895252, -83.472465 33.895403, -83.472328 33.895467, -83.471852 33.895678, -83.470233 33.896396, -83.470084 33.896172, -83.469533 33.895344, -83.469637 33.895303, -83.470053 33.895123, -83.470372 33.894974, -83.470421 33.894938, -83.470463 33.894895, -83.470509 33.894822, -83.470543 33.894745, -83.470546 33.894689, -83.470542 33.894578, -83.470534 33.894552, -83.47052 33.894527, -83.470465 33.894459, -83.470184 33.894193, -83.469041 33.893158, -83.468925 33.893061, -83.468869 33.893031, -83.468779 33.892997, -83.468686 33.89297, -83.468621 33.892963, -83.468522 33.892966, -83.468361 33.892996, -83.468272 33.893033, -83.468191 33.89308, -83.467821 33.893357, -83.467591 33.893544, -83.467492 33.893444, -83.467171 33.893172, -83.467061 33.893086, -83.467025 33.893057, -83.466973 33.893023, -83.466942 33.893006, -83.466779 33.892919, -83.46669 33.892883, -83.466563 33.892854, -83.4664 33.892836, -83.466202 33.892835, -83.466189 33.892602, -83.466183 33.892405, -83.466194 33.892351, -83.466272 33.892145, -83.466625 33.89139, -83.466643 33.891337, -83.466672 33.891202, -83.466668 33.891147, -83.466658 33.891121, -83.46663 33.891071, -83.46659 33.891027, -83.46654 33.890991, -83.466481 33.890966, -83.46609 33.890878, -83.465959 33.890866, -83.465894 33.890868, -83.46583 33.890881, -83.465742 33.890918, -83.46564 33.890988, -83.465436 33.891178, -83.46505 33.89157, -83.464752 33.891865, -83.464235 33.892398, -83.46401 33.892619, -83.463895 33.892741, -83.463806 33.892857, -83.46378 33.892907, -83.46377 33.893017, -83.463776 33.893044, -83.4638 33.893099, -83.464247 33.893729, -83.463566 33.894076, -83.463386 33.894196, -83.463202 33.894345, -83.463112 33.894438, -83.463016 33.894549, -83.46286 33.894791, -83.462765 33.894925, -83.462664 33.895034, -83.462556 33.895137, -83.462444 33.895216, -83.462335 33.895277, -83.462246 33.895313, -83.462029 33.895379, -83.461501 33.895526, -83.46119 33.895618, -83.460888 33.895702, -83.461331 33.896564, -83.461225 33.896586, -83.460844 33.896586, -83.460157 33.896231, -83.459921 33.89619, -83.458822 33.896602, -83.458028 33.896822, -83.457304 33.896849, -83.45635 33.897577, -83.455709 33.898044, -83.455068 33.89836, -83.454725 33.898662, -83.454687 33.899167, -83.454488 33.899431, -83.454061 33.899832, -83.453886 33.899775, -83.453321 33.899733, -83.452878 33.899183, -83.45255 33.898983, -83.452054 33.898993, -83.452024 33.899282, -83.452054 33.89957, -83.45197 33.89968, -83.451543 33.899774, -83.451314 33.89979, -83.451116 33.899708, -83.450849 33.899367, -83.45062 33.899199, -83.45049 33.899064, -83.450437 33.898792, -83.450635 33.898336, -83.450948 33.897899, -83.45123 33.897567, -83.451246 33.89735, -83.451208 33.897033, -83.451177 33.896676, -83.451047 33.896473, -83.450834 33.896445, -83.450384 33.89672, -83.449857 33.896758, -83.449682 33.896884, -83.449369 33.897527, -83.449056 33.897967, -83.448827 33.898462, -83.44853 33.898626, -83.448331 33.898626, -83.447934 33.898557, -83.447637 33.898695, -83.446851 33.898876, -83.446469 33.898859, -83.445859 33.898985, -83.445104 33.899122, -83.44505 33.899089, -83.444966 33.89904, -83.444967 33.89882, -83.444921 33.898683, -83.444394 33.898548, -83.444163 33.898471, -83.443654 33.898303, -83.443471 33.898303, -83.444328 33.902494, -83.444126 33.902529, -83.443799 33.902566, -83.443569 33.902578, -83.443319 33.902582, -83.443311 33.902126, -83.443331 33.901751, -83.44335 33.901521, -83.443372 33.9014, -83.443413 33.900763, -83.44341 33.900654, -83.443401 33.900574, -83.443381 33.90047, -83.44334 33.900317, -83.443044 33.899604, -83.44297 33.899433, -83.442918 33.899313, -83.442711 33.898811, -83.442615 33.898606, -83.442462 33.898321, -83.442293 33.898061, -83.442055 33.897746, -83.441582 33.897215, -83.441376 33.897003, -83.441339 33.89697, -83.44083 33.896515, -83.440681 33.896398, -83.440243 33.896076, -83.439446 33.89554, -83.439117 33.895311, -83.438184 33.896266, -83.437719 33.896744, -83.437677 33.896675, -83.437616 33.896631, -83.43757 33.896597, -83.437521 33.896559, -83.437467 33.896521, -83.437409 33.896483, -83.437347 33.896446, -83.437282 33.896412, -83.437211 33.89638, -83.437138 33.896352, -83.437058 33.896332, -83.436979 33.89632, -83.436896 33.89631, -83.436813 33.896305, -83.436733 33.896305, -83.436577 33.896321, -83.436499 33.896334, -83.436422 33.896353, -83.436349 33.896379, -83.436296 33.896406, -83.435971 33.896889, -83.435809 33.897093, -83.435812 33.897299, -83.435828 33.897344, -83.435757 33.897381, -83.435316 33.897605, -83.435125 33.897703, -83.434969 33.897458, -83.434948 33.897437, -83.434903 33.897396, -83.43488 33.897377, -83.434855 33.897359, -83.43483 33.897341, -83.434805 33.897324, -83.434778 33.897307, -83.434751 33.897292, -83.434723 33.897277, -83.434695 33.897263, -83.434666 33.897249, -83.434637 33.897237, -83.434607 33.897225, -83.434577 33.897214, -83.434546 33.897204, -83.434515 33.897195, -83.434484 33.897187, -83.434452 33.89718, -83.43442 33.897173, -83.434388 33.897168, -83.434355 33.897163, -83.434323 33.897159, -83.43429 33.897156, -83.434257 33.897154, -83.434224 33.897153, -83.434191 33.897153, -83.434158 33.897154, -83.434125 33.897156, -83.434092 33.897159, -83.43406 33.897163, -83.434027 33.897168, -83.433995 33.897174, -83.433963 33.897181, -83.433932 33.897189, -83.4339 33.897197, -83.43387 33.897207, -83.433839 33.897217, -83.433809 33.897229, -83.43378 33.897241, -83.43375 33.897254, -83.433722 33.897268, -83.433694 33.897282, -83.433666 33.897297, -83.43364 33.897313, -83.433613 33.89733, -83.433588 33.897347, -83.433563 33.897365, -83.433539 33.897384, -83.433515 33.897403, -83.433493 33.897423, -83.433471 33.897444, -83.43345 33.897465, -83.433426 33.897492, -83.433297 33.897635, -83.433281 33.897655, -83.433652 33.897979, -83.434384 33.898452, -83.434491 33.898417, -83.434918 33.898675, -83.4353 33.899214, -83.435681 33.899844, -83.43606 33.900166, -83.436261 33.900336, -83.436574 33.900707, -83.436917 33.901408, -83.43723 33.901666, -83.437444 33.902051, -83.437467 33.902381, -83.437253 33.90256, -83.436719 33.902697, -83.436314 33.902875, -83.436673 33.903257, -83.436747 33.903365, -83.436871 33.903549, -83.436841 33.90378, -83.436711 33.904041, -83.436261 33.90415, -83.43623 33.904164, -83.435834 33.904345, -83.435407 33.904397, -83.435094 33.904493, -83.434781 33.904796, -83.434712 33.905015, -83.434522 33.905139, -83.434254 33.905197, -83.434109 33.905565, -83.433926 33.905859, -83.433743 33.905867, -83.433057 33.905976, -83.432309 33.905976, -83.431584 33.905923, -83.431378 33.905962, -83.431195 33.905992, -83.431157 33.906129, -83.43137 33.906459, -83.431622 33.906789, -83.431592 33.906982, -83.431309 33.90724, -83.431029 33.90741, -83.430943 33.907462, -83.430684 33.907555, -83.43044 33.907572, -83.429745 33.907393, -83.429364 33.907327, -83.429204 33.907296, -83.428639 33.907296, -83.428196 33.907466, -83.428181 33.907656, -83.428297 33.907758, -83.428494 33.907931, -83.428753 33.908206, -83.428806 33.908393, -83.428906 33.90875, -83.429005 33.9093, -83.428937 33.909591, -83.428677 33.909907, -83.428181 33.910234, -83.428119 33.910306, -83.427565 33.909608, -83.427452 33.909461, -83.427402 33.90936, -83.427347 33.909201, -83.42732 33.909093, -83.427318 33.908956, -83.427528 33.908384, -83.427543 33.908275, -83.427536 33.908193, -83.427516 33.908112, -83.427491 33.908061, -83.427439 33.907991, -83.427228 33.907795, -83.426636 33.907353, -83.425625 33.90655, -83.425505 33.906475, -83.42539 33.906421, -83.425343 33.906553, -83.425281 33.90668, -83.42522 33.906778, -83.425128 33.906892, -83.425002 33.907018, -83.424883 33.907114, -83.42478 33.907182, -83.424614 33.907271, -83.424493 33.907315, -83.424336 33.907358, -83.424113 33.907408, -83.423983 33.907424, -83.422707 33.90749, -83.422511 33.907514, -83.422416 33.907535, -83.422233 33.907598, -83.421983 33.907706, -83.421763 33.907815, -83.421682 33.907728, -83.421631 33.907657, -83.421594 33.907581, -83.421501 33.907319, -83.421433 33.907051, -83.421387 33.906783, -83.421331 33.906378, -83.421271 33.90608, -83.421144 33.905599, -83.421095 33.9053, -83.421065 33.905026, -83.421073 33.90469, -83.421112 33.904111, -83.42114 33.903805, -83.421178 33.903615, -83.421227 33.903484, -83.421299 33.903331, -83.42136 33.903233, -83.421432 33.903141, -83.421566 33.90302, -83.421662 33.902944, -83.42182 33.902849, -83.422399 33.902534, -83.422778 33.902337, -83.423422 33.901995, -83.423616 33.901874, -83.423775 33.901769, -83.424645 33.901164, -83.425569 33.900522, -83.425519 33.900464, -83.425453 33.900403, -83.425376 33.900351, -83.425291 33.900309, -83.425169 33.900267, -83.424882 33.900205, -83.424687 33.90018, -83.424315 33.900158, -83.424101 33.900153, -83.423797 33.90014, -83.423535 33.900117, -83.423302 33.900071, -83.423164 33.900038, -83.422934 33.899983, -83.422613 33.899911, -83.422402 33.899864, -83.422184 33.899807, -83.421983 33.89974, -83.421866 33.89969, -83.42173 33.899613, -83.421419 33.899394, -83.421309 33.899334, -83.421189 33.89929, -83.421063 33.899257, -83.420965 33.899243, -83.420801 33.899233, -83.420637 33.899245, -83.420261 33.899306, -83.420096 33.899314, -83.419932 33.899304, -83.419802 33.899284, -83.419273 33.899158, -83.419039 33.899107, -83.418862 33.899068, -83.418734 33.899046, -83.418511 33.899017, -83.41789 33.898958, -83.416894 33.898887, -83.416665 33.898872, -83.415823 33.898829, -83.415453 33.898804, -83.415043 33.898776, -83.41488 33.898756, -83.414752 33.898728, -83.414599 33.898678, -83.414392 33.898593, -83.414253 33.898519, -83.414097 33.898418, -83.413954 33.898304, -83.413807 33.898156, -83.413723 33.898038, -83.413656 33.897925, -83.413207 33.897078, -83.41309 33.896912, -83.412972 33.89678, -83.41286 33.89668, -83.412709 33.896573, -83.412547 33.896478, -83.412373 33.8964, -83.412193 33.896332, -83.411236 33.895991, -83.410135 33.895605, -83.410319 33.895236, -83.410892 33.894151, -83.411021 33.893893, -83.411544 33.892896, -83.411399 33.892864, -83.410729 33.892779, -83.410535 33.892746, -83.410314 33.892691, -83.409973 33.892588, -83.409797 33.892513, -83.4096 33.892412, -83.409397 33.892285, -83.409064 33.892063, -83.408968 33.891998, -83.408632 33.891733, -83.4085 33.891608, -83.408192 33.891283, -83.407906 33.891002, -83.407498 33.890563, -83.40703 33.890037, -83.406899 33.889913, -83.406711 33.889759, -83.406319 33.889486, -83.406012 33.889279, -83.40552 33.889012, -83.405444 33.888971, -83.403731 33.888072, -83.40353 33.887977, -83.403278 33.887879, -83.402471 33.887616, -83.402209 33.887501, -83.401964 33.887371, -83.401352 33.887054, -83.401242 33.887006, -83.400977 33.886921, -83.400787 33.886875, -83.400593 33.886843, -83.400429 33.886831, -83.400098 33.886831, -83.399833 33.886844, -83.399635 33.886843, -83.399497 33.886828, -83.399356 33.886805, -83.39918 33.88676, -83.399012 33.887039, -83.39888 33.88728, -83.398765 33.887518, -83.398677 33.887735, -83.398598 33.888002, -83.398424 33.888733, -83.398351 33.889119, -83.398258 33.889469, -83.398156 33.88999, -83.398087 33.890423, -83.398071 33.890615, -83.398082 33.890779, -83.398121 33.890997, -83.398196 33.891269, -83.398314 33.891636, -83.398401 33.892012, -83.398419 33.892177, -83.398423 33.892369, -83.398389 33.892769, -83.398373 33.893029, -83.39833 33.893588, -83.398326 33.893642, -83.398304 33.894063, -83.398306 33.8942, -83.398319 33.894282, -83.398373 33.894525, -83.398497 33.894867, -83.398567 33.894992, -83.398809 33.895351, -83.398917 33.895489, -83.398982 33.895551, -83.399194 33.895737, -83.399605 33.89601, -83.399743 33.896085, -83.400514 33.896421, -83.400655 33.896492, -83.400751 33.896567, -83.400903 33.896712, -83.401477 33.897287, -83.401903 33.897696, -83.402022 33.89779, -83.402129 33.897855, -83.402217 33.897892, -83.402341 33.897931, -83.402437 33.89795, -83.402568 33.89796, -83.402667 33.897957, -83.402797 33.897939, -83.40297 33.897903, -83.403429 33.897766, -83.403561 33.897718, -83.403974 33.897571, -83.404132 33.897531, -83.404231 33.897533, -83.404407 33.897548, -83.404493 33.897556, -83.404625 33.897562, -83.404753 33.897585, -83.40491 33.89763, -83.404997 33.897668, -83.405127 33.897742, -83.405523 33.898037, -83.405796 33.898237, -83.406667 33.898807, -83.40701 33.898905, -83.407396 33.89897, -83.407788 33.898905, -83.407913 33.898828, -83.408126 33.898752, -83.408322 33.898654, -83.408447 33.898605, -83.408583 33.898551, -83.408811 33.898485, -83.409328 33.898499, -83.409243 33.899066, -83.409198 33.899232, -83.409154 33.899357, -83.409097 33.899494, -83.409018 33.899661, -83.408972 33.899745, -83.408938 33.899859, -83.408853 33.900096, -83.408656 33.900562, -83.408595 33.900678, -83.408378 33.900998, -83.408235 33.901181, -83.407707 33.901617, -83.407138 33.902147, -83.405877 33.903141, -83.405471 33.903491, -83.405399 33.903549, -83.405292 33.903486, -83.404931 33.903303, -83.404811 33.903261, -83.404685 33.903234, -83.404359 33.903206, -83.404232 33.903181, -83.403979 33.903104, -83.403639 33.902989, -83.403347 33.902856, -83.402602 33.90249, -83.402094 33.90222, -83.401695 33.902, -83.401484 33.901889, -83.401424 33.901867, -83.40113 33.901819, -83.400946 33.901803, -83.400637 33.90177, -83.400476 33.901742, -83.400353 33.901702, -83.400237 33.90165, -83.399861 33.90138, -83.399676 33.901256, -83.399344 33.900992, -83.399219 33.90086, -83.399053 33.900744, -83.39895 33.900687, -83.398848 33.900639, -83.398561 33.900534, -83.398183 33.90044, -83.398011 33.900824, -83.397911 33.900985, -83.397876 33.901079, -83.397853 33.901124, -83.39775 33.901245, -83.397724 33.901289, -83.39734 33.90219, -83.397304 33.902283, -83.397283 33.90238, -83.397263 33.902577, -83.39726 33.902774, -83.397274 33.902872, -83.397289 33.902919, -83.397329 33.903012, -83.397381 33.9033, -83.397412 33.903393, -83.39743 33.90356, -83.397387 33.903661, -83.397319 33.903757, -83.397117 33.90395, -83.397056 33.904008, -83.396257 33.904602, -83.396131 33.904725, -83.39607 33.904815, -83.396021 33.904988, -83.396035 33.905167, -83.396057 33.905216, -83.396128 33.905294, -83.396339 33.905424, -83.396599 33.905519, -83.39686 33.905568, -83.397464 33.905553, -83.397765 33.905503, -83.39805 33.90544, -83.398567 33.905309, -83.399412 33.905114, -83.399919 33.905005, -83.400015 33.904989, -83.400279 33.904975, -83.400757 33.904962, -83.40168 33.904965, -83.402064 33.904951, -83.40213 33.904955, -83.402226 33.904974, -83.40235 33.90501, -83.402559 33.905109, -83.403236 33.90548, -83.403329 33.905539, -83.403178 33.905735, -83.402971 33.906051, -83.402839 33.906289, -83.402584 33.906803, -83.401945 33.908128, -83.401719 33.908586, -83.401559 33.908903, -83.401397 33.909239, -83.400952 33.910131, -83.400856 33.910304, -83.400782 33.910447, -83.40067 33.910664, -83.400634 33.910657, -83.400475 33.910625, -83.400104 33.91043, -83.399567 33.910149, -83.398872 33.910122, -83.39843 33.910011, -83.398216 33.909794, -83.397652 33.909035, -83.39724 33.908848, -83.396767 33.90876, -83.396439 33.90863, -83.396286 33.908463, -83.396103 33.908226, -83.395348 33.908028, -83.394592 33.907806, -83.394295 33.90757, -83.393997 33.906952, -83.393787 33.906399, -83.393852 33.906193, -83.393852 33.906138, -83.393883 33.906025, -83.393658 33.904635, -83.393578 33.904144, -83.393587 33.904067, -83.393596 33.90399, -83.393619 33.903825, -83.393622 33.903768, -83.393665 33.902976, -83.393466 33.902291, -83.393024 33.901813, -83.392246 33.9014, -83.391651 33.900825, -83.391624 33.900797, -83.391218 33.900384, -83.391094 33.900259, -83.390811 33.899972, -83.39043 33.899714, -83.389987 33.899524, -83.389461 33.899413, -83.388072 33.899453, -83.38747 33.8994, -83.387044 33.899334, -83.38676 33.89929, -83.385882 33.899116, -83.384715 33.898936, -83.383861 33.898883, -83.3828 33.898649, -83.382472 33.898387, -83.382259 33.898098, -83.382182 33.897858, -83.382086 33.897915, -83.382111 33.897493, -83.382263 33.897189, -83.382566 33.896885, -83.382996 33.896718, -83.383312 33.896655, -83.383817 33.89656, -83.384221 33.896477, -83.384537 33.896256, -83.384777 33.895942, -83.384765 33.895502, -83.384411 33.895187, -83.383704 33.894731, -83.383781 33.894709, -83.383723 33.894686, -83.382946 33.894493, -83.382466 33.89418, -83.381865 33.893712, -83.381385 33.893626, -83.380297 33.893488, -83.379597 33.893267, -83.379033 33.893006, -83.378767 33.892703, -83.378523 33.892197, -83.378553 33.891375, -83.378467 33.891087, -83.378291 33.89093, -83.377696 33.890764, -83.37797 33.890668, -83.378617 33.890617, -83.379143 33.89056, -83.379309 33.890432, -83.379571 33.89023, -83.379745 33.890276, -83.379884 33.890313, -83.380867 33.890278, -83.381202 33.890092, -83.381704 33.890067, -83.381785 33.889965, -83.381856 33.889875, -83.381887 33.889479, -83.38202 33.8889, -83.382084 33.888677, -83.382174 33.888361, -83.382501 33.888089, -83.382774 33.888013, -83.383238 33.888043, -83.383762 33.887909, -83.384387 33.88766, -83.384906 33.887404, -83.385196 33.887265, -83.385593 33.887248, -83.385982 33.887359, -83.386348 33.887507, -83.386646 33.887508, -83.387187 33.887011, -83.387187 33.886942, -83.387203 33.886725, -83.387004 33.886505, -83.38624 33.886127, -83.387157 33.885699, -83.38777 33.885397, -83.388042 33.885279, -83.388135 33.885252, -83.388333 33.885247, -83.388879 33.885291, -83.389149 33.885294, -83.389235 33.885114, -83.389427 33.884796, -83.3895 33.884705, -83.389586 33.884622, -83.389658 33.884566, -83.389831 33.884456, -83.390029 33.88434, -83.390348 33.884177, -83.390755 33.883989, -83.391125 33.883831, -83.391692 33.883573, -83.391829 33.883496, -83.391902 33.883441, -83.392076 33.883275, -83.392159 33.883175, -83.39225 33.883042, -83.392281 33.882964, -83.392326 33.882747, -83.392336 33.88261, -83.392333 33.8825, -83.392281 33.882256, -83.392248 33.882179, -83.392184 33.882082, -83.392052 33.881925, -83.391908 33.881775, -83.392025 33.88167, -83.392243 33.881456, -83.392315 33.8814, -83.392369 33.881368, -83.392459 33.881334, -83.392647 33.881282, -83.392839 33.881243, -83.393066 33.88121, -83.393261 33.881196, -83.39375 33.879544, -83.393687 33.879297, -83.393528 33.87867, -83.393511 33.878406, -83.393677 33.878367, -83.394094 33.87809, -83.394263 33.878107, -83.395022 33.878753, -83.395253 33.878822, -83.395538 33.87874, -83.395826 33.878476, -83.396006 33.878072, -83.396365 33.877616, -83.396582 33.877408, -83.396961 33.877339, -83.397408 33.87731, -83.397862 33.877252, -83.398393 33.877209, -83.398622 33.877294, -83.398751 33.877467, -83.398733 33.877761, -83.398494 33.878454, -83.398458 33.878825, -83.398555 33.87902, -83.399227 33.879617, -83.399614 33.879738, -83.400291 33.87981, -83.400601 33.880069, -83.400881 33.880209, -83.401135 33.880528, -83.401496 33.880902, -83.402427 33.881535, -83.40298 33.881868, -83.405315 33.883021, -83.406353 33.88361, -83.406765 33.883951, -83.407459 33.884158, -83.407925 33.884472, -83.408222 33.884815, -83.408337 33.885008, -83.40881 33.885063, -83.411366 33.88498, -83.411877 33.885183, -83.413338 33.885498, -83.413195 33.88485, -83.413157 33.884659, -83.413089 33.884399, -83.412986 33.883964, -83.412937 33.883706, -83.412889 33.883326, -83.41285 33.882943, -83.412825 33.882572, -83.412797 33.882022, -83.412791 33.88179, -83.412769 33.881344, -83.412767 33.881183, -83.412757 33.880847, -83.412748 33.88075, -83.412746 33.880648, -83.41278 33.880569, -83.412819 33.880435, -83.412886 33.88028, -83.412961 33.880158, -83.41314 33.880158, -83.413211 33.880078, -83.413437 33.879829, -83.413368 33.87972, -83.413438 33.879761, -83.414478 33.880259, -83.415196 33.880589, -83.416686 33.87822, -83.418143 33.876003, -83.419529 33.873813, -83.420313 33.872766, -83.420366 33.872914, -83.420449 33.873144, -83.421192 33.87409, -83.42116 33.87446, -83.421358 33.874694, -83.421178 33.875051, -83.421417 33.875406, -83.42148 33.875876, -83.421818 33.876013, -83.422033 33.876137, -83.422316 33.876533, -83.422532 33.876536, -83.423011 33.876426, -83.423297 33.876443, -83.423583 33.876635, -83.423813 33.876886, -83.423999 33.876938, -83.424201 33.876886, -83.424366 33.876732, -83.42454 33.876444, -83.424759 33.876309, -83.425125 33.876241, -83.425171 33.876223, -83.425166 33.87633, -83.425182 33.876439, -83.425245 33.876709, -83.425551 33.877676, -83.425649 33.878036, -83.425745 33.878505, -83.42582 33.878991, -83.425862 33.87933, -83.426083 33.880982, -83.426127 33.881359, -83.42635 33.883009, -83.426448 33.883809, -83.426507 33.884213, -83.426551 33.884611, -83.42666 33.885427, -83.426713 33.885792, -83.426739 33.885898, -83.42679 33.886028, -83.42684 33.88613, -83.426906 33.886225, -83.427025 33.886357, -83.42718 33.886499, -83.427477 33.886723, -83.427669 33.886867, -83.427877 33.887003, -83.427908 33.886986, -83.428085 33.886913, -83.428256 33.88683, -83.428475 33.886707, -83.428682 33.886572, -83.428924 33.886373, -83.429714 33.885677, -83.430702 33.886317, -83.430998 33.886492, -83.431248 33.886624, -83.431396 33.886686, -83.43161 33.886758, -83.431767 33.886799, -83.431959 33.886836, -83.432187 33.886865, -83.43245 33.886881, -83.432615 33.886881, -83.432878 33.886865, -83.433523 33.886803, -83.4343 33.886762, -83.434887 33.886723, -83.43528 33.886702, -83.436276 33.886629, -83.436669 33.886591, -83.436993 33.886542, -83.437249 33.88649, -83.437567 33.886411, -83.43768 33.886384, -83.438034 33.886285, -83.43881 33.886078, -83.43924 33.885987, -83.440079 33.885831, -83.440479 33.885743, -83.440667 33.885693, -83.440912 33.885612, -83.441212 33.885497, -83.441637 33.885287, -83.442152 33.885042, -83.442362 33.884961, -83.442547 33.884904, -83.442836 33.884848, -83.442999 33.88483, -83.443284 33.884821, -83.444126 33.884803, -83.444381 33.8848, -83.444609 33.884799, -83.444931 33.884791, -83.445128 33.884779, -83.445387 33.884736, -83.445544 33.884695, -83.445665 33.884652, -83.445777 33.884594, -83.445989 33.884462, -83.446165 33.884338, -83.446307 33.884223, -83.44641 33.884117, -83.447192 33.883238, -83.447335 33.88306, -83.447386 33.882991, -83.447532 33.882802, -83.447635 33.882639, -83.447803 33.88234, -83.448043 33.881887, -83.448265 33.881501, -83.44864 33.880873, -83.448738 33.880698, -83.448972 33.880312, -83.44899 33.880275, -83.449228 33.879832, -83.449375 33.879516, -83.449408 33.879437, -83.449589 33.878911, -83.449657 33.878679, -83.449758 33.878208, -83.449802 33.878025, -83.449834 33.877947, -83.449116 33.877534, -83.447489 33.876596, -83.447365 33.876517, -83.44587 33.87565, -83.445486 33.875431, -83.444978 33.875123, -83.444489 33.874861, -83.444292 33.874747, -83.443931 33.874528, -83.443837 33.874471, -83.443337 33.874156, -83.44277 33.873813, -83.442563 33.873692, -83.442231 33.873499, -83.442031 33.873403, -83.441854 33.873328, -83.441693 33.873283, -83.441579 33.873244, -83.441234 33.87314, -83.441134 33.873106, -83.44067 33.872928, -83.44033 33.872803, -83.440144 33.872747, -83.439796 33.872664, -83.439709 33.872647, -83.439226 33.872556, -83.437943 33.872381, -83.438085 33.87189, -83.438101 33.871816, -83.438085 33.871743, -83.438057 33.871583, -83.437977 33.871472, -83.437889 33.871371, -83.437776 33.871178, -83.437637 33.871024, -83.437372 33.870821, -83.437216 33.87065, -83.436963 33.870432, -83.43685 33.870333, -83.436728 33.870241, -83.436648 33.870193, -83.436503 33.870128, -83.436409 33.870101, -83.436054 33.870041, -83.435865 33.870006, -83.435314 33.869905, -83.435178 33.869897, -83.435115 33.8699, -83.435056 33.869908, -83.434998 33.869925, -83.43495 33.869952, -83.434848 33.870021, -83.434712 33.870141, -83.434612 33.870212, -83.434472 33.870284, -83.434138 33.870418, -83.433737 33.870564, -83.433704 33.870577, -83.433658 33.870603, -83.43358 33.870667, -83.433547 33.870705, -83.43352 33.870757, -83.433504 33.87081, -83.433492 33.870927, -83.433467 33.871316, -83.433431 33.871651, -83.432511 33.871537, -83.431376 33.871415, -83.430585 33.87133, -83.430244 33.871301, -83.429257 33.871191, -83.428657 33.871113, -83.427609 33.870963, -83.426576 33.870809, -83.42578 33.870686, -83.424792 33.870538, -83.423805 33.870389, -83.423546 33.870351, -83.423284 33.870309, -83.422164 33.870144, -83.422062 33.870129, -83.421952 33.870112, -83.42148 33.870038, -83.421224 33.869983, -83.421154 33.869966, -83.420939 33.869912, -83.420882 33.869898, -83.420817 33.869878, -83.420761 33.869861, -83.420198 33.869691, -83.419834 33.869561, -83.419478 33.869418, -83.41922 33.869296, -83.418844 33.869104, -83.418554 33.868949, -83.418342 33.868837, -83.418546 33.868739, -83.41942 33.868368, -83.419575 33.86836, -83.420143 33.868293, -83.420439 33.868222, -83.420535 33.868172, -83.420585 33.867864, -83.420565 33.867618, -83.42069 33.867055, -83.42058 33.86686, -83.42045 33.86672, -83.420384 33.86672, -83.420285 33.866679, -83.42012 33.866514, -83.420202 33.866322, -83.420119 33.865952, -83.420447 33.865745, -83.420711 33.865759, -83.421286 33.865483, -83.421598 33.865208, -83.422419 33.864301, -83.422846 33.864163, -83.42324 33.86382, -83.423322 33.863627, -83.423272 33.863202, -83.423419 33.863064, -83.42355 33.862652, -83.423516 33.862309, -83.42373 33.862034, -83.423613 33.861568, -83.423816 33.861254, -83.423924 33.860977, -83.424286 33.860757, -83.424653 33.860756, -83.424773 33.860756, -83.424805 33.861322, -83.424876 33.861343, -83.424909 33.860756, -83.424911 33.860039, -83.424898 33.859574, -83.424845 33.858798, -83.424814 33.858411, -83.424742 33.857848, -83.424645 33.857272, -83.4244 33.856108, -83.424148 33.855164, -83.423991 33.854675, -83.423414 33.852884, -83.423027 33.851685, -83.422958 33.851491, -83.422874 33.851257, -83.422735 33.850869, -83.422484 33.850012, -83.422427 33.849722, -83.422487 33.849695, -83.422613 33.84964, -83.423548 33.849235, -83.423696 33.849171, -83.424312 33.848912, -83.424325 33.848906, -83.424927 33.848667, -83.42521 33.848587, -83.425316 33.84856, -83.425431 33.848531, -83.425687 33.848479, -83.425914 33.848444, -83.426274 33.84841, -83.426701 33.848391, -83.427001 33.848401, -83.427483 33.848432, -83.427684 33.84844, -83.42796 33.848452, -83.428494 33.848464, -83.428725 33.848459, -83.428821 33.848448, -83.429149 33.848411, -83.429765 33.848319, -83.429802 33.848311, -83.430115 33.848243, -83.430432 33.848168, -83.430631 33.848106, -83.430874 33.848031, -83.431186 33.847945, -83.433032 33.850784, -83.433767 33.851835, -83.434088 33.851657, -83.434514 33.851162, -83.434991 33.850791, -83.435417 33.850186, -83.435779 33.849332, -83.434935 33.848252, -83.434797 33.848064, -83.434683 33.847866, -83.43461 33.847713, -83.434553 33.847555, -83.434447 33.847068, -83.434706 33.847048, -83.434964 33.847034, -83.435206 33.84703, -83.43616 33.847044, -83.436683 33.847039, -83.437012 33.847023, -83.437272 33.846991, -83.437587 33.846931, -83.437909 33.84685, -83.438092 33.846789, -83.438385 33.846663, -83.438554 33.846578, -83.439078 33.846269, -83.439734 33.84585, -83.440405 33.84543, -83.441206 33.844929, -83.441434 33.844798, -83.441629 33.844695, -83.441936 33.844546, -83.442381 33.844367, -83.442837 33.844208, -83.443086 33.844147, -83.444074 33.84393, -83.444985 33.84374, -83.445336 33.843671, -83.446666 33.843393, -83.447045 33.8433, -83.447418 33.843192, -83.447694 33.843095, -83.445933 33.840658, -83.445781 33.840447, -83.445607 33.840182, -83.445428 33.839857, -83.445326 33.839655, -83.445207 33.839369, -83.445153 33.839209, -83.445143 33.83916, -83.445088 33.838974, -83.445007 33.838623, -83.444973 33.838417, -83.444955 33.838206, -83.444944 33.838117, -83.441671 33.838795, -83.440757 33.838781, -83.440187 33.838149, -83.439014 33.838164, -83.437669 33.83835, -83.436703 33.838422, -83.434943 33.838666, -83.433269 33.839336, -83.432576 33.839445, -83.431919 33.839516, -83.431739 33.839548, -83.431148 33.839678, -83.430803 33.839765, -83.430673 33.839785, -83.430481 33.839824, -83.430351 33.83984, -83.430022 33.839852, -83.429787 33.839892, -83.429575 33.839922, -83.429263 33.839955, -83.428688 33.840024, -83.428622 33.840026, -83.428494 33.839999, -83.428434 33.839976, -83.428281 33.839872, -83.428253 33.839857, -83.428222 33.839848, -83.428124 33.839836, -83.427789 33.839736, -83.42744 33.839615, -83.427266 33.839813, -83.426394 33.840825, -83.426118 33.841145, -83.425834 33.841468, -83.424553 33.84303, -83.424172 33.843484, -83.423921 33.84381, -83.423668 33.844168, -83.423448 33.84451, -83.423291 33.844785, -83.423201 33.844964, -83.423071 33.845219, -83.42288 33.84563, -83.422497 33.845671, -83.422342 33.845708, -83.422225 33.845753, -83.42214 33.845791, -83.422041 33.84586, -83.421952 33.845938, -83.421684 33.846225, -83.421323 33.846668, -83.420837 33.847263, -83.420664 33.84745, -83.420243 33.847891, -83.420071 33.848058, -83.419789 33.848288, -83.419537 33.848466, -83.419199 33.84868, -83.418388 33.849155, -83.418322 33.849196, -83.417948 33.849426, -83.417897 33.849458, -83.417775 33.849381, -83.417711 33.849342, -83.417667 33.849314, -83.417422 33.849165, -83.417365 33.849132, -83.416955 33.848932, -83.416856 33.848881, -83.416748 33.849054, -83.415902 33.850429, -83.415859 33.850508, -83.415713 33.850592, -83.413126 33.852143, -83.411577 33.853066, -83.410568 33.853656, -83.409023 33.854602, -83.409015 33.854527, -83.408981 33.854364, -83.408882 33.854045, -83.408778 33.854144, -83.408495 33.854539, -83.408218 33.854956, -83.408196 33.854989, -83.408099 33.855168, -83.406841 33.855874, -83.405057 33.856938, -83.404397 33.857336, -83.403893 33.857641, -83.40262 33.856495, -83.40234 33.856244, -83.401896 33.855846, -83.40025 33.854372, -83.399839 33.854004, -83.399688 33.853866, -83.398914 33.853159, -83.398229 33.852534, -83.398192 33.8525, -83.397465 33.851847, -83.396193 33.850704, -83.395821 33.850369, -83.395726 33.850283, -83.395654 33.850218, -83.395532 33.850108, -83.395425 33.850175, -83.395402 33.850286, -83.394768 33.850873, -83.394268 33.851333, -83.393478 33.852094, -83.392591 33.853113, -83.392207 33.853621, -83.392104 33.85376, -83.390864 33.853913, -83.390719 33.853918, -83.390565 33.853962, -83.390465 33.853999, -83.390197 33.854035, -83.390121 33.854063, -83.39 33.854176, -83.38993 33.854228, -83.38985 33.854259, -83.38972 33.854267, -83.389515 33.854218, -83.389446 33.854234, -83.389374 33.854252, -83.389259 33.854256, -83.389208 33.854278, -83.389157 33.854299, -83.388718 33.854303, -83.388442 33.85434, -83.388376 33.854338, -83.388247 33.854318, -83.388158 33.854318, -83.38798 33.854293, -83.38796 33.854599, -83.387922 33.855253, -83.387905 33.855533, -83.387888 33.855821, -83.387866 33.856182, -83.387865 33.856227, -83.387851 33.856421, -83.387822 33.856899, -83.387806 33.857075, -83.387793 33.857329, -83.387744 33.858253, -83.387736 33.858409, -83.387734 33.858442, -83.387713 33.858832, -83.387708 33.858941, -83.387702 33.859062, -83.387979 33.858825, -83.388236 33.858562, -83.388399 33.858461, -83.388782 33.858348, -83.38941 33.85821, -83.389969 33.858103, -83.390299 33.858052, -83.390148 33.858559, -83.390201 33.858555, -83.390558 33.858525, -83.390854 33.858515, -83.391291 33.858512, -83.391507 33.858511, -83.391866 33.858525, -83.391975 33.858533, -83.392195 33.858548, -83.392906 33.858621, -83.39331 33.858659, -83.393536 33.858681, -83.393968 33.85873, -83.394684 33.858812, -83.395974 33.858945, -83.39664 33.859017, -83.396845 33.859036, -83.397108 33.859069, -83.397302 33.859103, -83.397497 33.859159, -83.397582 33.859184, -83.397933 33.859329, -83.398709 33.859755, -83.398853 33.859821, -83.399031 33.859893, -83.399085 33.859908, -83.399249 33.859955, -83.399442 33.859992, -83.39967 33.860018, -83.399862 33.860027, -83.399048 33.860503, -83.39859 33.860782, -83.398084 33.861092, -83.397544 33.861449, -83.396993 33.861969, -83.39656 33.862399, -83.396169 33.862778, -83.395808 33.863145, -83.395447 33.863536, -83.39497 33.864017, -83.394191 33.864875, -83.39351 33.865521, -83.39276 33.866316, -83.392084 33.866981, -83.391834 33.867227, -83.391741 33.867325, -83.391664 33.867406, -83.391214 33.867881, -83.390924 33.868187, -83.390215 33.868883, -83.389734 33.869304, -83.389693 33.86934, -83.389094 33.869723, -83.389029 33.869754, -83.389043 33.869683, -83.389157 33.869279, -83.389167 33.869243, -83.38925 33.869005, -83.389398 33.868641, -83.389473 33.868488, -83.389632 33.868217, -83.389901 33.867807, -83.390167 33.867411, -83.390322 33.867161, -83.390598 33.866662, -83.390744 33.866356, -83.390932 33.865913, -83.390973 33.865825, -83.391067 33.865585, -83.39131 33.865011, -83.391555 33.86445, -83.391754 33.864015, -83.391795 33.863934, -83.39192 33.863677, -83.391991 33.863531, -83.392133 33.863266, -83.392271 33.86301, -83.392452 33.862711, -83.392468 33.862679, -83.39248 33.86261, -83.39248 33.862583, -83.392474 33.862556, -83.392462 33.86253, -83.392422 33.862487, -83.392366 33.862458, -83.392328 33.862451, -83.39227 33.86244, -83.392204 33.862437, -83.390396 33.86256, -83.390075 33.862579, -83.389172 33.862633, -83.388464 33.862681, -83.387894 33.862711, -83.387825 33.862853, -83.387744 33.863279, -83.386759 33.864379, -83.386233 33.864764, -83.38579 33.865258, -83.385347 33.865561, -83.38526 33.865587, -83.384606 33.865781, -83.384014 33.866248, -83.38321 33.867072, -83.382734 33.867375, -83.382405 33.867471, -83.381236 33.867332, -83.380627 33.867456, -83.38043 33.867496, -83.379592 33.867398, -83.378687 33.867332, -83.378424 33.867313, -83.377997 33.867477, -83.377389 33.867614, -83.377129 33.867566, -83.376715 33.86749, -83.376089 33.867339, -83.375759 33.867271, -83.375447 33.867367, -83.374889 33.867629, -83.374034 33.867867, -83.372951 33.867874, -83.372111 33.868071, -83.371356 33.868667, -83.37054 33.869084, -83.369807 33.869635, -83.369395 33.869847, -83.368792 33.870536, -83.368381 33.870868, -83.368121 33.87128, -83.367793 33.871543, -83.367633 33.871738, -83.367503 33.871835, -83.367335 33.871876, -83.367022 33.871878, -83.366641 33.871757, -83.365634 33.871033, -83.364871 33.870611, -83.364543 33.870463, -83.36365 33.870278, -83.363139 33.869813, -83.36291 33.869664, -83.362315 33.869457, -83.361788 33.869338, -83.36159 33.869597, -83.361018 33.870003, -83.360346 33.870431, -83.360118 33.870708, -83.359812 33.871315, -83.359782 33.871659, -83.359385 33.872032, -83.358897 33.872254, -83.358142 33.872231, -83.357928 33.872124, -83.357234 33.872095, -83.356707 33.872032, -83.356257 33.871855, -83.355868 33.871858, -83.355471 33.872014, -83.354876 33.872975, -83.353878 33.874563, -83.355669 33.874581, -83.356989 33.87464, -83.35806 33.874848, -83.358309 33.874897, -83.358653 33.875002, -83.358885 33.87513, -83.358904 33.87514, -83.359951 33.87557, -83.360447 33.876015, -83.360973 33.87653, -83.361187 33.876975, -83.36124 33.87744, -83.361255 33.877588, -83.361208 33.878067, -83.361025 33.879028, -83.361064 33.87955, -83.361193 33.880032, -83.361514 33.880661, -83.361941 33.881277, -83.362485 33.881708, -83.36323 33.882263, -83.363369 33.882348, -83.363741 33.882577, -83.36397 33.882973, -83.364009 33.883564, -83.36394 33.884058, -83.364077 33.884663, -83.364459 33.88518, -83.364855 33.885455, -83.365367 33.885893, -83.365634 33.88655, -83.365878 33.887347, -83.366053 33.887557, -83.36607 33.887577, -83.366244 33.887785, -83.366291 33.887796, -83.366329 33.887805, -83.366541 33.887854, -83.367198 33.887728, -83.368548 33.887356, -83.369136 33.88721, -83.370227 33.886939, -83.370738 33.886775, -83.371463 33.886221, -83.372302 33.885534, -83.372699 33.885315, -83.373027 33.885368, -83.373408 33.885588, -83.374019 33.886339, -83.374583 33.88715, -83.374896 33.887672, -83.375139 33.888198, -83.375488 33.888726, -83.375881 33.889127, -83.376191 33.889658, -83.376358 33.890035, -83.376589 33.890392, -83.376712 33.890433, -83.376571 33.890531, -83.376422 33.890635, -83.37594 33.89091, -83.37541 33.89116, -83.374811 33.89147, -83.373938 33.891944, -83.373237 33.892354, -83.372967 33.892498, -83.372141 33.892897, -83.371365 33.893263, -83.370657 33.893641, -83.369598 33.894284, -83.36761 33.895553, -83.366329 33.896356, -83.366181 33.89646, -83.366058 33.896548, -83.365981 33.896602, -83.365702 33.896875, -83.365491 33.897189, -83.365023 33.897973, -83.364814 33.898372, -83.364559 33.898841, -83.364433 33.899073, -83.363958 33.899825, -83.363771 33.90017, -83.363695 33.900482, -83.363605 33.901498, -83.363473 33.90193, -83.363111 33.902895, -83.362936 33.903279, -83.362736 33.903614, -83.362425 33.904056, -83.362586 33.904377, -83.362678 33.904643, -83.362583 33.904898, -83.362403 33.905085, -83.36219 33.905343, -83.362094 33.905528, -83.361998 33.905727, -83.361933 33.905812, -83.36191 33.906207, -83.361847 33.906364, -83.361731 33.906478, -83.361484 33.906624, -83.361317 33.90663, -83.360292 33.907301, -83.36027 33.907286, -83.360029 33.907131, -83.359934 33.907223, -83.359895 33.90726, -83.359764 33.907341, -83.359461 33.907095, -83.35917 33.906781, -83.358965 33.906561, -83.358729 33.906201, -83.35837 33.905723, -83.358072 33.90545, -83.357676 33.905288, -83.357309 33.905304, -83.357228 33.905333, -83.356867 33.905465, -83.356501 33.905698, -83.35586 33.90592, -83.355349 33.906087, -83.354548 33.906295, -83.35412 33.906353, -83.3538 33.906229, -83.353472 33.906272, -83.353357 33.906173, -83.353336 33.906139, -83.352877 33.905406, -83.352564 33.904886, -83.352426 33.904243, -83.352358 33.903143, -83.352259 33.901964, -83.352259 33.901291, -83.352335 33.900301, -83.352404 33.89981, -83.352419 33.899477, -83.352221 33.898768, -83.351885 33.89833, -83.351389 33.897711, -83.350848 33.897355, -83.350321 33.897136, -83.349398 33.896838, -83.348932 33.896466, -83.348482 33.895933, -83.348169 33.895443, -83.347673 33.894632, -83.347528 33.894332, -83.347345 33.893741, -83.34736 33.893635, -83.347406 33.893274, -83.347635 33.892807, -83.348047 33.892642, -83.348459 33.892544, -83.348902 33.892558, -83.349115 33.892638, -83.349283 33.892803, -83.349184 33.893202, -83.349108 33.893556, -83.349207 33.893781, -83.349482 33.893779, -83.351572 33.892853, -83.351938 33.892551, -83.351985 33.892473, -83.352116 33.892261, -83.352114 33.891916, -83.351801 33.891344, -83.351748 33.891, -83.352091 33.889739, -83.352076 33.889465, -83.351961 33.889297, -83.351664 33.889148, -83.350969 33.88901, -83.350313 33.888792, -83.349703 33.888439, -83.34952 33.888274, -83.349322 33.887535, -83.349283 33.887381, -83.349268 33.886806, -83.349419 33.886442, -83.349451 33.886367, -83.349795 33.885928, -83.34994 33.885364, -83.350069 33.884117, -83.3501 33.883704, -83.349985 33.883636, -83.349856 33.883652, -83.349657 33.883883, -83.349525 33.88408, -83.349434 33.884218, -83.34939 33.884281, -83.3491 33.884448, -83.348619 33.88456, -83.348093 33.884543, -83.347429 33.884421, -83.347086 33.884176, -83.346788 33.883766, -83.347086 33.883052, -83.347216 33.882613, -83.34762 33.882187, -83.34894 33.881252, -83.349138 33.880947, -83.349184 33.880482, -83.34926 33.879287, -83.34936 33.878627, -83.349525 33.878136, -83.349508 33.877886, -83.349452 33.87784, -83.34939 33.877789, -83.349192 33.877786, -83.348368 33.878299, -83.347831 33.878537, -83.347811 33.878546, -83.347185 33.878743, -83.346514 33.878869, -83.34598 33.878893, -83.345553 33.878771, -83.345392 33.878678, -83.34516 33.878586, -83.345102 33.878304, -83.345078 33.877983, -83.345137 33.877837, -83.345102 33.877399, -83.344926 33.876805, -83.344785 33.876086, -83.34448 33.875589, -83.344128 33.875288, -83.343541 33.875152, -83.342685 33.875093, -83.342309 33.875025, -83.34191 33.87484, -83.341511 33.874636, -83.341112 33.874383, -83.340513 33.874225, -83.339715 33.874177, -83.338941 33.874089, -83.338202 33.873924, -83.337639 33.873622, -83.337099 33.873271, -83.336407 33.872804, -83.335799 33.872305, -83.335722 33.872649, -83.335901 33.873047, -83.335916 33.87336, -83.335746 33.87377, -83.335684 33.874415, -83.335659 33.874957, -83.335659 33.875633, -83.335682 33.876118, -83.335636 33.876568, -83.335663 33.877209, -83.335887 33.877852, -83.33629 33.87852, -83.336672 33.879123, -83.337846 33.879614, -83.33803 33.879779, -83.337976 33.880587, -83.338175 33.881079, -83.338381 33.881533, -83.338381 33.882014, -83.338411 33.882506, -83.338495 33.882824, -83.338403 33.884113, -83.338587 33.885048, -83.338785 33.885554, -83.338754 33.885897, -83.338213 33.887117, -83.337801 33.88801, -83.337228 33.889108, -83.336984 33.889715, -83.336855 33.890086, -83.336427 33.890677, -83.336191 33.890882, -83.335947 33.891088, -83.335687 33.891448, -83.335245 33.892341, -83.334863 33.892848, -83.334818 33.893137, -83.334901 33.893657, -83.334589 33.893972, -83.334192 33.894222, -83.333696 33.894389, -83.333139 33.894278, -83.332895 33.894171, -83.332643 33.89417, -83.33246 33.894351, -83.332399 33.894582, -83.332399 33.895324, -83.33229 33.895459, -83.332086 33.895709, -83.331743 33.895999, -83.331445 33.896315, -83.331407 33.89667, -83.331399 33.896766, -83.33127 33.897029, -83.330926 33.897383, -83.330987 33.897727, -83.330972 33.898249, -83.330837 33.898755, -83.330812 33.898851, -83.330667 33.899183, -83.330056 33.899554, -83.329347 33.900119, -83.32882 33.900681, -83.32879 33.901094, -83.328904 33.901465, -83.32921 33.902548, -83.329538 33.903991, -83.329675 33.904714, -83.32982 33.904948, -83.330751 33.905741, -83.33098 33.906263, -83.331064 33.906755, -83.331079 33.907569, -83.331079 33.907745, -83.331086 33.9078, -83.331002 33.90825, -83.330407 33.908953, -83.330192 33.909157, -83.33003 33.90917, -83.329736 33.909206, -83.329347 33.909301, -83.328777 33.909511, -83.328474 33.909639, -83.327292 33.910231, -83.327156 33.910282, -83.325843 33.910904, -83.325565 33.911027, -83.325401 33.911081, -83.324962 33.911166, -83.32476 33.911185, -83.324454 33.911186, -83.324306 33.911179, -83.324165 33.911151, -83.324067 33.911147, -83.323981 33.911139, -83.323547 33.910968, -83.323411 33.910904, -83.323292 33.91084, -83.323143 33.910746, -83.324532 33.90879, -83.32525 33.907779, -83.325362 33.907653, -83.325451 33.907532, -83.325568 33.907412, -83.325724 33.907299, -83.32589 33.907171, -83.326056 33.907011, -83.326451 33.906439, -83.327876 33.904431, -83.328036 33.904195, -83.328168 33.904014, -83.328321 33.903715, -83.328411 33.903493, -83.328467 33.903243, -83.328488 33.903041, -83.328491 33.902948, -83.328481 33.902732, -83.32846 33.902524, -83.328404 33.902388, -83.328324 33.902267, -83.328154 33.902076, -83.328057 33.90194, -83.327937 33.9015, -83.327453 33.901562, -83.326931 33.901596, -83.326456 33.901614, -83.32608 33.901582, -83.325851 33.901555, -83.325607 33.901487, -83.325361 33.901357, -83.325156 33.901235, -83.324927 33.901054, -83.324903 33.90103, -83.324806 33.900926, -83.324339 33.900365, -83.324147 33.900166, -83.323998 33.900046, -83.323941 33.9, -83.32275 33.8993, -83.321539 33.898558, -83.320972 33.898295, -83.320721 33.898245, -83.32044 33.89826, -83.320202 33.898291, -83.319988 33.898413, -83.319018 33.899429, -83.318762 33.899698, -83.318132 33.899356, -83.318018 33.899305, -83.317855 33.899242, -83.317594 33.89916, -83.317324 33.899094, -83.317083 33.899042, -83.316869 33.899, -83.316684 33.898981, -83.316517 33.898987, -83.316173 33.899009, -83.316117 33.899015, -83.315852 33.899106, -83.315509 33.899209, -83.315065 33.899365, -83.314917 33.899133, -83.31476 33.898928, -83.314173 33.89828, -83.314126 33.898184, -83.314073 33.898088, -83.314018 33.897938, -83.313911 33.897491, -83.313898 33.897255, -83.313937 33.897068, -83.313936 33.896958, -83.31406 33.896812, -83.314155 33.896565, -83.314269 33.896379, -83.314481 33.896069, -83.314685 33.895805, -83.314746 33.895697, -83.314832 33.895481, -83.315056 33.894524, -83.315161 33.894075, -83.315184 33.894012, -83.315235 33.893904, -83.315535 33.892997, -83.315798 33.892517, -83.316461 33.891266, -83.316547 33.891117, -83.316677 33.890896, -83.316763 33.890717, -83.317096 33.890144, -83.317139 33.890036, -83.317655 33.888588, -83.318263 33.886672, -83.318328 33.886365, -83.318123 33.886306, -83.315444 33.885707, -83.314394 33.885574, -83.313658 33.885488, -83.313298 33.88538, -83.313057 33.885316, -83.312946 33.88528, -83.312769 33.885176, -83.312601 33.885049, -83.312374 33.884824, -83.312231 33.884597, -83.31208 33.884447, -83.312045 33.884348, -83.311975 33.88412, -83.311913 33.883892, -83.31187 33.883632, -83.311788 33.882999, -83.311613 33.881875, -83.31157 33.881649, -83.311248 33.87962, -83.309333 33.879846, -83.30881 33.87987, -83.308239 33.879959, -83.308003 33.88004, -83.307537 33.880118, -83.307381 33.880128, -83.307152 33.88011, -83.307041 33.880073, -83.306845 33.879962, -83.306761 33.879895, -83.306168 33.880422, -83.305853 33.8808, -83.305413 33.880577, -83.304757 33.880455, -83.304314 33.880226, -83.304001 33.880687, -83.303689 33.881225, -83.303101 33.882376, -83.302735 33.882922, -83.302506 33.883268, -83.302193 33.883834, -83.301865 33.884262, -83.301598 33.884501, -83.301392 33.884687, -83.300072 33.885952, -83.299454 33.886611, -83.298943 33.887297, -83.298615 33.887835, -83.298546 33.887945, -83.298188 33.889088, -83.297928 33.889662, -83.29786 33.89017, -83.297867 33.890662, -83.298409 33.89176, -83.298729 33.892818, -83.298775 33.893643, -83.298798 33.89405, -83.298714 33.895025, -83.2986 33.895904, -83.298538 33.896303, -83.298195 33.897003, -83.297783 33.897703, -83.296852 33.899447, -83.29644 33.900037, -83.295967 33.900905, -83.295654 33.901798, -83.29554 33.902303, -83.295525 33.903488, -83.295448 33.903924, -83.295373 33.904101, -83.295136 33.904666, -83.294952 33.905902, -83.294731 33.907194, -83.294434 33.907977, -83.294113 33.908663, -83.293762 33.909418, -83.29364 33.909635, -83.294174 33.909801, -83.294899 33.91028, -83.295202 33.910612, -83.295349 33.910773, -83.295571 33.911121, -83.295761 33.911417, -83.296242 33.912237, -83.29644 33.912608, -83.297333 33.913099, -83.297463 33.913198, -83.298103 33.913665, -83.29821 33.913743, -83.298668 33.914414, -83.299217 33.915542, -83.2994 33.915894, -83.30011 33.917308, -83.300362 33.917924, -83.300554 33.919422, -83.300119 33.919517, -83.299787 33.919533, -83.299467 33.919498, -83.299167 33.919319, -83.298752 33.918989, -83.298364 33.918729, -83.297986 33.918398, -83.297893 33.918133, -83.297884 33.917805, -83.297898 33.917416, -83.297923 33.916974, -83.29788 33.91676, -83.297545 33.916171, -83.297318 33.91597, -83.297107 33.915903, -83.296786 33.915857, -83.296406 33.915885, -83.296387 33.915886, -83.296387 33.9159, -83.296388 33.915931, -83.296387 33.915969, -83.296393 33.916017, -83.296409 33.916089, -83.296425 33.916161, -83.296444 33.916233, -83.296465 33.916304, -83.296491 33.916392, -83.296533 33.916522, -83.296572 33.916652, -83.296628 33.916864, -83.296635 33.916887, -83.29664 33.916909, -83.296645 33.916932, -83.296649 33.916955, -83.296653 33.916977, -83.296657 33.917, -83.29666 33.917023, -83.296663 33.917046, -83.296666 33.917068, -83.296667 33.917091, -83.296668 33.917114, -83.296669 33.917137, -83.29667 33.91716, -83.296669 33.917183, -83.296668 33.917206, -83.296667 33.917229, -83.296664 33.917263, -83.296596 33.917717, -83.2965 33.918349, -83.29643 33.918792, -83.296425 33.918816, -83.296421 33.91884, -83.296417 33.918864, -83.296414 33.918889, -83.296411 33.918913, -83.296409 33.918937, -83.296407 33.918961, -83.296406 33.918998, -83.296408 33.919027, -83.29641 33.919041, -83.296411 33.919056, -83.296414 33.919071, -83.296416 33.919085, -83.296418 33.919099, -83.296422 33.919114, -83.296425 33.919128, -83.296429 33.919142, -83.296433 33.919156, -83.296437 33.91917, -83.296441 33.919184, -83.296446 33.919199, -83.296451 33.919212, -83.296457 33.919226, -83.296462 33.91924, -83.29647 33.919256, -83.296637 33.919614, -83.296653 33.919646, -83.29667 33.919677, -83.296684 33.919709, -83.296699 33.919741, -83.296712 33.919773, -83.296725 33.919805, -83.296738 33.919838, -83.296749 33.91987, -83.29676 33.919903, -83.296771 33.919936, -83.29678 33.919969, -83.296795 33.920028, -83.296803 33.920056, -83.29681 33.920084, -83.296817 33.920113, -83.296822 33.920141, -83.296828 33.92017, -83.296832 33.920199, -83.296837 33.920227, -83.296841 33.920256, -83.296844 33.920285, -83.296846 33.920314, -83.296882 33.920357, -83.296847 33.920343, -83.296849 33.920372, -83.296849 33.920401, -83.296849 33.920429, -83.296849 33.920459, -83.296848 33.920488, -83.296846 33.920516, -83.29684 33.920573, -83.296836 33.920624, -83.296833 33.920675, -83.296826 33.920726, -83.29682 33.920777, -83.296787 33.920973, -83.296718 33.92133, -83.2966 33.92202, -83.296586 33.922104, -83.296573 33.922189, -83.296562 33.922274, -83.296553 33.922359, -83.296547 33.922445, -83.296542 33.92253, -83.296537 33.922619, -83.296541 33.922709, -83.296545 33.9228, -83.296551 33.92289, -83.296559 33.922979, -83.29657 33.923069, -83.296586 33.923186, -83.296676 33.923709, -83.296685 33.923749, -83.296695 33.923789, -83.296704 33.923829, -83.296715 33.923869, -83.296739 33.923948, -83.296782 33.924064, -83.296807 33.924125, -83.296829 33.924167, -83.296851 33.924208, -83.296874 33.924249, -83.296923 33.92433, -83.296948 33.92437, -83.296975 33.924409, -83.297003 33.924448, -83.29703 33.924487, -83.297086 33.924559, -83.297117 33.924595, -83.297148 33.924631, -83.297181 33.924666, -83.297214 33.9247, -83.297248 33.924735, -83.297282 33.924768, -83.297318 33.924802, -83.297354 33.924834, -83.29739 33.924866, -83.297427 33.924898, -83.297465 33.924929, -83.297503 33.924959, -83.297548 33.924993, -83.297593 33.925024, -83.297638 33.925056, -83.297681 33.925088, -83.297724 33.925121, -83.297766 33.925154, -83.297808 33.925189, -83.297878 33.925249, -83.297891 33.925259, -83.297903 33.925269, -83.297965 33.925261, -83.297974 33.925332, -83.298026 33.925389, -83.298064 33.925437, -83.298104 33.925501, -83.298135 33.925562, -83.298147 33.925598, -83.298163 33.925659, -83.298155 33.925697, -83.298175 33.925759, -83.298175 33.925797, -83.298173 33.925834, -83.298161 33.925909, -83.298144 33.925969, -83.298129 33.926024, -83.297966 33.926371, -83.297832 33.926526, -83.297673 33.926644, -83.297487 33.926793, -83.297391 33.926906, -83.297331 33.927005, -83.297144 33.927695, -83.297034 33.928101, -83.297492 33.928225, -83.298282 33.92844, -83.300102 33.928869, -83.30089 33.929054, -83.301618 33.929247, -83.302032 33.929357, -83.303024 33.929606, -83.304139 33.929911, -83.304126 33.929978, -83.303976 33.930387, -83.303468 33.93177, -83.303422 33.931994, -83.303418 33.932044, -83.303408 33.932163, -83.303396 33.932253, -83.30337 33.932332, -83.303317 33.932462, -83.303007 33.933081, -83.30231 33.934386, -83.30219 33.934567, -83.302118 33.934521, -83.301005 33.934178, -83.300788 33.934135, -83.300637 33.93413, -83.300492 33.934144, -83.30039 33.934181, -83.300279 33.934246, -83.300154 33.934338, -83.300072 33.93441, -83.299964 33.934589, -83.299925 33.934707, -83.299915 33.934852, -83.299952 33.935016, -83.300002 33.935128, -83.300094 33.935248, -83.30023 33.935406, -83.300352 33.935568, -83.300477 33.935691, -83.300588 33.935783, -83.300659 33.935821, -83.30083 33.93588, -83.300953 33.935909, -83.301071 33.935923, -83.301151 33.935925, -83.301545 33.935906, -83.301629 33.935908, -83.30164 33.93602, -83.301816 33.937076, -83.301836 33.938562, -83.301842 33.938923, -83.301855 33.939037, -83.301911 33.939192, -83.301991 33.939383, -83.302146 33.939915, -83.30241 33.940825, -83.302472 33.941038, -83.302788 33.942132, -83.302723 33.94211, -83.302677 33.942121, -83.302643 33.942145, -83.30217 33.942238, -83.302089 33.942259, -83.301204 33.942484, -83.300455 33.942689, -83.300116 33.942775, -83.299743 33.942828, -83.299342 33.942878, -83.298973 33.942903, -83.298123 33.943001, -83.297682 33.943018, -83.297229 33.943008, -83.296698 33.942983, -83.296669 33.942985, -83.296381 33.942966, -83.296111 33.942967, -83.295538 33.94292, -83.295366 33.942901, -83.295001 33.942879, -83.294416 33.942878, -83.294196 33.942888, -83.293929 33.94291, -83.293241 33.94299, -83.292935 33.943026, -83.292828 33.943042, -83.292202 33.943132, -83.292058 33.943144, -83.291763 33.943171, -83.291632 33.943182, -83.291211 33.943254, -83.290098 33.943443, -83.28916 33.943575, -83.288118 33.943729, -83.287803 33.943781, -83.287475 33.943844, -83.286997 33.943956, -83.286916 33.943986, -83.285409 33.944195, -83.284125 33.944575, -83.284038 33.944633, -83.283158 33.944885, -83.282689 33.945042, -83.282009 33.945281, -83.2815 33.945483, -83.281025 33.945635, -83.280033 33.945944, -83.277647 33.946741, -83.277631 33.946656, -83.277613 33.946566, -83.277595 33.946472, -83.277434 33.945949, -83.277409 33.945869, -83.277203 33.945377, -83.277062 33.945093, -83.276908 33.944782, -83.276739 33.944516, -83.276679 33.944413, -83.276544 33.944179, -83.276462 33.944038, -83.276172 33.943536, -83.275983 33.943211, -83.275929 33.943134, -83.275888 33.943076, -83.275881 33.943037, -83.275913 33.942994, -83.276008 33.942926, -83.276893 33.942663, -83.276953 33.942649, -83.277118 33.942611, -83.277189 33.942595, -83.277489 33.943085, -83.277621 33.94319, -83.277822 33.943178, -83.280259 33.942817, -83.280845 33.942732, -83.281174 33.942624, -83.281304 33.942431, -83.281436 33.942103, -83.280436 33.942195, -83.279789 33.942276, -83.279357 33.942148, -83.278948 33.942047, -83.278876 33.942022, -83.278783 33.941724, -83.278709 33.941693, -83.278469 33.941741, -83.276623 33.942044, -83.276446 33.94196, -83.275412 33.942142, -83.275258 33.94194, -83.275102 33.941677, -83.275088 33.941653, -83.274964 33.941403, -83.274855 33.941149, -83.274761 33.94089, -83.274572 33.940273, -83.274526 33.940133, -83.274419 33.939808, -83.274152 33.93976, -83.273688 33.939697, -83.273078 33.939639, -83.272936 33.93963, -83.272415 33.939583, -83.272169 33.939549, -83.271883 33.939515, -83.271538 33.939465, -83.270905 33.939338, -83.270607 33.939248, -83.270198 33.939117, -83.269851 33.938993, -83.269363 33.938831, -83.268896 33.938686, -83.268566 33.938573, -83.268234 33.938423, -83.268071 33.938343, -83.267896 33.938225, -83.267637 33.938063, -83.267465 33.937966, -83.266403 33.940577, -83.266328 33.940706, -83.265695 33.941605, -83.26554 33.941833, -83.26557 33.941876, -83.265807 33.942368, -83.265952 33.942946, -83.266288 33.943441, -83.266387 33.943864, -83.266631 33.944123, -83.266768 33.944206, -83.267096 33.944413, -83.267165 33.944836, -83.26705 33.945949, -83.267035 33.946444, -83.2683 33.946795, -83.268408 33.946825, -83.268836 33.946834, -83.269477 33.947014, -83.270003 33.947438, -83.270453 33.947862, -83.270568 33.948275, -83.27107 33.948358, -83.271125 33.947872, -83.271735 33.947999, -83.27246 33.948339, -83.273207 33.948296, -83.27378 33.948583, -83.273765 33.948665, -83.274333 33.948765, -83.274642 33.948884, -83.274955 33.948472, -83.275443 33.948509, -83.275611 33.948594, -83.276023 33.949156, -83.276237 33.949346, -83.276671 33.949525, -83.276786 33.949619, -83.277053 33.95084, -83.276839 33.950885, -83.276859 33.9509, -83.277112 33.951033, -83.276987 33.951047, -83.276597 33.951107, -83.276032 33.951173, -83.275871 33.951236, -83.275757 33.95132, -83.275428 33.951515, -83.275299 33.951553, -83.275194 33.951576, -83.275087 33.951592, -83.275065 33.95159, -83.274795 33.951641, -83.274736 33.951663, -83.274635 33.951758, -83.274591 33.95187, -83.274613 33.95218, -83.274814 33.953157, -83.2749 33.9537, -83.275082 33.95484, -83.27514 33.955114, -83.275167 33.955259, -83.274646 33.955368, -83.274114 33.955546, -83.273247 33.955898, -83.272751 33.956113, -83.272589 33.956183, -83.272499 33.956223, -83.27181 33.956593, -83.271114 33.956989, -83.270604 33.957316, -83.270077 33.957727, -83.269551 33.957969, -83.269193 33.958409, -83.269239 33.958418, -83.268836 33.958728, -83.269647 33.959398, -83.270319 33.95992, -83.270471 33.960044, -83.270633 33.960164, -83.270889 33.960331, -83.271066 33.960433, -83.27125 33.960528, -83.271439 33.960615, -83.271633 33.960694, -83.27239 33.961042, -83.272934 33.961252, -83.274196 33.961945, -83.274833 33.962305, -83.274928 33.962348, -83.27502 33.962396, -83.275193 33.962503, -83.275274 33.962563, -83.27755 33.964603, -83.277453 33.964672, -83.277377 33.964726, -83.27684 33.965133, -83.276774 33.965203, -83.27675 33.96529, -83.276778 33.965395, -83.278067 33.966504, -83.278643 33.966917, -83.278886 33.967115, -83.279095 33.967348, -83.27918 33.967746, -83.278981 33.973861, -83.27893 33.975457, -83.279127 33.975377, -83.279241 33.975336, -83.279822 33.975099, -83.280012 33.975013, -83.280195 33.974917, -83.280283 33.974864, -83.280463 33.974746, -83.280872 33.974468, -83.28112 33.974294, -83.281439 33.97405, -83.281544 33.973986, -83.281599 33.973929, -83.281925 33.973692, -83.282262 33.973465, -83.282455 33.973376, -83.283291 33.973151, -83.283988 33.972994, -83.286515 33.973601, -83.287203 33.973719, -83.287762 33.973815, -83.288198 33.973889, -83.288161 33.973938, -83.288138 33.974014, -83.28814 33.974025, -83.288163 33.974165, -83.288177 33.974246, -83.288238 33.974355, -83.288323 33.974469, -83.288758 33.974882, -83.289057 33.975228, -83.289083 33.975275, -83.289115 33.975315, -83.289191 33.975438, -83.289248 33.975542, -83.289488 33.976019, -83.290111 33.977538, -83.290779 33.977141, -83.291285 33.976914, -83.291781 33.977286, -83.292224 33.977558, -83.292604 33.977753, -83.292869 33.977878, -83.293599 33.978147, -83.295524 33.97881, -83.296012 33.978915, -83.29754 33.979185, -83.297997 33.979244, -83.29839 33.979267, -83.29958 33.979269, -83.300371 33.979254, -83.300815 33.979217, -83.301012 33.9792, -83.301893 33.979077, -83.302365 33.978991, -83.302743 33.978916, -83.303065 33.978843, -83.303597 33.97869, -83.304067 33.978562, -83.304237 33.978497, -83.304708 33.978316, -83.305183 33.978125, -83.305626 33.977922, -83.306014 33.977754, -83.306592 33.977447, -83.30704 33.97719, -83.307554 33.976853, -83.307958 33.976601, -83.308276 33.976352, -83.308752 33.975897, -83.309114 33.975552, -83.309478 33.975197, -83.309745 33.974907, -83.309877 33.974739, -83.31042 33.974056, -83.311237 33.972709, -83.312121 33.971303, -83.312506 33.97065, -83.31267 33.970669, -83.312794 33.970497, -83.313407 33.970724, -83.313319 33.970912, -83.313279 33.971011, -83.313258 33.971118, -83.313259 33.971243, -83.313269 33.971376, -83.313305 33.971475, -83.31334 33.971595, -83.314503 33.972636, -83.314797 33.972944, -83.315381 33.973348, -83.315967 33.973773, -83.316489 33.974099, -83.317022 33.974397, -83.317263 33.974399, -83.318761 33.974038, -83.318948 33.973966, -83.319023 33.973782, -83.319002 33.973546, -83.319034 33.973306, -83.319137 33.973146, -83.319363 33.973015, -83.319523 33.972931, -83.319957 33.973085, -83.320715 33.973324, -83.321323 33.973453, -83.324672 33.974061, -83.325793 33.974237, -83.327436 33.974554, -83.328948 33.975034, -83.329152 33.975119, -83.329183 33.975052, -83.329434 33.974789, -83.329576 33.974674, -83.329962 33.974443, -83.330268 33.974291, -83.331492 33.973752, -83.332776 33.9732, -83.33579 33.971906, -83.33648 33.971522, -83.336593 33.971492, -83.337516 33.970894, -83.340018 33.969228, -83.340394 33.968974, -83.342555 33.967516, -83.343119 33.967147, -83.343577 33.966876, -83.344339 33.966486, -83.344586 33.966356, -83.34464 33.966339, -83.344833 33.966233, -83.344954 33.966182, -83.345124 33.966111, -83.345453 33.965994, -83.346045 33.965811, -83.346572 33.965672, -83.347041 33.965559, -83.347376 33.965487, -83.351889 33.964309, -83.352206 33.964226, -83.352554 33.964135, -83.352871 33.964052, -83.353056 33.964003, -83.353095 33.964161, -83.353266 33.964774, -83.353355 33.965115, -83.353839 33.966979, -83.35407 33.967907, -83.354188 33.968509, -83.354711 33.970466, -83.354829 33.970888, -83.355083 33.971723, -83.355141 33.972038, -83.355062 33.972787, -83.355037 33.972896, -83.355043 33.972958, -83.355065 33.973177, -83.355043 33.973357, -83.355037 33.973402, -83.354942 33.973694, -83.354801 33.97397, -83.354655 33.974178, -83.354537 33.974296, -83.354357 33.974448, -83.354171 33.974572, -83.353935 33.974712, -83.353749 33.974831, -83.353609 33.974932, -83.353406 33.97514, -83.353311 33.975258, -83.353143 33.975403, -83.352922 33.975198, -83.352739 33.974857, -83.352685 33.974445, -83.352441 33.973991, -83.352362 33.973884, -83.352228 33.973705, -83.35196 33.973224, -83.351793 33.972841, -83.351708 33.97287, -83.351344 33.972981, -83.350869 33.973155, -83.347902 33.974169, -83.347495 33.974348, -83.346614 33.974815, -83.346236 33.975053, -83.345726 33.975398, -83.345001 33.976016, -83.34447 33.976533, -83.344099 33.977028, -83.34377 33.977444, -83.343472 33.977963, -83.343073 33.978738, -83.342877 33.97916, -83.34305 33.979234, -83.34338 33.979441, -83.343671 33.979706, -83.344477 33.98048, -83.344849 33.980832, -83.344432 33.981162, -83.34427 33.981319, -83.344085 33.981539, -83.343273 33.982793, -83.341958 33.984719, -83.34156 33.985244, -83.341388 33.985512, -83.340781 33.986417, -83.340432 33.986917, -83.340019 33.987582, -83.339767 33.987988, -83.339579 33.988321, -83.339443 33.98854, -83.33932 33.98868, -83.338858 33.9891, -83.337978 33.989827, -83.337254 33.990465, -83.334628 33.99289, -83.334181 33.99333, -83.334022 33.993538, -83.333971 33.993645, -83.333966 33.993752, -83.333977 33.993944, -83.333988 33.994006, -83.334157 33.994407, -83.334627 33.994266, -83.335637 33.993962, -83.335662 33.993955, -83.336626 33.993671, -83.337596 33.993385, -83.338632 33.993097, -83.338943 33.993006, -83.340459 33.992562, -83.341935 33.992143, -83.348117 33.990294, -83.35065 33.989544, -83.350722 33.989523, -83.350656 33.989612, -83.350427 33.989733, -83.3494 33.990776, -83.34929 33.990889, -83.348878 33.991083, -83.348451 33.991206, -83.347215 33.991406, -83.346559 33.991938, -83.346215 33.992105, -83.34578 33.992187, -83.345452 33.992176, -83.345239 33.992244, -83.3443 33.992947, -83.342644 33.994308, -83.34253 33.994461, -83.341836 33.995062, -83.341393 33.995559, -83.340951 33.996177, -83.340539 33.996932, -83.340226 33.997649, -83.340195 33.998061, -83.340264 33.998416, -83.340592 33.999252, -83.3406 33.999499, -83.340317 33.999801, -83.339761 34.000092, -83.339447 34.00024, -83.339263 34.000393, -83.339062 34.000654, -83.338691 34.001357, -83.337973 34.001909, -83.336709 34.002451, -83.335309 34.002785, -83.334896 34.002963, -83.334766 34.003581, -83.334524 34.004148, -83.334423 34.004102, -83.334285 34.00398, -83.334197 34.003745, -83.333986 34.003445, -83.333854 34.003239, -83.333806 34.003143, -83.333505 34.003062, -83.332695 34.002844, -83.332639 34.002808, -83.331406 34.004731, -83.33045 34.006227, -83.330236 34.006553, -83.329276 34.008046, -83.328605 34.00909, -83.328029 34.010003, -83.327683 34.010552, -83.327649 34.010602, -83.326744 34.01202, -83.325677 34.013707, -83.324376 34.015741, -83.32329 34.017369, -83.322542 34.018572, -83.321901 34.019568, -83.321378 34.020361, -83.320849 34.021317, -83.320512 34.021896, -83.32033 34.022253, -83.320011 34.022959, -83.319708 34.02369, -83.319269 34.024764, -83.31909 34.025259, -83.318974 34.025182, -83.31889 34.02514, -83.318766 34.025077, -83.317667 34.024372, -83.315869 34.0232, -83.316028 34.023064, -83.316068 34.023024, -83.316207 34.022901, -83.31866 34.020791, -83.318117 34.020336, -83.317199 34.019532, -83.316293 34.018785, -83.316017 34.018557, -83.315773 34.018332, -83.315572 34.018147, -83.315364 34.017933, -83.31512 34.017591, -83.314984 34.017357, -83.314927 34.01728, -83.314766 34.017045, -83.314622 34.016803, -83.314492 34.016555, -83.314379 34.016302, -83.314315 34.016129, -83.314269 34.015953, -83.31424 34.015775, -83.314234 34.015685, -83.314234 34.015595, -83.314253 34.015415, -83.314294 34.015238, -83.314358 34.015008, -83.314904 34.013367, -83.314907 34.013276, -83.314902 34.013186, -83.314891 34.013097, -83.314874 34.013008, -83.314849 34.01292, -83.314818 34.012834, -83.314781 34.012749, -83.314688 34.012586, -83.314633 34.012509, -83.314572 34.012434, -83.314506 34.012363, -83.314435 34.012295, -83.314358 34.012231, -83.314277 34.012172, -83.314191 34.012117, -83.314102 34.012066, -83.314009 34.012019, -83.313914 34.011977, -83.31376 34.01192, -83.31313 34.011638, -83.312984 34.011559, -83.313667 34.010689, -83.315063 34.008577, -83.315182 34.008453, -83.31566 34.007856, -83.31603 34.007417, -83.316473 34.006913, -83.316586 34.006742, -83.316664 34.006628, -83.316971 34.006149, -83.317208 34.005741, -83.31743 34.005328, -83.317915 34.004329, -83.318001 34.004141, -83.318065 34.003967, -83.318115 34.00379, -83.318136 34.003684, -83.31815 34.003612, -83.318156 34.003522, -83.318152 34.003432, -83.318137 34.003343, -83.318114 34.003255, -83.318081 34.003169, -83.318038 34.003086, -83.317987 34.003007, -83.317956 34.00297, -83.317872 34.002872, -83.316518 34.003747, -83.315769 34.004237, -83.315302 34.004547, -83.314535 34.005054, -83.311463 34.007041, -83.311358 34.006914, -83.311216 34.006758, -83.310956 34.006409, -83.310712 34.006007, -83.310569 34.005749, -83.310485 34.005597, -83.310448 34.005504, -83.310365 34.005337, -83.310265 34.005178, -83.310208 34.005101, -83.310085 34.004953, -83.309858 34.004709, -83.309734 34.004842, -83.309308 34.00542, -83.309008 34.005866, -83.307901 34.007684, -83.307631 34.008128, -83.307445 34.008388, -83.307024 34.008973, -83.306597 34.009501, -83.306239 34.009822, -83.305921 34.010067, -83.305668 34.010242, -83.305305 34.010431, -83.304862 34.01062, -83.304378 34.010806, -83.301128 34.01198, -83.299454 34.012551, -83.299341 34.012589, -83.299121 34.012415, -83.298939 34.01227, -83.29918 34.011948, -83.298862 34.01041, -83.298695 34.010103, -83.298359 34.009833, -83.298193 34.009719, -83.298052 34.00964, -83.29786 34.009561, -83.29762 34.009476, -83.296799 34.008989, -83.29552 34.008482, -83.295312 34.008575, -83.295164 34.008664, -83.294802 34.008823, -83.294508 34.008961, -83.294582 34.009343, -83.294654 34.009795, -83.294659 34.009984, -83.294638 34.010202, -83.294623 34.010283, -83.294573 34.010555, -83.294489 34.010717, -83.294424 34.010989, -83.294321 34.01118, -83.294243 34.011309, -83.293466 34.01133, -83.293284 34.011366, -83.293076 34.011396, -83.292903 34.011426, -83.292829 34.011438, -83.2927 34.011479, -83.292361 34.011588, -83.292152 34.011647, -83.291885 34.011781, -83.291493 34.011977, -83.29137 34.01206, -83.290963 34.012333, -83.290891 34.012405, -83.2907 34.012596, -83.290425 34.012869, -83.29006 34.013417, -83.290008 34.013497, -83.290251 34.013551, -83.290449 34.013574, -83.290606 34.013558, -83.290902 34.013521, -83.290969 34.013518, -83.291072 34.013529, -83.291167 34.013547, -83.291279 34.013604, -83.291392 34.013681, -83.291475 34.013769, -83.29226 34.013502, -83.293083 34.013195)))"} -{"geo_id":"29089","urban_area_code":"29089","name":"Fargo, ND--MN","lsad_name":"Fargo, ND--MN Urbanized Area","area_lsad_code":"75","mtfcc_feature_class_code":"G3500","type":"U","functional_status":"S","area_land_meters":181840759,"area_water_meters":149877,"internal_point_lon":-96.8206801,"internal_point_lat":46.8624502,"internal_point_geom":"POINT(-96.8206801 46.8624502)","urban_area_geom":"MULTIPOLYGON(((-96.79844 46.759629, -96.798765 46.759618, -96.798986 46.759624, -96.80027 46.759633, -96.800499 46.759635, -96.800647 46.759646, -96.8007 46.759656, -96.80076 46.759503, -96.800759 46.759397, -96.800724 46.759278, -96.800646 46.75912, -96.800424 46.758754, -96.800347 46.758682, -96.800313 46.758635, -96.800315 46.758533, -96.800281 46.75845, -96.80022 46.758366, -96.800195 46.758323, -96.800217 46.75808, -96.800285 46.758042, -96.800304 46.758003, -96.800294 46.757886, -96.800332 46.757679, -96.800409 46.757569, -96.80052 46.757461, -96.800653 46.75738, -96.800775 46.757325, -96.800922 46.75728, -96.801133 46.757254, -96.801985 46.757177, -96.802254 46.757148, -96.80296 46.757158, -96.803225 46.757141, -96.80329 46.757127, -96.803331 46.7571, -96.803356 46.757055, -96.803418 46.756199, -96.80355 46.755221, -96.803549 46.754858, -96.803537 46.754757, -96.803261 46.753866, -96.803237 46.753813, -96.803168 46.753702, -96.803044 46.753582, -96.802656 46.753256, -96.802264 46.752934, -96.80213 46.752864, -96.802001 46.752832, -96.801843 46.752819, -96.799186 46.75282, -96.79854 46.752811, -96.798508 46.753393, -96.798432 46.75477, -96.798388 46.755566, -96.798318 46.756644, -96.798315 46.756893, -96.798297 46.757341, -96.798302 46.757728, -96.798328 46.75837, -96.798349 46.758754, -96.798402 46.759232, -96.79844 46.759629)), ((-96.912147 46.773231, -96.912355 46.773038, -96.91291 46.772727, -96.913552 46.772367, -96.913952 46.771985, -96.914018 46.771714, -96.913836 46.771466, -96.913695 46.771391, -96.913558 46.771311, -96.913426 46.771228, -96.913305 46.771147, -96.913182 46.771059, -96.91309 46.771007, -96.912999 46.770947, -96.912916 46.770882, -96.912839 46.770813, -96.912773 46.770742, -96.912712 46.770663, -96.912661 46.770584, -96.912619 46.770502, -96.912587 46.770418, -96.912565 46.770333, -96.912552 46.77025, -96.912548 46.770188, -96.91255 46.770125, -96.912559 46.770051, -96.912574 46.769977, -96.912597 46.769905, -96.912608 46.769878, -96.912656 46.769775, -96.912712 46.769678, -96.912775 46.769583, -96.912847 46.769492, -96.912926 46.769403, -96.913013 46.769318, -96.913107 46.769237, -96.913208 46.769159, -96.913316 46.769086, -96.913364 46.769056, -96.913395 46.769041, -96.913416 46.769028, -96.913557 46.768876, -96.91359 46.768779, -96.913585 46.768643, -96.913536 46.768551, -96.913368 46.768453, -96.913151 46.768432, -96.912733 46.768339, -96.912337 46.768242, -96.911828 46.768019, -96.911497 46.767824, -96.911199 46.767661, -96.911079 46.767499, -96.911047 46.767352, -96.911041 46.767211, -96.911074 46.767114, -96.911188 46.766902, -96.911291 46.766729, -96.91135 46.766479, -96.911329 46.766262, -96.911307 46.766018, -96.911311 46.765771, -96.911632 46.76555, -96.912612 46.765243, -96.914084 46.765139, -96.914147 46.76512, -96.91488 46.763938, -96.914736 46.763588, -96.914465 46.763285, -96.914099 46.763094, -96.913589 46.762935, -96.913063 46.762951, -96.912665 46.763044, -96.912267 46.76311, -96.91198 46.763078, -96.911805 46.762935, -96.911852 46.762807, -96.912195 46.762523, -96.912448 46.762182, -96.912576 46.761829, -96.912583 46.761623, -96.912352 46.761333, -96.911959 46.761133, -96.911398 46.760968, -96.910858 46.760923, -96.910154 46.760928, -96.909453 46.760774, -96.909325 46.760729, -96.909267 46.760699, -96.909214 46.760665, -96.909166 46.760628, -96.909137 46.760602, -96.909084 46.760542, -96.909052 46.760493, -96.909027 46.760442, -96.90901 46.76039, -96.909002 46.760337, -96.909001 46.760284, -96.909006 46.760245, -96.909015 46.760207, -96.909037 46.760168, -96.909075 46.760114, -96.909119 46.760062, -96.90917 46.760013, -96.909227 46.759967, -96.909367 46.759881, -96.909427 46.759857, -96.90945 46.759842, -96.909539 46.759809, -96.909633 46.759783, -96.910012 46.759702, -96.910273 46.759643, -96.910642 46.759556, -96.910728 46.75955, -96.911025 46.759562, -96.911073 46.759564, -96.911144 46.759562, -96.911215 46.759558, -96.911296 46.759548, -96.911377 46.759534, -96.911435 46.759521, -96.911556 46.759485, -96.911617 46.759461, -96.911688 46.759428, -96.911761 46.759402, -96.911836 46.75938, -96.911896 46.759365, -96.911957 46.759352, -96.912061 46.759335, -96.912167 46.759299, -96.91227 46.759259, -96.912369 46.759215, -96.912464 46.759166, -96.912537 46.759124, -96.912606 46.759079, -96.912673 46.759032, -96.912693 46.759017, -96.91284 46.758926, -96.912929 46.758874, -96.913021 46.758823, -96.913091 46.758775, -96.913228 46.758676, -96.913359 46.758573, -96.91345 46.758496, -96.913537 46.758418, -96.91375 46.758201, -96.913825 46.758122, -96.913942 46.758007, -96.914026 46.757928, -96.914054 46.757904, -96.914221 46.757761, -96.914333 46.757672, -96.91443 46.757599, -96.914529 46.757528, -96.914581 46.757492, -96.914659 46.75744, -96.914831 46.757333, -96.914998 46.757223, -96.91516 46.75711, -96.915318 46.756994, -96.915343 46.756948, -96.91536 46.756901, -96.915367 46.756852, -96.915357 46.756759, -96.91534 46.756714, -96.915315 46.756671, -96.915283 46.75663, -96.915244 46.756592, -96.915199 46.756558, -96.915011 46.756475, -96.914828 46.756389, -96.91465 46.756298, -96.914612 46.756268, -96.914578 46.756236, -96.914598 46.7557, -96.914652 46.755655, -96.91471 46.755611, -96.914772 46.755571, -96.914858 46.755523, -96.914973 46.755449, -96.915082 46.755372, -96.915186 46.755291, -96.915285 46.755207, -96.91538 46.755119, -96.915478 46.755015, -96.915568 46.754908, -96.91565 46.754798, -96.915671 46.754767, -96.915718 46.754671, -96.915736 46.754607, -96.915745 46.754542, -96.915745 46.754483, -96.915738 46.754429, -96.915722 46.754367, -96.915699 46.75431, -96.915669 46.754255, -96.915632 46.754202, -96.915517 46.754123, -96.915397 46.754048, -96.915265 46.753971, -96.915143 46.753906, -96.914995 46.753833, -96.91485 46.753767, -96.914701 46.753706, -96.914557 46.753653, -96.914387 46.753596, -96.914291 46.753576, -96.914194 46.753561, -96.914096 46.753552, -96.913997 46.753549, -96.913894 46.753551, -96.9138 46.753559, -96.913707 46.753572, -96.913616 46.753589, -96.913527 46.753612, -96.913442 46.753651, -96.91336 46.753682, -96.913275 46.753708, -96.913187 46.753729, -96.913099 46.753745, -96.91301 46.753756, -96.912919 46.753762, -96.912828 46.753764, -96.912737 46.75376, -96.912683 46.753751, -96.912597 46.753733, -96.912515 46.753708, -96.912499 46.753708, -96.912477 46.753694, -96.912422 46.753671, -96.912369 46.753646, -96.912299 46.753605, -96.912234 46.753559, -96.912178 46.753509, -96.912167 46.753502, -96.912161 46.753491, -96.912107 46.753427, -96.91207 46.753366, -96.912041 46.753303, -96.912036 46.753287, -96.912017 46.753213, -96.912004 46.753138, -96.911996 46.753063, -96.911994 46.753006, -96.911996 46.752938, -96.912005 46.752856, -96.91202 46.752779, -96.912044 46.752692, -96.912048 46.752649, -96.91205 46.752572, -96.912046 46.752496, -96.912022 46.752322, -96.912012 46.752199, -96.912008 46.752083, -96.912012 46.751952, -96.912013 46.751933, -96.912024 46.751817, -96.912041 46.751702, -96.912064 46.751587, -96.912091 46.751513, -96.912125 46.75144, -96.912171 46.751362, -96.912227 46.751287, -96.91229 46.751215, -96.912362 46.751147, -96.912415 46.751103, -96.912472 46.751061, -96.912537 46.751033, -96.912607 46.75101, -96.91268 46.750993, -96.912755 46.750983, -96.912829 46.750978, -96.912908 46.750979, -96.912985 46.750986, -96.913059 46.750999, -96.913131 46.751019, -96.913199 46.751043, -96.913262 46.751073, -96.913386 46.751179, -96.913502 46.751288, -96.91361 46.751401, -96.91371 46.751518, -96.913801 46.751638, -96.913884 46.751761, -96.913957 46.751887, -96.914021 46.752015, -96.914065 46.752117, -96.914089 46.752223, -96.914127 46.752269, -96.91416 46.752316, -96.914201 46.75236, -96.914249 46.7524, -96.914304 46.752437, -96.914364 46.752468, -96.914429 46.752495, -96.9145 46.752517, -96.914575 46.752532, -96.914647 46.752541, -96.91472 46.752544, -96.914784 46.752542, -96.914866 46.752534, -96.914937 46.75252, -96.91506 46.752489, -96.915181 46.752452, -96.915288 46.752413, -96.91541 46.752362, -96.915518 46.75231, -96.91562 46.752253, -96.915718 46.752192, -96.915809 46.752127, -96.915894 46.752057, -96.915962 46.751994, -96.916066 46.751895, -96.916153 46.751803, -96.916232 46.751707, -96.916304 46.751608, -96.916368 46.751507, -96.91639 46.751432, -96.916405 46.751355, -96.916414 46.751278, -96.916415 46.751186, -96.916407 46.751093, -96.916388 46.751001, -96.916371 46.750901, -96.916353 46.750767, -96.916344 46.750642, -96.916343 46.750517, -96.916348 46.750393, -96.916349 46.750373, -96.916326 46.750182, -96.916326 46.750169, -96.916335 46.750099, -96.916353 46.75003, -96.91638 46.749962, -96.916415 46.749895, -96.916458 46.749832, -96.91651 46.749771, -96.916568 46.749713, -96.916634 46.749659, -96.916706 46.749609, -96.916785 46.749564, -96.916886 46.749533, -96.916991 46.749508, -96.917097 46.749487, -96.917206 46.749471, -96.917315 46.74946, -96.91741 46.749455, -96.917506 46.749454, -96.917601 46.749456, -96.917682 46.749467, -96.917762 46.749484, -96.917838 46.749508, -96.917909 46.749537, -96.917975 46.749572, -96.918027 46.749606, -96.918073 46.749643, -96.918114 46.749684, -96.918149 46.749727, -96.918303 46.750048, -96.91853 46.750523, -96.918578 46.750596, -96.918633 46.750666, -96.918691 46.75073, -96.918761 46.750798, -96.918842 46.750867, -96.91894 46.750939, -96.91904 46.751003, -96.919146 46.751061, -96.919258 46.751114, -96.919376 46.751162, -96.919498 46.751203, -96.91961 46.751242, -96.919764 46.751276, -96.919907 46.751308, -96.920054 46.751334, -96.920213 46.751355, -96.920368 46.75137, -96.920524 46.751377, -96.920681 46.751378, -96.920838 46.751372, -96.920928 46.751359, -96.921016 46.75134, -96.92107 46.751326, -96.921123 46.75131, -96.92126 46.751294, -96.921399 46.751283, -96.921538 46.751279, -96.921736 46.751284, -96.922305 46.751324, -96.923607 46.751199, -96.924374 46.750914, -96.925238 46.750753, -96.92607 46.750601, -96.926267 46.75057, -96.926266 46.750477, -96.926263 46.750111, -96.926261 46.750006, -96.926258 46.749819, -96.926246 46.749182, -96.926236 46.748819, -96.926205 46.748152, -96.921598 46.748656, -96.919535 46.748869, -96.918725 46.748953, -96.917888 46.749056, -96.917683 46.749095, -96.9175 46.749105, -96.917076 46.749153, -96.915979 46.749259, -96.915366 46.749335, -96.914659 46.749456, -96.914399 46.749503, -96.913738 46.749655, -96.912911 46.749891, -96.912463 46.75004, -96.912019 46.7502, -96.911549 46.750401, -96.911104 46.75061, -96.910357 46.751008, -96.909784 46.751364, -96.90946 46.751582, -96.909146 46.751831, -96.908742 46.752166, -96.908404 46.752478, -96.908129 46.752766, -96.907858 46.753068, -96.907821 46.75311, -96.907733 46.753225, -96.907625 46.753367, -96.907518 46.753508, -96.90744 46.75361, -96.907342 46.753742, -96.907236 46.753746, -96.907125 46.75375, -96.907009 46.753754, -96.906778 46.753763, -96.90666 46.753767, -96.906537 46.75377, -96.906273 46.753779, -96.905995 46.753784, -96.905712 46.753789, -96.905573 46.753791, -96.905436 46.753793, -96.905304 46.753794, -96.905178 46.753795, -96.905062 46.753796, -96.904954 46.753797, -96.904853 46.753799, -96.904666 46.753807, -96.904575 46.753811, -96.904484 46.753815, -96.904397 46.753818, -96.904319 46.753821, -96.90425 46.753824, -96.904151 46.753827, -96.904085 46.75383, -96.903968 46.753842, -96.903963 46.754013, -96.903951 46.754371, -96.90394 46.755004, -96.903924 46.755412, -96.902541 46.7554, -96.902492 46.755409, -96.902393 46.755427, -96.902306 46.75549, -96.902283 46.755594, -96.902279 46.756388, -96.902265 46.756667, -96.902251 46.756941, -96.901748 46.756946, -96.90063 46.756956, -96.9002 46.756959, -96.899159 46.756957, -96.899104 46.758989, -96.899078 46.760418, -96.899072 46.760494, -96.899034 46.760596, -96.901355 46.760589, -96.903029 46.760587, -96.903793 46.760587, -96.903774 46.761175, -96.903724 46.762674, -96.903717 46.762899, -96.903707 46.76308, -96.903567 46.763084, -96.903483 46.763095, -96.903432 46.763119, -96.903384 46.763206, -96.903331 46.763644, -96.903321 46.764097, -96.903341 46.764166, -96.903382 46.76419, -96.903457 46.764201, -96.90368 46.764209, -96.903621 46.766119, -96.903569 46.767792, -96.903568 46.767829, -96.903557 46.76816, -96.90351 46.76968, -96.905639 46.769808, -96.90584 46.769828, -96.907247 46.76991, -96.908136 46.769955, -96.908688 46.769991, -96.909176 46.770022, -96.909193 46.770375, -96.909209 46.770698, -96.909249 46.77086, -96.909355 46.771006, -96.909628 46.771328, -96.909767 46.771515, -96.909824 46.771713, -96.909821 46.77193, -96.909806 46.772178, -96.909788 46.773093, -96.909777 46.773378, -96.909729 46.773554, -96.909605 46.773752, -96.909256 46.774165, -96.909163 46.774324, -96.909114 46.774506, -96.909088 46.774927, -96.909083 46.775193, -96.908576 46.775182, -96.908546 46.77624, -96.908544 46.776287, -96.908441 46.779512, -96.908416 46.779693, -96.908317 46.779864, -96.90799 46.780308, -96.90741 46.781058, -96.907304 46.781219, -96.907162 46.781393, -96.90649 46.782273, -96.905678 46.783349, -96.905614 46.783443, -96.905579 46.783598, -96.905535 46.784205, -96.905452 46.785042, -96.905466 46.785134, -96.904199 46.785152, -96.903031 46.785162, -96.902933 46.788022, -96.902914 46.78858, -96.90289 46.789627, -96.902878 46.79016, -96.902838 46.791858, -96.902805 46.793624, -96.902773 46.794921, -96.90275 46.796156, -96.903074 46.79639, -96.903407 46.796523, -96.903787 46.796605, -96.904051 46.796618, -96.904314 46.796576, -96.904548 46.79645, -96.90474 46.796342, -96.904956 46.796162, -96.905153 46.796037, -96.905393 46.795923, -96.905597 46.795863, -96.905891 46.795785, -96.906172 46.795725, -96.906412 46.795677, -96.90661 46.795617, -96.906772 46.795461, -96.906855 46.795311, -96.906921 46.795012, -96.906951 46.79473, -96.906885 46.794532, -96.90682 46.794256, -96.906844 46.794035, -96.906873 46.793675, -96.906778 46.793151, -96.906623 46.792432, -96.906509 46.792178, -96.906623 46.791991, -96.90687 46.791817, -96.907305 46.791623, -96.907653 46.791583, -96.90802 46.79157, -96.908335 46.791597, -96.908689 46.79163, -96.909063 46.79161, -96.909283 46.791552, -96.909538 46.791463, -96.909812 46.791282, -96.909886 46.791088, -96.909799 46.790848, -96.909632 46.790694, -96.909277 46.790594, -96.908662 46.790554, -96.908174 46.79052, -96.907639 46.790507, -96.906991 46.790507, -96.906503 46.790447, -96.906075 46.790333, -96.905754 46.790159, -96.905346 46.789878, -96.904932 46.789627, -96.904647 46.789445, -96.904407 46.789395, -96.904265 46.78932, -96.904109 46.789164, -96.904042 46.789067, -96.904042 46.788873, -96.904109 46.788657, -96.904332 46.788411, -96.904578 46.788254, -96.904996 46.788113, -96.905466 46.787993, -96.906032 46.788038, -96.906584 46.788053, -96.906942 46.788053, -96.90721 46.787993, -96.90762 46.787941, -96.908052 46.787867, -96.908447 46.787829, -96.909126 46.787844, -96.909655 46.787799, -96.909841 46.787747, -96.909923 46.787643, -96.909946 46.787427, -96.909901 46.787233, -96.909715 46.78695, -96.909334 46.786607, -96.908613 46.786096, -96.909455 46.78466, -96.909518 46.784595, -96.909786 46.784425, -96.910072 46.784312, -96.910254 46.784214, -96.910376 46.784189, -96.910381 46.784076, -96.910501 46.783947, -96.910657 46.783875, -96.910792 46.783851, -96.911608 46.783858, -96.912201 46.783882, -96.912853 46.783892, -96.914388 46.78393, -96.914612 46.783912, -96.914716 46.783844, -96.914743 46.783827, -96.914786 46.783718, -96.914803 46.783429, -96.914904 46.779398, -96.915013 46.776297, -96.915048 46.775315, -96.912942 46.775265, -96.912007 46.775248, -96.911954 46.775177, -96.911355 46.774682, -96.911355 46.774457, -96.911655 46.774187, -96.911922 46.77385, -96.912089 46.773286, -96.912147 46.773231)), ((-96.753796 46.936212, -96.753957 46.936371, -96.754711 46.936883, -96.756011 46.937779, -96.756538 46.938131, -96.756735 46.938332, -96.757039 46.938707, -96.757071 46.938792, -96.757176 46.939022, -96.757243 46.939289, -96.757263 46.939365, -96.757286 46.938715, -96.757636 46.938717, -96.758689 46.938723, -96.759041 46.938725, -96.759045 46.939224, -96.759048 46.939481, -96.759036 46.939755, -96.759056 46.939916, -96.759101 46.940031, -96.759167 46.94014, -96.759372 46.940356, -96.759571 46.940515, -96.759643 46.940573, -96.759762 46.940656, -96.759992 46.940782, -96.760131 46.940825, -96.760289 46.940851, -96.760324 46.940853, -96.760514 46.940864, -96.761348 46.940864, -96.76169 46.940865, -96.762462 46.940869, -96.763052 46.940873, -96.764284 46.94087, -96.764726 46.940834, -96.764775 46.940821, -96.765101 46.940738, -96.765498 46.940557, -96.765937 46.940303, -96.767077 46.939647, -96.767228 46.939542, -96.767244 46.939528, -96.767294 46.939485, -96.767425 46.93933, -96.767484 46.939237, -96.767516 46.939165, -96.767519 46.939116, -96.767447 46.939051, -96.767958 46.93913, -96.768189 46.939138, -96.768349 46.939126, -96.768477 46.939061, -96.768517 46.939009, -96.768564 46.938765, -96.768635 46.938532, -96.768764 46.938401, -96.768929 46.938319, -96.76922 46.938264, -96.769684 46.938305, -96.770052 46.938361, -96.770593 46.938469, -96.770982 46.93857, -96.771256 46.938618, -96.771488 46.938678, -96.771839 46.938899, -96.771992 46.939004, -96.772114 46.939157, -96.772174 46.93926, -96.772183 46.939305, -96.772189 46.939353, -96.772193 46.939427, -96.772229 46.939611, -96.772338 46.939678, -96.772476 46.939731, -96.773063 46.939851, -96.77351 46.939928, -96.774079 46.940022, -96.774224 46.940025, -96.774437 46.940008, -96.77474 46.939907, -96.775178 46.939682, -96.77561 46.939429, -96.775738 46.939397, -96.775878 46.939373, -96.77608 46.939367, -96.77824 46.939378, -96.778258 46.941039, -96.778269 46.94121, -96.778268 46.941989, -96.777685 46.941988, -96.775939 46.941986, -96.775357 46.941986, -96.775136 46.941986, -96.774474 46.941989, -96.774254 46.94199, -96.774182 46.94199, -96.773991 46.942033, -96.773929 46.942048, -96.773812 46.942106, -96.773718 46.942193, -96.773642 46.942371, -96.773661 46.942541, -96.773707 46.942645, -96.773718 46.94267, -96.773838 46.942878, -96.773965 46.943048, -96.774082 46.943206, -96.774286 46.943458, -96.774366 46.943545, -96.774511 46.943702, -96.774858 46.944056, -96.774875 46.944073, -96.775191 46.94436, -96.775344 46.944426, -96.775506 46.944464, -96.77621 46.944472, -96.776706 46.944478, -96.777028 46.944477, -96.777994 46.944475, -96.778317 46.944475, -96.778329 46.945307, -96.778368 46.947803, -96.778381 46.948635, -96.778776 46.948632, -96.779964 46.948625, -96.78036 46.948623, -96.780657 46.94862, -96.78155 46.948614, -96.781848 46.948612, -96.783269 46.9486, -96.787532 46.948567, -96.788954 46.948557, -96.788953 46.948277, -96.788952 46.947437, -96.788952 46.947157, -96.788942 46.939899, -96.788744 46.939436, -96.788427 46.938931, -96.78808 46.938238, -96.787517 46.937256, -96.78733 46.936895, -96.787128 46.936491, -96.78707 46.936188, -96.787012 46.935567, -96.787041 46.9346, -96.787033 46.934115, -96.788533 46.934075, -96.789286 46.934007, -96.790119 46.933935, -96.791057 46.933872, -96.791213 46.933862, -96.791333 46.933853, -96.791419 46.934677, -96.791321 46.935321, -96.791071 46.935722, -96.79088 46.935919, -96.790512 46.936202, -96.790269 46.936393, -96.790013 46.936741, -96.789888 46.93705, -96.789908 46.937346, -96.78995 46.937685, -96.790184 46.938082, -96.790447 46.938404, -96.791873 46.939528, -96.792158 46.940564, -96.792083 46.941059, -96.792047 46.941287, -96.792286 46.941284, -96.793497 46.941299, -96.793556 46.941299, -96.793683 46.941299, -96.793745 46.941299, -96.793943 46.9413, -96.794195 46.9413, -96.794397 46.941302, -96.795236 46.941303, -96.796502 46.941306, -96.797801 46.94131, -96.797801 46.941352, -96.7978 46.941519, -96.797792 46.942355, -96.797795 46.943027, -96.797739 46.94317, -96.797974 46.944186, -96.798805 46.944206, -96.799449 46.944269, -96.800448 46.944676, -96.801461 46.945312, -96.802889 46.94589, -96.803977 46.946533, -96.80414 46.946911, -96.80408 46.947436, -96.803888 46.947813, -96.803472 46.948076, -96.802719 46.948411, -96.801293 46.94854, -96.800329 46.948893, -96.799678 46.949456, -96.799419 46.949976, -96.799277 46.950839, -96.799231 46.951765, -96.799242 46.951895, -96.79925 46.951985, -96.799296 46.952484, -96.799281 46.952692, -96.799409 46.952997, -96.799663 46.953653, -96.800045 46.954151, -96.800716 46.954488, -96.801195 46.954744, -96.801403 46.954888, -96.801674 46.954905, -96.802409 46.955306, -96.803176 46.955707, -96.80359 46.956252, -96.804102 46.956572, -96.804374 46.957292, -96.804486 46.957852, -96.80423 46.958332, -96.80391 46.958716, -96.802918 46.958924, -96.80167 46.959292, -96.801222 46.959388, -96.800454 46.95966, -96.800038 46.959836, -96.799638 46.960076, -96.79943 46.960396, -96.799366 46.960508, -96.799254 46.960524, -96.799126 46.960796, -96.799014 46.961308, -96.798774 46.961884, -96.798357 46.962556, -96.798213 46.962732, -96.797925 46.962972, -96.797285 46.96302, -96.796887 46.963313, -96.796388 46.963749, -96.795693 46.96426, -96.795201 46.964987, -96.794993 46.965433, -96.795045 46.965838, -96.795391 46.96614, -96.79622 46.966442, -96.79708 46.966841, -96.798272 46.967478, -96.7984 46.967398, -96.799049 46.967774, -96.799215 46.96787, -96.799615 46.968075, -96.800683 46.968323, -96.801209 46.968353, -96.80159 46.968287, -96.801971 46.96795, -96.80216 46.967519, -96.802268 46.966945, -96.802153 46.966467, -96.801817 46.96596, -96.80148 46.965564, -96.801191 46.965231, -96.801093 46.964817, -96.801313 46.964434, -96.801599 46.964209, -96.801981 46.964128, -96.802459 46.964204, -96.80313 46.964518, -96.803739 46.965337, -96.804457 46.963721, -96.80465 46.963493, -96.804762 46.963198, -96.805113 46.963201, -96.806612 46.963212, -96.80674 46.963212, -96.806818 46.963213, -96.806925 46.963212, -96.807129 46.963211, -96.807304 46.963163, -96.807432 46.963072, -96.807464 46.962916, -96.807463 46.962682, -96.807454 46.960114, -96.807424 46.959944, -96.807274 46.959871, -96.807241 46.959855, -96.806069 46.959848, -96.80603 46.958597, -96.805989 46.956469, -96.805963 46.955893, -96.805953 46.955678, -96.805909 46.955236, -96.805761 46.954901, -96.805373 46.954197, -96.804976 46.953475, -96.804725 46.952773, -96.804707 46.952153, -96.804731 46.951413, -96.804741 46.95083, -96.804744 46.95068, -96.804753 46.95013, -96.804793 46.949, -96.804817 46.948799, -96.804886 46.94822, -96.805597 46.948232, -96.806061 46.948235, -96.806521 46.948238, -96.806679 46.946961, -96.806741 46.946656, -96.80667 46.946272, -96.80661 46.945905, -96.806233 46.945116, -96.805739 46.944435, -96.805066 46.943615, -96.804819 46.943261, -96.804707 46.94297, -96.804621 46.942646, -96.804549 46.94209, -96.803993 46.942084, -96.803494 46.942081, -96.803335 46.942109, -96.803121 46.942211, -96.803143 46.941548, -96.803155 46.940959, -96.803179 46.939857, -96.803207 46.938502, -96.80321 46.938379, -96.803233 46.937517, -96.803285 46.934943, -96.80331 46.933693, -96.812337 46.937514, -96.812805 46.93773, -96.813637 46.938026, -96.814325 46.938192, -96.815003 46.938307, -96.815696 46.938355, -96.816118 46.938354, -96.816914 46.93831, -96.817552 46.938186, -96.818266 46.93803, -96.818958 46.937788, -96.819038 46.937959, -96.819034 46.938971, -96.819033 46.940529, -96.819032 46.941132, -96.819901 46.941144, -96.820238 46.941136, -96.820238 46.941859, -96.823 46.941859, -96.823 46.941161, -96.823472 46.941167, -96.825119 46.941181, -96.828353 46.939806, -96.829485 46.939281, -96.829727 46.938755, -96.829808 46.938109, -96.829688 46.933984, -96.829592 46.933572, -96.829691 46.933357, -96.829745 46.933069, -96.829727 46.930446, -96.829727 46.929485, -96.829835 46.929224, -96.830194 46.928613, -96.830374 46.928205, -96.83094 46.928083, -96.830899 46.925496, -96.830131 46.925456, -96.829768 46.924526, -96.829727 46.924001, -96.829618 46.921949, -96.829727 46.920605, -96.829808 46.919482, -96.829759 46.913765, -96.829877 46.911594, -96.829244 46.910453, -96.829361 46.910143, -96.827453 46.90751, -96.827161 46.90712, -96.82697 46.906931, -96.826689 46.906752, -96.826405 46.906611, -96.826112 46.906509, -96.826067 46.906493, -96.825618 46.906423, -96.824933 46.90641, -96.822561 46.906377, -96.822577 46.906104, -96.822587 46.905229, -96.822608 46.904921, -96.819785 46.904907, -96.819515 46.904904, -96.819251 46.904899, -96.816565 46.904885, -96.815325 46.904879, -96.812219 46.904863, -96.811681 46.904855, -96.809212 46.904819, -96.808807 46.904814, -96.80878 46.904622, -96.808808 46.903743, -96.808847 46.902997, -96.808843 46.902411, -96.808834 46.901219, -96.808834 46.901148, -96.808858 46.899394, -96.808882 46.897642, -96.808901 46.89613, -96.808879 46.894886, -96.808866 46.894152, -96.808872 46.894003, -96.808876 46.893135, -96.808877 46.893029, -96.808911 46.891778, -96.80898 46.891572, -96.809053 46.891352, -96.809183 46.890964, -96.809188 46.89055, -96.809189 46.890388, -96.809967 46.890391, -96.810694 46.890405, -96.810692 46.890526, -96.810734 46.890632, -96.810786 46.89071, -96.810925 46.890745, -96.814521 46.890766, -96.814445 46.890532, -96.814408 46.890403, -96.815333 46.8904, -96.815593 46.890399, -96.817393 46.890394, -96.821171 46.89042, -96.825193 46.891896, -96.826741 46.892246, -96.827487 46.892517, -96.829569 46.893347, -96.830148 46.893531, -96.830811 46.893742, -96.832989 46.894515, -96.833362 46.894662, -96.833926 46.894884, -96.83684 46.895912, -96.838229 46.896417, -96.839248 46.896788, -96.839518 46.896886, -96.8396 46.896916, -96.840639 46.897294, -96.842075 46.897816, -96.843221 46.898227, -96.844585 46.898716, -96.848464 46.900143, -96.852104 46.901468, -96.854508 46.902333, -96.857357 46.903381, -96.858554 46.903821, -96.861057 46.904714, -96.861429 46.904847, -96.861423 46.905152, -96.862291 46.905154, -96.867363 46.905167, -96.872278 46.9052, -96.877175 46.905193, -96.880473 46.905188, -96.880995 46.905205, -96.881168 46.905206, -96.88271 46.905217, -96.882717 46.904863, -96.882736 46.901601, -96.882777 46.898956, -96.882813 46.898056, -96.882816 46.897961, -96.882821 46.897538, -96.882824 46.897272, -96.885187 46.896403, -96.885181 46.896206, -96.886787 46.89562, -96.888734 46.89491, -96.888682 46.894679, -96.889581 46.894388, -96.893184 46.893222, -96.893193 46.892784, -96.894485 46.892323, -96.894855 46.892168, -96.895006 46.892458, -96.894955 46.892481, -96.894359 46.892913, -96.894081 46.893274, -96.899647 46.891166, -96.900045 46.890879, -96.900198 46.891127, -96.900154 46.891153, -96.899289 46.891596, -96.898004 46.892254, -96.897682 46.892386, -96.898058 46.892386, -96.899318 46.892408, -96.901233 46.892419, -96.901594 46.892397, -96.901986 46.892341, -96.90251 46.892195, -96.902854 46.892038, -96.903099 46.891837, -96.903672 46.891277, -96.904 46.891187, -96.903973 46.893436, -96.903961 46.894506, -96.903927 46.894893, -96.903928 46.895345, -96.905038 46.895353, -96.905032 46.894934, -96.905054 46.894578, -96.905186 46.89383, -96.905494 46.892634, -96.90566 46.891675, -96.905733 46.890972, -96.905717 46.89084, -96.905764 46.890841, -96.906618 46.890878, -96.907427 46.890953, -96.907227 46.890666, -96.907016 46.890202, -96.906869 46.889854, -96.906763 46.889485, -96.906711 46.889231, -96.906721 46.888968, -96.906668 46.888641, -96.906626 46.888335, -96.906563 46.887987, -96.906521 46.887881, -96.906384 46.887607, -96.906289 46.887396, -96.906236 46.887238, -96.906215 46.88708, -96.906268 46.886943, -96.906341 46.886785, -96.906426 46.886637, -96.906542 46.886436, -96.906668 46.886268, -96.906784 46.88612, -96.906858 46.886004, -96.906921 46.88592, -96.906932 46.885856, -96.906943 46.885804, -96.906953 46.885761, -96.90688 46.885654, -96.906697 46.885538, -96.906588 46.885512, -96.906277 46.885454, -96.905958 46.885386, -96.905595 46.885327, -96.905152 46.885261, -96.905026 46.885269, -96.904816 46.885277, -96.904572 46.88537, -96.904135 46.885462, -96.903951 46.885462, -96.903783 46.885395, -96.90369 46.885353, -96.903522 46.885151, -96.903455 46.884992, -96.90343 46.884874, -96.903547 46.884614, -96.903673 46.884479, -96.903934 46.88432, -96.904137 46.884167, -96.904264 46.883977, -96.904312 46.883724, -96.904287 46.883648, -96.904194 46.883556, -96.904043 46.883421, -96.903892 46.883304, -96.903825 46.883211, -96.903816 46.883069, -96.903925 46.882808, -96.904135 46.882514, -96.904406 46.881932, -96.904669 46.881948, -96.907852 46.882143, -96.908824 46.882203, -96.911766 46.882373, -96.913537 46.882476, -96.913976 46.882501, -96.914676 46.882542, -96.918014 46.882736, -96.920402 46.882881, -96.92042 46.881968, -96.92046 46.880711, -96.920464 46.880069, -96.920464 46.880007, -96.920466 46.879643, -96.920467 46.879414, -96.920462 46.879356, -96.920446 46.879166, -96.920443 46.879132, -96.920454 46.878834, -96.920492 46.877289, -96.922083 46.877303, -96.922429 46.877307, -96.923139 46.877314, -96.923457 46.877318, -96.924824 46.877323, -96.925156 46.877324, -96.926247 46.877344, -96.926246 46.877782, -96.926102 46.877794, -96.925987 46.877828, -96.925958 46.877909, -96.925934 46.878359, -96.925984 46.878424, -96.92607 46.878448, -96.926245 46.878451, -96.926242 46.878499, -96.926107 46.878674, -96.926234 46.878783, -96.926291 46.878912, -96.926428 46.879163, -96.926549 46.879207, -96.92665 46.879159, -96.929646 46.879149, -96.937901 46.879149, -96.937986 46.878822, -96.937995 46.878306, -96.938024 46.877383, -96.93786 46.877339, -96.936378 46.877331, -96.936401 46.877099, -96.93645 46.876779, -96.936537 46.876634, -96.936589 46.876456, -96.936622 46.876045, -96.936631 46.8756, -96.93461 46.875599, -96.932764 46.875578, -96.932541 46.875572, -96.932572 46.875445, -96.932845 46.873892, -96.93286 46.873698, -96.932861 46.873633, -96.932839 46.873225, -96.932842 46.873103, -96.932999 46.87308, -96.933679 46.873061, -96.935794 46.873086, -96.936618 46.873091, -96.936609 46.872333, -96.933684 46.872316, -96.932458 46.872305, -96.932463 46.869976, -96.92958 46.869975, -96.926214 46.869956, -96.926237 46.87234, -96.925172 46.872329, -96.924584 46.872323, -96.922814 46.87231, -96.922635 46.872345, -96.922501 46.872433, -96.922458 46.872573, -96.922462 46.872721, -96.922542 46.872812, -96.922699 46.872867, -96.923055 46.872873, -96.923387 46.872874, -96.925817 46.872878, -96.926229 46.872866, -96.926224 46.873279, -96.925791 46.873295, -96.923536 46.873261, -96.923337 46.873334, -96.923275 46.873468, -96.923271 46.873583, -96.923371 46.873672, -96.923392 46.873747, -96.922439 46.873729, -96.921936 46.873726, -96.921688 46.873843, -96.921619 46.873945, -96.921475 46.874019, -96.921315 46.874055, -96.920969 46.874049, -96.920839 46.873995, -96.920606 46.873971, -96.920473 46.874033, -96.920431 46.874154, -96.920393 46.874654, -96.920417 46.875008, -96.920433 46.875964, -96.920412 46.876825, -96.920453 46.877064, -96.918563 46.877055, -96.918361 46.87705, -96.917958 46.877042, -96.917586 46.877035, -96.917621 46.876822, -96.917774 46.875891, -96.917974 46.875045, -96.918102 46.874946, -96.918346 46.87462, -96.919823 46.872857, -96.92201 46.870306, -96.922384 46.869744, -96.922473 46.869464, -96.922493 46.869276, -96.922463 46.869034, -96.922294 46.868521, -96.921442 46.866326, -96.921322 46.86599, -96.921313 46.865964, -96.921276 46.865848, -96.920085 46.86584, -96.919781 46.865841, -96.919461 46.865841, -96.919142 46.865841, -96.918822 46.865841, -96.918486 46.865841, -96.918229 46.86584, -96.917739 46.865837, -96.91678 46.865824, -96.91682 46.863118, -96.916826 46.862714, -96.920245 46.862633, -96.920461 46.862635, -96.921595 46.862639, -96.921601 46.862571, -96.922864 46.862577, -96.923639 46.862581, -96.92373 46.862582, -96.92444 46.862595, -96.924912 46.862587, -96.925714 46.862586, -96.925879 46.862589, -96.926014 46.862615, -96.926115 46.862656, -96.926197 46.862751, -96.926206 46.86184, -96.926204 46.861808, -96.926206 46.861483, -96.921365 46.858416, -96.920778 46.858046, -96.92053 46.857889, -96.920193 46.857677, -96.920111 46.857626, -96.919835 46.857452, -96.919778 46.857416, -96.919063 46.856965, -96.918571 46.856655, -96.917063 46.855693, -96.912999 46.853102, -96.911887 46.852361, -96.91169 46.852235, -96.911151 46.851893, -96.910605 46.851558, -96.908095 46.849956, -96.907532 46.84965, -96.907637 46.849479, -96.907761 46.849204, -96.907919 46.848706, -96.907938 46.848512, -96.909627 46.84834, -96.912215 46.848077, -96.913064 46.848038, -96.913506 46.84802, -96.913754 46.848046, -96.914906 46.848046, -96.915106 46.848046, -96.915885 46.848046, -96.916407 46.848047, -96.917376 46.848056, -96.918282 46.848065, -96.918854 46.848067, -96.920332 46.848073, -96.920832 46.848075, -96.921231 46.848074, -96.921868 46.848073, -96.92289 46.848078, -96.923328 46.848081, -96.925059 46.845378, -96.925346 46.844924, -96.925474 46.844677, -96.925583 46.844351, -96.925662 46.843996, -96.925751 46.833813, -96.925745 46.833578, -96.92578 46.832904, -96.92577 46.832717, -96.925741 46.832568, -96.925642 46.83241, -96.925504 46.832233, -96.925198 46.832005, -96.924644 46.83165, -96.924358 46.831373, -96.92424 46.831107, -96.92422 46.830909, -96.924249 46.827383, -96.924245 46.827087, -96.92421 46.826899, -96.924081 46.826662, -96.923785 46.826376, -96.923351 46.826089, -96.922788 46.825704, -96.92258 46.825497, -96.922412 46.82523, -96.922363 46.825023, -96.922343 46.824924, -96.922358 46.824656, -96.92247 46.824148, -96.92364 46.819363, -96.923745 46.819031, -96.923816 46.819031, -96.923913 46.819032, -96.92412 46.819033, -96.925518 46.81904, -96.92641 46.819029, -96.926453 46.818258, -96.926434 46.815286, -96.926464 46.813101, -96.926409 46.812492, -96.926437 46.806904, -96.926455 46.804782, -96.926457 46.804551, -96.926063 46.804553, -96.924936 46.804509, -96.922938 46.804402, -96.922318 46.804378, -96.919237 46.804261, -96.915374 46.804116, -96.914083 46.804068, -96.913319 46.804039, -96.911815 46.803983, -96.911447 46.804173, -96.908993 46.804112, -96.905647 46.804025, -96.905147 46.804016, -96.904153 46.803991, -96.903444 46.803975, -96.903011 46.803963, -96.902806 46.803962, -96.902673 46.803962, -96.902611 46.803962, -96.902581 46.803962, -96.902521 46.803962, -96.902263 46.803961, -96.901996 46.803961, -96.901997 46.803927, -96.902013 46.803797, -96.901964 46.803679, -96.90195 46.803644, -96.901934 46.803439, -96.901979 46.803192, -96.902046 46.803081, -96.902134 46.802952, -96.902229 46.802862, -96.902431 46.802732, -96.902524 46.802658, -96.902574 46.802595, -96.902624 46.802474, -96.902632 46.802437, -96.902656 46.802339, -96.902724 46.802257, -96.902795 46.802218, -96.903058 46.802197, -96.903119 46.801694, -96.903144 46.801373, -96.903144 46.801008, -96.903114 46.800491, -96.903028 46.79982, -96.902966 46.79938, -96.902955 46.799212, -96.902908 46.798859, -96.902802 46.797474, -96.902759 46.797019, -96.902731 46.796718, -96.902748 46.796286, -96.90275 46.796156, -96.902636 46.796082, -96.902457 46.795986, -96.90233 46.795956, -96.902051 46.795991, -96.901865 46.796039, -96.901774 46.796091, -96.901668 46.796128, -96.90154 46.796403, -96.901572 46.796738, -96.901773 46.796884, -96.902036 46.797039, -96.902226 46.797202, -96.902349 46.797336, -96.902428 46.79749, -96.90244 46.797584, -96.902458 46.798016, -96.902416 46.798145, -96.90233 46.798245, -96.902162 46.798333, -96.901676 46.798486, -96.90163 46.798496, -96.901559 46.798513, -96.901404 46.798552, -96.901179 46.798617, -96.901057 46.798681, -96.900884 46.798767, -96.900834 46.798802, -96.900802 46.798824, -96.900787 46.798835, -96.900695 46.7989, -96.900618 46.799027, -96.900581 46.799207, -96.900581 46.79923, -96.90058 46.799303, -96.90058 46.799373, -96.900583 46.799442, -96.900588 46.799462, -96.900621 46.799537, -96.900627 46.799549, -96.900674 46.799638, -96.900688 46.799657, -96.900709 46.799676, -96.900762 46.799761, -96.900769 46.799771, -96.900846 46.799869, -96.900898 46.799922, -96.900952 46.799975, -96.901052 46.800063, -96.901158 46.800147, -96.90127 46.800228, -96.901389 46.800304, -96.901513 46.800376, -96.90168 46.800508, -96.901842 46.800643, -96.902025 46.800804, -96.9022 46.800969, -96.902257 46.801065, -96.902319 46.801156, -96.902388 46.801245, -96.902464 46.801332, -96.902513 46.801406, -96.902554 46.801482, -96.902577 46.801535, -96.902597 46.80159, -96.902609 46.801629, -96.902617 46.801685, -96.902618 46.801741, -96.902609 46.801801, -96.90259 46.801861, -96.902563 46.801919, -96.902526 46.801974, -96.902481 46.802028, -96.902434 46.802069, -96.902382 46.802109, -96.902324 46.802145, -96.902262 46.802177, -96.902196 46.802204, -96.902145 46.802222, -96.902092 46.802237, -96.902021 46.802248, -96.901952 46.802266, -96.901888 46.80229, -96.901829 46.802319, -96.901772 46.802356, -96.901487 46.802603, -96.901426 46.802657, -96.901373 46.802715, -96.901327 46.802778, -96.90129 46.802844, -96.901264 46.802905, -96.901246 46.802972, -96.901237 46.80304, -96.901236 46.803058, -96.901239 46.803134, -96.901248 46.803209, -96.901266 46.803306, -96.901271 46.803323, -96.901292 46.803403, -96.901321 46.803481, -96.901328 46.803502, -96.901337 46.803522, -96.901355 46.803561, -96.901376 46.803606, -96.901391 46.803634, -96.901426 46.803634, -96.901455 46.803733, -96.901583 46.803891, -96.901638 46.803961, -96.901442 46.80396, -96.901198 46.803966, -96.900952 46.803972, -96.900464 46.803942, -96.899546 46.803925, -96.896174 46.803937, -96.892411 46.803951, -96.892339 46.803951, -96.892211 46.803952, -96.892196 46.803952, -96.89218 46.803953, -96.892126 46.803955, -96.89209 46.803663, -96.892069 46.803548, -96.89203 46.803428, -96.892009 46.803299, -96.892013 46.803184, -96.89204 46.802654, -96.89192 46.802657, -96.89055 46.802666, -96.890058 46.802665, -96.889882 46.802632, -96.888834 46.802521, -96.888579 46.802508, -96.888135 46.802496, -96.887581 46.802511, -96.88648 46.802499, -96.886024 46.802482, -96.885839 46.802554, -96.885804 46.802674, -96.885783 46.803985, -96.884012 46.803976, -96.88368 46.803977, -96.88175 46.803983, -96.881746 46.803171, -96.881775 46.80152, -96.881756 46.801411, -96.881674 46.801353, -96.881726 46.799565, -96.881595 46.799656, -96.88104 46.79986, -96.878232 46.801102, -96.875545 46.802264, -96.872504 46.80353, -96.872274 46.803718, -96.872204 46.803912, -96.872069 46.803911, -96.871872 46.80391, -96.8718 46.803909, -96.871603 46.803908, -96.871452 46.803907, -96.871349 46.803906, -96.871284 46.803906, -96.86617 46.803871, -96.861233 46.803839, -96.860885 46.803837, -96.860861 46.803836, -96.860609 46.803914, -96.85847 46.803898, -96.854434 46.803867, -96.851238 46.803842, -96.850414 46.803838, -96.849252 46.803829, -96.848548 46.803846, -96.848196 46.803863, -96.846274 46.803972, -96.845973 46.803994, -96.845974 46.803922, -96.845976 46.80386, -96.84598 46.803797, -96.845948 46.803657, -96.845945 46.803643, -96.84595 46.803596, -96.845953 46.803526, -96.845954 46.803479, -96.845954 46.803426, -96.845955 46.803371, -96.845955 46.803314, -96.845956 46.803255, -96.845956 46.803194, -96.845956 46.803132, -96.845957 46.803071, -96.845957 46.803011, -96.845957 46.802952, -96.845958 46.802893, -96.845958 46.802833, -96.845957 46.80277, -96.845955 46.802706, -96.845953 46.802639, -96.845951 46.802569, -96.845949 46.802497, -96.845949 46.802423, -96.84595 46.80235, -96.845951 46.802278, -96.845953 46.802208, -96.845956 46.802145, -96.845964 46.802045, -96.845969 46.801985, -96.846007 46.80195, -96.845695 46.801962, -96.845588 46.80197, -96.845487 46.801985, -96.84539 46.802008, -96.845296 46.802037, -96.845207 46.802072, -96.845124 46.802114, -96.845048 46.802162, -96.84498 46.802214, -96.84492 46.802272, -96.844885 46.802313, -96.844803 46.80241, -96.844705 46.802511, -96.844599 46.802608, -96.844484 46.8027, -96.844362 46.802788, -96.844267 46.802849, -96.844116 46.802936, -96.843987 46.803001, -96.843926 46.803027, -96.843785 46.803081, -96.843704 46.803101, -96.843586 46.803127, -96.843521 46.803139, -96.843384 46.803156, -96.843318 46.803161, -96.843198 46.803165, -96.842813 46.803157, -96.842243 46.803143, -96.841678 46.803134, -96.841486 46.803122, -96.841371 46.803133, -96.841035 46.802707, -96.840409 46.801961, -96.840164 46.801633, -96.839975 46.801318, -96.83994 46.801218, -96.839875 46.801114, -96.839798 46.800958, -96.839805 46.800483, -96.83981 46.800221, -96.83981 46.800179, -96.839811 46.800158, -96.839814 46.800102, -96.839864 46.797331, -96.839074 46.797053, -96.838642 46.796914, -96.838075 46.796731, -96.838141 46.793686, -96.837912 46.793682, -96.837887 46.793682, -96.836197 46.793657, -96.835136 46.793642, -96.835124 46.794253, -96.836326 46.794271, -96.836305 46.795365, -96.836059 46.795361, -96.835998 46.795359, -96.835884 46.79535, -96.835771 46.795334, -96.835661 46.795311, -96.835558 46.795282, -96.834932 46.795085, -96.834553 46.795651, -96.837846 46.796661, -96.837851 46.796732, -96.834918 46.796674, -96.83477 46.796671, -96.829404 46.796565, -96.829389 46.79706, -96.829383 46.797246, -96.82937 46.797647, -96.829335 46.798804, -96.829285 46.800454, -96.829252 46.80151, -96.829223 46.802469, -96.829182 46.803565, -96.828881 46.803557, -96.828879 46.803793, -96.828294 46.803733, -96.82774 46.803678, -96.825473 46.80348, -96.825226 46.803455, -96.823958 46.803431, -96.823624 46.803415, -96.822668 46.803414, -96.822502 46.803414, -96.820473 46.803391, -96.820464 46.80355, -96.818919 46.803536, -96.818636 46.80353, -96.818634 46.803485, -96.818627 46.803374, -96.818621 46.803264, -96.818715 46.801698, -96.818796 46.799976, -96.819088 46.799975, -96.819983 46.799994, -96.820023 46.799995, -96.820139 46.799998, -96.820295 46.800001, -96.82045 46.800004, -96.820606 46.800008, -96.820762 46.800011, -96.820917 46.800014, -96.821073 46.800018, -96.821229 46.800021, -96.821384 46.800024, -96.82154 46.800028, -96.821695 46.800031, -96.821851 46.800034, -96.821982 46.800037, -96.822007 46.800038, -96.822162 46.800041, -96.822328 46.800042, -96.822518 46.800049, -96.822658 46.800052, -96.822656 46.799903, -96.82266 46.79982, -96.822663 46.799753, -96.822662 46.799686, -96.822657 46.799633, -96.822652 46.799604, -96.822631 46.799523, -96.822613 46.799477, -96.822599 46.799444, -96.822559 46.799369, -96.822525 46.799319, -96.822506 46.799294, -96.822463 46.799243, -96.822395 46.799164, -96.822368 46.79913, -96.822317 46.79906, -96.822259 46.798952, -96.822216 46.798894, -96.822189 46.798843, -96.82214 46.798731, -96.822111 46.798647, -96.822101 46.798617, -96.822074 46.798507, -96.822056 46.798392, -96.822049 46.798304, -96.822048 46.798146, -96.822052 46.797927, -96.822055 46.797749, -96.822059 46.79769, -96.821925 46.797693, -96.821609 46.79769, -96.821339 46.797688, -96.821313 46.797688, -96.82115 46.797689, -96.820987 46.797695, -96.820825 46.797705, -96.820663 46.79772, -96.820504 46.797739, -96.820346 46.797762, -96.820308 46.797766, -96.82027 46.797767, -96.820231 46.797764, -96.820194 46.797757, -96.820157 46.797746, -96.820122 46.797731, -96.820091 46.797713, -96.820064 46.797692, -96.82004 46.797666, -96.820023 46.797639, -96.820014 46.797617, -96.820009 46.797595, -96.820009 46.797572, -96.820036 46.796961, -96.82004 46.796933, -96.82005 46.796906, -96.820066 46.796881, -96.820087 46.796857, -96.820112 46.796836, -96.820144 46.796817, -96.820179 46.796801, -96.820217 46.796789, -96.820257 46.796781, -96.820289 46.796778, -96.82032 46.796778, -96.820525 46.796782, -96.82082 46.796788, -96.821115 46.796795, -96.82141 46.796801, -96.821706 46.796808, -96.822001 46.796814, -96.822123 46.796817, -96.822207 46.796819, -96.822281 46.796824, -96.822346 46.796833, -96.822408 46.796847, -96.822469 46.796866, -96.822525 46.796889, -96.822581 46.796918, -96.822631 46.79695, -96.822694 46.796881, -96.822749 46.796809, -96.822795 46.796733, -96.822832 46.796656, -96.822859 46.796576, -96.822877 46.796496, -96.822886 46.796414, -96.822887 46.79637, -96.822922 46.795721, -96.822951 46.795078, -96.823334 46.79509, -96.823406 46.795091, -96.823464 46.795088, -96.823513 46.79508, -96.823558 46.79507, -96.823604 46.795053, -96.823649 46.795033, -96.824507 46.794562, -96.824601 46.794494, -96.824672 46.794428, -96.824724 46.794355, -96.824764 46.794283, -96.82479 46.794208, -96.824804 46.794136, -96.82481 46.794059, -96.824835 46.793498, -96.824829 46.79344, -96.824809 46.793383, -96.824786 46.793343, -96.824754 46.793301, -96.824714 46.793259, -96.824657 46.793217, -96.824587 46.793182, -96.824517 46.793158, -96.822065 46.792388, -96.821984 46.792366, -96.821905 46.792349, -96.821831 46.79234, -96.821752 46.792334, -96.821658 46.79233, -96.821434 46.792327, -96.821464 46.79169, -96.821583 46.79169, -96.821721 46.791683, -96.82183 46.791668, -96.821892 46.791654, -96.821145 46.791415, -96.820553 46.791225, -96.819415 46.790922, -96.819383 46.791629, -96.819174 46.791623, -96.817722 46.791597, -96.81702 46.791585, -96.817039 46.791054, -96.817039 46.791034, -96.817036 46.791016, -96.81703 46.790998, -96.817017 46.790979, -96.816997 46.790957, -96.816975 46.790942, -96.81695 46.790927, -96.816917 46.790912, -96.815946 46.790599, -96.815915 46.790593, -96.815878 46.79059, -96.815842 46.79059, -96.815808 46.790592, -96.815779 46.790596, -96.815753 46.790601, -96.815728 46.790609, -96.81571 46.790618, -96.815691 46.79063, -96.815673 46.790645, -96.815655 46.790664, -96.815643 46.790683, -96.815637 46.790701, -96.815633 46.790719, -96.815624 46.791569, -96.814944 46.791563, -96.814561 46.79156, -96.814228 46.791557, -96.813625 46.791548, -96.813622 46.791572, -96.813617 46.791593, -96.81361 46.791612, -96.813601 46.79163, -96.813589 46.791647, -96.813571 46.791664, -96.813551 46.79168, -96.813528 46.791692, -96.813503 46.791703, -96.813472 46.791711, -96.813441 46.791715, -96.813409 46.791718, -96.813373 46.791718, -96.813332 46.791716, -96.813295 46.79171, -96.813258 46.791702, -96.813227 46.791691, -96.813201 46.791678, -96.813178 46.791662, -96.813166 46.79165, -96.813144 46.791624, -96.813134 46.791603, -96.813128 46.791581, -96.813126 46.791561, -96.813125 46.791543, -96.812118 46.791536, -96.81205 46.791536, -96.811999 46.791537, -96.811944 46.79154, -96.811885 46.791545, -96.811826 46.791555, -96.811771 46.791567, -96.811709 46.791582, -96.811645 46.791598, -96.811585 46.791615, -96.811514 46.791634, -96.8109 46.791812, -96.810157 46.792024, -96.810085 46.792043, -96.810022 46.792058, -96.809964 46.792071, -96.809902 46.792081, -96.809838 46.792088, -96.809772 46.792092, -96.809686 46.792093, -96.809522 46.792089, -96.809527 46.792008, -96.809671 46.789742, -96.812497 46.789772, -96.813311 46.789778, -96.813484 46.78978, -96.813512 46.789392, -96.813544 46.789, -96.813474 46.789, -96.810908 46.788975, -96.809172 46.788959, -96.809168 46.78879, -96.809178 46.787972, -96.80918 46.787783, -96.809073 46.787782, -96.80909 46.787557, -96.807815 46.787145, -96.807808 46.78777, -96.804919 46.787744, -96.801851 46.787719, -96.801794 46.787622, -96.80136 46.786994, -96.80087 46.786316, -96.800432 46.785718, -96.800089 46.785258, -96.800002 46.785141, -96.799943 46.785058, -96.799685 46.784695, -96.799564 46.784496, -96.799462 46.784329, -96.799331 46.784077, -96.799174 46.783755, -96.799029 46.783401, -96.798979 46.783189, -96.79895 46.783123, -96.798872 46.782822, -96.79878 46.782287, -96.798726 46.781734, -96.79872 46.781456, -96.798711 46.781028, -96.799111 46.781034, -96.79912 46.781435, -96.79988 46.78147, -96.799894 46.778093, -96.799111 46.778108, -96.799114 46.777911, -96.799895 46.777901, -96.799901 46.775262, -96.800839 46.775268, -96.800831 46.774415, -96.800833 46.774258, -96.800842 46.773688, -96.801348 46.773649, -96.802278 46.773649, -96.802664 46.773627, -96.80288 46.773527, -96.802979 46.773419, -96.803018 46.773307, -96.803018 46.773142, -96.80301 46.771462, -96.802999 46.771263, -96.802931 46.771121, -96.802768 46.770973, -96.802452 46.770815, -96.802526 46.770708, -96.802589 46.770586, -96.802621 46.770481, -96.802638 46.770317, -96.802629 46.770093, -96.802588 46.769942, -96.802429 46.769626, -96.802332 46.769509, -96.802145 46.769334, -96.802381 46.769209, -96.80256 46.769104, -96.802699 46.768976, -96.802755 46.768876, -96.802768 46.768852, -96.802856 46.768625, -96.802938 46.768175, -96.802983 46.768035, -96.803069 46.767868, -96.803094 46.767786, -96.803174 46.767647, -96.803028 46.767551, -96.802856 46.767428, -96.802444 46.767118, -96.802338 46.767035, -96.802215 46.766939, -96.802154 46.766877, -96.802113 46.766795, -96.802093 46.766679, -96.800829 46.766671, -96.798691 46.766657, -96.798694 46.764321, -96.798703 46.763348, -96.798705 46.763072, -96.798697 46.762653, -96.798674 46.762168, -96.798605 46.761285, -96.79856 46.760905, -96.79844 46.759629, -96.798148 46.759639, -96.798191 46.760147, -96.798314 46.761197, -96.798346 46.761993, -96.798394 46.762575, -96.798394 46.762652, -96.798399 46.763452, -96.798385 46.764235, -96.798388 46.765473, -96.798381 46.766642, -96.798392 46.770579, -96.79689 46.770555, -96.795079 46.77055, -96.79192 46.770506, -96.791207 46.770513, -96.790295 46.770503, -96.790495 46.770927, -96.790587 46.771134, -96.791418 46.772919, -96.791535 46.77319, -96.791603 46.773388, -96.791644 46.773809, -96.791652 46.774119, -96.791659 46.774217, -96.791723 46.774298, -96.791819 46.77435, -96.791942 46.774361, -96.792816 46.774362, -96.79501 46.774369, -96.79674 46.774386, -96.79697 46.774628, -96.798301 46.775732, -96.798421 46.775746, -96.798444 46.777014, -96.798437 46.777551, -96.798382 46.777711, -96.798341 46.777953, -96.798189 46.778009, -96.798122 46.778054, -96.797554 46.778581, -96.797465 46.778656, -96.797417 46.778698, -96.797228 46.778823, -96.797017 46.778951, -96.796423 46.779291, -96.796142 46.779485, -96.795996 46.779605, -96.795717 46.779902, -96.795234 46.780506, -96.794842 46.780996, -96.79441 46.781516, -96.793821 46.782662, -96.795946 46.783249, -96.798232 46.783215, -96.798653 46.783214, -96.798688 46.783352, -96.798952 46.784066, -96.799088 46.784332, -96.799198 46.784548, -96.799476 46.784945, -96.799561 46.785067, -96.79967 46.78521, -96.799699 46.785248, -96.799709 46.785262, -96.799893 46.785504, -96.800565 46.786428, -96.800498 46.786452, -96.800341 46.786509, -96.799969 46.786647, -96.800308 46.787114, -96.800456 46.787285, -96.800643 46.787478, -96.800806 46.787654, -96.801321 46.788395, -96.801522 46.788639, -96.801647 46.788764, -96.801766 46.788833, -96.801916 46.788857, -96.801988 46.788869, -96.802163 46.788884, -96.80218 46.788916, -96.802008 46.788922, -96.80205 46.789054, -96.802097 46.789203, -96.802204 46.789541, -96.802214 46.789586, -96.802219 46.789608, -96.802234 46.789676, -96.802248 46.789743, -96.802261 46.789811, -96.802273 46.789878, -96.802285 46.789946, -96.802296 46.790014, -96.802307 46.790082, -96.802317 46.79015, -96.802326 46.790218, -96.802376 46.790606, -96.802437 46.791087, -96.80246 46.791267, -96.802537 46.791869, -96.802637 46.79265, -96.802724 46.793334, -96.802649 46.79334, -96.802534 46.792487, -96.802505 46.792271, -96.802452 46.791794, -96.802421 46.791511, -96.802376 46.791211, -96.801996 46.791232, -96.801643 46.791258, -96.801416 46.791295, -96.801301 46.791343, -96.801019 46.791582, -96.800968 46.791626, -96.800829 46.791727, -96.800476 46.791986, -96.800205 46.792154, -96.800072 46.792211, -96.799369 46.79254, -96.799245 46.792604, -96.799155 46.792676, -96.799099 46.792768, -96.799097 46.792914, -96.799256 46.793385, -96.799308 46.793562, -96.79942 46.793945, -96.799463 46.794048, -96.799538 46.794138, -96.799609 46.794205, -96.799683 46.79425, -96.799867 46.794362, -96.799965 46.794409, -96.80012 46.794442, -96.800874 46.794582, -96.801005 46.794609, -96.801162 46.794641, -96.801437 46.794723, -96.801736 46.794834, -96.80267 46.795218, -96.802983 46.795357, -96.803036 46.795771, -96.803082 46.796129, -96.801599 46.796129, -96.801541 46.796128, -96.800091 46.796225, -96.799613 46.796257, -96.80135 46.796313, -96.801505 46.796401, -96.80207 46.796722, -96.802196 46.797334, -96.801904 46.797743, -96.801243 46.798093, -96.799901 46.798394, -96.798229 46.798725, -96.796596 46.79922, -96.795915 46.799755, -96.795575 46.800504, -96.795585 46.801194, -96.795918 46.801994, -96.796352 46.803135, -96.796616 46.803378, -96.796941 46.803936, -96.79751 46.804335, -96.798287 46.804651, -96.799221 46.804757, -96.800616 46.804417, -96.801681 46.804121, -96.802633 46.804232, -96.803163 46.804476, -96.803192 46.805039, -96.80292 46.805589, -96.801918 46.805898, -96.799872 46.805997, -96.79821 46.806128, -96.796897 46.806352, -96.796027 46.806828, -96.795803 46.807174, -96.79573 46.807733, -96.795964 46.808228, -96.796581 46.808705, -96.797578 46.809259, -96.798584 46.809531, -96.800106 46.809687, -96.801224 46.810042, -96.802065 46.810562, -96.802604 46.811194, -96.80275 46.81169, -96.802609 46.812108, -96.802138 46.812526, -96.8019 46.812646, -96.800474 46.813642, -96.799773 46.814307, -96.799468 46.815506, -96.799562 46.81662, -96.800105 46.818247, -96.800111 46.818299, -96.80017 46.81881, -96.80016 46.819664, -96.799751 46.820606, -96.799112 46.821432, -96.798327 46.821967, -96.797444 46.822398, -96.795153 46.822351, -96.79428 46.822367, -96.793252 46.822507, -96.792395 46.823037, -96.792083 46.823816, -96.792519 46.824767, -96.793096 46.826014, -96.792847 46.826824, -96.792395 46.827292, -96.791581 46.827733, -96.790657 46.827705, -96.789846 46.827533, -96.788646 46.827113, -96.788085 46.82691, -96.786994 46.826785, -96.783503 46.826224, -96.782817 46.825788, -96.782237 46.82521, -96.781679 46.82479, -96.78104 46.824697, -96.780398 46.824938, -96.780136 46.825429, -96.780252 46.825688, -96.778478 46.825671, -96.778477 46.825257, -96.778475 46.824016, -96.778475 46.823603, -96.778473 46.823527, -96.77847 46.8233, -96.778469 46.823225, -96.778467 46.823159, -96.778464 46.822965, -96.778464 46.8229, -96.778459 46.822585, -96.778444 46.821641, -96.77844 46.821327, -96.778433 46.820875, -96.778431 46.820729, -96.778418 46.818938, -96.778414 46.818341, -96.77338 46.818383, -96.773206 46.818384, -96.767774 46.818431, -96.767796 46.819895, -96.767865 46.824288, -96.767885 46.825571, -96.767887 46.825698, -96.767697 46.82572, -96.767611 46.825727, -96.767363 46.825728, -96.767283 46.825728, -96.767205 46.825728, -96.767042 46.825729, -96.766956 46.825729, -96.766868 46.825729, -96.766781 46.82573, -96.766608 46.82573, -96.766521 46.82573, -96.766433 46.82573, -96.766342 46.82573, -96.766253 46.82573, -96.766158 46.82573, -96.766063 46.825731, -96.765965 46.825732, -96.765868 46.825733, -96.765773 46.825733, -96.765585 46.82573, -96.765491 46.82573, -96.765399 46.82573, -96.765309 46.825731, -96.765221 46.825731, -96.765136 46.825732, -96.765055 46.825733, -96.764976 46.825734, -96.764898 46.825736, -96.764765 46.825741, -96.764663 46.82575, -96.764577 46.825779, -96.764466 46.825841, -96.764336 46.825928, -96.763795 46.826537, -96.763174 46.827308, -96.762002 46.828721, -96.761951 46.82878, -96.761926 46.828811, -96.761861 46.828889, -96.76182 46.828941, -96.761774 46.829, -96.761724 46.829063, -96.761671 46.829127, -96.761619 46.82919, -96.761566 46.829253, -96.761461 46.829379, -96.761404 46.82944, -96.761357 46.829485, -96.761344 46.829499, -96.761275 46.829552, -96.761197 46.829598, -96.76111 46.829635, -96.761017 46.829665, -96.760919 46.829682, -96.760818 46.829693, -96.760713 46.829697, -96.760608 46.829698, -96.7605 46.829697, -96.760391 46.829696, -96.760278 46.829695, -96.760165 46.829696, -96.759938 46.829699, -96.759825 46.8297, -96.759713 46.829699, -96.759601 46.829698, -96.75949 46.829696, -96.75938 46.829695, -96.759271 46.829695, -96.759162 46.829696, -96.759053 46.829697, -96.758943 46.829697, -96.758945 46.829637, -96.758946 46.829584, -96.758946 46.829518, -96.758947 46.829471, -96.758948 46.829358, -96.758948 46.829298, -96.758948 46.829237, -96.758948 46.829175, -96.758947 46.829048, -96.758946 46.828981, -96.758945 46.828911, -96.758943 46.828841, -96.758942 46.828776, -96.758942 46.828718, -96.75894 46.828632, -96.758939 46.828609, -96.758865 46.828607, -96.75878 46.828605, -96.758607 46.828603, -96.758436 46.828607, -96.758275 46.82861, -96.758194 46.82861, -96.758112 46.828609, -96.757943 46.828604, -96.757859 46.828604, -96.757775 46.828605, -96.757693 46.828605, -96.757614 46.828605, -96.757485 46.828603, -96.757441 46.828607, -96.757436 46.828262, -96.757435 46.828198, -96.757435 46.828187, -96.757434 46.828025, -96.757428 46.82758, -96.757375 46.827591, -96.757281 46.827594, -96.757164 46.827595, -96.757031 46.827595, -96.756962 46.827596, -96.756889 46.827597, -96.756819 46.827601, -96.756741 46.827596, -96.756667 46.827595, -96.756591 46.827596, -96.756515 46.827596, -96.756438 46.827597, -96.756363 46.827597, -96.756289 46.827598, -96.756217 46.827599, -96.756145 46.827599, -96.756072 46.827599, -96.755998 46.827598, -96.755925 46.827598, -96.75585 46.827598, -96.755773 46.827598, -96.755696 46.827599, -96.755617 46.827599, -96.755538 46.8276, -96.755457 46.827601, -96.755376 46.827601, -96.755292 46.8276, -96.755208 46.827598, -96.755124 46.827596, -96.755041 46.827595, -96.754957 46.827594, -96.754874 46.827593, -96.75479 46.827593, -96.754618 46.827594, -96.754533 46.827595, -96.754446 46.827597, -96.754183 46.827596, -96.754099 46.827596, -96.753834 46.827595, -96.753743 46.827596, -96.753652 46.827596, -96.753564 46.827597, -96.753479 46.827597, -96.753398 46.827597, -96.753319 46.827597, -96.753241 46.827597, -96.753164 46.827598, -96.753015 46.827601, -96.752942 46.827602, -96.752868 46.827602, -96.752793 46.827602, -96.752719 46.827602, -96.752644 46.827601, -96.752571 46.8276, -96.752498 46.827599, -96.752427 46.827599, -96.752358 46.827599, -96.752238 46.8276, -96.752118 46.827601, -96.752118 46.827636, -96.752127 46.827706, -96.752128 46.827794, -96.752128 46.827841, -96.752129 46.82789, -96.75213 46.827943, -96.752132 46.827999, -96.752134 46.828057, -96.752134 46.828114, -96.752134 46.828175, -96.752135 46.828237, -96.752136 46.8283, -96.752139 46.828363, -96.752142 46.828425, -96.752144 46.828488, -96.752144 46.82855, -96.752143 46.828611, -96.752142 46.828672, -96.75215 46.828863, -96.752152 46.828928, -96.752151 46.828973, -96.752151 46.829064, -96.75215 46.829133, -96.75215 46.829203, -96.752149 46.829274, -96.752149 46.829345, -96.752153 46.829416, -96.752156 46.829445, -96.752175 46.829869, -96.752177 46.829898, -96.751502 46.829896, -96.750853 46.829896, -96.750375 46.829896, -96.749561 46.829896, -96.749082 46.829896, -96.748246 46.829896, -96.747855 46.829896, -96.747259 46.829896, -96.747302 46.831657, -96.747313 46.832118, -96.747321 46.832491, -96.747334 46.833059, -96.747336 46.833125, -96.747156 46.833126, -96.747063 46.833126, -96.746204 46.833127, -96.74363 46.83313, -96.743295 46.833131, -96.743278 46.833131, -96.742817 46.833132, -96.742772 46.833132, -96.742544 46.833132, -96.742329 46.833132, -96.7423 46.833132, -96.742197 46.833132, -96.741862 46.833132, -96.741749 46.833132, -96.741635 46.833133, -96.741522 46.833133, -96.741186 46.833133, -96.741074 46.833134, -96.740838 46.833134, -96.740512 46.833134, -96.740485 46.833135, -96.740131 46.833135, -96.739896 46.833135, -96.739158 46.833135, -96.736947 46.833138, -96.73621 46.83314, -96.735533 46.833142, -96.733504 46.83315, -96.733422 46.83315, -96.732828 46.833154, -96.73169 46.833158, -96.730622 46.833162, -96.728275 46.833172, -96.727816 46.833174, -96.727138 46.833177, -96.726929 46.833177, -96.726305 46.833179, -96.726097 46.833181, -96.725854 46.833182, -96.725124 46.833185, -96.724882 46.833186, -96.72373 46.833192, -96.720275 46.833213, -96.719124 46.833221, -96.719121 46.833097, -96.719112 46.832726, -96.719109 46.832603, -96.718928 46.832599, -96.718904 46.832595, -96.718802 46.832578, -96.718676 46.83249, -96.718372 46.832302, -96.718309 46.832263, -96.718206 46.832184, -96.718022 46.83205, -96.717835 46.831926, -96.717173 46.831489, -96.716614 46.83148, -96.716164 46.831473, -96.715941 46.831471, -96.715274 46.831466, -96.715052 46.831465, -96.715038 46.830804, -96.715041 46.830629, -96.715037 46.830351, -96.714843 46.830353, -96.710734 46.830354, -96.710646 46.830293, -96.709902 46.830442, -96.707533 46.830346, -96.707123 46.830442, -96.706318 46.830631, -96.705599 46.830799, -96.705801 46.830987, -96.706408 46.831551, -96.706611 46.831739, -96.706927 46.832035, -96.707876 46.832926, -96.708193 46.833223, -96.709576 46.83452, -96.712756 46.837503, -96.712446 46.837669, -96.712298 46.837749, -96.71224 46.83778, -96.713337 46.838774, -96.713339 46.840248, -96.71334 46.840492, -96.713342 46.841482, -96.712255 46.841483, -96.708994 46.841488, -96.707908 46.841491, -96.707909 46.842149, -96.707912 46.843489, -96.708548 46.843488, -96.709207 46.843488, -96.709207 46.843969, -96.709207 46.845413, -96.709208 46.845895, -96.710036 46.845895, -96.712523 46.845896, -96.713352 46.845897, -96.71335 46.845414, -96.713347 46.843967, -96.713346 46.843485, -96.713509 46.843448, -96.713759 46.84343, -96.713924 46.843406, -96.714164 46.843357, -96.714358 46.843303, -96.714652 46.843191, -96.71492 46.843053, -96.715072 46.842935, -96.715746 46.84246, -96.715947 46.842335, -96.716112 46.842244, -96.716618 46.841989, -96.716676 46.84196, -96.71686 46.841868, -96.717148 46.841725, -96.71727 46.841727, -96.717414 46.841873, -96.717683 46.842117, -96.718218 46.842603, -96.718928 46.843282, -96.71934 46.843675, -96.719874 46.844185, -96.720552 46.844832, -96.721468 46.845723, -96.721652 46.845902, -96.721741 46.845991, -96.721994 46.846242, -96.722216 46.846453, -96.722504 46.846726, -96.722891 46.847077, -96.722937 46.847119, -96.723098 46.847273, -96.723149 46.847336, -96.72325 46.84743, -96.723554 46.847715, -96.723656 46.847811, -96.723863 46.848005, -96.724099 46.848226, -96.724486 46.848588, -96.724568 46.848665, -96.724695 46.848782, -96.725026 46.849093, -96.725337 46.849385, -96.72602 46.850028, -96.726351 46.850341, -96.726685 46.850653, -96.726776 46.850738, -96.727664 46.851573, -96.728049 46.851933, -96.728475 46.852331, -96.728437 46.852349, -96.728322 46.852406, -96.728285 46.852425, -96.72797 46.852581, -96.72788 46.852625, -96.72752 46.852808, -96.726531 46.853314, -96.725593 46.853808, -96.72529 46.853919, -96.725222 46.853937, -96.724923 46.85402, -96.724458 46.854109, -96.724288 46.854158, -96.724207 46.85417, -96.723907 46.854215, -96.723827 46.854235, -96.723745 46.854257, -96.723658 46.854281, -96.723566 46.854306, -96.723369 46.854358, -96.723264 46.854386, -96.723048 46.854442, -96.722827 46.854498, -96.722715 46.854527, -96.722601 46.854556, -96.722487 46.854583, -96.722367 46.854613, -96.722122 46.854677, -96.721916 46.854731, -96.721871 46.854743, -96.721742 46.854776, -96.721611 46.854808, -96.721478 46.854839, -96.721208 46.854897, -96.72107 46.854919, -96.720931 46.854937, -96.720791 46.854952, -96.72065 46.854962, -96.72051 46.854969, -96.72037 46.854971, -96.720229 46.854972, -96.719946 46.854973, -96.719805 46.854973, -96.719522 46.854974, -96.71938 46.854974, -96.719238 46.854974, -96.719096 46.854975, -96.718811 46.854975, -96.718668 46.854975, -96.718526 46.854976, -96.718384 46.854976, -96.718243 46.854976, -96.718105 46.854977, -96.717968 46.854979, -96.717834 46.854979, -96.715621 46.854997, -96.715443 46.855005, -96.715363 46.855024, -96.715269 46.855057, -96.715207 46.855104, -96.715165 46.855122, -96.715134 46.85516, -96.71512 46.855182, -96.715086 46.855259, -96.715066 46.855339, -96.715083 46.856859, -96.715072 46.858575, -96.715061 46.860291, -96.715048 46.862194, -96.715026 46.863419, -96.715014 46.864091, -96.714962 46.867096, -96.714941 46.868322, -96.716141 46.868328, -96.719744 46.868346, -96.720384 46.86835, -96.720395 46.868911, -96.725551 46.868942, -96.72584 46.869008, -96.726081 46.869063, -96.725983 46.869555, -96.725936 46.870309, -96.725926 46.87305, -96.725746 46.873046, -96.725742 46.873549, -96.725521 46.873528, -96.721819 46.873171, -96.721387 46.873166, -96.720431 46.873158, -96.718258 46.872939, -96.714895 46.8726, -96.714757 46.872586, -96.712654 46.872374, -96.70777 46.872328, -96.703556 46.87229, -96.703219 46.872287, -96.703216 46.872581, -96.703208 46.873465, -96.703207 46.873607, -96.703202 46.87376, -96.703199 46.873839, -96.703192 46.874078, -96.70319 46.874158, -96.703185 46.874293, -96.703173 46.874701, -96.703169 46.874837, -96.702618 46.874838, -96.700965 46.874841, -96.700415 46.874843, -96.699888 46.874841, -96.698307 46.874836, -96.69778 46.874835, -96.697249 46.874834, -96.695659 46.874832, -96.695129 46.874832, -96.694853 46.874829, -96.694027 46.874823, -96.693876 46.874822, -96.693753 46.874838, -96.693713 46.874898, -96.693699 46.874998, -96.693698 46.875252, -96.693696 46.875719, -96.69369 46.876126, -96.69369 46.876502, -96.693191 46.876505, -96.691784 46.876463, -96.690584 46.876436, -96.68949 46.876362, -96.687611 46.876329, -96.687429 46.87633, -96.687429 46.876577, -96.687422 46.877133, -96.687185 46.877403, -96.686909 46.877841, -96.68691 46.878995, -96.68691 46.879031, -96.686876 46.879138, -96.68689 46.879839, -96.68686 46.880688, -96.685843 46.880694, -96.685489 46.880696, -96.684156 46.880705, -96.683288 46.880711, -96.683393 46.883444, -96.683222 46.883936, -96.683277 46.890974, -96.693891 46.89092, -96.693891 46.890891, -96.693891 46.890806, -96.693891 46.890778, -96.693887 46.889571, -96.693885 46.889129, -96.693882 46.88774, -96.693881 46.887014, -96.693851 46.886327, -96.693849 46.886074, -96.693844 46.885315, -96.693843 46.885062, -96.693843 46.884888, -96.693843 46.884368, -96.693843 46.884195, -96.693843 46.884096, -96.693843 46.883799, -96.693843 46.883701, -96.694095 46.883701, -96.694852 46.883701, -96.695105 46.883702, -96.695361 46.883701, -96.696133 46.883701, -96.69639 46.883701, -96.696654 46.883698, -96.697448 46.883693, -96.697713 46.883691, -96.697963 46.883692, -96.698716 46.883697, -96.698967 46.883699, -96.699249 46.883697, -96.700096 46.883692, -96.700379 46.883691, -96.700633 46.883691, -96.701395 46.883693, -96.70165 46.883694, -96.7024 46.883688, -96.704651 46.883671, -96.705402 46.883666, -96.70731 46.883648, -96.713033 46.883595, -96.714942 46.883578, -96.714936 46.883239, -96.714918 46.882226, -96.714913 46.881888, -96.715832 46.881899, -96.715848 46.882102, -96.715879 46.882166, -96.715956 46.882271, -96.716576 46.882994, -96.7167 46.883091, -96.716809 46.883143, -96.717041 46.883206, -96.719613 46.883207, -96.719597 46.88224, -96.718621 46.882243, -96.718619 46.881119, -96.718579 46.881038, -96.718466 46.881008, -96.717474 46.880987, -96.717196 46.881062, -96.716994 46.881126, -96.716687 46.881285, -96.716513 46.881107, -96.716408 46.881031, -96.716265 46.880949, -96.716103 46.8809, -96.715863 46.880883, -96.714907 46.880874, -96.714905 46.880724, -96.714901 46.880275, -96.7149 46.880126, -96.716986 46.880114, -96.723246 46.88008, -96.723774 46.880077, -96.725449 46.880068, -96.725611 46.880067, -96.725589 46.880802, -96.725762 46.882162, -96.725807 46.883723, -96.725651 46.883721, -96.725715 46.887168, -96.725717 46.88731, -96.725722 46.887542, -96.725706 46.890194, -96.725716 46.890626, -96.725749 46.890771, -96.727436 46.890721, -96.73381 46.890683, -96.735935 46.890671, -96.735985 46.89067, -96.736136 46.890669, -96.736187 46.890669, -96.737625 46.890685, -96.73819 46.890683, -96.739892 46.890678, -96.741435 46.890683, -96.7442 46.890654, -96.744998 46.890646, -96.746204 46.890634, -96.746212 46.897805, -96.746202 46.905074, -96.746706 46.905071, -96.748644 46.90506, -96.754459 46.905029, -96.756237 46.90502, -96.756398 46.905018, -96.756585 46.905014, -96.757149 46.905006, -96.757275 46.905005, -96.757338 46.905005, -96.757379 46.905004, -96.757506 46.905004, -96.757548 46.905004, -96.757924 46.905002, -96.758195 46.905001, -96.758261 46.904999, -96.758327 46.905001, -96.759055 46.904998, -96.759432 46.904997, -96.75954 46.904996, -96.759865 46.904995, -96.759974 46.904995, -96.760423 46.904993, -96.761774 46.904987, -96.762224 46.904986, -96.762056 46.905203, -96.761934 46.905331, -96.761837 46.905413, -96.761796 46.90545, -96.761662 46.905551, -96.760357 46.906316, -96.759859 46.906609, -96.759574 46.906775, -96.759026 46.907097, -96.758725 46.907286, -96.758679 46.907316, -96.758447 46.907463, -96.758395 46.907495, -96.75824 46.907593, -96.758189 46.907626, -96.75815 46.90765, -96.758033 46.907724, -96.757995 46.907749, -96.757764 46.907895, -96.757319 46.908202, -96.755311 46.909591, -96.754642 46.910054, -96.753663 46.910722, -96.75348 46.910856, -96.753356 46.910948, -96.753151 46.911121, -96.752984 46.911254, -96.75273 46.911544, -96.752486 46.911859, -96.752289 46.912153, -96.752205 46.912327, -96.752154 46.912433, -96.752043 46.912734, -96.75197 46.91301, -96.751942 46.913211, -96.751924 46.913434, -96.751908 46.914551, -96.751888 46.915963, -96.751885 46.91613, -96.751878 46.916631, -96.751876 46.916799, -96.751874 46.917117, -96.751872 46.918074, -96.751871 46.918394, -96.751863 46.91867, -96.751841 46.9195, -96.751834 46.919777, -96.749375 46.919778, -96.74858 46.919806, -96.748375 46.919813, -96.746668 46.91984, -96.746445 46.919849, -96.746189 46.919852, -96.746179 46.934128, -96.746176 46.93612, -96.746272 46.936121, -96.746562 46.936124, -96.746659 46.936125, -96.746751 46.936125, -96.748061 46.936139, -96.752267 46.936183, -96.75367 46.936198, -96.753796 46.936212)))"} -{"geo_id":"33814","urban_area_code":"33814","name":"Goldsboro, NC","lsad_name":"Goldsboro, NC Urbanized Area","area_lsad_code":"75","mtfcc_feature_class_code":"G3500","type":"U","functional_status":"S","area_land_meters":137036928,"area_water_meters":467794,"internal_point_lon":-77.9833877,"internal_point_lat":35.387238,"internal_point_geom":"POINT(-77.9833877 35.387238)","urban_area_geom":"MULTIPOLYGON(((-78.016912 35.317081, -78.016669 35.316295, -78.016565 35.315968, -78.016506 35.315779, -78.016309 35.315053, -78.016252 35.314771, -78.016239 35.314681, -78.016217 35.314411, -78.016215 35.31423, -78.016231 35.313978, -78.016403 35.312526, -78.016444 35.312186, -78.016448 35.312112, -78.016543 35.31137, -78.016081 35.311342, -78.01569 35.31132, -78.014887 35.31127, -78.014831 35.311274, -78.014798 35.311287, -78.014769 35.31131, -78.014746 35.311349, -78.014741 35.311364, -78.014604 35.311806, -78.014592 35.311844, -78.014571 35.311878, -78.014538 35.311937, -78.014477 35.311989, -78.014403 35.312025, -78.014393 35.31203, -78.014325 35.312071, -78.014235 35.312106, -78.014135 35.312146, -78.014084 35.312167, -78.013968 35.312216, -78.01388 35.312254, -78.0138 35.312287, -78.013563 35.312388, -78.013484 35.312423, -78.013379 35.312467, -78.013196 35.312545, -78.013067 35.312599, -78.012964 35.312644, -78.012593 35.312802, -78.012162 35.312986, -78.01148 35.313276, -78.01111 35.313434, -78.011001 35.31348, -78.010966 35.313513, -78.010951 35.313552, -78.010951 35.313575, -78.010964 35.313605, -78.011206 35.313827, -78.011338 35.313943, -78.01159 35.314165, -78.012158 35.314671, -78.012574 35.315041, -78.013059 35.315475, -78.013283 35.315675, -78.013636 35.315984, -78.013673 35.316019, -78.013787 35.316124, -78.013826 35.316159, -78.013606 35.3163, -78.013597 35.316307, -78.013499 35.316361, -78.013401 35.316432, -78.013342 35.316539, -78.013728 35.316382, -78.013971 35.316285, -78.01405 35.31635, -78.014098 35.316367, -78.014129 35.316364, -78.014217 35.316359, -78.014672 35.316257, -78.014853 35.316218, -78.014978 35.316189, -78.015357 35.316103, -78.015483 35.316075, -78.015552 35.316321, -78.015762 35.317062, -78.015832 35.317309, -78.016047 35.31726, -78.016138 35.31724, -78.016695 35.317125, -78.016912 35.317081)), ((-78.055612 35.475624, -78.055717 35.47607, -78.056032 35.477411, -78.056137 35.477858, -78.056214 35.478163, -78.056403 35.478862, -78.05656 35.479476, -78.056696 35.479965, -78.056711 35.480032, -78.056743 35.48014, -78.056831 35.480482, -78.056835 35.480495, -78.056844 35.480549, -78.056931 35.480862, -78.057695 35.483742, -78.05803 35.485017, -78.058166 35.48557, -78.058269 35.48611, -78.058319 35.486436, -78.058377 35.486871, -78.058551 35.488427, -78.058574 35.488626, -78.058709 35.489679, -78.058792 35.490153, -78.058916 35.49066, -78.059036 35.491094, -78.059905 35.491088, -78.060828 35.491082, -78.061291 35.491061, -78.061385 35.491051, -78.061692 35.491031, -78.061927 35.491013, -78.062354 35.490963, -78.062453 35.490942, -78.062506 35.490933, -78.062589 35.490921, -78.062802 35.49087, -78.063044 35.490797, -78.063114 35.490772, -78.063158 35.490758, -78.063336 35.490681, -78.063446 35.490634, -78.063621 35.490545, -78.063781 35.490455, -78.063942 35.490349, -78.064131 35.490218, -78.064164 35.490196, -78.064572 35.489919, -78.065259 35.489444, -78.065692 35.489168, -78.066599 35.488651, -78.067029 35.488407, -78.067205 35.488309, -78.067433 35.48817, -78.067615 35.488073, -78.067886 35.487931, -78.068178 35.48782, -78.068297 35.487776, -78.068373 35.487753, -78.068299 35.487616, -78.068152 35.487358, -78.067842 35.48688, -78.067675 35.486688, -78.067507 35.486391, -78.067239 35.485985, -78.067051 35.485759, -78.066856 35.485534, -78.066728 35.485402, -78.066419 35.485122, -78.066217 35.484924, -78.065956 35.484716, -78.065714 35.484513, -78.065049 35.484068, -78.064022 35.483217, -78.06372 35.482909, -78.063599 35.48275, -78.062982 35.482025, -78.062377 35.481443, -78.062082 35.481184, -78.061893 35.480976, -78.061712 35.48074, -78.061558 35.480476, -78.061417 35.480201, -78.061061 35.479713, -78.06086 35.479493, -78.060611 35.479196, -78.060088 35.478631, -78.059773 35.478328, -78.05951 35.478048, -78.059282 35.477796, -78.059061 35.477614, -78.058728 35.477329, -78.058195 35.476873, -78.057939 35.476714, -78.057658 35.476549, -78.05698 35.476203, -78.056698 35.476115, -78.056436 35.476016, -78.056295 35.475939, -78.055879 35.475736, -78.055612 35.475624)), ((-78.060675 35.445599, -78.061156 35.445879, -78.061795 35.446251, -78.062144 35.446429, -78.062226 35.446465, -78.062303 35.446494, -78.062479 35.44656, -78.062653 35.446613, -78.062829 35.446657, -78.062997 35.44669, -78.0632 35.446723, -78.06353 35.446747, -78.063734 35.446752, -78.063902 35.446748, -78.064034 35.446736, -78.064159 35.446725, -78.064336 35.446698, -78.064513 35.446663, -78.064686 35.446619, -78.064895 35.446554, -78.065249 35.44643, -78.065565 35.446306, -78.065856 35.446178, -78.066075 35.446069, -78.066253 35.445969, -78.066377 35.445894, -78.066607 35.445756, -78.067095 35.445465, -78.066934 35.445282, -78.06652 35.44481, -78.066456 35.444733, -78.066302 35.444547, -78.06601 35.444716, -78.065207 35.445182, -78.065134 35.445223, -78.064842 35.44539, -78.064634 35.445083, -78.064498 35.444895, -78.064485 35.444881, -78.064293 35.444676, -78.064132 35.444526, -78.063611 35.444057, -78.063124 35.443612, -78.063013 35.44351, -78.062666 35.443194, -78.062473 35.442812, -78.061926 35.44366, -78.061663 35.444068, -78.061501 35.44432, -78.060675 35.445599)), ((-77.948414 35.351415, -77.948686 35.351344, -77.94875 35.351328, -77.949235 35.351207, -77.949403 35.351166, -77.949399 35.351121, -77.949397 35.350927, -77.949381 35.350807, -77.949287 35.350205, -77.949259 35.350066, -77.949235 35.349965, -77.949179 35.34983, -77.949161 35.349793, -77.94893 35.349842, -77.948912 35.349846, -77.948173 35.350034, -77.947927 35.350097, -77.948024 35.35036, -77.948073 35.350493, -77.948317 35.351151, -77.948341 35.351216, -77.948389 35.351321, -77.948414 35.351415)), ((-77.983455 35.468056, -77.983597 35.468082, -77.983761 35.468105, -77.984303 35.468215, -77.984699 35.468303, -77.985109 35.468394, -77.985467 35.468544, -77.985839 35.468706, -77.98642 35.468993, -77.986502 35.469032, -77.98675 35.469153, -77.987581 35.469554, -77.987983 35.469716, -77.988059 35.46974, -77.988196 35.469782, -77.988386 35.469842, -77.988938 35.469969, -77.989432 35.470069, -77.98899 35.469225, -77.988755 35.468777, -77.988549 35.468403, -77.987623 35.466719, -77.987542 35.466572, -77.987169 35.465883, -77.986861 35.466044, -77.986454 35.466147, -77.986383 35.466166, -77.986147 35.466226, -77.984222 35.466716, -77.983756 35.466835, -77.983479 35.466906, -77.983474 35.467136, -77.983459 35.467825, -77.983455 35.468056)), ((-77.867956 35.41317, -77.867308 35.41363, -77.866966 35.413875, -77.866415 35.414271, -77.865902 35.414661, -77.865727 35.414819, -77.865636 35.414915, -77.865465 35.415126, -77.865279 35.415398, -77.865044 35.415799, -77.865841 35.416088, -77.865915 35.416104, -77.866009 35.416117, -77.866152 35.416121, -77.866292 35.416106, -77.866461 35.416064, -77.866589 35.416027, -77.868344 35.415541, -77.868695 35.41633, -77.868786 35.416543, -77.868837 35.416652, -77.868911 35.416772, -77.868993 35.416857, -77.869095 35.416932, -77.869168 35.416974, -77.869316 35.417034, -77.869475 35.417074, -77.869575 35.417082, -77.869685 35.417077, -77.86986 35.41705, -77.870187 35.416935, -77.870321 35.416899, -77.870458 35.416872, -77.870594 35.416854, -77.870801 35.41682, -77.870886 35.416796, -77.87106 35.41672, -77.871137 35.416672, -77.87146 35.4164, -77.871658 35.416259, -77.87184 35.416152, -77.872031 35.416061, -77.872271 35.415959, -77.872551 35.415879, -77.872781 35.415828, -77.872996 35.415798, -77.87327 35.415783, -77.873538 35.415787, -77.874535 35.415879, -77.874633 35.415879, -77.874777 35.415863, -77.874896 35.415833, -77.875032 35.415777, -77.875154 35.415704, -77.87532 35.415579, -77.875563 35.415396, -77.875604 35.415366, -77.875877 35.415175, -77.875963 35.41512, -77.876188 35.415009, -77.876478 35.414889, -77.876541 35.414867, -77.876588 35.414851, -77.876882 35.414765, -77.877036 35.414697, -77.877111 35.414653, -77.877155 35.414619, -77.877229 35.414565, -77.877343 35.414444, -77.877495 35.414213, -77.877544 35.414102, -77.877563 35.414025, -77.877571 35.413946, -77.877569 35.413874, -77.877559 35.413827, -77.877544 35.413754, -77.877514 35.413677, -77.877432 35.413547, -77.877377 35.413486, -77.877274 35.413402, -77.877126 35.413319, -77.876901 35.41325, -77.87583 35.413107, -77.87477 35.412966, -77.87464 35.412957, -77.874567 35.412958, -77.874417 35.412976, -77.874349 35.412991, -77.874194 35.413041, -77.874021 35.413126, -77.873834 35.413269, -77.873728 35.413396, -77.873541 35.413647, -77.873427 35.413777, -77.8733 35.413888, -77.873175 35.413962, -77.872989 35.414035, -77.872845 35.414073, -77.872668 35.414088, -77.872488 35.414076, -77.872263 35.414052, -77.871164 35.41386, -77.87025 35.413701, -77.870002 35.413622, -77.869849 35.413541, -77.869588 35.413326, -77.869571 35.413337, -77.869521 35.413373, -77.869505 35.413385, -77.869195 35.413342, -77.868265 35.413213, -77.867956 35.41317)), ((-77.92021 35.337064, -77.920317 35.33706, -77.920432 35.337047, -77.92046 35.337052, -77.920476 35.337052, -77.920465 35.337089, -77.920449 35.337153, -77.920883 35.337102, -77.921524 35.337183, -77.921537 35.337172, -77.921638 35.33721, -77.92162 35.336876, -77.921616 35.336819, -77.921541 35.335649, -77.92153 35.335471, -77.921513 35.33526, -77.92151 35.335225, -77.921501 35.335123, -77.921499 35.335089, -77.921899 35.335186, -77.921947 35.335198, -77.923287 35.335542, -77.923588 35.335619, -77.923667 35.335639, -77.923734 35.335657, -77.924066 35.335742, -77.92424 35.335787, -77.924989 35.335975, -77.925063 35.335994, -77.925396 35.336078, -77.925472 35.336098, -77.925702 35.336158, -77.925779 35.336179, -77.925847 35.336196, -77.926054 35.33625, -77.926123 35.336268, -77.926262 35.336303, -77.926312 35.336315, -77.926679 35.336409, -77.926818 35.336445, -77.926966 35.336483, -77.927102 35.336519, -77.927412 35.336593, -77.927428 35.336597, -77.927563 35.336623, -77.927891 35.336686, -77.928094 35.336717, -77.928167 35.336728, -77.928426 35.336756, -77.928696 35.336779, -77.92894 35.336792, -77.929207 35.336797, -77.92951 35.33679, -77.929705 35.336781, -77.930079 35.336747, -77.930249 35.336725, -77.930406 35.336701, -77.930625 35.336663, -77.930876 35.336608, -77.931032 35.336574, -77.93119 35.336532, -77.931663 35.336408, -77.931822 35.336367, -77.931924 35.336339, -77.932231 35.336257, -77.932334 35.336231, -77.932632 35.336982, -77.932695 35.337097, -77.932733 35.337136, -77.93278 35.337168, -77.932834 35.337193, -77.933041 35.337243, -77.933265 35.337297, -77.934248 35.33752, -77.936681 35.338083, -77.936748 35.338097, -77.936872 35.338113, -77.936915 35.338113, -77.936956 35.338113, -77.93701 35.338109, -77.937906 35.337945, -77.938218 35.337882, -77.938271 35.338061, -77.938312 35.338153, -77.938356 35.338223, -77.938376 35.338255, -77.938435 35.338326, -77.93872 35.338585, -77.939175 35.338972, -77.939458 35.339213, -77.939887 35.339589, -77.940329 35.339967, -77.940383 35.34001, -77.94067 35.340243, -77.940693 35.340251, -77.940743 35.340259, -77.940819 35.340252, -77.940846 35.340243, -77.940951 35.340174, -77.941664 35.339579, -77.941929 35.339363, -77.942092 35.339222, -77.94273 35.338698, -77.942842 35.338597, -77.9429 35.338518, -77.942933 35.33843, -77.94294 35.338345, -77.94293 35.338286, -77.942871 35.338103, -77.942821 35.337946, -77.942488 35.336944, -77.942366 35.336568, -77.942061 35.335627, -77.941995 35.335445, -77.941987 35.335421, -77.941877 35.33507, -77.942272 35.334963, -77.943459 35.334646, -77.943538 35.334626, -77.943855 35.33454, -77.94412 35.334475, -77.944219 35.334444, -77.944253 35.33443, -77.94441 35.334373, -77.944485 35.334336, -77.944546 35.334284, -77.944609 35.334214, -77.944659 35.334144, -77.9449 35.333688, -77.944968 35.333546, -77.944987 35.333491, -77.945033 35.333313, -77.945053 35.333185, -77.945061 35.333085, -77.945461 35.333109, -77.945531 35.333116, -77.945799 35.333151, -77.946116 35.333208, -77.94641 35.333276, -77.947229 35.333498, -77.947371 35.333252, -77.947421 35.333168, -77.947777 35.332502, -77.947912 35.332252, -77.94814 35.331825, -77.948271 35.331557, -77.948392 35.331255, -77.948503 35.330938, -77.948605 35.330558, -77.94862 35.330483, -77.948651 35.330336, -77.948698 35.330012, -77.948721 35.329793, -77.948737 35.329321, -77.948718 35.328967, -77.948715 35.328897, -77.948653 35.32844, -77.948595 35.328181, -77.948488 35.327791, -77.948445 35.327661, -77.948353 35.327419, -77.948143 35.326966, -77.947989 35.32668, -77.947798 35.326372, -77.94763 35.326131, -77.947592 35.326083, -77.947344 35.325772, -77.947086 35.325473, -77.946911 35.325291, -77.946655 35.325079, -77.946477 35.324932, -77.94588 35.324452, -77.945622 35.324245, -77.945307 35.323992, -77.944382 35.323248, -77.940663 35.320258, -77.939902 35.319646, -77.939427 35.319257, -77.939323 35.319172, -77.939014 35.31892, -77.938911 35.318836, -77.938781 35.31872, -77.937991 35.318052, -77.937656 35.317769, -77.935971 35.316341, -77.935334 35.315801, -77.935226 35.315709, -77.934306 35.314926, -77.934153 35.314796, -77.933694 35.314406, -77.933542 35.314277, -77.934461 35.31437, -77.936513 35.31458, -77.936947 35.314624, -77.93722 35.314649, -77.937318 35.314658, -77.937639 35.314676, -77.938144 35.314695, -77.938093 35.314659, -77.938033 35.314605, -77.937659 35.314295, -77.937457 35.314119, -77.937356 35.314032, -77.936261 35.313105, -77.935409 35.312379, -77.934727 35.311798, -77.93429 35.311425, -77.93405 35.3112, -77.934027 35.311181, -77.933993 35.311152, -77.933082 35.310383, -77.932809 35.310141, -77.931897 35.309366, -77.931539 35.309062, -77.931189 35.30876, -77.93091 35.30851, -77.930725 35.308344, -77.930056 35.307783, -77.92977 35.307543, -77.929669 35.307446, -77.929581 35.307378, -77.929556 35.307357, -77.92934 35.307173, -77.929047 35.306938, -77.928901 35.306814, -77.928686 35.30663, -77.927881 35.305943, -77.927426 35.305555, -77.927334 35.305489, -77.926775 35.305016, -77.926492 35.304771, -77.926399 35.304697, -77.926007 35.304356, -77.925462 35.303891, -77.925045 35.303536, -77.924926 35.303442, -77.92472 35.303292, -77.924634 35.303235, -77.924459 35.303119, -77.924355 35.303056, -77.923995 35.302837, -77.92351 35.302534, -77.923264 35.302381, -77.923228 35.302363, -77.923001 35.302247, -77.922807 35.302172, -77.922415 35.302046, -77.922099 35.30195, -77.919946 35.301302, -77.919086 35.301039, -77.918639 35.300907, -77.917901 35.30069, -77.917829 35.300673, -77.917767 35.300669, -77.917469 35.300688, -77.917545 35.30076, -77.917711 35.300898, -77.91772 35.300906, -77.917828 35.301001, -77.917934 35.301086, -77.918481 35.301551, -77.918735 35.301767, -77.918848 35.30186, -77.919188 35.302142, -77.919302 35.302237, -77.919728 35.302592, -77.920153 35.302945, -77.921 35.303667, -77.921423 35.304028, -77.921678 35.304246, -77.922446 35.3049, -77.922702 35.305119, -77.923107 35.305465, -77.923625 35.305907, -77.923797 35.306047, -77.924252 35.306436, -77.924326 35.306499, -77.924735 35.306842, -77.924945 35.307019, -77.925377 35.307384, -77.925571 35.307555, -77.925724 35.307689, -77.925777 35.307738, -77.926149 35.308082, -77.92627 35.308194, -77.926825 35.308829, -77.926881 35.3089, -77.927086 35.309132, -77.927151 35.309223, -77.927313 35.309451, -77.927438 35.309641, -77.927519 35.309745, -77.927532 35.309761, -77.927731 35.31008, -77.927785 35.310203, -77.927944 35.310472, -77.92813 35.310828, -77.928152 35.310877, -77.928358 35.311326, -77.928555 35.311937, -77.928678 35.312359, -77.928767 35.3127, -77.928845 35.31307, -77.928852 35.313103, -77.928917 35.313451, -77.928978 35.313828, -77.928996 35.314025, -77.929006 35.314252, -77.929024 35.314624, -77.929022 35.314984, -77.929004 35.315327, -77.928983 35.315527, -77.928948 35.315871, -77.928936 35.315951, -77.92889 35.316221, -77.928856 35.316427, -77.928774 35.316803, -77.928714 35.317023, -77.928644 35.317288, -77.928638 35.317302, -77.928622 35.317347, -77.928618 35.317362, -77.9285 35.317324, -77.928314 35.317296, -77.928217 35.317286, -77.928005 35.317265, -77.92781 35.317252, -77.927721 35.317271, -77.927673 35.317302, -77.927624 35.31736, -77.927621 35.317395, -77.927637 35.317472, -77.927792 35.317534, -77.927955 35.317568, -77.928099 35.317602, -77.928498 35.317698, -77.928365 35.31807, -77.928346 35.318109, -77.928041 35.318773, -77.927788 35.319302, -77.9276 35.319699, -77.927087 35.319502, -77.926861 35.319416, -77.925556 35.318894, -77.925047 35.318691, -77.924768 35.318582, -77.923931 35.318255, -77.923652 35.318146, -77.923256 35.317979, -77.923062 35.317898, -77.92209 35.317515, -77.922061 35.317506, -77.921805 35.317435, -77.921648 35.317393, -77.921512 35.317347, -77.921452 35.31732, -77.921031 35.317135, -77.920882 35.317063, -77.920694 35.316973, -77.920509 35.31689, -77.920285 35.31679, -77.919956 35.316638, -77.919891 35.316608, -77.919764 35.316579, -77.919719 35.316581, -77.919636 35.316585, -77.919586 35.316591, -77.919542 35.316597, -77.919382 35.316616, -77.919311 35.316625, -77.919225 35.316622, -77.919141 35.316609, -77.919 35.316569, -77.918914 35.316538, -77.918903 35.316535, -77.918767 35.316476, -77.918587 35.316392, -77.918047 35.316143, -77.918027 35.316134, -77.917867 35.316061, -77.917683 35.315975, -77.917515 35.315897, -77.91713 35.315725, -77.916946 35.315643, -77.916775 35.315565, -77.91643 35.315409, -77.916263 35.315337, -77.916092 35.315264, -77.915958 35.315198, -77.915926 35.315183, -77.915425 35.314951, -77.915258 35.314874, -77.91513 35.315017, -77.915099 35.315062, -77.915054 35.315131, -77.914998 35.315253, -77.914928 35.315436, -77.914858 35.31571, -77.914845 35.315752, -77.914774 35.315988, -77.914652 35.315961, -77.914434 35.315923, -77.914188 35.315907, -77.914082 35.315901, -77.91402 35.315902, -77.91391 35.315914, -77.913833 35.315929, -77.913703 35.315983, -77.913579 35.316045, -77.913451 35.316139, -77.913363 35.316239, -77.912904 35.316889, -77.912869 35.316928, -77.91284 35.316963, -77.912527 35.317412, -77.912343 35.317668, -77.911792 35.318439, -77.911609 35.318696, -77.911847 35.318811, -77.912529 35.319141, -77.912563 35.319156, -77.912803 35.319269, -77.913187 35.319453, -77.914341 35.320008, -77.914726 35.320193, -77.914815 35.320236, -77.915083 35.320365, -77.915173 35.320408, -77.915612 35.32062, -77.916059 35.320835, -77.916622 35.321095, -77.916903 35.321206, -77.916948 35.321219, -77.917322 35.321332, -77.917417 35.321357, -77.917608 35.321408, -77.918184 35.321564, -77.918376 35.321617, -77.918569 35.321667, -77.91915 35.32182, -77.919344 35.321871, -77.919529 35.321919, -77.920086 35.322066, -77.920272 35.322115, -77.920738 35.322238, -77.922137 35.322609, -77.922457 35.322694, -77.922602 35.322741, -77.922718 35.322782, -77.922905 35.322849, -77.92302 35.32289, -77.923068 35.322909, -77.923183 35.322957, -77.923517 35.323097, -77.923776 35.323214, -77.924461 35.323526, -77.924754 35.323655, -77.925385 35.323908, -77.925547 35.323961, -77.925473 35.3241, -77.925457 35.324138, -77.925024 35.325039, -77.924754 35.325635, -77.924536 35.32607, -77.924516 35.326112, -77.924494 35.326154, -77.924235 35.326673, -77.923865 35.327437, -77.923786 35.327602, -77.923232 35.328798, -77.922759 35.329962, -77.922166 35.331523, -77.921643 35.332904, -77.921615 35.332977, -77.921549 35.333143, -77.921267 35.333859, -77.921173 35.334098, -77.921127 35.334225, -77.920991 35.334609, -77.920947 35.334738, -77.920932 35.334778, -77.92089 35.334897, -77.920876 35.334938, -77.920851 35.335007, -77.920777 35.335214, -77.920753 35.335284, -77.920727 35.335364, -77.920648 35.335605, -77.920623 35.335686, -77.92058 35.335816, -77.920539 35.335944, -77.920456 35.336208, -77.920415 35.336339, -77.920382 35.336444, -77.920257 35.336896, -77.92021 35.337064)), ((-78.07632 35.414903, -78.076063 35.417031, -78.07811 35.418346, -78.07807 35.418383, -78.077979 35.418489, -78.077922 35.418564, -78.077905 35.418588, -78.077834 35.418715, -78.077819 35.418742, -78.077803 35.418818, -78.077798 35.418845, -78.077797 35.418913, -78.077797 35.418924, -78.077791 35.418974, -78.07779 35.41899, -78.077769 35.418992, -78.077709 35.419, -78.077272 35.41906, -78.077223 35.419101, -78.076833 35.419391, -78.075759 35.419033, -78.075743 35.419194, -78.075735 35.419366, -78.076337 35.419699, -78.076755 35.419932, -78.079815 35.421638, -78.080305 35.421912, -78.080437 35.421986, -78.080834 35.42221, -78.080998 35.422302, -78.081297 35.42247, -78.081493 35.422576, -78.081659 35.422666, -78.082798 35.423297, -78.08368 35.423785, -78.085486 35.424789, -78.086213 35.425199, -78.086822 35.425542, -78.087354 35.425829, -78.087375 35.425819, -78.087384 35.425807, -78.087458 35.425721, -78.087483 35.425693, -78.087536 35.425632, -78.087589 35.425451, -78.087616 35.42527, -78.087688 35.42515, -78.08773 35.425083, -78.087952 35.424847, -78.088085 35.424693, -78.088301 35.42449, -78.088495 35.424325, -78.088615 35.424198, -78.088723 35.424039, -78.088797 35.423907, -78.088829 35.42381, -78.08885 35.423748, -78.088857 35.423671, -78.088877 35.423512, -78.088851 35.423298, -78.088848 35.423225, -78.088843 35.423067, -78.088816 35.422897, -78.088769 35.422683, -78.088756 35.422589, -78.088743 35.422474, -78.088702 35.421842, -78.088699 35.421772, -78.088689 35.421518, -78.088649 35.421178, -78.088615 35.420749, -78.088615 35.420574, -78.088628 35.420425, -78.088614 35.42025, -78.088554 35.420036, -78.088494 35.419915, -78.0884 35.41981, -78.088326 35.419673, -78.088305 35.419508, -78.088306 35.419311, -78.088286 35.419173, -78.088164 35.418998, -78.088118 35.41886, -78.087997 35.418586, -78.08795 35.418388, -78.087876 35.418135, -78.087862 35.417866, -78.087782 35.417636, -78.087757 35.417572, -78.087715 35.41746, -78.087667 35.417273, -78.087641 35.416872, -78.087667 35.416664, -78.087754 35.416417, -78.087871 35.41622, -78.087894 35.416179, -78.087648 35.416167, -78.087414 35.416147, -78.087132 35.416116, -78.086637 35.416055, -78.086038 35.415987, -78.085844 35.415976, -78.08546 35.415976, -78.084963 35.415997, -78.084211 35.41605, -78.083203 35.416174, -78.082971 35.41621, -78.08185 35.416395, -78.081671 35.41639, -78.081422 35.416384, -78.081104 35.416338, -78.080833 35.416274, -78.079921 35.415987, -78.07886 35.415648, -78.077585 35.415266, -78.076741 35.415021, -78.07632 35.414903)), ((-77.983455 35.468056, -77.982895 35.467956, -77.982862 35.46795, -77.981216 35.467667, -77.980785 35.467593, -77.980656 35.467579, -77.980669 35.467658, -77.980924 35.470132, -77.981049 35.471319, -77.981273 35.47346, -77.981352 35.47418, -77.9816 35.476592, -77.981637 35.476992, -77.981664 35.477753, -77.981719 35.480222, -77.981732 35.480656, -77.981745 35.48131, -77.981825 35.484877, -77.981832 35.485418, -77.981862 35.486796, -77.981866 35.487014, -77.981879 35.487559, -77.981885 35.487936, -77.98189 35.488206, -77.981891 35.488221, -77.981908 35.488857, -77.98192 35.489929, -77.981906 35.490223, -77.981905 35.490244, -77.981886 35.490899, -77.981871 35.491251, -77.981851 35.491736, -77.98184 35.492016, -77.981831 35.492214, -77.981828 35.492309, -77.981817 35.492617, -77.98192 35.492618, -77.98197 35.492619, -77.982396 35.492626, -77.983346 35.492638, -77.983355 35.492269, -77.983356 35.492223, -77.983356 35.492187, -77.983356 35.492166, -77.983356 35.492128, -77.983356 35.49175, -77.983356 35.491573, -77.983356 35.491457, -77.983356 35.491272, -77.98336 35.491109, -77.983363 35.490994, -77.983462 35.491048, -77.983635 35.491141, -77.98372 35.491157, -77.983778 35.49116, -77.983789 35.491161, -77.983892 35.491153, -77.983912 35.488017, -77.983917 35.487166, -77.983974 35.478609, -77.983995 35.475474, -77.983886 35.47399, -77.983562 35.469539, -77.983455 35.468056)), ((-77.989218 35.501639, -77.989298 35.501705, -77.989538 35.501903, -77.989618 35.50197, -77.989806 35.502125, -77.990066 35.50234, -77.99037 35.502592, -77.990558 35.502749, -77.99086 35.503003, -77.991142 35.503241, -77.99177 35.503765, -77.991893 35.503868, -77.992073 35.504021, -77.9922 35.504129, -77.992428 35.504323, -77.992576 35.504459, -77.992699 35.504573, -77.9928 35.50466, -77.993105 35.504923, -77.993207 35.505011, -77.9933 35.504937, -77.99332 35.504949, -77.993702 35.504848, -77.994007 35.50486, -77.994134 35.504865, -77.994246 35.504869, -77.994377 35.504874, -77.994611 35.504883, -77.994715 35.504507, -77.994982 35.50378, -77.99505 35.503607, -77.995135 35.503392, -77.995257 35.503083, -77.995345 35.50286, -77.995568 35.502297, -77.995637 35.50212, -77.995727 35.501893, -77.995752 35.50183, -77.995823 35.501651, -77.995931 35.501378, -77.996155 35.500813, -77.996179 35.500751, -77.996189 35.500723, -77.99627 35.500513, -77.996355 35.500244, -77.995037 35.500458, -77.994515 35.500542, -77.994128 35.500604, -77.993842 35.50065, -77.993766 35.500663, -77.993403 35.500721, -77.993 35.500782, -77.992929 35.500793, -77.992575 35.500847, -77.992235 35.500899, -77.992132 35.50091, -77.992018 35.500931, -77.99154 35.501122, -77.991045 35.501137, -77.990621 35.501152, -77.990611 35.501013, -77.990332 35.501035, -77.990332 35.501377, -77.990333 35.501633, -77.98956 35.501637, -77.989218 35.501639)), ((-78.04497 35.44555, -78.045845 35.445459, -78.0463 35.445546, -78.047334 35.44538, -78.047944 35.444979, -78.048114 35.44462, -78.048157 35.444067, -78.048178 35.443809, -78.048291 35.44354, -78.048793 35.443048, -78.049622 35.442784, -78.050448 35.44279, -78.050723 35.442883, -78.052637 35.444025, -78.054002 35.445072, -78.054272 35.44557, -78.054375 35.446157, -78.05431 35.447012, -78.054137 35.447687, -78.053797 35.448496, -78.05355 35.449903, -78.053443 35.450521, -78.052937 35.451374, -78.052915 35.453267, -78.053021 35.453583, -78.053195 35.453728, -78.053249 35.45367, -78.053303 35.453611, -78.054691 35.452102, -78.059179 35.447224, -78.059834 35.446513, -78.060675 35.445599, -78.060339 35.445403, -78.059332 35.444816, -78.059261 35.444775, -78.059048 35.444655, -78.058993 35.444628, -78.058722 35.444493, -78.058323 35.444324, -78.058052 35.444223, -78.05638 35.4436, -78.055174 35.443148, -78.055104 35.443122, -78.054219 35.442778, -78.054359 35.44255, -78.054531 35.442275, -78.054702 35.441948, -78.054745 35.441848, -78.054852 35.441603, -78.054104 35.441377, -78.053506 35.441197, -78.053089 35.441038, -78.051917 35.440548, -78.051819 35.440507, -78.051197 35.440248, -78.050389 35.439489, -78.049935 35.439062, -78.049757 35.439236, -78.049599 35.439393, -78.04855 35.440376, -78.047988 35.440909, -78.047585 35.441292, -78.046304 35.44251, -78.046176 35.442633, -78.046052 35.442757, -78.045774 35.443074, -78.045577 35.443352, -78.045568 35.443365, -78.045383 35.443686, -78.04529 35.443883, -78.045191 35.444138, -78.045149 35.444276, -78.045128 35.44435, -78.04507 35.444607, -78.045044 35.444795, -78.045024 35.44495, -78.044986 35.445361, -78.044979 35.445444, -78.04497 35.44555)), ((-77.867956 35.41317, -77.868279 35.412939, -77.869252 35.412247, -77.869576 35.412017, -77.870962 35.411031, -77.871302 35.410774, -77.871524 35.410576, -77.871641 35.410453, -77.871801 35.410262, -77.871968 35.410005, -77.872137 35.409709, -77.87305 35.408012, -77.873359 35.407456, -77.873532 35.407175, -77.87397 35.406519, -77.873902 35.406491, -77.873829 35.406403, -77.873715 35.406217, -77.873533 35.405939, -77.87298 35.405163, -77.872732 35.404829, -77.872432 35.404478, -77.871671 35.403668, -77.870554 35.402513, -77.870147 35.402072, -77.8699 35.401777, -77.869782 35.401607, -77.869483 35.401892, -77.869079 35.402281, -77.867129 35.404153, -77.867124 35.404372, -77.867107 35.405347, -77.86703 35.40688, -77.866801 35.41148, -77.866725 35.413014, -77.866971 35.413045, -77.867709 35.413138, -77.867956 35.41317)), ((-77.904297 35.399056, -77.904238 35.399019, -77.903422 35.398675, -77.90314 35.398569, -77.902974 35.398517, -77.902808 35.398474, -77.90266 35.398443, -77.90251 35.39842, -77.902282 35.398402, -77.902081 35.398404, -77.901837 35.398425, -77.901037 35.398541, -77.90091 35.398538, -77.900881 35.398736, -77.90082 35.39916, -77.900793 35.399333, -77.900763 35.399532, -77.900735 35.39971, -77.900687 35.400018, -77.900653 35.400247, -77.900627 35.400426, -77.90061 35.400534, -77.900588 35.400679, -77.900551 35.400859, -77.90053 35.400967, -77.900518 35.401029, -77.900466 35.401226, -77.900461 35.401246, -77.900395 35.401446, -77.900202 35.401922, -77.90018 35.401975, -77.900081 35.402224, -77.900445 35.401933, -77.900475 35.40191, -77.901335 35.401217, -77.901531 35.401055, -77.901641 35.400965, -77.901687 35.400928, -77.901894 35.400763, -77.902644 35.400163, -77.902853 35.400011, -77.903075 35.39986, -77.904297 35.399056)), ((-77.985314 35.494846, -77.984984 35.494807, -77.984544 35.494756, -77.984015 35.494601, -77.983697 35.494508, -77.983678 35.4945, -77.983622 35.494479, -77.983604 35.494473, -77.983676 35.49411, -77.983894 35.493021, -77.983915 35.492652, -77.983915 35.49264, -77.983346 35.492638, -77.983346 35.492655, -77.983337 35.493063, -77.983342 35.493573, -77.983336 35.493795, -77.983324 35.494351, -77.983317 35.494429, -77.98331 35.49451, -77.983236 35.494515, -77.982956 35.494371, -77.982464 35.494118, -77.982073 35.494088, -77.98176 35.494065, -77.981751 35.494295, -77.981727 35.494923, -77.981721 35.494988, -77.981702 35.495219, -77.981697 35.495608, -77.981685 35.496776, -77.981683 35.496995, -77.981678 35.497166, -77.980936 35.497174, -77.978711 35.4972, -77.978168 35.497207, -77.978104 35.501644, -77.981581 35.501682, -77.981579 35.501872, -77.981572 35.502445, -77.981571 35.502636, -77.981572 35.502834, -77.981573 35.502848, -77.981563 35.503414, -77.981562 35.503431, -77.981554 35.50363, -77.981554 35.503827, -77.981554 35.504009, -77.981544 35.504419, -77.98154 35.504617, -77.981537 35.504816, -77.981532 35.505206, -77.981528 35.505413, -77.981525 35.505612, -77.981523 35.50569, -77.981519 35.505926, -77.981518 35.506005, -77.981516 35.506076, -77.981515 35.506129, -77.981512 35.506289, -77.981512 35.506361, -77.981508 35.506582, -77.981505 35.506728, -77.981853 35.506728, -77.98324 35.506735, -77.983239 35.50637, -77.983615 35.506373, -77.983617 35.506302, -77.983625 35.506092, -77.983628 35.506022, -77.983624 35.505941, -77.983615 35.505729, -77.983614 35.505698, -77.983611 35.505618, -77.983605 35.505421, -77.983588 35.50483, -77.983583 35.504634, -77.983571 35.504435, -77.983535 35.503838, -77.983524 35.50364, -77.98352 35.503446, -77.983509 35.502867, -77.983506 35.502674, -77.983497 35.502482, -77.983471 35.501906, -77.983463 35.501715, -77.983485 35.501713, -77.983512 35.501711, -77.98355 35.501701, -77.983571 35.501692, -77.983592 35.501681, -77.983605 35.501671, -77.98363 35.501655, -77.98364 35.501604, -77.983642 35.501563, -77.983645 35.501523, -77.983969 35.501524, -77.984943 35.501528, -77.985268 35.50153, -77.986058 35.501551, -77.988428 35.501617, -77.989218 35.501639, -77.988661 35.501179, -77.988213 35.500809, -77.986989 35.499806, -77.986431 35.499349, -77.986631 35.499349, -77.987232 35.49935, -77.987433 35.499351, -77.988075 35.499347, -77.988302 35.499317, -77.988557 35.499206, -77.989033 35.498735, -77.989427 35.498935, -77.989541 35.498996, -77.989774 35.49912, -77.989822 35.499176, -77.989848 35.49922, -77.989866 35.499279, -77.989877 35.499347, -77.989977 35.499356, -77.990277 35.499383, -77.990497 35.49936, -77.990488 35.499236, -77.990459 35.498821, -77.990441 35.498564, -77.990434 35.498433, -77.990422 35.498228, -77.990405 35.497951, -77.990389 35.497676, -77.990381 35.497549, -77.990375 35.497443, -77.990372 35.497399, -77.990362 35.497127, -77.990341 35.496836, -77.990333 35.496721, -77.990321 35.496551, -77.990314 35.49641, -77.990325 35.496409, -77.990357 35.496406, -77.990486 35.496393, -77.990585 35.496384, -77.990749 35.496368, -77.990873 35.496356, -77.990896 35.496354, -77.991002 35.496338, -77.991 35.496317, -77.990996 35.496257, -77.990995 35.496237, -77.990989 35.496079, -77.990971 35.495606, -77.990966 35.495449, -77.990967 35.495436, -77.990971 35.495397, -77.990973 35.495385, -77.990867 35.495322, -77.990695 35.49522, -77.990557 35.495153, -77.990491 35.495138, -77.990385 35.495114, -77.990293 35.495105, -77.990244 35.495101, -77.990135 35.495032, -77.989979 35.495066, -77.98967 35.495135, -77.989514 35.495179, -77.989361 35.495223, -77.989189 35.495227, -77.988673 35.49524, -77.988502 35.495245, -77.988302 35.495183, -77.988277 35.495176, -77.987716 35.494967, -77.987629 35.494935, -77.987529 35.494877, -77.987082 35.494809, -77.986501 35.494721, -77.986249 35.494647, -77.985755 35.494751, -77.985314 35.494846)), ((-78.027029 35.326308, -78.027376 35.326461, -78.027706 35.326608, -78.028421 35.326915, -78.028771 35.327066, -78.028902 35.327119, -78.029061 35.327172, -78.029231 35.327219, -78.029386 35.327248, -78.029595 35.327269, -78.029665 35.327272, -78.029805 35.327271, -78.029971 35.327258, -78.029997 35.327256, -78.030277 35.327226, -78.030479 35.327209, -78.030771 35.327187, -78.030948 35.327182, -78.031089 35.32719, -78.031171 35.327195, -78.031292 35.327211, -78.031323 35.327069, -78.031438 35.326465, -78.031466 35.326322, -78.031554 35.325754, -78.031623 35.325207, -78.031637 35.325029, -78.031723 35.324203, -78.031803 35.323448, -78.031848 35.32302, -78.031983 35.321736, -78.032028 35.321308, -78.032166 35.321302, -78.032253 35.321291, -78.032327 35.321209, -78.032347 35.321159, -78.032373 35.320973, -78.032373 35.320908, -78.03238 35.32089, -78.03246 35.320813, -78.032514 35.32078, -78.032641 35.320682, -78.033003 35.320429, -78.033171 35.320281, -78.033251 35.320193, -78.033473 35.320017, -78.033549 35.319893, -78.033573 35.319853, -78.033513 35.319836, -78.033385 35.319792, -78.033285 35.31977, -78.033265 35.319666, -78.033265 35.319507, -78.033232 35.319397, -78.033238 35.319364, -78.033299 35.319331, -78.033359 35.319292, -78.033392 35.319194, -78.033406 35.319106, -78.033413 35.319023, -78.033452 35.319001, -78.03352 35.319007, -78.033687 35.319084, -78.033841 35.319122, -78.033916 35.319128, -78.03407 35.319001, -78.034177 35.318919, -78.034258 35.318908, -78.034311 35.31887, -78.034452 35.318859, -78.034498 35.318842, -78.034571 35.318837, -78.0346 35.318793, -78.034639 35.318749, -78.034686 35.318672, -78.034746 35.318623, -78.034814 35.318601, -78.03494 35.318628, -78.035175 35.318661, -78.035215 35.318656, -78.035262 35.318645, -78.035369 35.318557, -78.035591 35.318425, -78.036006 35.318014, -78.036174 35.317822, -78.036408 35.317569, -78.036602 35.317421, -78.0368 35.317301, -78.036869 35.317238, -78.037098 35.317119, -78.037107 35.317109, -78.037028 35.316955, -78.03704 35.316946, -78.037099 35.31692, -78.037027 35.316815, -78.037018 35.316801, -78.036813 35.316503, -78.036742 35.316399, -78.036665 35.316283, -78.036435 35.315938, -78.03641 35.3159, -78.036364 35.31582, -78.036325 35.315754, -78.036193 35.315489, -78.036069 35.315137, -78.036066 35.315127, -78.036024 35.314953, -78.035997 35.314799, -78.035975 35.314437, -78.035988 35.313901, -78.036041 35.312868, -78.036065 35.312421, -78.036096 35.312115, -78.036108 35.312006, -78.036112 35.311877, -78.036125 35.311493, -78.036145 35.311338, -78.036152 35.311221, -78.036171 35.311052, -78.036173 35.310943, -78.036185 35.310859, -78.036193 35.310645, -78.036203 35.31053, -78.036207 35.310351, -78.036211 35.310142, -78.036213 35.310115, -78.03623 35.309965, -78.036259 35.309409, -78.03626 35.309391, -78.036266 35.309174, -78.036294 35.308631, -78.036296 35.308597, -78.036316 35.308359, -78.036368 35.307975, -78.036444 35.307646, -78.036498 35.307444, -78.036617 35.307077, -78.036673 35.306912, -78.03686 35.306366, -78.036889 35.306282, -78.0369 35.306234, -78.036904 35.306222, -78.036947 35.306129, -78.036992 35.305996, -78.037014 35.305947, -78.037049 35.305848, -78.037096 35.305718, -78.037114 35.305665, -78.037172 35.305507, -78.037191 35.305455, -78.037275 35.305219, -78.03753 35.304512, -78.037615 35.304277, -78.037638 35.304212, -78.037694 35.304058, -78.037708 35.30402, -78.037733 35.303957, -78.037757 35.303887, -78.037832 35.303677, -78.037857 35.303608, -78.037878 35.303545, -78.037945 35.303357, -78.037968 35.303295, -78.038051 35.303058, -78.038182 35.302687, -78.038284 35.302421, -78.038308 35.302351, -78.038392 35.302115, -78.038398 35.302094, -78.03842 35.302031, -78.038428 35.302011, -78.038647 35.301388, -78.0387 35.301239, -78.038783 35.300935, -78.038871 35.300542, -78.038925 35.300233, -78.038972 35.299764, -78.038986 35.299517, -78.038986 35.299447, -78.038987 35.299287, -78.038974 35.298955, -78.038959 35.298789, -78.038929 35.29848, -78.038919 35.298405, -78.038896 35.298231, -78.038873 35.298019, -78.038839 35.297816, -78.038769 35.297259, -78.038731 35.296948, -78.038722 35.296877, -78.037998 35.296576, -78.035827 35.295673, -78.035104 35.295372, -78.035072 35.29538, -78.034985 35.295378, -78.034956 35.295381, -78.034875 35.295398, -78.034881 35.295332, -78.035035 35.293982, -78.035497 35.289931, -78.035652 35.288582, -78.035553 35.28856, -78.035257 35.288494, -78.035159 35.288473, -78.034548 35.28835, -78.034184 35.288272, -78.032651 35.287947, -78.032273 35.287876, -78.031267 35.287645, -78.031227 35.287636, -78.030827 35.287539, -78.030307 35.28739, -78.029947 35.287281, -78.02887 35.286954, -78.02881 35.286936, -78.02851 35.28685, -78.027954 35.286678, -78.027919 35.286668, -78.027599 35.286571, -78.027086 35.286417, -78.026284 35.286173, -78.025728 35.286005, -78.025504 35.285934, -78.024827 35.28573, -78.023368 35.285293, -78.022511 35.285032, -78.022263 35.284951, -78.022127 35.2849, -78.0219 35.284815, -78.021659 35.284718, -78.021258 35.284538, -78.020892 35.284371, -78.019794 35.28387, -78.019455 35.283715, -78.019429 35.283703, -78.018998 35.283498, -78.018662 35.283351, -78.018193 35.283137, -78.017579 35.282868, -78.01741 35.282802, -78.017165 35.282728, -78.017027 35.282695, -78.0169 35.282671, -78.016707 35.282642, -78.016399 35.282611, -78.016286 35.282604, -78.016155 35.282591, -78.016141 35.28259, -78.015854 35.282575, -78.014817 35.282509, -78.014279 35.282473, -78.012666 35.282365, -78.012159 35.282332, -78.012129 35.28233, -78.012069 35.282323, -78.011395 35.28228, -78.010494 35.282223, -78.009193 35.282142, -78.00846 35.282097, -78.008468 35.282473, -78.008483 35.283175, -78.008504 35.283543, -78.008505 35.283601, -78.008515 35.283978, -78.008518 35.284057, -78.008521 35.284192, -78.008536 35.284834, -78.008542 35.285049, -78.008543 35.285182, -78.008545 35.285437, -78.008537 35.285584, -78.008531 35.285718, -78.008521 35.285919, -78.008504 35.28604, -78.008445 35.286495, -78.008371 35.286853, -78.008343 35.286998, -78.008282 35.287317, -78.008226 35.28761, -78.008176 35.287872, -78.008181 35.288155, -78.00823 35.288458, -78.008242 35.288494, -78.00834 35.288777, -78.008367 35.288856, -78.008504 35.289117, -78.008648 35.289341, -78.00899 35.289729, -78.009439 35.290196, -78.009618 35.290415, -78.00985 35.290696, -78.010107 35.291062, -78.01034 35.29143, -78.010418 35.29157, -78.010481 35.291699, -78.010487 35.291711, -78.010661 35.292092, -78.010722 35.292223, -78.011149 35.29314, -78.011211 35.293274, -78.011278 35.293423, -78.009991 35.293822, -78.009914 35.293662, -78.009837 35.293543, -78.009755 35.29345, -78.009667 35.293368, -78.009568 35.293288, -78.009552 35.293277, -78.00949 35.293234, -78.009403 35.293182, -78.009354 35.293152, -78.009226 35.293089, -78.008973 35.293007, -78.008833 35.292984, -78.008655 35.292964, -78.008423 35.292958, -78.00837 35.292958, -78.008083 35.29296, -78.00656 35.292991, -78.005836 35.292999, -78.005182 35.293011, -78.005154 35.293012, -78.004869 35.293026, -78.004701 35.29306, -78.004566 35.293112, -78.004391 35.293195, -78.004229 35.29331, -78.004179 35.293369, -78.004081 35.293473, -78.004 35.293602, -78.003938 35.293791, -78.00391 35.293948, -78.003912 35.29398, -78.003927 35.294137, -78.003984 35.294381, -78.004117 35.294907, -78.004185 35.295233, -78.004267 35.295591, -78.004342 35.295873, -78.004381 35.295981, -78.004399 35.29603, -78.004464 35.29615, -78.004536 35.296258, -78.004653 35.296379, -78.004807 35.296507, -78.004937 35.296575, -78.00504 35.296618, -78.005053 35.296622, -78.005158 35.296661, -78.005291 35.296695, -78.005407 35.296716, -78.005537 35.296734, -78.005674 35.296735, -78.005857 35.296728, -78.00613 35.29672, -78.006144 35.296907, -78.006186 35.29747, -78.006189 35.297502, -78.006196 35.297658, -78.006376 35.297648, -78.006919 35.297622, -78.0071 35.297614, -78.007506 35.297594, -78.008187 35.297562, -78.00835 35.297551, -78.008726 35.297527, -78.009133 35.297502, -78.009759 35.297468, -78.010038 35.297454, -78.010507 35.297421, -78.011638 35.297377, -78.011822 35.29737, -78.012264 35.297339, -78.012284 35.297579, -78.012288 35.297623, -78.012359 35.297994, -78.01244 35.298285, -78.01245 35.29832, -78.012519 35.298514, -78.012257 35.298582, -78.012019 35.298644, -78.011758 35.298704, -78.011565 35.298728, -78.011506 35.298731, -78.011461 35.298726, -78.011343 35.298713, -78.011196 35.298679, -78.011056 35.298841, -78.010994 35.298932, -78.010879 35.299047, -78.010815 35.299112, -78.010725 35.299184, -78.010597 35.299255, -78.010507 35.299322, -78.010418 35.299399, -78.010334 35.299484, -78.010244 35.299593, -78.010176 35.299715, -78.010131 35.299823, -78.010096 35.299927, -78.010067 35.300041, -78.010049 35.300136, -78.010053 35.300172, -78.010069 35.300313, -78.010095 35.300467, -78.010149 35.300627, -78.010158 35.300645, -78.010239 35.300804, -78.010267 35.300859, -78.010474 35.301174, -78.010531 35.301254, -78.010636 35.3014, -78.010711 35.301358, -78.010807 35.301315, -78.010896 35.301284, -78.010954 35.301265, -78.011309 35.301164, -78.011694 35.301062, -78.011721 35.301053, -78.011996 35.300974, -78.012301 35.300891, -78.012571 35.300819, -78.013212 35.300631, -78.013516 35.300543, -78.013477 35.300465, -78.013363 35.300234, -78.013325 35.300157, -78.013565 35.299969, -78.014288 35.299407, -78.01453 35.29922, -78.01465 35.299323, -78.014798 35.299499, -78.014951 35.299702, -78.015206 35.299988, -78.015313 35.300196, -78.01544 35.30046, -78.015514 35.300641, -78.015573 35.300773, -78.015621 35.301124, -78.015721 35.301608, -78.015788 35.301888, -78.015831 35.30199, -78.015941 35.302245, -78.016035 35.302377, -78.016163 35.302498, -78.016256 35.302597, -78.016424 35.302674, -78.016464 35.302668, -78.01661 35.302663, -78.016899 35.302668, -78.017185 35.302683, -78.017141 35.301535, -78.017131 35.301415, -78.017455 35.300647, -78.017501 35.300557, -78.017559 35.300481, -78.017631 35.30041, -78.017722 35.300344, -78.017817 35.300294, -78.017922 35.300256, -78.018028 35.300231, -78.018148 35.300219, -78.018561 35.300232, -78.018754 35.300239, -78.018738 35.299984, -78.018661 35.299772, -78.018502 35.299587, -78.018317 35.299381, -78.018221 35.299183, -78.018197 35.29897, -78.018233 35.298757, -78.018239 35.298742, -78.018283 35.298641, -78.019141 35.296492, -78.019351 35.295994, -78.019486 35.295784, -78.01969 35.295556, -78.020046 35.295388, -78.020223 35.295306, -78.022002 35.294605, -78.022008 35.29463, -78.022095 35.294795, -78.022142 35.294976, -78.022249 35.295141, -78.022316 35.295328, -78.022396 35.295619, -78.022456 35.295756, -78.022671 35.296014, -78.022891 35.296152, -78.023293 35.296328, -78.023414 35.296394, -78.023494 35.296481, -78.02354 35.296566, -78.023689 35.296844, -78.023856 35.297053, -78.024083 35.297382, -78.024211 35.29752, -78.024451 35.297756, -78.024606 35.29786, -78.024813 35.298014, -78.02486 35.298069, -78.024873 35.298108, -78.02488 35.298146, -78.024847 35.298278, -78.024814 35.298338, -78.024605 35.29858, -78.024465 35.298766, -78.024297 35.298942, -78.02425 35.299002, -78.024176 35.299063, -78.024143 35.299178, -78.024109 35.299343, -78.02411 35.299612, -78.024129 35.299799, -78.024129 35.299903, -78.024116 35.29998, -78.024082 35.300117, -78.024061 35.300326, -78.024108 35.300535, -78.024209 35.3007, -78.024356 35.300892, -78.024524 35.301139, -78.024691 35.30148, -78.024765 35.301617, -78.024858 35.301831, -78.024912 35.301941, -78.025119 35.302584, -78.025327 35.303067, -78.025447 35.303287, -78.025477 35.303332, -78.025635 35.303567, -78.025742 35.303792, -78.026003 35.304176, -78.026231 35.304495, -78.026492 35.304929, -78.026599 35.305083, -78.026793 35.305341, -78.02694 35.305517, -78.027001 35.305555, -78.027094 35.305583, -78.027155 35.305627, -78.027261 35.305697, -78.027576 35.305907, -78.027601 35.305921, -78.028387 35.306374, -78.028608 35.306517, -78.028942 35.306715, -78.02913 35.306803, -78.029431 35.306978, -78.02963 35.307078, -78.029974 35.307253, -78.030188 35.307374, -78.030325 35.30743, -78.030261 35.307614, -78.030211 35.307761, -78.030142 35.307924, -78.030115 35.308006, -78.030057 35.308162, -78.029991 35.308346, -78.029904 35.308582, -78.029839 35.308762, -78.029731 35.309033, -78.029645 35.30929, -78.029639 35.309309, -78.029561 35.309528, -78.029412 35.309947, -78.029407 35.309962, -78.02935 35.310134, -78.029338 35.310173, -78.029239 35.310502, -78.029116 35.311073, -78.029085 35.311239, -78.029064 35.311357, -78.028989 35.311674, -78.028921 35.312034, -78.028912 35.312068, -78.028907 35.312089, -78.028673 35.313251, -78.028594 35.313646, -78.028561 35.313804, -78.028464 35.314281, -78.028432 35.31444, -78.02821 35.314457, -78.027769 35.314537, -78.027338 35.314618, -78.026656 35.314748, -78.02407 35.315252, -78.022982 35.315465, -78.022659 35.315525, -78.021692 35.315708, -78.021495 35.315746, -78.021371 35.315773, -78.02115 35.31582, -78.021006 35.315852, -78.020731 35.315902, -78.020572 35.315936, -78.020487 35.315951, -78.020403 35.315966, -78.020362 35.315975, -78.020267 35.315998, -78.020107 35.316035, -78.020013 35.316058, -78.019696 35.316145, -78.019633 35.316165, -78.019477 35.316215, -78.018961 35.316384, -78.01888 35.316411, -78.018509 35.316539, -78.018271 35.316614, -78.017983 35.316712, -78.017457 35.316883, -78.017415 35.316895, -78.017354 35.316915, -78.016973 35.317027, -78.016912 35.317081, -78.017046 35.317454, -78.017128 35.317696, -78.017201 35.317907, -78.017599 35.319024, -78.017696 35.319275, -78.017812 35.319527, -78.017837 35.319581, -78.017979 35.319841, -78.018078 35.319997, -78.018143 35.320089, -78.018244 35.320222, -78.018353 35.320346, -78.01869 35.320732, -78.019019 35.321091, -78.019148 35.321231, -78.019245 35.321336, -78.019269 35.321362, -78.019304 35.321401, -78.019179 35.321475, -78.01906 35.321553, -78.018993 35.321602, -78.018892 35.321688, -78.018831 35.321766, -78.018804 35.321821, -78.018798 35.321876, -78.018808 35.321934, -78.018851 35.321987, -78.018944 35.322054, -78.019248 35.32222, -78.019313 35.322231, -78.019375 35.322227, -78.019458 35.322186, -78.019525 35.322119, -78.019678 35.321981, -78.019781 35.321917, -78.02079 35.323032, -78.021341 35.323653, -78.021461 35.323773, -78.021566 35.323857, -78.021697 35.323941, -78.021804 35.323999, -78.022119 35.324142, -78.022261 35.324207, -78.022556 35.324343, -78.022977 35.324531, -78.023239 35.324649, -78.024644 35.325263, -78.025136 35.32548, -78.025856 35.325798, -78.02558 35.326169, -78.025327 35.326511, -78.024907 35.327102, -78.024828 35.327241, -78.024795 35.327313, -78.024692 35.327546, -78.024638 35.327747, -78.024711 35.327753, -78.025207 35.32783, -78.025275 35.327838, -78.025528 35.327868, -78.025676 35.327846, -78.026057 35.327687, -78.026353 35.327534, -78.026446 35.327435, -78.02652 35.327369, -78.02658 35.327303, -78.026701 35.327155, -78.026802 35.326941, -78.026815 35.326914, -78.026875 35.326798, -78.026997 35.326424, -78.027029 35.326308)), ((-77.978266 35.447107, -77.978292 35.447014, -77.978373 35.446736, -77.9784 35.446644, -77.978428 35.446543, -77.978452 35.446462, -77.97861 35.445919, -77.978663 35.445738, -77.978681 35.445674, -77.978831 35.445154, -77.979005 35.444556, -77.979334 35.443401, -77.979502 35.442817, -77.979543 35.442672, -77.979667 35.44224, -77.979709 35.442097, -77.979717 35.442067, -77.979743 35.441977, -77.979752 35.441948, -77.980038 35.441979, -77.980338 35.442013, -77.9809 35.442063, -77.981188 35.44209, -77.981688 35.442135, -77.98319 35.442273, -77.983487 35.442301, -77.983692 35.442313, -77.983863 35.442322, -77.984312 35.44233, -77.984377 35.442331, -77.984549 35.442334, -77.98507 35.442342, -77.986634 35.442366, -77.987156 35.442375, -77.987161 35.443261, -77.987176 35.445921, -77.987178 35.446265, -77.98718 35.446808, -77.987204 35.446909, -77.987277 35.447213, -77.987302 35.447315, -77.98751 35.447348, -77.987599 35.447384, -77.987779 35.447403, -77.988098 35.447439, -77.989234 35.447589, -77.989277 35.447595, -77.989716 35.447671, -77.989575 35.448043, -77.989515 35.448216, -77.989449 35.448455, -77.989418 35.448593, -77.989399 35.448705, -77.989381 35.448823, -77.989353 35.449113, -77.989329 35.449643, -77.989293 35.450703, -77.98928 35.451336, -77.989289 35.451577, -77.989295 35.451629, -77.989326 35.451887, -77.989335 35.451949, -77.989417 35.45249, -77.989503 35.45302, -77.989512 35.453076, -77.989539 35.453245, -77.989549 35.453302, -77.989554 35.453331, -77.989779 35.454795, -77.989794 35.454952, -77.989798 35.45511, -77.989794 35.455217, -77.989777 35.455337, -77.989736 35.45552, -77.989618 35.455872, -77.989475 35.456271, -77.989349 35.456626, -77.988997 35.45762, -77.988511 35.458955, -77.988281 35.459528, -77.988189 35.45975, -77.988136 35.459971, -77.988019 35.460213, -77.987995 35.460283, -77.987931 35.460478, -77.987863 35.46072, -77.987737 35.461053, -77.987651 35.461211, -77.987561 35.461402, -77.987499 35.461594, -77.987424 35.461761, -77.987346 35.461989, -77.987317 35.462073, -77.987166 35.462456, -77.987057 35.462752, -77.987026 35.462838, -77.986892 35.463179, -77.986771 35.463603, -77.98674 35.463803, -77.986733 35.464358, -77.986716 35.464558, -77.986757 35.464712, -77.98677 35.46483, -77.986812 35.464972, -77.986839 35.465138, -77.986964 35.465469, -77.987048 35.465659, -77.987169 35.465883, -77.987353 35.465843, -77.987373 35.465772, -77.98738 35.46569, -77.987441 35.465531, -77.987483 35.46545, -77.987515 35.465387, -77.987615 35.465217, -77.987715 35.465058, -77.98785 35.464789, -77.987903 35.464668, -77.987964 35.464586, -77.988031 35.464467, -77.988098 35.464349, -77.988151 35.464239, -77.988379 35.463921, -77.9886 35.46369, -77.988708 35.463553, -77.988788 35.463465, -77.988856 35.463383, -77.988969 35.463262, -77.989056 35.463157, -77.989225 35.46302, -77.989284 35.462959, -77.989332 35.462937, -77.989713 35.462758, -77.989889 35.462675, -77.989946 35.462723, -77.990003 35.462772, -77.990057 35.462789, -77.990131 35.4628, -77.990251 35.46286, -77.990346 35.46292, -77.990426 35.462948, -77.990499 35.462981, -77.990593 35.46303, -77.990707 35.463118, -77.990896 35.463239, -77.990997 35.463332, -77.991199 35.463475, -77.991299 35.463573, -77.991413 35.463667, -77.991568 35.463782, -77.991749 35.463936, -77.991849 35.464007, -77.991987 35.464097, -77.992118 35.464194, -77.992347 35.464303, -77.992494 35.464347, -77.992649 35.464386, -77.992743 35.464402, -77.992809 35.464429, -77.993072 35.46449, -77.993159 35.464517, -77.993294 35.464544, -77.993455 35.464555, -77.993562 35.464555, -77.993589 35.464566, -77.993629 35.464572, -77.99385 35.464533, -77.994072 35.464539, -77.994213 35.464516, -77.99436 35.464511, -77.994468 35.464483, -77.994548 35.464445, -77.994609 35.464423, -77.994663 35.464417, -77.994824 35.464373, -77.994924 35.464329, -77.995132 35.464274, -77.995266 35.464247, -77.995334 35.464241, -77.995401 35.46423, -77.995603 35.464214, -77.995676 35.464225, -77.995757 35.464225, -77.995837 35.464208, -77.995877 35.464203, -77.995945 35.464186, -77.995965 35.464181, -77.996005 35.464197, -77.996179 35.464213, -77.996374 35.464241, -77.996502 35.464235, -77.996588 35.464241, -77.996729 35.464257, -77.996837 35.464251, -77.996911 35.464263, -77.996985 35.464262, -77.997046 35.464257, -77.997314 35.464268, -77.997421 35.464268, -77.997697 35.464295, -77.997749 35.464295, -77.997824 35.464295, -77.997931 35.464311, -77.998045 35.464317, -77.99816 35.464333, -77.99828 35.464339, -77.998394 35.464328, -77.998575 35.464349, -77.998663 35.464344, -77.99875 35.464349, -77.99879 35.464356, -77.998818 35.46436, -77.998863 35.464352, -77.998885 35.464349, -77.998965 35.464355, -77.999066 35.464371, -77.999084 35.464371, -77.999159 35.464371, -77.999187 35.464371, -77.999287 35.464393, -77.999302 35.464402, -77.999335 35.464421, -77.999395 35.464409, -77.999429 35.464349, -77.999482 35.464223, -77.999515 35.464162, -77.999615 35.464063, -77.999682 35.464008, -77.999709 35.463992, -77.99979 35.463921, -78.000227 35.463421, -78.000361 35.463322, -78.000864 35.462894, -78.001192 35.462633, -78.001396 35.462472, -78.001529 35.462356, -78.001657 35.462219, -78.001811 35.462104, -78.002061 35.461928, -78.002295 35.461753, -78.002554 35.461579, -78.007275 35.464812, -78.009697 35.467309, -78.012891 35.464349, -78.012839 35.464232, -78.012692 35.4639, -78.012571 35.46351, -78.012551 35.463241, -78.012571 35.463109, -78.012612 35.462955, -78.012686 35.462593, -78.012713 35.462346, -78.012733 35.462093, -78.01278 35.462032, -78.012834 35.461989, -78.012914 35.461917, -78.013049 35.461841, -78.013183 35.461736, -78.013223 35.461648, -78.01323 35.461577, -78.01325 35.4615, -78.013371 35.461385, -78.013464 35.461315, -78.013498 35.461371, -78.013571 35.461508, -78.013653 35.4617, -78.013663 35.461721, -78.01371 35.461866, -78.01375 35.462022, -78.013928 35.462958, -78.013969 35.463174, -78.014013 35.46338, -78.014074 35.463569, -78.014157 35.463773, -78.014206 35.463871, -78.014232 35.463917, -78.014272 35.463988, -78.014373 35.464148, -78.014476 35.464284, -78.014615 35.464441, -78.01475 35.464572, -78.015005 35.464792, -78.015444 35.465151, -78.015895 35.46552, -78.016093 35.46569, -78.016703 35.466221, -78.016905 35.466397, -78.017089 35.46656, -78.017643 35.467051, -78.017685 35.467088, -78.017831 35.467212, -78.018197 35.467524, -78.018239 35.467559, -78.018764 35.467995, -78.01948 35.468581, -78.019828 35.468866, -78.019896 35.468921, -78.020149 35.469127, -78.020908 35.469747, -78.021162 35.469954, -78.021256 35.47003, -78.02154 35.470261, -78.021635 35.470338, -78.021743 35.470426, -78.021922 35.470571, -78.022065 35.470695, -78.022171 35.470787, -78.022138 35.470837, -78.021934 35.471201, -78.021236 35.472451, -78.021166 35.472579, -78.021011 35.472872, -78.021777 35.47334, -78.022215 35.473608, -78.022582 35.473832, -78.02344 35.474347, -78.023669 35.4745, -78.023981 35.474728, -78.024049 35.474783, -78.024133 35.474852, -78.02465 35.475306, -78.024725 35.475375, -78.024802 35.475447, -78.025145 35.475718, -78.025191 35.475755, -78.025282 35.475819, -78.025496 35.475938, -78.025663 35.476018, -78.025858 35.476091, -78.026503 35.476306, -78.026638 35.476348, -78.026916 35.476437, -78.027155 35.476517, -78.028513 35.476971, -78.02907 35.477158, -78.0297 35.477394, -78.030223 35.477582, -78.030854 35.477794, -78.031145 35.477868, -78.031522 35.477955, -78.031713 35.47799, -78.03187 35.478013, -78.032475 35.478077, -78.032641 35.478093, -78.032868 35.478115, -78.03319 35.478134, -78.034072 35.478106, -78.034546 35.478091, -78.034639 35.478094, -78.034746 35.478099, -78.034888 35.478111, -78.035538 35.478216, -78.035499 35.478465, -78.035503 35.478609, -78.035656 35.479705, -78.035766 35.480338, -78.035806 35.480641, -78.035804 35.480753, -78.035789 35.480836, -78.034977 35.483022, -78.034644 35.482963, -78.03435 35.482961, -78.034243 35.48298, -78.03414 35.483008, -78.034041 35.483044, -78.033946 35.483089, -78.033858 35.483141, -78.033753 35.483223, -78.033662 35.483315, -78.033641 35.48334, -78.031942 35.485771, -78.031867 35.485863, -78.031813 35.485949, -78.031733 35.486077, -78.031638 35.486245, -78.031553 35.486351, -78.031505 35.486419, -78.031467 35.486503, -78.031438 35.486594, -78.031406 35.486813, -78.031937 35.486835, -78.031983 35.48684, -78.032136 35.486846, -78.032282 35.486862, -78.032322 35.486873, -78.032364 35.486891, -78.032462 35.486921, -78.032577 35.486965, -78.032629 35.486998, -78.032662 35.48701, -78.032784 35.487071, -78.032863 35.487119, -78.033051 35.487213, -78.033084 35.487234, -78.033197 35.487285, -78.033318 35.48733, -78.033401 35.487349, -78.033471 35.487359, -78.033566 35.487365, -78.033908 35.487375, -78.034619 35.487389, -78.035334 35.487378, -78.035338 35.487288, -78.0354 35.48662, -78.035475 35.486224, -78.035609 35.485674, -78.036205 35.483721, -78.036332 35.483354, -78.036509 35.482843, -78.036534 35.482742, -78.0366 35.482625, -78.036704 35.482305, -78.036765 35.482122, -78.036818 35.481974, -78.037062 35.481347, -78.037063 35.481318, -78.037081 35.481277, -78.03712 35.480994, -78.037159 35.480862, -78.037164 35.480637, -78.037142 35.480554, -78.037142 35.480467, -78.037127 35.480203, -78.037088 35.479897, -78.037024 35.479466, -78.036879 35.478694, -78.036878 35.478661, -78.036917 35.478619, -78.037017 35.478516, -78.037043 35.47851, -78.037099 35.478499, -78.037576 35.478339, -78.0382 35.478225, -78.038483 35.478215, -78.038867 35.478517, -78.039069 35.478704, -78.039062 35.479582, -78.039178 35.4797, -78.039633 35.479704, -78.03989 35.479614, -78.040233 35.479664, -78.040547 35.479874, -78.040923 35.480246, -78.041322 35.480388, -78.041353 35.480425, -78.041437 35.480526, -78.041389 35.480805, -78.041008 35.481737, -78.041037 35.481745, -78.041059 35.481752, -78.041129 35.481763, -78.04116 35.481769, -78.041247 35.481489, -78.041328 35.481264, -78.041409 35.481154, -78.041457 35.481077, -78.041543 35.481039, -78.041604 35.481022, -78.041711 35.481028, -78.041805 35.481072, -78.041859 35.481148, -78.04196 35.481253, -78.042081 35.481429, -78.042174 35.481467, -78.042248 35.481566, -78.042329 35.481725, -78.042503 35.481994, -78.042578 35.482099, -78.04261 35.482137, -78.042764 35.482379, -78.042846 35.482489, -78.042872 35.482538, -78.042885 35.482604, -78.042912 35.482966, -78.042939 35.483043, -78.04304 35.483093, -78.043127 35.48312, -78.043188 35.483153, -78.043255 35.483252, -78.043435 35.483373, -78.043604 35.483351, -78.043853 35.483269, -78.043973 35.483203, -78.044074 35.483159, -78.044168 35.483153, -78.044236 35.483153, -78.044282 35.483197, -78.0443 35.483208, -78.044362 35.483274, -78.044496 35.483346, -78.044691 35.483412, -78.044713 35.483414, -78.045181 35.483472, -78.045456 35.483489, -78.045624 35.483505, -78.045846 35.483516, -78.045941 35.483533, -78.046014 35.48356, -78.046061 35.483588, -78.046444 35.483533, -78.046652 35.483538, -78.046928 35.483593, -78.046988 35.483616, -78.047123 35.48367, -78.047471 35.48389, -78.047599 35.48395, -78.047779 35.483994, -78.047988 35.484006, -78.048243 35.484005, -78.048411 35.483989, -78.0488 35.483885, -78.048988 35.483824, -78.049122 35.483769, -78.049257 35.483742, -78.049432 35.483742, -78.049676 35.483792, -78.0497 35.483797, -78.049956 35.483869, -78.050378 35.483995, -78.050589 35.483999, -78.05057 35.483969, -78.050544 35.483927, -78.050507 35.483884, -78.050485 35.483858, -78.050793 35.483703, -78.050972 35.483614, -78.051258 35.483494, -78.051614 35.48337, -78.051718 35.483346, -78.051758 35.483334, -78.051925 35.483287, -78.052089 35.483235, -78.052045 35.483139, -78.051929 35.482937, -78.051865 35.482844, -78.051702 35.482606, -78.05163 35.482511, -78.051514 35.482364, -78.051428 35.482272, -78.051244 35.482088, -78.051182 35.482033, -78.050981 35.481906, -78.050937 35.481884, -78.050799 35.481819, -78.050523 35.481708, -78.050506 35.481741, -78.050465 35.481799, -78.050357 35.481979, -78.050246 35.482166, -78.050073 35.482461, -78.049879 35.482805, -78.049724 35.483083, -78.049294 35.48266, -78.049077 35.482456, -78.048834 35.482217, -78.048723 35.482102, -78.048691 35.48207, -78.04742 35.480799, -78.046609 35.479977, -78.046251 35.479624, -78.04582 35.479165, -78.04565 35.478979, -78.045412 35.478718, -78.045208 35.478503, -78.045087 35.478361, -78.044818 35.478059, -78.04468 35.477908, -78.044614 35.477832, -78.044415 35.477605, -78.044346 35.477534, -78.044304 35.477481, -78.043952 35.477088, -78.043691 35.476816, -78.043622 35.476739, -78.041478 35.474332, -78.041355 35.474194, -78.040924 35.473716, -78.040906 35.473705, -78.040759 35.473535, -78.041083 35.473165, -78.042057 35.472058, -78.042154 35.471949, -78.042247 35.471852, -78.042345 35.47174, -78.042387 35.471693, -78.042421 35.471654, -78.042436 35.471634, -78.042576 35.471452, -78.042623 35.471392, -78.042793 35.471172, -78.04302 35.470836, -78.043055 35.47078, -78.043299 35.470885, -78.044164 35.471176, -78.044963 35.471368, -78.045179 35.471429, -78.045407 35.471484, -78.045689 35.471511, -78.045964 35.471544, -78.046131 35.471583, -78.046461 35.471769, -78.046655 35.471901, -78.046897 35.472017, -78.047139 35.472143, -78.047897 35.472467, -78.048166 35.472599, -78.048609 35.472758, -78.04883 35.472819, -78.049146 35.472874, -78.049414 35.472951, -78.049965 35.473055, -78.050153 35.473083, -78.050851 35.473544, -78.051368 35.473797, -78.051623 35.473929, -78.051891 35.474088, -78.052126 35.47422, -78.052583 35.47444, -78.052824 35.474522, -78.053106 35.474588, -78.053287 35.474626, -78.053435 35.474687, -78.054193 35.475104, -78.054362 35.475198, -78.05459 35.475274, -78.054778 35.475319, -78.055147 35.475428, -78.055315 35.475522, -78.05551 35.475582, -78.055612 35.475624, -78.055548 35.475354, -78.055479 35.475056, -78.055087 35.473353, -78.054957 35.472786, -78.054946 35.472748, -78.054856 35.472544, -78.054807 35.472434, -78.05472 35.472277, -78.054455 35.471871, -78.054312 35.471653, -78.053815 35.470889, -78.053332 35.470153, -78.05184 35.467876, -78.051513 35.467363, -78.051299 35.467037, -78.051137 35.466789, -78.050388 35.465656, -78.050036 35.465124, -78.049406 35.464159, -78.049773 35.464137, -78.050215 35.464111, -78.050875 35.464074, -78.051243 35.464054, -78.051217 35.463798, -78.05121 35.463719, -78.051154 35.463029, -78.051134 35.462773, -78.051102 35.462401, -78.051034 35.46161, -78.051006 35.461382, -78.050988 35.461322, -78.050973 35.461295, -78.050942 35.461237, -78.050905 35.461179, -78.050835 35.461104, -78.050779 35.461057, -78.050728 35.461022, -78.050679 35.460989, -78.050576 35.460949, -78.050533 35.460932, -78.0504 35.460881, -78.04992 35.460736, -78.049715 35.460674, -78.049519 35.461096, -78.049331 35.461502, -78.049243 35.46164, -78.049187 35.461711, -78.049145 35.461748, -78.049065 35.461804, -78.048936 35.461876, -78.048576 35.462038, -78.048152 35.462231, -78.047797 35.461688, -78.04703 35.460515, -78.046745 35.4601, -78.046723 35.460069, -78.04635 35.459541, -78.046236 35.459379, -78.045894 35.458895, -78.045781 35.458734, -78.045607 35.45849, -78.045339 35.458114, -78.045086 35.457758, -78.045002 35.457639, -78.044928 35.457505, -78.044871 35.457382, -78.044771 35.457114, -78.044729 35.456969, -78.044698 35.45682, -78.044666 35.456545, -78.044661 35.455831, -78.044659 35.455487, -78.044654 35.455279, -78.044651 35.454204, -78.044648 35.452503, -78.044644 35.450688, -78.044643 35.449969, -78.044646 35.449402, -78.044667 35.448974, -78.044697 35.448572, -78.044722 35.448288, -78.044907 35.446234, -78.044954 35.445725, -78.044961 35.445652, -78.04497 35.44555, -78.044246 35.445626, -78.043637 35.445937, -78.042636 35.446741, -78.04249 35.44682, -78.041083 35.447585, -78.039645 35.448114, -78.038376 35.44833, -78.038264 35.448419, -78.037604 35.448323, -78.036448 35.448314, -78.034628 35.448525, -78.034516 35.448614, -78.034296 35.448648, -78.033964 35.4487, -78.033743 35.448834, -78.032697 35.44878, -78.031764 35.448457, -78.031578 35.448319, -78.031556 35.448402, -78.031535 35.448485, -78.031368 35.44913, -78.030739 35.451565, -78.03053 35.452377, -78.030452 35.452618, -78.030222 35.453343, -78.030146 35.453585, -78.029928 35.45345, -78.029767 35.453357, -78.029532 35.453208, -78.029177 35.453038, -78.028888 35.45289, -78.027868 35.452324, -78.027618 35.4522, -78.027365 35.452076, -78.027264 35.45201, -78.02719 35.451961, -78.02715 35.4519, -78.027123 35.451835, -78.027085 35.451758, -78.026886 35.451792, -78.026827 35.451803, -78.026512 35.451864, -78.02636 35.451886, -78.026288 35.451891, -78.026207 35.451898, -78.026087 35.451896, -78.026017 35.451892, -78.025891 35.451876, -78.025771 35.451855, -78.025615 35.451823, -78.025433 35.451786, -78.025273 35.451745, -78.025116 35.451696, -78.0249 35.451611, -78.024777 35.451551, -78.024611 35.451451, -78.024512 35.451377, -78.02442 35.451297, -78.024344 35.451216, -78.024297 35.451166, -78.024062 35.450832, -78.023942 35.450661, -78.023915 35.450622, -78.023796 35.450487, -78.023658 35.450363, -78.023505 35.450253, -78.023486 35.450241, -78.023366 35.450171, -78.023303 35.450144, -78.023061 35.450047, -78.022897 35.449989, -78.02278 35.449952, -78.022759 35.449946, -78.022135 35.449767, -78.021795 35.449645, -78.021121 35.449374, -78.02083 35.449258, -78.020585 35.449158, -78.020473 35.449112, -78.020137 35.448974, -78.020026 35.448929, -78.019227 35.448604, -78.017386 35.447854, -78.016831 35.447635, -78.016521 35.447513, -78.01603 35.447319, -78.015666 35.447169, -78.015516 35.447108, -78.014573 35.446733, -78.014209 35.446588, -78.014347 35.446389, -78.014359 35.446374, -78.014786 35.44581, -78.014933 35.445618, -78.015328 35.445098, -78.016076 35.444118, -78.016318 35.443806, -78.016414 35.443659, -78.016449 35.443583, -78.016472 35.443516, -78.016476 35.443505, -78.016497 35.443384, -78.016489 35.443221, -78.016396 35.442879, -78.016244 35.442332, -78.016062 35.441676, -78.016012 35.441552, -78.01599 35.441512, -78.01593 35.441427, -78.015852 35.44134, -78.015697 35.441185, -78.015475 35.440992, -78.015395 35.440923, -78.01528 35.440823, -78.014953 35.440569, -78.014916 35.44054, -78.014806 35.440455, -78.01477 35.440427, -78.014631 35.440322, -78.014494 35.440225, -78.014106 35.439991, -78.014069 35.439969, -78.013473 35.439636, -78.013222 35.439491, -78.012316 35.438934, -78.012126 35.438823, -78.012054 35.438784, -78.012028 35.43877, -78.011861 35.438687, -78.011614 35.43859, -78.011319 35.438489, -78.011018 35.43841, -78.010944 35.438391, -78.010112 35.438195, -78.0101 35.438192, -78.009809 35.438131, -78.009909 35.437849, -78.010143 35.437193, -78.010206 35.437003, -78.010301 35.43672, -78.01026 35.436586, -78.010202 35.436643, -78.010163 35.436612, -78.009787 35.436332, -78.009765 35.436337, -78.009739 35.436342, -78.009726 35.436343, -78.009701 35.436349, -78.009687 35.436351, -78.009661 35.436348, -78.009636 35.436344, -78.009612 35.43633, -78.009508 35.436322, -78.009254 35.43646, -78.009064 35.436622, -78.009075 35.436642, -78.009093 35.436671, -78.009106 35.436702, -78.009107 35.436724, -78.009111 35.436734, -78.009119 35.436742, -78.009143 35.436753, -78.009205 35.436774, -78.009244 35.436783, -78.009266 35.436796, -78.009271 35.436805, -78.009275 35.436816, -78.009278 35.436837, -78.009277 35.436848, -78.00927 35.436869, -78.009264 35.436888, -78.009262 35.436899, -78.009252 35.436907, -78.009241 35.436913, -78.009219 35.43694, -78.009197 35.436967, -78.009173 35.436993, -78.009158 35.437019, -78.00914 35.437041, -78.009021 35.43713, -78.008997 35.437156, -78.008976 35.437185, -78.008968 35.437205, -78.008951 35.437236, -78.008942 35.437256, -78.008931 35.437276, -78.00892 35.437318, -78.008908 35.437351, -78.008892 35.437393, -78.008886 35.437403, -78.008882 35.437414, -78.008874 35.437435, -78.008868 35.437457, -78.008855 35.437476, -78.008835 35.437505, -78.008823 35.437525, -78.008812 35.437545, -78.008787 35.437572, -78.008757 35.437594, -78.008724 35.437628, -78.008701 35.437641, -78.008682 35.437656, -78.00864 35.437712, -78.008622 35.437729, -78.008605 35.437774, -78.008599 35.437804, -78.008572 35.437803, -78.00856 35.437807, -78.008538 35.43782, -78.008491 35.43784, -78.008452 35.437848, -78.008425 35.437848, -78.008372 35.437843, -78.008335 35.437833, -78.008293 35.437832, -78.008266 35.437833, -78.008182 35.437836, -78.008118 35.437837, -78.008038 35.437832, -78.007893 35.437813, -78.007724 35.437774, -78.007675 35.437757, -78.007641 35.437739, -78.007579 35.437719, -78.007526 35.43771, -78.007473 35.437706, -78.007432 35.437709, -78.007379 35.437715, -78.007314 35.437728, -78.007292 35.43774, -78.007273 35.437756, -78.00725 35.437783, -78.007221 35.437806, -78.007167 35.437834, -78.007103 35.437858, -78.007077 35.437862, -78.007037 35.437861, -78.006974 35.437842, -78.006909 35.437831, -78.006763 35.437849, -78.006671 35.437894, -78.006619 35.437944, -78.006501 35.437961, -78.006327 35.437973, -78.006249 35.437989, -78.00619 35.438015, -78.006172 35.438031, -78.006157 35.438062, -78.006147 35.438082, -78.006139 35.438091, -78.006131 35.4381, -78.006107 35.438119, -78.006072 35.438145, -78.006051 35.438159, -78.006042 35.438167, -78.006033 35.438175, -78.006005 35.438213, -78.005984 35.438226, -78.005964 35.438241, -78.005948 35.438259, -78.00594 35.438273, -78.00592 35.438309, -78.005905 35.438339, -78.005889 35.438357, -78.00587 35.438373, -78.005807 35.438413, -78.005746 35.438456, -78.005693 35.438487, -78.005656 35.438505, -78.005634 35.438517, -78.005598 35.438533, -78.005563 35.438566, -78.005524 35.43859, -78.005496 35.438614, -78.005473 35.438643, -78.005448 35.438692, -78.005423 35.438718, -78.005415 35.438726, -78.005393 35.43874, -78.005368 35.438748, -78.005342 35.438754, -78.005328 35.438756, -78.005281 35.438773, -78.005268 35.438778, -78.005234 35.438796, -78.005205 35.438819, -78.005169 35.438851, -78.005144 35.438877, -78.005098 35.4389, -78.005051 35.438922, -78.004988 35.438946, -78.004942 35.438965, -78.00488 35.438984, -78.004814 35.438997, -78.004724 35.439019, -78.00458 35.439044, -78.004567 35.439042, -78.004556 35.439035, -78.00452 35.43902, -78.004508 35.439016, -78.004482 35.439012, -78.004428 35.439017, -78.004402 35.439014, -78.004389 35.439012, -78.004375 35.439011, -78.004363 35.439014, -78.004349 35.439017, -78.004336 35.439017, -78.004322 35.439015, -78.004299 35.439005, -78.004286 35.439001, -78.004276 35.438995, -78.00427 35.438985, -78.004255 35.438966, -78.004238 35.438949, -78.004229 35.438941, -78.004218 35.438934, -78.004192 35.438928, -78.004099 35.438919, -78.004086 35.438916, -78.004029 35.438869, -78.004018 35.438862, -78.004006 35.438858, -78.003994 35.438856, -78.003952 35.438858, -78.003941 35.438864, -78.003933 35.438873, -78.003921 35.438893, -78.003919 35.438915, -78.003908 35.438921, -78.003893 35.438926, -78.003855 35.438924, -78.00383 35.438917, -78.003813 35.4389, -78.003793 35.438885, -78.003761 35.438865, -78.003749 35.43886, -78.00371 35.43885, -78.003657 35.438844, -78.003617 35.438843, -78.003553 35.438825, -78.003416 35.43878, -78.003355 35.438757, -78.003343 35.438753, -78.00332 35.438741, -78.003286 35.438727, -78.003259 35.438719, -78.003233 35.438713, -78.003208 35.438705, -78.003183 35.438698, -78.00317 35.438691, -78.00316 35.438687, -78.003147 35.438685, -78.003135 35.438682, -78.003075 35.438637, -78.003054 35.438624, -78.00303 35.438614, -78.002996 35.438596, -78.002959 35.438581, -78.002924 35.438566, -78.002873 35.438554, -78.002835 35.438541, -78.002805 35.43852, -78.002772 35.438501, -78.00271 35.438479, -78.002569 35.438441, -78.002478 35.438395, -78.001251 35.437753, -77.999681 35.436919, -77.999186 35.436652, -77.999022 35.43674, -77.998892 35.436834, -77.998591 35.437017, -77.998324 35.437144, -77.998127 35.437268, -77.998112 35.437273, -77.997975 35.437316, -77.997833 35.437349, -77.997662 35.437376, -77.997487 35.437385, -77.997124 35.437381, -77.996254 35.437375, -77.996243 35.437374, -77.995942 35.437332, -77.994682 35.436478, -77.994531 35.436938, -77.994401 35.437292, -77.994211 35.437739, -77.994205 35.437756, -77.99415 35.437872, -77.993949 35.438305, -77.993704 35.438791, -77.993692 35.438814, -77.993625 35.438942, -77.993426 35.439329, -77.99336 35.439458, -77.993062 35.440029, -77.992767 35.440596, -77.992315 35.441478, -77.992183 35.441749, -77.992094 35.441933, -77.991915 35.442316, -77.991908 35.442332, -77.991895 35.44236, -77.991549 35.442225, -77.991181 35.442081, -77.990974 35.44251, -77.991379 35.442616, -77.991738 35.442712, -77.991701 35.442792, -77.991623 35.442965, -77.991519 35.44293, -77.991461 35.443096, -77.991219 35.443047, -77.990855 35.442981, -77.988748 35.4426, -77.988046 35.442473, -77.987825 35.442361, -77.986593 35.441879, -77.984559 35.441073, -77.984324 35.440981, -77.983924 35.440821, -77.983925 35.440714, -77.98393 35.439658, -77.983943 35.437668, -77.983941 35.437585, -77.984184 35.437633, -77.984522 35.437701, -77.984647 35.437143, -77.984766 35.436729, -77.984875 35.436387, -77.985057 35.435851, -77.985192 35.435531, -77.985445 35.434991, -77.985506 35.434861, -77.985656 35.43458, -77.985757 35.43439, -77.986087 35.433842, -77.986444 35.433309, -77.986568 35.433138, -77.986932 35.432669, -77.987119 35.432448, -77.987846 35.43161, -77.988334 35.431035, -77.988648 35.43068, -77.988937 35.430353, -77.989153 35.430098, -77.989016 35.430036, -77.988634 35.429865, -77.988257 35.42969, -77.987194 35.429199, -77.987178 35.429192, -77.986994 35.429113, -77.986164 35.428755, -77.986007 35.428687, -77.985503 35.428479, -77.985152 35.428333, -77.984909 35.428259, -77.984114 35.428019, -77.983746 35.427912, -77.98375 35.42661, -77.98375 35.426553, -77.983752 35.425986, -77.983752 35.42577, -77.983761 35.42357, -77.983762 35.423424, -77.983764 35.422476, -77.983769 35.421118, -77.98377 35.420766, -77.983773 35.419855, -77.983775 35.419713, -77.983779 35.419404, -77.98378 35.419362, -77.983784 35.419115, -77.983816 35.418413, -77.983818 35.418389, -77.983832 35.418125, -77.983876 35.417576, -77.983966 35.416736, -77.984077 35.415938, -77.984138 35.415587, -77.984248 35.414961, -77.984311 35.414654, -77.984348 35.414471, -77.984447 35.414023, -77.984472 35.413921, -77.984509 35.413773, -77.984516 35.413739, -77.98457 35.413748, -77.984605 35.413753, -77.984681 35.413765, -77.984876 35.413797, -77.984966 35.413812, -77.985299 35.413865, -77.985747 35.413937, -77.986273 35.414025, -77.9863 35.414029, -77.986634 35.414087, -77.987002 35.414149, -77.988107 35.414337, -77.988172 35.414349, -77.988476 35.414397, -77.988791 35.414446, -77.989597 35.414552, -77.989976 35.4146, -77.991248 35.414761, -77.993193 35.415008, -77.993702 35.415069, -77.994191 35.41511, -77.994489 35.41512, -77.994656 35.415126, -77.995084 35.415127, -77.996004 35.415119, -77.995994 35.415215, -77.995964 35.415506, -77.995955 35.415603, -77.995927 35.415671, -77.995868 35.41584, -77.995674 35.416392, -77.995581 35.416654, -77.994912 35.41856, -77.994772 35.418945, -77.994711 35.419055, -77.99463 35.419155, -77.994531 35.419244, -77.99439 35.419332, -77.994306 35.419368, -77.994173 35.41941, -77.994033 35.419433, -77.993939 35.419438, -77.993843 35.419434, -77.993728 35.419416, -77.993608 35.419391, -77.992958 35.419194, -77.992606 35.419083, -77.992449 35.419034, -77.992185 35.419606, -77.991919 35.419526, -77.991615 35.419867, -77.99144 35.420046, -77.991371 35.420117, -77.991409 35.420424, -77.991411 35.420468, -77.991408 35.420543, -77.991484 35.420644, -77.991573 35.420741, -77.99163 35.420855, -77.991645 35.420981, -77.991649 35.421103, -77.99169 35.421173, -77.991719 35.421269, -77.991627 35.421328, -77.99168 35.421348, -77.991743 35.421372, -77.991834 35.421454, -77.991865 35.421487, -77.991876 35.421499, -77.991886 35.421533, -77.991904 35.42155, -77.991935 35.421537, -77.991948 35.421512, -77.991988 35.421496, -77.992049 35.421523, -77.992116 35.421521, -77.992188 35.421528, -77.992229 35.421478, -77.992272 35.42148, -77.992308 35.421458, -77.992321 35.421378, -77.992394 35.421377, -77.99243 35.421376, -77.992473 35.421375, -77.992617 35.421363, -77.992783 35.421386, -77.992838 35.421342, -77.992988 35.421376, -77.993021 35.421334, -77.993037 35.421314, -77.993074 35.421299, -77.993085 35.421302, -77.9931 35.421306, -77.993111 35.421328, -77.993095 35.421359, -77.993156 35.421407, -77.993207 35.421414, -77.993207 35.421387, -77.993189 35.421345, -77.993218 35.421311, -77.993237 35.421308, -77.993187 35.421249, -77.993147 35.421189, -77.993039 35.421075, -77.993052 35.421062, -77.993133 35.420982, -77.993269 35.420847, -77.993556 35.420749, -77.993707 35.420652, -77.993517 35.420435, -77.994052 35.420391, -77.995379 35.419985, -77.99566 35.419858, -77.995702 35.419539, -77.995742 35.419215, -77.996253 35.417162, -77.996381 35.41665, -77.996402 35.416567, -77.996472 35.416295, -77.996583 35.415839, -77.996784 35.415931, -77.996871 35.415997, -77.996915 35.41603, -77.997047 35.416145, -77.99717 35.416192, -77.997461 35.416302, -77.998069 35.41632, -77.998094 35.41632, -77.998193 35.416324, -77.998224 35.416354, -77.998243 35.416341, -77.998353 35.41622, -77.998383 35.416157, -77.998498 35.415919, -77.998688 35.415529, -77.99872 35.415465, -77.998754 35.415308, -77.99875 35.415245, -77.998738 35.415058, -77.998735 35.414996, -77.998685 35.414561, -77.998537 35.413257, -77.998488 35.412823, -77.998468 35.412616, -77.998408 35.411997, -77.998403 35.41194, -77.998401 35.41192, -77.998397 35.411791, -77.998852 35.411759, -78.000217 35.411664, -78.000673 35.411633, -78.000713 35.411632, -78.000822 35.411156, -78.000977 35.410103, -78.000991 35.410017, -78.001264 35.408386, -78.001276 35.408299, -78.001292 35.408301, -78.001377 35.408304, -78.001443 35.408299, -78.001805 35.408438, -78.002686 35.408841, -78.003009 35.408991, -78.00286 35.409302, -78.002824 35.409395, -78.002353 35.410736, -78.002197 35.411307, -78.00212 35.411613, -78.002133 35.411612, -78.002364 35.41161, -78.002462 35.411206, -78.002498 35.411073, -78.002558 35.410862, -78.003035 35.409504, -78.003057 35.409445, -78.003183 35.409158, -78.003217 35.409084, -78.003257 35.408998, -78.003284 35.408937, -78.003369 35.408757, -78.003397 35.408697, -78.003484 35.408509, -78.003515 35.408444, -78.003759 35.407953, -78.003788 35.407895, -78.003612 35.407735, -78.003401 35.407548, -78.003187 35.407355, -78.002553 35.406784, -78.002165 35.406437, -78.001586 35.405929, -78.001085 35.40549, -78.000474 35.404952, -77.999851 35.404403, -77.99971 35.404116, -77.999617 35.403984, -77.999615 35.404058, -77.999607 35.404162, -77.999544 35.404592, -77.999479 35.404981, -77.999429 35.405289, -77.999175 35.405269, -77.998965 35.405261, -77.998842 35.405264, -77.998725 35.405284, -77.998654 35.405307, -77.998601 35.405332, -77.998578 35.405344, -77.998498 35.405392, -77.998407 35.405461, -77.998218 35.40562, -77.998009 35.405823, -77.997887 35.405955, -77.997828 35.406035, -77.997757 35.406163, -77.997711 35.406288, -77.997682 35.406409, -77.997508 35.407458, -77.997371 35.408287, -77.997097 35.408258, -77.996316 35.408166, -77.996234 35.408157, -77.996052 35.408143, -77.996023 35.408264, -77.995961 35.408625, -77.995713 35.410079, -77.99566 35.410392, -77.995634 35.410476, -77.995599 35.410555, -77.995466 35.410485, -77.995169 35.410322, -77.993885 35.409619, -77.993871 35.409612, -77.99346 35.40938, -77.993511 35.40924, -77.994191 35.407543, -77.994511 35.406744, -77.994936 35.405699, -77.994954 35.405653, -77.995743 35.40368, -77.99586 35.40339, -77.996037 35.402973, -77.996168 35.402643, -77.99662 35.401551, -77.997248 35.400031, -77.998228 35.400319, -77.998677 35.400462, -77.998716 35.40048, -77.998743 35.400492, -77.998784 35.400517, -77.998804 35.40053, -77.998883 35.400595, -77.998926 35.400646, -77.998949 35.400682, -77.998961 35.4007, -77.998982 35.400754, -77.999003 35.400809, -77.999029 35.400898, -77.999128 35.400878, -78.000275 35.401097, -78.000415 35.400622, -78.000562 35.399861, -78.000711 35.399796, -78.001052 35.399644, -78.00136 35.39987, -78.001659 35.399755, -78.002685 35.399364, -78.002697 35.39936, -78.003025 35.399227, -78.002802 35.399665, -78.002725 35.399819, -78.002694 35.399906, -78.002698 35.399963, -78.002726 35.400017, -78.002755 35.400047, -78.003373 35.400504, -78.003601 35.400671, -78.003998 35.400962, -78.004038 35.400926, -78.004055 35.4009, -78.004083 35.400862, -78.004165 35.400672, -78.004199 35.400595, -78.004789 35.400766, -78.004832 35.400772, -78.004874 35.400779, -78.004896 35.400776, -78.004937 35.400763, -78.004973 35.400743, -78.005013 35.4007, -78.005293 35.400314, -78.005761 35.399689, -78.006026 35.399318, -78.006057 35.399275, -78.00626 35.399005, -78.006368 35.398875, -78.006479 35.398773, -78.006498 35.398776, -78.006555 35.398788, -78.006575 35.398792, -78.006565 35.398771, -78.006538 35.398708, -78.00653 35.398688, -78.006614 35.398723, -78.006869 35.398829, -78.006954 35.398865, -78.007183 35.398933, -78.007861 35.399136, -78.008101 35.399208, -78.008001 35.399405, -78.007701 35.399999, -78.007602 35.400197, -78.00754 35.400317, -78.007455 35.400484, -78.007728 35.40082, -78.009171 35.402599, -78.009283 35.40262, -78.009864 35.402731, -78.009978 35.403061, -78.010224 35.403424, -78.01027 35.403577, -78.010313 35.403625, -78.010322 35.403633, -78.010358 35.403751, -78.010441 35.403752, -78.010715 35.403845, -78.011263 35.403849, -78.011485 35.403851, -78.011701 35.404213, -78.011919 35.404395, -78.012193 35.404488, -78.012786 35.40451, -78.012865 35.404506, -78.012931 35.404515, -78.013789 35.404546, -78.013927 35.404585, -78.014118 35.404639, -78.014473 35.404977, -78.014537 35.404814, -78.015631 35.402502, -78.015675 35.402515, -78.015858 35.40257, -78.016247 35.40269, -78.016954 35.402903, -78.017056 35.402931, -78.017495 35.40307, -78.017804 35.403164, -78.017973 35.403216, -78.018172 35.403276, -78.01839 35.403333, -78.018493 35.403365, -78.018608 35.403403, -78.019022 35.403535, -78.019157 35.403259, -78.019255 35.40306, -78.019419 35.403118, -78.020045 35.403171, -78.020277 35.403114, -78.020303 35.403108, -78.020578 35.403215, -78.020742 35.403279, -78.020832 35.403314, -78.020927 35.403165, -78.021011 35.403032, -78.021036 35.402996, -78.021123 35.402862, -78.021152 35.402873, -78.021165 35.402878, -78.021204 35.402893, -78.021217 35.402899, -78.021183 35.402936, -78.021104 35.403023, -78.021192 35.403057, -78.021718 35.403262, -78.02228 35.403487, -78.022634 35.403644, -78.022877 35.403766, -78.023259 35.403987, -78.023606 35.404196, -78.025846 35.405614, -78.025942 35.405675, -78.026371 35.40594, -78.027029 35.406364, -78.029503 35.407938, -78.029093 35.408088, -78.028709 35.408229, -78.028232 35.408294, -78.027885 35.408383, -78.027222 35.408491, -78.02659 35.408594, -78.026908 35.408696, -78.027862 35.409004, -78.02818 35.409107, -78.02822 35.409089, -78.028342 35.409036, -78.028383 35.409019, -78.028532 35.408953, -78.028781 35.408844, -78.028981 35.408761, -78.029132 35.408699, -78.029353 35.408608, -78.029783 35.408432, -78.029862 35.408392, -78.029979 35.40831, -78.030002 35.408308, -78.030078 35.408304, -78.033036 35.410185, -78.033395 35.41044, -78.033752 35.410713, -78.034238 35.411105, -78.034397 35.410995, -78.034655 35.411196, -78.034724 35.41125, -78.035225 35.411642, -78.035981 35.412232, -78.036082 35.412311, -78.036547 35.412633, -78.037185 35.413039, -78.039187 35.414226, -78.040074 35.414741, -78.040681 35.415101, -78.041037 35.415282, -78.04113 35.415323, -78.041445 35.415462, -78.041463 35.415469, -78.041708 35.415565, -78.042763 35.415936, -78.043745 35.416271, -78.043764 35.416277, -78.043959 35.416343, -78.044508 35.416524, -78.044527 35.416489, -78.044585 35.416387, -78.044599 35.416364, -78.044605 35.416354, -78.044675 35.416243, -78.044722 35.41617, -78.044816 35.416014, -78.044883 35.415909, -78.044955 35.4158, -78.045048 35.415656, -78.045061 35.415637, -78.045207 35.415413, -78.045309 35.41524, -78.045362 35.415138, -78.045382 35.415101, -78.045438 35.414959, -78.045535 35.414715, -78.045614 35.4145, -78.046101 35.413181, -78.046119 35.413116, -78.046251 35.412643, -78.046446 35.412659, -78.046613 35.412657, -78.04678 35.412613, -78.046971 35.412553, -78.047212 35.412494, -78.047714 35.412396, -78.047844 35.412348, -78.047966 35.412284, -78.048054 35.412217, -78.048153 35.412126, -78.048294 35.411934, -78.048361 35.411756, -78.048382 35.411548, -78.04834 35.411328, -78.048247 35.411178, -78.047753 35.410621, -78.04765 35.410521, -78.047387 35.410265, -78.047207 35.41015, -78.046972 35.410061, -78.046824 35.410021, -78.046848 35.409957, -78.046877 35.409847, -78.046882 35.40977, -78.04689 35.40966, -78.046663 35.409033, -78.046575 35.40879, -78.04667 35.408764, -78.047268 35.408547, -78.047303 35.408532, -78.047336 35.408514, -78.047387 35.408498, -78.047888 35.408289, -78.050137 35.407339, -78.050259 35.407496, -78.050646 35.407936, -78.050666 35.407943, -78.050724 35.407995, -78.051239 35.408537, -78.051479 35.408782, -78.051815 35.409126, -78.052349 35.409647, -78.052779 35.410045, -78.052969 35.410212, -78.053042 35.410271, -78.052934 35.410371, -78.052681 35.410569, -78.052588 35.410619, -78.052482 35.410652, -78.052373 35.410666, -78.05223 35.410675, -78.051919 35.410682, -78.051894 35.410683, -78.051402 35.410689, -78.050976 35.410702, -78.05084 35.410731, -78.050765 35.410763, -78.05066 35.410834, -78.050613 35.410887, -78.050564 35.41097, -78.050536 35.411079, -78.05055 35.411212, -78.050831 35.411899, -78.0509 35.412047, -78.050984 35.412176, -78.051084 35.412289, -78.051179 35.412367, -78.051763 35.412765, -78.051826 35.412808, -78.051951 35.412909, -78.052108 35.413118, -78.052277 35.413401, -78.052469 35.413776, -78.05252 35.413866, -78.052677 35.414136, -78.052729 35.414226, -78.052756 35.414272, -78.052802 35.414356, -78.052954 35.414631, -78.053022 35.414749, -78.053097 35.414879, -78.053326 35.414779, -78.054015 35.41448, -78.054245 35.414381, -78.05458 35.413854, -78.055585 35.412273, -78.05592 35.411746, -78.056156 35.411829, -78.057089 35.412128, -78.057133 35.41214, -78.057271 35.412181, -78.057564 35.412251, -78.057744 35.412285, -78.057979 35.412318, -78.058654 35.412391, -78.058897 35.41241, -78.059186 35.412413, -78.05933 35.412408, -78.05947 35.412393, -78.059629 35.41237, -78.059789 35.412341, -78.059992 35.412286, -78.060374 35.412165, -78.060878 35.411993, -78.061611 35.411745, -78.061914 35.411646, -78.06209 35.411593, -78.063682 35.41248, -78.068461 35.415142, -78.069361 35.415644, -78.070028 35.416012, -78.070056 35.416027, -78.070109 35.415889, -78.070203 35.41565, -78.07026 35.415471, -78.070305 35.41533, -78.070342 35.415212, -78.070421 35.414937, -78.070732 35.413765, -78.070837 35.413373, -78.071018 35.413425, -78.071563 35.413583, -78.071746 35.413633, -78.071899 35.413675, -78.072361 35.413804, -78.072515 35.413847, -78.072659 35.413886, -78.073093 35.414006, -78.073238 35.414046, -78.073582 35.41414, -78.073714 35.414177, -78.074079 35.414277, -78.074615 35.414426, -78.074959 35.414523, -78.075187 35.414586, -78.075231 35.414599, -78.076047 35.414827, -78.07632 35.414903, -78.07638 35.4144, -78.076562 35.412894, -78.076623 35.412392, -78.076678 35.412115, -78.076696 35.412026, -78.076759 35.411864, -78.077089 35.411388, -78.07725 35.411157, -78.077636 35.410185, -78.078286 35.408554, -78.078796 35.407271, -78.079183 35.4063, -78.079555 35.405512, -78.079618 35.405381, -78.080674 35.403149, -78.081047 35.402362, -78.081221 35.402301, -78.081572 35.40216, -78.082649 35.401693, -78.083051 35.401548, -78.083199 35.401531, -78.083618 35.401484, -78.084049 35.401467, -78.084224 35.401469, -78.084841 35.401513, -78.085548 35.401571, -78.08589 35.401604, -78.086395 35.401626, -78.086666 35.401647, -78.086736 35.40164, -78.087223 35.401657, -78.087711 35.401664, -78.088149 35.401658, -78.088474 35.401637, -78.088633 35.40162, -78.089727 35.401458, -78.090112 35.401401, -78.09198 35.401131, -78.092403 35.401072, -78.092359 35.400485, -78.092343 35.400167, -78.092314 35.399776, -78.09227 35.399176, -78.092225 35.398913, -78.09213 35.39863, -78.091819 35.397777, -78.091462 35.397219, -78.090811 35.396259, -78.090437 35.395706, -78.090266 35.395422, -78.09022 35.395287, -78.090193 35.395133, -78.090185 35.39509, -78.090176 35.395032, -78.090174 35.394961, -78.090174 35.394918, -78.090187 35.394714, -78.090188 35.394626, -78.090204 35.393751, -78.090207 35.393615, -78.0902 35.39346, -78.090195 35.393343, -78.090111 35.393007, -78.089886 35.392449, -78.089247 35.390864, -78.08921 35.390783, -78.089107 35.390531, -78.088601 35.38953, -78.08832 35.388973, -78.08811 35.388587, -78.089154 35.38821, -78.089478 35.388094, -78.090814 35.387628, -78.091363 35.387432, -78.091702 35.3873, -78.091834 35.387235, -78.09201 35.38714, -78.092248 35.386994, -78.092336 35.386942, -78.092622 35.386736, -78.093127 35.386317, -78.093248 35.386216, -78.093348 35.386134, -78.093494 35.386021, -78.093625 35.385929, -78.093755 35.38584, -78.093594 35.385676, -78.093446 35.385517, -78.093446 35.385402, -78.093493 35.385313, -78.09356 35.385188, -78.093707 35.384792, -78.093747 35.384539, -78.093835 35.384265, -78.093875 35.384073, -78.093921 35.38388, -78.093848 35.383705, -78.093727 35.383534, -78.093695 35.383453, -78.093659 35.383359, -78.093587 35.38309, -78.093606 35.382974, -78.09368 35.382826, -78.093533 35.382804, -78.093331 35.38276, -78.093049 35.382667, -78.092475 35.382315, -78.092358 35.382244, -78.092117 35.382107, -78.091875 35.382003, -78.09152 35.381893, -78.090936 35.381811, -78.0905 35.381734, -78.090158 35.381651, -78.08983 35.381613, -78.089515 35.381558, -78.088945 35.381432, -78.08864 35.381405, -78.088321 35.381377, -78.088052 35.381383, -78.087697 35.381383, -78.08763 35.381378, -78.087416 35.381361, -78.087318 35.381367, -78.087286 35.382613, -78.087262 35.383554, -78.087141 35.384368, -78.087004 35.384835, -78.086883 35.385168, -78.086564 35.385533, -78.08629 35.385838, -78.08609 35.385975, -78.085985 35.386049, -78.085604 35.386291, -78.085019 35.386612, -78.081871 35.387737, -78.072426 35.391113, -78.069279 35.392239, -78.064965 35.393739, -78.064435 35.393858, -78.063975 35.393963, -78.062848 35.394018, -78.061796 35.394067, -78.060716 35.394228, -78.060189 35.394344, -78.059803 35.394537, -78.059308 35.394865, -78.059298 35.394958, -78.059173 35.395068, -78.05909 35.395148, -78.058712 35.395545, -78.05851 35.395745, -78.058302 35.395925, -78.058077 35.39609, -78.057836 35.396241, -78.057593 35.39637, -78.057382 35.396464, -78.056977 35.396615, -78.055939 35.396983, -78.054688 35.397431, -78.05035 35.398979, -78.049747 35.399195, -78.049568 35.399252, -78.049385 35.399301, -78.049184 35.399345, -78.048975 35.399382, -78.048672 35.399421, -78.048645 35.399422, -78.048379 35.399433, -78.048067 35.39943, -78.047875 35.399418, -78.047569 35.399382, -78.047371 35.399348, -78.047086 35.399282, -78.046805 35.399197, -78.046617 35.399128, -78.046444 35.399056, -78.046246 35.398961, -78.045918 35.398773, -78.045751 35.398661, -78.04552 35.398486, -78.04555 35.398525, -78.045789 35.398739, -78.046254 35.399168, -78.048463 35.401208, -78.048716 35.401442, -78.049201 35.401887, -78.048972 35.401898, -78.048285 35.401932, -78.048057 35.401944, -78.047711 35.401964, -78.046349 35.402029, -78.046235 35.402034, -78.045397 35.402067, -78.045155 35.402067, -78.044856 35.402051, -78.044625 35.402015, -78.044431 35.40197, -78.043078 35.401579, -78.042155 35.401291, -78.04135 35.401024, -78.041291 35.401005, -78.040394 35.400703, -78.039726 35.400473, -78.038762 35.400078, -78.038301 35.399881, -78.037711 35.399628, -78.037252 35.399432, -78.037162 35.399393, -78.034516 35.398263, -78.033683 35.3979, -78.032575 35.397406, -78.032301 35.397269, -78.031887 35.397017, -78.031624 35.396796, -78.031474 35.396638, -78.031362 35.396498, -78.030961 35.395891, -78.029731 35.393967, -78.029514 35.393628, -78.029154 35.393036, -78.029032 35.392844, -78.029197 35.392767, -78.029217 35.392758, -78.029702 35.392559, -78.029871 35.39249, -78.02999 35.392433, -78.030035 35.392414, -78.030537 35.392205, -78.030617 35.392173, -78.030662 35.392158, -78.030689 35.392131, -78.03069 35.392121, -78.030692 35.392103, -78.030681 35.392078, -78.030657 35.392018, -78.030627 35.391954, -78.030608 35.391914, -78.0305 35.391683, -78.030462 35.391602, -78.030433 35.391553, -78.030179 35.391316, -78.030106 35.391258, -78.030064 35.391203, -78.030007 35.391111, -78.029929 35.390984, -78.029863 35.390903, -78.030401 35.390518, -78.030411 35.39051, -78.03205 35.389327, -78.032597 35.388933, -78.032856 35.388742, -78.033636 35.38817, -78.033896 35.38798, -78.033922 35.38796, -78.034 35.387901, -78.034027 35.387882, -78.034052 35.387905, -78.034127 35.387974, -78.034153 35.387998, -78.034353 35.388183, -78.034953 35.388738, -78.035153 35.388923, -78.036472 35.390142, -78.036559 35.390223, -78.036761 35.39041, -78.037526 35.391116, -78.037841 35.390983, -78.039432 35.390313, -78.041386 35.389492, -78.043006 35.388185, -78.043015 35.387409, -78.043006 35.387162, -78.04286 35.386975, -78.042565 35.386497, -78.042444 35.386195, -78.042276 35.385816, -78.041962 35.38542, -78.041794 35.385217, -78.041559 35.384975, -78.041499 35.384893, -78.041458 35.384821, -78.041386 35.38464, -78.041325 35.384443, -78.041313 35.384421, -78.041204 35.384223, -78.041157 35.384158, -78.04105 35.384008, -78.040962 35.383826, -78.040583 35.383141, -78.040367 35.383298, -78.039737 35.383758, -78.03936 35.384027, -78.039023 35.384268, -78.038391 35.383971, -78.036496 35.383083, -78.035912 35.382896, -78.035864 35.382943, -78.035787 35.38302, -78.03542 35.383345, -78.035276 35.383474, -78.034996 35.383739, -78.034917 35.383816, -78.034891 35.383846, -78.034735 35.383988, -78.034609 35.384096, -78.034561 35.384132, -78.034466 35.384191, -78.034416 35.384229, -78.034373 35.384273, -78.034196 35.384458, -78.033942 35.384684, -78.0337 35.384901, -78.03351 35.385079, -78.033449 35.385132, -78.033201 35.385346, -78.03316 35.385383, -78.032804 35.385702, -78.032466 35.385996, -78.032319 35.386126, -78.032253 35.386188, -78.032226 35.386218, -78.032199 35.386247, -78.03215 35.386303, -78.032117 35.38633, -78.032087 35.386355, -78.032189 35.386457, -78.032497 35.386766, -78.0326 35.386869, -78.032496 35.386937, -78.032345 35.387047, -78.031599 35.387593, -78.031339 35.387778, -78.031435 35.38787, -78.031589 35.38801, -78.031896 35.388291, -78.031935 35.388323, -78.03197 35.388357, -78.032105 35.388481, -78.03235 35.388697, -78.032361 35.388707, -78.032417 35.388763, -78.032217 35.388838, -78.030208 35.389438, -78.030042 35.389497, -78.029975 35.389531, -78.029888 35.389583, -78.029755 35.389635, -78.02969 35.389656, -78.029465 35.389062, -78.029442 35.389035, -78.029414 35.389022, -78.029383 35.389017, -78.029352 35.38902, -78.028907 35.389139, -78.028315 35.389296, -78.028058 35.389369, -78.028135 35.389572, -78.028245 35.389823, -78.028579 35.39067, -78.028588 35.390709, -78.028583 35.390761, -78.028589 35.390829, -78.028628 35.391012, -78.028678 35.391148, -78.028757 35.391257, -78.028913 35.391432, -78.028991 35.391508, -78.028847 35.391606, -78.028757 35.391667, -78.028617 35.391764, -78.02849 35.391855, -78.028402 35.391919, -78.027967 35.39219, -78.02775 35.392326, -78.027216 35.392674, -78.026868 35.392855, -78.026624 35.392949, -78.026298 35.393046, -78.026226 35.393066, -78.026141 35.393089, -78.026028 35.393119, -78.025414 35.393285, -78.024098 35.393643, -78.023963 35.393678, -78.023716 35.393743, -78.023354 35.393831, -78.023229 35.393853, -78.023065 35.393883, -78.022809 35.393912, -78.022573 35.39392, -78.022482 35.393914, -78.022379 35.393907, -78.022261 35.3939, -78.022073 35.39387, -78.022029 35.393863, -78.021973 35.393849, -78.021728 35.39379, -78.021347 35.393659, -78.020522 35.393348, -78.020165 35.393214, -78.018658 35.392632, -78.017793 35.392299, -78.016219 35.391698, -78.014784 35.391151, -78.014744 35.391136, -78.014627 35.391091, -78.014589 35.391076, -78.014914 35.390417, -78.015014 35.390065, -78.015008 35.390051, -78.015012 35.389921, -78.015018 35.389775, -78.014961 35.389452, -78.0147 35.388656, -78.014409 35.387798, -78.014321 35.387429, -78.014254 35.387022, -78.014251 35.386763, -78.014254 35.38652, -78.014266 35.386304, -78.014298 35.385776, -78.014331 35.385064, -78.014333 35.385018, -78.014341 35.384701, -78.014341 35.384685, -78.016299 35.384276, -78.017258 35.384074, -78.01722 35.383947, -78.016762 35.382261, -78.01675 35.382216, -78.016682 35.38197, -78.016785 35.381931, -78.018665 35.381213, -78.018702 35.381198, -78.019479 35.380904, -78.020024 35.380713, -78.020135 35.380483, -78.02023 35.380317, -78.020149 35.38026, -78.02014 35.380253, -78.02005 35.38019, -78.020018 35.380167, -78.019907 35.380092, -78.019826 35.380037, -78.019654 35.37993, -78.019442 35.379823, -78.019269 35.379752, -78.019116 35.379701, -78.018917 35.37965, -78.018685 35.37961, -78.01848 35.37959, -78.018288 35.379584, -78.018079 35.379593, -78.01786 35.379619, -78.017706 35.379648, -78.017599 35.379674, -78.017553 35.379685, -78.017342 35.37975, -78.017206 35.379804, -78.017024 35.379891, -78.01688 35.379971, -78.016525 35.380196, -78.016226 35.380392, -78.015411 35.380923, -78.0138 35.381973, -78.013793 35.381934, -78.013764 35.381771, -78.013757 35.381723, -78.013729 35.381544, -78.013714 35.381444, -78.013664 35.38118, -78.013395 35.379373, -78.013238 35.378447, -78.013155 35.377953, -78.013071 35.377592, -78.013172 35.377549, -78.013255 35.377491, -78.013281 35.377466, -78.013388 35.377367, -78.013815 35.376952, -78.013993 35.376781, -78.01407 35.376705, -78.014304 35.376479, -78.014382 35.376405, -78.014391 35.376393, -78.014421 35.37636, -78.014431 35.376349, -78.014463 35.376312, -78.014495 35.376278, -78.014528 35.376244, -78.014347 35.376136, -78.014176 35.376045, -78.013952 35.375924, -78.01357 35.375728, -78.013264 35.375576, -78.012962 35.37542, -78.012967 35.374859, -78.012997 35.371254, -78.01301 35.369679, -78.013232 35.369675, -78.013522 35.369666, -78.013834 35.369658, -78.01439 35.369647, -78.01456 35.369471, -78.014583 35.369459, -78.014637 35.369399, -78.014653 35.369375, -78.014821 35.369207, -78.014819 35.369074, -78.015058 35.368359, -78.015234 35.368092, -78.015393 35.367849, -78.015366 35.367625, -78.014818 35.366867, -78.014509 35.366682, -78.01421 35.366504, -78.01358 35.366133, -78.01327 35.365951, -78.013394 35.365819, -78.01377 35.365426, -78.013895 35.365295, -78.014076 35.365426, -78.014254 35.365543, -78.014368 35.365618, -78.015359 35.366246, -78.015641 35.366425, -78.015657 35.366361, -78.015756 35.365971, -78.015787 35.36594, -78.014231 35.364943, -78.014426 35.364736, -78.014559 35.364596, -78.014351 35.364462, -78.013727 35.364063, -78.013519 35.36393, -78.014131 35.363289, -78.013291 35.362546, -78.011727 35.361163, -78.011589 35.361071, -78.011602 35.361021, -78.011715 35.360481, -78.011768 35.360234, -78.011802 35.360113, -78.012173 35.358431, -78.011828 35.358542, -78.011625 35.358609, -78.011689 35.358278, -78.011361 35.358215, -78.011388 35.358089, -78.011443 35.357851, -78.011487 35.357662, -78.011607 35.357253, -78.011724 35.356894, -78.01183 35.356584, -78.011844 35.356547, -78.011999 35.356176, -78.012005 35.356162, -78.012142 35.355878, -78.012211 35.355736, -78.012364 35.355452, -78.012618 35.355008, -78.012636 35.354977, -78.012794 35.354728, -78.012883 35.354588, -78.012997 35.354423, -78.013094 35.354283, -78.013179 35.354171, -78.013282 35.354037, -78.013348 35.353951, -78.013434 35.353841, -78.013594 35.353639, -78.013811 35.353381, -78.013924 35.353283, -78.014054 35.353172, -78.014116 35.353127, -78.014356 35.352976, -78.014634 35.352819, -78.014691 35.352787, -78.015948 35.352223, -78.016242 35.35208, -78.016276 35.352061, -78.016395 35.351999, -78.016789 35.351746, -78.016889 35.351682, -78.01723 35.351403, -78.017475 35.351205, -78.018317 35.350499, -78.018518 35.350326, -78.018943 35.349962, -78.019161 35.349774, -78.019344 35.349618, -78.019518 35.349476, -78.019824 35.349224, -78.020047 35.349042, -78.020213 35.348921, -78.020295 35.34886, -78.020886 35.348425, -78.021043 35.348318, -78.021299 35.348147, -78.021353 35.348109, -78.021517 35.347999, -78.021572 35.347962, -78.02167 35.347897, -78.021688 35.347886, -78.021734 35.347856, -78.022673 35.347266, -78.025984 35.345191, -78.026244 35.345029, -78.027089 35.344501, -78.027115 35.344484, -78.027194 35.344435, -78.027221 35.344419, -78.027293 35.344374, -78.027365 35.344329, -78.027461 35.344269, -78.027489 35.344251, -78.027557 35.344209, -78.027631 35.344268, -78.027752 35.344378, -78.027905 35.344499, -78.027939 35.34452, -78.028348 35.344773, -78.028488 35.34485, -78.028642 35.344993, -78.028743 35.345114, -78.028763 35.345147, -78.028777 35.34518, -78.028817 35.345317, -78.02883 35.345383, -78.028891 35.34556, -78.02895 35.345729, -78.029011 35.345855, -78.029051 35.34596, -78.029088 35.346011, -78.029098 35.346026, -78.029152 35.346092, -78.029233 35.346178, -78.029245 35.34619, -78.029306 35.346273, -78.029446 35.346438, -78.029674 35.346675, -78.029828 35.346836, -78.03021 35.347234, -78.030345 35.347386, -78.031001 35.348124, -78.031449 35.348685, -78.031809 35.349118, -78.032294 35.349699, -78.032428 35.34986, -78.032675 35.350129, -78.033131 35.350569, -78.033627 35.351058, -78.033962 35.351332, -78.034012 35.351362, -78.034337 35.351558, -78.034505 35.351646, -78.034746 35.351756, -78.034954 35.351821, -78.035523 35.35203, -78.03608 35.352212, -78.036475 35.352305, -78.037279 35.35247, -78.037561 35.352553, -78.037829 35.352619, -78.038138 35.35275, -78.038217 35.352822, -78.038272 35.352833, -78.038412 35.352882, -78.039083 35.353058, -78.039304 35.353097, -78.039798 35.353223, -78.040054 35.353289, -78.04083 35.353527, -78.040912 35.353553, -78.042294 35.354026, -78.042669 35.354119, -78.043057 35.354245, -78.043114 35.354245, -78.04328 35.354245, -78.043333 35.354235, -78.043439 35.354202, -78.043548 35.354113, -78.043607 35.35407, -78.043883 35.353781, -78.043922 35.353741, -78.044063 35.353603, -78.044303 35.353389, -78.044667 35.353005, -78.044814 35.352895, -78.044928 35.352741, -78.044962 35.352664, -78.045022 35.352499, -78.045042 35.352406, -78.045035 35.352312, -78.045042 35.352192, -78.045042 35.352055, -78.045036 35.351978, -78.045056 35.351819, -78.045089 35.35167, -78.045157 35.351473, -78.045174 35.351425, -78.045223 35.351292, -78.045304 35.351182, -78.045432 35.351045, -78.045499 35.350967, -78.045606 35.350885, -78.045767 35.350748, -78.045794 35.350736, -78.04578 35.35066, -78.045572 35.35044, -78.045351 35.350198, -78.045042 35.349847, -78.044755 35.349501, -78.044487 35.349119, -78.044493 35.349067, -78.044514 35.348996, -78.044554 35.348886, -78.044735 35.348518, -78.044849 35.348364, -78.044957 35.348166, -78.045063 35.347941, -78.045372 35.347513, -78.045808 35.347063, -78.046056 35.346832, -78.046305 35.346634, -78.046531 35.346481, -78.046941 35.346261, -78.047551 35.345926, -78.047933 35.345811, -78.048262 35.345729, -78.048818 35.345624, -78.048958 35.345586, -78.049227 35.345476, -78.049857 35.345273, -78.050266 35.345092, -78.05034 35.345042, -78.050809 35.344636, -78.051008 35.34444, -78.051097 35.345123, -78.051274 35.345448, -78.05149 35.345684, -78.051806 35.345881, -78.051858 35.345915, -78.052344 35.344435, -78.052376 35.344389, -78.052559 35.344161, -78.052747 35.343874, -78.052753 35.343866, -78.052786 35.343782, -78.052836 35.343554, -78.052857 35.343324, -78.052883 35.343122, -78.052911 35.342979, -78.052937 35.342902, -78.053025 35.342739, -78.053287 35.342433, -78.053521 35.34218, -78.053619 35.342071, -78.054085 35.341559, -78.054255 35.341358, -78.054347 35.341231, -78.054506 35.340947, -78.05454 35.340847, -78.054579 35.340737, -78.054635 35.34048, -78.054655 35.340318, -78.054658 35.340197, -78.054649 35.339044, -78.054647 35.338307, -78.054647 35.337985, -78.054656 35.337805, -78.054701 35.33746, -78.054928 35.337462, -78.055105 35.337455, -78.05527 35.337444, -78.055497 35.33742, -78.055899 35.337359, -78.056308 35.33729, -78.056422 35.337271, -78.056707 35.33722, -78.056868 35.33719, -78.057354 35.337104, -78.057516 35.337075, -78.057989 35.336992, -78.059251 35.336773, -78.059409 35.336744, -78.059883 35.33666, -78.059956 35.336938, -78.060178 35.337775, -78.060252 35.338054, -78.060328 35.338355, -78.060429 35.338752, -78.060516 35.339269, -78.06052 35.339293, -78.060575 35.339575, -78.060648 35.339872, -78.060745 35.340223, -78.060785 35.340377, -78.060949 35.341005, -78.061012 35.341215, -78.061065 35.341298, -78.06115 35.341398, -78.061247 35.341451, -78.061342 35.341484, -78.061457 35.341497, -78.061547 35.341496, -78.062309 35.341329, -78.062447 35.341271, -78.062483 35.341246, -78.062564 35.341193, -78.062771 35.34095, -78.063007 35.340611, -78.063202 35.340331, -78.063218 35.340306, -78.063338 35.340125, -78.063409 35.33997, -78.06343 35.339794, -78.063386 35.339599, -78.063296 35.339264, -78.0632 35.338907, -78.06313 35.338642, -78.062923 35.337847, -78.062919 35.337831, -78.062831 35.33759, -78.062732 35.337318, -78.062713 35.337265, -78.062609 35.337035, -78.062528 35.336786, -78.062466 35.336568, -78.062451 35.336501, -78.062388 35.33622, -78.062511 35.336198, -78.062881 35.336134, -78.063005 35.336113, -78.063263 35.336067, -78.06363 35.336004, -78.064039 35.335935, -78.064187 35.335911, -78.064298 35.335891, -78.065144 35.335739, -78.066546 35.335489, -78.067685 35.335284, -78.068081 35.335213, -78.068533 35.335134, -78.069155 35.335023, -78.070602 35.334768, -78.070797 35.334728, -78.070978 35.334698, -78.071023 35.334687, -78.071148 35.334659, -78.071635 35.334526, -78.072299 35.334307, -78.07252 35.334227, -78.07253 35.334223, -78.072569 35.334298, -78.072616 35.33439, -78.072636 35.334439, -78.072646 35.3345, -78.072704 35.334608, -78.072726 35.33466, -78.072784 35.334765, -78.072812 35.334821, -78.072841 35.334885, -78.072875 35.334946, -78.072916 35.335, -78.073016 35.335083, -78.073071 35.335119, -78.073129 35.335148, -78.073192 35.335175, -78.073256 35.335202, -78.073321 35.335227, -78.07338 35.335249, -78.073492 35.335285, -78.073548 35.335306, -78.073613 35.335327, -78.073669 35.33535, -78.073724 35.335374, -78.073784 35.335396, -78.073852 35.33542, -78.073979 35.33548, -78.074037 35.335519, -78.074088 35.33556, -78.074133 35.335604, -78.074165 35.335646, -78.074197 35.335696, -78.074269 35.335811, -78.074338 35.335926, -78.074368 35.335989, -78.074409 35.336055, -78.074454 35.336124, -78.074535 35.336249, -78.074615 35.336408, -78.074659 35.33649, -78.074757 35.336659, -78.074809 35.336745, -78.07486 35.336835, -78.074868 35.33685, -78.075471 35.336611, -78.076187 35.336466, -78.076794 35.336451, -78.077494 35.336729, -78.078128 35.336826, -78.07857 35.33675, -78.079016 35.336862, -78.079885 35.336852, -78.080924 35.336689, -78.081827 35.336809, -78.082042 35.336671, -78.082448 35.336468, -78.08276 35.336227, -78.082964 35.336112, -78.083451 35.335917, -78.084125 35.335687, -78.084621 35.335527, -78.08562 35.334017, -78.085635 35.333995, -78.085537 35.33395, -78.085477 35.333922, -78.08512 35.333756, -78.082812 35.332705, -78.082752 35.332677, -78.082572 35.332596, -78.082513 35.332569, -78.082175 35.332409, -78.081352 35.33202, -78.081152 35.331953, -78.081076 35.331928, -78.080929 35.331884, -78.080797 35.331837, -78.08085 35.331653, -78.081049 35.331122, -78.081147 35.330845, -78.081166 35.330776, -78.081199 35.330687, -78.081223 35.330582, -78.081236 35.330485, -78.08124 35.330385, -78.081236 35.330322, -78.081194 35.330158, -78.081148 35.330057, -78.081083 35.329953, -78.08095 35.329783, -78.080888 35.329727, -78.080712 35.329552, -78.079751 35.328632, -78.07908 35.327984, -78.078616 35.327537, -78.078236 35.327165, -78.078149 35.327081, -78.077053 35.326022, -78.076367 35.325358, -78.07552 35.324538, -78.075324 35.32435, -78.074598 35.323655, -78.074302 35.323374, -78.073611 35.322717, -78.073354 35.322473, -78.072584 35.321741, -78.072328 35.321498, -78.070601 35.319888, -78.068629 35.318036, -78.068446 35.317893, -78.068199 35.317663, -78.067899 35.317382, -78.067845 35.31734, -78.067263 35.317691, -78.066846 35.31792, -78.066557 35.318037, -78.066297 35.318132, -78.066165 35.318171, -78.06526 35.318466, -78.064685 35.318644, -78.064637 35.318659, -78.064002 35.318873, -78.063136 35.31915, -78.062905 35.319226, -78.062033 35.319515, -78.061454 35.319694, -78.06143 35.319702, -78.061252 35.319769, -78.060704 35.319946, -78.059616 35.320298, -78.059495 35.320254, -78.059249 35.320183, -78.058596 35.32006, -78.057962 35.319959, -78.058043 35.319447, -78.058138 35.318851, -78.059318 35.318979, -78.059501 35.318983, -78.059621 35.318976, -78.059777 35.318953, -78.059947 35.318909, -78.060132 35.318851, -78.060667 35.318664, -78.061028 35.318562, -78.060806 35.318099, -78.060353 35.317207, -78.060258 35.317003, -78.060199 35.316877, -78.060005 35.316496, -78.059931 35.31642, -78.059921 35.31641, -78.05974 35.316304, -78.058853 35.315216, -78.058976 35.315101, -78.059251 35.314997, -78.059539 35.314904, -78.059813 35.314821, -78.059969 35.314761, -78.060056 35.31471, -78.060143 35.314659, -78.062521 35.313706, -78.061758 35.312603, -78.061463 35.312053, -78.061163 35.311579, -78.060966 35.311195, -78.060891 35.311104, -78.060844 35.310969, -78.059725 35.310136, -78.059027 35.3097, -78.058968 35.309663, -78.057641 35.308695, -78.057536 35.308651, -78.057303 35.308449, -78.057093 35.308359, -78.056749 35.308316, -78.05675 35.308331, -78.056747 35.308573, -78.056747 35.308653, -78.056727 35.308992, -78.056667 35.309341, -78.056624 35.309595, -78.05658 35.309823, -78.056563 35.309925, -78.056518 35.310206, -78.056442 35.310526, -78.056418 35.3106, -78.056394 35.310682, -78.056323 35.310856, -78.0563 35.310891, -78.056255 35.310965, -78.056225 35.311035, -78.056137 35.311165, -78.056127 35.311178, -78.056101 35.311211, -78.056046 35.311285, -78.056022 35.311309, -78.055993 35.31134, -78.05591 35.311426, -78.05575 35.311556, -78.05553 35.311707, -78.055387 35.311806, -78.055165 35.311936, -78.054698 35.312181, -78.054265 35.312397, -78.054031 35.312531, -78.053974 35.312559, -78.053748 35.312674, -78.053449 35.312833, -78.053344 35.312888, -78.053257 35.312935, -78.053119 35.313005, -78.053028 35.313053, -78.052923 35.313108, -78.052523 35.313314, -78.052458 35.313348, -78.052031 35.313573, -78.051299 35.313969, -78.051153 35.314044, -78.051075 35.314091, -78.050963 35.314161, -78.050775 35.314306, -78.050659 35.314406, -78.050555 35.314496, -78.050445 35.31461, -78.050414 35.314644, -78.050186 35.314919, -78.049929 35.315328, -78.049772 35.315579, -78.049538 35.315946, -78.048838 35.317048, -78.048605 35.317416, -78.048581 35.317452, -78.048457 35.317648, -78.048437 35.317681, -78.048194 35.318055, -78.048001 35.318334, -78.047846 35.318561, -78.047709 35.318527, -78.047521 35.318449, -78.047251 35.318337, -78.046581 35.318019, -78.04627 35.317872, -78.045915 35.317703, -78.045718 35.317603, -78.045683 35.317586, -78.045545 35.317505, -78.045333 35.31738, -78.045287 35.317354, -78.044868 35.317115, -78.044094 35.31674, -78.044022 35.316706, -78.043712 35.316572, -78.043521 35.316529, -78.043437 35.316565, -78.042841 35.316796, -78.042692 35.316864, -78.042332 35.317032, -78.042258 35.317048, -78.042124 35.317054, -78.041475 35.317158, -78.041099 35.317213, -78.040677 35.317257, -78.040442 35.317268, -78.040335 35.317262, -78.040064 35.317287, -78.039625 35.317328, -78.039196 35.317328, -78.039173 35.317333, -78.039077 35.317351, -78.039029 35.317361, -78.038841 35.317366, -78.038788 35.317366, -78.038767 35.317366, -78.038694 35.317348, -78.03866 35.317339, -78.038633 35.317317, -78.038543 35.317268, -78.038517 35.317253, -78.038479 35.317223, -78.038352 35.317086, -78.038285 35.316982, -78.03813 35.316866, -78.038086 35.316836, -78.038024 35.316795, -78.037903 35.316756, -78.037839 35.316895, -78.037795 35.316992, -78.037695 35.31719, -78.037668 35.317218, -78.037615 35.317229, -78.037582 35.317223, -78.037554 35.317196, -78.037547 35.317178, -78.037488 35.317099, -78.037461 35.317458, -78.037668 35.317774, -78.037743 35.317887, -78.037891 35.318041, -78.038016 35.318143, -78.0381 35.31818, -78.038261 35.318251, -78.037963 35.319077, -78.037742 35.319967, -78.037695 35.320148, -78.037652 35.320309, -78.037526 35.320793, -78.037485 35.320955, -78.037479 35.320975, -78.037464 35.321035, -78.037459 35.321056, -78.037394 35.321306, -78.037202 35.322056, -78.037138 35.322307, -78.036965 35.322269, -78.036226 35.325137, -78.035594 35.327604, -78.035446 35.32818, -78.034631 35.331326, -78.033896 35.334164, -78.033013 35.33759, -78.032912 35.337552, -78.032293 35.337442, -78.032222 35.337711, -78.030051 35.337324, -78.029239 35.339371, -78.029387 35.339718, -78.029503 35.339932, -78.029852 35.340575, -78.029969 35.34079, -78.029989 35.340832, -78.030201 35.340859, -78.030674 35.340989, -78.030698 35.34099, -78.030529 35.341334, -78.030315 35.34132, -78.030368 35.341421, -78.030408 35.341496, -78.030477 35.341624, -78.030743 35.341642, -78.030987 35.341364, -78.031095 35.341239, -78.031208 35.341242, -78.031279 35.341244, -78.031235 35.341297, -78.030839 35.341701, -78.030583 35.341943, -78.030474 35.342046, -78.029915 35.342492, -78.029648 35.342685, -78.029124 35.34303, -78.027426 35.344099, -78.027231 35.344223, -78.027081 35.344316, -78.026942 35.344403, -78.026201 35.344869, -78.021611 35.347745, -78.021132 35.348054, -78.020876 35.348219, -78.020489 35.348469, -78.020267 35.348604, -78.0201 35.3487, -78.019838 35.348854, -78.018261 35.346611, -78.018244 35.346586, -78.01801 35.346856, -78.016298 35.34885, -78.016241 35.348916, -78.016061 35.349124, -78.015703 35.349542, -78.014913 35.350457, -78.014891 35.350485, -78.014662 35.350771, -78.013548 35.352122, -78.013534 35.35214, -78.013518 35.352156, -78.013377 35.352291, -78.013296 35.352368, -78.013203 35.352443, -78.01308 35.352529, -78.012895 35.352641, -78.012684 35.352744, -78.012537 35.352803, -78.012385 35.352854, -78.012228 35.352896, -78.012073 35.352927, -78.011915 35.35295, -78.011677 35.352968, -78.011517 35.352969, -78.01137 35.352964, -78.011136 35.35294, -78.010982 35.352913, -78.010757 35.352857, -78.007983 35.351913, -78.007944 35.351899, -78.007939 35.352117, -78.007862 35.352139, -78.007805 35.352307, -78.007599 35.352909, -78.007307 35.352823, -78.007255 35.352808, -78.006422 35.352613, -78.006127 35.352544, -78.005843 35.352478, -78.004992 35.352281, -78.004811 35.352239, -78.004778 35.352235, -78.004707 35.352243, -78.004628 35.352287, -78.004594 35.352339, -78.004559 35.352438, -78.004304 35.353169, -78.004219 35.353413, -78.004516 35.353483, -78.005407 35.353693, -78.005705 35.353763, -78.005619 35.354005, -78.005361 35.354735, -78.005276 35.354978, -78.005192 35.355219, -78.00494 35.355946, -78.004857 35.356188, -78.005153 35.356257, -78.006041 35.356465, -78.006143 35.356489, -78.006338 35.35653, -78.006295 35.356649, -78.006169 35.357009, -78.006127 35.35713, -78.00608 35.357265, -78.00594 35.357672, -78.005894 35.357808, -78.005578 35.357798, -78.00463 35.357769, -78.004315 35.35776, -78.004305 35.358026, -78.004307 35.358186, -78.004309 35.358266, -78.004399 35.358518, -78.004558 35.358894, -78.00463 35.359081, -78.004678 35.359145, -78.004698 35.359164, -78.004796 35.359223, -78.004896 35.359247, -78.005311 35.359349, -78.005176 35.359689, -78.005107 35.359866, -78.004759 35.360706, -78.004619 35.361045, -78.004507 35.361313, -78.004214 35.362021, -78.004175 35.362118, -78.004064 35.362387, -78.004058 35.3624, -78.00405 35.36242, -78.004041 35.36244, -78.004036 35.362454, -78.003995 35.362553, -78.003919 35.362736, -78.003572 35.363584, -78.003457 35.363867, -78.003408 35.363981, -78.003265 35.364325, -78.003218 35.36444, -78.00313 35.364397, -78.002956 35.364354, -78.002689 35.364307, -78.002623 35.3643, -78.002418 35.364278, -78.001483 35.364236, -78.001411 35.364244, -78.001308 35.364271, -78.001245 35.3643, -78.001132 35.364381, -78.000894 35.364581, -78.000735 35.364716, -78.00066 35.364818, -78.000618 35.36491, -78.000589 35.365085, -78.00041 35.365077, -77.999874 35.365055, -77.999775 35.365052, -77.999697 35.365068, -77.999526 35.3652, -77.999242 35.365421, -77.999209 35.365459, -77.999187 35.365536, -77.999178 35.365691, -77.999167 35.365907, -77.999156 35.366085, -77.999123 35.366623, -77.999112 35.366802, -77.998785 35.366787, -77.998427 35.366772, -77.998074 35.366733, -77.99781 35.366681, -77.997711 35.366662, -77.997494 35.366601, -77.997369 35.366559, -77.99719 35.366491, -77.997049 35.366429, -77.997017 35.366415, -77.996823 35.366319, -77.996575 35.366214, -77.996403 35.366153, -77.996047 35.366055, -77.995795 35.366009, -77.995693 35.365997, -77.995625 35.365989, -77.995218 35.365965, -77.994574 35.365942, -77.994442 35.36594, -77.994466 35.365395, -77.994804 35.365406, -77.99582 35.365439, -77.995974 35.365445, -77.996067 35.365421, -77.996144 35.365375, -77.996183 35.365321, -77.996202 35.365276, -77.996292 35.364861, -77.996325 35.36471, -77.996315 35.364628, -77.996284 35.364571, -77.996217 35.364509, -77.996174 35.364486, -77.996127 35.364471, -77.996077 35.364462, -77.994999 35.364476, -77.99446 35.364484, -77.994388 35.364484, -77.994172 35.364487, -77.994101 35.364489, -77.994098 35.364329, -77.994091 35.36385, -77.994089 35.363691, -77.99408 35.363178, -77.994076 35.363149, -77.994067 35.363063, -77.994043 35.36301, -77.993984 35.362949, -77.993936 35.362919, -77.993852 35.36289, -77.99376 35.362884, -77.993153 35.362964, -77.992613 35.363036, -77.992077 35.363109, -77.99187 35.361167, -77.991853 35.361089, -77.99204 35.361037, -77.992545 35.360899, -77.993067 35.360767, -77.993295 35.360709, -77.99398 35.360537, -77.994065 35.360516, -77.994144 35.360499, -77.994199 35.36046, -77.994032 35.360098, -77.993794 35.359581, -77.993538 35.359012, -77.993517 35.358964, -77.993386 35.358646, -77.993252 35.358676, -77.99285 35.358768, -77.992717 35.358799, -77.992548 35.358412, -77.992043 35.357254, -77.99195 35.35704, -77.991901 35.356961, -77.991821 35.356924, -77.991754 35.356923, -77.991492 35.356982, -77.99053 35.35721, -77.99021 35.357287, -77.989887 35.356579, -77.989575 35.355892, -77.98939 35.355588, -77.989176 35.355318, -77.988602 35.354653, -77.988482 35.354514, -77.988273 35.354293, -77.988067 35.354089, -77.988372 35.353828, -77.989289 35.353045, -77.989318 35.353021, -77.989574 35.35282, -77.989604 35.352795, -77.98935 35.352537, -77.988516 35.353234, -77.988098 35.352972, -77.986823 35.35217, -77.986767 35.352246, -77.984922 35.350999, -77.983401 35.349847, -77.983539 35.349678, -77.985538 35.347205, -77.98575 35.346953, -77.985847 35.346836, -77.985945 35.346714, -77.984182 35.34638, -77.983674 35.346274, -77.983337 35.346214, -77.983075 35.346173, -77.982948 35.346159, -77.982689 35.346133, -77.982567 35.346126, -77.982464 35.346125, -77.98234 35.346128, -77.982191 35.346143, -77.982109 35.346154, -77.982048 35.346169, -77.981952 35.346195, -77.98185 35.346229, -77.98173 35.346276, -77.9815 35.34638, -77.981354 35.346458, -77.981215 35.346544, -77.981108 35.346618, -77.980997 35.346703, -77.980908 35.346771, -77.980788 35.346871, -77.980804 35.346897, -77.980819 35.346944, -77.980825 35.346977, -77.980827 35.346989, -77.980829 35.347006, -77.980832 35.347021, -77.980834 35.347035, -77.980826 35.347065, -77.980826 35.347196, -77.980806 35.347296, -77.980772 35.347362, -77.980631 35.34751, -77.980572 35.347587, -77.980504 35.347664, -77.980404 35.347768, -77.980236 35.347971, -77.980091 35.348167, -77.980082 35.348181, -77.979874 35.348417, -77.97986 35.348432, -77.979834 35.348416, -77.979812 35.348402, -77.979569 35.34831, -77.979392 35.348211, -77.979323 35.348167, -77.979219 35.348101, -77.979119 35.348051, -77.978968 35.347989, -77.978882 35.347962, -77.978729 35.347935, -77.978572 35.347901, -77.978486 35.347911, -77.978365 35.347937, -77.978228 35.347983, -77.978183 35.348006, -77.97812 35.348042, -77.978065 35.348076, -77.977837 35.348262, -77.977022 35.348961, -77.976985 35.348994, -77.976672 35.349286, -77.976428 35.349512, -77.976395 35.349538, -77.976372 35.349564, -77.976353 35.349582, -77.976041 35.349855, -77.975556 35.350287, -77.975277 35.350537, -77.975368 35.350608, -77.97541 35.35064, -77.975652 35.35081, -77.975747 35.350878, -77.975696 35.350917, -77.975346 35.35119, -77.975275 35.351246, -77.975117 35.351379, -77.974203 35.352196, -77.973825 35.352535, -77.973604 35.352672, -77.97347 35.352744, -77.973168 35.352883, -77.97223 35.35332, -77.972021 35.353421, -77.971841 35.353518, -77.971383 35.353797, -77.971194 35.353918, -77.971041 35.354018, -77.970816 35.354184, -77.970603 35.354368, -77.970577 35.354353, -77.9703 35.354143, -77.970129 35.354037, -77.970155 35.353981, -77.970169 35.353923, -77.970175 35.353818, -77.970176 35.353556, -77.97084 35.353421, -77.970867 35.353416, -77.971744 35.353238, -77.971965 35.353182, -77.972128 35.353128, -77.972247 35.353081, -77.972346 35.353035, -77.972496 35.352957, -77.972675 35.352847, -77.972759 35.352788, -77.972958 35.352633, -77.973211 35.352404, -77.973395 35.352238, -77.97357 35.35207, -77.973221 35.352269, -77.973132 35.35231, -77.97302 35.352353, -77.972867 35.352393, -77.972711 35.352413, -77.972602 35.352416, -77.972449 35.352402, -77.972289 35.352372, -77.972182 35.352338, -77.971875 35.352208, -77.971681 35.352072, -77.971633 35.352039, -77.971428 35.351886, -77.971206 35.35172, -77.971016 35.351583, -77.970658 35.351324, -77.970535 35.35123, -77.970315 35.351062, -77.970467 35.35062, -77.970129 35.349228, -77.970087 35.348588, -77.970075 35.348392, -77.970059 35.348057, -77.970021 35.347768, -77.969963 35.347734, -77.969555 35.347441, -77.969133 35.347122, -77.968923 35.346963, -77.968619 35.346741, -77.967982 35.346267, -77.96787 35.346184, -77.967781 35.346118, -77.966894 35.345464, -77.966772 35.345385, -77.966649 35.345313, -77.966489 35.345231, -77.966321 35.345162, -77.966106 35.34509, -77.96596 35.345052, -77.965761 35.345015, -77.965595 35.344994, -77.965405 35.344983, -77.965349 35.34498, -77.965113 35.344985, -77.964902 35.345005, -77.964742 35.345033, -77.964483 35.345093, -77.964132 35.345181, -77.963079 35.345444, -77.962729 35.345533, -77.962811 35.345751, -77.962887 35.345957, -77.963358 35.347232, -77.963516 35.347658, -77.963866 35.34757, -77.964917 35.347309, -77.964942 35.347304, -77.965094 35.347247, -77.965248 35.347166, -77.965313 35.347306, -77.965331 35.347332, -77.965364 35.347371, -77.965377 35.347385, -77.965932 35.347802, -77.966122 35.347945, -77.966247 35.348038, -77.966623 35.348318, -77.966749 35.348412, -77.966626 35.348527, -77.966376 35.34875, -77.96525 35.349755, -77.964875 35.350091, -77.964652 35.350294, -77.964577 35.350364, -77.963978 35.350898, -77.963754 35.3511, -77.963627 35.351007, -77.963431 35.350865, -77.963248 35.350727, -77.963123 35.350633, -77.962747 35.350969, -77.961622 35.351977, -77.961247 35.352314, -77.961157 35.352395, -77.960972 35.352564, -77.96089 35.352638, -77.960801 35.35272, -77.960422 35.353059, -77.959536 35.353856, -77.959285 35.354076, -77.959039 35.354293, -77.958898 35.354406, -77.958669 35.354606, -77.958303 35.354927, -77.957991 35.355215, -77.957769 35.355422, -77.957394 35.355759, -77.957143 35.355985, -77.956808 35.355718, -77.956742 35.355657, -77.95672 35.355625, -77.956712 35.355592, -77.956721 35.35556, -77.956744 35.355529, -77.956813 35.355471, -77.957 35.355325, -77.957214 35.355142, -77.957295 35.355066, -77.956513 35.354482, -77.95634 35.354353, -77.956196 35.354254, -77.955821 35.354587, -77.955463 35.354906, -77.954858 35.355462, -77.954706 35.355595, -77.954449 35.355823, -77.954328 35.355925, -77.954286 35.355889, -77.954153 35.355793, -77.954109 35.355761, -77.954063 35.355802, -77.953928 35.355925, -77.953883 35.355967, -77.953775 35.355895, -77.953747 35.355882, -77.953652 35.355849, -77.95343 35.355799, -77.95307 35.355732, -77.953034 35.355726, -77.952714 35.355681, -77.952279 35.355641, -77.952091 35.355632, -77.951724 35.355624, -77.951225 35.355641, -77.950827 35.355673, -77.950571 35.355703, -77.950519 35.355711, -77.950128 35.355779, -77.949811 35.355845, -77.949683 35.355882, -77.949637 35.355271, -77.949629 35.355156, -77.949619 35.3549, -77.949621 35.354835, -77.949625 35.354786, -77.949631 35.354724, -77.949638 35.35467, -77.949646 35.354629, -77.949683 35.35449, -77.949725 35.354367, -77.94979 35.354226, -77.949855 35.354113, -77.949959 35.353964, -77.950048 35.35386, -77.950143 35.353759, -77.950273 35.353642, -77.950558 35.353388, -77.950733 35.353238, -77.950259 35.352883, -77.950004 35.352692, -77.948838 35.351818, -77.948645 35.351673, -77.948568 35.351608, -77.948495 35.351531, -77.948448 35.351446, -77.948414 35.351415, -77.948283 35.351451, -77.948164 35.351508, -77.948075 35.351568, -77.947933 35.351708, -77.947863 35.351793, -77.947816 35.351852, -77.947078 35.352761, -77.947027 35.352824, -77.946569 35.353391, -77.94614 35.353925, -77.945882 35.354245, -77.945776 35.354369, -77.945356 35.354863, -77.944891 35.355322, -77.94459 35.355584, -77.944573 35.355597, -77.944346 35.355782, -77.944125 35.355955, -77.943907 35.356125, -77.943782 35.356066, -77.943551 35.355957, -77.943453 35.355912, -77.943262 35.356258, -77.943202 35.356279, -77.942755 35.356438, -77.942286 35.356561, -77.942166 35.356549, -77.941816 35.356515, -77.941502 35.356429, -77.941293 35.356301, -77.941141 35.356231, -77.941064 35.356191, -77.940839 35.355874, -77.940769 35.35549, -77.940726 35.355126, -77.940633 35.354944, -77.936916 35.356237, -77.935218 35.356829, -77.934853 35.356742, -77.934825 35.356722, -77.934741 35.356664, -77.934713 35.356645, -77.934783 35.356515, -77.934993 35.356127, -77.935003 35.356109, -77.935051 35.356021, -77.935064 35.355998, -77.935317 35.355527, -77.93608 35.354115, -77.936334 35.353645, -77.936355 35.353604, -77.936422 35.353481, -77.936445 35.353441, -77.936494 35.353349, -77.936642 35.353076, -77.936692 35.352985, -77.936745 35.352886, -77.936776 35.352831, -77.936906 35.352591, -77.93696 35.352493, -77.936977 35.352461, -77.937074 35.352281, -77.937417 35.351648, -77.937532 35.351437, -77.937596 35.351317, -77.937792 35.350957, -77.937858 35.350838, -77.938025 35.350533, -77.938289 35.350053, -77.938519 35.349616, -77.938563 35.349534, -77.938686 35.349312, -77.938696 35.349292, -77.938726 35.349235, -77.938737 35.349216, -77.938266 35.349317, -77.936856 35.349621, -77.936386 35.349713, -77.935949 35.34982, -77.935627 35.349891, -77.935161 35.35, -77.936948 35.351001, -77.936641 35.351385, -77.936599 35.35136, -77.936462 35.351556, -77.936439 35.351587, -77.93664 35.351799, -77.936727 35.351822, -77.937175 35.351944, -77.937019 35.352238, -77.936839 35.352398, -77.936554 35.352923, -77.936517 35.352992, -77.935882 35.352755, -77.935699 35.353086, -77.935605 35.353257, -77.935406 35.353184, -77.935295 35.353229, -77.933268 35.354059, -77.93307 35.353779, -77.93283 35.353441, -77.932801 35.353463, -77.932783 35.353475, -77.932733 35.353514, -77.932716 35.353527, -77.93269 35.353545, -77.932615 35.353599, -77.93259 35.353618, -77.932554 35.353645, -77.93245 35.353726, -77.932415 35.353754, -77.932392 35.353768, -77.932324 35.353811, -77.932302 35.353826, -77.932222 35.353889, -77.931984 35.35408, -77.931905 35.354144, -77.931768 35.354272, -77.931357 35.354655, -77.931221 35.354784, -77.931211 35.35478, -77.93116 35.354775, -77.931113 35.35478, -77.931032 35.354815, -77.931015 35.354827, -77.930653 35.35512, -77.930508 35.355241, -77.930409 35.355325, -77.930344 35.355385, -77.930282 35.355423, -77.930177 35.355409, -77.930055 35.355332, -77.92983 35.35519, -77.929558 35.355017, -77.929477 35.354967, -77.929393 35.354914, -77.928894 35.354603, -77.928782 35.354534, -77.927926 35.355229, -77.927544 35.35468, -77.926017 35.352512, -77.924869 35.350217, -77.924802 35.350172, -77.924381 35.349926, -77.924156 35.349819, -77.922625 35.348935, -77.921471 35.348305, -77.920928 35.348117, -77.920162 35.347771, -77.921031 35.346776, -77.919826 35.346135, -77.92008 35.34582, -77.920357 35.345474, -77.920637 35.344558, -77.920696 35.344494, -77.920846 35.344328, -77.92105 35.344018, -77.921258 35.343717, -77.919229 35.342586, -77.918969 35.343845, -77.918956 35.343898, -77.918945 35.343958, -77.91893 35.344031, -77.918706 35.343965, -77.918725 35.343875, -77.918907 35.343004, -77.919034 35.342405, -77.919066 35.34225, -77.919165 35.341787, -77.919198 35.341633, -77.919221 35.341521, -77.919292 35.341187, -77.919316 35.341076, -77.919358 35.340876, -77.919611 35.339628, -77.919728 35.339056, -77.919748 35.338959, -77.919831 35.33857, -77.919865 35.338403, -77.919917 35.338158, -77.919962 35.337957, -77.920035 35.337643, -77.920047 35.3376, -77.92021 35.337064, -77.920097 35.33708, -77.920058 35.337096, -77.920016 35.337102, -77.919957 35.337134, -77.91989 35.337178, -77.919869 35.337184, -77.919857 35.3372, -77.919809 35.337244, -77.919716 35.337415, -77.919682 35.337486, -77.919668 35.337525, -77.919646 35.337566, -77.919614 35.337623, -77.919574 35.337728, -77.919561 35.337788, -77.919561 35.337854, -77.919568 35.337952, -77.919567 35.338002, -77.919554 35.338041, -77.919528 35.338084, -77.919487 35.338129, -77.919441 35.338151, -77.91942 35.338139, -77.919367 35.338129, -77.919319 35.338112, -77.919138 35.338079, -77.918998 35.338058, -77.918703 35.338035, -77.918657 35.33804, -77.918616 35.338035, -77.918576 35.338041, -77.918361 35.33804, -77.918321 35.338035, -77.918268 35.338036, -77.917986 35.338013, -77.917935 35.338013, -77.918027 35.338072, -77.918129 35.338095, -77.9182 35.338119, -77.918219 35.338121, -77.918428 35.338079, -77.918555 35.338085, -77.918814 35.338119, -77.919046 35.338083, -77.919178 35.338118, -77.919312 35.338177, -77.919399 35.338241, -77.919631 35.338336, -77.919659 35.338341, -77.919656 35.338356, -77.919499 35.338838, -77.919482 35.338917, -77.91909 35.340822, -77.919059 35.340982, -77.919033 35.341105, -77.918973 35.3414, -77.918936 35.341578, -77.918924 35.341576, -77.918856 35.341562, -77.918447 35.341477, -77.91837 35.341461, -77.918133 35.341421, -77.917704 35.341351, -77.917402 35.34125, -77.917259 35.341138, -77.91716 35.34106, -77.916937 35.340873, -77.91678 35.34073, -77.916674 35.340866, -77.916555 35.340986, -77.916143 35.341353, -77.916043 35.341432, -77.915471 35.341882, -77.915697 35.342273, -77.915974 35.342783, -77.916031 35.342888, -77.916376 35.343179, -77.916885 35.343618, -77.916961 35.343683, -77.917649 35.344276, -77.91771 35.344329, -77.917774 35.344385, -77.917706 35.344543, -77.917415 35.345226, -77.917344 35.345394, -77.917316 35.345439, -77.916949 35.345939, -77.916865 35.346054, -77.916685 35.345964, -77.916053 35.345648, -77.915654 35.345448, -77.914157 35.344701, -77.913894 35.34457, -77.913521 35.344394, -77.913424 35.344349, -77.913351 35.344316, -77.913173 35.344237, -77.912837 35.344097, -77.912712 35.344045, -77.912665 35.344027, -77.912571 35.343992, -77.912399 35.343928, -77.912292 35.343888, -77.91221 35.343858, -77.912199 35.343854, -77.912091 35.343815, -77.911767 35.3437, -77.911659 35.343662, -77.911629 35.34387, -77.91107 35.343677, -77.910968 35.343642, -77.909463 35.343122, -77.909176 35.343025, -77.908031 35.34263, -77.908123 35.342445, -77.907148 35.342116, -77.906951 35.342049, -77.906642 35.341946, -77.904224 35.341135, -77.90325 35.340808, -77.902294 35.340487, -77.901716 35.340293, -77.901165 35.340108, -77.899425 35.339525, -77.898712 35.339287, -77.89847 35.339204, -77.898217 35.339117, -77.897852 35.338981, -77.897469 35.33883, -77.897365 35.338789, -77.897222 35.338729, -77.896892 35.338578, -77.896672 35.338468, -77.896435 35.338349, -77.896191 35.338216, -77.895898 35.338043, -77.895691 35.337913, -77.895168 35.337571, -77.895111 35.337528, -77.89475 35.337259, -77.894623 35.337166, -77.893023 35.335943, -77.89251 35.335555, -77.891948 35.335119, -77.891084 35.334465, -77.890681 35.334161, -77.890486 35.334008, -77.890185 35.333791, -77.88988 35.333581, -77.889521 35.333345, -77.889462 35.333306, -77.889153 35.333117, -77.888668 35.332842, -77.888264 35.332639, -77.887815 35.33243, -77.887307 35.332218, -77.886918 35.332066, -77.886472 35.331894, -77.886271 35.331821, -77.885507 35.331526, -77.883214 35.330659, -77.88177 35.330108, -77.87935 35.329189, -77.878951 35.329036, -77.878268 35.328776, -77.877365 35.328431, -77.876326 35.328039, -77.875376 35.32768, -77.87479 35.327457, -77.873435 35.326942, -77.872956 35.326763, -77.872712 35.326667, -77.872513 35.326591, -77.871643 35.326261, -77.871316 35.326133, -77.870539 35.325843, -77.869102 35.325292, -77.868625 35.325099, -77.868274 35.324946, -77.867928 35.324789, -77.86724 35.324455, -77.867012 35.324341, -77.866618 35.324137, -77.866445 35.324047, -77.865804 35.32372, -77.86578 35.323708, -77.864997 35.323301, -77.864838 35.32322, -77.864761 35.323182, -77.864199 35.322887, -77.86365 35.322586, -77.863442 35.322471, -77.863417 35.322458, -77.862825 35.322119, -77.86262 35.322002, -77.862224 35.321776, -77.862066 35.321689, -77.86135 35.321295, -77.860795 35.321011, -77.86037 35.320816, -77.860088 35.320686, -77.859791 35.320555, -77.859747 35.320536, -77.859856 35.320353, -77.859863 35.320343, -77.859876 35.320323, -77.859902 35.320298, -77.85992 35.320281, -77.859951 35.32026, -77.859987 35.320246, -77.860026 35.320236, -77.86009 35.320222, -77.860123 35.320215, -77.86015 35.320202, -77.860191 35.320175, -77.86021 35.320147, -77.860232 35.320119, -77.860252 35.320099, -77.860281 35.320084, -77.860318 35.320078, -77.860384 35.320068, -77.860423 35.320059, -77.860456 35.320042, -77.860483 35.32002, -77.860507 35.319992, -77.860522 35.319962, -77.86054 35.319933, -77.860566 35.319909, -77.86057 35.319865, -77.860563 35.319789, -77.86057 35.319746, -77.860573 35.319735, -77.86061 35.319705, -77.860632 35.31968, -77.860638 35.31967, -77.860636 35.319653, -77.860631 35.319639, -77.8606 35.319579, -77.860591 35.319555, -77.860582 35.319538, -77.860569 35.319519, -77.860557 35.319494, -77.860552 35.319478, -77.860548 35.319434, -77.860548 35.319413, -77.860553 35.319391, -77.86056 35.31937, -77.860571 35.319339, -77.860586 35.319305, -77.860606 35.319274, -77.860619 35.319257, -77.861455 35.319086, -77.863295 35.318721, -77.863132 35.31812, -77.862482 35.318303, -77.862492 35.318168, -77.862534 35.317625, -77.862548 35.317439, -77.86294 35.317308, -77.863423 35.317145, -77.863535 35.317029, -77.86384 35.316715, -77.864156 35.316387, -77.864405 35.31613, -77.864954 35.315779, -77.865015 35.31574, -77.865344 35.315529, -77.864888 35.315035, -77.864931 35.315001, -77.864984 35.31496, -77.865028 35.314906, -77.865169 35.314734, -77.865226 35.314663, -77.865389 35.314465, -77.8654 35.31445, -77.865455 35.314377, -77.865529 35.314278, -77.865755 35.313979, -77.866604 35.312862, -77.866624 35.312819, -77.866633 35.312775, -77.86662 35.312755, -77.866612 35.312738, -77.866705 35.312772, -77.866771 35.312796, -77.866851 35.312824, -77.866941 35.312862, -77.866987 35.312889, -77.867089 35.312959, -77.867199 35.313049, -77.867252 35.313104, -77.867303 35.313165, -77.867352 35.313231, -77.867456 35.313375, -77.867559 35.313532, -77.867687 35.313523, -77.867719 35.313504, -77.867214 35.312437, -77.867416 35.311889, -77.867719 35.311069, -77.868229 35.310357, -77.868341 35.310203, -77.868499 35.310194, -77.868733 35.310208, -77.868819 35.310352, -77.868998 35.310636, -77.869193 35.310932, -77.869203 35.310945, -77.869328 35.311111, -77.869417 35.311247, -77.869787 35.311734, -77.87013 35.312015, -77.870306 35.31217, -77.870718 35.312464, -77.870818 35.312565, -77.871023 35.312805, -77.87123 35.313047, -77.87155 35.313389, -77.871622 35.313442, -77.87186 35.313616, -77.872111 35.313746, -77.872365 35.313877, -77.872831 35.314022, -77.873111 35.314075, -77.873262 35.315002, -77.873849 35.314908, -77.87443 35.314815, -77.874466 35.31482, -77.874896 35.31489, -77.874914 35.314893, -77.875324 35.314958, -77.875594 35.314717, -77.875868 35.314778, -77.876171 35.314846, -77.876167 35.314866, -77.876086 35.315269, -77.876041 35.315497, -77.875981 35.315797, -77.87587 35.315817, -77.875909 35.316035, -77.875964 35.316339, -77.876598 35.316226, -77.876613 35.316224, -77.876692 35.316209, -77.876774 35.316195, -77.876832 35.316184, -77.876926 35.316167, -77.876971 35.316159, -77.877226 35.316116, -77.877466 35.316074, -77.877472 35.31606, -77.877542 35.31592, -77.877534 35.315822, -77.877531 35.315776, -77.877529 35.315759, -77.877538 35.315743, -77.877553 35.315718, -77.877498 35.315703, -77.877509 35.315673, -77.877536 35.315593, -77.877567 35.315535, -77.877596 35.315484, -77.877632 35.31542, -77.877706 35.315171, -77.877787 35.315176, -77.877865 35.31508, -77.877939 35.314988, -77.877902 35.314935, -77.8779 35.314763, -77.877935 35.314719, -77.877955 35.314695, -77.877919 35.314641, -77.878021 35.314519, -77.878066 35.314496, -77.878075 35.314416, -77.878078 35.314382, -77.878029 35.314302, -77.877929 35.314338, -77.877929 35.314184, -77.877734 35.313941, -77.877652 35.313902, -77.877612 35.313869, -77.877579 35.313858, -77.877471 35.313809, -77.877177 35.313709, -77.876977 35.313661, -77.876641 35.313561, -77.87652 35.313534, -77.876153 35.313413, -77.875823 35.31332, -77.875676 35.313302, -77.875589 35.313286, -77.875536 35.313286, -77.875408 35.313264, -77.875301 35.313232, -77.875255 35.313214, -77.87514 35.31316, -77.875013 35.313113, -77.874967 35.313102, -77.87492 35.313076, -77.874872 35.313066, -77.874799 35.313039, -77.874704 35.312996, -77.874585 35.313039, -77.874243 35.313122, -77.874122 35.313139, -77.873974 35.313145, -77.873734 35.313139, -77.873238 35.313062, -77.872346 35.312936, -77.872192 35.312909, -77.872045 35.312876, -77.87195 35.312827, -77.871897 35.312756, -77.871809 35.312536, -77.871742 35.312398, -77.871615 35.312195, -77.871501 35.312102, -77.870402 35.311351, -77.870314 35.311251, -77.870275 35.311169, -77.870201 35.310988, -77.8701 35.310757, -77.87008 35.31057, -77.870026 35.31046, -77.869925 35.310362, -77.869791 35.310257, -77.869677 35.310203, -77.869603 35.310142, -77.869537 35.310038, -77.869483 35.309846, -77.869456 35.309648, -77.869463 35.309506, -77.869469 35.309385, -77.869456 35.309236, -77.869382 35.308945, -77.869254 35.30861, -77.8691 35.308363, -77.868932 35.308133, -77.868771 35.307967, -77.868631 35.307798, -77.868429 35.307688, -77.868275 35.307595, -77.868087 35.307468, -77.86804 35.30745, -77.867946 35.307414, -77.867887 35.307397, -77.867766 35.307397, -77.867712 35.307414, -77.867651 35.307425, -77.867583 35.307468, -77.867558 35.307485, -77.867518 35.307534, -77.867478 35.307683, -77.867445 35.307743, -77.867358 35.307853, -77.867303 35.307909, -77.867223 35.307968, -77.867184 35.308018, -77.867143 35.3081, -77.867102 35.30827, -77.867056 35.308408, -77.866975 35.308584, -77.866927 35.308719, -77.866923 35.308732, -77.866869 35.308924, -77.866849 35.309138, -77.866827 35.309188, -77.866802 35.309243, -77.866708 35.30938, -77.866535 35.309594, -77.86632 35.30993, -77.866307 35.309985, -77.866301 35.310067, -77.866307 35.310128, -77.86632 35.310199, -77.866361 35.310358, -77.866368 35.310413, -77.866368 35.310473, -77.866355 35.310506, -77.866335 35.310533, -77.866268 35.310605, -77.86618 35.310666, -77.86606 35.310731, -77.865973 35.310804, -77.865906 35.310869, -77.865885 35.310908, -77.865858 35.311017, -77.865852 35.311072, -77.865872 35.311204, -77.865873 35.311319, -77.865867 35.311374, -77.865832 35.311479, -77.865725 35.311611, -77.865618 35.31177, -77.865518 35.311902, -77.865477 35.31193, -77.865444 35.311924, -77.86541 35.311913, -77.865265 35.311829, -77.865023 35.31171, -77.865001 35.311699, -77.865114 35.311446, -77.865149 35.311353, -77.865188 35.31121, -77.865209 35.310743, -77.865194 35.310644, -77.865155 35.310502, -77.865115 35.310386, -77.865055 35.310249, -77.865041 35.310226, -77.865034 35.310205, -77.865033 35.310177, -77.86504 35.31015, -77.86506 35.310112, -77.865135 35.310029, -77.865221 35.309892, -77.865249 35.309837, -77.865308 35.309673, -77.865328 35.309589, -77.865349 35.309447, -77.865342 35.309276, -77.865363 35.309232, -77.865414 35.309172, -77.865562 35.309019, -77.865831 35.308722, -77.866185 35.308266, -77.866285 35.308128, -77.866372 35.307947, -77.86648 35.307601, -77.866527 35.307469, -77.866627 35.307238, -77.866693 35.307046, -77.866714 35.306925, -77.866707 35.306771, -77.866666 35.306613, -77.866607 35.306459, -77.866492 35.306322, -77.866391 35.306222, -77.866304 35.306156, -77.866123 35.306068, -77.865888 35.30597, -77.865642 35.305926, -77.86546 35.305927, -77.865386 35.30592, -77.865345 35.305905, -77.865245 35.305894, -77.865186 35.30591, -77.865146 35.30591, -77.864643 35.305773, -77.864529 35.305718, -77.864274 35.305614, -77.864247 35.305614, -77.864231 35.305628, -77.864181 35.305691, -77.864099 35.30574, -77.864019 35.305773, -77.863812 35.305812, -77.863764 35.305823, -77.863732 35.305861, -77.863731 35.305889, -77.863718 35.305905, -77.863685 35.305911, -77.863644 35.305905, -77.863604 35.30584, -77.863583 35.305828, -77.863543 35.305828, -77.863491 35.305845, -77.86335 35.305878, -77.863235 35.305894, -77.863021 35.305899, -77.862881 35.305894, -77.862746 35.305879, -77.862585 35.305856, -77.862472 35.305812, -77.862392 35.305775, -77.86229 35.305719, -77.86219 35.305653, -77.862109 35.305565, -77.862069 35.305505, -77.862042 35.305445, -77.86203 35.305389, -77.862036 35.305334, -77.862062 35.305296, -77.862103 35.305263, -77.862149 35.305241, -77.862257 35.305214, -77.86239 35.305203, -77.862565 35.305214, -77.862659 35.305241, -77.862713 35.305264, -77.862753 35.305301, -77.862792 35.305362, -77.862787 35.305488, -77.862813 35.305543, -77.862834 35.305554, -77.862867 35.305537, -77.862935 35.305483, -77.863067 35.3054, -77.863148 35.305334, -77.863194 35.30528, -77.863275 35.30523, -77.863368 35.305191, -77.863563 35.305087, -77.863609 35.305032, -77.863658 35.304927, -77.863683 35.304857, -77.865038 35.30385, -77.865157 35.303757, -77.865119 35.303732, -77.865007 35.303659, -77.86497 35.303635, -77.864596 35.303907, -77.864162 35.304233, -77.863649 35.30462, -77.863395 35.304824, -77.863173 35.304949, -77.862953 35.305037, -77.862915 35.305044, -77.86267 35.305059, -77.862029 35.305081, -77.861791 35.305143, -77.861638 35.305258, -77.861547 35.305427, -77.861516 35.305488, -77.861436 35.305919, -77.861356 35.306411, -77.861326 35.306594, -77.861236 35.307146, -77.861218 35.307258, -77.861199 35.307329, -77.860467 35.307366, -77.860405 35.307374, -77.860077 35.307433, -77.859919 35.30747, -77.859863 35.30748, -77.859741 35.307504, -77.858632 35.307747, -77.858589 35.307509, -77.858285 35.307554, -77.856626 35.307804, -77.853852 35.30822, -77.854237 35.309022, -77.854276 35.309103, -77.854461 35.309487, -77.854529 35.309687, -77.855526 35.309544, -77.855635 35.309752, -77.855722 35.309979, -77.856205 35.309793, -77.856615 35.309574, -77.85691 35.309409, -77.857057 35.309352, -77.857327 35.309285, -77.85757 35.309249, -77.857598 35.309393, -77.85768 35.309816, -77.857538 35.31034, -77.857058 35.31041, -77.857008 35.310417, -77.856953 35.310425, -77.856773 35.310452, -77.856324 35.310518, -77.85636 35.310676, -77.856383 35.31078, -77.856393 35.31082, -77.856438 35.311021, -77.85649 35.311258, -77.856527 35.311428, -77.856545 35.311513, -77.856582 35.311663, -77.856672 35.31209, -77.857001 35.31205, -77.857432 35.311998, -77.857725 35.311962, -77.857896 35.311942, -77.858374 35.311884, -77.858873 35.311824, -77.858777 35.312006, -77.858643 35.312259, -77.858536 35.312462, -77.858462 35.312602, -77.858392 35.312735, -77.858222 35.313056, -77.858105 35.313277, -77.857929 35.313611, -77.857813 35.313832, -77.857755 35.314032, -77.857634 35.314456, -77.857571 35.314616, -77.857608 35.314712, -77.857648 35.314888, -77.857809 35.31559, -77.85784 35.315726, -77.857854 35.315788, -77.857842 35.315862, -77.857787 35.316192, -77.85773 35.316541, -77.857669 35.316894, -77.857659 35.316936, -77.857607 35.317282, -77.85759 35.317385, -77.857574 35.317619, -77.857551 35.317945, -77.857531 35.318195, -77.857496 35.318624, -77.857483 35.318787, -77.857349 35.318996, -77.857321 35.319041, -77.857281 35.319103, -77.857186 35.319251, -77.857105 35.319378, -77.857199 35.319419, -77.857248 35.31944, -77.85785 35.319765, -77.859193 35.320565, -77.861216 35.321581, -77.860912 35.321987, -77.860316 35.322787, -77.860022 35.323222, -77.859739 35.323644, -77.859622 35.323828, -77.859577 35.32395, -77.859552 35.324082, -77.859546 35.324234, -77.859575 35.324424, -77.859594 35.324668, -77.859591 35.324865, -77.859575 35.324989, -77.859571 35.325028, -77.85951 35.325209, -77.859467 35.32532, -77.859602 35.325373, -77.859733 35.325411, -77.859855 35.32542, -77.860004 35.325418, -77.860088 35.325407, -77.860168 35.325397, -77.86054 35.325267, -77.86165 35.324821, -77.861867 35.324713, -77.861906 35.324694, -77.862048 35.32458, -77.86214 35.324474, -77.862203 35.324359, -77.862244 35.324224, -77.862244 35.324143, -77.862232 35.324063, -77.86221 35.323963, -77.862178 35.323873, -77.862148 35.323819, -77.8621 35.323733, -77.862052 35.323573, -77.86204 35.323442, -77.862056 35.323323, -77.862149 35.323132, -77.862453 35.322696, -77.862694 35.322351, -77.863495 35.322761, -77.86397 35.323011, -77.864306 35.323179, -77.864867 35.323474, -77.867111 35.324627, -77.867573 35.324855, -77.868014 35.325061, -77.868487 35.32527, -77.868849 35.325416, -77.869225 35.325558, -77.869578 35.325697, -77.870425 35.32602, -77.872709 35.326887, -77.874025 35.327389, -77.875198 35.32783, -77.87582 35.328072, -77.876112 35.328182, -77.877055 35.328537, -77.87728 35.328623, -77.881257 35.330132, -77.881683 35.330296, -77.88223 35.330503, -77.885872 35.331887, -77.886256 35.332031, -77.886374 35.33208, -77.886608 35.332165, -77.886813 35.332245, -77.887083 35.33235, -77.887794 35.33265, -77.888368 35.332929, -77.888695 35.333102, -77.889246 35.333421, -77.889368 35.333499, -77.889457 35.333556, -77.889863 35.333828, -77.890321 35.334152, -77.890351 35.334179, -77.890748 35.334476, -77.891461 35.335024, -77.892245 35.335623, -77.893417 35.336519, -77.894226 35.337128, -77.894502 35.33734, -77.894768 35.337542, -77.895176 35.337842, -77.895363 35.337972, -77.895683 35.338181, -77.8959 35.338314, -77.896115 35.33844, -77.896558 35.338671, -77.896783 35.338779, -77.89709 35.338915, -77.897395 35.339039, -77.897726 35.339163, -77.89798 35.339253, -77.8983 35.339362, -77.898232 35.33946, -77.897972 35.339884, -77.897881 35.340015, -77.89783 35.34008, -77.897658 35.340305, -77.896241 35.34211, -77.895711 35.342786, -77.895284 35.343326, -77.895024 35.343657, -77.894005 35.344949, -77.893956 35.345012, -77.893786 35.345261, -77.893681 35.345426, -77.893636 35.345528, -77.893544 35.345745, -77.893506 35.345888, -77.893459 35.346069, -77.893429 35.346284, -77.893428 35.346351, -77.893425 35.346584, -77.89345 35.34681, -77.893481 35.34696, -77.893499 35.347027, -77.893522 35.347108, -77.893577 35.347268, -77.893628 35.347389, -77.893672 35.34748, -77.893788 35.347685, -77.893815 35.347727, -77.893831 35.347752, -77.893921 35.347874, -77.894042 35.348017, -77.894206 35.348188, -77.89433 35.348316, -77.89492 35.348916, -77.894958 35.348949, -77.894988 35.348986, -77.895069 35.349068, -77.895344 35.349295, -77.895416 35.349345, -77.895591 35.349455, -77.895745 35.349541, -77.895905 35.349619, -77.896108 35.349703, -77.896599 35.349877, -77.896677 35.349905, -77.897165 35.350074, -77.897537 35.350205, -77.897696 35.350261, -77.898173 35.350429, -77.898333 35.350486, -77.8986 35.35058, -77.899403 35.350863, -77.899671 35.350958, -77.900295 35.351177, -77.900699 35.35132, -77.90217 35.351834, -77.902795 35.352053, -77.902858 35.352075, -77.903048 35.352141, -77.903112 35.352164, -77.903975 35.352465, -77.904211 35.352547, -77.905167 35.352881, -77.907518 35.353676, -77.907995 35.353837, -77.908531 35.354034, -77.908609 35.354081, -77.908888 35.35381, -77.90895 35.353749, -77.909611 35.353118, -77.909818 35.352931, -77.909998 35.352782, -77.910209 35.352619, -77.91038 35.352497, -77.910509 35.352405, -77.910647 35.352311, -77.911455 35.351767, -77.911574 35.351687, -77.911661 35.351743, -77.912065 35.352005, -77.912166 35.35207, -77.912306 35.352161, -77.912456 35.352258, -77.912828 35.352011, -77.912932 35.351943, -77.913135 35.351809, -77.913223 35.35175, -77.9132 35.351726, -77.913022 35.351554, -77.913212 35.351426, -77.913182 35.351391, -77.914723 35.350359, -77.915578 35.349788, -77.91548 35.34969, -77.915188 35.349396, -77.915091 35.349299, -77.915191 35.349226, -77.915349 35.349112, -77.915488 35.349, -77.915522 35.348973, -77.915583 35.34892, -77.915748 35.348779, -77.915849 35.348688, -77.916084 35.34848, -77.916316 35.348275, -77.916844 35.347791, -77.917287 35.34737, -77.917556 35.347128, -77.917754 35.346952, -77.917886 35.346765, -77.918075 35.346866, -77.918553 35.347151, -77.918873 35.347362, -77.919152 35.347565, -77.919331 35.347702, -77.919615 35.34794, -77.919896 35.348198, -77.920167 35.34847, -77.920348 35.348669, -77.920534 35.348889, -77.920711 35.349114, -77.921242 35.349851, -77.921551 35.350263, -77.921656 35.35043, -77.922374 35.351461, -77.923926 35.35365, -77.923989 35.353739, -77.924198 35.354032, -77.924465 35.354405, -77.925059 35.355262, -77.925301 35.355643, -77.925356 35.355742, -77.925733 35.356318, -77.926413 35.35744, -77.926525 35.357597, -77.926598 35.357666, -77.92671 35.357733, -77.92685 35.357782, -77.927017 35.357828, -77.92709 35.358014, -77.927124 35.358126, -77.927128 35.358138, -77.927171 35.358349, -77.927234 35.358785, -77.92724 35.358827, -77.927258 35.358915, -77.927288 35.359062, -77.927352 35.359373, -77.927369 35.359456, -77.927386 35.359509, -77.927146 35.359566, -77.927228 35.359754, -77.927953 35.361403, -77.929137 35.364114, -77.929223 35.364311, -77.929529 35.364995, -77.929926 35.365825, -77.93008 35.366137, -77.930386 35.366717, -77.930499 35.366932, -77.931734 35.369234, -77.931873 35.369494, -77.931845 35.369616, -77.9317 35.369795, -77.931638 35.369887, -77.931578 35.369964, -77.931459 35.370119, -77.931406 35.37019, -77.931252 35.3704, -77.931198 35.370475, -77.931161 35.370525, -77.931052 35.370678, -77.931017 35.370729, -77.930994 35.370761, -77.930851 35.370951, -77.930836 35.370972, -77.930807 35.371011, -77.930722 35.371114, -77.930521 35.371361, -77.93033 35.371559, -77.930206 35.371662, -77.930075 35.37177, -77.930014 35.371815, -77.929885 35.371901, -77.929719 35.372012, -77.929626 35.372055, -77.929435 35.372145, -77.929311 35.372198, -77.92918 35.372246, -77.929145 35.372257, -77.92904 35.37229, -77.929005 35.372301, -77.928968 35.372313, -77.928454 35.37243, -77.928414 35.372437, -77.928142 35.372491, -77.927906 35.372509, -77.927842 35.372513, -77.927578 35.372499, -77.927299 35.372466, -77.927088 35.372427, -77.926815 35.372358, -77.926638 35.372302, -77.926626 35.372297, -77.926518 35.372257, -77.926068 35.372061, -77.925775 35.371923, -77.925193 35.371656, -77.923734 35.370987, -77.922817 35.370567, -77.922566 35.370452, -77.922508 35.370426, -77.922257 35.370293, -77.921882 35.370072, -77.921778 35.370004, -77.921734 35.369971, -77.921377 35.370324, -77.921293 35.370409, -77.920372 35.371311, -77.920301 35.371377, -77.920068 35.371598, -77.920177 35.371639, -77.920614 35.371771, -77.920924 35.371865, -77.921723 35.372108, -77.921128 35.372987, -77.920986 35.373198, -77.920842 35.37341, -77.920417 35.374039, -77.920286 35.374233, -77.921257 35.374602, -77.922569 35.375102, -77.923187 35.375337, -77.923118 35.375419, -77.923035 35.375518, -77.922989 35.375571, -77.922793 35.3758, -77.922398 35.376265, -77.922048 35.376676, -77.921639 35.377158, -77.921051 35.377848, -77.920901 35.377819, -77.920586 35.377759, -77.920273 35.377698, -77.919796 35.377607, -77.919183 35.37741, -77.918594 35.377377, -77.918258 35.377312, -77.917785 35.377285, -77.917484 35.377268, -77.91685 35.37723, -77.916482 35.377365, -77.916384 35.377393, -77.916365 35.377314, -77.916346 35.377264, -77.91624 35.376977, -77.916065 35.376648, -77.916017 35.376558, -77.915954 35.376452, -77.91589 35.376344, -77.915776 35.376151, -77.91622 35.375615, -77.916549 35.375217, -77.916647 35.375102, -77.916617 35.375052, -77.916518 35.374882, -77.916428 35.37472, -77.915387 35.372992, -77.913466 35.372248, -77.913354 35.372059, -77.913301 35.371971, -77.913234 35.371861, -77.912656 35.371641, -77.911859 35.371338, -77.909126 35.370299, -77.908474 35.370051, -77.905517 35.368927, -77.901811 35.367517, -77.900964 35.367194, -77.900723 35.367098, -77.900502 35.367019, -77.896803 35.365612, -77.893876 35.3645, -77.89308 35.364201, -77.892695 35.364053, -77.892554 35.364088, -77.892131 35.364196, -77.8921 35.364205, -77.891992 35.364236, -77.891718 35.364314, -77.891413 35.364431, -77.891102 35.364572, -77.89109 35.364578, -77.890738 35.36477, -77.889979 35.365207, -77.889938 35.365231, -77.888625 35.365996, -77.888578 35.366023, -77.887803 35.366477, -77.885858 35.367615, -77.884836 35.368214, -77.884164 35.368604, -77.883685 35.368873, -77.883398 35.369004, -77.883133 35.369098, -77.882884 35.369158, -77.879525 35.369649, -77.878594 35.369785, -77.878005 35.369872, -77.877588 35.369931, -77.877301 35.369997, -77.877523 35.370686, -77.87756 35.3708, -77.877951 35.372009, -77.878198 35.372753, -77.878395 35.373347, -77.878427 35.373441, -77.878642 35.374073, -77.878675 35.374156, -77.878715 35.374253, -77.878724 35.37427, -77.8788 35.374416, -77.878883 35.374541, -77.878129 35.37508, -77.876408 35.376291, -77.876164 35.376465, -77.875044 35.377253, -77.874632 35.377548, -77.874422 35.377692, -77.873795 35.378125, -77.873586 35.37827, -77.872891 35.378752, -77.872822 35.378801, -77.872541 35.378989, -77.872156 35.379236, -77.871862 35.379417, -77.871522 35.379608, -77.871257 35.379733, -77.870715 35.379969, -77.870679 35.379983, -77.869899 35.38031, -77.869598 35.380435, -77.869126 35.380633, -77.868843 35.380738, -77.868685 35.380773, -77.86867 35.380777, -77.868361 35.380801, -77.868373 35.380853, -77.868322 35.381043, -77.868123 35.381626, -77.867288 35.384088, -77.867192 35.384373, -77.867084 35.384741, -77.867069 35.3848, -77.86704 35.384918, -77.866969 35.385207, -77.866927 35.385444, -77.866914 35.385545, -77.866903 35.3858, -77.866928 35.38607, -77.867111 35.387537, -77.867229 35.38828, -77.86724 35.388345, -77.868151 35.393463, -77.868246 35.393998, -77.86849 35.395359, -77.86856 35.395744, -77.868588 35.3959, -77.868824 35.397213, -77.869022 35.398313, -77.869074 35.3986, -77.869522 35.401015, -77.869607 35.401286, -77.869756 35.40157, -77.869782 35.401607, -77.871704 35.399765, -77.871877 35.3996, -77.871949 35.399346, -77.872059 35.398957, -77.873981 35.392144, -77.874473 35.390402, -77.874704 35.389582, -77.876498 35.385825, -77.876914 35.386192, -77.877611 35.386786, -77.87808 35.387176, -77.878717 35.387731, -77.878985 35.387978, -77.879092 35.388023, -77.879306 35.388138, -77.880145 35.388561, -77.880299 35.388644, -77.880466 35.388638, -77.881312 35.388677, -77.881508 35.388681, -77.881835 35.388688, -77.882137 35.388705, -77.882236 35.388743, -77.882298 35.38876, -77.882398 35.388787, -77.882753 35.388897, -77.883142 35.389024, -77.883296 35.389035, -77.883766 35.389128, -77.884067 35.3892, -77.884316 35.389244, -77.884605 35.38931, -77.884745 35.389381, -77.884946 35.389497, -77.885013 35.38954, -77.885173 35.389508, -77.885463 35.38947, -77.886717 35.389387, -77.886878 35.389377, -77.887039 35.389377, -77.887354 35.389366, -77.887407 35.389339, -77.887448 35.389311, -77.888119 35.388949, -77.888608 35.388659, -77.888742 35.388587, -77.888856 35.388515, -77.889004 35.388428, -77.889179 35.388335, -77.889334 35.388263, -77.889363 35.388245, -77.889702 35.388044, -77.89007 35.38784, -77.890165 35.387818, -77.890212 35.387802, -77.890299 35.387807, -77.890407 35.38783, -77.8905 35.387824, -77.890815 35.387858, -77.891443 35.387909, -77.891547 35.387918, -77.891956 35.387945, -77.892303 35.387979, -77.892338 35.387979, -77.892539 35.388011, -77.892901 35.388045, -77.893135 35.38805, -77.893148 35.388055, -77.893136 35.388127, -77.893141 35.388262, -77.893142 35.388275, -77.893243 35.388341, -77.893256 35.388363, -77.89327 35.38843, -77.893269 35.388501, -77.893249 35.388814, -77.893251 35.388827, -77.893269 35.388913, -77.893289 35.388951, -77.893344 35.388979, -77.89336 35.388981, -77.893079 35.38947, -77.892177 35.391537, -77.892107 35.39169, -77.892035 35.391853, -77.891983 35.391973, -77.891882 35.392235, -77.892256 35.392347, -77.892695 35.392478, -77.89285 35.392525, -77.893008 35.392572, -77.893363 35.392679, -77.893713 35.392783, -77.893981 35.392864, -77.89413 35.392909, -77.8944 35.39299, -77.894577 35.393043, -77.894752 35.393095, -77.894914 35.393144, -77.895312 35.393263, -77.895875 35.393432, -77.896399 35.393589, -77.896866 35.393729, -77.897587 35.393946, -77.898081 35.394094, -77.898535 35.394239, -77.898963 35.394375, -77.899266 35.394472, -77.900302 35.394807, -77.900909 35.395009, -77.901237 35.395115, -77.901377 35.395121, -77.901445 35.395127, -77.901975 35.395314, -77.902092 35.395338, -77.902397 35.3954, -77.902564 35.395448, -77.903044 35.395636, -77.903544 35.395832, -77.904019 35.396019, -77.90432 35.396136, -77.904407 35.396171, -77.90476 35.396309, -77.905141 35.396458, -77.905606 35.396641, -77.906131 35.396847, -77.906632 35.397044, -77.906959 35.397172, -77.907092 35.397222, -77.906978 35.397297, -77.905432 35.398313, -77.904297 35.399056, -77.905836 35.399564, -77.906873 35.399893, -77.907312 35.40004, -77.907326 35.400045, -77.907686 35.400164, -77.907846 35.400217, -77.908329 35.400379, -77.90849 35.400433, -77.908828 35.400544, -77.909301 35.400701, -77.909719 35.400839, -77.909845 35.40088, -77.910184 35.400993, -77.910828 35.401215, -77.911156 35.401343, -77.911197 35.40136, -77.911588 35.401531, -77.91366 35.402517, -77.913787 35.402581, -77.914116 35.402753, -77.914194 35.402794, -77.914384 35.402902, -77.91456 35.403012, -77.914682 35.403094, -77.914917 35.403265, -77.915015 35.403345, -77.915257 35.403543, -77.915588 35.40326, -77.917202 35.401945, -77.917339 35.401791, -77.917467 35.401629, -77.917518 35.401586, -77.917665 35.401484, -77.917647 35.401392, -77.917578 35.401301, -77.917559 35.401276, -77.917163 35.400908, -77.917014 35.400745, -77.916962 35.400688, -77.91682 35.400589, -77.916646 35.400424, -77.916519 35.400293, -77.916352 35.400029, -77.91621 35.399793, -77.916096 35.399568, -77.916035 35.399419, -77.915949 35.399233, -77.915876 35.399062, -77.915722 35.398788, -77.915681 35.398694, -77.915479 35.398419, -77.915413 35.398342, -77.915393 35.398283, -77.915352 35.398216, -77.915347 35.398194, -77.915326 35.398139, -77.915314 35.398118, -77.915277 35.398053, -77.915631 35.397675, -77.915749 35.39755, -77.916167 35.397106, -77.91668 35.39656, -77.916933 35.396692, -77.917247 35.396856, -77.917262 35.396866, -77.917302 35.396891, -77.917269 35.397008, -77.917242 35.397196, -77.918656 35.397714, -77.918781 35.397802, -77.919635 35.398299, -77.919459 35.398728, -77.919764 35.399293, -77.920537 35.399295, -77.920769 35.399258, -77.921099 35.39924, -77.921254 35.399202, -77.92166 35.399202, -77.921715 35.399206, -77.921808 35.399222, -77.922367 35.399363, -77.92313 35.399655, -77.923672 35.39956, -77.924079 35.401101, -77.924509 35.400905, -77.927221 35.399424, -77.927286 35.399415, -77.927366 35.399393, -77.927653 35.399314, -77.927714 35.399298, -77.928658 35.399039, -77.930137 35.398632, -77.929771 35.398221, -77.929958 35.398108, -77.930584 35.397735, -77.93097 35.398174, -77.931013 35.398226, -77.931103 35.398336, -77.931171 35.398429, -77.931255 35.398557, -77.931449 35.398897, -77.931598 35.399181, -77.931673 35.399348, -77.931748 35.399557, -77.931788 35.399697, -77.93179 35.399706, -77.931829 35.399892, -77.931845 35.400019, -77.931853 35.400186, -77.931841 35.400388, -77.931812 35.400642, -77.931782 35.40083, -77.931764 35.400948, -77.931718 35.401203, -77.931657 35.401533, -77.931637 35.401644, -77.931619 35.401878, -77.931619 35.402002, -77.931631 35.402145, -77.931681 35.402388, -77.931707 35.402506, -77.931714 35.40253, -77.931773 35.402719, -77.931826 35.402848, -77.931937 35.403056, -77.932031 35.403205, -77.932074 35.403266, -77.932215 35.403441, -77.932324 35.403558, -77.93247 35.403696, -77.932565 35.403777, -77.932597 35.403801, -77.932705 35.403886, -77.932862 35.403992, -77.932926 35.404031, -77.932964 35.404054, -77.933099 35.404128, -77.933552 35.404343, -77.934123 35.404606, -77.935032 35.405025, -77.935383 35.405186, -77.935587 35.405274, -77.93588 35.405389, -77.935936 35.405407, -77.936084 35.405458, -77.936256 35.405509, -77.936713 35.405626, -77.937131 35.405728, -77.937104 35.405852, -77.937098 35.405863, -77.937017 35.406116, -77.937044 35.406209, -77.937043 35.406423, -77.937052 35.406583, -77.937071 35.406791, -77.937119 35.407032, -77.937017 35.40729, -77.936977 35.407428, -77.936965 35.40751, -77.936957 35.407566, -77.936937 35.407883, -77.936972 35.40806, -77.936931 35.408274, -77.936917 35.408411, -77.936877 35.408549, -77.93679 35.408807, -77.936735 35.408906, -77.936662 35.409016, -77.936629 35.409054, -77.936616 35.40907, -77.936508 35.409224, -77.936428 35.409432, -77.936373 35.409542, -77.936259 35.409889, -77.93624 35.409993, -77.936213 35.410025, -77.936125 35.410124, -77.935944 35.410355, -77.935784 35.410619, -77.93565 35.410718, -77.935448 35.410883, -77.935018 35.411135, -77.934972 35.411163, -77.934777 35.411251, -77.934637 35.411322, -77.934428 35.411454, -77.9342 35.41158, -77.934015 35.411708, -77.933979 35.411734, -77.933811 35.411871, -77.933522 35.412074, -77.933436 35.412162, -77.933361 35.412256, -77.933309 35.41236, -77.933314 35.412426, -77.93328 35.412497, -77.933247 35.412591, -77.933113 35.412794, -77.933033 35.412882, -77.932973 35.412986, -77.932918 35.413069, -77.932907 35.413102, -77.932885 35.413167, -77.932825 35.413398, -77.932772 35.413546, -77.932657 35.413974, -77.932617 35.414079, -77.932617 35.414096, -77.932557 35.414265, -77.932542 35.414392, -77.932543 35.414501, -77.932522 35.414672, -77.932536 35.414842, -77.93253 35.414886, -77.932522 35.414946, -77.932536 35.415073, -77.932529 35.415145, -77.932496 35.415232, -77.932409 35.415375, -77.932295 35.41548, -77.932221 35.415512, -77.932167 35.415545, -77.931765 35.415704, -77.93151 35.415826, -77.931471 35.415847, -77.931363 35.415908, -77.931208 35.415979, -77.930846 35.416133, -77.930732 35.416172, -77.930537 35.416198, -77.930249 35.416243, -77.929933 35.416281, -77.929705 35.416281, -77.929551 35.416298, -77.929444 35.416325, -77.929309 35.416385, -77.929101 35.416463, -77.929 35.416517, -77.928786 35.416611, -77.92859 35.416714, -77.928417 35.416775, -77.92835 35.416797, -77.928102 35.416891, -77.927988 35.416967, -77.927806 35.417122, -77.927726 35.41716, -77.927511 35.417275, -77.927397 35.417347, -77.927263 35.417445, -77.927209 35.417549, -77.927115 35.417703, -77.927082 35.417779, -77.926988 35.417995, -77.926961 35.418088, -77.926813 35.418319, -77.92676 35.41844, -77.92672 35.418599, -77.926666 35.418747, -77.926585 35.419148, -77.926439 35.419559, -77.92641 35.419664, -77.926391 35.419774, -77.926351 35.419878, -77.926311 35.42001, -77.92625 35.420158, -77.926122 35.420438, -77.926009 35.420625, -77.925913 35.420796, -77.925821 35.420905, -77.925733 35.420982, -77.925626 35.421065, -77.925518 35.421169, -77.925491 35.421251, -77.925439 35.421295, -77.925418 35.421312, -77.925404 35.421399, -77.92539 35.421526, -77.92539 35.421652, -77.925384 35.421778, -77.925404 35.421867, -77.925451 35.422036, -77.925512 35.422289, -77.925605 35.422599, -77.925625 35.422663, -77.925659 35.4228, -77.925679 35.42297, -77.925673 35.423162, -77.925698 35.423289, -77.925706 35.423443, -77.925726 35.423635, -77.92572 35.423827, -77.925739 35.423991, -77.925738 35.424178, -77.925746 35.424388, -77.925752 35.42441, -77.925759 35.42453, -77.925793 35.424755, -77.925813 35.425052, -77.925853 35.425211, -77.925888 35.425381, -77.925908 35.425552, -77.92594 35.425672, -77.925967 35.425721, -77.925987 35.425837, -77.926022 35.425897, -77.926049 35.425965, -77.926188 35.42593, -77.926458 35.425848, -77.926598 35.425798, -77.926657 35.425778, -77.926898 35.425679, -77.92704 35.425607, -77.927357 35.425425, -77.927518 35.425325, -77.927972 35.425051, -77.928106 35.424969, -77.928474 35.424747, -77.928605 35.424683, -77.928665 35.424635, -77.929056 35.424399, -77.92909 35.424379, -77.929794 35.423934, -77.930071 35.42375, -77.930139 35.423706, -77.930201 35.423657, -77.930356 35.423523, -77.930379 35.423499, -77.930523 35.423354, -77.930742 35.423107, -77.93097 35.422846, -77.931654 35.422065, -77.931883 35.421805, -77.932022 35.421645, -77.93238 35.421254, -77.932752 35.420867, -77.933039 35.420591, -77.933952 35.419714, -77.93441 35.419276, -77.934741 35.418955, -77.93488 35.41882, -77.935644 35.418082, -77.936662 35.4171, -77.937098 35.41668, -77.937819 35.415995, -77.937874 35.415942, -77.937964 35.415855, -77.93798 35.415839, -77.9383 35.415532, -77.938407 35.41543, -77.939096 35.414779, -77.939313 35.414576, -77.940084 35.413841, -77.940109 35.413817, -77.940238 35.413696, -77.940363 35.413572, -77.94051 35.413415, -77.940692 35.413204, -77.941068 35.412737, -77.941575 35.41211, -77.941665 35.412164, -77.941895 35.412293, -77.941987 35.412353, -77.942073 35.412418, -77.942153 35.412489, -77.942289 35.412641, -77.942508 35.412993, -77.942637 35.413144, -77.942723 35.413219, -77.942746 35.41324, -77.942883 35.413338, -77.942976 35.413391, -77.943176 35.41348, -77.943391 35.413542, -77.943502 35.413563, -77.943615 35.413576, -77.943738 35.413578, -77.943992 35.413549, -77.944187 35.413538, -77.944366 35.413557, -77.944605 35.413635, -77.945932 35.414256, -77.946101 35.414315, -77.94619 35.414334, -77.946207 35.414657, -77.946321 35.41471, -77.946363 35.41473, -77.947427 35.415234, -77.947404 35.415254, -77.947256 35.415401, -77.947223 35.415433, -77.947105 35.415533, -77.947092 35.415657, -77.947162 35.415779, -77.947225 35.415815, -77.947317 35.415868, -77.947715 35.416057, -77.94794 35.415922, -77.948049 35.415767, -77.9483 35.415679, -77.948334 35.415665, -77.949051 35.416006, -77.949076 35.41614, -77.948881 35.416439, -77.948857 35.41648, -77.948672 35.41665, -77.948991 35.416805, -77.9489 35.416859, -77.948831 35.416909, -77.948743 35.41699, -77.948726 35.417001, -77.948692 35.417006, -77.948675 35.417001, -77.948585 35.417117, -77.948604 35.417133, -77.948611 35.417154, -77.948604 35.417175, -77.948562 35.417233, -77.948656 35.417277, -77.948737 35.417315, -77.949079 35.417477, -77.948927 35.417691, -77.950769 35.418563, -77.951533 35.418925, -77.951509 35.418943, -77.951475 35.419003, -77.951456 35.419119, -77.951429 35.41919, -77.951382 35.419339, -77.951369 35.419454, -77.951369 35.419547, -77.951348 35.419641, -77.951336 35.419718, -77.951336 35.419856, -77.95133 35.41989, -77.951302 35.42008, -77.951241 35.4203, -77.951195 35.42041, -77.951155 35.420553, -77.951101 35.420646, -77.951047 35.420701, -77.951034 35.420761, -77.951014 35.420794, -77.951 35.420832, -77.950993 35.420871, -77.950974 35.420921, -77.950933 35.420987, -77.95084 35.421118, -77.950752 35.421272, -77.950725 35.421343, -77.950708 35.421366, -77.950678 35.421409, -77.950651 35.421486, -77.95063 35.421596, -77.950598 35.421662, -77.950584 35.421717, -77.950524 35.421838, -77.950497 35.421942, -77.950483 35.422222, -77.950524 35.422294, -77.950571 35.422431, -77.950605 35.422591, -77.950584 35.422656, -77.950585 35.422778, -77.950578 35.422843, -77.950558 35.422892, -77.950532 35.422936, -77.95049 35.422975, -77.950443 35.423008, -77.950243 35.423095, -77.950169 35.423118, -77.950115 35.423123, -77.950041 35.423144, -77.949947 35.423161, -77.94993 35.423174, -77.94982 35.423266, -77.949759 35.423309, -77.949685 35.42337, -77.949653 35.423425, -77.94949 35.423629, -77.949438 35.423721, -77.949371 35.423843, -77.949351 35.423906, -77.949336 35.423958, -77.949323 35.424035, -77.949296 35.424139, -77.949282 35.424243, -77.949263 35.424293, -77.949262 35.424342, -77.949282 35.424402, -77.949276 35.424469, -77.949296 35.424733, -77.94929 35.424842, -77.949276 35.424946, -77.949217 35.425128, -77.949176 35.42527, -77.949156 35.425309, -77.949122 35.425418, -77.949063 35.425501, -77.948941 35.42565, -77.948894 35.425732, -77.948888 35.425753, -77.948882 35.425803, -77.948881 35.425891, -77.948888 35.425941, -77.94892 35.4261, -77.948955 35.426171, -77.948975 35.426231, -77.948996 35.426319, -77.949062 35.426429, -77.949063 35.426512, -77.949049 35.426583, -77.949029 35.426649, -77.948981 35.42671, -77.948961 35.426775, -77.948948 35.426797, -77.948927 35.426813, -77.9489 35.42688, -77.948881 35.426973, -77.94882 35.427045, -77.948753 35.427176, -77.9487 35.427341, -77.948673 35.42744, -77.948639 35.427506, -77.948606 35.427599, -77.948606 35.427704, -77.948586 35.427786, -77.948572 35.427824, -77.948539 35.427879, -77.948498 35.427928, -77.948425 35.42795, -77.948391 35.427967, -77.948284 35.428016, -77.948265 35.428038, -77.948042 35.428182, -77.947982 35.428236, -77.947781 35.428412, -77.947654 35.428583, -77.947526 35.428676, -77.947459 35.428769, -77.947404 35.42883, -77.947247 35.428979, -77.94711 35.42911, -77.947049 35.429181, -77.946949 35.429268, -77.946747 35.429598, -77.94672 35.42967, -77.9467 35.429769, -77.946654 35.429862, -77.946614 35.429972, -77.946587 35.430092, -77.946592 35.430274, -77.946599 35.430334, -77.946581 35.430499, -77.946499 35.430691, -77.946473 35.430774, -77.94642 35.430878, -77.946426 35.430949, -77.946426 35.431015, -77.946439 35.431037, -77.94646 35.431076, -77.946567 35.431202, -77.946674 35.43129, -77.946748 35.431312, -77.946842 35.431367, -77.94703 35.431488, -77.947138 35.431542, -77.947245 35.431674, -77.947285 35.431746, -77.947364 35.431916, -77.94744 35.432031, -77.947581 35.43219, -77.947775 35.432378, -77.947908 35.432487, -77.948011 35.432602, -77.948063 35.432685, -77.948145 35.432795, -77.948305 35.432987, -77.948431 35.433121, -77.94864 35.433344, -77.948748 35.433431, -77.948908 35.433525, -77.948997 35.433552, -77.949104 35.433607, -77.949244 35.433651, -77.949432 35.433723, -77.94954 35.433788, -77.949627 35.433849, -77.949788 35.434009, -77.949848 35.434101, -77.949895 35.434189, -77.949943 35.434299, -77.949983 35.434404, -77.950009 35.434453, -77.95015 35.434595, -77.950238 35.434656, -77.950399 35.434788, -77.950459 35.434854, -77.950534 35.434908, -77.950735 35.435128, -77.950856 35.435238, -77.951063 35.435402, -77.951184 35.43548, -77.951278 35.435546, -77.951339 35.435601, -77.951447 35.43577, -77.951466 35.435919, -77.951514 35.436007, -77.951574 35.436073, -77.951635 35.436116, -77.951715 35.436122, -77.951808 35.436172, -77.951909 35.436237, -77.951976 35.436275, -77.952049 35.436353, -77.952118 35.436479, -77.952346 35.436803, -77.952447 35.437, -77.952567 35.437177, -77.952647 35.437336, -77.952741 35.437473, -77.952748 35.437494, -77.952762 35.437516, -77.952782 35.437545, -77.95293 35.437726, -77.95301 35.437791, -77.953064 35.437852, -77.953044 35.437885, -77.95309 35.438066, -77.953155 35.438213, -77.953198 35.438307, -77.953252 35.438385, -77.953318 35.438461, -77.953393 35.438538, -77.953473 35.438588, -77.953601 35.438752, -77.953669 35.438829, -77.953695 35.438867, -77.953749 35.438933, -77.953808 35.438988, -77.953917 35.439044, -77.954003 35.439093, -77.954319 35.439203, -77.954379 35.439224, -77.954494 35.439268, -77.954843 35.439433, -77.95501 35.439565, -77.955098 35.439647, -77.955359 35.439801, -77.955533 35.439921, -77.955736 35.440043, -77.956024 35.440235, -77.956085 35.44029, -77.956138 35.440355, -77.956259 35.440564, -77.956339 35.440674, -77.956427 35.440778, -77.956513 35.440861, -77.956567 35.440982, -77.956608 35.44102, -77.956655 35.441092, -77.956702 35.441179, -77.95677 35.441248, -77.956783 35.441261, -77.956883 35.441387, -77.956991 35.441492, -77.957071 35.441585, -77.957278 35.44187, -77.957373 35.442019, -77.957391 35.44205, -77.957413 35.442085, -77.957514 35.442228, -77.957592 35.442323, -77.957601 35.442333, -77.957643 35.44239, -77.957682 35.442442, -77.95779 35.442546, -77.95787 35.442608, -77.958031 35.442733, -77.958199 35.442887, -77.958347 35.442975, -77.958668 35.443123, -77.95851 35.443295, -77.958384 35.443453, -77.958306 35.443552, -77.958207 35.443692, -77.958116 35.443833, -77.957811 35.444351, -77.957689 35.444561, -77.957472 35.444939, -77.958222 35.444854, -77.958471 35.444825, -77.959239 35.444738, -77.961464 35.444458, -77.962407 35.44434, -77.962463 35.444339, -77.963502 35.444198, -77.96385 35.444152, -77.964146 35.444105, -77.965029 35.443943, -77.965226 35.4439, -77.96534 35.443865, -77.965449 35.443822, -77.965604 35.443742, -77.9657 35.44368, -77.965807 35.443595, -77.966358 35.443109, -77.966644 35.442859, -77.967153 35.442425, -77.967251 35.442434, -77.967345 35.44245, -77.967957 35.442516, -77.968104 35.442549, -77.968225 35.442587, -77.968332 35.442637, -77.968413 35.442659, -77.968668 35.442757, -77.968836 35.442807, -77.969016 35.442846, -77.969145 35.442867, -77.969311 35.442923, -77.969355 35.442933, -77.9695 35.442967, -77.969768 35.44307, -77.969969 35.443158, -77.970145 35.443246, -77.97028 35.443323, -77.970319 35.44335, -77.970447 35.443422, -77.970527 35.44346, -77.970655 35.443537, -77.970951 35.44374, -77.971319 35.443982, -77.971554 35.444151, -77.971675 35.444246, -77.971809 35.444366, -77.971897 35.444438, -77.971997 35.444541, -77.972119 35.444685, -77.972179 35.444766, -77.972266 35.444866, -77.972487 35.445068, -77.972569 35.445151, -77.972668 35.44526, -77.972709 35.445316, -77.97279 35.445404, -77.972998 35.445612, -77.973125 35.445717, -77.97332 35.445881, -77.973354 35.445903, -77.973375 35.445925, -77.973394 35.445969, -77.973434 35.44604, -77.973487 35.446117, -77.973575 35.446233, -77.973643 35.44631, -77.97379 35.446502, -77.973851 35.446606, -77.973904 35.446682, -77.973953 35.446776, -77.974086 35.447149, -77.974113 35.447276, -77.974119 35.447326, -77.974114 35.447456, -77.974113 35.447496, -77.974093 35.447589, -77.974079 35.447611, -77.973993 35.447798, -77.973952 35.447902, -77.973879 35.448122, -77.97377 35.44872, -77.973772 35.448781, -77.973758 35.448846, -77.973798 35.449077, -77.973832 35.449204, -77.973865 35.449296, -77.973913 35.44939, -77.973993 35.449517, -77.974048 35.449593, -77.974055 35.449605, -77.974182 35.449646, -77.974334 35.449707, -77.974492 35.449781, -77.974646 35.449863, -77.974795 35.449953, -77.974937 35.450049, -77.975039 35.450127, -77.975161 35.450232, -77.975307 35.450366, -77.975443 35.450507, -77.975848 35.450947, -77.976049 35.451167, -77.976641 35.451813, -77.976801 35.451922, -77.97687 35.451945, -77.977083 35.451209, -77.977226 35.451098, -77.977267 35.451076, -77.977319 35.451057, -77.977403 35.451045, -77.97743 35.451045, -77.977564 35.451059, -77.977537 35.450935, -77.977519 35.450835, -77.977489 35.450572, -77.977474 35.450343, -77.97752 35.449799, -77.977684 35.449154, -77.977911 35.448347, -77.978266 35.447107)))"} -{"geo_id":"11350","urban_area_code":"11350","name":"Buffalo, NY","lsad_name":"Buffalo, NY Urbanized Area","area_lsad_code":"75","mtfcc_feature_class_code":"G3500","type":"U","functional_status":"S","area_land_meters":984089312,"area_water_meters":14339704,"internal_point_lon":-78.8236745,"internal_point_lat":42.9249297,"internal_point_geom":"POINT(-78.8236745 42.9249297)","urban_area_geom":"MULTIPOLYGON(((-78.783926 42.677774, -78.784382 42.677328, -78.784718 42.676996, -78.785254 42.676432, -78.785491 42.676579, -78.785662 42.676675, -78.785766 42.676719, -78.785876 42.676752, -78.786021 42.676778, -78.786173 42.67679, -78.786298 42.676789, -78.786422 42.676775, -78.786571 42.67674, -78.786682 42.676699, -78.786786 42.676647, -78.78694 42.676546, -78.787062 42.676484, -78.787204 42.676432, -78.787364 42.676391, -78.787531 42.676367, -78.787657 42.67636, -78.787851 42.676359, -78.788093 42.676349, -78.788454 42.676361, -78.789285 42.676368, -78.789424 42.676359, -78.789621 42.676329, -78.789764 42.676294, -78.789902 42.676249, -78.790098 42.676167, -78.790252 42.676089, -78.790396 42.676, -78.791169 42.67658, -78.791216 42.676616, -78.791283 42.676653, -78.791348 42.676678, -78.791332 42.674677, -78.790898 42.674677, -78.789385 42.674677, -78.78936 42.67314, -78.789365 42.672973, -78.789385 42.672806, -78.789421 42.67264, -78.78967 42.671817, -78.789863 42.671176, -78.789576 42.670702, -78.789704 42.666564, -78.781203 42.666746, -78.77886 42.666818, -78.778856 42.667489, -78.778849 42.669435, -78.776014 42.669388, -78.779141 42.672702, -78.779759 42.673367, -78.780547 42.674142, -78.781441 42.675113, -78.782885 42.676682, -78.783926 42.677774)), ((-78.73439 42.621649, -78.734601 42.621649, -78.734654 42.621628, -78.734666 42.621348, -78.734358 42.621208, -78.733997 42.62071, -78.733538 42.620116, -78.73298 42.619657, -78.731616 42.619179, -78.731062 42.61911, -78.730407 42.618824, -78.730256 42.619007, -78.730369 42.619094, -78.730601 42.619307, -78.730872 42.619487, -78.731052 42.619693, -78.731348 42.619888, -78.731766 42.620258, -78.731963 42.620399, -78.732203 42.620589, -78.732548 42.62077, -78.732872 42.620858, -78.733102 42.620883, -78.733296 42.62088, -78.733475 42.62085, -78.733661 42.620831, -78.733822 42.620921, -78.73399 42.621033, -78.734113 42.621189, -78.734327 42.621517, -78.73439 42.621649)), ((-78.998535 43.175032, -78.998391 43.175057, -78.998282 43.175075, -78.997832 43.17515, -78.996157 43.175433, -78.995599 43.175528, -78.993127 43.175946, -78.992083 43.176123, -78.991732 43.176189, -78.991352 43.176281, -78.990941 43.176395, -78.990731 43.176466, -78.99716 43.175692, -78.998609 43.175518, -78.998575 43.175297, -78.998535 43.175032)), ((-78.824606 43.122351, -78.824696 43.124321, -78.824703 43.124499, -78.824965 43.130946, -78.825012 43.132076, -78.825029 43.132567, -78.826151 43.131818, -78.829469 43.126826, -78.824606 43.122351)), ((-78.827278 42.679336, -78.827249 42.680702, -78.831456 42.680693, -78.831461 42.679541, -78.831442 42.679494, -78.83141 42.679457, -78.831359 42.679425, -78.831297 42.679406, -78.827278 42.679336)), ((-78.664066 42.81989, -78.663725 42.819634, -78.663457 42.819395, -78.662962 42.818954, -78.662401 42.818476, -78.661546 42.817787, -78.660774 42.817189, -78.660048 42.816675, -78.659248 42.816178, -78.658403 42.815682, -78.657548 42.81525, -78.656353 42.814735, -78.655002 42.814156, -78.653953 42.81366, -78.650887 42.812343, -78.650401 42.812121, -78.649752 42.811826, -78.648545 42.811283, -78.647497 42.810844, -78.646458 42.810329, -78.645912 42.809966, -78.645009 42.809332, -78.644522 42.808942, -78.643741 42.808259, -78.643079 42.807455, -78.641873 42.805526, -78.641691 42.805195, -78.641545 42.805196, -78.641461 42.805196, -78.641341 42.805197, -78.641253 42.805196, -78.640612 42.80519, -78.640446 42.805189, -78.636603 42.805157, -78.636628 42.807278, -78.63664 42.808804, -78.636639 42.809457, -78.636664 42.813766, -78.636677 42.817267, -78.636679 42.817864, -78.636689 42.820503, -78.636698 42.82134, -78.636722 42.822464, -78.637974 42.822462, -78.642924 42.82246, -78.644394 42.822459, -78.646449 42.822457, -78.648741 42.82245, -78.652894 42.822438, -78.653585 42.822432, -78.654161 42.822427, -78.66204 42.826742, -78.661866 42.826135, -78.661931 42.825467, -78.661801 42.824782, -78.66171 42.824253, -78.661622 42.82374, -78.661556 42.822583, -78.66154 42.821932, -78.661338 42.821362, -78.661687 42.821215, -78.663759 42.820107, -78.664101 42.81992, -78.664066 42.81989)), ((-78.52326 42.891962, -78.52333 42.891948, -78.524284 42.891824, -78.526043 42.891779, -78.532307 42.891683, -78.532571 42.891682, -78.533411 42.888837, -78.533597 42.88815, -78.533686 42.887601, -78.533687 42.887432, -78.533632 42.886715, -78.533592 42.886181, -78.533612 42.886038, -78.533655 42.885681, -78.534511 42.884127, -78.535019 42.88376, -78.535227 42.88361, -78.536411 42.882858, -78.536835 42.882564, -78.536667 42.882354, -78.536303 42.88214, -78.534904 42.88211, -78.533651 42.882312, -78.532434 42.8824, -78.531786 42.882272, -78.529907 42.881041, -78.529139 42.880796, -78.528455 42.880781, -78.528016 42.880863, -78.52653 42.881449, -78.525843 42.881526, -78.522194 42.880189, -78.522012 42.879993, -78.521112 42.88104, -78.522579 42.882414, -78.522728 42.882642, -78.522788 42.88287, -78.522775 42.884355, -78.522786 42.885803, -78.522841 42.886007, -78.52294 42.886244, -78.523112 42.886568, -78.523151 42.886814, -78.523188 42.887062, -78.523197 42.887359, -78.521156 42.887344, -78.521056 42.887355, -78.520927 42.887381, -78.520804 42.88742, -78.52069 42.887471, -78.520586 42.887533, -78.520517 42.887587, -78.520436 42.887665, -78.520372 42.887751, -78.520323 42.887843, -78.520293 42.887939, -78.520281 42.888037, -78.520282 42.888085, -78.520297 42.888183, -78.520331 42.888277, -78.520381 42.888367, -78.520448 42.888451, -78.520531 42.888528, -78.520626 42.888595, -78.520734 42.888653, -78.520851 42.888699, -78.520975 42.888732, -78.521105 42.888753, -78.523215 42.888772, -78.523234 42.890047, -78.52326 42.891962)), ((-78.636722 42.822464, -78.634826 42.822476, -78.633226 42.82247, -78.632529 42.822471, -78.631891 42.822471, -78.628984 42.822474, -78.628992 42.82228, -78.628022 42.822109, -78.627351 42.821728, -78.627192 42.821373, -78.627041 42.821278, -78.626479 42.821335, -78.626295 42.821263, -78.62627 42.821102, -78.62651 42.820512, -78.625995 42.820158, -78.626034 42.819953, -78.626925 42.819423, -78.626934 42.819171, -78.62633 42.818724, -78.626264 42.818013, -78.624853 42.816703, -78.624743 42.816529, -78.624652 42.816458, -78.624457 42.815903, -78.624464 42.814183, -78.624361 42.813601, -78.624338 42.813469, -78.624133 42.813143, -78.624365 42.812804, -78.624312 42.812551, -78.622952 42.812363, -78.622504 42.811896, -78.622478 42.811758, -78.621375 42.809975, -78.621377 42.810343, -78.621381 42.813305, -78.621382 42.813464, -78.621383 42.81382, -78.621411 42.818439, -78.621383 42.820775, -78.621391 42.822491, -78.6214 42.824403, -78.621417 42.827519, -78.621438 42.828639, -78.621465 42.831133, -78.62149 42.832877, -78.623575 42.832185, -78.625506 42.831453, -78.62794 42.830572, -78.628885 42.830245, -78.629339 42.830096, -78.630286 42.829854, -78.630835 42.829881, -78.631284 42.829915, -78.631552 42.829935, -78.634428 42.828863, -78.634469 42.828845, -78.635553 42.828379, -78.635759 42.828109, -78.635717 42.828021, -78.635824 42.828115, -78.635921 42.828202, -78.636485 42.828671, -78.636501 42.828444, -78.636686 42.827019, -78.636695 42.826936, -78.636712 42.82656, -78.636727 42.826226, -78.636734 42.825582, -78.636722 42.824822, -78.636718 42.823808, -78.636722 42.822464)), ((-78.983549 43.178846, -78.984479 43.178612, -78.984985 43.178454, -78.985949 43.17813, -78.986549 43.177929, -78.989494 43.17689, -78.990484 43.176551, -78.990731 43.176466, -78.985966 43.17704, -78.983558 43.177419, -78.983525 43.177422, -78.98343 43.177434, -78.983398 43.177438, -78.981573 43.177676, -78.9761 43.178394, -78.974276 43.178633, -78.974416 43.178719, -78.974715 43.178943, -78.974801 43.179007, -78.974849 43.179085, -78.974876 43.179288, -78.974944 43.179532, -78.974944 43.179721, -78.974903 43.179802, -78.974741 43.179978, -78.974487 43.180108, -78.974374 43.180214, -78.97428 43.180303, -78.974219 43.180466, -78.974246 43.180669, -78.974265 43.180692, -78.975763 43.180384, -78.978229 43.17988, -78.980263 43.179478, -78.981764 43.179183, -78.98212 43.179115, -78.983192 43.178913, -78.983549 43.178846)), ((-78.52326 42.891962, -78.522471 42.892091, -78.521786 42.89224, -78.520875 42.892475, -78.520041 42.892679, -78.51999 42.892691, -78.52002 42.893883, -78.520027 42.894986, -78.520054 42.89527, -78.520122 42.895446, -78.5204 42.895468, -78.520839 42.895475, -78.520844 42.896057, -78.520846 42.89734, -78.523297 42.897379, -78.523283 42.896392, -78.523283 42.893876, -78.52326 42.891962)), ((-79.066032 43.078266, -79.066338 43.078346, -79.066582 43.078381, -79.066689 43.078365, -79.06691 43.078308, -79.067039 43.078251, -79.067101 43.078171, -79.067098 43.078147, -79.067083 43.078104, -79.067062 43.07803, -79.066708 43.077897, -79.066475 43.077805, -79.065964 43.077853, -79.065651 43.077885, -79.065437 43.077934, -79.065273 43.078029, -79.065201 43.078136, -79.06523 43.078251, -79.065333 43.078283, -79.065747 43.078214, -79.065943 43.078238, -79.066032 43.078266)), ((-78.640424 42.86445, -78.640399 42.862263, -78.640391 42.858497, -78.640349 42.855627, -78.640304 42.850684, -78.640295 42.849774, -78.640289 42.849651, -78.640301 42.849468, -78.640323 42.849347, -78.640376 42.849168, -78.640454 42.848994, -78.640556 42.848827, -78.640648 42.84871, -78.642185 42.849, -78.643262 42.849297, -78.644204 42.849866, -78.644603 42.850011, -78.646087 42.850248, -78.648236 42.850155, -78.649231 42.850233, -78.650189 42.850309, -78.651498 42.850222, -78.651721 42.850089, -78.65256 42.848459, -78.653532 42.847427, -78.65386 42.846953, -78.654253 42.845883, -78.653541 42.845884, -78.653497 42.84563, -78.653294 42.845451, -78.653117 42.845157, -78.653111 42.844867, -78.653157 42.844604, -78.653134 42.844373, -78.652972 42.84409, -78.652635 42.843898, -78.652259 42.843749, -78.651548 42.843374, -78.650753 42.843053, -78.650209 42.84278, -78.649768 42.842575, -78.649196 42.842527, -78.648595 42.84244, -78.648211 42.842314, -78.648152 42.842295, -78.647815 42.842113, -78.647621 42.841868, -78.64751 42.841603, -78.647504 42.841329, -78.647508 42.840972, -78.647576 42.840715, -78.647656 42.84053, -78.647664 42.8403, -78.647476 42.839654, -78.647639 42.839427, -78.647849 42.839165, -78.647983 42.838944, -78.64794 42.838647, -78.647873 42.83796, -78.647862 42.837598, -78.647792 42.837229, -78.647999 42.836799, -78.64828 42.836173, -78.648561 42.835783, -78.648744 42.83544, -78.648836 42.835107, -78.648867 42.83463, -78.648789 42.834221, -78.642723 42.833513, -78.636462 42.829279, -78.636473 42.829396, -78.636498 42.82955, -78.636517 42.829668, -78.636627 42.830144, -78.636703 42.830472, -78.636741 42.830657, -78.636806 42.831029, -78.636814 42.831083, -78.636815 42.831253, -78.636818 42.833547, -78.636856 42.835538, -78.63686 42.835853, -78.636864 42.836191, -78.636517 42.836289, -78.636346 42.836407, -78.636147 42.83649, -78.636004 42.836378, -78.63583 42.836321, -78.635559 42.836267, -78.63542 42.83628, -78.635318 42.836454, -78.635338 42.83672, -78.635385 42.836815, -78.635563 42.836851, -78.635795 42.836775, -78.635916 42.836751, -78.636227 42.837377, -78.63587 42.837417, -78.635643 42.837417, -78.63542 42.837475, -78.635278 42.837577, -78.635285 42.837909, -78.6353 42.838125, -78.635501 42.838121, -78.635692 42.838094, -78.635984 42.838039, -78.63604 42.838263, -78.636093 42.838428, -78.636227 42.838433, -78.636423 42.83846, -78.636891 42.838455, -78.636901 42.839276, -78.635199 42.839287, -78.634938 42.839289, -78.63413 42.839354, -78.631678 42.839362, -78.631615 42.839354, -78.627051 42.839348, -78.626868 42.839348, -78.626888 42.84165, -78.62689 42.841864, -78.626761 42.842424, -78.626781 42.844496, -78.625394 42.844481, -78.625402 42.845183, -78.625433 42.847814, -78.622701 42.847829, -78.621551 42.847826, -78.620516 42.847862, -78.617026 42.847865, -78.616642 42.847866, -78.606247 42.847921, -78.606299 42.850631, -78.606301 42.850712, -78.606325 42.852135, -78.606397 42.85431, -78.606399 42.854368, -78.606396 42.855909, -78.606373 42.855909, -78.606423 42.857288, -78.606506 42.861107, -78.606509 42.861257, -78.606577 42.863975, -78.601253 42.862068, -78.597494 42.862274, -78.598453 42.863232, -78.598338 42.864738, -78.600668 42.864727, -78.606596 42.864698, -78.610512 42.864679, -78.61855 42.864641, -78.619504 42.864637, -78.637279 42.864467, -78.638114 42.864459, -78.638542 42.864688, -78.640436 42.865486, -78.640424 42.86445)), ((-78.590747 42.856102, -78.593408 42.856078, -78.595869 42.856048, -78.596743 42.856046, -78.596694 42.85447, -78.596649 42.853055, -78.59655 42.849947, -78.594978 42.849958, -78.594959 42.849386, -78.594939 42.849343, -78.594903 42.849305, -78.594854 42.849275, -78.594788 42.849256, -78.594718 42.849252, -78.591601 42.849263, -78.589702 42.849282, -78.58953 42.849503, -78.589547 42.84987, -78.589637 42.84994, -78.590134 42.849973, -78.590078 42.850613, -78.59029 42.850754, -78.590969 42.850883, -78.59102 42.851181, -78.59126 42.851392, -78.59119 42.851597, -78.590935 42.851752, -78.58991 42.85173, -78.589751 42.851818, -78.589535 42.851768, -78.58939 42.851513, -78.589078 42.85153, -78.58894 42.85187, -78.588948 42.852465, -78.589503 42.852591, -78.589858 42.853079, -78.589863 42.853106, -78.589909 42.853354, -78.589357 42.85396, -78.589373 42.85435, -78.589979 42.854751, -78.59061 42.855336, -78.590748 42.855774, -78.590747 42.856102)), ((-79.066055 43.078892, -79.066132 43.078888, -79.066315 43.078934, -79.066467 43.078953, -79.066605 43.078949, -79.06662 43.078892, -79.066475 43.0788, -79.066059 43.078571, -79.066022 43.078556, -79.065948 43.078527, -79.065748 43.078476, -79.065506 43.078426, -79.06518 43.078443, -79.065056 43.078449, -79.065037 43.078471, -79.065048 43.078518, -79.065084 43.078533, -79.065605 43.078671, -79.065712 43.078758, -79.065758 43.078827, -79.065819 43.078873, -79.065933 43.078896, -79.066055 43.078892)), ((-78.621375 42.809975, -78.621366 42.808126, -78.621367 42.806067, -78.62098 42.805408, -78.620738 42.804996, -78.619561 42.805036, -78.619487 42.805044, -78.61881 42.805122, -78.618924 42.806031, -78.618952 42.806114, -78.618966 42.806155, -78.619078 42.806248, -78.619237 42.806315, -78.619436 42.806359, -78.620161 42.806518, -78.620282 42.806612, -78.620213 42.806793, -78.620064 42.806934, -78.619858 42.807129, -78.619968 42.80752, -78.620567 42.808104, -78.620539 42.808836, -78.621073 42.809487, -78.621375 42.809975)), ((-78.552338 42.883628, -78.552422 42.882568, -78.556521 42.882479, -78.556643 42.882461, -78.556725 42.882436, -78.556799 42.882401, -78.556877 42.882348, -78.556938 42.882254, -78.556963 42.882182, -78.556966 42.882164, -78.55697 42.88214, -78.556963 42.881991, -78.556936 42.881588, -78.556929 42.881547, -78.556895 42.881447, -78.556839 42.881353, -78.556779 42.881283, -78.556415 42.880973, -78.555066 42.879847, -78.555004 42.879788, -78.554951 42.879725, -78.554894 42.879636, -78.554854 42.879541, -78.554833 42.879444, -78.554824 42.879122, -78.556847 42.879124, -78.556964 42.879123, -78.557816 42.879119, -78.55797 42.879129, -78.558084 42.879146, -78.55821 42.878974, -78.558616 42.878471, -78.558547 42.877689, -78.558296 42.8772, -78.559997 42.877188, -78.561682 42.877171, -78.561801 42.876272, -78.561993 42.874814, -78.562047 42.874406, -78.562133 42.873012, -78.56252 42.86806, -78.556085 42.868261, -78.556091 42.868302, -78.556101 42.868439, -78.556099 42.868575, -78.556083 42.868711, -78.556049 42.868871, -78.556003 42.868968, -78.555919 42.869108, -78.555815 42.869241, -78.555693 42.869365, -78.554975 42.869984, -78.553392 42.871329, -78.552766 42.87185, -78.552693 42.871954, -78.552523 42.872171, -78.552508 42.872191, -78.552149 42.872849, -78.552027 42.873095, -78.552106 42.873755, -78.552326 42.875954, -78.552478 42.877211, -78.551448 42.877234, -78.551209 42.877239, -78.550727 42.877244, -78.548491 42.877288, -78.547533 42.877302, -78.539583 42.877504, -78.539203 42.87751, -78.537999 42.877528, -78.538029 42.879107, -78.538064 42.880989, -78.538065 42.881028, -78.538004 42.881279, -78.53794 42.881432, -78.537627 42.881901, -78.537432 42.882113, -78.537228 42.882292, -78.536835 42.882564, -78.536964 42.882726, -78.538092 42.883299, -78.539083 42.88419, -78.539604 42.884384, -78.541211 42.884648, -78.541331 42.884765, -78.541346 42.885177, -78.541433 42.885339, -78.543842 42.885001, -78.544728 42.884631, -78.545714 42.884081, -78.547204 42.883381, -78.548085 42.883125, -78.54933 42.883129, -78.551863 42.883572, -78.552338 42.883628)), ((-79.066823 43.077443, -79.06683 43.077403, -79.06679 43.07735, -79.066709 43.077283, -79.066542 43.077256, -79.065764 43.077216, -79.065747 43.077219, -79.065523 43.077263, -79.065396 43.077316, -79.065335 43.077397, -79.065335 43.077497, -79.065422 43.077578, -79.065624 43.077624, -79.065691 43.077645, -79.065778 43.077638, -79.065844 43.0776, -79.065912 43.077557, -79.066046 43.07749, -79.066126 43.07749, -79.06618 43.077484, -79.066207 43.077464, -79.066267 43.07743, -79.066562 43.07745, -79.066763 43.077464, -79.066823 43.077443)), ((-78.687107 42.695254, -78.687295 42.695073, -78.687351 42.694826, -78.687115 42.695023, -78.687107 42.695254)), ((-78.577597 42.856299, -78.577595 42.854851, -78.575216 42.854832, -78.575066 42.854887, -78.575027 42.855001, -78.575041 42.856334, -78.577597 42.856299)), ((-78.851035 43.077546, -78.851044 43.078518, -78.85104 43.078874, -78.851034 43.079477, -78.851031 43.079711, -78.850998 43.08286, -78.850985 43.084189, -78.851089 43.084189, -78.851401 43.084192, -78.851506 43.084193, -78.851553 43.082742, -78.851653 43.079701, -78.851659 43.079515, -78.851697 43.078389, -78.851722 43.07763, -78.851035 43.077546)), ((-78.762806 42.69635, -78.759185 42.696258, -78.758639 42.696244, -78.758244 42.696241, -78.747021 42.69615, -78.747062 42.699657, -78.74707 42.700306, -78.74708 42.701121, -78.747118 42.701009, -78.74721 42.700788, -78.747322 42.700572, -78.747398 42.700451, -78.747453 42.700363, -78.747558 42.700261, -78.747571 42.700248, -78.747699 42.700139, -78.747837 42.700037, -78.747984 42.699942, -78.748139 42.699854, -78.748301 42.699775, -78.748471 42.699703, -78.748647 42.699641, -78.748828 42.699586, -78.749013 42.699541, -78.749202 42.699506, -78.749394 42.699479, -78.749588 42.699463, -78.75185 42.699415, -78.755728 42.699333, -78.756076 42.699286, -78.756421 42.699231, -78.756765 42.699168, -78.757105 42.699097, -78.757442 42.699019, -78.757776 42.698932, -78.758106 42.698839, -78.758431 42.698737, -78.758753 42.698628, -78.759069 42.698512, -78.759381 42.698389, -78.759687 42.698258, -78.759988 42.698121, -78.760283 42.697977, -78.760571 42.697826, -78.760853 42.697669, -78.761128 42.697505, -78.761378 42.697336, -78.761534 42.697227, -78.761866 42.69701, -78.762342 42.696684, -78.762806 42.69635)), ((-78.681673 42.723792, -78.681675 42.723585, -78.681674 42.722517, -78.681672 42.72145, -78.68169 42.718077, -78.681694 42.717186, -78.681699 42.71514, -78.681696 42.711375, -78.68229 42.710238, -78.684221 42.706475, -78.684765 42.705416, -78.685661 42.703856, -78.685916 42.703478, -78.687729 42.700786, -78.688469 42.699246, -78.689012 42.696235, -78.689095 42.695778, -78.686613 42.695728, -78.687107 42.695254, -78.686941 42.695005, -78.686823 42.695115, -78.68671 42.695227, -78.686581 42.695282, -78.686464 42.695365, -78.686308 42.695477, -78.685912 42.695698, -78.683765 42.695596, -78.682589 42.695559, -78.679306 42.695347, -78.679498 42.695387, -78.679637 42.695429, -78.679768 42.695482, -78.679852 42.695522, -78.679973 42.695615, -78.680053 42.695691, -78.680332 42.696008, -78.681836 42.697775, -78.682669 42.698771, -78.682726 42.698882, -78.682768 42.698996, -78.682793 42.699113, -78.682803 42.699232, -78.682789 42.699389, -78.68276 42.699506, -78.682714 42.69962, -78.682132 42.700878, -78.681742 42.701541, -78.681849 42.701586, -78.682098 42.701669, -78.682517 42.701807, -78.681743 42.7031, -78.681507 42.703511, -78.681209 42.704064, -78.681171 42.704169, -78.681146 42.704276, -78.681137 42.704384, -78.681141 42.704492, -78.68117 42.704635, -78.681715 42.705973, -78.681769 42.706166, -78.681769 42.706489, -78.681771 42.707163, -78.681771 42.70743, -78.681695 42.708753, -78.681695 42.709022, -78.679786 42.713989, -78.679728 42.714156, -78.679677 42.714325, -78.679634 42.714496, -78.679598 42.714667, -78.679571 42.714838, -78.679551 42.715011, -78.679539 42.715184, -78.679535 42.715357, -78.679538 42.71553, -78.679324 42.718298, -78.679219 42.71947, -78.679098 42.720608, -78.679105 42.720903, -78.679134 42.721261, -78.67928 42.721696, -78.679429 42.721961, -78.679559 42.722147, -78.679659 42.722256, -78.6802 42.722831, -78.680755 42.723378, -78.680963 42.723556, -78.680996 42.723591, -78.681158 42.723809, -78.681338 42.724083, -78.681529 42.724526, -78.681601 42.724802, -78.681643 42.725058, -78.681658 42.726133, -78.681652 42.726986, -78.681645 42.728064, -78.688025 42.728171, -78.689425 42.728187, -78.689077 42.727922, -78.686521 42.726566, -78.684225 42.725312, -78.682114 42.72416, -78.681753 42.723878, -78.681673 42.723792)), ((-78.696933 43.059234, -78.69677 43.059381, -78.696426 43.059419, -78.696243 43.059301, -78.696229 43.058844, -78.695895 43.058608, -78.69536 43.058712, -78.694592 43.059223, -78.693844 43.060054, -78.692489 43.060393, -78.692261 43.060663, -78.692283 43.060915, -78.693862 43.061221, -78.694351 43.061505, -78.694341 43.06178, -78.693578 43.062153, -78.692421 43.062222, -78.69153 43.06186, -78.690784 43.061777, -78.690262 43.061561, -78.689476 43.061705, -78.688979 43.061649, -78.688051 43.061447, -78.687397 43.061411, -78.68728 43.061203, -78.686694 43.060862, -78.686609 43.06474, -78.684649 43.064675, -78.683756 43.064682, -78.683651 43.069671, -78.683617 43.071897, -78.683595 43.071965, -78.683543 43.072035, -78.681856 43.071977, -78.681777 43.073937, -78.681533 43.079896, -78.681808 43.080269, -78.681419 43.080383, -78.681267 43.080452, -78.680977 43.080616, -78.680641 43.080879, -78.680557 43.080959, -78.680473 43.081089, -78.68026 43.081448, -78.680176 43.081707, -78.68013 43.081844, -78.680069 43.082218, -78.680069 43.082401, -78.680107 43.083, -78.680115 43.083218, -78.680115 43.083759, -78.680092 43.083996, -78.680031 43.084213, -78.679955 43.084408, -78.679855 43.084538, -78.679749 43.084625, -78.679512 43.084759, -78.679253 43.084839, -78.679001 43.084866, -78.678764 43.084873, -78.678413 43.084858, -78.678154 43.084827, -78.67794 43.084766, -78.677206 43.084345, -78.676888 43.084213, -78.676654 43.084133, -78.676346 43.084042, -78.676239 43.084003, -78.676071 43.083996, -78.675514 43.083942, -78.675244 43.083922, -78.674518 43.083941, -78.673973 43.083996, -78.673248 43.084122, -78.671844 43.084423, -78.671028 43.084595, -78.670029 43.084782, -78.669617 43.084839, -78.667647 43.085112, -78.667318 43.085168, -78.667062 43.085238, -78.666733 43.085364, -78.666756 43.085533, -78.666779 43.085696, -78.666864 43.086033, -78.666907 43.086199, -78.664859 43.086678, -78.66472 43.086711, -78.664492 43.086739, -78.664132 43.086748, -78.663645 43.086719, -78.66291 43.086658, -78.658572 43.086338, -78.656475 43.086184, -78.656156 43.086176, -78.655734 43.086217, -78.655579 43.086241, -78.655199 43.086342, -78.65405 43.086742, -78.652141 43.087427, -78.651872 43.087524, -78.651711 43.087589, -78.651132 43.087826, -78.650883 43.087963, -78.650459 43.088217, -78.649232 43.089012, -78.647825 43.089928, -78.647467 43.090119, -78.646806 43.090408, -78.645789 43.090795, -78.645116 43.091064, -78.643768 43.091581, -78.643562 43.091679, -78.643362 43.091793, -78.643167 43.091928, -78.642995 43.09207, -78.642836 43.092221, -78.642702 43.092376, -78.642512 43.092661, -78.642435 43.092816, -78.642365 43.092995, -78.642317 43.09317, -78.6423 43.093284, -78.642286 43.093586, -78.642301 43.093753, -78.642596 43.095129, -78.642669 43.095466, -78.642771 43.095847, -78.642868 43.096356, -78.642878 43.096639, -78.642859 43.096772, -78.642791 43.096955, -78.642669 43.097173, -78.642552 43.097316, -78.642395 43.097464, -78.642274 43.09755, -78.642023 43.097703, -78.640862 43.09835, -78.640644 43.098464, -78.641368 43.099154, -78.641615 43.099393, -78.642896 43.100637, -78.643112 43.100816, -78.643299 43.100954, -78.643444 43.101044, -78.643804 43.101222, -78.6441 43.101337, -78.644334 43.1014, -78.644574 43.101452, -78.644788 43.101488, -78.644928 43.101502, -78.645134 43.101525, -78.646269 43.101599, -78.647776 43.101683, -78.648364 43.101728, -78.652622 43.102, -78.652614 43.103842, -78.652616 43.108584, -78.652609 43.110647, -78.652677 43.110673, -78.652788 43.110714, -78.65289 43.110625, -78.652961 43.11054, -78.65306 43.110463, -78.653166 43.110413, -78.653811 43.11001, -78.654059 43.109917, -78.654527 43.109797, -78.654806 43.109682, -78.654863 43.109601, -78.654914 43.109463, -78.655133 43.109209, -78.655241 43.109064, -78.655494 43.108615, -78.655813 43.107843, -78.655947 43.107631, -78.656185 43.107333, -78.656326 43.107139, -78.656503 43.107018, -78.65678 43.106905, -78.657205 43.106767, -78.657687 43.106696, -78.657789 43.106634, -78.65782 43.106564, -78.657836 43.106444, -78.657888 43.106301, -78.657924 43.106277, -78.657973 43.106273, -78.65862 43.106303, -78.658712 43.106296, -78.658776 43.106275, -78.658981 43.105994, -78.658986 43.10597, -78.658955 43.105892, -78.658957 43.105838, -78.659022 43.105812, -78.659181 43.10583, -78.65923 43.105822, -78.659246 43.105789, -78.659233 43.105677, -78.659246 43.105656, -78.659282 43.105635, -78.659386 43.105627, -78.659581 43.105659, -78.659649 43.105648, -78.65967 43.105633, -78.659757 43.105492, -78.65991 43.105351, -78.66009 43.10522, -78.660236 43.105086, -78.660331 43.105022, -78.660434 43.104983, -78.660618 43.10494, -78.660879 43.104569, -78.661162 43.104073, -78.66137 43.10376, -78.661919 43.1039, -78.662642 43.1041, -78.66287 43.104177, -78.663425 43.104391, -78.663786 43.104558, -78.664068 43.104709, -78.664185 43.104772, -78.664564 43.105004, -78.664859 43.105214, -78.665938 43.10605, -78.666394 43.106404, -78.666585 43.106259, -78.666801 43.106116, -78.666887 43.106049, -78.667028 43.105905, -78.667149 43.105715, -78.667209 43.105536, -78.667214 43.105466, -78.667223 43.105355, -78.667203 43.103169, -78.667203 43.102226, -78.667197 43.101587, -78.667186 43.100294, -78.667309 43.100208, -78.667568 43.100081, -78.667773 43.100003, -78.667965 43.099946, -78.668241 43.099918, -78.668591 43.09994, -78.668829 43.099932, -78.669686 43.09996, -78.670147 43.09996, -78.670352 43.099989, -78.670875 43.100106, -78.671213 43.100138, -78.671299 43.100137, -78.671606 43.100135, -78.671915 43.100052, -78.672134 43.099876, -78.672262 43.099755, -78.672361 43.099592, -78.672496 43.099026, -78.672609 43.098651, -78.672701 43.098523, -78.672949 43.09841, -78.673091 43.09841, -78.673604 43.098481, -78.674101 43.098523, -78.674503 43.098507, -78.674818 43.098436, -78.675068 43.098293, -78.67539 43.09795, -78.675656 43.097858, -78.676229 43.09773, -78.676435 43.09773, -78.676676 43.097759, -78.677285 43.097851, -78.677583 43.097851, -78.677838 43.097836, -78.678079 43.097773, -78.678291 43.097659, -78.678504 43.097447, -78.679067 43.096923, -78.679379 43.096711, -78.679747 43.096399, -78.680059 43.096314, -78.68035 43.096293, -78.680555 43.096229, -78.680966 43.095967, -78.681136 43.095847, -78.681271 43.095713, -78.681455 43.095281, -78.68154 43.095153, -78.681592 43.095108, -78.681682 43.095033, -78.681845 43.094983, -78.682092 43.094941, -78.682277 43.094955, -78.682404 43.095012, -78.682539 43.095111, -78.682666 43.095146, -78.682815 43.095125, -78.682914 43.095047, -78.683092 43.094863, -78.683658 43.094481, -78.683885 43.094424, -78.684034 43.094438, -78.684303 43.09458, -78.684501 43.094629, -78.684714 43.094615, -78.684856 43.094537, -78.684976 43.094367, -78.685075 43.094176, -78.685122 43.09404, -78.685153 43.09395, -78.685183 43.093889, -78.685252 43.093751, -78.685359 43.093624, -78.685479 43.093596, -78.685522 43.093605, -78.685635 43.093631, -78.685675 43.093657, -78.685952 43.093688, -78.686372 43.093737, -78.686786 43.093784, -78.687064 43.093817, -78.687095 43.093709, -78.687131 43.093661, -78.687144 43.093645, -78.687215 43.093624, -78.687307 43.093617, -78.687501 43.093654, -78.687628 43.093691, -78.687794 43.09374, -78.687927 43.093623, -78.68813 43.093445, -78.688326 43.093272, -78.688459 43.093156, -78.688347 43.092906, -78.6883 43.092751, -78.688319 43.092582, -78.688348 43.092466, -78.688518 43.092218, -78.688653 43.092126, -78.688736 43.092095, -78.688809 43.092069, -78.689782 43.091615, -78.690339 43.09117, -78.690793 43.09083, -78.690906 43.090802, -78.691019 43.090809, -78.691431 43.090924, -78.691651 43.090962, -78.692073 43.091013, -78.692169 43.091021, -78.692556 43.091056, -78.692711 43.091102, -78.693128 43.091079, -78.693439 43.09107, -78.693575 43.091048, -78.69364 43.091038, -78.693761 43.091018, -78.693969 43.090931, -78.694009 43.090915, -78.694085 43.090857, -78.694564 43.09049, -78.694611 43.090455, -78.694736 43.090384, -78.694998 43.090257, -78.695239 43.090193, -78.695465 43.090122, -78.695706 43.090108, -78.69629 43.09018, -78.696889 43.090254, -78.696889 43.089753, -78.696891 43.088809, -78.696895 43.08825, -78.696899 43.08775, -78.696895 43.087467, -78.69689 43.087092, -78.6969 43.086619, -78.696906 43.086337, -78.696904 43.086287, -78.696865 43.085096, -78.696866 43.08386, -78.696866 43.082764, -78.696866 43.082344, -78.696867 43.080089, -78.696885 43.073923, -78.696897 43.073073, -78.696905 43.071345, -78.696896 43.067773, -78.696895 43.065965, -78.696909 43.063041, -78.696914 43.062215, -78.696933 43.059234)), ((-78.817999 42.711318, -78.817694 42.711175, -78.817382 42.710763, -78.816448 42.709527, -78.815115 42.708632, -78.814469 42.70846, -78.813568 42.708511, -78.812591 42.70895, -78.811872 42.709097, -78.81171 42.7093, -78.811692 42.710695, -78.811337 42.7111, -78.811115 42.711233, -78.810836 42.711227, -78.810699 42.711591, -78.810253 42.711926, -78.809632 42.711937, -78.809348 42.712091, -78.808939 42.713136, -78.808644 42.713308, -78.806737 42.712602, -78.806384 42.712465, -78.805746 42.712217, -78.805492 42.712103, -78.80461 42.711634, -78.804063 42.711273, -78.803752 42.711012, -78.803371 42.710643, -78.802602 42.709914, -78.801848 42.709187, -78.801674 42.709019, -78.801208 42.70857, -78.800106 42.707562, -78.80003 42.707492, -78.799024 42.706712, -78.798187 42.706142, -78.797417 42.705617, -78.79662 42.705063, -78.795145 42.70398, -78.794839 42.703754, -78.794494 42.703357, -78.794203 42.703024, -78.793327 42.7019, -78.792556 42.700911, -78.792532 42.700882, -78.791762 42.699964, -78.791264 42.699344, -78.791089 42.699166, -78.790125 42.698262, -78.788867 42.696964, -78.788306 42.696553, -78.787353 42.695915, -78.786827 42.695562, -78.78626 42.695182, -78.785853 42.694917, -78.785502 42.694688, -78.78512 42.694439, -78.78554 42.694535, -78.785929 42.694625, -78.786008 42.694426, -78.786381 42.693116, -78.786644 42.692372, -78.787002 42.69131, -78.787108 42.690994, -78.787252 42.690562, -78.787451 42.689882, -78.787718 42.689139, -78.787971 42.688204, -78.788045 42.687565, -78.788104 42.687001, -78.788164 42.686214, -78.788104 42.685145, -78.788015 42.684461, -78.787807 42.683585, -78.787539 42.68284, -78.78735 42.682352, -78.787146 42.681906, -78.786857 42.68139, -78.786534 42.680833, -78.786244 42.680403, -78.785827 42.679827, -78.78512 42.679003, -78.784607 42.678453, -78.783926 42.677774, -78.783908 42.677791, -78.783842 42.677856, -78.783567 42.67813, -78.783544 42.678153, -78.782583 42.679113, -78.781963 42.679712, -78.781541 42.680107, -78.780086 42.681554, -78.779691 42.681989, -78.779307 42.68243, -78.778934 42.682877, -78.778987 42.682877, -78.778651 42.683912, -78.778487 42.684211, -78.778377 42.684338, -78.778261 42.684441, -78.777702 42.684197, -78.776708 42.683766, -78.776092 42.683513, -78.774122 42.682739, -78.772468 42.681641, -78.772219 42.68147, -78.771953 42.681275, -78.771827 42.681173, -78.771588 42.680961, -78.771475 42.68085, -78.771265 42.680622, -78.770832 42.680088, -78.769707 42.678734, -78.769158 42.678033, -78.76986 42.677712, -78.770561 42.67739, -78.770067 42.676808, -78.770279 42.676624, -78.770311 42.676598, -78.770385 42.676556, -78.77047 42.676525, -78.770576 42.676506, -78.772292 42.676511, -78.773331 42.676515, -78.774196 42.676435, -78.774693 42.676442, -78.77507 42.676433, -78.775087 42.676418, -78.775188 42.67642, -78.775299 42.676411, -78.775406 42.676391, -78.775509 42.676361, -78.775628 42.676308, -78.775713 42.676255, -78.775804 42.676178, -78.775863 42.676109, -78.775908 42.676035, -78.77594 42.675956, -78.77595 42.675945, -78.775958 42.675928, -78.775983 42.675326, -78.776007 42.674738, -78.776017 42.674481, -78.776025 42.674434, -78.776019 42.674362, -78.775994 42.674293, -78.77595 42.67423, -78.775889 42.674174, -78.775814 42.674128, -78.775647 42.674081, -78.775336 42.674011, -78.775019 42.673958, -78.774358 42.673833, -78.773702 42.673696, -78.773391 42.673616, -78.773087 42.67352, -78.772792 42.67341, -78.772367 42.673227, -78.772219 42.67318, -78.772065 42.673143, -78.771907 42.673119, -78.771747 42.673106, -78.771586 42.673106, -78.771425 42.673117, -78.771268 42.673142, -78.771062 42.673186, -78.770861 42.673241, -78.770665 42.673305, -78.770476 42.673379, -78.77014 42.673534, -78.76998 42.673597, -78.769815 42.673652, -78.769644 42.673699, -78.769382 42.673753, -78.769204 42.673778, -78.768706 42.673817, -78.768388 42.673835, -78.768069 42.673848, -78.76775 42.673856, -78.767431 42.673858, -78.766793 42.673846, -78.766773 42.673702, -78.76674 42.673559, -78.766562 42.673, -78.766346 42.672324, -78.766106 42.671564, -78.765974 42.671237, -78.765891 42.67106, -78.765684 42.670584, -78.765594 42.670416, -78.765134 42.669608, -78.764779 42.669196, -78.76366 42.668109, -78.763485 42.667939, -78.763268 42.667716, -78.762199 42.666711, -78.761775 42.666247, -78.761731 42.666222, -78.761297 42.6658, -78.761229 42.665704, -78.761139 42.665717, -78.76078 42.665767, -78.760006 42.665729, -78.759236 42.6656, -78.758993 42.665458, -78.759286 42.665244, -78.759423 42.665108, -78.759562 42.664969, -78.759517 42.664485, -78.759231 42.664086, -78.758956 42.663834, -78.759051 42.663644, -78.758931 42.663484, -78.758448 42.663035, -78.758118 42.662739, -78.757283 42.662319, -78.756283 42.661929, -78.755343 42.661738, -78.754992 42.661545, -78.754585 42.661347, -78.754501 42.661307, -78.754152 42.661037, -78.754148 42.660675, -78.754183 42.660399, -78.754288 42.659953, -78.754329 42.659785, -78.754509 42.659057, -78.754832 42.658206, -78.754974 42.657891, -78.755616 42.65789, -78.755882 42.658024, -78.755843 42.657923, -78.755733 42.65776, -78.755691 42.657689, -78.755535 42.657425, -78.755448 42.657254, -78.755369 42.657081, -78.75526 42.656812, -78.755199 42.656622, -78.755193 42.656597, -78.755155 42.656428, -78.755129 42.656234, -78.755097 42.656113, -78.755018 42.655875, -78.75497 42.655757, -78.754857 42.655526, -78.754724 42.655301, -78.75457 42.655084, -78.754397 42.654874, -78.754303 42.654773, -78.754129 42.654569, -78.753961 42.654363, -78.753799 42.654155, -78.753494 42.65373, -78.753446 42.653657, -78.753351 42.653514, -78.753214 42.653296, -78.753107 42.653113, -78.752978 42.652924, -78.752825 42.652731, -78.752656 42.652546, -78.752471 42.652369, -78.752342 42.65226, -78.752219 42.652147, -78.752101 42.652031, -78.75188 42.651791, -78.751778 42.651667, -78.751682 42.651541, -78.751506 42.651282, -78.751427 42.651149, -78.75138 42.651044, -78.751313 42.650946, -78.751229 42.650855, -78.751129 42.650773, -78.751015 42.650702, -78.750889 42.650643, -78.750753 42.650597, -78.750565 42.650564, -78.750193 42.650485, -78.750045 42.650446, -78.74983 42.650374, -78.749699 42.650318, -78.749583 42.650253, -78.74948 42.650177, -78.749392 42.650092, -78.749337 42.650022, -78.749278 42.649923, -78.749189 42.649694, -78.749112 42.649468, -78.749046 42.649323, -78.748961 42.649184, -78.748858 42.649052, -78.748738 42.648928, -78.748656 42.648858, -78.7483 42.648554, -78.748084 42.648368, -78.747919 42.648196, -78.747779 42.648013, -78.747701 42.647885, -78.747634 42.647754, -78.74758 42.647619, -78.747538 42.647482, -78.74751 42.647343, -78.747304 42.647348, -78.74709 42.646621, -78.747159 42.646309, -78.74709 42.645808, -78.746986 42.645254, -78.746882 42.644408, -78.745957 42.644337, -78.745642 42.644338, -78.745159 42.644366, -78.74471 42.644436, -78.744547 42.644461, -78.743784 42.644579, -78.743755 42.644481, -78.743542 42.644137, -78.74304 42.643384, -78.742662 42.642997, -78.742185 42.642897, -78.741743 42.642792, -78.741491 42.642722, -78.741227 42.642515, -78.740979 42.642314, -78.740796 42.641992, -78.740738 42.641624, -78.740794 42.641377, -78.74088 42.641206, -78.741147 42.6411, -78.741466 42.641086, -78.741882 42.641077, -78.742273 42.640876, -78.742777 42.640713, -78.74313 42.640507, -78.743364 42.640198, -78.743507 42.639861, -78.743511 42.639636, -78.743389 42.639418, -78.743055 42.639402, -78.742727 42.639385, -78.742393 42.639426, -78.742123 42.639275, -78.742059 42.639001, -78.741949 42.638579, -78.741778 42.638092, -78.741503 42.637627, -78.741384 42.637123, -78.74143 42.636706, -78.74144 42.636376, -78.74143 42.636228, -78.741317 42.636086, -78.741182 42.636016, -78.740862 42.636024, -78.740595 42.636048, -78.740416 42.636039, -78.740244 42.635985, -78.740116 42.635888, -78.740084 42.635718, -78.740169 42.635426, -78.740382 42.635227, -78.740654 42.635071, -78.740899 42.634997, -78.74124 42.634967, -78.74153 42.634932, -78.741699 42.634804, -78.741852 42.634594, -78.741864 42.634424, -78.741892 42.634287, -78.741665 42.63402, -78.741377 42.633703, -78.741263 42.633612, -78.741086 42.63338, -78.740913 42.63301, -78.740877 42.6323, -78.741202 42.631003, -78.741209 42.630586, -78.741226 42.629494, -78.740898 42.628376, -78.740786 42.627992, -78.740593 42.627692, -78.740292 42.627242, -78.740067 42.626772, -78.739925 42.626517, -78.739562 42.626462, -78.739175 42.626456, -78.738993 42.626586, -78.738672 42.626899, -78.738315 42.627152, -78.738068 42.627237, -78.737527 42.62713, -78.737158 42.627014, -78.736961 42.626868, -78.736938 42.626615, -78.736981 42.626413, -78.737097 42.626266, -78.737418 42.625947, -78.737483 42.625733, -78.737391 42.625567, -78.737187 42.625428, -78.736652 42.625073, -78.735794 42.624821, -78.735205 42.62457, -78.734683 42.62437, -78.734458 42.624147, -78.734442 42.623932, -78.734545 42.623664, -78.734643 42.623347, -78.734711 42.623012, -78.73472 42.622683, -78.734666 42.622529, -78.734575 42.622268, -78.73452 42.622086, -78.734443 42.621898, -78.734412 42.621694, -78.73439 42.621649, -78.733557 42.621366, -78.733614 42.621467, -78.733735 42.62155, -78.73378 42.621639, -78.733783 42.621814, -78.733732 42.621945, -78.733637 42.622161, -78.733436 42.622358, -78.733338 42.622517, -78.733293 42.622705, -78.733303 42.622937, -78.732828 42.62306, -78.731936 42.623185, -78.731641 42.623211, -78.731575 42.623216, -78.728871 42.623399, -78.728338 42.623435, -78.727914 42.623464, -78.726239 42.623633, -78.72607 42.623675, -78.725364 42.623854, -78.724105 42.624046, -78.724655 42.624794, -78.725621 42.626188, -78.727204 42.628711, -78.72751 42.62927, -78.727575 42.629296, -78.727614 42.629289, -78.727658 42.629351, -78.727791 42.629595, -78.728461 42.630831, -78.728857 42.631627, -78.728973 42.631626, -78.72929 42.632286, -78.729381 42.632487, -78.731419 42.636959, -78.732345 42.639069, -78.732876 42.640312, -78.733408 42.641638, -78.733466 42.641819, -78.733767 42.642751, -78.734245 42.643951, -78.734472 42.644675, -78.734505 42.644779, -78.734885 42.64571, -78.735175 42.646549, -78.735436 42.647288, -78.73551 42.647497, -78.735897 42.648363, -78.736286 42.649051, -78.736365 42.64919, -78.736908 42.650054, -78.737506 42.650727, -78.737741 42.651169, -78.738116 42.651858, -78.738279 42.651839, -78.739203 42.652991, -78.739197 42.653057, -78.739217 42.653266, -78.739335 42.653242, -78.739392 42.653226, -78.739471 42.653325, -78.741202 42.655312, -78.741243 42.655312, -78.741577 42.655702, -78.742082 42.656322, -78.742741 42.657114, -78.743369 42.657804, -78.744152 42.658666, -78.746523 42.661341, -78.746667 42.661536, -78.747998 42.663345, -78.748096 42.663478, -78.748854 42.664544, -78.749345 42.665233, -78.749336 42.66533, -78.74974 42.665892, -78.75103 42.667791, -78.751084 42.667872, -78.75109 42.66789, -78.751843 42.669071, -78.751968 42.669073, -78.754807 42.673262, -78.755074 42.673828, -78.755134 42.673956, -78.75577 42.675302, -78.755732 42.675426, -78.755836 42.675622, -78.756704 42.677326, -78.756897 42.677704, -78.757085 42.678075, -78.757175 42.678254, -78.757488 42.6789, -78.757902 42.679413, -78.75852 42.679987, -78.758597 42.680028, -78.759127 42.680469, -78.762192 42.682858, -78.763955 42.684187, -78.765326 42.685221, -78.765525 42.685416, -78.765732 42.685622, -78.769775 42.68965, -78.770104 42.689828, -78.770593 42.690102, -78.770956 42.690306, -78.770981 42.690327, -78.770758 42.690586, -78.77052 42.690788, -78.770292 42.690997, -78.770073 42.69121, -78.769847 42.691406, -78.769554 42.691653, -78.76746 42.69283, -78.766012 42.693649, -78.765645 42.693887, -78.764844 42.69472, -78.764291 42.695246, -78.762806 42.69635, -78.764919 42.696192, -78.76741 42.696225, -78.768441 42.69624, -78.770909 42.696275, -78.776538 42.696357, -78.777504 42.696357, -78.777531 42.696357, -78.781824 42.696359, -78.782052 42.696359, -78.782571 42.696398, -78.783091 42.696436, -78.783173 42.696442, -78.783614 42.696441, -78.783646 42.697592, -78.784947 42.698751, -78.785195 42.69893, -78.785415 42.699088, -78.785474 42.699131, -78.785686 42.699284, -78.78824 42.701123, -78.788899 42.701598, -78.790243 42.702565, -78.790653 42.702859, -78.795953 42.706659, -78.797777 42.707996, -78.798344 42.708334, -78.798412 42.709852, -78.7986 42.714628, -78.798641 42.715657, -78.798456 42.717204, -78.798453 42.718259, -78.798453 42.718459, -78.797286 42.718435, -78.797434 42.72055, -78.793716 42.72149, -78.792561 42.721523, -78.792731 42.721745, -78.792909 42.721965, -78.793092 42.722182, -78.793262 42.722374, -78.793913 42.722228, -78.7944 42.722489, -78.794802 42.722543, -78.795521 42.722374, -78.795705 42.722446, -78.796268 42.722319, -78.796606 42.722417, -78.797047 42.72222, -78.79781 42.721708, -78.79809 42.721691, -78.798452 42.721777, -78.79821 42.725186, -78.79654 42.725208, -78.795336 42.725552, -78.795795 42.726179, -78.796446 42.727146, -78.797333 42.728409, -78.797794 42.729052, -78.797896 42.729275, -78.797902 42.729324, -78.797911 42.729438, -78.797915 42.729537, -78.797914 42.729598, -78.783396 42.729575, -78.783321 42.733317, -78.783215 42.738711, -78.783152 42.738699, -78.783079 42.738681, -78.778906 42.7379, -78.775921 42.737381, -78.775981 42.734533, -78.775981 42.73407, -78.775325 42.734035, -78.77538 42.73729, -78.769238 42.736254, -78.769002 42.736216, -78.767803 42.736018, -78.766704 42.735838, -78.766489 42.735803, -78.766281 42.735768, -78.76528 42.735604, -78.765209 42.734502, -78.765206 42.733593, -78.765238 42.73297, -78.765301 42.731992, -78.76538 42.731274, -78.765411 42.730801, -78.765382 42.730372, -78.765258 42.730484, -78.764836 42.731002, -78.763962 42.731145, -78.763205 42.731473, -78.76277 42.731511, -78.762556 42.731415, -78.761964 42.731472, -78.761707 42.731719, -78.761115 42.731776, -78.760799 42.73193, -78.759711 42.732, -78.759363 42.732176, -78.758833 42.732235, -78.758619 42.732139, -78.757437 42.73223, -78.756976 42.732107, -78.756032 42.731608, -78.754548 42.730572, -78.753938 42.730285, -78.752109 42.730249, -78.749763 42.730752, -78.749338 42.730866, -78.749139 42.73092, -78.748449 42.731069, -78.747203 42.731181, -78.7453 42.731486, -78.741977 42.731557, -78.741365 42.731316, -78.740465 42.731321, -78.740024 42.731038, -78.73995 42.730991, -78.73962 42.730664, -78.739152 42.730746, -78.738754 42.730578, -78.738133 42.730611, -78.737833 42.730308, -78.737567 42.72996, -78.737432 42.729924, -78.736963 42.729922, -78.732183 42.729892, -78.732179 42.728695, -78.732125 42.727583, -78.731491 42.727616, -78.728268 42.728351, -78.728062 42.728385, -78.727853 42.728402, -78.727712 42.728405, -78.727571 42.728402, -78.727362 42.728383, -78.726933 42.728316, -78.726639 42.728286, -78.726342 42.728271, -78.72588 42.728268, -78.725874 42.724438, -78.725877 42.724196, -78.725856 42.724169, -78.725817 42.724153, -78.723368 42.724096, -78.723281 42.724109, -78.723188 42.724134, -78.723133 42.724154, -78.723054 42.724176, -78.722972 42.724203, -78.722918 42.72422, -78.722846 42.72426, -78.72278 42.724307, -78.722719 42.724363, -78.722667 42.724423, -78.722621 42.724487, -78.722582 42.724556, -78.722575 42.724572, -78.722554 42.72463, -78.722537 42.724709, -78.722512 42.724869, -78.722495 42.72495, -78.722469 42.725028, -78.722391 42.72518, -78.72234 42.725255, -78.722213 42.725402, -78.722135 42.72547, -78.722087 42.725503, -78.722044 42.725532, -78.72195 42.725592, -78.721846 42.725646, -78.721734 42.725692, -78.721613 42.725728, -78.721487 42.725755, -78.721354 42.725774, -78.721215 42.725785, -78.721074 42.72579, -78.720932 42.725792, -78.720882 42.725793, -78.720789 42.725794, -78.720646 42.725795, -78.720503 42.725795, -78.72036 42.725797, -78.720216 42.725797, -78.72007 42.725795, -78.719923 42.725795, -78.719778 42.725794, -78.719633 42.725794, -78.719488 42.725794, -78.719343 42.725794, -78.719202 42.725796, -78.719066 42.725797, -78.718817 42.725799, -78.718704 42.725798, -78.718598 42.725795, -78.718427 42.725792, -78.718364 42.725791, -78.718279 42.725789, -78.718205 42.725786, -78.718131 42.725782, -78.71804 42.725767, -78.71802 42.725765, -78.718016 42.726347, -78.718006 42.727794, -78.717997 42.729242, -78.713588 42.72922, -78.713592 42.729642, -78.713735 42.729965, -78.714541 42.730827, -78.715311 42.731849, -78.715385 42.732377, -78.715899 42.73273, -78.715982 42.733029, -78.716193 42.733194, -78.716682 42.733394, -78.715536 42.733447, -78.715162 42.733457, -78.712222 42.733458, -78.706758 42.733443, -78.705913 42.733441, -78.701832 42.733452, -78.701137 42.733464, -78.700442 42.733487, -78.699748 42.73352, -78.699401 42.733541, -78.699077 42.733551, -78.699031 42.733551, -78.69683 42.733562, -78.69631 42.733575, -78.696245 42.733574, -78.691313 42.733531, -78.687896 42.733501, -78.685945 42.733479, -78.681607 42.733507, -78.681608 42.733428, -78.681614 42.732596, -78.681619 42.731763, -78.681645 42.728064, -78.675384 42.727952, -78.673784 42.727923, -78.672886 42.7279, -78.670372 42.727837, -78.670392 42.728751, -78.670411 42.729047, -78.670343 42.729049, -78.670402 42.729871, -78.670415 42.730069, -78.670471 42.730959, -78.670638 42.733595, -78.679671 42.733919, -78.681605 42.733953, -78.681599 42.73474, -78.685943 42.734736, -78.685944 42.733874, -78.687597 42.734527, -78.688817 42.737683, -78.6913 42.737672, -78.696718 42.737648, -78.69665 42.746159, -78.696941 42.750122, -78.696951 42.750251, -78.693555 42.750162, -78.693494 42.750286, -78.693424 42.750463, -78.69337 42.750643, -78.693331 42.750825, -78.693309 42.751009, -78.693303 42.751193, -78.693337 42.753003, -78.696672 42.753014, -78.696923 42.753011, -78.696944 42.750918, -78.696948 42.750457, -78.696961 42.750515, -78.696982 42.750268, -78.696984 42.750231, -78.69701 42.749923, -78.697062 42.749893, -78.697078 42.749817, -78.697335 42.749811, -78.697526 42.74979, -78.697713 42.749755, -78.697895 42.749705, -78.697978 42.749675, -78.698932 42.749335, -78.699304 42.749926, -78.699375 42.750055, -78.699405 42.750163, -78.699422 42.750309, -78.699412 42.750455, -78.699388 42.750563, -78.699351 42.750886, -78.69935 42.751064, -78.699365 42.751241, -78.699397 42.751417, -78.699445 42.751591, -78.699509 42.751762, -78.699589 42.75193, -78.699607 42.752009, -78.699787 42.752823, -78.701124 42.752712, -78.704919 42.752415, -78.705067 42.753433, -78.705109 42.753753, -78.705143 42.753859, -78.705196 42.753961, -78.705268 42.754057, -78.705357 42.754144, -78.705462 42.754222, -78.70558 42.754288, -78.705709 42.754342, -78.705847 42.754382, -78.705954 42.754402, -78.706094 42.754417, -78.706235 42.754418, -78.706665 42.754375, -78.706992 42.754347, -78.707272 42.754328, -78.707833 42.754306, -78.708671 42.754297, -78.709183 42.754279, -78.709418 42.754264, -78.709652 42.754244, -78.710116 42.754188, -78.710796 42.754077, -78.711012 42.754041, -78.711328 42.754001, -78.711646 42.753966, -78.711965 42.753936, -78.712407 42.753903, -78.71242 42.754, -78.712554 42.754536, -78.712748 42.755171, -78.712719 42.755176, -78.712552 42.755203, -78.712465 42.755217, -78.712376 42.755231, -78.712285 42.755246, -78.712191 42.755262, -78.712094 42.755279, -78.711995 42.755296, -78.711892 42.755314, -78.711785 42.755332, -78.711675 42.75535, -78.71156 42.755367, -78.711442 42.755384, -78.71132 42.7554, -78.711198 42.755416, -78.711039 42.755434, -78.71105 42.755496, -78.711125 42.756011, -78.711104 42.75635, -78.711053 42.756752, -78.711043 42.756851, -78.711041 42.756903, -78.711042 42.757005, -78.711043 42.757055, -78.711043 42.757105, -78.711046 42.757158, -78.711051 42.757212, -78.711056 42.757268, -78.711063 42.757324, -78.711071 42.757382, -78.711079 42.757439, -78.711086 42.757496, -78.711093 42.757552, -78.7111 42.757606, -78.711105 42.75766, -78.711117 42.757709, -78.711137 42.757755, -78.711212 42.757836, -78.711304 42.757828, -78.711392 42.757821, -78.711478 42.757815, -78.711562 42.757809, -78.711724 42.757799, -78.711801 42.757796, -78.711876 42.757793, -78.711952 42.757791, -78.71203 42.757789, -78.712113 42.757788, -78.712201 42.757787, -78.712291 42.757786, -78.71238 42.757786, -78.71247 42.757785, -78.71256 42.757785, -78.712651 42.757787, -78.712743 42.75779, -78.712836 42.757792, -78.713024 42.757808, -78.713117 42.757818, -78.713212 42.757824, -78.713395 42.75782, -78.713475 42.757815, -78.713668 42.757804, -78.713832 42.757795, -78.714009 42.757787, -78.714096 42.757783, -78.714176 42.757777, -78.714239 42.757769, -78.714312 42.757753, -78.714505 42.757739, -78.714595 42.757728, -78.714692 42.757715, -78.71479 42.7577, -78.714887 42.757685, -78.715078 42.757649, -78.715265 42.757609, -78.715354 42.757587, -78.715441 42.757563, -78.715528 42.757537, -78.715615 42.757508, -78.715705 42.757477, -78.715801 42.757441, -78.715902 42.757401, -78.716004 42.757357, -78.7161 42.757313, -78.716187 42.757268, -78.716273 42.757225, -78.716355 42.757181, -78.716432 42.757136, -78.716579 42.757044, -78.716649 42.756998, -78.716777 42.756907, -78.716832 42.756865, -78.716875 42.75683, -78.716917 42.756795, -78.716939 42.756775, -78.71697 42.756749, -78.717014 42.756713, -78.717053 42.756677, -78.717098 42.756633, -78.717148 42.756586, -78.717201 42.756536, -78.717253 42.756484, -78.717306 42.756431, -78.717358 42.756379, -78.717409 42.756328, -78.717457 42.756274, -78.717507 42.756232, -78.717549 42.75619, -78.717602 42.756139, -78.717644 42.7561, -78.717705 42.756041, -78.717753 42.755999, -78.717808 42.755954, -78.717865 42.75591, -78.717923 42.755868, -78.717983 42.755827, -78.718046 42.755786, -78.718093 42.755755, -78.718111 42.755744, -78.718177 42.755703, -78.718242 42.755662, -78.718302 42.755625, -78.71836 42.755591, -78.718446 42.755566, -78.718481 42.755542, -78.718529 42.755503, -78.718576 42.755461, -78.718663 42.755377, -78.718704 42.755337, -78.718742 42.755296, -78.718776 42.755254, -78.718808 42.755211, -78.718838 42.755167, -78.718866 42.755122, -78.718893 42.755076, -78.718938 42.755001, -78.718954 42.754976, -78.71898 42.754939, -78.719016 42.754856, -78.71905 42.754738, -78.719076 42.754481, -78.719074 42.75442, -78.719023 42.754171, -78.719001 42.754111, -78.718976 42.754048, -78.718912 42.753926, -78.718872 42.753867, -78.718831 42.75381, -78.718789 42.753756, -78.718702 42.753655, -78.718659 42.753608, -78.718619 42.753564, -78.718577 42.753517, -78.718535 42.753468, -78.718494 42.753418, -78.718418 42.753317, -78.718384 42.753263, -78.71829 42.753095, -78.718261 42.753036, -78.718236 42.752975, -78.718168 42.752785, -78.718151 42.752719, -78.71811 42.752514, -78.7181 42.752444, -78.718091 42.752375, -78.718082 42.752309, -78.718057 42.752118, -78.718033 42.751931, -78.718027 42.75187, -78.718011 42.75175, -78.717998 42.751636, -78.717985 42.751533, -78.717979 42.751454, -78.717996 42.751378, -78.718545 42.751339, -78.719201 42.751268, -78.719241 42.751264, -78.720551 42.751153, -78.7222 42.75102, -78.722705 42.750976, -78.723732 42.750894, -78.724619 42.750816, -78.725805 42.750826, -78.73005 42.750859, -78.731988 42.752393, -78.73201 42.754125, -78.732019 42.75473, -78.732023 42.755005, -78.732033 42.756158, -78.732028 42.756371, -78.732029 42.756449, -78.732042 42.757528, -78.732036 42.758335, -78.732022 42.759393, -78.732013 42.759975, -78.731982 42.761066, -78.731974 42.761978, -78.731951 42.763012, -78.731932 42.763603, -78.731931 42.763708, -78.730904 42.763698, -78.730824 42.763689, -78.730858 42.761976, -78.725553 42.761913, -78.725482 42.761913, -78.725269 42.76191, -78.725275 42.762472, -78.725049 42.762473, -78.72494 42.762474, -78.723601 42.762486, -78.722175 42.76249, -78.72213 42.762918, -78.721541 42.762906, -78.719409 42.762877, -78.719205 42.762875, -78.71909 42.762884, -78.719 42.762884, -78.718911 42.762886, -78.718724 42.762888, -78.718546 42.762886, -78.71846 42.762885, -78.718286 42.762883, -78.718197 42.762881, -78.718015 42.762875, -78.717924 42.762874, -78.717836 42.762873, -78.717751 42.762872, -78.71767 42.762872, -78.717592 42.762872, -78.717514 42.762873, -78.717439 42.762873, -78.717366 42.762871, -78.717293 42.762866, -78.717222 42.762862, -78.717158 42.76286, -78.717049 42.76285, -78.716977 42.762824, -78.716977 42.76288, -78.716977 42.762936, -78.716977 42.762992, -78.716975 42.763045, -78.716975 42.763097, -78.716976 42.763146, -78.71698 42.76322, -78.716956 42.76331, -78.716969 42.764236, -78.71697 42.764473, -78.716973 42.764897, -78.716995 42.764992, -78.717029 42.765066, -78.71709 42.765151, -78.717152 42.765214, -78.717207 42.765256, -78.717139 42.765293, -78.717076 42.765347, -78.717037 42.765402, -78.717012 42.765471, -78.717011 42.765954, -78.715304 42.76596, -78.715298 42.764402, -78.713216 42.764408, -78.713229 42.766525, -78.713247 42.766576, -78.713282 42.766623, -78.713339 42.766668, -78.713392 42.766693, -78.71346 42.766712, -78.713501 42.766717, -78.715279 42.766704, -78.715328 42.766704, -78.715333 42.769555, -78.715316 42.769661, -78.715278 42.769755, -78.715258 42.769851, -78.715215 42.770764, -78.715205 42.770864, -78.712721 42.770819, -78.711622 42.770788, -78.711502 42.771031, -78.711394 42.771441, -78.711568 42.771788, -78.711568 42.772611, -78.711826 42.773211, -78.71191 42.774311, -78.711996 42.774496, -78.713262 42.775551, -78.713554 42.776037, -78.713636 42.776359, -78.7139 42.776776, -78.715201 42.777717, -78.715812 42.777981, -78.716231 42.778424, -78.71675 42.778663, -78.7167 42.779188, -78.716852 42.77926, -78.716303 42.78069, -78.71635 42.781103, -78.716977 42.781802, -78.717995 42.781982, -78.718146 42.7821, -78.718138 42.782306, -78.718959 42.782802, -78.719512 42.782951, -78.72049 42.783382, -78.721822 42.7835, -78.722219 42.783668, -78.722515 42.784086, -78.723482 42.784815, -78.724128 42.784988, -78.724278 42.785128, -78.724161 42.785789, -78.724825 42.786306, -78.725208 42.786885, -78.725451 42.787027, -78.726497 42.787273, -78.726499 42.788181, -78.726491 42.789133, -78.726311 42.789135, -78.72385 42.789167, -78.717346 42.789258, -78.714328 42.789301, -78.714344 42.790772, -78.714347 42.790977, -78.714373 42.792502, -78.714397 42.794928, -78.714418 42.796454, -78.714416 42.797261, -78.714433 42.801251, -78.714072 42.802619, -78.713916 42.803214, -78.713759 42.803808, -78.713707 42.804008, -78.715131 42.80528, -78.715195 42.805341, -78.720203 42.809775, -78.721916 42.811265, -78.715519 42.811245, -78.715165 42.811245, -78.713679 42.811242, -78.713687 42.809927, -78.711212 42.809924, -78.711201 42.811236, -78.709913 42.811232, -78.708056 42.811224, -78.707581 42.811222, -78.706794 42.811223, -78.705131 42.811215, -78.701352 42.811195, -78.700802 42.811191, -78.697683 42.811198, -78.696915 42.811166, -78.696913 42.811595, -78.697004 42.811891, -78.697161 42.812195, -78.697348 42.812432, -78.697596 42.81266, -78.697309 42.813093, -78.697092 42.813438, -78.697021 42.813707, -78.69689 42.814389, -78.69683 42.81536, -78.696321 42.81538, -78.695567 42.815616, -78.695152 42.815905, -78.694985 42.816222, -78.69505 42.816979, -78.695585 42.818476, -78.695509 42.818864, -78.695281 42.819134, -78.695001 42.819151, -78.694324 42.818954, -78.693472 42.818479, -78.692367 42.818091, -78.691843 42.817989, -78.691156 42.818066, -78.69106 42.818156, -78.690916 42.819364, -78.690641 42.819679, -78.69032 42.819905, -78.690032 42.819995, -78.689603 42.819957, -78.687811 42.819683, -78.68751 42.819663, -78.687027 42.81963, -78.68555 42.819489, -78.684968 42.819515, -78.683547 42.819442, -78.6826 42.819551, -78.681434 42.819587, -78.680433 42.819296, -78.679545 42.818562, -78.679253 42.818696, -78.679055 42.818835, -78.678628 42.818921, -78.678415 42.818802, -78.678212 42.818629, -78.678061 42.81844, -78.677874 42.818228, -78.6778 42.818181, -78.676749 42.817512, -78.676323 42.817307, -78.676063 42.817207, -78.675381 42.818006, -78.674863 42.817772, -78.674523 42.817599, -78.674353 42.817504, -78.673911 42.817233, -78.672193 42.817889, -78.668024 42.817978, -78.667493 42.817992, -78.664101 42.81992, -78.664504 42.820261, -78.665949 42.821485, -78.667289 42.822603, -78.66801 42.823148, -78.668804 42.823766, -78.669634 42.824291, -78.670644 42.824987, -78.671814 42.825692, -78.672861 42.826305, -78.673675 42.826724, -78.675235 42.827548, -78.676504 42.828106, -78.678178 42.828828, -78.678973 42.829122, -78.679826 42.829461, -78.679827 42.829152, -78.679828 42.82789, -78.679814 42.824711, -78.679778 42.822381, -78.680818 42.822392, -78.681712 42.822408, -78.681888 42.822402, -78.681953 42.822392, -78.682027 42.82237, -78.682133 42.822319, -78.682221 42.822258, -78.68241 42.822373, -78.682928 42.822579, -78.684859 42.820862, -78.685205 42.820571, -78.685442 42.82017, -78.685542 42.819943, -78.69065 42.820759, -78.691336 42.82114, -78.691352 42.824125, -78.691354 42.826208, -78.690476 42.826008, -78.690227 42.825943, -78.689861 42.825829, -78.689585 42.825732, -78.689086 42.82552, -78.688953 42.825459, -78.688686 42.825326, -78.688669 42.826982, -78.688628 42.830732, -78.689098 42.830732, -78.690414 42.830733, -78.692238 42.830734, -78.692212 42.827937, -78.692219 42.826403, -78.694746 42.826976, -78.694856 42.827002, -78.695613 42.827155, -78.696934 42.827431, -78.696947 42.830313, -78.696954 42.831906, -78.696967 42.832639, -78.69697 42.832801, -78.696979 42.833093, -78.69697 42.833275, -78.696942 42.833866, -78.696934 42.834029, -78.69693 42.834388, -78.696922 42.835241, -78.69692 42.835958, -78.696919 42.836661, -78.696918 42.836883, -78.696918 42.836919, -78.694924 42.836668, -78.692253 42.836335, -78.692256 42.837598, -78.692256 42.837964, -78.692256 42.83819, -78.692257 42.838415, -78.686256 42.837841, -78.684036 42.837628, -78.682741 42.837504, -78.682753 42.839009, -78.685769 42.839052, -78.68654 42.839052, -78.690663 42.839062, -78.690933 42.839061, -78.691856 42.839056, -78.691982 42.839049, -78.692259 42.839021, -78.693298 42.838922, -78.693906 42.838859, -78.694333 42.838809, -78.694574 42.838797, -78.69475 42.838803, -78.695223 42.838857, -78.695621 42.838917, -78.696037 42.838994, -78.696346 42.839062, -78.696602 42.839124, -78.696844 42.839164, -78.696914 42.839164, -78.696914 42.839304, -78.696914 42.839552, -78.69691 42.841834, -78.696902 42.842957, -78.696898 42.843582, -78.6969 42.844568, -78.696903 42.846101, -78.696903 42.846135, -78.696913 42.849012, -78.696916 42.85054, -78.696917 42.85088, -78.69692 42.851427, -78.696921 42.851598, -78.696924 42.852033, -78.696932 42.85346, -78.696933 42.853572, -78.696939 42.854413, -78.696946 42.855591, -78.69361 42.856357, -78.693033 42.856494, -78.692969 42.856544, -78.692671 42.856206, -78.692604 42.856835, -78.692281 42.857076, -78.692099 42.857184, -78.691686 42.85743, -78.69105 42.858079, -78.6906 42.858893, -78.69022 42.859888, -78.688452 42.859718, -78.687259 42.859595, -78.686191 42.859485, -78.680321 42.858881, -78.679848 42.858878, -78.677839 42.858893, -78.67685 42.858901, -78.676394 42.85887, -78.676069 42.858816, -78.676089 42.860227, -78.676181 42.86369, -78.676183 42.863776, -78.676189 42.863961, -78.675339 42.863958, -78.674534 42.864557, -78.6731 42.864643, -78.671961 42.865146, -78.671178 42.86529, -78.670997 42.865364, -78.670167 42.865704, -78.667899 42.865657, -78.667284 42.865462, -78.666753 42.86511, -78.666247 42.864985, -78.665845 42.865274, -78.665604 42.865448, -78.664471 42.865816, -78.663563 42.866067, -78.662931 42.866242, -78.66239 42.866574, -78.661579 42.866649, -78.660878 42.867069, -78.660197 42.866987, -78.659822 42.867048, -78.659852 42.86717, -78.659827 42.867242, -78.659796 42.867734, -78.659262 42.86786, -78.6591 42.86804, -78.658724 42.868123, -78.658558 42.868394, -78.658215 42.86841, -78.658159 42.868249, -78.657284 42.868368, -78.657269 42.868757, -78.65671 42.868745, -78.656148 42.868803, -78.655658 42.868587, -78.655095 42.868667, -78.654853 42.868501, -78.654 42.868827, -78.653216 42.868994, -78.652745 42.869122, -78.652502 42.868956, -78.651191 42.86909, -78.650975 42.869039, -78.650385 42.869004, -78.649819 42.869176, -78.649611 42.86892, -78.64876 42.868399, -78.64838 42.867751, -78.646416 42.867047, -78.645564 42.866549, -78.644439 42.866686, -78.643272 42.866273, -78.64191 42.866107, -78.640436 42.865486, -78.640466 42.868176, -78.640511 42.872569, -78.640512 42.87261, -78.640528 42.874081, -78.640554 42.876477, -78.640558 42.876874, -78.637508 42.876802, -78.63739 42.876791, -78.637288 42.87676, -78.637172 42.876722, -78.636878 42.876573, -78.636728 42.876524, -78.636612 42.876487, -78.636034 42.876462, -78.63518 42.876568, -78.634956 42.876598, -78.63286 42.876627, -78.632851 42.876135, -78.63275 42.875946, -78.632686 42.875842, -78.632544 42.875745, -78.632327 42.875685, -78.631374 42.875681, -78.631034 42.875704, -78.630596 42.875717, -78.630367 42.875774, -78.630118 42.875939, -78.629888 42.876261, -78.629719 42.876456, -78.629579 42.876548, -78.629405 42.876604, -78.628511 42.876654, -78.626855 42.87669, -78.626435 42.876722, -78.6262 42.876805, -78.625918 42.877048, -78.6262 42.877237, -78.626601 42.877485, -78.626936 42.877617, -78.627377 42.877701, -78.627867 42.87767, -78.628312 42.877606, -78.62884 42.877582, -78.629311 42.877676, -78.629756 42.877835, -78.630172 42.878107, -78.630494 42.878369, -78.631187 42.878936, -78.631321 42.879126, -78.631403 42.8794, -78.631442 42.879591, -78.631413 42.879804, -78.631362 42.879999, -78.631318 42.880194, -78.631268 42.880418, -78.631265 42.880539, -78.631254 42.881178, -78.632483 42.881166, -78.638018 42.881118, -78.638962 42.881108, -78.640632 42.88109, -78.64065 42.883801, -78.640658 42.884264, -78.640662 42.885817, -78.640679 42.885969, -78.64073 42.886194, -78.640782 42.886341, -78.640847 42.886485, -78.640925 42.886626, -78.641016 42.886762, -78.641119 42.886894, -78.641234 42.88702, -78.641361 42.88714, -78.641499 42.887254, -78.641571 42.887308, -78.641771 42.887417, -78.641961 42.887535, -78.642138 42.887663, -78.642304 42.8878, -78.642456 42.887945, -78.642595 42.888097, -78.642719 42.888255, -78.642828 42.88842, -78.642922 42.888589, -78.643 42.888763, -78.643061 42.88894, -78.643036 42.8896, -78.64303 42.889779, -78.642789 42.889852, -78.642256 42.890013, -78.641015 42.889919, -78.641031 42.890331, -78.641325 42.890772, -78.641367 42.891299, -78.6412 42.891593, -78.640974 42.891817, -78.639523 42.892336, -78.639154 42.892214, -78.638517 42.891766, -78.638207 42.891737, -78.63786 42.891867, -78.637666 42.892046, -78.637217 42.892403, -78.636468 42.892456, -78.635761 42.892235, -78.634155 42.891104, -78.632598 42.890317, -78.632093 42.889689, -78.631922 42.889273, -78.631593 42.888924, -78.631289 42.888734, -78.630887 42.88868, -78.630666 42.888744, -78.629993 42.889256, -78.62989 42.889506, -78.629501 42.889933, -78.629147 42.890223, -78.628171 42.890523, -78.627516 42.890555, -78.626641 42.890607, -78.62473 42.89002, -78.623417 42.88933, -78.622888 42.88932, -78.622073 42.889463, -78.621677 42.889227, -78.619739 42.888593, -78.619056 42.888533, -78.618835 42.88862, -78.618191 42.889179, -78.617809 42.8894, -78.616531 42.889464, -78.615432 42.889716, -78.614467 42.889742, -78.613668 42.889474, -78.61336 42.889467, -78.613202 42.889464, -78.612128 42.889899, -78.611091 42.890152, -78.60975 42.890238, -78.609133 42.890111, -78.607927 42.889553, -78.60786 42.889522, -78.607488 42.88935, -78.607097 42.888856, -78.606361 42.888566, -78.605834 42.888532, -78.605053 42.888608, -78.604587 42.888575, -78.603976 42.888288, -78.603447 42.888299, -78.602533 42.888601, -78.601379 42.888668, -78.600311 42.888142, -78.599535 42.888103, -78.598494 42.88847, -78.597617 42.888612, -78.597153 42.888534, -78.596509 42.888291, -78.596207 42.888079, -78.59595 42.887479, -78.595056 42.886477, -78.595032 42.886293, -78.595049 42.885035, -78.594774 42.884114, -78.594714 42.884067, -78.594802 42.883406, -78.594819 42.882171, -78.594528 42.881662, -78.593766 42.881257, -78.593148 42.881152, -78.591964 42.88115, -78.591921 42.881146, -78.591909 42.880969, -78.591757 42.877437, -78.591721 42.876446, -78.591596 42.873023, -78.591509 42.871025, -78.591458 42.869859, -78.591438 42.869343, -78.589753 42.869387, -78.588997 42.869461, -78.58767 42.867913, -78.587031 42.867259, -78.587027 42.867145, -78.587024 42.867092, -78.586919 42.864659, -78.590707 42.864776, -78.590724 42.864053, -78.590609 42.862461, -78.590731 42.861012, -78.59046 42.860801, -78.590322 42.86034, -78.590287 42.858852, -78.5904 42.858329, -78.590808 42.857445, -78.590921 42.856921, -78.590744 42.856689, -78.590747 42.856102, -78.590408 42.856109, -78.586815 42.856193, -78.585766 42.85622, -78.584423 42.85622, -78.582295 42.856222, -78.581782 42.85623, -78.577597 42.856299, -78.577596 42.857826, -78.577594 42.861299, -78.577595 42.863239, -78.577593 42.864624, -78.577593 42.864842, -78.577596 42.86554, -78.577606 42.86729, -78.577606 42.867358, -78.577606 42.867377, -78.577608 42.867731, -78.577618 42.86947, -78.577667 42.873363, -78.577689 42.8744, -78.577747 42.877011, -78.577762 42.877922, -78.577802 42.880253, -78.577508 42.880159, -78.576448 42.880205, -78.575072 42.880405, -78.573731 42.880491, -78.573601 42.880625, -78.57361 42.881175, -78.573988 42.881846, -78.574098 42.882214, -78.574109 42.882741, -78.57308 42.883588, -78.572382 42.88394, -78.571443 42.884858, -78.570964 42.885191, -78.57065 42.885253, -78.570152 42.885265, -78.569721 42.885141, -78.569125 42.885266, -78.568355 42.885867, -78.567879 42.886109, -78.566537 42.886195, -78.565844 42.886432, -78.564874 42.886571, -78.564102 42.886418, -78.563679 42.886088, -78.563266 42.885531, -78.563041 42.884931, -78.562657 42.884397, -78.562404 42.883728, -78.562052 42.883171, -78.561537 42.882817, -78.560213 42.882469, -78.560087 42.882473, -78.558126 42.882539, -78.557654 42.882689, -78.557079 42.883065, -78.556816 42.883426, -78.55646 42.883761, -78.555455 42.883992, -78.552338 42.883628, -78.552216 42.885157, -78.552137 42.885956, -78.550601 42.885963, -78.550598 42.885347, -78.550578 42.885242, -78.550538 42.885139, -78.55048 42.885042, -78.550425 42.884973, -78.550363 42.884913, -78.550277 42.884853, -78.550181 42.884802, -78.550049 42.884753, -78.549936 42.884726, -78.54979 42.884709, -78.547537 42.884723, -78.547399 42.884733, -78.547264 42.884756, -78.547167 42.884783, -78.547044 42.884829, -78.546937 42.884884, -78.546289 42.885332, -78.546198 42.885412, -78.546139 42.885483, -78.546085 42.885579, -78.546057 42.88566, -78.546045 42.885743, -78.546057 42.885994, -78.545878 42.885993, -78.544508 42.886008, -78.543589 42.886012, -78.543463 42.886022, -78.543339 42.886045, -78.543222 42.88608, -78.543111 42.886126, -78.543045 42.886172, -78.54295 42.886258, -78.54289 42.886334, -78.542845 42.886417, -78.542816 42.886502, -78.542803 42.88659, -78.542828 42.887128, -78.54292 42.890141, -78.542942 42.890641, -78.542958 42.890727, -78.542989 42.890811, -78.543055 42.890926, -78.543106 42.891039, -78.543137 42.891155, -78.543161 42.891751, -78.54318 42.892218, -78.544727 42.892486, -78.545441 42.892605, -78.546008 42.892668, -78.546303 42.892703, -78.546793 42.892714, -78.547312 42.892737, -78.547801 42.892729, -78.548199 42.892714, -78.552286 42.892445, -78.554153 42.892336, -78.555097 42.89228, -78.556405 42.892187, -78.557143 42.892119, -78.557626 42.892026, -78.558073 42.89194, -78.559212 42.89172, -78.559543 42.891641, -78.560746 42.891283, -78.561696 42.890971, -78.562721 42.890615, -78.563578 42.890456, -78.563967 42.890412, -78.564613 42.89036, -78.565176 42.89033, -78.565787 42.890313, -78.567643 42.890374, -78.570069 42.890449, -78.570101 42.891769, -78.570122 42.892133, -78.5703 42.89273, -78.570302 42.892768, -78.568872 42.89279, -78.56858 42.892787, -78.568593 42.893281, -78.568656 42.894538, -78.568684 42.894635, -78.568717 42.894706, -78.568775 42.894795, -78.56885 42.894878, -78.56894 42.894952, -78.569043 42.895017, -78.569098 42.895045, -78.570398 42.895598, -78.570468 42.895629, -78.571371 42.89602, -78.571908 42.896243, -78.572004 42.896302, -78.572059 42.896348, -78.572123 42.896427, -78.572159 42.896499, -78.57218 42.89659, -78.572195 42.896698, -78.572236 42.896801, -78.5723 42.896899, -78.572368 42.896969, -78.572465 42.897046, -78.572572 42.897109, -78.572692 42.897158, -78.572821 42.89719, -78.572956 42.897208, -78.574754 42.897171, -78.575408 42.897154, -78.57555 42.897166, -78.575687 42.897195, -78.575815 42.897241, -78.575937 42.897306, -78.576055 42.897387, -78.576157 42.89748, -78.576259 42.897608, -78.576311 42.897643, -78.576409 42.897691, -78.576516 42.897726, -78.57663 42.897748, -78.576989 42.897746, -78.57813 42.897727, -78.578035 42.892558, -78.578011 42.891532, -78.578679 42.891531, -78.579139 42.8917, -78.579194 42.891908, -78.579462 42.892211, -78.579796 42.892424, -78.582693 42.892325, -78.584328 42.891901, -78.586038 42.891915, -78.586227 42.891873, -78.586519 42.891559, -78.587711 42.891618, -78.587774 42.891179, -78.588202 42.8912, -78.588687 42.891223, -78.589605 42.89126, -78.592648 42.891382, -78.597757 42.89157, -78.60072 42.891628, -78.602908 42.89166, -78.604063 42.8917, -78.605596 42.891715, -78.606746 42.891736, -78.607846 42.891832, -78.612827 42.892147, -78.613382 42.892215, -78.616667 42.892619, -78.617723 42.89275, -78.620022 42.893078, -78.620616 42.893163, -78.62067 42.893171, -78.62069 42.893265, -78.620704 42.893308, -78.6207 42.893433, -78.620691 42.893517, -78.620688 42.893646, -78.620687 42.893729, -78.620687 42.893801, -78.620688 42.893876, -78.62069 42.893966, -78.62069 42.894017, -78.62069 42.894071, -78.620687 42.894185, -78.620688 42.894245, -78.620687 42.894307, -78.620686 42.894369, -78.620684 42.894431, -78.620679 42.894494, -78.620663 42.894624, -78.62065 42.89469, -78.620636 42.894757, -78.620623 42.894825, -78.62061 42.894896, -78.6206 42.894968, -78.620593 42.895042, -78.620588 42.895117, -78.620586 42.895191, -78.620587 42.895264, -78.620588 42.895338, -78.62059 42.895412, -78.620592 42.895487, -78.620593 42.895562, -78.620593 42.895639, -78.620593 42.895713, -78.620591 42.895787, -78.620589 42.895861, -78.620584 42.895934, -78.620577 42.896008, -78.620568 42.896081, -78.620559 42.896152, -78.620541 42.896288, -78.620531 42.896351, -78.620519 42.896414, -78.620504 42.896477, -78.620487 42.896538, -78.620469 42.8966, -78.62045 42.896663, -78.620431 42.896729, -78.620411 42.896797, -78.620391 42.896865, -78.62037 42.896935, -78.620349 42.897006, -78.620329 42.897076, -78.620308 42.897147, -78.620288 42.897217, -78.62027 42.897287, -78.620254 42.897357, -78.620234 42.897434, -78.620308 42.897446, -78.62037 42.897456, -78.620467 42.897466, -78.620535 42.897471, -78.620609 42.897477, -78.620684 42.897479, -78.620755 42.89748, -78.620876 42.897468, -78.620986 42.89744, -78.621046 42.897422, -78.621114 42.897402, -78.621189 42.897381, -78.621269 42.897363, -78.621352 42.897347, -78.621439 42.897336, -78.621526 42.89733, -78.621615 42.897329, -78.621704 42.897331, -78.621796 42.897334, -78.62189 42.897338, -78.621989 42.897339, -78.622091 42.897339, -78.622196 42.897338, -78.622296 42.897338, -78.622394 42.897338, -78.62257 42.897333, -78.622627 42.897331, -78.622837 42.89733, -78.622848 42.898417, -78.622849 42.899472, -78.622831 42.900671, -78.620889 42.900799, -78.620438 42.901181, -78.618628 42.901264, -78.618093 42.901392, -78.617599 42.901778, -78.616672 42.901727, -78.612546 42.901496, -78.607718 42.901226, -78.607717 42.901559, -78.60771 42.903371, -78.607696 42.904418, -78.607673 42.904703, -78.607683 42.905042, -78.606965 42.905095, -78.606381 42.905701, -78.60543 42.906161, -78.605204 42.906362, -78.60443 42.906255, -78.604185 42.906158, -78.602931 42.906407, -78.601398 42.906386, -78.596492 42.906318, -78.595519 42.906526, -78.594511 42.906848, -78.593622 42.90731, -78.592976 42.907399, -78.592182 42.907508, -78.591374 42.908292, -78.5904 42.908523, -78.58898 42.90982, -78.588598 42.910041, -78.588451 42.910091, -78.588451 42.910336, -78.593007 42.910398, -78.59296 42.914847, -78.594732 42.914745, -78.594862 42.91461, -78.595367 42.914438, -78.596458 42.914392, -78.597286 42.913906, -78.597919 42.913645, -78.599631 42.913635, -78.600109 42.913348, -78.601409 42.913535, -78.603158 42.913388, -78.60363 42.913261, -78.603822 42.913105, -78.604201 42.912976, -78.605445 42.913002, -78.605535 42.913095, -78.605514 42.913621, -78.605886 42.913674, -78.606421 42.913502, -78.607105 42.913539, -78.607452 42.913409, -78.6079 42.913098, -78.609153 42.912896, -78.609419 42.912791, -78.609595 42.912722, -78.610254 42.912599, -78.61262 42.912579, -78.6127 42.912544, -78.613411 42.91223, -78.614007 42.912128, -78.614342 42.912318, -78.615028 42.912263, -78.615208 42.912273, -78.615684 42.911972, -78.616235 42.912037, -78.61658 42.911746, -78.61664 42.911511, -78.616833 42.911548, -78.616996 42.91183, -78.617169 42.911801, -78.61716 42.911608, -78.617231 42.911483, -78.617399 42.911602, -78.617453 42.911811, -78.617431 42.912008, -78.617458 42.912113, -78.617623 42.912094, -78.617744 42.912047, -78.617846 42.9119, -78.617945 42.911831, -78.618172 42.911966, -78.618508 42.911742, -78.618578 42.911661, -78.618808 42.911703, -78.619038 42.911767, -78.619315 42.911745, -78.619611 42.911585, -78.619585 42.911492, -78.619891 42.911245, -78.620587 42.910982, -78.620835 42.910943, -78.620924 42.910833, -78.622709 42.910864, -78.622691 42.91118, -78.622637 42.912466, -78.622631 42.913812, -78.62263 42.913958, -78.622607 42.914498, -78.622575 42.917267, -78.622565 42.918072, -78.622562 42.918351, -78.622546 42.919642, -78.622542 42.920052, -78.622579 42.923616, -78.624686 42.923298, -78.626167 42.92306, -78.632519 42.922036, -78.636307 42.921421, -78.6375 42.921211, -78.637609 42.921195, -78.639703 42.920883, -78.642421 42.920458, -78.642428 42.920605, -78.642481 42.92497, -78.642601 42.931775, -78.643324 42.931841, -78.644771 42.931936, -78.646562 42.93193, -78.650232 42.931913, -78.652401 42.931899, -78.654368 42.931885, -78.657372 42.931877, -78.657542 42.931898, -78.659464 42.932131, -78.662313 42.933122, -78.664277 42.93314, -78.67227 42.933214, -78.672266 42.932637, -78.672251 42.932057, -78.6722 42.928847, -78.672198 42.928759, -78.672192 42.928485, -78.672163 42.927079, -78.672164 42.926612, -78.672164 42.926497, -78.672203 42.926442, -78.672222 42.926592, -78.672274 42.926831, -78.672346 42.927067, -78.672391 42.927187, -78.672497 42.927424, -78.672623 42.927656, -78.672735 42.927828, -78.67277 42.927882, -78.672858 42.928002, -78.672952 42.92812, -78.673156 42.928349, -78.673247 42.92844, -78.673266 42.92846, -78.6735 42.928673, -78.67363 42.928777, -78.673765 42.928878, -78.673904 42.928975, -78.674048 42.929068, -78.674197 42.929158, -78.67508 42.929647, -78.675354 42.929809, -78.675794 42.930128, -78.675995 42.930295, -78.676181 42.930472, -78.676353 42.930656, -78.676433 42.930752, -78.676551 42.930905, -78.676702 42.93112, -78.676801 42.931284, -78.676897 42.931469, -78.676939 42.931563, -78.677022 42.931763, -78.677088 42.931967, -78.677126 42.932116, -78.677156 42.932256, -78.67718 42.932397, -78.677197 42.932539, -78.677207 42.932681, -78.677215 42.933219, -78.677224 42.93474, -78.677221 42.935057, -78.677203 42.936748, -78.677191 42.938695, -78.677186 42.939411, -78.677167 42.942285, -78.677139 42.944311, -78.676454 42.944574, -78.674063 42.945477, -78.673557 42.945627, -78.673302 42.945698, -78.673088 42.945748, -78.672871 42.945793, -78.672653 42.945833, -78.672294 42.945887, -78.671967 42.945921, -78.671592 42.945948, -78.671402 42.945958, -78.671022 42.945968, -78.669106 42.946005, -78.664619 42.945888, -78.66189 42.945895, -78.657324 42.945751, -78.652229 42.945682, -78.652238 42.947193, -78.65224 42.947445, -78.652242 42.94773, -78.652593 42.947718, -78.653714 42.947703, -78.655444 42.947689, -78.656504 42.947658, -78.657798 42.94764, -78.65916 42.947614, -78.660144 42.947601, -78.662884 42.947561, -78.663864 42.947556, -78.665053 42.947538, -78.66594 42.947534, -78.668857 42.94749, -78.669518 42.947481, -78.669979 42.947481, -78.671593 42.947459, -78.672054 42.947454, -78.672635 42.947485, -78.674449 42.947605, -78.674963 42.947663, -78.675779 42.947747, -78.67643 42.947826, -78.677083 42.947889, -78.680049 42.948189, -78.677097 42.949405, -78.677107 42.950468, -78.677138 42.956187, -78.677076 42.956187, -78.67635 42.956173, -78.676306 42.956179, -78.676073 42.956212, -78.675799 42.956264, -78.67553 42.956328, -78.675109 42.956447, -78.669814 42.958049, -78.667941 42.95861, -78.667272 42.958825, -78.666406 42.959086, -78.66368 42.959917, -78.663398 42.959987, -78.663114 42.960049, -78.662862 42.960098, -78.662627 42.960131, -78.66239 42.960153, -78.662151 42.960163, -78.661302 42.960173, -78.660538 42.960181, -78.659724 42.96019, -78.658337 42.960199, -78.657508 42.96021, -78.655663 42.960185, -78.652236 42.960229, -78.64972 42.960268, -78.64948 42.960268, -78.649239 42.960263, -78.648999 42.960252, -78.648521 42.960214, -78.648222 42.960177, -78.638758 42.963516, -78.632481 42.965742, -78.627983 42.967336, -78.625294 42.96829, -78.611862 42.973148, -78.609235 42.973932, -78.607461 42.974414, -78.607157 42.974496, -78.599047 42.976737, -78.599624 42.97618, -78.599344 42.975835, -78.599317 42.975517, -78.599382 42.974881, -78.599377 42.974596, -78.599564 42.974341, -78.599562 42.973952, -78.599654 42.973883, -78.600175 42.973496, -78.600658 42.973126, -78.600851 42.972937, -78.600965 42.972659, -78.601085 42.97242, -78.601024 42.972221, -78.600747 42.971997, -78.600854 42.971692, -78.601061 42.971306, -78.601181 42.970825, -78.601211 42.970365, -78.601651 42.969944, -78.601978 42.969566, -78.602266 42.969203, -78.602756 42.968839, -78.603103 42.968521, -78.603862 42.967947, -78.60426 42.967664, -78.6046 42.967324, -78.604908 42.967044, -78.605102 42.966822, -78.605738 42.966609, -78.60614 42.966429, -78.606168 42.966407, -78.606517 42.966417, -78.606754 42.966424, -78.607641 42.966452, -78.610022 42.965165, -78.612623 42.963769, -78.612629 42.962323, -78.612642 42.960049, -78.612644 42.95999, -78.610557 42.960255, -78.607703 42.960607, -78.602666 42.961236, -78.597748 42.961854, -78.592881 42.962471, -78.592874 42.962948, -78.592822 42.964243, -78.592753 42.966002, -78.59255 42.969588, -78.592502 42.970407, -78.592438 42.971941, -78.591642 42.971933, -78.590565 42.971927, -78.590543 42.971805, -78.59049 42.971679, -78.590395 42.971562, -78.590238 42.971466, -78.589946 42.971374, -78.589688 42.971342, -78.58946 42.971379, -78.589299 42.971479, -78.589201 42.971666, -78.58917 42.971834, -78.589168 42.971919, -78.587996 42.97191, -78.587898 42.97191, -78.582662 42.971885, -78.582584 42.974158, -78.582513 42.97665, -78.582503 42.976897, -78.582505 42.97717, -78.582489 42.977946, -78.582479 42.97838, -78.582184 42.978418, -78.581787 42.978438, -78.581014 42.978529, -78.580354 42.978578, -78.579589 42.978646, -78.579073 42.978648, -78.578417 42.978587, -78.577414 42.978355, -78.577179 42.978378, -78.57716 42.982226, -78.578739 42.981999, -78.580017 42.981797, -78.582433 42.98144, -78.584066 42.981268, -78.585802 42.981104, -78.58726 42.980951, -78.58728 42.981538, -78.587283 42.981643, -78.587297 42.982064, -78.587328 42.983132, -78.587393 42.985294, -78.587457 42.986646, -78.587479 42.9877, -78.591657 42.987821, -78.593567 42.987882, -78.593939 42.987894, -78.597702 42.988006, -78.598934 42.988036, -78.599705 42.988061, -78.600756 42.988096, -78.602398 42.988141, -78.602868 42.988153, -78.604933 42.988227, -78.60578 42.988252, -78.606277 42.988267, -78.607026 42.988279, -78.607391 42.988281, -78.610069 42.9883, -78.611537 42.988305, -78.612321 42.988304, -78.613899 42.988304, -78.615092 42.988308, -78.616414 42.988301, -78.617254 42.988287, -78.617373 42.989448, -78.617353 42.991083, -78.617393 42.991928, -78.617373 42.992657, -78.617348 42.993448, -78.617435 42.994699, -78.617376 42.997167, -78.617333 42.997741, -78.617325 42.997917, -78.617293 42.998092, -78.617237 42.998263, -78.617157 42.998429, -78.617092 42.998536, -78.616995 42.998639, -78.616898 42.998723, -78.617089 42.998973, -78.617366 42.998966, -78.617674 42.998944, -78.617593 42.99914, -78.617209 42.999672, -78.617169 42.999989, -78.61714 43.000186, -78.617258 43.000506, -78.617526 43.000829, -78.6177 43.000765, -78.617865 43.00074, -78.617959 43.000873, -78.618075 43.001276, -78.618218 43.001805, -78.618244 43.001942, -78.618106 43.002067, -78.618043 43.002197, -78.618264 43.002332, -78.618335 43.002471, -78.618744 43.002542, -78.619258 43.002358, -78.619484 43.002542, -78.619398 43.002837, -78.62087 43.002835, -78.621728 43.002418, -78.622166 43.002359, -78.62241 43.002501, -78.622608 43.003009, -78.617875 43.010169, -78.618209 43.010173, -78.619961 43.010198, -78.625644 43.01024, -78.627268 43.010295, -78.627311 43.011561, -78.62738 43.01392, -78.62739 43.014509, -78.627394 43.01465, -78.627403 43.014926, -78.62749 43.017733, -78.627503 43.018298, -78.627538 43.019096, -78.627574 43.020329, -78.627596 43.020921, -78.629397 43.020953, -78.63041 43.020974, -78.631167 43.020976, -78.63159 43.020985, -78.631629 43.020986, -78.63193 43.020992, -78.632523 43.021004, -78.633427 43.021018, -78.635339 43.021055, -78.636134 43.021074, -78.637043 43.02109, -78.637011 43.023232, -78.637006 43.023607, -78.637002 43.023965, -78.637001 43.024052, -78.636997 43.024402, -78.637 43.025052, -78.636992 43.025463, -78.636978 43.026289, -78.636951 43.027854, -78.636925 43.029536, -78.636899 43.031259, -78.636892 43.031744, -78.637477 43.03177, -78.640079 43.031883, -78.646666 43.032182, -78.649329 43.0323, -78.652665 43.032443, -78.65414 43.032491, -78.656771 43.03257, -78.660074 43.032681, -78.662733 43.032758, -78.662747 43.029133, -78.662753 43.024461, -78.662753 43.022966, -78.662767 43.021743, -78.662665 43.021016, -78.662557 43.020166, -78.663304 43.020505, -78.663823 43.020743, -78.666162 43.021815, -78.66737 43.022351, -78.669914 43.023529, -78.671026 43.024039, -78.67238 43.024654, -78.672932 43.024898, -78.674469 43.025043, -78.676577 43.025241, -78.677546 43.025302, -78.679631 43.025441, -78.680066 43.02547, -78.679909 43.025974, -78.679788 43.026183, -78.679687 43.026263, -78.679492 43.026344, -78.67921 43.026472, -78.679068 43.026607, -78.678981 43.026755, -78.678981 43.027031, -78.678429 43.027158, -78.678346 43.027364, -78.678305 43.02753, -78.678346 43.027695, -78.67847 43.027942, -78.678759 43.028231, -78.679337 43.028727, -78.680407 43.029347, -78.680417 43.033115, -78.682365 43.033155, -78.682426 43.033153, -78.683447 43.033173, -78.684128 43.033184, -78.685568 43.033206, -78.687513 43.033239, -78.691043 43.033299, -78.693288 43.03334, -78.693857 43.033883, -78.694287 43.034075, -78.694498 43.034262, -78.694675 43.034518, -78.69695 43.034794, -78.696949 43.035163, -78.696949 43.035203, -78.696942 43.038409, -78.696942 43.038607, -78.696935 43.040061, -78.696918 43.043992, -78.696912 43.045138, -78.69691 43.046584, -78.69691 43.047065, -78.696908 43.04839, -78.696901 43.052715, -78.696905 43.054711, -78.696913 43.057726, -78.696927 43.058331, -78.696936 43.058722, -78.696933 43.059234, -78.697093 43.05909, -78.698371 43.059115, -78.698499 43.059049, -78.698738 43.058482, -78.698898 43.058371, -78.700081 43.058463, -78.700303 43.058353, -78.70038 43.057966, -78.700359 43.057906, -78.70171 43.057974, -78.702749 43.058016, -78.704109 43.056453, -78.70454 43.055929, -78.705053 43.055348, -78.70661 43.05438, -78.707967 43.053784, -78.708429 43.05361, -78.708464 43.053596, -78.709768 43.053093, -78.710176 43.052947, -78.711516 43.052454, -78.712504 43.052075, -78.7142 43.051438, -78.714553 43.051597, -78.715217 43.0519, -78.71749 43.052823, -78.717633 43.052866, -78.718422 43.053025, -78.72087 43.05348, -78.721512 43.053473, -78.724557 43.053441, -78.724571 43.054152, -78.724531 43.055079, -78.724507 43.056302, -78.726911 43.054004, -78.727446 43.053418, -78.728065 43.053413, -78.733978 43.053365, -78.734085 43.053371, -78.734226 43.053393, -78.734361 43.053428, -78.734489 43.053476, -78.73455 43.053504, -78.737403 43.055006, -78.737652 43.055165, -78.737991 43.055357, -78.738283 43.055517, -78.738853 43.055844, -78.738793 43.056391, -78.738797 43.056524, -78.73881 43.056587, -78.738852 43.056711, -78.739057 43.057101, -78.739119 43.057251, -78.739179 43.057469, -78.739208 43.057597, -78.73924 43.057877, -78.739254 43.057942, -78.739329 43.05816, -78.739381 43.058252, -78.739438 43.058321, -78.739606 43.058579, -78.739685 43.058733, -78.73973 43.058856, -78.739761 43.058988, -78.739769 43.059116, -78.739753 43.059286, -78.73972 43.059378, -78.739675 43.059501, -78.739661 43.05956, -78.739621 43.059655, -78.739573 43.059726, -78.739519 43.059837, -78.739466 43.060011, -78.739312 43.061211, -78.739314 43.061292, -78.739334 43.061398, -78.739374 43.061501, -78.739432 43.0616, -78.739508 43.061692, -78.739576 43.061755, -78.739655 43.061797, -78.739742 43.06186, -78.739824 43.061949, -78.739872 43.062032, -78.739899 43.062119, -78.739916 43.062512, -78.73993 43.063744, -78.739959 43.064534, -78.73997 43.065166, -78.73999 43.065569, -78.739991 43.0659, -78.739998 43.066192, -78.739997 43.066518, -78.740015 43.067101, -78.740014 43.067335, -78.740028 43.067599, -78.740035 43.068053, -78.740049 43.068426, -78.739952 43.06849, -78.73987 43.068565, -78.739695 43.068782, -78.739593 43.068939, -78.739509 43.069113, -78.739414 43.069356, -78.739357 43.069551, -78.739282 43.069759, -78.739248 43.069906, -78.73877 43.071401, -78.738681 43.071656, -78.738072 43.073566, -78.737973 43.073874, -78.737482 43.075413, -78.73711 43.076581, -78.736091 43.079765, -78.735675 43.080988, -78.735623 43.081123, -78.7355 43.081389, -78.735353 43.081648, -78.73527 43.081775, -78.735089 43.082022, -78.73499 43.082142, -78.734884 43.082261, -78.734773 43.082377, -78.734535 43.082601, -78.734408 43.082708, -78.73414 43.082913, -78.734 43.083011, -78.733859 43.083119, -78.733714 43.083223, -78.733564 43.083323, -78.733251 43.083513, -78.733088 43.083603, -78.732921 43.083688, -78.73275 43.083769, -78.732576 43.083846, -78.732398 43.083918, -78.732217 43.083986, -78.732033 43.08405, -78.731847 43.084109, -78.731657 43.084164, -78.731272 43.084259, -78.731076 43.084299, -78.730878 43.084334, -78.729024 43.084793, -78.728651 43.084863, -78.728272 43.084915, -78.728081 43.084935, -78.727697 43.084959, -78.727504 43.084964, -78.727577 43.085764, -78.727647 43.08622, -78.727456 43.086223, -78.727242 43.086201, -78.726921 43.08614, -78.726707 43.08609, -78.72451 43.085621, -78.724251 43.085545, -78.724014 43.085461, -78.723816 43.085346, -78.723625 43.085194, -78.722997 43.084597, -78.722824 43.084484, -78.72245 43.084328, -78.722382 43.084286, -78.722298 43.084229, -78.72213 43.08408, -78.722008 43.083942, -78.721748 43.083641, -78.721458 43.083179, -78.720802 43.081825, -78.720665 43.081608, -78.720566 43.081509, -78.720449 43.081438, -78.720337 43.081364, -78.719879 43.0812, -78.719368 43.081089, -78.71921 43.081068, -78.719048 43.081055, -78.718903 43.081078, -78.718315 43.081226, -78.718033 43.081329, -78.717766 43.081448, -78.717377 43.081646, -78.717133 43.081802, -78.717071 43.08186, -78.716728 43.082223, -78.716499 43.082455, -78.716438 43.082516, -78.716095 43.082787, -78.715942 43.08289, -78.715735 43.082996, -78.7155 43.083092, -78.715218 43.083183, -78.714767 43.083237, -78.714479 43.083236, -78.71418 43.083199, -78.713776 43.083103, -78.713516 43.083015, -78.711746 43.082352, -78.711548 43.082298, -78.711357 43.08226, -78.711205 43.082249, -78.711058 43.082264, -78.709625 43.08252, -78.709488 43.082581, -78.709048 43.082934, -78.708834 43.08318, -78.708629 43.083423, -78.708351 43.083893, -78.708193 43.084208, -78.708054 43.084721, -78.708031 43.084877, -78.70802 43.085062, -78.708015 43.085611, -78.708 43.086044, -78.707999 43.086252, -78.707962 43.086403, -78.707893 43.086555, -78.708027 43.08659, -78.708176 43.08663, -78.708395 43.086729, -78.708416 43.086733, -78.708552 43.086763, -78.708993 43.086748, -78.709397 43.086784, -78.709865 43.086869, -78.71021 43.087016, -78.710446 43.087173, -78.710662 43.087499, -78.71069 43.087619, -78.710668 43.087736, -78.710598 43.087887, -78.710322 43.088093, -78.710092 43.088247, -78.710038 43.088285, -78.709968 43.088384, -78.709932 43.088504, -78.709932 43.088589, -78.709975 43.088674, -78.710043 43.088762, -78.710117 43.088828, -78.710237 43.088913, -78.710346 43.088929, -78.710641 43.088872, -78.710991 43.088738, -78.711629 43.088497, -78.711703 43.088469, -78.712284 43.088306, -78.712405 43.088299, -78.712568 43.088348, -78.712773 43.088469, -78.713164 43.088715, -78.713592 43.088986, -78.713712 43.089014, -78.713868 43.089007, -78.71456 43.088653, -78.71532 43.088263, -78.715455 43.088235, -78.715575 43.088228, -78.715689 43.088256, -78.715802 43.088348, -78.716124 43.088702, -78.716318 43.088914, -78.716426 43.088964, -78.717102 43.089063, -78.717315 43.088993, -78.717592 43.08884, -78.718012 43.088502, -78.718223 43.088333, -78.718348 43.088267, -78.718646 43.088236, -78.718849 43.088249, -78.718934 43.088292, -78.719047 43.088454, -78.719104 43.088596, -78.719132 43.088702, -78.719125 43.088865, -78.719047 43.089063, -78.718764 43.089502, -78.718689 43.08964, -78.71865 43.089768, -78.718642 43.089861, -78.718673 43.090237, -78.718743 43.090369, -78.719083 43.090748, -78.719579 43.091159, -78.719888 43.091386, -78.720156 43.091506, -78.720241 43.091529, -78.720471 43.091555, -78.720753 43.091552, -78.721067 43.091517, -78.721156 43.091529, -78.721407 43.091683, -78.721443 43.09175, -78.721463 43.091839, -78.721447 43.091963, -78.721427 43.092011, -78.721055 43.092538, -78.72101 43.092646, -78.720996 43.092801, -78.721012 43.092894, -78.721063 43.092995, -78.721231 43.093125, -78.72152 43.093212, -78.721898 43.093253, -78.722398 43.093334, -78.722522 43.0934, -78.722696 43.093594, -78.722914 43.093885, -78.723208 43.094098, -78.72335 43.094152, -78.72359 43.094163, -78.723854 43.094125, -78.72485 43.093764, -78.725401 43.093451, -78.725474 43.093427, -78.725532 43.093431, -78.725622 43.093467, -78.725736 43.093552, -78.725863 43.093736, -78.725991 43.093963, -78.726161 43.094217, -78.726317 43.094416, -78.726432 43.094606, -78.726558 43.094812, -78.726699 43.095124, -78.726756 43.095166, -78.726891 43.095165, -78.726926 43.095146, -78.726954 43.095095, -78.727011 43.094854, -78.72711 43.094699, -78.727195 43.09467, -78.727337 43.09467, -78.727698 43.094696, -78.727847 43.094652, -78.728045 43.094472, -78.728226 43.094262, -78.728361 43.094233, -78.72846 43.094248, -78.728524 43.094304, -78.728679 43.094561, -78.728779 43.094623, -78.729239 43.094663, -78.729367 43.094762, -78.729565 43.094982, -78.729671 43.095032, -78.729842 43.095024, -78.730077 43.094982, -78.728433 43.093674, -78.728199 43.093458, -78.727985 43.093207, -78.727817 43.092948, -78.727676 43.092657, -78.727584 43.092351, -78.727548 43.092019, -78.727556 43.091718, -78.727568 43.091634, -78.727627 43.091321, -78.727718 43.090834, -78.727752 43.090436, -78.727765 43.089054, -78.729303 43.089054, -78.729319 43.088098, -78.729298 43.087956, -78.729255 43.087893, -78.72913 43.0878, -78.728893 43.087652, -78.728102 43.087229, -78.727743 43.087054, -78.72773 43.086972, -78.727722 43.086922, -78.729553 43.086109, -78.729881 43.085987, -78.730362 43.085838, -78.730788 43.085767, -78.731301 43.085701, -78.731631 43.085689, -78.731939 43.085697, -78.732091 43.08571, -78.732435 43.085742, -78.733231 43.085859, -78.733361 43.085879, -78.733529 43.085905, -78.733754 43.085939, -78.733885 43.08596, -78.733995 43.085976, -78.734327 43.086027, -78.734438 43.086045, -78.734803 43.084723, -78.734934 43.084335, -78.735051 43.084098, -78.7353 43.083732, -78.735518 43.083495, -78.7357 43.083334, -78.73634 43.082807, -78.736526 43.082607, -78.736683 43.082363, -78.736776 43.082132, -78.736998 43.081433, -78.737147 43.081006, -78.737262 43.080808, -78.737288 43.080766, -78.737417 43.080592, -78.737637 43.080357, -78.738378 43.079677, -78.738735 43.079342, -78.739573 43.07856, -78.740028 43.078109, -78.740434 43.077638, -78.740916 43.077044, -78.741186 43.076683, -78.741377 43.076379, -78.742731 43.07386, -78.743648 43.072122, -78.743767 43.071923, -78.743985 43.071595, -78.744226 43.071342, -78.744367 43.071213, -78.74452 43.071094, -78.744682 43.070987, -78.745027 43.070787, -78.745373 43.070636, -78.745691 43.070525, -78.746077 43.070429, -78.746418 43.070367, -78.748321 43.070158, -78.748937 43.0701, -78.749438 43.070081, -78.749866 43.070073, -78.750425 43.070089, -78.750545 43.070093, -78.751287 43.070156, -78.751882 43.070245, -78.752648 43.070414, -78.752976 43.070503, -78.753268 43.070593, -78.753579 43.070703, -78.753852 43.0708, -78.754464 43.071062, -78.756209 43.071882, -78.756167 43.072254, -78.756209 43.072879, -78.756221 43.074876, -78.756279 43.083872, -78.756299 43.086871, -78.757131 43.086851, -78.759438 43.086786, -78.768748 43.086524, -78.768857 43.086521, -78.771997 43.086443, -78.771697 43.083415, -78.771667 43.083111, -78.771605 43.082371, -78.771453 43.078662, -78.771299 43.075682, -78.771217 43.0743, -78.771147 43.073119, -78.771089 43.072627, -78.770912 43.071716, -78.770841 43.071286, -78.770888 43.07128, -78.771031 43.071266, -78.771079 43.071262, -78.771821 43.071186, -78.772418 43.071112, -78.772661 43.071066, -78.773178 43.070932, -78.773415 43.070853, -78.773586 43.070784, -78.77392 43.070632, -78.774141 43.070511, -78.774664 43.070157, -78.775159 43.069765, -78.775739 43.069306, -78.776222 43.068967, -78.776585 43.068756, -78.776941 43.06859, -78.777386 43.068434, -78.777756 43.068332, -78.778224 43.068243, -78.778514 43.068203, -78.780509 43.067891, -78.781253 43.067808, -78.781608 43.06776, -78.78324 43.06759, -78.783771 43.067511, -78.784534 43.067364, -78.785135 43.067261, -78.785627 43.067196, -78.78607 43.067161, -78.787646 43.067114, -78.788532 43.067079, -78.789327 43.066999, -78.792141 43.066653, -78.792878 43.066584, -78.793406 43.066558, -78.795806 43.066553, -78.796621 43.066575, -78.796581 43.06693, -78.796544 43.067272, -78.796565 43.067429, -78.796622 43.067524, -78.796703 43.067586, -78.79686 43.067652, -78.797014 43.067642, -78.797045 43.067632, -78.797159 43.067597, -78.797258 43.067537, -78.797346 43.067452, -78.797305 43.067787, -78.797288 43.067931, -78.797173 43.068759, -78.797167 43.06879, -78.797104 43.069122, -78.797174 43.069004, -78.797356 43.068574, -78.797547 43.068128, -78.79774 43.067805, -78.797888 43.067604, -78.798053 43.067425, -78.798401 43.067129, -78.798758 43.066828, -78.798843 43.06672, -78.798565 43.066688, -78.798288 43.066656, -78.797732 43.066606, -78.797693 43.066603, -78.797454 43.066591, -78.797472 43.066455, -78.797496 43.066277, -78.797505 43.066161, -78.797496 43.066046, -78.797653 43.066047, -78.798356 43.066077, -78.798955 43.066125, -78.799449 43.066261, -78.799696 43.066304, -78.799906 43.066298, -78.800407 43.066219, -78.800698 43.066158, -78.801325 43.065887, -78.801676 43.065755, -78.802041 43.065628, -78.802355 43.06544, -78.802794 43.065198, -78.803175 43.065004, -78.803622 43.064745, -78.804032 43.064486, -78.804494 43.064178, -78.805038 43.063825, -78.805388 43.063588, -78.805627 43.063396, -78.805857 43.063187, -78.806028 43.063011, -78.806229 43.062829, -78.806481 43.062455, -78.806614 43.062148, -78.806732 43.061775, -78.806834 43.061423, -78.806892 43.061039, -78.806882 43.060661, -78.806858 43.06031, -78.806811 43.060009, -78.806757 43.059734, -78.806674 43.059504, -78.806506 43.058984, -78.806408 43.058787, -78.806212 43.058535, -78.806031 43.058322, -78.805723 43.058038, -78.805407 43.057775, -78.805159 43.057535, -78.804873 43.057229, -78.80467 43.05695, -78.804511 43.056671, -78.804366 43.056172, -78.804343 43.055969, -78.804341 43.055766, -78.804377 43.055519, -78.804473 43.055278, -78.804562 43.055108, -78.804614 43.055009, -78.804762 43.054783, -78.804903 43.054607, -78.805089 43.054492, -78.805313 43.054299, -78.805581 43.054117, -78.805969 43.053891, -78.806319 43.053742, -78.806446 43.053681, -78.806834 43.053532, -78.807282 43.053476, -78.807984 43.053293, -78.808447 43.053182, -78.809515 43.05286, -78.809948 43.052683, -78.810485 43.05244, -78.810753 43.052308, -78.811365 43.052092, -78.811918 43.051926, -78.812515 43.051771, -78.813187 43.051609, -78.813949 43.051492, -78.814689 43.051396, -78.815391 43.051295, -78.816109 43.051282, -78.816895 43.051302, -78.81744 43.051305, -78.818128 43.051281, -78.818854 43.051323, -78.819422 43.051305, -78.820185 43.051275, -78.820679 43.0513, -78.821292 43.051337, -78.821962 43.051334, -78.821999 43.051561, -78.822357 43.051557, -78.822337 43.051587, -78.82228 43.051679, -78.822261 43.05171, -78.822285 43.051857, -78.82236 43.0523, -78.822385 43.052448, -78.822401 43.052481, -78.822418 43.05258, -78.822428 43.053474, -78.822463 43.056568, -78.822475 43.0576, -78.822495 43.058989, -78.822501 43.059236, -78.822579 43.06229, -78.822622 43.064145, -78.82266 43.065782, -78.822588 43.06582, -78.822467 43.065914, -78.822415 43.066035, -78.822356 43.066506, -78.822279 43.066631, -78.82204 43.066836, -78.821832 43.06695, -78.821398 43.067248, -78.821235 43.067453, -78.821166 43.067584, -78.821138 43.067664, -78.821128 43.06773, -78.821135 43.067914, -78.821159 43.067962, -78.821208 43.068004, -78.821499 43.068167, -78.821783 43.068382, -78.8219 43.068511, -78.822037 43.068662, -78.822127 43.068791, -78.8222 43.06894, -78.822227 43.069037, -78.822227 43.06913, -78.822207 43.069213, -78.822078 43.069411, -78.821742 43.069671, -78.821589 43.069813, -78.82152 43.069844, -78.821447 43.069844, -78.821305 43.06981, -78.820725 43.069484, -78.820406 43.06938, -78.819931 43.069304, -78.819494 43.069297, -78.819203 43.069335, -78.819029 43.069366, -78.818894 43.069484, -78.81887 43.069557, -78.818884 43.069695, -78.818877 43.069772, -78.818728 43.070038, -78.818512 43.070528, -78.818454 43.070635, -78.818419 43.070676, -78.818301 43.07077, -78.818166 43.070832, -78.817992 43.070881, -78.817781 43.070999, -78.817732 43.071044, -78.817711 43.071092, -78.817711 43.071144, -78.817736 43.071189, -78.817784 43.071331, -78.817794 43.07139, -78.817683 43.071835, -78.817643 43.072312, -78.817654 43.072383, -78.817686 43.072438, -78.817808 43.072546, -78.817877 43.072655, -78.817976 43.072901, -78.818066 43.073013, -78.818218 43.073121, -78.818408 43.073244, -78.81888 43.07368, -78.819237 43.073982, -78.819324 43.0741, -78.81939 43.074231, -78.819563 43.074398, -78.819982 43.074755, -78.820209 43.074949, -78.8205 43.075181, -78.820694 43.075358, -78.820878 43.075493, -78.821197 43.07575, -78.821294 43.075888, -78.821319 43.075954, -78.821343 43.0761, -78.821384 43.076218, -78.821426 43.076273, -78.821513 43.076325, -78.821676 43.076384, -78.821953 43.076443, -78.822333 43.076566, -78.822806 43.076611, -78.823007 43.076673, -78.823023 43.077156, -78.823074 43.078606, -78.823079 43.078734, -78.8231 43.079089, -78.823107 43.079281, -78.823128 43.079857, -78.823135 43.08005, -78.823163 43.080709, -78.82325 43.082686, -78.82328 43.083346, -78.823285 43.08346, -78.8233 43.083802, -78.823305 43.083917, -78.82331 43.084035, -78.823311 43.084122, -78.823313 43.084196, -78.823314 43.08428, -78.82332 43.084506, -78.823329 43.084738, -78.823337 43.084944, -78.823376 43.086129, -78.82338 43.086254, -78.823506 43.090185, -78.823541 43.091496, -78.823755 43.098894, -78.823809 43.100455, -78.823833 43.101515, -78.823995 43.106417, -78.824001 43.106663, -78.824063 43.108269, -78.824279 43.113825, -78.824352 43.115677, -78.821293 43.115677, -78.82152 43.116231, -78.823375 43.120756, -78.824606 43.122351, -78.82666 43.122283, -78.827211 43.122267, -78.82818 43.122239, -78.831338 43.122128, -78.83129 43.120717, -78.831295 43.120694, -78.831333 43.120658, -78.831403 43.120632, -78.831758 43.12063, -78.83195 43.120612, -78.832075 43.12062, -78.832122 43.120651, -78.832147 43.120704, -78.832191 43.121539, -78.832207 43.122099, -78.833564 43.122045, -78.835026 43.122, -78.835856 43.121976, -78.837632 43.121911, -78.837673 43.122652, -78.8377 43.12314, -78.837894 43.126625, -78.837908 43.12683, -78.837936 43.127219, -78.837987 43.12806, -78.839107 43.12753, -78.842468 43.125944, -78.843589 43.125415, -78.84543 43.124538, -78.850349 43.122196, -78.850877 43.121969, -78.850967 43.121938, -78.851436 43.121783, -78.851691 43.121714, -78.851938 43.121661, -78.852174 43.121616, -78.852416 43.121579, -78.852957 43.121519, -78.852511 43.121516, -78.851173 43.121541, -78.850367 43.121563, -78.849891 43.121576, -78.846966 43.121662, -78.8432 43.121764, -78.840882 43.121825, -78.840907 43.120829, -78.838971 43.120887, -78.837582 43.121004, -78.837569 43.12076, -78.837554 43.120473, -78.837542 43.120228, -78.837506 43.119497, -78.837495 43.119253, -78.837462 43.118662, -78.837364 43.11689, -78.837332 43.1163, -78.837328 43.116223, -78.837326 43.116191, -78.837309 43.115867, -78.837304 43.115759, -78.837203 43.113787, -78.837128 43.112321, -78.836998 43.109948, -78.836954 43.108997, -78.836871 43.107872, -78.83685 43.107572, -78.836772 43.105901, -78.836578 43.102139, -78.836491 43.10044, -78.836401 43.098027, -78.836322 43.095354, -78.836172 43.090849, -78.836153 43.090267, -78.836064 43.087085, -78.836051 43.086619, -78.836012 43.085222, -78.835999 43.084757, -78.834865 43.084779, -78.831466 43.084845, -78.83096 43.084855, -78.830333 43.084862, -78.828651 43.084869, -78.827878 43.084876, -78.827131 43.084866, -78.827201 43.084601, -78.827201 43.084542, -78.827176 43.084469, -78.827155 43.084438, -78.82711 43.08441, -78.826822 43.084323, -78.826576 43.084195, -78.826493 43.084136, -78.826316 43.083984, -78.826236 43.083862, -78.826226 43.083755, -78.826292 43.083509, -78.826309 43.083377, -78.826302 43.083301, -78.826261 43.083209, -78.826129 43.08312, -78.826025 43.083029, -78.825876 43.08285, -78.825744 43.082739, -78.825699 43.082677, -78.825674 43.082601, -78.825685 43.082348, -78.825681 43.08207, -78.825733 43.081726, -78.825882 43.081494, -78.825934 43.081442, -78.826115 43.08134, -78.826202 43.081319, -78.826576 43.081312, -78.82684 43.081274, -78.826881 43.081246, -78.826913 43.081191, -78.826916 43.081149, -78.826906 43.081118, -78.826791 43.081007, -78.826694 43.080962, -78.826632 43.080945, -78.82651 43.080948, -78.826406 43.080993, -78.82633 43.081, -78.826245 43.080983, -78.82616 43.080904, -78.826479 43.080524, -78.82744 43.079384, -78.827761 43.079005, -78.827883 43.079003, -78.828209 43.078997, -78.82937 43.078976, -78.829543 43.078973, -78.829623 43.078971, -78.831091 43.07894, -78.831809 43.078925, -78.834197 43.078891, -78.835521 43.078873, -78.835806 43.078863, -78.835774 43.077727, -78.835763 43.077326, -78.835682 43.074357, -78.83568 43.074322, -78.835642 43.073188, -78.835956 43.073178, -78.836898 43.07315, -78.837213 43.073142, -78.83723 43.073258, -78.837261 43.073301, -78.837321 43.073354, -78.837433 43.073407, -78.8375 43.073427, -78.837897 43.073427, -78.839692 43.073412, -78.839846 43.073388, -78.839974 43.073314, -78.840001 43.073288, -78.840047 43.073229, -78.840066 43.073114, -78.840063 43.073021, -78.840537 43.073025, -78.840691 43.073029, -78.840668 43.073452, -78.843155 43.073444, -78.843414 43.073444, -78.843422 43.073532, -78.843598 43.073589, -78.844002 43.073719, -78.84436 43.073875, -78.844595 43.073936, -78.845102 43.074036, -78.845224 43.074229, -78.845372 43.074497, -78.845397 43.0746, -78.845423 43.074959, -78.846037 43.074962, -78.846392 43.074927, -78.847039 43.074826, -78.847194 43.074813, -78.847342 43.074813, -78.847556 43.07482, -78.847698 43.074839, -78.848234 43.074917, -78.848505 43.07493, -78.84879 43.07491, -78.849422 43.074826, -78.84941 43.075, -78.849422 43.075377, -78.846901 43.075432, -78.847023 43.076431, -78.844856 43.076477, -78.844986 43.077492, -78.851035 43.077546, -78.851029 43.076921, -78.851026 43.076542, -78.851004 43.075047, -78.851003 43.074958, -78.851001 43.074794, -78.850996 43.074423, -78.851654 43.074419, -78.8518 43.074406, -78.852066 43.074302, -78.85212 43.074289, -78.852196 43.074283, -78.853967 43.074268, -78.854445 43.074253, -78.854495 43.074238, -78.854601 43.074177, -78.854669 43.074113, -78.854714 43.074055, -78.854726 43.074024, -78.854725 43.073655, -78.854719 43.07324, -78.85468 43.073157, -78.854581 43.073065, -78.854456 43.073007, -78.854344 43.072989, -78.853975 43.072993, -78.852217 43.072993, -78.852045 43.073024, -78.851803 43.073123, -78.85173 43.073141, -78.850978 43.073143, -78.850956 43.070355, -78.851097 43.070075, -78.851114 43.070041, -78.851159 43.069965, -78.851194 43.069902, -78.850959 43.069795, -78.849963 43.069327, -78.849233 43.06897, -78.849198 43.068953, -78.848848 43.068786, -78.848241 43.068497, -78.847915 43.068323, -78.847814 43.068261, -78.847574 43.068116, -78.847482 43.068064, -78.851174 43.068082, -78.86225 43.068136, -78.865942 43.068155, -78.865939 43.068514, -78.865932 43.069591, -78.86593 43.069951, -78.865924 43.070534, -78.865909 43.072287, -78.865906 43.072636, -78.865903 43.072912, -78.865393 43.072908, -78.865111 43.072904, -78.86386 43.072904, -78.86335 43.072904, -78.863348 43.073487, -78.863346 43.074331, -78.863332 43.074501, -78.863238 43.074687, -78.863166 43.074769, -78.862838 43.075029, -78.862474 43.075319, -78.86242 43.075379, -78.86239 43.0754, -78.863165 43.076012, -78.864216 43.076788, -78.864662 43.077039, -78.865095 43.077277, -78.865573 43.077491, -78.86586 43.077562, -78.865868 43.077881, -78.86587 43.079157, -78.86587 43.079244, -78.86587 43.079506, -78.865871 43.079594, -78.865496 43.079597, -78.864374 43.079608, -78.864 43.079612, -78.863978 43.079681, -78.863914 43.07989, -78.863893 43.07996, -78.863874 43.080019, -78.863819 43.080197, -78.863802 43.080257, -78.86374 43.080454, -78.863554 43.081045, -78.863492 43.081242, -78.863178 43.08122, -78.862289 43.081233, -78.861664 43.081243, -78.861443 43.081239, -78.861266 43.08121, -78.861144 43.081169, -78.861029 43.081116, -78.860739 43.080951, -78.860666 43.080919, -78.86058 43.080897, -78.860421 43.080889, -78.860301 43.080901, -78.860135 43.08096, -78.858922 43.0816, -78.858002 43.082087, -78.857911 43.08216, -78.857883 43.082197, -78.858106 43.082328, -78.858777 43.082723, -78.859001 43.082855, -78.859058 43.082791, -78.859178 43.082702, -78.859744 43.0824, -78.859893 43.082322, -78.860055 43.082245, -78.860187 43.082195, -78.860271 43.082179, -78.860365 43.082172, -78.862327 43.082154, -78.862994 43.082148, -78.863202 43.082176, -78.862975 43.082902, -78.862695 43.083802, -78.862771 43.083818, -78.862924 43.083831, -78.863491 43.083821, -78.863978 43.083816, -78.865443 43.083801, -78.865931 43.083797, -78.865956 43.085108, -78.866034 43.089043, -78.86604 43.089333, -78.866076 43.090355, -78.866085 43.090531, -78.866112 43.091062, -78.866121 43.091239, -78.866131 43.091447, -78.866162 43.092074, -78.866173 43.092283, -78.866186 43.092557, -78.866226 43.093379, -78.86624 43.093654, -78.869384 43.093568, -78.871391 43.093514, -78.873974 43.093453, -78.878819 43.093317, -78.879871 43.093288, -78.881964 43.093227, -78.881987 43.094528, -78.882056 43.098431, -78.88208 43.099732, -78.882616 43.099721, -78.884227 43.09969, -78.884764 43.09968, -78.884771 43.099931, -78.884793 43.100687, -78.884801 43.100939, -78.885015 43.100933, -78.885312 43.100905, -78.885528 43.100886, -78.886155 43.100757, -78.886282 43.100742, -78.886513 43.100732, -78.886686 43.100748, -78.886831 43.100774, -78.886903 43.100788, -78.887099 43.100858, -78.8873 43.100974, -78.887343 43.101, -78.887477 43.101059, -78.88757 43.101061, -78.887667 43.101038, -78.887791 43.10098, -78.888316 43.100741, -78.888538 43.100664, -78.888762 43.100616, -78.889028 43.100588, -78.889298 43.10058, -78.889825 43.100567, -78.889829 43.100891, -78.889841 43.101863, -78.889845 43.102188, -78.889847 43.102651, -78.889856 43.104041, -78.88986 43.104505, -78.88986 43.104824, -78.889865 43.106821, -78.889874 43.110343, -78.889861 43.112501, -78.889857 43.113772, -78.889851 43.116089, -78.889317 43.116092, -78.888796 43.116096, -78.888494 43.116088, -78.887715 43.116085, -78.887182 43.116084, -78.886708 43.11608, -78.885287 43.116071, -78.88504 43.116084, -78.88493 43.116132, -78.884885 43.116192, -78.88488 43.116222, -78.884876 43.116274, -78.884891 43.117042, -78.884943 43.119505, -78.884961 43.120326, -78.884768 43.120376, -78.884585 43.120439, -78.884017 43.120638, -78.883585 43.120864, -78.883515 43.12092, -78.883447 43.120976, -78.883386 43.121077, -78.883358 43.121176, -78.883373 43.121259, -78.883693 43.121259, -78.884656 43.121259, -78.884977 43.121259, -78.884993 43.1227, -78.884998 43.12344, -78.885021 43.126485, -78.885022 43.128798, -78.884995 43.129985, -78.884981 43.130646, -78.884958 43.131595, -78.884931 43.132166, -78.884893 43.133093, -78.88478 43.135875, -78.884762 43.136338, -78.884763 43.136803, -78.884018 43.136922, -78.881783 43.13728, -78.881039 43.1374, -78.880889 43.137424, -78.87979 43.137602, -78.876044 43.138211, -78.874796 43.138415, -78.873336 43.138647, -78.872304 43.138811, -78.871128 43.139, -78.870378 43.139129, -78.866654 43.139736, -78.865937 43.139848, -78.864834 43.14003, -78.863744 43.140211, -78.862344 43.140433, -78.861286 43.1406, -78.85929 43.140917, -78.858115 43.141106, -78.857059 43.141277, -78.856669 43.14134, -78.855502 43.141529, -78.855113 43.141593, -78.852656 43.141987, -78.845821 43.143085, -78.845285 43.143173, -78.844416 43.143318, -78.842828 43.143564, -78.84316 43.143562, -78.843798 43.143537, -78.844768 43.143503, -78.845296 43.143488, -78.852701 43.143284, -78.852947 43.143278, -78.854434 43.143246, -78.85517 43.143219, -78.855488 43.143214, -78.856446 43.1432, -78.856765 43.143196, -78.857914 43.143176, -78.858171 43.143172, -78.861364 43.143145, -78.862515 43.143136, -78.863347 43.143128, -78.864098 43.143121, -78.865843 43.143108, -78.866676 43.143102, -78.867052 43.143097, -78.868033 43.143099, -78.869215 43.143103, -78.872104 43.143128, -78.873461 43.14314, -78.873485 43.142679, -78.873525 43.142332, -78.873574 43.142149, -78.873612 43.142009, -78.873652 43.141922, -78.874069 43.142151, -78.874186 43.142183, -78.87426 43.142187, -78.874428 43.142193, -78.874427 43.142212, -78.874423 43.143155, -78.875712 43.14318, -78.876248 43.143191, -78.877194 43.143198, -78.879581 43.14323, -78.880871 43.143248, -78.881652 43.143251, -78.883433 43.143259, -78.883998 43.143262, -78.88478 43.143266, -78.885077 43.143267, -78.885969 43.14327, -78.886267 43.143272, -78.886973 43.143273, -78.887663 43.143274, -78.888402 43.143263, -78.889094 43.143261, -78.889801 43.143259, -78.890129 43.143257, -78.891116 43.143254, -78.891445 43.143253, -78.891448 43.142695, -78.891458 43.141024, -78.891462 43.140467, -78.891464 43.140089, -78.89147 43.138955, -78.891473 43.138578, -78.891473 43.138461, -78.891475 43.138111, -78.891477 43.137995, -78.891479 43.137627, -78.891487 43.136526, -78.891489 43.136263, -78.89147 43.136201, -78.891441 43.136175, -78.891407 43.136158, -78.891272 43.136152, -78.890965 43.136195, -78.89082 43.136216, -78.889535 43.136432, -78.889059 43.136513, -78.889051 43.136431, -78.88903 43.136188, -78.889023 43.136107, -78.889009 43.135751, -78.889034 43.13533, -78.889201 43.133296, -78.88922 43.132981, -78.889268 43.132199, -78.889369 43.130531, -78.8894 43.130013, -78.889793 43.123456, -78.889823 43.122962, -78.889883 43.121825, -78.889889 43.121555, -78.889891 43.121269, -78.889888 43.12104, -78.88988 43.120495, -78.889868 43.119485, -78.889857 43.118173, -78.889852 43.117399, -78.890061 43.11735, -78.890114 43.117301, -78.890163 43.117228, -78.890359 43.116804, -78.890359 43.116747, -78.89033 43.11662, -78.890334 43.116498, -78.890514 43.116181, -78.890526 43.116104, -78.890514 43.116047, -78.890379 43.11572, -78.890375 43.115586, -78.890387 43.115553, -78.890441 43.115488, -78.890961 43.115162, -78.891039 43.115121, -78.891169 43.115072, -78.891357 43.115044, -78.891496 43.11495, -78.891586 43.114868, -78.89166 43.114722, -78.891683 43.1147, -78.892076 43.114346, -78.892223 43.114256, -78.892387 43.114175, -78.892489 43.11402, -78.89273 43.113528, -78.8928 43.11321, -78.892934 43.113047, -78.892943 43.113018, -78.892938 43.112986, -78.89291 43.112949, -78.892685 43.112735, -78.892615 43.112641, -78.892583 43.112551, -78.89262 43.112355, -78.892599 43.112269, -78.89255 43.112216, -78.892481 43.112163, -78.89226 43.112025, -78.892158 43.111943, -78.892125 43.111898, -78.892084 43.111739, -78.892088 43.111645, -78.892134 43.111418, -78.892228 43.111263, -78.892231 43.111156, -78.89217 43.11103, -78.892109 43.110936, -78.892101 43.110432, -78.892108 43.110289, -78.892155 43.110074, -78.892155 43.109995, -78.892124 43.109922, -78.892098 43.1099, -78.891544 43.109799, -78.891478 43.10978, -78.891414 43.109748, -78.891398 43.109704, -78.891414 43.109622, -78.891484 43.109568, -78.891943 43.109375, -78.892146 43.109302, -78.892196 43.10927, -78.892212 43.109198, -78.892187 43.108742, -78.892193 43.108702, -78.892215 43.108664, -78.892645 43.108309, -78.892747 43.108199, -78.892757 43.108171, -78.892757 43.108093, -78.892726 43.108023, -78.892712 43.107948, -78.892722 43.107905, -78.892766 43.10784, -78.892983 43.107714, -78.893013 43.107657, -78.892999 43.107604, -78.892956 43.107551, -78.892892 43.107516, -78.892467 43.107356, -78.892262 43.10731, -78.892163 43.107264, -78.89212 43.107204, -78.892117 43.107144, -78.892134 43.10708, -78.892183 43.107026, -78.8924 43.106833, -78.892542 43.10673, -78.892659 43.106672, -78.892701 43.106652, -78.892938 43.10656, -78.893038 43.106489, -78.893126 43.106383, -78.893165 43.106298, -78.893165 43.106227, -78.893123 43.106075, -78.893052 43.105958, -78.892907 43.105806, -78.892892 43.10576, -78.892797 43.105636, -78.892754 43.105487, -78.892786 43.105381, -78.892768 43.105325, -78.892765 43.10524, -78.892822 43.105144, -78.892825 43.105087, -78.89279 43.105031, -78.892613 43.1049, -78.892407 43.104833, -78.892074 43.104755, -78.891929 43.104698, -78.891837 43.104631, -78.891784 43.10456, -78.891766 43.104489, -78.89177 43.104372, -78.891798 43.104312, -78.891851 43.104259, -78.89195 43.10421, -78.892011 43.104153, -78.892016 43.104119, -78.893781 43.103798, -78.894174 43.103719, -78.896059 43.103346, -78.896871 43.103199, -78.897244 43.103151, -78.897913 43.103107, -78.900705 43.103084, -78.902901 43.103067, -78.902898 43.103214, -78.902891 43.103655, -78.902889 43.103803, -78.902893 43.103866, -78.902917 43.103913, -78.902941 43.103928, -78.903089 43.103941, -78.903565 43.103931, -78.905697 43.103887, -78.905873 43.103884, -78.906643 43.103875, -78.906647 43.104029, -78.906661 43.10449, -78.906666 43.104645, -78.90667 43.104821, -78.906682 43.105301, -78.906687 43.10535, -78.906697 43.105428, -78.906733 43.105471, -78.906772 43.105486, -78.906819 43.105497, -78.906903 43.105501, -78.907553 43.105484, -78.908001 43.105473, -78.909903 43.105445, -78.910687 43.105434, -78.911353 43.105428, -78.911798 43.105425, -78.913351 43.105385, -78.913774 43.105375, -78.914017 43.105359, -78.914057 43.105354, -78.914276 43.105311, -78.914538 43.105236, -78.915309 43.105018, -78.91606 43.104814, -78.916088 43.104806, -78.916465 43.104713, -78.916531 43.104681, -78.916577 43.104622, -78.916591 43.1045, -78.91659 43.104458, -78.916585 43.103966, -78.916584 43.103802, -78.91658 43.103471, -78.916568 43.102481, -78.916564 43.102151, -78.917636 43.10186, -78.920854 43.10099, -78.921927 43.1007, -78.92194 43.103526, -78.921943 43.104165, -78.921955 43.106526, -78.92195 43.10764, -78.921979 43.108502, -78.922043 43.109142, -78.922174 43.109907, -78.922319 43.110558, -78.922448 43.111042, -78.922668 43.111712, -78.922841 43.112176, -78.923047 43.112645, -78.923383 43.113336, -78.923549 43.113634, -78.923883 43.114191, -78.924084 43.114525, -78.924611 43.115272, -78.925094 43.115883, -78.925717 43.116592, -78.926024 43.116907, -78.925924 43.117118, -78.925867 43.117203, -78.925626 43.117387, -78.925499 43.117627, -78.925456 43.11784, -78.925447 43.117871, -78.925378 43.118137, -78.92523 43.118477, -78.924975 43.118873, -78.924798 43.119029, -78.924599 43.11915, -78.923919 43.119411, -78.923664 43.119617, -78.923445 43.119822, -78.923282 43.120042, -78.923161 43.120261, -78.923155 43.120274, -78.923049 43.12054, -78.922992 43.120802, -78.92297 43.121068, -78.922987 43.121407, -78.923757 43.121412, -78.9242 43.121415, -78.926067 43.121436, -78.926838 43.121446, -78.927656 43.121454, -78.929721 43.121476, -78.930112 43.121475, -78.930931 43.121475, -78.931361 43.121477, -78.932651 43.121485, -78.933081 43.121488, -78.934047 43.121912, -78.934191 43.121976, -78.936944 43.12319, -78.937231 43.123317, -78.937503 43.123443, -78.937911 43.123616, -78.940306 43.12467, -78.941051 43.125003, -78.942006 43.125395, -78.942156 43.125448, -78.942155 43.125749, -78.942389 43.125938, -78.942371 43.12709, -78.942358 43.127439, -78.942353 43.127595, -78.942249 43.13134, -78.942224 43.132504, -78.944766 43.132521, -78.945279 43.132518, -78.946833 43.132512, -78.950747 43.132432, -78.950826 43.132391, -78.951002 43.132392, -78.951097 43.132395, -78.951493 43.132411, -78.965955 43.132331, -78.96597 43.132881, -78.966015 43.134532, -78.96603 43.135083, -78.966048 43.135877, -78.966102 43.138262, -78.96612 43.139057, -78.965823 43.138981, -78.965398 43.139658, -78.96444 43.143458, -78.965285 43.143459, -78.971271 43.143461, -78.970976 43.143119, -78.970093 43.142096, -78.969799 43.141755, -78.96968 43.141618, -78.969326 43.141207, -78.969208 43.14107, -78.969103 43.140951, -78.968788 43.140595, -78.968684 43.140477, -78.968533 43.140307, -78.968082 43.139799, -78.968047 43.139742, -78.968025 43.139649, -78.968035 43.139587, -78.968061 43.139596, -78.968142 43.139623, -78.968169 43.139632, -78.969293 43.140011, -78.969781 43.140143, -78.97026 43.14025, -78.970651 43.140311, -78.971239 43.140383, -78.971595 43.140407, -78.971972 43.140417, -78.972607 43.140404, -78.973268 43.14035, -78.973844 43.140275, -78.974581 43.140146, -78.975295 43.139992, -78.976001 43.139822, -78.976236 43.139954, -78.976656 43.139995, -78.976805 43.140029, -78.976913 43.140096, -78.977503 43.140401, -78.978038 43.140766, -78.978541 43.141036, -78.978584 43.141154, -78.979469 43.140722, -78.983309 43.139138, -78.985883 43.138055, -78.987061 43.13754, -78.987224 43.137486, -78.987962 43.137473, -78.991633 43.137473, -78.996733 43.137433, -78.996855 43.137406, -78.997201 43.137121, -78.997648 43.136891, -78.997959 43.136756, -78.998149 43.136702, -78.998406 43.136675, -78.998718 43.136702, -78.999097 43.136715, -79.000005 43.136769, -79.000235 43.136826, -79.000369 43.13684, -79.000497 43.13684, -79.000745 43.136755, -79.001588 43.136295, -79.001723 43.136238, -79.001949 43.136196, -79.002105 43.136181, -79.00224 43.136181, -79.002375 43.13621, -79.002753 43.136337, -79.002743 43.136135, -79.002722 43.136043, -79.002779 43.135951, -79.002871 43.135936, -79.003005 43.135936, -79.003161 43.135922, -79.003317 43.135851, -79.003512 43.135677, -79.003641 43.135767, -79.003882 43.135936, -79.003969 43.136009, -79.004025 43.136041, -79.004115 43.136093, -79.004169 43.136098, -79.004226 43.136096, -79.004309 43.136066, -79.004882 43.135726, -79.006978 43.134488, -79.00714 43.134393, -79.007352 43.134257, -79.007556 43.134089, -79.007608 43.133995, -79.007707 43.134012, -79.007758 43.134009, -79.008036 43.133919, -79.009304 43.133514, -79.009727 43.133379, -79.009788 43.133332, -79.010006 43.133169, -79.010135 43.133074, -79.009763 43.132791, -79.009551 43.132603, -79.009475 43.13253, -79.009318 43.132379, -79.009258 43.132319, -79.00897 43.132036, -79.00889 43.131957, -79.008653 43.131723, -79.008627 43.131698, -79.008574 43.131646, -79.008435 43.131507, -79.008018 43.131091, -79.00788 43.130953, -79.008018 43.130952, -79.008238 43.130903, -79.008321 43.130898, -79.008339 43.130898, -79.008528 43.130889, -79.008712 43.130896, -79.008875 43.130811, -79.009102 43.130542, -79.009377 43.130284, -79.009407 43.130259, -79.009754 43.129977, -79.010399 43.129976, -79.010897 43.129976, -79.012337 43.129982, -79.012983 43.129985, -79.013552 43.129983, -79.013836 43.129983, -79.015262 43.129986, -79.015453 43.129987, -79.015832 43.130001, -79.015979 43.130006, -79.016127 43.130012, -79.016419 43.130036, -79.016566 43.130049, -79.016968 43.130089, -79.017887 43.13014, -79.018098 43.130153, -79.018399 43.130158, -79.018552 43.130157, -79.018566 43.130157, -79.018678 43.130157, -79.019069 43.130155, -79.019237 43.130155, -79.019655 43.130154, -79.020308 43.130129, -79.023146 43.130125, -79.023625 43.130118, -79.02413 43.130082, -79.024896 43.129987, -79.025488 43.129856, -79.025857 43.129735, -79.026101 43.129641, -79.026339 43.129524, -79.026493 43.129832, -79.026599 43.130072, -79.027162 43.131453, -79.027279 43.131599, -79.027362 43.131686, -79.027439 43.131746, -79.027473 43.131773, -79.027532 43.131814, -79.027645 43.131872, -79.027867 43.131948, -79.028061 43.131985, -79.028349 43.132003, -79.03114 43.132031, -79.034898 43.132067, -79.034916 43.132068, -79.035378 43.132096, -79.035831 43.132135, -79.037027 43.132325, -79.037384 43.132356, -79.034713 43.132268, -79.027637 43.132037, -79.026704 43.131978, -79.024038 43.131811, -79.023705 43.133106, -79.023575 43.133617, -79.023238 43.135682, -79.02315 43.136142, -79.02302 43.136139, -79.022857 43.136136, -79.022204 43.136123, -79.021141 43.136102, -79.020834 43.136096, -79.021576 43.137874, -79.021933 43.138828, -79.022068 43.139411, -79.022155 43.139921, -79.022177 43.140344, -79.022231 43.141601, -79.022234 43.143937, -79.022208 43.143936, -79.022129 43.143937, -79.022103 43.143937, -79.021979 43.143935, -79.021887 43.143934, -79.021783 43.143934, -79.021611 43.143904, -79.02149 43.143884, -79.021405 43.14387, -79.021306 43.143842, -79.021221 43.143842, -79.02115 43.14387, -79.021107 43.143969, -79.021171 43.145484, -79.021114 43.145711, -79.020243 43.147537, -79.018798 43.15095, -79.018642 43.151148, -79.018422 43.151346, -79.011741 43.154928, -79.006051 43.158015, -79.004025 43.159091, -79.003643 43.159205, -79.001831 43.159372, -79.001608 43.1594, -79.001765 43.159969, -79.001893 43.159953, -79.002141 43.159922, -79.002637 43.159901, -79.002768 43.15988, -79.002959 43.159979, -79.003087 43.160014, -79.004121 43.160121, -79.004241 43.160121, -79.004468 43.160036, -79.004518 43.160029, -79.004582 43.160057, -79.00466 43.160121, -79.004723 43.160156, -79.004822 43.16017, -79.005602 43.160177, -79.006278 43.160269, -79.00644 43.16028, -79.006532 43.160312, -79.006631 43.160393, -79.006649 43.160429, -79.006737 43.160535, -79.006844 43.160556, -79.007595 43.160542, -79.00819 43.160556, -79.008921 43.16062, -79.008421 43.160925, -79.007024 43.161862, -79.006353 43.162333, -79.003364 43.16472, -79.002743 43.165179, -79.002066 43.165608, -79.001614 43.165851, -79.001367 43.165984, -79.00134 43.165997, -79.000647 43.166343, -79.000505 43.166407, -79.000223 43.166537, -78.999545 43.166876, -78.999084 43.167108, -78.997508 43.167884, -78.997018 43.168126, -78.997329 43.16954, -78.997321 43.169584, -78.997352 43.169788, -78.997361 43.169827, -78.997575 43.170677, -78.997647 43.170961, -78.997713 43.171229, -78.997912 43.172035, -78.997945 43.172169, -78.997974 43.172241, -78.998008 43.172293, -78.998084 43.172845, -78.998535 43.175032, -78.99894 43.174963, -79.00059 43.174685, -79.00114 43.174592, -79.001543 43.174524, -79.002561 43.174389, -79.002915 43.17436, -79.003194 43.174338, -79.004021 43.174288, -79.007559 43.174072, -79.00828 43.174029, -79.010069 43.173921, -79.010568 43.173891, -79.012065 43.173801, -79.012564 43.173771, -79.013441 43.173716, -79.013942 43.173686, -79.015173 43.173619, -79.016073 43.173565, -79.016951 43.173514, -79.017675 43.173473, -79.017677 43.17427, -79.017678 43.174978, -79.017678 43.175065, -79.017683 43.176662, -79.017685 43.17746, -79.017684 43.177511, -79.017684 43.177665, -79.017684 43.177717, -79.017677 43.178867, -79.017675 43.17936, -79.017698 43.182317, -79.017707 43.183468, -79.017726 43.184851, -79.017729 43.185065, -79.017726 43.186399, -79.017738 43.187527, -79.017728 43.189, -79.017728 43.189024, -79.017729 43.190384, -79.017415 43.190382, -79.016709 43.190379, -79.016501 43.190391, -79.016475 43.190396, -79.016356 43.190421, -79.016173 43.190476, -79.016107 43.19036, -79.016065 43.190287, -79.01594 43.19001, -79.015937 43.190001, -79.015913 43.189923, -79.015875 43.18989, -79.015156 43.189884, -79.013001 43.189869, -79.012791 43.189868, -79.012459 43.189879, -79.012318 43.189901, -79.012289 43.189917, -79.01222 43.189977, -79.012204 43.190021, -79.012193 43.190124, -79.012188 43.190656, -79.012212 43.19072, -79.012253 43.190771, -79.012302 43.190798, -79.012378 43.190825, -79.012492 43.190836, -79.013314 43.19084, -79.014704 43.190855, -79.014852 43.190857, -79.015325 43.190847, -79.015547 43.190859, -79.015537 43.191202, -79.015526 43.191642, -79.015521 43.191878, -79.01553 43.192233, -79.01554 43.192577, -79.01553 43.19287, -79.015518 43.193283, -79.015519 43.193751, -79.015521 43.194045, -79.01596 43.194044, -79.017279 43.194044, -79.017719 43.194044, -79.017705 43.197811, -79.017702 43.198186, -79.017368 43.198182, -79.017272 43.204055, -79.017267 43.204383, -79.01719 43.208943, -79.017294 43.208923, -79.017608 43.208864, -79.017713 43.208845, -79.017713 43.208909, -79.017713 43.209102, -79.017713 43.209167, -79.017713 43.209783, -79.017713 43.211342, -79.017714 43.212346, -79.017727 43.215132, -79.017726 43.216591, -79.017727 43.217816, -79.017727 43.21787, -79.017728 43.218856, -79.017726 43.220046, -79.017773 43.220047, -79.017917 43.220053, -79.017965 43.220055, -79.019271 43.220034, -79.023191 43.219975, -79.024498 43.219955, -79.024494 43.220038, -79.024481 43.220117, -79.024433 43.220253, -79.02442 43.220289, -79.024298 43.220448, -79.02343 43.221255, -79.023207 43.221465, -79.022857 43.221796, -79.020232 43.224376, -79.02002 43.224636, -79.019092 43.225957, -79.018692 43.226529, -79.018409 43.226913, -79.018257 43.227062, -79.018078 43.227146, -79.018003 43.227164, -79.01763 43.227197, -79.01763 43.227543, -79.017631 43.227718, -79.017641 43.228582, -79.017646 43.228929, -79.017614 43.228925, -79.017073 43.229062, -79.017026 43.229074, -79.01697 43.229102, -79.016977 43.229201, -79.017023 43.229336, -79.01708 43.229442, -79.017108 43.229577, -79.017111 43.229768, -79.017069 43.229888, -79.017012 43.230002, -79.016968 43.230057, -79.016871 43.230178, -79.016601 43.230412, -79.016552 43.230476, -79.016539 43.230524, -79.016531 43.230554, -79.016531 43.230617, -79.016633 43.230752, -79.016732 43.230971, -79.016723 43.231057, -79.016329 43.231058, -79.015147 43.23106, -79.014754 43.231062, -79.014997 43.231199, -79.015208 43.231319, -79.0154 43.231461, -79.015652 43.231717, -79.015849 43.231917, -79.015719 43.232076, -79.015642 43.23221, -79.015542 43.232458, -79.015415 43.232862, -79.015337 43.233032, -79.015287 43.233216, -79.015224 43.233301, -79.015057 43.233442, -79.014774 43.233584, -79.014369 43.233931, -79.014346 43.234026, -79.014388 43.234186, -79.014487 43.234313, -79.014501 43.234511, -79.014493 43.234524, -79.01443 43.234632, -79.014345 43.234809, -79.014331 43.234964, -79.014296 43.235035, -79.014197 43.235106, -79.013842 43.235226, -79.013708 43.235219, -79.013382 43.235141, -79.012277 43.234936, -79.012121 43.234936, -79.01205 43.234978, -79.012029 43.235318, -79.011965 43.235403, -79.011781 43.235524, -79.011508 43.235892, -79.011006 43.236274, -79.010945 43.236312, -79.010919 43.236386, -79.010935 43.236529, -79.010991 43.236607, -79.011098 43.236692, -79.01114 43.236777, -79.01114 43.23684, -79.011027 43.236925, -79.010928 43.236961, -79.010556 43.237053, -79.009763 43.237357, -79.009536 43.237492, -79.009387 43.237605, -79.009253 43.237796, -79.009182 43.237931, -79.009083 43.238037, -79.008998 43.238108, -79.008941 43.238235, -79.008941 43.238391, -79.008969 43.238538, -79.008989 43.238564, -79.009044 43.238577, -79.009104 43.238639, -79.009104 43.238717, -79.00907 43.238775, -79.00864 43.239191, -79.008597 43.239304, -79.008526 43.239418, -79.008399 43.239531, -79.008378 43.239562, -79.008371 43.239573, -79.008378 43.239644, -79.008406 43.239694, -79.00852 43.239793, -79.008541 43.239885, -79.008526 43.239963, -79.008477 43.240048, -79.00842 43.240119, -79.008293 43.240203, -79.008279 43.240295, -79.008314 43.240345, -79.008505 43.240508, -79.008583 43.240614, -79.00859 43.24072, -79.00853 43.240989, -79.008466 43.241046, -79.008034 43.241166, -79.00797 43.24123, -79.007956 43.24128, -79.008027 43.241414, -79.008091 43.241478, -79.008459 43.241947, -79.009227 43.241942, -79.011242 43.241932, -79.011533 43.241934, -79.012094 43.24194, -79.012248 43.241963, -79.012293 43.241989, -79.011573 43.24323, -79.011327 43.243705, -79.011099 43.244261, -79.010973 43.244652, -79.010892 43.244982, -79.010858 43.245125, -79.010593 43.24654, -79.010176 43.248917, -79.010116 43.249266, -79.009509 43.252659, -79.009411 43.253388, -79.009303 43.254775, -79.0093 43.254811, -79.009177 43.256821, -79.009136 43.258128, -79.009736 43.25814, -79.010138 43.25812, -79.010569 43.25807, -79.011184 43.257957, -79.011342 43.257919, -79.014086 43.257268, -79.014483 43.257165, -79.015185 43.257004, -79.017815 43.256436, -79.017848 43.256428, -79.020025 43.255966, -79.0214 43.255678, -79.021922 43.255562, -79.02266 43.2554, -79.025088 43.254883, -79.026519 43.254574, -79.027615 43.254344, -79.029353 43.253981, -79.029514 43.253946, -79.029695 43.253906, -79.030238 43.253789, -79.03042 43.25375, -79.030469 43.253738, -79.03062 43.253705, -79.03067 43.253694, -79.031086 43.252319, -79.031179 43.25284, -79.031283 43.253286, -79.031338 43.253545, -79.03136 43.253612, -79.031552 43.254159, -79.032436 43.256389, -79.032709 43.257091, -79.032811 43.257353, -79.033053 43.257973, -79.03311 43.258142, -79.033202 43.258409, -79.035932 43.258173, -79.044123 43.257469, -79.046854 43.257235, -79.046854 43.257079, -79.046855 43.256909, -79.046862 43.25605, -79.046873 43.255932, -79.046879 43.255883, -79.046926 43.255676, -79.046951 43.255618, -79.050489 43.255163, -79.051836 43.254975, -79.051839 43.254911, -79.051858 43.25482, -79.051887 43.254777, -79.051906 43.254751, -79.051944 43.254721, -79.051948 43.254681, -79.051594 43.254384, -79.051566 43.254349, -79.051534 43.25431, -79.051493 43.254158, -79.051381 43.253739, -79.051361 43.253721, -79.051175 43.253661, -79.051111 43.253628, -79.050994 43.253532, -79.050903 43.253442, -79.050825 43.253288, -79.05071 43.252732, -79.050626 43.252557, -79.050606 43.252493, -79.050604 43.252369, -79.050633 43.25226, -79.050923 43.25167, -79.050939 43.251606, -79.050927 43.251435, -79.050905 43.251406, -79.050768 43.251355, -79.050728 43.251326, -79.050711 43.251302, -79.050708 43.251264, -79.050733 43.251189, -79.05095 43.250857, -79.051027 43.250675, -79.051074 43.250513, -79.051091 43.250403, -79.051119 43.250227, -79.05116 43.249904, -79.051266 43.249545, -79.05128 43.249325, -79.051231 43.248816, -79.051216 43.247839, -79.051227 43.247661, -79.051245 43.247371, -79.051358 43.246961, -79.051684 43.245998, -79.051769 43.245573, -79.05186 43.244533, -79.052145 43.243718, -79.052224 43.243292, -79.05218 43.243151, -79.052162 43.24301, -79.052189 43.242473, -79.052201 43.241736, -79.052228 43.240987, -79.052162 43.240383, -79.052221 43.239517, -79.052237 43.2393, -79.05218 43.238281, -79.052208 43.237899, -79.052214 43.236755, -79.052215 43.236582, -79.0523 43.236256, -79.052357 43.235746, -79.0523 43.235251, -79.052215 43.234939, -79.052088 43.234161, -79.051775 43.233057, -79.051755 43.232985, -79.051415 43.231966, -79.05116 43.230975, -79.050876 43.230309, -79.050402 43.228992, -79.049792 43.226458, -79.049261 43.224801, -79.049176 43.224419, -79.049034 43.224079, -79.048939 43.223708, -79.048864 43.222564, -79.048868 43.222157, -79.048885 43.220822, -79.048904 43.220613, -79.048943 43.220362, -79.048999 43.220179, -79.049008 43.220075, -79.048968 43.219989, -79.048949 43.219893, -79.048949 43.219822, -79.049089 43.219164, -79.049135 43.218883, -79.049301 43.21845, -79.049324 43.218389, -79.049543 43.217919, -79.049617 43.217783, -79.049646 43.217731, -79.049779 43.217564, -79.049912 43.217255, -79.050138 43.21698, -79.050181 43.216869, -79.050196 43.216764, -79.050224 43.216665, -79.050279 43.21653, -79.050552 43.216002, -79.050656 43.215852, -79.051079 43.215247, -79.05123 43.215031, -79.051646 43.214378, -79.052088 43.213686, -79.052584 43.212652, -79.052683 43.212327, -79.05274 43.212001, -79.052768 43.211675, -79.052745 43.211531, -79.052697 43.211222, -79.052591 43.210925, -79.052417 43.210555, -79.052364 43.210443, -79.051429 43.209311, -79.050612 43.208382, -79.04966 43.207298, -79.046986 43.203434, -79.046653 43.20301, -79.046483 43.202698, -79.046356 43.202273, -79.046285 43.201919, -79.046172 43.201494, -79.046108 43.201298, -79.04603 43.201056, -79.045846 43.200163, -79.045775 43.199923, -79.04564 43.199271, -79.045471 43.198545, -79.045456 43.198479, -79.045456 43.198323, -79.045456 43.198082, -79.045481 43.197656, -79.045484 43.197615, -79.045523 43.197438, -79.04573 43.196498, -79.045853 43.195944, -79.046386 43.193686, -79.046504 43.19319, -79.046567 43.192742, -79.046653 43.192135, -79.046859 43.190393, -79.046908 43.18994, -79.047071 43.188468, -79.047248 43.187501, -79.047371 43.187024, -79.047442 43.186627, -79.047492 43.186203, -79.047655 43.185827, -79.047864 43.185594, -79.047885 43.185523, -79.04785 43.185389, -79.047786 43.185296, -79.04775 43.185219, -79.047758 43.185098, -79.047928 43.18439, -79.048133 43.183682, -79.048268 43.183059, -79.048669 43.181799, -79.048679 43.181771, -79.048834 43.180907, -79.04893 43.18022, -79.048916 43.180022, -79.048951 43.17993, -79.049008 43.179881, -79.049005 43.179839, -79.049011 43.179788, -79.049059 43.179422, -79.049065 43.179404, -79.049051 43.179293, -79.049104 43.179073, -79.049095 43.179046, -79.04909 43.179031, -79.049004 43.178932, -79.048969 43.178847, -79.048965 43.178799, -79.048955 43.178642, -79.048976 43.178557, -79.048962 43.17833, -79.048969 43.17821, -79.049019 43.178033, -79.049059 43.177959, -79.04909 43.177905, -79.049111 43.17782, -79.04909 43.17775, -79.049055 43.177696, -79.048934 43.177509, -79.048877 43.177381, -79.04887 43.177304, -79.048884 43.177219, -79.04889 43.177025, -79.048895 43.176879, -79.048923 43.176518, -79.048966 43.176348, -79.049008 43.176284, -79.049128 43.176185, -79.049136 43.176093, -79.049093 43.175902, -79.049093 43.17576, -79.04915 43.175307, -79.049231 43.174989, -79.049244 43.174963, -79.049352 43.174762, -79.049479 43.174585, -79.049536 43.174535, -79.049619 43.174534, -79.049656 43.174521, -79.04966 43.174445, -79.04967 43.174255, -79.049688 43.17395, -79.04963 43.173915, -79.049525 43.173874, -79.049511 43.173839, -79.049507 43.173794, -79.049503 43.17374, -79.049528 43.173607, -79.049557 43.173452, -79.049633 43.172883, -79.049646 43.172794, -79.049679 43.172691, -79.049717 43.172518, -79.049724 43.172256, -79.049706 43.171817, -79.049713 43.171725, -79.049685 43.171689, -79.049649 43.171668, -79.049628 43.171633, -79.049635 43.171576, -79.049678 43.171519, -79.049699 43.171463, -79.049678 43.17135, -79.049571 43.171166, -79.049543 43.171066, -79.049543 43.170791, -79.049543 43.170712, -79.049635 43.170387, -79.049642 43.170224, -79.049597 43.17008, -79.049589 43.170054, -79.049518 43.169962, -79.049493 43.169887, -79.049481 43.169804, -79.049469 43.169707, -79.049511 43.169219, -79.049511 43.16902, -79.049476 43.168893, -79.049412 43.168652, -79.049388 43.168601, -79.04927 43.168348, -79.048944 43.167789, -79.048911 43.167752, -79.048863 43.167696, -79.048735 43.167597, -79.048671 43.167477, -79.048579 43.167342, -79.048466 43.167243, -79.048069 43.166847, -79.047672 43.16638, -79.047354 43.165919, -79.047182 43.165442, -79.047166 43.165396, -79.047017 43.165105, -79.046918 43.16495, -79.046826 43.164649, -79.04679 43.164556, -79.046695 43.164312, -79.04656 43.164114, -79.046192 43.163647, -79.046142 43.163595, -79.045937 43.163378, -79.045745 43.163102, -79.045589 43.162804, -79.045334 43.162167, -79.045317 43.161934, -79.045281 43.161842, -79.045246 43.161806, -79.04521 43.161587, -79.045225 43.161233, -79.045217 43.160985, -79.045182 43.160815, -79.04509 43.160539, -79.044982 43.160402, -79.044973 43.16039, -79.044923 43.160277, -79.044831 43.15998, -79.04471 43.159273, -79.044716 43.157946, -79.044712 43.157787, -79.044621 43.157512, -79.044526 43.157382, -79.044451 43.157325, -79.044294 43.15705, -79.044191 43.156767, -79.044149 43.156447, -79.044054 43.155907, -79.044034 43.155641, -79.044042 43.155237, -79.043976 43.154863, -79.043988 43.154642, -79.044038 43.154458, -79.044058 43.154331, -79.044041 43.154141, -79.044038 43.154096, -79.044001 43.153987, -79.043856 43.15317, -79.043843 43.153008, -79.04384 43.152969, -79.043837 43.152927, -79.043835 43.152901, -79.04381 43.152841, -79.043814 43.152796, -79.043799 43.152768, -79.043794 43.152753, -79.043782 43.152736, -79.04374 43.152657, -79.043492 43.152044, -79.043393 43.151706, -79.043182 43.151369, -79.042746 43.150857, -79.042152 43.150159, -79.041912 43.149897, -79.04178 43.149731, -79.041735 43.149619, -79.041664 43.14939, -79.041673 43.149148, -79.041743 43.148862, -79.041784 43.148418, -79.041821 43.148312, -79.041896 43.147643, -79.041896 43.147507, -79.041859 43.147389, -79.041854 43.147148, -79.04183 43.147003, -79.041801 43.146961, -79.04178 43.146846, -79.041652 43.144719, -79.041621 43.144634, -79.041578 43.144556, -79.041514 43.144492, -79.041489 43.144483, -79.04111 43.144358, -79.04094 43.144329, -79.040891 43.14428, -79.040877 43.144203, -79.040097 43.144136, -79.039945 43.144123, -79.039368 43.144074, -79.039462 43.142709, -79.039596 43.141401, -79.039809 43.141403, -79.039891 43.141402, -79.0406 43.141402, -79.040896 43.141384, -79.041231 43.141364, -79.041245 43.141316, -79.041288 43.141286, -79.04133 43.141271, -79.041443 43.141271, -79.041762 43.141319, -79.041892 43.141286, -79.041925 43.141245, -79.041954 43.141155, -79.041982 43.140704, -79.041925 43.140266, -79.041904 43.139784, -79.041943 43.139423, -79.041978 43.139345, -79.042 43.139246, -79.041886 43.139076, -79.041886 43.138998, -79.041922 43.138899, -79.041964 43.138821, -79.042113 43.138651, -79.042134 43.138538, -79.042134 43.138467, -79.042262 43.138354, -79.042439 43.138234, -79.042517 43.13812, -79.042588 43.137979, -79.0428 43.137724, -79.042914 43.137632, -79.043378 43.137342, -79.04342 43.137299, -79.043434 43.137242, -79.04342 43.137194, -79.043392 43.137143, -79.043406 43.137101, -79.043427 43.13708, -79.043533 43.13703, -79.044051 43.136719, -79.044178 43.136591, -79.044299 43.136527, -79.044518 43.13645, -79.04522 43.136159, -79.045772 43.135742, -79.046236 43.13555, -79.046513 43.135458, -79.04674 43.135409, -79.046924 43.135402, -79.047101 43.135373, -79.047257 43.135317, -79.047576 43.135119, -79.047809 43.134885, -79.047951 43.134765, -79.048277 43.134588, -79.048887 43.134119, -79.049127 43.133936, -79.049641 43.133596, -79.049981 43.133271, -79.050201 43.132988, -79.050689 43.132407, -79.050987 43.132096, -79.051034 43.132055, -79.051224 43.13189, -79.051274 43.131869, -79.051345 43.131812, -79.051515 43.131565, -79.05182 43.131388, -79.052025 43.131189, -79.052231 43.131012, -79.052379 43.130828, -79.052634 43.130581, -79.052705 43.130446, -79.05279 43.130156, -79.052999 43.129802, -79.053155 43.129639, -79.053318 43.129327, -79.053998 43.128804, -79.054467 43.128288, -79.055114 43.127727, -79.055334 43.127607, -79.055716 43.127366, -79.056 43.127161, -79.056059 43.127112, -79.05646 43.126786, -79.056691 43.126558, -79.056868 43.126484, -79.057375 43.126234, -79.057611 43.126095, -79.05775 43.126065, -79.058052 43.126028, -79.058626 43.125926, -79.05892 43.125889, -79.059185 43.125874, -79.059428 43.125801, -79.060102 43.125356, -79.060306 43.125155, -79.060728 43.124791, -79.060942 43.124639, -79.061019 43.124552, -79.061077 43.124456, -79.061244 43.124296, -79.061642 43.124058, -79.062037 43.123791, -79.062237 43.123642, -79.062496 43.123486, -79.063317 43.122918, -79.063583 43.122762, -79.063978 43.122402, -79.064227 43.122249, -79.064881 43.121969, -79.065124 43.121879, -79.065262 43.121858, -79.065595 43.121858, -79.065761 43.121817, -79.065941 43.121755, -79.067333 43.121125, -79.067499 43.121021, -79.067551 43.120921, -79.067579 43.120727, -79.067579 43.120499, -79.067558 43.120298, -79.067427 43.119952, -79.067253 43.119281, -79.06715 43.119128, -79.067087 43.11908, -79.066938 43.119042, -79.066876 43.119056, -79.066841 43.119083, -79.066786 43.11909, -79.066717 43.11907, -79.066613 43.118973, -79.066447 43.118862, -79.06635 43.118737, -79.066211 43.118675, -79.06608 43.11864, -79.065581 43.118613, -79.065352 43.118585, -79.064909 43.11844, -79.064643 43.118298, -79.064469 43.118194, -79.063825 43.117869, -79.063645 43.117751, -79.063496 43.11758, -79.063381 43.117402, -79.063168 43.117115, -79.063022 43.116858, -79.062742 43.116465, -79.062444 43.115958, -79.062286 43.115752, -79.062207 43.115649, -79.061975 43.115489, -79.06173 43.115207, -79.061576 43.115005, -79.061231 43.114706, -79.060817 43.114396, -79.060472 43.11416, -79.060111 43.113836, -79.059861 43.113407, -79.059288 43.112368, -79.059126 43.112002, -79.058927 43.11141, -79.058807 43.110993, -79.058604 43.110722, -79.058448 43.110431, -79.058265 43.110214, -79.058118 43.110128, -79.058055 43.110092, -79.057994 43.110013, -79.057986 43.110004, -79.057948 43.109963, -79.057843 43.109785, -79.057738 43.109516, -79.057702 43.109422, -79.05768 43.109342, -79.057662 43.109309, -79.057641 43.109229, -79.057553 43.108883, -79.057535 43.108812, -79.057522 43.108768, -79.057519 43.108748, -79.057514 43.108728, -79.057512 43.108703, -79.057482 43.108626, -79.057326 43.108484, -79.057181 43.108297, -79.057147 43.108203, -79.057132 43.107964, -79.05715 43.107867, -79.05728 43.107676, -79.057395 43.107314, -79.057423 43.107166, -79.057393 43.106967, -79.057305 43.106876, -79.057234 43.106769, -79.057224 43.106738, -79.057229 43.10667, -79.057214 43.106631, -79.057168 43.106611, -79.057106 43.106598, -79.057043 43.106542, -79.05692 43.106384, -79.056804 43.106075, -79.056747 43.105923, -79.056734 43.105809, -79.056769 43.10562, -79.056818 43.105483, -79.056882 43.10536, -79.056976 43.105253, -79.056981 43.105144, -79.056972 43.105127, -79.057089 43.104882, -79.057196 43.104696, -79.057206 43.104624, -79.057186 43.104555, -79.057073 43.104436, -79.057048 43.104374, -79.057074 43.103915, -79.057095 43.103735, -79.05715 43.103528, -79.057261 43.103362, -79.057476 43.103084, -79.057702 43.102851, -79.058042 43.102625, -79.058325 43.102487, -79.058633 43.102267, -79.058823 43.10205, -79.058954 43.10185, -79.059186 43.101189, -79.059318 43.100774, -79.059442 43.100483, -79.05956 43.100351, -79.059712 43.100206, -79.059927 43.100033, -79.060232 43.099812, -79.06037 43.099687, -79.060564 43.099466, -79.060789 43.099234, -79.06142 43.098729, -79.061429 43.098717, -79.06169 43.098383, -79.061821 43.09803, -79.061863 43.097843, -79.061856 43.097635, -79.061762 43.097389, -79.061693 43.097258, -79.061652 43.097113, -79.061659 43.096753, -79.061686 43.0966, -79.061769 43.096448, -79.061929 43.096254, -79.062147 43.09603, -79.062213 43.095946, -79.062496 43.095585, -79.062741 43.095313, -79.062858 43.095128, -79.062955 43.094939, -79.06308 43.094628, -79.0631 43.094596, -79.063381 43.094168, -79.063492 43.093766, -79.063596 43.093337, -79.063883 43.092759, -79.063932 43.09269, -79.064036 43.092621, -79.064139 43.092524, -79.064209 43.09242, -79.064396 43.092074, -79.064479 43.091971, -79.064603 43.091881, -79.06477 43.091784, -79.064922 43.091735, -79.065005 43.091666, -79.065068 43.091528, -79.065116 43.09132, -79.065334 43.090811, -79.065492 43.090513, -79.065604 43.090306, -79.065701 43.090188, -79.065819 43.090085, -79.066106 43.089857, -79.066574 43.089485, -79.066652 43.089423, -79.066668 43.089411, -79.066682 43.089399, -79.06693 43.089202, -79.067162 43.089031, -79.067353 43.088891, -79.068364 43.08804, -79.068643 43.087736, -79.069025 43.087323, -79.069261 43.087095, -79.069566 43.086901, -79.069753 43.086797, -79.069954 43.086652, -79.070016 43.086555, -79.070071 43.08641, -79.070113 43.086195, -79.07012 43.08605, -79.070106 43.085939, -79.070044 43.085863, -79.069947 43.085801, -79.069794 43.085766, -79.069206 43.085752, -79.069032 43.085732, -79.068627 43.085514, -79.06853 43.085472, -79.068413 43.085438, -79.067762 43.085188, -79.067678 43.085133, -79.067512 43.085071, -79.067408 43.085064, -79.067249 43.085071, -79.067041 43.08505, -79.066792 43.085009, -79.066335 43.084884, -79.065992 43.084735, -79.06566 43.084618, -79.065536 43.084564, -79.066193 43.083599, -79.066327 43.08363, -79.06641 43.083595, -79.06657 43.083429, -79.066756 43.083353, -79.066971 43.083367, -79.067207 43.083436, -79.067371 43.08347, -79.067435 43.083484, -79.067601 43.083484, -79.067747 43.08347, -79.067851 43.083401, -79.067903 43.083318, -79.067872 43.083242, -79.067712 43.083159, -79.067539 43.083041, -79.067435 43.082937, -79.067151 43.082578, -79.06712 43.082563, -79.067027 43.08255, -79.066853 43.082578, -79.06672 43.082581, -79.066803 43.082111, -79.066816 43.082003, -79.066878 43.082021, -79.067207 43.082157, -79.067345 43.082188, -79.068196 43.08221, -79.069328 43.082069, -79.069531 43.082076, -79.069695 43.082127, -79.070648 43.082916, -79.071533 43.083675, -79.071693 43.083767, -79.0718 43.08374, -79.071976 43.083561, -79.072021 43.083477, -79.072136 43.083401, -79.072227 43.083271, -79.07225 43.08308, -79.072258 43.082897, -79.072265 43.082725, -79.073303 43.08173, -79.073624 43.081436, -79.073814 43.081296, -79.073921 43.081234, -79.074203 43.081219, -79.074417 43.081196, -79.07473 43.080994, -79.074913 43.080898, -79.074997 43.08075, -79.075082 43.080541, -79.075089 43.08049, -79.074936 43.080036, -79.074913 43.080006, -79.074867 43.079987, -79.073959 43.079811, -79.073792 43.079739, -79.07325 43.079426, -79.072724 43.07909, -79.072433 43.078869, -79.07206 43.078678, -79.071884 43.078621, -79.07177 43.078609, -79.071632 43.07859, -79.071266 43.078598, -79.070786 43.078506, -79.070389 43.078407, -79.070007 43.078373, -79.069664 43.078423, -79.069344 43.078499, -79.069153 43.07859, -79.068665 43.078987, -79.06852 43.07906, -79.068359 43.079075, -79.068192 43.079044, -79.067986 43.078968, -79.067749 43.078903, -79.067501 43.078861, -79.067293 43.078848, -79.067116 43.078888, -79.06678 43.079018, -79.066596 43.079029, -79.066299 43.079033, -79.066055 43.079002, -79.06601 43.078995, -79.065826 43.078953, -79.065636 43.078876, -79.065145 43.078641, -79.064949 43.078541, -79.064835 43.078506, -79.064598 43.078514, -79.064153 43.078586, -79.063202 43.078449, -79.062492 43.078472, -79.061646 43.07848, -79.061264 43.078526, -79.061028 43.078568, -79.060811 43.078691, -79.060692 43.078785, -79.060616 43.078861, -79.060493 43.079025, -79.060425 43.079205, -79.060417 43.079361, -79.06044 43.079487, -79.060524 43.079659, -79.060608 43.079765, -79.06073 43.07988, -79.060852 43.079979, -79.061066 43.080063, -79.062126 43.080399, -79.062492 43.080566, -79.063232 43.080933, -79.063683 43.081207, -79.063972 43.081429, -79.06424 43.081545, -79.064186 43.081611, -79.064002 43.081791, -79.063971 43.081823, -79.063296 43.082539, -79.063061 43.082789, -79.063048 43.082775, -79.062496 43.082143, -79.062309 43.08203, -79.062173 43.081809, -79.062049 43.08164, -79.061831 43.08149, -79.061765 43.081362, -79.061689 43.081279, -79.061578 43.081224, -79.061391 43.081189, -79.061177 43.081169, -79.061024 43.081134, -79.061 43.081119, -79.060913 43.081065, -79.060789 43.081023, -79.06056 43.080996, -79.060117 43.080968, -79.059861 43.080933, -79.058701 43.080736, -79.057987 43.080653, -79.056955 43.080556, -79.056214 43.080529, -79.055241 43.080518, -79.054452 43.080449, -79.053919 43.080373, -79.053462 43.080324, -79.052752 43.080203, -79.052733 43.080198, -79.050285 43.079581, -79.050038 43.079516, -79.049652 43.079414, -79.048934 43.07926, -79.048345 43.07926, -79.047775 43.079222, -79.046558 43.079783, -79.045993 43.079515, -79.046404 43.079203, -79.046778 43.078816, -79.046813 43.078705, -79.04682 43.078615, -79.046785 43.078477, -79.046681 43.078359, -79.04619 43.078151, -79.045528 43.077975, -79.04485 43.077906, -79.043873 43.07785, -79.043222 43.07785, -79.040045 43.077903, -79.039989 43.077904, -79.037356 43.077926, -79.031656 43.078086, -79.028651 43.078148, -79.026571 43.078205, -79.02626 43.07818, -79.026177 43.078189, -79.02614 43.078212, -79.026016 43.078345, -79.025966 43.078359, -79.025893 43.078359, -79.025806 43.07834, -79.025746 43.078308, -79.025709 43.078244, -79.025673 43.078221, -79.025594 43.078212, -79.025457 43.078212, -79.025184 43.078229, -79.023508 43.078255, -79.022411 43.078224, -79.02194 43.078183, -79.021552 43.078134, -79.021032 43.078044, -79.02056 43.077945, -79.020284 43.077901, -79.018817 43.077628, -79.018577 43.077584, -79.016823 43.077303, -79.014453 43.076909, -79.012074 43.076525, -79.011562 43.076428, -79.010762 43.076196, -79.010028 43.075954, -79.008698 43.075463, -79.005813 43.074449, -79.005484 43.074349, -79.004044 43.073996, -79.0038 43.073965, -79.003711 43.073954, -79.003414 43.073968, -79.00297 43.073892, -79.00197 43.073778, -79.00073 43.073688, -79.000197 43.073674, -78.999992 43.07365, -78.999147 43.073634, -78.998318 43.073656, -78.997781 43.073707, -78.997543 43.07373, -78.995595 43.073868, -78.9949 43.073907, -78.993873 43.073996, -78.993594 43.074006, -78.993296 43.074006, -78.993186 43.073985, -78.992881 43.07396, -78.991504 43.074042, -78.990945 43.074046, -78.990925 43.074047, -78.990904 43.074046, -78.990597 43.074049, -78.990516 43.074049, -78.989592 43.074049, -78.989109 43.074049, -78.988748 43.074056, -78.9885 43.07407, -78.988408 43.074056, -78.988394 43.074013, -78.988394 43.073971, -78.988458 43.073886, -78.988458 43.073829, -78.988387 43.073737, -78.988302 43.073681, -78.988188 43.073652, -78.987636 43.073659, -78.987317 43.073652, -78.986906 43.07361, -78.98689 43.073611, -78.986715 43.073631, -78.986634 43.07362, -78.986503 43.07356, -78.986365 43.07355, -78.986194 43.073578, -78.985885 43.073552, -78.985588 43.073528, -78.98544 43.073505, -78.985068 43.07345, -78.985004 43.073422, -78.98499 43.073288, -78.984983 43.072665, -78.984969 43.072615, -78.984883 43.072626, -78.98444 43.072685, -78.984187 43.072719, -78.983615 43.072788, -78.98283 43.072884, -78.981958 43.072955, -78.981774 43.072962, -78.981604 43.072919, -78.981139 43.072877, -78.980903 43.072856, -78.980712 43.072863, -78.980682 43.072875, -78.980669 43.072918, -78.980665 43.073039, -78.98064 43.073105, -78.98057 43.073111, -78.980507 43.073103, -78.980456 43.073096, -78.980187 43.073047, -78.980157 43.073034, -78.980131 43.073004, -78.980118 43.072893, -78.980104 43.072878, -78.97962 43.072883, -78.979591 43.072874, -78.979544 43.072859, -78.979525 43.07284, -78.979373 43.072841, -78.979051 43.072863, -78.978998 43.072873, -78.978842 43.072983, -78.9787 43.073047, -78.978544 43.073096, -78.978048 43.073167, -78.977729 43.073167, -78.97746 43.073181, -78.977269 43.073146, -78.97666 43.073146, -78.976538 43.073153, -78.976304 43.073168, -78.975903 43.07318, -78.975712 43.073162, -78.975631 43.073162, -78.975564 43.073137, -78.975551 43.073075, -78.975551 43.073007, -78.975601 43.072958, -78.975699 43.07294, -78.975806 43.072935, -78.975896 43.072931, -78.976834 43.07282, -78.97687 43.072807, -78.976933 43.072659, -78.976974 43.072519, -78.976994 43.072453, -78.977146 43.072237, -78.977192 43.072099, -78.977192 43.072057, -78.977171 43.072011, -78.977086 43.071968, -78.97699 43.071968, -78.976887 43.071915, -78.974761 43.071878, -78.974433 43.071873, -78.974188 43.071873, -78.973328 43.071841, -78.972474 43.071873, -78.972301 43.071905, -78.972084 43.071997, -78.971985 43.072014, -78.971805 43.072025, -78.971493 43.072004, -78.971294 43.072064, -78.971094 43.072187, -78.97059 43.072392, -78.970385 43.072451, -78.969957 43.072521, -78.969734 43.072515, -78.969564 43.072498, -78.96878 43.072553, -78.968553 43.072595, -78.967441 43.072737, -78.967073 43.072751, -78.966719 43.072751, -78.966301 43.072708, -78.966173 43.07268, -78.966109 43.072652, -78.965982 43.07263, -78.9659 43.072636, -78.965881 43.072638, -78.965755 43.07263, -78.965528 43.072515, -78.965429 43.072503, -78.965316 43.072553, -78.965247 43.07255, -78.965139 43.072496, -78.964997 43.072482, -78.96482 43.072482, -78.964742 43.072453, -78.964636 43.072376, -78.964579 43.072291, -78.964473 43.072248, -78.964126 43.072213, -78.96341 43.072191, -78.96312 43.072191, -78.963035 43.072188, -78.962539 43.07217, -78.96215 43.072135, -78.962057 43.072092, -78.96193 43.072007, -78.96188 43.07193, -78.961866 43.071844, -78.961873 43.071646, -78.961795 43.071611, -78.961484 43.071611, -78.961243 43.071632, -78.961044 43.071665, -78.960932 43.071648, -78.960879 43.071619, -78.960783 43.071462, -78.960669 43.071413, -78.960513 43.071377, -78.95933 43.071193, -78.958714 43.071186, -78.958282 43.0712, -78.958041 43.071193, -78.95797 43.071179, -78.957906 43.071122, -78.957807 43.070924, -78.957708 43.070846, -78.957581 43.070811, -78.957177 43.07079, -78.957127 43.070811, -78.957106 43.070882, -78.957049 43.070995, -78.956964 43.071066, -78.95683 43.07108, -78.956716 43.071037, -78.956589 43.071009, -78.956263 43.071044, -78.956148 43.071044, -78.956044 43.07103, -78.955951 43.071044, -78.955859 43.071044, -78.955689 43.071016, -78.955307 43.071002, -78.954719 43.070967, -78.954379 43.070967, -78.954329 43.070988, -78.954315 43.071186, -78.95428 43.071321, -78.954216 43.071406, -78.953826 43.071632, -78.95355 43.071738, -78.953281 43.07176, -78.952976 43.071724, -78.952744 43.071748, -78.952608 43.071802, -78.95236 43.07188, -78.95202 43.072071, -78.951715 43.07222, -78.951648 43.072305, -78.951616 43.072413, -78.951599 43.072583, -78.951525 43.072817, -78.951446 43.072923, -78.951333 43.072971, -78.951173 43.07299, -78.950889 43.073002, -78.950624 43.072965, -78.949721 43.072721, -78.948943 43.072505, -78.948759 43.072533, -78.948518 43.072632, -78.948383 43.072668, -78.948312 43.072668, -78.948142 43.072618, -78.947675 43.072597, -78.947571 43.07255, -78.947498 43.072484, -78.947207 43.072292, -78.946761 43.072101, -78.946591 43.072073, -78.946421 43.072066, -78.946216 43.072045, -78.946131 43.072016, -78.945897 43.071903, -78.945715 43.071709, -78.945676 43.071606, -78.945717 43.071501, -78.945854 43.07128, -78.945854 43.071216, -78.94572 43.071146, -78.945351 43.07099, -78.945302 43.070947, -78.945274 43.070884, -78.945359 43.070678, -78.945337 43.070608, -78.945166 43.070543, -78.945184 43.069825, -78.945242 43.067673, -78.945262 43.066956, -78.945395 43.062623, -78.945631 43.062728, -78.945775 43.062675, -78.946086 43.062768, -78.946172 43.062933, -78.946799 43.062975, -78.948205 43.063314, -78.948652 43.063381, -78.94942 43.063507, -78.950242 43.06353, -78.951216 43.0635, -78.951285 43.063424, -78.952568 43.063316, -78.953232 43.063107, -78.953309 43.062998, -78.953632 43.062953, -78.953796 43.062994, -78.95426 43.062979, -78.955198 43.062877, -78.955929 43.062701, -78.95631 43.062427, -78.95679 43.062379, -78.957087 43.062471, -78.95801 43.062089, -78.958412 43.061892, -78.958999 43.061769, -78.959684 43.06191, -78.959932 43.06187, -78.960119 43.061872, -78.96044 43.061883, -78.961546 43.061394, -78.961581 43.061186, -78.96188 43.06119, -78.962177 43.061271, -78.9629 43.061117, -78.963327 43.06109, -78.963628 43.061023, -78.963646 43.060908, -78.963856 43.060889, -78.964615 43.060785, -78.965308 43.060592, -78.966126 43.060494, -78.966951 43.060423, -78.966994 43.06049, -78.967532 43.06053, -78.967663 43.06039, -78.96776 43.060402, -78.968281 43.060503, -78.968559 43.060433, -78.968303 43.059086, -78.966936 43.059104, -78.966782 43.055645, -78.966757 43.055095, -78.966749 43.054896, -78.969461 43.055367, -78.985987 43.054909, -78.986014 43.050166, -78.987373 43.05136, -78.987543 43.051509, -78.988303 43.052178, -78.98891 43.052711, -78.989607 43.053323, -78.990042 43.053706, -78.990351 43.053661, -78.990614 43.053623, -78.990588 43.051104, -78.990588 43.050663, -78.990588 43.05025, -78.990628 43.048613, -78.991363 43.048595, -78.991849 43.048533, -78.992156 43.048491, -78.992449 43.048402, -78.992631 43.048295, -78.992845 43.048137, -78.992962 43.047988, -78.993022 43.047834, -78.99305 43.047657, -78.993018 43.04748, -78.992906 43.047271, -78.992734 43.047089, -78.992543 43.046963, -78.992282 43.046861, -78.992012 43.046819, -78.991746 43.046824, -78.991504 43.046861, -78.991299 43.046945, -78.991108 43.047047, -78.990945 43.04721, -78.990815 43.047396, -78.990759 43.047573, -78.990707 43.047816, -78.990628 43.048295, -78.990571 43.047363, -78.990483 43.046759, -78.990332 43.046168, -78.990119 43.045363, -78.989762 43.044433, -78.989213 43.043426, -78.989053 43.043188, -78.988972 43.043066, -78.988652 43.042589, -78.988084 43.041747, -78.987812 43.041337, -78.987298 43.040562, -78.985763 43.038284, -78.985188 43.037453, -78.984526 43.036482, -78.984228 43.036046, -78.984199 43.036003, -78.983974 43.03566, -78.982761 43.033815, -78.980522 43.030468, -78.979366 43.028759, -78.976718 43.024734, -78.976648 43.024608, -78.975804 43.023405, -78.975027 43.022181, -78.974549 43.021542, -78.975395 43.021526, -78.975737 43.021519, -78.976679 43.021502, -78.977577 43.021485, -78.977073 43.021008, -78.975725 43.019733, -78.974353 43.018432, -78.971457 43.01569, -78.970675 43.014896, -78.970096 43.014307, -78.969206 43.013475, -78.968079 43.012402, -78.967286 43.01167, -78.96706 43.011471, -78.966828 43.011276, -78.96659 43.011085, -78.966347 43.010898, -78.966098 43.010715, -78.965843 43.010536, -78.965583 43.010362, -78.965318 43.010191, -78.965313 43.009544, -78.965262 43.007106, -78.965183 43.004744, -78.965146 43.003726, -78.965073 43.001868, -78.965064 43.00164, -78.965057 43.001488, -78.96482 42.99656, -78.9648 42.996031, -78.964751 42.994699, -78.964621 42.991709, -78.96462 42.991603, -78.964605 42.991027, -78.964526 42.988645, -78.964509 42.988129, -78.965973 42.988103, -78.966103 42.988102, -78.966174 42.988102, -78.966247 42.988101, -78.966311 42.988101, -78.96639 42.988104, -78.966464 42.988113, -78.966538 42.988123, -78.966608 42.988133, -78.966679 42.988144, -78.966749 42.988153, -78.966839 42.988156, -78.966911 42.988156, -78.96698 42.988149, -78.967052 42.988141, -78.967125 42.988129, -78.967196 42.988115, -78.967261 42.988095, -78.967389 42.988049, -78.967513 42.987991, -78.967563 42.987961, -78.967628 42.987924, -78.967691 42.987893, -78.967749 42.987845, -78.967799 42.987812, -78.967835 42.987767, -78.967869 42.98772, -78.967895 42.987669, -78.96792 42.987616, -78.967941 42.987561, -78.967955 42.987505, -78.967971 42.987453, -78.967981 42.987402, -78.967989 42.98735, -78.96799 42.987299, -78.967982 42.987196, -78.967978 42.98713, -78.967976 42.987075, -78.967974 42.987024, -78.967974 42.986971, -78.967975 42.986919, -78.967971 42.98682, -78.967968 42.986704, -78.967969 42.986601, -78.96797 42.986489, -78.967967 42.986383, -78.967965 42.986285, -78.967963 42.98624, -78.96797 42.986192, -78.967973 42.986136, -78.967985 42.986065, -78.967994 42.986029, -78.968019 42.985978, -78.96805 42.985926, -78.968091 42.985881, -78.968138 42.985837, -78.96819 42.985798, -78.968221 42.98578, -78.968343 42.985716, -78.968404 42.985687, -78.968471 42.985664, -78.968558 42.985641, -78.968634 42.985631, -78.96873 42.985626, -78.968824 42.98563, -78.968897 42.985638, -78.968971 42.98565, -78.969043 42.985662, -78.969116 42.98567, -78.969195 42.98568, -78.969264 42.985689, -78.969331 42.985693, -78.969405 42.985704, -78.969477 42.985702, -78.969552 42.985711, -78.969619 42.985713, -78.96969 42.985716, -78.969765 42.985715, -78.969852 42.985708, -78.969913 42.9857, -78.969987 42.985695, -78.970055 42.985686, -78.970149 42.985672, -78.97022 42.985657, -78.970291 42.985642, -78.970362 42.985626, -78.970509 42.985595, -78.970576 42.985574, -78.970706 42.985524, -78.970831 42.985476, -78.970951 42.985428, -78.971084 42.985376, -78.97105 42.98533, -78.971015 42.985276, -78.971002 42.985214, -78.970991 42.985161, -78.970989 42.985116, -78.970985 42.985071, -78.970978 42.985018, -78.970975 42.984957, -78.970978 42.984898, -78.970977 42.984836, -78.970982 42.984784, -78.970967 42.98473, -78.970954 42.98466, -78.970943 42.984611, -78.97095 42.98456, -78.970942 42.984497, -78.970947 42.984447, -78.970943 42.984399, -78.970932 42.984348, -78.970926 42.984302, -78.970906 42.984247, -78.970907 42.984201, -78.970902 42.984151, -78.97089 42.984086, -78.972531 42.984048, -78.976064 42.983998, -78.976967 42.983986, -78.980549 42.983927, -78.984289 42.983876, -78.992291 42.983766, -78.99387 42.983756, -78.996483 42.983725, -78.996864 42.983721, -78.998744 42.983702, -79.000581 42.983659, -79.000819 42.983351, -78.998589 42.9823, -78.997455 42.981618, -78.994863 42.980281, -78.993844 42.979754, -78.990233 42.977861, -78.987815 42.976812, -78.983465 42.975439, -78.983439 42.975227, -78.983399 42.974899, -78.983187 42.974839, -78.982446 42.97463, -78.982133 42.974556, -78.981851 42.974439, -78.981493 42.974348, -78.9813 42.974308, -78.981107 42.974213, -78.980824 42.974096, -78.980631 42.974056, -78.980206 42.973943, -78.979835 42.97377, -78.979637 42.973651, -78.979576 42.973614, -78.979175 42.973408, -78.978996 42.973385, -78.97884 42.973312, -78.978632 42.973185, -78.978426 42.973013, -78.978161 42.972665, -78.977909 42.97252, -78.977731 42.972475, -78.977486 42.972342, -78.977347 42.972154, -78.977237 42.971988, -78.977067 42.971872, -78.976798 42.971831, -78.976642 42.97177, -78.976435 42.971625, -78.975903 42.971171, -78.975822 42.971083, -78.975756 42.971038, -78.975697 42.970939, -78.975586 42.970878, -78.975482 42.970828, -78.975312 42.970711, -78.975098 42.970528, -78.974979 42.970445, -78.97486 42.9704, -78.974756 42.970388, -78.974281 42.970176, -78.97414 42.970104, -78.974029 42.97001, -78.973926 42.969899, -78.973718 42.969771, -78.973222 42.969444, -78.973089 42.969327, -78.972692 42.968874, -78.972553 42.96867, -78.97234 42.968427, -78.971389 42.967458, -78.970918 42.966977, -78.970844 42.966895, -78.970764 42.966746, -78.970654 42.96663, -78.970063 42.96612, -78.969812 42.965953, -78.969724 42.965821, -78.969673 42.965722, -78.969548 42.965573, -78.969341 42.965445, -78.969207 42.965356, -78.969149 42.965229, -78.969068 42.965152, -78.968787 42.964963, -78.968468 42.964763, -78.968054 42.964463, -78.967788 42.964264, -78.967536 42.964091, -78.967248 42.963886, -78.966915 42.96368, -78.966457 42.963276, -78.965717 42.962787, -78.965427 42.962653, -78.965108 42.962541, -78.964676 42.962423, -78.964319 42.962294, -78.963904 42.962065, -78.963695 42.962014, -78.963539 42.961991, -78.963381 42.962028, -78.963276 42.962077, -78.963209 42.962093, -78.963112 42.962076, -78.962896 42.962025, -78.962769 42.962029, -78.962721 42.962045, -78.962686 42.962056, -78.962573 42.962105, -78.962468 42.962192, -78.962436 42.962296, -78.962435 42.962362, -78.962449 42.962471, -78.962438 42.962707, -78.962407 42.962784, -78.962346 42.962833, -78.962234 42.962854, -78.961921 42.962808, -78.961652 42.962789, -78.961398 42.96282, -78.961069 42.962807, -78.960718 42.962821, -78.960211 42.962745, -78.959756 42.962665, -78.959323 42.962656, -78.95907 42.962583, -78.958892 42.962505, -78.958803 42.962433, -78.958639 42.962409, -78.95849 42.962403, -78.958311 42.962374, -78.958169 42.962323, -78.958066 42.962246, -78.957985 42.962146, -78.957852 42.962052, -78.957659 42.961957, -78.957556 42.961852, -78.957543 42.961709, -78.957477 42.961643, -78.957218 42.961454, -78.957042 42.961211, -78.956946 42.961177, -78.956878 42.961204, -78.956759 42.961192, -78.956677 42.96117, -78.956558 42.961098, -78.956522 42.96102, -78.95659 42.960983, -78.956858 42.961018, -78.956963 42.961002, -78.957023 42.960953, -78.95707 42.960865, -78.95703 42.960525, -78.956922 42.960211, -78.956894 42.960079, -78.956955 42.959986, -78.957024 42.95986, -78.957018 42.959794, -78.956958 42.959761, -78.956891 42.959733, -78.956787 42.959705, -78.956705 42.959693, -78.956646 42.959654, -78.956536 42.959544, -78.956411 42.959372, -78.956362 42.959147, -78.956348 42.959043, -78.956282 42.959015, -78.956192 42.959008, -78.956102 42.959008, -78.955946 42.958946, -78.955791 42.958813, -78.95571 42.958752, -78.955571 42.959108, -78.953828 42.958721, -78.953706 42.958901, -78.953638 42.958961, -78.953555 42.958972, -78.953443 42.958954, -78.953294 42.958937, -78.953152 42.958963, -78.952889 42.959038, -78.952425 42.959122, -78.951961 42.959135, -78.951453 42.959142, -78.9512 42.959118, -78.95096 42.959149, -78.950684 42.959125, -78.950491 42.959063, -78.950155 42.959044, -78.948804 42.958907, -78.94826 42.958821, -78.947918 42.958719, -78.947531 42.958579, -78.947294 42.958445, -78.947065 42.958301, -78.946843 42.958113, -78.946637 42.957902, -78.946446 42.957692, -78.946301 42.957411, -78.946265 42.95729, -78.946213 42.957251, -78.946124 42.957223, -78.946064 42.957244, -78.946019 42.957272, -78.945972 42.957408, -78.945924 42.957617, -78.945848 42.95772, -78.945749 42.957868, -78.945658 42.957955, -78.945515 42.95802, -78.94526 42.9581, -78.944405 42.958275, -78.944061 42.958294, -78.943674 42.958236, -78.943591 42.958252, -78.943057 42.958533, -78.942824 42.958581, -78.942548 42.958606, -78.942174 42.95862, -78.941629 42.958577, -78.941383 42.958509, -78.941227 42.95847, -78.941055 42.958485, -78.939851 42.958569, -78.939545 42.958561, -78.939319 42.958652, -78.939093 42.95876, -78.9388 42.958928, -78.938655 42.959119, -78.938454 42.959551, -78.938281 42.960148, -78.938168 42.960768, -78.938165 42.960927, -78.938187 42.960966, -78.938276 42.961005, -78.938343 42.961055, -78.938364 42.961099, -78.938349 42.961159, -78.938355 42.961242, -78.93833 42.96139, -78.938385 42.961742, -78.938441 42.961962, -78.938513 42.962182, -78.938629 42.962391, -78.93871 42.962491, -78.938784 42.962524, -78.938881 42.962574, -78.939029 42.962674, -78.939147 42.962796, -78.939197 42.962939, -78.93921 42.96306, -78.939251 42.963291, -78.93931 42.963379, -78.93933 42.963517, -78.939315 42.963561, -78.939329 42.963627, -78.939401 42.963775, -78.939482 42.963864, -78.939902 42.963796, -78.940141 42.96377, -78.940283 42.96376, -78.940394 42.963855, -78.9404 42.963948, -78.94031 42.963997, -78.939951 42.964021, -78.939689 42.964063, -78.939606 42.964101, -78.939635 42.964162, -78.939715 42.964272, -78.939759 42.964382, -78.939772 42.964498, -78.939745 42.964799, -78.939793 42.965069, -78.93986 42.965141, -78.939934 42.965169, -78.940009 42.965153, -78.940077 42.965115, -78.940227 42.965045, -78.940498 42.964948, -78.940865 42.964874, -78.941044 42.964864, -78.941155 42.964959, -78.941162 42.965008, -78.941027 42.96504, -78.940839 42.965099, -78.940606 42.965185, -78.940441 42.965261, -78.940334 42.965386, -78.940288 42.965495, -78.940346 42.965622, -78.940484 42.965848, -78.940814 42.966312, -78.941005 42.966566, -78.941262 42.96681, -78.941469 42.966965, -78.941513 42.967081, -78.941548 42.967202, -78.941547 42.967295, -78.941574 42.967466, -78.94164 42.967603, -78.941713 42.967708, -78.941794 42.96778, -78.941816 42.967813, -78.941861 42.967814, -78.941906 42.967765, -78.94193 42.967693, -78.942022 42.967529, -78.942059 42.967513, -78.942217 42.967482, -78.942209 42.967509, -78.942171 42.967553, -78.942102 42.967656, -78.942116 42.967722, -78.942101 42.967761, -78.942063 42.967793, -78.942039 42.967848, -78.942039 42.967908, -78.942105 42.967997, -78.942149 42.968019, -78.942194 42.968025, -78.942261 42.968025, -78.94235 42.968043, -78.942395 42.968065, -78.942454 42.968137, -78.942467 42.968225, -78.942519 42.968302, -78.94251 42.968368, -78.942479 42.968433, -78.942396 42.96851, -78.942373 42.968553, -78.942379 42.968619, -78.942415 42.968729, -78.942445 42.968757, -78.942467 42.968768, -78.942504 42.968769, -78.942857 42.9687, -78.942999 42.968696, -78.943103 42.96874, -78.943192 42.968785, -78.943258 42.96884, -78.943331 42.96894, -78.943533 42.969468, -78.943591 42.969606, -78.943619 42.969711, -78.943824 42.970036, -78.943868 42.97008, -78.944068 42.970181, -78.944157 42.970253, -78.944185 42.970368, -78.944191 42.970404, -78.944184 42.970462, -78.944167 42.970566, -78.944136 42.970621, -78.943971 42.97068, -78.94394 42.970745, -78.943909 42.970855, -78.943899 42.971014, -78.944227 42.97105, -78.944245 42.971357, -78.943968 42.971399, -78.943995 42.971602, -78.943808 42.971628, -78.943953 42.972051, -78.943987 42.972151, -78.94414 42.97246, -78.944262 42.972812, -78.944425 42.973385, -78.944695 42.973348, -78.945198 42.974181, -78.945277 42.974385, -78.945317 42.97477, -78.945322 42.974945, -78.94586 42.974955, -78.946264 42.975902, -78.946351 42.976139, -78.946439 42.976266, -78.946496 42.976459, -78.946539 42.976531, -78.946599 42.976558, -78.946703 42.976592, -78.94677 42.976598, -78.950388 42.976533, -78.950393 42.976709, -78.94685 42.976785, -78.946685 42.976795, -78.946565 42.976827, -78.946512 42.976887, -78.946496 42.976953, -78.946554 42.97708, -78.946657 42.977185, -78.946746 42.97724, -78.94682 42.977279, -78.946856 42.977357, -78.946877 42.977505, -78.946876 42.97756, -78.946883 42.977604, -78.946897 42.977626, -78.946927 42.977643, -78.946979 42.977648, -78.947031 42.977682, -78.947044 42.977781, -78.946914 42.977999, -78.946921 42.978043, -78.948153 42.977899, -78.950357 42.977642, -78.950463 42.977533, -78.950701 42.977677, -78.95064 42.977759, -78.950632 42.977787, -78.950796 42.978804, -78.950467 42.978845, -78.950408 42.978823, -78.950385 42.978806, -78.950356 42.978745, -78.950359 42.978559, -78.950331 42.978449, -78.950259 42.978212, -78.950216 42.978107, -78.950164 42.978063, -78.950112 42.978057, -78.950008 42.978056, -78.948114 42.978257, -78.947118 42.978363, -78.947123 42.978545, -78.947136 42.978649, -78.947253 42.978848, -78.947418 42.978832, -78.947508 42.9793, -78.947366 42.979315, -78.947371 42.979513, -78.947405 42.979716, -78.947419 42.979852, -78.947438 42.980029, -78.947494 42.98026, -78.947501 42.980326, -78.947546 42.980343, -78.94768 42.980355, -78.947815 42.980345, -78.947994 42.980358, -78.948098 42.980391, -78.948187 42.980436, -78.948276 42.98047, -78.948463 42.980488, -78.94865 42.980473, -78.948778 42.980375, -78.948841 42.980156, -78.948864 42.980117, -78.948969 42.980102, -78.949037 42.98008, -78.949089 42.980097, -78.949103 42.980136, -78.949109 42.980224, -78.949085 42.980339, -78.949047 42.980432, -78.948963 42.980497, -78.948828 42.980584, -78.948707 42.980627, -78.94861 42.980631, -78.948438 42.980619, -78.94823 42.980585, -78.948162 42.980584, -78.948132 42.980644, -78.948138 42.980743, -78.948127 42.980952, -78.948124 42.981171, -78.948167 42.981298, -78.948211 42.981353, -78.948285 42.981387, -78.94839 42.981398, -78.948532 42.981405, -78.948659 42.981384, -78.948787 42.981336, -78.9489 42.981287, -78.949027 42.981277, -78.94949 42.981303, -78.949669 42.981304, -78.949811 42.981327, -78.94984 42.981355, -78.949862 42.981399, -78.949839 42.981443, -78.949801 42.981497, -78.949726 42.981546, -78.949673 42.981573, -78.949576 42.981578, -78.949381 42.981565, -78.94915 42.981542, -78.948955 42.981573, -78.948663 42.981648, -78.948505 42.981674, -78.948282 42.981639, -78.948245 42.981606, -78.948119 42.981528, -78.94798 42.981329, -78.947922 42.981159, -78.947887 42.980988, -78.947862 42.980664, -78.947759 42.980559, -78.947677 42.980525, -78.947632 42.980536, -78.94758 42.980574, -78.947541 42.980645, -78.947485 42.980941, -78.947466 42.981165, -78.947478 42.981364, -78.947573 42.982051, -78.94763 42.982271, -78.947672 42.98248, -78.947691 42.982667, -78.947694 42.982996, -78.947661 42.983205, -78.947568 42.98344, -78.947507 42.983544, -78.947446 42.983631, -78.947422 42.983752, -78.947442 42.983884, -78.94747 42.983999, -78.947505 42.984203, -78.947487 42.984422, -78.947485 42.984478, -78.947485 42.984532, -78.947519 42.984752, -78.94761 42.985208, -78.947659 42.985423, -78.947662 42.985741, -78.947628 42.985993, -78.947611 42.986164, -78.947617 42.986251, -78.947653 42.986383, -78.947688 42.986488, -78.947694 42.986592, -78.947592 42.986987, -78.947568 42.987097, -78.947493 42.987612, -78.947466 42.987919, -78.94745 42.988013, -78.947471 42.988101, -78.947453 42.988287, -78.947442 42.988529, -78.947447 42.988715, -78.947416 42.988808, -78.947346 42.988945, -78.947324 42.988956, -78.947204 42.988982, -78.947174 42.988999, -78.947166 42.989021, -78.947135 42.98907, -78.947073 42.989234, -78.947049 42.98936, -78.947047 42.989503, -78.947052 42.98953, -78.947068 42.989618, -78.947073 42.989739, -78.947033 42.989942, -78.946956 42.99009, -78.946908 42.990303, -78.946883 42.990484, -78.946728 42.990911, -78.946557 42.9913, -78.94621 42.992033, -78.945903 42.992601, -78.945413 42.99341, -78.945054 42.993978, -78.944717 42.994486, -78.944031 42.995409, -78.943198 42.996298, -78.943178 42.996314, -78.943162 42.996335, -78.94164 42.997905, -78.941246 42.998226, -78.940269 42.999108, -78.940209 42.999146, -78.940112 42.999139, -78.94006 42.999128, -78.93994 42.999127, -78.939826 42.99928, -78.939293 42.999752, -78.939276 42.999767, -78.937071 42.998527, -78.934474 42.997004, -78.934568 42.996839, -78.93465 42.996824, -78.93477 42.996825, -78.93486 42.99682, -78.933821 42.996257, -78.934143 42.995722, -78.934047 42.995682, -78.934055 42.995633, -78.9341 42.9956, -78.934213 42.995579, -78.934326 42.995514, -78.935612 42.99598, -78.935688 42.995926, -78.935741 42.995877, -78.935734 42.995827, -78.93569 42.995767, -78.935713 42.995701, -78.935888 42.995538, -78.935926 42.99551, -78.9361 42.995353, -78.936304 42.995178, -78.936554 42.994983, -78.936667 42.994885, -78.93675 42.994831, -78.936841 42.994787, -78.936961 42.994755, -78.937253 42.994115, -78.937278 42.994061, -78.937578 42.993443, -78.937655 42.993322, -78.937718 42.993103, -78.937842 42.992764, -78.937966 42.992452, -78.938112 42.992228, -78.93822 42.99196, -78.938374 42.991659, -78.938459 42.991462, -78.93852 42.991396, -78.93867 42.991359, -78.938798 42.991278, -78.938989 42.991005, -78.939096 42.990835, -78.939166 42.990699, -78.939206 42.990496, -78.939254 42.990288, -78.9393 42.990162, -78.939326 42.989931, -78.939373 42.989827, -78.939427 42.989691, -78.939526 42.989527, -78.939733 42.989215, -78.939833 42.988985, -78.939964 42.988723, -78.940033 42.988597, -78.940217 42.988335, -78.940286 42.988182, -78.940333 42.988045, -78.940448 42.987876, -78.940677 42.98751, -78.94077 42.987302, -78.940811 42.987022, -78.9408 42.986742, -78.940852 42.986309, -78.941001 42.985772, -78.940992 42.985376, -78.940966 42.98514, -78.940975 42.984997, -78.941037 42.984855, -78.941106 42.984779, -78.941181 42.98473, -78.941295 42.984616, -78.941464 42.981729, -78.941391 42.98158, -78.941328 42.981305, -78.941228 42.980964, -78.941151 42.980601, -78.941124 42.98043, -78.94115 42.980211, -78.941131 42.979969, -78.941082 42.979749, -78.94095 42.979523, -78.940722 42.979263, -78.940422 42.978877, -78.940283 42.978678, -78.940209 42.978622, -78.940112 42.978567, -78.939799 42.978487, -78.939725 42.978465, -78.939666 42.978426, -78.939651 42.978387, -78.939645 42.978349, -78.93966 42.978283, -78.939707 42.978179, -78.940101 42.977804, -78.940064 42.977754, -78.939701 42.977537, -78.939656 42.977569, -78.939567 42.977541, -78.9395 42.977491, -78.939412 42.977364, -78.939265 42.97721, -78.93914 42.977104, -78.939095 42.97706, -78.938969 42.977021, -78.938887 42.977009, -78.93876 42.97698, -78.93856 42.976842, -78.938486 42.976819, -78.938441 42.976819, -78.938411 42.976868, -78.938395 42.976928, -78.938349 42.976988, -78.938304 42.977021, -78.938132 42.976987, -78.938059 42.97692, -78.937971 42.976793, -78.93792 42.976661, -78.937916 42.976469, -78.937873 42.97632, -78.937792 42.976226, -78.937718 42.976154, -78.937438 42.975921, -78.937305 42.975827, -78.937134 42.975732, -78.936817 42.975472, -78.936574 42.975234, -78.936257 42.97488, -78.936177 42.974769, -78.936126 42.974681, -78.936142 42.97461, -78.936209 42.974589, -78.936247 42.974589, -78.936292 42.974567, -78.936322 42.974524, -78.936338 42.974485, -78.936294 42.974441, -78.935583 42.973952, -78.933436 42.974605, -78.933211 42.974181, -78.935456 42.973474, -78.935626 42.973618, -78.936024 42.973489, -78.936343 42.973612, -78.936403 42.973613, -78.936433 42.973591, -78.936442 42.973531, -78.936397 42.973508, -78.936346 42.973448, -78.936242 42.973397, -78.936146 42.973309, -78.936036 42.973149, -78.935789 42.973191, -78.935654 42.973228, -78.935579 42.973233, -78.935534 42.973211, -78.935491 42.973106, -78.935486 42.972963, -78.93551 42.972865, -78.935511 42.972777, -78.935482 42.972711, -78.935423 42.972644, -78.935245 42.972516, -78.935069 42.972307, -78.934938 42.972091, -78.93485 42.971975, -78.934509 42.97177, -78.93425 42.971597, -78.933955 42.971331, -78.933676 42.971016, -78.933374 42.970728, -78.933168 42.970469, -78.932985 42.970259, -78.932749 42.970037, -78.932527 42.969882, -78.932224 42.969632, -78.931848 42.969311, -78.931522 42.969105, -78.931263 42.968905, -78.93099 42.968684, -78.930414 42.968201, -78.92972 42.967636, -78.929234 42.967198, -78.928983 42.96696, -78.92868 42.96676, -78.928286 42.966565, -78.927998 42.96637, -78.926952 42.965846, -78.926664 42.965613, -78.926449 42.965474, -78.926249 42.965385, -78.926189 42.96539, -78.926107 42.965384, -78.926026 42.965317, -78.926042 42.965224, -78.925923 42.965184, -78.925677 42.96516, -78.925476 42.96512, -78.925394 42.965081, -78.925351 42.965079, -78.925095 42.964946, -78.92484 42.964822, -78.924816 42.964707, -78.924933 42.964552, -78.925132 42.964315, -78.925145 42.964267, -78.924612 42.9639, -78.924272 42.963678, -78.923975 42.963505, -78.923644 42.963677, -78.922529 42.962944, -78.922209 42.962701, -78.922029 42.962748, -78.92194 42.962676, -78.921786 42.962516, -78.921638 42.962388, -78.921431 42.962222, -78.921158 42.962044, -78.920758 42.961794, -78.920632 42.961705, -78.920664 42.961596, -78.920368 42.961368, -78.920716 42.961107, -78.920605 42.961051, -78.920486 42.961007, -78.920101 42.961245, -78.920041 42.961261, -78.919989 42.961239, -78.91993 42.961178, -78.919842 42.961073, -78.919391 42.960767, -78.918726 42.960279, -78.918111 42.959884, -78.917128 42.959195, -78.915981 42.958434, -78.915322 42.958039, -78.914982 42.957827, -78.914095 42.957167, -78.913496 42.956745, -78.912539 42.956306, -78.912524 42.956296, -78.91245 42.956248, -78.912354 42.956175, -78.911654 42.955571, -78.911492 42.955411, -78.911389 42.955289, -78.911032 42.954704, -78.910755 42.954224, -78.910521 42.953871, -78.910354 42.953573, -78.910187 42.953248, -78.909978 42.952752, -78.909826 42.952421, -78.909705 42.95202, -78.909599 42.951618, -78.909545 42.95125, -78.9095 42.950766, -78.909463 42.95025, -78.909484 42.949811, -78.909762 42.949719, -78.909785 42.949654, -78.909778 42.949621, -78.909756 42.949604, -78.909667 42.949598, -78.909357 42.945856, -78.909447 42.945868, -78.909456 42.945709, -78.909628 42.94571, -78.909648 42.945859, -78.909746 42.945854, -78.90972 42.945052, -78.909048 42.945074, -78.909029 42.944976, -78.90902 42.944926, -78.909656 42.944837, -78.90971 42.943784, -78.909523 42.943755, -78.909471 42.943754, -78.909418 42.943776, -78.909246 42.943796, -78.908992 42.943816, -78.908813 42.943815, -78.908686 42.943797, -78.908485 42.943746, -78.908411 42.943685, -78.90839 42.943641, -78.90918 42.943214, -78.909191 42.943125, -78.9093 42.942216, -78.909755 42.942247, -78.909724 42.940846, -78.909138 42.939341, -78.909116 42.939323, -78.909113 42.939277, -78.909055 42.939177, -78.908914 42.939088, -78.908862 42.939071, -78.908817 42.939071, -78.908794 42.939109, -78.908771 42.939186, -78.908777 42.939246, -78.908807 42.939269, -78.908598 42.939355, -78.907627 42.939753, -78.907507 42.939769, -78.907485 42.939757, -78.90644 42.937739, -78.907722 42.937393, -78.907805 42.937388, -78.907864 42.937394, -78.907908 42.937427, -78.90796 42.937494, -78.90801 42.93762, -78.908717 42.938779, -78.908761 42.938867, -78.908783 42.938895, -78.908827 42.938912, -78.908865 42.938896, -78.908902 42.938874, -78.908919 42.938775, -78.90884 42.938572, -78.908738 42.938373, -78.908577 42.938152, -78.907949 42.937203, -78.907912 42.937197, -78.907298 42.937291, -78.907231 42.937296, -78.907194 42.937284, -78.907164 42.937262, -78.90715 42.937191, -78.907158 42.937174, -78.907181 42.937163, -78.907263 42.937153, -78.907645 42.937063, -78.90769 42.937047, -78.90775 42.937009, -78.907766 42.936987, -78.907781 42.936949, -78.907774 42.936916, -78.907672 42.936783, -78.906519 42.935472, -78.906373 42.935328, -78.906328 42.935284, -78.906297 42.935258, -78.906037 42.935034, -78.905981 42.93499, -78.90541 42.934695, -78.905336 42.934678, -78.905171 42.934726, -78.904526 42.934891, -78.904484 42.934737, -78.905248 42.934578, -78.905183 42.934424, -78.904584 42.934512, -78.904473 42.934445, -78.904387 42.93422, -78.9042 42.934262, -78.904082 42.934157, -78.904039 42.934047, -78.904496 42.933957, -78.904445 42.933814, -78.904132 42.933868, -78.903936 42.933903, -78.904299 42.933642, -78.904122 42.933482, -78.903873 42.933617, -78.903335 42.93319, -78.90378 42.932875, -78.903833 42.932837, -78.901932 42.931452, -78.902062 42.931374, -78.90215 42.931316, -78.902345 42.931193, -78.902445 42.931146, -78.902515 42.931227, -78.902609 42.931334, -78.904403 42.932655, -78.904586 42.932915, -78.904692 42.932908, -78.904751 42.932905, -78.904855 42.932906, -78.904892 42.932955, -78.904928 42.933033, -78.904994 42.933115, -78.905021 42.933135, -78.905149 42.933227, -78.905319 42.93331, -78.90543 42.933377, -78.905511 42.933466, -78.9056 42.933549, -78.905888 42.933771, -78.906095 42.93391, -78.90625 42.933999, -78.906464 42.934154, -78.906787 42.934419, -78.90687 42.934481, -78.906981 42.934548, -78.907071 42.93456, -78.90716 42.934566, -78.907199 42.93458, -78.907286 42.934655, -78.907315 42.934694, -78.907339 42.934698, -78.907397 42.934689, -78.907413 42.934662, -78.907421 42.934629, -78.907421 42.934572, -78.907389 42.934423, -78.907395 42.93437, -78.907426 42.934272, -78.907448 42.934225, -78.907472 42.93419, -78.907513 42.934156, -78.907683 42.934071, -78.907811 42.934, -78.907894 42.933946, -78.90797 42.93387, -78.908039 42.933755, -78.908063 42.933746, -78.908091 42.933745, -78.908143 42.933762, -78.908188 42.933784, -78.908277 42.933812, -78.908388 42.933879, -78.908459 42.933866, -78.908538 42.933853, -78.90815 42.932845, -78.908041 42.932641, -78.907792 42.932282, -78.907698 42.932122, -78.907466 42.931642, -78.907315 42.93124, -78.907201 42.930888, -78.906841 42.929721, -78.906582 42.928884, -78.906409 42.928466, -78.906316 42.928179, -78.906144 42.927728, -78.906101 42.927568, -78.90606 42.927354, -78.906022 42.926881, -78.905945 42.926513, -78.905832 42.926127, -78.905733 42.925753, -78.904805 42.922945, -78.904742 42.922643, -78.904693 42.922434, -78.904652 42.922208, -78.904613 42.921802, -78.904584 42.921263, -78.904573 42.920953, -78.904568 42.920808, -78.904538 42.920379, -78.904418 42.919867, -78.904236 42.919125, -78.903977 42.917991, -78.903921 42.917716, -78.903844 42.917403, -78.903788 42.917144, -78.903707 42.916578, -78.903673 42.916375, -78.903636 42.916331, -78.903592 42.916264, -78.90349 42.916126, -78.903417 42.915972, -78.90339 42.915818, -78.903371 42.915609, -78.903292 42.915411, -78.903228 42.915174, -78.903193 42.915042, -78.903372 42.915022, -78.902938 42.912857, -78.902648 42.912827, -78.90205 42.912759, -78.901676 42.912854, -78.90166 42.912251, -78.901624 42.910225, -78.901589 42.908594, -78.901592 42.907397, -78.901641 42.906874, -78.901687 42.906388, -78.901698 42.906288, -78.901708 42.906187, -78.901856 42.904797, -78.901923 42.903781, -78.901947 42.903194, -78.90195 42.902996, -78.901923 42.902837, -78.90186 42.902578, -78.901802 42.902424, -78.901621 42.902071, -78.901862 42.901936, -78.90209 42.902146, -78.902127 42.902158, -78.902165 42.902158, -78.902181 42.902098, -78.902164 42.90173, -78.902175 42.901494, -78.902162 42.901373, -78.902126 42.901296, -78.90209 42.901175, -78.902085 42.901043, -78.902089 42.900741, -78.902062 42.900598, -78.902026 42.90046, -78.901999 42.90029, -78.902084 42.899637, -78.90214 42.899385, -78.902149 42.899337, -78.902548 42.899225, -78.902188 42.896561, -78.90188 42.896473, -78.901814 42.896351, -78.900775 42.894443, -78.900717 42.894349, -78.90054 42.894167, -78.900259 42.893994, -78.898923 42.893057, -78.897734 42.892214, -78.895851 42.890925, -78.895303 42.890494, -78.893119 42.888656, -78.892298 42.887964, -78.890658 42.887736, -78.890211 42.887341, -78.890551 42.887111, -78.890277 42.886922, -78.889983 42.887123, -78.88952 42.886701, -78.889928 42.886326, -78.889967 42.886255, -78.890267 42.886131, -78.890674 42.884921, -78.891093 42.88437, -78.890697 42.884367, -78.889787 42.882937, -78.88945 42.882231, -78.888654 42.880563, -78.887857 42.878896, -78.886649 42.878676, -78.886991 42.878005, -78.882191 42.877482, -78.88033 42.876004, -78.882276 42.876005, -78.88234 42.876067, -78.882379 42.876105, -78.882415 42.876177, -78.88245 42.876281, -78.882545 42.876436, -78.882617 42.876574, -78.882698 42.876662, -78.882838 42.876773, -78.883073 42.876995, -78.883177 42.877084, -78.883243 42.877122, -78.883317 42.877145, -78.883852 42.876859, -78.883999 42.876992, -78.883599 42.877241, -78.883939 42.877491, -78.887238 42.877753, -78.88975 42.877952, -78.890578 42.877532, -78.890452 42.8773, -78.890108 42.876669, -78.889312 42.876981, -78.88924 42.877272, -78.888077 42.877201, -78.888119 42.876922, -78.888462 42.876914, -78.890387 42.875974, -78.89035 42.87593, -78.890299 42.875891, -78.890233 42.875819, -78.890018 42.875719, -78.889885 42.875646, -78.889736 42.875601, -78.889581 42.875534, -78.889476 42.875505, -78.88941 42.875467, -78.88927 42.875361, -78.889152 42.875221, -78.889033 42.875064, -78.888542 42.874472, -78.887927 42.87373, -78.887364 42.873191, -78.885475 42.870934, -78.885273 42.870948, -78.883823 42.871649, -78.883752 42.871489, -78.883566 42.871119, -78.883474 42.870904, -78.883456 42.870739, -78.883488 42.87063, -78.883624 42.870571, -78.884007 42.870461, -78.884307 42.87042, -78.88463 42.870315, -78.884744 42.870217, -78.884792 42.870114, -78.884802 42.869993, -78.884716 42.869844, -78.884621 42.869738, -78.884387 42.869805, -78.881487 42.870633, -78.881132 42.870491, -78.880865 42.87058, -78.880427 42.870061, -78.884006 42.86888, -78.88378 42.86857, -78.883659 42.868404, -78.879995 42.869549, -78.879258 42.86869, -78.882716 42.867531, -78.88268 42.867446, -78.882645 42.867365, -78.882555 42.867359, -78.882497 42.867314, -78.882365 42.86717, -78.882228 42.86696, -78.881995 42.866721, -78.881643 42.866453, -78.881349 42.866257, -78.880859 42.86579, -78.880429 42.865324, -78.879604 42.864195, -78.87929 42.863889, -78.878738 42.863235, -78.878316 42.862757, -78.877919 42.862165, -78.877677 42.861668, -78.877651 42.861493, -78.877573 42.86131, -78.877363 42.861017, -78.876933 42.860276, -78.87681 42.860088, -78.876656 42.85996, -78.876569 42.859922, -78.876516 42.859898, -78.876382 42.859864, -78.876211 42.859861, -78.875986 42.859903, -78.875714 42.860014, -78.87527 42.860174, -78.874854 42.860404, -78.874868 42.860459, -78.874904 42.86052, -78.874924 42.860592, -78.874907 42.860707, -78.874819 42.860953, -78.874711 42.861116, -78.874642 42.861192, -78.874596 42.861219, -78.874537 42.861207, -78.874486 42.861168, -78.874329 42.860853, -78.874219 42.860731, -78.874015 42.860548, -78.873813 42.86054, -78.873714 42.860319, -78.873835 42.860249, -78.873777 42.860161, -78.873689 42.86011, -78.873453 42.859992, -78.873642 42.859879, -78.873982 42.859702, -78.874387 42.859619, -78.874719 42.859492, -78.875185 42.859338, -78.875938 42.859068, -78.876051 42.859009, -78.876078 42.858977, -78.876097 42.858955, -78.876121 42.858905, -78.876123 42.858812, -78.87611 42.858702, -78.876076 42.858581, -78.875831 42.858199, -78.87579 42.858018, -78.875708 42.857726, -78.875616 42.8575, -78.875366 42.856981, -78.875073 42.856445, -78.872815 42.853203, -78.872748 42.853108, -78.869514 42.85406, -78.869223 42.853595, -78.869401 42.853362, -78.869883 42.853165, -78.872287 42.85253, -78.872197 42.852383, -78.872031 42.852113, -78.871764 42.851679, -78.866392 42.853128, -78.866305 42.853017, -78.866262 42.85295, -78.866016 42.85293, -78.865904 42.852951, -78.865778 42.852905, -78.865428 42.852582, -78.865336 42.852372, -78.86537 42.852208, -78.865405 42.851994, -78.865264 42.851685, -78.865143 42.851442, -78.865167 42.851382, -78.86519 42.851343, -78.865185 42.851277, -78.865007 42.85122, -78.86478 42.850734, -78.86448 42.850532, -78.86437 42.850443, -78.864244 42.850419, -78.864103 42.850368, -78.863779 42.850193, -78.863714 42.850099, -78.862538 42.847914, -78.862334 42.847645, -78.86227 42.847534, -78.86222 42.847424, -78.862229 42.847347, -78.862276 42.847255, -78.862367 42.847195, -78.865296 42.846431, -78.865409 42.846416, -78.865476 42.846416, -78.865578 42.846517, -78.867873 42.849903, -78.867121 42.850448, -78.867046 42.850453, -78.866986 42.850469, -78.86694 42.850517, -78.866946 42.850578, -78.866982 42.850633, -78.867078 42.850689, -78.867153 42.850685, -78.868215 42.84993, -78.863847 42.843587, -78.86091 42.844417, -78.860381 42.843088, -78.860114 42.842398, -78.859656 42.84124, -78.859603 42.840954, -78.859653 42.840724, -78.859778 42.840463, -78.85994 42.840267, -78.860145 42.840121, -78.860312 42.83997, -78.860531 42.839885, -78.863094 42.839116, -78.863008 42.838945, -78.862839 42.838981, -78.862356 42.839085, -78.862182 42.838852, -78.858435 42.839842, -78.857846 42.838356, -78.85797 42.838288, -78.858174 42.838213, -78.858459 42.838135, -78.858737 42.838045, -78.85903 42.837966, -78.859285 42.837942, -78.859517 42.837895, -78.859748 42.837904, -78.859964 42.837934, -78.860195 42.83792, -78.860384 42.837857, -78.860542 42.837799, -78.860715 42.837746, -78.860836 42.83766, -78.860919 42.837611, -78.861062 42.837547, -78.861101 42.837487, -78.861111 42.837389, -78.861099 42.837262, -78.861008 42.837003, -78.860977 42.836717, -78.860902 42.836442, -78.860767 42.836155, -78.860691 42.835885, -78.86063 42.83561, -78.860412 42.835048, -78.860515 42.834807, -78.860562 42.834715, -78.860586 42.834611, -78.860574 42.834495, -78.860533 42.834347, -78.860508 42.834149, -78.8605 42.833825, -78.860412 42.833774, -78.860308 42.833768, -78.859881 42.833817, -78.859472 42.833763, -78.858921 42.833701, -78.858287 42.833687, -78.857817 42.833704, -78.857638 42.833679, -78.857342 42.833588, -78.856985 42.833534, -78.855998 42.833615, -78.855488 42.833738, -78.855156 42.83382, -78.855021 42.833856, -78.854461 42.83401, -78.854283 42.834059, -78.854674 42.833736, -78.854392 42.833473, -78.854735 42.833376, -78.855802 42.833075, -78.860151 42.832816, -78.860186 42.832921, -78.860447 42.832935, -78.860229 42.832262, -78.86005 42.831712, -78.861205 42.831787, -78.861607 42.831501, -78.861516 42.831253, -78.861341 42.831081, -78.856754 42.821107, -78.857136 42.821062, -78.857231 42.82114, -78.857343 42.821131, -78.857564 42.821595, -78.857766 42.821548, -78.862033 42.830837, -78.862098 42.830909, -78.862172 42.830948, -78.862261 42.830961, -78.862344 42.830956, -78.862419 42.830924, -78.863244 42.830413, -78.86338 42.830365, -78.86362 42.830314, -78.863754 42.83031, -78.863865 42.83035, -78.864174 42.830633, -78.864574 42.831, -78.86474 42.831249, -78.864877 42.831416, -78.865678 42.832284, -78.865824 42.832443, -78.866388 42.832554, -78.867152 42.833376, -78.868649 42.833871, -78.868676 42.83321, -78.868674 42.832968, -78.868817 42.832651, -78.86895 42.832384, -78.868965 42.832299, -78.868994 42.832143, -78.869065 42.831678, -78.869028 42.831359, -78.868771 42.830922, -78.868575 42.830677, -78.868267 42.830481, -78.868042 42.830247, -78.867889 42.830108, -78.867855 42.829976, -78.867894 42.829905, -78.868067 42.82983, -78.869135 42.829499, -78.869692 42.829293, -78.870145 42.829074, -78.870555 42.8288, -78.870929 42.828437, -78.871302 42.828151, -78.87149 42.828105, -78.871889 42.828225, -78.872214 42.828367, -78.872488 42.828459, -78.872614 42.828477, -78.872667 42.828456, -78.872654 42.82839, -78.872487 42.828173, -78.872375 42.827914, -78.872306 42.827688, -78.872264 42.826973, -78.872205 42.826665, -78.872301 42.826403, -78.872466 42.826037, -78.872553 42.825874, -78.872516 42.825544, -78.872369 42.825174, -78.872254 42.824679, -78.872044 42.824132, -78.871955 42.823511, -78.871872 42.822988, -78.871805 42.822679, -78.871768 42.822377, -78.871703 42.822272, -78.871478 42.822016, -78.87129 42.82181, -78.871098 42.821429, -78.870834 42.820942, -78.870632 42.820676, -78.870429 42.820426, -78.870295 42.820133, -78.869953 42.819206, -78.869735 42.818671, -78.869569 42.818169, -78.869426 42.817903, -78.869214 42.817428, -78.868879 42.816809, -78.868629 42.816393, -78.868408 42.815962, -78.868268 42.815625, -78.867776 42.81502, -78.867376 42.814619, -78.867 42.814136, -78.866567 42.813559, -78.866235 42.812836, -78.865935 42.812315, -78.865634 42.811812, -78.865356 42.811292, -78.865205 42.811103, -78.865101 42.811058, -78.864989 42.81097, -78.864984 42.81087, -78.865034 42.810749, -78.865037 42.810634, -78.864977 42.810359, -78.864506 42.809507, -78.864316 42.809092, -78.864081 42.808639, -78.863879 42.808361, -78.863581 42.80805, -78.863439 42.807773, -78.863341 42.80752, -78.863013 42.806911, -78.862719 42.806166, -78.862569 42.805889, -78.862442 42.805613, -78.862375 42.805379, -78.862326 42.805205, -78.862092 42.804719, -78.86185 42.804276, -78.861538 42.803898, -78.861214 42.803466, -78.860833 42.802906, -78.86058 42.802567, -78.860433 42.802209, -78.860139 42.801743, -78.859779 42.801239, -78.859466 42.800938, -78.859322 42.800733, -78.859234 42.800666, -78.859131 42.800631, -78.858893 42.800578, -78.85736 42.800491, -78.856793 42.800483, -78.85663 42.800453, -78.856482 42.800402, -78.856167 42.800183, -78.856109 42.800117, -78.855989 42.799868, -78.855861 42.799323, -78.855725 42.79881, -78.855596 42.798292, -78.85549 42.798044, -78.855442 42.797906, -78.85532 42.797829, -78.85529 42.797734, -78.855276 42.797673, -78.855129 42.797583, -78.854985 42.797405, -78.854821 42.797085, -78.854566 42.796565, -78.85426 42.796012, -78.853969 42.79542, -78.853752 42.794868, -78.853665 42.794461, -78.853583 42.794169, -78.853475 42.794046, -78.853442 42.79388, -78.853498 42.793717, -78.853641 42.793368, -78.853588 42.793093, -78.853468 42.792855, -78.853271 42.792649, -78.853226 42.792385, -78.853256 42.792072, -78.853259 42.791666, -78.853218 42.790661, -78.853278 42.790058, -78.853292 42.789515, -78.853249 42.788844, -78.853228 42.788191, -78.853238 42.787895, -78.853227 42.78764, -78.853251 42.78728, -78.853237 42.786955, -78.853361 42.78648, -78.853516 42.78596, -78.853651 42.785347, -78.853751 42.784942, -78.854222 42.783999, -78.854257 42.783808, -78.854546 42.783576, -78.854672 42.783577, -78.855 42.783604, -78.855076 42.783523, -78.855158 42.783255, -78.855287 42.782861, -78.855479 42.782622, -78.855665 42.782367, -78.85585 42.782101, -78.856011 42.781933, -78.856381 42.781751, -78.856639 42.78159, -78.857083 42.78141, -78.857429 42.781283, -78.857685 42.781199, -78.857828 42.781129, -78.858442 42.780737, -78.858693 42.780543, -78.859042 42.780301, -78.859356 42.779943, -78.859547 42.779775, -78.859663 42.779645, -78.859731 42.779569, -78.859929 42.779385, -78.860141 42.779262, -78.860301 42.779116, -78.860433 42.778915, -78.860589 42.778665, -78.860614 42.778544, -78.860641 42.778487, -78.860764 42.778233, -78.860843 42.778037, -78.861041 42.777858, -78.861233 42.777663, -78.861626 42.777201, -78.861913 42.776711, -78.862203 42.7761, -78.862591 42.775452, -78.862674 42.77542, -78.862823 42.775422, -78.862899 42.775363, -78.863063 42.775063, -78.863305 42.774594, -78.863598 42.774191, -78.863742 42.774089, -78.863825 42.774041, -78.864063 42.774071, -78.864227 42.774073, -78.864261 42.774054, -78.864287 42.774041, -78.864261 42.773887, -78.864279 42.773756, -78.864417 42.773582, -78.864614 42.773118, -78.864747 42.772773, -78.864829 42.772561, -78.865097 42.772262, -78.865422 42.772047, -78.865698 42.771737, -78.865973 42.771461, -78.866247 42.771212, -78.866792 42.770873, -78.867341 42.770639, -78.867921 42.77041, -78.868524 42.770116, -78.868661 42.770057, -78.869104 42.769865, -78.869937 42.769299, -78.870278 42.76904, -78.870309 42.768985, -78.870525 42.768675, -78.870882 42.768422, -78.871376 42.768016, -78.871891 42.767644, -78.872468 42.767218, -78.872922 42.7669, -78.873262 42.766728, -78.873766 42.766532, -78.873992 42.76643, -78.874174 42.766284, -78.874334 42.766144, -78.874605 42.766015, -78.874861 42.76592, -78.875257 42.765864, -78.875825 42.7658, -78.876363 42.765763, -78.876713 42.765762, -78.877015 42.765936, -78.877236 42.766054, -78.877288 42.766038, -78.877313 42.76595, -78.877383 42.765825, -78.877551 42.76563, -78.877938 42.765327, -78.878434 42.764839, -78.878732 42.764525, -78.878902 42.764327, -78.879031 42.764177, -78.879247 42.763867, -78.879397 42.763518, -78.879646 42.763038, -78.879952 42.762707, -78.880008 42.762636, -78.880144 42.762462, -78.880428 42.76212, -78.880778 42.761784, -78.881028 42.761623, -78.88142 42.761419, -78.881932 42.761201, -78.882302 42.760991, -78.882621 42.760748, -78.883024 42.760413, -78.883446 42.760193, -78.883904 42.760045, -78.884287 42.759946, -78.884385 42.759892, -78.884573 42.759834, -78.884838 42.759634, -78.885315 42.759305, -78.885957 42.758946, -78.886221 42.758823, -78.886567 42.75869, -78.886882 42.7586, -78.887115 42.758499, -78.887358 42.758332, -78.887577 42.758203, -78.887825 42.758091, -78.88809 42.757918, -78.888265 42.757756, -78.889048 42.757118, -78.890247 42.756189, -78.891247 42.755482, -78.891981 42.754992, -78.892323 42.7547, -78.892651 42.754391, -78.892922 42.754252, -78.893543 42.753815, -78.894022 42.753415, -78.894208 42.753252, -78.894517 42.75308, -78.895032 42.752758, -78.895691 42.752322, -78.896654 42.751649, -78.89714 42.751266, -78.897663 42.750922, -78.898399 42.750383, -78.898687 42.750151, -78.898783 42.750069, -78.900186 42.749069, -78.901455 42.748164, -78.902214 42.747722, -78.90328 42.747422, -78.903453 42.747396, -78.904032 42.747311, -78.904372 42.74726, -78.905251 42.746957, -78.906038 42.746628, -78.906294 42.746404, -78.907902 42.745656, -78.909096 42.744282, -78.909828 42.743746, -78.912008 42.741681, -78.914717 42.739558, -78.916418 42.73785, -78.917679 42.737284, -78.918183 42.737059, -78.919091 42.7368, -78.920859 42.736833, -78.920923 42.736788, -78.921358 42.736773, -78.923684 42.735877, -78.925028 42.735604, -78.928009 42.734824, -78.931047 42.73403, -78.931276 42.73397, -78.932228 42.733721, -78.935962 42.732485, -78.937862 42.732244, -78.939927 42.731732, -78.943559 42.731706, -78.944212 42.731672, -78.945433 42.731328, -78.948745 42.729694, -78.950209 42.728577, -78.951297 42.72759, -78.951854 42.727251, -78.952246 42.727012, -78.955769 42.725565, -78.958584 42.723923, -78.959822 42.723075, -78.961274 42.722324, -78.962185 42.721974, -78.962685 42.721845, -78.963728 42.72223, -78.9647 42.721904, -78.965915 42.721766, -78.966575 42.721503, -78.967305 42.72099, -78.967663 42.72047, -78.968524 42.719753, -78.968539 42.719296, -78.968816 42.718687, -78.96901 42.718161, -78.969489 42.717073, -78.969868 42.716418, -78.969917 42.71644, -78.969687 42.716165, -78.969675 42.716143, -78.969302 42.715867, -78.969386 42.715686, -78.969338 42.715621, -78.969321 42.715456, -78.969537 42.71518, -78.969636 42.714841, -78.969585 42.714549, -78.969437 42.714278, -78.969218 42.714121, -78.968954 42.714007, -78.968621 42.713913, -78.968395 42.713762, -78.967895 42.713303, -78.967689 42.712971, -78.967585 42.712722, -78.967274 42.712404, -78.967726 42.712466, -78.968177 42.712578, -78.968568 42.712705, -78.968824 42.712852, -78.969516 42.713362, -78.969855 42.713741, -78.970041 42.713986, -78.970169 42.714213, -78.970315 42.714561, -78.970511 42.715223, -78.970532 42.715514, -78.970432 42.715815, -78.970255 42.717028, -78.970444 42.716929, -78.970765 42.716704, -78.971887 42.715558, -78.972754 42.714636, -78.973228 42.71437, -78.975366 42.713492, -78.976346 42.712938, -78.978796 42.711037, -78.980151 42.710375, -78.981168 42.709638, -78.98265 42.708886, -78.985258 42.707834, -78.987187 42.706701, -78.988974 42.706092, -78.990017 42.705516, -78.990929 42.70512, -78.991927 42.704932, -78.994077 42.704626, -78.995545 42.704309, -78.997628 42.704162, -78.998315 42.704037, -78.999774 42.703951, -79.000632 42.703828, -79.001347 42.703725, -79.002185 42.703717, -79.004965 42.704086, -79.005832 42.704124, -79.008302 42.703526, -79.009685 42.702955, -79.010884 42.702313, -79.012426 42.701631, -79.012808 42.701317, -79.014036 42.700766, -79.015004 42.700554, -79.016339 42.700509, -79.017845 42.700947, -79.018871 42.700873, -79.019717 42.700613, -79.020254 42.700302, -79.02067 42.699898, -79.021226 42.699015, -79.021828 42.698591, -79.022503 42.69787, -79.024521 42.69683, -79.027091 42.695982, -79.027966 42.695791, -79.028869 42.695948, -79.030073 42.696647, -79.030443 42.696675, -79.031598 42.696271, -79.032986 42.695924, -79.03439 42.69565, -79.035361 42.695347, -79.036495 42.694794, -79.037064 42.694461, -79.039803 42.693158, -79.041319 42.692292, -79.042518 42.691629, -79.042934 42.691415, -79.043506 42.691306, -79.04399 42.691207, -79.044331 42.691257, -79.044725 42.691378, -79.045193 42.691493, -79.045424 42.691576, -79.045706 42.691587, -79.045996 42.691515, -79.046219 42.691439, -79.046435 42.691357, -79.046754 42.691346, -79.047022 42.691368, -79.047007 42.691774, -79.047121 42.691731, -79.047215 42.691697, -79.04752 42.691516, -79.047669 42.691324, -79.047922 42.690907, -79.048145 42.690573, -79.048242 42.690189, -79.048406 42.689684, -79.048718 42.689228, -79.049128 42.688493, -79.049529 42.687862, -79.049946 42.687347, -79.050229 42.686908, -79.050422 42.686535, -79.050571 42.68614, -79.050557 42.685668, -79.050579 42.685235, -79.050727 42.684771, -79.050736 42.684746, -79.050929 42.684269, -79.051175 42.683863, -79.051487 42.683545, -79.051755 42.683314, -79.051971 42.682996, -79.052186 42.682557, -79.052209 42.681904, -79.052247 42.681202, -79.052246 42.681097, -79.05224 42.680319, -79.052189 42.679655, -79.05239 42.679194, -79.052494 42.679007, -79.052539 42.678793, -79.052561 42.678601, -79.052606 42.678321, -79.052695 42.678097, -79.052785 42.677833, -79.052896 42.677625, -79.052896 42.677367, -79.052971 42.677147, -79.05312 42.676895, -79.053231 42.676632, -79.053432 42.676303, -79.053715 42.675935, -79.054116 42.675655, -79.05448 42.675408, -79.054582 42.675326, -79.05541 42.674668, -79.055744 42.67435, -79.056049 42.674098, -79.056413 42.673867, -79.057551 42.673079, -79.058696 42.672161, -79.05961 42.671673, -79.06115 42.670113, -79.061818 42.669233, -79.062286 42.668383, -79.062304 42.668246, -79.062329 42.667532, -79.062473 42.667022, -79.062464 42.667, -79.062467 42.666979, -79.062489 42.666968, -79.062688 42.666264, -79.062965 42.665307, -79.063077 42.664095, -79.063136 42.663514, -79.063087 42.663101, -79.063021 42.662755, -79.062894 42.662462, -79.062535 42.66227, -79.062403 42.662192, -79.062315 42.661866, -79.062527 42.662012, -79.062636 42.662074, -79.062876 42.662028, -79.063115 42.661954, -79.063237 42.661539, -79.063169 42.661296, -79.063257 42.659907, -79.063237 42.659605, -79.063179 42.659238, -79.063194 42.658772, -79.063163 42.658191, -79.063044 42.657771, -79.062875 42.656921, -79.062831 42.65526, -79.062812 42.654526, -79.062722 42.652466, -79.062873 42.651599, -79.063104 42.650837, -79.063114 42.650803, -79.063171 42.650426, -79.063427 42.648726, -79.063536 42.647218, -79.063639 42.6469, -79.06364 42.646309, -79.063871 42.645675, -79.064302 42.644484, -79.064908 42.643735, -79.065713 42.643038, -79.066702 42.642415, -79.069639 42.641187, -79.070616 42.64075, -79.071129 42.640405, -79.071623 42.640297, -79.072193 42.640074, -79.073388 42.639816, -79.073708 42.639831, -79.074113 42.639947, -79.074799 42.640247, -79.075007 42.640224, -79.075164 42.640282, -79.075351 42.640374, -79.075604 42.640443, -79.075947 42.640517, -79.07649 42.640523, -79.077069 42.640524, -79.077632 42.640475, -79.078136 42.640399, -79.078675 42.640191, -79.079132 42.639963, -79.079433 42.639773, -79.079712 42.639552, -79.079946 42.639319, -79.080259 42.638949, -79.080528 42.638596, -79.080724 42.63827, -79.080962 42.637824, -79.081133 42.637378, -79.081296 42.6369, -79.081387 42.636597, -79.081559 42.636184, -79.081761 42.635837, -79.082064 42.635269, -79.082556 42.634453, -79.082941 42.633912, -79.083341 42.633421, -79.083763 42.632879, -79.084419 42.632095, -79.084917 42.631684, -79.085358 42.631401, -79.085392 42.631376, -79.085791 42.63109, -79.086483 42.630705, -79.086946 42.630411, -79.087351 42.630177, -79.088052 42.629924, -79.08842 42.629728, -79.088797 42.629637, -79.089323 42.629528, -79.08962 42.629509, -79.089893 42.629441, -79.090175 42.629417, -79.090792 42.629493, -79.091663 42.629617, -79.091992 42.629763, -79.092254 42.629897, -79.092478 42.629978, -79.092597 42.629982, -79.092686 42.629948, -79.093108 42.629901, -79.093285 42.629822, -79.093476 42.629673, -79.093691 42.629194, -79.093835 42.628902, -79.093891 42.628731, -79.093957 42.628665, -79.094097 42.628592, -79.094228 42.628437, -79.094337 42.628266, -79.094548 42.628023, -79.094707 42.627764, -79.094836 42.627494, -79.094935 42.62718, -79.09507 42.626812, -79.095256 42.62636, -79.095405 42.625953, -79.095599 42.625551, -79.095921 42.625052, -79.09607 42.624754, -79.096228 42.62446, -79.096302 42.624225, -79.096536 42.623877, -79.096692 42.623627, -79.097113 42.623249, -79.09767 42.622791, -79.097854 42.622619, -79.097963 42.622467, -79.098083 42.622177, -79.098113 42.62213, -79.098208 42.621982, -79.098481 42.621821, -79.09936 42.62154, -79.099743 42.621414, -79.1 42.621292, -79.100718 42.620925, -79.101156 42.620718, -79.101882 42.620307, -79.102509 42.620004, -79.102895 42.619808, -79.103208 42.619538, -79.103528 42.619049, -79.103731 42.618723, -79.103915 42.618303, -79.104365 42.617909, -79.10544 42.617306, -79.105729 42.617113, -79.105822 42.616972, -79.105907 42.616891, -79.106176 42.616824, -79.106282 42.616771, -79.106345 42.616667, -79.106379 42.616525, -79.106383 42.616382, -79.107149 42.615648, -79.107764 42.615246, -79.108271 42.614974, -79.108897 42.614677, -79.109402 42.614482, -79.109935 42.61431, -79.11032 42.614129, -79.110667 42.613976, -79.110953 42.613843, -79.111206 42.613611, -79.111344 42.613403, -79.111486 42.613215, -79.111554 42.61293, -79.11179 42.611753, -79.112041 42.610823, -79.112168 42.61046, -79.111873 42.610462, -79.109454 42.610423, -79.09976 42.610264, -79.099685 42.610396, -79.099023 42.613373, -79.096434 42.613381, -79.096431 42.614042, -79.098878 42.614035, -79.098732 42.614705, -79.096428 42.614712, -79.09442 42.614718, -79.094424 42.614048, -79.092347 42.614054, -79.092339 42.614852, -79.092334 42.615394, -79.092327 42.616054, -79.092569 42.616053, -79.092327 42.616087, -79.092321 42.616711, -79.092314 42.61737, -79.092308 42.618033, -79.092301 42.618671, -79.092292 42.619336, -79.092288 42.619586, -79.09212 42.619989, -79.092093 42.619988, -79.091912 42.619986, -79.089148 42.619985, -79.089153 42.620672, -79.089158 42.621355, -79.088601 42.621349, -79.088295 42.621062, -79.088219 42.620667, -79.087969 42.620666, -79.086976 42.620661, -79.084935 42.620652, -79.084927 42.619963, -79.084923 42.619571, -79.08492 42.619141, -79.084858 42.61913, -79.084412 42.619088, -79.084296 42.618824, -79.084032 42.618444, -79.083657 42.618117, -79.083204 42.618043, -79.082854 42.61808, -79.082813 42.619562, -79.082794 42.619916, -79.080389 42.619921, -79.078362 42.619925, -79.078374 42.620607, -79.078387 42.621291, -79.078381 42.621974, -79.07838 42.622667, -79.076333 42.622671, -79.076193 42.622671, -79.074295 42.622673, -79.074289 42.623367, -79.072253 42.62337, -79.070216 42.623372, -79.068178 42.623381, -79.066126 42.623388, -79.066121 42.622686, -79.064077 42.622685, -79.062034 42.622684, -79.059969 42.622758, -79.059269 42.622748, -79.058785 42.623382, -79.058752 42.623424, -79.058254 42.624067, -79.058217 42.624115, -79.058092 42.624277, -79.057715 42.624763, -79.057179 42.625448, -79.056646 42.626125, -79.056604 42.626178, -79.056117 42.626798, -79.056062 42.626868, -79.05555 42.62757, -79.054074 42.629483, -79.052894 42.631012, -79.052849 42.63107, -79.055558 42.631011, -79.056582 42.631011, -79.062823 42.631009, -79.063507 42.631009, -79.06414 42.631877, -79.064753 42.632717, -79.0654 42.633562, -79.065679 42.633935, -79.066168 42.634623, -79.066958 42.635736, -79.064801 42.636581, -79.06536 42.63751, -79.062612 42.639151, -79.063433 42.639911, -79.062918 42.640216, -79.063665 42.640922, -79.063461 42.641042, -79.063362 42.6411, -79.063733 42.641552, -79.065059 42.641559, -79.065025 42.641573, -79.062805 42.64332, -79.06269 42.643411, -79.062534 42.643556, -79.062306 42.643705, -79.062113 42.643619, -79.061921 42.643503, -79.058087 42.640691, -79.058023 42.640644, -79.05696 42.639865, -79.05523 42.638596, -79.048337 42.638607, -79.047986 42.638608, -79.047959 42.638608, -79.048005 42.638537, -79.04764 42.638469, -79.040866 42.638489, -79.039945 42.638477, -79.039865 42.638477, -79.039864 42.635613, -79.039661 42.631112, -79.039601 42.629606, -79.039567 42.628765, -79.039567 42.62807, -79.033069 42.627973, -79.031731 42.628154, -79.026962 42.628155, -79.025395 42.628173, -79.021816 42.628216, -79.019 42.62825, -79.01928 42.641142, -79.019317 42.641469, -79.019327 42.641553, -79.019304 42.644968, -79.019321 42.646109, -79.019322 42.646198, -79.01926 42.647675, -79.023333 42.647759, -79.025277 42.647742, -79.025703 42.647739, -79.031661 42.647558, -79.031745 42.64779, -79.031758 42.647808, -79.031959 42.648092, -79.032299 42.648541, -79.032501 42.648822, -79.033429 42.650121, -79.032864 42.650191, -79.032684 42.650214, -79.031261 42.65035, -79.02994 42.650749, -79.029141 42.65099, -79.028671 42.651142, -79.028104 42.651358, -79.027377 42.651601, -79.027082 42.651667, -79.02613 42.651943, -79.024959 42.652455, -79.02509 42.652467, -79.025351 42.652501, -79.025609 42.652548, -79.025861 42.652609, -79.026107 42.652681, -79.026346 42.652766, -79.026576 42.652863, -79.026797 42.652971, -79.027007 42.65309, -79.02717 42.653197, -79.027337 42.653299, -79.027509 42.653398, -79.027685 42.653492, -79.027865 42.653582, -79.028048 42.653668, -79.028235 42.65375, -79.02866 42.653919, -79.028846 42.653981, -79.029397 42.654137, -79.032196 42.654829, -79.032571 42.6555, -79.033252 42.656833, -79.033318 42.656945, -79.033436 42.657107, -79.033575 42.657259, -79.033679 42.657354, -79.033989 42.657569, -79.033263 42.658463, -79.033094 42.658671, -79.02683 42.660205, -79.026231 42.660359, -79.023274 42.661144, -79.021957 42.661488, -79.014805 42.661513, -79.012525 42.661511, -79.002545 42.661503, -79.002539 42.668313, -79.00258 42.668531, -79.00258 42.669565, -79.002579 42.670634, -79.002576 42.675344, -79.002574 42.680725, -79.002581 42.684446, -79.002577 42.684638, -79.002564 42.68483, -79.002542 42.685021, -79.002513 42.685204, -79.002476 42.685386, -79.002432 42.685567, -79.002379 42.685747, -79.001806 42.687315, -79.001713 42.687583, -79.001194 42.689069, -79.001026 42.689601, -79.000876 42.690137, -79.000742 42.690675, -79.000626 42.691215, -79.000594 42.691394, -79.000571 42.691574, -79.00053 42.692029, -79.000045 42.692082, -78.997005 42.692111, -78.994868 42.692031, -78.994272 42.692031, -78.992817 42.692036, -78.992081 42.692028, -78.990748 42.692005, -78.99012 42.692003, -78.988181 42.69198, -78.98341 42.691901, -78.982435 42.691883, -78.981239 42.691855, -78.976697 42.691762, -78.976281 42.691751, -78.97594 42.691709, -78.974174 42.691605, -78.973648 42.691568, -78.9725 42.691489, -78.970013 42.691316, -78.968738 42.692863, -78.968103 42.693731, -78.963348 42.696272, -78.959196 42.69624, -78.959061 42.696198, -78.958435 42.696116, -78.957809 42.695937, -78.957435 42.696236, -78.949685 42.702496, -78.949511 42.702381, -78.949473 42.702321, -78.948965 42.701533, -78.948165 42.700422, -78.947475 42.699463, -78.94746 42.694395, -78.943332 42.694396, -78.943293 42.694394, -78.943266 42.694427, -78.941956 42.696231, -78.939157 42.696286, -78.938728 42.696123, -78.936524 42.695286, -78.936402 42.695558, -78.936167 42.696028, -78.936052 42.696216, -78.935963 42.696349, -78.935827 42.69635, -78.931008 42.696439, -78.931158 42.696481, -78.931823 42.69668, -78.932151 42.696786, -78.932477 42.696897, -78.933105 42.697139, -78.933726 42.697392, -78.934338 42.697656, -78.93468 42.697852, -78.933401 42.698651, -78.93133 42.699849, -78.930512 42.700311, -78.929706 42.700785, -78.928894 42.701278, -78.9281 42.70182, -78.926215 42.703071, -78.925414 42.703601, -78.924019 42.704553, -78.920054 42.707121, -78.919582 42.707431, -78.91908 42.707755, -78.91833 42.708239, -78.91829 42.708265, -78.91772 42.708633, -78.916022 42.70973, -78.914995 42.710458, -78.914967 42.710477, -78.912425 42.71215, -78.914345 42.71295, -78.914862 42.713166, -78.914826 42.714017, -78.914662 42.717924, -78.914643 42.718404, -78.914584 42.719134, -78.91454 42.721484, -78.914471 42.723362, -78.914433 42.724175, -78.914391 42.72418, -78.913959 42.724126, -78.913072 42.723721, -78.912371 42.723342, -78.911663 42.723169, -78.909768 42.723249, -78.908656 42.72369, -78.90801 42.723664, -78.907316 42.723824, -78.905152 42.723943, -78.904356 42.723631, -78.903914 42.723352, -78.90387 42.723324, -78.902852 42.722231, -78.902356 42.72219, -78.901799 42.722143, -78.901361 42.722272, -78.900724 42.722737, -78.900566 42.722852, -78.899599 42.722995, -78.898503 42.723295, -78.897663 42.723348, -78.896251 42.72291, -78.896161 42.722858, -78.895827 42.72307, -78.894109 42.724218, -78.894061 42.72425, -78.89314 42.724885, -78.890602 42.726847, -78.890105 42.727231, -78.88969 42.727554, -78.88964 42.727594, -78.88926 42.727898, -78.887155 42.729538, -78.886849 42.729776, -78.886369 42.730166, -78.885829 42.730592, -78.883979 42.732046, -78.88139 42.734055, -78.879385 42.735613, -78.878274 42.735473, -78.873601 42.734921, -78.872965 42.734885, -78.871361 42.734902, -78.871393 42.734106, -78.871421 42.733243, -78.871427 42.7331, -78.871454 42.73241, -78.871659 42.731718, -78.871462 42.731756, -78.871112 42.731779, -78.870669 42.731817, -78.870111 42.731798, -78.869964 42.731796, -78.869538 42.73179, -78.869315 42.731792, -78.868544 42.731567, -78.867749 42.731649, -78.866598 42.731611, -78.865141 42.732106, -78.865011 42.732146, -78.865022 42.731275, -78.86514 42.731231, -78.865137 42.731065, -78.865138 42.73071, -78.865085 42.730694, -78.864873 42.730645, -78.863633 42.730364, -78.863039 42.730227, -78.861005 42.729763, -78.860521 42.729612, -78.860155 42.72956, -78.860061 42.729535, -78.859871 42.729492, -78.859308 42.729119, -78.859028 42.728933, -78.858906 42.728848, -78.85882 42.728788, -78.858371 42.728484, -78.857751 42.728072, -78.857044 42.727588, -78.85569 42.726671, -78.855273 42.726388, -78.855125 42.726282, -78.854836 42.726115, -78.852697 42.725028, -78.852384 42.724869, -78.852014 42.724847, -78.851708 42.724829, -78.851634 42.724826, -78.851365 42.724813, -78.84838 42.724723, -78.848145 42.72466, -78.848145 42.724692, -78.848051 42.724667, -78.848467 42.723857, -78.849276 42.723756, -78.849329 42.719947, -78.847548 42.720144, -78.84741 42.720141, -78.846059 42.72014, -78.845569 42.72014, -78.844283 42.720138, -78.844277 42.718683, -78.843546 42.718603, -78.843658 42.717668, -78.843692 42.717603, -78.844141 42.716844, -78.844488 42.716272, -78.844877 42.715535, -78.845083 42.715161, -78.845267 42.714815, -78.845385 42.714628, -78.845502 42.71446, -78.845903 42.71441, -78.847174 42.714416, -78.847312 42.714417, -78.84949 42.71444, -78.851136 42.714473, -78.853127 42.714513, -78.854718 42.714535, -78.858633 42.714591, -78.858911 42.71459, -78.859188 42.714584, -78.859465 42.714573, -78.863869 42.714455, -78.864491 42.714451, -78.865426 42.714463, -78.865805 42.714486, -78.865994 42.714504, -78.866499 42.714561, -78.86713 42.71464, -78.868449 42.714828, -78.869834 42.715011, -78.869862 42.713674, -78.867566 42.712149, -78.869137 42.711895, -78.869423 42.711747, -78.869945 42.711478, -78.870258 42.71132, -78.870402 42.711317, -78.870075 42.710218, -78.869992 42.71009, -78.868284 42.708185, -78.867421 42.707769, -78.866624 42.707459, -78.865506 42.707108, -78.861966 42.706923, -78.860405 42.707693, -78.854688 42.707671, -78.853898 42.707669, -78.853558 42.707671, -78.85352 42.707666, -78.853413 42.70765, -78.853143 42.70759, -78.851209 42.706515, -78.850047 42.705523, -78.849769 42.705396, -78.849811 42.705321, -78.850511 42.704108, -78.851012 42.703239, -78.851387 42.702592, -78.852156 42.701271, -78.852257 42.701106, -78.853387 42.699166, -78.85475 42.69684, -78.855516 42.695758, -78.855664 42.695513, -78.855842 42.695228, -78.85595 42.694997, -78.856047 42.694729, -78.856075 42.694588, -78.85609 42.694276, -78.856137 42.693757, -78.856149 42.693657, -78.856155 42.693527, -78.85619 42.693359, -78.856269 42.693074, -78.856358 42.692872, -78.856398 42.692799, -78.856423 42.692752, -78.856584 42.692537, -78.856637 42.692455, -78.85671 42.692355, -78.856754 42.692295, -78.856885 42.692141, -78.857225 42.691837, -78.857512 42.691586, -78.858205 42.691004, -78.859166 42.690183, -78.860046 42.689497, -78.861025 42.688758, -78.861378 42.688496, -78.861894 42.688112, -78.862007 42.68803, -78.86326 42.687128, -78.864934 42.685895, -78.865908 42.685194, -78.866442 42.684749, -78.867099 42.684286, -78.867733 42.683931, -78.86849 42.683572, -78.86939 42.683176, -78.869518 42.683114, -78.869842 42.682908, -78.870436 42.68247, -78.870737 42.682201, -78.871075 42.681834, -78.871469 42.681306, -78.871657 42.68106, -78.871993 42.680593, -78.87235 42.680097, -78.872641 42.679702, -78.873156 42.679004, -78.873465 42.678609, -78.873625 42.678346, -78.873691 42.678426, -78.873855 42.678639, -78.874012 42.678854, -78.87416 42.679073, -78.874292 42.679279, -78.874546 42.679693, -78.874599 42.67976, -78.874723 42.679886, -78.87483 42.679972, -78.874968 42.680061, -78.875157 42.68016, -78.87529 42.680217, -78.8755 42.680291, -78.875694 42.680344, -78.875894 42.680388, -78.876096 42.680423, -78.876504 42.680473, -78.876709 42.680491, -78.877331 42.680536, -78.877439 42.680523, -78.877776 42.680466, -78.877917 42.68043, -78.878153 42.680349, -78.87855 42.680206, -78.878747 42.680432, -78.878769 42.680467, -78.88088 42.681454, -78.880877 42.681556, -78.880859 42.681758, -78.880823 42.68196, -78.880719 42.682344, -78.880685 42.682531, -78.880645 42.68288, -78.880641 42.682958, -78.880633 42.683137, -78.880642 42.683407, -78.882092 42.681377, -78.88392 42.678472, -78.886197 42.675335, -78.886529 42.674839, -78.888286 42.672217, -78.889641 42.670437, -78.892082 42.667233, -78.892768 42.666193, -78.894477 42.663368, -78.894651 42.663074, -78.89579 42.661149, -78.896008 42.660729, -78.896983 42.658851, -78.89758 42.657702, -78.900166 42.652721, -78.900387 42.652408, -78.900187 42.652405, -78.900132 42.652405, -78.900195 42.65239, -78.900587 42.651861, -78.900675 42.65187, -78.900751 42.65189, -78.902528 42.652363, -78.906778 42.653173, -78.908494 42.653744, -78.910805 42.653911, -78.910802 42.65232, -78.910798 42.651861, -78.910775 42.648691, -78.910395 42.648686, -78.907858 42.648877, -78.906004 42.649053, -78.905906 42.649063, -78.903805 42.649824, -78.902539 42.649803, -78.902389 42.649801, -78.901589 42.649788, -78.898324 42.649734, -78.898703 42.649008, -78.898898 42.648633, -78.898997 42.648489, -78.899241 42.648153, -78.899803 42.647379, -78.899846 42.647319, -78.901062 42.645715, -78.901234 42.645489, -78.901372 42.645338, -78.901517 42.645189, -78.901666 42.645044, -78.901656 42.643002, -78.901646 42.640676, -78.901619 42.63856, -78.901627 42.636753, -78.893078 42.636752, -78.888659 42.640317, -78.888106 42.640751, -78.883759 42.644115, -78.881486 42.646268, -78.880757 42.64728, -78.880641 42.647442, -78.880353 42.648031, -78.880308 42.648122, -78.879827 42.648593, -78.874998 42.651179, -78.873098 42.651464, -78.871327 42.651765, -78.871297 42.652408, -78.871433 42.658035, -78.871486 42.660214, -78.871496 42.660603, -78.879338 42.660545, -78.879394 42.660537, -78.879474 42.660513, -78.879862 42.6604, -78.879974 42.660361, -78.880833 42.659977, -78.882718 42.659973, -78.891516 42.659949, -78.892794 42.660383, -78.892752 42.660443, -78.892157 42.66125, -78.891896 42.661596, -78.891665 42.661876, -78.891422 42.662151, -78.890682 42.663175, -78.8905 42.663395, -78.890328 42.663581, -78.890129 42.663774, -78.890003 42.663886, -78.889737 42.664101, -78.889597 42.664204, -78.889452 42.664303, -78.887019 42.666044, -78.885621 42.667046, -78.885599 42.667061, -78.883162 42.668812, -78.882906 42.669, -78.879538 42.671441, -78.877647 42.672818, -78.876756 42.673504, -78.875784 42.674402, -78.875451 42.674907, -78.874945 42.675895, -78.874563 42.676697, -78.874378 42.677053, -78.874293 42.677006, -78.874234 42.676947, -78.873498 42.676216, -78.873477 42.676234, -78.873315 42.676363, -78.873129 42.676495, -78.872937 42.676645, -78.872758 42.676802, -78.872674 42.676891, -78.872604 42.676986, -78.872554 42.677073, -78.872503 42.677193, -78.872495 42.677243, -78.872492 42.677342, -78.87255 42.677681, -78.8726 42.678065, -78.872594 42.678167, -78.872574 42.678268, -78.872526 42.6784, -78.872475 42.678495, -78.872412 42.678586, -78.872365 42.678668, -78.872308 42.678796, -78.872278 42.678896, -78.872256 42.678896, -78.866605 42.677657, -78.865104 42.67797, -78.862176 42.679389, -78.861823 42.67956, -78.861629 42.679653, -78.856788 42.678607, -78.856733 42.678594, -78.856734 42.678612, -78.856838 42.68198, -78.856832 42.68331, -78.856788 42.684708, -78.85187 42.68383, -78.841679 42.684015, -78.841662 42.684541, -78.841248 42.684854, -78.840714 42.68505, -78.839812 42.685147, -78.839147 42.685547, -78.838432 42.685602, -78.837859 42.686026, -78.837458 42.685972, -78.834711 42.690363, -78.834554 42.691036, -78.833947 42.691037, -78.833938 42.690997, -78.833891 42.690811, -78.833843 42.690656, -78.833834 42.690627, -78.83377 42.690444, -78.833698 42.690263, -78.833617 42.690084, -78.833529 42.689907, -78.833433 42.689732, -78.833329 42.689559, -78.833263 42.689461, -78.833156 42.689302, -78.833082 42.689203, -78.832839 42.688933, -78.832423 42.688528, -78.831949 42.688061, -78.831471 42.68761, -78.831247 42.687419, -78.831014 42.687234, -78.830773 42.687054, -78.830524 42.68688, -78.830268 42.686712, -78.830005 42.686551, -78.829015 42.685913, -78.828438 42.685375, -78.828331 42.685254, -78.828242 42.685126, -78.828009 42.684844, -78.827908 42.684703, -78.827753 42.684452, -78.827653 42.68427, -78.827561 42.684086, -78.827478 42.683899, -78.827404 42.683711, -78.827338 42.683521, -78.827281 42.683329, -78.827232 42.683136, -78.827193 42.682942, -78.827249 42.680702, -78.826903 42.680674, -78.826378 42.680672, -78.825203 42.680668, -78.822278 42.680653, -78.822021 42.680663, -78.821837 42.680679, -78.82187 42.68124, -78.821448 42.681781, -78.821468 42.682102, -78.821974 42.682683, -78.822174 42.68319, -78.822442 42.683493, -78.822898 42.683753, -78.823759 42.683975, -78.824356 42.683758, -78.824664 42.683809, -78.82503 42.683976, -78.826058 42.684728, -78.825977 42.685298, -78.82616 42.68537, -78.826646 42.685677, -78.826593 42.686294, -78.827088 42.68644, -78.828005 42.686709, -78.828246 42.68692, -78.828536 42.687497, -78.828779 42.687639, -78.829517 42.687813, -78.830048 42.687708, -78.830728 42.687767, -78.831332 42.688236, -78.83174 42.688999, -78.83296 42.689594, -78.832752 42.690207, -78.832899 42.690462, -78.833849 42.691216, -78.833651 42.691261, -78.833615 42.691479, -78.83361 42.691648, -78.833627 42.691817, -78.833637 42.691873, -78.833794 42.692311, -78.834007 42.692971, -78.834043 42.693119, -78.834073 42.693268, -78.834096 42.693418, -78.834112 42.693567, -78.834121 42.693718, -78.83412 42.69397, -78.834064 42.694629, -78.833981 42.695428, -78.833869 42.696418, -78.833854 42.696551, -78.832884 42.69655, -78.830697 42.696544, -78.826925 42.696525, -78.826466 42.696522, -78.826006 42.69652, -78.825229 42.696516, -78.816894 42.696478, -78.81405 42.696465, -78.814029 42.694531, -78.814021 42.692737, -78.81385 42.692737, -78.813318 42.692693, -78.810638 42.69247, -78.810144 42.692485, -78.806351 42.69288, -78.805788 42.69292, -78.804586 42.693006, -78.804276 42.693014, -78.804315 42.694516, -78.804364 42.696358, -78.80437 42.696602, -78.8044 42.6984, -78.804438 42.700026, -78.804506 42.701277, -78.80463 42.70159, -78.80488 42.701961, -78.80495 42.702047, -78.805679 42.702939, -78.805936 42.703215, -78.806096 42.703335, -78.806208 42.703413, -78.806441 42.703561, -78.806686 42.703698, -78.806665 42.70373, -78.807525 42.704209, -78.808482 42.704601, -78.810785 42.705229, -78.810806 42.705183, -78.811338 42.70532, -78.813065 42.705752, -78.813536 42.705865, -78.814221 42.706023, -78.815247 42.706274, -78.815822 42.706409, -78.816595 42.706597, -78.816804 42.706679, -78.817387 42.706973, -78.818068 42.707344, -78.818423 42.707587, -78.819205 42.708122, -78.819492 42.708328, -78.81973 42.708498, -78.820228 42.708872, -78.822509 42.710503, -78.822759 42.71066, -78.823124 42.710863, -78.823947 42.711296, -78.824298 42.711471, -78.823289 42.711482, -78.820846 42.711424, -78.820676 42.711071, -78.820322 42.710538, -78.819956 42.710348, -78.819304 42.710382, -78.818926 42.710535, -78.818377 42.711142, -78.817999 42.711318), (-78.797496 43.066046, -78.795813 43.06603, -78.795222 43.065994, -78.794286 43.06598, -78.793426 43.06601, -78.792798 43.066017, -78.792686 43.065985, -78.792633 43.065952, -78.792491 43.065952, -78.792417 43.065997, -78.792245 43.066057, -78.791378 43.066131, -78.790922 43.066187, -78.790443 43.066238, -78.789868 43.066322, -78.789322 43.066379, -78.787991 43.066432, -78.78737 43.066445, -78.786843 43.066465, -78.786851 43.066557, -78.78647 43.06656, -78.786085 43.066508, -78.783669 43.066184, -78.782387 43.065871, -78.77858 43.065415, -78.777644 43.065496, -78.774272 43.066818, -78.77389 43.067098, -78.773074 43.067812, -78.772811 43.068183, -78.77237 43.068545, -78.771057 43.069422, -78.770784 43.069539, -78.770785 43.069592, -78.770786 43.069669, -78.770784 43.069746, -78.770783 43.069819, -78.770018 43.070088, -78.769512 43.070248, -78.769102 43.070283, -78.768159 43.070421, -78.767084 43.070525, -78.764683 43.070594, -78.762824 43.070726, -78.762207 43.070809, -78.761138 43.071101, -78.760105 43.071281, -78.759522 43.071309, -78.758683 43.071253, -78.757829 43.071135, -78.757045 43.070969, -78.756359 43.070775, -78.755838 43.07056, -78.755688 43.070488, -78.755647 43.070405, -78.755527 43.070159, -78.755487 43.070077, -78.755481 43.069945, -78.755108 43.069797, -78.754726 43.069656, -78.753939 43.06934, -78.75355 43.06922, -78.753317 43.069161, -78.753325 43.068968, -78.753238 43.068947, -78.752944 43.068904, -78.752661 43.068886, -78.752157 43.068874, -78.751702 43.068896, -78.751269 43.068906, -78.750912 43.068937, -78.750702 43.068951, -78.749517 43.069, -78.749421 43.068926, -78.748902 43.068614, -78.748482 43.068399, -78.747986 43.068216, -78.747452 43.068027, -78.747127 43.067863, -78.746826 43.067655, -78.746579 43.067399, -78.746448 43.067238, -78.74635 43.067001, -78.74624 43.066648, -78.745996 43.066271, -78.74562 43.06609, -78.745293 43.065992, -78.744919 43.065991, -78.74461 43.066058, -78.74425 43.066091, -78.744027 43.066033, -78.743453 43.0657, -78.74301 43.065211, -78.742537 43.064726, -78.742059 43.064143, -78.741592 43.06373, -78.741145 43.063355, -78.740755 43.062856, -78.740046 43.06172, -78.739821 43.061442, -78.73976 43.061127, -78.739796 43.060723, -78.739971 43.060364, -78.740162 43.059664, -78.740139 43.059374, -78.740112 43.059038, -78.740072 43.058626, -78.739909 43.058361, -78.739725 43.058138, -78.739584 43.057845, -78.73949 43.057449, -78.739437 43.057179, -78.739342 43.056846, -78.739217 43.056462, -78.739237 43.056303, -78.739685 43.055566, -78.739883 43.055408, -78.740403 43.055257, -78.74105 43.055124, -78.741419 43.055026, -78.741619 43.054853, -78.741661 43.054673, -78.741501 43.054495, -78.74121 43.054468, -78.740489 43.054589, -78.740084 43.054621, -78.739794 43.054567, -78.73967 43.054423, -78.739662 43.054181, -78.739845 43.053795, -78.740141 43.053377, -78.740278 43.053, -78.740261 43.052802, -78.740176 43.052626, -78.740068 43.052465, -78.739796 43.052253, -78.739736 43.0522, -78.739695 43.052163, -78.739549 43.051782, -78.739307 43.051301, -78.73915 43.051041, -78.738882 43.050729, -78.738685 43.050534, -78.738441 43.050448, -78.738214 43.050538, -78.737975 43.050781, -78.737673 43.050881, -78.737343 43.050914, -78.736683 43.050701, -78.736113 43.050506, -78.735935 43.050437, -78.735776 43.050232, -78.735556 43.050064, -78.735365 43.050017, -78.735208 43.05001, -78.734365 43.050039, -78.733774 43.050851, -78.732715 43.051631, -78.731487 43.05195, -78.730144 43.051937, -78.729862 43.052032, -78.729274 43.052753, -78.729089 43.052935, -78.728894 43.052997, -78.728608 43.052987, -78.728394 43.052941, -78.728102 43.052831, -78.729282 43.051774, -78.731024 43.050137, -78.733481 43.047827, -78.733632 43.047685, -78.734174 43.047175, -78.734222 43.04713, -78.736463 43.045023, -78.736748 43.044754, -78.737853 43.043717, -78.737844 43.04477, -78.739421 43.04477, -78.741431 43.04477, -78.741463 43.046566, -78.741489 43.047246, -78.741499 43.047512, -78.741575 43.04751, -78.742757 43.047441, -78.745869 43.047359, -78.746365 43.047331, -78.746957 43.04729, -78.747728 43.047193, -78.748582 43.047056, -78.749422 43.046877, -78.749659 43.046812, -78.749916 43.046735, -78.750118 43.046684, -78.750403 43.046581, -78.750694 43.046493, -78.751007 43.046392, -78.751317 43.046284, -78.751623 43.046172, -78.751925 43.046053, -78.752224 43.04593, -78.752516 43.0458, -78.752806 43.045666, -78.75309 43.045525, -78.75337 43.045381, -78.753643 43.04523, -78.753916 43.045073, -78.754293 43.044843, -78.754342 43.045931, -78.754497 43.048883, -78.754538 43.04979, -78.754627 43.051761, -78.762742 43.051715, -78.766411 43.051694, -78.770114 43.051672, -78.769993 43.05008, -78.769932 43.049277, -78.769849 43.048133, -78.769757 43.046868, -78.769666 43.045619, -78.769628 43.045097, -78.769312 43.043489, -78.769281 43.043332, -78.769077 43.042138, -78.768527 43.038922, -78.768485 43.038678, -78.768348 43.037674, -78.768242 43.037081, -78.769509 43.037074, -78.770828 43.037061, -78.772212 43.037038, -78.772292 43.037018, -78.772353 43.036983, -78.772393 43.036942, -78.772417 43.036888, -78.77242 43.036869, -78.772406 43.036086, -78.772386 43.036035, -78.772342 43.035985, -78.772288 43.035951, -78.772223 43.035929, -78.772152 43.035921, -78.7716 43.035922, -78.771601 43.034745, -78.772982 43.034746, -78.773748 43.034738, -78.778542 43.034693, -78.783371 43.03466, -78.783455 43.034659, -78.783781 43.034659, -78.783739 43.033851, -78.784293 43.034167, -78.784528 43.034659, -78.788419 43.034659, -78.792459 43.034685, -78.793966 43.034685, -78.793958 43.035599, -78.794184 43.03559, -78.794686 43.035594, -78.795716 43.035585, -78.795826 43.035591, -78.795931 43.035613, -78.796013 43.035644, -78.7961 43.035693, -78.79615 43.035741, -78.79619 43.035805, -78.796209 43.035875, -78.796211 43.036016, -78.796187 43.036157, -78.796152 43.03626, -78.796146 43.036298, -78.796161 43.036336, -78.796193 43.036367, -78.796239 43.036387, -78.796289 43.036392, -78.797326 43.036394, -78.797376 43.036381, -78.797421 43.036353, -78.797448 43.036315, -78.797454 43.036272, -78.797432 43.036213, -78.797408 43.036125, -78.797405 43.03469, -78.798154 43.034688, -78.799547 43.034687, -78.79958 43.036111, -78.799593 43.037803, -78.799599 43.038645, -78.799605 43.039101, -78.799613 43.039624, -78.79962 43.040128, -78.799618 43.040586, -78.799614 43.041809, -78.79962 43.042299, -78.799674 43.044992, -78.799687 43.045424, -78.80004 43.045428, -78.800053 43.045663, -78.800081 43.046335, -78.800094 43.046611, -78.800095 43.046709, -78.800134 43.047428, -78.800186 43.048322, -78.800233 43.049008, -78.800406 43.051936, -78.800462 43.052622, -78.801835 43.053625, -78.804093 43.055208, -78.80402 43.055436, -78.803955 43.055666, -78.803899 43.055897, -78.803851 43.056129, -78.803817 43.056235, -78.803762 43.056447, -78.803371 43.057162, -78.801291 43.060052, -78.800772 43.060771, -78.800721 43.060872, -78.798519 43.063862, -78.798253 43.064154, -78.797995 43.064575, -78.797947 43.06466, -78.797721 43.065116, -78.797606 43.065468, -78.797533 43.065851, -78.797496 43.066046)))"} -{"geo_id":"50392","urban_area_code":"50392","name":"Little Rock, AR","lsad_name":"Little Rock, AR Urbanized Area","area_lsad_code":"75","mtfcc_feature_class_code":"G3500","type":"U","functional_status":"S","area_land_meters":667701854,"area_water_meters":10845972,"internal_point_lon":-92.3212212,"internal_point_lat":34.7565639,"internal_point_geom":"POINT(-92.3212212 34.7565639)","urban_area_geom":"MULTIPOLYGON(((-92.034942 35.028082, -92.034956 35.028232, -92.035391 35.028232, -92.035375 35.028361, -92.035337 35.028472, -92.035301 35.028582, -92.035286 35.02863, -92.035282 35.028677, -92.035293 35.02873, -92.035363 35.028817, -92.035442 35.028855, -92.035525 35.028866, -92.035725 35.028875, -92.036162 35.028879, -92.036315 35.028878, -92.036451 35.028863, -92.036582 35.028839, -92.036782 35.02877, -92.037167 35.028551, -92.03782 35.028253, -92.038172 35.028222, -92.038158 35.02807, -92.039187 35.028067, -92.039269 35.028067, -92.039333 35.028067, -92.039464 35.028066, -92.039506 35.028066, -92.040592 35.027989, -92.041041 35.027989, -92.041615 35.028005, -92.041904 35.028013, -92.04241 35.028027, -92.042585 35.028064, -92.043115 35.028076, -92.043355 35.028094, -92.043307 35.028028, -92.043228 35.027943, -92.043136 35.027793, -92.043144 35.027751, -92.043154 35.027722, -92.043165 35.027661, -92.04316 35.027614, -92.043146 35.027582, -92.043086 35.027564, -92.04303 35.027564, -92.042986 35.027556, -92.042978 35.02754, -92.043015 35.027492, -92.043139 35.027466, -92.043197 35.027445, -92.043252 35.027413, -92.043432 35.02744, -92.043632 35.02749, -92.043977 35.02755, -92.044101 35.027579, -92.044249 35.027577, -92.044307 35.027561, -92.044375 35.02749, -92.044652 35.027276, -92.044971 35.027152, -92.045101 35.027026, -92.045148 35.026936, -92.045198 35.02686, -92.045268 35.026829, -92.045309 35.026767, -92.045341 35.02673, -92.045393 35.02668, -92.045457 35.026654, -92.045533 35.026701, -92.045596 35.026736, -92.045644 35.026809, -92.045689 35.026896, -92.045736 35.027015, -92.045913 35.027131, -92.046084 35.027197, -92.046332 35.027247, -92.046353 35.027281, -92.046343 35.027355, -92.046294 35.027393, -92.046242 35.027469, -92.046261 35.027537, -92.046299 35.027579, -92.046293 35.027616, -92.046229 35.027667, -92.046163 35.027711, -92.04612 35.02777, -92.046087 35.027827, -92.04605 35.027888, -92.045929 35.027978, -92.04586 35.028028, -92.045786 35.028103, -92.046422 35.028106, -92.046922 35.028109, -92.050329 35.02813, -92.050359 35.028252, -92.050367 35.028283, -92.050379 35.029429, -92.050387 35.030294, -92.050412 35.032892, -92.050421 35.033758, -92.050456 35.034054, -92.050564 35.034944, -92.0506 35.035241, -92.050587 35.036561, -92.050584 35.036861, -92.052514 35.03691, -92.061331 35.037074, -92.065333 35.037148, -92.069043 35.037217, -92.069334 35.037223, -92.069421 35.037218, -92.069453 35.036658, -92.070198 35.036781, -92.070202 35.036745, -92.070216 35.03664, -92.070221 35.036606, -92.070232 35.036388, -92.070264 35.035735, -92.070276 35.035518, -92.070287 35.035319, -92.07027 35.035061, -92.070236 35.03455, -92.070218 35.034261, -92.070165 35.033444, -92.070124 35.032781, -92.070107 35.032225, -92.070084 35.031445, -92.070019 35.031304, -92.069975 35.031196, -92.069784 35.031273, -92.069723 35.031295, -92.069387 35.03142, -92.069166 35.031502, -92.069138 35.031585, -92.06293 35.031482, -92.062234 35.031471, -92.061695 35.031462, -92.06172 35.029627, -92.061206 35.029618, -92.061212 35.027829, -92.061689 35.027834, -92.063491 35.027852, -92.063836 35.027855, -92.063843 35.027656, -92.063853 35.027422, -92.066101 35.027443, -92.06615 35.026106, -92.066187 35.025094, -92.065974 35.025092, -92.066012 35.024052, -92.066225 35.024054, -92.066286 35.02241, -92.070605 35.02249, -92.072317 35.022513, -92.072314 35.022534, -92.072198 35.023045, -92.072195 35.023071, -92.072174 35.023179, -92.072099 35.023581, -92.072073 35.023715, -92.072311 35.023904, -92.072965 35.023944, -92.076238 35.024037, -92.087984 35.024372, -92.08809 35.023425, -92.088563 35.019218, -92.087975 35.019157, -92.087769 35.019146, -92.087201 35.019153, -92.086357 35.019127, -92.085883 35.019125, -92.08548 35.019123, -92.083344 35.019104, -92.083105 35.019096, -92.082275 35.01907, -92.081741 35.019081, -92.08074 35.01906, -92.079876 35.019043, -92.07774 35.018999, -92.076741 35.018979, -92.076515 35.018974, -92.075581 35.01898, -92.074999 35.018984, -92.073578 35.018993, -92.072103 35.018983, -92.071182 35.018978, -92.071084 35.018993, -92.071009 35.019024, -92.070911 35.018718, -92.070863 35.018478, -92.070839 35.018022, -92.070817 35.017598, -92.070824 35.01743, -92.07083 35.016409, -92.070829 35.014335, -92.070828 35.01259, -92.070822 35.012389, -92.070805 35.011802, -92.070098 35.011786, -92.069741 35.011779, -92.069673 35.011778, -92.068843 35.011745, -92.068177 35.011728, -92.066181 35.011679, -92.065516 35.011663, -92.065475 35.01179, -92.065433 35.011924, -92.065318 35.012159, -92.06526 35.01228, -92.065855 35.012383, -92.06648 35.012319, -92.066633 35.013022, -92.066616 35.013478, -92.066549 35.015251, -92.065948 35.015253, -92.065029 35.015337, -92.064191 35.015462, -92.062626 35.015534, -92.062128 35.015561, -92.062041 35.017927, -92.059464 35.018525, -92.059066 35.018617, -92.059006 35.018616, -92.057759 35.018596, -92.057758 35.018632, -92.057703 35.019644, -92.057662 35.0204, -92.057564 35.022248, -92.05754 35.022246, -92.056344 35.022223, -92.055628 35.022211, -92.053063 35.022163, -92.053048 35.022574, -92.053025 35.023195, -92.052998 35.023915, -92.052927 35.025859, -92.052455 35.026002, -92.051471 35.025979, -92.051456 35.025197, -92.048542 35.025199, -92.048655 35.022103, -92.044206 35.022042, -92.044187 35.024074, -92.044653 35.024074, -92.044586 35.024326, -92.044632 35.024419, -92.04467 35.024494, -92.04496 35.024567, -92.045532 35.024593, -92.046852 35.024726, -92.047081 35.02475, -92.047192 35.024757, -92.047653 35.024788, -92.047981 35.02504, -92.048001 35.025206, -92.047998 35.026307, -92.046123 35.026211, -92.046051 35.026207, -92.045993 35.026207, -92.045833 35.02621, -92.045603 35.026213, -92.045473 35.026215, -92.044754 35.02636, -92.044479 35.026407, -92.044319 35.026436, -92.043999 35.026463, -92.043716 35.02647, -92.043221 35.026344, -92.04287 35.026192, -92.042496 35.026058, -92.042252 35.026047, -92.04203 35.026028, -92.041588 35.026043, -92.04152 35.026047, -92.040833 35.026096, -92.040512 35.026133, -92.04038 35.026147, -92.040323 35.026154, -92.039709 35.026197, -92.039301 35.026234, -92.039013 35.026304, -92.038956 35.026314, -92.038924 35.026323, -92.038807 35.026358, -92.038636 35.02641, -92.038579 35.026437, -92.038206 35.026627, -92.038154 35.026657, -92.038093 35.026712, -92.037943 35.026831, -92.037766 35.026974, -92.037504 35.027204, -92.037361 35.027332, -92.03714 35.027542, -92.036705 35.02774, -92.036543 35.027797, -92.036224 35.027912, -92.035926 35.027963, -92.035538 35.02803, -92.035407 35.028041, -92.034942 35.028082)), ((-92.665023 34.535205, -92.664924 34.534947, -92.664789 34.535218, -92.664763 34.535306, -92.664631 34.535852, -92.664605 34.536013, -92.664605 34.536125, -92.664637 34.536414, -92.664644 34.536542, -92.664655 34.536738, -92.664657 34.537409, -92.664662 34.5375, -92.664701 34.538139, -92.665023 34.535205)), ((-92.034942 35.028082, -92.032776 35.028088, -92.033386 35.028172, -92.033723 35.028204, -92.033897 35.028221, -92.034359 35.028154, -92.034714 35.028103, -92.034942 35.028082)), ((-91.983857 35.019649, -91.986876 35.019662, -91.98687 35.018953, -91.984716 35.018909, -91.983857 35.019649)), ((-91.945683 35.036069, -91.944779 35.036072, -91.944814 35.036339, -91.94487 35.036755, -91.945365 35.036768, -91.945671 35.036777, -91.945683 35.036069)), ((-92.044099 34.941381, -92.046082 34.94199, -92.046716 34.942202, -92.048281 34.942724, -92.048395 34.942264, -92.048414 34.941892, -92.048442 34.94138, -92.048465 34.940964, -92.048477 34.940502, -92.048491 34.940221, -92.048939 34.940226, -92.049353 34.940237, -92.049423 34.940213, -92.049447 34.940141, -92.049426 34.939281, -92.048949 34.939257, -92.048962 34.938014, -92.048971 34.937125, -92.047657 34.937041, -92.047121 34.937007, -92.046945 34.936995, -92.046823 34.940645, -92.046727 34.942011, -92.046075 34.94137, -92.046051 34.941362, -92.045342 34.941181, -92.044588 34.940989, -92.044404 34.940942, -92.044104 34.940827, -92.044102 34.940937, -92.0441 34.94127, -92.044099 34.941381)), ((-92.665833 34.518515, -92.66599 34.518421, -92.666463 34.51814, -92.666621 34.518047, -92.666875 34.517895, -92.667638 34.517443, -92.667893 34.517293, -92.667907 34.517285, -92.668344 34.517027, -92.669697 34.516229, -92.670149 34.515964, -92.670206 34.515928, -92.670378 34.515821, -92.670436 34.515786, -92.670381 34.51563, -92.670301 34.515358, -92.670192 34.514945, -92.670131 34.514759, -92.670063 34.514369, -92.670049 34.514212, -92.670035 34.513953, -92.670008 34.513681, -92.669983 34.513502, -92.669955 34.513382, -92.66992 34.513314, -92.669858 34.51324, -92.669824 34.51321, -92.669785 34.513185, -92.669694 34.513161, -92.669544 34.513141, -92.668398 34.513087, -92.668243 34.513084, -92.667735 34.513077, -92.667173 34.513051, -92.667014 34.513041, -92.666793 34.513033, -92.6663 34.513018, -92.666145 34.513006, -92.666092 34.512997, -92.66599 34.512972, -92.665889 34.512926, -92.665846 34.512895, -92.665808 34.512859, -92.665758 34.512793, -92.665737 34.512754, -92.665727 34.512721, -92.665701 34.512526, -92.665695 34.512387, -92.665674 34.512821, -92.665613 34.514124, -92.665593 34.514559, -92.6656 34.515307, -92.665615 34.516719, -92.665617 34.516936, -92.665619 34.517152, -92.665623 34.517553, -92.665631 34.518302, -92.665625 34.518371, -92.665609 34.518581, -92.665604 34.518651, -92.665649 34.518623, -92.665787 34.518542, -92.665833 34.518515)), ((-92.4135 34.698682, -92.413602 34.698679, -92.413849 34.698671, -92.414055 34.698686, -92.414352 34.698714, -92.414439 34.698711, -92.414482 34.698684, -92.414522 34.698655, -92.414541 34.698564, -92.414559 34.698355, -92.414572 34.698296, -92.414585 34.698255, -92.414639 34.698166, -92.414715 34.698106, -92.414946 34.697933, -92.415065 34.697762, -92.414966 34.697688, -92.414793 34.697479, -92.414693 34.697303, -92.41464 34.69704, -92.414514 34.696913, -92.414447 34.696875, -92.414374 34.696875, -92.414068 34.696951, -92.413888 34.696951, -92.413742 34.696907, -92.413489 34.696676, -92.413463 34.696647, -92.413435 34.697562, -92.413467 34.697629, -92.413499 34.697764, -92.413523 34.697917, -92.41352 34.698037, -92.4135 34.698682)), ((-92.126965 34.83536, -92.126969 34.835036, -92.126984 34.834834, -92.126995 34.834755, -92.127028 34.834519, -92.127036 34.83446, -92.127051 34.834192, -92.127055 34.834115, -92.127058 34.834071, -92.127077 34.833926, -92.127096 34.833855, -92.127153 34.83336, -92.124666 34.83329, -92.122792 34.833238, -92.122791 34.833407, -92.122785 34.834425, -92.122822 34.835373, -92.122859 34.835375, -92.12302 34.835377, -92.123793 34.835385, -92.124189 34.835395, -92.125215 34.835411, -92.125443 34.835412, -92.125973 34.835415, -92.126546 34.835407, -92.126981 34.835411, -92.126965 34.83536)), ((-92.605727 34.633006, -92.605129 34.633107, -92.603335 34.63341, -92.602737 34.633511, -92.602654 34.634239, -92.602627 34.634428, -92.602592 34.634689, -92.602597 34.63479, -92.602623 34.63488, -92.602658 34.634949, -92.602708 34.635026, -92.603207 34.635487, -92.603269 34.635558, -92.603417 34.635764, -92.603613 34.635979, -92.603851 34.636188, -92.603974 34.636281, -92.604123 34.636362, -92.604293 34.636432, -92.604337 34.636451, -92.604438 34.636481, -92.604794 34.636552, -92.605135 34.636589, -92.6052 34.636593, -92.60527 34.636109, -92.605484 34.63466, -92.605556 34.634177, -92.605587 34.633942, -92.605684 34.633238, -92.605692 34.633183, -92.605727 34.633006)), ((-92.364999 34.610817, -92.365029 34.61057, -92.365064 34.6102, -92.365126 34.609732, -92.365219 34.609188, -92.365313 34.608648, -92.365372 34.608333, -92.365384 34.60827, -92.365504 34.607751, -92.365511 34.607686, -92.365552 34.607445, -92.365557 34.607392, -92.365561 34.607361, -92.365573 34.607126, -92.365569 34.607073, -92.365557 34.606986, -92.365524 34.606727, -92.365513 34.606641, -92.365429 34.60631, -92.365356 34.60602, -92.365298 34.605732, -92.365213 34.60531, -92.365181 34.60515, -92.36516 34.604974, -92.364571 34.604967, -92.364389 34.604958, -92.364023 34.60491, -92.363778 34.604892, -92.363711 34.604891, -92.363675 34.604891, -92.363575 34.604897, -92.363347 34.60492, -92.363114 34.604933, -92.362464 34.60492, -92.36181 34.604902, -92.36135 34.604897, -92.35993 34.604868, -92.359357 34.604845, -92.35933 34.604844, -92.359205 34.604839, -92.357905 34.604823, -92.357898 34.605033, -92.357898 34.605172, -92.357899 34.605254, -92.357892 34.60563, -92.35789 34.605647, -92.357881 34.605728, -92.357861 34.605788, -92.357843 34.605824, -92.357716 34.606005, -92.357654 34.606109, -92.357634 34.606151, -92.357606 34.606216, -92.357502 34.606475, -92.357239 34.606468, -92.357103 34.606465, -92.356451 34.606441, -92.356189 34.606432, -92.356171 34.60714, -92.356169 34.607178, -92.356153 34.607522, -92.356148 34.607561, -92.356151 34.607597, -92.356169 34.607619, -92.356193 34.607636, -92.356228 34.607653, -92.356266 34.607664, -92.356335 34.607675, -92.356472 34.607678, -92.35753 34.607667, -92.357736 34.607678, -92.357947 34.607705, -92.358187 34.607767, -92.358316 34.607811, -92.358367 34.607841, -92.358402 34.607886, -92.35843 34.607964, -92.358436 34.608007, -92.358434 34.608126, -92.358431 34.608471, -92.358436 34.608701, -92.358445 34.609096, -92.358419 34.609372, -92.358408 34.60983, -92.358392 34.610064, -92.358385 34.610428, -92.358383 34.610591, -92.358372 34.610783, -92.358355 34.610963, -92.358353 34.611003, -92.358347 34.611098, -92.358347 34.611172, -92.358347 34.611256, -92.358356 34.611337, -92.358375 34.611417, -92.358404 34.611494, -92.358433 34.611552, -92.35847 34.611607, -92.358502 34.61164, -92.358521 34.611659, -92.358584 34.611705, -92.358641 34.611736, -92.358983 34.611965, -92.359266 34.611802, -92.359719 34.611514, -92.35983 34.611449, -92.359892 34.611421, -92.35993 34.611404, -92.360081 34.611351, -92.360232 34.611323, -92.360636 34.611304, -92.360887 34.611303, -92.361101 34.611303, -92.362586 34.611337, -92.363731 34.611352, -92.363922 34.61135, -92.364182 34.611349, -92.364934 34.611362, -92.364999 34.610817)), ((-92.108278 34.886263, -92.108466 34.886265, -92.109442 34.886233, -92.11013 34.886153, -92.110882 34.886121, -92.111778 34.886281, -92.113154 34.886457, -92.117762 34.886633, -92.121531 34.886775, -92.121642 34.886874, -92.122307 34.887474, -92.122348 34.887511, -92.122432 34.887578, -92.12257 34.887668, -92.122719 34.887744, -92.122971 34.88785, -92.123247 34.887955, -92.12347 34.888026, -92.123671 34.888072, -92.123877 34.888108, -92.124563 34.888189, -92.124683 34.887562, -92.124755 34.887182, -92.124807 34.886893, -92.134088 34.887271, -92.134081 34.887761, -92.134052 34.889951, -92.134122 34.889952, -92.13532 34.889961, -92.135361 34.889962, -92.138761 34.890028, -92.138758 34.890074, -92.138731 34.890291, -92.138681 34.890549, -92.138648 34.890679, -92.138603 34.890804, -92.138466 34.891132, -92.138328 34.891361, -92.138171 34.891581, -92.137943 34.891846, -92.13775 34.892092, -92.137537 34.892307, -92.137398 34.892472, -92.136219 34.893773, -92.136089 34.893969, -92.135978 34.894173, -92.135923 34.89431, -92.135895 34.894403, -92.13586 34.89452, -92.135831 34.894662, -92.135801 34.894911, -92.135753 34.895534, -92.135726 34.89593, -92.135703 34.896205, -92.135151 34.896197, -92.134666 34.896186, -92.13427 34.896182, -92.133814 34.896184, -92.133323 34.896229, -92.133068 34.89627, -92.132661 34.896354, -92.132296 34.896437, -92.132082 34.896486, -92.132229 34.896949, -92.132357 34.89732, -92.132478 34.897666, -92.132598 34.898026, -92.132647 34.898112, -92.132668 34.898139, -92.132734 34.898328, -92.132672 34.898588, -92.132615 34.898782, -92.132535 34.898979, -92.132455 34.899123, -92.132315 34.899338, -92.131913 34.899893, -92.131766 34.900084, -92.131685 34.900175, -92.131558 34.900299, -92.13142 34.900418, -92.131277 34.900524, -92.131052 34.90067, -92.130899 34.90076, -92.130754 34.900835, -92.130618 34.900897, -92.130409 34.900976, -92.130267 34.901022, -92.130049 34.90108, -92.129826 34.901126, -92.129382 34.901209, -92.129535 34.901215, -92.129805 34.901239, -92.130226 34.901265, -92.130561 34.901294, -92.130822 34.901341, -92.131079 34.901405, -92.132354 34.901766, -92.133105 34.901986, -92.133464 34.902085, -92.133676 34.902144, -92.133957 34.90223, -92.134227 34.902336, -92.134997 34.902719, -92.135626 34.903336, -92.135675 34.90339, -92.135909 34.903692, -92.13604 34.903873, -92.136149 34.904055, -92.136239 34.904235, -92.136277 34.904326, -92.136326 34.904479, -92.136396 34.904768, -92.136948 34.907492, -92.136986 34.90772, -92.136997 34.907873, -92.136997 34.907971, -92.136976 34.908199, -92.136955 34.908294, -92.136901 34.908452, -92.136796 34.90869, -92.136679 34.90896, -92.136646 34.909063, -92.136609 34.909237, -92.136591 34.909407, -92.136588 34.909588, -92.136597 34.909713, -92.136611 34.909803, -92.136657 34.909979, -92.136714 34.910149, -92.136772 34.910284, -92.13665 34.910315, -92.136012 34.910506, -92.135904 34.910515, -92.135794 34.910507, -92.135484 34.910463, -92.135308 34.910433, -92.13522 34.910428, -92.135107 34.910435, -92.134995 34.910453, -92.134866 34.910486, -92.134435 34.910614, -92.134277 34.910672, -92.133835 34.910844, -92.13384 34.910893, -92.13388 34.911057, -92.133948 34.911216, -92.134026 34.911374, -92.134096 34.911534, -92.134242 34.911898, -92.134417 34.912362, -92.134451 34.912476, -92.13447 34.912568, -92.13448 34.912683, -92.134464 34.91279, -92.134736 34.912809, -92.134946 34.912807, -92.135076 34.912793, -92.135306 34.912751, -92.135456 34.912714, -92.13569 34.912646, -92.136127 34.912528, -92.136903 34.912305, -92.137238 34.912214, -92.137546 34.912119, -92.137424 34.911827, -92.137334 34.911632, -92.137285 34.911505, -92.138353 34.911193, -92.138503 34.911159, -92.138367 34.910805, -92.138203 34.910411, -92.138033 34.909976, -92.138013 34.909934, -92.138201 34.909872, -92.139432 34.909528, -92.140537 34.909223, -92.141366 34.908983, -92.143259 34.908439, -92.143312 34.908602, -92.144331 34.91106, -92.150908 34.909204, -92.153032 34.9086, -92.156862 34.90751, -92.158495 34.907047, -92.15847 34.907006, -92.158323 34.906672, -92.158205 34.906423, -92.157891 34.905646, -92.1574 34.904483, -92.157386 34.904425, -92.155776 34.904886, -92.151953 34.905971, -92.151513 34.904936, -92.151407 34.90469, -92.151288 34.904463, -92.151167 34.904276, -92.151006 34.904057, -92.150903 34.903931, -92.150793 34.903813, -92.150705 34.903729, -92.150613 34.903649, -92.150419 34.903492, -92.150197 34.903329, -92.149731 34.903016, -92.14922 34.902656, -92.148788 34.902367, -92.147822 34.901682, -92.147351 34.901368, -92.147073 34.901155, -92.146873 34.900976, -92.146743 34.900842, -92.146498 34.900566, -92.146359 34.900357, -92.146412 34.900342, -92.146461 34.900319, -92.147354 34.900078, -92.147577 34.900005, -92.147791 34.899917, -92.14801 34.8998, -92.148218 34.89967, -92.148341 34.899578, -92.148443 34.89949, -92.14871 34.899226, -92.149258 34.898654, -92.149626 34.898281, -92.150514 34.897324, -92.150764 34.897031, -92.150826 34.896945, -92.150843 34.896895, -92.15085 34.896843, -92.150859 34.896586, -92.15237 34.896604, -92.155916 34.896694, -92.157157 34.896732, -92.158168 34.896752, -92.15908 34.896778, -92.159874 34.896813, -92.160511 34.896831, -92.160666 34.896834, -92.161332 34.896839, -92.161516 34.896841, -92.163077 34.896872, -92.163875 34.896897, -92.164173 34.896907, -92.164631 34.896935, -92.164858 34.896954, -92.16551 34.897004, -92.166119 34.897082, -92.166709 34.897165, -92.167818 34.897345, -92.168284 34.897415, -92.169573 34.89763, -92.170154 34.897732, -92.170672 34.897816, -92.170881 34.897845, -92.170944 34.897854, -92.171201 34.897846, -92.171162 34.897783, -92.171076 34.897623, -92.171046 34.897562, -92.170993 34.897434, -92.170966 34.89737, -92.170882 34.897091, -92.170843 34.896935, -92.170812 34.89681, -92.170792 34.896698, -92.170776 34.896529, -92.170777 34.896359, -92.170857 34.896357, -92.170892 34.896357, -92.17097 34.895442, -92.171133 34.893458, -92.171022 34.893458, -92.17109 34.892733, -92.171116 34.892537, -92.17113 34.892437, -92.171197 34.892095, -92.171281 34.891755, -92.17138 34.891395, -92.171909 34.889552, -92.171942 34.889446, -92.172057 34.889468, -92.172069 34.889412, -92.172095 34.889321, -92.172101 34.8893, -92.172131 34.889207, -92.172258 34.888874, -92.172317 34.888779, -92.17245 34.888581, -92.172541 34.888457, -92.172617 34.888352, -92.172737 34.888217, -92.172831 34.888119, -92.172929 34.888025, -92.173024 34.88793, -92.173191 34.887778, -92.173311 34.887669, -92.173478 34.887534, -92.173972 34.887116, -92.174169 34.88692, -92.174416 34.886658, -92.17584 34.884621, -92.17591 34.884507, -92.176727 34.883338, -92.176796 34.883229, -92.177055 34.882866, -92.17722 34.882658, -92.177288 34.882579, -92.177387 34.882463, -92.177474 34.882332, -92.177807 34.882335, -92.177805 34.88188, -92.177781 34.881899, -92.177657 34.882013, -92.177594 34.881969, -92.177478 34.881895, -92.177072 34.881619, -92.176677 34.881342, -92.175861 34.880784, -92.175821 34.880756, -92.175678 34.880654, -92.175433 34.880482, -92.174951 34.880128, -92.174876 34.88006, -92.174842 34.880029, -92.174747 34.879921, -92.174667 34.879805, -92.174644 34.879765, -92.174541 34.879539, -92.174523 34.879492, -92.174501 34.879432, -92.174449 34.879329, -92.174406 34.879263, -92.174316 34.879146, -92.17422 34.879063, -92.17415 34.879014, -92.174103 34.878988, -92.174035 34.878951, -92.17391 34.8789, -92.173779 34.878864, -92.173688 34.878848, -92.173555 34.87883, -92.17259 34.878807, -92.172178 34.878802, -92.170712 34.878767, -92.17008 34.878751, -92.169978 34.882226, -92.166509 34.881954, -92.161523 34.881849, -92.158759 34.881737, -92.154979 34.881664, -92.153141 34.88163, -92.152266 34.880762, -92.151141 34.880743, -92.151052 34.880472, -92.150937 34.880253, -92.150874 34.880159, -92.150596 34.879931, -92.150355 34.879775, -92.150266 34.879671, -92.150253 34.879535, -92.150266 34.879358, -92.149824 34.879345, -92.149258 34.879328, -92.148323 34.879307, -92.148345 34.878788, -92.148347 34.87875, -92.150825 34.878817, -92.151627 34.878839, -92.152304 34.878845, -92.153885 34.878841, -92.154851 34.878827, -92.155772 34.878822, -92.156131 34.878809, -92.156501 34.878759, -92.156681 34.878721, -92.157211 34.878588, -92.157401 34.878548, -92.157564 34.878525, -92.157725 34.878507, -92.157839 34.8785, -92.158139 34.878499, -92.158425 34.878501, -92.159686 34.878524, -92.160409 34.878543, -92.16124 34.878553, -92.1613 34.878554, -92.161344 34.878555, -92.161404 34.878557, -92.161601 34.878563, -92.161805 34.874813, -92.16197 34.874815, -92.161973 34.874748, -92.161972 34.874671, -92.16196 34.871982, -92.161963 34.871935, -92.161999 34.871392, -92.160886 34.871359, -92.160502 34.871348, -92.158642 34.871294, -92.156545 34.871227, -92.156487 34.871225, -92.15622 34.871214, -92.154931 34.871187, -92.154441 34.871177, -92.154093 34.871181, -92.153931 34.871194, -92.15374 34.871216, -92.153629 34.871234, -92.153473 34.871262, -92.15231 34.871492, -92.15201 34.87154, -92.151798 34.871549, -92.151466 34.87155, -92.151042 34.871545, -92.15066 34.871545, -92.149826 34.871535, -92.148823 34.871528, -92.148348 34.871515, -92.14827 34.871496, -92.148221 34.871475, -92.148159 34.871432, -92.14814 34.871415, -92.148096 34.871356, -92.148077 34.871313, -92.148068 34.871268, -92.148059 34.870998, -92.14808 34.870281, -92.14808 34.869977, -92.148077 34.869747, -92.148059 34.869537, -92.148041 34.869425, -92.148017 34.869352, -92.147968 34.869247, -92.147925 34.86918, -92.147848 34.869087, -92.147766 34.869013, -92.147679 34.868958, -92.147587 34.868907, -92.147242 34.86877, -92.146681 34.868566, -92.146124 34.868384, -92.145582 34.868206, -92.145325 34.868116, -92.145095 34.868028, -92.144963 34.86797, -92.144386 34.86772, -92.144065 34.867555, -92.143843 34.867456, -92.14375 34.867425, -92.143516 34.867367, -92.143385 34.867344, -92.143241 34.867326, -92.143046 34.867308, -92.142073 34.867267, -92.141117 34.867246, -92.140587 34.867239, -92.139983 34.867221, -92.139489 34.867214, -92.139233 34.86722, -92.139263 34.866864, -92.139289 34.866382, -92.139312 34.866193, -92.139352 34.866005, -92.139365 34.865977, -92.139415 34.865895, -92.139482 34.865823, -92.139707 34.865647, -92.139795 34.865571, -92.139957 34.86541, -92.140012 34.865332, -92.140079 34.865208, -92.140128 34.865078, -92.140163 34.864977, -92.140181 34.864874, -92.140182 34.86477, -92.140163 34.864621, -92.140139 34.864526, -92.140086 34.86444, -92.140017 34.864362, -92.139932 34.864296, -92.139896 34.864274, -92.139726 34.864207, -92.139578 34.864165, -92.138886 34.863998, -92.138132 34.86383, -92.137099 34.86359, -92.136065 34.86336, -92.1344 34.862974, -92.133213 34.862696, -92.132544 34.862546, -92.13221 34.862477, -92.132138 34.862459, -92.132047 34.862436, -92.131739 34.862373, -92.131562 34.862326, -92.131416 34.862273, -92.131099 34.862124, -92.130949 34.862034, -92.130726 34.861912, -92.130148 34.861578, -92.12986 34.861433, -92.129943 34.86127, -92.130077 34.861005, -92.130303 34.860584, -92.130505 34.860257, -92.130719 34.859935, -92.13102 34.859527, -92.131279 34.859203, -92.131369 34.8591, -92.131651 34.858798, -92.13185 34.858604, -92.132057 34.858415, -92.132359 34.858125, -92.132658 34.857859, -92.132921 34.857641, -92.133227 34.857402, -92.133568 34.857157, -92.134302 34.856673, -92.135631 34.855809, -92.135498 34.855669, -92.135122 34.855273, -92.134962 34.855209, -92.134834 34.855257, -92.134802 34.855193, -92.134802 34.855097, -92.134866 34.854969, -92.135026 34.854777, -92.135122 34.854601, -92.135346 34.854505, -92.135538 34.854425, -92.135522 34.854217, -92.135394 34.854121, -92.135154 34.854041, -92.13501 34.853945, -92.134882 34.853849, -92.134802 34.853721, -92.134802 34.853593, -92.134818 34.853369, -92.13485 34.853113, -92.13493 34.852953, -92.134994 34.852953, -92.135298 34.852889, -92.135506 34.852729, -92.135938 34.852377, -92.136002 34.852201, -92.135794 34.851929, -92.135858 34.851833, -92.136274 34.851993, -92.136434 34.851929, -92.136402 34.851801, -92.136402 34.851497, -92.136674 34.851449, -92.13693 34.851321, -92.137138 34.851481, -92.137602 34.851257, -92.13757 34.851129, -92.13757 34.850393, -92.137426 34.850025, -92.137266 34.849817, -92.136866 34.849641, -92.135746 34.849177, -92.135522 34.849065, -92.13541 34.848889, -92.13541 34.848665, -92.135618 34.848185, -92.135602 34.848057, -92.135058 34.848185, -92.134546 34.848169, -92.13413 34.847993, -92.133858 34.847801, -92.133618 34.847481, -92.133426 34.847305, -92.133266 34.847433, -92.133298 34.847945, -92.133266 34.848297, -92.133202 34.848537, -92.133282 34.848777, -92.133346 34.849065, -92.132978 34.849225, -92.132626 34.849465, -92.132162 34.849529, -92.131954 34.849497, -92.131778 34.849305, -92.131554 34.848985, -92.13125 34.848873, -92.130978 34.848969, -92.130946 34.849305, -92.130914 34.849673, -92.130674 34.849865, -92.130322 34.849865, -92.130146 34.849769, -92.129906 34.849593, -92.129586 34.849337, -92.129154 34.849129, -92.12877 34.849001, -92.128514 34.849097, -92.12845 34.849369, -92.128578 34.849753, -92.12853 34.850041, -92.128434 34.850265, -92.128578 34.850649, -92.128562 34.850809, -92.128226 34.850905, -92.12789 34.851033, -92.127714 34.851001, -92.127602 34.850809, -92.127506 34.850521, -92.127442 34.850297, -92.127314 34.850217, -92.12701 34.85025, -92.126738 34.850298, -92.126483 34.850395, -92.126195 34.850347, -92.126083 34.850315, -92.125971 34.85014, -92.125875 34.849916, -92.125836 34.849894, -92.125731 34.849836, -92.125683 34.84974, -92.125811 34.849532, -92.125907 34.849292, -92.125811 34.849052, -92.125523 34.848795, -92.125299 34.848571, -92.125139 34.848235, -92.125059 34.847931, -92.125027 34.847771, -92.124755 34.847515, -92.124563 34.847307, -92.124386 34.846906, -92.124114 34.84633, -92.123826 34.84577, -92.12357 34.845466, -92.123265 34.845178, -92.122977 34.845017, -92.122737 34.844857, -92.122529 34.844665, -92.122417 34.844537, -92.122179 34.844322, -92.122286 34.844228, -92.1224 34.844127, -92.122577 34.844313, -92.122721 34.844601, -92.122897 34.844793, -92.123185 34.844969, -92.123537 34.845177, -92.12366 34.845281, -92.124154 34.844278, -92.124249 34.844172, -92.124599 34.843811, -92.124834 34.843535, -92.124596 34.843385, -92.124034 34.842991, -92.123787 34.84283, -92.124231 34.842356, -92.125173 34.841362, -92.125899 34.841813, -92.126969 34.842511, -92.12795 34.843134, -92.128735 34.843643, -92.128961 34.843742, -92.129117 34.843784, -92.129278 34.843809, -92.129442 34.843816, -92.129497 34.843814, -92.129604 34.843807, -92.12971 34.843788, -92.129879 34.843738, -92.129975 34.8437, -92.130067 34.843655, -92.130154 34.843603, -92.131065 34.842962, -92.131757 34.842476, -92.132385 34.842049, -92.134278 34.840716, -92.134875 34.840294, -92.135067 34.840162, -92.135143 34.840236, -92.135207 34.840294, -92.135247 34.840332, -92.135292 34.840376, -92.13534 34.840425, -92.135442 34.840538, -92.135495 34.840597, -92.135604 34.840714, -92.135659 34.840773, -92.135714 34.840831, -92.13575 34.840869, -92.135769 34.840889, -92.135823 34.840947, -92.135877 34.841005, -92.135987 34.841122, -92.136043 34.841183, -92.1361 34.841246, -92.136159 34.841309, -92.136281 34.841438, -92.136345 34.841505, -92.136375 34.841537, -92.13646 34.841486, -92.136509 34.841456, -92.136555 34.841423, -92.136616 34.841384, -92.136767 34.841288, -92.136852 34.841235, -92.13694 34.841177, -92.137032 34.841118, -92.137126 34.84106, -92.137217 34.841004, -92.137306 34.84095, -92.137472 34.840845, -92.137546 34.840798, -92.137614 34.840747, -92.137669 34.84069, -92.137727 34.84057, -92.137732 34.840521, -92.137724 34.840406, -92.137625 34.840404, -92.13754 34.840404, -92.137455 34.840407, -92.137366 34.840407, -92.137178 34.840405, -92.13708 34.840402, -92.136983 34.840397, -92.136885 34.84039, -92.136693 34.840358, -92.136604 34.840328, -92.136549 34.840301, -92.136515 34.840285, -92.136428 34.840238, -92.136257 34.840137, -92.136172 34.840086, -92.136088 34.840037, -92.136007 34.83999, -92.135932 34.839947, -92.135808 34.839873, -92.13574 34.839829, -92.135653 34.839748, -92.135715 34.839704, -92.135795 34.839633, -92.135902 34.839517, -92.135935 34.839476, -92.135998 34.839349, -92.136047 34.839254, -92.136108 34.839095, -92.136156 34.838932, -92.136188 34.838771, -92.136198 34.838619, -92.136151 34.83799, -92.136154 34.83789, -92.136177 34.83774, -92.136203 34.837641, -92.136211 34.83762, -92.136296 34.83741, -92.136631 34.837418, -92.137633 34.837402, -92.138939 34.837391, -92.140287 34.835672, -92.13946 34.83589, -92.137713 34.835874, -92.137759 34.835787, -92.137791 34.835726, -92.13784 34.835607, -92.137866 34.835536, -92.137899 34.835445, -92.137943 34.835298, -92.137955 34.835236, -92.137973 34.835149, -92.138 34.834836, -92.13802 34.834137, -92.138026 34.833858, -92.13803 34.833751, -92.138048 34.833522, -92.138061 34.833187, -92.138065 34.832832, -92.138056 34.832591, -92.137024 34.833183, -92.136433 34.833529, -92.135118 34.834288, -92.134667 34.834549, -92.133261 34.835361, -92.133005 34.835514, -92.132936 34.835557, -92.132523 34.835788, -92.132069 34.836055, -92.131398 34.836442, -92.131387 34.836448, -92.130915 34.83672, -92.130863 34.836749, -92.130428 34.836232, -92.130342 34.836141, -92.1302 34.836015, -92.130044 34.835899, -92.129871 34.835802, -92.129687 34.835722, -92.129522 34.835662, -92.129398 34.835632, -92.129053 34.835579, -92.12867 34.835539, -92.127444 34.835428, -92.126981 34.835411, -92.126994 34.835511, -92.127004 34.835886, -92.126996 34.836135, -92.126962 34.836607, -92.126957 34.836805, -92.126909 34.837378, -92.12689 34.837734, -92.126888 34.837958, -92.126841 34.837944, -92.126696 34.837912, -92.126547 34.837896, -92.125976 34.837897, -92.125537 34.837909, -92.124701 34.837912, -92.124291 34.837901, -92.124055 34.837899, -92.123989 34.837899, -92.123365 34.837904, -92.12294 34.837896, -92.122665 34.83789, -92.122638 34.83789, -92.122741 34.841382, -92.119535 34.841341, -92.119498 34.843385, -92.119493 34.843729, -92.119492 34.843861, -92.118993 34.844143, -92.118673 34.844361, -92.118193 34.844601, -92.117665 34.844889, -92.117105 34.845049, -92.116289 34.845241, -92.115425 34.845417, -92.114561 34.845465, -92.113937 34.845449, -92.113361 34.845401, -92.112721 34.845289, -92.112369 34.845305, -92.111985 34.845385, -92.111473 34.845465, -92.110737 34.845689, -92.110241 34.845721, -92.10968 34.845705, -92.109376 34.845689, -92.109088 34.845737, -92.108809 34.845652, -92.108368 34.845913, -92.108191 34.845993, -92.107727 34.846185, -92.107327 34.846234, -92.106446 34.846202, -92.105854 34.846154, -92.105582 34.846106, -92.105182 34.84601, -92.104781 34.84585, -92.104429 34.845706, -92.104205 34.845626, -92.103805 34.84553, -92.103308 34.845514, -92.102748 34.845578, -92.1023 34.845562, -92.101851 34.845434, -92.101499 34.84529, -92.101115 34.84513, -92.100827 34.844987, -92.100411 34.844731, -92.10017 34.844571, -92.09993 34.844315, -92.099722 34.843979, -92.099562 34.843707, -92.099305 34.843323, -92.099081 34.843051, -92.098761 34.842811, -92.098232 34.842555, -92.09804 34.842491, -92.097608 34.842459, -92.09724 34.842411, -92.096551 34.842299, -92.096183 34.842203, -92.095799 34.84214, -92.095399 34.842124, -92.095143 34.842268, -92.094982 34.842444, -92.094758 34.842684, -92.094502 34.842892, -92.094214 34.843068, -92.093862 34.8431, -92.093605 34.843052, -92.093253 34.842988, -92.093045 34.842908, -92.090135 34.84334, -92.089833 34.843517, -92.08953 34.843823, -92.088958 34.844561, -92.088656 34.844883, -92.088225 34.845142, -92.08757 34.845417, -92.087203 34.845515, -92.086771 34.845582, -92.086228 34.845617, -92.085683 34.845524, -92.08517 34.845351, -92.084721 34.845113, -92.084688 34.844985, -92.084752 34.844809, -92.084815 34.844664, -92.084639 34.844585, -92.084206 34.844524, -92.083919 34.844637, -92.083504 34.844928, -92.083313 34.844993, -92.082736 34.84474, -92.082702 34.844372, -92.082764 34.84397, -92.082778 34.843602, -92.08276 34.843282, -92.08263 34.843011, -92.082405 34.842836, -92.082149 34.842805, -92.08183 34.842919, -92.081832 34.843159, -92.081914 34.843607, -92.081886 34.844039, -92.081519 34.844265, -92.081103 34.844267, -92.080766 34.844077, -92.080557 34.84395, -92.079982 34.843937, -92.079775 34.844243, -92.079394 34.844693, -92.079186 34.84487, -92.078914 34.844856, -92.078594 34.844697, -92.078257 34.844427, -92.078063 34.84414, -92.078014 34.843915, -92.077964 34.843659, -92.077931 34.843484, -92.077834 34.843308, -92.077627 34.843229, -92.077323 34.843279, -92.077304 34.843287, -92.077004 34.843425, -92.076701 34.843602, -92.076381 34.84378, -92.076078 34.84399, -92.075743 34.843944, -92.075726 34.843768, -92.075708 34.8434, -92.075546 34.843113, -92.075433 34.845613, -92.075422 34.845706, -92.075417 34.84575, -92.074509 34.845695, -92.074059 34.848396, -92.073921 34.849075, -92.07386 34.849506, -92.073715 34.849609, -92.073456 34.849655, -92.071564 34.849529, -92.070862 34.849495, -92.070786 34.849705, -92.070724 34.849957, -92.070498 34.851452, -92.071362 34.851652, -92.071662 34.851699, -92.071922 34.851728, -92.072399 34.85176, -92.073164 34.85181, -92.07406 34.851853, -92.0752 34.85193, -92.075195 34.852384, -92.075168 34.852878, -92.075133 34.853556, -92.07483 34.859155, -92.074803 34.859636, -92.074726 34.861079, -92.0747 34.861561, -92.074682 34.86187, -92.074632 34.862799, -92.074616 34.863109, -92.074615 34.863134, -92.074549 34.863314, -92.074372 34.8666, -92.074753 34.866608, -92.07592 34.866636, -92.077287 34.866668, -92.077806 34.866685, -92.079018 34.86671, -92.079937 34.866729, -92.080162 34.866733, -92.080688 34.866734, -92.081419 34.866745, -92.082317 34.866767, -92.083123 34.866774, -92.08336 34.866779, -92.084685 34.866809, -92.085401 34.866815, -92.086132 34.866826, -92.087348 34.86685, -92.08799 34.866863, -92.088336 34.86687, -92.088248 34.870148, -92.088241 34.870428, -92.088217 34.871351, -92.088202 34.871794, -92.088157 34.873198, -92.088122 34.87431, -92.088257 34.874312, -92.088251 34.874471, -92.088196 34.875906, -92.088141 34.877067, -92.088117 34.877562, -92.088079 34.878392, -92.088078 34.878578, -92.088094 34.878763, -92.088123 34.878919, -92.088129 34.878947, -92.08818 34.879128, -92.088193 34.879161, -92.088226 34.87925, -92.088314 34.879418, -92.088407 34.879566, -92.088448 34.879625, -92.088582 34.879797, -92.088796 34.88003, -92.089196 34.880419, -92.089225 34.880448, -92.088344 34.881051, -92.087988 34.881295, -92.087505 34.881632, -92.086216 34.882516, -92.085642 34.88291, -92.08518 34.883234, -92.0846 34.883665, -92.08422 34.883966, -92.083852 34.884277, -92.083532 34.884564, -92.083365 34.884714, -92.083007 34.885052, -92.082615 34.885451, -92.08226 34.885849, -92.081916 34.886254, -92.081584 34.886665, -92.081045 34.887375, -92.079337 34.88965, -92.078658 34.890549, -92.078277 34.891056, -92.074951 34.895486, -92.074449 34.896156, -92.074604 34.896155, -92.081521 34.896136, -92.08197 34.896151, -92.086841 34.896316, -92.086605 34.896779, -92.086203 34.897541, -92.085812 34.8983, -92.085598 34.898673, -92.085399 34.899052, -92.085017 34.89979, -92.084641 34.90051, -92.086326 34.901405, -92.086609 34.901584, -92.08666 34.90162, -92.086824 34.90172, -92.087598 34.902111, -92.087661 34.902137, -92.087901 34.901821, -92.088106 34.901537, -92.088242 34.901358, -92.088655 34.900795, -92.089121 34.900147, -92.089708 34.899371, -92.089975 34.899004, -92.090369 34.898474, -92.090664 34.898103, -92.090862 34.897869, -92.091122 34.89794, -92.090847 34.898253, -92.090575 34.898596, -92.089802 34.899639, -92.088481 34.901444, -92.086824 34.903688, -92.086265 34.904452, -92.086031 34.904345, -92.085409 34.905201, -92.08519 34.905524, -92.084876 34.906026, -92.084625 34.906457, -92.084497 34.906694, -92.083886 34.906723, -92.083349 34.906331, -92.083098 34.905984, -92.082311 34.905646, -92.081793 34.905375, -92.081742 34.906991, -92.081618 34.910615, -92.081642 34.911002, -92.081547 34.913409, -92.081486 34.914676, -92.077161 34.914552, -92.077035 34.914556, -92.076993 34.915171, -92.076939 34.916012, -92.07692 34.916319, -92.076916 34.916389, -92.076892 34.916755, -92.076846 34.917385, -92.076853 34.917945, -92.076875 34.918094, -92.076899 34.918193, -92.077038 34.918199, -92.077376 34.918206, -92.077666 34.918213, -92.077719 34.918214, -92.078125 34.918223, -92.078939 34.918242, -92.079723 34.918258, -92.080193 34.918266, -92.081309 34.918265, -92.081354 34.918266, -92.081403 34.918267, -92.081782 34.918274, -92.08197 34.918285, -92.081908 34.918787, -92.081448 34.922172, -92.081285 34.923332, -92.081143 34.92444, -92.080934 34.925897, -92.080827 34.926696, -92.080739 34.927314, -92.080662 34.927906, -92.080571 34.92854, -92.080452 34.929431, -92.080327 34.930203, -92.080261 34.930559, -92.08019 34.930861, -92.080111 34.931148, -92.079995 34.931538, -92.079857 34.931943, -92.079806 34.932077, -92.079753 34.932267, -92.079654 34.932548, -92.079576 34.932732, -92.079533 34.932823, -92.07938 34.933188, -92.079134 34.933693, -92.078985 34.933984, -92.078826 34.934272, -92.078194 34.935337, -92.077875 34.935841, -92.07728 34.936838, -92.076366 34.938311, -92.076239 34.938486, -92.076193 34.938543, -92.075994 34.938793, -92.075783 34.939036, -92.075582 34.93925, -92.07537 34.939458, -92.074514 34.940175, -92.073869 34.940712, -92.073627 34.940896, -92.073374 34.94107, -92.073166 34.941193, -92.073155 34.941213, -92.073112 34.941277, -92.07251 34.941564, -92.072449 34.941593, -92.068896 34.943482, -92.067784 34.943627, -92.06674 34.943761, -92.06608 34.943923, -92.065243 34.944129, -92.064764 34.944247, -92.06446 34.944326, -92.06369 34.944526, -92.063608 34.944548, -92.063102 34.944682, -92.062643 34.944802, -92.061854 34.945009, -92.061224 34.945176, -92.060838 34.945299, -92.06063 34.945332, -92.060041 34.945486, -92.059845 34.945538, -92.059769 34.945578, -92.059543 34.945697, -92.059468 34.945738, -92.059381 34.945783, -92.059123 34.94592, -92.059037 34.945966, -92.058944 34.946014, -92.058792 34.946095, -92.058669 34.946162, -92.058578 34.946213, -92.058373 34.946322, -92.058174 34.94643, -92.057877 34.946583, -92.057744 34.94661, -92.057518 34.946659, -92.057344 34.946682, -92.056953 34.946735, -92.056822 34.946725, -92.056648 34.946712, -92.056584 34.946708, -92.056393 34.946698, -92.05633 34.946695, -92.055269 34.946639, -92.05291 34.94699, -92.050424 34.947362, -92.049668 34.947475, -92.049515 34.94751, -92.04937 34.947548, -92.047905 34.947826, -92.047295 34.948074, -92.046635 34.948342, -92.046478 34.948406, -92.045998 34.948616, -92.045659 34.948758, -92.044645 34.949184, -92.044307 34.949327, -92.043994 34.947793, -92.043991 34.947762, -92.043981 34.947618, -92.044007 34.946854, -92.044082 34.943017, -92.044082 34.942976, -92.044099 34.941381, -92.042093 34.940775, -92.041246 34.940518, -92.041098 34.940473, -92.040959 34.940429, -92.040878 34.940403, -92.040318 34.940221, -92.039391 34.939919, -92.039374 34.939913, -92.038897 34.939747, -92.038228 34.93953, -92.037797 34.939402, -92.036936 34.939174, -92.036407 34.939072, -92.036132 34.939032, -92.035858 34.939007, -92.035374 34.938996, -92.032053 34.93915, -92.029739 34.93927, -92.029412 34.939287, -92.027885 34.939368, -92.027479 34.93939, -92.025931 34.939465, -92.025325 34.939506, -92.024291 34.939547, -92.02298 34.939611, -92.022324 34.939603, -92.021915 34.939599, -92.021294 34.939528, -92.020482 34.939401, -92.019925 34.939261, -92.018196 34.938775, -92.018024 34.938728, -92.017193 34.938527, -92.016186 34.938363, -92.015046 34.938295, -92.014175 34.938266, -92.011132 34.938235, -92.009106 34.938214, -92.008762 34.938211, -92.008766 34.937836, -92.008776 34.936971, -92.008772 34.936704, -92.008754 34.936533, -92.00873 34.936362, -92.008669 34.936124, -92.008591 34.935889, -92.00848 34.93564, -92.008352 34.935419, -92.008242 34.935263, -92.008151 34.935141, -92.008096 34.935067, -92.007868 34.934814, -92.007601 34.934597, -92.007356 34.934416, -92.004922 34.932455, -92.00479 34.932323, -92.004687 34.932199, -92.004605 34.932103, -92.004555 34.932017, -92.004479 34.931898, -92.004112 34.93123, -92.003212 34.929646, -92.00226 34.928277, -92.000512 34.928259, -92.000248 34.928256, -91.994212 34.928195, -91.9922 34.928175, -91.99201 34.928174, -91.991442 34.928172, -91.991253 34.928172, -91.990804 34.928168, -91.98946 34.928158, -91.989012 34.928155, -91.988177 34.928149, -91.987813 34.928146, -91.987336 34.928143, -91.986844 34.92814, -91.986352 34.928137, -91.986017 34.928134, -91.985674 34.928132, -91.98484 34.928126, -91.98478 34.928851, -91.984678 34.930099, -91.984199 34.935967, -91.984194 34.936019, -91.984184 34.936148, -91.98417 34.936329, -91.984093 34.93727, -91.984494 34.937925, -91.982175 34.937922, -91.979994 34.937891, -91.979814 34.937891, -91.979622 34.937886, -91.977952 34.93786, -91.97734 34.937857, -91.977028 34.937851, -91.976418 34.937836, -91.975502 34.93782, -91.97319 34.937801, -91.971184 34.937794, -91.970145 34.937784, -91.969522 34.937779, -91.967884 34.937768, -91.967746 34.937761, -91.964317 34.937717, -91.964332 34.938685, -91.964337 34.939069, -91.964355 34.940163, -91.964364 34.941111, -91.964368 34.941589, -91.964378 34.942578, -91.964375 34.942736, -91.964368 34.943271, -91.964366 34.94345, -91.964363 34.94371, -91.964355 34.944309, -91.964321 34.946886, -91.96431 34.947746, -91.964302 34.948673, -91.964294 34.949673, -91.964309 34.951454, -91.964317 34.952381, -91.965319 34.952409, -91.966782 34.95245, -91.968331 34.952457, -91.969335 34.952463, -91.969665 34.952464, -91.969749 34.952465, -91.970655 34.952473, -91.970985 34.952477, -91.971598 34.95248, -91.972076 34.952484, -91.973438 34.952505, -91.973778 34.952511, -91.974052 34.952507, -91.974076 34.952507, -91.974149 34.952507, -91.974174 34.952507, -91.974619 34.952513, -91.975955 34.952534, -91.976372 34.952541, -91.976401 34.952541, -91.97706 34.952538, -91.978409 34.952534, -91.979038 34.952554, -91.979698 34.952576, -91.980137 34.952581, -91.981455 34.952596, -91.981895 34.952602, -91.982455 34.952605, -91.984135 34.952617, -91.984695 34.952621, -91.984207 34.953904, -91.984153 34.954047, -91.98407 34.954266, -91.983954 34.954608, -91.983612 34.955616, -91.983414 34.956284, -91.983407 34.956552, -91.983398 34.95697, -91.983376 34.958503, -91.983376 34.958515, -91.983471 34.959964, -91.983475 34.960026, -91.983485 34.960377, -91.98349 34.960552, -91.983498 34.961174, -91.983459 34.961403, -91.983445 34.961426, -91.983276 34.961735, -91.982735 34.962391, -91.981888 34.963421, -91.98175 34.963699, -91.981758 34.964607, -91.981766 34.965458, -91.981761 34.96582, -91.981747 34.966907, -91.981743 34.96727, -91.982188 34.967276, -91.983524 34.967294, -91.98397 34.9673, -91.984299 34.967304, -91.985096 34.967305, -91.988474 34.96731, -91.989601 34.967312, -91.989775 34.967312, -91.990297 34.967315, -91.990471 34.967316, -91.990611 34.967316, -91.991032 34.967316, -91.991173 34.967316, -91.991499 34.967323, -91.992462 34.967346, -91.99248 34.967344, -91.992806 34.967318, -91.992856 34.967313, -91.993006 34.9673, -91.993057 34.967297, -91.993484 34.967305, -91.994765 34.96733, -91.995193 34.967339, -91.996071 34.967343, -91.998708 34.967357, -91.999428 34.967361, -91.999445 34.967361, -91.999588 34.967361, -91.999852 34.967361, -92.000488 34.967361, -92.000644 34.967362, -92.000954 34.967365, -92.001117 34.967365, -92.001296 34.967365, -92.001744 34.967365, -92.001953 34.967365, -92.002599 34.967382, -92.003678 34.967412, -92.00454 34.967435, -92.005188 34.967453, -92.005369 34.967456, -92.005914 34.967465, -92.006096 34.967468, -92.00608 34.968093, -92.006034 34.96997, -92.00602 34.970596, -92.006058 34.971134, -92.006047 34.971426, -92.005957 34.973922, -92.005928 34.974754, -92.003708 34.974686, -92.001672 34.974695, -92.001346 34.974682, -92.00073 34.974659, -92.000371 34.974658, -92.000046 34.974659, -91.998932 34.974708, -91.997135 34.973038, -91.994656 34.973021, -91.994568 34.973276, -91.994462 34.974111, -91.994415 34.97426, -91.994421 34.974305, -91.994455 34.974534, -91.994442 34.974787, -91.994477 34.974909, -91.994509 34.975018, -91.994504 34.975059, -91.994291 34.975477, -91.993878 34.97636, -91.993847 34.97674, -91.993829 34.976816, -91.993869 34.977014, -91.993888 34.977262, -91.993899 34.977422, -91.993903 34.977464, -91.993977 34.977679, -91.99397 34.977767, -91.993928 34.977883, -91.99391 34.977937, -91.993903 34.978041, -91.993944 34.97819, -91.993971 34.978553, -91.994026 34.978653, -91.994071 34.978734, -91.994085 34.979487, -91.993938 34.979833, -91.993938 34.979927, -91.993998 34.980064, -91.993998 34.980125, -91.993958 34.980169, -91.993685 34.980283, -91.993667 34.980291, -91.993504 34.980361, -91.993484 34.980383, -91.993438 34.980499, -91.993398 34.980729, -91.993359 34.980785, -91.993344 34.980806, -91.993338 34.980927, -91.993291 34.981011, -91.993191 34.981197, -91.993138 34.981691, -91.993139 34.981714, -91.993152 34.981949, -91.993158 34.982082, -91.993044 34.982057, -91.992923 34.982025, -91.992645 34.981968, -91.99245 34.981941, -91.992391 34.981934, -91.992283 34.981236, -91.992252 34.981035, -91.992064 34.979973, -91.992205 34.979236, -91.992207 34.979148, -91.992226 34.978443, -91.991821 34.978436, -91.991625 34.978416, -91.991585 34.978413, -91.991455 34.978233, -91.991417 34.977829, -91.991318 34.977123, -91.991102 34.97676, -91.99058 34.976463, -91.990547 34.977557, -91.990449 34.980839, -91.990421 34.981788, -91.988977 34.981781, -91.98899 34.980738, -91.990299 34.980746, -91.9903 34.980686, -91.9904 34.976446, -91.990109 34.976444, -91.989906 34.976443, -91.98785 34.976432, -91.987557 34.980644, -91.988258 34.980651, -91.988218 34.981855, -91.988507 34.981781, -91.988772 34.981781, -91.988771 34.98194, -91.988856 34.981938, -91.988824 34.983725, -91.987678 34.983712, -91.985478 34.983711, -91.981502 34.98371, -91.981383 34.98371, -91.981381 34.983901, -91.98138 34.984024, -91.978259 34.984005, -91.978257 34.984482, -91.977936 34.984482, -91.977079 34.984482, -91.97708 34.984681, -91.977086 34.985447, -91.97709 34.986011, -91.97552 34.986136, -91.975224 34.98616, -91.975224 34.985421, -91.975225 34.985343, -91.974927 34.985343, -91.971585 34.985343, -91.971531 34.985344, -91.971529 34.984355, -91.971463 34.98433, -91.9702 34.984342, -91.969588 34.984348, -91.969623 34.984442, -91.968899 34.984433, -91.96816 34.984421, -91.968154 34.985115, -91.968048 34.985092, -91.967926 34.985035, -91.967896 34.984905, -91.967843 34.984831, -91.967781 34.984745, -91.967613 34.984596, -91.96743 34.98457, -91.967458 34.984012, -91.967468 34.983829, -91.967361 34.983692, -91.967367 34.983596, -91.967374 34.983511, -91.967374 34.983488, -91.967388 34.982875, -91.967395 34.982687, -91.966129 34.983494, -91.966108 34.983507, -91.9657 34.983772, -91.965646 34.983774, -91.965592 34.983776, -91.965484 34.983781, -91.965431 34.983784, -91.965279 34.983791, -91.965092 34.983792, -91.964075 34.9838, -91.963737 34.983803, -91.963709 34.983852, -91.963688 34.983894, -91.963684 34.985077, -91.962402 34.985874, -91.961714 34.986278, -91.961517 34.986393, -91.961295 34.986484, -91.961177 34.986528, -91.961127 34.986548, -91.960905 34.986619, -91.96113 34.984979, -91.961287 34.983838, -91.961248 34.98352, -91.961225 34.983399, -91.961193 34.983203, -91.961144 34.982579, -91.961131 34.982482, -91.961121 34.982399, -91.961044 34.981934, -91.96096 34.981777, -91.960728 34.981676, -91.959588 34.981592, -91.959171 34.981568, -91.958717 34.981517, -91.958515 34.981481, -91.95751 34.981317, -91.957399 34.981274, -91.957399 34.982891, -91.957407 34.986756, -91.95701 34.986748, -91.956288 34.98674, -91.956012 34.986744, -91.955185 34.986758, -91.954943 34.986762, -91.954939 34.98717, -91.954925 34.988373, -91.954925 34.988392, -91.954921 34.9888, -91.954922 34.988924, -91.954925 34.989297, -91.954926 34.989422, -91.95491 34.989915, -91.954864 34.991396, -91.954849 34.99189, -91.954838 34.992352, -91.954807 34.993738, -91.954797 34.994201, -91.954793 34.994436, -91.954783 34.995141, -91.954781 34.995377, -91.954765 34.99614, -91.954765 34.996153, -91.954765 34.996287, -91.954765 34.996422, -91.954217 34.996406, -91.952575 34.996358, -91.952028 34.996343, -91.951982 34.9964, -91.951922 34.996444, -91.951862 34.996455, -91.951794 34.996448, -91.951748 34.996444, -91.951611 34.996409, -91.951474 34.996373, -91.951361 34.9964, -91.95128 34.996466, -91.951214 34.996488, -91.951073 34.996455, -91.951015 34.996424, -91.950771 34.996294, -91.95042 34.996322, -91.949369 34.996407, -91.949019 34.996436, -91.948112 34.996435, -91.947937 34.997236, -91.946833 35.0023, -91.94655 35.003598, -91.946854 35.003598, -91.952895 35.003629, -91.952904 35.002723, -91.953531 35.002734, -91.954689 35.002754, -91.954692 35.002992, -91.954701 35.003705, -91.954704 35.003944, -91.954694 35.004845, -91.954701 35.005276, -91.954674 35.006545, -91.954623 35.007898, -91.954631 35.009241, -91.953503 35.009238, -91.95347 35.009699, -91.952284 35.009688, -91.950908 35.009683, -91.950813 35.009711, -91.950756 35.009738, -91.950721 35.009797, -91.950714 35.009926, -91.950687 35.010541, -91.950768 35.010544, -91.952477 35.010548, -91.953103 35.010549, -91.953629 35.01055, -91.953668 35.01055, -91.954715 35.010555, -91.954874 35.010557, -91.954817 35.014624, -91.954785 35.0169, -91.950451 35.0169, -91.950432 35.017361, -91.950431 35.017406, -91.950422 35.017816, -91.945999 35.017822, -91.945936 35.017822, -91.945919 35.020299, -91.945909 35.021101, -91.945877 35.023308, -91.945872 35.023669, -91.945869 35.023903, -91.945859 35.024224, -91.945852 35.024453, -91.945831 35.025204, -91.945171 35.0252, -91.943194 35.025191, -91.942535 35.025188, -91.942162 35.025178, -91.941046 35.025148, -91.940674 35.025139, -91.940361 35.025141, -91.939422 35.025151, -91.93911 35.025154, -91.938674 35.025159, -91.937708 35.025171, -91.937369 35.025175, -91.937099 35.025179, -91.937007 35.02518, -91.936935 35.025181, -91.936911 35.025181, -91.936771 35.025182, -91.936104 35.025185, -91.933615 35.025197, -91.932785 35.025202, -91.932473 35.025204, -91.932425 35.028771, -91.932762 35.028772, -91.936848 35.028766, -91.936846 35.028884, -91.936841 35.029147, -91.936837 35.02938, -91.936836 35.029423, -91.936836 35.029941, -91.936836 35.03001, -91.936829 35.030869, -91.93683 35.030987, -91.936831 35.031276, -91.936834 35.032146, -91.936836 35.032436, -91.936826 35.032811, -91.936798 35.033937, -91.93679 35.034313, -91.937242 35.034302, -91.937863 35.034287, -91.937872 35.03364, -91.937963 35.033638, -91.938416 35.033634, -91.938433 35.033387, -91.938434 35.033372, -91.938446 35.033203, -91.938472 35.032645, -91.938484 35.032398, -91.939995 35.032443, -91.943184 35.032539, -91.943924 35.032127, -91.944365 35.031894, -91.94449 35.031829, -91.945702 35.031189, -91.94577 35.031303, -91.945763 35.03154, -91.945744 35.031777, -91.945734 35.032038, -91.945733 35.032131, -91.945729 35.03245, -91.945729 35.032524, -91.945731 35.03391, -91.945732 35.034504, -91.945686 35.035473, -91.945685 35.035682, -91.945683 35.036045, -91.945683 35.036069, -91.94721 35.036063, -91.954645 35.036035, -91.954628 35.034926, -91.954636 35.033697, -91.954638 35.032622, -91.955315 35.032873, -91.957222 35.033575, -91.957434 35.033653, -91.957867 35.033785, -91.958024 35.033837, -91.958269 35.03392, -91.958176 35.034297, -91.958037 35.034678, -91.957874 35.035212, -91.957654 35.036023, -91.958199 35.03602, -91.958523 35.036019, -91.958404 35.036181, -91.956941 35.03808, -91.95622 35.039081, -91.956345 35.038988, -91.957283 35.038289, -91.957631 35.038032, -91.95765 35.03792, -91.957724 35.037808, -91.959808 35.03635, -91.960566 35.035845, -91.960788 35.035685, -91.961374 35.035259, -91.961523 35.035004, -91.961282 35.034918, -91.96113 35.034781, -91.96108 35.034685, -91.961055 35.034603, -91.961048 35.034518, -91.961055 35.034429, -91.961361 35.03344, -91.961432 35.033165, -91.961447 35.032931, -91.961467 35.032768, -91.961489 35.032646, -91.961578 35.032383, -91.96166 35.032205, -91.961874 35.03177, -91.961923 35.031663, -91.962076 35.03134, -91.962127 35.031234, -91.962332 35.030963, -91.964218 35.028473, -91.966294 35.025731, -91.968263 35.023093, -91.968335 35.023207, -91.968409 35.023333, -91.969815 35.023316, -91.973004 35.023278, -91.974034 35.023265, -91.975311 35.02325, -91.975302 35.02338, -91.975299 35.023855, -91.975293 35.025283, -91.975291 35.025759, -91.976206 35.025116, -91.977033 35.024537, -91.978946 35.023177, -91.979858 35.022529, -91.980151 35.022322, -91.981033 35.021703, -91.981327 35.021497, -91.981745 35.021201, -91.982268 35.020829, -91.982809 35.020444, -91.983407 35.019991, -91.983857 35.019649, -91.982038 35.01962, -91.982066 35.018858, -91.982077 35.01835, -91.982094 35.0182, -91.982182 35.018145, -91.98227 35.018123, -91.982431 35.018123, -91.985691 35.018184, -91.98635 35.017703, -91.986908 35.01709, -91.987 35.017036, -91.98735 35.016865, -91.987815 35.016817, -91.987877 35.016811, -91.989548 35.017136, -91.989922 35.017181, -91.990158 35.017162, -91.990295 35.017033, -91.990349 35.016711, -91.990395 35.016438, -91.990471 35.015755, -91.990554 35.015851, -91.990738 35.016064, -91.990798 35.016144, -91.990875 35.016246, -91.990898 35.016277, -91.990999 35.016395, -91.991119 35.016533, -91.991572 35.016741, -91.991875 35.01688, -91.992676 35.017105, -91.993149 35.017223, -91.994081 35.017194, -91.994122 35.017193, -91.994873 35.017643, -91.994944 35.017691, -91.99516 35.017835, -91.995232 35.017883, -91.99565 35.017929, -91.996011 35.017935, -91.99753 35.017961, -91.998269 35.017947, -91.998395 35.017963, -91.998724 35.018006, -91.998946 35.018104, -91.9991 35.018295, -91.999072 35.018572, -91.998989 35.019402, -91.998955 35.019717, -91.999233 35.019687, -92.000047 35.019709, -92.000319 35.019717, -92.001273 35.019778, -92.001974 35.019837, -92.004442 35.019889, -92.004848 35.019882, -92.005264 35.019867, -92.005399 35.019498, -92.005807 35.018391, -92.005814 35.018375, -92.005928 35.018015, -92.005974 35.016937, -92.00602 35.015869, -92.006045 35.013695, -92.006058 35.012615, -92.006016 35.012617, -92.005893 35.012622, -92.005852 35.012625, -92.005865 35.012246, -92.005906 35.011112, -92.00592 35.010734, -92.004798 35.010734, -92.003815 35.010735, -92.001434 35.010736, -92.000313 35.010738, -91.999011 35.010733, -91.998973 35.010733, -91.998728 35.010732, -91.997876 35.010729, -91.997578 35.010728, -91.995106 35.01072, -91.994537 35.010719, -91.994385 35.010718, -91.993805 35.010713, -91.99015 35.010689, -91.98999 35.010433, -91.989822 35.010212, -91.989758 35.010116, -91.989594 35.009869, -91.989304 35.00948, -91.989166 35.00911, -91.989029 35.008743, -91.98898 35.008616, -91.988866 35.008323, -91.98867 35.007801, -91.988196 35.006532, -91.988107 35.006287, -91.989781 35.005332, -91.990022 35.005187, -91.989581 35.005148, -91.989453 35.005137, -91.989011 35.004979, -91.988735 35.004872, -91.98846 35.004704, -91.988372 35.004637, -91.98819 35.004501, -91.988022 35.004368, -91.988332 35.004333, -91.988784 35.004333, -91.989082 35.004362, -91.98934 35.004392, -91.989542 35.004418, -91.989791 35.004447, -91.990125 35.004468, -91.990324 35.00445, -91.990549 35.004429, -91.990727 35.004383, -91.991011 35.004329, -91.99137 35.004213, -91.992928 35.003496, -91.994103 35.002802, -91.99569 35.001887, -91.996836 35.001193, -91.997519 35.000745, -91.998145 35.000335, -91.998636 35.000001, -91.999085 34.99967, -92.000152 34.998915, -92.00015 34.998528, -92.000145 34.997369, -92.000144 34.996983, -91.999876 34.996908, -91.999075 34.996686, -91.998901 34.996638, -91.998808 34.996612, -91.998915 34.996493, -91.998932 34.996475, -91.998925 34.99642, -91.998898 34.996398, -91.998611 34.996376, -91.998536 34.996357, -91.998491 34.99631, -91.998464 34.996255, -91.998471 34.996223, -91.998484 34.996167, -91.998598 34.996057, -91.998671 34.996035, -91.998738 34.99602, -91.998771 34.996013, -91.998925 34.996007, -91.999132 34.996024, -91.999693 34.996089, -91.999811 34.996126, -92.000061 34.996205, -92.000151 34.996248, -92.000292 34.996264, -92.000365 34.996269, -92.000525 34.996258, -92.000646 34.996269, -92.000736 34.996293, -92.000766 34.996302, -92.000886 34.996346, -92.001086 34.996423, -92.001213 34.996462, -92.001334 34.996484, -92.001627 34.996473, -92.001788 34.996473, -92.001935 34.996495, -92.002162 34.996567, -92.002375 34.996627, -92.002452 34.996656, -92.002489 34.996671, -92.002507 34.996681, -92.002596 34.996732, -92.002703 34.996776, -92.002816 34.996804, -92.002976 34.996864, -92.003 34.996872, -92.00313 34.996914, -92.003305 34.996978, -92.003696 34.996696, -92.012628 34.99026, -92.018861 34.985784, -92.018909 34.985815, -92.019005 34.985896, -92.019104 34.985979, -92.019027 34.98603, -92.018799 34.986185, -92.018723 34.986237, -92.018556 34.986363, -92.018057 34.986743, -92.017891 34.98687, -92.017724 34.986985, -92.017225 34.987334, -92.017059 34.98745, -92.016789 34.987649, -92.015979 34.988246, -92.015709 34.988445, -92.015264 34.988756, -92.013933 34.989692, -92.013489 34.990005, -92.013047 34.990329, -92.012987 34.990374, -92.011724 34.991302, -92.011284 34.991627, -92.010439 34.992219, -92.008461 34.99361, -92.008163 34.993919, -92.008102 34.994041, -92.00807 34.994147, -92.008064 34.994168, -92.008049 34.994217, -92.007988 34.994453, -92.007988 34.994656, -92.007957 34.995167, -92.007964 34.995475, -92.007973 34.995819, -92.007962 34.996402, -92.007957 34.996712, -92.009712 34.996754, -92.010114 34.996757, -92.012497 34.99678, -92.012543 34.996857, -92.012535 34.997688, -92.012444 34.998585, -92.012344 34.998924, -92.012276 34.999123, -92.012184 34.999634, -92.012146 34.999966, -92.012198 35.000087, -92.012354 35.000087, -92.017111 35.000087, -92.017587 35.000087, -92.019557 35.000107, -92.020277 35.000124, -92.020277 34.998263, -92.020277 34.997403, -92.021717 34.997434, -92.026037 34.997527, -92.027396 34.997556, -92.027406 34.997405, -92.027444 34.997273, -92.027471 34.99718, -92.027475 34.997105, -92.027489 34.996872, -92.027545 34.996485, -92.02758 34.996254, -92.027581 34.995886, -92.027586 34.994786, -92.027588 34.994419, -92.027588 34.993327, -92.027588 34.992741, -92.027626 34.990311, -92.027636 34.990054, -92.027657 34.989578, -92.027779 34.988976, -92.02784 34.988541, -92.027908 34.988636, -92.028 34.988739, -92.028122 34.988865, -92.028259 34.988956, -92.028496 34.989105, -92.028656 34.989197, -92.029182 34.989475, -92.029373 34.989594, -92.029594 34.989742, -92.029869 34.989983, -92.03006 34.990192, -92.030289 34.990616, -92.030307 34.990655, -92.030403 34.99086, -92.030556 34.991093, -92.030731 34.991295, -92.031029 34.99152, -92.031388 34.991761, -92.031403 34.991772, -92.031815 34.992069, -92.031914 34.992171, -92.033344 34.993733, -92.033702 34.994209, -92.033846 34.994439, -92.033936 34.994723, -92.034057 34.995393, -92.034255 34.995869, -92.034442 34.996173, -92.034937 34.996726, -92.035319 34.997069, -92.035738 34.997412, -92.036103 34.997646, -92.036365 34.997813, -92.036495 34.997896, -92.036769 34.998113, -92.03708 34.998421, -92.037197 34.998606, -92.037727 34.99935, -92.038022 34.999594, -92.038332 34.999704, -92.038757 34.99979, -92.039294 34.999869, -92.040636 35.000014, -92.041374 35.00009, -92.041754 35.000148, -92.042023 35.000213, -92.042212 35.000249, -92.042661 35.000431, -92.043215 35.000638, -92.043781 35.000805, -92.044042 35.000906, -92.044218 35.000954, -92.04463 35.001079, -92.0447 34.999293, -92.044734 34.998427, -92.044752 34.997952, -92.045556 34.997893, -92.045885 34.997887, -92.046873 34.997869, -92.047203 34.997864, -92.047913 34.997899, -92.048431 34.997925, -92.050046 34.997935, -92.050758 34.99794, -92.050936 34.997946, -92.051472 34.997964, -92.051651 34.997971, -92.05202 34.998001, -92.052505 34.998042, -92.05285 34.998071, -92.055069 34.998255, -92.055924 34.998327, -92.055893 34.998554, -92.055886 34.998596, -92.055765 34.999403, -92.055725 34.999672, -92.055603 34.999905, -92.055546 35.000194, -92.055533 35.000255, -92.055496 35.000443, -92.055179 35.001675, -92.055145 35.001808, -92.05526 35.001949, -92.055359 35.00214, -92.055428 35.002346, -92.05546 35.002552, -92.055473 35.002636, -92.055462 35.002704, -92.055443 35.002827, -92.055705 35.003195, -92.05567 35.003512, -92.05567 35.003588, -92.055283 35.004448, -92.054932 35.004681, -92.054749 35.005116, -92.054657 35.005287, -92.054644 35.005337, -92.054608 35.005471, -92.054588 35.00555, -92.054543 35.005699, -92.054551 35.005877, -92.054558 35.006016, -92.054484 35.006464, -92.054451 35.006668, -92.054382 35.007042, -92.054716 35.007744, -92.055023 35.00808, -92.055664 35.008316, -92.056316 35.008496, -92.057068 35.008705, -92.05941 35.009274, -92.060112 35.009354, -92.060295 35.009372, -92.061648 35.009513, -92.061729 35.009521, -92.062347 35.009579, -92.062806 35.009592, -92.062834 35.009593, -92.062859 35.009594, -92.063278 35.009579, -92.063761 35.009461, -92.064812 35.009209, -92.065292 35.009064, -92.065475 35.009038, -92.065849 35.008984, -92.066327 35.009045, -92.066765 35.009102, -92.067383 35.009399, -92.067429 35.009441, -92.06826 35.010376, -92.068355 35.010483, -92.068492 35.010473, -92.068566 35.010484, -92.068726 35.010555, -92.068987 35.010825, -92.069328 35.011061, -92.069555 35.01122, -92.069762 35.011308, -92.069996 35.01155, -92.070143 35.011655, -92.07021 35.011677, -92.070483 35.011737, -92.070716 35.011729, -92.070804 35.011726, -92.070804 35.011703, -92.070806 35.011561, -92.070828 35.009747, -92.070817 35.008694, -92.070796 35.007971, -92.070757 35.007551, -92.070742 35.007321, -92.070685 35.005946, -92.070671 35.005501, -92.070678 35.005142, -92.0707 35.004836, -92.070732 35.00437, -92.070776 35.003925, -92.070803 35.003356, -92.070813 35.003259, -92.070833 35.002868, -92.070897 35.002252, -92.070981 34.99995, -92.071038 34.998761, -92.07107 34.998122, -92.071141 34.997051, -92.071147 34.996889, -92.071152 34.99654, -92.071176 34.996057, -92.071187 34.995615, -92.071205 34.995136, -92.071263 34.993812, -92.071288 34.99319, -92.071343 34.991902, -92.071383 34.990824, -92.071391 34.990613, -92.071413 34.990197, -92.07144 34.989371, -92.071452 34.989119, -92.071459 34.988972, -92.071461 34.988937, -92.071476 34.988461, -92.071486 34.988201, -92.071568 34.987407, -92.071643 34.986895, -92.071728 34.98634, -92.071746 34.986119, -92.071778 34.985503, -92.071796 34.984124, -92.071824 34.983459, -92.071849 34.9829, -92.071674 34.982874, -92.07155 34.982857, -92.07129 34.982815, -92.071052 34.982754, -92.070774 34.982683, -92.070328 34.982536, -92.070276 34.982519, -92.070152 34.982478, -92.07013 34.982471, -92.069977 34.982419, -92.069868 34.982383, -92.069794 34.982358, -92.069763 34.982348, -92.069732 34.982338, -92.069716 34.982332, -92.069656 34.982312, -92.069607 34.982296, -92.069589 34.98229, -92.069434 34.982244, -92.0691 34.982146, -92.068959 34.982104, -92.068496 34.981978, -92.068014 34.981847, -92.067672 34.981769, -92.067553 34.98175, -92.067286 34.981707, -92.067223 34.981703, -92.067034 34.981692, -92.066971 34.981689, -92.065758 34.981617, -92.065298 34.981585, -92.064543 34.981542, -92.063896 34.981518, -92.063941 34.983459, -92.063944 34.983564, -92.062733 34.98367, -92.062605 34.983682, -92.062512 34.987054, -92.061559 34.987044, -92.058295 34.98702, -92.056105 34.98699, -92.055761 34.986985, -92.05586 34.98414, -92.055767 34.984168, -92.05548 34.984254, -92.055454 34.984262, -92.055372 34.984286, -92.054984 34.984386, -92.054625 34.984467, -92.054262 34.984531, -92.054031 34.98456, -92.053813 34.984588, -92.053368 34.984667, -92.052802 34.984734, -92.052265 34.984805, -92.051742 34.984887, -92.05145 34.984909, -92.051091 34.984919, -92.050746 34.984905, -92.050422 34.984866, -92.050198 34.98482, -92.04992 34.984727, -92.049639 34.984627, -92.049621 34.984621, -92.049521 34.984585, -92.048884 34.984357, -92.048122 34.98404, -92.047654 34.983847, -92.04742 34.983749, -92.047132 34.983649, -92.046779 34.983558, -92.046383 34.98349, -92.045944 34.983414, -92.045322 34.983335, -92.045158 34.983315, -92.044503 34.983232, -92.044495 34.98322, -92.044501 34.98317, -92.044728 34.981793, -92.044932 34.980549, -92.044966 34.980343, -92.045461 34.977339, -92.043457 34.977368, -92.040863 34.977406, -92.040844 34.974649, -92.039814 34.974613, -92.038792 34.974599, -92.038759 34.974599, -92.038406 34.974596, -92.037844 34.974603, -92.037854 34.972167, -92.037861 34.970549, -92.037862 34.97042, -92.037862 34.970292, -92.037864 34.969818, -92.03787 34.968474, -92.037871 34.967837, -92.037876 34.96593, -92.037878 34.965294, -92.03773 34.965282, -92.037287 34.965248, -92.037188 34.965229, -92.03714 34.965237, -92.037118 34.965236, -92.037054 34.965233, -92.037033 34.965233, -92.036959 34.965224, -92.03674 34.965199, -92.036667 34.965191, -92.036456 34.965181, -92.035824 34.965151, -92.035614 34.965141, -92.035398 34.965128, -92.034753 34.965092, -92.034538 34.96508, -92.034088 34.96505, -92.033783 34.965034, -92.033719 34.965052, -92.0336 34.965088, -92.03347 34.965233, -92.033455 34.965412, -92.033394 34.966877, -92.03337 34.967379, -92.033333 34.968201, -92.033206 34.968193, -92.032826 34.96817, -92.0327 34.968163, -92.0323 34.968134, -92.0311 34.968047, -92.030701 34.968018, -92.030655 34.968014, -92.030517 34.968004, -92.030472 34.968001, -92.030201 34.967981, -92.029877 34.967957, -92.029391 34.96793, -92.029121 34.967915, -92.029008 34.967903, -92.028669 34.967868, -92.028557 34.967857, -92.028565 34.967569, -92.028589 34.966708, -92.028595 34.966534, -92.028597 34.966421, -92.028598 34.966349, -92.028603 34.966135, -92.028605 34.966064, -92.02861 34.965794, -92.02861 34.965777, -92.028644 34.964919, -92.028656 34.964634, -92.031036 34.963341, -92.034286 34.961658, -92.036098 34.96072, -92.0385 34.959445, -92.041065 34.9581, -92.041878 34.957668, -92.043329 34.956903, -92.044258 34.956413, -92.044698 34.956186, -92.047546 34.954671, -92.047583 34.954698, -92.047838 34.954802, -92.048011 34.954907, -92.04811 34.955, -92.048198 34.955083, -92.048285 34.955242, -92.048438 34.955731, -92.048485 34.956006, -92.048505 34.956028, -92.048578 34.956336, -92.048545 34.956501, -92.048305 34.956985, -92.048498 34.95716, -92.048532 34.95727, -92.048532 34.957364, -92.048572 34.957413, -92.048605 34.95743, -92.048792 34.957446, -92.048899 34.95754, -92.048979 34.957809, -92.048979 34.957892, -92.049032 34.958441, -92.049092 34.95876, -92.049185 34.959073, -92.049185 34.959293, -92.049352 34.959788, -92.049359 34.959876, -92.049339 34.959991, -92.049252 34.960233, -92.049058 34.960585, -92.049058 34.960673, -92.049152 34.96092, -92.049439 34.961146, -92.049692 34.961563, -92.049839 34.961937, -92.049866 34.961954, -92.049926 34.962064, -92.049959 34.96208, -92.04997 34.962098, -92.05002 34.962176, -92.05011 34.962314, -92.051812 34.960469, -92.053894 34.95821, -92.055944 34.955996, -92.056883 34.954975, -92.057349 34.954491, -92.057808 34.954028, -92.058595 34.953277, -92.058872 34.953028, -92.059203 34.952726, -92.05963 34.952363, -92.060122 34.95197, -92.060417 34.951724, -92.06094 34.951281, -92.062179 34.950278, -92.064239 34.948726, -92.067598 34.946094, -92.067888 34.94586, -92.068022 34.945752, -92.06901 34.944965, -92.072121 34.942546, -92.072866 34.941979, -92.073174 34.941737, -92.073198 34.941449, -92.073489 34.941237, -92.073978 34.940876, -92.074515 34.940453, -92.07489 34.940163, -92.075113 34.939981, -92.075416 34.939717, -92.075745 34.939406, -92.075979 34.939164, -92.076285 34.938809, -92.076492 34.93855, -92.076665 34.938312, -92.076827 34.938067, -92.07717 34.93749, -92.077601 34.936787, -92.078224 34.935757, -92.07903 34.934404, -92.079242 34.934026, -92.07944 34.933643, -92.079624 34.933249, -92.079855 34.932714, -92.079966 34.932439, -92.080068 34.932161, -92.080251 34.931594, -92.080338 34.9313, -92.080449 34.930861, -92.080628 34.930004, -92.080666 34.929802, -92.080755 34.929217, -92.0808 34.928862, -92.080986 34.927535, -92.081059 34.926927, -92.081163 34.926193, -92.081311 34.925075, -92.081405 34.924442, -92.081414 34.924327, -92.081715 34.9221, -92.081844 34.921205, -92.082043 34.919734, -92.08225 34.91828, -92.082458 34.918276, -92.082617 34.917122, -92.083319 34.912146, -92.083538 34.910641, -92.083588 34.910417, -92.083731 34.909857, -92.083867 34.909383, -92.083998 34.908977, -92.084119 34.908636, -92.08432 34.90813, -92.084591 34.907522, -92.084801 34.907095, -92.085018 34.906691, -92.085251 34.906288, -92.085557 34.905795, -92.08585 34.905361, -92.086331 34.904699, -92.086451 34.904536, -92.08673 34.904563, -92.097222 34.909652, -92.097761 34.908962, -92.098006 34.908601, -92.098177 34.908336, -92.098326 34.908082, -92.098502 34.907755, -92.099018 34.906595, -92.100015 34.904329, -92.101394 34.90118, -92.101842 34.900161, -92.101937 34.899923, -92.10194 34.900112, -92.101963 34.900111, -92.102832 34.900112, -92.102832 34.900009, -92.102816 34.89853, -92.10335 34.898517, -92.103517 34.896453, -92.103533 34.896152, -92.103761 34.892428, -92.103798 34.891558, -92.103542 34.891466, -92.104654 34.889799, -92.104959 34.88956, -92.105422 34.889175, -92.105687 34.888971, -92.10628 34.888532, -92.106406 34.888458, -92.106727 34.888241, -92.107113 34.887963, -92.107228 34.887869, -92.107375 34.887748, -92.107472 34.887652, -92.107654 34.887426, -92.107675 34.887387, -92.10774 34.887267, -92.107812 34.887132, -92.108278 34.886263)), ((-92.266835 34.818642, -92.267049 34.818532, -92.267179 34.818475, -92.267403 34.818391, -92.267598 34.818329, -92.267809 34.818275, -92.267874 34.818257, -92.268034 34.818221, -92.268499 34.818138, -92.268786 34.818098, -92.269054 34.818076, -92.269229 34.818071, -92.269391 34.818075, -92.269577 34.818093, -92.269676 34.818108, -92.269807 34.818129, -92.270061 34.818185, -92.270176 34.818217, -92.270411 34.818291, -92.270671 34.818391, -92.270879 34.818485, -92.271218 34.818657, -92.271563 34.81884, -92.274939 34.820673, -92.276173 34.821334, -92.276462 34.821474, -92.276673 34.82157, -92.276901 34.82166, -92.277137 34.821737, -92.277392 34.821809, -92.277362 34.822044, -92.277343 34.82224, -92.277357 34.82241, -92.277313 34.822549, -92.277274 34.822948, -92.277265 34.823604, -92.277266 34.823882, -92.277261 34.824212, -92.277253 34.824283, -92.277237 34.824319, -92.277194 34.824366, -92.277176 34.824379, -92.277091 34.8244, -92.277004 34.824409, -92.276456 34.824363, -92.2764 34.824477, -92.276305 34.824649, -92.276037 34.825039, -92.275848 34.825296, -92.275681 34.825545, -92.275497 34.825836, -92.275293 34.826147, -92.275079 34.826497, -92.274971 34.826643, -92.274758 34.826954, -92.274742 34.826973, -92.2751 34.827165, -92.27536 34.827293, -92.275825 34.827553, -92.276139 34.827748, -92.276552 34.827943, -92.276949 34.828121, -92.277296 34.828252, -92.277692 34.828398, -92.278188 34.828577, -92.278584 34.828724, -92.278981 34.82895, -92.279246 34.829114, -92.279464 34.829308, -92.279387 34.829356, -92.279232 34.829406, -92.279015 34.829487, -92.278801 34.829697, -92.278618 34.82989, -92.278154 34.83015, -92.277921 34.830199, -92.277878 34.830345, -92.277965 34.830522, -92.278146 34.830722, -92.278294 34.830842, -92.27903 34.830314, -92.27935 34.830106, -92.279574 34.829994, -92.279878 34.829786, -92.280374 34.829514, -92.280486 34.82945, -92.280582 34.829482, -92.280614 34.829642, -92.280758 34.82969, -92.280902 34.829706, -92.281014 34.829722, -92.281206 34.829802, -92.281462 34.829882, -92.28167 34.829962, -92.281862 34.829978, -92.282054 34.829946, -92.282198 34.83001, -92.28231 34.830122, -92.28232 34.830156, -92.282619 34.830103, -92.282686 34.830096, -92.28299 34.830042, -92.283234 34.830028, -92.283309 34.830035, -92.283383 34.830051, -92.28354 34.830097, -92.283629 34.830105, -92.28369 34.830102, -92.283733 34.830238, -92.283774 34.830325, -92.283879 34.83049, -92.283942 34.830616, -92.28399 34.830747, -92.284022 34.830882, -92.284019 34.831008, -92.284034 34.831134, -92.284054 34.831216, -92.284087 34.831277, -92.284233 34.831272, -92.284377 34.831255, -92.284518 34.831224, -92.284639 34.831189, -92.284685 34.831167, -92.284745 34.831123, -92.284793 34.83107, -92.284828 34.831001, -92.284848 34.830923, -92.284851 34.830844, -92.284836 34.830765, -92.284791 34.830663, -92.284723 34.830555, -92.284637 34.830454, -92.284537 34.830364, -92.284501 34.830337, -92.2844 34.830231, -92.284309 34.830169, -92.284243 34.830135, -92.284208 34.830121, -92.283841 34.830004, -92.283677 34.829946, -92.283685 34.829868, -92.283713 34.829752, -92.2838 34.829526, -92.284002 34.828955, -92.283779 34.828852, -92.283402 34.828704, -92.283356 34.828685, -92.283202 34.828619, -92.283022 34.828551, -92.282783 34.828444, -92.282574 34.828341, -92.282437 34.828283, -92.282187 34.828168, -92.282218 34.828116, -92.282356 34.827912, -92.282813 34.827257, -92.283152 34.826759, -92.283359 34.826834, -92.283706 34.826989, -92.2841 34.827178, -92.28434 34.827303, -92.284483 34.827378, -92.284558 34.827402, -92.28461 34.827409, -92.28469 34.827407, -92.284792 34.827382, -92.284886 34.827341, -92.28497 34.827287, -92.28515 34.827117, -92.285306 34.826955, -92.28545 34.826786, -92.2859 34.826162, -92.285956 34.826111, -92.286025 34.826072, -92.28609 34.826108, -92.286276 34.826222, -92.286622 34.826461, -92.287076 34.826805, -92.287408 34.827075, -92.287766 34.827385, -92.288045 34.827643, -92.288315 34.827906, -92.288613 34.82822, -92.288853 34.828496, -92.289081 34.828779, -92.289498 34.829341, -92.289669 34.82959, -92.289732 34.829685, -92.289827 34.829829, -92.289915 34.82995, -92.290533 34.830873, -92.291267 34.831963, -92.291887 34.832881, -92.292167 34.833264, -92.292186 34.83329, -92.292333 34.833463, -92.2925 34.833632, -92.292682 34.833795, -92.292875 34.833948, -92.293316 34.83427, -92.293053 34.834484, -92.292768 34.83474, -92.292238 34.835184, -92.291715 34.835645, -92.291316 34.836012, -92.290878 34.836386, -92.290474 34.836748, -92.290037 34.837128, -92.29035 34.837348, -92.291594 34.838258, -92.292084 34.8386, -92.29262 34.838981, -92.292982 34.839251, -92.293371 34.83953, -92.294064 34.840006, -92.294248 34.83981, -92.294817 34.839269, -92.294996 34.83909, -92.295013 34.839077, -92.295043 34.839074, -92.295224 34.839127, -92.295436 34.839213, -92.295526 34.839238, -92.295664 34.839262, -92.295711 34.839264, -92.295756 34.839252, -92.295815 34.839213, -92.295913 34.839135, -92.296062 34.838986, -92.296198 34.838828, -92.296392 34.838635, -92.296446 34.838549, -92.296488 34.838458, -92.296508 34.838395, -92.296584 34.83802, -92.296638 34.837801, -92.296699 34.83752, -92.296727 34.837463, -92.296771 34.837413, -92.297076 34.83712, -92.297175 34.83702, -92.297241 34.83695, -92.29734 34.836864, -92.297589 34.836625, -92.29784 34.836369, -92.298144 34.836071, -92.297642 34.835716, -92.297355 34.835518, -92.296661 34.83503, -92.295908 34.834491, -92.295578 34.834264, -92.295246 34.834022, -92.294917 34.833797, -92.294644 34.833602, -92.294516 34.833518, -92.294865 34.833399, -92.295312 34.833237, -92.295618 34.833117, -92.295816 34.833029, -92.296166 34.83286, -92.296392 34.832736, -92.296817 34.832471, -92.296851 34.832447, -92.29704 34.832316, -92.297235 34.832166, -92.297366 34.832051, -92.297484 34.831928, -92.297616 34.831771, -92.297693 34.831649, -92.297733 34.831563, -92.298033 34.830807, -92.298133 34.830537, -92.298244 34.830257, -92.298322 34.830086, -92.298461 34.829814, -92.298632 34.829498, -92.29883 34.829155, -92.298933 34.828953, -92.299035 34.828784, -92.299127 34.828603, -92.299264 34.828365, -92.299402 34.828088, -92.300294 34.826491, -92.30036 34.826366, -92.299037 34.825871, -92.298833 34.826188, -92.298454 34.826846, -92.2983 34.827151, -92.298077 34.827577, -92.29758 34.827386, -92.297377 34.827304, -92.296673 34.827055, -92.296154 34.826865, -92.296067 34.82683, -92.295999 34.826802, -92.29568 34.826691, -92.294712 34.826327, -92.293957 34.82604, -92.293393 34.825818, -92.292823 34.825601, -92.29253 34.825482, -92.292483 34.825466, -92.292306 34.825408, -92.291801 34.825225, -92.290814 34.824846, -92.289215 34.824237, -92.28837 34.823905, -92.288574 34.823528, -92.28875 34.823221, -92.288876 34.822968, -92.288907 34.822888, -92.288934 34.822764, -92.28894 34.82268, -92.288926 34.822562, -92.288892 34.822447, -92.288837 34.822337, -92.288755 34.822211, -92.288688 34.822124, -92.288572 34.822002, -92.288529 34.821964, -92.288404 34.821873, -92.288063 34.821724, -92.287846 34.821637, -92.287547 34.821531, -92.28655 34.821149, -92.286111 34.820989, -92.286003 34.820949, -92.285617 34.820798, -92.285424 34.82073, -92.284772 34.820479, -92.284292 34.820302, -92.284079 34.820215, -92.283383 34.819956, -92.28293 34.819795, -92.282738 34.819715, -92.282427 34.820262, -92.282251 34.820562, -92.281924 34.821152, -92.281765 34.821421, -92.281498 34.821325, -92.280778 34.821053, -92.279944 34.820743, -92.279053 34.820402, -92.278589 34.820234, -92.277752 34.819908, -92.277199 34.819697, -92.27717 34.819688, -92.277036 34.819646, -92.276857 34.81959, -92.276889 34.818333, -92.276893 34.818196, -92.27691 34.81752, -92.276952 34.815867, -92.27698 34.814797, -92.276989 34.814439, -92.276999 34.814056, -92.277012 34.813542, -92.277053 34.812576, -92.27706 34.81241, -92.277186 34.812413, -92.277272 34.812415, -92.277417 34.812419, -92.280419 34.812504, -92.281059 34.812513, -92.282614 34.812538, -92.282626 34.812538, -92.284556 34.81257, -92.285085 34.812579, -92.285511 34.812586, -92.28917 34.812646, -92.289464 34.812701, -92.294677 34.812728, -92.294672 34.812893, -92.294672 34.81291, -92.29467 34.813028, -92.294615 34.816392, -92.295155 34.81641, -92.29528 34.816322, -92.295342 34.816278, -92.295449 34.816228, -92.295695 34.816173, -92.295775 34.816113, -92.295882 34.815893, -92.295989 34.815574, -92.296095 34.815459, -92.296249 34.815338, -92.296349 34.815283, -92.296435 34.815195, -92.296509 34.81509, -92.296569 34.814893, -92.296649 34.814794, -92.296802 34.8147, -92.296915 34.814667, -92.296962 34.814623, -92.296995 34.814574, -92.297009 34.814403, -92.297142 34.8142, -92.297422 34.813975, -92.297549 34.813782, -92.297642 34.813722, -92.297715 34.813705, -92.297962 34.813722, -92.298448 34.813821, -92.298608 34.813865, -92.298915 34.813997, -92.299123 34.814035, -92.299215 34.814052, -92.29925 34.814062, -92.299293 34.814075, -92.299821 34.814233, -92.300008 34.814244, -92.300068 34.814244, -92.300215 34.814206, -92.300341 34.814112, -92.300541 34.814052, -92.300828 34.814047, -92.301134 34.814107, -92.301174 34.814135, -92.301281 34.81414, -92.301354 34.814179, -92.302147 34.814365, -92.302174 34.814388, -92.302381 34.814448, -92.302427 34.814497, -92.302554 34.81453, -92.302607 34.814574, -92.302901 34.814657, -92.302981 34.814706, -92.303014 34.814767, -92.303027 34.814904, -92.303174 34.815058, -92.30328 34.81513, -92.303574 34.815223, -92.303627 34.815273, -92.3037 34.815289, -92.303707 34.815317, -92.303794 34.815366, -92.30382 34.815416, -92.30387 34.815444, -92.303977 34.815507, -92.304152 34.815534, -92.30436 34.815575, -92.304773 34.815564, -92.304873 34.815592, -92.30516 34.815729, -92.305207 34.815729, -92.30534 34.815812, -92.305369 34.815821, -92.306111 34.816156, -92.307623 34.81684, -92.307732 34.816786, -92.307991 34.816658, -92.308104 34.816602, -92.308073 34.81667, -92.30813 34.816677, -92.308087 34.816775, -92.308031 34.816906, -92.307956 34.817079, -92.307954 34.817163, -92.307952 34.817265, -92.30795 34.817372, -92.307953 34.817388, -92.307988 34.817489, -92.307833 34.817431, -92.307721 34.81738, -92.307665 34.817354, -92.307589 34.81732, -92.307325 34.8172, -92.307139 34.817128, -92.30687 34.817032, -92.306523 34.816908, -92.306427 34.816843, -92.306397 34.81692, -92.306287 34.81704, -92.306186 34.817221, -92.305927 34.817574, -92.306624 34.817885, -92.307577 34.818325, -92.308332 34.8187, -92.308396 34.818609, -92.308809 34.818025, -92.308947 34.818091, -92.310126 34.818661, -92.312126 34.819628, -92.313291 34.818424, -92.313792 34.818745, -92.314434 34.819166, -92.314704 34.819352, -92.314919 34.819491, -92.31511 34.819624, -92.315199 34.819679, -92.315301 34.819747, -92.315541 34.819907, -92.31575 34.820055, -92.315787 34.82008, -92.315929 34.820176, -92.315823 34.820496, -92.315772 34.820653, -92.316238 34.820885, -92.316265 34.820898, -92.316297 34.820766, -92.316316 34.820728, -92.316382 34.820612, -92.316462 34.820488, -92.31651 34.820507, -92.316619 34.820531, -92.316731 34.820541, -92.316839 34.820541, -92.316962 34.820543, -92.316994 34.821046, -92.317067 34.821091, -92.317146 34.821139, -92.317254 34.821205, -92.317411 34.821301, -92.318138 34.82146, -92.318357 34.821511, -92.318555 34.821547, -92.318811 34.821576, -92.318841 34.821578, -92.319038 34.821588, -92.319064 34.821581, -92.31909 34.821574, -92.319101 34.821659, -92.319131 34.821882, -92.319206 34.822407, -92.321204 34.82334, -92.321259 34.823389, -92.321332 34.82332, -92.321431 34.82325, -92.321503 34.823211, -92.321561 34.823196, -92.321615 34.823174, -92.321686 34.823128, -92.322038 34.822765, -92.322258 34.822553, -92.322362 34.822463, -92.322947 34.822725, -92.324777 34.824062, -92.325336 34.824463, -92.326942 34.825647, -92.327158 34.825816, -92.327369 34.82599, -92.327691 34.82628, -92.327756 34.826338, -92.328036 34.826599, -92.328534 34.827135, -92.32883 34.82746, -92.329113 34.827793, -92.329287 34.828028, -92.329394 34.828189, -92.329537 34.828438, -92.331275 34.831806, -92.331688 34.832607, -92.331816 34.832837, -92.331822 34.832849, -92.331886 34.832962, -92.331993 34.833171, -92.332106 34.833392, -92.332196 34.833577, -92.332329 34.833826, -92.332427 34.834017, -92.332531 34.834218, -92.332984 34.835096, -92.333198 34.835491, -92.333364 34.835821, -92.333542 34.836152, -92.333739 34.836551, -92.333953 34.836957, -92.334581 34.838174, -92.334595 34.838201, -92.335237 34.839445, -92.335422 34.839792, -92.336087 34.841079, -92.336556 34.841968, -92.336731 34.842327, -92.336619 34.842311, -92.336451 34.842294, -92.336308 34.842285, -92.335918 34.842279, -92.33577 34.842277, -92.335444 34.842264, -92.334894 34.842264, -92.334557 34.842258, -92.334031 34.842244, -92.33331 34.842235, -92.332305 34.842209, -92.33187 34.842201, -92.331313 34.84219, -92.330827 34.842178, -92.330503 34.842178, -92.330362 34.842182, -92.330223 34.84218, -92.329709 34.842167, -92.329606 34.842162, -92.329517 34.842159, -92.329227 34.842152, -92.328695 34.842139, -92.328182 34.842134, -92.327936 34.842123, -92.327807 34.842123, -92.327666 34.842122, -92.327426 34.842116, -92.325983 34.84209, -92.32539 34.842083, -92.325311 34.842097, -92.32524 34.842128, -92.325194 34.842178, -92.325166 34.842236, -92.325157 34.842299, -92.325116 34.843819, -92.325114 34.844016, -92.325109 34.844361, -92.325105 34.844705, -92.325101 34.844878, -92.325088 34.845372, -92.325072 34.845731, -92.325057 34.846009, -92.325058 34.846191, -92.32505 34.846922, -92.325033 34.847233, -92.325024 34.847584, -92.325015 34.848022, -92.325006 34.848216, -92.325004 34.848446, -92.324974 34.849385, -92.324946 34.850065, -92.324935 34.85022, -92.324917 34.850799, -92.324899 34.851168, -92.324851 34.852138, -92.32483 34.852923, -92.324813 34.853004, -92.324781 34.853083, -92.324752 34.853132, -92.324706 34.853181, -92.32465 34.853222, -92.324473 34.853309, -92.323056 34.853935, -92.32273 34.854074, -92.322018 34.854388, -92.321271 34.854717, -92.320912 34.854882, -92.320786 34.854963, -92.320697 34.855065, -92.32065 34.855141, -92.320627 34.855205, -92.320617 34.855291, -92.320615 34.855389, -92.320606 34.855596, -92.320607 34.855845, -92.320596 34.856196, -92.320581 34.857273, -92.3206 34.857317, -92.320634 34.857353, -92.320681 34.857378, -92.320717 34.857388, -92.320657 34.85741, -92.320473 34.85745, -92.320387 34.858707, -92.32054 34.858688, -92.320727 34.858628, -92.320854 34.858551, -92.320967 34.858523, -92.32102 34.858479, -92.321641 34.858386, -92.321861 34.858292, -92.321867 34.858265, -92.321934 34.858226, -92.321967 34.85816, -92.322001 34.858155, -92.322141 34.85805, -92.322201 34.858023, -92.322357 34.857992, -92.322508 34.857962, -92.322581 34.857918, -92.322688 34.857841, -92.322774 34.857611, -92.322834 34.857539, -92.322921 34.857479, -92.323074 34.857413, -92.323128 34.857363, -92.323261 34.857171, -92.323314 34.857044, -92.323308 34.857, -92.323434 34.856753, -92.323448 34.856649, -92.323514 34.856456, -92.323541 34.856434, -92.323628 34.856231, -92.323687 34.856139, -92.323814 34.855945, -92.323901 34.855703, -92.324041 34.855516, -92.324161 34.855406, -92.324234 34.855379, -92.324701 34.855296, -92.325048 34.855043, -92.325361 34.854922, -92.325455 34.854823, -92.325481 34.854691, -92.325561 34.854631, -92.325668 34.854592, -92.325741 34.854532, -92.325748 34.854373, -92.325695 34.854285, -92.325581 34.854164, -92.325555 34.854098, -92.325568 34.854015, -92.325635 34.853949, -92.325915 34.853828, -92.326041 34.853669, -92.326028 34.853477, -92.326088 34.853147, -92.326141 34.853048, -92.326248 34.852932, -92.326408 34.852822, -92.326528 34.852773, -92.327342 34.852542, -92.327702 34.852476, -92.327855 34.852426, -92.327922 34.852349, -92.327962 34.852261, -92.328062 34.852212, -92.328828 34.852047, -92.329102 34.851948, -92.329575 34.851811, -92.329789 34.851783, -92.329882 34.851745, -92.329955 34.851574, -92.329962 34.851514, -92.331714 34.852352, -92.331771 34.852367, -92.33185 34.8524, -92.331919 34.852447, -92.331957 34.852485, -92.332 34.852542, -92.33209 34.852636, -92.332162 34.8527, -92.332299 34.85279, -92.332377 34.852823, -92.332599 34.852866, -92.332794 34.85291, -92.332998 34.852938, -92.333458 34.853049, -92.333573 34.85309, -92.333666 34.853171, -92.333742 34.853261, -92.333822 34.853449, -92.333877 34.853633, -92.333934 34.853894, -92.33403 34.854175, -92.334105 34.854326, -92.334026 34.85435, -92.333942 34.85439, -92.333829 34.854474, -92.333726 34.854567, -92.333666 34.854675, -92.333631 34.854789, -92.333616 34.854993, -92.333609 34.855188, -92.333608 34.855555, -92.333643 34.857004, -92.333655 34.85722, -92.333679 34.857483, -92.3337 34.857687, -92.333708 34.857813, -92.333745 34.858137, -92.333852 34.858831, -92.333877 34.859111, -92.333871 34.859231, -92.33389 34.859542, -92.33391 34.859608, -92.33395 34.859667, -92.333987 34.859701, -92.334074 34.859749, -92.33425 34.85979, -92.335473 34.859996, -92.336764 34.860206, -92.337098 34.860261, -92.337536 34.860307, -92.337742 34.860321, -92.33837 34.86035, -92.339555 34.860386, -92.340128 34.860409, -92.340574 34.860426, -92.342445 34.860481, -92.343843 34.860523, -92.344397 34.860546, -92.344877 34.860541, -92.345019 34.861121, -92.345251 34.862118, -92.345281 34.86228, -92.34528 34.862488, -92.345255 34.862695, -92.345192 34.862926, -92.345103 34.863159, -92.345203 34.863203, -92.345403 34.863324, -92.345433 34.863357, -92.34551 34.863439, -92.345623 34.863522, -92.345637 34.863576, -92.345624 34.863604, -92.345537 34.863659, -92.345477 34.86373, -92.345477 34.863813, -92.34553 34.863895, -92.34565 34.863967, -92.345684 34.864077, -92.34567 34.864137, -92.345704 34.86422, -92.345844 34.864324, -92.346017 34.864401, -92.346217 34.864533, -92.346404 34.864566, -92.346504 34.864544, -92.346604 34.864428, -92.346684 34.864368, -92.346784 34.86434, -92.346851 34.864346, -92.346911 34.864373, -92.346951 34.864428, -92.346991 34.86461, -92.347098 34.864698, -92.347231 34.864714, -92.347465 34.864637, -92.347578 34.864626, -92.347618 34.864675, -92.347598 34.864785, -92.347665 34.864829, -92.347685 34.864873, -92.347752 34.864895, -92.347765 34.864928, -92.347885 34.865011, -92.347985 34.865055, -92.348005 34.865082, -92.348198 34.865164, -92.348212 34.865192, -92.348278 34.865197, -92.348339 34.865236, -92.348999 34.86539, -92.349039 34.865445, -92.348899 34.865626, -92.348906 34.865653, -92.348946 34.865697, -92.349139 34.865763, -92.349159 34.865785, -92.349452 34.865835, -92.349639 34.865741, -92.349926 34.865703, -92.349999 34.865642, -92.350053 34.86551, -92.350079 34.865488, -92.350119 34.865488, -92.350146 34.86551, -92.350219 34.865714, -92.35026 34.865719, -92.350293 34.86578, -92.350352 34.865783, -92.350393 34.865785, -92.350426 34.865769, -92.350453 34.865713, -92.350473 34.865532, -92.35054 34.865505, -92.350606 34.865532, -92.350633 34.86551, -92.350639 34.865444, -92.350566 34.865279, -92.350579 34.865158, -92.350539 34.865076, -92.350473 34.865054, -92.350393 34.865054, -92.350259 34.865147, -92.350186 34.86518, -92.350119 34.865186, -92.350059 34.865109, -92.350059 34.865016, -92.350092 34.8649, -92.350139 34.864818, -92.350192 34.864774, -92.350292 34.864735, -92.350546 34.864702, -92.350619 34.864653, -92.350646 34.864598, -92.350626 34.86457, -92.350432 34.864471, -92.350406 34.864383, -92.350426 34.864356, -92.350759 34.864213, -92.350952 34.864092, -92.351 34.864085, -92.351139 34.864064, -92.351713 34.864092, -92.35208 34.86413, -92.352673 34.864141, -92.353053 34.864185, -92.35324 34.864179, -92.353794 34.864217, -92.354014 34.86425, -92.354094 34.864278, -92.354494 34.864305, -92.354767 34.864277, -92.35525 34.864178, -92.355135 34.864101, -92.354703 34.863809, -92.354481 34.86365, -92.35419 34.863424, -92.353908 34.863215, -92.353498 34.862909, -92.353406 34.86286, -92.352938 34.862714, -92.352633 34.862575, -92.35224 34.862404, -92.351885 34.862269, -92.3514 34.862056, -92.351021 34.861939, -92.351042 34.860749, -92.350504 34.860734, -92.349455 34.86072, -92.348769 34.860722, -92.348271 34.86072, -92.347153 34.860717, -92.34678 34.860705, -92.346537 34.860686, -92.346312 34.860655, -92.344866 34.86043, -92.344801 34.860036, -92.344569 34.85851, -92.344296 34.856702, -92.344242 34.856481, -92.344196 34.856312, -92.344115 34.856062, -92.343801 34.855248, -92.343727 34.855058, -92.343507 34.854497, -92.343315 34.854018, -92.343253 34.853862, -92.343202 34.853731, -92.34295 34.853082, -92.342712 34.852466, -92.342627 34.852256, -92.342375 34.85162, -92.34217 34.851133, -92.342138 34.851055, -92.341981 34.850728, -92.341675 34.85009, -92.341548 34.849896, -92.341438 34.849757, -92.341314 34.849627, -92.341134 34.84948, -92.340608 34.849084, -92.340356 34.848877, -92.340088 34.848586, -92.340295 34.848456, -92.340228 34.848388, -92.339855 34.847981, -92.339695 34.847782, -92.339548 34.847588, -92.339342 34.84729, -92.339122 34.846934, -92.338936 34.846597, -92.338796 34.846306, -92.338673 34.846012, -92.338555 34.845713, -92.338463 34.845441, -92.337993 34.843853, -92.337534 34.842299, -92.336062 34.83734, -92.33555 34.835615, -92.335341 34.834918, -92.335186 34.834467, -92.335102 34.834257, -92.334971 34.833965, -92.334823 34.83367, -92.334656 34.833352, -92.334641 34.833329, -92.33433 34.832855, -92.334123 34.832573, -92.333975 34.832389, -92.333738 34.832123, -92.333417 34.831779, -92.331728 34.830058, -92.327875 34.826151, -92.326745 34.825005, -92.326487 34.824761, -92.325964 34.824298, -92.325703 34.824077, -92.325522 34.823935, -92.325215 34.823695, -92.324564 34.823216, -92.324281 34.823027, -92.323346 34.822408, -92.322773 34.822034, -92.321156 34.820978, -92.321319 34.820831, -92.323051 34.821951, -92.324096 34.822645, -92.324821 34.823148, -92.325373 34.823559, -92.325669 34.823786, -92.326479 34.824452, -92.327269 34.825062, -92.327427 34.825203, -92.32787 34.825636, -92.328148 34.825916, -92.328487 34.826259, -92.32865 34.826417, -92.328922 34.826705, -92.329468 34.82726, -92.329807 34.827612, -92.330016 34.827819, -92.330196 34.82799, -92.330269 34.828065, -92.330356 34.828129, -92.330455 34.82818, -92.330526 34.828207, -92.330638 34.828233, -92.331011 34.82825, -92.33152 34.828258, -92.331844 34.828255, -92.332238 34.828255, -92.332622 34.82825, -92.333148 34.828264, -92.333703 34.828265, -92.333962 34.828258, -92.334348 34.828258, -92.334997 34.828276, -92.335284 34.828275, -92.335317 34.828275, -92.335698 34.828287, -92.335985 34.828288, -92.336975 34.828309, -92.337132 34.828309, -92.337565 34.828311, -92.338474 34.828305, -92.338545 34.828297, -92.338577 34.828287, -92.338674 34.828259, -92.338691 34.828247, -92.338682 34.828147, -92.338694 34.827885, -92.338692 34.827752, -92.338829 34.827754, -92.339177 34.827792, -92.339202 34.827806, -92.33925 34.827856, -92.339354 34.827825, -92.339463 34.82781, -92.341011 34.827829, -92.341529 34.827831, -92.342054 34.827863, -92.342451 34.827875, -92.343026 34.827883, -92.343393 34.827893, -92.343865 34.827898, -92.344267 34.827913, -92.344312 34.827906, -92.344374 34.827883, -92.344403 34.82786, -92.344423 34.827844, -92.344454 34.827794, -92.344463 34.827757, -92.344486 34.827466, -92.344484 34.827258, -92.344494 34.8271, -92.344511 34.827067, -92.344543 34.827043, -92.344577 34.827029, -92.344614 34.827025, -92.344954 34.827024, -92.345405 34.827034, -92.345836 34.827042, -92.34572 34.826614, -92.345691 34.826496, -92.345588 34.826145, -92.345465 34.825798, -92.345371 34.82557, -92.345215 34.825233, -92.345114 34.825053, -92.345031 34.824905, -92.344898 34.82469, -92.344778 34.824514, -92.344684 34.824376, -92.34453 34.824171, -92.344285 34.823871, -92.344022 34.823582, -92.343754 34.82332, -92.343489 34.823084, -92.343374 34.822987, -92.343073 34.82275, -92.342865 34.822599, -92.342446 34.822331, -92.342119 34.822143, -92.341782 34.821968, -92.341397 34.82179, -92.340022 34.821181, -92.339748 34.821065, -92.339759 34.821027, -92.339843 34.820808, -92.339908 34.820676, -92.340015 34.82049, -92.340135 34.820321, -92.340273 34.820162, -92.340427 34.820014, -92.340596 34.819878, -92.340766 34.819764, -92.340925 34.819672, -92.341136 34.81957, -92.341407 34.819465, -92.341617 34.819399, -92.342871 34.819027, -92.343342 34.818856, -92.343589 34.818796, -92.343828 34.818725, -92.344271 34.818581, -92.344448 34.818516, -92.344708 34.818409, -92.344962 34.818291, -92.345058 34.81824, -92.34528 34.818123, -92.345589 34.817943, -92.346662 34.817293, -92.34731 34.816913, -92.347953 34.816562, -92.348666 34.816197, -92.349341 34.81588, -92.349527 34.81579, -92.350052 34.815535, -92.350794 34.815166, -92.351093 34.815004, -92.351228 34.815174, -92.351557 34.814984, -92.351807 34.814824, -92.352245 34.814504, -92.352548 34.814265, -92.352974 34.813905, -92.353053 34.813839, -92.35313 34.813774, -92.353822 34.813189, -92.353936 34.813138, -92.35416 34.81303, -92.354486 34.812856, -92.354693 34.812753, -92.355011 34.812608, -92.355337 34.812478, -92.355802 34.812321, -92.356281 34.81219, -92.356763 34.812069, -92.357151 34.811959, -92.357231 34.811932, -92.357345 34.811883, -92.357451 34.811823, -92.357791 34.81148, -92.358039 34.811055, -92.358736 34.811395, -92.359108 34.811606, -92.359356 34.811709, -92.359988 34.811982, -92.360221 34.812102, -92.360453 34.812223, -92.361057 34.812639, -92.361183 34.812745, -92.361293 34.812883, -92.361476 34.813073, -92.361647 34.81327, -92.361821 34.813498, -92.361977 34.813736, -92.362083 34.813922, -92.362113 34.813975, -92.36244 34.814579, -92.363189 34.815959, -92.363428 34.816395, -92.363689 34.816364, -92.363755 34.816486, -92.363957 34.816856, -92.364158 34.817224, -92.364355 34.817545, -92.364379 34.817584, -92.364569 34.81786, -92.364706 34.81804, -92.364926 34.818301, -92.365197 34.818594, -92.365318 34.818715, -92.365601 34.818969, -92.365799 34.819132, -92.366107 34.819365, -92.36638 34.819553, -92.366488 34.81937, -92.366291 34.818682, -92.366308 34.818224, -92.36733 34.818238, -92.367495 34.818241, -92.368145 34.818251, -92.368007 34.820531, -92.369092 34.821179, -92.369181 34.821232, -92.369261 34.82128, -92.369922 34.821671, -92.370207 34.821844, -92.373822 34.824002, -92.374463 34.824379, -92.374629 34.824466, -92.374889 34.824582, -92.375123 34.824672, -92.375348 34.824744, -92.375578 34.824801, -92.375845 34.824848, -92.37618 34.824889, -92.376405 34.824906, -92.377275 34.824935, -92.379548 34.825011, -92.381113 34.825063, -92.382415 34.825111, -92.382803 34.825137, -92.382831 34.825352, -92.382914 34.82536, -92.383227 34.825404, -92.383498 34.825448, -92.383663 34.825481, -92.383987 34.825559, -92.384554 34.825722, -92.385106 34.825885, -92.385428 34.825981, -92.386616 34.826324, -92.387196 34.826491, -92.38727 34.826513, -92.387433 34.82656, -92.389563 34.827182, -92.389589 34.827189, -92.390047 34.827322, -92.390578 34.827477, -92.39089 34.827569, -92.39117 34.82766, -92.391362 34.827732, -92.391558 34.827816, -92.391769 34.827917, -92.391844 34.827953, -92.392156 34.828121, -92.392434 34.828311, -92.392838 34.828607, -92.393239 34.828908, -92.39351 34.829112, -92.393896 34.829401, -92.394936 34.830191, -92.395404 34.830523, -92.395694 34.830698, -92.395998 34.830861, -92.396021 34.830873, -92.396294 34.831003, -92.396317 34.831013, -92.396388 34.831043, -92.396111 34.831466, -92.396006 34.831652, -92.395981 34.831881, -92.395974 34.832205, -92.395969 34.83251, -92.395951 34.83309, -92.395949 34.833154, -92.395942 34.833215, -92.395925 34.833272, -92.395838 34.833356, -92.395772 34.833374, -92.395698 34.833379, -92.395617 34.833378, -92.395134 34.833361, -92.394134 34.833333, -92.393381 34.833315, -92.392951 34.833301, -92.392533 34.833296, -92.392176 34.833292, -92.392022 34.833279, -92.391652 34.83328, -92.391409 34.833314, -92.391316 34.833304, -92.391257 34.833321, -92.391121 34.833349, -92.39096 34.833383, -92.390895 34.833397, -92.390732 34.833428, -92.390582 34.833453, -92.390384 34.833477, -92.3902 34.833484, -92.390018 34.833482, -92.389755 34.833471, -92.389233 34.83345, -92.388749 34.833431, -92.388434 34.833416, -92.388113 34.833403, -92.387793 34.833394, -92.387574 34.833386, -92.387461 34.833376, -92.387289 34.833378, -92.387221 34.833387, -92.387097 34.835838, -92.387035 34.836133, -92.386579 34.836116, -92.385725 34.836083, -92.38456 34.836038, -92.382469 34.835993, -92.382422 34.836639, -92.38237 34.837483, -92.382345 34.838268, -92.382329 34.838619, -92.382312 34.839002, -92.382287 34.839365, -92.382284 34.839525, -92.382283 34.839563, -92.382273 34.839864, -92.38227 34.840038, -92.382258 34.84084, -92.382241 34.84173, -92.382254 34.84219, -92.382251 34.842409, -92.382247 34.842559, -92.382229 34.842655, -92.382117 34.842873, -92.382102 34.842885, -92.382082 34.842901, -92.381991 34.842973, -92.381852 34.843032, -92.381762 34.843047, -92.38168 34.843061, -92.381104 34.843085, -92.379952 34.843067, -92.379562 34.843055, -92.37906 34.843045, -92.378967 34.843041, -92.378875 34.843038, -92.378701 34.843033, -92.378201 34.843026, -92.377707 34.843007, -92.377541 34.843003, -92.376684 34.842987, -92.37532 34.842966, -92.374704 34.842947, -92.373843 34.842914, -92.373556 34.842912, -92.373397 34.842907, -92.373385 34.843095, -92.373401 34.843154, -92.373397 34.846017, -92.37337 34.848187, -92.373369 34.848221, -92.373884 34.848206, -92.373926 34.848208, -92.374001 34.848211, -92.374076 34.848212, -92.374157 34.848215, -92.37424 34.848216, -92.374326 34.848218, -92.374413 34.848221, -92.374501 34.848227, -92.374588 34.848234, -92.374673 34.848245, -92.374763 34.84825, -92.374854 34.848262, -92.374946 34.848281, -92.37504 34.848303, -92.375147 34.848323, -92.375304 34.848368, -92.375356 34.848387, -92.375411 34.848406, -92.375467 34.848427, -92.375525 34.848448, -92.375583 34.84847, -92.375767 34.848534, -92.375825 34.848558, -92.375884 34.848582, -92.376044 34.848653, -92.376623 34.848898, -92.376664 34.848827, -92.376709 34.848751, -92.376851 34.848515, -92.376899 34.848435, -92.376994 34.848273, -92.377042 34.848191, -92.377137 34.848023, -92.377206 34.847893, -92.377229 34.84785, -92.377319 34.847671, -92.377407 34.847485, -92.377493 34.847298, -92.377577 34.847113, -92.377698 34.846857, -92.377717 34.846819, -92.377735 34.846781, -92.377772 34.846709, -92.377806 34.846643, -92.377839 34.846582, -92.377896 34.846477, -92.37792 34.846434, -92.377959 34.846367, -92.377996 34.846307, -92.378043 34.846232, -92.378106 34.846137, -92.37868 34.845448, -92.378909 34.845248, -92.379172 34.845028, -92.379356 34.844875, -92.379609 34.844717, -92.380142 34.8444, -92.380599 34.84414, -92.381148 34.843888, -92.381746 34.843652, -92.382399 34.843493, -92.382634 34.843436, -92.383058 34.843372, -92.383509 34.843292, -92.383902 34.843296, -92.384629 34.843324, -92.38671 34.843356, -92.38716 34.843362, -92.387114 34.844324, -92.38693 34.844558, -92.386328 34.845167, -92.386233 34.845262, -92.38603 34.845586, -92.385922 34.845808, -92.3858 34.846049, -92.385802 34.846131, -92.385846 34.846175, -92.385998 34.846201, -92.38662 34.846264, -92.387 34.84634, -92.387171 34.846404, -92.387406 34.846556, -92.38759 34.846607, -92.387793 34.846613, -92.387932 34.846581, -92.38804 34.846505, -92.388237 34.846328, -92.388509 34.846061, -92.388325 34.845922, -92.388224 34.845776, -92.388097 34.84544, -92.388072 34.845313, -92.388103 34.845161, -92.38823 34.844653, -92.388408 34.844197, -92.388484 34.844032, -92.388598 34.843905, -92.388655 34.843747, -92.388674 34.843588, -92.38869 34.843378, -92.3903 34.843401, -92.390555 34.843417, -92.390757 34.843421, -92.390686 34.847459, -92.390626 34.850885, -92.392382 34.850878, -92.39343 34.850873, -92.393489 34.850875, -92.393556 34.850888, -92.393607 34.850908, -92.393653 34.850938, -92.393697 34.850984, -92.393725 34.851039, -92.393735 34.851087, -92.393742 34.851169, -92.393753 34.851288, -92.393763 34.851353, -92.393795 34.851417, -92.393857 34.851481, -92.393944 34.851526, -92.393998 34.851544, -92.394056 34.851545, -92.395346 34.851547, -92.398346 34.851545, -92.399187 34.851541, -92.39929 34.851555, -92.399422 34.85159, -92.399382 34.851707, -92.399307 34.851929, -92.398993 34.852791, -92.398956 34.852925, -92.398928 34.853001, -92.398839 34.853247, -92.398733 34.853559, -92.3987 34.853716, -92.398688 34.853876, -92.398697 34.854035, -92.398715 34.854139, -92.398756 34.854289, -92.398819 34.854434, -92.398888 34.854548, -92.399028 34.854743, -92.399132 34.854867, -92.399425 34.855254, -92.398652 34.855662, -92.398047 34.855987, -92.397489 34.856279, -92.397123 34.856477, -92.396704 34.856693, -92.396451 34.856829, -92.394791 34.857714, -92.394454 34.857889, -92.394371 34.857954, -92.393935 34.85818, -92.393598 34.85834, -92.393394 34.858438, -92.393144 34.858514, -92.392951 34.858559, -92.392801 34.858586, -92.392264 34.858625, -92.391867 34.858613, -92.391368 34.858617, -92.391086 34.858643, -92.390719 34.858663, -92.390267 34.858698, -92.389946 34.858744, -92.389668 34.858784, -92.389292 34.858872, -92.388826 34.858978, -92.388467 34.859066, -92.387996 34.859193, -92.386713 34.859522, -92.386765 34.859684, -92.386744 34.861056, -92.3867 34.864029, -92.386698 34.864133, -92.386665 34.864204, -92.386622 34.86425, -92.386554 34.864294, -92.386487 34.864316, -92.386349 34.864325, -92.385995 34.864322, -92.384646 34.86431, -92.384621 34.866261, -92.39065 34.86633, -92.390929 34.866341, -92.391123 34.866349, -92.392131 34.866387, -92.392605 34.866389, -92.392975 34.866398, -92.393832 34.866404, -92.394453 34.866399, -92.39485 34.866401, -92.394919 34.866405, -92.39502 34.86642, -92.395118 34.866446, -92.39404 34.868413, -92.393947 34.868583, -92.393188 34.869982, -92.393056 34.870256, -92.392924 34.870578, -92.392832 34.870844, -92.39277 34.871067, -92.39264 34.871595, -92.392529 34.872115, -92.392482 34.872437, -92.392462 34.872578, -92.392431 34.872887, -92.392412 34.873197, -92.392404 34.873508, -92.392413 34.875093, -92.392415 34.875294, -92.392418 34.875348, -92.39244 34.875527, -92.39248 34.875705, -92.392534 34.875875, -92.392581 34.875993, -92.392672 34.876153, -92.392779 34.876307, -92.392901 34.876452, -92.393038 34.876588, -92.393633 34.877083, -92.393778 34.877199, -92.394245 34.877574, -92.394363 34.877669, -92.394816 34.878042, -92.39378 34.878904, -92.393516 34.87913, -92.393374 34.879277, -92.393287 34.879379, -92.393168 34.87954, -92.393065 34.879707, -92.393034 34.879764, -92.392978 34.879898, -92.392932 34.880034, -92.392895 34.880172, -92.392884 34.880235, -92.392722 34.88018, -92.392294 34.880034, -92.391187 34.879658, -92.390305 34.879363, -92.385496 34.877735, -92.385369 34.877692, -92.385324 34.879611, -92.385241 34.882925, -92.385239 34.883177, -92.385228 34.883634, -92.385226 34.88372, -92.385387 34.883684, -92.385857 34.88358, -92.385894 34.883572, -92.386016 34.883545, -92.386362 34.883468, -92.386716 34.883386, -92.387016 34.883332, -92.38732 34.883294, -92.387422 34.883285, -92.387909 34.883223, -92.388397 34.88317, -92.388617 34.883148, -92.388811 34.883137, -92.389102 34.883137, -92.389296 34.883148, -92.389489 34.883167, -92.38968 34.883194, -92.390202 34.883292, -92.390277 34.883307, -92.390237 34.883725, -92.390165 34.884973, -92.390166 34.885501, -92.390154 34.886307, -92.390104 34.887715, -92.390077 34.888901, -92.390063 34.889523, -92.390069 34.889865, -92.390089 34.889906, -92.390116 34.88993, -92.390151 34.889946, -92.390804 34.890162, -92.391153 34.890262, -92.391213 34.890266, -92.391238 34.890268, -92.391293 34.890261, -92.391339 34.89024, -92.391374 34.890207, -92.391395 34.890167, -92.39141 34.88944, -92.391419 34.889035, -92.39144 34.888507, -92.39147 34.886698, -92.391494 34.885519, -92.391501 34.884871, -92.391493 34.884325, -92.391512 34.8838, -92.391529 34.883675, -92.391506 34.883537, -92.391495 34.883469, -92.391868 34.883424, -92.391968 34.883444, -92.392063 34.883425, -92.391948 34.883544, -92.392072 34.883646, -92.392215 34.883703, -92.392468 34.883749, -92.392657 34.883779, -92.392818 34.883817, -92.392967 34.883851, -92.393079 34.883877, -92.393811 34.884013, -92.394182 34.884083, -92.3946 34.884162, -92.394927 34.884239, -92.395249 34.884331, -92.395873 34.884535, -92.396648 34.884788, -92.396695 34.884805, -92.396692 34.885363, -92.396675 34.885896, -92.396674 34.886487, -92.396693 34.886986, -92.396695 34.887319, -92.396701 34.88752, -92.396704 34.887624, -92.396692 34.88799, -92.396651 34.888292, -92.396627 34.888515, -92.396612 34.888649, -92.396565 34.889018, -92.396499 34.889453, -92.396467 34.889817, -92.396472 34.890105, -92.396498 34.890543, -92.396536 34.890873, -92.39654 34.890907, -92.396568 34.890992, -92.396669 34.891243, -92.396778 34.891459, -92.39688 34.891614, -92.396982 34.891603, -92.397015 34.891568, -92.397102 34.891446, -92.397147 34.891372, -92.397134 34.891285, -92.397139 34.891198, -92.397161 34.891113, -92.397194 34.891045, -92.397272 34.890931, -92.397369 34.890828, -92.397482 34.890736, -92.397896 34.890477, -92.398082 34.890389, -92.398607 34.890076, -92.398813 34.889982, -92.398841 34.889949, -92.398829 34.889876, -92.398747 34.889649, -92.398719 34.88953, -92.398729 34.889459, -92.398746 34.889394, -92.398815 34.889234, -92.398855 34.889159, -92.398879 34.889098, -92.398894 34.88901, -92.398908 34.888899, -92.398907 34.888871, -92.398903 34.888795, -92.398902 34.887725, -92.398884 34.886961, -92.398902 34.886595, -92.398905 34.886222, -92.398925 34.885631, -92.398922 34.885572, -92.398925 34.885527, -92.399064 34.88556, -92.399192 34.885587, -92.399387 34.885617, -92.399705 34.885645, -92.40081 34.885729, -92.401173 34.885748, -92.402154 34.88581, -92.402239 34.885815, -92.402367 34.885822, -92.4026 34.885841, -92.40322 34.885877, -92.403832 34.885917, -92.404231 34.885944, -92.404643 34.885972, -92.404945 34.885992, -92.405216 34.88601, -92.40543 34.886014, -92.405644 34.886006, -92.405734 34.885995, -92.405759 34.885992, -92.40593 34.88596, -92.406098 34.885917, -92.406339 34.88582, -92.406574 34.885712, -92.406644 34.885675, -92.406918 34.885533, -92.407192 34.88539, -92.407372 34.885309, -92.40756 34.885244, -92.407755 34.885195, -92.407844 34.885179, -92.407836 34.884743, -92.40785 34.883979, -92.407856 34.883662, -92.407862 34.883254, -92.407869 34.882832, -92.407864 34.882358, -92.407878 34.881828, -92.407877 34.881648, -92.407887 34.881106, -92.407893 34.880745, -92.407862 34.88062, -92.407893 34.880513, -92.407958 34.880513, -92.408003 34.880505, -92.40813 34.88046, -92.408288 34.880415, -92.408687 34.880333, -92.408862 34.880303, -92.408916 34.880284, -92.409002 34.880229, -92.409094 34.880188, -92.409195 34.880161, -92.409346 34.88016, -92.409461 34.880147, -92.4096 34.88012, -92.409818 34.880048, -92.410008 34.880005, -92.410358 34.879958, -92.410631 34.879955, -92.410765 34.879949, -92.410983 34.879925, -92.411093 34.879918, -92.411329 34.879915, -92.411879 34.879873, -92.412669 34.879801, -92.412883 34.879766, -92.412978 34.879756, -92.413054 34.879754, -92.413183 34.879741, -92.413384 34.87976, -92.413576 34.879766, -92.413766 34.87979, -92.413891 34.879815, -92.414159 34.879818, -92.414906 34.8798, -92.415162 34.879797, -92.416167 34.879722, -92.416486 34.879707, -92.416793 34.879686, -92.417177 34.879665, -92.418344 34.879677, -92.420061 34.879701, -92.420435 34.87971, -92.421159 34.879717, -92.421955 34.879739, -92.422163 34.879741, -92.422319 34.879748, -92.422485 34.879747, -92.423336 34.879776, -92.423559 34.879783, -92.424143 34.879794, -92.424368 34.879802, -92.424844 34.879807, -92.425422 34.879824, -92.42555 34.87981, -92.425673 34.879777, -92.425724 34.879722, -92.425753 34.87966, -92.425763 34.879616, -92.425759 34.879548, -92.425732 34.879399, -92.425693 34.879284, -92.42569 34.879194, -92.425696 34.879122, -92.425817 34.879081, -92.4259 34.879036, -92.425923 34.879018, -92.425945 34.879002, -92.426057 34.878988, -92.426086 34.878988, -92.425923 34.872968, -92.425887 34.872728, -92.428028 34.872715, -92.428222 34.872708, -92.429685 34.872707, -92.429858 34.872709, -92.431431 34.872692, -92.431747 34.87269, -92.431747 34.872429, -92.434692 34.872429, -92.434743 34.872429, -92.434702 34.871603, -92.434505 34.867616, -92.434056 34.85855, -92.433999 34.857403, -92.433932 34.856034, -92.433874 34.85487, -92.433857 34.854525, -92.433829 34.853954, -92.433767 34.852712, -92.433721 34.851771, -92.433685 34.851044, -92.43356 34.848518, -92.433445 34.846095, -92.433348 34.844487, -92.433302 34.843726, -92.432855 34.843336, -92.432782 34.843297, -92.432129 34.84283, -92.431702 34.842483, -92.431369 34.84222, -92.431215 34.842115, -92.430809 34.841697, -92.430595 34.841505, -92.430402 34.841296, -92.430235 34.841065, -92.429909 34.84068, -92.429755 34.840482, -92.429469 34.84018, -92.429162 34.839773, -92.429049 34.839669, -92.428929 34.83952, -92.428822 34.839295, -92.428395 34.83874, -92.428342 34.838657, -92.428089 34.838388, -92.427982 34.838322, -92.427909 34.838294, -92.427702 34.838289, -92.427469 34.83836, -92.427315 34.83836, -92.427129 34.838245, -92.427035 34.838206, -92.426789 34.838151, -92.426655 34.83803, -92.426402 34.83769, -92.426109 34.837382, -92.425655 34.836733, -92.425595 34.836596, -92.425555 34.836431, -92.425515 34.836392, -92.425402 34.836326, -92.425196 34.836139, -92.425102 34.836117, -92.424955 34.836172, -92.424855 34.836161, -92.424489 34.835991, -92.424302 34.835886, -92.424182 34.835776, -92.424136 34.835589, -92.423956 34.835216, -92.423882 34.835051, -92.423802 34.834952, -92.423409 34.834578, -92.423202 34.834374, -92.423036 34.834226, -92.422816 34.834001, -92.422096 34.833561, -92.421809 34.833379, -92.421509 34.833209, -92.421209 34.833022, -92.421069 34.832906, -92.420949 34.832846, -92.420443 34.832494, -92.419996 34.832164, -92.419883 34.832054, -92.419716 34.832016, -92.419289 34.831972, -92.419076 34.831911, -92.418816 34.831779, -92.418683 34.831686, -92.418443 34.831543, -92.41819 34.831345, -92.41787 34.831136, -92.417716 34.831108, -92.417436 34.831081, -92.417203 34.831119, -92.41693 34.831262, -92.416043 34.831603, -92.415753 34.831772, -92.415469 34.83155, -92.415156 34.831306, -92.414856 34.831515, -92.41469 34.83157, -92.41423 34.831476, -92.413816 34.831278, -92.41363 34.831152, -92.413463 34.830992, -92.413356 34.830833, -92.413323 34.830668, -92.413343 34.830613, -92.41337 34.830596, -92.413603 34.830684, -92.413823 34.830695, -92.41391 34.830646, -92.413877 34.83053, -92.41355 34.830349, -92.41325 34.830233, -92.413017 34.830168, -92.41271 34.830019, -92.412497 34.829854, -92.411964 34.82953, -92.411744 34.829431, -92.411464 34.829233, -92.411164 34.828996, -92.410771 34.828815, -92.410551 34.828573, -92.410457 34.828551, -92.410424 34.828595, -92.41053 34.828787, -92.411097 34.8292, -92.411164 34.82926, -92.411157 34.82931, -92.411144 34.829337, -92.410844 34.829271, -92.410624 34.829194, -92.41018 34.829282, -92.410159 34.829279, -92.409489 34.82917, -92.409297 34.828941, -92.409111 34.828787, -92.408911 34.828655, -92.408704 34.828429, -92.408544 34.828303, -92.408151 34.82811, -92.407651 34.827808, -92.407218 34.827627, -92.406991 34.827621, -92.406864 34.827588, -92.406718 34.827511, -92.406585 34.827467, -92.406378 34.827434, -92.406225 34.827362, -92.406091 34.827313, -92.405711 34.827192, -92.405605 34.827148, -92.405358 34.827082, -92.404871 34.826873, -92.404558 34.826763, -92.404445 34.826741, -92.404292 34.826669, -92.403865 34.826504, -92.403492 34.826334, -92.403139 34.826136, -92.402965 34.826163, -92.402885 34.826136, -92.402785 34.826048, -92.402699 34.82596, -92.402379 34.825778, -92.401752 34.825536, -92.401492 34.825465, -92.401366 34.825498, -92.401259 34.82564, -92.401205 34.825772, -92.401179 34.825794, -92.401179 34.825822, -92.401112 34.825877, -92.401045 34.825882, -92.401012 34.8258, -92.401086 34.825508, -92.401066 34.825393, -92.401012 34.825316, -92.400826 34.825244, -92.400544 34.825144, -92.399519 34.824837, -92.399373 34.824799, -92.399273 34.824788, -92.398686 34.824667, -92.398186 34.824507, -92.397913 34.824441, -92.39776 34.824391, -92.397713 34.824391, -92.397453 34.824309, -92.396973 34.824182, -92.396627 34.82405, -92.396414 34.823946, -92.396267 34.823803, -92.396194 34.823687, -92.39584 34.823385, -92.395354 34.823137, -92.395267 34.823137, -92.394914 34.822989, -92.394881 34.822989, -92.394841 34.822961, -92.394734 34.822928, -92.394248 34.822692, -92.393675 34.822461, -92.393601 34.822444, -92.393249 34.822308, -92.392548 34.822037, -92.392348 34.821976, -92.39206 34.821819, -92.392854 34.822397, -92.393114 34.822586, -92.392855 34.823383, -92.392775 34.824084, -92.392912 34.824751, -92.392975 34.825026, -92.392832 34.824908, -92.392677 34.824786, -92.392658 34.824776, -92.392611 34.82477, -92.39252 34.824785, -92.392455 34.824633, -92.392375 34.824486, -92.392328 34.824417, -92.39228 34.824346, -92.392171 34.824213, -92.392125 34.824164, -92.391961 34.82399, -92.391951 34.823981, -92.391942 34.823973, -92.39187 34.823907, -92.391716 34.823767, -92.391043 34.823139, -92.390959 34.823067, -92.390792 34.822923, -92.390741 34.82288, -92.390482 34.822658, -92.390114 34.822367, -92.390002 34.822295, -92.389822 34.822199, -92.389696 34.822144, -92.389415 34.822042, -92.389391 34.822034, -92.388315 34.821668, -92.387492 34.821392, -92.38708 34.821254, -92.387065 34.821527, -92.386942 34.822921, -92.386929 34.823078, -92.386797 34.823073, -92.386773 34.823071, -92.386513 34.823055, -92.386185 34.823037, -92.383349 34.822879, -92.383263 34.822874, -92.382969 34.822859, -92.382989 34.822442, -92.382988 34.822272, -92.382986 34.822237, -92.382976 34.822063, -92.382983 34.821961, -92.38299 34.821854, -92.383025 34.821532, -92.383061 34.821133, -92.383063 34.82096, -92.383028 34.820469, -92.383027 34.820339, -92.383039 34.820025, -92.382754 34.819981, -92.382683 34.819977, -92.382605 34.819981, -92.382368 34.819959, -92.380176 34.819697, -92.379775 34.819646, -92.379491 34.819584, -92.379314 34.819544, -92.379222 34.81952, -92.379054 34.819477, -92.378935 34.819441, -92.378751 34.819385, -92.378553 34.81933, -92.378348 34.819275, -92.378258 34.81925, -92.377996 34.819191, -92.377612 34.81912, -92.377526 34.819104, -92.377226 34.819061, -92.376876 34.819001, -92.376631 34.818964, -92.376331 34.818932, -92.376056 34.818915, -92.375755 34.818906, -92.375283 34.818905, -92.374692 34.818911, -92.374237 34.818921, -92.374186 34.818916, -92.374094 34.818925, -92.374062 34.818925, -92.373761 34.818926, -92.373562 34.818919, -92.373286 34.818909, -92.373271 34.818908, -92.373238 34.818906, -92.372991 34.818888, -92.372596 34.81884, -92.372363 34.818804, -92.372124 34.818758, -92.372005 34.818718, -92.371896 34.818664, -92.371692 34.818533, -92.371537 34.818433, -92.371256 34.818252, -92.370878 34.818029, -92.370796 34.817981, -92.370217 34.817639, -92.370109 34.817562, -92.370015 34.817474, -92.36996 34.81741, -92.36991 34.817304, -92.369887 34.817208, -92.369847 34.816981, -92.369808 34.816867, -92.369753 34.816758, -92.369649 34.816605, -92.369547 34.816487, -92.369073 34.816042, -92.368669 34.815675, -92.368485 34.815529, -92.36833 34.815419, -92.368233 34.815356, -92.367837 34.815107, -92.367514 34.814918, -92.367397 34.814859, -92.367289 34.814789, -92.367222 34.814738, -92.367125 34.814647, -92.367028 34.814502, -92.366945 34.81435, -92.366782 34.813989, -92.366704 34.813817, -92.36667 34.81374, -92.366582 34.81359, -92.36634 34.813242, -92.366116 34.812951, -92.365713 34.812442, -92.365535 34.812239, -92.365398 34.812097, -92.365311 34.81203, -92.365214 34.811974, -92.365142 34.811943, -92.365026 34.811908, -92.364906 34.811887, -92.364532 34.811853, -92.364235 34.811839, -92.363945 34.81181, -92.363662 34.811802, -92.363481 34.811804, -92.363733 34.811515, -92.363779 34.810569, -92.363097 34.81045, -92.362015 34.810238, -92.359386 34.809805, -92.359941 34.809015, -92.360505 34.808452, -92.36068 34.80836, -92.360831 34.808271, -92.360752 34.80821, -92.360256 34.808478, -92.359623 34.808855, -92.359001 34.809241, -92.35836 34.809659, -92.357541 34.810225, -92.357125 34.810517, -92.357027 34.810591, -92.356794 34.810483, -92.356501 34.810696, -92.355749 34.811273, -92.354997 34.811891, -92.354322 34.812459, -92.353468 34.813163, -92.353096 34.813494, -92.352793 34.813746, -92.352709 34.813817, -92.352639 34.813875, -92.352476 34.81386, -92.352309 34.813832, -92.352263 34.813788, -92.352029 34.813744, -92.35181 34.81364, -92.351669 34.813519, -92.351389 34.813349, -92.351209 34.813112, -92.350869 34.812865, -92.350767 34.812814, -92.351075 34.812462, -92.351332 34.812147, -92.351446 34.811989, -92.351543 34.811823, -92.351621 34.811651, -92.351684 34.811469, -92.351751 34.81122, -92.351834 34.810945, -92.351889 34.810823, -92.351967 34.810688, -92.351996 34.810648, -92.352172 34.810429, -92.352507 34.8101, -92.352666 34.809963, -92.35278 34.809878, -92.35296 34.80976, -92.353217 34.809639, -92.353483 34.809482, -92.353406 34.80935, -92.35338 34.8093, -92.353333 34.809378, -92.353317 34.809414, -92.35327 34.809472, -92.353219 34.809509, -92.353149 34.809549, -92.353066 34.809591, -92.352972 34.809639, -92.352868 34.809693, -92.352757 34.809754, -92.352643 34.809826, -92.35253 34.809907, -92.352415 34.81, -92.3523 34.810098, -92.352182 34.810202, -92.35206 34.810308, -92.351933 34.810416, -92.351894 34.810449, -92.351803 34.810526, -92.351669 34.810638, -92.35153 34.81075, -92.35139 34.810862, -92.351248 34.810974, -92.351105 34.811088, -92.350962 34.811202, -92.350819 34.811316, -92.350675 34.81143, -92.350529 34.811542, -92.350383 34.811651, -92.350236 34.811756, -92.350088 34.811858, -92.349939 34.811957, -92.349787 34.812053, -92.349694 34.812107, -92.34948 34.812234, -92.349164 34.8124, -92.349091 34.812433, -92.348065 34.812819, -92.346801 34.813175, -92.34658 34.813254, -92.346248 34.813352, -92.345797 34.813479, -92.345294 34.813606, -92.344895 34.813716, -92.344502 34.81382, -92.34417 34.813931, -92.343926 34.814033, -92.34381 34.814092, -92.343745 34.814133, -92.343389 34.814357, -92.343146 34.814505, -92.342654 34.814794, -92.342399 34.814958, -92.34221 34.815099, -92.341899 34.815369, -92.341669 34.81558, -92.341476 34.81576, -92.341138 34.816096, -92.3409 34.816317, -92.340808 34.816403, -92.340696 34.816308, -92.340598 34.816202, -92.340515 34.816088, -92.340368 34.815849, -92.340257 34.81565, -92.340199 34.815529, -92.340152 34.815431, -92.340107 34.8153, -92.340084 34.815166, -92.340112 34.814399, -92.34016 34.813559, -92.340152 34.8133, -92.340168 34.812888, -92.340189 34.812238, -92.34019 34.811715, -92.340179 34.811491, -92.340184 34.81144, -92.340186 34.811256, -92.340174 34.811127, -92.340141 34.811039, -92.340119 34.811018, -92.340043 34.810967, -92.339777 34.810851, -92.339177 34.810602, -92.338946 34.810487, -92.338773 34.810389, -92.338718 34.81035, -92.338647 34.810282, -92.33859 34.810205, -92.338537 34.810101, -92.338503 34.809992, -92.338274 34.809984, -92.337884 34.809971, -92.337803 34.809968, -92.33754 34.809965, -92.336895 34.809957, -92.336872 34.809728, -92.336897 34.809327, -92.336903 34.809233, -92.332804 34.809283, -92.331939 34.809656, -92.331627 34.809792, -92.331615 34.80872, -92.331546 34.808721, -92.331208 34.808733, -92.330563 34.808739, -92.330354 34.808752, -92.330338 34.808753, -92.330375 34.809906, -92.33023 34.809912, -92.328387 34.809882, -92.323327 34.809746, -92.321679 34.809688, -92.319225 34.809615, -92.318877 34.809595, -92.318494 34.809588, -92.31795 34.809578, -92.317821 34.809553, -92.317804 34.809665, -92.317485 34.809573, -92.317492 34.809692, -92.317515 34.810072, -92.312667 34.809673, -92.312594 34.809667, -92.312586 34.809524, -92.312543 34.808541, -92.312542 34.808278, -92.31254 34.807968, -92.312541 34.80778, -92.312685 34.807874, -92.312812 34.807925, -92.312818 34.807273, -92.312807 34.806981, -92.312819 34.806603, -92.312813 34.806214, -92.312816 34.806071, -92.3128 34.80595, -92.312768 34.805903, -92.312721 34.805864, -92.312683 34.805846, -92.312546 34.80584, -92.31245 34.805835, -92.3121 34.805834, -92.311925 34.805825, -92.311 34.80581, -92.310796 34.80581, -92.310575 34.805807, -92.309835 34.805819, -92.309692 34.805818, -92.309631 34.805817, -92.308762 34.805786, -92.308281 34.805781, -92.307863 34.805764, -92.307555 34.805766, -92.303551 34.80559, -92.299783 34.80553, -92.2968 34.805537, -92.295829 34.80555, -92.295695 34.803663, -92.295833 34.803651, -92.296091 34.803619, -92.296346 34.803579, -92.296772 34.803483, -92.297619 34.803277, -92.297684 34.803257, -92.298064 34.803243, -92.298186 34.80325, -92.29832 34.803276, -92.298559 34.803352, -92.298703 34.803473, -92.29909 34.803799, -92.299283 34.803939, -92.299407 34.804001, -92.299547 34.804057, -92.299678 34.804098, -92.299813 34.804127, -92.29993 34.804139, -92.300056 34.804139, -92.300219 34.804124, -92.300384 34.804095, -92.300586 34.804038, -92.301116 34.803842, -92.301396 34.803744, -92.301504 34.803719, -92.301598 34.803706, -92.301661 34.803702, -92.301697 34.8037, -92.303611 34.80371, -92.303884 34.803726, -92.30426 34.80377, -92.3048 34.803849, -92.305033 34.80389, -92.305258 34.803942, -92.305591 34.804036, -92.306215 34.804238, -92.306568 34.804367, -92.306878 34.804498, -92.307554 34.80481, -92.307587 34.804829, -92.307725 34.804875, -92.30799 34.804937, -92.31046 34.805286, -92.310861 34.805334, -92.311299 34.805368, -92.312363 34.805471, -92.312438 34.805474, -92.312663 34.805468, -92.312813 34.805455, -92.313008 34.805428, -92.313129 34.8054, -92.313241 34.805364, -92.313404 34.805301, -92.313559 34.805226, -92.314334 34.804787, -92.31438 34.804761, -92.314784 34.804532, -92.314634 34.804353, -92.314328 34.804007, -92.314016 34.803679, -92.313696 34.803359, -92.313366 34.803044, -92.31307 34.802775, -92.312766 34.802511, -92.312309 34.802139, -92.311858 34.801799, -92.31139 34.80147, -92.310901 34.801145, -92.310401 34.800833, -92.310058 34.800631, -92.30971 34.800436, -92.309606 34.800382, -92.309077 34.800133, -92.3077 34.799475, -92.30723 34.799242, -92.306538 34.798848, -92.305948 34.798573, -92.305817 34.798487, -92.305533 34.798333, -92.30529 34.798187, -92.303276 34.796858, -92.303191 34.796796, -92.303085 34.796807, -92.302998 34.796826, -92.303036 34.79674, -92.303096 34.796614, -92.30312 34.796564, -92.30287 34.796613, -92.302606 34.79649, -92.300697 34.795568, -92.30046 34.795434, -92.300304 34.795336, -92.300119 34.795198, -92.300029 34.795121, -92.299833 34.794927, -92.299719 34.794808, -92.2995 34.794652, -92.299334 34.794427, -92.298417 34.793142, -92.297704 34.792199, -92.297224 34.791545, -92.296153 34.790084, -92.295847 34.789684, -92.295648 34.789464, -92.295921 34.789326, -92.296247 34.78926, -92.296587 34.789277, -92.297207 34.789381, -92.297287 34.789381, -92.297653 34.789282, -92.29774 34.789282, -92.297873 34.78931, -92.29806 34.789403, -92.298213 34.789436, -92.298306 34.789486, -92.298406 34.789513, -92.298593 34.78953, -92.298753 34.789486, -92.298952 34.789376, -92.298999 34.789321, -92.299032 34.78931, -92.299059 34.789238, -92.299119 34.789189, -92.299239 34.788964, -92.299586 34.788749, -92.299725 34.788705, -92.299985 34.788694, -92.300045 34.788711, -92.300378 34.788722, -92.300612 34.788656, -92.300752 34.788639, -92.300845 34.788606, -92.300878 34.788568, -92.301098 34.788497, -92.301398 34.788354, -92.301678 34.788299, -92.301898 34.788293, -92.302151 34.78826, -92.302804 34.788337, -92.30291 34.788315, -92.303237 34.788293, -92.303743 34.788304, -92.30397 34.788381, -92.304203 34.788409, -92.304223 34.788431, -92.304403 34.78848, -92.304829 34.788568, -92.304989 34.788585, -92.305676 34.788563, -92.306035 34.788502, -92.306195 34.788447, -92.306429 34.788321, -92.306489 34.788244, -92.306509 34.788156, -92.306509 34.787782, -92.306442 34.787332, -92.306382 34.787238, -92.306282 34.787145, -92.30622 34.787047, -92.306202 34.787018, -92.306169 34.786925, -92.306149 34.786721, -92.306069 34.786634, -92.306162 34.786469, -92.306282 34.786386, -92.306682 34.786227, -92.306749 34.786155, -92.306762 34.7861, -92.306742 34.78604, -92.306522 34.785721, -92.306455 34.785589, -92.306396 34.78532, -92.306416 34.785259, -92.306422 34.785105, -92.306556 34.784583, -92.306862 34.783999, -92.306991 34.783753, -92.307126 34.783308, -92.307129 34.783154, -92.307126 34.782789, -92.306961 34.781961, -92.306789 34.781549, -92.306622 34.781109, -92.306516 34.780735, -92.306469 34.780658, -92.306309 34.780235, -92.306216 34.780043, -92.30605 34.779806, -92.305763 34.779443, -92.305563 34.77924, -92.305237 34.778619, -92.305124 34.778443, -92.304984 34.778283, -92.304803 34.777997, -92.304384 34.777338, -92.304264 34.777168, -92.304211 34.777124, -92.304138 34.777014, -92.304104 34.776997, -92.303905 34.776695, -92.303818 34.776623, -92.303378 34.776112, -92.303165 34.775738, -92.303098 34.775661, -92.303079 34.775601, -92.302825 34.775254, -92.302725 34.775062, -92.302486 34.774754, -92.302466 34.774699, -92.302486 34.774644, -92.302586 34.77449, -92.302559 34.774446, -92.302359 34.774529, -92.302179 34.774353, -92.30196 34.77404, -92.301846 34.773924, -92.30184 34.773891, -92.301806 34.773875, -92.301786 34.773814, -92.301646 34.773611, -92.3015 34.773292, -92.301353 34.773132, -92.301214 34.773034, -92.30106 34.772869, -92.301054 34.772836, -92.301087 34.772786, -92.301433 34.772621, -92.301607 34.7725, -92.30164 34.772451, -92.3016 34.772363, -92.300914 34.77272, -92.300794 34.772665, -92.300674 34.772572, -92.300563 34.772423, -92.301602 34.771744, -92.301665 34.771703, -92.302642 34.771066, -92.302691 34.771034, -92.303051 34.771591, -92.303734 34.772918, -92.304015 34.773463, -92.304052 34.773533, -92.304176 34.773771, -92.304299 34.77401, -92.304438 34.77428, -92.304576 34.774551, -92.304629 34.774655, -92.304699 34.77479, -92.305983 34.774342, -92.306485 34.774144, -92.306603 34.774379, -92.307032 34.775466, -92.307372 34.776057, -92.307454 34.776209, -92.307289 34.776184, -92.307375 34.776486, -92.307489 34.776706, -92.307595 34.777014, -92.307622 34.777036, -92.307695 34.777206, -92.307729 34.777228, -92.307808 34.777415, -92.307868 34.777476, -92.307875 34.777525, -92.307962 34.77769, -92.308022 34.777778, -92.308062 34.7778, -92.308068 34.777838, -92.308215 34.778009, -92.308255 34.778097, -92.308288 34.778119, -92.308448 34.778361, -92.308481 34.778377, -92.308535 34.778487, -92.308661 34.778641, -92.308941 34.779059, -92.309094 34.779251, -92.309247 34.7794, -92.309414 34.779625, -92.309447 34.779631, -92.309674 34.779988, -92.3097 34.780076, -92.30988 34.780307, -92.30996 34.780461, -92.309994 34.780477, -92.310007 34.780527, -92.31004 34.780554, -92.31034 34.781087, -92.310533 34.781357, -92.310593 34.781406, -92.310813 34.781692, -92.310846 34.781708, -92.311146 34.782066, -92.311259 34.782148, -92.311273 34.782181, -92.311353 34.782236, -92.311413 34.782319, -92.311459 34.782335, -92.311473 34.782374, -92.311533 34.782412, -92.311626 34.782522, -92.311726 34.782582, -92.311872 34.782731, -92.311972 34.78278, -92.312285 34.783011, -92.312305 34.783044, -92.312359 34.783055, -92.312372 34.783083, -92.312412 34.783094, -92.312425 34.783121, -92.312639 34.783264, -92.312699 34.783275, -92.312785 34.783336, -92.312832 34.783347, -92.312858 34.783379, -92.312945 34.783407, -92.312992 34.783451, -92.313072 34.783467, -92.313165 34.783517, -92.313358 34.783638, -92.313491 34.783693, -92.313571 34.783753, -92.313718 34.783786, -92.313738 34.783814, -92.313778 34.783814, -92.314051 34.783913, -92.314158 34.783957, -92.314178 34.783984, -92.314304 34.784012, -92.314451 34.784089, -92.314684 34.784177, -92.315237 34.784457, -92.31525 34.784484, -92.31529 34.784484, -92.315324 34.784517, -92.31545 34.784567, -92.31547 34.784605, -92.315504 34.784605, -92.315584 34.784671, -92.315777 34.784765, -92.315803 34.784798, -92.31585 34.784809, -92.31587 34.784836, -92.316117 34.784968, -92.316257 34.785023, -92.31627 34.785056, -92.316383 34.785078, -92.316436 34.785128, -92.316696 34.785237, -92.316723 34.785265, -92.317016 34.785358, -92.317109 34.785408, -92.317163 34.785408, -92.317909 34.785633, -92.318229 34.785683, -92.318729 34.785655, -92.318895 34.7856, -92.319002 34.785595, -92.319254 34.785523, -92.319615 34.785452, -92.319811 34.785415, -92.319936 34.785406, -92.319994 34.78542, -92.320053 34.785518, -92.320084 34.785599, -92.320111 34.785702, -92.320106 34.785863, -92.320106 34.786011, -92.320075 34.78619, -92.320044 34.786329, -92.319986 34.786477, -92.319941 34.78658, -92.319923 34.786643, -92.319954 34.786656, -92.320008 34.786571, -92.320066 34.786414, -92.320129 34.78624, -92.320174 34.786025, -92.320192 34.785877, -92.320183 34.785702, -92.320147 34.785505, -92.320133 34.785375, -92.320142 34.785339, -92.320196 34.785317, -92.320393 34.785285, -92.320801 34.785196, -92.320998 34.785142, -92.321231 34.785093, -92.321365 34.785062, -92.321504 34.785039, -92.321629 34.785021, -92.321853 34.784994, -92.322086 34.784985, -92.322261 34.784972, -92.322427 34.784941, -92.322673 34.784896, -92.322875 34.784914, -92.322928 34.784967, -92.322937 34.785021, -92.322937 34.785097, -92.322964 34.7852, -92.323049 34.785384, -92.323099 34.785518, -92.32309 34.785626, -92.323054 34.785724, -92.32296 34.785953, -92.322866 34.78619, -92.322799 34.78645, -92.322772 34.786522, -92.3227 34.78654, -92.322633 34.786517, -92.322624 34.786455, -92.322642 34.786369, -92.322687 34.786253, -92.322695 34.786181, -92.322669 34.786096, -92.322606 34.786016, -92.322467 34.78602, -92.322265 34.786016, -92.322006 34.785998, -92.321777 34.785962, -92.321661 34.785917, -92.321504 34.78589, -92.321307 34.785895, -92.321043 34.785935, -92.320747 34.786002, -92.320554 34.78606, -92.320402 34.786145, -92.320321 34.786177, -92.320295 34.786204, -92.320308 34.78624, -92.320321 34.786293, -92.320362 34.786383, -92.320416 34.786472, -92.320505 34.786558, -92.320675 34.786629, -92.32089 34.78671, -92.321132 34.786791, -92.321329 34.786835, -92.321732 34.786867, -92.321921 34.786867, -92.3221 34.786844, -92.322315 34.786782, -92.32244 34.786719, -92.322606 34.786643, -92.322655 34.786647, -92.3227 34.786696, -92.322682 34.786755, -92.322664 34.786831, -92.322646 34.78688, -92.322624 34.786947, -92.322561 34.7871, -92.322525 34.787194, -92.322507 34.78731, -92.322512 34.787359, -92.322548 34.787359, -92.322575 34.78731, -92.322615 34.787225, -92.322678 34.78718, -92.322816 34.787162, -92.323005 34.787167, -92.323237 34.787171, -92.323444 34.78718, -92.32386 34.787265, -92.32425 34.787324, -92.324514 34.787324, -92.32481 34.787279, -92.325029 34.787185, -92.325226 34.787149, -92.32545 34.787077, -92.325598 34.78701, -92.325692 34.786983, -92.325804 34.786988, -92.325907 34.787001, -92.326033 34.787006, -92.326131 34.787059, -92.326153 34.787095, -92.326145 34.787126, -92.326127 34.787176, -92.325916 34.787736, -92.325912 34.787807, -92.325925 34.787839, -92.325965 34.78783, -92.326015 34.787673, -92.32605 34.787612, -92.326158 34.787568, -92.326355 34.787541, -92.326588 34.787505, -92.327072 34.787433, -92.327385 34.787344, -92.327654 34.787245, -92.327869 34.787173, -92.328084 34.78712, -92.328424 34.787075, -92.328631 34.787039, -92.328899 34.786967, -92.329338 34.786842, -92.329544 34.786842, -92.329625 34.786869, -92.329625 34.786976, -92.329571 34.787111, -92.329491 34.787227, -92.329437 34.787433, -92.329374 34.787675, -92.329392 34.787756, -92.329428 34.787756, -92.329482 34.787666, -92.329508 34.787577, -92.32958 34.787496, -92.329759 34.787415, -92.330028 34.787335, -92.330198 34.787218, -92.330431 34.787066, -92.330812 34.786963, -92.33109 34.786876, -92.331327 34.786777, -92.331551 34.786728, -92.331932 34.786584, -92.332129 34.78649, -92.332254 34.786441, -92.332286 34.786401, -92.332263 34.786356, -92.332201 34.786307, -92.332165 34.786253, -92.33217 34.786138, -92.332173 34.786064, -92.332193 34.785963, -92.332179 34.785896, -92.332117 34.785838, -92.33205 34.785814, -92.331971 34.785805, -92.33183 34.785802, -92.331695 34.785802, -92.331601 34.785773, -92.331561 34.785737, -92.331453 34.785632, -92.331386 34.785531, -92.331366 34.785441, -92.331383 34.785369, -92.331428 34.785242, -92.331468 34.785132, -92.331466 34.785243, -92.331465 34.785328, -92.331461 34.785408, -92.331477 34.785496, -92.331578 34.785605, -92.331687 34.785684, -92.331838 34.785726, -92.332044 34.785718, -92.332153 34.78571, -92.332199 34.785756, -92.332278 34.78581, -92.33235 34.785907, -92.332383 34.785999, -92.332404 34.78607, -92.332417 34.786112, -92.332413 34.786217, -92.332413 34.78628, -92.332425 34.786364, -92.332463 34.786439, -92.33253 34.786494, -92.332605 34.786527, -92.332677 34.786582, -92.332689 34.786657, -92.332681 34.786745, -92.33266 34.786825, -92.332622 34.786917, -92.332589 34.787018, -92.332538 34.787211, -92.332526 34.787282, -92.332488 34.787395, -92.33245 34.787521, -92.332442 34.787588, -92.332446 34.787605, -92.332463 34.787605, -92.332501 34.787576, -92.332538 34.787513, -92.332601 34.787269, -92.332694 34.786976, -92.332731 34.78693, -92.332803 34.786909, -92.333058 34.786896, -92.333239 34.786879, -92.333423 34.786871, -92.333562 34.786863, -92.333658 34.786859, -92.333796 34.786867, -92.333914 34.78693, -92.333993 34.786938, -92.334023 34.786921, -92.334082 34.786884, -92.334119 34.7869, -92.334199 34.786968, -92.334258 34.787014, -92.334371 34.787089, -92.334446 34.787118, -92.334492 34.787118, -92.334522 34.787135, -92.334555 34.787173, -92.33461 34.787223, -92.334606 34.787248, -92.334539 34.787248, -92.333658 34.787383, -92.333608 34.787391, -92.333603 34.787412, -92.333608 34.787433, -92.333624 34.787446, -92.333897 34.787399, -92.33453 34.787307, -92.334736 34.787269, -92.334825 34.787256, -92.335583 34.787181, -92.335802 34.787158, -92.335851 34.787144, -92.335874 34.787095, -92.335882 34.787041, -92.335927 34.786902, -92.336008 34.786732, -92.336124 34.786392, -92.336209 34.786042, -92.336254 34.785895, -92.336335 34.785832, -92.336362 34.785832, -92.336447 34.785832, -92.336595 34.78585, -92.336944 34.785877, -92.337187 34.785981, -92.33732 34.786029, -92.337311 34.786096, -92.337302 34.786226, -92.337253 34.786347, -92.337208 34.786437, -92.33719 34.78649, -92.337195 34.786549, -92.337276 34.78662, -92.337477 34.786638, -92.337768 34.786643, -92.33814 34.78667, -92.338516 34.786719, -92.338843 34.786786, -92.338906 34.786791, -92.33891 34.78684, -92.33891 34.786876, -92.338825 34.78692, -92.338776 34.786929, -92.338669 34.786938, -92.338355 34.786938, -92.337706 34.786943, -92.336545 34.787055, -92.336492 34.787068, -92.336492 34.787091, -92.336505 34.7871, -92.336635 34.787095, -92.337011 34.787064, -92.337598 34.787032, -92.338382 34.787019, -92.339067 34.78701, -92.339596 34.786992, -92.339937 34.787038, -92.340124 34.787032, -92.340343 34.78706, -92.341301 34.78708, -92.341336 34.787081, -92.341709 34.78712, -92.342049 34.787131, -92.342755 34.787235, -92.343502 34.787268, -92.343763 34.787295, -92.344068 34.787328, -92.344095 34.787344, -92.344441 34.787366, -92.345561 34.787493, -92.346014 34.787564, -92.346327 34.787652, -92.34644 34.787641, -92.3467 34.78758, -92.346867 34.787575, -92.347027 34.787597, -92.34738 34.78769, -92.34774 34.787723, -92.348153 34.787794, -92.348399 34.787866, -92.348653 34.787964, -92.348872 34.787992, -92.349146 34.788058, -92.349432 34.788157, -92.349539 34.788244, -92.349452 34.788338, -92.349546 34.788409, -92.349759 34.78847, -92.350478 34.788585, -92.350818 34.788662, -92.350898 34.7887, -92.351465 34.788794, -92.351471 34.788821, -92.351618 34.788832, -92.351658 34.788859, -92.352104 34.788942, -92.352378 34.789008, -92.352404 34.78903, -92.353091 34.789189, -92.353237 34.7892, -92.353317 34.789266, -92.353397 34.789288, -92.353517 34.789354, -92.353937 34.789458, -92.35433 34.789535, -92.35451 34.789546, -92.35481 34.789601, -92.35515 34.789628, -92.355317 34.789688, -92.35561 34.789859, -92.355803 34.789875, -92.35613 34.789842, -92.356543 34.789974, -92.357023 34.790072, -92.357136 34.790144, -92.357176 34.790149, -92.357282 34.79027, -92.357309 34.790336, -92.357289 34.790496, -92.357016 34.790441, -92.356969 34.790314, -92.356516 34.790238, -92.356463 34.79032, -92.356563 34.790342, -92.35669 34.790534, -92.356463 34.7906, -92.356376 34.790595, -92.356337 34.790672, -92.356763 34.790776, -92.356943 34.79071, -92.358296 34.791023, -92.358369 34.791138, -92.358882 34.791248, -92.358909 34.79116, -92.358662 34.7911, -92.358889 34.791006, -92.358722 34.790792, -92.359115 34.790874, -92.359315 34.790412, -92.360394 34.79067, -92.360368 34.790747, -92.360588 34.790813, -92.360661 34.790736, -92.360774 34.79072, -92.361061 34.790791, -92.361388 34.790818, -92.361727 34.790846, -92.361787 34.790868, -92.36206 34.790884, -92.362514 34.790955, -92.362627 34.790944, -92.36332 34.790983, -92.363695 34.790928, -92.363866 34.791015, -92.364021 34.79095, -92.364191 34.790774, -92.364317 34.790644, -92.364413 34.790671, -92.364379 34.790696, -92.364372 34.790729, -92.364291 34.790801, -92.364273 34.790817, -92.364258 34.79085, -92.364246 34.790878, -92.364093 34.791037, -92.36406 34.791125, -92.36408 34.791153, -92.364093 34.791186, -92.364273 34.791224, -92.364693 34.791125, -92.365012 34.791158, -92.365512 34.791334, -92.365539 34.791361, -92.365772 34.791416, -92.366032 34.791515, -92.366665 34.79163, -92.366685 34.791652, -92.366892 34.791696, -92.367038 34.791751, -92.367099 34.7918, -92.367198 34.791844, -92.367232 34.791844, -92.367345 34.791932, -92.367578 34.79202, -92.368072 34.792129, -92.368252 34.792151, -92.368618 34.792267, -92.368645 34.792294, -92.368685 34.792294, -92.368705 34.792322, -92.368738 34.792322, -92.368791 34.792371, -92.368838 34.792376, -92.368865 34.792409, -92.368911 34.79242, -92.368918 34.792448, -92.368958 34.792453, -92.369158 34.79258, -92.369285 34.792613, -92.369778 34.792629, -92.369831 34.792645, -92.370164 34.79281, -92.370278 34.792849, -92.370471 34.792969, -92.370524 34.792975, -92.370958 34.793167, -92.371597 34.793321, -92.371817 34.793431, -92.371897 34.793452, -92.371924 34.793485, -92.372044 34.793518, -92.372404 34.793529, -92.37255 34.793568, -92.372657 34.793655, -92.372764 34.79371, -92.37287 34.793738, -92.372897 34.793765, -92.372977 34.793782, -92.373004 34.793809, -92.373044 34.793809, -92.373124 34.793848, -92.373384 34.793886, -92.37359 34.793946, -92.373684 34.793996, -92.37381 34.794018, -92.37395 34.794073, -92.373977 34.7941, -92.374077 34.794128, -92.374137 34.794171, -92.37425 34.794193, -92.37441 34.794254, -92.374463 34.794298, -92.374663 34.794369, -92.37479 34.794435, -92.375209 34.794559, -92.375226 34.794572, -92.375245 34.794599, -92.375278 34.794599, -92.375951 34.794907, -92.37633 34.795171, -92.376397 34.795309, -92.37645 34.795347, -92.37673 34.795375, -92.37681 34.795375, -92.37697 34.795413, -92.377276 34.795529, -92.377676 34.795754, -92.378195 34.796101, -92.378822 34.796382, -92.379188 34.79653, -92.379448 34.796684, -92.379774 34.796849, -92.380247 34.797036, -92.380327 34.797135, -92.380346 34.797361, -92.380419 34.797487, -92.380446 34.797517, -92.380438 34.797603, -92.380433 34.797655, -92.38043 34.797677, -92.38054 34.79763, -92.380773 34.797524, -92.381311 34.797323, -92.381504 34.797382, -92.381647 34.79741, -92.381775 34.797441, -92.382156 34.79748, -92.382478 34.797505, -92.383648 34.797538, -92.383738 34.797554, -92.384071 34.797649, -92.384129 34.797672, -92.384407 34.797808, -92.384896 34.798122, -92.385135 34.798287, -92.385264 34.798352, -92.385316 34.798364, -92.385369 34.798362, -92.385419 34.798346, -92.38548 34.798305, -92.385748 34.798524, -92.386067 34.798775, -92.386252 34.798912, -92.386451 34.79904, -92.38666 34.79916, -92.386866 34.799267, -92.38707 34.79936, -92.387379 34.79948, -92.389122 34.800093, -92.389569 34.800242, -92.389758 34.800298, -92.390141 34.800398, -92.3904 34.800454, -92.390806 34.80053, -92.39164 34.800681, -92.392137 34.800783, -92.392819 34.800942, -92.393819 34.8012, -92.394163 34.801296, -92.396626 34.801947, -92.39708 34.802081, -92.397363 34.802179, -92.397644 34.802283, -92.399145 34.80287, -92.399489 34.803008, -92.399756 34.803109, -92.400112 34.803232, -92.400397 34.803324, -92.400705 34.803407, -92.401036 34.803478, -92.401533 34.803566, -92.402598 34.803736, -92.402817 34.803778, -92.403298 34.803883, -92.403749 34.803996, -92.404124 34.804103, -92.404529 34.804233, -92.40624 34.804845, -92.407484 34.805296, -92.408955 34.805831, -92.409343 34.805988, -92.409724 34.806157, -92.410548 34.806547, -92.412891 34.807661, -92.415113 34.808712, -92.41583 34.809055, -92.419364 34.810732, -92.419923 34.810987, -92.42018 34.811091, -92.42038 34.811159, -92.420678 34.811244, -92.42114 34.811352, -92.425685 34.812299, -92.42653 34.812495, -92.430612 34.813578, -92.43102 34.813678, -92.431293 34.81373, -92.431477 34.813751, -92.431702 34.813759, -92.431912 34.813749, -92.432086 34.813727, -92.432312 34.813683, -92.43251 34.813624, -92.432684 34.813558, -92.432869 34.813477, -92.433142 34.813336, -92.433669 34.813049, -92.4339 34.812936, -92.434048 34.812876, -92.434215 34.812823, -92.434391 34.812779, -92.434657 34.812735, -92.434797 34.812723, -92.435027 34.812723, -92.435228 34.812734, -92.435444 34.812761, -92.435709 34.812816, -92.437779 34.813319, -92.440075 34.813875, -92.440371 34.813932, -92.440587 34.813962, -92.44084 34.813979, -92.441162 34.813998, -92.44138 34.81398, -92.441553 34.813957, -92.441801 34.813906, -92.44203 34.813843, -92.442326 34.813739, -92.442533 34.813645, -92.442912 34.81345, -92.443175 34.813299, -92.44339 34.813166, -92.443613 34.813038, -92.445234 34.812141, -92.445358 34.812073, -92.445563 34.811959, -92.445635 34.812025, -92.445766 34.811954, -92.44614 34.811754, -92.446403 34.811612, -92.446637 34.811465, -92.446951 34.811271, -92.447485 34.810974, -92.448457 34.810699, -92.448834 34.810679, -92.448836 34.810581, -92.448962 34.810575, -92.44915 34.810579, -92.449235 34.810582, -92.449539 34.810612, -92.449827 34.810663, -92.449937 34.810689, -92.449985 34.810701, -92.450035 34.810715, -92.450216 34.810773, -92.45026 34.81079, -92.450506 34.810887, -92.45054 34.810904, -92.450714 34.810989, -92.450943 34.811121, -92.451007 34.811165, -92.451121 34.811243, -92.451263 34.811355, -92.451448 34.811521, -92.451675 34.811757, -92.452993 34.813262, -92.453231 34.813532, -92.453482 34.813803, -92.453499 34.813819, -92.453664 34.813983, -92.453844 34.814135, -92.454046 34.814281, -92.454249 34.814405, -92.454451 34.814512, -92.454659 34.814605, -92.454763 34.814456, -92.454785 34.814444, -92.454839 34.814437, -92.454861 34.81444, -92.454977 34.814473, -92.455078 34.81449, -92.455181 34.814499, -92.455449 34.814496, -92.455725 34.814477, -92.455836 34.814473, -92.456012 34.814474, -92.456308 34.814485, -92.456374 34.814478, -92.456439 34.814463, -92.456604 34.814403, -92.459604 34.815142, -92.460901 34.815461, -92.461408 34.815586, -92.461437 34.815593, -92.461815 34.815695, -92.461731 34.814765, -92.461593 34.813239, -92.461612 34.813261, -92.461627 34.813282, -92.461825 34.813488, -92.461861 34.813534, -92.461903 34.813576, -92.461949 34.813615, -92.462219 34.813819, -92.462425 34.813974, -92.46253 34.814047, -92.462617 34.814097, -92.462519 34.814216, -92.462487 34.814259, -92.46246 34.814304, -92.462438 34.814351, -92.462422 34.8144, -92.462411 34.814449, -92.462405 34.814499, -92.462401 34.814588, -92.462294 34.81655, -92.462294 34.816565, -92.462294 34.816579, -92.462295 34.816593, -92.462296 34.816607, -92.462298 34.816622, -92.462301 34.816636, -92.462304 34.81665, -92.462307 34.816664, -92.462312 34.816678, -92.462316 34.816691, -92.462322 34.816705, -92.462328 34.816718, -92.462334 34.816731, -92.462342 34.816744, -92.462349 34.816757, -92.462357 34.81677, -92.462366 34.816782, -92.462375 34.816794, -92.462385 34.816806, -92.462395 34.816818, -92.462406 34.816829, -92.462417 34.81684, -92.462429 34.81685, -92.462441 34.816861, -92.462453 34.81687, -92.462466 34.81688, -92.462479 34.816889, -92.462493 34.816898, -92.462507 34.816906, -92.462521 34.816914, -92.462536 34.816922, -92.462551 34.816929, -92.462566 34.816936, -92.462582 34.816942, -92.462597 34.816948, -92.462614 34.816953, -92.463249 34.817156, -92.463266 34.817161, -92.463282 34.817165, -92.463299 34.817169, -92.463316 34.817173, -92.463333 34.817176, -92.46335 34.817178, -92.463361 34.81718, -92.463385 34.817182, -92.463909 34.81722, -92.464197 34.817241, -92.464219 34.817242, -92.464242 34.817241, -92.464264 34.817239, -92.464286 34.817234, -92.464307 34.817229, -92.464328 34.817221, -92.464347 34.817213, -92.464366 34.817202, -92.464384 34.817191, -92.4644 34.817178, -92.464414 34.817164, -92.464428 34.817149, -92.464439 34.817133, -92.464449 34.817117, -92.464457 34.817099, -92.464463 34.817082, -92.464467 34.817063, -92.464469 34.817045, -92.4646 34.814614, -92.4646 34.814581, -92.464597 34.814548, -92.464591 34.814515, -92.464551 34.81435, -92.464565 34.814348, -92.464719 34.814315, -92.46507 34.814225, -92.465252 34.814178, -92.465399 34.814141, -92.465404 34.814234, -92.465441 34.814619, -92.465445 34.814631, -92.465451 34.814642, -92.46546 34.814652, -92.46547 34.81466, -92.465482 34.814667, -92.465495 34.814672, -92.465512 34.814676, -92.467133 34.814724, -92.467136 34.814652, -92.467145 34.814479, -92.46715 34.814378, -92.467121 34.814378, -92.4671 34.814376, -92.467057 34.814367, -92.467017 34.814351, -92.466982 34.814329, -92.466953 34.814301, -92.466931 34.81427, -92.466918 34.814235, -92.466914 34.814212, -92.46692 34.814104, -92.466921 34.814056, -92.466818 34.814059, -92.466415 34.814072, -92.466321 34.814068, -92.466153 34.814056, -92.466007 34.81404, -92.465902 34.814023, -92.465747 34.814003, -92.465682 34.813997, -92.465645 34.813992, -92.465629 34.813987, -92.465596 34.81397, -92.465568 34.813947, -92.46555 34.813925, -92.465533 34.813884, -92.4656 34.81386, -92.465651 34.813849, -92.46579 34.813815, -92.465925 34.813773, -92.465993 34.813749, -92.466172 34.813679, -92.466286 34.813634, -92.46662 34.813499, -92.46664 34.813489, -92.46667 34.813538, -92.466765 34.813707, -92.467045 34.813553, -92.467167 34.813493, -92.467157 34.813544, -92.467314 34.813754, -92.467356 34.813767, -92.46738 34.81377, -92.467725 34.81378, -92.468029 34.813788, -92.468288 34.813787, -92.468953 34.813788, -92.469224 34.813797, -92.469322 34.813799, -92.469537 34.814177, -92.469867 34.814756, -92.470761 34.814266, -92.470764 34.814201, -92.470807 34.813499, -92.470841 34.813041, -92.470883 34.812344, -92.470899 34.812073, -92.4709 34.812052, -92.470921 34.811638, -92.471078 34.809083, -92.471099 34.808908, -92.471162 34.808786, -92.471872 34.808986, -92.472376 34.809136, -92.473139 34.809361, -92.473468 34.809456, -92.473607 34.809496, -92.474012 34.809613, -92.473967 34.809694, -92.473932 34.809784, -92.47393 34.809871, -92.473892 34.8104, -92.47388 34.810771, -92.473868 34.810923, -92.473856 34.8114, -92.473819 34.812236, -92.473816 34.812305, -92.473794 34.81282, -92.473788 34.812918, -92.473767 34.813269, -92.473759 34.813647, -92.473734 34.81404, -92.473732 34.814171, -92.473738 34.81424, -92.473751 34.814285, -92.473775 34.814325, -92.473852 34.814365, -92.473897 34.814372, -92.474156 34.814383, -92.474277 34.814388, -92.47429 34.814383, -92.474405 34.814312, -92.474485 34.814247, -92.474564 34.814164, -92.474628 34.814073, -92.47467 34.813988, -92.474703 34.813886, -92.474726 34.813725, -92.474762 34.813577, -92.474801 34.813474, -92.474861 34.813354, -92.474947 34.813232, -92.474979 34.813187, -92.475074 34.813085, -92.475256 34.812927, -92.476593 34.811768, -92.476612 34.811751, -92.476713 34.811651, -92.476813 34.811525, -92.476883 34.811409, -92.476946 34.811268, -92.476983 34.811142, -92.477004 34.811014, -92.477036 34.810279, -92.478167 34.810372, -92.479918 34.810511, -92.480143 34.810529, -92.481431 34.81063, -92.481967 34.810673, -92.483231 34.810773, -92.483648 34.810806, -92.48418 34.810848, -92.484632 34.810883, -92.484984 34.810909, -92.485425 34.810943, -92.485875 34.810961, -92.486525 34.810961, -92.487095 34.810951, -92.487353 34.810946, -92.487806 34.810952, -92.487864 34.810956, -92.488108 34.810971, -92.488367 34.811, -92.488674 34.811047, -92.489095 34.811112, -92.489165 34.81112, -92.489206 34.811125, -92.489471 34.81117, -92.492007 34.811581, -92.492188 34.811609, -92.492984 34.811733, -92.493656 34.811828, -92.495309 34.812061, -92.495349 34.812067, -92.495486 34.812086, -92.495908 34.812146, -92.496221 34.812194, -92.49664 34.812276, -92.497028 34.812365, -92.497305 34.812438, -92.497408 34.812469, -92.497479 34.812491, -92.497708 34.812561, -92.497984 34.812655, -92.49803 34.812665, -92.498529 34.812839, -92.49877 34.81292, -92.49886 34.81295, -92.499182 34.813051, -92.49967 34.813179, -92.500145 34.813279, -92.50055 34.81335, -92.501062 34.813439, -92.501424 34.813514, -92.501788 34.813607, -92.502198 34.813735, -92.502496 34.813843, -92.502784 34.813956, -92.505432 34.815077, -92.506395 34.815484, -92.506925 34.815719, -92.507347 34.815914, -92.50743 34.815953, -92.507507 34.815989, -92.507825 34.816137, -92.508176 34.816301, -92.508714 34.816552, -92.508891 34.816634, -92.509043 34.816426, -92.509243 34.816504, -92.510404 34.817028, -92.511531 34.817454, -92.512695 34.817801, -92.512721 34.817805, -92.512694 34.817211, -92.512692 34.817157, -92.512687 34.817049, -92.512672 34.816699, -92.514793 34.816738, -92.514798 34.816695, -92.514907 34.815817, -92.514972 34.814996, -92.515013 34.814997, -92.51932 34.81516, -92.51944 34.811568, -92.521491 34.811635, -92.521583 34.808112, -92.51729 34.80801, -92.516539 34.808013, -92.515066 34.807957, -92.515122 34.80532, -92.497384 34.804997, -92.497367 34.80383, -92.493214 34.803773, -92.493007 34.803762, -92.49305 34.80195, -92.489616 34.801882, -92.488646 34.801871, -92.488677 34.80103, -92.488695 34.800077, -92.48429 34.799997, -92.48429 34.799977, -92.484313 34.799171, -92.484313 34.799107, -92.482107 34.799064, -92.482128 34.798171, -92.480085 34.798126, -92.480182 34.794513, -92.475826 34.794398, -92.475826 34.792601, -92.478276 34.792657, -92.47863 34.792665, -92.480239 34.792703, -92.480324 34.789506, -92.480335 34.789122, -92.480543 34.789126, -92.480611 34.789127, -92.480535 34.789054, -92.480472 34.788969, -92.480444 34.788931, -92.480422 34.788889, -92.480393 34.788801, -92.480376 34.788695, -92.480604 34.788708, -92.481314 34.788777, -92.48308 34.788952, -92.483413 34.788974, -92.483747 34.788986, -92.484267 34.788986, -92.485358 34.788955, -92.485691 34.788955, -92.48693 34.788974, -92.488121 34.788993, -92.488325 34.789001, -92.489256 34.789091, -92.490709 34.789232, -92.490818 34.789241, -92.490873 34.789245, -92.490928 34.789247, -92.490984 34.789249, -92.491039 34.78925, -92.491095 34.78925, -92.49115 34.78925, -92.491206 34.789248, -92.491261 34.789246, -92.491317 34.789243, -92.491372 34.789239, -92.491427 34.789235, -92.491482 34.78923, -92.491537 34.789223, -92.491592 34.789216, -92.491647 34.789209, -92.49227 34.789117, -92.492421 34.789094, -92.495188 34.788683, -92.495247 34.788673, -92.495331 34.788656, -92.495415 34.788635, -92.495442 34.788628, -92.495497 34.788611, -92.495577 34.788583, -92.49563 34.788563, -92.495707 34.788529, -92.495757 34.788505, -92.495806 34.788479, -92.495854 34.788452, -92.495912 34.788418, -92.495969 34.788379, -92.496013 34.788347, -92.496055 34.788315, -92.496096 34.78828, -92.496135 34.788245, -92.496173 34.788209, -92.497084 34.787306, -92.497979 34.786417, -92.498014 34.786381, -92.498044 34.786345, -92.49808 34.786296, -92.498097 34.786271, -92.49812 34.786232, -92.49814 34.786192, -92.498169 34.786124, -92.498182 34.786083, -92.498193 34.78604, -92.498203 34.785984, -92.498208 34.785941, -92.498209 34.785912, -92.498209 34.785869, -92.498205 34.785811, -92.498196 34.785754, -92.498186 34.785712, -92.498173 34.78567, -92.498152 34.785615, -92.498127 34.785562, -92.498104 34.785523, -92.498071 34.785472, -92.498043 34.785435, -92.498013 34.7854, -92.497981 34.785366, -92.497935 34.785323, -92.497898 34.785292, -92.497872 34.785273, -92.497859 34.785264, -92.497832 34.785245, -92.497465 34.785005, -92.497077 34.78475, -92.497043 34.784727, -92.497003 34.784698, -92.496946 34.784681, -92.496928 34.784638, -92.496875 34.78459, -92.496826 34.78454, -92.496794 34.784506, -92.496764 34.78447, -92.496722 34.784416, -92.496684 34.784359, -92.496649 34.784301, -92.496618 34.784242, -92.496583 34.784161, -92.496561 34.784099, -92.496542 34.784036, -92.496528 34.783972, -92.496521 34.783929, -92.496512 34.783843, -92.496511 34.7838, -92.496511 34.783778, -92.496512 34.783231, -92.496513 34.783156, -92.496512 34.783104, -92.496509 34.783049, -92.4965 34.782967, -92.496491 34.782913, -92.496466 34.782805, -92.49645 34.782752, -92.496422 34.782672, -92.496389 34.782595, -92.496365 34.782544, -92.496338 34.782493, -92.496294 34.782419, -92.496262 34.782371, -92.496228 34.782324, -92.496192 34.782278, -92.496154 34.782233, -92.495096 34.781018, -92.495062 34.780979, -92.495035 34.780949, -92.495014 34.780927, -92.494972 34.780884, -92.494929 34.780843, -92.494883 34.780803, -92.494836 34.780764, -92.494787 34.780727, -92.494737 34.780691, -92.494685 34.780656, -92.494632 34.780623, -92.494577 34.780592, -92.494549 34.780577, -92.494521 34.780562, -92.492347 34.779446, -92.492286 34.779413, -92.492246 34.779392, -92.492149 34.779335, -92.490639 34.778424, -92.490525 34.778354, -92.490435 34.778297, -92.490389 34.778267, -92.490344 34.778236, -92.4903 34.778205, -92.490256 34.778173, -92.488643 34.776968, -92.488628 34.776957, -92.488604 34.77694, -92.488578 34.776924, -92.488552 34.776908, -92.488526 34.776894, -92.488499 34.77688, -92.488457 34.77686, -92.488414 34.776843, -92.48837 34.776827, -92.488325 34.776813, -92.488279 34.776801, -92.488233 34.776792, -92.488186 34.776784, -92.488139 34.776778, -92.488123 34.776776, -92.488091 34.776774, -92.488059 34.776773, -92.487134 34.776748, -92.487108 34.776747, -92.487092 34.776747, -92.487076 34.776748, -92.48706 34.776748, -92.487044 34.776749, -92.487028 34.77675, -92.487012 34.776751, -92.486996 34.776753, -92.486981 34.776755, -92.486965 34.776757, -92.486945 34.77676, -92.486191 34.77687, -92.486173 34.776872, -92.486157 34.776874, -92.486141 34.776876, -92.486125 34.77688, -92.486109 34.776879, -92.486093 34.77688, -92.486077 34.77688, -92.486061 34.776881, -92.486045 34.776881, -92.486029 34.776881, -92.486013 34.776881, -92.485997 34.776881, -92.485981 34.776884, -92.485963 34.77688, -92.48595 34.776878, -92.485934 34.776877, -92.485918 34.776875, -92.485902 34.776874, -92.485886 34.776872, -92.48587 34.776869, -92.485855 34.776867, -92.485839 34.776864, -92.485823 34.776861, -92.484804 34.776665, -92.484784 34.776661, -92.484768 34.776659, -92.484752 34.776656, -92.484736 34.776654, -92.484721 34.776652, -92.484705 34.776651, -92.484686 34.776649, -92.484673 34.776648, -92.484657 34.776647, -92.484641 34.776646, -92.484625 34.776646, -92.483852 34.776625, -92.482279 34.776584, -92.481819 34.776572, -92.481792 34.776571, -92.481776 34.776571, -92.48176 34.776572, -92.481744 34.776572, -92.481728 34.776573, -92.481712 34.776574, -92.481696 34.776575, -92.481681 34.776577, -92.481665 34.776578, -92.481649 34.77658, -92.481633 34.776582, -92.481617 34.776585, -92.481602 34.776587, -92.481586 34.77659, -92.481571 34.776593, -92.481555 34.776596, -92.48154 34.7766, -92.481524 34.776604, -92.481512 34.776608, -92.481494 34.776612, -92.481479 34.776616, -92.481466 34.776623, -92.481449 34.776625, -92.481434 34.77663, -92.480849 34.776834, -92.480826 34.776842, -92.480801 34.776851, -92.480786 34.776855, -92.48077 34.776859, -92.480758 34.776863, -92.48074 34.776868, -92.480725 34.776872, -92.48071 34.776875, -92.480694 34.776879, -92.480679 34.776882, -92.480663 34.776885, -92.480647 34.776887, -92.480632 34.77689, -92.480616 34.776892, -92.480599 34.776894, -92.480568 34.776897, -92.480536 34.776899, -92.480504 34.7769, -92.480472 34.776901, -92.48044 34.776901, -92.480409 34.776899, -92.480377 34.776897, -92.480345 34.776894, -92.480313 34.77689, -92.480282 34.776885, -92.480251 34.776879, -92.48022 34.776872, -92.480189 34.776864, -92.480159 34.776856, -92.480129 34.776846, -92.4801 34.776836, -92.480085 34.776831, -92.480073 34.776827, -92.480057 34.776819, -92.480042 34.776813, -92.480014 34.7768, -92.479991 34.776789, -92.47996 34.776773, -92.479934 34.776757, -92.479908 34.776742, -92.479883 34.776725, -92.479859 34.776708, -92.479836 34.77669, -92.479813 34.776671, -92.479781 34.776642, -92.47976 34.776622, -92.47974 34.776601, -92.479713 34.776569, -92.479695 34.776547, -92.479679 34.776524, -92.479663 34.776501, -92.479648 34.776478, -92.479635 34.776454, -92.479622 34.77643, -92.479611 34.776405, -92.4796 34.77638, -92.479591 34.776355, -92.479583 34.77633, -92.479578 34.776312, -92.479571 34.776286, -92.479564 34.776252, -92.479562 34.776239, -92.479496 34.775811, -92.479455 34.775508, -92.479436 34.77539, -92.479409 34.775257, -92.479398 34.775169, -92.479378 34.775078, -92.479334 34.774942, -92.479283 34.774821, -92.479138 34.774539, -92.478994 34.774285, -92.47901 34.774271, -92.479087 34.774161, -92.479224 34.774092, -92.479294 34.77406, -92.479367 34.774033, -92.479442 34.77401, -92.479787 34.773919, -92.47988 34.773895, -92.47996 34.77387, -92.480038 34.77384, -92.480112 34.773804, -92.480183 34.773764, -92.480652 34.773467, -92.480704 34.773434, -92.480774 34.773394, -92.480847 34.773359, -92.480924 34.773329, -92.481003 34.773304, -92.481085 34.773286, -92.481169 34.773273, -92.481523 34.77323, -92.48158 34.773221, -92.481635 34.773209, -92.481689 34.773192, -92.481741 34.773171, -92.48179 34.773147, -92.481837 34.77312, -92.481881 34.773089, -92.481921 34.773055, -92.481958 34.773019, -92.481991 34.772979, -92.48202 34.772938, -92.482173 34.772693, -92.482146 34.772686, -92.482091 34.772675, -92.480485 34.772427, -92.480392 34.772417, -92.480298 34.772413, -92.480204 34.772417, -92.48011 34.772428, -92.480019 34.772447, -92.47993 34.772472, -92.479031 34.77277, -92.478968 34.772793, -92.478909 34.77282, -92.478852 34.772851, -92.478799 34.772886, -92.478749 34.772924, -92.478598 34.773049, -92.478472 34.772872, -92.478309 34.772467, -92.47824 34.772116, -92.4782 34.771618, -92.47828 34.771046, -92.478697 34.770405, -92.479614 34.769242, -92.479693 34.768999, -92.479675 34.768303, -92.479513 34.768175, -92.479435 34.768113, -92.47924 34.767953, -92.479193 34.767915, -92.478866 34.767657, -92.478829 34.767628, -92.47837 34.767259, -92.478106 34.767057, -92.477617 34.766671, -92.477187 34.766341, -92.476478 34.765782, -92.475953 34.765362, -92.475681 34.765152, -92.475416 34.764933, -92.4749 34.764522, -92.474387 34.764127, -92.473794 34.763655, -92.473743 34.763615, -92.473214 34.763608, -92.473199 34.763607, -92.47313 34.763606, -92.473115 34.763606, -92.472838 34.763602, -92.472702 34.7636, -92.472524 34.763597, -92.472435 34.763595, -92.472407 34.763596, -92.472379 34.764367, -92.47122 34.763825, -92.470818 34.763652, -92.470042 34.763394, -92.468939 34.762987, -92.468061 34.762618, -92.468053 34.763404, -92.464742 34.763341, -92.464771 34.762152, -92.464779 34.761827, -92.464061 34.761802, -92.464096 34.760102, -92.463563 34.760087, -92.463292 34.760079, -92.462803 34.760057, -92.462652 34.760039, -92.462485 34.760008, -92.462159 34.759932, -92.461587 34.759795, -92.46106 34.759682, -92.460742 34.759623, -92.460724 34.760921, -92.460757 34.761053, -92.460924 34.761273, -92.460937 34.761344, -92.46085 34.761405, -92.460714 34.761304, -92.460027 34.761238, -92.459651 34.761249, -92.459605 34.761262, -92.459432 34.761323, -92.459185 34.761312, -92.458985 34.761257, -92.458692 34.761261, -92.458026 34.761221, -92.457371 34.761115, -92.45671 34.760921, -92.456249 34.760699, -92.455434 34.760361, -92.454968 34.760253, -92.454909 34.76024, -92.454918 34.759439, -92.454812 34.75944, -92.45483 34.758648, -92.454831 34.758622, -92.454858 34.75855, -92.454892 34.758452, -92.455213 34.758499, -92.455239 34.757725, -92.4556 34.757799, -92.45563 34.756886, -92.455024 34.756766, -92.455027 34.756263, -92.455039 34.755743, -92.454542 34.755735, -92.453626 34.75572, -92.451107 34.755673, -92.450885 34.755669, -92.446671 34.755602, -92.446366 34.7556, -92.446382 34.755043, -92.444979 34.755027, -92.444708 34.755019, -92.444597 34.755016, -92.444483 34.754937, -92.444462 34.754922, -92.444092 34.754679, -92.443966 34.754596, -92.44385 34.754541, -92.443709 34.754485, -92.4432 34.754346, -92.443103 34.754325, -92.44304 34.754311, -92.442845 34.754268, -92.442429 34.754183, -92.442265 34.754152, -92.441839 34.754071, -92.441359 34.753985, -92.441276 34.753967, -92.441111 34.753914, -92.441 34.753869, -92.440941 34.753839, -92.440818 34.753763, -92.439546 34.752768, -92.439368 34.752598, -92.439045 34.75224, -92.438682 34.751821, -92.43865 34.751785, -92.438917 34.751768, -92.439773 34.751772, -92.441084 34.751779, -92.441762 34.751784, -92.442257 34.751789, -92.442309 34.751789, -92.442308 34.751758, -92.44232 34.750727, -92.440209 34.750664, -92.440204 34.750773, -92.440075 34.75077, -92.438258 34.750722, -92.437705 34.750713, -92.437512 34.750493, -92.436782 34.749664, -92.436696 34.749563, -92.43666 34.74952, -92.436069 34.74884, -92.435994 34.748753, -92.435617 34.748318, -92.435371 34.748034, -92.435055 34.747668, -92.43491 34.747523, -92.434796 34.747427, -92.434641 34.747309, -92.434503 34.747216, -92.434286 34.747087, -92.433821 34.746821, -92.432552 34.746084, -92.431947 34.745733, -92.43187 34.745688, -92.431656 34.745568, -92.430748 34.745038, -92.430396 34.744832, -92.430347 34.744804, -92.430142 34.744687, -92.429824 34.744505, -92.429674 34.744425, -92.429347 34.744286, -92.429354 34.744117, -92.429466 34.74409, -92.429542 34.744079, -92.429655 34.744075, -92.430992 34.744113, -92.431875 34.744138, -92.432141 34.744145, -92.432716 34.744157, -92.433619 34.744184, -92.43376 34.744185, -92.433815 34.744186, -92.433941 34.744182, -92.434069 34.74417, -92.434221 34.744146, -92.434391 34.744106, -92.434522 34.744063, -92.434672 34.743998, -92.434757 34.743953, -92.435028 34.743784, -92.435485 34.743474, -92.435954 34.743163, -92.436177 34.743033, -92.43651 34.742875, -92.436651 34.742816, -92.437216 34.742586, -92.437367 34.742516, -92.437477 34.74245, -92.437523 34.742407, -92.437668 34.742238, -92.437775 34.742088, -92.437891 34.741892, -92.438002 34.741676, -92.438224 34.741295, -92.438435 34.74147, -92.438501 34.741498, -92.438568 34.741514, -92.438681 34.741503, -92.439007 34.741333, -92.439061 34.741289, -92.439467 34.741108, -92.439567 34.741086, -92.43968 34.741086, -92.43986 34.741185, -92.440086 34.741267, -92.440386 34.741339, -92.440772 34.741404, -92.440852 34.741498, -92.441032 34.742026, -92.441185 34.742619, -92.441218 34.742834, -92.441671 34.743114, -92.441871 34.743274, -92.441951 34.743373, -92.442091 34.74346, -92.442211 34.743516, -92.442485 34.743706, -92.442497 34.743715, -92.442557 34.743757, -92.44295 34.743961, -92.443256 34.744137, -92.443356 34.744214, -92.443469 34.744379, -92.443575 34.744487, -92.444461 34.744517, -92.446612 34.744594, -92.446622 34.744248, -92.446615 34.744083, -92.44664 34.743542, -92.446674 34.742126, -92.446713 34.740815, -92.446754 34.740488, -92.4468 34.739033, -92.446816 34.738513, -92.446875 34.736615, -92.446902 34.735586, -92.446922 34.733818, -92.446979 34.732659, -92.447008 34.731444, -92.447053 34.73018, -92.446809 34.730172, -92.444412 34.730099, -92.442894 34.730052, -92.442919 34.729866, -92.442902 34.729891, -92.442873 34.729939, -92.442853 34.729987, -92.442845 34.730037, -92.442846 34.730082, -92.442854 34.73012, -92.442868 34.730157, -92.442921 34.730234, -92.442961 34.730274, -92.443008 34.730312, -92.443063 34.730348, -92.443307 34.73049, -92.443676 34.730717, -92.443887 34.730859, -92.444048 34.730959, -92.444226 34.731061, -92.444719 34.731354, -92.444742 34.731371, -92.444808 34.73142, -92.444916 34.731519, -92.44499 34.731609, -92.445049 34.731697, -92.445066 34.731759, -92.445082 34.731875, -92.445065 34.732039, -92.444998 34.732323, -92.444991 34.732351, -92.444923 34.732554, -92.444857 34.732691, -92.444814 34.732751, -92.444762 34.732812, -92.44468 34.73289, -92.444631 34.732925, -92.444562 34.732963, -92.444448 34.733008, -92.44438 34.733026, -92.444244 34.733048, -92.444119 34.733057, -92.443186 34.733039, -92.443135 34.733034, -92.443045 34.733007, -92.44302 34.732994, -92.442863 34.733177, -92.44259 34.733535, -92.442485 34.733652, -92.442385 34.733736, -92.442239 34.733844, -92.442117 34.73394, -92.442042 34.734008, -92.441937 34.734126, -92.441891 34.734197, -92.441857 34.734267, -92.441829 34.734346, -92.441821 34.73443, -92.44183 34.734503, -92.441847 34.734557, -92.441881 34.734629, -92.441935 34.734722, -92.44204 34.734877, -92.442099 34.734987, -92.442124 34.735064, -92.442131 34.735105, -92.442131 34.735201, -92.4421 34.735434, -92.442061 34.735653, -92.442037 34.735729, -92.442014 34.73578, -92.44196 34.73587, -92.441914 34.735924, -92.441854 34.735981, -92.441723 34.736087, -92.441077 34.736539, -92.440928 34.736631, -92.440695 34.736759, -92.440589 34.736811, -92.440479 34.736868, -92.440397 34.736959, -92.440322 34.737074, -92.440265 34.73721, -92.440237 34.737292, -92.440222 34.737331, -92.440186 34.737431, -92.440142 34.737556, -92.440095 34.73773, -92.440061 34.73792, -92.440044 34.738059, -92.440024 34.738269, -92.44002 34.738493, -92.440041 34.738826, -92.440081 34.739023, -92.440207 34.739464, -92.44029 34.739728, -92.440141 34.739766, -92.440035 34.739796, -92.439927 34.73983, -92.439831 34.739865, -92.439738 34.739902, -92.439645 34.739941, -92.439555 34.739984, -92.439477 34.740024, -92.439197 34.740188, -92.43912 34.740241, -92.439046 34.740296, -92.438973 34.740353, -92.438915 34.740403, -92.438836 34.740473, -92.438771 34.740535, -92.438548 34.740809, -92.438469 34.740806, -92.43805 34.740799, -92.437489 34.740788, -92.430332 34.740572, -92.429386 34.740536, -92.429386 34.742067, -92.429265 34.741943, -92.429191 34.741844, -92.429058 34.741745, -92.428938 34.741607, -92.428532 34.741267, -92.428119 34.741019, -92.427625 34.740797, -92.427347 34.740673, -92.4272 34.740508, -92.42708 34.740436, -92.426887 34.740359, -92.426681 34.740332, -92.426441 34.740387, -92.426148 34.740486, -92.425828 34.740508, -92.425735 34.740464, -92.425629 34.740381, -92.425476 34.7402, -92.425362 34.740156, -92.425163 34.740029, -92.425056 34.739919, -92.424982 34.739909, -92.42495 34.739931, -92.424896 34.740095, -92.424796 34.740106, -92.42477 34.740084, -92.424756 34.739777, -92.424683 34.739557, -92.42449 34.739353, -92.424261 34.739194, -92.42423 34.739172, -92.423878 34.739018, -92.423285 34.738814, -92.423252 34.738825, -92.423218 34.73888, -92.423245 34.739045, -92.423172 34.739106, -92.423072 34.739265, -92.423012 34.739298, -92.422945 34.739205, -92.422766 34.738792, -92.422632 34.738715, -92.422779 34.738441, -92.422313 34.738122, -92.4222 34.737973, -92.422106 34.737737, -92.422064 34.73768, -92.422033 34.737638, -92.421847 34.737456, -92.421627 34.737308, -92.421454 34.737143, -92.421321 34.736967, -92.421248 34.736901, -92.420928 34.736703, -92.420797 34.736665, -92.425487 34.736738, -92.426645 34.736421, -92.426662 34.736442, -92.426716 34.736472, -92.426758 34.736486, -92.42716 34.736586, -92.427601 34.736696, -92.427643 34.736716, -92.427655 34.736719, -92.427696 34.736728, -92.427745 34.736739, -92.427803 34.736754, -92.427865 34.736767, -92.427933 34.736781, -92.428007 34.736793, -92.428088 34.736801, -92.428177 34.736805, -92.42827 34.736808, -92.428364 34.736815, -92.428455 34.736826, -92.428545 34.736836, -92.428637 34.736848, -92.428731 34.736859, -92.428827 34.736868, -92.428922 34.736877, -92.429018 34.736887, -92.429116 34.736896, -92.429213 34.73691, -92.429308 34.73692, -92.429495 34.73694, -92.429585 34.736953, -92.429679 34.736967, -92.429696 34.736969, -92.429718 34.736395, -92.429742 34.735782, -92.429743 34.735767, -92.429409 34.735727, -92.429255 34.735704, -92.428195 34.735995, -92.428178 34.735898, -92.428143 34.735836, -92.428128 34.735823, -92.428082 34.735784, -92.428002 34.735762, -92.427922 34.735768, -92.427858 34.735796, -92.427817 34.735838, -92.427799 34.735936, -92.427422 34.735481, -92.427378 34.735456, -92.427311 34.735413, -92.42722 34.73535, -92.427172 34.735314, -92.42712 34.735275, -92.427062 34.735237, -92.426997 34.7352, -92.426925 34.735166, -92.426764 34.735106, -92.426593 34.735049, -92.426506 34.735021, -92.426417 34.734994, -92.42633 34.734968, -92.426243 34.734941, -92.426156 34.734914, -92.426071 34.734888, -92.425987 34.734861, -92.425903 34.734834, -92.425821 34.734807, -92.425739 34.734779, -92.425581 34.734722, -92.425505 34.734693, -92.425431 34.734663, -92.425291 34.7346, -92.425221 34.734567, -92.425154 34.734531, -92.425093 34.734493, -92.425037 34.734455, -92.424983 34.734416, -92.42493 34.734376, -92.42488 34.734332, -92.424826 34.734287, -92.424711 34.734193, -92.424581 34.734102, -92.42451 34.734054, -92.424435 34.734006, -92.424359 34.733959, -92.424282 34.733914, -92.424124 34.733841, -92.424045 34.733819, -92.423912 34.733795, -92.423831 34.733803, -92.423811 34.733801, -92.423702 34.733794, -92.423457 34.733791, -92.423446 34.733791, -92.423434 34.73379, -92.423422 34.73379, -92.42341 34.733789, -92.423398 34.733788, -92.423386 34.733787, -92.423374 34.733786, -92.423363 34.733785, -92.423351 34.733783, -92.423339 34.733782, -92.423327 34.73378, -92.423315 34.733778, -92.423304 34.733776, -92.423292 34.733774, -92.42328 34.733772, -92.423269 34.73377, -92.423257 34.733767, -92.423246 34.733765, -92.423234 34.733762, -92.423223 34.733759, -92.423211 34.733756, -92.4232 34.733753, -92.423189 34.73375, -92.423177 34.733747, -92.423166 34.733744, -92.423155 34.73374, -92.423144 34.733736, -92.423133 34.733733, -92.423122 34.733729, -92.423111 34.733725, -92.4231 34.733721, -92.42309 34.733716, -92.423079 34.733712, -92.423068 34.733708, -92.423058 34.733703, -92.423047 34.733698, -92.423037 34.733694, -92.423026 34.733689, -92.423016 34.733684, -92.423006 34.733678, -92.422996 34.733673, -92.422986 34.733668, -92.422976 34.733662, -92.422966 34.733657, -92.422956 34.733651, -92.422947 34.733645, -92.422937 34.73364, -92.422928 34.733634, -92.422918 34.733627, -92.422909 34.733621, -92.4229 34.733615, -92.422891 34.733609, -92.422882 34.733602, -92.422873 34.733596, -92.422721 34.73348, -92.422712 34.733473, -92.422702 34.733466, -92.422692 34.733459, -92.422682 34.733452, -92.422672 34.733446, -92.422662 34.733439, -92.422652 34.733433, -92.422642 34.733427, -92.422631 34.73342, -92.422621 34.733414, -92.422604 34.733405, -92.422716 34.733227, -92.422727 34.733209, -92.422742 34.733184, -92.422755 34.733158, -92.422767 34.733132, -92.422778 34.733106, -92.422788 34.733079, -92.422797 34.733052, -92.422805 34.733025, -92.422811 34.732997, -92.422814 34.732984, -92.422818 34.73296, -92.422821 34.732934, -92.422825 34.732904, -92.422842 34.732909, -92.423439 34.733097, -92.423547 34.733117, -92.423745 34.733129, -92.424041 34.733129, -92.424267 34.73312, -92.424425 34.733135, -92.424875 34.733145, -92.425035 34.733144, -92.425142 34.73313, -92.425217 34.733101, -92.425248 34.733082, -92.425753 34.732693, -92.425939 34.732542, -92.426003 34.732401, -92.42602 34.732374, -92.426063 34.732333, -92.42613 34.732283, -92.426311 34.732187, -92.42646 34.732126, -92.426594 34.73208, -92.427149 34.731769, -92.426995 34.731679, -92.426929 34.731657, -92.426423 34.731322, -92.426336 34.731278, -92.42609 34.731212, -92.42591 34.73113, -92.42583 34.731124, -92.425747 34.731165, -92.425717 34.731179, -92.42565 34.731174, -92.42555 34.731086, -92.425457 34.731036, -92.425377 34.731025, -92.424745 34.731129, -92.424418 34.731162, -92.423972 34.731278, -92.423679 34.731322, -92.423579 34.731569, -92.423419 34.731745, -92.423293 34.731816, -92.42322 34.731805, -92.42316 34.731772, -92.42306 34.73158, -92.422967 34.73153, -92.422847 34.731426, -92.422774 34.731322, -92.422707 34.731124, -92.422654 34.731047, -92.422574 34.730843, -92.422448 34.730662, -92.422301 34.730541, -92.421975 34.730513, -92.421777 34.730517, -92.421688 34.730519, -92.421589 34.730541, -92.421529 34.730579, -92.421375 34.730766, -92.421242 34.730772, -92.421016 34.730673, -92.420443 34.730508, -92.420343 34.730464, -92.420024 34.730189, -92.419937 34.729996, -92.419784 34.729705, -92.419711 34.729633, -92.419498 34.729529, -92.419431 34.729512, -92.419292 34.72954, -92.419105 34.729672, -92.419005 34.729688, -92.418806 34.729611, -92.418712 34.729523, -92.418446 34.729386, -92.418446 34.729303, -92.418508 34.729181, -92.418533 34.729133, -92.418533 34.72905, -92.41834 34.72849, -92.418326 34.72838, -92.41836 34.728149, -92.418347 34.72783, -92.418227 34.727627, -92.418127 34.7275, -92.417927 34.727126, -92.417721 34.726923, -92.417594 34.726852, -92.417508 34.726753, -92.417395 34.726384, -92.417315 34.726318, -92.417188 34.726263, -92.416962 34.726082, -92.416802 34.725994, -92.416609 34.725856, -92.416469 34.725719, -92.416196 34.725559, -92.41611 34.725433, -92.41611 34.725405, -92.416143 34.725317, -92.416217 34.725241, -92.41607 34.724817, -92.415944 34.724581, -92.41593 34.724498, -92.41595 34.724449, -92.416004 34.7244, -92.41609 34.724114, -92.416077 34.724004, -92.41609 34.723949, -92.416077 34.723866, -92.415957 34.723762, -92.415831 34.723751, -92.415731 34.723767, -92.415451 34.723905, -92.415285 34.723927, -92.415112 34.72391, -92.415019 34.723822, -92.414965 34.723679, -92.414965 34.723597, -92.415065 34.72341, -92.415112 34.723212, -92.415019 34.722959, -92.414899 34.722849, -92.414799 34.722821, -92.414699 34.722832, -92.414513 34.72292, -92.414273 34.722964, -92.41418 34.723052, -92.41414 34.723157, -92.414127 34.723272, -92.41408 34.723349, -92.413967 34.723415, -92.413827 34.723421, -92.413734 34.723415, -92.413541 34.723239, -92.413461 34.72308, -92.413294 34.722843, -92.413208 34.722656, -92.413208 34.722574, -92.413275 34.722414, -92.413454 34.722189, -92.413521 34.722057, -92.413554 34.721865, -92.413461 34.721694, -92.413341 34.721595, -92.413142 34.72154, -92.412942 34.721546, -92.412802 34.721584, -92.412755 34.721628, -92.412662 34.721667, -92.412522 34.721672, -92.412496 34.721656, -92.412461 34.721621, -92.412429 34.72159, -92.412384 34.721505, -92.412268 34.720714, -92.411574 34.715969, -92.41132 34.71423, -92.41137 34.714203, -92.411425 34.714159, -92.411444 34.714129, -92.411452 34.714087, -92.411444 34.71405, -92.411444 34.713991, -92.411487 34.713898, -92.411544 34.713811, -92.411615 34.71373, -92.411698 34.713657, -92.411733 34.713613, -92.41178 34.713578, -92.411836 34.713553, -92.411897 34.713539, -92.411962 34.713539, -92.41221 34.713555, -92.412264 34.713544, -92.412311 34.713521, -92.412331 34.713506, -92.412489 34.7133, -92.412498 34.713253, -92.412491 34.713207, -92.41247 34.713163, -92.412436 34.713125, -92.41239 34.713096, -92.412365 34.713086, -92.411835 34.712961, -92.411729 34.712952, -92.411623 34.712955, -92.41157 34.712962, -92.411209 34.713041, -92.411198 34.712699, -92.412466 34.712582, -92.41225 34.711597, -92.412927 34.711532, -92.412953 34.711087, -92.414047 34.711113, -92.413972 34.711071, -92.413825 34.710973, -92.413314 34.710619, -92.413003 34.7104, -92.413037 34.709274, -92.413084 34.707732, -92.413198 34.706295, -92.413288 34.705158, -92.413096 34.705155, -92.413089 34.705031, -92.413072 34.704569, -92.413077 34.70405, -92.413097 34.703568, -92.413096 34.703509, -92.413113 34.703246, -92.413137 34.702988, -92.413158 34.702757, -92.413285 34.701533, -92.413326 34.700599, -92.41344 34.700689, -92.414888 34.700736, -92.415111 34.70074, -92.415124 34.700317, -92.415131 34.700163, -92.415076 34.700161, -92.415076 34.700147, -92.414193 34.700136, -92.413464 34.70011, -92.413469 34.699902, -92.413499 34.698707, -92.4135 34.698682, -92.413401 34.698682, -92.413419 34.698232, -92.413418 34.69812, -92.413407 34.697953, -92.413386 34.697844, -92.413351 34.697737, -92.413333 34.697701, -92.413301 34.697635, -92.413238 34.697538, -92.413038 34.697314, -92.412292 34.696643, -92.412222 34.696587, -92.412076 34.696482, -92.41197 34.696425, -92.411913 34.696402, -92.411796 34.696364, -92.411673 34.696339, -92.411584 34.696329, -92.411404 34.696319, -92.408919 34.696295, -92.408581 34.696292, -92.408089 34.696288, -92.406941 34.696282, -92.405929 34.696264, -92.403982 34.696228, -92.404729 34.691676, -92.40497 34.690233, -92.404872 34.690182, -92.404739 34.690056, -92.404626 34.689902, -92.404579 34.689792, -92.404507 34.689226, -92.404527 34.688835, -92.404493 34.688615, -92.40436 34.688445, -92.403988 34.688214, -92.403842 34.687862, -92.403223 34.687384, -92.40321 34.687268, -92.403243 34.687065, -92.403236 34.686949, -92.403157 34.686817, -92.403024 34.686751, -92.402897 34.686647, -92.402658 34.68652, -92.402005 34.686443, -92.401626 34.686361, -92.401387 34.686234, -92.401307 34.686135, -92.40112 34.686042, -92.400987 34.685893, -92.400848 34.685706, -92.400848 34.685739, -92.400588 34.685525, -92.400434 34.685439, -92.400346 34.685505, -92.400227 34.68559, -92.400065 34.685696, -92.399959 34.68576, -92.399404 34.686067, -92.398189 34.68674, -92.397715 34.687, -92.397613 34.68706, -92.397286 34.687266, -92.396761 34.687609, -92.395931 34.688165, -92.395556 34.688422, -92.395034 34.688799, -92.394839 34.688949, -92.394654 34.689108, -92.394335 34.689427, -92.394134 34.68928, -92.394046 34.689192, -92.393927 34.689035, -92.393827 34.688918, -92.393658 34.688772, -92.393495 34.688662, -92.393455 34.688639, -92.39343 34.68863, -92.393163 34.688616, -92.392816 34.688609, -92.392491 34.688584, -92.392223 34.688577, -92.392116 34.68859, -92.391827 34.688624, -92.391842 34.688674, -92.391839 34.688817, -92.391852 34.689019, -92.391853 34.689348, -92.391871 34.689535, -92.39189 34.689619, -92.391928 34.689716, -92.391962 34.68978, -92.392011 34.689843, -92.392117 34.689944, -92.392217 34.690025, -92.392372 34.690136, -92.392584 34.6903, -92.392884 34.69052, -92.393071 34.690647, -92.393124 34.690701, -92.39285 34.691029, -92.392669 34.691272, -92.392527 34.691462, -92.392018 34.692113, -92.391872 34.692283, -92.391794 34.692256, -92.391693 34.692246, -92.390532 34.69222, -92.389649 34.6922, -92.389379 34.692195, -92.387811 34.692157, -92.387293 34.692137, -92.386915 34.692158, -92.386692 34.692163, -92.385967 34.692148, -92.38464 34.69211, -92.384237 34.69209, -92.383631 34.692081, -92.383427 34.692078, -92.383156 34.692061, -92.383019 34.692053, -92.382885 34.692047, -92.382879 34.692193, -92.382836 34.693047, -92.382811 34.693723, -92.38279 34.694124, -92.382749 34.695163, -92.382729 34.695501, -92.38271 34.695641, -92.382622 34.695625, -92.382192 34.69562, -92.381933 34.695608, -92.380174 34.695565, -92.379914 34.695569, -92.379778 34.695576, -92.37968 34.695569, -92.379522 34.695559, -92.377854 34.695519, -92.377746 34.695524, -92.377565 34.695512, -92.377098 34.695499, -92.376513 34.695484, -92.375173 34.695458, -92.375098 34.695456, -92.374634 34.695442, -92.374454 34.695444, -92.374453 34.695539, -92.374424 34.696133, -92.374406 34.696685, -92.374378 34.69728, -92.37438 34.697354, -92.374387 34.697414, -92.374402 34.697473, -92.374429 34.697542, -92.374463 34.697611, -92.374509 34.697678, -92.374626 34.697803, -92.374583 34.697855, -92.374509 34.69793, -92.374427 34.698004, -92.374377 34.698062, -92.374342 34.698113, -92.374317 34.698158, -92.374285 34.698249, -92.374278 34.698302, -92.374278 34.698377, -92.374341 34.698839, -92.374354 34.699014, -92.374351 34.699123, -92.374331 34.699269, -92.374302 34.699388, -92.374252 34.699553, -92.374158 34.699832, -92.374127 34.699942, -92.374329 34.699991, -92.374892 34.700104, -92.37509 34.700137, -92.375499 34.700222, -92.376372 34.700389, -92.376689 34.700457, -92.377333 34.700662, -92.377508 34.700735, -92.378027 34.700916, -92.378298 34.700998, -92.378448 34.701037, -92.378697 34.701081, -92.379035 34.701122, -92.379217 34.701133, -92.379608 34.701145, -92.379996 34.701166, -92.380322 34.701179, -92.380602 34.7012, -92.380852 34.701231, -92.38124 34.701301, -92.381418 34.701326, -92.381487 34.701341, -92.381621 34.701384, -92.381827 34.701461, -92.382025 34.701547, -92.382211 34.701638, -92.382686 34.701893, -92.382954 34.702025, -92.382867 34.70215, -92.382787 34.702278, -92.382658 34.702544, -92.382591 34.702713, -92.382563 34.702795, -92.382493 34.702975, -92.382345 34.703398, -92.381676 34.703339, -92.381437 34.70329, -92.381031 34.703174, -92.380951 34.703158, -92.380937 34.703136, -92.380805 34.702718, -92.380745 34.702608, -92.380632 34.702503, -92.380385 34.702404, -92.380046 34.702349, -92.37954 34.702338, -92.379474 34.702316, -92.379454 34.702277, -92.379287 34.702316, -92.378668 34.702849, -92.378335 34.703085, -92.378115 34.70309, -92.378022 34.703068, -92.377842 34.702986, -92.377563 34.702799, -92.377044 34.702601, -92.376711 34.702501, -92.376318 34.702463, -92.376152 34.702419, -92.375992 34.70227, -92.375906 34.702155, -92.375839 34.702017, -92.375726 34.701902, -92.375287 34.701682, -92.375221 34.701616, -92.375073 34.701545, -92.37478 34.701391, -92.374407 34.701249, -92.374127 34.701177, -92.373921 34.701144, -92.372942 34.700864, -92.372703 34.700821, -92.37253 34.700848, -92.371997 34.701019, -92.371338 34.701036, -92.371092 34.701069, -92.370879 34.701069, -92.3704 34.701157, -92.369721 34.701234, -92.369588 34.701284, -92.369382 34.701317, -92.369235 34.701306, -92.368903 34.701139, -92.368842 34.701108, -92.368436 34.700834, -92.368003 34.700658, -92.367744 34.700471, -92.367204 34.700262, -92.366785 34.700048, -92.366545 34.699894, -92.366392 34.699713, -92.366312 34.699647, -92.366199 34.699521, -92.366066 34.699411, -92.365913 34.699323, -92.365646 34.699208, -92.364681 34.698988, -92.364248 34.698824, -92.364002 34.698631, -92.363909 34.698527, -92.363596 34.698104, -92.363482 34.697977, -92.363296 34.697823, -92.36289 34.69756, -92.362464 34.69734, -92.361931 34.697131, -92.361771 34.697038, -92.361678 34.696917, -92.361551 34.696769, -92.361432 34.696681, -92.361338 34.696631, -92.361065 34.696565, -92.361277 34.696295, -92.361293 34.696262, -92.361371 34.696032, -92.361378 34.695675, -92.361431 34.69551, -92.361324 34.695257, -92.361297 34.694773, -92.361364 34.694454, -92.361537 34.694141, -92.361736 34.693893, -92.361783 34.693729, -92.361842 34.693608, -92.361989 34.693388, -92.362155 34.693212, -92.362188 34.693102, -92.362155 34.692931, -92.362062 34.692788, -92.361915 34.69236, -92.361808 34.692167, -92.361766 34.692056, -92.361058 34.691762, -92.361125 34.691664, -92.36105 34.688022, -92.361073 34.687988, -92.361098 34.687924, -92.361106 34.687859, -92.361106 34.68774, -92.36114 34.687081, -92.361139 34.686868, -92.361147 34.686446, -92.361156 34.686329, -92.361166 34.685991, -92.361181 34.685891, -92.361243 34.684321, -92.361233 34.68423, -92.361184 34.684135, -92.361207 34.684112, -92.361219 34.684094, -92.361237 34.683988, -92.361243 34.683717, -92.361261 34.68339, -92.361308 34.682564, -92.361345 34.681614, -92.361371 34.681037, -92.36139 34.680818, -92.361404 34.680761, -92.361455 34.68064, -92.361864 34.680639, -92.36222 34.680647, -92.362514 34.680647, -92.363514 34.680657, -92.363615 34.680673, -92.363729 34.680677, -92.363804 34.680675, -92.364068 34.680686, -92.364464 34.68069, -92.36575 34.680704, -92.36635 34.680714, -92.367093 34.680737, -92.367387 34.680737, -92.367923 34.680752, -92.368468 34.680759, -92.369028 34.680772, -92.369754 34.680793, -92.370518 34.680806, -92.372598 34.680815, -92.37271 34.680805, -92.372818 34.680783, -92.372923 34.680751, -92.372974 34.68073, -92.373815 34.68032, -92.374188 34.680152, -92.374318 34.680088, -92.374383 34.680044, -92.374436 34.679992, -92.374479 34.679932, -92.374507 34.679868, -92.374521 34.6798, -92.374538 34.679571, -92.374562 34.679084, -92.374583 34.679043, -92.374661 34.677589, -92.374675 34.677198, -92.3747 34.676685, -92.374754 34.675654, -92.374767 34.675582, -92.374795 34.675512, -92.374838 34.675448, -92.374892 34.675393, -92.374957 34.675345, -92.376062 34.674826, -92.37635 34.674695, -92.376463 34.674645, -92.376665 34.674581, -92.376817 34.674546, -92.376951 34.674517, -92.377617 34.6744, -92.377863 34.674362, -92.378616 34.67423, -92.378657 34.67422, -92.378806 34.674184, -92.378883 34.674173, -92.379103 34.674124, -92.379187 34.674092, -92.379277 34.674049, -92.379343 34.674011, -92.379397 34.673967, -92.379474 34.673888, -92.379533 34.673814, -92.37957 34.673753, -92.379629 34.673637, -92.379646 34.673584, -92.379661 34.673514, -92.379675 34.673392, -92.379693 34.672857, -92.380129 34.672862, -92.381019 34.672884, -92.383417 34.672936, -92.38445 34.672963, -92.38449 34.672954, -92.384525 34.672934, -92.384551 34.672907, -92.384565 34.672874, -92.384581 34.672491, -92.38466 34.672494, -92.384847 34.672499, -92.384982 34.672534, -92.385128 34.672585, -92.385278 34.672657, -92.38522 34.67277, -92.385176 34.672885, -92.385147 34.673003, -92.385131 34.673123, -92.385122 34.67352, -92.385677 34.673535, -92.386369 34.673559, -92.387007 34.673575, -92.387091 34.673584, -92.387175 34.673582, -92.387258 34.673567, -92.387336 34.673541, -92.387407 34.673503, -92.387465 34.673458, -92.387513 34.673406, -92.387549 34.673348, -92.387573 34.673285, -92.387584 34.67322, -92.387581 34.673155, -92.387565 34.673091, -92.387535 34.67303, -92.38744 34.672911, -92.38725 34.672708, -92.387113 34.672569, -92.38699 34.672419, -92.386958 34.672364, -92.38694 34.672306, -92.386937 34.672276, -92.386981 34.671094, -92.386997 34.670801, -92.387006 34.670466, -92.387014 34.670396, -92.388205 34.670427, -92.388775 34.670442, -92.388993 34.670449, -92.389475 34.670462, -92.391696 34.670525, -92.391679 34.67036, -92.392054 34.666672, -92.392181 34.666342, -92.392181 34.66626, -92.392206 34.666177, -92.391691 34.666367, -92.390266 34.666882, -92.390164 34.666914, -92.389075 34.667312, -92.388358 34.667578, -92.387506 34.667888, -92.384023 34.669161, -92.383945 34.668994, -92.38386 34.668833, -92.385569 34.668211, -92.387622 34.667457, -92.390162 34.666529, -92.390264 34.666495, -92.390975 34.666235, -92.392236 34.665775, -92.39232 34.665745, -92.394648 34.664897, -92.395338 34.664631, -92.397232 34.663934, -92.397989 34.66366, -92.39857 34.663457, -92.399252 34.663202, -92.399845 34.662963, -92.400099 34.662856, -92.400329 34.662754, -92.400849 34.6625, -92.401474 34.66218, -92.401844 34.66198, -92.402158 34.661803, -92.402358 34.661684, -92.40231 34.661482, -92.402274 34.661299, -92.402226 34.660926, -92.402218 34.660749, -92.402218 34.66058, -92.402224 34.660496, -92.402246 34.660363, -92.402289 34.6602, -92.402333 34.660075, -92.402404 34.659913, -92.402497 34.659746, -92.402535 34.65969, -92.402669 34.65952, -92.40273 34.659451, -92.402876 34.659309, -92.403052 34.659173, -92.403172 34.659096, -92.403346 34.658992, -92.403495 34.658914, -92.403602 34.658865, -92.403749 34.65881, -92.403882 34.658769, -92.404044 34.658734, -92.404179 34.65871, -92.4043 34.658694, -92.404522 34.658675, -92.405242 34.658631, -92.405531 34.6586, -92.40565 34.658572, -92.405777 34.65853, -92.405885 34.658499, -92.406117 34.658408, -92.406267 34.658336, -92.406598 34.65813, -92.406839 34.657952, -92.406954 34.657851, -92.407169 34.65767, -92.407352 34.657501, -92.407638 34.657255, -92.408138 34.656812, -92.408664 34.656362, -92.409076 34.656003, -92.409206 34.65589, -92.409637 34.655517, -92.409794 34.65564, -92.411877 34.653831, -92.412296 34.653463, -92.413044 34.652818, -92.413254 34.65263, -92.414476 34.65157, -92.41543 34.650736, -92.415899 34.650338, -92.416195 34.650102, -92.416491 34.64988, -92.416807 34.649658, -92.417027 34.649516, -92.417178 34.649425, -92.417075 34.649274, -92.417057 34.649236, -92.416749 34.648598, -92.416603 34.648417, -92.41643 34.648268, -92.416231 34.648224, -92.415925 34.648279, -92.415758 34.648136, -92.415313 34.647993, -92.415173 34.647982, -92.415 34.647993, -92.41492 34.648026, -92.41484 34.648054, -92.414634 34.648207, -92.414461 34.648273, -92.414248 34.648295, -92.414162 34.648367, -92.414122 34.648422, -92.414046 34.648444, -92.413671 34.648472, -92.413281 34.648473, -92.412743 34.648523, -92.412402 34.648525, -92.412227 34.648441, -92.41214 34.64838, -92.412073 34.648223, -92.412013 34.648152, -92.41182 34.648064, -92.41184 34.648003, -92.411907 34.647943, -92.41188 34.647828, -92.411747 34.647734, -92.411654 34.647696, -92.411548 34.647569, -92.411515 34.647454, -92.411408 34.647344, -92.411402 34.647311, -92.411448 34.647201, -92.411395 34.647063, -92.411335 34.647019, -92.411335 34.646986, -92.411422 34.646854, -92.411408 34.646646, -92.411435 34.646629, -92.411508 34.64664, -92.411748 34.646816, -92.411847 34.646849, -92.411914 34.646833, -92.411967 34.646756, -92.41198 34.646673, -92.411841 34.646265, -92.409177 34.646561, -92.408208 34.646669, -92.407992 34.646696, -92.407231 34.646779, -92.40669 34.646843, -92.406444 34.646871, -92.405645 34.646993, -92.405197 34.647082, -92.404864 34.647161, -92.404362 34.6473, -92.403974 34.647428, -92.403545 34.647583, -92.403117 34.647761, -92.402815 34.647899, -92.402568 34.648022, -92.401988 34.64832, -92.401478 34.6486, -92.401347 34.648668, -92.398739 34.650105, -92.398396 34.65029, -92.397956 34.650532, -92.393999 34.652713, -92.391073 34.654319, -92.390106 34.65485, -92.388907 34.65551, -92.388518 34.655725, -92.388493 34.655694, -92.388266 34.655409, -92.388308 34.655393, -92.38836 34.655367, -92.388612 34.655223, -92.389074 34.654973, -92.389218 34.654888, -92.389229 34.654872, -92.389243 34.654829, -92.389242 34.654811, -92.389225 34.654756, -92.389206 34.65472, -92.389111 34.654591, -92.388717 34.654108, -92.388636 34.654021, -92.388121 34.654304, -92.387683 34.654543, -92.387648 34.654568, -92.38762 34.654602, -92.386962 34.653786, -92.386434 34.653176, -92.386322 34.653052, -92.386289 34.653026, -92.386252 34.653012, -92.386226 34.653007, -92.386203 34.653007, -92.386158 34.653015, -92.386064 34.653061, -92.385268 34.653512, -92.384626 34.652726, -92.3842 34.652212, -92.383982 34.651959, -92.383954 34.651933, -92.38389 34.651885, -92.383817 34.651847, -92.383689 34.651805, -92.383619 34.651792, -92.383535 34.651784, -92.382228 34.651773, -92.382014 34.651766, -92.381755 34.651763, -92.381732 34.651826, -92.381732 34.652036, -92.381754 34.652124, -92.38181 34.652201, -92.38182 34.652227, -92.38182 34.652246, -92.381812 34.652263, -92.381782 34.652271, -92.381727 34.652237, -92.381672 34.652179, -92.381633 34.652106, -92.381605 34.652035, -92.381594 34.651967, -92.381593 34.65176, -92.38095 34.651751, -92.380966 34.651266, -92.38097 34.650969, -92.381043 34.649335, -92.381045 34.649193, -92.381054 34.649035, -92.381066 34.648531, -92.381126 34.647107, -92.381138 34.646901, -92.381161 34.646231, -92.38117 34.645808, -92.381182 34.645502, -92.381236 34.644685, -92.381267 34.643627, -92.381297 34.642881, -92.381311 34.642649, -92.381353 34.641829, -92.381365 34.641479, -92.381366 34.641389, -92.381367 34.641232, -92.381385 34.640955, -92.381382 34.640803, -92.381403 34.640479, -92.381416 34.640069, -92.381433 34.639789, -92.381447 34.63956, -92.38149 34.638291, -92.381496 34.638111, -92.381527 34.637184, -92.381535 34.636953, -92.381548 34.636604, -92.381599 34.635419, -92.381614 34.635086, -92.381617 34.635025, -92.381619 34.634864, -92.381647 34.633763, -92.381649 34.633514, -92.381671 34.632356, -92.381703 34.631811, -92.381911 34.631835, -92.38251 34.631817, -92.382984 34.631803, -92.383514 34.631792, -92.385268 34.631756, -92.385794 34.63174, -92.386768 34.631719, -92.388478 34.631675, -92.38909 34.631656, -92.390117 34.631631, -92.39035 34.631633, -92.390435 34.631626, -92.390478 34.631616, -92.390491 34.63124, -92.390508 34.630899, -92.390566 34.629432, -92.390629 34.628172, -92.390658 34.627466, -92.390675 34.627163, -92.390681 34.627038, -92.3907 34.626652, -92.390707 34.626314, -92.394986 34.626349, -92.397507 34.62637, -92.397713 34.626371, -92.398331 34.626376, -92.398537 34.626378, -92.398723 34.626377, -92.399281 34.626374, -92.399467 34.626373, -92.399752 34.626371, -92.400609 34.626367, -92.400895 34.626366, -92.40145 34.626372, -92.403118 34.626392, -92.403674 34.626399, -92.408257 34.626441, -92.408879 34.626393, -92.408913 34.626393, -92.409152 34.626397, -92.409211 34.626399, -92.409306 34.626401, -92.409755 34.626404, -92.411328 34.626429, -92.412343 34.626444, -92.412736 34.626451, -92.41296 34.626457, -92.413034 34.626468, -92.413073 34.626479, -92.413131 34.626507, -92.413174 34.626514, -92.413236 34.626514, -92.413632 34.626522, -92.413685 34.626513, -92.413795 34.62648, -92.413867 34.62647, -92.414895 34.626485, -92.416881 34.626515, -92.416891 34.626588, -92.417164 34.626595, -92.417235 34.626597, -92.417476 34.626598, -92.418795 34.626618, -92.420385 34.626652, -92.420397 34.626653, -92.420625 34.626658, -92.421226 34.626679, -92.421916 34.626707, -92.422367 34.626707, -92.422367 34.626726, -92.423208 34.626732, -92.423784 34.626746, -92.423949 34.626756, -92.424418 34.626773, -92.424602 34.626785, -92.425189 34.626818, -92.425348 34.626828, -92.42555 34.626841, -92.425659 34.626842, -92.425713 34.626843, -92.425846 34.626825, -92.425887 34.626812, -92.425936 34.626734, -92.42595 34.626712, -92.426553 34.626722, -92.429225 34.626766, -92.432537 34.626821, -92.43305 34.626829, -92.433388 34.626835, -92.433407 34.626835, -92.433412 34.626681, -92.433417 34.626527, -92.433431 34.626077, -92.433865 34.626081, -92.434029 34.626084, -92.434082 34.626085, -92.434152 34.626086, -92.434233 34.626089, -92.434425 34.626094, -92.434424 34.626144, -92.434507 34.62611, -92.434486 34.625642, -92.434682 34.625665, -92.434963 34.625665, -92.435195 34.625756, -92.435307 34.625821, -92.435413 34.625853, -92.435545 34.625849, -92.43572 34.62556, -92.436322 34.625542, -92.436366 34.625742, -92.436381 34.626242, -92.436398 34.626849, -92.436782 34.626855, -92.437189 34.626861, -92.437561 34.626867, -92.438037 34.626874, -92.438077 34.626875, -92.438219 34.626877, -92.439565 34.626898, -92.440357 34.626911, -92.440407 34.626911, -92.440557 34.626913, -92.440607 34.626914, -92.44144 34.626923, -92.441501 34.626925, -92.442915 34.626962, -92.442593 34.627211, -92.442457 34.627323, -92.442288 34.627454, -92.442184 34.627546, -92.442028 34.627675, -92.441829 34.627851, -92.441667 34.628002, -92.44133 34.628298, -92.441162 34.628414, -92.440802 34.628701, -92.440718 34.628765, -92.440553 34.628889, -92.440495 34.628947, -92.440467 34.628983, -92.440454 34.629023, -92.43907 34.63026, -92.439049 34.630242, -92.438965 34.630171, -92.438749 34.630446, -92.438082 34.63122, -92.43799 34.631302, -92.440039 34.632965, -92.440669 34.633476, -92.440702 34.633509, -92.441236 34.633082, -92.441589 34.632789, -92.443299 34.631371, -92.443436 34.63149, -92.444204 34.632134, -92.444349 34.632256, -92.444509 34.63239, -92.444627 34.632509, -92.444682 34.632571, -92.444786 34.6327, -92.444859 34.632804, -92.444892 34.632861, -92.445006 34.633095, -92.445023 34.633131, -92.445057 34.63323, -92.445069 34.633267, -92.445083 34.633314, -92.445087 34.633328, -92.445093 34.633356, -92.445101 34.633403, -92.445106 34.633433, -92.445116 34.633505, -92.445126 34.633562, -92.445135 34.633648, -92.44514 34.633751, -92.445136 34.633844, -92.44512 34.634014, -92.445057 34.634501, -92.444962 34.635259, -92.444904 34.635714, -92.444889 34.635839, -92.44483 34.636306, -92.444781 34.636701, -92.444735 34.637099, -92.444715 34.637323, -92.444691 34.637837, -92.444691 34.637887, -92.444692 34.638013, -92.444683 34.6381, -92.44467 34.638314, -92.444667 34.638461, -92.444658 34.638666, -92.444649 34.638856, -92.44458 34.640425, -92.442317 34.641294, -92.441876 34.641471, -92.441668 34.641549, -92.440749 34.641909, -92.440154 34.642135, -92.4398 34.642282, -92.439571 34.642357, -92.439384 34.642419, -92.438976 34.642579, -92.438791 34.642644, -92.434659 34.644235, -92.434326 34.644369, -92.433524 34.644673, -92.43329 34.644757, -92.432691 34.644956, -92.432406 34.645044, -92.431666 34.645258, -92.430448 34.645542, -92.429252 34.645798, -92.425319 34.646638, -92.425335 34.646848, -92.42537 34.64702, -92.425404 34.647187, -92.425416 34.647224, -92.425583 34.647742, -92.425617 34.648055, -92.425424 34.64839, -92.425317 34.648478, -92.425217 34.648599, -92.424871 34.648803, -92.424645 34.648885, -92.424585 34.648924, -92.424379 34.649001, -92.424066 34.649072, -92.424033 34.649094, -92.423973 34.649314, -92.424053 34.649886, -92.424066 34.650353, -92.423887 34.650947, -92.42388 34.651034, -92.42392 34.651232, -92.423906 34.65137, -92.42386 34.651507, -92.423687 34.651793, -92.423514 34.652183, -92.423381 34.652623, -92.423341 34.652854, -92.423261 34.65309, -92.423134 34.653266, -92.422848 34.653404, -92.422609 34.653591, -92.422542 34.653618, -92.422409 34.65375, -92.422289 34.653992, -92.422303 34.654184, -92.422482 34.654745, -92.422569 34.654882, -92.422748 34.655025, -92.423107 34.655223, -92.42324 34.655355, -92.423387 34.655696, -92.423392 34.655995, -92.423467 34.656185, -92.42346 34.656268, -92.423367 34.656416, -92.42332 34.656532, -92.423254 34.656873, -92.423254 34.657109, -92.423287 34.657274, -92.42334 34.657411, -92.423321 34.657582, -92.4233 34.657774, -92.423273 34.657884, -92.42316 34.658093, -92.423149 34.65813, -92.42306 34.658456, -92.422934 34.658637, -92.422887 34.658835, -92.422834 34.659033, -92.422741 34.659231, -92.422714 34.659462, -92.422767 34.659715, -92.422867 34.659879, -92.42294 34.659934, -92.42298 34.66005, -92.422927 34.660352, -92.422927 34.660545, -92.422967 34.660622, -92.4231 34.660765, -92.42318 34.660957, -92.423166 34.661067, -92.423106 34.661193, -92.423 34.66132, -92.422854 34.661457, -92.422734 34.661633, -92.422428 34.661842, -92.422268 34.661897, -92.422102 34.661924, -92.421882 34.661919, -92.421782 34.661897, -92.421689 34.661842, -92.421583 34.661737, -92.421523 34.661704, -92.42143 34.661671, -92.42129 34.66166, -92.42125 34.661677, -92.42117 34.661776, -92.421011 34.661891, -92.420225 34.662155, -92.419973 34.662281, -92.419899 34.662562, -92.419866 34.662925, -92.419753 34.663205, -92.419766 34.66332, -92.419819 34.663397, -92.419859 34.663441, -92.420006 34.663524, -92.420365 34.663584, -92.420391 34.663606, -92.420538 34.663579, -92.420731 34.663474, -92.420837 34.663463, -92.42095 34.663474, -92.421037 34.663524, -92.42107 34.663568, -92.42113 34.663722, -92.42109 34.664033, -92.420924 34.664157, -92.420679 34.664342, -92.420471 34.664596, -92.420378 34.66459, -92.420318 34.664552, -92.420258 34.664447, -92.420272 34.664348, -92.420265 34.664288, -92.420225 34.664238, -92.420185 34.664222, -92.419979 34.664271, -92.419859 34.664381, -92.419633 34.664662, -92.419526 34.665052, -92.41952 34.665327, -92.419453 34.665569, -92.419406 34.665624, -92.419307 34.665706, -92.419127 34.6658, -92.418954 34.665964, -92.418887 34.665997, -92.418728 34.666162, -92.418581 34.666228, -92.418461 34.666283, -92.418375 34.666377, -92.418315 34.666536, -92.418335 34.666684, -92.418388 34.66675, -92.418521 34.6668, -92.419027 34.666734, -92.4191 34.66674, -92.41918 34.666794, -92.419193 34.667042, -92.419107 34.66735, -92.418973 34.66757, -92.418887 34.667663, -92.418734 34.667751, -92.418601 34.667855, -92.418394 34.667971, -92.418288 34.668009, -92.417969 34.668009, -92.417802 34.668048, -92.417689 34.668125, -92.417596 34.668158, -92.417336 34.668333, -92.417057 34.66857, -92.417017 34.668652, -92.416984 34.668845, -92.416977 34.66896, -92.417004 34.669015, -92.417197 34.669262, -92.417343 34.669339, -92.417476 34.669356, -92.417562 34.6694, -92.417609 34.669554, -92.417589 34.669686, -92.417502 34.669933, -92.417289 34.670301, -92.417176 34.670384, -92.416963 34.670444, -92.416671 34.670681, -92.416551 34.670862, -92.416251 34.671203, -92.416233 34.671239, -92.416198 34.671307, -92.416185 34.67139, -92.416191 34.671786, -92.416171 34.671923, -92.416118 34.672, -92.415739 34.672236, -92.415572 34.67239, -92.415472 34.672429, -92.415399 34.672429, -92.415373 34.672407, -92.415319 34.672242, -92.415246 34.672137, -92.41514 34.672143, -92.414641 34.672428, -92.414501 34.672533, -92.414361 34.67267, -92.414448 34.672852, -92.414467 34.672962, -92.414354 34.673253, -92.414407 34.673335, -92.414614 34.6735, -92.414674 34.673522, -92.414753 34.6735, -92.414966 34.673374, -92.415106 34.673358, -92.415259 34.673429, -92.415572 34.67366, -92.415898 34.673869, -92.415964 34.673891, -92.416005 34.67392, -92.416071 34.673968, -92.415697 34.674403, -92.415546 34.67455, -92.415443 34.674638, -92.415359 34.674696, -92.415268 34.67475, -92.415151 34.674807, -92.414972 34.674876, -92.414887 34.674904, -92.41483 34.674919, -92.414183 34.675079, -92.4114 34.675873, -92.410865 34.676029, -92.410738 34.676076, -92.410658 34.676108, -92.410455 34.676202, -92.410133 34.676372, -92.409934 34.676489, -92.409557 34.676684, -92.409436 34.676752, -92.409262 34.676871, -92.409107 34.676979, -92.408993 34.677069, -92.408811 34.677221, -92.408498 34.677482, -92.407938 34.677945, -92.407971 34.677985, -92.407976 34.678017, -92.407991 34.678122, -92.408104 34.67838, -92.408164 34.678429, -92.408303 34.678544, -92.408331 34.678567, -92.408484 34.678644, -92.40849 34.678672, -92.40861 34.678738, -92.408637 34.678793, -92.408776 34.678941, -92.408843 34.679079, -92.408962 34.679194, -92.409096 34.679238, -92.409601 34.679249, -92.409794 34.679299, -92.410426 34.67953, -92.410446 34.679557, -92.410699 34.679711, -92.411111 34.680129, -92.411231 34.680283, -92.411497 34.680987, -92.411843 34.681476, -92.412036 34.681696, -92.412142 34.681773, -92.412249 34.6819, -92.412495 34.682109, -92.413134 34.682477, -92.413633 34.682653, -92.413718 34.68272, -92.413879 34.682846, -92.41398 34.682907, -92.414013 34.682042, -92.414099 34.679629, -92.41414 34.678495, -92.414239 34.678498, -92.414256 34.678358, -92.414306 34.678386, -92.414398 34.678422, -92.414534 34.678452, -92.415128 34.678467, -92.417025 34.678498, -92.417178 34.678511, -92.417183 34.6786, -92.41783 34.678634, -92.418695 34.678681, -92.419302 34.678727, -92.41975 34.678761, -92.419727 34.679288, -92.419707 34.680322, -92.419701 34.680958, -92.419691 34.681003, -92.419697 34.681366, -92.420604 34.6814, -92.420762 34.681403, -92.420778 34.680786, -92.420807 34.67969, -92.42083 34.678776, -92.421667 34.678786, -92.422103 34.678802, -92.4225 34.678803, -92.422585 34.678794, -92.422623 34.678791, -92.422856 34.678789, -92.424407 34.67879, -92.424651 34.678789, -92.425601 34.678813, -92.42708 34.678851, -92.427048 34.679856, -92.427023 34.680672, -92.425685 34.680637, -92.422743 34.680558, -92.422641 34.680562, -92.422637 34.680817, -92.422632 34.68096, -92.422623 34.681299, -92.422619 34.68143, -92.422666 34.681437, -92.422717 34.681444, -92.424788 34.681513, -92.424781 34.681963, -92.425696 34.681988, -92.426981 34.682024, -92.426966 34.682482, -92.428025 34.682504, -92.430334 34.682553, -92.431226 34.682572, -92.431372 34.682552, -92.431375 34.682453, -92.431409 34.681236, -92.431454 34.680193, -92.431457 34.680123, -92.431517 34.67877, -92.43152 34.678649, -92.431543 34.677813, -92.431607 34.675425, -92.431614 34.675174, -92.43168 34.673662, -92.431692 34.67306, -92.431742 34.671713, -92.431758 34.671726, -92.433509 34.671767, -92.435158 34.671826, -92.43609 34.671843, -92.437408 34.671872, -92.43858 34.671906, -92.439937 34.67194, -92.44053 34.671966, -92.440821 34.671974, -92.442364 34.672017, -92.442662 34.672028, -92.443093 34.672037, -92.443265 34.672041, -92.443276 34.671892, -92.44328 34.671564, -92.443299 34.671263, -92.443303 34.671199, -92.443308 34.670789, -92.443305 34.67055, -92.44332 34.670342, -92.443342 34.669779, -92.443353 34.669578, -92.443379 34.6691, -92.443373 34.668887, -92.443339 34.668247, -92.443349 34.667986, -92.443405 34.667942, -92.443477 34.667096, -92.443513 34.666671, -92.443633 34.665279, -92.44367 34.66301, -92.443673 34.662804, -92.443687 34.662484, -92.443746 34.661085, -92.443793 34.65997, -92.443966 34.65593, -92.44404 34.654212, -92.444095 34.653407, -92.444336 34.646452, -92.444573 34.64642, -92.445236 34.646334, -92.445577 34.646293, -92.44687 34.64614, -92.448544 34.645887, -92.448569 34.645882, -92.448951 34.645814, -92.449327 34.645721, -92.449513 34.645676, -92.449545 34.645667, -92.45055 34.645398, -92.450645 34.645372, -92.45091 34.645303, -92.450904 34.645538, -92.450849 34.647626, -92.45082 34.64874, -92.450695 34.648739, -92.450656 34.64949, -92.451302 34.649476, -92.451343 34.649478, -92.452112 34.649483, -92.452382 34.649486, -92.452547 34.649408, -92.452704 34.649336, -92.45281 34.649242, -92.452957 34.649059, -92.453036 34.648962, -92.453065 34.648912, -92.453249 34.648594, -92.453335 34.648392, -92.453422 34.648192, -92.453535 34.648077, -92.453781 34.648033, -92.453954 34.648055, -92.454253 34.648154, -92.454386 34.648126, -92.454453 34.648049, -92.454579 34.647818, -92.454689 34.647729, -92.454792 34.647648, -92.454878 34.647538, -92.454925 34.6474, -92.454978 34.647356, -92.45507 34.647313, -92.455251 34.64723, -92.45541 34.647109, -92.455533 34.647036, -92.45571 34.646933, -92.456089 34.646856, -92.456162 34.646828, -92.456182 34.646801, -92.456355 34.646482, -92.456415 34.646064, -92.456482 34.645941, -92.456488 34.645932, -92.456607 34.64585, -92.456707 34.645817, -92.456989 34.645821, -92.45704 34.645822, -92.457177 34.64579, -92.457367 34.645747, -92.45737 34.64565, -92.457379 34.645411, -92.457384 34.645205, -92.457925 34.645202, -92.458051 34.6452, -92.458117 34.64508, -92.458124 34.645053, -92.458091 34.644998, -92.457964 34.644893, -92.457958 34.644833, -92.458124 34.644657, -92.458237 34.644596, -92.45833 34.644497, -92.458416 34.64436, -92.458576 34.644228, -92.458669 34.644178, -92.459002 34.644134, -92.459235 34.644074, -92.459434 34.643991, -92.459594 34.643821, -92.459727 34.643628, -92.459833 34.64354, -92.459893 34.643458, -92.460192 34.643255, -92.460259 34.643227, -92.460358 34.643068, -92.460391 34.642765, -92.460451 34.642677, -92.460618 34.642622, -92.460737 34.642615, -92.46105 34.6426, -92.461884 34.642611, -92.461916 34.64261, -92.461981 34.6426, -92.462347 34.642682, -92.463086 34.642968, -92.463438 34.642996, -92.463704 34.643056, -92.463984 34.643089, -92.464243 34.643089, -92.464343 34.643067, -92.464695 34.643105, -92.464941 34.643078, -92.465207 34.643171, -92.465354 34.643193, -92.465733 34.643187, -92.465859 34.64316, -92.466252 34.642951, -92.466544 34.642841, -92.466704 34.642819, -92.466804 34.642846, -92.46689 34.642907, -92.466997 34.642945, -92.46713 34.642929, -92.467183 34.64289, -92.467323 34.642857, -92.467356 34.642868, -92.467389 34.643138, -92.467456 34.643209, -92.467555 34.643242, -92.467795 34.64328, -92.468088 34.643308, -92.468447 34.643473, -92.46862 34.643577, -92.468853 34.643643, -92.468979 34.643703, -92.469338 34.643714, -92.469518 34.643676, -92.469671 34.643747, -92.469807 34.643784, -92.469948 34.643834, -92.470059 34.643825, -92.47022 34.643773, -92.47035 34.643702, -92.470479 34.64364, -92.47056 34.643579, -92.470674 34.643537, -92.470657 34.643834, -92.47065 34.643968, -92.470615 34.644596, -92.470575 34.645308, -92.470576 34.645423, -92.47057 34.645569, -92.469423 34.645537, -92.468968 34.645516, -92.468058 34.645483, -92.467542 34.645463, -92.467058 34.645456, -92.466799 34.645451, -92.466154 34.645436, -92.465699 34.645424, -92.465236 34.645416, -92.465055 34.645416, -92.464786 34.645417, -92.464324 34.645421, -92.463912 34.645418, -92.463199 34.64544, -92.462489 34.645445, -92.461866 34.645432, -92.461769 34.645181, -92.461758 34.645432, -92.461757 34.645465, -92.461755 34.645524, -92.46175 34.6457, -92.461734 34.646229, -92.46173 34.646406, -92.461687 34.647313, -92.461618 34.649037, -92.461592 34.649704, -92.461581 34.649966, -92.461572 34.650191, -92.461556 34.650592, -92.46154 34.650902, -92.46153 34.651184, -92.461486 34.652402, -92.46148 34.65256, -92.461465 34.653037, -92.46146 34.653196, -92.461429 34.654466, -92.461453 34.654532, -92.461458 34.654544, -92.461503 34.654609, -92.461558 34.654657, -92.461618 34.654688, -92.461685 34.654705, -92.461785 34.654703, -92.462122 34.654652, -92.463087 34.654534, -92.463319 34.654512, -92.463466 34.654512, -92.463696 34.654532, -92.46422 34.654628, -92.464669 34.654678, -92.464838 34.654707, -92.464946 34.654738, -92.465012 34.654769, -92.46506 34.654804, -92.465106 34.654856, -92.465134 34.654932, -92.465135 34.655083, -92.465114 34.655243, -92.46509 34.655345, -92.465034 34.655501, -92.464859 34.65588, -92.464844 34.655928, -92.464838 34.655991, -92.464843 34.656077, -92.464879 34.656141, -92.464946 34.656203, -92.465022 34.656246, -92.465055 34.656257, -92.465314 34.656279, -92.465385 34.65628, -92.466381 34.6563, -92.466403 34.656301, -92.466713 34.656312, -92.466764 34.656313, -92.466918 34.656319, -92.46697 34.656321, -92.466999 34.656101, -92.467021 34.655772, -92.467043 34.655623, -92.467151 34.65503, -92.467215 34.654737, -92.467264 34.654455, -92.467273 34.654409, -92.46741 34.653761, -92.467525 34.653379, -92.467568 34.653179, -92.467616 34.653037, -92.467642 34.652994, -92.467715 34.652919, -92.467738 34.652904, -92.467812 34.652871, -92.467864 34.652857, -92.467977 34.652843, -92.468154 34.652838, -92.468576 34.652852, -92.468816 34.652855, -92.469592 34.652844, -92.4698 34.652853, -92.469875 34.652865, -92.469935 34.652874, -92.470035 34.652909, -92.470078 34.652935, -92.470118 34.652971, -92.470151 34.653013, -92.470185 34.653096, -92.470195 34.653139, -92.4702 34.653397, -92.470177 34.654134, -92.470153 34.654557, -92.470123 34.655114, -92.470132 34.655427, -92.470136 34.655888, -92.470123 34.656447, -92.470347 34.656456, -92.47102 34.656486, -92.471245 34.656496, -92.471744 34.656522, -92.472223 34.656548, -92.473243 34.65657, -92.473743 34.656595, -92.474001 34.656605, -92.474775 34.656635, -92.475034 34.656646, -92.475157 34.65665, -92.475528 34.656663, -92.475652 34.656668, -92.47575 34.656673, -92.476045 34.65669, -92.476144 34.656696, -92.476737 34.656725, -92.476956 34.656736, -92.477154 34.656745, -92.477276 34.656747, -92.477508 34.656752, -92.478072 34.656763, -92.478935 34.656792, -92.479456 34.656812, -92.479641 34.65682, -92.48102 34.656851, -92.481338 34.656859, -92.481542 34.656866, -92.481578 34.656867, -92.481686 34.65687, -92.481723 34.656872, -92.481895 34.656877, -92.482413 34.656893, -92.482586 34.656899, -92.48288 34.656906, -92.483762 34.656927, -92.484056 34.656935, -92.484798 34.656959, -92.485718 34.65699, -92.485959 34.656995, -92.487027 34.657018, -92.487771 34.657034, -92.487932 34.657034, -92.488178 34.657036, -92.488416 34.657041, -92.488578 34.657045, -92.488837 34.657051, -92.488848 34.657051, -92.489658 34.65707, -92.489928 34.657077, -92.49081 34.657098, -92.492463 34.657139, -92.493456 34.657168, -92.494338 34.657195, -92.494769 34.657204, -92.495385 34.657219, -92.496065 34.65724, -92.496497 34.657254, -92.496546 34.656632, -92.496559 34.656307, -92.496591 34.655568, -92.496636 34.655269, -92.496708 34.654241, -92.4967 34.653593, -92.4967 34.653468, -92.4967 34.65252, -92.496703 34.652361, -92.496707 34.652095, -92.496711 34.65186, -92.496733 34.651318, -92.496772 34.650825, -92.496787 34.650639, -92.496712 34.650647, -92.494973 34.650977, -92.49465 34.651036, -92.493691 34.651218, -92.492425 34.65144, -92.492426 34.651335, -92.492419 34.649838, -92.492454 34.648825, -92.492449 34.647981, -92.49182 34.647985, -92.491134 34.647971, -92.490257 34.647964, -92.489861 34.647954, -92.489192 34.647935, -92.488462 34.647911, -92.488099 34.647898, -92.488117 34.647629, -92.488143 34.647255, -92.491397 34.647453, -92.493567 34.647507, -92.494836 34.647527, -92.494847 34.647337, -92.494851 34.647086, -92.494376 34.647078, -92.493478 34.647062, -92.493389 34.646587, -92.492075 34.646602, -92.491891 34.646647, -92.491623 34.646617, -92.49014 34.646587, -92.488374 34.646528, -92.488189 34.646518, -92.488211 34.646101, -92.488929 34.6461, -92.489663 34.646102, -92.490152 34.646111, -92.490879 34.646115, -92.490879 34.64616, -92.491066 34.646164, -92.491342 34.64617, -92.492038 34.646186, -92.492742 34.646202, -92.492962 34.646118, -92.492975 34.642869, -92.492977 34.642564, -92.492843 34.642562, -92.492843 34.642457, -92.49285 34.641798, -92.492866 34.640117, -92.492869 34.639691, -92.492907 34.639683, -92.493151 34.639633, -92.493252 34.639597, -92.493482 34.63946, -92.493534 34.639444, -92.493647 34.639429, -92.493768 34.639437, -92.493948 34.639469, -92.49412 34.639491, -92.494713 34.639569, -92.494943 34.639577, -92.495002 34.639572, -92.49501 34.639622, -92.495094 34.64046, -92.495101 34.640619, -92.495126 34.64116, -92.495102 34.642493, -92.495101 34.642603, -92.496262 34.64262, -92.497118 34.642633, -92.497362 34.642631, -92.497936 34.642629, -92.498093 34.642629, -92.498338 34.642631, -92.499047 34.642633, -92.49936 34.642635, -92.500602 34.642641, -92.501174 34.642641, -92.501259 34.642641, -92.501883 34.642654, -92.501876 34.64206, -92.501856 34.640278, -92.50185 34.639684, -92.501854 34.639561, -92.501866 34.639192, -92.50187 34.639069, -92.501966 34.636872, -92.502255 34.630281, -92.502307 34.6291, -92.503323 34.629133, -92.503324 34.629258, -92.503327 34.629633, -92.503329 34.629759, -92.503331 34.629963, -92.50333 34.63, -92.50332 34.630726, -92.503318 34.630968, -92.503265 34.631334, -92.503188 34.631882, -92.503118 34.632435, -92.503072 34.632803, -92.503059 34.633157, -92.503022 34.634219, -92.503011 34.634574, -92.50299 34.634936, -92.502926 34.636025, -92.502906 34.636388, -92.502889 34.636705, -92.50284 34.637658, -92.502824 34.637976, -92.502837 34.638076, -92.50286 34.638124, -92.502907 34.638163, -92.502953 34.638179, -92.503 34.638184, -92.504092 34.63818, -92.504526 34.638179, -92.505895 34.638198, -92.506654 34.638219, -92.507405 34.63825, -92.5083 34.638265, -92.509604 34.638288, -92.509703 34.638301, -92.509763 34.638309, -92.509908 34.638371, -92.510033 34.638435, -92.510154 34.638504, -92.510562 34.638763, -92.51064 34.638797, -92.51067 34.638807, -92.510692 34.638814, -92.510806 34.63884, -92.511042 34.638847, -92.511345 34.638847, -92.51195 34.638836, -92.512944 34.63884, -92.513962 34.638843, -92.514237 34.638845, -92.515061 34.638858, -92.515066 34.638749, -92.515084 34.638425, -92.51509 34.638317, -92.515095 34.63821, -92.515112 34.637889, -92.515118 34.637783, -92.515128 34.637537, -92.515159 34.636799, -92.51517 34.636554, -92.515173 34.636442, -92.515186 34.636108, -92.515191 34.635997, -92.515195 34.635865, -92.515205 34.635572, -92.515339 34.635575, -92.516303 34.635596, -92.516899 34.635608, -92.518668 34.635645, -92.519653 34.635661, -92.51994 34.635665, -92.520327 34.635672, -92.520708 34.635678, -92.521093 34.63569, -92.521378 34.635696, -92.521366 34.636022, -92.521336 34.636605, -92.52131 34.636906, -92.521296 34.637209, -92.521289 34.637506, -92.521271 34.637811, -92.521264 34.63812, -92.521246 34.638429, -92.521234 34.638722, -92.52123 34.639068, -92.521212 34.639284, -92.521251 34.639417, -92.521251 34.639568, -92.520982 34.639566, -92.520875 34.639277, -92.520561 34.639282, -92.520427 34.639281, -92.519811 34.639274, -92.519448 34.639263, -92.519427 34.639762, -92.51942 34.640197, -92.519418 34.640293, -92.519407 34.640977, -92.519304 34.645724, -92.519294 34.646564, -92.518867 34.646562, -92.518687 34.646561, -92.518402 34.646555, -92.518168 34.646552, -92.51791 34.646548, -92.517659 34.646545, -92.517236 34.64654, -92.516796 34.646534, -92.516781 34.646836, -92.516768 34.647097, -92.516762 34.647209, -92.516753 34.647378, -92.516469 34.647465, -92.516218 34.647542, -92.516082 34.647589, -92.515952 34.647581, -92.515663 34.647563, -92.515149 34.647531, -92.514893 34.647513, -92.514766 34.647508, -92.514752 34.647729, -92.514747 34.647817, -92.514733 34.648027, -92.514707 34.648436, -92.514698 34.648659, -92.51469 34.64887, -92.514559 34.64896, -92.514439 34.64913, -92.514466 34.64929, -92.514419 34.649394, -92.514173 34.649741, -92.514113 34.650109, -92.514053 34.650285, -92.5139 34.650417, -92.513838 34.650526, -92.513807 34.650582, -92.513773 34.650592, -92.5137 34.650702, -92.513667 34.650697, -92.513534 34.650823, -92.513327 34.651065, -92.512848 34.651741, -92.512489 34.652054, -92.512329 34.652148, -92.512149 34.652235, -92.511677 34.652411, -92.511544 34.652488, -92.511411 34.652609, -92.511138 34.652955, -92.510938 34.653109, -92.510599 34.653252, -92.510379 34.653384, -92.510206 34.653708, -92.510113 34.653834, -92.5099 34.654005, -92.50976 34.654076, -92.509723 34.654117, -92.509812 34.654119, -92.509904 34.654122, -92.509995 34.654126, -92.510087 34.654128, -92.510178 34.654131, -92.510268 34.654132, -92.51028 34.654132, -92.510277 34.654191, -92.510273 34.654259, -92.510271 34.65435, -92.510265 34.654454, -92.510263 34.654513, -92.51026 34.654572, -92.510258 34.654633, -92.510256 34.654696, -92.510253 34.65476, -92.51025 34.654879, -92.510245 34.654987, -92.510238 34.655037, -92.510224 34.655083, -92.51214 34.655125, -92.512138 34.654721, -92.512359 34.654718, -92.512443 34.654718, -92.512529 34.65472, -92.512615 34.654722, -92.512702 34.654724, -92.512792 34.654726, -92.512975 34.654733, -92.513068 34.654735, -92.513261 34.65474, -92.513461 34.654744, -92.513561 34.654747, -92.51366 34.654749, -92.513755 34.654752, -92.513846 34.654756, -92.51393 34.654759, -92.514009 34.65476, -92.514086 34.65476, -92.514156 34.654759, -92.514265 34.654755, -92.51434 34.654738, -92.514487 34.654737, -92.514503 34.654247, -92.514603 34.651163, -92.51464 34.650135, -92.51539 34.650135, -92.515666 34.650141, -92.517675 34.650186, -92.517839 34.65019, -92.518747 34.650212, -92.519774 34.650236, -92.519859 34.650237, -92.520117 34.650243, -92.520203 34.650245, -92.520422 34.650249, -92.520455 34.65025, -92.520447 34.650172, -92.520724 34.649951, -92.520961 34.649784, -92.521153 34.649654, -92.521375 34.649504, -92.521592 34.64935, -92.521834 34.649179, -92.521893 34.649138, -92.521946 34.649083, -92.521995 34.64902, -92.522039 34.648947, -92.522099 34.648821, -92.522123 34.648748, -92.522136 34.648685, -92.522144 34.648543, -92.522136 34.648444, -92.522111 34.648365, -92.522099 34.648324, -92.522052 34.648233, -92.522017 34.64816, -92.521811 34.647877, -92.521594 34.647574, -92.521376 34.647277, -92.521232 34.647073, -92.521163 34.646929, -92.521141 34.646837, -92.521127 34.64679, -92.521103 34.646619, -92.52127 34.646622, -92.521355 34.646624, -92.521612 34.646626, -92.521597 34.646555, -92.521588 34.646505, -92.521525 34.646181, -92.52153 34.646073, -92.521539 34.645928, -92.521592 34.645791, -92.522115 34.644916, -92.522125 34.6449, -92.522165 34.644692, -92.522165 34.644598, -92.522111 34.644499, -92.52211 34.644295, -92.522133 34.64418, -92.522408 34.643578, -92.522437 34.643469, -92.52253 34.64312, -92.522591 34.642971, -92.522682 34.642818, -92.52272 34.64267, -92.522682 34.642287, -92.522621 34.642212, -92.522484 34.642159, -92.522442 34.642096, -92.522469 34.641834, -92.522438 34.641766, -92.522316 34.641651, -92.522202 34.641579, -92.522156 34.641472, -92.522156 34.641095, -92.522156 34.640575, -92.522163 34.640457, -92.52227 34.640121, -92.522194 34.639915, -92.522217 34.639858, -92.522316 34.639729, -92.522324 34.639671, -92.522316 34.63961, -92.522252 34.639514, -92.522232 34.639416, -92.522255 34.63929, -92.522354 34.639008, -92.522385 34.638779, -92.522331 34.637871, -92.522339 34.637798, -92.522385 34.637726, -92.522308 34.637585, -92.522285 34.637447, -92.522301 34.637199, -92.522362 34.636955, -92.522491 34.636703, -92.52282 34.636375, -92.522888 34.636265, -92.522919 34.636143, -92.52298 34.636028, -92.523216 34.635803, -92.5233 34.635677, -92.523338 34.635551, -92.523422 34.635056, -92.523499 34.634808, -92.523575 34.634708, -92.523788 34.634533, -92.523994 34.634136, -92.524101 34.634007, -92.524261 34.633862, -92.52458 34.633641, -92.524734 34.633484, -92.525041 34.633136, -92.525051 34.633105, -92.525186 34.632842, -92.52527 34.632681, -92.525496 34.632109, -92.525546 34.632037, -92.525616 34.631939, -92.525656 34.631769, -92.525591 34.631728, -92.525516 34.631681, -92.52547 34.631681, -92.525399 34.631741, -92.525323 34.631741, -92.525323 34.631461, -92.525244 34.631213, -92.525151 34.631131, -92.525011 34.631114, -92.524978 34.631092, -92.524991 34.630955, -92.524911 34.630801, -92.524985 34.630641, -92.524971 34.630427, -92.525371 34.629636, -92.525451 34.629421, -92.525431 34.629322, -92.525338 34.629234, -92.525138 34.629174, -92.525085 34.629141, -92.525085 34.628877, -92.525039 34.628767, -92.524939 34.628662, -92.524985 34.628558, -92.525029 34.628515, -92.525494 34.628064, -92.525598 34.627965, -92.525904 34.627569, -92.526217 34.627063, -92.52637 34.626585, -92.526423 34.626491, -92.526563 34.626382, -92.526882 34.626294, -92.527048 34.626206, -92.527268 34.625942, -92.527461 34.625799, -92.527813 34.625458, -92.527933 34.625321, -92.528199 34.625145, -92.528352 34.625002, -92.528438 34.624949, -92.528531 34.624878, -92.528737 34.624576, -92.52875 34.624499, -92.528714 34.62432, -92.528657 34.624273, -92.528611 34.624257, -92.528544 34.624262, -92.527793 34.624411, -92.527573 34.624421, -92.527321 34.62441, -92.527201 34.624355, -92.527168 34.624317, -92.527208 34.624267, -92.527447 34.624229, -92.527567 34.624185, -92.527799 34.624141, -92.528345 34.623998, -92.528604 34.623883, -92.528717 34.623806, -92.528804 34.623713, -92.528837 34.623625, -92.528944 34.623015, -92.528924 34.622883, -92.528844 34.622795, -92.528798 34.622707, -92.528818 34.622547, -92.528971 34.622195, -92.529037 34.621646, -92.529004 34.621382, -92.528964 34.621283, -92.528905 34.621211, -92.528885 34.6212, -92.528812 34.621217, -92.528732 34.621272, -92.528639 34.621283, -92.528605 34.621244, -92.528605 34.621189, -92.528672 34.621046, -92.528739 34.620491, -92.528759 34.620425, -92.528885 34.620299, -92.528872 34.620216, -92.528898 34.620134, -92.528865 34.620101, -92.528839 34.620101, -92.528765 34.620156, -92.528692 34.620255, -92.528559 34.620255, -92.528413 34.620326, -92.528227 34.62031, -92.52818 34.620288, -92.528114 34.620227, -92.528087 34.620167, -92.528007 34.619875, -92.527868 34.619716, -92.527821 34.619485, -92.527828 34.619375, -92.527915 34.619166, -92.527994 34.619078, -92.528074 34.619023, -92.528174 34.61888, -92.528181 34.618781, -92.529405 34.61815, -92.529298 34.618018, -92.529212 34.617897, -92.529031 34.617645, -92.529013 34.617633, -92.529052 34.617605, -92.529245 34.617528, -92.529325 34.617435, -92.529358 34.617325, -92.529372 34.617171, -92.529425 34.617083, -92.529485 34.617056, -92.529651 34.617072, -92.529704 34.617105, -92.529744 34.617182, -92.529804 34.617188, -92.53001 34.617474, -92.53007 34.617562, -92.530176 34.617716, -92.533136 34.616166, -92.533215 34.616117, -92.533448 34.616199, -92.533661 34.616353, -92.533801 34.616513, -92.533933 34.616787, -92.533985 34.616838, -92.534186 34.617035, -92.534239 34.617123, -92.534272 34.617271, -92.534212 34.617788, -92.534226 34.617881, -92.534345 34.618167, -92.534412 34.61858, -92.534498 34.618783, -92.534624 34.618954, -92.53483 34.61936, -92.535003 34.619602, -92.535216 34.61985, -92.535309 34.620015, -92.535295 34.620191, -92.535142 34.620405, -92.535096 34.620509, -92.535082 34.62068, -92.535096 34.620784, -92.535142 34.620883, -92.535288 34.621048, -92.535468 34.621158, -92.535807 34.621158, -92.536133 34.621059, -92.536213 34.621059, -92.536253 34.62112, -92.536239 34.621175, -92.535947 34.621466, -92.535873 34.621582, -92.535847 34.621697, -92.535907 34.621889, -92.536 34.622027, -92.536093 34.62212, -92.536285 34.622258, -92.536512 34.622318, -92.536778 34.622329, -92.536837 34.622346, -92.536897 34.622395, -92.536877 34.622445, -92.536798 34.6225, -92.536398 34.622599, -92.536359 34.622632, -92.536385 34.62272, -92.536438 34.622758, -92.536591 34.622808, -92.536771 34.622912, -92.536957 34.623072, -92.537336 34.623286, -92.537874 34.623484, -92.538134 34.6236, -92.538539 34.623902, -92.538739 34.624001, -92.538952 34.624056, -92.539537 34.624265, -92.539636 34.624281, -92.53973 34.624265, -92.539869 34.624177, -92.539909 34.624177, -92.539996 34.624254, -92.540069 34.624436, -92.540315 34.624738, -92.540522 34.625, -92.540595 34.625137, -92.540668 34.625214, -92.540874 34.62533, -92.541067 34.625385, -92.541399 34.625396, -92.541499 34.625423, -92.541552 34.625467, -92.541612 34.625648, -92.541645 34.625692, -92.542177 34.626077, -92.54241 34.626297, -92.542663 34.626479, -92.542782 34.626589, -92.543027 34.626906, -92.543075 34.626968, -92.54352 34.627628, -92.543554 34.627721, -92.54352 34.627776, -92.543487 34.627793, -92.543414 34.627793, -92.543155 34.627716, -92.543068 34.627716, -92.543015 34.627765, -92.543022 34.627864, -92.543221 34.628062, -92.543254 34.628205, -92.543301 34.628249, -92.543334 34.628271, -92.54346 34.628282, -92.5436 34.62843, -92.543726 34.628485, -92.544072 34.62859, -92.544238 34.628694, -92.544318 34.628793, -92.544365 34.628958, -92.544378 34.629077, -92.544508 34.62937, -92.544655 34.629614, -92.544752 34.62984, -92.544832 34.629949, -92.545487 34.630128, -92.545484 34.630279, -92.545435 34.630313, -92.545259 34.630438, -92.545124 34.630615, -92.545082 34.630816, -92.545094 34.630895, -92.545173 34.631054, -92.545204 34.631194, -92.545216 34.631261, -92.545222 34.63131, -92.545265 34.631389, -92.545326 34.631469, -92.545783 34.631884, -92.546272 34.632305, -92.546345 34.632384, -92.54643 34.632524, -92.546528 34.632701, -92.546607 34.632787, -92.54665 34.632921, -92.546613 34.633019, -92.54654 34.633049, -92.546467 34.633074, -92.546229 34.63311, -92.546107 34.633098, -92.546015 34.633043, -92.545991 34.633006, -92.545966 34.632903, -92.545741 34.632622, -92.545503 34.632482, -92.545381 34.632433, -92.545201 34.632434, -92.545216 34.632678, -92.545518 34.632975, -92.54553 34.632987, -92.545519 34.633237, -92.545724 34.633439, -92.545856 34.633569, -92.545845 34.633819, -92.546182 34.634152, -92.546171 34.634402, -92.546467 34.634661, -92.546505 34.634805, -92.546715 34.635026, -92.547562 34.635767, -92.547554 34.635946, -92.547583 34.635979, -92.547679 34.636093, -92.547993 34.636961, -92.547995 34.637891, -92.547973 34.638051, -92.54793 34.638355, -92.54797 34.638463, -92.548266 34.638722, -92.548384 34.639048, -92.548412 34.639407, -92.548486 34.639571, -92.548513 34.639629, -92.548602 34.639744, -92.548692 34.63986, -92.548905 34.640025, -92.549025 34.640074, -92.549079 34.640084, -92.549298 34.640124, -92.549437 34.640091, -92.549703 34.640053, -92.54992 34.640131, -92.55039 34.640493, -92.550643 34.640689, -92.550902 34.640804, -92.551581 34.641361, -92.551663 34.641507, -92.55166 34.641578, -92.551873 34.641728, -92.552032 34.641877, -92.552192 34.642026, -92.552134 34.64204, -92.551962 34.642084, -92.551905 34.642099, -92.551512 34.64216, -92.55112 34.642222, -92.550497 34.642319, -92.549433 34.642485, -92.546273 34.642962, -92.544865 34.643176, -92.544377 34.643249, -92.542913 34.643471, -92.542426 34.643546, -92.542429 34.643607, -92.54244 34.643793, -92.542444 34.643855, -92.542415 34.644479, -92.542332 34.646351, -92.542304 34.646976, -92.542652 34.647039, -92.542792 34.647057, -92.543338 34.647131, -92.543561 34.647137, -92.543747 34.647117, -92.543856 34.647086, -92.543915 34.647059, -92.544078 34.646953, -92.544191 34.64685, -92.544296 34.646755, -92.544513 34.646533, -92.544548 34.646507, -92.54459 34.646471, -92.544706 34.646392, -92.54475 34.646366, -92.544809 34.646334, -92.544943 34.646272, -92.545094 34.64621, -92.545329 34.646178, -92.545456 34.646182, -92.545643 34.646188, -92.545703 34.646194, -92.545996 34.646235, -92.546877 34.646361, -92.547171 34.646403, -92.547411 34.646474, -92.548091 34.646676, -92.548128 34.646698, -92.548345 34.646826, -92.548619 34.646716, -92.549003 34.646587, -92.549191 34.646528, -92.549358 34.646477, -92.549487 34.646423, -92.549655 34.646327, -92.549805 34.646214, -92.549928 34.646096, -92.54994 34.646081, -92.550107 34.64589, -92.550414 34.645502, -92.550474 34.645443, -92.55054 34.645389, -92.550666 34.645311, -92.550791 34.645256, -92.550948 34.645218, -92.551034 34.645206, -92.551382 34.645137, -92.551783 34.645082, -92.55227 34.645003, -92.552291 34.644999, -92.552354 34.644991, -92.552376 34.644988, -92.552456 34.644977, -92.552675 34.644948, -92.55275 34.644939, -92.55283 34.64493, -92.55294 34.644928, -92.552969 34.644928, -92.553116 34.644913, -92.553266 34.644888, -92.553319 34.644874, -92.553481 34.644831, -92.553501 34.644822, -92.553679 34.644751, -92.553777 34.64471, -92.554024 34.644611, -92.554071 34.644586, -92.554121 34.64456, -92.554166 34.644537, -92.554203 34.644517, -92.554545 34.644337, -92.554887 34.644158, -92.554984 34.644109, -92.555357 34.643919, -92.555695 34.643762, -92.555963 34.643639, -92.55609 34.643598, -92.55639 34.643516, -92.556546 34.643495, -92.556753 34.643495, -92.557224 34.643511, -92.557282 34.643508, -92.557433 34.643503, -92.557525 34.643487, -92.557826 34.643399, -92.558374 34.643186, -92.558968 34.642982, -92.559575 34.64279, -92.559766 34.642741, -92.560353 34.642621, -92.560666 34.642567, -92.560756 34.642556, -92.561332 34.642493, -92.561956 34.642442, -92.562091 34.642439, -92.562499 34.642434, -92.562635 34.642433, -92.563002 34.642427, -92.563806 34.642416, -92.564077 34.642405, -92.564106 34.642402, -92.564473 34.642375, -92.56456 34.642364, -92.56474 34.642347, -92.565007 34.642322, -92.565465 34.642311, -92.565547 34.642307, -92.565723 34.642299, -92.565815 34.642283, -92.565913 34.642572, -92.565965 34.642697, -92.566064 34.642929, -92.566184 34.643192, -92.566464 34.643781, -92.566485 34.643848, -92.566498 34.643906, -92.566513 34.643967, -92.566582 34.644339, -92.566585 34.644355, -92.5666 34.64451, -92.566606 34.644666, -92.566605 34.644749, -92.566605 34.644882, -92.5666 34.645585, -92.56664 34.645984, -92.56665 34.646077, -92.566724 34.646387, -92.566883 34.646928, -92.566897 34.646981, -92.566928 34.647234, -92.566936 34.647351, -92.566941 34.647423, -92.56695 34.647868, -92.56694 34.648233, -92.566843 34.649174, -92.566788 34.649442, -92.566729 34.649585, -92.566689 34.649652, -92.566647 34.649704, -92.566279 34.650055, -92.566234 34.650103, -92.566134 34.650212, -92.566026 34.650354, -92.565948 34.650516, -92.565895 34.650657, -92.565871 34.650783, -92.565859 34.6509, -92.565861 34.650989, -92.567824 34.651041, -92.568487 34.65107, -92.569531 34.651097, -92.570239 34.651111, -92.570834 34.651128, -92.572254 34.651167, -92.572417 34.651172, -92.572459 34.651173, -92.572581 34.651175, -92.573731 34.651196, -92.573932 34.651202, -92.574537 34.651221, -92.574739 34.651228, -92.574806 34.651243, -92.574922 34.651292, -92.575073 34.651395, -92.5752 34.651507, -92.575796 34.652032, -92.576456 34.652565, -92.576685 34.652709, -92.576828 34.652793, -92.576936 34.652813, -92.576912 34.652473, -92.576911 34.652453, -92.576893 34.652035, -92.576903 34.651516, -92.576909 34.651454, -92.576947 34.651117, -92.577029 34.65043, -92.577084 34.649971, -92.577114 34.648359, -92.577116 34.648253, -92.5771 34.647942, -92.577059 34.647671, -92.57703 34.647527, -92.576943 34.647096, -92.576914 34.646953, -92.576913 34.646776, -92.576942 34.646531, -92.577046 34.646294, -92.577115 34.646139, -92.577161 34.646035, -92.577282 34.645832, -92.577511 34.645452, -92.577852 34.64495, -92.578049 34.644661, -92.578144 34.644522, -92.57843 34.644106, -92.578441 34.644091, -92.57851 34.643958, -92.578566 34.643682, -92.578567 34.64355, -92.578569 34.64348, -92.578552 34.643236, -92.578483 34.642915, -92.578447 34.642787, -92.578345 34.642539, -92.578254 34.642367, -92.578062 34.642002, -92.577678 34.641253, -92.577636 34.64117, -92.577436 34.640804, -92.577239 34.640442, -92.576817 34.639786, -92.576657 34.63949, -92.576488 34.639037, -92.576196 34.63825, -92.576136 34.638079, -92.576074 34.637903, -92.575886 34.637382, -92.575533 34.636456, -92.57547 34.636271, -92.575411 34.636036, -92.575396 34.635896, -92.575398 34.635358, -92.575439 34.634286, -92.57545 34.63397, -92.575486 34.633024, -92.575498 34.632709, -92.575528 34.632075, -92.575565 34.631322, -92.575595 34.630885, -92.575655 34.630176, -92.57571 34.629544, -92.57645 34.629566, -92.577199 34.629589, -92.57867 34.629644, -92.579411 34.629673, -92.579634 34.629684, -92.579861 34.629691, -92.581214 34.629733, -92.581665 34.629747, -92.581772 34.62975, -92.582093 34.62976, -92.5822 34.629764, -92.582538 34.629778, -92.583299 34.62981, -92.583552 34.629819, -92.583891 34.629831, -92.583932 34.629832, -92.584059 34.629836, -92.584101 34.629838, -92.584089 34.630142, -92.584052 34.631054, -92.584041 34.631359, -92.583993 34.632472, -92.583985 34.632681, -92.583871 34.635814, -92.583831 34.636929, -92.58386 34.636978, -92.583882 34.636996, -92.58397 34.637017, -92.584348 34.637036, -92.584681 34.637053, -92.585044 34.637077, -92.586011 34.637119, -92.586264 34.637131, -92.586566 34.637154, -92.586611 34.637157, -92.586749 34.637167, -92.586795 34.637171, -92.58691 34.637186, -92.589259 34.636675, -92.58979 34.636559, -92.59082 34.636335, -92.591417 34.636205, -92.591528 34.636178, -92.59405 34.635577, -92.594027 34.635494, -92.593995 34.635381, -92.593965 34.635274, -92.593937 34.635171, -92.593912 34.635072, -92.593887 34.634973, -92.59386 34.634873, -92.593795 34.634669, -92.593763 34.634563, -92.593729 34.634351, -92.593814 34.634347, -92.59391 34.634331, -92.59405 34.63431, -92.594139 34.634296, -92.594239 34.634278, -92.594344 34.634258, -92.594562 34.634217, -92.594677 34.634195, -92.594797 34.634172, -92.594917 34.634149, -92.595036 34.634128, -92.59515 34.634109, -92.595259 34.63409, -92.595457 34.634058, -92.595544 34.634045, -92.595626 34.634035, -92.595703 34.634023, -92.595773 34.634012, -92.595836 34.634001, -92.595892 34.633991, -92.595987 34.633978, -92.595965 34.633854, -92.59595 34.633755, -92.595937 34.633652, -92.595928 34.633548, -92.595922 34.633441, -92.595923 34.633335, -92.595928 34.63323, -92.595936 34.633125, -92.595942 34.63302, -92.595949 34.632916, -92.595957 34.632812, -92.595963 34.632705, -92.595977 34.632485, -92.595995 34.632255, -92.596003 34.632136, -92.59602 34.631891, -92.59603 34.631766, -92.596053 34.631523, -92.596063 34.631409, -92.596074 34.63121, -92.596066 34.631129, -92.596042 34.63106, -92.59594 34.630959, -92.595868 34.630928, -92.595785 34.63091, -92.595697 34.6309, -92.595602 34.630893, -92.595504 34.630888, -92.595404 34.630884, -92.595306 34.630879, -92.59521 34.630875, -92.595122 34.630868, -92.595048 34.63086, -92.595049 34.630794, -92.59505 34.630748, -92.595051 34.630696, -92.595052 34.630642, -92.595054 34.630589, -92.595054 34.630537, -92.595051 34.630485, -92.59505 34.630438, -92.595046 34.63035, -92.595044 34.63027, -92.595049 34.630164, -92.595509 34.630177, -92.596101 34.630195, -92.59612 34.630196, -92.596567 34.63021, -92.597229 34.630233, -92.597793 34.630257, -92.597967 34.630271, -92.598433 34.630309, -92.59883 34.630348, -92.600023 34.630465, -92.600421 34.630504, -92.600915 34.630556, -92.601727 34.630643, -92.602139 34.630678, -92.602398 34.630692, -92.602895 34.630721, -92.602954 34.630725, -92.603131 34.630737, -92.603191 34.630742, -92.603438 34.630754, -92.603837 34.630774, -92.60418 34.630795, -92.604428 34.630811, -92.604623 34.630817, -92.604944 34.630839, -92.605616 34.630873, -92.605655 34.630878, -92.605738 34.630901, -92.605784 34.630918, -92.605829 34.63094, -92.605861 34.630963, -92.6059 34.631015, -92.605932 34.63108, -92.605943 34.63112, -92.605974 34.631224, -92.606019 34.631414, -92.606038 34.631494, -92.606091 34.631638, -92.606141 34.631774, -92.606149 34.631809, -92.606068 34.63205, -92.605955 34.632393, -92.605851 34.632611, -92.605804 34.632731, -92.605794 34.632761, -92.605739 34.632945, -92.605727 34.633006, -92.606834 34.63283, -92.609119 34.632467, -92.609764 34.63236, -92.610154 34.632295, -92.611087 34.632142, -92.611205 34.632011, -92.611047 34.631706, -92.61101 34.631625, -92.610975 34.631547, -92.610841 34.631175, -92.610748 34.630917, -92.610627 34.630554, -92.610596 34.630397, -92.610583 34.630327, -92.610541 34.629969, -92.610538 34.629942, -92.610532 34.629864, -92.610531 34.629838, -92.61052 34.629681, -92.610546 34.629352, -92.610577 34.629217, -92.61059 34.629163, -92.610613 34.629065, -92.61017 34.629087, -92.609885 34.629102, -92.60962 34.629108, -92.609501 34.6291, -92.609391 34.629069, -92.609344 34.629048, -92.609258 34.628995, -92.609093 34.628844, -92.608982 34.628719, -92.608808 34.628523, -92.60874 34.628418, -92.608724 34.628365, -92.608145 34.628332, -92.607691 34.628295, -92.607415 34.62828, -92.607373 34.628283, -92.607202 34.628279, -92.607037 34.628258, -92.606983 34.628236, -92.606958 34.628216, -92.606927 34.628172, -92.606916 34.628135, -92.60691 34.627992, -92.606905 34.627874, -92.60692 34.62715, -92.606994 34.625821, -92.606987 34.625609, -92.606997 34.625425, -92.607022 34.625364, -92.607053 34.625331, -92.607119 34.625297, -92.607179 34.625281, -92.608263 34.625274, -92.608316 34.625269, -92.608418 34.625266, -92.608638 34.625261, -92.608726 34.625255, -92.608829 34.62525, -92.609177 34.625223, -92.60946 34.625175, -92.609571 34.625145, -92.609777 34.625069, -92.609944 34.625029, -92.610069 34.625008, -92.610219 34.624994, -92.610298 34.624988, -92.610574 34.624981, -92.610589 34.624819, -92.610637 34.624335, -92.610653 34.624174, -92.610678 34.624003, -92.610717 34.623838, -92.610802 34.62356, -92.610876 34.623321, -92.610904 34.623137, -92.610907 34.623045, -92.61089 34.622825, -92.610913 34.6221, -92.610899 34.621979, -92.61086 34.621809, -92.610818 34.621714, -92.610804 34.621694, -92.610747 34.621609, -92.610657 34.621508, -92.610517 34.621384, -92.610458 34.62134, -92.610348 34.621263, -92.61033 34.62125, -92.610277 34.621212, -92.61026 34.6212, -92.610229 34.621175, -92.610018 34.621037, -92.60968 34.620815, -92.609512 34.620687, -92.609344 34.620531, -92.609324 34.620509, -92.609251 34.62043, -92.609134 34.620289, -92.608842 34.619923, -92.60881 34.619882, -92.608503 34.619565, -92.608319 34.619356, -92.60827 34.619288, -92.608054 34.618944, -92.607969 34.618833, -92.607927 34.618779, -92.60782 34.618675, -92.607721 34.6186, -92.607611 34.618541, -92.607541 34.61852, -92.607257 34.618464, -92.606842 34.618421, -92.606729 34.61841, -92.606509 34.618381, -92.606324 34.618357, -92.606106 34.618318, -92.605813 34.618251, -92.60577 34.618237, -92.605646 34.618197, -92.605535 34.618152, -92.604983 34.617894, -92.604549 34.617712, -92.604514 34.617702, -92.604213 34.61758, -92.603941 34.617438, -92.603745 34.617314, -92.603462 34.617112, -92.603451 34.617103, -92.603164 34.616883, -92.602725 34.616521, -92.602592 34.616394, -92.602538 34.616337, -92.602376 34.616167, -92.602323 34.616111, -92.60297 34.615747, -92.603077 34.615687, -92.60332 34.615533, -92.603906 34.615139, -92.604118 34.615007, -92.604286 34.614913, -92.604427 34.614849, -92.604715 34.614732, -92.604898 34.614646, -92.605043 34.614579, -92.605182 34.6145, -92.605343 34.614397, -92.605506 34.614272, -92.605524 34.614252, -92.606256 34.613643, -92.607026 34.613002, -92.607092 34.612948, -92.608019 34.612265, -92.608502 34.612048, -92.609787 34.611557, -92.610077 34.611433, -92.610378 34.611274, -92.610715 34.611054, -92.61125 34.610688, -92.611485 34.610457, -92.611966 34.609966, -92.612139 34.609792, -92.613534 34.608804, -92.613506 34.60879, -92.613424 34.608748, -92.613397 34.608735, -92.613344 34.608708, -92.613327 34.6087, -92.613186 34.608635, -92.613134 34.608611, -92.613087 34.608589, -92.613012 34.608556, -92.612805 34.608466, -92.612644 34.608396, -92.612522 34.608343, -92.612491 34.608344, -92.612254 34.608379, -92.612093 34.608415, -92.612051 34.608428, -92.611873 34.608488, -92.61175 34.608543, -92.611692 34.608559, -92.611469 34.608605, -92.611338 34.608639, -92.611282 34.60866, -92.611151 34.608718, -92.611121 34.608735, -92.611035 34.608795, -92.610872 34.608928, -92.610768 34.609021, -92.61071 34.609074, -92.610674 34.609107, -92.610545 34.609232, -92.610478 34.609285, -92.610406 34.609335, -92.61025 34.609431, -92.610165 34.609475, -92.610039 34.609531, -92.609886 34.609587, -92.609715 34.609636, -92.609556 34.60967, -92.609245 34.609725, -92.60903 34.609775, -92.608746 34.60985, -92.608692 34.609859, -92.60858 34.609861, -92.60829 34.609837, -92.608193 34.609829, -92.608059 34.609822, -92.607728 34.609825, -92.607366 34.609835, -92.607191 34.60985, -92.606816 34.609924, -92.606711 34.60994, -92.606605 34.60995, -92.606516 34.60995, -92.605917 34.609931, -92.605647 34.60995, -92.605213 34.609945, -92.604976 34.609949, -92.604893 34.609956, -92.604797 34.609977, -92.604699 34.610018, -92.604369 34.610222, -92.604308 34.610253, -92.604244 34.61028, -92.604181 34.610295, -92.603985 34.610326, -92.603919 34.610332, -92.603802 34.610328, -92.60356 34.610299, -92.603439 34.610294, -92.603267 34.610305, -92.603053 34.610344, -92.602954 34.610355, -92.60286 34.610355, -92.602815 34.610351, -92.602744 34.610337, -92.602632 34.610291, -92.602534 34.610238, -92.602491 34.610207, -92.602414 34.610135, -92.60238 34.610096, -92.602352 34.610055, -92.602335 34.610017, -92.602319 34.609951, -92.602308 34.609735, -92.602315 34.609582, -92.602326 34.609346, -92.602323 34.608952, -92.602334 34.608384, -92.602356 34.607849, -92.602389 34.607377, -92.602402 34.607162, -92.60244 34.606577, -92.602444 34.606517, -92.602462 34.606303, -92.602481 34.606059, -92.602484 34.606035, -92.602515 34.605327, -92.602523 34.605153, -92.602506 34.605086, -92.602486 34.605033, -92.602456 34.604956, -92.602418 34.604897, -92.602312 34.604736, -92.602247 34.604597, -92.602204 34.604441, -92.602182 34.604325, -92.602143 34.604121, -92.602121 34.604003, -92.602092 34.603844, -92.602051 34.603686, -92.602043 34.603654, -92.602014 34.603539, -92.601891 34.603257, -92.601826 34.60326, -92.600811 34.603232, -92.600795 34.603232, -92.599738 34.60319, -92.5995 34.603178, -92.599232 34.603165, -92.598726 34.603141, -92.598251 34.603117, -92.598292 34.602395, -92.59835 34.601394, -92.598355 34.601302, -92.59833 34.601301, -92.598026 34.601289, -92.597544 34.601291, -92.597261 34.601292, -92.596945 34.601285, -92.595762 34.601256, -92.594839 34.601235, -92.594155 34.601218, -92.594094 34.601217, -92.593989 34.601214, -92.593993 34.601139, -92.594047 34.60006, -92.594052 34.599917, -92.59406 34.599709, -92.594069 34.599223, -92.594072 34.5991, -92.594112 34.598714, -92.594126 34.598182, -92.594137 34.598115, -92.594149 34.598041, -92.594187 34.597823, -92.5942 34.59775, -92.595798 34.597742, -92.596386 34.597739, -92.596466 34.597739, -92.596471 34.5976, -92.5965 34.596562, -92.596573 34.593993, -92.596951 34.594018, -92.598546 34.594128, -92.598554 34.594087, -92.598604 34.593939, -92.598606 34.593893, -92.59861 34.593835, -92.598588 34.593753, -92.59846 34.593436, -92.5984 34.593354, -92.598297 34.593252, -92.598215 34.593171, -92.5982 34.593129, -92.598233 34.593052, -92.598411 34.593163, -92.598726 34.59336, -92.598948 34.593491, -92.599001 34.593522, -92.599096 34.593637, -92.599204 34.593805, -92.599291 34.593976, -92.599428 34.594252, -92.599644 34.594688, -92.599712 34.594934, -92.599775 34.595381, -92.599812 34.595548, -92.599867 34.595672, -92.59994 34.59578, -92.600085 34.595914, -92.600205 34.595982, -92.600318 34.596026, -92.600427 34.596051, -92.600487 34.596057, -92.600532 34.596061, -92.601185 34.596109, -92.6012 34.59611, -92.601263 34.596115, -92.601859 34.59617, -92.601951 34.596197, -92.602004 34.596213, -92.602141 34.596274, -92.60229 34.596371, -92.602303 34.596383, -92.602402 34.596474, -92.602458 34.596541, -92.602491 34.596581, -92.602533 34.596652, -92.602579 34.596774, -92.602599 34.596826, -92.602607 34.596858, -92.602615 34.596891, -92.602616 34.59691, -92.602625 34.597035, -92.602619 34.597068, -92.602612 34.597121, -92.602607 34.597144, -92.602605 34.597162, -92.602591 34.597213, -92.602585 34.597237, -92.602547 34.59733, -92.602517 34.597386, -92.602443 34.597527, -92.602313 34.597743, -92.602279 34.597819, -92.602253 34.597879, -92.602228 34.597975, -92.602224 34.598039, -92.602222 34.59808, -92.602235 34.598228, -92.602255 34.598293, -92.602273 34.598353, -92.602336 34.598462, -92.60239 34.598536, -92.602432 34.598583, -92.602452 34.598601, -92.602581 34.598712, -92.602675 34.5988, -92.603252 34.599277, -92.603306 34.599313, -92.603413 34.599385, -92.60363 34.599493, -92.603909 34.599559, -92.604161 34.599613, -92.604319 34.59964, -92.60445 34.59964, -92.604999 34.599682, -92.605163 34.599701, -92.605272 34.599725, -92.605457 34.59979, -92.605587 34.599854, -92.605763 34.599957, -92.605856 34.600024, -92.606008 34.600116, -92.606165 34.600168, -92.606282 34.600218, -92.606495 34.600281, -92.606614 34.600288, -92.6067 34.600284, -92.606841 34.600264, -92.606892 34.600258, -92.607128 34.600212, -92.607383 34.600148, -92.60761 34.600096, -92.607664 34.60008, -92.607719 34.600058, -92.607923 34.59999, -92.608208 34.599896, -92.608308 34.599879, -92.608444 34.599876, -92.608629 34.5999, -92.608725 34.59993, -92.608799 34.599953, -92.608975 34.600043, -92.6091 34.600107, -92.609276 34.600197, -92.609337 34.600219, -92.609589 34.600312, -92.609759 34.60036, -92.609973 34.600398, -92.610514 34.600478, -92.61071 34.600507, -92.610912 34.600546, -92.611029 34.600572, -92.611381 34.600652, -92.611433 34.600664, -92.611502 34.600683, -92.611576 34.600697, -92.611629 34.600713, -92.611643 34.600771, -92.611628 34.600982, -92.611599 34.601455, -92.611633 34.601803, -92.611699 34.601966, -92.611712 34.60241, -92.611684 34.602529, -92.611682 34.602608, -92.611678 34.602707, -92.611677 34.602742, -92.611675 34.602792, -92.611672 34.602842, -92.611668 34.602892, -92.611665 34.602943, -92.611662 34.602994, -92.61166 34.603046, -92.611659 34.6031, -92.611658 34.603153, -92.611656 34.603207, -92.611654 34.603261, -92.611653 34.603315, -92.611652 34.603369, -92.61165 34.603424, -92.611649 34.60348, -92.611648 34.603535, -92.611647 34.60359, -92.611646 34.603645, -92.611646 34.6037, -92.611647 34.603755, -92.611647 34.603808, -92.611646 34.603859, -92.611647 34.603905, -92.61166 34.60406, -92.611664 34.604119, -92.611735 34.604093, -92.61181 34.604073, -92.611886 34.604053, -92.611964 34.604035, -92.61204 34.604017, -92.612116 34.603999, -92.612193 34.60398, -92.612271 34.603959, -92.612348 34.603936, -92.612426 34.60391, -92.612502 34.60388, -92.612575 34.603846, -92.612644 34.603808, -92.612707 34.60377, -92.612766 34.60373, -92.612824 34.603692, -92.612878 34.603653, -92.612928 34.603616, -92.612975 34.603583, -92.613017 34.603554, -92.613104 34.603465, -92.613144 34.60351, -92.613187 34.603555, -92.613234 34.603601, -92.613335 34.603697, -92.613388 34.603748, -92.613442 34.6038, -92.613499 34.603853, -92.613557 34.603909, -92.613616 34.603966, -92.613677 34.604025, -92.613739 34.604084, -92.613799 34.604142, -92.613856 34.604197, -92.613912 34.604251, -92.613964 34.604301, -92.614012 34.604347, -92.614099 34.604427, -92.614136 34.604461, -92.614189 34.604511, -92.614227 34.604551, -92.614261 34.604568, -92.614295 34.604536, -92.614368 34.604477, -92.614525 34.604374, -92.614681 34.604265, -92.61483 34.604158, -92.614903 34.604102, -92.614971 34.604042, -92.61503 34.603978, -92.615079 34.603909, -92.61512 34.603837, -92.6152 34.603698, -92.61524 34.603632, -92.615318 34.603496, -92.615397 34.603371, -92.615452 34.603264, -92.615522 34.603184, -92.615574 34.603106, -92.615652 34.603023, -92.615708 34.603073, -92.615741 34.603102, -92.61578 34.603137, -92.615848 34.603199, -92.615896 34.603246, -92.615935 34.603284, -92.61595 34.603299, -92.615997 34.603344, -92.616013 34.60336, -92.61604 34.603393, -92.616067 34.603427, -92.616085 34.603449, -92.616113 34.603442, -92.616851 34.603255, -92.617018 34.602249, -92.616223 34.601521, -92.61576 34.601099, -92.6149 34.600334, -92.614366 34.600023, -92.612836 34.599585, -92.612464 34.599475, -92.612018 34.599388, -92.611864 34.599357, -92.611746 34.599335, -92.611782 34.598661, -92.61179 34.598503, -92.611305 34.598444, -92.610897 34.598497, -92.610621 34.598488, -92.610395 34.598398, -92.610249 34.598344, -92.610217 34.598321, -92.610197 34.598295, -92.610079 34.598206, -92.609599 34.597738, -92.609068 34.597216, -92.608567 34.596723, -92.60845 34.596626, -92.607949 34.596215, -92.607574 34.595739, -92.606947 34.595155, -92.606348 34.594406, -92.606171 34.594396, -92.606094 34.594391, -92.605838 34.594376, -92.60563 34.594203, -92.605567 34.59415, -92.605502 34.594096, -92.605102 34.593502, -92.604931 34.593266, -92.604602 34.592604, -92.60454 34.592398, -92.604343 34.592043, -92.604208 34.59172, -92.604141 34.591037, -92.604171 34.590816, -92.604171 34.590514, -92.60417 34.59012, -92.604413 34.589448, -92.604584 34.588923, -92.604641 34.588489, -92.604584 34.588069, -92.604616 34.587995, -92.6049 34.587357, -92.604945 34.587246, -92.605304 34.587261, -92.605376 34.587265, -92.604473 34.586309, -92.600843 34.58612, -92.600204 34.585847, -92.600199 34.585769, -92.60019 34.585627, -92.600484 34.585516, -92.600536 34.585499, -92.600735 34.585435, -92.600904 34.585424, -92.600943 34.585422, -92.601413 34.585451, -92.601583 34.585462, -92.601917 34.585474, -92.601958 34.585476, -92.602071 34.585467, -92.602153 34.585435, -92.602287 34.585366, -92.602574 34.585173, -92.602639 34.58514, -92.602801 34.585082, -92.602822 34.585076, -92.603149 34.585001, -92.603127 34.584965, -92.603034 34.584764, -92.602897 34.584468, -92.602752 34.584112, -92.602711 34.584045, -92.602664 34.583968, -92.60255 34.583838, -92.602586 34.583811, -92.602744 34.583715, -92.602769 34.5837, -92.602875 34.583612, -92.602936 34.583532, -92.603054 34.583336, -92.603077 34.583268, -92.603087 34.583206, -92.603083 34.58314, -92.603073 34.58311, -92.602985 34.582971, -92.602965 34.582953, -92.603144 34.582834, -92.603499 34.582598, -92.60358 34.582492, -92.603617 34.582415, -92.603713 34.582223, -92.603734 34.582173, -92.603809 34.581959, -92.603875 34.581775, -92.603904 34.581716, -92.603958 34.581637, -92.604093 34.581515, -92.60434 34.581339, -92.604536 34.581201, -92.604572 34.58118, -92.604486 34.581099, -92.604224 34.580907, -92.604156 34.580858, -92.604086 34.580818, -92.604027 34.580796, -92.603898 34.580772, -92.603471 34.580734, -92.603293 34.580728, -92.603208 34.580731, -92.603057 34.580756, -92.602974 34.580784, -92.602944 34.5808, -92.60291 34.58082, -92.602881 34.580845, -92.602662 34.581097, -92.602648 34.581124, -92.602509 34.5813, -92.602365 34.581486, -92.602292 34.5816, -92.602193 34.581886, -92.60212 34.582099, -92.602085 34.58209, -92.602001 34.582093, -92.601953 34.582107, -92.601889 34.582142, -92.601251 34.582499, -92.601092 34.582589, -92.601038 34.582616, -92.60093 34.58252, -92.600854 34.582453, -92.600594 34.58225, -92.600578 34.582237, -92.600456 34.582226, -92.599193 34.582055, -92.599289 34.582015, -92.5999 34.581872, -92.6001 34.581691, -92.6002 34.581509, -92.600366 34.581328, -92.600388 34.581326, -92.600479 34.581229, -92.600625 34.581135, -92.600884 34.580811, -92.600922 34.580706, -92.600944 34.580646, -92.60103 34.580509, -92.601163 34.580421, -92.601349 34.580355, -92.601462 34.580278, -92.601542 34.580107, -92.601655 34.579975, -92.602219 34.579447, -92.60251 34.579328, -92.602638 34.579277, -92.602851 34.579227, -92.602964 34.579172, -92.603037 34.579101, -92.60299 34.578958, -92.602894 34.578886, -92.602777 34.578798, -92.602704 34.578628, -92.602877 34.578502, -92.60297 34.578463, -92.602996 34.578403, -92.602996 34.578342, -92.602963 34.578304, -92.60299 34.578216, -92.602976 34.578188, -92.602996 34.578166, -92.603023 34.57793, -92.603076 34.577792, -92.603196 34.577688, -92.603368 34.577649, -92.603521 34.577666, -92.603674 34.577644, -92.6038 34.577567, -92.60402 34.577517, -92.604173 34.577457, -92.604305 34.577275, -92.604378 34.577226, -92.604531 34.57722, -92.604631 34.577275, -92.604671 34.57733, -92.604718 34.577638, -92.604817 34.577737, -92.604924 34.577731, -92.605076 34.577643, -92.605289 34.577583, -92.605622 34.57777, -92.606034 34.577687, -92.606326 34.57767, -92.606585 34.577698, -92.606745 34.577753, -92.606911 34.577917, -92.606977 34.577945, -92.607177 34.577956, -92.607407 34.57792, -92.607503 34.577906, -92.607675 34.577851, -92.607921 34.57789, -92.608174 34.5779, -92.608333 34.577961, -92.608426 34.577972, -92.608539 34.57795, -92.608679 34.577889, -92.608805 34.577818, -92.608965 34.577675, -92.609184 34.577598, -92.609722 34.57735, -92.610001 34.577163, -92.610094 34.577141, -92.610227 34.577147, -92.6103 34.57713, -92.610506 34.577026, -92.610659 34.576921, -92.610772 34.576883, -92.610905 34.576652, -92.610931 34.576437, -92.610891 34.576355, -92.610825 34.576272, -92.610479 34.575998, -92.610455 34.575962, -92.610399 34.575877, -92.610353 34.575717, -92.610339 34.575585, -92.610352 34.575415, -92.610379 34.575338, -92.610479 34.575178, -92.610552 34.575107, -92.610572 34.575107, -92.610751 34.574898, -92.611003 34.574211, -92.611149 34.573914, -92.611255 34.573776, -92.611528 34.573512, -92.611607 34.573457, -92.611774 34.573402, -92.611966 34.573276, -92.612064 34.573181, -92.612126 34.573122, -92.612431 34.572715, -92.612584 34.572643, -92.61271 34.572616, -92.612856 34.572555, -92.613055 34.572421, -92.613033 34.572374, -92.612277 34.571507, -92.61181 34.5708, -92.611534 34.570479, -92.611391 34.570314, -92.610835 34.56967, -92.610726 34.569667, -92.61065 34.569667, -92.610657 34.569271, -92.610666 34.568817, -92.610554 34.568751, -92.610499 34.568596, -92.610672 34.568524, -92.610676 34.568294, -92.610881 34.567961, -92.611051 34.56767, -92.611401 34.567343, -92.611554 34.567205, -92.611922 34.566893, -92.612163 34.566648, -92.612637 34.566105, -92.612872 34.566112, -92.61344 34.565358, -92.613902 34.56478, -92.614139 34.564484, -92.614783 34.563678, -92.615116 34.563287, -92.615357 34.563004, -92.615539 34.562839, -92.615495 34.562818, -92.615342 34.562746, -92.615139 34.56265, -92.614911 34.562544, -92.614807 34.562499, -92.614595 34.562417, -92.614377 34.562345, -92.614141 34.562283, -92.614021 34.562257, -92.613835 34.562224, -92.613717 34.562204, -92.613382 34.562157, -92.61318 34.562135, -92.612576 34.562072, -92.612375 34.562051, -92.61236 34.562049, -92.612315 34.562044, -92.6123 34.562043, -92.612207 34.562022, -92.612165 34.562011, -92.612071 34.561969, -92.61204 34.561952, -92.611982 34.561911, -92.611954 34.561895, -92.611872 34.561849, -92.611933 34.561798, -92.612116 34.561644, -92.612178 34.561594, -92.61222 34.561565, -92.612428 34.561389, -92.612565 34.561275, -92.612777 34.561065, -92.613107 34.5607, -92.613175 34.560626, -92.613314 34.560452, -92.613495 34.560271, -92.614293 34.559551, -92.614724 34.559153, -92.615602 34.558345, -92.616923 34.557151, -92.617705 34.556458, -92.618604 34.555635, -92.618986 34.55529, -92.620262 34.554143, -92.620412 34.554008, -92.620502 34.553927, -92.620556 34.553877, -92.620646 34.553796, -92.62089 34.553574, -92.62099 34.553483, -92.621135 34.553352, -92.621559 34.552966, -92.621785 34.552759, -92.621938 34.552621, -92.621984 34.55258, -92.622573 34.552045, -92.624353 34.550436, -92.624393 34.5504, -92.625162 34.549712, -92.625942 34.548997, -92.626167 34.548792, -92.627776 34.547337, -92.627941 34.547207, -92.628112 34.547143, -92.628241 34.547088, -92.628339 34.547046, -92.628492 34.546986, -92.628943 34.546834, -92.629133 34.546761, -92.629293 34.546686, -92.62951 34.54656, -92.629626 34.54648, -92.629722 34.546424, -92.629821 34.546417, -92.629946 34.546419, -92.630045 34.546436, -92.630149 34.546469, -92.630195 34.546491, -92.630258 34.546537, -92.630312 34.546591, -92.630369 34.54667, -92.630444 34.546799, -92.630441 34.546814, -92.630434 34.546852, -92.630425 34.546904, -92.630415 34.546964, -92.630407 34.547014, -92.63006 34.549069, -92.629644 34.551262, -92.629508 34.551974, -92.629373 34.552692, -92.629324 34.552948, -92.629312 34.552995, -92.629278 34.553143, -92.629225 34.553337, -92.629168 34.553507, -92.629023 34.553878, -92.629018 34.553893, -92.628901 34.554163, -92.62881 34.554375, -92.628776 34.554451, -92.628402 34.555314, -92.628278 34.555603, -92.628382 34.555617, -92.628564 34.555642, -92.628695 34.555653, -92.6288 34.555662, -92.629372 34.555685, -92.631124 34.55573, -92.631415 34.555738, -92.631706 34.555742, -92.63182 34.555743, -92.632164 34.555747, -92.632279 34.555749, -92.632733 34.555755, -92.634096 34.555773, -92.634551 34.555779, -92.63473 34.555784, -92.636536 34.555801, -92.636917 34.5558, -92.63743 34.555799, -92.63765 34.55579, -92.63781 34.555777, -92.637954 34.555761, -92.638116 34.555734, -92.63915 34.555537, -92.639901 34.555382, -92.640323 34.555306, -92.640525 34.555281, -92.640729 34.555266, -92.640908 34.555264, -92.641086 34.555276, -92.641215 34.555295, -92.641343 34.555324, -92.641468 34.55536, -92.641597 34.55541, -92.641722 34.555468, -92.641872 34.555549, -92.642152 34.555736, -92.642193 34.555768, -92.642299 34.55585, -92.642726 34.556204, -92.643457 34.556839, -92.643877 34.557204, -92.644052 34.557338, -92.644237 34.557462, -92.644337 34.557518, -92.644517 34.557606, -92.644878 34.557736, -92.645064 34.557787, -92.645257 34.557823, -92.645442 34.557835, -92.645536 34.557836, -92.645746 34.557855, -92.645954 34.557888, -92.646658 34.558038, -92.646992 34.558122, -92.647107 34.558171, -92.647173 34.558208, -92.647237 34.558256, -92.647293 34.558311, -92.647311 34.558337, -92.647396 34.558455, -92.647493 34.558617, -92.647515 34.558648, -92.64753 34.558668, -92.647586 34.558729, -92.647648 34.558787, -92.64771 34.558834, -92.647777 34.558876, -92.647878 34.55892, -92.648094 34.558984, -92.648629 34.559159, -92.648815 34.559227, -92.648917 34.559272, -92.649035 34.559343, -92.649134 34.559431, -92.649162 34.559464, -92.649223 34.559562, -92.649251 34.559629, -92.649275 34.559725, -92.649339 34.560127, -92.649358 34.560216, -92.649417 34.560357, -92.649489 34.560482, -92.649588 34.560611, -92.649664 34.560685, -92.649693 34.560705, -92.649753 34.560734, -92.649819 34.560755, -92.649887 34.560771, -92.649985 34.560781, -92.650187 34.56077, -92.650444 34.560729, -92.650528 34.560708, -92.650675 34.560657, -92.650817 34.560598, -92.651131 34.560447, -92.651281 34.560361, -92.65156 34.560208, -92.651818 34.560068, -92.652182 34.559866, -92.652383 34.559771, -92.652787 34.559567, -92.652864 34.559537, -92.652982 34.5595, -92.653261 34.559435, -92.653352 34.559418, -92.653548 34.559337, -92.653658 34.559271, -92.653695 34.559243, -92.65376 34.559197, -92.653859 34.55911, -92.653951 34.559017, -92.654173 34.55874, -92.654611 34.558141, -92.654778 34.557915, -92.654908 34.557765, -92.655033 34.557642, -92.655175 34.557533, -92.655272 34.55746, -92.655636 34.557198, -92.65598 34.556916, -92.656018 34.556888, -92.656309 34.556686, -92.656477 34.556569, -92.656667 34.556442, -92.65708 34.556169, -92.65727 34.556055, -92.65735 34.556014, -92.657434 34.55598, -92.657549 34.555954, -92.657667 34.555942, -92.657767 34.555939, -92.657834 34.555943, -92.657867 34.555945, -92.658046 34.555979, -92.658257 34.556035, -92.658611 34.556151, -92.65893 34.556261, -92.658956 34.55627, -92.659152 34.556342, -92.659755 34.556624, -92.65988 34.556673, -92.659962 34.556697, -92.660072 34.556711, -92.660216 34.556718, -92.660396 34.556716, -92.66097 34.556695, -92.661152 34.556689, -92.661523 34.556688, -92.66168 34.556698, -92.66203 34.556749, -92.662194 34.556773, -92.662373 34.556805, -92.662549 34.556845, -92.662791 34.556918, -92.663056 34.557017, -92.663166 34.557059, -92.663321 34.55711, -92.663394 34.557121, -92.663494 34.557125, -92.663612 34.557107, -92.663778 34.557044, -92.663968 34.556941, -92.664062 34.556874, -92.664149 34.556814, -92.664183 34.55678, -92.664232 34.556705, -92.664269 34.556623, -92.664342 34.556386, -92.664395 34.556251, -92.664419 34.556206, -92.664459 34.556156, -92.664575 34.556057, -92.664663 34.556015, -92.665112 34.555846, -92.66552 34.555702, -92.665692 34.555638, -92.66612 34.555483, -92.666286 34.555419, -92.666372 34.555375, -92.666544 34.555294, -92.667092 34.554964, -92.667519 34.554707, -92.668699 34.554021, -92.669423 34.553582, -92.669623 34.553445, -92.668423 34.553168, -92.668386 34.553143, -92.668314 34.553075, -92.668243 34.552987, -92.668116 34.552803, -92.668014 34.552672, -92.667905 34.552547, -92.667703 34.552356, -92.667612 34.552277, -92.667514 34.552204, -92.667331 34.552109, -92.667146 34.552042, -92.667023 34.552007, -92.666732 34.551943, -92.666561 34.551894, -92.666421 34.551831, -92.666342 34.551781, -92.666282 34.551733, -92.66615 34.551595, -92.665817 34.551146, -92.665739 34.551047, -92.6656 34.550872, -92.665467 34.550715, -92.665375 34.550626, -92.665298 34.55057, -92.665212 34.55052, -92.664183 34.550029, -92.663991 34.549932, -92.663652 34.54976, -92.66344 34.549647, -92.663419 34.549637, -92.663322 34.549592, -92.663222 34.549555, -92.663099 34.549509, -92.662949 34.549465, -92.662839 34.549447, -92.662252 34.549407, -92.661647 34.549372, -92.661546 34.549367, -92.661294 34.549335, -92.661253 34.549325, -92.661169 34.549304, -92.660992 34.549243, -92.660925 34.54922, -92.660746 34.549148, -92.660693 34.549124, -92.66057 34.549069, -92.659331 34.548439, -92.659097 34.548323, -92.658564 34.54806, -92.658463 34.54801, -92.65834 34.547933, -92.658228 34.547846, -92.658178 34.54779, -92.658133 34.547741, -92.657927 34.54747, -92.657713 34.54716, -92.657628 34.547012, -92.657582 34.546912, -92.657552 34.546811, -92.657507 34.546563, -92.657506 34.546554, -92.657471 34.546433, -92.657437 34.546365, -92.657397 34.546312, -92.657259 34.546173, -92.657135 34.546085, -92.656984 34.545979, -92.65677 34.545813, -92.656651 34.54572, -92.656568 34.54565, -92.656298 34.545489, -92.656174 34.545433, -92.656135 34.545416, -92.656056 34.545389, -92.655423 34.545216, -92.654973 34.545078, -92.654629 34.544948, -92.654579 34.544931, -92.654314 34.544844, -92.654122 34.544752, -92.654065 34.544719, -92.653912 34.544582, -92.653787 34.54447, -92.653487 34.544142, -92.653349 34.543992, -92.652962 34.543566, -92.65284 34.543398, -92.652587 34.543046, -92.652358 34.542682, -92.652264 34.542507, -92.652207 34.542376, -92.652043 34.541945, -92.651976 34.541819, -92.6519 34.541698, -92.651727 34.541469, -92.651657 34.541386, -92.651154 34.540787, -92.651132 34.540753, -92.650963 34.540493, -92.650806 34.540067, -92.651184 34.539975, -92.651156 34.539846, -92.651107 34.539627, -92.651066 34.539443, -92.651047 34.539357, -92.651017 34.539223, -92.651005 34.539171, -92.650651 34.539205, -92.650656 34.539138, -92.650661 34.538922, -92.65067 34.538563, -92.650673 34.538462, -92.650652 34.538274, -92.650597 34.538104, -92.650533 34.537994, -92.650358 34.537737, -92.6502 34.537479, -92.649749 34.536703, -92.649699 34.536617, -92.649547 34.536395, -92.649339 34.536134, -92.649575 34.536178, -92.650284 34.536375, -92.650496 34.536434, -92.651125 34.536642, -92.651539 34.536766, -92.651716 34.536796, -92.651932 34.536796, -92.652203 34.536764, -92.653131 34.536583, -92.653512 34.53651, -92.654088 34.53639, -92.654388 34.536328, -92.655031 34.536213, -92.655381 34.536183, -92.655693 34.536195, -92.656396 34.536265, -92.656562 34.536274, -92.656805 34.53627, -92.657048 34.53625, -92.65728 34.536214, -92.657984 34.536064, -92.65902 34.535873, -92.659403 34.53578, -92.659636 34.535694, -92.660431 34.535335, -92.660907 34.53515, -92.661218 34.535045, -92.661534 34.53495, -92.661855 34.534864, -92.66218 34.534788, -92.66251 34.534683, -92.662688 34.534632, -92.662817 34.534595, -92.66299 34.534555, -92.663222 34.53452, -92.663373 34.534512, -92.663465 34.534514, -92.663558 34.534521, -92.663739 34.534549, -92.663864 34.534579, -92.663988 34.534615, -92.664924 34.534947, -92.664868 34.534209, -92.664855 34.533151, -92.664868 34.531068, -92.664944 34.530113, -92.665147 34.527557, -92.665174 34.526789, -92.66519 34.526346, -92.665237 34.525019, -92.665272 34.524013, -92.665305 34.52308, -92.66558 34.51934, -92.665583 34.519257, -92.665592 34.519011, -92.665596 34.518929, -92.665597 34.518873, -92.665602 34.518706, -92.665604 34.518651, -92.665571 34.51867, -92.665474 34.518727, -92.665442 34.518747, -92.665411 34.518765, -92.665318 34.518819, -92.665288 34.518838, -92.665046 34.51898, -92.664487 34.519311, -92.664319 34.519406, -92.664076 34.519546, -92.663721 34.519755, -92.66355 34.519855, -92.663233 34.520043, -92.662659 34.520384, -92.66238 34.520551, -92.662742 34.521056, -92.662244 34.521275, -92.662607 34.521792, -92.662664 34.521864, -92.662724 34.522806, -92.662713 34.523037, -92.662703 34.523251, -92.662695 34.523424, -92.662685 34.52363, -92.662675 34.523846, -92.662665 34.524062, -92.662655 34.524276, -92.662651 34.52446, -92.66264 34.524673, -92.662626 34.52488, -92.6626 34.525093, -92.662602 34.525303, -92.662392 34.525291, -92.662086 34.525293, -92.661913 34.525293, -92.66148 34.525293, -92.660869 34.525306, -92.660461 34.525327, -92.660484 34.524155, -92.660531 34.52182, -92.660428 34.521709, -92.660397 34.521727, -92.660324 34.52177, -92.660081 34.521915, -92.66002 34.521951, -92.659836 34.522059, -92.659776 34.522096, -92.65976 34.522105, -92.659711 34.522134, -92.659696 34.522144, -92.659612 34.522193, -92.65943 34.522302, -92.659361 34.522343, -92.659278 34.522394, -92.659245 34.522413, -92.659193 34.522445, -92.659145 34.522471, -92.659112 34.52249, -92.659094 34.521032, -92.65904 34.516661, -92.659037 34.516438, -92.657272 34.516394, -92.652259 34.516265, -92.648666 34.516178, -92.646702 34.51492, -92.646298 34.514901, -92.646213 34.514793, -92.645134 34.514199, -92.644906 34.514073, -92.644836 34.514035, -92.644715 34.513968, -92.644649 34.513932, -92.644658 34.513794, -92.644679 34.513407, -92.644681 34.513348, -92.644672 34.512995, -92.644651 34.512641, -92.644643 34.512383, -92.644685 34.512384, -92.645012 34.512391, -92.645654 34.512406, -92.646208 34.512417, -92.646168 34.512522, -92.648037 34.512559, -92.648037 34.512199, -92.648037 34.511042, -92.648091 34.508859, -92.648122 34.507611, -92.648252 34.507129, -92.648235 34.506625, -92.648225 34.506325, -92.648219 34.506148, -92.648195 34.505395, -92.648189 34.505205, -92.648415 34.5052, -92.650315 34.5052, -92.651822 34.505007, -92.652654 34.505201, -92.652819 34.505203, -92.652854 34.503635, -92.653355 34.503649, -92.65394 34.50365, -92.653955 34.503223, -92.653959 34.50311, -92.653971 34.502765, -92.653977 34.502685, -92.654012 34.502246, -92.654044 34.501794, -92.654048 34.501735, -92.653383 34.501711, -92.652896 34.5017, -92.652333 34.501689, -92.651437 34.501641, -92.650951 34.501615, -92.650954 34.501476, -92.650965 34.50106, -92.650969 34.500922, -92.651019 34.500135, -92.651041 34.499577, -92.651049 34.499203, -92.651054 34.499081, -92.651055 34.499016, -92.651093 34.498074, -92.651129 34.497434, -92.651316 34.497438, -92.65188 34.497453, -92.652068 34.497458, -92.652136 34.497463, -92.652273 34.497463, -92.652299 34.497464, -92.652575 34.497476, -92.652653 34.497485, -92.652856 34.497519, -92.652886 34.49752, -92.652998 34.497527, -92.65309 34.497546, -92.65312 34.497582, -92.653117 34.497599, -92.653113 34.497629, -92.652986 34.49773, -92.652936 34.49777, -92.653093 34.497724, -92.653566 34.497586, -92.653724 34.497541, -92.653728 34.497131, -92.653684 34.495928, -92.65368 34.49581, -92.653692 34.495525, -92.653691 34.495171, -92.653689 34.494112, -92.653689 34.493759, -92.65326 34.493754, -92.652365 34.493745, -92.651975 34.493733, -92.651582 34.493722, -92.651547 34.493721, -92.65129 34.493713, -92.651082 34.493712, -92.650742 34.493712, -92.65027 34.493699, -92.64984 34.493694, -92.649686 34.493694, -92.649427 34.493696, -92.649213 34.493814, -92.649113 34.493683, -92.649027 34.493607, -92.648997 34.493554, -92.648971 34.493459, -92.648961 34.493418, -92.648938 34.493208, -92.648918 34.492738, -92.648904 34.492642, -92.648895 34.492574, -92.648862 34.49245, -92.648833 34.49238, -92.648689 34.492098, -92.648602 34.491958, -92.64842 34.4917, -92.648228 34.491439, -92.648015 34.491151, -92.647634 34.49058, -92.647443 34.490274, -92.647412 34.490203, -92.647383 34.490051, -92.647382 34.48974, -92.647363 34.489599, -92.647318 34.489475, -92.647287 34.489423, -92.647231 34.489367, -92.64705 34.489227, -92.646731 34.489024, -92.646396 34.488819, -92.646103 34.48864, -92.646011 34.488564, -92.645966 34.488508, -92.645942 34.488451, -92.645927 34.488391, -92.645917 34.488221, -92.645915 34.48793, -92.646098 34.487892, -92.64626 34.487845, -92.646356 34.487815, -92.646897 34.48762, -92.647373 34.487435, -92.647491 34.487381, -92.647598 34.487319, -92.647657 34.487286, -92.647769 34.487212, -92.647966 34.487063, -92.648039 34.486996, -92.648215 34.486838, -92.648261 34.486796, -92.648336 34.48673, -92.648464 34.486614, -92.648591 34.486501, -92.6484 34.486498, -92.648147 34.486492, -92.647865 34.486485, -92.647504 34.486477, -92.647106 34.486467, -92.645824 34.486437, -92.645428 34.486428, -92.645199 34.486423, -92.644735 34.486405, -92.643027 34.486345, -92.641656 34.486296, -92.641581 34.486289, -92.641525 34.486287, -92.641497 34.486332, -92.64128 34.486694, -92.641178 34.486849, -92.641134 34.486917, -92.64105 34.487035, -92.64099 34.487134, -92.640811 34.487431, -92.640752 34.487531, -92.640666 34.487469, -92.640657 34.487459, -92.64062 34.487414, -92.640602 34.487377, -92.640594 34.487343, -92.640594 34.487273, -92.640604 34.487218, -92.640621 34.48716, -92.640635 34.487126, -92.64065 34.487094, -92.640693 34.487023, -92.640121 34.487029, -92.638405 34.487048, -92.637834 34.487055, -92.636747 34.487055, -92.633487 34.487055, -92.633212 34.487056, -92.631936 34.486911, -92.6319 34.487421, -92.631759 34.489426, -92.631716 34.490555, -92.631698 34.491008, -92.631689 34.491242, -92.631664 34.491899, -92.628907 34.491826, -92.628438 34.491813, -92.628423 34.49007, -92.628422 34.489881, -92.627431 34.489881, -92.627452 34.489255, -92.627319 34.48925, -92.627309 34.489566, -92.627267 34.489565, -92.627231 34.489563, -92.627211 34.489562, -92.626687 34.489539, -92.626459 34.48953, -92.62508 34.48947, -92.625076 34.489567, -92.625076 34.489593, -92.624807 34.489602, -92.624207 34.489623, -92.62421 34.489946, -92.624217 34.490466, -92.623766 34.49046, -92.623443 34.490457, -92.623412 34.490538, -92.623318 34.490781, -92.623288 34.490863, -92.623351 34.490971, -92.623439 34.491171, -92.623492 34.49132, -92.62351 34.49137, -92.623538 34.491437, -92.623556 34.491481, -92.623595 34.491575, -92.623615 34.491615, -92.623637 34.491659, -92.623666 34.491711, -92.623753 34.491867, -92.623782 34.49192, -92.623934 34.492199, -92.623963 34.492271, -92.623998 34.492395, -92.624052 34.49286, -92.624067 34.492985, -92.624083 34.49312, -92.624101 34.493336, -92.624138 34.493485, -92.624162 34.493548, -92.624195 34.493633, -92.624296 34.493853, -92.624342 34.49394, -92.624395 34.494023, -92.624449 34.494088, -92.62451 34.494147, -92.624593 34.494206, -92.624686 34.494256, -92.62482 34.494307, -92.624879 34.494321, -92.625063 34.494348, -92.625152 34.494367, -92.625258 34.494401, -92.625297 34.494413, -92.625383 34.494454, -92.62605 34.494833, -92.626142 34.494894, -92.626262 34.494989, -92.626314 34.49503, -92.627021 34.495687, -92.627159 34.495788, -92.62722 34.495817, -92.627306 34.495846, -92.627398 34.495865, -92.627553 34.495888, -92.627743 34.495907, -92.627853 34.495925, -92.627961 34.495949, -92.628221 34.49604, -92.62833 34.496065, -92.628683 34.496135, -92.628861 34.496185, -92.629081 34.496283, -92.629192 34.496326, -92.629277 34.496344, -92.629365 34.496356, -92.629495 34.496363, -92.629548 34.496377, -92.629597 34.496396, -92.629627 34.496414, -92.629703 34.496482, -92.629751 34.496547, -92.62982 34.496689, -92.629684 34.496886, -92.629565 34.497081, -92.629018 34.497989, -92.628835 34.498279, -92.628586 34.498675, -92.628258 34.499207, -92.628247 34.499225, -92.628046 34.499563, -92.627802 34.499957, -92.627257 34.500792, -92.626916 34.501316, -92.626494 34.501152, -92.626105 34.501016, -92.625964 34.500982, -92.625933 34.500975, -92.625757 34.500944, -92.625427 34.500899, -92.625369 34.500891, -92.625098 34.500872, -92.6242 34.500828, -92.622946 34.500773, -92.622812 34.500768, -92.622316 34.50074, -92.622264 34.500824, -92.622252 34.501275, -92.622173 34.504346, -92.62211 34.504665, -92.622144 34.504671, -92.622142 34.504736, -92.622137 34.504855, -92.622088 34.504984, -92.622 34.505211, -92.621998 34.505288, -92.622002 34.505406, -92.622007 34.505519, -92.621996 34.505594, -92.621993 34.505617, -92.621973 34.505716, -92.62197 34.505744, -92.621966 34.505809, -92.621966 34.505877, -92.621956 34.5062, -92.621952 34.506353, -92.62195 34.50663, -92.621941 34.506674, -92.621934 34.506718, -92.621889 34.506899, -92.621878 34.507002, -92.62188 34.507371, -92.621874 34.50757, -92.621863 34.507633, -92.621972 34.507966, -92.62212 34.507979, -92.622249 34.507982, -92.623569 34.508021, -92.62401 34.508035, -92.624089 34.508031, -92.624158 34.50801, -92.624204 34.50798, -92.62429 34.507893, -92.624327 34.507836, -92.624459 34.507595, -92.62453 34.507467, -92.62484 34.506947, -92.625065 34.506585, -92.625468 34.505901, -92.625488 34.505867, -92.625623 34.505647, -92.625825 34.505288, -92.625841 34.505258, -92.625892 34.505168, -92.625909 34.505139, -92.625997 34.505011, -92.626079 34.504894, -92.626251 34.504621, -92.626334 34.50449, -92.626667 34.503949, -92.627668 34.502328, -92.628002 34.501788, -92.628215 34.501878, -92.628227 34.501883, -92.628644 34.502069, -92.628855 34.502153, -92.629071 34.502239, -92.629367 34.502369, -92.629413 34.50239, -92.629741 34.502521, -92.629878 34.50257, -92.630111 34.502646, -92.63048 34.502739, -92.630504 34.502746, -92.630627 34.502775, -92.630843 34.502834, -92.630973 34.502873, -92.631187 34.502938, -92.631369 34.502982, -92.631502 34.503015, -92.63156 34.503028, -92.631737 34.503069, -92.631796 34.503083, -92.631846 34.503094, -92.631996 34.503128, -92.632047 34.50314, -92.632296 34.503208, -92.633043 34.503415, -92.633293 34.503484, -92.633199 34.503716, -92.63292 34.504411, -92.632827 34.504644, -92.632772 34.504795, -92.632618 34.505228, -92.63261 34.505249, -92.632554 34.5054, -92.632521 34.505487, -92.632421 34.50575, -92.632389 34.505838, -92.632271 34.506147, -92.631966 34.506949, -92.631919 34.507074, -92.631804 34.507385, -92.63196 34.507421, -92.632142 34.50745, -92.632283 34.507466, -92.632305 34.507468, -92.632833 34.50754, -92.633819 34.507653, -92.634325 34.507711, -92.634489 34.507727, -92.634758 34.507754, -92.634887 34.507762, -92.635804 34.507843, -92.635887 34.507792, -92.636874 34.507175, -92.638664 34.506057, -92.63959 34.50548, -92.639967 34.505922, -92.640393 34.506444, -92.640554 34.506631, -92.641351 34.507436, -92.642051 34.508141, -92.642108 34.508193, -92.642392 34.508452, -92.642426 34.508483, -92.642527 34.508613, -92.642631 34.508746, -92.642722 34.508831, -92.642853 34.509094, -92.6433 34.509847, -92.643439 34.510172, -92.643576 34.5107, -92.643624 34.511063, -92.643631 34.511459, -92.64363 34.511554, -92.643631 34.511586, -92.643599 34.512018, -92.643535 34.512965, -92.642028 34.512833, -92.641815 34.51282, -92.641325 34.512792, -92.641176 34.512788, -92.640964 34.512783, -92.640933 34.512797, -92.640908 34.51282, -92.64089 34.512846, -92.640878 34.512886, -92.640876 34.512928, -92.640892 34.513014, -92.640898 34.513044, -92.640947 34.513237, -92.640945 34.513381, -92.640951 34.51382, -92.640954 34.514007, -92.640959 34.514091, -92.640982 34.514158, -92.641021 34.514234, -92.641074 34.5143, -92.641106 34.514338, -92.641143 34.514398, -92.64122 34.514543, -92.641231 34.514592, -92.641236 34.514641, -92.641231 34.514849, -92.641223 34.514982, -92.641213 34.515173, -92.641207 34.515223, -92.641196 34.51536, -92.641184 34.515516, -92.641176 34.515773, -92.641172 34.515911, -92.641061 34.515908, -92.640731 34.515902, -92.640621 34.5159, -92.639625 34.515888, -92.639368 34.515885, -92.639065 34.515881, -92.637838 34.515838, -92.635967 34.515792, -92.634802 34.515772, -92.634742 34.515771, -92.63474 34.51606, -92.634696 34.516779, -92.634677 34.517085, -92.634677 34.517177, -92.634685 34.517266, -92.63469 34.517329, -92.634718 34.51739, -92.634744 34.517419, -92.634777 34.517444, -92.634822 34.517471, -92.634913 34.517502, -92.635177 34.517571, -92.635655 34.517692, -92.637015 34.518037, -92.637217 34.518103, -92.637292 34.518138, -92.63744 34.518232, -92.637705 34.518432, -92.638021 34.518653, -92.63833 34.51889, -92.638448 34.518994, -92.63853 34.519116, -92.638562 34.519201, -92.638566 34.51921, -92.63857 34.519286, -92.638564 34.519362, -92.638426 34.520188, -92.63841 34.520314, -92.63839 34.520514, -92.638387 34.520552, -92.638361 34.521117, -92.638353 34.521318, -92.638304 34.521301, -92.638157 34.521253, -92.638109 34.521237, -92.636888 34.520994, -92.635801 34.52077, -92.635045 34.520615, -92.634615 34.520534, -92.634377 34.520502, -92.633731 34.520448, -92.633481 34.520421, -92.633045 34.520395, -92.632997 34.520403, -92.632915 34.520416, -92.632789 34.520467, -92.632744 34.520503, -92.632715 34.520552, -92.632705 34.520695, -92.632678 34.520778, -92.632633 34.520833, -92.63255 34.520879, -92.632665 34.520985, -92.632751 34.521063, -92.632757 34.521418, -92.632547 34.521422, -92.6324 34.521545, -92.632385 34.521562, -92.63236 34.521566, -92.632211 34.521636, -92.632093 34.521664, -92.631971 34.521652, -92.631834 34.521596, -92.631783 34.521594, -92.631658 34.521613, -92.631528 34.52167, -92.63127 34.521855, -92.631322 34.521884, -92.631563 34.521911, -92.631731 34.522194, -92.632226 34.522827, -92.632392 34.523038, -92.632578 34.523255, -92.633014 34.523767, -92.633025 34.523703, -92.633057 34.523511, -92.633069 34.523448, -92.633117 34.523485, -92.633289 34.523579, -92.633408 34.523636, -92.63358 34.523719, -92.633694 34.52378, -92.633863 34.523904, -92.63423 34.524231, -92.634301 34.524278, -92.634354 34.524303, -92.63438 34.524316, -92.6345 34.524343, -92.634691 34.524371, -92.634735 34.52438, -92.634943 34.52442, -92.635027 34.524431, -92.635154 34.524438, -92.635355 34.524419, -92.63571 34.524358, -92.635953 34.524317, -92.636448 34.524252, -92.636654 34.52424, -92.636773 34.524241, -92.636891 34.524251, -92.63698 34.524269, -92.637068 34.524293, -92.637246 34.524363, -92.637531 34.524519, -92.63766 34.524582, -92.637862 34.524666, -92.638148 34.524761, -92.638537 34.524877, -92.638701 34.524926, -92.638889 34.524999, -92.638962 34.525036, -92.63909 34.525116, -92.639184 34.525188, -92.63929 34.525291, -92.639376 34.525367, -92.639595 34.525613, -92.639696 34.52571, -92.640135 34.526043, -92.640299 34.526155, -92.640365 34.5262, -92.640455 34.526252, -92.640607 34.526325, -92.640787 34.526387, -92.640903 34.526413, -92.641129 34.526445, -92.64191 34.526529, -92.643626 34.526687, -92.64383 34.526714, -92.643884 34.526723, -92.644032 34.52675, -92.644326 34.526815, -92.644528 34.526848, -92.644699 34.52685, -92.644812 34.52684, -92.64498 34.526808, -92.645083 34.526775, -92.645147 34.526755, -92.645209 34.526743, -92.645269 34.526733, -92.645413 34.526735, -92.645597 34.526753, -92.645727 34.526767, -92.646038 34.526815, -92.646928 34.526954, -92.646971 34.526956, -92.647286 34.526976, -92.647261 34.527465, -92.647186 34.528934, -92.647185 34.528961, -92.647204 34.529291, -92.647234 34.529421, -92.647281 34.529629, -92.647285 34.529644, -92.647407 34.529986, -92.647518 34.530227, -92.647608 34.530422, -92.647559 34.530444, -92.647413 34.53051, -92.647365 34.530533, -92.647002 34.530699, -92.646709 34.530834, -92.64634 34.530992, -92.645908 34.531189, -92.645546 34.531356, -92.645355 34.531444, -92.645241 34.531505, -92.645134 34.531572, -92.645075 34.531618, -92.645038 34.531655, -92.64498 34.531711, -92.644919 34.531772, -92.644808 34.531905, -92.644714 34.532046, -92.644644 34.532186, -92.64459 34.532331, -92.644546 34.532509, -92.644503 34.532796, -92.644421 34.533597, -92.644386 34.533947, -92.644342 34.534127, -92.644293 34.534254, -92.644261 34.534314, -92.644215 34.534401, -92.644157 34.534489, -92.64412 34.534547, -92.643806 34.534973, -92.642866 34.536253, -92.6427 34.53648, -92.64265 34.536546, -92.642691 34.53565, -92.64276 34.534124, -92.642755 34.534078, -92.642686 34.534589, -92.642482 34.536124, -92.642414 34.536636, -92.642412 34.536682, -92.642409 34.536823, -92.642408 34.53687, -92.642141 34.537228, -92.642103 34.537277, -92.64119 34.5385, -92.640886 34.538908, -92.640671 34.539188, -92.640556 34.539322, -92.640465 34.539406, -92.640373 34.53949, -92.640183 34.539613, -92.640053 34.539699, -92.639908 34.539772, -92.639793 34.539818, -92.639614 34.539871, -92.639506 34.539895, -92.639354 34.539922, -92.63919 34.539941, -92.638978 34.539958, -92.63889 34.539966, -92.638366 34.539992, -92.637572 34.54004, -92.637056 34.540067, -92.636415 34.540101, -92.636207 34.540111, -92.635857 34.540129, -92.635586 34.540144, -92.63538 34.540156, -92.635318 34.540159, -92.635134 34.54017, -92.635073 34.540174, -92.634799 34.540191, -92.633978 34.540242, -92.633884 34.540248, -92.633769 34.540269, -92.633708 34.540284, -92.633645 34.540299, -92.633445 34.540356, -92.633172 34.540472, -92.632956 34.540604, -92.632799 34.540726, -92.632733 34.540789, -92.632633 34.540888, -92.632555 34.540987, -92.632488 34.541089, -92.632391 34.541264, -92.632377 34.541296, -92.632296 34.541487, -92.63225 34.541604, -92.632203 34.54173, -92.632015 34.542229, -92.631968 34.542358, -92.631847 34.542353, -92.631713 34.542717, -92.631347 34.54371, -92.6312 34.54401, -92.631072 34.544156, -92.630658 34.544563, -92.629783 34.545283, -92.629013 34.545901, -92.628499 34.546314, -92.624859 34.549569, -92.621695 34.552453, -92.62169 34.552576, -92.621604 34.552655, -92.62156 34.552728, -92.62143 34.552808, -92.621315 34.552883, -92.621256 34.552926, -92.621244 34.55297, -92.620227 34.553889, -92.620189 34.553785, -92.620085 34.553731, -92.619938 34.553702, -92.619887 34.553705, -92.619838 34.553716, -92.619793 34.553739, -92.61968 34.553837, -92.619506 34.554011, -92.619304 34.554229, -92.618942 34.554621, -92.618615 34.554958, -92.618511 34.555059, -92.618347 34.555198, -92.618263 34.555244, -92.618238 34.55525, -92.618192 34.55525, -92.618025 34.555206, -92.617901 34.555184, -92.617264 34.555195, -92.617069 34.55519, -92.616506 34.555155, -92.616316 34.555135, -92.616013 34.555097, -92.61568 34.555057, -92.615432 34.555045, -92.61531 34.555052, -92.615216 34.555069, -92.615123 34.555093, -92.614971 34.555143, -92.614867 34.555159, -92.61479 34.555164, -92.614778 34.555122, -92.614733 34.55506, -92.61471 34.555028, -92.614709 34.554992, -92.61472 34.554872, -92.614716 34.554791, -92.614705 34.554712, -92.614672 34.554598, -92.614654 34.554287, -92.614608 34.554061, -92.61451 34.554056, -92.614222 34.554044, -92.613902 34.554047, -92.613575 34.554041, -92.613193 34.554022, -92.612618 34.55398, -92.612595 34.553983, -92.612539 34.553994, -92.612479 34.554028, -92.612427 34.554091, -92.612401 34.554159, -92.612398 34.554194, -92.612402 34.554517, -92.612405 34.554634, -92.612408 34.55474, -92.612327 34.554972, -92.612289 34.555083, -92.612121 34.555077, -92.612004 34.55507, -92.611823 34.55506, -92.611348 34.555041, -92.611151 34.555037, -92.611074 34.55504, -92.611001 34.555048, -92.610947 34.555065, -92.610913 34.555076, -92.610878 34.555099, -92.61085 34.555136, -92.610829 34.555177, -92.610806 34.555247, -92.610794 34.555388, -92.61079 34.556061, -92.610783 34.556344, -92.610776 34.55666, -92.610515 34.556653, -92.609734 34.556632, -92.609474 34.556625, -92.609183 34.556617, -92.608781 34.556606, -92.608311 34.556581, -92.608021 34.556567, -92.608 34.556522, -92.607942 34.556518, -92.607875 34.556511, -92.607804 34.556504, -92.60771 34.556515, -92.607657 34.556321, -92.60764 34.556261, -92.607626 34.556196, -92.607616 34.556127, -92.607608 34.556055, -92.607603 34.555982, -92.607598 34.555904, -92.607594 34.555822, -92.607586 34.555736, -92.607579 34.555649, -92.607571 34.555559, -92.607561 34.555464, -92.60755 34.555369, -92.607538 34.555272, -92.607527 34.555174, -92.607518 34.555075, -92.607512 34.55498, -92.607509 34.554893, -92.607509 34.554811, -92.607509 34.554783, -92.60751 34.554728, -92.607511 34.554641, -92.607509 34.554551, -92.607505 34.554458, -92.607501 34.554363, -92.607496 34.554266, -92.607491 34.554072, -92.607491 34.553875, -92.607492 34.553777, -92.607491 34.553582, -92.60749 34.553484, -92.607489 34.553289, -92.60749 34.553191, -92.607492 34.553094, -92.607494 34.552997, -92.607498 34.552801, -92.607495 34.552703, -92.607489 34.552609, -92.607479 34.552519, -92.607467 34.552435, -92.607444 34.552294, -92.607431 34.552242, -92.607409 34.552174, -92.607392 34.552123, -92.607348 34.552071, -92.607332 34.552027, -92.607324 34.551649, -92.607297 34.550223, -92.607238 34.550045, -92.60826 34.549537, -92.608181 34.549438, -92.607719 34.548801, -92.607588 34.548626, -92.607439 34.548445, -92.607114 34.548066, -92.607003 34.547929, -92.606985 34.547907, -92.606834 34.547709, -92.60673 34.547592, -92.606681 34.547523, -92.607068 34.546919, -92.607135 34.546815, -92.607691 34.545968, -92.60795 34.545589, -92.608115 34.545351, -92.608403 34.544942, -92.608948 34.544131, -92.605258 34.543854, -92.60509 34.543908, -92.604759 34.544015, -92.604773 34.543996, -92.604826 34.543963, -92.604844 34.543951, -92.60483 34.54388, -92.604806 34.543818, -92.604772 34.543725, -92.604657 34.543413, -92.604507 34.543063, -92.604453 34.542931, -92.604418 34.542845, -92.60441 34.542825, -92.604393 34.542782, -92.604191 34.542176, -92.603005 34.543187, -92.60239 34.543712, -92.602084 34.543695, -92.603833 34.542206, -92.605193 34.54109, -92.604988 34.540926, -92.604769 34.540747, -92.604176 34.541011, -92.604302 34.540365, -92.602495 34.541662, -92.60106 34.542692, -92.600705 34.542946, -92.600513 34.543085, -92.599781 34.54361, -92.599556 34.543602, -92.599449 34.543598, -92.599346 34.543594, -92.599724 34.543318, -92.600531 34.542727, -92.601207 34.542234, -92.602805 34.541066, -92.602665 34.541019, -92.602057 34.540803, -92.601489 34.540603, -92.60109 34.540463, -92.600873 34.540385, -92.59996 34.540063, -92.600046 34.539924, -92.600128 34.53979, -92.600524 34.539148, -92.600512 34.53881, -92.600112 34.538646, -92.600159 34.537621, -92.598152 34.537586, -92.598137 34.538556, -92.597329 34.538534, -92.597061 34.538527, -92.596801 34.53852, -92.596786 34.538758, -92.596743 34.539469, -92.596737 34.539564, -92.596089 34.539541, -92.595413 34.539517, -92.594649 34.53949, -92.594651 34.539441, -92.594536 34.539442, -92.594589 34.538963, -92.591263 34.538685, -92.591268 34.538573, -92.591285 34.538238, -92.591291 34.538127, -92.591422 34.537132, -92.591467 34.537008, -92.591507 34.536758, -92.591533 34.536607, -92.591551 34.536483, -92.591592 34.536231, -92.59151 34.536214, -92.589928 34.535824, -92.589923 34.536028, -92.58992 34.536134, -92.589895 34.537111, -92.589834 34.539352, -92.589779 34.540405, -92.589739 34.541157, -92.589781 34.541257, -92.589788 34.541295, -92.589775 34.541496, -92.589737 34.542101, -92.589725 34.542303, -92.589724 34.54232, -92.589722 34.542374, -92.589722 34.5424, -92.589653 34.5424, -92.589253 34.542396, -92.589174 34.542395, -92.589122 34.542395, -92.589102 34.542875, -92.589137 34.5429, -92.589152 34.542912, -92.589228 34.542969, -92.589616 34.54326, -92.589697 34.543279, -92.589696 34.543293, -92.589695 34.543338, -92.589695 34.543354, -92.589097 34.543236, -92.587428 34.543161, -92.584894 34.543048, -92.582888 34.542962, -92.580547 34.542863, -92.579676 34.542828, -92.577362 34.542737, -92.57606 34.542685, -92.575986 34.542684, -92.575998 34.542324, -92.576003 34.542198, -92.576005 34.542173, -92.576008 34.542142, -92.576011 34.542116, -92.576003 34.54205, -92.576 34.54202, -92.575975 34.541947, -92.575953 34.541906, -92.57592 34.541868, -92.575844 34.541769, -92.575818 34.541734, -92.575493 34.541389, -92.57533 34.541236, -92.575212 34.541137, -92.575177 34.541114, -92.575065 34.541053, -92.574958 34.540983, -92.574647 34.541141, -92.574509 34.541231, -92.574426 34.541296, -92.574248 34.541473, -92.57418 34.541569, -92.574135 34.541635, -92.573774 34.542215, -92.573703 34.542319, -92.573621 34.542417, -92.573442 34.542578, -92.573153 34.542798, -92.572953 34.542965, -92.572152 34.543693, -92.571558 34.544233, -92.571407 34.54434, -92.570989 34.544328, -92.570888 34.544323, -92.569844 34.544281, -92.569565 34.544266, -92.569332 34.544258, -92.569211 34.544254, -92.569137 34.544258, -92.569082 34.544271, -92.568987 34.54431, -92.568948 34.544332, -92.56885 34.544401, -92.568659 34.544529, -92.568085 34.544915, -92.567895 34.545044, -92.567904 34.544947, -92.568022 34.544719, -92.568036 34.54468, -92.568047 34.544605, -92.568048 34.544529, -92.568032 34.544386, -92.568021 34.544339, -92.567996 34.544275, -92.56797 34.544205, -92.567904 34.544087, -92.567853 34.544015, -92.567715 34.543858, -92.56758 34.543732, -92.567306 34.543508, -92.567128 34.543347, -92.567052 34.543256, -92.567017 34.543202, -92.566994 34.543134, -92.56699 34.543122, -92.566945 34.54287, -92.56694 34.542746, -92.56694 34.542453, -92.56695 34.542336, -92.566959 34.542259, -92.567002 34.541936, -92.567025 34.541618, -92.567026 34.541456, -92.567042 34.541355, -92.567098 34.540778, -92.567112 34.540473, -92.567112 34.540216, -92.567112 34.540091, -92.567142 34.539679, -92.567175 34.539368, -92.567311 34.536492, -92.567322 34.536274, -92.567322 34.536191, -92.567325 34.535693, -92.567314 34.535615, -92.567282 34.53552, -92.567233 34.535434, -92.56719 34.535381, -92.567117 34.535317, -92.567016 34.535227, -92.566976 34.535197, -92.566868 34.535136, -92.566734 34.535084, -92.566652 34.535062, -92.566603 34.53505, -92.566521 34.535035, -92.566426 34.535016, -92.566328 34.534998, -92.566181 34.534978, -92.56614 34.534974, -92.566021 34.534968, -92.565901 34.534973, -92.565783 34.535001, -92.565577 34.535072, -92.565532 34.535084, -92.565432 34.535128, -92.565306 34.535185, -92.565177 34.53525, -92.565031 34.535341, -92.564713 34.53556, -92.564674 34.535579, -92.564578 34.535615, -92.564544 34.535622, -92.564486 34.535626, -92.564429 34.53562, -92.564338 34.535584, -92.564018 34.535402, -92.563741 34.535253, -92.563711 34.535237, -92.563296 34.535027, -92.563241 34.535003, -92.563167 34.534976, -92.563202 34.534938, -92.563304 34.534834, -92.562963 34.534818, -92.56293 34.534816, -92.562648 34.534799, -92.560758 34.534722, -92.560605 34.534718, -92.559067 34.53466, -92.558542 34.534639, -92.558523 34.534618, -92.558504 34.534617, -92.558157 34.534604, -92.557116 34.534568, -92.55677 34.534556, -92.556118 34.53454, -92.554164 34.534493, -92.554111 34.534492, -92.554083 34.535089, -92.554518 34.535505, -92.555823 34.536753, -92.556259 34.53717, -92.55647 34.537371, -92.556558 34.537455, -92.55661 34.537504, -92.556406 34.537579, -92.556107 34.537627, -92.555842 34.537638, -92.555477 34.537627, -92.55546 34.537626, -92.55546 34.537682, -92.555459 34.537726, -92.555451 34.538059, -92.55545 34.53814, -92.555701 34.538167, -92.555668 34.539035, -92.555642 34.539663, -92.555823 34.539668, -92.555946 34.539671, -92.55593 34.540016, -92.555821 34.540014, -92.555765 34.54001, -92.55571 34.541065, -92.555676 34.541704, -92.555672 34.541801, -92.554988 34.541774, -92.553878 34.541731, -92.553843 34.541726, -92.553752 34.541716, -92.55374 34.541713, -92.553707 34.541705, -92.553518 34.544572, -92.553235 34.544432, -92.553166 34.545409, -92.55311 34.546191, -92.552966 34.548824, -92.550391 34.54877, -92.550098 34.548207, -92.549115 34.548219, -92.549116 34.548675, -92.54887 34.548678, -92.548714 34.548676, -92.548011 34.548678, -92.548196 34.548983, -92.547833 34.548957, -92.547815 34.548936, -92.547775 34.548886, -92.547435 34.548883, -92.547336 34.548882, -92.547284 34.548881, -92.547067 34.548879, -92.546679 34.548875, -92.546684 34.548809, -92.546701 34.548615, -92.546714 34.548456, -92.546718 34.548408, -92.546733 34.548214, -92.546752 34.548, -92.546757 34.547803, -92.546759 34.547715, -92.54676 34.547595, -92.546526 34.547306, -92.54623 34.546918, -92.5456 34.54614, -92.545592 34.546364, -92.545588 34.546483, -92.545572 34.546956, -92.54557 34.547419, -92.545575 34.547679, -92.545574 34.547932, -92.545574 34.548025, -92.545571 34.54811, -92.545562 34.548343, -92.545547 34.548715, -92.54535 34.54871, -92.545232 34.54871, -92.544895 34.548705, -92.544802 34.548703, -92.544722 34.548703, -92.544184 34.548686, -92.544372 34.549061, -92.543972 34.549242, -92.543933 34.549219, -92.543896 34.549196, -92.543396 34.548759, -92.54329 34.548668, -92.543164 34.548672, -92.541678 34.548655, -92.541149 34.548647, -92.540527 34.548639, -92.540202 34.548634, -92.540204 34.5486, -92.540211 34.548437, -92.537938 34.548399, -92.537529 34.548392, -92.537521 34.548466, -92.53752 34.54874, -92.537514 34.548836, -92.537506 34.548969, -92.537517 34.548987, -92.537467 34.549767, -92.537447 34.550093, -92.537413 34.550519, -92.537312 34.5518, -92.537285 34.55215, -92.537279 34.552227, -92.537252 34.552866, -92.537218 34.553694, -92.537172 34.554784, -92.537146 34.555424, -92.537129 34.555486, -92.537079 34.555673, -92.537063 34.555736, -92.536829 34.555734, -92.536128 34.555728, -92.535895 34.555727, -92.535895 34.555657, -92.535897 34.55545, -92.535899 34.555381, -92.535539 34.555379, -92.534461 34.555374, -92.534102 34.555373, -92.534111 34.555202, -92.534124 34.555048, -92.534133 34.554957, -92.534157 34.55481, -92.534158 34.554742, -92.534153 34.554674, -92.534135 34.554588, -92.53413 34.554489, -92.534152 34.554274, -92.534159 34.554105, -92.53416 34.554078, -92.534161 34.554067, -92.53416 34.554017, -92.534157 34.553811, -92.534159 34.553784, -92.534084 34.553784, -92.533427 34.553774, -92.533421 34.5539, -92.533418 34.554052, -92.532689 34.554043, -92.532585 34.554041, -92.532646 34.552775, -92.532679 34.552096, -92.532738 34.551603, -92.532767 34.551294, -92.532794 34.55101, -92.532802 34.550932, -92.532823 34.550718, -92.532839 34.550545, -92.532827 34.55055, -92.532747 34.550544, -92.532675 34.550539, -92.532581 34.550512, -92.532393 34.550381, -92.532289 34.550308, -92.532163 34.550193, -92.53213 34.550142, -92.53211 34.55011, -92.532063 34.549863, -92.532263 34.549599, -92.532303 34.549319, -92.532263 34.549236, -92.532144 34.549137, -92.532031 34.548906, -92.532011 34.548813, -92.531865 34.54867, -92.531851 34.548582, -92.531871 34.548533, -92.531986 34.54838, -92.531913 34.54838, -92.531824 34.548378, -92.529651 34.548341, -92.52923 34.548333, -92.52833 34.548318, -92.527627 34.548322, -92.526373 34.548322, -92.523781 34.548361, -92.519402 34.548315, -92.519241 34.550635, -92.519183 34.551472, -92.519184 34.551499, -92.519178 34.551658, -92.51916 34.552137, -92.519155 34.552297, -92.519121 34.552929, -92.519021 34.554827, -92.518996 34.55531, -92.518988 34.55546, -92.518985 34.555491, -92.518978 34.555587, -92.518977 34.555619, -92.518794 34.555634, -92.518573 34.555632, -92.518499 34.555631, -92.51839 34.555631, -92.518281 34.555631, -92.517739 34.555625, -92.516496 34.555612, -92.516485 34.555788, -92.516454 34.556252, -92.516434 34.556576, -92.516433 34.556759, -92.516441 34.556909, -92.516459 34.557052, -92.516485 34.557178, -92.516497 34.557213, -92.516564 34.557413, -92.516621 34.557526, -92.516667 34.557603, -92.516687 34.557636, -92.516868 34.557881, -92.516931 34.557986, -92.516959 34.558039, -92.517045 34.558199, -92.517074 34.558253, -92.517126 34.558351, -92.517161 34.558405, -92.517401 34.558776, -92.517445 34.558849, -92.517535 34.559001, -92.518044 34.55878, -92.519572 34.558119, -92.520082 34.557899, -92.520146 34.558048, -92.520263 34.558316, -92.5203 34.55843, -92.520308 34.558507, -92.520309 34.558516, -92.520306 34.558564, -92.520276 34.558666, -92.520245 34.558754, -92.520219 34.55886, -92.52019 34.559062, -92.520196 34.559229, -92.520212 34.5593, -92.520237 34.559361, -92.520297 34.559466, -92.520401 34.559594, -92.520476 34.559672, -92.52056 34.559744, -92.52069 34.559832, -92.520754 34.559856, -92.52082 34.559873, -92.520875 34.559888, -92.521172 34.559943, -92.521377 34.559972, -92.521737 34.559985, -92.522666 34.559981, -92.522769 34.559996, -92.522815 34.560015, -92.522851 34.560041, -92.522909 34.56011, -92.522928 34.560152, -92.522939 34.5602, -92.522941 34.560291, -92.522847 34.560767, -92.522819 34.561036, -92.522826 34.56108, -92.522844 34.56112, -92.522871 34.561145, -92.522951 34.561182, -92.523004 34.561188, -92.523376 34.561197, -92.524025 34.561196, -92.52407 34.561193, -92.524145 34.561181, -92.524182 34.561169, -92.524216 34.561159, -92.52426 34.561139, -92.524317 34.561102, -92.524429 34.560999, -92.524495 34.560946, -92.524587 34.560901, -92.524678 34.560877, -92.524781 34.560866, -92.524955 34.560881, -92.525329 34.560933, -92.525496 34.560948, -92.525598 34.560933, -92.5256 34.560996, -92.525653 34.561117, -92.525796 34.561305, -92.525853 34.56137, -92.525919 34.561445, -92.52629 34.561796, -92.526385 34.561862, -92.526488 34.561919, -92.526678 34.561995, -92.526828 34.562027, -92.527016 34.562029, -92.527179 34.562045, -92.52728 34.562056, -92.527509 34.562068, -92.527578 34.562057, -92.527606 34.562043, -92.527633 34.562007, -92.527638 34.561966, -92.527636 34.561766, -92.527635 34.561659, -92.527654 34.561168, -92.527663 34.560969, -92.527674 34.560784, -92.527711 34.560232, -92.527723 34.560048, -92.527736 34.559861, -92.527757 34.559589, -92.527773 34.559303, -92.527785 34.559117, -92.527794 34.558928, -92.527823 34.558363, -92.527833 34.558175, -92.527851 34.557908, -92.527884 34.557458, -92.5279 34.557108, -92.527913 34.556842, -92.527916 34.556739, -92.527933 34.556562, -92.527985 34.556057, -92.527987 34.555916, -92.527981 34.555874, -92.527942 34.555808, -92.527889 34.555754, -92.527787 34.55568, -92.527719 34.555643, -92.52765 34.555613, -92.527658 34.55559, -92.527685 34.555522, -92.527695 34.5555, -92.528649 34.555899, -92.529911 34.556428, -92.531129 34.556648, -92.53163 34.556641, -92.532353 34.556631, -92.532369 34.556628, -92.532478 34.556607, -92.532478 34.556664, -92.532479 34.556691, -92.533284 34.556614, -92.534567 34.556452, -92.534562 34.556148, -92.534514 34.556148, -92.534503 34.556148, -92.534485 34.55615, -92.534425 34.55616, -92.534435 34.555815, -92.534437 34.555719, -92.534449 34.555719, -92.534573 34.555719, -92.53539 34.555724, -92.535365 34.55616, -92.535351 34.556434, -92.535775 34.556437, -92.536936 34.556614, -92.536927 34.556745, -92.536924 34.5568, -92.536918 34.557435, -92.536917 34.557566, -92.536917 34.557665, -92.536916 34.557687, -92.536911 34.557778, -92.536901 34.558001, -92.536861 34.558886, -92.536852 34.559011, -92.536838 34.559224, -92.536831 34.559348, -92.536828 34.55939, -92.536825 34.559427, -92.53681 34.559664, -92.536807 34.559718, -92.536805 34.559743, -92.536804 34.559766, -92.536796 34.559888, -92.536769 34.560323, -92.536761 34.560469, -92.536722 34.560931, -92.536718 34.560984, -92.536681 34.561578, -92.536623 34.562534, -92.536592 34.563051, -92.536545 34.563825, -92.536408 34.56615, -92.536398 34.566334, -92.536329 34.566632, -92.536315 34.566808, -92.53637 34.566808, -92.53654 34.566811, -92.536938 34.566818, -92.537745 34.566821, -92.538574 34.566827, -92.539975 34.566845, -92.540488 34.566851, -92.540978 34.566833, -92.541117 34.56684, -92.541243 34.566848, -92.541536 34.566842, -92.541676 34.56684, -92.54186 34.566845, -92.542277 34.566846, -92.544081 34.566853, -92.544216 34.566854, -92.544451 34.566866, -92.544568 34.566887, -92.544587 34.566891, -92.544678 34.566912, -92.544686 34.566931, -92.54471 34.566988, -92.544719 34.567008, -92.544824 34.567003, -92.544821 34.567308, -92.544814 34.567589, -92.544841 34.569944, -92.54497 34.570329, -92.54507 34.570431, -92.545237 34.570601, -92.545233 34.570684, -92.545215 34.571143, -92.545161 34.571128, -92.545106 34.571105, -92.545001 34.571061, -92.544942 34.571036, -92.544926 34.57103, -92.544767 34.570959, -92.544709 34.570934, -92.544597 34.571105, -92.544538 34.571188, -92.544465 34.571259, -92.544388 34.571312, -92.544302 34.571355, -92.544233 34.57138, -92.544091 34.571405, -92.543946 34.571412, -92.542871 34.571407, -92.542772 34.571407, -92.54235 34.571411, -92.542071 34.571415, -92.54094 34.571401, -92.540822 34.571413, -92.540711 34.571449, -92.540663 34.571488, -92.540605 34.571564, -92.540561 34.571658, -92.540528 34.571782, -92.54051 34.572161, -92.540465 34.572739, -92.540425 34.573066, -92.540393 34.573192, -92.54036 34.573259, -92.540323 34.573312, -92.540316 34.573322, -92.54027 34.573368, -92.540182 34.573431, -92.540064 34.573484, -92.539987 34.5735, -92.539829 34.57352, -92.539528 34.573536, -92.539338 34.57353, -92.53888 34.573476, -92.538377 34.57343, -92.538187 34.573421, -92.538029 34.573425, -92.538006 34.573426, -92.537758 34.573443, -92.537531 34.573466, -92.537418 34.573479, -92.536079 34.573647, -92.536021 34.573657, -92.535922 34.573692, -92.535895 34.573712, -92.535874 34.573746, -92.535846 34.573859, -92.53584 34.574016, -92.535829 34.574371, -92.5358 34.574958, -92.535786 34.575272, -92.536007 34.575272, -92.536372 34.575229, -92.536634 34.57517, -92.536811 34.575111, -92.536937 34.575059, -92.536968 34.575047, -92.537856 34.574632, -92.538018 34.574561, -92.538127 34.574515, -92.5385 34.574392, -92.538839 34.574306, -92.53904 34.574268, -92.539424 34.574231, -92.539701 34.574215, -92.540016 34.574216, -92.540278 34.57423, -92.540323 34.574236, -92.54037 34.574242, -92.540768 34.574296, -92.540967 34.574336, -92.541277 34.574418, -92.541435 34.574464, -92.541477 34.574478, -92.541625 34.574528, -92.54189 34.574619, -92.54207 34.574681, -92.542219 34.574733, -92.541444 34.575226, -92.540233 34.575997, -92.539119 34.576706, -92.538345 34.5772, -92.538231 34.577268, -92.53789 34.577475, -92.537777 34.577544, -92.537359 34.577806, -92.536105 34.578595, -92.535688 34.578858, -92.53553 34.578956, -92.535059 34.579253, -92.534903 34.579353, -92.53469 34.579484, -92.534052 34.57988, -92.53384 34.580012, -92.533966 34.580159, -92.53409 34.580275, -92.53413 34.580304, -92.534168 34.580321, -92.534199 34.580335, -92.534468 34.580433, -92.534604 34.580468, -92.534648 34.580611, -92.534744 34.580844, -92.534973 34.581293, -92.534988 34.581323, -92.535011 34.581369, -92.534956 34.581374, -92.53491 34.581379, -92.534889 34.581378, -92.53458 34.581365, -92.534232 34.581351, -92.534151 34.581347, -92.53407 34.581344, -92.534051 34.581619, -92.53407 34.58162, -92.534017 34.582214, -92.534006 34.582469, -92.533982 34.58302, -92.53396 34.58353, -92.533955 34.583759, -92.533945 34.58398, -92.533903 34.585004, -92.533785 34.585005, -92.533739 34.585005, -92.53342 34.585009, -92.53342 34.58516, -92.533417 34.585805, -92.533415 34.586193, -92.532694 34.586181, -92.532668 34.586932, -92.532639 34.587773, -92.53263 34.588046, -92.532616 34.588616, -92.532602 34.588684, -92.532602 34.588699, -92.532275 34.588687, -92.531968 34.588671, -92.531729 34.588669, -92.531594 34.588679, -92.531434 34.588716, -92.53127 34.588737, -92.531171 34.58874, -92.531143 34.588741, -92.531125 34.588741, -92.531076 34.588744, -92.531007 34.588741, -92.530887 34.588737, -92.530735 34.588715, -92.530701 34.58871, -92.530634 34.588701, -92.53062 34.588752, -92.530612 34.588794, -92.530606 34.588848, -92.530589 34.589167, -92.530569 34.589533, -92.530541 34.590035, -92.530535 34.59017, -92.530524 34.590541, -92.530523 34.590585, -92.530511 34.590732, -92.530485 34.591049, -92.530461 34.591306, -92.530425 34.591768, -92.530389 34.592145, -92.530385 34.592189, -92.530383 34.592214, -92.5303 34.592211, -92.528658 34.592193, -92.528003 34.592415, -92.527806 34.592407, -92.527216 34.592386, -92.52702 34.592379, -92.526441 34.592379, -92.525004 34.592154, -92.524619 34.59215, -92.52383 34.592142, -92.523767 34.592379, -92.523747 34.592943, -92.523723 34.593006, -92.523652 34.593056, -92.522649 34.593055, -92.522627 34.593253, -92.522616 34.593841, -92.522635 34.59396, -92.523139 34.593977, -92.523125 34.594055, -92.523123 34.594102, -92.523125 34.594177, -92.523129 34.594229, -92.523127 34.594286, -92.523124 34.594344, -92.523122 34.5944, -92.523118 34.594455, -92.523115 34.594509, -92.523111 34.594563, -92.523108 34.594618, -92.523107 34.594672, -92.523106 34.594727, -92.523116 34.594776, -92.52312 34.594887, -92.523144 34.594984, -92.523154 34.595085, -92.523149 34.595143, -92.52315 34.5952, -92.523152 34.59525, -92.523152 34.595306, -92.523162 34.595415, -92.523166 34.595469, -92.523173 34.59552, -92.523189 34.59561, -92.523203 34.595682, -92.523191 34.595747, -92.522754 34.595739, -92.52187 34.595722, -92.520519 34.595696, -92.520166 34.595691, -92.517244 34.595658, -92.515819 34.595641, -92.515741 34.59564, -92.515473 34.595638, -92.515116 34.595636, -92.515057 34.595636, -92.514365 34.595641, -92.513941 34.595647, -92.513354 34.595656, -92.513127 34.595659, -92.51295 34.595656, -92.512874 34.595655, -92.512796 34.59565, -92.512565 34.595636, -92.511619 34.595523, -92.511352 34.595512, -92.511131 34.595516, -92.511084 34.595517, -92.510956 34.595514, -92.510628 34.595518, -92.510574 34.595516, -92.510561 34.595351, -92.510614 34.595219, -92.510688 34.595155, -92.510907 34.594972, -92.510967 34.594901, -92.510971 34.594881, -92.510987 34.594818, -92.510967 34.594681, -92.510874 34.594411, -92.510828 34.594087, -92.510582 34.593548, -92.510529 34.593207, -92.510489 34.592751, -92.510539 34.592556, -92.510552 34.592505, -92.510743 34.591778, -92.510776 34.59119, -92.510769 34.591128, -92.510763 34.591063, -92.510677 34.590931, -92.510438 34.590871, -92.510231 34.590876, -92.510165 34.59086, -92.509932 34.590744, -92.509707 34.590579, -92.509235 34.590123, -92.50923 34.590114, -92.508916 34.589545, -92.508892 34.589497, -92.508859 34.589431, -92.508845 34.589403, -92.50882 34.589354, -92.508797 34.589307, -92.507658 34.590221, -92.506355 34.59127, -92.504244 34.592966, -92.503107 34.593881, -92.502947 34.594009, -92.502623 34.594272, -92.502442 34.594352, -92.502256 34.594437, -92.502256 34.594345, -92.502259 34.594073, -92.50226 34.593982, -92.502251 34.593389, -92.502248 34.593135, -92.502128 34.593132, -92.501855 34.593026, -92.501899 34.59255, -92.501935 34.592167, -92.502027 34.592161, -92.502028 34.592006, -92.50203 34.591853, -92.502665 34.591851, -92.502664 34.59166, -92.502666 34.591438, -92.502668 34.591265, -92.50267 34.59102, -92.50267 34.590989, -92.50219 34.591242, -92.50199 34.591555, -92.501944 34.591538, -92.501905 34.59153, -92.50185 34.591533, -92.501821 34.591542, -92.501811 34.591546, -92.501779 34.591562, -92.501561 34.591697, -92.501425 34.591781, -92.501389 34.591796, -92.50137 34.591801, -92.501328 34.591813, -92.501317 34.591814, -92.501198 34.59182, -92.501112 34.591822, -92.500898 34.591819, -92.500162 34.59181, -92.499998 34.59181, -92.499699 34.59181, -92.499722 34.591281, -92.499754 34.59053, -92.499765 34.590358, -92.499835 34.584555, -92.499852 34.584556, -92.500127 34.58456, -92.500158 34.58456, -92.501411 34.584575, -92.502967 34.584594, -92.503087 34.584596, -92.503738 34.584606, -92.503782 34.583531, -92.503846 34.582228, -92.503906 34.58103, -92.501614 34.581052, -92.500993 34.581058, -92.501002 34.581317, -92.501085 34.583509, -92.50112 34.584062, -92.501128 34.584181, -92.500637 34.58416, -92.500606 34.583556, -92.500511 34.583557, -92.500352 34.583558, -92.500355 34.581068, -92.4999 34.581076, -92.499701 34.581079, -92.499105 34.581089, -92.498907 34.581093, -92.498487 34.581093, -92.498474 34.581093, -92.497228 34.581094, -92.496809 34.581095, -92.496813 34.581323, -92.496827 34.58201, -92.496832 34.582239, -92.496757 34.582239, -92.496533 34.582241, -92.496459 34.582242, -92.496473 34.582056, -92.496514 34.581499, -92.496529 34.581314, -92.496069 34.581301, -92.495132 34.581668, -92.494784 34.581757, -92.494738 34.581757, -92.4946 34.581757, -92.494555 34.581757, -92.49439 34.582113, -92.494304 34.582284, -92.493524 34.583854, -92.493265 34.584378, -92.493175 34.584566, -92.492991 34.584954, -92.492917 34.585135, -92.492839 34.585329, -92.492521 34.585329, -92.492389 34.585329, -92.492201 34.586019, -92.492306 34.58603, -92.492622 34.586065, -92.492543 34.586415, -92.492407 34.587029, -92.492291 34.587462, -92.492234 34.587679, -92.4922 34.58781, -92.49182 34.587859, -92.491561 34.587893, -92.491098 34.587733, -92.491143 34.587996, -92.491014 34.588019, -92.490752 34.588066, -92.490807 34.588169, -92.490602 34.589104, -92.490319 34.590407, -92.489972 34.590848, -92.489535 34.591105, -92.489103 34.591247, -92.489086 34.591248, -92.488426 34.591309, -92.488397 34.591602, -92.488364 34.5916, -92.488268 34.591597, -92.488236 34.591596, -92.488016 34.591591, -92.487356 34.591578, -92.487333 34.591578, -92.487137 34.591571, -92.487098 34.591585, -92.487076 34.591585, -92.487057 34.591598, -92.487043 34.591621, -92.487033 34.591665, -92.487029 34.591687, -92.487027 34.591762, -92.486996 34.592137, -92.486983 34.592295, -92.486753 34.592282, -92.486063 34.592243, -92.485833 34.592231, -92.485397 34.592203, -92.484934 34.592174, -92.484577 34.592111, -92.484515 34.592093, -92.484235 34.592013, -92.484109 34.59199, -92.483862 34.591947, -92.483741 34.591956, -92.483677 34.591962, -92.483458 34.591923, -92.483396 34.591926, -92.482571 34.591973, -92.482549 34.591985, -92.482304 34.592127, -92.482265 34.592191, -92.482148 34.592383, -92.48211 34.592448, -92.482094 34.592709, -92.482046 34.593492, -92.48204 34.593577, -92.48203 34.593753, -92.481986 34.594472, -92.481855 34.596632, -92.481812 34.597352, -92.481804 34.597473, -92.481783 34.597838, -92.481776 34.59796, -92.481749 34.598402, -92.481716 34.598954, -92.480939 34.598919, -92.480497 34.5989, -92.480421 34.598968, -92.480196 34.599175, -92.480122 34.599244, -92.47921 34.600415, -92.478824 34.60154, -92.47846 34.602605, -92.478275 34.603146, -92.477587 34.60498, -92.477117 34.605624, -92.476425 34.606313, -92.474346 34.607863, -92.472498 34.609243, -92.472473 34.609262, -92.47235 34.609354, -92.472296 34.609394, -92.472204 34.609463, -92.472135 34.609512, -92.471929 34.609662, -92.471861 34.609712, -92.471139 34.610253, -92.469435 34.611532, -92.468974 34.611877, -92.468253 34.612419, -92.468235 34.612395, -92.468164 34.612316, -92.468084 34.612266, -92.468036 34.612248, -92.467939 34.612242, -92.467873 34.612257, -92.46784 34.612276, -92.46774 34.612359, -92.467693 34.61241, -92.467503 34.612619, -92.467403 34.612713, -92.467316 34.612782, -92.467232 34.612849, -92.467026 34.612997, -92.466548 34.613326, -92.466345 34.613451, -92.466288 34.613474, -92.466266 34.613479, -92.46625 34.613483, -92.46618 34.613489, -92.466146 34.613486, -92.466075 34.613467, -92.466039 34.61345, -92.46602 34.613436, -92.465986 34.613412, -92.465938 34.613369, -92.465817 34.613238, -92.465724 34.61312, -92.46557 34.612925, -92.465554 34.612904, -92.465523 34.612876, -92.465416 34.612802, -92.46535 34.612778, -92.465224 34.612761, -92.465059 34.612758, -92.464002 34.612746, -92.463472 34.612725, -92.46338 34.612721, -92.462912 34.612703, -92.462821 34.612702, -92.462822 34.612529, -92.462829 34.612011, -92.462832 34.611839, -92.462835 34.611662, -92.462844 34.611135, -92.462848 34.610959, -92.462852 34.610787, -92.462868 34.610272, -92.462874 34.610101, -92.462876 34.609933, -92.462886 34.609432, -92.462889 34.609265, -92.462891 34.609098, -92.462899 34.608598, -92.462902 34.608432, -92.462904 34.608258, -92.462914 34.607736, -92.462917 34.607562, -92.462918 34.607393, -92.462924 34.606887, -92.462926 34.606719, -92.462931 34.606665, -92.462935 34.606544, -92.462954 34.606036, -92.462953 34.606021, -92.462944 34.605883, -92.462933 34.605849, -92.462925 34.605769, -92.462925 34.605684, -92.462926 34.605647, -92.462936 34.605608, -92.462954 34.605574, -92.463014 34.605534, -92.463198 34.605421, -92.463275 34.605374, -92.463359 34.605323, -92.463415 34.605286, -92.463923 34.605012, -92.463976 34.604984, -92.462536 34.605421, -92.461908 34.605422, -92.46138 34.605418, -92.459169 34.605323, -92.458348 34.605306, -92.457874 34.605297, -92.457506 34.60529, -92.457506 34.605351, -92.457115 34.605358, -92.456051 34.605325, -92.45575 34.605315, -92.455666 34.605285, -92.455666 34.605198, -92.455204 34.605191, -92.454795 34.605184, -92.454205 34.605182, -92.454136 34.605176, -92.454103 34.605176, -92.454091 34.605176, -92.454039 34.605183, -92.45257 34.605205, -92.4519 34.605215, -92.4511 34.605199, -92.450892 34.605195, -92.450247 34.605185, -92.449781 34.605179, -92.449416 34.605174, -92.44877 34.605166, -92.447447 34.605149, -92.446519 34.605136, -92.446504 34.605383, -92.446345 34.605375, -92.446243 34.606787, -92.44622 34.606759, -92.446164 34.606705, -92.446134 34.606682, -92.4461 34.606657, -92.445945 34.606564, -92.445741 34.606461, -92.44566 34.606408, -92.445618 34.606375, -92.445579 34.606345, -92.445547 34.60631, -92.445514 34.60627, -92.445497 34.606246, -92.445407 34.606111, -92.445285 34.605862, -92.445253 34.605775, -92.445194 34.605609, -92.445081 34.605335, -92.444544 34.605326, -92.443816 34.605313, -92.443147 34.605301, -92.442526 34.605294, -92.441394 34.60529, -92.44139 34.605148, -92.440034 34.605297, -92.440032 34.605451, -92.440031 34.605555, -92.440028 34.605942, -92.440025 34.606346, -92.440431 34.607638, -92.440412 34.608239, -92.44039 34.608743, -92.440305 34.60899, -92.440184 34.609241, -92.440102 34.609422, -92.44002 34.609603, -92.439938 34.609783, -92.439856 34.609964, -92.439719 34.610262, -92.439676 34.610328, -92.440096 34.61043, -92.440273 34.610492, -92.440445 34.610647, -92.440411 34.611287, -92.440571 34.611281, -92.440664 34.611279, -92.440886 34.611192, -92.440899 34.610838, -92.441015 34.610882, -92.441756 34.611608, -92.441735 34.611821, -92.441844 34.612025, -92.441753 34.612099, -92.441649 34.612022, -92.44163 34.612062, -92.441616 34.612094, -92.441749 34.61259, -92.44175 34.612705, -92.441298 34.613977, -92.441763 34.613658, -92.442299 34.614258, -92.442526 34.614312, -92.442827 34.614, -92.443153 34.614121, -92.443448 34.61422, -92.443372 34.614378, -92.443452 34.614612, -92.443784 34.614616, -92.443782 34.614896, -92.443779 34.615178, -92.443778 34.615465, -92.443774 34.615735, -92.443767 34.616003, -92.443765 34.616145, -92.443762 34.616263, -92.443562 34.616259, -92.443416 34.616258, -92.443414 34.616201, -92.443416 34.616137, -92.442656 34.616144, -92.442659 34.616666, -92.442949 34.617744, -92.44296 34.617815, -92.443057 34.617936, -92.442952 34.618041, -92.442632 34.618296, -92.442659 34.618312, -92.442781 34.618386, -92.442822 34.618411, -92.442957 34.61887, -92.442929 34.619465, -92.44292 34.61964, -92.442872 34.620686, -92.44243 34.6221, -92.442295 34.62253, -92.442246 34.622578, -92.441562 34.623262, -92.441486 34.623338, -92.441382 34.623515, -92.441249 34.62368, -92.440468 34.624658, -92.440354 34.624802, -92.438792 34.624828, -92.438774 34.625009, -92.438675 34.624881, -92.438547 34.624853, -92.438394 34.624806, -92.438097 34.624739, -92.438071 34.624551, -92.438008 34.624437, -92.437901 34.624283, -92.43781 34.624129, -92.437769 34.624032, -92.43774 34.623962, -92.437667 34.623985, -92.437594 34.623535, -92.437531 34.623053, -92.437466 34.622618, -92.437428 34.622521, -92.437476 34.622469, -92.437493 34.622435, -92.437506 34.62241, -92.43752 34.62229, -92.437498 34.622232, -92.437482 34.622211, -92.437452 34.622172, -92.437551 34.622119, -92.437662 34.622061, -92.437856 34.621909, -92.437939 34.621836, -92.438043 34.621757, -92.438135 34.621653, -92.438262 34.621526, -92.438482 34.621521, -92.438525 34.620753, -92.438532 34.620571, -92.438563 34.620384, -92.438588 34.620178, -92.438564 34.619753, -92.438388 34.620604, -92.437884 34.620908, -92.437245 34.621304, -92.437253 34.621234, -92.437259 34.62111, -92.437284 34.620671, -92.437276 34.620453, -92.437264 34.62038, -92.437196 34.620159, -92.437141 34.619882, -92.437128 34.61982, -92.437102 34.619685, -92.437095 34.619636, -92.437087 34.619574, -92.437174 34.619495, -92.437342 34.619266, -92.436778 34.619234, -92.436771 34.619381, -92.436769 34.619479, -92.436762 34.619574, -92.436429 34.619567, -92.436183 34.619562, -92.436038 34.619556, -92.434787 34.619509, -92.434505 34.619505, -92.434511 34.61917, -92.433746 34.619098, -92.432693 34.618998, -92.432 34.618933, -92.43176 34.618942, -92.431416 34.618955, -92.43104 34.618932, -92.430801 34.618918, -92.430725 34.618927, -92.430499 34.618957, -92.430424 34.618967, -92.429892 34.618967, -92.429278 34.618969, -92.429242 34.618969, -92.428296 34.61897, -92.427765 34.618972, -92.427402 34.618935, -92.427415 34.619203, -92.42743 34.619489, -92.427433 34.619546, -92.427311 34.619546, -92.426988 34.619549, -92.426973 34.620561, -92.426872 34.620557, -92.426239 34.620535, -92.426242 34.620631, -92.426251 34.62092, -92.426255 34.621017, -92.425916 34.621033, -92.425927 34.620832, -92.425941 34.620599, -92.425972 34.620085, -92.425982 34.619911, -92.425983 34.619892, -92.425852 34.619887, -92.425558 34.619874, -92.425573 34.619718, -92.425586 34.619551, -92.425576 34.6195, -92.425514 34.619499, -92.424152 34.619471, -92.42397 34.619467, -92.421904 34.619425, -92.42174 34.619494, -92.421734 34.619744, -92.421729 34.62003, -92.421698 34.62164, -92.421688 34.622177, -92.420773 34.622154, -92.419673 34.622127, -92.418959 34.622076, -92.41896 34.62201, -92.418316 34.621992, -92.417213 34.621961, -92.417119 34.621959, -92.417133 34.621622, -92.41714 34.621453, -92.417155 34.621132, -92.417185 34.620457, -92.417193 34.620291, -92.417213 34.619848, -92.416586 34.619842, -92.414706 34.619824, -92.41408 34.619819, -92.413871 34.619818, -92.413684 34.619818, -92.413513 34.619818, -92.413423 34.619839, -92.413398 34.619867, -92.413378 34.61992, -92.413374 34.620001, -92.413365 34.62021, -92.41336 34.620375, -92.413345 34.62087, -92.41334 34.621035, -92.413332 34.621126, -92.413303 34.622029, -92.4133 34.622153, -92.413307 34.6222, -92.413319 34.622215, -92.413345 34.622232, -92.413392 34.622241, -92.413461 34.622245, -92.414109 34.622246, -92.414646 34.622253, -92.415432 34.622257, -92.416109 34.622257, -92.417058 34.622258, -92.417105 34.622261, -92.417097 34.622382, -92.417094 34.622435, -92.417084 34.622625, -92.417076 34.622748, -92.41707 34.622868, -92.417066 34.622934, -92.417059 34.623091, -92.417054 34.623235, -92.417044 34.623508, -92.417034 34.623667, -92.417005 34.624145, -92.416996 34.624305, -92.416989 34.624478, -92.416974 34.624856, -92.415273 34.624768, -92.414766 34.624748, -92.413765 34.624721, -92.413078 34.624697, -92.412751 34.624672, -92.412562 34.624653, -92.412374 34.624626, -92.41219 34.62459, -92.412045 34.624555, -92.411901 34.624514, -92.411832 34.62449, -92.411672 34.624417, -92.411583 34.624366, -92.411501 34.624313, -92.411434 34.62428, -92.411219 34.624196, -92.411099 34.624158, -92.41096 34.624121, -92.410793 34.624088, -92.410025 34.623915, -92.409948 34.623898, -92.409231 34.623723, -92.408653 34.623577, -92.408622 34.623571, -92.408679 34.622351, -92.40871 34.62171, -92.408734 34.621199, -92.408809 34.619665, -92.408834 34.619155, -92.408837 34.619089, -92.40869 34.619087, -92.408552 34.619086, -92.408494 34.619085, -92.40851 34.618503, -92.408522 34.618041, -92.408535 34.617579, -92.408548 34.617116, -92.408562 34.616653, -92.408569 34.616423, -92.408577 34.616191, -92.408584 34.61573, -92.408584 34.615507, -92.407706 34.613883, -92.407346 34.613139, -92.407276 34.612993, -92.407008 34.612438, -92.406781 34.611968, -92.406712 34.611824, -92.405992 34.611815, -92.405919 34.611815, -92.405827 34.611814, -92.405282 34.611807, -92.404621 34.611798, -92.404395 34.611796, -92.404114 34.611792, -92.403886 34.61179, -92.403409 34.611784, -92.401563 34.613177, -92.400454 34.614014, -92.400099 34.614282, -92.400062 34.614062, -92.399945 34.61384, -92.399372 34.614154, -92.399034 34.614149, -92.395759 34.614097, -92.395776 34.614424, -92.394959 34.614423, -92.395064 34.614879, -92.39508 34.614938, -92.395267 34.615618, -92.395478 34.616385, -92.395676 34.617184, -92.395598 34.617223, -92.395589 34.617177, -92.395526 34.616903, -92.395499 34.616785, -92.395391 34.616404, -92.395317 34.616139, -92.39526 34.615956, -92.395164 34.615643, -92.395126 34.6155, -92.395012 34.615072, -92.394975 34.61493, -92.394972 34.61492, -92.394965 34.61489, -92.394963 34.614881, -92.394852 34.614422, -92.394811 34.614248, -92.394788 34.614149, -92.394444 34.612558, -92.394428 34.612464, -92.394418 34.612414, -92.394408 34.61234, -92.394387 34.612176, -92.394377 34.612045, -92.394373 34.611914, -92.394377 34.611783, -92.394386 34.611693, -92.394024 34.611707, -92.393563 34.611699, -92.392035 34.611676, -92.391093 34.61165, -92.390271 34.611629, -92.389865 34.611618, -92.389392 34.611609, -92.387264 34.611573, -92.386754 34.61156, -92.386046 34.611543, -92.385876 34.611539, -92.385641 34.611533, -92.384938 34.611516, -92.384704 34.611511, -92.382871 34.611468, -92.382615 34.61146, -92.378891 34.611352, -92.37835 34.611342, -92.376763 34.611313, -92.376379 34.6113, -92.376349 34.611299, -92.37426 34.611288, -92.373869 34.611288, -92.373709 34.611289, -92.372057 34.611293, -92.37178 34.611294, -92.371507 34.611295, -92.370192 34.611315, -92.36714 34.611362, -92.366248 34.611369, -92.366193 34.61137, -92.364934 34.611362, -92.364918 34.611469, -92.364906 34.611559, -92.364889 34.611792, -92.364882 34.6119, -92.364867 34.612102, -92.364859 34.612238, -92.36485 34.612433, -92.364828 34.613255, -92.36482 34.613594, -92.364807 34.613875, -92.364783 34.614461, -92.364744 34.614716, -92.364735 34.614776, -92.364687 34.614992, -92.364655 34.615143, -92.364559 34.615599, -92.364528 34.615751, -92.364469 34.616012, -92.364293 34.616796, -92.364235 34.617058, -92.364224 34.617107, -92.364194 34.617253, -92.364184 34.617303, -92.363289 34.617291, -92.360604 34.617255, -92.35971 34.617243, -92.359242 34.617239, -92.359096 34.617238, -92.358861 34.617223, -92.358727 34.617191, -92.358445 34.617077, -92.358299 34.617029, -92.358111 34.617003, -92.357965 34.617013, -92.357877 34.617029, -92.357778 34.617048, -92.357633 34.617102, -92.357449 34.617206, -92.357127 34.617205, -92.356321 34.617203, -92.356161 34.617196, -92.356046 34.617191, -92.35584 34.617186, -92.355834 34.617004, -92.355822 34.616609, -92.355825 34.616458, -92.355829 34.616277, -92.355828 34.616086, -92.355852 34.615595, -92.35585 34.615511, -92.355833 34.61546, -92.355805 34.615428, -92.355722 34.615382, -92.355657 34.615374, -92.35531 34.61537, -92.354074 34.615357, -92.353662 34.615353, -92.353191 34.61534, -92.353038 34.615336, -92.352856 34.615315, -92.352828 34.615318, -92.352768 34.615332, -92.352727 34.615353, -92.352689 34.615378, -92.352563 34.615486, -92.352403 34.615612, -92.351983 34.615925, -92.351938 34.615959, -92.351613 34.616216, -92.351583 34.616234, -92.351495 34.616287, -92.351466 34.616306, -92.351447 34.616469, -92.351423 34.616677, -92.351403 34.616961, -92.351392 34.617126, -92.351389 34.617308, -92.35138 34.617855, -92.351378 34.618038, -92.350922 34.618028, -92.349586 34.618002, -92.349557 34.618002, -92.349102 34.618003, -92.349097 34.618187, -92.349088 34.618616, -92.349088 34.618742, -92.34909 34.618927, -92.348691 34.61892, -92.348447 34.618916, -92.347497 34.61891, -92.347099 34.618908, -92.346807 34.618889, -92.346755 34.619778, -92.346743 34.619969, -92.34671 34.620542, -92.3467 34.620734, -92.346598 34.622704, -92.346659 34.622762, -92.346692 34.622815, -92.346714 34.622871, -92.346689 34.624245, -92.346656 34.624691, -92.346641 34.625135, -92.346627 34.625603, -92.346466 34.625597, -92.346461 34.625733, -92.346443 34.626166, -92.346386 34.627615, -92.346362 34.628234, -92.346327 34.628733, -92.343594 34.628667, -92.343337 34.62866, -92.343383 34.627679, -92.343414 34.627025, -92.343459 34.626089, -92.34341 34.626088, -92.33913 34.625974, -92.339129 34.625986, -92.339128 34.626007, -92.339125 34.626061, -92.339121 34.626145, -92.339099 34.626416, -92.339027 34.627306, -92.339004 34.627587, -92.338956 34.628194, -92.33887 34.629224, -92.338845 34.629526, -92.338842 34.629563, -92.342108 34.629665, -92.343194 34.629698, -92.343286 34.629701, -92.343156 34.632411, -92.343115 34.633281, -92.343114 34.633306, -92.343534 34.633321, -92.346239 34.633389, -92.346233 34.633527, -92.346097 34.636468, -92.346087 34.636613, -92.346064 34.636927, -92.346021 34.637525, -92.345905 34.640189, -92.345875 34.640762, -92.345795 34.641829, -92.345789 34.641906, -92.345752 34.642623, -92.345672 34.644196, -92.345628 34.644746, -92.345294 34.644959, -92.341622 34.646914, -92.34116 34.647083, -92.340619 34.64728, -92.340516 34.647432, -92.340171 34.647614, -92.339991 34.64768, -92.339811 34.647784, -92.339313 34.648114, -92.338973 34.648191, -92.33888 34.648246, -92.338834 34.648318, -92.338867 34.648362, -92.33887 34.648381, -92.338887 34.648488, -92.338987 34.648598, -92.336114 34.650138, -92.336074 34.650176, -92.336061 34.650237, -92.336134 34.650314, -92.3363 34.650374, -92.336387 34.650435, -92.33644 34.650572, -92.33646 34.65066, -92.33652 34.650737, -92.336666 34.65083, -92.336739 34.650836, -92.336872 34.650792, -92.336906 34.650797, -92.336925 34.650825, -92.336859 34.650935, -92.336846 34.651056, -92.336646 34.65121, -92.33664 34.651303, -92.33652 34.651402, -92.336433 34.651506, -92.3364 34.651589, -92.336394 34.651704, -92.336487 34.65193, -92.336467 34.652018, -92.336247 34.65215, -92.336181 34.652172, -92.336081 34.652144, -92.335981 34.652177, -92.335835 34.652359, -92.335802 34.65237, -92.335768 34.652375, -92.335675 34.65232, -92.335569 34.652315, -92.335522 34.652364, -92.335482 34.65248, -92.335496 34.652628, -92.335323 34.652738, -92.335263 34.652821, -92.335243 34.652876, -92.33525 34.652936, -92.335343 34.652985, -92.335616 34.653046, -92.335656 34.65309, -92.335636 34.653145, -92.335483 34.653216, -92.33521 34.65326, -92.334844 34.65337, -92.334791 34.653414, -92.334744 34.653497, -92.334778 34.653552, -92.334851 34.653607, -92.334977 34.653761, -92.334964 34.653849, -92.334864 34.653893, -92.334591 34.653915, -92.334492 34.653948, -92.334478 34.653975, -92.334498 34.65403, -92.334651 34.654118, -92.334705 34.654162, -92.334731 34.654217, -92.334685 34.654266, -92.334585 34.654299, -92.334558 34.654321, -92.334558 34.654354, -92.334605 34.654404, -92.334838 34.654558, -92.334864 34.654613, -92.334851 34.654673, -92.334691 34.654739, -92.334472 34.654723, -92.334152 34.654662, -92.333966 34.654684, -92.333926 34.654739, -92.33392 34.654805, -92.334239 34.654959, -92.334299 34.654998, -92.334312 34.655053, -92.334272 34.655141, -92.334 34.655306, -92.33382 34.655459, -92.333574 34.655459, -92.333095 34.655603, -92.332869 34.655614, -92.332762 34.655581, -92.332716 34.655531, -92.332663 34.655421, -92.332656 34.655295, -92.332702 34.655069, -92.332649 34.654987, -92.332576 34.654998, -92.332343 34.65524, -92.332257 34.6553, -92.332177 34.6553, -92.332117 34.655262, -92.332077 34.655091, -92.33205 34.655069, -92.331851 34.654982, -92.331784 34.654971, -92.331751 34.654976, -92.331578 34.655141, -92.331518 34.655174, -92.331452 34.655157, -92.331412 34.655103, -92.331299 34.655026, -92.331232 34.655048, -92.331206 34.655103, -92.331199 34.655191, -92.331172 34.655218, -92.331106 34.655218, -92.330933 34.655169, -92.330826 34.655158, -92.330693 34.655191, -92.33054 34.655279, -92.330467 34.655273, -92.330434 34.655257, -92.330387 34.65518, -92.330394 34.654998, -92.330361 34.654916, -92.330268 34.654866, -92.330088 34.654839, -92.329895 34.654894, -92.329715 34.655009, -92.329616 34.655141, -92.329556 34.655312, -92.329456 34.655405, -92.32939 34.655438, -92.32929 34.655466, -92.329024 34.655482, -92.328724 34.655543, -92.328372 34.655532, -92.328159 34.655576, -92.328046 34.655658, -92.327933 34.655796, -92.327833 34.65584, -92.327713 34.655955, -92.327693 34.656076, -92.327746 34.656241, -92.327733 34.656296, -92.32768 34.65634, -92.327613 34.656362, -92.327381 34.656373, -92.327281 34.656411, -92.327248 34.656406, -92.327214 34.656395, -92.327088 34.656203, -92.327008 34.656181, -92.326942 34.656247, -92.326835 34.656274, -92.326755 34.656214, -92.326682 34.656208, -92.326589 34.656252, -92.326556 34.656247, -92.326496 34.656203, -92.326423 34.656197, -92.326263 34.656241, -92.32611 34.656335, -92.325964 34.65634, -92.325831 34.656296, -92.325684 34.656324, -92.325591 34.656384, -92.325378 34.656368, -92.325185 34.656472, -92.325086 34.656467, -92.324979 34.656384, -92.324886 34.65639, -92.32478 34.656434, -92.3246 34.656544, -92.324001 34.656846, -92.323695 34.65695, -92.323469 34.657055, -92.322957 34.657203, -92.322611 34.657363, -92.322391 34.657423, -92.322289 34.657417, -92.32146 34.657368, -92.321294 34.657374, -92.321227 34.65739, -92.321174 34.657434, -92.321088 34.657566, -92.320775 34.657632, -92.320695 34.657632, -92.320662 34.657616, -92.320555 34.657479, -92.320474 34.65744, -92.320429 34.657418, -92.320143 34.657424, -92.31983 34.65744, -92.319702 34.657545, -92.31967 34.657572, -92.319504 34.657649, -92.319458 34.657726, -92.319464 34.657819, -92.319491 34.657874, -92.319531 34.657929, -92.31967 34.658028, -92.319717 34.658111, -92.31971 34.658171, -92.319664 34.658254, -92.319431 34.658413, -92.319265 34.658452, -92.319078 34.658463, -92.319039 34.658512, -92.319065 34.658622, -92.319291 34.658776, -92.319305 34.658803, -92.319251 34.658847, -92.319178 34.658853, -92.318872 34.658798, -92.318566 34.658705, -92.31848 34.658644, -92.318433 34.658589, -92.3184 34.658507, -92.318393 34.658347, -92.318367 34.658325, -92.3182 34.658287, -92.318067 34.658314, -92.317908 34.658391, -92.317788 34.658512, -92.317748 34.658655, -92.317755 34.658748, -92.317775 34.658803, -92.317868 34.658902, -92.318014 34.658996, -92.31818 34.659056, -92.3182 34.659078, -92.318214 34.659161, -92.318167 34.65921, -92.318101 34.659243, -92.317968 34.659271, -92.317797 34.659266, -92.317748 34.659265, -92.317362 34.659139, -92.316956 34.658913, -92.31685 34.658908, -92.316797 34.658946, -92.316783 34.659001, -92.316837 34.659166, -92.317129 34.659452, -92.317256 34.659529, -92.317336 34.659639, -92.317316 34.659721, -92.317249 34.659743, -92.317109 34.659727, -92.31659 34.659743, -92.316484 34.659733, -92.316431 34.659689, -92.316338 34.659518, -92.316298 34.659496, -92.316085 34.65948, -92.315972 34.659491, -92.315958 34.659518, -92.315998 34.659606, -92.316105 34.6597, -92.316098 34.659755, -92.315965 34.659864, -92.315865 34.659903, -92.315792 34.659914, -92.315719 34.659897, -92.315446 34.659727, -92.315346 34.659683, -92.315286 34.659689, -92.31522 34.659722, -92.315233 34.659804, -92.315406 34.660018, -92.315852 34.660436, -92.315918 34.660519, -92.315945 34.660601, -92.315919 34.660717, -92.315732 34.660909, -92.315639 34.661046, -92.315553 34.661101, -92.315486 34.661101, -92.315433 34.661057, -92.315386 34.660854, -92.315326 34.660777, -92.31526 34.66075, -92.31516 34.660766, -92.315127 34.660777, -92.315087 34.660826, -92.315074 34.660914, -92.31508 34.660969, -92.315213 34.661184, -92.315233 34.661266, -92.315213 34.661371, -92.31518 34.661558, -92.315187 34.661673, -92.315273 34.661772, -92.315493 34.661887, -92.315526 34.661942, -92.315506 34.662058, -92.31542 34.662124, -92.315326 34.662151, -92.315114 34.662118, -92.315087 34.66214, -92.3151 34.662206, -92.315173 34.662305, -92.3151 34.662377, -92.315027 34.662382, -92.314595 34.66225, -92.314488 34.662267, -92.314428 34.662305, -92.314348 34.662443, -92.314289 34.662619, -92.314275 34.662827, -92.314209 34.66291, -92.314089 34.662981, -92.314056 34.663036, -92.314062 34.663119, -92.314169 34.663344, -92.314155 34.663432, -92.314089 34.663504, -92.313989 34.663537, -92.313876 34.663537, -92.31367 34.663493, -92.31359 34.663493, -92.313324 34.663564, -92.313217 34.663542, -92.313197 34.66346, -92.313377 34.663262, -92.313384 34.663207, -92.313311 34.66319, -92.313184 34.663245, -92.313078 34.663256, -92.313018 34.663223, -92.312991 34.663135, -92.313031 34.663053, -92.313271 34.662849, -92.313324 34.662773, -92.31337 34.66269, -92.313364 34.662597, -92.313324 34.662547, -92.313224 34.662514, -92.313084 34.662553, -92.312938 34.662652, -92.312865 34.662657, -92.312772 34.662602, -92.312685 34.662509, -92.312612 34.662476, -92.312499 34.66247, -92.312432 34.662487, -92.312399 34.662569, -92.312432 34.662712, -92.312506 34.662822, -92.312519 34.662965, -92.312399 34.66308, -92.312266 34.663119, -92.3122 34.663108, -92.31214 34.663075, -92.312033 34.66291, -92.311973 34.662871, -92.31184 34.662844, -92.311681 34.662855, -92.311594 34.662915, -92.311548 34.66308, -92.311481 34.66319, -92.311408 34.663201, -92.311195 34.663174, -92.311135 34.663201, -92.311102 34.663289, -92.311015 34.663344, -92.310975 34.663399, -92.310969 34.663647, -92.310915 34.663724, -92.310816 34.663756, -92.310616 34.663756, -92.310377 34.6638, -92.310224 34.663883, -92.31009 34.663993, -92.310037 34.664075, -92.310011 34.664191, -92.310057 34.664273, -92.310177 34.664394, -92.310224 34.664504, -92.310204 34.664559, -92.31011 34.664603, -92.309831 34.664642, -92.309731 34.66468, -92.309678 34.664724, -92.309645 34.664779, -92.309638 34.664867, -92.309685 34.664977, -92.309691 34.66507, -92.309571 34.665318, -92.309478 34.665373, -92.309305 34.6654, -92.309212 34.665477, -92.309179 34.66556, -92.309192 34.665713, -92.309245 34.665796, -92.309505 34.665977, -92.309571 34.666049, -92.309625 34.666159, -92.309605 34.666247, -92.309545 34.66628, -92.309472 34.666285, -92.309199 34.666225, -92.308959 34.666197, -92.308853 34.666214, -92.30884 34.666241, -92.308866 34.666329, -92.308953 34.666461, -92.308959 34.666521, -92.308846 34.666675, -92.308813 34.666758, -92.308846 34.666813, -92.309146 34.667049, -92.309205 34.667126, -92.309192 34.667181, -92.308919 34.667302, -92.308866 34.667346, -92.308819 34.667401, -92.3088 34.667555, -92.30884 34.66767, -92.308986 34.667879, -92.309092 34.667973, -92.309252 34.668061, -92.309338 34.668127, -92.309352 34.668242, -92.309345 34.66827, -92.308893 34.668479, -92.30874 34.668621, -92.3085 34.668979, -92.3085 34.669023, -92.30856 34.66916, -92.308606 34.669391, -92.308412 34.669597, -92.308394 34.669616, -92.30834 34.669699, -92.308334 34.669754, -92.308387 34.669891, -92.30854 34.670034, -92.308573 34.670117, -92.30858 34.670243, -92.308646 34.670348, -92.308706 34.670392, -92.308739 34.670474, -92.30866 34.670611, -92.308586 34.670683, -92.308487 34.67093, -92.308487 34.670996, -92.308573 34.671348, -92.308553 34.671469, -92.308666 34.671777, -92.308626 34.671887, -92.308467 34.672057, -92.308407 34.672167, -92.308407 34.672321, -92.30848 34.672486, -92.308467 34.672541, -92.30836 34.672667, -92.308334 34.67275, -92.308393 34.672948, -92.30838 34.673003, -92.308234 34.673146, -92.308227 34.673179, -92.308267 34.673261, -92.308327 34.6733, -92.308467 34.673344, -92.308586 34.67342, -92.308766 34.673668, -92.308819 34.673838, -92.308813 34.673965, -92.308779 34.67402, -92.308693 34.67408, -92.30836 34.674168, -92.308267 34.674223, -92.308234 34.674272, -92.308187 34.674542, -92.308147 34.674652, -92.308047 34.674701, -92.307768 34.674751, -92.307648 34.674949, -92.307588 34.674993, -92.307482 34.675031, -92.307402 34.675037, -92.307262 34.675004, -92.307089 34.674938, -92.306963 34.674905, -92.306823 34.674916, -92.30673 34.674965, -92.306716 34.674998, -92.306663 34.675086, -92.306637 34.675196, -92.30665 34.675256, -92.306676 34.675344, -92.30681 34.675493, -92.306883 34.675608, -92.306889 34.675768, -92.306863 34.675856, -92.30673 34.676037, -92.30667 34.676306, -92.30657 34.676438, -92.30631 34.67662, -92.306231 34.676757, -92.306244 34.676906, -92.306457 34.677186, -92.306477 34.677235, -92.306463 34.677323, -92.306403 34.677505, -92.306284 34.677609, -92.306051 34.677692, -92.305991 34.67773, -92.305938 34.677785, -92.305871 34.677928, -92.305878 34.678208, -92.305991 34.678456, -92.306144 34.678632, -92.30615 34.678698, -92.306137 34.678753, -92.305858 34.678967, -92.305818 34.679049, -92.305811 34.679176, -92.305878 34.679456, -92.305891 34.6795, -92.305911 34.67961, -92.305838 34.679775, -92.305698 34.680446, -92.305671 34.680825, -92.305771 34.6811, -92.305784 34.681193, -92.305625 34.681611, -92.305678 34.681869, -92.305584 34.682199, -92.305604 34.682381, -92.305791 34.682678, -92.305817 34.68276, -92.305817 34.682853, -92.305758 34.683035, -92.305751 34.683321, -92.305717 34.683491, -92.305598 34.683612, -92.305172 34.683898, -92.305005 34.68403, -92.304606 34.68425, -92.30454 34.684261, -92.30444 34.684244, -92.30418 34.68414, -92.304067 34.684145, -92.304 34.684173, -92.303814 34.684332, -92.303608 34.684453, -92.303016 34.684761, -92.302889 34.684799, -92.302663 34.684799, -92.302483 34.684827, -92.302377 34.68486, -92.30227 34.684953, -92.302197 34.685068, -92.30215 34.68536, -92.302024 34.685585, -92.301957 34.68575, -92.301897 34.68625, -92.301904 34.686773, -92.301897 34.687086, -92.301751 34.687377, -92.301684 34.687685, -92.301757 34.68807, -92.301744 34.688196, -92.301691 34.688334, -92.301451 34.688609, -92.301383 34.688744, -92.302961 34.688877, -92.302991 34.68989, -92.30302 34.689947, -92.303029 34.690097, -92.303044 34.692366, -92.303045 34.692404, -92.302958 34.693236, -92.302966 34.693466, -92.30298 34.693881, -92.303149 34.693606, -92.30353 34.692975, -92.303865 34.692447, -92.303888 34.69241, -92.304264 34.691798, -92.304463 34.691593, -92.304563 34.691497, -92.304828 34.691216, -92.304985 34.691061, -92.305422 34.690605, -92.305647 34.690348, -92.305764 34.690194, -92.305821 34.69011, -92.306123 34.689632, -92.306261 34.689405, -92.306288 34.689368, -92.306353 34.689247, -92.306362 34.689227, -92.306378 34.689188, -92.306415 34.68908, -92.306488 34.689085, -92.306661 34.689097, -92.307026 34.689136, -92.307532 34.689151, -92.307701 34.689165, -92.307998 34.689188, -92.307979 34.689691, -92.309075 34.689754, -92.309803 34.689765, -92.309849 34.689755, -92.30989 34.689733, -92.309921 34.689703, -92.309939 34.689666, -92.309969 34.68929, -92.311691 34.689371, -92.312138 34.689388, -92.31297 34.689433, -92.312962 34.68965, -92.312923 34.690187, -92.312859 34.691228, -92.312792 34.692221, -92.31277 34.69261, -92.312744 34.692898, -92.312747 34.692964, -92.312747 34.693049, -92.312754 34.693111, -92.312774 34.693189, -92.312808 34.693276, -92.312842 34.693341, -92.312882 34.693396, -92.312925 34.693442, -92.313003 34.69351, -92.313133 34.693591, -92.314097 34.69414, -92.3142 34.694199, -92.314559 34.694391, -92.314661 34.694461, -92.314721 34.694514, -92.314764 34.694562, -92.314824 34.694654, -92.314869 34.694741, -92.31489 34.694809, -92.314909 34.694895, -92.314915 34.694961, -92.314911 34.695047, -92.314895 34.695155, -92.314745 34.697032, -92.314643 34.698434, -92.314711 34.698453, -92.314802 34.698509, -92.314883 34.69859, -92.315178 34.698858, -92.315296 34.698996, -92.315369 34.699138, -92.315406 34.69932, -92.315397 34.699522, -92.315332 34.700187, -92.315317 34.700427, -92.315284 34.700958, -92.316673 34.700935, -92.316673 34.701181, -92.317826 34.701205, -92.320698 34.701266, -92.321998 34.701293, -92.324685 34.701195, -92.324743 34.701193, -92.323506 34.702368, -92.323412 34.702457, -92.323356 34.70251, -92.323301 34.702564, -92.322011 34.703789, -92.32162 34.704166, -92.320996 34.704755, -92.319496 34.706193, -92.318486 34.707134, -92.315513 34.709968, -92.314783 34.710677, -92.312528 34.712832, -92.310763 34.714508, -92.310099 34.715146, -92.308699 34.716481, -92.308573 34.716601, -92.308463 34.716706, -92.308421 34.71669, -92.308171 34.716595, -92.307585 34.716331, -92.307438 34.716183, -92.307359 34.716073, -92.307319 34.715979, -92.307252 34.715902, -92.307192 34.715869, -92.307132 34.715853, -92.307039 34.715869, -92.306986 34.715913, -92.306859 34.716106, -92.306733 34.716155, -92.306633 34.716139, -92.3065 34.716078, -92.306253 34.715743, -92.306187 34.715715, -92.306114 34.715726, -92.306087 34.715754, -92.306067 34.715869, -92.30618 34.716337, -92.306134 34.71648, -92.306 34.716589, -92.305901 34.716622, -92.305641 34.71665, -92.305361 34.716557, -92.305135 34.716353, -92.305042 34.716282, -92.304949 34.716144, -92.304709 34.715902, -92.304549 34.715825, -92.304416 34.715798, -92.304369 34.715803, -92.304323 34.715798, -92.304263 34.715831, -92.304296 34.715968, -92.304216 34.716073, -92.30407 34.716177, -92.30373 34.716243, -92.303544 34.716232, -92.303424 34.716243, -92.303178 34.716265, -92.302998 34.716347, -92.302958 34.716402, -92.302818 34.716501, -92.302798 34.716507, -92.302552 34.716573, -92.302485 34.716556, -92.302485 34.716501, -92.302559 34.716435, -92.302706 34.716348, -92.302745 34.716325, -92.302898 34.716193, -92.302918 34.716138, -92.302872 34.716056, -92.302778 34.716001, -92.302678 34.715974, -92.302572 34.715985, -92.302515 34.716017, -92.302445 34.716056, -92.302312 34.716072, -92.302133 34.716067, -92.301833 34.71599, -92.3018 34.715995, -92.301673 34.716116, -92.30164 34.716105, -92.301627 34.716045, -92.30166 34.715908, -92.301653 34.715847, -92.30142 34.715528, -92.3013 34.715451, -92.300835 34.715341, -92.300668 34.715275, -92.300562 34.715193, -92.300495 34.71511, -92.300428 34.714967, -92.300422 34.714824, -92.300575 34.71461, -92.300588 34.714517, -92.30054 34.714443, -92.300446 34.714301, -92.300429 34.714275, -92.300202 34.713807, -92.300156 34.713763, -92.300049 34.713747, -92.299996 34.713791, -92.299916 34.713923, -92.299829 34.713972, -92.299756 34.713972, -92.29935 34.713802, -92.29927 34.713741, -92.299237 34.713686, -92.299224 34.713565, -92.299257 34.713307, -92.299224 34.713225, -92.299177 34.713197, -92.299137 34.713197, -92.298725 34.713378, -92.298518 34.713444, -92.298248 34.713491, -92.298165 34.713505, -92.297866 34.713488, -92.297806 34.713521, -92.297852 34.713703, -92.297872 34.713956, -92.297899 34.714038, -92.297926 34.71406, -92.298025 34.714109, -92.298165 34.714142, -92.298322 34.714144, -92.298651 34.714148, -92.298758 34.714192, -92.298758 34.714219, -92.298691 34.714291, -92.298591 34.714346, -92.298536 34.714364, -92.298353 34.714424, -92.298119 34.714467, -92.297879 34.714428, -92.297746 34.714324, -92.297633 34.714159, -92.297579 34.714043, -92.2975 34.713791, -92.29744 34.713384, -92.297393 34.713279, -92.29734 34.713235, -92.296954 34.713147, -92.296641 34.713032, -92.295662 34.712762, -92.295356 34.712658, -92.295163 34.712564, -92.295096 34.712512, -92.294848 34.71232, -92.294424 34.711993, -92.294211 34.711773, -92.294105 34.71158, -92.294045 34.711228, -92.293972 34.711118, -92.293892 34.711058, -92.293333 34.710849, -92.292421 34.710623, -92.292396 34.71061, -92.292383 34.71061, -92.291842 34.710414, -92.291529 34.710173, -92.291236 34.710035, -92.29075 34.709887, -92.290391 34.709798, -92.290125 34.709678, -92.290032 34.709617, -92.289732 34.709342, -92.289479 34.70915, -92.289319 34.709067, -92.288388 34.70877, -92.287942 34.70866, -92.287489 34.7086, -92.287283 34.708599, -92.286963 34.708649, -92.286471 34.70866, -92.286197 34.708764, -92.286027 34.708767, -92.285858 34.70877, -92.285672 34.708753, -92.285505 34.708693, -92.285386 34.708566, -92.285312 34.708401, -92.285113 34.708165, -92.28496 34.708077, -92.284707 34.708016, -92.28444 34.708033, -92.284174 34.708088, -92.283981 34.708175, -92.283928 34.708219, -92.283841 34.708329, -92.283821 34.708406, -92.283828 34.708753, -92.283788 34.708802, -92.283681 34.708813, -92.283388 34.708708, -92.283029 34.708599, -92.282956 34.708527, -92.282832 34.708382, -92.282769 34.708307, -92.28243 34.708192, -92.282317 34.708115, -92.282037 34.707884, -92.281998 34.707824, -92.281933 34.707738, -92.281669 34.707394, -92.281026 34.707529, -92.279178 34.707915, -92.27859 34.707997, -92.277832 34.708081, -92.277165 34.708131, -92.276815 34.708145, -92.276463 34.708148, -92.276288 34.708145, -92.275734 34.708115, -92.275315 34.70808, -92.274816 34.708012, -92.274568 34.707973, -92.27443 34.707947, -92.274302 34.708002, -92.27399 34.708041, -92.27359 34.708035, -92.273317 34.707991, -92.273151 34.70793, -92.272998 34.707826, -92.272774 34.707641, -92.272159 34.707527, -92.27168 34.707454, -92.2715 34.707435, -92.271312 34.707422, -92.27115 34.707415, -92.270945 34.707413, -92.270929 34.707082, -92.270911 34.706258, -92.270914 34.706009, -92.270901 34.705179, -92.270856 34.705192, -92.27033 34.705456, -92.270121 34.705531, -92.269764 34.705659, -92.269371 34.705846, -92.269311 34.705895, -92.269271 34.706071, -92.269218 34.706467, -92.269224 34.706725, -92.269151 34.707192, -92.269143 34.707347, -92.269131 34.707572, -92.269115 34.707732, -92.26905 34.708372, -92.269017 34.708699, -92.269003 34.709127, -92.268983 34.709182, -92.268823 34.709193, -92.268557 34.70916, -92.267246 34.70911, -92.266991 34.709085, -92.266913 34.709077, -92.266241 34.709082, -92.265931 34.709072, -92.265721 34.709066, -92.265628 34.709093, -92.265562 34.709252, -92.265528 34.709395, -92.265519 34.709549, -92.265508 34.709753, -92.265521 34.710022, -92.265576 34.710246, -92.265446 34.710354, -92.265307 34.710469, -92.265211 34.709913, -92.265042 34.710113, -92.264884 34.71029, -92.264388 34.710887, -92.263889 34.711466, -92.263758 34.711602, -92.254725 34.710858, -92.254277 34.71084, -92.254058 34.710168, -92.254051 34.710146, -92.253811 34.70934, -92.25376 34.709183, -92.253168 34.707115, -92.253122 34.706989, -92.253062 34.706867, -92.252989 34.706785, -92.252904 34.706712, -92.25281 34.706652, -92.252761 34.706626, -92.252658 34.706583, -92.25255 34.706552, -92.25225 34.706492, -92.250802 34.706226, -92.250801 34.706078, -92.25084 34.70489, -92.250837 34.704657, -92.250841 34.704472, -92.250865 34.70379, -92.250877 34.703462, -92.249389 34.703417, -92.248666 34.703401, -92.248622 34.703407, -92.248576 34.70343, -92.248542 34.703468, -92.248533 34.703504, -92.248499 34.704395, -92.247932 34.704376, -92.247894 34.704388, -92.247864 34.70441, -92.247846 34.704441, -92.24782 34.704759, -92.247804 34.704785, -92.247775 34.704803, -92.24774 34.704809, -92.247468 34.704806, -92.247098 34.704792, -92.247028 34.704799, -92.246946 34.704815, -92.246845 34.704821, -92.246647 34.704821, -92.246332 34.704808, -92.24632 34.704925, -92.246314 34.705418, -92.24603 34.705369, -92.24511 34.705202, -92.244577 34.705103, -92.2442 34.705023, -92.243986 34.704959, -92.243963 34.704884, -92.243954 34.704401, -92.243995 34.703975, -92.243125 34.70386, -92.243013 34.703867, -92.242747 34.703541, -92.242602 34.703371, -92.242237 34.702942, -92.242169 34.702848, -92.242022 34.702611, -92.242013 34.702392, -92.242013 34.702151, -92.242046 34.700938, -92.242071 34.700373, -92.242126 34.698637, -92.242152 34.697791, -92.242166 34.697583, -92.242177 34.697316, -92.242177 34.69711, -92.242234 34.695425, -92.242239 34.695279, -92.243095 34.695307, -92.244556 34.695363, -92.244653 34.695363, -92.244797 34.695348, -92.244937 34.695315, -92.245068 34.695268, -92.245188 34.695213, -92.245298 34.695145, -92.245412 34.695056, -92.245517 34.69495, -92.245606 34.694834, -92.245654 34.694752, -92.246043 34.693921, -92.24535 34.693892, -92.24391 34.69386, -92.243193 34.693833, -92.242296 34.693805, -92.24236 34.692083, -92.242391 34.69112, -92.242427 34.690301, -92.242443 34.689776, -92.242501 34.688273, -92.242521 34.687688, -92.242525 34.687609, -92.242527 34.687562, -92.24255 34.687045, -92.242569 34.686496, -92.24261 34.685339, -92.242628 34.684869, -92.242633 34.684803, -92.242656 34.684473, -92.242684 34.684226, -92.242696 34.684125, -92.242787 34.683336, -92.242838 34.68297, -92.242871 34.682789, -92.24292 34.682611, -92.242851 34.682587, -92.242791 34.682549, -92.242758 34.682518, -92.242722 34.682463, -92.242706 34.682402, -92.242718 34.681861, -92.242756 34.681189, -92.242775 34.680844, -92.242839 34.679838, -92.242849 34.679174, -92.242476 34.679155, -92.241819 34.679129, -92.240269 34.67908, -92.239953 34.679067, -92.239566 34.67906, -92.238988 34.679039, -92.238845 34.679026, -92.238633 34.678991, -92.238494 34.678958, -92.23815 34.678886, -92.237879 34.678862, -92.236822 34.678822, -92.23655 34.678802, -92.236186 34.678792, -92.235408 34.678792, -92.234597 34.678803, -92.234351 34.678826, -92.234118 34.678264, -92.234071 34.678107, -92.234049 34.678, -92.234051 34.677896, -92.234072 34.677792, -92.234097 34.677726, -92.234194 34.677544, -92.234451 34.677074, -92.234523 34.676911, -92.234579 34.676743, -92.234617 34.676571, -92.234708 34.675851, -92.234738 34.675543, -92.234756 34.675406, -92.234765 34.675236, -92.234777 34.675028, -92.234772 34.674866, -92.234736 34.673395, -92.23471 34.672511, -92.2347 34.67217, -92.234698 34.671861, -92.234687 34.671356, -92.234692 34.671057, -92.234723 34.670447, -92.234761 34.669977, -92.234769 34.66961, -92.234767 34.669513, -92.234859 34.669363, -92.234912 34.669256, -92.235178 34.668651, -92.235374 34.668187, -92.235587 34.667706, -92.235813 34.667216, -92.235922 34.666943, -92.236192 34.666288, -92.236199 34.666269, -92.236451 34.665608, -92.236564 34.665277, -92.236589 34.665187, -92.236676 34.664881, -92.2368 34.664331, -92.237047 34.664332, -92.238004 34.664357, -92.238705 34.664386, -92.239311 34.664418, -92.239397 34.664423, -92.23999 34.664443, -92.240162 34.664451, -92.240764 34.664479, -92.241348 34.664517, -92.241422 34.664518, -92.241477 34.664505, -92.241509 34.664488, -92.241544 34.664452, -92.241561 34.664408, -92.241579 34.664116, -92.241599 34.663648, -92.241658 34.662661, -92.241676 34.661844, -92.241707 34.661363, -92.24171 34.661318, -92.241794 34.659467, -92.24182 34.65848, -92.241821 34.65755, -92.241814 34.657202, -92.241828 34.656286, -92.241833 34.655993, -92.241855 34.655097, -92.241864 34.654068, -92.241875 34.653655, -92.241901 34.653145, -92.241922 34.65209, -92.241915 34.651655, -92.240358 34.651636, -92.239959 34.651634, -92.238731 34.651616, -92.238138 34.651602, -92.237258 34.651597, -92.23692 34.651593, -92.236678 34.651589, -92.23596 34.65158, -92.235439 34.651565, -92.234683 34.651558, -92.234408 34.651554, -92.234271 34.651556, -92.234087 34.651554, -92.233722 34.650245, -92.233203 34.648339, -92.232385 34.645394, -92.232062 34.64423, -92.231862 34.643494, -92.231818 34.643366, -92.231764 34.643241, -92.231701 34.643119, -92.231444 34.642183, -92.229551 34.635346, -92.229336 34.634569, -92.228469 34.631437, -92.227032 34.626244, -92.226407 34.623986, -92.225756 34.621634, -92.225429 34.620454, -92.225375 34.620258, -92.225246 34.619777, -92.225453 34.619751, -92.225643 34.6204, -92.225718 34.620327, -92.225707 34.616902, -92.225326 34.616892, -92.225195 34.616889, -92.224874 34.616884, -92.224817 34.616883, -92.224695 34.616881, -92.224442 34.616877, -92.223756 34.614405, -92.222912 34.611358, -92.22213 34.608536, -92.221898 34.607697, -92.221412 34.605946, -92.220966 34.604337, -92.22007 34.601102, -92.21988 34.600414, -92.21974 34.599907, -92.219602 34.599404, -92.219389 34.598641, -92.219338 34.598456, -92.219334 34.598443, -92.219182 34.597901, -92.218269 34.59457, -92.21552 34.594559, -92.213017 34.594525, -92.20839 34.594456, -92.200159 34.594307, -92.199939 34.594302, -92.198996 34.594295, -92.194825 34.594213, -92.194157 34.594187, -92.194115 34.594395, -92.194068 34.594532, -92.19402 34.594614, -92.193929 34.594768, -92.19383 34.594908, -92.193676 34.595126, -92.192918 34.596357, -92.192682 34.596674, -92.192526 34.596885, -92.192293 34.597149, -92.191974 34.597435, -92.19131 34.59777, -92.191077 34.597819, -92.190919 34.597816, -92.190778 34.597814, -92.190738 34.59777, -92.190758 34.59772, -92.190961 34.597558, -92.19111 34.59744, -92.19115 34.597385, -92.19109 34.597121, -92.191084 34.59694, -92.19119 34.59661, -92.191735 34.595934, -92.19218 34.595439, -92.192539 34.594966, -92.192583 34.594893, -92.1914 34.594888, -92.190511 34.594885, -92.19067 34.599327, -92.190794 34.602787, -92.190765 34.602919, -92.190393 34.604429, -92.190221 34.605067, -92.190132 34.605424, -92.190592 34.605515, -92.190894 34.605571, -92.19103 34.606455, -92.191431 34.606524, -92.192711 34.606693, -92.193307 34.606733, -92.193458 34.606739, -92.193636 34.606195, -92.193713 34.60594, -92.194488 34.603595, -92.194964 34.60212, -92.196591 34.602145, -92.208397 34.602306, -92.212515 34.602363, -92.213139 34.602369, -92.213222 34.602271, -92.213259 34.602151, -92.213261 34.601869, -92.213279 34.601481, -92.213292 34.601412, -92.213351 34.601258, -92.213418 34.601142, -92.213467 34.601108, -92.213532 34.601074, -92.213611 34.601049, -92.213736 34.60104, -92.214061 34.601023, -92.214207 34.601004, -92.214854 34.601107, -92.215337 34.601149, -92.215464 34.601154, -92.215646 34.601167, -92.216648 34.601171, -92.216648 34.6012, -92.216629 34.602185, -92.216609 34.60322, -92.216598 34.603846, -92.216604 34.604034, -92.216621 34.604164, -92.216652 34.604314, -92.216886 34.605084, -92.217125 34.605849, -92.217407 34.60677, -92.217506 34.607111, -92.217562 34.607335, -92.217635 34.607681, -92.217726 34.608094, -92.217782 34.608404, -92.217928 34.609128, -92.218011 34.609476, -92.217918 34.609481, -92.217481 34.609468, -92.217157 34.609449, -92.21668 34.609434, -92.215896 34.609422, -92.215835 34.60942, -92.215901 34.610863, -92.217859 34.610859, -92.217856 34.612538, -92.217853 34.613672, -92.217853 34.613993, -92.217852 34.614139, -92.217851 34.614863, -92.217832 34.624281, -92.219538 34.624278, -92.219539 34.624696, -92.220297 34.624695, -92.220742 34.625843, -92.220111 34.625896, -92.220112 34.626366, -92.221265 34.626364, -92.221312 34.626364, -92.221313 34.62652, -92.221313 34.626786, -92.221314 34.626853, -92.221318 34.628453, -92.221246 34.628451, -92.21778 34.628355, -92.217787 34.630998, -92.217787 34.631079, -92.217789 34.631312, -92.21952 34.631333, -92.220242 34.631338, -92.222553 34.63137, -92.222829 34.631374, -92.223263 34.631388, -92.223981 34.631375, -92.224093 34.63159, -92.224188 34.631811, -92.224373 34.632437, -92.224507 34.632966, -92.224665 34.63356, -92.224802 34.634099, -92.224941 34.634603, -92.225013 34.634859, -92.225105 34.63511, -92.225245 34.63543, -92.225349 34.635664, -92.225599 34.636203, -92.226518 34.638217, -92.226599 34.638439, -92.226611 34.638471, -92.226684 34.638729, -92.226721 34.638903, -92.226771 34.639286, -92.226846 34.639796, -92.226934 34.640435, -92.227184 34.642231, -92.227221 34.642424, -92.22728 34.642636, -92.227302 34.642697, -92.228135 34.644961, -92.228233 34.645252, -92.229144 34.647736, -92.229255 34.647985, -92.22934 34.648149, -92.229681 34.648778, -92.229758 34.648943, -92.229813 34.649097, -92.22985 34.649254, -92.229947 34.649935, -92.230119 34.651013, -92.230194 34.651403, -92.230222 34.651503, -92.23038 34.65221, -92.230559 34.653063, -92.230641 34.653534, -92.230722 34.654137, -92.230767 34.654429, -92.230916 34.655572, -92.231145 34.657203, -92.231198 34.657603, -92.231211 34.657664, -92.231228 34.657735, -92.231277 34.657863, -92.231343 34.657985, -92.231499 34.658199, -92.232029 34.658834, -92.232076 34.658908, -92.23213 34.659025, -92.232153 34.659106, -92.232169 34.65923, -92.232167 34.659313, -92.232074 34.660219, -92.232061 34.660445, -92.232059 34.660578, -92.232063 34.660644, -92.232096 34.660904, -92.232298 34.662028, -92.232386 34.662473, -92.232454 34.662852, -92.232534 34.663255, -92.232574 34.663602, -92.232577 34.663792, -92.232563 34.663982, -92.232399 34.664989, -92.23239 34.665119, -92.232401 34.665249, -92.232431 34.665376, -92.232514 34.665537, -92.232634 34.66568, -92.232791 34.66584, -92.232956 34.665984, -92.233145 34.666139, -92.233359 34.666291, -92.233718 34.666561, -92.233811 34.666641, -92.233927 34.666751, -92.234065 34.666908, -92.234185 34.667078, -92.234306 34.667276, -92.234415 34.66748, -92.234503 34.66774, -92.23455 34.667939, -92.234671 34.668755, -92.234733 34.669242, -92.234556 34.669651, -92.234322 34.670217, -92.233448 34.672269, -92.233144 34.673058, -92.233107 34.673166, -92.23301 34.673494, -92.232934 34.673826, -92.232878 34.67416, -92.232843 34.674493, -92.232827 34.674821, -92.232828 34.67488, -92.23283 34.675149, -92.232853 34.675476, -92.232898 34.675821, -92.232946 34.676073, -92.233004 34.676324, -92.233072 34.676574, -92.233193 34.676944, -92.233287 34.677188, -92.233362 34.677362, -92.234441 34.679695, -92.234907 34.680712, -92.235731 34.682488, -92.236127 34.683385, -92.236166 34.683478, -92.236285 34.683793, -92.23637 34.684018, -92.23649 34.684375, -92.236599 34.684736, -92.236696 34.685098, -92.236794 34.685516, -92.236892 34.68599, -92.236924 34.686182, -92.236972 34.686465, -92.237031 34.686895, -92.237086 34.687469, -92.237106 34.687853, -92.237117 34.688383, -92.237114 34.688649, -92.237096 34.689179, -92.237079 34.689444, -92.237022 34.689973, -92.236947 34.690501, -92.236854 34.691027, -92.236647 34.69189, -92.236508 34.692385, -92.236351 34.692854, -92.236222 34.693197, -92.236083 34.693538, -92.236076 34.693552, -92.235911 34.693926, -92.235852 34.694059, -92.235678 34.694422, -92.235492 34.694781, -92.235 34.695652, -92.234884 34.69595, -92.234753 34.69618, -92.234214 34.697126, -92.234011 34.69747, -92.2337 34.698017, -92.23358 34.698231, -92.233181 34.698913, -92.23302 34.699201, -92.23231 34.700436, -92.231879 34.701191, -92.231724 34.701447, -92.2315 34.701762, -92.231419 34.701896, -92.231049 34.702543, -92.231086 34.702555, -92.231123 34.702567, -92.231156 34.702591, -92.231185 34.702643, -92.231203 34.702707, -92.231207 34.702771, -92.231201 34.702859, -92.231197 34.703046, -92.231209 34.703177, -92.231232 34.703275, -92.231273 34.703379, -92.23131 34.703447, -92.231326 34.703476, -92.231393 34.703563, -92.231495 34.703685, -92.231621 34.703781, -92.231756 34.703872, -92.231923 34.703967, -92.232091 34.704043, -92.232306 34.704105, -92.232532 34.704149, -92.232737 34.704173, -92.232937 34.704182, -92.23298 34.704178, -92.233138 34.704161, -92.233331 34.704124, -92.233576 34.704039, -92.234005 34.703856, -92.234048 34.70384, -92.234492 34.703677, -92.234942 34.703505, -92.235325 34.70336, -92.235631 34.703233, -92.235683 34.703209, -92.236051 34.703036, -92.236609 34.702751, -92.236737 34.702671, -92.236845 34.702561, -92.236937 34.702441, -92.237055 34.702254, -92.23716 34.702019, -92.237223 34.701986, -92.237271 34.701969, -92.237321 34.70196, -92.237399 34.70196, -92.237469 34.70197, -92.237538 34.701991, -92.237968 34.704904, -92.237561 34.705358, -92.237506 34.70537, -92.237421 34.705389, -92.237287 34.705402, -92.237032 34.705411, -92.236811 34.70541, -92.236279 34.705417, -92.235771 34.705434, -92.234932 34.705448, -92.234622 34.705449, -92.234313 34.705456, -92.234092 34.70547, -92.233676 34.705504, -92.233456 34.705504, -92.233204 34.705516, -92.232998 34.705519, -92.23294 34.70552, -92.232161 34.705502, -92.231753 34.705481, -92.231453 34.705477, -92.230831 34.705461, -92.230751 34.70546, -92.230528 34.705459, -92.230482 34.705465, -92.230435 34.705483, -92.230397 34.705511, -92.230371 34.705548, -92.23036 34.705589, -92.230378 34.705769, -92.230413 34.70602, -92.230454 34.706206, -92.230506 34.70639, -92.23058 34.706681, -92.230641 34.706878, -92.230725 34.707091, -92.230774 34.707196, -92.230833 34.707308, -92.230912 34.707444, -92.231059 34.707682, -92.231207 34.707964, -92.23101 34.707962, -92.230888 34.707961, -92.230836 34.707957, -92.230734 34.707936, -92.230692 34.707933, -92.230291 34.707151, -92.230257 34.707105, -92.230211 34.707067, -92.230154 34.707039, -92.230092 34.707024, -92.229256 34.706998, -92.22926 34.706802, -92.229281 34.706291, -92.229293 34.706141, -92.229314 34.706095, -92.229035 34.7061, -92.22851 34.706089, -92.228163 34.706077, -92.228194 34.705238, -92.228208 34.705162, -92.228216 34.704775, -92.228218 34.704381, -92.228212 34.704321, -92.228152 34.704277, -92.227843 34.704255, -92.227346 34.70424, -92.227082 34.70424, -92.226351 34.704219, -92.226001 34.704218, -92.225619 34.704198, -92.225184 34.704193, -92.225133 34.704198, -92.225084 34.70421, -92.225008 34.704249, -92.22497 34.704287, -92.224938 34.70433, -92.224898 34.70441, -92.224864 34.704537, -92.224848 34.705064, -92.224841 34.70516, -92.224824 34.70561, -92.224804 34.705955, -92.224771 34.706471, -92.224756 34.706867, -92.224741 34.707295, -92.224737 34.707412, -92.224727 34.707589, -92.224709 34.707792, -92.224178 34.707779, -92.223993 34.707771, -92.223645 34.707762, -92.223439 34.707754, -92.223152 34.707743, -92.223182 34.706951, -92.222456 34.706953, -92.222547 34.70407, -92.221882 34.704049, -92.221587 34.704041, -92.22159 34.703901, -92.221593 34.703847, -92.22039 34.703785, -92.220425 34.703202, -92.220402 34.703227, -92.220362 34.703238, -92.220222 34.703354, -92.219969 34.703447, -92.21971 34.703497, -92.21959 34.703541, -92.21937 34.703541, -92.219284 34.703497, -92.219197 34.703404, -92.21917 34.703321, -92.21917 34.703233, -92.219224 34.7032, -92.21937 34.703162, -92.21945 34.703107, -92.219696 34.703008, -92.219916 34.702914, -92.220109 34.70281, -92.220335 34.702744, -92.220445 34.702731, -92.22046 34.702444, -92.219895 34.702563, -92.219741 34.702595, -92.218679 34.70282, -92.217728 34.70302, -92.217428 34.70313, -92.216608 34.703432, -92.215924 34.703999, -92.215765 34.704043, -92.215703 34.704058, -92.215663 34.704058, -92.215497 34.704086, -92.214974 34.704175, -92.213972 34.704395, -92.213392 34.704701, -92.213304 34.704827, -92.213182 34.705007, -92.213208 34.705032, -92.21322 34.705043, -92.213271 34.705091, -92.21322 34.705131, -92.213094 34.705191, -92.212994 34.705224, -92.212881 34.70523, -92.212701 34.705186, -92.212648 34.70518, -92.212561 34.705125, -92.212475 34.704966, -92.212288 34.704757, -92.212145 34.704713, -92.211957 34.704713, -92.211828 34.704786, -92.211835 34.7049, -92.211807 34.704923, -92.211621 34.705269, -92.211344 34.705885, -92.211112 34.706308, -92.211026 34.70643, -92.210788 34.706769, -92.210686 34.706896, -92.210592 34.706995, -92.210478 34.707187, -92.210189 34.707467, -92.209853 34.707979, -92.2098 34.708017, -92.20978 34.708072, -92.209693 34.708182, -92.209633 34.708221, -92.209607 34.70827, -92.209507 34.708358, -92.209207 34.708567, -92.209061 34.708716, -92.209041 34.708771, -92.208994 34.70882, -92.208821 34.70898, -92.208635 34.709106, -92.208455 34.709337, -92.208402 34.70937, -92.208242 34.709749, -92.208189 34.709942, -92.208163 34.710442, -92.208023 34.71131, -92.20791 34.711607, -92.207737 34.711976, -92.207624 34.712303, -92.207617 34.71237, -92.207448 34.712361, -92.205925 34.712354, -92.203285 34.712351, -92.203316 34.707341, -92.200631 34.707298, -92.199044 34.707267, -92.193499 34.70719, -92.192935 34.707182, -92.190322 34.707146, -92.189761 34.707118, -92.189673 34.710419, -92.189624 34.71201, -92.189618 34.712345, -92.189611 34.712479, -92.189574 34.712824, -92.189541 34.713209, -92.189532 34.713392, -92.189421 34.713405, -92.189364 34.713408, -92.188999 34.713401, -92.188704 34.713406, -92.188439 34.713421, -92.188253 34.713444, -92.18776 34.71352, -92.187505 34.713564, -92.186312 34.713759, -92.186237 34.713771, -92.183739 34.714182, -92.183334 34.714254, -92.183096 34.714311, -92.182659 34.714431, -92.182354 34.714529, -92.182061 34.714632, -92.181903 34.714693, -92.181054 34.715005, -92.180556 34.715182, -92.179578 34.715543, -92.17931 34.715649, -92.178895 34.7158, -92.17864 34.715913, -92.178467 34.716008, -92.178356 34.716078, -92.178187 34.716209, -92.178046 34.716339, -92.177922 34.716474, -92.177804 34.716625, -92.177681 34.716829, -92.17754 34.717092, -92.175915 34.716499, -92.175053 34.716194, -92.17449 34.715983, -92.172904 34.71541, -92.172796 34.715376, -92.172563 34.715321, -92.17245 34.7153, -92.171958 34.715225, -92.171693 34.715187, -92.171698 34.715148, -92.171729 34.714889, -92.171823 34.714249, -92.17196 34.713336, -92.172064 34.712656, -92.172194 34.711749, -92.172144 34.711744, -92.172104 34.711739, -92.171951 34.711734, -92.171768 34.711733, -92.171523 34.711733, -92.171307 34.711728, -92.171179 34.711723, -92.17101 34.711719, -92.17086 34.711715, -92.170839 34.711714, -92.170751 34.712303, -92.170699 34.712601, -92.17066 34.712889, -92.170632 34.713043, -92.17055 34.713633, -92.170421 34.71442, -92.170354 34.714915, -92.170338 34.714999, -92.168805 34.714778, -92.168616 34.714746, -92.168442 34.714708, -92.16831 34.714671, -92.168155 34.714617, -92.167995 34.714551, -92.167893 34.714502, -92.167802 34.714451, -92.167602 34.714318, -92.167424 34.714183, -92.167269 34.714036, -92.166789 34.713539, -92.166686 34.713446, -92.16654 34.71333, -92.166335 34.713188, -92.166196 34.713107, -92.16609 34.713056, -92.165902 34.712982, -92.165579 34.712882, -92.16506 34.712781, -92.165005 34.712771, -92.164155 34.712628, -92.163 34.71242, -92.163795 34.713244, -92.164467 34.713724, -92.164616 34.713857, -92.1646 34.713728, -92.164634 34.713574, -92.16466 34.713552, -92.16488 34.713503, -92.165106 34.713492, -92.165133 34.71364, -92.16506 34.713882, -92.16506 34.714008, -92.16502 34.714218, -92.165058 34.714252, -92.165682 34.714876, -92.166309 34.715289, -92.167881 34.715923, -92.167882 34.71643, -92.168611 34.715887, -92.167885 34.716775, -92.169744 34.716707, -92.170317 34.717192, -92.17118 34.717241, -92.171673 34.717444, -92.172969 34.718206, -92.173832 34.718154, -92.175435 34.718558, -92.175684 34.71927, -92.176054 34.719575, -92.176979 34.719828, -92.177535 34.720235, -92.17864 34.720537, -92.179015 34.720894, -92.179703 34.721158, -92.179755 34.721178, -92.179816 34.721201, -92.179845 34.721209, -92.180032 34.721286, -92.180496 34.721503, -92.181978 34.722467, -92.182638 34.722911, -92.183645 34.723584, -92.184632 34.724295, -92.185744 34.725057, -92.186978 34.725666, -92.186979 34.72592, -92.187147 34.725489, -92.187274 34.725609, -92.187393 34.725681, -92.187433 34.725686, -92.18754 34.725774, -92.187706 34.725873, -92.18774 34.725934, -92.187859 34.725989, -92.187953 34.72606, -92.188086 34.726115, -92.188106 34.726143, -92.188152 34.726154, -92.188172 34.726181, -92.188223 34.72643, -92.188583 34.726529, -92.188953 34.72663, -92.189112 34.726692, -92.189225 34.726737, -92.190313 34.727174, -92.190556 34.727286, -92.190756 34.727517, -92.191035 34.727643, -92.191202 34.727891, -92.191268 34.727957, -92.191275 34.72799, -92.191362 34.728056, -92.191495 34.728204, -92.191535 34.728231, -92.191635 34.728298, -92.191754 34.728342, -92.191854 34.728342, -92.191874 34.72832, -92.192034 34.728276, -92.192141 34.728204, -92.192314 34.72821, -92.19262 34.728276, -92.19274 34.728342, -92.192846 34.728364, -92.193193 34.72855, -92.193492 34.728677, -92.193898 34.728908, -92.194031 34.728957, -92.194052 34.728985, -92.194171 34.729012, -92.194524 34.729183, -92.195084 34.729342, -92.19551 34.729545, -92.195696 34.729611, -92.195896 34.729661, -92.196655 34.729782, -92.196761 34.72982, -92.196875 34.729831, -92.196968 34.72987, -92.197108 34.729886, -92.197214 34.729919, -92.197467 34.729936, -92.197527 34.729919, -92.197905 34.730013, -92.198432 34.730073, -92.198617 34.730109, -92.198915 34.730163, -92.199078 34.730204, -92.199367 34.730326, -92.199687 34.730366, -92.200008 34.730398, -92.200297 34.730479, -92.200735 34.730646, -92.201163 34.730759, -92.201502 34.730885, -92.201972 34.731043, -92.202231 34.731107, -92.202594 34.731224, -92.202611 34.731385, -92.20258 34.731448, -92.202488 34.731529, -92.202395 34.731617, -92.202315 34.731612, -92.201928 34.73148, -92.201775 34.731408, -92.201389 34.731287, -92.201363 34.731265, -92.20105 34.731189, -92.200683 34.731068, -92.199491 34.730864, -92.198819 34.7307, -92.198333 34.73054, -92.198 34.730463, -92.197374 34.730386, -92.197268 34.730353, -92.196915 34.730326, -92.196702 34.730342, -92.196482 34.730331, -92.196255 34.730359, -92.195703 34.730337, -92.19521 34.73026, -92.194691 34.730134, -92.194404 34.730018, -92.194191 34.729969, -92.194158 34.729974, -92.194091 34.730035, -92.194131 34.730117, -92.194291 34.730205, -92.194644 34.730337, -92.194797 34.730419, -92.195783 34.730799, -92.195883 34.730892, -92.195883 34.73092, -92.195823 34.730942, -92.19569 34.730914, -92.195423 34.730766, -92.195124 34.730667, -92.19507 34.730628, -92.19499 34.730612, -92.194637 34.730463, -92.194564 34.730452, -92.193905 34.730183, -92.193785 34.730101, -92.193772 34.730073, -92.193772 34.730046, -92.193858 34.729958, -92.193839 34.729875, -92.193699 34.729782, -92.193346 34.729705, -92.193013 34.729595, -92.192946 34.729611, -92.192986 34.729694, -92.193066 34.729787, -92.193266 34.729936, -92.193712 34.730348, -92.194378 34.731233, -92.194564 34.73137, -92.194691 34.731431, -92.194897 34.731497, -92.19523 34.731684, -92.195303 34.7317, -92.195516 34.73181, -92.195763 34.731986, -92.195816 34.732068, -92.195849 34.732079, -92.195889 34.732129, -92.195896 34.732189, -92.195876 34.732211, -92.195843 34.732222, -92.195763 34.732211, -92.195317 34.731865, -92.195097 34.731761, -92.19503 34.731783, -92.195084 34.73186, -92.195164 34.731915, -92.195297 34.732063, -92.195563 34.732437, -92.195676 34.732552, -92.19577 34.732717, -92.19577 34.732772, -92.19573 34.732822, -92.195343 34.732942, -92.194937 34.733135, -92.194811 34.733168, -92.194744 34.733151, -92.194731 34.733124, -92.194758 34.733096, -92.194944 34.733041, -92.194951 34.733014, -92.194984 34.733008, -92.194977 34.732899, -92.194997 34.732871, -92.195064 34.732844, -92.19511 34.732778, -92.195117 34.732734, -92.195084 34.732646, -92.194518 34.731991, -92.194365 34.7317, -92.194171 34.731469, -92.193845 34.731156, -92.193659 34.730942, -92.193399 34.730705, -92.193273 34.730557, -92.193113 34.730463, -92.192973 34.730348, -92.192793 34.730134, -92.192467 34.729837, -92.192254 34.729611, -92.191888 34.729348, -92.191789 34.729171, -92.191701 34.729012, -92.191288 34.728891, -92.191142 34.728814, -92.190876 34.728605, -92.190629 34.728446, -92.190589 34.728397, -92.19037 34.72827, -92.190057 34.728001, -92.189943 34.727847, -92.189684 34.727693, -92.189633 34.72768, -92.188952 34.727337, -92.187978 34.72666, -92.187546 34.72659, -92.187612 34.727018, -92.188287 34.727933, -92.188359 34.728029, -92.190733 34.730885, -92.192705 34.733165, -92.193615 34.733949, -92.194457 34.733948, -92.195085 34.734179, -92.195415 34.734453, -92.19559 34.734564, -92.195743 34.734696, -92.195923 34.734905, -92.196083 34.735031, -92.196183 34.735169, -92.196333 34.735305, -92.196861 34.735871, -92.197338 34.736387, -92.197728 34.736869, -92.197967 34.737136, -92.198506 34.737081, -92.198723 34.737224, -92.198356 34.737474, -92.198595 34.737706, -92.198984 34.737652, -92.199524 34.737793, -92.200108 34.737989, -92.200585 34.73838, -92.200817 34.738476, -92.200931 34.738523, -92.20078 34.738755, -92.202339 34.739644, -92.203225 34.739678, -92.20301 34.74, -92.203205 34.740196, -92.203529 34.740106, -92.20394 34.740177, -92.204784 34.740514, -92.2058 34.740816, -92.206839 34.741207, -92.208244 34.741543, -92.208871 34.741292, -92.208988 34.74105, -92.209043 34.740935, -92.209194 34.740971, -92.209086 34.74115, -92.209026 34.74125, -92.208764 34.741685, -92.208678 34.741953, -92.209089 34.74197, -92.20945 34.741521, -92.20952 34.741434, -92.209822 34.741344, -92.210384 34.741557, -92.210515 34.7417, -92.210882 34.741878, -92.211313 34.741324, -92.211531 34.741469, -92.212028 34.741804, -92.212654 34.74175, -92.212958 34.741803, -92.213282 34.741802, -92.213866 34.742086, -92.214932 34.742441, -92.215002 34.742439, -92.215462 34.742692, -92.215688 34.742736, -92.215934 34.742714, -92.216148 34.742653, -92.216241 34.742609, -92.216394 34.742576, -92.216647 34.742554, -92.216887 34.742587, -92.217586 34.742752, -92.217673 34.742752, -92.218092 34.742823, -92.218758 34.742889, -92.219264 34.7429, -92.219285 34.742898, -92.219311 34.743226, -92.220683 34.743275, -92.220418 34.744419, -92.219402 34.744333, -92.21941 34.744432, -92.219519 34.744584, -92.219813 34.745387, -92.220886 34.745423, -92.222007 34.745461, -92.221986 34.745512, -92.221866 34.74578, -92.221829 34.745862, -92.220857 34.748045, -92.220736 34.748026, -92.220451 34.748006, -92.219752 34.747869, -92.219432 34.747853, -92.218926 34.74777, -92.218846 34.747781, -92.218823 34.747779, -92.21866 34.747765, -92.2183 34.747688, -92.217608 34.747622, -92.216615 34.747474, -92.216162 34.747397, -92.215603 34.747249, -92.214331 34.747068, -92.213958 34.746974, -92.213485 34.746815, -92.213279 34.746771, -92.213252 34.746749, -92.212832 34.746656, -92.212699 34.74659, -92.211993 34.74643, -92.211913 34.746392, -92.211094 34.746167, -92.210668 34.746024, -92.210295 34.745947, -92.209742 34.745777, -92.209396 34.745733, -92.209336 34.745771, -92.209309 34.745837, -92.209343 34.74592, -92.209403 34.745964, -92.209616 34.746018, -92.209949 34.746172, -92.210322 34.746249, -92.210628 34.746365, -92.211514 34.746474, -92.21184 34.74654, -92.21218 34.746656, -92.212666 34.746963, -92.213032 34.747018, -92.213172 34.747057, -92.213305 34.747167, -92.213379 34.747194, -92.213525 34.747216, -92.213938 34.747167, -92.214058 34.747172, -92.214278 34.747282, -92.214413 34.747393, -92.211205 34.747042, -92.210755 34.746845, -92.210681 34.746683, -92.210628 34.746639, -92.210561 34.746623, -92.210528 34.746639, -92.210515 34.746694, -92.210535 34.746777, -92.210515 34.746892, -92.210488 34.746914, -92.210382 34.746936, -92.210228 34.746914, -92.209762 34.746755, -92.209636 34.746684, -92.20897 34.746425, -92.208737 34.746227, -92.20863 34.746101, -92.208457 34.745964, -92.208357 34.74592, -92.208124 34.745749, -92.207964 34.745672, -92.207904 34.745606, -92.207864 34.745606, -92.207771 34.745541, -92.207624 34.745486, -92.207588 34.745468, -92.206004 34.748909, -92.205934 34.749003, -92.205903 34.749053, -92.207485 34.749643, -92.207908 34.749798, -92.208328 34.749957, -92.209074 34.750234, -92.208652 34.751738, -92.208622 34.751803, -92.207241 34.751432, -92.206552 34.751248, -92.205867 34.751064, -92.205131 34.750867, -92.20453 34.752402, -92.20447 34.752552, -92.204438 34.752602, -92.204401 34.752686, -92.204045 34.753589, -92.20337 34.755304, -92.202935 34.756412, -92.202419 34.756278, -92.201168 34.755948, -92.200998 34.755904, -92.200045 34.755655, -92.199384 34.755496, -92.199026 34.755419, -92.198835 34.755383, -92.195066 34.754724, -92.191921 34.754189, -92.192499 34.752692, -92.192841 34.751808, -92.192943 34.751582, -92.193041 34.751456, -92.193139 34.751358, -92.193195 34.751309, -92.193525 34.751018, -92.193829 34.750746, -92.1939 34.750778, -92.1941 34.750829, -92.194703 34.750953, -92.195088 34.751025, -92.195197 34.751019, -92.19524 34.751006, -92.195292 34.750978, -92.19533 34.750951, -92.195371 34.750903, -92.195405 34.750852, -92.195443 34.750779, -92.195673 34.750203, -92.195692 34.750139, -92.195705 34.750046, -92.195716 34.749858, -92.195711 34.749817, -92.195689 34.749744, -92.195664 34.749691, -92.195615 34.749623, -92.195574 34.749586, -92.19528 34.749448, -92.195635 34.749131, -92.195929 34.748868, -92.196053 34.748752, -92.196193 34.748598, -92.196323 34.74846, -92.196433 34.748318, -92.196506 34.748203, -92.196623 34.747969, -92.196859 34.747371, -92.196971 34.747085, -92.197097 34.746782, -92.197141 34.746637, -92.197154 34.746594, -92.197136 34.746539, -92.197112 34.74651, -92.197068 34.74648, -92.196274 34.746185, -92.195347 34.745841, -92.194453 34.745509, -92.193826 34.745277, -92.192157 34.749582, -92.191518 34.751221, -92.190928 34.751066, -92.188414 34.750395, -92.188337 34.750384, -92.188196 34.750377, -92.188092 34.750387, -92.187941 34.750431, -92.187851 34.750471, -92.187659 34.750566, -92.18761 34.750596, -92.187527 34.750655, -92.18744 34.750742, -92.18741 34.75079, -92.187343 34.750948, -92.187046 34.751707, -92.18681 34.752313, -92.186787 34.752385, -92.186793 34.752439, -92.186805 34.752463, -92.186827 34.752489, -92.186873 34.752518, -92.187578 34.752745, -92.18793 34.752851, -92.188189 34.752915, -92.190063 34.753302, -92.18986 34.753836, -92.185213 34.753042, -92.184235 34.752879, -92.184111 34.752858, -92.18359 34.752772, -92.183229 34.752708, -92.18285 34.752634, -92.182818 34.750363, -92.182632 34.750637, -92.182362 34.750897, -92.182268 34.750957, -92.182009 34.751123, -92.181652 34.751238, -92.181179 34.751385, -92.180587 34.751565, -92.180133 34.751712, -92.179987 34.751651, -92.179205 34.751323, -92.174496 34.74935, -92.174224 34.749236, -92.173033 34.748743, -92.172676 34.748603, -92.172525 34.748549, -92.172261 34.74847, -92.172075 34.748424, -92.17183 34.748373, -92.171555 34.748327, -92.171103 34.748272, -92.170645 34.748209, -92.170549 34.748201, -92.170256 34.748174, -92.1698 34.748142, -92.167209 34.747888, -92.165873 34.747758, -92.165296 34.747718, -92.16459 34.747685, -92.163895 34.747657, -92.163815 34.746139, -92.163801 34.745747, -92.163813 34.745505, -92.163843 34.745273, -92.163882 34.745067, -92.163931 34.744875, -92.16402 34.74461, -92.16408 34.74433, -92.163869 34.744729, -92.163695 34.745125, -92.163564 34.745461, -92.163429 34.745844, -92.163294 34.746271, -92.163187 34.746668, -92.163092 34.747088, -92.16302 34.747473, -92.162997 34.747625, -92.162798 34.747618, -92.162756 34.747916, -92.162726 34.748202, -92.162711 34.74839, -92.162638 34.749934, -92.162601 34.750396, -92.16259 34.750706, -92.162548 34.751768, -92.162536 34.752512, -92.162511 34.753128, -92.162503 34.753343, -92.162501 34.753381, -92.162484 34.753791, -92.162282 34.758808, -92.162191 34.761055, -92.162065 34.76417, -92.162012 34.765424, -92.162239 34.765422, -92.162225 34.765761, -92.162219 34.765931, -92.162211 34.766017, -92.162169 34.766558, -92.162129 34.767413, -92.162004 34.770705, -92.161993 34.771002, -92.162099 34.770976, -92.163741 34.770583, -92.16403 34.77051, -92.16425 34.770454, -92.164558 34.770367, -92.164581 34.770361, -92.164622 34.770353, -92.164655 34.770346, -92.164925 34.770289, -92.165092 34.770254, -92.16561 34.770134, -92.166051 34.770026, -92.168638 34.769402, -92.169761 34.76913, -92.169925 34.769095, -92.170225 34.769047, -92.170432 34.769023, -92.170646 34.769009, -92.170826 34.769005, -92.170852 34.769004, -92.171309 34.769036, -92.173098 34.76923, -92.173797 34.769306, -92.173889 34.769316, -92.175076 34.769445, -92.177337 34.769686, -92.177338 34.769917, -92.177286 34.772045, -92.177267 34.773098, -92.177253 34.773555, -92.177233 34.773908, -92.181497 34.773405, -92.181823 34.773393, -92.183053 34.773344, -92.184634 34.773076, -92.187302 34.772624, -92.1891 34.772139, -92.190498 34.771678, -92.190464 34.772309, -92.190425 34.773085, -92.19039 34.773703, -92.190382 34.773909, -92.190217 34.773912, -92.189274 34.773891, -92.188709 34.773874, -92.188749 34.772623, -92.188744 34.772547, -92.188735 34.772522, -92.188709 34.772497, -92.188688 34.772492, -92.188646 34.772492, -92.187892 34.772676, -92.187191 34.772832, -92.186832 34.772905, -92.186636 34.772941, -92.1862 34.77302, -92.185927 34.773064, -92.185221 34.773162, -92.18468 34.773225, -92.184373 34.773271, -92.184319 34.773294, -92.184292 34.773313, -92.184286 34.773331, -92.184272 34.773751, -92.184354 34.77375, -92.18598 34.773797, -92.185957 34.774591, -92.185948 34.775118, -92.185916 34.77597, -92.185918 34.776165, -92.185904 34.776314, -92.185828 34.776732, -92.185817 34.776864, -92.185821 34.777367, -92.185825 34.777409, -92.186513 34.777409, -92.186726 34.777418, -92.186756 34.777424, -92.186778 34.777434, -92.186811 34.777465, -92.186822 34.777493, -92.186825 34.77793, -92.18684 34.778068, -92.186878 34.778117, -92.186922 34.77815, -92.186984 34.778175, -92.18702 34.778181, -92.187075 34.77818, -92.188252 34.778061, -92.189292 34.777948, -92.189431 34.777927, -92.189483 34.777913, -92.189589 34.777869, -92.189664 34.777824, -92.189733 34.777764, -92.189768 34.777722, -92.189815 34.777646, -92.189837 34.777597, -92.189861 34.777503, -92.189877 34.777061, -92.189901 34.776263, -92.189917 34.775847, -92.189935 34.775804, -92.18997 34.775761, -92.190009 34.775733, -92.190075 34.775706, -92.190324 34.775633, -92.190312 34.77606, -92.190245 34.778058, -92.190238 34.778292, -92.190227 34.778659, -92.190186 34.780136, -92.190173 34.780532, -92.190158 34.781142, -92.190105 34.783011, -92.190071 34.784208, -92.190023 34.786125, -92.190004 34.786786, -92.190007 34.786979, -92.189981 34.787825, -92.189965 34.788383, -92.189952 34.788926, -92.189922 34.790201, -92.189904 34.790933, -92.189873 34.792107, -92.189175 34.792081, -92.188598 34.792069, -92.187337 34.792029, -92.18683 34.792019, -92.186785 34.792031, -92.186765 34.792042, -92.18674 34.792064, -92.186716 34.792104, -92.186662 34.793583, -92.186642 34.794046, -92.18663 34.794528, -92.186575 34.795561, -92.186235 34.795553, -92.18575 34.795532, -92.184321 34.795491, -92.183354 34.795459, -92.182454 34.795431, -92.181849 34.795418, -92.18172 34.795415, -92.180949 34.795393, -92.180903 34.795945, -92.180873 34.796892, -92.180836 34.798141, -92.181024 34.798162, -92.181347 34.798165, -92.181911 34.798189, -92.182548 34.798205, -92.183311 34.798236, -92.183813 34.798267, -92.183994 34.798271, -92.184073 34.798253, -92.184121 34.798233, -92.184153 34.798212, -92.1842 34.798154, -92.184229 34.798093, -92.184242 34.798029, -92.184249 34.797746, -92.184257 34.797437, -92.184269 34.797226, -92.184276 34.796998, -92.184659 34.797016, -92.185443 34.797043, -92.18593 34.797052, -92.186451 34.797071, -92.18737 34.797098, -92.187604 34.797098, -92.187893 34.797808, -92.188131 34.797823, -92.189733 34.797964, -92.189704 34.798155, -92.189668 34.798698, -92.190848 34.798727, -92.191478 34.798754, -92.191502 34.79855, -92.191578 34.798201, -92.191682 34.797638, -92.19174 34.797374, -92.192098 34.797423, -92.193698 34.797595, -92.193924 34.797616, -92.193997 34.797652, -92.19376 34.797861, -92.193473 34.798101, -92.192993 34.798484, -92.192385 34.79895, -92.191972 34.799261, -92.191411 34.799684, -92.190183 34.800623, -92.189145 34.801406, -92.188809 34.801655, -92.185389 34.804247, -92.185148 34.804425, -92.184264 34.805097, -92.182538 34.806408, -92.18139 34.807274, -92.179078 34.809029, -92.177277 34.81039, -92.176862 34.810715, -92.176678 34.810879, -92.17653 34.811026, -92.175736 34.811107, -92.175311 34.811077, -92.174969 34.811127, -92.174626 34.81121, -92.174074 34.811373, -92.173983 34.811394, -92.173652 34.811471, -92.172758 34.811684, -92.172573 34.811734, -92.172468 34.811735, -92.172314 34.811832, -92.172056 34.811961, -92.171839 34.812045, -92.17134 34.812169, -92.170622 34.812455, -92.170024 34.812667, -92.169907 34.812709, -92.168985 34.812946, -92.167955 34.813295, -92.167116 34.813539, -92.167204 34.814453, -92.167228 34.814602, -92.167253 34.814827, -92.167262 34.815053, -92.16756 34.815431, -92.167609 34.815487, -92.167673 34.815531, -92.167755 34.815534, -92.167835 34.815521, -92.168174 34.815387, -92.168337 34.815681, -92.168353 34.815732, -92.168378 34.816102, -92.168386 34.816157, -92.168412 34.816241, -92.168439 34.816309, -92.168499 34.81643, -92.168544 34.816654, -92.168564 34.816713, -92.168653 34.816818, -92.168552 34.816874, -92.167983 34.817218, -92.167364 34.817577, -92.167064 34.817755, -92.166838 34.817887, -92.166442 34.818118, -92.166189 34.818264, -92.165541 34.818648, -92.165271 34.81883, -92.165156 34.818927, -92.164996 34.819081, -92.164145 34.820018, -92.16359 34.820624, -92.163415 34.820791, -92.163221 34.82096, -92.162923 34.821207, -92.162674 34.8214, -92.162443 34.821559, -92.162201 34.821706, -92.162495 34.821976, -92.162542 34.822039, -92.162579 34.822105, -92.162597 34.822192, -92.162599 34.822474, -92.162565 34.822894, -92.162576 34.823019, -92.162597 34.82311, -92.162623 34.823166, -92.162675 34.823245, -92.162725 34.823317, -92.16293 34.823572, -92.162981 34.823634, -92.162389 34.824109, -92.162151 34.8243, -92.162123 34.825327, -92.162144 34.82774, -92.162395 34.827741, -92.162528 34.827741, -92.16692 34.827744, -92.166954 34.827837, -92.166987 34.827889, -92.167031 34.827908, -92.166904 34.82813, -92.166868 34.829008, -92.166859 34.829222, -92.166843 34.82981, -92.166833 34.830315, -92.166821 34.830951, -92.166809 34.83157, -92.166754 34.831571, -92.166721 34.832006, -92.166676 34.83327, -92.164538 34.833369, -92.163781 34.833404, -92.163901 34.835087, -92.1643 34.835294, -92.164352 34.835322, -92.164509 34.835435, -92.164617 34.835511, -92.164804 34.835681, -92.165154 34.835978, -92.165536 34.836335, -92.16505 34.836561, -92.164735 34.836696, -92.164306 34.836879, -92.161944 34.837895, -92.161072 34.838705, -92.161212 34.838912, -92.161313 34.838914, -92.161991 34.838698, -92.162049 34.838753, -92.162212 34.83891, -92.162472 34.839318, -92.162875 34.83958, -92.163251 34.83996, -92.163986 34.839471, -92.163521 34.83882, -92.163812 34.838676, -92.164195 34.838346, -92.164579 34.838234, -92.165043 34.838106, -92.165235 34.83809, -92.165443 34.838202, -92.165667 34.838298, -92.165987 34.838506, -92.166483 34.838458, -92.166514 34.838458, -92.166503 34.83873, -92.168779 34.838759, -92.168747 34.838068, -92.169536 34.838074, -92.169926 34.838109, -92.170715 34.838018, -92.171114 34.837965, -92.171183 34.837957, -92.172052 34.83803, -92.172439 34.838063, -92.172419 34.8381, -92.172179 34.83857, -92.172627 34.838506, -92.172899 34.838474, -92.173267 34.83849, -92.173395 34.838586, -92.173343 34.839387, -92.172765 34.83972, -92.173596 34.840669, -92.173555 34.840794, -92.173395 34.84121, -92.173347 34.841418, -92.173299 34.841802, -92.173267 34.842234, -92.173171 34.84273, -92.173091 34.843066, -92.173011 34.843338, -92.173075 34.843562, -92.173184 34.843768, -92.173211 34.84382, -92.173235 34.843866, -92.173208 34.843923, -92.173196 34.843949, -92.173107 34.844138, -92.173107 34.84449, -92.173139 34.844826, -92.173331 34.845146, -92.173501 34.84543, -92.173587 34.845754, -92.173491 34.846058, -92.173331 34.84649, -92.173331 34.846809, -92.173315 34.847305, -92.173395 34.847785, -92.173459 34.848121, -92.173523 34.848537, -92.17358 34.848935, -92.173587 34.848985, -92.173619 34.849385, -92.173547 34.850189, -92.173308 34.850644, -92.172879 34.851463, -92.171764 34.851442, -92.171678 34.851432, -92.170387 34.851289, -92.169203 34.851193, -92.167059 34.850937, -92.165683 34.850681, -92.164531 34.850505, -92.163603 34.850489, -92.162947 34.850649, -92.162675 34.850761, -92.162099 34.851033, -92.162195 34.851369, -92.162291 34.851737, -92.162387 34.851961, -92.162515 34.852137, -92.162595 34.852185, -92.162675 34.852313, -92.162643 34.852345, -92.162796 34.852424, -92.162791 34.852519, -92.162688 34.852661, -92.16258 34.852883, -92.162507 34.853089, -92.16256 34.853294, -92.162775 34.853372, -92.163052 34.853386, -92.163173 34.85348, -92.163099 34.853686, -92.163093 34.853829, -92.16323 34.853908, -92.16349 34.854049, -92.163577 34.853887, -92.163666 34.853758, -92.163849 34.853627, -92.164 34.853754, -92.164226 34.85396, -92.164458 34.853973, -92.164683 34.853746, -92.164818 34.853585, -92.164941 34.853615, -92.165092 34.853773, -92.165276 34.853772, -92.165491 34.853722, -92.165828 34.853879, -92.166071 34.854052, -92.166469 34.85424, -92.166637 34.854228, -92.16685 34.854975, -92.166901 34.855079, -92.167154 34.855586, -92.167278 34.855837, -92.167467 34.856234, -92.167611 34.856477, -92.16767 34.85656, -92.167706 34.856605, -92.167791 34.856734, -92.16795 34.856893, -92.168079 34.857011, -92.168289 34.857166, -92.168546 34.857338, -92.168988 34.857604, -92.169153 34.857726, -92.169247 34.857776, -92.169331 34.857827, -92.169635 34.857971, -92.169841 34.858041, -92.169667 34.858858, -92.169659 34.858933, -92.169586 34.859215, -92.169548 34.859463, -92.169503 34.859641, -92.169384 34.859964, -92.169315 34.860087, -92.169154 34.860323, -92.16902 34.860449, -92.168863 34.860631, -92.168782 34.860739, -92.16864 34.860891, -92.168563 34.860978, -92.167175 34.862582, -92.166317 34.863552, -92.165893 34.864021, -92.165727 34.864256, -92.165676 34.864349, -92.165643 34.864435, -92.165594 34.864675, -92.165828 34.864947, -92.165846 34.864935, -92.165995 34.86488, -92.166177 34.86483, -92.166229 34.864819, -92.166369 34.864803, -92.166389 34.864801, -92.16655 34.864801, -92.166723 34.864824, -92.166813 34.864845, -92.167014 34.864908, -92.16728 34.865008, -92.167481 34.865108, -92.167669 34.865212, -92.167713 34.865246, -92.167833 34.865355, -92.167935 34.865476, -92.168019 34.865606, -92.16808 34.865748, -92.168146 34.865998, -92.168204 34.866308, -92.168271 34.866599, -92.16833 34.866794, -92.168345 34.866841, -92.168363 34.866879, -92.168429 34.866991, -92.168521 34.867106, -92.168592 34.867181, -92.170379 34.866835, -92.169962 34.867649, -92.170357 34.867732, -92.170679 34.867801, -92.170846 34.867827, -92.17103 34.867848, -92.171239 34.867861, -92.172261 34.867884, -92.172328 34.867894, -92.172394 34.867911, -92.172487 34.867949, -92.172568 34.867999, -92.17278 34.868172, -92.172488 34.868329, -92.172157 34.868567, -92.171655 34.868963, -92.171402 34.869154, -92.171039 34.869441, -92.171355 34.869471, -92.172036 34.869695, -92.172209 34.869833, -92.172592 34.869977, -92.172891 34.870285, -92.172824 34.870472, -92.172641 34.870681, -92.17236 34.870856, -92.171894 34.871028, -92.171172 34.871224, -92.170948 34.871594, -92.171239 34.872148, -92.171934 34.872629, -92.172898 34.873146, -92.17362 34.873883, -92.174154 34.874394, -92.174243 34.874489, -92.174579 34.874937, -92.174736 34.875231, -92.174774 34.875225, -92.174857 34.875309, -92.175008 34.87552, -92.175209 34.87573, -92.175409 34.87601, -92.17561 34.876221, -92.175845 34.876417, -92.176029 34.876572, -92.176214 34.876727, -92.176466 34.876896, -92.176751 34.877093, -92.176936 34.87722, -92.177138 34.877346, -92.17739 34.877474, -92.177592 34.877559, -92.177756 34.877639, -92.177828 34.877672, -92.17798 34.877728, -92.178115 34.877771, -92.178233 34.877814, -92.178334 34.877842, -92.178486 34.877857, -92.178672 34.8779, -92.178936 34.877995, -92.179147 34.878063, -92.180435 34.878246, -92.18147 34.878716, -92.181863 34.879025, -92.182232 34.879316, -92.182644 34.879808, -92.183069 34.880231, -92.181996 34.880464, -92.179562 34.880984, -92.179303 34.881046, -92.179049 34.881119, -92.178804 34.881206, -92.178632 34.881279, -92.178566 34.881307, -92.178337 34.88142, -92.178116 34.88155, -92.177907 34.881697, -92.177804 34.88178, -92.177805 34.88188, -92.177977 34.881743, -92.178187 34.8816, -92.178338 34.881509, -92.1785 34.881423, -92.178631 34.881363, -92.178753 34.881307, -92.178927 34.881239, -92.179196 34.881151, -92.179472 34.881082, -92.183136 34.880298, -92.18322 34.88028, -92.18367 34.880189, -92.183876 34.880162, -92.184083 34.880144, -92.184291 34.880135, -92.184499 34.880134, -92.184708 34.880142, -92.185004 34.880176, -92.1852 34.880207, -92.185394 34.880244, -92.186768 34.880579, -92.18734 34.880726, -92.187639 34.880804, -92.188017 34.880897, -92.18828 34.880948, -92.188547 34.880984, -92.188816 34.881007, -92.189086 34.881015, -92.189369 34.88101, -92.189657 34.880988, -92.189942 34.88095, -92.190224 34.880895, -92.19049 34.880827, -92.190746 34.880748, -92.190995 34.880654, -92.191235 34.880546, -92.191452 34.880437, -92.191535 34.88039, -92.191697 34.880638, -92.191767 34.880768, -92.191821 34.880894, -92.191843 34.881014, -92.19182 34.88198, -92.191819 34.882206, -92.191802 34.882636, -92.191796 34.882806, -92.191794 34.882851, -92.191772 34.883407, -92.191766 34.883549, -92.191759 34.883869, -92.191746 34.884078, -92.191728 34.884473, -92.1917 34.885744, -92.191715 34.885833, -92.19174 34.88592, -92.191797 34.886046, -92.191821 34.886086, -92.191964 34.886238, -92.192041 34.886296, -92.192169 34.886373, -92.192253 34.886412, -92.192374 34.886454, -92.192458 34.886476, -92.192678 34.886503, -92.192928 34.886502, -92.193551 34.886515, -92.193551 34.886713, -92.193531 34.887069, -92.193539 34.887107, -92.193563 34.887181, -92.193618 34.887299, -92.193692 34.887416, -92.193774 34.887519, -92.193946 34.887708, -92.194016 34.887757, -92.194097 34.887795, -92.194239 34.887838, -92.194497 34.887904, -92.194672 34.887939, -92.194892 34.887997, -92.195338 34.888099, -92.195552 34.888156, -92.195976 34.888281, -92.196396 34.887889, -92.196507 34.887765, -92.196581 34.887672, -92.19662 34.887605, -92.196665 34.8875, -92.1967 34.887342, -92.196708 34.887213, -92.196698 34.887083, -92.196662 34.886972, -92.19661 34.886864, -92.19655 34.886776, -92.196494 34.886721, -92.196427 34.886676, -92.19635 34.886642, -92.196067 34.886568, -92.196068 34.885844, -92.196117 34.884213, -92.196114 34.88406, -92.196155 34.882367, -92.196173 34.881967, -92.196174 34.881905, -92.196193 34.88077, -92.196216 34.880424, -92.196243 34.879859, -92.196271 34.879059, -92.196293 34.878586, -92.196297 34.87805, -92.196271 34.877089, -92.196286 34.876746, -92.196306 34.876321, -92.196322 34.876075, -92.196331 34.875811, -92.196315 34.875768, -92.196293 34.875744, -92.196248 34.875718, -92.196194 34.875708, -92.194816 34.875684, -92.194556 34.875652, -92.194735 34.875275, -92.19511 34.87448, -92.195986 34.872607, -92.196089 34.872397, -92.196225 34.872104, -92.196352 34.87184, -92.196509 34.871489, -92.1967 34.871104, -92.196972 34.870484, -92.197125 34.870071, -92.197161 34.869942, -92.197176 34.869894, -92.197227 34.869718, -92.19736 34.869165, -92.197411 34.868958, -92.197517 34.868467, -92.197534 34.868414, -92.197722 34.86754, -92.197858 34.866918, -92.198194 34.865394, -92.198296 34.864974, -92.198373 34.864696, -92.198447 34.864489, -92.198663 34.863917, -92.198881 34.86334, -92.199242 34.862338, -92.199791 34.860838, -92.200078 34.860044, -92.200181 34.859758, -92.200323 34.85936, -92.200508 34.858842, -92.200639 34.858437, -92.200829 34.857929, -92.20124 34.856852, -92.201474 34.856239, -92.201785 34.855403, -92.2019 34.855507, -92.203199 34.856213, -92.204325 34.85684, -92.204458 34.856922, -92.204597 34.856997, -92.204815 34.857095, -92.205101 34.857197, -92.205421 34.857293, -92.205748 34.857376, -92.206936 34.857659, -92.207322 34.857761, -92.207726 34.857856, -92.207941 34.857892, -92.20828 34.857926, -92.20838 34.857933, -92.20942 34.857943, -92.209622 34.857948, -92.210172 34.857959, -92.210447 34.857971, -92.21072 34.857993, -92.210796 34.858006, -92.210908 34.858032, -92.211054 34.858076, -92.211221 34.858145, -92.211344 34.858225, -92.211519 34.858353, -92.212094 34.858823, -92.212323 34.859023, -92.213329 34.859899, -92.213887 34.860394, -92.214039 34.860518, -92.214246 34.860674, -92.214404 34.860775, -92.21471 34.860936, -92.214861 34.86102, -92.215391 34.861268, -92.215951 34.861545, -92.216206 34.861663, -92.216408 34.861742, -92.216537 34.86178, -92.216656 34.8618, -92.216737 34.861805, -92.218244 34.861827, -92.218522 34.861838, -92.218637 34.861848, -92.218867 34.86188, -92.218924 34.861895, -92.219091 34.86195, -92.219321 34.862052, -92.219857 34.86233, -92.220091 34.862443, -92.220333 34.862569, -92.220537 34.862663, -92.220666 34.862713, -92.220801 34.862748, -92.220984 34.86278, -92.221178 34.862795, -92.222042 34.862812, -92.222072 34.867752, -92.222081 34.869292, -92.222088 34.870437, -92.22461 34.870438, -92.224608 34.869771, -92.22559 34.869701, -92.225553 34.869108, -92.225546 34.868998, -92.227377 34.868999, -92.227374 34.869136, -92.227375 34.870671, -92.227401 34.870952, -92.227439 34.871365, -92.227467 34.871537, -92.227505 34.871587, -92.227663 34.871737, -92.2278 34.871835, -92.228026 34.87198, -92.22849 34.872246, -92.229016 34.872553, -92.228982 34.872694, -92.228546 34.873114, -92.228243 34.873178, -92.22786 34.873066, -92.227605 34.872825, -92.227381 34.87279, -92.227195 34.872761, -92.227171 34.874025, -92.227161 34.874555, -92.226981 34.874473, -92.226889 34.874446, -92.225909 34.874153, -92.225826 34.874211, -92.225493 34.874441, -92.225029 34.874665, -92.224381 34.87438, -92.224396 34.873977, -92.224244 34.873977, -92.224029 34.873978, -92.224031 34.874246, -92.224013 34.874449, -92.223993 34.874498, -92.223966 34.874539, -92.22393 34.874571, -92.223688 34.874693, -92.223664 34.874723, -92.223657 34.874747, -92.223681 34.874899, -92.223687 34.875053, -92.223675 34.87509, -92.223651 34.875122, -92.223602 34.875145, -92.223546 34.875154, -92.223318 34.875145, -92.223008 34.87512, -92.22285 34.875114, -92.222754 34.875121, -92.222676 34.875151, -92.222683 34.875309, -92.2227 34.875403, -92.222729 34.875478, -92.222769 34.875551, -92.222812 34.87554, -92.222878 34.875536, -92.222944 34.875545, -92.223159 34.875617, -92.223403 34.875688, -92.223442 34.875705, -92.223478 34.87573, -92.223556 34.875787, -92.223548 34.875924, -92.223538 34.876093, -92.223537 34.876125, -92.223525 34.876335, -92.223491 34.87646, -92.22348 34.876665, -92.223469 34.876997, -92.221681 34.876989, -92.220175 34.876972, -92.220243 34.882672, -92.220271 34.88315, -92.220276 34.883623, -92.218995 34.883598, -92.218506 34.883588, -92.218265 34.883583, -92.218274 34.883535, -92.214 34.883423, -92.213657 34.890675, -92.21331 34.898024, -92.213524 34.898057, -92.213471 34.900617, -92.213588 34.900652, -92.213711 34.900672, -92.213836 34.900678, -92.214088 34.90068, -92.214476 34.900662, -92.214978 34.900661, -92.215189 34.900648, -92.215579 34.900608, -92.215758 34.900582, -92.216368 34.900474, -92.216635 34.900439, -92.216875 34.90042, -92.217106 34.900414, -92.217245 34.900422, -92.217452 34.90045, -92.217696 34.900507, -92.218222 34.900665, -92.218682 34.900818, -92.218821 34.900875, -92.219036 34.900948, -92.21911 34.900969, -92.219372 34.901057, -92.219641 34.90113, -92.219824 34.901169, -92.219934 34.901185, -92.220068 34.90121, -92.220271 34.901231, -92.220477 34.901233, -92.220946 34.901263, -92.221308 34.901331, -92.221938 34.901499, -92.222128 34.901565, -92.222207 34.901601, -92.22228 34.901644, -92.222314 34.901669, -92.222406 34.901698, -92.222502 34.901714, -92.222601 34.901716, -92.222698 34.901704, -92.222994 34.901649, -92.223494 34.901534, -92.223653 34.901501, -92.223904 34.901437, -92.224338 34.90135, -92.224548 34.901297, -92.224692 34.901265, -92.224874 34.901231, -92.22509 34.901181, -92.225301 34.901118, -92.225476 34.901044, -92.22573 34.900925, -92.225859 34.900876, -92.225995 34.90084, -92.226112 34.900826, -92.226291 34.900813, -92.226779 34.900809, -92.226979 34.900821, -92.227176 34.90085, -92.22734 34.900896, -92.228028 34.901043, -92.228147 34.901084, -92.22826 34.901136, -92.228373 34.901222, -92.228535 34.901383, -92.22864 34.901471, -92.228787 34.901574, -92.228908 34.901635, -92.229092 34.901699, -92.22939 34.901784, -92.229578 34.901816, -92.229701 34.901828, -92.229887 34.901836, -92.230035 34.901834, -92.230165 34.901845, -92.230291 34.901873, -92.230362 34.901902, -92.230404 34.90193, -92.230456 34.90198, -92.230479 34.902019, -92.230535 34.902139, -92.230562 34.902222, -92.230578 34.902396, -92.230566 34.902749, -92.230509 34.903625, -92.230509 34.903763, -92.23052 34.903902, -92.231435 34.90364, -92.231629 34.903571, -92.231713 34.903526, -92.231764 34.90349, -92.231839 34.903417, -92.231901 34.903336, -92.23212 34.903004, -92.232194 34.902916, -92.232341 34.902754, -92.232482 34.902577, -92.232543 34.902479, -92.232613 34.902328, -92.232748 34.901993, -92.233095 34.901056, -92.233189 34.90077, -92.23344 34.900042, -92.233467 34.89993, -92.233496 34.899738, -92.233532 34.899087, -92.233557 34.898398, -92.233582 34.897937, -92.233606 34.897248, -92.233611 34.897193, -92.233626 34.897057, -92.233634 34.896891, -92.233658 34.896725, -92.233716 34.896492, -92.233779 34.896309, -92.233859 34.89613, -92.234019 34.895831, -92.234049 34.895791, -92.234088 34.895701, -92.234113 34.895608, -92.234121 34.895544, -92.234108 34.895404, -92.234067 34.895243, -92.234008 34.895073, -92.233819 34.894683, -92.23377 34.894549, -92.233746 34.894458, -92.233723 34.894308, -92.23369 34.893831, -92.233649 34.893653, -92.233593 34.893479, -92.233495 34.893246, -92.233473 34.893166, -92.233467 34.893085, -92.233482 34.892977, -92.233528 34.892871, -92.233617 34.892734, -92.233896 34.892367, -92.234065 34.892072, -92.234148 34.891904, -92.23425 34.89164, -92.234299 34.891419, -92.234303 34.89107, -92.234299 34.890714, -92.234302 34.890474, -92.234296 34.890186, -92.234293 34.889793, -92.234298 34.889447, -92.234302 34.889168, -92.234293 34.888712, -92.23431 34.888354, -92.234331 34.888182, -92.234351 34.888025, -92.234527 34.887124, -92.234551 34.886977, -92.234578 34.886851, -92.234666 34.886357, -92.234703 34.886177, -92.234818 34.885549, -92.23487 34.885356, -92.234916 34.885229, -92.235002 34.885044, -92.235518 34.884185, -92.235676 34.884189, -92.2358 34.884194, -92.235989 34.884202, -92.236485 34.884224, -92.237257 34.884251, -92.237984 34.884276, -92.239052 34.884329, -92.239487 34.884343, -92.239826 34.884362, -92.240465 34.884389, -92.241024 34.884418, -92.241027 34.884516, -92.241018 34.884844, -92.241012 34.885611, -92.240991 34.885724, -92.240823 34.886119, -92.240799 34.88619, -92.240764 34.886333, -92.240751 34.886519, -92.240747 34.88667, -92.24073 34.88702, -92.240731 34.887356, -92.240707 34.888477, -92.24071 34.888731, -92.240695 34.889429, -92.240684 34.891023, -92.240673 34.891214, -92.240905 34.891231, -92.24185 34.891263, -92.242742 34.891291, -92.242952 34.891303, -92.244046 34.891337, -92.244041 34.890794, -92.244057 34.8903, -92.244067 34.889427, -92.244063 34.888878, -92.244072 34.888608, -92.244072 34.888233, -92.244083 34.88766, -92.244097 34.887294, -92.244104 34.886817, -92.244125 34.886294, -92.24411 34.886067, -92.2441 34.88598, -92.244099 34.885897, -92.244083 34.885738, -92.244079 34.885614, -92.244082 34.885304, -92.244098 34.884625, -92.244084 34.884581, -92.244053 34.88454, -92.244674 34.884562, -92.244715 34.883418, -92.244729 34.882465, -92.24473 34.881923, -92.244713 34.881613, -92.244693 34.881471, -92.245164 34.881406, -92.245926 34.881646, -92.246755 34.882311, -92.246919 34.88255, -92.247046 34.882736, -92.247427 34.882939, -92.247783 34.882982, -92.248598 34.882991, -92.249237 34.883001, -92.249372 34.877788, -92.244402 34.877673, -92.244401 34.877137, -92.244402 34.875845, -92.242506 34.875811, -92.242506 34.875534, -92.242108 34.875527, -92.241733 34.87534, -92.241195 34.874801, -92.241277 34.874732, -92.241315 34.8747, -92.241333 34.874685, -92.241373 34.874653, -92.241409 34.874623, -92.241447 34.874592, -92.241808 34.874296, -92.242173 34.874, -92.242489 34.873728, -92.242574 34.873634, -92.242627 34.873555, -92.241433 34.873522, -92.240493 34.873366, -92.240438 34.872866, -92.240449 34.872379, -92.240451 34.872314, -92.240451 34.872289, -92.240453 34.87222, -92.240504 34.869986, -92.240513 34.869618, -92.234067 34.869505, -92.234439 34.86941, -92.235164 34.869214, -92.235531 34.869125, -92.235766 34.869072, -92.23595 34.869025, -92.236041 34.868959, -92.23605 34.868931, -92.236053 34.868851, -92.236027 34.868765, -92.236007 34.868712, -92.236362 34.868702, -92.236576 34.868696, -92.236559 34.868273, -92.237389 34.868069, -92.237625 34.867636, -92.237667 34.867559, -92.237711 34.867477, -92.237386 34.867345, -92.237084 34.867223, -92.236843 34.867383, -92.236707 34.867473, -92.236637 34.867438, -92.236436 34.867338, -92.236228 34.867178, -92.236036 34.867146, -92.235796 34.867098, -92.235636 34.867162, -92.235508 34.867226, -92.235412 34.867466, -92.2353 34.867706, -92.235235 34.867898, -92.234979 34.868282, -92.234788 34.868379, -92.234596 34.86841, -92.234356 34.868426, -92.234132 34.868458, -92.23402 34.86857, -92.23394 34.868618, -92.233748 34.868666, -92.233507 34.868698, -92.233187 34.868762, -92.232947 34.868858, -92.232659 34.868986, -92.232451 34.869066, -92.232227 34.869226, -92.231732 34.869418, -92.231679 34.869443, -92.231684 34.869273, -92.231725 34.867732, -92.227405 34.867652, -92.227406 34.867615, -92.227414 34.867233, -92.227418 34.867063, -92.227444 34.866112, -92.227443 34.865943, -92.227642 34.865937, -92.227961 34.865918, -92.228084 34.865902, -92.228296 34.865842, -92.228368 34.86581, -92.228504 34.865736, -92.228679 34.865581, -92.228806 34.865432, -92.229171 34.864985, -92.22937 34.86473, -92.229451 34.864632, -92.229516 34.864554, -92.229562 34.864491, -92.229662 34.864353, -92.229724 34.864276, -92.229824 34.864157, -92.23007 34.863873, -92.230641 34.863188, -92.230684 34.863133, -92.231335 34.862315, -92.231508 34.862125, -92.231684 34.861946, -92.23183 34.861837, -92.231867 34.861821, -92.231987 34.86177, -92.232034 34.861753, -92.232045 34.860568, -92.231891 34.860593, -92.230729 34.860782, -92.228773 34.859516, -92.226824 34.858253, -92.226621 34.857685, -92.226594 34.857046, -92.226874 34.856746, -92.227143 34.856923, -92.227464 34.857123, -92.227863 34.857397, -92.228231 34.857631, -92.228325 34.857687, -92.229362 34.858382, -92.229738 34.858625, -92.229863 34.858687, -92.229997 34.858734, -92.230168 34.858776, -92.230226 34.858787, -92.230353 34.858792, -92.230543 34.858785, -92.230635 34.85877, -92.230773 34.858725, -92.230923 34.858663, -92.231051 34.858594, -92.231146 34.858527, -92.231229 34.85845, -92.2313 34.858365, -92.231376 34.858254, -92.231436 34.858136, -92.231464 34.858004, -92.231474 34.85787, -92.231474 34.857775, -92.23146 34.857681, -92.231443 34.85762, -92.231561 34.856692, -92.231565 34.85666, -92.231596 34.856419, -92.231691 34.855752, -92.231934 34.855745, -92.235867 34.855632, -92.236353 34.855618, -92.23636 34.855158, -92.236382 34.854929, -92.236617 34.851871, -92.236629 34.851716, -92.236653 34.851409, -92.239734 34.851493, -92.243253 34.851578, -92.245474 34.851624, -92.245622 34.846444, -92.245681 34.844916, -92.245694 34.844461, -92.245383 34.844459, -92.245365 34.843866, -92.245365 34.843482, -92.245399 34.842936, -92.245823 34.84075, -92.246728 34.840767, -92.246853 34.840767, -92.246977 34.840767, -92.248668 34.840796, -92.2488 34.840798, -92.24902 34.840798, -92.250308 34.84082, -92.253189 34.840871, -92.254856 34.840889, -92.255855 34.840905, -92.256388 34.840913, -92.258968 34.84094, -92.259278 34.840939, -92.259458 34.840953, -92.25948 34.840905, -92.259524 34.840835, -92.259592 34.840759, -92.259654 34.840667, -92.259766 34.840469, -92.260081 34.839808, -92.260189 34.839613, -92.26031 34.839424, -92.260458 34.83923, -92.260582 34.839093, -92.260904 34.838773, -92.262087 34.837654, -92.262184 34.837562, -92.262341 34.837406, -92.262372 34.837355, -92.262386 34.8373, -92.262382 34.837261, -92.262363 34.837213, -92.26233 34.83717, -92.262047 34.836918, -92.26191 34.836775, -92.261828 34.836674, -92.261757 34.836579, -92.261709 34.836502, -92.261585 34.836301, -92.261435 34.835952, -92.261352 34.835658, -92.261297 34.835219, -92.261298 34.835009, -92.261302 34.834976, -92.261325 34.834787, -92.261517 34.833854, -92.261609 34.833481, -92.261865 34.832215, -92.262289 34.83011, -92.262405 34.829534, -92.262634 34.828176, -92.262813 34.827114, -92.262864 34.826844, -92.26303 34.825962, -92.263166 34.825414, -92.263181 34.825365, -92.263432 34.82454, -92.263922 34.823054, -92.263954 34.822986, -92.26418 34.822392, -92.264451 34.821777, -92.264495 34.82168, -92.264736 34.821152, -92.264853 34.820913, -92.264874 34.820876, -92.265055 34.820564, -92.265282 34.820202, -92.265331 34.820116, -92.265543 34.819759, -92.265606 34.81964, -92.265689 34.819455, -92.265723 34.819381, -92.26574 34.819336, -92.265752 34.819305, -92.265731 34.819242, -92.265748 34.819219, -92.265772 34.819202, -92.265825 34.819178, -92.265964 34.81913, -92.266071 34.819077, -92.266835 34.818642)))"} -{"geo_id":"02062","urban_area_code":"02062","name":"Ames, IA","lsad_name":"Ames, IA Urbanized Area","area_lsad_code":"75","mtfcc_feature_class_code":"G3500","type":"U","functional_status":"S","area_land_meters":59373042,"area_water_meters":880226,"internal_point_lon":-93.6337621,"internal_point_lat":42.0312162,"internal_point_geom":"POINT(-93.6337621 42.0312162)","urban_area_geom":"MULTIPOLYGON(((-93.646261 42.107847, -93.646233 42.108718, -93.646245 42.108776, -93.646262 42.108814, -93.646285 42.108838, -93.646386 42.108892, -93.646431 42.108908, -93.646498 42.108912, -93.646643 42.108903, -93.646672 42.108903, -93.64851 42.108905, -93.648657 42.108912, -93.648866 42.108922, -93.64941 42.108947, -93.649528 42.108953, -93.650107 42.108954, -93.651317 42.108955, -93.652138 42.108971, -93.652876 42.108985, -93.653473 42.108993, -93.653529 42.108988, -93.653563 42.10898, -93.653601 42.108946, -93.653618 42.108909, -93.653617 42.108822, -93.653601 42.106851, -93.652879 42.106841, -93.652155 42.106835, -93.650144 42.106819, -93.649462 42.106814, -93.649296 42.106813, -93.649345 42.106331, -93.649388 42.103976, -93.649383 42.103215, -93.649372 42.101755, -93.649386 42.100917, -93.649408 42.099639, -93.64941 42.099492, -93.64941 42.097629, -93.649422 42.09601, -93.649426 42.095513, -93.649449 42.093582, -93.649452 42.092445, -93.649459 42.090347, -93.649505 42.084807, -93.649512 42.083985, -93.649531 42.082611, -93.649561 42.07797, -93.64891 42.077962, -93.644969 42.077908, -93.644859 42.077907, -93.64204 42.07788, -93.640552 42.077859, -93.640025 42.077857, -93.640023 42.078259, -93.640021 42.078693, -93.640022 42.080156, -93.640015 42.081573, -93.640005 42.082244, -93.640002 42.082465, -93.639995 42.082873, -93.639995 42.084601, -93.639992 42.085142, -93.639976 42.087805, -93.639943 42.09119, -93.639941 42.092227, -93.639926 42.092386, -93.639936 42.093002, -93.639908 42.095245, -93.639911 42.095735, -93.639912 42.095975, -93.639916 42.096455, -93.639912 42.099044, -93.639899 42.099539, -93.639882 42.100207, -93.639883 42.100732, -93.639881 42.102249, -93.639885 42.102874, -93.639889 42.103455, -93.639876 42.104696, -93.639876 42.104728, -93.639868 42.105617, -93.639875 42.106742, -93.639872 42.107929, -93.640167 42.107948, -93.640262 42.107943, -93.640557 42.107883, -93.640641 42.107858, -93.64078 42.107807, -93.640897 42.107782, -93.641066 42.107781, -93.641139 42.10778, -93.641206 42.107787, -93.641545 42.107819, -93.642026 42.107871, -93.642104 42.107862, -93.64216 42.107837, -93.642193 42.107812, -93.642204 42.107774, -93.64219 42.106774, -93.644748 42.10679, -93.646253 42.106794, -93.646261 42.107256, -93.646267 42.107648, -93.646261 42.107847)), ((-93.600809 42.03766, -93.600934 42.03784, -93.602268 42.038766, -93.60229 42.038924, -93.602317 42.039112, -93.602792 42.042429, -93.603066 42.044341, -93.603814 42.045378, -93.605104 42.047165, -93.605392 42.047564, -93.605462 42.047661, -93.605579 42.047823, -93.607861 42.049013, -93.609975 42.04934, -93.610379 42.049598, -93.61074 42.049572, -93.611408 42.05036, -93.611653 42.051354, -93.611626 42.051418, -93.612077 42.05207, -93.612852 42.052324, -93.613253 42.052257, -93.613988 42.052284, -93.614843 42.052592, -93.615244 42.052952, -93.615458 42.053968, -93.615004 42.054877, -93.614189 42.05485, -93.613694 42.055051, -93.613614 42.055479, -93.613908 42.056494, -93.614282 42.057096, -93.614937 42.057203, -93.615204 42.057069, -93.615338 42.056775, -93.615913 42.056441, -93.616069 42.056381, -93.616327 42.05628, -93.616554 42.056374, -93.617088 42.056593, -93.617312 42.057724, -93.617412 42.057821, -93.617778 42.058205, -93.617878 42.058306, -93.619194 42.059501, -93.619369 42.059593, -93.619977 42.059914, -93.620024 42.059953, -93.61999 42.060435, -93.619845 42.061279, -93.62005 42.061288, -93.620155 42.061289, -93.62061 42.061292, -93.621853 42.061302, -93.621398 42.059883, -93.621303 42.059507, -93.62119 42.059088, -93.621047 42.058428, -93.621058 42.058221, -93.620925 42.057498, -93.620827 42.056856, -93.620787 42.056643, -93.621048 42.056746, -93.621017 42.056887, -93.621005 42.057114, -93.621071 42.057497, -93.62108 42.057554, -93.621116 42.057752, -93.621165 42.057876, -93.621253 42.058005, -93.62143 42.058212, -93.622892 42.059982, -93.623081 42.060209, -93.623331 42.06047, -93.623535 42.060889, -93.623708 42.061282, -93.623724 42.061318, -93.623744 42.061507, -93.623752 42.06158, -93.62375 42.061737, -93.624037 42.061735, -93.623971 42.062396, -93.623943 42.062537, -93.623611 42.064245, -93.623529 42.064856, -93.623417 42.066674, -93.623394 42.067057, -93.623169 42.068281, -93.622836 42.069487, -93.622526 42.070698, -93.622428 42.071021, -93.622396 42.071599, -93.622207 42.072283, -93.621971 42.072313, -93.622092 42.071382, -93.622267 42.070747, -93.621243 42.070721, -93.621256 42.07032, -93.621188 42.070246, -93.621182 42.069666, -93.621177 42.068489, -93.620639 42.068503, -93.620237 42.068538, -93.619856 42.068529, -93.619493 42.068473, -93.619093 42.06838, -93.618786 42.068352, -93.61846 42.068259, -93.617986 42.068129, -93.61767 42.068082, -93.617332 42.06789, -93.61733 42.068253, -93.617314 42.068546, -93.61713 42.068747, -93.617204 42.069017, -93.617204 42.069125, -93.617065 42.069264, -93.616927 42.069304, -93.616759 42.069312, -93.616682 42.06952, -93.616238 42.069677, -93.616175 42.069862, -93.616236 42.070009, -93.616479 42.070079, -93.616645 42.070088, -93.616859 42.070082, -93.617056 42.070115, -93.617162 42.070238, -93.61736 42.070286, -93.617497 42.070348, -93.617495 42.070603, -93.617434 42.07068, -93.617631 42.070688, -93.61793 42.070713, -93.617867 42.070734, -93.617768 42.070798, -93.617709 42.070854, -93.617587 42.07102, -93.617278 42.071478, -93.617146 42.071687, -93.617118 42.071758, -93.617084 42.071864, -93.617062 42.071972, -93.617026 42.072151, -93.617016 42.07222, -93.616968 42.072344, -93.616864 42.072521, -93.61645 42.073068, -93.615982 42.0737, -93.615848 42.073863, -93.615652 42.074064, -93.615543 42.074152, -93.615466 42.074208, -93.615399 42.074245, -93.615297 42.07429, -93.615209 42.074328, -93.614948 42.074424, -93.614282 42.074616, -93.614102 42.074659, -93.613954 42.074689, -93.613749 42.074717, -93.613265 42.07476, -93.612361 42.074841, -93.61228 42.0745, -93.612175 42.073787, -93.611968 42.073048, -93.611906 42.072923, -93.611581 42.072263, -93.611406 42.072319, -93.610966 42.072512, -93.610885 42.072576, -93.61081 42.072662, -93.610701 42.072819, -93.610626 42.072945, -93.61057 42.073112, -93.610552 42.073187, -93.610568 42.073347, -93.610617 42.073518, -93.610663 42.073734, -93.610757 42.074207, -93.610813 42.074584, -93.610854 42.074805, -93.610899 42.074974, -93.609751 42.075064, -93.609608 42.075086, -93.609508 42.075118, -93.609441 42.075158, -93.609379 42.075208, -93.609341 42.075255, -93.609304 42.075344, -93.609281 42.07544, -93.609272 42.075525, -93.609293 42.076215, -93.609298 42.076835, -93.609276 42.077928, -93.61057 42.07794, -93.610655 42.077935, -93.610722 42.077926, -93.610768 42.077913, -93.610824 42.077878, -93.610856 42.077852, -93.610873 42.07782, -93.61088 42.077778, -93.610886 42.076548, -93.612523 42.076561, -93.613204 42.076557, -93.613424 42.076556, -93.613606 42.076546, -93.613756 42.076521, -93.614306 42.076304, -93.614625 42.076723, -93.614659 42.076793, -93.614738 42.0771, -93.614767 42.077196, -93.6148 42.077246, -93.614936 42.077305, -93.615897 42.077308, -93.616205 42.077305, -93.616401 42.077308, -93.61638 42.077182, -93.616363 42.077018, -93.616384 42.076965, -93.61649 42.07696, -93.616623 42.076969, -93.61676 42.076998, -93.616806 42.07704, -93.616783 42.077176, -93.61676 42.077305, -93.616782 42.077305, -93.619565 42.077305, -93.619656 42.077311, -93.619746 42.077338, -93.61982 42.077406, -93.619886 42.077536, -93.619911 42.077576, -93.620042 42.077749, -93.62009 42.077806, -93.620346 42.077943, -93.620434 42.077952, -93.620669 42.077954, -93.621497 42.077954, -93.621589 42.077954, -93.622422 42.077953, -93.62267 42.077953, -93.622911 42.077953, -93.624345 42.077945, -93.624353 42.078399, -93.624348 42.07898, -93.625191 42.07898, -93.626983 42.078981, -93.626991 42.07841, -93.62848 42.078408, -93.628423 42.077939, -93.629671 42.077955, -93.630118 42.077961, -93.631978 42.077947, -93.633926 42.077939, -93.635442 42.077938, -93.638179 42.077887, -93.63861 42.077876, -93.639352 42.077858, -93.639542 42.077857, -93.640025 42.077857, -93.640013 42.074705, -93.640015 42.073901, -93.640015 42.073828, -93.640004 42.072913, -93.64001 42.072678, -93.640022 42.072248, -93.640022 42.072227, -93.640021 42.072131, -93.64001 42.07077, -93.640007 42.070382, -93.640002 42.069015, -93.64 42.06826, -93.639998 42.068122, -93.63998 42.067162, -93.637536 42.067146, -93.637538 42.063803, -93.637639 42.063804, -93.638033 42.063806, -93.638033 42.063684, -93.638034 42.063532, -93.638167 42.063533, -93.638417 42.063535, -93.639865 42.063554, -93.639992 42.063547, -93.640005 42.063547, -93.640149 42.063547, -93.641496 42.06354, -93.643875 42.063529, -93.644343 42.063525, -93.644548 42.063526, -93.644829 42.063524, -93.644892 42.063524, -93.64512 42.063523, -93.645353 42.063522, -93.645649 42.063521, -93.645965 42.063519, -93.646431 42.063517, -93.646539 42.063544, -93.646526 42.063517, -93.646659 42.063518, -93.647491 42.063513, -93.64741 42.063397, -93.647045 42.062878, -93.645785 42.061126, -93.645603 42.060852, -93.645111 42.060186, -93.644815 42.059756, -93.644625 42.059499, -93.643611 42.058041, -93.643567 42.057981, -93.643139 42.057399, -93.643365 42.057393, -93.643503 42.057586, -93.643648 42.057791, -93.643957 42.058224, -93.64437 42.058804, -93.644783 42.059383, -93.644817 42.059433, -93.645157 42.059909, -93.645473 42.060353, -93.647724 42.063516, -93.649656 42.063511, -93.651723 42.063498, -93.651907 42.063496, -93.652091 42.063496, -93.652515 42.063494, -93.653064 42.06349, -93.653447 42.063488, -93.653838 42.063486, -93.654257 42.063483, -93.654498 42.063482, -93.654648 42.063481, -93.655042 42.063478, -93.655385 42.063476, -93.656312 42.06347, -93.656583 42.063469, -93.65691 42.063467, -93.65723 42.063465, -93.657568 42.063463, -93.657894 42.063462, -93.65821 42.06346, -93.658783 42.063456, -93.659319 42.063453, -93.659318 42.063423, -93.659311 42.062967, -93.6593 42.061539, -93.659294 42.061199, -93.659294 42.061186, -93.659259 42.059519, -93.659252 42.0592, -93.65923 42.058983, -93.659201 42.05886, -93.65916 42.058717, -93.659099 42.05855, -93.659026 42.058371, -93.658946 42.05821, -93.658814 42.057992, -93.658729 42.057861, -93.658478 42.057578, -93.658307 42.05742, -93.658089 42.057237, -93.65789 42.057083, -93.657659 42.056936, -93.657141 42.056689, -93.657096 42.05667, -93.656582 42.056397, -93.65628 42.056236, -93.658268 42.05623, -93.659041 42.056227, -93.659196 42.056227, -93.659218 42.056103, -93.659232 42.056023, -93.659228 42.055864, -93.65922 42.055493, -93.659219 42.05546, -93.659212 42.054554, -93.659206 42.053998, -93.659194 42.053001, -93.659189 42.052594, -93.659179 42.052127, -93.659161 42.051627, -93.659143 42.051162, -93.659126 42.050686, -93.65912 42.050536, -93.659115 42.050367, -93.659096 42.0497, -93.659076 42.049018, -93.65908 42.048992, -93.659079 42.048916, -93.65908 42.048846, -93.658338 42.048604, -93.657519 42.047785, -93.656651 42.046691, -93.656522 42.045793, -93.656024 42.045015, -93.656295 42.043988, -93.656143 42.043469, -93.655495 42.043188, -93.654997 42.04242, -93.654278 42.042044, -93.654176 42.041863, -93.654019 42.041585, -93.653734 42.041315, -93.653208 42.041866, -93.652741 42.042356, -93.652705 42.042354, -93.652639 42.042343, -93.652575 42.042325, -93.652513 42.042301, -93.652453 42.042271, -93.652397 42.042235, -93.652348 42.042194, -93.652296 42.042157, -93.652241 42.042125, -93.652183 42.042098, -93.652124 42.042075, -93.651718 42.041928, -93.651668 42.041912, -93.651617 42.041902, -93.651564 42.041897, -93.651512 42.041896, -93.65146 42.041901, -93.651114 42.041927, -93.650924 42.04195, -93.650734 42.041979, -93.650546 42.042013, -93.650358 42.042054, -93.650298 42.042067, -93.650238 42.042085, -93.650181 42.042109, -93.650125 42.042137, -93.650073 42.04217, -93.650023 42.042207, -93.649977 42.042249, -93.649928 42.042281, -93.649914 42.042285, -93.649898 42.042285, -93.649882 42.042282, -93.649867 42.042277, -93.649853 42.042269, -93.649841 42.04226, -93.649817 42.04224, -93.649797 42.042217, -93.649779 42.042192, -93.649766 42.042165, -93.649645 42.041875, -93.649612 42.041795, -93.649589 42.041707, -93.649478 42.0417, -93.649175 42.04168, -93.649034 42.041676, -93.648936 42.041669, -93.648868 42.04166, -93.64848 42.041573, -93.648218 42.041483, -93.647771 42.041397, -93.647644 42.041401, -93.647463 42.041417, -93.647408 42.041427, -93.647312 42.041451, -93.647154 42.041506, -93.646853 42.041596, -93.64656 42.041665, -93.646378 42.041699, -93.646293 42.041699, -93.646124 42.041687, -93.645928 42.041656, -93.645741 42.041632, -93.645444 42.041618, -93.645265 42.041616, -93.645198 42.041616, -93.644991 42.0416, -93.644952 42.041594, -93.644826 42.041578, -93.644824 42.041499, -93.644949 42.041503, -93.644944 42.041068, -93.644938 42.040608, -93.644936 42.040309, -93.644926 42.039834, -93.644927 42.039607, -93.644921 42.039151, -93.644923 42.03895, -93.644905 42.038021, -93.644901 42.03767, -93.644902 42.037421, -93.644897 42.037142, -93.644896 42.037036, -93.644886 42.036949, -93.644882 42.036906, -93.644876 42.03639, -93.648518 42.036385, -93.65004 42.035068, -93.650084 42.035059, -93.650557 42.034991, -93.650811 42.034972, -93.651078 42.034979, -93.652223 42.035065, -93.652781 42.035112, -93.653111 42.035131, -93.653274 42.035133, -93.653537 42.035114, -93.653723 42.035089, -93.653819 42.03507, -93.654042 42.035011, -93.654684 42.035106, -93.655467 42.035209, -93.65552 42.035214, -93.65623 42.035282, -93.656885 42.035326, -93.657994 42.035401, -93.658326 42.035424, -93.658953 42.035472, -93.66049 42.03559, -93.661201 42.035645, -93.661952 42.035686, -93.662797 42.035724, -93.663679 42.035772, -93.664723 42.035843, -93.665416 42.035883, -93.666826 42.035985, -93.668341 42.036081, -93.669365 42.036161, -93.670705 42.036294, -93.674096 42.036672, -93.675515 42.036821, -93.67711 42.036996, -93.678783 42.037191, -93.678784 42.037283, -93.678787 42.037801, -93.680075 42.037786, -93.680546 42.03778, -93.680858 42.037776, -93.680859 42.037542, -93.683532 42.037826, -93.683521 42.037709, -93.683559 42.037713, -93.684157 42.037777, -93.684384 42.037801, -93.68567 42.037945, -93.687478 42.038133, -93.688034 42.038198, -93.688261 42.038225, -93.689135 42.038313, -93.689732 42.038381, -93.691105 42.038525, -93.692399 42.038675, -93.692885 42.038727, -93.693233 42.038775, -93.694184 42.038873, -93.694566 42.038913, -93.695362 42.039002, -93.6966 42.039151, -93.697953 42.039303, -93.698759 42.039384, -93.698762 42.039212, -93.698762 42.039183, -93.698766 42.038981, -93.698767 42.038941, -93.698753 42.038004, -93.698752 42.037921, -93.698752 42.037881, -93.698752 42.037787, -93.698752 42.037725, -93.698755 42.036521, -93.698713 42.03456, -93.698702 42.0333, -93.698724 42.029696, -93.699319 42.029705, -93.699394 42.029701, -93.699642 42.029608, -93.699669 42.02958, -93.699671 42.029553, -93.699658 42.029508, -93.699621 42.029468, -93.699521 42.029425, -93.699421 42.029413, -93.699094 42.02943, -93.698726 42.029462, -93.698727 42.029318, -93.698728 42.028839, -93.698728 42.028771, -93.698728 42.028709, -93.698734 42.026354, -93.698734 42.026165, -93.698734 42.026151, -93.698738 42.025876, -93.698741 42.025569, -93.69875 42.023801, -93.698748 42.02305, -93.698922 42.023128, -93.698992 42.023179, -93.699081 42.023316, -93.699124 42.023366, -93.699191 42.023397, -93.699288 42.023425, -93.699497 42.023438, -93.699972 42.02343, -93.70006 42.023396, -93.700071 42.023371, -93.700104 42.022784, -93.700386 42.022788, -93.701005 42.022805, -93.70105 42.022806, -93.701937 42.02283, -93.705019 42.0229, -93.705459 42.022909, -93.707277 42.022945, -93.710299 42.023018, -93.712464 42.023069, -93.713282 42.023077, -93.714034 42.023069, -93.71486 42.023072, -93.715659 42.023098, -93.7169 42.023124, -93.718328 42.023143, -93.718326 42.022003, -93.718062 42.021954, -93.717389 42.021771, -93.717063 42.021647, -93.716935 42.021584, -93.716673 42.021415, -93.716128 42.020939, -93.715427 42.020286, -93.715017 42.019963, -93.714595 42.019689, -93.714223 42.01948, -93.713842 42.019301, -93.713358 42.019111, -93.711514 42.018452, -93.711167 42.018312, -93.709663 42.017865, -93.707433 42.017191, -93.705378 42.016582, -93.702704 42.015775, -93.701361 42.015379, -93.698744 42.014591, -93.698693 42.014576, -93.69869 42.014343, -93.697986 42.014131, -93.697033 42.013837, -93.695506 42.013385, -93.694777 42.013156, -93.693263 42.012709, -93.692672 42.012519, -93.691363 42.012139, -93.690595 42.011905, -93.689592 42.011592, -93.688585 42.011288, -93.687311 42.01091, -93.686236 42.010585, -93.685221 42.010279, -93.684596 42.010105, -93.683451 42.009738, -93.682832 42.009569, -93.680852 42.008959, -93.679662 42.008606, -93.67941 42.008535, -93.678937 42.008402, -93.678866 42.008374, -93.678751 42.008331, -93.678603 42.008284, -93.678186 42.008165, -93.675696 42.007404, -93.674685 42.00718, -93.673989 42.00704, -93.672578 42.006839, -93.671787 42.006784, -93.671058 42.006773, -93.669496 42.006763, -93.668779 42.006782, -93.668725 42.006782, -93.668605 42.006781, -93.666339 42.006765, -93.664796 42.006762, -93.66203 42.006792, -93.660654 42.006772, -93.660062 42.006768, -93.658934 42.006779, -93.657392 42.006785, -93.656441 42.006776, -93.65485 42.006776, -93.654275 42.006769, -93.653651 42.006737, -93.653197 42.006692, -93.652789 42.006641, -93.652557 42.00659, -93.652042 42.006506, -93.651895 42.006468, -93.654139 42.006463, -93.655291 42.006466, -93.655291 42.006225, -93.655324 42.003608, -93.655329 42.003487, -93.656454 42.003482, -93.656879 42.003474, -93.657618 42.003472, -93.658261 42.003462, -93.658888 42.003474, -93.658892 42.003988, -93.659168 42.003996, -93.661838 42.004006, -93.661823 42.00307, -93.661819 42.002851, -93.661812 42.00237, -93.660765 42.002374, -93.660673 42.002373, -93.660709 42.002906, -93.660522 42.002911, -93.660483 42.00237, -93.660407 42.002369, -93.65916 42.002355, -93.659032 42.002354, -93.659009 42.002354, -93.658878 42.002354, -93.658878 42.002317, -93.658875 42.002059, -93.658864 42.001903, -93.658854 42.001846, -93.658834 42.001736, -93.658812 42.001612, -93.658775 42.00145, -93.658742 42.00134, -93.658687 42.001188, -93.658609 42.001023, -93.658422 42.000659, -93.65795 41.999751, -93.65778 41.999458, -93.657722 41.99922, -93.657714 41.999157, -93.658692 41.999961, -93.658804 42.000023, -93.658782 41.999143, -93.659255 41.999145, -93.65925 41.99858, -93.65925 41.998476, -93.659372 41.998484, -93.659395 41.998486, -93.660069 41.998548, -93.66029 41.99857, -93.660486 41.998594, -93.660567 41.998605, -93.660682 41.998613, -93.660825 41.998615, -93.660949 41.998605, -93.661082 41.998579, -93.661194 41.998552, -93.661529 41.998479, -93.662 41.99843, -93.662518 41.998434, -93.662517 41.998303, -93.663257 41.998428, -93.663269 41.997677, -93.663746 41.997681, -93.663737 41.99763, -93.663735 41.997418, -93.663457 41.997305, -93.661119 41.996486, -93.66015 41.997188, -93.658413 41.99845, -93.658269 41.998442, -93.657886 41.998383, -93.657936 41.998312, -93.658362 41.997781, -93.658653 41.997438, -93.658789 41.997223, -93.658812 41.997165, -93.658834 41.996948, -93.65882 41.996759, -93.656919 41.996902, -93.654819 41.997043, -93.654753 41.997047, -93.654639 41.997054, -93.654231 41.997081, -93.653998 41.997096, -93.653719 41.997113, -93.652831 41.997171, -93.651601 41.997245, -93.65147 41.997253, -93.651366 41.997258, -93.650831 41.997291, -93.649828 41.997354, -93.649149 41.997379, -93.649133 41.996278, -93.649131 41.995947, -93.649131 41.995836, -93.649129 41.995394, -93.64926 41.995367, -93.649326 41.995366, -93.649422 41.995365, -93.649543 41.995364, -93.649802 41.995361, -93.650057 41.995351, -93.650181 41.995341, -93.650304 41.995325, -93.650424 41.995306, -93.650546 41.995284, -93.650669 41.995259, -93.65079 41.99523, -93.650909 41.995198, -93.651025 41.995164, -93.651138 41.995132, -93.651249 41.995104, -93.651362 41.995085, -93.651479 41.995077, -93.651596 41.995077, -93.65171 41.995085, -93.651753 41.995091, -93.651822 41.995101, -93.651931 41.995124, -93.652038 41.995155, -93.652352 41.995259, -93.652455 41.995285, -93.652556 41.995306, -93.652654 41.995324, -93.652749 41.995338, -93.652838 41.995343, -93.652916 41.995337, -93.652979 41.995323, -93.653035 41.995246, -93.653042 41.995181, -93.653056 41.995105, -93.653075 41.995021, -93.653091 41.994934, -93.653105 41.994842, -93.653114 41.994745, -93.653119 41.994645, -93.65312 41.994543, -93.653117 41.99444, -93.653112 41.994339, -93.653106 41.994239, -93.6531 41.99414, -93.653093 41.993948, -93.653093 41.993856, -93.653095 41.993766, -93.653098 41.993679, -93.653102 41.993595, -93.653105 41.993513, -93.653109 41.993434, -93.653116 41.993355, -93.65311 41.993289, -93.653013 41.993262, -93.652955 41.993239, -93.652887 41.993213, -93.652813 41.99318, -93.652734 41.993146, -93.652649 41.993115, -93.65256 41.993087, -93.652466 41.993059, -93.652369 41.993035, -93.652271 41.993012, -93.652175 41.992991, -93.652079 41.992975, -93.651985 41.992965, -93.651892 41.992959, -93.651799 41.992959, -93.651704 41.992965, -93.651607 41.992977, -93.651409 41.993019, -93.651311 41.993043, -93.651213 41.993068, -93.651115 41.993093, -93.651015 41.993119, -93.650914 41.993144, -93.650811 41.99317, -93.650709 41.993191, -93.650605 41.99321, -93.650499 41.993229, -93.650391 41.993246, -93.650284 41.99326, -93.650175 41.993272, -93.650064 41.993282, -93.649954 41.993291, -93.649845 41.993297, -93.649739 41.9933, -93.649636 41.993301, -93.649445 41.9933, -93.649357 41.9933, -93.649276 41.993302, -93.649203 41.993303, -93.649077 41.993309, -93.649075 41.992903, -93.649073 41.992659, -93.649071 41.992569, -93.648943 41.992581, -93.646017 41.992552, -93.646022 41.992875, -93.646027 41.993246, -93.646029 41.993433, -93.646034 41.993802, -93.646039 41.994025, -93.646044 41.994244, -93.646049 41.994464, -93.646054 41.994683, -93.64606 41.994903, -93.646065 41.995122, -93.64607 41.995342, -93.646075 41.995507, -93.64608 41.995707, -93.646084 41.995865, -93.646087 41.996019, -93.646091 41.996173, -93.646095 41.996326, -93.646098 41.996474, -93.646102 41.996623, -93.646105 41.996776, -93.646109 41.99693, -93.646113 41.997084, -93.646117 41.997276, -93.64612 41.997385, -93.646123 41.997406, -93.645515 41.997412, -93.644339 41.997422, -93.64419 41.997415, -93.644221 41.996561, -93.644103 41.996248, -93.644139 41.995848, -93.644158 41.995726, -93.64415 41.995611, -93.644126 41.995542, -93.644056 41.995366, -93.644057 41.995082, -93.644108 41.994913, -93.644103 41.994257, -93.644082 41.993797, -93.643682 41.99378, -93.643328 41.993787, -93.641767 41.993776, -93.640553 41.993767, -93.640246 41.993768, -93.639477 41.99467, -93.639384 41.994776, -93.639384 41.99494, -93.639384 41.995017, -93.639384 41.995098, -93.639381 41.995478, -93.639367 41.997349, -93.639375 41.997428, -93.639048 41.997473, -93.638458 41.997498, -93.638015 41.997538, -93.637614 41.997615, -93.637357 41.997702, -93.637239 41.997742, -93.636789 41.997963, -93.636523 41.998157, -93.636396 41.99826, -93.63629 41.998353, -93.635918 41.998748, -93.635545 41.999181, -93.635041 41.999783, -93.634734 42.000127, -93.634502 42.000353, -93.634178 42.000586, -93.633837 42.000788, -93.633675 42.000826, -93.633587 42.000734, -93.633196 42.000864, -93.633048 42.000904, -93.632731 42.000961, -93.632523 42.000986, -93.632331 42.000998, -93.631959 42.001005, -93.630339 42.001, -93.629632 42.000992, -93.629653 42.001137, -93.629516 42.001134, -93.62747 42.001097, -93.626696 42.001095, -93.626712 42.000959, -93.62587 42.000949, -93.625316 42.000946, -93.624868 42.00095, -93.625446 42.000374, -93.629643 41.996883, -93.62965 41.996366, -93.629653 41.993846, -93.629625 41.992638, -93.629609 41.991682, -93.625016 41.992885, -93.624127 41.993627, -93.623718 41.993886, -93.620485 41.993385, -93.619589 41.993246, -93.616621 41.992787, -93.614722 41.994887, -93.610222 41.994888, -93.610217 41.99455, -93.610211 41.994068, -93.610205 41.993908, -93.610192 41.993535, -93.610193 41.993071, -93.610195 41.991991, -93.610195 41.991644, -93.610193 41.990921, -93.610191 41.9905, -93.610189 41.98998, -93.610188 41.989844, -93.610185 41.988967, -93.610181 41.988072, -93.61018 41.987783, -93.610175 41.986574, -93.610179 41.986013, -93.603815 41.986064, -93.603076 41.986023, -93.600473 41.986034, -93.600477 41.986279, -93.600493 41.986573, -93.599299 41.986555, -93.597986 41.986587, -93.595801 41.986576, -93.595597 41.986575, -93.595598 41.986684, -93.595632 41.990602, -93.595624 41.990962, -93.595606 41.997387, -93.591462 41.997394, -93.591504 41.997863, -93.591145 41.99887, -93.591125 41.999096, -93.591219 41.99951, -93.591788 42.000508, -93.592132 42.001063, -93.592459 42.001458, -93.593068 42.002011, -93.593277 42.002203, -93.59386 42.002909, -93.593895 42.003494, -93.59378 42.003897, -93.593652 42.004266, -93.593895 42.005215, -93.59417 42.005574, -93.59419 42.005599, -93.594212 42.005629, -93.591215 42.005798, -93.590865 42.005817, -93.590866 42.005789, -93.5897 42.005821, -93.589423 42.005798, -93.589155 42.005768, -93.588828 42.005701, -93.586998 42.005255, -93.586821 42.0052, -93.586704 42.005151, -93.586653 42.005088, -93.58661 42.004983, -93.586556 42.004509, -93.586526 42.004103, -93.586254 42.004103, -93.585982 42.004103, -93.585673 42.004096, -93.585625 42.004092, -93.584847 42.004096, -93.584162 42.0041, -93.581167 42.004082, -93.581119 42.004082, -93.581123 42.005151, -93.581123 42.005185, -93.579708 42.005195, -93.579729 42.006734, -93.581193 42.006642, -93.581194 42.00674, -93.580514 42.006939, -93.580317 42.007163, -93.58033 42.007401, -93.579859 42.007436, -93.578917 42.007503, -93.577677 42.007564, -93.575446 42.007676, -93.575315 42.007714, -93.574784 42.0079, -93.574456 42.008044, -93.573954 42.008375, -93.573442 42.008748, -93.57252 42.009472, -93.572318 42.009691, -93.571939 42.010254, -93.571786 42.010505, -93.571609 42.010907, -93.57146 42.011596, -93.571341 42.012415, -93.571371 42.01502, -93.571374 42.015638, -93.571381 42.017462, -93.571409 42.019869, -93.571457 42.022021, -93.571462 42.022815, -93.571177 42.022817, -93.571174 42.022668, -93.570106 42.022697, -93.567721 42.022722, -93.564567 42.022738, -93.562219 42.022816, -93.561856 42.022846, -93.561918 42.023033, -93.561674 42.023039, -93.561193 42.02305, -93.560493 42.023074, -93.560543 42.024366, -93.560571 42.024488, -93.560654 42.024545, -93.560763 42.024557, -93.561184 42.024555, -93.561205 42.024522, -93.561227 42.024432, -93.561254 42.024387, -93.561625 42.024389, -93.56168 42.024381, -93.561707 42.024336, -93.561714 42.024686, -93.561715 42.024739, -93.561718 42.024853, -93.561272 42.024856, -93.560484 42.024864, -93.560486 42.024955, -93.560415 42.024958, -93.560415 42.025086, -93.560415 42.027586, -93.560381 42.027829, -93.560514 42.028944, -93.560521 42.029955, -93.560524 42.030026, -93.560523 42.030097, -93.560649 42.030082, -93.562213 42.029903, -93.562313 42.029893, -93.565698 42.029541, -93.569187 42.029192, -93.570453 42.029051, -93.570858 42.029006, -93.570752 42.030101, -93.570668 42.030944, -93.570444 42.033059, -93.570298 42.03451, -93.57029 42.034593, -93.570573 42.034593, -93.572576 42.034589, -93.573486 42.034585, -93.574932 42.034577, -93.575596 42.034574, -93.576235 42.034571, -93.579075 42.034547, -93.581543 42.034527, -93.581544 42.034985, -93.581544 42.035179, -93.581546 42.035695, -93.581542 42.03606, -93.581541 42.036186, -93.582747 42.036192, -93.582748 42.036807, -93.584223 42.036809, -93.584846 42.036823, -93.584975 42.036816, -93.585042 42.036796, -93.58514 42.036745, -93.58519 42.036704, -93.585221 42.036669, -93.585237 42.036619, -93.585246 42.036297, -93.585249 42.035411, -93.584035 42.035405, -93.584036 42.034994, -93.584038 42.034513, -93.585903 42.034503, -93.586915 42.034499, -93.588079 42.034493, -93.589149 42.034473, -93.589435 42.034468, -93.590194 42.034454, -93.591206 42.034428, -93.593242 42.034399, -93.593639 42.034411, -93.594169 42.034428, -93.594422 42.034435, -93.595856 42.036223, -93.596427 42.036712, -93.59683 42.037012, -93.598753 42.037619, -93.600809 42.03766)))"} -{"geo_id":"78580","urban_area_code":"78580","name":"San Antonio, TX","lsad_name":"San Antonio, TX Urbanized Area","area_lsad_code":"75","mtfcc_feature_class_code":"G3500","type":"U","functional_status":"S","area_land_meters":1547447607,"area_water_meters":9035408,"internal_point_lon":-98.4753761,"internal_point_lat":29.5143992,"internal_point_geom":"POINT(-98.4753761 29.5143992)","urban_area_geom":"MULTIPOLYGON(((-98.190306 29.728891, -98.189396 29.728203, -98.188496 29.727507, -98.187421 29.726675, -98.186765 29.726175, -98.18644 29.725925, -98.18571 29.725361, -98.186589 29.724877, -98.186714 29.724827, -98.18734 29.724602, -98.187471 29.724177, -98.18753 29.723998, -98.187622 29.723822, -98.187646 29.723789, -98.187684 29.723755, -98.187736 29.723739, -98.187895 29.723701, -98.188119 29.723659, -98.188245 29.723627, -98.188499 29.723582, -98.188765 29.723547, -98.188959 29.723542, -98.189138 29.723512, -98.189219 29.723491, -98.189374 29.72344, -98.189507 29.723384, -98.189667 29.723621, -98.189744 29.723719, -98.189805 29.723784, -98.189883 29.72385, -98.189991 29.723896, -98.190132 29.723942, -98.190204 29.723959, -98.190281 29.723971, -98.190413 29.723954, -98.190786 29.723891, -98.190939 29.723837, -98.191037 29.723784, -98.191099 29.723725, -98.19117 29.723627, -98.191208 29.723555, -98.191254 29.723445, -98.191262 29.723401, -98.191289 29.723263, -98.191297 29.72317, -98.191275 29.723042, -98.191255 29.722981, -98.191197 29.72288, -98.190863 29.722365, -98.190618 29.722063, -98.190548 29.721985, -98.190519 29.721744, -98.190511 29.721682, -98.19045 29.721447, -98.190425 29.72132, -98.190398 29.721228, -98.190681 29.721049, -98.191378 29.720609, -98.191538 29.720784, -98.19277 29.722203, -98.193566 29.72166, -98.194578 29.720969, -98.196161 29.719888, -98.196468 29.719678, -98.196533 29.719634, -98.196352 29.719084, -98.196215 29.718751, -98.196022 29.718394, -98.196113 29.71837, -98.196179 29.718344, -98.196252 29.718306, -98.196329 29.718254, -98.196452 29.718151, -98.196593 29.718005, -98.196755 29.717798, -98.19741 29.716987, -98.197705 29.716605, -98.197769 29.716502, -98.197783 29.716462, -98.19781 29.716296, -98.197796 29.716146, -98.197747 29.716001, -98.197679 29.715887, -98.197631 29.715825, -98.197554 29.715749, -98.197435 29.715661, -98.19712 29.715498, -98.196977 29.715435, -98.196886 29.715384, -98.196686 29.71524, -98.196621 29.715181, -98.196438 29.714996, -98.19608 29.714563, -98.195779 29.714211, -98.195685 29.714142, -98.195614 29.714108, -98.195521 29.714084, -98.195439 29.714074, -98.195372 29.714074, -98.195301 29.714086, -98.195243 29.714107, -98.195202 29.714138, -98.195165 29.714186, -98.195119 29.714272, -98.195 29.714524, -98.194863 29.714835, -98.194738 29.715067, -98.194674 29.715155, -98.194607 29.715237, -98.19452 29.715312, -98.194434 29.715368, -98.19406 29.715577, -98.193972 29.71563, -98.193738 29.715746, -98.19351 29.715455, -98.193471 29.715414, -98.19333 29.715308, -98.192998 29.715089, -98.192355 29.714675, -98.191953 29.714387, -98.191892 29.714329, -98.191811 29.714243, -98.191687 29.714078, -98.191607 29.713932, -98.191559 29.713808, -98.192616 29.713522, -98.192778 29.71347, -98.192988 29.713397, -98.19327 29.713275, -98.193506 29.713154, -98.193635 29.713082, -98.193921 29.712897, -98.194283 29.712653, -98.194456 29.712527, -98.193929 29.7119, -98.193535 29.711418, -98.19394 29.711162, -98.194861 29.710597, -98.196509 29.709577, -98.195632 29.708533, -98.194822 29.707509, -98.194492 29.707106, -98.194293 29.706871, -98.194096 29.706606, -98.19406 29.706574, -98.194032 29.70656, -98.193997 29.706549, -98.193941 29.706554, -98.193867 29.706588, -98.19373 29.706666, -98.193581 29.706757, -98.193465 29.706836, -98.192921 29.707172, -98.192268 29.707587, -98.191243 29.708204, -98.191152 29.708265, -98.191115 29.708302, -98.191029 29.708373, -98.190649 29.708591, -98.190321 29.708835, -98.189869 29.709138, -98.188824 29.709954, -98.189391 29.710485, -98.189886 29.710971, -98.190084 29.711216, -98.190181 29.711343, -98.190287 29.711531, -98.190481 29.711897, -98.191473 29.713828, -98.191076 29.71392, -98.190045 29.714209, -98.189805 29.714282, -98.189537 29.714386, -98.189293 29.714493, -98.189197 29.71454, -98.189125 29.714575, -98.188957 29.71466, -98.188864 29.714707, -98.187994 29.715175, -98.187071 29.715671, -98.186294 29.716088, -98.186157 29.716158, -98.185616 29.716457, -98.184473 29.717077, -98.183509 29.717593, -98.182966 29.717889, -98.182696 29.718051, -98.181266 29.719004, -98.180766 29.719348, -98.18048 29.719534, -98.178735 29.720716, -98.178278 29.721025, -98.178165 29.721124, -98.178044 29.721299, -98.178412 29.721328, -98.17901 29.721402, -98.179535 29.721514, -98.17991 29.721605, -98.180233 29.721702, -98.180493 29.721794, -98.180797 29.721907, -98.181235 29.722108, -98.181442 29.722213, -98.181852 29.722452, -98.182006 29.722547, -98.182377 29.722803, -98.182747 29.723081, -98.181873 29.723756, -98.179946 29.722371, -98.179883 29.722475, -98.179398 29.723166, -98.179264 29.723331, -98.179065 29.723551, -98.178672 29.723971, -98.178591 29.724079, -98.178492 29.724199, -98.177506 29.725506, -98.177396 29.725623, -98.177197 29.725789, -98.176778 29.726062, -98.17494 29.725671, -98.173181 29.723577, -98.173407 29.723441, -98.173585 29.723301, -98.173729 29.723161, -98.173863 29.72302, -98.173982 29.72288, -98.174149 29.722627, -98.174322 29.722217, -98.174335 29.722144, -98.174387 29.721867, -98.174429 29.721334, -98.173683 29.721289, -98.173398 29.721266, -98.172777 29.721198, -98.172298 29.721133, -98.171802 29.721048, -98.171513 29.720998, -98.169888 29.720651, -98.169793 29.720631, -98.168868 29.720434, -98.167536 29.720143, -98.166332 29.719888, -98.166449 29.719514, -98.16649 29.719437, -98.166572 29.719299, -98.166631 29.719219, -98.166824 29.718964, -98.167087 29.71875, -98.167541 29.718463, -98.167679 29.718376, -98.167947 29.718188, -98.168147 29.71806, -98.168389 29.717891, -98.1687 29.717633, -98.169028 29.717141, -98.169095 29.716993, -98.169112 29.716903, -98.16936 29.716897, -98.169559 29.716868, -98.169905 29.716722, -98.170144 29.716598, -98.170457 29.716416, -98.170582 29.716302, -98.170705 29.7162, -98.171 29.715995, -98.17142 29.715726, -98.172424 29.715066, -98.17296 29.714698, -98.173124 29.714606, -98.173285 29.714527, -98.173472 29.714455, -98.17356 29.714411, -98.173691 29.714319, -98.173754 29.714236, -98.173851 29.714119, -98.173908 29.714044, -98.173957 29.71399, -98.174025 29.713926, -98.174339 29.713721, -98.174439 29.713643, -98.174493 29.713592, -98.17454 29.713525, -98.174595 29.713435, -98.174661 29.713272, -98.174716 29.713107, -98.174774 29.712982, -98.174818 29.712913, -98.174915 29.712795, -98.175013 29.712696, -98.175112 29.71262, -98.175226 29.712574, -98.17536 29.712531, -98.175692 29.712461, -98.175778 29.712434, -98.17585 29.712404, -98.175971 29.712344, -98.176693 29.712849, -98.176796 29.712926, -98.176926 29.712989, -98.177036 29.713036, -98.177206 29.71309, -98.17789 29.713319, -98.181485 29.710225, -98.181773 29.709968, -98.180843 29.709105, -98.178847 29.707255, -98.174978 29.70367, -98.172993 29.701823, -98.172641 29.701553, -98.171251 29.700601, -98.165872 29.697392, -98.165931 29.697328, -98.165865 29.697291, -98.166296 29.696876, -98.1673 29.695864, -98.168035 29.695067, -98.168963 29.694222, -98.169134 29.694047, -98.164957 29.690893, -98.163644 29.689901, -98.163437 29.689798, -98.164039 29.689453, -98.164597 29.689145, -98.16467 29.689096, -98.164719 29.689056, -98.16474 29.689034, -98.164796 29.688977, -98.164799 29.688881, -98.164787 29.688835, -98.164734 29.688699, -98.16468 29.688579, -98.164342 29.688021, -98.16427 29.687834, -98.163559 29.685539, -98.163248 29.684519, -98.163031 29.683853, -98.162808 29.683118, -98.164641 29.68255, -98.164853 29.682363, -98.164936 29.682251, -98.164899 29.682139, -98.164626 29.68136, -98.163489 29.681894, -98.162562 29.682306, -98.162389 29.681755, -98.162237 29.681329, -98.162151 29.681155, -98.162027 29.680964, -98.161847 29.680739, -98.161826 29.680713, -98.16179 29.680674, -98.161739 29.680639, -98.161681 29.680606, -98.16163 29.680589, -98.161572 29.680577, -98.161497 29.680573, -98.161446 29.680578, -98.16138 29.68059, -98.161186 29.680678, -98.161048 29.68076, -98.160896 29.680834, -98.160795 29.680859, -98.160702 29.680865, -98.160629 29.680852, -98.160527 29.680813, -98.159853 29.680483, -98.159736 29.680416, -98.159119 29.680019, -98.158523 29.679671, -98.159281 29.679154, -98.163356 29.676103, -98.16815 29.672569, -98.170488 29.670807, -98.172165 29.669651, -98.172125 29.669527, -98.1721 29.669448, -98.171826 29.668551, -98.171737 29.668281, -98.171722 29.668235, -98.173412 29.667479, -98.173961 29.667232, -98.174372 29.666957, -98.176274 29.66629, -98.17656 29.66619, -98.176675 29.66615, -98.176936 29.666058, -98.176923 29.666026, -98.176901 29.66597, -98.176878 29.665917, -98.176857 29.665869, -98.176838 29.665823, -98.176823 29.665778, -98.17681 29.665734, -98.176799 29.665684, -98.176781 29.665635, -98.176764 29.665588, -98.176745 29.665529, -98.176731 29.665481, -98.176707 29.665427, -98.176687 29.665375, -98.176669 29.665328, -98.17665 29.66528, -98.176633 29.665232, -98.176616 29.665184, -98.1766 29.665134, -98.176583 29.665083, -98.176565 29.665033, -98.17654 29.664967, -98.176525 29.66492, -98.176496 29.664843, -98.176485 29.664794, -98.176468 29.664729, -98.176454 29.664675, -98.176407 29.664622, -98.176395 29.664577, -98.176373 29.664535, -98.176354 29.664475, -98.17633 29.664435, -98.176309 29.664393, -98.17628 29.664355, -98.176228 29.664329, -98.176206 29.664375, -98.176147 29.664411, -98.176101 29.664435, -98.176034 29.66444, -98.175998 29.664443, -98.175986 29.664394, -98.176003 29.66435, -98.176064 29.66434, -98.176113 29.664303, -98.177225 29.663999, -98.177754 29.663838, -98.177837 29.663815, -98.179084 29.66347, -98.17915 29.663449, -98.179887 29.665025, -98.180087 29.665625, -98.180456 29.666279, -98.180817 29.666151, -98.181292 29.665982, -98.18826 29.663509, -98.188228 29.663423, -98.188095 29.663159, -98.18769 29.662305, -98.187647 29.662216, -98.187534 29.661964, -98.18752 29.661917, -98.187513 29.661851, -98.187841 29.661805, -98.189303 29.661565, -98.189474 29.661542, -98.189608 29.661529, -98.189891 29.661502, -98.18994 29.661497, -98.190229 29.661485, -98.190458 29.661471, -98.190752 29.661447, -98.190963 29.661419, -98.191351 29.661332, -98.191739 29.661209, -98.192141 29.661068, -98.19243 29.660974, -98.193119 29.660741, -98.195086 29.660103, -98.195909 29.659836, -98.195313 29.65832, -98.194837 29.658497, -98.19393 29.659095, -98.192655 29.657151, -98.191771 29.655767, -98.189913 29.656725, -98.189883 29.656683, -98.189701 29.656388, -98.187962 29.657655, -98.186652 29.658347, -98.186513 29.658418, -98.185978 29.657296, -98.186096 29.657213, -98.185915 29.656821, -98.185362 29.657201, -98.185294 29.65705, -98.185273 29.657006, -98.185203 29.65686, -98.185176 29.656805, -98.185041 29.656567, -98.185836 29.656004, -98.185584 29.656098, -98.185228 29.656303, -98.184982 29.656458, -98.184627 29.656684, -98.184468 29.656793, -98.184255 29.656932, -98.183705 29.657233, -98.183541 29.657328, -98.182978 29.657653, -98.182882 29.65752, -98.182747 29.657598, -98.1823 29.657825, -98.18191 29.657996, -98.176851 29.660117, -98.176243 29.660377, -98.174917 29.660919, -98.174594 29.661062, -98.174067 29.661276, -98.171585 29.662317, -98.17101 29.662552, -98.170757 29.662669, -98.169829 29.663151, -98.16958 29.662821, -98.169477 29.662665, -98.16941 29.662534, -98.169374 29.662449, -98.169309 29.662263, -98.169287 29.662184, -98.169258 29.662015, -98.169254 29.661989, -98.16925 29.661953, -98.169241 29.661861, -98.169245 29.661764, -98.169273 29.661214, -98.169283 29.661145, -98.169269 29.660821, -98.169248 29.660698, -98.169211 29.660524, -98.169 29.659783, -98.16876 29.659004, -98.168657 29.658689, -98.168637 29.658627, -98.168503 29.658164, -98.168465 29.658056, -98.168429 29.658001, -98.168364 29.657964, -98.168322 29.657951, -98.168282 29.657944, -98.168249 29.657943, -98.168211 29.657949, -98.167969 29.658033, -98.16768 29.65815, -98.167663 29.658157, -98.166943 29.658464, -98.16691 29.658387, -98.166773 29.65844, -98.165254 29.655457, -98.16456 29.655736, -98.1624 29.656606, -98.161123 29.657122, -98.158296 29.658264, -98.158207 29.658102, -98.154541 29.659621, -98.153211 29.660172, -98.153194 29.660138, -98.153125 29.660165, -98.153132 29.660179, -98.153193 29.660308, -98.153247 29.660422, -98.153621 29.661153, -98.153726 29.661346, -98.154187 29.662189, -98.154322 29.662418, -98.154592 29.662912, -98.154815 29.663302, -98.154888 29.663445, -98.154106 29.663762, -98.153324 29.664074, -98.152895 29.664258, -98.152702 29.664337, -98.152004 29.664624, -98.15119 29.664959, -98.150891 29.665088, -98.150608 29.6652, -98.150327 29.665319, -98.150114 29.665436, -98.14996 29.665562, -98.149881 29.665641, -98.149823 29.66571, -98.149508 29.666124, -98.148932 29.666902, -98.148315 29.667733, -98.148142 29.667966, -98.146703 29.66993, -98.146657 29.670001, -98.146409 29.670391, -98.146315 29.670527, -98.146178 29.670758, -98.145949 29.671119, -98.145801 29.671352, -98.145149 29.672367, -98.144885 29.672794, -98.144598 29.673226, -98.144446 29.673472, -98.144287 29.673734, -98.144264 29.673706, -98.144186 29.67338, -98.144166 29.673296, -98.144007 29.673063, -98.143678 29.672861, -98.143475 29.672705, -98.143328 29.672447, -98.143214 29.672093, -98.14321 29.671952, -98.143422 29.671641, -98.141356 29.669841, -98.139872 29.668437, -98.13876 29.667384, -98.136239 29.668471, -98.132438 29.670117, -98.132394 29.669956, -98.13231 29.669535, -98.132292 29.669318, -98.132286 29.669189, -98.132278 29.668658, -98.132318 29.668191, -98.132392 29.667804, -98.130921 29.66752, -98.130387 29.667413, -98.130203 29.667376, -98.129545 29.667237, -98.128892 29.667127, -98.128607 29.667108, -98.128391 29.66711, -98.128084 29.667131, -98.127913 29.667157, -98.129386 29.666525, -98.130546 29.666029, -98.132064 29.66538, -98.132104 29.665363, -98.13213 29.665352, -98.13216 29.665339, -98.133888 29.6646, -98.135549 29.663904, -98.135889 29.663762, -98.14205 29.661187, -98.142125 29.661156, -98.142227 29.661113, -98.142241 29.661107, -98.142277 29.661092, -98.143072 29.66076, -98.14246 29.66011, -98.141569 29.660472, -98.140037 29.661136, -98.139864 29.661204, -98.139789 29.66122, -98.13971 29.661223, -98.139628 29.661216, -98.139541 29.661195, -98.139455 29.661153, -98.139346 29.66107, -98.138882 29.660606, -98.138773 29.660506, -98.138601 29.66033, -98.138402 29.660098, -98.138348 29.660053, -98.138289 29.660036, -98.137802 29.659991, -98.137138 29.659915, -98.135548 29.659711, -98.135416 29.660829, -98.13536 29.661202, -98.135311 29.66147, -98.135244 29.661732, -98.135132 29.66205, -98.134992 29.662412, -98.134862 29.662694, -98.13418 29.664034, -98.1341 29.663921, -98.133524 29.662868, -98.133477 29.662779, -98.133121 29.662107, -98.132216 29.660538, -98.131353 29.659011, -98.130754 29.658005, -98.130555 29.657715, -98.130481 29.657596, -98.130452 29.657539, -98.130437 29.657492, -98.130433 29.657456, -98.130436 29.657432, -98.130441 29.657394, -98.130452 29.65737, -98.130465 29.657343, -98.130483 29.657321, -98.130495 29.657305, -98.130541 29.657262, -98.13094 29.657007, -98.131103 29.656897, -98.131858 29.65642, -98.131902 29.656382, -98.131932 29.656342, -98.131939 29.656315, -98.13194 29.656288, -98.13194 29.656264, -98.131928 29.656223, -98.131868 29.656102, -98.131776 29.655948, -98.131713 29.655842, -98.131559 29.655556, -98.130533 29.655845, -98.130727 29.656657, -98.130089 29.656578, -98.130075 29.656576, -98.127209 29.65665, -98.127126 29.656503, -98.126884 29.656079, -98.124842 29.65242, -98.124571 29.651897, -98.12374 29.652679, -98.12031 29.650249, -98.120181 29.650165, -98.120013 29.650051, -98.115473 29.646839, -98.114289 29.647896, -98.114229 29.647949, -98.11365 29.648465, -98.113221 29.648848, -98.112764 29.649257, -98.112697 29.649317, -98.111599 29.650269, -98.110453 29.651296, -98.108506 29.653044, -98.108294 29.653234, -98.107624 29.653815, -98.107192 29.65419, -98.106119 29.655152, -98.105737 29.655488, -98.105233 29.655932, -98.103103 29.657808, -98.102409 29.658431, -98.101792 29.658975, -98.100956 29.659707, -98.10006 29.660491, -98.099593 29.660893, -98.099395 29.661064, -98.099242 29.6612, -98.099168 29.661265, -98.098939 29.661467, -98.098874 29.661524, -98.098313 29.662016, -98.097292 29.662913, -98.096405 29.663696, -98.095182 29.66478, -98.094885 29.665033, -98.094699 29.66519, -98.094347 29.66549, -98.094314 29.665454, -98.094073 29.665656, -98.091786 29.663409, -98.091472 29.663129, -98.09001 29.661827, -98.090245 29.661616, -98.089971 29.661372, -98.090223 29.661153, -98.090488 29.660923, -98.090773 29.660676, -98.090785 29.660665, -98.093439 29.658324, -98.093837 29.658942, -98.094979 29.659969, -98.098154 29.657155, -98.09648 29.655736, -98.095315 29.65475, -98.094853 29.655161, -98.092328 29.65739, -98.092147 29.657547, -98.091541 29.658094, -98.089734 29.659745, -98.089418 29.660025, -98.08921 29.660209, -98.089092 29.660315, -98.088954 29.66015, -98.08793 29.659133, -98.087105 29.658437, -98.086929 29.658596, -98.086674 29.658817, -98.084693 29.657087, -98.084614 29.657018, -98.083876 29.656374, -98.083838 29.656336, -98.082532 29.655015, -98.082768 29.65485, -98.082392 29.654462, -98.082199 29.654239, -98.082001 29.654, -98.081849 29.653789, -98.081796 29.653716, -98.081617 29.653448, -98.081124 29.652601, -98.079839 29.650302, -98.079034 29.648861, -98.078843 29.648523, -98.078221 29.647423, -98.077949 29.646968, -98.077745 29.646644, -98.077604 29.646462, -98.077463 29.646296, -98.077328 29.646149, -98.077238 29.646051, -98.076557 29.645413, -98.075967 29.644814, -98.075622 29.644427, -98.077754 29.642588, -98.079986 29.640616, -98.080947 29.639775, -98.082076 29.638787, -98.083157 29.63785, -98.083249 29.637747, -98.083281 29.637691, -98.08329 29.63765, -98.08329 29.637594, -98.083279 29.637542, -98.083255 29.637486, -98.08322 29.63743, -98.083206 29.637414, -98.08285 29.637004, -98.082771 29.636941, -98.082708 29.636902, -98.082648 29.636881, -98.082589 29.63687, -98.082525 29.63688, -98.082441 29.636911, -98.082361 29.636962, -98.08093 29.638245, -98.080895 29.638274, -98.080786 29.638366, -98.08071 29.638414, -98.080642 29.638424, -98.080563 29.638423, -98.080511 29.638402, -98.080333 29.638304, -98.08029 29.6383, -98.080238 29.638303, -98.080155 29.638372, -98.08013 29.638393, -98.079613 29.638844, -98.079192 29.639147, -98.078202 29.64002, -98.076599 29.641454, -98.074637 29.643195, -98.074173 29.642539, -98.072274 29.639777, -98.071509 29.638636, -98.071335 29.638349, -98.071131 29.637957, -98.070304 29.636258, -98.070098 29.63583, -98.069419 29.63442, -98.068428 29.63237, -98.065599 29.626566, -98.06526 29.625882, -98.064545 29.624444, -98.064224 29.623798, -98.063997 29.623342, -98.063861 29.623066, -98.063443 29.622219, -98.062802 29.620965, -98.062565 29.620541, -98.062406 29.620282, -98.06212 29.619844, -98.061929 29.619556, -98.06168 29.619193, -98.061413 29.618821, -98.060784 29.617975, -98.060083 29.617097, -98.059542 29.616438, -98.059201 29.616044, -98.057809 29.614495, -98.057474 29.614131, -98.057065 29.613654, -98.056837 29.613376, -98.056354 29.61271, -98.056089 29.612328, -98.056074 29.612305, -98.05583 29.611928, -98.055449 29.611263, -98.054274 29.609118, -98.054071 29.608747, -98.053579 29.607882, -98.053168 29.607159, -98.052766 29.606461, -98.052439 29.605862, -98.052376 29.605708, -98.05235 29.605614, -98.052322 29.605424, -98.052318 29.605307, -98.052332 29.605204, -98.052356 29.605096, -98.052399 29.604932, -98.052478 29.604763, -98.052527 29.604682, -98.052584 29.604609, -98.051992 29.605142, -98.051878 29.605212, -98.051671 29.605387, -98.049428 29.607371, -98.048503 29.608189, -98.048415 29.608267, -98.048358 29.608317, -98.047695 29.608921, -98.047608 29.60898, -98.047443 29.60907, -98.047308 29.609132, -98.04619 29.608083, -98.046171 29.608014, -98.046209 29.607869, -98.046249 29.607801, -98.046341 29.60743, -98.046379 29.607203, -98.046345 29.607044, -98.046327 29.606789, -98.046261 29.60663, -98.046243 29.606431, -98.046378 29.606312, -98.046521 29.606265, -98.046743 29.606198, -98.046814 29.60609, -98.047026 29.60571, -98.046966 29.605737, -98.046902 29.605743, -98.046846 29.60573, -98.046797 29.605714, -98.046589 29.605533, -98.04588 29.604915, -98.045643 29.604713, -98.045586 29.604679, -98.04545 29.604613, -98.045401 29.604585, -98.044911 29.604132, -98.044812 29.604043, -98.044173 29.603467, -98.044149 29.603411, -98.044111 29.60313, -98.044056 29.60273, -98.044028 29.60253, -98.044017 29.602446, -98.043943 29.6021, -98.043933 29.601989, -98.043954 29.601653, -98.043726 29.600693, -98.04365 29.600158, -98.043491 29.599248, -98.04351 29.599174, -98.04353 29.599116, -98.043604 29.599012, -98.043735 29.598895, -98.044719 29.597931, -98.045552 29.597137, -98.045741 29.596944, -98.04579 29.596871, -98.045801 29.596816, -98.045758 29.596692, -98.045725 29.596633, -98.045293 29.596062, -98.045267 29.595991, -98.045261 29.595932, -98.045268 29.595862, -98.045306 29.5958, -98.045377 29.595733, -98.045506 29.595628, -98.04579 29.595412, -98.046301 29.594982, -98.046426 29.594856, -98.046292 29.594783, -98.04606 29.594671, -98.045886 29.594594, -98.045667 29.594511, -98.045244 29.594367, -98.043401 29.593763, -98.042239 29.593381, -98.04146 29.593126, -98.040236 29.592758, -98.039942 29.59266, -98.039824 29.592612, -98.039731 29.592567, -98.039627 29.592508, -98.039597 29.592488, -98.039533 29.592446, -98.039456 29.59238, -98.039363 29.592292, -98.039332 29.592257, -98.041143 29.591603, -98.041407 29.591513, -98.042924 29.59102, -98.044275 29.590681, -98.044683 29.590584, -98.045645 29.590407, -98.048996 29.590084, -98.048441 29.590003, -98.047994 29.589853, -98.048758 29.589798, -98.048823 29.589779, -98.048856 29.589742, -98.048885 29.589694, -98.048952 29.589517, -98.048566 29.589463, -98.048106 29.589391, -98.047067 29.58919, -98.045872 29.58896, -98.043457 29.588488, -98.043208 29.588439, -98.043468 29.588139, -98.043537 29.588071, -98.044639 29.587088, -98.043509 29.586051, -98.042214 29.584863, -98.042232 29.584801, -98.042256 29.58471, -98.04687 29.58136, -98.046363 29.580816, -98.046117 29.580536, -98.045973 29.580425, -98.045753 29.580299, -98.04569 29.5802, -98.045608 29.58004, -98.045564 29.57998, -98.045004 29.579556, -98.044815 29.579441, -98.044677 29.579402, -98.044557 29.579413, -98.044217 29.579485, -98.04389 29.579518, -98.04372 29.579551, -98.043607 29.579583, -98.043267 29.579743, -98.043002 29.57993, -98.042581 29.580282, -98.041787 29.580992, -98.041491 29.581225, -98.041388 29.581306, -98.041089 29.581541, -98.040447 29.581937, -98.040264 29.582009, -98.040176 29.582025, -98.040119 29.582031, -98.040063 29.582025, -98.039993 29.581992, -98.039874 29.58191, -98.039534 29.581541, -98.039352 29.581365, -98.039295 29.581321, -98.039251 29.581305, -98.039131 29.581283, -98.039031 29.581288, -98.038902 29.581319, -98.038867 29.581327, -98.037445 29.581733, -98.037193 29.581772, -98.037013 29.581758, -98.036979 29.581755, -98.036903 29.581733, -98.036715 29.581651, -98.036568 29.581602, -98.036431 29.581557, -98.036293 29.581722, -98.036142 29.581871, -98.035644 29.582239, -98.034933 29.582685, -98.03487 29.582728, -98.034832 29.582772, -98.033139 29.583839, -98.033064 29.5839, -98.032969 29.584026, -98.032849 29.58434, -98.032837 29.584659, -98.032887 29.584984, -98.032956 29.585237, -98.032975 29.585358, -98.033214 29.586012, -98.033321 29.58643, -98.03344 29.586788, -98.033522 29.587003, -98.033572 29.587107, -98.033635 29.587206, -98.033962 29.587531, -98.034202 29.587751, -98.034705 29.588279, -98.035359 29.588939, -98.035548 29.589077, -98.035624 29.589143, -98.035693 29.589187, -98.035781 29.589265, -98.035793 29.589275, -98.035819 29.589314, -98.035825 29.589457, -98.035957 29.589611, -98.036077 29.589704, -98.036152 29.589814, -98.036321 29.590001, -98.036191 29.590097, -98.036056 29.590196, -98.034819 29.591108, -98.034737 29.591168, -98.033493 29.592083, -98.033648 29.592416, -98.03366 29.59292, -98.033499 29.59342, -98.033334 29.593582, -98.033024 29.59382, -98.033309 29.593807, -98.033691 29.593758, -98.034396 29.593639, -98.034597 29.593585, -98.03475 29.593522, -98.034897 29.593438, -98.035032 29.593342, -98.035872 29.592656, -98.036168 29.592968, -98.036274 29.593097, -98.036356 29.593209, -98.036409 29.593303, -98.036478 29.593511, -98.036561 29.5938, -98.036683 29.594322, -98.037017 29.595737, -98.03697 29.595758, -98.037059 29.596165, -98.037103 29.596369, -98.037154 29.5966, -98.037176 29.596891, -98.036684 29.597117, -98.03616 29.59737, -98.035561 29.597576, -98.034167 29.597806, -98.03204 29.597817, -98.031905 29.597829, -98.031879 29.597831, -98.031803 29.597832, -98.031499 29.597849, -98.028969 29.597801, -98.026773 29.597787, -98.023767 29.597679, -98.023766 29.598591, -98.023772 29.59922, -98.023793 29.600224, -98.02381 29.601044, -98.023822 29.602049, -98.023892 29.605585, -98.023899 29.606076, -98.023924 29.607091, -98.023912 29.607211, -98.023896 29.607278, -98.023877 29.607315, -98.023842 29.607349, -98.023797 29.607374, -98.023726 29.607405, -98.024089 29.607397, -98.024061 29.607532, -98.024703 29.607529, -98.024826 29.607536, -98.024948 29.607552, -98.025046 29.607572, -98.025122 29.607596, -98.025211 29.607636, -98.025326 29.607701, -98.025628 29.607895, -98.025683 29.607946, -98.025729 29.607995, -98.025791 29.608085, -98.025835 29.608187, -98.025871 29.6083, -98.025931 29.608574, -98.025961 29.608747, -98.025969 29.608839, -98.025968 29.60894, -98.02596 29.609038, -98.025937 29.609147, -98.025877 29.609347, -98.025462 29.610407, -98.025449 29.61046, -98.025437 29.610579, -98.025392 29.611029, -98.025368 29.611271, -98.025298 29.611832, -98.025276 29.612219, -98.025282 29.612513, -98.025291 29.61254, -98.02532 29.612546, -98.025793 29.612524, -98.026016 29.612522, -98.026373 29.612556, -98.026366 29.61343, -98.026364 29.613719, -98.027458 29.613709, -98.027679 29.613707, -98.027717 29.614017, -98.027796 29.614659, -98.027219 29.617995, -98.027194 29.618923, -98.027169 29.619375, -98.027169 29.619754, -98.026951 29.619854, -98.026976 29.620156, -98.027064 29.620327, -98.027077 29.620475, -98.027087 29.620901, -98.027407 29.620325, -98.027393 29.62009, -98.027426 29.620292, -98.02772 29.619764, -98.027732 29.620118, -98.027726 29.620272, -98.027757 29.62041, -98.02777 29.620575, -98.02787 29.620938, -98.027908 29.621108, -98.028065 29.621345, -98.028147 29.621438, -98.028311 29.621587, -98.028688 29.62189, -98.028871 29.621989, -98.02916 29.62211, -98.029557 29.622319, -98.029922 29.622446, -98.031326 29.622616, -98.031509 29.622622, -98.031937 29.622611, -98.032082 29.622584, -98.032365 29.622485, -98.033014 29.622303, -98.03341 29.622155, -98.033495 29.622129, -98.03349 29.622417, -98.033489 29.622466, -98.033483 29.622848, -98.033479 29.623122, -98.033474 29.623278, -98.033499 29.623326, -98.033528 29.623358, -98.033575 29.623387, -98.033633 29.623403, -98.033709 29.62341, -98.033786 29.623411, -98.033979 29.62339, -98.0341 29.623369, -98.034462 29.623212, -98.034678 29.623096, -98.035011 29.62298, -98.035424 29.622865, -98.0355 29.62285, -98.035523 29.622843, -98.035727 29.622778, -98.03617 29.622575, -98.036537 29.623169, -98.036864 29.623706, -98.037211 29.62427, -98.03754 29.624789, -98.037766 29.625159, -98.037518 29.62526, -98.037306 29.625338, -98.037083 29.625429, -98.036878 29.625532, -98.036684 29.625649, -98.036545 29.625766, -98.036496 29.625807, -98.036426 29.625892, -98.036386 29.625956, -98.036363 29.625999, -98.036006 29.62667, -98.035941 29.626775, -98.035739 29.627176, -98.03565 29.627365, -98.035595 29.627513, -98.035394 29.628249, -98.035355 29.628374, -98.035283 29.628519, -98.035198 29.628625, -98.034863 29.628913, -98.032583 29.630937, -98.032441 29.631063, -98.03118 29.632249, -98.029605 29.633693, -98.031243 29.635427, -98.032403 29.636655, -98.032459 29.636713, -98.032959 29.637235, -98.033741 29.638065, -98.03427 29.63862, -98.034641 29.639029, -98.034949 29.639384, -98.035478 29.640063, -98.035911 29.640666, -98.036212 29.641161, -98.036581 29.641774, -98.036804 29.642152, -98.037517 29.643363, -98.038611 29.64524, -98.039287 29.646398, -98.039698 29.647066, -98.039724 29.647107, -98.040085 29.647656, -98.039883 29.647757, -98.040169 29.648211, -98.040711 29.649037, -98.040755 29.649105, -98.040816 29.649184, -98.042105 29.650957, -98.043143 29.652649, -98.045409 29.655844, -98.045949 29.656647, -98.046022 29.656753, -98.047028 29.658205, -98.047331 29.658687, -98.047756 29.659489, -98.048044 29.65982, -98.048431 29.660341, -98.048828 29.660874, -98.048936 29.66102, -98.049621 29.662175, -98.049673 29.662266, -98.049712 29.662314, -98.049018 29.662751, -98.046834 29.664436, -98.046354 29.664734, -98.04605 29.664994, -98.045687 29.665304, -98.045534 29.665435, -98.044803 29.666059, -98.045554 29.667498, -98.04576 29.667873, -98.044012 29.669345, -98.042674 29.670479, -98.042654 29.670497, -98.042228 29.670857, -98.041077 29.66921, -98.041065 29.669202, -98.03874 29.667063, -98.035718 29.669627, -98.034418 29.67073, -98.032258 29.672568, -98.031626 29.673111, -98.031539 29.673163, -98.031523 29.673169, -98.031607 29.673374, -98.035432 29.676984, -98.035781 29.676697, -98.035858 29.676635, -98.03994 29.673292, -98.040624 29.672731, -98.042327 29.671337, -98.042736 29.671003, -98.043764 29.672756, -98.043626 29.672885, -98.043823 29.673171, -98.042544 29.674244, -98.042892 29.674755, -98.042936 29.674808, -98.042982 29.674862, -98.043459 29.675558, -98.043888 29.676183, -98.044741 29.677436, -98.045292 29.678237, -98.045595 29.677982, -98.048231 29.675766, -98.04935 29.674806, -98.049709 29.674504, -98.049781 29.674444, -98.050047 29.67422, -98.05065 29.673721, -98.051964 29.672611, -98.05355 29.67128, -98.054238 29.670696, -98.054917 29.67013, -98.055158 29.669972, -98.055333 29.670234, -98.055502 29.670487, -98.056519 29.67198, -98.056992 29.672674, -98.05763 29.67357, -98.057993 29.674062, -98.058666 29.674984, -98.05911 29.675575, -98.059812 29.676513, -98.059862 29.676581, -98.059873 29.676595, -98.06008 29.676878, -98.060434 29.677363, -98.060551 29.677523, -98.060614 29.67761, -98.060799 29.677864, -98.060861 29.677948, -98.062103 29.679637, -98.06223 29.679818, -98.063452 29.681446, -98.066534 29.685645, -98.067034 29.686318, -98.067216 29.686539, -98.067452 29.686797, -98.067679 29.687042, -98.068033 29.687357, -98.068268 29.687556, -98.068571 29.687796, -98.068747 29.687935, -98.069144 29.688199, -98.069541 29.688431, -98.069839 29.688589, -98.070056 29.688695, -98.070005 29.688788, -98.069803 29.689094, -98.069612 29.689334, -98.069455 29.689524, -98.069281 29.689698, -98.069221 29.689759, -98.068268 29.690605, -98.067335 29.6914, -98.065515 29.692982, -98.064798 29.693605, -98.064725 29.693648, -98.064661 29.693671, -98.064588 29.693686, -98.064447 29.693682, -98.064375 29.693665, -98.064302 29.693629, -98.064194 29.693565, -98.063987 29.693389, -98.062084 29.691679, -98.061794 29.691429, -98.061729 29.691384, -98.061664 29.691354, -98.061585 29.691329, -98.061489 29.691313, -98.061387 29.691305, -98.06129 29.691312, -98.061197 29.691333, -98.061111 29.691365, -98.06104 29.691399, -98.060972 29.691443, -98.060886 29.691512, -98.06008 29.692215, -98.059488 29.692723, -98.059051 29.693106, -98.057951 29.694027, -98.058187 29.694222, -98.064235 29.699704, -98.067555 29.696841, -98.068174 29.697374, -98.068682 29.697812, -98.068791 29.698006, -98.069785 29.699024, -98.069916 29.698921, -98.070622 29.699532, -98.070662 29.699568, -98.070413 29.699761, -98.070437 29.699855, -98.070268 29.700003, -98.068345 29.701646, -98.068006 29.701932, -98.067933 29.701999, -98.067898 29.701963, -98.067149 29.702618, -98.06682 29.702887, -98.065722 29.703805, -98.063867 29.705394, -98.063085 29.706069, -98.060169 29.708554, -98.059893 29.7088, -98.059797 29.708913, -98.060952 29.709977, -98.06178 29.710744, -98.062444 29.71136, -98.06285 29.711743, -98.065548 29.709482, -98.065773 29.709301, -98.065806 29.709268, -98.065937 29.709157, -98.066689 29.708521, -98.067208 29.708081, -98.067446 29.708255, -98.070203 29.710707, -98.070467 29.710942, -98.072293 29.712566, -98.07349 29.71362, -98.073394 29.7137, -98.072547 29.714414, -98.071869 29.714988, -98.071413 29.715373, -98.07135 29.715427, -98.071212 29.715545, -98.070424 29.716217, -98.070385 29.71625, -98.069924 29.716643, -98.069595 29.716924, -98.06914 29.717313, -98.068045 29.718233, -98.066206 29.719779, -98.065879 29.720066, -98.065407 29.720476, -98.064925 29.720895, -98.06402 29.721683, -98.063679 29.72198, -98.063001 29.722565, -98.062781 29.722755, -98.062415 29.723071, -98.060464 29.724755, -98.059563 29.725523, -98.059121 29.725902, -98.058562 29.726366, -98.058284 29.726596, -98.057125 29.727556, -98.057051 29.72762, -98.05672 29.727908, -98.056542 29.728079, -98.056397 29.728235, -98.056252 29.728399, -98.056082 29.728602, -98.05541 29.72946, -98.053725 29.731582, -98.052138 29.733562, -98.051767 29.734024, -98.051572 29.734259, -98.051257 29.734608, -98.051038 29.734825, -98.050843 29.73501, -98.049728 29.735955, -98.049537 29.736113, -98.04864 29.736856, -98.048442 29.737008, -98.048248 29.73715, -98.048111 29.737244, -98.050766 29.739635, -98.051778 29.740543, -98.053654 29.742228, -98.056251 29.744547, -98.056623 29.744894, -98.057184 29.745392, -98.057329 29.745511, -98.057415 29.745582, -98.057652 29.745754, -98.058091 29.745997, -98.057965 29.746191, -98.057817 29.746443, -98.05765 29.746699, -98.057829 29.746796, -98.058011 29.746898, -98.056424 29.749124, -98.05536 29.750601, -98.055341 29.750629, -98.055284 29.750704, -98.055376 29.750612, -98.05557 29.750742, -98.055649 29.750636, -98.055831 29.750766, -98.056428 29.75119, -98.056702 29.750992, -98.058451 29.748551, -98.058612 29.748343, -98.058667 29.748287, -98.058788 29.748378, -98.058934 29.748499, -98.058957 29.74852, -98.059208 29.748745, -98.060081 29.74951, -98.060357 29.749758, -98.060394 29.749791, -98.060861 29.750188, -98.060955 29.750268, -98.06203 29.751219, -98.063137 29.75217, -98.063216 29.752239, -98.0633 29.752312, -98.064284 29.753172, -98.06587 29.754544, -98.066009 29.754668, -98.066795 29.755369, -98.066572 29.755579, -98.065352 29.756613, -98.064697 29.75717, -98.064646 29.757222, -98.064617 29.757269, -98.064598 29.75731, -98.064668 29.757419, -98.064768 29.757514, -98.065012 29.757722, -98.065877 29.758463, -98.066146 29.758683, -98.066337 29.758839, -98.067252 29.75962, -98.067951 29.760216, -98.068224 29.760449, -98.069211 29.761283, -98.069494 29.761529, -98.069744 29.761737, -98.069915 29.76189, -98.070216 29.762158, -98.070494 29.762419, -98.07147 29.763307, -98.071732 29.763551, -98.072509 29.763132, -98.080721 29.758701, -98.0813 29.757336, -98.08305 29.752524, -98.082948 29.752432, -98.08258 29.752101, -98.082075 29.751655, -98.07843 29.748476, -98.078098 29.748188, -98.076636 29.74692, -98.077405 29.746265, -98.07773 29.74598, -98.07811 29.745647, -98.078602 29.745196, -98.078918 29.744926, -98.079233 29.744645, -98.080931 29.743158, -98.081774 29.74243, -98.082616 29.741676, -98.082761 29.741557, -98.084141 29.740318, -98.084197 29.740271, -98.084291 29.740191, -98.086167 29.738495, -98.087733 29.737064, -98.088255 29.737504, -98.088406 29.737623, -98.089411 29.738421, -98.090001 29.738835, -98.090302 29.739036, -98.091536 29.739874, -98.092098 29.740272, -98.092506 29.74055, -98.092728 29.740694, -98.093048 29.740912, -98.093214 29.741025, -98.093362 29.741125, -98.094715 29.742042, -98.097472 29.743952, -98.097758 29.744167, -98.098377 29.744567, -98.0989 29.74492, -98.099122 29.745083, -98.099571 29.745452, -98.099772 29.745652, -98.099885 29.745774, -98.100127 29.746057, -98.100199 29.746141, -98.100344 29.746346, -98.102137 29.744926, -98.103068 29.744347, -98.103787 29.74396, -98.104274 29.743747, -98.10474 29.743592, -98.105213 29.743453, -98.105729 29.743334, -98.106412 29.743206, -98.107211 29.743076, -98.107306 29.743061, -98.107432 29.74304, -98.10756 29.743005, -98.108236 29.742822, -98.109036 29.742617, -98.109098 29.742601, -98.109101 29.742827, -98.109105 29.743082, -98.109108 29.743193, -98.109132 29.743289, -98.109301 29.743625, -98.109447 29.743864, -98.109543 29.744267, -98.109586 29.744343, -98.109774 29.744619, -98.109853 29.744708, -98.109977 29.744811, -98.110053 29.744856, -98.110187 29.744902, -98.110286 29.744915, -98.110417 29.744922, -98.110545 29.744917, -98.110665 29.744905, -98.110797 29.74488, -98.111379 29.744706, -98.111668 29.744602, -98.112315 29.744408, -98.113604 29.744016, -98.113776 29.743957, -98.113988 29.743869, -98.114717 29.743552, -98.115779 29.743088, -98.116018 29.742959, -98.116187 29.742855, -98.11697 29.742315, -98.118986 29.740944, -98.120961 29.739593, -98.123061 29.738135, -98.124925 29.736853, -98.126109 29.736064, -98.126571 29.735723, -98.126283 29.735389, -98.125883 29.734941, -98.125568 29.73457, -98.12553 29.734535, -98.125484 29.734504, -98.125424 29.734491, -98.12536 29.734495, -98.125297 29.734507, -98.125235 29.734532, -98.125096 29.734627, -98.124688 29.734927, -98.123756 29.735547, -98.121887 29.736824, -98.11978 29.738266, -98.117797 29.739612, -98.115793 29.740987, -98.114204 29.742071, -98.114087 29.742143, -98.113944 29.742213, -98.113867 29.742253, -98.113702 29.742322, -98.111771 29.743042, -98.111426 29.74217, -98.111341 29.741902, -98.111311 29.741837, -98.111792 29.741545, -98.112247 29.741237, -98.11258 29.740924, -98.112935 29.740533, -98.113118 29.740333, -98.113324 29.740056, -98.113521 29.739737, -98.113691 29.739425, -98.113836 29.739092, -98.11399 29.738654, -98.114186 29.737972, -98.114982 29.735053, -98.115384 29.733535, -98.115945 29.73175, -98.116044 29.731664, -98.116102 29.731609, -98.11643 29.731298, -98.116543 29.731191, -98.117018 29.73076, -98.117084 29.730693, -98.117325 29.730475, -98.117473 29.730334, -98.117894 29.729967, -98.118132 29.729747, -98.118244 29.72963, -98.118325 29.729534, -98.118377 29.729488, -98.118739 29.729598, -98.118995 29.72966, -98.119214 29.729707, -98.119447 29.729745, -98.119696 29.729777, -98.119892 29.729795, -98.120092 29.729803, -98.120617 29.729803, -98.120711 29.729796, -98.121019 29.729774, -98.121082 29.729769, -98.121911 29.729654, -98.12221 29.729608, -98.123221 29.729452, -98.124758 29.729207, -98.125487 29.729101, -98.126809 29.728898, -98.12823 29.728672, -98.13006 29.728389, -98.13051 29.728315, -98.134516 29.727692, -98.135142 29.727584, -98.136252 29.727327, -98.13759 29.726931, -98.138136 29.726733, -98.138494 29.72661, -98.13942 29.726282, -98.140464 29.72589, -98.141047 29.725656, -98.1416 29.725406, -98.14242 29.725007, -98.143029 29.724663, -98.143505 29.724393, -98.144091 29.724002, -98.145644 29.723008, -98.146827 29.722242, -98.148674 29.721091, -98.149407 29.720613, -98.152628 29.718556, -98.154083 29.717619, -98.15474 29.718777, -98.155142 29.718508, -98.155478 29.718256, -98.155871 29.717926, -98.157701 29.720033, -98.158547 29.721006, -98.162173 29.725179, -98.164395 29.727728, -98.167375 29.731166, -98.169513 29.729782, -98.171915 29.728263, -98.172768 29.727716, -98.173044 29.727539, -98.174071 29.726883, -98.17491 29.727512, -98.176032 29.728359, -98.177289 29.729313, -98.177996 29.729839, -98.177263 29.731334, -98.172486 29.734086, -98.170854 29.735027, -98.171059 29.735245, -98.171931 29.736167, -98.174317 29.738672, -98.174956 29.738235, -98.175642 29.737758, -98.177944 29.736156, -98.178468 29.735789, -98.179474 29.735083, -98.17976 29.734882, -98.181315 29.733818, -98.18137 29.73377, -98.181492 29.733687, -98.185488 29.730991, -98.185774 29.731339, -98.18582 29.731308, -98.186035 29.731161, -98.186381 29.730923, -98.186663 29.730729, -98.186799 29.730636, -98.186911 29.730559, -98.187507 29.730149, -98.187999 29.729807, -98.188223 29.729654, -98.18844 29.729505, -98.188509 29.729457, -98.188961 29.729147, -98.189578 29.729614, -98.18989 29.729856, -98.192494 29.730582, -98.192915 29.730906, -98.193692 29.73151, -98.193928 29.731687, -98.19413 29.731853, -98.194279 29.731966, -98.195011 29.732517, -98.195485 29.732893, -98.195844 29.733166, -98.1985 29.735213, -98.198599 29.735288, -98.199491 29.735961, -98.199745 29.736142, -98.200149 29.736393, -98.200461 29.736577, -98.200769 29.73675, -98.202201 29.737572, -98.202769 29.737894, -98.203303 29.738198, -98.203368 29.738237, -98.203584 29.738368, -98.203704 29.738434, -98.203751 29.73846, -98.20472 29.738994, -98.205054 29.739173, -98.205451 29.739343, -98.205862 29.739504, -98.206367 29.739687, -98.206588 29.739764, -98.206708 29.739806, -98.207666 29.740141, -98.208034 29.740274, -98.209198 29.740686, -98.209909 29.740938, -98.214711 29.737812, -98.214974 29.737638, -98.214091 29.736662, -98.211919 29.734269, -98.211721 29.734051, -98.211036 29.734602, -98.210795 29.73482, -98.210563 29.73503, -98.210298 29.735216, -98.209916 29.735487, -98.208191 29.7367, -98.208143 29.736672, -98.206184 29.734436, -98.204504 29.732521, -98.204302 29.732291, -98.202371 29.730089, -98.200988 29.731017, -98.200607 29.73056, -98.200198 29.730845, -98.200097 29.730915, -98.199844 29.730596, -98.199378 29.730017, -98.199135 29.729716, -98.198525 29.728958, -98.198475 29.728896, -98.198497 29.72888, -98.198116 29.728403, -98.197817 29.728037, -98.197678 29.727865, -98.197606 29.727775, -98.197467 29.727597, -98.19721 29.727269, -98.197055 29.727071, -98.196878 29.726845, -98.196851 29.72681, -98.196646 29.726541, -98.196625 29.726514, -98.196614 29.7265, -98.195132 29.724951, -98.195074 29.724899, -98.194833 29.724908, -98.194732 29.724919, -98.194661 29.724926, -98.194544 29.724956, -98.194488 29.724988, -98.19437 29.725065, -98.194302 29.725109, -98.193793 29.725447, -98.193163 29.725889, -98.192354 29.726438, -98.192019 29.726664, -98.191864 29.726774, -98.191364 29.727116, -98.191282 29.727181, -98.191199 29.727275, -98.191107 29.727408, -98.19099 29.727628, -98.190895 29.727818, -98.190818 29.727959, -98.190306 29.728891)), ((-98.211887 29.639886, -98.211941 29.639859, -98.213477 29.639079, -98.214038 29.638787, -98.214631 29.638492, -98.215863 29.637861, -98.217284 29.637145, -98.217696 29.636929, -98.218526 29.636508, -98.218904 29.636311, -98.220893 29.635294, -98.221335 29.635077, -98.22148 29.635002, -98.222359 29.63455, -98.222557 29.634253, -98.222002 29.634536, -98.221395 29.634848, -98.220514 29.635301, -98.217669 29.636757, -98.216302 29.637448, -98.216153 29.637523, -98.215625 29.637794, -98.214553 29.638336, -98.214198 29.638518, -98.213849 29.638698, -98.213187 29.639031, -98.21313 29.63906, -98.211828 29.63973, -98.211786 29.639752, -98.211627 29.639833, -98.211411 29.63994, -98.210895 29.640198, -98.209153 29.641091, -98.208289 29.641557, -98.205813 29.643066, -98.205908 29.642965, -98.205982 29.642901, -98.206033 29.642868, -98.206107 29.642813, -98.206361 29.642641, -98.207176 29.642111, -98.207559 29.641833, -98.207839 29.641611, -98.20796 29.6415, -98.208003 29.641447, -98.208064 29.641356, -98.207259 29.641849, -98.206931 29.642049, -98.206313 29.642432, -98.205047 29.643217, -98.204571 29.643518, -98.203674 29.644102, -98.203741 29.644349, -98.197896 29.647968, -98.197636 29.64813, -98.19729 29.648345, -98.196784 29.648659, -98.196882 29.648794, -98.197297 29.64854, -98.197458 29.648442, -98.19771 29.648288, -98.198073 29.648067, -98.198723 29.647668, -98.200396 29.646626, -98.200942 29.646296, -98.200957 29.646283, -98.202847 29.645112, -98.203792 29.644534, -98.204043 29.644381, -98.205503 29.64346, -98.20732 29.642352, -98.207704 29.642109, -98.208527 29.641619, -98.208769 29.641483, -98.209267 29.641221, -98.211014 29.640329, -98.211508 29.640078, -98.211887 29.639886)), ((-98.777845 29.421386, -98.778215 29.42138, -98.778394 29.421377, -98.779709 29.421348, -98.782623 29.421309, -98.785858 29.421247, -98.786237 29.421244, -98.786476 29.421257, -98.786594 29.421269, -98.786749 29.421284, -98.78702 29.421321, -98.787289 29.421369, -98.787555 29.421428, -98.787818 29.421498, -98.788076 29.421578, -98.788334 29.421673, -98.788554 29.421761, -98.788795 29.421876, -98.788995 29.421982, -98.789456 29.422255, -98.789757 29.422457, -98.790898 29.423368, -98.791066 29.423499, -98.791066 29.423435, -98.789747 29.422409, -98.789489 29.422231, -98.789 29.421941, -98.788554 29.421716, -98.788414 29.421656, -98.788096 29.421544, -98.787732 29.421438, -98.787587 29.421396, -98.787254 29.421318, -98.786756 29.42123, -98.786592 29.421211, -98.786489 29.421199, -98.786176 29.421176, -98.785967 29.421173, -98.782622 29.421236, -98.779709 29.421315, -98.778949 29.421329, -98.778216 29.421342, -98.777845 29.421348, -98.777845 29.421386)), ((-98.131978 29.572808, -98.13287 29.572674, -98.13312 29.572629, -98.134157 29.572428, -98.135124 29.572228, -98.135631 29.572131, -98.136799 29.571929, -98.138538 29.57164, -98.138588 29.571913, -98.140319 29.571639, -98.142055 29.571343, -98.141995 29.571057, -98.142395 29.570987, -98.143713 29.57077, -98.143868 29.570743, -98.144023 29.57072, -98.144279 29.570682, -98.144537 29.570652, -98.145535 29.570556, -98.144911 29.567623, -98.143142 29.567923, -98.142132 29.56368, -98.140933 29.563858, -98.140859 29.563206, -98.140264 29.56202, -98.140184 29.562085, -98.139624 29.561032, -98.137012 29.562247, -98.138165 29.563991, -98.138222 29.564078, -98.13702 29.56451, -98.137279 29.565091, -98.138144 29.564759, -98.138485 29.566486, -98.138896 29.568429, -98.134253 29.569193, -98.134436 29.570255, -98.13348 29.570435, -98.133706 29.57149, -98.131381 29.571896, -98.130993 29.571965, -98.131431 29.572615, -98.131503 29.572714, -98.131547 29.57281, -98.131567 29.57287, -98.131978 29.572808)), ((-98.100344 29.746346, -98.099791 29.746753, -98.099492 29.746974, -98.099564 29.747018, -98.099842 29.747639, -98.1012 29.750682, -98.102367 29.753266, -98.102866 29.754715, -98.102914 29.754921, -98.103144 29.755886, -98.103288 29.757275, -98.103473 29.759218, -98.103487 29.759377, -98.103636 29.761055, -98.103658 29.761304, -98.103672 29.76145, -98.10368 29.761538, -98.103736 29.761547, -98.103859 29.761589, -98.103907 29.761618, -98.103942 29.761666, -98.103986 29.761839, -98.104017 29.76203, -98.104015 29.762183, -98.104008 29.762256, -98.103982 29.762334, -98.103908 29.762531, -98.103812 29.762906, -98.103755 29.762309, -98.103707 29.762304, -98.102749 29.762348, -98.102492 29.762313, -98.102155 29.762247, -98.101818 29.762155, -98.101821 29.762395, -98.101816 29.762624, -98.101818 29.762836, -98.10192 29.763218, -98.10193 29.763339, -98.101931 29.763407, -98.101902 29.763624, -98.101761 29.764347, -98.100874 29.76421, -98.100553 29.764176, -98.100409 29.764157, -98.100032 29.764157, -98.099454 29.764213, -98.099266 29.764249, -98.099043 29.764317, -98.098906 29.764379, -98.098834 29.764428, -98.098391 29.76483, -98.100329 29.766476, -98.101374 29.767329, -98.101925 29.767798, -98.102009 29.767827, -98.102059 29.767828, -98.10212 29.767814, -98.102788 29.767572, -98.103297 29.767396, -98.103385 29.767348, -98.103437 29.767295, -98.103452 29.767231, -98.103464 29.767063, -98.103471 29.766838, -98.103481 29.766162, -98.103502 29.765731, -98.103964 29.765734, -98.10422 29.765736, -98.104502 29.765737, -98.105096 29.765742, -98.105102 29.765399, -98.105088 29.764618, -98.105078 29.764478, -98.105033 29.763872, -98.104694 29.760352, -98.104685 29.760255, -98.104626 29.759691, -98.104609 29.759475, -98.104496 29.758281, -98.104395 29.757237, -98.104341 29.756744, -98.104283 29.756028, -98.104259 29.755812, -98.104243 29.755615, -98.104193 29.755274, -98.10417 29.755117, -98.104129 29.7549, -98.104064 29.75461, -98.103904 29.754057, -98.1038 29.753773, -98.103655 29.753415, -98.102922 29.751817, -98.102138 29.750082, -98.101331 29.748377, -98.101143 29.747938, -98.100836 29.747215, -98.100653 29.746852, -98.100491 29.74656, -98.100473 29.746529, -98.100344 29.746346)), ((-98.791066 29.423499, -98.791079 29.428839, -98.791081 29.429258, -98.791157 29.429372, -98.790996 29.429995, -98.791008 29.430105, -98.791165 29.431538, -98.791224 29.431991, -98.791197 29.434169, -98.790909 29.434742, -98.790935 29.435521, -98.790542 29.435819, -98.79049 29.435957, -98.790646 29.437997, -98.790908 29.4388, -98.791301 29.439625, -98.792479 29.440496, -98.793134 29.441207, -98.793265 29.44162, -98.793452 29.441842, -98.793788 29.442239, -98.802038 29.447513, -98.802615 29.447559, -98.802824 29.44694, -98.803048 29.44623, -98.804095 29.442929, -98.804344 29.442148, -98.805052 29.43935, -98.805036 29.435975, -98.805018 29.432618, -98.805002 29.429509, -98.804659 29.429442, -98.802534 29.429013, -98.800781 29.428659, -98.799962 29.428456, -98.799887 29.428437, -98.799798 29.428415, -98.799782 29.428451, -98.799698 29.428431, -98.799096 29.428255, -98.798083 29.427902, -98.797519 29.427675, -98.797191 29.427522, -98.79678 29.427346, -98.796349 29.427143, -98.795988 29.426958, -98.795526 29.426707, -98.795106 29.426465, -98.794766 29.426253, -98.794741 29.426237, -98.794355 29.425989, -98.793573 29.425443, -98.792457 29.424581, -98.791066 29.423499)), ((-98.66548 29.719753, -98.665671 29.719925, -98.666043 29.72026, -98.666683 29.720836, -98.667014 29.721135, -98.667785 29.721781, -98.667948 29.721785, -98.668134 29.721808, -98.668279 29.721909, -98.668443 29.722045, -98.668662 29.722238, -98.669469 29.722842, -98.66578 29.719511, -98.66548 29.719753)), ((-98.726475 29.376523, -98.726489 29.376797, -98.729517 29.376405, -98.732113 29.376068, -98.730847 29.375946, -98.729505 29.376123, -98.726475 29.376523)), ((-98.645329 29.29018, -98.644773 29.290312, -98.644333 29.290461, -98.643498 29.290847, -98.643406 29.290889, -98.643613 29.290923, -98.64457 29.290471, -98.645329 29.29018)), ((-98.194644 29.649986, -98.194831 29.649871, -98.195176 29.649655, -98.196214 29.649013, -98.196784 29.648659, -98.196641 29.648456, -98.195945 29.648885, -98.195885 29.648923, -98.195408 29.649222, -98.195016 29.649461, -98.194566 29.649737, -98.194381 29.649851, -98.194236 29.64994, -98.194137 29.650002, -98.194121 29.650012, -98.193335 29.6505, -98.193235 29.650567, -98.192793 29.650823, -98.192539 29.650958, -98.192238 29.651119, -98.192159 29.651162, -98.191292 29.651614, -98.190469 29.652102, -98.189978 29.652413, -98.189301 29.652835, -98.188728 29.653196, -98.188976 29.653501, -98.194165 29.650283, -98.194644 29.649986)), ((-98.406256 29.315694, -98.405863 29.314921, -98.405543 29.314368, -98.404891 29.313454, -98.40447 29.312891, -98.40393 29.312249, -98.40372 29.312038, -98.40317 29.311479, -98.402256 29.31056, -98.402031 29.31033, -98.401276 29.309776, -98.400826 29.309463, -98.400344 29.309149, -98.399539 29.308763, -98.398911 29.308506, -98.398487 29.308377, -98.398153 29.308276, -98.397811 29.308184, -98.397597 29.308126, -98.397535 29.308114, -98.397299 29.30807, -98.397024 29.308019, -98.3965 29.307954, -98.396282 29.307941, -98.395736 29.307909, -98.395091 29.307906, -98.39474 29.307917, -98.394318 29.307931, -98.39459 29.308226, -98.39591 29.309654, -98.397107 29.310965, -98.397257 29.311128, -98.397675 29.311582, -98.39725 29.311391, -98.396589 29.311093, -98.391546 29.308817, -98.390511 29.308311, -98.390039 29.307986, -98.389827 29.307837, -98.389465 29.307592, -98.389251 29.307528, -98.388957 29.307428, -98.388461 29.30726, -98.387994 29.307067, -98.387494 29.306835, -98.384508 29.305074, -98.384382 29.304999, -98.383003 29.304214, -98.381082 29.303102, -98.380298 29.302656, -98.380126 29.302568, -98.380003 29.302505, -98.380186 29.302268, -98.379753 29.302015, -98.378754 29.301487, -98.376731 29.300471, -98.375571 29.299639, -98.373909 29.298507, -98.373732 29.29838, -98.373287 29.298091, -98.372316 29.297441, -98.369753 29.295742, -98.369419 29.295521, -98.367951 29.294529, -98.367253 29.294072, -98.365098 29.292662, -98.363498 29.291603, -98.363335 29.291505, -98.363201 29.291432, -98.36303 29.291373, -98.362213 29.29114, -98.361753 29.291021, -98.361307 29.290927, -98.360088 29.290706, -98.358892 29.290517, -98.357279 29.290277, -98.35704 29.290296, -98.356817 29.29044, -98.356568 29.290729, -98.35648 29.290852, -98.355415 29.290694, -98.355528 29.290747, -98.356317 29.291113, -98.357879 29.291831, -98.358855 29.292296, -98.361044 29.293301, -98.362336 29.293886, -98.363508 29.294443, -98.364179 29.294757, -98.365697 29.295469, -98.365803 29.295518, -98.367774 29.29642, -98.368637 29.296813, -98.369637 29.297278, -98.371029 29.297934, -98.372528 29.298663, -98.372793 29.298802, -98.37334 29.299091, -98.373102 29.299344, -98.373036 29.299414, -98.372268 29.300228, -98.372034 29.300484, -98.370857 29.301766, -98.369133 29.303645, -98.369065 29.30372, -98.367559 29.305378, -98.367117 29.305865, -98.365883 29.307206, -98.363809 29.309479, -98.363375 29.309909, -98.363207 29.310055, -98.363054 29.310174, -98.362897 29.31028, -98.362845 29.310314, -98.362494 29.31051, -98.362209 29.310633, -98.362041 29.310694, -98.361833 29.31076, -98.36152 29.310828, -98.361078 29.310908, -98.360959 29.310925, -98.360202 29.311037, -98.35976 29.311106, -98.359588 29.311141, -98.359394 29.311193, -98.359161 29.311272, -98.358944 29.311358, -98.358698 29.311475, -98.358545 29.311554, -98.358372 29.311656, -98.358209 29.311762, -98.357995 29.311921, -98.357697 29.312193, -98.357571 29.312333, -98.35662 29.313477, -98.356095 29.314116, -98.354928 29.315537, -98.354741 29.315759, -98.354207 29.316392, -98.353731 29.316973, -98.3526 29.318335, -98.351735 29.319377, -98.351886 29.319458, -98.352873 29.320097, -98.354431 29.321076, -98.355481 29.32175, -98.360496 29.324932, -98.36172 29.325705, -98.362093 29.325949, -98.363119 29.326589, -98.364164 29.327251, -98.365568 29.32814, -98.366083 29.328464, -98.367262 29.329204, -98.368847 29.330137, -98.370763 29.331265, -98.372491 29.332377, -98.373548 29.333065, -98.374567 29.333729, -98.37737 29.335523, -98.378503 29.336257, -98.379322 29.33678, -98.380171 29.337323, -98.381277 29.338029, -98.384118 29.339866, -98.388468 29.342659, -98.388588 29.342598, -98.389084 29.342558, -98.389185 29.342562, -98.38922 29.342506, -98.38969 29.34195, -98.399521 29.330019, -98.399653 29.329846, -98.399665 29.32983, -98.3997 29.329775, -98.399719 29.329745, -98.399754 29.329673, -98.399808 29.329537, -98.399843 29.329375, -98.399858 29.329243, -98.399864 29.32912, -98.399865 29.328928, -98.399774 29.326955, -98.399684 29.325166, -98.399679 29.325044, -98.399662 29.324651, -98.39964 29.324125, -98.399641 29.324023, -98.399662 29.32384, -98.399701 29.323674, -98.399751 29.323542, -98.399795 29.32344, -98.399844 29.323342, -98.400438 29.32239, -98.400693 29.321995, -98.401826 29.320164, -98.401904 29.320049, -98.401968 29.319968, -98.402051 29.319875, -98.402137 29.319797, -98.402364 29.319595, -98.402613 29.319396, -98.40272 29.319282, -98.402808 29.319167, -98.402877 29.319061, -98.402956 29.318891, -98.403028 29.318698, -98.403119 29.318457, -98.403469 29.317462, -98.401753 29.315806, -98.400076 29.314185, -98.399785 29.313874, -98.40054 29.313271, -98.401109 29.313522, -98.401305 29.313622, -98.401726 29.313815, -98.40211 29.313991, -98.40243 29.314131, -98.402903 29.314341, -98.404321 29.315004, -98.405719 29.31564, -98.405749 29.315605, -98.405815 29.315498, -98.406256 29.315694)), ((-98.738178 29.382709, -98.73842 29.382651, -98.738573 29.382626, -98.738733 29.382607, -98.738857 29.382607, -98.739075 29.382627, -98.739424 29.382672, -98.739686 29.382666, -98.740402 29.382574, -98.740365 29.382395, -98.740314 29.38222, -98.740233 29.381992, -98.740121 29.38171, -98.738913 29.379014, -98.738826 29.378785, -98.738705 29.378387, -98.738659 29.378199, -98.738614 29.377988, -98.738571 29.37764, -98.738559 29.377554, -98.738539 29.377102, -98.738535 29.376726, -98.738562 29.376189, -98.738605 29.37605, -98.738622 29.375577, -98.738622 29.375544, -98.737833 29.375582, -98.736602 29.375642, -98.735483 29.37571, -98.734985 29.37575, -98.733894 29.375837, -98.732514 29.376016, -98.732113 29.376068, -98.733094 29.376016, -98.734202 29.375938, -98.734591 29.375929, -98.734956 29.375906, -98.734983 29.375927, -98.735042 29.375994, -98.73506 29.376048, -98.735058 29.376102, -98.73504 29.376156, -98.734989 29.376234, -98.735641 29.376217, -98.736041 29.376182, -98.7372 29.376122, -98.737182 29.376566, -98.737143 29.376852, -98.737071 29.377133, -98.736928 29.37744, -98.736804 29.377669, -98.736687 29.377841, -98.736512 29.378051, -98.736366 29.37821, -98.736074 29.378515, -98.735746 29.378878, -98.735593 29.379095, -98.735498 29.379266, -98.735395 29.379477, -98.735293 29.37977, -98.73524 29.380029, -98.735204 29.38032, -98.735203 29.380612, -98.735275 29.381013, -98.735536 29.381829, -98.735127 29.381833, -98.734923 29.381861, -98.734709 29.381925, -98.734559 29.381998, -98.734436 29.382082, -98.734319 29.382185, -98.734246 29.38227, -98.73414 29.382421, -98.734096 29.3825, -98.73406 29.382602, -98.73403 29.382743, -98.734019 29.383093, -98.733999 29.383399, -98.73397 29.383533, -98.733933 29.383638, -98.733875 29.383769, -98.733807 29.383874, -98.733721 29.384007, -98.73355 29.384233, -98.733429 29.384421, -98.733389 29.384513, -98.733338 29.384692, -98.733319 29.384807, -98.733319 29.384899, -98.733337 29.385043, -98.733362 29.385145, -98.733422 29.385311, -98.733514 29.385457, -98.733637 29.385601, -98.733764 29.38571, -98.733895 29.385802, -98.734072 29.38588, -98.73424 29.385937, -98.734527 29.385979, -98.734996 29.385973, -98.735086 29.385973, -98.73571 29.385973, -98.7362 29.385973, -98.736426 29.385963, -98.736608 29.385948, -98.736757 29.385923, -98.736914 29.385882, -98.737128 29.385802, -98.737225 29.385758, -98.737289 29.385729, -98.737471 29.385621, -98.737567 29.385553, -98.737624 29.385513, -98.737733 29.385415, -98.737875 29.385265, -98.738014 29.385093, -98.738142 29.38487, -98.738215 29.384692, -98.73827 29.384479, -98.738286 29.384325, -98.738293 29.384255, -98.738292 29.383665, -98.738292 29.383306, -98.738292 29.383213, -98.73827 29.383047, -98.738234 29.382872, -98.738178 29.382709)), ((-98.638069 29.294345, -98.638142 29.294284, -98.639099 29.293524, -98.639205 29.29344, -98.639121 29.293376, -98.638738 29.292985, -98.638739 29.292967, -98.638782 29.292919, -98.63887 29.292847, -98.638895 29.292836, -98.638927 29.292836, -98.638952 29.292847, -98.638996 29.292946, -98.639015 29.293029, -98.63904 29.293068, -98.639071 29.29309, -98.63919 29.293139, -98.639272 29.293216, -98.639303 29.293271, -98.639357 29.293327, -98.63941 29.293381, -98.639472 29.293409, -98.639623 29.293414, -98.640764 29.29252, -98.640809 29.292602, -98.641621 29.291933, -98.64201 29.291619, -98.642406 29.291367, -98.642841 29.291114, -98.643264 29.290888, -98.643939 29.290585, -98.6444 29.290416, -98.644957 29.290229, -98.64547 29.290126, -98.645446 29.29003, -98.645422 29.289934, -98.64545 29.289927, -98.646171 29.289788, -98.646682 29.28969, -98.647362 29.28956, -98.647378 29.289492, -98.64719 29.289447, -98.646097 29.289655, -98.645645 29.289742, -98.644906 29.289922, -98.644877 29.28934, -98.644822 29.28909, -98.644759 29.288963, -98.644901 29.28884, -98.645367 29.288473, -98.64559 29.288272, -98.644478 29.288743, -98.643796 29.289066, -98.643456 29.289254, -98.643339 29.289135, -98.643893 29.288826, -98.644455 29.288586, -98.644778 29.28843, -98.645413 29.28817, -98.645615 29.288087, -98.646655 29.28763, -98.6475 29.287259, -98.648562 29.286848, -98.648464 29.286637, -98.64838 29.286455, -98.648821 29.286283, -98.649936 29.285808, -98.650472 29.285554, -98.65061 29.285499, -98.651005 29.285341, -98.651134 29.285281, -98.651452 29.285159, -98.651883 29.285065, -98.652456 29.284973, -98.652743 29.284911, -98.653317 29.284723, -98.653957 29.284441, -98.653935 29.28206, -98.653935 29.281609, -98.653936 29.280459, -98.653936 29.279943, -98.653918 29.278743, -98.653917 29.27846, -98.653908 29.27788, -98.653882 29.276777, -98.650513 29.276821, -98.650445 29.280008, -98.650369 29.280189, -98.650177 29.280185, -98.647394 29.280142, -98.646907 29.280161, -98.646017 29.27988, -98.645259 29.279669, -98.644417 29.279374, -98.644128 29.279379, -98.644139 29.27977, -98.644203 29.280423, -98.64329 29.280478, -98.643285 29.279396, -98.641132 29.279374, -98.641152 29.28437, -98.641146 29.284667, -98.64114 29.28684, -98.641132 29.290044, -98.641525 29.289707, -98.641647 29.289614, -98.64166 29.289638, -98.641672 29.289657, -98.64171 29.289704, -98.641593 29.289797, -98.64151 29.289872, -98.641118 29.290226, -98.640733 29.290516, -98.640562 29.290645, -98.640485 29.290709, -98.640196 29.290953, -98.640112 29.291019, -98.640055 29.290961, -98.639152 29.291649, -98.638942 29.291805, -98.63833 29.292125, -98.63831 29.292141, -98.637349 29.292922, -98.63759 29.293166, -98.637897 29.292947, -98.637995 29.293013, -98.638063 29.293147, -98.638121 29.293262, -98.638145 29.293377, -98.638131 29.293428, -98.63812 29.293471, -98.638083 29.293556, -98.637773 29.293743, -98.637643 29.293815, -98.637745 29.294005, -98.638069 29.294345)), ((-98.777845 29.421386, -98.775974 29.421417, -98.774913 29.421426, -98.774655 29.421427, -98.77466 29.421394, -98.770358 29.421418, -98.768608 29.421426, -98.768574 29.421426, -98.76348 29.421448, -98.762286 29.421454, -98.761911 29.42146, -98.761333 29.421468, -98.761243 29.421475, -98.761092 29.421487, -98.760886 29.421511, -98.760647 29.421548, -98.760425 29.421598, -98.760189 29.421664, -98.75993 29.421747, -98.759679 29.421841, -98.75937 29.421977, -98.758921 29.422233, -98.75873 29.422362, -98.758456 29.422577, -98.757921 29.423022, -98.756244 29.424403, -98.754383 29.425952, -98.754408 29.425972, -98.755594 29.42499, -98.756366 29.424344, -98.757951 29.423049, -98.758588 29.422523, -98.758767 29.422386, -98.759139 29.422152, -98.75935 29.422038, -98.759551 29.421942, -98.759762 29.42185, -98.75993 29.421787, -98.759998 29.421762, -98.760209 29.421697, -98.76045 29.421634, -98.760681 29.421583, -98.760914 29.421544, -98.761097 29.421523, -98.761333 29.421503, -98.761907 29.421493, -98.768216 29.421465, -98.768593 29.421462, -98.768595 29.421945, -98.768598 29.422837, -98.768604 29.424134, -98.768609 29.425127, -98.768596 29.425298, -98.768407 29.425398, -98.768417 29.425515, -98.768479 29.425573, -98.768529 29.425592, -98.768648 29.425575, -98.768724 29.425491, -98.769203 29.425566, -98.77073 29.425572, -98.771561 29.425576, -98.772859 29.425583, -98.774227 29.42559, -98.777003 29.425603, -98.777938 29.425608, -98.777845 29.421386)), ((-98.657857 29.372102, -98.657864 29.372135, -98.657889 29.372399, -98.65794 29.372548, -98.657983 29.372768, -98.658021 29.372779, -98.658103 29.372768, -98.658197 29.372884, -98.658222 29.372939, -98.658253 29.373181, -98.658285 29.37361, -98.658253 29.373769, -98.658096 29.37405, -98.657813 29.374391, -98.657764 29.37444, -98.657857 29.374686, -98.657987 29.375075, -98.657987 29.375397, -98.657931 29.375757, -98.657835 29.375955, -98.657667 29.376297, -98.657339 29.376715, -98.657122 29.377022, -98.657034 29.37727, -98.657002 29.377558, -98.657034 29.377808, -98.657106 29.378068, -98.657266 29.378406, -98.657498 29.378616, -98.657746 29.37872, -98.658138 29.37875, -98.658629 29.378698, -98.658793 29.37865, -98.658914 29.378542, -98.65897 29.378438, -98.659017 29.377408, -98.660736 29.377114, -98.661017 29.378072, -98.661076 29.378285, -98.661147 29.37819, -98.66128 29.378039, -98.661708 29.377485, -98.661784 29.37738, -98.661873 29.377209, -98.661895 29.377109, -98.661941 29.376821, -98.661946 29.376654, -98.661933 29.376526, -98.661911 29.37639, -98.661867 29.376211, -98.66177 29.375878, -98.661631 29.375403, -98.66156 29.375158, -98.66123 29.373968, -98.660821 29.374063, -98.660185 29.37421, -98.66007 29.374233, -98.659981 29.374229, -98.659931 29.374207, -98.659884 29.374171, -98.659853 29.374089, -98.659406 29.372565, -98.659317 29.372336, -98.659243 29.372223, -98.659132 29.37213, -98.659035 29.372067, -98.658911 29.372013, -98.658791 29.371985, -98.658685 29.371985, -98.658561 29.371996, -98.658415 29.372015, -98.658179 29.372057, -98.657857 29.372102)), ((-98.183333 29.657259, -98.183794 29.656947, -98.184855 29.656104, -98.185225 29.655816, -98.185478 29.655581, -98.185674 29.655369, -98.187267 29.654154, -98.187658 29.65386, -98.188222 29.653496, -98.188728 29.653196, -98.188593 29.653031, -98.188575 29.653, -98.18849 29.652849, -98.188424 29.652687, -98.188392 29.652585, -98.188362 29.652452, -98.188351 29.65225, -98.188351 29.652149, -98.188369 29.651983, -98.18841 29.651728, -98.188431 29.651553, -98.188428 29.651454, -98.18842 29.651335, -98.188387 29.651167, -98.188329 29.651004, -98.188225 29.650775, -98.18767 29.64972, -98.187071 29.648595, -98.186846 29.648162, -98.186807 29.648088, -98.186347 29.647207, -98.186264 29.647049, -98.186013 29.646588, -98.18596 29.646478, -98.18565 29.645882, -98.185377 29.645358, -98.185166 29.644974, -98.185111 29.645221, -98.184956 29.645336, -98.183981 29.645763, -98.184046 29.646086, -98.184249 29.646553, -98.18424 29.646754, -98.184078 29.647116, -98.18418 29.647324, -98.184373 29.647515, -98.184796 29.647787, -98.184862 29.647829, -98.185469 29.648265, -98.185672 29.648474, -98.185704 29.648577, -98.182303 29.649811, -98.181818 29.648885, -98.181468 29.648215, -98.180992 29.647305, -98.18 29.645425, -98.1767 29.64678, -98.177029 29.647387, -98.177186 29.647667, -98.177218 29.647731, -98.177569 29.64832, -98.177599 29.648377, -98.178405 29.649907, -98.178625 29.650325, -98.17932 29.651661, -98.179489 29.651999, -98.179898 29.652788, -98.180224 29.653352, -98.180287 29.653449, -98.180334 29.653544, -98.180794 29.654246, -98.180922 29.654457, -98.182286 29.656585, -98.182368 29.656712, -98.182769 29.657363, -98.182882 29.65752, -98.183333 29.657259)), ((-98.411972 29.291295, -98.411687 29.291113, -98.408656 29.289165, -98.407733 29.288572, -98.407207 29.288232, -98.405674 29.287243, -98.405149 29.286914, -98.404142 29.286271, -98.400926 29.284217, -98.399856 29.283533, -98.398083 29.282401, -98.398014 29.282358, -98.39789 29.282279, -98.397305 29.281909, -98.397216 29.281857, -98.397129 29.281835, -98.39639 29.281792, -98.395857 29.281765, -98.395541 29.281752, -98.395464 29.283426, -98.39544 29.283942, -98.395353 29.285714, -98.395238 29.288032, -98.395152 29.289867, -98.395478 29.289924, -98.397664 29.290332, -98.397816 29.29036, -98.399324 29.290666, -98.401627 29.291103, -98.402466 29.29124, -98.403404 29.291328, -98.404601 29.291398, -98.405499 29.291388, -98.407047 29.291352, -98.410578 29.291272, -98.411319 29.291261, -98.411972 29.291295)), ((-98.317368 29.4582, -98.317419 29.45812, -98.317499 29.457947, -98.317528 29.457862, -98.317655 29.457489, -98.31805 29.456223, -98.313698 29.457467, -98.315351 29.457901, -98.316187 29.458113, -98.316286 29.458145, -98.316315 29.45816, -98.316358 29.458182, -98.316419 29.458225, -98.316461 29.458278, -98.31652 29.45842, -98.317368 29.4582)), ((-98.420737 29.686014, -98.420103 29.685339, -98.419786 29.685562, -98.419668 29.685636, -98.419549 29.685693, -98.419383 29.685748, -98.419238 29.685778, -98.418509 29.685887, -98.418332 29.685902, -98.418209 29.685897, -98.418053 29.685873, -98.417852 29.68581, -98.417716 29.685746, -98.417273 29.68645, -98.417846 29.686711, -98.417979 29.686744, -98.418103 29.686759, -98.418464 29.686723, -98.419136 29.686627, -98.419305 29.686633, -98.419358 29.686642, -98.41954 29.686712, -98.419662 29.686796, -98.419789 29.686706, -98.42064 29.686086, -98.420737 29.686014)), ((-98.439292 29.736009, -98.439281 29.736061, -98.439229 29.736294, -98.439194 29.736453, -98.438954 29.737451, -98.438858 29.737771, -98.438762 29.738055, -98.438682 29.738255, -98.438432 29.738783, -98.438054 29.739391, -98.437804 29.739887, -98.437713 29.740042, -98.437634 29.740167, -98.437443 29.740443, -98.437087 29.740942, -98.435996 29.742306, -98.435699 29.742654, -98.435701 29.742797, -98.435717 29.742862, -98.435748 29.742921, -98.435877 29.743052, -98.436648 29.742827, -98.437704 29.742497, -98.437869 29.742451, -98.438066 29.742419, -98.438252 29.742402, -98.43986 29.742386, -98.439867 29.743005, -98.439854 29.743146, -98.439834 29.743554, -98.439932 29.743554, -98.440886 29.743542, -98.441383 29.743448, -98.441383 29.743497, -98.441396 29.745775, -98.44138 29.747892, -98.441309 29.752073, -98.441312 29.752128, -98.441233 29.756998, -98.441164 29.757, -98.441163 29.757162, -98.441147 29.760155, -98.441149 29.760237, -98.441187 29.761431, -98.441198 29.765876, -98.4412 29.766494, -98.439849 29.766487, -98.439352 29.766481, -98.439364 29.767149, -98.439422 29.770291, -98.439458 29.772214, -98.432019 29.772215, -98.428783 29.772213, -98.428545 29.772186, -98.4288 29.770665, -98.428802 29.77064, -98.428865 29.770253, -98.428356 29.770202, -98.42784 29.770148, -98.427535 29.770114, -98.427446 29.770757, -98.427439 29.770811, -98.427202 29.772556, -98.427124 29.773068, -98.42698 29.774147, -98.426965 29.774236, -98.426909 29.774559, -98.426797 29.775071, -98.426696 29.775428, -98.426627 29.775653, -98.426584 29.775791, -98.426295 29.776541, -98.426248 29.776647, -98.426189 29.776782, -98.426152 29.776856, -98.426438 29.776948, -98.427316 29.777227, -98.427472 29.776853, -98.427485 29.776822, -98.42759 29.776546, -98.427639 29.776404, -98.427701 29.776231, -98.427819 29.775866, -98.427921 29.775517, -98.428019 29.775134, -98.428099 29.774781, -98.428183 29.774346, -98.428192 29.774287, -98.428545 29.774331, -98.42866 29.774347, -98.428734 29.774249, -98.429036 29.774301, -98.429467 29.774415, -98.429906 29.774584, -98.430974 29.775275, -98.431275 29.775454, -98.431784 29.775637, -98.432401 29.775694, -98.43391 29.775715, -98.43692 29.77566, -98.437119 29.77427, -98.437255 29.773438, -98.438406 29.773434, -98.43915 29.773417, -98.439712 29.773394, -98.43992 29.77337, -98.439996 29.773358, -98.440124 29.773311, -98.440238 29.773252, -98.440446 29.773102, -98.441093 29.772555, -98.441868 29.77185, -98.440973 29.771099, -98.440892 29.771027, -98.44084 29.770956, -98.440789 29.770823, -98.440772 29.77068, -98.440757 29.770461, -98.440745 29.770119, -98.440733 29.769565, -98.440742 29.768331, -98.440731 29.767573, -98.44074 29.767488, -98.440754 29.767419, -98.440779 29.767359, -98.440802 29.767324, -98.440832 29.767293, -98.44085 29.767268, -98.440972 29.767221, -98.441048 29.767205, -98.441124 29.767203, -98.441257 29.767217, -98.4418 29.767313, -98.44311 29.7675, -98.443413 29.767563, -98.443559 29.7676, -98.443705 29.767648, -98.443815 29.767692, -98.444063 29.767836, -98.444306 29.768023, -98.444975 29.768591, -98.445248 29.768834, -98.445816 29.76931, -98.446197 29.769648, -98.44644 29.769853, -98.447051 29.77038, -98.447693 29.770924, -98.447861 29.771095, -98.447945 29.771218, -98.448101 29.771542, -98.448141 29.771676, -98.448178 29.771823, -98.448265 29.772029, -98.448305 29.772107, -98.448397 29.772228, -98.448457 29.772286, -98.448581 29.772417, -98.448823 29.772671, -98.449128 29.772423, -98.449325 29.772293, -98.449499 29.772186, -98.449644 29.772111, -98.449779 29.77205, -98.449851 29.77148, -98.449909 29.771016, -98.450114 29.769667, -98.450155 29.769321, -98.450075 29.76935, -98.449986 29.769373, -98.449793 29.769404, -98.449678 29.769411, -98.449507 29.769395, -98.449375 29.769368, -98.449265 29.769341, -98.449148 29.769298, -98.448991 29.769231, -98.448876 29.76917, -98.448598 29.768993, -98.448459 29.768896, -98.448052 29.768587, -98.447243 29.767987, -98.44704 29.767844, -98.446683 29.767555, -98.44662 29.767471, -98.44656 29.767382, -98.446481 29.767242, -98.446436 29.767094, -98.446398 29.766949, -98.446306 29.766604, -98.446189 29.766091, -98.44614 29.765723, -98.44614 29.765603, -98.446265 29.765575, -98.447955 29.765294, -98.448129 29.765262, -98.448189 29.765242, -98.448243 29.76521, -98.448269 29.765174, -98.44829 29.765136, -98.448307 29.765027, -98.448317 29.764766, -98.448608 29.764769, -98.448724 29.764753, -98.448828 29.76472, -98.448905 29.764682, -98.449082 29.76454, -98.449205 29.764464, -98.449345 29.764406, -98.449536 29.764371, -98.449808 29.764328, -98.449994 29.76431, -98.450122 29.76431, -98.450312 29.764332, -98.45079 29.764403, -98.450712 29.763985, -98.450703 29.76389, -98.450702 29.763789, -98.450707 29.763654, -98.450691 29.762951, -98.450691 29.762761, -98.450691 29.762396, -98.450703 29.762056, -98.450716 29.761486, -98.450723 29.76138, -98.450736 29.761316, -98.450795 29.761186, -98.450857 29.761094, -98.450922 29.761027, -98.451008 29.760961, -98.451136 29.760838, -98.451176 29.760773, -98.451224 29.760671, -98.451282 29.760519, -98.45136 29.760369, -98.451455 29.760142, -98.451486 29.760041, -98.451513 29.759897, -98.451579 29.759553, -98.452584 29.75968, -98.452652 29.759697, -98.453428 29.755893, -98.451139 29.755491, -98.452629 29.748756, -98.45279 29.748057, -98.452733 29.748058, -98.452735 29.748048, -98.451746 29.748067, -98.452448 29.746142, -98.453058 29.744586, -98.454119 29.744972, -98.454827 29.74527, -98.454953 29.745336, -98.455063 29.745412, -98.455172 29.745504, -98.455263 29.745605, -98.455431 29.745844, -98.455533 29.745332, -98.455619 29.74491, -98.455706 29.744827, -98.455974 29.744425, -98.455862 29.744348, -98.455749 29.744278, -98.455618 29.744207, -98.455479 29.744141, -98.45471 29.743816, -98.45429 29.743674, -98.454359 29.7434, -98.453542 29.743256, -98.453525 29.743332, -98.453442 29.743711, -98.453378 29.744007, -98.453146 29.74393, -98.452643 29.743728, -98.45239 29.743627, -98.452288 29.743585, -98.452176 29.743538, -98.451859 29.743407, -98.451303 29.743196, -98.451324 29.743126, -98.451221 29.742792, -98.450885 29.74232, -98.450809 29.742219, -98.450625 29.742147, -98.45036 29.742088, -98.450128 29.742037, -98.449664 29.741969, -98.449385 29.741929, -98.449107 29.741942, -98.448743 29.742022, -98.447004 29.742162, -98.446612 29.742151, -98.445704 29.742094, -98.444755 29.742075, -98.444504 29.742063, -98.444244 29.742043, -98.443983 29.742013, -98.443589 29.741952, -98.442363 29.741711, -98.44142 29.741525, -98.441271 29.741523, -98.441273 29.741051, -98.441123 29.741059, -98.440977 29.741073, -98.440838 29.741092, -98.440568 29.74114, -98.440372 29.741169, -98.44015 29.741193, -98.439924 29.741209, -98.439642 29.741218, -98.438392 29.741217, -98.438206 29.741218, -98.437903 29.74123, -98.438116 29.740947, -98.438321 29.740659, -98.438515 29.740367, -98.438701 29.74007, -98.438876 29.739768, -98.439042 29.739462, -98.439198 29.739153, -98.439344 29.738839, -98.439473 29.738541, -98.439595 29.73823, -98.439708 29.737916, -98.439811 29.7376, -98.439894 29.737319, -98.439969 29.737037, -98.440172 29.736162, -98.439665 29.736074, -98.439292 29.736009)), ((-98.657264 29.723257, -98.658264 29.722898, -98.658716 29.723994, -98.659236 29.723859, -98.6595 29.723757, -98.660242 29.723474, -98.660636 29.723324, -98.660834 29.723248, -98.660916 29.723217, -98.660957 29.723201, -98.661785 29.722885, -98.664488 29.721196, -98.663864 29.720485, -98.663814 29.720414, -98.664036 29.720314, -98.664429 29.720173, -98.66446 29.720163, -98.6649 29.720024, -98.665155 29.71993, -98.665369 29.719824, -98.66548 29.719753, -98.665408 29.719693, -98.665066 29.719407, -98.664631 29.718952, -98.663737 29.719445, -98.662989 29.718714, -98.662927 29.718673, -98.662687 29.718432, -98.662894 29.71829, -98.663078 29.718165, -98.66291 29.71804, -98.662844 29.717853, -98.662754 29.717575, -98.662683 29.717356, -98.662676 29.716823, -98.662845 29.716553, -98.662371 29.715873, -98.661664 29.714802, -98.661347 29.714315, -98.660606 29.713218, -98.660297 29.71276, -98.658914 29.713157, -98.658637 29.713177, -98.658319 29.713497, -98.657771 29.713629, -98.657434 29.712915, -98.657027 29.7122, -98.656162 29.712621, -98.656002 29.712707, -98.655384 29.712991, -98.654642 29.71332, -98.65399 29.712238, -98.653191 29.712605, -98.652734 29.712815, -98.652803 29.712973, -98.652815 29.713049, -98.652808 29.713228, -98.652572 29.713762, -98.65227 29.714278, -98.652086 29.71476, -98.652171 29.715314, -98.652454 29.715723, -98.6529 29.716092, -98.652928 29.716243, -98.65294 29.716557, -98.652895 29.716776, -98.652858 29.716914, -98.6528 29.717183, -98.652782 29.717241, -98.65273 29.717565, -98.652729 29.717632, -98.652733 29.717919, -98.652731 29.717985, -98.652755 29.718259, -98.652786 29.718391, -98.65285 29.718563, -98.652892 29.71875, -98.652895 29.718869, -98.65287 29.719088, -98.652875 29.719137, -98.652886 29.719186, -98.652918 29.719277, -98.65294 29.719364, -98.652969 29.719432, -98.653012 29.719506, -98.653074 29.719577, -98.653165 29.719642, -98.653218 29.719668, -98.653272 29.719689, -98.654117 29.719964, -98.653871 29.720538, -98.653856 29.720647, -98.653861 29.720762, -98.653868 29.720812, -98.653883 29.720864, -98.654249 29.721693, -98.654302 29.721785, -98.654344 29.721841, -98.654396 29.721889, -98.654453 29.721933, -98.65466 29.722044, -98.654977 29.722208, -98.655088 29.722253, -98.655155 29.722269, -98.65523 29.722282, -98.655285 29.722286, -98.655369 29.722282, -98.655476 29.722261, -98.657274 29.72166, -98.656992 29.721017, -98.656968 29.720909, -98.656963 29.720841, -98.656954 29.720467, -98.657056 29.720464, -98.657302 29.720447, -98.657427 29.720443, -98.658343 29.720388, -98.658359 29.720539, -98.658372 29.72059, -98.658388 29.720615, -98.658392 29.720633, -98.658512 29.720903, -98.658533 29.720951, -98.658547 29.720993, -98.658553 29.721048, -98.658543 29.721102, -98.658491 29.72121, -98.658448 29.721274, -98.658435 29.721322, -98.658431 29.721374, -98.658441 29.721436, -98.658635 29.721807, -98.659118 29.722631, -98.658834 29.722685, -98.658366 29.722793, -98.65825 29.722828, -98.657798 29.722965, -98.657256 29.723198, -98.657233 29.723209, -98.656816 29.723401, -98.656246 29.723718, -98.65571 29.724031, -98.654235 29.724942, -98.654101 29.725024, -98.653996 29.725083, -98.653776 29.725183, -98.653728 29.725206, -98.653406 29.725314, -98.652995 29.725408, -98.652589 29.725459, -98.65216 29.725452, -98.652147 29.725163, -98.652076 29.724771, -98.651998 29.724542, -98.651893 29.724322, -98.651783 29.72416, -98.651576 29.723829, -98.651545 29.72376, -98.651471 29.723511, -98.651447 29.72339, -98.651501 29.72309, -98.65152 29.722959, -98.65156 29.722739, -98.651565 29.722638, -98.651581 29.722516, -98.651534 29.722392, -98.65143 29.722167, -98.651347 29.72205, -98.651255 29.721903, -98.651152 29.721686, -98.651111 29.721576, -98.651083 29.721407, -98.651035 29.721251, -98.651017 29.720959, -98.650987 29.720792, -98.650945 29.72064, -98.650888 29.720477, -98.65067 29.720106, -98.65062 29.719979, -98.65051 29.719682, -98.650445 29.719273, -98.650428 29.719133, -98.650372 29.718833, -98.65031 29.718642, -98.650239 29.718456, -98.650141 29.71831, -98.650026 29.71817, -98.649896 29.718036, -98.649733 29.717915, -98.649358 29.717709, -98.648881 29.717472, -98.648318 29.717211, -98.648126 29.717099, -98.647949 29.71697, -98.647789 29.716825, -98.647646 29.716666, -98.647529 29.716515, -98.647278 29.716084, -98.647001 29.715597, -98.646891 29.71546, -98.646757 29.715325, -98.646527 29.715143, -98.646435 29.715084, -98.646267 29.714999, -98.646099 29.714929, -98.645921 29.714874, -98.645738 29.714836, -98.645552 29.714815, -98.645364 29.714811, -98.645177 29.714824, -98.644592 29.714924, -98.644413 29.714989, -98.644166 29.715065, -98.643915 29.715226, -98.644472 29.716272, -98.644533 29.716415, -98.644577 29.716563, -98.644602 29.716714, -98.64461 29.716866, -98.644599 29.717019, -98.644571 29.717169, -98.644471 29.71758, -98.644441 29.71771, -98.644437 29.71795, -98.644457 29.718215, -98.644496 29.718341, -98.644533 29.718441, -98.64457 29.718506, -98.64467 29.718643, -98.644973 29.71893, -98.645081 29.719048, -98.645175 29.719176, -98.645252 29.719312, -98.645311 29.719504, -98.645438 29.71988, -98.645525 29.720039, -98.645629 29.720185, -98.645756 29.720327, -98.645831 29.720394, -98.64605 29.720564, -98.645337 29.721145, -98.644897 29.721472, -98.644618 29.721628, -98.644374 29.721733, -98.644195 29.721771, -98.644129 29.721785, -98.64388 29.721826, -98.643642 29.721857, -98.64333 29.721865, -98.642986 29.721864, -98.642766 29.721836, -98.642253 29.721726, -98.641792 29.721546, -98.641671 29.721491, -98.641545 29.721456, -98.641342 29.721452, -98.64125 29.721464, -98.640976 29.721541, -98.64088 29.721581, -98.640717 29.721664, -98.64047 29.721785, -98.639828 29.72204, -98.63911 29.722249, -98.638767 29.722324, -98.637519 29.722526, -98.636774 29.722588, -98.63658 29.722634, -98.636493 29.722669, -98.636392 29.722739, -98.636331 29.722803, -98.636223 29.72295, -98.636176 29.723049, -98.636034 29.723401, -98.63601 29.723497, -98.635997 29.723594, -98.636006 29.723699, -98.636072 29.723962, -98.636271 29.724678, -98.636317 29.724894, -98.636383 29.725902, -98.635529 29.725935, -98.634844 29.725939, -98.634289 29.725925, -98.634147 29.725931, -98.63404 29.725966, -98.634016 29.725984, -98.63433 29.727578, -98.634339 29.727663, -98.634336 29.727726, -98.634324 29.727803, -98.634411 29.727827, -98.634526 29.727886, -98.634663 29.727965, -98.634817 29.728094, -98.634924 29.728204, -98.635016 29.728415, -98.635058 29.728598, -98.635075 29.728902, -98.635094 29.729018, -98.635161 29.729213, -98.635231 29.729353, -98.635331 29.729499, -98.635438 29.729619, -98.635534 29.729709, -98.635646 29.729796, -98.635849 29.729937, -98.635933 29.729998, -98.636076 29.730088, -98.636143 29.730121, -98.636901 29.730396, -98.637048 29.730478, -98.637179 29.730557, -98.637128 29.730639, -98.63709 29.730727, -98.637075 29.730786, -98.637053 29.730923, -98.637046 29.731078, -98.636629 29.731085, -98.635926 29.731092, -98.635907 29.731093, -98.635718 29.731103, -98.63555 29.73112, -98.632933 29.731125, -98.632821 29.731113, -98.632646 29.731101, -98.632254 29.7311, -98.631009 29.731096, -98.629634 29.731095, -98.62963 29.730748, -98.629588 29.73052, -98.629557 29.730378, -98.629444 29.730044, -98.629378 29.729774, -98.629346 29.729538, -98.62935 29.729386, -98.629373 29.729025, -98.629382 29.728725, -98.629329 29.72802, -98.629318 29.72787, -98.629335 29.727619, -98.629396 29.727379, -98.629431 29.727278, -98.629569 29.727013, -98.629711 29.726798, -98.629774 29.726714, -98.629828 29.726622, -98.62987 29.726526, -98.629901 29.726426, -98.629968 29.726084, -98.629988 29.725928, -98.630154 29.724919, -98.630171 29.724694, -98.630179 29.724634, -98.63018 29.724576, -98.630174 29.724517, -98.630161 29.724459, -98.630141 29.724403, -98.630049 29.724256, -98.629711 29.723927, -98.629579 29.723789, -98.629487 29.723663, -98.629381 29.723481, -98.629274 29.723212, -98.6292 29.722961, -98.629178 29.722759, -98.629156 29.722669, -98.629108 29.722556, -98.629065 29.722472, -98.628963 29.722309, -98.628903 29.722192, -98.628889 29.722139, -98.628876 29.722022, -98.628882 29.721922, -98.629128 29.72125, -98.629157 29.72113, -98.629171 29.721008, -98.629171 29.720886, -98.629156 29.720765, -98.629126 29.720645, -98.629082 29.720529, -98.628981 29.720358, -98.628718 29.720467, -98.628461 29.720542, -98.62838 29.720551, -98.628104 29.720565, -98.627802 29.720567, -98.627042 29.72051, -98.626714 29.720462, -98.626088 29.720337, -98.625658 29.720308, -98.623616 29.72033, -98.623619 29.720742, -98.623622 29.721185, -98.623634 29.721836, -98.62348 29.72381, -98.623466 29.724333, -98.623452 29.726346, -98.623453 29.726436, -98.623475 29.728453, -98.623453 29.731075, -98.622943 29.731042, -98.622075 29.731039, -98.622118 29.731807, -98.623457 29.731658, -98.623462 29.731912, -98.62346 29.732203, -98.623457 29.733331, -98.623466 29.734239, -98.623451 29.734836, -98.623426 29.735092, -98.623387 29.735573, -98.623384 29.735634, -98.623311 29.736947, -98.62331 29.736962, -98.623296 29.737227, -98.623295 29.737643, -98.623292 29.738727, -98.62332 29.741568, -98.62332 29.741606, -98.623329 29.742475, -98.623329 29.743311, -98.623339 29.743708, -98.623339 29.743933, -98.62335 29.743941, -98.62337 29.74395, -98.623388 29.743956, -98.623398 29.743959, -98.623412 29.743963, -98.623472 29.74398, -98.623493 29.743986, -98.623541 29.744007, -98.623619 29.744041, -98.623739 29.744118, -98.624196 29.744176, -98.624427 29.744205, -98.624609 29.744266, -98.624836 29.744304, -98.625 29.744288, -98.625105 29.744288, -98.62532 29.744287, -98.62571 29.74432, -98.626051 29.744332, -98.627469 29.744316, -98.628327 29.744261, -98.628756 29.744146, -98.629197 29.743964, -98.62923 29.743947, -98.629733 29.74369, -98.629852 29.743598, -98.630193 29.743338, -98.630383 29.743233, -98.630805 29.743035, -98.631644 29.742634, -98.631767 29.742524, -98.631846 29.742453, -98.632467 29.742136, -98.632741 29.741996, -98.63288 29.741936, -98.632949 29.741925, -98.633022 29.741913, -98.633145 29.741892, -98.633302 29.741843, -98.634021 29.741562, -98.634991 29.74134, -98.635106 29.741315, -98.636114 29.741049, -98.636379 29.74098, -98.636568 29.740953, -98.637905 29.740766, -98.638352 29.740722, -98.638434 29.7407, -98.638851 29.740651, -98.638989 29.740651, -98.63933 29.740596, -98.63955 29.740486, -98.639695 29.740437, -98.640036 29.740376, -98.640156 29.740327, -98.640969 29.740228, -98.641044 29.740313, -98.641164 29.740215, -98.642605 29.740207, -98.643545 29.740357, -98.64366 29.740445, -98.643774 29.740532, -98.6439 29.740642, -98.644228 29.740812, -98.644722 29.741178, -98.644736 29.741184, -98.644765 29.741205, -98.644791 29.741224, -98.644802 29.741232, -98.644852 29.741269, -98.645129 29.741599, -98.645209 29.741623, -98.645402 29.741893, -98.64546 29.741997, -98.645665 29.742364, -98.646231 29.743602, -98.646244 29.744054, -98.646187 29.744351, -98.646119 29.744587, -98.646242 29.744604, -98.646341 29.745134, -98.646453 29.745659, -98.646467 29.746013, -98.646439 29.746562, -98.646257 29.747148, -98.646102 29.747478, -98.645551 29.747977, -98.645466 29.747855, -98.645516 29.747547, -98.645516 29.747404, -98.645649 29.746991, -98.645921 29.7467, -98.646079 29.746507, -98.646243 29.746144, -98.646256 29.745594, -98.646193 29.745346, -98.646124 29.745181, -98.646035 29.745302, -98.645701 29.74566, -98.64569 29.745677, -98.64527 29.74602, -98.644703 29.746487, -98.643414 29.747556, -98.643162 29.747465, -98.643116 29.747449, -98.64302 29.747419, -98.642823 29.747388, -98.642618 29.747373, -98.642449 29.747381, -98.642231 29.747398, -98.641811 29.747476, -98.641239 29.747572, -98.641128 29.748684, -98.641044 29.74953, -98.640788 29.749747, -98.640224 29.750221, -98.639699 29.750641, -98.63918 29.751056, -98.638422 29.751663, -98.637943 29.752046, -98.63688 29.752898, -98.636041 29.75357, -98.63557 29.75395, -98.635961 29.753911, -98.636176 29.75387, -98.636414 29.753796, -98.636729 29.753668, -98.637825 29.753122, -98.63833 29.752871, -98.638436 29.752824, -98.638537 29.752793, -98.638659 29.752768, -98.638809 29.752763, -98.638979 29.752776, -98.63907 29.752795, -98.639261 29.752867, -98.6394 29.752944, -98.639548 29.753043, -98.639695 29.753184, -98.639762 29.753267, -98.639773 29.753282, -98.639919 29.753476, -98.639987 29.753602, -98.640009 29.75366, -98.640031 29.753761, -98.640041 29.753995, -98.640039 29.754008, -98.64001 29.754226, -98.639888 29.754685, -98.639842 29.754877, -98.639814 29.755035, -98.639809 29.755068, -98.639787 29.755385, -98.639785 29.755413, -98.63979 29.755511, -98.639801 29.755676, -98.639835 29.755888, -98.639845 29.755948, -98.639864 29.756011, -98.639973 29.756369, -98.639977 29.756381, -98.640074 29.75662, -98.640256 29.756947, -98.640467 29.757249, -98.640537 29.757333, -98.640616 29.757427, -98.64079 29.75761, -98.641092 29.757946, -98.641141 29.758, -98.64122 29.758099, -98.641293 29.75821, -98.641429 29.758469, -98.641497 29.758641, -98.641555 29.758874, -98.641591 29.759178, -98.641599 29.759236, -98.641597 29.75963, -98.641582 29.760247, -98.641597 29.760737, -98.641605 29.760834, -98.641643 29.761257, -98.641663 29.761386, -98.641694 29.761465, -98.641808 29.761657, -98.641944 29.761847, -98.642066 29.76198, -98.642753 29.762594, -98.642962 29.762749, -98.643057 29.762803, -98.643227 29.762869, -98.643458 29.762902, -98.643669 29.762902, -98.643791 29.762892, -98.644083 29.762836, -98.644445 29.762755, -98.645257 29.762548, -98.645375 29.762924, -98.645383 29.763072, -98.645384 29.763081, -98.645352 29.76323, -98.645297 29.763353, -98.644957 29.763909, -98.644884 29.764083, -98.644854 29.764258, -98.644852 29.764466, -98.644873 29.764569, -98.644882 29.764607, -98.644946 29.764744, -98.644995 29.764814, -98.645179 29.765008, -98.645347 29.765128, -98.645434 29.765171, -98.645521 29.7652, -98.64573 29.765238, -98.645944 29.765238, -98.646478 29.76524, -98.646698 29.76525, -98.646828 29.765267, -98.646936 29.765291, -98.647101 29.765343, -98.647291 29.765436, -98.647382 29.765492, -98.647606 29.765674, -98.647728 29.765806, -98.647862 29.766029, -98.647916 29.766151, -98.647952 29.766262, -98.64821 29.767043, -98.648573 29.768054, -98.648721 29.768467, -98.648824 29.768639, -98.648872 29.768705, -98.649035 29.768856, -98.649223 29.768992, -98.649989 29.769425, -98.650183 29.769553, -98.650518 29.769824, -98.650717 29.770018, -98.65086 29.770197, -98.650944 29.770303, -98.651091 29.770518, -98.651302 29.770884, -98.651408 29.771119, -98.650832 29.771193, -98.650266 29.771237, -98.650092 29.771251, -98.649845 29.771263, -98.649548 29.771258, -98.647427 29.771112, -98.646833 29.771047, -98.646626 29.771007, -98.646309 29.770906, -98.645752 29.770674, -98.645502 29.770571, -98.645448 29.770549, -98.645076 29.770346, -98.644958 29.770282, -98.644802 29.770164, -98.644718 29.770101, -98.644602 29.769985, -98.64451 29.769877, -98.644408 29.769735, -98.64409 29.769293, -98.643869 29.768953, -98.643837 29.768904, -98.643813 29.768864, -98.643787 29.768826, -98.643697 29.768688, -98.643392 29.768294, -98.643332 29.768235, -98.643235 29.768162, -98.64322 29.76815, -98.643077 29.768095, -98.642817 29.768043, -98.64159 29.767854, -98.641476 29.767836, -98.641249 29.767822, -98.638457 29.767794, -98.637417 29.767786, -98.636766 29.767781, -98.63541 29.767744, -98.634619 29.767667, -98.634304 29.767643, -98.63408 29.767626, -98.633619 29.767619, -98.633267 29.767646, -98.633167 29.767653, -98.632668 29.767703, -98.631522 29.767793, -98.631257 29.767801, -98.630611 29.767784, -98.63002 29.767721, -98.629645 29.767664, -98.629311 29.767626, -98.627792 29.76755, -98.627146 29.767541, -98.626925 29.767564, -98.626763 29.767603, -98.626649 29.767648, -98.626512 29.767721, -98.625883 29.768107, -98.625741 29.768207, -98.625633 29.768296, -98.625551 29.768383, -98.625426 29.768579, -98.625387 29.768679, -98.62535 29.768864, -98.625341 29.769758, -98.625321 29.770546, -98.625483 29.770545, -98.625972 29.770545, -98.626056 29.770555, -98.626129 29.770585, -98.626413 29.770585, -98.627688 29.770587, -98.629365 29.77059, -98.631063 29.770593, -98.632367 29.770596, -98.633927 29.770599, -98.635352 29.770597, -98.636006 29.770596, -98.636328 29.770595, -98.639628 29.770594, -98.641054 29.770593, -98.643686 29.770646, -98.644352 29.770851, -98.644427 29.770875, -98.645423 29.771135, -98.646053 29.771299, -98.647679 29.77156, -98.649155 29.771559, -98.650932 29.771525, -98.654262 29.771457, -98.657592 29.77139, -98.657594 29.769837, -98.657598 29.766846, -98.6576 29.76518, -98.657602 29.763628, -98.657616 29.76225, -98.657639 29.759998, -98.657938 29.76, -98.65952 29.760016, -98.660899 29.76003, -98.660946 29.760022, -98.661089 29.760001, -98.661137 29.759994, -98.66115 29.759774, -98.661187 29.759164, -98.661337 29.756675, -98.661357 29.756361, -98.661532 29.756065, -98.661478 29.755902, -98.660826 29.754465, -98.659232 29.750947, -98.659551 29.751046, -98.659289 29.75058, -98.658515 29.749207, -98.658651 29.74922, -98.658826 29.749239, -98.658977 29.749239, -98.659046 29.749233, -98.6593 29.749211, -98.659167 29.74869, -98.659628 29.748835, -98.658312 29.747323, -98.657852 29.74644, -98.657336 29.745297, -98.657205 29.744845, -98.657051 29.744307, -98.656997 29.743536, -98.657171 29.742819, -98.657512 29.741822, -98.658009 29.740351, -98.657844 29.740351, -98.657946 29.740283, -98.6585 29.738238, -98.658546 29.737407, -98.658705 29.736486, -98.658864 29.735566, -98.658889 29.735424, -98.659118 29.734103, -98.659253 29.73413, -98.659582 29.734171, -98.659857 29.734168, -98.659874 29.734014, -98.659908 29.733879, -98.659957 29.733747, -98.660022 29.733621, -98.660058 29.733533, -98.660143 29.733383, -98.660183 29.733283, -98.660234 29.733178, -98.660249 29.733073, -98.660251 29.732966, -98.660241 29.732861, -98.660218 29.732756, -98.660182 29.732654, -98.660073 29.732391, -98.659896 29.731965, -98.65983 29.731739, -98.659821 29.731578, -98.659852 29.731102, -98.656742 29.731105, -98.656197 29.731102, -98.656094 29.731102, -98.654827 29.728779, -98.655122 29.728621, -98.654789 29.727947, -98.656417 29.72704, -98.65573 29.725738, -98.656178 29.724879, -98.65594 29.72451, -98.655725 29.724175, -98.655864 29.724068, -98.65631 29.723808, -98.657208 29.723284, -98.657264 29.723257)), ((-98.47405 29.310238, -98.473292 29.307877, -98.473006 29.306853, -98.47302 29.306342, -98.472908 29.305441, -98.47288 29.304728, -98.473295 29.304719, -98.473437 29.304701, -98.473547 29.304674, -98.473674 29.304629, -98.4738 29.304569, -98.47391 29.304496, -98.473972 29.304449, -98.474195 29.304258, -98.474327 29.304171, -98.47448 29.304093, -98.474606 29.304043, -98.474742 29.304011, -98.474884 29.303993, -98.475325 29.303987, -98.475394 29.303986, -98.476255 29.303999, -98.477767 29.304014, -98.477769 29.303441, -98.478509 29.303457, -98.478955 29.303466, -98.479117 29.303456, -98.479192 29.303433, -98.479289 29.303393, -98.479365 29.303348, -98.479323 29.303245, -98.479307 29.303168, -98.479324 29.302511, -98.47931 29.30244, -98.479263 29.301725, -98.479237 29.300934, -98.479098 29.30012, -98.47909 29.299336, -98.479034 29.299075, -98.478914 29.298858, -98.478705 29.29866, -98.478477 29.298488, -98.478177 29.298184, -98.477906 29.297857, -98.477663 29.297604, -98.477439 29.297343, -98.477308 29.29709, -98.47715 29.296775, -98.47682 29.296116, -98.476747 29.296039, -98.47667 29.296011, -98.476611 29.29599, -98.476072 29.295996, -98.476072 29.295823, -98.47607 29.295698, -98.476069 29.295575, -98.476077 29.294971, -98.476078 29.294856, -98.476081 29.294743, -98.476085 29.294633, -98.476088 29.294523, -98.476095 29.294324, -98.476098 29.294239, -98.476103 29.294173, -98.476107 29.294124, -98.476111 29.294062, -98.476106 29.293994, -98.476107 29.293974, -98.476049 29.293975, -98.472766 29.293995, -98.47277 29.292187, -98.472761 29.291018, -98.472746 29.288885, -98.472735 29.287315, -98.472716 29.284706, -98.472708 29.283571, -98.47268 29.279827, -98.472675 29.27857, -98.472665 29.277745, -98.472652 29.276595, -98.472625 29.274273, -98.472616 29.273035, -98.472593 29.270068, -98.47259 29.269618, -98.472588 29.267783, -98.472556 29.26477, -98.472529 29.261904, -98.472524 29.259839, -98.472524 29.259702, -98.47252 29.258417, -98.472493 29.256985, -98.4725 29.256621, -98.472504 29.256406, -98.472225 29.256388, -98.472073 29.256394, -98.471998 29.256397, -98.471468 29.256285, -98.470131 29.256294, -98.47014 29.256688, -98.470408 29.256683, -98.471696 29.25666, -98.472 29.256648, -98.472067 29.256645, -98.472227 29.256639, -98.472269 29.259388, -98.472267 29.259838, -98.472266 29.260193, -98.472262 29.261158, -98.472285 29.264769, -98.472321 29.267807, -98.472322 29.269617, -98.472354 29.273034, -98.472384 29.276594, -98.472393 29.277742, -98.472396 29.278097, -98.47242 29.279826, -98.47242 29.279902, -98.472423 29.281841, -98.472437 29.28357, -98.472446 29.284741, -98.472465 29.287314, -98.472474 29.288858, -98.472477 29.289462, -98.472481 29.291017, -98.472527 29.294043, -98.472531 29.294485, -98.472561 29.297973, -98.472573 29.298603, -98.472561 29.300438, -98.472578 29.301563, -98.472591 29.303589, -98.472651 29.304745, -98.472258 29.304774, -98.471729 29.303089, -98.47159 29.302676, -98.47113 29.301303, -98.471126 29.301166, -98.470976 29.300712, -98.470946 29.300615, -98.470847 29.300289, -98.47056 29.299349, -98.470028 29.29763, -98.469221 29.294725, -98.469142 29.294392, -98.469078 29.294091, -98.468579 29.290538, -98.468512 29.290184, -98.46842 29.289837, -98.468041 29.288753, -98.467872 29.288266, -98.466897 29.285463, -98.466688 29.284874, -98.466364 29.283887, -98.466102 29.283171, -98.465772 29.28221, -98.465185 29.280517, -98.465146 29.280388, -98.4651 29.280262, -98.464692 29.279116, -98.464467 29.278457, -98.464157 29.277552, -98.463708 29.276346, -98.463251 29.275154, -98.463039 29.274585, -98.462635 29.273558, -98.46236 29.272821, -98.462077 29.272004, -98.461947 29.271638, -98.461743 29.271062, -98.461911 29.271063, -98.462072 29.271084, -98.462344 29.271089, -98.462356 29.271013, -98.462366 29.270911, -98.462379 29.27069, -98.462386 29.270629, -98.46237 29.270542, -98.462373 29.270436, -98.462353 29.27026, -98.462341 29.269555, -98.46235 29.269503, -98.46236 29.269421, -98.462352 29.269186, -98.462351 29.268965, -98.462362 29.26821, -98.462357 29.268064, -98.462355 29.267817, -98.462361 29.26658, -98.462371 29.265101, -98.462377 29.264338, -98.462378 29.264318, -98.462383 29.263675, -98.462385 29.262631, -98.462386 29.262195, -98.462387 29.261245, -98.462387 29.261058, -98.461693 29.261045, -98.461201 29.261045, -98.460983 29.261052, -98.460981 29.261227, -98.460972 29.262191, -98.46097 29.262637, -98.460966 29.263665, -98.460963 29.263798, -98.460963 29.264333, -98.460963 29.265098, -98.460965 29.266575, -98.460965 29.268056, -98.460785 29.268057, -98.460731 29.268065, -98.460622 29.268117, -98.46094 29.268906, -98.45967 29.268871, -98.459901 29.269454, -98.460094 29.269967, -98.460105 29.270038, -98.460103 29.270073, -98.459336 29.270105, -98.459343 29.270326, -98.459397 29.270518, -98.459384 29.270646, -98.459358 29.270741, -98.459351 29.27106, -98.459355 29.271139, -98.459245 29.271138, -98.459125 29.271132, -98.459027 29.271116, -98.457355 29.270639, -98.453322 29.269488, -98.452721 29.269317, -98.451631 29.269006, -98.449835 29.268498, -98.44633 29.267487, -98.444638 29.267008, -98.443957 29.266808, -98.440502 29.265814, -98.439843 29.26562, -98.43968 29.26558, -98.439569 29.265558, -98.439432 29.26554, -98.439298 29.265528, -98.439131 29.265525, -98.43894 29.265532, -98.43872 29.265562, -98.438564 29.265593, -98.438406 29.265635, -98.438244 29.265689, -98.438089 29.265756, -98.437894 29.26586, -98.437724 29.265969, -98.437607 29.26606, -98.437481 29.26617, -98.437373 29.266287, -98.437274 29.266407, -98.437175 29.266553, -98.43553 29.269527, -98.435133 29.270233, -98.435054 29.270373, -98.434897 29.270634, -98.434648 29.270984, -98.434545 29.271117, -98.433708 29.272103, -98.432384 29.273638, -98.432257 29.273816, -98.432188 29.27392, -98.432095 29.274119, -98.432046 29.274262, -98.431996 29.274466, -98.431969 29.274669, -98.431968 29.274782, -98.43211 29.274789, -98.432273 29.274893, -98.432518 29.275086, -98.432593 29.275169, -98.432806 29.275295, -98.432888 29.275323, -98.43307 29.275356, -98.433139 29.275356, -98.433252 29.275317, -98.43329 29.275257, -98.433302 29.275185, -98.433221 29.274805, -98.433208 29.274684, -98.433208 29.274569, -98.433221 29.274514, -98.433265 29.274448, -98.433327 29.274398, -98.433516 29.27431, -98.43366 29.27431, -98.433779 29.274327, -98.434093 29.274459, -98.4342 29.274514, -98.434325 29.274596, -98.434601 29.274734, -98.43472 29.274783, -98.434827 29.2748, -98.434909 29.274794, -98.434978 29.2748, -98.435059 29.274838, -98.435109 29.274937, -98.435147 29.275097, -98.435097 29.275378, -98.435072 29.275455, -98.435034 29.275647, -98.435009 29.27573, -98.434833 29.276115, -98.434808 29.276192, -98.434777 29.276341, -98.434783 29.276544, -98.434915 29.27683, -98.434978 29.27688, -98.435072 29.27699, -98.435367 29.277298, -98.435536 29.277452, -98.435668 29.277639, -98.435712 29.277738, -98.435768 29.27781, -98.435982 29.278228, -98.436113 29.27864, -98.436138 29.278756, -98.436157 29.278948, -98.436207 29.279168, -98.436207 29.279278, -98.436189 29.279455, -98.436138 29.279598, -98.436132 29.279664, -98.436214 29.280016, -98.436465 29.280626, -98.436515 29.280846, -98.436571 29.280984, -98.436678 29.281171, -98.43691 29.281518, -98.437048 29.281853, -98.437143 29.282018, -98.437161 29.282139, -98.437199 29.282282, -98.437199 29.282453, -98.437161 29.282657, -98.437105 29.282789, -98.437017 29.282882, -98.436948 29.282921, -98.436873 29.282926, -98.436778 29.282921, -98.436735 29.28291, -98.436333 29.282904, -98.436151 29.282877, -98.436088 29.28286, -98.435887 29.282756, -98.435825 29.282706, -98.43568 29.282558, -98.435643 29.282492, -98.435567 29.282068, -98.435517 29.282007, -98.435448 29.281974, -98.435373 29.281958, -98.435304 29.281958, -98.435197 29.282002, -98.435159 29.28204, -98.435128 29.282079, -98.435109 29.282134, -98.43509 29.282249, -98.435097 29.282437, -98.43509 29.282657, -98.435147 29.28313, -98.435122 29.283284, -98.435078 29.283383, -98.434965 29.283551, -98.434814 29.283982, -98.434751 29.284252, -98.434733 29.284395, -98.434726 29.284654, -98.434707 29.284736, -98.434582 29.284929, -98.434525 29.285055, -98.434469 29.285242, -98.434474 29.285312, -98.434691 29.285223, -98.434868 29.285122, -98.435035 29.285028, -98.43511 29.285, -98.435192 29.284969, -98.435518 29.284846, -98.435612 29.284841, -98.43566 29.284832, -98.435708 29.284823, -98.435751 29.284805, -98.435797 29.284781, -98.435826 29.284765, -98.435875 29.28473, -98.435898 29.284711, -98.43591 29.284701, -98.435929 29.284685, -98.435978 29.284625, -98.43603 29.284516, -98.436105 29.284443, -98.436156 29.28442, -98.436231 29.284419, -98.436323 29.284437, -98.436419 29.284472, -98.436493 29.284509, -98.436592 29.284573, -98.436661 29.284628, -98.436757 29.284718, -98.436796 29.284766, -98.436848 29.284834, -98.436894 29.284909, -98.436933 29.284994, -98.436948 29.285081, -98.436943 29.285121, -98.436939 29.285175, -98.436927 29.285208, -98.436876 29.285313, -98.436843 29.285374, -98.436809 29.285428, -98.436781 29.285483, -98.436764 29.285542, -98.436772 29.285568, -98.436792 29.285639, -98.436823 29.285747, -98.436876 29.285793, -98.436966 29.285839, -98.437071 29.285908, -98.437142 29.285952, -98.437375 29.285958, -98.437607 29.285914, -98.437732 29.285908, -98.437896 29.285947, -98.437939 29.28598, -98.437971 29.286046, -98.437996 29.286271, -98.43809 29.286645, -98.438021 29.286706, -98.437758 29.286799, -98.437575 29.286816, -98.437469 29.286794, -98.437368 29.286788, -98.437312 29.286793, -98.437273 29.286842, -98.437087 29.287078, -98.436876 29.287511, -98.436829 29.287711, -98.436842 29.287728, -98.436986 29.287913, -98.437295 29.288124, -98.437394 29.288125, -98.437519 29.288092, -98.437563 29.288054, -98.437896 29.2879, -98.438159 29.287845, -98.438335 29.287817, -98.438529 29.287823, -98.438598 29.287839, -98.438711 29.2879, -98.438743 29.287966, -98.438749 29.288004, -98.43865 29.288238, -98.438684 29.288281, -98.438746 29.28865, -98.438806 29.288703, -98.438887 29.288736, -98.438969 29.288753, -98.439101 29.288808, -98.439308 29.288984, -98.439433 29.289039, -98.439684 29.289105, -98.43972 29.289123, -98.43976 29.289143, -98.439873 29.289248, -98.439916 29.289325, -98.440004 29.28955, -98.440055 29.289809, -98.440073 29.289864, -98.440149 29.290029, -98.440299 29.290282, -98.440356 29.290359, -98.440475 29.29048, -98.440657 29.290612, -98.440764 29.290662, -98.440827 29.290706, -98.440914 29.290827, -98.440918 29.290865, -98.440952 29.290986, -98.440958 29.291212, -98.440933 29.291267, -98.44087 29.291355, -98.440676 29.291564, -98.440563 29.291647, -98.440488 29.291685, -98.440366 29.291706, -98.440343 29.291727, -98.44032 29.29175, -98.440269 29.291807, -98.440202 29.291887, -98.440174 29.291927, -98.440139 29.291996, -98.440173 29.292045, -98.440219 29.29207, -98.440311 29.29212, -98.440395 29.29217, -98.440512 29.292262, -98.440527 29.292361, -98.440539 29.292441, -98.440493 29.292503, -98.440339 29.292713, -98.440249 29.292834, -98.440261 29.292863, -98.440281 29.292954, -98.440309 29.293061, -98.440353 29.29316, -98.440402 29.293257, -98.440455 29.293337, -98.440464 29.293373, -98.440476 29.293423, -98.44049 29.293492, -98.440486 29.293512, -98.440453 29.293613, -98.440427 29.293652, -98.440395 29.293687, -98.44038 29.293698, -98.440351 29.293725, -98.440326 29.293743, -98.440297 29.293764, -98.440273 29.293779, -98.440246 29.293798, -98.44021 29.293822, -98.440195 29.293827, -98.440166 29.293841, -98.440155 29.293847, -98.440112 29.293863, -98.440084 29.293873, -98.440045 29.293884, -98.440008 29.293892, -98.439984 29.293901, -98.439916 29.293934, -98.439862 29.293964, -98.439811 29.293981, -98.439771 29.29399, -98.439758 29.293988, -98.439685 29.293978, -98.439612 29.293967, -98.439543 29.29396, -98.439528 29.293968, -98.4395 29.293985, -98.439474 29.293999, -98.439417 29.294002, -98.439368 29.294004, -98.439335 29.294009, -98.439308 29.294016, -98.439297 29.294022, -98.439266 29.294036, -98.439237 29.29407, -98.439202 29.294109, -98.43918 29.294126, -98.439164 29.294147, -98.439149 29.294169, -98.439139 29.294182, -98.439131 29.294195, -98.439102 29.294255, -98.43909 29.294342, -98.439084 29.294391, -98.439083 29.294402, -98.439061 29.294473, -98.439054 29.294513, -98.439047 29.294556, -98.439053 29.294593, -98.439045 29.294627, -98.439033 29.294673, -98.439044 29.294725, -98.439063 29.294763, -98.43909 29.294813, -98.439099 29.294853, -98.439126 29.294915, -98.439168 29.294989, -98.439219 29.295071, -98.43929 29.295137, -98.439369 29.295193, -98.439415 29.295224, -98.439442 29.295253, -98.439464 29.295283, -98.439479 29.295299, -98.439486 29.295306, -98.439519 29.295355, -98.439553 29.2954, -98.439584 29.295452, -98.43959 29.295463, -98.439598 29.2955, -98.439617 29.295537, -98.439631 29.295565, -98.439654 29.295603, -98.439682 29.29564, -98.439698 29.295671, -98.439731 29.295717, -98.439747 29.295731, -98.439773 29.295769, -98.439788 29.295788, -98.439805 29.295819, -98.439824 29.295854, -98.43986 29.295933, -98.439871 29.295963, -98.439901 29.296015, -98.439919 29.296045, -98.439937 29.29608, -98.439967 29.296137, -98.439997 29.296184, -98.440049 29.296252, -98.440059 29.296262, -98.440081 29.296284, -98.440096 29.296306, -98.440104 29.296322, -98.440119 29.296343, -98.44013 29.296357, -98.440165 29.296398, -98.440184 29.296417, -98.4402 29.296429, -98.440243 29.296458, -98.44026 29.296472, -98.440289 29.296497, -98.440307 29.29651, -98.440347 29.296535, -98.440392 29.296558, -98.440477 29.296599, -98.440489 29.296605, -98.440508 29.296613, -98.440545 29.296631, -98.440563 29.296641, -98.440597 29.296656, -98.440624 29.296669, -98.440646 29.296679, -98.440669 29.296694, -98.440707 29.29671, -98.440746 29.296722, -98.440788 29.296738, -98.440821 29.296748, -98.440888 29.296765, -98.440943 29.29678, -98.440992 29.296794, -98.441116 29.296854, -98.441205 29.296897, -98.441217 29.296901, -98.44129 29.29693, -98.441336 29.296955, -98.441379 29.296986, -98.441416 29.297016, -98.441486 29.297049, -98.441548 29.297066, -98.441618 29.29711, -98.441693 29.297176, -98.441793 29.29733, -98.441844 29.297506, -98.441887 29.297715, -98.441881 29.297847, -98.441825 29.298128, -98.44183 29.298191, -98.44185 29.298419, -98.441865 29.298491, -98.441913 29.298521, -98.441955 29.298539, -98.442003 29.298554, -98.442047 29.298561, -98.442125 29.298572, -98.442204 29.298587, -98.442265 29.298596, -98.442351 29.29859, -98.442463 29.298565, -98.442605 29.298526, -98.442734 29.298492, -98.442792 29.298477, -98.442854 29.298465, -98.442904 29.298456, -98.44297 29.298446, -98.44304 29.298436, -98.443125 29.298419, -98.44319 29.298402, -98.443259 29.298387, -98.443427 29.298362, -98.443518 29.298357, -98.443589 29.298362, -98.443664 29.298371, -98.44375 29.29838, -98.443853 29.298377, -98.443896 29.298375, -98.444 29.298376, -98.444094 29.29838, -98.444173 29.298387, -98.444208 29.298391, -98.444293 29.298401, -98.444401 29.298409, -98.444444 29.298411, -98.444517 29.298439, -98.444623 29.298505, -98.444672 29.29855, -98.444688 29.298585, -98.4447 29.298606, -98.444712 29.298624, -98.444725 29.298639, -98.444737 29.298731, -98.444745 29.298795, -98.444779 29.298848, -98.444814 29.298909, -98.444827 29.298938, -98.444831 29.299024, -98.444834 29.299086, -98.444797 29.299203, -98.444779 29.29926, -98.444749 29.299481, -98.444756 29.299581, -98.444765 29.299699, -98.444764 29.299713, -98.444762 29.299754, -98.444768 29.299843, -98.444728 29.299951, -98.44475 29.300086, -98.444844 29.300273, -98.444919 29.300389, -98.444969 29.30051, -98.44502 29.300598, -98.445095 29.300669, -98.445158 29.300713, -98.445277 29.300763, -98.445409 29.300779, -98.445509 29.300807, -98.445698 29.300829, -98.445785 29.300818, -98.446036 29.300867, -98.446193 29.300917, -98.446294 29.300966, -98.446357 29.30101, -98.446413 29.301093, -98.446451 29.301192, -98.446451 29.30128, -98.446432 29.30139, -98.446407 29.301467, -98.446256 29.301671, -98.446156 29.30183, -98.446068 29.301902, -98.446003 29.301914, -98.445846 29.302226, -98.445805 29.302245, -98.445767 29.302263, -98.445674 29.302292, -98.445646 29.302296, -98.44559 29.302299, -98.445549 29.302294, -98.445534 29.302291, -98.445486 29.302285, -98.445325 29.302284, -98.445239 29.302279, -98.445207 29.302277, -98.445088 29.302246, -98.445048 29.302234, -98.445002 29.30222, -98.444954 29.302214, -98.44492 29.302214, -98.444888 29.302215, -98.444869 29.302216, -98.444831 29.302219, -98.444654 29.302113, -98.444574 29.302072, -98.444524 29.302386, -98.444518 29.302485, -98.444461 29.302672, -98.444317 29.302958, -98.444198 29.303085, -98.444129 29.30314, -98.444109 29.30318, -98.444085 29.303212, -98.444068 29.303235, -98.444035 29.303274, -98.443989 29.303319, -98.443948 29.303353, -98.443927 29.303373, -98.443869 29.303422, -98.443842 29.303449, -98.443819 29.303471, -98.443796 29.303496, -98.443787 29.303505, -98.443767 29.303521, -98.443667 29.303581, -98.443654 29.303589, -98.443579 29.303634, -98.44352 29.303669, -98.443511 29.303675, -98.443455 29.303709, -98.443425 29.303783, -98.443438 29.30397, -98.443469 29.304168, -98.443432 29.304334, -98.443407 29.304378, -98.443325 29.304466, -98.443287 29.304521, -98.443074 29.304735, -98.442942 29.304933, -98.442836 29.30506, -98.442781 29.305101, -98.442772 29.305189, -98.44277 29.305289, -98.442761 29.305348, -98.442721 29.305415, -98.442667 29.305454, -98.442613 29.305481, -98.442542 29.305505, -98.442474 29.305521, -98.442429 29.305531, -98.442392 29.305538, -98.442352 29.305543, -98.442312 29.305546, -98.442312 29.305556, -98.442237 29.305559, -98.44219 29.305556, -98.44209 29.305528, -98.442066 29.30552, -98.441985 29.305511, -98.441674 29.305638, -98.441561 29.305665, -98.441467 29.305676, -98.441399 29.305676, -98.441279 29.305676, -98.441224 29.305685, -98.441183 29.305719, -98.44107 29.305814, -98.440994 29.305925, -98.440981 29.305961, -98.440954 29.306089, -98.440951 29.306103, -98.440947 29.306134, -98.440947 29.306172, -98.440968 29.306259, -98.440979 29.306298, -98.441009 29.306369, -98.441166 29.306474, -98.441248 29.306501, -98.441348 29.306518, -98.441618 29.306644, -98.4418 29.306754, -98.442089 29.306969, -98.442158 29.307035, -98.44222 29.307134, -98.442239 29.307228, -98.442239 29.307387, -98.442277 29.307492, -98.442333 29.307563, -98.442421 29.307745, -98.442421 29.3078, -98.442377 29.307882, -98.442296 29.307954, -98.442026 29.308119, -98.441932 29.308157, -98.44185 29.308179, -98.441706 29.308179, -98.441599 29.308157, -98.441517 29.30813, -98.441398 29.308069, -98.441065 29.307866, -98.440827 29.307761, -98.440601 29.307706, -98.440494 29.30769, -98.440419 29.30769, -98.440312 29.307629, -98.440249 29.307607, -98.440124 29.307613, -98.439986 29.307662, -98.439848 29.307789, -98.439816 29.307871, -98.43981 29.307921, -98.439848 29.308025, -98.43991 29.308146, -98.439954 29.308196, -98.44003 29.308207, -98.440143 29.308278, -98.440369 29.308394, -98.440488 29.308466, -98.440727 29.308697, -98.44084 29.308774, -98.44099 29.3089, -98.441147 29.308977, -98.441354 29.30912, -98.441442 29.309164, -98.441511 29.309175, -98.441637 29.309148, -98.441706 29.309082, -98.441762 29.308983, -98.441781 29.3089, -98.441813 29.308642, -98.441819 29.308432, -98.441888 29.308339, -98.441969 29.308273, -98.442051 29.308223, -98.442277 29.308141, -98.442415 29.308146, -98.442534 29.308196, -98.442481 29.308231, -98.442675 29.30841, -98.442769 29.30864, -98.442743 29.308994, -98.442836 29.309111, -98.442862 29.30954, -98.442772 29.309888, -98.442751 29.309918, -98.442708 29.30998, -98.442483 29.310063, -98.442122 29.3103, -98.441625 29.310449, -98.441495 29.310603, -98.441415 29.310697, -98.441401 29.3107, -98.441253 29.311078, -98.441394 29.311288, -98.441879 29.311599, -98.442141 29.311693, -98.442385 29.311655, -98.442403 29.311711, -98.442403 29.311805, -98.442352 29.312064, -98.442258 29.31219, -98.44202 29.312278, -98.441449 29.312251, -98.441003 29.312295, -98.440839 29.312327, -98.44051 29.311807, -98.440106 29.31138, -98.439605 29.310836, -98.438863 29.310157, -98.438791 29.310094, -98.438574 29.309902, -98.438182 29.309556, -98.437814 29.309161, -98.43776 29.309111, -98.435582 29.307127, -98.435514 29.307045, -98.435203 29.306674, -98.434503 29.305773, -98.433592 29.304475, -98.433261 29.304033, -98.43238 29.302807, -98.432121 29.302506, -98.431875 29.30228, -98.43179 29.302201, -98.431065 29.3018, -98.430372 29.301521, -98.428834 29.301052, -98.426252 29.30032, -98.423647 29.299519, -98.423036 29.299233, -98.42229 29.298755, -98.421568 29.298147, -98.421389 29.297982, -98.420196 29.296892, -98.420026 29.29669, -98.41959 29.296289, -98.418497 29.295282, -98.4184 29.295182, -98.417604 29.294523, -98.41618 29.293522, -98.415487 29.293048, -98.414486 29.292363, -98.413527 29.291757, -98.412923 29.291488, -98.412272 29.29134, -98.411972 29.291295, -98.41216 29.291415, -98.412312 29.291508, -98.412627 29.291699, -98.414499 29.292884, -98.415078 29.29326, -98.415114 29.293283, -98.41607 29.293905, -98.416247 29.294022, -98.416683 29.294311, -98.417137 29.29463, -98.417387 29.294814, -98.417484 29.294891, -98.417758 29.295121, -98.418192 29.295501, -98.417525 29.296043, -98.417381 29.296176, -98.418135 29.296859, -98.418889 29.297552, -98.419178 29.297822, -98.419478 29.298099, -98.419578 29.298181, -98.419625 29.298199, -98.419665 29.298199, -98.419732 29.298182, -98.419799 29.298141, -98.420379 29.297661, -98.420467 29.297567, -98.421207 29.298209, -98.421367 29.298348, -98.421707 29.298623, -98.422001 29.298853, -98.422636 29.299306, -98.42281 29.299413, -98.422952 29.299492, -98.423111 29.299579, -98.423418 29.29971, -98.423626 29.299787, -98.424349 29.300008, -98.426752 29.300712, -98.429497 29.30153, -98.430019 29.301691, -98.430374 29.301822, -98.430641 29.301946, -98.430955 29.302147, -98.431102 29.302277, -98.431315 29.302508, -98.431388 29.302614, -98.431488 29.302791, -98.431574 29.302974, -98.43164 29.303133, -98.43168 29.303268, -98.431693 29.303403, -98.43169 29.303868, -98.431634 29.306726, -98.431609 29.308514, -98.431594 29.309684, -98.431598 29.310156, -98.431601 29.310225, -98.431665 29.310726, -98.431739 29.311215, -98.432083 29.313284, -98.432237 29.314199, -98.432487 29.314163, -98.432539 29.314162, -98.432631 29.314185, -98.432723 29.31422, -98.432812 29.314228, -98.432947 29.314222, -98.432994 29.314216, -98.433075 29.314198, -98.433364 29.314116, -98.433687 29.314033, -98.433838 29.314012, -98.433947 29.314018, -98.434025 29.314028, -98.434108 29.314042, -98.434382 29.314042, -98.434385 29.314528, -98.434392 29.315314, -98.435515 29.315316, -98.435516 29.315726, -98.435528 29.316498, -98.435525 29.317103, -98.436637 29.317094, -98.436721 29.317086, -98.436687 29.317157, -98.436671 29.317238, -98.436668 29.317303, -98.436689 29.317655, -98.436683 29.318395, -98.434019 29.318406, -98.432913 29.318411, -98.432918 29.318452, -98.432923 29.320412, -98.432922 29.322438, -98.432922 29.322683, -98.431948 29.3228, -98.4317 29.322839, -98.431332 29.322924, -98.430154 29.323273, -98.428942 29.323627, -98.426882 29.324237, -98.426243 29.324473, -98.425407 29.324204, -98.42455 29.323899, -98.424062 29.323707, -98.423222 29.323376, -98.422843 29.323206, -98.42101 29.322383, -98.420151 29.321994, -98.419953 29.322191, -98.419013 29.323762, -98.419059 29.323873, -98.419059 29.324079, -98.418917 29.324308, -98.418753 29.324455, -98.418505 29.324547, -98.418078 29.324539, -98.41776 29.324642, -98.417579 29.324865, -98.417332 29.325129, -98.4172 29.325336, -98.416892 29.325832, -98.416507 29.325601, -98.416292 29.325457, -98.416161 29.325388, -98.41603 29.325332, -98.41592 29.325294, -98.415621 29.32527, -98.413836 29.325235, -98.413321 29.325229, -98.412606 29.324181, -98.411476 29.322795, -98.409367 29.320288, -98.40847 29.319067, -98.40837 29.318916, -98.408086 29.318485, -98.407622 29.317718, -98.406846 29.316265, -98.40663 29.31586, -98.406428 29.31577, -98.406256 29.315694, -98.406458 29.316062, -98.407204 29.317424, -98.407793 29.31847, -98.408167 29.31901, -98.40858 29.319605, -98.409253 29.320486, -98.411145 29.322745, -98.412058 29.32391, -98.412997 29.325224, -98.413513 29.325991, -98.413802 29.326763, -98.413928 29.32723, -98.414104 29.328069, -98.414128 29.328577, -98.414117 29.328777, -98.414089 29.328981, -98.41404 29.329131, -98.413943 29.329364, -98.413861 29.329509, -98.413743 29.329699, -98.413605 29.329866, -98.413365 29.330183, -98.412903 29.330588, -98.41252 29.330917, -98.412288 29.331107, -98.411731 29.331563, -98.411722 29.331554, -98.411592 29.331469, -98.411483 29.331463, -98.411387 29.331463, -98.411298 29.331486, -98.411195 29.331552, -98.410981 29.331768, -98.410524 29.332231, -98.410273 29.332492, -98.409943 29.332835, -98.409318 29.333461, -98.408957 29.333817, -98.408369 29.334464, -98.407622 29.335328, -98.407073 29.336049, -98.406427 29.336961, -98.406033 29.337568, -98.405556 29.338394, -98.405279 29.338913, -98.404845 29.339809, -98.404325 29.341057, -98.403913 29.342257, -98.402976 29.345215, -98.401887 29.348633, -98.40179 29.348949, -98.401429 29.350129, -98.40099 29.351439, -98.400804 29.351893, -98.400541 29.352698, -98.400433 29.353061, -98.400322 29.353433, -98.400557 29.353805, -98.400528 29.353896, -98.400494 29.354002, -98.400255 29.353637, -98.400002 29.354353, -98.399864 29.354865, -98.399772 29.35543, -98.399405 29.356538, -98.39874 29.358684, -98.398394 29.359759, -98.398007 29.360967, -98.396541 29.365612, -98.394932 29.370581, -98.394801 29.370827, -98.394608 29.371232, -98.394447 29.371688, -98.394397 29.371861, -98.394307 29.372174, -98.394142 29.372096, -98.394025 29.372058, -98.393937 29.372029, -98.393711 29.371968, -98.392942 29.371826, -98.392388 29.37173, -98.391379 29.371554, -98.390569 29.371406, -98.390398 29.371357, -98.38635 29.369928, -98.381669 29.368261, -98.379427 29.367468, -98.376599 29.366484, -98.375977 29.366282, -98.375634 29.366199, -98.373835 29.365821, -98.373381 29.36572, -98.372258 29.365473, -98.371685 29.365346, -98.371576 29.36532, -98.369878 29.364928, -98.36959 29.364865, -98.369396 29.36483, -98.367854 29.364636, -98.367849 29.364229, -98.364241 29.363722, -98.364226 29.364056, -98.364213 29.364095, -98.364182 29.364122, -98.364119 29.364128, -98.364088 29.364111, -98.364069 29.364012, -98.364056 29.363891, -98.364069 29.36371, -98.364071 29.363697, -98.362559 29.363483, -98.360786 29.363232, -98.35967 29.362761, -98.35889 29.362409, -98.3576 29.361827, -98.357563 29.361814, -98.357274 29.361756, -98.35712 29.361713, -98.356999 29.361679, -98.356645 29.361601, -98.355846 29.361426, -98.355396 29.36133, -98.354903 29.361232, -98.353161 29.360854, -98.353153 29.360896, -98.350285 29.360266, -98.349637 29.360103, -98.348611 29.359867, -98.347576 29.359629, -98.346505 29.359387, -98.345956 29.359272, -98.345905 29.35926, -98.345915 29.359226, -98.345734 29.359188, -98.34554 29.359144, -98.345409 29.359114, -98.34536 29.359103, -98.345036 29.359029, -98.344594 29.358928, -98.344424 29.358889, -98.344262 29.358859, -98.343825 29.358778, -98.343376 29.358694, -98.343281 29.358675, -98.343223 29.358664, -98.34318 29.358655, -98.34317 29.358653, -98.34311 29.358639, -98.34146 29.358292, -98.341373 29.35827, -98.341295 29.358255, -98.340969 29.358183, -98.33926 29.357799, -98.339194 29.357787, -98.338088 29.357594, -98.336588 29.357338, -98.336504 29.357265, -98.33136 29.356328, -98.325437 29.355085, -98.319539 29.353744, -98.319614 29.35386, -98.319606 29.353875, -98.319002 29.353747, -98.31477 29.352835, -98.31451 29.352779, -98.314745 29.352918, -98.318419 29.353732, -98.319561 29.353956, -98.319633 29.35397, -98.319972 29.354036, -98.320216 29.35409, -98.320232 29.354007, -98.326171 29.355305, -98.328353 29.355763, -98.330869 29.356273, -98.331349 29.356365, -98.331439 29.356383, -98.333106 29.356704, -98.334726 29.357035, -98.338047 29.35773, -98.339796 29.358096, -98.342983 29.35874, -98.343174 29.358783, -98.34308 29.358854, -98.343046 29.35888, -98.343053 29.358935, -98.343102 29.35901, -98.343298 29.358899, -98.343497 29.358943, -98.343909 29.359034, -98.344544 29.359174, -98.345184 29.359316, -98.345415 29.359365, -98.348134 29.359951, -98.352407 29.360893, -98.354292 29.361308, -98.354269 29.361388, -98.354598 29.36145, -98.354746 29.361482, -98.35719 29.362299, -98.35969 29.363358, -98.360787 29.363934, -98.360811 29.368233, -98.360809 29.369315, -98.360807 29.370024, -98.36081 29.371732, -98.360822 29.374559, -98.360823 29.374786, -98.360833 29.376903, -98.360826 29.379111, -98.360826 29.379615, -98.361915 29.379606, -98.363978 29.379588, -98.366378 29.379622, -98.367041 29.379633, -98.367041 29.380918, -98.367599 29.38126, -98.367662 29.381278, -98.36772 29.381283, -98.367959 29.381281, -98.368253 29.381273, -98.370053 29.381277, -98.370051 29.38171, -98.370049 29.382145, -98.369263 29.382157, -98.36911 29.382158, -98.369028 29.382152, -98.36892 29.382136, -98.368795 29.382112, -98.368686 29.382096, -98.368594 29.382094, -98.368318 29.382098, -98.367676 29.382109, -98.367568 29.382108, -98.367465 29.382096, -98.367365 29.38208, -98.367294 29.382059, -98.367213 29.382028, -98.367123 29.381976, -98.366643 29.381672, -98.366538 29.381867, -98.366475 29.382061, -98.366499 29.384171, -98.366777 29.384171, -98.366955 29.384194, -98.36706 29.384232, -98.367147 29.384283, -98.367198 29.384322, -98.368041 29.384954, -98.368122 29.385014, -98.368399 29.385153, -98.368588 29.385212, -98.368682 29.385234, -98.368927 29.385264, -98.369264 29.385267, -98.370057 29.385273, -98.370047 29.385641, -98.370062 29.385856, -98.370097 29.386044, -98.370189 29.386318, -98.370297 29.386543, -98.370405 29.386795, -98.370456 29.387001, -98.370481 29.387202, -98.370482 29.388127, -98.370482 29.388747, -98.370466 29.388893, -98.370416 29.389109, -98.369593 29.388901, -98.369523 29.388882, -98.369436 29.388847, -98.369369 29.388811, -98.369301 29.38877, -98.369118 29.388641, -98.369004 29.388774, -98.368923 29.388865, -98.368882 29.388948, -98.368869 29.388994, -98.368865 29.389046, -98.368862 29.389191, -98.36886 29.389323, -98.368858 29.389514, -98.368853 29.390261, -98.368933 29.390256, -98.368989 29.390258, -98.369052 29.390264, -98.36912 29.390275, -98.369175 29.390287, -98.369967 29.390494, -98.369735 29.391178, -98.36952 29.391874, -98.369297 29.392563, -98.369071 29.393265, -98.368809 29.394101, -98.368723 29.394309, -98.368664 29.394424, -98.36851 29.394696, -98.368398 29.394893, -98.367419 29.394445, -98.365183 29.39341, -98.364716 29.393198, -98.363678 29.392728, -98.363176 29.392513, -98.362272 29.392165, -98.361789 29.392, -98.361133 29.39182, -98.360815 29.391726, -98.360507 29.391647, -98.360158 29.391558, -98.356853 29.390814, -98.355701 29.390555, -98.354639 29.390325, -98.353613 29.390091, -98.35298 29.389947, -98.351806 29.38968, -98.35093 29.389481, -98.350678 29.389427, -98.350365 29.389338, -98.350233 29.389294, -98.350079 29.389159, -98.349862 29.388901, -98.34971 29.388681, -98.349646 29.388582, -98.349499 29.38837, -98.349452 29.388315, -98.349413 29.388268, -98.349351 29.38823, -98.349272 29.388186, -98.34917 29.388152, -98.348404 29.387984, -98.348275 29.387959, -98.347147 29.387708, -98.347075 29.387705, -98.347022 29.387707, -98.346975 29.387721, -98.346917 29.387778, -98.34685 29.387884, -98.345692 29.387363, -98.345181 29.387162, -98.344704 29.386996, -98.344345 29.386881, -98.343841 29.386744, -98.343351 29.38662, -98.341966 29.386322, -98.340777 29.386058, -98.338226 29.385501, -98.338147 29.38577, -98.338109 29.385762, -98.337516 29.387836, -98.337065 29.389431, -98.336908 29.390023, -98.336575 29.391226, -98.336492 29.391524, -98.336217 29.39241, -98.335907 29.393497, -98.335818 29.393805, -98.335513 29.394868, -98.335061 29.396442, -98.33439 29.398703, -98.334378 29.398765, -98.334392 29.398817, -98.334416 29.398857, -98.334446 29.398878, -98.334592 29.39893, -98.334796 29.398982, -98.340687 29.400295, -98.341457 29.400457, -98.341483 29.400478, -98.341504 29.400494, -98.341515 29.400527, -98.341506 29.400576, -98.341278 29.401344, -98.340911 29.402604, -98.340763 29.403091, -98.340517 29.403902, -98.34027 29.40475, -98.340145 29.405184, -98.340105 29.405318, -98.339229 29.408271, -98.339231 29.408336, -98.339252 29.408424, -98.339271 29.408468, -98.339503 29.40818, -98.339816 29.407865, -98.339984 29.407719, -98.340178 29.407574, -98.340605 29.407298, -98.340865 29.407168, -98.342776 29.406189, -98.343195 29.40595, -98.34348 29.405754, -98.343703 29.405572, -98.344001 29.405295, -98.344186 29.405076, -98.34438 29.404821, -98.344522 29.404598, -98.344675 29.40431, -98.344777 29.404099, -98.344797 29.40405, -98.344862 29.403894, -98.344937 29.403606, -98.345332 29.402287, -98.345513 29.401596, -98.34575 29.400663, -98.34618 29.399172, -98.346266 29.398858, -98.346319 29.398667, -98.346591 29.397724, -98.347144 29.395855, -98.34768 29.39596, -98.349486 29.396316, -98.349543 29.396314, -98.349581 29.396302, -98.349613 29.39628, -98.349641 29.396237, -98.34986 29.395418, -98.349943 29.395166, -98.350094 29.394601, -98.350119 29.394481, -98.35012 29.394431, -98.350098 29.394378, -98.350053 29.394348, -98.349971 29.394323, -98.347663 29.393873, -98.347788 29.393315, -98.347834 29.393148, -98.348083 29.392253, -98.348383 29.391217, -98.348574 29.39056, -98.347067 29.390277, -98.346921 29.390274, -98.34713 29.389596, -98.347305 29.389047, -98.347428 29.388618, -98.347469 29.388475, -98.348988 29.389158, -98.349587 29.389379, -98.350048 29.389539, -98.350097 29.389553, -98.350526 29.389676, -98.350892 29.389759, -98.351746 29.389953, -98.353526 29.390358, -98.354596 29.390591, -98.355742 29.39084, -98.356764 29.391082, -98.357578 29.391268, -98.358251 29.391417, -98.359935 29.391789, -98.360484 29.391929, -98.360815 29.392014, -98.360814 29.392239, -98.360813 29.392434, -98.360807 29.393905, -98.360814 29.394175, -98.360813 29.398187, -98.36233 29.39815, -98.373 29.397897, -98.374428 29.397853, -98.374808 29.397958, -98.375321 29.398066, -98.37581 29.398142, -98.376337 29.398205, -98.377073 29.398239, -98.37756 29.398233, -98.378244 29.398207, -98.37874 29.398201, -98.379595 29.39819, -98.380795 29.398222, -98.381297 29.398236, -98.383836 29.39824, -98.383869 29.398241, -98.385871 29.398287, -98.386572 29.398312, -98.386676 29.398312, -98.387689 29.398308, -98.38782 29.398308, -98.388633 29.398305, -98.38863 29.398538, -98.38864 29.399192, -98.388645 29.400067, -98.388665 29.400579, -98.388724 29.400899, -98.388827 29.401248, -98.388883 29.401527, -98.38889 29.401781, -98.388891 29.40405, -98.388901 29.406181, -98.388903 29.408111, -98.388905 29.409695, -98.388889 29.415283, -98.38889 29.416031, -98.388894 29.419566, -98.388828 29.419997, -98.388687 29.420537, -98.388536 29.42119, -98.388493 29.421417, -98.388493 29.421671, -98.388502 29.421991, -98.388478 29.422507, -98.388407 29.422834, -98.388308 29.423086, -98.387931 29.422998, -98.386787 29.422732, -98.385982 29.422585, -98.385289 29.422474, -98.384461 29.42238, -98.383757 29.422332, -98.383592 29.422325, -98.382902 29.422295, -98.382666 29.422297, -98.37987 29.422325, -98.377519 29.422365, -98.377515 29.420736, -98.377931 29.420738, -98.377936 29.419962, -98.377939 29.419558, -98.37795 29.419201, -98.377952 29.418546, -98.377948 29.417885, -98.377945 29.417223, -98.376582 29.417223, -98.375822 29.417226, -98.375716 29.417231, -98.375616 29.417243, -98.375458 29.417293, -98.375376 29.41733, -98.374772 29.417745, -98.373974 29.418302, -98.373893 29.418369, -98.373817 29.41844, -98.373685 29.418568, -98.373618 29.418655, -98.373563 29.418748, -98.373404 29.419065, -98.373353 29.419146, -98.373235 29.419113, -98.373026 29.419083, -98.372936 29.419082, -98.372794 29.419111, -98.372658 29.4192, -98.372596 29.419269, -98.372357 29.419569, -98.372237 29.419771, -98.372123 29.419983, -98.372009 29.420171, -98.371891 29.420313, -98.371707 29.420509, -98.371563 29.420649, -98.371497 29.420806, -98.371472 29.420907, -98.371476 29.42235, -98.370775 29.422354, -98.37042 29.422347, -98.370346 29.422347, -98.370315 29.422347, -98.365727 29.422335, -98.36451 29.422341, -98.363754 29.422288, -98.363177 29.422203, -98.362637 29.422068, -98.36211 29.4219, -98.361545 29.421633, -98.360931 29.421299, -98.360737 29.42116, -98.360706 29.422248, -98.360688 29.424082, -98.360689 29.424932, -98.360825 29.42494, -98.360903 29.424944, -98.361159 29.424957, -98.362691 29.425033, -98.368108 29.424911, -98.370624 29.424854, -98.388285 29.423847, -98.388283 29.424249, -98.388392 29.425176, -98.388467 29.426063, -98.38853 29.426605, -98.388603 29.427735, -98.386763 29.427733, -98.384804 29.427728, -98.384808 29.427944, -98.384835 29.428243, -98.384855 29.428374, -98.384936 29.428739, -98.384956 29.428878, -98.384944 29.430996, -98.384939 29.432537, -98.384947 29.432854, -98.384941 29.434407, -98.384954 29.436857, -98.382593 29.437608, -98.382243 29.437729, -98.381641 29.437922, -98.381106 29.438094, -98.380727 29.438216, -98.380866 29.438599, -98.38096 29.438808, -98.377498 29.4399, -98.373059 29.441321, -98.369982 29.44228, -98.368027 29.44289, -98.364508 29.443988, -98.364103 29.444114, -98.362425 29.444665, -98.360822 29.445192, -98.360728 29.445222, -98.359061 29.445748, -98.359878 29.445941, -98.360727 29.446135, -98.360727 29.446413, -98.36073 29.447514, -98.359395 29.447944, -98.358239 29.448317, -98.357933 29.44835, -98.355035 29.449154, -98.345904 29.452017, -98.344465 29.452466, -98.344464 29.452487, -98.344483 29.452498, -98.344527 29.452498, -98.344562 29.452489, -98.344583 29.452492, -98.344583 29.45252, -98.344508 29.452558, -98.344496 29.45258, -98.344502 29.452635, -98.344521 29.452657, -98.344565 29.452674, -98.344602 29.452674, -98.34469 29.452652, -98.344697 29.452679, -98.344634 29.452795, -98.344577 29.452855, -98.344389 29.452905, -98.344326 29.452888, -98.344282 29.452844, -98.344064 29.452589, -98.343598 29.452738, -98.33978 29.45393, -98.339776 29.453858, -98.339774 29.453788, -98.339806 29.453584, -98.339823 29.453512, -98.339844 29.45344, -98.33987 29.453354, -98.339911 29.453225, -98.339933 29.453154, -98.339956 29.453083, -98.339973 29.453032, -98.339993 29.452953, -98.340016 29.452881, -98.340038 29.452828, -98.340061 29.452776, -98.340135 29.452588, -98.340161 29.452517, -98.340189 29.452442, -98.340215 29.45237, -98.340245 29.452236, -98.340241 29.452175, -98.340228 29.452116, -98.340199 29.452037, -98.340181 29.451988, -98.340127 29.451952, -98.33803 29.452629, -98.336712 29.453035, -98.335346 29.453468, -98.329277 29.455384, -98.328985 29.455479, -98.328976 29.45546, -98.328913 29.45535, -98.328882 29.455309, -98.326911 29.455938, -98.325271 29.456436, -98.321529 29.457624, -98.321503 29.457632, -98.32163 29.457337, -98.321743 29.457076, -98.32037 29.45744, -98.31974 29.457593, -98.317368 29.4582, -98.317318 29.458288, -98.317271 29.458409, -98.317218 29.458638, -98.317137 29.458917, -98.317023 29.459327, -98.316756 29.459396, -98.315632 29.459686, -98.314742 29.459926, -98.314307 29.460062, -98.313684 29.460212, -98.312409 29.460469, -98.310232 29.460956, -98.309784 29.461052, -98.306549 29.461917, -98.305088 29.462308, -98.305444 29.463297, -98.30476 29.463493, -98.304449 29.463576, -98.304355 29.463608, -98.304274 29.463645, -98.304209 29.46368, -98.304175 29.463722, -98.304147 29.463775, -98.304117 29.463904, -98.303837 29.464722, -98.303798 29.464846, -98.303575 29.465553, -98.303306 29.466379, -98.303036 29.467202, -98.302771 29.468011, -98.301829 29.467779, -98.301069 29.467589, -98.30102 29.467655, -98.300996 29.467732, -98.300976 29.467818, -98.300953 29.467909, -98.30094 29.467959, -98.30093 29.467998, -98.302919 29.468511, -98.303991 29.468951, -98.304189 29.468886, -98.305061 29.468659, -98.305197 29.468692, -98.305399 29.468747, -98.305511 29.468775, -98.306053 29.468908, -98.306058 29.468829, -98.30747 29.464512, -98.307934 29.463094, -98.308105 29.462571, -98.308729 29.462727, -98.309882 29.463004, -98.310169 29.463071, -98.310635 29.463187, -98.310816 29.462659, -98.310916 29.462634, -98.312947 29.462186, -98.314811 29.461772, -98.316399 29.461344, -98.316394 29.461358, -98.31623 29.461861, -98.315806 29.463164, -98.315596 29.46381, -98.314845 29.466125, -98.314326 29.467724, -98.314186 29.468159, -98.313372 29.470654, -98.312942 29.471969, -98.312874 29.472189, -98.312818 29.472446, -98.312812 29.472478, -98.312764 29.472785, -98.312749 29.473, -98.312748 29.473255, -98.312759 29.473465, -98.312781 29.47375, -98.312874 29.474149, -98.312926 29.474317, -98.31303 29.474573, -98.313115 29.474741, -98.313207 29.474939, -98.313594 29.475727, -98.313684 29.475953, -98.313775 29.476154, -98.313903 29.476593, -98.313916 29.47667, -98.31393 29.47676, -98.31396 29.476939, -98.313986 29.477239, -98.313978 29.477489, -98.313956 29.477796, -98.313914 29.478028, -98.313772 29.478514, -98.313562 29.479157, -98.3125 29.48248, -98.312228 29.483283, -98.312181 29.483421, -98.312108 29.483638, -98.311503 29.485516, -98.310673 29.488099, -98.310436 29.488836, -98.310369 29.489045, -98.310281 29.489352, -98.310234 29.489549, -98.310213 29.489694, -98.310198 29.489839, -98.3102 29.489922, -98.310201 29.490078, -98.310209 29.490245, -98.310228 29.490401, -98.31026 29.490541, -98.310325 29.49075, -98.310409 29.490971, -98.310657 29.49154, -98.310847 29.49197, -98.31112 29.492615, -98.311172 29.492801, -98.311198 29.492952, -98.311222 29.493231, -98.311222 29.493463, -98.31122 29.493619, -98.311192 29.49379, -98.311092 29.494139, -98.310919 29.494694, -98.310604 29.495706, -98.310093 29.497378, -98.309951 29.497845, -98.309557 29.499023, -98.30946 29.499324, -98.309247 29.498962, -98.309065 29.498791, -98.309009 29.498769, -98.308939 29.498681, -98.308782 29.498587, -98.308688 29.49862, -98.30838 29.498791, -98.308336 29.49878, -98.308153 29.498692, -98.307713 29.49856, -98.307386 29.498516, -98.307197 29.498444, -98.306889 29.498208, -98.306745 29.49807, -98.306575 29.497817, -98.306512 29.497773, -98.306424 29.497773, -98.306229 29.497856, -98.305669 29.498158, -98.305543 29.498175, -98.305449 29.498164, -98.305336 29.498065, -98.304877 29.497619, -98.304789 29.49747, -98.304795 29.497399, -98.30482 29.497344, -98.304915 29.497261, -98.30543 29.497041, -98.305896 29.496651, -98.306279 29.496552, -98.306361 29.496453, -98.306449 29.49631, -98.306449 29.496106, -98.30638 29.495875, -98.306317 29.495754, -98.306273 29.495694, -98.306229 29.495655, -98.306128 29.495606, -98.305908 29.495539, -98.305739 29.495512, -98.305625 29.495512, -98.305437 29.495539, -98.305267 29.495539, -98.305118 29.495506, -98.305097 29.495501, -98.304953 29.495479, -98.304715 29.495467, -98.304596 29.495825, -98.304454 29.496252, -98.304318 29.49669, -98.303059 29.496374, -98.297895 29.495076, -98.296544 29.494737, -98.296476 29.494649, -98.296447 29.494613, -98.296245 29.49436, -98.296697 29.492928, -98.293938 29.4923, -98.291085 29.491607, -98.291047 29.491598, -98.290981 29.491582, -98.290924 29.491565, -98.290804 29.491534, -98.290592 29.491479, -98.290586 29.491502, -98.290558 29.491608, -98.290523 29.49174, -98.290449 29.492017, -98.290135 29.49319, -98.289937 29.494062, -98.289754 29.494989, -98.289721 29.495203, -98.289624 29.49584, -98.289507 29.496649, -98.28935 29.497732, -98.288953 29.500298, -98.288729 29.501918, -98.288462 29.501697, -98.288434 29.501674, -98.288318 29.501621, -98.28797 29.501525, -98.286625 29.501206, -98.286603 29.501201, -98.284705 29.500716, -98.283167 29.500331, -98.281574 29.499934, -98.279179 29.499336, -98.278904 29.49927, -98.278805 29.499245, -98.278122 29.501349, -98.277191 29.504153, -98.280391 29.50494, -98.284272 29.505957, -98.284421 29.505881, -98.284767 29.505704, -98.285603 29.505278, -98.285877 29.505138, -98.288083 29.504014, -98.288466 29.503819, -98.288273 29.505172, -98.287574 29.50993, -98.287546 29.510229, -98.287527 29.510555, -98.287562 29.511253, -98.287632 29.511663, -98.287777 29.51227, -98.287897 29.512646, -98.288043 29.513007, -98.288067 29.513061, -98.288164 29.513276, -98.288179 29.513302, -98.288493 29.513861, -98.289072 29.514764, -98.288774 29.514923, -98.288333 29.514934, -98.288461 29.515152, -98.288603 29.515391, -98.288799 29.515753, -98.292449 29.521271, -98.294075 29.523741, -98.294306 29.524052, -98.294559 29.524344, -98.294786 29.524632, -98.294992 29.524924, -98.295773 29.526115, -98.297538 29.528822, -98.298139 29.529726, -98.298285 29.529957, -98.298414 29.530198, -98.298527 29.530472, -98.298618 29.530836, -98.298658 29.531073, -98.298675 29.531257, -98.298676 29.5314, -98.298736 29.53149, -98.298865 29.530923, -98.298925 29.530664, -98.29926 29.531161, -98.300569 29.533115, -98.300544 29.533198, -98.300463 29.533256, -98.300395 29.533347, -98.299959 29.533863, -98.299808 29.534028, -98.299654 29.534219, -98.299621 29.53426, -98.299516 29.534418, -98.299369 29.534572, -98.299191 29.534776, -98.299123 29.53488, -98.299107 29.534935, -98.29829 29.535529, -98.298178 29.535597, -98.298778 29.536525, -98.298824 29.536596, -98.298685 29.53664, -98.298014 29.536856, -98.297363 29.537776, -98.296056 29.539154, -98.294778 29.54016, -98.29337 29.540975, -98.292183 29.541661, -98.291227 29.542094, -98.290901 29.542199, -98.290393 29.542364, -98.289478 29.542524, -98.288018 29.543238, -98.287724 29.543414, -98.287521 29.543494, -98.287712 29.543397, -98.286874 29.542155, -98.286997 29.542136, -98.287235 29.542122, -98.287301 29.542121, -98.288086 29.542122, -98.28832 29.542083, -98.288339 29.542122, -98.288388 29.542291, -98.288395 29.542366, -98.28839 29.54248, -98.28858 29.542473, -98.288708 29.542455, -98.288773 29.542431, -98.289547 29.542053, -98.289626 29.541972, -98.289641 29.54191, -98.289648 29.540953, -98.28962 29.540853, -98.289592 29.54079, -98.289255 29.54029, -98.288209 29.538688, -98.288093 29.538531, -98.288033 29.538471, -98.288127 29.538411, -98.288357 29.538301, -98.289045 29.539334, -98.290185 29.538766, -98.288636 29.53642, -98.288068 29.535545, -98.287616 29.534865, -98.287002 29.533941, -98.287916 29.533461, -98.287714 29.533125, -98.287686 29.533063, -98.287658 29.533031, -98.287488 29.532813, -98.286282 29.530994, -98.285922 29.530428, -98.28456 29.528346, -98.283963 29.527441, -98.283386 29.52657, -98.282803 29.525694, -98.282292 29.524913, -98.281578 29.523855, -98.281195 29.523241, -98.281018 29.522984, -98.280968 29.522946, -98.280904 29.522927, -98.280826 29.522946, -98.279344 29.523706, -98.279276 29.523739, -98.278583 29.524097, -98.278461 29.524147, -98.278361 29.524158, -98.278239 29.524152, -98.277561 29.524023, -98.27749 29.524022, -98.27739 29.524041, -98.276802 29.524342, -98.274078 29.525701, -98.274652 29.526585, -98.27572 29.528224, -98.275269 29.528445, -98.274839 29.52866, -98.274422 29.528868, -98.273581 29.52761, -98.272756 29.526353, -98.2719 29.526787, -98.271132 29.527168, -98.271081 29.527215, -98.271029 29.527308, -98.270756 29.528165, -98.270749 29.528259, -98.270767 29.528351, -98.27081 29.528438, -98.271089 29.528862, -98.271925 29.530135, -98.272647 29.529778, -98.272743 29.529731, -98.273221 29.529488, -98.273734 29.530274, -98.27244 29.530917, -98.27302 29.531799, -98.273595 29.532674, -98.274195 29.533565, -98.27552 29.53558, -98.275733 29.53554, -98.27654 29.535142, -98.27674 29.535055, -98.276862 29.535012, -98.27697 29.534997, -98.277065 29.534989, -98.27703 29.534813, -98.276952 29.534572, -98.276918 29.534393, -98.277104 29.534588, -98.277269 29.534743, -98.277559 29.534991, -98.277907 29.535237, -98.277982 29.535271, -98.277612 29.535421, -98.277552 29.535445, -98.277441 29.53549, -98.277237 29.535591, -98.276108 29.536168, -98.275968 29.53624, -98.277139 29.538021, -98.278196 29.539629, -98.27888 29.540676, -98.278937 29.540758, -98.279064 29.540908, -98.279513 29.541291, -98.280094 29.540776, -98.280296 29.540629, -98.280504 29.540524, -98.280636 29.54073, -98.280683 29.540791, -98.280765 29.540818, -98.280853 29.540825, -98.282364 29.540811, -98.282527 29.541056, -98.282902 29.541636, -98.282962 29.541726, -98.283426 29.542433, -98.283451 29.54247, -98.283855 29.543086, -98.28399 29.543294, -98.282527 29.543307, -98.282479 29.543319, -98.282446 29.543349, -98.282433 29.54339, -98.282425 29.543447, -98.282849 29.544075, -98.282885 29.5441, -98.28296 29.544122, -98.283031 29.544133, -98.284551 29.544126, -98.284684 29.544341, -98.28474 29.544392, -98.284833 29.544429, -98.285012 29.544474, -98.285195 29.54448, -98.285398 29.544467, -98.285189 29.544578, -98.284952 29.544703, -98.284705 29.544827, -98.284234 29.545094, -98.283926 29.545217, -98.283436 29.545414, -98.282991 29.545685, -98.282617 29.545874, -98.282812 29.546206, -98.28286 29.546287, -98.282083 29.546643, -98.281985 29.546688, -98.281518 29.54691, -98.280212 29.547531, -98.276487 29.549217, -98.276426 29.549098, -98.27613 29.548511, -98.275544 29.54761, -98.274906 29.547957, -98.27345 29.548685, -98.273208 29.548331, -98.2732 29.548321, -98.272922 29.547955, -98.272545 29.547513, -98.272171 29.547082, -98.271202 29.545939, -98.271074 29.545797, -98.272909 29.544776, -98.273624 29.544267, -98.272939 29.543758, -98.270581 29.543034, -98.270189 29.542829, -98.269089 29.542229, -98.269017 29.542188, -98.266989 29.541029, -98.2662 29.54024, -98.265989 29.540029, -98.265389 29.540229, -98.266289 29.541429, -98.264956 29.542021, -98.263067 29.54124, -98.262428 29.540975, -98.262288 29.540931, -98.260537 29.540382, -98.260519 29.540376, -98.260313 29.541039, -98.259995 29.540898, -98.25978 29.540809, -98.259486 29.540688, -98.259123 29.540572, -98.257551 29.540099, -98.257095 29.539962, -98.256913 29.539898, -98.256557 29.539742, -98.256305 29.539623, -98.256098 29.539504, -98.2559 29.539372, -98.255722 29.539246, -98.25554 29.539107, -98.25519 29.538771, -98.255104 29.538681, -98.254852 29.538362, -98.254755 29.538215, -98.254515 29.537851, -98.25422 29.537402, -98.253294 29.53598, -98.252358 29.534553, -98.252146 29.534229, -98.251883 29.534361, -98.251496 29.534556, -98.251441 29.534583, -98.251105 29.534748, -98.250746 29.534927, -98.252302 29.537355, -98.253972 29.539958, -98.254063 29.540039, -98.25416 29.540094, -98.254312 29.540056, -98.254574 29.540036, -98.255024 29.540017, -98.255551 29.540437, -98.255377 29.540491, -98.255305 29.540438, -98.255191 29.54041, -98.255009 29.540465, -98.254802 29.540509, -98.254436 29.540652, -98.253882 29.540982, -98.253775 29.541109, -98.253599 29.54146, -98.253353 29.541917, -98.253064 29.542577, -98.252984 29.542879, -98.252956 29.542984, -98.252987 29.543165, -98.253021 29.54321, -98.252127 29.543667, -98.250291 29.544599, -98.247748 29.545889, -98.250473 29.550142, -98.250165 29.550277, -98.249995 29.550425, -98.249903 29.550453, -98.249545 29.55069, -98.2494 29.550805, -98.249319 29.550882, -98.249205 29.551058, -98.248929 29.551394, -98.24891 29.551433, -98.248885 29.551521, -98.248898 29.551746, -98.248929 29.551906, -98.249005 29.552115, -98.249062 29.552582, -98.248967 29.554037, -98.248931 29.554596, -98.248767 29.554976, -98.248566 29.555262, -98.248421 29.555377, -98.24815 29.555515, -98.247881 29.555653, -98.247384 29.555801, -98.247251 29.555873, -98.247075 29.555939, -98.24688 29.555923, -98.246698 29.555961, -98.246402 29.556, -98.246345 29.556, -98.246175 29.556044, -98.246043 29.556094, -98.24598 29.556105, -98.245818 29.556166, -98.245512 29.556282, -98.245471 29.556297, -98.245263 29.556402, -98.245232 29.556435, -98.245119 29.556501, -98.245068 29.556556, -98.244987 29.5566, -98.244848 29.556639, -98.244659 29.556672, -98.244597 29.556694, -98.244458 29.55671, -98.244383 29.556732, -98.244244 29.556798, -98.243974 29.556903, -98.243892 29.55692, -98.243709 29.556931, -98.243395 29.556991, -98.243162 29.557052, -98.243081 29.557055, -98.242992 29.557058, -98.242942 29.557025, -98.242873 29.556997, -98.24281 29.55692, -98.242734 29.556876, -98.242665 29.55686, -98.242438 29.556942, -98.242519 29.558314, -98.242543 29.558573, -98.242529 29.558646, -98.242438 29.558843, -98.242412 29.55891, -98.242417 29.559011, -98.242455 29.559203, -98.242499 29.559333, -98.242637 29.559509, -98.242677 29.559587, -98.242696 29.559661, -98.242737 29.560097, -98.241351 29.560214, -98.240882 29.560253, -98.23903 29.560378, -98.238999 29.560547, -98.238951 29.56071, -98.236362 29.560902, -98.232844 29.561179, -98.227099 29.561624, -98.226908 29.561332, -98.226968 29.561319, -98.227161 29.561277, -98.228497 29.561174, -98.230056 29.561063, -98.231222 29.560975, -98.232544 29.560863, -98.232862 29.560833, -98.233352 29.560811, -98.233688 29.560802, -98.233816 29.560805, -98.234204 29.560816, -98.234627 29.560817, -98.234664 29.560738, -98.234826 29.560495, -98.234859 29.560422, -98.234879 29.560343, -98.23486 29.560269, -98.234287 29.559416, -98.234236 29.559269, -98.234201 29.559142, -98.232677 29.558575, -98.231104 29.557986, -98.229806 29.557503, -98.229575 29.557434, -98.229285 29.557365, -98.228957 29.557312, -98.228687 29.557294, -98.228449 29.557292, -98.228159 29.557302, -98.227901 29.557334, -98.227637 29.557384, -98.227424 29.557433, -98.227004 29.557571, -98.226759 29.557683, -98.225838 29.558146, -98.224912 29.558612, -98.225496 29.559359, -98.226316 29.560422, -98.225559 29.560803, -98.224258 29.561473, -98.223996 29.561585, -98.223788 29.561659, -98.223603 29.561709, -98.223414 29.561739, -98.222688 29.561808, -98.220942 29.561941, -98.219145 29.5621, -98.218447 29.562157, -98.217876 29.562203, -98.21755 29.562216, -98.217491 29.562155, -98.217347 29.56185, -98.217237 29.561666, -98.21589 29.559638, -98.214558 29.557651, -98.213871 29.556608, -98.21263 29.557245, -98.21372 29.558868, -98.213627 29.558913, -98.213485 29.558949, -98.21334 29.559008, -98.213302 29.559027, -98.212996 29.559182, -98.212991 29.559257, -98.212999 29.559297, -98.213142 29.559502, -98.213193 29.559557, -98.213261 29.559607, -98.213292 29.55961, -98.213596 29.559455, -98.213983 29.559258, -98.214647 29.560276, -98.213896 29.560653, -98.213812 29.560694, -98.21376 29.560721, -98.213001 29.561108, -98.211071 29.562079, -98.210561 29.561388, -98.209952 29.561733, -98.208707 29.559866, -98.207265 29.560693, -98.208858 29.562906, -98.206574 29.563081, -98.205489 29.563138, -98.204723 29.563196, -98.204269 29.563229, -98.202435 29.563334, -98.201765 29.563386, -98.199494 29.559921, -98.199443 29.559861, -98.199393 29.55981, -98.199314 29.559768, -98.199263 29.559765, -98.199198 29.559793, -98.198398 29.560192, -98.197701 29.56054, -98.197636 29.560591, -98.19761 29.560628, -98.197599 29.560659, -98.197606 29.560705, -98.197647 29.560791, -98.198916 29.562723, -98.198943 29.562746, -98.198979 29.562762, -98.19902 29.56277, -98.199841 29.562704, -98.200016 29.562659, -98.200562 29.563485, -98.199005 29.56361, -98.197469 29.56377, -98.197035 29.563811, -98.196163 29.563893, -98.191309 29.564288, -98.189251 29.564463, -98.189012 29.564483, -98.188387 29.564533, -98.187839 29.564577, -98.185847 29.564732, -98.180718 29.565151, -98.178424 29.565342, -98.178151 29.565362, -98.175989 29.565542, -98.176042 29.565835, -98.176067 29.565877, -98.176094 29.56592, -98.17615 29.565969, -98.176226 29.565994, -98.176327 29.566007, -98.176428 29.566003, -98.177482 29.565916, -98.1778 29.565914, -98.177932 29.565924, -98.178049 29.565948, -98.178176 29.565962, -98.178387 29.56601, -98.178503 29.566043, -98.178604 29.566085, -98.178677 29.566128, -98.178803 29.566253, -98.178877 29.566351, -98.178966 29.56649, -98.18064 29.569001, -98.180739 29.569159, -98.181588 29.570439, -98.181948 29.570982, -98.181991 29.571047, -98.18194 29.571092, -98.181901 29.571122, -98.181842 29.571139, -98.181795 29.571166, -98.181733 29.5712, -98.181685 29.571222, -98.181609 29.571257, -98.181558 29.571278, -98.181506 29.571301, -98.181454 29.571321, -98.181399 29.57134, -98.181344 29.571357, -98.181292 29.571374, -98.181225 29.5714, -98.181174 29.571422, -98.181125 29.571446, -98.18108 29.571468, -98.181023 29.571494, -98.18094 29.571537, -98.180893 29.57156, -98.181895 29.572971, -98.181249 29.573283, -98.181608 29.573844, -98.18089 29.574156, -98.180244 29.57347, -98.179742 29.573657, -98.180316 29.574406, -98.179738 29.574695, -98.179554 29.574786, -98.179027 29.57505, -98.169698 29.579708, -98.167976 29.577275, -98.170272 29.575965, -98.172137 29.575716, -98.173524 29.574916, -98.17119 29.571174, -98.170439 29.571316, -98.16947 29.570039, -98.169398 29.570074, -98.169354 29.570016, -98.169314 29.569977, -98.169249 29.569941, -98.169184 29.569928, -98.169124 29.569923, -98.169054 29.569936, -98.16645 29.571264, -98.163968 29.57251, -98.162982 29.571001, -98.162055 29.569621, -98.161951 29.569465, -98.161687 29.569078, -98.161664 29.569013, -98.161653 29.568946, -98.161662 29.568881, -98.161678 29.56882, -98.161717 29.56877, -98.161767 29.56873, -98.161928 29.568644, -98.161873 29.56803, -98.161093 29.568155, -98.159053 29.568513, -98.153221 29.569521, -98.149881 29.570106, -98.150588 29.571891, -98.150551 29.571858, -98.150511 29.571831, -98.150465 29.571817, -98.150415 29.571811, -98.150365 29.571813, -98.150312 29.571827, -98.150247 29.571851, -98.149519 29.572228, -98.147831 29.573103, -98.146667 29.573707, -98.146558 29.573758, -98.146318 29.573841, -98.146246 29.573859, -98.146042 29.572928, -98.145831 29.572035, -98.14566 29.571316, -98.145624 29.571238, -98.145596 29.571196, -98.14556 29.571171, -98.145516 29.571149, -98.145448 29.571149, -98.145359 29.571159, -98.144221 29.571347, -98.143923 29.571396, -98.143875 29.571404, -98.142131 29.571701, -98.141762 29.571761, -98.141503 29.571791, -98.140788 29.571913, -98.140393 29.571986, -98.138658 29.572293, -98.138635 29.572169, -98.138604 29.571998, -98.138588 29.571913, -98.136856 29.57217, -98.136875 29.572263, -98.135161 29.572534, -98.13519 29.57277, -98.135221 29.572986, -98.135232 29.573082, -98.135285 29.573293, -98.135326 29.573531, -98.13539 29.573763, -98.135464 29.574059, -98.135558 29.574529, -98.135604 29.574696, -98.135703 29.575181, -98.135737 29.57535, -98.138478 29.57504, -98.138639 29.575647, -98.140811 29.57541, -98.140769 29.575187, -98.140788 29.575185, -98.140844 29.575177, -98.140896 29.575168, -98.140966 29.575157, -98.141014 29.575138, -98.141052 29.575125, -98.14113 29.5755, -98.141295 29.576274, -98.141435 29.576854, -98.141489 29.577052, -98.141543 29.577221, -98.141597 29.577365, -98.141648 29.577475, -98.141657 29.577494, -98.141696 29.577568, -98.141832 29.577769, -98.142358 29.578532, -98.143267 29.57989, -98.144287 29.581435, -98.144429 29.581646, -98.146407 29.58085, -98.146812 29.581542, -98.149246 29.580272, -98.148472 29.579403, -98.150819 29.57796, -98.15179 29.577424, -98.151876 29.57738, -98.15193 29.57735, -98.152401 29.577088, -98.152688 29.576934, -98.153599 29.576435, -98.153277 29.575953, -98.154305 29.575438, -98.161905 29.571552, -98.162139 29.572197, -98.162958 29.571697, -98.16369 29.572757, -98.161531 29.574108, -98.161919 29.57465, -98.162239 29.575171, -98.164179 29.574328, -98.164719 29.574883, -98.164563 29.574944, -98.164847 29.575486, -98.165663 29.57507, -98.165996 29.575585, -98.166671 29.576592, -98.166747 29.576711, -98.16716 29.577347, -98.167256 29.577483, -98.167389 29.577695, -98.168522 29.579417, -98.169504 29.580937, -98.173545 29.578905, -98.175558 29.577885, -98.177916 29.576711, -98.17829 29.576522, -98.178416 29.57698, -98.179099 29.576614, -98.179482 29.576408, -98.18191 29.575106, -98.183952 29.57401, -98.184055 29.574163, -98.184095 29.574202, -98.184146 29.574234, -98.184209 29.57426, -98.184256 29.574264, -98.184311 29.574261, -98.184377 29.574252, -98.184454 29.574233, -98.184531 29.574201, -98.186021 29.573432, -98.188961 29.571947, -98.189552 29.571647, -98.191838 29.570486, -98.191987 29.570729, -98.192142 29.57098, -98.192487 29.571523, -98.193261 29.572711, -98.193641 29.573283, -98.193883 29.573647, -98.19407 29.573928, -98.194108 29.573986, -98.19421 29.574139, -98.194266 29.574223, -98.194653 29.574806, -98.194706 29.574886, -98.195166 29.575578, -98.195423 29.575965, -98.195943 29.576748, -98.193975 29.577737, -98.192397 29.578541, -98.190252 29.579625, -98.190517 29.580021, -98.19083 29.580499, -98.190879 29.580579, -98.191422 29.581382, -98.19711 29.578504, -98.197166 29.578588, -98.198697 29.580899, -98.19882 29.581086, -98.198911 29.581223, -98.200398 29.58045, -98.200709 29.580294, -98.200622 29.58019, -98.200534 29.580014, -98.200528 29.57997, -98.200534 29.579926, -98.20061 29.57986, -98.200637 29.579812, -98.199207 29.577646, -98.195208 29.571589, -98.194993 29.571699, -98.194957 29.571702, -98.194917 29.571695, -98.194878 29.57167, -98.194842 29.571626, -98.194643 29.5713, -98.194122 29.570493, -98.193972 29.570261, -98.193751 29.569942, -98.193536 29.569652, -98.192774 29.570026, -98.191811 29.568617, -98.191248 29.567793, -98.189872 29.565706, -98.189579 29.565213, -98.189472 29.565032, -98.189357 29.56472, -98.197073 29.564086, -98.199012 29.563943, -98.201339 29.563746, -98.205563 29.5634, -98.208929 29.56315, -98.208895 29.563194, -98.208826 29.563232, -98.208493 29.563366, -98.208188 29.56351, -98.207901 29.563653, -98.207273 29.563973, -98.20432 29.565492, -98.204148 29.565582, -98.202713 29.566329, -98.201175 29.5671, -98.201644 29.567799, -98.201793 29.568024, -98.202017 29.568363, -98.203005 29.569874, -98.203883 29.571201, -98.204686 29.572412, -98.204733 29.572483, -98.205172 29.573155, -98.205298 29.573349, -98.205307 29.573364, -98.205848 29.574194, -98.206751 29.575582, -98.206864 29.575756, -98.207495 29.57673, -98.208002 29.577516, -98.208409 29.578129, -98.208484 29.578242, -98.208514 29.578286, -98.208657 29.578502, -98.209145 29.579237, -98.209457 29.579717, -98.209952 29.580466, -98.21007 29.580649, -98.210695 29.581615, -98.210978 29.582053, -98.211039 29.582148, -98.211458 29.581931, -98.212108 29.581594, -98.212175 29.58156, -98.212276 29.581526, -98.212312 29.58152, -98.212371 29.581523, -98.212443 29.581552, -98.212598 29.581656, -98.212879 29.582038, -98.212792 29.5821, -98.21252 29.582294, -98.212299 29.582452, -98.212183 29.582535, -98.212038 29.582638, -98.2128 29.58401, -98.211698 29.58461, -98.211705 29.584654, -98.211748 29.584889, -98.211808 29.585123, -98.211826 29.585176, -98.211875 29.585319, -98.211985 29.585567, -98.211991 29.585579, -98.212009 29.585612, -98.212073 29.585732, -98.210351 29.586521, -98.209673 29.586833, -98.209461 29.58693, -98.209236 29.586525, -98.208713 29.585581, -98.208385 29.585728, -98.207867 29.585968, -98.207144 29.586336, -98.206702 29.586485, -98.207496 29.587749, -98.207715 29.58843, -98.207768 29.58861, -98.207769 29.588828, -98.206382 29.589413, -98.20607 29.589668, -98.206007 29.589719, -98.206114 29.589877, -98.206585 29.590574, -98.206652 29.590695, -98.206673 29.590748, -98.206681 29.590798, -98.206677 29.590848, -98.206664 29.590896, -98.206629 29.590959, -98.206586 29.591006, -98.206488 29.591072, -98.207147 29.591688, -98.207398 29.59212, -98.207872 29.592939, -98.210759 29.591562, -98.21099 29.590858, -98.211197 29.590225, -98.211102 29.589979, -98.213775 29.588658, -98.213913 29.588569, -98.21416 29.588934, -98.214334 29.589193, -98.215208 29.590489, -98.215815 29.591399, -98.215909 29.591542, -98.216564 29.592528, -98.216771 29.592841, -98.216841 29.592947, -98.21775 29.594293, -98.218435 29.595308, -98.219997 29.597634, -98.220492 29.598367, -98.221954 29.600532, -98.222145 29.600819, -98.222811 29.601816, -98.223085 29.602227, -98.223242 29.602446, -98.223364 29.602607, -98.223518 29.602772, -98.223649 29.602893, -98.223798 29.60301, -98.223904 29.603092, -98.224258 29.603342, -98.224331 29.603394, -98.224366 29.603419, -98.224437 29.603469, -98.224687 29.603655, -98.224814 29.603768, -98.224859 29.603812, -98.224942 29.603891, -98.225086 29.604048, -98.225263 29.604268, -98.226023 29.605421, -98.226919 29.606778, -98.227072 29.60701, -98.227497 29.607669, -98.228107 29.608614, -98.228169 29.608709, -98.228316 29.608935, -98.229104 29.610153, -98.225993 29.611713, -98.225926 29.611623, -98.225254 29.61058, -98.224944 29.610098, -98.224715 29.609743, -98.224694 29.60971, -98.224535 29.609463, -98.22375 29.608952, -98.223541 29.609129, -98.219715 29.611899, -98.219764 29.611972, -98.220752 29.613424, -98.221146 29.614187, -98.22117 29.614233, -98.221224 29.614371, -98.221252 29.614413, -98.221036 29.614521, -98.219419 29.61533, -98.218873 29.615617, -98.218805 29.615516, -98.218737 29.615414, -98.216912 29.616345, -98.216653 29.616257, -98.215701 29.61665, -98.215857 29.616882, -98.214418 29.617628, -98.21346 29.618118, -98.213205 29.618263, -98.212967 29.618429, -98.212749 29.618614, -98.212347 29.618988, -98.21197 29.619339, -98.211885 29.619413, -98.211796 29.619483, -98.211702 29.619548, -98.211604 29.619609, -98.211503 29.619664, -98.211359 29.619732, -98.211385 29.619803, -98.211427 29.620004, -98.211426 29.620089, -98.211443 29.620201, -98.211493 29.620348, -98.211595 29.62056, -98.212 29.621419, -98.212031 29.621486, -98.21262 29.622656, -98.213197 29.623894, -98.213277 29.624085, -98.213296 29.624164, -98.213301 29.624192, -98.213306 29.624226, -98.213326 29.624461, -98.21332 29.624609, -98.213305 29.624669, -98.213279 29.624725, -98.21312 29.624961, -98.212931 29.625232, -98.214191 29.627134, -98.214546 29.627917, -98.214819 29.628519, -98.215138 29.629466, -98.215292 29.630329, -98.214881 29.631564, -98.214989 29.631525, -98.215377 29.630806, -98.215475 29.631349, -98.215567 29.631316, -98.215819 29.631216, -98.215976 29.631158, -98.216473 29.630975, -98.216499 29.630965, -98.217522 29.630588, -98.217542 29.630581, -98.217591 29.630563, -98.217604 29.630558, -98.219588 29.629826, -98.219632 29.629823, -98.219643 29.629822, -98.220085 29.629659, -98.220103 29.629652, -98.220153 29.629634, -98.220246 29.6296, -98.220279 29.629776, -98.220336 29.62992, -98.220435 29.630074, -98.220806 29.630655, -98.221942 29.632434, -98.222091 29.63264, -98.222158 29.632708, -98.222288 29.632817, -98.222438 29.632915, -98.22258 29.632981, -98.222629 29.633001, -98.222785 29.633064, -98.222867 29.633109, -98.222944 29.633165, -98.223156 29.63335, -98.222715 29.634017, -98.222557 29.634253, -98.228277 29.631337, -98.228931 29.631007, -98.230626 29.630133, -98.231407 29.62973, -98.235066 29.627875, -98.235193 29.627811, -98.239738 29.625491, -98.239457 29.625057, -98.239613 29.624979, -98.240574 29.624495, -98.241104 29.62426, -98.241649 29.624034, -98.242403 29.623736, -98.242825 29.623579, -98.243067 29.623475, -98.243184 29.623425, -98.243604 29.623234, -98.244011 29.623037, -98.244981 29.622545, -98.244191 29.621252, -98.24393 29.620904, -98.245942 29.620126, -98.247592 29.619489, -98.248406 29.619173, -98.250623 29.618314, -98.250657 29.618301, -98.253044 29.617377, -98.254715 29.616715, -98.25481 29.616677, -98.254844 29.616663, -98.255083 29.616568, -98.255342 29.616962, -98.25597 29.616612, -98.256565 29.616318, -98.256868 29.616786, -98.256941 29.616901, -98.257722 29.616514, -98.257999 29.616371, -98.258723 29.615982, -98.259138 29.615737, -98.259443 29.615553, -98.259921 29.615245, -98.260253 29.615015, -98.260686 29.614699, -98.261266 29.614246, -98.261402 29.614135, -98.261595 29.614062, -98.261948 29.613928, -98.261998 29.613909, -98.26239 29.613756, -98.267589 29.611727, -98.268089 29.611527, -98.273805 29.609052, -98.278465 29.607034, -98.2787 29.606936, -98.278726 29.606925, -98.278743 29.606918, -98.278762 29.60691, -98.278864 29.606868, -98.278955 29.60683, -98.27899 29.606815, -98.27901 29.606807, -98.279032 29.606798, -98.279051 29.60679, -98.279061 29.606786, -98.280032 29.606383, -98.280325 29.606262, -98.280354 29.60625, -98.280554 29.606167, -98.280658 29.606365, -98.280795 29.606617, -98.280869 29.606737, -98.281163 29.607297, -98.281368 29.607702, -98.281881 29.608646, -98.28206 29.608971, -98.282472 29.609748, -98.282993 29.61071, -98.283157 29.611025, -98.283508 29.611661, -98.283641 29.611913, -98.28381 29.612215, -98.28439 29.613171, -98.284559 29.613471, -98.284733 29.613779, -98.285342 29.614887, -98.285491 29.615158, -98.28554 29.615243, -98.285636 29.615406, -98.285866 29.615798, -98.286133 29.616274, -98.286551 29.616984, -98.286907 29.617575, -98.287065 29.617821, -98.287228 29.618069, -98.288109 29.619377, -98.288631 29.620142, -98.289201 29.620947, -98.28933 29.621129, -98.289564 29.621438, -98.290275 29.622422, -98.290751 29.62301, -98.293308 29.626841, -98.294024 29.627914, -98.294718 29.628976, -98.294869 29.629202, -98.295316 29.629869, -98.295893 29.630745, -98.296069 29.631021, -98.29637 29.631455, -98.296701 29.631973, -98.297248 29.632784, -98.297302 29.632867, -98.29839 29.634516, -98.299002 29.635429, -98.300839 29.638207, -98.301482 29.639165, -98.302089 29.6401, -98.303134 29.641834, -98.303863 29.64306, -98.304108 29.643508, -98.304159 29.643652, -98.304237 29.643875, -98.304304 29.644171, -98.304337 29.644409, -98.304354 29.644572, -98.304357 29.644916, -98.304186 29.644954, -98.304065 29.64498, -98.30399 29.644998, -98.30392 29.645023, -98.303853 29.64506, -98.303784 29.645116, -98.303687 29.645232, -98.303611 29.645362, -98.303559 29.645567, -98.303521 29.645727, -98.30337 29.646386, -98.303291 29.646703, -98.303205 29.647128, -98.303106 29.647489, -98.303042 29.647582, -98.30299 29.647649, -98.302934 29.647705, -98.302797 29.647807, -98.30239 29.648134, -98.302374 29.648147, -98.302159 29.648341, -98.301958 29.64851, -98.301872 29.648594, -98.301789 29.648705, -98.301667 29.648898, -98.301594 29.649049, -98.301559 29.649127, -98.301527 29.649203, -98.301454 29.649407, -98.301407 29.649521, -98.301375 29.649611, -98.301242 29.649571, -98.301196 29.649558, -98.301085 29.649525, -98.300956 29.649487, -98.300876 29.649463, -98.300805 29.649442, -98.30069 29.649408, -98.300525 29.649857, -98.300933 29.649884, -98.301333 29.649912, -98.301338 29.649849, -98.301358 29.649709, -98.301455 29.649724, -98.302502 29.649954, -98.302706 29.649986, -98.302533 29.650574, -98.302482 29.650861, -98.302471 29.651094, -98.302487 29.651495, -98.302492 29.651533, -98.302283 29.651556, -98.30227 29.651557, -98.30201 29.651586, -98.301922 29.651595, -98.302093 29.651834, -98.30214 29.651871, -98.302183 29.651895, -98.302292 29.651926, -98.302404 29.651926, -98.302562 29.651881, -98.302611 29.652037, -98.302673 29.652215, -98.302811 29.652569, -98.302939 29.652912, -98.303096 29.653314, -98.303571 29.653228, -98.30554 29.652902, -98.306023 29.65278, -98.306489 29.652589, -98.307019 29.652367, -98.3075 29.651972, -98.307838 29.651808, -98.308005 29.651779, -98.308151 29.651758, -98.30855 29.651736, -98.308685 29.651729, -98.30908 29.651659, -98.309516 29.651582, -98.310366 29.651279, -98.316483 29.649008, -98.317585 29.648493, -98.319347 29.647683, -98.320816 29.646952, -98.32239 29.646106, -98.322682 29.64597, -98.322908 29.64585, -98.323201 29.645686, -98.32334 29.645596, -98.323416 29.645531, -98.323626 29.645335, -98.323719 29.645227, -98.323832 29.645086, -98.323909 29.644981, -98.323988 29.644844, -98.324046 29.644725, -98.324089 29.644574, -98.324098 29.64451, -98.324095 29.644407, -98.324077 29.644308, -98.324413 29.644212, -98.324556 29.644008, -98.324611 29.643909, -98.324852 29.643501, -98.324939 29.64335, -98.325139 29.643319, -98.325301 29.643294, -98.32525 29.64309, -98.325116 29.642542, -98.325242 29.642503, -98.325375 29.642438, -98.325524 29.64235, -98.325753 29.642124, -98.325694 29.642084, -98.325247 29.641706, -98.325169 29.64164, -98.325618 29.641214, -98.326326 29.640264, -98.326418 29.640176, -98.326563 29.640022, -98.325953 29.639677, -98.325826 29.639603, -98.325772 29.639535, -98.325669 29.639724, -98.325585 29.639886, -98.325532 29.640012, -98.325331 29.640416, -98.325278 29.640496, -98.325162 29.640639, -98.325099 29.640704, -98.324987 29.640795, -98.32487 29.640888, -98.324792 29.640935, -98.324557 29.64104, -98.324441 29.641079, -98.324288 29.641113, -98.324086 29.641147, -98.32395 29.641184, -98.323856 29.641225, -98.323694 29.641308, -98.323557 29.641373, -98.323411 29.641449, -98.323202 29.641548, -98.322874 29.641717, -98.322099 29.642098, -98.32032 29.642997, -98.32021 29.64284, -98.319671 29.642021, -98.31886 29.640819, -98.318445 29.640184, -98.318389 29.640203, -98.317992 29.640093, -98.317784 29.639973, -98.317916 29.639842, -98.318082 29.63963, -98.317915 29.639383, -98.317428 29.638666, -98.316934 29.637925, -98.316547 29.637353, -98.316257 29.636896, -98.316042 29.636579, -98.315917 29.636396, -98.315447 29.635688, -98.315105 29.635162, -98.315518 29.634954, -98.316191 29.634616, -98.31657 29.634419, -98.317256 29.634079, -98.317424 29.634004, -98.317559 29.633944, -98.317668 29.633895, -98.318325 29.633571, -98.319021 29.633228, -98.319117 29.633181, -98.320214 29.632626, -98.32033 29.632577, -98.320563 29.632448, -98.321402 29.632036, -98.321757 29.631869, -98.318546 29.626778, -98.319229 29.626425, -98.319748 29.626159, -98.321677 29.625168, -98.323931 29.624026, -98.323462 29.622484, -98.323365 29.622146, -98.32318 29.621533, -98.323135 29.621441, -98.322756 29.62067, -98.322542 29.620235, -98.322519 29.620197, -98.322382 29.620266, -98.320872 29.621035, -98.319968 29.621494, -98.31939 29.621789, -98.317715 29.622641, -98.316858 29.623065, -98.316309 29.623338, -98.314661 29.62089, -98.313695 29.619455, -98.312941 29.618355, -98.311985 29.61696, -98.312398 29.616741, -98.312803 29.616528, -98.315013 29.615364, -98.315375 29.615168, -98.315521 29.615093, -98.316716 29.614475, -98.317078 29.614297, -98.317097 29.614287, -98.317774 29.613944, -98.317365 29.613342, -98.31709 29.611913, -98.318431 29.611583, -98.31854 29.611974, -98.3188 29.612846, -98.318876 29.613114, -98.318907 29.613204, -98.318983 29.613341, -98.319139 29.613264, -98.319764 29.612973, -98.31939 29.611311, -98.320371 29.611135, -98.320648 29.612146, -98.320754 29.612505, -98.320876 29.612451, -98.321675 29.612076, -98.321804 29.612018, -98.322542 29.611669, -98.32329 29.611326, -98.323655 29.611148, -98.324053 29.610933, -98.324341 29.610725, -98.32457 29.610537, -98.324739 29.610366, -98.324867 29.610222, -98.325077 29.609937, -98.325219 29.609701, -98.32527 29.6096, -98.325282 29.609576, -98.325362 29.609379, -98.325414 29.609232, -98.325491 29.608962, -98.325532 29.60881, -98.325887 29.608735, -98.328651 29.608233, -98.328647 29.60821, -98.328475 29.608003, -98.32833 29.607444, -98.328062 29.606419, -98.327993 29.606251, -98.327953 29.606152, -98.327876 29.605963, -98.327848 29.605905, -98.327817 29.605819, -98.327655 29.605683, -98.327514 29.605565, -98.327282 29.605371, -98.327262 29.605361, -98.327167 29.605273, -98.327064 29.605188, -98.326941 29.605088, -98.326862 29.605019, -98.326896 29.604984, -98.32707 29.604807, -98.327227 29.604668, -98.327408 29.604527, -98.327612 29.60439, -98.328008 29.604159, -98.328678 29.603832, -98.329153 29.603593, -98.334426 29.600936, -98.338203 29.599018, -98.338742 29.598727, -98.341763 29.596977, -98.34361 29.595919, -98.344145 29.595639, -98.344556 29.595415, -98.347221 29.594091, -98.35104 29.592187, -98.351323 29.592046, -98.351504 29.59231, -98.352139 29.59324, -98.352879 29.594339, -98.353413 29.595121, -98.353879 29.595707, -98.354459 29.596402, -98.354687 29.59672, -98.354894 29.597048, -98.355035 29.597327, -98.355122 29.597529, -98.355219 29.597702, -98.355338 29.597972, -98.355363 29.598023, -98.355512 29.598319, -98.355664 29.59855, -98.356307 29.599456, -98.356503 29.599745, -98.356645 29.599871, -98.356732 29.599909, -98.356919 29.599891, -98.357239 29.599666, -98.357352 29.599823, -98.357436 29.599941, -98.358034 29.600761, -98.358274 29.601021, -98.358616 29.601338, -98.358901 29.601573, -98.359345 29.601901, -98.359808 29.602201, -98.360384 29.602568, -98.360724 29.602743, -98.361025 29.602885, -98.361367 29.603022, -98.361721 29.603139, -98.361984 29.60321, -98.362434 29.603289, -98.362873 29.603339, -98.363261 29.603394, -98.365658 29.603364, -98.366232 29.603357, -98.366805 29.603339, -98.367102 29.60333, -98.36881 29.603295, -98.369959 29.603276, -98.370739 29.603194, -98.37152 29.603092, -98.372278 29.603018, -98.374596 29.602962, -98.376062 29.60294, -98.376937 29.602936, -98.378256 29.602912, -98.379265 29.602888, -98.379842 29.602875, -98.380364 29.602864, -98.380869 29.602866, -98.381232 29.602858, -98.382121 29.602911, -98.382868 29.602981, -98.38323 29.603002, -98.383886 29.603002, -98.38431 29.602996, -98.38483 29.602999, -98.38544 29.602985, -98.386041 29.60298, -98.386515 29.602982, -98.387235 29.602954, -98.387535 29.602948, -98.387854 29.602941, -98.387964 29.602939, -98.388504 29.602931, -98.388646 29.602929, -98.390391 29.602889, -98.391513 29.602858, -98.392129 29.602841, -98.393569 29.602656, -98.394063 29.602629, -98.395 29.602609, -98.39536 29.602587, -98.396039 29.602582, -98.396868 29.602554, -98.398183 29.602532, -98.398666 29.602515, -98.399465 29.602502, -98.400226 29.602484, -98.400865 29.602486, -98.401275 29.602472, -98.40183 29.602462, -98.402501 29.602427, -98.403027 29.602422, -98.403629 29.602369, -98.404325 29.602315, -98.405059 29.602244, -98.405242 29.602231, -98.407138 29.602102, -98.408139 29.60202, -98.408815 29.601919, -98.410113 29.601747, -98.411698 29.601638, -98.413895 29.601486, -98.414326 29.601479, -98.414732 29.601472, -98.415568 29.601436, -98.416029 29.60141, -98.417026 29.60134, -98.417827 29.601288, -98.417843 29.601276, -98.418363 29.60126, -98.418389 29.60145, -98.418411 29.601559, -98.418441 29.601676, -98.418468 29.601783, -98.418575 29.602122, -98.418681 29.602517, -98.418731 29.602702, -98.418757 29.602848, -98.418769 29.603001, -98.418771 29.603021, -98.418774 29.6032, -98.418769 29.603356, -98.418749 29.603614, -98.418696 29.60399, -98.418648 29.604251, -98.418596 29.604457, -98.418476 29.604845, -98.418399 29.605134, -98.418347 29.605364, -98.41831 29.6057, -98.418302 29.605855, -98.418327 29.606161, -98.418349 29.60631, -98.41839 29.606574, -98.418424 29.606732, -98.418474 29.606902, -98.418702 29.607531, -98.419168 29.608486, -98.420186 29.610228, -98.420342 29.610507, -98.42046 29.610736, -98.420562 29.610948, -98.420853 29.611714, -98.420884 29.611793, -98.420925 29.611923, -98.422271 29.616113, -98.422298 29.616203, -98.422342 29.61635, -98.422373 29.616452, -98.422421 29.616654, -98.422523 29.617355, -98.422604 29.617708, -98.42297 29.619565, -98.423041 29.620054, -98.42342 29.622864, -98.423491 29.623386, -98.423773 29.625541, -98.423795 29.625883, -98.4238 29.626203, -98.423774 29.626476, -98.423705 29.62682, -98.423687 29.626917, -98.423257 29.628526, -98.423135 29.629026, -98.423109 29.629247, -98.423108 29.629472, -98.423304 29.631024, -98.421927 29.631086, -98.42011 29.63117, -98.417535 29.631288, -98.417213 29.631321, -98.416983 29.631357, -98.416674 29.631396, -98.416331 29.631425, -98.416328 29.631345, -98.416308 29.631221, -98.416287 29.631136, -98.416235 29.630974, -98.416202 29.630896, -98.416162 29.630816, -98.416085 29.630692, -98.415934 29.63049, -98.415753 29.630597, -98.415657 29.630644, -98.41554 29.630679, -98.415488 29.630687, -98.415288 29.630696, -98.413478 29.630752, -98.413363 29.630749, -98.413294 29.630741, -98.413222 29.630723, -98.41316 29.6307, -98.413099 29.630672, -98.413038 29.630632, -98.412971 29.63058, -98.412632 29.630284, -98.412583 29.630262, -98.412528 29.630243, -98.412476 29.630234, -98.412221 29.630233, -98.412199 29.630731, -98.412198 29.630757, -98.41217 29.631369, -98.412128 29.63239, -98.412114 29.632717, -98.412123 29.632827, -98.412162 29.632979, -98.412167 29.633024, -98.412158 29.633188, -98.412128 29.633825, -98.412106 29.633985, -98.411986 29.634744, -98.411786 29.636002, -98.411767 29.636122, -98.411704 29.636517, -98.411566 29.63739, -98.416278 29.636459, -98.416287 29.63564, -98.416296 29.63482, -98.416305 29.634023, -98.417931 29.634039, -98.418086 29.634033, -98.418245 29.634021, -98.418429 29.633997, -98.418577 29.63397, -98.418728 29.633935, -98.418873 29.633895, -98.419037 29.633838, -98.419172 29.633781, -98.419461 29.633651, -98.419609 29.633596, -98.419907 29.63351, -98.420098 29.633477, -98.420228 29.633454, -98.420527 29.633428, -98.420649 29.633428, -98.420905 29.63344, -98.421097 29.63346, -98.421197 29.633477, -98.421414 29.633503, -98.421624 29.633515, -98.421806 29.633513, -98.421958 29.633499, -98.423642 29.633313, -98.423813 29.634429, -98.424 29.63581, -98.424009 29.635873, -98.424131 29.636778, -98.424339 29.638145, -98.42436 29.638244, -98.424419 29.638513, -98.424474 29.63877, -98.424672 29.639549, -98.424718 29.639768, -98.424783 29.640086, -98.424807 29.640247, -98.424834 29.640506, -98.424841 29.640868, -98.424832 29.64134, -98.42481 29.641836, -98.424652 29.641848, -98.424632 29.64185, -98.424646 29.641592, -98.42462 29.640547, -98.424589 29.640229, -98.423838 29.640297, -98.423283 29.640343, -98.422713 29.64039, -98.421897 29.640457, -98.421426 29.640497, -98.414279 29.641091, -98.413768 29.641138, -98.412273 29.641279, -98.411583 29.64135, -98.410708 29.641431, -98.410184 29.641482, -98.409515 29.641547, -98.408698 29.641626, -98.40766 29.641727, -98.40694 29.641797, -98.406625 29.641826, -98.406377 29.641846, -98.406143 29.641866, -98.405927 29.641883, -98.405843 29.64189, -98.40569 29.641917, -98.405786 29.641544, -98.406193 29.639925, -98.406393 29.639084, -98.406472 29.638753, -98.406514 29.638583, -98.406744 29.637511, -98.407398 29.634474, -98.406946 29.633781, -98.403203 29.627998, -98.402603 29.627155, -98.402337 29.626833, -98.399983 29.628021, -98.39846 29.62878, -98.394851 29.630601, -98.394856 29.630683, -98.394837 29.630793, -98.394913 29.631453, -98.394906 29.631723, -98.394957 29.631943, -98.395164 29.632455, -98.395391 29.632746, -98.395844 29.633181, -98.395995 29.633385, -98.396171 29.633528, -98.396303 29.633676, -98.396738 29.63437, -98.397015 29.63454, -98.39709 29.634623, -98.397147 29.634749, -98.397311 29.634909, -98.397417 29.63508, -98.397562 29.635201, -98.397827 29.635635, -98.397864 29.636252, -98.397833 29.636455, -98.397763 29.636785, -98.397574 29.637225, -98.397385 29.637775, -98.397284 29.63794, -98.396912 29.63805, -98.39627 29.63816, -98.395936 29.638435, -98.39576 29.638611, -98.395665 29.638771, -98.395565 29.638859, -98.395136 29.639469, -98.394657 29.639815, -98.394531 29.640003, -98.394386 29.640178, -98.394109 29.640619, -98.393901 29.641075, -98.393806 29.641455, -98.393838 29.641884, -98.393819 29.642236, -98.393875 29.642516, -98.394001 29.642792, -98.394114 29.642957, -98.394517 29.643314, -98.3948 29.643507, -98.395172 29.643705, -98.395814 29.643859, -98.396294 29.644073, -98.393245 29.643453, -98.390544 29.642946, -98.39051 29.643089, -98.390213 29.644903, -98.389955 29.64639, -98.389331 29.650079, -98.388785 29.653243, -98.388588 29.654482, -98.388392 29.655604, -98.388343 29.656029, -98.388231 29.656554, -98.388176 29.656738, -98.388107 29.656996, -98.388045 29.657132, -98.38759 29.65752, -98.387217 29.657802, -98.387083 29.657924, -98.386985 29.6581, -98.386939 29.658162, -98.386829 29.658267, -98.386681 29.658394, -98.386555 29.658524, -98.386409 29.658742, -98.386343 29.659031, -98.388377 29.660316, -98.390596 29.661717, -98.392154 29.661821, -98.39503 29.665206, -98.396049 29.665778, -98.397547 29.665778, -98.398746 29.664945, -98.399285 29.664425, -98.401143 29.663435, -98.401631 29.663234, -98.402282 29.662966, -98.402562 29.662916, -98.403517 29.66275, -98.403664 29.662725, -98.40378 29.662706, -98.403821 29.662696, -98.405038 29.662394, -98.406794 29.661884, -98.406836 29.661873, -98.407818 29.66199, -98.40814 29.662028, -98.40936 29.662175, -98.409513 29.662194, -98.409571 29.6622, -98.41205 29.662498, -98.412718 29.662504, -98.414149 29.662517, -98.416552 29.662539, -98.418228 29.662555, -98.418627 29.662573, -98.418601 29.662585, -98.418425 29.662582, -98.418245 29.662593, -98.418179 29.66259, -98.418026 29.662586, -98.417551 29.662584, -98.416553 29.662589, -98.41571 29.662592, -98.415091 29.662678, -98.413688 29.662855, -98.412898 29.662862, -98.41447 29.665988, -98.416207 29.669444, -98.41898 29.674964, -98.420075 29.677141, -98.420863 29.678707, -98.425031 29.678889, -98.425086 29.678891, -98.425044 29.678971, -98.425117 29.679008, -98.425224 29.679049, -98.425334 29.679105, -98.42545 29.679173, -98.425573 29.67925, -98.4257 29.67934, -98.425911 29.679529, -98.426125 29.679787, -98.426694 29.680586, -98.426596 29.680638, -98.425826 29.681058, -98.425699 29.681122, -98.425539 29.681197, -98.425454 29.681232, -98.425188 29.681338, -98.424786 29.681487, -98.424393 29.681636, -98.424273 29.681682, -98.424141 29.681738, -98.424025 29.681807, -98.423727 29.682022, -98.423539 29.682172, -98.423269 29.681784, -98.422823 29.681138, -98.423633 29.680723, -98.42373 29.680669, -98.423957 29.68053, -98.424365 29.680252, -98.424507 29.680141, -98.424634 29.680014, -98.424731 29.67989, -98.424901 29.679622, -98.424742 29.679545, -98.42463 29.679508, -98.42452 29.67948, -98.424398 29.679455, -98.424135 29.679429, -98.42394 29.679439, -98.423909 29.679494, -98.423872 29.679544, -98.423734 29.679671, -98.423506 29.679829, -98.423285 29.679966, -98.422999 29.680123, -98.422458 29.680395, -98.422344 29.68045, -98.422066 29.680573, -98.421722 29.680697, -98.421469 29.680771, -98.421238 29.680825, -98.421019 29.680862, -98.420842 29.680882, -98.420956 29.682451, -98.420581 29.682511, -98.420419 29.68254, -98.420261 29.682587, -98.420156 29.682649, -98.42022 29.682743, -98.420338 29.682877, -98.420536 29.683061, -98.420616 29.683136, -98.420361 29.683369, -98.420205 29.683509, -98.420083 29.683589, -98.419984 29.683638, -98.420678 29.68466, -98.420737 29.684737, -98.421447 29.685515, -98.421387 29.685558, -98.420737 29.686014, -98.420941 29.686243, -98.421028 29.68633, -98.421137 29.686425, -98.421222 29.686488, -98.421453 29.686625, -98.422398 29.687079, -98.422538 29.687158, -98.422642 29.687248, -98.422706 29.68734, -98.422966 29.687253, -98.423071 29.68719, -98.423259 29.687048, -98.423615 29.686805, -98.423785 29.686677, -98.423905 29.686584, -98.424034 29.686477, -98.424165 29.686353, -98.424351 29.68616, -98.424428 29.686069, -98.424512 29.685964, -98.424658 29.68575, -98.42469 29.685701, -98.424806 29.685486, -98.424844 29.685407, -98.425006 29.685044, -98.425069 29.684911, -98.42513 29.684812, -98.425184 29.684734, -98.425252 29.684649, -98.425308 29.684584, -98.4254 29.684493, -98.425469 29.684438, -98.425571 29.684365, -98.425866 29.684167, -98.427495 29.68314, -98.42761 29.683064, -98.42713 29.682392, -98.426379 29.682849, -98.425649 29.68332, -98.425158 29.683639, -98.425023 29.683727, -98.424923 29.6838, -98.424132 29.682877, -98.423642 29.682295, -98.423807 29.682171, -98.424316 29.681836, -98.424452 29.681751, -98.424547 29.681701, -98.424635 29.681659, -98.42473 29.681618, -98.425305 29.681403, -98.425442 29.681353, -98.425597 29.681285, -98.425731 29.681225, -98.425863 29.681163, -98.426019 29.681083, -98.42666 29.680731, -98.427095 29.681347, -98.428036 29.682668, -98.428337 29.683111, -98.428369 29.683158, -98.428476 29.683283, -98.42861 29.683427, -98.428759 29.683541, -98.428887 29.683629, -98.429105 29.683756, -98.429361 29.683868, -98.429504 29.683922, -98.42968 29.683968, -98.429854 29.684003, -98.430016 29.684023, -98.430192 29.684035, -98.43047 29.684029, -98.43067 29.684009, -98.431004 29.683928, -98.431441 29.68375, -98.431748 29.683618, -98.432019 29.683516, -98.43233 29.683447, -98.432648 29.68342, -98.433105 29.683453, -98.434327 29.683633, -98.435657 29.68383, -98.435806 29.68385, -98.435961 29.683865, -98.436099 29.683866, -98.436279 29.683856, -98.436492 29.683831, -98.436751 29.683776, -98.43688 29.683735, -98.437087 29.683647, -98.437255 29.683574, -98.437382 29.683502, -98.437514 29.683413, -98.437612 29.683341, -98.437807 29.683164, -98.438492 29.682353, -98.438578 29.682255, -98.438757 29.682086, -98.438836 29.682026, -98.438935 29.681953, -98.43918 29.681818, -98.439366 29.681738, -98.439535 29.681681, -98.439751 29.681632, -98.43999 29.681594, -98.440216 29.681581, -98.440495 29.681596, -98.440847 29.681631, -98.441118 29.681648, -98.44133 29.681641, -98.441512 29.681627, -98.441656 29.681635, -98.441793 29.681805, -98.441978 29.682071, -98.442074 29.68226, -98.44219 29.682529, -98.442594 29.683589, -98.442702 29.68392, -98.442765 29.684212, -98.442796 29.684669, -98.442815 29.684939, -98.442784 29.685279, -98.442615 29.686085, -98.442547 29.686444, -98.44252 29.686861, -98.442518 29.687496, -98.442498 29.687779, -98.442492 29.687861, -98.442119 29.689746, -98.442035 29.690027, -98.441991 29.690149, -98.441752 29.69076, -98.441568 29.691203, -98.441544 29.691261, -98.440327 29.694417, -98.440295 29.694471, -98.440221 29.694598, -98.440094 29.694769, -98.439947 29.694927, -98.439782 29.69507, -98.435963 29.698048, -98.435159 29.698673, -98.434748 29.698992, -98.43435 29.699303, -98.434449 29.699434, -98.434888 29.699428, -98.435052 29.699444, -98.435376 29.699517, -98.435868 29.699516, -98.43656 29.699126, -98.436613 29.699782, -98.436652 29.699928, -98.436708 29.70007, -98.43678 29.700206, -98.437135 29.700741, -98.43721 29.700895, -98.43729 29.70105, -98.437399 29.701355, -98.437457 29.701621, -98.437651 29.702832, -98.437667 29.702943, -98.437718 29.70346, -98.437727 29.703688, -98.437715 29.703868, -98.437651 29.705149, -98.437624 29.705624, -98.437627 29.705846, -98.437688 29.706214, -98.437834 29.706771, -98.437838 29.70684, -98.437829 29.70692, -98.43777 29.707049, -98.437572 29.70731, -98.43752 29.707447, -98.437508 29.707495, -98.437507 29.707618, -98.437557 29.707749, -98.437866 29.708211, -98.438393 29.708969, -98.438501 29.70921, -98.438523 29.709358, -98.438487 29.709472, -98.438454 29.709538, -98.438389 29.709632, -98.438203 29.709831, -98.437974 29.710086, -98.4377 29.710444, -98.437648 29.710538, -98.437567 29.710705, -98.437229 29.711504, -98.436955 29.712079, -98.436822 29.712393, -98.436772 29.712587, -98.436761 29.71265, -98.436778 29.712776, -98.43679 29.712834, -98.436813 29.712906, -98.436863 29.712981, -98.436959 29.713097, -98.437069 29.713181, -98.43713 29.713215, -98.437196 29.713244, -98.437265 29.713266, -98.439018 29.713568, -98.439246 29.713597, -98.439392 29.713601, -98.43949 29.713591, -98.439898 29.71349, -98.440138 29.713377, -98.440242 29.713306, -98.440325 29.71322, -98.440395 29.713103, -98.440483 29.712894, -98.44054 29.712803, -98.440608 29.712733, -98.440692 29.712677, -98.440787 29.712637, -98.441328 29.712539, -98.441449 29.712493, -98.441583 29.712415, -98.442003 29.711959, -98.442533 29.71139, -98.442745 29.711183, -98.442848 29.7111, -98.442979 29.711056, -98.443081 29.711033, -98.44321 29.711023, -98.443247 29.71102, -98.44379 29.711021, -98.443945 29.711001, -98.444346 29.710964, -98.444474 29.710949, -98.444607 29.710956, -98.44479 29.710978, -98.44492 29.711007, -98.445065 29.711046, -98.445224 29.71111, -98.445364 29.711225, -98.445756 29.711964, -98.445866 29.712124, -98.446014 29.712339, -98.446029 29.712361, -98.44607 29.712567, -98.446056 29.71282, -98.446146 29.713152, -98.446232 29.713341, -98.446411 29.713481, -98.446503 29.713527, -98.446724 29.713589, -98.44515 29.716648, -98.445102 29.716737, -98.444439 29.718002, -98.443779 29.719266, -98.44369 29.719436, -98.44346 29.719934, -98.443135 29.720555, -98.44242 29.721953, -98.44207 29.722723, -98.441856 29.723322, -98.441703 29.723861, -98.441139 29.726383, -98.441068 29.726699, -98.440635 29.728654, -98.440383 29.729749, -98.440318 29.730034, -98.439865 29.732078, -98.439581 29.733327, -98.439248 29.73482, -98.438994 29.735958, -98.439292 29.736009, -98.439551 29.734839, -98.439873 29.733381, -98.440115 29.732287, -98.440616 29.730087, -98.440649 29.729943, -98.440959 29.728581, -98.441126 29.727809, -98.441279 29.727149, -98.441363 29.726762, -98.441442 29.726395, -98.441514 29.726063, -98.441868 29.724479, -98.441968 29.724009, -98.44214 29.723352, -98.44232 29.722806, -98.442519 29.722327, -98.442691 29.721942, -98.443553 29.720235, -98.443852 29.71965, -98.444015 29.719334, -98.444697 29.718009, -98.445313 29.716816, -98.446208 29.71509, -98.446935 29.713677, -98.448158 29.7113, -98.448782 29.710099, -98.449872 29.70796, -98.450082 29.707442, -98.450221 29.707082, -98.450427 29.706424, -98.450582 29.705714, -98.450633 29.705371, -98.450805 29.70422, -98.450841 29.703978, -98.451077 29.702246, -98.451199 29.701355, -98.451548 29.698861, -98.451996 29.695657, -98.452271 29.693723, -98.452292 29.693578, -98.452591 29.693607, -98.452981 29.693692, -98.453192 29.69373, -98.453487 29.693879, -98.453677 29.694027, -98.453876 29.69424, -98.453991 29.694416, -98.454075 29.694573, -98.45409 29.694663, -98.454126 29.694878, -98.454232 29.695757, -98.454245 29.695859, -98.463414 29.695663, -98.46616 29.695604, -98.470877 29.695499, -98.475087 29.695157, -98.476668 29.695029, -98.477362 29.694485, -98.477472 29.694369, -98.477544 29.694264, -98.477603 29.694151, -98.477754 29.694213, -98.477843 29.694262, -98.47792 29.694318, -98.478397 29.694792, -98.478929 29.695319, -98.479041 29.695455, -98.479118 29.695621, -98.479208 29.696109, -98.479235 29.696282, -98.479234 29.696357, -98.479209 29.696446, -98.479164 29.696586, -98.479141 29.696694, -98.479158 29.696824, -98.479189 29.696893, -98.479214 29.696925, -98.479457 29.697239, -98.479479 29.697266, -98.479523 29.697358, -98.479535 29.69741, -98.479596 29.69774, -98.479743 29.698539, -98.479932 29.699561, -98.480079 29.700353, -98.4795 29.700535, -98.479653 29.700903, -98.479833 29.701332, -98.480392 29.701156, -98.480585 29.701891, -98.48065 29.702114, -98.480664 29.702176, -98.480664 29.702376, -98.480653 29.702452, -98.481099 29.702445, -98.481155 29.702445, -98.481269 29.702429, -98.481378 29.702527, -98.481466 29.702626, -98.48151 29.702703, -98.481536 29.702934, -98.481517 29.703039, -98.481466 29.703143, -98.481359 29.703297, -98.48129 29.703418, -98.481306 29.703527, -98.481387 29.703798, -98.481486 29.703875, -98.481574 29.70393, -98.481984 29.704122, -98.482078 29.704205, -98.48211 29.704276, -98.482141 29.704408, -98.482261 29.704606, -98.482293 29.704722, -98.482293 29.704914, -98.482349 29.705013, -98.482488 29.705123, -98.482576 29.705178, -98.482595 29.705255, -98.48217 29.70571, -98.48202 29.70577, -98.481251 29.705219, -98.480529 29.704702, -98.479019 29.703574, -98.476203 29.705112, -98.475199 29.705649, -98.470022 29.70868, -98.47029 29.709167, -98.471258 29.710928, -98.468217 29.713272, -98.468612 29.713467, -98.468908 29.713618, -98.469311 29.713823, -98.469544 29.71392, -98.469582 29.713967, -98.469657 29.713994, -98.469549 29.714155, -98.471609 29.715173, -98.471866 29.715265, -98.472106 29.715309, -98.472314 29.715324, -98.472545 29.715322, -98.47273 29.715298, -98.472911 29.715256, -98.473086 29.715199, -98.473223 29.715139, -98.473254 29.715125, -98.475604 29.713965, -98.47939 29.712031, -98.48407 29.709641, -98.484183 29.709805, -98.487062 29.708281, -98.488651 29.708272, -98.488694 29.708272, -98.492356 29.70832, -98.492496 29.70832, -98.494682 29.708294, -98.49844 29.70825, -98.499304 29.70824, -98.499304 29.708212, -98.504841 29.7082, -98.506262 29.708197, -98.508791 29.708191, -98.50972 29.708189, -98.510904 29.708187, -98.512787 29.708186, -98.512887 29.708186, -98.512894 29.707997, -98.512915 29.707879, -98.51295 29.707765, -98.513015 29.707668, -98.513098 29.70757, -98.513278 29.707425, -98.51344 29.707323, -98.513629 29.707245, -98.513801 29.707183, -98.514075 29.707112, -98.514368 29.707036, -98.514585 29.706997, -98.514855 29.706967, -98.514938 29.70696, -98.515044 29.706952, -98.515314 29.706961, -98.515891 29.707025, -98.516312 29.707067, -98.516546 29.707107, -98.517322 29.707273, -98.517677 29.707314, -98.517336 29.705627, -98.518352 29.705471, -98.518458 29.70545, -98.518561 29.705418, -98.518659 29.705378, -98.518752 29.705329, -98.518839 29.705271, -98.518918 29.705206, -98.518989 29.705135, -98.519051 29.705057, -98.519104 29.704973, -98.519146 29.704886, -98.519178 29.704795, -98.519198 29.704702, -98.519207 29.704607, -98.519205 29.704512, -98.519191 29.704418, -98.518386 29.701608, -98.517921 29.697937, -98.517931 29.697749, -98.518046 29.69706, -98.521232 29.697441, -98.520838 29.699948, -98.52082 29.700177, -98.520839 29.700202, -98.520819 29.700268, -98.521204 29.701205, -98.521161 29.70093, -98.521115 29.700684, -98.521105 29.700253, -98.52113 29.699915, -98.521264 29.698921, -98.521326 29.698173, -98.521543 29.697046, -98.521772 29.695694, -98.521892 29.69513, -98.522 29.694741, -98.522096 29.69427, -98.522192 29.693706, -98.522241 29.693266, -98.522314 29.692897, -98.522316 29.692456, -98.52234 29.69222, -98.522436 29.691739, -98.522643 29.690325, -98.522774 29.689731, -98.522787 29.689515, -98.522823 29.689321, -98.522836 29.689034, -98.522827 29.688572, -98.522796 29.687598, -98.522811 29.686932, -98.522754 29.686665, -98.52272 29.686265, -98.522697 29.686132, -98.522664 29.685824, -98.52263 29.685435, -98.522562 29.68486, -98.522484 29.68406, -98.522439 29.683712, -98.52244 29.683394, -98.522383 29.682973, -98.522385 29.682625, -98.52241 29.682256, -98.522424 29.681928, -98.522449 29.681538, -98.522533 29.68121, -98.522676 29.680668, -98.522783 29.680289, -98.522867 29.679971, -98.522962 29.679746, -98.523033 29.679623, -98.523043 29.679568, -98.523105 29.679234, -98.523189 29.678845, -98.52325 29.678322, -98.523263 29.678045, -98.523229 29.677748, -98.523242 29.677471, -98.523187 29.676887, -98.523152 29.676784, -98.523084 29.676179, -98.523006 29.675235, -98.522849 29.673758, -98.522715 29.672353, -98.522652 29.671769, -98.522647 29.671728, -98.522637 29.671431, -98.522442 29.670507, -98.522361 29.670281, -98.52221 29.669953, -98.522071 29.669604, -98.521768 29.669018, -98.521512 29.668546, -98.521233 29.667991, -98.521024 29.667591, -98.520896 29.667303, -98.520745 29.666913, -98.52063 29.666554, -98.520526 29.666153, -98.520434 29.665733, -98.520389 29.665343, -98.520391 29.664892, -98.520405 29.664369, -98.520395 29.663949, -98.520408 29.663703, -98.520469 29.663334, -98.520912 29.661377, -98.521116 29.660384, -98.521392 29.659072, -98.521525 29.658376, -98.521609 29.657863, -98.521606 29.657728, -98.5216 29.657478, -98.521599 29.657453, -98.521603 29.656551, -98.521604 29.656479, -98.521571 29.655967, -98.521468 29.655443, -98.5214 29.654859, -98.521285 29.654387, -98.521158 29.653987, -98.520996 29.653566, -98.520694 29.652847, -98.520462 29.652354, -98.520136 29.651779, -98.51981 29.651224, -98.519525 29.650764, -98.519298 29.650348, -98.519118 29.649907, -98.51902 29.64965, -98.518818 29.648834, -98.518762 29.64827, -98.518688 29.647844, -98.51865 29.647055, -98.518606 29.646619, -98.518566 29.646286, -98.518521 29.645793, -98.518517 29.645517, -98.518531 29.645014, -98.51858 29.644604, -98.518694 29.644097, -98.518783 29.6438, -98.518967 29.643376, -98.519015 29.643191, -98.519092 29.642992, -98.519223 29.642633, -98.519377 29.642254, -98.519722 29.64122, -98.519805 29.640995, -98.519989 29.640575, -98.520072 29.640319, -98.520091 29.640017, -98.520122 29.639745, -98.520124 29.639269, -98.520097 29.638858, -98.520103 29.638761, -98.520051 29.638556, -98.519915 29.637612, -98.519729 29.636038, -98.519627 29.635279, -98.519598 29.635094, -98.519605 29.634981, -98.519623 29.634889, -98.519647 29.634812, -98.519654 29.634738, -98.519639 29.634705, -98.519553 29.634599, -98.519459 29.634531, -98.519289 29.634487, -98.51923 29.633785, -98.519109 29.631541, -98.51893 29.628769, -98.518876 29.627706, -98.518823 29.626925, -98.523038 29.626929, -98.528662 29.626934, -98.532089 29.626937, -98.534586 29.626938, -98.536897 29.626941, -98.539005 29.626944, -98.550845 29.626956, -98.550835 29.629297, -98.550835 29.629315, -98.550858 29.629315, -98.550992 29.629315, -98.550991 29.629351, -98.553161 29.62937, -98.557243 29.629408, -98.55965 29.629432, -98.559652 29.629327, -98.559692 29.629055, -98.559742 29.628703, -98.55961 29.628081, -98.559522 29.627905, -98.559459 29.62768, -98.559175 29.627091, -98.559175 29.626876, -98.559238 29.626453, -98.55922 29.626255, -98.559062 29.625677, -98.558716 29.625, -98.558425 29.624628, -98.558098 29.624304, -98.557896 29.624045, -98.557814 29.62383, -98.557317 29.623137, -98.557015 29.622862, -98.556782 29.622686, -98.55648 29.622433, -98.556436 29.622367, -98.556423 29.62202, -98.556429 29.621646, -98.556587 29.62081, -98.556744 29.620304, -98.557028 29.619534, -98.557311 29.618824, -98.557544 29.618477, -98.557784 29.618162, -98.558023 29.61785, -98.558098 29.617724, -98.558186 29.617619, -98.558255 29.61741, -98.558337 29.616964, -98.558375 29.61675, -98.558419 29.615974, -98.5584 29.615732, -98.55835 29.614621, -98.558218 29.613735, -98.558029 29.613174, -98.557891 29.612646, -98.557787 29.612427, -98.5575 29.611826, -98.557236 29.611479, -98.557072 29.611237, -98.556405 29.610714, -98.55592 29.610373, -98.555586 29.610109, -98.554881 29.609741, -98.554201 29.609311, -98.553547 29.608849, -98.553257 29.60869, -98.552955 29.608492, -98.55281 29.608354, -98.552445 29.608156, -98.552105 29.608013, -98.551889 29.607919, -98.551658 29.60782, -98.55152 29.607738, -98.551369 29.607556, -98.551406 29.607292, -98.551551 29.607056, -98.551759 29.606764, -98.552036 29.606549, -98.552445 29.606423, -98.553786 29.605587, -98.554158 29.605125, -98.554403 29.604591, -98.554429 29.604222, -98.554277 29.604002, -98.554038 29.603914, -98.553956 29.603876, -98.553642 29.603777, -98.553478 29.60376, -98.552893 29.603771, -98.552049 29.603843, -98.551904 29.603837, -98.551589 29.603892, -98.551149 29.603931, -98.55045 29.603958, -98.550211 29.60403, -98.550015 29.604151, -98.549789 29.604255, -98.549524 29.604299, -98.549222 29.604288, -98.548605 29.604194, -98.548442 29.6042, -98.548108 29.604183, -98.547333 29.604183, -98.546931 29.604128, -98.546547 29.604024, -98.54615 29.603963, -98.545986 29.60393, -98.545678 29.603941, -98.545212 29.603864, -98.5444 29.603759, -98.54428 29.603732, -98.543481 29.603434, -98.542725 29.603297, -98.542146 29.603143, -98.541661 29.603066, -98.541511 29.602984, -98.541521 29.602414, -98.541527 29.602112, -98.540503 29.602174, -98.540478 29.602108, -98.540452 29.601961, -98.540434 29.601861, -98.540379 29.601736, -98.540321 29.601607, -98.540239 29.601503, -98.540176 29.601426, -98.539635 29.601041, -98.539547 29.600914, -98.53944 29.600771, -98.539389 29.600485, -98.539408 29.600314, -98.539419 29.600276, -98.539541 29.599858, -98.53973 29.59928, -98.539761 29.598785, -98.539761 29.598312, -98.539797 29.597813, -98.539869 29.596826, -98.539944 29.595985, -98.540039 29.595638, -98.540058 29.595319, -98.540008 29.595077, -98.540121 29.594417, -98.540216 29.594131, -98.540455 29.593784, -98.540505 29.593597, -98.540524 29.593229, -98.540707 29.592706, -98.540795 29.592321, -98.540921 29.591881, -98.540927 29.591864, -98.541123 29.591666, -98.541588 29.591474, -98.541733 29.591452, -98.54218 29.591298, -98.54274 29.591067, -98.542967 29.591006, -98.543181 29.590896, -98.543741 29.590671, -98.543899 29.590583, -98.544428 29.590326, -98.545589 29.589872, -98.546621 29.587994, -98.546974 29.587352, -98.546863 29.587059, -98.54732 29.586514, -98.54743 29.586407, -98.548269 29.586041, -98.548727 29.5857, -98.550791 29.584163, -98.551208 29.583879, -98.552128 29.583456, -98.552611 29.58399, -98.553238 29.584713, -98.549193 29.589202, -98.550626 29.592194, -98.550553 29.592273, -98.550508 29.592288, -98.54963 29.592666, -98.548867 29.59305, -98.549889 29.594805, -98.550935 29.596581, -98.550972 29.596644, -98.551015 29.596686, -98.551059 29.596704, -98.551127 29.596691, -98.552177 29.596228, -98.552264 29.596154, -98.552323 29.59609, -98.552614 29.59556, -98.556026 29.590367, -98.556205 29.590607, -98.556511 29.591069, -98.556918 29.591538, -98.557179 29.591991, -98.557298 29.592274, -98.557362 29.592528, -98.557371 29.59263, -98.557351 29.592732, -98.557125 29.593268, -98.557035 29.593659, -98.557005 29.59394, -98.557381 29.594844, -98.557478 29.594964, -98.557614 29.595067, -98.559762 29.596244, -98.560441 29.596622, -98.560704 29.596712, -98.560937 29.596773, -98.561249 29.596791, -98.561344 29.596789, -98.56157 29.596783, -98.562016 29.596647, -98.562594 29.597593, -98.562649 29.597683, -98.562743 29.597837, -98.562804 29.597937, -98.56289 29.598078, -98.563008 29.598279, -98.563526 29.599158, -98.563745 29.59952, -98.563844 29.599685, -98.564054 29.600034, -98.564994 29.599601, -98.565795 29.599232, -98.566124 29.599064, -98.566631 29.598839, -98.567522 29.598386, -98.569119 29.597547, -98.570977 29.596642, -98.57228 29.596049, -98.573142 29.595657, -98.574171 29.595165, -98.574415 29.595057, -98.575675 29.5945, -98.576186 29.594308, -98.576596 29.594154, -98.577084 29.593996, -98.577457 29.593875, -98.577838 29.593773, -98.5779 29.593756, -98.578253 29.593677, -98.579338 29.593444, -98.57997 29.593334, -98.581149 29.593179, -98.58218 29.593048, -98.583402 29.592912, -98.584063 29.592839, -98.58667 29.592543, -98.58787 29.592384, -98.587856 29.59216, -98.589724 29.591946, -98.590776 29.591893, -98.591445 29.591848, -98.593456 29.591756, -98.594653 29.591721, -98.595462 29.591665, -98.59597 29.591615, -98.596068 29.591606, -98.596258 29.591565, -98.59631 29.591582, -98.596717 29.591543, -98.597194 29.59149, -98.597735 29.591429, -98.598256 29.591363, -98.598274 29.591361, -98.59842 29.591346, -98.598656 29.591323, -98.598808 29.591308, -98.599797 29.591222, -98.599947 29.591217, -98.600197 29.591205, -98.60034 29.591198, -98.600628 29.591185, -98.601032 29.591167, -98.601302 29.591148, -98.601992 29.591068, -98.602291 29.591603, -98.602399 29.591872, -98.602457 29.592098, -98.602511 29.592358, -98.602535 29.59254, -98.602519 29.592887, -98.602467 29.593119, -98.602398 29.593424, -98.602308 29.593675, -98.600746 29.597373, -98.600447 29.598109, -98.600139 29.598865, -98.600208 29.599345, -98.60068 29.600806, -98.601287 29.602684, -98.601612 29.603695, -98.60163 29.603793, -98.601894 29.605205, -98.601939 29.605341, -98.602013 29.605559, -98.601876 29.605639, -98.601818 29.605669, -98.601854 29.605771, -98.601948 29.606037, -98.602052 29.606329, -98.603223 29.610022, -98.603508 29.610857, -98.603603 29.611092, -98.603813 29.611613, -98.604313 29.612552, -98.605023 29.613676, -98.606063 29.615369, -98.606417 29.616053, -98.606865 29.617076, -98.607461 29.618703, -98.607887 29.619868, -98.60813 29.620531, -98.608416 29.621332, -98.608459 29.621452, -98.608915 29.622726, -98.608726 29.622412, -98.608381 29.621782, -98.608014 29.621206, -98.607949 29.621003, -98.607626 29.619993, -98.607361 29.620081, -98.607347 29.620087, -98.607698 29.621107, -98.608212 29.622646, -98.608454 29.623396, -98.609117 29.625426, -98.608992 29.62551, -98.608665 29.625746, -98.608111 29.62615, -98.607688 29.626459, -98.607021 29.626945, -98.607123 29.627144, -98.607339 29.627467, -98.607705 29.62805, -98.607842 29.628278, -98.60795 29.628506, -98.608107 29.628949, -98.608185 29.629253, -98.608221 29.629512, -98.608247 29.630208, -98.608246 29.630524, -98.608242 29.630647, -98.608219 29.630774, -98.608117 29.631099, -98.607905 29.631715, -98.60784 29.631873, -98.607815 29.631916, -98.607774 29.631985, -98.607686 29.632062, -98.607306 29.632336, -98.607233 29.632368, -98.607172 29.632377, -98.60711 29.632377, -98.607041 29.632367, -98.60694 29.632338, -98.606117 29.63201, -98.606016 29.631981, -98.605947 29.631984, -98.605835 29.632003, -98.604303 29.6324, -98.604195 29.632409, -98.603945 29.632415, -98.603015 29.632415, -98.602838 29.63243, -98.602733 29.632455, -98.602711 29.632522, -98.602703 29.63261, -98.602708 29.633401, -98.6027 29.633511, -98.602656 29.633657, -98.602499 29.633995, -98.601861 29.635283, -98.60181 29.635359, -98.601741 29.635428, -98.6012 29.63582, -98.601172 29.635841, -98.601059 29.635938, -98.600493 29.636474, -98.60042 29.636559, -98.600344 29.636682, -98.600296 29.636815, -98.600238 29.636919, -98.600169 29.636998, -98.600103 29.637064, -98.600038 29.637118, -98.599958 29.637171, -98.59981 29.637234, -98.599726 29.637259, -98.599472 29.637311, -98.599563 29.637593, -98.599596 29.637709, -98.599532 29.637867, -98.599375 29.638066, -98.599082 29.638231, -98.597448 29.639111, -98.597171 29.63865, -98.597102 29.638693, -98.596841 29.638822, -98.59673 29.638869, -98.596486 29.638973, -98.596269 29.639042, -98.596059 29.639073, -98.595827 29.639081, -98.595755 29.639094, -98.595657 29.639106, -98.595588 29.639122, -98.59507 29.639367, -98.595008 29.639383, -98.594914 29.639385, -98.594791 29.639366, -98.593957 29.639158, -98.593877 29.639145, -98.593765 29.639138, -98.59341 29.639147, -98.593309 29.63914, -98.593222 29.639127, -98.593121 29.639105, -98.592641 29.638952, -98.5925 29.638897, -98.592384 29.638824, -98.59228 29.638745, -98.592204 29.638659, -98.592158 29.638564, -98.592129 29.63846, -98.592119 29.63833, -98.592113 29.638049, -98.592121 29.637748, -98.59214 29.637637, -98.592169 29.637508, -98.592235 29.637296, -98.592393 29.636646, -98.592422 29.636541, -98.59246 29.636276, -98.592468 29.636124, -98.59249 29.636026, -98.592512 29.63595, -98.592592 29.63584, -98.592661 29.63578, -98.592733 29.635727, -98.592813 29.635679, -98.592907 29.635639, -98.592994 29.635614, -98.593211 29.635576, -98.593295 29.635551, -98.593367 29.635514, -98.593469 29.635428, -98.593574 29.635312, -98.593763 29.634999, -98.593847 29.634921, -98.593945 29.634854, -98.594046 29.634801, -98.594173 29.634754, -98.594325 29.634716, -98.594477 29.634685, -98.594593 29.634648, -98.594684 29.634613, -98.594771 29.634557, -98.59484 29.634487, -98.594872 29.634437, -98.594902 29.634288, -98.594932 29.634019, -98.594954 29.633959, -98.594983 29.633903, -98.595045 29.633824, -98.595252 29.633654, -98.595364 29.633594, -98.595476 29.633544, -98.595607 29.6335, -98.595766 29.633465, -98.595929 29.63345, -98.596153 29.633438, -98.59628 29.63342, -98.596349 29.633398, -98.596439 29.63336, -98.59686 29.633055, -98.59717 29.632801, -98.597108 29.632582, -98.596841 29.63174, -98.596827 29.631586, -98.596162 29.631563, -98.595307 29.631514, -98.59444 29.631489, -98.594079 29.631503, -98.593718 29.631523, -98.593411 29.631548, -98.593058 29.631594, -98.592742 29.63164, -98.592684 29.631653, -98.592339 29.63173, -98.592296 29.631741, -98.592448 29.635344, -98.591915 29.636366, -98.59172 29.63674, -98.591719 29.637424, -98.591909 29.638092, -98.592016 29.63847, -98.592115 29.643107, -98.592274 29.643159, -98.598522 29.64306, -98.598849 29.643055, -98.598928 29.648748, -98.606852 29.648755, -98.606978 29.648755, -98.608858 29.648758, -98.608854 29.649168, -98.608741 29.662017, -98.608688 29.668034, -98.615398 29.668269, -98.61554 29.668274, -98.616339 29.668303, -98.616304 29.676006, -98.616303 29.676246, -98.616302 29.67656, -98.616302 29.676645, -98.625305 29.676862, -98.627847 29.676867, -98.629808 29.676867, -98.63081 29.676875, -98.631446 29.676874, -98.631402 29.678687, -98.631518 29.67868, -98.631515 29.678942, -98.631518 29.678955, -98.631538 29.679042, -98.63161 29.679133, -98.631693 29.679183, -98.631547 29.679553, -98.631497 29.67979, -98.631492 29.68136, -98.631497 29.684442, -98.631502 29.688774, -98.632128 29.688802, -98.632005 29.690257, -98.631925 29.690254, -98.631846 29.690253, -98.631764 29.690258, -98.631699 29.690264, -98.631636 29.690272, -98.631577 29.690277, -98.631503 29.690283, -98.631505 29.691287, -98.631508 29.69228, -98.631834 29.692285, -98.632426 29.692282, -98.635447 29.692248, -98.640118 29.692196, -98.640281 29.692194, -98.641328 29.692182, -98.642894 29.692165, -98.643598 29.692155, -98.6424 29.69056, -98.642337 29.690476, -98.642295 29.690421, -98.640147 29.687565, -98.638592 29.685496, -98.638453 29.685312, -98.637359 29.683829, -98.636901 29.683034, -98.636864 29.682966, -98.637053 29.682911, -98.637134 29.682888, -98.637315 29.682819, -98.63741 29.682773, -98.637505 29.682716, -98.637594 29.682652, -98.637676 29.682586, -98.637834 29.682438, -98.637911 29.68236, -98.63799 29.68228, -98.638148 29.682118, -98.638226 29.682041, -98.638306 29.681962, -98.638387 29.681888, -98.638467 29.681817, -98.638546 29.681752, -98.638705 29.681641, -98.638785 29.681585, -98.638864 29.681534, -98.638936 29.681489, -98.639042 29.681423, -98.639065 29.681381, -98.639133 29.681323, -98.639044 29.68119, -98.638397 29.680529, -98.638107 29.680227, -98.637804 29.679856, -98.637493 29.679409, -98.637514 29.6793, -98.637581 29.679242, -98.637647 29.679207, -98.637709 29.67919, -98.637793 29.679219, -98.638069 29.679591, -98.638228 29.679776, -98.639052 29.680789, -98.637609 29.678659, -98.637182 29.678028, -98.63659 29.677155, -98.635985 29.676272, -98.636196 29.676159, -98.636508 29.676626, -98.636792 29.677051, -98.63741 29.677962, -98.637814 29.678558, -98.63932 29.680781, -98.638626 29.679511, -98.638428 29.67921, -98.63832 29.679024, -98.638283 29.67892, -98.638324 29.67885, -98.638456 29.678781, -98.638523 29.678782, -98.638597 29.678795, -98.638991 29.679397, -98.639195 29.679744, -98.639536 29.680578, -98.639786 29.68103, -98.640181 29.681617, -98.642287 29.684753, -98.642844 29.685529, -98.643924 29.687141, -98.644255 29.687628, -98.645928 29.690086, -98.646363 29.690596, -98.64664 29.690933, -98.646944 29.691364, -98.647502 29.692162, -98.647642 29.692351, -98.648075 29.693015, -98.648683 29.693947, -98.649067 29.694607, -98.649157 29.694828, -98.649981 29.696082, -98.650617 29.695769, -98.6508 29.695705, -98.650968 29.695685, -98.652219 29.695676, -98.652616 29.695689, -98.652772 29.695715, -98.652937 29.6958, -98.653823 29.696365, -98.653998 29.696459, -98.654251 29.696468, -98.658208 29.696454, -98.65968 29.69645, -98.660047 29.696475, -98.660286 29.696535, -98.660611 29.696678, -98.660903 29.696374, -98.66104 29.696249, -98.661192 29.696138, -98.661241 29.696109, -98.661355 29.696041, -98.661743 29.695856, -98.661914 29.695817, -98.662109 29.695786, -98.662258 29.695782, -98.662384 29.695773, -98.663408 29.69579, -98.664543 29.695805, -98.665851 29.695863, -98.665824 29.696276, -98.665938 29.697213, -98.665967 29.697409, -98.665976 29.697639, -98.665973 29.697831, -98.665928 29.698032, -98.665858 29.698228, -98.665765 29.698417, -98.66565 29.698596, -98.665515 29.698763, -98.66536 29.698917, -98.665187 29.699056, -98.664999 29.699179, -98.664849 29.699263, -98.664736 29.699312, -98.664595 29.699365, -98.664381 29.699424, -98.664176 29.699474, -98.66372 29.699517, -98.663347 29.699515, -98.663071 29.699559, -98.662888 29.699599, -98.662596 29.6997, -98.66242 29.699776, -98.662215 29.699887, -98.661975 29.700044, -98.661903 29.700114, -98.658043 29.699998, -98.658045 29.700021, -98.657078 29.699998, -98.657027 29.703887, -98.657079 29.704987, -98.657141 29.70646, -98.657147 29.706595, -98.657056 29.706463, -98.656785 29.706471, -98.657163 29.707027, -98.65699 29.707028, -98.656881 29.70703, -98.656939 29.707114, -98.659427 29.710797, -98.661152 29.713354, -98.661924 29.714451, -98.662813 29.715784, -98.663481 29.716796, -98.664013 29.717562, -98.664594 29.718304, -98.665016 29.71878, -98.66578 29.719511, -98.665965 29.719356, -98.666426 29.719771, -98.66906 29.722146, -98.669668 29.72269, -98.672628 29.725338, -98.674438 29.726951, -98.678429 29.730525, -98.679487 29.731465, -98.680096 29.732018, -98.683359 29.734981, -98.687283 29.738483, -98.687419 29.73841, -98.687608 29.738307, -98.686875 29.737651, -98.686156 29.737153, -98.683954 29.735226, -98.682897 29.734274, -98.682382 29.733809, -98.680731 29.732322, -98.680193 29.731837, -98.679448 29.731166, -98.679382 29.731106, -98.678702 29.730452, -98.678795 29.730365, -98.678871 29.730325, -98.678971 29.730309, -98.679146 29.730303, -98.679931 29.730299, -98.679729 29.729059, -98.679629 29.728382, -98.677452 29.728392, -98.677369 29.72841, -98.677279 29.72844, -98.677195 29.7285, -98.676836 29.728808, -98.673108 29.725468, -98.673891 29.72515, -98.674325 29.724961, -98.674509 29.724914, -98.674504 29.72376, -98.674532 29.720247, -98.674539 29.720157, -98.674594 29.719885, -98.674615 29.719373, -98.67463 29.719263, -98.674655 29.719111, -98.674666 29.718991, -98.674716 29.718914, -98.675207 29.71806, -98.675249 29.717976, -98.675282 29.717889, -98.675303 29.7178, -98.675314 29.717709, -98.675314 29.717618, -98.675303 29.717527, -98.675281 29.717437, -98.675248 29.71735, -98.675205 29.717267, -98.675153 29.717188, -98.674976 29.716999, -98.675322 29.717019, -98.679734 29.717019, -98.679793 29.717019, -98.679743 29.717107, -98.678447 29.719392, -98.679112 29.719374, -98.679726 29.71937, -98.679811 29.719373, -98.68003 29.719393, -98.680134 29.719409, -98.680216 29.719428, -98.681311 29.719801, -98.681516 29.719865, -98.681672 29.719908, -98.6818 29.719927, -98.681917 29.719616, -98.682051 29.719388, -98.682138 29.719262, -98.68223 29.719157, -98.682355 29.719026, -98.68248 29.718909, -98.682584 29.718809, -98.682817 29.718639, -98.683079 29.718495, -98.683312 29.718398, -98.683599 29.718308, -98.684679 29.718034, -98.685522 29.717782, -98.687384 29.719684, -98.68745 29.719631, -98.687564 29.719574, -98.687742 29.719521, -98.688022 29.719481, -98.690295 29.719312, -98.690374 29.719299, -98.690451 29.719278, -98.690525 29.71925, -98.690596 29.719216, -98.690662 29.719175, -98.690777 29.719077, -98.690826 29.719021, -98.690867 29.71896, -98.690901 29.718896, -98.690927 29.718829, -98.690945 29.718761, -98.690949 29.71873, -98.690955 29.718616, -98.690948 29.718546, -98.690932 29.718477, -98.690908 29.718409, -98.69041 29.717514, -98.690336 29.717373, -98.690224 29.717103, -98.690145 29.716824, -98.69014 29.716794, -98.690123 29.71672, -98.690099 29.716551, -98.690099 29.716237, -98.690125 29.715878, -98.690193 29.715436, -98.69034 29.714589, -98.690573 29.71457, -98.692529 29.714372, -98.692735 29.714348, -98.692816 29.714333, -98.692964 29.714275, -98.693093 29.714195, -98.692915 29.714001, -98.692728 29.713767, -98.692475 29.713364, -98.692345 29.713157, -98.692225 29.712942, -98.69203 29.712512, -98.691831 29.71191, -98.69164 29.711293, -98.691561 29.710902, -98.691464 29.71032, -98.691063 29.707804, -98.690965 29.707485, -98.690829 29.707177, -98.690657 29.706883, -98.690451 29.706606, -98.690213 29.70635, -98.689075 29.705247, -98.68848 29.704606, -98.68797 29.703993, -98.686857 29.702652, -98.686478 29.702171, -98.686323 29.701952, -98.6855 29.70067, -98.685406 29.700505, -98.685331 29.700357, -98.685167 29.699985, -98.684852 29.699094, -98.684772 29.698891, -98.684729 29.698803, -98.684674 29.698688, -98.684555 29.698504, -98.684394 29.698315, -98.684233 29.698138, -98.684198 29.698109, -98.684055 29.697984, -98.683819 29.697788, -98.683337 29.697404, -98.683119 29.697259, -98.682994 29.697192, -98.682106 29.696773, -98.681994 29.696705, -98.681794 29.696584, -98.681459 29.69633, -98.681285 29.696165, -98.681089 29.695919, -98.680916 29.695641, -98.680796 29.695391, -98.680699 29.695137, -98.680508 29.694535, -98.680371 29.694137, -98.680176 29.693683, -98.680042 29.69341, -98.679829 29.693046, -98.679607 29.69269, -98.679371 29.69235, -98.679113 29.692006, -98.67877 29.691634, -98.678345 29.691213, -98.676983 29.689845, -98.676818 29.689665, -98.676672 29.689497, -98.67652 29.689282, -98.67648 29.689199, -98.675848 29.689298, -98.675478 29.689357, -98.675238 29.689302, -98.675062 29.689225, -98.67493 29.689132, -98.674784 29.689068, -98.674747 29.68833, -98.674729 29.687991, -98.674666 29.68737, -98.67453 29.6867, -98.674475 29.686433, -98.674549 29.686329, -98.675066 29.68612, -98.67525 29.686032, -98.675296 29.686, -98.675408 29.685742, -98.675546 29.685645, -98.675876 29.685613, -98.675818 29.685337, -98.675738 29.684985, -98.675385 29.683524, -98.675295 29.683172, -98.675284 29.683129, -98.675182 29.682801, -98.675063 29.682496, -98.674734 29.681867, -98.674574 29.681585, -98.674481 29.681397, -98.674409 29.681133, -98.674411 29.680964, -98.674262 29.680971, -98.673456 29.681102, -98.673072 29.681117, -98.672714 29.6811, -98.672347 29.681037, -98.672045 29.680955, -98.671577 29.680843, -98.671453 29.680814, -98.67095 29.680662, -98.670711 29.680571, -98.670443 29.680446, -98.670148 29.680289, -98.669103 29.679629, -98.667455 29.678562, -98.667407 29.678531, -98.666838 29.678175, -98.666559 29.678005, -98.666174 29.677824, -98.665712 29.677659, -98.665279 29.677558, -98.664928 29.677501, -98.664757 29.677476, -98.66444 29.677452, -98.664055 29.677451, -98.662655 29.677544, -98.662346 29.677564, -98.661397 29.677571, -98.660417 29.677578, -98.660163 29.677559, -98.659948 29.677535, -98.65972 29.677481, -98.6595 29.677407, -98.65929 29.677313, -98.659092 29.677201, -98.658909 29.677071, -98.658416 29.676592, -98.657015 29.675231, -98.656853 29.675074, -98.657144 29.674842, -98.657963 29.67419, -98.659242 29.674534, -98.659311 29.674538, -98.659363 29.674534, -98.659445 29.674508, -98.659583 29.674441, -98.65996 29.674219, -98.660012 29.674159, -98.660038 29.674095, -98.660055 29.674038, -98.660063 29.673883, -98.660067 29.673032, -98.660075 29.671983, -98.660075 29.670527, -98.660048 29.669405, -98.659934 29.668895, -98.65993 29.668781, -98.659935 29.668563, -98.660011 29.667037, -98.660106 29.66705, -98.660424 29.667058, -98.660632 29.667044, -98.660803 29.667006, -98.66096 29.666962, -98.661109 29.666906, -98.661202 29.666872, -98.661366 29.666842, -98.661535 29.666823, -98.662601 29.666739, -98.662791 29.666747, -98.663002 29.666762, -98.663274 29.66678, -98.664298 29.666951, -98.664557 29.666982, -98.664695 29.666982, -98.665274 29.666946, -98.665464 29.666931, -98.665619 29.666932, -98.665792 29.666944, -98.666313 29.66702, -98.666301 29.667054, -98.666296 29.667126, -98.666423 29.667964, -98.666453 29.668066, -98.666509 29.668172, -98.666563 29.66825, -98.666758 29.66847, -98.666913 29.668622, -98.667086 29.668781, -98.667219 29.668868, -98.667331 29.668906, -98.667443 29.668932, -98.667789 29.668975, -98.6681 29.669002, -98.668385 29.668988, -98.668475 29.668965, -98.668829 29.668849, -98.669131 29.668749, -98.669288 29.668688, -98.669415 29.668618, -98.669543 29.668485, -98.66961 29.668401, -98.669726 29.668183, -98.669962 29.667165, -98.670033 29.666922, -98.670189 29.666649, -98.670341 29.666511, -98.670348 29.666504, -98.67047 29.666461, -98.670604 29.666429, -98.670772 29.66642, -98.670889 29.666426, -98.670919 29.666428, -98.670992 29.666466, -98.671051 29.666511, -98.671118 29.666614, -98.67113 29.666632, -98.671177 29.666772, -98.671258 29.667206, -98.671283 29.667285, -98.671313 29.667361, -98.671369 29.667452, -98.671618 29.667784, -98.671867 29.668264, -98.671983 29.668442, -98.672168 29.668653, -98.672427 29.668862, -98.672692 29.669006, -98.672879 29.669108, -98.673056 29.669191, -98.673224 29.669256, -98.673418 29.669302, -98.673846 29.669382, -98.674053 29.669413, -98.674208 29.669421, -98.674364 29.669417, -98.674485 29.669399, -98.674835 29.66932, -98.674969 29.669309, -98.675025 29.66932, -98.675083 29.669344, -98.675165 29.669408, -98.675247 29.669461, -98.675324 29.669503, -98.67538 29.66951, -98.675475 29.669514, -98.676132 29.669388, -98.676218 29.669377, -98.677292 29.669377, -98.677882 29.667952, -98.677983 29.667659, -98.678068 29.667346, -98.678076 29.667317, -98.678156 29.666956, -98.678165 29.666818, -98.678307 29.6646, -98.678316 29.664465, -98.678323 29.664363, -98.678343 29.664044, -98.678345 29.663992, -98.678351 29.663869, -98.678352 29.663718, -98.678262 29.663318, -98.678148 29.663009, -98.677984 29.662563, -98.677746 29.662001, -98.677572 29.66162, -98.67748 29.661457, -98.677242 29.66103, -98.676688 29.659999, -98.676613 29.659837, -98.676546 29.659693, -98.676487 29.659517, -98.676217 29.659671, -98.676062 29.659746, -98.675924 29.659804, -98.675783 29.659841, -98.675632 29.659865, -98.675136 29.659858, -98.674957 29.659872, -98.674781 29.659903, -98.67461 29.65995, -98.674445 29.660012, -98.673992 29.660205, -98.672984 29.660643, -98.672781 29.660731, -98.672568 29.660791, -98.672348 29.660831, -98.672125 29.660851, -98.672071 29.660851, -98.671901 29.66085, -98.671683 29.66083, -98.671478 29.66079, -98.67128 29.660741, -98.67106 29.660705, -98.670838 29.660689, -98.670449 29.660697, -98.670242 29.660686, -98.669018 29.660564, -98.669005 29.660465, -98.669007 29.660436, -98.669015 29.660284, -98.669025 29.660214, -98.669072 29.660116, -98.669151 29.660035, -98.670509 29.659384, -98.670622 29.659316, -98.670717 29.659228, -98.670789 29.659126, -98.670815 29.65907, -98.670835 29.659013, -98.670847 29.658954, -98.670853 29.658873, -98.670852 29.658733, -98.670828 29.658397, -98.670801 29.658256, -98.670768 29.658137, -98.670732 29.658038, -98.670459 29.657488, -98.670292 29.657216, -98.670191 29.657095, -98.670086 29.657005, -98.669968 29.656921, -98.66982 29.656838, -98.669635 29.656759, -98.668511 29.656406, -98.66838 29.656342, -98.668262 29.656275, -98.668182 29.656214, -98.668081 29.656124, -98.667992 29.656021, -98.667915 29.655911, -98.667853 29.655795, -98.667513 29.655064, -98.667382 29.654809, -98.667233 29.6546, -98.66698 29.654276, -98.665993 29.654905, -98.665847 29.655008, -98.665759 29.655083, -98.665673 29.65517, -98.665555 29.655312, -98.665056 29.65597, -98.664965 29.656054, -98.664862 29.656127, -98.66475 29.656189, -98.664636 29.656241, -98.664492 29.656293, -98.663884 29.656455, -98.663162 29.656661, -98.662983 29.656724, -98.662821 29.656812, -98.662633 29.656949, -98.662505 29.6571, -98.662378 29.657267, -98.662363 29.657293, -98.662321 29.657368, -98.662284 29.657445, -98.662257 29.657526, -98.662239 29.657609, -98.662231 29.657692, -98.662252 29.657998, -98.662244 29.658112, -98.662228 29.658171, -98.662175 29.6583, -98.662092 29.658466, -98.66175 29.658315, -98.661326 29.658601, -98.660477 29.658866, -98.660406 29.658919, -98.659815 29.659258, -98.659397 29.659615, -98.6594 29.659646, -98.659413 29.659714, -98.659444 29.659803, -98.659598 29.660155, -98.659679 29.660409, -98.659719 29.660596, -98.659725 29.660624, -98.659783 29.661013, -98.659807 29.661281, -98.659799 29.661562, -98.659784 29.66177, -98.659794 29.661895, -98.659818 29.662027, -98.659907 29.662354, -98.659915 29.662451, -98.659915 29.662518, -98.659867 29.662635, -98.659797 29.662731, -98.659685 29.66281, -98.659604 29.662844, -98.659487 29.662865, -98.659307 29.662887, -98.659193 29.662914, -98.659049 29.662966, -98.658205 29.663355, -98.65803 29.663415, -98.657878 29.663441, -98.657339 29.663486, -98.657119 29.663514, -98.657089 29.663518, -98.656686 29.663617, -98.656526 29.663667, -98.656436 29.663696, -98.656192 29.663795, -98.655972 29.663907, -98.655759 29.664045, -98.654656 29.664759, -98.654109 29.665049, -98.653888 29.665214, -98.653651 29.665435, -98.652342 29.666595, -98.650963 29.667828, -98.65064 29.668116, -98.650549 29.668029, -98.650485 29.667984, -98.650411 29.667924, -98.650372 29.667889, -98.650331 29.667848, -98.65029 29.667799, -98.650249 29.667747, -98.650209 29.667689, -98.650167 29.667628, -98.650116 29.667572, -98.650006 29.667455, -98.649942 29.667397, -98.649875 29.66734, -98.649807 29.667284, -98.649738 29.667227, -98.649586 29.667109, -98.649508 29.66705, -98.649425 29.666997, -98.649346 29.666947, -98.649267 29.6669, -98.64911 29.666812, -98.649035 29.666769, -98.648961 29.666725, -98.648885 29.66668, -98.648806 29.666636, -98.648726 29.666596, -98.64848 29.666503, -98.648395 29.666476, -98.648309 29.66645, -98.648139 29.666408, -98.648095 29.666398, -98.648056 29.66639, -98.647969 29.666376, -98.647933 29.66637, -98.647881 29.666361, -98.647616 29.666305, -98.647525 29.666285, -98.647435 29.666265, -98.647256 29.666212, -98.647219 29.6662, -98.647164 29.666198, -98.647079 29.666172, -98.646986 29.666148, -98.646863 29.666143, -98.646784 29.666145, -98.646706 29.666157, -98.646627 29.666181, -98.64656 29.666201, -98.646437 29.666271, -98.646349 29.666796, -98.646512 29.667352, -98.646393 29.667329, -98.645523 29.667173, -98.645396 29.667157, -98.644927 29.667029, -98.644357 29.66683, -98.644225 29.66678, -98.64267 29.66619, -98.642549 29.666144, -98.642404 29.666089, -98.641983 29.665925, -98.641682 29.665807, -98.641325 29.665681, -98.641101 29.66561, -98.640875 29.665566, -98.64064 29.665537, -98.64026 29.665553, -98.639919 29.665597, -98.639584 29.665655, -98.637495 29.666018, -98.637437 29.666029, -98.637211 29.666069, -98.636917 29.666119, -98.636884 29.666125, -98.636881 29.666076, -98.636881 29.666035, -98.636898 29.664613, -98.636658 29.664591, -98.635904 29.664515, -98.635477 29.664474, -98.635443 29.664435, -98.635341 29.66437, -98.635285 29.664376, -98.635148 29.664351, -98.634957 29.664315, -98.634907 29.664299, -98.634875 29.664266, -98.634882 29.664216, -98.634926 29.664155, -98.634957 29.664139, -98.635128 29.664171, -98.63519 29.664172, -98.635234 29.664128, -98.635323 29.664134, -98.635344 29.66415, -98.635373 29.664172, -98.635398 29.664227, -98.635549 29.66393, -98.634519 29.663649, -98.633489 29.663259, -98.633495 29.663156, -98.633478 29.663017, -98.633456 29.662928, -98.633441 29.662869, -98.633394 29.662739, -98.633347 29.662626, -98.633332 29.662599, -98.633287 29.662519, -98.633216 29.662419, -98.63313 29.662334, -98.633042 29.662248, -98.632878 29.66212, -98.63274 29.662003, -98.632416 29.661796, -98.631903 29.661522, -98.63147 29.661332, -98.63116 29.661177, -98.630984 29.661076, -98.630842 29.660969, -98.630735 29.660845, -98.630627 29.660691, -98.630419 29.660294, -98.630345 29.660104, -98.630311 29.659968, -98.630312 29.659738, -98.630326 29.65959, -98.630347 29.659501, -98.630388 29.659377, -98.630429 29.65929, -98.630504 29.659129, -98.630633 29.658934, -98.630769 29.658769, -98.630925 29.65861, -98.63119 29.658415, -98.631685 29.658174, -98.631881 29.658116, -98.631996 29.658092, -98.632159 29.658099, -98.632348 29.658129, -98.633012 29.658312, -98.6332 29.658347, -98.633372 29.658355, -98.63352 29.658348, -98.633635 29.658336, -98.634396 29.658257, -98.634486 29.658236, -98.634546 29.658212, -98.634642 29.658172, -98.634726 29.658125, -98.63475 29.658096, -98.634806 29.658029, -98.634913 29.657915, -98.63498 29.657803, -98.635023 29.657667, -98.635032 29.657585, -98.635054 29.657479, -98.635062 29.657358, -98.635041 29.657243, -98.63503 29.657179, -98.634969 29.657052, -98.634872 29.656852, -98.63477 29.656648, -98.634713 29.656505, -98.634689 29.656405, -98.634673 29.656212, -98.634698 29.656076, -98.634789 29.655904, -98.634994 29.655726, -98.635323 29.655498, -98.635758 29.655227, -98.63595 29.655157, -98.636476 29.654978, -98.636697 29.654914, -98.636924 29.654871, -98.637156 29.654849, -98.637304 29.654846, -98.637439 29.654847, -98.637856 29.65485, -98.638037 29.65486, -98.6381 29.654869, -98.638216 29.654886, -98.638391 29.654929, -98.638559 29.654987, -98.638582 29.654997, -98.638676 29.654877, -98.638944 29.65479, -98.639148 29.654821, -98.639225 29.65503, -98.63919 29.65519, -98.639075 29.655289, -98.639334 29.655468, -98.639444 29.655519, -98.63961 29.655569, -98.640144 29.655711, -98.640259 29.655719, -98.640288 29.655719, -98.640323 29.655719, -98.641467 29.655524, -98.641572 29.655491, -98.641673 29.655449, -98.641768 29.655398, -98.641821 29.655362, -98.641856 29.655338, -98.641937 29.655271, -98.641985 29.655221, -98.642026 29.655177, -98.642163 29.655012, -98.642281 29.65487, -98.642357 29.654791, -98.642443 29.654719, -98.642537 29.654655, -98.642638 29.6546, -98.642809 29.654531, -98.642973 29.654445, -98.64312 29.654331, -98.643189 29.654253, -98.643307 29.654145, -98.6434 29.654024, -98.643481 29.653909, -98.64354 29.653774, -98.643576 29.65367, -98.643582 29.653622, -98.643579 29.653606, -98.64356 29.653518, -98.643409 29.653236, -98.643366 29.653172, -98.643271 29.653069, -98.64275 29.652613, -98.642682 29.652534, -98.64265 29.652478, -98.642625 29.652418, -98.642581 29.652228, -98.641932 29.652362, -98.641515 29.652492, -98.641225 29.652558, -98.640928 29.652598, -98.640629 29.65261, -98.63933 29.652479, -98.639261 29.65247, -98.639008 29.652439, -98.638677 29.652375, -98.638329 29.652265, -98.637972 29.652115, -98.637713 29.651974, -98.637614 29.651903, -98.637519 29.65181, -98.637382 29.651595, -98.637343 29.651514, -98.637307 29.651354, -98.637302 29.651264, -98.637304 29.651176, -98.637356 29.650908, -98.637363 29.650892, -98.637397 29.650818, -98.637467 29.650714, -98.637589 29.650558, -98.637763 29.650379, -98.637898 29.650278, -98.638023 29.650208, -98.638193 29.65013, -98.638354 29.650068, -98.638614 29.650006, -98.639581 29.649876, -98.639786 29.649845, -98.64001 29.64983, -98.640189 29.649838, -98.642372 29.650125, -98.642496 29.650144, -98.642572 29.650171, -98.642546 29.650001, -98.642197 29.649149, -98.642088 29.648815, -98.642082 29.648623, -98.642075 29.6484, -98.642089 29.648157, -98.642128 29.64794, -98.642227 29.647575, -98.642303 29.647376, -98.642384 29.647204, -98.642626 29.646782, -98.642642 29.646764, -98.642718 29.646677, -98.642674 29.646643, -98.642621 29.646601, -98.642574 29.646562, -98.642525 29.646524, -98.642479 29.646489, -98.64244 29.646452, -98.642398 29.646419, -98.642373 29.646374, -98.642308 29.646365, -98.642251 29.646362, -98.642182 29.64636, -98.642106 29.64636, -98.641863 29.646364, -98.641771 29.646363, -98.641679 29.64636, -98.6414 29.646352, -98.64122 29.646363, -98.641134 29.64637, -98.640876 29.646336, -98.640787 29.646315, -98.640696 29.646298, -98.6404 29.646258, -98.640294 29.646246, -98.640188 29.646232, -98.639878 29.646193, -98.639777 29.646181, -98.639681 29.646169, -98.639371 29.646118, -98.639269 29.646101, -98.639166 29.646085, -98.63885 29.646025, -98.638737 29.646007, -98.638409 29.645972, -98.638301 29.645957, -98.638157 29.645935, -98.640437 29.644047, -98.640849 29.643705, -98.641436 29.643218, -98.641999 29.642749, -98.642242 29.64255, -98.643006 29.64178, -98.643523 29.641257, -98.643564 29.641322, -98.643615 29.641378, -98.643723 29.641449, -98.643797 29.64148, -98.643897 29.641502, -98.644069 29.641494, -98.644176 29.641457, -98.644523 29.641272, -98.645805 29.640578, -98.646586 29.640163, -98.646651 29.640126, -98.646727 29.640071, -98.646756 29.640041, -98.646802 29.639985, -98.646837 29.63992, -98.646859 29.639849, -98.646863 29.63982, -98.646865 29.639772, -98.646855 29.639706, -98.646846 29.639664, -98.646831 29.639626, -98.6468 29.639569, -98.646707 29.639401, -98.646592 29.639195, -98.646194 29.63848, -98.646143 29.638445, -98.645988 29.638368, -98.645925 29.638348, -98.645865 29.63833, -98.645807 29.63832, -98.645768 29.638314, -98.645722 29.638308, -98.64566 29.638293, -98.6456 29.638259, -98.645559 29.63823, -98.645514 29.638179, -98.645492 29.638136, -98.645473 29.638072, -98.645476 29.638049, -98.645476 29.637993, -98.645489 29.637947, -98.646165 29.637134, -98.646204 29.637118, -98.64627 29.637039, -98.648624 29.636117, -98.648876 29.636014, -98.648684 29.635872, -98.648624 29.635696, -98.648531 29.635615, -98.648309 29.635421, -98.646656 29.634733, -98.645948 29.635053, -98.645633 29.635007, -98.644898 29.634526, -98.644426 29.63409, -98.644321 29.633792, -98.643036 29.633058, -98.642433 29.632829, -98.642039 29.632805, -98.641764 29.632906, -98.641462 29.633012, -98.640989 29.63308, -98.640479 29.632979, -98.640176 29.632731, -98.640377 29.631785, -98.640382 29.63167, -98.640372 29.631522, -98.640335 29.631365, -98.640315 29.631282, -98.640273 29.631199, -98.640184 29.631079, -98.639832 29.630636, -98.639759 29.630571, -98.639669 29.630478, -98.639438 29.630353, -98.639327 29.630284, -98.639001 29.63002, -98.638875 29.629895, -98.638544 29.62959, -98.638481 29.629526, -98.638354 29.629433, -98.638212 29.629313, -98.63806 29.629151, -98.637913 29.629008, -98.637823 29.628952, -98.637671 29.628892, -98.637529 29.628845, -98.63746 29.628845, -98.637323 29.628863, -98.637191 29.6289, -98.63708 29.628936, -98.636842 29.629037, -98.636515 29.629211, -98.636372 29.629298, -98.636245 29.629386, -98.636145 29.629441, -98.636013 29.629463, -98.635891 29.629477, -98.635728 29.629477, -98.634959 29.629497, -98.634855 29.629473, -98.634758 29.629431, -98.634674 29.629371, -98.634573 29.629289, -98.634456 29.629166, -98.634646 29.629084, -98.634823 29.628998, -98.635101 29.628838, -98.63632 29.627964, -98.637765 29.626935, -98.637968 29.626791, -98.639177 29.625914, -98.639435 29.625739, -98.639688 29.625568, -98.640015 29.625309, -98.640314 29.624975, -98.640527 29.624712, -98.640698 29.624463, -98.641312 29.623441, -98.641537 29.623114, -98.642194 29.622509, -98.642363 29.622557, -98.642782 29.622675, -98.643294 29.622819, -98.643522 29.622901, -98.64379 29.623033, -98.643934 29.62287, -98.644299 29.622412, -98.644453 29.622219, -98.644617 29.622002, -98.644651 29.621955, -98.64472 29.621865, -98.64474 29.621852, -98.644879 29.621655, -98.644916 29.621601, -98.645013 29.621461, -98.64509 29.621383, -98.645163 29.621302, -98.645232 29.621219, -98.645439 29.621043, -98.645533 29.620975, -98.64563 29.620912, -98.645732 29.620852, -98.645835 29.620797, -98.645942 29.620746, -98.647204 29.620184, -98.647256 29.620159, -98.647374 29.620105, -98.647542 29.62002, -98.647705 29.619928, -98.647864 29.619832, -98.648019 29.61973, -98.648169 29.619623, -98.648295 29.619522, -98.648468 29.619381, -98.648751 29.61913, -98.64911 29.618811, -98.649319 29.618627, -98.649455 29.618501, -98.649636 29.618342, -98.649725 29.618264, -98.649817 29.618337, -98.65007 29.618136, -98.650214 29.618023, -98.650345 29.617896, -98.650447 29.617775, -98.650522 29.617669, -98.650618 29.617485, -98.650722 29.61723, -98.650761 29.617089, -98.650804 29.616907, -98.650819 29.616583, -98.650786 29.616382, -98.650692 29.615994, -98.6506 29.615809, -98.650487 29.615634, -98.650353 29.61547, -98.6502 29.615319, -98.649782 29.614936, -98.649635 29.614763, -98.649492 29.614576, -98.649389 29.61438, -98.649319 29.614198, -98.649244 29.613965, -98.649215 29.61375, -98.649216 29.613543, -98.649247 29.613329, -98.649379 29.612749, -98.649381 29.612548, -98.649372 29.612473, -98.649359 29.612347, -98.649313 29.61215, -98.649244 29.611957, -98.649152 29.611773, -98.649039 29.611597, -98.648905 29.611433, -98.648492 29.611039, -98.648355 29.610877, -98.648238 29.610704, -98.648142 29.61052, -98.648069 29.610329, -98.64802 29.610132, -98.647994 29.609938, -98.647985 29.609828, -98.64805 29.60926, -98.648081 29.608916, -98.648026 29.608428, -98.647949 29.608065, -98.647926 29.607982, -98.647872 29.607829, -98.647849 29.607632, -98.647836 29.607423, -98.647851 29.607224, -98.647886 29.607034, -98.64797 29.606818, -98.648104 29.606567, -98.648378 29.606159, -98.648554 29.605942, -98.648895 29.605969, -98.648884 29.606067, -98.648647 29.606047, -98.648338 29.606426, -98.648228 29.606577, -98.64809 29.606832, -98.648019 29.607028, -98.647974 29.60723, -98.647956 29.607435, -98.648919 29.607216, -98.649443 29.606999, -98.650022 29.606758, -98.650324 29.606723, -98.650783 29.606669, -98.651008 29.606643, -98.651241 29.606571, -98.651668 29.606441, -98.652266 29.606259, -98.652466 29.606217, -98.652976 29.606111, -98.653172 29.60596, -98.653502 29.606087, -98.653808 29.606201, -98.653927 29.606252, -98.653944 29.606231, -98.653997 29.606167, -98.654053 29.606098, -98.6541 29.606036, -98.654162 29.605953, -98.654219 29.605879, -98.654248 29.60584, -98.654265 29.605817, -98.654255 29.605814, -98.654307 29.605743, -98.654395 29.605629, -98.654606 29.605349, -98.654841 29.605036, -98.65498 29.604852, -98.655173 29.604697, -98.65565 29.604309, -98.656539 29.603999, -98.657054 29.603519, -98.657161 29.603417, -98.657936 29.602444, -98.656302 29.601805, -98.655703 29.601596, -98.655084 29.60137, -98.654896 29.601271, -98.654695 29.601169, -98.65468 29.600931, -98.654665 29.600914, -98.654655 29.600713, -98.65437 29.600541, -98.654881 29.600094, -98.655951 29.599185, -98.656059 29.599091, -98.659243 29.596311, -98.660905 29.594843, -98.661202 29.594617, -98.661224 29.594084, -98.663351 29.593275, -98.663904 29.593496, -98.664783 29.592675, -98.662779 29.592376, -98.662079 29.592282, -98.662086 29.588375, -98.662336 29.588265, -98.662532 29.588231, -98.662933 29.588164, -98.663869 29.588156, -98.663852 29.586762, -98.66275 29.586459, -98.662236 29.586289, -98.662091 29.586196, -98.662095 29.584267, -98.662097 29.583476, -98.662654 29.583468, -98.662741 29.584225, -98.666657 29.584196, -98.667743 29.584227, -98.66987 29.584139, -98.669879 29.584247, -98.669877 29.584826, -98.67128 29.583955, -98.673988 29.585721, -98.675178 29.586154, -98.676439 29.586478, -98.676452 29.586639, -98.676466 29.586721, -98.676502 29.586836, -98.676556 29.587006, -98.676661 29.587195, -98.678532 29.585584, -98.679165 29.58504, -98.68006 29.584606, -98.68083 29.583936, -98.681157 29.583242, -98.682406 29.582476, -98.682719 29.582369, -98.684122 29.58224, -98.68369 29.581658, -98.683804 29.581614, -98.683894 29.581568, -98.683979 29.581513, -98.684769 29.580814, -98.684861 29.580739, -98.68501 29.580647, -98.685533 29.580373, -98.685406 29.580237, -98.685299 29.580097, -98.685221 29.579978, -98.685118 29.579761, -98.684986 29.579414, -98.684897 29.579056, -98.684867 29.57886, -98.684767 29.57804, -98.684745 29.577854, -98.684735 29.57764, -98.684766 29.577046, -98.684749 29.576722, -98.684718 29.57656, -98.684671 29.576375, -98.684638 29.57625, -98.684596 29.57603, -98.684553 29.575749, -98.684554 29.575677, -98.684545 29.575598, -98.684589 29.575348, -98.684746 29.574984, -98.684894 29.574719, -98.685199 29.574326, -98.686387 29.572896, -98.687094 29.572065, -98.68729 29.571869, -98.687238 29.571737, -98.6872 29.571677, -98.687105 29.571405, -98.687084 29.571307, -98.68709 29.571192, -98.687109 29.571078, -98.687261 29.57039, -98.687394 29.569801, -98.687586 29.569127, -98.687649 29.568849, -98.687667 29.568737, -98.687687 29.568537, -98.687683 29.568335, -98.687656 29.568136, -98.687604 29.567939, -98.687471 29.567558, -98.687309 29.567162, -98.687111 29.566775, -98.686852 29.566373, -98.686498 29.56594, -98.686162 29.565615, -98.685998 29.565481, -98.687283 29.564291, -98.687528 29.564492, -98.688016 29.565028, -98.688479 29.56564, -98.688755 29.566086, -98.688931 29.566465, -98.68904 29.566736, -98.689185 29.567096, -98.689338 29.56762, -98.689436 29.568202, -98.689456 29.56891, -98.689421 29.569404, -98.689394 29.569633, -98.689387 29.569686, -98.689253 29.570248, -98.688462 29.572822, -98.688301 29.573378, -98.688233 29.573746, -98.688199 29.574134, -98.688194 29.574308, -98.688186 29.57458, -98.68819 29.574749, -98.688196 29.575036, -98.688228 29.575375, -98.688272 29.575608, -98.68837 29.576025, -98.688546 29.576627, -98.688733 29.577015, -98.689053 29.577617, -98.689472 29.578161, -98.690023 29.578841, -98.690352 29.579251, -98.690787 29.579793, -98.691439 29.580551, -98.692114 29.581391, -98.692371 29.581747, -98.692484 29.581891, -98.6927 29.582168, -98.693559 29.583266, -98.693741 29.5835, -98.694096 29.583956, -98.694749 29.584629, -98.694658 29.584697, -98.694543 29.584793, -98.694393 29.585678, -98.694479 29.585706, -98.694536 29.585743, -98.69465 29.585837, -98.694891 29.586065, -98.694978 29.586112, -98.695135 29.586176, -98.695208 29.586226, -98.695272 29.586293, -98.69531 29.58634, -98.695383 29.586457, -98.695459 29.586611, -98.695527 29.586892, -98.695576 29.587153, -98.695594 29.587337, -98.695582 29.587464, -98.695539 29.587556, -98.695597 29.587641, -98.695661 29.587956, -98.695707 29.588093, -98.695768 29.5882, -98.695851 29.588311, -98.695954 29.588401, -98.696076 29.588472, -98.696245 29.588529, -98.696359 29.588546, -98.696446 29.588543, -98.696464 29.588542, -98.696547 29.588516, -98.696731 29.588444, -98.696969 29.588778, -98.697932 29.589763, -98.698188 29.589559, -98.700943 29.592265, -98.702101 29.593405, -98.702203 29.593516, -98.702406 29.593736, -98.702642 29.594017, -98.703448 29.595099, -98.703681 29.595387, -98.70388 29.595601, -98.704156 29.595863, -98.704444 29.596096, -98.704535 29.596161, -98.704692 29.596273, -98.70503 29.596515, -98.705307 29.596719, -98.705595 29.596952, -98.705783 29.597117, -98.705949 29.597302, -98.70617 29.597574, -98.706357 29.597846, -98.706419 29.597943, -98.706512 29.598089, -98.706677 29.598419, -98.706809 29.598778, -98.707024 29.599489, -98.708337 29.599158, -98.708769 29.599036, -98.709393 29.598792, -98.709735 29.598643, -98.710117 29.598504, -98.71067 29.598346, -98.71098 29.599202, -98.711042 29.599431, -98.711064 29.599538, -98.711106 29.599707, -98.711194 29.599954, -98.711312 29.600192, -98.711457 29.600418, -98.711947 29.601095, -98.712037 29.601205, -98.712081 29.60126, -98.712233 29.601411, -98.712339 29.601496, -98.712407 29.601551, -98.712564 29.601655, -98.712798 29.601783, -98.713034 29.601925, -98.713293 29.602089, -98.713758 29.602436, -98.713798 29.602466, -98.714387 29.603012, -98.714452 29.603073, -98.714627 29.603203, -98.714817 29.603317, -98.71502 29.603413, -98.715233 29.60349, -98.715449 29.603548, -98.715643 29.603596, -98.715768 29.603633, -98.715888 29.603681, -98.716001 29.60374, -98.716149 29.603841, -98.716329 29.603986, -98.71643 29.60406, -98.71654 29.604123, -98.716657 29.604177, -98.716779 29.604219, -98.716949 29.604257, -98.717044 29.604271, -98.717231 29.604278, -98.717362 29.604268, -98.717491 29.604246, -98.717617 29.604213, -98.717739 29.604168, -98.717963 29.604066, -98.718192 29.603993, -98.718343 29.603971, -98.718551 29.603971, -98.718682 29.603958, -98.718811 29.603934, -98.718936 29.603898, -98.719056 29.60385, -98.719153 29.603801, -98.719269 29.603728, -98.719367 29.60365, -98.719454 29.603564, -98.719723 29.603272, -98.721158 29.604279, -98.721375 29.60441, -98.721605 29.60452, -98.721848 29.604609, -98.722101 29.604675, -98.722359 29.604718, -98.722381 29.60472, -98.722622 29.604737, -98.722885 29.604732, -98.723146 29.604703, -98.723403 29.60465, -98.723651 29.604574, -98.72375 29.604535, -98.723926 29.604235, -98.724792 29.602762, -98.724184 29.602094, -98.723258 29.600923, -98.722757 29.600294, -98.722335 29.599335, -98.7192 29.593337, -98.718827 29.592624, -98.719051 29.592451, -98.719134 29.592368, -98.719145 29.592312, -98.719083 29.59218, -98.71912 29.592111, -98.719167 29.592053, -98.718951 29.59187, -98.718859 29.591784, -98.718798 29.591726, -98.718669 29.591605, -98.718748 29.591513, -98.718953 29.591281, -98.719167 29.590543, -98.719515 29.590641, -98.716643 29.587929, -98.716526 29.587922, -98.716452 29.587916, -98.713745 29.587726, -98.713566 29.587714, -98.7126 29.587548, -98.710906 29.587408, -98.709926 29.586928, -98.710001 29.586405, -98.709538 29.585887, -98.709912 29.585633, -98.709973 29.585085, -98.710075 29.584894, -98.710372 29.584338, -98.710249 29.584168, -98.710007 29.583854, -98.710165 29.583424, -98.710154 29.583386, -98.71032 29.583041, -98.710547 29.582535, -98.710603 29.582453, -98.710713 29.582178, -98.710495 29.581405, -98.709935 29.58038, -98.70982 29.580168, -98.709679 29.579897, -98.709671 29.579891, -98.709505 29.579595, -98.709254 29.579593, -98.708491 29.57956, -98.704823 29.579381, -98.703826 29.579388, -98.703345 29.579384, -98.703156 29.579383, -98.702975 29.579209, -98.70227 29.575281, -98.702223 29.575109, -98.702174 29.574848, -98.70211 29.574514, -98.702283 29.574693, -98.703031 29.575468, -98.703283 29.575729, -98.70342 29.575897, -98.704489 29.577114, -98.704958 29.577631, -98.705847 29.577892, -98.707581 29.578396, -98.70798 29.578511, -98.710186 29.577837, -98.7133 29.578671, -98.713306 29.57878, -98.713784 29.5788, -98.714008 29.578698, -98.714562 29.578448, -98.715149 29.578181, -98.715741 29.577912, -98.715764 29.577902, -98.716063 29.577406, -98.716359 29.576881, -98.716472 29.576682, -98.716482 29.57654, -98.716544 29.575671, -98.716633 29.574416, -98.716778 29.572381, -98.716838 29.571532, -98.716929 29.570212, -98.716619 29.568814, -98.71154 29.566217, -98.712386 29.564814, -98.712541 29.564568, -98.712653 29.564398, -98.712774 29.564147, -98.714109 29.561402, -98.715489 29.558656, -98.717233 29.555185, -98.717403 29.554813, -98.717624 29.554335, -98.71818 29.553283, -98.718327 29.552961, -98.71847 29.552651, -98.719107 29.551567, -98.719491 29.550804, -98.721216 29.547518, -98.722214 29.545575, -98.723204 29.546101, -98.723425 29.545673, -98.723229 29.545578, -98.722415 29.545187, -98.722466 29.545089, -98.72211 29.544915, -98.721798 29.544801, -98.721054 29.544529, -98.719253 29.543873, -98.719201 29.543829, -98.718968 29.543726, -98.718882 29.543693, -98.718725 29.543627, -98.718235 29.54346, -98.712347 29.540793, -98.712068 29.540685, -98.711494 29.540457, -98.711864 29.539738, -98.712061 29.539368, -98.71211 29.539277, -98.71215 29.539208, -98.712324 29.538905, -98.71256 29.538521, -98.712862 29.538064, -98.713069 29.53777, -98.713112 29.53771, -98.713492 29.537198, -98.713948 29.536602, -98.714826 29.535457, -98.715071 29.535101, -98.71552 29.534404, -98.715836 29.533838, -98.71634 29.532852, -98.71659 29.532362, -98.717496 29.530592, -98.717632 29.530313, -98.717752 29.530038, -98.71786 29.52976, -98.717956 29.529478, -98.71804 29.529193, -98.718185 29.52861, -98.718199 29.528521, -98.718414 29.528555, -98.718503 29.528558, -98.718562 29.528535, -98.718605 29.528495, -98.718665 29.528369, -98.718727 29.528213, -98.7197 29.526287, -98.724061 29.527999, -98.724223 29.528063, -98.72459 29.528207, -98.726348 29.5289, -98.726742 29.529056, -98.727207 29.52924, -98.727488 29.529351, -98.728529 29.529762, -98.72866 29.529813, -98.729543 29.530162, -98.730676 29.530605, -98.733556 29.531732, -98.733674 29.531778, -98.737347 29.533214, -98.738491 29.533661, -98.740777 29.53459, -98.744421 29.536128, -98.745481 29.536575, -98.746361 29.536744, -98.746962 29.536822, -98.747167 29.536822, -98.747329 29.536771, -98.747524 29.536637, -98.747413 29.536565, -98.747447 29.536531, -98.747492 29.536443, -98.747565 29.536304, -98.748199 29.535086, -98.750086 29.531466, -98.750408 29.530848, -98.750571 29.530906, -98.750653 29.530938, -98.750499 29.531233, -98.748336 29.535395, -98.751181 29.536522, -98.752691 29.537747, -98.754634 29.539366, -98.755106 29.539744, -98.755379 29.53994, -98.755423 29.539904, -98.755775 29.540207, -98.755987 29.540329, -98.756165 29.540402, -98.756323 29.54048, -98.756398 29.540535, -98.756485 29.540631, -98.756697 29.540953, -98.75677 29.54103, -98.756858 29.541101, -98.756953 29.541158, -98.757136 29.541204, -98.757323 29.541244, -98.757252 29.541508, -98.758125 29.542245, -98.75978 29.543611, -98.762638 29.544721, -98.763836 29.542455, -98.763934 29.542224, -98.764946 29.540175, -98.765195 29.539691, -98.765226 29.539583, -98.764083 29.540002, -98.763288 29.540295, -98.762467 29.540012, -98.76231 29.539964, -98.761996 29.539897, -98.761888 29.539884, -98.761346 29.539837, -98.76092 29.539801, -98.760749 29.539792, -98.760568 29.539767, -98.760401 29.539738, -98.760208 29.539685, -98.760112 29.539652, -98.759565 29.539422, -98.759343 29.53933, -98.759125 29.539234, -98.758998 29.539164, -98.758898 29.539101, -98.758715 29.538945, -98.758645 29.538872, -98.758544 29.538741, -98.757964 29.537912, -98.757842 29.537756, -98.757751 29.537665, -98.75763 29.53757, -98.757533 29.537513, -98.757429 29.537459, -98.75732 29.537417, -98.757217 29.537385, -98.757093 29.537359, -98.756047 29.537168, -98.755606 29.537087, -98.75519 29.537017, -98.75422 29.53684, -98.754089 29.53681, -98.75397 29.536775, -98.753855 29.536735, -98.753728 29.536682, -98.75364 29.53664, -98.753585 29.536612, -98.753423 29.536513, -98.753288 29.536418, -98.752253 29.535569, -98.752156 29.535476, -98.752098 29.535406, -98.752053 29.535335, -98.752009 29.535252, -98.751978 29.53517, -98.751964 29.535084, -98.751958 29.534996, -98.751993 29.534378, -98.752101 29.532523, -98.752123 29.532102, -98.752107 29.531956, -98.752062 29.531795, -98.751968 29.531614, -98.751846 29.531473, -98.751707 29.531371, -98.751529 29.531273, -98.751339 29.531203, -98.751411 29.531053, -98.751468 29.530988, -98.751836 29.530728, -98.751889 29.530686, -98.751933 29.53064, -98.751995 29.530558, -98.752165 29.530224, -98.752446 29.529748, -98.751916 29.52958, -98.751462 29.529366, -98.751386 29.529332, -98.752805 29.526594, -98.752658 29.526532, -98.753836 29.524226, -98.754822 29.522292, -98.756377 29.519305, -98.757978 29.516228, -98.756066 29.515475, -98.756048 29.515468, -98.754988 29.515054, -98.754164 29.514731, -98.753318 29.514401, -98.75269 29.514155, -98.752644 29.514138, -98.752556 29.514104, -98.752054 29.513908, -98.751264 29.513599, -98.750483 29.513294, -98.749706 29.51299, -98.74896 29.512699, -98.748539 29.512534, -98.74817 29.51239, -98.744812 29.511075, -98.744062 29.510787, -98.744077 29.510756, -98.744177 29.510567, -98.744273 29.510365, -98.744394 29.510113, -98.744414 29.510075, -98.744391 29.510069, -98.74431 29.510038, -98.744229 29.510006, -98.744149 29.509974, -98.744062 29.509942, -98.743967 29.509907, -98.743876 29.509872, -98.743799 29.509843, -98.743715 29.509814, -98.743646 29.509793, -98.743584 29.50977, -98.743515 29.509737, -98.743447 29.509704, -98.743377 29.50967, -98.743316 29.509642, -98.743225 29.509599, -98.742937 29.509477, -98.742652 29.509365, -98.742129 29.509149, -98.741743 29.509837, -98.74173 29.509875, -98.741231 29.509681, -98.741248 29.509642, -98.741889 29.508436, -98.741823 29.50841, -98.74179 29.508397, -98.739321 29.507417, -98.739231 29.507381, -98.737908 29.506855, -98.73772 29.507295, -98.737522 29.507694, -98.737286 29.508148, -98.735055 29.507267, -98.735624 29.505977, -98.735299 29.505852, -98.736627 29.503233, -98.737396 29.501718, -98.737448 29.501617, -98.73895 29.498681, -98.739715 29.49719, -98.741924 29.492837, -98.741975 29.492739, -98.74206 29.492588, -98.742237 29.492281, -98.742532 29.492018, -98.743088 29.491586, -98.743509 29.491245, -98.743629 29.491148, -98.743983 29.490705, -98.744138 29.490319, -98.74418 29.489697, -98.744184 29.489023, -98.744184 29.48835, -98.744193 29.487734, -98.744327 29.487072, -98.744475 29.486638, -98.744562 29.486383, -98.744704 29.485935, -98.744759 29.485579, -98.744769 29.48455, -98.744851 29.48455, -98.744851 29.48405, -98.744851 29.484004, -98.74469 29.484002, -98.744686 29.483563, -98.744657 29.483108, -98.744322 29.482457, -98.743938 29.482005, -98.74358 29.481597, -98.743327 29.481221, -98.743186 29.480862, -98.743162 29.480346, -98.743218 29.479521, -98.743299 29.47836, -98.743298 29.477787, -98.743242 29.477438, -98.74312 29.476978, -98.742998 29.476679, -98.74297 29.47649, -98.74205 29.474947, -98.742642 29.474679, -98.74307 29.474487, -98.743396 29.474261, -98.743809 29.473959, -98.745005 29.473329, -98.744541 29.472629, -98.744393 29.472407, -98.744323 29.47227, -98.744281 29.47164, -98.744288 29.471049, -98.74416 29.470591, -98.744031 29.470337, -98.743943 29.470164, -98.743749 29.469782, -98.74345 29.469299, -98.743195 29.469099, -98.742903 29.468976, -98.742846 29.468952, -98.742215 29.468932, -98.740984 29.469157, -98.739044 29.469511, -98.738974 29.469534, -98.738098 29.469839, -98.738041 29.469872, -98.737711 29.470066, -98.737497 29.470239, -98.737331 29.470374, -98.736938 29.470731, -98.736607 29.470933, -98.736313 29.471025, -98.735886 29.471031, -98.735517 29.470939, -98.734961 29.47075, -98.734448 29.470576, -98.734262 29.470513, -98.734501 29.47004, -98.734869 29.469568, -98.734961 29.469488, -98.735178 29.469239, -98.734962 29.469039, -98.734062 29.46821, -98.732648 29.466907, -98.73258 29.466845, -98.732542 29.466809, -98.732149 29.466443, -98.731752 29.466074, -98.731196 29.465556, -98.731072 29.465383, -98.730952 29.465144, -98.73089 29.464957, -98.730684 29.464338, -98.730658 29.464264, -98.729807 29.461822, -98.729915 29.461798, -98.731921 29.461357, -98.73385 29.46093, -98.737702 29.460078, -98.738405 29.459985, -98.738387 29.459868, -98.738714 29.459787, -98.741579 29.459565, -98.743756 29.459913, -98.745483 29.460386, -98.747575 29.461149, -98.74823 29.461359, -98.750557 29.461997, -98.750879 29.462087, -98.750997 29.461978, -98.751506 29.461207, -98.751717 29.46123, -98.752326 29.461092, -98.752628 29.46091, -98.753744 29.459847, -98.75535 29.457726, -98.756427 29.45675, -98.756465 29.45685, -98.756622 29.457252, -98.75703 29.458299, -98.757245 29.458848, -98.757764 29.460264, -98.757819 29.460415, -98.757927 29.46067, -98.758067 29.460937, -98.75831 29.461271, -98.758344 29.461307, -98.758441 29.461408, -98.75864 29.461584, -98.758901 29.461775, -98.759266 29.462012, -98.76087 29.463007, -98.761181 29.4632, -98.762617 29.464098, -98.762311 29.464487, -98.762294 29.464587, -98.762294 29.464658, -98.762307 29.464749, -98.762313 29.464999, -98.762302 29.465325, -98.762251 29.465692, -98.761991 29.467037, -98.761987 29.467079, -98.761972 29.467179, -98.761972 29.467311, -98.761986 29.467559, -98.761978 29.467666, -98.76196 29.467766, -98.761882 29.468057, -98.761533 29.469096, -98.761523 29.469138, -98.761482 29.469213, -98.761434 29.46927, -98.761336 29.46937, -98.761296 29.469445, -98.761279 29.469497, -98.761127 29.470081, -98.760955 29.470632, -98.760791 29.471036, -98.760708 29.471242, -98.760651 29.471416, -98.760568 29.471674, -98.760036 29.471542, -98.759968 29.471521, -98.759732 29.471422, -98.759625 29.471389, -98.759219 29.471339, -98.758575 29.471176, -98.757937 29.471014, -98.757843 29.470996, -98.757735 29.470984, -98.756833 29.470973, -98.75683 29.471805, -98.7568 29.472793, -98.756785 29.474047, -98.756779 29.474603, -98.756768 29.475661, -98.756749 29.477295, -98.757874 29.477305, -98.758729 29.477312, -98.759158 29.475983, -98.759174 29.475942, -98.759253 29.475839, -98.760112 29.476077, -98.760943 29.476286, -98.761964 29.473173, -98.762196 29.472747, -98.762435 29.472535, -98.763402 29.471686, -98.763522 29.471796, -98.763654 29.472069, -98.763779 29.472173, -98.763984 29.472252, -98.764132 29.472326, -98.764584 29.472609, -98.76476 29.472784, -98.764871 29.472981, -98.765006 29.47324, -98.765042 29.473493, -98.765114 29.474364, -98.765302 29.476126, -98.766041 29.4758, -98.766259 29.475704, -98.766603 29.475516, -98.766658 29.475487, -98.766863 29.475209, -98.76698 29.474846, -98.767215 29.474123, -98.767648 29.472801, -98.768368 29.472987, -98.76852 29.473028, -98.769993 29.468648, -98.770106 29.468456, -98.770255 29.468037, -98.770299 29.46787, -98.770369 29.467702, -98.770565 29.467455, -98.770639 29.467379, -98.775459 29.46317, -98.777188 29.46166, -98.777297 29.461538, -98.777359 29.461451, -98.777583 29.46106, -98.778601 29.458981, -98.778325 29.458813, -98.77798 29.458676, -98.777785 29.458681, -98.777401 29.458588, -98.777263 29.458544, -98.777181 29.458489, -98.777137 29.458445, -98.777093 29.458362, -98.777037 29.458329, -98.776842 29.458268, -98.776735 29.458252, -98.776389 29.458147, -98.776333 29.458098, -98.776314 29.458048, -98.776308 29.457872, -98.776276 29.457784, -98.776226 29.457729, -98.776038 29.457586, -98.77595 29.457547, -98.775931 29.457514, -98.775906 29.457371, -98.775855 29.457294, -98.775849 29.457267, -98.775849 29.457206, -98.775918 29.457014, -98.775849 29.456766, -98.775768 29.456656, -98.775611 29.456502, -98.775548 29.456436, -98.775523 29.456353, -98.775365 29.45626, -98.775158 29.456188, -98.774982 29.456166, -98.774693 29.455913, -98.77463 29.455858, -98.774523 29.455698, -98.774134 29.455313, -98.773945 29.455203, -98.773474 29.455192, -98.773027 29.45512, -98.772807 29.455027, -98.772223 29.454509, -98.772047 29.454405, -98.771984 29.454273, -98.771877 29.454163, -98.77177 29.45402, -98.771626 29.453794, -98.771475 29.453656, -98.771048 29.453194, -98.77091 29.453067, -98.770652 29.452798, -98.770608 29.452638, -98.770489 29.45255, -98.769609 29.452242, -98.769326 29.452049, -98.769018 29.451889, -98.76893 29.451779, -98.768842 29.451702, -98.76849 29.45162, -98.768433 29.451603, -98.767993 29.451526, -98.767792 29.451509, -98.767201 29.451542, -98.766931 29.451531, -98.766805 29.45152, -98.766541 29.451454, -98.766296 29.451498, -98.765963 29.45152, -98.765171 29.451665, -98.765064 29.451685, -98.764397 29.451855, -98.764152 29.452025, -98.76407 29.452119, -98.763725 29.452652, -98.763347 29.453076, -98.763246 29.453323, -98.763079 29.453489, -98.7629 29.45367, -98.762712 29.453939, -98.762561 29.454055, -98.762529 29.454066, -98.762448 29.454066, -98.762385 29.454077, -98.762341 29.454104, -98.762265 29.454187, -98.762253 29.454225, -98.762259 29.454352, -98.762227 29.454445, -98.762095 29.454599, -98.761938 29.454753, -98.761724 29.454792, -98.761525 29.454856, -98.761316 29.454924, -98.761228 29.454929, -98.760291 29.454731, -98.759945 29.45467, -98.759166 29.454378, -98.759116 29.454329, -98.759059 29.454241, -98.758953 29.453894, -98.758852 29.453773, -98.75872 29.453322, -98.758884 29.452992, -98.75906 29.452425, -98.758916 29.452144, -98.758771 29.451996, -98.758363 29.451522, -98.758231 29.451445, -98.758156 29.451313, -98.757804 29.450884, -98.757471 29.450581, -98.757451 29.45056, -98.757183 29.450705, -98.756973 29.450771, -98.756821 29.45078, -98.756542 29.450815, -98.756387 29.45086, -98.756222 29.450926, -98.755721 29.451057, -98.755257 29.45165, -98.754907 29.451842, -98.754562 29.451922, -98.754062 29.450638, -98.753436 29.449014, -98.753104 29.448155, -98.752303 29.446044, -98.752158 29.445661, -98.752116 29.445551, -98.751525 29.444352, -98.75432 29.44409, -98.754853 29.443974, -98.755733 29.443603, -98.756417 29.443379, -98.757415 29.44321, -98.757693 29.443055, -98.759233 29.442554, -98.76006 29.442506, -98.760339 29.442388, -98.760416 29.442311, -98.76063 29.442097, -98.761254 29.442027, -98.761533 29.442145, -98.762883 29.442432, -98.763819 29.442432, -98.764883 29.44216, -98.765762 29.442125, -98.765946 29.442036, -98.765134 29.440706, -98.764991 29.440446, -98.764928 29.440302, -98.764491 29.439223, -98.761822 29.440051, -98.761774 29.440066, -98.761705 29.436248, -98.758565 29.436291, -98.755263 29.436337, -98.755173 29.43146, -98.755119 29.429514, -98.755115 29.429472, -98.755101 29.4294, -98.755079 29.429331, -98.75503 29.42923, -98.754997 29.429176, -98.754959 29.429099, -98.754931 29.429018, -98.754912 29.428935, -98.754904 29.428852, -98.754906 29.42689, -98.754906 29.426733, -98.754891 29.426573, -98.754872 29.426508, -98.754835 29.426425, -98.754739 29.426287, -98.754675 29.426205, -98.754574 29.426105, -98.754408 29.425972, -98.751849 29.428079, -98.751498 29.428337, -98.751179 29.428551, -98.750866 29.428727, -98.750218 29.429043, -98.749871 29.429177, -98.749542 29.429272, -98.749147 29.429377, -98.748718 29.429461, -98.748349 29.429498, -98.747797 29.429511, -98.745263 29.429547, -98.745077 29.42955, -98.74245 29.429572, -98.741193 29.429584, -98.739915 29.429603, -98.739392 29.429611, -98.737535 29.429665, -98.737531 29.429619, -98.737396 29.428, -98.737246 29.426317, -98.737099 29.424642, -98.736953 29.422968, -98.736859 29.421904, -98.736798 29.42154, -98.736457 29.419515, -98.736323 29.418733, -98.736205 29.418043, -98.735992 29.4168, -98.735911 29.416535, -98.735859 29.416399, -98.735784 29.416247, -98.735645 29.416054, -98.735505 29.41584, -98.735413 29.415686, -98.735355 29.415513, -98.73532 29.41538, -98.735309 29.415227, -98.735298 29.414931, -98.735297 29.411001, -98.732935 29.410992, -98.730649 29.411001, -98.728431 29.411014, -98.728008 29.411013, -98.727709 29.410951, -98.727505 29.410876, -98.727269 29.410753, -98.727077 29.410626, -98.726901 29.41044, -98.726779 29.410286, -98.726675 29.40999, -98.726625 29.409725, -98.726614 29.409441, -98.726616 29.409163, -98.726606 29.408929, -98.726602 29.406916, -98.722287 29.406981, -98.722254 29.40698, -98.721175 29.406994, -98.71995 29.406982, -98.718369 29.406968, -98.718369 29.407032, -98.718372 29.407294, -98.718372 29.407305, -98.719402 29.40731, -98.719392 29.408057, -98.71768 29.408042, -98.716619 29.408546, -98.715992 29.407527, -98.715949 29.407297, -98.715949 29.407064, -98.715948 29.406913, -98.713402 29.406924, -98.713497 29.406527, -98.714365 29.406526, -98.71435 29.404757, -98.715187 29.404757, -98.715195 29.403952, -98.715193 29.402491, -98.714332 29.402483, -98.713482 29.402475, -98.71349 29.404757, -98.71266 29.404757, -98.711688 29.404759, -98.711681 29.40246, -98.711685 29.401996, -98.711687 29.401872, -98.71171 29.401354, -98.711719 29.399322, -98.711716 29.399044, -98.711768 29.398811, -98.71188 29.398598, -98.71193 29.398527, -98.712093 29.398377, -98.712597 29.397983, -98.71223 29.397577, -98.712042 29.397455, -98.711873 29.3974, -98.711435 29.397394, -98.711137 29.397392, -98.711107 29.397491, -98.711053 29.397628, -98.710966 29.397761, -98.710864 29.397924, -98.710829 29.398055, -98.710821 29.398265, -98.710254 29.398264, -98.710255 29.398201, -98.710259 29.396717, -98.710248 29.395564, -98.710252 29.393678, -98.710234 29.392791, -98.710231 29.391765, -98.71 29.391578, -98.709897 29.391691, -98.709679 29.391857, -98.709398 29.391982, -98.708749 29.392116, -98.708637 29.391648, -98.708461 29.390914, -98.708161 29.390978, -98.707276 29.391282, -98.705883 29.391314, -98.705345 29.391317, -98.704355 29.391325, -98.704359 29.392347, -98.704409 29.392775, -98.704503 29.393558, -98.704636 29.394601, -98.705137 29.394374, -98.705453 29.394779, -98.705076 29.39497, -98.704672 29.395112, -98.70418 29.395217, -98.702545 29.395387, -98.702147 29.392173, -98.702058 29.391417, -98.701768 29.388971, -98.701382 29.385708, -98.701252 29.384609, -98.701236 29.384473, -98.701185 29.384019, -98.701158 29.383779, -98.70094 29.382021, -98.707342 29.38052, -98.710115 29.379866, -98.710168 29.379852, -98.710996 29.379636, -98.712425 29.379227, -98.711857 29.379121, -98.713652 29.378599, -98.714124 29.378479, -98.715413 29.378152, -98.716636 29.377894, -98.717736 29.377689, -98.718623 29.377556, -98.719071 29.377489, -98.720402 29.377313, -98.722283 29.377065, -98.723751 29.376882, -98.723774 29.376989, -98.723808 29.377146, -98.724038 29.377116, -98.723991 29.376853, -98.724105 29.376838, -98.724264 29.376817, -98.724425 29.376795, -98.726289 29.376548, -98.726475 29.376523, -98.72648 29.372451, -98.7267 29.367734, -98.725012 29.367646, -98.724868 29.367626, -98.724812 29.367568, -98.724813 29.36706, -98.724815 29.366263, -98.725102 29.364588, -98.725774 29.364401, -98.726076 29.364186, -98.725851 29.363795, -98.724822 29.363547, -98.724024 29.363444, -98.724016 29.362846, -98.723799 29.362845, -98.7235 29.362844, -98.723278 29.362839, -98.723218 29.36283, -98.723174 29.362818, -98.723106 29.362773, -98.723043 29.362702, -98.722888 29.362722, -98.722265 29.362171, -98.721943 29.361854, -98.72158 29.361752, -98.720248 29.361709, -98.72032 29.360747, -98.720305 29.360778, -98.720274 29.360805, -98.720135 29.360824, -98.718287 29.36081, -98.717904 29.360819, -98.712626 29.360849, -98.710534 29.360824, -98.709186 29.360817, -98.707253 29.360807, -98.707233 29.3612, -98.707193 29.361412, -98.707173 29.361624, -98.707165 29.362083, -98.707173 29.363827, -98.707165 29.364669, -98.707158 29.365339, -98.707167 29.366873, -98.707169 29.36757, -98.70717 29.367751, -98.707192 29.374008, -98.707201 29.376575, -98.707201 29.376707, -98.707211 29.379223, -98.707213 29.379685, -98.707214 29.380076, -98.706477 29.380214, -98.705711 29.380281, -98.704585 29.380399, -98.704292 29.380418, -98.70401 29.380422, -98.703844 29.38041, -98.703717 29.380409, -98.703482 29.380364, -98.703176 29.38029, -98.703085 29.380262, -98.702954 29.380222, -98.702564 29.38008, -98.702182 29.379979, -98.701871 29.379929, -98.701574 29.379915, -98.701305 29.379935, -98.700942 29.380001, -98.700699 29.38006, -98.699778 29.380275, -98.699444 29.380399, -98.699138 29.380582, -98.698966 29.380724, -98.698505 29.381279, -98.698265 29.381509, -98.698001 29.381698, -98.697662 29.381883, -98.69734 29.381997, -98.696991 29.382098, -98.696194 29.382315, -98.693938 29.382907, -98.693196 29.383083, -98.692341 29.383318, -98.692066 29.383417, -98.691792 29.383537, -98.691357 29.383766, -98.691197 29.383856, -98.69091 29.383975, -98.690567 29.384105, -98.690373 29.384174, -98.688993 29.384593, -98.688331 29.384748, -98.688271 29.384762, -98.688221 29.384557, -98.686605 29.384938, -98.683189 29.385755, -98.681537 29.386121, -98.681248 29.386185, -98.678607 29.38681, -98.677792 29.386997, -98.674799 29.387682, -98.673587 29.387969, -98.672489 29.388277, -98.671459 29.388632, -98.671334 29.388675, -98.669808 29.389288, -98.667002 29.390415, -98.66712 29.390539, -98.667194 29.390589, -98.665138 29.391414, -98.664251 29.391641, -98.663622 29.39185, -98.663206 29.392014, -98.662748 29.392204, -98.662455 29.392307, -98.661597 29.392575, -98.661305 29.392689, -98.659756 29.393304, -98.65972 29.393231, -98.659613 29.393009, -98.659551 29.392904, -98.659467 29.392727, -98.659397 29.392608, -98.659353 29.39255, -98.659282 29.392511, -98.658985 29.392401, -98.658896 29.392393, -98.658812 29.392404, -98.658688 29.392451, -98.657991 29.392733, -98.657893 29.392767, -98.657818 29.392786, -98.657702 29.392802, -98.657614 29.392805, -98.657507 29.392786, -98.65741 29.392747, -98.657308 29.392692, -98.657246 29.392633, -98.65718 29.392548, -98.657087 29.392388, -98.656954 29.392142, -98.656827 29.391906, -98.65607 29.390481, -98.655816 29.39005, -98.655609 29.389673, -98.655001 29.388525, -98.6549 29.388366, -98.654829 29.38823, -98.654715 29.387985, -98.654693 29.387911, -98.65468 29.387849, -98.654676 29.387732, -98.654641 29.38748, -98.654615 29.38741, -98.654594 29.387385, -98.654575 29.387363, -98.654518 29.387312, -98.654407 29.387238, -98.654319 29.387172, -98.654244 29.387106, -98.65416 29.386989, -98.65408 29.386853, -98.653939 29.386592, -98.653763 29.386308, -98.653486 29.385764, -98.652891 29.38469, -98.652824 29.384604, -98.652732 29.384453, -98.652666 29.384289, -98.652622 29.384192, -98.652477 29.38392, -98.652362 29.383737, -98.652005 29.383099, -98.65197 29.383002, -98.651957 29.38294, -98.651957 29.382889, -98.651975 29.382823, -98.652011 29.382769, -98.652028 29.382718, -98.652031 29.382654, -98.651966 29.382699, -98.651818 29.382755, -98.651657 29.382809, -98.65154 29.382596, -98.650073 29.379944, -98.650003 29.379818, -98.648702 29.377465, -98.649292 29.377209, -98.649363 29.377202, -98.649425 29.377202, -98.649497 29.377211, -98.649558 29.377218, -98.649664 29.377265, -98.649779 29.377335, -98.649894 29.377413, -98.649965 29.377467, -98.649991 29.377545, -98.649983 29.377631, -98.649943 29.378578, -98.649951 29.378951, -98.649995 29.379177, -98.650056 29.379371, -98.650126 29.379573, -98.650241 29.379768, -98.650373 29.379962, -98.650523 29.380133, -98.650709 29.380313, -98.65093 29.380476, -98.65116 29.380625, -98.651434 29.380742, -98.651709 29.380836, -98.651948 29.380891, -98.652205 29.38093, -98.652308 29.380937, -98.652453 29.380946, -98.652657 29.380955, -98.652835 29.380947, -98.653003 29.380925, -98.65319 29.380886, -98.653411 29.380809, -98.653625 29.380701, -98.6538 29.380576, -98.6539 29.3805, -98.654025 29.38036, -98.654123 29.380236, -98.654203 29.380096, -98.654266 29.379941, -98.654329 29.379677, -98.654347 29.37953, -98.654332 29.378737, -98.654326 29.377929, -98.654318 29.377525, -98.65431 29.377424, -98.654283 29.377323, -98.653975 29.376646, -98.652244 29.372917, -98.653163 29.372786, -98.653957 29.37266, -98.654268 29.372626, -98.655164 29.3725, -98.656228 29.372336, -98.656836 29.372248, -98.657064 29.372213, -98.657416 29.372159, -98.657457 29.372153, -98.657745 29.372118, -98.657857 29.372102, -98.657833 29.371987, -98.657833 29.371849, -98.657852 29.371827, -98.65789 29.371734, -98.657902 29.371624, -98.657896 29.371585, -98.657758 29.371459, -98.657726 29.371409, -98.657707 29.371332, -98.657701 29.371277, -98.65772 29.371161, -98.657708 29.370941, -98.657626 29.370672, -98.65757 29.370199, -98.657538 29.370072, -98.657337 29.369676, -98.657118 29.36934, -98.656785 29.368971, -98.656622 29.368812, -98.656465 29.368713, -98.656195 29.368581, -98.655893 29.368482, -98.655391 29.368371, -98.655328 29.368349, -98.655259 29.368278, -98.655127 29.367975, -98.654958 29.367733, -98.654901 29.367579, -98.654845 29.367321, -98.654775 29.367177, -98.65455 29.366847, -98.654267 29.366468, -98.654054 29.366143, -98.653627 29.365604, -98.653589 29.365471, -98.65352 29.365301, -98.653501 29.365251, -98.653344 29.365086, -98.653244 29.365009, -98.65298 29.364954, -98.652905 29.364894, -98.652873 29.364861, -98.652842 29.364806, -98.652829 29.36458, -98.652779 29.364464, -98.652647 29.364299, -98.652528 29.364184, -98.652384 29.36398, -98.652308 29.363909, -98.651913 29.36365, -98.651793 29.363534, -98.651737 29.363457, -98.651549 29.363281, -98.651511 29.363221, -98.651435 29.363166, -98.651297 29.363111, -98.650619 29.363006, -98.650487 29.362967, -98.650443 29.36294, -98.650305 29.362791, -98.65018 29.362555, -98.650142 29.362186, -98.650117 29.362032, -98.650035 29.361718, -98.649954 29.361564, -98.649897 29.36141, -98.649791 29.36103, -98.649784 29.360909, -98.649854 29.360678, -98.649942 29.360524, -98.650036 29.360387, -98.650262 29.36015, -98.650419 29.360018, -98.650557 29.35993, -98.650809 29.359831, -98.650991 29.359782, -98.651814 29.359468, -98.652084 29.359392, -98.652335 29.359287, -98.652492 29.359205, -98.652718 29.359133, -98.652875 29.359095, -98.652969 29.359089, -98.65307 29.359062, -98.653208 29.358974, -98.653679 29.358578, -98.654069 29.358275, -98.654188 29.358198, -98.654326 29.358143, -98.654527 29.358088, -98.655733 29.357946, -98.656246 29.357862, -98.656174 29.357687, -98.656014 29.357584, -98.655686 29.357512, -98.655406 29.357496, -98.654895 29.35759, -98.654311 29.357688, -98.653847 29.357868, -98.653223 29.35798, -98.652647 29.358104, -98.65208 29.358194, -98.651656 29.358214, -98.651248 29.358296, -98.650672 29.358459, -98.65008 29.358639, -98.649529 29.358853, -98.649233 29.359109, -98.649073 29.359417, -98.648873 29.359799, -98.648657 29.360085, -98.648457 29.360195, -98.648153 29.360311, -98.64777 29.360443, -98.647226 29.360559, -98.646482 29.360633, -98.644904 29.360605, -98.645144 29.360539, -98.645391 29.360431, -98.645579 29.360335, -98.645752 29.360207, -98.645905 29.360066, -98.646029 29.359925, -98.646224 29.359627, -98.646298 29.359475, -98.646373 29.359235, -98.646388 29.359083, -98.646394 29.358854, -98.646385 29.357587, -98.646384 29.356483, -98.646382 29.354895, -98.646382 29.354561, -98.646386 29.353459, -98.646382 29.352444, -98.646379 29.351688, -98.64638 29.350444, -98.64624 29.350444, -98.646102 29.3504, -98.646046 29.350345, -98.645864 29.350092, -98.645795 29.349965, -98.645757 29.349932, -98.645688 29.349893, -98.645487 29.349816, -98.645041 29.349503, -98.644947 29.34942, -98.644847 29.349288, -98.644809 29.349046, -98.64474 29.348782, -98.644734 29.348661, -98.64469 29.348353, -98.644609 29.348094, -98.644502 29.347896, -98.644471 29.347467, -98.644521 29.347186, -98.644584 29.346966, -98.644615 29.346674, -98.644641 29.346124, -98.644635 29.345492, -98.64456 29.344936, -98.644585 29.34482, -98.644635 29.344699, -98.644704 29.344567, -98.644717 29.34449, -98.644692 29.344397, -98.64461 29.344221, -98.644435 29.34372, -98.644315 29.343445, -98.644184 29.34263, -98.644197 29.342361, -98.644171 29.34219, -98.64387 29.341536, -98.643745 29.341293, -98.643739 29.341139, -98.64372 29.341051, -98.6435 29.340743, -98.643368 29.340583, -98.643255 29.340501, -98.643155 29.340413, -98.643092 29.340347, -98.643061 29.340297, -98.643017 29.340182, -98.642841 29.339351, -98.642823 29.339158, -98.642741 29.338982, -98.642735 29.338839, -98.642792 29.33874, -98.642766 29.338537, -98.64271 29.338327, -98.642597 29.338019, -98.642491 29.33759, -98.642403 29.337359, -98.64229 29.336979, -98.642284 29.336776, -98.642309 29.33649, -98.642315 29.336165, -98.642353 29.335918, -98.642372 29.33567, -98.642341 29.335406, -98.642322 29.335323, -98.642316 29.335131, -98.642473 29.334597, -98.642574 29.334372, -98.642593 29.334135, -98.642549 29.333728, -98.642492 29.333491, -98.642411 29.333299, -98.642342 29.33326, -98.642304 29.333216, -98.64226 29.333134, -98.642267 29.333101, -98.642317 29.333057, -98.642342 29.332963, -98.642367 29.332842, -98.642348 29.332715, -98.642242 29.33249, -98.642028 29.332231, -98.641991 29.332176, -98.641972 29.33211, -98.641978 29.331973, -98.64201 29.331907, -98.642029 29.331796, -98.642035 29.331631, -98.641941 29.331351, -98.641928 29.331191, -98.641884 29.331092, -98.641834 29.33101, -98.641477 29.330531, -98.641276 29.330377, -98.641131 29.330311, -98.640943 29.330245, -98.640485 29.330063, -98.640246 29.330057, -98.63992 29.33003, -98.639863 29.329964, -98.639826 29.329881, -98.639819 29.329661, -98.639895 29.329512, -98.639939 29.329402, -98.639951 29.329347, -98.639964 29.329177, -98.639927 29.32899, -98.639845 29.328836, -98.639732 29.328671, -98.639079 29.328137, -98.638859 29.328061, -98.6368 29.329574, -98.635803 29.330315, -98.635193 29.330775, -98.633959 29.331707, -98.633205 29.332253, -98.628573 29.335697, -98.628528 29.335731, -98.628485 29.335762, -98.625864 29.337714, -98.62563 29.337887, -98.624893 29.338435, -98.624499 29.338727, -98.624323 29.338343, -98.623697 29.336975, -98.622898 29.335723, -98.62188 29.33321, -98.621172 29.331437, -98.620851 29.329937, -98.620474 29.328973, -98.620132 29.327867, -98.620005 29.327437, -98.619909 29.327324, -98.619781 29.327274, -98.619535 29.327278, -98.619455 29.327082, -98.619379 29.326893, -98.61944 29.326889, -98.619562 29.326828, -98.619653 29.326716, -98.619653 29.326604, -98.619601 29.326478, -98.619358 29.325974, -98.618736 29.324747, -98.618033 29.323601, -98.617266 29.322949, -98.6166 29.322067, -98.615541 29.320955, -98.614713 29.320186, -98.614585 29.320066, -98.616123 29.319095, -98.616364 29.318939, -98.616678 29.318736, -98.617739 29.317999, -98.618439 29.317409, -98.61915 29.316755, -98.619536 29.316386, -98.620417 29.315431, -98.631805 29.301742, -98.63415 29.298943, -98.635784 29.296995, -98.636098 29.296636, -98.636387 29.296331, -98.638402 29.294638, -98.638069 29.294345, -98.635459 29.296539, -98.635372 29.296645, -98.635238 29.296821, -98.633794 29.298588, -98.631451 29.301457, -98.620234 29.315202, -98.620125 29.315374, -98.619507 29.316061, -98.618978 29.316599, -98.618352 29.317174, -98.617743 29.317675, -98.616743 29.318417, -98.616536 29.318552, -98.616154 29.318802, -98.615967 29.31892, -98.614757 29.319684, -98.614391 29.319915, -98.614184 29.320046, -98.613564 29.319535, -98.612517 29.318857, -98.611294 29.318243, -98.610881 29.318077, -98.610621 29.317989, -98.61011 29.317976, -98.609486 29.318081, -98.608817 29.318201, -98.60796 29.31836, -98.607088 29.318529, -98.606684 29.318659, -98.606463 29.318781, -98.606106 29.319092, -98.605632 29.319495, -98.604923 29.319992, -98.604752 29.320139, -98.60446 29.320395, -98.603594 29.321149, -98.602928 29.321709, -98.602084 29.322404, -98.601576 29.322746, -98.601155 29.323006, -98.601032 29.32334, -98.600916 29.323602, -98.600765 29.323804, -98.600603 29.323975, -98.60034 29.324168, -98.59857 29.325222, -98.598917 29.32492, -98.599183 29.324709, -98.599253 29.324577, -98.599258 29.324453, -98.599184 29.324365, -98.599023 29.324294, -98.598897 29.324304, -98.598498 29.324462, -98.598402 29.324505, -98.59773 29.32468, -98.596834 29.324913, -98.596394 29.325095, -98.592742 29.326607, -98.590937 29.327567, -98.586867 29.329238, -98.585665 29.329721, -98.585621 29.329674, -98.58554 29.329561, -98.585477 29.329473, -98.585438 29.32934, -98.585418 29.329272, -98.585419 29.329123, -98.586378 29.328728, -98.58571 29.328418, -98.585626 29.328481, -98.585462 29.328559, -98.5849 29.328785, -98.584501 29.328945, -98.584053 29.329136, -98.583824 29.329241, -98.5836 29.329353, -98.583497 29.329398, -98.58335 29.329447, -98.583302 29.329453, -98.583242 29.329462, -98.583113 29.329469, -98.582976 29.329457, -98.581894 29.329303, -98.579832 29.329009, -98.579664 29.328998, -98.579518 29.328997, -98.579234 29.329015, -98.579135 29.329011, -98.579122 29.329011, -98.578924 29.328988, -98.577633 29.328803, -98.577426 29.328773, -98.569947 29.32744, -98.56999 29.327419, -98.570091 29.327342, -98.570455 29.325394, -98.570498 29.325114, -98.570517 29.324784, -98.57053 29.32474, -98.570084 29.324432, -98.569783 29.324267, -98.569563 29.324091, -98.569506 29.324036, -98.569444 29.323931, -98.569406 29.323788, -98.569412 29.323606, -98.569393 29.323524, -98.569337 29.32337, -98.569218 29.323254, -98.569111 29.323128, -98.568992 29.322748, -98.568872 29.322572, -98.568772 29.322385, -98.56864 29.322214, -98.568408 29.321835, -98.568263 29.321631, -98.568113 29.321175, -98.567987 29.320724, -98.567949 29.320509, -98.567942 29.320289, -98.567937 29.320107, -98.56794 29.320062, -98.567952 29.319854, -98.567967 29.319618, -98.567993 29.319194, -98.567987 29.318974, -98.568012 29.318831, -98.568169 29.318567, -98.56847 29.318248, -98.568816 29.317774, -98.569085 29.316971, -98.56918 29.316828, -98.569192 29.316735, -98.56918 29.316658, -98.569117 29.316559, -98.56906 29.316437, -98.569035 29.316289, -98.569048 29.316113, -98.569092 29.31586, -98.56911 29.315585, -98.568953 29.315277, -98.568916 29.31515, -98.568884 29.314996, -98.568853 29.31416, -98.568853 29.313885, -98.568822 29.313604, -98.568746 29.313279, -98.568646 29.312581, -98.568526 29.312361, -98.568351 29.311788, -98.5683 29.311403, -98.568338 29.311211, -98.568407 29.31104, -98.56852 29.310864, -98.56869 29.310606, -98.568803 29.310374, -98.56889 29.31027, -98.568959 29.310215, -98.569129 29.310226, -98.569324 29.310341, -98.569656 29.310661, -98.569876 29.310826, -98.570058 29.310892, -98.57078 29.311447, -98.571408 29.312124, -98.571791 29.312603, -98.572036 29.313147, -98.572061 29.313235, -98.572249 29.313395, -98.572337 29.313455, -98.5724 29.313477, -98.572475 29.313483, -98.572544 29.313477, -98.57267 29.313428, -98.572739 29.31345, -98.572781 29.313468, -98.573416 29.309821, -98.573234 29.309747, -98.572958 29.309654, -98.572833 29.309637, -98.572625 29.309576, -98.572236 29.3094, -98.571765 29.309153, -98.571527 29.309021, -98.571382 29.308911, -98.571043 29.308729, -98.570692 29.308625, -98.569901 29.308196, -98.569371 29.307891, -98.56852 29.307299, -98.568281 29.307095, -98.568068 29.306826, -98.567896 29.306144, -98.567892 29.306127, -98.567892 29.30599, -98.566772 29.305837, -98.566815 29.305593, -98.5661 29.305491, -98.565646 29.305428, -98.562637 29.304973, -98.561003 29.304727, -98.559457 29.304491, -98.558378 29.304335, -98.557995 29.304282, -98.557264 29.304172, -98.55632 29.304041, -98.55624 29.304034, -98.556068 29.304013, -98.556132 29.303615, -98.556181 29.303311, -98.556529 29.301465, -98.55691 29.299194, -98.557145 29.297872, -98.557406 29.296458, -98.557688 29.294845, -98.557809 29.294155, -98.557946 29.293262, -98.558099 29.292413, -98.55793 29.292383, -98.557725 29.292347, -98.55652 29.292135, -98.555904 29.292064, -98.555656 29.292063, -98.555098 29.292108, -98.554657 29.292202, -98.554375 29.292291, -98.553954 29.292465, -98.553706 29.292605, -98.553415 29.292795, -98.552184 29.293752, -98.552114 29.293806, -98.551781 29.294065, -98.550804 29.294801, -98.550325 29.29517, -98.549666 29.295677, -98.548975 29.296221, -98.547316 29.297505, -98.547268 29.298728, -98.547272 29.298771, -98.547304 29.299095, -98.547444 29.299514, -98.5481 29.301913, -98.548597 29.303896, -98.548645 29.304032, -98.548668 29.304166, -98.548687 29.304221, -98.548951 29.305854, -98.549026 29.30623, -98.549087 29.306593, -98.549203 29.307287, -98.54934 29.308041, -98.549466 29.308733, -98.54951 29.308828, -98.549561 29.308895, -98.549611 29.308951, -98.549792 29.309063, -98.550043 29.309108, -98.550227 29.30912, -98.550316 29.30912, -98.550934 29.309066, -98.552336 29.308945, -98.553434 29.310687, -98.554272 29.311759, -98.554503 29.311689, -98.553753 29.31587, -98.553111 29.319356, -98.552619 29.319424, -98.551539 29.319572, -98.550309 29.319735, -98.549602 29.319747, -98.547667 29.319748, -98.544104 29.319749, -98.543014 29.319755, -98.543027 29.318529, -98.543017 29.317248, -98.543025 29.317125, -98.543046 29.317018, -98.543078 29.31694, -98.543124 29.316872, -98.543185 29.316811, -98.54323 29.316772, -98.543475 29.316634, -98.543577 29.316567, -98.543634 29.316506, -98.543675 29.316449, -98.543708 29.316374, -98.543737 29.316289, -98.543745 29.316207, -98.543747 29.315846, -98.543728 29.315772, -98.543718 29.315681, -98.543708 29.315619, -98.543692 29.315548, -98.543635 29.315393, -98.543593 29.31531, -98.543538 29.315227, -98.543468 29.315147, -98.543395 29.315072, -98.543054 29.314802, -98.543016 29.314774, -98.543036 29.314761, -98.54308 29.31473, -98.543134 29.314694, -98.5433 29.314618, -98.543365 29.314603, -98.543438 29.314594, -98.543512 29.31459, -98.543585 29.314587, -98.543656 29.314585, -98.543723 29.314586, -98.543792 29.31459, -98.543855 29.314594, -98.543956 29.314606, -98.543978 29.314618, -98.543974 29.314553, -98.543976 29.314453, -98.543973 29.314318, -98.543968 29.314251, -98.543966 29.314203, -98.543961 29.314108, -98.543958 29.314037, -98.543956 29.313966, -98.543958 29.313898, -98.543956 29.313831, -98.543956 29.313809, -98.543956 29.313758, -98.543959 29.313683, -98.543958 29.313608, -98.543959 29.313537, -98.543959 29.313464, -98.543959 29.313447, -98.54396 29.313391, -98.543962 29.31332, -98.543964 29.313246, -98.543966 29.313171, -98.543969 29.313093, -98.54397 29.313062, -98.543973 29.313015, -98.543976 29.312937, -98.543976 29.312859, -98.543976 29.31278, -98.543974 29.312699, -98.543972 29.312623, -98.543972 29.312546, -98.543973 29.312468, -98.543972 29.31239, -98.543971 29.312315, -98.54397 29.312231, -98.543971 29.312146, -98.543971 29.312059, -98.543972 29.311972, -98.543972 29.311903, -98.543972 29.311886, -98.543972 29.311805, -98.543972 29.311724, -98.543972 29.311644, -98.543972 29.311565, -98.543972 29.311486, -98.543972 29.311408, -98.543971 29.31133, -98.54397 29.311253, -98.54397 29.311179, -98.543973 29.311106, -98.543976 29.311034, -98.543979 29.310962, -98.543978 29.310891, -98.54394 29.310888, -98.543853 29.310882, -98.543786 29.31088, -98.543715 29.310879, -98.543565 29.310878, -98.543356 29.310885, -98.54329 29.310887, -98.543233 29.310887, -98.543153 29.31089, -98.543085 29.31086, -98.54304 29.310823, -98.543045 29.310811, -98.543046 29.310754, -98.543048 29.310688, -98.543053 29.310549, -98.543045 29.310498, -98.543056 29.310478, -98.543058 29.310411, -98.543059 29.310348, -98.543059 29.31029, -98.543058 29.310233, -98.543033 29.310078, -98.542938 29.310058, -98.542803 29.310061, -98.542723 29.31006, -98.54264 29.310058, -98.542556 29.310056, -98.542473 29.310056, -98.542319 29.310062, -98.542175 29.310078, -98.542055 29.310111, -98.541971 29.310139, -98.541889 29.310175, -98.541846 29.310105, -98.541756 29.309999, -98.541652 29.309904, -98.541535 29.309818, -98.541411 29.309744, -98.541279 29.309683, -98.541138 29.309636, -98.54099 29.309602, -98.540838 29.309589, -98.540689 29.309587, -98.54061 29.309587, -98.540609 29.30965, -98.5406 29.309697, -98.540604 29.309756, -98.540603 29.309814, -98.540605 29.30987, -98.540607 29.309928, -98.540609 29.309976, -98.540613 29.310036, -98.54061 29.310089, -98.54061 29.310147, -98.540614 29.310205, -98.54062 29.310287, -98.540621 29.310343, -98.540626 29.310401, -98.540626 29.31046, -98.540625 29.310513, -98.540625 29.310568, -98.540622 29.310619, -98.540622 29.31067, -98.540621 29.310739, -98.540617 29.310786, -98.540608 29.310831, -98.540563 29.310835, -98.540474 29.310837, -98.540384 29.310839, -98.540296 29.310841, -98.540202 29.310842, -98.540205 29.310918, -98.540207 29.311017, -98.540209 29.311118, -98.540212 29.31122, -98.540213 29.311322, -98.540214 29.311424, -98.540214 29.311523, -98.540214 29.311609, -98.540215 29.311622, -98.540216 29.311719, -98.540217 29.311818, -98.540218 29.311919, -98.540217 29.312022, -98.540218 29.312125, -98.540218 29.312229, -98.54022 29.312332, -98.540221 29.312433, -98.540221 29.312531, -98.540221 29.312627, -98.540221 29.312719, -98.540221 29.312808, -98.540222 29.312893, -98.540223 29.312968, -98.540225 29.313046, -98.540127 29.313041, -98.540036 29.313042, -98.539969 29.313043, -98.539891 29.313044, -98.539809 29.313043, -98.539809 29.313, -98.539809 29.312918, -98.53981 29.312835, -98.539812 29.312752, -98.539813 29.312668, -98.539814 29.312584, -98.539815 29.3125, -98.539816 29.312413, -98.539816 29.312329, -98.539815 29.312245, -98.539814 29.312162, -98.539813 29.31208, -98.539812 29.311998, -98.539811 29.311917, -98.53981 29.311835, -98.539809 29.311753, -98.539808 29.311669, -98.539806 29.311586, -98.539805 29.311505, -98.539806 29.311423, -98.539807 29.311342, -98.539807 29.311262, -98.539807 29.311183, -98.539807 29.311105, -98.539809 29.310948, -98.539809 29.310872, -98.539808 29.310843, -98.539808 29.310796, -98.539807 29.31072, -98.539806 29.310642, -98.539806 29.310562, -98.539805 29.310481, -98.539805 29.3104, -98.539805 29.310318, -98.539803 29.310237, -98.539802 29.310159, -98.5398 29.310083, -98.539799 29.309938, -98.539797 29.309869, -98.539795 29.309803, -98.539791 29.309745, -98.539789 29.309697, -98.53974 29.309605, -98.539677 29.309608, -98.53954 29.309622, -98.539403 29.309646, -98.539273 29.309687, -98.539143 29.309735, -98.539013 29.309786, -98.538881 29.309834, -98.538743 29.309873, -98.538602 29.309903, -98.538459 29.309922, -98.538317 29.309933, -98.538176 29.309938, -98.538036 29.309934, -98.537897 29.309917, -98.537761 29.309887, -98.53763 29.30984, -98.537507 29.309779, -98.537394 29.309707, -98.537287 29.309618, -98.537185 29.309524, -98.537084 29.309429, -98.536984 29.309334, -98.536883 29.309238, -98.536782 29.309142, -98.536681 29.309045, -98.536579 29.308948, -98.536478 29.308851, -98.536376 29.308754, -98.536274 29.308656, -98.536171 29.308558, -98.536067 29.30846, -98.535963 29.308363, -98.535859 29.308266, -98.535756 29.308169, -98.535653 29.308073, -98.535552 29.307979, -98.53546 29.307892, -98.535385 29.30782, -98.535326 29.307763, -98.535286 29.307725, -98.535201 29.307644, -98.535075 29.307747, -98.534665 29.308094, -98.534129 29.30853, -98.533605 29.308987, -98.533365 29.309225, -98.533251 29.309384, -98.533204 29.30946, -98.533034 29.309692, -98.532893 29.309974, -98.532736 29.310367, -98.532639 29.310707, -98.532613 29.310858, -98.532577 29.311153, -98.532585 29.311327, -98.53261 29.312977, -98.532601 29.314979, -98.532603 29.315492, -98.53261 29.317877, -98.532609 29.318141, -98.532608 29.318352, -98.532614 29.319306, -98.532617 29.319689, -98.532089 29.319687, -98.531135 29.319683, -98.527718 29.319704, -98.524887 29.319708, -98.524644 29.319708, -98.524447 29.319709, -98.522384 29.319707, -98.521861 29.319707, -98.520066 29.319694, -98.518844 29.319706, -98.51836 29.319716, -98.518339 29.319716, -98.518061 29.31969, -98.517587 29.319624, -98.517428 29.319623, -98.517371 29.319627, -98.517295 29.319649, -98.517272 29.319656, -98.517207 29.319705, -98.517181 29.319739, -98.517164 29.319777, -98.517159 29.319815, -98.517167 29.319891, -98.517229 29.320035, -98.516958 29.320037, -98.516749 29.319495, -98.516345 29.318231, -98.515018 29.319316, -98.513052 29.319211, -98.507735 29.318391, -98.505856 29.318064, -98.504622 29.318126, -98.503454 29.318187, -98.503437 29.318084, -98.502899 29.315033, -98.503282 29.314979, -98.503625 29.314903, -98.503929 29.314799, -98.504114 29.314649, -98.504219 29.314534, -98.504259 29.314384, -98.504218 29.314243, -98.5041 29.314116, -98.50377 29.314001, -98.503288 29.313949, -98.502721 29.314024, -98.502558 29.313099, -98.502414 29.312282, -98.502304 29.311661, -98.502292 29.311577, -98.502276 29.311464, -98.502266 29.311291, -98.502131 29.311907, -98.501784 29.315242, -98.501677 29.316201, -98.501426 29.318193, -98.501283 29.31933, -98.501265 29.319605, -98.501005 29.319645, -98.49984 29.319897, -98.498404 29.320202, -98.496616 29.320605, -98.495943 29.320752, -98.495253 29.320958, -98.49488 29.321041, -98.494319 29.321132, -98.494258 29.320969, -98.494224 29.320906, -98.494167 29.320856, -98.494123 29.320837, -98.494057 29.320832, -98.491659 29.320872, -98.488892 29.320919, -98.488651 29.32092, -98.485394 29.320939, -98.482731 29.32092, -98.48139 29.320918, -98.481391 29.319586, -98.48142 29.318999, -98.476853 29.318858, -98.476741 29.318515, -98.476472 29.317698, -98.476201 29.316873, -98.475929 29.316043, -98.475666 29.315222, -98.475389 29.314388, -98.475122 29.313563, -98.474874 29.312807, -98.47405 29.310238)))"} -{"geo_id":"36892","urban_area_code":"36892","name":"Harlingen, TX","lsad_name":"Harlingen, TX Urbanized Area","area_lsad_code":"75","mtfcc_feature_class_code":"G3500","type":"U","functional_status":"S","area_land_meters":214303050,"area_water_meters":2432294,"internal_point_lon":-97.7053245,"internal_point_lat":26.1666112,"internal_point_geom":"POINT(-97.7053245 26.1666112)","urban_area_geom":"MULTIPOLYGON(((-97.807789 26.248195, -97.807935 26.24827, -97.808257 26.248449, -97.809051 26.24883, -97.809583 26.24911, -97.809698 26.249172, -97.810003 26.24932, -97.81036 26.249497, -97.81061 26.24964, -97.811835 26.250225, -97.814526 26.251526, -97.815535 26.252067, -97.815694 26.264444, -97.815744 26.269272, -97.817417 26.269241, -97.817593 26.269238, -97.821271 26.269169, -97.82559 26.269092, -97.825538 26.265681, -97.825477 26.261843, -97.825473 26.261573, -97.825474 26.261501, -97.825472 26.261383, -97.825466 26.260737, -97.825448 26.259924, -97.826158 26.259926, -97.827149 26.25993, -97.827407 26.259928, -97.827417 26.260713, -97.828469 26.260702, -97.828462 26.259924, -97.829208 26.259922, -97.829677 26.259922, -97.829671 26.260964, -97.829677 26.262118, -97.829695 26.262206, -97.82976 26.262334, -97.830268 26.262462, -97.830918 26.26241, -97.831543 26.262389, -97.832178 26.262373, -97.832833 26.262364, -97.832832 26.262253, -97.832831 26.261927, -97.832822 26.261037, -97.832823 26.261015, -97.832818 26.260522, -97.833211 26.260691, -97.834159 26.26107, -97.836604 26.261981, -97.837002 26.262114, -97.837184 26.262169, -97.837163 26.258904, -97.83716 26.258863, -97.833489 26.258926, -97.833265 26.258915, -97.8328 26.258905, -97.83277 26.257793, -97.832771 26.256584, -97.832748 26.255625, -97.832012 26.255652, -97.831312 26.255662, -97.830585 26.255675, -97.82984 26.255705, -97.829636 26.255749, -97.829442 26.255754, -97.829462 26.255072, -97.829545 26.254028, -97.829512 26.253213, -97.829547 26.252352, -97.82953 26.251515, -97.82728 26.251528, -97.825358 26.251544, -97.82534 26.250772, -97.825329 26.250325, -97.825328 26.250276, -97.825318 26.249528, -97.825307 26.249074, -97.825307 26.249045, -97.8253 26.248707, -97.825298 26.248179, -97.825287 26.247334, -97.826272 26.247337, -97.826259 26.246485, -97.826227 26.24418, -97.826227 26.243705, -97.829442 26.243657, -97.829438 26.243293, -97.82943 26.242958, -97.82942 26.242883, -97.8294 26.242842, -97.829363 26.242813, -97.829313 26.242797, -97.829262 26.242799, -97.826603 26.242829, -97.826596 26.242394, -97.825969 26.242402, -97.825637 26.242407, -97.825222 26.242403, -97.825228 26.242846, -97.82524 26.243721, -97.825273 26.245734, -97.823291 26.24578, -97.820982 26.245818, -97.820981 26.245756, -97.820978 26.245544, -97.819748 26.24564, -97.818604 26.245621, -97.817843 26.245623, -97.817218 26.245583, -97.816642 26.245546, -97.815849 26.245491, -97.815449 26.24552, -97.815483 26.248155, -97.809994 26.248183, -97.809691 26.248185, -97.807789 26.248195)), ((-97.658303 26.109871, -97.658963 26.11035, -97.65902 26.110274, -97.660928 26.108172, -97.66158 26.107429, -97.661934 26.107055, -97.665129 26.103495, -97.665543 26.103066, -97.665638 26.102946, -97.665714 26.102851, -97.665921 26.102664, -97.666293 26.102273, -97.666537 26.101971, -97.666609 26.101783, -97.666659 26.101652, -97.666769 26.101437, -97.666927 26.101129, -97.667159 26.10065, -97.667513 26.099979, -97.667647 26.099753, -97.667927 26.09923, -97.668226 26.098619, -97.668354 26.098289, -97.668373 26.098196, -97.668501 26.097833, -97.668653 26.097442, -97.668915 26.09671, -97.669159 26.095873, -97.669239 26.095675, -97.669287 26.095609, -97.669525 26.095455, -97.669725 26.095363, -97.669656 26.095315, -97.669626 26.095295, -97.666579 26.093131, -97.665469 26.094351, -97.664934 26.09494, -97.664271 26.095672, -97.661778 26.098425, -97.661691 26.098521, -97.661265 26.098991, -97.6608 26.099504, -97.660779 26.099527, -97.65566 26.105238, -97.654187 26.106881, -97.654101 26.106977, -97.65362 26.106635, -97.652966 26.106133, -97.650465 26.108957, -97.651582 26.109785, -97.652637 26.110561, -97.654669 26.112055, -97.656165 26.113155, -97.656937 26.113723, -97.657623 26.114227, -97.657942 26.114462, -97.658073 26.11439, -97.658342 26.114142, -97.658756 26.113713, -97.659098 26.113301, -97.659384 26.112976, -97.659647 26.112712, -97.659939 26.112354, -97.660195 26.112096, -97.660317 26.111958, -97.660397 26.111831, -97.659678 26.111281, -97.658306 26.11029, -97.658178 26.110147, -97.65816 26.110064, -97.658208 26.109988, -97.658303 26.109871)), ((-97.67184 26.087219, -97.665659 26.082658, -97.665013 26.083384, -97.664794 26.083631, -97.662567 26.086116, -97.660603 26.088307, -97.660491 26.088432, -97.660422 26.088509, -97.66643 26.093019, -97.666579 26.093131, -97.666675 26.093023, -97.666821 26.092859, -97.666902 26.092768, -97.667356 26.092258, -97.670146 26.089121, -97.67097 26.088193, -97.67184 26.087219)), ((-97.786723 26.237831, -97.786754 26.241071, -97.786813 26.246491, -97.786814 26.246607, -97.786818 26.246944, -97.786833 26.248347, -97.791022 26.248313, -97.791192 26.248312, -97.79535 26.248281, -97.79698 26.248272, -97.801829 26.248236, -97.80181 26.245252, -97.800224 26.244483, -97.79895 26.243858, -97.797674 26.243243, -97.797169 26.243013, -97.796951 26.242872, -97.79515 26.241998, -97.793283 26.241055, -97.791087 26.239984, -97.790926 26.239909, -97.788383 26.238681, -97.786723 26.237831)), ((-97.826891 26.143807, -97.825513 26.141201, -97.824864 26.140234, -97.825394 26.140162, -97.825318 26.140078, -97.825202 26.13966, -97.824982 26.13939, -97.824921 26.139258, -97.824958 26.13857, -97.824891 26.138466, -97.824647 26.138295, -97.824561 26.138114, -97.824476 26.138075, -97.824385 26.138125, -97.824366 26.138185, -97.824043 26.138394, -97.824049 26.13846, -97.823988 26.13846, -97.823909 26.13846, -97.823915 26.1384, -97.823854 26.1384, -97.823915 26.13824, -97.824171 26.138042, -97.824403 26.137992, -97.824397 26.13791, -97.824256 26.137547, -97.824275 26.137189, -97.824281 26.13715, -97.824531 26.13715, -97.824531 26.137095, -97.824262 26.137057, -97.82375 26.137073, -97.82377 26.138616, -97.823802 26.141044, -97.823803 26.141158, -97.823811 26.141718, -97.82384 26.143819, -97.826709 26.143808, -97.826814 26.143807, -97.826891 26.143807)), ((-97.861837 26.15948, -97.861843 26.159621, -97.861846 26.159699, -97.861852 26.159879, -97.861876 26.15989, -97.861852 26.159893, -97.861876 26.161082, -97.86223 26.161073, -97.862271 26.161081, -97.862271 26.16078, -97.862271 26.159864, -97.862958 26.159858, -97.863735 26.159847, -97.864547 26.159838, -97.864546 26.159976, -97.864569 26.160737, -97.864576 26.160961, -97.864516 26.161044, -97.864589 26.161099, -97.864778 26.161132, -97.864821 26.16113, -97.865799 26.161102, -97.866126 26.161093, -97.866897 26.161047, -97.867437 26.161015, -97.867699 26.160856, -97.869172 26.16083, -97.869699 26.160822, -97.869852 26.160778, -97.869939 26.16078, -97.869945 26.161028, -97.869945 26.161052, -97.869961 26.161796, -97.869963 26.16187, -97.86997 26.162143, -97.869976 26.162575, -97.869989 26.163422, -97.869996 26.163873, -97.870003 26.164306, -97.870733 26.164261, -97.872923 26.164128, -97.873654 26.164085, -97.873813 26.171454, -97.873795 26.171564, -97.873754 26.171691, -97.874631 26.171681, -97.875205 26.171676, -97.877263 26.171642, -97.878141 26.171628, -97.87814 26.171605, -97.878139 26.171536, -97.878139 26.171513, -97.878137 26.171374, -97.878109 26.170043, -97.878086 26.168931, -97.878034 26.165633, -97.878013 26.164224, -97.878012 26.164163, -97.878011 26.164151, -97.878011 26.164115, -97.878011 26.164104, -97.878003 26.163681, -97.877996 26.163313, -97.877964 26.161697, -97.877952 26.161028, -97.87795 26.160936, -97.877945 26.160673, -97.877941 26.160483, -97.87793 26.159913, -97.877927 26.159723, -97.877926 26.159674, -97.875993 26.159702, -97.87587 26.159671, -97.874995 26.159614, -97.873892 26.159593, -97.873568 26.159568, -97.873036 26.159529, -97.87248 26.159469, -97.869896 26.159543, -97.869781 26.159545, -97.868542 26.159557, -97.867952 26.159563, -97.865868 26.159574, -97.864542 26.159579, -97.86454 26.159432, -97.864348 26.159435, -97.86415 26.159439, -97.864045 26.15944, -97.863716 26.159446, -97.863667 26.159448, -97.862765 26.159465, -97.862312 26.159474, -97.861837 26.15948)), ((-97.825048 26.228907, -97.825081 26.231448, -97.825102 26.233063, -97.829354 26.233026, -97.829269 26.228829, -97.825048 26.228907)), ((-97.807789 26.248195, -97.807558 26.248097, -97.807215 26.247927, -97.807119 26.247874, -97.807123 26.248198, -97.807789 26.248195)), ((-97.823723 26.135328, -97.823711 26.134522, -97.82371 26.134432, -97.823699 26.133592, -97.819445 26.133647, -97.819456 26.134541, -97.819465 26.135383, -97.819484 26.137066, -97.823519 26.137002, -97.82375 26.137073, -97.823744 26.136685, -97.823742 26.136612, -97.823723 26.135328)), ((-97.611954 26.183661, -97.611927 26.183642, -97.611277 26.183165, -97.610568 26.182645, -97.609891 26.18215, -97.609205 26.181646, -97.608306 26.181018, -97.606045 26.183523, -97.603191 26.186695, -97.606643 26.189311, -97.607161 26.189704, -97.607303 26.189832, -97.607317 26.189817, -97.607339 26.189793, -97.609828 26.187, -97.610205 26.186577, -97.610401 26.186722, -97.610932 26.187117, -97.61175 26.186233, -97.612346 26.185583, -97.612922 26.184954, -97.613242 26.184605, -97.612659 26.184178, -97.611979 26.18368, -97.611954 26.183661)), ((-97.824786 26.208674, -97.824787 26.208776, -97.824793 26.209228, -97.824803 26.210047, -97.824828 26.211947, -97.824863 26.214645, -97.824873 26.21543, -97.824885 26.216335, -97.824895 26.217147, -97.824906 26.217981, -97.824961 26.222155, -97.827022 26.222114, -97.829177 26.222072, -97.831135 26.222069, -97.83175 26.222045, -97.832184 26.222013, -97.832291 26.222016, -97.832372 26.222032, -97.832418 26.222051, -97.832465 26.22209, -97.8325 26.222142, -97.832525 26.222237, -97.832519 26.222871, -97.832523 26.223067, -97.832533 26.223118, -97.832537 26.223129, -97.83255 26.223154, -97.832573 26.223181, -97.832628 26.223204, -97.832736 26.223204, -97.833305 26.223231, -97.833288 26.222218, -97.833239 26.221789, -97.833166 26.221524, -97.833013 26.221277, -97.832909 26.221161, -97.830902 26.219026, -97.830676 26.218602, -97.83059 26.218311, -97.830541 26.217518, -97.830504 26.213429, -97.830547 26.213214, -97.831974 26.209334, -97.832011 26.209103, -97.831945 26.208538, -97.829207 26.208593, -97.82902 26.208597, -97.824786 26.208674)), ((-97.686457 26.092502, -97.68649 26.09239, -97.68643 26.092358, -97.686241 26.092058, -97.686163 26.091828, -97.686097 26.091564, -97.686015 26.09105, -97.68585 26.090557, -97.685665 26.08996, -97.6855 26.089611, -97.685274 26.089282, -97.684945 26.088932, -97.684327 26.088356, -97.683957 26.088192, -97.683299 26.08813, -97.682558 26.088069, -97.682023 26.088089, -97.681438 26.08821, -97.681508 26.088131, -97.681963 26.087618, -97.682979 26.086467, -97.683229 26.086184, -97.684111 26.085187, -97.677887 26.080523, -97.677123 26.081368, -97.676639 26.081902, -97.676348 26.082225, -97.676075 26.082527, -97.675848 26.082778, -97.67445 26.084323, -97.673028 26.085898, -97.671874 26.087175, -97.67184 26.087219, -97.676151 26.09038, -97.676303 26.090491, -97.677255 26.091187, -97.677311 26.091228, -97.677355 26.09126, -97.678685 26.092233, -97.680331 26.093436, -97.682195 26.094799, -97.684311 26.096357, -97.68442 26.096437, -97.684476 26.096478, -97.687579 26.09875, -97.68768 26.098824, -97.688341 26.099308, -97.689485 26.100148, -97.690629 26.100985, -97.693154 26.102834, -97.693446 26.103047, -97.693777 26.103289, -97.694184 26.103585, -97.694801 26.104035, -97.696143 26.105015, -97.696906 26.105576, -97.699449 26.102771, -97.700929 26.10114, -97.701068 26.101013, -97.701142 26.100975, -97.701369 26.100554, -97.701523 26.100359, -97.701481 26.100327, -97.701335 26.100195, -97.701323 26.100129, -97.701256 26.09992, -97.701213 26.099865, -97.701054 26.099705, -97.700987 26.099584, -97.700969 26.099315, -97.700985 26.09926, -97.700029 26.098566, -97.699948 26.098515, -97.698979 26.097806, -97.697569 26.096784, -97.697194 26.096512, -97.695518 26.095252, -97.694861 26.094758, -97.694344 26.094369, -97.692896 26.093303, -97.692814 26.093382, -97.692241 26.094037, -97.691948 26.094356, -97.691753 26.094549, -97.691698 26.094576, -97.691613 26.094587, -97.691516 26.09456, -97.691162 26.094417, -97.691083 26.094395, -97.69087 26.094406, -97.69023 26.09451, -97.690089 26.094554, -97.689791 26.094665, -97.689382 26.094912, -97.689279 26.094945, -97.689004 26.094901, -97.688736 26.094885, -97.688663 26.094835, -97.687639 26.093971, -97.687584 26.093905, -97.68742 26.093558, -97.686725 26.092727, -97.686609 26.092606, -97.68653 26.092546, -97.686457 26.092502)), ((-97.646357 26.162275, -97.646189 26.162144, -97.646082 26.16206, -97.645899 26.161917, -97.64076 26.167317, -97.640652 26.16743, -97.640104 26.167995, -97.639502 26.168709, -97.639836 26.168999, -97.639973 26.169074, -97.640072 26.169129, -97.6413 26.167764, -97.646357 26.162275)), ((-97.607018 26.170774, -97.606898 26.170682, -97.601864 26.176292, -97.601959 26.176361, -97.602085 26.176453, -97.608306 26.181018, -97.611278 26.177713, -97.61307 26.175755, -97.613383 26.175402, -97.612761 26.174948, -97.610083 26.172962, -97.607203 26.170855, -97.607161 26.170844, -97.607018 26.170774)), ((-97.606101 26.16329, -97.605582 26.163858, -97.604755 26.164581, -97.603557 26.165121, -97.60243 26.165562, -97.602381 26.16559, -97.601331 26.166184, -97.601113 26.166328, -97.606898 26.170682, -97.606927 26.17065, -97.606968 26.170548, -97.606975 26.170426, -97.606978 26.170412, -97.606986 26.170364, -97.607018 26.170297, -97.607061 26.17024, -97.607523 26.169741, -97.608187 26.168951, -97.608328 26.168723, -97.608451 26.168445, -97.608538 26.168167, -97.608584 26.167893, -97.608598 26.167638, -97.60856 26.167288, -97.608351 26.166628, -97.608141 26.166222, -97.607669 26.165438, -97.606101 26.16329)), ((-97.737783 26.240445, -97.737766 26.243971, -97.741751 26.243994, -97.741775 26.241647, -97.741788 26.240422, -97.738398 26.240441, -97.737783 26.240445)), ((-97.825035 26.227928, -97.824961 26.222155, -97.82262 26.222201, -97.820631 26.22224, -97.820641 26.223041, -97.820718 26.228987, -97.825048 26.228907, -97.825035 26.227928)), ((-97.763151 26.21139, -97.763575 26.213015, -97.764585 26.213013, -97.764943 26.213019, -97.76496 26.214358, -97.765615 26.214356, -97.765759 26.21437, -97.765986 26.214343, -97.765965 26.21139, -97.765721 26.21139, -97.765583 26.21139, -97.764925 26.21139, -97.763624 26.21139, -97.763562 26.21139, -97.763151 26.21139)), ((-97.801829 26.248236, -97.801855 26.251299, -97.801864 26.252336, -97.801865 26.252421, -97.80187 26.253049, -97.801871 26.253144, -97.801876 26.253767, -97.801877 26.253874, -97.801882 26.254485, -97.801883 26.254576, -97.801888 26.255218, -97.801889 26.255298, -97.801894 26.255926, -97.801895 26.256018, -97.8019 26.256649, -97.801901 26.256751, -97.801967 26.264509, -97.803317 26.264503, -97.804564 26.264497, -97.807321 26.264484, -97.807301 26.262915, -97.8072 26.25453, -97.807199 26.25444, -97.807191 26.25381, -97.80719 26.253725, -97.807182 26.253091, -97.807181 26.253007, -97.807173 26.252363, -97.807172 26.252282, -97.807164 26.25165, -97.807163 26.251567, -97.807122 26.248246, -97.807123 26.248198, -97.80504 26.248213, -97.801829 26.248236)), ((-97.68412 26.156992, -97.682018 26.160121, -97.680614 26.15911, -97.67963 26.158403, -97.678559 26.157632, -97.67777 26.157064, -97.678293 26.15642, -97.678075 26.156274, -97.678102 26.156249, -97.678147 26.156206, -97.678194 26.156158, -97.678242 26.156109, -97.678292 26.156056, -97.678343 26.156001, -97.678416 26.155918, -97.678463 26.155865, -97.678512 26.155809, -97.678586 26.155724, -97.678652 26.155644, -97.678689 26.155598, -97.678726 26.155553, -97.678782 26.155485, -97.678816 26.155441, -97.678852 26.155394, -97.678893 26.155341, -97.678935 26.155288, -97.679027 26.155187, -97.679037 26.155176, -97.67908 26.155133, -97.679132 26.155076, -97.679182 26.155016, -97.679229 26.154962, -97.679303 26.154886, -97.679349 26.154835, -97.679407 26.154756, -97.679435 26.154703, -97.679448 26.15465, -97.679416 26.154579, -97.679362 26.154554, -97.679275 26.154564, -97.679224 26.154591, -97.679173 26.154628, -97.679126 26.15467, -97.679085 26.154712, -97.679045 26.154754, -97.679002 26.154802, -97.678957 26.154851, -97.678916 26.1549, -97.678875 26.15495, -97.678833 26.154997, -97.678799 26.155032, -97.678792 26.15504, -97.678748 26.155083, -97.678703 26.155127, -97.678654 26.155172, -97.678609 26.155214, -97.678564 26.155253, -97.678521 26.155291, -97.678479 26.155332, -97.678436 26.155381, -97.678396 26.155432, -97.678358 26.155484, -97.678319 26.155536, -97.678281 26.155585, -97.678244 26.155629, -97.678207 26.155669, -97.678167 26.155712, -97.678126 26.155755, -97.678087 26.155797, -97.678051 26.155842, -97.678016 26.155887, -97.677978 26.155935, -97.677937 26.155987, -97.677895 26.156041, -97.677852 26.156095, -97.677835 26.156114, -97.67741 26.15583, -97.676887 26.156428, -97.67607 26.15584, -97.675436 26.15537, -97.674035 26.15433, -97.673903 26.154232, -97.673752 26.154401, -97.673646 26.154324, -97.673498 26.154488, -97.673452 26.154454, -97.67333 26.154592, -97.671801 26.156301, -97.671183 26.156991, -97.67053 26.156507, -97.66986 26.156019, -97.669193 26.155542, -97.668526 26.155055, -97.666063 26.157785, -97.666719 26.158269, -97.667387 26.158755, -97.667413 26.158775, -97.668046 26.159246, -97.668529 26.159596, -97.668723 26.159737, -97.666242 26.162489, -97.663736 26.16527, -97.663167 26.164835, -97.662644 26.164436, -97.660044 26.162451, -97.658535 26.161298, -97.657787 26.160727, -97.658306 26.16015, -97.65905 26.159335, -97.660108 26.158177, -97.660425 26.15783, -97.660707 26.157515, -97.661026 26.15716, -97.661501 26.156612, -97.66207 26.155925, -97.662626 26.1553, -97.662938 26.154955, -97.66393 26.153883, -97.664451 26.153309, -97.665483 26.15216, -97.665665 26.151952, -97.665858 26.151721, -97.666284 26.15117, -97.666495 26.150903, -97.667199 26.150114, -97.667323 26.149957, -97.667482 26.149805, -97.667554 26.149726, -97.667625 26.149646, -97.667852 26.149379, -97.668221 26.148957, -97.668382 26.148784, -97.668903 26.148224, -97.669809 26.147223, -97.670211 26.146801, -97.670579 26.146444, -97.670611 26.146417, -97.671358 26.145489, -97.670721 26.144686, -97.672737 26.143951, -97.673059 26.143595, -97.67342 26.143196, -97.673496 26.143112, -97.673824 26.142749, -97.673997 26.142558, -97.67432 26.142201, -97.675215 26.141211, -97.675448 26.140953, -97.67577 26.140597, -97.675875 26.140487, -97.676063 26.14029, -97.676183 26.140106, -97.676296 26.139872, -97.676614 26.138805, -97.676641 26.138557, -97.676614 26.138386, -97.676574 26.138255, -97.67654 26.138195, -97.67643 26.138088, -97.676076 26.137811, -97.674967 26.137134, -97.67469 26.136934, -97.674521 26.136734, -97.67432 26.136411, -97.674074 26.135995, -97.673982 26.135734, -97.673874 26.135487, -97.673782 26.13538, -97.674211 26.135686, -97.675042 26.136278, -97.675253 26.136491, -97.675672 26.136824, -97.676733 26.137587, -97.677005 26.137899, -97.677086 26.138056, -97.677652 26.138455, -97.677623 26.138369, -97.677604 26.138255, -97.677587 26.138154, -97.677453 26.137964, -97.677742 26.138155, -97.679032 26.136979, -97.682889 26.132729, -97.682305 26.132278, -97.68213 26.132148, -97.681677 26.131812, -97.680645 26.131067, -97.680463 26.131143, -97.677055 26.132567, -97.674583 26.133599, -97.674259 26.133778, -97.674132 26.133866, -97.673934 26.134004, -97.673102 26.134905, -97.671975 26.134033, -97.671719 26.134119, -97.667465 26.138864, -97.662338 26.144598, -97.662006 26.144923, -97.661723 26.145315, -97.661624 26.145241, -97.661511 26.145155, -97.661386 26.145058, -97.661284 26.14498, -97.660926 26.144705, -97.659616 26.14374, -97.659073 26.143346, -97.658629 26.143011, -97.65834 26.142793, -97.658161 26.142646, -97.658014 26.142527, -97.657567 26.14212, -97.657097 26.141686, -97.657203 26.141563, -97.659588 26.138928, -97.659905 26.138625, -97.659942 26.138542, -97.659954 26.138399, -97.659784 26.137634, -97.659808 26.137464, -97.659954 26.137178, -97.661326 26.135659, -97.661058 26.135468, -97.660913 26.13537, -97.660553 26.135117, -97.660168 26.134847, -97.660032 26.134752, -97.659743 26.134539, -97.659158 26.134107, -97.65669 26.132283, -97.656169 26.131899, -97.656113 26.131858, -97.656055 26.131815, -97.65603 26.131797, -97.655875 26.131682, -97.655766 26.131602, -97.655836 26.131478, -97.655745 26.131388, -97.6567 26.129577, -97.657103 26.129034, -97.657121 26.12901, -97.657634 26.128427, -97.65806 26.12808, -97.658603 26.127739, -97.659188 26.127475, -97.660658 26.127046, -97.661158 26.126997, -97.661487 26.126804, -97.661609 26.126766, -97.661719 26.126782, -97.661755 26.126881, -97.661794 26.126882, -97.661871 26.126881, -97.661981 26.126722, -97.661931 26.126686, -97.661957 26.126652, -97.661969 26.126629, -97.661985 26.12658, -97.661978 26.12654, -97.661954 26.126481, -97.661652 26.126243, -97.661313 26.12601, -97.659498 26.124676, -97.659223 26.124532, -97.659117 26.124511, -97.659075 26.124486, -97.658919 26.124379, -97.658759 26.124261, -97.657814 26.123562, -97.652824 26.119912, -97.648371 26.116672, -97.646589 26.115375, -97.646532 26.115439, -97.646401 26.115586, -97.646339 26.115551, -97.646236 26.115479, -97.646086 26.11536, -97.646017 26.115305, -97.645699 26.115074, -97.64536 26.114823, -97.645476 26.114674, -97.645637 26.114531, -97.650465 26.108957, -97.645412 26.105212, -97.6479 26.102421, -97.64752 26.10217, -97.64724 26.101966, -97.647002 26.101779, -97.6466 26.101498, -97.645162 26.100419, -97.64504 26.100336, -97.644973 26.10032, -97.644882 26.100364, -97.644467 26.100595, -97.644302 26.10071, -97.642931 26.101486, -97.642406 26.101766, -97.642266 26.10186, -97.642291 26.101997, -97.642303 26.102157, -97.642327 26.102256, -97.642437 26.102586, -97.642668 26.103021, -97.642717 26.10317, -97.642704 26.103395, -97.642662 26.103533, -97.642406 26.104028, -97.642192 26.104402, -97.641887 26.104897, -97.641667 26.105156, -97.640411 26.106525, -97.640314 26.10663, -97.638435 26.10876, -97.638137 26.10898, -97.637906 26.10913, -97.638851 26.109823, -97.640317 26.110898, -97.638169 26.113287, -97.636148 26.115535, -97.633313 26.112924, -97.63232 26.112169, -97.631814 26.111817, -97.631467 26.111547, -97.631077 26.111272, -97.630796 26.111057, -97.630242 26.11066, -97.630228 26.110651, -97.62899 26.111956, -97.624544 26.108695, -97.622749 26.107378, -97.622644 26.107301, -97.620612 26.105807, -97.616138 26.10252, -97.619014 26.09977, -97.621697 26.097205, -97.625511 26.093559, -97.625741 26.0932, -97.625937 26.092894, -97.626177 26.092201, -97.626177 26.090204, -97.626336 26.08949, -97.626481 26.08923, -97.626683 26.088873, -97.627136 26.088287, -97.628042 26.087329, -97.628553 26.086747, -97.6287 26.086582, -97.630283 26.084787, -97.63222 26.082694, -97.632779 26.082089, -97.632899 26.082212, -97.63554 26.08379, -97.636942 26.084628, -97.638323 26.082824, -97.640084 26.080726, -97.641572 26.079073, -97.64403 26.076344, -97.643267 26.075781, -97.64159 26.074544, -97.640933 26.07406, -97.640338 26.073621, -97.640736 26.073148, -97.641286 26.072488, -97.642133 26.071539, -97.645308 26.067983, -97.645463 26.067809, -97.647971 26.065, -97.653963 26.058287, -97.655151 26.056955, -97.655427 26.056646, -97.655669 26.056375, -97.657822 26.053963, -97.65847 26.053241, -97.659198 26.052424, -97.660542 26.05092, -97.662128 26.051813, -97.662476 26.0521, -97.662774 26.052302, -97.663085 26.05246, -97.663219 26.052594, -97.663389 26.052838, -97.663682 26.053037, -97.665472 26.051059, -97.666029 26.050444, -97.666657 26.049729, -97.667304 26.050218, -97.66737 26.050263, -97.667405 26.050263, -97.667432 26.050255, -97.667455 26.05024, -97.667642 26.050043, -97.668049 26.049613, -97.668423 26.049221, -97.668751 26.049471, -97.669459 26.050096, -97.670743 26.051038, -97.669405 26.052287, -97.666407 26.055104, -97.66077 26.060375, -97.660892 26.060463, -97.661111 26.060221, -97.661867 26.059539, -97.661976 26.059424, -97.662409 26.059038, -97.662805 26.058653, -97.663616 26.057905, -97.666242 26.0555, -97.666584 26.055253, -97.667772 26.054169, -97.66793 26.054042, -97.670318 26.055782, -97.670428 26.055853, -97.670491 26.055901, -97.67266 26.053478, -97.673218 26.052854, -97.674403 26.053721, -97.674627 26.053921, -97.674839 26.054158, -97.674977 26.05437, -97.675102 26.054582, -97.675189 26.054794, -97.675264 26.055093, -97.675276 26.055177, -97.675301 26.055343, -97.675327 26.055443, -97.675384 26.055594, -97.675404 26.055635, -97.675438 26.055715, -97.675472 26.055789, -97.675502 26.055839, -97.675599 26.055971, -97.675702 26.056086, -97.675953 26.056309, -97.676111 26.056429, -97.676684 26.056845, -97.676758 26.056899, -97.677298 26.057289, -97.677372 26.057343, -97.677922 26.057748, -97.677985 26.057794, -97.678525 26.058184, -97.678604 26.058244, -97.679294 26.058747, -97.679787 26.059106, -97.679886 26.059178, -97.681435 26.0603, -97.682292 26.060921, -97.683872 26.062066, -97.688411 26.057047, -97.688477 26.056688, -97.688528 26.056282, -97.68866 26.055547, -97.689723 26.049979, -97.690823 26.044219, -97.692908 26.044306, -97.694562 26.044225, -97.695669 26.04417, -97.69815 26.044048, -97.698406 26.044035, -97.698407 26.045369, -97.699434 26.045415, -97.700602 26.045547, -97.7001 26.057895, -97.700805 26.057826, -97.703308 26.057582, -97.705288 26.045678, -97.705808 26.045701, -97.709531 26.04659, -97.709537 26.050724, -97.711087 26.050676, -97.711087 26.050263, -97.711911 26.050352, -97.711917 26.051554, -97.711377 26.051554, -97.711309 26.051557, -97.711249 26.051565, -97.711197 26.051584, -97.711154 26.051614, -97.711123 26.051654, -97.711105 26.051701, -97.711099 26.051754, -97.711108 26.052429, -97.711113 26.05326, -97.713029 26.053252, -97.714242 26.053247, -97.714232 26.052568, -97.714215 26.052517, -97.714184 26.052474, -97.714138 26.052441, -97.714072 26.05242, -97.713365 26.05242, -97.713022 26.052421, -97.712982 26.047713, -97.713323 26.047822, -97.717055 26.049017, -97.717195 26.048846, -97.717366 26.048736, -97.717689 26.048576, -97.717805 26.048571, -97.717951 26.048593, -97.718176 26.048708, -97.718463 26.048824, -97.720723 26.049616, -97.721479 26.049858, -97.722039 26.049995, -97.722399 26.050056, -97.724318 26.050209, -97.724348 26.050182, -97.724397 26.05011, -97.724428 26.05011, -97.724431 26.0498, -97.722565 26.049562, -97.722019 26.049462, -97.721324 26.049289, -97.720717 26.04908, -97.717043 26.047746, -97.715664 26.047246, -97.713415 26.046505, -97.712972 26.04636, -97.712795 26.046302, -97.71277 26.044692, -97.711215 26.044062, -97.7107 26.044219, -97.710225 26.044323, -97.710146 26.044318, -97.710072 26.044296, -97.709999 26.044257, -97.709808 26.044176, -97.709573 26.044076, -97.708738 26.04368, -97.70858 26.043619, -97.708366 26.043553, -97.707209 26.043306, -97.706935 26.04324, -97.70677 26.043207, -97.706621 26.043187, -97.706621 26.043167, -97.706592 26.043033, -97.705763 26.043028, -97.704636 26.043185, -97.704069 26.043255, -97.701869 26.043355, -97.701841 26.043555, -97.70136 26.043587, -97.699782 26.043064, -97.699474 26.042952, -97.699161 26.042839, -97.699161 26.042812, -97.69837 26.042704, -97.697384 26.04253, -97.696851 26.042447, -97.696272 26.042408, -97.695773 26.042385, -97.695791 26.042498, -97.695676 26.042498, -97.694701 26.042591, -97.694092 26.04269, -97.693861 26.042707, -97.693593 26.042712, -97.692581 26.042668, -97.691765 26.042553, -97.69129 26.042476, -97.690967 26.042454, -97.690876 26.042415, -97.690705 26.042239, -97.690224 26.041903, -97.689523 26.041441, -97.689316 26.041287, -97.689133 26.041177, -97.688396 26.040638, -97.687866 26.040181, -97.687568 26.039867, -97.68744 26.039708, -97.687385 26.039597, -97.68736 26.039482, -97.687367 26.038695, -97.687421 26.038183, -97.687421 26.038035, -97.687397 26.037969, -97.687251 26.037858, -97.686648 26.037259, -97.686514 26.037143, -97.686374 26.037005, -97.686337 26.036983, -97.686221 26.037, -97.677728 26.044847, -97.677489 26.045075, -97.677123 26.045081, -97.675248 26.045113, -97.671633 26.045149, -97.670835 26.045052, -97.670414 26.045001, -97.669531 26.044956, -97.669588 26.044129, -97.669078 26.04374, -97.667739 26.043052, -97.666536 26.042468, -97.666113 26.042056, -97.665496 26.04153, -97.664576 26.040742, -97.663913 26.040234, -97.663468 26.039855, -97.663034 26.039685, -97.662868 26.039555, -97.662393 26.039234, -97.662072 26.038918, -97.661645 26.038426, -97.661263 26.037995, -97.660714 26.037522, -97.660501 26.037129, -97.660043 26.036614, -97.659468 26.035555, -97.657759 26.037624, -97.657614 26.037424, -97.657239 26.03696, -97.657205 26.036919, -97.656842 26.03647, -97.656375 26.036078, -97.655892 26.035673, -97.65587 26.035655, -97.65576 26.035563, -97.655706 26.035517, -97.65551 26.0354, -97.654969 26.035078, -97.649874 26.04303, -97.649747 26.04323, -97.648833 26.044655, -97.644994 26.050807, -97.644864 26.051016, -97.644438 26.051714, -97.644346 26.051846, -97.640816 26.057552, -97.638634 26.061002, -97.638091 26.06044, -97.637374 26.059699, -97.636228 26.058514, -97.636064 26.05831, -97.635808 26.058084, -97.635661 26.058013, -97.635546 26.058002, -97.635412 26.058029, -97.63479 26.058309, -97.634424 26.058436, -97.633559 26.058628, -97.633358 26.058639, -97.633169 26.058612, -97.633023 26.058556, -97.632883 26.058457, -97.632816 26.058386, -97.6337 26.056664, -97.634371 26.055277, -97.634463 26.055117, -97.635432 26.053104, -97.635566 26.052873, -97.635664 26.052658, -97.635944 26.052108, -97.637121 26.049709, -97.638371 26.047238, -97.638481 26.047013, -97.638518 26.046914, -97.6385 26.046809, -97.638189 26.046495, -97.638012 26.046264, -97.637014 26.045042, -97.63691 26.044877, -97.636874 26.044849, -97.643345 26.030312, -97.643594 26.02977, -97.643713 26.029791, -97.644068 26.029055, -97.643904 26.029054, -97.64372 26.028958, -97.642897 26.028528, -97.642133 26.028123, -97.641666 26.027786, -97.641273 26.027325, -97.640988 26.026801, -97.640736 26.026508, -97.640456 26.026294, -97.639753 26.025607, -97.639478 26.02557, -97.639532 26.025623, -97.63956 26.025675, -97.639545 26.02574, -97.639816 26.025985, -97.640029 26.02614, -97.640171 26.026237, -97.640182 26.02626, -97.640186 26.026286, -97.640172 26.026352, -97.640008 26.026796, -97.639982 26.026922, -97.639925 26.027068, -97.639838 26.027318, -97.639685 26.027713, -97.639638 26.027824, -97.639013 26.027106, -97.638795 26.026822, -97.638011 26.02586, -97.637726 26.025479, -97.637615 26.025337, -97.636643 26.024112, -97.636515 26.024439, -97.636394 26.02481, -97.636289 26.025071, -97.636198 26.025351, -97.63588 26.025749, -97.635607 26.026022, -97.635471 26.026248, -97.63538 26.026385, -97.635336 26.026613, -97.635428 26.026911, -97.635382 26.027133, -97.634218 26.026768, -97.633351 26.026496, -97.633367 26.026442, -97.634432 26.023048, -97.635052 26.021025, -97.634952 26.020801, -97.634831 26.020366, -97.634781 26.019602, -97.634673 26.019083, -97.634601 26.018111, -97.634565 26.01688, -97.634601 26.015325, -97.634673 26.013997, -97.634736 26.012788, -97.634671 26.012761, -97.634427 26.012608, -97.633897 26.012271, -97.632236 26.011282, -97.631626 26.010925, -97.630303 26.010261, -97.629339 26.009782, -97.629779 26.013473, -97.626566 26.012278, -97.626411 26.01267, -97.62635 26.0132, -97.626228 26.01373, -97.626091 26.014131, -97.626027 26.014265, -97.624361 26.020819, -97.623644 26.023434, -97.622409 26.029455, -97.619028 26.0458, -97.61917 26.04611, -97.619219 26.046198, -97.619225 26.046225, -97.6192 26.046374, -97.619219 26.046451, -97.619261 26.046517, -97.619322 26.046577, -97.619383 26.046693, -97.619383 26.046759, -97.619341 26.047029, -97.619152 26.047678, -97.618939 26.048052, -97.61886 26.048113, -97.618457 26.048626, -97.617522 26.052885, -97.617507 26.053023, -97.617459 26.053163, -97.616549 26.057537, -97.616516 26.057693, -97.616304 26.058695, -97.616647 26.058641, -97.616805 26.058597, -97.617043 26.058553, -97.617147 26.058504, -97.617274 26.05846, -97.61739 26.058388, -97.6175 26.058382, -97.617932 26.058267, -97.618127 26.058184, -97.618871 26.058096, -97.619011 26.058085, -97.620595 26.05809, -97.620802 26.058073, -97.621216 26.057985, -97.621533 26.057946, -97.621728 26.057913, -97.622453 26.057753, -97.622819 26.057599, -97.622922 26.057538, -97.622965 26.0575, -97.623141 26.057252, -97.623312 26.057269, -97.623331 26.057252, -97.62333 26.057221, -97.623309 26.057202, -97.623493 26.057211, -97.624067 26.057212, -97.624065 26.057227, -97.623835 26.059004, -97.623772 26.059462, -97.623462 26.061744, -97.623421 26.062675, -97.623428 26.062978, -97.62344 26.063475, -97.623382 26.063842, -97.623378 26.063867, -97.623252 26.064376, -97.623047 26.064384, -97.623023 26.064429, -97.623011 26.064489, -97.622974 26.064522, -97.622706 26.064588, -97.622517 26.064599, -97.621957 26.064671, -97.621262 26.064814, -97.620769 26.064958, -97.620007 26.065228, -97.619593 26.065387, -97.619142 26.06558, -97.618783 26.065784, -97.618411 26.066021, -97.618015 26.066301, -97.617175 26.067061, -97.616974 26.067309, -97.61673 26.067639, -97.616712 26.067782, -97.616481 26.068283, -97.616438 26.068355, -97.61642 26.068415, -97.616389 26.068553, -97.616347 26.069131, -97.616378 26.070088, -97.616408 26.070639, -97.616445 26.070837, -97.616469 26.070908, -97.61647 26.070947, -97.616445 26.071029, -97.616451 26.071217, -97.616537 26.071475, -97.617081 26.07068, -97.618993 26.067213, -97.619153 26.067006, -97.619728 26.066671, -97.620223 26.066447, -97.620654 26.06632, -97.622075 26.066064, -97.622745 26.065889, -97.622871 26.065914, -97.622672 26.066661, -97.622453 26.067483, -97.622372 26.067655, -97.622598 26.067676, -97.622671 26.067488, -97.622691 26.067483, -97.622726 26.067488, -97.623232 26.067697, -97.623482 26.067917, -97.623652 26.068253, -97.623701 26.068402, -97.623707 26.068578, -97.623677 26.069062, -97.623616 26.069425, -97.62358 26.069827, -97.623525 26.070047, -97.623458 26.070432, -97.622972 26.072474, -97.62296 26.072489, -97.62296 26.072573, -97.622978 26.072628, -97.623009 26.072661, -97.622997 26.073569, -97.623082 26.073938, -97.623107 26.073977, -97.623247 26.074654, -97.623449 26.075275, -97.623607 26.075677, -97.623999 26.076157, -97.624034 26.0762, -97.624052 26.076211, -97.624019 26.076311, -97.623985 26.076414, -97.623949 26.076453, -97.623888 26.076519, -97.623815 26.076585, -97.623784 26.076585, -97.623638 26.07648, -97.623577 26.076332, -97.623557 26.07633, -97.623419 26.076172, -97.623413 26.076139, -97.623419 26.076068, -97.623315 26.075936, -97.623278 26.075941, -97.623108 26.075727, -97.623101 26.075512, -97.623089 26.075468, -97.623034 26.07543, -97.622967 26.07543, -97.622925 26.075413, -97.622912 26.075397, -97.621572 26.076091, -97.621188 26.076308, -97.619144 26.075773, -97.618898 26.076457, -97.618874 26.076556, -97.6186 26.07769, -97.618567 26.077825, -97.618448 26.078319, -97.618293 26.078765, -97.615156 26.087781, -97.615001 26.087728, -97.614903 26.087694, -97.614858 26.087659, -97.614786 26.087546, -97.614551 26.086835, -97.6145 26.086733, -97.614418 26.086656, -97.614249 26.0866, -97.613564 26.086411, -97.613052 26.086269, -97.612488 26.086096, -97.612388 26.08608, -97.612312 26.086094, -97.612254 26.086143, -97.612216 26.086195, -97.612178 26.086346, -97.611869 26.087815, -97.61167 26.088733, -97.611491 26.089489, -97.611472 26.089609, -97.611493 26.089673, -97.61152 26.089724, -97.611555 26.089774, -97.611652 26.089854, -97.613064 26.090886, -97.613467 26.091195, -97.613544 26.091268, -97.613582 26.091304, -97.613584 26.091384, -97.612968 26.093245, -97.612526 26.094795, -97.612434 26.095119, -97.612181 26.095859, -97.60941 26.103913, -97.609191 26.104547, -97.608975 26.105173, -97.608759 26.105807, -97.608544 26.106433, -97.607347 26.109908, -97.607292 26.110033, -97.607186 26.109996, -97.605159 26.108161, -97.603704 26.106853, -97.602741 26.105976, -97.602104 26.10545, -97.599544 26.103168, -97.597978 26.101771, -97.596232 26.100227, -97.595038 26.099176, -97.594657 26.098824, -97.594156 26.098297, -97.593541 26.097625, -97.593084 26.097041, -97.592542 26.096343, -97.592012 26.095658, -97.589994 26.093056, -97.584796 26.086329, -97.5837 26.084909, -97.58333 26.084434, -97.583075 26.084099, -97.582579 26.083457, -97.582054 26.082787, -97.581247 26.081737, -97.580794 26.081147, -97.580744 26.081082, -97.580797 26.081084, -97.580926 26.081079, -97.581051 26.081075, -97.581199 26.081064, -97.581231 26.08106, -97.581277 26.081049, -97.581302 26.081032, -97.581559 26.081006, -97.581995 26.080983, -97.581907 26.080919, -97.581872 26.080893, -97.581567 26.080871, -97.581506 26.080855, -97.581476 26.080838, -97.581452 26.080811, -97.58136 26.080503, -97.581336 26.080382, -97.58133 26.080217, -97.581336 26.080173, -97.581372 26.080085, -97.581482 26.079914, -97.581549 26.079837, -97.581695 26.079782, -97.581909 26.079776, -97.581963 26.079765, -97.582 26.079738, -97.582012 26.079721, -97.582061 26.079441, -97.582152 26.079121, -97.582219 26.078956, -97.582627 26.078533, -97.582895 26.078224, -97.5832 26.077944, -97.583328 26.07768, -97.584333 26.076595, -97.584504 26.076392, -97.584534 26.076338, -97.584595 26.076232, -97.584626 26.076149, -97.584705 26.076006, -97.584729 26.075924, -97.584607 26.075627, -97.584577 26.075577, -97.584546 26.075555, -97.584217 26.07544, -97.583041 26.075071, -97.58273 26.075021, -97.582225 26.074917, -97.582036 26.07489, -97.58139 26.074873, -97.580537 26.074879, -97.58047 26.074846, -97.580439 26.074791, -97.580482 26.074598, -97.580379 26.074644, -97.580297 26.074681, -97.580275 26.074851, -97.580196 26.075104, -97.580171 26.075143, -97.58011 26.075165, -97.579592 26.075247, -97.579093 26.075396, -97.578666 26.075578, -97.578404 26.07571, -97.577874 26.076013, -97.577722 26.076117, -97.577582 26.076249, -97.577557 26.076293, -97.577539 26.076365, -97.577521 26.076387, -97.577501 26.076387, -97.577472 26.07637, -97.577265 26.07609, -97.577143 26.075908, -97.577085 26.075937, -97.577401 26.076398, -97.578403 26.077831, -97.579003 26.078718, -97.579684 26.079698, -97.580112 26.080282, -97.580676 26.081004, -97.580584 26.080994, -97.580327 26.080957, -97.580403 26.081057, -97.580642 26.081372, -97.580783 26.081557, -97.580943 26.081767, -97.581185 26.082087, -97.581547 26.082563, -97.582667 26.083999, -97.584542 26.086414, -97.587059 26.089667, -97.588577 26.091638, -97.589205 26.092453, -97.589636 26.093013, -97.592157 26.096284, -97.592491 26.096713, -97.593393 26.097872, -97.593856 26.098357, -97.594464 26.098985, -97.594868 26.099349, -97.595131 26.099587, -97.595555 26.099962, -97.596125 26.100467, -97.596593 26.100882, -97.597748 26.101936, -97.597521 26.102102, -97.59733 26.102243, -97.597158 26.10237, -97.597028 26.102542, -97.59669 26.103017, -97.597381 26.103812, -97.598956 26.105334, -97.599091 26.104703, -97.599731 26.105372, -97.59996 26.105612, -97.60114 26.106883, -97.602008 26.107808, -97.602192 26.107988, -97.602383 26.108163, -97.602587 26.108333, -97.602797 26.108493, -97.602833 26.108519, -97.60286 26.108538, -97.602776 26.108679, -97.605257 26.110129, -97.605795 26.111196, -97.605772 26.111554, -97.605841 26.111752, -97.604519 26.113076, -97.603727 26.112498, -97.602427 26.111548, -97.602088 26.111395, -97.602047 26.111377, -97.599241 26.110961, -97.597851 26.110754, -97.595394 26.110388, -97.593089 26.110044, -97.590936 26.109724, -97.590897 26.109718, -97.590305 26.112987, -97.590281 26.113118, -97.590008 26.113086, -97.587569 26.112745, -97.583071 26.112146, -97.577286 26.111348, -97.577041 26.111315, -97.57702 26.111442, -97.576447 26.114924, -97.575947 26.117957, -97.575924 26.118096, -97.575982 26.118106, -97.578945 26.118546, -97.580536 26.118794, -97.585656 26.119547, -97.586601 26.119696, -97.588985 26.120048, -97.588577 26.122172, -97.588345 26.123454, -97.588284 26.123707, -97.588303 26.123751, -97.588352 26.123759, -97.588949 26.12385, -97.592363 26.124323, -97.592503 26.12434, -97.592613 26.124307, -97.593935 26.123101, -97.59474 26.122287, -97.595544 26.121521, -97.596678 26.120473, -97.597336 26.119865, -97.598927 26.118378, -97.599116 26.118511, -97.59925 26.118395, -97.599975 26.117668, -97.601298 26.116452, -97.603279 26.114553, -97.603693 26.114156, -97.604162 26.113634, -97.60464 26.113164, -97.605827 26.114032, -97.606056 26.114208, -97.60617 26.114267, -97.603994 26.116683, -97.604031 26.116785, -97.605446 26.120733, -97.60644 26.123603, -97.606518 26.123906, -97.606966 26.124917, -97.607208 26.124648, -97.607342 26.124499, -97.607467 26.12436, -97.607712 26.124088, -97.609565 26.122027, -97.610222 26.123984, -97.610412 26.124454, -97.610556 26.124777, -97.610664 26.124998, -97.610889 26.12542, -97.611079 26.125743, -97.611152 26.125858, -97.611322 26.126127, -97.611567 26.126478, -97.611666 26.126611, -97.611744 26.126716, -97.612041 26.127085, -97.612252 26.127328, -97.61266 26.127754, -97.61299 26.12807, -97.613446 26.128468, -97.613843 26.128778, -97.614105 26.128977, -97.61567 26.130116, -97.61593 26.130307, -97.61695 26.131056, -97.6182 26.131968, -97.619005 26.132567, -97.61921 26.132737, -97.61939 26.132896, -97.619473 26.132973, -97.620015 26.13348, -97.619863 26.133649, -97.61916 26.134431, -97.618457 26.135207, -97.615745 26.138208, -97.614766 26.139293, -97.614726 26.139356, -97.61292 26.138003, -97.608548 26.134726, -97.607591 26.135784, -97.613829 26.140359, -97.609976 26.144663, -97.609902 26.144746, -97.609798 26.14467, -97.608297 26.143565, -97.606717 26.14242, -97.605193 26.141287, -97.603662 26.140181, -97.603633 26.140158, -97.603551 26.140248, -97.600442 26.143678, -97.59853 26.145798, -97.597174 26.147307, -97.596988 26.147514, -97.593682 26.151194, -97.593498 26.151397, -97.594101 26.15184, -97.599778 26.156002, -97.599597 26.15617, -97.599544 26.15622, -97.599381 26.156355, -97.599306 26.156418, -97.599138 26.156548, -97.598903 26.156791, -97.598766 26.156981, -97.598657 26.157106, -97.598421 26.157353, -97.598203 26.157606, -97.597899 26.15795, -97.597645 26.158226, -97.597416 26.158496, -97.597326 26.158595, -97.597153 26.158744, -97.597143 26.158758, -97.597044 26.158893, -97.596432 26.159571, -97.596126 26.159931, -97.596007 26.16005, -97.595581 26.160511, -97.595279 26.160846, -97.595186 26.160963, -97.594811 26.161244, -97.594637 26.161356, -97.594586 26.161395, -97.594554 26.161416, -97.594431 26.161487, -97.593229 26.162143, -97.592951 26.16229, -97.592908 26.162315, -97.592694 26.162438, -97.592577 26.16252, -97.592472 26.162608, -97.592376 26.162706, -97.591912 26.163226, -97.591776 26.163415, -97.591721 26.163515, -97.591701 26.16357, -97.591684 26.163618, -97.591664 26.163728, -97.591648 26.163914, -97.591645 26.164134, -97.588116 26.167405, -97.584776 26.170496, -97.584326 26.170728, -97.583784 26.171041, -97.583477 26.171225, -97.582741 26.17168, -97.582557 26.171794, -97.582255 26.171949, -97.582757 26.172382, -97.583173 26.172001, -97.583584 26.172308, -97.583742 26.172208, -97.584608 26.171906, -97.585437 26.171416, -97.586633 26.170772, -97.587364 26.170469, -97.587828 26.170205, -97.588425 26.169665, -97.589523 26.168994, -97.591871 26.167887, -97.592419 26.167584, -97.592651 26.167392, -97.592749 26.166891, -97.592755 26.166148, -97.592541 26.164547, -97.592711 26.16371, -97.592858 26.163468, -97.593205 26.16311, -97.593668 26.162884, -97.594583 26.162582, -97.595449 26.162631, -97.596108 26.162639, -97.596358 26.162642, -97.597913 26.162619, -97.599132 26.162619, -97.599736 26.162575, -97.600206 26.162575, -97.600596 26.162558, -97.600907 26.162575, -97.601108 26.162597, -97.60156 26.162712, -97.602054 26.162916, -97.603176 26.163405, -97.603316 26.163455, -97.603566 26.163526, -97.603853 26.163631, -97.603975 26.163664, -97.604054 26.163636, -97.604145 26.163581, -97.604402 26.163361, -97.60445 26.163311, -97.604523 26.163201, -97.604597 26.162981, -97.60459 26.16292, -97.604572 26.162876, -97.604499 26.162728, -97.604139 26.16226, -97.603913 26.162035, -97.602797 26.160709, -97.602419 26.16018, -97.60237 26.160092, -97.602156 26.159647, -97.601656 26.158678, -97.601503 26.158315, -97.601491 26.158194, -97.601497 26.157231, -97.601515 26.157099, -97.601649 26.156824, -97.601771 26.156664, -97.601936 26.156356, -97.602015 26.156284, -97.602137 26.156202, -97.602387 26.156103, -97.602625 26.156047, -97.603076 26.156014, -97.603679 26.156025, -97.603893 26.156036, -97.604112 26.156064, -97.604411 26.15613, -97.60485 26.156251, -97.60493 26.156267, -97.605021 26.156272, -97.605509 26.156371, -97.606755 26.156654, -97.606912 26.15669, -97.607143 26.156729, -97.607347 26.156773, -97.607087 26.157145, -97.606816 26.157532, -97.606168 26.157411, -97.605954 26.157356, -97.605747 26.157285, -97.605534 26.15723, -97.60529 26.157186, -97.604877 26.157094, -97.604771 26.157071, -97.603948 26.156917, -97.603454 26.156856, -97.60307 26.156818, -97.602832 26.156818, -97.60268 26.156834, -97.602558 26.156862, -97.602436 26.156912, -97.60232 26.156983, -97.602265 26.157027, -97.602229 26.157077, -97.602204 26.157121, -97.60218 26.157198, -97.602168 26.157297, -97.602168 26.157445, -97.602217 26.157858, -97.602235 26.157952, -97.602308 26.158194, -97.60268 26.158898, -97.60287 26.159223, -97.602967 26.159421, -97.603345 26.16001, -97.603663 26.160428, -97.604193 26.161038, -97.604717 26.161604, -97.605361 26.162157, -97.605601 26.162176, -97.605749 26.162521, -97.605907 26.1629, -97.605968 26.163041, -97.606101 26.16329, -97.606133 26.163255, -97.60761 26.161642, -97.608124 26.161082, -97.608661 26.160492, -97.608804 26.160337, -97.608888 26.160245, -97.609404 26.159574, -97.609927 26.158603, -97.610128 26.158231, -97.610178 26.158076, -97.610232 26.157909, -97.610323 26.15763, -97.610461 26.157691, -97.610485 26.157647, -97.61051 26.157548, -97.610583 26.157333, -97.610632 26.157283, -97.611906 26.157404, -97.612924 26.157387, -97.613022 26.157376, -97.61315 26.157349, -97.613327 26.15726, -97.613443 26.157178, -97.613583 26.157057, -97.613662 26.156974, -97.613833 26.156831, -97.613863 26.156776, -97.613918 26.156627, -97.613936 26.156523, -97.613942 26.156303, -97.613924 26.156143, -97.613863 26.155873, -97.613747 26.155444, -97.61371 26.155021, -97.613692 26.154553, -97.613655 26.154316, -97.613643 26.153832, -97.613649 26.153639, -97.613667 26.15348, -97.613679 26.153408, -97.613782 26.153138, -97.613837 26.153039, -97.613929 26.152913, -97.61402 26.152803, -97.614148 26.152682, -97.61441 26.152494, -97.614599 26.152434, -97.616776 26.152945, -97.616911 26.153027, -97.617252 26.153198, -97.617313 26.153225, -97.617441 26.153264, -97.617661 26.153291, -97.617923 26.153291, -97.618106 26.153263, -97.618277 26.153214, -97.618594 26.153153, -97.618892 26.152955, -97.619776 26.152239, -97.619929 26.152091, -97.620965 26.151055, -97.621318 26.150648, -97.621452 26.150411, -97.621507 26.149993, -97.62136 26.149691, -97.620366 26.14826, -97.61981 26.147556, -97.619322 26.146676, -97.619261 26.146489, -97.619151 26.146224, -97.619189 26.146152, -97.61928 26.14605, -97.619497 26.145803, -97.619636 26.145647, -97.619645 26.145723, -97.619657 26.145778, -97.619791 26.145828, -97.619865 26.146153, -97.620005 26.146664, -97.620365 26.147314, -97.621543 26.148617, -97.621836 26.148848, -97.622013 26.149101, -97.622287 26.149657, -97.622386 26.150003, -97.622599 26.149957, -97.623885 26.149681, -97.623899 26.149733, -97.623938 26.149836, -97.624056 26.150138, -97.624219 26.150479, -97.624362 26.150705, -97.624539 26.150934, -97.624602 26.151053, -97.624599 26.151114, -97.62458 26.151175, -97.624508 26.15127, -97.62436 26.151422, -97.623783 26.152081, -97.623935 26.1522, -97.628076 26.154958, -97.628334 26.15513, -97.630315 26.156449, -97.630426 26.156523, -97.630454 26.156492, -97.631367 26.155477, -97.631474 26.155357, -97.63206 26.154704, -97.632625 26.154072, -97.633533 26.153058, -97.633588 26.152997, -97.633643 26.152935, -97.63367 26.152905, -97.633696 26.152876, -97.633731 26.152837, -97.635696 26.150638, -97.636187 26.150088, -97.637373 26.148761, -97.638789 26.147178, -97.639328 26.147534, -97.640282 26.148164, -97.641198 26.148768, -97.642439 26.149587, -97.643779 26.150485, -97.645228 26.151542, -97.646755 26.15268, -97.648799 26.154195, -97.649649 26.154818, -97.650516 26.155463, -97.651154 26.155933, -97.651325 26.15606, -97.651426 26.156135, -97.651531 26.156212, -97.651657 26.156305, -97.648517 26.159842, -97.646357 26.162275, -97.651831 26.166085, -97.652244 26.166373, -97.652355 26.166451, -97.652519 26.166624, -97.652968 26.166113, -97.655747 26.163025, -97.656552 26.162111, -97.656845 26.162341, -97.657288 26.162682, -97.656774 26.163256, -97.656544 26.163509, -97.656517 26.163548, -97.65653 26.163616, -97.656558 26.163642, -97.657784 26.164562, -97.657879 26.164613, -97.656688 26.165852, -97.657307 26.166381, -97.658433 26.165444, -97.658691 26.165205, -97.658053 26.164518, -97.658124 26.164443, -97.658323 26.16422, -97.658695 26.163804, -97.659968 26.164753, -97.66129 26.165705, -97.661374 26.165877, -97.661592 26.165922, -97.662549 26.166595, -97.658562 26.171047, -97.658605 26.171079, -97.657122 26.172739, -97.65711 26.1728, -97.65603 26.173994, -97.65595 26.173988, -97.655774 26.173878, -97.655685 26.173908, -97.655738 26.173952, -97.655731 26.173966, -97.654834 26.174423, -97.654749 26.174428, -97.65448 26.174313, -97.65443 26.174376, -97.654379 26.174438, -97.654303 26.174533, -97.654358 26.174555, -97.654303 26.17472, -97.654194 26.174879, -97.654047 26.175028, -97.653998 26.175132, -97.653956 26.175314, -97.654004 26.175523, -97.653943 26.175661, -97.653895 26.175683, -97.653431 26.17565, -97.652949 26.175963, -97.6529 26.176024, -97.652833 26.176447, -97.652857 26.176783, -97.65279 26.177212, -97.652741 26.177339, -97.652595 26.17757, -97.652284 26.177889, -97.652131 26.178093, -97.652174 26.178252, -97.652234 26.178347, -97.652271 26.178406, -97.652304 26.178441, -97.65246 26.17861, -97.652735 26.178638, -97.652899 26.17861, -97.653253 26.178401, -97.653388 26.178219, -97.653455 26.17817, -97.653949 26.178121, -97.654028 26.178093, -97.654363 26.177857, -97.654754 26.177515, -97.654839 26.177537, -97.654858 26.177615, -97.654534 26.178049, -97.654339 26.178242, -97.654156 26.17833, -97.653717 26.178434, -97.653637 26.178517, -97.65354 26.17872, -97.653497 26.178748, -97.653217 26.178737, -97.652527 26.17894, -97.652393 26.17894, -97.652198 26.178896, -97.652125 26.179199, -97.651899 26.179584, -97.65164 26.179917, -97.651514 26.180079, -97.651495 26.180131, -97.651435 26.180289, -97.651347 26.180523, -97.649845 26.181869, -97.649625 26.182339, -97.649542 26.182781, -97.649637 26.182815, -97.649456 26.182931, -97.648982 26.183237, -97.648885 26.183281, -97.648378 26.183358, -97.648293 26.183397, -97.648201 26.183474, -97.647969 26.183804, -97.647902 26.183842, -97.647841 26.18382, -97.647827 26.183762, -97.647811 26.183699, -97.647707 26.183611, -97.647262 26.183567, -97.647085 26.183787, -97.646627 26.18404, -97.646426 26.184189, -97.646115 26.184436, -97.645798 26.184744, -97.645114 26.185058, -97.644718 26.185118, -97.644413 26.185124, -97.644321 26.185157, -97.644864 26.18558, -97.645193 26.185801, -97.645559 26.185993, -97.645693 26.18604, -97.646078 26.186175, -97.646517 26.186241, -97.647115 26.186231, -97.647432 26.186115, -97.647914 26.185862, -97.648249 26.185593, -97.648365 26.185383, -97.648414 26.184844, -97.648512 26.184641, -97.648774 26.184305, -97.648994 26.184107, -97.649757 26.183568, -97.650342 26.183342, -97.650599 26.18332, -97.650739 26.183364, -97.651135 26.183651, -97.651379 26.183959, -97.65155 26.184047, -97.651641 26.184135, -97.651928 26.184509, -97.652159 26.18495, -97.65222 26.185153, -97.652409 26.185346, -97.652696 26.185555, -97.653019 26.185693, -97.653324 26.185693, -97.653708 26.185588, -97.653837 26.185533, -97.654184 26.185297, -97.654477 26.185203, -97.654916 26.185143, -97.655349 26.185132, -97.65566 26.185187, -97.656368 26.185474, -97.65663 26.18565, -97.656691 26.185721, -97.656734 26.185804, -97.656752 26.186013, -97.656636 26.186541, -97.65674 26.186838, -97.656971 26.187059, -97.657185 26.187202, -97.657642 26.187345, -97.658014 26.187339, -97.658557 26.187257, -97.658874 26.187114, -97.65946 26.186745, -97.659855 26.186451, -97.660149 26.186234, -97.660918 26.185155, -97.661229 26.184814, -97.661711 26.18438, -97.662321 26.183989, -97.6632 26.183637, -97.663993 26.183488, -97.664749 26.183461, -97.664871 26.183475, -97.66517 26.183511, -97.665743 26.183659, -97.66639 26.183874, -97.666358 26.183923, -97.666313 26.183989, -97.66474 26.18635, -97.66427 26.187083, -97.663777 26.187833, -97.663593 26.18814, -97.663373 26.1886, -97.663212 26.189047, -97.663107 26.189461, -97.663073 26.189595, -97.663015 26.189961, -97.66299 26.190305, -97.662985 26.190378, -97.662985 26.190712, -97.661608 26.190723, -97.661251 26.190726, -97.66059 26.190727, -97.658999 26.19074, -97.653495 26.190783, -97.653484 26.190904, -97.653495 26.193035, -97.659019 26.193059, -97.661627 26.19307, -97.661648 26.195568, -97.66165 26.195858, -97.662991 26.195858, -97.663133 26.195861, -97.665349 26.195854, -97.666654 26.195851, -97.667774 26.195824, -97.669272 26.195824, -97.669497 26.195828, -97.669491 26.19805, -97.668856 26.198053, -97.667867 26.198056, -97.666862 26.198053, -97.665913 26.198061, -97.665568 26.198065, -97.663138 26.198063, -97.663136 26.204477, -97.663142 26.205315, -97.665564 26.205314, -97.665568 26.206953, -97.665564 26.209012, -97.669368 26.209013, -97.66949 26.209014, -97.669507 26.212587, -97.669348 26.212588, -97.666766 26.21258, -97.665661 26.212576, -97.663281 26.212569, -97.663141 26.212571, -97.662465 26.212601, -97.658293 26.21264, -97.654253 26.212647, -97.653908 26.212643, -97.653721 26.212711, -97.653663 26.212758, -97.653638 26.212872, -97.653637 26.21327, -97.653663 26.21593, -97.653662 26.216233, -97.653655 26.216253, -97.653613 26.216368, -97.653413 26.216425, -97.653042 26.216432, -97.645227 26.216442, -97.641098 26.216448, -97.641059 26.217944, -97.641083 26.219749, -97.641069 26.221506, -97.64107 26.221522, -97.641067 26.222073, -97.641047 26.225479, -97.643159 26.225491, -97.645141 26.225502, -97.645195 26.227493, -97.645154 26.227528, -97.645162 26.227732, -97.643169 26.229506, -97.641405 26.23103, -97.639556 26.231063, -97.639495 26.231096, -97.639494 26.231354, -97.639667 26.231355, -97.639668 26.235788, -97.64537 26.235927, -97.645378 26.236025, -97.64547 26.237237, -97.654872 26.2374, -97.655074 26.24405, -97.657162 26.244056, -97.657145 26.24093, -97.657608 26.240924, -97.658604 26.240915, -97.659445 26.240908, -97.661673 26.240876, -97.661618 26.239544, -97.661865 26.237294, -97.661862 26.237255, -97.661843 26.237033, -97.665243 26.237101, -97.665323 26.235858, -97.66542 26.234346, -97.666669 26.234346, -97.669589 26.234347, -97.669589 26.234323, -97.669689 26.234309, -97.669689 26.234347, -97.669756 26.234345, -97.669767 26.23733, -97.669806 26.248092, -97.669739 26.248147, -97.669733 26.248283, -97.669478 26.248277, -97.669244 26.248276, -97.664986 26.248256, -97.66471 26.248212, -97.664378 26.248159, -97.663633 26.247925, -97.662639 26.247621, -97.661879 26.247469, -97.661619 26.247469, -97.661619 26.24919, -97.661619 26.250973, -97.664695 26.251233, -97.665368 26.251289, -97.669207 26.251613, -97.669207 26.251818, -97.66922 26.257364, -97.670973 26.257348, -97.672066 26.257354, -97.672572 26.257387, -97.673933 26.25742, -97.676014 26.257426, -97.676173 26.25741, -97.676271 26.257349, -97.676326 26.257233, -97.676362 26.257063, -97.67635 26.256859, -97.676347 26.256569, -97.677813 26.256297, -97.678226 26.25638, -97.679656 26.256406, -97.680048 26.256359, -97.681163 26.256504, -97.681966 26.256565, -97.682184 26.256648, -97.682258 26.256645, -97.682278 26.256646, -97.6826 26.256656, -97.682832 26.256645, -97.682833 26.256345, -97.682844 26.252892, -97.682863 26.251351, -97.682856 26.250536, -97.682875 26.250002, -97.682878 26.248446, -97.682905 26.248316, -97.689383 26.248345, -97.689373 26.244756, -97.689475 26.244748, -97.697519 26.244767, -97.697519 26.248403, -97.69724 26.248402, -97.696571 26.248397, -97.69594 26.248393, -97.69372 26.248377, -97.693837 26.252645, -97.696308 26.252673, -97.696709 26.252664, -97.697127 26.252653, -97.697172 26.252652, -97.697208 26.252651, -97.697199 26.252498, -97.69724 26.250782, -97.69724 26.249268, -97.70148 26.24919, -97.701644 26.249187, -97.703654 26.249327, -97.703414 26.248417, -97.701651 26.248413, -97.701681 26.244737, -97.70168 26.24456, -97.703055 26.244567, -97.705651 26.24458, -97.705651 26.24407, -97.705651 26.24388, -97.705651 26.241055, -97.705671 26.241055, -97.713832 26.241052, -97.713941 26.241052, -97.713951 26.239056, -97.713958 26.237558, -97.713958 26.237488, -97.713966 26.235685, -97.714048 26.235681, -97.714131 26.235931, -97.714475 26.237052, -97.714478 26.23756, -97.714478 26.237576, -97.714483 26.238227, -97.714473 26.238753, -97.71708 26.238763, -97.717525 26.238757, -97.717548 26.238735, -97.717552 26.238665, -97.717556 26.237592, -97.717557 26.237178, -97.717555 26.237158, -97.717586 26.237024, -97.717865 26.235829, -97.718034 26.235203, -97.71801 26.235176, -97.717991 26.234922, -97.718089 26.234823, -97.718168 26.234801, -97.71826 26.23473, -97.71826 26.234691, -97.718162 26.234603, -97.717991 26.234581, -97.717991 26.234543, -97.717981 26.233638, -97.71802 26.23361, -97.718087 26.233568, -97.718222 26.233482, -97.718542 26.233297, -97.718852 26.233135, -97.719207 26.233017, -97.719985 26.233039, -97.720082 26.232964, -97.720305 26.232961, -97.72032 26.233003, -97.720452 26.233365, -97.720673 26.233968, -97.720727 26.23418, -97.720828 26.234171, -97.72105 26.234151, -97.721183 26.2351, -97.721427 26.236114, -97.721523 26.236512, -97.721843 26.237454, -97.721877 26.237538, -97.722223 26.238385, -97.722662 26.239279, -97.723581 26.241061, -97.72431 26.242487, -97.724617 26.243087, -97.726062 26.24591, -97.727356 26.248429, -97.727424 26.248428, -97.72757 26.248428, -97.727617 26.248427, -97.727934 26.248426, -97.727968 26.248428, -97.72799 26.248426, -97.730077 26.248454, -97.730045 26.249765, -97.730045 26.25091, -97.73004 26.251835, -97.730933 26.251852, -97.732048 26.251858, -97.733248 26.251863, -97.734785 26.251858, -97.735069 26.251868, -97.73509 26.250943, -97.735532 26.250947, -97.736093 26.251812, -97.736087 26.25201, -97.736082 26.252089, -97.736076 26.25218, -97.736073 26.252265, -97.736065 26.253088, -97.736076 26.253672, -97.736096 26.253743, -97.736124 26.253825, -97.736127 26.254012, -97.736121 26.254273, -97.736119 26.254846, -97.736116 26.255394, -97.736102 26.255629, -97.736107 26.255864, -97.735578 26.260343, -97.735631 26.260388, -97.735958 26.260657, -97.735993 26.260688, -97.737644 26.262066, -97.738039 26.262398, -97.738712 26.262958, -97.738903 26.263121, -97.739026 26.263225, -97.741886 26.265571, -97.742285 26.265866, -97.742308 26.265883, -97.742614 26.266111, -97.743235 26.266629, -97.74456 26.267736, -97.744924 26.268036, -97.745554 26.268554, -97.746144 26.269044, -97.746676 26.269487, -97.74713 26.269871, -97.748043 26.270641, -97.748438 26.270496, -97.74846 26.270489, -97.748478 26.270481, -97.748765 26.270373, -97.748289 26.269594, -97.747742 26.268807, -97.745012 26.264627, -97.744607 26.264855, -97.744528 26.264742, -97.74368 26.263442, -97.743473 26.263117, -97.743326 26.262918, -97.742018 26.260904, -97.73971 26.257346, -97.738792 26.255945, -97.738684 26.255786, -97.737764 26.254368, -97.736482 26.25242, -97.736376 26.252255, -97.736957 26.252255, -97.735888 26.250645, -97.735785 26.250144, -97.735753 26.24991, -97.737638 26.249792, -97.737639 26.248499, -97.736528 26.248499, -97.735755 26.248499, -97.735365 26.248501, -97.735222 26.248502, -97.734761 26.248504, -97.734676 26.248504, -97.734645 26.248446, -97.734077 26.247537, -97.73377 26.24696, -97.733693 26.246098, -97.733704 26.245562, -97.733712 26.245207, -97.733713 26.244915, -97.733715 26.244425, -97.733717 26.243997, -97.733729 26.240475, -97.736533 26.240454, -97.737218 26.240448, -97.737783 26.240445, -97.737817 26.233243, -97.737817 26.233162, -97.737854 26.231995, -97.737871 26.231494, -97.737718 26.231335, -97.734265 26.231341, -97.734112 26.231325, -97.733965 26.231187, -97.733788 26.231187, -97.733794 26.229557, -97.73737 26.229452, -97.737271 26.225918, -97.738605 26.225913, -97.740121 26.225913, -97.741882 26.225916, -97.741974 26.225916, -97.742769 26.225911, -97.743909 26.225905, -97.745673 26.22591, -97.745953 26.225911, -97.747026 26.225914, -97.749937 26.225912, -97.749926 26.227546, -97.752441 26.227556, -97.752432 26.229153, -97.752436 26.229826, -97.755652 26.229716, -97.755573 26.233178, -97.755574 26.233388, -97.755636 26.238036, -97.754206 26.238059, -97.754199 26.240422, -97.755467 26.240422, -97.755919 26.240422, -97.755938 26.243801, -97.755954 26.246535, -97.755777 26.246535, -97.755778 26.248465, -97.756862 26.248461, -97.75791 26.248459, -97.758851 26.248458, -97.759904 26.248457, -97.759927 26.243272, -97.759941 26.240422, -97.75954 26.240422, -97.758687 26.240422, -97.75795 26.240422, -97.757954 26.23875, -97.757955 26.238563, -97.757956 26.238386, -97.75796 26.236834, -97.757962 26.233418, -97.757962 26.233188, -97.759566 26.233194, -97.759975 26.233195, -97.759991 26.230132, -97.761697 26.230132, -97.7617 26.229638, -97.761763 26.228196, -97.761783 26.2279, -97.766071 26.227704, -97.76606 26.225983, -97.766055 26.225928, -97.770073 26.225927, -97.770067 26.227846, -97.770065 26.228506, -97.770062 26.229115, -97.770746 26.229128, -97.770745 26.229788, -97.770059 26.229677, -97.773832 26.231427, -97.77394 26.230755, -97.774065 26.230777, -97.775438 26.231021, -97.777494 26.230612, -97.77832 26.230654, -97.778501 26.230655, -97.778528 26.233249, -97.77853 26.233454, -97.778534 26.233816, -97.781188 26.235114, -97.782518 26.235783, -97.784148 26.236573, -97.786099 26.237553, -97.786723 26.237831, -97.786719 26.237331, -97.786707 26.236058, -97.786694 26.234749, -97.786693 26.234628, -97.78668 26.233297, -97.78667 26.232204, -97.786564 26.232193, -97.786528 26.23144, -97.782537 26.231505, -97.782525 26.229953, -97.782525 26.229892, -97.782458 26.22943, -97.78243 26.226083, -97.782314 26.226084, -97.780927 26.226089, -97.778487 26.226144, -97.77849 26.225917, -97.778248 26.225916, -97.776193 26.225905, -97.77447 26.225907, -97.774549 26.223926, -97.766058 26.223952, -97.76216 26.224029, -97.76194 26.224038, -97.761938 26.22362, -97.761941 26.223242, -97.76193 26.222347, -97.760186 26.222366, -97.760207 26.221021, -97.760214 26.220471, -97.760225 26.219769, -97.760241 26.218774, -97.762011 26.218792, -97.762011 26.218651, -97.761905 26.218598, -97.760254 26.217945, -97.760258 26.217711, -97.758617 26.2171, -97.758028 26.216901, -97.758032 26.216187, -97.75805 26.215049, -97.758063 26.212993, -97.758093 26.211442, -97.758987 26.211422, -97.761195 26.211405, -97.7624 26.211395, -97.762823 26.211392, -97.763151 26.21139, -97.763149 26.210128, -97.763148 26.209667, -97.763147 26.209277, -97.763142 26.206671, -97.763142 26.206452, -97.763142 26.206233, -97.763141 26.205994, -97.763141 26.205781, -97.76314 26.205476, -97.763084 26.205464, -97.763005 26.205446, -97.762958 26.20543, -97.762909 26.205403, -97.762847 26.205353, -97.762778 26.205293, -97.762698 26.205239, -97.762655 26.205214, -97.762567 26.205168, -97.762519 26.205147, -97.762471 26.205132, -97.762366 26.205115, -97.762312 26.205112, -97.762256 26.205111, -97.762203 26.205114, -97.762096 26.205129, -97.76189 26.205183, -97.761839 26.205197, -97.761788 26.205209, -97.761736 26.205219, -97.76163 26.205232, -97.761576 26.205235, -97.761521 26.205237, -97.761353 26.20522, -97.761293 26.20521, -97.761173 26.205193, -97.761113 26.205185, -97.761052 26.205177, -97.76099 26.205171, -97.760929 26.205167, -97.760867 26.205167, -97.760744 26.205175, -97.760683 26.205185, -97.760623 26.205197, -97.760567 26.205207, -97.760457 26.205229, -97.760403 26.20524, -97.760301 26.205259, -97.760156 26.205275, -97.760014 26.205265, -97.75992 26.205241, -97.759874 26.205223, -97.759753 26.20516, -97.759681 26.205107, -97.759617 26.205055, -97.75956 26.205007, -97.759509 26.204968, -97.75946 26.204946, -97.759402 26.204933, -97.759341 26.204928, -97.759267 26.204924, -97.759189 26.204917, -97.759189 26.204282, -97.759189 26.204188, -97.759189 26.203994, -97.759189 26.203878, -97.759188 26.203096, -97.759188 26.202887, -97.760352 26.202905, -97.760973 26.20294, -97.761362 26.202766, -97.762527 26.202731, -97.763148 26.20294, -97.763437 26.20304, -97.763653 26.203115, -97.764819 26.20308, -97.764815 26.201339, -97.764661 26.201236, -97.763913 26.201223, -97.763488 26.201198, -97.7622 26.201082, -97.761788 26.201082, -97.761156 26.201172, -97.760576 26.201249, -97.760203 26.201275, -97.759713 26.201249, -97.759187 26.201249, -97.759187 26.200574, -97.759169 26.20015, -97.758457 26.200154, -97.758407 26.200155, -97.758407 26.200109, -97.758964 26.200122, -97.760301 26.200149, -97.765068 26.200138, -97.766099 26.200136, -97.766422 26.200081, -97.766475 26.200059, -97.76663 26.199993, -97.77216 26.19994, -97.772167 26.19761, -97.773326 26.197609, -97.778072 26.197606, -97.778065 26.197074, -97.782027 26.197042, -97.782182 26.197041, -97.786319 26.197007, -97.787042 26.197002, -97.792831 26.200254, -97.79274 26.200753, -97.792074 26.203775, -97.792001 26.203907, -97.791781 26.203973, -97.790726 26.203956, -97.79061 26.203995, -97.790632 26.204226, -97.790722 26.204228, -97.79072 26.204132, -97.790781 26.204083, -97.792007 26.204083, -97.79216 26.203967, -97.792447 26.202971, -97.793051 26.199994, -97.793362 26.19973, -97.794637 26.198778, -97.794692 26.198635, -97.794717 26.198085, -97.794796 26.197903, -97.794845 26.197875, -97.795693 26.197732, -97.795809 26.197611, -97.7958 26.196941, -97.795877 26.19694, -97.795979 26.19694, -97.796048 26.196939, -97.801835 26.196891, -97.801815 26.195626, -97.80178 26.193061, -97.801744 26.189944, -97.801717 26.187632, -97.801707 26.187508, -97.801698 26.186868, -97.801687 26.185913, -97.801632 26.18172, -97.801602 26.179331, -97.801593 26.17867, -97.801582 26.177902, -97.807374 26.177801, -97.807474 26.175519, -97.80748 26.175386, -97.807519 26.172809, -97.807465 26.171234, -97.807987 26.170964, -97.809997 26.169925, -97.810002 26.170915, -97.810003 26.170973, -97.811063 26.170962, -97.813651 26.170936, -97.814753 26.170925, -97.815084 26.17093, -97.815246 26.170933, -97.815933 26.170949, -97.816393 26.170973, -97.816429 26.170973, -97.817621 26.170989, -97.819932 26.170941, -97.819959 26.172939, -97.819967 26.173617, -97.819976 26.174258, -97.820001 26.176194, -97.820071 26.181467, -97.820118 26.185066, -97.820132 26.186196, -97.820329 26.19986, -97.82034 26.200822, -97.820355 26.20152, -97.820375 26.203045, -97.820395 26.204423, -97.820433 26.207077, -97.820457 26.208753, -97.822849 26.208709, -97.824786 26.208674, -97.824764 26.206976, -97.82473 26.204317, -97.824691 26.201381, -97.824682 26.200682, -97.824671 26.199716, -97.824656 26.198582, -97.824649 26.198067, -97.824479 26.184965, -97.82445 26.183143, -97.824424 26.181547, -97.824371 26.178268, -97.824369 26.178135, -97.824336 26.176096, -97.824316 26.17486, -97.824313 26.174687, -97.824295 26.173553, -97.828447 26.173442, -97.828427 26.168759, -97.828425 26.168183, -97.828415 26.165888, -97.826404 26.165907, -97.825977 26.165915, -97.825221 26.165962, -97.825013 26.165975, -97.824175 26.165989, -97.824167 26.165707, -97.824655 26.165701, -97.828277 26.16566, -97.832308 26.165613, -97.832528 26.165611, -97.83347 26.1656, -97.835534 26.165576, -97.83627 26.165568, -97.836497 26.165565, -97.836496 26.165363, -97.836494 26.165105, -97.837912 26.165098, -97.839768 26.16509, -97.839879 26.16509, -97.84046 26.165088, -97.841419 26.165084, -97.842199 26.165063, -97.842335 26.165056, -97.843296 26.165, -97.843453 26.16498, -97.84346 26.164098, -97.843457 26.163783, -97.843458 26.163183, -97.843456 26.162573, -97.843452 26.161967, -97.843446 26.161469, -97.843449 26.161424, -97.84346 26.161383, -97.843488 26.161341, -97.843523 26.161309, -97.843599 26.161265, -97.844115 26.161053, -97.844299 26.160968, -97.844412 26.160888, -97.844459 26.160837, -97.844495 26.160778, -97.844521 26.160714, -97.844532 26.160647, -97.844543 26.160437, -97.844504 26.158494, -97.844511 26.157837, -97.844491 26.155481, -97.844504 26.155386, -97.844516 26.155344, -97.844536 26.155304, -97.844569 26.15527, -97.844616 26.155245, -97.847195 26.154486, -97.848504 26.154108, -97.848487 26.152329, -97.8501 26.152231, -97.850547 26.152209, -97.850966 26.152193, -97.85331 26.152161, -97.853683 26.152123, -97.853715 26.153997, -97.853403 26.154014, -97.853269 26.154046, -97.853227 26.154174, -97.853225 26.154314, -97.853246 26.154473, -97.853272 26.154658, -97.85334 26.154888, -97.853444 26.155041, -97.853736 26.155208, -97.853742 26.15555, -97.853787 26.15819, -97.857774 26.157906, -97.857774 26.157961, -97.857775 26.158016, -97.857795 26.159719, -97.857751 26.159969, -97.857785 26.159959, -97.858065 26.159885, -97.858296 26.159824, -97.858632 26.159753, -97.858944 26.159686, -97.859425 26.159609, -97.859973 26.159539, -97.860287 26.159512, -97.860912 26.159486, -97.861295 26.159475, -97.861837 26.15948, -97.861838 26.159231, -97.86182 26.158371, -97.861811 26.157916, -97.861808 26.157775, -97.861793 26.157074, -97.861782 26.156518, -97.862718 26.156528, -97.86315 26.156534, -97.86358 26.156539, -97.863906 26.156543, -97.86443 26.157016, -97.864496 26.157076, -97.864526 26.157235, -97.864969 26.157224, -97.864983 26.157224, -97.865008 26.157191, -97.864993 26.155913, -97.864989 26.155469, -97.864977 26.155424, -97.864942 26.155289, -97.864931 26.155245, -97.864924 26.154995, -97.86491 26.154513, -97.864903 26.154245, -97.864896 26.153996, -97.864888 26.153729, -97.864864 26.152929, -97.864857 26.152663, -97.864856 26.152594, -97.864853 26.152389, -97.864852 26.152321, -97.865042 26.15233, -97.865352 26.152346, -97.865359 26.152083, -97.865365 26.151893, -97.866249 26.151875, -97.867128 26.151859, -97.868416 26.151827, -97.868901 26.151814, -97.869786 26.151793, -97.869784 26.151753, -97.869783 26.15169, -97.869781 26.151636, -97.86978 26.151598, -97.869778 26.15155, -97.869774 26.151406, -97.869773 26.151359, -97.869766 26.151147, -97.869764 26.150899, -97.869759 26.149897, -97.869746 26.149522, -97.869731 26.149064, -97.869726 26.148908, -97.869661 26.148991, -97.869605 26.149064, -97.869611 26.149247, -97.869636 26.149751, -97.869648 26.150559, -97.869667 26.151363, -97.868752 26.151399, -97.865692 26.151519, -97.864672 26.15156, -97.864842 26.151689, -97.864488 26.151693, -97.863977 26.1517, -97.863427 26.151713, -97.863074 26.151723, -97.862469 26.151739, -97.861876 26.15174, -97.861684 26.151736, -97.857705 26.151827, -97.855028 26.15189, -97.85368 26.151921, -97.851117 26.15198, -97.850964 26.151984, -97.850547 26.151993, -97.850103 26.152003, -97.849295 26.152022, -97.848485 26.152122, -97.847401 26.152255, -97.846141 26.15255, -97.846115 26.151143, -97.846019 26.15114, -97.845969 26.151141, -97.845902 26.151144, -97.845826 26.151146, -97.845647 26.151148, -97.845551 26.151148, -97.845352 26.151147, -97.845155 26.151151, -97.845058 26.151152, -97.844964 26.151154, -97.844875 26.151154, -97.844709 26.151154, -97.844569 26.151152, -97.844514 26.151153, -97.844442 26.151151, -97.844443 26.151202, -97.844443 26.151248, -97.844443 26.151308, -97.844445 26.151377, -97.844446 26.151454, -97.844448 26.151537, -97.844453 26.151714, -97.844455 26.151807, -97.844457 26.151901, -97.844459 26.151996, -97.84446 26.152093, -97.844461 26.15219, -97.844462 26.152285, -97.844462 26.152379, -97.844463 26.152471, -97.844464 26.15264, -97.844464 26.152715, -97.844464 26.152783, -97.844463 26.152843, -97.844465 26.152891, -97.844427 26.153002, -97.842671 26.15352, -97.842637 26.153195, -97.842601 26.152847, -97.842348 26.15286, -97.842239 26.152873, -97.842075 26.152892, -97.841898 26.152929, -97.841905 26.153219, -97.841853 26.153402, -97.841905 26.153598, -97.841944 26.153734, -97.84112 26.153977, -97.838069 26.154875, -97.837991 26.154715, -97.837854 26.154631, -97.83762 26.154563, -97.837405 26.154564, -97.837193 26.154577, -97.837116 26.154582, -97.83691 26.154574, -97.836618 26.154563, -97.836599 26.154629, -97.836593 26.154753, -97.836599 26.154876, -97.836608 26.155003, -97.836618 26.155155, -97.836642 26.155294, -97.836415 26.155362, -97.836407 26.154314, -97.83638 26.150621, -97.834308 26.15062, -97.834168 26.15062, -97.834116 26.148551, -97.834038 26.145416, -97.834075 26.145179, -97.834111 26.145097, -97.834201 26.144998, -97.833815 26.145001, -97.833825 26.144142, -97.83207 26.144173, -97.832069 26.143787, -97.828319 26.143801, -97.826891 26.143807, -97.827434 26.144833, -97.828044 26.146104, -97.828065 26.146172, -97.828154 26.146464, -97.82816 26.146484, -97.828164 26.146649, -97.828221 26.148856, -97.828264 26.149104, -97.828392 26.149307, -97.828551 26.1495, -97.829429 26.150392, -97.829478 26.150463, -97.829459 26.150507, -97.829392 26.150551, -97.829313 26.150507, -97.828672 26.149869, -97.828227 26.149385, -97.828105 26.149126, -97.828032 26.148174, -97.82801 26.147444, -97.826174 26.147447, -97.824064 26.14745, -97.823873 26.147451, -97.823885 26.147964, -97.823901 26.148611, -97.823906 26.148824, -97.823911 26.149058, -97.823915 26.149261, -97.823933 26.150024, -97.823944 26.150508, -97.823948 26.150807, -97.819643 26.150651, -97.819637 26.150375, -97.819635 26.150273, -97.816162 26.150333, -97.816134 26.150459, -97.816073 26.150753, -97.816077 26.150907, -97.816149 26.151544, -97.816167 26.151842, -97.81621 26.152527, -97.81616 26.153833, -97.816273 26.155693, -97.816273 26.156853, -97.812973 26.156849, -97.809784 26.156839, -97.809785 26.156936, -97.807427 26.156936, -97.807305 26.156892, -97.807268 26.15682, -97.807271 26.157527, -97.801304 26.157527, -97.801342 26.160156, -97.801366 26.161825, -97.799014 26.161872, -97.79824 26.161888, -97.798256 26.16316, -97.799033 26.163145, -97.799875 26.163128, -97.799867 26.162645, -97.801378 26.162645, -97.801383 26.163033, -97.801388 26.163372, -97.801397 26.164024, -97.801413 26.165112, -97.801212 26.165162, -97.80108 26.1652, -97.800817 26.165283, -97.800748 26.165298, -97.80064 26.16533, -97.800573 26.16534, -97.80052 26.165332, -97.800472 26.16531, -97.800458 26.165288, -97.80045 26.165262, -97.800449 26.165224, -97.800439 26.164944, -97.798945 26.165359, -97.798947 26.165437, -97.798965 26.166295, -97.796597 26.166987, -97.795421 26.167331, -97.79533 26.167358, -97.795307 26.167365, -97.795086 26.155448, -97.795018 26.151817, -97.794897 26.1453, -97.79486 26.14508, -97.794726 26.144855, -97.793141 26.142532, -97.792391 26.141294, -97.792188 26.140794, -97.792173 26.140801, -97.792074 26.141097, -97.792041 26.141046, -97.792019 26.140986, -97.792003 26.140929, -97.791985 26.140862, -97.791964 26.140799, -97.791937 26.140741, -97.791911 26.140681, -97.791895 26.140636, -97.791885 26.140587, -97.791907 26.140521, -97.791937 26.140483, -97.792017 26.140437, -97.792041 26.14043, -97.791757 26.139681, -97.791654 26.140017, -97.791562 26.140187, -97.79133 26.140435, -97.79116 26.140495, -97.789958 26.14066, -97.789446 26.140578, -97.788733 26.140214, -97.788068 26.139394, -97.787776 26.138871, -97.787483 26.13848, -97.787288 26.13831, -97.787026 26.138233, -97.786721 26.138227, -97.786307 26.138161, -97.786081 26.138084, -97.786058 26.138081, -97.78606 26.140703, -97.786059 26.142664, -97.784544 26.140772, -97.783545 26.139649, -97.783214 26.139276, -97.783186 26.139241, -97.783084 26.139436, -97.78235 26.141005, -97.781725 26.142255, -97.781666 26.14263, -97.781666 26.144628, -97.777333 26.144669, -97.777187 26.135749, -97.777237 26.135162, -97.777304 26.135045, -97.776809 26.134893, -97.776438 26.134875, -97.77605 26.134945, -97.775665 26.135117, -97.775461 26.135377, -97.774971 26.136499, -97.775045 26.13654, -97.776989 26.139854, -97.777067 26.144356, -97.777036 26.144972, -97.775967 26.150817, -97.775894 26.151031, -97.775845 26.151114, -97.772716 26.153447, -97.772662 26.153403, -97.772527 26.153505, -97.772506 26.151016, -97.772453 26.144576, -97.767774 26.144689, -97.767767 26.143683, -97.767234 26.143861, -97.766736 26.143982, -97.766255 26.144077, -97.765913 26.144139, -97.765556 26.144167, -97.76526 26.144216, -97.764662 26.144512, -97.764329 26.144772, -97.764275 26.144815, -97.763464 26.145789, -97.763106 26.145958, -97.762794 26.146059, -97.762623 26.146086, -97.762421 26.146129, -97.762234 26.146202, -97.762099 26.146306, -97.761838 26.146307, -97.761696 26.146308, -97.761695 26.146173, -97.761684 26.145193, -97.761634 26.145188, -97.761475 26.145221, -97.760329 26.145011, -97.759829 26.144962, -97.759622 26.144835, -97.759602 26.144814, -97.759512 26.144719, -97.759347 26.144664, -97.759091 26.144686, -97.758835 26.144752, -97.758597 26.144675, -97.757823 26.144289, -97.75736 26.144141, -97.756695 26.144124, -97.756433 26.14403, -97.756201 26.143854, -97.755945 26.143843, -97.755689 26.14387, -97.755012 26.144085, -97.753543 26.144354, -97.753067 26.144342, -97.752756 26.144414, -97.752421 26.144441, -97.752171 26.144381, -97.751945 26.144254, -97.751695 26.144182, -97.751116 26.144232, -97.750909 26.1441, -97.750622 26.143918, -97.750232 26.143791, -97.749884 26.143755, -97.748982 26.143854, -97.747726 26.14386, -97.747451 26.143756, -97.746988 26.143668, -97.746774 26.143547, -97.746567 26.143365, -97.746366 26.143107, -97.745932 26.142859, -97.745762 26.1427, -97.745682 26.142546, -97.745329 26.14221, -97.745017 26.141968, -97.744676 26.141478, -97.744121 26.140961, -97.744072 26.140824, -97.744053 26.140582, -97.744004 26.140406, -97.743913 26.140284, -97.743693 26.140174, -97.743291 26.140098, -97.742827 26.139762, -97.742773 26.139652, -97.742669 26.139245, -97.742602 26.13919, -97.742199 26.139102, -97.741833 26.13848, -97.741632 26.13826, -97.741485 26.137847, -97.741272 26.137611, -97.740991 26.137539, -97.740205 26.13727, -97.739125 26.1371, -97.739015 26.137127, -97.738589 26.13754, -97.738369 26.137683, -97.738247 26.137744, -97.737583 26.137744, -97.737382 26.137849, -97.737071 26.138218, -97.736669 26.138256, -97.736461 26.138488, -97.736218 26.138911, -97.736035 26.1395, -97.735943 26.139605, -97.735852 26.13993, -97.736017 26.140458, -97.736023 26.140629, -97.735968 26.140854, -97.735853 26.141047, -97.735664 26.141229, -97.735201 26.141807, -97.734969 26.142032, -97.734689 26.142478, -97.734396 26.142753, -97.734164 26.142874, -97.733238 26.143056, -97.732884 26.143167, -97.732725 26.143112, -97.732177 26.142782, -97.731872 26.142809, -97.731677 26.142853, -97.731231 26.142721, -97.730701 26.142633, -97.730225 26.142474, -97.729975 26.142469, -97.729616 26.142579, -97.729583 26.142611, -97.729317 26.142865, -97.729158 26.142975, -97.728872 26.143003, -97.728561 26.143069, -97.728372 26.143245, -97.728347 26.143303, -97.728278 26.143465, -97.728189 26.143674, -97.728151 26.143745, -97.728082 26.143872, -97.727817 26.144362, -97.727452 26.145171, -97.727369 26.145428, -97.727269 26.145738, -97.72722 26.146162, -97.726873 26.146812, -97.726898 26.147654, -97.726782 26.148545, -97.726825 26.14898, -97.726923 26.149343, -97.726917 26.149596, -97.726716 26.150097, -97.726689 26.150294, -97.726637 26.150664, -97.726588 26.150807, -97.726393 26.150989, -97.726039 26.151231, -97.725801 26.151429, -97.725326 26.151892, -97.724948 26.152062, -97.724625 26.152536, -97.724107 26.152795, -97.723625 26.152872, -97.723235 26.153097, -97.723009 26.153196, -97.722625 26.153285, -97.721936 26.153433, -97.721308 26.153423, -97.720729 26.153874, -97.720558 26.154045, -97.720424 26.154254, -97.720265 26.154716, -97.720113 26.154986, -97.719961 26.15514, -97.719826 26.155244, -97.719363 26.155333, -97.719143 26.155426, -97.718723 26.155724, -97.717674 26.156577, -97.716888 26.1571, -97.716369 26.157551, -97.715869 26.157931, -97.715601 26.158241, -97.715498 26.15836, -97.715333 26.158498, -97.714809 26.158762, -97.713961 26.159483, -97.713644 26.160001, -97.713357 26.160237, -97.7132 26.160399, -97.712827 26.160735, -97.712843 26.160766, -97.712412 26.161212, -97.712181 26.161355, -97.71201 26.161553, -97.711965 26.161529, -97.712169 26.161329, -97.712083 26.161301, -97.711814 26.161214, -97.711607 26.161024, -97.711429 26.160855, -97.711237 26.160612, -97.711075 26.160357, -97.711018 26.160212, -97.710946 26.159963, -97.71089 26.159756, -97.710834 26.159481, -97.710854 26.15928, -97.710889 26.159073, -97.71097 26.158798, -97.711114 26.15852, -97.711452 26.157791, -97.711217 26.157598, -97.711162 26.157553, -97.711114 26.157521, -97.712317 26.154717, -97.713473 26.152023, -97.713784 26.1513, -97.713987 26.150826, -97.714105 26.150551, -97.714301 26.150276, -97.714532 26.150056, -97.716148 26.149164, -97.716459 26.148933, -97.716733 26.148581, -97.716934 26.148206, -97.717068 26.14792, -97.71719 26.147216, -97.717336 26.145174, -97.717409 26.144899, -97.717823 26.144024, -97.718377 26.143062, -97.71853 26.142796, -97.719055 26.142196, -97.719353 26.14197, -97.719829 26.141717, -97.720054 26.141547, -97.720231 26.141326, -97.723845 26.134998, -97.723867 26.134959, -97.723045 26.134592, -97.722756 26.13505, -97.722745 26.135067, -97.72223 26.135977, -97.721286 26.137647, -97.719505 26.140726, -97.718729 26.142018, -97.718172 26.142981, -97.717634 26.143935, -97.717559 26.144068, -97.713773 26.150758, -97.713764 26.150774, -97.713519 26.151202, -97.71311 26.151917, -97.711439 26.154838, -97.710512 26.156458, -97.710221 26.156937, -97.706654 26.155302, -97.706432 26.1552, -97.703053 26.15362, -97.701221 26.152763, -97.698464 26.151474, -97.695867 26.150259, -97.695502 26.150088, -97.695407 26.150043, -97.693158 26.148992, -97.692301 26.148591, -97.694166 26.145379, -97.695133 26.145802, -97.696987 26.142809, -97.697369 26.142953, -97.699369 26.144053, -97.699467 26.143852, -97.699646 26.143539, -97.699811 26.14325, -97.700293 26.142408, -97.700259 26.142374, -97.70018 26.142314, -97.700094 26.142263, -97.700003 26.142218, -97.69991 26.142179, -97.699816 26.142142, -97.699724 26.142107, -97.699633 26.142071, -97.699544 26.142035, -97.699457 26.142, -97.69937 26.141967, -97.699284 26.141934, -97.699175 26.141894, -97.699112 26.141866, -97.699025 26.14183, -97.698936 26.141794, -97.698848 26.141759, -97.69876 26.141724, -97.69867 26.141694, -97.69858 26.141668, -97.698489 26.141649, -97.698304 26.14163, -97.69822 26.141626, -97.698119 26.14162, -97.698027 26.141615, -97.697759 26.141606, -97.697673 26.141601, -97.6975 26.141592, -97.697415 26.141589, -97.697331 26.141587, -97.697167 26.141592, -97.697017 26.141609, -97.696946 26.141622, -97.696811 26.141655, -97.696619 26.141704, -97.696527 26.141732, -97.696493 26.141736, -97.696365 26.141767, -97.696172 26.141816, -97.696041 26.14185, -97.695922 26.141881, -97.695675 26.1419, -97.696746 26.142419, -97.696805 26.142476, -97.696747 26.142508, -97.696645 26.142534, -97.696541 26.142562, -97.696434 26.142596, -97.696337 26.142619, -97.696239 26.142646, -97.69614 26.142671, -97.696041 26.142695, -97.695942 26.142719, -97.695845 26.142744, -97.69575 26.142768, -97.695676 26.142786, -97.695657 26.142792, -97.695564 26.142818, -97.695472 26.142846, -97.695381 26.142881, -97.695292 26.142923, -97.695207 26.142976, -97.695131 26.143039, -97.695066 26.143115, -97.695007 26.143193, -97.694953 26.143276, -97.694902 26.143358, -97.694853 26.143443, -97.694806 26.143529, -97.694759 26.143615, -97.69475 26.143631, -97.694713 26.143699, -97.694666 26.143782, -97.694619 26.143863, -97.694573 26.143945, -97.694527 26.144027, -97.694483 26.144107, -97.69444 26.144184, -97.694397 26.144258, -97.694349 26.144328, -97.694296 26.144393, -97.694236 26.144454, -97.694171 26.144509, -97.694101 26.14456, -97.694027 26.144609, -97.693947 26.144655, -97.693874 26.144695, -97.693794 26.144731, -97.693713 26.144762, -97.69363 26.144787, -97.693546 26.144806, -97.69346 26.144818, -97.693374 26.144824, -97.693287 26.144824, -97.693201 26.144819, -97.693117 26.144809, -97.693036 26.144795, -97.692957 26.144776, -97.692882 26.144749, -97.692813 26.144715, -97.692749 26.144676, -97.692688 26.144637, -97.692632 26.144599, -97.692579 26.144566, -97.692491 26.144518, -97.692409 26.144503, -97.692314 26.144509, -97.692283 26.144555, -97.691773 26.14534, -97.691452 26.145186, -97.69137 26.145163, -97.691303 26.145198, -97.689796 26.14748, -97.689224 26.147046, -97.687525 26.149387, -97.68722 26.149607, -97.686903 26.149634, -97.686671 26.149612, -97.685013 26.149161, -97.684504 26.148997, -97.684578 26.149765, -97.684604 26.15012, -97.684667 26.150912, -97.684627 26.15102, -97.684557 26.15112, -97.68431 26.151391, -97.683983 26.151765, -97.683963 26.151802, -97.68396 26.151842, -97.683977 26.151882, -97.684048 26.151951, -97.684583 26.152346, -97.685713 26.15316, -97.68575 26.153197, -97.685835 26.153258, -97.685883 26.153277, -97.685922 26.153278, -97.68596 26.153263, -97.685998 26.153235, -97.686268 26.152833, -97.686602 26.152319, -97.686944 26.151806, -97.687077 26.151602, -97.687529 26.151829, -97.687505 26.151869, -97.687006 26.152645, -97.686507 26.153422, -97.686271 26.153789, -97.685866 26.154409, -97.684179 26.156905, -97.68412 26.156992)))"} -{"geo_id":"83332","urban_area_code":"83332","name":"South Lyon--Howell, MI","lsad_name":"South Lyon--Howell, MI Urbanized Area","area_lsad_code":"75","mtfcc_feature_class_code":"G3500","type":"U","functional_status":"S","area_land_meters":265933233,"area_water_meters":24500157,"internal_point_lon":-83.806042,"internal_point_lat":42.5072785,"internal_point_geom":"POINT(-83.806042 42.5072785)","urban_area_geom":"MULTIPOLYGON(((-83.741819 42.483085, -83.740995 42.482938, -83.740737 42.483767, -83.740707 42.484101, -83.740387 42.484564, -83.739524 42.485453, -83.739549 42.485701, -83.739594 42.485845, -83.739797 42.486067, -83.739849 42.486036, -83.740106 42.485954, -83.740059 42.485812, -83.740249 42.485759, -83.740378 42.485845, -83.740518 42.485853, -83.740745 42.485761, -83.740802 42.485672, -83.740977 42.485581, -83.741163 42.485572, -83.74127 42.485526, -83.741495 42.485362, -83.74155 42.485014, -83.741641 42.48487, -83.741576 42.484695, -83.741819 42.484393, -83.741697 42.484055, -83.741765 42.48385, -83.741886 42.483704, -83.74208 42.483563, -83.742133 42.48348, -83.741951 42.483475, -83.741784 42.483392, -83.741747 42.483234, -83.741819 42.483085)), ((-83.589497 42.412654, -83.589513 42.408822, -83.589513 42.408466, -83.589509 42.405866, -83.586836 42.405954, -83.584685 42.406007, -83.582296 42.406087, -83.581928 42.406096, -83.580826 42.406127, -83.579733 42.406164, -83.5797 42.406037, -83.579651 42.405767, -83.579526 42.405191, -83.57943 42.405226, -83.579327 42.405245, -83.578961 42.405268, -83.578372 42.405295, -83.577409 42.405302, -83.577316 42.405311, -83.577241 42.405339, -83.577181 42.405421, -83.577141 42.405513, -83.57715 42.405634, -83.577302 42.406232, -83.576984 42.406241, -83.576569 42.406245, -83.577656 42.407203, -83.579655 42.408966, -83.580347 42.409576, -83.58154 42.410628, -83.582111 42.411013, -83.582755 42.411415, -83.585139 42.412636, -83.587411 42.413947, -83.589469 42.41514, -83.589497 42.412654)), ((-83.907903 42.415412, -83.908565 42.415514, -83.908587 42.415576, -83.90912 42.415634, -83.909243 42.415595, -83.91065 42.41515, -83.910682 42.415135, -83.910964 42.415102, -83.911086 42.414608, -83.911344 42.414637, -83.911245 42.415186, -83.911422 42.415236, -83.911674 42.415286, -83.911766 42.41566, -83.911558 42.41594, -83.911267 42.416214, -83.911385 42.416346, -83.911667 42.416237, -83.912008 42.416062, -83.912484 42.415866, -83.913344 42.415617, -83.914085 42.41546, -83.914707 42.415446, -83.914996 42.415414, -83.915448 42.415267, -83.915959 42.415165, -83.916524 42.415079, -83.917397 42.415082, -83.918203 42.4152, -83.918868 42.41528, -83.919281 42.415534, -83.91959 42.415827, -83.919966 42.41602, -83.920498 42.41616, -83.921271 42.416273, -83.92203 42.416361, -83.922913 42.416484, -83.923596 42.416639, -83.924221 42.416877, -83.924668 42.417085, -83.925029 42.417317, -83.925559 42.417658, -83.925908 42.417403, -83.92515 42.416849, -83.924864 42.416649, -83.924462 42.416449, -83.92416 42.41634, -83.923837 42.416256, -83.922022 42.415886, -83.921325 42.415708, -83.920917 42.415575, -83.920442 42.415375, -83.919093 42.414779, -83.918545 42.414496, -83.91805 42.414201, -83.91729 42.413694, -83.916342 42.413099, -83.914568 42.411949, -83.913669 42.411467, -83.912877 42.412328, -83.912608 42.412604, -83.912391 42.412764, -83.912065 42.412948, -83.911216 42.413361, -83.909995 42.413895, -83.909433 42.414173, -83.909145 42.414347, -83.908787 42.414603, -83.908402 42.414916, -83.908172 42.415111, -83.907903 42.415412)), ((-83.966416 42.461836, -83.966672 42.465004, -83.966693 42.465264, -83.966724 42.466053, -83.966789 42.46685, -83.966806 42.467103, -83.966899 42.468192, -83.966908 42.468341, -83.967142 42.472016, -83.967194 42.472838, -83.967252 42.473818, -83.967311 42.474797, -83.967533 42.474972, -83.96883 42.474337, -83.968964 42.474271, -83.969998 42.473765, -83.970295 42.473601, -83.970496 42.473429, -83.972796 42.471328, -83.972963 42.471187, -83.973162 42.471047, -83.973507 42.47089, -83.973826 42.470793, -83.974218 42.470693, -83.974733 42.470614, -83.974825 42.470588, -83.974879 42.470548, -83.974935 42.470499, -83.974992 42.470432, -83.975036 42.470365, -83.975088 42.470282, -83.975124 42.470213, -83.975146 42.470154, -83.975165 42.470093, -83.975174 42.469905, -83.975167 42.469842, -83.975156 42.469781, -83.975149 42.469719, -83.975127 42.469597, -83.97512 42.469534, -83.97511 42.469469, -83.975099 42.469407, -83.975085 42.469344, -83.975063 42.469284, -83.975037 42.469227, -83.975004 42.469172, -83.974964 42.46912, -83.974916 42.469071, -83.97486 42.469025, -83.974801 42.468982, -83.974735 42.468943, -83.974665 42.468907, -83.974583 42.468878, -83.974502 42.468855, -83.974417 42.46884, -83.974328 42.468832, -83.974239 42.46883, -83.97415 42.46883, -83.974057 42.468832, -83.973965 42.468834, -83.973872 42.468837, -83.973783 42.468841, -83.97369 42.468846, -83.973598 42.46885, -83.973505 42.468855, -83.973416 42.468859, -83.973331 42.468862, -83.973242 42.468865, -83.973219 42.468866, -83.973153 42.46887, -83.973067 42.468875, -83.972978 42.468882, -83.972889 42.468891, -83.972797 42.468902, -83.972704 42.468917, -83.972615 42.468934, -83.972522 42.468953, -83.972429 42.468975, -83.972336 42.468999, -83.972244 42.469026, -83.972154 42.469054, -83.972065 42.469083, -83.97198 42.469114, -83.971894 42.469146, -83.971813 42.469179, -83.971735 42.469213, -83.971657 42.469248, -83.971586 42.469283, -83.971512 42.469319, -83.971445 42.469355, -83.971378 42.469391, -83.971314 42.469428, -83.971251 42.469465, -83.971195 42.469502, -83.97114 42.469539, -83.971091 42.469574, -83.971046 42.469607, -83.970998 42.469632, -83.970942 42.469657, -83.970905 42.469681, -83.970794 42.469599, -83.970758 42.469574, -83.97071 42.469543, -83.970654 42.469506, -83.970599 42.469468, -83.97054 42.469428, -83.970477 42.469387, -83.970414 42.469345, -83.970352 42.469303, -83.970285 42.469259, -83.970222 42.469216, -83.970156 42.469172, -83.970086 42.469127, -83.970019 42.469082, -83.969949 42.469037, -83.969879 42.468993, -83.969813 42.468949, -83.969739 42.468899, -83.970085 42.468608, -83.970321 42.468343, -83.970488 42.468107, -83.970576 42.467861, -83.970616 42.467547, -83.970625 42.467243, -83.970616 42.46688, -83.970596 42.466615, -83.970527 42.466379, -83.970341 42.465918, -83.970311 42.46579, -83.970311 42.465574, -83.970341 42.465437, -83.970409 42.465231, -83.970419 42.465083, -83.9704 42.464956, -83.97036 42.464867, -83.970311 42.46473, -83.970243 42.464504, -83.970233 42.464327, -83.970243 42.46418, -83.970262 42.464062, -83.970334 42.463864, -83.970346 42.463836, -83.970368 42.463797, -83.970394 42.463761, -83.970417 42.463726, -83.970439 42.463692, -83.970462 42.463658, -83.970484 42.463627, -83.970503 42.463596, -83.970544 42.463539, -83.970578 42.463488, -83.970611 42.463442, -83.970637 42.4634, -83.970714 42.463187, -83.968345 42.462394, -83.966416 42.461836)), ((-83.935627 42.436677, -83.935539 42.434694, -83.935495 42.432709, -83.935472 42.431604, -83.935446 42.430903, -83.935437 42.430826, -83.935435 42.430517, -83.935392 42.429899, -83.935376 42.429335, -83.935349 42.42914, -83.9353 42.428951, -83.935218 42.428822, -83.935099 42.428701, -83.934997 42.428629, -83.934831 42.428545, -83.934572 42.428437, -83.934301 42.428336, -83.933988 42.428168, -83.933549 42.427908, -83.932941 42.427561, -83.932599 42.427355, -83.932104 42.427031, -83.93173 42.426743, -83.931509 42.426548, -83.931224 42.426281, -83.930861 42.425921, -83.930755 42.425805, -83.930257 42.425193, -83.930107 42.424946, -83.930004 42.424765, -83.929967 42.424652, -83.929966 42.424556, -83.930018 42.423454, -83.930032 42.423228, -83.930037 42.422523, -83.930009 42.422335, -83.929998 42.422264, -83.929915 42.421927, -83.929676 42.42062, -83.932419 42.42048, -83.932433 42.419705, -83.932388 42.419623, -83.9323 42.419541, -83.932248 42.419628, -83.932187 42.419786, -83.932149 42.419868, -83.932112 42.419917, -83.931986 42.419912, -83.931875 42.419846, -83.93184 42.419704, -83.931947 42.419286, -83.932 42.419124, -83.931867 42.4192, -83.931615 42.419297, -83.931422 42.419379, -83.931258 42.419509, -83.931161 42.419547, -83.930284 42.419312, -83.93006 42.419241, -83.92965 42.41911, -83.929325 42.418997, -83.929148 42.418885, -83.929108 42.41811, -83.929086 42.417792, -83.929087 42.417711, -83.928789 42.417688, -83.928856 42.417772, -83.928857 42.417845, -83.928589 42.417887, -83.928165 42.417893, -83.927789 42.417792, -83.927467 42.417674, -83.927177 42.417558, -83.926892 42.41739, -83.926651 42.417232, -83.926519 42.417137, -83.92639 42.417204, -83.926321 42.417246, -83.926037 42.417491, -83.925856 42.417633, -83.925839 42.417733, -83.927005 42.418485, -83.927332 42.418873, -83.927518 42.419381, -83.927624 42.419918, -83.92746 42.420253, -83.927184 42.420453, -83.927159 42.420704, -83.927135 42.420927, -83.927028 42.421373, -83.926882 42.421913, -83.926734 42.422166, -83.926633 42.422339, -83.926393 42.422611, -83.926324 42.422678, -83.926236 42.422749, -83.926079 42.422812, -83.92597 42.42284, -83.925745 42.422956, -83.922393 42.42469, -83.922513 42.424849, -83.922647 42.424882, -83.922957 42.425042, -83.923126 42.425036, -83.923313 42.425122, -83.923489 42.425037, -83.923617 42.424941, -83.923758 42.42489, -83.92382 42.424878, -83.923958 42.424819, -83.924058 42.424736, -83.924177 42.42473, -83.924498 42.424733, -83.924727 42.424613, -83.924912 42.424607, -83.924826 42.424707, -83.924583 42.424834, -83.924432 42.424905, -83.92431 42.424881, -83.924091 42.424886, -83.923974 42.424933, -83.923845 42.424973, -83.923749 42.425026, -83.923675 42.425099, -83.923481 42.425203, -83.923288 42.425274, -83.923147 42.425295, -83.923007 42.425295, -83.922752 42.425103, -83.922461 42.424964, -83.922365 42.424908, -83.922037 42.425026, -83.921971 42.42505, -83.92137 42.425115, -83.921037 42.425075, -83.920823 42.42502, -83.920671 42.425046, -83.920349 42.425101, -83.919386 42.425111, -83.919093 42.425114, -83.918818 42.425196, -83.917222 42.426219, -83.916319 42.427124, -83.915388 42.428199, -83.915195 42.428368, -83.914351 42.428756, -83.913995 42.428865, -83.913768 42.429436, -83.913789 42.429645, -83.913892 42.429755, -83.913935 42.429887, -83.91412 42.430031, -83.914393 42.43013, -83.914357 42.430031, -83.91447 42.429795, -83.914515 42.429592, -83.914651 42.429235, -83.914892 42.428968, -83.915572 42.428457, -83.915825 42.428381, -83.916202 42.428366, -83.916432 42.42846, -83.91666 42.428609, -83.916896 42.428725, -83.917214 42.428802, -83.917244 42.428759, -83.917121 42.428445, -83.917195 42.42828, -83.917666 42.427732, -83.917827 42.427458, -83.917784 42.427353, -83.917703 42.427292, -83.917866 42.427232, -83.918007 42.427233, -83.918245 42.427189, -83.91843 42.427124, -83.9186 42.427152, -83.918614 42.427333, -83.919033 42.427714, -83.919141 42.428132, -83.919125 42.428269, -83.919069 42.428395, -83.919653 42.428611, -83.919711 42.428672, -83.919704 42.428721, -83.919645 42.428737, -83.91903 42.42864, -83.919 42.428711, -83.919062 42.428871, -83.919055 42.428926, -83.918875 42.429089, -83.91883 42.42921, -83.918903 42.429381, -83.91946 42.429648, -83.919563 42.429737, -83.919688 42.429886, -83.919724 42.430024, -83.919812 42.430189, -83.919908 42.430289, -83.920181 42.430417, -83.920388 42.430446, -83.920522 42.430425, -83.920648 42.430365, -83.920954 42.430098, -83.921202 42.429984, -83.921336 42.429968, -83.92341 42.429971, -83.923543 42.430005, -83.923617 42.430082, -83.923609 42.430203, -83.923527 42.430241, -83.923297 42.43025, -83.922194 42.430205, -83.921745 42.430284, -83.921559 42.430371, -83.921239 42.430589, -83.920946 42.430697, -83.92036 42.430726, -83.920034 42.430702, -83.919797 42.430651, -83.919546 42.43055, -83.919399 42.430456, -83.918924 42.430029, -83.918667 42.429725, -83.918462 42.429422, -83.918342 42.429036, -83.918255 42.428866, -83.918166 42.428793, -83.918077 42.428787, -83.917766 42.428873, -83.917617 42.428971, -83.917217 42.429337, -83.91709 42.429561, -83.916897 42.429653, -83.916678 42.429718, -83.916223 42.430083, -83.915847 42.430289, -83.915603 42.430354, -83.915136 42.430406, -83.914343 42.430345, -83.914246 42.430367, -83.913937 42.43065, -83.913609 42.430989, -83.913538 42.431076, -83.9135 42.431208, -83.913603 42.431329, -83.913891 42.431364, -83.913972 42.431442, -83.913971 42.431601, -83.913882 42.431688, -83.913748 42.431754, -83.913566 42.431741, -83.913452 42.43168, -83.913439 42.431482, -83.913394 42.431399, -83.913343 42.431388, -83.913224 42.431453, -83.913141 42.431607, -83.913255 42.431805, -83.913254 42.431888, -83.913224 42.431931, -83.91303 42.432051, -83.912973 42.432123, -83.912758 42.43239, -83.912602 42.432466, -83.912744 42.43277, -83.912766 42.432812, -83.912867 42.43287, -83.913018 42.43295, -83.913461 42.433198, -83.913592 42.433419, -83.913583 42.433556, -83.913523 42.433743, -83.913725 42.433815, -83.913992 42.433849, -83.914072 42.434015, -83.914176 42.434015, -83.914383 42.434132, -83.914432 42.434401, -83.914373 42.4345, -83.91443 42.434633, -83.914652 42.434765, -83.914991 42.434921, -83.915325 42.434911, -83.915787 42.434753, -83.916216 42.434804, -83.916223 42.434887, -83.916282 42.434964, -83.916393 42.434882, -83.916498 42.434695, -83.916596 42.434602, -83.916747 42.434608, -83.916799 42.434537, -83.916763 42.434427, -83.916802 42.434256, -83.916876 42.434174, -83.917047 42.434147, -83.917195 42.434219, -83.917336 42.434203, -83.917491 42.434259, -83.917505 42.43433, -83.917385 42.434528, -83.91754 42.434583, -83.918185 42.434591, -83.918431 42.435385, -83.918444 42.435616, -83.918207 42.435593, -83.918214 42.435698, -83.918272 42.435819, -83.918324 42.435896, -83.918628 42.43587, -83.918826 42.436107, -83.918981 42.436179, -83.919032 42.436268, -83.918869 42.436284, -83.918713 42.436371, -83.918764 42.436481, -83.918904 42.436564, -83.918969 42.436823, -83.919109 42.436857, -83.919346 42.436874, -83.919561 42.436941, -83.919727 42.437074, -83.919815 42.43703, -83.920024 42.436987, -83.920357 42.437005, -83.920512 42.437104, -83.920482 42.437159, -83.920503 42.437341, -83.92096 42.437651, -83.920937 42.437761, -83.920691 42.437963, -83.920712 42.438074, -83.920853 42.438069, -83.921002 42.43797, -83.921217 42.437943, -83.92138 42.438026, -83.921401 42.438126, -83.921155 42.438411, -83.921176 42.438521, -83.921672 42.438589, -83.921782 42.43871, -83.921825 42.438903, -83.921728 42.438991, -83.921528 42.439007, -83.921357 42.439056, -83.921356 42.439248, -83.921451 42.439436, -83.922206 42.439477, -83.922213 42.439571, -83.922093 42.439801, -83.922226 42.439846, -83.922352 42.439835, -83.922582 42.439776, -83.923473 42.438287, -83.924237 42.437848, -83.924622 42.437466, -83.925085 42.437064, -83.925679 42.436631, -83.926094 42.436395, -83.926481 42.43673, -83.927079 42.437338, -83.927216 42.437739, -83.927282 42.438053, -83.92732 42.439128, -83.935738 42.438869, -83.935627 42.436677)), ((-83.967509 42.436478, -83.967524 42.436706, -83.967577 42.436709, -83.967617 42.436835, -83.967639 42.437511, -83.967689 42.437841, -83.967454 42.437931, -83.967258 42.437944, -83.96732 42.438097, -83.967719 42.438291, -83.967947 42.438476, -83.968144 42.439062, -83.968201 42.439386, -83.96808 42.439695, -83.968165 42.439859, -83.968608 42.440037, -83.969137 42.440125, -83.969952 42.44015, -83.970434 42.440068, -83.970887 42.439942, -83.971008 42.440062, -83.970938 42.440206, -83.971011 42.440293, -83.970925 42.440398, -83.970902 42.440481, -83.970984 42.440486, -83.971229 42.440335, -83.971338 42.440196, -83.971435 42.439937, -83.971633 42.439715, -83.972434 42.439581, -83.972667 42.43971, -83.972951 42.439785, -83.973045 42.439745, -83.973112 42.439651, -83.973072 42.439569, -83.972905 42.439477, -83.972928 42.439389, -83.973051 42.439338, -83.973236 42.439353, -83.973414 42.439411, -83.973531 42.439515, -83.973784 42.439941, -83.973904 42.44038, -83.97414 42.440681, -83.974271 42.440849, -83.974466 42.441034, -83.974582 42.441016, -83.97495 42.440721, -83.975225 42.440609, -83.975714 42.440538, -83.97732 42.44039, -83.977697 42.440463, -83.978345 42.440467, -83.979273 42.440211, -83.979562 42.440263, -83.979835 42.44037, -83.980278 42.440608, -83.980404 42.440628, -83.981012 42.440567, -83.981432 42.440447, -83.981759 42.440268, -83.981989 42.440095, -83.982065 42.439836, -83.982094 42.439374, -83.982166 42.439214, -83.982466 42.43915, -83.982628 42.439226, -83.982776 42.439329, -83.983037 42.439618, -83.983432 42.4399, -83.983706 42.43993, -83.983828 42.439896, -83.983977 42.439762, -83.984141 42.439106, -83.984113 42.438695, -83.98409 42.438354, -83.983942 42.438217, -83.983728 42.438192, -83.983264 42.438208, -83.983165 42.438093, -83.98309 42.437555, -83.983107 42.437308, -83.983217 42.436844, -83.983478 42.436567, -83.983923 42.436441, -83.984165 42.436428, -83.984386 42.436618, -83.984659 42.43667, -83.984915 42.436789, -83.984976 42.436981, -83.98483 42.437241, -83.984761 42.437494, -83.984854 42.437565, -83.984988 42.437552, -83.985199 42.437457, -83.98543 42.437278, -83.986067 42.436673, -83.986354 42.436406, -83.987001 42.435976, -83.987183 42.435815, -83.987247 42.435699, -83.986949 42.434745, -83.987053 42.434727, -83.987433 42.435614, -83.987712 42.435485, -83.988001 42.43535, -83.988339 42.435165, -83.988322 42.435033, -83.988219 42.434842, -83.988416 42.434796, -83.988554 42.434741, -83.989497 42.434941, -83.989535 42.434949, -83.989872 42.435014, -83.990094 42.435028, -83.990376 42.434977, -83.990429 42.434966, -83.990761 42.434843, -83.99264 42.433897, -83.992516 42.433655, -83.992432 42.433533, -83.99216 42.433169, -83.992022 42.432995, -83.991814 42.432735, -83.991402 42.432301, -83.991346 42.432207, -83.991328 42.432126, -83.99132 42.432035, -83.991347 42.431931, -83.991462 42.431754, -83.991514 42.431643, -83.991136 42.431645, -83.990686 42.431419, -83.990611 42.431381, -83.990356 42.431312, -83.989989 42.431349, -83.989554 42.431551, -83.989376 42.431696, -83.989282 42.431862, -83.98923 42.431994, -83.989232 42.43216, -83.989172 42.432402, -83.989241 42.432588, -83.989247 42.432803, -83.989191 42.433441, -83.989161 42.43348, -83.989054 42.433502, -83.988764 42.433561, -83.988565 42.433392, -83.988418 42.433333, -83.988261 42.433368, -83.988135 42.433435, -83.987987 42.433426, -83.987809 42.433482, -83.987697 42.433561, -83.987584 42.433528, -83.987439 42.433486, -83.987428 42.433376, -83.987784 42.433356, -83.988472 42.43303, -83.988672 42.432874, -83.988576 42.432727, -83.988367 42.432559, -83.98802 42.432364, -83.987957 42.432277, -83.988077 42.432006, -83.988406 42.431601, -83.988837 42.431278, -83.988982 42.431293, -83.989168 42.431269, -83.989283 42.431142, -83.988952 42.430689, -83.988863 42.4302, -83.988749 42.430064, -83.988486 42.429951, -83.988314 42.429749, -83.988393 42.429402, -83.988218 42.429052, -83.988252 42.428898, -83.988117 42.428553, -83.988099 42.427827, -83.988 42.427773, -83.987873 42.427824, -83.987779 42.428001, -83.987704 42.428254, -83.987736 42.428457, -83.987687 42.428491, -83.987573 42.428421, -83.987574 42.428212, -83.987535 42.428053, -83.987439 42.42801, -83.98736 42.428186, -83.987311 42.428231, -83.987114 42.428224, -83.987049 42.428222, -83.98697 42.428344, -83.986874 42.428309, -83.98682 42.428071, -83.986643 42.427974, -83.986573 42.427859, -83.986607 42.427639, -83.986458 42.427283, -83.986507 42.427095, -83.986475 42.426931, -83.986383 42.426772, -83.986328 42.426591, -83.985907 42.426491, -83.985796 42.426432, -83.985711 42.425773, -83.98581 42.425488, -83.985983 42.425473, -83.986046 42.425527, -83.98609 42.425659, -83.986074 42.425818, -83.986218 42.425889, -83.986433 42.425875, -83.986645 42.425697, -83.986753 42.425674, -83.987037 42.425732, -83.987274 42.425944, -83.987331 42.426257, -83.987621 42.426628, -83.987778 42.427055, -83.987877 42.427208, -83.988043 42.427311, -83.988184 42.427298, -83.988248 42.427578, -83.98843 42.427653, -83.988746 42.427474, -83.988925 42.427192, -83.988768 42.426286, -83.988794 42.426132, -83.988528 42.425617, -83.988652 42.425281, -83.988519 42.425006, -83.988633 42.424779, -83.989091 42.424555, -83.989405 42.424573, -83.989819 42.424612, -83.990184 42.424706, -83.990283 42.424941, -83.99019 42.425062, -83.989712 42.425177, -83.989464 42.425313, -83.989394 42.425585, -83.988848 42.42584, -83.988871 42.426001, -83.989028 42.426095, -83.989242 42.425896, -83.989479 42.425845, -83.989809 42.425844, -83.990099 42.426042, -83.989867 42.426412, -83.989829 42.426534, -83.989888 42.426671, -83.990098 42.426784, -83.990612 42.426872, -83.991035 42.426851, -83.990938 42.426632, -83.990953 42.426457, -83.990918 42.426159, -83.99091 42.425871, -83.990907 42.425791, -83.990905 42.42571, -83.990736 42.425612, -83.990555 42.425547, -83.990478 42.42534, -83.990646 42.425145, -83.990903 42.424911, -83.991262 42.424823, -83.992012 42.425049, -83.992389 42.425123, -83.992713 42.425186, -83.993016 42.425105, -83.993054 42.425086, -83.99304 42.42502, -83.992988 42.424778, -83.992353 42.424665, -83.991877 42.424585, -83.991414 42.42452, -83.99064 42.42443, -83.989942 42.424274, -83.989685 42.424229, -83.989365 42.424205, -83.989011 42.424195, -83.988722 42.424215, -83.988532 42.424283, -83.988383 42.424385, -83.988218 42.424598, -83.98803 42.424867, -83.987831 42.425188, -83.987775 42.425293, -83.987711 42.42534, -83.987622 42.425374, -83.98742 42.425391, -83.987237 42.425388, -83.987049 42.425343, -83.986796 42.425269, -83.986454 42.425189, -83.986233 42.425148, -83.985855 42.425149, -83.985682 42.425193, -83.985541 42.425222, -83.985411 42.425293, -83.985319 42.425403, -83.985253 42.425542, -83.985203 42.425714, -83.985148 42.426038, -83.985132 42.426194, -83.985128 42.426293, -83.985148 42.426384, -83.985189 42.426471, -83.985248 42.426546, -83.98535 42.426643, -83.985611 42.426841, -83.985762 42.426976, -83.985864 42.427093, -83.985936 42.427221, -83.986025 42.427429, -83.986078 42.427517, -83.986107 42.427628, -83.98612 42.427764, -83.986098 42.427908, -83.986061 42.428039, -83.986043 42.428405, -83.986062 42.428509, -83.986098 42.428636, -83.986096 42.428775, -83.986066 42.428863, -83.985988 42.429004, -83.98588 42.429122, -83.985804 42.429213, -83.985547 42.429491, -83.985668 42.429569, -83.985746 42.429679, -83.985813 42.429862, -83.986008 42.430364, -83.986288 42.431054, -83.986456 42.431479, -83.986546 42.431682, -83.986813 42.432404, -83.986858 42.432512, -83.986923 42.432621, -83.987136 42.432891, -83.987219 42.433041, -83.987276 42.433128, -83.986992 42.433314, -83.986722 42.433452, -83.986568 42.433515, -83.98647 42.433547, -83.986176 42.433574, -83.985762 42.433585, -83.985543 42.433595, -83.985417 42.433611, -83.985308 42.433641, -83.985213 42.43369, -83.985124 42.433773, -83.985045 42.433889, -83.984965 42.434034, -83.984748 42.434466, -83.984491 42.434464, -83.984542 42.434971, -83.98343 42.435393, -83.983232 42.435462, -83.982929 42.435515, -83.98248 42.435549, -83.976726 42.436414, -83.974636 42.436713, -83.974017 42.436775, -83.970937 42.437104, -83.970588 42.437124, -83.97028 42.437119, -83.970032 42.437087, -83.969728 42.436988, -83.968807 42.436646, -83.968476 42.436536, -83.968224 42.436481, -83.967989 42.43646, -83.967736 42.436456, -83.967509 42.436478)), ((-83.7848 42.600635, -83.783812 42.60065, -83.783171 42.600672, -83.783171 42.600695, -83.783171 42.600802, -83.783195 42.60246, -83.784873 42.602389, -83.7848 42.600635)), ((-83.746023 42.398742, -83.745851 42.398651, -83.745652 42.398452, -83.745678 42.398084, -83.745503 42.397736, -83.742836 42.39655, -83.742722 42.398022, -83.73892 42.398106, -83.739027 42.39838, -83.739084 42.398448, -83.739311 42.39872, -83.739634 42.399071, -83.740055 42.399548, -83.740617 42.400037, -83.74098 42.400321, -83.741434 42.400617, -83.741753 42.40079, -83.742951 42.401441, -83.74334 42.401422, -83.743541 42.401421, -83.744257 42.401418, -83.744475 42.401409, -83.744622 42.401345, -83.74474 42.40124, -83.744806 42.401103, -83.745037 42.400039, -83.745155 42.399635, -83.745265 42.399431, -83.745403 42.399277, -83.746023 42.398742)), ((-83.613597 42.432238, -83.612832 42.431731, -83.611188 42.430412, -83.609237 42.428653, -83.609079 42.428511, -83.60904 42.428475, -83.608764 42.428224, -83.608666 42.428136, -83.606819 42.426625, -83.600979 42.422163, -83.599226 42.420835, -83.598636 42.420456, -83.595951 42.418904, -83.593068 42.417212, -83.589469 42.41514, -83.589469 42.415269, -83.589443 42.419487, -83.589521 42.420354, -83.589908 42.426524, -83.590119 42.430386, -83.59025 42.432756, -83.59031 42.433857, -83.59079 42.433863, -83.592525 42.433778, -83.593875 42.43373, -83.594604 42.433704, -83.597347 42.433605, -83.597765 42.43359, -83.598796 42.43355, -83.600628 42.433488, -83.600686 42.433486, -83.601448 42.433455, -83.601488 42.433453, -83.601719 42.433448, -83.601975 42.433442, -83.602487 42.433422, -83.60357 42.433381, -83.60524 42.433322, -83.606595 42.433262, -83.606992 42.433256, -83.608878 42.433193, -83.609291 42.433175, -83.609412 42.43317, -83.609904 42.433148, -83.611222 42.433107, -83.613409 42.433022, -83.614623 42.432969, -83.613597 42.432238)), ((-83.966416 42.461836, -83.966309 42.460777, -83.966189 42.459835, -83.966075 42.458986, -83.966066 42.458564, -83.966068 42.458491, -83.966069 42.458421, -83.966107 42.457942, -83.966106 42.457343, -83.966003 42.456701, -83.965799 42.453678, -83.965767 42.453087, -83.962318 42.453127, -83.960139 42.453113, -83.959597 42.453119, -83.957221 42.453115, -83.955823 42.451357, -83.955867 42.453021, -83.955924 42.453125, -83.955371 42.453143, -83.950813 42.453195, -83.950365 42.453193, -83.948512 42.45322, -83.948134 42.453226, -83.947441 42.453236, -83.945973 42.453252, -83.945976 42.452557, -83.945949 42.451762, -83.94594 42.451507, -83.945801 42.448202, -83.945714 42.44593, -83.94562 42.442953, -83.945516 42.439277, -83.9455 42.438691, -83.940725 42.438746, -83.935738 42.438869, -83.935786 42.440243, -83.935893 42.442437, -83.935983 42.444258, -83.93603 42.44526, -83.936115 42.447034, -83.936249 42.44978, -83.934863 42.449814, -83.934871 42.449973, -83.935359 42.450191, -83.93552 42.450406, -83.935598 42.45089, -83.935516 42.45101, -83.93536 42.451032, -83.935292 42.451125, -83.935277 42.451197, -83.935388 42.451313, -83.935801 42.451507, -83.936168 42.452135, -83.936372 42.452301, -83.936289 42.450752, -83.937665 42.450748, -83.937739 42.453392, -83.937461 42.453405, -83.936443 42.453436, -83.936574 42.45519, -83.936345 42.455357, -83.935397 42.455739, -83.933988 42.456389, -83.933676 42.456518, -83.932938 42.456822, -83.932851 42.456858, -83.931868 42.456963, -83.931859 42.457245, -83.929269 42.457312, -83.929049 42.456854, -83.928737 42.456887, -83.928655 42.456882, -83.927685 42.456817, -83.927099 42.456914, -83.925854 42.457366, -83.924821 42.457665, -83.923633 42.457972, -83.923244 42.45805, -83.923249 42.458336, -83.923532 42.460341, -83.923618 42.460485, -83.924331 42.460231, -83.924733 42.460101, -83.927368 42.459294, -83.927495 42.460907, -83.931605 42.460791, -83.931867 42.463683, -83.931875 42.463769, -83.931942 42.464497, -83.936859 42.464365, -83.937295 42.464353, -83.942272 42.464219, -83.952262 42.464043, -83.955305 42.463997, -83.9553 42.463931, -83.95523 42.463088, -83.955222 42.462895, -83.955192 42.46261, -83.955154 42.462155, -83.955051 42.460866, -83.956656 42.460841, -83.956651 42.460771, -83.956629 42.460613, -83.957301 42.460609, -83.957245 42.45955, -83.957214 42.459269, -83.958162 42.45951, -83.958784 42.459677, -83.961062 42.460288, -83.961757 42.460494, -83.962981 42.460859, -83.966416 42.461836)), ((-83.737681 42.582305, -83.736167 42.582358, -83.734757 42.582409, -83.734436 42.582418, -83.734116 42.582428, -83.734113 42.582735, -83.73413 42.583206, -83.734163 42.583617, -83.734243 42.584858, -83.734291 42.585697, -83.734322 42.586276, -83.734376 42.586854, -83.734387 42.58715, -83.734407 42.58741, -83.734427 42.5878, -83.734482 42.588817, -83.734512 42.589103, -83.734544 42.589626, -83.734521 42.589696, -83.734469 42.589758, -83.734562 42.589776, -83.734619 42.589824, -83.734792 42.590883, -83.734883 42.591562, -83.734897 42.592743, -83.73492 42.592873, -83.734971 42.592947, -83.735026 42.593007, -83.735135 42.593062, -83.736189 42.593221, -83.736507 42.593341, -83.736802 42.593532, -83.737368 42.593771, -83.738277 42.594405, -83.738649 42.594613, -83.738891 42.594668, -83.74054 42.595819, -83.740558 42.595194, -83.740504 42.594546, -83.740432 42.593973, -83.740193 42.592714, -83.740006 42.592083, -83.738052 42.586116, -83.737804 42.585227, -83.737654 42.584471, -83.737601 42.58389, -83.737572 42.583399, -83.737601 42.582854, -83.737681 42.582305)), ((-83.966924 42.593034, -83.966921 42.592659, -83.966674 42.592887, -83.966486 42.59297, -83.966226 42.593022, -83.965304 42.593022, -83.964788 42.593029, -83.964453 42.593059, -83.960931 42.593637, -83.960932 42.593722, -83.961004 42.594029, -83.961063 42.594698, -83.961231 42.595165, -83.96181 42.595842, -83.962081 42.596337, -83.962929 42.597349, -83.963668 42.598454, -83.963587 42.598854, -83.963635 42.599683, -83.963618 42.600008, -83.974691 42.599926, -83.9775 42.599918, -83.981409 42.599892, -83.982342 42.599886, -83.987864 42.59985, -83.991994 42.599817, -83.99309 42.599809, -83.995566 42.599778, -84.003614 42.599717, -84.005641 42.599702, -84.005738 42.597478, -84.005961 42.593134, -84.006067 42.591327, -84.006176 42.58952, -84.001954 42.589435, -84.001584 42.589471, -84.001373 42.58954, -84.00116 42.589643, -84.000771 42.590014, -83.999187 42.592048, -83.998659 42.592695, -83.998377 42.592957, -83.998045 42.59318, -83.996557 42.593985, -83.994473 42.594985, -83.993734 42.595317, -83.993463 42.595343, -83.993154 42.595338, -83.989624 42.595028, -83.989115 42.59497, -83.988349 42.594884, -83.98652 42.594748, -83.985697 42.594687, -83.982879 42.594396, -83.981963 42.59432, -83.981581 42.594234, -83.978967 42.593513, -83.977763 42.593167, -83.977013 42.592998, -83.975962 42.592976, -83.975875 42.592974, -83.975173 42.592973, -83.972074 42.593017, -83.971594 42.593019, -83.966924 42.593034)), ((-83.9455 42.438691, -83.946314 42.438682, -83.947863 42.438661, -83.949205 42.438657, -83.950503 42.43865, -83.950945 42.438647, -83.955338 42.438627, -83.962368 42.438605, -83.962762 42.4386, -83.96415 42.438586, -83.9645 42.438565, -83.964791 42.438516, -83.96515 42.438399, -83.965376 42.438273, -83.965521 42.43816, -83.966431 42.437044, -83.966647 42.436812, -83.966947 42.436651, -83.967246 42.436545, -83.967509 42.436478, -83.967351 42.436129, -83.966753 42.43471, -83.96678 42.434507, -83.967394 42.434066, -83.967529 42.433751, -83.967553 42.43346, -83.967685 42.433239, -83.967875 42.433105, -83.968281 42.432617, -83.968165 42.432255, -83.968041 42.432036, -83.967867 42.431846, -83.967825 42.431571, -83.967916 42.431334, -83.968064 42.431178, -83.968286 42.430753, -83.967956 42.430228, -83.967848 42.429707, -83.967764 42.429559, -83.967262 42.429339, -83.967014 42.429165, -83.966767 42.42903, -83.96646 42.429017, -83.966055 42.429098, -83.965862 42.429204, -83.965776 42.429332, -83.965749 42.429447, -83.965848 42.429617, -83.966146 42.429938, -83.966404 42.430051, -83.966529 42.430198, -83.966688 42.430928, -83.966432 42.431084, -83.966212 42.431147, -83.965516 42.431209, -83.965382 42.431243, -83.965337 42.431326, -83.965414 42.431507, -83.965209 42.431608, -83.964924 42.431561, -83.964094 42.431646, -83.963956 42.431263, -83.963809 42.431094, -83.963869 42.430989, -83.964006 42.430927, -83.964192 42.430804, -83.963897 42.430637, -83.963697 42.430595, -83.963445 42.430669, -83.962963 42.430717, -83.962587 42.430941, -83.962394 42.430998, -83.962075 42.431216, -83.961698 42.431038, -83.961747 42.430889, -83.961647 42.430785, -83.961551 42.430814, -83.961394 42.430997, -83.961223 42.431114, -83.960876 42.430815, -83.960741 42.430651, -83.960738 42.430536, -83.96095 42.43033, -83.961037 42.430137, -83.960972 42.429907, -83.960821 42.429749, -83.960617 42.429668, -83.960388 42.429714, -83.960218 42.429661, -83.960316 42.42938, -83.960149 42.429343, -83.959786 42.429336, -83.959709 42.429265, -83.959135 42.428138, -83.959126 42.427759, -83.959254 42.427416, -83.959419 42.42714, -83.959532 42.426814, -83.959159 42.426713, -83.958495 42.426803, -83.958073 42.427065, -83.957783 42.427231, -83.957486 42.42738, -83.957236 42.427489, -83.957028 42.427537, -83.956789 42.427537, -83.955821 42.427511, -83.953542 42.427379, -83.952394 42.427346, -83.950674 42.427277, -83.949872 42.427262, -83.948742 42.427319, -83.948398 42.427332, -83.948055 42.427346, -83.947775 42.427361, -83.945058 42.427502, -83.945252 42.432609, -83.945293 42.433376, -83.945403 42.436035, -83.945485 42.438273, -83.945499 42.438666, -83.945499 42.438678, -83.9455 42.438691)), ((-83.746023 42.398742, -83.749954 42.401232, -83.749947 42.405258, -83.742409 42.405411, -83.742584 42.412449, -83.742607 42.413246, -83.738665 42.413158, -83.738912 42.414253, -83.739043 42.414685, -83.739231 42.415169, -83.739394 42.415498, -83.739485 42.415682, -83.739667 42.415992, -83.739843 42.4162, -83.740056 42.416402, -83.740262 42.416563, -83.740783 42.41688, -83.741295 42.417217, -83.74202 42.417763, -83.745789 42.421013, -83.74629 42.42146, -83.745906 42.421782, -83.7456 42.422135, -83.74518 42.422692, -83.745025 42.423012, -83.744893 42.423368, -83.744724 42.423899, -83.744635 42.424212, -83.744586 42.424496, -83.74459 42.424948, -83.744671 42.425665, -83.74474 42.426092, -83.744793 42.426782, -83.74484 42.427635, -83.744856 42.427933, -83.744845 42.428231, -83.744841 42.42834, -83.744824 42.428479, -83.743239 42.428485, -83.742276 42.428525, -83.739961 42.428593, -83.734971 42.428756, -83.735505 42.429002, -83.735518 42.429134, -83.73528 42.429335, -83.735087 42.429444, -83.734857 42.42947, -83.734712 42.429442, -83.734541 42.429474, -83.734282 42.429588, -83.734358 42.429671, -83.734385 42.4297, -83.734528 42.429859, -83.734776 42.430234, -83.734921 42.430556, -83.734988 42.430802, -83.7352 42.431167, -83.735448 42.431492, -83.735692 42.431917, -83.736047 42.432361, -83.736712 42.432954, -83.737337 42.433405, -83.737712 42.433734, -83.737972 42.434042, -83.738119 42.434425, -83.738238 42.434824, -83.738421 42.435707, -83.738638 42.436241, -83.740093 42.436192, -83.740227 42.436964, -83.740215 42.437595, -83.740182 42.439401, -83.739743 42.439419, -83.732175 42.439768, -83.728223 42.43994, -83.727375 42.439978, -83.725503 42.440104, -83.725071 42.440154, -83.725009 42.440157, -83.724623 42.440198, -83.723737 42.440331, -83.722809 42.440621, -83.722737 42.440644, -83.721816 42.441073, -83.720507 42.441685, -83.717051 42.443376, -83.715797 42.443988, -83.711438 42.444115, -83.710281 42.444149, -83.709117 42.444184, -83.707981 42.4442, -83.707255 42.444189, -83.703916 42.44432, -83.703124 42.444313, -83.70274 42.444326, -83.702736 42.444253, -83.702707 42.44382, -83.702692 42.443494, -83.702669 42.443206, -83.702821 42.443199, -83.702988 42.443192, -83.703148 42.443185, -83.703243 42.443178, -83.703387 42.443174, -83.703559 42.443169, -83.703789 42.443162, -83.704004 42.443156, -83.704132 42.443148, -83.704213 42.443147, -83.704316 42.443146, -83.70443 42.443142, -83.704561 42.443137, -83.704693 42.443125, -83.704768 42.443107, -83.704808 42.443091, -83.704842 42.443078, -83.704898 42.443033, -83.704939 42.442971, -83.704966 42.442902, -83.704978 42.442843, -83.704975 42.442752, -83.704973 42.442619, -83.70497 42.442544, -83.704978 42.442453, -83.705005 42.442378, -83.705043 42.442296, -83.705146 42.442331, -83.705246 42.442353, -83.705394 42.442381, -83.705505 42.442397, -83.705627 42.442403, -83.705749 42.442401, -83.705878 42.4424, -83.706016 42.442396, -83.706194 42.442388, -83.706383 42.442379, -83.706509 42.442369, -83.706668 42.442357, -83.706833 42.442342, -83.706974 42.442332, -83.707122 42.442317, -83.707279 42.442303, -83.707346 42.442294, -83.707417 42.442283, -83.707512 42.442257, -83.707623 42.44222, -83.70768 42.442197, -83.707729 42.442171, -83.707778 42.442135, -83.707827 42.442097, -83.707934 42.442151, -83.708034 42.442212, -83.708111 42.442259, -83.708177 42.442303, -83.708247 42.442339, -83.708314 42.442355, -83.708406 42.442371, -83.708499 42.442372, -83.708563 42.442363, -83.708673 42.442319, -83.708758 42.44227, -83.708821 42.4422, -83.708868 42.442105, -83.708904 42.442016, -83.708916 42.441889, -83.70891 42.441759, -83.708874 42.441566, -83.708821 42.441243, -83.70879 42.441117, -83.708761 42.441054, -83.708728 42.440999, -83.70868 42.440961, -83.708603 42.440924, -83.7085 42.440897, -83.708385 42.440889, -83.708251 42.440888, -83.708088 42.440905, -83.707984 42.440932, -83.707928 42.440951, -83.707886 42.440766, -83.707833 42.440541, -83.707837 42.440481, -83.707842 42.440386, -83.707839 42.440265, -83.707825 42.440151, -83.707787 42.440044, -83.707722 42.439965, -83.707666 42.439901, -83.707711 42.439833, -83.707742 42.439768, -83.707772 42.439689, -83.707779 42.439642, -83.707773 42.439607, -83.70777 42.439488, -83.707763 42.439407, -83.707755 42.439332, -83.707739 42.439248, -83.707754 42.439099, -83.707745 42.438978, -83.707714 42.438904, -83.707665 42.438839, -83.707586 42.438776, -83.707392 42.438711, -83.707214 42.438653, -83.707056 42.438598, -83.706839 42.438518, -83.706679 42.438469, -83.706544 42.438431, -83.706476 42.438407, -83.706408 42.438401, -83.7063 42.438406, -83.706205 42.438433, -83.706149 42.438469, -83.706003 42.438576, -83.705905 42.438651, -83.705852 42.438702, -83.705798 42.438774, -83.705742 42.438873, -83.705678 42.438948, -83.705609 42.439005, -83.70554 42.439041, -83.705458 42.439072, -83.705371 42.439112, -83.705297 42.439159, -83.705212 42.439236, -83.705156 42.439289, -83.705121 42.439339, -83.705092 42.439386, -83.705078 42.439431, -83.705066 42.439528, -83.705067 42.439631, -83.705088 42.439727, -83.705105 42.439848, -83.705093 42.439969, -83.705074 42.440049, -83.704936 42.440048, -83.704761 42.440057, -83.704553 42.440077, -83.704403 42.440093, -83.704255 42.440103, -83.70411 42.440105, -83.703951 42.440112, -83.703684 42.440121, -83.703254 42.44014, -83.702795 42.440153, -83.702435 42.44017, -83.702024 42.440183, -83.701894 42.440188, -83.701797 42.440201, -83.701716 42.44023, -83.701656 42.440272, -83.7016 42.44032, -83.70157 42.440373, -83.701558 42.440424, -83.701564 42.440509, -83.701588 42.44106, -83.701609 42.441562, -83.701619 42.441688, -83.701489 42.441695, -83.701274 42.44171, -83.701088 42.441746, -83.700969 42.441813, -83.700857 42.441889, -83.700726 42.441993, -83.700618 42.442072, -83.700543 42.442147, -83.700475 42.442234, -83.700432 42.442307, -83.700407 42.44237, -83.700399 42.442477, -83.700404 42.442651, -83.700421 42.442785, -83.700461 42.442889, -83.700479 42.442916, -83.700516 42.44297, -83.70057 42.443037, -83.700625 42.44309, -83.700721 42.443163, -83.700685 42.443202, -83.700648 42.443257, -83.700621 42.443296, -83.700593 42.443352, -83.700568 42.443421, -83.700546 42.443496, -83.700529 42.443564, -83.70052 42.443664, -83.700522 42.443758, -83.700532 42.443827, -83.700531 42.443945, -83.70053 42.444089, -83.700528 42.444242, -83.700535 42.444292, -83.700549 42.444335, -83.700551 42.444404, -83.695455 42.444561, -83.686997 42.444871, -83.687006 42.446112, -83.686999 42.446288, -83.686913 42.446473, -83.686561 42.446869, -83.686347 42.447068, -83.686263 42.447196, -83.686241 42.447346, -83.686243 42.447494, -83.686289 42.447666, -83.686811 42.448465, -83.68671 42.4485, -83.686624 42.448509, -83.686558 42.448507, -83.68649 42.448498, -83.686407 42.448505, -83.686324 42.448522, -83.686197 42.448548, -83.686113 42.448557, -83.686032 42.448553, -83.68596 42.448547, -83.685886 42.44854, -83.685767 42.448544, -83.68568 42.448543, -83.685611 42.448558, -83.684942 42.448566, -83.684892 42.448234, -83.684933 42.448054, -83.685053 42.447773, -83.685014 42.447573, -83.684708 42.447009, -83.684321 42.446342, -83.684311 42.446028, -83.684346 42.445678, -83.684189 42.44499, -83.682944 42.445048, -83.68293 42.444822, -83.682905 42.444708, -83.682865 42.444614, -83.6828 42.444515, -83.682708 42.444411, -83.681456 42.44338, -83.681114 42.443098, -83.680982 42.443009, -83.680846 42.442944, -83.680361 42.44289, -83.67968 42.442908, -83.679603 42.441835, -83.679562 42.44168, -83.679499 42.441507, -83.679401 42.441359, -83.67929 42.441237, -83.679053 42.441078, -83.678447 42.44076, -83.677465 42.440196, -83.676118 42.439463, -83.675639 42.439198, -83.675357 42.439012, -83.675154 42.438871, -83.674914 42.43878, -83.674673 42.438724, -83.674385 42.438709, -83.674105 42.438755, -83.673835 42.438844, -83.673622 42.438968, -83.673478 42.439128, -83.673333 42.439313, -83.672351 42.439024, -83.671909 42.438624, -83.671849 42.438743, -83.671748 42.438821, -83.671547 42.438869, -83.671454 42.438931, -83.671869 42.439204, -83.671727 42.439366, -83.671589 42.43943, -83.671599 42.439539, -83.671701 42.43967, -83.671791 42.43991, -83.671734 42.440021, -83.671781 42.440141, -83.671892 42.440123, -83.671991 42.440203, -83.671943 42.440276, -83.671678 42.440396, -83.671582 42.440409, -83.671478 42.440389, -83.6714 42.440433, -83.67154 42.440549, -83.671524 42.440648, -83.671456 42.440719, -83.671588 42.440835, -83.671624 42.440939, -83.671577 42.441197, -83.671597 42.441356, -83.67164 42.441482, -83.671743 42.441615, -83.672152 42.441765, -83.672271 42.441782, -83.672359 42.441843, -83.672644 42.442168, -83.672895 42.442312, -83.672982 42.442428, -83.67301 42.442598, -83.673097 42.442709, -83.673432 42.44299, -83.673535 42.443029, -83.673744 42.442871, -83.673855 42.44285, -83.673916 42.442675, -83.674109 42.442676, -83.674279 42.442781, -83.674452 42.442804, -83.674532 42.442969, -83.674538 42.443139, -83.674596 42.443211, -83.67473 42.443228, -83.674988 42.443339, -83.674835 42.443446, -83.674963 42.443558, -83.674954 42.443635, -83.674917 42.443684, -83.674717 42.443694, -83.674635 42.443843, -83.674404 42.443777, -83.674244 42.443748, -83.674071 42.443752, -83.673886 42.443766, -83.673678 42.443813, -83.671254 42.444611, -83.669359 42.445412, -83.66908 42.445499, -83.668744 42.445578, -83.668454 42.445614, -83.667281 42.445679, -83.666094 42.445707, -83.665878 42.445684, -83.665724 42.445659, -83.665716 42.445594, -83.665698 42.44545, -83.665698 42.445322, -83.665698 42.445184, -83.665681 42.444951, -83.665666 42.444733, -83.665647 42.444458, -83.665638 42.4442, -83.665606 42.443743, -83.665568 42.443218, -83.665561 42.443129, -83.665511 42.443133, -83.663119 42.443201, -83.662539 42.44334, -83.662504 42.443437, -83.662229 42.443557, -83.660322 42.44358, -83.660012 42.442263, -83.659736 42.441032, -83.659652 42.439851, -83.659595 42.4387, -83.65959 42.438597, -83.659052 42.431454, -83.659047 42.431385, -83.659045 42.431359, -83.658725 42.431369, -83.658553 42.431377, -83.6579 42.431406, -83.657678 42.431416, -83.657218 42.431437, -83.656671 42.431448, -83.656547 42.43145, -83.655039 42.431509, -83.655018 42.43151, -83.653948 42.431544, -83.653285 42.431571, -83.652949 42.431589, -83.652215 42.431617, -83.651294 42.431642, -83.650503 42.431675, -83.650239 42.431683, -83.649952 42.431692, -83.649739 42.431699, -83.649445 42.431716, -83.649247 42.431722, -83.649022 42.431721, -83.648719 42.431727, -83.648686 42.431729, -83.64836 42.431752, -83.647877 42.431768, -83.647733 42.431771, -83.646924 42.431789, -83.646809 42.431791, -83.646303 42.431819, -83.646176 42.431826, -83.646067 42.431831, -83.645495 42.431855, -83.645068 42.431873, -83.644509 42.431891, -83.64382 42.431913, -83.643626 42.43192, -83.643096 42.43194, -83.642716 42.431954, -83.642457 42.431958, -83.641863 42.431986, -83.640981 42.432015, -83.640562 42.432029, -83.640206 42.432041, -83.639809 42.432065, -83.639521 42.432074, -83.637675 42.43214, -83.636401 42.432186, -83.635993 42.432201, -83.63591 42.432201, -83.635696 42.432202, -83.635108 42.43223, -83.634684 42.43225, -83.633938 42.432267, -83.633826 42.432279, -83.63302 42.43231, -83.632523 42.432334, -83.631436 42.432377, -83.630212 42.43241, -83.629816 42.432436, -83.62974 42.432438, -83.629491 42.432443, -83.627792 42.4325, -83.627641 42.432505, -83.627329 42.432515, -83.626197 42.432549, -83.625995 42.432555, -83.625962 42.432556, -83.625754 42.432565, -83.625391 42.432581, -83.625062 42.432595, -83.624986 42.432598, -83.62412 42.432628, -83.623699 42.432643, -83.62348 42.432653, -83.623203 42.432662, -83.622073 42.4327, -83.621432 42.432718, -83.619853 42.43279, -83.619252 42.432812, -83.618781 42.43282, -83.617607 42.432859, -83.616458 42.432905, -83.616188 42.43291, -83.615708 42.432933, -83.615091 42.432956, -83.614799 42.432962, -83.614623 42.432969, -83.618348 42.435654, -83.62434 42.439973, -83.627702 42.442246, -83.629677 42.44386, -83.630642 42.444734, -83.630717 42.445635, -83.630793 42.44685, -83.630965 42.449099, -83.630969 42.449517, -83.631017 42.449818, -83.631131 42.451412, -83.631256 42.453137, -83.631318 42.454166, -83.631429 42.455968, -83.631492 42.456929, -83.631538 42.457625, -83.631605 42.458286, -83.631641 42.458787, -83.631682 42.459571, -83.631737 42.460293, -83.631788 42.461293, -83.631544 42.461306, -83.630516 42.461335, -83.628114 42.461415, -83.627923 42.461421, -83.627925 42.461799, -83.627997 42.462095, -83.62808 42.462291, -83.627987 42.462374, -83.62788 42.462454, -83.627725 42.462509, -83.627439 42.462569, -83.627181 42.462567, -83.626933 42.462617, -83.626837 42.462636, -83.626692 42.462693, -83.626551 42.4628, -83.626423 42.46293, -83.626336 42.463189, -83.626363 42.463338, -83.626394 42.463459, -83.626532 42.463584, -83.626713 42.463734, -83.627015 42.463843, -83.627577 42.46404, -83.627805 42.464132, -83.627944 42.464212, -83.628059 42.464348, -83.628151 42.464504, -83.628177 42.464615, -83.628185 42.464784, -83.62821 42.465061, -83.628189 42.465188, -83.628127 42.465344, -83.628094 42.465392, -83.627966 42.465501, -83.627806 42.465601, -83.627647 42.465652, -83.627511 42.465678, -83.627301 42.465701, -83.627128 42.465724, -83.626893 42.465826, -83.62678 42.465898, -83.626629 42.466025, -83.626572 42.466173, -83.626533 42.466329, -83.626559 42.466498, -83.626655 42.466665, -83.626876 42.466849, -83.627065 42.466736, -83.627266 42.466665, -83.627514 42.466625, -83.627678 42.466616, -83.628425 42.46659, -83.628593 42.466581, -83.628738 42.466557, -83.62907 42.46649, -83.629869 42.466464, -83.630275 42.466446, -83.630485 42.466451, -83.63069 42.466497, -83.630853 42.466557, -83.63101 42.466679, -83.631088 42.466766, -83.631139 42.466846, -83.631532 42.467941, -83.631517 42.468027, -83.631357 42.468102, -83.631156 42.468135, -83.630983 42.468148, -83.630703 42.468167, -83.630437 42.468169, -83.627957 42.468256, -83.627981 42.468774, -83.627984 42.468819, -83.62799 42.46893, -83.627995 42.468995, -83.628 42.469063, -83.628011 42.469209, -83.628017 42.469284, -83.628023 42.469362, -83.628066 42.469602, -83.62816 42.469745, -83.628226 42.469807, -83.628259 42.469882, -83.628262 42.469918, -83.628267 42.470004, -83.628232 42.470053, -83.628135 42.470167, -83.628092 42.470237, -83.628061 42.470314, -83.628041 42.47048, -83.628048 42.470564, -83.628063 42.47065, -83.628096 42.470827, -83.628116 42.470918, -83.628136 42.47101, -83.628176 42.47119, -83.628197 42.471282, -83.628214 42.471372, -83.628226 42.471644, -83.628168 42.471914, -83.628149 42.472002, -83.628115 42.472181, -83.628107 42.472269, -83.628111 42.472359, -83.628172 42.472522, -83.628224 42.472594, -83.628282 42.472658, -83.628382 42.472741, -83.628424 42.472782, -83.628483 42.472839, -83.628462 42.472863, -83.628426 42.472909, -83.628389 42.472955, -83.628356 42.473005, -83.628313 42.473121, -83.628302 42.473183, -83.628299 42.473248, -83.628302 42.47339, -83.628306 42.473463, -83.62831 42.473539, -83.628322 42.473781, -83.628328 42.473862, -83.628361 42.474099, -83.628377 42.474178, -83.628442 42.474409, -83.628469 42.474481, -83.628495 42.474552, -83.628529 42.474758, -83.628528 42.474827, -83.628521 42.474896, -83.628512 42.474966, -83.628491 42.475101, -83.628478 42.475165, -83.628451 42.475292, -83.628429 42.475418, -83.628418 42.475481, -83.628409 42.475546, -83.628401 42.475675, -83.628404 42.475732, -83.628419 42.475783, -83.628452 42.475822, -83.628557 42.475855, -83.628677 42.475854, -83.628756 42.475884, -83.627686 42.475926, -83.626529 42.475987, -83.626096 42.476004, -83.625641 42.476031, -83.625211 42.476051, -83.624301 42.476092, -83.62325 42.476147, -83.622118 42.476192, -83.622008 42.476197, -83.621279 42.476216, -83.620315 42.476253, -83.620056 42.476251, -83.61982 42.476261, -83.61901 42.476278, -83.618099 42.476312, -83.617617 42.476321, -83.615644 42.476385, -83.615272 42.476392, -83.614476 42.476408, -83.613233 42.47645, -83.613285 42.477196, -83.613313 42.477602, -83.613374 42.47821, -83.613408 42.478616, -83.613441 42.479209, -83.613451 42.479367, -83.613452 42.479503, -83.613465 42.47966, -83.613462 42.479769, -83.613446 42.479942, -83.613359 42.480257, -83.613284 42.48044, -83.613058 42.480803, -83.612777 42.481234, -83.612678 42.481413, -83.612556 42.481776, -83.612528 42.481876, -83.612524 42.482061, -83.612532 42.482306, -83.612786 42.483727, -83.612126 42.483774, -83.610854 42.483829, -83.609559 42.483866, -83.609187 42.483868, -83.609001 42.483881, -83.608946 42.483917, -83.608915 42.483959, -83.608898 42.484008, -83.608884 42.484142, -83.608979 42.485418, -83.609072 42.486806, -83.609147 42.488013, -83.609207 42.489102, -83.609338 42.490513, -83.609389 42.49113, -83.609811 42.491113, -83.610316 42.491099, -83.613624 42.490976, -83.614288 42.490935, -83.614371 42.492112, -83.614388 42.492338, -83.614528 42.49418, -83.615084 42.494157, -83.615433 42.494167, -83.615833 42.494208, -83.616401 42.494293, -83.616693 42.494335, -83.617074 42.494372, -83.617043 42.494795, -83.616954 42.49499, -83.616281 42.495754, -83.616179 42.49586, -83.615816 42.496085, -83.615685 42.496113, -83.615099 42.496124, -83.614685 42.496138, -83.614696 42.496264, -83.614761 42.497024, -83.614822 42.497743, -83.614862 42.49822, -83.614977 42.500127, -83.615151 42.50281, -83.615259 42.504371, -83.615308 42.505138, -83.62051 42.499382, -83.622731 42.497053, -83.623357 42.49754, -83.624255 42.497396, -83.624439 42.498047, -83.624627 42.498726, -83.625121 42.498657, -83.625484 42.498604, -83.625513 42.4986, -83.625837 42.498539, -83.626143 42.498471, -83.626249 42.498452, -83.627454 42.498116, -83.627743 42.498036, -83.627946 42.497979, -83.628188 42.497902, -83.62977 42.497398, -83.631642 42.496786, -83.631994 42.496671, -83.632229 42.496581, -83.632301 42.496564, -83.63239 42.496562, -83.632468 42.496572, -83.632531 42.496591, -83.632574 42.496624, -83.632599 42.496682, -83.632615 42.49672, -83.632645 42.497126, -83.632661 42.497217, -83.632685 42.497282, -83.63272 42.497345, -83.632795 42.497403, -83.632958 42.497434, -83.635452 42.497344, -83.636878 42.497284, -83.638966 42.49722, -83.638794 42.494635, -83.638361 42.494671, -83.637496 42.494603, -83.636044 42.494306, -83.634561 42.493803, -83.634067 42.493346, -83.633325 42.492477, -83.633109 42.49234, -83.633109 42.492226, -83.632738 42.4917, -83.632738 42.491563, -83.632429 42.4909, -83.632336 42.490351, -83.63215 42.490009, -83.632058 42.489734, -83.631841 42.489483, -83.631656 42.489231, -83.631375 42.488893, -83.630914 42.48834, -83.630945 42.48818, -83.63218 42.486899, -83.632198 42.486723, -83.633145 42.486679, -83.63358 42.486663, -83.63616 42.486572, -83.637277 42.486541, -83.638253 42.486482, -83.638211 42.485726, -83.638205 42.485602, -83.638193 42.485302, -83.638188 42.485178, -83.638153 42.484693, -83.638142 42.484511, -83.638086 42.483548, -83.637974 42.482102, -83.637955 42.481797, -83.639089 42.480938, -83.639164 42.480862, -83.642759 42.480789, -83.642762 42.480802, -83.642772 42.482636, -83.650188 42.48241, -83.650222 42.482869, -83.650324 42.482926, -83.650353 42.48349, -83.650285 42.483746, -83.650315 42.48421, -83.65039 42.484209, -83.650655 42.484207, -83.651528 42.484182, -83.652824 42.484122, -83.652746 42.482907, -83.652709 42.482323, -83.652614 42.480901, -83.652596 42.480632, -83.652594 42.480589, -83.652544 42.479633, -83.654814 42.479521, -83.657337 42.479397, -83.657342 42.479425, -83.657434 42.479906, -83.657575 42.482115, -83.657623 42.482113, -83.66276 42.48183, -83.663108 42.481926, -83.663706 42.481904, -83.663699 42.481778, -83.663803 42.481772, -83.663452 42.474642, -83.663283 42.474618, -83.665888 42.474515, -83.667024 42.474475, -83.667176 42.474493, -83.66735 42.474529, -83.667443 42.474566, -83.667531 42.47464, -83.66761 42.474726, -83.667641 42.474763, -83.667686 42.474852, -83.667685 42.474668, -83.667685 42.474541, -83.667681 42.47447, -83.667677 42.474408, -83.667672 42.474357, -83.667312 42.468717, -83.666789 42.468566, -83.666507 42.468058, -83.666699 42.467521, -83.66696 42.467387, -83.667227 42.467388, -83.667222 42.467313, -83.667209 42.467171, -83.666644 42.460733, -83.666694 42.460615, -83.667695 42.460471, -83.667747 42.460464, -83.668848 42.460314, -83.670027 42.460199, -83.670501 42.460149, -83.67061 42.460855, -83.670644 42.461203, -83.670647 42.461376, -83.670612 42.461534, -83.670526 42.46175, -83.67098 42.461837, -83.671204 42.461842, -83.671399 42.461824, -83.671576 42.46177, -83.671765 42.461681, -83.671906 42.461561, -83.671989 42.461426, -83.672013 42.461289, -83.672018 42.46116, -83.671967 42.4607, -83.672426 42.460678, -83.672999 42.46069, -83.673272 42.460688, -83.673536 42.460665, -83.673859 42.460614, -83.674132 42.460591, -83.674603 42.460562, -83.674669 42.461077, -83.674731 42.461237, -83.674805 42.461347, -83.674918 42.461443, -83.675041 42.461525, -83.675176 42.461571, -83.67532 42.46161, -83.675476 42.46162, -83.675982 42.461607, -83.676047 42.462675, -83.675357 42.462692, -83.675085 42.462679, -83.674843 42.462652, -83.674389 42.462571, -83.674107 42.462551, -83.673796 42.462557, -83.673769 42.462998, -83.673721 42.463249, -83.673654 42.463471, -83.673577 42.4637, -83.673571 42.463866, -83.673604 42.464018, -83.673716 42.46415, -83.67394 42.464336, -83.674661 42.464904, -83.674879 42.46506, -83.675079 42.465172, -83.675299 42.465264, -83.675649 42.465377, -83.675721 42.465193, -83.675812 42.465087, -83.675951 42.46501, -83.676129 42.464942, -83.676427 42.464864, -83.67665 42.464823, -83.67701 42.464809, -83.677262 42.464843, -83.677416 42.464896, -83.677549 42.464986, -83.677877 42.465262, -83.677728 42.4654, -83.677654 42.46555, -83.677616 42.465794, -83.67766 42.466193, -83.67771 42.466823, -83.677786 42.467632, -83.677802 42.46809, -83.677864 42.46825, -83.677937 42.468367, -83.67805 42.468477, -83.678232 42.468567, -83.678396 42.468607, -83.678667 42.468634, -83.679635 42.468665, -83.67961 42.469439, -83.679652 42.469627, -83.679716 42.469751, -83.67979 42.469854, -83.679903 42.469935, -83.680008 42.470002, -83.680143 42.470041, -83.680404 42.470075, -83.681381 42.470046, -83.681251 42.466986, -83.681236 42.466863, -83.681173 42.466711, -83.681051 42.466593, -83.680852 42.466452, -83.680527 42.466294, -83.680335 42.466225, -83.680114 42.466156, -83.679833 42.466121, -83.679342 42.466115, -83.679237 42.46505, -83.679834 42.465018, -83.680196 42.464983, -83.680373 42.464936, -83.68058 42.464846, -83.68073 42.464749, -83.680851 42.464629, -83.680955 42.464472, -83.681032 42.464236, -83.681042 42.462723, -83.68102 42.462463, -83.680925 42.462151, -83.680567 42.461587, -83.680451 42.461297, -83.680384 42.460717, -83.680343 42.459647, -83.68059 42.45964, -83.685743 42.459483, -83.686476 42.459445, -83.686641 42.461965, -83.686757 42.463895, -83.686866 42.465806, -83.686888 42.466238, -83.686969 42.467427, -83.687039 42.468455, -83.687104 42.469407, -83.687113 42.469521, -83.687165 42.470343, -83.687211 42.471211, -83.687211 42.471398, -83.687269 42.47281, -83.687367 42.473966, -83.687375 42.474106, -83.687797 42.481176, -83.684605 42.481314, -83.677168 42.481715, -83.678309 42.482475, -83.680614 42.484232, -83.681908 42.485236, -83.681633 42.485245, -83.680756 42.486351, -83.679749 42.487617, -83.679581 42.487757, -83.678742 42.488459, -83.678711 42.488483, -83.678406 42.488487, -83.678474 42.489062, -83.678429 42.489773, -83.678604 42.493832, -83.678724 42.495693, -83.678725 42.4958, -83.678914 42.499379, -83.680201 42.498878, -83.682434 42.498136, -83.687964 42.496174, -83.688549 42.495966, -83.688669 42.496111, -83.688755 42.496291, -83.688813 42.496585, -83.688817 42.496615, -83.688951 42.498014, -83.689003 42.49874, -83.689033 42.499153, -83.689179 42.501247, -83.689293 42.502916, -83.689315 42.503707, -83.690408 42.503677, -83.690914 42.50362, -83.691299 42.503548, -83.691764 42.503386, -83.692145 42.503181, -83.692622 42.502842, -83.693573 42.501693, -83.693708 42.501586, -83.69399 42.501312, -83.69429 42.501185, -83.694473 42.50108, -83.695722 42.500649, -83.697875 42.499906, -83.698417 42.499751, -83.699003 42.499521, -83.699758 42.499215, -83.699945 42.499086, -83.700073 42.498797, -83.700211 42.497829, -83.699384 42.497552, -83.698486 42.497185, -83.697852 42.496933, -83.697983 42.496878, -83.698579 42.496807, -83.698971 42.496878, -83.699533 42.497119, -83.700532 42.497351, -83.701506 42.497428, -83.702521 42.4975, -83.703688 42.497316, -83.703922 42.497279, -83.704304 42.497222, -83.704475 42.497186, -83.70429 42.497134, -83.704109 42.497084, -83.704013 42.497148, -83.7038 42.497218, -83.703523 42.497107, -83.703264 42.497054, -83.702953 42.49702, -83.702397 42.496978, -83.702218 42.497027, -83.701959 42.497008, -83.701741 42.496955, -83.701412 42.496852, -83.70099 42.496726, -83.700739 42.496634, -83.700497 42.496375, -83.700359 42.496135, -83.700203 42.495829, -83.700192 42.495798, -83.700116 42.495578, -83.700108 42.49533, -83.70016 42.495002, -83.700374 42.494693, -83.700181 42.494662, -83.699912 42.494632, -83.699768 42.494582, -83.699594 42.494487, -83.699354 42.494376, -83.698961 42.494403, -83.698728 42.494369, -83.698697 42.492743, -83.698199 42.492305, -83.698331 42.492134, -83.698381 42.492011, -83.69841 42.491887, -83.698407 42.491476, -83.698233 42.489477, -83.69822 42.489287, -83.698208 42.489096, -83.698189 42.488749, -83.698156 42.488125, -83.694878 42.488258, -83.692874 42.484847, -83.69289 42.484852, -83.69315 42.484804, -83.693357 42.484849, -83.693423 42.484964, -83.693527 42.484951, -83.693684 42.484838, -83.693696 42.484706, -83.693822 42.48472, -83.694057 42.484606, -83.693982 42.484376, -83.694109 42.484247, -83.694265 42.484217, -83.694577 42.484211, -83.69447 42.484158, -83.694497 42.48407, -83.6946 42.484068, -83.694811 42.484125, -83.69505 42.484038, -83.69519 42.484101, -83.695313 42.484016, -83.695329 42.483895, -83.695267 42.483781, -83.695357 42.483713, -83.6958 42.483518, -83.696185 42.483247, -83.696266 42.483289, -83.696379 42.483425, -83.69645 42.483413, -83.69657 42.483278, -83.696646 42.483134, -83.696873 42.482992, -83.696871 42.482915, -83.69692 42.482794, -83.697013 42.482748, -83.697184 42.482739, -83.697341 42.482599, -83.697353 42.482533, -83.697239 42.482441, -83.697303 42.482385, -83.697432 42.482388, -83.697774 42.481961, -83.697663 42.480853, -83.697551 42.478554, -83.697462 42.477514, -83.697368 42.477188, -83.696751 42.475758, -83.697595 42.475998, -83.697993 42.476142, -83.698205 42.476183, -83.699293 42.476195, -83.699459 42.475992, -83.699411 42.475888, -83.699188 42.475617, -83.699186 42.475512, -83.699338 42.475455, -83.699441 42.475469, -83.699529 42.475281, -83.699404 42.475162, -83.699226 42.475193, -83.6991 42.475173, -83.699157 42.475051, -83.69951 42.474929, -83.699702 42.474749, -83.699812 42.474808, -83.699903 42.47496, -83.699992 42.47497, -83.700067 42.474913, -83.69995 42.474695, -83.699852 42.474609, -83.699789 42.474522, -83.699569 42.474636, -83.699507 42.474577, -83.69962 42.474366, -83.699462 42.474314, -83.699309 42.474355, -83.699146 42.47433, -83.699108 42.474111, -83.699281 42.473872, -83.698984 42.47359, -83.698851 42.473224, -83.698443 42.473232, -83.698355 42.473112, -83.69812 42.472935, -83.698002 42.472805, -83.698024 42.472519, -83.69828 42.472168, -83.698502 42.471927, -83.698738 42.471708, -83.69904 42.47151, -83.699298 42.471313, -83.699362 42.471225, -83.6995 42.471145, -83.70031 42.471015, -83.700335 42.471048, -83.700286 42.471164, -83.700341 42.471229, -83.700641 42.471163, -83.700651 42.471003, -83.700797 42.470918, -83.700911 42.470933, -83.700958 42.471086, -83.701069 42.471078, -83.701248 42.471015, -83.70163 42.470931, -83.701808 42.47096, -83.702001 42.471248, -83.702179 42.471255, -83.702439 42.471558, -83.702588 42.471528, -83.702992 42.471148, -83.703133 42.471129, -83.70317 42.471095, -83.703175 42.470991, -83.703345 42.471037, -83.703481 42.471123, -83.704016 42.471337, -83.704063 42.471436, -83.704159 42.471461, -83.704378 42.471402, -83.704665 42.471282, -83.704704 42.471255, -83.704758 42.471215, -83.704803 42.471153, -83.704746 42.471006, -83.705222 42.470865, -83.705526 42.470849, -83.705948 42.470907, -83.70611 42.470965, -83.706161 42.471041, -83.70606 42.471191, -83.706407 42.471284, -83.706521 42.471348, -83.706678 42.471378, -83.706741 42.471256, -83.706931 42.471192, -83.707245 42.471274, -83.707315 42.471361, -83.707423 42.471409, -83.707568 42.471257, -83.707693 42.471327, -83.707747 42.471381, -83.707709 42.471398, -83.707748 42.471417, -83.707816 42.471357, -83.707758 42.471192, -83.707771 42.46973, -83.707332 42.467524, -83.707028 42.466059, -83.707464 42.466013, -83.71014 42.465924, -83.711319 42.465884, -83.711686 42.465872, -83.711926 42.466408, -83.711883 42.466637, -83.712062 42.466521, -83.712279 42.466246, -83.712246 42.465852, -83.715741 42.465741, -83.716914 42.465734, -83.716866 42.467045, -83.716918 42.467539, -83.717017 42.467951, -83.717419 42.468635, -83.717847 42.468933, -83.718393 42.469248, -83.719269 42.46971, -83.719675 42.469865, -83.720418 42.47013, -83.721653 42.470632, -83.722018 42.470746, -83.722335 42.470799, -83.722429 42.470808, -83.722419 42.470858, -83.722409 42.470904, -83.722411 42.47095, -83.722419 42.471019, -83.72243 42.471067, -83.722438 42.471114, -83.722456 42.471176, -83.722474 42.471221, -83.72249 42.47128, -83.722504 42.471328, -83.722522 42.471371, -83.722541 42.471416, -83.722557 42.471462, -83.722579 42.471501, -83.722987 42.472303, -83.723023 42.47236, -83.723106 42.472375, -83.723767 42.472333, -83.724812 42.472222, -83.725008 42.472239, -83.724111 42.473202, -83.723564 42.473642, -83.722854 42.474159, -83.722015 42.474657, -83.719639 42.475801, -83.718368 42.476378, -83.717533 42.476951, -83.716784 42.477687, -83.71636 42.478287, -83.715267 42.479941, -83.715131 42.480285, -83.715118 42.480919, -83.714977 42.481399, -83.714871 42.481573, -83.714772 42.481826, -83.71478 42.482068, -83.714802 42.482236, -83.714883 42.482471, -83.715017 42.48267, -83.715176 42.482949, -83.715251 42.483184, -83.715295 42.483495, -83.715312 42.48388, -83.715812 42.48387, -83.717615 42.483844, -83.718984 42.483806, -83.721787 42.48373, -83.725026 42.483529, -83.725096 42.483527, -83.725831 42.483512, -83.730729 42.483406, -83.730853 42.483404, -83.730964 42.483397, -83.732253 42.483327, -83.732732 42.483301, -83.733836 42.483256, -83.735358 42.483184, -83.736551 42.483119, -83.736972 42.483106, -83.737952 42.483073, -83.739265 42.483027, -83.74089 42.482969, -83.740995 42.482938, -83.742 42.480821, -83.742119 42.480485, -83.742122 42.480165, -83.741992 42.474566, -83.741982 42.473757, -83.741937 42.472382, -83.741915 42.471893, -83.741799 42.469001, -83.741524 42.468984, -83.738552 42.468712, -83.738284 42.46867, -83.737393 42.468574, -83.736381 42.468465, -83.73524 42.46837, -83.734279 42.46832, -83.733363 42.468292, -83.73238 42.468309, -83.73131 42.468328, -83.730853 42.468349, -83.730339 42.468374, -83.730296 42.468376, -83.728667 42.468398, -83.728441 42.468423, -83.728188 42.468486, -83.727924 42.468611, -83.727731 42.468425, -83.727694 42.468276, -83.727693 42.468121, -83.727866 42.466943, -83.727889 42.466762, -83.727949 42.466297, -83.728067 42.46571, -83.728166 42.465355, -83.72836 42.464655, -83.728457 42.464307, -83.728554 42.46343, -83.728539 42.462957, -83.728456 42.461923, -83.728326 42.460298, -83.729096 42.460275, -83.730518 42.460267, -83.730492 42.459921, -83.73045 42.459765, -83.73034 42.459577, -83.730037 42.459228, -83.730269 42.459018, -83.730372 42.458868, -83.730416 42.458687, -83.730409 42.457994, -83.72911 42.45802, -83.728198 42.458037, -83.728048 42.456781, -83.727263 42.454191, -83.726826 42.452841, -83.726221 42.451749, -83.725809 42.451047, -83.725725 42.450807, -83.725656 42.45019, -83.725658 42.450155, -83.725657 42.450072, -83.72554 42.448305, -83.725448 42.447241, -83.727742 42.447169, -83.72999 42.447099, -83.730281 42.447098, -83.730356 42.447116, -83.730471 42.447182, -83.730537 42.447277, -83.73226 42.44933, -83.732451 42.449482, -83.732978 42.449297, -83.733163 42.449007, -83.73328 42.448736, -83.733873 42.44769, -83.734027 42.447361, -83.734191 42.447013, -83.734434 42.446767, -83.734876 42.446637, -83.735151 42.446616, -83.735813 42.446708, -83.735982 42.446887, -83.736371 42.447595, -83.736828 42.447808, -83.737469 42.447501, -83.737792 42.447196, -83.738052 42.44695, -83.738317 42.446814, -83.738051 42.446687, -83.737377 42.446305, -83.737356 42.446162, -83.737998 42.445672, -83.738109 42.445727, -83.737548 42.446256, -83.738168 42.446526, -83.738525 42.446706, -83.738681 42.44675, -83.738741 42.446652, -83.738728 42.446433, -83.738927 42.446516, -83.739127 42.446544, -83.739406 42.44642, -83.739933 42.446346, -83.740248 42.446446, -83.740418 42.44643, -83.740507 42.446325, -83.740517 42.446121, -83.740501 42.445889, -83.745327 42.446038, -83.745445 42.449725, -83.745719 42.449899, -83.745836 42.449927, -83.745955 42.449913, -83.746057 42.449879, -83.746604 42.449744, -83.746808 42.449728, -83.747044 42.449742, -83.747219 42.449775, -83.747373 42.449868, -83.747493 42.449978, -83.74758 42.450166, -83.747644 42.450239, -83.747781 42.45032, -83.7479 42.450384, -83.748671 42.450523, -83.749496 42.450597, -83.749878 42.450593, -83.750093 42.45061, -83.750274 42.450705, -83.75035 42.450752, -83.750401 42.450811, -83.750475 42.450927, -83.750592 42.45126, -83.750654 42.451355, -83.750734 42.451422, -83.750822 42.451487, -83.751022 42.451553, -83.751775 42.451658, -83.751881 42.450104, -83.751983 42.448584, -83.752218 42.445105, -83.751491 42.443749, -83.751434 42.443515, -83.75139 42.442823, -83.751823 42.442809, -83.753051 42.442771, -83.754081 42.442739, -83.754256 42.442741, -83.75471 42.442717, -83.756064 42.442673, -83.756603 42.442648, -83.763803 42.442332, -83.766731 42.442238, -83.767113 42.442229, -83.774339 42.441946, -83.776208 42.441874, -83.777361 42.441836, -83.777483 42.441833, -83.777485 42.441268, -83.775733 42.44093, -83.774764 42.440744, -83.774686 42.440746, -83.776027 42.439636, -83.776435 42.439526, -83.776599 42.439462, -83.77673 42.439338, -83.776835 42.439207, -83.776902 42.439075, -83.776903 42.438962, -83.776814 42.438734, -83.776681 42.438212, -83.776702 42.437943, -83.776836 42.437104, -83.776822 42.436995, -83.776764 42.436881, -83.776445 42.436507, -83.775987 42.4361, -83.775795 42.436042, -83.775391 42.436019, -83.775173 42.436031, -83.775058 42.436031, -83.774921 42.436042, -83.774728 42.43608, -83.774545 42.436153, -83.7744 42.436252, -83.774263 42.436313, -83.774043 42.436374, -83.773821 42.436382, -83.773577 42.436363, -83.773318 42.436305, -83.773097 42.436206, -83.772857 42.436031, -83.772585 42.435848, -83.772416 42.435722, -83.772172 42.435592, -83.771929 42.435547, -83.771654 42.435546, -83.770516 42.435684, -83.770318 42.435783, -83.77022 42.435951, -83.770137 42.436115, -83.770079 42.43636, -83.770066 42.436611, -83.769975 42.436817, -83.769874 42.436904, -83.769624 42.437053, -83.769341 42.437206, -83.768083 42.43785, -83.766862 42.438526, -83.766626 42.438728, -83.765708 42.439181, -83.765688 42.439381, -83.765648 42.439701, -83.765928 42.440001, -83.766191 42.440371, -83.766646 42.440397, -83.766693 42.440516, -83.766753 42.440868, -83.766775 42.441029, -83.764353 42.441154, -83.761466 42.441199, -83.760132 42.441234, -83.758599 42.441219, -83.757335 42.441192, -83.757126 42.441168, -83.757179 42.441013, -83.757256 42.44076, -83.757358 42.440452, -83.757403 42.440257, -83.75748 42.439797, -83.757518 42.439487, -83.757623 42.438776, -83.757676 42.43835, -83.757717 42.438177, -83.757803 42.437969, -83.7579 42.437798, -83.758104 42.437488, -83.758321 42.437238, -83.758564 42.437, -83.758821 42.43677, -83.759214 42.436437, -83.764604 42.432009, -83.764877 42.431773, -83.765097 42.431597, -83.76546 42.431343, -83.765776 42.431121, -83.766007 42.430991, -83.766204 42.430903, -83.766662 42.430734, -83.767289 42.430549, -83.768221 42.43032, -83.768862 42.430126, -83.769083 42.430036, -83.769236 42.42994, -83.769418 42.429766, -83.769547 42.429579, -83.769612 42.429418, -83.769632 42.429308, -83.769657 42.429125, -83.771409 42.429061, -83.771807 42.429046, -83.774672 42.428966, -83.775123 42.428951, -83.777645 42.428874, -83.778082 42.42886, -83.778519 42.428848, -83.778955 42.428837, -83.783516 42.428702, -83.787247 42.428591, -83.786895 42.422198, -83.78681 42.421439, -83.786777 42.42047, -83.786744 42.419807, -83.786723 42.419339, -83.786704 42.419099, -83.786683 42.419037, -83.786656 42.418999, -83.786607 42.418963, -83.786478 42.418898, -83.78638 42.41887, -83.786221 42.418868, -83.785893 42.418887, -83.78513 42.418943, -83.783598 42.419043, -83.781411 42.419115, -83.781225 42.419109, -83.781102 42.419085, -83.781026 42.419053, -83.780964 42.418992, -83.780938 42.41893, -83.780916 42.417146, -83.780832 42.416155, -83.780769 42.415565, -83.780731 42.415489, -83.780715 42.415416, -83.780631 42.41534, -83.780518 42.415317, -83.780266 42.415313, -83.780198 42.415294, -83.780069 42.41521, -83.780007 42.415134, -83.779992 42.415035, -83.779843 42.412868, -83.772636 42.413263, -83.77307 42.40813, -83.77299 42.408247, -83.772892 42.408319, -83.768168 42.411428, -83.767772 42.411701, -83.767194 42.411971, -83.7669 42.41214, -83.766539 42.41237, -83.766322 42.412588, -83.765657 42.413278, -83.765482 42.413465, -83.765398 42.413576, -83.765383 42.413694, -83.765429 42.413892, -83.767071 42.417901, -83.767681 42.419373, -83.768488 42.421276, -83.768762 42.421991, -83.768352 42.421448, -83.768187 42.42122, -83.767782 42.420614, -83.764881 42.413581, -83.763896 42.411241, -83.763488 42.410089, -83.763287 42.409452, -83.763117 42.40876, -83.762999 42.408153, -83.762912 42.407596, -83.762833 42.407009, -83.762753 42.406098, -83.762745 42.405005, -83.762805 42.40396, -83.763003 42.40212, -83.763073 42.401561, -83.763431 42.401879, -83.763635 42.401094, -83.763625 42.401057, -83.763623 42.400989, -83.763678 42.400866, -83.763757 42.400754, -83.764288 42.400108, -83.764902 42.399362, -83.765536 42.398591, -83.765741 42.398343, -83.7661 42.397921, -83.766668 42.397249, -83.767556 42.396212, -83.766764 42.395825, -83.765955 42.395441, -83.76582 42.395378, -83.765454 42.395207, -83.764938 42.395018, -83.763754 42.394572, -83.763471 42.394466, -83.762492 42.394151, -83.761931 42.394, -83.761507 42.393914, -83.761143 42.393874, -83.760857 42.393849, -83.760479 42.393833, -83.759849 42.393818, -83.759028 42.393829, -83.75834 42.393857, -83.756815 42.393957, -83.756566 42.393984, -83.756292 42.394014, -83.755869 42.394066, -83.755676 42.39408, -83.755389 42.3941, -83.755099 42.394112, -83.754519 42.394087, -83.753821 42.394034, -83.753611 42.394021, -83.75338 42.394006, -83.75317 42.394014, -83.753039 42.394047, -83.75296 42.394103, -83.752931 42.394189, -83.752925 42.394271, -83.752931 42.394426, -83.752956 42.394905, -83.749159 42.394998, -83.748778 42.394654, -83.748909 42.395004, -83.749771 42.397778, -83.747335 42.397837, -83.747133 42.397861, -83.746978 42.397895, -83.746837 42.397958, -83.74673 42.398045, -83.746023 42.398742)), ((-83.753453 42.581762, -83.758905 42.581562, -83.75965 42.58153, -83.760223 42.581506, -83.760226 42.582034, -83.760218 42.582097, -83.760199 42.582164, -83.760168 42.582231, -83.760127 42.582297, -83.760071 42.582359, -83.760007 42.582415, -83.759932 42.582467, -83.759854 42.582515, -83.759775 42.58256, -83.759704 42.582609, -83.759637 42.582664, -83.759581 42.582725, -83.759532 42.582792, -83.759498 42.582864, -83.759471 42.582939, -83.759459 42.583014, -83.759455 42.583091, -83.759457 42.583169, -83.759464 42.583247, -83.759467 42.583323, -83.759474 42.583399, -83.759477 42.583476, -83.759483 42.583554, -83.759522 42.584209, -83.759528 42.584265, -83.759525 42.584518, -83.759528 42.584585, -83.759531 42.584652, -83.759536 42.584718, -83.759543 42.584784, -83.759548 42.584821, -83.759553 42.584859, -83.759557 42.584897, -83.759562 42.584934, -83.759567 42.584972, -83.759571 42.58501, -83.759574 42.585048, -83.759574 42.585084, -83.759573 42.585121, -83.759569 42.585157, -83.759563 42.585195, -83.759555 42.585234, -83.759545 42.585273, -83.759533 42.58531, -83.75952 42.585345, -83.759504 42.58538, -83.759485 42.585416, -83.759464 42.585451, -83.759442 42.585485, -83.759421 42.585518, -83.759401 42.585551, -83.759368 42.585613, -83.759346 42.585672, -83.759334 42.585728, -83.759329 42.585791, -83.759331 42.585859, -83.759336 42.585926, -83.759339 42.585962, -83.759341 42.585989, -83.759475 42.585975, -83.759525 42.585972, -83.759587 42.58597, -83.759658 42.585967, -83.759733 42.585965, -83.759806 42.585963, -83.759877 42.58596, -83.759945 42.585957, -83.760011 42.585954, -83.760076 42.585951, -83.76014 42.585949, -83.760207 42.585946, -83.760276 42.585943, -83.760345 42.585941, -83.760414 42.585938, -83.760482 42.585935, -83.760549 42.585931, -83.760615 42.585929, -83.76068 42.585926, -83.760745 42.585923, -83.760811 42.58592, -83.760876 42.585918, -83.760941 42.585915, -83.761007 42.585912, -83.761073 42.585909, -83.761141 42.585906, -83.761209 42.585903, -83.761278 42.5859, -83.761349 42.585898, -83.76137 42.585897, -83.761421 42.585895, -83.761494 42.585893, -83.761567 42.58589, -83.761641 42.585887, -83.761716 42.585884, -83.761791 42.585881, -83.761867 42.585878, -83.761944 42.585875, -83.762021 42.585872, -83.762099 42.58587, -83.762177 42.585868, -83.762255 42.585866, -83.762331 42.585863, -83.762407 42.585859, -83.762482 42.585855, -83.762556 42.585852, -83.762629 42.585849, -83.762701 42.585845, -83.762771 42.585843, -83.76284 42.585841, -83.762907 42.585842, -83.762974 42.585846, -83.763039 42.585854, -83.763102 42.585865, -83.763163 42.58588, -83.763222 42.585898, -83.763278 42.585919, -83.763332 42.585944, -83.763382 42.585971, -83.763428 42.586, -83.763472 42.586031, -83.763512 42.586065, -83.76355 42.5861, -83.763584 42.586136, -83.763614 42.586173, -83.76364 42.586212, -83.763661 42.586252, -83.763678 42.586292, -83.763691 42.586335, -83.7637 42.586378, -83.763707 42.586422, -83.76371 42.586468, -83.76371 42.586515, -83.763708 42.586564, -83.76371 42.586614, -83.763717 42.586665, -83.763731 42.586718, -83.763752 42.586772, -83.763782 42.586826, -83.763817 42.586878, -83.763857 42.586928, -83.763904 42.586974, -83.763957 42.587018, -83.764017 42.587059, -83.764082 42.587094, -83.76415 42.587126, -83.764223 42.587151, -83.764301 42.587172, -83.764382 42.587187, -83.764464 42.587196, -83.764546 42.5872, -83.764629 42.5872, -83.764713 42.587197, -83.764799 42.587194, -83.76482 42.587183, -83.765611 42.587163, -83.765637 42.587968, -83.765758 42.5886, -83.768024 42.588532, -83.772476 42.588399, -83.772402 42.587069, -83.772329 42.585375, -83.772231 42.583076, -83.772205 42.58234, -83.772139 42.581263, -83.772044 42.579528, -83.772046 42.579149, -83.773435 42.579109, -83.773454 42.579109, -83.77414 42.57909, -83.774195 42.579245, -83.774199 42.579319, -83.774188 42.579807, -83.774208 42.579901, -83.774266 42.579954, -83.774335 42.579998, -83.774499 42.580001, -83.778442 42.57988, -83.778629 42.581063, -83.780443 42.580859, -83.780819 42.583859, -83.780838 42.584146, -83.781087 42.588102, -83.782455 42.588128, -83.782208 42.58452, -83.783297 42.583997, -83.784099 42.582517, -83.78436 42.580815, -83.784441 42.580794, -83.784671 42.580789, -83.784694 42.580833, -83.784671 42.580916, -83.784722 42.580949, -83.785063 42.581005, -83.785188 42.581094, -83.785508 42.581139, -83.785559 42.581194, -83.785543 42.581332, -83.785568 42.581475, -83.785671 42.581519, -83.785756 42.581508, -83.785862 42.581344, -83.786153 42.581653, -83.786438 42.581682, -83.78656 42.581792, -83.786598 42.582089, -83.786663 42.582315, -83.786743 42.582447, -83.786661 42.582479, -83.786469 42.582424, -83.786328 42.582423, -83.786334 42.582533, -83.786422 42.582638, -83.786407 42.582703, -83.786234 42.582812, -83.786293 42.582895, -83.786382 42.582956, -83.786411 42.583033, -83.786358 42.583076, -83.786284 42.583054, -83.786109 42.583136, -83.786171 42.583186, -83.785794 42.583348, -83.785739 42.583411, -83.785991 42.584958, -83.78604 42.585056, -83.786024 42.585114, -83.785974 42.585158, -83.785718 42.585286, -83.785621 42.585363, -83.78559 42.585468, -83.785688 42.587189, -83.785733 42.587465, -83.785791 42.587694, -83.785775 42.588022, -83.784749 42.588044, -83.783033 42.588118, -83.783097 42.588421, -83.783246 42.588724, -83.783267 42.588883, -83.783262 42.58894, -83.78325 42.589059, -83.783123 42.589141, -83.782953 42.589118, -83.78287 42.589139, -83.782913 42.589321, -83.782972 42.589381, -83.783014 42.589623, -83.783252 42.589608, -83.783341 42.589625, -83.783466 42.589724, -83.783473 42.58979, -83.783413 42.5899, -83.783346 42.589938, -83.783085 42.589986, -83.782951 42.589975, -83.783129 42.590047, -83.783143 42.590124, -83.782991 42.590474, -83.782775 42.590605, -83.782503 42.590687, -83.783016 42.59265, -83.78335 42.592657, -83.783409 42.592717, -83.783378 42.592783, -83.783274 42.592843, -83.783219 42.593134, -83.783026 42.593199, -83.783005 42.593335, -83.783073 42.593325, -83.783165 42.593326, -83.783318 42.593365, -83.783432 42.593431, -83.783446 42.593486, -83.783394 42.593525, -83.783126 42.593589, -83.783044 42.593589, -83.782974 42.593524, -83.782848 42.59435, -83.782864 42.594545, -83.782882 42.594989, -83.784508 42.594929, -83.78462 42.597119, -83.7848 42.600635, -83.784874 42.600634, -83.78564 42.600611, -83.785771 42.600602, -83.785642 42.598105, -83.785576 42.59711, -83.785513 42.595452, -83.785536 42.595434, -83.785351 42.594898, -83.786746 42.5949, -83.787336 42.594901, -83.787492 42.594894, -83.787291 42.58799, -83.788633 42.587961, -83.788718 42.587962, -83.789191 42.587949, -83.79218 42.587872, -83.794856 42.587808, -83.795098 42.587713, -83.79522 42.587606, -83.7956 42.591003, -83.796621 42.590973, -83.798419 42.590922, -83.798431 42.591915, -83.798467 42.593821, -83.798465 42.594474, -83.797186 42.594497, -83.795787 42.594534, -83.795803 42.595718, -83.796017 42.595396, -83.796307 42.595141, -83.796695 42.594902, -83.797187 42.59475, -83.799049 42.594381, -83.799504 42.594198, -83.799836 42.593994, -83.800067 42.593781, -83.800331 42.59344, -83.800433 42.593167, -83.800473 42.592866, -83.800458 42.592219, -83.800452 42.591971, -83.800446 42.591858, -83.80035 42.589457, -83.801701 42.589452, -83.80204 42.58953, -83.802249 42.589621, -83.802389 42.589757, -83.802463 42.589946, -83.802503 42.590186, -83.802509 42.590373, -83.802591 42.590538, -83.802717 42.590683, -83.803021 42.590838, -83.803933 42.590886, -83.804347 42.590834, -83.804633 42.590689, -83.804762 42.590548, -83.804826 42.590421, -83.804831 42.590295, -83.80483 42.59014, -83.80476 42.589984, -83.804657 42.589853, -83.804488 42.589728, -83.804204 42.589636, -83.803649 42.589545, -83.803429 42.589468, -83.803276 42.589328, -83.803182 42.589201, -83.803113 42.588924, -83.802996 42.588717, -83.802789 42.588607, -83.80264 42.58854, -83.802344 42.588486, -83.800327 42.588494, -83.800277 42.587379, -83.804223 42.587431, -83.805395 42.587412, -83.810252 42.587313, -83.810205 42.585266, -83.810255 42.584488, -83.810398 42.583817, -83.810499 42.583195, -83.810535 42.582707, -83.810501 42.582205, -83.810221 42.582239, -83.810047 42.582293, -83.809976 42.582327, -83.809794 42.582315, -83.809613 42.582285, -83.809464 42.582224, -83.809318 42.582225, -83.809222 42.580973, -83.809267 42.580912, -83.809365 42.580812, -83.809494 42.580786, -83.809654 42.580809, -83.809869 42.580824, -83.810151 42.580824, -83.810021 42.579664, -83.809979 42.579142, -83.809921 42.577717, -83.809733 42.574049, -83.809538 42.570766, -83.809776 42.570756, -83.811033 42.570724, -83.811431 42.570715, -83.812323 42.570694, -83.812479 42.570677, -83.812584 42.570647, -83.812647 42.570617, -83.81274 42.570531, -83.812788 42.570464, -83.812808 42.570396, -83.812808 42.570328, -83.812785 42.570017, -83.812763 42.569514, -83.812747 42.569274, -83.812749 42.569066, -83.812739 42.568881, -83.812741 42.568744, -83.812725 42.568527, -83.814378 42.568905, -83.817272 42.569596, -83.817244 42.570142, -83.817339 42.57048, -83.817474 42.5707, -83.817451 42.570913, -83.817508 42.570996, -83.8181 42.571078, -83.818346 42.571197, -83.818446 42.571251, -83.818974 42.571306, -83.819065 42.571333, -83.819093 42.57137, -83.819129 42.571433, -83.819129 42.571934, -83.819138 42.571989, -83.819184 42.572035, -83.819257 42.572062, -83.819458 42.572079, -83.820067 42.572062, -83.821771 42.572016, -83.821734 42.571616, -83.821643 42.571515, -83.821479 42.571388, -83.821716 42.570938, -83.825824 42.572016, -83.83195 42.573742, -83.831651 42.574195, -83.8312 42.574803, -83.830775 42.575398, -83.83061 42.575655, -83.83047 42.575911, -83.830342 42.576194, -83.830205 42.576534, -83.829958 42.577215, -83.829904 42.577445, -83.8299 42.577502, -83.829906 42.577559, -83.829939 42.577671, -83.830001 42.577777, -83.830042 42.577827, -83.830089 42.577874, -83.830141 42.577917, -83.830256 42.577991, -83.830433 42.578092, -83.831379 42.578502, -83.831352 42.578613, -83.831361 42.578674, -83.831419 42.578721, -83.831562 42.57874, -83.831579 42.578689, -83.83162 42.5786, -83.832956 42.579142, -83.832324 42.579497, -83.82965 42.580045, -83.829763 42.58673, -83.834991 42.586562, -83.83567 42.58654, -83.836721 42.586506, -83.836674 42.586657, -83.836574 42.587136, -83.836544 42.58715, -83.836537 42.587234, -83.836535 42.587286, -83.836533 42.587343, -83.83653 42.587406, -83.836526 42.587471, -83.836519 42.587537, -83.836506 42.5876, -83.836487 42.58766, -83.836462 42.587719, -83.836435 42.587775, -83.836407 42.587831, -83.836377 42.587886, -83.836345 42.58794, -83.836331 42.587962, -83.836362 42.587972, -83.836445 42.588, -83.836527 42.588027, -83.836609 42.588048, -83.836694 42.588064, -83.836783 42.588074, -83.836878 42.588079, -83.836974 42.588079, -83.83707 42.588075, -83.837167 42.58807, -83.837267 42.588062, -83.837364 42.588054, -83.837459 42.588047, -83.837548 42.58804, -83.837633 42.588033, -83.837711 42.588029, -83.837783 42.588026, -83.837857 42.588033, -83.837834 42.587777, -83.837834 42.587703, -83.83785 42.587633, -83.837881 42.587554, -83.83791 42.587493, -83.837969 42.587366, -83.83801 42.58723, -83.838026 42.587115, -83.838017 42.587002, -83.837954 42.586852, -83.837955 42.586714, -83.83798 42.586602, -83.838044 42.58647, -83.838116 42.586326, -83.838304 42.585937, -83.838671 42.585696, -83.838882 42.585926, -83.839546 42.586231, -83.84011 42.586414, -83.840131 42.587374, -83.840215 42.587589, -83.840331 42.587654, -83.840494 42.587698, -83.840958 42.587698, -83.841573 42.58772, -83.84192 42.587789, -83.842184 42.587929, -83.84241 42.58811, -83.842538 42.588257, -83.842626 42.588464, -83.842693 42.588749, -83.841489 42.588759, -83.841374 42.588763, -83.841244 42.588776, -83.841121 42.58881, -83.841051 42.588842, -83.840961 42.588901, -83.840893 42.588975, -83.84086 42.589034, -83.840848 42.589116, -83.840843 42.589238, -83.840848 42.589564, -83.840868 42.589773, -83.840868 42.589832, -83.840904 42.590892, -83.840931 42.591952, -83.840963 42.593016, -83.841008 42.594078, -83.844372 42.594007, -83.844367 42.593709, -83.844329 42.592952, -83.844297 42.59189, -83.844265 42.590829, -83.844238 42.589766, -83.844716 42.589759, -83.844918 42.589744, -83.844982 42.589793, -83.845088 42.593579, -83.845017 42.593925, -83.845146 42.598835, -83.845184 42.600045, -83.845108 42.601062, -83.854898 42.600896, -83.85677 42.600862, -83.857682 42.600834, -83.86034 42.600843, -83.861296 42.600833, -83.862396 42.6008, -83.863746 42.60076, -83.86427 42.600745, -83.864761 42.600738, -83.864761 42.600988, -83.86479 42.602954, -83.864823 42.604467, -83.864972 42.60977, -83.865065 42.613052, -83.865133 42.615468, -83.865207 42.616974, -83.865339 42.619042, -83.865343 42.619948, -83.865446 42.622836, -83.865448 42.622879, -83.865454 42.622989, -83.865483 42.623545, -83.865582 42.625447, -83.865742 42.628537, -83.865774 42.629164, -83.865819 42.629943, -83.870565 42.629785, -83.870796 42.629984, -83.871687 42.629978, -83.875503 42.629944, -83.875452 42.629521, -83.876393 42.629381, -83.87703 42.629257, -83.878289 42.629024, -83.879288 42.628769, -83.879481 42.628721, -83.879678 42.628671, -83.880617 42.628359, -83.881709 42.627955, -83.88789 42.625312, -83.8908 42.624063, -83.891902 42.623695, -83.893843 42.623168, -83.894418 42.623046, -83.89463 42.623001, -83.89558 42.622839, -83.897838 42.622571, -83.897974 42.626793, -83.897931 42.628368, -83.897886 42.62856, -83.898083 42.628876, -83.898325 42.628776, -83.898281 42.62873, -83.898289 42.628686, -83.898408 42.628614, -83.898575 42.628576, -83.898732 42.628498, -83.898844 42.628476, -83.898933 42.628493, -83.898977 42.628581, -83.899373 42.628742, -83.899548 42.628749, -83.899723 42.628701, -83.900122 42.628467, -83.900424 42.628362, -83.900528 42.628414, -83.900585 42.628672, -83.90087 42.628804, -83.900941 42.628879, -83.901134 42.628875, -83.901465 42.628813, -83.901648 42.62862, -83.901813 42.628552, -83.901976 42.628532, -83.902299 42.628608, -83.902361 42.628761, -83.902471 42.628896, -83.902639 42.628953, -83.902756 42.629083, -83.902856 42.629152, -83.902972 42.629117, -83.903066 42.629064, -83.903117 42.629037, -83.903244 42.629034, -83.903344 42.628988, -83.903457 42.628809, -83.903661 42.628772, -83.904078 42.628725, -83.904375 42.628718, -83.904829 42.628736, -83.904973 42.628794, -83.904977 42.629289, -83.904938 42.629466, -83.904799 42.629689, -83.904798 42.62981, -83.904923 42.629967, -83.904919 42.630061, -83.90477 42.630113, -83.904783 42.630372, -83.904887 42.630392, -83.904987 42.630324, -83.905079 42.630514, -83.905168 42.630573, -83.905332 42.630553, -83.905503 42.630533, -83.905644 42.630579, -83.905601 42.630817, -83.905783 42.630885, -83.905883 42.630949, -83.906087 42.630878, -83.906316 42.631062, -83.906489 42.631311, -83.906496 42.631465, -83.906461 42.631663, -83.906701 42.632104, -83.907239 42.631809, -83.907716 42.631675, -83.908342 42.631403, -83.908939 42.631049, -83.909172 42.630703, -83.909512 42.62993, -83.909737 42.629639, -83.910151 42.629565, -83.910354 42.629648, -83.911041 42.629744, -83.911849 42.629523, -83.912635 42.629242, -83.913306 42.628968, -83.91375 42.628727, -83.914074 42.628671, -83.914493 42.628725, -83.914734 42.628864, -83.914833 42.629019, -83.914887 42.629289, -83.914905 42.629424, -83.914914 42.62956, -83.914988 42.630127, -83.915055 42.630329, -83.915017 42.629422, -83.914998 42.62849, -83.914982 42.628094, -83.914962 42.626731, -83.914898 42.626377, -83.914862 42.624556, -83.915106 42.624594, -83.915599 42.624647, -83.915617 42.624498, -83.915639 42.623887, -83.915639 42.623676, -83.91562 42.623541, -83.915569 42.623466, -83.915475 42.62341, -83.915389 42.623396, -83.915299 42.623396, -83.915223 42.623396, -83.915161 42.623419, -83.915129 42.623487, -83.915107 42.623467, -83.915069 42.62345, -83.915025 42.623437, -83.914809 42.623402, -83.91481 42.62316, -83.914818 42.622242, -83.915291 42.622266, -83.918072 42.622415, -83.919125 42.622473, -83.920175 42.622539, -83.920464 42.622552, -83.921998 42.62258, -83.922811 42.622603, -83.923869 42.622633, -83.924203 42.622633, -83.925292 42.622636, -83.92532 42.622914, -83.925365 42.623118, -83.92555 42.623494, -83.925679 42.623669, -83.925825 42.623857, -83.926861 42.625079, -83.927047 42.625301, -83.927765 42.625086, -83.928227 42.625756, -83.934463 42.625743, -83.934928 42.625751, -83.934957 42.62626, -83.935035 42.62626, -83.934434 42.626727, -83.936102 42.626724, -83.936361 42.627217, -83.937043 42.627209, -83.937111 42.629358, -83.934505 42.629367, -83.929696 42.629382, -83.929731 42.629596, -83.929769 42.631545, -83.929814 42.632579, -83.929811 42.632766, -83.929958 42.633231, -83.930041 42.633445, -83.930486 42.634164, -83.93093 42.634955, -83.931559 42.63609, -83.932125 42.637187, -83.932585 42.638096, -83.932913 42.638672, -83.932724 42.638772, -83.932551 42.638796, -83.930618 42.638798, -83.930385 42.638814, -83.930225 42.638858, -83.93006 42.638943, -83.92996 42.639025, -83.929892 42.639121, -83.929853 42.639196, -83.929817 42.639266, -83.92977 42.639458, -83.92976 42.640078, -83.928156 42.640048, -83.928165 42.640697, -83.928188 42.641336, -83.933713 42.641383, -83.933972 42.641357, -83.934244 42.641276, -83.934593 42.641959, -83.934715 42.642369, -83.934836 42.642834, -83.934928 42.643313, -83.935012 42.643873, -83.935094 42.645467, -83.935144 42.646428, -83.935209 42.64853, -83.934043 42.648302, -83.931807 42.647137, -83.928735 42.647217, -83.928207 42.647231, -83.928234 42.648462, -83.928772 42.648866, -83.931071 42.650591, -83.933655 42.650591, -83.933687 42.651212, -83.935304 42.651223, -83.938581 42.651208, -83.940111 42.651205, -83.940211 42.651204, -83.940083 42.649833, -83.940034 42.647756, -83.939842 42.643959, -83.939763 42.642371, -83.939684 42.641898, -83.939648 42.641709, -83.939576 42.641421, -83.938652 42.63851, -83.938568 42.638269, -83.938455 42.637982, -83.938393 42.637236, -83.938307 42.636236, -83.938234 42.634685, -83.93821 42.633274, -83.938267 42.632504, -83.938449 42.631919, -83.938536 42.631719, -83.939099 42.630662, -83.939371 42.629915, -83.939466 42.629571, -83.939483 42.629351, -83.944427 42.629286, -83.950492 42.629293, -83.954303 42.629264, -83.954287 42.627979, -83.954255 42.625873, -83.954239 42.624882, -83.954185 42.621834, -83.954507 42.621838, -83.955866 42.621825, -83.959208 42.621826, -83.959779 42.621843, -83.96035 42.62186, -83.961346 42.621783, -83.961584 42.62178, -83.962481 42.621975, -83.964648 42.622078, -83.965161 42.622091, -83.966472 42.622182, -83.968057 42.62225, -83.970368 42.622361, -83.970623 42.62239, -83.971344 42.62242, -83.97363 42.622518, -83.974838 42.622591, -83.975611 42.62261, -83.976083 42.622657, -83.977165 42.622701, -83.993702 42.631995, -83.993615 42.628903, -83.993585 42.627198, -83.993569 42.626237, -83.993563 42.625869, -83.993542 42.624742, -83.993494 42.622801, -83.993466 42.62164, -83.993439 42.619951, -83.993341 42.619481, -83.993249 42.619323, -83.993109 42.61914, -83.992902 42.618945, -83.992676 42.618804, -83.992444 42.618695, -83.99217 42.618603, -83.991932 42.618567, -83.991633 42.618548, -83.991426 42.618548, -83.99103 42.618628, -83.99056 42.61878, -83.990139 42.61889, -83.989822 42.618896, -83.989603 42.618829, -83.989328 42.618626, -83.987213 42.620126, -83.98666 42.619724, -83.986545 42.619642, -83.984603 42.618254, -83.984532 42.618203, -83.983785 42.617667, -83.983279 42.617252, -83.982627 42.616936, -83.981237 42.616386, -83.980237 42.615925, -83.980002 42.615846, -83.978717 42.615413, -83.977226 42.614951, -83.976047 42.614672, -83.97556 42.614593, -83.973685 42.614373, -83.969765 42.613958, -83.968248 42.613721, -83.967722 42.613604, -83.967131 42.613449, -83.967007 42.613606, -83.966359 42.614009, -83.966138 42.614196, -83.966032 42.614328, -83.966008 42.614472, -83.966039 42.61523, -83.966014 42.615502, -83.965944 42.615815, -83.96569 42.616381, -83.965199 42.616237, -83.965047 42.615724, -83.964731 42.615163, -83.964601 42.614551, -83.96553 42.614447, -83.964477 42.613972, -83.964199 42.613813, -83.964226 42.613234, -83.96436 42.613139, -83.964528 42.613041, -83.964405 42.612889, -83.964257 42.612838, -83.963702 42.612645, -83.963555 42.612594, -83.96354 42.612468, -83.961544 42.611761, -83.959878 42.611123, -83.959925 42.610803, -83.959907 42.610545, -83.963748 42.611916, -83.963841 42.61002, -83.96462 42.608909, -83.965418 42.60813, -83.965891 42.607336, -83.966066 42.606916, -83.962965 42.606229, -83.955886 42.604662, -83.954907 42.604392, -83.95413 42.604165, -83.953471 42.603916, -83.952018 42.603334, -83.95159 42.603143, -83.95152 42.600626, -83.951517 42.600478, -83.952172 42.600463, -83.953819 42.600441, -83.953803 42.600082, -83.954032 42.599999, -83.95417 42.599897, -83.955181 42.598739, -83.958056 42.595451, -83.959176 42.594171, -83.959313 42.594054, -83.95945 42.593964, -83.959578 42.593879, -83.959766 42.593834, -83.960931 42.593637, -83.960768 42.593277, -83.960796 42.592991, -83.96051 42.592892, -83.960369 42.592869, -83.960154 42.592885, -83.959908 42.592967, -83.959618 42.593026, -83.959403 42.592976, -83.959231 42.593217, -83.959082 42.5932, -83.957804 42.592822, -83.956285 42.592121, -83.955288 42.590675, -83.954845 42.590251, -83.954059 42.590023, -83.953429 42.589994, -83.95217 42.591125, -83.950185 42.59306, -83.94799 42.593075, -83.946984 42.592956, -83.944888 42.592538, -83.944517 42.592503, -83.944063 42.592666, -83.943044 42.593084, -83.943004 42.591769, -83.942939 42.589558, -83.942872 42.587099, -83.942787 42.584408, -83.942708 42.581888, -83.942679 42.581325, -83.942432 42.578646, -83.942362 42.577706, -83.942172 42.575132, -83.941928 42.571921, -83.941893 42.571455, -83.93259 42.571639, -83.932399 42.568071, -83.918789 42.568595, -83.918602 42.568618, -83.91845 42.568676, -83.917555 42.569347, -83.917409 42.569426, -83.917226 42.569439, -83.913835 42.569326, -83.913904 42.569844, -83.913942 42.571432, -83.914006 42.574385, -83.913994 42.575326, -83.913959 42.577318, -83.913961 42.577894, -83.913972 42.578491, -83.913984 42.582647, -83.914014 42.582839, -83.914049 42.582988, -83.914034 42.583522, -83.913862 42.583426, -83.913585 42.583182, -83.9129 42.582461, -83.912567 42.582246, -83.912281 42.582218, -83.912559 42.583486, -83.912712 42.583714, -83.912851 42.583808, -83.913055 42.583903, -83.913243 42.58394, -83.913648 42.583983, -83.913774 42.583955, -83.913893 42.583893, -83.914115 42.584319, -83.910711 42.584113, -83.91073 42.584321, -83.904693 42.583911, -83.904435 42.583894, -83.903752 42.583851, -83.902864 42.583759, -83.902162 42.58365, -83.901314 42.58346, -83.900528 42.58323, -83.899801 42.582971, -83.899049 42.582672, -83.897879 42.582165, -83.897077 42.581817, -83.89612 42.581418, -83.890477 42.579063, -83.889661 42.57875, -83.888846 42.578537, -83.887902 42.578373, -83.88707 42.578287, -83.886216 42.578263, -83.88545 42.578298, -83.88508 42.578308, -83.873987 42.578921, -83.870707 42.579103, -83.869146 42.579134, -83.86149 42.579464, -83.859818 42.579499, -83.858726 42.579436, -83.858015 42.579356, -83.857497 42.579282, -83.857187 42.579214, -83.856455 42.579055, -83.856128 42.578966, -83.855853 42.578881, -83.855584 42.578798, -83.855355 42.578719, -83.854859 42.578557, -83.853192 42.577728, -83.852752 42.577445, -83.852176 42.576995, -83.847659 42.573327, -83.844638 42.57096, -83.843271 42.569796, -83.840783 42.567803, -83.839932 42.567213, -83.839371 42.566894, -83.834596 42.564218, -83.83397 42.563867, -83.83396 42.563536, -83.833702 42.558673, -83.833635 42.557429, -83.834611 42.557398, -83.835427 42.557864, -83.844225 42.56312, -83.845411 42.563739, -83.847434 42.564642, -83.848672 42.565157, -83.848982 42.564443, -83.849022 42.564389, -83.849106 42.564357, -83.849209 42.564327, -83.851121 42.564274, -83.853799 42.564202, -83.853665 42.5606, -83.853503 42.556961, -83.859548 42.556661, -83.860979 42.556588, -83.864983 42.556383, -83.873144 42.556045, -83.873834 42.556015, -83.873697 42.554009, -83.873668 42.553883, -83.873591 42.553783, -83.873521 42.553738, -83.873291 42.553696, -83.873161 42.553684, -83.873044 42.55359, -83.872634 42.545036, -83.872424 42.541542, -83.872433 42.541392, -83.872488 42.541268, -83.872738 42.541045, -83.872484 42.540685, -83.872374 42.540365, -83.872334 42.540046, -83.872304 42.539612, -83.87218 42.537807, -83.872117 42.537613, -83.872023 42.537422, -83.871904 42.537243, -83.871765 42.537082, -83.87145 42.536807, -83.871556 42.536726, -83.871729 42.536643, -83.87194 42.53655, -83.872129 42.53651, -83.872367 42.536507, -83.872705 42.536493, -83.87307 42.536442, -83.873253 42.536373, -83.873481 42.536241, -83.873821 42.535994, -83.873895 42.535924, -83.873489 42.535699, -83.872957 42.535513, -83.871864 42.535039, -83.871918 42.534236, -83.871743 42.53068, -83.871693 42.529686, -83.871724 42.529639, -83.871747 42.529586, -83.871768 42.529534, -83.871791 42.529484, -83.871813 42.529433, -83.871834 42.529381, -83.871857 42.529328, -83.871881 42.529277, -83.871904 42.529224, -83.871929 42.529171, -83.871951 42.529118, -83.871977 42.529068, -83.872003 42.529015, -83.872028 42.528963, -83.872048 42.528911, -83.872067 42.528859, -83.872081 42.528805, -83.872088 42.528751, -83.872092 42.528697, -83.872091 42.528612, -83.872088 42.528558, -83.872076 42.528504, -83.872059 42.528438, -83.872042 42.528384, -83.872021 42.528332, -83.871991 42.528279, -83.871961 42.528228, -83.871928 42.52818, -83.871892 42.528133, -83.871856 42.528088, -83.871815 42.528044, -83.871773 42.528001, -83.871727 42.52796, -83.871657 42.527904, -83.871603 42.527867, -83.871582 42.527426, -83.871521 42.526663, -83.871633 42.52671, -83.871743 42.526744, -83.871828 42.526775, -83.871946 42.526783, -83.872177 42.526749, -83.872299 42.52674, -83.872442 42.526716, -83.872572 42.526716, -83.87268 42.526694, -83.872998 42.526744, -83.873299 42.526875, -83.873651 42.527034, -83.873871 42.527114, -83.874112 42.527226, -83.874336 42.52726, -83.874568 42.527267, -83.875277 42.527163, -83.875614 42.52711, -83.875925 42.527005, -83.87657 42.526807, -83.876756 42.526699, -83.877011 42.526576, -83.881176 42.526447, -83.881391 42.526443, -83.881181 42.522935, -83.881147 42.522361, -83.881005 42.519291, -83.871139 42.519592, -83.87119 42.520164, -83.871212 42.520439, -83.871112 42.52045, -83.871014 42.52046, -83.870931 42.520462, -83.870847 42.520456, -83.87075 42.520446, -83.870588 42.520417, -83.870488 42.520389, -83.870396 42.520346, -83.870348 42.520317, -83.870301 42.520285, -83.870255 42.520254, -83.870168 42.520188, -83.864858 42.520317, -83.864842 42.52049, -83.864726 42.520611, -83.864502 42.520775, -83.864462 42.520993, -83.864276 42.521208, -83.864199 42.521476, -83.864109 42.521587, -83.863994 42.521669, -83.863808 42.521673, -83.863652 42.521793, -83.863518 42.521829, -83.863392 42.521864, -83.863212 42.522017, -83.863288 42.522786, -83.863212 42.523094, -83.86327 42.52327, -83.863484 42.523425, -83.863563 42.523639, -83.863518 42.523738, -83.863197 42.524094, -83.863137 42.524143, -83.863056 42.524154, -83.862828 42.524374, -83.862812 42.524527, -83.862774 42.524604, -83.862781 42.524725, -83.862961 42.524951, -83.862938 42.52505, -83.862951 42.525165, -83.863047 42.525275, -83.863541 42.525706, -83.863801 42.52613, -83.864036 42.526317, -83.864364 42.526659, -83.86444 42.526973, -83.863542 42.52705, -83.862792 42.52708, -83.861994 42.527108, -83.860184 42.52601, -83.860391 42.526501, -83.860434 42.526682, -83.860384 42.526872, -83.860236 42.527064, -83.85994 42.527192, -83.850389 42.527569, -83.848923 42.527611, -83.847943 42.527664, -83.846601 42.527692, -83.846335 42.527702, -83.846059 42.527725, -83.8444 42.52777, -83.844323 42.52628, -83.844322 42.526046, -83.844523 42.525728, -83.844568 42.525488, -83.844526 42.525152, -83.844626 42.524872, -83.844686 42.524465, -83.84464 42.523693, -83.844633 42.523468, -83.844132 42.523469, -83.843769 42.523482, -83.843442 42.523509, -83.843167 42.523585, -83.842982 42.523702, -83.842869 42.523833, -83.842799 42.52402, -83.842838 42.524959, -83.842818 42.525548, -83.8427 42.5258, -83.842466 42.52601, -83.842004 42.526276, -83.840989 42.526799, -83.84057 42.526872, -83.840162 42.526891, -83.838812 42.526905, -83.838743 42.52679, -83.838729 42.526707, -83.838728 42.52663, -83.838782 42.526435, -83.838947 42.526144, -83.838996 42.525769, -83.838964 42.524961, -83.838929 42.524807, -83.838879 42.524701, -83.838763 42.524565, -83.838659 42.524459, -83.838199 42.524176, -83.838108 42.52408, -83.838018 42.523964, -83.837948 42.523858, -83.837905 42.523728, -83.837897 42.523589, -83.837957 42.522996, -83.837944 42.522761, -83.838033 42.522227, -83.838188 42.52091, -83.838256 42.520516, -83.838224 42.52014, -83.838096 42.51991, -83.837922 42.519726, -83.837601 42.519579, -83.837323 42.519493, -83.836835 42.519477, -83.836555 42.519452, -83.835881 42.519237, -83.835763 42.519081, -83.835632 42.518925, -83.835521 42.518735, -83.835338 42.518537, -83.835126 42.518354, -83.834868 42.518225, -83.834 42.517927, -83.833623 42.51784, -83.833211 42.517704, -83.832885 42.517678, -83.83264 42.517693, -83.832339 42.517768, -83.832056 42.51792, -83.831902 42.518053, -83.831823 42.513605, -83.831009 42.51363, -83.830846 42.511449, -83.830646 42.511276, -83.830573 42.511116, -83.83046 42.510511, -83.830211 42.510053, -83.830147 42.509783, -83.830105 42.509508, -83.830204 42.509261, -83.830197 42.509179, -83.830011 42.509211, -83.829945 42.5092, -83.829878 42.509144, -83.829886 42.509079, -83.83017 42.508843, -83.830515 42.50841, -83.830618 42.508374, -83.830453 42.50615, -83.829617 42.505932, -83.829447 42.505827, -83.829244 42.505733, -83.829149 42.505622, -83.829053 42.505561, -83.828949 42.505539, -83.8288 42.505555, -83.828667 42.505538, -83.828459 42.505559, -83.828028 42.505651, -83.827873 42.505606, -83.827718 42.505501, -83.827505 42.505263, -83.827396 42.504982, -83.827354 42.504658, -83.827274 42.504487, -83.826859 42.504375, -83.826654 42.504182, -83.826559 42.504055, -83.826634 42.503901, -83.82685 42.503781, -83.827205 42.503708, -83.826277 42.503711, -83.826209 42.507513, -83.826195 42.507548, -83.820622 42.507572, -83.819429 42.507557, -83.819502 42.507277, -83.819379 42.506801, -83.81935 42.50674, -83.819233 42.506614, -83.819166 42.506587, -83.819103 42.506587, -83.818906 42.506641, -83.81709 42.506919, -83.815054 42.507872, -83.814916 42.507952, -83.814797 42.508028, -83.814635 42.508173, -83.814557 42.508272, -83.814415 42.508387, -83.814299 42.508467, -83.814132 42.508562, -83.81239 42.509014, -83.812188 42.508987, -83.812999 42.507876, -83.813067 42.507733, -83.813103 42.50754, -83.813069 42.507277, -83.812827 42.506542, -83.812692 42.505332, -83.812569 42.505066, -83.812346 42.504855, -83.811348 42.504288, -83.810554 42.503829, -83.809963 42.503305, -83.809745 42.502967, -83.809651 42.502229, -83.809706 42.501985, -83.809516 42.501884, -83.809307 42.50071, -83.810184 42.500552, -83.810538 42.50048, -83.811511 42.500135, -83.812107 42.499949, -83.813071 42.49921, -83.813746 42.498569, -83.816625 42.497412, -83.81769 42.496909, -83.817982 42.496802, -83.818233 42.496746, -83.818537 42.496712, -83.820456 42.49667, -83.82057 42.496661, -83.820651 42.496615, -83.820696 42.49654, -83.820725 42.496424, -83.820502 42.493233, -83.820508 42.493143, -83.820547 42.493077, -83.820642 42.493029, -83.820756 42.493, -83.822037 42.492957, -83.823562 42.492913, -83.823991 42.492891, -83.824298 42.492825, -83.824642 42.492691, -83.824884 42.492521, -83.825051 42.492358, -83.825162 42.492127, -83.825229 42.49186, -83.825107 42.489288, -83.824953 42.487406, -83.824875 42.486234, -83.82485 42.48569, -83.823332 42.485768, -83.822758 42.485768, -83.817878 42.485773, -83.817946 42.483453, -83.817744 42.483071, -83.817379 42.482511, -83.817224 42.482279, -83.815577 42.482208, -83.815671 42.482143, -83.815746 42.48206, -83.815955 42.481665, -83.816077 42.481464, -83.816372 42.481174, -83.816547 42.481002, -83.816819 42.480624, -83.817164 42.480232, -83.81743 42.480008, -83.817672 42.479881, -83.818372 42.479026, -83.819674 42.477555, -83.81981 42.477488, -83.819993 42.477464, -83.820154 42.47748, -83.821019 42.477643, -83.821045 42.477956, -83.821084 42.478057, -83.821202 42.478146, -83.821879 42.478458, -83.822164 42.478575, -83.822762 42.478998, -83.823429 42.479495, -83.823977 42.47997, -83.824159 42.480078, -83.824268 42.480129, -83.824855 42.480276, -83.825405 42.48032, -83.825534 42.480307, -83.825711 42.480268, -83.825886 42.480183, -83.82672 42.479807, -83.826978 42.479748, -83.827273 42.479746, -83.828521 42.479766, -83.828778 42.479771, -83.829075 42.479735, -83.829186 42.479703, -83.829799 42.480373, -83.830359 42.480013, -83.833252 42.480078, -83.833917 42.47878, -83.835035 42.479018, -83.836389 42.478029, -83.836387 42.477979, -83.83755 42.477942, -83.837863 42.479168, -83.838128 42.479821, -83.83814 42.48001, -83.838153 42.48078, -83.838116 42.480862, -83.838057 42.480948, -83.837878 42.48114, -83.83603 42.482268, -83.836803 42.482943, -83.836953 42.483039, -83.837121 42.483085, -83.837243 42.483112, -83.837331 42.483127, -83.837769 42.483062, -83.838075 42.483071, -83.838224 42.483099, -83.838348 42.483139, -83.838492 42.483207, -83.838601 42.483285, -83.838788 42.483409, -83.838989 42.483484, -83.839192 42.483513, -83.839397 42.483514, -83.840814 42.483464, -83.841072 42.483472, -83.841277 42.48351, -83.841429 42.483561, -83.841588 42.483624, -83.841754 42.483701, -83.841979 42.48376, -83.842182 42.483779, -83.842589 42.483776, -83.846799 42.483741, -83.846977 42.483732, -83.847124 42.483703, -83.847247 42.483655, -83.849107 42.482674, -83.849192 42.482772, -83.850444 42.483987, -83.851585 42.485115, -83.852025 42.486783, -83.852693 42.488831, -83.852871 42.489374, -83.8529 42.489464, -83.853006 42.489672, -83.853123 42.489845, -83.853357 42.490048, -83.853639 42.49022, -83.853986 42.490364, -83.854443 42.490194, -83.854576 42.490134, -83.85491 42.490163, -83.855163 42.490136, -83.855548 42.490132, -83.855831 42.49004, -83.856068 42.490052, -83.856217 42.489976, -83.856247 42.489839, -83.856315 42.489811, -83.856235 42.489553, -83.856272 42.489492, -83.856369 42.489465, -83.856452 42.489328, -83.856549 42.489257, -83.856572 42.489164, -83.856461 42.489125, -83.856331 42.488805, -83.856108 42.488854, -83.855982 42.488787, -83.855945 42.488732, -83.856028 42.488612, -83.856181 42.488475, -83.856123 42.488403, -83.856204 42.488321, -83.856354 42.48825, -83.856406 42.488157, -83.85634 42.488091, -83.856498 42.487872, -83.856491 42.487778, -83.856367 42.487695, -83.856292 42.487645, -83.856219 42.487497, -83.856264 42.487403, -83.856443 42.487289, -83.856689 42.487202, -83.856874 42.487241, -83.857088 42.487385, -83.857295 42.487298, -83.857565 42.487029, -83.858018 42.486855, -83.858143 42.487015, -83.858194 42.487153, -83.858306 42.48706, -83.858343 42.486994, -83.8585 42.486956, -83.858449 42.486835, -83.858464 42.486769, -83.858583 42.486753, -83.858679 42.486842, -83.858842 42.486848, -83.858968 42.486766, -83.858865 42.486688, -83.858829 42.486622, -83.859008 42.486502, -83.859208 42.486499, -83.859139 42.486727, -83.858724 42.487656, -83.857796 42.489153, -83.856774 42.490853, -83.856436 42.491317, -83.857325 42.49166, -83.857352 42.49167, -83.857605 42.491731, -83.857879 42.491773, -83.858126 42.491794, -83.859002 42.491769, -83.859013 42.491843, -83.862027 42.494763, -83.865928 42.495188, -83.865832 42.491609, -83.865828 42.491554, -83.864544 42.491587, -83.864449 42.490101, -83.864433 42.489729, -83.864362 42.488694, -83.864283 42.487502, -83.86419 42.486069, -83.864164 42.485426, -83.864139 42.484782, -83.864054 42.482614, -83.864134 42.482267, -83.864274 42.48192, -83.864469 42.481538, -83.86642 42.479131, -83.866754 42.478688, -83.868486 42.476367, -83.868602 42.476212, -83.869348 42.475136, -83.870655 42.475212, -83.871388 42.475225, -83.87178 42.475213, -83.872213 42.475179, -83.872409 42.475725, -83.872533 42.475984, -83.872566 42.476424, -83.87284 42.476414, -83.873044 42.476391, -83.873465 42.476297, -83.873631 42.476233, -83.874239 42.47593, -83.874462 42.476123, -83.875149 42.476573, -83.875385 42.476686, -83.875578 42.476752, -83.875808 42.476796, -83.875987 42.476819, -83.876193 42.476833, -83.878027 42.476825, -83.878104 42.476817, -83.87814 42.476795, -83.878183 42.476755, -83.878206 42.476703, -83.878201 42.476308, -83.87827 42.476354, -83.878995 42.476387, -83.879314 42.476386, -83.879377 42.476379, -83.879418 42.476365, -83.879507 42.476344, -83.879533 42.47633, -83.879664 42.476193, -83.879749 42.476123, -83.879775 42.47611, -83.879827 42.476094, -83.87989 42.476094, -83.880068 42.476133, -83.880124 42.476042, -83.88014 42.476002, -83.880151 42.475945, -83.880183 42.475662, -83.880183 42.47553, -83.881709 42.475891, -83.881934 42.475913, -83.882127 42.475919, -83.882371 42.475899, -83.883204 42.475801, -83.883444 42.475791, -83.883706 42.475808, -83.884958 42.476118, -83.886235 42.476418, -83.88723 42.476677, -83.887029 42.477133, -83.887044 42.477266, -83.887091 42.477451, -83.887144 42.477606, -83.887323 42.47779, -83.887905 42.478247, -83.888515 42.478777, -83.888766 42.478976, -83.889091 42.479424, -83.889434 42.479769, -83.889595 42.479887, -83.889729 42.479962, -83.889882 42.480037, -83.890036 42.480086, -83.890235 42.480101, -83.890444 42.480094, -83.8909 42.479987, -83.891109 42.479973, -83.891382 42.479968, -83.891643 42.480016, -83.892706 42.480391, -83.893129 42.479636, -83.893247 42.479506, -83.893392 42.479419, -83.893594 42.479319, -83.893858 42.479263, -83.895197 42.479193, -83.8971 42.479068, -83.898289 42.479009, -83.898082 42.476123, -83.8994 42.476045, -83.901997 42.475893, -83.902666 42.475849, -83.902878 42.475836, -83.903033 42.475779, -83.903369 42.475617, -83.903646 42.475506, -83.904177 42.475343, -83.904653 42.475155, -83.904777 42.475106, -83.905789 42.474705, -83.905964 42.474652, -83.906124 42.47463, -83.90629 42.474634, -83.906455 42.474671, -83.906619 42.474763, -83.90701 42.474994, -83.907177 42.475064, -83.907329 42.475111, -83.907584 42.475145, -83.908972 42.475231, -83.909621 42.475235, -83.913902 42.475258, -83.913687 42.472922, -83.91361 42.471657, -83.913413 42.469018, -83.913211 42.465853, -83.913158 42.464816, -83.913109 42.463857, -83.913095 42.46358, -83.913036 42.462902, -83.912994 42.462421, -83.912894 42.460732, -83.911428 42.460727, -83.910747 42.460718, -83.909702 42.460718, -83.908842 42.460759, -83.908102 42.460886, -83.907613 42.450506, -83.907568 42.449552, -83.907518 42.448497, -83.907351 42.447935, -83.907261 42.447475, -83.907376 42.446927, -83.907209 42.44688, -83.906816 42.4469, -83.906671 42.446933, -83.906457 42.447026, -83.906378 42.447099, -83.906305 42.447145, -83.906185 42.447338, -83.906159 42.44777, -83.906103 42.447942, -83.905998 42.448133, -83.905879 42.448286, -83.905569 42.448657, -83.905463 42.448776, -83.905349 42.448915, -83.905242 42.448994, -83.904935 42.449131, -83.904716 42.449298, -83.904549 42.449602, -83.903225 42.449175, -83.903174 42.449158, -83.903023 42.449129, -83.902636 42.449102, -83.902153 42.449176, -83.901354 42.449208, -83.901204 42.44712, -83.901002 42.443457, -83.906169 42.443284, -83.906522 42.443252, -83.906573 42.443242, -83.907265 42.4431, -83.908259 42.442987, -83.908788 42.442991, -83.909248 42.442994, -83.909883 42.442978, -83.911002 42.442952, -83.911692 42.44288, -83.911235 42.436263, -83.910778 42.436041, -83.91078 42.436097, -83.910705 42.436578, -83.910741 42.436677, -83.910837 42.436721, -83.91097 42.436733, -83.911066 42.436766, -83.911111 42.436843, -83.911028 42.436997, -83.910827 42.437024, -83.910671 42.437122, -83.910611 42.437265, -83.910579 42.437534, -83.910593 42.437611, -83.910659 42.437683, -83.910948 42.437689, -83.911089 42.437711, -83.911141 42.437744, -83.911161 42.437898, -83.91065 42.437952, -83.910695 42.43848, -83.911212 42.438574, -83.911241 42.438684, -83.910781 42.438727, -83.910775 42.438656, -83.910686 42.438661, -83.910947 42.442399, -83.910591 42.442403, -83.910113 42.437511, -83.91012 42.437423, -83.910386 42.436654, -83.910342 42.4365, -83.91021 42.436395, -83.909914 42.43634, -83.909611 42.43624, -83.909285 42.436217, -83.909119 42.436574, -83.909222 42.436623, -83.908795 42.437776, -83.908824 42.437974, -83.90897 42.438288, -83.908998 42.438425, -83.909213 42.442553, -83.909079 42.442567, -83.908812 42.442596, -83.908726 42.442195, -83.908823 42.442129, -83.908853 42.441986, -83.908784 42.441398, -83.908635 42.440947, -83.908532 42.440782, -83.908558 42.440386, -83.90846 42.439479, -83.908433 42.438633, -83.908383 42.438451, -83.908256 42.438473, -83.908018 42.438681, -83.907594 42.438905, -83.907596 42.438929, -83.907536 42.438961, -83.907439 42.438986, -83.907319 42.439019, -83.907396 42.439191, -83.906955 42.439207, -83.90676 42.439214, -83.906136 42.439143, -83.906006 42.439119, -83.905618 42.439048, -83.905138 42.438866, -83.904666 42.438644, -83.904285 42.438077, -83.903565 42.43835, -83.903533 42.438304, -83.903486 42.438234, -83.904145 42.437934, -83.90384 42.437554, -83.903532 42.437218, -83.903327 42.436849, -83.902838 42.435869, -83.902712 42.435353, -83.902796 42.434985, -83.903237 42.434436, -83.903506 42.434179, -83.903697 42.433883, -83.903893 42.433421, -83.903838 42.433273, -83.903554 42.43319, -83.903271 42.433293, -83.902974 42.433457, -83.902787 42.433627, -83.902298 42.43423, -83.902163 42.434488, -83.902199 42.434576, -83.902104 42.434659, -83.90202 42.434664, -83.901933 42.434639, -83.901847 42.434588, -83.901838 42.434518, -83.901934 42.434445, -83.902074 42.434362, -83.902652 42.432973, -83.902955 42.433001, -83.903185 42.432985, -83.903348 42.432931, -83.903648 42.433009, -83.9038 42.433075, -83.904051 42.433076, -83.904402 42.432802, -83.904674 42.432522, -83.904692 42.432121, -83.90473 42.431973, -83.90468 42.431753, -83.904428 42.431681, -83.904217 42.431632, -83.904077 42.431551, -83.904015 42.431515, -83.903645 42.431415, -83.903566 42.431568, -83.903469 42.431678, -83.903317 42.431661, -83.903326 42.431463, -83.903545 42.430925, -83.903776 42.430651, -83.903997 42.430454, -83.904151 42.430537, -83.904225 42.430647, -83.904209 42.43079, -83.904101 42.430905, -83.903759 42.43108, -83.903706 42.431151, -83.903683 42.43125, -83.903779 42.431316, -83.904105 42.431442, -83.904606 42.431637, -83.904815 42.431468, -83.905045 42.431413, -83.905661 42.431365, -83.905713 42.431261, -83.905449 42.430876, -83.905315 42.430651, -83.905138 42.430464, -83.904985 42.430155, -83.904936 42.429848, -83.904982 42.429562, -83.905136 42.429106, -83.905307 42.42887, -83.905835 42.428564, -83.906074 42.428454, -83.906043 42.428426, -83.90577 42.428174, -83.905383 42.427814, -83.905383 42.427759, -83.90545 42.427716, -83.905524 42.42776, -83.905925 42.428075, -83.906135 42.428251, -83.906248 42.428208, -83.90658 42.428088, -83.906848 42.42794, -83.907348 42.427854, -83.907549 42.427805, -83.907721 42.427728, -83.907582 42.42747, -83.90749 42.42739, -83.907609 42.427338, -83.907741 42.427608, -83.907863 42.427722, -83.907965 42.427709, -83.908249 42.427509, -83.908474 42.427397, -83.908732 42.427365, -83.90887 42.427309, -83.908902 42.427132, -83.908881 42.427, -83.909307 42.426731, -83.909327 42.42659, -83.909287 42.426444, -83.909297 42.426304, -83.909494 42.426119, -83.909887 42.425606, -83.909925 42.425562, -83.910304 42.425316, -83.910408 42.425224, -83.910456 42.425086, -83.910467 42.424946, -83.910468 42.424855, -83.910388 42.42468, -83.910334 42.424371, -83.910148 42.424043, -83.909924 42.423682, -83.909496 42.423041, -83.909013 42.422301, -83.908845 42.422045, -83.908637 42.421594, -83.908678 42.421327, -83.908747 42.421255, -83.908713 42.421121, -83.908645 42.421039, -83.908474 42.421111, -83.908375 42.421247, -83.90822 42.421341, -83.908021 42.421317, -83.907786 42.421232, -83.90759 42.421097, -83.9074 42.420918, -83.907 42.42052, -83.906303 42.419965, -83.905943 42.419656, -83.905595 42.419488, -83.905617 42.419381, -83.905823 42.419427, -83.905928 42.419505, -83.906241 42.41979, -83.906571 42.42006, -83.907045 42.420506, -83.907486 42.420896, -83.908015 42.421184, -83.908112 42.421184, -83.908674 42.420965, -83.90864 42.420832, -83.908438 42.420904, -83.908353 42.420802, -83.908303 42.420713, -83.908275 42.420663, -83.908212 42.42066, -83.908059 42.420353, -83.90795 42.419685, -83.908082 42.419685, -83.908084 42.41966, -83.908217 42.419428, -83.908254 42.419302, -83.908145 42.419208, -83.907974 42.419185, -83.907782 42.419184, -83.907538 42.419129, -83.907102 42.419017, -83.906814 42.418983, -83.906652 42.418878, -83.90658 42.418521, -83.90665 42.418081, -83.906896 42.41772, -83.907231 42.417534, -83.907477 42.417293, -83.907799 42.416816, -83.908039 42.416388, -83.908383 42.415807, -83.908502 42.415676, -83.908429 42.415597, -83.908185 42.415558, -83.908115 42.415555, -83.907917 42.415545, -83.907797 42.415533, -83.907848 42.415475, -83.907903 42.415412, -83.907649 42.415304, -83.907572 42.415271, -83.907403 42.415238, -83.907065 42.414657, -83.906833 42.414257, -83.9066 42.413751, -83.906522 42.413201, -83.906721 42.412218, -83.907037 42.41156, -83.907343 42.411138, -83.907666 42.410535, -83.908175 42.409784, -83.908294 42.409504, -83.90861 42.408846, -83.9088 42.408187, -83.909076 42.407793, -83.909368 42.407415, -83.909622 42.407042, -83.909705 42.406756, -83.909407 42.40603, -83.909128 42.40576, -83.908864 42.405418, -83.908622 42.40512, -83.90841 42.404697, -83.908214 42.40419, -83.9081 42.403498, -83.908104 42.402981, -83.908254 42.402718, -83.90844 42.402603, -83.908552 42.402433, -83.908628 42.402142, -83.908741 42.401868, -83.90874 42.401729, -83.908616 42.40157, -83.908551 42.401488, -83.908382 42.401553, -83.908018 42.40173, -83.907739 42.402034, -83.907538 42.402491, -83.907535 42.402913, -83.907655 42.403523, -83.9078 42.404057, -83.907813 42.40426, -83.90773 42.404568, -83.90781 42.404755, -83.907852 42.405101, -83.908057 42.405371, -83.908388 42.405603, -83.908691 42.405945, -83.909005 42.40632, -83.909046 42.406814, -83.908812 42.407434, -83.90855 42.407835, -83.90828 42.408378, -83.907995 42.408965, -83.907753 42.409535, -83.907463 42.409935, -83.907291 42.410253, -83.907019 42.410774, -83.906772 42.411262, -83.906391 42.411767, -83.906278 42.412096, -83.906083 42.412595, -83.906057 42.413024, -83.906095 42.413859, -83.906211 42.41414, -83.906556 42.414465, -83.906925 42.414857, -83.906818 42.415246, -83.906653 42.415526, -83.906333 42.415844, -83.906027 42.416194, -83.905212 42.416909, -83.905204 42.417119, -83.905197 42.417312, -83.905066 42.417747, -83.904832 42.418031, -83.904581 42.418333, -83.904554 42.418769, -83.904355 42.418983, -83.904429 42.419135, -83.905083 42.419284, -83.905319 42.41927, -83.905323 42.419347, -83.905327 42.419421, -83.905055 42.419325, -83.904528 42.419235, -83.904062 42.41935, -83.903819 42.41955, -83.903 42.419656, -83.902465 42.419795, -83.901951 42.420102, -83.901876 42.420179, -83.901956 42.420289, -83.902258 42.42045, -83.90249 42.420616, -83.902604 42.420698, -83.902765 42.420924, -83.903099 42.421221, -83.90311 42.421516, -83.902779 42.421914, -83.902413 42.422206, -83.902166 42.422179, -83.902022 42.422164, -83.901701 42.421995, -83.90157 42.421833, -83.901461 42.421732, -83.901282 42.421853, -83.901262 42.422214, -83.901411 42.422554, -83.901348 42.422854, -83.901456 42.423161, -83.901645 42.423701, -83.902056 42.42431, -83.902205 42.42465, -83.90192 42.424993, -83.901563 42.425349, -83.901389 42.425513, -83.90109 42.425719, -83.901271 42.42588, -83.901285 42.426011, -83.901179 42.426253, -83.901179 42.426346, -83.901211 42.42644, -83.901403 42.426517, -83.901592 42.426548, -83.901947 42.426658, -83.90194 42.426872, -83.90136 42.427995, -83.901267 42.428059, -83.901163 42.428076, -83.900919 42.42796, -83.900595 42.427751, -83.900441 42.427563, -83.900345 42.427563, -83.900219 42.427606, -83.900122 42.427641, -83.899972 42.42776, -83.899712 42.427959, -83.899571 42.428008, -83.899119 42.428034, -83.898666 42.428187, -83.898283 42.428234, -83.897891 42.428211, -83.89752 42.428282, -83.897217 42.428165, -83.897003 42.428126, -83.896494 42.428196, -83.89635 42.428163, -83.896236 42.428036, -83.896014 42.427937, -83.895711 42.427886, -83.895388 42.428001, -83.895045 42.428275, -83.894783 42.428697, -83.89402 42.429547, -83.89373 42.429749, -83.89347 42.429843, -83.89311 42.429973, -83.89274 42.429966, -83.892285 42.429872, -83.891858 42.429681, -83.891442 42.429495, -83.891001 42.429279, -83.890665 42.429114, -83.890583 42.429074, -83.888705 42.427678, -83.888379 42.42765, -83.887975 42.427665, -83.887675 42.427648, -83.887154 42.427499, -83.886849 42.42724, -83.886771 42.427254, -83.886425 42.427323, -83.886155 42.427342, -83.88556 42.427162, -83.885493 42.427244, -83.88542 42.427547, -83.885524 42.427981, -83.885634 42.428073, -83.88626 42.428161, -83.886633 42.428295, -83.886972 42.42855, -83.886878 42.428743, -83.88672 42.428915, -83.886686 42.429103, -83.886814 42.43019, -83.88676 42.43025, -83.886639 42.430252, -83.88657 42.430236, -83.886419 42.429111, -83.886375 42.428561, -83.886095 42.428327, -83.885381 42.428251, -83.885241 42.428104, -83.885298 42.427863, -83.885288 42.427485, -83.885316 42.427162, -83.885446 42.426931, -83.885418 42.426786, -83.885165 42.426569, -83.884738 42.426753, -83.884214 42.42719, -83.8837 42.427488, -83.883189 42.427708, -83.882796 42.427865, -83.88236 42.428136, -83.881964 42.428508, -83.881772 42.42895, -83.881487 42.429326, -83.881073 42.429721, -83.880635 42.429823, -83.880288 42.430063, -83.879942 42.430424, -83.879969 42.430588, -83.880024 42.430709, -83.879938 42.430847, -83.879718 42.43097, -83.879496 42.430989, -83.879377 42.431083, -83.879509 42.43128, -83.878766 42.43173, -83.878672 42.431788, -83.878511 42.431855, -83.878498 42.432265, -83.878479 42.432326, -83.878476 42.433173, -83.878436 42.433486, -83.878353 42.433711, -83.878306 42.434084, -83.878349 42.434249, -83.878649 42.434531, -83.878846 42.435004, -83.878993 42.435147, -83.879126 42.435219, -83.879489 42.435209, -83.880139 42.435079, -83.880375 42.435091, -83.880859 42.435257, -83.881153 42.435549, -83.881162 42.435605, -83.881193 42.435797, -83.881139 42.436016, -83.881025 42.436263, -83.880884 42.436367, -83.88052 42.43652, -83.880326 42.436679, -83.880309 42.437426, -83.880182 42.437563, -83.879661 42.437803, -83.879312 42.438017, -83.879163 42.438027, -83.879076 42.437824, -83.879212 42.43775, -83.879634 42.437518, -83.879802 42.437315, -83.879726 42.437188, -83.879734 42.437062, -83.879835 42.436925, -83.879872 42.436854, -83.879978 42.436656, -83.879992 42.436605, -83.880016 42.436513, -83.879987 42.436365, -83.879847 42.436348, -83.87943 42.436583, -83.878959 42.436642, -83.878577 42.4368, -83.878131 42.436914, -83.87799 42.436974, -83.877729 42.437122, -83.877688 42.437188, -83.877799 42.437254, -83.877828 42.437336, -83.877775 42.437457, -83.877611 42.437616, -83.877391 42.437747, -83.877168 42.437796, -83.877013 42.437757, -83.876895 42.437658, -83.8769 42.437504, -83.877303 42.43711, -83.877286 42.436977, -83.877176 42.436867, -83.876917 42.436707, -83.876688 42.436278, -83.876696 42.436118, -83.876794 42.435904, -83.876776 42.435794, -83.876717 42.435761, -83.876561 42.435766, -83.876473 42.435651, -83.876291 42.435271, -83.876255 42.435139, -83.876179 42.435056, -83.875709 42.434899, -83.875549 42.434823, -83.875399 42.434743, -83.875228 42.43455, -83.875164 42.434406, -83.87508 42.434244, -83.874995 42.433848, -83.874948 42.433803, -83.874898 42.433802, -83.874848 42.43382, -83.874812 42.433866, -83.874805 42.433947, -83.874772 42.43403, -83.87471 42.434102, -83.87461 42.434162, -83.874525 42.434193, -83.874411 42.434217, -83.874308 42.4342, -83.874218 42.434228, -83.874106 42.434279, -83.874055 42.434364, -83.87396 42.434402, -83.873929 42.434453, -83.873941 42.434519, -83.873974 42.434574, -83.874058 42.434572, -83.874178 42.434676, -83.874457 42.434861, -83.874735 42.435096, -83.874754 42.435257, -83.874879 42.435387, -83.874707 42.435639, -83.874731 42.435903, -83.874541 42.436441, -83.874436 42.436628, -83.873825 42.437015, -83.873534 42.437284, -83.873251 42.437568, -83.872817 42.438115, -83.872739 42.438457, -83.872781 42.438765, -83.872983 42.439535, -83.872906 42.43981, -83.872816 42.440027, -83.872695 42.440304, -83.872739 42.440414, -83.872894 42.440442, -83.873042 42.440503, -83.873121 42.44074, -83.873138 42.440902, -83.873156 42.441059, -83.873325 42.441246, -83.873444 42.441291, -83.87365 42.441314, -83.874348 42.441135, -83.874446 42.441048, -83.874461 42.440943, -83.874596 42.440784, -83.874692 42.440752, -83.874833 42.440796, -83.87491 42.440868, -83.874861 42.440967, -83.874682 42.441137, -83.874563 42.441213, -83.874348 42.441289, -83.873902 42.441381, -83.873761 42.441435, -83.873581 42.441594, -83.873454 42.441786, -83.873081 42.442164, -83.872641 42.442459, -83.871516 42.442855, -83.871352 42.442943, -83.871195 42.443074, -83.871075 42.443222, -83.870985 42.443403, -83.870938 42.443639, -83.870997 42.443815, -83.870928 42.444051, -83.870985 42.444283, -83.870905 42.445118, -83.870799 42.445332, -83.87056 42.445567, -83.870374 42.445698, -83.870195 42.445786, -83.869936 42.445856, -83.869418 42.445711, -83.869263 42.445628, -83.869138 42.445517, -83.869036 42.445297, -83.869023 42.445022, -83.869189 42.44472, -83.869473 42.444364, -83.869646 42.444079, -83.869554 42.443452, -83.869482 42.443221, -83.869334 42.442984, -83.869231 42.442951, -83.869069 42.4429, -83.868589 42.442843, -83.868316 42.442716, -83.867825 42.442411, -83.867478 42.442284, -83.866772 42.442099, -83.866235 42.44152, -83.866126 42.441305, -83.865668 42.440796, -83.86549 42.440659, -83.865313 42.440598, -83.864919 42.440701, -83.864681 42.440821, -83.864631 42.441068, -83.864709 42.441486, -83.864536 42.442761, -83.863977 42.443121, -83.863614 42.443164, -83.86227 42.443016, -83.862156 42.443004, -83.861091 42.442675, -83.860735 42.442685, -83.860274 42.442864, -83.858856 42.443259, -83.858689 42.443224, -83.858333 42.442801, -83.857861 42.442607, -83.857707 42.442386, -83.85753 42.44227, -83.85751 42.442077, -83.857534 42.441836, -83.857706 42.441556, -83.857833 42.441452, -83.858232 42.441576, -83.858824 42.441261, -83.858981 42.441106, -83.860527 42.440941, -83.860887 42.440903, -83.86102 42.440845, -83.861251 42.440749, -83.861457 42.44046, -83.861958 42.440341, -83.862063 42.440198, -83.861979 42.440065, -83.861384 42.439869, -83.861067 42.439598, -83.860976 42.439027, -83.861214 42.43854, -83.861728 42.43808, -83.862547 42.437999, -83.86335 42.438216, -83.864084 42.438525, -83.864223 42.43851, -83.864337 42.438498, -83.864308 42.438314, -83.863896 42.437968, -83.863916 42.437828, -83.864032 42.437276, -83.863963 42.437095, -83.863602 42.436875, -83.864205 42.436948, -83.864719 42.437011, -83.8646 42.437148, -83.864509 42.437401, -83.864493 42.437604, -83.864654 42.437781, -83.864821 42.438122, -83.864625 42.438567, -83.86469 42.438759, -83.864815 42.438919, -83.864646 42.439661, -83.864856 42.439807, -83.8651 42.439899, -83.865407 42.439933, -83.865542 42.439758, -83.865765 42.439621, -83.865855 42.439596, -83.865858 42.439522, -83.865863 42.439351, -83.865876 42.439186, -83.865888 42.439099, -83.865899 42.439024, -83.865918 42.438979, -83.865975 42.438874, -83.866027 42.438784, -83.866061 42.43873, -83.866083 42.438682, -83.866144 42.438556, -83.866227 42.438382, -83.866267 42.438322, -83.866363 42.437991, -83.866427 42.437889, -83.86654 42.437817, -83.866707 42.437544, -83.866844 42.437244, -83.866889 42.437146, -83.866946 42.436786, -83.866973 42.436414, -83.867003 42.436268, -83.867067 42.436123, -83.86717 42.435996, -83.867443 42.435659, -83.867892 42.435218, -83.867917 42.435125, -83.867911 42.435012, -83.867888 42.434903, -83.867431 42.434155, -83.867329 42.434044, -83.867215 42.434005, -83.86579 42.433717, -83.865689 42.433291, -83.865588 42.433015, -83.865378 42.432486, -83.865251 42.432228, -83.86512 42.432009, -83.865101 42.431923, -83.865135 42.431846, -83.865219 42.431743, -83.865407 42.431651, -83.865663 42.43156, -83.86585 42.431482, -83.865933 42.431397, -83.865949 42.431278, -83.865908 42.431044, -83.865679 42.427622, -83.864004 42.428148, -83.863867 42.428187, -83.863563 42.428286, -83.862213 42.428724, -83.860755 42.429188, -83.859631 42.429549, -83.857018 42.430399, -83.855634 42.43085, -83.854433 42.431254, -83.853636 42.431523, -83.853584 42.431542, -83.853473 42.431583, -83.853006 42.431754, -83.852656 42.431902, -83.852348 42.432083, -83.852064 42.432283, -83.851866 42.432434, -83.851692 42.432599, -83.851492 42.432806, -83.851361 42.432965, -83.851032 42.433574, -83.850792 42.434123, -83.850629 42.434455, -83.850514 42.434633, -83.850358 42.434845, -83.847953 42.437115, -83.847137 42.43783, -83.846838 42.438052, -83.846517 42.438221, -83.846182 42.438337, -83.845777 42.438423, -83.842318 42.438598, -83.84162 42.438681, -83.839369 42.438965, -83.838946 42.439018, -83.836986 42.439223, -83.836873 42.439235, -83.836307 42.439341, -83.835872 42.439472, -83.8352 42.439709, -83.834764 42.439921, -83.834629 42.439996, -83.834441 42.440101, -83.833804 42.440588, -83.833832 42.438714, -83.8339 42.434616, -83.828827 42.434616, -83.828819 42.438393, -83.827159 42.438394, -83.824867 42.438396, -83.823886 42.438346, -83.823389 42.438319, -83.823184 42.438375, -83.822957 42.438404, -83.821103 42.43848, -83.821057 42.438159, -83.82013 42.436044, -83.82003 42.435595, -83.819819 42.434485, -83.819572 42.432944, -83.819387 42.431785, -83.819293 42.431218, -83.818999 42.429641, -83.817779 42.429664, -83.816747 42.429684, -83.816025 42.429698, -83.814404 42.42973, -83.813561 42.429785, -83.812231 42.429912, -83.811841 42.429983, -83.810748 42.430108, -83.808994 42.430308, -83.806897 42.430549, -83.804534 42.43081, -83.80353 42.430939, -83.803029 42.430981, -83.802418 42.431048, -83.802285 42.431167, -83.802234 42.431312, -83.802217 42.43157, -83.802269 42.432256, -83.802273 42.432584, -83.802389 42.434439, -83.802495 42.435986, -83.799319 42.436041, -83.797604 42.431401, -83.797347 42.430848, -83.797109 42.43037, -83.79681 42.429739, -83.796698 42.429468, -83.796607 42.429209, -83.796398 42.428282, -83.79053 42.428505, -83.790881 42.428516, -83.791234 42.428596, -83.791543 42.428725, -83.791749 42.428847, -83.791942 42.429029, -83.792054 42.42917, -83.79214 42.429342, -83.792194 42.429534, -83.792235 42.429976, -83.792261 42.430595, -83.792305 42.430777, -83.792395 42.430985, -83.792471 42.431123, -83.792537 42.431191, -83.792638 42.431267, -83.792534 42.431394, -83.792354 42.431673, -83.792334 42.431825, -83.792345 42.43202, -83.792368 42.43224, -83.792486 42.433367, -83.792537 42.434363, -83.79029 42.434472, -83.79001 42.434738, -83.789825 42.435044, -83.789398 42.435165, -83.789008 42.435306, -83.788791 42.435559, -83.788792 42.435766, -83.788691 42.435883, -83.788136 42.435887, -83.788114 42.436109, -83.788047 42.436332, -83.788618 42.436649, -83.78928 42.437078, -83.791569 42.438828, -83.792821 42.439939, -83.792823 42.440109, -83.790828 42.438341, -83.790253 42.438432, -83.789891 42.438777, -83.79027 42.438991, -83.790236 42.439073, -83.789676 42.438822, -83.789543 42.438783, -83.789328 42.438842, -83.789145 42.438968, -83.789024 42.439115, -83.788927 42.439186, -83.788682 42.439229, -83.78836 42.439248, -83.788277 42.44247, -83.788177 42.444363, -83.788773 42.452406, -83.788847 42.455259, -83.788847 42.455713, -83.788963 42.455963, -83.788926 42.456067, -83.788902 42.456444, -83.788988 42.456662, -83.788918 42.457527, -83.788918 42.462064, -83.788994 42.462417, -83.789449 42.470574, -83.789772 42.474575, -83.789833 42.475445, -83.78984 42.475634, -83.789843 42.475724, -83.789848 42.475874, -83.78986 42.476517, -83.789844 42.476689, -83.789854 42.476877, -83.790119 42.477594, -83.789857 42.477697, -83.789564 42.477912, -83.788842 42.478627, -83.788502 42.479164, -83.788354 42.479637, -83.788284 42.480077, -83.788311 42.480442, -83.788412 42.480926, -83.78857 42.481419, -83.788792 42.481795, -83.789112 42.48231, -83.79003 42.484066, -83.790129 42.484447, -83.789937 42.484597, -83.789691 42.48506, -83.789646 42.485226, -83.789717 42.485299, -83.789792 42.485354, -83.789954 42.485391, -83.790283 42.485411, -83.790565 42.485415, -83.79073 42.487522, -83.790494 42.488955, -83.790565 42.489928, -83.791011 42.492828, -83.791083 42.492925, -83.79115 42.493016, -83.791171 42.494319, -83.791179 42.494638, -83.791182 42.49485, -83.791193 42.49516, -83.791193 42.495581, -83.791137 42.496001, -83.791194 42.49613, -83.791205 42.496157, -83.791228 42.496208, -83.79125 42.49626, -83.791263 42.496496, -83.791136 42.4966, -83.790908 42.496725, -83.790667 42.496768, -83.790305 42.496541, -83.789976 42.496444, -83.789576 42.496365, -83.789039 42.496304, -83.788782 42.496297, -83.788495 42.496285, -83.788328 42.496355, -83.787961 42.496771, -83.787937 42.497003, -83.788016 42.497246, -83.788159 42.497438, -83.788313 42.49757, -83.78837 42.497708, -83.788297 42.497971, -83.78815 42.498257, -83.788151 42.4988, -83.788039 42.498899, -83.787957 42.498936, -83.787693 42.499189, -83.786772 42.499308, -83.786262 42.499405, -83.785708 42.49959, -83.784652 42.500008, -83.784355 42.500091, -83.783545 42.500157, -83.78347 42.500068, -83.783346 42.499512, -83.783179 42.499502, -83.783098 42.499454, -83.783013 42.499392, -83.782972 42.499094, -83.782906 42.498923, -83.782387 42.498369, -83.782098 42.49939, -83.782107 42.499566, -83.782313 42.499953, -83.782318 42.500201, -83.780242 42.500332, -83.778903 42.50033, -83.777871 42.500332, -83.774034 42.500341, -83.772193 42.500346, -83.768313 42.500375, -83.768132 42.496277, -83.767988 42.493005, -83.767966 42.492497, -83.767909 42.491569, -83.767894 42.491334, -83.76786 42.490632, -83.767793 42.488849, -83.767777 42.487792, -83.767767 42.487662, -83.767668 42.486414, -83.767561 42.48457, -83.7675 42.483418, -83.767449 42.482438, -83.767413 42.481743, -83.767398 42.481098, -83.767266 42.479274, -83.767236 42.478812, -83.766685 42.47912, -83.76642 42.48046, -83.766338 42.480465, -83.76625 42.480393, -83.766267 42.480107, -83.766477 42.479113, -83.766256 42.479052, -83.764918 42.479386, -83.764508 42.479532, -83.7641 42.47964, -83.763418 42.479598, -83.762958 42.479602, -83.762719 42.479539, -83.762507 42.479484, -83.762072 42.479267, -83.761357 42.478758, -83.761188 42.478603, -83.760855 42.478541, -83.760033 42.478504, -83.759603 42.478535, -83.758805 42.478899, -83.758508 42.478991, -83.758382 42.479002, -83.758279 42.479012, -83.758087 42.47894, -83.757962 42.478797, -83.757861 42.478571, -83.757878 42.47834, -83.758018 42.478055, -83.75837 42.477567, -83.758653 42.477299, -83.758887 42.477133, -83.759079 42.476961, -83.759171 42.47673, -83.759056 42.475559, -83.75915 42.475004, -83.759112 42.474377, -83.759034 42.474025, -83.758897 42.473617, -83.758649 42.47322, -83.757924 42.472376, -83.757475 42.471967, -83.757186 42.471673, -83.756925 42.471409, -83.75644 42.471342, -83.756079 42.47125, -83.75574 42.471083, -83.755667 42.471025, -83.755414 42.470899, -83.755062 42.470722, -83.754796 42.470627, -83.754009 42.470843, -83.753356 42.470851, -83.752268 42.470658, -83.752038 42.470635, -83.751996 42.471234, -83.751862 42.471228, -83.751868 42.47064, -83.751617 42.470528, -83.751202 42.470515, -83.750816 42.470574, -83.750199 42.470768, -83.749909 42.470789, -83.749661 42.470797, -83.749346 42.470845, -83.749174 42.470932, -83.749084 42.471046, -83.749112 42.471288, -83.7493 42.471684, -83.749306 42.471783, -83.749269 42.471881, -83.749163 42.471991, -83.748978 42.472018, -83.748808 42.472, -83.748308 42.471871, -83.74819 42.471875, -83.748026 42.47194, -83.74774 42.472142, -83.747664 42.472197, -83.747522 42.472345, -83.747328 42.47247, -83.746844 42.472687, -83.746534 42.472861, -83.746355 42.473002, -83.745881 42.473285, -83.745762 42.473416, -83.745702 42.473504, -83.745782 42.473663, -83.746079 42.473966, -83.746358 42.474182, -83.74641 42.474253, -83.746507 42.474834, -83.746491 42.474987, -83.7464 42.475184, -83.746257 42.475387, -83.746219 42.475424, -83.746195 42.475307, -83.74614 42.475038, -83.745965 42.475387, -83.745684 42.475602, -83.745171 42.475979, -83.744659 42.475972, -83.743565 42.476142, -83.743083 42.476298, -83.74243 42.476697, -83.74232 42.476838, -83.74236 42.477068, -83.742541 42.477193, -83.742882 42.477297, -83.744516 42.477671, -83.74476 42.47776, -83.745108 42.477804, -83.745352 42.47788, -83.745496 42.4783, -83.745747 42.47906, -83.745784 42.479268, -83.745767 42.47946, -83.745772 42.47961, -83.745608 42.479822, -83.745362 42.480055, -83.745047 42.480336, -83.743895 42.481004, -83.743644 42.481123, -83.743536 42.481172, -83.743112 42.481737, -83.743019 42.481876, -83.742955 42.482191, -83.742881 42.482357, -83.742697 42.482739, -83.742583 42.482813, -83.742479 42.482813, -83.742287 42.482744, -83.742171 42.482754, -83.741904 42.482908, -83.741819 42.483085, -83.741927 42.483104, -83.742019 42.483119, -83.74215 42.483141, -83.742219 42.483177, -83.742243 42.483259, -83.74249 42.483376, -83.74298 42.48408, -83.743064 42.48453, -83.743082 42.484887, -83.74303 42.48504, -83.742888 42.485105, -83.742701 42.485242, -83.742532 42.485455, -83.742449 42.485591, -83.742448 42.485641, -83.742813 42.485873, -83.742938 42.485978, -83.743197 42.486012, -83.743287 42.485996, -83.743436 42.485898, -83.743577 42.485882, -83.743728 42.485998, -83.743779 42.48607, -83.743756 42.486119, -83.743463 42.486101, -83.743189 42.486127, -83.74313 42.486154, -83.743165 42.486308, -83.743334 42.486408, -83.743519 42.486436, -83.74365 42.486382, -83.743968 42.486433, -83.744329 42.486605, -83.744374 42.486665, -83.744387 42.486874, -83.744296 42.486994, -83.744251 42.48712, -83.744271 42.487235, -83.744633 42.487396, -83.744714 42.487539, -83.744827 42.488033, -83.744654 42.488274, -83.744645 42.488444, -83.744696 42.48857, -83.744784 42.488625, -83.744858 42.48862, -83.745088 42.488276, -83.745171 42.488106, -83.74529 42.488036, -83.74552 42.488037, -83.745728 42.488087, -83.745982 42.488215, -83.746159 42.488364, -83.746379 42.488612, -83.746843 42.488927, -83.746915 42.489108, -83.746832 42.489311, -83.74679 42.489771, -83.746602 42.490022, -83.746505 42.490088, -83.746327 42.490103, -83.746164 42.490042, -83.745842 42.490002, -83.745673 42.48988, -83.745859 42.489799, -83.745667 42.489666, -83.745415 42.489654, -83.745223 42.489681, -83.74493 42.489685, -83.744648 42.48965, -83.744492 42.48966, -83.744447 42.489704, -83.744424 42.489808, -83.744482 42.489913, -83.744644 42.490073, -83.744821 42.490211, -83.745049 42.490327, -83.745219 42.490394, -83.745519 42.490445, -83.745837 42.490446, -83.746097 42.49048, -83.746499 42.490658, -83.746742 42.490862, -83.74685 42.491203, -83.746796 42.491351, -83.746587 42.491492, -83.745914 42.491697, -83.745456 42.491881, -83.745226 42.491918, -83.744981 42.491912, -83.744479 42.491657, -83.744262 42.491579, -83.743988 42.491517, -83.743103 42.491397, -83.742993 42.491325, -83.742696 42.491253, -83.742329 42.490922, -83.742214 42.49091, -83.742021 42.490931, -83.741879 42.491018, -83.741797 42.491138, -83.741889 42.491512, -83.741984 42.491693, -83.742323 42.492179, -83.742342 42.492205, -83.742375 42.492309, -83.742232 42.492863, -83.74202 42.49313, -83.741701 42.493545, -83.741461 42.493764, -83.740767 42.494215, -83.74048 42.494378, -83.740287 42.494449, -83.740019 42.494485, -83.739674 42.494339, -83.739284 42.494175, -83.739047 42.494157, -83.739011 42.494047, -83.738858 42.493904, -83.738463 42.493677, -83.738392 42.493441, -83.738296 42.493391, -83.738081 42.493351, -83.737814 42.493339, -83.737606 42.493382, -83.737252 42.493566, -83.736631 42.493673, -83.73655 42.493716, -83.736399 42.49394, -83.736352 42.494181, -83.736325 42.494653, -83.736279 42.494866, -83.736157 42.495085, -83.735999 42.495276, -83.735846 42.495391, -83.735147 42.495572, -83.734485 42.495471, -83.734165 42.495556, -83.734054 42.495578, -83.734037 42.495587, -83.733901 42.49567, -83.733802 42.495883, -83.733875 42.496048, -83.73404 42.496225, -83.734223 42.496505, -83.734228 42.496698, -83.734081 42.496889, -83.733857 42.497003, -83.733545 42.4971, -83.733217 42.497301, -83.733079 42.497328, -83.732937 42.49742, -83.732817 42.497578, -83.732868 42.497765, -83.732814 42.49793, -83.732656 42.498099, -83.73255 42.498301, -83.73237 42.498459, -83.732087 42.498326, -83.732028 42.498205, -83.731918 42.498133, -83.731859 42.498051, -83.732192 42.4978, -83.732208 42.497652, -83.731938 42.49759, -83.731188 42.497762, -83.729995 42.498348, -83.729517 42.498663, -83.72935 42.498695, -83.729106 42.49853, -83.728967 42.498364, -83.72888 42.498199, -83.728726 42.498029, -83.727971 42.497547, -83.727564 42.497424, -83.727029 42.497147, -83.726572 42.496871, -83.725746 42.49679, -83.725528 42.497035, -83.725826 42.497657, -83.725861 42.497893, -83.725777 42.498106, -83.725537 42.498341, -83.725265 42.498509, -83.724855 42.498688, -83.724284 42.499025, -83.72409 42.499172, -83.723874 42.499215, -83.723378 42.499185, -83.723175 42.499085, -83.723006 42.498952, -83.722956 42.498788, -83.723016 42.498607, -83.722981 42.498464, -83.722856 42.49843, -83.722507 42.498401, -83.722062 42.498404, -83.721815 42.498332, -83.721734 42.498254, -83.721615 42.498243, -83.721521 42.498719, -83.721319 42.498943, -83.721019 42.499161, -83.720785 42.499209, -83.720541 42.499142, -83.720312 42.499108, -83.720115 42.499129, -83.719639 42.499236, -83.719511 42.499378, -83.719621 42.499554, -83.719605 42.499696, -83.719522 42.499789, -83.719313 42.499945, -83.719078 42.500047, -83.718566 42.500034, -83.718343 42.500133, -83.718187 42.500165, -83.71809 42.500225, -83.718143 42.500405, -83.718221 42.500488, -83.718301 42.500653, -83.718277 42.500784, -83.718164 42.50091, -83.717576 42.501116, -83.717412 42.501203, -83.716688 42.501721, -83.716551 42.501957, -83.716564 42.50216, -83.716488 42.50239, -83.716305 42.502488, -83.716115 42.502531, -83.715704 42.502469, -83.715046 42.502262, -83.714511 42.502011, -83.714107 42.501976, -83.713206 42.501993, -83.712905 42.502008, -83.712448 42.502069, -83.71224 42.502131, -83.711875 42.502283, -83.711639 42.502441, -83.711343 42.503104, -83.711074 42.503323, -83.710687 42.503502, -83.710477 42.503732, -83.710268 42.5038, -83.710424 42.504171, -83.710382 42.504286, -83.710418 42.504347, -83.710996 42.504394, -83.711322 42.504462, -83.711899 42.504619, -83.712165 42.50473, -83.712378 42.5049, -83.713636 42.505518, -83.71768 42.505415, -83.719199 42.506254, -83.72026 42.515628, -83.719722 42.515602, -83.715614 42.515443, -83.714838 42.515481, -83.713753 42.515661, -83.709455 42.51657, -83.720178 42.51618, -83.71981 42.518848, -83.719844 42.51951, -83.7199 42.520147, -83.719931 42.521412, -83.720013 42.522352, -83.720014 42.522554, -83.720064 42.523506, -83.719301 42.523545, -83.718914 42.523532, -83.718514 42.523485, -83.716906 42.523044, -83.7163 42.522895, -83.715825 42.522843, -83.714584 42.522736, -83.700659 42.523225, -83.698944 42.523265, -83.697117 42.523208, -83.695737 42.523079, -83.694969 42.522989, -83.694298 42.522886, -83.692882 42.522611, -83.692376 42.522471, -83.692364 42.522558, -83.692025 42.522532, -83.691707 42.522563, -83.691425 42.522643, -83.69114 42.522755, -83.690722 42.523006, -83.690559 42.523103, -83.690364 42.523193, -83.690174 42.523251, -83.68994 42.523288, -83.688985 42.523298, -83.689132 42.523713, -83.689343 42.523997, -83.689593 42.524216, -83.690066 42.524611, -83.690509 42.524993, -83.690735 42.525281, -83.690871 42.525565, -83.690963 42.525867, -83.691076 42.52636, -83.691125 42.52677, -83.691149 42.527448, -83.691204 42.528279, -83.691387 42.530134, -83.691455 42.531059, -83.691469 42.531234, -83.691711 42.534259, -83.691924 42.537182, -83.692107 42.539694, -83.697687 42.539526, -83.699003 42.53947, -83.702273 42.539359, -83.703432 42.539328, -83.704504 42.539284, -83.709044 42.539182, -83.708979 42.538665, -83.708912 42.537926, -83.708815 42.536958, -83.708717 42.536086, -83.708639 42.53534, -83.708574 42.534524, -83.708581 42.53427, -83.708923 42.53392, -83.709058 42.533835, -83.709125 42.533707, -83.709215 42.533054, -83.709316 42.532687, -83.711133 42.532721, -83.713684 42.539015, -83.714033 42.538996, -83.715456 42.538926, -83.714785 42.541107, -83.71067 42.54351, -83.711936 42.543648, -83.711973 42.54529, -83.711998 42.546366, -83.711989 42.546721, -83.71208 42.547631, -83.712257 42.549427, -83.712288 42.550011, -83.712516 42.553725, -83.708953 42.553842, -83.703571 42.55402, -83.700296 42.554118, -83.699954 42.554123, -83.699511 42.554218, -83.699273 42.554285, -83.698966 42.554403, -83.698701 42.554584, -83.698394 42.554839, -83.698203 42.555088, -83.698099 42.555297, -83.698012 42.555686, -83.698294 42.559336, -83.698257 42.559573, -83.698144 42.559834, -83.697922 42.560085, -83.698459 42.560522, -83.698517 42.560689, -83.698548 42.560995, -83.69854 42.561321, -83.706439 42.561024, -83.706671 42.561142, -83.706835 42.561149, -83.707256 42.561003, -83.707473 42.560972, -83.708987 42.563942, -83.709126 42.563893, -83.709693 42.563632, -83.710013 42.563546, -83.710199 42.563552, -83.710486 42.563741, -83.710939 42.56376, -83.71093 42.563897, -83.711102 42.563922, -83.710608 42.564628, -83.708901 42.566923, -83.707914 42.56838, -83.706928 42.569838, -83.709295 42.569485, -83.71049 42.569357, -83.710763 42.569339, -83.712666 42.569273, -83.713161 42.569269, -83.713727 42.569242, -83.71481 42.569161, -83.7153 42.569077, -83.716182 42.568846, -83.716993 42.568588, -83.71734 42.568486, -83.718419 42.56824, -83.718643 42.568206, -83.719799 42.568184, -83.720065 42.568171, -83.720904 42.568153, -83.722146 42.568108, -83.724549 42.568029, -83.72577 42.567973, -83.726826 42.567932, -83.728031 42.567896, -83.728065 42.567895, -83.729819 42.567855, -83.73021 42.567841, -83.731909 42.567787, -83.732184 42.56773, -83.732455 42.567614, -83.73266 42.567457, -83.732888 42.567097, -83.732947 42.566827, -83.733004 42.567225, -83.733078 42.567819, -83.733397 42.567889, -83.733929 42.56793, -83.734489 42.567934, -83.73493 42.56782, -83.735107 42.567794, -83.735338 42.567789, -83.735525 42.567827, -83.73576 42.567926, -83.735945 42.568078, -83.736043 42.568275, -83.73635 42.570134, -83.737629 42.571121, -83.737956 42.57112, -83.737953 42.571083, -83.738182 42.571087, -83.738358 42.571083, -83.7393 42.571133, -83.739855 42.571164, -83.740966 42.571224, -83.740944 42.57166, -83.740865 42.572413, -83.740718 42.573414, -83.740391 42.574898, -83.740098 42.575831, -83.73988 42.576428, -83.738525 42.579707, -83.738157 42.580616, -83.738004 42.581059, -83.737681 42.582305, -83.738041 42.582295, -83.739513 42.582241, -83.74084 42.582187, -83.742698 42.582125, -83.743916 42.582107, -83.744257 42.582132, -83.74458 42.58221, -83.745711 42.58257, -83.746068 42.582641, -83.746836 42.582707, -83.747521 42.582757, -83.747882 42.582757, -83.748239 42.582677, -83.749154 42.582316, -83.749936 42.582037, -83.750426 42.581904, -83.750932 42.581832, -83.752779 42.581774, -83.752873 42.58177, -83.753453 42.581762)))"} -{"geo_id":"80362","urban_area_code":"80362","name":"Seaside--Monterey, CA","lsad_name":"Seaside--Monterey, CA Urbanized Area","area_lsad_code":"75","mtfcc_feature_class_code":"G3500","type":"U","functional_status":"S","area_land_meters":100229204,"area_water_meters":586335,"internal_point_lon":-121.8454871,"internal_point_lat":36.6133896,"internal_point_geom":"POINT(-121.8454871 36.6133896)","urban_area_geom":"MULTIPOLYGON(((-121.94797 36.572293, -121.948658 36.572733, -121.948778 36.572795, -121.949008 36.572913, -121.94973 36.57334, -121.949812 36.573238, -121.950001 36.573088, -121.950222 36.572986, -121.950559 36.572878, -121.950885 36.572797, -121.951144 36.572706, -121.951379 36.572589, -121.951595 36.572505, -121.951697 36.572466, -121.95225 36.572064, -121.95249 36.571974, -121.952713 36.571874, -121.953042 36.571664, -121.953794 36.571144, -121.953837 36.571121, -121.954778 36.570631, -121.955015 36.570462, -121.955333 36.570284, -121.955492 36.570235, -121.955846 36.570068, -121.956265 36.569841, -121.95659 36.569702, -121.956782 36.569593, -121.956985 36.569513, -121.957277 36.569399, -121.957595 36.569245, -121.957898 36.569071, -121.958075 36.568957, -121.958313 36.568741, -121.958701 36.56827, -121.958874 36.567936, -121.95805 36.56741, -121.956957 36.567023, -121.956935 36.566988, -121.95671 36.5665, -121.956384 36.566261, -121.956041 36.566097, -121.955984 36.566053, -121.955943 36.565999, -121.955906 36.565396, -121.955861 36.56522, -121.955857 36.564762, -121.955861 36.564521, -121.955593 36.564546, -121.955195 36.56466, -121.955026 36.564764, -121.954873 36.564884, -121.954723 36.565039, -121.954588 36.565229, -121.953395 36.566548, -121.953595 36.566743, -121.953637 36.56681, -121.953715 36.566975, -121.953759 36.567149, -121.953823 36.568336, -121.953819 36.568581, -121.953809 36.56914, -121.953517 36.569103, -121.953389 36.569069, -121.953342 36.569053, -121.953267 36.569026, -121.953133 36.568964, -121.95307 36.568921, -121.952953 36.568809, -121.952891 36.568689, -121.952826 36.568531, -121.952771 36.568341, -121.952622 36.568162, -121.952521 36.568092, -121.952311 36.568034, -121.952154 36.568129, -121.951921 36.568215, -121.951716 36.56826, -121.95164 36.568268, -121.951442 36.568288, -121.951149 36.568313, -121.951092 36.568327, -121.951042 36.568351, -121.950979 36.568411, -121.950951 36.568483, -121.950986 36.568742, -121.951002 36.568865, -121.950992 36.568928, -121.95096 36.568985, -121.950881 36.569051, -121.950574 36.569185, -121.950271 36.569353, -121.950106 36.569542, -121.950055 36.569581, -121.949996 36.56966, -121.949948 36.569724, -121.949875 36.569915, -121.94974 36.570135, -121.949607 36.570351, -121.94959 36.570375, -121.949455 36.570576, -121.94919 36.570911, -121.948895 36.571212, -121.948832 36.571307, -121.948756 36.57148, -121.948723 36.571621, -121.948706 36.571734, -121.948706 36.57203, -121.948674 36.572106, -121.948651 36.572134, -121.948585 36.572196, -121.948504 36.57224, -121.948329 36.572278, -121.9481 36.572292, -121.94797 36.572293)), ((-121.842263 36.630059, -121.849563 36.621559, -121.857663 36.613559, -121.85767 36.613427, -121.857677 36.613305, -121.858214 36.612738, -121.858557 36.612376, -121.858663 36.612459, -121.858844 36.612335, -121.859098 36.612162, -121.860604 36.611136, -121.865427 36.607339, -121.866494 36.606498, -121.867965 36.60585, -121.871601 36.604173, -121.87206 36.603952, -121.875793 36.602511, -121.876324 36.602371, -121.877904 36.602091, -121.878884 36.601915, -121.879148 36.601884, -121.879773 36.60183, -121.883216 36.601335, -121.883801 36.601288, -121.884655 36.601256, -121.886102 36.601315, -121.886764 36.601459, -121.888989 36.601749, -121.889583 36.601885, -121.88926 36.603684, -121.88938 36.603729, -121.889162 36.604893, -121.889237 36.605073, -121.88974 36.605877, -121.889876 36.605832, -121.889973 36.605787, -121.890003 36.60572, -121.88944 36.604728, -121.8895 36.604255, -121.889545 36.604262, -121.889568 36.604112, -121.88944 36.604097, -121.889506 36.603714, -121.889515 36.603669, -121.889538 36.603474, -121.889568 36.603474, -121.889643 36.603112, -121.889748 36.602617, -121.889815 36.602617, -121.889883 36.602287, -121.8898 36.602212, -121.889921 36.602227, -121.889958 36.601971, -121.890694 36.602054, -121.890807 36.602114, -121.890904 36.602204, -121.89113 36.602309, -121.891325 36.602377, -121.89125 36.602482, -121.892444 36.603015, -121.892564 36.603061, -121.892684 36.603121, -121.892797 36.603338, -121.892812 36.603496, -121.892752 36.603616, -121.892632 36.603744, -121.892602 36.603736, -121.892512 36.603849, -121.892692 36.603962, -121.892584 36.60413, -121.892647 36.604187, -121.892504 36.60439, -121.892422 36.604405, -121.892309 36.604713, -121.891933 36.604645, -121.891993 36.604472, -121.891911 36.60445, -121.891701 36.605051, -121.891813 36.605073, -121.891911 36.604705, -121.892091 36.604758, -121.891933 36.605156, -121.892031 36.605186, -121.892174 36.60478, -121.892437 36.604821, -121.892331 36.605186, -121.892347 36.605247, -121.892232 36.6055, -121.892339 36.605539, -121.892572 36.605471, -121.892624 36.605486, -121.892684 36.605321, -121.892639 36.605314, -121.892699 36.605186, -121.892775 36.605148, -121.892842 36.604946, -121.892827 36.60481, -121.893322 36.604062, -121.893413 36.604398, -121.893595 36.604733, -121.89371 36.604958, -121.893795 36.605123, -121.893902 36.605809, -121.893688 36.606946, -121.893856 36.607656, -121.894437 36.607984, -121.894765 36.608459, -121.894284 36.608509, -121.893929 36.608535, -121.893781 36.608547, -121.893773 36.608667, -121.893128 36.60866, -121.893128 36.60872, -121.893773 36.608727, -121.893766 36.608998, -121.893661 36.608975, -121.893668 36.608915, -121.893541 36.6089, -121.893518 36.608953, -121.89336 36.60893, -121.893338 36.608998, -121.893804 36.609073, -121.89381 36.609105, -121.892669 36.60914, -121.89113 36.608735, -121.891014 36.608789, -121.890799 36.608735, -121.890274 36.608585, -121.88968 36.608397, -121.88965 36.608487, -121.890146 36.60866, -121.890769 36.608832, -121.892226 36.609321, -121.892767 36.609486, -121.893285 36.609538, -121.894265 36.609559, -121.894929 36.609988, -121.895266 36.610206, -121.895965 36.610659, -121.897065 36.611759, -121.896865 36.613959, -121.898965 36.615559, -121.899265 36.617059, -121.900865 36.617659, -121.900865 36.618759, -121.902065 36.618459, -121.902107 36.618497, -121.90322 36.619517, -121.903265 36.619559, -121.903765 36.621759, -121.905165 36.621859, -121.905565 36.620759, -121.906095 36.620841, -121.909465 36.621359, -121.911665 36.623359, -121.913865 36.623159, -121.914765 36.624259, -121.915887 36.625208, -121.916065 36.625359, -121.915865 36.625859, -121.914965 36.626959, -121.916566 36.626659, -121.920366 36.628059, -121.920866 36.628959, -121.921366 36.629659, -121.921366 36.630359, -121.921502 36.632229, -121.923959 36.634481, -121.925382 36.634961, -121.925866 36.635259, -121.926366 36.635359, -121.927466 36.635759, -121.929666 36.636959, -121.930366 36.637059, -121.930437 36.637066, -121.932524 36.637265, -121.934751 36.637418, -121.93643 36.636746, -121.937266 36.635659, -121.938466 36.634023, -121.938551 36.633908, -121.938666 36.632659, -121.937366 36.630159, -121.938066 36.627259, -121.940266 36.626959, -121.940466 36.623759, -121.941317 36.622686, -121.941666 36.620559, -121.941666 36.618059, -121.940175 36.617046, -121.939003 36.617729, -121.938837 36.617556, -121.938443 36.617107, -121.937938 36.616553, -121.937786 36.616404, -121.937691 36.616326, -121.937608 36.616274, -121.937492 36.616218, -121.937337 36.616162, -121.937173 36.616122, -121.936938 36.616087, -121.936701 36.616066, -121.93653 36.616061, -121.936401 36.616069, -121.936366 36.615759, -121.937018 36.615117, -121.939274 36.614572, -121.939492 36.612855, -121.939456 36.612852, -121.939325 36.612826, -121.939222 36.61279, -121.939068 36.612708, -121.938952 36.612608, -121.938846 36.612449, -121.938659 36.612172, -121.938599 36.612103, -121.938528 36.61204, -121.93842 36.611971, -121.938299 36.611919, -121.938219 36.611895, -121.9381 36.611873, -121.937922 36.611839, -121.937755 36.611796, -121.93751 36.611709, -121.937247 36.611571, -121.93753 36.611247, -121.937603 36.611134, -121.937642 36.611088, -121.937727 36.61101, -121.937813 36.610924, -121.937966 36.610843, -121.938169 36.610789, -121.938278 36.610783, -121.938431 36.610782, -121.93849 36.610812, -121.938792 36.610816, -121.938965 36.610791, -121.939113 36.610742, -121.939525 36.610539, -121.940577 36.610005, -121.941433 36.609525, -121.941559 36.609426, -121.941654 36.609351, -121.941872 36.608977, -121.941949 36.608852, -121.94202 36.608566, -121.942068 36.60845, -121.942167 36.608319, -121.942398 36.608156, -121.942708 36.60793, -121.942961 36.607733, -121.943108 36.607526, -121.943241 36.607151, -121.943349 36.607041, -121.943591 36.606813, -121.944446 36.606256, -121.944614 36.606108, -121.944758 36.605968, -121.944883 36.605822, -121.944993 36.60572, -121.945521 36.605201, -121.945609 36.605048, -121.945626 36.604878, -121.945594 36.604769, -121.945444 36.604489, -121.945267 36.604107, -121.945214 36.603951, -121.945134 36.603686, -121.945226 36.603667, -121.94547 36.603617, -121.945764 36.603547, -121.946265 36.603761, -121.946484 36.603872, -121.946652 36.603997, -121.946814 36.604128, -121.946969 36.604301, -121.947066 36.604402, -121.947268 36.60459, -121.947392 36.604681, -121.947599 36.604795, -121.94783 36.604884, -121.948098 36.604944, -121.94832 36.604926, -121.948475 36.604895, -121.948633 36.604666, -121.948822 36.604362, -121.949003 36.604201, -121.950048 36.604737, -121.950434 36.604942, -121.950603 36.605075, -121.950927 36.605347, -121.951201 36.605564, -121.952013 36.606312, -121.952558 36.60597, -121.952788 36.605789, -121.953238 36.605543, -121.953355 36.605423, -121.95341 36.605247, -121.953404 36.605101, -121.953291 36.604828, -121.953196 36.604662, -121.953054 36.604137, -121.95294 36.603908, -121.952912 36.603783, -121.952606 36.603148, -121.952503 36.602997, -121.952427 36.602946, -121.952543 36.602821, -121.952741 36.602608, -121.952943 36.602392, -121.953422 36.601952, -121.95358 36.601807, -121.953694 36.601703, -121.954043 36.601985, -121.954248 36.602295, -121.954381 36.602605, -121.954431 36.602767, -121.954466 36.602881, -121.954519 36.602971, -121.954574 36.603064, -121.954786 36.603216, -121.955015 36.603263, -121.95504 36.603258, -121.955149 36.603233, -121.955319 36.603133, -121.955595 36.602926, -121.955883 36.602708, -121.956355 36.602267, -121.957212 36.601476, -121.957332 36.601116, -121.957395 36.600854, -121.95733 36.600362, -121.957421 36.600021, -121.957477 36.599843, -121.957594 36.599534, -121.957703 36.599337, -121.957631 36.599183, -121.957745 36.598629, -121.957787 36.598135, -121.957828 36.597994, -121.957864 36.597788, -121.957891 36.597544, -121.957813 36.597338, -121.957678 36.597055, -121.957632 36.596966, -121.957523 36.596756, -121.957236 36.596458, -121.957093 36.596393, -121.956147 36.5957, -121.955967 36.595554, -121.955742 36.595371, -121.955595 36.595286, -121.955484 36.59527, -121.955379 36.595315, -121.954643 36.595917, -121.954536 36.596005, -121.954085 36.59631, -121.953595 36.59662, -121.953133 36.596849, -121.952874 36.596957, -121.952743 36.597012, -121.952387 36.597022, -121.952339 36.597087, -121.95216 36.596983, -121.952112 36.596951, -121.952041 36.596905, -121.951904 36.596752, -121.951806 36.59651, -121.951556 36.595835, -121.951482 36.595613, -121.951983 36.595119, -121.952524 36.594467, -121.952605 36.594474, -121.952812 36.594482, -121.95305 36.594476, -121.953236 36.594443, -121.953478 36.594373, -121.953572 36.594317, -121.953752 36.594208, -121.953917 36.59404, -121.954225 36.59393, -121.954391 36.593859, -121.954615 36.593706, -121.954789 36.593567, -121.954952 36.593418, -121.955077 36.593283, -121.955135 36.593221, -121.955581 36.59289, -121.95579 36.592907, -121.955999 36.592913, -121.956221 36.592906, -121.956396 36.592889, -121.956685 36.592841, -121.95697 36.59277, -121.95722 36.592736, -121.957551 36.59267, -121.957702 36.592621, -121.957934 36.592582, -121.958101 36.592519, -121.958262 36.592411, -121.958397 36.59229, -121.958521 36.592138, -121.958607 36.591991, -121.958624 36.591952, -121.958635 36.591874, -121.958651 36.591663, -121.958644 36.591452, -121.958614 36.591242, -121.958586 36.591117, -121.958487 36.590892, -121.958341 36.590563, -121.958307 36.590462, -121.958728 36.590314, -121.958947 36.590239, -121.959079 36.590217, -121.959454 36.590212, -121.959965 36.59034, -121.960365 36.59046, -121.960713 36.590524, -121.960922 36.590547, -121.961486 36.590552, -121.961835 36.59053, -121.962113 36.590542, -121.962312 36.590566, -121.962521 36.590607, -121.962948 36.590734, -121.96286 36.590435, -121.962187 36.590438, -121.961274 36.590456, -121.960511 36.590348, -121.960102 36.590251, -121.959796 36.590252, -121.959382 36.589981, -121.95921 36.589876, -121.958587 36.589811, -121.957824 36.590017, -121.957467 36.59016, -121.956557 36.590094, -121.955779 36.590002, -121.954967 36.58976, -121.954867 36.58976, -121.954633 36.589615, -121.954473 36.589513, -121.954242 36.589387, -121.953925 36.589214, -121.953583 36.588919, -121.952743 36.588397, -121.952335 36.588043, -121.95253 36.587888, -121.952599 36.587824, -121.952714 36.587717, -121.952824 36.587606, -121.952925 36.587457, -121.953058 36.587192, -121.953091 36.586814, -121.953166 36.586598, -121.953241 36.586481, -121.953367 36.586373, -121.95357 36.586297, -121.954965 36.586103, -121.955077 36.586066, -121.955237 36.586013, -121.955355 36.585908, -121.955537 36.585654, -121.955599 36.585328, -121.955632 36.585007, -121.955659 36.584796, -121.955699 36.584581, -121.955806 36.584379, -121.955813 36.584365, -121.955839 36.584315, -121.955986 36.584134, -121.95635 36.583749, -121.956692 36.583374, -121.956908 36.583183, -121.95754 36.582561, -121.958148 36.58188, -121.958172 36.581853, -121.957762 36.581267, -121.957153 36.580697, -121.956945 36.580564, -121.956786 36.580501, -121.956587 36.580458, -121.956323 36.580323, -121.956193 36.580256, -121.955761 36.580505, -121.955277 36.580735, -121.954898 36.580892, -121.955251 36.580677, -121.955321 36.580594, -121.955358 36.580498, -121.95536 36.580399, -121.955329 36.580305, -121.955085 36.579978, -121.954953 36.580058, -121.954718 36.580198, -121.954464 36.580318, -121.954193 36.580417, -121.953891 36.580495, -121.953661 36.580522, -121.953428 36.580491, -121.952363 36.58012, -121.951512 36.579793, -121.951654 36.579595, -121.951759 36.579405, -121.951828 36.579258, -121.951854 36.579146, -121.951851 36.578959, -121.951818 36.577917, -121.951774 36.577555, -121.950933 36.576851, -121.950328 36.576476, -121.950018 36.576188, -121.949042 36.575051, -121.947987 36.573885, -121.947601 36.573431, -121.947208 36.573074, -121.947371 36.572969, -121.947897 36.572337, -121.94797 36.572293, -121.947692 36.572067, -121.947438 36.57181, -121.947414 36.571776, -121.947062 36.571472, -121.946896 36.571371, -121.946782 36.571326, -121.94666 36.571295, -121.946361 36.57127, -121.946185 36.571271, -121.946016 36.571289, -121.945676 36.571355, -121.945193 36.57147, -121.944896 36.571505, -121.944784 36.571546, -121.944552 36.57168, -121.944366 36.571749, -121.944162 36.571782, -121.943949 36.571777, -121.943736 36.571733, -121.943431 36.571576, -121.943327 36.571544, -121.943176 36.571513, -121.942813 36.57147, -121.942277 36.571347, -121.942762 36.571859, -121.943396 36.572421, -121.944355 36.573134, -121.945153 36.573797, -121.945041 36.574266, -121.944921 36.574564, -121.944627 36.575208, -121.944571 36.575377, -121.944547 36.575549, -121.944483 36.575676, -121.944093 36.575678, -121.943552 36.575632, -121.943012 36.575608, -121.942673 36.575598, -121.942489 36.575621, -121.942157 36.575689, -121.941997 36.575643, -121.941766 36.575453, -121.941567 36.575368, -121.941366 36.575348, -121.94104 36.575377, -121.940769 36.575459, -121.940504 36.575579, -121.940285 36.575694, -121.940148 36.575732, -121.940063 36.575728, -121.939995 36.575655, -121.939806 36.575273, -121.939676 36.575054, -121.9395 36.574937, -121.939036 36.574778, -121.938785 36.57463, -121.938508 36.574355, -121.938246 36.574128, -121.937971 36.57397, -121.937501 36.573921, -121.93742 36.573854, -121.937297 36.573739, -121.937183 36.573646, -121.936934 36.573586, -121.936861 36.573548, -121.93679 36.573471, -121.936743 36.573386, -121.936658 36.573209, -121.936619 36.573028, -121.936455 36.572865, -121.936181 36.572701, -121.935959 36.572603, -121.935637 36.572501, -121.935505 36.572432, -121.93535 36.572242, -121.935366 36.572075, -121.935319 36.571946, -121.935052 36.571713, -121.93476 36.571562, -121.934464 36.57139, -121.934168 36.571245, -121.933973 36.571102, -121.933602 36.570699, -121.933342 36.570563, -121.933115 36.570581, -121.932884 36.570713, -121.932702 36.57086, -121.932508 36.57114, -121.932352 36.571473, -121.932248 36.571527, -121.932162 36.571522, -121.93195 36.571433, -121.931779 36.571256, -121.931759 36.571042, -121.931771 36.570737, -121.93168 36.570528, -121.931471 36.570387, -121.931287 36.570398, -121.931008 36.570595, -121.930749 36.570797, -121.930595 36.570887, -121.930323 36.571007, -121.929995 36.571061, -121.929745 36.571078, -121.929578 36.571128, -121.928835 36.571542, -121.928533 36.571722, -121.928242 36.571882, -121.928092 36.572052, -121.927905 36.57224, -121.927678 36.572342, -121.927339 36.57241, -121.927059 36.572413, -121.927021 36.572281, -121.927063 36.572094, -121.927279 36.57178, -121.927453 36.571606, -121.927967 36.571251, -121.928179 36.571076, -121.928407 36.570685, -121.928559 36.570242, -121.928636 36.57005, -121.928935 36.57004, -121.929187 36.569944, -121.929428 36.569689, -121.929553 36.5695, -121.929588 36.569291, -121.929603 36.569046, -121.929441 36.568934, -121.928875 36.568798, -121.92884 36.56841, -121.928752 36.567757, -121.928716 36.567667, -121.928653 36.567589, -121.928626 36.567541, -121.928498 36.567418, -121.929331 36.56778, -121.929644 36.567879, -121.929798 36.567881, -121.92996 36.567848, -121.930199 36.567719, -121.930334 36.567483, -121.930396 36.567275, -121.930427 36.567111, -121.930519 36.566517, -121.9306 36.5664, -121.930701 36.566333, -121.93082 36.566326, -121.930944 36.566347, -121.931253 36.56645, -121.931434 36.566535, -121.931695 36.566804, -121.931777 36.566923, -121.931912 36.566976, -121.932111 36.566987, -121.932639 36.566904, -121.93278 36.56692, -121.932942 36.566963, -121.933105 36.56704, -121.933516 36.567216, -121.933839 36.567297, -121.934209 36.567395, -121.934439 36.567442, -121.934662 36.56746, -121.934915 36.56744, -121.935104 36.567328, -121.935303 36.567159, -121.935518 36.566885, -121.9356 36.566843, -121.935465 36.566596, -121.934799 36.56614, -121.933811 36.565334, -121.933528 36.56515, -121.93303 36.56478, -121.931637 36.563706, -121.931565 36.563589, -121.931374 36.563511, -121.931232 36.563417, -121.930878 36.563159, -121.930662 36.562995, -121.930211 36.562674, -121.9297 36.56229, -121.929255 36.561972, -121.928675 36.561532, -121.928077 36.561099, -121.927795 36.560987, -121.927504 36.560864, -121.927093 36.560684, -121.926857 36.560565, -121.926622 36.56041, -121.926584 36.560307, -121.926581 36.560233, -121.926609 36.560162, -121.926641 36.560096, -121.926705 36.560064, -121.926761 36.560044, -121.926848 36.560024, -121.926943 36.560024, -121.927055 36.560043, -121.927158 36.560065, -121.927282 36.560117, -121.927551 36.560258, -121.92769 36.560313, -121.927821 36.560347, -121.928008 36.56037, -121.928367 36.560368, -121.928571 36.560311, -121.928664 36.560271, -121.928737 36.560221, -121.928792 36.560167, -121.928829 36.560116, -121.928849 36.560062, -121.92884 36.559972, -121.928794 36.559791, -121.928789 36.559711, -121.928805 36.559563, -121.928852 36.559411, -121.928934 36.559146, -121.928946 36.558973, -121.928953 36.558926, -121.929559 36.558647, -121.931283 36.557853, -121.931866 36.55756, -121.931603 36.556925, -121.930844 36.555091, -121.930784 36.554945, -121.929394 36.548087, -121.929806 36.546043, -121.930371 36.545341, -121.930936 36.54515, -121.9315 36.545158, -121.931765 36.545061, -121.932309 36.54476, -121.93292 36.544813, -121.933532 36.544806, -121.933257 36.544248, -121.933608 36.543975, -121.933654 36.543471, -121.93315 36.54295, -121.933288 36.542222, -121.933059 36.541764, -121.933135 36.541284, -121.932906 36.540679, -121.93286 36.54023, -121.932953 36.540075, -121.932327 36.539791, -121.932388 36.539443, -121.931992 36.539478, -121.930571 36.539754, -121.930022 36.539394, -121.929334 36.538683, -121.928907 36.537842, -121.928451 36.536736, -121.928526 36.536534, -121.92844 36.535925, -121.928256 36.53511, -121.927951 36.533996, -121.927568 36.533553, -121.92734 36.533073, -121.926896 36.532333, -121.927079 36.531768, -121.926529 36.531226, -121.925995 36.530707, -121.925841 36.529838, -121.926024 36.529258, -121.926161 36.528693, -121.926039 36.528525, -121.926222 36.528075, -121.925657 36.527915, -121.925488 36.527404, -121.925665 36.527161, -121.925165 36.526261, -121.925119 36.525712, -121.926764 36.523722, -121.927056 36.522773, -121.927117 36.52247, -121.924623 36.524476, -121.924491 36.524583, -121.924108 36.524883, -121.923289 36.525544, -121.922745 36.525983, -121.921821 36.526728, -121.921471 36.527011, -121.920604 36.527704, -121.920507 36.527781, -121.920233 36.52801, -121.920135 36.528094, -121.919935 36.528268, -121.919508 36.52865, -121.919182 36.528954, -121.918973 36.529149, -121.918625 36.529471, -121.918086 36.530001, -121.917703 36.530439, -121.91753 36.530637, -121.917379 36.530794, -121.91707 36.531099, -121.916603 36.531675, -121.916374 36.531963, -121.916234 36.532138, -121.916111 36.532292, -121.915762 36.532729, -121.915686 36.532825, -121.915658 36.532859, -121.915217 36.533396, -121.914728 36.533957, -121.914334 36.534447, -121.914132 36.534654, -121.91309 36.535647, -121.912626 36.536109, -121.912539 36.53619, -121.912452 36.53627, -121.912409 36.536311, -121.912366 36.536351, -121.911803 36.536003, -121.911728 36.535957, -121.911007 36.53562, -121.910528 36.535352, -121.910297 36.53519, -121.909909 36.535032, -121.909456 36.534959, -121.909195 36.534951, -121.908799 36.535017, -121.908388 36.535167, -121.90796 36.535317, -121.907479 36.535441, -121.906996 36.535495, -121.906493 36.535507, -121.906236 36.535569, -121.905996 36.535645, -121.905651 36.535709, -121.905099 36.535779, -121.90477 36.535787, -121.90446 36.535864, -121.904032 36.536015, -121.90388 36.536103, -121.903484 36.536182, -121.902966 36.536237, -121.902307 36.536239, -121.901822 36.536279, -121.901511 36.536286, -121.901283 36.536222, -121.900476 36.53596, -121.900159 36.535857, -121.899898 36.535822, -121.89947 36.535972, -121.898952 36.536027, -121.898587 36.536008, -121.898287 36.535985, -121.896795 36.535869, -121.896431 36.535864, -121.89602 36.536, -121.895701 36.536302, -121.895597 36.536386, -121.895449 36.536504, -121.894935 36.53667, -121.894534 36.537072, -121.893454 36.537896, -121.89291 36.538189, -121.892067 36.53835, -121.891755 36.538437, -121.891517 36.538503, -121.891172 36.538553, -121.890896 36.538574, -121.890668 36.538524, -121.89056 36.538435, -121.890436 36.538333, -121.890148 36.53806, -121.889777 36.537873, -121.889304 36.537759, -121.888769 36.537814, -121.888337 36.537852, -121.887888 36.537919, -121.887557 36.537871, -121.887292 36.537752, -121.887127 36.537518, -121.886962 36.537256, -121.886754 36.536813, -121.886373 36.536361, -121.886122 36.536143, -121.885994 36.536054, -121.88593 36.536009, -121.885909 36.535994, -121.885694 36.535817, -121.885395 36.535699, -121.884925 36.535668, -121.884495 36.535748, -121.884015 36.535914, -121.883698 36.536244, -121.883314 36.537107, -121.883051 36.537505, -121.882839 36.53786, -121.882677 36.538172, -121.882461 36.538415, -121.881969 36.538721, -121.881662 36.538789, -121.88147 36.538831, -121.880919 36.538928, -121.880524 36.539021, -121.880446 36.539023, -121.879831 36.539038, -121.879396 36.539021, -121.878872 36.538907, -121.8785 36.538707, -121.87787 36.53854, -121.877724 36.538535, -121.877383 36.538524, -121.876846 36.538537, -121.876309 36.538536, -121.87603 36.5385, -121.875748 36.538381, -121.875642 36.538328, -121.87548 36.538283, -121.875204 36.53829, -121.87494 36.538354, -121.87441 36.538425, -121.874124 36.53844, -121.873797 36.538448, -121.873581 36.538428, -121.873312 36.538352, -121.872698 36.538094, -121.872376 36.537986, -121.872085 36.537869, -121.871884 36.537692, -121.871219 36.537155, -121.870862 36.536932, -121.870563 36.536873, -121.870308 36.53683, -121.870423 36.536425, -121.870543 36.535965, -121.870584 36.535671, -121.87076 36.53392, -121.870591 36.53393, -121.87039 36.533914, -121.870241 36.533846, -121.869845 36.533554, -121.869749 36.533523, -121.869527 36.53345, -121.869245 36.533445, -121.86868 36.533529, -121.868305 36.533535, -121.867388 36.533496, -121.865983 36.533062, -121.865702 36.532976, -121.864399 36.532741, -121.864131 36.532683, -121.863951 36.532635, -121.863822 36.532593, -121.863493 36.532419, -121.863329 36.532338, -121.863162 36.532212, -121.862954 36.532003, -121.86272 36.531755, -121.862562 36.531648, -121.862364 36.531529, -121.861903 36.531302, -121.860339 36.53077, -121.860112 36.530667, -121.859313 36.53029, -121.858934 36.530224, -121.858705 36.530196, -121.858348 36.530205, -121.857925 36.530306, -121.857954 36.530368, -121.857994 36.530561, -121.85805 36.530686, -121.858123 36.5309, -121.858183 36.531104, -121.858248 36.531276, -121.858311 36.531401, -121.858428 36.531724, -121.858512 36.53188, -121.858492 36.532044, -121.858556 36.53219, -121.858609 36.532409, -121.8586 36.53251, -121.858599 36.532652, -121.858658 36.532829, -121.858676 36.532966, -121.858724 36.533049, -121.858846 36.533177, -121.858971 36.533201, -121.859055 36.533211, -121.859296 36.533517, -121.86007 36.5345, -121.860368 36.534658, -121.860518 36.534749, -121.860841 36.535044, -121.860211 36.535484, -121.860128 36.535532, -121.85942 36.535774, -121.858619 36.536038, -121.858284 36.536129, -121.860057 36.537699, -121.861064 36.538242, -121.862567 36.539053, -121.862762 36.539151, -121.864016 36.539778, -121.864155 36.539813, -121.864566 36.539916, -121.864825 36.53998, -121.865043 36.540035, -121.86583 36.54017, -121.866675 36.540182, -121.86735 36.540117, -121.867905 36.540017, -121.868134 36.539943, -121.868172 36.539934, -121.868441 36.539869, -121.868622 36.53982, -121.868691 36.539801, -121.86944 36.539632, -121.869618 36.539602, -121.869798 36.539585, -121.87005 36.539572, -121.870609 36.539559, -121.871284 36.53962, -121.871759 36.539743, -121.872023 36.539803, -121.87306 36.540039, -121.873372 36.540116, -121.873604 36.540173, -121.874648 36.54043, -121.875964 36.540778, -121.8761 36.540814, -121.87794 36.541204, -121.880168 36.541727, -121.880235 36.541743, -121.880374 36.541785, -121.880952 36.54196, -121.881321 36.542071, -121.881416 36.5421, -121.883052 36.542508, -121.883368 36.542536, -121.883713 36.542557, -121.884187 36.542557, -121.88695 36.54228, -121.888374 36.542228, -121.88955 36.542324, -121.89004 36.542407, -121.890843 36.542543, -121.891812 36.542742, -121.89252 36.54289, -121.893421 36.543048, -121.894137 36.54336, -121.894965 36.543669, -121.895715 36.54392, -121.896314 36.544056, -121.897022 36.544128, -121.897729 36.544171, -121.899859 36.544092, -121.899874 36.544583, -121.899993 36.544967, -121.898356 36.546502, -121.897806 36.547025, -121.897415 36.547389, -121.89725 36.547543, -121.897008 36.547708, -121.896648 36.548026, -121.896599 36.548084, -121.89655 36.548165, -121.896485 36.548328, -121.896473 36.548358, -121.896461 36.548447, -121.896465 36.548679, -121.896488 36.548987, -121.896475 36.549084, -121.896466 36.549154, -121.896421 36.54936, -121.89624 36.55003, -121.896053 36.550698, -121.896009 36.55086, -121.895818 36.551565, -121.895796 36.551683, -121.895789 36.551824, -121.895796 36.552047, -121.895865 36.55214, -121.896125 36.552757, -121.896234 36.55316, -121.896302 36.553261, -121.896425 36.553384, -121.896552 36.553499, -121.896622 36.553597, -121.896706 36.55377, -121.896786 36.553994, -121.896858 36.554268, -121.896942 36.554457, -121.897041 36.554646, -121.89712 36.554767, -121.897281 36.554966, -121.895744 36.555314, -121.895448 36.555465, -121.895312 36.555691, -121.895091 36.556354, -121.895361 36.556365, -121.895469 36.556381, -121.89555 36.556404, -121.89563 36.556445, -121.895707 36.556505, -121.895758 36.556559, -121.895802 36.556631, -121.895871 36.556807, -121.895884 36.556909, -121.895877 36.556996, -121.895855 36.557063, -121.89582 36.557123, -121.89568 36.557259, -121.895548 36.557374, -121.895332 36.557563, -121.895524 36.557723, -121.895936 36.558041, -121.896173 36.558184, -121.896757 36.558577, -121.896886 36.558673, -121.89697 36.558809, -121.897028 36.558945, -121.897029 36.559202, -121.897067 36.559329, -121.897118 36.559443, -121.897147 36.559486, -121.897182 36.559517, -121.897278 36.55957, -121.897347 36.559597, -121.897439 36.559605, -121.897571 36.559583, -121.897851 36.5595, -121.898141 36.559385, -121.898878 36.559041, -121.899106 36.558944, -121.899323 36.558888, -121.899506 36.558865, -121.899684 36.558869, -121.899909 36.558895, -121.900088 36.55894, -121.900294 36.55902, -121.900467 36.559118, -121.899762 36.560323, -121.899679 36.560434, -121.899644 36.560533, -121.899641 36.56064, -121.899666 36.560847, -121.899066 36.560938, -121.898804 36.561024, -121.898534 36.561142, -121.898089 36.561355, -121.897843 36.561483, -121.896906 36.561971, -121.896807 36.56205, -121.896723 36.562184, -121.896654 36.562385, -121.896632 36.562521, -121.896622 36.56277, -121.896672 36.562863, -121.896767 36.56295, -121.89696 36.563047, -121.897094 36.563082, -121.89722 36.563092, -121.897334 36.56308, -121.897424 36.563045, -121.897722 36.56293, -121.897894 36.562893, -121.897974 36.562886, -121.898054 36.562892, -121.898135 36.562911, -121.898209 36.562942, -121.898396 36.563065, -121.898583 36.563234, -121.898745 36.563414, -121.898837 36.563479, -121.89885 36.563488, -121.898942 36.563531, -121.899042 36.563564, -121.899148 36.563587, -121.899258 36.563599, -121.899351 36.563598, -121.899467 36.563583, -121.899592 36.563556, -121.899681 36.563527, -121.899749 36.563484, -121.899917 36.563249, -121.900011 36.563051, -121.900204 36.562619, -121.900289 36.56245, -121.900465 36.562273, -121.900569 36.562215, -121.900722 36.562167, -121.900885 36.562152, -121.899993 36.565525, -121.899987 36.565546, -121.899821 36.566173, -121.909843 36.56805, -121.910457 36.568096, -121.910989 36.568137, -121.911013 36.568104, -121.911195 36.568103, -121.911297 36.568089, -121.91146 36.568039, -121.91163 36.567964, -121.911748 36.567902, -121.91186 36.567783, -121.911949 36.567632, -121.912048 36.56739, -121.9121 36.567213, -121.912177 36.566814, -121.912227 36.566724, -121.912329 36.566628, -121.912414 36.566579, -121.912534 36.566548, -121.912985 36.566457, -121.913013 36.566648, -121.913019 36.566911, -121.912969 36.567195, -121.912731 36.567796, -121.9127 36.568157, -121.912702 36.568555, -121.912702 36.568782, -121.912706 36.569579, -121.913043 36.57203, -121.913048 36.572097, -121.913104 36.572927, -121.913065 36.573152, -121.913009 36.573427, -121.912894 36.573823, -121.91275 36.574242, -121.912692 36.574368, -121.912629 36.574473, -121.912467 36.574702, -121.912411 36.574791, -121.912347 36.574893, -121.91221 36.575069, -121.91208 36.575218, -121.911968 36.575345, -121.911787 36.575508, -121.91159 36.575398, -121.9115 36.575471, -121.910863 36.575926, -121.910204 36.576359, -121.909521 36.576767, -121.909202 36.576954, -121.907649 36.578083, -121.907449 36.57799, -121.906887 36.577729, -121.906706 36.577707, -121.906573 36.577703, -121.906434 36.577708, -121.90632 36.577812, -121.906448 36.577821, -121.906646 36.577934, -121.906708 36.578019, -121.906711 36.578119, -121.906616 36.578262, -121.904506 36.57936, -121.904081 36.579581, -121.90356 36.579857, -121.903119 36.580192, -121.902799 36.580381, -121.902167 36.580506, -121.901729 36.580645, -121.900644 36.581185, -121.900345 36.581273, -121.900121 36.5813, -121.899424 36.581316, -121.8991 36.581324, -121.899216 36.58159, -121.897784 36.581984, -121.895462 36.582453, -121.895314 36.582483, -121.895138 36.582519, -121.893068 36.582849, -121.891262 36.583111, -121.888369 36.583531, -121.88732 36.583718, -121.886894 36.583808, -121.886113 36.584082, -121.885524 36.584351, -121.884936 36.584671, -121.884443 36.585014, -121.883983 36.585382, -121.883462 36.585827, -121.883008 36.586348, -121.882586 36.586919, -121.882293 36.587512, -121.882 36.588156, -121.881924 36.588388, -121.88149 36.589271, -121.881212 36.589856, -121.880823 36.590451, -121.880544 36.590767, -121.880047 36.591156, -121.880194 36.590602, -121.880325 36.589877, -121.880594 36.589589, -121.880843 36.588796, -121.881125 36.58881, -121.881826 36.587732, -121.882668 36.586394, -121.883157 36.58588, -121.883417 36.583421, -121.883709 36.58229, -121.883786 36.581546, -121.884047 36.581381, -121.887116 36.579425, -121.888039 36.579384, -121.889078 36.578905, -121.890064 36.578451, -121.890964 36.57766, -121.878751 36.576394, -121.87794 36.57631, -121.873926 36.576069, -121.873931 36.576028, -121.87401 36.575692, -121.874091 36.575347, -121.873476 36.575205, -121.871844 36.574942, -121.871278 36.574983, -121.87082 36.574875, -121.868984 36.57408, -121.868232 36.573618, -121.867972 36.573459, -121.867534 36.573323, -121.867081 36.573333, -121.866654 36.573491, -121.866323 36.573737, -121.866017 36.574074, -121.865664 36.574358, -121.865215 36.574515, -121.864716 36.574527, -121.861885 36.574192, -121.861587 36.574198, -121.8615 36.574201, -121.861004 36.574341, -121.860737 36.574439, -121.860437 36.574471, -121.860141 36.574451, -121.859852 36.574403, -121.858244 36.57408, -121.857712 36.573997, -121.857496 36.574007, -121.857032 36.574608, -121.856287 36.575637, -121.856238 36.575782, -121.856058 36.576035, -121.855885 36.576192, -121.855507 36.576458, -121.855305 36.576589, -121.854184 36.577317, -121.853889 36.577436, -121.853603 36.577499, -121.853264 36.577507, -121.851516 36.577324, -121.851148 36.577349, -121.850843 36.577437, -121.850711 36.577497, -121.850659 36.578341, -121.850597 36.579369, -121.852913 36.579034, -121.852822 36.579033, -121.85274 36.579021, -121.852673 36.578931, -121.852703 36.578844, -121.852802 36.578792, -121.852904 36.578814, -121.853071 36.57884, -121.853423 36.578476, -121.853594 36.578471, -121.853813 36.578419, -121.853901 36.57833, -121.853941 36.578413, -121.85398 36.57835, -121.854444 36.579347, -121.854552 36.579582, -121.854729 36.579961, -121.855184 36.580939, -121.85576 36.582174, -121.856238 36.583204, -121.856462 36.583593, -121.8564 36.583582, -121.855929 36.583507, -121.855283 36.583453, -121.854965 36.583427, -121.854093 36.583431, -121.853213 36.58351, -121.852523 36.583614, -121.850294 36.583961, -121.849792 36.583982, -121.849483 36.583973, -121.848964 36.583942, -121.848218 36.583795, -121.840123 36.581226, -121.838925 36.580952, -121.837851 36.580848, -121.836826 36.580852, -121.835742 36.580945, -121.835175 36.581042, -121.83403 36.581347, -121.833269 36.581571, -121.832829 36.581658, -121.832327 36.581758, -121.831742 36.581814, -121.83105 36.581809, -121.830456 36.581751, -121.829557 36.581556, -121.829105 36.581412, -121.827966 36.58088, -121.828328 36.580443, -121.828455 36.580321, -121.82845 36.580195, -121.828321 36.58002, -121.828066 36.579761, -121.827879 36.579581, -121.827742 36.579425, -121.827648 36.579264, -121.826214 36.579276, -121.82517 36.577876, -121.82462 36.57813, -121.824189 36.57822, -121.823679 36.57825, -121.821981 36.57825, -121.82089 36.57797, -121.820636 36.57795, -121.820154 36.577991, -121.819899 36.578053, -121.81975 36.578101, -121.819551 36.577676, -121.819449 36.577367, -121.819412 36.577186, -121.819344 36.576533, -121.819329 36.576488, -121.819266 36.57635, -121.819184 36.576219, -121.819081 36.576099, -121.81896 36.575992, -121.818839 36.57591, -121.818707 36.575841, -121.818545 36.575778, -121.818395 36.575739, -121.817679 36.575627, -121.816867 36.575546, -121.816627 36.5755, -121.816473 36.575453, -121.816327 36.575394, -121.816165 36.575309, -121.816084 36.575247, -121.815978 36.575182, -121.81586 36.575129, -121.815568 36.575047, -121.81537 36.575009, -121.815172 36.574983, -121.81497 36.574967, -121.814718 36.574964, -121.814255 36.574846, -121.813898 36.574753, -121.813109 36.574427, -121.812538 36.574309, -121.812457 36.574292, -121.811996 36.574268, -121.811196 36.574193, -121.810983 36.574186, -121.810842 36.574195, -121.810701 36.574221, -121.810567 36.574264, -121.810448 36.574321, -121.810341 36.574393, -121.810285 36.574448, -121.809857 36.574247, -121.809648 36.574111, -121.809555 36.574028, -121.809471 36.573939, -121.809208 36.573528, -121.809052 36.573322, -121.808663 36.57307, -121.808279 36.572871, -121.808296 36.57466, -121.808297 36.574768, -121.808295 36.574987, -121.808288 36.575021, -121.808318 36.575857, -121.808323 36.576008, -121.808309 36.576367, -121.8273 36.588423, -121.828462 36.58916, -121.828797 36.588825, -121.829002 36.58862, -121.829605 36.588017, -121.829258 36.587923, -121.828298 36.587662, -121.826809 36.586601, -121.826966 36.586461, -121.827147 36.5863, -121.826859 36.58601, -121.827349 36.585627, -121.827779 36.585934, -121.830153 36.586312, -121.830398 36.586099, -121.83049 36.58627, -121.830994 36.587225, -121.831203 36.587678, -121.831413 36.588163, -121.831925 36.589475, -121.832046 36.589846, -121.832179 36.590172, -121.832527 36.590811, -121.832227 36.591478, -121.832173 36.591599, -121.831201 36.593528, -121.830419 36.59503, -121.830191 36.595405, -121.829955 36.595765, -121.829775 36.596007, -121.829636 36.596167, -121.829442 36.596354, -121.829387 36.596395, -121.829231 36.596513, -121.829087 36.596628, -121.828943 36.596844, -121.827942 36.598315, -121.827873 36.598516, -121.827741 36.598994, -121.827679 36.599118, -121.827582 36.599319, -121.827242 36.599832, -121.827125 36.600068, -121.827007 36.6004, -121.826695 36.6011, -121.826283 36.601959, -121.826175 36.602191, -121.826006 36.602531, -121.825715 36.603102, -121.82559 36.603362, -121.82459 36.603772, -121.823999 36.605159, -121.824103 36.605661, -121.824284 36.605943, -121.824048 36.606518, -121.823903 36.606782, -121.823584 36.607398, -121.823037 36.608528, -121.822441 36.609803, -121.821013 36.612706, -121.820729 36.613253, -121.820238 36.614266, -121.819645 36.615495, -121.819713 36.616637, -121.819409 36.617299, -121.819264 36.617557, -121.819188 36.617693, -121.818956 36.618205, -121.818656 36.61881, -121.818392 36.619373, -121.818082 36.619974, -121.817989 36.620171, -121.817809 36.620551, -121.817632 36.620972, -121.817403 36.621424, -121.817342 36.62155, -121.817274 36.621689, -121.817256 36.621725, -121.817162 36.621918, -121.816823 36.622726, -121.816711 36.623017, -121.816621 36.623215, -121.816482 36.623518, -121.816379 36.623831, -121.8161 36.624444, -121.815834 36.624954, -121.815738 36.625167, -121.815435 36.625837, -121.815346 36.62601, -121.815176 36.626347, -121.815452 36.626412, -121.814612 36.628132, -121.813312 36.630774, -121.813566 36.630855, -121.813842 36.632016, -121.812763 36.634199, -121.813498 36.634773, -121.812704 36.634284, -121.81201 36.633856, -121.812129 36.633763, -121.812536 36.632885, -121.812727 36.632518, -121.813026 36.631888, -121.813086 36.63166, -121.813 36.631462, -121.81274 36.631245, -121.813043 36.630752, -121.81325 36.630258, -121.813455 36.629861, -121.813593 36.629513, -121.813803 36.629146, -121.814027 36.628651, -121.814406 36.627913, -121.814512 36.627683, -121.813781 36.626684, -121.812791 36.626784, -121.809397 36.626889, -121.809031 36.627265, -121.807592 36.627312, -121.806276 36.62779, -121.806406 36.628212, -121.805028 36.62865, -121.804297 36.628311, -121.802064 36.628549, -121.800246 36.629131, -121.79947 36.629932, -121.799684 36.630848, -121.79978 36.630911, -121.800391 36.631315, -121.802803 36.63214, -121.804007 36.633488, -121.805271 36.634336, -121.809013 36.634901, -121.807904 36.637301, -121.807425 36.637447, -121.807254 36.637475, -121.806991 36.637517, -121.805254 36.63778, -121.80375 36.637937, -121.802652 36.638032, -121.802229 36.63804, -121.80183 36.637998, -121.801348 36.637901, -121.800847 36.637781, -121.799794 36.637518, -121.798635 36.637238, -121.798612 36.637484, -121.798602 36.63835, -121.798608 36.63927, -121.798584 36.639728, -121.798532 36.640286, -121.798434 36.640599, -121.798285 36.640853, -121.798207 36.641036, -121.798086 36.641027, -121.797883 36.64088, -121.797072 36.64165, -121.796126 36.641376, -121.792958 36.641342, -121.792319 36.641656, -121.792221 36.643872, -121.789858 36.643851, -121.78782 36.643782, -121.787537 36.643752, -121.787389 36.643705, -121.787255 36.645053, -121.787171 36.646266, -121.787101 36.647274, -121.787087 36.647483, -121.78609 36.64743, -121.786122 36.647207, -121.786236 36.64644, -121.78589 36.646422, -121.785917 36.64617, -121.781345 36.645864, -121.781312 36.646184, -121.781072 36.648561, -121.786986 36.649004, -121.786956 36.649474, -121.786925 36.649838, -121.786913 36.649979, -121.786912 36.650154, -121.78688 36.650311, -121.786773 36.650663, -121.786388 36.651513, -121.786326 36.651827, -121.786313 36.651984, -121.786283 36.654665, -121.782623 36.654633, -121.770789 36.655151, -121.770406 36.655151, -121.768029 36.655065, -121.766529 36.655123, -121.758498 36.655435, -121.756332 36.655519, -121.750429 36.655509, -121.747218 36.655504, -121.745947 36.655445, -121.743724 36.655342, -121.743558 36.655334, -121.742586 36.655289, -121.741775 36.655252, -121.740316 36.655185, -121.739021 36.655127, -121.738753 36.655118, -121.737855 36.65509, -121.73746 36.655107, -121.737122 36.655144, -121.73633 36.655263, -121.736082 36.655286, -121.735694 36.655295, -121.735422 36.655279, -121.735193 36.655255, -121.734936 36.655217, -121.734611 36.655148, -121.734429 36.655101, -121.73412 36.655, -121.733881 36.654907, -121.733586 36.654782, -121.733353 36.654653, -121.733126 36.6545, -121.733555 36.654075, -121.73369 36.65391, -121.733751 36.653791, -121.73379 36.653633, -121.733834 36.653151, -121.733379 36.653128, -121.733248 36.653113, -121.733135 36.653086, -121.733273 36.652765, -121.732976 36.652407, -121.733035 36.652223, -121.733058 36.65207, -121.733066 36.651901, -121.73305 36.65177, -121.733012 36.65159, -121.732897 36.651125, -121.732891 36.651039, -121.732897 36.650956, -121.732929 36.650838, -121.732974 36.650734, -121.733047 36.650603, -121.732383 36.650318, -121.731815 36.650004, -121.731715 36.650141, -121.731523 36.650318, -121.730962 36.650832, -121.730483 36.650565, -121.730352 36.650508, -121.730106 36.650418, -121.72977 36.650418, -121.729565 36.650451, -121.729294 36.650467, -121.729188 36.650467, -121.728905 36.650463, -121.728429 36.6504, -121.727985 36.650342, -121.727712 36.650337, -121.727528 36.650391, -121.72738 36.650458, -121.727237 36.650552, -121.72711 36.650703, -121.727256 36.650773, -121.727683 36.651046, -121.726752 36.652044, -121.726171 36.651675, -121.725706 36.65138, -121.725708 36.651731, -121.725829 36.651924, -121.726096 36.652092, -121.72648 36.652336, -121.727024 36.652681, -121.729556 36.654284, -121.730099 36.654628, -121.730485 36.65486, -121.731648 36.655563, -121.731613 36.655587, -121.731576 36.65577, -121.732663 36.655884, -121.733102 36.655946, -121.733533 36.65603, -121.733819 36.656097, -121.734099 36.656173, -121.734513 36.656304, -121.736963 36.657443, -121.738046 36.657967, -121.740546 36.659159, -121.741699 36.659708, -121.742034 36.659868, -121.745843 36.661684, -121.748349 36.662879, -121.748974 36.663177, -121.750861 36.664099, -121.7529 36.665047, -121.755572 36.666324, -121.75587 36.666457, -121.756736 36.666857, -121.754969 36.669253, -121.755677 36.669617, -121.757096 36.670348, -121.757491 36.669826, -121.75764 36.669629, -121.757741 36.669538, -121.757825 36.669446, -121.757958 36.669342, -121.758102 36.669264, -121.758261 36.669205, -121.758424 36.669171, -121.758589 36.66916, -121.758756 36.669166, -121.758913 36.669195, -121.759061 36.669247, -121.759209 36.669318, -121.760629 36.670052, -121.763113 36.671352, -121.763224 36.671456, -121.763318 36.671574, -121.763379 36.671701, -121.763461 36.671923, -121.763585 36.67226, -121.763613 36.672359, -121.763706 36.672478, -121.763818 36.672581, -121.763951 36.672667, -121.764232 36.672812, -121.765152 36.673285, -121.76629 36.67185, -121.766527 36.671523, -121.766935 36.671721, -121.772902 36.6746, -121.775234 36.675694, -121.776952 36.676512, -121.778169 36.677115, -121.777883 36.677316, -121.776064 36.676316, -121.775051 36.678018, -121.789234 36.684929, -121.78722 36.685653, -121.787561 36.685976, -121.787521 36.68599, -121.785156 36.68682, -121.785116 36.686995, -121.785428 36.687511, -121.785943 36.688364, -121.786099 36.688511, -121.786667 36.688842, -121.787254 36.689183, -121.786879 36.689615, -121.786849 36.689788, -121.78723 36.690405, -121.787572 36.690244, -121.788054 36.689648, -121.788277 36.689778, -121.78849 36.689781, -121.788945 36.689277, -121.789694 36.689709, -121.788588 36.691215, -121.788709 36.691356, -121.789759 36.691466, -121.792025 36.691725, -121.792548 36.691785, -121.79457 36.692021, -121.794779 36.692091, -121.794806 36.692106, -121.792351 36.694195, -121.791768 36.694693, -121.791265 36.695233, -121.790779 36.695698, -121.790338 36.696119, -121.789678 36.696674, -121.7896 36.696756, -121.789627 36.696798, -121.789665 36.696855, -121.78971 36.696923, -121.789921 36.69724, -121.789721 36.697328, -121.789585 36.697411, -121.789023 36.697933, -121.788968 36.698062, -121.788997 36.698197, -121.788826 36.698547, -121.788805 36.69859, -121.78686 36.702572, -121.789209 36.703075, -121.789188 36.70311, -121.789076 36.703322, -121.788939 36.703625, -121.788822 36.703933, -121.788727 36.704246, -121.788664 36.704518, -121.78861 36.704838, -121.788586 36.705145, -121.78873 36.705318, -121.788922 36.704694, -121.788908 36.704229, -121.789016 36.70378, -121.789461 36.703158, -121.789661 36.703158, -121.798361 36.705158, -121.800011 36.702958, -121.800873 36.703325, -121.801249 36.703486, -121.801928 36.70383, -121.801459 36.705228, -121.801035 36.706196, -121.808633 36.707511, -121.808973 36.706072, -121.808977 36.706055, -121.809204 36.705097, -121.809495 36.703867, -121.810583 36.699263, -121.814462 36.682858, -121.815881 36.679386, -121.816263 36.678081, -121.816507 36.677738, -121.816568 36.677128, -121.816522 36.676807, -121.81814 36.673489, -121.818369 36.672588, -121.819391 36.670673, -121.819437 36.670216, -121.819635 36.669834, -121.820093 36.669094, -121.820322 36.668148, -121.820963 36.666561, -121.821588 36.665684, -121.821695 36.665249, -121.821878 36.664547, -121.822275 36.664043, -121.822626 36.663387, -121.822778 36.662701, -121.823236 36.662052, -121.823557 36.661388, -121.823557 36.660854, -121.824197 36.659115, -121.82467 36.658169, -121.825052 36.657207, -121.826105 36.655437, -121.826288 36.654797, -121.82731 36.653134, -121.827966 36.651799, -121.828882 36.650006, -121.830087 36.648061, -121.83111 36.646276, -121.831995 36.644856, -121.833063 36.643422, -121.83381 36.642255, -121.834848 36.640889, -121.83558 36.639737, -121.836343 36.638646, -121.836664 36.637937, -121.837306 36.637105, -121.837931 36.636022, -121.838954 36.634885, -121.839747 36.633908, -121.840388 36.632779, -121.840922 36.631871, -121.841731 36.631017, -121.842263 36.630059)))"} -{"geo_id":"07921","urban_area_code":"07921","name":"Bismarck, ND","lsad_name":"Bismarck, ND Urbanized Area","area_lsad_code":"75","mtfcc_feature_class_code":"G3500","type":"U","functional_status":"S","area_land_meters":99720416,"area_water_meters":2418446,"internal_point_lon":-100.8010048,"internal_point_lat":46.8198476,"internal_point_geom":"POINT(-100.8010048 46.8198476)","urban_area_geom":"MULTIPOLYGON(((-100.790014 46.88984, -100.790261 46.889516, -100.790469 46.889258, -100.791042 46.8885, -100.79114 46.888341, -100.791188 46.88823, -100.791216 46.888116, -100.791226 46.887936, -100.791217 46.887832, -100.791188 46.88773, -100.791152 46.887658, -100.791139 46.887631, -100.791007 46.887456, -100.790856 46.887284, -100.790798 46.887238, -100.79067 46.887154, -100.790528 46.887081, -100.790307 46.886988, -100.789816 46.886798, -100.789706 46.886749, -100.789504 46.886647, -100.789377 46.88657, -100.789266 46.886482, -100.78917 46.886386, -100.789072 46.886271, -100.789011 46.886174, -100.78897 46.886071, -100.78895 46.885975, -100.78894 46.885827, -100.78895 46.885678, -100.788981 46.885574, -100.78905 46.885414, -100.789116 46.885295, -100.789245 46.885166, -100.789391 46.885045, -100.789491 46.884979, -100.789565 46.884938, -100.78972 46.884865, -100.78988 46.884806, -100.790074 46.884749, -100.790462 46.884646, -100.790606 46.884592, -100.790955 46.884408, -100.791097 46.884529, -100.791257 46.884607, -100.791325 46.884632, -100.791478 46.884676, -100.791538 46.884699, -100.791645 46.884726, -100.791757 46.884739, -100.792303 46.884746, -100.792697 46.884742, -100.79342 46.884745, -100.793419 46.881553, -100.793022 46.881556, -100.79071 46.88154, -100.788063 46.881529, -100.786868 46.881526, -100.785926 46.881528, -100.783754 46.881533, -100.781233 46.881528, -100.779538 46.881519, -100.777525 46.881504, -100.775865 46.881501, -100.774489 46.881498, -100.772342 46.8815, -100.772242 46.882656, -100.772063 46.884731, -100.77203 46.885397, -100.771964 46.886723, -100.771964 46.886924, -100.773158 46.888698, -100.773089 46.888712, -100.772977 46.888737, -100.77287 46.888756, -100.772765 46.888767, -100.772667 46.88877, -100.772583 46.888769, -100.772466 46.888762, -100.772399 46.88876, -100.772353 46.888776, -100.772477 46.888807, -100.772577 46.888848, -100.772874 46.888837, -100.773223 46.888861, -100.773283 46.888883, -100.774418 46.890569, -100.776618 46.890571, -100.777604 46.890571, -100.77895 46.890571, -100.780262 46.890573, -100.780873 46.890574, -100.78149 46.890576, -100.781642 46.890506, -100.782073 46.890172, -100.782206 46.890158, -100.78228 46.890161, -100.782301 46.890186, -100.782307 46.890577, -100.782476 46.890578, -100.783251 46.890569, -100.783633 46.890585, -100.783893 46.890636, -100.784348 46.890735, -100.784807 46.89084, -100.784776 46.89093, -100.784745 46.89099, -100.784699 46.891114, -100.784674 46.891241, -100.78467 46.891369, -100.784688 46.891513, -100.784732 46.891708, -100.784778 46.891853, -100.784854 46.892012, -100.78495 46.892166, -100.785042 46.892287, -100.785226 46.892463, -100.785401 46.89261, -100.785684 46.89278, -100.785828 46.892858, -100.785969 46.892913, -100.78649 46.893144, -100.786621 46.893217, -100.786737 46.893301, -100.786861 46.893416, -100.786949 46.893524, -100.787018 46.893637, -100.787062 46.893741, -100.787104 46.893901, -100.787125 46.894063, -100.787125 46.894346, -100.787126 46.894975, -100.787117 46.896111, -100.783179 46.896125, -100.78273 46.896127, -100.780407 46.896084, -100.779653 46.89607, -100.778031 46.89604, -100.777719 46.896039, -100.776314 46.896036, -100.776311 46.896129, -100.776312 46.896172, -100.776309 46.896235, -100.77631 46.89638, -100.776312 46.896463, -100.776313 46.896554, -100.776321 46.896752, -100.776337 46.89686, -100.776402 46.89708, -100.77645 46.897192, -100.77658 46.8974, -100.776659 46.8975, -100.776836 46.897698, -100.776923 46.897794, -100.777098 46.897986, -100.777185 46.898083, -100.777358 46.898274, -100.777438 46.898369, -100.777515 46.898465, -100.777588 46.898564, -100.777658 46.898666, -100.777722 46.89877, -100.777778 46.898875, -100.777874 46.899085, -100.777918 46.899188, -100.777997 46.899386, -100.778034 46.89948, -100.778069 46.899569, -100.778131 46.899732, -100.778189 46.899868, -100.778212 46.899923, -100.778232 46.89997, -100.778257 46.900041, -100.778277 46.900105, -100.778302 46.900178, -100.778333 46.900258, -100.778361 46.900339, -100.778457 46.900322, -100.779244 46.900202, -100.779506 46.900174, -100.779913 46.900177, -100.780316 46.900238, -100.780355 46.900249, -100.781417 46.901943, -100.784082 46.901926, -100.784363 46.902991, -100.784379 46.903057, -100.784401 46.903182, -100.784412 46.903308, -100.784413 46.903336, -100.784765 46.903335, -100.785009 46.903332, -100.785335 46.903322, -100.785659 46.903305, -100.785983 46.903281, -100.786305 46.903249, -100.786626 46.903211, -100.786944 46.903165, -100.787184 46.903125, -100.787261 46.903112, -100.787574 46.903052, -100.787885 46.902985, -100.788192 46.902911, -100.788496 46.90283, -100.78867 46.90278, -100.789846 46.902435, -100.789902 46.902418, -100.7901 46.902373, -100.790291 46.902325, -100.79049 46.902288, -100.790691 46.902259, -100.790895 46.902237, -100.7911 46.902222, -100.791305 46.902214, -100.791408 46.902213, -100.793247 46.902208, -100.793248 46.902123, -100.793259 46.902008, -100.793266 46.901766, -100.793265 46.901642, -100.793267 46.901513, -100.793273 46.901384, -100.793285 46.901116, -100.793289 46.90098, -100.793291 46.900842, -100.793293 46.900704, -100.793293 46.900607, -100.793294 46.900567, -100.793295 46.900436, -100.793296 46.900311, -100.793297 46.900196, -100.793298 46.899991, -100.793298 46.899807, -100.793299 46.899714, -100.793299 46.89962, -100.793297 46.899434, -100.793297 46.899341, -100.793294 46.89924, -100.793293 46.899212, -100.793291 46.899065, -100.793293 46.898967, -100.793294 46.89887, -100.793296 46.898669, -100.793299 46.898569, -100.793302 46.898363, -100.793303 46.898254, -100.793306 46.898033, -100.793308 46.897921, -100.793315 46.897691, -100.793318 46.897573, -100.793323 46.897334, -100.793324 46.897215, -100.793323 46.896987, -100.793325 46.896881, -100.793332 46.896683, -100.793332 46.896586, -100.793332 46.896495, -100.793333 46.896412, -100.793334 46.896334, -100.793335 46.896266, -100.793336 46.896208, -100.793338 46.896159, -100.793315 46.896104, -100.793162 46.896087, -100.793087 46.896084, -100.793004 46.896085, -100.792916 46.896089, -100.792823 46.89609, -100.792718 46.89609, -100.792607 46.896089, -100.79249 46.896089, -100.792366 46.896089, -100.792236 46.896088, -100.792103 46.896086, -100.791968 46.896085, -100.791695 46.896084, -100.791439 46.896088, -100.791235 46.896087, -100.791139 46.896109, -100.791157 46.894909, -100.791155 46.894716, -100.791169 46.894599, -100.791204 46.894485, -100.791302 46.894268, -100.791419 46.893965, -100.79145 46.893761, -100.791446 46.893639, -100.791424 46.893519, -100.791383 46.893395, -100.791285 46.893208, -100.791218 46.89311, -100.791121 46.893, -100.791006 46.892897, -100.790881 46.892799, -100.790814 46.892754, -100.790668 46.892672, -100.79051 46.892602, -100.790342 46.892545, -100.790202 46.892508, -100.790057 46.892459, -100.789717 46.892327, -100.789565 46.892254, -100.789426 46.892169, -100.789317 46.892087, -100.789214 46.891961, -100.78913 46.891829, -100.789084 46.891736, -100.789041 46.891602, -100.789018 46.891466, -100.789015 46.891353, -100.789043 46.89122, -100.789092 46.891089, -100.789161 46.890962, -100.790014 46.88984)), ((-100.907821 46.846298, -100.908097 46.847046, -100.908186 46.847246, -100.907811 46.847231, -100.907349 46.847151, -100.907078 46.847051, -100.90675 46.846877, -100.906676 46.846821, -100.905793 46.847289, -100.90647 46.8473, -100.906773 46.847597, -100.905784 46.847615, -100.905682 46.847615, -100.904467 46.847636, -100.903281 46.847655, -100.902935 46.847661, -100.902208 46.847655, -100.902112 46.847654, -100.902111 46.848769, -100.902119 46.850216, -100.902151 46.851087, -100.902126 46.852203, -100.902113 46.854064, -100.913808 46.854123, -100.913792 46.856792, -100.919342 46.856433, -100.919391 46.857157, -100.920098 46.856977, -100.920232 46.857182, -100.921126 46.858707, -100.921457 46.858556, -100.921689 46.858441, -100.922567 46.858071, -100.921954 46.858537, -100.923661 46.859695, -100.923345 46.859888, -100.923826 46.860365, -100.924322 46.860043, -100.924562 46.860305, -100.924944 46.860791, -100.925417 46.861245, -100.925815 46.861578, -100.926455 46.862062, -100.926776 46.862297, -100.92777 46.863, -100.928944 46.863955, -100.929287 46.86442, -100.929587 46.864917, -100.929877 46.865266, -100.930365 46.865705, -100.931129 46.866538, -100.931679 46.867318, -100.932214 46.868372, -100.932612 46.869564, -100.932734 46.870466, -100.932766 46.871421, -100.932848 46.873685, -100.932852 46.873804, -100.934095 46.873826, -100.934406 46.873831, -100.934463 46.873832, -100.935423 46.873641, -100.935078 46.873194, -100.934456 46.872308, -100.934177 46.871762, -100.934166 46.871681, -100.934144 46.871516, -100.937116 46.871503, -100.938104 46.871497, -100.954405 46.871454, -100.954449 46.871434, -100.954792 46.871304, -100.954871 46.871223, -100.955034 46.871057, -100.955173 46.870915, -100.955356 46.870671, -100.955677 46.870152, -100.955738 46.870121, -100.95586 46.870129, -100.956348 46.870038, -100.956282 46.866039, -100.956261 46.864318, -100.963758 46.864329, -100.963782 46.864248, -100.964371 46.864249, -100.96458 46.86425, -100.964625 46.86425, -100.965029 46.864252, -100.965514 46.864253, -100.965591 46.864253, -100.965896 46.864254, -100.9661 46.864254, -100.968358 46.864216, -100.968983 46.863761, -100.968917 46.863677, -100.966538 46.863705, -100.962169 46.863732, -100.957851 46.863798, -100.955103 46.863813, -100.954245 46.863772, -100.953535 46.863732, -100.953099 46.863703, -100.95252 46.863645, -100.951473 46.863516, -100.950423 46.86333, -100.949121 46.863045, -100.947669 46.862702, -100.946623 46.862433, -100.945379 46.862002, -100.944812 46.861796, -100.944144 46.861524, -100.9434 46.861197, -100.940388 46.859783, -100.937555 46.858477, -100.935365 46.857437, -100.934012 46.856785, -100.929277 46.854553, -100.926393 46.8532, -100.9245 46.852322, -100.923389 46.851798, -100.917715 46.849121, -100.91613 46.848363, -100.914284 46.847642, -100.913851 46.847514, -100.913742 46.847492, -100.910113 46.846746, -100.907821 46.846298)), ((-100.968358 46.864216, -100.968412 46.864482, -100.968494 46.864636, -100.968689 46.864818, -100.968212 46.864827, -100.967542 46.864813, -100.967434 46.864773, -100.967328 46.864679, -100.966416 46.864621, -100.966271 46.864665, -100.965896 46.864636, -100.965895 46.865075, -100.965895 46.865165, -100.965895 46.865387, -100.965898 46.866248, -100.965903 46.867543, -100.965908 46.868844, -100.965914 46.870285, -100.965926 46.871395, -100.966719 46.871423, -100.967625 46.871455, -100.967762 46.87146, -100.970169 46.871547, -100.971408 46.871592, -100.971407 46.871531, -100.971406 46.871414, -100.971864 46.871409, -100.971818 46.870222, -100.971322 46.869623, -100.971162 46.869428, -100.971067 46.869358, -100.970494 46.868935, -100.970321 46.86881, -100.970254 46.868777, -100.968632 46.868469, -100.96855 46.868391, -100.968761 46.864831, -100.968799 46.864209, -100.968358 46.864216)), ((-100.709698 46.826859, -100.709696 46.825982, -100.709696 46.825358, -100.709699 46.824866, -100.709704 46.824227, -100.709704 46.824166, -100.709709 46.823515, -100.709711 46.822277, -100.707879 46.822283, -100.707891 46.822025, -100.707967 46.821806, -100.708022 46.821587, -100.70806 46.821478, -100.708062 46.821065, -100.708062 46.820869, -100.708063 46.820696, -100.708056 46.820483, -100.707979 46.819783, -100.707928 46.819258, -100.707899 46.819021, -100.707924 46.818694, -100.708034 46.818266, -100.708093 46.817943, -100.708111 46.817413, -100.706924 46.817418, -100.705398 46.817374, -100.704934 46.817392, -100.704791 46.817397, -100.704089 46.817474, -100.703424 46.817607, -100.703021 46.817722, -100.702318 46.817984, -100.702147 46.81808, -100.702297 46.81842, -100.702586 46.819041, -100.70264 46.819231, -100.702656 46.819286, -100.702704 46.819489, -100.702675 46.820096, -100.702626 46.820727, -100.70255 46.820965, -100.70254 46.820985, -100.702427 46.821209, -100.702101 46.82181, -100.702087 46.821855, -100.702032 46.822039, -100.701966 46.822996, -100.701955 46.823534, -100.704571 46.823534, -100.704553 46.825349, -100.704547 46.825997, -100.704515 46.826746, -100.704503 46.827077, -100.708806 46.826848, -100.709698 46.826859)), ((-100.829432 46.834172, -100.82942 46.834198, -100.82928 46.834538, -100.829254 46.834723, -100.82926 46.834914, -100.829308 46.835063, -100.829472 46.83544, -100.829522 46.835517, -100.829791 46.836068, -100.829892 46.836254, -100.83011 46.836848, -100.830235 46.837047, -100.83037 46.837239, -100.830551 46.837418, -100.830866 46.837663, -100.830987 46.837774, -100.831076 46.837863, -100.831116 46.837906, -100.831202 46.83809, -100.831284 46.838141, -100.831322 46.838157, -100.831483 46.83873, -100.831035 46.838991, -100.830754 46.839459, -100.830852 46.83979, -100.831184 46.840299, -100.831375 46.840536, -100.831577 46.840785, -100.831614 46.841103, -100.831465 46.841439, -100.83068 46.842168, -100.831075 46.842307, -100.831715 46.842438, -100.832413 46.842519, -100.832572 46.842554, -100.832587 46.842558, -100.832679 46.842585, -100.832816 46.842632, -100.832948 46.842685, -100.833075 46.842744, -100.833195 46.842809, -100.833308 46.84288, -100.833414 46.842956, -100.833486 46.843015, -100.834184 46.843578, -100.834535 46.843372, -100.834548 46.843366, -100.834618 46.843328, -100.834681 46.843285, -100.834735 46.843236, -100.834781 46.843184, -100.834817 46.843128, -100.834843 46.84307, -100.834859 46.84301, -100.834864 46.842949, -100.834858 46.842888, -100.834853 46.842861, -100.834823 46.842802, -100.834744 46.842631, -100.834675 46.842458, -100.834617 46.842283, -100.834576 46.842138, -100.834543 46.841959, -100.834521 46.841684, -100.834421 46.840456, -100.83442 46.840442, -100.834251 46.839205, -100.834187 46.839063, -100.83414 46.839007, -100.834084 46.838956, -100.834021 46.838911, -100.83395 46.83887, -100.833873 46.838836, -100.833791 46.838808, -100.833695 46.838783, -100.833596 46.838749, -100.833502 46.83871, -100.833414 46.838664, -100.833333 46.838613, -100.833259 46.838556, -100.833242 46.838542, -100.832912 46.838189, -100.832677 46.837936, -100.832586 46.837838, -100.832545 46.83768, -100.832538 46.837619, -100.832532 46.837563, -100.832532 46.837455, -100.832531 46.837341, -100.832531 46.837106, -100.832516 46.836824, -100.832467 46.836628, -100.832139 46.835903, -100.832009 46.835818, -100.831822 46.835759, -100.83155 46.835686, -100.83136 46.835647, -100.831218 46.835642, -100.831073 46.835677, -100.830967 46.83577, -100.830896 46.83591, -100.830887 46.836063, -100.830911 46.836202, -100.831094 46.836588, -100.831199 46.836892, -100.83131 46.837091, -100.831507 46.837332, -100.831646 46.837484, -100.831443 46.83757, -100.831203 46.837484, -100.829297 46.834734, -100.829442 46.834203, -100.829432 46.834172)), ((-100.763237 46.765703, -100.762628 46.765705, -100.762561 46.765715, -100.762507 46.765727, -100.76243 46.765765, -100.762379 46.765811, -100.762328 46.76588, -100.762287 46.766035, -100.762037 46.765922, -100.761824 46.765842, -100.761607 46.765782, -100.761476 46.765758, -100.76133 46.765731, -100.76111 46.76571, -100.760702 46.765704, -100.760619 46.765703, -100.757999 46.765702, -100.756232 46.765712, -100.75493 46.765704, -100.75487 46.7657, -100.753895 46.765697, -100.753619 46.765696, -100.752704 46.765693, -100.752593 46.765692, -100.752617 46.764187, -100.75264 46.762756, -100.752719 46.760904, -100.750992 46.7571, -100.749695 46.757629, -100.748138 46.758206, -100.746667 46.758563, -100.7459 46.758586, -100.742729 46.758573, -100.741424 46.758571, -100.7382 46.758565, -100.737866 46.758571, -100.737492 46.758627, -100.736994 46.758712, -100.73657 46.758834, -100.736227 46.758981, -100.735839 46.759185, -100.734921 46.759746, -100.732732 46.761117, -100.732487 46.761314, -100.732211 46.761547, -100.731991 46.761785, -100.731724 46.762208, -100.731657 46.76242, -100.731587 46.762729, -100.731544 46.763281, -100.731522 46.763828, -100.731509 46.765641, -100.73148 46.767435, -100.731475 46.767791, -100.731453 46.76981, -100.731451 46.769875, -100.731415 46.771393, -100.731413 46.771493, -100.731361 46.771944, -100.731238 46.772291, -100.731024 46.772699, -100.730723 46.773112, -100.7303 46.773472, -100.730046 46.773665, -100.729462 46.774018, -100.728726 46.774425, -100.726998 46.775366, -100.725744 46.776093, -100.725547 46.776211, -100.72511 46.776539, -100.724883 46.776763, -100.724606 46.777089, -100.724362 46.77741, -100.724185 46.777725, -100.723995 46.778271, -100.72389 46.778805, -100.723884 46.779228, -100.723942 46.779581, -100.723988 46.779769, -100.724177 46.780234, -100.724208 46.78029, -100.72439 46.780618, -100.724564 46.780854, -100.724759 46.781086, -100.724966 46.781303, -100.725269 46.781551, -100.725438 46.781683, -100.726185 46.782149, -100.726818 46.782497, -100.728425 46.783401, -100.729699 46.784103, -100.73053 46.78456, -100.731103 46.784898, -100.731345 46.785041, -100.731648 46.785219, -100.731957 46.785383, -100.732411 46.785625, -100.73384 46.786399, -100.734356 46.786625, -100.734715 46.786776, -100.735202 46.786925, -100.735722 46.787064, -100.736227 46.787167, -100.736913 46.787276, -100.737751 46.787341, -100.738607 46.78736, -100.739362 46.78736, -100.739435 46.78736, -100.744555 46.787369, -100.745732 46.787367, -100.746622 46.787358, -100.74755 46.787349, -100.748589 46.787339, -100.749116 46.787334, -100.752352 46.787337, -100.752373 46.788584, -100.752378 46.788867, -100.752345 46.790153, -100.752324 46.791006, -100.752315 46.791971, -100.752303 46.793532, -100.752044 46.793539, -100.751885 46.79354, -100.751802 46.793541, -100.751487 46.793539, -100.751253 46.793537, -100.751127 46.793537, -100.750995 46.793537, -100.75086 46.793537, -100.75072 46.793537, -100.750574 46.793537, -100.750422 46.793536, -100.750264 46.793535, -100.7501 46.793533, -100.749934 46.793531, -100.749768 46.793529, -100.749603 46.793526, -100.749441 46.793524, -100.749125 46.793521, -100.74897 46.793524, -100.748818 46.793531, -100.748666 46.793549, -100.748517 46.793574, -100.748372 46.793604, -100.748229 46.79364, -100.74809 46.793683, -100.747957 46.79373, -100.747837 46.793779, -100.747734 46.793821, -100.747656 46.793855, -100.747597 46.793885, -100.747544 46.793914, -100.74747 46.793963, -100.747429 46.794002, -100.747352 46.794062, -100.747304 46.794096, -100.747176 46.794185, -100.747099 46.794236, -100.747017 46.794288, -100.746929 46.794338, -100.746834 46.794386, -100.746731 46.79443, -100.746622 46.794469, -100.746506 46.7945, -100.746384 46.794523, -100.746257 46.794534, -100.746124 46.794536, -100.745838 46.794531, -100.745685 46.794529, -100.745367 46.794523, -100.745209 46.79452, -100.744913 46.794518, -100.744787 46.794519, -100.744683 46.79452, -100.744609 46.79452, -100.744541 46.794516, -100.74446 46.794515, -100.744445 46.794516, -100.744375 46.794558, -100.744313 46.79462, -100.744267 46.794677, -100.744208 46.794779, -100.743918 46.794683, -100.743801 46.79467, -100.743565 46.794654, -100.743327 46.794653, -100.74314 46.794663, -100.742521 46.794637, -100.742441 46.794623, -100.742359 46.794589, -100.74211 46.794614, -100.741637 46.794617, -100.740872 46.795036, -100.740925 46.795954, -100.740985 46.797194, -100.740618 46.797024, -100.740087 46.796765, -100.739447 46.796492, -100.738831 46.796191, -100.73681 46.795244, -100.735429 46.794608, -100.735282 46.794538, -100.734856 46.794334, -100.734048 46.793978, -100.732822 46.793483, -100.732242 46.793278, -100.73163 46.79309, -100.731159 46.79294, -100.731156 46.793172, -100.731155 46.793273, -100.731155 46.793334, -100.731153 46.793907, -100.731158 46.794657, -100.732218 46.794718, -100.732769 46.794788, -100.733554 46.794947, -100.734083 46.795098, -100.734933 46.795402, -100.735572 46.795695, -100.736393 46.796221, -100.736737 46.796485, -100.7372 46.796968, -100.738077 46.798137, -100.738209 46.798312, -100.736643 46.798916, -100.736265 46.799053, -100.735332 46.799422, -100.734608 46.799728, -100.734374 46.799831, -100.734158 46.799946, -100.733928 46.800079, -100.733213 46.800559, -100.732889 46.800817, -100.732639 46.801045, -100.73239 46.801287, -100.732289 46.8014, -100.732058 46.801655, -100.731848 46.801953, -100.731595 46.802417, -100.731394 46.80286, -100.731304 46.803131, -100.731245 46.803408, -100.731199 46.803737, -100.731147 46.804239, -100.731116 46.80468, -100.731088 46.80498, -100.730992 46.806235, -100.730964 46.806666, -100.73089 46.807555, -100.730848 46.808276, -100.730839 46.80843, -100.730785 46.809076, -100.730705 46.810041, -100.730653 46.811022, -100.73065 46.811083, -100.730646 46.811145, -100.730634 46.811361, -100.730646 46.811979, -100.730653 46.812079, -100.73047 46.81208, -100.73033 46.812101, -100.730255 46.812155, -100.730251 46.812198, -100.729676 46.812198, -100.728999 46.812227, -100.728595 46.812222, -100.72892 46.812503, -100.730202 46.813786, -100.730535 46.813783, -100.730343 46.816303, -100.730582 46.816302, -100.730549 46.818042, -100.730469 46.820078, -100.730474 46.821616, -100.730476 46.822044, -100.730464 46.823549, -100.729007 46.823556, -100.728199 46.823557, -100.727942 46.823587, -100.727727 46.823635, -100.727269 46.823832, -100.726745 46.824106, -100.725514 46.824751, -100.725094 46.824999, -100.725067 46.825024, -100.724399 46.824729, -100.723655 46.825092, -100.723668 46.826935, -100.723668 46.827123, -100.721192 46.827123, -100.720335 46.827123, -100.720337 46.827053, -100.716126 46.827056, -100.715357 46.827057, -100.71544 46.827008, -100.715664 46.826891, -100.714718 46.826885, -100.713279 46.826878, -100.709698 46.826859, -100.709699 46.827072, -100.713258 46.827092, -100.714681 46.8271, -100.715173 46.827103, -100.715681 46.827157, -100.716094 46.827259, -100.716184 46.827281, -100.716525 46.827376, -100.716768 46.827479, -100.717188 46.827692, -100.717937 46.828093, -100.718562 46.828403, -100.719043 46.828612, -100.719084 46.828622, -100.719617 46.828754, -100.720009 46.828836, -100.720353 46.828869, -100.720813 46.828878, -100.72167 46.828881, -100.724162 46.828894, -100.72525 46.828901, -100.725567 46.828903, -100.725849 46.8289, -100.726167 46.828897, -100.726712 46.828891, -100.726823 46.82889, -100.727357 46.828884, -100.727392 46.829191, -100.727268 46.829199, -100.727236 46.830647, -100.729873 46.829747, -100.729851 46.829299, -100.729682 46.829136, -100.729681 46.828863, -100.730484 46.828869, -100.730493 46.830124, -100.730489 46.831234, -100.730147 46.831234, -100.729278 46.831235, -100.727583 46.831257, -100.726108 46.831325, -100.725101 46.831404, -100.724068 46.831485, -100.723151 46.831609, -100.721885 46.831806, -100.721628 46.831846, -100.720445 46.832055, -100.719938 46.83216, -100.718602 46.832471, -100.716973 46.832911, -100.715957 46.833212, -100.71396 46.833901, -100.711403 46.834784, -100.71073 46.835011, -100.709643 46.835388, -100.709641 46.835664, -100.70964 46.835728, -100.712107 46.836789, -100.712339 46.836662, -100.712448 46.836711, -100.712726 46.837313, -100.712931 46.83797, -100.712426 46.837967, -100.712072 46.838894, -100.711564 46.839727, -100.71188 46.839777, -100.712442 46.839864, -100.712939 46.839942, -100.713149 46.839974, -100.713768 46.840071, -100.714282 46.840151, -100.714741 46.840222, -100.714847 46.840239, -100.715178 46.840291, -100.7157 46.840372, -100.716228 46.840454, -100.716758 46.840537, -100.717077 46.840587, -100.717384 46.840634, -100.717535 46.840658, -100.717479 46.840814, -100.720452 46.84128, -100.720527 46.841685, -100.720531 46.841702, -100.720416 46.841977, -100.72034 46.842447, -100.720378 46.843557, -100.7205 46.844106, -100.720512 46.844158, -100.720798 46.844589, -100.721379 46.845048, -100.721117 46.845154, -100.720867 46.845212, -100.720409 46.845374, -100.720163 46.845424, -100.720192 46.84546, -100.720217 46.845497, -100.720239 46.845536, -100.72035 46.845757, -100.720175 46.845801, -100.720001 46.845852, -100.71983 46.845909, -100.71966 46.845973, -100.719493 46.846044, -100.719329 46.84612, -100.719168 46.846203, -100.718886 46.846357, -100.718608 46.846509, -100.718496 46.846566, -100.718381 46.846616, -100.718263 46.846661, -100.718144 46.846699, -100.718275 46.846896, -100.718314 46.846959, -100.718347 46.847025, -100.718374 46.847094, -100.718394 46.847166, -100.718407 46.847239, -100.718413 46.847313, -100.718412 46.847387, -100.718403 46.84746, -100.718387 46.847557, -100.718311 46.847997, -100.718302 46.84807, -100.7183 46.848144, -100.718305 46.848212, -100.718316 46.848279, -100.718332 46.848345, -100.718482 46.848849, -100.718841 46.848799, -100.718981 46.848783, -100.719121 46.848774, -100.719262 46.848771, -100.719402 46.848775, -100.719542 46.848787, -100.719873 46.848821, -100.719934 46.848741, -100.719998 46.848664, -100.720066 46.848589, -100.720137 46.848518, -100.72052 46.848144, -100.7206 46.848069, -100.720683 46.847998, -100.720768 46.84793, -100.721423 46.848194, -100.721961 46.847875, -100.726488 46.847763, -100.726501 46.846458, -100.726529 46.84571, -100.726952 46.845222, -100.727012 46.845243, -100.727353 46.845633, -100.727574 46.845886, -100.72766 46.845957, -100.727761 46.846018, -100.727875 46.846068, -100.728001 46.846106, -100.728126 46.846169, -100.728259 46.846218, -100.728681 46.846221, -100.728743 46.846222, -100.729002 46.846199, -100.729124 46.846189, -100.729341 46.846134, -100.729612 46.84608, -100.73059 46.846081, -100.730589 46.846125, -100.730579 46.846617, -100.730562 46.847977, -100.730555 46.848641, -100.730551 46.849157, -100.730526 46.852495, -100.730478 46.854738, -100.730463 46.856744, -100.730448 46.857963, -100.730423 46.860343, -100.730406 46.862375, -100.733337 46.862353, -100.733496 46.862331, -100.733589 46.862298, -100.73368 46.862219, -100.733734 46.862118, -100.733737 46.861654, -100.733759 46.861441, -100.733787 46.861308, -100.733895 46.861142, -100.734083 46.860962, -100.734164 46.860883, -100.734793 46.860423, -100.735198 46.860162, -100.735498 46.85999, -100.736142 46.859696, -100.7367 46.859458, -100.736695 46.859243, -100.736681 46.858726, -100.73668 46.858672, -100.736689 46.858092, -100.73666 46.857054, -100.73668 46.856919, -100.736701 46.856864, -100.736761 46.856811, -100.736872 46.856774, -100.738446 46.856766, -100.739639 46.856751, -100.739832 46.856768, -100.739937 46.856802, -100.739994 46.856836, -100.740045 46.856948, -100.740049 46.857064, -100.740018 46.857287, -100.739971 46.857436, -100.739919 46.857566, -100.739821 46.857738, -100.739733 46.857853, -100.739538 46.858085, -100.739086 46.85861, -100.738877 46.858842, -100.738721 46.858964, -100.739304 46.859352, -100.739507 46.859508, -100.739569 46.859567, -100.739683 46.859696, -100.73978 46.859832, -100.739849 46.859954, -100.739917 46.860107, -100.739965 46.860263, -100.739993 46.860422, -100.740004 46.860642, -100.740014 46.861096, -100.740049 46.861295, -100.740102 46.861491, -100.740238 46.861766, -100.740371 46.861948, -100.740707 46.862358, -100.739853 46.862713, -100.739319 46.863004, -100.739091 46.863138, -100.738835 46.863306, -100.738654 46.863442, -100.738474 46.863596, -100.738311 46.863758, -100.738234 46.863844, -100.738125 46.864006, -100.738036 46.864171, -100.738 46.864255, -100.737969 46.864397, -100.737935 46.864559, -100.73791 46.864955, -100.737847 46.865763, -100.737824 46.866224, -100.737779 46.867025, -100.739621 46.867012, -100.740955 46.866998, -100.742963 46.867016, -100.74582 46.867011, -100.747142 46.867003, -100.747184 46.866749, -100.747249 46.86652, -100.74735 46.866369, -100.746928 46.866194, -100.746718 46.866162, -100.746185 46.866205, -100.745779 46.866211, -100.745505 46.8662, -100.745244 46.86616, -100.744981 46.866094, -100.744739 46.865969, -100.744535 46.865793, -100.744412 46.865687, -100.744039 46.865316, -100.743769 46.864994, -100.743561 46.864596, -100.743427 46.864256, -100.743375 46.863984, -100.743312 46.863604, -100.743279 46.863247, -100.743218 46.862931, -100.743078 46.862611, -100.742948 46.862386, -100.74282 46.862209, -100.742632 46.861974, -100.742474 46.861802, -100.742212 46.861505, -100.742067 46.861265, -100.74199 46.861113, -100.741925 46.860891, -100.74193 46.860603, -100.741964 46.860523, -100.74202 46.860468, -100.742097 46.860417, -100.742205 46.860395, -100.742696 46.860398, -100.745149 46.86039, -100.745132 46.859289, -100.745138 46.859166, -100.746549 46.859161, -100.748036 46.85916, -100.74836 46.859139, -100.74849 46.859089, -100.748656 46.858999, -100.748811 46.858846, -100.748972 46.858614, -100.749111 46.858399, -100.749256 46.858249, -100.749538 46.858087, -100.749831 46.857973, -100.749927 46.857948, -100.75005 46.857918, -100.750176 46.857906, -100.750551 46.857888, -100.751143 46.857885, -100.751465 46.857887, -100.751468 46.857715, -100.7515 46.856963, -100.751513 46.856669, -100.751532 46.856207, -100.751542 46.855968, -100.751744 46.856077, -100.755338 46.856099, -100.75578 46.856101, -100.756259 46.856053, -100.756672 46.856061, -100.756745 46.856063, -100.756821 46.856065, -100.756855 46.856038, -100.756881 46.856062, -100.757071 46.855929, -100.757521 46.855623, -100.757699 46.855502, -100.757978 46.855312, -100.758484 46.85497, -100.758811 46.854778, -100.758962 46.854701, -100.759299 46.854546, -100.759406 46.854686, -100.75956 46.854593, -100.758986 46.853892, -100.7587 46.853599, -100.758291 46.853271, -100.75787 46.852986, -100.757814 46.852947, -100.757499 46.852755, -100.757024 46.852464, -100.758542 46.852486, -100.758578 46.852486, -100.759903 46.852494, -100.760201 46.85249, -100.762161 46.852503, -100.764858 46.852522, -100.766314 46.852525, -100.766846 46.85253, -100.767559 46.852536, -100.767674 46.852537, -100.768739 46.852546, -100.769126 46.852542, -100.769387 46.852539, -100.771591 46.852529, -100.772376 46.852526, -100.772295 46.854787, -100.772294 46.85517, -100.772291 46.856177, -100.772276 46.858812, -100.772262 46.859647, -100.772259 46.859824, -100.772247 46.86054, -100.772243 46.86083, -100.772244 46.8612, -100.772245 46.862157, -100.772246 46.862795, -100.772238 46.863405, -100.772231 46.863893, -100.772221 46.864731, -100.772221 46.865318, -100.772214 46.86598, -100.772225 46.867041, -100.772229 46.867284, -100.772268 46.867909, -100.772299 46.868268, -100.772373 46.868796, -100.772414 46.869052, -100.772582 46.870109, -100.772663 46.870728, -100.772669 46.871029, -100.772694 46.871664, -100.772675 46.872212, -100.772617 46.872917, -100.772599 46.873115, -100.772541 46.873752, -100.772445 46.875103, -100.772428 46.875353, -100.772285 46.876922, -100.772212 46.877894, -100.772205 46.877993, -100.772138 46.878885, -100.771913 46.8815, -100.772342 46.8815, -100.772413 46.880656, -100.77262 46.877996, -100.772724 46.876652, -100.772844 46.875362, -100.772864 46.8751, -100.773012 46.873124, -100.773022 46.872995, -100.773077 46.872388, -100.773076 46.871619, -100.772958 46.870334, -100.772793 46.869243, -100.772758 46.86901, -100.772652 46.868386, -100.772579 46.867878, -100.772538 46.867025, -100.772532 46.866873, -100.772561 46.864725, -100.772564 46.863891, -100.772567 46.863406, -100.772571 46.862161, -100.77272 46.862166, -100.772755 46.862163, -100.77282 46.862158, -100.772844 46.862156, -100.772884 46.862139, -100.772959 46.862106, -100.772999 46.862085, -100.773111 46.862027, -100.773122 46.862006, -100.773173 46.861911, -100.773342 46.861596, -100.773372 46.86154, -100.773561 46.861127, -100.773643 46.860799, -100.773727 46.860466, -100.773827 46.860076, -100.773915 46.859748, -100.773987 46.859445, -100.774011 46.859345, -100.774077 46.859068, -100.77413 46.858848, -100.774191 46.858594, -100.774247 46.858356, -100.774295 46.858161, -100.774319 46.858042, -100.774328 46.857992, -100.774355 46.857861, -100.77436 46.857835, -100.774411 46.857449, -100.774446 46.857311, -100.774532 46.856303, -100.774646 46.856313, -100.774705 46.856318, -100.774758 46.856311, -100.774824 46.856288, -100.774821 46.856312, -100.77486 46.856312, -100.776459 46.856315, -100.776457 46.85615, -100.776455 46.855872, -100.774911 46.855874, -100.774868 46.855874, -100.774845 46.855875, -100.774546 46.855878, -100.774552 46.85554, -100.774559 46.855165, -100.774554 46.855025, -100.774526 46.854163, -100.77418 46.854187, -100.774185 46.853823, -100.773118 46.853809, -100.772822 46.853824, -100.772686 46.85384, -100.772723 46.852883, -100.772727 46.852524, -100.772867 46.852523, -100.772974 46.852523, -100.7742 46.852734, -100.774203 46.852535, -100.77459 46.852539, -100.774832 46.85254, -100.775208 46.852544, -100.775859 46.852548, -100.777891 46.85256, -100.778228 46.852558, -100.778408 46.852558, -100.778968 46.852556, -100.779207 46.852556, -100.779384 46.852556, -100.779509 46.852556, -100.780274 46.852557, -100.780354 46.852557, -100.781852 46.85256, -100.78214 46.852552, -100.782312 46.852552, -100.782304 46.853693, -100.783135 46.853692, -100.783143 46.852557, -100.783207 46.852557, -100.785246 46.852565, -100.786071 46.852564, -100.786707 46.852564, -100.786944 46.852564, -100.788121 46.85468, -100.787818 46.854886, -100.787641 46.855008, -100.787641 46.855304, -100.787716 46.855406, -100.787785 46.855501, -100.787665 46.855796, -100.787519 46.856492, -100.787418 46.856398, -100.787403 46.856487, -100.787388 46.856579, -100.787309 46.856838, -100.787233 46.857088, -100.787156 46.857341, -100.787082 46.857606, -100.78731 46.857609, -100.787368 46.85796, -100.787271 46.858094, -100.787226 46.858155, -100.787295 46.85844, -100.787323 46.858559, -100.787355 46.858688, -100.787499 46.858975, -100.787646 46.859263, -100.787934 46.859487, -100.788145 46.859651, -100.788254 46.859682, -100.788714 46.859807, -100.78883 46.85988, -100.78887 46.859905, -100.788927 46.859941, -100.789355 46.860042, -100.789591 46.859997, -100.789779 46.860029, -100.789988 46.860008, -100.790595 46.859947, -100.791292 46.859717, -100.792576 46.859083, -100.792604 46.859098, -100.792626 46.859106, -100.792649 46.859112, -100.792694 46.859125, -100.792716 46.859132, -100.792739 46.859137, -100.792785 46.859148, -100.792847 46.858951, -100.792155 46.858637, -100.792541 46.85829, -100.792532 46.857991, -100.792565 46.85669, -100.792449 46.856532, -100.792125 46.856424, -100.791759 46.856349, -100.791947 46.85582, -100.79253 46.855899, -100.793024 46.855934, -100.793494 46.855916, -100.793629 46.85592, -100.793629 46.857028, -100.793617 46.857707, -100.793615 46.857801, -100.793604 46.858469, -100.793596 46.858958, -100.793595 46.859013, -100.793595 46.859064, -100.793594 46.859176, -100.793594 46.859197, -100.793587 46.86102, -100.793581 46.861854, -100.793581 46.861924, -100.797556 46.861916, -100.797524 46.860708, -100.797518 46.860583, -100.797643 46.860702, -100.798174 46.861211, -100.798291 46.861279, -100.799017 46.861705, -100.800019 46.861738, -100.800499 46.861754, -100.801186 46.86132, -100.80127 46.861179, -100.801311 46.861164, -100.801901 46.860478, -100.802181 46.859935, -100.802222 46.859897, -100.802893 46.860144, -100.803489 46.860362, -100.802656 46.861676, -100.802643 46.861696, -100.803254 46.863037, -100.80345 46.863466, -100.80525 46.863712, -100.805958 46.86381, -100.806167 46.863287, -100.80585 46.863291, -100.805833 46.862789, -100.80583 46.862696, -100.805482 46.861812, -100.805851 46.861228, -100.806091 46.860756, -100.806105 46.86073, -100.806898 46.860937, -100.807001 46.861004, -100.807109 46.861075, -100.807604 46.861213, -100.807657 46.861062, -100.807667 46.860902, -100.808084 46.860902, -100.808107 46.860846, -100.808283 46.860421, -100.808381 46.860189, -100.80856 46.8597, -100.80847 46.859435, -100.808292 46.859251, -100.808143 46.858864, -100.808096 46.858332, -100.808084 46.858192, -100.808085 46.858163, -100.808113 46.85754, -100.808381 46.857295, -100.808564 46.857016, -100.808849 46.857104, -100.809961 46.857416, -100.810228 46.857496, -100.809816 46.858572, -100.809712 46.858965, -100.812042 46.859333, -100.812132 46.859149, -100.812638 46.858783, -100.812042 46.858416, -100.81138 46.857849, -100.811442 46.857872, -100.811615 46.857936, -100.811686 46.857962, -100.811785 46.858002, -100.811818 46.858015, -100.811947 46.857884, -100.81251 46.857269, -100.812826 46.857342, -100.812827 46.857228, -100.813517 46.857143, -100.813949 46.857261, -100.814304 46.857301, -100.814695 46.857348, -100.81469 46.856731, -100.814658 46.852843, -100.81463 46.850802, -100.813801 46.850794, -100.813438 46.850904, -100.813246 46.850954, -100.813004 46.850867, -100.812631 46.850726, -100.812508 46.850726, -100.812317 46.850718, -100.812122 46.850709, -100.81204 46.850819, -100.811801 46.851079, -100.811724 46.851079, -100.811401 46.851018, -100.811142 46.851006, -100.811125 46.851006, -100.811032 46.851002, -100.809387 46.851017, -100.808848 46.850941, -100.80874 46.850618, -100.808643 46.85014, -100.808639 46.849833, -100.808626 46.848944, -100.808619 46.848409, -100.807988 46.847033, -100.808085 46.846752, -100.808113 46.84668, -100.808159 46.846588, -100.808214 46.846498, -100.808279 46.846412, -100.808354 46.846329, -100.808437 46.846249, -100.808528 46.846175, -100.808627 46.846105, -100.808734 46.84604, -100.808847 46.845981, -100.808966 46.845928, -100.80909 46.845881, -100.80922 46.845841, -100.809353 46.845807, -100.809489 46.84578, -100.809628 46.84576, -100.809769 46.845747, -100.809911 46.845741, -100.80994 46.845741, -100.81034 46.845739, -100.810391 46.845738, -100.810487 46.845731, -100.810582 46.845716, -100.810673 46.845695, -100.810761 46.845666, -100.810844 46.845632, -100.810921 46.845591, -100.810992 46.845545, -100.811054 46.845494, -100.811108 46.845439, -100.811153 46.84538, -100.811189 46.845318, -100.811214 46.845254, -100.81123 46.845188, -100.811235 46.845122, -100.811232 46.845078, -100.811199 46.844757, -100.811134 46.844147, -100.811131 46.844095, -100.811136 46.844017, -100.811152 46.84394, -100.811178 46.843864, -100.811214 46.843791, -100.81126 46.84372, -100.811315 46.843652, -100.81138 46.843588, -100.811453 46.843529, -100.811533 46.843474, -100.811621 46.843425, -100.811712 46.843383, -100.81211 46.843219, -100.812314 46.843124, -100.812562 46.842952, -100.812789 46.8428, -100.812875 46.84276, -100.812966 46.842726, -100.813062 46.842699, -100.813161 46.842679, -100.813263 46.842666, -100.813366 46.84266, -100.813392 46.84266, -100.8138 46.84266, -100.813894 46.842661, -100.814022 46.84267, -100.814149 46.842686, -100.814273 46.842709, -100.81433 46.842721, -100.814571 46.842779, -100.814598 46.842786, -100.814721 46.842809, -100.814805 46.84282, -100.815873 46.842941, -100.815985 46.842951, -100.816107 46.842954, -100.816228 46.84295, -100.816349 46.842939, -100.816467 46.84292, -100.816583 46.842895, -100.816696 46.842863, -100.816803 46.842824, -100.816906 46.842779, -100.817394 46.842551, -100.817535 46.842451, -100.817659 46.842538, -100.817761 46.842609, -100.81778 46.842622, -100.817855 46.842674, -100.817896 46.84266, -100.81793 46.842648, -100.818646 46.842402, -100.819302 46.842492, -100.820376 46.842876, -100.820413 46.842665, -100.820809 46.842734, -100.821544 46.842796, -100.822088 46.842979, -100.822249 46.843106, -100.822378 46.8433, -100.822479 46.84345, -100.822531 46.843696, -100.822601 46.843742, -100.823062 46.844051, -100.823074 46.844182, -100.823639 46.84431, -100.824302 46.84428, -100.824792 46.843969, -100.824971 46.843562, -100.824895 46.843135, -100.824876 46.843033, -100.824828 46.842767, -100.824478 46.842692, -100.824501 46.842562, -100.824537 46.842265, -100.824737 46.841715, -100.824856 46.841079, -100.824859 46.841063, -100.824862 46.840553, -100.825513 46.840629, -100.825454 46.840548, -100.825024 46.839963, -100.825647 46.839617, -100.825761 46.839272, -100.825357 46.839316, -100.82528 46.839046, -100.825019 46.838939, -100.824755 46.838829, -100.824601 46.838767, -100.824558 46.838794, -100.824536 46.838784, -100.824568 46.838752, -100.824793 46.83853, -100.824994 46.838332, -100.825015 46.837968, -100.824512 46.837448, -100.824311 46.837422, -100.823555 46.837325, -100.823561 46.837317, -100.823702 46.837133, -100.8237 46.83679, -100.824016 46.836792, -100.824211 46.836589, -100.824222 46.836579, -100.824356 46.836632, -100.824998 46.836889, -100.825058 46.836912, -100.825391 46.837049, -100.826208 46.837371, -100.826248 46.837378, -100.826351 46.837394, -100.826974 46.837513, -100.827086 46.837235, -100.826648 46.835904, -100.826601 46.835847, -100.826549 46.835791, -100.82649 46.835742, -100.826477 46.835728, -100.826126 46.835418, -100.826043 46.835342, -100.825968 46.835257, -100.82591 46.835171, -100.825865 46.835081, -100.825834 46.834988, -100.825818 46.834894, -100.825816 46.834803, -100.825828 46.834704, -100.825855 46.834611, -100.825883 46.834545, -100.826 46.83431, -100.826173 46.833966, -100.826199 46.833894, -100.826215 46.83382, -100.826219 46.833746, -100.826212 46.833672, -100.826194 46.833599, -100.826164 46.833528, -100.826124 46.833459, -100.82608 46.833396, -100.826006 46.83332, -100.826548 46.832149, -100.827177 46.832202, -100.82755 46.832208, -100.827756 46.832263, -100.827909 46.832386, -100.828026 46.832306, -100.828315 46.832567, -100.828827 46.832982, -100.82884 46.832993, -100.828896 46.832962, -100.829134 46.832832, -100.829307 46.832971, -100.829324 46.832985, -100.829338 46.832996, -100.829348 46.833004, -100.829379 46.833026, -100.829399 46.833041, -100.829425 46.833059, -100.829451 46.833077, -100.829478 46.833094, -100.829505 46.833111, -100.829533 46.833128, -100.829561 46.833144, -100.829589 46.83316, -100.829618 46.833176, -100.829641 46.833188, -100.829666 46.833201, -100.829686 46.833211, -100.829637 46.833323, -100.829626 46.833368, -100.829432 46.834172, -100.829747 46.833508, -100.829795 46.833395, -100.830324 46.832184, -100.83039 46.832214, -100.830729 46.832296, -100.831869 46.832469, -100.832253 46.832505, -100.832386 46.83251, -100.832566 46.83249, -100.832604 46.832542, -100.832721 46.83268, -100.832816 46.832791, -100.832896 46.832893, -100.833108 46.833164, -100.83319 46.833269, -100.833341 46.833462, -100.833638 46.833842, -100.833867 46.834135, -100.834078 46.834405, -100.834518 46.834983, -100.834937 46.835644, -100.835337 46.836432, -100.835421 46.836576, -100.835889 46.837381, -100.835958 46.837499, -100.836021 46.837608, -100.836101 46.837746, -100.836133 46.837746, -100.836193 46.837755, -100.836246 46.837778, -100.836274 46.837799, -100.836677 46.838259, -100.836944 46.838324, -100.837011 46.838333, -100.837324 46.838232, -100.837261 46.837878, -100.836981 46.837363, -100.836725 46.836791, -100.836576 46.836477, -100.836486 46.83633, -100.836478 46.836239, -100.836546 46.83616, -100.836765 46.835979, -100.836969 46.835763, -100.837188 46.835378, -100.837302 46.835272, -100.837506 46.835068, -100.837782 46.834788, -100.838073 46.83449, -100.838278 46.83425, -100.838312 46.83421, -100.838364 46.834131, -100.838458 46.833915, -100.838636 46.833727, -100.838768 46.833572, -100.838912 46.833347, -100.839091 46.833039, -100.839259 46.832723, -100.839369 46.83254, -100.839388 46.832415, -100.839439 46.8321, -100.839511 46.831809, -100.839594 46.831633, -100.839667 46.831251, -100.83969 46.831128, -100.839583 46.830708, -100.839522 46.830426, -100.8394 46.830075, -100.839202 46.829724, -100.83911 46.829487, -100.839125 46.829236, -100.837569 46.828, -100.836364 46.827261, -100.835983 46.827077, -100.834991 46.826658, -100.833358 46.825956, -100.831925 46.825385, -100.831488 46.825161, -100.831285 46.822961, -100.831878 46.823182, -100.832292 46.823564, -100.832706 46.823794, -100.832935 46.823924, -100.833134 46.823954, -100.833196 46.823901, -100.832999 46.823443, -100.833 46.823329, -100.833062 46.823223, -100.833553 46.822734, -100.833844 46.822644, -100.834441 46.82243, -100.834914 46.822331, -100.835967 46.822079, -100.836577 46.822033, -100.836806 46.822079, -100.837035 46.822186, -100.838332 46.823178, -100.841368 46.825535, -100.841872 46.825726, -100.842711 46.825924, -100.843994 46.826436, -100.845688 46.82729, -100.847 46.827656, -100.847442 46.827687, -100.847946 46.8279, -100.849289 46.828404, -100.849929 46.828602, -100.850311 46.828633, -100.850708 46.828625, -100.851562 46.828724, -100.853729 46.829518, -100.856125 46.830533, -100.857681 46.831189, -100.857925 46.831318, -100.858459 46.831662, -100.859588 46.83244, -100.859768 46.832589, -100.860656 46.833325, -100.861847 46.834347, -100.862137 46.834599, -100.86264 46.835286, -100.8638 46.836308, -100.863952 46.836514, -100.864558 46.837337, -100.864917 46.837824, -100.86513 46.838166, -100.865431 46.838679, -100.865582 46.838937, -100.865717 46.839167, -100.865815 46.839344, -100.865887 46.839499, -100.866033 46.839763, -100.866933 46.841472, -100.868073 46.843434, -100.868096 46.843499, -100.868154 46.843494, -100.868354 46.843813, -100.868507 46.844112, -100.868756 46.844531, -100.868823 46.844659, -100.868937 46.844816, -100.869294 46.845303, -100.869492 46.845768, -100.869782 46.846402, -100.869935 46.846882, -100.869996 46.847218, -100.870362 46.847722, -100.870523 46.848241, -100.870606 46.848507, -100.870988 46.849667, -100.871299 46.850134, -100.872363 46.850074, -100.872597 46.850061, -100.873157 46.85003, -100.87325 46.850353, -100.873574 46.851437, -100.873819 46.851595, -100.874822 46.852262, -100.87488 46.852415, -100.874925 46.852536, -100.87489 46.85276, -100.874567 46.853128, -100.874366 46.853399, -100.8745 46.853872, -100.874892 46.855503, -100.874943 46.855958, -100.875053 46.856265, -100.875168 46.856466, -100.876039 46.857601, -100.87614 46.857635, -100.876338 46.857657, -100.876552 46.857681, -100.876722 46.85775, -100.876852 46.857927, -100.876956 46.858245, -100.876995 46.85846, -100.877118 46.858622, -100.877245 46.858708, -100.877501 46.858747, -100.878227 46.858727, -100.878704 46.858708, -100.878816 46.858727, -100.87885 46.858098, -100.878816 46.856718, -100.880622 46.856704, -100.880713 46.856659, -100.880733 46.856634, -100.880737 46.856194, -100.880825 46.85592, -100.881023 46.855669, -100.881024 46.855455, -100.882743 46.855445, -100.884139 46.855437, -100.884649 46.855428, -100.885902 46.855428, -100.886043 46.855393, -100.886122 46.855328, -100.886157 46.855272, -100.886191 46.855171, -100.886195 46.854592, -100.88618 46.853442, -100.887807 46.853436, -100.887985 46.853435, -100.889004 46.853436, -100.889196 46.853425, -100.889245 46.853415, -100.889356 46.853386, -100.889411 46.853357, -100.88948 46.853208, -100.889436 46.85048, -100.889421 46.848007, -100.889406 46.846514, -100.889375 46.846319, -100.889335 46.846256, -100.889291 46.84624, -100.889173 46.846222, -100.888377 46.846212, -100.88779 46.846219, -100.886083 46.846241, -100.884359 46.846257, -100.882559 46.846264, -100.880888 46.846279, -100.880864 46.843874, -100.880862 46.843689, -100.880859 46.843432, -100.880874 46.843213, -100.880884 46.843073, -100.880867 46.841504, -100.880865 46.841272, -100.880841 46.840366, -100.87751 46.840413, -100.876468 46.840442, -100.87725 46.841538, -100.877893 46.842426, -100.877486 46.842482, -100.877836 46.84299, -100.877969 46.843166, -100.875023 46.843331, -100.871111 46.843395, -100.871175 46.843546, -100.871312 46.843854, -100.871355 46.84395, -100.871497 46.844268, -100.871672 46.844709, -100.871551 46.844968, -100.871536 46.845076, -100.871629 46.845434, -100.871898 46.84618, -100.872127 46.846527, -100.872281 46.847024, -100.872265 46.847132, -100.8722 46.847193, -100.872108 46.847238, -100.871827 46.84729, -100.871503 46.84648, -100.870745 46.844484, -100.870545 46.843957, -100.870506 46.843843, -100.870087 46.842612, -100.870027 46.842457, -100.86987 46.842051, -100.86963 46.841276, -100.869511 46.840714, -100.869429 46.840128, -100.869292 46.838882, -100.869295 46.838022, -100.869247 46.836971, -100.86956 46.837046, -100.87095 46.837322, -100.872437 46.837557, -100.873973 46.837824, -100.874282 46.837881, -100.878402 46.838642, -100.880292 46.838973, -100.882491 46.839358, -100.888253 46.840384, -100.891201 46.840926, -100.892269 46.841132, -100.892863 46.841261, -100.892859 46.84102, -100.893374 46.841138, -100.895189 46.841587, -100.896135 46.841851, -100.897267 46.84216, -100.899751 46.842838, -100.900577 46.843063, -100.901998 46.843468, -100.90201 46.843715, -100.903317 46.844075, -100.904742 46.844564, -100.905322 46.844817, -100.905657 46.845003, -100.905913 46.845199, -100.906279 46.845564, -100.906628 46.845957, -100.906793 46.846046, -100.907025 46.846138, -100.907821 46.846298, -100.907514 46.845462, -100.907416 46.84521, -100.907305 46.844924, -100.906889 46.843848, -100.90655 46.842967, -100.906519 46.842875, -100.907495 46.842863, -100.907653 46.842861, -100.907869 46.842858, -100.908608 46.842849, -100.908696 46.842848, -100.908682 46.842619, -100.908631 46.842368, -100.90853 46.842051, -100.908352 46.84159, -100.908141 46.841045, -100.908087 46.84102, -100.907989 46.841038, -100.907817 46.84107, -100.907293 46.841166, -100.906964 46.840336, -100.906855 46.840063, -100.907292 46.839979, -100.907401 46.839944, -100.907316 46.839703, -100.907168 46.8394, -100.907512 46.839349, -100.907738 46.83929, -100.907682 46.839246, -100.9077 46.839146, -100.907827 46.839025, -100.908025 46.838946, -100.908135 46.83891, -100.908215 46.838828, -100.908237 46.838676, -100.908219 46.838562, -100.908123 46.838383, -100.907929 46.838177, -100.907749 46.838009, -100.90758 46.837927, -100.907292 46.83785, -100.907159 46.837795, -100.907074 46.837735, -100.907036 46.837659, -100.90703 46.837488, -100.906989 46.837289, -100.90687 46.837058, -100.906656 46.836905, -100.906346 46.836763, -100.906227 46.836722, -100.906103 46.836724, -100.906174 46.836405, -100.906103 46.83633, -100.906054 46.836102, -100.905724 46.836022, -100.905375 46.835865, -100.905316 46.835839, -100.905286 46.835743, -100.905327 46.835743, -100.905422 46.835704, -100.905592 46.835615, -100.905651 46.8355, -100.905652 46.835334, -100.905573 46.835254, -100.905467 46.835213, -100.904941 46.835227, -100.904477 46.834451, -100.904406 46.834399, -100.904344 46.834394, -100.904335 46.834135, -100.90434 46.833692, -100.90444 46.833201, -100.904452 46.832939, -100.904425 46.832768, -100.904402 46.832746, -100.904655 46.832798, -100.904965 46.832954, -100.905397 46.833213, -100.905538 46.833287, -100.905888 46.8334, -100.906112 46.833458, -100.906316 46.833564, -100.906485 46.833637, -100.906632 46.833663, -100.906699 46.8336, -100.906787 46.833561, -100.907029 46.833533, -100.907167 46.833531, -100.907397 46.833565, -100.907789 46.833682, -100.908013 46.833726, -100.908588 46.833711, -100.909042 46.833637, -100.909291 46.833619, -100.909414 46.833626, -100.909534 46.833634, -100.909976 46.833603, -100.910282 46.833626, -100.910575 46.833688, -100.910938 46.833753, -100.911377 46.833822, -100.911566 46.833899, -100.911747 46.83411, -100.911967 46.834273, -100.912082 46.834367, -100.912142 46.834518, -100.912139 46.834632, -100.912052 46.834695, -100.911901 46.834736, -100.911811 46.834737, -100.911649 46.834673, -100.911508 46.83459, -100.911214 46.834904, -100.911172 46.835095, -100.911145 46.835299, -100.911107 46.835405, -100.911118 46.835518, -100.911163 46.835622, -100.918608 46.835471, -100.923068 46.835404, -100.922921 46.828819, -100.912414 46.828784, -100.912481 46.828201, -100.912515 46.827911, -100.912503 46.827674, -100.91248 46.827215, -100.91241 46.825793, -100.912406 46.825209, -100.912405 46.825007, -100.91225 46.824785, -100.912184 46.824526, -100.912078 46.824109, -100.912398 46.824076, -100.912417 46.824074, -100.912464 46.824069, -100.912654 46.824049, -100.914195 46.823888, -100.914259 46.823377, -100.914246 46.822826, -100.913671 46.822882, -100.913294 46.822919, -100.912816 46.822966, -100.913011 46.822768, -100.913131 46.82267, -100.913197 46.822586, -100.913202 46.822565, -100.913251 46.822365, -100.913241 46.822096, -100.91323 46.821621, -100.913218 46.819651, -100.913213 46.819239, -100.913199 46.818445, -100.913182 46.817463, -100.91311 46.817361, -100.912898 46.816972, -100.912905 46.81669, -100.912491 46.816707, -100.912466 46.816708, -100.912485 46.819219, -100.912506 46.821603, -100.91251 46.822624, -100.912509 46.822658, -100.912445 46.822667, -100.912389 46.822675, -100.911429 46.822804, -100.910886 46.822878, -100.910558 46.822922, -100.910075 46.822997, -100.910062 46.822938, -100.910068 46.822891, -100.910106 46.82279, -100.910101 46.822757, -100.910041 46.822335, -100.909906 46.821812, -100.90984 46.821558, -100.909703 46.821089, -100.909565 46.821103, -100.909213 46.821128, -100.907871 46.821198, -100.906926 46.821216, -100.905666 46.821239, -100.904853 46.821248, -100.904847 46.820719, -100.904842 46.820212, -100.904836 46.819699, -100.904827 46.819179, -100.904802 46.818149, -100.905036 46.818125, -100.905459 46.818118, -100.905501 46.818094, -100.90556 46.81806, -100.905568 46.818027, -100.905555 46.817518, -100.905512 46.817291, -100.90546 46.817182, -100.905292 46.817123, -100.904776 46.817099, -100.903268 46.817113, -100.901742 46.817126, -100.90104 46.817132, -100.900788 46.817135, -100.900243 46.817161, -100.899808 46.817182, -100.89966 46.817189, -100.899076 46.817174, -100.89786 46.817143, -100.89729 46.817145, -100.897135 46.817159, -100.896952 46.81725, -100.896873 46.817376, -100.896665 46.817219, -100.896687 46.816677, -100.896479 46.816643, -100.895966 46.816632, -100.895063 46.816562, -100.894329 46.816555, -100.893637 46.816566, -100.892971 46.816596, -100.892557 46.816651, -100.891854 46.816738, -100.890554 46.816846, -100.890004 46.816892, -100.889022 46.816947, -100.88861 46.816979, -100.888097 46.817019, -100.887324 46.817108, -100.886662 46.817176, -100.884741 46.817312, -100.883663 46.817434, -100.883633 46.817438, -100.883409 46.816893, -100.88719 46.816669, -100.887562 46.816647, -100.888098 46.816479, -100.888524 46.816312, -100.88932 46.81609, -100.889932 46.81596, -100.890746 46.815886, -100.891672 46.815867, -100.892246 46.815886, -100.893154 46.815886, -100.894246 46.815979, -100.895061 46.816127, -100.895948 46.816151, -100.897154 46.816219, -100.897932 46.816145, -100.899154 46.816053, -100.899743 46.815926, -100.900382 46.815863, -100.90076 46.815707, -100.901447 46.815424, -100.902283 46.81496, -100.902635 46.814812, -100.902961 46.814799, -100.904059 46.814348, -100.902538 46.814453, -100.902233 46.814414, -100.901962 46.814311, -100.901747 46.814202, -100.900848 46.81421, -100.900636 46.814172, -100.900591 46.814154, -100.900497 46.814115, -100.900362 46.814052, -100.900293 46.81402, -100.900107 46.813923, -100.900127 46.813336, -100.900136 46.812568, -100.900162 46.812442, -100.900196 46.812404, -100.900217 46.81238, -100.900388 46.81234, -100.900499 46.812335, -100.900697 46.812326, -100.900854 46.812325, -100.90178 46.812329, -100.901777 46.812196, -100.900855 46.8122, -100.900696 46.812201, -100.900696 46.811913, -100.899681 46.811921, -100.898929 46.811927, -100.899137 46.812582, -100.899497 46.812572, -100.899499 46.813851, -100.899498 46.813994, -100.899499 46.814058, -100.891239 46.814129, -100.884854 46.814158, -100.884931 46.812372, -100.884913 46.811306, -100.884671 46.811154, -100.884572 46.811004, -100.884575 46.81072, -100.884522 46.810461, -100.884438 46.810359, -100.884349 46.810297, -100.884175 46.810271, -100.883945 46.810275, -100.883186 46.810376, -100.882408 46.810492, -100.881668 46.810641, -100.880781 46.810652, -100.880648 46.810697, -100.88064 46.809612, -100.880644 46.809198, -100.880667 46.807364, -100.880647 46.806953, -100.881692 46.806943, -100.882194 46.806396, -100.882273 46.806363, -100.883085 46.806354, -100.883154 46.806385, -100.883181 46.806432, -100.883198 46.806929, -100.884074 46.806921, -100.884286 46.806916, -100.884264 46.80512, -100.884272 46.803505, -100.883835 46.803288, -100.883876 46.802969, -100.883896 46.802818, -100.883688 46.802736, -100.883417 46.802726, -100.88328 46.802722, -100.883134 46.802725, -100.883 46.802488, -100.882855 46.802234, -100.882547 46.80223, -100.882063 46.802235, -100.880661 46.802205, -100.880691 46.80127, -100.880702 46.800935, -100.88071 46.800715, -100.880721 46.800363, -100.880729 46.800117, -100.879906 46.800106, -100.879895 46.799848, -100.878655 46.799776, -100.877902 46.799733, -100.877725 46.799759, -100.876032 46.799768, -100.87555 46.799774, -100.874732 46.799785, -100.874716 46.799711, -100.874695 46.799645, -100.874692 46.799558, -100.874683 46.799504, -100.87467 46.799446, -100.874654 46.799387, -100.874638 46.799327, -100.874623 46.799271, -100.87461 46.799219, -100.8746 46.799174, -100.874581 46.799072, -100.87457 46.799014, -100.874562 46.798966, -100.874511 46.798905, -100.874357 46.798893, -100.874271 46.798887, -100.874079 46.798844, -100.873986 46.798805, -100.873904 46.798757, -100.873831 46.798702, -100.873772 46.79864, -100.873696 46.798497, -100.873677 46.798421, -100.873667 46.798343, -100.873664 46.798263, -100.873662 46.79818, -100.873661 46.798006, -100.873661 46.797826, -100.873663 46.797735, -100.873665 46.797645, -100.873664 46.797473, -100.873656 46.797392, -100.873644 46.797325, -100.873642 46.797312, -100.87362 46.797236, -100.873561 46.797093, -100.873526 46.797029, -100.873489 46.796974, -100.873453 46.796929, -100.873367 46.796879, -100.8733 46.796856, -100.873198 46.796876, -100.873126 46.796904, -100.873043 46.796942, -100.872951 46.796987, -100.872859 46.797035, -100.872772 46.797084, -100.872692 46.797134, -100.872621 46.797188, -100.872562 46.797243, -100.872513 46.7973, -100.872441 46.797418, -100.872415 46.797482, -100.872394 46.797553, -100.872377 46.797631, -100.872362 46.797716, -100.872347 46.797807, -100.872326 46.7979, -100.8723 46.79799, -100.872265 46.798077, -100.872221 46.798158, -100.872169 46.798235, -100.872055 46.79837, -100.871956 46.798469, -100.871873 46.798559, -100.871957 46.798599, -100.872048 46.798646, -100.872142 46.798702, -100.872318 46.798822, -100.872397 46.798894, -100.872475 46.798971, -100.87261 46.799142, -100.872663 46.799229, -100.872706 46.799316, -100.87276 46.79948, -100.872786 46.799617, -100.872796 46.799675, -100.872804 46.799723, -100.87282 46.799792, -100.872819 46.79991, -100.872816 46.800594, -100.872818 46.800728, -100.872829 46.801246, -100.872833 46.801467, -100.87284 46.802205, -100.872841 46.802398, -100.872846 46.802882, -100.872871 46.803201, -100.872878 46.803288, -100.872895 46.803591, -100.872952 46.804023, -100.872968 46.804144, -100.871707 46.804144, -100.871577 46.804144, -100.871553 46.804141, -100.871539 46.804144, -100.87147 46.804145, -100.871478 46.805692, -100.871565 46.805692, -100.871856 46.806979, -100.872053 46.806977, -100.872143 46.806976, -100.872143 46.807057, -100.872144 46.807482, -100.872154 46.808213, -100.87221 46.809103, -100.872212 46.809148, -100.872238 46.809846, -100.87285 46.809804, -100.872875 46.810511, -100.872832 46.810539, -100.872777 46.810575, -100.872241 46.810928, -100.872153 46.810957, -100.872239 46.811038, -100.872327 46.811122, -100.872691 46.811469, -100.873431 46.812044, -100.873603 46.812046, -100.874188 46.812525, -100.875116 46.813339, -100.875307 46.813504, -100.875651 46.813801, -100.876129 46.814214, -100.876165 46.814245, -100.87664 46.814643, -100.877873 46.815679, -100.878328 46.816095, -100.878689 46.816476, -100.878799 46.816626, -100.87906 46.816981, -100.879447 46.817533, -100.879822 46.818068, -100.879545 46.81811, -100.878912 46.818225, -100.878021 46.818496, -100.877282 46.818727, -100.876584 46.818986, -100.876215 46.819136, -100.875374 46.819745, -100.875107 46.819946, -100.874802 46.820222, -100.8746 46.820486, -100.874477 46.820723, -100.87439 46.821238, -100.874221 46.82134, -100.873541 46.821441, -100.873029 46.82145, -100.872438 46.821468, -100.871504 46.82142, -100.870625 46.821397, -100.870465 46.821392, -100.869874 46.821402, -100.869224 46.821464, -100.868944 46.821489, -100.867736 46.821509, -100.866359 46.821524, -100.866292 46.821525, -100.866258 46.821525, -100.866209 46.821349, -100.866091 46.82104, -100.865864 46.820936, -100.865465 46.820852, -100.865199 46.820757, -100.864765 46.820773, -100.864632 46.820703, -100.864365 46.82059, -100.86415 46.82045, -100.863208 46.819807, -100.86294 46.819658, -100.862736 46.819463, -100.862067 46.819104, -100.861425 46.81878, -100.861065 46.818632, -100.860757 46.818457, -100.860342 46.818247, -100.859526 46.817801, -100.859367 46.817776, -100.859397 46.8182, -100.859341 46.818477, -100.859358 46.818535, -100.859358 46.81904, -100.858672 46.81897, -100.858171 46.818906, -100.85786 46.818844, -100.857759 46.818778, -100.857542 46.818583, -100.857537 46.818439, -100.857489 46.817798, -100.85756 46.81758, -100.857617 46.817327, -100.857414 46.817132, -100.856156 46.816485, -100.8549 46.815875, -100.854741 46.815823, -100.854532 46.815854, -100.854391 46.815946, -100.853804 46.816469, -100.853713 46.816507, -100.853056 46.816499, -100.852111 46.816515, -100.852098 46.816136, -100.85207 46.81454, -100.85108 46.814554, -100.850444 46.814563, -100.848625 46.814584, -100.848565 46.81477, -100.848481 46.814908, -100.848379 46.81504, -100.848217 46.815202, -100.846757 46.816489, -100.84545 46.815828, -100.844908 46.815545, -100.843274 46.814722, -100.843107 46.814638, -100.841741 46.813922, -100.840834 46.813464, -100.839507 46.812792, -100.839245 46.812658, -100.839222 46.811592, -100.839216 46.810671, -100.838882 46.810108, -100.838757 46.81002, -100.838459 46.809812, -100.838455 46.809608, -100.838444 46.808854, -100.838419 46.807266, -100.838418 46.807191, -100.838417 46.807116, -100.838389 46.805467, -100.838359 46.803689, -100.838357 46.802635, -100.838356 46.802, -100.838355 46.801553, -100.838348 46.800748, -100.838346 46.800531, -100.838346 46.800506, -100.838342 46.799991, -100.838339 46.79965, -100.838332 46.798835, -100.838325 46.797977, -100.838318 46.797028, -100.838316 46.796696, -100.838316 46.796425, -100.838315 46.796054, -100.838315 46.795826, -100.838315 46.795705, -100.838314 46.795235, -100.838313 46.794798, -100.838305 46.792888, -100.838304 46.792706, -100.838297 46.790418, -100.838354 46.790316, -100.838359 46.790306, -100.838438 46.79024, -100.83851 46.7902, -100.838599 46.790183, -100.839074 46.790149, -100.839571 46.790166, -100.839916 46.790141, -100.840084 46.790115, -100.840219 46.790056, -100.84032 46.789998, -100.840429 46.789888, -100.840581 46.789753, -100.840724 46.78961, -100.841448 46.788819, -100.841501 46.788644, -100.84166 46.78856, -100.842032 46.788071, -100.842106 46.787933, -100.842138 46.787817, -100.84217 46.78771, -100.84218 46.787572, -100.84217 46.78718, -100.842159 46.786734, -100.842159 46.786394, -100.842148 46.785991, -100.839352 46.786035, -100.836902 46.786026, -100.835642 46.785992, -100.834402 46.785959, -100.834517 46.787682, -100.834527 46.787826, -100.834551 46.78819, -100.834562 46.788223, -100.834269 46.788228, -100.834316 46.788117, -100.834312 46.788021, -100.834295 46.78785, -100.834281 46.787692, -100.834273 46.787603, -100.834226 46.787472, -100.834161 46.787381, -100.834078 46.787334, -100.833928 46.787289, -100.833761 46.78712, -100.833498 46.786673, -100.833359 46.786595, -100.833305 46.786572, -100.833151 46.785961, -100.832465 46.785975, -100.831799 46.785986, -100.830234 46.786033, -100.829529 46.786044, -100.828759 46.786071, -100.828531 46.786097, -100.828319 46.786113, -100.8277 46.786246, -100.827528 46.786124, -100.827432 46.786035, -100.827379 46.785986, -100.827331 46.785911, -100.827321 46.785832, -100.827395 46.78571, -100.827536 46.785566, -100.826573 46.785602, -100.826272 46.78572, -100.824893 46.787309, -100.824613 46.787632, -100.824473 46.787771, -100.822451 46.786668, -100.820026 46.785345, -100.820104 46.785163, -100.820194 46.784954, -100.820697 46.783779, -100.820776 46.783593, -100.820483 46.783167, -100.820777 46.782767, -100.821129 46.782235, -100.821396 46.78193, -100.822699 46.780406, -100.823113 46.780016, -100.823626 46.779498, -100.824207 46.778865, -100.824635 46.778575, -100.82493 46.778308, -100.82501 46.778242, -100.82511 46.778218, -100.825134 46.778185, -100.825106 46.778133, -100.825082 46.778047, -100.825153 46.778023, -100.825267 46.778033, -100.825339 46.778037, -100.825439 46.777938, -100.825448 46.777899, -100.825405 46.777804, -100.825377 46.777781, -100.825367 46.777709, -100.825381 46.777671, -100.825405 46.777605, -100.825496 46.777514, -100.825586 46.777467, -100.825634 46.777448, -100.825695 46.777405, -100.825781 46.777314, -100.825867 46.777238, -100.825919 46.777167, -100.825957 46.777096, -100.826052 46.777005, -100.826123 46.776953, -100.826219 46.77691, -100.826304 46.776863, -100.82629 46.776834, -100.826257 46.776815, -100.825813 46.776801, -100.824614 46.776852, -100.824619 46.776805, -100.824619 46.77679, -100.824609 46.776752, -100.824602 46.776718, -100.824597 46.776696, -100.822979 46.77675, -100.823004 46.776446, -100.823053 46.776289, -100.823209 46.776116, -100.823145 46.775855, -100.823072 46.77558, -100.823085 46.775173, -100.822865 46.775165, -100.822823 46.775164, -100.822856 46.773436, -100.822896 46.77031, -100.822907 46.769473, -100.821869 46.769431, -100.821492 46.769439, -100.821528 46.768301, -100.821531 46.767815, -100.821517 46.767551, -100.821469 46.767407, -100.821103 46.765854, -100.82129 46.765825, -100.821881 46.765682, -100.822021 46.76566, -100.822161 46.765653, -100.822633 46.76565, -100.822694 46.76545, -100.822915 46.764899, -100.822848 46.764603, -100.822759 46.763988, -100.816506 46.765694, -100.816001 46.765689, -100.815764 46.765396, -100.815638 46.765507, -100.815472 46.76562, -100.815276 46.76571, -100.815183 46.765739, -100.815009 46.765793, -100.814803 46.765836, -100.814596 46.765879, -100.814113 46.765996, -100.813502 46.766119, -100.81307 46.766183, -100.812824 46.766202, -100.812584 46.766209, -100.812426 46.766197, -100.812243 46.766165, -100.811843 46.766076, -100.811611 46.766024, -100.810983 46.765834, -100.810705 46.765786, -100.810497 46.765761, -100.810241 46.765748, -100.809953 46.765741, -100.809296 46.765738, -100.808786 46.765725, -100.806964 46.765732, -100.806213 46.765728, -100.805922 46.765728, -100.805114 46.765728, -100.801523 46.765731, -100.800976 46.765727, -100.80098 46.765661, -100.800987 46.765602, -100.800996 46.765486, -100.800996 46.76544, -100.800995 46.765395, -100.800988 46.765348, -100.800964 46.765254, -100.800927 46.765172, -100.800987 46.765171, -100.801088 46.765169, -100.801218 46.76518, -100.801288 46.765192, -100.801435 46.765232, -100.801509 46.765259, -100.801584 46.765287, -100.801661 46.765317, -100.801741 46.765348, -100.801911 46.765399, -100.802001 46.765415, -100.802095 46.765425, -100.802188 46.765429, -100.802371 46.765429, -100.80246 46.765429, -100.802551 46.765429, -100.802737 46.76543, -100.802832 46.765431, -100.802927 46.765432, -100.803023 46.765432, -100.803215 46.765432, -100.80331 46.765433, -100.803401 46.765434, -100.803491 46.765435, -100.80376 46.765437, -100.803936 46.765432, -100.804023 46.765425, -100.804108 46.765413, -100.804267 46.765373, -100.80434 46.765349, -100.804409 46.765322, -100.804474 46.765296, -100.804535 46.765272, -100.804709 46.765208, -100.804826 46.765178, -100.80502 46.765163, -100.80517 46.76518, -100.805201 46.765073, -100.805211 46.764987, -100.805217 46.764758, -100.805218 46.7647, -100.805225 46.76371, -100.805236 46.762262, -100.805237 46.762159, -100.805242 46.762127, -100.805257 46.762094, -100.805282 46.762064, -100.805315 46.762038, -100.805355 46.762018, -100.805401 46.762004, -100.80545 46.761997, -100.805477 46.761996, -100.805628 46.761996, -100.805803 46.761996, -100.806464 46.761997, -100.806659 46.761996, -100.807721 46.761996, -100.808994 46.761998, -100.810348 46.762002, -100.812967 46.76201, -100.815068 46.762011, -100.815068 46.761876, -100.815073 46.76182, -100.815088 46.761764, -100.815114 46.761711, -100.815149 46.76166, -100.815156 46.761652, -100.815382 46.761415, -100.815392 46.761404, -100.815427 46.761352, -100.815453 46.761297, -100.815468 46.76124, -100.815473 46.761186, -100.815473 46.759822, -100.815467 46.759767, -100.815451 46.759712, -100.815425 46.75966, -100.815389 46.75961, -100.81538 46.7596, -100.815165 46.759362, -100.815153 46.759348, -100.815117 46.759297, -100.815091 46.759243, -100.815075 46.759188, -100.815069 46.759134, -100.815073 46.758978, -100.810457 46.75897, -100.810454 46.758495, -100.810323 46.758495, -100.809817 46.758297, -100.809165 46.758041, -100.804427 46.757999, -100.804262 46.758041, -100.804097 46.758083, -100.80354 46.758051, -100.80346 46.758702, -100.801958 46.758986, -100.801399 46.759096, -100.800551 46.759103, -100.800141 46.759107, -100.800119 46.761932, -100.800087 46.765723, -100.799474 46.76572, -100.799237 46.765719, -100.799005 46.765719, -100.799004 46.764794, -100.798049 46.764794, -100.797983 46.764794, -100.797983 46.764891, -100.797982 46.765212, -100.797773 46.765723, -100.797419 46.765723, -100.796958 46.765725, -100.796821 46.765725, -100.794987 46.765715, -100.794839 46.76571, -100.794837 46.766252, -100.794812 46.768166, -100.794788 46.769335, -100.79477 46.770217, -100.794768 46.770569, -100.794746 46.772816, -100.794746 46.772867, -100.794746 46.772938, -100.794744 46.773101, -100.794739 46.773467, -100.794708 46.775751, -100.79468 46.777814, -100.793806 46.777806, -100.793789 46.778825, -100.793781 46.779352, -100.793744 46.779524, -100.793646 46.779647, -100.793499 46.779708, -100.793352 46.779733, -100.792027 46.779708, -100.791598 46.779708, -100.791463 46.779659, -100.791352 46.779561, -100.791303 46.779463, -100.79134 46.77777, -100.791315 46.777307, -100.791356 46.777157, -100.791465 46.777007, -100.791504 46.776961, -100.791555 46.776903, -100.792051 46.776518, -100.79112 46.776003, -100.790636 46.776373, -100.79047 46.776476, -100.790202 46.776543, -100.789982 46.776517, -100.789838 46.776466, -100.789799 46.776545, -100.789623 46.77686, -100.788837 46.776648, -100.788718 46.776648, -100.788247 46.776645, -100.78794 46.776725, -100.787797 46.776728, -100.787623 46.776732, -100.787334 46.776856, -100.787151 46.776943, -100.786841 46.776756, -100.786755 46.776705, -100.786648 46.776639, -100.786281 46.776307, -100.785246 46.77631, -100.784852 46.776301, -100.78476 46.7763, -100.784384 46.776321, -100.784356 46.776328, -100.784165 46.776328, -100.784174 46.776376, -100.784182 46.776422, -100.783626 46.776643, -100.783535 46.776698, -100.783431 46.77676, -100.783317 46.776828, -100.783092 46.776963, -100.782831 46.777119, -100.78279 46.77719, -100.782782 46.777205, -100.782621 46.777485, -100.782537 46.777641, -100.782328 46.777545, -100.782209 46.77749, -100.781668 46.777242, -100.781084 46.776831, -100.780969 46.776661, -100.781187 46.776484, -100.781983 46.775705, -100.782098 46.775593, -100.783076 46.77423, -100.78356 46.77372, -100.783817 46.772393, -100.78355 46.770763, -100.782839 46.769992, -100.781535 46.769577, -100.781229 46.769637, -100.781178 46.769321, -100.78443 46.769319, -100.784426 46.769163, -100.784422 46.769075, -100.784409 46.768987, -100.784385 46.7689, -100.784351 46.768815, -100.784308 46.768732, -100.784255 46.768651, -100.784239 46.768632, -100.784214 46.768562, -100.784165 46.768503, -100.784112 46.768448, -100.784055 46.768397, -100.783995 46.76835, -100.783822 46.768211, -100.783638 46.768054, -100.783538 46.76794, -100.783471 46.767833, -100.783434 46.767747, -100.783414 46.7677, -100.783396 46.767631, -100.783378 46.767416, -100.783386 46.766766, -100.7834 46.765705, -100.783317 46.765705, -100.783245 46.765705, -100.782919 46.765705, -100.780073 46.765706, -100.773776 46.765713, -100.773833 46.762067, -100.773836 46.761428, -100.772688 46.761409, -100.772515 46.761399, -100.772247 46.76137, -100.771983 46.761327, -100.771724 46.76127, -100.771473 46.7612, -100.771346 46.761158, -100.771186 46.761092, -100.770048 46.76062, -100.769273 46.760286, -100.767695 46.759598, -100.767176 46.760146, -100.767071 46.760306, -100.766986 46.760472, -100.766921 46.760642, -100.766876 46.760814, -100.766852 46.760989, -100.766823 46.762664, -100.766819 46.763079, -100.766836 46.763242, -100.766874 46.763404, -100.766932 46.763563, -100.76701 46.763718, -100.767108 46.763867, -100.767225 46.76401, -100.767359 46.764146, -100.76751 46.764273, -100.767631 46.764359, -100.767476 46.764461, -100.767336 46.764574, -100.767213 46.764695, -100.767108 46.764825, -100.767021 46.76496, -100.766954 46.765101, -100.766907 46.765245, -100.766881 46.76539, -100.766899 46.765709, -100.764187 46.7657, -100.763237 46.765703)))"} diff --git a/tests/data/urban_areas_schema.json b/tests/data/urban_areas_schema.json deleted file mode 100644 index 23c88c1ba8e..00000000000 --- a/tests/data/urban_areas_schema.json +++ /dev/null @@ -1,72 +0,0 @@ -[ - { - "mode": "NULLABLE", - "name": "geo_id", - "type": "STRING" - }, - { - "mode": "NULLABLE", - "name": "urban_area_code", - "type": "STRING" - }, - { - "mode": "NULLABLE", - "name": "name", - "type": "STRING" - }, - { - "mode": "NULLABLE", - "name": "lsad_name", - "type": "STRING" - }, - { - "mode": "NULLABLE", - "name": "area_lsad_code", - "type": "STRING" - }, - { - "mode": "NULLABLE", - "name": "mtfcc_feature_class_code", - "type": "STRING" - }, - { - "mode": "NULLABLE", - "name": "type", - "type": "STRING" - }, - { - "mode": "NULLABLE", - "name": "functional_status", - "type": "STRING" - }, - { - "mode": "NULLABLE", - "name": "area_land_meters", - "type": "FLOAT" - }, - { - "mode": "NULLABLE", - "name": "area_water_meters", - "type": "FLOAT" - }, - { - "mode": "NULLABLE", - "name": "internal_point_lon", - "type": "FLOAT" - }, - { - "mode": "NULLABLE", - "name": "internal_point_lat", - "type": "FLOAT" - }, - { - "mode": "NULLABLE", - "name": "internal_point_geom", - "type": "GEOGRAPHY" - }, - { - "mode": "NULLABLE", - "name": "urban_area_geom", - "type": "GEOGRAPHY" - } -] diff --git a/tests/js/babel.config.cjs b/tests/js/babel.config.cjs deleted file mode 100644 index 549f612a2dd..00000000000 --- a/tests/js/babel.config.cjs +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright 2025 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -module.exports = { - presets: [['@babel/preset-env', {targets: {node: 'current'}}]], -}; diff --git a/tests/js/jest.config.cjs b/tests/js/jest.config.cjs deleted file mode 100644 index ad7dbf97ee3..00000000000 --- a/tests/js/jest.config.cjs +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright 2025 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** @type {import('jest').Config} */ -const config = { - testEnvironment: 'jsdom', - transform: { - '^.+\.js$': 'babel-jest', - }, - setupFilesAfterEnv: ['./jest.setup.js'], - transformIgnorePatterns: [], -}; - -module.exports = config; diff --git a/tests/js/jest.setup.js b/tests/js/jest.setup.js deleted file mode 100644 index b6b5934d76e..00000000000 --- a/tests/js/jest.setup.js +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright 2025 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { TextDecoder, TextEncoder } from "node:util"; - -global.TextEncoder = TextEncoder; -global.TextDecoder = TextDecoder; diff --git a/tests/js/package-lock.json b/tests/js/package-lock.json deleted file mode 100644 index 241ebd2a8d5..00000000000 --- a/tests/js/package-lock.json +++ /dev/null @@ -1,7074 +0,0 @@ -{ - "name": "js-tests", - "version": "1.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "js-tests", - "version": "1.0.0", - "license": "ISC", - "devDependencies": { - "@babel/preset-env": "^7.24.7", - "@testing-library/jest-dom": "^6.4.6", - "jest": "^30.0.0", - "jest-environment-jsdom": "^30.2.0", - "jsdom": "^29.0.0" - } - }, - "node_modules/@adobe/css-tools": { - "version": "4.4.4", - "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.4.4.tgz", - "integrity": "sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg==", - "dev": true, - "license": "MIT" - }, - "node_modules/@asamuzakjp/css-color": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-3.2.0.tgz", - "integrity": "sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@csstools/css-calc": "^2.1.3", - "@csstools/css-color-parser": "^3.0.9", - "@csstools/css-parser-algorithms": "^3.0.4", - "@csstools/css-tokenizer": "^3.0.3", - "lru-cache": "^10.4.3" - } - }, - "node_modules/@asamuzakjp/dom-selector": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-7.1.1.tgz", - "integrity": "sha512-67RZDnYRc8H/8MLDgQCDE//zoqVFwajkepHZgmXrbwybzXOEwOWGPYGmALYl9J2DOLfFPPs6kKCqmbzV895hTQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@asamuzakjp/generational-cache": "^1.0.1", - "@asamuzakjp/nwsapi": "^2.3.9", - "bidi-js": "^1.0.3", - "css-tree": "^3.2.1", - "is-potential-custom-element-name": "^1.0.1" - }, - "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0" - } - }, - "node_modules/@asamuzakjp/generational-cache": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@asamuzakjp/generational-cache/-/generational-cache-1.0.1.tgz", - "integrity": "sha512-wajfB8KqzMCN2KGNFdLkReeHncd0AslUSrvHVvvYWuU8ghncRJoA50kT3zP9MVL0+9g4/67H+cdvBskj9THPzg==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0" - } - }, - "node_modules/@asamuzakjp/nwsapi": { - "version": "2.3.9", - "resolved": "https://registry.npmjs.org/@asamuzakjp/nwsapi/-/nwsapi-2.3.9.tgz", - "integrity": "sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/@babel/code-frame": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", - "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-validator-identifier": "^7.28.5", - "js-tokens": "^4.0.0", - "picocolors": "^1.1.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/code-frame/node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@babel/compat-data": { - "version": "7.29.3", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.3.tgz", - "integrity": "sha512-LIVqM46zQWZhj17qA8wb4nW/ixr2y1Nw+r1etiAWgRM6U1IqP+LNhL1yg440jYZR72jCWcWbLWzIosH+uP1fqg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/core": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.5.tgz", - "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.28.5", - "@babel/helper-compilation-targets": "^7.27.2", - "@babel/helper-module-transforms": "^7.28.3", - "@babel/helpers": "^7.28.4", - "@babel/parser": "^7.28.5", - "@babel/template": "^7.27.2", - "@babel/traverse": "^7.28.5", - "@babel/types": "^7.28.5", - "@jridgewell/remapping": "^2.3.5", - "convert-source-map": "^2.0.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.3", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/babel" - } - }, - "node_modules/@babel/core/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@babel/generator": { - "version": "7.29.1", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", - "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.29.0", - "@babel/types": "^7.29.0", - "@jridgewell/gen-mapping": "^0.3.12", - "@jridgewell/trace-mapping": "^0.3.28", - "jsesc": "^3.0.2" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-annotate-as-pure": { - "version": "7.27.3", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz", - "integrity": "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.27.3" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-compilation-targets": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", - "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/compat-data": "^7.28.6", - "@babel/helper-validator-option": "^7.27.1", - "browserslist": "^4.24.0", - "lru-cache": "^5.1.1", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-compilation-targets/node_modules/lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^3.0.2" - } - }, - "node_modules/@babel/helper-compilation-targets/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.28.6.tgz", - "integrity": "sha512-dTOdvsjnG3xNT9Y0AUg1wAl38y+4Rl4sf9caSQZOXdNqVn+H+HbbJ4IyyHaIqNR6SW9oJpA/RuRjsjCw2IdIow==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.3", - "@babel/helper-member-expression-to-functions": "^7.28.5", - "@babel/helper-optimise-call-expression": "^7.27.1", - "@babel/helper-replace-supers": "^7.28.6", - "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", - "@babel/traverse": "^7.28.6", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-create-class-features-plugin/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@babel/helper-create-regexp-features-plugin": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.28.5.tgz", - "integrity": "sha512-N1EhvLtHzOvj7QQOUCCS3NrPJP8c5W6ZXCHDn7Yialuy1iu4r5EmIYkXlKNqT99Ciw+W0mDqWoR6HWMZlFP3hw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.3", - "regexpu-core": "^6.3.1", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-create-regexp-features-plugin/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@babel/helper-define-polyfill-provider": { - "version": "0.6.8", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.8.tgz", - "integrity": "sha512-47UwBLPpQi1NoWzLuHNjRoHlYXMwIJoBf7MFou6viC/sIHWYygpvr0B6IAyh5sBdA2nr2LPIRww8lfaUVQINBA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-compilation-targets": "^7.28.6", - "@babel/helper-plugin-utils": "^7.28.6", - "debug": "^4.4.3", - "lodash.debounce": "^4.0.8", - "resolve": "^1.22.11" - }, - "peerDependencies": { - "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" - } - }, - "node_modules/@babel/helper-globals": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", - "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.28.5.tgz", - "integrity": "sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/traverse": "^7.28.5", - "@babel/types": "^7.28.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-imports": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", - "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/traverse": "^7.28.6", - "@babel/types": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-transforms": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", - "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-module-imports": "^7.28.6", - "@babel/helper-validator-identifier": "^7.28.5", - "@babel/traverse": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-optimise-call-expression": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.27.1.tgz", - "integrity": "sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-plugin-utils": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", - "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-remap-async-to-generator": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.27.1.tgz", - "integrity": "sha512-7fiA521aVw8lSPeI4ZOD3vRFkoqkJcS+z4hFo82bFSH/2tNd6eJ5qCVMS5OzDmZh/kaHQeBaeyxK6wljcPtveA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.1", - "@babel/helper-wrap-function": "^7.27.1", - "@babel/traverse": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-replace-supers": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.28.6.tgz", - "integrity": "sha512-mq8e+laIk94/yFec3DxSjCRD2Z0TAjhVbEJY3UQrlwVo15Lmt7C2wAUbK4bjnTs4APkwsYLTahXRraQXhb1WCg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-member-expression-to-functions": "^7.28.5", - "@babel/helper-optimise-call-expression": "^7.27.1", - "@babel/traverse": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-skip-transparent-expression-wrappers": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.27.1.tgz", - "integrity": "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/traverse": "^7.27.1", - "@babel/types": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-string-parser": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", - "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", - "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-option": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", - "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-wrap-function": { - "version": "7.28.3", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.28.3.tgz", - "integrity": "sha512-zdf983tNfLZFletc0RRXYrHrucBEg95NIFMkn6K9dbeMYnsgHaSBGcQqdsCSStG2PYwRre0Qc2NNSCXbG+xc6g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/template": "^7.27.2", - "@babel/traverse": "^7.28.3", - "@babel/types": "^7.28.2" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helpers": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz", - "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/template": "^7.27.2", - "@babel/types": "^7.28.4" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/parser": { - "version": "7.29.2", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz", - "integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.29.0" - }, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.28.5.tgz", - "integrity": "sha512-87GDMS3tsmMSi/3bWOte1UblL+YUTFMV8SZPZ2eSEL17s74Cw/l63rR6NmGVKMYW2GYi85nE+/d6Hw5N0bEk2Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/traverse": "^7.28.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-bugfix-safari-class-field-initializer-scope": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.27.1.tgz", - "integrity": "sha512-qNeq3bCKnGgLkEXUuFry6dPlGfCdQNZbn7yUAPCInwAJHMU7THJfrBSozkcWq5sNM6RcF3S8XyQL2A52KNR9IA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.27.1.tgz", - "integrity": "sha512-g4L7OYun04N1WyqMNjldFwlfPCLVkgB54A/YCXICZYBsvJJE3kByKv9c9+R/nAfmIfjl2rKYLNyMHboYbZaWaA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-bugfix-safari-rest-destructuring-rhs-array": { - "version": "7.29.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-rest-destructuring-rhs-array/-/plugin-bugfix-safari-rest-destructuring-rhs-array-7.29.3.tgz", - "integrity": "sha512-SRS46DFR4HqzUzCVgi90/xMoL+zeBDBvWdKYXSEzh79kXswNFEglUpMKxR04//dPqwYXWUBJ3mpUd933ru9Kmg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6", - "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.27.1.tgz", - "integrity": "sha512-oO02gcONcD5O1iTLi/6frMJBIwWEHceWGSGqrpCmEL8nogiS6J9PBlE48CaK20/Jx1LuRml9aDftLgdjXT8+Cw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", - "@babel/plugin-transform-optional-chaining": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.13.0" - } - }, - "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.28.6.tgz", - "integrity": "sha512-a0aBScVTlNaiUe35UtfxAN7A/tehvvG4/ByO6+46VPKTRSlfnAFsgKy0FUh+qAkQrDTmhDkT+IBOKlOoMUxQ0g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6", - "@babel/traverse": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-proposal-private-property-in-object": { - "version": "7.21.0-placeholder-for-preset-env.2", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", - "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-async-generators": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", - "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-bigint": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", - "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-class-properties": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", - "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.12.13" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-class-static-block": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", - "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-import-assertions": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.28.6.tgz", - "integrity": "sha512-pSJUpFHdx9z5nqTSirOCMtYVP2wFgoWhP0p3g8ONK/4IHhLIBd0B9NYqAvIUAhq+OkhO4VM1tENCt0cjlsNShw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-import-attributes": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.28.6.tgz", - "integrity": "sha512-jiLC0ma9XkQT3TKJ9uYvlakm66Pamywo+qwL+oL8HJOvc6TWdZXVfhqJr8CCzbSGUAbDOzlGHJC1U+vRfLQDvw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-import-meta": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", - "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-json-strings": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", - "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.28.6.tgz", - "integrity": "sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-logical-assignment-operators": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", - "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", - "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-numeric-separator": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", - "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-object-rest-spread": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", - "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-optional-catch-binding": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", - "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-optional-chaining": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", - "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-private-property-in-object": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", - "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-top-level-await": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", - "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.28.6.tgz", - "integrity": "sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-unicode-sets-regex": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz", - "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-transform-arrow-functions": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.27.1.tgz", - "integrity": "sha512-8Z4TGic6xW70FKThA5HYEKKyBpOOsucTOD1DjU3fZxDg+K3zBJcXMFnt/4yQiZnf5+MiOMSXQ9PaEK/Ilh1DeA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-async-generator-functions": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.29.0.tgz", - "integrity": "sha512-va0VdWro4zlBr2JsXC+ofCPB2iG12wPtVGTWFx2WLDOM3nYQZZIGP82qku2eW/JR83sD+k2k+CsNtyEbUqhU6w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6", - "@babel/helper-remap-async-to-generator": "^7.27.1", - "@babel/traverse": "^7.29.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-async-to-generator": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.28.6.tgz", - "integrity": "sha512-ilTRcmbuXjsMmcZ3HASTe4caH5Tpo93PkTxF9oG2VZsSWsahydmcEHhix9Ik122RcTnZnUzPbmux4wh1swfv7g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-module-imports": "^7.28.6", - "@babel/helper-plugin-utils": "^7.28.6", - "@babel/helper-remap-async-to-generator": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-block-scoped-functions": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.27.1.tgz", - "integrity": "sha512-cnqkuOtZLapWYZUYM5rVIdv1nXYuFVIltZ6ZJ7nIj585QsjKM5dhL2Fu/lICXZ1OyIAFc7Qy+bvDAtTXqGrlhg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-block-scoping": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.28.6.tgz", - "integrity": "sha512-tt/7wOtBmwHPNMPu7ax4pdPz6shjFrmHDghvNC+FG9Qvj7D6mJcoRQIF5dy4njmxR941l6rgtvfSB2zX3VlUIw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-class-properties": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.28.6.tgz", - "integrity": "sha512-dY2wS3I2G7D697VHndN91TJr8/AAfXQNt5ynCTI/MpxMsSzHp+52uNivYT5wCPax3whc47DR8Ba7cmlQMg24bw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.28.6", - "@babel/helper-plugin-utils": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-class-static-block": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.28.6.tgz", - "integrity": "sha512-rfQ++ghVwTWTqQ7w8qyDxL1XGihjBss4CmTgGRCTAC9RIbhVpyp4fOeZtta0Lbf+dTNIVJer6ych2ibHwkZqsQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.28.6", - "@babel/helper-plugin-utils": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.12.0" - } - }, - "node_modules/@babel/plugin-transform-classes": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.28.6.tgz", - "integrity": "sha512-EF5KONAqC5zAqT783iMGuM2ZtmEBy+mJMOKl2BCvPZ2lVrwvXnB6o+OBWCS+CoeCCpVRF2sA2RBKUxvT8tQT5Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.3", - "@babel/helper-compilation-targets": "^7.28.6", - "@babel/helper-globals": "^7.28.0", - "@babel/helper-plugin-utils": "^7.28.6", - "@babel/helper-replace-supers": "^7.28.6", - "@babel/traverse": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-computed-properties": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.28.6.tgz", - "integrity": "sha512-bcc3k0ijhHbc2lEfpFHgx7eYw9KNXqOerKWfzbxEHUGKnS3sz9C4CNL9OiFN1297bDNfUiSO7DaLzbvHQQQ1BQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6", - "@babel/template": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-destructuring": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.28.5.tgz", - "integrity": "sha512-Kl9Bc6D0zTUcFUvkNuQh4eGXPKKNDOJQXVyyM4ZAQPMveniJdxi8XMJwLo+xSoW3MIq81bD33lcUe9kZpl0MCw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/traverse": "^7.28.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-dotall-regex": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.28.6.tgz", - "integrity": "sha512-SljjowuNKB7q5Oayv4FoPzeB74g3QgLt8IVJw9ADvWy3QnUb/01aw8I4AVv8wYnPvQz2GDDZ/g3GhcNyDBI4Bg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.28.5", - "@babel/helper-plugin-utils": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-duplicate-keys": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.27.1.tgz", - "integrity": "sha512-MTyJk98sHvSs+cvZ4nOauwTTG1JeonDjSGvGGUNHreGQns+Mpt6WX/dVzWBHgg+dYZhkC4X+zTDfkTU+Vy9y7Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-duplicate-named-capturing-groups-regex": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.29.0.tgz", - "integrity": "sha512-zBPcW2lFGxdiD8PUnPwJjag2J9otbcLQzvbiOzDxpYXyCuYX9agOwMPGn1prVH0a4qzhCKu24rlH4c1f7yA8rw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.28.5", - "@babel/helper-plugin-utils": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-transform-dynamic-import": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.27.1.tgz", - "integrity": "sha512-MHzkWQcEmjzzVW9j2q8LGjwGWpG2mjwaaB0BNQwst3FIjqsg8Ct/mIZlvSPJvfi9y2AC8mi/ktxbFVL9pZ1I4A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-explicit-resource-management": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-explicit-resource-management/-/plugin-transform-explicit-resource-management-7.28.6.tgz", - "integrity": "sha512-Iao5Konzx2b6g7EPqTy40UZbcdXE126tTxVFr/nAIj+WItNxjKSYTEw3RC+A2/ZetmdJsgueL1KhaMCQHkLPIg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6", - "@babel/plugin-transform-destructuring": "^7.28.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-exponentiation-operator": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.28.6.tgz", - "integrity": "sha512-WitabqiGjV/vJ0aPOLSFfNY1u9U3R7W36B03r5I2KoNix+a3sOhJ3pKFB3R5It9/UiK78NiO0KE9P21cMhlPkw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-export-namespace-from": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.27.1.tgz", - "integrity": "sha512-tQvHWSZ3/jH2xuq/vZDy0jNn+ZdXJeM8gHvX4lnJmsc3+50yPlWdZXIc5ay+umX+2/tJIqHqiEqcJvxlmIvRvQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-for-of": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.27.1.tgz", - "integrity": "sha512-BfbWFFEJFQzLCQ5N8VocnCtA8J1CLkNTe2Ms2wocj75dd6VpiqS5Z5quTYcUoo4Yq+DN0rtikODccuv7RU81sw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-function-name": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.27.1.tgz", - "integrity": "sha512-1bQeydJF9Nr1eBCMMbC+hdwmRlsv5XYOMu03YSWFwNs0HsAmtSxxF1fyuYPqemVldVyFmlCU7w8UE14LupUSZQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-compilation-targets": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/traverse": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-json-strings": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.28.6.tgz", - "integrity": "sha512-Nr+hEN+0geQkzhbdgQVPoqr47lZbm+5fCUmO70722xJZd0Mvb59+33QLImGj6F+DkK3xgDi1YVysP8whD6FQAw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-literals": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.27.1.tgz", - "integrity": "sha512-0HCFSepIpLTkLcsi86GG3mTUzxV5jpmbv97hTETW3yzrAij8aqlD36toB1D0daVFJM8NK6GvKO0gslVQmm+zZA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-logical-assignment-operators": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.28.6.tgz", - "integrity": "sha512-+anKKair6gpi8VsM/95kmomGNMD0eLz1NQ8+Pfw5sAwWH9fGYXT50E55ZpV0pHUHWf6IUTWPM+f/7AAff+wr9A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-member-expression-literals": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.27.1.tgz", - "integrity": "sha512-hqoBX4dcZ1I33jCSWcXrP+1Ku7kdqXf1oeah7ooKOIiAdKQ+uqftgCFNOSzA5AMS2XIHEYeGFg4cKRCdpxzVOQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-modules-amd": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.27.1.tgz", - "integrity": "sha512-iCsytMg/N9/oFq6n+gFTvUYDZQOMK5kEdeYxmxt91fcJGycfxVP9CnrxoliM0oumFERba2i8ZtwRUCMhvP1LnA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-module-transforms": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-modules-commonjs": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.28.6.tgz", - "integrity": "sha512-jppVbf8IV9iWWwWTQIxJMAJCWBuuKx71475wHwYytrRGQ2CWiDvYlADQno3tcYpS/T2UUWFQp3nVtYfK/YBQrA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-module-transforms": "^7.28.6", - "@babel/helper-plugin-utils": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-modules-systemjs": { - "version": "7.29.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.29.4.tgz", - "integrity": "sha512-N7QmZ0xRZfjHOfZeQLJjwgX2zS9pdGHSVl/cjSGlo4dXMqvurfxXDMKY4RqEKzPozV78VMcd0lxyG13mlbKc4w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-module-transforms": "^7.28.6", - "@babel/helper-plugin-utils": "^7.28.6", - "@babel/helper-validator-identifier": "^7.28.5", - "@babel/traverse": "^7.29.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-modules-umd": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.27.1.tgz", - "integrity": "sha512-iQBE/xC5BV1OxJbp6WG7jq9IWiD+xxlZhLrdwpPkTX3ydmXdvoCpyfJN7acaIBZaOqTfr76pgzqBJflNbeRK+w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-module-transforms": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.29.0.tgz", - "integrity": "sha512-1CZQA5KNAD6ZYQLPw7oi5ewtDNxH/2vuCh+6SmvgDfhumForvs8a1o9n0UrEoBD8HU4djO2yWngTQlXl1NDVEQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.28.5", - "@babel/helper-plugin-utils": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-transform-new-target": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.27.1.tgz", - "integrity": "sha512-f6PiYeqXQ05lYq3TIfIDu/MtliKUbNwkGApPUvyo6+tc7uaR4cPjPe7DFPr15Uyycg2lZU6btZ575CuQoYh7MQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.28.6.tgz", - "integrity": "sha512-3wKbRgmzYbw24mDJXT7N+ADXw8BC/imU9yo9c9X9NKaLF1fW+e5H1U5QjMUBe4Qo4Ox/o++IyUkl1sVCLgevKg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-numeric-separator": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.28.6.tgz", - "integrity": "sha512-SJR8hPynj8outz+SlStQSwvziMN4+Bq99it4tMIf5/Caq+3iOc0JtKyse8puvyXkk3eFRIA5ID/XfunGgO5i6w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-object-rest-spread": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.28.6.tgz", - "integrity": "sha512-5rh+JR4JBC4pGkXLAcYdLHZjXudVxWMXbB6u6+E9lRL5TrGVbHt1TjxGbZ8CkmYw9zjkB7jutzOROArsqtncEA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-compilation-targets": "^7.28.6", - "@babel/helper-plugin-utils": "^7.28.6", - "@babel/plugin-transform-destructuring": "^7.28.5", - "@babel/plugin-transform-parameters": "^7.27.7", - "@babel/traverse": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-object-super": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.27.1.tgz", - "integrity": "sha512-SFy8S9plRPbIcxlJ8A6mT/CxFdJx/c04JEctz4jf8YZaVS2px34j7NXRrlGlHkN/M2gnpL37ZpGRGVFLd3l8Ng==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-replace-supers": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-optional-catch-binding": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.28.6.tgz", - "integrity": "sha512-R8ja/Pyrv0OGAvAXQhSTmWyPJPml+0TMqXlO5w+AsMEiwb2fg3WkOvob7UxFSL3OIttFSGSRFKQsOhJ/X6HQdQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-optional-chaining": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.28.6.tgz", - "integrity": "sha512-A4zobikRGJTsX9uqVFdafzGkqD30t26ck2LmOzAuLL8b2x6k3TIqRiT2xVvA9fNmFeTX484VpsdgmKNA0bS23w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6", - "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-parameters": { - "version": "7.27.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.27.7.tgz", - "integrity": "sha512-qBkYTYCb76RRxUM6CcZA5KRu8K4SM8ajzVeUgVdMVO9NN9uI/GaVmBg/WKJJGnNokV9SY8FxNOVWGXzqzUidBg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-private-methods": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.28.6.tgz", - "integrity": "sha512-piiuapX9CRv7+0st8lmuUlRSmX6mBcVeNQ1b4AYzJxfCMuBfB0vBXDiGSmm03pKJw1v6cZ8KSeM+oUnM6yAExg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.28.6", - "@babel/helper-plugin-utils": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-private-property-in-object": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.28.6.tgz", - "integrity": "sha512-b97jvNSOb5+ehyQmBpmhOCiUC5oVK4PMnpRvO7+ymFBoqYjeDHIU9jnrNUuwHOiL9RpGDoKBpSViarV+BU+eVA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.3", - "@babel/helper-create-class-features-plugin": "^7.28.6", - "@babel/helper-plugin-utils": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-property-literals": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.27.1.tgz", - "integrity": "sha512-oThy3BCuCha8kDZ8ZkgOg2exvPYUlprMukKQXI1r1pJ47NCvxfkEy8vK+r/hT9nF0Aa4H1WUPZZjHTFtAhGfmQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-regenerator": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.29.0.tgz", - "integrity": "sha512-FijqlqMA7DmRdg/aINBSs04y8XNTYw/lr1gJ2WsmBnnaNw1iS43EPkJW+zK7z65auG3AWRFXWj+NcTQwYptUog==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-regexp-modifiers": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.28.6.tgz", - "integrity": "sha512-QGWAepm9qxpaIs7UM9FvUSnCGlb8Ua1RhyM4/veAxLwt3gMat/LSGrZixyuj4I6+Kn9iwvqCyPTtbdxanYoWYg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.28.5", - "@babel/helper-plugin-utils": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-transform-reserved-words": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.27.1.tgz", - "integrity": "sha512-V2ABPHIJX4kC7HegLkYoDpfg9PVmuWy/i6vUM5eGK22bx4YVFD3M5F0QQnWQoDs6AGsUWTVOopBiMFQgHaSkVw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-shorthand-properties": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.27.1.tgz", - "integrity": "sha512-N/wH1vcn4oYawbJ13Y/FxcQrWk63jhfNa7jef0ih7PHSIHX2LB7GWE1rkPrOnka9kwMxb6hMl19p7lidA+EHmQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-spread": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.28.6.tgz", - "integrity": "sha512-9U4QObUC0FtJl05AsUcodau/RWDytrU6uKgkxu09mLR9HLDAtUMoPuuskm5huQsoktmsYpI+bGmq+iapDcriKA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6", - "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-sticky-regex": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.27.1.tgz", - "integrity": "sha512-lhInBO5bi/Kowe2/aLdBAawijx+q1pQzicSgnkB6dUPc1+RC8QmJHKf2OjvU+NZWitguJHEaEmbV6VWEouT58g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-template-literals": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.27.1.tgz", - "integrity": "sha512-fBJKiV7F2DxZUkg5EtHKXQdbsbURW3DZKQUWphDum0uRP6eHGGa/He9mc0mypL680pb+e/lDIthRohlv8NCHkg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-typeof-symbol": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.27.1.tgz", - "integrity": "sha512-RiSILC+nRJM7FY5srIyc4/fGIwUhyDuuBSdWn4y6yT6gm652DpCHZjIipgn6B7MQ1ITOUnAKWixEUjQRIBIcLw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-unicode-escapes": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.27.1.tgz", - "integrity": "sha512-Ysg4v6AmF26k9vpfFuTZg8HRfVWzsh1kVfowA23y9j/Gu6dOuahdUVhkLqpObp3JIv27MLSii6noRnuKN8H0Mg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-unicode-property-regex": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.28.6.tgz", - "integrity": "sha512-4Wlbdl/sIZjzi/8St0evF0gEZrgOswVO6aOzqxh1kDZOl9WmLrHq2HtGhnOJZmHZYKP8WZ1MDLCt5DAWwRo57A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.28.5", - "@babel/helper-plugin-utils": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-unicode-regex": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.27.1.tgz", - "integrity": "sha512-xvINq24TRojDuyt6JGtHmkVkrfVV3FPT16uytxImLeBZqW3/H52yN+kM1MGuyPkIQxrzKwPHs5U/MP3qKyzkGw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-unicode-sets-regex": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.28.6.tgz", - "integrity": "sha512-/wHc/paTUmsDYN7SZkpWxogTOBNnlx7nBQYfy6JJlCT7G3mVhltk3e++N7zV0XfgGsrqBxd4rJQt9H16I21Y1Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.28.5", - "@babel/helper-plugin-utils": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/preset-env": { - "version": "7.29.5", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.29.5.tgz", - "integrity": "sha512-/69t2aEzGKHD76DyLbHysF/QH2LJOB8iFnYO37unDTKBTubzcMRv0f3H5EiN1Q6ajOd/eB7dAInF0qdFVS06kA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/compat-data": "^7.29.3", - "@babel/helper-compilation-targets": "^7.28.6", - "@babel/helper-plugin-utils": "^7.28.6", - "@babel/helper-validator-option": "^7.27.1", - "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.28.5", - "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.27.1", - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.27.1", - "@babel/plugin-bugfix-safari-rest-destructuring-rhs-array": "^7.29.3", - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.27.1", - "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.28.6", - "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", - "@babel/plugin-syntax-import-assertions": "^7.28.6", - "@babel/plugin-syntax-import-attributes": "^7.28.6", - "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", - "@babel/plugin-transform-arrow-functions": "^7.27.1", - "@babel/plugin-transform-async-generator-functions": "^7.29.0", - "@babel/plugin-transform-async-to-generator": "^7.28.6", - "@babel/plugin-transform-block-scoped-functions": "^7.27.1", - "@babel/plugin-transform-block-scoping": "^7.28.6", - "@babel/plugin-transform-class-properties": "^7.28.6", - "@babel/plugin-transform-class-static-block": "^7.28.6", - "@babel/plugin-transform-classes": "^7.28.6", - "@babel/plugin-transform-computed-properties": "^7.28.6", - "@babel/plugin-transform-destructuring": "^7.28.5", - "@babel/plugin-transform-dotall-regex": "^7.28.6", - "@babel/plugin-transform-duplicate-keys": "^7.27.1", - "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.29.0", - "@babel/plugin-transform-dynamic-import": "^7.27.1", - "@babel/plugin-transform-explicit-resource-management": "^7.28.6", - "@babel/plugin-transform-exponentiation-operator": "^7.28.6", - "@babel/plugin-transform-export-namespace-from": "^7.27.1", - "@babel/plugin-transform-for-of": "^7.27.1", - "@babel/plugin-transform-function-name": "^7.27.1", - "@babel/plugin-transform-json-strings": "^7.28.6", - "@babel/plugin-transform-literals": "^7.27.1", - "@babel/plugin-transform-logical-assignment-operators": "^7.28.6", - "@babel/plugin-transform-member-expression-literals": "^7.27.1", - "@babel/plugin-transform-modules-amd": "^7.27.1", - "@babel/plugin-transform-modules-commonjs": "^7.28.6", - "@babel/plugin-transform-modules-systemjs": "^7.29.4", - "@babel/plugin-transform-modules-umd": "^7.27.1", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.29.0", - "@babel/plugin-transform-new-target": "^7.27.1", - "@babel/plugin-transform-nullish-coalescing-operator": "^7.28.6", - "@babel/plugin-transform-numeric-separator": "^7.28.6", - "@babel/plugin-transform-object-rest-spread": "^7.28.6", - "@babel/plugin-transform-object-super": "^7.27.1", - "@babel/plugin-transform-optional-catch-binding": "^7.28.6", - "@babel/plugin-transform-optional-chaining": "^7.28.6", - "@babel/plugin-transform-parameters": "^7.27.7", - "@babel/plugin-transform-private-methods": "^7.28.6", - "@babel/plugin-transform-private-property-in-object": "^7.28.6", - "@babel/plugin-transform-property-literals": "^7.27.1", - "@babel/plugin-transform-regenerator": "^7.29.0", - "@babel/plugin-transform-regexp-modifiers": "^7.28.6", - "@babel/plugin-transform-reserved-words": "^7.27.1", - "@babel/plugin-transform-shorthand-properties": "^7.27.1", - "@babel/plugin-transform-spread": "^7.28.6", - "@babel/plugin-transform-sticky-regex": "^7.27.1", - "@babel/plugin-transform-template-literals": "^7.27.1", - "@babel/plugin-transform-typeof-symbol": "^7.27.1", - "@babel/plugin-transform-unicode-escapes": "^7.27.1", - "@babel/plugin-transform-unicode-property-regex": "^7.28.6", - "@babel/plugin-transform-unicode-regex": "^7.27.1", - "@babel/plugin-transform-unicode-sets-regex": "^7.28.6", - "@babel/preset-modules": "0.1.6-no-external-plugins", - "babel-plugin-polyfill-corejs2": "^0.4.15", - "babel-plugin-polyfill-corejs3": "^0.14.0", - "babel-plugin-polyfill-regenerator": "^0.6.6", - "core-js-compat": "^3.48.0", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/preset-env/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@babel/preset-modules": { - "version": "0.1.6-no-external-plugins", - "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz", - "integrity": "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/types": "^7.4.4", - "esutils": "^2.0.2" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0 || ^8.0.0-0 <8.0.0" - } - }, - "node_modules/@babel/template": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", - "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.28.6", - "@babel/parser": "^7.28.6", - "@babel/types": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/traverse": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", - "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.29.0", - "@babel/generator": "^7.29.0", - "@babel/helper-globals": "^7.28.0", - "@babel/parser": "^7.29.0", - "@babel/template": "^7.28.6", - "@babel/types": "^7.29.0", - "debug": "^4.3.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/types": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", - "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.28.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@bcoe/v8-coverage": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", - "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@bramus/specificity": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/@bramus/specificity/-/specificity-2.4.2.tgz", - "integrity": "sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw==", - "dev": true, - "license": "MIT", - "dependencies": { - "css-tree": "^3.0.0" - }, - "bin": { - "specificity": "bin/cli.js" - } - }, - "node_modules/@csstools/color-helpers": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.1.0.tgz", - "integrity": "sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "engines": { - "node": ">=18" - } - }, - "node_modules/@csstools/css-calc": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.4.tgz", - "integrity": "sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT", - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4" - } - }, - "node_modules/@csstools/css-color-parser": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.1.0.tgz", - "integrity": "sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT", - "dependencies": { - "@csstools/color-helpers": "^5.1.0", - "@csstools/css-calc": "^2.1.4" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4" - } - }, - "node_modules/@csstools/css-parser-algorithms": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.5.tgz", - "integrity": "sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT", - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@csstools/css-tokenizer": "^3.0.4" - } - }, - "node_modules/@csstools/css-syntax-patches-for-csstree": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.1.3.tgz", - "integrity": "sha512-SH60bMfrRCJF3morcdk57WklujF4Jr/EsQUzqkarfHXEFcAR1gg7fS/chAE922Sehgzc1/+Tz5H3Ypa1HiEKrg==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "peerDependencies": { - "css-tree": "^3.2.1" - }, - "peerDependenciesMeta": { - "css-tree": { - "optional": true - } - } - }, - "node_modules/@csstools/css-tokenizer": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz", - "integrity": "sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/@emnapi/core": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", - "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/wasi-threads": "1.2.1", - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/runtime": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", - "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/wasi-threads": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", - "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@exodus/bytes": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/@exodus/bytes/-/bytes-1.15.0.tgz", - "integrity": "sha512-UY0nlA+feH81UGSHv92sLEPLCeZFjXOuHhrIo0HQydScuQc8s0A7kL/UdgwgDq8g8ilksmuoF35YVTNphV2aBQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0" - }, - "peerDependencies": { - "@noble/hashes": "^1.8.0 || ^2.0.0" - }, - "peerDependenciesMeta": { - "@noble/hashes": { - "optional": true - } - } - }, - "node_modules/@isaacs/cliui": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", - "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^5.1.2", - "string-width-cjs": "npm:string-width@^4.2.0", - "strip-ansi": "^7.0.1", - "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", - "wrap-ansi": "^8.1.0", - "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@istanbuljs/load-nyc-config": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", - "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "camelcase": "^5.3.1", - "find-up": "^4.1.0", - "get-package-type": "^0.1.0", - "js-yaml": "^3.13.1", - "resolve-from": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@istanbuljs/schema": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.6.tgz", - "integrity": "sha512-+Sg6GCR/wy1oSmQDFq4LQDAhm3ETKnorxN+y5nbLULOR3P0c14f2Wurzj3/xqPXtasLFfHd5iRFQ7AJt4KH2cw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/console": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-30.4.1.tgz", - "integrity": "sha512-v3bhyxUh9Hgmo5p6hAOXe14/R3ZxZDOsvHleh4B07z3m/x4/ngPUXEm9XwK4sF4u+f+P2ORb0Ge+MgpaqRMVDA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "30.4.1", - "@types/node": "*", - "chalk": "^4.1.2", - "jest-message-util": "30.4.1", - "jest-util": "30.4.1", - "slash": "^3.0.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jest/core": { - "version": "30.4.2", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-30.4.2.tgz", - "integrity": "sha512-TZJA6cPJUFxoWhxaLo8t0VX/MZX2wPWr0uIDvLSHIvN4gu9h02vSzqI2kBADG1ExqQlC+cY09xKMSreivvrChQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/console": "30.4.1", - "@jest/pattern": "30.4.0", - "@jest/reporters": "30.4.1", - "@jest/test-result": "30.4.1", - "@jest/transform": "30.4.1", - "@jest/types": "30.4.1", - "@types/node": "*", - "ansi-escapes": "^4.3.2", - "chalk": "^4.1.2", - "ci-info": "^4.2.0", - "exit-x": "^0.2.2", - "fast-json-stable-stringify": "^2.1.0", - "graceful-fs": "^4.2.11", - "jest-changed-files": "30.4.1", - "jest-config": "30.4.2", - "jest-haste-map": "30.4.1", - "jest-message-util": "30.4.1", - "jest-regex-util": "30.4.0", - "jest-resolve": "30.4.1", - "jest-resolve-dependencies": "30.4.2", - "jest-runner": "30.4.2", - "jest-runtime": "30.4.2", - "jest-snapshot": "30.4.1", - "jest-util": "30.4.1", - "jest-validate": "30.4.1", - "jest-watcher": "30.4.1", - "pretty-format": "30.4.1", - "slash": "^3.0.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/@jest/diff-sequences": { - "version": "30.4.0", - "resolved": "https://registry.npmjs.org/@jest/diff-sequences/-/diff-sequences-30.4.0.tgz", - "integrity": "sha512-zOpzlfUs45l6u7jm39qr87JCHUDsaeCtvL+kQe/Vn9jSnRB4/5IPXISm0h9I1vZW/o00Kn4UTJ2MOlhnUGwv3g==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jest/environment": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-30.4.1.tgz", - "integrity": "sha512-AK9yNRqgKxiabqMoe4oW+3/TSSeV8vkdC7BGaxZdU0AFXfOpofTLqdru2GXKZghP3sdgwE9XXpnVwfZ8JnFV4w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/fake-timers": "30.4.1", - "@jest/types": "30.4.1", - "@types/node": "*", - "jest-mock": "30.4.1" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jest/environment-jsdom-abstract": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/@jest/environment-jsdom-abstract/-/environment-jsdom-abstract-30.4.1.tgz", - "integrity": "sha512-dSlKrqug3siYNHVnjwIldShY12wAH3spwRltO/+8VOjg0X+xEq7vOs3DbBs4LRKsu7OH+NUb9kuZUNBF9Ho3TA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/environment": "30.4.1", - "@jest/fake-timers": "30.4.1", - "@jest/types": "30.4.1", - "@types/jsdom": "^21.1.7", - "@types/node": "*", - "jest-mock": "30.4.1", - "jest-util": "30.4.1" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - }, - "peerDependencies": { - "canvas": "^3.0.0", - "jsdom": "*" - }, - "peerDependenciesMeta": { - "canvas": { - "optional": true - } - } - }, - "node_modules/@jest/expect": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-30.4.1.tgz", - "integrity": "sha512-ginrj6TMgh2GshLUGCjO94Ptx9HhdZA/I6A9iUfyeLKFtdAjnKzHDgzgP9HYQgbxM1lbXScQ2eUBz2lGeVDPWA==", - "dev": true, - "license": "MIT", - "dependencies": { - "expect": "30.4.1", - "jest-snapshot": "30.4.1" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jest/expect-utils": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-30.4.1.tgz", - "integrity": "sha512-ZBn5CglH8fBsQsvs4VWNzD4aWfUYks+IdOOQU3MEK71ol/BcVm+P+rtb1KpiFBpSWSCE27uOahyyf1vfqOVbcQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/get-type": "30.1.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jest/fake-timers": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-30.4.1.tgz", - "integrity": "sha512-iW5umdmfPeWzehrVhugFQZqCchSCud5S1l2YT0O9ZhjRR0ExclANDZkiSBwzqtnlOn0J1JXvO+HZ6rkuyOVOgQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "30.4.1", - "@sinonjs/fake-timers": "^15.4.0", - "@types/node": "*", - "jest-message-util": "30.4.1", - "jest-mock": "30.4.1", - "jest-util": "30.4.1" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jest/get-type": { - "version": "30.1.0", - "resolved": "https://registry.npmjs.org/@jest/get-type/-/get-type-30.1.0.tgz", - "integrity": "sha512-eMbZE2hUnx1WV0pmURZY9XoXPkUYjpc55mb0CrhtdWLtzMQPFvu/rZkTLZFTsdaVQa+Tr4eWAteqcUzoawq/uA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jest/globals": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-30.4.1.tgz", - "integrity": "sha512-ZbuY4cmXC8DkxYjfvT2DbcHWL2T6vmsMhXCDcmTB2T0y0gaezBI77ufq5ZAIdcRkYZ7NEQEDg1xFeKbxUJ5v5Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/environment": "30.4.1", - "@jest/expect": "30.4.1", - "@jest/types": "30.4.1", - "jest-mock": "30.4.1" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jest/pattern": { - "version": "30.4.0", - "resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.4.0.tgz", - "integrity": "sha512-RAWn3+f9u8BsHijKJ71uHcFp6vmyEt6VvoWXkl6hKF3qVIuWNmudVjg12DlBPGup/frIl5UcUlH5HfEuvHpEXg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*", - "jest-regex-util": "30.4.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jest/reporters": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-30.4.1.tgz", - "integrity": "sha512-/SnkPCzEQpUaBH81kjdEdDdo2WZl5hxw+BmLDGWjRkm8o7XlhjwsU36cqwe5PGBE5WYpBvDzRSdXx9rbGuJtNA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "30.4.1", - "@jest/test-result": "30.4.1", - "@jest/transform": "30.4.1", - "@jest/types": "30.4.1", - "@jridgewell/trace-mapping": "^0.3.25", - "@types/node": "*", - "chalk": "^4.1.2", - "collect-v8-coverage": "^1.0.2", - "exit-x": "^0.2.2", - "glob": "^10.5.0", - "graceful-fs": "^4.2.11", - "istanbul-lib-coverage": "^3.0.0", - "istanbul-lib-instrument": "^6.0.0", - "istanbul-lib-report": "^3.0.0", - "istanbul-lib-source-maps": "^5.0.0", - "istanbul-reports": "^3.1.3", - "jest-message-util": "30.4.1", - "jest-util": "30.4.1", - "jest-worker": "30.4.1", - "slash": "^3.0.0", - "string-length": "^4.0.2", - "v8-to-istanbul": "^9.0.1" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/@jest/schemas": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.4.1.tgz", - "integrity": "sha512-i6b4qw5qnP8c5FEeBJg/uZQ4ddrkN6Ca8qISJh0pr7a5hfn3h3v5x60BEbOC7OYAGZNMs1LfFLwnW2CuK8F57Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@sinclair/typebox": "^0.34.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jest/snapshot-utils": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/@jest/snapshot-utils/-/snapshot-utils-30.4.1.tgz", - "integrity": "sha512-ObY4ljvQ95mt6iwKtVLetR/4yXiAgl3H4nJxhztr0MTjrN97TwDYrnCp/kF60Ec9HdhkWTHSu+Hg05aXfngpOA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "30.4.1", - "chalk": "^4.1.2", - "graceful-fs": "^4.2.11", - "natural-compare": "^1.4.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jest/source-map": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-30.0.1.tgz", - "integrity": "sha512-MIRWMUUR3sdbP36oyNyhbThLHyJ2eEDClPCiHVbrYAe5g3CHRArIVpBw7cdSB5fr+ofSfIb2Tnsw8iEHL0PYQg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.25", - "callsites": "^3.1.0", - "graceful-fs": "^4.2.11" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jest/test-result": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-30.4.1.tgz", - "integrity": "sha512-/ZG7pgEiOmmWkN9TplKbOu4id2N5lh7FHwRwlkgBVAzGdRH+OkkQ8wX/kIxg4zmd3ZQvAL1RwL2yWsvNYYECTw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/console": "30.4.1", - "@jest/types": "30.4.1", - "@types/istanbul-lib-coverage": "^2.0.6", - "collect-v8-coverage": "^1.0.2" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jest/test-sequencer": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-30.4.1.tgz", - "integrity": "sha512-PeYE+4td5rKjoRPxztObrXU+H8hsjZfxKMXOcmrr34JerSyB/ROOxbbicz8B7A5j9R9VayDnVPvBmedqCsFCdw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/test-result": "30.4.1", - "graceful-fs": "^4.2.11", - "jest-haste-map": "30.4.1", - "slash": "^3.0.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jest/transform": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-30.4.1.tgz", - "integrity": "sha512-Wz0LyktlTvRefoymh+n64hQ84KNXsRGcwdoZ8CSa0Ea+fgYcHZlnk+hDP7v2MS7il2bQ5uTEIxf4/NNfhMN4KQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/core": "^7.27.4", - "@jest/types": "30.4.1", - "@jridgewell/trace-mapping": "^0.3.25", - "babel-plugin-istanbul": "^7.0.1", - "chalk": "^4.1.2", - "convert-source-map": "^2.0.0", - "fast-json-stable-stringify": "^2.1.0", - "graceful-fs": "^4.2.11", - "jest-haste-map": "30.4.1", - "jest-regex-util": "30.4.0", - "jest-util": "30.4.1", - "pirates": "^4.0.7", - "slash": "^3.0.0", - "write-file-atomic": "^5.0.1" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jest/types": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.4.1.tgz", - "integrity": "sha512-f1x/vJXIfjOlEmejYpbkbgw1gOqpPECwMvMEtBqe47j7H2Hg8h8w3o3ikhSXq3MI15kg+oQ0exWO0uCtTNJLoQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/pattern": "30.4.0", - "@jest/schemas": "30.4.1", - "@types/istanbul-lib-coverage": "^2.0.6", - "@types/istanbul-reports": "^3.0.4", - "@types/node": "*", - "@types/yargs": "^17.0.33", - "chalk": "^4.1.2" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.13", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", - "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/remapping": { - "version": "2.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", - "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "dev": true, - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.31", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", - "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@napi-rs/wasm-runtime": { - "version": "0.2.12", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz", - "integrity": "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/core": "^1.4.3", - "@emnapi/runtime": "^1.4.3", - "@tybys/wasm-util": "^0.10.0" - } - }, - "node_modules/@pkgjs/parseargs": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", - "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", - "dev": true, - "license": "MIT", - "optional": true, - "engines": { - "node": ">=14" - } - }, - "node_modules/@pkgr/core": { - "version": "0.2.9", - "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.9.tgz", - "integrity": "sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.20.0 || ^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/pkgr" - } - }, - "node_modules/@sinclair/typebox": { - "version": "0.34.49", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.49.tgz", - "integrity": "sha512-brySQQs7Jtn0joV8Xh9ZV/hZb9Ozb0pmazDIASBkYKCjXrXU3mpcFahmK/z4YDhGkQvP9mWJbVyahdtU5wQA+A==", - "dev": true, - "license": "MIT" - }, - "node_modules/@sinonjs/commons": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", - "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "type-detect": "4.0.8" - } - }, - "node_modules/@sinonjs/fake-timers": { - "version": "15.4.0", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-15.4.0.tgz", - "integrity": "sha512-DsG+8/LscQIQg68J6Ef3dv10u6nVyetYn923s3/sus5eaGfTo1of5WMZSLf0UJc9KDuKPilPH0UDJCjvNbDNCA==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@sinonjs/commons": "^3.0.1" - } - }, - "node_modules/@testing-library/jest-dom": { - "version": "6.9.1", - "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.9.1.tgz", - "integrity": "sha512-zIcONa+hVtVSSep9UT3jZ5rizo2BsxgyDYU7WFD5eICBE7no3881HGeb/QkGfsJs6JTkY1aQhT7rIPC7e+0nnA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@adobe/css-tools": "^4.4.0", - "aria-query": "^5.0.0", - "css.escape": "^1.5.1", - "dom-accessibility-api": "^0.6.3", - "picocolors": "^1.1.1", - "redent": "^3.0.0" - }, - "engines": { - "node": ">=14", - "npm": ">=6", - "yarn": ">=1" - } - }, - "node_modules/@tybys/wasm-util": { - "version": "0.10.2", - "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.2.tgz", - "integrity": "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@types/babel__core": { - "version": "7.20.5", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", - "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.20.7", - "@babel/types": "^7.20.7", - "@types/babel__generator": "*", - "@types/babel__template": "*", - "@types/babel__traverse": "*" - } - }, - "node_modules/@types/babel__generator": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", - "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__template": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", - "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__traverse": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", - "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.28.2" - } - }, - "node_modules/@types/istanbul-lib-coverage": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", - "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/istanbul-lib-report": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", - "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/istanbul-lib-coverage": "*" - } - }, - "node_modules/@types/istanbul-reports": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", - "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/istanbul-lib-report": "*" - } - }, - "node_modules/@types/jsdom": { - "version": "21.1.7", - "resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-21.1.7.tgz", - "integrity": "sha512-yOriVnggzrnQ3a9OKOCxaVuSug3w3/SbOj5i7VwXWZEyUNl3bLF9V3MfxGbZKuwqJOQyRfqXyROBB1CoZLFWzA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*", - "@types/tough-cookie": "*", - "parse5": "^7.0.0" - } - }, - "node_modules/@types/node": { - "version": "24.10.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz", - "integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~7.16.0" - } - }, - "node_modules/@types/stack-utils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", - "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/tough-cookie": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz", - "integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/yargs": { - "version": "17.0.35", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.35.tgz", - "integrity": "sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/@types/yargs-parser": { - "version": "21.0.3", - "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", - "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@ungap/structured-clone": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.1.tgz", - "integrity": "sha512-mUFwbeTqrVgDQxFveS+df2yfap6iuP20NAKAsBt5jDEoOTDew+zwLAOilHCeQJOVSvmgCX4ogqIrA0mnyr08yQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/@unrs/resolver-binding-android-arm-eabi": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.11.1.tgz", - "integrity": "sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@unrs/resolver-binding-android-arm64": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.11.1.tgz", - "integrity": "sha512-lCxkVtb4wp1v+EoN+HjIG9cIIzPkX5OtM03pQYkG+U5O/wL53LC4QbIeazgiKqluGeVEeBlZahHalCaBvU1a2g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@unrs/resolver-binding-darwin-arm64": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.11.1.tgz", - "integrity": "sha512-gPVA1UjRu1Y/IsB/dQEsp2V1pm44Of6+LWvbLc9SDk1c2KhhDRDBUkQCYVWe6f26uJb3fOK8saWMgtX8IrMk3g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@unrs/resolver-binding-darwin-x64": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.11.1.tgz", - "integrity": "sha512-cFzP7rWKd3lZaCsDze07QX1SC24lO8mPty9vdP+YVa3MGdVgPmFc59317b2ioXtgCMKGiCLxJ4HQs62oz6GfRQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@unrs/resolver-binding-freebsd-x64": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.11.1.tgz", - "integrity": "sha512-fqtGgak3zX4DCB6PFpsH5+Kmt/8CIi4Bry4rb1ho6Av2QHTREM+47y282Uqiu3ZRF5IQioJQ5qWRV6jduA+iGw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@unrs/resolver-binding-linux-arm-gnueabihf": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.11.1.tgz", - "integrity": "sha512-u92mvlcYtp9MRKmP+ZvMmtPN34+/3lMHlyMj7wXJDeXxuM0Vgzz0+PPJNsro1m3IZPYChIkn944wW8TYgGKFHw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-linux-arm-musleabihf": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.11.1.tgz", - "integrity": "sha512-cINaoY2z7LVCrfHkIcmvj7osTOtm6VVT16b5oQdS4beibX2SYBwgYLmqhBjA1t51CarSaBuX5YNsWLjsqfW5Cw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-linux-arm64-gnu": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.11.1.tgz", - "integrity": "sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-linux-arm64-musl": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.11.1.tgz", - "integrity": "sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-linux-ppc64-gnu": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.11.1.tgz", - "integrity": "sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-linux-riscv64-gnu": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.11.1.tgz", - "integrity": "sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-linux-riscv64-musl": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.11.1.tgz", - "integrity": "sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-linux-s390x-gnu": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.11.1.tgz", - "integrity": "sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-linux-x64-gnu": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.11.1.tgz", - "integrity": "sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-linux-x64-musl": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.11.1.tgz", - "integrity": "sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-wasm32-wasi": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.11.1.tgz", - "integrity": "sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==", - "cpu": [ - "wasm32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@napi-rs/wasm-runtime": "^0.2.11" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@unrs/resolver-binding-win32-arm64-msvc": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.11.1.tgz", - "integrity": "sha512-nRcz5Il4ln0kMhfL8S3hLkxI85BXs3o8EYoattsJNdsX4YUU89iOkVn7g0VHSRxFuVMdM4Q1jEpIId1Ihim/Uw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@unrs/resolver-binding-win32-ia32-msvc": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.11.1.tgz", - "integrity": "sha512-DCEI6t5i1NmAZp6pFonpD5m7i6aFrpofcp4LA2i8IIq60Jyo28hamKBxNrZcyOwVOZkgsRp9O2sXWBWP8MnvIQ==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@unrs/resolver-binding-win32-x64-msvc": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.11.1.tgz", - "integrity": "sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/agent-base": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", - "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 14" - } - }, - "node_modules/ansi-escapes": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", - "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "type-fest": "^0.21.3" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ansi-regex": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", - "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, - "license": "ISC", - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/anymatch/node_modules/picomatch": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", - "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "license": "MIT", - "dependencies": { - "sprintf-js": "~1.0.2" - } - }, - "node_modules/aria-query": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", - "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/babel-jest": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-30.4.1.tgz", - "integrity": "sha512-fATAbM8piYxkiXQp3RBXmZHxZVNJZAVXXfyeyCN2Tida3+qJ8ea9UxhiJ2y4fLO90ZImKt6k9FlcH2+rLkJGhw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/transform": "30.4.1", - "@types/babel__core": "^7.20.5", - "babel-plugin-istanbul": "^7.0.1", - "babel-preset-jest": "30.4.0", - "chalk": "^4.1.2", - "graceful-fs": "^4.2.11", - "slash": "^3.0.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - }, - "peerDependencies": { - "@babel/core": "^7.11.0 || ^8.0.0-0" - } - }, - "node_modules/babel-plugin-istanbul": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-7.0.1.tgz", - "integrity": "sha512-D8Z6Qm8jCvVXtIRkBnqNHX0zJ37rQcFJ9u8WOS6tkYOsRdHBzypCstaxWiu5ZIlqQtviRYbgnRLSoCEvjqcqbA==", - "dev": true, - "license": "BSD-3-Clause", - "workspaces": [ - "test/babel-8" - ], - "dependencies": { - "@babel/helper-plugin-utils": "^7.0.0", - "@istanbuljs/load-nyc-config": "^1.0.0", - "@istanbuljs/schema": "^0.1.3", - "istanbul-lib-instrument": "^6.0.2", - "test-exclude": "^6.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/babel-plugin-jest-hoist": { - "version": "30.4.0", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-30.4.0.tgz", - "integrity": "sha512-9EdtWM/sSfXLOGLwSn+GS6pIXyBnL07/8gyJlwFXjWy4DxMOyItqyUT29d4lQiS380EZwYlX7/At4PgBS+m2aA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/babel__core": "^7.20.5" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/babel-plugin-polyfill-corejs2": { - "version": "0.4.17", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.17.tgz", - "integrity": "sha512-aTyf30K/rqAsNwN76zYrdtx8obu0E4KoUME29B1xj+B3WxgvWkp943vYQ+z8Mv3lw9xHXMHpvSPOBxzAkIa94w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/compat-data": "^7.28.6", - "@babel/helper-define-polyfill-provider": "^0.6.8", - "semver": "^6.3.1" - }, - "peerDependencies": { - "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" - } - }, - "node_modules/babel-plugin-polyfill-corejs2/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/babel-plugin-polyfill-corejs3": { - "version": "0.14.2", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.14.2.tgz", - "integrity": "sha512-coWpDLJ410R781Npmn/SIBZEsAetR4xVi0SxLMXPaMO4lSf1MwnkGYMtkFxew0Dn8B3/CpbpYxN0JCgg8mn67g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.6.8", - "core-js-compat": "^3.48.0" - }, - "peerDependencies": { - "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" - } - }, - "node_modules/babel-plugin-polyfill-regenerator": { - "version": "0.6.8", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.8.tgz", - "integrity": "sha512-M762rNHfSF1EV3SLtnCJXFoQbbIIz0OyRwnCmV0KPC7qosSfCO0QLTSuJX3ayAebubhE6oYBAYPrBA5ljowaZg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.6.8" - }, - "peerDependencies": { - "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" - } - }, - "node_modules/babel-preset-current-node-syntax": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.2.0.tgz", - "integrity": "sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/plugin-syntax-class-properties": "^7.12.13", - "@babel/plugin-syntax-class-static-block": "^7.14.5", - "@babel/plugin-syntax-import-attributes": "^7.24.7", - "@babel/plugin-syntax-import-meta": "^7.10.4", - "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.10.4", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-syntax-private-property-in-object": "^7.14.5", - "@babel/plugin-syntax-top-level-await": "^7.14.5" - }, - "peerDependencies": { - "@babel/core": "^7.0.0 || ^8.0.0-0" - } - }, - "node_modules/babel-preset-jest": { - "version": "30.4.0", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-30.4.0.tgz", - "integrity": "sha512-lBY4jxsNmCnSiu7kquw8ZC9F4+XLMOKypT3RnNHPvU2Kpd4W0xaPuLr5ZkRyOsvLYAY4yaW1ZwTW4xB7NIiZzg==", - "dev": true, - "license": "MIT", - "dependencies": { - "babel-plugin-jest-hoist": "30.4.0", - "babel-preset-current-node-syntax": "^1.2.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - }, - "peerDependencies": { - "@babel/core": "^7.11.0 || ^8.0.0-beta.1" - } - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/baseline-browser-mapping": { - "version": "2.10.23", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.23.tgz", - "integrity": "sha512-xwVXGqevyKPsiuQdLj+dZMVjidjJV508TBqexND5HrF89cGdCYCJFB3qhcxRHSeMctdCfbR1jrxBajhDy7o29g==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "baseline-browser-mapping": "dist/cli.cjs" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/bidi-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz", - "integrity": "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==", - "dev": true, - "license": "MIT", - "dependencies": { - "require-from-string": "^2.0.2" - } - }, - "node_modules/brace-expansion": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz", - "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/browserslist": { - "version": "4.28.2", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz", - "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "baseline-browser-mapping": "^2.10.12", - "caniuse-lite": "^1.0.30001782", - "electron-to-chromium": "^1.5.328", - "node-releases": "^2.0.36", - "update-browserslist-db": "^1.2.3" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, - "node_modules/bser": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", - "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "node-int64": "^0.4.0" - } - }, - "node_modules/buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001791", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001791.tgz", - "integrity": "sha512-yk0l/YSrOnFZk3UROpDLQD9+kC1l4meK/wed583AXrzoarMGJcbRi2Q4RaUYbKxYAsZ8sWmaSa/DsLmdBeI1vQ==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "CC-BY-4.0" - }, - "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/chalk/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/char-regex": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", - "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - } - }, - "node_modules/ci-info": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.4.0.tgz", - "integrity": "sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/sibiraj-s" - } - ], - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/cjs-module-lexer": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-2.2.0.tgz", - "integrity": "sha512-4bHTS2YuzUvtoLjdy+98ykbNB5jS0+07EvFNXerqZQJ89F7DI6ET7OQo/HJuW6K0aVsKA9hj9/RVb2kQVOrPDQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/cliui/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/cliui/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/cliui/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, - "node_modules/cliui/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/cliui/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/cliui/node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/co": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", - "dev": true, - "license": "MIT", - "engines": { - "iojs": ">= 1.0.0", - "node": ">= 0.12.0" - } - }, - "node_modules/collect-v8-coverage": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.3.tgz", - "integrity": "sha512-1L5aqIkwPfiodaMgQunkF1zRhNqifHBmtbbbxcr6yVxxBnliw4TDOW6NxpO8DJLgJ16OT+Y4ztZqP6p/FtXnAw==", - "dev": true, - "license": "MIT" - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "license": "MIT" - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true, - "license": "MIT" - }, - "node_modules/convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true, - "license": "MIT" - }, - "node_modules/core-js-compat": { - "version": "3.49.0", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.49.0.tgz", - "integrity": "sha512-VQXt1jr9cBz03b331DFDCCP90b3fanciLkgiOoy8SBHy06gNf+vQ1A3WFLqG7I8TipYIKeYK9wxd0tUrvHcOZA==", - "dev": true, - "license": "MIT", - "dependencies": { - "browserslist": "^4.28.1" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/core-js" - } - }, - "node_modules/cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/css-tree": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.2.1.tgz", - "integrity": "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==", - "dev": true, - "license": "MIT", - "dependencies": { - "mdn-data": "2.27.1", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" - } - }, - "node_modules/css.escape": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", - "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==", - "dev": true, - "license": "MIT" - }, - "node_modules/cssstyle": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-4.6.0.tgz", - "integrity": "sha512-2z+rWdzbbSZv6/rhtvzvqeZQHrBaqgogqt85sqFNbabZOuFbCVFb8kPeEtZjiKkbrm395irpNKiYeFeLiQnFPg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@asamuzakjp/css-color": "^3.2.0", - "rrweb-cssom": "^0.8.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/data-urls": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz", - "integrity": "sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==", - "dev": true, - "license": "MIT", - "dependencies": { - "whatwg-mimetype": "^4.0.0", - "whatwg-url": "^14.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/decimal.js": { - "version": "10.6.0", - "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", - "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", - "dev": true, - "license": "MIT" - }, - "node_modules/dedent": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.7.2.tgz", - "integrity": "sha512-WzMx3mW98SN+zn3hgemf4OzdmyNhhhKz5Ay0pUfQiMQ3e1g+xmTJWp/pKdwKVXhdSkAEGIIzqeuWrL3mV/AXbA==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "babel-plugin-macros": "^3.1.0" - }, - "peerDependenciesMeta": { - "babel-plugin-macros": { - "optional": true - } - } - }, - "node_modules/deepmerge": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", - "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/detect-newline": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", - "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/dom-accessibility-api": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz", - "integrity": "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==", - "dev": true, - "license": "MIT" - }, - "node_modules/eastasianwidth": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", - "dev": true, - "license": "MIT" - }, - "node_modules/electron-to-chromium": { - "version": "1.5.344", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.344.tgz", - "integrity": "sha512-4MxfbmNDm+KPh066EZy+eUnkcDPcZ35wNmOWzFuh/ijvHsve6kbLTLURy88uCNK5FbpN+yk2nQY6BYh1GEt+wg==", - "dev": true, - "license": "ISC" - }, - "node_modules/emittery": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", - "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sindresorhus/emittery?sponsor=1" - } - }, - "node_modules/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true, - "license": "MIT" - }, - "node_modules/entities": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", - "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/error-ex": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", - "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-arrayish": "^0.2.1" - } - }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true, - "license": "BSD-2-Clause", - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "dev": true, - "license": "MIT", - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/execa/node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/exit-x": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/exit-x/-/exit-x-0.2.2.tgz", - "integrity": "sha512-+I6B/IkJc1o/2tiURyz/ivu/O0nKNEArIUB5O7zBrlDVJr22SCLH3xTeEry428LvFhRzIA1g8izguxJ/gbNcVQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/expect": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/expect/-/expect-30.4.1.tgz", - "integrity": "sha512-PMARsyh/JtqC20HoGqlFcIlQAyqUtW4PlI1rup1uhYJtKuwAjbvWi3GQMAn+STdHum/dk8xrKfUM1+5SAwpolA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/expect-utils": "30.4.1", - "@jest/get-type": "30.1.0", - "jest-matcher-utils": "30.4.1", - "jest-message-util": "30.4.1", - "jest-mock": "30.4.1", - "jest-util": "30.4.1" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true, - "license": "MIT" - }, - "node_modules/fb-watchman": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", - "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "bser": "2.1.1" - } - }, - "node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "license": "MIT", - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/foreground-child": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", - "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", - "dev": true, - "license": "ISC", - "dependencies": { - "cross-spawn": "^7.0.6", - "signal-exit": "^4.0.1" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true, - "license": "ISC" - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true, - "license": "ISC", - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "node_modules/get-package-type": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", - "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/glob": { - "version": "10.5.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", - "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", - "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", - "dev": true, - "license": "ISC", - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/html-encoding-sniffer": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz", - "integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "whatwg-encoding": "^3.1.1" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/html-escaper": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", - "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", - "dev": true, - "license": "MIT" - }, - "node_modules/http-proxy-agent": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", - "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", - "dev": true, - "license": "MIT", - "dependencies": { - "agent-base": "^7.1.0", - "debug": "^4.3.4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/https-proxy-agent": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", - "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", - "dev": true, - "license": "MIT", - "dependencies": { - "agent-base": "^7.1.2", - "debug": "4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/human-signals": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=10.17.0" - } - }, - "node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "dev": true, - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/import-local": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", - "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", - "dev": true, - "license": "MIT", - "dependencies": { - "pkg-dir": "^4.2.0", - "resolve-cwd": "^3.0.0" - }, - "bin": { - "import-local-fixture": "fixtures/cli.js" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", - "dev": true, - "license": "ISC", - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", - "dev": true, - "license": "MIT" - }, - "node_modules/is-core-module": { - "version": "2.16.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", - "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", - "dev": true, - "license": "MIT", - "dependencies": { - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/is-generator-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", - "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/is-potential-custom-element-name": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", - "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true, - "license": "ISC" - }, - "node_modules/istanbul-lib-coverage": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", - "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-instrument": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", - "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@babel/core": "^7.23.9", - "@babel/parser": "^7.23.9", - "@istanbuljs/schema": "^0.1.3", - "istanbul-lib-coverage": "^3.2.0", - "semver": "^7.5.4" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-lib-report": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", - "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^4.0.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-lib-source-maps": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz", - "integrity": "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.23", - "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-reports": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", - "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "html-escaper": "^2.0.0", - "istanbul-lib-report": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jackspeak": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", - "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/cliui": "^8.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - }, - "optionalDependencies": { - "@pkgjs/parseargs": "^0.11.0" - } - }, - "node_modules/jest": { - "version": "30.4.2", - "resolved": "https://registry.npmjs.org/jest/-/jest-30.4.2.tgz", - "integrity": "sha512-Yi1jqNC/Oq0N4hBgNH/YvBpP1P57QqundgytzYqy3yqAa7NZPNjSoi4SGbRAXDMdBzNE6xBCi5U7RgfrvMEUVQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/core": "30.4.2", - "@jest/types": "30.4.1", - "import-local": "^3.2.0", - "jest-cli": "30.4.2" - }, - "bin": { - "jest": "bin/jest.js" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/jest-changed-files": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-30.4.1.tgz", - "integrity": "sha512-IuctmYrxi21iOSOaIXpJWalHyPAsVv0GeBHKDn8C1CA4W5htHn7INL+wdnL4Bo0+olEndvAFkmb++tIQJG+vvg==", - "dev": true, - "license": "MIT", - "dependencies": { - "execa": "^5.1.1", - "jest-util": "30.4.1", - "p-limit": "^3.1.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-circus": { - "version": "30.4.2", - "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-30.4.2.tgz", - "integrity": "sha512-rvHH7VlY6LgbJXJTQ87GW62g1FntOtbhh0zT+v04kC+pgL6aBKyYINXxWukCpj3dcIBMw5/XUbtDS9dU9JTXeQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/environment": "30.4.1", - "@jest/expect": "30.4.1", - "@jest/test-result": "30.4.1", - "@jest/types": "30.4.1", - "@types/node": "*", - "chalk": "^4.1.2", - "co": "^4.6.0", - "dedent": "^1.6.0", - "is-generator-fn": "^2.1.0", - "jest-each": "30.4.1", - "jest-matcher-utils": "30.4.1", - "jest-message-util": "30.4.1", - "jest-runtime": "30.4.2", - "jest-snapshot": "30.4.1", - "jest-util": "30.4.1", - "p-limit": "^3.1.0", - "pretty-format": "30.4.1", - "pure-rand": "^7.0.0", - "slash": "^3.0.0", - "stack-utils": "^2.0.6" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-cli": { - "version": "30.4.2", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-30.4.2.tgz", - "integrity": "sha512-jfA2ocvVHMXS2QijrJ0d31ektP+d/W0T5RpcTX2Pq+3sVqHlsXVCM2+FmwpL+bdY8OfHpIg9xMxLF17Zg0U49Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/core": "30.4.2", - "@jest/test-result": "30.4.1", - "@jest/types": "30.4.1", - "chalk": "^4.1.2", - "exit-x": "^0.2.2", - "import-local": "^3.2.0", - "jest-config": "30.4.2", - "jest-util": "30.4.1", - "jest-validate": "30.4.1", - "yargs": "^17.7.2" - }, - "bin": { - "jest": "bin/jest.js" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/jest-config": { - "version": "30.4.2", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-30.4.2.tgz", - "integrity": "sha512-rNHAShJQqQwFNoL0hbf3BphSBOWnpOUAKvidLS/AjNVLPfoj5mSf4jQMfW3cYOs6hXeZC7nF7mDHaBnbxELOzg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/core": "^7.27.4", - "@jest/get-type": "30.1.0", - "@jest/pattern": "30.4.0", - "@jest/test-sequencer": "30.4.1", - "@jest/types": "30.4.1", - "babel-jest": "30.4.1", - "chalk": "^4.1.2", - "ci-info": "^4.2.0", - "deepmerge": "^4.3.1", - "glob": "^10.5.0", - "graceful-fs": "^4.2.11", - "jest-circus": "30.4.2", - "jest-docblock": "30.4.0", - "jest-environment-node": "30.4.1", - "jest-regex-util": "30.4.0", - "jest-resolve": "30.4.1", - "jest-runner": "30.4.2", - "jest-util": "30.4.1", - "jest-validate": "30.4.1", - "parse-json": "^5.2.0", - "pretty-format": "30.4.1", - "slash": "^3.0.0", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - }, - "peerDependencies": { - "@types/node": "*", - "esbuild-register": ">=3.4.0", - "ts-node": ">=9.0.0" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "esbuild-register": { - "optional": true - }, - "ts-node": { - "optional": true - } - } - }, - "node_modules/jest-diff": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.4.1.tgz", - "integrity": "sha512-CRpFK0RtLriVDGcPPAnR6HMVI8bSR2jnUIgralhauzYQZIb4RH9AtEInTuQr65LmmGggGcRT6HIASxwqsVsmlA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/diff-sequences": "30.4.0", - "@jest/get-type": "30.1.0", - "chalk": "^4.1.2", - "pretty-format": "30.4.1" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-docblock": { - "version": "30.4.0", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-30.4.0.tgz", - "integrity": "sha512-ZPMabUZCx5MpbZ2eBYSvZ0J8fvo3dR9oM+eeUpb3aKNQFuS2tu3Duw1TNlMoP8k3WQgKGJuhcMFvwcVuq6T7oA==", - "dev": true, - "license": "MIT", - "dependencies": { - "detect-newline": "^3.1.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-each": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-30.4.1.tgz", - "integrity": "sha512-/8MJbH6fuj48TstjrMf+u/pd06Qezz5xOXvZA6442heNOWr8bdeoGZX2d9fCn028CoMgYmroH9//zky5GfyYmA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/get-type": "30.1.0", - "@jest/types": "30.4.1", - "chalk": "^4.1.2", - "jest-util": "30.4.1", - "pretty-format": "30.4.1" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-environment-jsdom": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-30.4.1.tgz", - "integrity": "sha512-o3nfaN4zej7qgk2X0j8Jhq/S9nAVKs2xK3QeQxeHVvpkEPxaA1yxDGydR+iVI7zPy7Cp62Aq2h3Ja46QvfWHGA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/environment": "30.4.1", - "@jest/environment-jsdom-abstract": "30.4.1", - "jsdom": "^26.1.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - }, - "peerDependencies": { - "canvas": "^3.0.0" - }, - "peerDependenciesMeta": { - "canvas": { - "optional": true - } - } - }, - "node_modules/jest-environment-jsdom/node_modules/jsdom": { - "version": "26.1.0", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-26.1.0.tgz", - "integrity": "sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg==", - "dev": true, - "license": "MIT", - "dependencies": { - "cssstyle": "^4.2.1", - "data-urls": "^5.0.0", - "decimal.js": "^10.5.0", - "html-encoding-sniffer": "^4.0.0", - "http-proxy-agent": "^7.0.2", - "https-proxy-agent": "^7.0.6", - "is-potential-custom-element-name": "^1.0.1", - "nwsapi": "^2.2.16", - "parse5": "^7.2.1", - "rrweb-cssom": "^0.8.0", - "saxes": "^6.0.0", - "symbol-tree": "^3.2.4", - "tough-cookie": "^5.1.1", - "w3c-xmlserializer": "^5.0.0", - "webidl-conversions": "^7.0.0", - "whatwg-encoding": "^3.1.1", - "whatwg-mimetype": "^4.0.0", - "whatwg-url": "^14.1.1", - "ws": "^8.18.0", - "xml-name-validator": "^5.0.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "canvas": "^3.0.0" - }, - "peerDependenciesMeta": { - "canvas": { - "optional": true - } - } - }, - "node_modules/jest-environment-node": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-30.4.1.tgz", - "integrity": "sha512-4FZYVOk85hz2AyT6BbarKy9u37g6DbrDyCdFhsnDdXqyrueYQvB+0zO4f/kqLCRD0BsPRXPMNJeQwihKZV8naw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/environment": "30.4.1", - "@jest/fake-timers": "30.4.1", - "@jest/types": "30.4.1", - "@types/node": "*", - "jest-mock": "30.4.1", - "jest-util": "30.4.1", - "jest-validate": "30.4.1" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-haste-map": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-30.4.1.tgz", - "integrity": "sha512-rFrcONd8jeFsyw+Z9CrScJgglRf2+NFmNam8dKu7n+SoHqNYT47mn0DdEcVUZJpvh7Iz6/si7f7yUH7GJHVgnw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "30.4.1", - "@types/node": "*", - "anymatch": "^3.1.3", - "fb-watchman": "^2.0.2", - "graceful-fs": "^4.2.11", - "jest-regex-util": "30.4.0", - "jest-util": "30.4.1", - "jest-worker": "30.4.1", - "picomatch": "^4.0.3", - "walker": "^1.0.8" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - }, - "optionalDependencies": { - "fsevents": "^2.3.3" - } - }, - "node_modules/jest-leak-detector": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-30.4.1.tgz", - "integrity": "sha512-IpmyiioeHxiWDhesHnUFmOxcTzwCwKpgACgWajtAP+nYQXiY7DakTxB6Bx9JFiRMljr0AX1PvnQdaU1KFoz6NQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/get-type": "30.1.0", - "pretty-format": "30.4.1" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-matcher-utils": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-30.4.1.tgz", - "integrity": "sha512-zvYfX5CaeEkFrrLS9suWe9rvJrm9J1Iv3ua8kIBv9GEPzcnsfBf0bob37la7s67fs0nlBC3EuvkOLnXQKxtx4A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/get-type": "30.1.0", - "chalk": "^4.1.2", - "jest-diff": "30.4.1", - "pretty-format": "30.4.1" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-message-util": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.4.1.tgz", - "integrity": "sha512-kwCKIvq0MCW1HzLoGola9Te6JUdzgV0loyKJ3Qghrkz9i5/RRIHsL95BMQc2HBBhlBKC4j22K9p11TGHH8RBpQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.27.1", - "@jest/types": "30.4.1", - "@types/stack-utils": "^2.0.3", - "chalk": "^4.1.2", - "graceful-fs": "^4.2.11", - "jest-util": "30.4.1", - "picomatch": "^4.0.3", - "pretty-format": "30.4.1", - "slash": "^3.0.0", - "stack-utils": "^2.0.6" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-mock": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-30.4.1.tgz", - "integrity": "sha512-/i8SVb8/NSB7RfNi8gfqu8gxLV23KaL5EpAttyb9iz8qWRIqXRLflycz/32wXsYkOnaUlx8NAKnJYtpsmXUmfw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "30.4.1", - "@types/node": "*", - "jest-util": "30.4.1" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-pnp-resolver": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", - "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - }, - "peerDependencies": { - "jest-resolve": "*" - }, - "peerDependenciesMeta": { - "jest-resolve": { - "optional": true - } - } - }, - "node_modules/jest-regex-util": { - "version": "30.4.0", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.4.0.tgz", - "integrity": "sha512-mWlvLviKIgIQ8VCuM1xRdD0TWp3zlzionlmDBjuXVBs+VkmXq6FgW9T4Emr7oGz/Rk6feDCGyiugolcQEyp3mg==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-resolve": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-30.4.1.tgz", - "integrity": "sha512-Zry8Yq/yJcNAZ7dJ5F2heic8AheXvbFZ7XI5V+h28nrYZ7Qoyy4dItq8OodjnYD270mvX+ZudmrNV9cysqhW5Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "^4.1.2", - "graceful-fs": "^4.2.11", - "jest-haste-map": "30.4.1", - "jest-pnp-resolver": "^1.2.3", - "jest-util": "30.4.1", - "jest-validate": "30.4.1", - "slash": "^3.0.0", - "unrs-resolver": "^1.7.11" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-resolve-dependencies": { - "version": "30.4.2", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-30.4.2.tgz", - "integrity": "sha512-gDiVh1I+GxYzz9oXlyw+1wv6VOYX1WYxMOfjsA3iGKePV2oxmbHhwxfkALxNxYy1ciw6APWwkW2zZONwP97aEQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "jest-regex-util": "30.4.0", - "jest-snapshot": "30.4.1" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-runner": { - "version": "30.4.2", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-30.4.2.tgz", - "integrity": "sha512-2dw0PslVYXxffXGpLo+Ejad+KcI1Qkjn7f4X4619gf21oCUmL+SPfjqIa/losUem3yEOvfNZe/F1HWUcNpODcg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/console": "30.4.1", - "@jest/environment": "30.4.1", - "@jest/test-result": "30.4.1", - "@jest/transform": "30.4.1", - "@jest/types": "30.4.1", - "@types/node": "*", - "chalk": "^4.1.2", - "emittery": "^0.13.1", - "exit-x": "^0.2.2", - "graceful-fs": "^4.2.11", - "jest-docblock": "30.4.0", - "jest-environment-node": "30.4.1", - "jest-haste-map": "30.4.1", - "jest-leak-detector": "30.4.1", - "jest-message-util": "30.4.1", - "jest-resolve": "30.4.1", - "jest-runtime": "30.4.2", - "jest-util": "30.4.1", - "jest-watcher": "30.4.1", - "jest-worker": "30.4.1", - "p-limit": "^3.1.0", - "source-map-support": "0.5.13" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-runtime": { - "version": "30.4.2", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-30.4.2.tgz", - "integrity": "sha512-3/5e8iPz2k/VLqlr8DgTftYyLUv8Su3FkCAO2/Od81UsUTpSxOrS6O5x5KkoQwyUjmpYyDJKeyAvg2T2nvpNkQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/environment": "30.4.1", - "@jest/fake-timers": "30.4.1", - "@jest/globals": "30.4.1", - "@jest/source-map": "30.0.1", - "@jest/test-result": "30.4.1", - "@jest/transform": "30.4.1", - "@jest/types": "30.4.1", - "@types/node": "*", - "chalk": "^4.1.2", - "cjs-module-lexer": "^2.1.0", - "collect-v8-coverage": "^1.0.2", - "glob": "^10.5.0", - "graceful-fs": "^4.2.11", - "jest-haste-map": "30.4.1", - "jest-message-util": "30.4.1", - "jest-mock": "30.4.1", - "jest-regex-util": "30.4.0", - "jest-resolve": "30.4.1", - "jest-snapshot": "30.4.1", - "jest-util": "30.4.1", - "slash": "^3.0.0", - "strip-bom": "^4.0.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-snapshot": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-30.4.1.tgz", - "integrity": "sha512-tEOkkfOMppUyeiHwjZswOQ3lcnoTnws/q5FnGIaeIh/jmoU0ZlgMYRR8sTlTj+nNGCoJ0RDq6SfxGxCsyMTPmw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/core": "^7.27.4", - "@babel/generator": "^7.27.5", - "@babel/plugin-syntax-jsx": "^7.27.1", - "@babel/plugin-syntax-typescript": "^7.27.1", - "@babel/types": "^7.27.3", - "@jest/expect-utils": "30.4.1", - "@jest/get-type": "30.1.0", - "@jest/snapshot-utils": "30.4.1", - "@jest/transform": "30.4.1", - "@jest/types": "30.4.1", - "babel-preset-current-node-syntax": "^1.2.0", - "chalk": "^4.1.2", - "expect": "30.4.1", - "graceful-fs": "^4.2.11", - "jest-diff": "30.4.1", - "jest-matcher-utils": "30.4.1", - "jest-message-util": "30.4.1", - "jest-util": "30.4.1", - "pretty-format": "30.4.1", - "semver": "^7.7.2", - "synckit": "^0.11.8" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-util": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.4.1.tgz", - "integrity": "sha512-vjQb1sACEiv13DKJMDToJpzVW0joCsIQrmbg0fi7CyOOt+g9jTuQl2A216pWRBYhOVt53XbL/2LbMKg1BECWOw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "30.4.1", - "@types/node": "*", - "chalk": "^4.1.2", - "ci-info": "^4.2.0", - "graceful-fs": "^4.2.11", - "picomatch": "^4.0.3" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-validate": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-30.4.1.tgz", - "integrity": "sha512-PDWi4SOwLnwqNDfHZjOcsEFyZ4fc/2W2gVL3DEoyqnB6jCQMLRtfBong8s6omIw3lI0HWOus12xfnFmQtjW3fw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/get-type": "30.1.0", - "@jest/types": "30.4.1", - "camelcase": "^6.3.0", - "chalk": "^4.1.2", - "leven": "^3.1.0", - "pretty-format": "30.4.1" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-validate/node_modules/camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/jest-watcher": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-30.4.1.tgz", - "integrity": "sha512-/l9UonmvCwjHH7d2h3iAwIloLc1H0S8mJZ/LNK3i86hqwPAz8otUJjP9MfYtz9Tt77Su5FD2xGjZn8d31IZHlw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/test-result": "30.4.1", - "@jest/types": "30.4.1", - "@types/node": "*", - "ansi-escapes": "^4.3.2", - "chalk": "^4.1.2", - "emittery": "^0.13.1", - "jest-util": "30.4.1", - "string-length": "^4.0.2" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-worker": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-30.4.1.tgz", - "integrity": "sha512-SHynN/q/QD++iNyvMdy+WMmbCGk8jIsNcRxycXbWubSOhvo6T+j2afcfUSl+3hYsiBebOTo0cT7c2H7CXugu1g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*", - "@ungap/structured-clone": "^1.3.0", - "jest-util": "30.4.1", - "merge-stream": "^2.0.0", - "supports-color": "^8.1.1" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-worker/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "node_modules/js-yaml": { - "version": "3.14.2", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", - "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", - "dev": true, - "license": "MIT", - "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/jsdom": { - "version": "29.1.1", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-29.1.1.tgz", - "integrity": "sha512-ECi4Fi2f7BdJtUKTflYRTiaMxIB0O6zfR1fX0GXpUrf6flp8QIYn1UT20YQqdSOfk2dfkCwS8LAFoJDEppNK5Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@asamuzakjp/css-color": "^5.1.11", - "@asamuzakjp/dom-selector": "^7.1.1", - "@bramus/specificity": "^2.4.2", - "@csstools/css-syntax-patches-for-csstree": "^1.1.3", - "@exodus/bytes": "^1.15.0", - "css-tree": "^3.2.1", - "data-urls": "^7.0.0", - "decimal.js": "^10.6.0", - "html-encoding-sniffer": "^6.0.0", - "is-potential-custom-element-name": "^1.0.1", - "lru-cache": "^11.3.5", - "parse5": "^8.0.1", - "saxes": "^6.0.0", - "symbol-tree": "^3.2.4", - "tough-cookie": "^6.0.1", - "undici": "^7.25.0", - "w3c-xmlserializer": "^5.0.0", - "webidl-conversions": "^8.0.1", - "whatwg-mimetype": "^5.0.0", - "whatwg-url": "^16.0.1", - "xml-name-validator": "^5.0.0" - }, - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24.0.0" - }, - "peerDependencies": { - "canvas": "^3.0.0" - }, - "peerDependenciesMeta": { - "canvas": { - "optional": true - } - } - }, - "node_modules/jsdom/node_modules/@asamuzakjp/css-color": { - "version": "5.1.11", - "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-5.1.11.tgz", - "integrity": "sha512-KVw6qIiCTUQhByfTd78h2yD1/00waTmm9uy/R7Ck/ctUyAPj+AEDLkQIdJW0T8+qGgj3j5bpNKK7Q3G+LedJWg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@asamuzakjp/generational-cache": "^1.0.1", - "@csstools/css-calc": "^3.2.0", - "@csstools/css-color-parser": "^4.1.0", - "@csstools/css-parser-algorithms": "^4.0.0", - "@csstools/css-tokenizer": "^4.0.0" - }, - "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0" - } - }, - "node_modules/jsdom/node_modules/@csstools/color-helpers": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-6.0.2.tgz", - "integrity": "sha512-LMGQLS9EuADloEFkcTBR3BwV/CGHV7zyDxVRtVDTwdI2Ca4it0CCVTT9wCkxSgokjE5Ho41hEPgb8OEUwoXr6Q==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "engines": { - "node": ">=20.19.0" - } - }, - "node_modules/jsdom/node_modules/@csstools/css-calc": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-3.2.0.tgz", - "integrity": "sha512-bR9e6o2BDB12jzN/gIbjHa5wLJ4UjD1CB9pM7ehlc0ddk6EBz+yYS1EV2MF55/HUxrHcB/hehAyt5vhsA3hx7w==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT", - "engines": { - "node": ">=20.19.0" - }, - "peerDependencies": { - "@csstools/css-parser-algorithms": "^4.0.0", - "@csstools/css-tokenizer": "^4.0.0" - } - }, - "node_modules/jsdom/node_modules/@csstools/css-color-parser": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-4.1.0.tgz", - "integrity": "sha512-U0KhLYmy2GVj6q4T3WaAe6NPuFYCPQoE3b0dRGxejWDgcPp8TP7S5rVdM5ZrFaqu4N67X8YaPBw14dQSYx3IyQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT", - "dependencies": { - "@csstools/color-helpers": "^6.0.2", - "@csstools/css-calc": "^3.2.0" - }, - "engines": { - "node": ">=20.19.0" - }, - "peerDependencies": { - "@csstools/css-parser-algorithms": "^4.0.0", - "@csstools/css-tokenizer": "^4.0.0" - } - }, - "node_modules/jsdom/node_modules/@csstools/css-parser-algorithms": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-4.0.0.tgz", - "integrity": "sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT", - "engines": { - "node": ">=20.19.0" - }, - "peerDependencies": { - "@csstools/css-tokenizer": "^4.0.0" - } - }, - "node_modules/jsdom/node_modules/@csstools/css-tokenizer": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-4.0.0.tgz", - "integrity": "sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT", - "engines": { - "node": ">=20.19.0" - } - }, - "node_modules/jsdom/node_modules/data-urls": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-7.0.0.tgz", - "integrity": "sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA==", - "dev": true, - "license": "MIT", - "dependencies": { - "whatwg-mimetype": "^5.0.0", - "whatwg-url": "^16.0.0" - }, - "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0" - } - }, - "node_modules/jsdom/node_modules/entities": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-8.0.0.tgz", - "integrity": "sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=20.19.0" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/jsdom/node_modules/html-encoding-sniffer": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-6.0.0.tgz", - "integrity": "sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@exodus/bytes": "^1.6.0" - }, - "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0" - } - }, - "node_modules/jsdom/node_modules/lru-cache": { - "version": "11.3.6", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.3.6.tgz", - "integrity": "sha512-Gf/KoL3C/MlI7Bt0PGI9I+TeTC/I6r/csU58N4BSNc4lppLBeKsOdFYkK+dX0ABDUMJNfCHTyPpzwwO21Awd3A==", - "dev": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": "20 || >=22" - } - }, - "node_modules/jsdom/node_modules/parse5": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.1.tgz", - "integrity": "sha512-z1e/HMG90obSGeidlli3hj7cbocou0/wa5HacvI3ASx34PecNjNQeaHNo5WIZpWofN9kgkqV1q5YvXe3F0FoPw==", - "dev": true, - "license": "MIT", - "dependencies": { - "entities": "^8.0.0" - }, - "funding": { - "url": "https://github.com/inikulin/parse5?sponsor=1" - } - }, - "node_modules/jsdom/node_modules/tldts": { - "version": "7.0.30", - "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.0.30.tgz", - "integrity": "sha512-ELrFxuqsDdHUwoh0XxDbxuLD3Wnz49Z57IFvTtvWy1hJdcMZjXLIuonjilCiWHlT2GbE4Wlv1wKVTzDFnXH1aw==", - "dev": true, - "license": "MIT", - "dependencies": { - "tldts-core": "^7.0.30" - }, - "bin": { - "tldts": "bin/cli.js" - } - }, - "node_modules/jsdom/node_modules/tldts-core": { - "version": "7.0.30", - "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.0.30.tgz", - "integrity": "sha512-uiHN8PIB1VmWyS98eZYja4xzlYqeFZVjb4OuYlJQnZAuJhMw4PbKQOKgHKhBdJR3FE/t5mUQ1Kd80++B+qhD1Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/jsdom/node_modules/tough-cookie": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.1.tgz", - "integrity": "sha512-LktZQb3IeoUWB9lqR5EWTHgW/VTITCXg4D21M+lvybRVdylLrRMnqaIONLVb5mav8vM19m44HIcGq4qASeu2Qw==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "tldts": "^7.0.5" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/jsdom/node_modules/tr46": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-6.0.0.tgz", - "integrity": "sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==", - "dev": true, - "license": "MIT", - "dependencies": { - "punycode": "^2.3.1" - }, - "engines": { - "node": ">=20" - } - }, - "node_modules/jsdom/node_modules/webidl-conversions": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.1.tgz", - "integrity": "sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=20" - } - }, - "node_modules/jsdom/node_modules/whatwg-mimetype": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-5.0.0.tgz", - "integrity": "sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=20" - } - }, - "node_modules/jsdom/node_modules/whatwg-url": { - "version": "16.0.1", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-16.0.1.tgz", - "integrity": "sha512-1to4zXBxmXHV3IiSSEInrreIlu02vUOvrhxJJH5vcxYTBDAx51cqZiKdyTxlecdKNSjj8EcxGBxNf6Vg+945gw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@exodus/bytes": "^1.11.0", - "tr46": "^6.0.0", - "webidl-conversions": "^8.0.1" - }, - "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0" - } - }, - "node_modules/jsesc": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", - "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", - "dev": true, - "license": "MIT", - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "dev": true, - "license": "MIT" - }, - "node_modules/json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "dev": true, - "license": "MIT", - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/leven": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", - "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/lines-and-columns": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", - "dev": true, - "license": "MIT" - }, - "node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/lodash.debounce": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", - "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", - "dev": true, - "license": "MIT" - }, - "node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/make-dir": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", - "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", - "dev": true, - "license": "MIT", - "dependencies": { - "semver": "^7.5.3" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/makeerror": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", - "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "tmpl": "1.0.5" - } - }, - "node_modules/mdn-data": { - "version": "2.27.1", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.27.1.tgz", - "integrity": "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==", - "dev": true, - "license": "CC0-1.0" - }, - "node_modules/merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true, - "license": "MIT" - }, - "node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/min-indent": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", - "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/minimatch": { - "version": "9.0.9", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", - "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.2" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/minipass": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", - "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", - "dev": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT" - }, - "node_modules/napi-postinstall": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.4.tgz", - "integrity": "sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==", - "dev": true, - "license": "MIT", - "bin": { - "napi-postinstall": "lib/cli.js" - }, - "engines": { - "node": "^12.20.0 || ^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/napi-postinstall" - } - }, - "node_modules/natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true, - "license": "MIT" - }, - "node_modules/node-int64": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", - "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", - "dev": true, - "license": "MIT" - }, - "node_modules/node-releases": { - "version": "2.0.38", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.38.tgz", - "integrity": "sha512-3qT/88Y3FbH/Kx4szpQQ4HzUbVrHPKTLVpVocKiLfoYvw9XSGOX2FmD2d6DrXbVYyAQTF2HeF6My8jmzx7/CRw==", - "dev": true, - "license": "MIT" - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/nwsapi": { - "version": "2.2.22", - "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.22.tgz", - "integrity": "sha512-ujSMe1OWVn55euT1ihwCI1ZcAaAU3nxUiDwfDQldc51ZXaB9m2AyOn6/jh1BLe2t/G8xd6uKG1UBF2aZJeg2SQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, - "license": "ISC", - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "mimic-fn": "^2.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/p-locate/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/package-json-from-dist": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", - "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", - "dev": true, - "license": "BlueOak-1.0.0" - }, - "node_modules/parse-json": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/parse5": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", - "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", - "dev": true, - "license": "MIT", - "dependencies": { - "entities": "^6.0.0" - }, - "funding": { - "url": "https://github.com/inikulin/parse5?sponsor=1" - } - }, - "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true, - "license": "MIT" - }, - "node_modules/path-scurry": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", - "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "lru-cache": "^10.2.0", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" - }, - "engines": { - "node": ">=16 || 14 >=14.18" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true, - "license": "ISC" - }, - "node_modules/picomatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", - "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/pirates": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", - "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 6" - } - }, - "node_modules/pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "find-up": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/pretty-format": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.4.1.tgz", - "integrity": "sha512-K6KiKMHTL4jjX4u3Kir2EW07nRfcqVTXIImx50wbjHQTcZPgg+gjVeNTIT3l3L1Rd4UefxfogquC9J37SoFyyw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/schemas": "30.4.1", - "ansi-styles": "^5.2.0", - "react-is-18": "npm:react-is@^18.3.1", - "react-is-19": "npm:react-is@^19.2.5" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/punycode": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/pure-rand": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-7.0.1.tgz", - "integrity": "sha512-oTUZM/NAZS8p7ANR3SHh30kXB+zK2r2BPcEn/awJIbOvq82WoMN4p62AWWp3Hhw50G0xMsw1mhIBLqHw64EcNQ==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/dubzzz" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fast-check" - } - ], - "license": "MIT" - }, - "node_modules/react-is-18": { - "name": "react-is", - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", - "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", - "dev": true, - "license": "MIT" - }, - "node_modules/react-is-19": { - "name": "react-is", - "version": "19.2.6", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.6.tgz", - "integrity": "sha512-XjBR15BhXuylgWGuslhDKqlSayuqvqBX91BP8pauG8kd1zY8kotkNWbXksTCNRarse4kuGbe2kIY05ARtwNIvw==", - "dev": true, - "license": "MIT" - }, - "node_modules/redent": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", - "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", - "dev": true, - "license": "MIT", - "dependencies": { - "indent-string": "^4.0.0", - "strip-indent": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/regenerate": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", - "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", - "dev": true, - "license": "MIT" - }, - "node_modules/regenerate-unicode-properties": { - "version": "10.2.2", - "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.2.tgz", - "integrity": "sha512-m03P+zhBeQd1RGnYxrGyDAPpWX/epKirLrp8e3qevZdVkKtnCrjjWczIbYc8+xd6vcTStVlqfycTx1KR4LOr0g==", - "dev": true, - "license": "MIT", - "dependencies": { - "regenerate": "^1.4.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/regexpu-core": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.4.0.tgz", - "integrity": "sha512-0ghuzq67LI9bLXpOX/ISfve/Mq33a4aFRzoQYhnnok1JOFpmE/A2TBGkNVenOGEeSBCjIiWcc6MVOG5HEQv0sA==", - "dev": true, - "license": "MIT", - "dependencies": { - "regenerate": "^1.4.2", - "regenerate-unicode-properties": "^10.2.2", - "regjsgen": "^0.8.0", - "regjsparser": "^0.13.0", - "unicode-match-property-ecmascript": "^2.0.0", - "unicode-match-property-value-ecmascript": "^2.2.1" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/regjsgen": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.8.0.tgz", - "integrity": "sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/regjsparser": { - "version": "0.13.0", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.13.0.tgz", - "integrity": "sha512-NZQZdC5wOE/H3UT28fVGL+ikOZcEzfMGk/c3iN9UGxzWHMa1op7274oyiUVrAG4B2EuFhus8SvkaYnhvW92p9Q==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "jsesc": "~3.1.0" - }, - "bin": { - "regjsparser": "bin/parser" - } - }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/require-from-string": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/resolve": { - "version": "1.22.11", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", - "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-core-module": "^2.16.1", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/resolve-cwd": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", - "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "resolve-from": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/rrweb-cssom": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.8.0.tgz", - "integrity": "sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==", - "dev": true, - "license": "MIT" - }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true, - "license": "MIT" - }, - "node_modules/saxes": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", - "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", - "dev": true, - "license": "ISC", - "dependencies": { - "xmlchars": "^2.2.0" - }, - "engines": { - "node": ">=v12.22.7" - } - }, - "node_modules/semver": { - "version": "7.8.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.0.tgz", - "integrity": "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "license": "MIT", - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-map-support": { - "version": "0.5.13", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", - "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", - "dev": true, - "license": "MIT", - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "node_modules/sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/stack-utils": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", - "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "escape-string-regexp": "^2.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/stack-utils/node_modules/escape-string-regexp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", - "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/string-length": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", - "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "char-regex": "^1.0.2", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/string-length/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/string-length/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/string-width": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/string-width-cjs": { - "name": "string-width", - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/string-width-cjs/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/string-width-cjs/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, - "node_modules/string-width-cjs/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", - "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.2.2" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/strip-ansi-cjs": { - "name": "strip-ansi", - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-bom": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", - "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/strip-indent": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", - "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "min-indent": "^1.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/symbol-tree": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", - "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", - "dev": true, - "license": "MIT" - }, - "node_modules/synckit": { - "version": "0.11.12", - "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.12.tgz", - "integrity": "sha512-Bh7QjT8/SuKUIfObSXNHNSK6WHo6J1tHCqJsuaFDP7gP0fkzSfTxI8y85JrppZ0h8l0maIgc2tfuZQ6/t3GtnQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@pkgr/core": "^0.2.9" - }, - "engines": { - "node": "^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/synckit" - } - }, - "node_modules/test-exclude": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", - "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", - "dev": true, - "license": "ISC", - "dependencies": { - "@istanbuljs/schema": "^0.1.2", - "glob": "^7.1.4", - "minimatch": "^3.0.4" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/test-exclude/node_modules/brace-expansion": { - "version": "1.1.14", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", - "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/test-exclude/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", - "dev": true, - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/test-exclude/node_modules/minimatch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", - "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/tldts": { - "version": "6.1.86", - "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.86.tgz", - "integrity": "sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "tldts-core": "^6.1.86" - }, - "bin": { - "tldts": "bin/cli.js" - } - }, - "node_modules/tldts-core": { - "version": "6.1.86", - "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.86.tgz", - "integrity": "sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==", - "dev": true, - "license": "MIT" - }, - "node_modules/tmpl": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", - "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/tough-cookie": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.1.2.tgz", - "integrity": "sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "tldts": "^6.1.32" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/tr46": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz", - "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==", - "dev": true, - "license": "MIT", - "dependencies": { - "punycode": "^2.3.1" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD", - "optional": true - }, - "node_modules/type-detect": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/type-fest": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/undici": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-7.29.0.tgz", - "integrity": "sha512-IDxfleLmmbSskfWSUATiN1nfn2rDuvnMOqb5CWR92iIfojA0Ud+ulOAAEQ57LPr9rWmsreUyf5lwyao+7GNNVw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=20.18.1" - } - }, - "node_modules/undici-types": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", - "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", - "dev": true, - "license": "MIT" - }, - "node_modules/unicode-canonical-property-names-ecmascript": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.1.tgz", - "integrity": "sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/unicode-match-property-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", - "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "unicode-canonical-property-names-ecmascript": "^2.0.0", - "unicode-property-aliases-ecmascript": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/unicode-match-property-value-ecmascript": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.1.tgz", - "integrity": "sha512-JQ84qTuMg4nVkx8ga4A16a1epI9H6uTXAknqxkGF/aFfRLw1xC/Bp24HNLaZhHSkWd3+84t8iXnp1J0kYcZHhg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/unicode-property-aliases-ecmascript": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.2.0.tgz", - "integrity": "sha512-hpbDzxUY9BFwX+UeBnxv3Sh1q7HFxj48DTmXchNgRa46lO8uj3/1iEn3MiNUYTg1g9ctIqXCCERn8gYZhHC5lQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/unrs-resolver": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.11.1.tgz", - "integrity": "sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "dependencies": { - "napi-postinstall": "^0.3.0" - }, - "funding": { - "url": "https://opencollective.com/unrs-resolver" - }, - "optionalDependencies": { - "@unrs/resolver-binding-android-arm-eabi": "1.11.1", - "@unrs/resolver-binding-android-arm64": "1.11.1", - "@unrs/resolver-binding-darwin-arm64": "1.11.1", - "@unrs/resolver-binding-darwin-x64": "1.11.1", - "@unrs/resolver-binding-freebsd-x64": "1.11.1", - "@unrs/resolver-binding-linux-arm-gnueabihf": "1.11.1", - "@unrs/resolver-binding-linux-arm-musleabihf": "1.11.1", - "@unrs/resolver-binding-linux-arm64-gnu": "1.11.1", - "@unrs/resolver-binding-linux-arm64-musl": "1.11.1", - "@unrs/resolver-binding-linux-ppc64-gnu": "1.11.1", - "@unrs/resolver-binding-linux-riscv64-gnu": "1.11.1", - "@unrs/resolver-binding-linux-riscv64-musl": "1.11.1", - "@unrs/resolver-binding-linux-s390x-gnu": "1.11.1", - "@unrs/resolver-binding-linux-x64-gnu": "1.11.1", - "@unrs/resolver-binding-linux-x64-musl": "1.11.1", - "@unrs/resolver-binding-wasm32-wasi": "1.11.1", - "@unrs/resolver-binding-win32-arm64-msvc": "1.11.1", - "@unrs/resolver-binding-win32-ia32-msvc": "1.11.1", - "@unrs/resolver-binding-win32-x64-msvc": "1.11.1" - } - }, - "node_modules/update-browserslist-db": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", - "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "escalade": "^3.2.0", - "picocolors": "^1.1.1" - }, - "bin": { - "update-browserslist-db": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" - } - }, - "node_modules/v8-to-istanbul": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", - "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", - "dev": true, - "license": "ISC", - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.12", - "@types/istanbul-lib-coverage": "^2.0.1", - "convert-source-map": "^2.0.0" - }, - "engines": { - "node": ">=10.12.0" - } - }, - "node_modules/w3c-xmlserializer": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", - "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", - "dev": true, - "license": "MIT", - "dependencies": { - "xml-name-validator": "^5.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/walker": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", - "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "makeerror": "1.0.12" - } - }, - "node_modules/webidl-conversions": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", - "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=12" - } - }, - "node_modules/whatwg-encoding": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", - "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "iconv-lite": "0.6.3" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/whatwg-mimetype": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", - "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/whatwg-url": { - "version": "14.2.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz", - "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==", - "dev": true, - "license": "MIT", - "dependencies": { - "tr46": "^5.1.0", - "webidl-conversions": "^7.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/wrap-ansi": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", - "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.1.0", - "string-width": "^5.0.1", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrap-ansi-cjs": { - "name": "wrap-ansi", - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, - "node_modules/wrap-ansi-cjs/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi/node_modules/ansi-styles": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", - "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/write-file-atomic": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-5.0.1.tgz", - "integrity": "sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==", - "dev": true, - "license": "ISC", - "dependencies": { - "imurmurhash": "^0.1.4", - "signal-exit": "^4.0.1" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/ws": { - "version": "8.21.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.1.tgz", - "integrity": "sha512-+0NTnW77fFN/DjQi6k/Sq/Yvk4Sgajw7urW8V+asjXnRgDs9gyGkdb7EzgfhA4goXsRIZKE28fzIXBHEzhuiWw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/xml-name-validator": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", - "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=18" - } - }, - "node_modules/xmlchars": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", - "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", - "dev": true, - "license": "MIT" - }, - "node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=10" - } - }, - "node_modules/yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true, - "license": "ISC" - }, - "node_modules/yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", - "dev": true, - "license": "MIT", - "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/yargs/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/yargs/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, - "node_modules/yargs/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/yargs/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - } - } -} diff --git a/tests/js/package.json b/tests/js/package.json deleted file mode 100644 index 86031e068c1..00000000000 --- a/tests/js/package.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "name": "js-tests", - "version": "1.0.0", - "description": "", - "main": "index.js", - "type": "module", - "scripts": { - "test": "jest" - }, - "keywords": [], - "author": "", - "license": "ISC", - "devDependencies": { - "@babel/preset-env": "^7.24.7", - "jest": "^30.0.0", - "jest-environment-jsdom": "^30.2.0", - "@testing-library/jest-dom": "^6.4.6", - "jsdom": "^29.0.0" - } -} diff --git a/tests/js/table_widget.test.js b/tests/js/table_widget.test.js deleted file mode 100644 index d701d8692e5..00000000000 --- a/tests/js/table_widget.test.js +++ /dev/null @@ -1,531 +0,0 @@ -/* - * Copyright 2025 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { jest } from '@jest/globals'; - -describe('TableWidget', () => { - let model; - let el; - let render; - - beforeEach(async () => { - jest.resetModules(); - document.body.innerHTML = '
'; - el = document.body.querySelector('div'); - - const tableWidget = ( - await import('../../bigframes/display/table_widget.js') - ).default; - render = tableWidget.render; - - model = { - get: jest.fn(), - set: jest.fn(), - save_changes: jest.fn(), - on: jest.fn(), - }; - }); - - it('should have a render function', () => { - expect(render).toBeDefined(); - }); - - describe('render', () => { - it('should create the basic structure', () => { - // Mock the initial state - model.get.mockImplementation((property) => { - if (property === 'table_html') { - return ''; - } - if (property === 'row_count') { - return 100; - } - if (property === 'error_message') { - return null; - } - if (property === 'page_size') { - return 10; - } - if (property === 'page') { - return 0; - } - return null; - }); - - render({ model, el }); - - expect(el.classList.contains('bigframes-widget')).toBe(true); - expect(el.querySelector('.error-message')).not.toBeNull(); - expect(el.querySelector('div')).not.toBeNull(); - expect(el.querySelector('div:nth-child(3)')).not.toBeNull(); - }); - - it('should sort when a sortable column is clicked', () => { - // Mock the initial state - model.get.mockImplementation((property) => { - if (property === 'table_html') { - return '
col1
'; - } - if (property === 'orderable_columns') { - return ['col1']; - } - if (property === 'sort_context') { - return []; - } - return null; - }); - - render({ model, el }); - - // Manually trigger the table_html change handler - const tableHtmlChangeHandler = model.on.mock.calls.find( - (call) => call[0] === 'change:table_html', - )[1]; - tableHtmlChangeHandler(); - - const header = el.querySelector('th'); - header.click(); - - expect(model.set).toHaveBeenCalledWith('sort_context', [ - { column: 'col1', ascending: true }, - ]); - expect(model.save_changes).toHaveBeenCalled(); - }); - - it('should reverse sort direction when a sorted column is clicked', () => { - // Mock the initial state - model.get.mockImplementation((property) => { - if (property === 'table_html') { - return '
col1
'; - } - if (property === 'orderable_columns') { - return ['col1']; - } - if (property === 'sort_context') { - return [{ column: 'col1', ascending: true }]; - } - return null; - }); - - render({ model, el }); - - // Manually trigger the table_html change handler - const tableHtmlChangeHandler = model.on.mock.calls.find( - (call) => call[0] === 'change:table_html', - )[1]; - tableHtmlChangeHandler(); - - const header = el.querySelector('th'); - header.click(); - - expect(model.set).toHaveBeenCalledWith('sort_context', [ - { column: 'col1', ascending: false }, - ]); - expect(model.save_changes).toHaveBeenCalled(); - }); - - it('should clear sort when a descending sorted column is clicked', () => { - // Mock the initial state - model.get.mockImplementation((property) => { - if (property === 'table_html') { - return '
col1
'; - } - if (property === 'orderable_columns') { - return ['col1']; - } - if (property === 'sort_context') { - return [{ column: 'col1', ascending: false }]; - } - return null; - }); - - render({ model, el }); - - // Manually trigger the table_html change handler - const tableHtmlChangeHandler = model.on.mock.calls.find( - (call) => call[0] === 'change:table_html', - )[1]; - tableHtmlChangeHandler(); - - const header = el.querySelector('th'); - header.click(); - - expect(model.set).toHaveBeenCalledWith('sort_context', []); - expect(model.save_changes).toHaveBeenCalled(); - }); - - it('should display the correct sort indicator', () => { - // Mock the initial state - model.get.mockImplementation((property) => { - if (property === 'table_html') { - return '
col1
col2
'; - } - if (property === 'orderable_columns') { - return ['col1', 'col2']; - } - if (property === 'sort_context') { - return [{ column: 'col1', ascending: true }]; - } - return null; - }); - - render({ model, el }); - - // Manually trigger the table_html change handler - const tableHtmlChangeHandler = model.on.mock.calls.find( - (call) => call[0] === 'change:table_html', - )[1]; - tableHtmlChangeHandler(); - - const headers = el.querySelectorAll('th'); - const indicator1 = headers[0].querySelector('.sort-indicator'); - const indicator2 = headers[1].querySelector('.sort-indicator'); - - expect(indicator1.textContent).toBe('▲'); - expect(indicator2.textContent).toBe('●'); - }); - - it('should add a column to sort when Shift+Click is used', () => { - // Mock the initial state: already sorted by col1 asc - model.get.mockImplementation((property) => { - if (property === 'table_html') { - return '
col1
col2
'; - } - if (property === 'orderable_columns') { - return ['col1', 'col2']; - } - if (property === 'sort_context') { - return [{ column: 'col1', ascending: true }]; - } - return null; - }); - - render({ model, el }); - - // Manually trigger the table_html change handler - const tableHtmlChangeHandler = model.on.mock.calls.find( - (call) => call[0] === 'change:table_html', - )[1]; - tableHtmlChangeHandler(); - - const headers = el.querySelectorAll('th'); - const header2 = headers[1]; // col2 - - // Simulate Shift+Click - const clickEvent = new MouseEvent('click', { - bubbles: true, - cancelable: true, - shiftKey: true, - }); - header2.dispatchEvent(clickEvent); - - expect(model.set).toHaveBeenCalledWith('sort_context', [ - { column: 'col1', ascending: true }, - { column: 'col2', ascending: true }, - ]); - expect(model.save_changes).toHaveBeenCalled(); - }); - }); - - describe('Theme detection', () => { - beforeEach(() => { - jest.useFakeTimers(); - // Mock the initial state for theme detection tests - model.get.mockImplementation((property) => { - if (property === 'table_html') { - return ''; - } - if (property === 'row_count') { - return 100; - } - if (property === 'error_message') { - return null; - } - if (property === 'page_size') { - return 10; - } - if (property === 'page') { - return 0; - } - return null; - }); - }); - - afterEach(() => { - jest.useRealTimers(); - document.body.classList.remove('vscode-dark'); - }); - - it('should add bigframes-dark-mode class in dark mode', () => { - document.body.classList.add('vscode-dark'); - render({ model, el }); - jest.runAllTimers(); - expect(el.classList.contains('bigframes-dark-mode')).toBe(true); - }); - - it('should not add bigframes-dark-mode class in light mode', () => { - render({ model, el }); - jest.runAllTimers(); - expect(el.classList.contains('bigframes-dark-mode')).toBe(false); - }); - }); - - it('should render the series as a table with an index and one value column', () => { - // Mock the initial state - model.get.mockImplementation((property) => { - if (property === 'table_html') { - return ` -
-
- - - - - - - - - - - - - - - - - -
value
0a
1b
-
-
`; - } - if (property === 'orderable_columns') { - return []; - } - return null; - }); - - render({ model, el }); - - // Manually trigger the table_html change handler - const tableHtmlChangeHandler = model.on.mock.calls.find( - (call) => call[0] === 'change:table_html', - )[1]; - tableHtmlChangeHandler(); - - // Check that the table has two columns - const headers = el.querySelectorAll( - '.paginated-table-container .col-header-name', - ); - expect(headers).toHaveLength(2); - - // Check that the headers are an empty string (for the index) and "value" - expect(headers[0].textContent).toBe(''); - expect(headers[1].textContent).toBe('value'); - }); - - /* - * Tests that the widget correctly renders HTML with truncated columns (ellipsis) - * and ensures that the ellipsis column is not treated as a sortable column. - */ - it('should set height dynamically on first load and remain fixed', () => { - jest.useFakeTimers(); - - // Mock the table's offsetHeight - let mockHeight = 150; - Object.defineProperty(HTMLElement.prototype, 'offsetHeight', { - configurable: true, - get: () => mockHeight, - }); - - // Mock model properties - model.get.mockImplementation((property) => { - if (property === 'table_html') { - return '...
'; - } - return null; - }); - - render({ model, el }); - - const tableContainer = el.querySelector('.table-container'); - - // --- First render --- - const tableHtmlChangeHandler = model.on.mock.calls.find( - (call) => call[0] === 'change:table_html', - )[1]; - tableHtmlChangeHandler(); - jest.runAllTimers(); - - // Height should be set to the mocked offsetHeight + 2px buffer - expect(tableContainer.style.height).toBe('152px'); - - // --- Second render (e.g., page size change) --- - // Simulate the new content being taller - mockHeight = 350; - tableHtmlChangeHandler(); - jest.runAllTimers(); - - // Height should NOT change - expect(tableContainer.style.height).toBe('152px'); - - // Restore original implementation - Object.defineProperty(HTMLElement.prototype, 'offsetHeight', { - value: 0, - }); - jest.useRealTimers(); - }); - - it('should render truncated columns with ellipsis and not make ellipsis sortable', () => { - // Mock HTML with truncated columns - // Use the structure produced by the python backend - const mockHtml = ` - - - - - - - - - - - - - - - -
col1
...
col10
1...10
- `; - - model.get.mockImplementation((property) => { - if (property === 'table_html') { - return mockHtml; - } - if (property === 'orderable_columns') { - // Only actual columns are orderable - return ['col1', 'col10']; - } - if (property === 'sort_context') { - return []; - } - return null; - }); - - render({ model, el }); - - // Manually trigger the table_html change handler - const tableHtmlChangeHandler = model.on.mock.calls.find( - (call) => call[0] === 'change:table_html', - )[1]; - tableHtmlChangeHandler(); - - const headers = el.querySelectorAll('th'); - expect(headers).toHaveLength(3); - - // Check col1 (sortable) - const col1Header = headers[0]; - const col1Indicator = col1Header.querySelector('.sort-indicator'); - expect(col1Indicator).not.toBeNull(); // Should exist (hidden by default) - - // Check ellipsis (not sortable) - const ellipsisHeader = headers[1]; - const ellipsisIndicator = ellipsisHeader.querySelector('.sort-indicator'); - // The render function adds sort indicators only if the column name matches an entry in orderable_columns. - // The ellipsis header content is "..." which is not in ['col1', 'col10']. - expect(ellipsisIndicator).toBeNull(); - - // Check col10 (sortable) - const col10Header = headers[2]; - const col10Indicator = col10Header.querySelector('.sort-indicator'); - expect(col10Indicator).not.toBeNull(); - }); - - describe('Max columns', () => { - /* - * Tests for the max columns dropdown functionality. - */ - - it('should render the max columns dropdown', () => { - // Mock basic state - model.get.mockImplementation((property) => { - if (property === 'max_columns') { - return 20; - } - return null; - }); - - render({ model, el }); - - const maxColumnsContainer = el.querySelector('.max-columns'); - expect(maxColumnsContainer).not.toBeNull(); - const label = maxColumnsContainer.querySelector('label'); - expect(label.textContent).toBe('Max columns:'); - const select = maxColumnsContainer.querySelector('select'); - expect(select).not.toBeNull(); - }); - - it('should select the correct initial value', () => { - const initialMaxColumns = 20; - model.get.mockImplementation((property) => { - if (property === 'max_columns') { - return initialMaxColumns; - } - return null; - }); - - render({ model, el }); - - const select = el.querySelector('.max-columns select'); - expect(Number(select.value)).toBe(initialMaxColumns); - }); - - it('should handle None/null initial value as 0 (All)', () => { - model.get.mockImplementation((property) => { - if (property === 'max_columns') { - return null; // Python None is null in JS - } - return null; - }); - - render({ model, el }); - - const select = el.querySelector('.max-columns select'); - expect(Number(select.value)).toBe(0); - expect(select.options[select.selectedIndex].textContent).toBe('All'); - }); - - it('should update model when value changes', () => { - model.get.mockImplementation((property) => { - if (property === 'max_columns') { - return 20; - } - return null; - }); - - render({ model, el }); - - const select = el.querySelector('.max-columns select'); - - // Change to 10 - select.value = '10'; - const event = new Event('change'); - select.dispatchEvent(event); - - expect(model.set).toHaveBeenCalledWith('max_columns', 10); - expect(model.save_changes).toHaveBeenCalled(); - }); - }); -}); diff --git a/tests/js/table_widget_angular.test.js b/tests/js/table_widget_angular.test.js deleted file mode 100644 index 1e7d0275c5d..00000000000 --- a/tests/js/table_widget_angular.test.js +++ /dev/null @@ -1,178 +0,0 @@ -/* - * Copyright 2026 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 { jest } from '@jest/globals'; - -describe('TableWidgetAngular', () => { - let render; - - beforeEach(async () => { - jest.resetModules(); - const tableWidgetAngular = ( - await import('../../bigframes/display/table_widget_angular.js') - ).default; - render = tableWidgetAngular.render; - }); - - it('should have a render function', () => { - expect(render).toBeDefined(); - }); - - it( - 'should bootstrap multiple widgets independently ' + - 'on their respective elements', - async () => { - const el1 = document.createElement('div'); - document.body.appendChild(el1); - - const model1 = { - get: jest.fn((prop) => { - if (prop === 'table_html') { - return '
Widget 1 Content
'; - } - if (prop === 'page_size') return 10; - if (prop === 'page') return 0; - if (prop === 'row_count') return 100; - if (prop === 'max_columns') return 20; - return null; - }), - set: jest.fn(), - save_changes: jest.fn(), - on: jest.fn(), - }; - - const el2 = document.createElement('div'); - document.body.appendChild(el2); - - const model2 = { - get: jest.fn((prop) => { - if (prop === 'table_html') { - return '
Widget 2 Content
'; - } - if (prop === 'page_size') return 25; - if (prop === 'page') return 0; - if (prop === 'row_count') return 200; - if (prop === 'max_columns') return 20; - return null; - }), - set: jest.fn(), - save_changes: jest.fn(), - on: jest.fn(), - }; - - render({ model: model1, el: el1 }); - render({ model: model2, el: el2 }); - - // Wait for async angular bootstrap to complete - await new Promise((resolve) => setTimeout(resolve, 200)); - - const appRoot1 = el1.querySelector('.bigframes-widget'); - expect(appRoot1).not.toBeNull(); - expect(el1.textContent).toContain('Widget 1 Content'); - expect(el1.textContent).toContain('100 total rows'); - expect(el1.textContent).toContain('Page 1 of 10'); - - const appRoot2 = el2.querySelector('.bigframes-widget'); - expect(appRoot2).not.toBeNull(); - expect(el2.textContent).toContain('Widget 2 Content'); - expect(el2.textContent).toContain('200 total rows'); - expect(el2.textContent).toContain('Page 1 of 8'); - - document.body.removeChild(el1); - document.body.removeChild(el2); - }); - - it( - 'should render deferred card and trigger execution on click', - async () => { - // Arrange - const el = document.createElement('div'); - document.body.appendChild(el); - - const state = { - is_deferred_mode: true, - dry_run_info: 'Estimated cost: $0.05', - start_execution: false, - table_html: '', - page_size: 10, - page: 0, - row_count: 0, - max_columns: 20, - }; - - const listeners = {}; - const model = { - get: jest.fn((prop) => state[prop]), - set: jest.fn((prop, val) => { - state[prop] = val; - }), - save_changes: jest.fn(), - on: jest.fn((event, callback) => { - listeners[event] = callback; - }), - }; - - // Act - render({ model, el }); - await new Promise((resolve) => setTimeout(resolve, 200)); - - // Assert (Initial state) - const estimate = el.querySelector('.deferred-estimate'); - expect(estimate).not.toBeNull(); - expect(estimate.textContent).toContain('Estimated cost: $0.05'); - - const runButton = el.querySelector('.run-query-button'); - expect(runButton).not.toBeNull(); - expect(runButton.textContent).toContain('Run Query'); - expect(el.querySelector('.table-container')).toBeNull(); - - // Act (Click Run Query) - runButton.click(); - await new Promise((resolve) => setTimeout(resolve, 50)); - - // Assert (Execution requested) - expect(model.set).toHaveBeenCalledWith('start_execution', true); - expect(model.save_changes).toHaveBeenCalled(); - expect(runButton.disabled).toBe(true); - expect(el.querySelector('.spinner')).not.toBeNull(); - - // Act (Simulate Python load completion) - state.is_deferred_mode = false; - state.table_html = '
Data Loaded
'; - state.row_count = 50; - - if (listeners['change:is_deferred_mode']) { - listeners['change:is_deferred_mode'](); - } - if (listeners['change:table_html']) { - listeners['change:table_html'](); - } - if (listeners['change:row_count']) { - listeners['change:row_count'](); - } - await new Promise((resolve) => setTimeout(resolve, 200)); - - // Assert (Transition to loaded state) - expect(el.querySelector('.deferred-container')).toBeNull(); - const tableContainer = el.querySelector('.table-container'); - expect(tableContainer).not.toBeNull(); - expect(el.textContent).toContain('Data Loaded'); - expect(el.textContent).toContain('50 total rows'); - - // Clean up - document.body.removeChild(el); - }); -}); diff --git a/tests/system/conftest.py b/tests/system/conftest.py index 0b30c331a1e..f9f69c6c8ee 100644 --- a/tests/system/conftest.py +++ b/tests/system/conftest.py @@ -12,58 +12,29 @@ # See the License for the specific language governing permissions and # limitations under the License. -import base64 -import datetime -import decimal +from datetime import datetime import hashlib -import json import logging import math import pathlib import textwrap -import traceback import typing -from typing import Dict, Generator, Optional +from typing import Dict, Optional -import db_dtypes # type: ignore[import-untyped] -import fsspec # type: ignore[import-untyped] -import gcsfs # type: ignore[import-untyped] -import geopandas as gpd # type: ignore[import-untyped] -import google.api_core.exceptions import google.cloud.bigquery as bigquery import google.cloud.bigquery_connection_v1 as bigquery_connection_v1 -import google.cloud.bigquery_storage_v1 import google.cloud.exceptions import google.cloud.functions_v2 as functions_v2 import google.cloud.resourcemanager_v3 as resourcemanager_v3 import google.cloud.storage as storage # type: ignore -import numpy as np +import ibis.backends.base import pandas as pd -import pandas.arrays -import pyarrow as pa import pytest import pytz import test_utils.prefixer import bigframes -import bigframes.dataframe -import bigframes.pandas as bpd -import bigframes.series -import bigframes.testing.utils - -# Use this to control the number of cloud functions being deleted in a single -# test session. This should help soften the spike of the number of mutations per -# minute tracked against the quota limit: -# Cloud Functions API -> Per project mutation requests per minute per region -# (default 60, increased to 1000 for the test projects) -# We are running pytest with "-n 20". For a rough estimation, let's say all -# parallel sessions run in parallel. So that allows 1000/20 = 50 mutations per -# minute. One session takes about 1 minute to create a remote function. This -# would allow 50-1 = 49 deletions per session. -# However, because of b/356217175 the service may throw ResourceExhausted("Too -# many operations are currently being executed, try again later."), so we peg -# the cleanup to a more controlled rate. -MAX_NUM_FUNCTIONS_TO_DELETE_PER_SESSION = 15 +from tests.system.utils import convert_pandas_dtypes CURRENT_DIR = pathlib.Path(__file__).parent DATA_DIR = CURRENT_DIR.parent / "data" @@ -79,15 +50,6 @@ def _hash_digest_file(hasher, filepath): hasher.update(chunk) -@pytest.fixture(scope="session", autouse=True) -def configure_gcsfs(): - # gcsfs by default uses a cache that can be stale, causing file loads to - # fail if the file was uploaded indirectly (eg via bq export job) during the - # course of the tests. disable the cache to avoid this. - fsspec.config.conf["gcs"] = {"use_listings_cache": False} - gcsfs.GCSFileSystem.clear_instance_cache() - - @pytest.fixture(scope="session") def tokyo_location() -> str: return TOKYO_LOCATION @@ -108,12 +70,9 @@ def gcs_folder(gcs_client: storage.Client): prefix = prefixer.create_prefix() path = f"gs://{bucket}/{prefix}/" yield path - try: - for blob in gcs_client.list_blobs(bucket, prefix=prefix): - blob = typing.cast(storage.Blob, blob) - blob.delete() - except Exception as exc: - traceback.print_exception(type(exc), exc, None) + for blob in gcs_client.list_blobs(bucket, prefix=prefix): + blob = typing.cast(storage.Blob, blob) + blob.delete() @pytest.fixture(scope="session") @@ -122,15 +81,13 @@ def bigquery_client(session: bigframes.Session) -> bigquery.Client: @pytest.fixture(scope="session") -def bigquery_storage_read_client( - session: bigframes.Session, -) -> google.cloud.bigquery_storage_v1.BigQueryReadClient: - return session.bqstoragereadclient +def bigquery_client_tokyo(session_tokyo: bigframes.Session) -> bigquery.Client: + return session_tokyo.bqclient @pytest.fixture(scope="session") -def bigquery_client_tokyo(session_tokyo: bigframes.Session) -> bigquery.Client: - return session_tokyo.bqclient +def ibis_client(session: bigframes.Session) -> ibis.backends.base.BaseBackend: + return session.ibis_client @pytest.fixture(scope="session") @@ -147,11 +104,6 @@ def cloudfunctions_client( return session.cloudfunctionsclient -@pytest.fixture(scope="session") -def project_id(bigquery_client: bigquery.Client) -> str: - return bigquery_client.project - - @pytest.fixture(scope="session") def resourcemanager_client( session: bigframes.Session, @@ -160,68 +112,17 @@ def resourcemanager_client( @pytest.fixture(scope="session") -def session() -> Generator[bigframes.Session, None, None]: - context = bigframes.BigQueryOptions(location="US") - session = bigframes.Session(context=context) - yield session - session.close() # close generated session at cleanup time - - -@pytest.fixture(scope="session") -def session_load() -> Generator[bigframes.Session, None, None]: - context = bigframes.BigQueryOptions(location="US", project="bigframes-load-testing") - session = bigframes.Session(context=context) - yield session - session.close() # close generated session at cleanup time - - -@pytest.fixture(scope="session", params=["strict", "partial"]) -def maybe_ordered_session(request) -> Generator[bigframes.Session, None, None]: - context = bigframes.BigQueryOptions(location="US", ordering_mode=request.param) - session = bigframes.Session(context=context) - yield session - session.close() # close generated session at cleanup type - - -@pytest.fixture(scope="session") -def unordered_session() -> Generator[bigframes.Session, None, None]: - context = bigframes.BigQueryOptions(location="US", ordering_mode="partial") - session = bigframes.Session(context=context) - yield session - session.close() # close generated session at cleanup type +def session() -> bigframes.Session: + return bigframes.Session() @pytest.fixture(scope="session") -def session_tokyo(tokyo_location: str) -> Generator[bigframes.Session, None, None]: - context = bigframes.BigQueryOptions(location=tokyo_location) - session = bigframes.Session(context=context) - yield session - session.close() # close generated session at cleanup type - - -@pytest.fixture(scope="session") -def test_session() -> Generator[bigframes.Session, None, None]: +def session_tokyo(tokyo_location: str) -> bigframes.Session: context = bigframes.BigQueryOptions( - client_endpoints_override={ - "bqclient": "https://test-bigquery.sandbox.google.com", - "bqconnectionclient": "test-bigqueryconnection.sandbox.googleapis.com", - "bqstoragereadclient": "test-bigquerystorage-grpc.sandbox.googleapis.com", - }, + location=tokyo_location, + use_regional_endpoints=True, ) - session = bigframes.Session(context=context) - yield session - session.close() - - -@pytest.fixture(scope="session") -def bq_connection_name() -> str: - return "bigframes-rf-conn" - - -@pytest.fixture(scope="session") -def bq_connection(bigquery_client: bigquery.Client, bq_connection_name: str) -> str: - # TODO(b/458169181): LOCATION casefold is needed for the mutimodal backend bug. Remove after the bug is fixed. - return f"{bigquery_client.project}.{bigquery_client.location.casefold()}.{bq_connection_name}" + return bigframes.Session(context=context) @pytest.fixture(scope="session", autouse=True) @@ -259,8 +160,9 @@ def dataset_id_not_created(bigquery_client: bigquery.Client): @pytest.fixture(scope="session") -def dataset_id_permanent(bigquery_client: bigquery.Client, project_id: str) -> str: +def dataset_id_permanent(bigquery_client: bigquery.Client) -> str: """Create a dataset if it doesn't exist.""" + project_id = bigquery_client.project dataset_id = f"{project_id}.{PERMANENT_DATASET}" dataset = bigquery.Dataset(dataset_id) bigquery_client.create_dataset(dataset, exists_ok=True) @@ -281,21 +183,6 @@ def dataset_id_permanent_tokyo( return dataset_id -@pytest.fixture(scope="session") -def table_id_not_created(dataset_id: str): - return f"{dataset_id}.{prefixer.create_prefix()}" - - -@pytest.fixture(scope="function") -def table_id_unique(dataset_id: str): - return f"{dataset_id}.{prefixer.create_prefix()}" - - -@pytest.fixture(scope="function") -def routine_id_unique(dataset_id: str): - return f"{dataset_id}.{prefixer.create_prefix()}" - - @pytest.fixture(scope="session") def scalars_schema(bigquery_client: bigquery.Client): # TODO(swast): Add missing scalar data types such as BIGNUMERIC. @@ -345,17 +232,11 @@ def load_test_data_tables( for table_name, schema_filename, data_filename in [ ("scalars", "scalars_schema.json", "scalars.jsonl"), ("scalars_too", "scalars_schema.json", "scalars.jsonl"), - ("nested", "nested_schema.json", "nested.jsonl"), - ("nested_structs", "nested_structs_schema.json", "nested_structs.jsonl"), - ("repeated", "repeated_schema.json", "repeated.jsonl"), - ("json", "json_schema.json", "json.jsonl"), ("penguins", "penguins_schema.json", "penguins.jsonl"), - ("ratings", "ratings_schema.json", "ratings.jsonl"), ("time_series", "time_series_schema.json", "time_series.jsonl"), ("hockey_players", "hockey_players.json", "hockey_players.jsonl"), ("matrix_2by3", "matrix_2by3.json", "matrix_2by3.jsonl"), ("matrix_3by4", "matrix_3by4.json", "matrix_3by4.jsonl"), - ("urban_areas", "urban_areas_schema.json", "urban_areas.jsonl"), ]: test_data_hash = hashlib.md5() _hash_digest_file(test_data_hash, DATA_DIR / schema_filename) @@ -400,13 +281,6 @@ def scalars_table_id(test_data_tables) -> str: return test_data_tables["scalars"] -@pytest.fixture(scope="session") -def baseball_schedules_df(session: bigframes.Session) -> bigframes.dataframe.DataFrame: - """Public BQ table""" - df = session.read_gbq("bigquery-public-data.baseball.schedules") - return df - - @pytest.fixture(scope="session") def hockey_table_id(test_data_tables) -> str: return test_data_tables["hockey_players"] @@ -422,41 +296,11 @@ def scalars_table_tokyo(test_data_tables_tokyo) -> str: return test_data_tables_tokyo["scalars"] -@pytest.fixture(scope="session") -def nested_table_id(test_data_tables) -> str: - return test_data_tables["nested"] - - -@pytest.fixture(scope="session") -def nested_structs_table_id(test_data_tables) -> str: - return test_data_tables["nested_structs"] - - -@pytest.fixture(scope="session") -def repeated_table_id(test_data_tables) -> str: - return test_data_tables["repeated"] - - -@pytest.fixture(scope="session") -def json_table_id(test_data_tables) -> str: - return test_data_tables["json"] - - @pytest.fixture(scope="session") def penguins_table_id(test_data_tables) -> str: return test_data_tables["penguins"] -@pytest.fixture(scope="session") -def ratings_table_id(test_data_tables) -> str: - return test_data_tables["ratings"] - - -@pytest.fixture(scope="session") -def urban_areas_table_id(test_data_tables) -> str: - return test_data_tables["urban_areas"] - - @pytest.fixture(scope="session") def time_series_table_id(test_data_tables) -> str: return test_data_tables["time_series"] @@ -472,302 +316,6 @@ def matrix_3by4_table_id(test_data_tables) -> str: return test_data_tables["matrix_3by4"] -@pytest.fixture(scope="session") -def nested_df( - nested_table_id: str, session: bigframes.Session -) -> bigframes.dataframe.DataFrame: - """DataFrame pointing at test data.""" - return session.read_gbq(nested_table_id, index_col="rowindex") - - -@pytest.fixture(scope="session") -def nested_pandas_df() -> pd.DataFrame: - """pd.DataFrame pointing at test data.""" - - df = pd.read_json( - DATA_DIR / "nested.jsonl", - lines=True, - ) - df = df.set_index("rowindex") - return df - - -@pytest.fixture(scope="session") -def nested_structs_df( - nested_structs_table_id: str, session: bigframes.Session -) -> bigframes.dataframe.DataFrame: - """DataFrame pointing at test data.""" - return session.read_gbq(nested_structs_table_id, index_col="id") - - -@pytest.fixture(scope="session") -def nested_structs_pandas_df(nested_structs_pandas_type: pd.ArrowDtype) -> pd.DataFrame: - """pd.DataFrame pointing at test data. - - Manually parses using json.loads to preserve data types. - """ - with open(DATA_DIR / "nested_structs.jsonl") as f: - raw_rows = [json.loads(line) for line in f] - - ids = [row["id"] for row in raw_rows] - - def get_val(row, col_name): - return row.get(col_name) - - # person - person_struct_schema = nested_structs_pandas_type.pyarrow_dtype - processed_person: list[Optional[dict[str, typing.Any]]] = [] - for row in raw_rows: - x = get_val(row, "person") - if x is None: - processed_person.append(None) - else: - d = dict(x) - if "age" in d and d["age"] is not None: - d["age"] = int(d["age"]) - processed_person.append(d) - person_arr = pa.array(processed_person, type=person_struct_schema) - person_ser = pd.Series(person_arr, index=ids, dtype=nested_structs_pandas_type) - - # bool_col - bool_vals = [ - bool(get_val(row, "bool_col")) if get_val(row, "bool_col") is not None else None - for row in raw_rows - ] - bool_ser = pd.Series(bool_vals, index=ids, dtype=pd.BooleanDtype()) - - # int64_col - int64_vals = [ - int(get_val(row, "int64_col")) - if get_val(row, "int64_col") is not None - else None - for row in raw_rows - ] - int64_ser = pd.Series(int64_vals, index=ids, dtype=pd.Int64Dtype()) - - # float64_col - float64_vals = [ - float(get_val(row, "float64_col")) - if get_val(row, "float64_col") is not None - else None - for row in raw_rows - ] - np_vals = np.array( - [x if x is not None else np.nan for x in float64_vals], dtype=np.float64 - ) - mask = np.array([x is None for x in float64_vals], dtype=bool) - float64_arr = pd.arrays.FloatingArray(np_vals, mask) # type: ignore - float64_ser = pd.Series(float64_arr, index=ids) - - # string_col - string_vals = [ - str(get_val(row, "string_col")) - if get_val(row, "string_col") is not None - else None - for row in raw_rows - ] - string_ser = pd.Series( - string_vals, index=ids, dtype=pd.StringDtype(storage="pyarrow") - ) - - # json_col - json_strs: list[Optional[str]] = [] - for row in raw_rows: - if "json_col" not in row: - json_strs.append(None) - elif row["json_col"] is None: - json_strs.append("null") - else: - json_strs.append( - json.dumps(row["json_col"], sort_keys=True, separators=(",", ":")) - ) - json_arr = pa.array(json_strs, type=db_dtypes.JSONArrowType()) - json_ser = pd.Series( - json_arr, index=ids, dtype=pd.ArrowDtype(db_dtypes.JSONArrowType()) - ) - - # date_col - date_vals = [ - datetime.date.fromisoformat(get_val(row, "date_col")) - if get_val(row, "date_col") is not None - else None - for row in raw_rows - ] - date_arr = pa.array(date_vals, type=pa.date32()) - date_ser = pd.Series(date_arr, index=ids, dtype=pd.ArrowDtype(pa.date32())) - - # time_col - time_vals = [ - datetime.time.fromisoformat(get_val(row, "time_col")) - if get_val(row, "time_col") is not None - else None - for row in raw_rows - ] - time_arr = pa.array(time_vals, type=pa.time64("us")) - time_ser = pd.Series(time_arr, index=ids, dtype=pd.ArrowDtype(pa.time64("us"))) - - # datetime_col - datetime_vals: list[Optional[datetime.datetime]] = [] - for row in raw_rows: - val = get_val(row, "datetime_col") - if val is None: - datetime_vals.append(None) - else: - datetime_vals.append(datetime.datetime.fromisoformat(val.replace(" ", "T"))) - datetime_arr = pa.array(datetime_vals, type=pa.timestamp("us")) - datetime_ser = pd.Series( - datetime_arr, index=ids, dtype=pd.ArrowDtype(pa.timestamp("us")) - ) - - # timestamp_col - timestamp_vals = [ - datetime.datetime.fromisoformat( - get_val(row, "timestamp_col").replace("Z", "+00:00") - ) - if get_val(row, "timestamp_col") is not None - else None - for row in raw_rows - ] - timestamp_arr = pa.array(timestamp_vals, type=pa.timestamp("us", tz="UTC")) - timestamp_ser = pd.Series( - timestamp_arr, index=ids, dtype=pd.ArrowDtype(pa.timestamp("us", tz="UTC")) - ) - - # bytes_col - bytes_vals: list[Optional[bytes]] = [] - for row in raw_rows: - val = get_val(row, "bytes_col") - if val is None: - bytes_vals.append(None) - elif val == "": - bytes_vals.append(b"") - else: - bytes_vals.append(base64.b64decode(val)) - bytes_arr = pa.array(bytes_vals, type=pa.binary()) - bytes_ser = pd.Series(bytes_arr, index=ids, dtype=pd.ArrowDtype(pa.binary())) - - # numeric_col - numeric_vals = [ - decimal.Decimal(str(get_val(row, "numeric_col"))) - if get_val(row, "numeric_col") is not None - else None - for row in raw_rows - ] - numeric_arr = pa.array(numeric_vals, type=pa.decimal128(38, 9)) - numeric_ser = pd.Series( - numeric_arr, index=ids, dtype=pd.ArrowDtype(pa.decimal128(38, 9)) - ) - - # bignumeric_col - bignumeric_vals = [ - decimal.Decimal(str(get_val(row, "bignumeric_col"))) - if get_val(row, "bignumeric_col") is not None - else None - for row in raw_rows - ] - bignumeric_arr = pa.array(bignumeric_vals, type=pa.decimal256(76, 38)) - bignumeric_ser = pd.Series( - bignumeric_arr, index=ids, dtype=pd.ArrowDtype(pa.decimal256(76, 38)) - ) - - # geography_col - geo_vals = [get_val(row, "geography_col") for row in raw_rows] - geo_ser = gpd.GeoSeries.from_wkt(geo_vals) - geo_ser.index = ids - - # duration_col - duration_vals = [ - int(get_val(row, "duration_col")) - if get_val(row, "duration_col") is not None - else None - for row in raw_rows - ] - duration_arr = pa.array(duration_vals, type=pa.duration("us")) - duration_ser = pd.Series( - duration_arr, index=ids, dtype=pd.ArrowDtype(pa.duration("us")) - ) - - df = pd.DataFrame( - { - "person": person_ser, - "bool_col": bool_ser, - "int64_col": int64_ser, - "float64_col": float64_ser, - "string_col": string_ser, - "json_col": json_ser, - "date_col": date_ser, - "time_col": time_ser, - "datetime_col": datetime_ser, - "timestamp_col": timestamp_ser, - "bytes_col": bytes_ser, - "numeric_col": numeric_ser, - "bignumeric_col": bignumeric_ser, - "geography_col": geo_ser, - "duration_col": duration_ser, - }, - index=ids, - ) - df.index.name = "id" - - return df - - -@pytest.fixture(scope="session") -def nested_structs_pandas_type() -> pd.ArrowDtype: - address_struct_schema = pa.struct( - [pa.field("city", pa.string()), pa.field("country", pa.string())] - ) - - person_struct_schema = pa.struct( - [ - pa.field("name", pa.string()), - pa.field("age", pa.int64()), - pa.field("address", address_struct_schema), - ] - ) - - return pd.ArrowDtype(person_struct_schema) - - -@pytest.fixture(scope="session") -def repeated_df( - repeated_table_id: str, session: bigframes.Session -) -> bigframes.dataframe.DataFrame: - """Returns a DataFrame containing columns of list type.""" - return session.read_gbq(repeated_table_id, index_col="rowindex") - - -@pytest.fixture(scope="session") -def repeated_pandas_df() -> pd.DataFrame: - """Returns a DataFrame containing columns of list type.""" - - df = pd.read_json( - DATA_DIR / "repeated.jsonl", - lines=True, - ) - df = df.set_index("rowindex") - return df - - -@pytest.fixture(scope="session") -def json_df( - json_table_id: str, session: bigframes.Session -) -> bigframes.dataframe.DataFrame: - """Returns a DataFrame containing columns of JSON type.""" - return session.read_gbq(json_table_id, index_col="rowindex") - - -@pytest.fixture(scope="session") -def json_pandas_df() -> pd.DataFrame: - """Returns a DataFrame containing columns of JSON type.""" - df = pd.read_json( - DATA_DIR / "json.jsonl", - lines=True, - ) - df = df.set_index("rowindex") - return df - - @pytest.fixture(scope="session") def scalars_df_default_index( scalars_df_index: bigframes.dataframe.DataFrame, @@ -790,38 +338,6 @@ def scalars_df_index( return session.read_gbq(scalars_table_id, index_col="rowindex") -@pytest.fixture(scope="session") -def scalars_df_partial_ordering( - scalars_table_id: str, unordered_session: bigframes.Session -) -> bigframes.dataframe.DataFrame: - """DataFrame pointing at test data.""" - return unordered_session.read_gbq( - scalars_table_id, index_col="rowindex" - ).sort_index() - - -@pytest.fixture(scope="session") -def scalars_df_null_index( - scalars_table_id: str, session: bigframes.Session -) -> bigframes.dataframe.DataFrame: - """DataFrame pointing at test data.""" - return session.read_gbq( - scalars_table_id, index_col=bigframes.enums.DefaultIndexKind.NULL - ).sort_values("rowindex") - - -@pytest.fixture(scope="session") -def scalars_df_unordered( - scalars_table_id: str, unordered_session: bigframes.Session -) -> bigframes.dataframe.DataFrame: - """DataFrame pointing at test data.""" - df = unordered_session.read_gbq( - scalars_table_id, index_col=bigframes.enums.DefaultIndexKind.NULL - ) - assert not df._block.explicitly_ordered - return df - - @pytest.fixture(scope="session") def scalars_df_2_default_index( scalars_df_2_index: bigframes.dataframe.DataFrame, @@ -846,7 +362,7 @@ def scalars_pandas_df_default_index() -> pd.DataFrame: DATA_DIR / "scalars.jsonl", lines=True, ) - bigframes.testing.utils.convert_pandas_dtypes(df, bytes_col=True) + convert_pandas_dtypes(df, bytes_col=True) df = df.set_index("rowindex", drop=False) df.index.name = None @@ -879,39 +395,6 @@ def scalars_dfs( return scalars_df_index, scalars_pandas_df_index -@pytest.fixture(scope="session") -def scalars_dfs_maybe_ordered( - maybe_ordered_session, - scalars_pandas_df_index, -): - return ( - maybe_ordered_session.read_pandas(scalars_pandas_df_index), - scalars_pandas_df_index, - ) - - -@pytest.fixture(scope="session") -def scalars_df_numeric_150_columns_maybe_ordered( - maybe_ordered_session, - scalars_pandas_df_index, -): - """DataFrame pointing at test data.""" - # TODO(b/379911038): After the error fixed, add numeric type. - pandas_df = scalars_pandas_df_index.reset_index(drop=False)[ - [ - "rowindex", - "rowindex_2", - "float64_col", - "int64_col", - "int64_too", - ] - * 30 - ] - - df = maybe_ordered_session.read_pandas(pandas_df) - return (df, pandas_df) - - @pytest.fixture(scope="session") def hockey_df( hockey_table_id: str, session: bigframes.Session @@ -1008,22 +491,6 @@ def penguins_df_default_index( return session.read_gbq(penguins_table_id) -@pytest.fixture(scope="session") -def penguins_df_null_index( - penguins_table_id: str, unordered_session: bigframes.Session -) -> bigframes.dataframe.DataFrame: - """DataFrame pointing at test data.""" - return unordered_session.read_gbq(penguins_table_id) - - -@pytest.fixture(scope="session") -def ratings_df_default_index( - ratings_table_id: str, session: bigframes.Session -) -> bigframes.dataframe.DataFrame: - """DataFrame pointing at test data.""" - return session.read_gbq(ratings_table_id) - - @pytest.fixture(scope="session") def time_series_df_default_index( time_series_table_id: str, session: bigframes.Session @@ -1039,9 +506,9 @@ def new_time_series_pandas_df(): return pd.DataFrame( { "parsed_date": [ - datetime.datetime(2017, 8, 2, tzinfo=utc), - datetime.datetime(2017, 8, 3, tzinfo=utc), - datetime.datetime(2017, 8, 4, tzinfo=utc), + datetime(2017, 8, 2, tzinfo=utc), + datetime(2017, 8, 3, tzinfo=utc), + datetime(2017, 8, 4, tzinfo=utc), ], "total_visits": [2500, 2500, 2500], } @@ -1053,31 +520,6 @@ def new_time_series_df(session, new_time_series_pandas_df): return session.read_pandas(new_time_series_pandas_df) -@pytest.fixture(scope="session") -def new_time_series_pandas_df_w_id(): - """Additional data matching the time series dataset. The values are dummy ones used to basically check the prediction scores.""" - utc = pytz.utc - return pd.DataFrame( - { - "parsed_date": [ - datetime.datetime(2017, 8, 2, tzinfo=utc), - datetime.datetime(2017, 8, 2, tzinfo=utc), - datetime.datetime(2017, 8, 3, tzinfo=utc), - datetime.datetime(2017, 8, 3, tzinfo=utc), - datetime.datetime(2017, 8, 4, tzinfo=utc), - datetime.datetime(2017, 8, 4, tzinfo=utc), - ], - "id": ["1", "2", "1", "2", "1", "2"], - "total_visits": [2500, 2500, 2500, 2500, 2500, 2500], - } - ) - - -@pytest.fixture(scope="session") -def new_time_series_df_w_id(session, new_time_series_pandas_df_w_id): - return session.read_pandas(new_time_series_pandas_df_w_id) - - @pytest.fixture(scope="session") def penguins_pandas_df_default_index() -> pd.DataFrame: """Consistently ordered pandas dataframe for penguins test data""" @@ -1119,42 +561,11 @@ def new_penguins_pandas_df(): ).set_index("tag_number") -@pytest.fixture(scope="session") -def missing_values_penguins_df(): - """Additional data matching the missing values penguins dataset""" - return bpd.DataFrame( - { - "culmen_length_mm": [39.5, 38.5, 37.9], - "culmen_depth_mm": [np.nan, 17.2, 18.1], - "flipper_length_mm": [np.nan, 181.0, 188.0], - } - ) - - @pytest.fixture(scope="session") def new_penguins_df(session, new_penguins_pandas_df): return session.read_pandas(new_penguins_pandas_df) -@pytest.fixture(scope="session") -def llm_text_pandas_df(): - """Additional data matching the penguins dataset, with a new index""" - return pd.DataFrame( - { - "prompt": [ - "What is BigQuery?", - "What is BQML?", - "What is BigQuery DataFrame?", - ], - } - ) - - -@pytest.fixture(scope="session") -def llm_text_df(session, llm_text_pandas_df): - return session.read_pandas(llm_text_pandas_df) - - @pytest.fixture(scope="session") def penguins_linear_model_name( session: bigframes.Session, dataset_id_permanent, penguins_table_id @@ -1321,65 +732,34 @@ def penguins_xgbregressor_model_name( return model_name -def _get_or_create_arima_plus_model( - session: bigframes.Session, dataset_id_permanent, sql -) -> str: - """Internal helper to compute a model name by hasing the given SQL. - attempst to retreive the model, create it if not exist. - retursn the fully qualitifed model""" - - # We use the SQL hash as the name to ensure the model is regenerated if this fixture is edited - model_name = f"{dataset_id_permanent}.time_series_arima_plus_{hashlib.md5(sql.encode()).hexdigest()}" - sql = sql.replace("$model_name", model_name) - try: - session.bqclient.get_model(model_name) - except google.cloud.exceptions.NotFound: - logging.info( - "time_series_arima_plus_model fixture was not found in the permanent dataset, regenerating it..." - ) - session.bqclient.query(sql).result() - finally: - return model_name - - @pytest.fixture(scope="session") def time_series_arima_plus_model_name( session: bigframes.Session, dataset_id_permanent, time_series_table_id ) -> str: """Provides a pretrained model as a test fixture that is cached across test runs. - This lets us run system tests without having to wait for a model.fit(...). - This version does not include time_series_id_col.""" + This lets us run system tests without having to wait for a model.fit(...)""" sql = f""" CREATE OR REPLACE MODEL `$model_name` OPTIONS ( model_type='ARIMA_PLUS', time_series_timestamp_col = 'parsed_date', time_series_data_col = 'total_visits' -) AS SELECT - parsed_date, - total_visits -FROM `{time_series_table_id}`""" - return _get_or_create_arima_plus_model(session, dataset_id_permanent, sql) - - -@pytest.fixture(scope="session") -def time_series_arima_plus_model_name_w_id( - session: bigframes.Session, dataset_id_permanent, time_series_table_id -) -> str: - """Provides a pretrained model as a test fixture that is cached across test runs. - This lets us run system tests without having to wait for a model.fit(...). - This version includes time_series_id_col.""" - sql = f""" -CREATE OR REPLACE MODEL `$model_name` -OPTIONS ( - model_type='ARIMA_PLUS', - time_series_timestamp_col = 'parsed_date', - time_series_data_col = 'total_visits', - time_series_id_col = 'id' ) AS SELECT * FROM `{time_series_table_id}`""" - return _get_or_create_arima_plus_model(session, dataset_id_permanent, sql) + # We use the SQL hash as the name to ensure the model is regenerated if this fixture is edited + model_name = f"{dataset_id_permanent}.time_series_arima_plus_{hashlib.md5(sql.encode()).hexdigest()}" + sql = sql.replace("$model_name", model_name) + + try: + session.bqclient.get_model(model_name) + except google.cloud.exceptions.NotFound: + logging.info( + "time_series_arima_plus_model fixture was not found in the permanent dataset, regenerating it..." + ) + session.bqclient.query(sql).result() + finally: + return model_name @pytest.fixture(scope="session") @@ -1488,18 +868,6 @@ def penguins_randomforest_classifier_model_name( return model_name -@pytest.fixture(scope="session") -def llm_fine_tune_df_default_index( - session: bigframes.Session, -) -> bigframes.dataframe.DataFrame: - training_table_name = "llm_tuning.emotion_classification_train" - df = session.read_gbq(training_table_name).dropna().head(30) - prefix = "Please do sentiment analysis on the following text and only output a number from 0 to 5 where 0 means sadness, 1 means joy, 2 means love, 3 means anger, 4 means fear, and 5 means surprise. Text: " - df["prompt"] = prefix + df["text"] - df["label"] = df["label"].astype("string") - return df - - @pytest.fixture(scope="session") def usa_names_grouped_table( session: bigframes.Session, dataset_id_permanent @@ -1530,14 +898,6 @@ def usa_names_grouped_table( return session.bqclient.get_table(table_id) -@pytest.fixture(scope="session", autouse=True) -def use_sqlglot_compiler(): - original_setting = bigframes.options.experiments.sql_compiler - bigframes.options.experiments.sql_compiler = "experimental" - yield - bigframes.options.experiments.sql_compiler = original_setting - - @pytest.fixture() def restore_sampling_settings(): enable_downsampling = bigframes.options.sampling.enable_downsampling @@ -1547,14 +907,6 @@ def restore_sampling_settings(): bigframes.options.sampling.max_download_size = max_download_size -@pytest.fixture() -def with_multiquery_execution(): - original_setting = bigframes.options.compute.enable_multi_query_execution - bigframes.options.compute.enable_multi_query_execution = True - yield - bigframes.options.compute.enable_multi_query_execution = original_setting - - @pytest.fixture() def weird_strings_pd(): df = pd.DataFrame( @@ -1627,7 +979,7 @@ def floats_pd(): dtype=pd.Float64Dtype(), ) # Index helps debug failed cases - df.index = df.float64_col # type: ignore + df.index = df.float64_col # Upload fails if index name same as column name df.index.name = None return df.float64_col @@ -1637,7 +989,7 @@ def floats_pd(): def floats_product_pd(floats_pd): df = pd.merge(floats_pd, floats_pd, how="cross") # Index helps debug failed cases - df = df.set_index([df.float64_col_x, df.float64_col_y]) # type: ignore + df = df.set_index([df.float64_col_x, df.float64_col_y]) df.index.names = ["left", "right"] return df @@ -1650,91 +1002,3 @@ def floats_bf(session, floats_pd): @pytest.fixture() def floats_product_bf(session, floats_product_pd): return session.read_pandas(floats_product_pd) - - -@pytest.fixture(scope="session", autouse=True) -def use_fast_query_path(): - with bpd.option_context("compute.allow_large_results", False): - yield - - -@pytest.fixture(scope="session", autouse=True) -def cleanup_cloud_functions(session, cloudfunctions_client, dataset_id_permanent): - """Clean up stale cloud functions.""" - permanent_endpoints = bigframes.testing.utils.get_remote_function_endpoints( - session.bqclient, dataset_id_permanent - ) - delete_count = 0 - try: - for cloud_function in bigframes.testing.utils.get_cloud_functions( - cloudfunctions_client, - session.bqclient.project, - session.bqclient.location, - name_prefix="bigframes-", - ): - # Ignore bigframes cloud functions referred by the remote functions in - # the permanent dataset - if cloud_function.service_config.uri in permanent_endpoints: - continue - - # Ignore the functions less than one day old - age = datetime.datetime.now() - datetime.datetime.fromtimestamp( - cloud_function.update_time.timestamp() - ) - if age.days <= 0: - continue - - # Go ahead and delete - try: - bigframes.testing.utils.delete_cloud_function( - cloudfunctions_client, cloud_function.name - ) - delete_count += 1 - if delete_count >= MAX_NUM_FUNCTIONS_TO_DELETE_PER_SESSION: - break - except google.api_core.exceptions.NotFound: - # This can happen when multiple pytest sessions are running in - # parallel. Two or more sessions may discover the same cloud - # function, but only one of them would be able to delete it - # successfully, while the other instance will run into this - # exception. Ignore this exception. - pass - except Exception as exc: - # Don't fail the tests for unknown exceptions. - # - # This can happen if we are hitting GCP limits, e.g. - # google.api_core.exceptions.ResourceExhausted: 429 Quota exceeded - # for quota metric 'Per project mutation requests' and limit - # 'Per project mutation requests per minute per region' of service - # 'cloudfunctions.googleapis.com' for consumer - # 'project_number:1084210331973'. - # [reason: "RATE_LIMIT_EXCEEDED" domain: "googleapis.com" ... - # - # It can also happen occasionally with - # google.api_core.exceptions.ServiceUnavailable when there is some - # backend flakiness. - # - # Let's stop further clean up and leave it to later. - traceback.print_exception(type(exc), exc, None) - - -@pytest.fixture(scope="session") -def images_gcs_path() -> str: - return "gs://bigframes_blob_test/images/*" - - -@pytest.fixture(scope="session") -def images_uris() -> list[str]: - return [ - "gs://bigframes_blob_test/images/img0.jpg", - "gs://bigframes_blob_test/images/img1.jpg", - ] - - -@pytest.fixture() -def reset_default_session_and_location(): - bpd.close_session() - with bpd.option_context("bigquery.location", None): - yield - bpd.close_session() - bpd.options.bigquery.location = None diff --git a/tests/system/large/bigquery/__init__.py b/tests/system/large/bigquery/__init__.py deleted file mode 100644 index 58d482ea386..00000000000 --- a/tests/system/large/bigquery/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -# Copyright 2026 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/large/bigquery/test_ai.py b/tests/system/large/bigquery/test_ai.py deleted file mode 100644 index 504fe5aa389..00000000000 --- a/tests/system/large/bigquery/test_ai.py +++ /dev/null @@ -1,130 +0,0 @@ -# Copyright 2026 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 - -import bigframes.pandas as bpd -from bigframes.bigquery import ai, ml - - -@pytest.fixture(scope="session") -def embedding_model(bq_connection, dataset_id): - model_name = f"{dataset_id}.embedding_model" - return ml.create_model( - model_name=model_name, - options={"endpoint": "gemini-embedding-001"}, - connection_name=bq_connection, - ) - - -@pytest.fixture(scope="session") -def text_model(bq_connection, dataset_id): - model_name = f"{dataset_id}.text_model" - return ml.create_model( - model_name=model_name, - options={"endpoint": "gemini-2.5-flash"}, - connection_name=bq_connection, - ) - - -def test_generate_embedding(embedding_model): - df = bpd.DataFrame( - { - "content": [ - "What is BigQuery?", - "What is BQML?", - ] - } - ) - - result = ai.generate_embedding(embedding_model, df) - - assert len(result) == 2 - assert "embedding" in result.columns - assert "statistics" in result.columns - assert "status" in result.columns - - -def test_generate_embedding_with_options(embedding_model): - df = bpd.DataFrame( - { - "content": [ - "What is BigQuery?", - "What is BQML?", - ] - } - ) - - result = ai.generate_embedding( - embedding_model, df, task_type="RETRIEVAL_DOCUMENT", output_dimensionality=256 - ) - - assert len(result) == 2 - embedding = result["embedding"].to_pandas() - assert len(embedding[0]) == 256 - - -def test_generate_text(text_model): - df = bpd.DataFrame({"prompt": ["Dog", "Cat"]}) - - result = ai.generate_text(text_model, df) - - assert len(result) == 2 - assert "result" in result.columns - assert "statistics" in result.columns - assert "full_response" in result.columns - assert "status" in result.columns - - -def test_generate_text_with_options(text_model): - df = bpd.DataFrame({"prompt": ["Dog", "Cat"]}) - - result = ai.generate_text(text_model, df, max_output_tokens=1) - - # It basically asserts that the results are still returned. - assert len(result) == 2 - - -def test_generate_table(text_model): - df = bpd.DataFrame( - {"prompt": ["Generate a table of 2 programming languages and their creators."]} - ) - - result = ai.generate_table( - text_model, - df, - output_schema="language STRING, creator STRING", - ) - - assert "language" in result.columns - assert "creator" in result.columns - # The model may not always return the exact number of rows requested. - assert len(result) > 0 - - -def test_generate_table_with_mapping_schema(text_model): - df = bpd.DataFrame( - {"prompt": ["Generate a table of 2 programming languages and their creators."]} - ) - - result = ai.generate_table( - text_model, - df, - output_schema={"language": "STRING", "creator": "STRING"}, - ) - - assert "language" in result.columns - assert "creator" in result.columns - # The model may not always return the exact number of rows requested. - assert len(result) > 0 diff --git a/tests/system/large/bigquery/test_io.py b/tests/system/large/bigquery/test_io.py deleted file mode 100644 index 024c6174709..00000000000 --- a/tests/system/large/bigquery/test_io.py +++ /dev/null @@ -1,39 +0,0 @@ -# Copyright 2026 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 for the specific language governing permissions and -# limitations under the License. - -import bigframes.bigquery as bbq - - -def test_load_data(session, dataset_id): - table_name = f"{dataset_id}.test_load_data" - uri = "gs://cloud-samples-data/bigquery/us-states/us-states.csv" - - # Create the external table - table = bbq.load_data( - table_name, - columns={ - "name": "STRING", - "post_abbr": "STRING", - }, - from_files_options={"format": "CSV", "uris": [uri], "skip_leading_rows": 1}, - session=session, - ) - assert table is not None - - # Read the table to verify - import bigframes.pandas as bpd - - bf_df = bpd.read_gbq(table_name) - pd_df = bf_df.to_pandas() - assert len(pd_df) > 0 diff --git a/tests/system/large/bigquery/test_ml.py b/tests/system/large/bigquery/test_ml.py deleted file mode 100644 index f0f7d4f6917..00000000000 --- a/tests/system/large/bigquery/test_ml.py +++ /dev/null @@ -1,117 +0,0 @@ -# Copyright 2026 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 - -import bigframes.bigquery.ml as ml -import bigframes.pandas as bpd - - -@pytest.fixture(scope="session") -def embedding_model(bq_connection, dataset_id): - model_name = f"{dataset_id}.embedding_model" - return ml.create_model( - model_name=model_name, - options={"endpoint": "gemini-embedding-001"}, - connection_name=bq_connection, - ) - - -def test_generate_embedding(embedding_model): - df = bpd.DataFrame( - { - "content": [ - "What is BigQuery?", - "What is BQML?", - ] - } - ) - - result = ml.generate_embedding(embedding_model, df) - assert len(result) == 2 - assert "ml_generate_embedding_result" in result.columns - assert "ml_generate_embedding_status" in result.columns - - -def test_generate_embedding_with_options(embedding_model): - df = bpd.DataFrame( - { - "content": [ - "What is BigQuery?", - "What is BQML?", - ] - } - ) - - result = ml.generate_embedding( - embedding_model, df, task_type="RETRIEVAL_DOCUMENT", output_dimensionality=256 - ) - assert len(result) == 2 - assert "ml_generate_embedding_result" in result.columns - assert "ml_generate_embedding_status" in result.columns - embedding = result["ml_generate_embedding_result"].to_pandas() - assert len(embedding[0]) == 256 - - -def test_get_insights(dataset_id): - df = bpd.DataFrame( - { - "dim1": ["a", "a", "b", "b", "a", "a", "b", "b"], - "dim2": ["x", "y", "x", "y", "x", "y", "x", "y"], - "metric": [10, 20, 30, 40, 12, 25, 35, 45], - "is_test": [False, False, False, False, True, True, True, True], - } - ) - model_name = f"{dataset_id}.contribution_analysis_model" - - ml.create_model( - model_name=model_name, - options={ - "model_type": "CONTRIBUTION_ANALYSIS", - "contribution_metric": "SUM(metric)", - "is_test_col": "is_test", - }, - training_data=df, - ) - - result = ml.get_insights(model_name) - assert len(result) > 0 - assert "contributors" in result.columns - - -def test_create_model_linear_regression(dataset_id): - df = bpd.DataFrame({"x": [1, 2, 3], "y": [2, 4, 6]}) - model_name = f"{dataset_id}.linear_regression_model" - - result = ml.create_model( - model_name=model_name, - options={"model_type": "LINEAR_REG", "input_label_cols": ["y"]}, - training_data=df, - ) - - assert result["modelType"] == "LINEAR_REGRESSION" - - -def test_create_model_with_transform(dataset_id): - df = bpd.DataFrame({"x": [1, 2, 3], "y": [2, 4, 6]}) - model_name = f"{dataset_id}.transform_model" - - result = ml.create_model( - model_name=model_name, - options={"model_type": "LINEAR_REG", "input_label_cols": ["y"]}, - training_data=df, - transform=["x * 2 AS x_doubled", "y"], - ) - - assert result["modelType"] == "LINEAR_REGRESSION" diff --git a/tests/system/large/bigquery/test_table.py b/tests/system/large/bigquery/test_table.py deleted file mode 100644 index dd956b3a040..00000000000 --- a/tests/system/large/bigquery/test_table.py +++ /dev/null @@ -1,36 +0,0 @@ -# Copyright 2026 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_create_external_table(session, dataset_id, bq_connection): - table_name = f"{dataset_id}.test_object_table" - uri = "gs://cloud-samples-data/bigquery/tutorials/cymbal-pets/images/*" - - # Create the external table - table = bbq.create_external_table( - table_name, - connection_name=bq_connection, - options={"object_metadata": "SIMPLE", "uris": [uri]}, - session=session, - ) - assert table is not None - - # Read the table to verify - import bigframes.pandas as bpd - - bf_df = bpd.read_gbq(table_name) - pd_df = bf_df.to_pandas() - assert len(pd_df) > 0 diff --git a/tests/system/large/functions/__init__.py b/tests/system/large/functions/__init__.py deleted file mode 100644 index 0a2669d7a25..00000000000 --- a/tests/system/large/functions/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. diff --git a/tests/system/large/functions/test_managed_function.py b/tests/system/large/functions/test_managed_function.py deleted file mode 100644 index 888852edd4d..00000000000 --- a/tests/system/large/functions/test_managed_function.py +++ /dev/null @@ -1,1199 +0,0 @@ -# Copyright 2023 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 warnings - -import google.api_core.exceptions -import pandas -import pytest -import test_utils.prefixer - -import bigframes -import bigframes.dataframe -import bigframes.dtypes -import bigframes.exceptions as bfe -import bigframes.pandas as bpd - -prefixer = test_utils.prefixer.Prefixer("bigframes", "") - - -@pytest.fixture -def function_id(dataset_id, session): - name = prefixer.create_prefix() - yield name - try: - session.bqclient.delete_routine(f"{dataset_id}.{name}") - # some tests, like test_managed_function_options_errors, should not actually create the function. - # so we ignore the not found error. - except google.api_core.exceptions.NotFound: - pass - - -def test_managed_function_array_output(session, scalars_dfs, dataset_id, function_id): - with warnings.catch_warnings(record=True) as record: - - @session.udf( - dataset=dataset_id, - name=function_id, - ) - def featurize(x: int) -> list[float]: - return [float(i) for i in [x, x + 1, x + 2]] - - # No following conflict warning when there is no redundant type hints. - input_type_warning = "Conflicting input types detected" - return_type_warning = "Conflicting return type detected" - assert not any(input_type_warning in str(warning.message) for warning in record) - assert not any(return_type_warning in str(warning.message) for warning in record) - - scalars_df, scalars_pandas_df = scalars_dfs - - bf_int64_col = scalars_df["int64_too"] - bf_result = bf_int64_col.apply(featurize).to_pandas() - - pd_int64_col = scalars_pandas_df["int64_too"] - pd_result = pd_int64_col.apply(featurize) - - # Ignore any dtype disparity. - pandas.testing.assert_series_equal(pd_result, bf_result, check_dtype=False) - - # Make sure the read_gbq_function path works for this function. - featurize_ref = session.read_gbq_function(f"{dataset_id}.{function_id}") - - # Test on the function from read_gbq_function. - got = featurize_ref(10) - assert got == [10.0, 11.0, 12.0] - - bf_result_gbq = bf_int64_col.apply(featurize_ref).to_pandas() - pandas.testing.assert_series_equal(bf_result_gbq, pd_result, check_dtype=False) - - -def test_managed_function_series_apply(session, dataset_id, scalars_dfs, function_id): - @session.udf(dataset=dataset_id, name=function_id) - def foo(x: int) -> bytes: - return bytes(abs(x)) - - # Function should still work normally. - assert foo(-2) == bytes(2) - - scalars_df, scalars_pandas_df = scalars_dfs - - bf_result_col = scalars_df["int64_too"].apply(foo) - bf_result = ( - scalars_df["int64_too"].to_frame().assign(result=bf_result_col).to_pandas() - ) - - pd_result_col = scalars_pandas_df["int64_too"].apply(foo) - pd_result = scalars_pandas_df["int64_too"].to_frame().assign(result=pd_result_col) - - pandas.testing.assert_frame_equal(bf_result, pd_result, check_dtype=False) - - # Make sure the read_gbq_function path works for this function. - foo_ref = session.read_gbq_function(f"{dataset_id}.{function_id}") - - bf_result_col_gbq = scalars_df["int64_too"].apply(foo_ref) - bf_result_gbq = ( - scalars_df["int64_too"].to_frame().assign(result=bf_result_col_gbq).to_pandas() - ) - - pandas.testing.assert_frame_equal(bf_result_gbq, pd_result, check_dtype=False) - - -def test_managed_function_series_apply_array_output( - session, - dataset_id, - scalars_dfs, - function_id, -): - with pytest.warns(bfe.PreviewWarning, match="udf is in preview."): - - @session.udf(dataset=dataset_id, name=function_id) - def foo_list(x: int) -> list[float]: - return [float(abs(x)), float(abs(x) + 1)] - - scalars_df, scalars_pandas_df = scalars_dfs - - bf_result_col = scalars_df["int64_too"].apply(foo_list) - bf_result = ( - scalars_df["int64_too"].to_frame().assign(result=bf_result_col).to_pandas() - ) - - pd_result_col = scalars_pandas_df["int64_too"].apply(foo_list) - pd_result = scalars_pandas_df["int64_too"].to_frame().assign(result=pd_result_col) - - # Ignore any dtype difference. - pandas.testing.assert_frame_equal(bf_result, pd_result, check_dtype=False) - - -def test_managed_function_series_combine(session, dataset_id, scalars_dfs, function_id): - # This function is deliberately written to not work with NA input. - def add(x: int, y: int) -> int: - return x + y - - scalars_df, scalars_pandas_df = scalars_dfs - int_col_name_with_nulls = "int64_col" - int_col_name_no_nulls = "int64_too" - bf_df = scalars_df[[int_col_name_with_nulls, int_col_name_no_nulls]] - pd_df = scalars_pandas_df[[int_col_name_with_nulls, int_col_name_no_nulls]] - - # make sure there are NA values in the test column. - assert any([pandas.isna(val) for val in bf_df[int_col_name_with_nulls]]) - - add_managed_func = session.udf(dataset=dataset_id, name=function_id)(add) - - # with nulls in the series the managed function application would fail. - with pytest.raises( - google.api_core.exceptions.BadRequest, match="unsupported operand" - ): - bf_df[int_col_name_with_nulls].combine( - bf_df[int_col_name_no_nulls], add_managed_func - ).to_pandas() - - # after filtering out nulls the managed function application should work - # similar to pandas. - pd_filter = pd_df[int_col_name_with_nulls].notnull() - pd_result = pd_df[pd_filter][int_col_name_with_nulls].combine( - pd_df[pd_filter][int_col_name_no_nulls], add - ) - bf_filter = bf_df[int_col_name_with_nulls].notnull() - bf_result = ( - bf_df[bf_filter][int_col_name_with_nulls] - .combine(bf_df[bf_filter][int_col_name_no_nulls], add_managed_func) - .to_pandas() - ) - - # ignore any dtype difference. - pandas.testing.assert_series_equal(pd_result, bf_result, check_dtype=False) - - # Make sure the read_gbq_function path works for this function. - add_managed_func_ref = session.read_gbq_function(f"{dataset_id}.{function_id}") - bf_result = ( - bf_df[bf_filter][int_col_name_with_nulls] - .combine(bf_df[bf_filter][int_col_name_no_nulls], add_managed_func_ref) - .to_pandas() - ) - pandas.testing.assert_series_equal(bf_result, pd_result, check_dtype=False) - - -def test_managed_function_series_combine_array_output( - session, dataset_id, scalars_dfs, function_id -): - # The type hints in this function's signature has conflicts. The - # `input_types` and `output_type` arguments from udf decorator take - # precedence and will be used instead. - def add_list(x, y: bool) -> list[bool]: - return [x, y] - - scalars_df, scalars_pandas_df = scalars_dfs - int_col_name_with_nulls = "int64_col" - int_col_name_no_nulls = "int64_too" - bf_df = scalars_df[[int_col_name_with_nulls, int_col_name_no_nulls]] - pd_df = scalars_pandas_df[[int_col_name_with_nulls, int_col_name_no_nulls]] - - # Make sure there are NA values in the test column. - assert any([pandas.isna(val) for val in bf_df[int_col_name_with_nulls]]) - - with warnings.catch_warnings(record=True) as record: - add_list_managed_func = session.udf( - input_types=[int, int], - output_type=list[int], - dataset=dataset_id, - name=function_id, - )(add_list) - - input_type_warning = "Conflicting input types detected" - assert any(input_type_warning in str(warning.message) for warning in record) - return_type_warning = "Conflicting return type detected" - assert any(return_type_warning in str(warning.message) for warning in record) - - # After filtering out nulls the managed function application should work - # similar to pandas. - pd_filter = pd_df[int_col_name_with_nulls].notnull() - pd_result = pd_df[pd_filter][int_col_name_with_nulls].combine( - pd_df[pd_filter][int_col_name_no_nulls], add_list - ) - bf_filter = bf_df[int_col_name_with_nulls].notnull() - bf_result = ( - bf_df[bf_filter][int_col_name_with_nulls] - .combine(bf_df[bf_filter][int_col_name_no_nulls], add_list_managed_func) - .to_pandas() - ) - - # Ignore any dtype difference. - pandas.testing.assert_series_equal(pd_result, bf_result, check_dtype=False) - - # Make sure the read_gbq_function path works for this function. - add_list_managed_func_ref = session.read_gbq_function(f"{dataset_id}.{function_id}") - - # Test on the function from read_gbq_function. - got = add_list_managed_func_ref(10, 38) - assert got == [10, 38] - - bf_result_gbq = ( - bf_df[bf_filter][int_col_name_with_nulls] - .combine(bf_df[bf_filter][int_col_name_no_nulls], add_list_managed_func_ref) - .to_pandas() - ) - - pandas.testing.assert_series_equal(bf_result_gbq, pd_result, check_dtype=False) - - -def test_managed_function_dataframe_map(session, dataset_id, scalars_dfs, function_id): - def add_one(x): - return x + 1 - - mf_add_one = session.udf( - input_types=[int], - output_type=int, - dataset=dataset_id, - name=function_id, - )(add_one) - - scalars_df, scalars_pandas_df = scalars_dfs - int64_cols = ["int64_col", "int64_too"] - - bf_int64_df = scalars_df[int64_cols] - bf_int64_df_filtered = bf_int64_df.dropna() - bf_result = bf_int64_df_filtered.map(mf_add_one).to_pandas() - - pd_int64_df = scalars_pandas_df[int64_cols] - pd_int64_df_filtered = pd_int64_df.dropna() - pd_result = pd_int64_df_filtered.map(add_one) - # TODO(shobs): Figure why pandas .map() changes the dtype, i.e. - # pd_int64_df_filtered.dtype is Int64Dtype() - # pd_int64_df_filtered.map(lambda x: x).dtype is int64. - # For this test let's force the pandas dtype to be same as input. - for col in pd_result: - pd_result[col] = pd_result[col].astype(pd_int64_df_filtered[col].dtype) - - pandas.testing.assert_frame_equal(bf_result, pd_result) - - -def test_managed_function_dataframe_map_array_output( - session, scalars_dfs, dataset_id, function_id -): - def add_one_list(x): - return [x + 1] * 3 - - mf_add_one_list = session.udf( - input_types=[int], - output_type=list[int], - dataset=dataset_id, - name=function_id, - )(add_one_list) - - scalars_df, scalars_pandas_df = scalars_dfs - int64_cols = ["int64_col", "int64_too"] - - bf_int64_df = scalars_df[int64_cols] - bf_int64_df_filtered = bf_int64_df.dropna() - bf_result = bf_int64_df_filtered.map(mf_add_one_list).to_pandas() - - pd_int64_df = scalars_pandas_df[int64_cols] - pd_int64_df_filtered = pd_int64_df.dropna() - pd_result = pd_int64_df_filtered.map(add_one_list) - - # Ignore any dtype difference. - pandas.testing.assert_frame_equal(bf_result, pd_result, check_dtype=False) - - # Make sure the read_gbq_function path works for this function. - mf_add_one_list_ref = session.read_gbq_function(f"{dataset_id}.{function_id}") - - bf_result_gbq = bf_int64_df_filtered.map(mf_add_one_list_ref).to_pandas() - pandas.testing.assert_frame_equal(bf_result_gbq, pd_result, check_dtype=False) - - -def test_managed_function_dataframe_apply_axis_1( - session, dataset_id, scalars_dfs, function_id -): - scalars_df, scalars_pandas_df = scalars_dfs - series = scalars_df["int64_too"] - series_pandas = scalars_pandas_df["int64_too"] - - def add_ints(x, y): - return x + y - - add_ints_mf = session.udf( - input_types=[int, int], - output_type=int, - dataset=dataset_id, - name=function_id, - )(add_ints) - - with pytest.warns( - bigframes.exceptions.PreviewWarning, match="axis=1 scenario is in preview." - ): - bf_result = ( - bpd.DataFrame({"x": series, "y": series}) - .apply(add_ints_mf, axis=1) - .to_pandas() - ) - - pd_result = pandas.DataFrame({"x": series_pandas, "y": series_pandas}).apply( - lambda row: add_ints(row["x"], row["y"]), axis=1 - ) - - pandas.testing.assert_series_equal( - pd_result, bf_result, check_dtype=False, check_exact=True - ) - - -def test_managed_function_dataframe_apply_axis_1_array_output( - session, dataset_id, function_id -): - bf_df = bigframes.dataframe.DataFrame( - { - "Id": [1, 2, 3], - "Age": [22.5, 23, 23.5], - "Name": ["alpha", "beta", "gamma"], - } - ) - - expected_dtypes = ( - bigframes.dtypes.INT_DTYPE, - bigframes.dtypes.FLOAT_DTYPE, - bigframes.dtypes.STRING_DTYPE, - ) - - # Assert the dataframe dtypes. - assert tuple(bf_df.dtypes) == expected_dtypes - - @session.udf( - input_types=[int, float, str], - output_type=list[str], - dataset=dataset_id, - name=function_id, - ) - def foo(x, y, z): - return [str(x), str(y), z] - - # Fails to apply on dataframe with incompatible number of columns. - with pytest.raises( - ValueError, - match="^Parameter count mismatch:.* expected 3 parameters but received 2 DataFrame columns.", - ): - bf_df[["Id", "Age"]].apply(foo, axis=1) - - with pytest.raises( - ValueError, - match="^Parameter count mismatch:.* expected 3 parameters but received 4 DataFrame columns.", - ): - bf_df.assign(Country="lalaland").apply(foo, axis=1) - - # Fails to apply on dataframe with incompatible column datatypes. - with pytest.raises( - ValueError, - match="^Data type mismatch for DataFrame columns: Expected .* Received .*", - ): - bf_df.assign(Age=bf_df["Age"].astype("Int64")).apply(foo, axis=1) - - # Successfully applies to dataframe with matching number of columns. - # and their datatypes. - with pytest.warns( - bigframes.exceptions.PreviewWarning, - match="axis=1 scenario is in preview.", - ): - bf_result = bf_df.apply(foo, axis=1).to_pandas() - - # Since this scenario is not pandas-like, let's handcraft the - # expected result. - expected_result = pandas.Series( - [ - ["1", "22.5", "alpha"], - ["2", "23.0", "beta"], - ["3", "23.5", "gamma"], - ] - ) - - pandas.testing.assert_series_equal( - expected_result, bf_result, check_dtype=False, check_index_type=False - ) - - # Make sure the read_gbq_function path works for this function. - foo_ref = session.read_gbq_function(f"{dataset_id}.{function_id}") - - # Test on the function from read_gbq_function. - got = foo_ref(10, 38, "hello") - assert got == ["10", "38.0", "hello"] - - with pytest.warns( - bigframes.exceptions.PreviewWarning, - match="axis=1 scenario is in preview.", - ): - bf_result_gbq = bf_df.apply(foo_ref, axis=1).to_pandas() - - pandas.testing.assert_series_equal( - bf_result_gbq, expected_result, check_dtype=False, check_index_type=False - ) - - -@pytest.mark.parametrize( - "connection_fixture", - [ - "bq_connection_name", - "bq_connection", - ], -) -def test_managed_function_with_connection( - session, scalars_dfs, dataset_id, request, connection_fixture, function_id -): - bigquery_connection = request.getfixturevalue(connection_fixture) - - @session.udf( - bigquery_connection=bigquery_connection, - dataset=dataset_id, - name=function_id, - ) - def foo(x: int) -> int: - return x + 10 - - # Function should still work normally. - assert foo(-2) == 8 - - scalars_df, scalars_pandas_df = scalars_dfs - - bf_result_col = scalars_df["int64_too"].apply(foo) - bf_result = ( - scalars_df["int64_too"].to_frame().assign(result=bf_result_col).to_pandas() - ) - - pd_result_col = scalars_pandas_df["int64_too"].apply(foo) - pd_result = scalars_pandas_df["int64_too"].to_frame().assign(result=pd_result_col) - - pandas.testing.assert_frame_equal(bf_result, pd_result, check_dtype=False) - - -def test_managed_function_options(session, dataset_id, scalars_dfs, function_id): - def multiply_five(x: int) -> int: - return x * 5 - - mf_multiply_five = session.udf( - dataset=dataset_id, - name=function_id, - max_batching_rows=100, - container_cpu=2, - container_memory="2Gi", - )(multiply_five) - - scalars_df, scalars_pandas_df = scalars_dfs - - bf_int64_df = scalars_df["int64_col"] - bf_int64_df_filtered = bf_int64_df.dropna() - bf_result = bf_int64_df_filtered.apply(mf_multiply_five).to_pandas() - - pd_int64_df = scalars_pandas_df["int64_col"] - pd_int64_df_filtered = pd_int64_df.dropna() - pd_result = pd_int64_df_filtered.apply(multiply_five) - - pandas.testing.assert_series_equal(bf_result, pd_result, check_dtype=False) - - # Make sure the read_gbq_function path works for this function. - multiply_five_ref = session.read_gbq_function( - function_name=f"{dataset_id}.{function_id}" # type: ignore - ) - - bf_result = bf_int64_df_filtered.apply(multiply_five_ref).to_pandas() - pandas.testing.assert_series_equal(bf_result, pd_result, check_dtype=False) - - # Retrieve the routine and validate its runtime configuration. - routine = session.bqclient.get_routine(f"{dataset_id}.{function_id}") - - # TODO(jialuo): Use the newly exposed class properties instead of - # accessing the hidden _properties after resolve of this issue: - # https://github.com/googleapis/python-bigquery/issues/2240. - assert routine._properties["externalRuntimeOptions"]["maxBatchingRows"] == "100" - assert routine._properties["externalRuntimeOptions"]["containerCpu"] == 2 - assert routine._properties["externalRuntimeOptions"]["containerMemory"] == "2Gi" - - -def test_managed_function_options_errors(session, dataset_id, function_id): - def foo(x: int) -> int: - return 0 - - with pytest.raises( - google.api_core.exceptions.BadRequest, - # For CPU Value >= 1.0, the value must be one of [1, 2, ...]. - match="Invalid container_cpu function OPTIONS value", - ): - session.udf( - dataset=dataset_id, - name=function_id, - max_batching_rows=100, - container_cpu=2.5, - container_memory="2Gi", - )(foo) - - with pytest.raises( - google.api_core.exceptions.BadRequest, - # For less than 1.0 CPU, the value must be no less than 0.33. - match="Invalid container_cpu function OPTIONS value", - ): - session.udf( - dataset=dataset_id, - name=function_id, - max_batching_rows=100, - container_cpu=0.10, - container_memory="512Mi", - )(foo) - - with pytest.raises( - google.api_core.exceptions.BadRequest, - # For 2.00 CPU, the memory must be in the range of [256Mi, 8Gi]. - match="Invalid container_memory function OPTIONS value", - ): - session.udf( - dataset=dataset_id, - name=function_id, - max_batching_rows=100, - container_cpu=2, - container_memory="64Mi", - )(foo) - - -def test_managed_function_df_apply_axis_1( - session, dataset_id, scalars_dfs, function_id -): - columns = ["bool_col", "int64_col", "int64_too", "float64_col", "string_col"] - scalars_df, scalars_pandas_df = scalars_dfs - - def serialize_row(row): - # TODO(b/435021126): Remove explicit type conversion of the field - # "name" after the issue has been addressed. It is added only to - # accept partial pandas parity for the time being. - custom = { - "name": int(row.name), - "index": [idx for idx in row.index], - "values": [ - val.item() if hasattr(val, "item") else val for val in row.values - ], - } - - return str( - { - "default": row.to_json(), - "split": row.to_json(orient="split"), - "records": row.to_json(orient="records"), - "index": row.to_json(orient="index"), - "table": row.to_json(orient="table"), - "custom": custom, - } - ) - - with pytest.raises( - TypeError, - match="Argument type hint must be Pandas Series, not BigFrames Series.", - ): - serialize_row_mf = session.udf( - input_types=bigframes.series.Series, - output_type=str, - dataset=dataset_id, - name=function_id, - )(serialize_row) - - serialize_row_mf = session.udf( - input_types=pandas.Series, - output_type=str, - dataset=dataset_id, - name=function_id, - )(serialize_row) - - bf_result = scalars_df[columns].apply(serialize_row_mf, axis=1).to_pandas() - pd_result = scalars_pandas_df[columns].apply(serialize_row, axis=1) - - # bf_result.dtype is 'string[pyarrow]' while pd_result.dtype is 'object' - # , ignore this mismatch by using check_dtype=False. - pandas.testing.assert_series_equal(pd_result, bf_result, check_dtype=False) - - # Let's make sure the read_gbq_function path works for this function. - serialize_row_reuse = session.read_gbq_function( - f"{dataset_id}.{function_id}", is_row_processor=True - ) - bf_result = scalars_df[columns].apply(serialize_row_reuse, axis=1).to_pandas() - pandas.testing.assert_series_equal(pd_result, bf_result, check_dtype=False) - - -def test_managed_function_df_apply_axis_1_aggregates( - session, dataset_id, scalars_dfs, function_id -): - columns = ["int64_col", "int64_too", "float64_col"] - scalars_df, scalars_pandas_df = scalars_dfs - - def analyze(row): - # TODO(b/435021126): Remove explicit type conversion of the fields - # after the issue has been addressed. It is added only to accept - # partial pandas parity for the time being. - return str( - { - "dtype": row.dtype, - "count": int(row.count()), - "min": int(row.min()), - "max": int(row.max()), - "mean": float(row.mean()), - "std": float(row.std()), - "var": float(row.var()), - } - ) - - with pytest.warns( - bfe.FunctionPackageVersionWarning, - match=( - "numpy, pandas, and pyarrow versions in the function execution" - "\nenvironment may not precisely match your local environment." - ), - ): - analyze_mf = session.udf( - input_types=pandas.Series, - output_type=str, - dataset=dataset_id, - name=function_id, - )(analyze) - - bf_result = scalars_df[columns].dropna().apply(analyze_mf, axis=1).to_pandas() - pd_result = scalars_pandas_df[columns].dropna().apply(analyze, axis=1) - - # bf_result.dtype is 'string[pyarrow]' while pd_result.dtype is 'object' - # , ignore this mismatch by using check_dtype=False. - pandas.testing.assert_series_equal(pd_result, bf_result, check_dtype=False) - - -@pytest.mark.parametrize( - ("pd_df",), - [ - pytest.param( - pandas.DataFrame( - { - "2": [1, 2, 3], - 2: [1.5, 3.75, 5], - "name, [with. special'- chars\")/\\": [10, 20, 30], - (3, 4): ["pq", "rs", "tu"], - (5.0, "six", 7): [8, 9, 10], - 'raise Exception("hacked!")': [11, 12, 13], - }, - # Default pandas index has non-numpy type, whereas bigframes is - # always numpy-based type, so let's use the index compatible - # with bigframes. See more details in b/369689696. - index=pandas.Index([0, 1, 2], dtype=pandas.Int64Dtype()), - ), - id="all-kinds-of-column-names", - ), - pytest.param( - pandas.DataFrame( - { - "x": [1, 2, 3], - "y": [1.5, 3.75, 5], - "z": ["pq", "rs", "tu"], - }, - index=pandas.MultiIndex.from_frame( - pandas.DataFrame( - { - "idx0": pandas.Series( - ["a", "a", "b"], dtype=pandas.StringDtype() - ), - "idx1": pandas.Series( - [100, 200, 300], dtype=pandas.Int64Dtype() - ), - } - ) - ), - ), - id="multiindex", - marks=pytest.mark.skip( - reason="TODO: revert this skip after this pandas bug is fixed: https://github.com/pandas-dev/pandas/issues/59908" - ), - ), - pytest.param( - pandas.DataFrame( - [ - [10, 1.5, "pq"], - [20, 3.75, "rs"], - [30, 8.0, "tu"], - ], - # Default pandas index has non-numpy type, whereas bigframes is - # always numpy-based type, so let's use the index compatible - # with bigframes. See more details in b/369689696. - index=pandas.Index([0, 1, 2], dtype=pandas.Int64Dtype()), - columns=pandas.MultiIndex.from_arrays( - [ - ["first", "last_two", "last_two"], - [1, 2, 3], - ] - ), - ), - id="column-multiindex", - ), - ], -) -def test_managed_function_df_apply_axis_1_complex( - session, dataset_id, pd_df, function_id -): - bf_df = session.read_pandas(pd_df) - - def serialize_row(row): - # TODO(b/435021126): Remove explicit type conversion of the field - # "name" after the issue has been addressed. It is added only to - # accept partial pandas parity for the time being. - custom = { - "name": int(row.name), - "index": [idx for idx in row.index], - "values": [ - val.item() if hasattr(val, "item") else val for val in row.values - ], - } - return str( - { - "default": row.to_json(), - "split": row.to_json(orient="split"), - "records": row.to_json(orient="records"), - "index": row.to_json(orient="index"), - "custom": custom, - } - ) - - serialize_row_mf = session.udf( - input_types=pandas.Series, - output_type=str, - dataset=dataset_id, - name=function_id, - )(serialize_row) - - bf_result = bf_df.apply(serialize_row_mf, axis=1).to_pandas() - pd_result = pd_df.apply(serialize_row, axis=1) - - # ignore known dtype difference between pandas and bigframes. - pandas.testing.assert_series_equal( - pd_result, bf_result, check_dtype=False, check_index_type=False - ) - - -@pytest.mark.skip(reason="Revert after this bug b/435018880 is fixed.") -def test_managed_function_df_apply_axis_1_na_nan_inf(dataset_id, session, function_id): - """This test is for special cases of float values, to make sure any (nan, - inf, -inf) produced by user code is honored. - """ - bf_df = session.read_gbq( - """\ -SELECT "1" AS text, 1 AS num -UNION ALL -SELECT "2.5" AS text, 2.5 AS num -UNION ALL -SELECT "nan" AS text, IEEE_DIVIDE(0, 0) AS num -UNION ALL -SELECT "inf" AS text, IEEE_DIVIDE(1, 0) AS num -UNION ALL -SELECT "-inf" AS text, IEEE_DIVIDE(-1, 0) AS num -UNION ALL -SELECT "numpy nan" AS text, IEEE_DIVIDE(0, 0) AS num -UNION ALL -SELECT "pandas na" AS text, NULL AS num - """ - ) - - pd_df = bf_df.to_pandas() - - def float_parser(row: pandas.Series): - import numpy as mynp - import pandas as mypd - - if row["text"] == "pandas na": - return mypd.NA - if row["text"] == "numpy nan": - return mynp.nan - return float(row["text"]) - - float_parser_mf = session.udf( - input_types=pandas.Series, - output_type=float, - dataset=dataset_id, - name=function_id, - )(float_parser) - - pd_result = pd_df.apply(float_parser, axis=1) - bf_result = bf_df.apply(float_parser_mf, axis=1).to_pandas() - - # bf_result.dtype is 'Float64' while pd_result.dtype is 'object' - # , ignore this mismatch by using check_dtype=False. - pandas.testing.assert_series_equal(pd_result, bf_result, check_dtype=False) - - # Let's also assert that the data is consistent in this round trip - # (BQ -> BigFrames -> BQ -> GCF -> BQ -> BigFrames) w.r.t. their - # expected values in BQ. - bq_result = bf_df["num"].to_pandas() - bq_result.name = None - pandas.testing.assert_series_equal(bq_result, bf_result) - - -def test_managed_function_df_apply_axis_1_args( - session, dataset_id, scalars_dfs, function_id -): - columns = ["int64_col", "int64_too"] - scalars_df, scalars_pandas_df = scalars_dfs - - def the_sum(s1, s2, x): - return s1 + s2 + x - - the_sum_mf = session.udf( - input_types=[int, int, int], - output_type=int, - dataset=dataset_id, - name=function_id, - )(the_sum) - - args1 = (1,) - - # Fails to apply on dataframe with incompatible number of columns and args. - with pytest.raises( - ValueError, - match="^Parameter count mismatch:.* expected 3 parameters but received 4 values \\(3 DataFrame columns and 1 args\\)", - ): - scalars_df[columns + ["float64_col"]].apply(the_sum_mf, axis=1, args=args1) - - # Fails to apply on dataframe with incompatible column datatypes. - with pytest.raises( - ValueError, - match="^Data type mismatch for DataFrame columns: Expected .* Received .*", - ): - scalars_df[columns].assign( - int64_col=lambda df: df["int64_col"].astype("Float64") - ).apply(the_sum_mf, axis=1, args=args1) - - # Fails to apply on dataframe with incompatible args datatypes. - with pytest.raises( - ValueError, - match="^Data type mismatch for 'args' parameter: Expected .* Received .*", - ): - scalars_df[columns].apply(the_sum_mf, axis=1, args=(1.3,)) - - bf_result = ( - scalars_df[columns].dropna().apply(the_sum_mf, axis=1, args=args1).to_pandas() - ) - pd_result = scalars_pandas_df[columns].dropna().apply(sum, axis=1, args=args1) - - pandas.testing.assert_series_equal(pd_result, bf_result, check_dtype=False) - - -def test_managed_function_df_apply_axis_1_series_args( - session, dataset_id, scalars_dfs, function_id -): - columns = ["int64_col", "float64_col"] - scalars_df, scalars_pandas_df = scalars_dfs - - def analyze(s: pandas.Series, x: bool, y: float) -> str: - value = f"value is {s['int64_col']} and {s['float64_col']}" - if x: - return f"{value}, x is True!" - if y > 0: - return f"{value}, x is False, y is positive!" - return f"{value}, x is False, y is non-positive!" - - analyze_mf = session.udf( - dataset=dataset_id, - name=function_id, - )(analyze) - - args1 = (True, 10.0) - bf_result = ( - scalars_df[columns].dropna().apply(analyze_mf, axis=1, args=args1).to_pandas() - ) - pd_result = scalars_pandas_df[columns].dropna().apply(analyze, axis=1, args=args1) - - pandas.testing.assert_series_equal(pd_result, bf_result, check_dtype=False) - - args2 = (False, -10.0) - analyze_mf_ref = session.read_gbq_function( - f"{dataset_id}.{function_id}", is_row_processor=True - ) - bf_result = ( - scalars_df[columns] - .dropna() - .apply(analyze_mf_ref, axis=1, args=args2) - .to_pandas() - ) - pd_result = scalars_pandas_df[columns].dropna().apply(analyze, axis=1, args=args2) - - pandas.testing.assert_series_equal(pd_result, bf_result, check_dtype=False) - - -def test_managed_function_df_where_mask(session, dataset_id, scalars_dfs, function_id): - # The return type has to be bool type for callable where condition. - def is_sum_positive(a, b): - return a + b > 0 - - is_sum_positive_mf = session.udf( - input_types=[int, int], - output_type=bool, - dataset=dataset_id, - name=function_id, - )(is_sum_positive) - - scalars_df, scalars_pandas_df = scalars_dfs - int64_cols = ["int64_col", "int64_too"] - - bf_int64_df = scalars_df[int64_cols] - bf_int64_df_filtered = bf_int64_df.dropna() - pd_int64_df = scalars_pandas_df[int64_cols] - pd_int64_df_filtered = pd_int64_df.dropna() - - # Test callable condition in dataframe.where method. - bf_result = bf_int64_df_filtered.where(is_sum_positive_mf).to_pandas() - # Pandas doesn't support such case, use following as workaround. - pd_result = pd_int64_df_filtered.where(pd_int64_df_filtered.sum(axis=1) > 0) - - # Ignore any dtype difference. - pandas.testing.assert_frame_equal(bf_result, pd_result, check_dtype=False) - - # Make sure the read_gbq_function path works for dataframe.where method. - is_sum_positive_ref = session.read_gbq_function(f"{dataset_id}.{function_id}") - - bf_result_gbq = bf_int64_df_filtered.where( - is_sum_positive_ref, -bf_int64_df_filtered - ).to_pandas() - pd_result_gbq = pd_int64_df_filtered.where( - pd_int64_df_filtered.sum(axis=1) > 0, -pd_int64_df_filtered - ) - - # Ignore any dtype difference. - pandas.testing.assert_frame_equal(bf_result_gbq, pd_result_gbq, check_dtype=False) - - # Test callable condition in dataframe.mask method. - bf_result_gbq = bf_int64_df_filtered.mask( - is_sum_positive_ref, -bf_int64_df_filtered - ).to_pandas() - pd_result_gbq = pd_int64_df_filtered.mask( - pd_int64_df_filtered.sum(axis=1) > 0, -pd_int64_df_filtered - ) - - # Ignore any dtype difference. - pandas.testing.assert_frame_equal(bf_result_gbq, pd_result_gbq, check_dtype=False) - - -def test_managed_function_df_where_mask_series( - session, dataset_id, scalars_dfs, function_id -): - # The return type has to be bool type for callable where condition. - def is_sum_positive_series(s): - return s["int64_col"] + s["int64_too"] > 0 - - is_sum_positive_series_mf = session.udf( - input_types=pandas.Series, - output_type=bool, - dataset=dataset_id, - name=function_id, - )(is_sum_positive_series) - - scalars_df, scalars_pandas_df = scalars_dfs - int64_cols = ["int64_col", "int64_too"] - - bf_int64_df = scalars_df[int64_cols] - bf_int64_df_filtered = bf_int64_df.dropna() - pd_int64_df = scalars_pandas_df[int64_cols] - pd_int64_df_filtered = pd_int64_df.dropna() - - # Test callable condition in dataframe.where method. - bf_result = bf_int64_df_filtered.where(is_sum_positive_series_mf).to_pandas() - pd_result = pd_int64_df_filtered.where(is_sum_positive_series) - - # Ignore any dtype difference. - pandas.testing.assert_frame_equal(bf_result, pd_result, check_dtype=False) - - # Make sure the read_gbq_function path works for dataframe.where method. - is_sum_positive_series_ref = session.read_gbq_function( - f"{dataset_id}.{function_id}", is_row_processor=True - ) - - # This is for callable `other` arg in dataframe.where method. - def func_for_other(x): - return -x - - bf_result_gbq = bf_int64_df_filtered.where( - is_sum_positive_series_ref, func_for_other - ).to_pandas() - pd_result_gbq = pd_int64_df_filtered.where(is_sum_positive_series, func_for_other) - - # Ignore any dtype difference. - pandas.testing.assert_frame_equal(bf_result_gbq, pd_result_gbq, check_dtype=False) - - # Test callable condition in dataframe.mask method. - bf_result_gbq = bf_int64_df_filtered.mask( - is_sum_positive_series_ref, func_for_other - ).to_pandas() - pd_result_gbq = pd_int64_df_filtered.mask(is_sum_positive_series, func_for_other) - - # Ignore any dtype difference. - pandas.testing.assert_frame_equal(bf_result_gbq, pd_result_gbq, check_dtype=False) - - -def test_managed_function_df_where_other_issue( - session, dataset_id, scalars_df_index, function_id -): - def the_sum(s: pandas.Series) -> int: - return s["int64_col"] + s["int64_too"] - - the_sum_mf = session.udf( - dataset=dataset_id, - name=function_id, - )(the_sum) - - int64_cols = ["int64_col", "int64_too"] - - bf_int64_df = scalars_df_index[int64_cols] - bf_int64_df_filtered = bf_int64_df.dropna() - - with pytest.raises( - ValueError, - match="Seires is not a supported replacement type!", - ): - # The execution of the callable other=the_sum_mf will return a - # Series, which is not a supported replacement type. - bf_int64_df_filtered.where(cond=bf_int64_df_filtered, other=the_sum_mf) - - -def test_managed_function_series_where_mask_map( - session, dataset_id, scalars_dfs, function_id -): - # The return type has to be bool type for callable where condition. - def _is_positive(s): - return s + 1000 > 0 - - is_positive_mf = session.udf( - input_types=int, - output_type=bool, - dataset=dataset_id, - name=function_id, - )(_is_positive) - - scalars, scalars_pandas = scalars_dfs - - bf_int64 = scalars["int64_col"] - bf_int64_filtered = bf_int64.dropna() - pd_int64 = scalars_pandas["int64_col"] - pd_int64_filtered = pd_int64.dropna() - - # Test series.where method: the cond is a callable (managed function) - # and the other is not a callable. - bf_result = bf_int64_filtered.where( - cond=is_positive_mf, other=-bf_int64_filtered - ).to_pandas() - pd_result = pd_int64_filtered.where(cond=_is_positive, other=-pd_int64_filtered) - - # Ignore any dtype difference. - pandas.testing.assert_series_equal(bf_result, pd_result, check_dtype=False) - - # Test series.mask method: the cond is a callable (managed function) - # and the other is not a callable. - bf_result = bf_int64_filtered.mask( - cond=is_positive_mf, other=-bf_int64_filtered - ).to_pandas() - pd_result = pd_int64_filtered.mask(cond=_is_positive, other=-pd_int64_filtered) - - # Ignore any dtype difference. - pandas.testing.assert_series_equal(bf_result, pd_result, check_dtype=False) - - # Test series.map method. - bf_result = bf_int64_filtered.map(is_positive_mf).to_pandas() - pd_result = pd_int64_filtered.map(_is_positive) - - # Ignore any dtype difference. - pandas.testing.assert_series_equal(bf_result, pd_result, check_dtype=False) - - -def test_managed_function_series_apply_args( - session, dataset_id, scalars_dfs, function_id -): - with pytest.warns(bfe.PreviewWarning, match="udf is in preview."): - - @session.udf(dataset=dataset_id, name=function_id) - def foo_list(x: int, y0: float, y1: bytes, y2: bool) -> list[str]: - return [str(x), str(y0), str(y1), str(y2)] - - scalars_df, scalars_pandas_df = scalars_dfs - - bf_result = ( - scalars_df["int64_too"] - .apply(foo_list, args=(12.34, b"hello world", False)) - .to_pandas() - ) - pd_result = scalars_pandas_df["int64_too"].apply( - foo_list, args=(12.34, b"hello world", False) - ) - - # Ignore any dtype difference. - pandas.testing.assert_series_equal(bf_result, pd_result, check_dtype=False) - - -def test_deferred_unnamed_udf_execution(session, scalars_dfs): - import bigframes.functions.udf_def as udf_def - - # Create an unnamed UDF (name=None) - @session.udf() - def unnamed_multiplier(x: int) -> int: - return x * 3 - - assert isinstance(unnamed_multiplier.udf_def, udf_def.PythonUdf) - - scalars_df, scalars_pandas_df = scalars_dfs - bf_series = scalars_df["int64_too"] - pd_series = scalars_pandas_df["int64_too"] - - bf_result = bf_series.apply(unnamed_multiplier).to_pandas() - pd_result = pd_series.apply(lambda x: x * 3) - - pandas.testing.assert_series_equal(bf_result, pd_result, check_dtype=False) - - import bigframes.functions._function_session as functions_sessions - - config = unnamed_multiplier.udf_def.to_managed_function_config() - expected_routine_name = functions_sessions.get_managed_function_name( - config, session.session_id - ) - routine = session.bqclient.get_routine( - f"{session._anonymous_dataset.project}.{session._anonymous_dataset.dataset_id}.{expected_routine_name}" - ) - assert routine is not None - - -def test_deferred_udf_with_runtime_requirements(session, scalars_dfs): - import bigframes.functions.udf_def as udf_def - - # Create an unnamed UDF with custom options - @session.udf( - container_cpu=1, - container_memory="2Gi", - max_batching_rows=25, - ) - def heavy_unnamed_udf(x: int) -> int: - return x + 100 - - assert isinstance(heavy_unnamed_udf.udf_def, udf_def.PythonUdf) - - scalars_df, scalars_pandas_df = scalars_dfs - bf_series = scalars_df["int64_too"] - pd_series = scalars_pandas_df["int64_too"] - - bf_result = bf_series.apply(heavy_unnamed_udf).to_pandas() - pd_result = pd_series.apply(lambda x: x + 100) - - pandas.testing.assert_series_equal(bf_result, pd_result, check_dtype=False) - - # Verify it was deployed with the correct runtime options - import bigframes.functions._function_session as functions_sessions - - config = heavy_unnamed_udf.udf_def.to_managed_function_config() - expected_routine_name = functions_sessions.get_managed_function_name( - config, session.session_id - ) - routine = session.bqclient.get_routine( - f"{session._anonymous_dataset.project}.{session._anonymous_dataset.dataset_id}.{expected_routine_name}" - ) - assert routine._properties["externalRuntimeOptions"]["containerCpu"] == 1 - assert routine._properties["externalRuntimeOptions"]["containerMemory"] == "2Gi" - assert routine._properties["externalRuntimeOptions"]["maxBatchingRows"] == "25" diff --git a/tests/system/large/functions/test_remote_function.py b/tests/system/large/functions/test_remote_function.py deleted file mode 100644 index 69769a1a846..00000000000 --- a/tests/system/large/functions/test_remote_function.py +++ /dev/null @@ -1,3265 +0,0 @@ -# Copyright 2023 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 importlib.util -import inspect -import math # must keep this at top level to test udf referring global import -import os.path -import shutil -import tempfile -import textwrap -import uuid -import warnings -from datetime import datetime - -import google.api_core.exceptions -import pandas -import pytest -import test_utils.prefixer -from google.cloud import bigquery, functions_v2, storage - -import bigframes -import bigframes.dataframe -import bigframes.dtypes -import bigframes.exceptions -import bigframes.pandas as bpd -import bigframes.series -from bigframes.testing.utils import ( - assert_frame_equal, - cleanup_function_assets, - delete_cloud_function, - get_cloud_functions, -) - -# NOTE: Keep this import at the top level to test global var behavior with -# remote functions -_team_pi = "Team Pi" -_team_euler = "Team Euler" - - -def make_uniq_udf(udf): - """Transform a udf to another with same behavior but a unique name. - Use this to test remote functions with reuse=True, in which case parallel - instances of the same tests may evaluate same named cloud functions and BQ - remote functions, therefore interacting with each other and causing unwanted - failures. With this method one can transform a udf into another with the - same behavior but a different name which will remain unique for the - lifetime of one test instance. - """ - - prefixer = test_utils.prefixer.Prefixer(udf.__name__, "") - udf_uniq_name = prefixer.create_prefix() - udf_file_name = f"{udf_uniq_name}.py" - - # We are not using `tempfile.TemporaryDirectory()` because we want to keep - # the temp code around, otherwise `inspect.getsource()` complains. - tmpdir = tempfile.mkdtemp() - udf_file_path = os.path.join(tmpdir, udf_file_name) - with open(udf_file_path, "w") as f: - # TODO(shobs): Find a better way of modifying the udf, maybe regex? - source_key = f"def {udf.__name__}" - target_key = f"def {udf_uniq_name}" - source_code = textwrap.dedent(inspect.getsource(udf)) - target_code = source_code.replace(source_key, target_key, 1) - f.write(target_code) - spec = importlib.util.spec_from_file_location(udf_file_name, udf_file_path) - - assert (spec is not None) and (spec.loader is not None) - module = importlib.util.module_from_spec(spec) - - # exec_module fills the module object with all the functions, classes, and - # variables defined in the module file. - spec.loader.exec_module(module) - udf_uniq = getattr(module, udf_uniq_name) - - return udf_uniq, tmpdir - - -@pytest.fixture(scope="module") -def bq_cf_connection() -> str: - """Pre-created BQ connection in the test project in US location, used to - invoke cloud function. - - $ bq show --connection --location=us --project_id=PROJECT_ID bigframes-rf-conn - """ - return "bigframes-rf-conn" - - -@pytest.mark.flaky(retries=2, delay=120) -def test_remote_function_binop(session, scalars_dfs, dataset_id, bq_cf_connection): - try: - - def func(x, y): - return x * abs(y % 4) - - remote_func = session.remote_function( - # Make sure that the input/output types can be used positionally. - # This avoids the worst of the breaking change from 1.x to 2.x. - [str, int], - str, - dataset_id, - bigquery_connection=bq_cf_connection, - reuse=False, - cloud_function_service_account="default", - )(func) - - scalars_df, scalars_pandas_df = scalars_dfs - - scalars_df = scalars_df.dropna() - scalars_pandas_df = scalars_pandas_df.dropna() - bf_result = ( - scalars_df["string_col"] - .combine(scalars_df["int64_col"], remote_func) - .to_pandas() - ) - pd_result = scalars_pandas_df["string_col"].combine( - scalars_pandas_df["int64_col"], func - ) - pandas.testing.assert_series_equal(bf_result, pd_result) - finally: - # clean up the gcp assets created for the remote function - cleanup_function_assets( - remote_func, session.bqclient, session.cloudfunctionsclient - ) - - -@pytest.mark.flaky(retries=2, delay=120) -def test_remote_function_binop_array_output( - session, scalars_dfs, dataset_id, bq_cf_connection -): - try: - - def func(x, y): - return [len(x), abs(y % 4)] - - remote_func = session.remote_function( - # Make sure that the input/output types can be used positionally. - # This avoids the worst of the breaking change from 1.x to 2.x. - [str, int], - list[int], - dataset_id, - bigquery_connection=bq_cf_connection, - reuse=False, - cloud_function_service_account="default", - )(func) - - scalars_df, scalars_pandas_df = scalars_dfs - - scalars_df = scalars_df.dropna() - scalars_pandas_df = scalars_pandas_df.dropna() - bf_result = ( - scalars_df["string_col"] - .combine(scalars_df["int64_col"], remote_func) - .to_pandas() - ) - pd_result = scalars_pandas_df["string_col"].combine( - scalars_pandas_df["int64_col"], func - ) - pandas.testing.assert_series_equal(bf_result, pd_result, check_dtype=False) - finally: - # clean up the gcp assets created for the remote function - cleanup_function_assets( - remote_func, session.bqclient, session.cloudfunctionsclient - ) - - -@pytest.mark.flaky(retries=2, delay=120) -def test_remote_function_decorator_with_bigframes_series( - session, scalars_dfs, dataset_id, bq_cf_connection -): - try: - - @session.remote_function( - # Make sure that the input/output types can be used positionally. - # This avoids the worst of the breaking change from 1.x to 2.x. - [int], - int, - dataset_id, - bigquery_connection=bq_cf_connection, - reuse=False, - cloud_function_service_account="default", - ) - def square(x): - return x * x - - scalars_df, scalars_pandas_df = scalars_dfs - - bf_int64_col = scalars_df["int64_col"] - bf_int64_col_filter = bf_int64_col.notnull() - bf_int64_col_filtered = bf_int64_col[bf_int64_col_filter] - bf_result_col = bf_int64_col_filtered.apply(square) - bf_result = ( - bf_int64_col_filtered.to_frame().assign(result=bf_result_col).to_pandas() - ) - - pd_int64_col = scalars_pandas_df["int64_col"] - pd_int64_col_filter = pd_int64_col.notnull() - pd_int64_col_filtered = pd_int64_col[pd_int64_col_filter] - pd_result_col = pd_int64_col_filtered.apply(lambda x: x * x) - # TODO(shobs): Figure why pandas .apply() changes the dtype, i.e. - # pd_int64_col_filtered.dtype is Int64Dtype() - # pd_int64_col_filtered.apply(lambda x: x * x).dtype is int64. - # For this test let's force the pandas dtype to be same as bigframes' dtype. - pd_result_col = pd_result_col.astype(pandas.Int64Dtype()) - pd_result = pd_int64_col_filtered.to_frame().assign(result=pd_result_col) - - assert_frame_equal(bf_result, pd_result) - finally: - # clean up the gcp assets created for the remote function - cleanup_function_assets(square, session.bqclient, session.cloudfunctionsclient) - - -@pytest.mark.flaky(retries=2, delay=120) -def test_remote_function_explicit_with_bigframes_series( - session, scalars_dfs, dataset_id, bq_cf_connection -): - try: - - def add_one(x): - return x + 1 - - remote_add_one = session.remote_function( - # Make sure that the input/output types can be used positionally. - # This avoids the worst of the breaking change from 1.x to 2.x. - [int], - int, - dataset_id, - bigquery_connection=bq_cf_connection, - reuse=False, - cloud_function_service_account="default", - )(add_one) - - scalars_df, scalars_pandas_df = scalars_dfs - - bf_int64_col = scalars_df["int64_col"] - bf_int64_col_filter = bf_int64_col.notnull() - bf_int64_col_filtered = bf_int64_col[bf_int64_col_filter] - bf_result_col = bf_int64_col_filtered.apply(remote_add_one) - bf_result = ( - bf_int64_col_filtered.to_frame().assign(result=bf_result_col).to_pandas() - ) - - pd_int64_col = scalars_pandas_df["int64_col"] - pd_int64_col_filter = pd_int64_col.notnull() - pd_int64_col_filtered = pd_int64_col[pd_int64_col_filter] - pd_result_col = pd_int64_col_filtered.apply(add_one) - # TODO(shobs): Figure why pandas .apply() changes the dtype, e.g. - # pd_int64_col_filtered.dtype is Int64Dtype() - # pd_int64_col_filtered.apply(lambda x: x).dtype is int64. - # For this test let's force the pandas dtype to be same as bigframes' dtype. - pd_result_col = pd_result_col.astype(pandas.Int64Dtype()) - pd_result = pd_int64_col_filtered.to_frame().assign(result=pd_result_col) - - assert_frame_equal(bf_result, pd_result) - finally: - # clean up the gcp assets created for the remote function - cleanup_function_assets( - remote_add_one, session.bqclient, session.cloudfunctionsclient - ) - - -@pytest.mark.parametrize( - ("input_types"), - [ - pytest.param([int], id="list-of-int"), - pytest.param(int, id="int"), - ], -) -@pytest.mark.flaky(retries=2, delay=120) -def test_remote_function_input_types(session, scalars_dfs, input_types): - try: - - def add_one(x): - return x + 1 - - remote_add_one = session.remote_function( - # Make sure that the input/output types can be used positionally. - # This avoids the worst of the breaking change from 1.x to 2.x. - input_types, - int, - reuse=False, - cloud_function_service_account="default", - )(add_one) - assert remote_add_one.input_dtypes == (bigframes.dtypes.INT_DTYPE,) - - scalars_df, scalars_pandas_df = scalars_dfs - - bf_result = scalars_df.int64_too.map(remote_add_one).to_pandas() - pd_result = scalars_pandas_df.int64_too.map(add_one) - - pandas.testing.assert_series_equal(bf_result, pd_result, check_dtype=False) - finally: - # clean up the gcp assets created for the remote function - cleanup_function_assets( - remote_add_one, session.bqclient, session.cloudfunctionsclient - ) - - -@pytest.mark.flaky(retries=2, delay=120) -def test_remote_function_explicit_dataset_not_created( - session, - scalars_dfs, - dataset_id_not_created, - bq_cf_connection, -): - try: - - @session.remote_function( - # Make sure that the input/output types can be used positionally. - # This avoids the worst of the breaking change from 1.x to 2.x. - [int], - int, - dataset=dataset_id_not_created, - bigquery_connection=bq_cf_connection, - reuse=False, - cloud_function_service_account="default", - ) - def square(x): - return x * x - - scalars_df, scalars_pandas_df = scalars_dfs - - bf_int64_col = scalars_df["int64_col"] - bf_int64_col_filter = bf_int64_col.notnull() - bf_int64_col_filtered = bf_int64_col[bf_int64_col_filter] - bf_result_col = bf_int64_col_filtered.apply(square) - bf_result = ( - bf_int64_col_filtered.to_frame().assign(result=bf_result_col).to_pandas() - ) - - pd_int64_col = scalars_pandas_df["int64_col"] - pd_int64_col_filter = pd_int64_col.notnull() - pd_int64_col_filtered = pd_int64_col[pd_int64_col_filter] - pd_result_col = pd_int64_col_filtered.apply(lambda x: x * x) - # TODO(shobs): Figure why pandas .apply() changes the dtype, i.e. - # pd_int64_col_filtered.dtype is Int64Dtype() - # pd_int64_col_filtered.apply(lambda x: x * x).dtype is int64. - # For this test let's force the pandas dtype to be same as bigframes' dtype. - pd_result_col = pd_result_col.astype(pandas.Int64Dtype()) - pd_result = pd_int64_col_filtered.to_frame().assign(result=pd_result_col) - - assert_frame_equal(bf_result, pd_result) - finally: - # clean up the gcp assets created for the remote function - cleanup_function_assets(square, session.bqclient, session.cloudfunctionsclient) - - -@pytest.mark.flaky(retries=2, delay=120) -def test_remote_udf_referring_outside_var( - session, scalars_dfs, dataset_id, bq_cf_connection -): - try: - POSITIVE_SIGN = 1 - NEGATIVE_SIGN = -1 - NO_SIGN = 0 - - def sign(num): - if num > 0: - return POSITIVE_SIGN - elif num < 0: - return NEGATIVE_SIGN - return NO_SIGN - - remote_sign = session.remote_function( - # Make sure that the input/output types can be used positionally. - # This avoids the worst of the breaking change from 1.x to 2.x. - [int], - int, - dataset_id, - bigquery_connection=bq_cf_connection, - reuse=False, - cloud_function_service_account="default", - )(sign) - - scalars_df, scalars_pandas_df = scalars_dfs - - bf_int64_col = scalars_df["int64_col"] - bf_int64_col_filter = bf_int64_col.notnull() - bf_int64_col_filtered = bf_int64_col[bf_int64_col_filter] - bf_result_col = bf_int64_col_filtered.apply(remote_sign) - bf_result = ( - bf_int64_col_filtered.to_frame().assign(result=bf_result_col).to_pandas() - ) - - pd_int64_col = scalars_pandas_df["int64_col"] - pd_int64_col_filter = pd_int64_col.notnull() - pd_int64_col_filtered = pd_int64_col[pd_int64_col_filter] - pd_result_col = pd_int64_col_filtered.apply(sign) - # TODO(shobs): Figure why pandas .apply() changes the dtype, e.g. - # pd_int64_col_filtered.dtype is Int64Dtype() - # pd_int64_col_filtered.apply(lambda x: x).dtype is int64. - # For this test let's force the pandas dtype to be same as bigframes' dtype. - pd_result_col = pd_result_col.astype(pandas.Int64Dtype()) - pd_result = pd_int64_col_filtered.to_frame().assign(result=pd_result_col) - - assert_frame_equal(bf_result, pd_result) - finally: - # clean up the gcp assets created for the remote function - cleanup_function_assets( - remote_sign, session.bqclient, session.cloudfunctionsclient - ) - - -@pytest.mark.flaky(retries=2, delay=120) -def test_remote_udf_referring_outside_import( - session, scalars_dfs, dataset_id, bq_cf_connection -): - try: - import math as mymath - - def circumference(radius): - return 2 * mymath.pi * radius - - remote_circumference = session.remote_function( - # Make sure that the input/output types can be used positionally. - # This avoids the worst of the breaking change from 1.x to 2.x. - [float], - float, - dataset_id, - bigquery_connection=bq_cf_connection, - reuse=False, - cloud_function_service_account="default", - )(circumference) - - scalars_df, scalars_pandas_df = scalars_dfs - - bf_float64_col = scalars_df["float64_col"] - bf_float64_col_filter = bf_float64_col.notnull() - bf_float64_col_filtered = bf_float64_col[bf_float64_col_filter] - bf_result_col = bf_float64_col_filtered.apply(remote_circumference) - bf_result = ( - bf_float64_col_filtered.to_frame().assign(result=bf_result_col).to_pandas() - ) - - pd_float64_col = scalars_pandas_df["float64_col"] - pd_float64_col_filter = pd_float64_col.notnull() - pd_float64_col_filtered = pd_float64_col[pd_float64_col_filter] - pd_result_col = pd_float64_col_filtered.apply(circumference) - # TODO(shobs): Figure why pandas .apply() changes the dtype, e.g. - # pd_float64_col_filtered.dtype is Float64Dtype() - # pd_float64_col_filtered.apply(lambda x: x).dtype is float64. - # For this test let's force the pandas dtype to be same as bigframes' dtype. - pd_result_col = pd_result_col.astype(pandas.Float64Dtype()) - pd_result = pd_float64_col_filtered.to_frame().assign(result=pd_result_col) - - assert_frame_equal(bf_result, pd_result) - finally: - # clean up the gcp assets created for the remote function - cleanup_function_assets( - remote_circumference, session.bqclient, session.cloudfunctionsclient - ) - - -@pytest.mark.flaky(retries=2, delay=120) -def test_remote_udf_referring_global_var_and_import( - session, scalars_dfs, dataset_id, bq_cf_connection -): - try: - - def find_team(num): - boundary = (math.pi + math.e) / 2 - if num >= boundary: - return _team_euler - return _team_pi - - remote_find_team = session.remote_function( - input_types=[float], - output_type=str, - dataset=dataset_id, - bigquery_connection=bq_cf_connection, - reuse=False, - cloud_function_service_account="default", - )(find_team) - - scalars_df, scalars_pandas_df = scalars_dfs - - bf_float64_col = scalars_df["float64_col"] - bf_float64_col_filter = bf_float64_col.notnull() - bf_float64_col_filtered = bf_float64_col[bf_float64_col_filter] - bf_result_col = bf_float64_col_filtered.apply(remote_find_team) - bf_result = ( - bf_float64_col_filtered.to_frame().assign(result=bf_result_col).to_pandas() - ) - - pd_float64_col = scalars_pandas_df["float64_col"] - pd_float64_col_filter = pd_float64_col.notnull() - pd_float64_col_filtered = pd_float64_col[pd_float64_col_filter] - pd_result_col = pd_float64_col_filtered.apply(find_team) - # TODO(shobs): Figure if the dtype mismatch is by design: - # bf_result.dtype: string[pyarrow] - # pd_result.dtype: dtype('O'). - # For this test let's force the pandas dtype to be same as bigframes' dtype. - pd_result_col = pd_result_col.astype(pandas.StringDtype(storage="pyarrow")) - pd_result = pd_float64_col_filtered.to_frame().assign(result=pd_result_col) - - assert_frame_equal(bf_result, pd_result) - finally: - # clean up the gcp assets created for the remote function - cleanup_function_assets( - remote_find_team, session.bqclient, session.cloudfunctionsclient - ) - - -@pytest.mark.flaky(retries=2, delay=120) -def test_remote_function_restore_with_bigframes_series( - session, - scalars_dfs, - dataset_id, - bq_cf_connection, -): - try: - - def add_one(x): - return x + 1 - - # Make a unique udf - add_one_uniq, add_one_uniq_dir = make_uniq_udf(add_one) - - # The first time both the cloud function and the bq remote function don't - # exist and would be created - remote_add_one = session.remote_function( - input_types=[int], - output_type=int, - dataset=dataset_id, - bigquery_connection=bq_cf_connection, - reuse=True, - cloud_function_service_account="default", - )(add_one_uniq) - - assert remote_add_one.bigframes_cloud_function is not None - add_one_uniq_cf_name = remote_add_one.bigframes_cloud_function.split("/")[-1] - - # There should have been excactly one cloud function created at this point - cloud_functions = list( - get_cloud_functions( - session.cloudfunctionsclient, - session.bqclient.project, - session.bqclient.location, - name=add_one_uniq_cf_name, - ) - ) - assert len(cloud_functions) == 1 - - # We will test this twice - def inner_test(): - scalars_df, scalars_pandas_df = scalars_dfs - - bf_int64_col = scalars_df["int64_col"] - bf_int64_col_filter = bf_int64_col.notnull() - bf_int64_col_filtered = bf_int64_col[bf_int64_col_filter] - bf_result_col = bf_int64_col_filtered.apply(remote_add_one) - bf_result = ( - bf_int64_col_filtered.to_frame() - .assign(result=bf_result_col) - .to_pandas() - ) - - pd_int64_col = scalars_pandas_df["int64_col"] - pd_int64_col_filter = pd_int64_col.notnull() - pd_int64_col_filtered = pd_int64_col[pd_int64_col_filter] - pd_result_col = pd_int64_col_filtered.apply(add_one_uniq) - # TODO(shobs): Figure why pandas .apply() changes the dtype, i.e. - # pd_int64_col_filtered.dtype is Int64Dtype() - # pd_int64_col_filtered.apply(lambda x: x * x).dtype is int64. - # For this test let's force the pandas dtype to be same as bigframes' dtype. - pd_result_col = pd_result_col.astype(pandas.Int64Dtype()) - pd_result = pd_int64_col_filtered.to_frame().assign(result=pd_result_col) - - assert_frame_equal(bf_result, pd_result) - - # Test that the remote function works as expected - inner_test() - - # Let's delete the cloud function while not touching the bq remote function - delete_operation = delete_cloud_function( - session.cloudfunctionsclient, cloud_functions[0].name - ) - delete_operation.result() - assert delete_operation.done() - - # There should be no cloud functions at this point for the uniq udf - cloud_functions = list( - get_cloud_functions( - session.cloudfunctionsclient, - session.bqclient.project, - session.bqclient.location, - name=add_one_uniq_cf_name, - ) - ) - assert len(cloud_functions) == 0 - - # The second time bigframes detects that the required cloud function doesn't - # exist even though the remote function exists, and goes ahead and recreates - # the cloud function - remote_add_one = session.remote_function( - input_types=[int], - output_type=int, - dataset=dataset_id, - bigquery_connection=bq_cf_connection, - reuse=True, - cloud_function_service_account="default", - )(add_one_uniq) - - # There should be excactly one cloud function again - cloud_functions = list( - get_cloud_functions( - session.cloudfunctionsclient, - session.bqclient.project, - session.bqclient.location, - name=add_one_uniq_cf_name, - ) - ) - assert len(cloud_functions) == 1 - - # Test again after the cloud function is restored that the remote function - # works as expected - inner_test() - - # clean up the temp code - shutil.rmtree(add_one_uniq_dir) - finally: - # clean up the gcp assets created for the remote function - cleanup_function_assets( - remote_add_one, session.bqclient, session.cloudfunctionsclient - ) - - -@pytest.mark.flaky(retries=2, delay=120) -def test_remote_udf_mask_default_value( - session, scalars_dfs, dataset_id, bq_cf_connection -): - try: - - def is_odd(num): - flag = False - try: - flag = num % 2 == 1 - except TypeError: - pass - return flag - - is_odd_remote = session.remote_function( - input_types=[int], - output_type=bool, - dataset=dataset_id, - bigquery_connection=bq_cf_connection, - reuse=False, - cloud_function_service_account="default", - )(is_odd) - - scalars_df, scalars_pandas_df = scalars_dfs - - bf_int64_col = scalars_df["int64_col"] - bf_result_col = bf_int64_col.mask(is_odd_remote) - bf_result = bf_int64_col.to_frame().assign(result=bf_result_col).to_pandas() - - pd_int64_col = scalars_pandas_df["int64_col"] - pd_result_col = pd_int64_col.mask(is_odd) - pd_result = pd_int64_col.to_frame().assign(result=pd_result_col) - - assert_frame_equal(bf_result, pd_result) - finally: - # clean up the gcp assets created for the remote function - cleanup_function_assets( - is_odd_remote, session.bqclient, session.cloudfunctionsclient - ) - - -@pytest.mark.flaky(retries=2, delay=120) -def test_remote_udf_mask_custom_value( - session, scalars_dfs, dataset_id, bq_cf_connection -): - try: - - def is_odd(num): - flag = False - try: - flag = num % 2 == 1 - except TypeError: - pass - return flag - - is_odd_remote = session.remote_function( - input_types=[int], - output_type=bool, - dataset=dataset_id, - bigquery_connection=bq_cf_connection, - reuse=False, - cloud_function_service_account="default", - )(is_odd) - - scalars_df, scalars_pandas_df = scalars_dfs - - # TODO(shobs): Revisit this test when NA handling of pandas' Series.mask is - # fixed https://github.com/pandas-dev/pandas/issues/52955, - # for now filter out the nulls and test the rest - bf_int64_col = scalars_df["int64_col"] - bf_result_col = bf_int64_col[bf_int64_col.notnull()].mask(is_odd_remote, -1) - bf_result = bf_int64_col.to_frame().assign(result=bf_result_col).to_pandas() - - pd_int64_col = scalars_pandas_df["int64_col"] - pd_result_col = pd_int64_col[pd_int64_col.notnull()].mask(is_odd, -1) - pd_result = pd_int64_col.to_frame().assign(result=pd_result_col) - - assert_frame_equal(bf_result, pd_result) - finally: - # clean up the gcp assets created for the remote function - cleanup_function_assets( - is_odd_remote, session.bqclient, session.cloudfunctionsclient - ) - - -@pytest.mark.flaky(retries=2, delay=120) -def test_remote_udf_lambda(session, scalars_dfs, dataset_id, bq_cf_connection): - try: - add_one_lambda = lambda x: x + 1 # noqa: E731 - - add_one_lambda_remote = session.remote_function( - input_types=[int], - output_type=int, - dataset=dataset_id, - bigquery_connection=bq_cf_connection, - reuse=False, - cloud_function_service_account="default", - )(add_one_lambda) - - scalars_df, scalars_pandas_df = scalars_dfs - - bf_int64_col = scalars_df["int64_col"] - bf_int64_col_filter = bf_int64_col.notnull() - bf_int64_col_filtered = bf_int64_col[bf_int64_col_filter] - bf_result_col = bf_int64_col_filtered.apply(add_one_lambda_remote) - bf_result = ( - bf_int64_col_filtered.to_frame().assign(result=bf_result_col).to_pandas() - ) - - pd_int64_col = scalars_pandas_df["int64_col"] - pd_int64_col_filter = pd_int64_col.notnull() - pd_int64_col_filtered = pd_int64_col[pd_int64_col_filter] - pd_result_col = pd_int64_col_filtered.apply(add_one_lambda) - # TODO(shobs): Figure why pandas .apply() changes the dtype, i.e. - # pd_int64_col_filtered.dtype is Int64Dtype() - # pd_int64_col_filtered.apply(lambda x: x).dtype is int64. - # For this test let's force the pandas dtype to be same as bigframes' dtype. - pd_result_col = pd_result_col.astype(pandas.Int64Dtype()) - pd_result = pd_int64_col_filtered.to_frame().assign(result=pd_result_col) - - assert_frame_equal(bf_result, pd_result) - finally: - # clean up the gcp assets created for the remote function - cleanup_function_assets( - add_one_lambda_remote, session.bqclient, session.cloudfunctionsclient - ) - - -@pytest.mark.flaky(retries=2, delay=120) -def test_remote_function_with_explicit_name( - session, scalars_dfs, dataset_id, bq_cf_connection -): - try: - - def square(x): - return x * x - - prefixer = test_utils.prefixer.Prefixer(square.__name__, "") - rf_name = prefixer.create_prefix() - expected_remote_function = f"{dataset_id}.{rf_name}" - - # Initially the expected BQ remote function should not exist - with pytest.raises(google.api_core.exceptions.NotFound): - session.bqclient.get_routine(expected_remote_function) - - # Create the remote function with the name provided explicitly - square_remote = session.remote_function( - input_types=[int], - output_type=int, - dataset=dataset_id, - bigquery_connection=bq_cf_connection, - reuse=False, - name=rf_name, - cloud_function_service_account="default", - )(square) - - # The remote function should reflect the explicitly provided name - assert square_remote.bigframes_remote_function == expected_remote_function - assert square_remote.bigframes_bigquery_function == expected_remote_function - - # Now the expected BQ remote function should exist - session.bqclient.get_routine(expected_remote_function) - - # The behavior of the created remote function should be as expected - scalars_df, scalars_pandas_df = scalars_dfs - - bf_int64_col = scalars_df["int64_too"] - bf_result_col = bf_int64_col.apply(square_remote) - bf_result = bf_int64_col.to_frame().assign(result=bf_result_col).to_pandas() - - pd_int64_col = scalars_pandas_df["int64_too"] - pd_result_col = pd_int64_col.apply(square) - # TODO(shobs): Figure why pandas .apply() changes the dtype, i.e. - # pd_int64_col.dtype is Int64Dtype() - # pd_int64_col.apply(square).dtype is int64. - # For this test let's force the pandas dtype to be same as bigframes' dtype. - pd_result_col = pd_result_col.astype(pandas.Int64Dtype()) - pd_result = pd_int64_col.to_frame().assign(result=pd_result_col) - - assert_frame_equal(bf_result, pd_result) - finally: - # clean up the gcp assets created for the remote function - cleanup_function_assets( - square_remote, session.bqclient, session.cloudfunctionsclient - ) - - -@pytest.mark.flaky(retries=2, delay=120) -def test_remote_function_with_external_package_dependencies( - session, scalars_dfs, dataset_id, bq_cf_connection -): - try: - # The return type hint in this function's signature has conflict. The - # `output_type` argument from remote_function decorator takes precedence - # and will be used instead. - def pd_np_foo(x) -> None: - import numpy as mynp - import pandas as mypd - - return mypd.Series([x, mynp.sqrt(mynp.abs(x))]).sum() - - with warnings.catch_warnings(record=True) as record: - # Create the remote function with the name provided explicitly - pd_np_foo_remote = session.remote_function( - input_types=[int], - output_type=float, - dataset=dataset_id, - bigquery_connection=bq_cf_connection, - reuse=False, - packages=["numpy", "pandas >= 2.0.0"], - cloud_function_service_account="default", - )(pd_np_foo) - - input_type_warning = "Conflicting input types detected" - assert not any(input_type_warning in str(warning.message) for warning in record) - return_type_warning = "Conflicting return type detected" - assert any(return_type_warning in str(warning.message) for warning in record) - - # The behavior of the created remote function should be as expected - scalars_df, scalars_pandas_df = scalars_dfs - - bf_int64_col = scalars_df["int64_too"] - bf_result_col = bf_int64_col.apply(pd_np_foo_remote) - bf_result = bf_int64_col.to_frame().assign(result=bf_result_col).to_pandas() - - pd_int64_col = scalars_pandas_df["int64_too"] - pd_result_col = pd_int64_col.apply(pd_np_foo) - pd_result = pd_int64_col.to_frame().assign(result=pd_result_col) - - # pandas result is non-nullable type float64, make it Float64 before - # comparing for the purpose of this test - pd_result.result = pd_result.result.astype(pandas.Float64Dtype()) - - assert_frame_equal(bf_result, pd_result) - finally: - # clean up the gcp assets created for the remote function - cleanup_function_assets( - pd_np_foo_remote, session.bqclient, session.cloudfunctionsclient - ) - - -@pytest.mark.flaky(retries=2, delay=120) -def test_remote_function_with_explicit_name_reuse( - session, scalars_dfs, dataset_id, bq_cf_connection -): - try: - dirs_to_cleanup = [] - - # Define a user code - def square(x): - return x * x - - # Make it a unique udf - square_uniq, square_uniq_dir = make_uniq_udf(square) - dirs_to_cleanup.append(square_uniq_dir) - - # Define a common routine which accepts a remote function and the - # corresponding user defined function and tests that bigframes bahavior - # on the former is in parity with the pandas behaviour on the latter - def test_internal(rf, udf): - # The behavior of the created remote function should be as expected - scalars_df, scalars_pandas_df = scalars_dfs - - bf_int64_col = scalars_df["int64_too"] - bf_result_col = bf_int64_col.apply(rf) - bf_result = bf_int64_col.to_frame().assign(result=bf_result_col).to_pandas() - - pd_int64_col = scalars_pandas_df["int64_too"] - pd_result_col = pd_int64_col.apply(udf) - # TODO(shobs): Figure why pandas .apply() changes the dtype, i.e. - # pd_int64_col.dtype is Int64Dtype() - # pd_int64_col.apply(square).dtype is int64. - # For this test let's force the pandas dtype to be same as bigframes' dtype. - pd_result_col = pd_result_col.astype(pandas.Int64Dtype()) - pd_result = pd_int64_col.to_frame().assign(result=pd_result_col) - - assert_frame_equal(bf_result, pd_result) - - # Create an explicit name for the remote function - prefixer = test_utils.prefixer.Prefixer("foo", "") - rf_name = prefixer.create_prefix() - expected_remote_function = f"{dataset_id}.{rf_name}" - - # Initially the expected BQ remote function should not exist - with pytest.raises(google.api_core.exceptions.NotFound): - session.bqclient.get_routine(expected_remote_function) - - # Create a new remote function with the name provided explicitly - square_remote1 = session.remote_function( - input_types=[int], - output_type=int, - dataset=dataset_id, - bigquery_connection=bq_cf_connection, - name=rf_name, - cloud_function_service_account="default", - )(square_uniq) - - # The remote function should reflect the explicitly provided name - assert square_remote1.bigframes_remote_function == expected_remote_function - assert square_remote1.bigframes_bigquery_function == expected_remote_function - - # Now the expected BQ remote function should exist - routine = session.bqclient.get_routine(expected_remote_function) - square_remote1_created = routine.created - square_remote1_cf_updated = session.cloudfunctionsclient.get_function( - name=square_remote1.bigframes_cloud_function - ).update_time - - # Test pandas parity with square udf - test_internal(square_remote1, square) - - # Now Create another remote function with the same name provided - # explicitly. Since reuse is True by default, the previously created - # remote function with the same name will be reused. - square_remote2 = session.remote_function( - input_types=[int], - output_type=int, - dataset=dataset_id, - bigquery_connection=bq_cf_connection, - name=rf_name, - cloud_function_service_account="default", - )(square_uniq) - - # The new remote function should still reflect the explicitly provided name - assert square_remote2.bigframes_remote_function == expected_remote_function - assert square_remote2.bigframes_bigquery_function == expected_remote_function - - # The expected BQ remote function should still exist - routine = session.bqclient.get_routine(expected_remote_function) - square_remote2_created = routine.created - square_remote2_cf_updated = session.cloudfunctionsclient.get_function( - name=square_remote2.bigframes_cloud_function - ).update_time - - # The new remote function should reflect that the previous BQ remote - # function and the cloud function were reused instead of creating anew - assert square_remote2_created == square_remote1_created - assert ( - square_remote2.bigframes_cloud_function - == square_remote1.bigframes_cloud_function - ) - assert square_remote2_cf_updated == square_remote1_cf_updated - - # Test again that the new remote function is actually same as the - # previous remote function - test_internal(square_remote2, square) - - # Now define a different user code - def plusone(x): - return x + 1 - - # Make it a unique udf - plusone_uniq, plusone_uniq_dir = make_uniq_udf(plusone) - dirs_to_cleanup.append(plusone_uniq_dir) - - # Now Create a third remote function with the same name provided - # explicitly. Even though reuse is True by default, the previously - # created remote function with the same name should not be reused since - # this time it is a different user code. - plusone_remote = session.remote_function( - input_types=[int], - output_type=int, - dataset=dataset_id, - bigquery_connection=bq_cf_connection, - name=rf_name, - cloud_function_service_account="default", - )(plusone_uniq) - - # The new remote function should still reflect the explicitly provided name - assert plusone_remote.bigframes_remote_function == expected_remote_function - assert plusone_remote.bigframes_bigquery_function == expected_remote_function - - # The expected BQ remote function should still exist - routine = session.bqclient.get_routine(expected_remote_function) - plusone_remote_created = routine.created - plusone_remote_cf_updated = session.cloudfunctionsclient.get_function( - name=plusone_remote.bigframes_cloud_function - ).update_time - - # The new remote function should reflect that the previous BQ remote - # function and the cloud function were NOT reused, instead were created - # anew - assert plusone_remote_created > square_remote2_created - assert ( - plusone_remote.bigframes_cloud_function - != square_remote2.bigframes_cloud_function - ) - assert plusone_remote_cf_updated > square_remote2_cf_updated - - # Test again that the new remote function is equivalent to the new user - # defined function - test_internal(plusone_remote, plusone) - finally: - # clean up the gcp assets created for the remote function - cleanup_function_assets( - square_remote1, session.bqclient, session.cloudfunctionsclient - ) - cleanup_function_assets( - square_remote2, session.bqclient, session.cloudfunctionsclient - ) - cleanup_function_assets( - plusone_remote, session.bqclient, session.cloudfunctionsclient - ) - for dir_ in dirs_to_cleanup: - shutil.rmtree(dir_) - - -@pytest.mark.flaky(retries=2, delay=120) -def test_remote_function_via_session_context_connection_setter( - scalars_dfs, dataset_id, bq_cf_connection -): - # Creating a session scoped only to this test as we would be setting a - # property in it - context = bigframes.BigQueryOptions() - context.bq_connection = bq_cf_connection - session = bigframes.connect(context) - - try: - # Without an explicit bigquery connection, the one present in Session, - # set via context setter would be used. Without an explicit `reuse` the - # default behavior of reuse=True will take effect. Please note that the - # udf is same as the one used in other tests in this file so the underlying - # cloud function would be common with reuse=True. Since we are using a - # unique dataset_id, even though the cloud function would be reused, the bq - # remote function would still be created, making use of the bq connection - # set in the BigQueryOptions above. - @session.remote_function( - input_types=[int], - output_type=int, - dataset=dataset_id, - reuse=False, - cloud_function_service_account="default", - ) - def square(x): - return x * x - - scalars_df, scalars_pandas_df = scalars_dfs - - bf_int64_col = scalars_df["int64_col"] - bf_int64_col_filter = bf_int64_col.notnull() - bf_int64_col_filtered = bf_int64_col[bf_int64_col_filter] - bf_result_col = bf_int64_col_filtered.apply(square) - bf_result = ( - bf_int64_col_filtered.to_frame().assign(result=bf_result_col).to_pandas() - ) - - pd_int64_col = scalars_pandas_df["int64_col"] - pd_int64_col_filter = pd_int64_col.notnull() - pd_int64_col_filtered = pd_int64_col[pd_int64_col_filter] - pd_result_col = pd_int64_col_filtered.apply(lambda x: x * x) - # TODO(shobs): Figure why pandas .apply() changes the dtype, i.e. - # pd_int64_col_filtered.dtype is Int64Dtype() - # pd_int64_col_filtered.apply(lambda x: x * x).dtype is int64. - # For this test let's force the pandas dtype to be same as bigframes' dtype. - pd_result_col = pd_result_col.astype(pandas.Int64Dtype()) - pd_result = pd_int64_col_filtered.to_frame().assign(result=pd_result_col) - - assert_frame_equal(bf_result, pd_result) - finally: - # clean up the gcp assets created for the remote function - cleanup_function_assets(square, session.bqclient, session.cloudfunctionsclient) - - -@pytest.mark.flaky(retries=2, delay=120) -def test_remote_function_default_connection(session, scalars_dfs, dataset_id): - try: - - @session.remote_function( - input_types=[int], - output_type=int, - dataset=dataset_id, - reuse=False, - cloud_function_service_account="default", - ) - def square(x): - return x * x - - scalars_df, scalars_pandas_df = scalars_dfs - - bf_int64_col = scalars_df["int64_col"] - bf_int64_col_filter = bf_int64_col.notnull() - bf_int64_col_filtered = bf_int64_col[bf_int64_col_filter] - bf_result_col = bf_int64_col_filtered.apply(square) - bf_result = ( - bf_int64_col_filtered.to_frame().assign(result=bf_result_col).to_pandas() - ) - - pd_int64_col = scalars_pandas_df["int64_col"] - pd_int64_col_filter = pd_int64_col.notnull() - pd_int64_col_filtered = pd_int64_col[pd_int64_col_filter] - pd_result_col = pd_int64_col_filtered.apply(lambda x: x * x) - # TODO(shobs): Figure why pandas .apply() changes the dtype, i.e. - # pd_int64_col_filtered.dtype is Int64Dtype() - # pd_int64_col_filtered.apply(lambda x: x * x).dtype is int64. - # For this test let's force the pandas dtype to be same as bigframes' dtype. - pd_result_col = pd_result_col.astype(pandas.Int64Dtype()) - pd_result = pd_int64_col_filtered.to_frame().assign(result=pd_result_col) - - assert_frame_equal(bf_result, pd_result) - finally: - # clean up the gcp assets created for the remote function - cleanup_function_assets(square, session.bqclient, session.cloudfunctionsclient) - - -@pytest.mark.flaky(retries=2, delay=120) -def test_remote_function_runtime_error(session, scalars_dfs, dataset_id): - try: - - @session.remote_function( - input_types=[int], - output_type=int, - dataset=dataset_id, - reuse=False, - cloud_function_service_account="default", - ) - def square(x): - return x * x - - scalars_df, _ = scalars_dfs - - with pytest.raises( - google.api_core.exceptions.BadRequest, - match="400.*errorMessage.*unsupported operand type", - ): - # int64_col has nulls which should cause error in square - scalars_df["int64_col"].apply(square).to_pandas() - finally: - # clean up the gcp assets created for the remote function - cleanup_function_assets(square, session.bqclient, session.cloudfunctionsclient) - - -@pytest.mark.flaky(retries=2, delay=120) -def test_remote_function_anonymous_dataset(session, scalars_dfs): - try: - # This usage of remote_function is expected to create the remote - # function in the bigframes session's anonymous dataset. Use reuse=False - # param to make sure parallel instances of the test don't step over each - # other due to the common anonymous dataset. - @session.remote_function( - input_types=[int], - output_type=int, - reuse=False, - cloud_function_service_account="default", - ) - def square(x): - return x * x - - assert ( - bigquery.Routine(square.bigframes_bigquery_function).dataset_id - == session._anonymous_dataset.dataset_id - ) - - scalars_df, scalars_pandas_df = scalars_dfs - - bf_int64_col = scalars_df["int64_col"] - bf_int64_col_filter = bf_int64_col.notnull() - bf_int64_col_filtered = bf_int64_col[bf_int64_col_filter] - bf_result_col = bf_int64_col_filtered.apply(square) - bf_result = ( - bf_int64_col_filtered.to_frame().assign(result=bf_result_col).to_pandas() - ) - - pd_int64_col = scalars_pandas_df["int64_col"] - pd_int64_col_filter = pd_int64_col.notnull() - pd_int64_col_filtered = pd_int64_col[pd_int64_col_filter] - pd_result_col = pd_int64_col_filtered.apply(lambda x: x * x) - # TODO(shobs): Figure why pandas .apply() changes the dtype, i.e. - # pd_int64_col_filtered.dtype is Int64Dtype() - # pd_int64_col_filtered.apply(lambda x: x * x).dtype is int64. - # For this test let's force the pandas dtype to be same as bigframes' dtype. - pd_result_col = pd_result_col.astype(pandas.Int64Dtype()) - pd_result = pd_int64_col_filtered.to_frame().assign(result=pd_result_col) - - assert_frame_equal(bf_result, pd_result) - finally: - # clean up the gcp assets created for the remote function - cleanup_function_assets(square, session.bqclient, session.cloudfunctionsclient) - - -@pytest.mark.flaky(retries=2, delay=120) -def test_remote_function_via_session_custom_sa(scalars_pandas_df_index): - # TODO(shobs): Automate the following set-up during testing in the test project. - # - # For upfront convenience, the following set up has been statically created - # in the project bigfrmames-dev-perf via cloud console: - # - # 1. Create a service account bigframes-dev-perf-1@bigframes-dev-perf.iam.gserviceaccount.com as per - # https://cloud.google.com/iam/docs/service-accounts-create#iam-service-accounts-create-console - # 2. Give necessary roles as per - # https://cloud.google.com/functions/docs/reference/iam/roles#additional-configuration - # - project = "bigframes-dev-perf" - gcf_service_account = ( - "bigframes-dev-perf-1@bigframes-dev-perf.iam.gserviceaccount.com" - ) - - rf_session = bigframes.Session(context=bigframes.BigQueryOptions(project=project)) - - try: - - @rf_session.remote_function( - input_types=[int], - output_type=int, - reuse=False, - cloud_function_service_account=gcf_service_account, - cloud_function_ingress_settings="internal-and-gclb", - ) - def double_num(x): - if x is None: - return x - return x + x - - # assert that the GCF is created with the intended SA - gcf = rf_session.cloudfunctionsclient.get_function( - name=double_num.bigframes_cloud_function - ) - assert gcf.service_config.service_account_email == gcf_service_account - - # assert that the function works as expected on data - - bf_int64_col = rf_session.read_pandas(scalars_pandas_df_index.int64_col) - bf_result_col = bf_int64_col.apply(double_num) - bf_result = bf_int64_col.to_frame().assign(result=bf_result_col).to_pandas() - - pd_int64_col = scalars_pandas_df_index.int64_col - pd_result_col = pd_int64_col.apply(lambda x: x if x is None else x + x) - pd_result = pd_int64_col.to_frame().assign(result=pd_result_col) - - assert_frame_equal(bf_result, pd_result, check_dtype=False) - finally: - # clean up the gcp assets created for the remote function - cleanup_function_assets( - double_num, rf_session.bqclient, rf_session.cloudfunctionsclient - ) - - -@pytest.mark.parametrize( - ("set_build_service_account"), - [ - pytest.param( - "projects/bigframes-dev-perf/serviceAccounts/bigframes-dev-perf-1@bigframes-dev-perf.iam.gserviceaccount.com", - id="fully-qualified-sa", - ), - pytest.param( - "bigframes-dev-perf-1@bigframes-dev-perf.iam.gserviceaccount.com", - id="just-sa-email", - ), - ], -) -@pytest.mark.flaky(retries=2, delay=120) -def test_remote_function_via_session_custom_build_sa( - set_build_service_account, scalars_pandas_df_index -): - # TODO(shobs): Automate the following set-up during testing in the test project. - # - # For upfront convenience, the following set up has been statically created - # in the project bigfrmames-dev-perf via cloud console: - # - # 1. Create a service account bigframes-dev-perf-1@bigframes-dev-perf.iam.gserviceaccount.com as per - # https://cloud.google.com/iam/docs/service-accounts-create#iam-service-accounts-create-console - # 2. Give "Cloud Build Service Account (roles/cloudbuild.builds.builder)" role as per - # https://cloud.google.com/build/docs/cloud-build-service-account#default_permissions_of_the_legacy_service_account - # - project = "bigframes-dev-perf" - expected_build_service_account = "projects/bigframes-dev-perf/serviceAccounts/bigframes-dev-perf-1@bigframes-dev-perf.iam.gserviceaccount.com" - - rf_session = bigframes.Session(context=bigframes.BigQueryOptions(project=project)) - - try: - - @rf_session.remote_function( - input_types=[int], - output_type=int, - reuse=False, - cloud_function_service_account="default", - cloud_build_service_account=set_build_service_account, - cloud_function_ingress_settings="internal-and-gclb", - ) - def double_num(x): - if x is None: - return x - return x + x - - # assert that the GCF is created with the intended SA - gcf = rf_session.cloudfunctionsclient.get_function( - name=double_num.bigframes_cloud_function - ) - assert gcf.build_config.service_account == expected_build_service_account - - bf_int64_col = rf_session.read_pandas(scalars_pandas_df_index.int64_col) - bf_result_col = bf_int64_col.apply(double_num) - bf_result = bf_int64_col.to_frame().assign(result=bf_result_col).to_pandas() - - pd_int64_col = scalars_pandas_df_index.int64_col - pd_result_col = pd_int64_col.apply(lambda x: x if x is None else x + x) - pd_result = pd_int64_col.to_frame().assign(result=pd_result_col) - - assert_frame_equal(bf_result, pd_result, check_dtype=False) - finally: - # clean up the gcp assets created for the remote function - cleanup_function_assets( - double_num, rf_session.bqclient, rf_session.cloudfunctionsclient - ) - - -def test_remote_function_throws_none_cloud_function_service_account(session): - with pytest.raises( - ValueError, - match='^You must provide a user managed cloud_function_service_account, or "default" if you would like to let the default service account be used.$', - ): - session.remote_function(cloud_function_service_account=None) - - -@pytest.mark.flaky(retries=2, delay=120) -def test_remote_function_with_gcf_cmek(): - # TODO(shobs): Automate the following set-up during testing in the test project. - # - # For upfront convenience, the following set up has been statically created - # in the project bigfrmames-dev-perf via cloud console: - # - # 1. Created an encryption key and granting the necessary service accounts - # the required IAM permissions as per https://cloud.google.com/kms/docs/create-key - # 2. Created a docker repository with CMEK (created in step 1) enabled as per - # https://cloud.google.com/artifact-registry/docs/repositories/create-repos#overview - # - project = "bigframes-dev-perf" - cmek = "projects/bigframes-dev-perf/locations/us-central1/keyRings/bigframesKeyRing/cryptoKeys/bigframesKey" - docker_repository = ( - "projects/bigframes-dev-perf/locations/us-central1/repositories/rf-artifacts" - ) - - session = bigframes.Session(context=bigframes.BigQueryOptions(project=project)) - try: - - @session.remote_function( - input_types=[int], - output_type=int, - reuse=False, - cloud_function_service_account="default", - cloud_function_kms_key_name=cmek, - cloud_function_docker_repository=docker_repository, - ) - def square_num(x): - if x is None: - return x - return x * x - - df = pandas.DataFrame({"num": [-1, 0, None, 1]}, dtype="Int64") - bf = session.read_pandas(df) - - bf_result_col = bf["num"].apply(square_num) - bf_result = bf.assign(result=bf_result_col).to_pandas() - - pd_result_col = df["num"].apply(lambda x: x if x is None else x * x) - pd_result = df.assign(result=pd_result_col) - - assert_frame_equal( - bf_result, pd_result, check_dtype=False, check_index_type=False - ) - - # Assert that the GCF is created with the intended SA - gcf = session.cloudfunctionsclient.get_function( - name=square_num.bigframes_cloud_function - ) - assert gcf.kms_key_name == cmek - - # Assert that GCS artifact has CMEK applied - storage_client = storage.Client() - bucket = storage_client.bucket(gcf.build_config.source.storage_source.bucket) - blob = bucket.get_blob(gcf.build_config.source.storage_source.object_) - assert blob.kms_key_name.startswith(cmek) - - finally: - # clean up the gcp assets created for the remote function - cleanup_function_assets( - square_num, session.bqclient, session.cloudfunctionsclient - ) - - -@pytest.mark.flaky(retries=2, delay=120) -def test_remote_function_via_session_vpc(scalars_pandas_df_index): - # TODO(shobs): Automate the following set-up during testing in the test project. - # - # For upfront convenience, the following set up has been statically created - # in the project bigfrmames-dev-perf via cloud console: - # - # 1. Create a vpc connector as per - # https://cloud.google.com/vpc/docs/configure-serverless-vpc-access#gcloud - # - # $ gcloud compute networks vpc-access connectors create bigframes-vpc --project=bigframes-dev-perf --region=us-central1 --range 10.8.0.0/28 - # Create request issued for: [bigframes-vpc] - # Waiting for operation [projects/bigframes-dev-perf/locations/us-central1/operations/f9f90df6-7cf4-4420-8c2f-b3952775dcfb] to complete...done. - # Created connector [bigframes-vpc]. - # - # $ gcloud compute networks vpc-access connectors list --project=bigframes-dev-perf --region=us-central1 - # CONNECTOR_ID REGION NETWORK IP_CIDR_RANGE SUBNET SUBNET_PROJECT MACHINE_TYPE MIN_INSTANCES MAX_INSTANCES MIN_THROUGHPUT MAX_THROUGHPUT STATE - # bigframes-vpc us-central1 default 10.8.0.0/28 e2-micro 2 10 200 1000 READY - - project = "bigframes-dev-perf" - gcf_vpc_connector = "bigframes-vpc" - - rf_session = bigframes.Session(context=bigframes.BigQueryOptions(project=project)) - - try: - - def double_num(x): - if x is None: - return x - return x + x - - double_num_remote = rf_session.remote_function( - input_types=[int], - output_type=int, - reuse=False, - cloud_function_service_account="default", - cloud_function_vpc_connector=gcf_vpc_connector, - cloud_function_vpc_connector_egress_settings="all", - cloud_function_ingress_settings="internal-and-gclb", - )(double_num) - - gcf = rf_session.cloudfunctionsclient.get_function( - name=double_num_remote.bigframes_cloud_function - ) - - # assert that the GCF test_remote_function_via_session_custom_sais created with the intended vpc connector and - # egress settings. - assert gcf.service_config.vpc_connector == gcf_vpc_connector - # The value is since we set - # cloud_function_vpc_connector_egress_settings="all" earlier. - assert gcf.service_config.vpc_connector_egress_settings == 2 - - bf_int64_col = rf_session.read_pandas(scalars_pandas_df_index.int64_col) - bf_result_col = bf_int64_col.apply(double_num_remote) - bf_result = bf_int64_col.to_frame().assign(result=bf_result_col).to_pandas() - - pd_int64_col = scalars_pandas_df_index.int64_col - pd_result_col = pd_int64_col.apply(double_num) - pd_result = pd_int64_col.to_frame().assign(result=pd_result_col) - - assert_frame_equal(bf_result, pd_result, check_dtype=False) - finally: - # clean up the gcp assets created for the remote function - cleanup_function_assets( - double_num_remote, rf_session.bqclient, rf_session.cloudfunctionsclient - ) - - -@pytest.mark.flaky(retries=2, delay=120) -def test_remote_function_no_vpc_connector(session): - def foo(x): - return x - - with pytest.raises( - ValueError, - match="^cloud_function_vpc_connector must be specified before cloud_function_vpc_connector_egress_settings", - ): - session.remote_function( - input_types=[int], - output_type=int, - reuse=False, - cloud_function_service_account="default", - cloud_function_vpc_connector=None, - cloud_function_vpc_connector_egress_settings="all", - cloud_function_ingress_settings="all", - )(foo) - - -@pytest.mark.flaky(retries=2, delay=120) -def test_remote_function_wrong_vpc_egress_value(session): - def foo(x): - return x - - with pytest.raises( - ValueError, - match="^'wrong-egress-value' is not one of the supported vpc egress settings values:", - ): - session.remote_function( - input_types=[int], - output_type=int, - reuse=False, - cloud_function_service_account="default", - cloud_function_vpc_connector="dummy-value", - cloud_function_vpc_connector_egress_settings="wrong-egress-value", - cloud_function_ingress_settings="all", - )(foo) - - -@pytest.mark.parametrize( - ("max_batching_rows"), - [ - 10_000, - None, - ], -) -@pytest.mark.flaky(retries=2, delay=120) -def test_remote_function_max_batching_rows(session, scalars_dfs, max_batching_rows): - try: - - def square(x): - return x * x - - square_remote = session.remote_function( - input_types=[int], - output_type=int, - reuse=False, - max_batching_rows=max_batching_rows, - cloud_function_service_account="default", - )(square) - - bq_routine = session.bqclient.get_routine( - square_remote.bigframes_bigquery_function - ) - assert bq_routine.remote_function_options.max_batching_rows == ( - max_batching_rows or 1000 - ) - - scalars_df, scalars_pandas_df = scalars_dfs - - bf_result = scalars_df["int64_too"].apply(square_remote).to_pandas() - pd_result = scalars_pandas_df["int64_too"].apply(square) - - pandas.testing.assert_series_equal(bf_result, pd_result, check_dtype=False) - finally: - # clean up the gcp assets created for the remote function - cleanup_function_assets( - square_remote, session.bqclient, session.cloudfunctionsclient - ) - - -@pytest.mark.parametrize( - ("timeout_args", "effective_gcf_timeout"), - [ - pytest.param({}, 600, id="no-set"), - pytest.param({"cloud_function_timeout": None}, 60, id="set-None"), - pytest.param({"cloud_function_timeout": 1200}, 1200, id="set-max-allowed"), - ], -) -@pytest.mark.flaky(retries=2, delay=120) -def test_remote_function_gcf_timeout( - session, scalars_dfs, timeout_args, effective_gcf_timeout -): - try: - - def square(x): - return x * x - - square_remote = session.remote_function( - input_types=[int], - output_type=int, - reuse=False, - cloud_function_service_account="default", - **timeout_args, - )(square) - - # Assert that the GCF is created with the intended maximum timeout - gcf = session.cloudfunctionsclient.get_function( - name=square_remote.bigframes_cloud_function - ) - assert gcf.service_config.timeout_seconds == effective_gcf_timeout - - scalars_df, scalars_pandas_df = scalars_dfs - - bf_result = scalars_df["int64_too"].apply(square_remote).to_pandas() - pd_result = scalars_pandas_df["int64_too"].apply(square) - - pandas.testing.assert_series_equal(bf_result, pd_result, check_dtype=False) - finally: - # clean up the gcp assets created for the remote function - cleanup_function_assets( - square_remote, session.bqclient, session.cloudfunctionsclient - ) - - -@pytest.mark.flaky(retries=2, delay=120) -def test_remote_function_gcf_timeout_max_supported_exceeded(session): - with pytest.raises(ValueError): - - @session.remote_function( - input_types=[int], - output_type=int, - reuse=False, - cloud_function_service_account="default", - cloud_function_timeout=1201, - ) - def square(x): - return x * x - - -# Note: Zero represents default, which is 100 instances actually, which is why the remote function still works -# in the df.apply() call here -@pytest.mark.parametrize( - ("max_instances_args", "expected_max_instances"), - [ - pytest.param({}, 0, id="no-set"), - pytest.param({"cloud_function_max_instances": None}, 0, id="set-None"), - pytest.param({"cloud_function_max_instances": 1000}, 1000, id="set-explicit"), - ], -) -@pytest.mark.flaky(retries=2, delay=120) -def test_remote_function_max_instances( - session, scalars_dfs, max_instances_args, expected_max_instances -): - try: - - def square(x): - return x * x - - square_remote = session.remote_function( - input_types=[int], - output_type=int, - reuse=False, - cloud_function_service_account="default", - **max_instances_args, - )(square) - - # Assert that the GCF is created with the intended max instance count - gcf = session.cloudfunctionsclient.get_function( - name=square_remote.bigframes_cloud_function - ) - assert gcf.service_config.max_instance_count == expected_max_instances - - scalars_df, scalars_pandas_df = scalars_dfs - - bf_result = scalars_df["int64_too"].apply(square_remote).to_pandas() - pd_result = scalars_pandas_df["int64_too"].apply(square) - - pandas.testing.assert_series_equal(bf_result, pd_result, check_dtype=False) - finally: - # clean up the gcp assets created for the remote function - cleanup_function_assets( - square_remote, session.bqclient, session.cloudfunctionsclient - ) - - -@pytest.mark.flaky(retries=2, delay=120) -def test_remote_function_reflects_config_change_with_reuse(session): - square_remote = None - square_remote_2 = None - try: - - def square(x): - return x * x - - # random alphanumeric name starting with a letter - deploy_name = "a" + str(uuid.uuid4().hex) - square_remote = session.remote_function( - input_types=[int], - name=deploy_name, - output_type=int, - reuse=True, - cloud_function_service_account="default", - cloud_function_cpus=1, - )(square) - square_remote_2 = session.remote_function( - input_types=[int], - name=deploy_name, - output_type=int, - reuse=True, - cloud_function_service_account="default", - cloud_function_cpus=2, - )(square) - - # Assert that the GCF is created with the intended max instance count - gcf = session.cloudfunctionsclient.get_function( - name=square_remote_2.bigframes_cloud_function - ) - assert float(gcf.service_config.available_cpu) == 2.0 - finally: - # clean up the gcp assets created for the remote function - if square_remote is not None: - cleanup_function_assets( - square_remote, session.bqclient, session.cloudfunctionsclient - ) - if square_remote_2 is not None: - cleanup_function_assets( - square_remote_2, session.bqclient, session.cloudfunctionsclient - ) - - -@pytest.mark.flaky(retries=2, delay=120) -def test_df_apply_axis_1(session, scalars_dfs): - columns = ["bool_col", "int64_col", "int64_too", "float64_col", "string_col"] - scalars_df, scalars_pandas_df = scalars_dfs - try: - - def serialize_row(row): - custom = { - "name": row.name, - "index": [idx for idx in row.index], - "values": [ - val.item() if hasattr(val, "item") else val for val in row.values - ], - } - - return str( - { - "default": row.to_json(), - "split": row.to_json(orient="split"), - "records": row.to_json(orient="records"), - "index": row.to_json(orient="index"), - "table": row.to_json(orient="table"), - "custom": custom, - } - ) - - serialize_row_remote = session.remote_function( - input_types=pandas.Series, - output_type=str, - reuse=False, - cloud_function_service_account="default", - )(serialize_row) - - assert getattr(serialize_row_remote, "is_row_processor") - - bf_result = scalars_df[columns].apply(serialize_row_remote, axis=1).to_pandas() - pd_result = scalars_pandas_df[columns].apply(serialize_row, axis=1) - - # bf_result.dtype is 'string[pyarrow]' while pd_result.dtype is 'object' - # , ignore this mismatch by using check_dtype=False. - pandas.testing.assert_series_equal(pd_result, bf_result, check_dtype=False) - - # Let's make sure the read_gbq_function path works for this function - serialize_row_reuse = session.read_gbq_function( - serialize_row_remote.bigframes_bigquery_function, is_row_processor=True - ) - bf_result = scalars_df[columns].apply(serialize_row_reuse, axis=1).to_pandas() - pandas.testing.assert_series_equal(pd_result, bf_result, check_dtype=False) - finally: - # clean up the gcp assets created for the remote function - cleanup_function_assets( - serialize_row_remote, session.bqclient, session.cloudfunctionsclient - ) - - -@pytest.mark.flaky(retries=2, delay=120) -def test_df_apply_axis_1_aggregates(session, scalars_dfs): - columns = ["int64_col", "int64_too", "float64_col"] - scalars_df, scalars_pandas_df = scalars_dfs - - try: - - def analyze(row): - return str( - { - "dtype": row.dtype, - "count": row.count(), - "min": row.min(), - "max": row.max(), - "mean": row.mean(), - "std": row.std(), - "var": row.var(), - } - ) - - analyze_remote = session.remote_function( - input_types=pandas.Series, - output_type=str, - reuse=False, - cloud_function_service_account="default", - )(analyze) - - assert getattr(analyze_remote, "is_row_processor") - - bf_result = ( - scalars_df[columns].dropna().apply(analyze_remote, axis=1).to_pandas() - ) - pd_result = scalars_pandas_df[columns].dropna().apply(analyze, axis=1) - - # bf_result.dtype is 'string[pyarrow]' while pd_result.dtype is 'object' - # , ignore this mismatch by using check_dtype=False. - pandas.testing.assert_series_equal(pd_result, bf_result, check_dtype=False) - finally: - # clean up the gcp assets created for the remote function - cleanup_function_assets( - analyze_remote, session.bqclient, session.cloudfunctionsclient - ) - - -@pytest.mark.parametrize( - ("pd_df"), - [ - pytest.param( - pandas.DataFrame( - { - "2": [1, 2, 3], - 2: [1.5, 3.75, 5], - "name, [with. special'- chars\")/\\": [10, 20, 30], - (3, 4): ["pq", "rs", "tu"], - (5.0, "six", 7): [8, 9, 10], - 'raise Exception("hacked!")': [11, 12, 13], - }, - # Default pandas index has non-numpy type, whereas bigframes is - # always numpy-based type, so let's use the index compatible - # with bigframes. See more details in b/369689696. - index=pandas.Index([0, 1, 2], dtype=pandas.Int64Dtype()), - ), - id="all-kinds-of-column-names", - ), - pytest.param( - pandas.DataFrame( - { - "x": [1, 2, 3], - "y": [1.5, 3.75, 5], - "z": ["pq", "rs", "tu"], - }, - index=pandas.MultiIndex.from_frame( - pandas.DataFrame( - { - "idx0": pandas.Series( - ["a", "a", "b"], dtype=pandas.StringDtype() - ), - "idx1": pandas.Series( - [100, 200, 300], dtype=pandas.Int64Dtype() - ), - } - ) - ), - ), - id="multiindex", - marks=pytest.mark.skip( - reason="TODO: revert this skip after this pandas bug is fixed: https://github.com/pandas-dev/pandas/issues/59908" - ), - ), - pytest.param( - pandas.DataFrame( - [ - [10, 1.5, "pq"], - [20, 3.75, "rs"], - [30, 8.0, "tu"], - ], - # Default pandas index has non-numpy type, whereas bigframes is - # always numpy-based type, so let's use the index compatible - # with bigframes. See more details in b/369689696. - index=pandas.Index([0, 1, 2], dtype=pandas.Int64Dtype()), - columns=pandas.MultiIndex.from_arrays( - [ - ["first", "last_two", "last_two"], - [1, 2, 3], - ] - ), - ), - id="column-multiindex", - ), - pytest.param( - pandas.DataFrame( - { - datetime.now(): [1, 2, 3], - } - ), - id="column-name-not-supported", - marks=pytest.mark.xfail(raises=NameError), - ), - ], -) -@pytest.mark.flaky(retries=2, delay=120) -def test_df_apply_axis_1_complex(session, pd_df): - bf_df = session.read_pandas(pd_df) - - try: - - def serialize_row(row): - custom = { - "name": row.name, - "index": [idx for idx in row.index], - "values": [ - val.item() if hasattr(val, "item") else val for val in row.values - ], - } - return str( - { - "default": row.to_json(), - "split": row.to_json(orient="split"), - "records": row.to_json(orient="records"), - "index": row.to_json(orient="index"), - "custom": custom, - } - ) - - serialize_row_remote = session.remote_function( - input_types=pandas.Series, - output_type=str, - reuse=False, - cloud_function_service_account="default", - )(serialize_row) - - assert getattr(serialize_row_remote, "is_row_processor") - - bf_result = bf_df.apply(serialize_row_remote, axis=1).to_pandas() - pd_result = pd_df.apply(serialize_row, axis=1) - - # ignore known dtype difference between pandas and bigframes - pandas.testing.assert_series_equal( - pd_result, bf_result, check_dtype=False, check_index_type=False - ) - finally: - # clean up the gcp assets created for the remote function - cleanup_function_assets( - serialize_row_remote, session.bqclient, session.cloudfunctionsclient - ) - - -@pytest.mark.flaky(retries=2, delay=120) -def test_df_apply_axis_1_na_nan_inf(session): - """This test is for special cases of float values, to make sure any (nan, - inf, -inf) produced by user code is honored. - """ - bf_df = session.read_gbq( - """\ -SELECT "1" AS text, 1 AS num -UNION ALL -SELECT "2.5" AS text, 2.5 AS num -UNION ALL -SELECT "nan" AS text, IEEE_DIVIDE(0, 0) AS num -UNION ALL -SELECT "inf" AS text, IEEE_DIVIDE(1, 0) AS num -UNION ALL -SELECT "-inf" AS text, IEEE_DIVIDE(-1, 0) AS num -UNION ALL -SELECT "numpy nan" AS text, IEEE_DIVIDE(0, 0) AS num -UNION ALL -SELECT "pandas na" AS text, NULL AS num - """ - ) - - pd_df = bf_df.to_pandas() - - try: - - def float_parser(row: pandas.Series): - import numpy as mynp - import pandas as mypd - - if row["text"] == "pandas na": - return mypd.NA - if row["text"] == "numpy nan": - return mynp.nan - return float(row["text"]) - - float_parser_remote = session.remote_function( - output_type=float, - reuse=False, - cloud_function_service_account="default", - )(float_parser) - - assert getattr(float_parser_remote, "is_row_processor") - - pd_result = pd_df.apply(float_parser, axis=1) - bf_result = bf_df.apply(float_parser_remote, axis=1).to_pandas() - - # bf_result.dtype is 'Float64' while pd_result.dtype is 'object' - # , ignore this mismatch by using check_dtype=False. - pandas.testing.assert_series_equal(pd_result, bf_result, check_dtype=False) - - # Let's also assert that the data is consistent in this round trip - # (BQ -> BigFrames -> BQ -> GCF -> BQ -> BigFrames) w.r.t. their - # expected values in BQ - bq_result = bf_df["num"].to_pandas() - bq_result.name = None - pandas.testing.assert_series_equal(bq_result, bf_result) - finally: - # clean up the gcp assets created for the remote function - cleanup_function_assets( - float_parser_remote, session.bqclient, session.cloudfunctionsclient - ) - - -@pytest.mark.flaky(retries=2, delay=120) -def test_df_apply_axis_1_args(session, scalars_dfs): - columns = ["int64_col", "int64_too"] - scalars_df, scalars_pandas_df = scalars_dfs - - try: - - def the_sum(s1, s2, x): - return s1 + s2 + x - - the_sum_mf = session.remote_function( - input_types=[int, int, int], - output_type=int, - reuse=False, - cloud_function_service_account="default", - )(the_sum) - - args1 = (1,) - - # Fails to apply on dataframe with incompatible number of columns and args. - with pytest.raises( - ValueError, - match="^Parameter count mismatch:.* expected 3 parameters but received 4 values \\(2 DataFrame columns and 2 args\\)", - ): - scalars_df[columns].apply( - the_sum_mf, - axis=1, - args=( - 1, - 1, - ), - ) - - # Fails to apply on dataframe with incompatible column datatypes. - with pytest.raises( - ValueError, - match="^Data type mismatch for DataFrame columns: Expected .* Received .*", - ): - scalars_df[columns].assign( - int64_col=lambda df: df["int64_col"].astype("Float64") - ).apply(the_sum_mf, axis=1, args=args1) - - # Fails to apply on dataframe with incompatible args datatypes. - with pytest.raises( - ValueError, - match="^Data type mismatch for 'args' parameter: Expected .* Received .*", - ): - scalars_df[columns].apply(the_sum_mf, axis=1, args=("hello world",)) - - bf_result = ( - scalars_df[columns] - .dropna() - .apply(the_sum_mf, axis=1, args=args1) - .to_pandas() - ) - pd_result = scalars_pandas_df[columns].dropna().apply(sum, axis=1, args=args1) - - pandas.testing.assert_series_equal(pd_result, bf_result, check_dtype=False) - - finally: - # clean up the gcp assets created for the remote function. - cleanup_function_assets(the_sum_mf, session.bqclient, ignore_failures=False) - - -@pytest.mark.flaky(retries=2, delay=120) -def test_df_apply_axis_1_series_args(session, scalars_dfs): - columns = ["int64_col", "float64_col"] - scalars_df, scalars_pandas_df = scalars_dfs - - try: - - @session.remote_function( - input_types=[pandas.Series, float, str, bool], - output_type=list[str], - reuse=False, - cloud_function_service_account="default", - ) - def foo_list(x: pandas.Series, y0: float, y1, y2) -> list[str]: - return ( - [str(x["int64_col"]), str(y0), str(y1), str(y2)] - if y2 - else [str(x["float64_col"])] - ) - - args1 = (12.34, "hello world", True) - bf_result = scalars_df[columns].apply(foo_list, axis=1, args=args1).to_pandas() - pd_result = scalars_pandas_df[columns].apply(foo_list, axis=1, args=args1) - - # Ignore any dtype difference. - pandas.testing.assert_series_equal(bf_result, pd_result, check_dtype=False) - - args2 = (43.21, "xxx3yyy", False) - foo_list_ref = session.read_gbq_function( - foo_list.bigframes_bigquery_function, is_row_processor=True - ) - bf_result = ( - scalars_df[columns].apply(foo_list_ref, axis=1, args=args2).to_pandas() - ) - pd_result = scalars_pandas_df[columns].apply(foo_list, axis=1, args=args2) - - # Ignore any dtype difference. - pandas.testing.assert_series_equal(bf_result, pd_result, check_dtype=False) - - finally: - # Clean up the gcp assets created for the remote function. - cleanup_function_assets(foo_list, session.bqclient, ignore_failures=False) - - -@pytest.mark.parametrize( - ( - "memory_mib_args", - "expected_memory", - "expected_cpus", - ), - [ - pytest.param({}, "1024Mi", None, id="no-set"), - pytest.param( - {"cloud_function_memory_mib": None}, "1024Mi", None, id="set-None" - ), - pytest.param({"cloud_function_memory_mib": 128}, "128Mi", None, id="set-128"), - pytest.param( - {"cloud_function_memory_mib": 512, "cloud_function_cpus": 0.6}, - "512Mi", - "0.6", - id="set-512", - ), - pytest.param( - {"cloud_function_memory_mib": 1024}, "1024Mi", None, id="set-1024" - ), - pytest.param( - {"cloud_function_memory_mib": 4096, "cloud_function_cpus": 4}, - "4096Mi", - "4", - id="set-4096", - ), - pytest.param( - {"cloud_function_memory_mib": 32768}, "32768Mi", None, id="set-32768" - ), - ], -) -@pytest.mark.flaky(retries=2, delay=120) -def test_remote_function_gcf_memory( - session, scalars_dfs, memory_mib_args, expected_memory, expected_cpus -): - try: - - def square(x: int) -> int: - return x * x - - square_remote = session.remote_function( - reuse=False, cloud_function_service_account="default", **memory_mib_args - )(square) - - # Assert that the GCF is created with the intended memory - gcf = session.cloudfunctionsclient.get_function( - name=square_remote.bigframes_cloud_function - ) - assert gcf.service_config.available_memory == expected_memory - if expected_cpus is not None: - assert gcf.service_config.available_cpu == expected_cpus - if float(gcf.service_config.available_cpu) >= 1.0: - assert gcf.service_config.max_instance_request_concurrency >= float( - gcf.service_config.available_cpu - ) - - scalars_df, scalars_pandas_df = scalars_dfs - - bf_result = scalars_df["int64_too"].apply(square_remote).to_pandas() - pd_result = scalars_pandas_df["int64_too"].apply(square) - - pandas.testing.assert_series_equal(bf_result, pd_result, check_dtype=False) - finally: - # clean up the gcp assets created for the remote function - cleanup_function_assets( - square_remote, session.bqclient, session.cloudfunctionsclient - ) - - -@pytest.mark.parametrize( - ("memory_mib",), - [ - pytest.param(127, id="127-too-low"), - pytest.param(32769, id="set-32769-too-high"), - ], -) -def test_remote_function_gcf_memory_unsupported(session, memory_mib): - with pytest.raises(ValueError, match="Cloud run supports"): - - @session.remote_function( - reuse=False, - cloud_function_service_account="default", - cloud_function_memory_mib=memory_mib, - ) - def square(x: int) -> int: - return x * x - - -@pytest.mark.flaky(retries=2, delay=120) -def test_remote_function_unnamed_removed_w_session_cleanup(): - # create a clean session - session = bigframes.connect() - - with warnings.catch_warnings(record=True) as record: - # create an unnamed remote function in the session. - # The type hints in this function's signature are redundant. The - # `input_types` and `output_type` arguments from remote_function - # decorator take precedence and will be used instead. - @session.remote_function( - input_types=[int], - output_type=int, - reuse=False, - cloud_function_service_account="default", - ) - def foo(x: int) -> int: - return x + 1 - - # No following warning with only redundant type hints (no conflict). - input_type_warning = "Conflicting input types detected" - assert not any(input_type_warning in str(warning.message) for warning in record) - return_type_warning = "Conflicting return type detected" - assert not any(return_type_warning in str(warning.message) for warning in record) - - # ensure that remote function artifacts are created - assert foo.bigframes_remote_function is not None - session.bqclient.get_routine(foo.bigframes_remote_function) is not None - assert foo.bigframes_bigquery_function is not None - session.bqclient.get_routine(foo.bigframes_bigquery_function) is not None - assert foo.bigframes_cloud_function is not None - session.cloudfunctionsclient.get_function( - name=foo.bigframes_cloud_function - ) is not None - - # explicitly close the session - session.close() - - # ensure that the bq remote function is deleted - with pytest.raises(google.cloud.exceptions.NotFound): - session.bqclient.get_routine(foo.bigframes_bigquery_function) - - # the deletion of cloud function happens in a non-blocking way, ensure that - # it either exists in a being-deleted state, or is already deleted - try: - gcf = session.cloudfunctionsclient.get_function( - name=foo.bigframes_cloud_function - ) - assert gcf.state is functions_v2.Function.State.DELETING - except google.cloud.exceptions.NotFound: - pass - - -@pytest.mark.flaky(retries=2, delay=120) -def test_remote_function_named_perists_w_session_cleanup(): - try: - # create a clean session - session = bigframes.connect() - - # create a name for the remote function - name = test_utils.prefixer.Prefixer("bigframes", "").create_prefix() - - # create an unnamed remote function in the session - @session.remote_function( - reuse=False, name=name, cloud_function_service_account="default" - ) - def foo(x: int) -> int: - return x + 1 - - # ensure that remote function artifacts are created - assert foo.bigframes_remote_function is not None - session.bqclient.get_routine(foo.bigframes_remote_function) is not None - assert foo.bigframes_bigquery_function is not None - session.bqclient.get_routine(foo.bigframes_bigquery_function) is not None - assert foo.bigframes_cloud_function is not None - session.cloudfunctionsclient.get_function( - name=foo.bigframes_cloud_function - ) is not None - - # explicitly close the session - session.close() - - # ensure that the bq remote function still exists - session.bqclient.get_routine(foo.bigframes_bigquery_function) is not None - - # the deletion of cloud function happens in a non-blocking way, ensure - # that it was not deleted and still exists in active state - gcf = session.cloudfunctionsclient.get_function( - name=foo.bigframes_cloud_function - ) - assert gcf.state is functions_v2.Function.State.ACTIVE - finally: - # clean up the gcp assets created for the remote function - cleanup_function_assets(foo, session.bqclient, session.cloudfunctionsclient) - - -@pytest.mark.flaky(retries=2, delay=120) -def test_remote_function_clean_up_by_session_id(): - # Use a brand new session to avoid conflict with other tests - session = bigframes.Session() - session_id = session.session_id - try: - # we will create remote functions, one with explicit name and another - # without it, and later confirm that the former is deleted when the session - # is cleaned up by session id, but the latter remains - ## unnamed - @session.remote_function(reuse=False, cloud_function_service_account="default") - def foo_unnamed(x: int) -> int: - return x + 1 - - ## named - rf_name = test_utils.prefixer.Prefixer("bigframes", "").create_prefix() - - @session.remote_function( - reuse=False, name=rf_name, cloud_function_service_account="default" - ) - def foo_named(x: int) -> int: - return x + 2 - - # check that BQ remote functiosn were created with corresponding cloud - # functions - for foo in [foo_unnamed, foo_named]: - assert foo.bigframes_remote_function is not None - session.bqclient.get_routine(foo.bigframes_remote_function) is not None - assert foo.bigframes_bigquery_function is not None - session.bqclient.get_routine(foo.bigframes_bigquery_function) is not None - assert foo.bigframes_cloud_function is not None - session.cloudfunctionsclient.get_function( - name=foo.bigframes_cloud_function - ) is not None - - # clean up using explicit session id - bpd.clean_up_by_session_id( - session_id, location=session._location, project=session._project - ) - - # ensure that the unnamed bq remote function is deleted along with its - # corresponding cloud function - with pytest.raises(google.cloud.exceptions.NotFound): - session.bqclient.get_routine(foo_unnamed.bigframes_bigquery_function) - try: - gcf = session.cloudfunctionsclient.get_function( - name=foo_unnamed.bigframes_cloud_function - ) - assert gcf.state is functions_v2.Function.State.DELETING - except google.cloud.exceptions.NotFound: - pass - - # ensure that the named bq remote function still exists along with its - # corresponding cloud function - session.bqclient.get_routine(foo_named.bigframes_bigquery_function) is not None - gcf = session.cloudfunctionsclient.get_function( - name=foo_named.bigframes_cloud_function - ) - assert gcf.state is functions_v2.Function.State.ACTIVE - finally: - # clean up the gcp assets created for the remote function - cleanup_function_assets( - foo_named, session.bqclient, session.cloudfunctionsclient - ) - - -def test_df_apply_axis_1_multiple_params(session): - bf_df = bigframes.dataframe.DataFrame( - { - "Id": [1, 2, 3], - "Age": [22.5, 23, 23.5], - "Name": ["alpha", "beta", "gamma"], - } - ) - - expected_dtypes = ( - bigframes.dtypes.INT_DTYPE, - bigframes.dtypes.FLOAT_DTYPE, - bigframes.dtypes.STRING_DTYPE, - ) - - # Assert the dataframe dtypes - assert tuple(bf_df.dtypes) == expected_dtypes - - try: - - @session.remote_function( - input_types=[int, float, str], - output_type=str, - reuse=False, - cloud_function_service_account="default", - ) - def foo(x, y, z): - return f"I got {x}, {y} and {z}" - - assert getattr(foo, "is_row_processor") is False - assert getattr(foo, "input_dtypes") == expected_dtypes - - # Fails to apply on dataframe with incompatible number of columns - with pytest.raises( - ValueError, - match="^Parameter count mismatch:.* expected 3 parameters but received 2 DataFrame columns.", - ): - bf_df[["Id", "Age"]].apply(foo, axis=1) - with pytest.raises( - ValueError, - match="^Parameter count mismatch:.* expected 3 parameters but received 4 DataFrame columns.", - ): - bf_df.assign(Country="lalaland").apply(foo, axis=1) - - # Fails to apply on dataframe with incompatible column datatypes - with pytest.raises( - ValueError, - match="^Data type mismatch for DataFrame columns: Expected .* Received .*", - ): - bf_df.assign(Age=bf_df["Age"].astype("Int64")).apply(foo, axis=1) - - # Successfully applies to dataframe with matching number of columns - # and their datatypes - bf_result = bf_df.apply(foo, axis=1).to_pandas() - - # Since this scenario is not pandas-like, let's handcraft the - # expected result - expected_result = pandas.Series( - [ - "I got 1, 22.5 and alpha", - "I got 2, 23 and beta", - "I got 3, 23.5 and gamma", - ] - ) - - pandas.testing.assert_series_equal( - expected_result, bf_result, check_dtype=False, check_index_type=False - ) - - # Let's make sure the read_gbq_function path works for this function - foo_reuse = session.read_gbq_function(foo.bigframes_bigquery_function) - bf_result = bf_df.apply(foo_reuse, axis=1).to_pandas() - pandas.testing.assert_series_equal( - expected_result, bf_result, check_dtype=False, check_index_type=False - ) - finally: - # clean up the gcp assets created for the remote function - cleanup_function_assets(foo, session.bqclient, session.cloudfunctionsclient) - - -def test_df_apply_axis_1_multiple_params_array_output(session): - bf_df = bigframes.dataframe.DataFrame( - { - "Id": [1, 2, 3], - "Age": [22.5, 23, 23.5], - "Name": ["alpha", "beta", "gamma"], - } - ) - - expected_dtypes = ( - bigframes.dtypes.INT_DTYPE, - bigframes.dtypes.FLOAT_DTYPE, - bigframes.dtypes.STRING_DTYPE, - ) - - # Assert the dataframe dtypes - assert tuple(bf_df.dtypes) == expected_dtypes - - try: - - @session.remote_function( - input_types=[int, float, str], - output_type=list[str], - reuse=False, - cloud_function_service_account="default", - ) - def foo(x, y, z): - return [str(x), str(y), z] - - assert getattr(foo, "is_row_processor") is False - assert getattr(foo, "input_dtypes") == expected_dtypes - assert ( - getattr(foo, "bigframes_bigquery_function_output_dtype") - == bigframes.dtypes.STRING_DTYPE - ) - - # Fails to apply on dataframe with incompatible number of columns - with pytest.raises( - ValueError, - match="^Parameter count mismatch:.* expected 3 parameters but received 2 DataFrame columns.", - ): - bf_df[["Id", "Age"]].apply(foo, axis=1) - with pytest.raises( - ValueError, - match="^Parameter count mismatch:.* expected 3 parameters but received 4 DataFrame columns.", - ): - bf_df.assign(Country="lalaland").apply(foo, axis=1) - - # Fails to apply on dataframe with incompatible column datatypes - with pytest.raises( - ValueError, - match="^Data type mismatch for DataFrame columns: Expected .* Received .*", - ): - bf_df.assign(Age=bf_df["Age"].astype("Int64")).apply(foo, axis=1) - - # Successfully applies to dataframe with matching number of columns - # and their datatypes - bf_result = bf_df.apply(foo, axis=1).to_pandas() - - # Since this scenario is not pandas-like, let's handcraft the - # expected result - expected_result = pandas.Series( - [ - ["1", "22.5", "alpha"], - ["2", "23", "beta"], - ["3", "23.5", "gamma"], - ] - ) - - pandas.testing.assert_series_equal( - expected_result, bf_result, check_dtype=False, check_index_type=False - ) - - # Let's make sure the read_gbq_function path works for this function - foo_reuse = session.read_gbq_function(foo.bigframes_bigquery_function) - bf_result = bf_df.apply(foo_reuse, axis=1).to_pandas() - pandas.testing.assert_series_equal( - expected_result, bf_result, check_dtype=False, check_index_type=False - ) - finally: - # clean up the gcp assets created for the remote function - cleanup_function_assets(foo, session.bqclient, session.cloudfunctionsclient) - - -def test_df_apply_axis_1_single_param_non_series(session): - bf_df = bigframes.dataframe.DataFrame( - { - "Id": [1, 2, 3], - } - ) - - expected_dtypes = (bigframes.dtypes.INT_DTYPE,) - - # Assert the dataframe dtypes - assert tuple(bf_df.dtypes) == expected_dtypes - - try: - - @session.remote_function( - input_types=[int], - output_type=str, - reuse=False, - cloud_function_service_account="default", - ) - def foo(x): - return f"I got {x}" - - assert getattr(foo, "is_row_processor") is False - assert getattr(foo, "input_dtypes") == expected_dtypes - - # Fails to apply on dataframe with incompatible number of columns - with pytest.raises( - ValueError, - match="^Parameter count mismatch:.* expected 1 parameters but received 0 DataFrame.*", - ): - bf_df[[]].apply(foo, axis=1) - with pytest.raises( - ValueError, - match="^Parameter count mismatch:.* expected 1 parameters but received 2 DataFrame.*", - ): - bf_df.assign(Country="lalaland").apply(foo, axis=1) - - # Fails to apply on dataframe with incompatible column datatypes - with pytest.raises( - ValueError, - match="^Data type mismatch for DataFrame columns: Expected .* Received .*", - ): - bf_df.assign(Id=bf_df["Id"].astype("Float64")).apply(foo, axis=1) - - # Successfully applies to dataframe with matching number of columns - # and their datatypes - bf_result = bf_df.apply(foo, axis=1).to_pandas() - - # Since this scenario is not pandas-like, let's handcraft the - # expected result - expected_result = pandas.Series( - [ - "I got 1", - "I got 2", - "I got 3", - ] - ) - - pandas.testing.assert_series_equal( - expected_result, bf_result, check_dtype=False, check_index_type=False - ) - finally: - # clean up the gcp assets created for the remote function - cleanup_function_assets(foo, session.bqclient, session.cloudfunctionsclient) - - -@pytest.mark.flaky(retries=2, delay=120) -def test_df_apply_axis_1_array_output(session, scalars_dfs): - columns = ["int64_col", "int64_too"] - scalars_df, scalars_pandas_df = scalars_dfs - try: - - @session.remote_function(reuse=False, cloud_function_service_account="default") - def generate_stats(row: pandas.Series) -> list[int]: - import pandas as pd - - sum = row["int64_too"] - avg = row["int64_too"] - if pd.notna(row["int64_col"]): - sum += row["int64_col"] - avg = round((avg + row["int64_col"]) / 2) - return [sum, avg] - - assert getattr(generate_stats, "is_row_processor") - - bf_result = scalars_df[columns].apply(generate_stats, axis=1).to_pandas() - pd_result = scalars_pandas_df[columns].apply(generate_stats, axis=1) - - # bf_result.dtype is 'list[pyarrow]' while pd_result.dtype - # is 'object', ignore this mismatch by using check_dtype=False. - pandas.testing.assert_series_equal(pd_result, bf_result, check_dtype=False) - - # Let's make sure the read_gbq_function path works for this function - generate_stats_reuse = session.read_gbq_function( - generate_stats.bigframes_bigquery_function, - is_row_processor=True, - ) - bf_result = scalars_df[columns].apply(generate_stats_reuse, axis=1).to_pandas() - pandas.testing.assert_series_equal(pd_result, bf_result, check_dtype=False) - finally: - # clean up the gcp assets created for the remote function - cleanup_function_assets( - generate_stats, session.bqclient, session.cloudfunctionsclient - ) - - -@pytest.mark.parametrize( - ( - "ingress_settings_args", - "effective_ingress_settings", - "expect_default_ingress_setting_warning", - ), - [ - pytest.param( - {}, - functions_v2.ServiceConfig.IngressSettings.ALLOW_INTERNAL_ONLY, - False, - id="no-set", - ), - pytest.param( - {"cloud_function_ingress_settings": None}, - functions_v2.ServiceConfig.IngressSettings.ALLOW_INTERNAL_ONLY, - True, - id="set-none", - ), - pytest.param( - {"cloud_function_ingress_settings": "internal-only"}, - functions_v2.ServiceConfig.IngressSettings.ALLOW_INTERNAL_ONLY, - False, - id="set-internal-only", - ), - pytest.param( - {"cloud_function_ingress_settings": "internal-and-gclb"}, - functions_v2.ServiceConfig.IngressSettings.ALLOW_INTERNAL_AND_GCLB, - False, - id="set-internal-and-gclb", - ), - ], -) -@pytest.mark.flaky(retries=2, delay=120) -def test_remote_function_ingress_settings( - session, - scalars_dfs, - ingress_settings_args, - effective_ingress_settings, - expect_default_ingress_setting_warning, -): - try: - # Verify the function raises the expected security warning message. - with warnings.catch_warnings(record=True) as record: - - def square(x: int) -> int: - return x * x - - square_remote = session.remote_function( - reuse=False, - cloud_function_service_account="default", - **ingress_settings_args, - )(square) - - default_ingress_setting_warnings = [ - warn - for warn in record - if isinstance(warn.message, UserWarning) - and "The `cloud_function_ingress_settings` is being set to 'internal-only' by default." - ] - assert len(default_ingress_setting_warnings) == ( - 1 if expect_default_ingress_setting_warning else 0 - ) - - # Assert that the GCF is created with the intended maximum timeout - gcf = session.cloudfunctionsclient.get_function( - name=square_remote.bigframes_cloud_function - ) - assert gcf.service_config.ingress_settings == effective_ingress_settings - - scalars_df, scalars_pandas_df = scalars_dfs - - bf_result = scalars_df["int64_too"].apply(square_remote).to_pandas() - pd_result = scalars_pandas_df["int64_too"].apply(square) - - pandas.testing.assert_series_equal(bf_result, pd_result, check_dtype=False) - finally: - # clean up the gcp assets created for the remote function - cleanup_function_assets( - square_remote, session.bqclient, session.cloudfunctionsclient - ) - - -@pytest.mark.flaky(retries=2, delay=120) -def test_remote_function_ingress_settings_unsupported(session): - with pytest.raises( - ValueError, match="'unknown' not one of the supported ingress settings values" - ): - - @session.remote_function( - reuse=False, - cloud_function_service_account="default", - cloud_function_ingress_settings="unknown", - ) - def square(x: int) -> int: - return x * x - - -@pytest.mark.parametrize( - ("session_creator"), - [ - pytest.param(bigframes.Session, id="session-constructor"), - pytest.param(bigframes.connect, id="connect-method"), - ], -) -@pytest.mark.flaky(retries=2, delay=120) -def test_remote_function_w_context_manager_unnamed( - scalars_dfs, dataset_id, bq_cf_connection, session_creator -): - def add_one(x: int) -> int: - return x + 1 - - scalars_df, scalars_pandas_df = scalars_dfs - pd_result = scalars_pandas_df["int64_too"].apply(add_one) - - temporary_bigquery_remote_function = None - temporary_cloud_run_function = None - - try: - with session_creator() as session: - # create a temporary remote function - add_one_remote_temp = session.remote_function( - dataset=dataset_id, - bigquery_connection=bq_cf_connection, - reuse=False, - cloud_function_service_account="default", - )(add_one) - - temporary_bigquery_remote_function = ( - add_one_remote_temp.bigframes_bigquery_function - ) - assert temporary_bigquery_remote_function is not None - assert ( - session.bqclient.get_routine(temporary_bigquery_remote_function) - is not None - ) - - temporary_cloud_run_function = add_one_remote_temp.bigframes_cloud_function - assert temporary_cloud_run_function is not None - assert ( - session.cloudfunctionsclient.get_function( - name=temporary_cloud_run_function - ) - is not None - ) - - bf_result = scalars_df["int64_too"].apply(add_one_remote_temp).to_pandas() - pandas.testing.assert_series_equal(bf_result, pd_result, check_dtype=False) - - # outside the with statement context manager the temporary BQ remote - # function and the underlying cloud run function should have been - # cleaned up - assert temporary_bigquery_remote_function is not None - with pytest.raises(google.api_core.exceptions.NotFound): - session.bqclient.get_routine(temporary_bigquery_remote_function) - # the deletion of cloud function happens in a non-blocking way, ensure that - # it either exists in a being-deleted state, or is already deleted - assert temporary_cloud_run_function is not None - try: - gcf = session.cloudfunctionsclient.get_function( - name=temporary_cloud_run_function - ) - assert gcf.state is functions_v2.Function.State.DELETING - except google.cloud.exceptions.NotFound: - pass - finally: - # clean up the gcp assets created for the temporary remote function, - # just in case it was not explicitly cleaned up in the try clause due - # to assertion failure or exception earlier than that - cleanup_function_assets( - add_one_remote_temp, session.bqclient, session.cloudfunctionsclient - ) - - -@pytest.mark.parametrize( - ("session_creator"), - [ - pytest.param(bigframes.Session, id="session-constructor"), - pytest.param(bigframes.connect, id="connect-method"), - ], -) -@pytest.mark.flaky(retries=2, delay=120) -def test_remote_function_w_context_manager_named( - scalars_dfs, dataset_id, bq_cf_connection, session_creator -): - def add_one(x: int) -> int: - return x + 1 - - scalars_df, scalars_pandas_df = scalars_dfs - pd_result = scalars_pandas_df["int64_too"].apply(add_one) - - persistent_bigquery_remote_function = None - persistent_cloud_run_function = None - - try: - with session_creator() as session: - # create a persistent remote function - name = test_utils.prefixer.Prefixer("bigframes", "").create_prefix() - add_one_remote_persist = session.remote_function( - dataset=dataset_id, - bigquery_connection=bq_cf_connection, - reuse=False, - name=name, - cloud_function_service_account="default", - )(add_one) - - persistent_bigquery_remote_function = ( - add_one_remote_persist.bigframes_bigquery_function - ) - assert persistent_bigquery_remote_function is not None - assert ( - session.bqclient.get_routine(persistent_bigquery_remote_function) - is not None - ) - - persistent_cloud_run_function = ( - add_one_remote_persist.bigframes_cloud_function - ) - assert persistent_cloud_run_function is not None - assert ( - session.cloudfunctionsclient.get_function( - name=persistent_cloud_run_function - ) - is not None - ) - - bf_result = ( - scalars_df["int64_too"].apply(add_one_remote_persist).to_pandas() - ) - pandas.testing.assert_series_equal(bf_result, pd_result, check_dtype=False) - - # outside the with statement context manager the persistent BQ remote - # function and the underlying cloud run function should still exist - assert persistent_bigquery_remote_function is not None - assert ( - session.bqclient.get_routine(persistent_bigquery_remote_function) - is not None - ) - assert persistent_cloud_run_function is not None - assert ( - session.cloudfunctionsclient.get_function( - name=persistent_cloud_run_function - ) - is not None - ) - finally: - # clean up the gcp assets created for the persistent remote function - cleanup_function_assets( - add_one_remote_persist, session.bqclient, session.cloudfunctionsclient - ) - - -@pytest.mark.parametrize( - "array_dtype", - [ - bool, - int, - float, - str, - ], -) -@pytest.mark.flaky(retries=2, delay=120) -def test_remote_function_array_output( - session, scalars_dfs, dataset_id, bq_cf_connection, array_dtype -): - try: - - @session.remote_function( - dataset=dataset_id, - bigquery_connection=bq_cf_connection, - reuse=False, - cloud_function_service_account="default", - ) - def featurize(x: int) -> list[array_dtype]: # type: ignore - return [array_dtype(i) for i in [x, x + 1, x + 2]] - - scalars_df, scalars_pandas_df = scalars_dfs - - bf_int64_col = scalars_df["int64_too"] - bf_result = bf_int64_col.apply(featurize).to_pandas() - - pd_int64_col = scalars_pandas_df["int64_too"] - pd_result = pd_int64_col.apply(featurize) - - # ignore any dtype disparity - pandas.testing.assert_series_equal(pd_result, bf_result, check_dtype=False) - - # Let's make sure the read_gbq_function path works for this function - featurize_reuse = session.read_gbq_function( - featurize.bigframes_bigquery_function # type: ignore - ) - bf_result = scalars_df["int64_too"].apply(featurize_reuse).to_pandas() - pandas.testing.assert_series_equal(pd_result, bf_result, check_dtype=False) - finally: - # clean up the gcp assets created for the remote function - cleanup_function_assets( - featurize, session.bqclient, session.cloudfunctionsclient - ) - - -@pytest.mark.flaky(retries=2, delay=120) -def test_remote_function_array_output_partial_ordering_mode( - unordered_session, scalars_dfs, dataset_id, bq_cf_connection -): - try: - - @unordered_session.remote_function( - dataset=dataset_id, - bigquery_connection=bq_cf_connection, - reuse=False, - cloud_function_service_account="default", - ) - def featurize(x: float) -> list[float]: # type: ignore - return [x, x + 1, x + 2] - - scalars_df, scalars_pandas_df = scalars_dfs - - bf_int64_col = scalars_df["float64_col"].dropna() - bf_result = bf_int64_col.apply(featurize).to_pandas() - - pd_int64_col = scalars_pandas_df["float64_col"].dropna() - pd_result = pd_int64_col.apply(featurize) - - # ignore any dtype disparity - pandas.testing.assert_series_equal(pd_result, bf_result, check_dtype=False) - - # Let's make sure the read_gbq_function path works for this function - featurize_reuse = unordered_session.read_gbq_function( - featurize.bigframes_bigquery_function # type: ignore - ) - bf_int64_col = scalars_df["float64_col"].dropna() - bf_result = bf_int64_col.apply(featurize_reuse).to_pandas() - pandas.testing.assert_series_equal(pd_result, bf_result, check_dtype=False) - finally: - # clean up the gcp assets created for the remote function - cleanup_function_assets( - featurize, - unordered_session.bqclient, - unordered_session.cloudfunctionsclient, - ) - - -@pytest.mark.flaky(retries=2, delay=120) -def test_remote_function_array_output_multiindex( - session, scalars_dfs, dataset_id, bq_cf_connection -): - try: - - @session.remote_function( - dataset=dataset_id, - bigquery_connection=bq_cf_connection, - reuse=False, - cloud_function_service_account="default", - ) - def featurize(x: int) -> list[float]: - return [x, x + 0.5, x + 0.33] - - scalars_df, scalars_pandas_df = scalars_dfs - multiindex_cols = ["rowindex", "string_col"] - scalars_df = scalars_df.reset_index().set_index(multiindex_cols) - scalars_pandas_df = scalars_pandas_df.reset_index().set_index(multiindex_cols) - - bf_int64_col = scalars_df["int64_too"] - bf_result = bf_int64_col.apply(featurize).to_pandas() - - pd_int64_col = scalars_pandas_df["int64_too"] - pd_result = pd_int64_col.apply(featurize) - - # ignore any dtype disparity - pandas.testing.assert_series_equal(pd_result, bf_result, check_dtype=False) - finally: - # clean up the gcp assets created for the remote function - cleanup_function_assets( - featurize, session.bqclient, session.cloudfunctionsclient - ) - - -@pytest.mark.flaky(retries=2, delay=120) -def test_remote_function_connection_path_format( - session, scalars_dfs, dataset_id, bq_cf_connection -): - try: - - @session.remote_function( - dataset=dataset_id, - bigquery_connection=f"projects/{session.bqclient.project}/locations/{session._location}/connections/{bq_cf_connection}", - reuse=False, - cloud_function_service_account="default", - ) - def foo(x: int) -> int: - return x + 1 - - scalars_df, scalars_pandas_df = scalars_dfs - - bf_int64_col = scalars_df["int64_too"] - bf_result = bf_int64_col.apply(foo).to_pandas() - - pd_int64_col = scalars_pandas_df["int64_too"] - pd_result = pd_int64_col.apply(foo) - - # ignore any dtype disparity - pandas.testing.assert_series_equal(pd_result, bf_result, check_dtype=False) - finally: - # clean up the gcp assets created for the remote function - cleanup_function_assets(foo, session.bqclient, session.cloudfunctionsclient) - - -@pytest.mark.flaky(retries=2, delay=120) -def test_remote_function_df_where_mask(session, dataset_id, scalars_dfs): - try: - # The return type has to be bool type for callable where condition. - def is_sum_positive(a, b): - return a + b > 0 - - is_sum_positive_mf = session.remote_function( - input_types=[int, int], - output_type=bool, - dataset=dataset_id, - reuse=False, - cloud_function_service_account="default", - )(is_sum_positive) - - scalars_df, scalars_pandas_df = scalars_dfs - int64_cols = ["int64_col", "int64_too"] - - bf_int64_df = scalars_df[int64_cols] - bf_int64_df_filtered = bf_int64_df.dropna() - pd_int64_df = scalars_pandas_df[int64_cols] - pd_int64_df_filtered = pd_int64_df.dropna() - - # Test callable condition in dataframe.where method. - bf_result = bf_int64_df_filtered.where(is_sum_positive_mf, 0).to_pandas() - # Pandas doesn't support such case, use following as workaround. - pd_result = pd_int64_df_filtered.where(pd_int64_df_filtered.sum(axis=1) > 0, 0) - - # Ignore any dtype difference. - pandas.testing.assert_frame_equal(bf_result, pd_result, check_dtype=False) - - # Test callable condition in dataframe.mask method. - bf_result = bf_int64_df_filtered.mask(is_sum_positive_mf, 0).to_pandas() - # Pandas doesn't support such case, use following as workaround. - pd_result = pd_int64_df_filtered.mask(pd_int64_df_filtered.sum(axis=1) > 0, 0) - - # Ignore any dtype difference. - pandas.testing.assert_frame_equal(bf_result, pd_result, check_dtype=False) - - finally: - # Clean up the gcp assets created for the remote function. - cleanup_function_assets( - is_sum_positive_mf, session.bqclient, ignore_failures=False - ) - - -@pytest.mark.flaky(retries=2, delay=120) -def test_remote_function_df_where_other_issue(session, dataset_id, scalars_df_index): - try: - - def the_sum(a, b): - return a + b - - the_sum_mf = session.remote_function( - input_types=[int, float], - output_type=float, - dataset=dataset_id, - reuse=False, - cloud_function_service_account="default", - )(the_sum) - - int64_cols = ["int64_col", "float64_col"] - bf_int64_df = scalars_df_index[int64_cols] - bf_int64_df_filtered = bf_int64_df.dropna() - - with pytest.raises( - ValueError, - match="Seires is not a supported replacement type!", - ): - # The execution of the callable other=the_sum_mf will return a - # Series, which is not a supported replacement type. - bf_int64_df_filtered.where(cond=bf_int64_df > 100, other=the_sum_mf) - - finally: - # Clean up the gcp assets created for the remote function. - cleanup_function_assets(the_sum_mf, session.bqclient, ignore_failures=False) - - -@pytest.mark.flaky(retries=2, delay=120) -def test_remote_function_df_where_mask_series(session, dataset_id, scalars_dfs): - try: - # The return type has to be bool type for callable where condition. - def is_sum_positive_series(s: pandas.Series) -> bool: - return s["int64_col"] + s["int64_too"] > 0 - - with pytest.raises( - TypeError, - match="Argument type hint must be Pandas Series, not BigFrames Series.", - ): - session.remote_function( - input_types=bigframes.series.Series, - dataset=dataset_id, - reuse=False, - cloud_function_service_account="default", - )(is_sum_positive_series) - - is_sum_positive_series_mf = session.remote_function( - dataset=dataset_id, - reuse=False, - cloud_function_service_account="default", - )(is_sum_positive_series) - - scalars_df, scalars_pandas_df = scalars_dfs - int64_cols = ["int64_col", "int64_too"] - - bf_int64_df = scalars_df[int64_cols] - bf_int64_df_filtered = bf_int64_df.dropna() - pd_int64_df = scalars_pandas_df[int64_cols] - pd_int64_df_filtered = pd_int64_df.dropna() - - # This is for callable `other` arg in dataframe.where method. - def func_for_other(x): - return -x - - # Test callable condition in dataframe.where method. - bf_result = bf_int64_df_filtered.where( - is_sum_positive_series_mf, func_for_other - ).to_pandas() - pd_result = pd_int64_df_filtered.where(is_sum_positive_series, func_for_other) - - # Ignore any dtype difference. - pandas.testing.assert_frame_equal(bf_result, pd_result, check_dtype=False) - - # Test callable condition in dataframe.mask method. - bf_result = bf_int64_df_filtered.mask( - is_sum_positive_series_mf, func_for_other - ).to_pandas() - pd_result = pd_int64_df_filtered.mask(is_sum_positive_series, func_for_other) - - # Ignore any dtype difference. - pandas.testing.assert_frame_equal(bf_result, pd_result, check_dtype=False) - - finally: - # Clean up the gcp assets created for the remote function. - cleanup_function_assets( - is_sum_positive_series_mf, session.bqclient, ignore_failures=False - ) - - -@pytest.mark.flaky(retries=2, delay=120) -def test_remote_function_series_where_mask(session, dataset_id, scalars_dfs): - try: - - def _ten_times(x): - return x * 10 - - ten_times_mf = session.remote_function( - input_types=float, - output_type=float, - dataset=dataset_id, - reuse=False, - cloud_function_service_account="default", - )(_ten_times) - - scalars, scalars_pandas = scalars_dfs - - bf_int64 = scalars["float64_col"] - bf_int64_filtered = bf_int64.dropna() - pd_int64 = scalars_pandas["float64_col"] - pd_int64_filtered = pd_int64.dropna() - - # Test series.where method: the cond is not a callable and the other is - # a callable (remote function). - bf_result = bf_int64_filtered.where( - cond=bf_int64_filtered < 0, other=ten_times_mf - ).to_pandas() - pd_result = pd_int64_filtered.where( - cond=pd_int64_filtered < 0, other=_ten_times - ) - - # Ignore any dtype difference. - pandas.testing.assert_series_equal(bf_result, pd_result, check_dtype=False) - - # Test series.mask method: the cond is not a callable and the other is - # a callable (remote function). - bf_result = bf_int64_filtered.mask( - cond=bf_int64_filtered < 0, other=ten_times_mf - ).to_pandas() - pd_result = pd_int64_filtered.mask(cond=pd_int64_filtered < 0, other=_ten_times) - - # Ignore any dtype difference. - pandas.testing.assert_series_equal(bf_result, pd_result, check_dtype=False) - - finally: - # Clean up the gcp assets created for the remote function. - cleanup_function_assets(ten_times_mf, session.bqclient, ignore_failures=False) - - -@pytest.mark.flaky(retries=2, delay=120) -def test_remote_function_series_apply_args(session, dataset_id, scalars_dfs): - try: - - @session.remote_function( - dataset=dataset_id, - reuse=False, - cloud_function_service_account="default", - ) - def foo(x: int, y: bool, z: float) -> str: - if y: - return f"{x}: y is True." - if z > 0.0: - return f"{x}: y is False and z is positive." - return f"{x}: y is False and z is non-positive." - - scalars_df, scalars_pandas_df = scalars_dfs - - args1 = (True, 10.0) - bf_result = scalars_df["int64_too"].apply(foo, args=args1).to_pandas() - pd_result = scalars_pandas_df["int64_too"].apply(foo, args=args1) - - # Ignore any dtype difference. - pandas.testing.assert_series_equal(bf_result, pd_result, check_dtype=False) - - args2 = (False, -10.0) - foo_ref = session.read_gbq_function(foo.bigframes_bigquery_function) - - bf_result = scalars_df["int64_too"].apply(foo_ref, args=args2).to_pandas() - pd_result = scalars_pandas_df["int64_too"].apply(foo, args=args2) - - # Ignore any dtype difference. - pandas.testing.assert_series_equal(bf_result, pd_result, check_dtype=False) - - finally: - # Clean up the gcp assets created for the remote function. - cleanup_function_assets(foo, session.bqclient, ignore_failures=False) diff --git a/tests/system/large/ml/conftest.py b/tests/system/large/ml/conftest.py deleted file mode 100644 index ffb02e8beb8..00000000000 --- a/tests/system/large/ml/conftest.py +++ /dev/null @@ -1,87 +0,0 @@ -# Copyright 2023 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 hashlib -import logging - -import google.cloud.exceptions -import pytest -from google.cloud import bigquery - -import bigframes -from bigframes.ml import core, linear_model - -PERMANENT_DATASET = "bigframes_testing" - - -@pytest.fixture(scope="session") -def dataset_id_permanent(bigquery_client: bigquery.Client, project_id: str) -> str: - """Create a dataset if it doesn't exist.""" - dataset_id = f"{project_id}.{PERMANENT_DATASET}" - dataset = bigquery.Dataset(dataset_id) - bigquery_client.create_dataset(dataset, exists_ok=True) - return dataset_id - - -@pytest.fixture(scope="session") -def penguins_bqml_linear_model(session, penguins_linear_model_name) -> core.BqmlModel: - model = session.bqclient.get_model(penguins_linear_model_name) - return core.BqmlModel(session, model) - - -@pytest.fixture(scope="function") -def penguins_linear_model_w_global_explain( - penguins_bqml_linear_model: core.BqmlModel, -) -> linear_model.LinearRegression: - bf_model = linear_model.LinearRegression(enable_global_explain=True) - bf_model._bqml_model = penguins_bqml_linear_model - return bf_model - - -@pytest.fixture(scope="session") -def penguins_table_id(test_data_tables) -> str: - return test_data_tables["penguins"] - - -@pytest.fixture(scope="session") -def penguins_linear_model_name( - session: bigframes.Session, dataset_id_permanent, penguins_table_id -) -> str: - """Provides a pretrained model as a test fixture that is cached across test runs. - This lets us run system tests without having to wait for a model.fit(...)""" - sql = f""" -CREATE OR REPLACE MODEL `$model_name` -OPTIONS ( - model_type='linear_reg', - input_label_cols=['body_mass_g'], - data_split_method='NO_SPLIT' -) AS -SELECT - * -FROM - `{penguins_table_id}` -WHERE - body_mass_g IS NOT NULL""" - # We use the SQL hash as the name to ensure the model is regenerated if this fixture is edited - model_name = f"{dataset_id_permanent}.penguins_linear_reg_{hashlib.md5(sql.encode()).hexdigest()}" - sql = sql.replace("$model_name", model_name) - - try: - session.bqclient.get_model(model_name) - except google.cloud.exceptions.NotFound: - logging.info( - "penguins_linear_model fixture was not found in the permanent dataset, regenerating it..." - ) - session.bqclient.query(sql).result() - finally: - return model_name diff --git a/tests/system/large/ml/test_cluster.py b/tests/system/large/ml/test_cluster.py index 9736199b176..eae6896669b 100644 --- a/tests/system/large/ml/test_cluster.py +++ b/tests/system/large/ml/test_cluster.py @@ -13,15 +13,17 @@ # limitations under the License. import pandas as pd +import pytest from bigframes.ml import cluster -from bigframes.testing import utils +from tests.system.utils import assert_pandas_df_equal_ignore_ordering +@pytest.mark.flaky(retries=2, delay=120) def test_cluster_configure_fit_score_predict( session, penguins_df_default_index, dataset_id ): - model = cluster.KMeans(n_clusters=3, init="random") + model = cluster.KMeans(n_clusters=3) df = penguins_df_default_index.dropna()[ [ @@ -86,81 +88,31 @@ def test_cluster_configure_fit_score_predict( # Check score to ensure the model was fitted score_result = model.score(new_penguins).to_pandas() - - eval_metrics = ["davies_bouldin_index", "mean_squared_distance"] - utils.check_pandas_df_schema_and_index(score_result, columns=eval_metrics, index=1) - - predictions = model.predict(new_penguins).to_pandas() - assert predictions.shape == (4, 9) - utils.check_pandas_df_schema_and_index( - predictions, - columns=["CENTROID_ID"], - index=["test1", "test2", "test3", "test4"], - col_exact=False, + score_expected = pd.DataFrame( + {"davies_bouldin_index": [1.502182], "mean_squared_distance": [1.953408]}, + dtype="Float64", ) + score_expected = score_expected.reindex(index=score_expected.index.astype("Int64")) - # save, load, check n_clusters to ensure configuration was kept - reloaded_model = model.to_gbq( - f"{dataset_id}.temp_configured_cluster_model", replace=True - ) - assert reloaded_model._bqml_model is not None - assert ( - f"{dataset_id}.temp_configured_cluster_model" - in reloaded_model._bqml_model.model_name + pd.testing.assert_frame_equal( + score_result, score_expected, check_exact=False, rtol=0.1 ) - assert reloaded_model.n_clusters == 3 - assert reloaded_model.init == "RANDOM" - assert reloaded_model.distance_type == "EUCLIDEAN" - assert reloaded_model.max_iter == 20 - assert reloaded_model.tol == 0.01 - -def test_cluster_configure_fit_load_params(penguins_df_default_index, dataset_id): - model = cluster.KMeans( - n_clusters=4, - init="random", - distance_type="cosine", - max_iter=30, - tol=0.001, + result = model.predict(new_penguins).to_pandas() + expected = pd.DataFrame( + {"CENTROID_ID": [2, 3, 1, 2]}, + dtype="Int64", + index=pd.Index(["test1", "test2", "test3", "test4"], dtype="string[pyarrow]"), ) - - df = penguins_df_default_index.dropna()[ - [ - "culmen_length_mm", - "culmen_depth_mm", - "flipper_length_mm", - "sex", - ] - ] - - # TODO(swast): How should we handle the default index? Currently, we get: - # "Column bigframes_index_0_z is not found in the input data to the - # EVALUATE function." - df = df.reset_index(drop=True) - - model.fit(df) + expected.index.name = "observation" + assert_pandas_df_equal_ignore_ordering(result, expected) # save, load, check n_clusters to ensure configuration was kept reloaded_model = model.to_gbq( f"{dataset_id}.temp_configured_cluster_model", replace=True ) - assert reloaded_model._bqml_model is not None assert ( f"{dataset_id}.temp_configured_cluster_model" in reloaded_model._bqml_model.model_name ) - assert reloaded_model.n_clusters == 4 - assert reloaded_model.init == "RANDOM" - assert reloaded_model.distance_type == "COSINE" - assert reloaded_model.max_iter == 30 - assert reloaded_model.tol == 0.001 - - -def test_model_centroids_with_custom_index(penguins_df_default_index): - model = cluster.KMeans(n_clusters=3) - penguins = penguins_df_default_index.set_index(["species", "island", "sex"]) - model.fit(penguins) - - assert ( - not model.cluster_centers_["feature"].isin(["species", "island", "sex"]).any() - ) + assert reloaded_model.n_clusters == 3 diff --git a/tests/system/large/ml/test_compose.py b/tests/system/large/ml/test_compose.py index 9279324b3c7..0c280e5d020 100644 --- a/tests/system/large/ml/test_compose.py +++ b/tests/system/large/ml/test_compose.py @@ -12,54 +12,28 @@ # See the License for the specific language governing permissions and # limitations under the License. -from bigframes.ml import compose, preprocessing -from bigframes.testing import utils +import pandas + +import bigframes.ml.cluster +import bigframes.ml.compose +import bigframes.ml.linear_model +import bigframes.ml.pipeline +import bigframes.ml.preprocessing def test_columntransformer_standalone_fit_and_transform( penguins_df_default_index, new_penguins_df ): - transformer = compose.ColumnTransformer( + transformer = bigframes.ml.compose.ColumnTransformer( [ ( "onehot", - preprocessing.OneHotEncoder(), - "species", - ), - ( - "starndard_scale", - preprocessing.StandardScaler(), - ["culmen_length_mm", "flipper_length_mm"], - ), - ( - "min_max_scale", - preprocessing.MinMaxScaler(), - ["culmen_length_mm"], - ), - ( - "increment", - compose.SQLScalarColumnTransformer("{0}+1"), - ["culmen_length_mm", "flipper_length_mm"], - ), - ( - "length", - compose.SQLScalarColumnTransformer( - "CASE WHEN {0} IS NULL THEN -1 ELSE LENGTH({0}) END", - target_column="len_{0}", - ), - "species", - ), - ( - "ohe", - compose.SQLScalarColumnTransformer( - "CASE WHEN {0}='Adelie Penguin (Pygoscelis adeliae)' THEN 1 ELSE 0 END", - target_column="ohe_adelie", - ), + bigframes.ml.preprocessing.OneHotEncoder(), "species", ), ( - "identity", - compose.SQLScalarColumnTransformer("{0}", target_column="{0}"), + "scale", + bigframes.ml.preprocessing.StandardScaler(), ["culmen_length_mm", "flipper_length_mm"], ), ] @@ -70,178 +44,83 @@ def test_columntransformer_standalone_fit_and_transform( ) result = transformer.transform(new_penguins_df).to_pandas() - utils.check_pandas_df_schema_and_index( - result, - columns=[ - "onehotencoded_species", - "standard_scaled_culmen_length_mm", - "min_max_scaled_culmen_length_mm", - "standard_scaled_flipper_length_mm", - "transformed_culmen_length_mm", - "transformed_flipper_length_mm", - "len_species", - "ohe_adelie", - "culmen_length_mm", - "flipper_length_mm", - ], - index=[1633, 1672, 1690], - col_exact=False, + # TODO: bug? feature columns seem to be in nondeterministic random order + # workaround: sort columns by name. Can't repro it in pantheon, so could + # be a bigframes issue... + result = result.reindex(sorted(result.columns), axis=1) + + expected = pandas.DataFrame( + { + "onehotencoded_species": [ + [{"index": 1, "value": 1.0}], + [{"index": 1, "value": 1.0}], + [{"index": 2, "value": 1.0}], + ], + "standard_scaled_culmen_length_mm": [ + -0.811119671289163, + -0.9945520581113803, + -1.104611490204711, + ], + "standard_scaled_flipper_length_mm": [-0.350044, -1.418336, -0.9198], + }, + index=pandas.Index([1633, 1672, 1690], dtype="Int64", name="tag_number"), ) - - -def test_columntransformer_standalone_fit_transform(new_penguins_df): - # rename column to ensure robustness to column names that must be escaped - new_penguins_df = new_penguins_df.rename(columns={"species": "123 'species'"}) - transformer = compose.ColumnTransformer( - [ - ( - "onehot", - preprocessing.OneHotEncoder(), - "123 'species'", - ), - ( - "standard_scale", - preprocessing.StandardScaler(), - ["culmen_length_mm", "flipper_length_mm"], - ), - ( - "length", - compose.SQLScalarColumnTransformer( - "CASE WHEN {0} IS NULL THEN -1 ELSE LENGTH({0}) END", - target_column="len_{0}", - ), - "123 'species'", - ), - ( - "identity", - compose.SQLScalarColumnTransformer("{0}", target_column="{0}"), - ["culmen_length_mm", "flipper_length_mm"], - ), - ] + expected.standard_scaled_culmen_length_mm = ( + expected.standard_scaled_culmen_length_mm.astype("Float64") ) - - result = transformer.fit_transform( - new_penguins_df[["123 'species'", "culmen_length_mm", "flipper_length_mm"]] - ).to_pandas() - - utils.check_pandas_df_schema_and_index( - result, - columns=[ - "onehotencoded_123 'species'", - "standard_scaled_culmen_length_mm", - "standard_scaled_flipper_length_mm", - "len_123 'species'", - "culmen_length_mm", - "flipper_length_mm", - ], - index=[1633, 1672, 1690], - col_exact=False, + expected.standard_scaled_flipper_length_mm = ( + expected.standard_scaled_flipper_length_mm.astype("Float64") ) + pandas.testing.assert_frame_equal(result, expected, rtol=1e-3) + -def test_columntransformer_save_load(new_penguins_df, dataset_id): - transformer = compose.ColumnTransformer( +def test_columntransformer_standalone_fit_transform(new_penguins_df): + transformer = bigframes.ml.compose.ColumnTransformer( [ ( "onehot", - preprocessing.OneHotEncoder(), + bigframes.ml.preprocessing.OneHotEncoder(), "species", ), ( - "standard_scale", - preprocessing.StandardScaler(), + "scale", + bigframes.ml.preprocessing.StandardScaler(), ["culmen_length_mm", "flipper_length_mm"], ), - ( - "length", - compose.SQLScalarColumnTransformer( - "CASE WHEN {0} IS NULL THEN -1 ELSE LENGTH({0}) END", - target_column="len_{0}", - ), - "species", - ), - ( - "identity", - compose.SQLScalarColumnTransformer("{0}", target_column="{0}"), - ["culmen_length_mm", "flipper_length_mm"], - ), - ( - "flexname", - compose.SQLScalarColumnTransformer( - "CASE WHEN {0} IS NULL THEN -1 ELSE LENGTH({0}) END", - target_column="Flex {0} Name", - ), - "species", - ), ] ) - transformer.fit( - new_penguins_df[["species", "culmen_length_mm", "flipper_length_mm"]] - ) - - reloaded_transformer = transformer.to_gbq( - f"{dataset_id}.temp_configured_model", replace=True - ) - - assert isinstance(reloaded_transformer, compose.ColumnTransformer) - - expected = [ - ( - "one_hot_encoder", - preprocessing.OneHotEncoder(max_categories=1000001, min_frequency=0), - "species", - ), - ("standard_scaler", preprocessing.StandardScaler(), "culmen_length_mm"), - ("standard_scaler", preprocessing.StandardScaler(), "flipper_length_mm"), - ( - "sql_scalar_column_transformer", - compose.SQLScalarColumnTransformer( - "CASE WHEN `species` IS NULL THEN -1 ELSE LENGTH(`species`) END", - target_column="len_species", - ), - "?len_species", - ), - ( - "sql_scalar_column_transformer", - compose.SQLScalarColumnTransformer( - "`flipper_length_mm`", target_column="flipper_length_mm" - ), - "?flipper_length_mm", - ), - ( - "sql_scalar_column_transformer", - compose.SQLScalarColumnTransformer( - "`culmen_length_mm`", target_column="culmen_length_mm" - ), - "?culmen_length_mm", - ), - ( - "sql_scalar_column_transformer", - compose.SQLScalarColumnTransformer( - "CASE WHEN `species` IS NULL THEN -1 ELSE LENGTH(`species`) END", - target_column="Flex species Name", - ), - "?Flex species Name", - ), - ] - assert set(reloaded_transformer.transformers) == set(expected) - assert reloaded_transformer._bqml_model is not None result = transformer.fit_transform( new_penguins_df[["species", "culmen_length_mm", "flipper_length_mm"]] ).to_pandas() - utils.check_pandas_df_schema_and_index( - result, - columns=[ - "onehotencoded_species", - "standard_scaled_culmen_length_mm", - "standard_scaled_flipper_length_mm", - "len_species", - "culmen_length_mm", - "flipper_length_mm", - "Flex species Name", - ], - index=[1633, 1672, 1690], - col_exact=False, + # TODO: bug? feature columns seem to be in nondeterministic random order + # workaround: sort columns by name. Can't repro it in pantheon, so could + # be a bigframes issue... + result = result.reindex(sorted(result.columns), axis=1) + + expected = pandas.DataFrame( + { + "onehotencoded_species": [ + [{"index": 1, "value": 1.0}], + [{"index": 1, "value": 1.0}], + [{"index": 2, "value": 1.0}], + ], + "standard_scaled_culmen_length_mm": [ + 1.313249, + -0.20198, + -1.111118, + ], + "standard_scaled_flipper_length_mm": [1.251098, -1.196588, -0.054338], + }, + index=pandas.Index([1633, 1672, 1690], dtype="Int64", name="tag_number"), ) + expected.standard_scaled_culmen_length_mm = ( + expected.standard_scaled_culmen_length_mm.astype("Float64") + ) + expected.standard_scaled_flipper_length_mm = ( + expected.standard_scaled_flipper_length_mm.astype("Float64") + ) + + pandas.testing.assert_frame_equal(result, expected, rtol=1e-3) diff --git a/tests/system/large/ml/test_core.py b/tests/system/large/ml/test_core.py index 6f0551b1efd..3b30d7eb1d9 100644 --- a/tests/system/large/ml/test_core.py +++ b/tests/system/large/ml/test_core.py @@ -12,8 +12,9 @@ # See the License for the specific language governing permissions and # limitations under the License. +import pandas + from bigframes.ml import globals -from bigframes.testing import utils def test_bqml_e2e(session, dataset_id, penguins_df_default_index, new_penguins_df): @@ -34,33 +35,41 @@ def test_bqml_e2e(session, dataset_id, penguins_df_default_index, new_penguins_d X_train, y_train, options={"model_type": "linear_reg"} ) - eval_metrics = [ - "mean_absolute_error", - "mean_squared_error", - "mean_squared_log_error", - "median_absolute_error", - "r2_score", - "explained_variance", - ] # no data - report evaluation from the automatic data split evaluate_result = model.evaluate().to_pandas() - utils.check_pandas_df_schema_and_index( - evaluate_result, columns=eval_metrics, index=1 + evaluate_expected = pandas.DataFrame( + { + "mean_absolute_error": [225.817334], + "mean_squared_error": [80540.705944], + "mean_squared_log_error": [0.004972], + "median_absolute_error": [173.080816], + "r2_score": [0.87529], + "explained_variance": [0.87529], + }, + dtype="Float64", + ) + evaluate_expected = evaluate_expected.reindex( + index=evaluate_expected.index.astype("Int64") + ) + pandas.testing.assert_frame_equal( + evaluate_result, evaluate_expected, check_exact=False, rtol=0.1 ) # evaluate on all training data evaluate_result = model.evaluate(df).to_pandas() - utils.check_pandas_df_schema_and_index( - evaluate_result, columns=eval_metrics, index=1 + pandas.testing.assert_frame_equal( + evaluate_result, evaluate_expected, check_exact=False, rtol=0.1 ) # predict new labels predictions = model.predict(new_penguins_df).to_pandas() - utils.check_pandas_df_schema_and_index( - predictions, - columns=["predicted_body_mass_g"], - index=[1633, 1672, 1690], - col_exact=False, + expected = pandas.DataFrame( + {"predicted_body_mass_g": [4030.1, 3280.8, 3177.9]}, + dtype="Float64", + index=pandas.Index([1633, 1672, 1690], name="tag_number", dtype="Int64"), + ) + pandas.testing.assert_frame_equal( + predictions[["predicted_body_mass_g"]], expected, check_exact=False, rtol=0.1 ) new_name = f"{dataset_id}.my_model" @@ -96,34 +105,42 @@ def test_bqml_manual_preprocessing_e2e( X_train, y_train, transforms=transforms, options=options ) - eval_metrics = [ - "mean_absolute_error", - "mean_squared_error", - "mean_squared_log_error", - "median_absolute_error", - "r2_score", - "explained_variance", - ] - # no data - report evaluation from the automatic data split evaluate_result = model.evaluate().to_pandas() - utils.check_pandas_df_schema_and_index( - evaluate_result, columns=eval_metrics, index=1 + evaluate_expected = pandas.DataFrame( + { + "mean_absolute_error": [309.477334], + "mean_squared_error": [152184.227218], + "mean_squared_log_error": [0.009524], + "median_absolute_error": [257.727777], + "r2_score": [0.764356], + "explained_variance": [0.764356], + }, + dtype="Float64", + ) + evaluate_expected = evaluate_expected.reindex( + index=evaluate_expected.index.astype("Int64") + ) + + pandas.testing.assert_frame_equal( + evaluate_result, evaluate_expected, check_exact=False, rtol=0.1 ) # evaluate on all training data evaluate_result = model.evaluate(df).to_pandas() - utils.check_pandas_df_schema_and_index( - evaluate_result, columns=eval_metrics, index=1 + pandas.testing.assert_frame_equal( + evaluate_result, evaluate_expected, check_exact=False, rtol=0.1 ) # predict new labels predictions = model.predict(new_penguins_df).to_pandas() - utils.check_pandas_df_schema_and_index( - predictions, - columns=["predicted_body_mass_g"], - index=[1633, 1672, 1690], - col_exact=False, + expected = pandas.DataFrame( + {"predicted_body_mass_g": [3968.8, 3176.3, 3545.2]}, + dtype="Float64", + index=pandas.Index([1633, 1672, 1690], name="tag_number", dtype="Int64"), + ) + pandas.testing.assert_frame_equal( + predictions[["predicted_body_mass_g"]], expected, check_exact=False, rtol=0.1 ) new_name = f"{dataset_id}.my_model" @@ -146,16 +163,25 @@ def test_bqml_standalone_transform(penguins_df_default_index, new_penguins_df): "ML.ONE_HOT_ENCODER(species, 'none', 1000000, 0) OVER() AS onehotencoded_species", ], ) - start_execution_count = model.session._metrics.execution_count - - transformed = model.transform(new_penguins_df) - end_execution_count = model.session._metrics.execution_count - assert end_execution_count - start_execution_count == 1 - - utils.check_pandas_df_schema_and_index( - transformed.to_pandas(), - columns=["scaled_culmen_length_mm", "onehotencoded_species"], - index=[1633, 1672, 1690], - col_exact=False, + transformed = model.transform(new_penguins_df).to_pandas() + expected = pandas.DataFrame( + { + "scaled_culmen_length_mm": [-0.8099, -0.9931, -1.103], + "onehotencoded_species": [ + [{"index": 1, "value": 1.0}], + [{"index": 1, "value": 1.0}], + [{"index": 2, "value": 1.0}], + ], + }, + index=pandas.Index([1633, 1672, 1690], name="tag_number", dtype="Int64"), + ) + expected["scaled_culmen_length_mm"] = expected["scaled_culmen_length_mm"].astype( + "Float64" + ) + pandas.testing.assert_frame_equal( + transformed[["scaled_culmen_length_mm", "onehotencoded_species"]], + expected, + check_exact=False, + rtol=0.1, ) diff --git a/tests/system/large/ml/test_decomposition.py b/tests/system/large/ml/test_decomposition.py index c36e8738162..a7049d4c18e 100644 --- a/tests/system/large/ml/test_decomposition.py +++ b/tests/system/large/ml/test_decomposition.py @@ -13,10 +13,8 @@ # limitations under the License. import pandas as pd -import pandas.testing from bigframes.ml import decomposition -from bigframes.testing import utils def test_decomposition_configure_fit_score_predict( @@ -46,176 +44,41 @@ def test_decomposition_configure_fit_score_predict( # Check score to ensure the model was fitted score_result = model.score(new_penguins).to_pandas() - utils.check_pandas_df_schema_and_index( - score_result, columns=["total_explained_variance_ratio"], index=1 + score_expected = pd.DataFrame( + { + "total_explained_variance_ratio": [0.812383], + }, + dtype="Float64", ) + score_expected = score_expected.reindex(index=score_expected.index.astype("Int64")) - result = model.predict(new_penguins).to_pandas() - utils.check_pandas_df_schema_and_index( - result, - columns=[ - "principal_component_1", - "principal_component_2", - "principal_component_3", - ], - index=[1633, 1672, 1690], - ) - - # save, load, check n_components to ensure configuration was kept - reloaded_model = model.to_gbq( - f"{dataset_id}.temp_configured_pca_model", replace=True - ) - assert reloaded_model._bqml_model is not None - assert ( - f"{dataset_id}.temp_configured_pca_model" - in reloaded_model._bqml_model.model_name - ) - assert reloaded_model.n_components == 3 - - -def test_decomposition_configure_fit_score_predict_params( - session, penguins_df_default_index, dataset_id -): - model = decomposition.PCA(n_components=5, svd_solver="randomized") - model.fit(penguins_df_default_index) - - new_penguins = session.read_pandas( - pd.DataFrame( - { - "tag_number": [1633, 1672, 1690], - "species": [ - "Adelie Penguin (Pygoscelis adeliae)", - "Gentoo penguin (Pygoscelis papua)", - "Adelie Penguin (Pygoscelis adeliae)", - ], - "island": ["Dream", "Biscoe", "Torgersen"], - "culmen_length_mm": [37.8, 46.5, 41.1], - "culmen_depth_mm": [18.1, 14.8, 18.6], - "flipper_length_mm": [193.0, 217.0, 189.0], - "body_mass_g": [3750.0, 5200.0, 3325.0], - "sex": ["MALE", "FEMALE", "MALE"], - } - ).set_index("tag_number") - ) - - # Check score to ensure the model was fitted - score_result = model.score(new_penguins).to_pandas() - utils.check_pandas_df_schema_and_index( - score_result, columns=["total_explained_variance_ratio"], index=1 + pd.testing.assert_frame_equal( + score_result, score_expected, check_exact=False, rtol=0.1 ) result = model.predict(new_penguins).to_pandas() - utils.check_pandas_df_schema_and_index( - result, - columns=[ - "principal_component_1", - "principal_component_2", - "principal_component_3", - "principal_component_4", - "principal_component_5", - ], - index=[1633, 1672, 1690], + expected = pd.DataFrame( + { + "principal_component_1": [-1.459, 2.258, -1.685], + "principal_component_2": [-1.120, -1.351, -0.874], + "principal_component_3": [-0.646, 0.443, -0.704], + }, + dtype="Float64", + index=pd.Index([1633, 1672, 1690], name="tag_number", dtype="Int64"), + ) + pd.testing.assert_frame_equal( + abs(result.sort_index()), # results may differ by a minus sign + abs(expected), + check_exact=False, + rtol=0.1, ) # save, load, check n_components to ensure configuration was kept reloaded_model = model.to_gbq( f"{dataset_id}.temp_configured_pca_model", replace=True ) - assert reloaded_model._bqml_model is not None assert ( f"{dataset_id}.temp_configured_pca_model" in reloaded_model._bqml_model.model_name ) - assert reloaded_model.n_components == 5 - assert reloaded_model.svd_solver == "RANDOMIZED" - - -def test_decomposition_configure_fit_load_float_component( - penguins_df_default_index, dataset_id -): - model = decomposition.PCA(n_components=0.2) - model.fit(penguins_df_default_index) - - # save, load, check n_components to ensure configuration was kept - reloaded_model = model.to_gbq( - f"{dataset_id}.temp_configured_pca_model", replace=True - ) - assert reloaded_model._bqml_model is not None - assert ( - f"{dataset_id}.temp_configured_pca_model" - in reloaded_model._bqml_model.model_name - ) - assert reloaded_model.n_components == 0.2 - - -def test_decomposition_configure_fit_load_none_component( - penguins_df_default_index, dataset_id -): - model = decomposition.PCA(n_components=None) - model.fit(penguins_df_default_index) - - # save, load, check n_components. Here n_components is the column size of the training input. - reloaded_model = model.to_gbq( - f"{dataset_id}.temp_configured_pca_model", replace=True - ) - assert reloaded_model._bqml_model is not None - assert ( - f"{dataset_id}.temp_configured_pca_model" - in reloaded_model._bqml_model.model_name - ) - assert reloaded_model.n_components == 7 - - -def test_decomposition_mf_configure_fit_load( - session, ratings_df_default_index, dataset_id -): - model = decomposition.MatrixFactorization( - num_factors=6, - feedback_type="explicit", - user_col="user_id", - item_col="item_id", - rating_col="rating", - l2_reg=9.83, - ) - - model.fit(ratings_df_default_index) - - reloaded_model = model.to_gbq( - f"{dataset_id}.temp_configured_mf_model", replace=True - ) - - new_ratings = session.read_pandas( - pd.DataFrame( - { - "user_id": ["11", "12", "13"], - "item_id": [1, 2, 3], - "rating": [1.0, 2.0, 3.0], - } - ) - ) - - # Make sure the input to score is not ignored. - scores_training_data = reloaded_model.score().to_pandas() - scores_new_ratings = reloaded_model.score(new_ratings).to_pandas() - pandas.testing.assert_index_equal( - scores_training_data.columns, scores_new_ratings.columns - ) - assert ( - scores_training_data["mean_squared_error"].iloc[0] - != scores_new_ratings["mean_squared_error"].iloc[0] - ) - - result = reloaded_model.predict(new_ratings).to_pandas() - - assert reloaded_model._bqml_model is not None - assert ( - f"{dataset_id}.temp_configured_mf_model" - in reloaded_model._bqml_model.model_name - ) - assert result is not None - assert reloaded_model.feedback_type == "explicit" - assert reloaded_model.num_factors == 6 - assert reloaded_model.user_col == "user_id" - assert reloaded_model.item_col == "item_id" - assert reloaded_model.rating_col == "rating" - assert reloaded_model.l2_reg == 9.83 + assert reloaded_model.n_components == 3 diff --git a/tests/system/large/ml/test_ensemble.py b/tests/system/large/ml/test_ensemble.py index eabd36ab387..a8613dfeb9b 100644 --- a/tests/system/large/ml/test_ensemble.py +++ b/tests/system/large/ml/test_ensemble.py @@ -12,13 +12,15 @@ # See the License for the specific language governing permissions and # limitations under the License. +from unittest import TestCase + +import pandas import pytest import bigframes.ml.ensemble -from bigframes.testing import utils -@pytest.mark.flaky(retries=2) +@pytest.mark.flaky(retries=2, delay=120) def test_xgbregressor_default_params(penguins_df_default_index, dataset_id): model = bigframes.ml.ensemble.XGBRegressor() @@ -38,22 +40,31 @@ def test_xgbregressor_default_params(penguins_df_default_index, dataset_id): # Check score to ensure the model was fitted result = model.score(X_train, y_train).to_pandas() - utils.check_pandas_df_schema_and_index( - result, columns=utils.ML_REGRESSION_METRICS, index=1 + expected = pandas.DataFrame( + { + "mean_absolute_error": [97.368139], + "mean_squared_error": [16284.877027], + "mean_squared_log_error": [0.0010189], + "median_absolute_error": [72.158691], + "r2_score": [0.974784], + "explained_variance": [0.974845], + }, + dtype="Float64", ) + expected = expected.reindex(index=expected.index.astype("Int64")) + pandas.testing.assert_frame_equal(result, expected, check_exact=False, rtol=0.1) # save, load, check parameters to ensure configuration was kept reloaded_model = model.to_gbq( f"{dataset_id}.temp_configured_xgbregressor_model", replace=True ) - assert reloaded_model._bqml_model is not None assert ( f"{dataset_id}.temp_configured_xgbregressor_model" in reloaded_model._bqml_model.model_name ) -@pytest.mark.flaky(retries=2) +@pytest.mark.flaky(retries=2, delay=120) def test_xgbregressor_dart_booster_multiple_params( penguins_df_default_index, dataset_id ): @@ -64,14 +75,14 @@ def test_xgbregressor_dart_booster_multiple_params( colsample_bytree=0.95, colsample_bylevel=0.95, colsample_bynode=0.95, - n_estimators=2, + num_parallel_tree=2, max_depth=4, subsample=0.95, reg_alpha=0.0001, reg_lambda=0.0001, learning_rate=0.015, max_iterations=4, - tol=0.02, + min_rel_progress=0.02, ) df = penguins_df_default_index.dropna().sample(n=70) @@ -90,38 +101,45 @@ def test_xgbregressor_dart_booster_multiple_params( # Check score to ensure the model was fitted result = model.score(X_train, y_train).to_pandas() - utils.check_pandas_df_schema_and_index( - result, columns=utils.ML_REGRESSION_METRICS, index=1 - ) + TestCase().assertSequenceEqual(result.shape, (1, 6)) + for col_name in [ + "mean_absolute_error", + "mean_squared_error", + "mean_squared_log_error", + "median_absolute_error", + "r2_score", + "explained_variance", + ]: + assert col_name in result.columns # save, load, check parameters to ensure configuration was kept reloaded_model = model.to_gbq( f"{dataset_id}.temp_configured_xgbregressor_model", replace=True ) - assert reloaded_model._bqml_model is not None assert ( f"{dataset_id}.temp_configured_xgbregressor_model" in reloaded_model._bqml_model.model_name ) assert reloaded_model.booster == "DART" - assert reloaded_model.dart_normalized_type == "TREE" + assert reloaded_model.dart_normalized_type == "tree" assert reloaded_model.tree_method == "AUTO" assert reloaded_model.colsample_bytree == 0.95 assert reloaded_model.colsample_bylevel == 0.95 assert reloaded_model.colsample_bynode == 0.95 + assert reloaded_model.early_stop is True assert reloaded_model.subsample == 0.95 assert reloaded_model.reg_alpha == 0.0001 assert reloaded_model.reg_lambda == 0.0001 assert reloaded_model.learning_rate == 0.015 assert reloaded_model.max_iterations == 4 - assert reloaded_model.tol == 0.02 + assert reloaded_model.min_rel_progress == 0.02 assert reloaded_model.gamma == 0.0 assert reloaded_model.max_depth == 4 assert reloaded_model.min_tree_child_weight == 2 - assert reloaded_model.n_estimators == 2 + assert reloaded_model.num_parallel_tree == 2 -@pytest.mark.flaky(retries=2) +@pytest.mark.flaky(retries=2, delay=120) def test_xgbclassifier_default_params(penguins_df_default_index, dataset_id): model = bigframes.ml.ensemble.XGBClassifier() @@ -140,22 +158,28 @@ def test_xgbclassifier_default_params(penguins_df_default_index, dataset_id): # Check score to ensure the model was fitted result = model.score(X_train, y_train).to_pandas() - utils.check_pandas_df_schema_and_index( - result, columns=utils.ML_CLASSFICATION_METRICS, index=1 - ) + TestCase().assertSequenceEqual(result.shape, (1, 6)) + for col_name in [ + "precision", + "recall", + "accuracy", + "f1_score", + "log_loss", + "roc_auc", + ]: + assert col_name in result.columns # save, load, check parameters to ensure configuration was kept reloaded_model = model.to_gbq( f"{dataset_id}.temp_configured_xgbclassifier_model", replace=True ) - assert reloaded_model._bqml_model is not None assert ( f"{dataset_id}.temp_configured_xgbclassifier_model" in reloaded_model._bqml_model.model_name ) -@pytest.mark.flaky(retries=2) +@pytest.mark.flaky(retries=2, delay=120) def test_xgbclassifier_dart_booster_multiple_params( penguins_df_default_index, dataset_id ): @@ -166,14 +190,14 @@ def test_xgbclassifier_dart_booster_multiple_params( colsample_bytree=0.95, colsample_bylevel=0.95, colsample_bynode=0.95, - n_estimators=2, + num_parallel_tree=2, max_depth=4, subsample=0.95, reg_alpha=0.0001, reg_lambda=0.0001, learning_rate=0.015, max_iterations=4, - tol=0.02, + min_rel_progress=0.02, ) df = penguins_df_default_index.dropna().sample(n=70) @@ -191,38 +215,45 @@ def test_xgbclassifier_dart_booster_multiple_params( # Check score to ensure the model was fitted result = model.score(X_train, y_train).to_pandas() - utils.check_pandas_df_schema_and_index( - result, columns=utils.ML_CLASSFICATION_METRICS, index=1 - ) + TestCase().assertSequenceEqual(result.shape, (1, 6)) + for col_name in [ + "precision", + "recall", + "accuracy", + "f1_score", + "log_loss", + "roc_auc", + ]: + assert col_name in result.columns # save, load, check parameters to ensure configuration was kept reloaded_model = model.to_gbq( f"{dataset_id}.temp_configured_xgbclassifier_model", replace=True ) - assert reloaded_model._bqml_model is not None assert ( f"{dataset_id}.temp_configured_xgbclassifier_model" in reloaded_model._bqml_model.model_name ) assert reloaded_model.booster == "DART" - assert reloaded_model.dart_normalized_type == "TREE" + assert reloaded_model.dart_normalized_type == "tree" assert reloaded_model.tree_method == "AUTO" assert reloaded_model.colsample_bytree == 0.95 assert reloaded_model.colsample_bylevel == 0.95 assert reloaded_model.colsample_bynode == 0.95 + assert reloaded_model.early_stop is True assert reloaded_model.subsample == 0.95 assert reloaded_model.reg_alpha == 0.0001 assert reloaded_model.reg_lambda == 0.0001 assert reloaded_model.learning_rate == 0.015 assert reloaded_model.max_iterations == 4 - assert reloaded_model.tol == 0.02 + assert reloaded_model.min_rel_progress == 0.02 assert reloaded_model.gamma == 0.0 assert reloaded_model.max_depth == 4 assert reloaded_model.min_tree_child_weight == 2 - assert reloaded_model.n_estimators == 2 + assert reloaded_model.num_parallel_tree == 2 -@pytest.mark.flaky(retries=2) +@pytest.mark.flaky(retries=2, delay=120) def test_randomforestregressor_default_params(penguins_df_default_index, dataset_id): model = bigframes.ml.ensemble.RandomForestRegressor() @@ -242,22 +273,28 @@ def test_randomforestregressor_default_params(penguins_df_default_index, dataset # Check score to ensure the model was fitted result = model.score(X_train, y_train).to_pandas() - utils.check_pandas_df_schema_and_index( - result, columns=utils.ML_REGRESSION_METRICS, index=1 - ) + TestCase().assertSequenceEqual(result.shape, (1, 6)) + for col_name in [ + "mean_absolute_error", + "mean_squared_error", + "mean_squared_log_error", + "median_absolute_error", + "r2_score", + "explained_variance", + ]: + assert col_name in result.columns # save, load, check parameters to ensure configuration was kept reloaded_model = model.to_gbq( f"{dataset_id}.temp_configured_randomforestregressor_model", replace=True ) - assert reloaded_model._bqml_model is not None assert ( f"{dataset_id}.temp_configured_randomforestregressor_model" in reloaded_model._bqml_model.model_name ) -@pytest.mark.flaky(retries=2) +@pytest.mark.flaky(retries=2, delay=120) def test_randomforestregressor_multiple_params(penguins_df_default_index, dataset_id): model = bigframes.ml.ensemble.RandomForestRegressor( tree_method="auto", @@ -265,12 +302,12 @@ def test_randomforestregressor_multiple_params(penguins_df_default_index, datase colsample_bytree=0.95, colsample_bylevel=0.95, colsample_bynode=0.95, - n_estimators=90, + num_parallel_tree=90, max_depth=14, subsample=0.95, reg_alpha=0.0001, reg_lambda=0.0001, - tol=0.02, + min_rel_progress=0.02, ) df = penguins_df_default_index.dropna().sample(n=70) @@ -289,15 +326,21 @@ def test_randomforestregressor_multiple_params(penguins_df_default_index, datase # Check score to ensure the model was fitted result = model.score(X_train, y_train).to_pandas() - utils.check_pandas_df_schema_and_index( - result, columns=utils.ML_REGRESSION_METRICS, index=1 - ) + TestCase().assertSequenceEqual(result.shape, (1, 6)) + for col_name in [ + "mean_absolute_error", + "mean_squared_error", + "mean_squared_log_error", + "median_absolute_error", + "r2_score", + "explained_variance", + ]: + assert col_name in result.columns # save, load, check parameters to ensure configuration was kept reloaded_model = model.to_gbq( f"{dataset_id}.temp_configured_randomforestregressor_model", replace=True ) - assert reloaded_model._bqml_model is not None assert ( f"{dataset_id}.temp_configured_randomforestregressor_model" in reloaded_model._bqml_model.model_name @@ -306,18 +349,19 @@ def test_randomforestregressor_multiple_params(penguins_df_default_index, datase assert reloaded_model.colsample_bytree == 0.95 assert reloaded_model.colsample_bylevel == 0.95 assert reloaded_model.colsample_bynode == 0.95 + assert reloaded_model.early_stop is True assert reloaded_model.subsample == 0.95 assert reloaded_model.reg_alpha == 0.0001 assert reloaded_model.reg_lambda == 0.0001 - assert reloaded_model.tol == 0.02 + assert reloaded_model.min_rel_progress == 0.02 assert reloaded_model.gamma == 0.0 assert reloaded_model.max_depth == 14 assert reloaded_model.min_tree_child_weight == 2 - assert reloaded_model.n_estimators == 90 + assert reloaded_model.num_parallel_tree == 90 assert reloaded_model.enable_global_explain is False -@pytest.mark.flaky(retries=2) +@pytest.mark.flaky(retries=2, delay=120) def test_randomforestclassifier_default_params(penguins_df_default_index, dataset_id): model = bigframes.ml.ensemble.RandomForestClassifier() @@ -336,35 +380,41 @@ def test_randomforestclassifier_default_params(penguins_df_default_index, datase # Check score to ensure the model was fitted result = model.score(X_train, y_train).to_pandas() - utils.check_pandas_df_schema_and_index( - result, columns=utils.ML_CLASSFICATION_METRICS, index=1 - ) + TestCase().assertSequenceEqual(result.shape, (1, 6)) + for col_name in [ + "precision", + "recall", + "accuracy", + "f1_score", + "log_loss", + "roc_auc", + ]: + assert col_name in result.columns # save, load, check parameters to ensure configuration was kept reloaded_model = model.to_gbq( f"{dataset_id}.temp_configured_randomforestclassifier_model", replace=True ) - assert reloaded_model._bqml_model is not None assert ( f"{dataset_id}.temp_configured_randomforestclassifier_model" in reloaded_model._bqml_model.model_name ) -@pytest.mark.flaky(retries=2) +@pytest.mark.flaky(retries=2, delay=120) def test_randomforestclassifier_multiple_params(penguins_df_default_index, dataset_id): model = bigframes.ml.ensemble.RandomForestClassifier( - tree_method="auto", + tree_method="AUTO", min_tree_child_weight=2, colsample_bytree=0.95, colsample_bylevel=0.95, colsample_bynode=0.95, - n_estimators=90, + num_parallel_tree=90, max_depth=14, subsample=0.95, reg_alpha=0.0001, reg_lambda=0.0001, - tol=0.02, + min_rel_progress=0.02, ) df = penguins_df_default_index.dropna().sample(n=70) @@ -382,29 +432,36 @@ def test_randomforestclassifier_multiple_params(penguins_df_default_index, datas # Check score to ensure the model was fitted result = model.score(X_train, y_train).to_pandas() - utils.check_pandas_df_schema_and_index( - result, columns=utils.ML_CLASSFICATION_METRICS, index=1 - ) + TestCase().assertSequenceEqual(result.shape, (1, 6)) + for col_name in [ + "precision", + "recall", + "accuracy", + "f1_score", + "log_loss", + "roc_auc", + ]: + assert col_name in result.columns # save, load, check parameters to ensure configuration was kept reloaded_model = model.to_gbq( f"{dataset_id}.temp_configured_randomforestclassifier_model", replace=True ) - assert reloaded_model._bqml_model is not None assert ( f"{dataset_id}.temp_configured_randomforestclassifier_model" in reloaded_model._bqml_model.model_name ) - assert reloaded_model.tree_method.casefold() == "auto" + assert reloaded_model.tree_method == "AUTO" assert reloaded_model.colsample_bytree == 0.95 assert reloaded_model.colsample_bylevel == 0.95 assert reloaded_model.colsample_bynode == 0.95 + assert reloaded_model.early_stop is True assert reloaded_model.subsample == 0.95 assert reloaded_model.reg_alpha == 0.0001 assert reloaded_model.reg_lambda == 0.0001 - assert reloaded_model.tol == 0.02 + assert reloaded_model.min_rel_progress == 0.02 assert reloaded_model.gamma == 0.0 assert reloaded_model.max_depth == 14 assert reloaded_model.min_tree_child_weight == 2 - assert reloaded_model.n_estimators == 90 + assert reloaded_model.num_parallel_tree == 90 assert reloaded_model.enable_global_explain is False diff --git a/tests/system/large/ml/test_forecasting.py b/tests/system/large/ml/test_forecasting.py index 8500ad9d5f1..33b835e8522 100644 --- a/tests/system/large/ml/test_forecasting.py +++ b/tests/system/large/ml/test_forecasting.py @@ -12,182 +12,37 @@ # See the License for the specific language governing permissions and # limitations under the License. -import pytest +import pandas as pd from bigframes.ml import forecasting -from bigframes.testing import utils -ARIMA_EVALUATE_OUTPUT_COL = [ - "non_seasonal_p", - "non_seasonal_d", - "non_seasonal_q", - "has_drift", - "log_likelihood", - "AIC", - "variance", - "seasonal_periods", - "has_holiday_effect", - "has_spikes_and_dips", - "has_step_changes", - "error_message", -] - -def _fit_arima_model(time_series_df_default_index): +def test_arima_plus_model_fit_score( + time_series_df_default_index, dataset_id, new_time_series_df +): model = forecasting.ARIMAPlus() - X_train = time_series_df_default_index["parsed_date"] + X_train = time_series_df_default_index[["parsed_date"]] y_train = time_series_df_default_index[["total_visits"]] - return model, X_train, y_train - - -@pytest.fixture(scope="module") -def arima_model(time_series_df_default_index): - model, X_train, y_train = _fit_arima_model(time_series_df_default_index) model.fit(X_train, y_train) - return model - - -@pytest.fixture(scope="module") -def arima_model_w_id(time_series_df_default_index): - model, X_train, y_train = _fit_arima_model(time_series_df_default_index) - id_cols = time_series_df_default_index[["id"]] - model.fit(X_train, y_train, id_col=id_cols) - return model - -@pytest.mark.parametrize("id_col_name", [None, "id"]) -def test_arima_plus_model_fit_score( - dataset_id, - new_time_series_df, - new_time_series_df_w_id, - arima_model, - arima_model_w_id, - id_col_name, -): - curr_model = arima_model_w_id if id_col_name else arima_model - if id_col_name: - result = curr_model.score( - new_time_series_df_w_id[["parsed_date"]], - new_time_series_df_w_id[["total_visits"]], - id_col=new_time_series_df_w_id[[id_col_name]], - ).to_pandas() - else: - result = curr_model.score( - new_time_series_df[["parsed_date"]], new_time_series_df[["total_visits"]] - ).to_pandas() - expected_columns = [ - "mean_absolute_error", - "mean_squared_error", - "root_mean_squared_error", - "mean_absolute_percentage_error", - "symmetric_mean_absolute_percentage_error", - ] - if id_col_name: - expected_columns.insert(0, id_col_name) - utils.check_pandas_df_schema_and_index( - result, - columns=expected_columns, - index=2 if id_col_name else 1, - col_exact=False, - ) + result = model.score( + new_time_series_df[["parsed_date"]], new_time_series_df[["total_visits"]] + ).to_pandas() + expected = pd.DataFrame( + { + "mean_absolute_error": [154.742547], + "mean_squared_error": [26844.868855], + "root_mean_squared_error": [163.844038], + "mean_absolute_percentage_error": [6.189702], + "symmetric_mean_absolute_percentage_error": [6.097155], + }, + dtype="Float64", + ) + expected = expected.reindex(index=expected.index.astype("Int64")) + pd.testing.assert_frame_equal(result, expected, check_exact=False, rtol=0.1) # save, load to ensure configuration was kept - reloaded_model = curr_model.to_gbq( - f"{dataset_id}.temp_arima_plus_model", replace=True - ) + reloaded_model = model.to_gbq(f"{dataset_id}.temp_configured_model", replace=True) assert ( - f"{dataset_id}.temp_arima_plus_model" in reloaded_model._bqml_model.model_name + f"{dataset_id}.temp_configured_model" in reloaded_model._bqml_model.model_name ) - - -@pytest.mark.parametrize("id_col_name", [None, "id"]) -def test_arima_plus_model_fit_summary( - dataset_id, arima_model, arima_model_w_id, id_col_name -): - curr_model = arima_model_w_id if id_col_name else arima_model - result = curr_model.summary().to_pandas() - expected_columns = ( - [id_col_name] + ARIMA_EVALUATE_OUTPUT_COL - if id_col_name - else ARIMA_EVALUATE_OUTPUT_COL - ) - utils.check_pandas_df_schema_and_index( - result, columns=expected_columns, index=2 if id_col_name else 1 - ) - # save, load to ensure configuration was kept - reloaded_model = curr_model.to_gbq( - f"{dataset_id}.temp_arima_plus_model", replace=True - ) - assert ( - f"{dataset_id}.temp_arima_plus_model" in reloaded_model._bqml_model.model_name - ) - - -@pytest.mark.parametrize("id_col_name", [None, "id"]) -def test_arima_coefficients(arima_model, arima_model_w_id, id_col_name): - result = ( - arima_model_w_id.coef_.to_pandas() - if id_col_name - else arima_model.coef_.to_pandas() - ) - expected_columns = [ - "ar_coefficients", - "ma_coefficients", - "intercept_or_drift", - ] - if id_col_name: - expected_columns.insert(0, id_col_name) - utils.check_pandas_df_schema_and_index( - result, columns=expected_columns, index=2 if id_col_name else 1 - ) - - -@pytest.mark.parametrize("id_col_name", [None, "id"]) -def test_arima_plus_model_fit_params( - time_series_df_default_index, dataset_id, id_col_name -): - model = forecasting.ARIMAPlus( - horizon=100, - auto_arima=True, - auto_arima_max_order=4, - auto_arima_min_order=1, - data_frequency="daily", - holiday_region="US", - clean_spikes_and_dips=False, - adjust_step_changes=False, - forecast_limit_lower_bound=0.0, - time_series_length_fraction=0.5, - min_time_series_length=10, - trend_smoothing_window_size=5, - decompose_time_series=False, - ) - - X_train = time_series_df_default_index[["parsed_date"]] - y_train = time_series_df_default_index["total_visits"] - if id_col_name is None: - model.fit(X_train, y_train) - else: - id_cols = time_series_df_default_index[[id_col_name]] - model.fit(X_train, y_train, id_col=id_cols) - - # save, load to ensure configuration was kept - reloaded_model = model.to_gbq(f"{dataset_id}.temp_arima_plus_model", replace=True) - assert reloaded_model._bqml_model is not None - assert ( - f"{dataset_id}.temp_arima_plus_model" in reloaded_model._bqml_model.model_name - ) - - assert reloaded_model.horizon == 100 - assert reloaded_model.auto_arima is True - assert reloaded_model.auto_arima_max_order == 4 - assert reloaded_model.auto_arima_min_order == 1 - assert reloaded_model.data_frequency == "DAILY" - assert reloaded_model.holiday_region == "US" - assert reloaded_model.clean_spikes_and_dips is False - assert reloaded_model.adjust_step_changes is False - # TODO(b/391399223): API must return forecastLimitLowerBound for the following assertion - # assert reloaded_model.forecast_limit_lower_bound == 0.0 - assert reloaded_model.time_series_length_fraction == 0.5 - assert reloaded_model.min_time_series_length == 10 - assert reloaded_model.trend_smoothing_window_size == 5 - assert reloaded_model.decompose_time_series is False diff --git a/tests/system/large/ml/test_linear_model.py b/tests/system/large/ml/test_linear_model.py index 60edc717a5a..a0f4182e6fb 100644 --- a/tests/system/large/ml/test_linear_model.py +++ b/tests/system/large/ml/test_linear_model.py @@ -13,11 +13,8 @@ # limitations under the License. import pandas as pd -import pytest import bigframes.ml.linear_model -from bigframes.ml import model_selection -from bigframes.testing import utils def test_linear_regression_configure_fit_score(penguins_df_default_index, dataset_id): @@ -39,128 +36,42 @@ def test_linear_regression_configure_fit_score(penguins_df_default_index, datase # Check score to ensure the model was fitted result = model.score(X_train, y_train).to_pandas() - utils.check_pandas_df_schema_and_index( - result, columns=utils.ML_REGRESSION_METRICS, index=1 - ) + expected = pd.DataFrame( + { + "mean_absolute_error": [225.735767], + "mean_squared_error": [80417.461828], + "mean_squared_log_error": [0.004967], + "median_absolute_error": [172.543702], + "r2_score": [0.87548], + "explained_variance": [0.87548], + }, + dtype="Float64", + ) + expected = expected.reindex(index=expected.index.astype("Int64")) + pd.testing.assert_frame_equal(result, expected, check_exact=False, rtol=0.1) # save, load, check parameters to ensure configuration was kept reloaded_model = model.to_gbq(f"{dataset_id}.temp_configured_model", replace=True) - assert reloaded_model._bqml_model is not None - assert ( - f"{dataset_id}.temp_configured_model" in reloaded_model._bqml_model.model_name - ) - assert reloaded_model.optimize_strategy == "NORMAL_EQUATION" - assert reloaded_model.fit_intercept is True - assert reloaded_model.calculate_p_values is False - assert reloaded_model.enable_global_explain is False - assert reloaded_model.l1_reg is None - assert reloaded_model.l2_reg == 0.0 - assert reloaded_model.learning_rate is None - assert reloaded_model.learning_rate_strategy == "line_search" - assert reloaded_model.ls_init_learning_rate is None - assert reloaded_model.max_iterations == 20 - assert reloaded_model.tol == 0.01 - - -@pytest.mark.parametrize( - "df_fixture", - [ - "penguins_df_default_index", - "penguins_df_null_index", - ], -) -def test_linear_regression_configure_fit_with_eval_score( - df_fixture, dataset_id, request -): - df = request.getfixturevalue(df_fixture) - model = bigframes.ml.linear_model.LinearRegression() - - df = df.dropna() - X = df[ - [ - "species", - "island", - "culmen_length_mm", - "culmen_depth_mm", - "flipper_length_mm", - "sex", - ] - ] - y = df[["body_mass_g"]] - - X_train, X_eval, y_train, y_eval = model_selection.train_test_split(X, y) - - model.fit(X_train, y_train, X_eval=X_eval, y_eval=y_eval) - - # Check score to ensure the model was fitted - result = model.score(X_eval, y_eval).to_pandas() - utils.check_pandas_df_schema_and_index( - result, columns=utils.ML_REGRESSION_METRICS, index=1 - ) - - # save, load, check parameters to ensure configuration was kept - bq_model_name = f"{dataset_id}.temp_configured_model" - reloaded_model = model.to_gbq(bq_model_name, replace=True) - assert reloaded_model._bqml_model is not None assert ( f"{dataset_id}.temp_configured_model" in reloaded_model._bqml_model.model_name ) assert reloaded_model.optimize_strategy == "NORMAL_EQUATION" assert reloaded_model.fit_intercept is True assert reloaded_model.calculate_p_values is False + assert reloaded_model.early_stop is True assert reloaded_model.enable_global_explain is False - assert reloaded_model.l1_reg is None assert reloaded_model.l2_reg == 0.0 - assert reloaded_model.learning_rate is None - assert reloaded_model.learning_rate_strategy == "line_search" - assert reloaded_model.ls_init_learning_rate is None + assert reloaded_model.learn_rate_strategy == "line_search" + assert reloaded_model.ls_init_learn_rate == 0.1 assert reloaded_model.max_iterations == 20 - assert reloaded_model.tol == 0.01 - - # make sure the bqml model was internally created with custom split - bq_model = df._session.bqclient.get_model(bq_model_name) - last_fitting = bq_model.training_runs[-1]["trainingOptions"] - assert last_fitting["dataSplitMethod"] == "CUSTOM" - assert "dataSplitColumn" in last_fitting - - # make sure the bqml model has the same evaluation metrics attached as - # returned by model.score() - bq_model_expected_eval_metrics = result[utils.ML_REGRESSION_METRICS[:5]] - bq_model_eval_metrics = bq_model.training_runs[-1]["evaluationMetrics"][ - "regressionMetrics" - ] - bq_model_eval_metrics = pd.DataFrame( - [ - [ - bq_model_eval_metrics["meanAbsoluteError"], - bq_model_eval_metrics["meanSquaredError"], - bq_model_eval_metrics["meanSquaredLogError"], - bq_model_eval_metrics["medianAbsoluteError"], - bq_model_eval_metrics["rSquared"], - ] - ], - columns=utils.ML_REGRESSION_METRICS[:5], - ) - pd.testing.assert_frame_equal( - bq_model_expected_eval_metrics, - bq_model_eval_metrics, - check_dtype=False, - check_index_type=False, - ) + assert reloaded_model.min_rel_progress == 0.01 def test_linear_regression_customized_params_fit_score( penguins_df_default_index, dataset_id ): model = bigframes.ml.linear_model.LinearRegression( - fit_intercept=False, - l2_reg=0.2, - tol=0.02, - l1_reg=0.2, - max_iterations=30, - optimize_strategy="batch_gradient_descent", - learning_rate_strategy="constant", - learning_rate=0.2, + fit_intercept=False, l2_reg=0.1, min_rel_progress=0.01 ) df = penguins_df_default_index.dropna() @@ -179,96 +90,35 @@ def test_linear_regression_customized_params_fit_score( # Check score to ensure the model was fitted result = model.score(X_train, y_train).to_pandas() - utils.check_pandas_df_schema_and_index( - result, columns=utils.ML_REGRESSION_METRICS, index=1 - ) - - # save, load, check parameters to ensure configuration was kept - reloaded_model = model.to_gbq(f"{dataset_id}.temp_configured_model", replace=True) - assert reloaded_model._bqml_model is not None - assert ( - f"{dataset_id}.temp_configured_model" in reloaded_model._bqml_model.model_name - ) - assert reloaded_model.optimize_strategy == "BATCH_GRADIENT_DESCENT" - assert reloaded_model.fit_intercept is False - assert reloaded_model.calculate_p_values is False - assert reloaded_model.enable_global_explain is False - assert reloaded_model.l1_reg == 0.2 - assert reloaded_model.l2_reg == 0.2 - assert reloaded_model.ls_init_learning_rate is None - assert reloaded_model.max_iterations == 30 - assert reloaded_model.tol == 0.02 - assert reloaded_model.learning_rate_strategy == "CONSTANT" - assert reloaded_model.learning_rate == 0.2 - - -def test_unordered_mode_linear_regression_configure_fit_score_predict( - unordered_session, penguins_table_id, dataset_id -): - model = bigframes.ml.linear_model.LinearRegression() - - df = unordered_session.read_gbq(penguins_table_id).dropna() - X_train = df[ - [ - "species", - "island", - "culmen_length_mm", - "culmen_depth_mm", - "flipper_length_mm", - "sex", - ] - ] - y_train = df[["body_mass_g"]] - - start_execution_count = df._block._expr.session._metrics.execution_count - model.fit(X_train, y_train) - end_execution_count = df._block._expr.session._metrics.execution_count - # The fit function initiates two queries: the first generates and caches - # the training data, while the second creates and fits the model. - assert end_execution_count - start_execution_count == 2 - - # Check score to ensure the model was fitted - start_execution_count = end_execution_count - result = model.score(X_train, y_train).to_pandas() - end_execution_count = df._block._expr.session._metrics.execution_count - # The score function and to_pandas reuse same result. - assert end_execution_count - start_execution_count == 1 - - utils.check_pandas_df_schema_and_index( - result, columns=utils.ML_REGRESSION_METRICS, index=1 - ) + expected = pd.DataFrame( + { + "mean_absolute_error": [226.108411], + "mean_squared_error": [80459.668456], + "mean_squared_log_error": [0.00497], + "median_absolute_error": [171.618872], + "r2_score": [0.875415], + "explained_variance": [0.875417], + }, + dtype="Float64", + ) + expected = expected.reindex(index=expected.index.astype("Int64")) + pd.testing.assert_frame_equal(result, expected, check_exact=False, rtol=0.1) # save, load, check parameters to ensure configuration was kept reloaded_model = model.to_gbq(f"{dataset_id}.temp_configured_model", replace=True) - assert reloaded_model._bqml_model is not None assert ( f"{dataset_id}.temp_configured_model" in reloaded_model._bqml_model.model_name ) assert reloaded_model.optimize_strategy == "NORMAL_EQUATION" - assert reloaded_model.fit_intercept is True + assert reloaded_model.fit_intercept is False assert reloaded_model.calculate_p_values is False + assert reloaded_model.early_stop is True assert reloaded_model.enable_global_explain is False - assert reloaded_model.l1_reg is None - assert reloaded_model.l2_reg == 0.0 - assert reloaded_model.learning_rate is None - assert reloaded_model.learning_rate_strategy == "line_search" - assert reloaded_model.ls_init_learning_rate is None + assert reloaded_model.l2_reg == 0.1 + assert reloaded_model.learn_rate_strategy == "line_search" + assert reloaded_model.ls_init_learn_rate == 0.1 assert reloaded_model.max_iterations == 20 - assert reloaded_model.tol == 0.01 - - start_execution_count = df._block._expr.session._metrics.execution_count - pred = reloaded_model.predict(df) - end_execution_count = df._block._expr.session._metrics.execution_count - assert end_execution_count - start_execution_count == 1 - utils.check_pandas_df_schema_and_index( - pred, - columns=("predicted_body_mass_g",), - col_exact=False, - index=334, - ) - - -# TODO(garrettwu): add tests for param warm_start. Requires a trained model. + assert reloaded_model.min_rel_progress == 0.01 def test_logistic_regression_configure_fit_score(penguins_df_default_index, dataset_id): @@ -290,110 +140,37 @@ def test_logistic_regression_configure_fit_score(penguins_df_default_index, data # Check score to ensure the model was fitted result = model.score(X_train, y_train).to_pandas() - utils.check_pandas_df_schema_and_index( - result, columns=utils.ML_CLASSFICATION_METRICS, index=1 - ) + expected = pd.DataFrame( + { + "precision": [0.616753], + "recall": [0.618615], + "accuracy": [0.92515], + "f1_score": [0.617681], + "log_loss": [1.498832], + "roc_auc": [0.975807], + }, + dtype="Float64", + ) + expected = expected.reindex(index=expected.index.astype("Int64")) + pd.testing.assert_frame_equal(result, expected, check_exact=False, rtol=0.1) # save, load, check parameters to ensure configuration was kept reloaded_model = model.to_gbq( f"{dataset_id}.temp_configured_logistic_reg_model", replace=True ) - assert reloaded_model._bqml_model is not None assert ( f"{dataset_id}.temp_configured_logistic_reg_model" in reloaded_model._bqml_model.model_name ) assert reloaded_model.fit_intercept is True - assert reloaded_model.class_weight is None - - -def test_logistic_regression_configure_fit_with_eval_score( - penguins_df_default_index, dataset_id -): - model = bigframes.ml.linear_model.LogisticRegression() - - df = penguins_df_default_index.dropna() - df = df[df["sex"].isin(["MALE", "FEMALE"])] - - X = df[ - [ - "species", - "island", - "culmen_length_mm", - "culmen_depth_mm", - "flipper_length_mm", - "body_mass_g", - ] - ] - y = df[["sex"]] - - X_train, X_eval, y_train, y_eval = model_selection.train_test_split(X, y) - - model.fit(X_train, y_train, X_eval=X_eval, y_eval=y_eval) - - # Check score to ensure the model was fitted - result = model.score(X_eval, y_eval).to_pandas() - utils.check_pandas_df_schema_and_index( - result, columns=utils.ML_CLASSFICATION_METRICS, index=1 - ) - - # save, load, check parameters to ensure configuration was kept - bq_model_name = f"{dataset_id}.temp_configured_logistic_reg_model" - reloaded_model = model.to_gbq(bq_model_name, replace=True) - assert reloaded_model._bqml_model is not None - assert ( - f"{dataset_id}.temp_configured_logistic_reg_model" - in reloaded_model._bqml_model.model_name - ) - assert reloaded_model.fit_intercept is True - assert reloaded_model.class_weight is None - - # make sure the bqml model was internally created with custom split - bq_model = penguins_df_default_index._session.bqclient.get_model(bq_model_name) - last_fitting = bq_model.training_runs[-1]["trainingOptions"] - assert last_fitting["dataSplitMethod"] == "CUSTOM" - assert "dataSplitColumn" in last_fitting - - # make sure the bqml model has the same evaluation metrics attached as - # returned by model.score() - bq_model_expected_eval_metrics = result - bq_model_eval_metrics = bq_model.training_runs[-1]["evaluationMetrics"][ - "binaryClassificationMetrics" - ]["aggregateClassificationMetrics"] - bq_model_eval_metrics = pd.DataFrame( - [ - [ - bq_model_eval_metrics["precision"], - bq_model_eval_metrics["recall"], - bq_model_eval_metrics["accuracy"], - bq_model_eval_metrics["f1Score"], - bq_model_eval_metrics["logLoss"], - bq_model_eval_metrics["rocAuc"], - ] - ], - columns=utils.ML_CLASSFICATION_METRICS, - ) - pd.testing.assert_frame_equal( - bq_model_expected_eval_metrics, - bq_model_eval_metrics, - check_dtype=False, - check_index_type=False, - ) + assert reloaded_model.class_weights is None def test_logistic_regression_customized_params_fit_score( penguins_df_default_index, dataset_id ): model = bigframes.ml.linear_model.LogisticRegression( - fit_intercept=False, - class_weight="balanced", - l2_reg=0.2, - tol=0.02, - l1_reg=0.2, - max_iterations=30, - optimize_strategy="batch_gradient_descent", - learning_rate_strategy="constant", - learning_rate=0.2, + fit_intercept=False, class_weights="balanced" ) df = penguins_df_default_index.dropna() X_train = df[ @@ -410,90 +187,27 @@ def test_logistic_regression_customized_params_fit_score( # Check score to ensure the model was fitted result = model.score(X_train, y_train).to_pandas() - utils.check_pandas_df_schema_and_index( - result, columns=utils.ML_CLASSFICATION_METRICS, index=1 - ) + expected = pd.DataFrame( + { + "precision": [0.58483], + "recall": [0.586616], + "accuracy": [0.877246], + "f1_score": [0.58571], + "log_loss": [1.032699], + "roc_auc": [0.924132], + }, + dtype="Float64", + ) + expected = expected.reindex(index=expected.index.astype("Int64")) + pd.testing.assert_frame_equal(result, expected, check_exact=False, rtol=0.1) # save, load, check parameters to ensure configuration was kept reloaded_model = model.to_gbq( f"{dataset_id}.temp_configured_logistic_reg_model", replace=True ) - assert reloaded_model._bqml_model is not None assert ( f"{dataset_id}.temp_configured_logistic_reg_model" in reloaded_model._bqml_model.model_name ) assert reloaded_model.fit_intercept is False - assert reloaded_model.class_weight == "balanced" - assert reloaded_model.calculate_p_values is False - assert reloaded_model.enable_global_explain is False - assert reloaded_model.l1_reg == 0.2 - assert reloaded_model.l2_reg == 0.2 - assert reloaded_model.ls_init_learning_rate is None - assert reloaded_model.max_iterations == 30 - assert reloaded_model.tol == 0.02 - assert reloaded_model.learning_rate_strategy == "CONSTANT" - assert reloaded_model.learning_rate == 0.2 - - -def test_model_centroids_with_custom_index(penguins_df_default_index): - model = bigframes.ml.linear_model.LogisticRegression( - fit_intercept=False, - class_weight="balanced", - l2_reg=0.2, - tol=0.02, - l1_reg=0.2, - max_iterations=30, - optimize_strategy="batch_gradient_descent", - learning_rate_strategy="constant", - learning_rate=0.2, - ) - df = penguins_df_default_index.dropna().set_index(["species", "island"]) - X_train = df[ - [ - "culmen_length_mm", - "culmen_depth_mm", - "flipper_length_mm", - ] - ] - y_train = df[["sex"]] - model.fit(X_train, y_train) - - # If this line executes without errors, the model has correctly ignored the custom index columns - model.predict(X_train.reset_index(drop=True)) - - -def test_linear_reg_model_global_explain( - penguins_linear_model_w_global_explain, new_penguins_df -): - training_data = new_penguins_df.dropna(subset=["body_mass_g"]) - X = training_data.drop(columns=["body_mass_g"]) - y = training_data[["body_mass_g"]] - penguins_linear_model_w_global_explain.fit(X, y) - global_ex = penguins_linear_model_w_global_explain.global_explain() - assert global_ex.shape == (6, 1) - expected_columns = pd.Index(["attribution"]) - pd.testing.assert_index_equal(global_ex.columns, expected_columns) - result = global_ex.to_pandas().drop(["attribution"], axis=1).sort_index() - expected_feature = ( - pd.DataFrame( - { - "feature": [ - "island", - "species", - "sex", - "flipper_length_mm", - "culmen_depth_mm", - "culmen_length_mm", - ] - }, - ) - .set_index("feature") - .sort_index() - ) - pd.testing.assert_frame_equal( - result, - expected_feature, - check_exact=False, - check_index_type=False, - ) + assert reloaded_model.class_weights == "balanced" diff --git a/tests/system/large/ml/test_llm.py b/tests/system/large/ml/test_llm.py deleted file mode 100644 index 638e151ca14..00000000000 --- a/tests/system/large/ml/test_llm.py +++ /dev/null @@ -1,799 +0,0 @@ -# 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. -# 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 typing import Callable -from unittest import mock - -import pandas as pd -import pyarrow as pa -import pytest - -import bigframes.pandas as bpd -from bigframes.ml import core, llm -from bigframes.testing import utils - - -@pytest.mark.parametrize( - "model_name", - ( - "gemini-2.5-pro", - "gemini-2.5-flash", - "gemini-2.5-flash-lite", - ), -) -@pytest.mark.flaky(retries=2) -def test_create_load_gemini_text_generator_model( - dataset_id, model_name, session, bq_connection -): - gemini_text_generator_model = llm.GeminiTextGenerator( - model_name=model_name, connection_name=bq_connection, session=session - ) - assert gemini_text_generator_model is not None - assert gemini_text_generator_model._bqml_model is not None - - # save, load to ensure configuration was kept - reloaded_model = gemini_text_generator_model.to_gbq( - f"{dataset_id}.temp_text_model", replace=True - ) - assert f"{dataset_id}.temp_text_model" == reloaded_model._bqml_model.model_name - assert reloaded_model.connection_name == bq_connection - assert reloaded_model.model_name == model_name - - -@pytest.mark.parametrize( - "model_name", - ( - "gemini-2.5-pro", - "gemini-2.5-flash", - "gemini-2.5-flash-lite", - ), -) -@pytest.mark.flaky(retries=2) -def test_gemini_text_generator_predict_default_params_success( - llm_text_df, model_name, session, bq_connection -): - gemini_text_generator_model = llm.GeminiTextGenerator( - model_name=model_name, connection_name=bq_connection, session=session - ) - df = gemini_text_generator_model.predict(llm_text_df).to_pandas() - utils.check_pandas_df_schema_and_index( - df, columns=utils.ML_GENERATE_TEXT_OUTPUT, index=3, col_exact=False - ) - - -@pytest.mark.parametrize( - "model_name", - ( - "gemini-2.5-pro", - "gemini-2.5-flash", - "gemini-2.5-flash-lite", - ), -) -@pytest.mark.flaky(retries=2) -def test_gemini_text_generator_predict_with_params_success( - llm_text_df, model_name, session, bq_connection -): - gemini_text_generator_model = llm.GeminiTextGenerator( - model_name=model_name, connection_name=bq_connection, session=session - ) - df = gemini_text_generator_model.predict( - llm_text_df, temperature=0.5, max_output_tokens=100, top_k=20, top_p=0.5 - ).to_pandas() - utils.check_pandas_df_schema_and_index( - df, columns=utils.ML_GENERATE_TEXT_OUTPUT, index=3, col_exact=False - ) - - -@pytest.mark.parametrize( - "model_name", - ( - "gemini-2.5-pro", - "gemini-2.5-flash", - "gemini-2.5-flash-lite", - ), -) -@pytest.mark.flaky(retries=2) -def test_gemini_text_generator_multi_cols_predict_success( - llm_text_df: bpd.DataFrame, model_name, session, bq_connection -): - df = llm_text_df.assign(additional_col=1) - gemini_text_generator_model = llm.GeminiTextGenerator( - model_name=model_name, connection_name=bq_connection, session=session - ) - pd_df = gemini_text_generator_model.predict(df).to_pandas() - utils.check_pandas_df_schema_and_index( - pd_df, - columns=utils.ML_GENERATE_TEXT_OUTPUT + ["additional_col"], - index=3, - col_exact=False, - ) - - -@pytest.mark.parametrize( - "model_name", - ( - "gemini-2.5-pro", - "gemini-2.5-flash", - "gemini-2.5-flash-lite", - ), -) -@pytest.mark.flaky(retries=2) -def test_gemini_text_generator_predict_output_schema_success( - llm_text_df: bpd.DataFrame, model_name, session, bq_connection -): - gemini_text_generator_model = llm.GeminiTextGenerator( - model_name=model_name, connection_name=bq_connection, session=session - ) - output_schema = { - "bool_output": "bool", - "int_output": "int64", - "float_output": "float64", - "str_output": "string", - "array_output": "array", - "struct_output": "struct", - } - df = gemini_text_generator_model.predict(llm_text_df, output_schema=output_schema) - assert df["bool_output"].dtype == pd.BooleanDtype() - assert df["int_output"].dtype == pd.Int64Dtype() - assert df["float_output"].dtype == pd.Float64Dtype() - assert df["str_output"].dtype == pd.StringDtype(storage="pyarrow") - assert df["array_output"].dtype == pd.ArrowDtype(pa.list_(pa.int64())) - assert df["struct_output"].dtype == pd.ArrowDtype( - pa.struct([("number", pa.int64())]) - ) - - pd_df = df.to_pandas() - utils.check_pandas_df_schema_and_index( - pd_df, - columns=list(output_schema.keys()) + ["prompt", "full_response", "status"], - index=3, - col_exact=False, - ) - - -@pytest.mark.flaky(retries=2) -@pytest.mark.parametrize( - "model_name", - ( - "gemini-2.5-flash", - "gemini-2.5-flash-lite", - ), -) -def test_llm_gemini_score(llm_fine_tune_df_default_index, model_name): - model = llm.GeminiTextGenerator(model_name=model_name) - - # Check score to ensure the model was fitted - score_result = model.score( - X=llm_fine_tune_df_default_index[["prompt"]], - y=llm_fine_tune_df_default_index[["label"]], - ).to_pandas() - utils.check_pandas_df_schema_and_index( - score_result, - columns=[ - "bleu4_score", - "rouge-l_precision", - "rouge-l_recall", - "rouge-l_f1_score", - "evaluation_status", - ], - index=1, - col_exact=False, - ) - - -@pytest.mark.parametrize( - "model_name", - ( - "gemini-2.5-flash", - "gemini-2.5-flash-lite", - ), -) -def test_llm_gemini_pro_score_params(llm_fine_tune_df_default_index, model_name): - model = llm.GeminiTextGenerator(model_name=model_name) - - # Check score to ensure the model was fitted - score_result = model.score( - X=llm_fine_tune_df_default_index["prompt"], - y=llm_fine_tune_df_default_index["label"], - task_type="classification", - ).to_pandas() - utils.check_pandas_df_schema_and_index( - score_result, - columns=[ - "precision", - "recall", - "f1_score", - "label", - "evaluation_status", - ], - col_exact=False, - ) - - -@pytest.mark.parametrize( - "model_name", - ("text-embedding-005", "text-embedding-004", "text-multilingual-embedding-002"), -) -def test_create_load_text_embedding_generator_model( - dataset_id, model_name, session, bq_connection -): - text_embedding_model = llm.TextEmbeddingGenerator( - model_name=model_name, connection_name=bq_connection, session=session - ) - assert text_embedding_model is not None - assert text_embedding_model._bqml_model is not None - - # save, load to ensure configuration was kept - reloaded_model = text_embedding_model.to_gbq( - f"{dataset_id}.temp_text_model", replace=True - ) - assert f"{dataset_id}.temp_text_model" == reloaded_model._bqml_model.model_name - assert reloaded_model.connection_name == bq_connection - assert reloaded_model.model_name == model_name - - -@pytest.mark.parametrize( - "model_name", - ("text-embedding-005", "text-embedding-004", "text-multilingual-embedding-002"), -) -@pytest.mark.flaky(retries=2) -def test_text_embedding_generator_predict_default_params_success( - llm_text_df, model_name, session, bq_connection -): - text_embedding_model = llm.TextEmbeddingGenerator( - model_name=model_name, connection_name=bq_connection, session=session - ) - df = text_embedding_model.predict(llm_text_df).to_pandas() - utils.check_pandas_df_schema_and_index( - df, columns=utils.ML_GENERATE_EMBEDDING_OUTPUT, index=3, col_exact=False - ) - assert len(df["ml_generate_embedding_result"][0]) == 768 - - -@pytest.mark.parametrize( - "model_name", - ("text-embedding-005", "text-embedding-004", "text-multilingual-embedding-002"), -) -@pytest.mark.flaky(retries=2) -def test_text_embedding_generator_multi_cols_predict_success( - llm_text_df: bpd.DataFrame, model_name, session, bq_connection -): - df = llm_text_df.assign(additional_col=1) - df = df.rename(columns={"prompt": "content"}) - text_embedding_model = llm.TextEmbeddingGenerator( - model_name=model_name, connection_name=bq_connection, session=session - ) - pd_df = text_embedding_model.predict(df).to_pandas() - utils.check_pandas_df_schema_and_index( - pd_df, - columns=utils.ML_GENERATE_EMBEDDING_OUTPUT + ["additional_col"], - index=3, - col_exact=False, - ) - assert len(pd_df["ml_generate_embedding_result"][0]) == 768 - - -def test_create_load_multimodal_embedding_generator_model( - dataset_id, session, bq_connection -): - mm_embedding_model = llm.MultimodalEmbeddingGenerator( - connection_name=bq_connection, session=session - ) - assert mm_embedding_model is not None - assert mm_embedding_model._bqml_model is not None - - # save, load to ensure configuration was kept - reloaded_model = mm_embedding_model.to_gbq( - f"{dataset_id}.temp_mm_model", replace=True - ) - assert f"{dataset_id}.temp_mm_model" == reloaded_model._bqml_model.model_name - assert reloaded_model.connection_name == bq_connection - - -# Overrides __eq__ function for comparing as mock.call parameter -class EqCmpAllDataFrame(bpd.DataFrame): - def __eq__(self, other): - return self.equals(other) - - -@pytest.mark.skip("b/436340035 test failed") -@pytest.mark.parametrize( - ( - "model_class", - "options", - ), - [ - ( - llm.GeminiTextGenerator, - { - "temperature": 0.9, - "max_output_tokens": 8192, - "top_p": 1.0, - "ground_with_google_search": False, - }, - ), - ( - llm.Claude3TextGenerator, - { - "max_output_tokens": 128, - "top_k": 40, - "top_p": 0.95, - }, - ), - ], -) -def test_text_generator_retry_success( - session, - model_class, - options, - bq_connection, -): - # Requests. - df0 = EqCmpAllDataFrame( - { - "prompt": [ - "What is BigQuery?", - "What is BQML?", - "What is BigQuery DataFrame?", - ] - }, - index=[0, 1, 2], - session=session, - ) - df1 = EqCmpAllDataFrame( - { - "ml_generate_text_status": ["error", "error"], - "prompt": [ - "What is BQML?", - "What is BigQuery DataFrame?", - ], - }, - index=[1, 2], - session=session, - ) - df2 = EqCmpAllDataFrame( - { - "ml_generate_text_status": ["error"], - "prompt": [ - "What is BQML?", - ], - }, - index=[1], - session=session, - ) - - mock_generate_text = mock.create_autospec( - Callable[[core.BqmlModel, bpd.DataFrame, dict], bpd.DataFrame] - ) - mock_bqml_model = mock.create_autospec(spec=core.BqmlModel) - type(mock_bqml_model).session = mock.PropertyMock(return_value=session) - generate_text_tvf = core.BqmlModel.TvfDef( - mock_generate_text, "ml_generate_text_status" - ) - # Responses. Retry twice then all succeeded. - mock_generate_text.side_effect = [ - EqCmpAllDataFrame( - { - "ml_generate_text_status": ["", "error", "error"], - "prompt": [ - "What is BigQuery?", - "What is BQML?", - "What is BigQuery DataFrame?", - ], - }, - index=[0, 1, 2], - session=session, - ), - EqCmpAllDataFrame( - { - "ml_generate_text_status": ["error", ""], - "prompt": [ - "What is BQML?", - "What is BigQuery DataFrame?", - ], - }, - index=[1, 2], - session=session, - ), - EqCmpAllDataFrame( - { - "ml_generate_text_status": [""], - "prompt": [ - "What is BQML?", - ], - }, - index=[1], - session=session, - ), - ] - - text_generator_model = model_class(connection_name=bq_connection, session=session) - text_generator_model._bqml_model = mock_bqml_model - - with mock.patch.object(core.BqmlModel, "generate_text_tvf", generate_text_tvf): - # 3rd retry isn't triggered - result = text_generator_model.predict(df0, max_retries=3) - - mock_generate_text.assert_has_calls( - [ - mock.call(mock_bqml_model, df0, options), - mock.call(mock_bqml_model, df1, options), - mock.call(mock_bqml_model, df2, options), - ] - ) - pd.testing.assert_frame_equal( - result.to_pandas(), - pd.DataFrame( - { - "ml_generate_text_status": ["", "", ""], - "prompt": [ - "What is BigQuery?", - "What is BigQuery DataFrame?", - "What is BQML?", - ], - }, - index=[0, 2, 1], - ), - check_dtype=False, - check_index_type=False, - ) - - -@pytest.mark.skip("b/436340035 test failed") -@pytest.mark.parametrize( - ( - "model_class", - "options", - ), - [ - ( - llm.GeminiTextGenerator, - { - "temperature": 0.9, - "max_output_tokens": 8192, - "top_p": 1.0, - "ground_with_google_search": False, - }, - ), - ( - llm.Claude3TextGenerator, - { - "max_output_tokens": 128, - "top_k": 40, - "top_p": 0.95, - }, - ), - ], -) -def test_text_generator_retry_no_progress(session, model_class, options, bq_connection): - # Requests. - df0 = EqCmpAllDataFrame( - { - "prompt": [ - "What is BigQuery?", - "What is BQML?", - "What is BigQuery DataFrame?", - ] - }, - index=[0, 1, 2], - session=session, - ) - df1 = EqCmpAllDataFrame( - { - "ml_generate_text_status": ["error", "error"], - "prompt": [ - "What is BQML?", - "What is BigQuery DataFrame?", - ], - }, - index=[1, 2], - session=session, - ) - - mock_generate_text = mock.create_autospec( - Callable[[core.BqmlModel, bpd.DataFrame, dict], bpd.DataFrame] - ) - mock_bqml_model = mock.create_autospec(spec=core.BqmlModel) - type(mock_bqml_model).session = mock.PropertyMock(return_value=session) - generate_text_tvf = core.BqmlModel.TvfDef( - mock_generate_text, "ml_generate_text_status" - ) - # Responses. Retry once, no progress, just stop. - mock_generate_text.side_effect = [ - EqCmpAllDataFrame( - { - "ml_generate_text_status": ["", "error", "error"], - "prompt": [ - "What is BigQuery?", - "What is BQML?", - "What is BigQuery DataFrame?", - ], - }, - index=[0, 1, 2], - session=session, - ), - EqCmpAllDataFrame( - { - "ml_generate_text_status": ["error", "error"], - "prompt": [ - "What is BQML?", - "What is BigQuery DataFrame?", - ], - }, - index=[1, 2], - session=session, - ), - ] - - text_generator_model = model_class(connection_name=bq_connection, session=session) - text_generator_model._bqml_model = mock_bqml_model - - with mock.patch.object(core.BqmlModel, "generate_text_tvf", generate_text_tvf): - # No progress, only conduct retry once - result = text_generator_model.predict(df0, max_retries=3) - - mock_generate_text.assert_has_calls( - [ - mock.call(mock_bqml_model, df0, options), - mock.call(mock_bqml_model, df1, options), - ] - ) - pd.testing.assert_frame_equal( - result.to_pandas(), - pd.DataFrame( - { - "ml_generate_text_status": ["", "error", "error"], - "prompt": [ - "What is BigQuery?", - "What is BQML?", - "What is BigQuery DataFrame?", - ], - }, - index=[0, 1, 2], - ), - check_dtype=False, - check_index_type=False, - ) - - -@pytest.mark.skip("b/436340035 test failed") -def test_text_embedding_generator_retry_success(session, bq_connection): - # Requests. - df0 = EqCmpAllDataFrame( - { - "content": [ - "What is BigQuery?", - "What is BQML?", - "What is BigQuery DataFrame?", - ] - }, - index=[0, 1, 2], - session=session, - ) - df1 = EqCmpAllDataFrame( - { - "ml_generate_embedding_status": ["error", "error"], - "content": [ - "What is BQML?", - "What is BigQuery DataFrame?", - ], - }, - index=[1, 2], - session=session, - ) - df2 = EqCmpAllDataFrame( - { - "ml_generate_embedding_status": ["error"], - "content": [ - "What is BQML?", - ], - }, - index=[1], - session=session, - ) - - mock_generate_embedding = mock.create_autospec( - Callable[[core.BqmlModel, bpd.DataFrame, dict], bpd.DataFrame] - ) - mock_bqml_model = mock.create_autospec(spec=core.BqmlModel) - type(mock_bqml_model).session = mock.PropertyMock(return_value=session) - generate_embedding_tvf = core.BqmlModel.TvfDef( - mock_generate_embedding, "ml_generate_embedding_status" - ) - - # Responses. Retry twice then all succeeded. - mock_generate_embedding.side_effect = [ - EqCmpAllDataFrame( - { - "ml_generate_embedding_status": ["", "error", "error"], - "content": [ - "What is BigQuery?", - "What is BQML?", - "What is BigQuery DataFrame?", - ], - }, - index=[0, 1, 2], - session=session, - ), - EqCmpAllDataFrame( - { - "ml_generate_embedding_status": ["error", ""], - "content": [ - "What is BQML?", - "What is BigQuery DataFrame?", - ], - }, - index=[1, 2], - session=session, - ), - EqCmpAllDataFrame( - { - "ml_generate_embedding_status": [""], - "content": [ - "What is BQML?", - ], - }, - index=[1], - session=session, - ), - ] - options: dict = {} - - text_embedding_model = llm.TextEmbeddingGenerator( - connection_name=bq_connection, session=session - ) - text_embedding_model._bqml_model = mock_bqml_model - - with mock.patch.object( - core.BqmlModel, "generate_embedding_tvf", generate_embedding_tvf - ): - # 3rd retry isn't triggered - result = text_embedding_model.predict(df0, max_retries=3) - - mock_generate_embedding.assert_has_calls( - [ - mock.call(mock_bqml_model, df0, options), - mock.call(mock_bqml_model, df1, options), - mock.call(mock_bqml_model, df2, options), - ] - ) - pd.testing.assert_frame_equal( - result.to_pandas(), - pd.DataFrame( - { - "ml_generate_embedding_status": ["", "", ""], - "content": [ - "What is BigQuery?", - "What is BigQuery DataFrame?", - "What is BQML?", - ], - }, - index=[0, 2, 1], - ), - check_dtype=False, - check_index_type=False, - ) - - -def test_text_embedding_generator_retry_no_progress(session, bq_connection): - # Requests. - df0 = EqCmpAllDataFrame( - { - "content": [ - "What is BigQuery?", - "What is BQML?", - "What is BigQuery DataFrame?", - ] - }, - index=[0, 1, 2], - session=session, - ) - df1 = EqCmpAllDataFrame( - { - "ml_generate_embedding_status": ["error", "error"], - "content": [ - "What is BQML?", - "What is BigQuery DataFrame?", - ], - }, - index=[1, 2], - session=session, - ) - - mock_generate_embedding = mock.create_autospec( - Callable[[core.BqmlModel, bpd.DataFrame, dict], bpd.DataFrame] - ) - mock_bqml_model = mock.create_autospec(spec=core.BqmlModel) - type(mock_bqml_model).session = mock.PropertyMock(return_value=session) - generate_embedding_tvf = core.BqmlModel.TvfDef( - mock_generate_embedding, "ml_generate_embedding_status" - ) - - # Responses. Retry once, no progress, just stop. - mock_generate_embedding.side_effect = [ - EqCmpAllDataFrame( - { - "ml_generate_embedding_status": ["", "error", "error"], - "content": [ - "What is BigQuery?", - "What is BQML?", - "What is BigQuery DataFrame?", - ], - }, - index=[0, 1, 2], - session=session, - ), - EqCmpAllDataFrame( - { - "ml_generate_embedding_status": ["error", "error"], - "content": [ - "What is BQML?", - "What is BigQuery DataFrame?", - ], - }, - index=[1, 2], - session=session, - ), - ] - options: dict = {} - - text_embedding_model = llm.TextEmbeddingGenerator( - connection_name=bq_connection, session=session - ) - text_embedding_model._bqml_model = mock_bqml_model - - with mock.patch.object( - core.BqmlModel, "generate_embedding_tvf", generate_embedding_tvf - ): - # No progress, only conduct retry once - result = text_embedding_model.predict(df0, max_retries=3) - - mock_generate_embedding.assert_has_calls( - [ - mock.call(mock_bqml_model, df0, options), - mock.call(mock_bqml_model, df1, options), - ] - ) - pd.testing.assert_frame_equal( - result.to_pandas(), - pd.DataFrame( - { - "ml_generate_embedding_status": ["", "error", "error"], - "content": [ - "What is BigQuery?", - "What is BQML?", - "What is BigQuery DataFrame?", - ], - }, - index=[0, 1, 2], - ), - check_dtype=False, - check_index_type=False, - ) - - -# b/436340035 temp disable the test to unblock presumbit -@pytest.mark.parametrize( - "model_class", - [ - llm.TextEmbeddingGenerator, - llm.MultimodalEmbeddingGenerator, - llm.GeminiTextGenerator, - # llm.Claude3TextGenerator, - ], -) -def test_text_embedding_generator_no_default_model_warning(model_class): - message = "Since upgrading the default model can cause unintended breakages, the\ndefault model will be removed in BigFrames 3.0. Please supply an\nexplicit model to avoid this message." - with pytest.warns(FutureWarning, match=message): - model_class(model_name=None) diff --git a/tests/system/large/ml/test_model_selection.py b/tests/system/large/ml/test_model_selection.py deleted file mode 100644 index 26174b7ee98..00000000000 --- a/tests/system/large/ml/test_model_selection.py +++ /dev/null @@ -1,64 +0,0 @@ -# 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. -# 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.ml import linear_model, model_selection -from bigframes.testing import utils - - -@pytest.mark.parametrize( - ("cv", "n_fold"), - ( - pytest.param( - None, - 5, - ), - pytest.param( - 4, - 4, - ), - pytest.param( - model_selection.KFold(3), - 3, - ), - ), -) -def test_cross_validate(penguins_df_default_index, cv, n_fold): - model = linear_model.LinearRegression() - df = penguins_df_default_index.dropna() - X = df[ - [ - "species", - "island", - "culmen_length_mm", - ] - ] - y = df["body_mass_g"] - - cv_results = model_selection.cross_validate(model, X, y, cv=cv) - - assert "test_score" in cv_results - assert "fit_time" in cv_results - assert "score_time" in cv_results - - assert len(cv_results["test_score"]) == n_fold - assert len(cv_results["fit_time"]) == n_fold - assert len(cv_results["score_time"]) == n_fold - - utils.check_pandas_df_schema_and_index( - cv_results["test_score"][0].to_pandas(), - columns=utils.ML_REGRESSION_METRICS, - index=1, - ) diff --git a/tests/system/large/ml/test_pipeline.py b/tests/system/large/ml/test_pipeline.py index 6c51a11a113..6874a9f301a 100644 --- a/tests/system/large/ml/test_pipeline.py +++ b/tests/system/large/ml/test_pipeline.py @@ -20,12 +20,11 @@ compose, decomposition, ensemble, - impute, linear_model, pipeline, preprocessing, ) -from bigframes.testing import utils +from tests.system.utils import assert_pandas_df_equal_ignore_ordering def test_pipeline_linear_regression_fit_score_predict( @@ -52,8 +51,21 @@ def test_pipeline_linear_regression_fit_score_predict( # Check score to ensure the model was fitted score_result = pl.score(X_train, y_train).to_pandas() - utils.check_pandas_df_schema_and_index( - score_result, columns=utils.ML_REGRESSION_METRICS, index=1 + score_expected = pd.DataFrame( + { + "mean_absolute_error": [309.477331], + "mean_squared_error": [152184.227219], + "mean_squared_log_error": [0.009524], + "median_absolute_error": [257.728263], + "r2_score": [0.764356], + "explained_variance": [0.764356], + }, + dtype="Float64", + ) + score_expected = score_expected.reindex(index=score_expected.index.astype("Int64")) + + pd.testing.assert_frame_equal( + score_result, score_expected, check_exact=False, rtol=0.1 ) # predict new labels @@ -75,11 +87,13 @@ def test_pipeline_linear_regression_fit_score_predict( ).set_index("tag_number") ) predictions = pl.predict(new_penguins).to_pandas() - utils.check_pandas_df_schema_and_index( - predictions, - columns=["predicted_body_mass_g"], - index=[1633, 1672, 1690], - col_exact=False, + expected = pd.DataFrame( + {"predicted_body_mass_g": [3968.8, 3176.3, 3545.2]}, + dtype="Float64", + index=pd.Index([1633, 1672, 1690], name="tag_number", dtype="Int64"), + ) + pd.testing.assert_frame_equal( + predictions[["predicted_body_mass_g"]], expected, check_exact=False, rtol=0.1 ) @@ -101,8 +115,21 @@ def test_pipeline_linear_regression_series_fit_score_predict( # Check score to ensure the model was fitted score_result = pl.score(X_train, y_train).to_pandas() - utils.check_pandas_df_schema_and_index( - score_result, columns=utils.ML_REGRESSION_METRICS, index=1 + score_expected = pd.DataFrame( + { + "mean_absolute_error": [528.495599], + "mean_squared_error": [421722.261808], + "mean_squared_log_error": [0.022963], + "median_absolute_error": [468.895249], + "r2_score": [0.346999], + "explained_variance": [0.346999], + }, + dtype="Float64", + ) + score_expected = score_expected.reindex(index=score_expected.index.astype("Int64")) + + pd.testing.assert_frame_equal( + score_result, score_expected, check_exact=False, rtol=0.1 ) # predict new labels @@ -115,11 +142,13 @@ def test_pipeline_linear_regression_series_fit_score_predict( ).set_index("tag_number") ) predictions = pl.predict(new_penguins["culmen_length_mm"]).to_pandas() - utils.check_pandas_df_schema_and_index( - predictions, - columns=["predicted_body_mass_g"], - index=[1633, 1672, 1690], - col_exact=False, + expected = pd.DataFrame( + {"predicted_body_mass_g": [3818.845703, 3732.022253, 3679.928123]}, + dtype="Float64", + index=pd.Index([1633, 1672, 1690], name="tag_number", dtype="Int64"), + ) + pd.testing.assert_frame_equal( + predictions[["predicted_body_mass_g"]], expected, check_exact=False, rtol=0.1 ) @@ -147,8 +176,21 @@ def test_pipeline_logistic_regression_fit_score_predict( # Check score to ensure the model was fitted score_result = pl.score(X_train, y_train).to_pandas() - utils.check_pandas_df_schema_and_index( - score_result, columns=utils.ML_CLASSFICATION_METRICS, index=1 + score_expected = pd.DataFrame( + { + "precision": [0.537091], + "recall": [0.538636], + "accuracy": [0.805389], + "f1_score": [0.537716], + "log_loss": [1.445433], + "roc_auc": [0.917818], + }, + dtype="Float64", + ) + score_expected = score_expected.reindex(index=score_expected.index.astype("Int64")) + + pd.testing.assert_frame_equal( + score_result, score_expected, check_exact=False, rtol=0.1 ) # predict new labels @@ -169,15 +211,18 @@ def test_pipeline_logistic_regression_fit_score_predict( ).set_index("tag_number") ) predictions = pl.predict(new_penguins).to_pandas() - utils.check_pandas_df_schema_and_index( - predictions, - columns=["predicted_sex"], - index=[1633, 1672, 1690], - col_exact=False, + expected = pd.DataFrame( + {"predicted_sex": ["MALE", "FEMALE", "FEMALE"]}, + dtype=pd.StringDtype(storage="pyarrow"), + index=pd.Index([1633, 1672, 1690], name="tag_number", dtype="Int64"), + ) + pd.testing.assert_frame_equal( + predictions[["predicted_sex"]], + expected, ) -@pytest.mark.flaky(retries=2) +@pytest.mark.flaky(retries=2, delay=120) def test_pipeline_xgbregressor_fit_score_predict(session, penguins_df_default_index): """Test a supervised model with a minimal preprocessing step""" pl = pipeline.Pipeline( @@ -200,8 +245,21 @@ def test_pipeline_xgbregressor_fit_score_predict(session, penguins_df_default_in # Check score to ensure the model was fitted score_result = pl.score(X_train, y_train).to_pandas() - utils.check_pandas_df_schema_and_index( - score_result, columns=utils.ML_REGRESSION_METRICS, index=1 + score_expected = pd.DataFrame( + { + "mean_absolute_error": [202.298434], + "mean_squared_error": [74515.108971], + "mean_squared_log_error": [0.004365], + "median_absolute_error": [142.949219], + "r2_score": [0.88462], + "explained_variance": [0.886454], + }, + dtype="Float64", + ) + score_expected = score_expected.reindex(index=score_expected.index.astype("Int64")) + + pd.testing.assert_frame_equal( + score_result, score_expected, check_exact=False, rtol=0.1 ) # predict new labels @@ -223,15 +281,23 @@ def test_pipeline_xgbregressor_fit_score_predict(session, penguins_df_default_in ).set_index("tag_number") ) predictions = pl.predict(new_penguins).to_pandas() - utils.check_pandas_df_schema_and_index( - predictions, - columns=["predicted_body_mass_g"], - index=[1633, 1672, 1690], - col_exact=False, + expected = pd.DataFrame( + { + "predicted_body_mass_g": [ + 4287.34521484375, + 3198.351806640625, + 3385.34130859375, + ] + }, + dtype="Float64", + index=pd.Index([1633, 1672, 1690], name="tag_number", dtype="Int64"), + ) + pd.testing.assert_frame_equal( + predictions[["predicted_body_mass_g"]], expected, check_exact=False, rtol=0.1 ) -@pytest.mark.flaky(retries=2) +@pytest.mark.flaky(retries=2, delay=120) def test_pipeline_random_forest_classifier_fit_score_predict( session, penguins_df_default_index ): @@ -256,8 +322,21 @@ def test_pipeline_random_forest_classifier_fit_score_predict( # Check score to ensure the model was fitted score_result = pl.score(X_train, y_train).to_pandas() - utils.check_pandas_df_schema_and_index( - score_result, columns=utils.ML_CLASSFICATION_METRICS, index=1 + score_expected = pd.DataFrame( + { + "precision": [0.585505], + "recall": [0.58676], + "accuracy": [0.877246], + "f1_score": [0.585657], + "log_loss": [0.880643], + "roc_auc": [0.970697], + }, + dtype="Float64", + ) + score_expected = score_expected.reindex(index=score_expected.index.astype("Int64")) + + pd.testing.assert_frame_equal( + score_result, score_expected, check_exact=False, rtol=0.1 ) # predict new labels @@ -278,11 +357,14 @@ def test_pipeline_random_forest_classifier_fit_score_predict( ).set_index("tag_number") ) predictions = pl.predict(new_penguins).to_pandas() - utils.check_pandas_df_schema_and_index( - predictions, - columns=["predicted_sex"], - index=[1633, 1672, 1690], - col_exact=False, + expected = pd.DataFrame( + {"predicted_sex": ["MALE", "FEMALE", "FEMALE"]}, + dtype=pd.StringDtype(storage="pyarrow"), + index=pd.Index([1633, 1672, 1690], name="tag_number", dtype="Int64"), + ) + pd.testing.assert_frame_equal( + predictions[["predicted_sex"]], + expected, ) @@ -326,24 +408,45 @@ def test_pipeline_PCA_fit_score_predict(session, penguins_df_default_index): # Check score to ensure the model was fitted score_result = pl.score(new_penguins).to_pandas() - utils.check_pandas_df_schema_and_index( - score_result, columns=["total_explained_variance_ratio"], index=1 + score_expected = pd.DataFrame( + { + "total_explained_variance_ratio": [1.0], + }, + dtype="Float64", + ) + score_expected = score_expected.reindex(index=score_expected.index.astype("Int64")) + + pd.testing.assert_frame_equal( + score_result, score_expected, check_exact=False, rtol=0.1 ) predictions = pl.predict(new_penguins).to_pandas() - utils.check_pandas_df_schema_and_index( - predictions, - columns=[ - "principal_component_1", - "principal_component_2", - "principal_component_3", - ], - index=[1633, 1672, 1690], - col_exact=False, + expected = pd.DataFrame( + { + "principal_component_1": [-1.115259, -1.506141, -1.471173], + "principal_component_2": [-0.074825, 0.69664, 0.406103], + "principal_component_3": [0.500013, -0.544479, 0.075849], + }, + dtype="Float64", + index=pd.Index([1633, 1672, 1690], name="tag_number", dtype="Int64"), + ) + pd.testing.assert_frame_equal( + abs( # results may differ by a minus sign + predictions[ + [ + "principal_component_1", + "principal_component_2", + "principal_component_3", + ] + ] + ), + abs(expected), + check_exact=False, + rtol=0.1, ) -@pytest.mark.flaky(retries=2) +@pytest.mark.flaky(retries=2, delay=120) def test_pipeline_standard_scaler_kmeans_fit_score_predict( session, penguins_pandas_df_default_index ): @@ -432,16 +535,27 @@ def test_pipeline_standard_scaler_kmeans_fit_score_predict( # Check score to ensure the model was fitted score_result = pl.score(new_penguins).to_pandas() - eval_metrics = ["davies_bouldin_index", "mean_squared_distance"] - utils.check_pandas_df_schema_and_index(score_result, columns=eval_metrics, index=1) + score_expected = pd.DataFrame( + {"davies_bouldin_index": [7.542981], "mean_squared_distance": [94.692409]}, + dtype="Float64", + ) + score_expected = score_expected.reindex(index=score_expected.index.astype("Int64")) + + pd.testing.assert_frame_equal( + score_result, score_expected, check_exact=False, rtol=0.1 + ) - predictions = pl.predict(new_penguins).to_pandas().sort_index() - utils.check_pandas_df_schema_and_index( - predictions, - columns=["CENTROID_ID"], - index=["test1", "test2", "test3", "test4", "test5", "test6"], - col_exact=False, + result = pl.predict(new_penguins).to_pandas().sort_index() + expected = pd.DataFrame( + {"CENTROID_ID": [1, 2, 1, 2, 1, 2]}, + dtype="Int64", + index=pd.Index( + ["test1", "test2", "test3", "test4", "test5", "test6"], + dtype="string[pyarrow]", + ), ) + expected.index.name = "observation" + assert_pandas_df_equal_ignore_ordering(result, expected) def test_pipeline_columntransformer_fit_predict(session, penguins_df_default_index): @@ -477,21 +591,11 @@ def test_pipeline_columntransformer_fit_predict(session, penguins_df_default_ind preprocessing.KBinsDiscretizer(strategy="uniform"), ["culmen_length_mm", "flipper_length_mm"], ), - ( - "simple_imputer", - impute.SimpleImputer(strategy="mean"), - ["culmen_length_mm", "flipper_length_mm"], - ), ( "label", preprocessing.LabelEncoder(), "species", ), - ( - "poly_feats", - preprocessing.PolynomialFeatures(), - ["culmen_length_mm", "flipper_length_mm"], - ), ] ), ), @@ -523,11 +627,13 @@ def test_pipeline_columntransformer_fit_predict(session, penguins_df_default_ind ).set_index("tag_number") ) predictions = pl.predict(new_penguins).to_pandas() - utils.check_pandas_df_schema_and_index( - predictions, - columns=["predicted_body_mass_g"], - index=[1633, 1672, 1690], - col_exact=False, + expected = pd.DataFrame( + {"predicted_body_mass_g": [3909.2, 3436.0, 2860.0]}, + dtype="Float64", + index=pd.Index([1633, 1672, 1690], name="tag_number", dtype="Int64"), + ) + pd.testing.assert_frame_equal( + predictions[["predicted_body_mass_g"]], expected, check_exact=False, rtol=0.1 ) @@ -539,7 +645,7 @@ def test_pipeline_columntransformer_to_gbq(penguins_df_default_index, dataset_id compose.ColumnTransformer( [ ( - "one_hot_encoder", + "ont_hot_encoder", preprocessing.OneHotEncoder( drop="most_frequent", min_frequency=5, @@ -567,16 +673,6 @@ def test_pipeline_columntransformer_to_gbq(penguins_df_default_index, dataset_id preprocessing.KBinsDiscretizer(strategy="uniform"), ["culmen_length_mm", "flipper_length_mm"], ), - ( - "simple_imputer", - impute.SimpleImputer(), - ["culmen_length_mm", "flipper_length_mm"], - ), - ( - "polynomial_features", - preprocessing.PolynomialFeatures(), - ["culmen_length_mm", "flipper_length_mm"], - ), ( "label", preprocessing.LabelEncoder(), @@ -599,10 +695,10 @@ def test_pipeline_columntransformer_to_gbq(penguins_df_default_index, dataset_id ) assert isinstance(pl_loaded._transform, compose.ColumnTransformer) - transformers = pl_loaded._transform.transformers + transformers = pl_loaded._transform.transformers_ expected = [ ( - "one_hot_encoder", + "ont_hot_encoder", preprocessing.OneHotEncoder( drop="most_frequent", max_categories=100, min_frequency=5 ), @@ -621,11 +717,6 @@ def test_pipeline_columntransformer_to_gbq(penguins_df_default_index, dataset_id preprocessing.KBinsDiscretizer(strategy="uniform"), "culmen_length_mm", ), - ( - "simple_imputer", - impute.SimpleImputer(), - "culmen_length_mm", - ), ("standard_scaler", preprocessing.StandardScaler(), "flipper_length_mm"), ("max_abs_scaler", preprocessing.MaxAbsScaler(), "flipper_length_mm"), ("min_max_scaler", preprocessing.MinMaxScaler(), "flipper_length_mm"), @@ -634,19 +725,9 @@ def test_pipeline_columntransformer_to_gbq(penguins_df_default_index, dataset_id preprocessing.KBinsDiscretizer(strategy="uniform"), "flipper_length_mm", ), - ( - "simple_imputer", - impute.SimpleImputer(), - "flipper_length_mm", - ), - ( - "polynomial_features", - preprocessing.PolynomialFeatures(), - ("culmen_length_mm", "flipper_length_mm"), - ), ] - assert set(transformers) == set(expected) + assert transformers == expected assert isinstance(pl_loaded._estimator, linear_model.LinearRegression) assert pl_loaded._estimator.fit_intercept is False @@ -831,69 +912,3 @@ def test_pipeline_label_encoder_to_gbq(penguins_df_default_index, dataset_id): assert isinstance(pl_loaded._estimator, linear_model.LinearRegression) assert pl_loaded._estimator.fit_intercept is False - - -def test_pipeline_simple_imputer_to_gbq(penguins_df_default_index, dataset_id): - pl = pipeline.Pipeline( - [ - ( - "transform", - impute.SimpleImputer(strategy="most_frequent"), - ), - ("estimator", linear_model.LinearRegression(fit_intercept=False)), - ] - ) - - df = penguins_df_default_index.dropna() - X_train = df[ - [ - "sex", - "species", - ] - ] - y_train = df[["body_mass_g"]] - pl.fit(X_train, y_train) - - pl_loaded = pl.to_gbq( - f"{dataset_id}.test_penguins_pipeline_simple_imputer", replace=True - ) - assert isinstance(pl_loaded._transform, impute.SimpleImputer) - - simple_imputer = pl_loaded._transform - assert simple_imputer.strategy == "most_frequent" - - assert isinstance(pl_loaded._estimator, linear_model.LinearRegression) - assert pl_loaded._estimator.fit_intercept is False - - -def test_pipeline_poly_features_to_gbq(penguins_df_default_index, dataset_id): - pl = pipeline.Pipeline( - [ - ( - "transform", - preprocessing.PolynomialFeatures(degree=3), - ), - ("estimator", linear_model.LinearRegression(fit_intercept=False)), - ] - ) - - df = penguins_df_default_index.dropna() - X_train = df[ - [ - "culmen_length_mm", - "flipper_length_mm", - ] - ] - y_train = df[["body_mass_g"]] - pl.fit(X_train, y_train) - - pl_loaded = pl.to_gbq( - f"{dataset_id}.test_penguins_pipeline_poly_features", replace=True - ) - assert isinstance(pl_loaded._transform, preprocessing.PolynomialFeatures) - - poly_features = pl_loaded._transform - assert poly_features.degree == 3 - - assert isinstance(pl_loaded._estimator, linear_model.LinearRegression) - assert pl_loaded._estimator.fit_intercept is False diff --git a/tests/system/large/streaming/test_bigtable.py b/tests/system/large/streaming/test_bigtable.py deleted file mode 100644 index f10c534404e..00000000000 --- a/tests/system/large/streaming/test_bigtable.py +++ /dev/null @@ -1,106 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import time -import uuid -from datetime import datetime, timedelta -from typing import Generator - -import pytest - -import bigframes - -pytest.importorskip("google.cloud.bigtable") - -from google.cloud import bigtable # noqa -from google.cloud.bigtable import column_family, instance, table # noqa - - -@pytest.fixture(scope="session") -def bigtable_instance(session_load: bigframes.Session) -> instance.Instance: - client = bigtable.Client(project=session_load._project, admin=True) - - instance_name = "streaming-testing-instance" - bt_instance = instance.Instance( - instance_name, - client, - ) - - if not bt_instance.exists(): - cluster_id = "streaming-testing-instance-c1" - cluster = bt_instance.cluster( - cluster_id, - location_id="us-west1-a", - serve_nodes=1, - ) - operation = bt_instance.create( - clusters=[cluster], - ) - operation.result(timeout=480) - return bt_instance - - -@pytest.fixture(scope="function") -def bigtable_table( - bigtable_instance: instance.Instance, -) -> Generator[table.Table, None, None]: - table_id = "bigframes_test_" + uuid.uuid4().hex - bt_table = table.Table( - table_id, - bigtable_instance, - ) - max_versions_rule = column_family.MaxVersionsGCRule(1) - column_family_id = "body_mass_g" - column_families = {column_family_id: max_versions_rule} - bt_table.create(column_families=column_families) - yield bt_table - bt_table.delete() - - -@pytest.mark.flaky(retries=3, delay=10) -def test_streaming_df_to_bigtable( - session_load: bigframes.Session, bigtable_table: table.Table -): - # launch a continuous query - job_id_prefix = "test_streaming_" - sdf = session_load.read_gbq_table_streaming("birds.penguins_bigtable_streaming") - - sdf = sdf[["species", "island", "body_mass_g"]] - sdf = sdf[sdf["body_mass_g"] < 4000] - sdf = sdf.rename(columns={"island": "rowkey"}) - - try: - query_job = sdf.to_bigtable( - instance="streaming-testing-instance", - table=bigtable_table.table_id, - service_account_email="streaming-testing-admin@bigframes-load-testing.iam.gserviceaccount.com", - app_profile=None, - truncate=True, - overwrite=True, - auto_create_column_families=True, - bigtable_options={}, - job_id=None, - job_id_prefix=job_id_prefix, - start_timestamp=datetime.now() - timedelta(days=1), - ) - - # wait 200 seconds in order to ensure the query doesn't stop - # (i.e. it is continuous) - time.sleep(200) - assert query_job.running() - assert query_job.error_result is None - assert str(query_job.job_id).startswith(job_id_prefix) - assert len(list(bigtable_table.read_rows())) > 0 - finally: - query_job.cancel() diff --git a/tests/system/large/streaming/test_pubsub.py b/tests/system/large/streaming/test_pubsub.py deleted file mode 100644 index cdc27ae65cf..00000000000 --- a/tests/system/large/streaming/test_pubsub.py +++ /dev/null @@ -1,116 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import uuid -from concurrent import futures -from datetime import datetime, timedelta -from typing import Generator - -import pytest - -import bigframes - -pytest.importorskip("google.cloud.pubsub") -from google.cloud import pubsub # type: ignore # noqa - - -def resource_name_full(project_id: str, resource_type: str, resource_id: str): - """Used for bigtable or pubsub resources.""" - return f"projects/{project_id}/{resource_type}/{resource_id}" - - -@pytest.fixture(scope="function") -def pubsub_topic_id(session_load: bigframes.Session) -> Generator[str, None, None]: - publisher = pubsub.PublisherClient() - topic_id = "bigframes_test_topic_" + uuid.uuid4().hex - - topic_name = resource_name_full(session_load._project, "topics", topic_id) - - publisher.create_topic(name=topic_name) - yield topic_id - publisher.delete_topic(topic=topic_name) - - -@pytest.fixture(scope="function") -def pubsub_topic_subscription_ids( - session_load: bigframes.Session, pubsub_topic_id: str -) -> Generator[tuple[str, str], None, None]: - subscriber = pubsub.SubscriberClient() - subscription_id = "bigframes_test_subscription_" + uuid.uuid4().hex - - subscription_name = resource_name_full( - session_load._project, "subscriptions", subscription_id - ) - topic_name = resource_name_full(session_load._project, "topics", pubsub_topic_id) - - subscriber.create_subscription(name=subscription_name, topic=topic_name) - yield (pubsub_topic_id, subscription_id) - subscriber.delete_subscription(subscription=subscription_name) - - -@pytest.mark.flaky(retries=3, delay=10) -def test_streaming_df_to_pubsub( - session_load: bigframes.Session, pubsub_topic_subscription_ids: tuple[str, str] -): - topic_id, subscription_id = pubsub_topic_subscription_ids - - subscriber = pubsub.SubscriberClient() - - subscription_name = "projects/{project_id}/subscriptions/{sub}".format( - project_id=session_load._project, - sub=subscription_id, - ) - - # launch a continuous query - job_id_prefix = "test_streaming_pubsub_" - sdf = session_load.read_gbq_table_streaming("birds.penguins_bigtable_streaming") - - sdf = sdf[sdf["body_mass_g"] < 4000] - sdf = sdf[["island"]] - - try: - - def counter(func): - def wrapper(*args, **kwargs): - wrapper.count += 1 # type: ignore - return func(*args, **kwargs) - - wrapper.count = 0 # type: ignore - return wrapper - - @counter - def callback(message): - message.ack() - - future = subscriber.subscribe(subscription_name, callback) - - query_job = sdf.to_pubsub( - topic=topic_id, - service_account_email="streaming-testing@bigframes-load-testing.iam.gserviceaccount.com", - job_id=None, - job_id_prefix=job_id_prefix, - start_timestamp=datetime.now() - timedelta(days=1), - ) - try: - # wait 200 seconds in order to ensure the query doesn't stop - # (i.e. it is continuous) - future.result(timeout=200) - except futures.TimeoutError: - future.cancel() - assert query_job.running() - assert query_job.error_result is None - assert str(query_job.job_id).startswith(job_id_prefix) - assert callback.count > 0 # type: ignore - finally: - query_job.cancel() diff --git a/tests/system/large/test_dataframe.py b/tests/system/large/test_dataframe.py deleted file mode 100644 index dc7671d18a6..00000000000 --- a/tests/system/large/test_dataframe.py +++ /dev/null @@ -1,66 +0,0 @@ -import sys - -import pandas as pd -import pytest - - -@pytest.mark.skipif( - sys.version_info >= (3, 12), - # See: https://github.com/python/cpython/issues/112282 - reason="setrecursionlimit has no effect on the Python C stack since Python 3.12.", -) -def test_corr_150_columns(scalars_df_numeric_150_columns_maybe_ordered): - scalars_df, scalars_pandas_df = scalars_df_numeric_150_columns_maybe_ordered - bf_result = scalars_df.corr(numeric_only=True).to_pandas() - pd_result = scalars_pandas_df.corr(numeric_only=True) - - pd.testing.assert_frame_equal( - bf_result, - pd_result, - check_dtype=False, - check_index_type=False, - check_column_type=False, - ) - - -@pytest.mark.skipif( - sys.version_info >= (3, 12), - # See: https://github.com/python/cpython/issues/112282 - reason="setrecursionlimit has no effect on the Python C stack since Python 3.12.", -) -def test_cov_150_columns(scalars_df_numeric_150_columns_maybe_ordered): - scalars_df, scalars_pandas_df = scalars_df_numeric_150_columns_maybe_ordered - bf_result = scalars_df.cov(numeric_only=True).to_pandas() - pd_result = scalars_pandas_df.cov(numeric_only=True) - - pd.testing.assert_frame_equal( - bf_result, - pd_result, - check_dtype=False, - check_index_type=False, - check_column_type=False, - ) - - -@pytest.mark.parametrize( - ("keep",), - [ - ("first",), - ("last",), - (False,), - ], -) -def test_drop_duplicates_unordered( - scalars_df_unordered, scalars_pandas_df_default_index, keep -): - uniq_scalar_rows = scalars_df_unordered.drop_duplicates( - subset="bool_col", keep=keep - ) - uniq_pd_rows = scalars_pandas_df_default_index.drop_duplicates( - subset="bool_col", keep=keep - ) - - assert len(uniq_scalar_rows) == len(uniq_pd_rows) - assert len(uniq_scalar_rows.groupby("bool_col")) == len( - uniq_pd_rows.groupby("bool_col") - ) diff --git a/tests/system/large/test_dataframe_io.py b/tests/system/large/test_dataframe_io.py deleted file mode 100644 index c352d618d6a..00000000000 --- a/tests/system/large/test_dataframe_io.py +++ /dev/null @@ -1,68 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import google.api_core.exceptions -import pytest - -import bigframes - -WIKIPEDIA_TABLE = "bigquery-public-data.samples.wikipedia" -LARGE_TABLE_OPTION = "compute.allow_large_results" - - -def test_to_pandas_batches_raise_when_large_result_not_allowed(session): - with ( - bigframes.option_context(LARGE_TABLE_OPTION, False), - pytest.raises(google.api_core.exceptions.Forbidden), - ): - df = session.read_gbq(WIKIPEDIA_TABLE) - next(df.to_pandas_batches(page_size=500, max_results=1500)) - - -def test_large_df_peek_no_job(session): - execution_count_before = session._metrics.execution_count - - # only works with null index, as sequential index requires row_number over full table scan. - df = session.read_gbq( - WIKIPEDIA_TABLE, index_col=bigframes.enums.DefaultIndexKind.NULL - ) - result = df.peek(50) - execution_count_after = session._metrics.execution_count - - assert len(result) == 50 - assert execution_count_after == execution_count_before - - -def test_to_pandas_batches_override_global_option( - session, -): - with bigframes.option_context(LARGE_TABLE_OPTION, False): - df = session.read_gbq(WIKIPEDIA_TABLE) - batches = df.sort_values("id").to_pandas_batches( - page_size=500, max_results=1500, allow_large_results=True - ) - assert batches.total_rows > 0 - assert batches.total_bytes_processed > 0 - pages = list(batches) - assert all((len(page) <= 500) for page in pages) - assert sum(len(page) for page in pages) == 1500 - - -def test_to_pandas_raise_when_large_result_not_allowed(session): - with ( - bigframes.option_context(LARGE_TABLE_OPTION, False), - pytest.raises(google.api_core.exceptions.Forbidden), - ): - df = session.read_gbq(WIKIPEDIA_TABLE) - next(df.to_pandas()) diff --git a/tests/system/large/test_location.py b/tests/system/large/test_location.py deleted file mode 100644 index 3127d5865a9..00000000000 --- a/tests/system/large/test_location.py +++ /dev/null @@ -1,224 +0,0 @@ -# Copyright 2023 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 typing -import unittest.mock as mock - -import google.auth.credentials -import pandas -import pandas.testing -import pytest - -import bigframes -import bigframes.constants -import bigframes.session.clients - - -def _assert_bq_execution_location( - session: bigframes.Session, expected_location: typing.Optional[str] = None -): - df = session.read_gbq( - """ - SELECT "aaa" as name, 111 as number - UNION ALL - SELECT "bbb" as name, 222 as number - UNION ALL - SELECT "aaa" as name, 333 as number - """ - ) - - if expected_location is None: - expected_location = session._location - - query_job = df.query_job - assert query_job is not None - assert query_job.location == expected_location - destination = query_job.destination - assert destination is not None - destination_dataset = session.bqclient.get_dataset( - f"{destination.project}.{destination.dataset_id}" - ) - assert destination_dataset.location == expected_location - - # Ensure operation involving BQ client suceeds - result = ( - df[["name", "number"]] - .groupby("name") - .sum(numeric_only=True) - .sort_values("number", ascending=False) - .head() - ) - - # Use allow_large_results = True to force a job to be created. - result_pd = result.to_pandas(allow_large_results=True) - - query_job = df.query_job - assert query_job is not None - assert query_job.location == expected_location - destination = query_job.destination - assert destination is not None - destination_dataset = session.bqclient.get_dataset( - f"{destination.project}.{destination.dataset_id}" - ) - assert destination_dataset.location == expected_location - - expected_result = pandas.DataFrame( - {"number": [444, 222]}, index=pandas.Index(["aaa", "bbb"], name="name") - ) - pandas.testing.assert_frame_equal( - expected_result, - result_pd, - check_dtype=False, - check_index_type=False, - ) - - -def test_bq_location_default(): - session = bigframes.Session() - - assert session.bqclient.location == "US" - - # by default global endpoint is used - assert ( - session.bqclient._connection.API_BASE_URL == "https://bigquery.googleapis.com" - ) - - # assert that bigframes session honors the location - _assert_bq_execution_location(session) - - -@pytest.mark.parametrize( - "bigquery_location", - # Sort the set to avoid nondeterminism. - sorted(bigframes.constants.ALL_BIGQUERY_LOCATIONS), -) -def test_bq_location(bigquery_location): - session = bigframes.Session( - context=bigframes.BigQueryOptions(location=bigquery_location) - ) - - assert session.bqclient.location == bigquery_location - - # by default global endpoint is used - assert ( - session.bqclient._connection.API_BASE_URL == "https://bigquery.googleapis.com" - ) - - # assert that bigframes session honors the location - _assert_bq_execution_location(session) - - -@pytest.mark.parametrize( - ("set_location", "resolved_location"), - # Sort the set to avoid nondeterminism. - [ - (loc.capitalize(), loc) - for loc in sorted(bigframes.constants.ALL_BIGQUERY_LOCATIONS) - ], -) -def test_bq_location_non_canonical(set_location, resolved_location): - session = bigframes.Session( - context=bigframes.BigQueryOptions(location=set_location) - ) - - assert session.bqclient.location == resolved_location - - # by default global endpoint is used - assert ( - session.bqclient._connection.API_BASE_URL == "https://bigquery.googleapis.com" - ) - - # assert that bigframes session honors the location - _assert_bq_execution_location(session, resolved_location) - - -@pytest.mark.parametrize( - "bigquery_location", - # Sort the set to avoid nondeterminism. - sorted(bigframes.constants.REP_ENABLED_BIGQUERY_LOCATIONS), -) -def test_bq_rep_endpoints(bigquery_location): - session = bigframes.Session( - context=bigframes.BigQueryOptions( - location=bigquery_location, use_regional_endpoints=True - ) - ) - - # Verify that location and endpoint is correctly set for the BigQuery API - # client - assert session.bqclient.location == bigquery_location - assert ( - session.bqclient._connection.API_BASE_URL - == "https://bigquery.{location}.rep.googleapis.com".format( - location=bigquery_location - ) - ) - - # Verify that endpoint is correctly set for the BigQuery Storage API client - # TODO(shobs): Figure out if we can verify that location is set in the - # BigQuery Storage API client. - assert ( - session.bqstoragereadclient.api_endpoint - == f"bigquerystorage.{bigquery_location}.rep.googleapis.com" - ) - - # assert that bigframes session honors the location - _assert_bq_execution_location(session) - - -def test_clients_provider_no_location(): - credentials = mock.create_autospec(google.auth.credentials.Credentials) - - with pytest.raises(ValueError, match="Must set location to use regional endpoints"): - bigframes.session.clients.ClientsProvider( - project="", credentials=credentials, use_regional_endpoints=True - ) - - -@pytest.mark.parametrize( - "bigquery_location", - # Sort the set to avoid nondeterminism. - sorted(bigframes.constants.REP_NOT_ENABLED_BIGQUERY_LOCATIONS), -) -def test_clients_provider_use_regional_endpoints_non_rep_locations(bigquery_location): - credentials = mock.create_autospec(google.auth.credentials.Credentials) - with pytest.raises( - ValueError, - match=f"not .*available in the location {bigquery_location}", - ): - bigframes.session.clients.ClientsProvider( - project="", - credentials=credentials, - location=bigquery_location, - use_regional_endpoints=True, - ) - - -@pytest.mark.parametrize( - "bigquery_location", - # Sort the set to avoid nondeterminism. - sorted(bigframes.constants.REP_NOT_ENABLED_BIGQUERY_LOCATIONS), -) -def test_session_init_fails_to_use_regional_endpoints_non_rep_endpoints( - bigquery_location, -): - with pytest.raises( - ValueError, - match=f"not .*available in the location {bigquery_location}", - ): - bigframes.Session( - context=bigframes.BigQueryOptions( - location=bigquery_location, use_regional_endpoints=True - ) - ) diff --git a/tests/system/large/test_remote_function.py b/tests/system/large/test_remote_function.py new file mode 100644 index 00000000000..c8f8f66ebaa --- /dev/null +++ b/tests/system/large/test_remote_function.py @@ -0,0 +1,1212 @@ +# Copyright 2023 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 datetime import datetime +import importlib.util +import inspect +import math # must keep this at top level to test udf referring global import +import os.path +import shutil +import tempfile +import textwrap + +from google.api_core.exceptions import NotFound, ResourceExhausted +from google.cloud import functions_v2 +import pandas +import pytest +import test_utils.prefixer + +import bigframes +from bigframes.remote_function import ( + get_cloud_function_name, + get_remote_function_locations, +) +from tests.system.utils import assert_pandas_df_equal_ignore_ordering + +# Use this to control the number of cloud functions being deleted in a single +# test session. This should help soften the spike of the number of mutations per +# minute tracked against a quota limit (default 60, increased to 120 for +# bigframes-dev project) by the Cloud Functions API +# We are running pytest with "-n 20". Let's say each session lasts about a +# minute, so we are setting a limit of 120/20 = 6 deletions per session. +_MAX_NUM_FUNCTIONS_TO_DELETE_PER_SESSION = 6 + +# NOTE: Keep this import at the top level to test global var behavior with +# remote functions +_team_pi = "Team Pi" +_team_euler = "Team Euler" + + +def get_remote_function_endpoints(bigquery_client, dataset_id): + """Get endpoints used by the remote functions in a datset""" + endpoints = set() + routines = bigquery_client.list_routines(dataset=dataset_id) + for routine in routines: + rf_options = routine._properties.get("remoteFunctionOptions") + if not rf_options: + continue + rf_endpoint = rf_options.get("endpoint") + if rf_endpoint: + endpoints.add(rf_endpoint) + return endpoints + + +def get_cloud_functions( + functions_client, project, location, name=None, name_prefix=None +): + """Get the cloud functions in the given project and location.""" + + assert ( + not name or not name_prefix + ), f"At most one of the {name.__name__} or {name_prefix.__name__} can be passed." + + _, location = get_remote_function_locations(location) + parent = f"projects/{project}/locations/{location}" + request = functions_v2.ListFunctionsRequest(parent=parent) + page_result = functions_client.list_functions(request=request) + for response in page_result: + # If name is provided and it does not match then skip + if bool(name): + full_name = parent + f"/functions/{name}" + if response.name != full_name: + continue + # If name prefix is provided and it does not match then skip + elif bool(name_prefix): + full_name_prefix = parent + f"/functions/{name_prefix}" + if not response.name.startswith(full_name_prefix): + continue + + yield response + + +def delete_cloud_function(functions_client, full_name): + """Delete a cloud function with the given fully qualified name.""" + request = functions_v2.DeleteFunctionRequest(name=full_name) + operation = functions_client.delete_function(request=request) + return operation + + +def cleanup_remote_function_assets( + bigquery_client, functions_client, remote_udf, ignore_failures=True +): + """Clean up the GCP assets behind a bigframes remote function.""" + + # Clean up BQ remote function + try: + bigquery_client.delete_routine(remote_udf.bigframes_remote_function) + except Exception: + # By default don't raise exception in cleanup + if not ignore_failures: + raise + + # Clean up cloud function + try: + delete_cloud_function(functions_client, remote_udf.bigframes_cloud_function) + except Exception: + # By default don't raise exception in cleanup + if not ignore_failures: + raise + + +def make_uniq_udf(udf): + """Transform a udf to another with same behavior but a unique name. + Use this to test remote functions with reuse=True, in which case parallel + instances of the same tests may evaluate same named cloud functions and BQ + remote functions, therefore interacting with each other and causing unwanted + failures. With this method one can transform a udf into another with the + same behavior but a different name which will remain unique for the + lifetime of one test instance. + """ + + prefixer = test_utils.prefixer.Prefixer(udf.__name__, "") + udf_uniq_name = prefixer.create_prefix() + udf_file_name = f"{udf_uniq_name}.py" + + # We are not using `tempfile.TemporaryDirectory()` because we want to keep + # the temp code around, otherwise `inspect.getsource()` complains. + tmpdir = tempfile.mkdtemp() + udf_file_path = os.path.join(tmpdir, udf_file_name) + with open(udf_file_path, "w") as f: + # TODO(shobs): Find a better way of modifying the udf, maybe regex? + source_key = f"def {udf.__name__}" + target_key = f"def {udf_uniq_name}" + source_code = textwrap.dedent(inspect.getsource(udf)) + target_code = source_code.replace(source_key, target_key, 1) + f.write(target_code) + spec = importlib.util.spec_from_file_location(udf_file_name, udf_file_path) + udf_uniq = getattr(spec.loader.load_module(), udf_uniq_name) + + # This is a bit of a hack but we need to remove the reference to a foreign + # module, otherwise the serialization would keep the foreign module + # reference and deserialization would fail with error like following: + # ModuleNotFoundError: No module named 'add_one_2nxcmd9j' + # TODO(shobs): Figure out if there is a better way of generating the unique + # function object, but for now let's just set it to same module as the + # original udf. + udf_uniq.__module__ = udf.__module__ + + return udf_uniq, tmpdir + + +@pytest.fixture(scope="module") +def bq_cf_connection() -> str: + """Pre-created BQ connection to invoke cloud function for bigframes-dev + $ bq show --connection --location=us --project_id=bigframes-dev bigframes-rf-conn + """ + return "bigframes-rf-conn" + + +@pytest.fixture(scope="module") +def functions_client() -> functions_v2.FunctionServiceClient: + """Cloud Functions client""" + return functions_v2.FunctionServiceClient() + + +@pytest.fixture(scope="module", autouse=True) +def cleanup_cloud_functions(session, functions_client, dataset_id_permanent): + """Clean up stale cloud functions.""" + permanent_endpoints = get_remote_function_endpoints( + session.bqclient, dataset_id_permanent + ) + delete_count = 0 + for cloud_function in get_cloud_functions( + functions_client, + session.bqclient.project, + session.bqclient.location, + name_prefix="bigframes-", + ): + # Ignore bigframes cloud functions referred by the remote functions in + # the permanent dataset + if cloud_function.service_config.uri in permanent_endpoints: + continue + + # Ignore the functions less than one day old + age = datetime.now() - datetime.fromtimestamp( + cloud_function.update_time.timestamp() + ) + if age.days <= 0: + continue + + # Go ahead and delete + try: + delete_cloud_function(functions_client, cloud_function.name) + delete_count += 1 + if delete_count >= _MAX_NUM_FUNCTIONS_TO_DELETE_PER_SESSION: + break + except NotFound: + # This can happen when multiple pytest sessions are running in + # parallel. Two or more sessions may discover the same cloud + # function, but only one of them would be able to delete it + # successfully, while the other instance will run into this + # exception. Ignore this exception. + pass + except ResourceExhausted: + # This can happen if we are hitting GCP limits, e.g. + # google.api_core.exceptions.ResourceExhausted: 429 Quota exceeded + # for quota metric 'Per project mutation requests' and limit + # 'Per project mutation requests per minute per region' of service + # 'cloudfunctions.googleapis.com' for consumer + # 'project_number:1084210331973'. + # [reason: "RATE_LIMIT_EXCEEDED" domain: "googleapis.com" ... + # Let's stop further clean up and leave it to later. + break + + +@pytest.mark.flaky(retries=2, delay=120) +def test_remote_function_multiply_with_ibis( + session, + scalars_table_id, + ibis_client, + dataset_id, + bq_cf_connection, + functions_client, +): + try: + + @session.remote_function( + [int, int], + int, + dataset_id, + bq_cf_connection, + reuse=False, + ) + def multiply(x, y): + return x * y + + project_id, dataset_name, table_name = scalars_table_id.split(".") + if not ibis_client.dataset: + ibis_client.dataset = dataset_name + + col_name = "int64_col" + table = ibis_client.tables[table_name] + table = table.filter(table[col_name].notnull()).order_by("rowindex").head(10) + pandas_df_orig = table.execute() + + col = table[col_name] + col_2x = multiply(col, 2).name("int64_col_2x") + col_square = multiply(col, col).name("int64_col_square") + table = table.mutate([col_2x, col_square]) + pandas_df_new = table.execute() + + pandas.testing.assert_series_equal( + pandas_df_orig[col_name] * 2, + pandas_df_new["int64_col_2x"], + check_names=False, + ) + + pandas.testing.assert_series_equal( + pandas_df_orig[col_name] * pandas_df_orig[col_name], + pandas_df_new["int64_col_square"], + check_names=False, + ) + finally: + # clean up the gcp assets created for the remote function + cleanup_remote_function_assets(session.bqclient, functions_client, multiply) + + +@pytest.mark.flaky(retries=2, delay=120) +def test_remote_function_stringify_with_ibis( + session, + scalars_table_id, + ibis_client, + dataset_id, + bq_cf_connection, + functions_client, +): + try: + + @session.remote_function( + [int], + str, + dataset_id, + bq_cf_connection, + reuse=False, + ) + def stringify(x): + return f"I got {x}" + + project_id, dataset_name, table_name = scalars_table_id.split(".") + if not ibis_client.dataset: + ibis_client.dataset = dataset_name + + col_name = "int64_col" + table = ibis_client.tables[table_name] + table = table.filter(table[col_name].notnull()).order_by("rowindex").head(10) + pandas_df_orig = table.execute() + + col = table[col_name] + col_2x = stringify(col).name("int64_str_col") + table = table.mutate([col_2x]) + pandas_df_new = table.execute() + + pandas.testing.assert_series_equal( + pandas_df_orig[col_name].apply(lambda x: f"I got {x}"), + pandas_df_new["int64_str_col"], + check_names=False, + ) + finally: + # clean up the gcp assets created for the remote function + cleanup_remote_function_assets(session.bqclient, functions_client, stringify) + + +@pytest.mark.flaky(retries=2, delay=120) +def test_remote_function_decorator_with_bigframes_series( + session, scalars_dfs, dataset_id, bq_cf_connection, functions_client +): + try: + + @session.remote_function( + [int], + int, + dataset_id, + bq_cf_connection, + reuse=False, + ) + def square(x): + return x * x + + scalars_df, scalars_pandas_df = scalars_dfs + + bf_int64_col = scalars_df["int64_col"] + bf_int64_col_filter = bf_int64_col.notnull() + bf_int64_col_filtered = bf_int64_col[bf_int64_col_filter] + bf_result_col = bf_int64_col_filtered.apply(square) + bf_result = ( + bf_int64_col_filtered.to_frame().assign(result=bf_result_col).to_pandas() + ) + + pd_int64_col = scalars_pandas_df["int64_col"] + pd_int64_col_filter = pd_int64_col.notnull() + pd_int64_col_filtered = pd_int64_col[pd_int64_col_filter] + pd_result_col = pd_int64_col_filtered.apply(lambda x: x * x) + # TODO(shobs): Figure why pandas .apply() changes the dtype, i.e. + # pd_int64_col_filtered.dtype is Int64Dtype() + # pd_int64_col_filtered.apply(lambda x: x * x).dtype is int64. + # For this test let's force the pandas dtype to be same as bigframes' dtype. + pd_result_col = pd_result_col.astype(pandas.Int64Dtype()) + pd_result = pd_int64_col_filtered.to_frame().assign(result=pd_result_col) + + assert_pandas_df_equal_ignore_ordering(bf_result, pd_result) + finally: + # clean up the gcp assets created for the remote function + cleanup_remote_function_assets(session.bqclient, functions_client, square) + + +@pytest.mark.flaky(retries=2, delay=120) +def test_remote_function_explicit_with_bigframes_series( + session, scalars_dfs, dataset_id, bq_cf_connection, functions_client +): + try: + + def add_one(x): + return x + 1 + + remote_add_one = session.remote_function( + [int], + int, + dataset_id, + bq_cf_connection, + reuse=False, + )(add_one) + + scalars_df, scalars_pandas_df = scalars_dfs + + bf_int64_col = scalars_df["int64_col"] + bf_int64_col_filter = bf_int64_col.notnull() + bf_int64_col_filtered = bf_int64_col[bf_int64_col_filter] + bf_result_col = bf_int64_col_filtered.apply(remote_add_one) + bf_result = ( + bf_int64_col_filtered.to_frame().assign(result=bf_result_col).to_pandas() + ) + + pd_int64_col = scalars_pandas_df["int64_col"] + pd_int64_col_filter = pd_int64_col.notnull() + pd_int64_col_filtered = pd_int64_col[pd_int64_col_filter] + pd_result_col = pd_int64_col_filtered.apply(add_one) + # TODO(shobs): Figure why pandas .apply() changes the dtype, e.g. + # pd_int64_col_filtered.dtype is Int64Dtype() + # pd_int64_col_filtered.apply(lambda x: x).dtype is int64. + # For this test let's force the pandas dtype to be same as bigframes' dtype. + pd_result_col = pd_result_col.astype(pandas.Int64Dtype()) + pd_result = pd_int64_col_filtered.to_frame().assign(result=pd_result_col) + + assert_pandas_df_equal_ignore_ordering(bf_result, pd_result) + finally: + # clean up the gcp assets created for the remote function + cleanup_remote_function_assets( + session.bqclient, functions_client, remote_add_one + ) + + +@pytest.mark.flaky(retries=2, delay=120) +def test_remote_function_explicit_dataset_not_created( + session, scalars_dfs, dataset_id_not_created, bq_cf_connection, functions_client +): + try: + + @session.remote_function( + [int], + int, + dataset_id_not_created, + bq_cf_connection, + reuse=False, + ) + def square(x): + return x * x + + scalars_df, scalars_pandas_df = scalars_dfs + + bf_int64_col = scalars_df["int64_col"] + bf_int64_col_filter = bf_int64_col.notnull() + bf_int64_col_filtered = bf_int64_col[bf_int64_col_filter] + bf_result_col = bf_int64_col_filtered.apply(square) + bf_result = ( + bf_int64_col_filtered.to_frame().assign(result=bf_result_col).to_pandas() + ) + + pd_int64_col = scalars_pandas_df["int64_col"] + pd_int64_col_filter = pd_int64_col.notnull() + pd_int64_col_filtered = pd_int64_col[pd_int64_col_filter] + pd_result_col = pd_int64_col_filtered.apply(lambda x: x * x) + # TODO(shobs): Figure why pandas .apply() changes the dtype, i.e. + # pd_int64_col_filtered.dtype is Int64Dtype() + # pd_int64_col_filtered.apply(lambda x: x * x).dtype is int64. + # For this test let's force the pandas dtype to be same as bigframes' dtype. + pd_result_col = pd_result_col.astype(pandas.Int64Dtype()) + pd_result = pd_int64_col_filtered.to_frame().assign(result=pd_result_col) + + assert_pandas_df_equal_ignore_ordering(bf_result, pd_result) + finally: + # clean up the gcp assets created for the remote function + cleanup_remote_function_assets(session.bqclient, functions_client, square) + + +@pytest.mark.flaky(retries=2, delay=120) +def test_remote_udf_referring_outside_var( + session, scalars_dfs, dataset_id, bq_cf_connection, functions_client +): + try: + POSITIVE_SIGN = 1 + NEGATIVE_SIGN = -1 + NO_SIGN = 0 + + def sign(num): + if num > 0: + return POSITIVE_SIGN + elif num < 0: + return NEGATIVE_SIGN + return NO_SIGN + + remote_sign = session.remote_function( + [int], + int, + dataset_id, + bq_cf_connection, + reuse=False, + )(sign) + + scalars_df, scalars_pandas_df = scalars_dfs + + bf_int64_col = scalars_df["int64_col"] + bf_int64_col_filter = bf_int64_col.notnull() + bf_int64_col_filtered = bf_int64_col[bf_int64_col_filter] + bf_result_col = bf_int64_col_filtered.apply(remote_sign) + bf_result = ( + bf_int64_col_filtered.to_frame().assign(result=bf_result_col).to_pandas() + ) + + pd_int64_col = scalars_pandas_df["int64_col"] + pd_int64_col_filter = pd_int64_col.notnull() + pd_int64_col_filtered = pd_int64_col[pd_int64_col_filter] + pd_result_col = pd_int64_col_filtered.apply(sign) + # TODO(shobs): Figure why pandas .apply() changes the dtype, e.g. + # pd_int64_col_filtered.dtype is Int64Dtype() + # pd_int64_col_filtered.apply(lambda x: x).dtype is int64. + # For this test let's force the pandas dtype to be same as bigframes' dtype. + pd_result_col = pd_result_col.astype(pandas.Int64Dtype()) + pd_result = pd_int64_col_filtered.to_frame().assign(result=pd_result_col) + + assert_pandas_df_equal_ignore_ordering(bf_result, pd_result) + finally: + # clean up the gcp assets created for the remote function + cleanup_remote_function_assets(session.bqclient, functions_client, remote_sign) + + +@pytest.mark.flaky(retries=2, delay=120) +def test_remote_udf_referring_outside_import( + session, scalars_dfs, dataset_id, bq_cf_connection, functions_client +): + try: + import math as mymath + + def circumference(radius): + return 2 * mymath.pi * radius + + remote_circumference = session.remote_function( + [float], + float, + dataset_id, + bq_cf_connection, + reuse=False, + )(circumference) + + scalars_df, scalars_pandas_df = scalars_dfs + + bf_float64_col = scalars_df["float64_col"] + bf_float64_col_filter = bf_float64_col.notnull() + bf_float64_col_filtered = bf_float64_col[bf_float64_col_filter] + bf_result_col = bf_float64_col_filtered.apply(remote_circumference) + bf_result = ( + bf_float64_col_filtered.to_frame().assign(result=bf_result_col).to_pandas() + ) + + pd_float64_col = scalars_pandas_df["float64_col"] + pd_float64_col_filter = pd_float64_col.notnull() + pd_float64_col_filtered = pd_float64_col[pd_float64_col_filter] + pd_result_col = pd_float64_col_filtered.apply(circumference) + # TODO(shobs): Figure why pandas .apply() changes the dtype, e.g. + # pd_float64_col_filtered.dtype is Float64Dtype() + # pd_float64_col_filtered.apply(lambda x: x).dtype is float64. + # For this test let's force the pandas dtype to be same as bigframes' dtype. + pd_result_col = pd_result_col.astype(pandas.Float64Dtype()) + pd_result = pd_float64_col_filtered.to_frame().assign(result=pd_result_col) + + assert_pandas_df_equal_ignore_ordering(bf_result, pd_result) + finally: + # clean up the gcp assets created for the remote function + cleanup_remote_function_assets( + session.bqclient, functions_client, remote_circumference + ) + + +@pytest.mark.flaky(retries=2, delay=120) +def test_remote_udf_referring_global_var_and_import( + session, scalars_dfs, dataset_id, bq_cf_connection, functions_client +): + try: + + def find_team(num): + boundary = (math.pi + math.e) / 2 + if num >= boundary: + return _team_euler + return _team_pi + + remote_find_team = session.remote_function( + [float], + str, + dataset_id, + bq_cf_connection, + reuse=False, + )(find_team) + + scalars_df, scalars_pandas_df = scalars_dfs + + bf_float64_col = scalars_df["float64_col"] + bf_float64_col_filter = bf_float64_col.notnull() + bf_float64_col_filtered = bf_float64_col[bf_float64_col_filter] + bf_result_col = bf_float64_col_filtered.apply(remote_find_team) + bf_result = ( + bf_float64_col_filtered.to_frame().assign(result=bf_result_col).to_pandas() + ) + + pd_float64_col = scalars_pandas_df["float64_col"] + pd_float64_col_filter = pd_float64_col.notnull() + pd_float64_col_filtered = pd_float64_col[pd_float64_col_filter] + pd_result_col = pd_float64_col_filtered.apply(find_team) + # TODO(shobs): Figure if the dtype mismatch is by design: + # bf_result.dtype: string[pyarrow] + # pd_result.dtype: dtype('O'). + # For this test let's force the pandas dtype to be same as bigframes' dtype. + pd_result_col = pd_result_col.astype(pandas.StringDtype(storage="pyarrow")) + pd_result = pd_float64_col_filtered.to_frame().assign(result=pd_result_col) + + assert_pandas_df_equal_ignore_ordering(bf_result, pd_result) + finally: + # clean up the gcp assets created for the remote function + cleanup_remote_function_assets( + session.bqclient, functions_client, remote_find_team + ) + + +@pytest.mark.flaky(retries=2, delay=120) +def test_remote_function_restore_with_bigframes_series( + session, + scalars_dfs, + dataset_id, + bq_cf_connection, + functions_client, +): + try: + + def add_one(x): + return x + 1 + + # Make a unique udf + add_one_uniq, add_one_uniq_dir = make_uniq_udf(add_one) + + # Expected cloud function name for the unique udf + add_one_uniq_cf_name = get_cloud_function_name(add_one_uniq) + + # There should be no cloud function yet for the unique udf + cloud_functions = list( + get_cloud_functions( + functions_client, + session.bqclient.project, + session.bqclient.location, + name=add_one_uniq_cf_name, + ) + ) + assert len(cloud_functions) == 0 + + # The first time both the cloud function and the bq remote function don't + # exist and would be created + remote_add_one = session.remote_function( + [int], + int, + dataset_id, + bq_cf_connection, + reuse=True, + )(add_one_uniq) + + # There should have been excactly one cloud function created at this point + cloud_functions = list( + get_cloud_functions( + functions_client, + session.bqclient.project, + session.bqclient.location, + name=add_one_uniq_cf_name, + ) + ) + assert len(cloud_functions) == 1 + + # We will test this twice + def inner_test(): + scalars_df, scalars_pandas_df = scalars_dfs + + bf_int64_col = scalars_df["int64_col"] + bf_int64_col_filter = bf_int64_col.notnull() + bf_int64_col_filtered = bf_int64_col[bf_int64_col_filter] + bf_result_col = bf_int64_col_filtered.apply(remote_add_one) + bf_result = ( + bf_int64_col_filtered.to_frame() + .assign(result=bf_result_col) + .to_pandas() + ) + + pd_int64_col = scalars_pandas_df["int64_col"] + pd_int64_col_filter = pd_int64_col.notnull() + pd_int64_col_filtered = pd_int64_col[pd_int64_col_filter] + pd_result_col = pd_int64_col_filtered.apply(add_one_uniq) + # TODO(shobs): Figure why pandas .apply() changes the dtype, i.e. + # pd_int64_col_filtered.dtype is Int64Dtype() + # pd_int64_col_filtered.apply(lambda x: x * x).dtype is int64. + # For this test let's force the pandas dtype to be same as bigframes' dtype. + pd_result_col = pd_result_col.astype(pandas.Int64Dtype()) + pd_result = pd_int64_col_filtered.to_frame().assign(result=pd_result_col) + + assert_pandas_df_equal_ignore_ordering(bf_result, pd_result) + + # Test that the remote function works as expected + inner_test() + + # Let's delete the cloud function while not touching the bq remote function + delete_operation = delete_cloud_function( + functions_client, cloud_functions[0].name + ) + delete_operation.result() + assert delete_operation.done() + + # There should be no cloud functions at this point for the uniq udf + cloud_functions = list( + get_cloud_functions( + functions_client, + session.bqclient.project, + session.bqclient.location, + name=add_one_uniq_cf_name, + ) + ) + assert len(cloud_functions) == 0 + + # The second time bigframes detects that the required cloud function doesn't + # exist even though the remote function exists, and goes ahead and recreates + # the cloud function + remote_add_one = session.remote_function( + [int], + int, + dataset_id, + bq_cf_connection, + reuse=True, + )(add_one_uniq) + + # There should be excactly one cloud function again + cloud_functions = list( + get_cloud_functions( + functions_client, + session.bqclient.project, + session.bqclient.location, + name=add_one_uniq_cf_name, + ) + ) + assert len(cloud_functions) == 1 + + # Test again after the cloud function is restored that the remote function + # works as expected + inner_test() + + # clean up the temp code + shutil.rmtree(add_one_uniq_dir) + finally: + # clean up the gcp assets created for the remote function + cleanup_remote_function_assets( + session.bqclient, functions_client, remote_add_one + ) + + +@pytest.mark.flaky(retries=2, delay=120) +def test_remote_udf_mask_default_value( + session, scalars_dfs, dataset_id, bq_cf_connection, functions_client +): + try: + + def is_odd(num): + flag = False + try: + flag = num % 2 == 1 + except TypeError: + pass + return flag + + is_odd_remote = session.remote_function( + [int], + bool, + dataset_id, + bq_cf_connection, + reuse=False, + )(is_odd) + + scalars_df, scalars_pandas_df = scalars_dfs + + bf_int64_col = scalars_df["int64_col"] + bf_result_col = bf_int64_col.mask(is_odd_remote) + bf_result = bf_int64_col.to_frame().assign(result=bf_result_col).to_pandas() + + pd_int64_col = scalars_pandas_df["int64_col"] + pd_result_col = pd_int64_col.mask(is_odd) + pd_result = pd_int64_col.to_frame().assign(result=pd_result_col) + + assert_pandas_df_equal_ignore_ordering(bf_result, pd_result) + finally: + # clean up the gcp assets created for the remote function + cleanup_remote_function_assets( + session.bqclient, functions_client, is_odd_remote + ) + + +@pytest.mark.flaky(retries=2, delay=120) +def test_remote_udf_mask_custom_value( + session, scalars_dfs, dataset_id, bq_cf_connection, functions_client +): + try: + + def is_odd(num): + flag = False + try: + flag = num % 2 == 1 + except TypeError: + pass + return flag + + is_odd_remote = session.remote_function( + [int], + bool, + dataset_id, + bq_cf_connection, + reuse=False, + )(is_odd) + + scalars_df, scalars_pandas_df = scalars_dfs + + # TODO(shobs): Revisit this test when NA handling of pandas' Series.mask is + # fixed https://github.com/pandas-dev/pandas/issues/52955, + # for now filter out the nulls and test the rest + bf_int64_col = scalars_df["int64_col"] + bf_result_col = bf_int64_col[bf_int64_col.notnull()].mask(is_odd_remote, -1) + bf_result = bf_int64_col.to_frame().assign(result=bf_result_col).to_pandas() + + pd_int64_col = scalars_pandas_df["int64_col"] + pd_result_col = pd_int64_col[pd_int64_col.notnull()].mask(is_odd, -1) + pd_result = pd_int64_col.to_frame().assign(result=pd_result_col) + + assert_pandas_df_equal_ignore_ordering(bf_result, pd_result) + finally: + # clean up the gcp assets created for the remote function + cleanup_remote_function_assets( + session.bqclient, functions_client, is_odd_remote + ) + + +@pytest.mark.flaky(retries=2, delay=120) +def test_remote_udf_lambda( + session, scalars_dfs, dataset_id, bq_cf_connection, functions_client +): + try: + add_one_lambda = lambda x: x + 1 # noqa: E731 + + add_one_lambda_remote = session.remote_function( + [int], + int, + dataset_id, + bq_cf_connection, + reuse=False, + )(add_one_lambda) + + scalars_df, scalars_pandas_df = scalars_dfs + + bf_int64_col = scalars_df["int64_col"] + bf_int64_col_filter = bf_int64_col.notnull() + bf_int64_col_filtered = bf_int64_col[bf_int64_col_filter] + bf_result_col = bf_int64_col_filtered.apply(add_one_lambda_remote) + bf_result = ( + bf_int64_col_filtered.to_frame().assign(result=bf_result_col).to_pandas() + ) + + pd_int64_col = scalars_pandas_df["int64_col"] + pd_int64_col_filter = pd_int64_col.notnull() + pd_int64_col_filtered = pd_int64_col[pd_int64_col_filter] + pd_result_col = pd_int64_col_filtered.apply(add_one_lambda) + # TODO(shobs): Figure why pandas .apply() changes the dtype, i.e. + # pd_int64_col_filtered.dtype is Int64Dtype() + # pd_int64_col_filtered.apply(lambda x: x).dtype is int64. + # For this test let's force the pandas dtype to be same as bigframes' dtype. + pd_result_col = pd_result_col.astype(pandas.Int64Dtype()) + pd_result = pd_int64_col_filtered.to_frame().assign(result=pd_result_col) + + assert_pandas_df_equal_ignore_ordering(bf_result, pd_result) + finally: + # clean up the gcp assets created for the remote function + cleanup_remote_function_assets( + session.bqclient, functions_client, add_one_lambda_remote + ) + + +@pytest.mark.flaky(retries=2, delay=120) +def test_remote_function_with_explicit_name( + session, scalars_dfs, dataset_id, bq_cf_connection, functions_client +): + try: + + def square(x): + return x * x + + prefixer = test_utils.prefixer.Prefixer(square.__name__, "") + rf_name = prefixer.create_prefix() + expected_remote_function = f"{dataset_id}.{rf_name}" + + # Initially the expected BQ remote function should not exist + with pytest.raises(NotFound): + session.bqclient.get_routine(expected_remote_function) + + # Create the remote function with the name provided explicitly + square_remote = session.remote_function( + [int], + int, + dataset_id, + bq_cf_connection, + reuse=False, + name=rf_name, + )(square) + + # The remote function should reflect the explicitly provided name + assert square_remote.bigframes_remote_function == expected_remote_function + + # Now the expected BQ remote function should exist + session.bqclient.get_routine(expected_remote_function) + + # The behavior of the created remote function should be as expected + scalars_df, scalars_pandas_df = scalars_dfs + + bf_int64_col = scalars_df["int64_too"] + bf_result_col = bf_int64_col.apply(square_remote) + bf_result = bf_int64_col.to_frame().assign(result=bf_result_col).to_pandas() + + pd_int64_col = scalars_pandas_df["int64_too"] + pd_result_col = pd_int64_col.apply(square) + # TODO(shobs): Figure why pandas .apply() changes the dtype, i.e. + # pd_int64_col.dtype is Int64Dtype() + # pd_int64_col.apply(square).dtype is int64. + # For this test let's force the pandas dtype to be same as bigframes' dtype. + pd_result_col = pd_result_col.astype(pandas.Int64Dtype()) + pd_result = pd_int64_col.to_frame().assign(result=pd_result_col) + + assert_pandas_df_equal_ignore_ordering(bf_result, pd_result) + finally: + # clean up the gcp assets created for the remote function + cleanup_remote_function_assets( + session.bqclient, functions_client, square_remote + ) + + +@pytest.mark.flaky(retries=2, delay=120) +def test_remote_function_with_external_package_dependencies( + session, scalars_dfs, dataset_id, bq_cf_connection, functions_client +): + try: + + def pd_np_foo(x): + import numpy as mynp + import pandas as mypd + + return mypd.Series([x, mynp.sqrt(mynp.abs(x))]).sum() + + # Create the remote function with the name provided explicitly + pd_np_foo_remote = session.remote_function( + [int], + float, + dataset_id, + bq_cf_connection, + reuse=False, + packages=["numpy", "pandas >= 2.0.0"], + )(pd_np_foo) + + # The behavior of the created remote function should be as expected + scalars_df, scalars_pandas_df = scalars_dfs + + bf_int64_col = scalars_df["int64_too"] + bf_result_col = bf_int64_col.apply(pd_np_foo_remote) + bf_result = bf_int64_col.to_frame().assign(result=bf_result_col).to_pandas() + + pd_int64_col = scalars_pandas_df["int64_too"] + pd_result_col = pd_int64_col.apply(pd_np_foo) + pd_result = pd_int64_col.to_frame().assign(result=pd_result_col) + + # pandas result is non-nullable type float64, make it Float64 before + # comparing for the purpose of this test + pd_result.result = pd_result.result.astype(pandas.Float64Dtype()) + + assert_pandas_df_equal_ignore_ordering(bf_result, pd_result) + finally: + # clean up the gcp assets created for the remote function + cleanup_remote_function_assets( + session.bqclient, functions_client, pd_np_foo_remote + ) + + +@pytest.mark.flaky(retries=2, delay=120) +def test_remote_function_with_explicit_name_reuse( + session, scalars_dfs, dataset_id, bq_cf_connection, functions_client +): + try: + + dirs_to_cleanup = [] + + # Define a user code + def square(x): + return x * x + + # Make it a unique udf + square_uniq, square_uniq_dir = make_uniq_udf(square) + dirs_to_cleanup.append(square_uniq_dir) + + # Define a common routine which accepts a remote function and the + # corresponding user defined function and tests that bigframes bahavior + # on the former is in parity with the pandas behaviour on the latter + def test_internal(rf, udf): + # The behavior of the created remote function should be as expected + scalars_df, scalars_pandas_df = scalars_dfs + + bf_int64_col = scalars_df["int64_too"] + bf_result_col = bf_int64_col.apply(rf) + bf_result = bf_int64_col.to_frame().assign(result=bf_result_col).to_pandas() + + pd_int64_col = scalars_pandas_df["int64_too"] + pd_result_col = pd_int64_col.apply(udf) + # TODO(shobs): Figure why pandas .apply() changes the dtype, i.e. + # pd_int64_col.dtype is Int64Dtype() + # pd_int64_col.apply(square).dtype is int64. + # For this test let's force the pandas dtype to be same as bigframes' dtype. + pd_result_col = pd_result_col.astype(pandas.Int64Dtype()) + pd_result = pd_int64_col.to_frame().assign(result=pd_result_col) + + assert_pandas_df_equal_ignore_ordering(bf_result, pd_result) + + # Create an explicit name for the remote function + prefixer = test_utils.prefixer.Prefixer("foo", "") + rf_name = prefixer.create_prefix() + expected_remote_function = f"{dataset_id}.{rf_name}" + + # Initially the expected BQ remote function should not exist + with pytest.raises(NotFound): + session.bqclient.get_routine(expected_remote_function) + + # Create a new remote function with the name provided explicitly + square_remote1 = session.remote_function( + [int], + int, + dataset_id, + bq_cf_connection, + name=rf_name, + )(square_uniq) + + # The remote function should reflect the explicitly provided name + assert square_remote1.bigframes_remote_function == expected_remote_function + + # Now the expected BQ remote function should exist + routine = session.bqclient.get_routine(expected_remote_function) + square_remote1_created = routine.created + square_remote1_cf_updated = session.cloudfunctionsclient.get_function( + name=square_remote1.bigframes_cloud_function + ).update_time + + # Test pandas parity with square udf + test_internal(square_remote1, square) + + # Now Create another remote function with the same name provided + # explicitly. Since reuse is True by default, the previously created + # remote function with the same name will be reused. + square_remote2 = session.remote_function( + [int], + int, + dataset_id, + bq_cf_connection, + name=rf_name, + )(square_uniq) + + # The new remote function should still reflect the explicitly provided name + assert square_remote2.bigframes_remote_function == expected_remote_function + + # The expected BQ remote function should still exist + routine = session.bqclient.get_routine(expected_remote_function) + square_remote2_created = routine.created + square_remote2_cf_updated = session.cloudfunctionsclient.get_function( + name=square_remote2.bigframes_cloud_function + ).update_time + + # The new remote function should reflect that the previous BQ remote + # function and the cloud function were reused instead of creating anew + assert square_remote2_created == square_remote1_created + assert ( + square_remote2.bigframes_cloud_function + == square_remote1.bigframes_cloud_function + ) + assert square_remote2_cf_updated == square_remote1_cf_updated + + # Test again that the new remote function is actually same as the + # previous remote function + test_internal(square_remote2, square) + + # Now define a different user code + def plusone(x): + return x + 1 + + # Make it a unique udf + plusone_uniq, plusone_uniq_dir = make_uniq_udf(plusone) + dirs_to_cleanup.append(plusone_uniq_dir) + + # Now Create a third remote function with the same name provided + # explicitly. Even though reuse is True by default, the previously + # created remote function with the same name should not be reused since + # this time it is a different user code. + plusone_remote = session.remote_function( + [int], + int, + dataset_id, + bq_cf_connection, + name=rf_name, + )(plusone_uniq) + + # The new remote function should still reflect the explicitly provided name + assert plusone_remote.bigframes_remote_function == expected_remote_function + + # The expected BQ remote function should still exist + routine = session.bqclient.get_routine(expected_remote_function) + plusone_remote_created = routine.created + plusone_remote_cf_updated = session.cloudfunctionsclient.get_function( + name=plusone_remote.bigframes_cloud_function + ).update_time + + # The new remote function should reflect that the previous BQ remote + # function and the cloud function were NOT reused, instead were created + # anew + assert plusone_remote_created > square_remote2_created + assert ( + plusone_remote.bigframes_cloud_function + != square_remote2.bigframes_cloud_function + ) + assert plusone_remote_cf_updated > square_remote2_cf_updated + + # Test again that the new remote function is equivalent to the new user + # defined function + test_internal(plusone_remote, plusone) + finally: + # clean up the gcp assets created for the remote function + cleanup_remote_function_assets( + session.bqclient, functions_client, square_remote1 + ) + cleanup_remote_function_assets( + session.bqclient, functions_client, square_remote2 + ) + cleanup_remote_function_assets( + session.bqclient, functions_client, plusone_remote + ) + for dir_ in dirs_to_cleanup: + shutil.rmtree(dir_) + + +@pytest.mark.flaky(retries=2, delay=120) +def test_remote_function_via_session_context_connection_setter( + scalars_dfs, dataset_id, bq_cf_connection +): + # Creating a session scoped only to this test as we would be setting a + # property in it + context = bigframes.BigQueryOptions() + context.bq_connection = bq_cf_connection + session = bigframes.connect(context) + + try: + # Without an explicit bigquery connection, the one present in Session, + # set via context setter would be used. Without an explicit `reuse` the + # default behavior of reuse=True will take effect. Please note that the + # udf is same as the one used in other tests in this file so the underlying + # cloud function would be common with reuse=True. Since we are using a + # unique dataset_id, even though the cloud function would be reused, the bq + # remote function would still be created, making use of the bq connection + # set in the BigQueryOptions above. + @session.remote_function([int], int, dataset=dataset_id) + def square(x): + return x * x + + scalars_df, scalars_pandas_df = scalars_dfs + + bf_int64_col = scalars_df["int64_col"] + bf_int64_col_filter = bf_int64_col.notnull() + bf_int64_col_filtered = bf_int64_col[bf_int64_col_filter] + bf_result_col = bf_int64_col_filtered.apply(square) + bf_result = ( + bf_int64_col_filtered.to_frame().assign(result=bf_result_col).to_pandas() + ) + + pd_int64_col = scalars_pandas_df["int64_col"] + pd_int64_col_filter = pd_int64_col.notnull() + pd_int64_col_filtered = pd_int64_col[pd_int64_col_filter] + pd_result_col = pd_int64_col_filtered.apply(lambda x: x * x) + # TODO(shobs): Figure why pandas .apply() changes the dtype, i.e. + # pd_int64_col_filtered.dtype is Int64Dtype() + # pd_int64_col_filtered.apply(lambda x: x * x).dtype is int64. + # For this test let's force the pandas dtype to be same as bigframes' dtype. + pd_result_col = pd_result_col.astype(pandas.Int64Dtype()) + pd_result = pd_int64_col_filtered.to_frame().assign(result=pd_result_col) + + assert_pandas_df_equal_ignore_ordering(bf_result, pd_result) + finally: + # clean up the gcp assets created for the remote function + cleanup_remote_function_assets( + session.bqclient, session.cloudfunctionsclient, square + ) + + +@pytest.mark.flaky(retries=2, delay=120) +def test_remote_function_default_connection(session, scalars_dfs, dataset_id): + try: + + @session.remote_function([int], int, dataset=dataset_id) + def square(x): + return x * x + + scalars_df, scalars_pandas_df = scalars_dfs + + bf_int64_col = scalars_df["int64_col"] + bf_int64_col_filter = bf_int64_col.notnull() + bf_int64_col_filtered = bf_int64_col[bf_int64_col_filter] + bf_result_col = bf_int64_col_filtered.apply(square) + bf_result = ( + bf_int64_col_filtered.to_frame().assign(result=bf_result_col).to_pandas() + ) + + pd_int64_col = scalars_pandas_df["int64_col"] + pd_int64_col_filter = pd_int64_col.notnull() + pd_int64_col_filtered = pd_int64_col[pd_int64_col_filter] + pd_result_col = pd_int64_col_filtered.apply(lambda x: x * x) + # TODO(shobs): Figure why pandas .apply() changes the dtype, i.e. + # pd_int64_col_filtered.dtype is Int64Dtype() + # pd_int64_col_filtered.apply(lambda x: x * x).dtype is int64. + # For this test let's force the pandas dtype to be same as bigframes' dtype. + pd_result_col = pd_result_col.astype(pandas.Int64Dtype()) + pd_result = pd_int64_col_filtered.to_frame().assign(result=pd_result_col) + + assert_pandas_df_equal_ignore_ordering(bf_result, pd_result) + finally: + # clean up the gcp assets created for the remote function + cleanup_remote_function_assets( + session.bqclient, session.cloudfunctionsclient, square + ) diff --git a/tests/system/large/test_session.py b/tests/system/large/test_session.py index 937b3c9e274..62fa5a83d33 100644 --- a/tests/system/large/test_session.py +++ b/tests/system/large/test_session.py @@ -12,200 +12,41 @@ # See the License for the specific language governing permissions and # limitations under the License. -import datetime -from unittest import mock - -import google.cloud.bigquery as bigquery -import google.cloud.exceptions -import numpy as np -import pandas as pd import pytest -import bigframes -import bigframes.pandas as bpd -import bigframes.session._io.bigquery - - -@pytest.fixture -def large_pd_df(): - nrows = 1000000 - - np_int1 = np.random.randint(0, 1000, size=nrows, dtype=np.int32) - np_int2 = np.random.randint(10000, 20000, size=nrows, dtype=np.int64) - np_bool = np.random.choice([True, False], size=nrows) - np_float1 = np.random.rand(nrows).astype(np.float32) - np_float2 = np.random.normal(loc=50.0, scale=10.0, size=nrows).astype(np.float64) - - return pd.DataFrame( - { - "int_col_1": np_int1, - "int_col_2": np_int2, - "bool_col": np_bool, - "float_col_1": np_float1, - "float_col_2": np_float2, - } - ) - - -@pytest.mark.parametrize( - ("write_engine"), - [ - ("bigquery_load"), - ("bigquery_streaming"), - # TODO(b/502298527): Reenable bigquery_write test - # ("bigquery_write"), - ], -) -def test_read_pandas_large_df(session, large_pd_df, write_engine: str): - df = session.read_pandas(large_pd_df, write_engine=write_engine) - assert len(df.peek(5)) == 5 - assert len(large_pd_df) == 1000000 - - -def test_close(session: bigframes.Session): - # we will create two tables and confirm that they are deleted - # when the session is closed - - bqclient = session.bqclient - - expiration = ( - datetime.datetime.now(datetime.timezone.utc) - + bigframes.constants.DEFAULT_EXPIRATION - ) - full_id_1 = bigframes.session._io.bigquery.create_temp_table( - session.bqclient, - session._anon_dataset_manager.allocate_temp_table(), - expiration, - ) - full_id_2 = bigframes.session._io.bigquery.create_temp_table( - session.bqclient, - session._anon_dataset_manager.allocate_temp_table(), - expiration, - ) - - # check that the tables were actually created - assert bqclient.get_table(full_id_1).created is not None - assert bqclient.get_table(full_id_2).created is not None - - session.close() - - # check that the tables are already deleted - with pytest.raises(google.cloud.exceptions.NotFound): - bqclient.delete_table(full_id_1) - with pytest.raises(google.cloud.exceptions.NotFound): - bqclient.delete_table(full_id_2) - - -def test_clean_up_by_session_id(): - # we do this test in a different region in order to avoid - # overly large amounts of temp tables slowing the test down - option_context = bigframes.BigQueryOptions() - option_context.location = "europe-west10" - session = bigframes.Session(context=option_context) - session_id = session.session_id - - # we will create two tables and confirm that they are deleted - # when the session is cleaned up by id - bqclient = session.bqclient - dataset = session._anonymous_dataset - expiration = ( - datetime.datetime.now(datetime.timezone.utc) - + bigframes.constants.DEFAULT_EXPIRATION - ) - bigframes.session._io.bigquery.create_temp_table( - session.bqclient, - session._anon_dataset_manager.allocate_temp_table(), - expiration, - ) - bigframes.session._io.bigquery.create_temp_table( - session.bqclient, - session._anon_dataset_manager.allocate_temp_table(), - expiration, - ) - - # check that some table exists with the expected session_id - tables_before = bqclient.list_tables( - dataset, - max_results=bigframes.session._io.bigquery._LIST_TABLES_LIMIT, - page_size=bigframes.session._io.bigquery._LIST_TABLES_LIMIT, - ) - assert any([(session.session_id in table.full_table_id) for table in tables_before]) - - bpd.clean_up_by_session_id( - session_id, location=session._location, project=session._project - ) - - # check that no tables with the session_id are left after cleanup - tables_after = bqclient.list_tables( - dataset, - max_results=bigframes.session._io.bigquery._LIST_TABLES_LIMIT, - page_size=bigframes.session._io.bigquery._LIST_TABLES_LIMIT, - ) - assert not any( - [(session.session_id in table.full_table_id) for table in tables_after] - ) +from bigframes import Session @pytest.mark.parametrize( - ("session_creator"), + ("query_or_table", "index_col"), [ - pytest.param(bigframes.Session, id="session-constructor"), - pytest.param(bigframes.connect, id="connect-method"), + pytest.param( + "bigquery-public-data.patents_view.ipcr_201708", + (), + id="1g_table_w_default_index", + ), + pytest.param( + "bigquery-public-data.new_york_taxi_trips.tlc_yellow_trips_2011", + (), + id="30g_table_w_default_index", + ), + # TODO(chelsealin): Disable the long run tests until we have propertily + # ordering support to avoid materializating any data. + # # Adding default index to large tables would take much longer time, + # # e.g. ~5 mins for a 100G table, ~20 mins for a 1T table. + # pytest.param( + # "bigquery-public-data.stackoverflow.post_history", + # ["id"], + # id="100g_table_w_unique_column_index", + # ), + # pytest.param( + # "bigquery-public-data.wise_all_sky_data_release.all_wise", + # ["cntr"], + # id="1t_table_w_unique_column_index", + # ), ], ) -@pytest.mark.flaky(retries=3) -def test_clean_up_via_context_manager(session_creator): - # we will create two tables and confirm that they are deleted - # when the session is closed - with session_creator() as session: - bqclient = session.bqclient - - full_id_1 = session._anon_dataset_manager.create_temp_table( - [bigquery.SchemaField("a", "INT64")], cluster_cols=[] - ) - assert session._session_resource_manager is not None - full_id_2 = session._session_resource_manager.create_temp_table( - [bigquery.SchemaField("b", "STRING")], cluster_cols=["b"] - ) - - # check that the tables were actually created - assert bqclient.get_table(full_id_1).created is not None - assert bqclient.get_table(full_id_2).created is not None - - # check that the tables are already deleted - with pytest.raises(google.cloud.exceptions.NotFound): - bqclient.delete_table(full_id_1) - with pytest.raises(google.cloud.exceptions.NotFound): - bqclient.delete_table(full_id_2) - - -def test_cleanup_old_udfs(session: bigframes.Session): - routine_ref = session._anon_dataset_manager.dataset.routine("test_routine_cleanup") - - # Create a dummy function to be deleted. - create_function_sql = f""" -CREATE OR REPLACE FUNCTION `{routine_ref.project}.{routine_ref.dataset_id}.{routine_ref.routine_id}`(x INT64) -RETURNS INT64 LANGUAGE python -OPTIONS (entry_point='dummy_func', runtime_version='python-3.11') -AS r''' -def dummy_func(x): - return x + 1 -''' - """ - session.bqclient.query(create_function_sql).result() - - assert session.bqclient.get_routine(routine_ref) is not None - - mock_routine = mock.MagicMock(spec=bigquery.Routine) - mock_routine.created = datetime.datetime.now( - datetime.timezone.utc - ) - datetime.timedelta(days=100) - mock_routine.reference = routine_ref - mock_routine._properties = {"routineType": "SCALAR_FUNCTION"} - routines = [mock_routine] - - with mock.patch.object(session.bqclient, "list_routines", return_value=routines): - session._anon_dataset_manager._cleanup_old_udfs() - - with pytest.raises(google.cloud.exceptions.NotFound): - session.bqclient.get_routine(routine_ref) +def test_read_gbq_for_large_tables(session: Session, query_or_table, index_col): + """Verify read_gbq() is able to read large tables.""" + df = session.read_gbq(query_or_table, index_col=index_col) + assert len(df.columns) != 0 diff --git a/tests/system/large/test_tpch.py b/tests/system/large/test_tpch.py deleted file mode 100644 index de630ce0dd4..00000000000 --- a/tests/system/large/test_tpch.py +++ /dev/null @@ -1,101 +0,0 @@ -# Copyright 2026 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 os -import re - -import pandas as pd -import pytest -from google.cloud import bigquery - -TPCH_PATH = "third_party/bigframes_vendored/tpch" -PROJECT_ID = "bigframes-dev-perf" -DATASET_ID = "tpch_0001g" -DATASET = { - "line_item_ds": f"{PROJECT_ID}.{DATASET_ID}.LINEITEM", - "region_ds": f"{PROJECT_ID}.{DATASET_ID}.REGION", - "nation_ds": f"{PROJECT_ID}.{DATASET_ID}.NATION", - "supplier_ds": f"{PROJECT_ID}.{DATASET_ID}.SUPPLIER", - "part_ds": f"{PROJECT_ID}.{DATASET_ID}.PART", - "part_supp_ds": f"{PROJECT_ID}.{DATASET_ID}.PARTSUPP", - "customer_ds": f"{PROJECT_ID}.{DATASET_ID}.CUSTOMER", - "orders_ds": f"{PROJECT_ID}.{DATASET_ID}.ORDERS", -} - - -def _execute_sql_query(bigquery_client, sql_query): - sql_query = sql_query.format(**DATASET) - - job_config = bigquery.QueryJobConfig(use_query_cache=False) - query_job = bigquery_client.query(sql_query, job_config=job_config) - query_job.result() - df = query_job.to_dataframe() - df.columns = df.columns.str.upper() - return df - - -def _execute_bigframes_script(session, bigframes_script): - bigframes_script = re.sub( - r"next\((\w+)\.to_pandas_batches\((.*?)\)\)", - r"return \1.to_pandas()", - bigframes_script, - ) - bigframes_script = re.sub(r"_\s*=\s*(\w+)", r"return \1", bigframes_script) - - bigframes_script = ( - bigframes_script - + f"\nresult = q('{PROJECT_ID}', '{DATASET_ID}', _initialize_session)" - ) - exec_globals = {"_initialize_session": session} - exec(bigframes_script, exec_globals) - bigframes_result = exec_globals.get("result") - return bigframes_result - - -def _verify_result(bigframes_result, sql_result): - if isinstance(bigframes_result, pd.DataFrame): - pd.testing.assert_frame_equal( - sql_result.reset_index(drop=True), - bigframes_result.reset_index(drop=True), - check_dtype=False, - ) - else: - assert sql_result.shape == (1, 1) - sql_scalar = sql_result.iloc[0, 0] - assert sql_scalar == bigframes_result - - -@pytest.mark.parametrize("query_num", range(1, 23)) -@pytest.mark.parametrize("ordered", [True, False]) -def test_tpch_correctness(session, unordered_session, query_num, ordered): - """Runs verification of TPCH benchmark script outputs to ensure correctness.""" - # Execute SQL: - sql_file_path = f"{TPCH_PATH}/sql_queries/q{query_num}.sql" - assert os.path.exists(sql_file_path) - with open(sql_file_path, "r") as f: - sql_query = f.read() - - sql_result = _execute_sql_query(session.bqclient, sql_query) - - # Execute BigFrames: - file_path = f"{TPCH_PATH}/queries/q{query_num}.py" - assert os.path.exists(file_path) - with open(file_path, "r") as file: - bigframes_script = file.read() - - bigframes_result = _execute_bigframes_script( - session if ordered else unordered_session, bigframes_script - ) - - _verify_result(bigframes_result, sql_result) diff --git a/tests/system/load/conftest.py b/tests/system/load/conftest.py deleted file mode 100644 index f15f50c7e73..00000000000 --- a/tests/system/load/conftest.py +++ /dev/null @@ -1,39 +0,0 @@ -# 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. -# 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 typing import Generator - -import pytest - -import bigframes - - -# Override the session to target at bigframes-load-testing at all load tests. That allows to run load tests locally with authentic env. -@pytest.fixture(scope="session") -def session() -> Generator[bigframes.Session, None, None]: - context = bigframes.BigQueryOptions(location="US", project="bigframes-load-testing") - session = bigframes.Session(context=context) - yield session - session.close() # close generated session at cleanup time - - -@pytest.fixture(scope="session") -def session_us_east5() -> Generator[bigframes.Session, None, None]: - context = bigframes.BigQueryOptions( - location="us-east5", project="bigframes-load-testing" - ) - session = bigframes.Session(context=context) - yield session - session.close() # close generated session at cleanup time diff --git a/tests/system/load/test_large_tables.py b/tests/system/load/test_large_tables.py deleted file mode 100644 index ee49c2703ef..00000000000 --- a/tests/system/load/test_large_tables.py +++ /dev/null @@ -1,102 +0,0 @@ -# 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. -# You may obtain a copy of the License at -# -# https://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. - -"""Load test for query (SQL) inputs with large results sizes.""" - -import pytest - -import bigframes.pandas as bpd - -KB_BYTES = 1000 -MB_BYTES = 1000 * KB_BYTES -GB_BYTES = 1000 * MB_BYTES -TB_BYTES = 1000 * GB_BYTES - - -@pytest.mark.parametrize( - ("sql", "expected_bytes"), - ( - pytest.param( - "SELECT * FROM load_testing.scalars_1gb", - GB_BYTES, - id="1gb", - ), - pytest.param( - "SELECT * FROM load_testing.scalars_10gb", - 10 * GB_BYTES, - id="10gb", - ), - pytest.param( - "SELECT * FROM load_testing.scalars_100gb", - 100 * GB_BYTES, - id="100gb", - ), - pytest.param( - "SELECT * FROM load_testing.scalars_1tb", - TB_BYTES, - id="1tb", - ), - ), -) -def test_read_gbq_sql_large_results(sql, expected_bytes): - df = bpd.read_gbq(sql) - assert df.memory_usage().sum() >= expected_bytes - - -def test_df_repr_large_table(): - df = bpd.read_gbq("load_testing.scalars_100gb") - row_count, column_count = df.shape - expected = f"[{row_count} rows x {column_count} columns]" - actual = repr(df) - assert expected in actual - - -def test_series_repr_large_table(): - df = bpd.read_gbq("load_testing.scalars_1tb") - actual = repr(df["string_col"]) - assert actual is not None - - -def test_index_repr_large_table(): - df = bpd.read_gbq("load_testing.scalars_1tb") - actual = repr(df.index) - assert actual is not None - - -def test_to_pandas_batches_large_table(): - df = bpd.read_gbq("load_testing.scalars_100gb") - _, expected_column_count = df.shape - - # download only a few batches, since 1tb would be too much - iterable = df.to_pandas_batches( - page_size=500, max_results=1500, allow_large_results=True - ) - # use page size since client library doesn't support - # streaming only part of the dataframe via bqstorage - for pdf in iterable: - batch_row_count, batch_column_count = pdf.shape - assert batch_column_count == expected_column_count - assert 0 < batch_row_count <= 500 - - -@pytest.mark.skip(reason="See if it caused kokoro build aborted.") -def test_to_pandas_large_table(): - df = bpd.read_gbq("load_testing.scalars_10gb") - # df will be downloaded locally - expected_row_count, expected_column_count = df.shape - - df_converted = df.to_pandas() - row_count, column_count = df_converted.shape - assert column_count == expected_column_count - assert row_count == expected_row_count diff --git a/tests/system/load/test_llm.py b/tests/system/load/test_llm.py deleted file mode 100644 index eec76cf9b67..00000000000 --- a/tests/system/load/test_llm.py +++ /dev/null @@ -1,160 +0,0 @@ -# Copyright 2023 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 as pd -import pytest - -from bigframes.ml import llm -from bigframes.testing import utils - - -@pytest.fixture(scope="session") -def llm_remote_text_pandas_df(): - """Additional data matching the penguins dataset, with a new index""" - return pd.DataFrame( - { - "prompt": [ - "Please do sentiment analysis on the following text and only output a number from 0 to 5 where 0 means sadness, 1 means joy, 2 means love, 3 means anger, 4 means fear, and 5 means surprise. Text: i feel beautifully emotional knowing that these women of whom i knew just a handful were holding me and my baba on our journey", - "Please do sentiment analysis on the following text and only output a number from 0 to 5 where 0 means sadness, 1 means joy, 2 means love, 3 means anger, 4 means fear, and 5 means surprise. Text: i was feeling a little vain when i did this one", - "Please do sentiment analysis on the following text and only output a number from 0 to 5 where 0 means sadness, 1 means joy, 2 means love, 3 means anger, 4 means fear, and 5 means surprise. Text: a father of children killed in an accident", - ], - } - ) - - -@pytest.fixture(scope="session") -def llm_remote_text_df(session, llm_remote_text_pandas_df): - return session.read_pandas(llm_remote_text_pandas_df) - - -@pytest.mark.parametrize( - "model_name", - ( - "gemini-2.5-flash", - "gemini-2.5-flash-lite", - ), -) -def test_llm_gemini_configure_fit( - session, model_name, llm_fine_tune_df_default_index, llm_remote_text_df -): - model = llm.GeminiTextGenerator( - session=session, model_name=model_name, max_iterations=1 - ) - - X_train = llm_fine_tune_df_default_index[["prompt"]] - y_train = llm_fine_tune_df_default_index[["label"]] - model.fit(X_train, y_train) - - assert model is not None - - df = model.predict( - llm_remote_text_df["prompt"], - temperature=0.5, - max_output_tokens=100, - top_k=20, - top_p=0.5, - ).to_pandas() - utils.check_pandas_df_schema_and_index( - df, - columns=[ - "ml_generate_text_llm_result", - "ml_generate_text_rai_result", - "ml_generate_text_status", - "prompt", - ], - index=3, - ) - - -@pytest.mark.flaky(retries=2) -def test_llm_gemini_w_ground_with_google_search(llm_remote_text_df): - model = llm.GeminiTextGenerator(model_name="gemini-2.5-flash", max_iterations=1) - df = model.predict( - llm_remote_text_df["prompt"], - ground_with_google_search=True, - ).to_pandas() - utils.check_pandas_df_schema_and_index( - df, - columns=[ - "ml_generate_text_llm_result", - "ml_generate_text_rai_result", - "ml_generate_text_grounding_result", - "ml_generate_text_status", - "prompt", - ], - index=3, - ) - - -# (b/366290533): Claude models are of extremely low capacity. The tests should reside in small tests. Moving these here just to protect BQML's shared capacity(as load test only runs once per day.) and make sure we still have minimum coverage. -@pytest.mark.flaky(retries=3, delay=120) -def test_claude3_text_generator_create_load(dataset_id, session, bq_connection): - claude3_text_generator_model = llm.Claude3TextGenerator( - model_name="claude-3-haiku", connection_name=bq_connection, session=session - ) - assert claude3_text_generator_model is not None - assert claude3_text_generator_model._bqml_model is not None - - # save, load to ensure configuration was kept - reloaded_model = claude3_text_generator_model.to_gbq( - f"{dataset_id}.temp_text_model", replace=True - ) - assert f"{dataset_id}.temp_text_model" == reloaded_model._bqml_model.model_name - assert reloaded_model.connection_name == bq_connection - assert reloaded_model.model_name == "claude-3-haiku" - - -@pytest.mark.flaky(retries=3, delay=120) -def test_claude3_text_generator_predict_default_params_success( - llm_text_df, session, bq_connection -): - claude3_text_generator_model = llm.Claude3TextGenerator( - model_name="claude-3-haiku", connection_name=bq_connection, session=session - ) - df = claude3_text_generator_model.predict(llm_text_df).to_pandas() - utils.check_pandas_df_schema_and_index( - df, columns=utils.ML_GENERATE_TEXT_OUTPUT, index=3, col_exact=False - ) - - -@pytest.mark.flaky(retries=3, delay=120) -def test_claude3_text_generator_predict_with_params_success( - llm_text_df, session, bq_connection -): - claude3_text_generator_model = llm.Claude3TextGenerator( - model_name="claude-3-haiku", connection_name=bq_connection, session=session - ) - df = claude3_text_generator_model.predict( - llm_text_df, max_output_tokens=100, top_k=20, top_p=0.5 - ).to_pandas() - utils.check_pandas_df_schema_and_index( - df, columns=utils.ML_GENERATE_TEXT_OUTPUT, index=3, col_exact=False - ) - - -@pytest.mark.flaky(retries=3, delay=120) -def test_claude3_text_generator_predict_multi_col_success( - llm_text_df, session, bq_connection -): - llm_text_df["additional_col"] = 1 - claude3_text_generator_model = llm.Claude3TextGenerator( - model_name="claude-3-haiku", connection_name=bq_connection, session=session - ) - df = claude3_text_generator_model.predict(llm_text_df).to_pandas() - utils.check_pandas_df_schema_and_index( - df, - columns=utils.ML_GENERATE_TEXT_OUTPUT + ["additional_col"], - index=3, - col_exact=False, - ) diff --git a/tests/system/small/bigquery/__init__.py b/tests/system/small/bigquery/__init__.py deleted file mode 100644 index 6d5e14bcf4a..00000000000 --- a/tests/system/small/bigquery/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -# 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. -# 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/bigquery/test_ai.py b/tests/system/small/bigquery/test_ai.py deleted file mode 100644 index 0b6738dec80..00000000000 --- a/tests/system/small/bigquery/test_ai.py +++ /dev/null @@ -1,590 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from __future__ import annotations - -import uuid -from unittest import mock - -import google.cloud.bigquery -import pandas as pd -import pyarrow as pa -import pytest - -import bigframes.bigquery as bbq -import bigframes.pandas as bpd -from bigframes import dataframe, dtypes, series -from bigframes.testing import utils as test_utils - - -@pytest.fixture -def use_ibis_compiler(): - original_setting = bpd.options.experiments.sql_compiler - bpd.options.experiments.sql_compiler = "legacy" - try: - yield - finally: - bpd.options.experiments.sql_compiler = original_setting - - -def _create_mock_obj_ref_df(session, uris, name="image", connection=None): - df = bpd.DataFrame({name: uris}, session=session) - # Convert string URIs to ObjectRef structs - if connection is None: - connection = "us.bigframes-rf-conn" - df[name] = bbq.obj.make_ref(df[name], authorizer=connection) - - table_id = f"bigframes-dev.bigframes_tests_sys.tmp_obj_ref_{uuid.uuid4().hex}" - df.to_gbq(table_id, if_exists="replace") - - client = session.bqclient - table = client.get_table(table_id) - schema = list(table.schema) - for i, field in enumerate(schema): - if field.name == name: - schema[i] = google.cloud.bigquery.SchemaField( - name=field.name, - field_type=field.field_type, - mode=field.mode, - description="bigframes_dtype: OBJ_REF_DTYPE", - fields=field.fields, - ) - break - table.schema = schema - client.update_table(table, ["schema"]) - - return session.read_gbq(table_id) - - -def test_ai_function_pandas_tuple_input(session): - s1 = pd.Series(["apple", "bear"]) - s2 = bpd.Series(["fruit", "tree"], session=session) - prompt = (s1, " is a ", s2) - - result = bbq.ai.generate_bool(prompt, endpoint="gemini-2.5-flash") - - assert _contains_no_nulls(result) - assert result.dtype == pd.ArrowDtype( - pa.struct( - ( - pa.field("result", pa.bool_()), - pa.field("full_response", dtypes.JSON_ARROW_TYPE), - pa.field("status", pa.string()), - ) - ) - ) - - -def test_ai_function_pandas_series_input(session): - s = pd.Series(["cat", "lavender"]) - - result = bbq.ai.classify( - s, categories=["animal", "plant"], endpoint="gemini-2.5-flash" - ) - - assert len(result) == len(s) - assert result.dtype == dtypes.STRING_DTYPE - - -def test_ai_function_string_input(session): - with mock.patch( - "bigframes.core.global_session.get_global_session" - ) as mock_get_session: - mock_get_session.return_value = session - prompt = "Is apple a fruit?" - - result = bbq.ai.generate_bool(prompt, endpoint="gemini-2.5-flash") - - assert _contains_no_nulls(result) - assert result.dtype == pd.ArrowDtype( - pa.struct( - ( - pa.field("result", pa.bool_()), - pa.field("full_response", dtypes.JSON_ARROW_TYPE), - pa.field("status", pa.string()), - ) - ) - ) - - -def test_ai_function_compile_model_params(session): - s1 = bpd.Series(["apple", "bear"], session=session) - s2 = bpd.Series(["fruit", "tree"], session=session) - prompt = (s1, " is a ", s2) - model_params = {"generation_config": {"thinking_config": {"thinking_budget": 0}}} - - result = bbq.ai.generate_bool( - prompt, endpoint="gemini-2.5-flash", model_params=model_params - ) - - assert _contains_no_nulls(result) - assert result.dtype == pd.ArrowDtype( - pa.struct( - ( - pa.field("result", pa.bool_()), - pa.field("full_response", dtypes.JSON_ARROW_TYPE), - pa.field("status", pa.string()), - ) - ) - ) - - -def test_ai_generate(session): - country = bpd.Series(["Japan", "Canada"], session=session) - prompt = ("What's the capital city of ", country, "? one word only") - - result = bbq.ai.generate(prompt, endpoint="gemini-2.5-flash") - - assert _contains_no_nulls(result) - assert result.dtype == pd.ArrowDtype( - pa.struct( - ( - pa.field("result", pa.string()), - pa.field("full_response", dtypes.JSON_ARROW_TYPE), - pa.field("status", pa.string()), - ) - ) - ) - - -def test_ai_generate_access_full_response_with_ibis(session, use_ibis_compiler): - country = bpd.Series(["Japan", "Canada"], session=session) - prompt = ("What's the capital city of ", country, "? one word only") - - result = ( - bbq.ai.generate(prompt, endpoint="gemini-2.5-flash") - .struct.field("full_response") - .to_pandas() - ) - - assert _contains_no_nulls(result) - - -def test_ai_generate_with_output_schema(session): - country = bpd.Series(["Japan", "Canada"], session=session) - prompt = ("Describe ", country) - - result = bbq.ai.generate( - prompt, - endpoint="gemini-2.5-flash", - output_schema={"population": "INT64", "is_in_north_america": "bool"}, - ) - - assert _contains_no_nulls(result) - assert result.dtype == pd.ArrowDtype( - pa.struct( - ( - pa.field("is_in_north_america", pa.bool_()), - pa.field("population", pa.int64()), - pa.field("full_response", dtypes.JSON_ARROW_TYPE), - pa.field("status", pa.string()), - ) - ) - ) - - -def test_ai_generate_with_invalid_output_schema_raise_error(session): - country = bpd.Series(["Japan", "Canada"], session=session) - prompt = ("Describe ", country) - - with pytest.raises(ValueError): - bbq.ai.generate( - prompt, - endpoint="gemini-2.5-flash", - output_schema={"population": "INT64", "is_in_north_america": "JSON"}, - ) - - -def test_ai_generate_bool(session): - s1 = bpd.Series(["apple", "bear"], session=session) - s2 = bpd.Series(["fruit", "tree"], session=session) - prompt = (s1, " is a ", s2) - - result = bbq.ai.generate_bool(prompt, endpoint="gemini-2.5-flash") - - assert _contains_no_nulls(result) - assert result.dtype == pd.ArrowDtype( - pa.struct( - ( - pa.field("result", pa.bool_()), - pa.field("full_response", dtypes.JSON_ARROW_TYPE), - pa.field("status", pa.string()), - ) - ) - ) - - -def test_ai_generate_bool_access_full_response_with_ibis(session, use_ibis_compiler): - s1 = bpd.Series(["apple", "bear"], session=session) - s2 = bpd.Series(["fruit", "tree"], session=session) - prompt = (s1, " is a ", s2) - - result = ( - bbq.ai.generate_bool(prompt, endpoint="gemini-2.5-flash") - .struct.field("full_response") - .to_pandas() - ) - - assert _contains_no_nulls(result) - - -def test_ai_generate_bool_multi_model(session, bq_connection): - df = _create_mock_obj_ref_df( - session, - ["gs://cloud-samples-data/vision/ocr/sign.jpg"], - name="image", - connection=bq_connection, - ) - - image_runtime = bbq.obj.get_access_url(df["image"], mode="R") - result = bbq.ai.generate_bool((image_runtime, " contains an animal")) - - assert _contains_no_nulls(result) - assert result.dtype == pd.ArrowDtype( - pa.struct( - ( - pa.field("result", pa.bool_()), - pa.field("full_response", dtypes.JSON_ARROW_TYPE), - pa.field("status", pa.string()), - ) - ) - ) - - -def test_ai_generate_int(session): - s = bpd.Series(["Cat"], session=session) - prompt = ("How many legs does a ", s, " have?") - - result = bbq.ai.generate_int(prompt, endpoint="gemini-2.5-flash") - - assert _contains_no_nulls(result) - assert result.dtype == pd.ArrowDtype( - pa.struct( - ( - pa.field("result", pa.int64()), - pa.field("full_response", dtypes.JSON_ARROW_TYPE), - pa.field("status", pa.string()), - ) - ) - ) - - -def test_ai_generate_int_access_full_response_with_ibis(session, use_ibis_compiler): - s = bpd.Series(["Cat"], session=session) - prompt = ("How many legs does a ", s, " have?") - - result = ( - bbq.ai.generate_int(prompt, endpoint="gemini-2.5-flash") - .struct.field("full_response") - .to_pandas() - ) - - assert _contains_no_nulls(result) - - -def test_ai_generate_int_multi_model(session, bq_connection): - df = _create_mock_obj_ref_df( - session, - ["gs://cloud-samples-data/vision/ocr/sign.jpg"], - name="image", - connection=bq_connection, - ) - - image_runtime = bbq.obj.get_access_url(df["image"], mode="R") - result = bbq.ai.generate_int( - ("How many animals are there in the picture ", image_runtime) - ) - - assert _contains_no_nulls(result) - assert result.dtype == pd.ArrowDtype( - pa.struct( - ( - pa.field("result", pa.int64()), - pa.field("full_response", dtypes.JSON_ARROW_TYPE), - pa.field("status", pa.string()), - ) - ) - ) - - -def test_ai_generate_double(session): - s = bpd.Series(["Cat"], session=session) - prompt = ("How many legs does a ", s, " have?") - - result = bbq.ai.generate_double(prompt, endpoint="gemini-2.5-flash") - - assert _contains_no_nulls(result) - assert result.dtype == pd.ArrowDtype( - pa.struct( - ( - pa.field("result", pa.float64()), - pa.field("full_response", dtypes.JSON_ARROW_TYPE), - pa.field("status", pa.string()), - ) - ) - ) - - -def test_ai_generate_double_access_full_response_with_ibis(session, use_ibis_compiler): - s = bpd.Series(["Cat"], session=session) - prompt = ("How many legs does a ", s, " have?") - - result = ( - bbq.ai.generate_double(prompt, endpoint="gemini-2.5-flash") - .struct.field("full_response") - .to_pandas() - ) - - assert _contains_no_nulls(result) - - -def test_ai_generate_double_multi_model(session, bq_connection): - df = _create_mock_obj_ref_df( - session, - ["gs://cloud-samples-data/vision/ocr/sign.jpg"], - name="image", - connection=bq_connection, - ) - - image_runtime = bbq.obj.get_access_url(df["image"], mode="R") - result = bbq.ai.generate_double( - ("How many animals are there in the picture ", image_runtime) - ) - - assert _contains_no_nulls(result) - assert result.dtype == pd.ArrowDtype( - pa.struct( - ( - pa.field("result", pa.float64()), - pa.field("full_response", dtypes.JSON_ARROW_TYPE), - pa.field("status", pa.string()), - ) - ) - ) - - -def test_ai_embed_series_content(session): - content = bpd.Series(["dog"], session=session) - - result = bbq.ai.embed(content, endpoint="text-embedding-005") - - assert _contains_no_nulls(result) - assert result.dtype == pd.ArrowDtype( - pa.struct( - ( - pa.field("result", pa.list_(pa.float64())), - pa.field("status", pa.string()), - ) - ) - ) - - -def test_ai_embed_string_content(session): - with mock.patch( - "bigframes.core.global_session.get_global_session" - ) as mock_get_session: - mock_get_session.return_value = session - - result = bbq.ai.embed("dog", endpoint="text-embedding-005") - - assert _contains_no_nulls(result) - assert result.dtype == pd.ArrowDtype( - pa.struct( - ( - pa.field("result", pa.list_(pa.float64())), - pa.field("status", pa.string()), - ) - ) - ) - - -def test_ai_if(session): - s1 = bpd.Series(["apple", "bear"], session=session) - s2 = bpd.Series(["fruit", "tree"], session=session) - prompt = (s1, " is a ", s2) - - result = bbq.ai.if_( - prompt, - optimization_mode="maximize_quality", - max_error_ratio=0.5, - ) - - assert len(result) == len(s1) - assert result.dtype == dtypes.BOOL_DTYPE - - -def test_ai_if_multi_model(session, bq_connection): - df = _create_mock_obj_ref_df( - session, - ["gs://cloud-samples-data/vision/ocr/sign.jpg"], - name="image", - connection=bq_connection, - ) - - image_runtime = bbq.obj.get_access_url(df["image"], mode="R") - result = bbq.ai.if_((image_runtime, " contains an animal")) - - assert len(result) == len(df) - assert result.dtype == dtypes.BOOL_DTYPE - - -def test_ai_classify(session): - s = bpd.Series(["cat", "orchid"], session=session) - - result = bbq.ai.classify(s, ["animal", "plant"]) - - assert len(result) == len(s) - assert result.dtype == dtypes.STRING_DTYPE - - -def test_ai_classify_with_examples(session): - s = bpd.Series(["cat", "orchid"], session=session) - - result = bbq.ai.classify(s, ["animal", "plant"], examples=[("dog", "animal")]) - - assert len(result) == len(s) - assert result.dtype == dtypes.STRING_DTYPE - - -def test_ai_classify_output_mode(session, bq_connection): - s = bpd.Series(["cat", "orchid"], session=session) - - result = bbq.ai.classify( - s, ["animal", "plant"], output_mode="multi", examples=[("dog", ["animal"])] - ) - - assert len(result) == len(s) - assert result.dtype == dtypes.list_type(dtypes.STRING_DTYPE) - - -def test_ai_classify_multi_model(session, bq_connection): - df = _create_mock_obj_ref_df( - session, - ["gs://cloud-samples-data/vision/ocr/sign.jpg"], - name="image", - connection=bq_connection, - ) - - image_runtime = bbq.obj.get_access_url(df["image"], mode="R") - result = bbq.ai.classify(image_runtime, ["photo", "cartoon"]) - - assert len(result) == len(df) - assert result.dtype == dtypes.STRING_DTYPE - - -def test_ai_score(session): - s = bpd.Series(["Tiger", "Rabbit"], session=session) - prompt = ("Rank the relative weights of ", s, " on the scale from 1 to 3") - - result = bbq.ai.score(prompt) - - assert len(result) == len(s) - assert result.dtype == dtypes.FLOAT_DTYPE - - -def test_ai_score_multi_model(session, bq_connection): - df = _create_mock_obj_ref_df( - session, - ["gs://cloud-samples-data/vision/ocr/sign.jpg"], - name="image", - connection=bq_connection, - ) - image_runtime = bbq.obj.get_access_url(df["image"], mode="R") - prompt = ("Rank the liveliness of ", image_runtime, "on the scale from 1 to 3") - - result = bbq.ai.score(prompt) - - assert len(result) == len(df) - assert result.dtype == dtypes.FLOAT_DTYPE - - -def test_forecast_default_params(time_series_df_default_index: dataframe.DataFrame): - df = time_series_df_default_index[time_series_df_default_index["id"] == "1"] - - result = bbq.ai.forecast(df, timestamp_col="parsed_date", data_col="total_visits") - - expected_columns = [ - "forecast_timestamp", - "forecast_value", - "confidence_level", - "prediction_interval_lower_bound", - "prediction_interval_upper_bound", - "ai_forecast_status", - ] - test_utils.check_pandas_df_schema_and_index( - result, - columns=expected_columns, - index=10, - ) - - -def test_forecast_w_params(time_series_df_default_index: dataframe.DataFrame): - result = bbq.ai.forecast( - time_series_df_default_index, - timestamp_col="parsed_date", - data_col="total_visits", - id_cols=["id"], - horizon=20, - confidence_level=0.98, - context_window=64, - ) - - expected_columns = [ - "id", - "forecast_timestamp", - "forecast_value", - "confidence_level", - "prediction_interval_lower_bound", - "prediction_interval_upper_bound", - "ai_forecast_status", - ] - test_utils.check_pandas_df_schema_and_index( - result, - columns=expected_columns, - index=20 * 2, # 20 for each id - ) - - -def test_ai_similarity(session): - s1 = bpd.Series(["happy", "sad"], session=session) - s2 = pd.Series(["glad", "angry"]) - - result = bbq.ai.similarity(s1, s2, endpoint="text-embedding-005") - - assert _contains_no_nulls(result) - assert result.dtype == dtypes.FLOAT_DTYPE - - -def test_ai_similarity_one_content_is_string_literal(session): - s1 = "happy" - s2 = bpd.Series(["glad", "angry"], session=session) - - result = bbq.ai.similarity(s1, s2, model="embeddinggemma-300m") - - assert _contains_no_nulls(result) - assert result.dtype == dtypes.FLOAT_DTYPE - - -def test_ai_similarity_both_contents_are_string_literals(session): - s1 = "happy" - s2 = "glad" - - result = bbq.ai.similarity(s1, s2, endpoint="text-embedding-005") - - assert _contains_no_nulls(result) - assert result.dtype == dtypes.FLOAT_DTYPE - - -def _contains_no_nulls(s: series.Series | pd.Series) -> bool: - return len(s) == s.count() diff --git a/tests/system/small/bigquery/test_approx_agg.py b/tests/system/small/bigquery/test_approx_agg.py deleted file mode 100644 index c88f5850f80..00000000000 --- a/tests/system/small/bigquery/test_approx_agg.py +++ /dev/null @@ -1,76 +0,0 @@ -# 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. -# 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 - -import bigframes.bigquery as bbq -import bigframes.pandas as bpd - - -@pytest.mark.parametrize( - ("data", "expected"), - [ - pytest.param( - [1, 2, 3, 3, 2], [{"value": 3, "count": 2}, {"value": 2, "count": 2}] - ), - pytest.param( - ["apple", "apple", "pear", "pear", "pear", "banana"], - [{"value": "pear", "count": 3}, {"value": "apple", "count": 2}], - ), - pytest.param( - [True, False, True, False, True], - [{"value": True, "count": 3}, {"value": False, "count": 2}], - ), - pytest.param( - [], - [], - ), - pytest.param( - [[1, 2], [1], [1, 2]], - [], - marks=pytest.mark.xfail(raises=TypeError), - ), - ], - ids=["int64", "string", "bool", "null", "array"], -) -def test_approx_top_count_w_dtypes(data, expected): - s = bpd.Series(data) - result = bbq.approx_top_count(s, number=2) - assert result == expected - - -@pytest.mark.parametrize( - ("number", "expected"), - [ - pytest.param( - 0, - [], - marks=pytest.mark.xfail(raises=ValueError), - ), - pytest.param(1, [{"value": 3, "count": 2}]), - pytest.param( - 4, - [ - {"value": 3, "count": 2}, - {"value": 2, "count": 2}, - {"value": 1, "count": 1}, - ], - ), - ], - ids=["zero", "one", "full"], -) -def test_approx_top_count_w_numbers(number, expected): - s = bpd.Series([1, 2, 3, 3, 2]) - result = bbq.approx_top_count(s, number=number) - assert result == expected diff --git a/tests/system/small/bigquery/test_array.py b/tests/system/small/bigquery/test_array.py deleted file mode 100644 index c8c69f7457e..00000000000 --- a/tests/system/small/bigquery/test_array.py +++ /dev/null @@ -1,205 +0,0 @@ -# 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. -# 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 typing import cast - -import numpy as np -import pandas as pd -import pytest - -import bigframes.bigquery as bbq -import bigframes.dtypes -import bigframes.pandas as bpd - - -@pytest.mark.parametrize( - ["input_data", "expected"], - [ - pytest.param( - [["A", "AA", "AAA"], ["BB", "B"], np.nan, [], ["C"]], - [ - 3, - 2, - # TODO(b/336880368): Allow for NULL values to be input for ARRAY - # columns. Once we actually store NULL values, this will be - # NULL where the input is NULL. - 0, - 0, - 1, - ], - id="small-string", - ), - pytest.param( - [[1, 2, 3], [4, 5], [], [], [6]], [3, 2, 0, 0, 1], id="small-int64" - ), - pytest.param( - [ - # Regression test for b/414374215 where the Series constructor - # returns empty lists when the lists are too big to embed in - # SQL. - list(np.random.randint(-1_000_000, 1_000_000, size=1000)), - list(np.random.randint(-1_000_000, 1_000_000, size=967)), - list(np.random.randint(-1_000_000, 1_000_000, size=423)), - list(np.random.randint(-1_000_000, 1_000_000, size=5000)), - list(np.random.randint(-1_000_000, 1_000_000, size=1003)), - list(np.random.randint(-1_000_000, 1_000_000, size=9999)), - ], - [ - 1000, - 967, - 423, - 5000, - 1003, - 9999, - ], - id="larger-int64", - ), - ], -) -def test_array_length(input_data, expected): - series = pd.Series(input_data) - expected = pd.Series( - expected, - index=pd.Index(range(len(input_data)), dtype="Int64"), - dtype=bigframes.dtypes.INT_DTYPE, - ) - result = cast(bpd.Series, bbq.array_length(series)) - pd.testing.assert_series_equal( - result.to_pandas(), - expected, - check_index_type=False, - ) - - -@pytest.mark.parametrize( - ("input_data", "output_data"), - [ - pytest.param([1, 2, 3, 4, 5], [[1, 2], [3, 4], [5]], id="ints"), - pytest.param( - ["e", "d", "c", "b", "a"], - [["e", "d"], ["c", "b"], ["a"]], - id="reverse_strings", - ), - pytest.param( - [1.0, 2.0, np.nan, np.nan, np.nan], [[1.0, 2.0], [], []], id="nans" - ), - pytest.param( - [{"A": {"x": 1.0}}, {"A": {"z": 4.0}}, {}, {"B": "b"}, np.nan], - [[{"A": {"x": 1.0}}, {"A": {"z": 4.0}}], [{}, {"B": "b"}], []], - id="structs", - ), - ], -) -def test_array_agg_w_series_groupby(input_data, output_data): - input_index = ["a", "a", "b", "b", "c"] - series = bpd.Series(input_data, index=input_index) - result = bbq.array_agg(series.groupby(level=0)) - - expected = bpd.Series(output_data, index=["a", "b", "c"]) - pd.testing.assert_series_equal( - result.to_pandas(), # type: ignore - expected.to_pandas(), - ) - - -def test_array_agg_w_dataframe_groupby(): - data = { - "a": [1, 1, 2, 1], - "b": [2, None, 1, 2], - "c": [3, 4, 3, 2], - } - df = bpd.DataFrame(data) - result = bbq.array_agg(df.groupby(by=["b"])) - - expected_data = { - "b": [1.0, 2.0], - "a": [[2], [1, 1]], - "c": [[3], [3, 2]], - } - expected = bpd.DataFrame(expected_data).set_index("b") - - pd.testing.assert_frame_equal( - result.to_pandas(), # type: ignore - expected.to_pandas(), - ) - - -def test_array_agg_w_series(): - series = bpd.Series([1, 2, 3, 4, 5], index=["a", "a", "b", "b", "c"]) - # Mypy error expected: array_agg currently incompatible with Series. - # Test for coverage. - with pytest.raises(ValueError): - bbq.array_agg(series) # type: ignore - - -@pytest.mark.parametrize( - ("ascending", "expected_b", "expected_c"), - [ - pytest.param( - True, [["a", "b"], ["e", "d", "c"]], [[4, 5], [1, 2, 3]], id="asc" - ), - pytest.param( - False, [["b", "a"], ["c", "d", "e"]], [[5, 4], [3, 2, 1]], id="des" - ), - ], -) -def test_array_agg_reserve_order(ascending, expected_b, expected_c): - data = { - "a": [1, 1, 2, 2, 2], - "b": ["a", "b", "c", "d", "e"], - "c": [4, 5, 3, 2, 1], - } - df = bpd.DataFrame(data) - - result = bbq.array_agg(df.sort_values("c", ascending=ascending).groupby(by=["a"])) - expected_data = { - "a": [1, 2], - "b": expected_b, - "c": expected_c, - } - expected = bpd.DataFrame(expected_data).set_index("a") - - pd.testing.assert_frame_equal( - result.to_pandas(), # type: ignore - expected.to_pandas(), - ) - - -def test_array_agg_matches_after_explode(): - data = { - "index": np.arange(10), - "a": [np.random.randint(0, 10, 10) for _ in range(10)], - "b": [np.random.randint(0, 10, 10) for _ in range(10)], - } - df = bpd.DataFrame(data).set_index("index") - result = bbq.array_agg(df.explode(["a", "b"]).groupby(level=0)) - result.index.name = "index" - - pd.testing.assert_frame_equal( - result.to_pandas(), # type: ignore - df.to_pandas(), - ) - - -@pytest.mark.parametrize( - ("data"), - [ - pytest.param([[1, 2], [3, 4], [5]], id="int_array"), - pytest.param(["hello", "world"], id="string"), - ], -) -def test_array_to_string_w_type_checks(data): - series = bpd.Series(data) - with pytest.raises(TypeError): - bbq.array_to_string(series, delimiter=", ") diff --git a/tests/system/small/bigquery/test_datetime.py b/tests/system/small/bigquery/test_datetime.py deleted file mode 100644 index 58e07928f0c..00000000000 --- a/tests/system/small/bigquery/test_datetime.py +++ /dev/null @@ -1,144 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import typing - -import pandas as pd -import pyarrow as pa -import pytest - -import bigframes.testing.utils -from bigframes import bigquery - -_TIMESTAMP_DTYPE = pd.ArrowDtype(pa.timestamp("us", tz="UTC")) - - -@pytest.fixture -def int_series(session): - pd_series = pd.Series([1, 2, 3, 4, 5]) - - return session.read_pandas(pd_series), pd_series - - -def test_unix_seconds(scalars_dfs): - bigframes_df, pandas_df = scalars_dfs - - actual_res = bigquery.unix_seconds(bigframes_df["timestamp_col"]).to_pandas() - - expected_res = ( - pandas_df["timestamp_col"] - .apply(lambda ts: _to_unix_epoch(ts, "s")) - .astype("Int64") - ) - bigframes.testing.utils.assert_series_equal(actual_res, expected_res) - - -def test_unix_seconds_after_type_casting(int_series): - bf_series, pd_series = int_series - - actual_res = bigquery.unix_seconds(bf_series.astype(_TIMESTAMP_DTYPE)).to_pandas() - - expected_res = ( - pd_series.astype(_TIMESTAMP_DTYPE) - .apply(lambda ts: _to_unix_epoch(ts, "s")) - .astype("Int64") - ) - bigframes.testing.utils.assert_series_equal( - actual_res, expected_res, check_index_type=False - ) - - -def test_unix_seconds_incorrect_input_type_raise_error(scalars_dfs): - df, _ = scalars_dfs - - with pytest.raises(TypeError): - bigquery.unix_seconds(df["string_col"]) - - -def test_unix_millis(scalars_dfs): - bigframes_df, pandas_df = scalars_dfs - - actual_res = bigquery.unix_millis(bigframes_df["timestamp_col"]).to_pandas() - - expected_res = ( - pandas_df["timestamp_col"] - .apply(lambda ts: _to_unix_epoch(ts, "ms")) - .astype("Int64") - ) - bigframes.testing.utils.assert_series_equal(actual_res, expected_res) - - -def test_unix_millis_after_type_casting(int_series): - bf_series, pd_series = int_series - - actual_res = bigquery.unix_millis(bf_series.astype(_TIMESTAMP_DTYPE)).to_pandas() - - expected_res = ( - pd_series.astype(_TIMESTAMP_DTYPE) - .apply(lambda ts: _to_unix_epoch(ts, "ms")) - .astype("Int64") - ) - bigframes.testing.utils.assert_series_equal( - actual_res, expected_res, check_index_type=False - ) - - -def test_unix_millis_incorrect_input_type_raise_error(scalars_dfs): - df, _ = scalars_dfs - - with pytest.raises(TypeError): - bigquery.unix_millis(df["string_col"]) - - -def test_unix_micros(scalars_dfs): - bigframes_df, pandas_df = scalars_dfs - - actual_res = bigquery.unix_micros(bigframes_df["timestamp_col"]).to_pandas() - - expected_res = ( - pandas_df["timestamp_col"] - .apply(lambda ts: _to_unix_epoch(ts, "us")) - .astype("Int64") - ) - bigframes.testing.utils.assert_series_equal(actual_res, expected_res) - - -def test_unix_micros_after_type_casting(int_series): - bf_series, pd_series = int_series - - actual_res = bigquery.unix_micros(bf_series.astype(_TIMESTAMP_DTYPE)).to_pandas() - - expected_res = ( - pd_series.astype(_TIMESTAMP_DTYPE) - .apply(lambda ts: _to_unix_epoch(ts, "us")) - .astype("Int64") - ) - bigframes.testing.utils.assert_series_equal( - actual_res, expected_res, check_index_type=False - ) - - -def test_unix_micros_incorrect_input_type_raise_error(scalars_dfs): - df, _ = scalars_dfs - - with pytest.raises(TypeError): - bigquery.unix_micros(df["string_col"]) - - -def _to_unix_epoch( - ts: pd.Timestamp, unit: typing.Literal["s", "ms", "us"] -) -> typing.Optional[int]: - if pd.isna(ts): - return None - return (ts - pd.Timestamp("1970-01-01", tz="UTC")) // pd.Timedelta(1, unit) diff --git a/tests/system/small/bigquery/test_geo.py b/tests/system/small/bigquery/test_geo.py deleted file mode 100644 index 16df467d24a..00000000000 --- a/tests/system/small/bigquery/test_geo.py +++ /dev/null @@ -1,490 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from __future__ import annotations - -import geopandas # type: ignore -import pandas as pd -import pandas.testing -import pytest -from shapely.geometry import ( # type: ignore - GeometryCollection, - LineString, - MultiLineString, - MultiPoint, - MultiPolygon, - Point, - Polygon, -) - -import bigframes.bigquery as bbq -import bigframes.geopandas -import bigframes.session -import bigframes.testing.utils -from bigframes.bigquery import st_length - - -def test_geo_st_area(session: bigframes.session.Session): - data = [ - Polygon([(0.000, 0.0), (0.001, 0.001), (0.000, 0.001)]), - Polygon([(0.0010, 0.004), (0.009, 0.005), (0.0010, 0.005)]), - Polygon([(0.001, 0.001), (0.002, 0.001), (0.002, 0.002)]), - LineString([(0, 0), (1, 1), (0, 1)]), - Point(0, 1), - ] - - geopd_s = geopandas.GeoSeries(data=data, crs="EPSG:4326") - geobf_s = bigframes.geopandas.GeoSeries(data=data, session=session) - - # For `geopd_s`, the data was further projected with `geopandas.GeoSeries.to_crs` - # to `to_crs(26393)` to get the area in square meter. See: https://geopandas.org/en/stable/docs/user_guide/projections.html - # and https://spatialreference.org/ref/epsg/26393/. We then rounded both results - # to get them as close to each other as possible. Initially, the area results - # were +ten-millions. We added more zeros after the decimal point to round the - # area results to the nearest thousands. - geopd_s_result = geopd_s.to_crs(26393).area.round(-3) - geobf_s_result = bbq.st_area(geobf_s).to_pandas().round(-3) - assert geobf_s_result.iloc[0] >= 1000 - - bigframes.testing.utils.assert_series_equal( - geobf_s_result, - geopd_s_result, - check_dtype=False, - check_index_type=False, - check_exact=False, - rtol=0.1, - ) - - -# Expected length for 1 degree of longitude at the equator is approx 111195.079734 meters -DEG_LNG_EQUATOR_METERS = 111195.07973400292 - - -def test_st_length_various_geometries(session): - input_geometries = [ - Point(0, 0), - LineString([(0, 0), (1, 0)]), - Polygon([(0, 0), (1, 0), (0, 1), (0, 0)]), - MultiPoint([Point(0, 0), Point(1, 1)]), - MultiLineString([LineString([(0, 0), (1, 0)]), LineString([(0, 0), (0, 1)])]), - MultiPolygon( - [ - Polygon([(0, 0), (1, 0), (0, 1), (0, 0)]), - Polygon([(2, 2), (3, 2), (2, 3), (2, 2)]), - ] - ), - GeometryCollection([Point(0, 0), LineString([(0, 0), (1, 0)])]), - GeometryCollection([]), - None, # Represents NULL geography input - GeometryCollection([Point(1, 1), Point(2, 2)]), - ] - geoseries = bigframes.geopandas.GeoSeries(input_geometries, session=session) - - expected_lengths = pd.Series( - [ - 0.0, # Point - DEG_LNG_EQUATOR_METERS, # LineString - 0.0, # Polygon - 0.0, # MultiPoint - 2 * DEG_LNG_EQUATOR_METERS, # MultiLineString - 0.0, # MultiPolygon - DEG_LNG_EQUATOR_METERS, # GeometryCollection (Point + LineString) - 0.0, # Empty GeometryCollection - pd.NA, # None input for ST_LENGTH(NULL) is NULL - 0.0, # GeometryCollection (Point + Point) - ], - index=pd.Index(range(10), dtype="Int64"), - dtype="Float64", - ) - - # Test default use_spheroid - result_default = st_length(geoseries).to_pandas() - bigframes.testing.utils.assert_series_equal( - result_default, - expected_lengths, - rtol=1e-3, - atol=1e-3, # For comparisons involving 0.0 - ) # type: ignore - - # Test explicit use_spheroid=False - result_explicit_false = st_length(geoseries, use_spheroid=False).to_pandas() - bigframes.testing.utils.assert_series_equal( - result_explicit_false, - expected_lengths, - rtol=1e-3, - atol=1e-3, # For comparisons involving 0.0 - ) # type: ignore - - -def test_geo_st_difference_with_geometry_objects(session: bigframes.session.Session): - data1 = [ - Polygon([(0, 0), (10, 0), (10, 10), (0, 0)]), - Polygon([(0, 0), (1, 1), (0, 1), (0, 0)]), - Point(0, 1), - ] - - data2 = [ - Polygon([(0, 0), (10, 0), (10, 10), (0, 0)]), - Polygon([(0, 0), (1, 1), (0, 1), (0, 0)]), - LineString([(2, 0), (0, 2)]), - ] - - geobf_s1 = bigframes.geopandas.GeoSeries(data=data1, session=session) - geobf_s2 = bigframes.geopandas.GeoSeries(data=data2, session=session) - geobf_s_result = bbq.st_difference(geobf_s1, geobf_s2).to_pandas() - - expected = pd.Series( - [ - GeometryCollection([]), - GeometryCollection([]), - Point(0, 1), - ], - index=[0, 1, 2], - dtype=geopandas.array.GeometryDtype(), - ) - bigframes.testing.utils.assert_series_equal( - geobf_s_result, - expected, - check_index_type=False, - check_exact=False, - rtol=0.1, - ) - - -def test_geo_st_difference_with_single_geometry_object( - session: bigframes.session.Session, -): - pytest.importorskip( - "shapely", - minversion="2.0.0", - reason="shapely objects must be hashable to include in our expression trees", - ) - - data1 = [ - Polygon([(0, 0), (10, 0), (10, 10), (0, 10), (0, 0)]), - Polygon([(0, 1), (10, 1), (10, 9), (0, 9), (0, 1)]), - Point(0, 1), - ] - - geobf_s1 = bigframes.geopandas.GeoSeries(data=data1, session=session) - geobf_s_result = bbq.st_difference( - geobf_s1, - Polygon([(0, 0), (10, 0), (10, 5), (0, 5), (0, 0)]), - ).to_pandas() - - expected = pd.Series( - [ - Polygon([(10, 5), (10, 10), (0, 10), (0, 5), (10, 5)]), - Polygon([(10, 5), (10, 9), (0, 9), (0, 5), (10, 5)]), - GeometryCollection([]), - ], - index=[0, 1, 2], - dtype=geopandas.array.GeometryDtype(), - ) - bigframes.testing.utils.assert_series_equal( - geobf_s_result, - expected, - check_index_type=False, - check_exact=False, - rtol=0.1, - ) - - -def test_geo_st_difference_with_similar_geometry_objects( - session: bigframes.session.Session, -): - data1 = [ - Polygon([(0, 0), (10, 0), (10, 10), (0, 0)]), - Polygon([(0, 0), (1, 1), (0, 1)]), - Point(0, 1), - ] - - geobf_s1 = bigframes.geopandas.GeoSeries(data=data1, session=session) - geobf_s_result = bbq.st_difference(geobf_s1, geobf_s1).to_pandas() - - expected = pd.Series( - [GeometryCollection([]), GeometryCollection([]), GeometryCollection([])], - index=[0, 1, 2], - dtype=geopandas.array.GeometryDtype(), - ) - bigframes.testing.utils.assert_series_equal( - geobf_s_result, - expected, - check_index_type=False, - check_exact=False, - rtol=0.1, - ) - - -def test_geo_st_distance_with_geometry_objects(session: bigframes.session.Session): - data1 = [ - # 0.00001 is approximately 1 meter. - Polygon([(0, 0), (0.00001, 0), (0.00001, 0.00001), (0, 0.00001), (0, 0)]), - Polygon( - [ - (0.00002, 0), - (0.00003, 0), - (0.00003, 0.00001), - (0.00002, 0.00001), - (0.00002, 0), - ] - ), - Point(0, 0.00002), - ] - - data2 = [ - Polygon( - [ - (0.00002, 0), - (0.00003, 0), - (0.00003, 0.00001), - (0.00002, 0.00001), - (0.00002, 0), - ] - ), - Point(0, 0.00002), - Polygon([(0, 0), (0.00001, 0), (0.00001, 0.00001), (0, 0.00001), (0, 0)]), - Point( - 1, 1 - ), # No matching row in data1, so this will be NULL after the call to distance. - ] - - geobf_s1 = bigframes.geopandas.GeoSeries(data=data1, session=session) - geobf_s2 = bigframes.geopandas.GeoSeries(data=data2, session=session) - geobf_s_result = bbq.st_distance(geobf_s1, geobf_s2).to_pandas() - - expected = pd.Series( - [ - 1.112, - 2.486, - 1.112, - None, - ], - index=[0, 1, 2, 3], - dtype="Float64", - ) - bigframes.testing.utils.assert_series_equal( - geobf_s_result, - expected, - check_index_type=False, - check_exact=False, - rtol=0.1, - ) - - -def test_geo_st_distance_with_single_geometry_object( - session: bigframes.session.Session, -): - pytest.importorskip( - "shapely", - minversion="2.0.0", - reason="shapely objects must be hashable to include in our expression trees", - ) - - data1 = [ - # 0.00001 is approximately 1 meter. - Polygon([(0, 0), (0.00001, 0), (0.00001, 0.00001), (0, 0.00001), (0, 0)]), - Polygon( - [ - (0.00001, 0), - (0.00002, 0), - (0.00002, 0.00001), - (0.00001, 0.00001), - (0.00001, 0), - ] - ), - Point(0, 0.00002), - ] - - geobf_s1 = bigframes.geopandas.GeoSeries(data=data1, session=session) - geobf_s_result = bbq.st_distance( - geobf_s1, - Point(0, 0), - ).to_pandas() - - expected = pd.Series( - [ - 0, - 1.112, - 2.224, - ], - dtype="Float64", - ) - bigframes.testing.utils.assert_series_equal( - geobf_s_result, - expected, - check_index_type=False, - check_exact=False, - rtol=0.1, - ) - - -def test_geo_st_intersection_with_geometry_objects(session: bigframes.session.Session): - data1 = [ - Polygon([(0, 0), (10, 0), (10, 10), (0, 0)]), - Polygon([(0, 0), (1, 1), (0, 1), (0, 0)]), - Point(0, 1), - ] - - data2 = [ - Polygon([(0, 0), (10, 0), (10, 10), (0, 0)]), - Polygon([(0, 0), (1, 1), (0, 1), (0, 0)]), - LineString([(2, 0), (0, 2)]), - ] - - geobf_s1 = bigframes.geopandas.GeoSeries(data=data1, session=session) - geobf_s2 = bigframes.geopandas.GeoSeries(data=data2, session=session) - geobf_s_result = bbq.st_intersection(geobf_s1, geobf_s2).to_pandas() - - expected = pd.Series( - [ - Polygon([(0, 0), (10, 0), (10, 10), (0, 0)]), - Polygon([(0, 0), (1, 1), (0, 1), (0, 0)]), - GeometryCollection([]), - ], - index=[0, 1, 2], - dtype=geopandas.array.GeometryDtype(), - ) - bigframes.testing.utils.assert_series_equal( - geobf_s_result, - expected, - check_index_type=False, - check_exact=False, - rtol=0.1, - ) - - -def test_geo_st_intersection_with_single_geometry_object( - session: bigframes.session.Session, -): - pytest.importorskip( - "shapely", - minversion="2.0.0", - reason="shapely objects must be hashable to include in our expression trees", - ) - - data1 = [ - Polygon([(0, 0), (10, 0), (10, 10), (0, 10), (0, 0)]), - Polygon([(0, 1), (10, 1), (10, 9), (0, 9), (0, 1)]), - Point(0, 1), - ] - - geobf_s1 = bigframes.geopandas.GeoSeries(data=data1, session=session) - geobf_s_result = bbq.st_intersection( - geobf_s1, - Polygon([(0, 0), (10, 0), (10, 5), (0, 5), (0, 0)]), - ).to_pandas() - - expected = pd.Series( - [ - Polygon([(0, 0), (10, 0), (10, 5), (0, 5), (0, 0)]), - Polygon([(0, 1), (10, 1), (10, 5), (0, 5), (0, 1)]), - Point(0, 1), - ], - index=[0, 1, 2], - dtype=geopandas.array.GeometryDtype(), - ) - bigframes.testing.utils.assert_series_equal( - geobf_s_result, - expected, - check_index_type=False, - check_exact=False, - rtol=0.1, - ) - - -def test_geo_st_intersection_with_similar_geometry_objects( - session: bigframes.session.Session, -): - data1 = [ - Polygon([(0, 0), (10, 0), (10, 10), (0, 0)]), - Polygon([(0, 0), (1, 1), (0, 1)]), - Point(0, 1), - ] - - geobf_s1 = bigframes.geopandas.GeoSeries(data=data1, session=session) - geobf_s_result = bbq.st_intersection(geobf_s1, geobf_s1).to_pandas() - - expected = pd.Series( - [ - Polygon([(0, 0), (10, 0), (10, 10), (0, 0)]), - Polygon([(0, 0), (1, 1), (0, 1)]), - Point(0, 1), - ], - index=[0, 1, 2], - dtype=geopandas.array.GeometryDtype(), - ) - bigframes.testing.utils.assert_series_equal( - geobf_s_result, - expected, - check_index_type=False, - check_exact=False, - rtol=0.1, - ) - - -def test_geo_st_isclosed(session: bigframes.session.Session): - bf_gs = bigframes.geopandas.GeoSeries( - [ - Point(0, 0), # Point - LineString([(0, 0), (1, 1)]), # Open LineString - LineString([(0, 0), (1, 1), (0, 1), (0, 0)]), # Closed LineString - Polygon([(0, 0), (1, 1), (0, 1)]), # Open polygon - GeometryCollection(), # Empty GeometryCollection - bigframes.geopandas.GeoSeries.from_wkt( - ["GEOMETRYCOLLECTION EMPTY"], session=session - ).iloc[0], # Also empty - None, # Should be filtered out by dropna - ], - index=[0, 1, 2, 3, 4, 5, 6], - session=session, - ) - bf_result = bbq.st_isclosed(bf_gs).to_pandas() - - # Expected results based on ST_ISCLOSED documentation: - expected_data = [ - True, # Point: True - False, # Open LineString: False - True, # Closed LineString: True - False, # Polygon: False (only True if it's a full polygon) - False, # Empty GeometryCollection: False (An empty GEOGRAPHY isn't closed) - False, # GEOMETRYCOLLECTION EMPTY: False - None, - ] - expected_series = pd.Series(data=expected_data, dtype="boolean") - - bigframes.testing.utils.assert_series_equal( - bf_result, - expected_series, - # We default to Int64 (nullable) dtype, but pandas defaults to int64 index. - check_index_type=False, - ) - - -def test_st_buffer(session): - geoseries = bigframes.geopandas.GeoSeries( - [Point(0, 0), LineString([(1, 1), (2, 2)])], session=session - ) - result = bbq.st_buffer(geoseries, 1000).to_pandas() - assert result.iloc[0].geom_type == "Polygon" - assert result.iloc[1].geom_type == "Polygon" - - -def test_st_simplify(session): - geoseries = bigframes.geopandas.GeoSeries( - [LineString([(0, 0), (1, 1), (2, 0)])], session=session - ) - result = bbq.st_simplify(geoseries, 100000).to_pandas() - assert len(result.index) == 1 - assert result.isna().sum() == 0 diff --git a/tests/system/small/bigquery/test_json.py b/tests/system/small/bigquery/test_json.py deleted file mode 100644 index 2d97172e7b5..00000000000 --- a/tests/system/small/bigquery/test_json.py +++ /dev/null @@ -1,487 +0,0 @@ -# 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. -# 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 geopandas as gpd # type: ignore -import pandas as pd -import pyarrow as pa -import pytest - -import bigframes.bigquery as bbq -import bigframes.dtypes as dtypes -import bigframes.pandas as bpd - - -@pytest.mark.parametrize( - ("json_path", "expected_json"), - [ - pytest.param("$.a", ['{"a": 10}'], id="simple"), - pytest.param("$.a.b.c", ['{"a": {"b": {"c": 10, "d": []}}}'], id="nested"), - ], -) -def test_json_set_at_json_path(json_path, expected_json): - original_json = ['{"a": {"b": {"c": "tester", "d": []}}}'] - s = bpd.Series(original_json, dtype=dtypes.JSON_DTYPE) - - actual = bbq.json_set(s, json_path_value_pairs=[(json_path, 10)]) - expected = bpd.Series(expected_json, dtype=dtypes.JSON_DTYPE) - - pd.testing.assert_series_equal(actual.to_pandas(), expected.to_pandas()) - - -@pytest.mark.parametrize( - ("json_value", "expected_json"), - [ - pytest.param(10, ['{"a": {"b": 10}}', '{"a": {"b": 10}}'], id="int"), - pytest.param(0.333, ['{"a": {"b": 0.333}}', '{"a": {"b": 0.333}}'], id="float"), - pytest.param( - "eng", ['{"a": {"b": "eng"}}', '{"a": {"b": "eng"}}'], id="string" - ), - pytest.param([1, 2], ['{"a": {"b": 1}}', '{"a": {"b": 2}}'], id="series"), - ], -) -def test_json_set_at_json_value_type(json_value, expected_json): - original_json = ['{"a": {"b": "dev"}}', '{"a": {"b": [1, 2]}}'] - s = bpd.Series(original_json, dtype=dtypes.JSON_DTYPE) - actual = bbq.json_set(s, json_path_value_pairs=[("$.a.b", json_value)]) - expected = bpd.Series(expected_json, dtype=dtypes.JSON_DTYPE) - - pd.testing.assert_series_equal(actual.to_pandas(), expected.to_pandas()) - - -def test_json_set_w_more_pairs(): - original_json = ['{"a": 2}', '{"b": 5}', '{"c": 1}'] - s = bpd.Series(original_json, dtype=dtypes.JSON_DTYPE) - actual = bbq.json_set( - s, json_path_value_pairs=[("$.a", 1), ("$.b", 2), ("$.a", [3, 4, 5])] - ) - - expected_json = ['{"a": 3,"b":2}', '{"a":4,"b": 2}', '{"a": 5,"b":2,"c":1}'] - expected = bpd.Series(expected_json, dtype=dtypes.JSON_DTYPE) - - pd.testing.assert_series_equal(actual.to_pandas(), expected.to_pandas()) - - -def test_json_set_w_invalid_value_type(): - s = bpd.Series(['{"a": 10}'], dtype=dtypes.JSON_DTYPE) - with pytest.raises(TypeError): - bbq.json_set( - s, - json_path_value_pairs=[ - ( - "$.a", - bpd.read_pandas( - gpd.GeoSeries.from_wkt(["POINT (1 2)", "POINT (2 1)"]) - ), - ) - ], - ) - - -def test_json_set_w_invalid_series_type(): - s = bpd.Series([1, 2]) - with pytest.raises(TypeError): - bbq.json_set(s, json_path_value_pairs=[("$.a", 1)]) - - -def test_json_extract_from_json(): - s = bpd.Series( - ['{"a": {"b": [1, 2]}}', '{"a": {"c": 1}}', '{"a": {"b": 0}}'], - dtype=dtypes.JSON_DTYPE, - ) - with pytest.warns(UserWarning, match="The `json_extract` is deprecated"): - actual = bbq.json_extract(s, "$.a.b") - expected = bpd.Series(["[1, 2]", None, "0"], dtype=dtypes.JSON_DTYPE) - - pd.testing.assert_series_equal(actual.to_pandas(), expected.to_pandas()) - - -def test_json_extract_from_string(): - s = bpd.Series( - ['{"a": {"b": [1, 2]}}', '{"a": {"c": 1}}', '{"a": {"b": 0}}'], - dtype=pd.StringDtype(storage="pyarrow"), - ) - actual = bbq.json_extract(s, "$.a.b") - expected = bpd.Series(["[1,2]", None, "0"], dtype=pd.StringDtype(storage="pyarrow")) - - pd.testing.assert_series_equal(actual.to_pandas(), expected.to_pandas()) - - -def test_json_extract_w_invalid_series_type(): - s = bpd.Series([1, 2]) - with pytest.raises(TypeError): - bbq.json_extract(s, "$.a") - - -def test_json_extract_array_from_json(): - s = bpd.Series( - ['{"a": ["ab", "2", "3 xy"]}', '{"a": []}', '{"a": ["4", "5"]}', "{}"], - dtype=dtypes.JSON_DTYPE, - ) - with pytest.warns(UserWarning, match="The `json_extract_array` is deprecated"): - actual = bbq.json_extract_array(s, "$.a") - - # This code provides a workaround for issue https://github.com/apache/arrow/issues/45262, - # which currently prevents constructing a series using the pa.list_(db_types.JSONArrrowType()) - sql = """ - SELECT 0 AS id, [JSON '"ab"', JSON '"2"', JSON '"3 xy"'] AS data, - UNION ALL - SELECT 1, [], - UNION ALL - SELECT 2, [JSON '"4"', JSON '"5"'], - UNION ALL - SELECT 3, null, - """ - df = bpd.read_gbq(sql).set_index("id").sort_index() - expected = df["data"] - expected.index.name = None - expected.name = None - - pd.testing.assert_series_equal(actual.to_pandas(), expected.to_pandas()) - - -def test_json_extract_array_from_json_strings(): - s = bpd.Series( - ['{"a": ["ab", "2", "3 xy"]}', '{"a": []}', '{"a": ["4","5"]}', "{}"], - dtype=pd.StringDtype(storage="pyarrow"), - ) - actual = bbq.json_extract_array(s, "$.a") - expected = bpd.Series( - [['"ab"', '"2"', '"3 xy"'], [], ['"4"', '"5"'], None], - dtype=pd.ArrowDtype(pa.list_(pa.string())), - ) - - pd.testing.assert_series_equal(actual.to_pandas(), expected.to_pandas()) - - -def test_json_extract_array_from_json_array_strings(): - s = bpd.Series( - ["[1, 2, 3]", "[]", "[4,5]"], - dtype=pd.StringDtype(storage="pyarrow"), - ) - actual = bbq.json_extract_array(s) - expected = bpd.Series( - [["1", "2", "3"], [], ["4", "5"]], - dtype=pd.ArrowDtype(pa.list_(pa.string())), - ) - - pd.testing.assert_series_equal(actual.to_pandas(), expected.to_pandas()) - - -def test_json_extract_array_w_invalid_series_type(): - s = bpd.Series([1, 2]) - with pytest.raises(TypeError): - bbq.json_extract_array(s) - - -def test_json_extract_string_array_from_json_strings(): - s = bpd.Series(['{"a": ["ab", "2", "3 xy"]}', '{"a": []}', '{"a": ["4","5"]}']) - with pytest.warns( - UserWarning, match="The `json_extract_string_array` is deprecated" - ): - actual = bbq.json_extract_string_array(s, "$.a") - expected = bpd.Series([["ab", "2", "3 xy"], [], ["4", "5"]]) - - pd.testing.assert_series_equal(actual.to_pandas(), expected.to_pandas()) - - -def test_json_extract_string_array_from_array_strings(): - s = bpd.Series(["[1, 2, 3]", "[]", "[4,5]"]) - actual = bbq.json_extract_string_array(s) - expected = bpd.Series([["1", "2", "3"], [], ["4", "5"]]) - - pd.testing.assert_series_equal(actual.to_pandas(), expected.to_pandas()) - - -def test_json_extract_string_array_as_float_array_from_array_strings(): - s = bpd.Series(["[1, 2.5, 3]", "[]", "[4,5]"]) - actual = bbq.json_extract_string_array(s, value_dtype=dtypes.FLOAT_DTYPE) - expected = bpd.Series([[1, 2.5, 3], [], [4, 5]]) - - pd.testing.assert_series_equal(actual.to_pandas(), expected.to_pandas()) - - -def test_json_extract_string_array_w_invalid_series_type(): - s = bpd.Series([1, 2]) - with pytest.raises(TypeError): - bbq.json_extract_string_array(s) - - -def test_json_value_array_from_json_strings(): - s = bpd.Series(['{"a": ["ab", "2", "3 xy"]}', '{"a": []}', '{"a": ["4","5"]}']) - actual = bbq.json_value_array(s, "$.a") - expected_data = [["ab", "2", "3 xy"], [], ["4", "5"]] - # Expected dtype after JSON_VALUE_ARRAY is ARRAY - expected = bpd.Series(expected_data, dtype=pd.ArrowDtype(pa.list_(pa.string()))) - pd.testing.assert_series_equal( - actual.to_pandas(), - expected.to_pandas(), - ) - - -def test_json_value_array_from_array_strings(): - s = bpd.Series(["[1, 2, 3]", "[]", "[4,5]"]) - actual = bbq.json_value_array(s) - expected_data = [["1", "2", "3"], [], ["4", "5"]] - expected = bpd.Series(expected_data, dtype=pd.ArrowDtype(pa.list_(pa.string()))) - pd.testing.assert_series_equal( - actual.to_pandas(), - expected.to_pandas(), - ) - - -def test_json_value_array_w_invalid_series_type(): - s = bpd.Series([1, 2], dtype=dtypes.INT_DTYPE) # Not a JSON-like string - with pytest.raises(TypeError): - bbq.json_value_array(s) - - -def test_json_value_array_from_json_native(): - json_data = [ - '{"key": ["hello", "world"]}', - '{"key": ["123", "45.6"]}', - '{"key": []}', - "{}", # case with missing key - ] - s = bpd.Series(json_data, dtype=dtypes.JSON_DTYPE) - actual = bbq.json_value_array(s, json_path="$.key") - - expected_data_pandas = [["hello", "world"], ["123", "45.6"], [], None] - expected = bpd.Series( - expected_data_pandas, dtype=pd.ArrowDtype(pa.list_(pa.string())) - ).fillna(pd.NA) - result_pd = actual.to_pandas().fillna(pd.NA) - pd.testing.assert_series_equal(result_pd, expected.to_pandas()) - - -def test_json_query_from_json(): - s = bpd.Series( - ['{"a": {"b": [1, 2]}}', '{"a": {"c": 1}}', '{"a": {"b": 0}}'], - dtype=dtypes.JSON_DTYPE, - ) - actual = bbq.json_query(s, "$.a.b") - expected = bpd.Series(["[1, 2]", None, "0"], dtype=dtypes.JSON_DTYPE) - - pd.testing.assert_series_equal(actual.to_pandas(), expected.to_pandas()) - - -def test_json_query_from_string(): - s = bpd.Series( - ['{"a": {"b": [1, 2]}}', '{"a": {"c": 1}}', '{"a": {"b": 0}}'], - dtype=pd.StringDtype(storage="pyarrow"), - ) - actual = bbq.json_query(s, "$.a.b") - expected = bpd.Series(["[1,2]", None, "0"], dtype=pd.StringDtype(storage="pyarrow")) - - pd.testing.assert_series_equal(actual.to_pandas(), expected.to_pandas()) - - -def test_json_query_w_invalid_series_type(): - s = bpd.Series([1, 2]) - with pytest.raises(TypeError): - bbq.json_query(s, "$.a") - - -def test_json_query_array_from_json(): - s = bpd.Series( - ['{"a": ["ab", "2", "3 xy"]}', '{"a": []}', '{"a": ["4", "5"]}', "{}"], - dtype=dtypes.JSON_DTYPE, - ) - actual = bbq.json_query_array(s, "$.a") - - # This code provides a workaround for issue https://github.com/apache/arrow/issues/45262, - # which currently prevents constructing a series using the pa.list_(db_types.JSONArrrowType()) - sql = """ - SELECT 0 AS id, [JSON '"ab"', JSON '"2"', JSON '"3 xy"'] AS data, - UNION ALL - SELECT 1, [], - UNION ALL - SELECT 2, [JSON '"4"', JSON '"5"'], - UNION ALL - SELECT 3, null, - """ - df = bpd.read_gbq(sql).set_index("id").sort_index() - expected = df["data"] - expected.index.name = None - expected.name = None - - pd.testing.assert_series_equal(actual.to_pandas(), expected.to_pandas()) - - -def test_json_query_array_from_json_strings(): - s = bpd.Series( - ['{"a": ["ab", "2", "3 xy"]}', '{"a": []}', '{"a": ["4","5"]}', "{}"], - dtype=pd.StringDtype(storage="pyarrow"), - ) - actual = bbq.json_query_array(s, "$.a") - expected = bpd.Series( - [['"ab"', '"2"', '"3 xy"'], [], ['"4"', '"5"'], None], - dtype=pd.ArrowDtype(pa.list_(pa.string())), - ) - - pd.testing.assert_series_equal(actual.to_pandas(), expected.to_pandas()) - - -def test_json_query_array_from_json_array_strings(): - s = bpd.Series( - ["[1, 2, 3]", "[]", "[4,5]"], - dtype=pd.StringDtype(storage="pyarrow"), - ) - actual = bbq.json_query_array(s) - expected = bpd.Series( - [["1", "2", "3"], [], ["4", "5"]], - dtype=pd.ArrowDtype(pa.list_(pa.string())), - ) - - pd.testing.assert_series_equal(actual.to_pandas(), expected.to_pandas()) - - -def test_json_query_array_w_invalid_series_type(): - s = bpd.Series([1, 2]) - with pytest.raises(TypeError): - bbq.json_query_array(s) - - -def test_json_value_from_json(): - s = bpd.Series( - ['{"a": {"b": [1, 2]}}', '{"a": {"c": 1}}', '{"a": {"b": 0}}'], - dtype=dtypes.JSON_DTYPE, - ) - actual = bbq.json_value(s, "$.a.b") - expected = bpd.Series([None, None, "0"], dtype=dtypes.STRING_DTYPE) - - pd.testing.assert_series_equal(actual.to_pandas(), expected.to_pandas()) - - -def test_json_value_from_string(): - s = bpd.Series( - ['{"a": {"b": [1, 2]}}', '{"a": {"c": 1}}', '{"a": {"b": 0}}'], - dtype=pd.StringDtype(storage="pyarrow"), - ) - actual = bbq.json_value(s, "$.a.b") - expected = bpd.Series([None, None, "0"], dtype=dtypes.STRING_DTYPE) - - pd.testing.assert_series_equal(actual.to_pandas(), expected.to_pandas()) - - -def test_json_value_w_invalid_series_type(): - s = bpd.Series([1, 2]) - with pytest.raises(TypeError): - bbq.json_value(s, "$.a") - - -def test_parse_json_w_invalid_series_type(): - s = bpd.Series([1, 2]) - with pytest.raises(TypeError): - bbq.parse_json(s) - - -def test_to_json_from_int(): - s = bpd.Series([1, 2, None, 3]) - actual = bbq.to_json(s) - expected = bpd.Series(["1.0", "2.0", None, "3.0"], dtype=dtypes.JSON_DTYPE) - pd.testing.assert_series_equal(actual.to_pandas(), expected.to_pandas()) - - -def test_to_json_from_struct(): - s = bpd.Series( - [ - {"version": 1, "project": "pandas"}, - {"version": 2, "project": "numpy"}, - ] - ) - assert dtypes.is_struct_like(s.dtype) - - actual = bbq.to_json(s) - expected = bpd.Series( - ['{"version":1,"project":"pandas"}', '{"version":2,"project":"numpy"}'], - dtype=dtypes.JSON_DTYPE, - ) - - pd.testing.assert_series_equal(actual.to_pandas(), expected.to_pandas()) - - -def test_to_json_string_from_int(): - s = bpd.Series([1, 2, None, 3]) - actual = bbq.to_json_string(s) - expected = bpd.Series(["1", "2", "null", "3"], dtype=dtypes.STRING_DTYPE) - pd.testing.assert_series_equal(actual.to_pandas(), expected.to_pandas()) - - -def test_to_json_string_from_struct(): - s = bpd.Series( - [ - {"version": 1, "project": "pandas"}, - {"version": 2, "project": "numpy"}, - ] - ) - assert dtypes.is_struct_like(s.dtype) - - actual = bbq.to_json_string(s) - expected = bpd.Series( - ['{"version":1,"project":"pandas"}', '{"version":2,"project":"numpy"}'], - dtype=dtypes.STRING_DTYPE, - ) - - pd.testing.assert_series_equal(actual.to_pandas(), expected.to_pandas()) - - -def test_json_keys(): - json_data = [ - '{"name": "Alice", "age": 30}', - '{"city": "New York", "country": "USA", "active": true}', - "{}", - '{"items": [1, 2, 3]}', - ] - s = bpd.Series(json_data, dtype=dtypes.JSON_DTYPE) - actual = bbq.json_keys(s) - - expected_data_pandas = [ - ["age", "name"], - [ - "active", - "city", - "country", - ], - [], - ["items"], - ] - expected = bpd.Series( - expected_data_pandas, dtype=pd.ArrowDtype(pa.list_(pa.string())) - ) - pd.testing.assert_series_equal(actual.to_pandas(), expected.to_pandas()) - - -def test_json_keys_with_max_depth(): - json_data = [ - '{"user": {"name": "Bob", "details": {"id": 123, "status": "approved"}}}', - '{"user": {"name": "Charlie"}}', - ] - s = bpd.Series(json_data, dtype=dtypes.JSON_DTYPE) - actual = bbq.json_keys(s, max_depth=2) - - expected_data_pandas = [ - ["user", "user.details", "user.name"], - ["user", "user.name"], - ] - expected = bpd.Series( - expected_data_pandas, dtype=pd.ArrowDtype(pa.list_(pa.string())) - ) - pd.testing.assert_series_equal(actual.to_pandas(), expected.to_pandas()) - - -def test_json_keys_from_string_error(): - s = bpd.Series(['{"a": 1, "b": 2}', '{"c": 3}']) - with pytest.raises(TypeError): - bbq.json_keys(s) diff --git a/tests/system/small/bigquery/test_mathematical.py b/tests/system/small/bigquery/test_mathematical.py deleted file mode 100644 index 66aef96e57d..00000000000 --- a/tests/system/small/bigquery/test_mathematical.py +++ /dev/null @@ -1,37 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import 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 diff --git a/tests/system/small/bigquery/test_sql.py b/tests/system/small/bigquery/test_sql.py deleted file mode 100644 index c0f7eed938e..00000000000 --- a/tests/system/small/bigquery/test_sql.py +++ /dev/null @@ -1,177 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import pytest - -import bigframes.bigquery as bbq -import bigframes.dtypes as dtypes -import bigframes.pandas as bpd -import bigframes.testing.utils - - -def test_sql_scalar_for_all_scalar_types(scalars_df_null_index): - series = bbq.sql_scalar( - """ - CAST({0} AS INT64) - + BYTE_LENGTH({1}) - + UNIX_DATE({2}) - + EXTRACT(YEAR FROM {3}) - + ST_NUMPOINTS({4}) - + LEAST( - {5}, - CAST({6} AS INT64), - CAST({7} AS INT64) - ) + CHAR_LENGTH({8}) - + EXTRACT(SECOND FROM {9}) - + UNIX_SECONDS({10}) - """, - columns=[ - # Try to include all scalar types in a single test. - scalars_df_null_index["bool_col"], - scalars_df_null_index["bytes_col"], - scalars_df_null_index["date_col"], - scalars_df_null_index["datetime_col"], - scalars_df_null_index["geography_col"], - scalars_df_null_index["int64_col"], - scalars_df_null_index["numeric_col"], - scalars_df_null_index["float64_col"], - scalars_df_null_index["string_col"], - scalars_df_null_index["time_col"], - scalars_df_null_index["timestamp_col"], - ], - ) - result = series.to_pandas() - assert len(result) == len(scalars_df_null_index) - - -def test_sql_scalar_for_bool_series(scalars_df_index): - series: bpd.Series = scalars_df_index["bool_col"] - result = bbq.sql_scalar("CAST({0} AS INT64)", [series]) - expected = series.astype(dtypes.INT_DTYPE) - expected.name = result.name - bigframes.testing.utils.assert_series_equal( - result.to_pandas(), expected.to_pandas() - ) - - -@pytest.mark.parametrize( - ("column_name"), - [ - pytest.param("bool_col"), - pytest.param("bytes_col"), - pytest.param("date_col"), - pytest.param("datetime_col"), - pytest.param("geography_col"), - pytest.param("int64_col"), - pytest.param("numeric_col"), - pytest.param("float64_col"), - pytest.param("string_col"), - pytest.param("time_col"), - pytest.param("timestamp_col"), - ], -) -def test_sql_scalar_outputs_all_scalar_types(scalars_df_index, column_name): - series: bpd.Series = scalars_df_index[column_name] - result = bbq.sql_scalar("{0}", [series]) - expected = series - expected.name = result.name - bigframes.testing.utils.assert_series_equal( - result.to_pandas(), expected.to_pandas() - ) - - -def test_sql_scalar_for_array_series(repeated_df): - result = bbq.sql_scalar( - """ - ARRAY_LENGTH({0}) + ARRAY_LENGTH({1}) + ARRAY_LENGTH({2}) - + ARRAY_LENGTH({3}) + ARRAY_LENGTH({4}) + ARRAY_LENGTH({5}) - + ARRAY_LENGTH({6}) - """, - [ - repeated_df["int_list_col"], - repeated_df["bool_list_col"], - repeated_df["float_list_col"], - repeated_df["date_list_col"], - repeated_df["date_time_list_col"], - repeated_df["numeric_list_col"], - repeated_df["string_list_col"], - ], - ) - - expected = ( - repeated_df["int_list_col"].list.len() - + repeated_df["bool_list_col"].list.len() - + repeated_df["float_list_col"].list.len() - + repeated_df["date_list_col"].list.len() - + repeated_df["date_time_list_col"].list.len() - + repeated_df["numeric_list_col"].list.len() - + repeated_df["string_list_col"].list.len() - ) - bigframes.testing.utils.assert_series_equal( - result.to_pandas(), expected.to_pandas() - ) - - -def test_sql_scalar_outputs_array_series(repeated_df): - result = bbq.sql_scalar("{0}", [repeated_df["int_list_col"]]) - expected = repeated_df["int_list_col"] - expected.name = result.name - bigframes.testing.utils.assert_series_equal( - result.to_pandas(), expected.to_pandas() - ) - - -def test_sql_scalar_for_struct_series(nested_structs_df): - result = bbq.sql_scalar( - "CHAR_LENGTH({0}.name) + {0}.age", - [nested_structs_df["person"]], - ) - expected = nested_structs_df["person"].struct.field( - "name" - ).str.len() + nested_structs_df["person"].struct.field("age") - bigframes.testing.utils.assert_series_equal( - result.to_pandas(), expected.to_pandas() - ) - - -def test_sql_scalar_outputs_struct_series(nested_structs_df): - result = bbq.sql_scalar("{0}", [nested_structs_df["person"]]) - expected = nested_structs_df["person"] - expected.name = result.name - bigframes.testing.utils.assert_series_equal( - result.to_pandas(), expected.to_pandas() - ) - - -def test_sql_scalar_for_json_series(json_df): - result = bbq.sql_scalar( - """JSON_VALUE({0}, '$.int_value')""", - [ - json_df["json_col"], - ], - ) - expected = bbq.json_value(json_df["json_col"], "$.int_value") - expected.name = result.name - bigframes.testing.utils.assert_series_equal( - result.to_pandas(), expected.to_pandas() - ) - - -def test_sql_scalar_outputs_json_series(json_df): - result = bbq.sql_scalar("{0}", [json_df["json_col"]]) - expected = json_df["json_col"] - expected.name = result.name - bigframes.testing.utils.assert_series_equal( - result.to_pandas(), expected.to_pandas() - ) diff --git a/tests/system/small/bigquery/test_struct.py b/tests/system/small/bigquery/test_struct.py deleted file mode 100644 index 85404969605..00000000000 --- a/tests/system/small/bigquery/test_struct.py +++ /dev/null @@ -1,62 +0,0 @@ -# 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. -# 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 - -import bigframes.bigquery as bbq -import bigframes.series as series -import bigframes.testing.utils - - -@pytest.mark.parametrize( - "columns_arg", - [ - [ - {"version": 1, "project": "pandas"}, - {"version": 2, "project": "pandas"}, - {"version": 1, "project": "numpy"}, - ], - [ - {"version": 1, "project": "pandas"}, - {"version": None, "project": "pandas"}, - {"version": 1, "project": "numpy"}, - ], - [ - {"array": [6, 4, 6], "project": "pandas"}, - {"array": [6, 4, 7, 6], "project": "pandas"}, - {"array": [7, 2, 3], "project": "numpy"}, - ], - [ - {"array": [6, 4, 6], "project": "pandas"}, - {"array": [6, 4, 7, 6], "project": "pandas"}, - {"array": [7, 2, 3], "project": "numpy"}, - ], - [ - {"struct": [{"x": 2, "y": 4}], "project": "pandas"}, - {"struct": [{"x": 9, "y": 3}], "project": "pandas"}, - {"struct": [{"x": 1, "y": 2}], "project": "numpy"}, - ], - ], -) -def test_struct_from_dataframe(columns_arg): - srs = series.Series( - columns_arg, - ) - bigframes.testing.utils.assert_series_equal( - srs.to_pandas(), - bbq.struct(srs.struct.explode()).to_pandas(), - check_index_type=False, - check_dtype=False, - check_names=False, # None vs nan version dependent - ) diff --git a/tests/system/small/bigquery/test_vector_search.py b/tests/system/small/bigquery/test_vector_search.py deleted file mode 100644 index b8ad4c0df22..00000000000 --- a/tests/system/small/bigquery/test_vector_search.py +++ /dev/null @@ -1,221 +0,0 @@ -# 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. -# 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 random -from typing import Any, Dict, Iterable, cast - -import google.cloud.bigquery -import numpy as np -import pandas as pd -import pyarrow -import pytest - -import bigframes.bigquery as bbq -import bigframes.pandas as bpd -from bigframes.testing.utils import assert_frame_equal - -# Need at least 5,000 rows to create a vector index. -VECTOR_DF = pd.DataFrame( - { - "rowid": np.arange(9_999), - # 3D values, clustered around the three unit vector axes. - "my_embedding": pd.Series( - [ - [ - 1 + (random.random() - 0.5) if (row % 3) == 0 else 0, - 1 + (random.random() - 0.5) if (row % 3) == 1 else 0, - 1 + (random.random() - 0.5) if (row % 3) == 2 else 0, - ] - for row in range(9_999) - ], - dtype=pd.ArrowDtype(pyarrow.list_(pyarrow.float64())), - ), - # Three groups of animal, vegetable, and mineral, corresponding to - # the embeddings above. - "mystery_word": [ - "aarvark", - "broccoli", - "calcium", - "dog", - "eggplant", - "ferrite", - "gopher", - "huckleberry", - "ice", - ] - * 1_111, - }, -) - - -@pytest.fixture -def vector_table_id( - bigquery_client: google.cloud.bigquery.Client, - # Use non-US location to ensure location autodetection works. - table_id_not_created: str, -): - table = google.cloud.bigquery.Table( - table_id_not_created, - [ - {"name": "rowid", "type": "INT64"}, - {"name": "my_embedding", "type": "FLOAT64", "mode": "REPEATED"}, - {"name": "mystery_word", "type": "STRING"}, - ], - ) - bigquery_client.create_table(table) - bigquery_client.load_table_from_json( - cast(Iterable[Dict[str, Any]], VECTOR_DF.to_dict(orient="records")), - table_id_not_created, - ).result() - yield table_id_not_created - bigquery_client.delete_table(table_id_not_created, not_found_ok=True) - - -def test_create_vector_index_ivf( - session, vector_table_id: str, bigquery_client: google.cloud.bigquery.Client -): - bbq.create_vector_index( - vector_table_id, - "my_embedding", - distance_type="cosine", - stored_column_names=["mystery_word"], - index_type="ivf", - ivf_options={"num_lists": 3}, - session=session, - ) - - # Check that the index was created successfully. - project_id, dataset_id, table_name = vector_table_id.split(".") - indexes = bigquery_client.query_and_wait( - f""" - SELECT index_catalog, index_schema, table_name, index_name, index_column_name - FROM `{project_id}`.`{dataset_id}`.INFORMATION_SCHEMA.VECTOR_INDEX_COLUMNS - WHERE table_name = '{table_name}'; - """ - ).to_dataframe() - - # There should only be one vector index. - assert len(indexes.index) == 1 - assert indexes["index_catalog"].iloc[0] == project_id - assert indexes["index_schema"].iloc[0] == dataset_id - assert indexes["table_name"].iloc[0] == table_name - assert indexes["index_column_name"].iloc[0] == "my_embedding" - - # If no name is specified, use the table name as the index name - assert indexes["index_name"].iloc[0] == table_name - - -def test_vector_search_basic_params_with_df(): - search_query = bpd.DataFrame( - { - "query_id": ["dog", "cat"], - "embedding": [[1.0, 2.0], [3.0, 5.2]], - } - ) - vector_search_result = ( - bbq.vector_search( - base_table="bigframes-dev.bigframes_tests_sys.base_table", - column_to_search="my_embedding", - query=search_query, - top_k=2, - ) - .sort_values("distance") - .sort_index() - .to_pandas() - ) # type:ignore - expected = pd.DataFrame( - { - "query_id": ["cat", "dog", "dog", "cat"], - "embedding": [ - np.array([3.0, 5.2]), - np.array([1.0, 2.0]), - np.array([1.0, 2.0]), - np.array([3.0, 5.2]), - ], - "id": [5, 1, 4, 2], - "my_embedding": [ - np.array([5.0, 5.4]), - np.array([1.0, 2.0]), - np.array([1.0, 3.2]), - np.array([2.0, 4.0]), - ], - "distance": [2.009975, 0.0, 1.2, 1.56205], - }, - index=pd.Index([1, 0, 0, 1], dtype="Int64"), - ) - assert_frame_equal( - expected.sort_values("id"), - vector_search_result.sort_values("id"), - check_dtype=False, - rtol=0.1, - ) - - -def test_vector_search_different_params_with_query(session): - base_df = bpd.DataFrame( - { - "id": [1, 2, 3, 4], - "my_embedding": [ - np.array([0.0, 1.0]), - np.array([1.0, 0.0]), - np.array([0.0, -1.0]), - np.array([-1.0, 0.0]), - ], - }, - session=session, - ) - base_table = base_df.to_gbq() - try: - search_query = bpd.Series([[0.75, 0.25], [-0.25, -0.75]], session=session) - vector_search_result = ( - bbq.vector_search( - base_table=base_table, - column_to_search="my_embedding", - query=search_query, - distance_type="cosine", - top_k=2, - ) - .sort_values("distance") - .sort_index() - .to_pandas() - ) # type:ignore - expected = pd.DataFrame( - { - "0": [ - [0.75, 0.25], - [0.75, 0.25], - [-0.25, -0.75], - [-0.25, -0.75], - ], - "id": [2, 1, 3, 4], - "my_embedding": [ - [1.0, 0.0], - [0.0, 1.0], - [0.0, -1.0], - [-1.0, 0.0], - ], - "distance": [ - 0.051317, - 0.683772, - 0.051317, - 0.683772, - ], - }, - index=pd.Index([0, 0, 1, 1], dtype="Int64"), - ) - pd.testing.assert_frame_equal( - vector_search_result, expected, check_dtype=False, rtol=0.1 - ) - finally: - session.bqclient.delete_table(base_table, not_found_ok=True) diff --git a/tests/system/small/blob/test_properties.py b/tests/system/small/blob/test_properties.py deleted file mode 100644 index c3597b37116..00000000000 --- a/tests/system/small/blob/test_properties.py +++ /dev/null @@ -1,132 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import pandas as pd -import pytest - -import bigframes.bigquery as bbq -import bigframes.dtypes as dtypes -import bigframes.pandas as bpd - -pytest.skip("Skipping blob tests due to b/481790217", allow_module_level=True) - - -def test_blob_uri(images_uris: list[str], images_mm_df: bpd.DataFrame): - actual = images_mm_df["blob_col"].struct.field("uri").to_pandas() - expected = pd.Series(images_uris, name="uri") - - pd.testing.assert_series_equal( - actual, expected, check_dtype=False, check_index_type=False - ) - - -def test_blob_authorizer(images_mm_df: bpd.DataFrame, bq_connection: str): - actual = images_mm_df["blob_col"].struct.field("authorizer").to_pandas() - expected = pd.Series( - [bq_connection.casefold(), bq_connection.casefold()], name="authorizer" - ) - - pd.testing.assert_series_equal( - actual, expected, check_dtype=False, check_index_type=False - ) - - -def test_blob_version(images_mm_df: bpd.DataFrame): - actual = bbq.json_value( - images_mm_df["blob_col"].struct.field("details"), "$.version" - ).to_pandas() - expected = pd.Series(["1753907851152593", "1753907851111538"], name="version") - - pd.testing.assert_series_equal( - actual, expected, check_dtype=False, check_index_type=False - ) - - -def test_blob_metadata(images_mm_df: bpd.DataFrame): - actual = images_mm_df["blob_col"].struct.field("details").to_pandas() - expected = pd.Series( - [ - ( - '{"content_type":"image/jpeg",' - '"md5_hash":"e130ad042261a1883cd2cc06831cf748",' - '"size":338390,' - '"updated":1753907851000000}' - ), - ( - '{"content_type":"image/jpeg",' - '"md5_hash":"e2ae3191ff2b809fd0935f01a537c650",' - '"size":43333,' - '"updated":1753907851000000}' - ), - ], - name="metadata", - dtype=dtypes.JSON_DTYPE, - ) - expected.index = expected.index.astype(dtypes.INT_DTYPE) - pd.testing.assert_series_equal(actual, expected) - - -def test_blob_content_type(images_mm_df: bpd.DataFrame): - actual = bbq.json_value( - images_mm_df["blob_col"].struct.field("details"), "$.content_type" - ).to_pandas() - expected = pd.Series(["image/jpeg", "image/jpeg"], name="content_type") - - pd.testing.assert_series_equal( - actual, expected, check_dtype=False, check_index_type=False - ) - - -def test_blob_md5_hash(images_mm_df: bpd.DataFrame): - actual = bbq.json_value( - images_mm_df["blob_col"].struct.field("details"), "$.md5_hash" - ).to_pandas() - expected = pd.Series( - ["e130ad042261a1883cd2cc06831cf748", "e2ae3191ff2b809fd0935f01a537c650"], - name="md5_hash", - ) - - pd.testing.assert_series_equal( - actual, expected, check_dtype=False, check_index_type=False - ) - - -def test_blob_size(images_mm_df: bpd.DataFrame): - actual = ( - bbq.json_value(images_mm_df["blob_col"].struct.field("details"), "$.size") - .astype("Int64") - .to_pandas() - ) - expected = pd.Series([338390, 43333], name="size") - - pd.testing.assert_series_equal( - actual, expected, check_dtype=False, check_index_type=False - ) - - -def test_blob_updated(images_mm_df: bpd.DataFrame): - actual = bbq.json_value( - images_mm_df["blob_col"].struct.field("details"), "$.updated" - ).to_pandas() - expected = pd.Series( - [ - pd.Timestamp("2025-07-30 20:37:31", tz="UTC"), - pd.Timestamp("2025-07-30 20:37:31", tz="UTC"), - ], - name="updated", - ) - - pd.testing.assert_series_equal( - actual, expected, check_dtype=False, check_index_type=False - ) diff --git a/tests/system/small/core/__init__.py b/tests/system/small/core/__init__.py deleted file mode 100644 index 6d5e14bcf4a..00000000000 --- a/tests/system/small/core/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -# 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. -# 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/core/indexes/__init__.py b/tests/system/small/core/indexes/__init__.py deleted file mode 100644 index 0a2669d7a25..00000000000 --- a/tests/system/small/core/indexes/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. diff --git a/tests/system/small/core/indexes/test_base.py b/tests/system/small/core/indexes/test_base.py deleted file mode 100644 index 3225f643299..00000000000 --- a/tests/system/small/core/indexes/test_base.py +++ /dev/null @@ -1,35 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import pandas as pd -import pandas.testing -import pytest -from packaging import version - - -@pytest.mark.parametrize("level", [None, 0, 1, "level0", "level1"]) -def test_unique(session, level): - if version.Version(pd.__version__) < version.Version("2.0.0"): - pytest.skip("StringDtype for multi-index not supported until Pandas 2.0") - arrays = [ - pd.Series(["A", "A", "B", "B", "A"], dtype=pd.StringDtype(storage="pyarrow")), - pd.Series([1, 2, 1, 2, 1], dtype=pd.Int64Dtype()), - ] - pd_idx = pd.MultiIndex.from_arrays(arrays, names=["level0", "level1"]) - bf_idx = session.read_pandas(pd_idx) - - actual_result = bf_idx.unique(level).to_pandas() - - expected_result = pd_idx.unique(level) - pandas.testing.assert_index_equal(actual_result, expected_result) diff --git a/tests/system/small/core/indexes/test_datetimes.py b/tests/system/small/core/indexes/test_datetimes.py deleted file mode 100644 index 40ce310b313..00000000000 --- a/tests/system/small/core/indexes/test_datetimes.py +++ /dev/null @@ -1,46 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - - -import pandas -import pandas.testing -import pytest - - -@pytest.fixture(scope="module") -def datetime_indexes(session): - pd_index = pandas.date_range("2024-12-25", periods=10, freq="d") - bf_index = session.read_pandas(pd_index) - - return bf_index, pd_index - - -@pytest.mark.parametrize( - "access", - [ - pytest.param(lambda x: x.year, id="year"), - pytest.param(lambda x: x.month, id="month"), - pytest.param(lambda x: x.day, id="day"), - pytest.param(lambda x: x.dayofweek, id="dayofweek"), - pytest.param(lambda x: x.day_of_week, id="day_of_week"), - pytest.param(lambda x: x.weekday, id="weekday"), - ], -) -def test_datetime_index_properties(datetime_indexes, access): - bf_index, pd_index = datetime_indexes - - actual_result = access(bf_index).to_pandas() - - expected_result = access(pd_index).astype(pandas.Int64Dtype()) - pandas.testing.assert_index_equal(actual_result, expected_result) diff --git a/tests/system/small/core/logging/__init__.py b/tests/system/small/core/logging/__init__.py deleted file mode 100644 index 58d482ea386..00000000000 --- a/tests/system/small/core/logging/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -# Copyright 2026 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/core/logging/test_data_types.py b/tests/system/small/core/logging/test_data_types.py deleted file mode 100644 index d69e17cfff8..00000000000 --- a/tests/system/small/core/logging/test_data_types.py +++ /dev/null @@ -1,113 +0,0 @@ -# Copyright 2026 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 typing import Sequence - -import pandas as pd -import pyarrow as pa - -import bigframes.pandas as bpd -from bigframes import dtypes -from bigframes.core.logging import data_types - - -def encode_types(inputs: Sequence[dtypes.Dtype]) -> str: - encoded_val = 0 - for t in inputs: - encoded_val = encoded_val | data_types._get_dtype_mask(t) - - return f"{encoded_val:x}" - - -def test_get_type_refs_no_op(scalars_df_index): - node = scalars_df_index._block._expr.node - expected_types: list[dtypes.Dtype] = [] - - assert data_types.encode_type_refs(node) == encode_types(expected_types) - - -def test_get_type_refs_projection(scalars_df_index): - node = ( - scalars_df_index["datetime_col"] - scalars_df_index["datetime_col"] - )._block._expr.node - expected_types = [dtypes.DATETIME_DTYPE, dtypes.TIMEDELTA_DTYPE] - - assert data_types.encode_type_refs(node) == encode_types(expected_types) - - -def test_get_type_refs_filter(scalars_df_index): - node = scalars_df_index[scalars_df_index["int64_col"] > 0]._block._expr.node - expected_types = [dtypes.INT_DTYPE, dtypes.BOOL_DTYPE] - - assert data_types.encode_type_refs(node) == encode_types(expected_types) - - -def test_get_type_refs_order_by(scalars_df_index): - node = scalars_df_index.sort_index()._block._expr.node - expected_types = [dtypes.INT_DTYPE] - - assert data_types.encode_type_refs(node) == encode_types(expected_types) - - -def test_get_type_refs_join(scalars_df_index): - node = ( - scalars_df_index[["int64_col"]].merge( - scalars_df_index[["float64_col"]], - left_on="int64_col", - right_on="float64_col", - ) - )._block._expr.node - expected_types = [dtypes.INT_DTYPE, dtypes.FLOAT_DTYPE] - - assert data_types.encode_type_refs(node) == encode_types(expected_types) - - -def test_get_type_refs_isin(scalars_df_index): - node = scalars_df_index["string_col"].isin(["a"])._block._expr.node - expected_types = [dtypes.STRING_DTYPE, dtypes.BOOL_DTYPE] - - assert data_types.encode_type_refs(node) == encode_types(expected_types) - - -def test_get_type_refs_agg(scalars_df_index): - node = scalars_df_index[["bool_col", "string_col"]].count()._block._expr.node - expected_types = [ - dtypes.INT_DTYPE, - dtypes.BOOL_DTYPE, - dtypes.STRING_DTYPE, - dtypes.FLOAT_DTYPE, - ] - - assert data_types.encode_type_refs(node) == encode_types(expected_types) - - -def test_get_type_refs_window(scalars_df_index): - node = ( - scalars_df_index[["string_col", "bool_col"]] - .groupby("string_col") - .rolling(window=3) - .count() - ._block._expr.node - ) - expected_types = [dtypes.STRING_DTYPE, dtypes.BOOL_DTYPE, dtypes.INT_DTYPE] - - assert data_types.encode_type_refs(node) == encode_types(expected_types) - - -def test_get_type_refs_explode(): - df = bpd.DataFrame({"A": ["a", "b"], "B": [[1, 2], [3, 4, 5]]}) - node = df.explode("B")._block._expr.node - expected_types = [pd.ArrowDtype(pa.list_(pa.int64()))] - - assert data_types.encode_type_refs(node) == encode_types(expected_types) diff --git a/tests/system/small/core/test_convert.py b/tests/system/small/core/test_convert.py deleted file mode 100644 index f63f945ad24..00000000000 --- a/tests/system/small/core/test_convert.py +++ /dev/null @@ -1,62 +0,0 @@ -# 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. -# 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 as pd -import pytest -from pandas import testing - -from bigframes import dataframe -from bigframes.core import convert - - -@pytest.mark.parametrize( - ("input", "expected"), - [ - pytest.param(pd.Series([1, 2, 3], name="test"), True, id="pd.Series"), - pytest.param(pd.Index([1, 2, 3], name="test"), True, id="pd.Index"), - pytest.param(pd.DataFrame({"test": [1, 2, 3]}), True, id="pd.DataFrame"), - pytest.param("something", False, id="string"), - ], -) -def test_can_convert_to_dataframe(input, expected): - assert convert.can_convert_to_dataframe(input) is expected - - -@pytest.mark.parametrize( - "input", - [ - pytest.param(pd.Series([1, 2, 3], name="test"), id="pd.Series"), - pytest.param(pd.Index([1, 2, 3], name="test"), id="pd.Index"), - pytest.param(pd.DataFrame({"test": [1, 2, 3]}), id="pd.DataFrame"), - ], -) -def test_to_bf_dataframe(input, session): - result = convert.to_bf_dataframe(input, None, session) - - testing.assert_frame_equal( - result.to_pandas(), - pd.DataFrame({"test": [1, 2, 3]}), - check_dtype=False, - check_index_type=False, - ) - - -def test_to_bf_dataframe_with_bf_dataframe(session): - bf = dataframe.DataFrame({"test": [1, 2, 3]}, session=session) - - testing.assert_frame_equal( - convert.to_bf_dataframe(bf, None, session).to_pandas(), - bf.to_pandas(), - ) diff --git a/tests/system/small/core/test_indexers.py b/tests/system/small/core/test_indexers.py deleted file mode 100644 index 20f1c561853..00000000000 --- a/tests/system/small/core/test_indexers.py +++ /dev/null @@ -1,78 +0,0 @@ -# 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. -# 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 warnings - -import pyarrow as pa -import pytest - -import bigframes.exceptions -import bigframes.pandas as bpd - - -@pytest.fixture(scope="module") -def string_indexed_struct_series(session): - return bpd.Series( - [ - {"project": "pandas", "version": 1}, - ], - dtype=bpd.ArrowDtype( - pa.struct([("project", pa.string()), ("version", pa.int64())]) - ), - index=["a"], - session=session, - ) - - -@pytest.fixture(scope="module") -def number_series(session): - return bpd.Series( - [0], - dtype=bpd.Int64Dtype, - session=session, - ) - - -@pytest.fixture(scope="module") -def string_indexed_number_series(session): - return bpd.Series( - [0], - dtype=bpd.Int64Dtype, - index=["a"], - session=session, - ) - - -@pytest.mark.parametrize( - "series", - [ - "string_indexed_struct_series", - "string_indexed_number_series", - ], -) -@pytest.mark.parametrize( - "key", - [ - 0, - "a", - ], -) -def test_struct_series_indexers_should_not_warn(request, series, key): - s = request.getfixturevalue(series) - - with warnings.catch_warnings(): - warnings.simplefilter( - "error", category=bigframes.exceptions.BadIndexerKeyWarning - ) - s[key] diff --git a/tests/system/small/core/test_reshape.py b/tests/system/small/core/test_reshape.py deleted file mode 100644 index aba9bf01859..00000000000 --- a/tests/system/small/core/test_reshape.py +++ /dev/null @@ -1,120 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import pandas as pd -import pytest - -import bigframes.testing.utils -from bigframes import session -from bigframes.core.reshape import merge - - -@pytest.mark.parametrize( - ("left_on", "right_on", "left_index", "right_index"), - [ - ("col_a", None, False, True), - (None, "col_d", True, False), - (None, None, True, True), - ], -) -@pytest.mark.parametrize("how", ["inner", "left", "right", "outer"]) -def test_join_with_index( - session: session.Session, left_on, right_on, left_index, right_index, how -): - df1 = pd.DataFrame({"col_a": [1, 2, 3], "col_b": [2, 3, 4]}, index=[1, 2, 3]) - bf1 = session.read_pandas(df1) - df2 = pd.DataFrame({"col_c": [1, 2, 3], "col_d": [2, 3, 4]}, index=[2, 3, 4]) - bf2 = session.read_pandas(df2) - - bf_result = merge.merge( - bf1, - bf2, - left_on=left_on, - right_on=right_on, - left_index=left_index, - right_index=right_index, - how=how, - ).to_pandas() - pd_result = pd.merge( - df1, - df2, - left_on=left_on, - right_on=right_on, - left_index=left_index, - right_index=right_index, - how=how, - ) - - bigframes.testing.utils.assert_frame_equal( - bf_result, pd_result, check_dtype=False, check_index_type=False - ) - - -@pytest.mark.parametrize( - ("on", "left_on", "right_on", "left_index", "right_index"), - [ - (None, "col_a", None, True, False), - (None, None, "col_c", None, True), - ("col_a", None, None, True, True), - ], -) -def test_join_with_index_invalid_index_arg_raise_error( - session: session.Session, on, left_on, right_on, left_index, right_index -): - df1 = pd.DataFrame({"col_a": [1, 2, 3], "col_b": [2, 3, 4]}, index=[1, 2, 3]) - bf1 = session.read_pandas(df1) - df2 = pd.DataFrame({"col_c": [1, 2, 3], "col_d": [2, 3, 4]}, index=[2, 3, 4]) - bf2 = session.read_pandas(df2) - - with pytest.raises(ValueError): - merge.merge( - bf1, - bf2, - on=on, - left_on=left_on, - right_on=right_on, - left_index=left_index, - right_index=right_index, - ).to_pandas() - - -@pytest.mark.parametrize( - ("left_on", "right_on", "left_index", "right_index"), - [ - (["col_a", "col_b"], None, False, True), - (None, ["col_c", "col_d"], True, False), - (None, None, True, True), - ], -) -@pytest.mark.parametrize("how", ["inner", "left", "right", "outer"]) -def test_join_with_multiindex_raises_error( - session: session.Session, left_on, right_on, left_index, right_index, how -): - multi_idx1 = pd.MultiIndex.from_tuples([(1, 2), (2, 3), (3, 5)]) - df1 = pd.DataFrame({"col_a": [1, 2, 3], "col_b": [2, 3, 4]}, index=multi_idx1) - bf1 = session.read_pandas(df1) - multi_idx2 = pd.MultiIndex.from_tuples([(1, 2), (2, 3), (3, 2)]) - df2 = pd.DataFrame({"col_c": [1, 2, 3], "col_d": [2, 3, 4]}, index=multi_idx2) - bf2 = session.read_pandas(df2) - - with pytest.raises(ValueError): - merge.merge( - bf1, - bf2, - left_on=left_on, - right_on=right_on, - left_index=left_index, - right_index=right_index, - how=how, - ) diff --git a/tests/system/small/engines/__init__.py b/tests/system/small/engines/__init__.py deleted file mode 100644 index 0a2669d7a25..00000000000 --- a/tests/system/small/engines/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. diff --git a/tests/system/small/engines/conftest.py b/tests/system/small/engines/conftest.py deleted file mode 100644 index 823ba9806d5..00000000000 --- a/tests/system/small/engines/conftest.py +++ /dev/null @@ -1,135 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -import pathlib -from typing import Generator - -import google.cloud.bigquery_storage_v1 -import pandas as pd -import pytest -from google.cloud import bigquery - -import bigframes -from bigframes.core import ArrayValue, events, local_data -from bigframes.session import ( - direct_gbq_execution, - local_scan_executor, - polars_executor, - semi_executor, -) - -CURRENT_DIR = pathlib.Path(__file__).parent -DATA_DIR = CURRENT_DIR.parent.parent.parent / "data" - - -@pytest.fixture(scope="module") -def fake_session() -> Generator[bigframes.Session, None, None]: - import bigframes.core.global_session - - # its a "polars session", but we are bypassing session-provided execution - # we just want a minimal placeholder session without expensive setup - from bigframes.testing import polars_session - - session = polars_session.TestSession() - with bigframes.core.global_session._GlobalSessionContext(session): - yield session - - -@pytest.fixture(scope="session") -def pyarrow_engine(): - return local_scan_executor.LocalScanExecutor() - - -@pytest.fixture(scope="session") -def polars_engine(): - return polars_executor.PolarsExecutor() - - -@pytest.fixture(scope="session") -def bq_engine( - bigquery_client: bigquery.Client, - bigquery_storage_read_client: google.cloud.bigquery_storage_v1.BigQueryReadClient, -): - return direct_gbq_execution.DirectGbqExecutor( - bigquery_client, - bqstoragereadclient=bigquery_storage_read_client, - publisher=events.Publisher(), - compiler="ibis", - ) - - -@pytest.fixture(scope="session") -def sqlglot_engine( - bigquery_client: bigquery.Client, - bigquery_storage_read_client: google.cloud.bigquery_storage_v1.BigQueryReadClient, -) -> semi_executor.SemiExecutor: - return direct_gbq_execution.DirectGbqExecutor( - bigquery_client, - bqstoragereadclient=bigquery_storage_read_client, - publisher=events.Publisher(), - ) - - -@pytest.fixture(scope="session", params=["pyarrow", "polars", "bq", "bq-sqlglot"]) -def engine( - request, pyarrow_engine, polars_engine, bq_engine, sqlglot_engine -) -> semi_executor.SemiExecutor: - if request.param == "pyarrow": - return pyarrow_engine - if request.param == "polars": - return polars_engine - if request.param == "bq": - return bq_engine - if request.param == "bq-sqlglot": - return sqlglot_engine - raise ValueError(f"Unrecognized param: {request.param}") - - -@pytest.fixture(scope="module") -def managed_data_source( - scalars_pandas_df_index: pd.DataFrame, -) -> local_data.ManagedArrowTable: - return local_data.ManagedArrowTable.from_pandas(scalars_pandas_df_index) - - -@pytest.fixture(scope="module") -def scalars_array_value( - managed_data_source: local_data.ManagedArrowTable, fake_session: bigframes.Session -): - return ArrayValue.from_managed(managed_data_source, fake_session) - - -@pytest.fixture(scope="module") -def zero_row_source() -> local_data.ManagedArrowTable: - return local_data.ManagedArrowTable.from_pandas(pd.DataFrame({"a": [], "b": []})) - - -@pytest.fixture(scope="module") -def nested_data_source( - nested_pandas_df: pd.DataFrame, -) -> local_data.ManagedArrowTable: - return local_data.ManagedArrowTable.from_pandas(nested_pandas_df) - - -@pytest.fixture(scope="module") -def repeated_data_source( - repeated_pandas_df: pd.DataFrame, -) -> local_data.ManagedArrowTable: - return local_data.ManagedArrowTable.from_pandas(repeated_pandas_df) - - -@pytest.fixture(scope="module") -def arrays_array_value( - repeated_data_source: local_data.ManagedArrowTable, fake_session: bigframes.Session -): - return ArrayValue.from_managed(repeated_data_source, fake_session) diff --git a/tests/system/small/engines/test_aggregation.py b/tests/system/small/engines/test_aggregation.py deleted file mode 100644 index 669eae9ebf7..00000000000 --- a/tests/system/small/engines/test_aggregation.py +++ /dev/null @@ -1,171 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import pytest -from google.cloud import bigquery - -import bigframes.operations.aggregations as agg_ops -from bigframes.core import ( - agg_expressions, - array_value, - expression, - identifiers, - nodes, -) -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() - - -def apply_agg_to_all_valid( - array: array_value.ArrayValue, op: agg_ops.UnaryAggregateOp, excluded_cols=[] -) -> array_value.ArrayValue: - """ - Apply the aggregation to every column in the array that has a compatible datatype. - """ - exprs_by_name = [] - for arg in array.column_ids: - if arg in excluded_cols: - continue - try: - _ = op.output_type(array.get_column_type(arg)) - expr = agg_expressions.UnaryAggregation(op, expression.deref(arg)) - name = f"{arg}-{op.name}" - exprs_by_name.append((expr, name)) - except TypeError: - continue - assert len(exprs_by_name) > 0 - new_arr = array.aggregate(exprs_by_name) - return new_arr - - -@pytest.mark.parametrize("engine", ["polars", "bq", "bq-sqlglot"], indirect=True) -def test_engines_aggregate_post_filter_size( - scalars_array_value: array_value.ArrayValue, - engine, -): - w_offsets, offsets_id = ( - scalars_array_value.select_columns(("bool_col", "string_col")) - .filter(expression.deref("bool_col")) - .promote_offsets() - ) - plan = ( - w_offsets.select_columns((offsets_id, "bool_col", "string_col")) - .row_count() - .node - ) - - assert_equivalence_execution(plan, REFERENCE_ENGINE, engine) - - -@pytest.mark.parametrize("engine", ["polars", "bq", "bq-sqlglot"], indirect=True) -def test_engines_aggregate_size( - scalars_array_value: array_value.ArrayValue, - engine, -): - node = nodes.AggregateNode( - scalars_array_value.node, - aggregations=( - ( - agg_expressions.NullaryAggregation(agg_ops.SizeOp()), - identifiers.ColumnId("size_op"), - ), - ( - agg_expressions.UnaryAggregation( - agg_ops.SizeUnaryOp(), expression.deref("string_col") - ), - identifiers.ColumnId("unary_size_op"), - ), - ), - ) - assert_equivalence_execution(node, REFERENCE_ENGINE, engine) - - -@pytest.mark.parametrize("engine", ["polars", "bq", "bq-sqlglot"], indirect=True) -@pytest.mark.parametrize( - "op", - [agg_ops.min_op, agg_ops.max_op, agg_ops.mean_op, agg_ops.sum_op, agg_ops.count_op], -) -def test_engines_unary_aggregates( - scalars_array_value: array_value.ArrayValue, - engine, - op, -): - node = apply_agg_to_all_valid(scalars_array_value, op).node - assert_equivalence_execution(node, REFERENCE_ENGINE, engine) - - -@pytest.mark.parametrize("engine", ["polars", "bq", "bq-sqlglot"], indirect=True) -@pytest.mark.parametrize( - "op", - [agg_ops.std_op, agg_ops.var_op, agg_ops.PopVarOp()], -) -def test_engines_unary_variance_aggregates( - scalars_array_value: array_value.ArrayValue, - engine, - op, -): - node = apply_agg_to_all_valid(scalars_array_value, op).node - assert_equivalence_execution(node, REFERENCE_ENGINE, engine) - - -def test_sql_engines_median_op_aggregates( - scalars_array_value: array_value.ArrayValue, - bigquery_client: bigquery.Client, - bq_engine, - sqlglot_engine, -): - node = apply_agg_to_all_valid( - scalars_array_value, - agg_ops.MedianOp(), - ).node - assert_equivalence_execution(node, bq_engine, sqlglot_engine) - - -@pytest.mark.parametrize("engine", ["polars", "bq", "bq-sqlglot"], indirect=True) -@pytest.mark.parametrize( - "grouping_cols", - [ - ["bool_col"], - ["string_col", "int64_col"], - ["date_col"], - ["datetime_col"], - ["timestamp_col"], - ["bytes_col"], - ], -) -def test_engines_grouped_aggregate( - scalars_array_value: array_value.ArrayValue, engine, grouping_cols -): - node = nodes.AggregateNode( - scalars_array_value.node, - aggregations=( - ( - agg_expressions.NullaryAggregation(agg_ops.SizeOp()), - identifiers.ColumnId("size_op"), - ), - ( - agg_expressions.UnaryAggregation( - agg_ops.SizeUnaryOp(), expression.deref("string_col") - ), - identifiers.ColumnId("unary_size_op"), - ), - ), - by_column_ids=tuple(expression.deref(id) for id in grouping_cols), - ) - assert_equivalence_execution(node, REFERENCE_ENGINE, engine) diff --git a/tests/system/small/engines/test_array_ops.py b/tests/system/small/engines/test_array_ops.py deleted file mode 100644 index 159f23f48d6..00000000000 --- a/tests/system/small/engines/test_array_ops.py +++ /dev/null @@ -1,60 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import pytest - -import bigframes.operations as ops -import bigframes.operations.aggregations as agg_ops -from bigframes.core import array_value, expression -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", "bq-sqlglot"], indirect=True) -def test_engines_to_array_op(scalars_array_value: array_value.ArrayValue, engine): - # Bigquery won't allow you to materialize arrays with null, so use non-nullable - int64_non_null = ops.coalesce_op.as_expr("int64_col", expression.const(0)) - bool_col_non_null = ops.coalesce_op.as_expr("bool_col", expression.const(False)) - float_col_non_null = ops.coalesce_op.as_expr("float64_col", expression.const(0.0)) - string_col_non_null = ops.coalesce_op.as_expr("string_col", expression.const("")) - - arr, _ = scalars_array_value.compute_values( - [ - ops.ToArrayOp().as_expr(int64_non_null), - ops.ToArrayOp().as_expr( - int64_non_null, bool_col_non_null, float_col_non_null - ), - ops.ToArrayOp().as_expr(string_col_non_null, string_col_non_null), - ] - ) - assert_equivalence_execution(arr.node, REFERENCE_ENGINE, engine) - - -@pytest.mark.parametrize("engine", ["polars", "bq", "bq-sqlglot"], indirect=True) -def test_engines_array_reduce_op(arrays_array_value: array_value.ArrayValue, engine): - arr, _ = arrays_array_value.compute_values( - [ - ops.ArrayReduceOp(agg_ops.SumOp()).as_expr("float_list_col"), - ops.ArrayReduceOp(agg_ops.StdOp()).as_expr("float_list_col"), - ops.ArrayReduceOp(agg_ops.MaxOp()).as_expr("date_list_col"), - ops.ArrayReduceOp(agg_ops.CountOp()).as_expr("string_list_col"), - ops.ArrayReduceOp(agg_ops.AnyOp()).as_expr("bool_list_col"), - ] - ) - assert_equivalence_execution(arr.node, REFERENCE_ENGINE, engine) diff --git a/tests/system/small/engines/test_bool_ops.py b/tests/system/small/engines/test_bool_ops.py deleted file mode 100644 index a6ef702885b..00000000000 --- a/tests/system/small/engines/test_bool_ops.py +++ /dev/null @@ -1,64 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import itertools - -import pytest - -import bigframes.operations as ops -from bigframes.core import array_value -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() - - -def apply_op_pairwise( - array: array_value.ArrayValue, op: ops.BinaryOp, excluded_cols=[] -) -> array_value.ArrayValue: - exprs = [] - for l_arg, r_arg in itertools.permutations(array.column_ids, 2): - if (l_arg in excluded_cols) or (r_arg in excluded_cols): - continue - try: - _ = op.output_type( - array.get_column_type(l_arg), array.get_column_type(r_arg) - ) - exprs.append(op.as_expr(l_arg, r_arg)) - except TypeError: - continue - assert len(exprs) > 0 - new_arr, _ = array.compute_values(exprs) - return new_arr - - -@pytest.mark.parametrize("engine", ["polars", "bq", "bq-sqlglot"], indirect=True) -@pytest.mark.parametrize( - "op", - [ - ops.and_op, - ops.or_op, - ops.xor_op, - ], -) -def test_engines_project_boolean_op( - scalars_array_value: array_value.ArrayValue, engine, op -): - # exclude string cols as does not contain dates - # bool col actually doesn't work properly for bq engine - arr = apply_op_pairwise(scalars_array_value, op, excluded_cols=["string_col"]) - assert_equivalence_execution(arr.node, REFERENCE_ENGINE, engine) diff --git a/tests/system/small/engines/test_comparison_ops.py b/tests/system/small/engines/test_comparison_ops.py deleted file mode 100644 index cd6ece55863..00000000000 --- a/tests/system/small/engines/test_comparison_ops.py +++ /dev/null @@ -1,84 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import asyncio -import itertools - -import pytest - -import bigframes.operations as ops -from bigframes.core import array_value -from bigframes.session import polars_executor -from bigframes.testing.engine_utils import SPEC, 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() - -# numeric domain - - -def apply_op_pairwise( - array: array_value.ArrayValue, op: ops.BinaryOp, excluded_cols=[] -) -> array_value.ArrayValue: - exprs = [] - for l_arg, r_arg in itertools.permutations(array.column_ids, 2): - if (l_arg in excluded_cols) or (r_arg in excluded_cols): - continue - try: - _ = op.output_type( - array.get_column_type(l_arg), array.get_column_type(r_arg) - ) - exprs.append(op.as_expr(l_arg, r_arg)) - except TypeError: - continue - assert len(exprs) > 0 - new_arr, _ = array.compute_values(exprs) - return new_arr - - -@pytest.mark.parametrize("engine", ["polars", "bq", "bq-sqlglot"], indirect=True) -@pytest.mark.parametrize( - "op", - [ - ops.eq_op, - ops.eq_null_match_op, - ops.ne_op, - ops.gt_op, - ops.lt_op, - ops.le_op, - ops.ge_op, - ], -) -def test_engines_project_comparison_op( - scalars_array_value: array_value.ArrayValue, engine, op -): - # exclude string cols as does not contain dates - # bool col actually doesn't work properly for bq engine - arr = apply_op_pairwise(scalars_array_value, op, excluded_cols=["string_col"]) - assert_equivalence_execution(arr.node, REFERENCE_ENGINE, engine) - - -@pytest.mark.parametrize("engine", ["bq-sqlglot"], indirect=True) -def test_engines_precedence_like_and_in( - scalars_array_value: array_value.ArrayValue, engine -): - exprs = [ - ops.eq_op.as_expr("bool_col", ops.StrContainsOp("a").as_expr("string_col")), - ] - arr, _ = scalars_array_value.compute_values(exprs) - res = asyncio.run(engine.execute(arr.node, SPEC)) - assert res is not None - assert len(res.batches().to_pandas()) > 0 diff --git a/tests/system/small/engines/test_concat.py b/tests/system/small/engines/test_concat.py deleted file mode 100644 index 5786cfc4193..00000000000 --- a/tests/system/small/engines/test_concat.py +++ /dev/null @@ -1,51 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import pytest - -from bigframes.core import array_value, ordering -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", "bq-sqlglot"], indirect=True) -def test_engines_concat_self( - scalars_array_value: array_value.ArrayValue, - engine, -): - result = scalars_array_value.concat([scalars_array_value, scalars_array_value]) - - assert_equivalence_execution(result.node, REFERENCE_ENGINE, engine) - - -@pytest.mark.parametrize("engine", ["polars", "bq", "bq-sqlglot"], indirect=True) -def test_engines_concat_filtered_sorted( - scalars_array_value: array_value.ArrayValue, - engine, -): - input_1 = scalars_array_value.select_columns(["float64_col", "int64_col"]).order_by( - [ordering.ascending_over("int64_col")] - ) - input_2 = scalars_array_value.filter_by_id("bool_col").select_columns( - ["float64_col", "int64_too"] - ) - - result = input_1.concat([input_2, input_1, input_2]) - - assert_equivalence_execution(result.node, REFERENCE_ENGINE, engine) diff --git a/tests/system/small/engines/test_filtering.py b/tests/system/small/engines/test_filtering.py deleted file mode 100644 index fcb85aa8859..00000000000 --- a/tests/system/small/engines/test_filtering.py +++ /dev/null @@ -1,67 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -import pytest - -import bigframes.operations as ops -from bigframes.core import array_value, expression, nodes -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", "bq-sqlglot"], indirect=True) -def test_engines_filter_bool_col( - scalars_array_value: array_value.ArrayValue, - engine, -): - node = nodes.FilterNode( - scalars_array_value.node, predicate=expression.deref("bool_col") - ) - assert_equivalence_execution(node, REFERENCE_ENGINE, engine) - - -@pytest.mark.parametrize("engine", ["polars", "bq", "bq-sqlglot"], indirect=True) -def test_engines_filter_expr_cond( - scalars_array_value: array_value.ArrayValue, - engine, -): - predicate = ops.gt_op.as_expr( - expression.deref("float64_col"), expression.deref("int64_col") - ) - node = nodes.FilterNode(scalars_array_value.node, predicate=predicate) - assert_equivalence_execution(node, REFERENCE_ENGINE, engine) - - -@pytest.mark.parametrize("engine", ["polars", "bq", "bq-sqlglot"], indirect=True) -def test_engines_filter_true( - scalars_array_value: array_value.ArrayValue, - engine, -): - predicate = expression.const(True) - node = nodes.FilterNode(scalars_array_value.node, predicate=predicate) - assert_equivalence_execution(node, REFERENCE_ENGINE, engine) - - -@pytest.mark.parametrize("engine", ["polars", "bq", "bq-sqlglot"], indirect=True) -def test_engines_filter_false( - scalars_array_value: array_value.ArrayValue, - engine, -): - predicate = expression.const(False) - node = nodes.FilterNode(scalars_array_value.node, predicate=predicate) - assert_equivalence_execution(node, REFERENCE_ENGINE, engine) diff --git a/tests/system/small/engines/test_generic_ops.py b/tests/system/small/engines/test_generic_ops.py deleted file mode 100644 index 96beb51f99d..00000000000 --- a/tests/system/small/engines/test_generic_ops.py +++ /dev/null @@ -1,535 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import re - -import pytest - -import bigframes.dtypes -import bigframes.operations as ops -from bigframes.core import array_value, expression -from bigframes.session import polars_executor -from bigframes.testing.engine_utils import assert_equivalence_execution - -polars = 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() - - -def apply_op( - array: array_value.ArrayValue, op: ops.AsTypeOp, excluded_cols=[] -) -> array_value.ArrayValue: - exprs = [] - labels = [] - for arg in array.column_ids: - if arg in excluded_cols: - continue - try: - _ = op.output_type(array.get_column_type(arg)) - expr = op.as_expr(arg) - exprs.append(expr) - type_string = re.sub(r"[^a-zA-Z\d]", "_", str(op.to_type)) - labels.append(f"{arg}_as_{type_string}") - except TypeError: - continue - assert len(exprs) > 0 - new_arr, ids = array.compute_values(exprs) - new_arr = new_arr.rename_columns( - {new_col: label for new_col, label in zip(ids, labels)} - ) - return new_arr - - -@pytest.mark.parametrize("engine", ["polars", "bq", "bq-sqlglot"], indirect=True) -def test_engines_astype_int(scalars_array_value: array_value.ArrayValue, engine): - polars_version = tuple([int(part) for part in polars.__version__.split(".")]) - if polars_version >= (1, 34, 0): - # TODO(https://github.com/pola-rs/polars/issues/24841): Remove this when - # polars fixes Decimal to Int cast. - scalars_array_value = scalars_array_value.drop_columns(["numeric_col"]) - - arr = apply_op( - scalars_array_value, - ops.AsTypeOp(to_type=bigframes.dtypes.INT_DTYPE), - excluded_cols=["string_col"], - ) - - assert_equivalence_execution(arr.node, REFERENCE_ENGINE, engine) - - -@pytest.mark.parametrize("engine", ["polars", "bq", "bq-sqlglot"], indirect=True) -def test_engines_astype_string_int(scalars_array_value: array_value.ArrayValue, engine): - vals = ["1", "100", "-3"] - arr, _ = scalars_array_value.compute_values( - [ - ops.AsTypeOp(to_type=bigframes.dtypes.INT_DTYPE).as_expr( - expression.const(val) - ) - for val in vals - ] - ) - - assert_equivalence_execution(arr.node, REFERENCE_ENGINE, engine) - - -@pytest.mark.parametrize("engine", ["polars", "bq", "bq-sqlglot"], indirect=True) -def test_engines_astype_float(scalars_array_value: array_value.ArrayValue, engine): - arr = apply_op( - scalars_array_value, - ops.AsTypeOp(to_type=bigframes.dtypes.FLOAT_DTYPE), - excluded_cols=["string_col"], - ) - - assert_equivalence_execution(arr.node, REFERENCE_ENGINE, engine) - - -@pytest.mark.parametrize("engine", ["polars", "bq", "bq-sqlglot"], indirect=True) -def test_engines_astype_string_float( - scalars_array_value: array_value.ArrayValue, engine -): - vals = ["1", "1.1", ".1", "1e3", "1.34235e4", "3.33333e-4"] - arr, _ = scalars_array_value.compute_values( - [ - ops.AsTypeOp(to_type=bigframes.dtypes.FLOAT_DTYPE).as_expr( - expression.const(val) - ) - for val in vals - ] - ) - - assert_equivalence_execution(arr.node, REFERENCE_ENGINE, engine) - - -@pytest.mark.parametrize("engine", ["polars", "bq", "bq-sqlglot"], indirect=True) -def test_engines_astype_bool(scalars_array_value: array_value.ArrayValue, engine): - arr = apply_op( - scalars_array_value, ops.AsTypeOp(to_type=bigframes.dtypes.BOOL_DTYPE) - ) - - assert_equivalence_execution(arr.node, REFERENCE_ENGINE, engine) - - -@pytest.mark.parametrize("engine", ["polars", "bq", "bq-sqlglot"], indirect=True) -def test_engines_astype_string(scalars_array_value: array_value.ArrayValue, engine): - # floats work slightly different with trailing zeroes rn - arr = apply_op( - scalars_array_value, - ops.AsTypeOp(to_type=bigframes.dtypes.STRING_DTYPE), - excluded_cols=["float64_col"], - ) - - assert_equivalence_execution(arr.node, REFERENCE_ENGINE, engine) - - -@pytest.mark.parametrize("engine", ["polars", "bq", "bq-sqlglot"], indirect=True) -def test_engines_astype_numeric(scalars_array_value: array_value.ArrayValue, engine): - arr = apply_op( - scalars_array_value, - ops.AsTypeOp(to_type=bigframes.dtypes.NUMERIC_DTYPE), - excluded_cols=["string_col"], - ) - - assert_equivalence_execution(arr.node, REFERENCE_ENGINE, engine) - - -@pytest.mark.parametrize("engine", ["polars", "bq", "bq-sqlglot"], indirect=True) -def test_engines_astype_string_numeric( - scalars_array_value: array_value.ArrayValue, engine -): - vals = ["1", "1.1", ".1", "23428975070235903.209", "-23428975070235903.209"] - arr, _ = scalars_array_value.compute_values( - [ - ops.AsTypeOp(to_type=bigframes.dtypes.NUMERIC_DTYPE).as_expr( - expression.const(val) - ) - for val in vals - ] - ) - - assert_equivalence_execution(arr.node, REFERENCE_ENGINE, engine) - - -@pytest.mark.parametrize("engine", ["polars", "bq", "bq-sqlglot"], indirect=True) -def test_engines_astype_date(scalars_array_value: array_value.ArrayValue, engine): - arr = apply_op( - scalars_array_value, - ops.AsTypeOp(to_type=bigframes.dtypes.DATE_DTYPE), - excluded_cols=["string_col"], - ) - - assert_equivalence_execution(arr.node, REFERENCE_ENGINE, engine) - - -@pytest.mark.parametrize("engine", ["polars", "bq", "bq-sqlglot"], indirect=True) -def test_engines_astype_string_date( - scalars_array_value: array_value.ArrayValue, engine -): - vals = ["2014-08-15", "2215-08-15", "2016-02-29"] - arr, _ = scalars_array_value.compute_values( - [ - ops.AsTypeOp(to_type=bigframes.dtypes.DATE_DTYPE).as_expr( - expression.const(val) - ) - for val in vals - ] - ) - - assert_equivalence_execution(arr.node, REFERENCE_ENGINE, engine) - - -@pytest.mark.parametrize("engine", ["polars", "bq", "bq-sqlglot"], indirect=True) -def test_engines_astype_datetime(scalars_array_value: array_value.ArrayValue, engine): - arr = apply_op( - scalars_array_value, - ops.AsTypeOp(to_type=bigframes.dtypes.DATETIME_DTYPE), - excluded_cols=["string_col"], - ) - - assert_equivalence_execution(arr.node, REFERENCE_ENGINE, engine) - - -@pytest.mark.parametrize("engine", ["polars", "bq", "bq-sqlglot"], indirect=True) -def test_engines_astype_string_datetime( - scalars_array_value: array_value.ArrayValue, engine -): - vals = ["2014-08-15 08:15:12", "2015-08-15 08:15:12.654754", "2016-02-29 00:00:00"] - arr, _ = scalars_array_value.compute_values( - [ - ops.AsTypeOp(to_type=bigframes.dtypes.DATETIME_DTYPE).as_expr( - expression.const(val) - ) - for val in vals - ] - ) - - assert_equivalence_execution(arr.node, REFERENCE_ENGINE, engine) - - -@pytest.mark.parametrize("engine", ["polars", "bq", "bq-sqlglot"], indirect=True) -def test_engines_astype_timestamp(scalars_array_value: array_value.ArrayValue, engine): - arr = apply_op( - scalars_array_value, - ops.AsTypeOp(to_type=bigframes.dtypes.TIMESTAMP_DTYPE), - excluded_cols=["string_col"], - ) - - assert_equivalence_execution(arr.node, REFERENCE_ENGINE, engine) - - -@pytest.mark.parametrize("engine", ["polars", "bq", "bq-sqlglot"], indirect=True) -def test_engines_astype_string_timestamp( - scalars_array_value: array_value.ArrayValue, engine -): - vals = [ - "2014-08-15 08:15:12+00:00", - "2015-08-15 08:15:12.654754+05:00", - "2016-02-29 00:00:00+08:00", - ] - arr, _ = scalars_array_value.compute_values( - [ - ops.AsTypeOp(to_type=bigframes.dtypes.TIMESTAMP_DTYPE).as_expr( - expression.const(val) - ) - for val in vals - ] - ) - - assert_equivalence_execution(arr.node, REFERENCE_ENGINE, engine) - - -@pytest.mark.parametrize("engine", ["polars", "bq", "bq-sqlglot"], indirect=True) -def test_engines_astype_time(scalars_array_value: array_value.ArrayValue, engine): - arr = apply_op( - scalars_array_value, - ops.AsTypeOp(to_type=bigframes.dtypes.TIME_DTYPE), - excluded_cols=["string_col", "int64_col", "int64_too"], - ) - - assert_equivalence_execution(arr.node, REFERENCE_ENGINE, engine) - - -@pytest.mark.parametrize("engine", ["polars", "bq", "bq-sqlglot"], indirect=True) -def test_engines_astype_from_json(scalars_array_value: array_value.ArrayValue, engine): - exprs = [ - ops.JSONDecode(to_type=bigframes.dtypes.INT_DTYPE).as_expr( - expression.const("5", bigframes.dtypes.JSON_DTYPE) - ), - ops.JSONDecode(to_type=bigframes.dtypes.FLOAT_DTYPE).as_expr( - expression.const("5", bigframes.dtypes.JSON_DTYPE) - ), - ops.JSONDecode(to_type=bigframes.dtypes.BOOL_DTYPE).as_expr( - expression.const("true", bigframes.dtypes.JSON_DTYPE) - ), - ops.JSONDecode(to_type=bigframes.dtypes.STRING_DTYPE).as_expr( - expression.const('"hello world"', bigframes.dtypes.JSON_DTYPE) - ), - ] - arr, _ = scalars_array_value.compute_values(exprs) - - assert_equivalence_execution(arr.node, REFERENCE_ENGINE, engine) - - -@pytest.mark.parametrize("engine", ["polars", "bq", "bq-sqlglot"], indirect=True) -def test_engines_astype_to_json(scalars_array_value: array_value.ArrayValue, engine): - exprs = [ - ops.ToJSON().as_expr(expression.deref("int64_col")), - ops.ToJSON().as_expr( - # Use a const since float to json has precision issues - expression.const(5.2, bigframes.dtypes.FLOAT_DTYPE) - ), - ops.ToJSON().as_expr(expression.deref("bool_col")), - ops.ToJSON().as_expr( - # Use a const since "str_col" has special chars. - expression.const('"hello world"', bigframes.dtypes.STRING_DTYPE) - ), - ] - arr, _ = scalars_array_value.compute_values(exprs) - - assert_equivalence_execution(arr.node, REFERENCE_ENGINE, engine) - - -@pytest.mark.parametrize("engine", ["polars", "bq", "bq-sqlglot"], indirect=True) -def test_engines_to_json_string(scalars_array_value: array_value.ArrayValue, engine): - exprs = [ - ops.ToJSONString().as_expr(expression.deref("int64_col")), - ops.ToJSONString().as_expr( - # Use a const since float to json has precision issues - expression.const(5.2, bigframes.dtypes.FLOAT_DTYPE) - ), - ops.ToJSONString().as_expr(expression.deref("bool_col")), - ops.ToJSONString().as_expr( - # Use a const since "str_col" has special chars. - expression.const('"hello world"', bigframes.dtypes.STRING_DTYPE) - ), - ] - arr, _ = scalars_array_value.compute_values(exprs) - - assert_equivalence_execution(arr.node, REFERENCE_ENGINE, engine) - - -@pytest.mark.parametrize("engine", ["polars", "bq", "bq-sqlglot"], indirect=True) -def test_engines_astype_timedelta(scalars_array_value: array_value.ArrayValue, engine): - arr = apply_op( - scalars_array_value, - ops.AsTypeOp(to_type=bigframes.dtypes.TIMEDELTA_DTYPE), - ) - - assert_equivalence_execution(arr.node, REFERENCE_ENGINE, engine) - - -@pytest.mark.parametrize("engine", ["polars", "bq", "bq-sqlglot"], indirect=True) -def test_engines_where_op(scalars_array_value: array_value.ArrayValue, engine): - arr, _ = scalars_array_value.compute_values( - [ - ops.where_op.as_expr( - expression.deref("int64_col"), - expression.deref("bool_col"), - expression.deref("float64_col"), - ) - ] - ) - - assert_equivalence_execution(arr.node, REFERENCE_ENGINE, engine) - - -@pytest.mark.parametrize("engine", ["polars", "bq", "bq-sqlglot"], indirect=True) -def test_engines_coalesce_op(scalars_array_value: array_value.ArrayValue, engine): - arr, _ = scalars_array_value.compute_values( - [ - ops.coalesce_op.as_expr( - expression.deref("int64_col"), - expression.deref("float64_col"), - ) - ] - ) - - assert_equivalence_execution(arr.node, REFERENCE_ENGINE, engine) - - -@pytest.mark.parametrize("engine", ["polars", "bq", "bq-sqlglot"], indirect=True) -def test_engines_fillna_op(scalars_array_value: array_value.ArrayValue, engine): - arr, _ = scalars_array_value.compute_values( - [ - ops.fillna_op.as_expr( - expression.deref("int64_col"), - expression.deref("float64_col"), - ) - ] - ) - - assert_equivalence_execution(arr.node, REFERENCE_ENGINE, engine) - - -@pytest.mark.parametrize("engine", ["polars", "bq", "bq-sqlglot"], indirect=True) -def test_engines_casewhen_op_single_case( - scalars_array_value: array_value.ArrayValue, engine -): - arr, _ = scalars_array_value.compute_values( - [ - ops.case_when_op.as_expr( - expression.deref("bool_col"), - expression.deref("int64_col"), - ) - ] - ) - - assert_equivalence_execution(arr.node, REFERENCE_ENGINE, engine) - - -@pytest.mark.parametrize("engine", ["polars", "bq", "bq-sqlglot"], indirect=True) -def test_engines_casewhen_op_double_case( - scalars_array_value: array_value.ArrayValue, engine -): - arr, _ = scalars_array_value.compute_values( - [ - ops.case_when_op.as_expr( - ops.gt_op.as_expr(expression.deref("int64_col"), expression.const(3)), - expression.deref("int64_col"), - ops.lt_op.as_expr(expression.deref("int64_col"), expression.const(-3)), - expression.deref("int64_too"), - ) - ] - ) - - assert_equivalence_execution(arr.node, REFERENCE_ENGINE, engine) - - -@pytest.mark.parametrize("engine", ["polars", "bq", "bq-sqlglot"], indirect=True) -def test_engines_isnull_op(scalars_array_value: array_value.ArrayValue, engine): - arr, _ = scalars_array_value.compute_values( - [ops.isnull_op.as_expr(expression.deref("string_col"))] - ) - - assert_equivalence_execution(arr.node, REFERENCE_ENGINE, engine) - - -@pytest.mark.parametrize("engine", ["polars", "bq", "bq-sqlglot"], indirect=True) -def test_engines_notnull_op(scalars_array_value: array_value.ArrayValue, engine): - arr, _ = scalars_array_value.compute_values( - [ops.notnull_op.as_expr(expression.deref("string_col"))] - ) - - assert_equivalence_execution(arr.node, REFERENCE_ENGINE, engine) - - -@pytest.mark.parametrize("engine", ["polars", "bq", "bq-sqlglot"], indirect=True) -def test_engines_coerce_to_bool_op_scalars( - scalars_array_value: array_value.ArrayValue, engine -): - arr, _ = scalars_array_value.compute_values( - [ - ops.coerce_to_bool_op.as_expr(expression.deref("bool_col")), - ops.coerce_to_bool_op.as_expr(expression.deref("int64_col")), - ops.coerce_to_bool_op.as_expr(expression.deref("float64_col")), - ops.coerce_to_bool_op.as_expr(expression.deref("string_col")), - ops.coerce_to_bool_op.as_expr(expression.deref("bytes_col")), - ] - ) - - assert_equivalence_execution(arr.node, REFERENCE_ENGINE, engine) - - -@pytest.mark.parametrize("engine", ["polars", "bq", "bq-sqlglot"], indirect=True) -def test_engines_coerce_to_bool_op_arrays( - arrays_array_value: array_value.ArrayValue, engine -): - arr, _ = arrays_array_value.compute_values( - [ - ops.coerce_to_bool_op.as_expr(expression.deref("int_list_col")), - ops.coerce_to_bool_op.as_expr(expression.deref("bool_list_col")), - ops.coerce_to_bool_op.as_expr(expression.deref("float_list_col")), - ops.coerce_to_bool_op.as_expr(expression.deref("string_list_col")), - ] - ) - - assert_equivalence_execution(arr.node, REFERENCE_ENGINE, engine) - - -@pytest.mark.parametrize("engine", ["polars", "bq", "bq-sqlglot"], indirect=True) -def test_engines_invert_op(scalars_array_value: array_value.ArrayValue, engine): - arr, _ = scalars_array_value.compute_values( - [ - ops.invert_op.as_expr(expression.deref("bytes_col")), - ops.invert_op.as_expr(expression.deref("bool_col")), - ] - ) - - assert_equivalence_execution(arr.node, REFERENCE_ENGINE, engine) - - -@pytest.mark.parametrize("engine", ["polars", "bq", "bq-sqlglot"], indirect=True) -def test_engines_isin_op(scalars_array_value: array_value.ArrayValue, engine): - arr, col_ids = scalars_array_value.compute_values( - [ - ops.IsInOp((1, 2, 3)).as_expr(expression.deref("int64_col")), - ops.IsInOp((None, 123456)).as_expr(expression.deref("int64_col")), - ops.IsInOp((None, 123456), match_nulls=False).as_expr( - expression.deref("int64_col") - ), - ops.IsInOp((1.0, 2.0, 3.0)).as_expr(expression.deref("int64_col")), - ops.IsInOp(("1.0", "2.0")).as_expr(expression.deref("int64_col")), - ops.IsInOp(("1.0", 2.5, 3)).as_expr(expression.deref("int64_col")), - ops.IsInOp(()).as_expr(expression.deref("int64_col")), - ops.IsInOp((1, 2, 3, None)).as_expr(expression.deref("float64_col")), - ] - ) - new_names = ( - "int in ints", - "int in ints w null", - "int in ints w null wo match nulls", - "int in floats", - "int in strings", - "int in mixed", - "int in empty", - "float in ints", - ) - arr = arr.rename_columns( - {old_name: new_names[i] for i, old_name in enumerate(col_ids)} - ) - - assert_equivalence_execution(arr.node, REFERENCE_ENGINE, engine) - - -@pytest.mark.parametrize("engine", ["polars", "bq", "bq-sqlglot"], indirect=True) -def test_engines_isin_op_nested_filter( - scalars_array_value: array_value.ArrayValue, engine -): - isin_clause = ops.IsInOp((1, 2, 3)).as_expr(expression.deref("int64_col")) - filter_clause = ops.invert_op.as_expr( - ops.or_op.as_expr( - expression.deref("bool_col"), ops.invert_op.as_expr(isin_clause) - ) - ) - arr = scalars_array_value.filter(filter_clause) - - assert_equivalence_execution(arr.node, REFERENCE_ENGINE, engine) - - -@pytest.mark.parametrize("engine", ["polars", "bq", "bq-sqlglot"], indirect=True) -def test_engines_getitem_ops(arrays_array_value: array_value.ArrayValue, engine): - arr, _ = arrays_array_value.compute_values( - [ - ops.GetItemOp(0).as_expr(expression.deref("float_list_col")), - ops.DynamicGetItemOp().as_expr( - expression.deref("float_list_col"), expression.const(0) - ), - ops.GetItemOp(0).as_expr(expression.deref("string_list_col")), - ops.DynamicGetItemOp().as_expr( - expression.deref("string_list_col"), expression.const(0) - ), - ] - ) - - assert_equivalence_execution(arr.node, REFERENCE_ENGINE, engine) diff --git a/tests/system/small/engines/test_googlesql_ops.py b/tests/system/small/engines/test_googlesql_ops.py deleted file mode 100644 index e47308fa355..00000000000 --- a/tests/system/small/engines/test_googlesql_ops.py +++ /dev/null @@ -1,36 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - - -import pytest - -import bigframes.operations.googlesql as gsql_ops -from bigframes.core import array_value -from bigframes.session import polars_executor -from bigframes.testing.engine_utils import assert_equivalence_execution - -polars = 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() - - -def test_engines_googlesql_st_area( - scalars_array_value: array_value.ArrayValue, bq_engine, sqlglot_engine -): - expr = gsql_ops.ST_AREA.as_expr("geography_col") - - arr, _ = scalars_array_value.compute_values([expr]) - - assert_equivalence_execution(arr.node, bq_engine, sqlglot_engine) diff --git a/tests/system/small/engines/test_join.py b/tests/system/small/engines/test_join.py deleted file mode 100644 index 15dbfabdac3..00000000000 --- a/tests/system/small/engines/test_join.py +++ /dev/null @@ -1,111 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from typing import Literal - -import pytest - -from bigframes import operations as ops -from bigframes.core import array_value, expression, ordering -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", "bq-sqlglot"], indirect=True) -@pytest.mark.parametrize("join_type", ["left", "inner", "right", "outer"]) -def test_engines_join_on_key( - scalars_array_value: array_value.ArrayValue, - engine, - join_type: Literal["inner", "outer", "left", "right"], -): - result, _ = scalars_array_value.relational_join( - scalars_array_value, conditions=(("int64_col", "int64_col"),), type=join_type - ) - - assert_equivalence_execution(result.node, REFERENCE_ENGINE, engine) - - -@pytest.mark.parametrize("engine", ["polars", "bq", "bq-sqlglot"], indirect=True) -@pytest.mark.parametrize("join_type", ["left", "inner", "right", "outer"]) -def test_engines_join_on_coerced_key( - scalars_array_value: array_value.ArrayValue, - engine, - join_type: Literal["inner", "outer", "left", "right"], -): - result, _ = scalars_array_value.relational_join( - scalars_array_value, conditions=(("int64_col", "float64_col"),), type=join_type - ) - - assert_equivalence_execution(result.node, REFERENCE_ENGINE, engine) - - -@pytest.mark.parametrize("engine", ["polars", "bq", "bq-sqlglot"], indirect=True) -@pytest.mark.parametrize("join_type", ["left", "inner", "right", "outer"]) -def test_engines_join_multi_key( - scalars_array_value: array_value.ArrayValue, - engine, - join_type: Literal["inner", "outer", "left", "right"], -): - l_input = scalars_array_value.order_by([ordering.ascending_over("float64_col")]) - l_input, l_join_cols = scalars_array_value.compute_values( - [ - ops.mod_op.as_expr("int64_col", expression.const(2)), - ops.invert_op.as_expr("bool_col"), - ] - ) - r_input, r_join_cols = scalars_array_value.compute_values( - [ops.mod_op.as_expr("int64_col", expression.const(3)), expression.const(True)] - ) - - conditions = tuple((l_col, r_col) for l_col, r_col in zip(l_join_cols, r_join_cols)) - - result, _ = l_input.relational_join(r_input, conditions=conditions, type=join_type) - - assert_equivalence_execution(result.node, REFERENCE_ENGINE, engine) - - -@pytest.mark.parametrize("engine", ["polars", "bq", "bq-sqlglot"], indirect=True) -def test_engines_cross_join( - scalars_array_value: array_value.ArrayValue, - engine, -): - result, _ = scalars_array_value.relational_join(scalars_array_value, type="cross") - - assert_equivalence_execution(result.node, REFERENCE_ENGINE, engine) - - -@pytest.mark.parametrize("engine", ["polars", "bq", "bq-sqlglot"], indirect=True) -@pytest.mark.parametrize( - ("left_key", "right_key"), - [ - ("int64_col", "float64_col"), - ("float64_col", "int64_col"), - ("int64_too", "int64_col"), - ], -) -def test_engines_isin( - scalars_array_value: array_value.ArrayValue, engine, left_key, right_key -): - other = scalars_array_value.select_columns([right_key]) - result, _ = scalars_array_value.isin( - other, - lcol=left_key, - ) - - assert_equivalence_execution(result.node, REFERENCE_ENGINE, engine) diff --git a/tests/system/small/engines/test_numeric_ops.py b/tests/system/small/engines/test_numeric_ops.py deleted file mode 100644 index c188e37370c..00000000000 --- a/tests/system/small/engines/test_numeric_ops.py +++ /dev/null @@ -1,211 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import datetime -import itertools - -import pytest - -import bigframes.operations as ops -from bigframes.core import array_value, expression -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() - - -def apply_op_pairwise( - array: array_value.ArrayValue, op: ops.BinaryOp, excluded_cols=[] -) -> array_value.ArrayValue: - exprs = [] - labels = [] - for l_arg, r_arg in itertools.product(array.column_ids, array.column_ids): - if (l_arg in excluded_cols) or (r_arg in excluded_cols): - continue - try: - _ = op.output_type( - array.get_column_type(l_arg), array.get_column_type(r_arg) - ) - expr = op.as_expr(l_arg, r_arg) - exprs.append(expr) - labels.append(f"{l_arg}_{r_arg}") - except TypeError: - continue - assert len(exprs) > 0 - new_arr, ids = array.compute_values(exprs) - new_arr = new_arr.rename_columns( - {new_col: label for new_col, label in zip(ids, labels)} - ) - return new_arr - - -def apply_op( - array: array_value.ArrayValue, op: ops.UnaryOp, excluded_cols=[] -) -> array_value.ArrayValue: - exprs = [] - labels = [] - for arg in array.column_ids: - if arg in excluded_cols: - continue - try: - _ = op.output_type(array.get_column_type(arg)) - expr = op.as_expr(arg) - exprs.append(expr) - labels.append(f"{arg}_{op.name}") - except TypeError: - continue - assert len(exprs) > 0 - new_arr, ids = array.compute_values(exprs) - new_arr = new_arr.rename_columns( - {new_col: label for new_col, label in zip(ids, labels)} - ) - return new_arr - - -@pytest.mark.parametrize("engine", ["polars", "bq"], indirect=True) -def test_engines_project_ceil( - scalars_array_value: array_value.ArrayValue, - engine, -): - arr = apply_op(scalars_array_value, ops.ceil_op) - assert_equivalence_execution(arr.node, REFERENCE_ENGINE, engine) - - -@pytest.mark.parametrize("engine", ["polars", "bq"], indirect=True) -def test_engines_project_floor( - scalars_array_value: array_value.ArrayValue, - engine, -): - arr = apply_op(scalars_array_value, ops.floor_op) - assert_equivalence_execution(arr.node, REFERENCE_ENGINE, engine) - - -@pytest.mark.parametrize("engine", ["polars", "bq", "bq-sqlglot"], indirect=True) -def test_engines_project_add( - scalars_array_value: array_value.ArrayValue, - engine, -): - arr = apply_op_pairwise(scalars_array_value, ops.add_op) - assert_equivalence_execution(arr.node, REFERENCE_ENGINE, engine) - - -@pytest.mark.parametrize("engine", ["polars", "bq", "bq-sqlglot"], indirect=True) -def test_engines_project_sub( - scalars_array_value: array_value.ArrayValue, - engine, -): - arr = apply_op_pairwise(scalars_array_value, ops.sub_op) - assert_equivalence_execution(arr.node, REFERENCE_ENGINE, engine) - - -@pytest.mark.parametrize("engine", ["polars", "bq", "bq-sqlglot"], indirect=True) -def test_engines_project_mul( - scalars_array_value: array_value.ArrayValue, - engine, -): - arr = apply_op_pairwise(scalars_array_value, ops.mul_op) - assert_equivalence_execution(arr.node, REFERENCE_ENGINE, engine) - - -@pytest.mark.parametrize("engine", ["polars", "bq", "bq-sqlglot"], indirect=True) -def test_engines_project_div(scalars_array_value: array_value.ArrayValue, engine): - # TODO: Duration div is sensitive to zeroes - # TODO: Numeric col is sensitive to scale shifts - arr = apply_op_pairwise( - scalars_array_value, ops.div_op, excluded_cols=["duration_col", "numeric_col"] - ) - assert_equivalence_execution(arr.node, REFERENCE_ENGINE, engine) - - -@pytest.mark.parametrize("engine", ["polars", "bq", "bq-sqlglot"], indirect=True) -def test_engines_project_div_durations( - scalars_array_value: array_value.ArrayValue, engine -): - arr, _ = scalars_array_value.compute_values( - [ - ops.div_op.as_expr( - expression.deref("duration_col"), - expression.const(datetime.timedelta(seconds=3)), - ), - ops.div_op.as_expr( - expression.deref("duration_col"), - expression.const(datetime.timedelta(seconds=-3)), - ), - ops.div_op.as_expr(expression.deref("duration_col"), expression.const(4)), - ops.div_op.as_expr(expression.deref("duration_col"), expression.const(-4)), - ops.div_op.as_expr( - expression.deref("duration_col"), expression.const(55.55) - ), - ops.div_op.as_expr( - expression.deref("duration_col"), expression.const(-55.55) - ), - ] - ) - assert_equivalence_execution(arr.node, REFERENCE_ENGINE, engine) - - -@pytest.mark.parametrize("engine", ["polars", "bq", "bq-sqlglot"], indirect=True) -def test_engines_project_floordiv( - scalars_array_value: array_value.ArrayValue, - engine, -): - arr = apply_op_pairwise( - scalars_array_value, - ops.floordiv_op, - excluded_cols=["duration_col", "numeric_col"], - ) - assert_equivalence_execution(arr.node, REFERENCE_ENGINE, engine) - - -@pytest.mark.parametrize("engine", ["polars", "bq", "bq-sqlglot"], indirect=True) -def test_engines_project_floordiv_durations( - scalars_array_value: array_value.ArrayValue, engine -): - arr, _ = scalars_array_value.compute_values( - [ - ops.floordiv_op.as_expr( - expression.deref("duration_col"), - expression.const(datetime.timedelta(seconds=3)), - ), - ops.floordiv_op.as_expr( - expression.deref("duration_col"), - expression.const(datetime.timedelta(seconds=-3)), - ), - ops.floordiv_op.as_expr( - expression.deref("duration_col"), expression.const(4) - ), - ops.floordiv_op.as_expr( - expression.deref("duration_col"), expression.const(-4) - ), - ops.floordiv_op.as_expr( - expression.deref("duration_col"), expression.const(55.55) - ), - ops.floordiv_op.as_expr( - expression.deref("duration_col"), expression.const(-55.55) - ), - ] - ) - assert_equivalence_execution(arr.node, REFERENCE_ENGINE, engine) - - -@pytest.mark.parametrize("engine", ["polars", "bq", "bq-sqlglot"], indirect=True) -def test_engines_project_mod( - scalars_array_value: array_value.ArrayValue, - engine, -): - arr = apply_op_pairwise(scalars_array_value, ops.mod_op) - assert_equivalence_execution(arr.node, REFERENCE_ENGINE, engine) diff --git a/tests/system/small/engines/test_read_local.py b/tests/system/small/engines/test_read_local.py deleted file mode 100644 index 257bddd9179..00000000000 --- a/tests/system/small/engines/test_read_local.py +++ /dev/null @@ -1,121 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import pytest - -import bigframes -from bigframes.core import identifiers, local_data, nodes -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() - - -def test_engines_read_local( - fake_session: bigframes.Session, - managed_data_source: local_data.ManagedArrowTable, - engine, -): - scan_list = nodes.ScanList.from_items( - nodes.ScanItem(identifiers.ColumnId(item.column), item.column) - for item in managed_data_source.schema.items - ) - local_node = nodes.ReadLocalNode( - managed_data_source, scan_list, fake_session, offsets_col=None - ) - assert_equivalence_execution(local_node, REFERENCE_ENGINE, engine) - - -def test_engines_read_local_w_offsets( - fake_session: bigframes.Session, - managed_data_source: local_data.ManagedArrowTable, - engine, -): - scan_list = nodes.ScanList.from_items( - nodes.ScanItem(identifiers.ColumnId(item.column), item.column) - for item in managed_data_source.schema.items - ) - local_node = nodes.ReadLocalNode( - managed_data_source, - scan_list, - fake_session, - offsets_col=identifiers.ColumnId("offsets"), - ) - assert_equivalence_execution(local_node, REFERENCE_ENGINE, engine) - - -def test_engines_read_local_w_col_subset( - fake_session: bigframes.Session, - managed_data_source: local_data.ManagedArrowTable, - engine, -): - scan_list = nodes.ScanList.from_items( - nodes.ScanItem(identifiers.ColumnId(item.column), item.column) - for item in managed_data_source.schema.items[::-2] - ) - local_node = nodes.ReadLocalNode( - managed_data_source, scan_list, fake_session, offsets_col=None - ) - assert_equivalence_execution(local_node, REFERENCE_ENGINE, engine) - - -def test_engines_read_local_w_zero_row_source( - fake_session: bigframes.Session, - zero_row_source: local_data.ManagedArrowTable, - engine, -): - scan_list = nodes.ScanList.from_items( - nodes.ScanItem(identifiers.ColumnId(item.column), item.column) - for item in zero_row_source.schema.items - ) - local_node = nodes.ReadLocalNode( - zero_row_source, scan_list, fake_session, offsets_col=None - ) - assert_equivalence_execution(local_node, REFERENCE_ENGINE, engine) - - -@pytest.mark.parametrize( - "engine", ["polars", "bq", "pyarrow", "bq-sqlglot"], indirect=True -) -def test_engines_read_local_w_nested_source( - fake_session: bigframes.Session, - nested_data_source: local_data.ManagedArrowTable, - engine, -): - scan_list = nodes.ScanList.from_items( - nodes.ScanItem(identifiers.ColumnId(item.column), item.column) - for item in nested_data_source.schema.items - ) - local_node = nodes.ReadLocalNode( - nested_data_source, scan_list, fake_session, offsets_col=None - ) - assert_equivalence_execution(local_node, REFERENCE_ENGINE, engine) - - -def test_engines_read_local_w_repeated_source( - fake_session: bigframes.Session, - repeated_data_source: local_data.ManagedArrowTable, - engine, -): - scan_list = nodes.ScanList.from_items( - nodes.ScanItem(identifiers.ColumnId(item.column), item.column) - for item in repeated_data_source.schema.items - ) - local_node = nodes.ReadLocalNode( - repeated_data_source, scan_list, fake_session, offsets_col=None - ) - assert_equivalence_execution(local_node, REFERENCE_ENGINE, engine) diff --git a/tests/system/small/engines/test_selection.py b/tests/system/small/engines/test_selection.py deleted file mode 100644 index 94c8a6463ca..00000000000 --- a/tests/system/small/engines/test_selection.py +++ /dev/null @@ -1,60 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import pytest - -from bigframes.core import array_value, expression, identifiers, nodes -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() - - -def test_engines_select_identity( - scalars_array_value: array_value.ArrayValue, - engine, -): - selection = tuple( - nodes.AliasedRef(expression.deref(col), identifiers.ColumnId(col)) - for col in scalars_array_value.column_ids - ) - node = nodes.SelectionNode(scalars_array_value.node, selection) - assert_equivalence_execution(node, REFERENCE_ENGINE, engine) - - -def test_engines_select_rename( - scalars_array_value: array_value.ArrayValue, - engine, -): - selection = tuple( - nodes.AliasedRef(expression.deref(col), identifiers.ColumnId(f"renamed_{col}")) - for col in scalars_array_value.column_ids - ) - node = nodes.SelectionNode(scalars_array_value.node, selection) - assert_equivalence_execution(node, REFERENCE_ENGINE, engine) - - -def test_engines_select_reorder_rename_drop( - scalars_array_value: array_value.ArrayValue, - engine, -): - selection = tuple( - nodes.AliasedRef(expression.deref(col), identifiers.ColumnId(f"renamed_{col}")) - for col in scalars_array_value.column_ids[::-2] - ) - node = nodes.SelectionNode(scalars_array_value.node, selection) - assert_equivalence_execution(node, REFERENCE_ENGINE, engine) diff --git a/tests/system/small/engines/test_slicing.py b/tests/system/small/engines/test_slicing.py deleted file mode 100644 index 022758893d2..00000000000 --- a/tests/system/small/engines/test_slicing.py +++ /dev/null @@ -1,56 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import pytest - -from bigframes.core import array_value, nodes -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", "bq-sqlglot"], indirect=True) -@pytest.mark.parametrize( - ("start", "stop", "step"), - [ - (1, None, None), - (None, 4, None), - (None, None, 2), - (None, 50_000_000_000, 1), - (5, 4, None), - (3, None, 2), - (1, 7, 2), - (1, 7, 50_000_000_000), - (-1, -7, -2), - (None, -7, -2), - (-1, None, -2), - (-7, -1, 2), - (-7, -1, None), - (-7, 7, None), - (7, -7, -2), - ], -) -def test_engines_slice( - scalars_array_value: array_value.ArrayValue, - engine, - start, - stop, - step, -): - node = nodes.SliceNode(scalars_array_value.node, start, stop, step) - assert_equivalence_execution(node, REFERENCE_ENGINE, engine) diff --git a/tests/system/small/engines/test_sorting.py b/tests/system/small/engines/test_sorting.py deleted file mode 100644 index cbb6215adae..00000000000 --- a/tests/system/small/engines/test_sorting.py +++ /dev/null @@ -1,108 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import asyncio - -import pytest - -import bigframes.operations as bf_ops -from bigframes.core import array_value, nodes, ordering -from bigframes.session import execution_spec, 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", "bq-sqlglot"], 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", "bq-sqlglot"], 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", "bq-sqlglot"], 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", "bq-sqlglot"], 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) - result = asyncio.run( - engine.execute(node, execution_spec.ExecutionSpec(ordered=True)) - ) - assert result is None - - -def apply_reverse(node: nodes.BigFrameNode) -> nodes.BigFrameNode: - return nodes.ReversedNode(node) diff --git a/tests/system/small/engines/test_strings.py b/tests/system/small/engines/test_strings.py deleted file mode 100644 index 32a8c4bcd78..00000000000 --- a/tests/system/small/engines/test_strings.py +++ /dev/null @@ -1,77 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import pytest - -import bigframes.operations as ops -from bigframes.core import array_value -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", "bq-sqlglot"], indirect=True) -def test_engines_str_contains(scalars_array_value: array_value.ArrayValue, engine): - arr, _ = scalars_array_value.compute_values( - [ - ops.StrContainsOp("(?i)hEllo").as_expr("string_col"), - ops.StrContainsOp("Hello").as_expr("string_col"), - ops.StrContainsOp("T").as_expr("string_col"), - ops.StrContainsOp(".*").as_expr("string_col"), - ] - ) - assert_equivalence_execution(arr.node, REFERENCE_ENGINE, engine) - - -@pytest.mark.parametrize("engine", ["polars", "bq", "bq-sqlglot"], indirect=True) -def test_engines_str_contains_regex( - scalars_array_value: array_value.ArrayValue, engine -): - arr, _ = scalars_array_value.compute_values( - [ - ops.StrContainsRegexOp("(?i)hEllo").as_expr("string_col"), - ops.StrContainsRegexOp("Hello").as_expr("string_col"), - ops.StrContainsRegexOp("T").as_expr("string_col"), - ops.StrContainsRegexOp(".*").as_expr("string_col"), - ] - ) - assert_equivalence_execution(arr.node, REFERENCE_ENGINE, engine) - - -@pytest.mark.parametrize("engine", ["polars", "bq", "bq-sqlglot"], indirect=True) -def test_engines_str_startswith(scalars_array_value: array_value.ArrayValue, engine): - arr, _ = scalars_array_value.compute_values( - [ - ops.StartsWithOp("He").as_expr("string_col"), - ops.StartsWithOp("llo").as_expr("string_col"), - ops.StartsWithOp(("He", "T", "ca")).as_expr("string_col"), - ] - ) - assert_equivalence_execution(arr.node, REFERENCE_ENGINE, engine) - - -@pytest.mark.parametrize("engine", ["polars", "bq", "bq-sqlglot"], indirect=True) -def test_engines_str_endswith(scalars_array_value: array_value.ArrayValue, engine): - arr, _ = scalars_array_value.compute_values( - [ - ops.EndsWithOp("!").as_expr("string_col"), - ops.EndsWithOp("llo").as_expr("string_col"), - ops.EndsWithOp(("He", "T", "ca")).as_expr("string_col"), - ] - ) - assert_equivalence_execution(arr.node, REFERENCE_ENGINE, engine) diff --git a/tests/system/small/engines/test_temporal_ops.py b/tests/system/small/engines/test_temporal_ops.py deleted file mode 100644 index 61b1b06b1f2..00000000000 --- a/tests/system/small/engines/test_temporal_ops.py +++ /dev/null @@ -1,113 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import asyncio - -import pandas as pd -import pytest - -import bigframes.operations as ops -from bigframes import dtypes -from bigframes.core import array_value -from bigframes.core import expression as ex -from bigframes.session import polars_executor -from bigframes.testing.engine_utils import SPEC, 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", "bq-sqlglot"], indirect=True) -def test_engines_dt_floor(scalars_array_value: array_value.ArrayValue, engine): - arr, _ = scalars_array_value.compute_values( - [ - ops.FloorDtOp("us").as_expr("timestamp_col"), - ops.FloorDtOp("ms").as_expr("timestamp_col"), - ops.FloorDtOp("s").as_expr("timestamp_col"), - ops.FloorDtOp("min").as_expr("timestamp_col"), - ops.FloorDtOp("h").as_expr("timestamp_col"), - ops.FloorDtOp("D").as_expr("timestamp_col"), - ops.FloorDtOp("W").as_expr("timestamp_col"), - ops.FloorDtOp("M").as_expr("timestamp_col"), - ops.FloorDtOp("Q").as_expr("timestamp_col"), - ops.FloorDtOp("Y").as_expr("timestamp_col"), - ops.FloorDtOp("Q").as_expr("datetime_col"), - ops.FloorDtOp("us").as_expr("datetime_col"), - ] - ) - assert_equivalence_execution(arr.node, REFERENCE_ENGINE, engine) - - -@pytest.mark.parametrize("engine", ["polars", "bq", "bq-sqlglot"], indirect=True) -def test_engines_date_accessors(scalars_array_value: array_value.ArrayValue, engine): - datelike_cols = ["datetime_col", "timestamp_col", "date_col"] - accessors = [ - ops.day_op, - ops.dayofweek_op, - ops.month_op, - ops.quarter_op, - ops.year_op, - ops.iso_day_op, - ops.iso_week_op, - ops.iso_year_op, - ] - - exprs = [acc.as_expr(col) for acc in accessors for col in datelike_cols] - - arr, _ = scalars_array_value.compute_values(exprs) - assert_equivalence_execution(arr.node, REFERENCE_ENGINE, engine) - - -@pytest.mark.parametrize("engine", ["bq", "bq-sqlglot"], indirect=True) -def test_engines_temporal_arithmetic( - scalars_array_value: array_value.ArrayValue, engine -): - exprs = [ - ops.timestamp_add_op.as_expr( - "timestamp_col", ex.const(pd.Timedelta(seconds=1), dtypes.TIMEDELTA_DTYPE) - ), - ops.timestamp_sub_op.as_expr( - "timestamp_col", ex.const(pd.Timedelta(seconds=1), dtypes.TIMEDELTA_DTYPE) - ), - ops.date_add_op.as_expr( - "date_col", ex.const(pd.Timedelta(days=1), dtypes.TIMEDELTA_DTYPE) - ), - ops.date_sub_op.as_expr( - "date_col", ex.const(pd.Timedelta(days=1), dtypes.TIMEDELTA_DTYPE) - ), - ops.timestamp_diff_op.as_expr("timestamp_col", "timestamp_col"), - ops.date_diff_op.as_expr("date_col", "date_col"), - ] - - arr, _ = scalars_array_value.compute_values(exprs) - res = asyncio.run(engine.execute(arr.node, SPEC)) - assert res is not None - assert len(res.batches().to_pandas()) > 0 - - -@pytest.mark.parametrize("engine", ["bq", "bq-sqlglot"], indirect=True) -def test_engines_to_datetime(scalars_array_value: array_value.ArrayValue, engine): - exprs = [ - ops.ToDatetimeOp().as_expr("timestamp_col"), - ] - arr, _ = scalars_array_value.compute_values(exprs) - res = asyncio.run(engine.execute(arr.node, SPEC)) - assert res is not None - df = res.batches().to_pandas() - # The input timestamp was: TIMESTAMP('2021-07-21T17:43:43.945289+00:00') - # The output should be naive DATETIME('2021-07-21T17:43:43.945289') - val = df.iloc[0, -1] - assert pd.Timestamp(val) == pd.Timestamp("2021-07-21T17:43:43.945289") diff --git a/tests/system/small/engines/test_windowing.py b/tests/system/small/engines/test_windowing.py deleted file mode 100644 index 8235fe0ef6b..00000000000 --- a/tests/system/small/engines/test_windowing.py +++ /dev/null @@ -1,64 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import pytest - -import bigframes.operations.aggregations as agg_ops -from bigframes.core import ( - agg_expressions, - array_value, - expression, - identifiers, - nodes, - window_spec, -) -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", "bq-sqlglot"], indirect=True) -def test_engines_with_offsets( - scalars_array_value: array_value.ArrayValue, - engine, -): - result, _ = scalars_array_value.promote_offsets() - assert_equivalence_execution(result.node, REFERENCE_ENGINE, engine) - - -@pytest.mark.parametrize("agg_op", [agg_ops.sum_op, agg_ops.count_op]) -def test_engines_with_rows_window( - scalars_array_value: array_value.ArrayValue, - agg_op, - bq_engine, - sqlglot_engine, -): - window = window_spec.WindowSpec( - bounds=window_spec.RowsWindowBounds.from_window_size(3, "left"), - ) - window_node = nodes.WindowOpNode( - child=scalars_array_value.node, - agg_exprs=( - nodes.ColumnDef( - agg_expressions.UnaryAggregation(agg_op, expression.deref("int64_too")), - identifiers.ColumnId("agg_int64"), - ), - ), - window_spec=window, - ) - assert_equivalence_execution(window_node, bq_engine, sqlglot_engine) diff --git a/tests/system/small/functions/__init__.py b/tests/system/small/functions/__init__.py deleted file mode 100644 index 0a2669d7a25..00000000000 --- a/tests/system/small/functions/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. diff --git a/tests/system/small/functions/test_remote_function.py b/tests/system/small/functions/test_remote_function.py deleted file mode 100644 index 869b26ca38c..00000000000 --- a/tests/system/small/functions/test_remote_function.py +++ /dev/null @@ -1,1616 +0,0 @@ -# Copyright 2023 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 inspect -import re -import textwrap -from typing import Sequence - -import bigframes_vendored.constants as constants -import google.api_core.exceptions -import pandas -import pandas as pd -import pyarrow -import pytest -import test_utils.prefixer -from google.cloud import bigquery - -import bigframes -import bigframes.clients -import bigframes.core.events -import bigframes.dtypes -import bigframes.exceptions -import bigframes.session._io.bigquery -from bigframes.functions import _utils as bff_utils -from bigframes.functions import function as bff -from bigframes.testing.utils import assert_frame_equal, assert_series_equal - -_prefixer = test_utils.prefixer.Prefixer("bigframes", "") - - -def get_function_name(func, package_requirements=None, is_row_processor=False): - """Get a bigframes function name for testing given a udf.""" - # Augment user package requirements with any internal package - # requirements. - package_requirements = bff_utils.get_updated_package_requirements( - package_requirements or [], is_row_processor - ) - - # Compute a unique hash representing the user code. - function_hash = bff_utils.get_hash(func, package_requirements) - - return f"bigframes_{function_hash}" - - -@pytest.fixture(scope="module") -def bq_cf_connection() -> str: - """Pre-created BQ connection in the test project in US location, used to - invoke cloud function. - - $ bq show --connection --location=us --project_id=PROJECT_ID bigframes-rf-conn - """ - return "bigframes-rf-conn" - - -@pytest.fixture(scope="module") -def bq_cf_connection_location() -> str: - """Pre-created BQ connection in the test project in US location, in format - PROJECT_ID.LOCATION.CONNECTION_NAME, used to invoke cloud function. - - $ bq show --connection --location=us --project_id=PROJECT_ID bigframes-rf-conn - """ - return "us.bigframes-rf-conn" - - -@pytest.fixture(scope="module") -def bq_cf_connection_location_mismatched() -> str: - """Pre-created BQ connection in the test project in EU location, in format - LOCATION.CONNECTION_NAME, used to invoke cloud function. - - $ bq show --connection --location=us --project_id=PROJECT_ID bigframes-rf-conn - """ - return "eu.bigframes-rf-conn" - - -@pytest.fixture(scope="module") -def bq_cf_connection_location_project(bigquery_client) -> str: - """Pre-created BQ connection in the test project in US location, in format - PROJECT_ID.LOCATION.CONNECTION_NAME, used to invoke cloud function. - - $ bq show --connection --location=us --project_id=PROJECT_ID bigframes-rf-conn - """ - return f"{bigquery_client.project}.us.bigframes-rf-conn" - - -@pytest.fixture(scope="module") -def bq_cf_connection_location_project_mismatched() -> str: - """Pre-created BQ connection in the bigframes-metrics project in US location, - in format PROJECT_ID.LOCATION.CONNECTION_NAME, used to invoke cloud function. - - $ bq show --connection --location=us --project_id=PROJECT_ID bigframes-rf-conn - """ - return "bigframes-metrics.eu.bigframes-rf-conn" - - -@pytest.fixture(scope="module") -def session_with_bq_connection(bq_cf_connection) -> bigframes.Session: - session = bigframes.Session( - bigframes.BigQueryOptions(bq_connection=bq_cf_connection, location="US") - ) - return session - - -def get_bq_connection_id_path_format(connection_id_dot_format): - fields = connection_id_dot_format.split(".") - return f"projects/{fields[0]}/locations/{fields[1]}/connections/{fields[2]}" - - -@pytest.mark.flaky(retries=2, delay=120) -def test_remote_function_direct_no_session_param( - scalars_dfs, - dataset_id_permanent, - bq_cf_connection, -): - def square(x): - return x * x - - square = bff.remote_function( - input_types=int, - output_type=int, - dataset=dataset_id_permanent, - bigquery_connection=bq_cf_connection, - # See e2e tests for tests that actually deploy the Cloud Function. - reuse=True, - name=get_function_name(square), - cloud_function_service_account="default", - )(square) - - # Function should still work normally. - assert square(2) == 4 - - # Function should have extra metadata attached for remote execution. - assert hasattr(square, "bigframes_remote_function") - assert hasattr(square, "bigframes_bigquery_function") - assert hasattr(square, "bigframes_cloud_function") - - scalars_df, scalars_pandas_df = scalars_dfs - - bf_int64_col = scalars_df["int64_col"] - bf_int64_col_filter = bf_int64_col.notnull() - bf_int64_col_filtered = bf_int64_col[bf_int64_col_filter] - bf_result_col = bf_int64_col_filtered.apply(square) - bf_result = ( - bf_int64_col_filtered.to_frame().assign(result=bf_result_col).to_pandas() - ) - - pd_int64_col = scalars_pandas_df["int64_col"] - pd_int64_col_filter = pd_int64_col.notnull() - pd_int64_col_filtered = pd_int64_col[pd_int64_col_filter] - pd_result_col = pd_int64_col_filtered.apply(lambda x: x * x) - # TODO(shobs): Figure why pandas .apply() changes the dtype, i.e. - # pd_int64_col_filtered.dtype is Int64Dtype() - # pd_int64_col_filtered.apply(lambda x: x * x).dtype is int64. - # For this test let's force the pandas dtype to be same as bigframes' dtype. - pd_result_col = pd_result_col.astype(pd.Int64Dtype()) - pd_result = pd_int64_col_filtered.to_frame().assign(result=pd_result_col) - - assert_frame_equal(bf_result, pd_result) - - -@pytest.mark.flaky(retries=2, delay=120) -def test_remote_function_connection_w_location( - session, - scalars_dfs, - dataset_id_permanent, - bq_cf_connection_location, -): - def square(x): - return x * x - - square = session.remote_function( - input_types=int, - output_type=int, - dataset=dataset_id_permanent, - bigquery_connection=bq_cf_connection_location, - # See e2e tests for tests that actually deploy the Cloud Function. - reuse=True, - name=get_function_name(square), - cloud_function_service_account="default", - )(square) - - # Function should still work normally. - assert square(2) == 4 - - scalars_df, scalars_pandas_df = scalars_dfs - - bf_int64_col = scalars_df["int64_col"] - bf_int64_col_filter = bf_int64_col.notnull() - bf_int64_col_filtered = bf_int64_col[bf_int64_col_filter] - bf_result_col = bf_int64_col_filtered.apply(square) - bf_result = ( - bf_int64_col_filtered.to_frame().assign(result=bf_result_col).to_pandas() - ) - - pd_int64_col = scalars_pandas_df["int64_col"] - pd_int64_col_filter = pd_int64_col.notnull() - pd_int64_col_filtered = pd_int64_col[pd_int64_col_filter] - pd_result_col = pd_int64_col_filtered.apply(lambda x: x * x) - # TODO(shobs): Figure why pandas .apply() changes the dtype, i.e. - # pd_int64_col_filtered.dtype is Int64Dtype() - # pd_int64_col_filtered.apply(lambda x: x * x).dtype is int64. - # For this test let's force the pandas dtype to be same as bigframes' dtype. - pd_result_col = pd_result_col.astype(pd.Int64Dtype()) - pd_result = pd_int64_col_filtered.to_frame().assign(result=pd_result_col) - - assert_frame_equal(bf_result, pd_result) - - -@pytest.mark.flaky(retries=2, delay=120) -def test_remote_function_connection_w_location_mismatched( - session, - dataset_id_permanent, - bq_cf_connection_location_mismatched, -): - def square(x): - # Not expected to reach this code, as the location of the - # connection doesn't match the location of the dataset. - return x * x # pragma: NO COVER - - bq_cf_connection_location_mismatched_path_fmt = get_bq_connection_id_path_format( - bigframes.clients.get_canonical_bq_connection_id( - bq_cf_connection_location_mismatched, - session.bqclient.project, - session._location, - ) - ) - connection_ids = [ - bq_cf_connection_location_mismatched, - bq_cf_connection_location_mismatched_path_fmt, - ] - - for connection_id in connection_ids: - with pytest.raises( - ValueError, - match=re.escape( - "The location does not match BigQuery connection location:" - ), - ): - session.remote_function( - input_types=int, - output_type=int, - dataset=dataset_id_permanent, - bigquery_connection=connection_id, - # See e2e tests for tests that actually deploy the Cloud Function. - reuse=True, - name=get_function_name(square), - cloud_function_service_account="default", - )(square) - - -@pytest.mark.flaky(retries=2, delay=120) -def test_remote_function_connection_w_location_project( - session, - scalars_dfs, - dataset_id_permanent, - bq_cf_connection_location_project, -): - def square(x): - return x * x - - square = session.remote_function( - input_types=int, - output_type=int, - dataset=dataset_id_permanent, - bigquery_connection=bq_cf_connection_location_project, - # See e2e tests for tests that actually deploy the Cloud Function. - reuse=True, - name=get_function_name(square), - cloud_function_service_account="default", - )(square) - - # Function should still work normally. - assert square(2) == 4 - - scalars_df, scalars_pandas_df = scalars_dfs - - bf_int64_col = scalars_df["int64_col"] - bf_int64_col_filter = bf_int64_col.notnull() - bf_int64_col_filtered = bf_int64_col[bf_int64_col_filter] - bf_result_col = bf_int64_col_filtered.apply(square) - bf_result = ( - bf_int64_col_filtered.to_frame().assign(result=bf_result_col).to_pandas() - ) - - pd_int64_col = scalars_pandas_df["int64_col"] - pd_int64_col_filter = pd_int64_col.notnull() - pd_int64_col_filtered = pd_int64_col[pd_int64_col_filter] - pd_result_col = pd_int64_col_filtered.apply(lambda x: x * x) - # TODO(shobs): Figure why pandas .apply() changes the dtype, i.e. - # pd_int64_col_filtered.dtype is Int64Dtype() - # pd_int64_col_filtered.apply(lambda x: x * x).dtype is int64. - # For this test let's force the pandas dtype to be same as bigframes' dtype. - pd_result_col = pd_result_col.astype(pd.Int64Dtype()) - pd_result = pd_int64_col_filtered.to_frame().assign(result=pd_result_col) - - assert_frame_equal(bf_result, pd_result) - - -@pytest.mark.flaky(retries=2, delay=120) -def test_remote_function_connection_w_project_mismatched( - session, - dataset_id_permanent, - bq_cf_connection_location_project_mismatched, -): - def square(x): - # Not expected to reach this code, as the project of the - # connection doesn't match the project of the dataset. - return x * x # pragma: NO COVER - - bq_cf_connection_location_project_mismatched_path_fmt = ( - get_bq_connection_id_path_format( - bigframes.clients.get_canonical_bq_connection_id( - bq_cf_connection_location_project_mismatched, - session.bqclient.project, - session._location, - ) - ) - ) - connection_ids = [ - bq_cf_connection_location_project_mismatched, - bq_cf_connection_location_project_mismatched_path_fmt, - ] - - for connection_id in connection_ids: - with pytest.raises( - ValueError, - match=re.escape( - "The project_id does not match BigQuery connection gcp_project_id:" - ), - ): - session.remote_function( - input_types=int, - output_type=int, - dataset=dataset_id_permanent, - bigquery_connection=connection_id, - # See e2e tests for tests that actually deploy the Cloud Function. - reuse=True, - name=get_function_name(square), - cloud_function_service_account="default", - )(square) - - -@pytest.mark.flaky(retries=2, delay=120) -def test_remote_function_via_session_default( - session_with_bq_connection, scalars_dfs, dataset_id_permanent -): - def square(x): - return x * x - - # Session has bigquery connection initialized via context. Without an - # explicit dataset the default dataset from the session would be used. - # Without an explicit bigquery connection, the one present in Session set - # through the explicit BigQueryOptions would be used. Without an explicit `reuse` - # the default behavior of reuse=True will take effect. Please note that the - # udf is same as the one used in other tests in this file so the underlying - # cloud function would be common and quickly reused. - square = session_with_bq_connection.remote_function( - input_types=int, - output_type=int, - dataset=dataset_id_permanent, - name=get_function_name(square), - cloud_function_service_account="default", - )(square) - - # Function should still work normally. - assert square(2) == 4 - - scalars_df, scalars_pandas_df = scalars_dfs - - bf_int64_col = scalars_df["int64_col"] - bf_int64_col_filter = bf_int64_col.notnull() - bf_int64_col_filtered = bf_int64_col[bf_int64_col_filter] - bf_result_col = bf_int64_col_filtered.apply(square) - bf_result = ( - bf_int64_col_filtered.to_frame().assign(result=bf_result_col).to_pandas() - ) - - pd_int64_col = scalars_pandas_df["int64_col"] - pd_int64_col_filter = pd_int64_col.notnull() - pd_int64_col_filtered = pd_int64_col[pd_int64_col_filter] - pd_result_col = pd_int64_col_filtered.apply(lambda x: x * x) - # TODO(shobs): Figure why pandas .apply() changes the dtype, i.e. - # pd_int64_col_filtered.dtype is Int64Dtype() - # pd_int64_col_filtered.apply(lambda x: x * x).dtype is int64. - # For this test let's force the pandas dtype to be same as bigframes' dtype. - pd_result_col = pd_result_col.astype(pd.Int64Dtype()) - pd_result = pd_int64_col_filtered.to_frame().assign(result=pd_result_col) - - assert_frame_equal(bf_result, pd_result) - - -@pytest.mark.flaky(retries=2, delay=120) -def test_remote_function_via_session_with_overrides( - session, scalars_dfs, dataset_id_permanent, bq_cf_connection -): - def square(x): - return x * x - - square = session.remote_function( - input_types=int, - output_type=int, - dataset=dataset_id_permanent, - bigquery_connection=bq_cf_connection, - # See e2e tests for tests that actually deploy the Cloud Function. - reuse=True, - name=get_function_name(square), - cloud_function_service_account="default", - )(square) - - # Function should still work normally. - assert square(2) == 4 - - scalars_df, scalars_pandas_df = scalars_dfs - - bf_int64_col = scalars_df["int64_col"] - bf_int64_col_filter = bf_int64_col.notnull() - bf_int64_col_filtered = bf_int64_col[bf_int64_col_filter] - bf_result_col = bf_int64_col_filtered.apply(square) - bf_result = ( - bf_int64_col_filtered.to_frame().assign(result=bf_result_col).to_pandas() - ) - - pd_int64_col = scalars_pandas_df["int64_col"] - pd_int64_col_filter = pd_int64_col.notnull() - pd_int64_col_filtered = pd_int64_col[pd_int64_col_filter] - pd_result_col = pd_int64_col_filtered.apply(lambda x: x * x) - # TODO(shobs): Figure why pandas .apply() changes the dtype, i.e. - # pd_int64_col_filtered.dtype is Int64Dtype() - # pd_int64_col_filtered.apply(lambda x: x * x).dtype is int64. - # For this test let's force the pandas dtype to be same as bigframes' dtype. - pd_result_col = pd_result_col.astype(pd.Int64Dtype()) - pd_result = pd_int64_col_filtered.to_frame().assign(result=pd_result_col) - - assert_frame_equal(bf_result, pd_result) - - -@pytest.mark.flaky(retries=2, delay=120) -def test_dataframe_applymap( - session_with_bq_connection, scalars_dfs, dataset_id_permanent -): - def add_one(x): - return x + 1 - - remote_add_one = session_with_bq_connection.remote_function( - input_types=[int], - output_type=int, - dataset=dataset_id_permanent, - name=get_function_name(add_one), - cloud_function_service_account="default", - )(add_one) - - scalars_df, scalars_pandas_df = scalars_dfs - int64_cols = ["int64_col", "int64_too"] - - bf_int64_df = scalars_df[int64_cols] - bf_int64_df_filtered = bf_int64_df.dropna() - bf_result = bf_int64_df_filtered.applymap(remote_add_one).to_pandas() - - pd_int64_df = scalars_pandas_df[int64_cols] - pd_int64_df_filtered = pd_int64_df.dropna() - - # TODO(swast): Remove when pandas 2.1.x+ is the minimum supported. - if hasattr(pd_int64_df_filtered, "map"): - pd_result = pd_int64_df_filtered.map(add_one) - else: - pd_result = pd_int64_df_filtered.applymap(add_one) - # TODO(shobs): Figure why pandas .applymap() changes the dtype, i.e. - # pd_int64_df_filtered.dtype is Int64Dtype() - # pd_int64_df_filtered.applymap(lambda x: x).dtype is int64. - # For this test let's force the pandas dtype to be same as input. - for col in pd_result: - pd_result[col] = pd_result[col].astype(pd_int64_df_filtered[col].dtype) - - assert_frame_equal(bf_result, pd_result) - - -@pytest.mark.flaky(retries=2, delay=120) -def test_dataframe_applymap_explicit_filter( - session_with_bq_connection, scalars_dfs, dataset_id_permanent -): - def add_one(x): - return x + 1 - - remote_add_one = session_with_bq_connection.remote_function( - input_types=[int], - output_type=int, - dataset=dataset_id_permanent, - name=get_function_name(add_one), - cloud_function_service_account="default", - )(add_one) - - scalars_df, scalars_pandas_df = scalars_dfs - int64_cols = ["int64_col", "int64_too"] - - bf_int64_df = scalars_df[int64_cols] - bf_int64_df_filtered = bf_int64_df[bf_int64_df["int64_col"].notnull()] - bf_result = bf_int64_df_filtered.applymap(remote_add_one).to_pandas() - - pd_int64_df = scalars_pandas_df[int64_cols] - pd_int64_df_filtered = pd_int64_df[pd_int64_df["int64_col"].notnull()] - - # TODO(swast): Remove when pandas 2.1.x+ is the minimum supported. - if hasattr(pd_int64_df_filtered, "map"): - pd_result = pd_int64_df_filtered.map(add_one) - else: - pd_result = pd_int64_df_filtered.applymap(add_one) - - # TODO(shobs): Figure why pandas .applymap() changes the dtype, i.e. - # pd_int64_df_filtered.dtype is Int64Dtype() - # pd_int64_df_filtered.applymap(lambda x: x).dtype is int64. - # For this test let's force the pandas dtype to be same as input. - for col in pd_result: - pd_result[col] = pd_result[col].astype(pd_int64_df_filtered[col].dtype) - - assert_frame_equal(bf_result, pd_result) - - -@pytest.mark.flaky(retries=2, delay=120) -def test_dataframe_applymap_na_ignore( - session_with_bq_connection, scalars_dfs, dataset_id_permanent -): - def add_one(x): - return x + 1 - - remote_add_one = session_with_bq_connection.remote_function( - input_types=[int], - output_type=int, - dataset=dataset_id_permanent, - name=get_function_name(add_one), - cloud_function_service_account="default", - )(add_one) - - scalars_df, scalars_pandas_df = scalars_dfs - int64_cols = ["int64_col", "int64_too"] - - bf_int64_df = scalars_df[int64_cols] - bf_result = bf_int64_df.applymap(remote_add_one, na_action="ignore").to_pandas() - - pd_int64_df = scalars_pandas_df[int64_cols] - - # TODO(swast): Remove when pandas 2.1.x+ is the minimum supported. - if hasattr(pd_int64_df, "map"): - pd_result = pd_int64_df.map(add_one, na_action="ignore") - else: - pd_result = pd_int64_df.applymap(add_one, na_action="ignore") - - # TODO(shobs): Figure why pandas .applymap() changes the dtype, i.e. - # pd_int64_df_filtered.dtype is Int64Dtype() - # pd_int64_df_filtered.applymap(lambda x: x).dtype is int64. - # For this test let's force the pandas dtype to be same as input. - for col in pd_result: - pd_result[col] = pd_result[col].astype(pd_int64_df[col].dtype) - - assert_frame_equal(bf_result, pd_result) - - -@pytest.mark.flaky(retries=2, delay=120) -def test_series_map_bytes( - session_with_bq_connection, scalars_dfs, dataset_id_permanent -): - """Check that bytes is support as input and output.""" - scalars_df, scalars_pandas_df = scalars_dfs - - def bytes_to_hex(mybytes: bytes) -> bytes: - import pandas - - return mybytes.hex().encode("utf-8") if pandas.notna(mybytes) else None # type: ignore - - # TODO(b/345516010): the type: ignore is because "Optional" not yet - # supported as a type annotation in @remote_function(). - assert bytes_to_hex(None) is None # type: ignore - assert bytes_to_hex(b"\x00\xdd\xba\x11") == b"00ddba11" - pd_result = scalars_pandas_df.bytes_col.map(bytes_to_hex).astype( - pd.ArrowDtype(pyarrow.binary()) - ) - - packages = ["pandas"] - remote_bytes_to_hex = session_with_bq_connection.remote_function( - dataset=dataset_id_permanent, - name=get_function_name(bytes_to_hex, package_requirements=packages), - packages=packages, - cloud_function_service_account="default", - )(bytes_to_hex) - bf_result = scalars_df.bytes_col.map(remote_bytes_to_hex).to_pandas() - - assert_series_equal( - bf_result, - pd_result, - ) - - -def test_skip_bq_connection_check(dataset_id_permanent): - connection_name = "connection_does_not_exist" - session = bigframes.Session( - context=bigframes.BigQueryOptions( - bq_connection=connection_name, skip_bq_connection_check=True - ) - ) - - # Make sure that the connection does not exist - with pytest.raises(google.api_core.exceptions.NotFound): - session.bqconnectionclient.get_connection( - name=session.bqconnectionclient.connection_path( - session._project, session._location, connection_name - ) - ) - - # Make sure that an attempt to create a remote function routine with - # non-existent connection would result in an exception thrown by the BQ - # service. - # This is different from the exception throw by the BQ Connection service - # if it was not able to create the connection because of lack of permission - # when skip_bq_connection_check was not set to True: - # google.api_core.exceptions.PermissionDenied: 403 Permission 'resourcemanager.projects.setIamPolicy' denied on resource - with pytest.raises( - google.api_core.exceptions.NotFound, - match=f"Not found: Connection {connection_name}", - ): - - def add_one(x): - # Not expected to reach this code, as the connection doesn't exist. - return x + 1 # pragma: NO COVER - - session.remote_function( - input_types=[int], - output_type=int, - dataset=dataset_id_permanent, - name=get_function_name(add_one), - cloud_function_service_account="default", - )(add_one) - - -def test_read_gbq_function_detects_invalid_function(session, dataset_id): - dataset_ref = bigquery.DatasetReference.from_string(dataset_id) - with pytest.raises(ValueError) as e: - session.read_gbq_function( - str(dataset_ref.routine("not_a_function")), - ) - - assert "Unknown function" in str(e.value) - - -@pytest.mark.flaky(retries=2, delay=120) -def test_read_gbq_function_like_original( - session, - scalars_df_index, - dataset_id_permanent, - bq_cf_connection, -): - def square1(x): - return x * x - - square1 = bff.remote_function( - input_types=[int], - output_type=int, - dataset=dataset_id_permanent, - bigquery_connection=bq_cf_connection, - reuse=True, - name=get_function_name(square1), - cloud_function_service_account="default", - )(square1) - - # Function should still work normally. - assert square1(2) == 4 - - square2 = session.read_gbq_function( - function_name=square1.bigframes_bigquery_function, # type: ignore - ) - - # The newly-created function (square1) should have a remote function AND a - # cloud function associated with it, while the read-back version (square2) - # should only have a remote function. - assert square1.bigframes_remote_function # type: ignore - assert square1.bigframes_bigquery_function # type: ignore - assert square1.bigframes_cloud_function # type: ignore - - assert square2.bigframes_remote_function - assert square2.bigframes_bigquery_function - assert square2.bigframes_cloud_function is None - - # They should point to the same function. - assert square1.bigframes_remote_function == square2.bigframes_remote_function # type: ignore - assert square1.bigframes_bigquery_function == square2.bigframes_bigquery_function # type: ignore - assert square2.bigframes_remote_function == square2.bigframes_bigquery_function # type: ignore - - # The result of applying them should be the same. - int64_col = scalars_df_index["int64_col"] - int64_col_filter = int64_col.notnull() - int64_col_filtered = int64_col[int64_col_filter] - - s1_result_col = int64_col_filtered.apply(square1) - s1_result = int64_col_filtered.to_frame().assign(result=s1_result_col) - - s2_result_col = int64_col_filtered.apply(square2) - s2_result = int64_col_filtered.to_frame().assign(result=s2_result_col) - - assert_frame_equal(s1_result.to_pandas(), s2_result.to_pandas()) - - -def test_read_gbq_function_runs_existing_udf(session): - func = session.read_gbq_function("bqutil.fn.cw_lower_case_ascii_only") - got = func("AURÉLIE") - assert got == "aurÉlie" - - -def test_read_gbq_function_runs_existing_udf_4_params(session): - func = session.read_gbq_function("bqutil.fn.cw_instr4") - got = func("TestStr123456Str", "Str", 1, 2) - assert got == 14 - - -def test_read_gbq_function_runs_existing_udf_array_output(session, routine_id_unique): - bigframes.session._io.bigquery.start_query_with_job( - session.bqclient, - textwrap.dedent( - f""" - CREATE OR REPLACE FUNCTION `{routine_id_unique}`(x STRING) - RETURNS ARRAY - AS ( - [x, x] - ) - """ - ), - job_config=bigquery.QueryJobConfig(), - location=None, - project=None, - timeout=None, - metrics=None, - publisher=bigframes.core.events.Publisher(), - ) - func = session.read_gbq_function(routine_id_unique) - - # Test on scalar value - got = func("hello") - assert got == ["hello", "hello"] - - # Test on a series, assert pandas parity - pd_s = pd.Series(["alpha", "beta", "gamma"]) - bf_s = session.read_pandas(pd_s) - pd_result = pd_s.apply(func) - bf_result = bf_s.apply(func) - assert bigframes.dtypes.is_array_string_like(bf_result.dtype) - assert_series_equal( - pd_result, bf_result.to_pandas(), check_dtype=False, check_index_type=False - ) - - -def test_read_gbq_function_runs_existing_udf_2_params_array_output( - session, routine_id_unique -): - bigframes.session._io.bigquery.start_query_with_job( - session.bqclient, - textwrap.dedent( - f""" - CREATE OR REPLACE FUNCTION `{routine_id_unique}`(x STRING, y STRING) - RETURNS ARRAY - AS ( - [x, y] - ) - """ - ), - job_config=bigquery.QueryJobConfig(), - location=None, - project=None, - timeout=None, - metrics=None, - publisher=bigframes.core.events.Publisher(), - ) - func = session.read_gbq_function(routine_id_unique) - - # Test on scalar value - got = func("hello", "world") - assert got == ["hello", "world"] - - # Test on series, assert pandas parity - pd_df = pd.DataFrame( - {"col0": ["alpha", "beta", "gamma"], "col1": ["delta", "theta", "phi"]} - ) - bf_df = session.read_pandas(pd_df) - pd_result = pd_df["col0"].combine(pd_df["col1"], func) - bf_result = bf_df["col0"].combine(bf_df["col1"], func) - assert bigframes.dtypes.is_array_string_like(bf_result.dtype) - assert_series_equal( - pd_result, bf_result.to_pandas(), check_dtype=False, check_index_type=False - ) - - -def test_read_gbq_function_runs_existing_udf_4_params_array_output( - session, routine_id_unique -): - bigframes.session._io.bigquery.start_query_with_job( - session.bqclient, - textwrap.dedent( - f""" - CREATE OR REPLACE FUNCTION `{routine_id_unique}`(x STRING, y BOOL, z INT64, w FLOAT64) - RETURNS ARRAY - AS ( - [x, CAST(y AS STRING), CAST(z AS STRING), CAST(w AS STRING)] - ) - """ - ), - job_config=bigquery.QueryJobConfig(), - location=None, - project=None, - timeout=None, - metrics=None, - publisher=bigframes.core.events.Publisher(), - ) - func = session.read_gbq_function(routine_id_unique) - - # Test on scalar value - got = func("hello", True, 1, 2.3) - assert got == ["hello", "true", "1", "2.3"] - - # Test on a dataframe, assert pandas parity - pd_df = pd.DataFrame( - { - "col0": ["alpha", "beta", "gamma"], - "col1": [True, False, True], - "col2": [1, 2, 3], - "col3": [4.5, 6, 7.75], - } - ) - bf_df = session.read_pandas(pd_df) - # Simulate the result directly, since the function cannot be applied - # directly on a pandas dataframe with axis=1, as this is a special type of - # function with multiple params supported only on bigframes dataframe. - pd_result = pd.Series( - [ - ["alpha", "true", "1", "4.5"], - ["beta", "false", "2", "6"], - ["gamma", "true", "3", "7.75"], - ] - ) - bf_result = bf_df.apply(func, axis=1) - assert bigframes.dtypes.is_array_string_like(bf_result.dtype) - assert_series_equal( - pd_result, bf_result.to_pandas(), check_dtype=False, check_index_type=False - ) - - -def test_read_gbq_function_reads_udfs(session, bigquery_client, dataset_id): - dataset_ref = bigquery.DatasetReference.from_string(dataset_id) - arg = bigquery.RoutineArgument( - name="x", - data_type=bigquery.StandardSqlDataType(bigquery.StandardSqlTypeNames.INT64), - ) - sql_routine = bigquery.Routine( - dataset_ref.routine("square_sql"), - body="x * x", - arguments=[arg], - return_type=bigquery.StandardSqlDataType(bigquery.StandardSqlTypeNames.INT64), - type_=bigquery.RoutineType.SCALAR_FUNCTION, - ) - js_routine = bigquery.Routine( - dataset_ref.routine("square_js"), - body="return x * x", - language="JAVASCRIPT", - arguments=[arg], - return_type=bigquery.StandardSqlDataType(bigquery.StandardSqlTypeNames.INT64), - type_=bigquery.RoutineType.SCALAR_FUNCTION, - ) - - for routine in (sql_routine, js_routine): - # Create the routine in BigQuery and read it back using read_gbq_function. - bigquery_client.create_routine(routine, exists_ok=True) - square = session.read_gbq_function( - str(routine.reference), - ) - - # It should point to the named routine and yield the expected results. - assert square.bigframes_bigquery_function == str(routine.reference) - assert square.input_dtypes == (bigframes.dtypes.INT_DTYPE,) - assert square.output_dtype == bigframes.dtypes.INT_DTYPE - assert ( - square.bigframes_bigquery_function_output_dtype - == bigframes.dtypes.INT_DTYPE - ) - - src = {"x": [-5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5]} - - routine_ref_str = bff_utils.routine_ref_to_string_for_query(routine.reference) - direct_sql = " UNION ALL ".join( - [f"SELECT {x} AS x, {routine_ref_str}({x}) AS y" for x in src["x"]] - ) - direct_df = bigquery_client.query(direct_sql).to_dataframe() - - indirect_df = bigframes.dataframe.DataFrame(src) - indirect_df = indirect_df.assign(y=indirect_df.x.apply(square)) - converted_indirect_df = indirect_df.to_pandas() - - assert_frame_equal( - direct_df, converted_indirect_df, ignore_order=True, check_index_type=False - ) - - -def test_read_gbq_function_requires_explicit_types( - session, bigquery_client, dataset_id -): - dataset_ref = bigquery.DatasetReference.from_string(dataset_id) - typed_arg = bigquery.RoutineArgument( - name="x", - data_type=bigquery.StandardSqlDataType(bigquery.StandardSqlTypeNames.INT64), - ) - untyped_arg = bigquery.RoutineArgument( - name="x", - kind="ANY_TYPE", # With this kind, data_type not required for SQL functions. - ) - - both_types_specified = bigquery.Routine( - dataset_ref.routine("both_types_specified"), - body="x * x", - arguments=[typed_arg], - return_type=bigquery.StandardSqlDataType(bigquery.StandardSqlTypeNames.INT64), - type_=bigquery.RoutineType.SCALAR_FUNCTION, - ) - only_return_type_specified = bigquery.Routine( - dataset_ref.routine("only_return_type_specified"), - body="x * x", - arguments=[untyped_arg], - return_type=bigquery.StandardSqlDataType(bigquery.StandardSqlTypeNames.INT64), - type_=bigquery.RoutineType.SCALAR_FUNCTION, - ) - only_arg_type_specified = bigquery.Routine( - dataset_ref.routine("only_arg_type_specified"), - body="x * x", - arguments=[typed_arg], - type_=bigquery.RoutineType.SCALAR_FUNCTION, - ) - neither_type_specified = bigquery.Routine( - dataset_ref.routine("neither_type_specified"), - body="x * x", - arguments=[untyped_arg], - type_=bigquery.RoutineType.SCALAR_FUNCTION, - ) - - bigquery_client.create_routine(both_types_specified, exists_ok=True) - bigquery_client.create_routine(only_return_type_specified, exists_ok=True) - bigquery_client.create_routine(only_arg_type_specified, exists_ok=True) - bigquery_client.create_routine(neither_type_specified, exists_ok=True) - - session.read_gbq_function( - str(both_types_specified.reference), - ) - with pytest.warns( - bigframes.exceptions.UnknownDataTypeWarning, - match=r"missing input data types[\s\S]*assume default data type", - ): - session.read_gbq_function( - str(only_return_type_specified.reference), - ) - with pytest.raises(ValueError): - session.read_gbq_function( - str(only_arg_type_specified.reference), - ) - with pytest.raises(ValueError): - session.read_gbq_function( - str(neither_type_specified.reference), - ) - - -@pytest.mark.parametrize( - ("session_fixture",), - [ - pytest.param("session"), - pytest.param("unordered_session"), - ], -) -@pytest.mark.parametrize( - ("array_type", "expected_data"), - [ - pytest.param(None, ["[1,2,3]", "[10,11,12]", "[100,101,102]"], id="None"), - pytest.param( - list[str], - [["1", "2", "3"], ["10", "11", "12"], ["100", "101", "102"]], - id="list-str", - ), - pytest.param( - list[int], [[1, 2, 3], [10, 11, 12], [100, 101, 102]], id="list-int" - ), - ], -) -def test_read_gbq_function_respects_python_output_type( - request, session_fixture, bigquery_client, dataset_id, array_type, expected_data -): - session = request.getfixturevalue(session_fixture) - dataset_ref = bigquery.DatasetReference.from_string(dataset_id) - arg = bigquery.RoutineArgument( - name="x", - data_type=bigquery.StandardSqlDataType(bigquery.StandardSqlTypeNames.INT64), - ) - sql_routine = bigquery.Routine( - dataset_ref.routine(_prefixer.create_prefix()), - body="TO_JSON_STRING([x, x+1, x+2])", - arguments=[arg], - return_type=bigquery.StandardSqlDataType(bigquery.StandardSqlTypeNames.STRING), - description=bff_utils.get_bigframes_metadata(python_output_type=array_type), - type_=bigquery.RoutineType.SCALAR_FUNCTION, - ) - - # Create the routine in BigQuery and read it back using read_gbq_function. - bigquery_client.create_routine(sql_routine, exists_ok=True) - func = session.read_gbq_function(str(sql_routine.reference)) - - # test that the function works as expected - s = bigframes.series.Series([1, 10, 100]) - expected = pd.Series(expected_data) - actual = s.apply(func).to_pandas() - - # ignore type disparities, e.g. "int64" in pandas v/s "Int64" in bigframes - assert_series_equal(expected, actual, check_dtype=False, check_index_type=False) - - -@pytest.mark.parametrize( - ("array_type",), - [ - pytest.param(list[bool], id="list-bool"), - pytest.param(list[float], id="list-float"), - pytest.param(list[int], id="list-int"), - pytest.param(list[str], id="list-str"), - ], -) -def test_read_gbq_function_supports_python_output_type_only_for_string_outputs( - session, bigquery_client, dataset_id, array_type -): - dataset_ref = bigquery.DatasetReference.from_string(dataset_id) - arg = bigquery.RoutineArgument( - name="x", - data_type=bigquery.StandardSqlDataType(bigquery.StandardSqlTypeNames.INT64), - ) - sql_routine = bigquery.Routine( - dataset_ref.routine(_prefixer.create_prefix()), - body="x+1", - arguments=[arg], - return_type=bigquery.StandardSqlDataType(bigquery.StandardSqlTypeNames.INT64), - description=bff_utils.get_bigframes_metadata(python_output_type=array_type), - type_=bigquery.RoutineType.SCALAR_FUNCTION, - ) - - # Create the routine in BigQuery and read it back using read_gbq_function. - bigquery_client.create_routine(sql_routine, exists_ok=True) - - # reading back will fail because we currently allow specifying an explicit - # output_type for BQ functions with STRING output - with pytest.raises( - TypeError, - match="An explicit output_type should be provided only for a BigQuery function with STRING output.", - ): - session.read_gbq_function(str(sql_routine.reference)) - - -@pytest.mark.parametrize( - ("array_type",), - [ - pytest.param(list[bool], id="list-bool"), - pytest.param(list[float], id="list-float"), - pytest.param(list[int], id="list-int"), - pytest.param(list[str], id="list-str"), - ], -) -def test_read_gbq_function_supported_python_output_type( - session, bigquery_client, dataset_id, array_type -): - dataset_ref = bigquery.DatasetReference.from_string(dataset_id) - arg = bigquery.RoutineArgument( - name="x", - data_type=bigquery.StandardSqlDataType(bigquery.StandardSqlTypeNames.INT64), - ) - sql_routine = bigquery.Routine( - dataset_ref.routine(_prefixer.create_prefix()), - body="CAST(x AS STRING)", - arguments=[arg], - return_type=bigquery.StandardSqlDataType(bigquery.StandardSqlTypeNames.STRING), - description=bff_utils.get_bigframes_metadata(python_output_type=array_type), - type_=bigquery.RoutineType.SCALAR_FUNCTION, - ) - - # Create the routine in BigQuery and read it back using read_gbq_function. - bigquery_client.create_routine(sql_routine, exists_ok=True) - session.read_gbq_function(str(sql_routine.reference)) - - -@pytest.mark.flaky(retries=2, delay=120) -def test_df_apply_scalar_func(session, scalars_dfs): - scalars_df, _ = scalars_dfs - bdf = bigframes.pandas.DataFrame( - { - "Column1": scalars_df["string_col"], - "Column2": scalars_df["string_col"], - } - ) - - # The "cw_lower_case_ascii_only" is a scalar function. - func_ref = session.read_gbq_function("bqutil.fn.cw_lower_case_ascii_only") - - # DataFrame '.apply()' only supports series level application. - with pytest.raises(NotImplementedError) as context: - bdf.apply(func_ref) - assert str(context.value) == ( - "BigFrames DataFrame '.apply()' does not support BigFrames BigQuery " - "function for column-wise (i.e. with axis=0) operations, please use a " - "regular python function instead. For element-wise operations of the " - "BigFrames BigQuery function, please use '.map()'. " - f"{constants.FEEDBACK_LINK}" - ) - - -@pytest.mark.flaky(retries=2, delay=120) -def test_df_apply_axis_1(session, scalars_dfs, dataset_id_permanent): - columns = [ - "bool_col", - "int64_col", - "int64_too", - "float64_col", - "string_col", - "bytes_col", - ] - scalars_df, scalars_pandas_df = scalars_dfs - - def add_ints(row: pandas.Series) -> int: - return row["int64_col"] + row["int64_too"] - - with pytest.warns( - bigframes.exceptions.PreviewWarning, - match="input_types=Series is in preview.", - ): - add_ints_remote = session.remote_function( - dataset=dataset_id_permanent, - name=get_function_name(add_ints, is_row_processor=True), - cloud_function_service_account="default", - )(add_ints) - assert add_ints_remote.bigframes_remote_function # type: ignore - assert add_ints_remote.bigframes_bigquery_function # type: ignore - assert add_ints_remote.bigframes_cloud_function # type: ignore - - with pytest.warns( - bigframes.exceptions.PreviewWarning, match="axis=1 scenario is in preview." - ): - bf_result = scalars_df[columns].apply(add_ints_remote, axis=1).to_pandas() - - pd_result = scalars_pandas_df[columns].apply(add_ints, axis=1) - - # bf_result.dtype is 'Int64' while pd_result.dtype is 'object', ignore this - # mismatch by using check_dtype=False. - # - # bf_result.to_numpy() produces an array of numpy.float64's - # (in system_prerelease tests), while pd_result.to_numpy() produces an - # array of ints, ignore this mismatch by using check_exact=False. - assert_series_equal(pd_result, bf_result, check_dtype=False, check_exact=False) - - # Read back the deployed BQ remote function using read_gbq_function. - func_ref = session.read_gbq_function( - function_name=add_ints_remote.bigframes_bigquery_function, # type: ignore - is_row_processor=True, - ) - - assert ( - func_ref.bigframes_remote_function == add_ints_remote.bigframes_remote_function - ) # type: ignore - assert ( - func_ref.bigframes_bigquery_function - == add_ints_remote.bigframes_bigquery_function - ) # type: ignore - assert func_ref.bigframes_remote_function == func_ref.bigframes_bigquery_function # type: ignore - - bf_result_gbq = scalars_df[columns].apply(func_ref, axis=1).to_pandas() - assert_series_equal(pd_result, bf_result_gbq, check_dtype=False, check_exact=False) - - -@pytest.mark.flaky(retries=2, delay=120) -def test_df_apply_axis_1_ordering(session, scalars_dfs, dataset_id_permanent): - columns = ["bool_col", "int64_col", "int64_too", "float64_col", "string_col"] - ordering_columns = ["bool_col", "int64_col"] - scalars_df, scalars_pandas_df = scalars_dfs - - def add_ints(row: pandas.Series) -> int: - return row["int64_col"] + row["int64_too"] - - add_ints_remote = session.remote_function( - input_types=pandas.Series, - output_type=int, - dataset=dataset_id_permanent, - name=get_function_name(add_ints, is_row_processor=True), - cloud_function_service_account="default", - )(add_ints) - - bf_result = ( - scalars_df[columns] - .sort_values(ordering_columns) - .apply(add_ints_remote, axis=1) - .to_pandas() - ) - pd_result = ( - scalars_pandas_df[columns].sort_values(ordering_columns).apply(add_ints, axis=1) - ) - - # bf_result.dtype is 'Int64' while pd_result.dtype is 'object', ignore this - # mismatch by using check_dtype=False. - # - # bf_result.to_numpy() produces an array of numpy.float64's - # (in system_prerelease tests), while pd_result.to_numpy() produces an - # array of ints, ignore this mismatch by using check_exact=False. - assert_series_equal(pd_result, bf_result, check_dtype=False, check_exact=False) - - -@pytest.mark.flaky(retries=2, delay=120) -def test_df_apply_axis_1_multiindex(session, dataset_id_permanent): - pd_df = pd.DataFrame( - {"x": [1, 2, 3], "y": [1.5, 3.75, 5], "z": ["pq", "rs", "tu"]}, - index=pd.MultiIndex.from_tuples([("a", 100), ("a", 200), ("b", 300)]), - ) - bf_df = session.read_pandas(pd_df) - - def add_numbers(row): - return row["x"] + row["y"] - - add_numbers_remote = session.remote_function( - input_types=pandas.Series, - output_type=float, - dataset=dataset_id_permanent, - name=get_function_name(add_numbers, is_row_processor=True), - cloud_function_service_account="default", - )(add_numbers) - - bf_result = bf_df.apply(add_numbers_remote, axis=1).to_pandas() - pd_result = pd_df.apply(add_numbers, axis=1) - - # bf_result.dtype is 'Float64' while pd_result.dtype is 'float64', ignore this - # mismatch by using check_dtype=False. - # - # bf_result.index[0].dtype is 'string[pyarrow]' while - # pd_result.index[0].dtype is 'object', ignore this mismatch by using - # check_index_type=False. - assert_series_equal(pd_result, bf_result, check_dtype=False, check_index_type=False) - - -def test_df_apply_axis_1_unsupported_callable(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - columns = ["bool_col", "int64_col", "int64_too", "float64_col", "string_col"] - - def add_ints(row): - return row["int64_col"] + row["int64_too"] - - # pandas works - scalars_pandas_df.apply(add_ints, axis=1) - - with pytest.raises( - ValueError, match="For axis=1 a BigFrames BigQuery function must be used." - ): - scalars_df[columns].apply(add_ints, axis=1) - - -@pytest.mark.flaky(retries=2, delay=120) -def test_df_apply_axis_1_unsupported_dtype(session, scalars_dfs, dataset_id_permanent): - columns_with_not_supported_dtypes = [ - "date_col", - "datetime_col", - "geography_col", - "numeric_col", - "time_col", - "timestamp_col", - ] - - scalars_df, scalars_pandas_df = scalars_dfs - - def echo_len(row): - return len(row) - - echo_len_remote = session.remote_function( - input_types=pandas.Series, - output_type=float, - dataset=dataset_id_permanent, - name=get_function_name(echo_len, is_row_processor=True), - cloud_function_service_account="default", - )(echo_len) - - for column in columns_with_not_supported_dtypes: - # pandas works - scalars_pandas_df[[column]].apply(echo_len, axis=1) - - dtype = scalars_df[column].dtype - - with ( - pytest.raises( - NotImplementedError, - match=re.escape( - f"DataFrame has a column of dtype '{dtype}' which is not supported with axis=1. Supported dtypes are (" - ), - ), - pytest.warns( - bigframes.exceptions.PreviewWarning, - match="axis=1 scenario is in preview.", - ), - ): - scalars_df[[column]].apply(echo_len_remote, axis=1) - - -@pytest.mark.flaky(retries=2, delay=120) -def test_remote_function_application_repr(session, dataset_id_permanent): - # This function deliberately has a param with name "name", this is to test - # a specific ibis' internal handling of object names - def should_mask(name: str) -> bool: - hash = 0 - for char_ in name: - hash += ord(char_) - return hash % 2 == 0 - - assert "name" in inspect.signature(should_mask).parameters - - should_mask = session.remote_function( - dataset=dataset_id_permanent, - name=get_function_name(should_mask), - cloud_function_service_account="default", - )(should_mask) - - s = bigframes.series.Series(["Alice", "Bob", "Caroline"]) - - repr(s.apply(should_mask)) - repr(s.where(s.apply(should_mask))) - repr(s.where(~s.apply(should_mask))) - repr(s.mask(should_mask)) - repr(s.mask(should_mask, "REDACTED")) - - -def test_read_gbq_function_application_repr( - session, routine_id_unique, scalars_df_index -): - # This function deliberately has a param with name "name", this is to test - # a specific ibis' internal handling of object names - session.bqclient.query_and_wait( - f"CREATE OR REPLACE FUNCTION `{routine_id_unique}`(name STRING) RETURNS BOOL AS (MOD(LENGTH(name), 2) = 1)" - ) - routine = session.bqclient.get_routine(routine_id_unique) - assert "name" in [arg.name for arg in routine.arguments] - - # read the function and apply to dataframe - should_mask = session.read_gbq_function(routine_id_unique) - - s = scalars_df_index["string_col"] - - repr(s.apply(should_mask)) - repr(s.where(s.apply(should_mask))) - repr(s.where(~s.apply(should_mask))) - repr(s.mask(should_mask)) - repr(s.mask(should_mask, "REDACTED")) - - -@pytest.mark.parametrize( - ("method",), - [ - pytest.param("apply"), - pytest.param("map"), - pytest.param("mask"), - ], -) -@pytest.mark.flaky(retries=2, delay=120) -def test_remote_function_unary_applied_after_filter( - session, dataset_id_permanent, scalars_dfs, method -): - # This function is deliberately written to not work with NA input - def is_odd(x: int) -> bool: - return x % 2 == 1 - - scalars_df, scalars_pandas_df = scalars_dfs - int_col_name_with_nulls = "int64_col" - - # make sure there are NA values in the test column - assert any([pd.isna(val) for val in scalars_df[int_col_name_with_nulls]]) - - # create a remote function - is_odd_remote = session.remote_function( - dataset=dataset_id_permanent, - name=get_function_name(is_odd), - cloud_function_service_account="default", - )(is_odd) - - # with nulls in the series the remote function application would fail - with pytest.raises( - google.api_core.exceptions.BadRequest, match="unsupported operand" - ): - bf_method = getattr(scalars_df[int_col_name_with_nulls], method) - bf_method(is_odd_remote).to_pandas() - - # after filtering out nulls the remote function application should work - # similar to pandas - pd_method = getattr( - scalars_pandas_df[scalars_pandas_df[int_col_name_with_nulls].notnull()][ - int_col_name_with_nulls - ], - method, - ) - pd_result = pd_method(is_odd) - bf_method = getattr( - scalars_df[scalars_df[int_col_name_with_nulls].notnull()][ - int_col_name_with_nulls - ], - method, - ) - bf_result = bf_method(is_odd_remote).to_pandas() - - # ignore any dtype difference - assert_series_equal(pd_result, bf_result, check_dtype=False) - - -@pytest.mark.flaky(retries=2, delay=120) -def test_remote_function_binary_applied_after_filter( - session, dataset_id_permanent, scalars_dfs -): - # This function is deliberately written to not work with NA input - def add(x: int, y: int) -> int: - return x + y - - scalars_df, scalars_pandas_df = scalars_dfs - int_col_name_with_nulls = "int64_col" - int_col_name_no_nulls = "int64_too" - bf_df = scalars_df[[int_col_name_with_nulls, int_col_name_no_nulls]] - pd_df = scalars_pandas_df[[int_col_name_with_nulls, int_col_name_no_nulls]] - - # make sure there are NA values in the test column - assert any([pd.isna(val) for val in bf_df[int_col_name_with_nulls]]) - - # create a remote function - add_remote = session.remote_function( - dataset=dataset_id_permanent, - name=get_function_name(add), - cloud_function_service_account="default", - )(add) - - # with nulls in the series the remote function application would fail - with pytest.raises( - google.api_core.exceptions.BadRequest, match="unsupported operand" - ): - bf_df[int_col_name_with_nulls].combine( - bf_df[int_col_name_no_nulls], add_remote - ).to_pandas() - - # after filtering out nulls the remote function application should work - # similar to pandas - pd_filter = pd_df[int_col_name_with_nulls].notnull() - pd_result = pd_df[pd_filter][int_col_name_with_nulls].combine( - pd_df[pd_filter][int_col_name_no_nulls], add - ) - bf_filter = bf_df[int_col_name_with_nulls].notnull() - bf_result = ( - bf_df[bf_filter][int_col_name_with_nulls] - .combine(bf_df[bf_filter][int_col_name_no_nulls], add_remote) - .to_pandas() - ) - - # ignore any dtype difference - assert_series_equal(pd_result, bf_result, check_dtype=False) - - -@pytest.mark.flaky(retries=2, delay=120) -def test_remote_function_nary_applied_after_filter( - session, dataset_id_permanent, scalars_dfs -): - # This function is deliberately written to not work with NA input - def add(x: int, y: int, z: float) -> float: - return x + y + z - - scalars_df, scalars_pandas_df = scalars_dfs - int_col_name_with_nulls = "int64_col" - int_col_name_no_nulls = "int64_too" - float_col_name_with_nulls = "float64_col" - bf_df = scalars_df[ - [int_col_name_with_nulls, int_col_name_no_nulls, float_col_name_with_nulls] - ] - pd_df = scalars_pandas_df[ - [int_col_name_with_nulls, int_col_name_no_nulls, float_col_name_with_nulls] - ] - - # make sure there are NA values in the test columns - assert any([pd.isna(val) for val in bf_df[int_col_name_with_nulls]]) - assert any([pd.isna(val) for val in bf_df[float_col_name_with_nulls]]) - - # create a remote function - add_remote = session.remote_function( - dataset=dataset_id_permanent, - name=get_function_name(add), - cloud_function_service_account="default", - )(add) - - # pandas does not support nary functions, so let's create a proxy function - # for testing purpose that takes a series and in turn calls the naray function - def add_pandas(s: pd.Series) -> float: - return add( - s[int_col_name_with_nulls], - s[int_col_name_no_nulls], - s[float_col_name_with_nulls], - ) - - # with nulls in the series the remote function application would fail - with pytest.raises( - google.api_core.exceptions.BadRequest, match="unsupported operand" - ): - bf_df.apply(add_remote, axis=1).to_pandas() - - # after filtering out nulls the remote function application should work - # similar to pandas - pd_filter = ( - pd_df[int_col_name_with_nulls].notnull() - & pd_df[float_col_name_with_nulls].notnull() - ) - pd_result = pd_df[pd_filter].apply(add_pandas, axis=1) - bf_filter = ( - bf_df[int_col_name_with_nulls].notnull() - & bf_df[float_col_name_with_nulls].notnull() - ) - bf_result = bf_df[bf_filter].apply(add_remote, axis=1).to_pandas() - - # ignore any dtype difference - assert_series_equal(pd_result, bf_result, check_dtype=False) - - -@pytest.mark.parametrize( - ("method",), - [ - pytest.param("apply"), - pytest.param("map"), - pytest.param("mask"), - ], -) -def test_remote_function_unary_partial_ordering_mode_assign( - unordered_session, dataset_id_permanent, method -): - df = unordered_session.read_gbq("bigquery-public-data.baseball.schedules")[ - ["duration_minutes"] - ] - - def is_long_duration(minutes: int) -> bool: - return minutes >= 120 - - is_long_duration = unordered_session.remote_function( - dataset=dataset_id_permanent, - name=get_function_name(is_long_duration), - cloud_function_service_account="default", - )(is_long_duration) - - method = getattr(df["duration_minutes"], method) - - df1 = df.assign(duration_meta=method(is_long_duration)) - repr(df1) - - -@pytest.mark.flaky(retries=2, delay=120) -def test_remote_function_binary_partial_ordering_mode_assign( - unordered_session, dataset_id_permanent, scalars_df_index -): - def combiner(x: int, y: int) -> int: - if x is None: - return y - return x - - combiner = unordered_session.remote_function( - dataset=dataset_id_permanent, - name=get_function_name(combiner), - cloud_function_service_account="default", - )(combiner) - - df = scalars_df_index[["int64_col", "int64_too", "float64_col", "string_col"]] - df1 = df.assign(int64_combined=df["int64_col"].combine(df["int64_too"], combiner)) - repr(df1) - - -@pytest.mark.flaky(retries=2, delay=120) -def test_remote_function_nary_partial_ordering_mode_assign( - unordered_session, dataset_id_permanent, scalars_df_index -): - def processor(x: int, y: int, z: float, w: str) -> str: - return f"I got x={x}, y={y}, z={z} and w={w}" - - processor = unordered_session.remote_function( - dataset=dataset_id_permanent, - name=get_function_name(processor), - cloud_function_service_account="default", - )(processor) - - df = scalars_df_index[["int64_col", "int64_too", "float64_col", "string_col"]] - df1 = df.assign(combined=df.apply(processor, axis=1)) - repr(df1) - - -@pytest.mark.flaky(retries=2, delay=120) -def test_remote_function_unsupported_type( - session, - dataset_id_permanent, - bq_cf_connection, -): - # Remote functions do not support tuple return types. - def func_tuple(x): - return (x, x, x) - - with pytest.raises( - ValueError, - match=r"must be one of the supported types", - ): - session.remote_function( - input_types=int, - output_type=Sequence[int], - dataset=dataset_id_permanent, - bigquery_connection=bq_cf_connection, - reuse=True, - name=get_function_name(func_tuple), - cloud_function_service_account="default", - )(func_tuple) diff --git a/tests/system/small/geopandas/test_geoseries.py b/tests/system/small/geopandas/test_geoseries.py deleted file mode 100644 index 9f1f830dc68..00000000000 --- a/tests/system/small/geopandas/test_geoseries.py +++ /dev/null @@ -1,577 +0,0 @@ -# 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. -# 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 re - -import bigframes_vendored.constants as constants -import geopandas # type: ignore -import geopandas.testing # type:ignore -import google.api_core.exceptions -import pandas as pd -import pytest -from geopandas.array import GeometryDtype # type:ignore -from shapely.geometry import ( # type: ignore - GeometryCollection, - LineString, - Point, - Polygon, -) - -import bigframes.geopandas -import bigframes.pandas -import bigframes.series -import bigframes.session -from bigframes.testing.utils import assert_series_equal - - -@pytest.fixture(scope="session") -def urban_areas_dfs(session, urban_areas_table_id): - bf_ua = session.read_gbq(urban_areas_table_id, index_col="geo_id") - pd_ua = bf_ua.to_pandas() - return (bf_ua, pd_ua) - - -def test_geo_x(urban_areas_dfs): - bf_ua, pd_ua = urban_areas_dfs - bf_series: bigframes.geopandas.GeoSeries = bf_ua["internal_point_geom"].geo - pd_series: geopandas.GeoSeries = geopandas.GeoSeries(pd_ua["internal_point_geom"]) - bf_result = bf_series.x.to_pandas() - pd_result = pd_series.x - - assert_series_equal( - pd_result.astype(pd.Float64Dtype()), - bf_result, - ) - - -def test_geo_x_non_point(urban_areas_dfs): - bf_ua, _ = urban_areas_dfs - bf_series: bigframes.geopandas.GeoSeries = bf_ua["urban_area_geom"].geo - - with pytest.raises(google.api_core.exceptions.BadRequest, match="ST_X"): - bf_series.x.to_pandas() - - -def test_geo_y(urban_areas_dfs): - bf_ua, pd_ua = urban_areas_dfs - bf_series: bigframes.geopandas.GeoSeries = bf_ua["internal_point_geom"].geo - pd_series: geopandas.GeoSeries = geopandas.GeoSeries(pd_ua["internal_point_geom"]) - bf_result = bf_series.y.to_pandas() - pd_result = pd_series.y - - assert_series_equal( - pd_result.astype(pd.Float64Dtype()), - bf_result, - ) - - -def test_geo_area_not_supported(session: bigframes.session.Session): - s = bigframes.pandas.Series( - [ - Polygon([(0, 0), (1, 1), (0, 1)]), - Polygon([(10, 0), (10, 5), (0, 0)]), - Polygon([(0, 0), (2, 2), (2, 0)]), - LineString([(0, 0), (1, 1), (0, 1)]), - Point(0, 1), - ], - dtype=GeometryDtype(), - session=session, - ) - bf_series: bigframes.geopandas.GeoSeries = s.geo - with pytest.raises( - NotImplementedError, - match=re.escape( - f"GeoSeries.area is not supported. Use bigframes.bigquery.st_area(series), instead. {constants.FEEDBACK_LINK}" - ), - ): - bf_series.area - - -def test_geoseries_length_property_not_implemented(session): - gs = bigframes.geopandas.GeoSeries([Point(0, 0)], session=session) - with pytest.raises( - NotImplementedError, - match=re.escape( - "GeoSeries.length is not yet implemented. Please use bigframes.bigquery.st_length(geoseries) instead." - ), - ): - _ = gs.length - - -def test_geo_distance_not_supported(session: bigframes.session.Session): - s1 = bigframes.pandas.Series( - [ - Polygon([(0, 0), (1, 1), (0, 1)]), - Polygon([(10, 0), (10, 5), (0, 0)]), - Polygon([(0, 0), (2, 2), (2, 0)]), - LineString([(0, 0), (1, 1), (0, 1)]), - Point(0, 1), - ], - dtype=GeometryDtype(), - session=session, - ) - s2 = bigframes.geopandas.GeoSeries( - [ - Polygon([(0, 0), (1, 1), (0, 1)]), - Polygon([(10, 0), (10, 5), (0, 0)]), - Polygon([(0, 0), (2, 2), (2, 0)]), - LineString([(0, 0), (1, 1), (0, 1)]), - Point(0, 1), - ], - session=session, - ) - with pytest.raises( - NotImplementedError, - match=re.escape("GeoSeries.distance is not supported."), - ): - s1.geo.distance(s2) - - -def test_geo_from_xy(session: bigframes.session.Session): - x = [2.5, 5, -3.0] - y = [0.5, 1, 1.5] - bf_result = ( - bigframes.geopandas.GeoSeries.from_xy(x, y, session=session) - .astype(geopandas.array.GeometryDtype()) - .to_pandas() - ) - pd_result = geopandas.GeoSeries.from_xy(x, y, crs="EPSG:4326").astype( - geopandas.array.GeometryDtype() - ) - - pd.testing.assert_series_equal( - bf_result, - pd_result, - check_series_type=False, - check_index=False, - ) - - -def test_geo_from_wkt(session: bigframes.session.Session): - wkts = [ - "Point(0 1)", - "Point(2 4)", - "Point(5 3)", - "Point(6 8)", - ] - - bf_result = bigframes.geopandas.GeoSeries.from_wkt( - wkts, session=session - ).to_pandas() - - pd_result = geopandas.GeoSeries.from_wkt(wkts) - - pd.testing.assert_series_equal( - bf_result, - pd_result, - check_series_type=False, - check_index=False, - ) - - -def test_geo_to_wkt(session: bigframes.session.Session): - bf_geo = bigframes.geopandas.GeoSeries( - [ - Point(0, 1), - Point(2, 4), - Point(5, 3), - Point(6, 8), - ], - session=session, - ) - - pd_geo = geopandas.GeoSeries( - [ - Point(0, 1), - Point(2, 4), - Point(5, 3), - Point(6, 8), - ] - ) - - # Test was failing before using str.replace because the pd_result had extra - # whitespace "POINT (0 1)" while bf_result had none "POINT(0 1)". - # str.replace replaces any encountered whitespaces with none. - bf_result = ( - bf_geo.to_wkt().astype("string[pyarrow]").to_pandas().str.replace(" ", "") - ) - - pd_result = pd_geo.to_wkt().astype("string[pyarrow]").str.replace(" ", "") - - pd.testing.assert_series_equal( - bf_result, - pd_result, - check_index=False, - ) - - -def test_geo_boundary(session: bigframes.session.Session): - bf_s = bigframes.series.Series( - [ - Polygon([(0, 0), (1, 1), (0, 1)]), - Polygon([(10, 0), (10, 5), (0, 0)]), - Polygon([(0, 0), (2, 2), (2, 0)]), - LineString([(0, 0), (1, 1), (0, 1)]), - Point(0, 1), - ], - session=session, - ) - - pd_s = geopandas.GeoSeries( - [ - Polygon([(0, 0), (1, 1), (0, 1)]), - Polygon([(10, 0), (10, 5), (0, 0)]), - Polygon([(0, 0), (2, 2), (2, 0)]), - LineString([(0, 0), (1, 1), (0, 1)]), - Point(0, 1), - ], - index=pd.Index([0, 1, 2, 3, 4], dtype="Int64"), - crs="WGS84", - ) - - bf_result = bf_s.geo.boundary.to_pandas() - pd_result = pd_s.boundary - - geopandas.testing.assert_geoseries_equal( - bf_result, - pd_result, - check_series_type=False, - check_index_type=False, - ) - - -# the GeoSeries and GeoPandas results are not always the same. -# For example, when the difference between two polygons is empty, -# GeoPandas returns 'POLYGON EMPTY' while GeoSeries returns 'GeometryCollection([])'. -# This is why we are hard-coding the expected results. -def test_geo_difference_with_geometry_objects(session: bigframes.session.Session): - data1 = [ - Polygon([(0, 0), (10, 0), (10, 10), (0, 0)]), - Polygon([(0, 0), (1, 1), (0, 1), (0, 0)]), - Point(0, 1), - ] - - data2 = [ - Polygon([(0, 0), (10, 0), (10, 10), (0, 0)]), - Polygon([(0, 0), (1, 1), (0, 1), (0, 0)]), - LineString([(2, 0), (0, 2)]), - ] - - bf_s1 = bigframes.geopandas.GeoSeries(data=data1, session=session) - bf_s2 = bigframes.geopandas.GeoSeries(data=data2, session=session) - - bf_result = bf_s1.difference(bf_s2).to_pandas() - - expected = bigframes.geopandas.GeoSeries( - [ - Polygon([]), - Polygon([]), - Point(0, 1), - ], - index=[0, 1, 2], - session=session, - ).to_pandas() - - assert bf_result.dtype == "geometry" - assert expected.iloc[0].equals(bf_result.iloc[0]) - assert expected.iloc[1].equals(bf_result.iloc[1]) - assert expected.iloc[2].equals(bf_result.iloc[2]) - - -def test_geo_difference_with_single_geometry_object(session: bigframes.session.Session): - data1 = [ - Polygon([(0, 0), (10, 0), (10, 10), (0, 0)]), - Polygon([(4, 2), (6, 2), (8, 6), (4, 2)]), - Point(0, 1), - ] - - bf_s1 = bigframes.geopandas.GeoSeries(data=data1, session=session) - bf_result = bf_s1.difference( - bigframes.geopandas.GeoSeries( - [ - Polygon([(0, 0), (10, 0), (10, 10), (0, 0)]), - Polygon([(1, 0), (0, 5), (0, 0), (1, 0)]), - ], - session=session, - ), - ).to_pandas() - - expected = bigframes.geopandas.GeoSeries( - [ - GeometryCollection([]), - Polygon([(4, 2), (6, 2), (8, 6), (4, 2)]), - None, - ], - index=[0, 1, 2], - session=session, - ).to_pandas() - - assert bf_result.dtype == "geometry" - assert (expected.iloc[0]).equals(bf_result.iloc[0]) - assert expected.iloc[1] == bf_result.iloc[1] - assert expected.iloc[2] == bf_result.iloc[2] - - -def test_geo_difference_with_similar_geometry_objects( - session: bigframes.session.Session, -): - data1 = [ - Polygon([(0, 0), (10, 0), (10, 10), (0, 0)]), - Polygon([(0, 0), (1, 1), (0, 1)]), - Point(0, 1), - ] - - bf_s1 = bigframes.geopandas.GeoSeries(data=data1, session=session) - bf_result = bf_s1.difference(bf_s1).to_pandas() - - expected = bigframes.geopandas.GeoSeries( - [GeometryCollection([]), GeometryCollection([]), GeometryCollection([])], - index=[0, 1, 2], - session=session, - ).to_pandas() - - assert bf_result.dtype == "geometry" - assert expected.iloc[0].equals(bf_result.iloc[0]) - assert expected.iloc[1].equals(bf_result.iloc[1]) - assert expected.iloc[2].equals(bf_result.iloc[2]) - - -def test_geo_drop_duplicates(session: bigframes.session.Session): - bf_series = bigframes.geopandas.GeoSeries( - [Point(1, 1), Point(2, 2), Point(3, 3), Point(2, 2)], - session=session, - ) - - pd_series = geopandas.GeoSeries( - [Point(1, 1), Point(2, 2), Point(3, 3), Point(2, 2)] - ) - - bf_result = bf_series.drop_duplicates().to_pandas() - pd_result = pd_series.drop_duplicates() - - pd.testing.assert_series_equal( - geopandas.GeoSeries(bf_result), pd_result, check_index=False - ) - - -# the GeoSeries and GeoPandas results are not always the same. -# For example, when the intersection between two polygons is empty, -# GeoPandas returns 'POLYGON EMPTY' while GeoSeries returns 'GeometryCollection([])'. -# This is why we are hard-coding the expected results. -def test_geo_intersection_with_geometry_objects(session: bigframes.session.Session): - data1 = [ - Polygon([(0, 0), (10, 0), (10, 10), (0, 0)]), - Polygon([(0, 0), (1, 1), (0, 1), (0, 0)]), - Point(0, 1), - ] - - data2 = [ - Polygon([(0, 0), (10, 0), (10, 10), (0, 0)]), - Polygon([(0, 0), (1, 1), (0, 1), (0, 0)]), - LineString([(2, 0), (0, 2)]), - ] - - bf_s1 = bigframes.geopandas.GeoSeries(data=data1, session=session) - bf_s2 = bigframes.geopandas.GeoSeries(data=data2, session=session) - - bf_result = bf_s1.intersection(bf_s2).to_pandas() - - expected = bigframes.geopandas.GeoSeries( - [ - Polygon([(0, 0), (10, 0), (10, 10), (0, 0)]), - Polygon([(0, 0), (1, 1), (0, 1), (0, 0)]), - GeometryCollection([]), - ], - session=session, - ).to_pandas() - - assert bf_result.dtype == "geometry" - assert expected.iloc[0].equals(bf_result.iloc[0]) - assert expected.iloc[1].equals(bf_result.iloc[1]) - assert expected.iloc[2].equals(bf_result.iloc[2]) - - -def test_geo_intersection_with_single_geometry_object( - session: bigframes.session.Session, -): - data1 = [ - Polygon([(0, 0), (10, 0), (10, 10), (0, 0)]), - Polygon([(4, 2), (6, 2), (8, 6), (4, 2)]), - Point(0, 1), - ] - - bf_s1 = bigframes.geopandas.GeoSeries(data=data1, session=session) - bf_result = bf_s1.intersection( - bigframes.geopandas.GeoSeries( - [ - Polygon([(0, 0), (10, 0), (10, 10), (0, 0)]), - Polygon([(1, 0), (0, 5), (0, 0), (1, 0)]), - ], - session=session, - ), - ).to_pandas() - - expected = bigframes.geopandas.GeoSeries( - [ - Polygon([(0, 0), (10, 0), (10, 10), (0, 0)]), - GeometryCollection([]), - None, - ], - index=[0, 1, 2], - session=session, - ).to_pandas() - - assert bf_result.dtype == "geometry" - assert (expected.iloc[0]).equals(bf_result.iloc[0]) - assert expected.iloc[1] == bf_result.iloc[1] - assert expected.iloc[2] == bf_result.iloc[2] - - -def test_geo_intersection_with_similar_geometry_objects( - session: bigframes.session.Session, -): - data1 = [ - Polygon([(0, 0), (10, 0), (10, 10), (0, 0)]), - Polygon([(0, 0), (1, 1), (0, 1)]), - Point(0, 1), - ] - - bf_s1 = bigframes.geopandas.GeoSeries(data=data1, session=session) - bf_result = bf_s1.intersection(bf_s1).to_pandas() - - expected = bigframes.geopandas.GeoSeries( - [ - Polygon([(0, 0), (10, 0), (10, 10), (0, 0)]), - Polygon([(0, 0), (1, 1), (0, 1)]), - Point(0, 1), - ], - index=[0, 1, 2], - session=session, - ).to_pandas() - - assert bf_result.dtype == "geometry" - assert expected.iloc[0].equals(bf_result.iloc[0]) - assert expected.iloc[1].equals(bf_result.iloc[1]) - assert expected.iloc[2].equals(bf_result.iloc[2]) - - -def test_geo_is_closed_not_supported(session: bigframes.session.Session): - s = bigframes.series.Series( - [ - Polygon([(0, 0), (1, 1), (0, 1)]), - Polygon([(10, 0), (10, 5), (0, 0)]), - Polygon([(0, 0), (2, 2), (2, 0)]), - LineString([(0, 0), (1, 1), (0, 1)]), - Point(0, 1), - ], - dtype=GeometryDtype(), - session=session, - ) - bf_series: bigframes.geopandas.GeoSeries = s.geo - with pytest.raises( - NotImplementedError, - match=re.escape( - f"GeoSeries.is_closed is not supported. Use bigframes.bigquery.st_isclosed(series), instead. {constants.FEEDBACK_LINK}" - ), - ): - bf_series.is_closed - - -def test_geo_buffer_raises_notimplemented(session: bigframes.session.Session): - """GeoPandas takes distance in units of the coordinate system, but BigQuery - uses meters. - """ - s = bigframes.geopandas.GeoSeries( - [ - Point(0, 0), - ], - session=session, - ) - with pytest.raises( - NotImplementedError, match=re.escape("bigframes.bigquery.st_buffer") - ): - s.buffer(1000) - - -def test_geo_centroid(session: bigframes.session.Session): - bf_s = bigframes.series.Series( - [ - Polygon([(0, 0), (0.1, 0.1), (0, 0.1)]), - LineString([(10, 10), (10.0001, 10.0001), (10, 10.0001)]), - Point(-10, -10), - ], - session=session, - ) - - pd_s = geopandas.GeoSeries( - [ - Polygon([(0, 0), (0.1, 0.1), (0, 0.1)]), - LineString([(10, 10), (10.0001, 10.0001), (10, 10.0001)]), - Point(-10, -10), - ], - index=pd.Index([0, 1, 2], dtype="Int64"), - crs="WGS84", - ) - - bf_result = bf_s.geo.centroid.to_pandas() - # Avoid warning that centroid is incorrect for geographic CRS. - # https://gis.stackexchange.com/a/401815/275289 - pd_result = pd_s.to_crs("+proj=cea").centroid.to_crs("WGS84") - - geopandas.testing.assert_geoseries_equal( - bf_result, - pd_result, - check_series_type=False, - check_index_type=False, - # BigQuery geography calculations are on a sphere, so results will be - # slightly different. - check_less_precise=True, - ) - - -def test_geo_convex_hull(session: bigframes.session.Session): - bf_s = bigframes.series.Series( - [ - Polygon([(0, 0), (1, 1), (0, 1)]), - Polygon([(10, 0), (10, 5), (0, 0)]), - Polygon([(0, 0), (2, 2), (2, 0)]), - LineString([(0, 0), (1, 1), (0, 1)]), - Point(0, 1), - ], - session=session, - ) - - pd_s = geopandas.GeoSeries( - [ - Polygon([(0, 0), (1, 1), (0, 1)]), - Polygon([(10, 0), (10, 5), (0, 0)]), - Polygon([(0, 0), (2, 2), (2, 0)]), - LineString([(0, 0), (1, 1), (0, 1)]), - Point(0, 1), - ], - index=pd.Index([0, 1, 2, 3, 4], dtype="Int64"), - crs="WGS84", - ) - - bf_result = bf_s.geo.convex_hull.to_pandas() - pd_result = pd_s.convex_hull - - geopandas.testing.assert_geoseries_equal( - bf_result, - pd_result, - check_series_type=False, - check_index_type=False, - ) diff --git a/tests/system/small/ml/conftest.py b/tests/system/small/ml/conftest.py index 2f84b351e04..c11445b79a5 100644 --- a/tests/system/small/ml/conftest.py +++ b/tests/system/small/ml/conftest.py @@ -12,9 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. -import os -import uuid from typing import cast +import uuid import pandas as pd import pytest @@ -29,10 +28,15 @@ globals, imported, linear_model, - remote, + llm, ) +@pytest.fixture(scope="session") +def bq_connection() -> str: + return "bigframes-dev.us.bigframes-rf-conn" + + @pytest.fixture(scope="session") def penguins_bqml_linear_model(session, penguins_linear_model_name) -> core.BqmlModel: model = session.bqclient.get_model(penguins_linear_model_name) @@ -41,11 +45,12 @@ def penguins_bqml_linear_model(session, penguins_linear_model_name) -> core.Bqml @pytest.fixture(scope="function") def ephemera_penguins_bqml_linear_model( - session: bigframes.Session, - penguins_bqml_linear_model: core.BqmlModel, + penguins_bqml_linear_model, ) -> core.BqmlModel: model = penguins_bqml_linear_model - return model.copy(f"{session._anonymous_dataset}.{uuid.uuid4().hex}") + return model.copy( + f"{model._model.project}.{model._model.dataset_id}.{uuid.uuid4().hex}" + ) @pytest.fixture(scope="session") @@ -152,25 +157,21 @@ def penguins_pca_model( @pytest.fixture(scope="session") -def onnx_iris_pandas_df(): - """Data matching the iris dataset.""" +def llm_text_pandas_df(): + """Additional data matching the penguins dataset, with a new index""" return pd.DataFrame( { - "sepal_length": [4.9, 5.1, 34.7], - "sepal_width": [3.0, 5.1, 24.7], - "petal_length": [1.4, 1.5, 13.3], - "petal_width": [0.4, 0.2, 18.3], - "species": [ - "setosa", - "setosa", - "virginica", + "prompt": [ + "What is BigQuery?", + "What is BQML?", + "What is BigQuery DataFrame?", ], } ) @pytest.fixture(scope="session") -def xgboost_iris_pandas_df(): +def onnx_iris_pandas_df(): """Data matching the iris dataset.""" return pd.DataFrame( { @@ -178,6 +179,11 @@ def xgboost_iris_pandas_df(): "sepal_width": [3.0, 5.1, 24.7], "petal_length": [1.4, 1.5, 13.3], "petal_width": [0.4, 0.2, 18.3], + "species": [ + "setosa", + "setosa", + "virginica", + ], } ) @@ -188,50 +194,54 @@ def onnx_iris_df(session, onnx_iris_pandas_df): @pytest.fixture(scope="session") -def xgboost_iris_df(session, xgboost_iris_pandas_df): - return session.read_pandas(xgboost_iris_pandas_df) +def llm_text_df(session, llm_text_pandas_df): + return session.read_pandas(llm_text_pandas_df) @pytest.fixture(scope="session") -def linear_remote_model_params() -> dict: - # Pre-deployed endpoint of linear reg model in Vertex. - # bigframes-test-linreg2 -> bigframes-test-linreg-endpoint2 - model_vertex_endpoint = os.environ.get( - "BIGFRAMES_TEST_MODEL_VERTEX_ENDPOINT", - "https://us-central1-aiplatform.googleapis.com/v1/projects/1084210331973/locations/us-central1/endpoints/3193318217619603456", +def bqml_palm2_text_generator_model(session, bq_connection) -> core.BqmlModel: + options = { + "remote_service_type": "CLOUD_AI_LARGE_LANGUAGE_MODEL_V1", + } + return globals.bqml_model_factory().create_remote_model( + session=session, connection_name=bq_connection, options=options ) - return { - "input": {"culmen_length_mm": "float64"}, - "output": {"predicted_body_mass_g": "array"}, - "endpoint": model_vertex_endpoint, - } + +@pytest.fixture(scope="session") +def palm2_text_generator_model(session, bq_connection) -> llm.PaLM2TextGenerator: + return llm.PaLM2TextGenerator(session=session, connection_name=bq_connection) @pytest.fixture(scope="session") -def bqml_linear_remote_model( - session, bq_connection, linear_remote_model_params -) -> core.BqmlModel: - options = { - "endpoint": linear_remote_model_params["endpoint"], - } - return globals.bqml_model_factory().create_remote_model( - session=session, - input=linear_remote_model_params["input"], - output=linear_remote_model_params["output"], - connection_name=bq_connection, - options=options, +def palm2_text_generator_32k_model(session, bq_connection) -> llm.PaLM2TextGenerator: + return llm.PaLM2TextGenerator( + model_name="text-bison-32k", session=session, connection_name=bq_connection ) +@pytest.fixture(scope="function") +def ephemera_palm2_text_generator_model( + session, bq_connection +) -> llm.PaLM2TextGenerator: + return llm.PaLM2TextGenerator(session=session, connection_name=bq_connection) + + @pytest.fixture(scope="session") -def linear_remote_vertex_model( - session, bq_connection, linear_remote_model_params -) -> remote.VertexAIModel: - return remote.VertexAIModel( - endpoint=linear_remote_model_params["endpoint"], - input=linear_remote_model_params["input"], - output=linear_remote_model_params["output"], +def palm2_embedding_generator_model( + session, bq_connection +) -> llm.PaLM2TextEmbeddingGenerator: + return llm.PaLM2TextEmbeddingGenerator( + session=session, connection_name=bq_connection + ) + + +@pytest.fixture(scope="session") +def palm2_embedding_generator_multilingual_model( + session, bq_connection +) -> llm.PaLM2TextEmbeddingGenerator: + return llm.PaLM2TextEmbeddingGenerator( + model_name="textembedding-gecko-multilingual", session=session, connection_name=bq_connection, ) @@ -245,14 +255,6 @@ def time_series_bqml_arima_plus_model( return core.BqmlModel(session, model) -@pytest.fixture(scope="session") -def time_series_bqml_arima_plus_model_w_id( - session, time_series_arima_plus_model_name_w_id -) -> core.BqmlModel: - model = session.bqclient.get_model(time_series_arima_plus_model_name_w_id) - return core.BqmlModel(session, model) - - @pytest.fixture(scope="session") def time_series_arima_plus_model( session, time_series_arima_plus_model_name @@ -263,16 +265,6 @@ def time_series_arima_plus_model( ) -@pytest.fixture(scope="session") -def time_series_arima_plus_model_w_id( - session, time_series_arima_plus_model_name_w_id -) -> forecasting.ARIMAPlus: - return cast( - forecasting.ARIMAPlus, - session.read_gbq_model(time_series_arima_plus_model_name_w_id), - ) - - @pytest.fixture(scope="session") def imported_tensorflow_model_path() -> str: return "gs://cloud-training-demos/txtclass/export/exporter/1549825580/*" @@ -283,11 +275,6 @@ def imported_onnx_model_path() -> str: return "gs://cloud-samples-data/bigquery/ml/onnx/pipeline_rf.onnx" -@pytest.fixture(scope="session") -def imported_xgboost_array_model_path() -> str: - return "gs://bigframes-dev-testing/xgboost-testdata/model.bst" - - @pytest.fixture(scope="session") def imported_tensorflow_model( session, imported_tensorflow_model_path @@ -312,20 +299,3 @@ def imported_onnx_model(session, imported_onnx_model_path) -> imported.ONNXModel session=session, model_path=imported_onnx_model_path, ) - - -@pytest.fixture(scope="session") -def imported_xgboost_model( - session, imported_xgboost_array_model_path -) -> imported.XGBoostModel: - return imported.XGBoostModel( - session=session, - input={ - "petal_length": "float64", - "petal_width": "float64", - "sepal_length": "float64", - "sepal_width": "float64", - }, - output={"predicted_label": "float64"}, - model_path=imported_xgboost_array_model_path, - ) diff --git a/tests/system/small/ml/test_cluster.py b/tests/system/small/ml/test_cluster.py index 2bf334e84df..d95a1e1bc22 100644 --- a/tests/system/small/ml/test_cluster.py +++ b/tests/system/small/ml/test_cluster.py @@ -12,12 +12,10 @@ # See the License for the specific language governing permissions and # limitations under the License. -import numpy as np import pandas as pd -import bigframes.pandas as bpd from bigframes.ml import cluster -from bigframes.testing.utils import assert_frame_equal +from tests.system.utils import assert_pandas_df_equal_ignore_ordering _PD_NEW_PENGUINS = pd.DataFrame.from_dict( { @@ -64,59 +62,13 @@ def test_kmeans_predict(session, penguins_kmeans_model: cluster.KMeans): new_penguins = session.read_pandas(_PD_NEW_PENGUINS) - predictions = penguins_kmeans_model.predict(new_penguins).to_pandas() - assert predictions.shape == (4, 9) - result = predictions[["CENTROID_ID"]] + result = penguins_kmeans_model.predict(new_penguins).to_pandas() expected = pd.DataFrame( {"CENTROID_ID": [2, 3, 1, 2]}, dtype="Int64", index=pd.Index(["test1", "test2", "test3", "test4"], dtype="string[pyarrow]"), ) - assert_frame_equal(result, expected, ignore_order=True) - - -def test_kmeans_detect_anomalies( - penguins_kmeans_model: cluster.KMeans, new_penguins_df: bpd.DataFrame -): - anomalies = penguins_kmeans_model.detect_anomalies(new_penguins_df).to_pandas() - expected = pd.DataFrame( - { - "is_anomaly": [False, False, False], - "normalized_distance": [1.082937, 0.77139, 0.478304], - }, - index=pd.Index([1633, 1672, 1690], name="tag_number", dtype="Int64"), - ) - - pd.testing.assert_frame_equal( - anomalies[["is_anomaly", "normalized_distance"]].sort_index(), - expected, - check_exact=False, - check_dtype=False, - rtol=0.1, - ) - - -def test_kmeans_detect_anomalies_params( - penguins_kmeans_model: cluster.KMeans, new_penguins_df: bpd.DataFrame -): - anomalies = penguins_kmeans_model.detect_anomalies( - new_penguins_df, contamination=0.4 - ).to_pandas() - expected = pd.DataFrame( - { - "is_anomaly": [True, False, False], - "normalized_distance": [1.082937, 0.77139, 0.478304], - }, - index=pd.Index([1633, 1672, 1690], name="tag_number", dtype="Int64"), - ) - - pd.testing.assert_frame_equal( - anomalies[["is_anomaly", "normalized_distance"]].sort_index(), - expected, - check_exact=False, - check_dtype=False, - rtol=0.1, - ) + assert_pandas_df_equal_ignore_ordering(result, expected) def test_kmeans_score(session, penguins_kmeans_model: cluster.KMeans): @@ -137,100 +89,65 @@ def test_kmeans_score(session, penguins_kmeans_model: cluster.KMeans): def test_kmeans_cluster_centers(penguins_kmeans_model: cluster.KMeans): - result = ( - penguins_kmeans_model.cluster_centers_.to_pandas() - .sort_values(["centroid_id", "feature"]) - .reset_index(drop=True) - ) - - # FIX: Helper to ignore row order inside categorical_value lists - # and sign flipping of values inside numerical_value list. - # This prevents the test from failing if BQML returns [MALE, FEMALE] instead of [FEMALE, MALE] - # or 0.197 versus -0.197. - def sort_and_abs_categorical(val): - # Accept BOTH python lists AND numpy arrays - if isinstance(val, (list, np.ndarray)) and len(val) > 0: - # Take abs of value first, then sort - processed = [ - {"category": x["category"], "value": abs(x["value"])} for x in val + result = penguins_kmeans_model.cluster_centers_.to_pandas() + expected = pd.DataFrame( + { + "centroid_id": [1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3], + "feature": [ + "culmen_length_mm", + "culmen_depth_mm", + "flipper_length_mm", + "sex", ] - return sorted(processed, key=lambda x: x["category"]) - return val - - result["numerical_value"] = result["numerical_value"].abs() - result["categorical_value"] = result["categorical_value"].apply( - sort_and_abs_categorical - ) - - expected = ( - pd.DataFrame( - { - "centroid_id": [1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3], - "feature": [ - "culmen_length_mm", - "culmen_depth_mm", - "flipper_length_mm", - "sex", - ] - * 3, - "numerical_value": [ - 47.509677, - 14.993548, - 217.040123, - pd.NA, - 38.207813, - 18.03125, - 187.992188, - pd.NA, - 47.036346, - 18.834808, - 197.1612, - pd.NA, + * 3, + "numerical_value": [ + 47.509677, + 14.993548, + 217.040123, + pd.NA, + 38.207813, + 18.03125, + 187.992188, + pd.NA, + 47.036346, + 18.834808, + 197.1612, + pd.NA, + ], + "categorical_value": [ + [], + [], + [], + [ + {"category": ".", "value": 0.008064516129032258}, + {"category": "MALE", "value": 0.49193548387096775}, + {"category": "FEMALE", "value": 0.47580645161290325}, + {"category": "_null_filler", "value": 0.024193548387096774}, ], - "categorical_value": [ - [], - [], - [], - [ - {"category": ".", "value": 0.008064516129032258}, - {"category": "MALE", "value": 0.49193548387096775}, - {"category": "FEMALE", "value": 0.47580645161290325}, - {"category": "_null_filler", "value": 0.024193548387096774}, - ], - [], - [], - [], - [ - {"category": "MALE", "value": 0.34375}, - {"category": "FEMALE", "value": 0.625}, - {"category": "_null_filler", "value": 0.03125}, - ], - [], - [], - [], - [ - {"category": "MALE", "value": 0.6847826086956522}, - {"category": "FEMALE", "value": 0.2826086956521739}, - {"category": "_null_filler", "value": 0.03260869565217391}, - ], + [], + [], + [], + [ + {"category": "MALE", "value": 0.34375}, + {"category": "FEMALE", "value": 0.625}, + {"category": "_null_filler", "value": 0.03125}, ], - }, - ) - .sort_values(["centroid_id", "feature"]) - .reset_index(drop=True) - ) - - # Sort and sign flip expected values to match the output of the model. - expected["numerical_value"] = expected["numerical_value"].abs() - expected["categorical_value"] = expected["categorical_value"].apply( - sort_and_abs_categorical + [], + [], + [], + [ + {"category": "MALE", "value": 0.6847826086956522}, + {"category": "FEMALE", "value": 0.2826086956521739}, + {"category": "_null_filler", "value": 0.03260869565217391}, + ], + ], + }, ) - pd.testing.assert_frame_equal( result, expected, check_exact=False, - rtol=0.1, # Keep or slightly increase if numerical drift persists + rtol=0.1, # int64 Index by default in pandas versus Int64 (nullable) Index in BigQuery DataFrame check_index_type=False, check_dtype=False, diff --git a/tests/system/small/ml/test_core.py b/tests/system/small/ml/test_core.py index c32ba80b30e..f911dd7eebc 100644 --- a/tests/system/small/ml/test_core.py +++ b/tests/system/small/ml/test_core.py @@ -12,19 +12,18 @@ # See the License for the specific language governing permissions and # limitations under the License. -import typing from datetime import datetime +import typing +from unittest import TestCase -import numpy as np import pandas as pd import pyarrow as pa import pytest import pytz import bigframes -import bigframes.features from bigframes.ml import core -from bigframes.testing import utils +import tests.system.utils def test_model_eval( @@ -79,79 +78,59 @@ def test_model_eval_with_data(penguins_bqml_linear_model, penguins_df_default_in def test_model_centroids(penguins_bqml_kmeans_model: core.BqmlModel): result = penguins_bqml_kmeans_model.centroids().to_pandas() - - # FIX: Helper to ignore row order inside categorical_value lists - # This prevents the test from failing if BQML returns [MALE, FEMALE] instead of [FEMALE, MALE] - def sort_categorical(val): - if isinstance(val, (list, np.ndarray)) and len(val) > 0: - return sorted(val, key=lambda x: x["category"]) - return val - - result["categorical_value"] = result["categorical_value"].apply(sort_categorical) - - expected = ( - pd.DataFrame( - { - "centroid_id": [1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3], - "feature": [ - "culmen_length_mm", - "culmen_depth_mm", - "flipper_length_mm", - "sex", - ] - * 3, - "numerical_value": [ - 47.509677, - 14.993548, - 217.040123, - pd.NA, - 38.207813, - 18.03125, - 187.992188, - pd.NA, - 47.036346, - 18.834808, - 197.1612, - pd.NA, + expected = pd.DataFrame( + { + "centroid_id": [1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3], + "feature": [ + "culmen_length_mm", + "culmen_depth_mm", + "flipper_length_mm", + "sex", + ] + * 3, + "numerical_value": [ + 47.509677, + 14.993548, + 217.040123, + pd.NA, + 38.207813, + 18.03125, + 187.992188, + pd.NA, + 47.036346, + 18.834808, + 197.1612, + pd.NA, + ], + "categorical_value": [ + [], + [], + [], + [ + {"category": ".", "value": 0.008064516129032258}, + {"category": "MALE", "value": 0.49193548387096775}, + {"category": "FEMALE", "value": 0.47580645161290325}, + {"category": "_null_filler", "value": 0.024193548387096774}, ], - "categorical_value": [ - [], - [], - [], - [ - {"category": ".", "value": 0.008064516129032258}, - {"category": "MALE", "value": 0.49193548387096775}, - {"category": "FEMALE", "value": 0.47580645161290325}, - {"category": "_null_filler", "value": 0.024193548387096774}, - ], - [], - [], - [], - [ - {"category": "MALE", "value": 0.34375}, - {"category": "FEMALE", "value": 0.625}, - {"category": "_null_filler", "value": 0.03125}, - ], - [], - [], - [], - [ - {"category": "MALE", "value": 0.6847826086956522}, - {"category": "FEMALE", "value": 0.2826086956521739}, - {"category": "_null_filler", "value": 0.03260869565217391}, - ], + [], + [], + [], + [ + {"category": "MALE", "value": 0.34375}, + {"category": "FEMALE", "value": 0.625}, + {"category": "_null_filler", "value": 0.03125}, ], - }, - ) - .sort_values(["centroid_id", "feature"]) - .reset_index(drop=True) - ) - - # Sort expected values to match the output of the model. - expected["categorical_value"] = expected["categorical_value"].apply( - sort_categorical + [], + [], + [], + [ + {"category": "MALE", "value": 0.6847826086956522}, + {"category": "FEMALE", "value": 0.2826086956521739}, + {"category": "_null_filler", "value": 0.03260869565217391}, + ], + ], + }, ) - pd.testing.assert_frame_equal( result, expected, @@ -169,96 +148,66 @@ def test_pca_model_principal_components(penguins_bqml_pca_model: core.BqmlModel) # result is too long, only check the first principal component here. result = result.head(7) - - # FIX: Helper to ignore row order inside categorical_value lists - # and sign flipping of values inside numerical_value list. - # This prevents the test from failing if BQML returns [MALE, FEMALE] instead of [FEMALE, MALE] - # or 0.197 versus -0.197. - def sort_and_abs_categorical(val): - # Accept BOTH python lists AND numpy arrays - if isinstance(val, (list, np.ndarray)) and len(val) > 0: - # Take abs of value first, then sort - processed = [ - {"category": x["category"], "value": abs(x["value"])} for x in val - ] - return sorted(processed, key=lambda x: x["category"]) - return val - - result["numerical_value"] = result["numerical_value"].abs() - result["categorical_value"] = result["categorical_value"].apply( - sort_and_abs_categorical - ) - - expected = ( - pd.DataFrame( - { - "principal_component_id": [0] * 7, - "feature": [ - "species", - "island", - "culmen_length_mm", - "culmen_depth_mm", - "flipper_length_mm", - "body_mass_g", - "sex", + expected = pd.DataFrame( + { + "principal_component_id": [0] * 7, + "feature": [ + "species", + "island", + "culmen_length_mm", + "culmen_depth_mm", + "flipper_length_mm", + "body_mass_g", + "sex", + ], + "numerical_value": [ + pd.NA, + pd.NA, + 0.401489, + -0.377482, + 0.524052, + 0.501174, + pd.NA, + ], + "categorical_value": [ + [ + { + "category": "Gentoo penguin (Pygoscelis papua)", + "value": 0.25068877125667804, + }, + { + "category": "Adelie Penguin (Pygoscelis adeliae)", + "value": -0.20622291900416198, + }, + { + "category": "Chinstrap penguin (Pygoscelis antarctica)", + "value": -0.030161149275185855, + }, ], - "numerical_value": [ - pd.NA, - pd.NA, - 0.401489, - -0.377482, - 0.524052, - 0.501174, - pd.NA, + [ + {"category": "Biscoe", "value": 0.19761120114410635}, + {"category": "Dream", "value": -0.11264736305259061}, + {"category": "Torgersen", "value": -0.07065913511418596}, ], - "categorical_value": [ - [ - { - "category": "Gentoo penguin (Pygoscelis papua)", - "value": 0.25068877125667804, - }, - { - "category": "Adelie Penguin (Pygoscelis adeliae)", - "value": -0.20622291900416198, - }, - { - "category": "Chinstrap penguin (Pygoscelis antarctica)", - "value": -0.030161149275185855, - }, - ], - [ - {"category": "Biscoe", "value": 0.19761120114410635}, - {"category": "Dream", "value": -0.11264736305259061}, - {"category": "Torgersen", "value": -0.07065913511418596}, - ], - [], - [], - [], - [], - [ - {"category": ".", "value": 0.0015916894448071784}, - {"category": "MALE", "value": 0.06869704739750442}, - {"category": "FEMALE", "value": -0.052521171596813174}, - {"category": "_null_filler", "value": -0.0034628622681684906}, - ], + [], + [], + [], + [], + [ + {"category": ".", "value": 0.0015916894448071784}, + {"category": "MALE", "value": 0.06869704739750442}, + {"category": "FEMALE", "value": -0.052521171596813174}, + {"category": "_null_filler", "value": -0.0034628622681684906}, ], - }, - ) - .sort_values(["principal_component_id", "feature"]) - .reset_index(drop=True) - ) - - # Sort and sign flip expected values to match the output of the model. - expected["numerical_value"] = expected["numerical_value"].abs() - expected["categorical_value"] = expected["categorical_value"].apply( - sort_and_abs_categorical + ], + }, ) - - utils.assert_pandas_df_equal_pca_components( + pd.testing.assert_frame_equal( result, expected, check_exact=False, rtol=0.1, + # int64 Index by default in pandas versus Int64 (nullable) Index in BigQuery DataFrame check_index_type=False, check_dtype=False, ) @@ -276,7 +225,7 @@ def test_pca_model_principal_component_info(penguins_bqml_pca_model: core.BqmlMo "cumulative_explained_variance_ratio": [0.469357, 0.651283, 0.812383], }, ) - utils.assert_frame_equal( + tests.system.utils.assert_pandas_df_equal_ignore_ordering( result, expected, check_exact=False, @@ -284,7 +233,6 @@ def test_pca_model_principal_component_info(penguins_bqml_pca_model: core.BqmlMo # int64 Index by default in pandas versus Int64 (nullable) Index in BigQuery DataFrame check_index_type=False, check_dtype=False, - ignore_order=True, ) @@ -303,32 +251,10 @@ def test_model_predict(penguins_bqml_linear_model: core.BqmlModel, new_penguins_ ) -def test_model_predict_explain( - penguins_bqml_linear_model: core.BqmlModel, new_penguins_df -): - options = {"top_k_features": 3} - predictions = penguins_bqml_linear_model.explain_predict( - new_penguins_df, options - ).to_pandas() - expected = pd.DataFrame( - { - "predicted_body_mass_g": [4030.1, 3280.8, 3177.9], - "approximation_error": [0.0, 0.0, 0.0], - }, - dtype="Float64", - index=pd.Index([1633, 1672, 1690], name="tag_number", dtype="Int64"), - ) - pd.testing.assert_frame_equal( - predictions[["predicted_body_mass_g", "approximation_error"]].sort_index(), - expected, - check_exact=False, - rtol=0.1, - ) - - def test_model_predict_with_unnamed_index( penguins_bqml_linear_model: core.BqmlModel, new_penguins_df ): + # This will result in an index that lacks a name, which the ML library will # need to persist through the call to ML.PREDICT new_penguins_df = new_penguins_df.reset_index() @@ -354,144 +280,50 @@ def test_model_predict_with_unnamed_index( ) -def test_model_predict_explain_with_unnamed_index( - penguins_bqml_linear_model: core.BqmlModel, new_penguins_df +@pytest.mark.flaky(retries=2, delay=120) +def test_model_generate_text( + bqml_palm2_text_generator_model: core.BqmlModel, llm_text_df ): - # This will result in an index that lacks a name, which the ML library will - # need to persist through the call to ML.PREDICT - new_penguins_df = new_penguins_df.reset_index() - - options = {"top_k_features": 3} - # remove the middle tag number to ensure we're really keeping the unnamed index - new_penguins_df = typing.cast( - bigframes.dataframe.DataFrame, - new_penguins_df[new_penguins_df.tag_number != 1672], - ) - - predictions = penguins_bqml_linear_model.explain_predict( - new_penguins_df, options + options = { + "temperature": 0.5, + "max_output_tokens": 100, + "top_k": 20, + "top_p": 0.5, + "flatten_json_output": True, + } + df = bqml_palm2_text_generator_model.generate_text( + llm_text_df, options=options ).to_pandas() - expected = pd.DataFrame( - { - "predicted_body_mass_g": [4030.1, 3177.9], - "approximation_error": [0.0, 0.0], - }, - dtype="Float64", - index=pd.Index([0, 2], dtype="Int64"), - ) - pd.testing.assert_frame_equal( - predictions[["predicted_body_mass_g", "approximation_error"]].sort_index(), - expected, - check_exact=False, - rtol=0.1, + TestCase().assertSequenceEqual(df.shape, (3, 4)) + TestCase().assertSequenceEqual( + [ + "ml_generate_text_llm_result", + "ml_generate_text_rai_result", + "ml_generate_text_status", + "prompt", + ], + df.columns.to_list(), ) + series = df["ml_generate_text_llm_result"] + assert all(series.str.len() > 20) -def test_model_detect_anomalies( - penguins_bqml_pca_model: core.BqmlModel, new_penguins_df -): - options = {"contamination": 0.25} - anomalies = penguins_bqml_pca_model.detect_anomalies( - new_penguins_df, options - ).to_pandas() +def test_model_forecast(time_series_bqml_arima_plus_model: core.BqmlModel): + utc = pytz.utc + forecast = time_series_bqml_arima_plus_model.forecast().to_pandas()[ + ["forecast_timestamp", "forecast_value"] + ] expected = pd.DataFrame( { - "is_anomaly": [True, True, True], - "mean_squared_error": [0.254188, 0.731243, 0.298889], - }, - index=pd.Index([1633, 1672, 1690], name="tag_number", dtype="Int64"), - ) - pd.testing.assert_frame_equal( - anomalies[["is_anomaly", "mean_squared_error"]].sort_index(), - expected, - check_exact=False, - check_dtype=False, - rtol=0.1, - ) - - -@pytest.mark.skip("b/353775058 BQML internal error") -def test_remote_model_predict( - bqml_linear_remote_model: core.BqmlModel, new_penguins_df -): - expected = pd.DataFrame( - {"predicted_body_mass_g": [[3739.54], [3675.79], [3619.54]]}, - index=pd.Index([1633, 1672, 1690], name="tag_number", dtype="Int64"), - dtype=( - pd.ArrowDtype(pa.list_(pa.float64())) - if bigframes.features.PANDAS_VERSIONS.is_arrow_list_dtype_usable - else "object" - ), - ) - predictions = bqml_linear_remote_model.predict(new_penguins_df).to_pandas() - pd.testing.assert_frame_equal( - predictions[["predicted_body_mass_g"]].sort_index(), - expected, - check_exact=False, - rtol=0.1, + "forecast_timestamp": [ + datetime(2017, 8, 2, tzinfo=utc), + datetime(2017, 8, 3, tzinfo=utc), + datetime(2017, 8, 4, tzinfo=utc), + ], + "forecast_value": [2724.472284, 2593.368389, 2353.613034], + } ) - - -@pytest.mark.parametrize("id_col_name", [None, "id"]) -def test_model_forecast( - time_series_bqml_arima_plus_model: core.BqmlModel, - time_series_bqml_arima_plus_model_w_id: core.BqmlModel, - id_col_name, -): - utc = pytz.utc - forecast_cols = ["forecast_timestamp", "forecast_value"] - if id_col_name: - forecast_cols.insert(0, id_col_name) - - forecast = ( - time_series_bqml_arima_plus_model_w_id.forecast( - {"horizon": 4, "confidence_level": 0.8} - ) - if id_col_name - else time_series_bqml_arima_plus_model.forecast( - {"horizon": 4, "confidence_level": 0.8} - ) - ).to_pandas()[forecast_cols] - if id_col_name: - expected = pd.DataFrame( - { - "id": ["1", "2", "1", "2", "1", "2", "1", "2"], - "forecast_timestamp": [ - datetime(2017, 8, 2, tzinfo=utc), - datetime(2017, 8, 2, tzinfo=utc), - datetime(2017, 8, 3, tzinfo=utc), - datetime(2017, 8, 3, tzinfo=utc), - datetime(2017, 8, 4, tzinfo=utc), - datetime(2017, 8, 4, tzinfo=utc), - datetime(2017, 8, 5, tzinfo=utc), - datetime(2017, 8, 5, tzinfo=utc), - ], - "forecast_value": [ - 2634.796023, - 2634.796023, - 2621.332462, - 2621.332462, - 2396.095463, - 2396.095463, - 1742.878278, - 1742.878278, - ], - } - ) - expected["id"] = expected["id"].astype("string[pyarrow]") - else: - expected = pd.DataFrame( - { - "forecast_timestamp": [ - datetime(2017, 8, 2, tzinfo=utc), - datetime(2017, 8, 3, tzinfo=utc), - datetime(2017, 8, 4, tzinfo=utc), - datetime(2017, 8, 5, tzinfo=utc), - ], - "forecast_value": [2634.796023, 2621.332462, 2396.095463, 1742.878278], - } - ) expected["forecast_value"] = expected["forecast_value"].astype(pd.Float64Dtype()) expected["forecast_timestamp"] = expected["forecast_timestamp"].astype( pd.ArrowDtype(pa.timestamp("us", tz="UTC")) @@ -504,25 +336,16 @@ def test_model_forecast( ) -def test_model_register(ephemera_penguins_bqml_linear_model: core.BqmlModel): +def test_model_register(ephemera_penguins_bqml_linear_model): model = ephemera_penguins_bqml_linear_model - - start_execution_count = model.session._metrics.execution_count - model.register() - end_execution_count = model.session._metrics.execution_count - assert end_execution_count - start_execution_count == 1 - - assert model.model.model_id is not None model_name = "bigframes_" + model.model.model_id # Only registered model contains the field, and the field includes project/dataset. Here only check model_id. assert model_name in model.model.training_runs[-1]["vertexAiModelId"] -def test_model_register_with_params( - ephemera_penguins_bqml_linear_model: core.BqmlModel, -): +def test_model_register_with_params(ephemera_penguins_bqml_linear_model): model_name = "bigframes_system_test_model" model = ephemera_penguins_bqml_linear_model model.register(model_name) diff --git a/tests/system/small/ml/test_decomposition.py b/tests/system/small/ml/test_decomposition.py index 36abfe55adf..e31681f4a09 100644 --- a/tests/system/small/ml/test_decomposition.py +++ b/tests/system/small/ml/test_decomposition.py @@ -12,17 +12,13 @@ # See the License for the specific language governing permissions and # limitations under the License. -import numpy as np import pandas as pd -import bigframes.pandas as bpd -import bigframes.testing.utils from bigframes.ml import decomposition +import tests.system.utils -def test_pca_predict( - penguins_pca_model: decomposition.PCA, new_penguins_df: bpd.DataFrame -): +def test_pca_predict(penguins_pca_model, new_penguins_df): predictions = penguins_pca_model.predict(new_penguins_df).to_pandas() expected = pd.DataFrame( { @@ -33,53 +29,11 @@ def test_pca_predict( dtype="Float64", index=pd.Index([1633, 1672, 1690], name="tag_number", dtype="Int64"), ) - - bigframes.testing.utils.assert_pandas_df_equal_pca( - predictions, expected, check_exact=False, rtol=0.2 - ) - - -def test_pca_detect_anomalies( - penguins_pca_model: decomposition.PCA, new_penguins_df: bpd.DataFrame -): - anomalies = penguins_pca_model.detect_anomalies(new_penguins_df).to_pandas() - expected = pd.DataFrame( - { - "is_anomaly": [False, True, False], - "mean_squared_error": [0.254188, 0.731243, 0.298889], - }, - index=pd.Index([1633, 1672, 1690], name="tag_number", dtype="Int64"), - ) - pd.testing.assert_frame_equal( - anomalies[["is_anomaly", "mean_squared_error"]].sort_index(), + predictions.sort_index(), expected, check_exact=False, - check_dtype=False, - rtol=0.2, - ) - - -def test_pca_detect_anomalies_params( - penguins_pca_model: decomposition.PCA, new_penguins_df: bpd.DataFrame -): - anomalies = penguins_pca_model.detect_anomalies( - new_penguins_df, contamination=0.2 - ).to_pandas() - expected = pd.DataFrame( - { - "is_anomaly": [False, True, True], - "mean_squared_error": [0.254188, 0.731243, 0.298889], - }, - index=pd.Index([1633, 1672, 1690], name="tag_number", dtype="Int64"), - ) - - pd.testing.assert_frame_equal( - anomalies[["is_anomaly", "mean_squared_error"]].sort_index(), - expected, - check_exact=False, - check_dtype=False, - rtol=0.2, + rtol=0.1, ) @@ -93,7 +47,7 @@ def test_pca_score(penguins_pca_model: decomposition.PCA): result, expected, check_exact=False, - rtol=0.2, + rtol=0.1, check_index_type=False, ) @@ -103,96 +57,65 @@ def test_pca_components_(penguins_pca_model: decomposition.PCA): # result is too long, only check the first principal component here. result = result.head(7) - - # FIX: Helper to ignore row order inside categorical_value lists - # and sign flipping of values inside numerical_value list. - # This prevents the test from failing if BQML returns [MALE, FEMALE] instead of [FEMALE, MALE] - # or 0.197 versus -0.197. - def sort_and_abs_categorical(val): - # Accept BOTH python lists AND numpy arrays - if isinstance(val, (list, np.ndarray)) and len(val) > 0: - # Take abs of value first, then sort - processed = [ - {"category": x["category"], "value": abs(x["value"])} for x in val - ] - return sorted(processed, key=lambda x: x["category"]) - return val - - result["numerical_value"] = result["numerical_value"].abs() - result["categorical_value"] = result["categorical_value"].apply( - sort_and_abs_categorical - ) - - expected = ( - pd.DataFrame( - { - "principal_component_id": [0] * 7, - "feature": [ - "species", - "island", - "culmen_length_mm", - "culmen_depth_mm", - "flipper_length_mm", - "body_mass_g", - "sex", + expected = pd.DataFrame( + { + "principal_component_id": [0] * 7, + "feature": [ + "species", + "island", + "culmen_length_mm", + "culmen_depth_mm", + "flipper_length_mm", + "body_mass_g", + "sex", + ], + "numerical_value": [ + pd.NA, + pd.NA, + 0.401489, + -0.377482, + 0.524052, + 0.501174, + pd.NA, + ], + "categorical_value": [ + [ + { + "category": "Gentoo penguin (Pygoscelis papua)", + "value": 0.25068877125667804, + }, + { + "category": "Adelie Penguin (Pygoscelis adeliae)", + "value": -0.20622291900416198, + }, + { + "category": "Chinstrap penguin (Pygoscelis antarctica)", + "value": -0.030161149275185855, + }, ], - "numerical_value": [ - pd.NA, - pd.NA, - 0.401489, - -0.377482, - 0.524052, - 0.501174, - pd.NA, + [ + {"category": "Biscoe", "value": 0.19761120114410635}, + {"category": "Dream", "value": -0.11264736305259061}, + {"category": "Torgersen", "value": -0.07065913511418596}, ], - "categorical_value": [ - [ - { - "category": "Gentoo penguin (Pygoscelis papua)", - "value": 0.25068877125667804, - }, - { - "category": "Adelie Penguin (Pygoscelis adeliae)", - "value": -0.20622291900416198, - }, - { - "category": "Chinstrap penguin (Pygoscelis antarctica)", - "value": -0.030161149275185855, - }, - ], - [ - {"category": "Biscoe", "value": 0.19761120114410635}, - {"category": "Dream", "value": -0.11264736305259061}, - {"category": "Torgersen", "value": -0.07065913511418596}, - ], - [], - [], - [], - [], - [ - {"category": ".", "value": 0.0015916894448071784}, - {"category": "MALE", "value": 0.06869704739750442}, - {"category": "FEMALE", "value": -0.052521171596813174}, - {"category": "_null_filler", "value": -0.0034628622681684906}, - ], + [], + [], + [], + [], + [ + {"category": ".", "value": 0.0015916894448071784}, + {"category": "MALE", "value": 0.06869704739750442}, + {"category": "FEMALE", "value": -0.052521171596813174}, + {"category": "_null_filler", "value": -0.0034628622681684906}, ], - }, - ) - .sort_values(["principal_component_id", "feature"]) - .reset_index(drop=True) - ) - - # Sort and sign flip expected values to match the output of the model. - expected["numerical_value"] = expected["numerical_value"].abs() - expected["categorical_value"] = expected["categorical_value"].apply( - sort_and_abs_categorical + ], + }, ) - - bigframes.testing.utils.assert_pandas_df_equal_pca_components( + pd.testing.assert_frame_equal( result, expected, check_exact=False, - rtol=0.2, # FIX: Slightly increased rtol for numerical drift (from 0.1) + rtol=0.1, check_index_type=False, check_dtype=False, ) @@ -207,14 +130,13 @@ def test_pca_explained_variance_(penguins_pca_model: decomposition.PCA): "explained_variance": [3.278657, 1.270829, 1.125354], }, ) - bigframes.testing.utils.assert_frame_equal( + tests.system.utils.assert_pandas_df_equal_ignore_ordering( result, expected, check_exact=False, - rtol=0.2, + rtol=0.1, check_index_type=False, check_dtype=False, - ignore_order=True, ) @@ -227,12 +149,11 @@ def test_pca_explained_variance_ratio_(penguins_pca_model: decomposition.PCA): "explained_variance_ratio": [0.469357, 0.181926, 0.1611], }, ) - bigframes.testing.utils.assert_frame_equal( + tests.system.utils.assert_pandas_df_equal_ignore_ordering( result, expected, check_exact=False, - rtol=0.2, + rtol=0.1, check_index_type=False, check_dtype=False, - ignore_order=True, ) diff --git a/tests/system/small/ml/test_ensemble.py b/tests/system/small/ml/test_ensemble.py index a15e53fb17f..bba083d98d9 100644 --- a/tests/system/small/ml/test_ensemble.py +++ b/tests/system/small/ml/test_ensemble.py @@ -14,7 +14,9 @@ from unittest import TestCase +import google.api_core.exceptions import pandas +import pytest import bigframes.ml.ensemble @@ -96,9 +98,7 @@ def test_xgbregressor_model_score_series( def test_xgbregressor_model_predict( penguins_xgbregressor_model: bigframes.ml.ensemble.XGBRegressor, new_penguins_df ): - predictions = penguins_xgbregressor_model.predict(new_penguins_df).to_pandas() - assert predictions.shape == (3, 8) - result = predictions[["predicted_body_mass_g"]] + result = penguins_xgbregressor_model.predict(new_penguins_df).to_pandas() expected = pandas.DataFrame( {"predicted_body_mass_g": ["4293.1538089", "3410.0271", "3357.944"]}, dtype="Float64", @@ -114,9 +114,11 @@ def test_xgbregressor_model_predict( def test_to_gbq_saved_xgbregressor_model_scores( - penguins_xgbregressor_model, table_id_unique, penguins_df_default_index + penguins_xgbregressor_model, dataset_id, penguins_df_default_index ): - saved_model = penguins_xgbregressor_model.to_gbq(table_id_unique, replace=True) + saved_model = penguins_xgbregressor_model.to_gbq( + f"{dataset_id}.test_penguins_model", replace=True + ) df = penguins_df_default_index.dropna() X_test = df[ [ @@ -151,6 +153,14 @@ def test_to_gbq_saved_xgbregressor_model_scores( ) +def test_to_xgbregressor_model_gbq_replace(penguins_xgbregressor_model, dataset_id): + penguins_xgbregressor_model.to_gbq( + f"{dataset_id}.test_penguins_model", replace=True + ) + with pytest.raises(google.api_core.exceptions.Conflict): + penguins_xgbregressor_model.to_gbq(f"{dataset_id}.test_penguins_model") + + def test_xgbclassifier_model_score( penguins_xgbclassifier_model, penguins_df_default_index ): @@ -210,9 +220,7 @@ def test_xgbclassifier_model_score_series( def test_xgbclassifier_model_predict( penguins_xgbclassifier_model: bigframes.ml.ensemble.XGBClassifier, new_penguins_df ): - predictions = penguins_xgbclassifier_model.predict(new_penguins_df).to_pandas() - assert predictions.shape == (3, 9) - result = predictions[["predicted_sex"]] + result = penguins_xgbclassifier_model.predict(new_penguins_df).to_pandas() expected = pandas.DataFrame( {"predicted_sex": ["MALE", "MALE", "FEMALE"]}, dtype="string[pyarrow]", @@ -228,9 +236,11 @@ def test_xgbclassifier_model_predict( def test_to_gbq_saved_xgbclassifier_model_scores( - penguins_xgbclassifier_model, table_id_unique, penguins_df_default_index + penguins_xgbclassifier_model, dataset_id, penguins_df_default_index ): - saved_model = penguins_xgbclassifier_model.to_gbq(table_id_unique, replace=True) + saved_model = penguins_xgbclassifier_model.to_gbq( + f"{dataset_id}.test_penguins_model", replace=True + ) df = penguins_df_default_index.dropna() X_test = df[ [ @@ -267,6 +277,14 @@ def test_to_gbq_saved_xgbclassifier_model_scores( assert saved_model.max_iterations == 20 +def test_to_xgbclassifier_model_gbq_replace(penguins_xgbclassifier_model, dataset_id): + penguins_xgbclassifier_model.to_gbq( + f"{dataset_id}.test_penguins_model", replace=True + ) + with pytest.raises(google.api_core.exceptions.Conflict): + penguins_xgbclassifier_model.to_gbq(f"{dataset_id}.test_penguins_model") + + def test_randomforestregressor_model_score( penguins_randomforest_regressor_model, penguins_df_default_index ): @@ -345,11 +363,7 @@ def test_randomforestregressor_model_predict( penguins_randomforest_regressor_model: bigframes.ml.ensemble.RandomForestRegressor, new_penguins_df, ): - predictions = penguins_randomforest_regressor_model.predict( - new_penguins_df - ).to_pandas() - assert predictions.shape == (3, 8) - result = predictions[["predicted_body_mass_g"]] + result = penguins_randomforest_regressor_model.predict(new_penguins_df).to_pandas() expected = pandas.DataFrame( {"predicted_body_mass_g": ["3897.341797", "3458.385742", "3458.385742"]}, dtype="Float64", @@ -365,10 +379,10 @@ def test_randomforestregressor_model_predict( def test_to_gbq_saved_randomforestregressor_model_scores( - penguins_randomforest_regressor_model, table_id_unique, penguins_df_default_index + penguins_randomforest_regressor_model, dataset_id, penguins_df_default_index ): saved_model = penguins_randomforest_regressor_model.to_gbq( - table_id_unique, replace=True + f"{dataset_id}.test_penguins_model", replace=True ) df = penguins_df_default_index.dropna() X_test = df[ @@ -404,6 +418,18 @@ def test_to_gbq_saved_randomforestregressor_model_scores( ) +def test_to_randomforestregressor_model_gbq_replace( + penguins_randomforest_regressor_model, dataset_id +): + penguins_randomforest_regressor_model.to_gbq( + f"{dataset_id}.test_penguins_model", replace=True + ) + with pytest.raises(google.api_core.exceptions.Conflict): + penguins_randomforest_regressor_model.to_gbq( + f"{dataset_id}.test_penguins_model" + ) + + def test_randomforestclassifier_model_score( penguins_randomforest_classifier_model, penguins_df_default_index ): @@ -464,11 +490,7 @@ def test_randomforestclassifier_model_predict( penguins_randomforest_classifier_model: bigframes.ml.ensemble.RandomForestClassifier, new_penguins_df, ): - predictions = penguins_randomforest_classifier_model.predict( - new_penguins_df - ).to_pandas() - assert predictions.shape == (3, 9) - result = predictions[["predicted_sex"]] + result = penguins_randomforest_classifier_model.predict(new_penguins_df).to_pandas() expected = pandas.DataFrame( {"predicted_sex": ["MALE", "MALE", "FEMALE"]}, dtype="string[pyarrow]", @@ -484,10 +506,10 @@ def test_randomforestclassifier_model_predict( def test_to_gbq_saved_randomforestclassifier_model_scores( - penguins_randomforest_classifier_model, table_id_unique, penguins_df_default_index + penguins_randomforest_classifier_model, dataset_id, penguins_df_default_index ): saved_model = penguins_randomforest_classifier_model.to_gbq( - table_id_unique, replace=True + f"{dataset_id}.test_penguins_model", replace=True ) df = penguins_df_default_index.dropna() X_test = df[ @@ -521,3 +543,15 @@ def test_to_gbq_saved_randomforestclassifier_model_scores( # int64 Index by default in pandas versus Int64 (nullable) Index in BigQuery DataFrame check_index_type=False, ) + + +def test_to_randomforestclassifier_model_gbq_replace( + penguins_randomforest_classifier_model, dataset_id +): + penguins_randomforest_classifier_model.to_gbq( + f"{dataset_id}.test_penguins_model", replace=True + ) + with pytest.raises(google.api_core.exceptions.Conflict): + penguins_randomforest_classifier_model.to_gbq( + f"{dataset_id}.test_penguins_model" + ) diff --git a/tests/system/small/ml/test_forecasting.py b/tests/system/small/ml/test_forecasting.py index 23487983ee3..cb27dd388c3 100644 --- a/tests/system/small/ml/test_forecasting.py +++ b/tests/system/small/ml/test_forecasting.py @@ -16,169 +16,48 @@ import pandas as pd import pyarrow as pa -import pytest import pytz -from bigframes.ml import forecasting -ARIMA_EVALUATE_OUTPUT_COL = [ - "non_seasonal_p", - "non_seasonal_d", - "non_seasonal_q", - "log_likelihood", - "AIC", - "variance", - "seasonal_periods", - "has_holiday_effect", - "has_spikes_and_dips", - "has_step_changes", - "error_message", -] - - -@pytest.mark.parametrize("id_col_name", [None, "id"]) -def test_arima_plus_predict_default( - time_series_arima_plus_model: forecasting.ARIMAPlus, - time_series_arima_plus_model_w_id: forecasting.ARIMAPlus, - id_col_name, -): +def test_model_predict(time_series_arima_plus_model): utc = pytz.utc - predictions = ( - ( - time_series_arima_plus_model_w_id - if id_col_name - else time_series_arima_plus_model - ) - .predict() - .to_pandas() + predictions = time_series_arima_plus_model.predict().to_pandas() + expected = pd.DataFrame( + { + "forecast_timestamp": [ + datetime(2017, 8, 2, tzinfo=utc), + datetime(2017, 8, 3, tzinfo=utc), + datetime(2017, 8, 4, tzinfo=utc), + ], + "forecast_value": [2724.472284, 2593.368389, 2353.613034], + } ) - assert predictions.shape == ((6, 9) if id_col_name else (3, 8)) - result = predictions[["forecast_timestamp", "forecast_value"]] - if id_col_name: - result["id"] = predictions[["id"]] - result = result[["id", "forecast_timestamp", "forecast_value"]] - - if id_col_name: - expected = pd.DataFrame( - { - "id": ["1", "2", "1", "2", "1", "2"], - "forecast_timestamp": [ - datetime(2017, 8, 2, tzinfo=utc), - datetime(2017, 8, 2, tzinfo=utc), - datetime(2017, 8, 3, tzinfo=utc), - datetime(2017, 8, 3, tzinfo=utc), - datetime(2017, 8, 4, tzinfo=utc), - datetime(2017, 8, 4, tzinfo=utc), - ], - "forecast_value": [ - 2634.796023, - 2634.796023, - 2621.332461, - 2621.332461, - 2396.095462, - 2396.095462, - ], - } - ) - expected["id"] = expected["id"].astype("string[pyarrow]") - else: - expected = pd.DataFrame( - { - "forecast_timestamp": [ - datetime(2017, 8, 2, tzinfo=utc), - datetime(2017, 8, 3, tzinfo=utc), - datetime(2017, 8, 4, tzinfo=utc), - ], - "forecast_value": [ - 2634.796023, - 2621.332461, - 2396.095462, - ], - } - ) expected["forecast_value"] = expected["forecast_value"].astype(pd.Float64Dtype()) expected["forecast_timestamp"] = expected["forecast_timestamp"].astype( pd.ArrowDtype(pa.timestamp("us", tz="UTC")) ) - pd.testing.assert_frame_equal( - result, + predictions, expected, rtol=0.1, check_index_type=False, ) -@pytest.mark.parametrize("id_col_name", [None, "id"]) -def test_arima_plus_predict_explain_default( - time_series_arima_plus_model: forecasting.ARIMAPlus, - time_series_arima_plus_model_w_id: forecasting.ARIMAPlus, - id_col_name, -): - utc = pytz.utc - predictions = ( - ( - time_series_arima_plus_model_w_id - if id_col_name - else time_series_arima_plus_model - ) - .predict_explain() - .to_pandas() +def test_model_score(time_series_arima_plus_model, new_time_series_df): + result = time_series_arima_plus_model.score( + new_time_series_df[["parsed_date"]], new_time_series_df[["total_visits"]] + ).to_pandas() + expected = pd.DataFrame( + { + "mean_absolute_error": [154.742547], + "mean_squared_error": [26844.868855], + "root_mean_squared_error": [163.844038], + "mean_absolute_percentage_error": [6.189702], + "symmetric_mean_absolute_percentage_error": [6.097155], + }, + dtype="Float64", ) - assert predictions.shape[0] == (738 if id_col_name else 369) - predictions = predictions[ - predictions["time_series_type"] == "forecast" - ].reset_index(drop=True) - assert predictions.shape[0] == (6 if id_col_name else 3) - result = predictions[["time_series_timestamp", "time_series_data"]] - if id_col_name: - result["id"] = predictions[["id"]] - result = result[["id", "time_series_timestamp", "time_series_data"]] - if id_col_name: - expected = pd.DataFrame( - { - "id": ["1", "2", "1", "2", "1", "2"], - "time_series_timestamp": [ - datetime(2017, 8, 2, tzinfo=utc), - datetime(2017, 8, 2, tzinfo=utc), - datetime(2017, 8, 3, tzinfo=utc), - datetime(2017, 8, 3, tzinfo=utc), - datetime(2017, 8, 4, tzinfo=utc), - datetime(2017, 8, 4, tzinfo=utc), - ], - "time_series_data": [ - 2634.796023, - 2634.796023, - 2621.332461, - 2621.332461, - 2396.095462, - 2396.095462, - ], - } - ) - expected["id"] = expected["id"].astype("string[pyarrow]") - else: - expected = pd.DataFrame( - { - "time_series_timestamp": [ - datetime(2017, 8, 2, tzinfo=utc), - datetime(2017, 8, 3, tzinfo=utc), - datetime(2017, 8, 4, tzinfo=utc), - ], - "time_series_data": [ - 2634.796023, - 2621.332461, - 2396.095462, - ], - } - ) - expected["time_series_data"] = expected["time_series_data"].astype( - pd.Float64Dtype() - ) - expected["time_series_timestamp"] = expected["time_series_timestamp"].astype( - pd.ArrowDtype(pa.timestamp("us", tz="UTC")) - ) - pd.testing.assert_frame_equal( result, expected, @@ -187,436 +66,23 @@ def test_arima_plus_predict_explain_default( ) -@pytest.mark.parametrize("id_col_name", [None, "id"]) -def test_arima_plus_predict_params( - time_series_arima_plus_model: forecasting.ARIMAPlus, - time_series_arima_plus_model_w_id: forecasting.ARIMAPlus, - id_col_name, -): - utc = pytz.utc - predictions = ( - ( - time_series_arima_plus_model_w_id - if id_col_name - else time_series_arima_plus_model - ) - .predict(horizon=4, confidence_level=0.9) - .to_pandas() - ) - assert predictions.shape == ((8, 9) if id_col_name else (4, 8)) - result = predictions[["forecast_timestamp", "forecast_value"]] - if id_col_name: - result["id"] = predictions[["id"]] - result = result[["id", "forecast_timestamp", "forecast_value"]] - - if id_col_name: - expected = pd.DataFrame( - { - "id": ["1", "2", "1", "2", "1", "2", "1", "2"], - "forecast_timestamp": [ - datetime(2017, 8, 2, tzinfo=utc), - datetime(2017, 8, 2, tzinfo=utc), - datetime(2017, 8, 3, tzinfo=utc), - datetime(2017, 8, 3, tzinfo=utc), - datetime(2017, 8, 4, tzinfo=utc), - datetime(2017, 8, 4, tzinfo=utc), - datetime(2017, 8, 5, tzinfo=utc), - datetime(2017, 8, 5, tzinfo=utc), - ], - "forecast_value": [ - 2634.796023, - 2634.796023, - 2621.332461, - 2621.332461, - 2396.095462, - 2396.095462, - 1781.623071, - 1781.623071, - ], - } - ) - expected["id"] = expected["id"].astype("string[pyarrow]") - else: - expected = pd.DataFrame( - { - "forecast_timestamp": [ - datetime(2017, 8, 2, tzinfo=utc), - datetime(2017, 8, 3, tzinfo=utc), - datetime(2017, 8, 4, tzinfo=utc), - datetime(2017, 8, 5, tzinfo=utc), - ], - "forecast_value": [ - 2634.796023, - 2621.332461, - 2396.095462, - 1781.623071, - ], - } - ) - expected["forecast_value"] = expected["forecast_value"].astype(pd.Float64Dtype()) - expected["forecast_timestamp"] = expected["forecast_timestamp"].astype( - pd.ArrowDtype(pa.timestamp("us", tz="UTC")) +def test_model_score_series(time_series_arima_plus_model, new_time_series_df): + result = time_series_arima_plus_model.score( + new_time_series_df["parsed_date"], new_time_series_df["total_visits"] + ).to_pandas() + expected = pd.DataFrame( + { + "mean_absolute_error": [154.742547], + "mean_squared_error": [26844.868855], + "root_mean_squared_error": [163.844038], + "mean_absolute_percentage_error": [6.189702], + "symmetric_mean_absolute_percentage_error": [6.097155], + }, + dtype="Float64", ) - pd.testing.assert_frame_equal( result, expected, rtol=0.1, check_index_type=False, ) - - -@pytest.mark.parametrize("id_col_name", [None, "id"]) -def test_arima_plus_predict_explain_params( - time_series_arima_plus_model: forecasting.ARIMAPlus, - time_series_arima_plus_model_w_id: forecasting.ARIMAPlus, - id_col_name, -): - predictions = ( - ( - time_series_arima_plus_model_w_id - if id_col_name - else time_series_arima_plus_model - ) - .predict_explain(horizon=4, confidence_level=0.9) - .to_pandas() - ) - assert predictions.shape[0] >= 1 - prediction_columns = set(predictions.columns) - expected_columns = { - "time_series_timestamp", - "time_series_type", - "time_series_data", - "time_series_adjusted_data", - "standard_error", - "confidence_level", - "prediction_interval_lower_bound", - "trend", - "seasonal_period_yearly", - "seasonal_period_quarterly", - "seasonal_period_monthly", - "seasonal_period_weekly", - "seasonal_period_daily", - "holiday_effect", - } - if id_col_name: - expected_columns.add("id") - assert expected_columns <= prediction_columns - - -@pytest.mark.parametrize("id_col_name", [None, "id"]) -def test_arima_plus_detect_anomalies( - time_series_arima_plus_model: forecasting.ARIMAPlus, - time_series_arima_plus_model_w_id: forecasting.ARIMAPlus, - new_time_series_df, - new_time_series_df_w_id, - id_col_name, -): - anomalies = ( - ( - time_series_arima_plus_model_w_id - if id_col_name - else time_series_arima_plus_model - ) - .detect_anomalies( - new_time_series_df_w_id if id_col_name else new_time_series_df - ) - .to_pandas() - ) - - if id_col_name: - expected = pd.DataFrame( - { - "is_anomaly": [False, False, False, False, False, False], - "lower_bound": [ - 2229.930578, - 2229.930578, - 2149.645455, - 2149.645455, - 1892.873256, - 1892.873256, - ], - "upper_bound": [ - 3039.6614686, - 3039.6614686, - 3093.019467, - 3093.019467, - 2899.317669, - 2899.317669, - ], - "anomaly_probability": [ - 0.48545926, - 0.48545926, - 0.3856835, - 0.3856835, - 0.314156, - 0.314156, - ], - }, - ) - else: - expected = pd.DataFrame( - { - "is_anomaly": [False, False, False], - "lower_bound": [2229.930578, 2149.645455, 1892.873256], - "upper_bound": [3039.6614686, 3093.019467, 2899.317669], - "anomaly_probability": [0.48545926, 0.3856835, 0.314156], - }, - ) - pd.testing.assert_frame_equal( - anomalies[["is_anomaly", "lower_bound", "upper_bound", "anomaly_probability"]], - expected, - rtol=0.1, - check_index_type=False, - check_dtype=False, - ) - - -@pytest.mark.parametrize("id_col_name", [None, "id"]) -def test_arima_plus_detect_anomalies_params( - time_series_arima_plus_model: forecasting.ARIMAPlus, - time_series_arima_plus_model_w_id: forecasting.ARIMAPlus, - new_time_series_df, - new_time_series_df_w_id, - id_col_name, -): - anomalies = ( - ( - time_series_arima_plus_model_w_id - if id_col_name - else time_series_arima_plus_model - ) - .detect_anomalies( - new_time_series_df_w_id if id_col_name else new_time_series_df, - anomaly_prob_threshold=0.7, - ) - .to_pandas() - ) - if id_col_name: - expected = pd.DataFrame( - { - "is_anomaly": [False, False, False, False, False, False], - "lower_bound": [ - 2420.11419, - 2420.11419, - 2360.1870, - 2360.1870, - 2086.0609, - 2086.0609, - ], - "upper_bound": [ - 2849.47785, - 2849.47785, - 2826.54981, - 2826.54981, - 2621.165188, - 2621.165188, - ], - "anomaly_probability": [ - 0.485459, - 0.485459, - 0.385683, - 0.385683, - 0.314156, - 0.314156, - ], - }, - ) - else: - expected = pd.DataFrame( - { - "is_anomaly": [False, False, False], - "lower_bound": [2420.11419, 2360.1870, 2086.0609], - "upper_bound": [2849.47785, 2826.54981, 2621.165188], - "anomaly_probability": [0.485459, 0.385683, 0.314156], - }, - ) - pd.testing.assert_frame_equal( - anomalies[["is_anomaly", "lower_bound", "upper_bound", "anomaly_probability"]] - .sort_values("anomaly_probability") - .reset_index(drop=True), - expected.sort_values("anomaly_probability").reset_index(drop=True), - rtol=0.1, - check_index_type=False, - check_dtype=False, - ) - - -@pytest.mark.parametrize("id_col_name", [None, "id"]) -def test_arima_plus_score( - time_series_arima_plus_model: forecasting.ARIMAPlus, - time_series_arima_plus_model_w_id: forecasting.ARIMAPlus, - new_time_series_df, - new_time_series_df_w_id, - id_col_name, -): - if id_col_name: - result = ( - time_series_arima_plus_model_w_id.score( - new_time_series_df_w_id[["parsed_date"]], - new_time_series_df_w_id[["total_visits"]], - new_time_series_df_w_id[["id"]], - ) - .to_pandas() - .sort_values("id") - .reset_index(drop=True) - ) - else: - result = time_series_arima_plus_model.score( - new_time_series_df[["parsed_date"]], new_time_series_df[["total_visits"]] - ).to_pandas() - if id_col_name: - expected = pd.DataFrame( - { - "id": ["2", "1"], - "mean_absolute_error": [120.011007, 120.011007], - "mean_squared_error": [14562.562359, 14562.562359], - "root_mean_squared_error": [120.675442, 120.675442], - "mean_absolute_percentage_error": [4.80044, 4.80044], - "symmetric_mean_absolute_percentage_error": [4.744332, 4.744332], - }, - dtype="Float64", - ) - expected["id"] = expected["id"].astype(str).str.replace(r"\.0$", "", regex=True) - expected["id"] = expected["id"].astype("string[pyarrow]") - expected = expected.sort_values("id") - expected = expected.reset_index(drop=True) - else: - expected = pd.DataFrame( - { - "mean_absolute_error": [120.0110074], - "mean_squared_error": [14562.5623594], - "root_mean_squared_error": [120.675442], - "mean_absolute_percentage_error": [4.80044], - "symmetric_mean_absolute_percentage_error": [4.744332], - }, - dtype="Float64", - ) - pd.testing.assert_frame_equal( - result[expected.columns], - expected, - rtol=0.1, - check_index_type=False, - check_dtype=False, - ) - - -@pytest.mark.parametrize("id_col_name", [None, "id"]) -def test_arima_plus_summary( - time_series_arima_plus_model: forecasting.ARIMAPlus, - time_series_arima_plus_model_w_id: forecasting.ARIMAPlus, - id_col_name, -): - result = ( - time_series_arima_plus_model_w_id - if id_col_name - else time_series_arima_plus_model - ).summary() - assert result.shape == ((2, 13) if id_col_name else (1, 12)) - expected_columns = ( - [id_col_name] + ARIMA_EVALUATE_OUTPUT_COL - if id_col_name - else ARIMA_EVALUATE_OUTPUT_COL - ) - assert all(column in result.columns for column in expected_columns) - - -@pytest.mark.parametrize("id_col_name", [None, "id"]) -def test_arima_plus_summary_show_all_candidates( - time_series_arima_plus_model: forecasting.ARIMAPlus, - time_series_arima_plus_model_w_id: forecasting.ARIMAPlus, - id_col_name, -): - result = ( - time_series_arima_plus_model_w_id - if id_col_name - else time_series_arima_plus_model - ).summary( - show_all_candidate_models=True, - ) - assert result.shape[0] > 1 - expected_columns = ( - [id_col_name] + ARIMA_EVALUATE_OUTPUT_COL - if id_col_name - else ARIMA_EVALUATE_OUTPUT_COL - ) - assert all(column in result.columns for column in expected_columns) - - -@pytest.mark.parametrize("id_col_name", [None, "id"]) -def test_arima_plus_score_series( - time_series_arima_plus_model: forecasting.ARIMAPlus, - time_series_arima_plus_model_w_id: forecasting.ARIMAPlus, - new_time_series_df, - new_time_series_df_w_id, - id_col_name, -): - if id_col_name: - result = ( - time_series_arima_plus_model_w_id.score( - new_time_series_df_w_id["parsed_date"], - new_time_series_df_w_id["total_visits"], - new_time_series_df_w_id["id"], - ) - .to_pandas() - .sort_values("id") - .reset_index(drop=True) - ) - else: - result = time_series_arima_plus_model.score( - new_time_series_df["parsed_date"], new_time_series_df["total_visits"] - ).to_pandas() - if id_col_name: - expected = pd.DataFrame( - { - "id": ["2", "1"], - "mean_absolute_error": [120.011007, 120.011007], - "mean_squared_error": [14562.562359, 14562.562359], - "root_mean_squared_error": [120.675442, 120.675442], - "mean_absolute_percentage_error": [4.80044, 4.80044], - "symmetric_mean_absolute_percentage_error": [4.744332, 4.744332], - }, - dtype="Float64", - ) - expected["id"] = expected["id"].astype(str).str.replace(r"\.0$", "", regex=True) - expected["id"] = expected["id"].astype("string[pyarrow]") - expected = expected.sort_values("id") - expected = expected.reset_index(drop=True) - else: - expected = pd.DataFrame( - { - "mean_absolute_error": [120.0110074], - "mean_squared_error": [14562.5623594], - "root_mean_squared_error": [120.675442], - "mean_absolute_percentage_error": [4.80044], - "symmetric_mean_absolute_percentage_error": [4.744332], - }, - dtype="Float64", - ) - pd.testing.assert_frame_equal( - result[expected.columns], - expected, - rtol=0.1, - check_index_type=False, - check_dtype=False, - ) - - -@pytest.mark.parametrize("id_col_name", [None, "id"]) -def test_arima_plus_summary_series( - time_series_arima_plus_model: forecasting.ARIMAPlus, - time_series_arima_plus_model_w_id: forecasting.ARIMAPlus, - id_col_name, -): - result = ( - time_series_arima_plus_model_w_id - if id_col_name - else time_series_arima_plus_model - ).summary() - assert result.shape == ((2, 13) if id_col_name else (1, 12)) - expected_columns = ( - [id_col_name] + ARIMA_EVALUATE_OUTPUT_COL - if id_col_name - else ARIMA_EVALUATE_OUTPUT_COL - ) - assert all(column in result.columns for column in expected_columns) diff --git a/tests/system/small/ml/test_imported.py b/tests/system/small/ml/test_imported.py index 2b8d04c3aef..d3055670668 100644 --- a/tests/system/small/ml/test_imported.py +++ b/tests/system/small/ml/test_imported.py @@ -32,9 +32,7 @@ def test_tensorflow_create_model_default_session(imported_tensorflow_model_path) def test_tensorflow_model_predict(imported_tensorflow_model, llm_text_df): df = llm_text_df.rename(columns={"prompt": "input"}) - predictions = imported_tensorflow_model.predict(df).to_pandas() - assert predictions.shape == (3, 2) - result = predictions[["dense_1"]] + result = imported_tensorflow_model.predict(df).to_pandas() # The values are non-human-readable. As they are a dense layer of Neural Network. # And since it is pretrained and imported, the model is a opaque-box. # We may want to switch to better test model and cases. @@ -51,7 +49,6 @@ def test_tensorflow_model_predict(imported_tensorflow_model, llm_text_df): result, expected, check_exact=False, - check_dtype=False, atol=0.1, ) @@ -70,14 +67,12 @@ def test_onnx_create_model(imported_onnx_model): def test_onnx_create_model_default_session(imported_onnx_model_path): - model = imported.ONNXModel(model_path=imported_onnx_model_path) + model = imported.TensorFlowModel(model_path=imported_onnx_model_path) assert model is not None def test_onnx_model_predict(imported_onnx_model, onnx_iris_df): - predictions = imported_onnx_model.predict(onnx_iris_df).to_pandas() - assert predictions.shape == (3, 7) - result = predictions[["label", "probabilities"]] + result = imported_onnx_model.predict(onnx_iris_df).to_pandas() value1 = np.array([0.9999993443489075, 0.0, 0.0]) value2 = np.array([0.0, 0.0, 0.9999993443489075]) expected = pd.DataFrame( @@ -91,7 +86,6 @@ def test_onnx_model_predict(imported_onnx_model, onnx_iris_df): result, expected, check_exact=False, - check_dtype=False, atol=0.1, ) @@ -100,43 +94,3 @@ def test_onnx_model_to_gbq(imported_onnx_model: imported.ONNXModel, dataset_id: imported_onnx_model.to_gbq(f"{dataset_id}.test_onnx_model", replace=True) with pytest.raises(google.api_core.exceptions.Conflict): imported_onnx_model.to_gbq(f"{dataset_id}.test_onnx_model") - - -def test_xgboost_create_model(imported_xgboost_model): - # Model creation doesn't return error - assert imported_xgboost_model is not None - - -def test_xgboost_create_model_default_session(imported_xgboost_array_model_path): - model = imported.XGBoostModel(model_path=imported_xgboost_array_model_path) - assert model is not None - - -def test_xgboost_model_predict(imported_xgboost_model, xgboost_iris_df): - predictions = imported_xgboost_model.predict(xgboost_iris_df).to_pandas() - assert predictions.shape == (3, 5) - result = predictions[["predicted_label"]] - value1 = np.array([0.00362173, 0.01580198, 0.98057634]) - value2 = np.array([0.00349651, 0.00999565, 0.98650789]) - value3 = np.array([0.00561748, 0.0108124, 0.98357016]) - expected = pd.DataFrame( - { - "predicted_label": [value1, value2, value3], - }, - index=pd.Index([0, 1, 2], dtype="Int64"), - ) - pd.testing.assert_frame_equal( - result, - expected, - check_exact=False, - check_dtype=False, - atol=0.1, - ) - - -def test_xgboost_model_to_gbq( - imported_xgboost_model: imported.XGBoostModel, dataset_id: str -): - imported_xgboost_model.to_gbq(f"{dataset_id}.test_xgboost_model", replace=True) - with pytest.raises(google.api_core.exceptions.Conflict): - imported_xgboost_model.to_gbq(f"{dataset_id}.test_xgboost_model") diff --git a/tests/system/small/ml/test_impute.py b/tests/system/small/ml/test_impute.py deleted file mode 100644 index 46a614d7033..00000000000 --- a/tests/system/small/ml/test_impute.py +++ /dev/null @@ -1,89 +0,0 @@ -# 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. -# 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 as pd - -from bigframes.ml import impute - - -def test_simple_imputer_fit_transform_default_params(missing_values_penguins_df): - imputer = impute.SimpleImputer(strategy="mean") - result = imputer.fit_transform( - missing_values_penguins_df[ - ["culmen_length_mm", "culmen_depth_mm", "flipper_length_mm"] - ] - ).to_pandas() - - expected = pd.DataFrame( - { - "imputer_culmen_length_mm": [39.5, 38.5, 37.9], - "imputer_culmen_depth_mm": [17.65, 17.2, 18.1], - "imputer_flipper_length_mm": [184.5, 181.0, 188.0], - }, - dtype="Float64", - index=pd.Index([0, 1, 2], dtype="Int64"), - ) - - pd.testing.assert_frame_equal(result, expected) - - -def test_simple_imputer_series(missing_values_penguins_df): - imputer = impute.SimpleImputer(strategy="mean") - imputer.fit(missing_values_penguins_df["culmen_depth_mm"]) - - result = imputer.transform( - missing_values_penguins_df["culmen_depth_mm"] - ).to_pandas() - - expected = pd.DataFrame( - { - "imputer_culmen_depth_mm": [17.65, 17.2, 18.1], - }, - dtype="Float64", - index=pd.Index([0, 1, 2], dtype="Int64"), - ) - - pd.testing.assert_frame_equal(result, expected, rtol=0.1) - - -def test_simple_imputer_save_load_mean(missing_values_penguins_df, dataset_id): - transformer = impute.SimpleImputer(strategy="mean") - transformer.fit( - missing_values_penguins_df[ - ["culmen_length_mm", "culmen_depth_mm", "flipper_length_mm"] - ] - ) - - reloaded_transformer = transformer.to_gbq( - f"{dataset_id}.temp_configured_model", replace=True - ) - assert isinstance(reloaded_transformer, impute.SimpleImputer) - assert reloaded_transformer.strategy == transformer.strategy - assert reloaded_transformer._bqml_model is not None - - -def test_simple_imputer_save_load_most_frequent(missing_values_penguins_df, dataset_id): - transformer = impute.SimpleImputer(strategy="most_frequent") - transformer.fit( - missing_values_penguins_df[ - ["culmen_length_mm", "culmen_depth_mm", "flipper_length_mm"] - ] - ) - - reloaded_transformer = transformer.to_gbq( - f"{dataset_id}.temp_configured_model", replace=True - ) - assert isinstance(reloaded_transformer, impute.SimpleImputer) - assert reloaded_transformer.strategy == transformer.strategy - assert reloaded_transformer._bqml_model is not None diff --git a/tests/system/small/ml/test_linear_model.py b/tests/system/small/ml/test_linear_model.py index da9fc8e14f8..3a8232ed9ec 100644 --- a/tests/system/small/ml/test_linear_model.py +++ b/tests/system/small/ml/test_linear_model.py @@ -12,14 +12,10 @@ # See the License for the specific language governing permissions and # limitations under the License. -import re - import google.api_core.exceptions import pandas import pytest -from bigframes.ml import linear_model - def test_linear_reg_model_score(penguins_linear_model, penguins_df_default_index): df = penguins_df_default_index.dropna() @@ -95,105 +91,25 @@ def test_linear_reg_model_score_series( def test_linear_reg_model_predict(penguins_linear_model, new_penguins_df): predictions = penguins_linear_model.predict(new_penguins_df).to_pandas() - assert predictions.shape == (3, 8) - result = predictions[["predicted_body_mass_g"]] expected = pandas.DataFrame( {"predicted_body_mass_g": [4030.1, 3280.8, 3177.9]}, dtype="Float64", index=pandas.Index([1633, 1672, 1690], name="tag_number", dtype="Int64"), ) pandas.testing.assert_frame_equal( - result.sort_index(), + predictions.sort_index(), expected, check_exact=False, rtol=0.1, ) -def test_linear_reg_model_predict_explain(penguins_linear_model, new_penguins_df): - predictions = penguins_linear_model.predict_explain(new_penguins_df).to_pandas() - assert predictions.shape == (3, 12) - result = predictions[["predicted_body_mass_g", "approximation_error"]] - expected = pandas.DataFrame( - { - "predicted_body_mass_g": [4030.1, 3280.8, 3177.9], - "approximation_error": [ - 0.0, - 0.0, - 0.0, - ], - }, - dtype="Float64", - index=pandas.Index([1633, 1672, 1690], name="tag_number", dtype="Int64"), - ) - pandas.testing.assert_frame_equal( - result.sort_index(), - expected, - check_exact=False, - rtol=0.1, - ) - - -def test_linear_model_predict_explain_top_k_features( - penguins_logistic_model: linear_model.LinearRegression, new_penguins_df -): - top_k_features = 0 - - with pytest.raises( - ValueError, - match=re.escape(f"top_k_features must be at least 1, but is {top_k_features}."), - ): - penguins_logistic_model.predict_explain( - new_penguins_df, top_k_features=top_k_features - ).to_pandas() - - -def test_linear_reg_model_predict_params( - penguins_linear_model: linear_model.LinearRegression, new_penguins_df -): - predictions = penguins_linear_model.predict(new_penguins_df).to_pandas() - assert predictions.shape[0] >= 1 - prediction_columns = set(predictions.columns) - expected_columns = { - "predicted_body_mass_g", - "species", - "island", - "culmen_length_mm", - "culmen_depth_mm", - "flipper_length_mm", - "body_mass_g", - "sex", - } - assert expected_columns <= prediction_columns - - -def test_linear_reg_model_predict_explain_params( - penguins_linear_model: linear_model.LinearRegression, new_penguins_df -): - predictions = penguins_linear_model.predict_explain(new_penguins_df).to_pandas() - assert predictions.shape[0] >= 1 - prediction_columns = set(predictions.columns) - expected_columns = { - "predicted_body_mass_g", - "top_feature_attributions", - "baseline_prediction_value", - "prediction_value", - "approximation_error", - "species", - "island", - "culmen_length_mm", - "culmen_depth_mm", - "flipper_length_mm", - "body_mass_g", - "sex", - } - assert expected_columns <= prediction_columns - - def test_to_gbq_saved_linear_reg_model_scores( - penguins_linear_model, table_id_unique, penguins_df_default_index + penguins_linear_model, dataset_id, penguins_df_default_index ): - saved_model = penguins_linear_model.to_gbq(table_id_unique, replace=True) + saved_model = penguins_linear_model.to_gbq( + f"{dataset_id}.test_penguins_model", replace=True + ) df = penguins_df_default_index.dropna() X_test = df[ [ @@ -228,10 +144,10 @@ def test_to_gbq_saved_linear_reg_model_scores( ) -def test_to_gbq_replace(penguins_linear_model, table_id_unique): - penguins_linear_model.to_gbq(table_id_unique, replace=True) +def test_to_gbq_replace(penguins_linear_model, dataset_id): + penguins_linear_model.to_gbq(f"{dataset_id}.test_penguins_model", replace=True) with pytest.raises(google.api_core.exceptions.Conflict): - penguins_linear_model.to_gbq(table_id_unique) + penguins_linear_model.to_gbq(f"{dataset_id}.test_penguins_model") def test_logistic_model_score(penguins_logistic_model, penguins_df_default_index): @@ -306,85 +222,27 @@ def test_logistic_model_score_series( ) -def test_logistic_model_predict(penguins_logistic_model, new_penguins_df): +def test_logsitic_model_predict(penguins_logistic_model, new_penguins_df): predictions = penguins_logistic_model.predict(new_penguins_df).to_pandas() - assert predictions.shape == (3, 9) - result = predictions[["predicted_sex"]] expected = pandas.DataFrame( {"predicted_sex": ["MALE", "MALE", "FEMALE"]}, dtype="string[pyarrow]", index=pandas.Index([1633, 1672, 1690], name="tag_number", dtype="Int64"), ) pandas.testing.assert_frame_equal( - result.sort_index(), + predictions.sort_index(), expected, check_exact=False, rtol=0.1, ) -def test_logistic_model_predict_explain_top_k_features( - penguins_logistic_model: linear_model.LogisticRegression, new_penguins_df +def test_logsitic_model_to_gbq_saved_score( + penguins_logistic_model, dataset_id, penguins_df_default_index ): - top_k_features = 0 - - with pytest.raises( - ValueError, - match=re.escape(f"top_k_features must be at least 1, but is {top_k_features}."), - ): - penguins_logistic_model.predict_explain( - new_penguins_df, top_k_features=top_k_features - ).to_pandas() - - -def test_logistic_model_predict_params( - penguins_logistic_model: linear_model.LogisticRegression, new_penguins_df -): - predictions = penguins_logistic_model.predict(new_penguins_df).to_pandas() - assert predictions.shape[0] >= 1 - prediction_columns = set(predictions.columns) - expected_columns = { - "predicted_sex", - "predicted_sex_probs", - "species", - "island", - "culmen_length_mm", - "culmen_depth_mm", - "flipper_length_mm", - "body_mass_g", - "sex", - } - assert expected_columns <= prediction_columns - - -def test_logistic_model_predict_explain_params( - penguins_logistic_model: linear_model.LogisticRegression, new_penguins_df -): - predictions = penguins_logistic_model.predict_explain(new_penguins_df).to_pandas() - assert predictions.shape[0] >= 1 - prediction_columns = set(predictions.columns) - expected_columns = { - "predicted_sex", - "probability", - "top_feature_attributions", - "baseline_prediction_value", - "prediction_value", - "approximation_error", - "species", - "island", - "culmen_length_mm", - "culmen_depth_mm", - "flipper_length_mm", - "body_mass_g", - "sex", - } - assert expected_columns <= prediction_columns - - -def test_logistic_model_to_gbq_saved_score( - penguins_logistic_model, table_id_unique, penguins_df_default_index -): - saved_model = penguins_logistic_model.to_gbq(table_id_unique, replace=True) + saved_model = penguins_logistic_model.to_gbq( + f"{dataset_id}.test_penguins_model", replace=True + ) df = penguins_df_default_index.dropna() X_test = df[ [ @@ -417,3 +275,9 @@ def test_logistic_model_to_gbq_saved_score( # int64 Index by default in pandas versus Int64 (nullable) Index in BigQuery DataFrame check_index_type=False, ) + + +def test_logistic_model_to_gbq_replace(penguins_logistic_model, dataset_id): + penguins_logistic_model.to_gbq(f"{dataset_id}.test_penguins_model", replace=True) + with pytest.raises(google.api_core.exceptions.Conflict): + penguins_logistic_model.to_gbq(f"{dataset_id}.test_penguins_model") diff --git a/tests/system/small/ml/test_llm.py b/tests/system/small/ml/test_llm.py new file mode 100644 index 00000000000..79d3c40317d --- /dev/null +++ b/tests/system/small/ml/test_llm.py @@ -0,0 +1,230 @@ +# Copyright 2023 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 unittest import TestCase + +import numpy as np +import pytest + +from bigframes.ml import llm + + +def test_create_text_generator_model(palm2_text_generator_model): + # Model creation doesn't return error + assert palm2_text_generator_model is not None + assert palm2_text_generator_model._bqml_model is not None + + +def test_create_text_generator_32k_model(palm2_text_generator_32k_model): + # Model creation doesn't return error + assert palm2_text_generator_32k_model is not None + assert palm2_text_generator_32k_model._bqml_model is not None + + +@pytest.mark.flaky(retries=2, delay=120) +def test_create_text_generator_model_default_session(bq_connection, llm_text_pandas_df): + import bigframes.pandas as bpd + + bpd.close_session() + bpd.options.bigquery.bq_connection = bq_connection + bpd.options.bigquery.location = "us" + + model = llm.PaLM2TextGenerator() + assert model is not None + assert model._bqml_model is not None + assert model.connection_name.casefold() == "bigframes-dev.us.bigframes-rf-conn" + + llm_text_df = bpd.read_pandas(llm_text_pandas_df) + + df = model.predict(llm_text_df).to_pandas() + TestCase().assertSequenceEqual(df.shape, (3, 1)) + assert "ml_generate_text_llm_result" in df.columns + series = df["ml_generate_text_llm_result"] + assert all(series.str.len() > 20) + + +@pytest.mark.flaky(retries=2, delay=120) +def test_create_text_generator_32k_model_default_session( + bq_connection, llm_text_pandas_df +): + import bigframes.pandas as bpd + + bpd.close_session() + bpd.options.bigquery.bq_connection = bq_connection + bpd.options.bigquery.location = "us" + + model = llm.PaLM2TextGenerator(model_name="text-bison-32k") + assert model is not None + assert model._bqml_model is not None + assert model.connection_name.casefold() == "bigframes-dev.us.bigframes-rf-conn" + + llm_text_df = bpd.read_pandas(llm_text_pandas_df) + + df = model.predict(llm_text_df).to_pandas() + TestCase().assertSequenceEqual(df.shape, (3, 1)) + assert "ml_generate_text_llm_result" in df.columns + series = df["ml_generate_text_llm_result"] + assert all(series.str.len() > 20) + + +@pytest.mark.flaky(retries=2, delay=120) +def test_create_text_generator_model_default_connection(llm_text_pandas_df): + from bigframes import _config + import bigframes.pandas as bpd + + bpd.close_session() + _config.options = _config.Options() # reset configs + + llm_text_df = bpd.read_pandas(llm_text_pandas_df) + + model = llm.PaLM2TextGenerator() + assert model is not None + assert model._bqml_model is not None + assert ( + model.connection_name.casefold() + == "bigframes-dev.us.bigframes-default-connection" + ) + + df = model.predict(llm_text_df).to_pandas() + TestCase().assertSequenceEqual(df.shape, (3, 1)) + assert "ml_generate_text_llm_result" in df.columns + series = df["ml_generate_text_llm_result"] + assert all(series.str.len() > 20) + + +# Marked as flaky only because BQML LLM is in preview, the service only has limited capacity, not stable enough. +@pytest.mark.flaky(retries=2, delay=120) +def test_text_generator_predict_default_params_success( + palm2_text_generator_model, llm_text_df +): + df = palm2_text_generator_model.predict(llm_text_df).to_pandas() + TestCase().assertSequenceEqual(df.shape, (3, 1)) + assert "ml_generate_text_llm_result" in df.columns + series = df["ml_generate_text_llm_result"] + assert all(series.str.len() > 20) + + +@pytest.mark.flaky(retries=2, delay=120) +def test_text_generator_predict_series_default_params_success( + palm2_text_generator_model, llm_text_df +): + df = palm2_text_generator_model.predict(llm_text_df["prompt"]).to_pandas() + TestCase().assertSequenceEqual(df.shape, (3, 1)) + assert "ml_generate_text_llm_result" in df.columns + series = df["ml_generate_text_llm_result"] + assert all(series.str.len() > 20) + + +@pytest.mark.flaky(retries=2, delay=120) +def test_text_generator_predict_arbitrary_col_label_success( + palm2_text_generator_model, llm_text_df +): + llm_text_df = llm_text_df.rename(columns={"prompt": "arbitrary"}) + df = palm2_text_generator_model.predict(llm_text_df).to_pandas() + TestCase().assertSequenceEqual(df.shape, (3, 1)) + assert "ml_generate_text_llm_result" in df.columns + series = df["ml_generate_text_llm_result"] + assert all(series.str.len() > 20) + + +@pytest.mark.flaky(retries=2, delay=120) +def test_text_generator_predict_with_params_success( + palm2_text_generator_model, llm_text_df +): + df = palm2_text_generator_model.predict( + llm_text_df, temperature=0.5, max_output_tokens=100, top_k=20, top_p=0.5 + ).to_pandas() + TestCase().assertSequenceEqual(df.shape, (3, 1)) + assert "ml_generate_text_llm_result" in df.columns + series = df["ml_generate_text_llm_result"] + assert all(series.str.len() > 20) + + +def test_create_embedding_generator_model(palm2_embedding_generator_model): + # Model creation doesn't return error + assert palm2_embedding_generator_model is not None + assert palm2_embedding_generator_model._bqml_model is not None + + +def test_create_embedding_generator_multilingual_model( + palm2_embedding_generator_multilingual_model, +): + # Model creation doesn't return error + assert palm2_embedding_generator_multilingual_model is not None + assert palm2_embedding_generator_multilingual_model._bqml_model is not None + + +def test_create_text_embedding_generator_model_defaults(bq_connection): + import bigframes.pandas as bpd + + bpd.close_session() + bpd.options.bigquery.bq_connection = bq_connection + bpd.options.bigquery.location = "us" + + model = llm.PaLM2TextEmbeddingGenerator() + assert model is not None + assert model._bqml_model is not None + + +def test_create_text_embedding_generator_multilingual_model_defaults(bq_connection): + import bigframes.pandas as bpd + + bpd.close_session() + bpd.options.bigquery.bq_connection = bq_connection + bpd.options.bigquery.location = "us" + + model = llm.PaLM2TextEmbeddingGenerator( + model_name="textembedding-gecko-multilingual" + ) + assert model is not None + assert model._bqml_model is not None + + +@pytest.mark.flaky(retries=2, delay=120) +def test_embedding_generator_predict_success( + palm2_embedding_generator_model, llm_text_df +): + df = palm2_embedding_generator_model.predict(llm_text_df).to_pandas() + TestCase().assertSequenceEqual(df.shape, (3, 1)) + assert "text_embedding" in df.columns + series = df["text_embedding"] + value = series[0] + assert isinstance(value, np.ndarray) + assert value.size == 768 + + +@pytest.mark.flaky(retries=2, delay=120) +def test_embedding_generator_multilingual_predict_success( + palm2_embedding_generator_multilingual_model, llm_text_df +): + df = palm2_embedding_generator_multilingual_model.predict(llm_text_df).to_pandas() + TestCase().assertSequenceEqual(df.shape, (3, 1)) + assert "text_embedding" in df.columns + series = df["text_embedding"] + value = series[0] + assert isinstance(value, np.ndarray) + assert value.size == 768 + + +@pytest.mark.flaky(retries=2, delay=120) +def test_embedding_generator_predict_series_success( + palm2_embedding_generator_model, llm_text_df +): + df = palm2_embedding_generator_model.predict(llm_text_df["prompt"]).to_pandas() + TestCase().assertSequenceEqual(df.shape, (3, 1)) + assert "text_embedding" in df.columns + series = df["text_embedding"] + value = series[0] + assert isinstance(value, np.ndarray) + assert value.size == 768 diff --git a/tests/system/small/ml/test_metrics.py b/tests/system/small/ml/test_metrics.py index ab9c3e4552c..b40982e2829 100644 --- a/tests/system/small/ml/test_metrics.py +++ b/tests/system/small/ml/test_metrics.py @@ -17,10 +17,9 @@ import numpy as np import pandas as pd import pytest +import sklearn.metrics as sklearn_metrics # type: ignore -import bigframes -import bigframes.testing.utils -from bigframes.ml import metrics +import bigframes.ml.metrics def test_r2_score_perfect_fit(session): @@ -33,7 +32,9 @@ def test_r2_score_perfect_fit(session): df = session.read_pandas(pd_df) assert ( - metrics.r2_score(df[["y_true_arbitrary_name"]], df[["y_pred_arbitrary_name"]]) + bigframes.ml.metrics.r2_score( + df[["y_true_arbitrary_name"]], df[["y_pred_arbitrary_name"]] + ) == 1.0 ) @@ -42,7 +43,7 @@ def test_r2_score_bad_fit(session): pd_df = pd.DataFrame({"y_true": [1, 2, 3, 4, 5], "y_pred": [5, 4, 3, 2, 1]}) df = session.read_pandas(pd_df) - assert metrics.r2_score(df[["y_true"]], df[["y_pred"]]) == -3.0 + assert bigframes.ml.metrics.r2_score(df[["y_true"]], df[["y_pred"]]) == -3.0 def test_r2_score_force_finite(session): @@ -55,22 +56,23 @@ def test_r2_score_force_finite(session): ) df = session.read_pandas(pd_df) - assert metrics.r2_score( + assert bigframes.ml.metrics.r2_score( df[["y_true"]], df[["y_pred_1"]], force_finite=False ) == float("-inf") - assert metrics.r2_score(df[["y_true"]], df[["y_pred_1"]]) == 0.0 + assert bigframes.ml.metrics.r2_score(df[["y_true"]], df[["y_pred_1"]]) == 0.0 assert math.isnan( - metrics.r2_score(df[["y_true"]], df[["y_pred_2"]], force_finite=False) + bigframes.ml.metrics.r2_score( + df[["y_true"]], df[["y_pred_2"]], force_finite=False + ) ) - assert metrics.r2_score(df[["y_true"]], df[["y_pred_2"]]) == 1.0 + assert bigframes.ml.metrics.r2_score(df[["y_true"]], df[["y_pred_2"]]) == 1.0 def test_r2_score_ok_fit_matches_sklearn(session): - sklearn_metrics = pytest.importorskip("sklearn.metrics") pd_df = pd.DataFrame({"y_true": [1, 2, 3, 4, 5], "y_pred": [2, 3, 4, 3, 6]}) df = session.read_pandas(pd_df) - bf_result = metrics.r2_score(df[["y_true"]], df[["y_pred"]]) + bf_result = bigframes.ml.metrics.r2_score(df[["y_true"]], df[["y_pred"]]) sklearn_result = sklearn_metrics.r2_score(pd_df[["y_true"]], pd_df[["y_pred"]]) assert math.isclose(bf_result, sklearn_result) @@ -79,7 +81,7 @@ def test_r2_score_series(session): pd_df = pd.DataFrame({"y_true": [1, 7, 3, 2, 5], "y_pred": [1, 7, 3, 2, 5]}) df = session.read_pandas(pd_df) - assert metrics.r2_score(df["y_true"], df["y_pred"]) == 1.0 + assert bigframes.ml.metrics.r2_score(df["y_true"], df["y_pred"]) == 1.0 def test_accuracy_score_perfect_fit(session): @@ -92,7 +94,7 @@ def test_accuracy_score_perfect_fit(session): df = session.read_pandas(pd_df) assert ( - metrics.accuracy_score( + bigframes.ml.metrics.accuracy_score( df[["y_true_arbitrary_name"]], df[["y_pred_arbitrary_name"]] ) == 1.0 @@ -103,22 +105,26 @@ def test_accuracy_score_bad_fit(session): pd_df = pd.DataFrame({"y_true": [0, 2, 1, 3, 4], "y_pred": [0, 1, 2, 3, 4]}) df = session.read_pandas(pd_df) - assert metrics.accuracy_score(df[["y_true"]], df[["y_pred"]]) == 0.6 + assert bigframes.ml.metrics.accuracy_score(df[["y_true"]], df[["y_pred"]]) == 0.6 def test_accuracy_score_not_normailze(session): pd_df = pd.DataFrame({"y_true": [0, 2, 1, 3, 4], "y_pred": [0, 1, 2, 3, 4]}) df = session.read_pandas(pd_df) - assert metrics.accuracy_score(df[["y_true"]], df[["y_pred"]], normalize=False) == 3 + assert ( + bigframes.ml.metrics.accuracy_score( + df[["y_true"]], df[["y_pred"]], normalize=False + ) + == 3 + ) def test_accuracy_score_fit_matches_sklearn(session): - sklearn_metrics = pytest.importorskip("sklearn.metrics") pd_df = pd.DataFrame({"y_true": [1, 2, 3, 4, 5], "y_pred": [2, 3, 4, 3, 6]}) df = session.read_pandas(pd_df) - bf_result = metrics.accuracy_score(df[["y_true"]], df[["y_pred"]]) + bf_result = bigframes.ml.metrics.accuracy_score(df[["y_true"]], df[["y_pred"]]) sklearn_result = sklearn_metrics.accuracy_score( pd_df[["y_true"]], pd_df[["y_pred"]] ) @@ -129,7 +135,7 @@ def test_accuracy_score_series(session): pd_df = pd.DataFrame({"y_true": [1, 7, 3, 2, 5], "y_pred": [1, 7, 3, 2, 5]}) df = session.read_pandas(pd_df) - assert metrics.accuracy_score(df["y_true"], df["y_pred"]) == 1.0 + assert bigframes.ml.metrics.accuracy_score(df["y_true"], df["y_pred"]) == 1.0 def test_roc_curve_binary_classification_prediction_returns_expected(session): @@ -152,7 +158,7 @@ def test_roc_curve_binary_classification_prediction_returns_expected(session): ) df = session.read_pandas(pd_df) - fpr, tpr, thresholds = metrics.roc_curve( + fpr, tpr, thresholds = bigframes.ml.metrics.roc_curve( df[["y_true_arbitrary_name"]], df[["y_score_arbitrary_name"]], drop_intermediate=False, @@ -162,7 +168,7 @@ def test_roc_curve_binary_classification_prediction_returns_expected(session): pd_tpr = tpr.to_pandas() pd_thresholds = thresholds.to_pandas() - bigframes.testing.utils.assert_series_equal( + pd.testing.assert_series_equal( # skip testing the first value, as it is redundant and inconsistent across sklearn versions pd_thresholds[1:], pd.Series( @@ -172,7 +178,7 @@ def test_roc_curve_binary_classification_prediction_returns_expected(session): ), check_index=False, ) - bigframes.testing.utils.assert_series_equal( + pd.testing.assert_series_equal( pd_fpr, pd.Series( [0.0, 0.0, 0.0, 0.25, 0.25, 0.5, 0.5, 0.75, 0.75, 0.75, 1.0], @@ -181,7 +187,7 @@ def test_roc_curve_binary_classification_prediction_returns_expected(session): ), check_index_type=False, ) - bigframes.testing.utils.assert_series_equal( + pd.testing.assert_series_equal( pd_tpr, pd.Series( [ @@ -205,7 +211,6 @@ def test_roc_curve_binary_classification_prediction_returns_expected(session): def test_roc_curve_binary_classification_prediction_matches_sklearn(session): - sklearn_metrics = pytest.importorskip("sklearn.metrics") pd_df = pd.DataFrame( { "y_true": [0, 0, 1, 1, 0, 1, 0, 1, 1, 1], @@ -214,7 +219,7 @@ def test_roc_curve_binary_classification_prediction_matches_sklearn(session): ) df = session.read_pandas(pd_df) - fpr, tpr, thresholds = metrics.roc_curve( + fpr, tpr, thresholds = bigframes.ml.metrics.roc_curve( df[["y_true"]], df[["y_score"]], drop_intermediate=False ) expected_fpr, expected_tpr, expected_thresholds = sklearn_metrics.roc_curve( @@ -222,8 +227,8 @@ def test_roc_curve_binary_classification_prediction_matches_sklearn(session): ) # sklearn returns float64 np arrays - np_fpr = fpr.to_pandas().astype("float64").array.to_numpy() - np_tpr = tpr.to_pandas().astype("float64").array.to_numpy() + np_fpr = fpr.to_pandas().astype("float64").array + np_tpr = tpr.to_pandas().astype("float64").array np_thresholds = thresholds.to_pandas().astype("float64").array np.testing.assert_array_equal( @@ -254,7 +259,7 @@ def test_roc_curve_binary_classification_decision_returns_expected(session): ) df = session.read_pandas(pd_df) - fpr, tpr, thresholds = metrics.roc_curve( + fpr, tpr, thresholds = bigframes.ml.metrics.roc_curve( df[["y_true"]], df[["y_score"]], drop_intermediate=False ) @@ -262,7 +267,7 @@ def test_roc_curve_binary_classification_decision_returns_expected(session): pd_tpr = tpr.to_pandas() pd_thresholds = thresholds.to_pandas() - bigframes.testing.utils.assert_series_equal( + pd.testing.assert_series_equal( # skip testing the first value, as it is redundant and inconsistent across sklearn versions pd_thresholds[1:], pd.Series( @@ -272,7 +277,7 @@ def test_roc_curve_binary_classification_decision_returns_expected(session): ), check_index=False, ) - bigframes.testing.utils.assert_series_equal( + pd.testing.assert_series_equal( pd_fpr, pd.Series( [0.0, 0.0, 1.0], @@ -281,7 +286,7 @@ def test_roc_curve_binary_classification_decision_returns_expected(session): ), check_index_type=False, ) - bigframes.testing.utils.assert_series_equal( + pd.testing.assert_series_equal( pd_tpr, pd.Series( [ @@ -297,7 +302,6 @@ def test_roc_curve_binary_classification_decision_returns_expected(session): def test_roc_curve_binary_classification_decision_matches_sklearn(session): - sklearn_metrics = pytest.importorskip("sklearn.metrics") # Instead of operating on probabilities, assume a 70% decision threshold # has been applied, and operate on the final output y_score = [0.1, 0.4, 0.35, 0.8, 0.65, 0.9, 0.5, 0.3, 0.6, 0.45] @@ -310,7 +314,7 @@ def test_roc_curve_binary_classification_decision_matches_sklearn(session): ) df = session.read_pandas(pd_df) - fpr, tpr, thresholds = metrics.roc_curve( + fpr, tpr, thresholds = bigframes.ml.metrics.roc_curve( df[["y_true"]], df[["y_score"]], drop_intermediate=False ) expected_fpr, expected_tpr, expected_thresholds = sklearn_metrics.roc_curve( @@ -318,8 +322,8 @@ def test_roc_curve_binary_classification_decision_matches_sklearn(session): ) # sklearn returns float64 np arrays - np_fpr = fpr.to_pandas().astype("float64").array.to_numpy() - np_tpr = tpr.to_pandas().astype("float64").array.to_numpy() + np_fpr = fpr.to_pandas().astype("float64").array + np_tpr = tpr.to_pandas().astype("float64").array np_thresholds = thresholds.to_pandas().astype("float64").array np.testing.assert_array_equal( @@ -346,7 +350,7 @@ def test_roc_curve_binary_classification_prediction_series(session): ) df = session.read_pandas(pd_df) - fpr, tpr, thresholds = metrics.roc_curve( + fpr, tpr, thresholds = bigframes.ml.metrics.roc_curve( df["y_true"], df["y_score"], drop_intermediate=False ) @@ -354,7 +358,7 @@ def test_roc_curve_binary_classification_prediction_series(session): pd_tpr = tpr.to_pandas() pd_thresholds = thresholds.to_pandas() - bigframes.testing.utils.assert_series_equal( + pd.testing.assert_series_equal( # skip testing the first value, as it is redundant and inconsistent across sklearn versions pd_thresholds[1:], pd.Series( @@ -364,7 +368,7 @@ def test_roc_curve_binary_classification_prediction_series(session): ), check_index=False, ) - bigframes.testing.utils.assert_series_equal( + pd.testing.assert_series_equal( pd_fpr, pd.Series( [0.0, 0.0, 0.0, 0.25, 0.25, 0.5, 0.5, 0.75, 0.75, 0.75, 1.0], @@ -373,7 +377,7 @@ def test_roc_curve_binary_classification_prediction_series(session): ), check_index_type=False, ) - bigframes.testing.utils.assert_series_equal( + pd.testing.assert_series_equal( pd_tpr, pd.Series( [ @@ -416,7 +420,7 @@ def test_roc_auc_score_returns_expected(session): ) df = session.read_pandas(pd_df) - score = metrics.roc_auc_score( + score = bigframes.ml.metrics.roc_auc_score( df[["y_true_arbitrary_name"]], df[["y_score_arbitrary_name"]] ) @@ -424,7 +428,6 @@ def test_roc_auc_score_returns_expected(session): def test_roc_auc_score_returns_matches_sklearn(session): - sklearn_metrics = pytest.importorskip("sklearn.metrics") pd_df = pd.DataFrame( { "y_true": [0, 0, 1, 1, 0, 1, 0, 1, 1, 1], @@ -433,7 +436,7 @@ def test_roc_auc_score_returns_matches_sklearn(session): ) df = session.read_pandas(pd_df) - score = metrics.roc_auc_score(df[["y_true"]], df[["y_score"]]) + score = bigframes.ml.metrics.roc_auc_score(df[["y_true"]], df[["y_score"]]) expected_score = sklearn_metrics.roc_auc_score( pd_df[["y_true"]], pd_df[["y_score"]] ) @@ -450,7 +453,7 @@ def test_roc_auc_score_series(session): ) df = session.read_pandas(pd_df) - score = metrics.roc_auc_score(df["y_true"], df["y_score"]) + score = bigframes.ml.metrics.roc_auc_score(df["y_true"], df["y_score"]) assert score == 0.625 @@ -459,33 +462,33 @@ def test_auc_invalid_x_size(session): pd_df = pd.DataFrame({"x_arbitrary_name": [0], "y_arbitrary_name": [0]}) df = session.read_pandas(pd_df) with pytest.raises(ValueError): - metrics.auc(df[["x_arbitrary_name"]], df[["y_arbitrary_name"]]) + bigframes.ml.metrics.auc(df[["x_arbitrary_name"]], df[["y_arbitrary_name"]]) def test_auc_nondecreasing_x(session): pd_df = pd.DataFrame({"x": [0, 0, 0.5, 0.5, 1], "y": [0, 0.5, 0.5, 1, 1]}) df = session.read_pandas(pd_df) - assert metrics.auc(df[["x"]], df[["y"]]) == 0.75 + assert bigframes.ml.metrics.auc(df[["x"]], df[["y"]]) == 0.75 def test_auc_nonincreasing_x(session): pd_df = pd.DataFrame({"x": [0, 0, -0.5, -0.5, -1], "y": [0, 0.5, 0.5, 1, 1]}) df = session.read_pandas(pd_df) - assert metrics.auc(df[["x"]], df[["y"]]) == 0.75 + assert bigframes.ml.metrics.auc(df[["x"]], df[["y"]]) == 0.75 def test_auc_nonincreasing_x_negative(session): pd_df = pd.DataFrame({"x": [0, 0, -0.5, -0.5, -1], "y": [0, -0.5, -0.5, -1, -1]}) df = session.read_pandas(pd_df) - assert metrics.auc(df[["x"]], df[["y"]]) == -0.75 + assert bigframes.ml.metrics.auc(df[["x"]], df[["y"]]) == -0.75 def test_auc_series(session): pd_df = pd.DataFrame({"x": [0, 0, 0.5, 0.5, 1], "y": [0, 0.5, 0.5, 1, 1]}) df = session.read_pandas(pd_df) - assert metrics.auc(df["x"], df["y"]) == 0.75 + assert bigframes.ml.metrics.auc(df["x"], df["y"]) == 0.75 def test_confusion_matrix(session): @@ -496,7 +499,7 @@ def test_confusion_matrix(session): } ).astype("Int64") df = session.read_pandas(pd_df) - confusion_matrix = metrics.confusion_matrix( + confusion_matrix = bigframes.ml.metrics.confusion_matrix( df[["y_true_arbitrary_name"]], df[["y_pred_arbitrary_name"]] ) expected_pd_df = pd.DataFrame( @@ -506,7 +509,7 @@ def test_confusion_matrix(session): 2: [0, 1, 2], } ).astype("int64") - bigframes.testing.utils.assert_frame_equal( + pd.testing.assert_frame_equal( confusion_matrix, expected_pd_df, check_index_type=False ) @@ -519,18 +522,22 @@ def test_confusion_matrix_column_index(session): } ).astype("Int64") df = session.read_pandas(pd_df) - confusion_matrix = metrics.confusion_matrix(df[["y_true"]], df[["y_pred"]]) - expected_pd_df = pd.DataFrame( - {1: [1, 0, 1, 0], 2: [0, 0, 2, 0], 3: [0, 0, 0, 0], 4: [0, 1, 0, 1]}, - index=[1, 2, 3, 4], - ).astype("int64") - bigframes.testing.utils.assert_frame_equal( + confusion_matrix = bigframes.ml.metrics.confusion_matrix( + df[["y_true"]], df[["y_pred"]] + ) + expected_pd_df = ( + pd.DataFrame( + {1: [1, 0, 1, 0], 2: [0, 0, 2, 0], 3: [0, 0, 0, 0], 4: [0, 1, 0, 1]} + ) + .astype("int64") + .set_index([pd.Index([1, 2, 3, 4])]) + ) + pd.testing.assert_frame_equal( confusion_matrix, expected_pd_df, check_index_type=False ) def test_confusion_matrix_matches_sklearn(session): - sklearn_metrics = pytest.importorskip("sklearn.metrics") pd_df = pd.DataFrame( { "y_true": [2, 3, 3, 3, 4, 1], @@ -538,18 +545,19 @@ def test_confusion_matrix_matches_sklearn(session): } ).astype("Int64") df = session.read_pandas(pd_df) - confusion_matrix = metrics.confusion_matrix(df[["y_true"]], df[["y_pred"]]) + confusion_matrix = bigframes.ml.metrics.confusion_matrix( + df[["y_true"]], df[["y_pred"]] + ) expected_confusion_matrix = sklearn_metrics.confusion_matrix( pd_df[["y_true"]], pd_df[["y_pred"]] ) expected_pd_df = pd.DataFrame(expected_confusion_matrix) - bigframes.testing.utils.assert_frame_equal( + pd.testing.assert_frame_equal( confusion_matrix, expected_pd_df, check_index_type=False ) def test_confusion_matrix_str_matches_sklearn(session): - sklearn_metrics = pytest.importorskip("sklearn.metrics") pd_df = pd.DataFrame( { "y_true": ["cat", "ant", "cat", "cat", "ant", "bird"], @@ -557,15 +565,17 @@ def test_confusion_matrix_str_matches_sklearn(session): } ).astype("str") df = session.read_pandas(pd_df) - confusion_matrix = metrics.confusion_matrix(df[["y_true"]], df[["y_pred"]]) + confusion_matrix = bigframes.ml.metrics.confusion_matrix( + df[["y_true"]], df[["y_pred"]] + ) expected_confusion_matrix = sklearn_metrics.confusion_matrix( pd_df[["y_true"]], pd_df[["y_pred"]] ) - expected_pd_df = pd.DataFrame( - expected_confusion_matrix, index=["ant", "bird", "cat"] + expected_pd_df = pd.DataFrame(expected_confusion_matrix).set_index( + [pd.Index(["ant", "bird", "cat"])] ) expected_pd_df.columns = pd.Index(["ant", "bird", "cat"]) - bigframes.testing.utils.assert_frame_equal( + pd.testing.assert_frame_equal( confusion_matrix, expected_pd_df, check_index_type=False ) @@ -578,7 +588,7 @@ def test_confusion_matrix_series(session): } ).astype("Int64") df = session.read_pandas(pd_df) - confusion_matrix = metrics.confusion_matrix(df["y_true"], df["y_pred"]) + confusion_matrix = bigframes.ml.metrics.confusion_matrix(df["y_true"], df["y_pred"]) expected_pd_df = pd.DataFrame( { 0: [2, 0, 1], @@ -586,7 +596,7 @@ def test_confusion_matrix_series(session): 2: [0, 1, 2], } ).astype("int64") - bigframes.testing.utils.assert_frame_equal( + pd.testing.assert_frame_equal( confusion_matrix, expected_pd_df, check_index_type=False ) @@ -599,20 +609,17 @@ def test_recall_score(session): } ).astype("Int64") df = session.read_pandas(pd_df) - recall = metrics.recall_score( + recall = bigframes.ml.metrics.recall_score( df[["y_true_arbitrary_name"]], df[["y_pred_arbitrary_name"]], average=None ) expected_values = [1.000000, 0.000000, 0.666667] expected_index = [0, 1, 2] expected_recall = pd.Series(expected_values, index=expected_index) - bigframes.testing.utils.assert_series_equal( - recall, expected_recall, check_index_type=False - ) + pd.testing.assert_series_equal(recall, expected_recall, check_index_type=False) def test_recall_score_matches_sklearn(session): - sklearn_metrics = pytest.importorskip("sklearn.metrics") pd_df = pd.DataFrame( { "y_true": [2, 0, 2, 2, 0, 1], @@ -620,19 +627,18 @@ def test_recall_score_matches_sklearn(session): } ).astype("Int64") df = session.read_pandas(pd_df) - recall = metrics.recall_score(df[["y_true"]], df[["y_pred"]], average=None) + recall = bigframes.ml.metrics.recall_score( + df[["y_true"]], df[["y_pred"]], average=None + ) expected_values = sklearn_metrics.recall_score( pd_df[["y_true"]], pd_df[["y_pred"]], average=None ) expected_index = [0, 1, 2] expected_recall = pd.Series(expected_values, index=expected_index) - bigframes.testing.utils.assert_series_equal( - recall, expected_recall, check_index_type=False - ) + pd.testing.assert_series_equal(recall, expected_recall, check_index_type=False) def test_recall_score_str_matches_sklearn(session): - sklearn_metrics = pytest.importorskip("sklearn.metrics") pd_df = pd.DataFrame( { "y_true": ["cat", "ant", "cat", "cat", "ant", "bird"], @@ -640,15 +646,15 @@ def test_recall_score_str_matches_sklearn(session): } ).astype("str") df = session.read_pandas(pd_df) - recall = metrics.recall_score(df[["y_true"]], df[["y_pred"]], average=None) + recall = bigframes.ml.metrics.recall_score( + df[["y_true"]], df[["y_pred"]], average=None + ) expected_values = sklearn_metrics.recall_score( pd_df[["y_true"]], pd_df[["y_pred"]], average=None ) expected_index = ["ant", "bird", "cat"] expected_recall = pd.Series(expected_values, index=expected_index) - bigframes.testing.utils.assert_series_equal( - recall, expected_recall, check_index_type=False - ) + pd.testing.assert_series_equal(recall, expected_recall, check_index_type=False) def test_recall_score_series(session): @@ -659,14 +665,12 @@ def test_recall_score_series(session): } ).astype("Int64") df = session.read_pandas(pd_df) - recall = metrics.recall_score(df["y_true"], df["y_pred"], average=None) + recall = bigframes.ml.metrics.recall_score(df["y_true"], df["y_pred"], average=None) expected_values = [1.000000, 0.000000, 0.666667] expected_index = [0, 1, 2] expected_recall = pd.Series(expected_values, index=expected_index) - bigframes.testing.utils.assert_series_equal( - recall, expected_recall, check_index_type=False - ) + pd.testing.assert_series_equal(recall, expected_recall, check_index_type=False) def test_precision_score(session): @@ -677,20 +681,19 @@ def test_precision_score(session): } ).astype("Int64") df = session.read_pandas(pd_df) - precision_score = metrics.precision_score( + precision_score = bigframes.ml.metrics.precision_score( df[["y_true_arbitrary_name"]], df[["y_pred_arbitrary_name"]], average=None ) expected_values = [0.666667, 0.000000, 0.666667] expected_index = [0, 1, 2] expected_precision = pd.Series(expected_values, index=expected_index) - bigframes.testing.utils.assert_series_equal( + pd.testing.assert_series_equal( precision_score, expected_precision, check_index_type=False ) def test_precision_score_matches_sklearn(session): - sklearn_metrics = pytest.importorskip("sklearn.metrics") pd_df = pd.DataFrame( { "y_true": [2, 0, 2, 2, 0, 1], @@ -698,8 +701,7 @@ def test_precision_score_matches_sklearn(session): } ).astype("Int64") df = session.read_pandas(pd_df) - # TODO(b/340872435): fix type error - precision_score = metrics.precision_score( + precision_score = bigframes.ml.metrics.precision_score( df[["y_true"]], df[["y_pred"]], average=None ) expected_values = sklearn_metrics.precision_score( @@ -707,13 +709,12 @@ def test_precision_score_matches_sklearn(session): ) expected_index = [0, 1, 2] expected_precision = pd.Series(expected_values, index=expected_index) - bigframes.testing.utils.assert_series_equal( + pd.testing.assert_series_equal( precision_score, expected_precision, check_index_type=False ) def test_precision_score_str_matches_sklearn(session): - sklearn_metrics = pytest.importorskip("sklearn.metrics") pd_df = pd.DataFrame( { "y_true": ["cat", "ant", "cat", "cat", "ant", "bird"], @@ -721,7 +722,7 @@ def test_precision_score_str_matches_sklearn(session): } ).astype("str") df = session.read_pandas(pd_df) - precision_score = metrics.precision_score( + precision_score = bigframes.ml.metrics.precision_score( df[["y_true"]], df[["y_pred"]], average=None ) expected_values = sklearn_metrics.precision_score( @@ -729,7 +730,7 @@ def test_precision_score_str_matches_sklearn(session): ) expected_index = ["ant", "bird", "cat"] expected_precision = pd.Series(expected_values, index=expected_index) - bigframes.testing.utils.assert_series_equal( + pd.testing.assert_series_equal( precision_score, expected_precision, check_index_type=False ) @@ -742,80 +743,18 @@ def test_precision_score_series(session): } ).astype("Int64") df = session.read_pandas(pd_df) - precision_score = metrics.precision_score(df["y_true"], df["y_pred"], average=None) + precision_score = bigframes.ml.metrics.precision_score( + df["y_true"], df["y_pred"], average=None + ) expected_values = [0.666667, 0.000000, 0.666667] expected_index = [0, 1, 2] expected_precision = pd.Series(expected_values, index=expected_index) - bigframes.testing.utils.assert_series_equal( + pd.testing.assert_series_equal( precision_score, expected_precision, check_index_type=False ) -@pytest.mark.parametrize( - ("pos_label", "expected_score"), - [ - ("a", 1 / 3), - ("b", 0), - ], -) -def test_precision_score_binary(session, pos_label, expected_score): - pd_df = pd.DataFrame( - { - "y_true": ["a", "a", "a", "b", "b"], - "y_pred": ["b", "b", "a", "a", "a"], - } - ) - df = session.read_pandas(pd_df) - - precision_score = metrics.precision_score( - df["y_true"], df["y_pred"], average="binary", pos_label=pos_label - ) - - assert precision_score == pytest.approx(expected_score) - - -def test_precision_score_binary_default_arguments(session): - pd_df = pd.DataFrame( - { - "y_true": [1, 1, 1, 0, 0], - "y_pred": [0, 0, 1, 1, 1], - } - ) - df = session.read_pandas(pd_df) - - precision_score = metrics.precision_score(df["y_true"], df["y_pred"]) - - assert precision_score == pytest.approx(1 / 3) - - -@pytest.mark.parametrize( - ("y_true", "y_pred", "pos_label"), - [ - pytest.param( - pd.Series([1, 2, 3]), pd.Series([1, 0]), 1, id="y_true-non-binary-label" - ), - pytest.param( - pd.Series([1, 0]), pd.Series([1, 2, 3]), 1, id="y_pred-non-binary-label" - ), - pytest.param( - pd.Series([1, 0]), pd.Series([1, 2]), 1, id="combined-non-binary-label" - ), - pytest.param(pd.Series([1, 0]), pd.Series([1, 0]), 2, id="invalid-pos_label"), - ], -) -def test_precision_score_binary_invalid_input_raise_error( - session, y_true, y_pred, pos_label -): - bf_y_true = session.read_pandas(y_true) - bf_y_pred = session.read_pandas(y_pred) - - with pytest.raises(ValueError): - metrics.precision_score( - bf_y_true, bf_y_pred, average="binary", pos_label=pos_label - ) - - def test_f1_score(session): pd_df = pd.DataFrame( { @@ -824,20 +763,17 @@ def test_f1_score(session): } ).astype("Int64") df = session.read_pandas(pd_df) - f1_score = metrics.f1_score( + f1_score = bigframes.ml.metrics.f1_score( df[["y_true_arbitrary_name"]], df[["y_pred_arbitrary_name"]], average=None ) expected_values = [0.8, 0.000000, 0.666667] expected_index = [0, 1, 2] expected_f1 = pd.Series(expected_values, index=expected_index) - bigframes.testing.utils.assert_series_equal( - f1_score, expected_f1, check_index_type=False - ) + pd.testing.assert_series_equal(f1_score, expected_f1, check_index_type=False) def test_f1_score_matches_sklearn(session): - sklearn_metrics = pytest.importorskip("sklearn.metrics") pd_df = pd.DataFrame( { "y_true": [2, 0, 2, 2, 0, 1], @@ -845,19 +781,18 @@ def test_f1_score_matches_sklearn(session): } ).astype("Int64") df = session.read_pandas(pd_df) - f1_score = metrics.f1_score(df[["y_true"]], df[["y_pred"]], average=None) + f1_score = bigframes.ml.metrics.f1_score( + df[["y_true"]], df[["y_pred"]], average=None + ) expected_values = sklearn_metrics.f1_score( pd_df[["y_true"]], pd_df[["y_pred"]], average=None ) expected_index = [0, 1, 2] expected_f1 = pd.Series(expected_values, index=expected_index) - bigframes.testing.utils.assert_series_equal( - f1_score, expected_f1, check_index_type=False - ) + pd.testing.assert_series_equal(f1_score, expected_f1, check_index_type=False) def test_f1_score_str_matches_sklearn(session): - sklearn_metrics = pytest.importorskip("sklearn.metrics") pd_df = pd.DataFrame( { "y_true": ["cat", "ant", "cat", "cat", "ant", "bird"], @@ -865,15 +800,15 @@ def test_f1_score_str_matches_sklearn(session): } ).astype("str") df = session.read_pandas(pd_df) - f1_score = metrics.f1_score(df[["y_true"]], df[["y_pred"]], average=None) + f1_score = bigframes.ml.metrics.f1_score( + df[["y_true"]], df[["y_pred"]], average=None + ) expected_values = sklearn_metrics.f1_score( pd_df[["y_true"]], pd_df[["y_pred"]], average=None ) expected_index = ["ant", "bird", "cat"] expected_f1 = pd.Series(expected_values, index=expected_index) - bigframes.testing.utils.assert_series_equal( - f1_score, expected_f1, check_index_type=False - ) + pd.testing.assert_series_equal(f1_score, expected_f1, check_index_type=False) def test_f1_score_series(session): @@ -884,25 +819,9 @@ def test_f1_score_series(session): } ).astype("Int64") df = session.read_pandas(pd_df) - f1_score = metrics.f1_score(df["y_true"], df["y_pred"], average=None) + f1_score = bigframes.ml.metrics.f1_score(df["y_true"], df["y_pred"], average=None) expected_values = [0.8, 0.000000, 0.666667] expected_index = [0, 1, 2] expected_f1 = pd.Series(expected_values, index=expected_index) - bigframes.testing.utils.assert_series_equal( - f1_score, expected_f1, check_index_type=False - ) - - -def test_mean_squared_error(session: bigframes.Session): - pd_df = pd.DataFrame({"y_true": [3, -0.5, 2, 7], "y_pred": [2.5, 0.0, 2, 8]}) - df = session.read_pandas(pd_df) - mse = metrics.mean_squared_error(df["y_true"], df["y_pred"]) - assert mse == 0.375 - - -def test_mean_absolute_error(session: bigframes.Session): - pd_df = pd.DataFrame({"y_true": [3, -0.5, 2, 7], "y_pred": [2.5, 0.0, 2, 8]}) - df = session.read_pandas(pd_df) - mse = metrics.mean_absolute_error(df["y_true"], df["y_pred"]) - assert mse == 0.5 + pd.testing.assert_series_equal(f1_score, expected_f1, check_index_type=False) diff --git a/tests/system/small/ml/test_metrics_pairwise.py b/tests/system/small/ml/test_metrics_pairwise.py deleted file mode 100644 index 44f1ed671b7..00000000000 --- a/tests/system/small/ml/test_metrics_pairwise.py +++ /dev/null @@ -1,106 +0,0 @@ -# Copyright 2023 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 numpy as np -import pandas as pd - -import bigframes.pandas as bpd -from bigframes.ml import metrics - - -def test_paired_cosine_distances(): - x_col = [np.array([4.1, 0.5, 1.0])] - y_col = [np.array([3.0, 0.0, 2.5])] - X = bpd.read_pandas(pd.DataFrame({"X": x_col})) - Y = bpd.read_pandas(pd.DataFrame({"Y": y_col})) - - result = metrics.pairwise.paired_cosine_distances(X, Y) - expected_pd_df = pd.DataFrame( - {"X": x_col, "Y": y_col, "cosine_distance": [0.108199]} - ) - - pd.testing.assert_frame_equal( - result.to_pandas(), expected_pd_df, check_dtype=False, check_index_type=False - ) - - -def test_paired_cosine_distances_multiindex(): - x_col = [np.array([4.1, 0.5, 1.0])] - y_col = [np.array([3.0, 0.0, 2.5])] - data = bpd.read_pandas( - pd.DataFrame( - {("DATA", "X"): x_col, ("DATA", "Y"): y_col}, - ) - ) - - result = metrics.pairwise.paired_cosine_distances( - data[("DATA", "X")], data[("DATA", "Y")] - ) - expected_pd_df = pd.DataFrame( - { - ("DATA", "X"): x_col, - ("DATA", "Y"): y_col, - ("cosine_distance", ""): [0.108199], - } - ) - - pd.testing.assert_frame_equal( - result.to_pandas(), expected_pd_df, check_dtype=False, check_index_type=False - ) - - -def test_paired_cosine_distances_single_frame(): - x_col = [np.array([4.1, 0.5, 1.0])] - y_col = [np.array([3.0, 0.0, 2.5])] - input = bpd.read_pandas(pd.DataFrame({"X": x_col})) - input["Y"] = y_col # type: ignore - - result = metrics.pairwise.paired_cosine_distances(input.X, input.Y) - expected_pd_df = pd.DataFrame( - {"X": x_col, "Y": y_col, "cosine_distance": [0.108199]} - ) - - pd.testing.assert_frame_equal( - result.to_pandas(), expected_pd_df, check_dtype=False, check_index_type=False - ) - - -def test_paired_manhattan_distance(): - x_col = [np.array([4.1, 0.5, 1.0])] - y_col = [np.array([3.0, 0.0, 2.5])] - X = bpd.read_pandas(pd.DataFrame({"X": x_col})) - Y = bpd.read_pandas(pd.DataFrame({"Y": y_col})) - - result = metrics.pairwise.paired_manhattan_distance(X, Y) - expected_pd_df = pd.DataFrame({"X": x_col, "Y": y_col, "manhattan_distance": [3.1]}) - - pd.testing.assert_frame_equal( - result.to_pandas(), expected_pd_df, check_dtype=False, check_index_type=False - ) - - -def test_paired_euclidean_distances(): - x_col = [np.array([4.1, 0.5, 1.0])] - y_col = [np.array([3.0, 0.0, 2.5])] - X = bpd.read_pandas(pd.DataFrame({"X": x_col})) - Y = bpd.read_pandas(pd.DataFrame({"Y": y_col})) - - result = metrics.pairwise.paired_euclidean_distances(X, Y) - expected_pd_df = pd.DataFrame( - {"X": x_col, "Y": y_col, "euclidean_distance": [1.926136]} - ) - - pd.testing.assert_frame_equal( - result.to_pandas(), expected_pd_df, check_dtype=False, check_index_type=False - ) diff --git a/tests/system/small/ml/test_model_selection.py b/tests/system/small/ml/test_model_selection.py index b7764a7d916..9eb36455913 100644 --- a/tests/system/small/ml/test_model_selection.py +++ b/tests/system/small/ml/test_model_selection.py @@ -12,31 +12,22 @@ # See the License for the specific language governing permissions and # limitations under the License. -import math -from typing import cast - import pandas as pd import pytest -import bigframes.pandas as bpd -import bigframes.session from bigframes.ml import model_selection +import bigframes.pandas as bpd -@pytest.mark.parametrize( - "df_fixture", - ("penguins_df_default_index", "penguins_df_null_index"), -) -def test_train_test_split_default_correct_shape(df_fixture, request): - df = request.getfixturevalue(df_fixture) - X = df[ +def test_train_test_split_default_correct_shape(penguins_df_default_index): + X = penguins_df_default_index[ [ "species", "island", "culmen_length_mm", ] ] - y = df[["body_mass_g"]] + y = penguins_df_default_index[["body_mass_g"]] X_train, X_test, y_train, y_test = model_selection.train_test_split(X, y) # even though the default seed is random, it should always result in this shape @@ -139,12 +130,12 @@ def test_train_test_split_seeded_correct_rows( X, y, random_state=42 ) - X_train_sorted = X_train.to_pandas().sort_index() - X_test_sorted = X_test.to_pandas().sort_index() - y_train_sorted = y_train.to_pandas().sort_index() - y_test_sorted = y_test.to_pandas().sort_index() + X_train = X_train.to_pandas().sort_index() + X_test = X_test.to_pandas().sort_index() + y_train = y_train.to_pandas().sort_index() + y_test = y_test.to_pandas().sort_index() - train_index: pd.Index = pd.Index( + train_index = pd.Index( [ 144, 146, @@ -171,20 +162,13 @@ def test_train_test_split_seeded_correct_rows( dtype="Int64", name="rowindex", ) - test_index: pd.Index = pd.Index( + test_index = pd.Index( [148, 161, 226, 269, 278, 289, 291], dtype="Int64", name="rowindex" ) all_data.index.name = "_" - - assert ( - isinstance(X_train_sorted, pd.DataFrame) - and isinstance(X_test_sorted, pd.DataFrame) - and isinstance(y_train_sorted, pd.DataFrame) - and isinstance(y_test_sorted, pd.DataFrame) - ) pd.testing.assert_frame_equal( - X_train_sorted, + X_train, all_data[ [ "species", @@ -194,7 +178,7 @@ def test_train_test_split_seeded_correct_rows( ].loc[train_index], ) pd.testing.assert_frame_equal( - X_test_sorted, + X_test, all_data[ [ "species", @@ -204,7 +188,7 @@ def test_train_test_split_seeded_correct_rows( ].loc[test_index], ) pd.testing.assert_frame_equal( - y_train_sorted, + y_train, all_data[ [ "body_mass_g", @@ -212,7 +196,7 @@ def test_train_test_split_seeded_correct_rows( ].loc[train_index], ) pd.testing.assert_frame_equal( - y_test_sorted, + y_test, all_data[ [ "body_mass_g", @@ -221,78 +205,6 @@ def test_train_test_split_seeded_correct_rows( ) -def test_train_test_split_no_shuffle_correct_shape( - penguins_df_default_index: bpd.DataFrame, -): - X = penguins_df_default_index[["species"]] - y = penguins_df_default_index["body_mass_g"] - X_train, X_test, y_train, y_test = model_selection.train_test_split( - X, y, shuffle=False - ) - assert isinstance(X_train, bpd.DataFrame) - assert isinstance(X_test, bpd.DataFrame) - assert isinstance(y_train, bpd.Series) - assert isinstance(y_test, bpd.Series) - - assert X_train.shape == (258, 1) - assert X_test.shape == (86, 1) - assert y_train.shape == (258,) - assert y_test.shape == (86,) - - -def test_train_test_split_no_shuffle_correct_rows( - session: bigframes.session.Session, penguins_pandas_df_default_index: bpd.DataFrame -): - # Note that we're using `penguins_pandas_df_default_index` as this test depends - # on a stable row order being present end to end - # filter down to the chunkiest penguins, to keep our test code a reasonable size - all_data = penguins_pandas_df_default_index[ - penguins_pandas_df_default_index.body_mass_g > 5500 - ].sort_index() - - # Note that bigframes loses the index if it doesn't have a name - all_data.index.name = "rowindex" - - df = session.read_pandas(all_data) - - X = df[ - [ - "species", - "island", - "culmen_length_mm", - ] - ] - y = df["body_mass_g"] - X_train, X_test, y_train, y_test = model_selection.train_test_split( - X, y, shuffle=False - ) - - X_train_pd = cast(bpd.DataFrame, X_train).to_pandas() - X_test_pd = cast(bpd.DataFrame, X_test).to_pandas() - y_train_pd = cast(bpd.Series, y_train).to_pandas() - y_test_pd = cast(bpd.Series, y_test).to_pandas() - - total_rows = len(all_data) - train_size = 0.75 - train_rows = int(total_rows * train_size) - test_rows = total_rows - train_rows - - expected_X_train = all_data.head(train_rows)[ - ["species", "island", "culmen_length_mm"] - ] - expected_y_train = all_data.head(train_rows)["body_mass_g"] - - expected_X_test = all_data.tail(test_rows)[ - ["species", "island", "culmen_length_mm"] - ] - expected_y_test = all_data.tail(test_rows)["body_mass_g"] - - pd.testing.assert_frame_equal(X_train_pd, expected_X_train) - pd.testing.assert_frame_equal(X_test_pd, expected_X_test) - pd.testing.assert_series_equal(y_train_pd, expected_y_train) - pd.testing.assert_series_equal(y_test_pd, expected_y_test) - - @pytest.mark.parametrize( ("train_size", "test_size"), [ @@ -315,245 +227,3 @@ def test_train_test_split_value_error(penguins_df_default_index, train_size, tes model_selection.train_test_split( X, y, train_size=train_size, test_size=test_size ) - - -@pytest.mark.parametrize( - "df_fixture", - ("penguins_df_default_index", "penguins_df_null_index"), -) -def test_train_test_split_stratify(df_fixture, request): - df = request.getfixturevalue(df_fixture) - X = df[ - [ - "species", - "island", - "culmen_length_mm", - ] - ].rename( - columns={"species": "x_species"} - ) # Keep "species" col just for easy checking. Rename to avoid conflicts. - y = df[["species"]] - X_train, X_test, y_train, y_test = model_selection.train_test_split( - X, y, stratify=df["species"] - ) - - # Original distribution is [152, 124, 68]. All the categories follow 75/25 split - train_counts = pd.Series( - [114, 93, 51], - index=pd.Index( - [ - "Adelie Penguin (Pygoscelis adeliae)", - "Gentoo penguin (Pygoscelis papua)", - "Chinstrap penguin (Pygoscelis antarctica)", - ], - name="species", - ), - dtype="Int64", - name="count", - ) - test_counts = pd.Series( - [38, 31, 17], - index=pd.Index( - [ - "Adelie Penguin (Pygoscelis adeliae)", - "Gentoo penguin (Pygoscelis papua)", - "Chinstrap penguin (Pygoscelis antarctica)", - ], - name="species", - ), - dtype="Int64", - name="count", - ) - pd.testing.assert_series_equal( - X_train["x_species"].rename("species").value_counts().to_pandas(), - train_counts, - check_index_type=False, - ) - pd.testing.assert_series_equal( - X_test["x_species"].rename("species").value_counts().to_pandas(), - test_counts, - check_index_type=False, - ) - pd.testing.assert_series_equal( - y_train["species"].value_counts().to_pandas(), - train_counts, - check_index_type=False, - ) - pd.testing.assert_series_equal( - y_test["species"].value_counts().to_pandas(), - test_counts, - check_index_type=False, - ) - - -@pytest.mark.parametrize( - "n_splits", - (3, 5, 10), -) -def test_KFold_get_n_splits(n_splits): - kf = model_selection.KFold(n_splits) - assert kf.get_n_splits() == n_splits - - -@pytest.mark.parametrize( - "df_fixture", - ("penguins_df_default_index", "penguins_df_null_index"), -) -@pytest.mark.parametrize( - "n_splits", - (3, 5), -) -def test_KFold_split(df_fixture, n_splits, request): - df = request.getfixturevalue(df_fixture) - - kf = model_selection.KFold(n_splits=n_splits) - - X = df[ - [ - "species", - "island", - "culmen_length_mm", - ] - ] - y = df["body_mass_g"] - - len_test_upper, len_test_lower = ( - math.ceil(len(df) / n_splits), - math.floor(len(df) / n_splits), - ) - len_train_upper, len_train_lower = ( - len(df) - len_test_lower, - len(df) - len_test_upper, - ) - - for X_train, X_test, y_train, y_test in kf.split(X, y): # type: ignore - assert isinstance(X_train, bpd.DataFrame) - assert isinstance(X_test, bpd.DataFrame) - assert isinstance(y_train, bpd.Series) - assert isinstance(y_test, bpd.Series) - - # Depend on the iteration, train/test can +-1 in size. - assert ( - X_train.shape == (len_train_upper, 3) - and y_train.shape == (len_train_upper,) - and X_test.shape == (len_test_lower, 3) - and y_test.shape == (len_test_lower,) - ) or ( - X_train.shape == (len_train_lower, 3) - and y_train.shape == (len_train_lower,) - and X_test.shape == (len_test_upper, 3) - and y_test.shape == (len_test_upper,) - ) - - -@pytest.mark.parametrize( - "df_fixture", - ("penguins_df_default_index", "penguins_df_null_index"), -) -@pytest.mark.parametrize( - "n_splits", - (3, 5), -) -def test_KFold_split_X_only(df_fixture, n_splits, request): - df = request.getfixturevalue(df_fixture) - - kf = model_selection.KFold(n_splits=n_splits) - - X = df[ - [ - "species", - "island", - "culmen_length_mm", - ] - ] - - len_test_upper, len_test_lower = ( - math.ceil(len(df) / n_splits), - math.floor(len(df) / n_splits), - ) - len_train_upper, len_train_lower = ( - len(df) - len_test_lower, - len(df) - len_test_upper, - ) - - for X_train, X_test, y_train, y_test in kf.split(X, y=None): # type: ignore - assert isinstance(X_train, bpd.DataFrame) - assert isinstance(X_test, bpd.DataFrame) - assert y_train is None - assert y_test is None - - # Depend on the iteration, train/test can +-1 in size. - assert ( - X_train.shape == (len_train_upper, 3) - and X_test.shape == (len_test_lower, 3) - ) or ( - X_train.shape == (len_train_lower, 3) - and X_test.shape == (len_test_upper, 3) - ) - - -def test_KFold_seeded_correct_rows(session, penguins_pandas_df_default_index): - kf = model_selection.KFold(random_state=42) - # Note that we're using `penguins_pandas_df_default_index` as this test depends - # on a stable row order being present end to end - # filter down to the chunkiest penguins, to keep our test code a reasonable size - all_data = penguins_pandas_df_default_index[ - penguins_pandas_df_default_index.body_mass_g > 5500 - ] - - # Note that bigframes loses the index if it doesn't have a name - all_data.index.name = "rowindex" - - df = session.read_pandas(all_data) - - X = df[ - [ - "species", - "island", - "culmen_length_mm", - ] - ] - y = df["body_mass_g"] - X_train, X_test, y_train, y_test = next(kf.split(X, y)) # type: ignore - - X_train_sorted = X_train.to_pandas().sort_index() # type: ignore - X_test_sorted = X_test.to_pandas().sort_index() # type: ignore - y_train_sorted = y_train.to_pandas().sort_index() # type: ignore - y_test_sorted = y_test.to_pandas().sort_index() # type: ignore - - train_index: pd.Index = pd.Index( - [ - 144, - 146, - 148, - 161, - 168, - 183, - 217, - 221, - 225, - 226, - 237, - 244, - 257, - 262, - 264, - 266, - 267, - 269, - 278, - 289, - 290, - 291, - ], - dtype="Int64", - name="rowindex", - ) - test_index: pd.Index = pd.Index( - [186, 240, 245, 260, 263, 268], dtype="Int64", name="rowindex" - ) - - pd.testing.assert_index_equal(X_train_sorted.index, train_index) - pd.testing.assert_index_equal(X_test_sorted.index, test_index) - pd.testing.assert_index_equal(y_train_sorted.index, train_index) - pd.testing.assert_index_equal(y_test_sorted.index, test_index) diff --git a/tests/system/small/ml/test_preprocessing.py b/tests/system/small/ml/test_preprocessing.py index ec63cc94f23..45548acca32 100644 --- a/tests/system/small/ml/test_preprocessing.py +++ b/tests/system/small/ml/test_preprocessing.py @@ -15,23 +15,13 @@ import math import pandas as pd -import pyarrow as pa -import bigframes.features -import bigframes.pandas as bpd -from bigframes.ml import preprocessing -from bigframes.testing import utils - -ONE_HOT_ENCODED_DTYPE = ( - pd.ArrowDtype(pa.list_(pa.struct([("index", pa.int64()), ("value", pa.float64())]))) - if bigframes.features.PANDAS_VERSIONS.is_arrow_list_dtype_usable - else "object" -) +import bigframes.ml.preprocessing def test_standard_scaler_normalizes(penguins_df_default_index, new_penguins_df): # TODO(http://b/292431644): add a second test that compares output to sklearn.preprocessing.StandardScaler, when BQML's change is in prod. - scaler = preprocessing.StandardScaler() + scaler = bigframes.ml.preprocessing.StandardScaler() scaler.fit( penguins_df_default_index[ ["culmen_length_mm", "culmen_depth_mm", "flipper_length_mm"] @@ -50,22 +40,27 @@ def test_standard_scaler_normalizes(penguins_df_default_index, new_penguins_df): result = scaler.transform(new_penguins_df).to_pandas() + # TODO: bug? feature columns seem to be in nondeterministic random order + # workaround: sort columns by name. Can't repro it in pantheon, so could + # be a bigframes issue... + result = result.reindex(sorted(result.columns), axis=1) + expected = pd.DataFrame( { - "standard_scaled_culmen_length_mm": [-0.81112, -0.994552, -1.104611], "standard_scaled_culmen_depth_mm": [0.836148, 0.024748, 0.48116], + "standard_scaled_culmen_length_mm": [-0.81112, -0.994552, -1.104611], "standard_scaled_flipper_length_mm": [-0.350044, -1.418336, -0.9198], }, dtype="Float64", index=pd.Index([1633, 1672, 1690], name="tag_number", dtype="Int64"), ) - pd.testing.assert_frame_equal(result, expected, rtol=0.1) + pd.testing.assert_frame_equal(result, expected, rtol=1e-3) -def test_standard_scaler_normalizes_fit_transform(new_penguins_df): +def test_standard_scaler_normalizeds_fit_transform(new_penguins_df): # TODO(http://b/292431644): add a second test that compares output to sklearn.preprocessing.StandardScaler, when BQML's change is in prod. - scaler = preprocessing.StandardScaler() + scaler = bigframes.ml.preprocessing.StandardScaler() result = scaler.fit_transform( new_penguins_df[["culmen_length_mm", "culmen_depth_mm", "flipper_length_mm"]] ).to_pandas() @@ -74,22 +69,27 @@ def test_standard_scaler_normalizes_fit_transform(new_penguins_df): for column in result.columns: assert math.isclose(result[column].mean(), 0.0, abs_tol=1e-3) + # TODO: bug? feature columns seem to be in nondeterministic random order + # workaround: sort columns by name. Can't repro it in pantheon, so could + # be a bigframes issue... + result = result.reindex(sorted(result.columns), axis=1) + expected = pd.DataFrame( { - "standard_scaled_culmen_length_mm": [1.313249, -0.20198, -1.111118], "standard_scaled_culmen_depth_mm": [1.17072, -1.272416, 0.101848], + "standard_scaled_culmen_length_mm": [1.313249, -0.20198, -1.111118], "standard_scaled_flipper_length_mm": [1.251089, -1.196588, -0.054338], }, dtype="Float64", index=pd.Index([1633, 1672, 1690], name="tag_number", dtype="Int64"), ) - pd.testing.assert_frame_equal(result, expected, rtol=0.1) + pd.testing.assert_frame_equal(result, expected, rtol=1e-3) def test_standard_scaler_series_normalizes(penguins_df_default_index, new_penguins_df): # TODO(http://b/292431644): add a second test that compares output to sklearn.preprocessing.StandardScaler, when BQML's change is in prod. - scaler = preprocessing.StandardScaler() + scaler = bigframes.ml.preprocessing.StandardScaler() scaler.fit(penguins_df_default_index["culmen_length_mm"]) result = scaler.transform(penguins_df_default_index["culmen_length_mm"]).to_pandas() @@ -100,6 +100,11 @@ def test_standard_scaler_series_normalizes(penguins_df_default_index, new_pengui result = scaler.transform(new_penguins_df).to_pandas() + # TODO: bug? feature columns seem to be in nondeterministic random order + # workaround: sort columns by name. Can't repro it in pantheon, so could + # be a bigframes issue... + result = result.reindex(sorted(result.columns), axis=1) + expected = pd.DataFrame( { "standard_scaled_culmen_length_mm": [ @@ -112,72 +117,12 @@ def test_standard_scaler_series_normalizes(penguins_df_default_index, new_pengui index=pd.Index([1633, 1672, 1690], name="tag_number", dtype="Int64"), ) - pd.testing.assert_frame_equal(result, expected, rtol=0.1) - - -def test_standard_scaler_normalizes_non_standard_column_names( - new_penguins_df: bpd.DataFrame, -): - new_penguins_df = new_penguins_df.rename( - columns={ - "culmen_length_mm": "culmen?metric", - "culmen_depth_mm": "culmen/metric", - } - ) - scaler = preprocessing.StandardScaler() - result = scaler.fit_transform( - new_penguins_df[["culmen?metric", "culmen/metric", "flipper_length_mm"]] - ).to_pandas() - - # If standard-scaled correctly, mean should be 0.0 - for column in result.columns: - assert math.isclose(result[column].mean(), 0.0, abs_tol=1e-3) - - expected = pd.DataFrame( - { - "standard_scaled_culmen_metric": [1.313249, -0.20198, -1.111118], - "standard_scaled_culmen_metric_1": [1.17072, -1.272416, 0.101848], - "standard_scaled_flipper_length_mm": [1.251089, -1.196588, -0.054338], - }, - dtype="Float64", - index=pd.Index([1633, 1672, 1690], name="tag_number", dtype="Int64"), - ) - - pd.testing.assert_frame_equal(result, expected, rtol=0.1) - - -def test_standard_scaler_save_load(new_penguins_df, dataset_id): - transformer = preprocessing.StandardScaler() - transformer.fit( - new_penguins_df[["culmen_length_mm", "culmen_depth_mm", "flipper_length_mm"]] - ) - - reloaded_transformer = transformer.to_gbq( - f"{dataset_id}.temp_configured_model", replace=True - ) - assert isinstance(reloaded_transformer, preprocessing.StandardScaler) - assert reloaded_transformer._bqml_model is not None - - result = reloaded_transformer.transform( - new_penguins_df[["culmen_length_mm", "culmen_depth_mm", "flipper_length_mm"]] - ).to_pandas() - - expected = pd.DataFrame( - { - "standard_scaled_culmen_length_mm": [1.313249, -0.20198, -1.111118], - "standard_scaled_culmen_depth_mm": [1.17072, -1.272416, 0.101848], - "standard_scaled_flipper_length_mm": [1.251089, -1.196588, -0.054338], - }, - dtype="Float64", - index=pd.Index([1633, 1672, 1690], name="tag_number", dtype="Int64"), - ) - - pd.testing.assert_frame_equal(result, expected, rtol=0.1) + pd.testing.assert_frame_equal(result, expected, rtol=1e-3) def test_max_abs_scaler_normalizes(penguins_df_default_index, new_penguins_df): # TODO(http://b/292431644): add a second test that compares output to sklearn.preprocessing.MaxAbsScaler, when BQML's change is in prod. - scaler = preprocessing.MaxAbsScaler() + scaler = bigframes.ml.preprocessing.MaxAbsScaler() scaler.fit( penguins_df_default_index[ ["culmen_length_mm", "culmen_depth_mm", "flipper_length_mm"] @@ -196,40 +141,50 @@ def test_max_abs_scaler_normalizes(penguins_df_default_index, new_penguins_df): result = scaler.transform(new_penguins_df).to_pandas() + # TODO: bug? feature columns seem to be in nondeterministic random order + # workaround: sort columns by name. Can't repro it in pantheon, so could + # be a bigframes issue... + result = result.reindex(sorted(result.columns), axis=1) + expected = pd.DataFrame( { - "max_abs_scaled_culmen_length_mm": [0.662752, 0.645973, 0.635906], "max_abs_scaled_culmen_depth_mm": [0.874419, 0.8, 0.84186], + "max_abs_scaled_culmen_length_mm": [0.662752, 0.645973, 0.635906], "max_abs_scaled_flipper_length_mm": [0.848485, 0.78355, 0.813853], }, dtype="Float64", index=pd.Index([1633, 1672, 1690], name="tag_number", dtype="Int64"), ) - pd.testing.assert_frame_equal(result, expected, rtol=0.1) + pd.testing.assert_frame_equal(result, expected, rtol=1e-3) def test_max_abs_scaler_normalizeds_fit_transform(new_penguins_df): - scaler = preprocessing.MaxAbsScaler() + scaler = bigframes.ml.preprocessing.MaxAbsScaler() result = scaler.fit_transform( new_penguins_df[["culmen_length_mm", "culmen_depth_mm", "flipper_length_mm"]] ).to_pandas() + # TODO: bug? feature columns seem to be in nondeterministic random order + # workaround: sort columns by name. Can't repro it in pantheon, so could + # be a bigframes issue... + result = result.reindex(sorted(result.columns), axis=1) + expected = pd.DataFrame( { - "max_abs_scaled_culmen_length_mm": [1.0, 0.974684, 0.959494], "max_abs_scaled_culmen_depth_mm": [1.0, 0.914894, 0.962766], + "max_abs_scaled_culmen_length_mm": [1.0, 0.974684, 0.959494], "max_abs_scaled_flipper_length_mm": [1.0, 0.923469, 0.959184], }, dtype="Float64", index=pd.Index([1633, 1672, 1690], name="tag_number", dtype="Int64"), ) - pd.testing.assert_frame_equal(result, expected, rtol=0.1) + pd.testing.assert_frame_equal(result, expected, rtol=1e-3) def test_max_abs_scaler_series_normalizes(penguins_df_default_index, new_penguins_df): - scaler = preprocessing.MaxAbsScaler() + scaler = bigframes.ml.preprocessing.MaxAbsScaler() scaler.fit(penguins_df_default_index["culmen_length_mm"]) result = scaler.transform(penguins_df_default_index["culmen_length_mm"]).to_pandas() @@ -240,67 +195,48 @@ def test_max_abs_scaler_series_normalizes(penguins_df_default_index, new_penguin result = scaler.transform(new_penguins_df).to_pandas() - expected = pd.DataFrame( - { - "max_abs_scaled_culmen_length_mm": [0.662752, 0.645973, 0.635906], - }, - dtype="Float64", - index=pd.Index([1633, 1672, 1690], name="tag_number", dtype="Int64"), - ) - - pd.testing.assert_frame_equal(result, expected, rtol=0.1) - - -def test_max_abs_scaler_save_load(new_penguins_df, dataset_id): - transformer = preprocessing.MaxAbsScaler() - transformer.fit( - new_penguins_df[["culmen_length_mm", "culmen_depth_mm", "flipper_length_mm"]] - ) - - reloaded_transformer = transformer.to_gbq( - f"{dataset_id}.temp_configured_model", replace=True - ) - assert isinstance(reloaded_transformer, preprocessing.MaxAbsScaler) - assert reloaded_transformer._bqml_model is not None - - result = reloaded_transformer.transform( - new_penguins_df[["culmen_length_mm", "culmen_depth_mm", "flipper_length_mm"]] - ).to_pandas() + # TODO: bug? feature columns seem to be in nondeterministic random order + # workaround: sort columns by name. Can't repro it in pantheon, so could + # be a bigframes issue... + result = result.reindex(sorted(result.columns), axis=1) expected = pd.DataFrame( { - "max_abs_scaled_culmen_length_mm": [1.0, 0.974684, 0.959494], - "max_abs_scaled_culmen_depth_mm": [1.0, 0.914894, 0.962766], - "max_abs_scaled_flipper_length_mm": [1.0, 0.923469, 0.959184], + "max_abs_scaled_culmen_length_mm": [0.662752, 0.645973, 0.635906], }, dtype="Float64", index=pd.Index([1633, 1672, 1690], name="tag_number", dtype="Int64"), ) - pd.testing.assert_frame_equal(result.sort_index(), expected.sort_index(), rtol=0.1) + pd.testing.assert_frame_equal(result, expected, rtol=1e-3) def test_min_max_scaler_normalized_fit_transform(new_penguins_df): - scaler = preprocessing.MinMaxScaler() + scaler = bigframes.ml.preprocessing.MinMaxScaler() result = scaler.fit_transform( new_penguins_df[["culmen_length_mm", "culmen_depth_mm", "flipper_length_mm"]] ).to_pandas() + # TODO: bug? feature columns seem to be in nondeterministic random order + # workaround: sort columns by name. Can't repro it in pantheon, so could + # be a bigframes issue... + result = result.reindex(sorted(result.columns), axis=1) + expected = pd.DataFrame( { - "min_max_scaled_culmen_length_mm": [1.0, 0.375, 0.0], "min_max_scaled_culmen_depth_mm": [1.0, 0.0, 0.5625], + "min_max_scaled_culmen_length_mm": [1.0, 0.375, 0.0], "min_max_scaled_flipper_length_mm": [1.0, 0.0, 0.466667], }, dtype="Float64", index=pd.Index([1633, 1672, 1690], name="tag_number", dtype="Int64"), ) - pd.testing.assert_frame_equal(result, expected, rtol=0.1) + pd.testing.assert_frame_equal(result, expected, rtol=1e-3) def test_min_max_scaler_series_normalizes(penguins_df_default_index, new_penguins_df): - scaler = preprocessing.MinMaxScaler() + scaler = bigframes.ml.preprocessing.MinMaxScaler() scaler.fit(penguins_df_default_index["culmen_length_mm"]) result = scaler.transform(penguins_df_default_index["culmen_length_mm"]).to_pandas() @@ -312,6 +248,11 @@ def test_min_max_scaler_series_normalizes(penguins_df_default_index, new_penguin result = scaler.transform(new_penguins_df).to_pandas() + # TODO: bug? feature columns seem to be in nondeterministic random order + # workaround: sort columns by name. Can't repro it in pantheon, so could + # be a bigframes issue... + result = result.reindex(sorted(result.columns), axis=1) + expected = pd.DataFrame( { "min_max_scaled_culmen_length_mm": [0.269091, 0.232727, 0.210909], @@ -320,12 +261,12 @@ def test_min_max_scaler_series_normalizes(penguins_df_default_index, new_penguin index=pd.Index([1633, 1672, 1690], name="tag_number", dtype="Int64"), ) - pd.testing.assert_frame_equal(result, expected, rtol=0.1) + pd.testing.assert_frame_equal(result, expected, rtol=1e-3) def test_min_max_scaler_normalizes(penguins_df_default_index, new_penguins_df): # TODO(http://b/292431644): add a second test that compares output to sklearn.preprocessing.MinMaxScaler, when BQML's change is in prod. - scaler = preprocessing.MinMaxScaler() + scaler = bigframes.ml.preprocessing.MinMaxScaler() scaler.fit( penguins_df_default_index[ ["culmen_length_mm", "culmen_depth_mm", "flipper_length_mm"] @@ -345,92 +286,52 @@ def test_min_max_scaler_normalizes(penguins_df_default_index, new_penguins_df): result = scaler.transform(new_penguins_df).to_pandas() + # TODO: bug? feature columns seem to be in nondeterministic random order + # workaround: sort columns by name. Can't repro it in pantheon, so could + # be a bigframes issue... + result = result.reindex(sorted(result.columns), axis=1) + expected = pd.DataFrame( { - "min_max_scaled_culmen_length_mm": [0.269091, 0.232727, 0.210909], "min_max_scaled_culmen_depth_mm": [0.678571, 0.4880952, 0.595238], + "min_max_scaled_culmen_length_mm": [0.269091, 0.232727, 0.210909], "min_max_scaled_flipper_length_mm": [0.40678, 0.152542, 0.271186], }, dtype="Float64", index=pd.Index([1633, 1672, 1690], name="tag_number", dtype="Int64"), ) - pd.testing.assert_frame_equal(result, expected, rtol=0.1) - - -def test_min_max_scaler_save_load(new_penguins_df, dataset_id): - transformer = preprocessing.MinMaxScaler() - transformer.fit( - new_penguins_df[["culmen_length_mm", "culmen_depth_mm", "flipper_length_mm"]] - ) - - reloaded_transformer = transformer.to_gbq( - f"{dataset_id}.temp_configured_model", replace=True - ) - assert isinstance(reloaded_transformer, preprocessing.MinMaxScaler) - assert reloaded_transformer._bqml_model is not None - - result = reloaded_transformer.fit_transform( - new_penguins_df[["culmen_length_mm", "culmen_depth_mm", "flipper_length_mm"]] - ).to_pandas() - - expected = pd.DataFrame( - { - "min_max_scaled_culmen_length_mm": [1.0, 0.375, 0.0], - "min_max_scaled_culmen_depth_mm": [1.0, 0.0, 0.5625], - "min_max_scaled_flipper_length_mm": [1.0, 0.0, 0.466667], - }, - dtype="Float64", - index=pd.Index([1633, 1672, 1690], name="tag_number", dtype="Int64"), - ) - - pd.testing.assert_frame_equal(result, expected, rtol=0.1) + pd.testing.assert_frame_equal(result, expected, rtol=1e-3) def test_k_bins_discretizer_normalized_fit_transform_default_params(new_penguins_df): - discretizer = preprocessing.KBinsDiscretizer(strategy="uniform") + discretizer = bigframes.ml.preprocessing.KBinsDiscretizer(strategy="uniform") result = discretizer.fit_transform( new_penguins_df[["culmen_length_mm", "culmen_depth_mm", "flipper_length_mm"]] ).to_pandas() + # TODO: bug? feature columns seem to be in nondeterministic random order + # workaround: sort columns by name. Can't repro it in pantheon, so could + # be a bigframes issue... + result = result.reindex(sorted(result.columns), axis=1) + expected = pd.DataFrame( { - "kbinsdiscretizer_culmen_length_mm": ["bin_5", "bin_3", "bin_2"], "kbinsdiscretizer_culmen_depth_mm": ["bin_5", "bin_2", "bin_4"], + "kbinsdiscretizer_culmen_length_mm": ["bin_5", "bin_3", "bin_2"], "kbinsdiscretizer_flipper_length_mm": ["bin_5", "bin_2", "bin_4"], }, dtype="string[pyarrow]", index=pd.Index([1633, 1672, 1690], name="tag_number", dtype="Int64"), ) - pd.testing.assert_frame_equal(result, expected, rtol=0.1) - - -def test_k_bins_discretizer_normalized_fit_transform_default_params_quantile( - new_penguins_df, -): - discretizer = preprocessing.KBinsDiscretizer(strategy="quantile") - result = discretizer.fit_transform( - new_penguins_df[["culmen_length_mm", "culmen_depth_mm", "flipper_length_mm"]] - ).to_pandas() - - expected = pd.DataFrame( - { - "kbinsdiscretizer_culmen_length_mm": ["bin_2", "bin_2", "bin_1"], - "kbinsdiscretizer_culmen_depth_mm": ["bin_2", "bin_1", "bin_2"], - "kbinsdiscretizer_flipper_length_mm": ["bin_2", "bin_1", "bin_2"], - }, - dtype="string[pyarrow]", - index=pd.Index([1633, 1672, 1690], name="tag_number", dtype="Int64"), - ) - - pd.testing.assert_frame_equal(result, expected, rtol=0.1) + pd.testing.assert_frame_equal(result, expected, rtol=1e-3) def test_k_bins_discretizer_series_normalizes( penguins_df_default_index, new_penguins_df ): - discretizer = preprocessing.KBinsDiscretizer(strategy="uniform") + discretizer = bigframes.ml.preprocessing.KBinsDiscretizer(strategy="uniform") discretizer.fit(penguins_df_default_index["culmen_length_mm"]) result = discretizer.transform( @@ -438,42 +339,25 @@ def test_k_bins_discretizer_series_normalizes( ).to_pandas() result = discretizer.transform(new_penguins_df).to_pandas() - expected = pd.DataFrame( - { - "kbinsdiscretizer_culmen_length_mm": ["bin_3", "bin_3", "bin_3"], - }, - dtype="string[pyarrow]", - index=pd.Index([1633, 1672, 1690], name="tag_number", dtype="Int64"), - ) - - pd.testing.assert_frame_equal(result, expected, rtol=0.1) - - -def test_k_bins_discretizer_series_normalizes_quantile( - penguins_df_default_index, new_penguins_df -): - discretizer = preprocessing.KBinsDiscretizer(strategy="quantile") - discretizer.fit(penguins_df_default_index["culmen_length_mm"]) - - result = discretizer.transform( - penguins_df_default_index["culmen_length_mm"] - ).to_pandas() - result = discretizer.transform(new_penguins_df).to_pandas() + # TODO: bug? feature columns seem to be in nondeterministic random order + # workaround: sort columns by name. Can't repro it in pantheon, so could + # be a bigframes issue... + result = result.reindex(sorted(result.columns), axis=1) expected = pd.DataFrame( { - "kbinsdiscretizer_culmen_length_mm": ["bin_2", "bin_2", "bin_1"], + "kbinsdiscretizer_culmen_length_mm": ["bin_3", "bin_3", "bin_3"], }, dtype="string[pyarrow]", index=pd.Index([1633, 1672, 1690], name="tag_number", dtype="Int64"), ) - pd.testing.assert_frame_equal(result, expected, rtol=0.1) + pd.testing.assert_frame_equal(result, expected, rtol=1e-3) def test_k_bins_discretizer_normalizes(penguins_df_default_index, new_penguins_df): # TODO(http://b/292431644): add a second test that compares output to sklearn.preprocessing.KBinsDiscretizer, when BQML's change is in prod. - discretizer = preprocessing.KBinsDiscretizer(strategy="uniform") + discretizer = bigframes.ml.preprocessing.KBinsDiscretizer(strategy="uniform") discretizer.fit( penguins_df_default_index[ ["culmen_length_mm", "culmen_depth_mm", "flipper_length_mm"] @@ -488,24 +372,31 @@ def test_k_bins_discretizer_normalizes(penguins_df_default_index, new_penguins_d result = discretizer.transform(new_penguins_df).to_pandas() + # TODO: bug? feature columns seem to be in nondeterministic random order + # workaround: sort columns by name. Can't repro it in pantheon, so could + # be a bigframes issue... + result = result.reindex(sorted(result.columns), axis=1) + expected = pd.DataFrame( { - "kbinsdiscretizer_culmen_length_mm": ["bin_3", "bin_3", "bin_3"], "kbinsdiscretizer_culmen_depth_mm": ["bin_5", "bin_4", "bin_4"], + "kbinsdiscretizer_culmen_length_mm": ["bin_3", "bin_3", "bin_3"], "kbinsdiscretizer_flipper_length_mm": ["bin_4", "bin_2", "bin_3"], }, dtype="string[pyarrow]", index=pd.Index([1633, 1672, 1690], name="tag_number", dtype="Int64"), ) - pd.testing.assert_frame_equal(result, expected, rtol=0.1) + pd.testing.assert_frame_equal(result, expected, rtol=1e-3) def test_k_bins_discretizer_normalizes_different_params( penguins_df_default_index, new_penguins_df ): # TODO(http://b/292431644): add a second test that compares output to sklearn.preprocessing.KBinsDiscretizer, when BQML's change is in prod. - discretizer = preprocessing.KBinsDiscretizer(n_bins=6, strategy="uniform") + discretizer = bigframes.ml.preprocessing.KBinsDiscretizer( + n_bins=6, strategy="uniform" + ) discretizer.fit( penguins_df_default_index[ ["culmen_length_mm", "culmen_depth_mm", "flipper_length_mm"] @@ -520,85 +411,48 @@ def test_k_bins_discretizer_normalizes_different_params( result = discretizer.transform(new_penguins_df).to_pandas() + # TODO: bug? feature columns seem to be in nondeterministic random order + # workaround: sort columns by name. Can't repro it in pantheon, so could + # be a bigframes issue... + result = result.reindex(sorted(result.columns), axis=1) + expected = pd.DataFrame( { - "kbinsdiscretizer_culmen_length_mm": ["bin_3", "bin_3", "bin_3"], "kbinsdiscretizer_culmen_depth_mm": ["bin_6", "bin_4", "bin_5"], + "kbinsdiscretizer_culmen_length_mm": ["bin_3", "bin_3", "bin_3"], "kbinsdiscretizer_flipper_length_mm": ["bin_4", "bin_2", "bin_3"], }, dtype="string[pyarrow]", index=pd.Index([1633, 1672, 1690], name="tag_number", dtype="Int64"), ) - pd.testing.assert_frame_equal(result, expected, rtol=0.1) - - -def test_k_bins_discretizer_save_load(new_penguins_df, dataset_id): - transformer = preprocessing.KBinsDiscretizer(n_bins=6, strategy="uniform") - transformer.fit( - new_penguins_df[["culmen_length_mm", "culmen_depth_mm", "flipper_length_mm"]] - ) - - reloaded_transformer = transformer.to_gbq( - f"{dataset_id}.temp_configured_model", replace=True - ) - assert isinstance(reloaded_transformer, preprocessing.KBinsDiscretizer) - assert reloaded_transformer.n_bins == transformer.n_bins - assert reloaded_transformer.strategy == transformer.strategy - assert reloaded_transformer._bqml_model is not None - - result = reloaded_transformer.fit_transform( - new_penguins_df[["culmen_length_mm", "culmen_depth_mm", "flipper_length_mm"]] - ).to_pandas() - - expected = pd.DataFrame( - { - "kbinsdiscretizer_culmen_length_mm": ["bin_6", "bin_4", "bin_2"], - "kbinsdiscretizer_culmen_depth_mm": ["bin_6", "bin_2", "bin_5"], - "kbinsdiscretizer_flipper_length_mm": ["bin_6", "bin_2", "bin_4"], - }, - dtype="string[pyarrow]", - index=pd.Index([1633, 1672, 1690], name="tag_number", dtype="Int64"), - ) - - pd.testing.assert_frame_equal(result, expected, rtol=0.1) - - -def test_k_bins_discretizer_save_load_quantile(new_penguins_df, dataset_id): - transformer = preprocessing.KBinsDiscretizer(n_bins=6, strategy="quantile") - transformer.fit( - new_penguins_df[["culmen_length_mm", "culmen_depth_mm", "flipper_length_mm"]] - ) - - reloaded_transformer = transformer.to_gbq( - f"{dataset_id}.temp_configured_model", replace=True - ) - assert isinstance(reloaded_transformer, preprocessing.KBinsDiscretizer) - assert reloaded_transformer.n_bins == transformer.n_bins - assert reloaded_transformer.strategy == transformer.strategy - assert reloaded_transformer._bqml_model is not None + pd.testing.assert_frame_equal(result, expected, rtol=1e-3) def test_one_hot_encoder_default_params(new_penguins_df): - encoder = preprocessing.OneHotEncoder() + encoder = bigframes.ml.preprocessing.OneHotEncoder() encoder.fit(new_penguins_df[["species", "sex"]]) result = encoder.transform(new_penguins_df).to_pandas() + # TODO: bug? feature columns seem to be in nondeterministic random order + # workaround: sort columns by name. Can't repro it in pantheon, so could + # be a bigframes issue... + result = result.reindex(sorted(result.columns), axis=1) + expected = pd.DataFrame( { - "onehotencoded_species": [ + "onehotencoded_sex": [ + [{"index": 2, "value": 1.0}], [{"index": 1, "value": 1.0}], [{"index": 1, "value": 1.0}], - [{"index": 2, "value": 1.0}], ], - "onehotencoded_sex": [ - [{"index": 2, "value": 1.0}], + "onehotencoded_species": [ [{"index": 1, "value": 1.0}], [{"index": 1, "value": 1.0}], + [{"index": 2, "value": 1.0}], ], }, - dtype=ONE_HOT_ENCODED_DTYPE, index=pd.Index([1633, 1672, 1690], name="tag_number", dtype="Int64"), ) @@ -606,24 +460,28 @@ def test_one_hot_encoder_default_params(new_penguins_df): def test_one_hot_encoder_default_params_fit_transform(new_penguins_df): - encoder = preprocessing.OneHotEncoder() + encoder = bigframes.ml.preprocessing.OneHotEncoder() result = encoder.fit_transform(new_penguins_df[["species", "sex"]]).to_pandas() + # TODO: bug? feature columns seem to be in nondeterministic random order + # workaround: sort columns by name. Can't repro it in pantheon, so could + # be a bigframes issue... + result = result.reindex(sorted(result.columns), axis=1) + expected = pd.DataFrame( { - "onehotencoded_species": [ + "onehotencoded_sex": [ + [{"index": 2, "value": 1.0}], [{"index": 1, "value": 1.0}], [{"index": 1, "value": 1.0}], - [{"index": 2, "value": 1.0}], ], - "onehotencoded_sex": [ - [{"index": 2, "value": 1.0}], + "onehotencoded_species": [ [{"index": 1, "value": 1.0}], [{"index": 1, "value": 1.0}], + [{"index": 2, "value": 1.0}], ], }, - dtype=ONE_HOT_ENCODED_DTYPE, index=pd.Index([1633, 1672, 1690], name="tag_number", dtype="Int64"), ) @@ -631,11 +489,16 @@ def test_one_hot_encoder_default_params_fit_transform(new_penguins_df): def test_one_hot_encoder_series_default_params(new_penguins_df): - encoder = preprocessing.OneHotEncoder() + encoder = bigframes.ml.preprocessing.OneHotEncoder() encoder.fit(new_penguins_df["species"]) result = encoder.transform(new_penguins_df).to_pandas() + # TODO: bug? feature columns seem to be in nondeterministic random order + # workaround: sort columns by name. Can't repro it in pantheon, so could + # be a bigframes issue... + result = result.reindex(sorted(result.columns), axis=1) + expected = pd.DataFrame( { "onehotencoded_species": [ @@ -644,7 +507,6 @@ def test_one_hot_encoder_series_default_params(new_penguins_df): [{"index": 2, "value": 1.0}], ], }, - dtype=ONE_HOT_ENCODED_DTYPE, index=pd.Index([1633, 1672, 1690], name="tag_number", dtype="Int64"), ) @@ -652,25 +514,29 @@ def test_one_hot_encoder_series_default_params(new_penguins_df): def test_one_hot_encoder_params(new_penguins_df): - encoder = preprocessing.OneHotEncoder("most_frequent", 100, 2) + encoder = bigframes.ml.preprocessing.OneHotEncoder("most_frequent", 100, 2) encoder.fit(new_penguins_df[["species", "sex"]]) result = encoder.transform(new_penguins_df).to_pandas() + # TODO: bug? feature columns seem to be in nondeterministic random order + # workaround: sort columns by name. Can't repro it in pantheon, so could + # be a bigframes issue... + result = result.reindex(sorted(result.columns), axis=1) + expected = pd.DataFrame( { - "onehotencoded_species": [ + "onehotencoded_sex": [ [{"index": 0, "value": 1.0}], [{"index": 0, "value": 1.0}], [{"index": 0, "value": 1.0}], ], - "onehotencoded_sex": [ + "onehotencoded_species": [ [{"index": 0, "value": 1.0}], [{"index": 0, "value": 1.0}], [{"index": 0, "value": 1.0}], ], }, - dtype=ONE_HOT_ENCODED_DTYPE, index=pd.Index([1633, 1672, 1690], name="tag_number", dtype="Int64"), ) @@ -678,61 +544,29 @@ def test_one_hot_encoder_params(new_penguins_df): def test_one_hot_encoder_different_data(penguins_df_default_index, new_penguins_df): - encoder = preprocessing.OneHotEncoder() + encoder = bigframes.ml.preprocessing.OneHotEncoder() encoder.fit(penguins_df_default_index[["species", "sex"]]) result = encoder.transform(new_penguins_df).to_pandas() + # TODO: bug? feature columns seem to be in nondeterministic random order + # workaround: sort columns by name. Can't repro it in pantheon, so could + # be a bigframes issue... + result = result.reindex(sorted(result.columns), axis=1) + expected = pd.DataFrame( { - "onehotencoded_species": [ - [{"index": 1, "value": 1.0}], - [{"index": 1, "value": 1.0}], - [{"index": 2, "value": 1.0}], - ], "onehotencoded_sex": [ [{"index": 3, "value": 1.0}], [{"index": 2, "value": 1.0}], [{"index": 2, "value": 1.0}], ], - }, - dtype=ONE_HOT_ENCODED_DTYPE, - index=pd.Index([1633, 1672, 1690], name="tag_number", dtype="Int64"), - ) - - pd.testing.assert_frame_equal(result, expected) - - -def test_one_hot_encoder_save_load(new_penguins_df, dataset_id): - transformer = preprocessing.OneHotEncoder(min_frequency=1, max_categories=10) - transformer.fit(new_penguins_df[["species", "sex"]]) - - reloaded_transformer = transformer.to_gbq( - f"{dataset_id}.temp_configured_model", replace=True - ) - assert isinstance(reloaded_transformer, preprocessing.OneHotEncoder) - assert reloaded_transformer.min_frequency == transformer.min_frequency - assert reloaded_transformer.max_categories == transformer.max_categories - assert reloaded_transformer._bqml_model is not None - - result = reloaded_transformer.fit_transform( - new_penguins_df[["species", "sex"]] - ).to_pandas() - - expected = pd.DataFrame( - { "onehotencoded_species": [ [{"index": 1, "value": 1.0}], [{"index": 1, "value": 1.0}], [{"index": 2, "value": 1.0}], ], - "onehotencoded_sex": [ - [{"index": 2, "value": 1.0}], - [{"index": 1, "value": 1.0}], - [{"index": 1, "value": 1.0}], - ], }, - dtype=ONE_HOT_ENCODED_DTYPE, index=pd.Index([1633, 1672, 1690], name="tag_number", dtype="Int64"), ) @@ -740,11 +574,16 @@ def test_one_hot_encoder_save_load(new_penguins_df, dataset_id): def test_label_encoder_default_params(new_penguins_df): - encoder = preprocessing.LabelEncoder() + encoder = bigframes.ml.preprocessing.LabelEncoder() encoder.fit(new_penguins_df["species"]) result = encoder.transform(new_penguins_df["species"]).to_pandas() + # TODO: bug? feature columns seem to be in nondeterministic random order + # workaround: sort columns by name. Can't repro it in pantheon, so could + # be a bigframes issue... + result = result.reindex(sorted(result.columns), axis=1) + expected = pd.DataFrame( { "labelencoded_species": [ @@ -761,10 +600,15 @@ def test_label_encoder_default_params(new_penguins_df): def test_label_encoder_default_params_fit_transform(new_penguins_df): - encoder = preprocessing.LabelEncoder() + encoder = bigframes.ml.preprocessing.LabelEncoder() result = encoder.fit_transform(new_penguins_df[["species"]]).to_pandas() + # TODO: bug? feature columns seem to be in nondeterministic random order + # workaround: sort columns by name. Can't repro it in pantheon, so could + # be a bigframes issue... + result = result.reindex(sorted(result.columns), axis=1) + expected = pd.DataFrame( { "labelencoded_species": [ @@ -781,11 +625,16 @@ def test_label_encoder_default_params_fit_transform(new_penguins_df): def test_label_encoder_series_default_params(new_penguins_df): - encoder = preprocessing.LabelEncoder() + encoder = bigframes.ml.preprocessing.LabelEncoder() encoder.fit(new_penguins_df["species"]) result = encoder.transform(new_penguins_df).to_pandas() + # TODO: bug? feature columns seem to be in nondeterministic random order + # workaround: sort columns by name. Can't repro it in pantheon, so could + # be a bigframes issue... + result = result.reindex(sorted(result.columns), axis=1) + expected = pd.DataFrame( { "labelencoded_species": [ @@ -802,11 +651,16 @@ def test_label_encoder_series_default_params(new_penguins_df): def test_label_encoder_params(new_penguins_df): - encoder = preprocessing.LabelEncoder(100, 2) + encoder = bigframes.ml.preprocessing.LabelEncoder(100, 2) encoder.fit(new_penguins_df[["species"]]) result = encoder.transform(new_penguins_df).to_pandas() + # TODO: bug? feature columns seem to be in nondeterministic random order + # workaround: sort columns by name. Can't repro it in pantheon, so could + # be a bigframes issue... + result = result.reindex(sorted(result.columns), axis=1) + expected = pd.DataFrame( { "labelencoded_species": [ @@ -823,39 +677,15 @@ def test_label_encoder_params(new_penguins_df): def test_label_encoder_different_data(penguins_df_default_index, new_penguins_df): - encoder = preprocessing.LabelEncoder() + encoder = bigframes.ml.preprocessing.LabelEncoder() encoder.fit(penguins_df_default_index[["species"]]) result = encoder.transform(new_penguins_df).to_pandas() - expected = pd.DataFrame( - { - "labelencoded_species": [ - 1, - 1, - 2, - ], - }, - dtype="Int64", - index=pd.Index([1633, 1672, 1690], name="tag_number", dtype="Int64"), - ) - - pd.testing.assert_frame_equal(result, expected) - - -def test_label_encoder_save_load(new_penguins_df, dataset_id): - transformer = preprocessing.LabelEncoder(min_frequency=1, max_categories=10) - transformer.fit(new_penguins_df[["species"]]) - - reloaded_transformer = transformer.to_gbq( - f"{dataset_id}.temp_configured_model", replace=True - ) - assert isinstance(reloaded_transformer, preprocessing.LabelEncoder) - assert reloaded_transformer.min_frequency == transformer.min_frequency - assert reloaded_transformer.max_categories == transformer.max_categories - assert reloaded_transformer._bqml_model is not None - - result = reloaded_transformer.transform(new_penguins_df).to_pandas() + # TODO: bug? feature columns seem to be in nondeterministic random order + # workaround: sort columns by name. Can't repro it in pantheon, so could + # be a bigframes issue... + result = result.reindex(sorted(result.columns), axis=1) expected = pd.DataFrame( { @@ -873,101 +703,3 @@ def test_label_encoder_save_load(new_penguins_df, dataset_id): # TODO(garrettwu): add OneHotEncoder tests to compare with sklearn. - - -def test_poly_features_default_params(new_penguins_df): - transformer = preprocessing.PolynomialFeatures() - df = new_penguins_df[["culmen_length_mm", "culmen_depth_mm"]] - transformer.fit(df) - - result = transformer.transform(df).to_pandas() - - expected = pd.DataFrame( - { - "poly_feat_culmen_length_mm": [ - 39.5, - 38.5, - 37.9, - ], - "poly_feat_culmen_length_mm_culmen_length_mm": [ - 1560.25, - 1482.25, - 1436.41, - ], - "poly_feat_culmen_length_mm_culmen_depth_mm": [ - 742.6, - 662.2, - 685.99, - ], - "poly_feat_culmen_depth_mm": [ - 18.8, - 17.2, - 18.1, - ], - "poly_feat_culmen_depth_mm_culmen_depth_mm": [ - 353.44, - 295.84, - 327.61, - ], - }, - dtype="Float64", - index=pd.Index([1633, 1672, 1690], name="tag_number", dtype="Int64"), - ) - - pd.testing.assert_frame_equal(result, expected, check_exact=False, rtol=0.1) - - -def test_poly_features_params(new_penguins_df): - transformer = preprocessing.PolynomialFeatures(degree=3) - df = new_penguins_df[["culmen_length_mm", "culmen_depth_mm"]] - transformer.fit(df) - - result = transformer.transform(df).to_pandas() - - utils.check_pandas_df_schema_and_index( - result, - [ - "poly_feat_culmen_length_mm", - "poly_feat_culmen_length_mm_culmen_length_mm", - "poly_feat_culmen_length_mm_culmen_length_mm_culmen_length_mm", - "poly_feat_culmen_length_mm_culmen_length_mm_culmen_depth_mm", - "poly_feat_culmen_length_mm_culmen_depth_mm", - "poly_feat_culmen_length_mm_culmen_depth_mm_culmen_depth_mm", - "poly_feat_culmen_depth_mm", - "poly_feat_culmen_depth_mm_culmen_depth_mm", - "poly_feat_culmen_depth_mm_culmen_depth_mm_culmen_depth_mm", - ], - [1633, 1672, 1690], - ) - - -def test_poly_features_save_load(new_penguins_df, dataset_id): - transformer = preprocessing.PolynomialFeatures(degree=3) - transformer.fit(new_penguins_df[["culmen_length_mm", "culmen_depth_mm"]]) - - reloaded_transformer = transformer.to_gbq( - f"{dataset_id}.temp_configured_model", replace=True - ) - assert isinstance(reloaded_transformer, preprocessing.PolynomialFeatures) - assert reloaded_transformer.degree == 3 - assert reloaded_transformer._bqml_model is not None - - result = reloaded_transformer.transform( - new_penguins_df[["culmen_length_mm", "culmen_depth_mm"]] - ).to_pandas() - - utils.check_pandas_df_schema_and_index( - result, - [ - "poly_feat_culmen_length_mm", - "poly_feat_culmen_length_mm_culmen_length_mm", - "poly_feat_culmen_length_mm_culmen_length_mm_culmen_length_mm", - "poly_feat_culmen_length_mm_culmen_length_mm_culmen_depth_mm", - "poly_feat_culmen_length_mm_culmen_depth_mm", - "poly_feat_culmen_length_mm_culmen_depth_mm_culmen_depth_mm", - "poly_feat_culmen_depth_mm", - "poly_feat_culmen_depth_mm_culmen_depth_mm", - "poly_feat_culmen_depth_mm_culmen_depth_mm_culmen_depth_mm", - ], - [1633, 1672, 1690], - ) diff --git a/tests/system/small/ml/test_register.py b/tests/system/small/ml/test_register.py index f21567da63e..bcf1f4a5b0e 100644 --- a/tests/system/small/ml/test_register.py +++ b/tests/system/small/ml/test_register.py @@ -14,7 +14,7 @@ from typing import cast -from bigframes.ml import core, imported, linear_model +from bigframes.ml import core, imported, linear_model, llm def test_linear_reg_register( @@ -51,6 +51,24 @@ def test_linear_reg_register_with_params( ) +def test_palm2_text_generator_register( + ephemera_palm2_text_generator_model: llm.PaLM2TextGenerator, +): + model = ephemera_palm2_text_generator_model + model.register() + + model_name = "bigframes_" + cast( + str, cast(core.BqmlModel, model._bqml_model).model.model_id + ) + # Only registered model contains the field, and the field includes project/dataset. Here only check model_id. + assert ( + model_name[:63] # truncated + in cast(core.BqmlModel, model._bqml_model).model.training_runs[-1][ + "vertexAiModelId" + ] + ) + + def test_imported_tensorflow_register( ephemera_imported_tensorflow_model: imported.TensorFlowModel, ): diff --git a/tests/system/small/ml/test_remote.py b/tests/system/small/ml/test_remote.py deleted file mode 100644 index c52c4522448..00000000000 --- a/tests/system/small/ml/test_remote.py +++ /dev/null @@ -1,36 +0,0 @@ -# Copyright 2023 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 as pd -import pytest - -from bigframes.ml import remote - - -@pytest.mark.skip("b/353775058 BQML internal error") -def test_remote_linear_vertex_model_predict( - linear_remote_vertex_model: remote.VertexAIModel, new_penguins_df -): - predictions = linear_remote_vertex_model.predict(new_penguins_df).to_pandas() - expected = pd.DataFrame( - {"predicted_body_mass_g": [[3739.54], [3675.79], [3619.54]]}, - index=pd.Index([1633, 1672, 1690], name="tag_number", dtype="Int64"), - ) - pd.testing.assert_frame_equal( - predictions[["predicted_body_mass_g"]].sort_index(), - expected, - check_exact=False, - check_dtype=False, - rtol=0.1, - ) diff --git a/tests/system/small/ml/test_utils.py b/tests/system/small/ml/test_utils.py deleted file mode 100644 index ec3bd315b13..00000000000 --- a/tests/system/small/ml/test_utils.py +++ /dev/null @@ -1,80 +0,0 @@ -# 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. -# 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 as pd -import pytest - -import bigframes.ml.utils as utils -import bigframes.testing.utils - -_DATA_FRAME = pd.DataFrame({"column": [1, 2, 3]}) -_SERIES = pd.Series([1, 2, 3], name="column") - - -@pytest.mark.parametrize( - "data", - [pytest.param(_DATA_FRAME, id="dataframe"), pytest.param(_SERIES, id="series")], -) -def test_convert_to_dataframe(session, data): - bf_data = session.read_pandas(data) - - (actual_result,) = utils.batch_convert_to_dataframe(bf_data) - - bigframes.testing.utils.assert_frame_equal( - actual_result.to_pandas(), - _DATA_FRAME, - check_index_type=False, - check_dtype=False, - ) - - -@pytest.mark.parametrize( - "data", - [pytest.param(_DATA_FRAME, id="dataframe"), pytest.param(_SERIES, id="series")], -) -def test_convert_pandas_to_dataframe(data, session): - (actual_result,) = utils.batch_convert_to_dataframe(data, session=session) - - bigframes.testing.utils.assert_frame_equal( - actual_result.to_pandas(), - _DATA_FRAME, - check_index_type=False, - check_dtype=False, - ) - - -@pytest.mark.parametrize( - "data", - [pytest.param(_DATA_FRAME, id="dataframe"), pytest.param(_SERIES, id="series")], -) -def test_convert_to_series(session, data): - bf_data = session.read_pandas(data) - - (actual_result,) = utils.batch_convert_to_series(bf_data) - - bigframes.testing.utils.assert_series_equal( - actual_result.to_pandas(), _SERIES, check_index_type=False, check_dtype=False - ) - - -@pytest.mark.parametrize( - "data", - [pytest.param(_DATA_FRAME, id="dataframe"), pytest.param(_SERIES, id="series")], -) -def test_convert_pandas_to_series(data, session): - (actual_result,) = utils.batch_convert_to_series(data, session=session) - - bigframes.testing.utils.assert_series_equal( - actual_result.to_pandas(), _SERIES, check_index_type=False, check_dtype=False - ) diff --git a/tests/system/small/operations/test_dates.py b/tests/system/small/operations/test_dates.py deleted file mode 100644 index 3554322462b..00000000000 --- a/tests/system/small/operations/test_dates.py +++ /dev/null @@ -1,91 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - - -import datetime - -import pandas as pd -import pytest -from packaging import version - -import bigframes.testing.utils -from bigframes import dtypes - - -def test_date_diff_between_series(session): - pd_df = pd.DataFrame( - { - "col_1": [datetime.date(2025, 1, 2), datetime.date(2025, 2, 1)], - "col_2": [datetime.date(2024, 1, 2), datetime.date(2026, 1, 30)], - } - ).astype(dtypes.DATE_DTYPE) - bf_df = session.read_pandas(pd_df) - - actual_result = (bf_df["col_1"] - bf_df["col_2"]).to_pandas() - - expected_result = (pd_df["col_1"] - pd_df["col_2"]).astype(dtypes.TIMEDELTA_DTYPE) - bigframes.testing.utils.assert_series_equal( - actual_result, expected_result, check_index_type=False - ) - - -def test_date_diff_literal_sub_series(scalars_dfs): - bf_df, pd_df = scalars_dfs - literal = datetime.date(2030, 5, 20) - - actual_result = (literal - bf_df["date_col"]).to_pandas() - - expected_result = (literal - pd_df["date_col"]).astype(dtypes.TIMEDELTA_DTYPE) - bigframes.testing.utils.assert_series_equal( - actual_result, expected_result, check_index_type=False - ) - - -def test_date_diff_series_sub_literal(scalars_dfs): - bf_df, pd_df = scalars_dfs - literal = datetime.date(1980, 5, 20) - - actual_result = (bf_df["date_col"] - literal).to_pandas() - - expected_result = (pd_df["date_col"] - literal).astype(dtypes.TIMEDELTA_DTYPE) - bigframes.testing.utils.assert_series_equal( - actual_result, expected_result, check_index_type=False - ) - - -def test_date_series_diff_agg(scalars_dfs): - bf_df, pd_df = scalars_dfs - - actual_result = bf_df["date_col"].diff().to_pandas() - - expected_result = pd_df["date_col"].diff().astype(dtypes.TIMEDELTA_DTYPE) - bigframes.testing.utils.assert_series_equal( - actual_result, expected_result, check_index_type=False - ) - - -def test_date_can_cast_after_accessor(scalars_dfs): - if version.Version(pd.__version__) <= version.Version("2.1.0"): - pytest.skip("pd timezone conversion bug") - bf_df, pd_df = scalars_dfs - - actual_result = bf_df["date_col"].dt.isocalendar().week.astype("Int64").to_pandas() - # convert to pd date type rather than arrow, as pandas doesn't handle arrow date well here - expected_result = ( - pd.to_datetime(pd_df["date_col"]).dt.isocalendar().week.astype("Int64") - ) - - bigframes.testing.utils.assert_series_equal( - actual_result, expected_result, check_dtype=False, check_index_type=False - ) diff --git a/tests/system/small/operations/test_datetimes.py b/tests/system/small/operations/test_datetimes.py index ebfe0414de0..7dc55b9367c 100644 --- a/tests/system/small/operations/test_datetimes.py +++ b/tests/system/small/operations/test_datetimes.py @@ -12,47 +12,28 @@ # See the License for the specific language governing permissions and # limitations under the License. -import datetime -import typing - -import numpy import pandas as pd import pytest -from packaging import version -import bigframes.pandas as bpd import bigframes.series -from bigframes.testing.utils import assert_frame_equal, assert_series_equal +from tests.system.utils import assert_series_equal_ignoring_order DATETIME_COL_NAMES = [("datetime_col",), ("timestamp_col",)] -DATE_COLUMNS = [ - ("datetime_col",), - ("timestamp_col",), - ("date_col",), -] - - -@pytest.fixture -def timedelta_series(session): - pd_s = pd.Series(pd.to_timedelta([1.1010101, 2.2020102, 3.3030103], unit="d")) - bf_s = session.read_pandas(pd_s) - - return bf_s, pd_s @pytest.mark.parametrize( ("col_name",), - DATE_COLUMNS, + DATETIME_COL_NAMES, ) -def test_dt_day(scalars_dfs, col_name): - # TODO: supply a reason why this isn't compatible with pandas 1.x - pytest.importorskip("pandas", minversion="2.0.0") +def test_day(scalars_dfs, col_name): + if pd.__version__.startswith("1."): + pytest.skip("Pyarrow datetime objects not support in pandas 1.x.") scalars_df, scalars_pandas_df = scalars_dfs bf_series: bigframes.series.Series = scalars_df[col_name] bf_result = bf_series.dt.day.to_pandas() pd_result = scalars_pandas_df[col_name].dt.day - assert_series_equal( + assert_series_equal_ignoring_order( pd_result.astype(pd.Int64Dtype()), bf_result, ) @@ -62,15 +43,15 @@ def test_dt_day(scalars_dfs, col_name): ("col_name",), DATETIME_COL_NAMES, ) -def test_dt_date(scalars_dfs, col_name): - # TODO: supply a reason why this isn't compatible with pandas 1.x - pytest.importorskip("pandas", minversion="2.0.0") +def test_date(scalars_dfs, col_name): + if pd.__version__.startswith("1."): + pytest.skip("Pyarrow datetime objects not support in pandas 1.x.") scalars_df, scalars_pandas_df = scalars_dfs bf_series: bigframes.series.Series = scalars_df[col_name] bf_result = bf_series.dt.date.to_pandas() pd_result = scalars_pandas_df[col_name].dt.date - assert_series_equal( + assert_series_equal_ignoring_order( pd_result, bf_result, ) @@ -78,108 +59,32 @@ def test_dt_date(scalars_dfs, col_name): @pytest.mark.parametrize( ("col_name",), - DATE_COLUMNS, + DATETIME_COL_NAMES, ) -def test_dt_dayofweek(scalars_dfs, col_name): - # TODO: supply a reason why this isn't compatible with pandas 1.x - pytest.importorskip("pandas", minversion="2.0.0") +def test_dayofweek(scalars_dfs, col_name): + if pd.__version__.startswith("1."): + pytest.skip("Pyarrow datetime objects not support in pandas 1.x.") scalars_df, scalars_pandas_df = scalars_dfs bf_series: bigframes.series.Series = scalars_df[col_name] - bf_result = bf_series.dt.dayofweek.to_pandas() pd_result = scalars_pandas_df[col_name].dt.dayofweek - assert_series_equal(pd_result, bf_result, check_dtype=False) - - -@pytest.mark.parametrize( - ("col_name",), - DATE_COLUMNS, -) -def test_dt_day_of_week(scalars_dfs, col_name): - pytest.importorskip("pandas", minversion="2.0.0") - scalars_df, scalars_pandas_df = scalars_dfs - bf_series: bigframes.series.Series = scalars_df[col_name] - - bf_result = bf_series.dt.day_of_week.to_pandas() - pd_result = scalars_pandas_df[col_name].dt.day_of_week - - assert_series_equal(pd_result, bf_result, check_dtype=False) - - -@pytest.mark.parametrize( - ("col_name",), - DATE_COLUMNS, -) -def test_dt_weekday(scalars_dfs, col_name): - pytest.importorskip("pandas", minversion="2.0.0") - scalars_df, scalars_pandas_df = scalars_dfs - bf_series: bigframes.series.Series = scalars_df[col_name] - - bf_result = bf_series.dt.weekday.to_pandas() - pd_result = scalars_pandas_df[col_name].dt.weekday - - assert_series_equal(pd_result, bf_result, check_dtype=False) - - -@pytest.mark.parametrize( - ("col_name",), - DATE_COLUMNS, -) -def test_dt_dayofyear(scalars_dfs, col_name): - pytest.importorskip("pandas", minversion="2.0.0") - scalars_df, scalars_pandas_df = scalars_dfs - bf_series: bigframes.series.Series = scalars_df[col_name] - - bf_result = bf_series.dt.dayofyear.to_pandas() - pd_result = scalars_pandas_df[col_name].dt.dayofyear - - assert_series_equal(pd_result, bf_result, check_dtype=False) - - -@pytest.mark.parametrize( - ("col_name",), - DATE_COLUMNS, -) -def test_dt_day_name(scalars_dfs, col_name): - pytest.importorskip("pandas", minversion="2.0.0") - scalars_df, scalars_pandas_df = scalars_dfs - bf_series: bigframes.series.Series = scalars_df[col_name] - - bf_result = bf_series.dt.day_name().to_pandas() - pd_result = scalars_pandas_df[col_name].dt.day_name() - - assert_series_equal(pd_result, bf_result, check_dtype=False) - - -@pytest.mark.parametrize( - ("col_name",), - DATE_COLUMNS, -) -def test_dt_day_of_year(scalars_dfs, col_name): - pytest.importorskip("pandas", minversion="2.0.0") - scalars_df, scalars_pandas_df = scalars_dfs - bf_series: bigframes.series.Series = scalars_df[col_name] - - bf_result = bf_series.dt.day_of_year.to_pandas() - pd_result = scalars_pandas_df[col_name].dt.day_of_year - - assert_series_equal(pd_result, bf_result, check_dtype=False) + assert_series_equal_ignoring_order(pd_result, bf_result, check_dtype=False) @pytest.mark.parametrize( ("col_name",), DATETIME_COL_NAMES, ) -def test_dt_hour(scalars_dfs, col_name): - # TODO: supply a reason why this isn't compatible with pandas 1.x - pytest.importorskip("pandas", minversion="2.0.0") +def test_hour(scalars_dfs, col_name): + if pd.__version__.startswith("1."): + pytest.skip("Pyarrow datetime objects not support in pandas 1.x.") scalars_df, scalars_pandas_df = scalars_dfs bf_series: bigframes.series.Series = scalars_df[col_name] bf_result = bf_series.dt.hour.to_pandas() pd_result = scalars_pandas_df[col_name].dt.hour - assert_series_equal( + assert_series_equal_ignoring_order( pd_result.astype(pd.Int64Dtype()), bf_result, ) @@ -189,15 +94,15 @@ def test_dt_hour(scalars_dfs, col_name): ("col_name",), DATETIME_COL_NAMES, ) -def test_dt_minute(scalars_dfs, col_name): - # TODO: supply a reason why this isn't compatible with pandas 1.x - pytest.importorskip("pandas", minversion="2.0.0") +def test_minute(scalars_dfs, col_name): + if pd.__version__.startswith("1."): + pytest.skip("Pyarrow datetime objects not support in pandas 1.x.") scalars_df, scalars_pandas_df = scalars_dfs bf_series: bigframes.series.Series = scalars_df[col_name] bf_result = bf_series.dt.minute.to_pandas() pd_result = scalars_pandas_df[col_name].dt.minute - assert_series_equal( + assert_series_equal_ignoring_order( pd_result.astype(pd.Int64Dtype()), bf_result, ) @@ -205,17 +110,17 @@ def test_dt_minute(scalars_dfs, col_name): @pytest.mark.parametrize( ("col_name",), - DATE_COLUMNS, + DATETIME_COL_NAMES, ) -def test_dt_month(scalars_dfs, col_name): - # TODO: supply a reason why this isn't compatible with pandas 1.x - pytest.importorskip("pandas", minversion="2.0.0") +def test_month(scalars_dfs, col_name): + if pd.__version__.startswith("1."): + pytest.skip("Pyarrow datetime objects not support in pandas 1.x.") scalars_df, scalars_pandas_df = scalars_dfs bf_series: bigframes.series.Series = scalars_df[col_name] bf_result = bf_series.dt.month.to_pandas() pd_result = scalars_pandas_df[col_name].dt.month - assert_series_equal( + assert_series_equal_ignoring_order( pd_result.astype(pd.Int64Dtype()), bf_result, ) @@ -223,17 +128,17 @@ def test_dt_month(scalars_dfs, col_name): @pytest.mark.parametrize( ("col_name",), - DATE_COLUMNS, + DATETIME_COL_NAMES, ) -def test_dt_quarter(scalars_dfs, col_name): - # TODO: supply a reason why this isn't compatible with pandas 1.x - pytest.importorskip("pandas", minversion="2.0.0") +def test_quarter(scalars_dfs, col_name): + if pd.__version__.startswith("1."): + pytest.skip("Pyarrow datetime objects not support in pandas 1.x.") scalars_df, scalars_pandas_df = scalars_dfs bf_series: bigframes.series.Series = scalars_df[col_name] bf_result = bf_series.dt.quarter.to_pandas() pd_result = scalars_pandas_df[col_name].dt.quarter - assert_series_equal( + assert_series_equal_ignoring_order( pd_result.astype(pd.Int64Dtype()), bf_result, ) @@ -243,15 +148,15 @@ def test_dt_quarter(scalars_dfs, col_name): ("col_name",), DATETIME_COL_NAMES, ) -def test_dt_second(scalars_dfs, col_name): - # TODO: supply a reason why this isn't compatible with pandas 1.x - pytest.importorskip("pandas", minversion="2.0.0") +def test_second(scalars_dfs, col_name): + if pd.__version__.startswith("1."): + pytest.skip("Pyarrow datetime objects not support in pandas 1.x.") scalars_df, scalars_pandas_df = scalars_dfs bf_series: bigframes.series.Series = scalars_df[col_name] bf_result = bf_series.dt.second.to_pandas() pd_result = scalars_pandas_df[col_name].dt.second - assert_series_equal( + assert_series_equal_ignoring_order( pd_result.astype(pd.Int64Dtype()), bf_result, ) @@ -261,15 +166,15 @@ def test_dt_second(scalars_dfs, col_name): ("col_name",), DATETIME_COL_NAMES, ) -def test_dt_time(scalars_dfs, col_name): - # TODO: supply a reason why this isn't compatible with pandas 1.x - pytest.importorskip("pandas", minversion="2.0.0") +def test_time(scalars_dfs, col_name): + if pd.__version__.startswith("1."): + pytest.skip("Pyarrow datetime objects not support in pandas 1.x.") scalars_df, scalars_pandas_df = scalars_dfs bf_series: bigframes.series.Series = scalars_df[col_name] bf_result = bf_series.dt.time.to_pandas() pd_result = scalars_pandas_df[col_name].dt.time - assert_series_equal( + assert_series_equal_ignoring_order( pd_result, bf_result, ) @@ -277,404 +182,17 @@ def test_dt_time(scalars_dfs, col_name): @pytest.mark.parametrize( ("col_name",), - DATE_COLUMNS, + DATETIME_COL_NAMES, ) -def test_dt_year(scalars_dfs, col_name): - # TODO: supply a reason why this isn't compatible with pandas 1.x - pytest.importorskip("pandas", minversion="2.0.0") +def test_year(scalars_dfs, col_name): + if pd.__version__.startswith("1."): + pytest.skip("Pyarrow datetime objects not support in pandas 1.x.") scalars_df, scalars_pandas_df = scalars_dfs bf_series: bigframes.series.Series = scalars_df[col_name] bf_result = bf_series.dt.year.to_pandas() pd_result = scalars_pandas_df[col_name].dt.year - assert_series_equal( + assert_series_equal_ignoring_order( pd_result.astype(pd.Int64Dtype()), bf_result, ) - - -def test_dt_isocalendar(session): - # We don't re-use the exisintg scalars_dfs fixture because iso calendar - # get tricky when a new year starts, but the dataset `scalars_dfs` does not cover - # this case. - pd_s = pd.Series(pd.date_range("2009-12-25", "2010-01-07", freq="d")) - bf_s = session.read_pandas(pd_s) - - actual_result = bf_s.dt.isocalendar().to_pandas() - - expected_result = pd_s.dt.isocalendar() - assert_frame_equal( - actual_result, expected_result, check_dtype=False, check_index_type=False - ) - - -@pytest.mark.parametrize( - ("col_name",), - DATETIME_COL_NAMES, -) -def test_dt_tz(scalars_dfs, col_name): - # 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 - bf_series: bigframes.series.Series = scalars_df[col_name] - bf_result = bf_series.dt.tz - pd_result = scalars_pandas_df[col_name].dt.tz - - assert bf_result == pd_result - - -@pytest.mark.parametrize( - ("col_name", "tz"), - [ - ("datetime_col", None), - ("timestamp_col", None), - ("datetime_col", "UTC"), - ], -) -def test_dt_tz_localize(scalars_dfs, col_name, tz): - pytest.importorskip("pandas", minversion="2.0.0") - scalars_df, scalars_pandas_df = scalars_dfs - bf_series = scalars_df[col_name] - - bf_result = bf_series.dt.tz_localize(tz) - pd_result = scalars_pandas_df[col_name].dt.tz_localize(tz) - - assert_series_equal(bf_result.to_pandas(), pd_result, check_index_type=False) - - -def test_dt_tz_localize_already_localized(scalars_dfs): - pytest.importorskip("pandas", minversion="2.0.0") - scalars_df, _ = scalars_dfs - - with pytest.raises(TypeError): - scalars_df["timestamp_col"].dt.tz_localize("UTC") - - -def test_dt_tz_localize_invalid_timezone(scalars_dfs): - pytest.importorskip("pandas", minversion="2.0.0") - scalars_df, _ = scalars_dfs - - with pytest.raises(ValueError): - scalars_df["datetime_col"].dt.tz_localize("US/Eastern") - - -@pytest.mark.parametrize( - ("col_name",), - DATETIME_COL_NAMES, -) -def test_dt_unit(scalars_dfs, col_name): - # 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 - bf_series: bigframes.series.Series = scalars_df[col_name] - bf_result = bf_series.dt.unit - pd_result = scalars_pandas_df[col_name].dt.unit - - assert bf_result == pd_result - - -@pytest.mark.parametrize( - ("column", "date_format"), - [ - ("timestamp_col", "%B %d, %Y, %r"), - ("timestamp_col", "%m-%d-%Y %H:%M"), - ("datetime_col", "%m-%d-%Y %H:%M"), - ("datetime_col", "%H:%M"), - ], -) -def test_dt_strftime(scalars_df_index, scalars_pandas_df_index, column, date_format): - # TODO: supply a reason why this isn't compatible with pandas 1.x - pytest.importorskip("pandas", minversion="2.0.0") - bf_result = scalars_df_index[column].dt.strftime(date_format).to_pandas() - pd_result = scalars_pandas_df_index[column].dt.strftime(date_format) - assert_series_equal(bf_result, pd_result, check_dtype=False) - assert bf_result.dtype == "string[pyarrow]" - - -def test_dt_strftime_date(): - bf_series = bigframes.series.Series( - ["2014-08-15", "2215-08-15", "2016-02-29"] - ).astype("date32[day][pyarrow]") - - expected_result = pd.Series(["08/15/2014", "08/15/2215", "02/29/2016"]) - bf_result = bf_series.dt.strftime("%m/%d/%Y").to_pandas() - - assert_series_equal( - bf_result, expected_result, check_index_type=False, check_dtype=False - ) - assert bf_result.dtype == "string[pyarrow]" - - -def test_dt_strftime_time(): - bf_series = bigframes.series.Series( - [143542314, 345234512341, 75543252344, 626546437654754, 8543523452345234] - ).astype("time64[us][pyarrow]") - - expected_result = pd.Series( - ["00:02:23", "23:53:54", "20:59:03", "16:40:37", "08:57:32"] - ) - bf_result = bf_series.dt.strftime("%X").to_pandas() - - assert_series_equal( - bf_result, expected_result, check_index_type=False, check_dtype=False - ) - assert bf_result.dtype == "string[pyarrow]" - - -@pytest.mark.parametrize( - ("col_name",), - DATETIME_COL_NAMES, -) -def test_dt_normalize(scalars_dfs, col_name): - # 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 - bf_result = scalars_df[col_name].dt.normalize().to_pandas() - pd_result = scalars_pandas_df[col_name].dt.normalize() - - assert_series_equal( - pd_result.astype(scalars_df[col_name].dtype), # normalize preserves type - bf_result, - ) - - -@pytest.mark.parametrize( - ("col_name", "freq"), - [ - ("timestamp_col", "D"), - ("timestamp_col", "min"), - ("datetime_col", "s"), - ("datetime_col", "us"), - ], -) -def test_dt_floor(scalars_dfs, col_name, freq): - # 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 - bf_result = scalars_df[col_name].dt.floor(freq).to_pandas() - pd_result = scalars_pandas_df[col_name].dt.floor(freq) - - assert_series_equal( - pd_result.astype(scalars_df[col_name].dtype), # floor preserves type - bf_result, - ) - - -def test_dt_compare_coerce_str_datetime(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - bf_series: bigframes.series.Series = scalars_df["datetime_col"] - - bf_result = (bf_series >= "2024-01-01").to_pandas() - pd_result = scalars_pandas_df["datetime_col"] >= pd.to_datetime("2024-01-01") - - # pandas produces pyarrow bool dtype - assert_series_equal(pd_result, bf_result, check_dtype=False) - - -def test_dt_clip_datetime_literals(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - bf_series: bigframes.series.Series = scalars_df["date_col"] - bf_result = bf_series.clip( - datetime.date(2020, 1, 1), datetime.date(2024, 1, 1) - ).to_pandas() - - pd_result = scalars_pandas_df["date_col"].clip( - datetime.date(2020, 1, 1), datetime.date(2024, 1, 1) - ) - - assert_series_equal( - pd_result, - bf_result, - ) - - -def test_dt_clip_coerce_str_date(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - bf_series: bigframes.series.Series = scalars_df["date_col"] - bf_result = bf_series.clip("2020-01-01", "2024-01-01").to_pandas() - - # Pandas can't coerce with pyarrow types so convert first - pd_result = scalars_pandas_df["date_col"].clip( - datetime.date(2020, 1, 1), datetime.date(2024, 1, 1) - ) - - assert_series_equal( - pd_result, - bf_result, - ) - - -def test_dt_clip_coerce_str_timestamp(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - bf_series: bigframes.series.Series = scalars_df["timestamp_col"] - bf_result = bf_series.clip( - "2020-01-01T20:03:50Z", "2024-01-01T20:03:50Z" - ).to_pandas() - - pd_result = scalars_pandas_df["timestamp_col"].clip( - pd.to_datetime("2020-01-01T20:03:50Z", utc=True), - pd.to_datetime("2024-01-01T20:03:50Z", utc=True), - ) - - assert_series_equal( - pd_result, - bf_result, - ) - - -@pytest.mark.parametrize("column", ["timestamp_col", "datetime_col"]) -def test_timestamp_diff_two_series(scalars_dfs, column): - bf_df, pd_df = scalars_dfs - bf_series = bf_df[column] - pd_series = pd_df[column] - - actual_result = (bf_series - bf_series).to_pandas() - - expected_result = pd_series - pd_series - assert_series_equal(actual_result, expected_result) - - -@pytest.mark.parametrize("column", ["timestamp_col", "datetime_col"]) -def test_timestamp_diff_two_series_with_numpy_ops(scalars_dfs, column): - bf_df, pd_df = scalars_dfs - bf_series = bf_df[column] - pd_series = pd_df[column] - - actual_result = numpy.subtract(bf_series, bf_series).to_pandas() - - expected_result = numpy.subtract(pd_series, pd_series) - assert_series_equal(actual_result, expected_result) - - -def test_timestamp_diff_two_dataframes(scalars_dfs): - columns = ["timestamp_col", "datetime_col"] - bf_df, pd_df = scalars_dfs - bf_df = bf_df[columns] - pd_df = pd_df[columns] - - actual_result = (bf_df - bf_df).to_pandas() - - expected_result = pd_df - pd_df - assert_frame_equal(actual_result, expected_result) - - -def test_timestamp_diff_two_series_with_different_types_raise_error(scalars_dfs): - df, _ = scalars_dfs - - with pytest.raises(TypeError): - (df["timestamp_col"] - df["datetime_col"]).to_pandas() - - -@pytest.mark.parametrize( - ("column", "value"), - [ - ("timestamp_col", pd.Timestamp("2025-01-01 00:00:01", tz="America/New_York")), - ("datetime_col", datetime.datetime(2025, 1, 1, 0, 0, 1)), - ], -) -def test_timestamp_diff_series_sub_literal(scalars_dfs, column, value): - bf_df, pd_df = scalars_dfs - bf_series = bf_df[column] - pd_series = pd_df[column] - - # Pandas doesn't handle nulls properly here so we ffill - # overflows for no good reason - # related? https://github.com/apache/arrow/issues/43031 - actual_result = (bf_series.ffill() - value).to_pandas() - - expected_result = pd_series.ffill() - value - assert_series_equal(actual_result, expected_result) - - -@pytest.mark.parametrize( - ("column", "value"), - [ - ("timestamp_col", pd.Timestamp("2025-01-01 00:00:01", tz="America/New_York")), - ("datetime_col", datetime.datetime(2025, 1, 1, 0, 0, 1)), - ], -) -def test_timestamp_diff_literal_sub_series(scalars_dfs, column, value): - bf_df, pd_df = scalars_dfs - bf_series = bf_df[column] - pd_series = pd_df[column] - - # Pandas doesn't handle nulls properly here so we ffill - # overflows for no good reason - # related? https://github.com/apache/arrow/issues/43031 - actual_result = (value - bf_series.ffill()).to_pandas() - - expected_result = value - pd_series.ffill() - assert_series_equal(actual_result, expected_result) - - -@pytest.mark.parametrize("column", ["timestamp_col", "datetime_col"]) -def test_timestamp_series_diff_agg(scalars_dfs, column): - bf_df, pd_df = scalars_dfs - bf_series = bf_df[column] - pd_series = pd_df[column] - - actual_result = bf_series.diff().to_pandas() - - # overflows for no good reason - # related? https://github.com/apache/arrow/issues/43031 - expected_result = pd_series.ffill().diff() - expected_result = expected_result.mask( - pd_series.isnull() | pd_series.shift(1).isnull() - ) - assert_series_equal(actual_result, expected_result) - - -@pytest.mark.parametrize( - "access", - [ - pytest.param(lambda x: x.dt.days, id="dt.days"), - pytest.param(lambda x: x.dt.seconds, id="dt.seconds"), - pytest.param(lambda x: x.dt.microseconds, id="dt.microseconds"), - pytest.param(lambda x: x.dt.total_seconds(), id="dt.total_seconds()"), - ], -) -def test_timedelta_dt_accessors(timedelta_series, access): - bf_s, pd_s = timedelta_series - - actual_result = access(bf_s).to_pandas() - - expected_result = access(pd_s) - assert_series_equal( - actual_result, expected_result, check_dtype=False, check_index_type=False - ) - - -@pytest.mark.parametrize( - "access", - [ - pytest.param(lambda x: x.dt.days, id="dt.days"), - pytest.param(lambda x: x.dt.seconds, id="dt.seconds"), - pytest.param(lambda x: x.dt.microseconds, id="dt.microseconds"), - pytest.param(lambda x: x.dt.total_seconds(), id="dt.total_seconds()"), - ], -) -def test_timedelta_dt_accessors_on_wrong_type_raise_exception(scalars_dfs, access): - bf_df, _ = scalars_dfs - - with pytest.raises(TypeError): - access(bf_df["timestamp_col"]) - - -@pytest.mark.parametrize( - "col", - # TODO(b/431276706) test timestamp_col too. - ["date_col", "datetime_col"], -) -def test_to_datetime(scalars_dfs, col): - if version.Version(pd.__version__) <= version.Version("2.1.0"): - pytest.skip("timezone conversion bug") - bf_df, pd_df = scalars_dfs - - actual_result = typing.cast( - bigframes.series.Series, bpd.to_datetime(bf_df[col]) - ).to_pandas() - - expected_result = pd.Series(pd.to_datetime(pd_df[col])) - assert_series_equal( - actual_result, expected_result, check_dtype=False, check_index_type=False - ) diff --git a/tests/system/small/operations/test_lists.py b/tests/system/small/operations/test_lists.py deleted file mode 100644 index 16a68025721..00000000000 --- a/tests/system/small/operations/test_lists.py +++ /dev/null @@ -1,138 +0,0 @@ -# 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. -# 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 packaging.version -import pandas as pd -import pyarrow as pa -import pytest - -from bigframes.testing.utils import assert_series_equal - - -@pytest.mark.parametrize( - ("key"), - [ - pytest.param(0, id="int"), - pytest.param(slice(None, None, None), id="default_start_slice"), - pytest.param(slice(0, None, 1), id="default_stop_slice"), - pytest.param(slice(0, 2, None), id="default_step_slice"), - ], -) -@pytest.mark.parametrize( - ("column_name", "dtype"), - [ - pytest.param("int_list_col", pd.ArrowDtype(pa.list_(pa.int64()))), - pytest.param("bool_list_col", pd.ArrowDtype(pa.list_(pa.bool_()))), - pytest.param("float_list_col", pd.ArrowDtype(pa.list_(pa.float64()))), - pytest.param("date_list_col", pd.ArrowDtype(pa.list_(pa.date32()))), - pytest.param("date_time_list_col", pd.ArrowDtype(pa.list_(pa.timestamp("us")))), - pytest.param("numeric_list_col", pd.ArrowDtype(pa.list_(pa.decimal128(38, 9)))), - pytest.param("string_list_col", pd.ArrowDtype(pa.list_(pa.string()))), - ], -) -def test_getitem(key, column_name, dtype, repeated_df, repeated_pandas_df): - if packaging.version.Version(pd.__version__) < packaging.version.Version("2.2.0"): - pytest.skip( - "https://pandas.pydata.org/docs/whatsnew/v2.2.0.html#series-list-accessor-for-pyarrow-list-data" - ) - - bf_result = repeated_df[column_name].list[key].to_pandas() - pd_result = repeated_pandas_df[column_name].astype(dtype).list[key] - - assert_series_equal( - pd_result, - bf_result, - check_dtype=False, - check_index_type=False, - check_names=False, - ) - - -@pytest.mark.parametrize( - ("key", "expectation"), - [ - # Negative index - (-1, pytest.raises(NotImplementedError)), - # Slice with negative start - (slice(-1, None, None), pytest.raises(NotImplementedError)), - # Slice with negatiev end - (slice(0, -1, None), pytest.raises(NotImplementedError)), - # Slice with step not equal to 1 - (slice(0, 2, 2), pytest.raises(NotImplementedError)), - ], -) -def test_getitem_notsupported(key, expectation, repeated_df): - with expectation as e: - assert repeated_df["int_list_col"].list[key] == e - - -@pytest.mark.parametrize( - ("column_name", "dtype"), - [ - pytest.param("int_list_col", pd.ArrowDtype(pa.list_(pa.int64()))), - pytest.param("bool_list_col", pd.ArrowDtype(pa.list_(pa.bool_()))), - pytest.param("float_list_col", pd.ArrowDtype(pa.list_(pa.float64()))), - pytest.param("date_list_col", pd.ArrowDtype(pa.list_(pa.date32()))), - pytest.param("date_time_list_col", pd.ArrowDtype(pa.list_(pa.timestamp("us")))), - pytest.param("numeric_list_col", pd.ArrowDtype(pa.list_(pa.decimal128(38, 9)))), - pytest.param("string_list_col", pd.ArrowDtype(pa.list_(pa.string()))), - ], -) -def test_len(column_name, dtype, repeated_df, repeated_pandas_df): - if packaging.version.Version(pd.__version__) < packaging.version.Version("2.2.0"): - pytest.skip( - "https://pandas.pydata.org/docs/whatsnew/v2.2.0.html#series-list-accessor-for-pyarrow-list-data" - ) - - bf_result = repeated_df[column_name].list.len().to_pandas() - pd_result = repeated_pandas_df[column_name].astype(dtype).list.len() - - assert_series_equal( - pd_result, - bf_result, - check_dtype=False, - check_index_type=False, - check_names=False, - ) - - -@pytest.mark.parametrize( - ("column_name", "dtype"), - [ - pytest.param("int_list_col", pd.ArrowDtype(pa.list_(pa.int64()))), - pytest.param("float_list_col", pd.ArrowDtype(pa.list_(pa.float64()))), - ], -) -@pytest.mark.parametrize( - ("func",), - [ - pytest.param(len), - pytest.param(all), - pytest.param(any), - pytest.param(min), - pytest.param(max), - pytest.param(sum), - ], -) -def test_list_apply_callable(column_name, dtype, repeated_df, repeated_pandas_df, func): - bf_result = repeated_df[column_name].apply(func).to_pandas() - pd_result = repeated_pandas_df[column_name].astype(dtype).apply(func) - pd_result.index = pd_result.index.astype("Int64") - - assert_series_equal( - pd_result, - bf_result, - check_dtype=False, - ) diff --git a/tests/system/small/operations/test_plotting.py b/tests/system/small/operations/test_plotting.py deleted file mode 100644 index e579c90b4df..00000000000 --- a/tests/system/small/operations/test_plotting.py +++ /dev/null @@ -1,467 +0,0 @@ -# Copyright 2023 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 numpy as np -import pandas as pd -import pandas._testing as tm -import pytest -from matplotlib.collections import PathCollection - -import bigframes.operations._matplotlib.core as bf_mpl -import bigframes.pandas as bpd - - -def _check_legend_labels(ax, labels): - """ - Check the ax has expected legend label - """ - assert ax.get_legend() is not None - texts = ax.get_legend().get_texts() - actual_labels = [t.get_text() for t in texts] - assert len(actual_labels) == len(labels) - for label, e in zip(actual_labels, labels): - assert label == e - - -@pytest.mark.parametrize( - ("alias"), - [ - pytest.param(True), - pytest.param(False), - ], -) -def test_series_hist_bins(scalars_dfs, alias): - scalars_df, scalars_pandas_df = scalars_dfs - bins = 5 - if alias: - ax = scalars_df["int64_col"].hist(bins=bins) - else: - ax = scalars_df["int64_col"].plot.hist(bins=bins) - pd_ax = scalars_pandas_df["int64_col"].plot.hist(bins=bins) - - # Compares axis values and height between bigframes and pandas histograms. - # Note: Due to potential float rounding by matplotlib, this test may not - # be applied to all cases. - assert len(ax.patches) == len(pd_ax.patches) - for i in range(len(ax.patches)): - assert ax.patches[i].xy == pd_ax.patches[i].xy - assert ax.patches[i]._height == pd_ax.patches[i]._height - - -@pytest.mark.parametrize( - ("alias"), - [ - pytest.param(True), - pytest.param(False), - ], -) -def test_dataframes_hist_bins(scalars_dfs, alias): - scalars_df, scalars_pandas_df = scalars_dfs - bins = 7 - columns = ["int64_col", "int64_too", "float64_col"] - if alias: - ax = scalars_df[columns].hist(bins=bins) - else: - ax = scalars_df[columns].plot.hist(bins=bins) - pd_ax = scalars_pandas_df[columns].plot.hist(bins=bins) - - # Compares axis values and height between bigframes and pandas histograms. - # Note: Due to potential float rounding by matplotlib, this test may not - # be applied to all cases. - assert len(ax.patches) == len(pd_ax.patches) - for i in range(len(ax.patches)): - assert ax.patches[i]._height == pd_ax.patches[i]._height - - -@pytest.mark.parametrize( - ("col_names"), - [ - pytest.param(["int64_col"]), - pytest.param(["float64_col"]), - pytest.param(["int64_too", "bool_col"]), - pytest.param(["bool_col"], marks=pytest.mark.xfail(raises=TypeError)), - pytest.param(["date_col"], marks=pytest.mark.xfail(raises=TypeError)), - pytest.param(["datetime_col"], marks=pytest.mark.xfail(raises=TypeError)), - pytest.param(["time_col"], marks=pytest.mark.xfail(raises=TypeError)), - pytest.param(["timestamp_col"], marks=pytest.mark.xfail(raises=TypeError)), - ], -) -def test_hist_include_types(scalars_dfs, col_names): - scalars_df, _ = scalars_dfs - ax = scalars_df[col_names].plot.hist() - assert len(ax.patches) == 10 - - -@pytest.mark.parametrize( - ("arg_name", "arg_value"), - [ - pytest.param( - "by", ["int64_col"], marks=pytest.mark.xfail(raises=NotImplementedError) - ), - pytest.param( - "bins", [1, 3, 5], marks=pytest.mark.xfail(raises=NotImplementedError) - ), - pytest.param( - "weight", [2, 3], marks=pytest.mark.xfail(raises=NotImplementedError) - ), - pytest.param( - "backend", - "backend.module", - marks=pytest.mark.xfail(raises=NotImplementedError), - ), - ], -) -def test_hist_not_implemented_error(scalars_dfs, arg_name, arg_value): - scalars_df, _ = scalars_dfs - kwargs = {arg_name: arg_value} - scalars_df.plot.hist(**kwargs) - - -def test_hist_kwargs_true_subplots(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - columns = ["int64_col", "int64_too", "float64_col"] - axes = scalars_df[columns].plot.hist(subplots=True) - pd_axes = scalars_pandas_df[columns].plot.hist(subplots=True) - assert len(axes) == len(pd_axes) - - expected_labels = (["int64_col"], ["int64_too"], ["float64_col"]) - for ax, labels in zip(axes, expected_labels): - _check_legend_labels(ax, labels) - - -def test_hist_kwargs_list_subplots(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - columns = ["int64_col", "int64_too", "float64_col"] - subplots = [["int64_col", "int64_too"]] - axes = scalars_df[columns].plot.hist(subplots=subplots) - pd_axes = scalars_pandas_df[columns].plot.hist(subplots=subplots) - assert len(axes) == len(pd_axes) - - expected_labels = (["int64_col", "int64_too"], ["float64_col"]) - for ax, labels in zip(axes, expected_labels): - _check_legend_labels(ax, labels=labels) - - -@pytest.mark.parametrize( - ("orientation"), - [ - pytest.param("horizontal"), - pytest.param("vertical"), - ], -) -def test_hist_kwargs_orientation(scalars_dfs, orientation): - scalars_df, scalars_pandas_df = scalars_dfs - ax = scalars_df["int64_col"].plot.hist(orientation=orientation) - pd_ax = scalars_pandas_df["int64_col"].plot.hist(orientation=orientation) - assert ax.xaxis.get_label().get_text() == pd_ax.xaxis.get_label().get_text() - assert ax.yaxis.get_label().get_text() == pd_ax.yaxis.get_label().get_text() - - -def test_hist_kwargs_ticks_props(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - - xticks = [20, 18] - yticks = [30, 40] - - ax = scalars_df["float64_col"].plot.hist(xticks=xticks, yticks=yticks) - pd_ax = scalars_pandas_df["float64_col"].plot.hist(xticks=xticks, yticks=yticks) - xlabels = ax.get_xticklabels() - pd_xlables = pd_ax.get_xticklabels() - assert len(xlabels) == len(pd_xlables) - for i in range(len(pd_xlables)): - tm.assert_almost_equal(xlabels[i].get_fontsize(), pd_xlables[i].get_fontsize()) - tm.assert_almost_equal(xlabels[i].get_rotation(), pd_xlables[i].get_rotation()) - - ylabels = ax.get_yticklabels() - pd_ylables = pd_ax.get_yticklabels() - assert len(xlabels) == len(pd_xlables) - for i in range(len(pd_xlables)): - tm.assert_almost_equal(ylabels[i].get_fontsize(), pd_ylables[i].get_fontsize()) - tm.assert_almost_equal(ylabels[i].get_rotation(), pd_ylables[i].get_rotation()) - - -@pytest.mark.parametrize( - ("col_names", "alias"), - [ - pytest.param( - ["int64_col", "float64_col", "int64_too", "bool_col"], True, id="df_alias" - ), - pytest.param( - ["int64_col", "float64_col", "int64_too", "bool_col"], False, id="df" - ), - pytest.param(["int64_col"], True, id="series_alias"), - pytest.param(["int64_col"], False, id="series"), - ], -) -def test_line(scalars_dfs, col_names, alias): - scalars_df, scalars_pandas_df = scalars_dfs - if alias: - ax = scalars_df[col_names].line() - else: - ax = scalars_df[col_names].plot.line() - pd_ax = scalars_pandas_df[col_names].plot.line() - tm.assert_almost_equal(ax.get_xticks(), pd_ax.get_xticks()) - tm.assert_almost_equal(ax.get_yticks(), pd_ax.get_yticks()) - for line, pd_line in zip(ax.lines, pd_ax.lines): - # Compare y coordinates between the lines - tm.assert_almost_equal(line.get_data()[1], pd_line.get_data()[1]) - - -@pytest.mark.parametrize( - ("col_names", "alias"), - [ - pytest.param(["int64_col", "float64_col", "int64_too"], True, id="df_alias"), - pytest.param(["int64_col", "float64_col", "int64_too"], False, id="df"), - pytest.param(["int64_col"], True, id="series_alias"), - pytest.param(["int64_col"], False, id="series"), - ], -) -def test_area(scalars_dfs, col_names, alias): - scalars_df, scalars_pandas_df = scalars_dfs - if alias: - ax = scalars_df[col_names].area(stacked=False) - else: - ax = scalars_df[col_names].plot.area(stacked=False) - pd_ax = scalars_pandas_df[col_names].plot.area(stacked=False) - tm.assert_almost_equal(ax.get_xticks(), pd_ax.get_xticks()) - tm.assert_almost_equal(ax.get_yticks(), pd_ax.get_yticks()) - for line, pd_line in zip(ax.lines, pd_ax.lines): - # Compare y coordinates between the lines - tm.assert_almost_equal(line.get_data()[1], pd_line.get_data()[1]) - - -@pytest.mark.parametrize( - ("col_names", "alias"), - [ - pytest.param(["int64_col", "float64_col", "int64_too"], True, id="df_alias"), - pytest.param(["int64_col", "float64_col", "int64_too"], False, id="df"), - pytest.param(["int64_col"], True, id="series_alias"), - pytest.param(["int64_col"], False, id="series"), - ], -) -def test_bar(scalars_dfs, col_names, alias): - scalars_df, scalars_pandas_df = scalars_dfs - if alias: - ax = scalars_df[col_names].bar() - else: - ax = scalars_df[col_names].plot.bar() - pd_ax = scalars_pandas_df[col_names].plot.bar() - tm.assert_almost_equal(ax.get_xticks(), pd_ax.get_xticks()) - tm.assert_almost_equal(ax.get_yticks(), pd_ax.get_yticks()) - for line, pd_line in zip(ax.lines, pd_ax.lines): - # Compare y coordinates between the lines - tm.assert_almost_equal(line.get_data()[1], pd_line.get_data()[1]) - - -@pytest.mark.parametrize( - ("col_names",), - [ - pytest.param(["int64_col", "float64_col", "int64_too"], id="df"), - pytest.param(["int64_col"], id="series"), - ], -) -def test_barh(scalars_dfs, col_names): - scalars_df, scalars_pandas_df = scalars_dfs - ax = scalars_df[col_names].plot.barh() - pd_ax = scalars_pandas_df[col_names].plot.barh() - tm.assert_almost_equal(ax.get_xticks(), pd_ax.get_xticks()) - tm.assert_almost_equal(ax.get_yticks(), pd_ax.get_yticks()) - for line, pd_line in zip(ax.lines, pd_ax.lines): - # Compare y coordinates between the lines - tm.assert_almost_equal(line.get_data()[1], pd_line.get_data()[1]) - - -@pytest.mark.parametrize( - ("col_names",), - [ - pytest.param(["int64_col", "float64_col", "int64_too"], id="df"), - pytest.param(["int64_col"], id="series"), - ], -) -def test_pie(scalars_dfs, col_names): - scalars_df, scalars_pandas_df = scalars_dfs - ax = scalars_df[col_names].abs().plot.pie(y="int64_col") - pd_ax = scalars_pandas_df[col_names].abs().plot.pie(y="int64_col") - tm.assert_almost_equal(ax.get_xticks(), pd_ax.get_xticks()) - tm.assert_almost_equal(ax.get_yticks(), pd_ax.get_yticks()) - for line, pd_line in zip(ax.lines, pd_ax.lines): - # Compare y coordinates between the lines - tm.assert_almost_equal(line.get_data()[1], pd_line.get_data()[1]) - - -@pytest.mark.parametrize( - ("col_names", "alias"), - [ - pytest.param( - ["int64_col", "float64_col", "int64_too", "bool_col"], True, id="df_alias" - ), - pytest.param( - ["int64_col", "float64_col", "int64_too", "bool_col"], False, id="df" - ), - ], -) -def test_scatter(scalars_dfs, col_names, alias): - scalars_df, scalars_pandas_df = scalars_dfs - if alias: - ax = scalars_df[col_names].scatter(x="int64_col", y="float64_col") - else: - ax = scalars_df[col_names].plot.scatter(x="int64_col", y="float64_col") - pd_ax = scalars_pandas_df[col_names].plot.scatter(x="int64_col", y="float64_col") - tm.assert_almost_equal(ax.get_xticks(), pd_ax.get_xticks()) - tm.assert_almost_equal(ax.get_yticks(), pd_ax.get_yticks()) - tm.assert_almost_equal( - ax.collections[0].get_sizes(), pd_ax.collections[0].get_sizes() - ) - - -@pytest.mark.parametrize( - ("c"), - [ - pytest.param("red", id="red"), - pytest.param("c", id="int_column"), - pytest.param("species", id="color_column"), - pytest.param(3, id="column_index"), - ], -) -def test_scatter_args_c(c): - data = { - "a": [1, 2, 3], - "b": [1, 2, 3], - "c": [1, 2, 3], - "species": ["r", "g", "b"], - } - df = bpd.DataFrame(data) - pd_df = pd.DataFrame(data) - - ax = df.plot.scatter(x="a", y="b", c=c) - pd_ax = pd_df.plot.scatter(x="a", y="b", c=c) - assert len(ax.collections[0].get_facecolor()) == len( - pd_ax.collections[0].get_facecolor() - ) - for idx in range(len(ax.collections[0].get_facecolor())): - tm.assert_numpy_array_equal( - ax.collections[0].get_facecolor()[idx], - pd_ax.collections[0].get_facecolor()[idx], - ) - - -@pytest.mark.parametrize( - ("s"), - [ - pytest.param([10, 34, 50], id="int"), - pytest.param([1.0, 3.4, 5.0], id="float"), - pytest.param( - [True, True, False], id="bool", marks=pytest.mark.xfail(raises=ValueError) - ), - ], -) -def test_scatter_args_s(s): - data = { - "a": [1, 2, 3], - "b": [1, 2, 3], - } - data["s"] = s - df = bpd.DataFrame(data) - pd_df = pd.DataFrame(data) - - ax = df.plot.scatter(x="a", y="b", s="s") - pd_ax = pd_df.plot.scatter(x="a", y="b", s="s") - - assert isinstance(pd_ax.collections[0], PathCollection) - tm.assert_numpy_array_equal( - ax.collections[0].get_sizes(), pd_ax.collections[0].get_sizes() - ) - - -@pytest.mark.parametrize( - ("arg_name"), - [ - pytest.param("c", marks=pytest.mark.xfail(raises=NotImplementedError)), - pytest.param("s", marks=pytest.mark.xfail(raises=NotImplementedError)), - ], -) -def test_scatter_sequence_arg(arg_name): - data = { - "a": [1, 2, 3], - "b": [1, 2, 3], - } - arg_value = [3, 3, 1] - bpd.DataFrame(data).plot.scatter(x="a", y="b", **{arg_name: arg_value}) - - -def test_sampling_plot_args_n(): - df = bpd.DataFrame(np.arange(bf_mpl.DEFAULT_SAMPLING_N * 10), columns=["one"]) - ax = df.plot.line() - assert len(ax.lines) == 1 - assert len(ax.lines[0].get_data()[1]) == bf_mpl.DEFAULT_SAMPLING_N - - ax = df.plot.line(sampling_n=2) - assert len(ax.lines) == 1 - assert len(ax.lines[0].get_data()[1]) == 2 - - -def test_sampling_plot_args_random_state(): - df = bpd.DataFrame(np.arange(bf_mpl.DEFAULT_SAMPLING_N * 10), columns=["one"]) - ax_0 = df.plot.line() - ax_1 = df.plot.line() - ax_2 = df.plot.line(sampling_random_state=100) - ax_3 = df.plot.line(sampling_random_state=100) - - # Setting a fixed sampling_random_state guarantees reproducible plotted sampling. - tm.assert_almost_equal(ax_0.lines[0].get_data()[1], ax_1.lines[0].get_data()[1]) - tm.assert_almost_equal(ax_2.lines[0].get_data()[1], ax_3.lines[0].get_data()[1]) - - msg = "numpy array are different" - with pytest.raises(AssertionError, match=msg): - tm.assert_almost_equal(ax_0.lines[0].get_data()[1], ax_2.lines[0].get_data()[1]) - - -def test_sampling_preserve_ordering(): - df = bpd.DataFrame([0.0, 1.0, 2.0, 3.0, 4.0], index=[1, 3, 4, 2, 0]) - pd_df = pd.DataFrame([0.0, 1.0, 2.0, 3.0, 4.0], index=[1, 3, 4, 2, 0]) - ax = df.plot.line() - pd_ax = pd_df.plot.line() - tm.assert_almost_equal(ax.get_xticks(), pd_ax.get_xticks()) - tm.assert_almost_equal(ax.get_yticks(), pd_ax.get_yticks()) - for line, pd_line in zip(ax.lines, pd_ax.lines): - # Compare y coordinates between the lines - tm.assert_almost_equal(line.get_data()[1], pd_line.get_data()[1]) - - -@pytest.mark.parametrize( - ("kind", "col_names", "kwargs"), - [ - pytest.param("hist", ["int64_col", "int64_too"], {}), - pytest.param("line", ["int64_col", "int64_too"], {}), - pytest.param("area", ["int64_col", "int64_too"], {"stacked": False}), - pytest.param( - "scatter", ["int64_col", "int64_too"], {"x": "int64_col", "y": "int64_too"} - ), - pytest.param( - "scatter", - ["int64_col"], - {}, - marks=pytest.mark.xfail(raises=ValueError), - ), - pytest.param( - "bar", - ["int64_col", "int64_too"], - {}, - marks=pytest.mark.xfail(raises=NotImplementedError), - ), - ], -) -def test_plot_call(scalars_dfs, kind, col_names, kwargs): - scalars_df, _ = scalars_dfs - scalars_df[col_names].plot(kind=kind, **kwargs) diff --git a/tests/system/small/operations/test_strings.py b/tests/system/small/operations/test_strings.py index 94285cc7dc4..241cbd576b1 100644 --- a/tests/system/small/operations/test_strings.py +++ b/tests/system/small/operations/test_strings.py @@ -15,24 +15,23 @@ import re import pandas as pd -import pyarrow as pa import pytest -import bigframes.dtypes as dtypes -import bigframes.pandas as bpd -from bigframes.testing.utils import assert_series_equal +import bigframes.series + +from ...utils import assert_series_equal_ignoring_order def test_find(scalars_dfs): scalars_df, scalars_pandas_df = scalars_dfs col_name = "string_col" - bf_series: bpd.Series = scalars_df[col_name] + bf_series: bigframes.series.Series = scalars_df[col_name] bf_result = bf_series.str.find("W").to_pandas() pd_result = scalars_pandas_df[col_name].str.find("W") # One of type mismatches to be documented. Here, the `bf_result.dtype` is `Int64` but # the `pd_result.dtype` is `float64`: https://github.com/pandas-dev/pandas/issues/51948 - assert_series_equal( + assert_series_equal_ignoring_order( pd_result.astype(pd.Int64Dtype()), bf_result, ) @@ -51,7 +50,7 @@ def test_find(scalars_dfs): def test_str_contains(scalars_dfs, pat, case, flags, regex): scalars_df, scalars_pandas_df = scalars_dfs col_name = "string_col" - bf_series: bpd.Series = scalars_df[col_name] + bf_series: bigframes.series.Series = scalars_df[col_name] bf_result = bf_series.str.contains( pat, case=case, flags=flags, regex=regex @@ -73,11 +72,13 @@ def test_str_contains(scalars_dfs, pat, case, flags, regex): def test_str_extract(scalars_dfs, pat): scalars_df, scalars_pandas_df = scalars_dfs col_name = "string_col" - bf_series: bpd.Series = scalars_df[col_name] + bf_series: bigframes.series.Series = scalars_df[col_name] bf_result = bf_series.str.extract(pat).to_pandas() pd_result = scalars_pandas_df[col_name].str.extract(pat) + # Pandas produces int col labels, while bq df only supports str labels at present + pd_result = pd_result.set_axis(pd_result.columns.astype(str), axis=1) pd.testing.assert_frame_equal( pd_result, bf_result, @@ -93,15 +94,12 @@ def test_str_extract(scalars_dfs, pat): (".*", "blah", True, 0, True), ("h.l", "blah", False, 0, True), (re.compile("(?i).e.."), "blah", None, 0, True), - ("H", "h", True, 0, False), - (", ", "__", True, 0, False), - (re.compile(r"hEllo", flags=re.I), "blah", None, 0, True), ], ) def test_str_replace(scalars_dfs, pat, repl, case, flags, regex): scalars_df, scalars_pandas_df = scalars_dfs col_name = "string_col" - bf_series: bpd.Series = scalars_df[col_name] + bf_series: bigframes.series.Series = scalars_df[col_name] bf_result = bf_series.str.replace( pat, repl=repl, case=case, flags=flags, regex=regex @@ -132,7 +130,7 @@ def test_str_replace(scalars_dfs, pat, repl, case, flags, regex): def test_str_startswith(scalars_dfs, pat): scalars_df, scalars_pandas_df = scalars_dfs col_name = "string_col" - bf_series: bpd.Series = scalars_df[col_name] + bf_series: bigframes.series.Series = scalars_df[col_name] pd_series = scalars_pandas_df[col_name].astype("object") bf_result = bf_series.str.startswith(pat).to_pandas() @@ -157,7 +155,7 @@ def test_str_startswith(scalars_dfs, pat): def test_str_endswith(scalars_dfs, pat): scalars_df, scalars_pandas_df = scalars_dfs col_name = "string_col" - bf_series: bpd.Series = scalars_df[col_name] + bf_series: bigframes.series.Series = scalars_df[col_name] pd_series = scalars_pandas_df[col_name].astype("object") bf_result = bf_series.str.endswith(pat).to_pandas() @@ -169,46 +167,26 @@ def test_str_endswith(scalars_dfs, pat): def test_len(scalars_dfs): scalars_df, scalars_pandas_df = scalars_dfs col_name = "string_col" - bf_series: bpd.Series = scalars_df[col_name] + bf_series: bigframes.series.Series = scalars_df[col_name] bf_result = bf_series.str.len().to_pandas() pd_result = scalars_pandas_df[col_name].str.len() # One of dtype mismatches to be documented. Here, the `bf_result.dtype` is `Int64` but # the `pd_result.dtype` is `float64`: https://github.com/pandas-dev/pandas/issues/51948 - assert_series_equal( + assert_series_equal_ignoring_order( pd_result.astype(pd.Int64Dtype()), bf_result, ) -def test_len_with_array_column(nested_df, nested_pandas_df): - """ - Series.str.len() is expected to work on columns containing lists as well as strings. - - See: https://stackoverflow.com/a/41340543/101923 - """ - col_name = "event_sequence" - bf_series: bpd.Series = nested_df[col_name] - bf_result = bf_series.str.len().to_pandas() - pd_result = nested_pandas_df[col_name].str.len() - - # One of dtype mismatches to be documented. Here, the `bf_result.dtype` is `Int64` but - # the `pd_result.dtype` is `float64`: https://github.com/pandas-dev/pandas/issues/51948 - assert_series_equal( - pd_result.astype(pd.Int64Dtype()), - bf_result, - check_index_type=False, - ) - - def test_lower(scalars_dfs): scalars_df, scalars_pandas_df = scalars_dfs col_name = "string_col" - bf_series: bpd.Series = scalars_df[col_name] + bf_series: bigframes.series.Series = scalars_df[col_name] bf_result = bf_series.str.lower().to_pandas() pd_result = scalars_pandas_df[col_name].str.lower() - assert_series_equal( + assert_series_equal_ignoring_order( pd_result, bf_result, ) @@ -217,7 +195,7 @@ def test_lower(scalars_dfs): def test_reverse(scalars_dfs): scalars_df, scalars_pandas_df = scalars_dfs col_name = "string_col" - bf_series: bpd.Series = scalars_df[col_name] + bf_series: bigframes.series.Series = scalars_df[col_name] bf_result = bf_series.str.reverse().to_pandas() pd_result = scalars_pandas_df[col_name].copy() for i in pd_result.index: @@ -227,37 +205,24 @@ def test_reverse(scalars_dfs): else: pd_result.loc[i] = cell[::-1] - assert_series_equal( + assert_series_equal_ignoring_order( pd_result, bf_result, ) @pytest.mark.parametrize( - ["start", "stop"], - [ - (0, 1), - (3, 5), - (100, 101), - (None, 1), - (0, 12), - (0, None), - (None, -1), - (-1, None), - (-5, -1), - (1, -1), - (-10, 10), - ], + ["start", "stop"], [(0, 1), (3, 5), (100, 101), (None, 1), (0, 12), (0, None)] ) def test_slice(scalars_dfs, start, stop): scalars_df, scalars_pandas_df = scalars_dfs col_name = "string_col" - bf_series: bpd.Series = scalars_df[col_name] + bf_series: bigframes.series.Series = scalars_df[col_name] bf_result = bf_series.str.slice(start, stop).to_pandas() pd_series = scalars_pandas_df[col_name] pd_result = pd_series.str.slice(start, stop) - assert_series_equal( + assert_series_equal_ignoring_order( pd_result, bf_result, ) @@ -266,33 +231,11 @@ def test_slice(scalars_dfs, start, stop): def test_strip(scalars_dfs): scalars_df, scalars_pandas_df = scalars_dfs col_name = "string_col" - bf_series: bpd.Series = scalars_df[col_name] + bf_series: bigframes.series.Series = scalars_df[col_name] bf_result = bf_series.str.strip().to_pandas() pd_result = scalars_pandas_df[col_name].str.strip() - assert_series_equal( - pd_result, - bf_result, - ) - - -@pytest.mark.parametrize( - ("to_strip"), - [ - pytest.param(None, id="none"), - pytest.param(" ", id="space"), - pytest.param(" \n", id="space_newline"), - pytest.param("123.!? \n\t", id="multiple_chars"), - ], -) -def test_strip_w_to_strip(to_strip): - s = bpd.Series(["1. Ant. ", "2. Bee!\n", "3. Cat?\t", pd.NA]) - pd_s = s.to_pandas() - - bf_result = s.str.strip(to_strip=to_strip).to_pandas() - pd_result = pd_s.str.strip(to_strip=to_strip) - - assert_series_equal( + assert_series_equal_ignoring_order( pd_result, bf_result, ) @@ -301,11 +244,11 @@ def test_strip_w_to_strip(to_strip): def test_upper(scalars_dfs): scalars_df, scalars_pandas_df = scalars_dfs col_name = "string_col" - bf_series: bpd.Series = scalars_df[col_name] + bf_series: bigframes.series.Series = scalars_df[col_name] bf_result = bf_series.str.upper().to_pandas() pd_result = scalars_pandas_df[col_name].str.upper() - assert_series_equal( + assert_series_equal_ignoring_order( pd_result, bf_result, ) @@ -317,7 +260,7 @@ def test_isnumeric(weird_strings, weird_strings_pd): pd.testing.assert_series_equal( bf_result, - pd_result.astype(pd.BooleanDtype()), + pd_result.astype(pd.BooleanDtype()) # the dtype here is a case of intentional diversion from pandas # see go/bigframes-dtypes ) @@ -329,21 +272,19 @@ def test_isalpha(weird_strings, weird_strings_pd): pd.testing.assert_series_equal( bf_result, - pd_result.astype(pd.BooleanDtype()), + pd_result.astype(pd.BooleanDtype()) # the dtype here is a case of intentional diversion from pandas # see go/bigframes-dtypes ) def test_isdigit(weird_strings, weird_strings_pd): - # check the behavior against normal pandas str, since pyarrow has a bug with superscripts/fractions b/333484335 - # astype object instead of str to support pd.NA - pd_result = weird_strings_pd.astype(object).str.isdigit() + pd_result = weird_strings_pd.str.isdigit() bf_result = weird_strings.str.isdigit().to_pandas() pd.testing.assert_series_equal( bf_result, - pd_result.astype(pd.BooleanDtype()), + pd_result.astype(pd.BooleanDtype()) # the dtype here is a case of intentional diversion from pandas # see go/bigframes-dtypes ) @@ -355,7 +296,7 @@ def test_isdecimal(weird_strings, weird_strings_pd): pd.testing.assert_series_equal( bf_result, - pd_result.astype(pd.BooleanDtype()), + pd_result.astype(pd.BooleanDtype()) # the dtype here is a case of intentional diversion from pandas # see go/bigframes-dtypes ) @@ -367,7 +308,7 @@ def test_isalnum(weird_strings, weird_strings_pd): pd.testing.assert_series_equal( bf_result, - pd_result.astype(pd.BooleanDtype()), + pd_result.astype(pd.BooleanDtype()) # the dtype here is a case of intentional diversion from pandas # see go/bigframes-dtypes ) @@ -379,7 +320,7 @@ def test_isspace(weird_strings, weird_strings_pd): pd.testing.assert_series_equal( bf_result, - pd_result.astype(pd.BooleanDtype()), + pd_result.astype(pd.BooleanDtype()) # the dtype here is a case of intentional diversion from pandas # see go/bigframes-dtypes ) @@ -389,9 +330,9 @@ def test_islower(weird_strings, weird_strings_pd): pd_result = weird_strings_pd.str.islower() bf_result = weird_strings.str.islower().to_pandas() - assert_series_equal( + assert_series_equal_ignoring_order( bf_result, - pd_result.astype(pd.BooleanDtype()), + pd_result.astype(pd.BooleanDtype()) # the dtype here is a case of intentional diversion from pandas # see go/bigframes-dtypes ) @@ -401,9 +342,9 @@ def test_isupper(weird_strings, weird_strings_pd): pd_result = weird_strings_pd.str.isupper() bf_result = weird_strings.str.isupper().to_pandas() - assert_series_equal( + assert_series_equal_ignoring_order( bf_result, - pd_result.astype(pd.BooleanDtype()), + pd_result.astype(pd.BooleanDtype()) # the dtype here is a case of intentional diversion from pandas # see go/bigframes-dtypes ) @@ -412,33 +353,11 @@ def test_isupper(weird_strings, weird_strings_pd): def test_rstrip(scalars_dfs): scalars_df, scalars_pandas_df = scalars_dfs col_name = "string_col" - bf_series: bpd.Series = scalars_df[col_name] + bf_series: bigframes.series.Series = scalars_df[col_name] bf_result = bf_series.str.rstrip().to_pandas() pd_result = scalars_pandas_df[col_name].str.rstrip() - assert_series_equal( - pd_result, - bf_result, - ) - - -@pytest.mark.parametrize( - ("to_strip"), - [ - pytest.param(None, id="none"), - pytest.param(" ", id="space"), - pytest.param(" \n", id="space_newline"), - pytest.param("123.!? \n\t", id="multiple_chars"), - ], -) -def test_rstrip_w_to_strip(to_strip): - s = bpd.Series(["1. Ant. ", "2. Bee!\n", "3. Cat?\t", pd.NA]) - pd_s = s.to_pandas() - - bf_result = s.str.rstrip(to_strip=to_strip).to_pandas() - pd_result = pd_s.str.rstrip(to_strip=to_strip) - - assert_series_equal( + assert_series_equal_ignoring_order( pd_result, bf_result, ) @@ -447,33 +366,11 @@ def test_rstrip_w_to_strip(to_strip): def test_lstrip(scalars_dfs): scalars_df, scalars_pandas_df = scalars_dfs col_name = "string_col" - bf_series: bpd.Series = scalars_df[col_name] + bf_series: bigframes.series.Series = scalars_df[col_name] bf_result = bf_series.str.lstrip().to_pandas() pd_result = scalars_pandas_df[col_name].str.lstrip() - assert_series_equal( - pd_result, - bf_result, - ) - - -@pytest.mark.parametrize( - ("to_strip"), - [ - pytest.param(None, id="none"), - pytest.param(" ", id="space"), - pytest.param(" \n", id="space_newline"), - pytest.param("123.!? \n\t", id="multiple_chars"), - ], -) -def test_lstrip_w_to_strip(to_strip): - s = bpd.Series(["1. Ant. ", "2. Bee!\n", "3. Cat?\t", pd.NA]) - pd_s = s.to_pandas() - - bf_result = s.str.lstrip(to_strip=to_strip).to_pandas() - pd_result = pd_s.str.lstrip(to_strip=to_strip) - - assert_series_equal( + assert_series_equal_ignoring_order( pd_result, bf_result, ) @@ -483,11 +380,11 @@ def test_lstrip_w_to_strip(to_strip): def test_repeat(scalars_dfs, repeats): scalars_df, scalars_pandas_df = scalars_dfs col_name = "string_col" - bf_series: bpd.Series = scalars_df[col_name] + bf_series: bigframes.series.Series = scalars_df[col_name] bf_result = bf_series.str.repeat(repeats).to_pandas() pd_result = scalars_pandas_df[col_name].str.repeat(repeats) - assert_series_equal( + assert_series_equal_ignoring_order( pd_result, bf_result, ) @@ -496,11 +393,11 @@ def test_repeat(scalars_dfs, repeats): def test_capitalize(scalars_dfs): scalars_df, scalars_pandas_df = scalars_dfs col_name = "string_col" - bf_series: bpd.Series = scalars_df[col_name] + bf_series: bigframes.series.Series = scalars_df[col_name] bf_result = bf_series.str.capitalize().to_pandas() pd_result = scalars_pandas_df[col_name].str.capitalize() - assert_series_equal( + assert_series_equal_ignoring_order( pd_result, bf_result, ) @@ -509,16 +406,16 @@ def test_capitalize(scalars_dfs): def test_cat_with_series(scalars_dfs): scalars_df, scalars_pandas_df = scalars_dfs col_name = "string_col" - bf_filter: bpd.Series = scalars_df["bool_col"] - bf_left: bpd.Series = scalars_df[col_name][bf_filter] - bf_right: bpd.Series = scalars_df[col_name] + bf_filter: bigframes.series.Series = scalars_df["bool_col"] + bf_left: bigframes.series.Series = scalars_df[col_name][bf_filter] + bf_right: bigframes.series.Series = scalars_df[col_name] bf_result = bf_left.str.cat(others=bf_right).to_pandas() pd_filter = scalars_pandas_df["bool_col"] pd_left = scalars_pandas_df[col_name][pd_filter] pd_right = scalars_pandas_df[col_name] pd_result = pd_left.str.cat(others=pd_right) - assert_series_equal( + assert_series_equal_ignoring_order( pd_result, bf_result, ) @@ -528,11 +425,11 @@ def test_str_match(scalars_dfs): scalars_df, scalars_pandas_df = scalars_dfs col_name = "string_col" pattern = "[A-Z].*" - bf_series: bpd.Series = scalars_df[col_name] + bf_series: bigframes.series.Series = scalars_df[col_name] bf_result = bf_series.str.match(pattern).to_pandas() pd_result = scalars_pandas_df[col_name].str.match(pattern) - assert_series_equal( + assert_series_equal_ignoring_order( pd_result, bf_result, ) @@ -542,11 +439,11 @@ def test_str_fullmatch(scalars_dfs): scalars_df, scalars_pandas_df = scalars_dfs col_name = "string_col" pattern = "[A-Z].*!" - bf_series: bpd.Series = scalars_df[col_name] + bf_series: bigframes.series.Series = scalars_df[col_name] bf_result = bf_series.str.fullmatch(pattern).to_pandas() pd_result = scalars_pandas_df[col_name].str.fullmatch(pattern) - assert_series_equal( + assert_series_equal_ignoring_order( pd_result, bf_result, ) @@ -555,11 +452,11 @@ def test_str_fullmatch(scalars_dfs): def test_str_get(scalars_dfs): scalars_df, scalars_pandas_df = scalars_dfs col_name = "string_col" - bf_series: bpd.Series = scalars_df[col_name] + bf_series: bigframes.series.Series = scalars_df[col_name] bf_result = bf_series.str.get(8).to_pandas() pd_result = scalars_pandas_df[col_name].str.get(8) - assert_series_equal( + assert_series_equal_ignoring_order( pd_result, bf_result, ) @@ -568,11 +465,11 @@ def test_str_get(scalars_dfs): def test_str_pad(scalars_dfs): scalars_df, scalars_pandas_df = scalars_dfs col_name = "string_col" - bf_series: bpd.Series = scalars_df[col_name] + bf_series: bigframes.series.Series = scalars_df[col_name] bf_result = bf_series.str.pad(8, side="both", fillchar="%").to_pandas() pd_result = scalars_pandas_df[col_name].str.pad(8, side="both", fillchar="%") - assert_series_equal( + assert_series_equal_ignoring_order( pd_result, bf_result, ) @@ -591,11 +488,11 @@ def test_str_zfill(weird_strings, weird_strings_pd): def test_str_ljust(scalars_dfs): scalars_df, scalars_pandas_df = scalars_dfs col_name = "string_col" - bf_series: bpd.Series = scalars_df[col_name] + bf_series: bigframes.series.Series = scalars_df[col_name] bf_result = bf_series.str.ljust(7, fillchar="%").to_pandas() pd_result = scalars_pandas_df[col_name].str.ljust(7, fillchar="%") - assert_series_equal( + assert_series_equal_ignoring_order( pd_result, bf_result, ) @@ -604,157 +501,11 @@ def test_str_ljust(scalars_dfs): def test_str_rjust(scalars_dfs): scalars_df, scalars_pandas_df = scalars_dfs col_name = "string_col" - bf_series: bpd.Series = scalars_df[col_name] + bf_series: bigframes.series.Series = scalars_df[col_name] bf_result = bf_series.str.rjust(9, fillchar="%").to_pandas() pd_result = scalars_pandas_df[col_name].str.rjust(9, fillchar="%") - assert_series_equal( + assert_series_equal_ignoring_order( pd_result, bf_result, ) - - -@pytest.mark.parametrize( - ("pat", "regex"), - [ - pytest.param(" ", None, id="one_char"), - pytest.param("ll", False, id="two_chars"), - pytest.param( - " ", - True, - id="one_char_reg", - marks=pytest.mark.xfail(raises=NotImplementedError), - ), - pytest.param( - "ll", - None, - id="two_chars_reg", - marks=pytest.mark.xfail(raises=NotImplementedError), - ), - ], -) -def test_str_split_raise_errors(scalars_dfs, pat, regex): - scalars_df, scalars_pandas_df = scalars_dfs - col_name = "string_col" - bf_result = scalars_df[col_name].str.split(pat=pat, regex=regex).to_pandas() - pd_result = scalars_pandas_df[col_name].str.split(pat=pat, regex=regex) - - # TODO(b/336880368): Allow for NULL values for ARRAY columns in BigQuery. - pd_result = pd_result.apply(lambda x: [] if pd.isnull(x) is True else x) - - assert_series_equal(pd_result, bf_result, check_dtype=False) - - -@pytest.mark.parametrize( - ("index"), - [ - pytest.param( - "first", id="invalid_type", marks=pytest.mark.xfail(raises=ValueError) - ), - pytest.param( - -1, id="neg_index", marks=pytest.mark.xfail(raises=NotImplementedError) - ), - pytest.param( - slice(0, 2, 2), - id="only_allow_one_step", - marks=pytest.mark.xfail(raises=NotImplementedError), - ), - pytest.param( - slice(-1, None, None), - id="neg_slicing", - marks=pytest.mark.xfail(raises=NotImplementedError), - ), - ], -) -def test_getitem_raise_errors(scalars_dfs, index): - scalars_df, _ = scalars_dfs - col_name = "string_col" - scalars_df[col_name].str[index] - - -@pytest.mark.parametrize( - ("index"), - [ - pytest.param(2, id="int"), - pytest.param(slice(None, None, None), id="default_start_slice"), - pytest.param(slice(0, None, 1), id="default_stop_slice"), - pytest.param(slice(0, 2, None), id="default_step_slice"), - ], -) -def test_getitem_w_string(scalars_dfs, index): - scalars_df, scalars_pandas_df = scalars_dfs - col_name = "string_col" - bf_result = scalars_df[col_name].str[index].to_pandas() - pd_result = scalars_pandas_df[col_name].str[index] - - assert_series_equal(pd_result, bf_result) - - -@pytest.mark.parametrize( - ("index"), - [ - pytest.param(0, id="int"), - pytest.param(slice(None, None, None), id="default_start_slice"), - pytest.param(slice(0, None, 1), id="default_stop_slice"), - pytest.param(slice(0, 2, None), id="default_step_slice"), - pytest.param(slice(0, 0, None), id="single_one_slice"), - ], -) -@pytest.mark.parametrize( - "column_name", - [ - pytest.param("int_list_col"), - pytest.param("bool_list_col"), - pytest.param("float_list_col"), - pytest.param("string_list_col"), - # date, date_time and numeric are excluded because their default types are different - # in Pandas and BigFrames - ], -) -def test_getitem_w_array(index, column_name, repeated_df, repeated_pandas_df): - bf_result = repeated_df[column_name].str[index].to_pandas() - pd_result = repeated_pandas_df[column_name].str[index] - - assert_series_equal(pd_result, bf_result, check_dtype=False, check_index_type=False) - - -def test_getitem_w_struct_array(): - pa_struct = pa.struct( - [ - ("name", pa.string()), - ("age", pa.int64()), - ] - ) - data: list[list[dict]] = [ - [ - {"name": "Alice", "age": 30}, - {"name": "Bob", "age": 25}, - ], - [ - {"name": "Charlie", "age": 35}, - {"name": "David", "age": 40}, - {"name": "Eva", "age": 28}, - ], - [], - [{"name": "Frank", "age": 50}], - ] - s = bpd.Series(data, dtype=bpd.ArrowDtype(pa.list_(pa_struct))) - - result = s.str[1] - assert dtypes.is_struct_like(result.dtype) - - expected_data = [item[1] if len(item) > 1 else None for item in data] - expected = bpd.Series(expected_data, dtype=bpd.ArrowDtype((pa_struct))) - - assert_series_equal(result.to_pandas(), expected.to_pandas()) - - -def test_string_join(session): - pd_series = pd.Series([["a", "b", "c"], ["100"], ["hello", "world"], []]) - bf_series = session.read_pandas(pd_series) - - pd_result = pd_series.str.join("--") - bf_result = bf_series.str.join("--").to_pandas() - - pd_result = pd_result.astype("string[pyarrow]") - assert_series_equal(pd_result, bf_result, check_dtype=False, check_index_type=False) diff --git a/tests/system/small/operations/test_struct.py b/tests/system/small/operations/test_struct.py deleted file mode 100644 index ddb65248d08..00000000000 --- a/tests/system/small/operations/test_struct.py +++ /dev/null @@ -1,40 +0,0 @@ -# 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. -# 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. - - -def test_dataframe_struct_explode_multiple_columns(nested_df): - got = nested_df.struct.explode(["label", "address"]) - assert got.columns.to_list() == [ - "customer_id", - "day", - "flag", - "label.key", - "label.value", - "event_sequence", - "address.street", - "address.city", - ] - - -def test_dataframe_struct_explode_separator(nested_df): - got = nested_df.struct.explode("label", separator="__sep__") - assert got.columns.to_list() == [ - "customer_id", - "day", - "flag", - "label__sep__key", - "label__sep__value", - "event_sequence", - "address", - ] diff --git a/tests/system/small/operations/test_timedeltas.py b/tests/system/small/operations/test_timedeltas.py deleted file mode 100644 index 9512950e168..00000000000 --- a/tests/system/small/operations/test_timedeltas.py +++ /dev/null @@ -1,656 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - - -import datetime -import operator - -import numpy as np -import pandas as pd -import pyarrow as pa -import pytest -from packaging import version - -import bigframes.testing.utils -from bigframes import dtypes - -# Some methods/features used by this test don't exist in pandas 1.x -pytest.importorskip("pandas", minversion="2.0.0") - - -@pytest.fixture(scope="module") -def temporal_dfs(session): - pandas_df = pd.DataFrame( - { - "datetime_col": pd.Series( - [ - pd.Timestamp("2025-02-01 01:00:01"), - pd.Timestamp("2019-01-02 02:00:00"), - pd.Timestamp("1997-01-01 19:00:00"), - ], - dtype=dtypes.DATETIME_DTYPE, - ), - "timestamp_col": pd.Series( - [ - pd.Timestamp("2023-01-01 01:00:01", tz="UTC"), - pd.Timestamp("2024-01-02 02:00:00", tz="UTC"), - pd.Timestamp("2005-03-05 02:00:00", tz="UTC"), - ], - dtype=dtypes.TIMESTAMP_DTYPE, - ), - "date_col": pd.Series( - [ - datetime.date(2000, 1, 1), - datetime.date(2001, 2, 3), - datetime.date(2020, 9, 30), - ], - dtype=dtypes.DATE_DTYPE, - ), - "timedelta_col_1": pd.Series( - [ - pd.Timedelta(5, "s"), - pd.Timedelta(-4, "m"), - pd.Timedelta(5, "h"), - ], - dtype=dtypes.TIMEDELTA_DTYPE, - ), - "timedelta_col_2": pd.Series( - [ - pd.Timedelta(3, "s"), - pd.Timedelta(-4, "m"), - pd.Timedelta(6, "h"), - ], - dtype=dtypes.TIMEDELTA_DTYPE, - ), - "float_col": pd.Series([1.5, 2, -3], dtype=dtypes.FLOAT_DTYPE), - "int_col": pd.Series([1, 2, -3], dtype="Int64"), - "positive_int_col": pd.Series([1, 2, 3], dtype="Int64"), - }, - index=pd.Index(range(3), dtype="Int64"), - ) - - bigframes_df = session.read_pandas(pandas_df) - - return bigframes_df, pandas_df - - -def _assert_series_equal(actual: pd.Series, expected: pd.Series): - """Helper function specifically for timedelta testing. Don't use it outside of this module.""" - bigframes.testing.utils.assert_series_equal( - actual, - expected, - check_index_type=False, - check_dtype=False, - ) - - -@pytest.mark.parametrize( - ("op", "col_1", "col_2", "arrow_supported"), - [ - (operator.add, "timedelta_col_1", "timedelta_col_2", True), - (operator.sub, "timedelta_col_1", "timedelta_col_2", True), - (operator.truediv, "timedelta_col_1", "timedelta_col_2", True), - (operator.floordiv, "timedelta_col_1", "timedelta_col_2", True), - (operator.truediv, "timedelta_col_1", "float_col", False), - (operator.floordiv, "timedelta_col_1", "float_col", False), - (operator.mul, "timedelta_col_1", "float_col", False), - (operator.mul, "float_col", "timedelta_col_1", False), - (operator.mod, "timedelta_col_1", "timedelta_col_2", False), - ], -) -def test_timedelta_binary_ops_between_series( - temporal_dfs, op, col_1, col_2, arrow_supported -): - bf_df, pd_df = temporal_dfs - - actual_result = op(bf_df[col_1], bf_df[col_2]).to_pandas() - - if not arrow_supported: - expected_result = pd_df.apply(lambda x: op(x[col_1], x[col_2]), axis=1) - else: - expected_result = op(pd_df[col_1], pd_df[col_2]) - _assert_series_equal(actual_result, expected_result) - - -@pytest.mark.parametrize( - ("op", "col", "literal", "arrow_supported"), - [ - (operator.add, "timedelta_col_1", pd.Timedelta(2, "s").as_unit("us"), True), - (operator.sub, "timedelta_col_1", pd.Timedelta(2, "s").as_unit("us"), True), - (operator.truediv, "timedelta_col_1", pd.Timedelta(2, "s").as_unit("us"), True), - ( - operator.floordiv, - "timedelta_col_1", - pd.Timedelta(2, "s").as_unit("us"), - False, - ), - (operator.truediv, "timedelta_col_1", 3, True), - (operator.floordiv, "timedelta_col_1", 3, False), - (operator.mul, "timedelta_col_1", 3, True), - (operator.mul, "float_col", pd.Timedelta(1, "s").as_unit("us"), True), - (operator.mod, "timedelta_col_1", pd.Timedelta(7, "s").as_unit("us"), False), - ], -) -def test_timedelta_binary_ops_series_and_literal( - temporal_dfs, op, col, literal, arrow_supported -): - bf_df, pd_df = temporal_dfs - - actual_result = op(bf_df[col], literal).to_pandas() - - if not arrow_supported: - expected_result = pd_df[col].map(lambda x: op(x, literal)) - else: - expected_result = op(pd_df[col], literal) - _assert_series_equal(actual_result, expected_result) - - -@pytest.mark.parametrize( - ("op", "col", "literal", "arrow_supported"), - [ - (operator.add, "timedelta_col_1", pd.Timedelta(2, "s").as_unit("us"), True), - (operator.sub, "timedelta_col_1", pd.Timedelta(2, "s").as_unit("us"), True), - (operator.truediv, "timedelta_col_1", pd.Timedelta(2, "s").as_unit("us"), True), - ( - operator.floordiv, - "timedelta_col_1", - pd.Timedelta(2, "s").as_unit("us"), - True, - ), - (operator.truediv, "float_col", pd.Timedelta(2, "s").as_unit("us"), True), - (operator.floordiv, "float_col", pd.Timedelta(2, "s").as_unit("us"), True), - (operator.mul, "timedelta_col_1", 3, True), - (operator.mul, "float_col", pd.Timedelta(1, "s").as_unit("us"), False), - (operator.mod, "timedelta_col_1", pd.Timedelta(7, "s").as_unit("us"), False), - ], -) -def test_timedelta_binary_ops_literal_and_series( - temporal_dfs, op, col, literal, arrow_supported -): - bf_df, pd_df = temporal_dfs - - actual_result = op(literal, bf_df[col]).to_pandas() - - if not arrow_supported: - expected_result = pd_df[col].map(lambda x: op(literal, x)) - else: - expected_result = op(literal, pd_df[col]) - _assert_series_equal(actual_result, expected_result) - - -@pytest.mark.parametrize("op", [operator.pos, operator.neg, operator.abs]) -def test_timedelta_unary_ops(temporal_dfs, op): - bf_df, pd_df = temporal_dfs - - actual_result = op(bf_df["timedelta_col_1"]).to_pandas() - - expected_result = op(pd_df["timedelta_col_1"]) - _assert_series_equal(actual_result, expected_result) - - -@pytest.mark.parametrize( - ("column", "pd_dtype"), - [ - ("datetime_col", " 0 - - -@pytest.mark.parametrize( - "literal", - [ - pytest.param(pd.Timedelta(1, unit="s").as_unit("us"), id="pandas"), - pytest.param(datetime.timedelta(seconds=1), id="python-datetime"), - pytest.param(np.timedelta64(1, "s"), id="numpy"), - ], -) -def test_timestamp_add__ts_series_plus_td_literal(temporal_dfs, literal): - bf_df, pd_df = temporal_dfs - - actual_result = (bf_df["timestamp_col"] + literal).to_pandas() - - expected_result = pd_df["timestamp_col"] + literal - bigframes.testing.utils.assert_series_equal( - actual_result, expected_result, check_index_type=False - ) - - -@pytest.mark.parametrize( - ("column", "pd_dtype"), - [ - ("datetime_col", " pd.Timedelta(1, "h")) - ].to_pandas() - - expected_result = pd_series[(pd_series - timestamp) > pd.Timedelta(1, "h")] - bigframes.testing.utils.assert_series_equal( - actual_result, expected_result, check_index_type=False - ) - - -def test_timedelta_ordering(session): - pd_df = pd.DataFrame( - { - "col_1": pd.Series( - [ - pd.Timestamp("2025-01-01 01:00:00"), - pd.Timestamp("2025-01-01 02:00:00"), - pd.Timestamp("2025-01-01 03:00:00"), - ], - dtype=dtypes.TIMESTAMP_DTYPE, - ), - "col_2": pd.Series( - [ - pd.Timestamp("2025-01-01 01:00:02"), - pd.Timestamp("2025-01-01 02:00:01"), - pd.Timestamp("2025-01-01 02:59:59"), - ], - dtype=dtypes.TIMESTAMP_DTYPE, - ), - } - ) - bf_df = session.read_pandas(pd_df) - - actual_result = (bf_df["col_2"] - bf_df["col_1"]).sort_values().to_pandas() - - expected_result = (pd_df["col_2"] - pd_df["col_1"]).sort_values() - bigframes.testing.utils.assert_series_equal( - actual_result, expected_result, check_index_type=False - ) - - -def test_timedelta_cumsum(temporal_dfs): - bf_df, pd_df = temporal_dfs - - actual_result = bf_df["timedelta_col_1"].cumsum().to_pandas() - - expected_result = pd_df["timedelta_col_1"].cumsum() - _assert_series_equal(actual_result, expected_result) - - -@pytest.mark.parametrize( - "agg_func", - [ - pytest.param(lambda x: x.min(), id="min"), - pytest.param(lambda x: x.max(), id="max"), - pytest.param(lambda x: x.sum(), id="sum"), - pytest.param(lambda x: x.mean(), id="mean"), - pytest.param(lambda x: x.median(), id="median"), - pytest.param(lambda x: x.quantile(0.5), id="quantile"), - pytest.param(lambda x: x.std(), id="std"), - ], -) -def test_timedelta_agg__timedelta_result(temporal_dfs, agg_func): - bf_df, pd_df = temporal_dfs - - actual_result = agg_func(bf_df["timedelta_col_1"]) - - expected_result = agg_func(pd_df["timedelta_col_1"]) - assert actual_result == expected_result - - -@pytest.mark.parametrize( - "agg_func", - [ - pytest.param(lambda x: x.count(), id="count"), - pytest.param(lambda x: x.nunique(), id="nunique"), - ], -) -def test_timedelta_agg__int_result(temporal_dfs, agg_func): - bf_df, pd_df = temporal_dfs - - actual_result = agg_func(bf_df["timedelta_col_1"]) - - expected_result = agg_func(pd_df["timedelta_col_1"]) - assert actual_result == expected_result - - -def test_timestamp_diff_after_type_casting(temporal_dfs): - if version.Version(pd.__version__) <= version.Version("2.1.0"): - pytest.skip( - "Temporal type casting is not well-supported in older verions of Pandas." - ) - - bf_df, pd_df = temporal_dfs - dtype = pd.ArrowDtype(pa.timestamp("us", tz="UTC")) - - actual_result = ( - bf_df["timestamp_col"] - bf_df["positive_int_col"].astype(dtype) - ).to_pandas() - - expected_result = pd_df["timestamp_col"] - pd_df["positive_int_col"].astype( - "datetime64[us, UTC]" - ) - bigframes.testing.utils.assert_series_equal( - actual_result, expected_result, check_index_type=False, check_dtype=False - ) diff --git a/tests/system/small/pandas/test_describe.py b/tests/system/small/pandas/test_describe.py deleted file mode 100644 index beb7a1968fc..00000000000 --- a/tests/system/small/pandas/test_describe.py +++ /dev/null @@ -1,415 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import pandas.testing -import pytest - -import bigframes.pandas as bpd - - -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", - "duration_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, - ) - - -def test_df_groupby_describe(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 - - bf_full_result = ( - scalars_df.groupby("bool_col")[supported_columns] - .describe(include="all") - .to_pandas() - ) - - pd_full_result = scalars_pandas_df.groupby("bool_col")[supported_columns].describe( - include="all" - ) - - for col in supported_columns: - pd_result = pd_full_result[col] - bf_result = bf_full_result[col] - - if col in numeric_columns: - # Drop quartiles, as they are approximate - bf_min = bf_result["min"] - bf_p25 = bf_result["25%"] - bf_p50 = bf_result["50%"] - bf_p75 = bf_result["75%"] - bf_max = bf_result["max"] - - # Reindex results with the specified keys and their order, because - # the relative order is not important. - bf_result = bf_result.reindex( - columns=["count", "mean", "std", "min", "max"] - ) - pd_result = pd_result.reindex( - columns=["count", "mean", "std", "min", "max"] - ) - - # 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() - else: - # Reindex results with the specified keys and their order, because - # the relative order is not important. - bf_result = bf_result.reindex(columns=["count", "nunique"]) - pd_result = pd_result.reindex(columns=["count", "unique"]) - pandas.testing.assert_frame_equal( - # BF counter part of "unique" is called "nunique" - pd_result.astype("Float64").rename(columns={"unique": "nunique"}), - bf_result, - check_dtype=False, - check_index_type=False, - ) - - -def test_series_groupby_describe(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 - - bf_df = scalars_df.groupby("bool_col") - - pd_df = scalars_pandas_df.groupby("bool_col") - - for col in supported_columns: - pd_result = pd_df[col].describe(include="all") - bf_result = bf_df[col].describe(include="all").to_pandas() - - if col in numeric_columns: - # Drop quartiles, as they are approximate - bf_min = bf_result["min"] - bf_p25 = bf_result["25%"] - bf_p50 = bf_result["50%"] - bf_p75 = bf_result["75%"] - bf_max = bf_result["max"] - - # Reindex results with the specified keys and their order, because - # the relative order is not important. - bf_result = bf_result.reindex( - columns=["count", "mean", "std", "min", "max"] - ) - pd_result = pd_result.reindex( - columns=["count", "mean", "std", "min", "max"] - ) - - # 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() - else: - # Reindex results with the specified keys and their order, because - # the relative order is not important. - bf_result = bf_result.reindex(columns=["count", "nunique"]) - pd_result = pd_result.reindex(columns=["count", "unique"]) - pandas.testing.assert_frame_equal( - # BF counter part of "unique" is called "nunique" - pd_result.astype("Float64").rename(columns={"unique": "nunique"}), - bf_result, - check_dtype=False, - check_index_type=False, - ) - - -def test_describe_json_and_obj_ref_returns_count(session): - # Test describe() works on JSON and OBJ_REF types (without nunique, which fails) - import uuid - - import google.cloud.bigquery - - sql = """ - SELECT - PARSE_JSON('{"a": 1}') AS json_col, - 'gs://cloud-samples-data/vision/ocr/sign.jpg' AS uri_col - """ - df_init = session.read_gbq(sql) - - table_id = f"bigframes-dev.bigframes_tests_sys.tmp_obj_ref_{uuid.uuid4().hex}" - df_init.to_gbq(table_id, if_exists="replace") - - client = session.bqclient - table = client.get_table(table_id) - schema = list(table.schema) - for i, field in enumerate(schema): - if field.name == "uri_col": - schema[i] = google.cloud.bigquery.SchemaField( - name=field.name, - field_type=field.field_type, - mode=field.mode, - description="bigframes_dtype: OBJ_REF_DTYPE", - ) - break - table.schema = schema - client.update_table(table, ["schema"]) - - df = session.read_gbq(table_id) - df = df.rename(columns={"uri_col": "obj_ref_col"}) - - res = df.describe(include="all").to_pandas() - - assert "count" in res.index - assert res.loc["count", "json_col"] == 1.0 - assert res.loc["count", "obj_ref_col"] == 1.0 - - -def test_describe_with_unsupported_type_returns_empty_dataframe(session): - df = session.read_gbq("SELECT ST_GEOGPOINT(1.0, 2.0) AS geo_col") - - res = df.describe().to_pandas() - - assert len(res.columns) == 0 - assert len(res.index) == 1 - - -def test_describe_empty_dataframe_returns_empty_dataframe(session): - df = bpd.DataFrame() - - res = df.describe().to_pandas() - - assert len(res.columns) == 0 - assert len(res.index) == 1 diff --git a/tests/system/small/pandas/test_read_gbq_colab.py b/tests/system/small/pandas/test_read_gbq_colab.py deleted file mode 100644 index 6e848ed9eaa..00000000000 --- a/tests/system/small/pandas/test_read_gbq_colab.py +++ /dev/null @@ -1,329 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from __future__ import annotations - -import datetime -import decimal - -import db_dtypes # type: ignore -import geopandas # type: ignore -import numpy -import pandas -import pyarrow -import pytest -import shapely.geometry # type: ignore - -from bigframes.pandas.io import api as module_under_test - - -@pytest.mark.parametrize( - ("df_pd",), - ( - # Regression tests for b/428190014. - # - # Test every BigQuery type we support, especially those where the legacy - # SQL type name differs from the GoogleSQL type name. - # - # See: - # https://cloud.google.com/bigquery/docs/reference/standard-sql/data-types - # and compare to the legacy types at - # https://cloud.google.com/bigquery/docs/data-types - pytest.param( - pandas.DataFrame( - { - "ints": pandas.Series( - [[1], [2], [3]], - dtype=pandas.ArrowDtype(pyarrow.list_(pyarrow.int64())), - ), - "floats": pandas.Series( - [[1.0], [2.0], [3.0]], - dtype=pandas.ArrowDtype(pyarrow.list_(pyarrow.float64())), - ), - } - ), - id="arrays", - ), - pytest.param( - pandas.DataFrame( - { - "bool": pandas.Series([True, False, True], dtype="bool"), - "boolean": pandas.Series([True, None, True], dtype="boolean"), - "object": pandas.Series([True, None, True], dtype="object"), - "arrow": pandas.Series( - [True, None, True], dtype=pandas.ArrowDtype(pyarrow.bool_()) - ), - } - ), - id="bools", - ), - pytest.param( - pandas.DataFrame( - { - "bytes": pandas.Series([b"a", b"b", b"c"], dtype=numpy.bytes_), - "object": pandas.Series([b"a", None, b"c"], dtype="object"), - "arrow": pandas.Series( - [b"a", None, b"c"], dtype=pandas.ArrowDtype(pyarrow.binary()) - ), - } - ), - id="bytes", - ), - pytest.param( - pandas.DataFrame( - { - "object": pandas.Series( - [ - datetime.date(2023, 11, 23), - None, - datetime.date(1970, 1, 1), - ], - dtype="object", - ), - "arrow": pandas.Series( - [ - datetime.date(2023, 11, 23), - None, - datetime.date(1970, 1, 1), - ], - dtype=pandas.ArrowDtype(pyarrow.date32()), - ), - } - ), - id="dates", - ), - pytest.param( - pandas.DataFrame( - { - "object": pandas.Series( - [ - datetime.datetime(2023, 11, 23, 13, 14, 15), - None, - datetime.datetime(1970, 1, 1, 0, 0, 0), - ], - dtype="object", - ), - "datetime64": pandas.Series( - [ - datetime.datetime(2023, 11, 23, 13, 14, 15), - None, - datetime.datetime(1970, 1, 1, 0, 0, 0), - ], - dtype="datetime64[us]", - ), - "arrow": pandas.Series( - [ - datetime.datetime(2023, 11, 23, 13, 14, 15), - None, - datetime.datetime(1970, 1, 1, 0, 0, 0), - ], - dtype=pandas.ArrowDtype(pyarrow.timestamp("us")), - ), - } - ), - id="datetimes", - ), - pytest.param( - pandas.DataFrame( - { - "object": pandas.Series( - [ - shapely.geometry.Point(145.0, -37.8), - None, - shapely.geometry.Point(-122.3, 47.6), - ], - dtype="object", - ), - "geopandas": geopandas.GeoSeries( - [ - shapely.geometry.Point(145.0, -37.8), - None, - shapely.geometry.Point(-122.3, 47.6), - ] - ), - } - ), - id="geographys", - ), - # TODO(tswast): Add INTERVAL once BigFrames supports it. - pytest.param( - pandas.DataFrame( - { - # TODO(tswast): Is there an equivalent object type we can use here? - # TODO(tswast): Add built-in Arrow extension type - "db_dtypes": pandas.Series( - ["{}", None, "123"], - dtype=pandas.ArrowDtype(db_dtypes.JSONArrowType()), - ), - } - ), - id="jsons", - ), - pytest.param( - pandas.DataFrame( - { - "int64": pandas.Series([1, 2, 3], dtype="int64"), - "Int64": pandas.Series([1, None, 3], dtype="Int64"), - "object": pandas.Series([1, None, 3], dtype="object"), - "arrow": pandas.Series( - [1, None, 3], dtype=pandas.ArrowDtype(pyarrow.int64()) - ), - } - ), - id="ints", - ), - pytest.param( - pandas.DataFrame( - { - "object": pandas.Series( - [decimal.Decimal("1.23"), None, decimal.Decimal("4.56")], - dtype="object", - ), - "arrow": pandas.Series( - [decimal.Decimal("1.23"), None, decimal.Decimal("4.56")], - dtype=pandas.ArrowDtype(pyarrow.decimal128(38, 9)), - ), - } - ), - id="numerics", - ), - pytest.param( - pandas.DataFrame( - { - # TODO(tswast): Add object type for BIGNUMERIC. Can bigframes disambiguate? - "arrow": pandas.Series( - [decimal.Decimal("1.23"), None, decimal.Decimal("4.56")], - dtype=pandas.ArrowDtype(pyarrow.decimal256(76, 38)), - ), - } - ), - id="bignumerics", - ), - pytest.param( - pandas.DataFrame( - { - "float64": pandas.Series([1.23, None, 4.56], dtype="float64"), - "Float64": pandas.Series([1.23, None, 4.56], dtype="Float64"), - "object": pandas.Series([1.23, None, 4.56], dtype="object"), - "arrow": pandas.Series( - [1.23, None, 4.56], dtype=pandas.ArrowDtype(pyarrow.float64()) - ), - } - ), - id="floats", - ), - # TODO(tswast): Add RANGE once BigFrames supports it. - pytest.param( - pandas.DataFrame( - { - "string": pandas.Series(["a", "b", "c"], dtype="string[python]"), - "object": pandas.Series(["a", None, "c"], dtype="object"), - "arrow": pandas.Series(["a", None, "c"], dtype="string[pyarrow]"), - } - ), - id="strings", - ), - pytest.param( - pandas.DataFrame( - { - # TODO(tswast): Add object type for STRUCT? How to tell apart from JSON? - "arrow": pandas.Series( - [{"a": 1, "b": 1.0, "c": "c"}], - dtype=pandas.ArrowDtype( - pyarrow.struct( - [ - ("a", pyarrow.int64()), - ("b", pyarrow.float64()), - ("c", pyarrow.string()), - ] - ) - ), - ), - } - ), - id="structs", - ), - pytest.param( - pandas.DataFrame( - { - "object": pandas.Series( - [ - datetime.time(0, 0, 0), - None, - datetime.time(13, 7, 11), - ], - dtype="object", - ), - "arrow": pandas.Series( - [ - datetime.time(0, 0, 0), - None, - datetime.time(13, 7, 11), - ], - dtype=pandas.ArrowDtype(pyarrow.time64("us")), - ), - } - ), - id="times", - ), - pytest.param( - pandas.DataFrame( - { - "object": pandas.Series( - [ - datetime.datetime( - 2023, 11, 23, 13, 14, 15, tzinfo=datetime.timezone.utc - ), - None, - datetime.datetime( - 1970, 1, 1, 0, 0, 0, tzinfo=datetime.timezone.utc - ), - ], - dtype="object", - ), - "datetime64": pandas.Series( - [ - datetime.datetime(2023, 11, 23, 13, 14, 15), - None, - datetime.datetime(1970, 1, 1, 0, 0, 0), - ], - dtype="datetime64[us]", - ).dt.tz_localize("UTC"), - "arrow": pandas.Series( - [ - datetime.datetime( - 2023, 11, 23, 13, 14, 15, tzinfo=datetime.timezone.utc - ), - None, - datetime.datetime( - 1970, 1, 1, 0, 0, 0, tzinfo=datetime.timezone.utc - ), - ], - dtype=pandas.ArrowDtype(pyarrow.timestamp("us", "UTC")), - ), - } - ), - id="timestamps", - ), - ), -) -def test_read_gbq_colab_sessionless_dry_run_generates_valid_sql_for_local_dataframe( - df_pd: pandas.DataFrame, -): - # This method will fail with an exception if it receives invalid SQL. - result = module_under_test._run_read_gbq_colab_sessionless_dry_run( - query="SELECT * FROM {df_pd}", - pyformat_args={"df_pd": df_pd}, - ) - assert isinstance(result, pandas.Series) diff --git a/tests/system/small/pandas/test_read_gbq_information_schema.py b/tests/system/small/pandas/test_read_gbq_information_schema.py deleted file mode 100644 index 32e2dc4712e..00000000000 --- a/tests/system/small/pandas/test_read_gbq_information_schema.py +++ /dev/null @@ -1,50 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import pytest - - -@pytest.mark.parametrize("include_project", [True, False]) -@pytest.mark.parametrize( - "view_id", - [ - # https://cloud.google.com/bigquery/docs/information-schema-intro - "region-US.INFORMATION_SCHEMA.SESSIONS_BY_USER", - "region-US.INFORMATION_SCHEMA.SCHEMATA", - ], -) -def test_read_gbq_jobs_by_user_returns_schema( - unordered_session, view_id: str, include_project: bool -): - if include_project: - table_id = unordered_session.bqclient.project + "." + view_id - else: - table_id = view_id - - df = unordered_session.read_gbq(table_id, max_results=10) - assert df.dtypes is not None - - -def test_read_gbq_schemata_can_be_peeked(unordered_session): - df = unordered_session.read_gbq("region-US.INFORMATION_SCHEMA.SCHEMATA") - result = df.peek() - assert result is not None - - -def test_read_gbq_schemata_four_parts_can_be_peeked(unordered_session): - df = unordered_session.read_gbq( - f"{unordered_session.bqclient.project}.region-US.INFORMATION_SCHEMA.SCHEMATA" - ) - result = df.peek() - assert result is not None diff --git a/tests/system/small/regression/test_issue355_merge_after_filter.py b/tests/system/small/regression/test_issue355_merge_after_filter.py deleted file mode 100644 index d3486810f7c..00000000000 --- a/tests/system/small/regression/test_issue355_merge_after_filter.py +++ /dev/null @@ -1,70 +0,0 @@ -# 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. -# 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 as pd -import pytest - -from bigframes.testing.utils import assert_frame_equal - - -@pytest.mark.parametrize( - ("merge_how",), - [ - ("inner",), - ("outer",), - ("left",), - ("right",), - ], -) -def test_merge_after_filter(baseball_schedules_df, merge_how): - on = ["awayTeamName"] - left_columns = [ - "gameId", - "year", - "homeTeamName", - "awayTeamName", - "duration_minutes", - ] - right_columns = [ - "gameId", - "year", - "homeTeamName", - "awayTeamName", - "duration_minutes", - ] - - left = baseball_schedules_df[left_columns] - left = left[left["homeTeamName"] == "Rays"] - # Offset the rows somewhat so that outer join can have an effect. - right = baseball_schedules_df[right_columns] - right = right[right["homeTeamName"] == "White Sox"] - - df = left.merge(right, on=on, how=merge_how) - bf_result = df.to_pandas() - - left_pandas = baseball_schedules_df.to_pandas()[left_columns] - left_pandas = left_pandas[left_pandas["homeTeamName"] == "Rays"] - - right_pandas = baseball_schedules_df.to_pandas()[right_columns] - right_pandas = right_pandas[right_pandas["homeTeamName"] == "White Sox"] - - pd_result = pd.merge( - left_pandas, - right_pandas, - merge_how, - on, - sort=True, - ) - - assert_frame_equal(bf_result, pd_result, ignore_order=True) diff --git a/tests/system/small/session/__init__.py b/tests/system/small/session/__init__.py deleted file mode 100644 index 0a2669d7a25..00000000000 --- a/tests/system/small/session/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. diff --git a/tests/system/small/session/test_read_gbq_colab.py b/tests/system/small/session/test_read_gbq_colab.py deleted file mode 100644 index 6ba9c760847..00000000000 --- a/tests/system/small/session/test_read_gbq_colab.py +++ /dev/null @@ -1,373 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -"""System tests for read_gbq_colab helper functions.""" - -import numpy -import pandas -import pandas.testing -import pytest - -import bigframes -import bigframes.pandas - -pytest.importorskip("polars") - - -def test_read_gbq_colab_to_pandas_batches_preserves_order_by(maybe_ordered_session): - # This query should return enough results to be too big to fit in a single - # page from jobs.query. - executions_before_sql = maybe_ordered_session._metrics.execution_count - df = maybe_ordered_session._read_gbq_colab( - """ - SELECT - name, - state, - gender, - year, - SUM(number) AS total - FROM - `bigquery-public-data.usa_names.usa_1910_2013` - WHERE state LIKE 'W%' - GROUP BY name, state, gender, year - ORDER BY total DESC - """ - ) - executions_before_python = maybe_ordered_session._metrics.execution_count - batches = df.to_pandas_batches( - page_size=100, - ) - assert batches.total_rows > 0 - assert batches.total_bytes_processed is None # No additional query. - - executions_after = maybe_ordered_session._metrics.execution_count - - num_batches = 0 - for batch in batches: - assert batch["total"].is_monotonic_decreasing - assert len(batch.index) == 100 - num_batches += 1 - - # Only test the first few pages to avoid downloading unnecessary data - # and so we can confirm we have full pages in each batch. - if num_batches >= 3: - break - - assert executions_after == executions_before_python == executions_before_sql + 1 - - -def test_read_gbq_colab_fresh_session_is_hybrid(): - bigframes.close_session() - df = bigframes.pandas._read_gbq_colab( - """ - SELECT - name, - SUM(number) AS total - FROM - `bigquery-public-data.usa_names.usa_1910_2013` - WHERE state LIKE 'W%' - GROUP BY name - ORDER BY total DESC - LIMIT 300 - """ - ) - session = df._session - executions_before_python = session._metrics.execution_count - result = df.sort_values("name").peek(100) - executions_after = session._metrics.execution_count - - assert len(result) == 100 - assert session._executor._enable_polars_execution is True # type: ignore - assert executions_before_python == 1 - assert executions_after == 2 - history = session.execution_history().to_dataframe() - assert history.iloc[-1]["job_type"] == "polars" - - -def test_read_gbq_colab_peek_avoids_requery(maybe_ordered_session): - history_before = maybe_ordered_session.execution_history().to_dataframe() - queries_before = ( - len(history_before[history_before["job_type"] == "query"]) - if "job_type" in history_before.columns - else 0 - ) - - df = maybe_ordered_session._read_gbq_colab( - """ - SELECT - name, - SUM(number) AS total - FROM - `bigquery-public-data.usa_names.usa_1910_2013` - WHERE state LIKE 'W%' - GROUP BY name - ORDER BY total DESC - LIMIT 300 - """ - ) - - history_after_read = maybe_ordered_session.execution_history().to_dataframe() - queries_after_read = len( - history_after_read[history_after_read["job_type"] == "query"] - ) - - result = df.peek(100) - - history_after_peek = maybe_ordered_session.execution_history().to_dataframe() - queries_after_peek = len( - history_after_peek[history_after_peek["job_type"] == "query"] - ) - - # Ok, this isn't guaranteed by peek, but should happen with read api based impl - # if starts failing, maybe stopped using read api? - assert result["total"].is_monotonic_decreasing - - assert len(result) == 100 - assert queries_after_read == queries_before + 1 - assert queries_after_peek == queries_after_read - - -def test_read_gbq_colab_repr_avoids_requery(maybe_ordered_session): - history_before = maybe_ordered_session.execution_history().to_dataframe() - queries_before = ( - len(history_before[history_before["job_type"] == "query"]) - if "job_type" in history_before.columns - else 0 - ) - - df = maybe_ordered_session._read_gbq_colab( - """ - SELECT - name, - SUM(number) AS total - FROM - `bigquery-public-data.usa_names.usa_1910_2013` - WHERE state LIKE 'W%' - GROUP BY name - ORDER BY total DESC - LIMIT 300 - """ - ) - - history_after_read = maybe_ordered_session.execution_history().to_dataframe() - queries_after_read = len( - history_after_read[history_after_read["job_type"] == "query"] - ) - - _ = repr(df) - - history_after_repr = maybe_ordered_session.execution_history().to_dataframe() - queries_after_repr = len( - history_after_repr[history_after_repr["job_type"] == "query"] - ) - - assert queries_after_read == queries_before + 1 - assert queries_after_repr == queries_after_read - - -def test_read_gbq_colab_includes_formatted_scalars(session): - pyformat_args = { - "some_integer": 123, - "some_string": "This could be dangerous.", - # This is not a supported type, but ignored if not referenced. - "some_object": object(), - } - - # This query should return few enough results to be small enough to fit in a - # single page from jobs.query. - df = session._read_gbq_colab( - """ - SELECT {some_integer} as some_integer, - '{some_string}' as some_string, - '{{escaped}}' as escaped - """, - pyformat_args=pyformat_args, - ) - result = df.to_pandas() - pandas.testing.assert_frame_equal( - result, - pandas.DataFrame( - { - "some_integer": pandas.Series([123], dtype=pandas.Int64Dtype()), - "some_string": pandas.Series( - ["This could be dangerous."], - dtype="string[pyarrow]", - ), - "escaped": pandas.Series(["{escaped}"], dtype="string[pyarrow]"), - } - ), - check_index_type=False, # int64 vs Int64 - ) - - -@pytest.mark.skipif( - pandas.__version__.startswith("1."), reason="bad left join in pandas 1.x" -) -def test_read_gbq_colab_includes_formatted_dataframes( - session, scalars_df_index, scalars_pandas_df_index -): - pd_df = pandas.DataFrame( - { - "rowindex": [0, 1, 2, 3, 4, 5], - "value": [0, 100, 200, 300, 400, 500], - } - ) - - # Make sure we test with some data that is too large to inline as SQL. - pd_df_large = pandas.DataFrame( - { - "rowindex": numpy.arange(100_000), - "large_value": numpy.arange(100_000), - } - ) - - pyformat_args = { - # Apply some operations to make sure the columns aren't renamed. - "bf_df": scalars_df_index[scalars_df_index["int64_col"] > 0].assign( - int64_col=scalars_df_index["int64_too"] - ), - "pd_df": pd_df, - "pd_df_large": pd_df_large, - # This is not a supported type, but ignored if not referenced. - "some_object": object(), - } - sql = """ - SELECT bf_df.int64_col + pd_df.value + pd_df_large.large_value AS int64_col, - COALESCE(bf_df.rowindex, pd_df.rowindex, pd_df_large.rowindex) AS rowindex - FROM {bf_df} AS bf_df - FULL OUTER JOIN {pd_df} AS pd_df - ON bf_df.rowindex = pd_df.rowindex - LEFT JOIN {pd_df_large} AS pd_df_large - ON bf_df.rowindex = pd_df_large.rowindex - ORDER BY rowindex ASC - """ - - # Do the dry run first so that we don't re-use the uploaded data from the - # real query. - dry_run_output = session._read_gbq_colab( - sql, - pyformat_args=pyformat_args, - dry_run=True, - ) - - df = session._read_gbq_colab( - sql, - pyformat_args=pyformat_args, - ) - - # Confirm that dry_run was accurate. - pandas.testing.assert_series_equal( - pandas.Series(dry_run_output["columnDtypes"]), - df.dtypes, - ) - - result = df.to_pandas() - expected = ( - scalars_pandas_df_index[scalars_pandas_df_index["int64_col"] > 0] - .assign(int64_col=scalars_pandas_df_index["int64_too"]) - .reset_index(drop=False)[["int64_col", "rowindex"]] - .merge( - pd_df, - on="rowindex", - how="outer", - ) - .merge( - pd_df_large, - on="rowindex", - how="left", - ) - .assign( - int64_col=lambda df: ( - df["int64_col"] + df["value"] + df["large_value"] - ).astype("Int64") - ) - .drop(columns=["value", "large_value"]) - .sort_values(by="rowindex") - .reset_index(drop=True) - ) - pandas.testing.assert_frame_equal( - result, - expected, - check_index_type=False, # int64 vs Int64 - ) - - -@pytest.mark.parametrize( - ("pd_df",), - ( - pytest.param( - pandas.DataFrame( - { - "rowindex": [0, 1, 2, 3, 4, 5], - "value": [0, 100, 200, 300, 400, 500], - "value2": [-1, -2, -3, -4, -5, -6], - } - ), - id="inline-df", - ), - pytest.param( - pandas.DataFrame( - { - # Make sure we test with some data that is too large to - # inline as SQL. - "rowindex": numpy.arange(100_000), - "value": numpy.arange(100_000), - "value2": numpy.arange(100_000), - } - ), - id="large-df", - ), - ), -) -def test_read_gbq_colab_with_formatted_dataframe_deduplicates_column_names_just_like_to_gbq( - session, - pd_df, -): - # Create duplicate column names. - pd_df.columns = ["rowindex", "value", "value"] - - pyformat_args = { - "pd_df": pd_df, - } - sql = """ - SELECT rowindex, value, value_1 - FROM {pd_df} - """ - - # Do the dry run first so that we don't re-use the uploaded data from the - # real query. - dry_run_output = session._read_gbq_colab( - sql, - pyformat_args=pyformat_args, - dry_run=True, - ) - - df = session._read_gbq_colab( - sql, - pyformat_args=pyformat_args, - ) - - # Confirm that dry_run was accurate. - pandas.testing.assert_series_equal( - pandas.Series(dry_run_output["columnDtypes"]), - df.dtypes, - ) - - # Make sure the query doesn't fail. - df.to_pandas_batches() - - # Make sure the - table_id = session.read_pandas(pd_df).to_gbq() - table = session.bqclient.get_table(table_id) - assert [field.name for field in table.schema] == ["rowindex", "value", "value_1"] diff --git a/tests/system/small/session/test_read_gbq_query.py b/tests/system/small/session/test_read_gbq_query.py deleted file mode 100644 index bb9026dc705..00000000000 --- a/tests/system/small/session/test_read_gbq_query.py +++ /dev/null @@ -1,113 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import datetime - -import pytest - -import bigframes -import bigframes.core.nodes as nodes - - -def test_read_gbq_query_w_allow_large_results(session: bigframes.Session): - if not hasattr(session.bqclient, "default_job_creation_mode"): - pytest.skip("Jobless query only available on newer google-cloud-bigquery.") - - query = "SELECT 1" - - # Make sure we don't get a cached table. - configuration = {"query": {"useQueryCache": False}} - - # Very small results should wrap a local node. - df_false = session.read_gbq( - query, - configuration=configuration, - allow_large_results=False, - ) - assert df_false.shape == (1, 1) - nodes_false = df_false._get_block().expr.node.unique_nodes() - assert any(isinstance(node, nodes.ReadLocalNode) for node in nodes_false) - assert not any(isinstance(node, nodes.ReadTableNode) for node in nodes_false) - - # Large results allowed should wrap a table. - df_true = session.read_gbq( - query, - configuration=configuration, - allow_large_results=True, - ) - assert df_true.shape == (1, 1) - nodes_true = df_true._get_block().expr.node.unique_nodes() - assert any(isinstance(node, nodes.ReadTableNode) for node in nodes_true) - - -def test_read_gbq_query_w_columns(session: bigframes.Session): - query = """ - SELECT 1 as int_col, - 'a' as str_col, - TIMESTAMP('2025-08-21 10:41:32.123456') as timestamp_col - """ - - result = session.read_gbq( - query, - columns=["timestamp_col", "int_col"], - ) - assert list(result.columns) == ["timestamp_col", "int_col"] - assert result.to_dict(orient="records") == [ - { - "timestamp_col": datetime.datetime( - 2025, 8, 21, 10, 41, 32, 123456, tzinfo=datetime.timezone.utc - ), - "int_col": 1, - } - ] - - -@pytest.mark.parametrize( - ("index_col", "expected_index_names"), - ( - pytest.param( - "my_custom_index", - ("my_custom_index",), - id="string", - ), - pytest.param( - ("my_custom_index",), - ("my_custom_index",), - id="iterable", - ), - pytest.param( - ("my_custom_index", "int_col"), - ("my_custom_index", "int_col"), - id="multiindex", - ), - ), -) -def test_read_gbq_query_w_index_col( - session: bigframes.Session, index_col, expected_index_names -): - query = """ - SELECT 1 as int_col, - 'a' as str_col, - 0 as my_custom_index, - TIMESTAMP('2025-08-21 10:41:32.123456') as timestamp_col - """ - - result = session.read_gbq( - query, - index_col=index_col, - ) - assert tuple(result.index.names) == expected_index_names - assert frozenset(result.columns) == frozenset( - {"int_col", "str_col", "my_custom_index", "timestamp_col"} - ) - frozenset(expected_index_names) diff --git a/tests/system/small/session/test_session_logging.py b/tests/system/small/session/test_session_logging.py deleted file mode 100644 index 4618e110687..00000000000 --- a/tests/system/small/session/test_session_logging.py +++ /dev/null @@ -1,40 +0,0 @@ -# Copyright 2026 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 unittest import mock - -import bigframes.session._io.bigquery as bq_io -from bigframes.core.logging import data_types - - -def test_data_type_logging(scalars_df_index): - s = scalars_df_index["int64_col"] + 1.5 - - # We want to check the job_config passed to _query_and_wait_bigframes - with mock.patch( - "bigframes.session._io.bigquery.start_query_job_optional", - wraps=bq_io.start_query_job_optional, - ) as mock_query: - s.to_pandas() - - # Fetch job labels sent to the BQ client and verify their values - assert mock_query.called - call_args = mock_query.call_args - job_config = call_args.kwargs.get("job_config") - assert job_config is not None - job_labels = job_config.labels - assert "bigframes-dtypes" in job_labels - assert job_labels["bigframes-dtypes"] == data_types.encode_type_refs( - s._block._expr.node - ) diff --git a/tests/system/small/test_anywidget.py b/tests/system/small/test_anywidget.py deleted file mode 100644 index 70106f490b6..00000000000 --- a/tests/system/small/test_anywidget.py +++ /dev/null @@ -1,1158 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -"""System tests for the anywidget-based table widget.""" - -from typing import Any -from unittest import mock - -import pandas as pd -import pytest - -import bigframes as bf -import bigframes.core.blocks -import bigframes.dataframe -import bigframes.display - -pytest.importorskip("anywidget") - -# Test constants to avoid change detector tests -EXPECTED_ROW_COUNT = 6 -EXPECTED_PAGE_SIZE = 2 -EXPECTED_TOTAL_PAGES = 3 - - -@pytest.fixture(scope="module") -def paginated_pandas_df() -> pd.DataFrame: - """Create a minimal test DataFrame with exactly 3 pages of 2 rows each.""" - test_data = pd.DataFrame( - { - "id": [5, 4, 3, 2, 1, 0], - "page_indicator": [ - "page_3_row_2", - "page_3_row_1", - "page_2_row_2", - "page_2_row_1", - "page_1_row_2", - "page_1_row_1", - ], - "value": [5, 4, 3, 2, 1, 0], - } - ) - return test_data - - -@pytest.fixture(scope="module") -def paginated_bf_df( - session: bf.Session, paginated_pandas_df: pd.DataFrame -) -> bigframes.dataframe.DataFrame: - return session.read_pandas(paginated_pandas_df) - - -@pytest.fixture -def table_widget(paginated_bf_df: bigframes.dataframe.DataFrame): - """ - Helper fixture to create a TableWidget instance with a fixed page size. - This reduces duplication across tests that use the same widget configuration. - """ - - from bigframes.display import TableWidget - - with bigframes.option_context( - "display.render_mode", "anywidget", "display.max_rows", 2 - ): - # Delay context manager cleanup of `max_rows` until after tests finish. - yield TableWidget(paginated_bf_df) - - -@pytest.fixture(scope="module") -def small_pandas_df() -> pd.DataFrame: - """Create a DataFrame smaller than the page size for edge case testing.""" - return pd.DataFrame( - { - "id": [0, 1], - "page_indicator": ["small_row_1", "small_row_2"], - "value": [0, 1], - } - ) - - -@pytest.fixture(scope="module") -def small_bf_df( - session: bf.Session, small_pandas_df: pd.DataFrame -) -> bf.dataframe.DataFrame: - return session.read_pandas(small_pandas_df) - - -@pytest.fixture -def small_widget(small_bf_df): - """Helper fixture for tests using a DataFrame smaller than the page size.""" - from bigframes.display import TableWidget - - with bf.option_context("display.render_mode", "anywidget", "display.max_rows", 5): - yield TableWidget(small_bf_df) - - -@pytest.fixture -def unknown_row_count_widget(session): - """Fixture to create a TableWidget with an unknown row count.""" - from bigframes.core import blocks - from bigframes.display import TableWidget - - # Create a small DataFrame with known content - test_data = pd.DataFrame( - { - "id": [0, 1, 2, 3, 4], - "value": ["row_0", "row_1", "row_2", "row_3", "row_4"], - } - ) - bf_df = session.read_pandas(test_data) - - # Simulate a scenario where total_rows is not available from the iterator - with mock.patch.object(bf_df, "_to_pandas_batches") as mock_batches: - # We need to provide an iterator of DataFrames, not Series - batches_iterator = iter([test_data]) - mock_batches.return_value = blocks.PandasBatches( - batches_iterator, total_rows=None - ) - with bf.option_context( - "display.render_mode", "anywidget", "display.max_rows", 2 - ): - widget = TableWidget(bf_df) - yield widget - - -@pytest.fixture(scope="module") -def empty_pandas_df() -> pd.DataFrame: - """Create an empty DataFrame for edge case testing.""" - return pd.DataFrame(columns=["id", "page_indicator", "value"]) - - -@pytest.fixture(scope="module") -def empty_bf_df( - session: bf.Session, empty_pandas_df: pd.DataFrame -) -> bf.dataframe.DataFrame: - return session.read_pandas(empty_pandas_df) - - -def mock_execute_result_with_params( - self, schema, total_rows_val, arrow_batches_val, *args, **kwargs -): - """ - Mocks an execution result with configurable total_rows and arrow_batches. - """ - from bigframes.session.executor import ( - ExecuteResult, - ExecutionMetadata, - ResultsIterator, - ) - - class MockExecuteResult(ExecuteResult): - @property - def execution_metadata(self) -> ExecutionMetadata: - return ExecutionMetadata() - - @property - def schema(self) -> Any: - return schema - - def batches(self, sample_rate=None) -> ResultsIterator: - return ResultsIterator( - arrow_batches_val, - self.schema, - total_rows_val, - None, - ) - - return MockExecuteResult() - - -def _assert_html_matches_pandas_slice( - table_html: str, - expected_pd_slice: pd.DataFrame, - full_pd_df: pd.DataFrame, -): - """ - Assertion helper to verify that the rendered HTML contains exactly the - rows from the expected pandas DataFrame slice and no others. This is - inspired by the pattern of comparing BigFrames output to pandas output. - """ - # Check that the unique indicator from each expected row is present. - for _, row in expected_pd_slice.iterrows(): - assert row["page_indicator"] in table_html - - # Create a DataFrame of all rows that should NOT be present. - unexpected_pd_df = full_pd_df.drop(expected_pd_slice.index) - - # Check that no unique indicators from unexpected rows are present. - for _, row in unexpected_pd_df.iterrows(): - assert row["page_indicator"] not in table_html - - -def test_widget_initialization_should_calculate_total_row_count( - paginated_bf_df: bf.dataframe.DataFrame, -): - """Test that a TableWidget calculates the total row count on creation.""" - """A TableWidget should correctly calculate the total row count on creation.""" - from bigframes.display import TableWidget - - with bigframes.option_context( - "display.render_mode", "anywidget", "display.max_rows", 2 - ): - widget = TableWidget(paginated_bf_df) - - assert widget.row_count == EXPECTED_ROW_COUNT - - -def test_widget_initialization_should_default_to_page_zero( - table_widget, -): - """ - Given a new TableWidget, when it is initialized, - then its page number should default to 0. - """ - # The `table_widget` fixture already creates the widget. - # Assert its state. - assert table_widget.page == 0 - assert table_widget.page_size == EXPECTED_PAGE_SIZE - - -def test_widget_display_should_show_first_page_on_load( - table_widget, paginated_pandas_df: pd.DataFrame -): - """ - Given a widget, when it is first loaded, then it should display - the first page of data. - """ - expected_slice = paginated_pandas_df.iloc[0:2] - - html = table_widget.table_html - - _assert_html_matches_pandas_slice(html, expected_slice, paginated_pandas_df) - - -@pytest.mark.parametrize( - "page_number, start_row, end_row", - [ - (1, 2, 4), # Second page - (2, 4, 6), # Last page - ], - ids=["second_page", "last_page"], -) -def test_widget_navigation_should_display_correct_page( - table_widget, - paginated_pandas_df: pd.DataFrame, - page_number: int, - start_row: int, - end_row: int, -): - """ - Given a widget, when the page is set, then it should display the correct - slice of data. - """ - expected_slice = paginated_pandas_df.iloc[start_row:end_row] - - table_widget.page = page_number - html = table_widget.table_html - - assert table_widget.page == page_number - _assert_html_matches_pandas_slice(html, expected_slice, paginated_pandas_df) - - -def test_setting_negative_page_should_raise_error( - table_widget, -): - """ - Given a widget, when a negative page number is set, - then a ValueError should be raised. - """ - with pytest.raises(ValueError, match="Page number cannot be negative."): - table_widget.page = -1 - - -def test_setting_page_beyond_max_should_clamp_to_last_page( - table_widget, paginated_pandas_df: pd.DataFrame -): - """ - Given a widget, - when a page number greater than the max is set, - then the page number should be clamped to the last valid page. - """ - expected_slice = paginated_pandas_df.iloc[4:6] # Last page data - - table_widget.page = 100 # Set page far beyond the total of 3 pages - html = table_widget.table_html - - assert table_widget.page == 2 # Page is clamped to the last valid page (0-indexed) - _assert_html_matches_pandas_slice(html, expected_slice, paginated_pandas_df) - - -@pytest.mark.parametrize( - "page, start_row, end_row", - [ - (0, 0, 3), # Page 0: rows 0-2 - (1, 3, 6), # Page 1: rows 3-5 - ], - ids=[ - "Page 0 (Rows 0-2)", - "Page 1 (Rows 3-5)", - ], -) -def test_widget_pagination_should_work_with_custom_page_size( - paginated_bf_df: bf.dataframe.DataFrame, - paginated_pandas_df: pd.DataFrame, - page: int, - start_row: int, - end_row: int, -): - """Test that a widget paginates correctly with a custom page size.""" - with bigframes.option_context( - "display.render_mode", "anywidget", "display.max_rows", 3 - ): - from bigframes.display import TableWidget - - widget = TableWidget(paginated_bf_df) - assert widget.page_size == 3 - - expected_slice = paginated_pandas_df.iloc[start_row:end_row] - - widget.page = page - html = widget.table_html - - assert widget.page == page - _assert_html_matches_pandas_slice(html, expected_slice, paginated_pandas_df) - - -def test_widget_with_few_rows_should_display_all_rows(small_widget, small_pandas_df): - """ - Given a DataFrame smaller than the page size, the widget should - display all rows on the first page. - """ - html = small_widget.table_html - - _assert_html_matches_pandas_slice(html, small_pandas_df, small_pandas_df) - - -def test_navigation_beyond_last_page_should_be_clamped(small_widget): - """ - Given a DataFrame smaller than the page size, - when navigating beyond the last page, - then the page should be clamped to the last valid page (page 0). - """ - # For a DataFrame with 2 rows and page_size 5 (from small_widget fixture), - # the frontend should calculate 1 total page. - assert small_widget.row_count == 2 - - # The widget should always be on page 0 for a single-page dataset. - assert small_widget.page == 0 - - # Attempting to navigate to page 1 should be clamped back to page 0, - # confirming that only one page is recognized by the backend. - small_widget.page = 1 - assert small_widget.page == 0 - - -def test_global_options_change_should_not_affect_existing_widget_page_size( - paginated_bf_df: bf.dataframe.DataFrame, -): - """ - Given an existing widget, - when global display options are changed, - then the widget's page size should remain unchanged. - """ - with bigframes.option_context( - "display.render_mode", "anywidget", "display.max_rows", 2 - ): - from bigframes.display import TableWidget - - widget = TableWidget(paginated_bf_df) - initial_page_size = widget.page_size - assert initial_page_size == 2 - widget.page = 1 # a non-default state - assert widget.page == 1 - - bf.options.display.max_rows = 10 # Change global setting - - assert widget.page_size == initial_page_size # Should remain unchanged - assert widget.page == 1 # Page should not be reset - - -def test_widget_with_empty_dataframe_should_have_zero_row_count( - empty_bf_df: bf.dataframe.DataFrame, -): - """ - Given an empty DataFrame, - when a widget is created from it, - then its row_count should be 0. - """ - - with bigframes.option_context("display.render_mode", "anywidget"): - from bigframes.display import TableWidget - - widget = TableWidget(empty_bf_df) - - assert widget.row_count == 0 - - -def test_widget_with_empty_dataframe_should_render_table_headers( - empty_bf_df: bf.dataframe.DataFrame, -): - """ - Given an empty DataFrame, - when a widget is created from it, - then its HTML representation should still render the table headers. - """ - - with bigframes.option_context("display.render_mode", "anywidget"): - from bigframes.display import TableWidget - - widget = TableWidget(empty_bf_df) - html = widget.table_html - assert " 0 - assert ".bigframes-widget .footer" in css_content - - -def test_widget_row_count_should_be_immutable_after_creation( - paginated_bf_df: bf.dataframe.DataFrame, -): - """ - Given a widget created with a specific configuration when global display - options are changed later, the widget's original row_count should remain - unchanged. - """ - from bigframes.display import TableWidget - - # Use a context manager to ensure the option is reset - with bigframes.option_context( - "display.render_mode", "anywidget", "display.max_rows", 2 - ): - widget = TableWidget(paginated_bf_df) - initial_row_count = widget.row_count - - # Change a global option that could influence row count - bf.options.display.max_rows = 10 - - # Verify the row count remains immutable. - assert widget.row_count == initial_row_count - - -class FaultyIterator: - def __iter__(self): - return self - - def __next__(self): - raise ValueError("Simulated read error") - - -def test_widget_should_show_error_on_batch_failure( - paginated_bf_df: bf.dataframe.DataFrame, - monkeypatch: pytest.MonkeyPatch, -): - """ - Given that the internal call to `_to_pandas_batches` fails and returns None, - when the TableWidget is created, its `error_message` should be set and displayed. - """ - # Patch the DataFrame's batch creation method to simulate a failure. - monkeypatch.setattr( - "bigframes.dataframe.DataFrame._to_pandas_batches", - lambda self, *args, **kwargs: None, - ) - - # Create the TableWidget under the error condition. - with bigframes.option_context("display.render_mode", "anywidget"): - from bigframes.display import TableWidget - - # The widget should handle the faulty data from the mock without crashing. - widget = TableWidget(paginated_bf_df) - - # The widget should have an error message and display it in the HTML. - assert widget.row_count is None - assert widget._error_message is not None - assert "Could not retrieve data batches" in widget._error_message - assert widget._error_message in widget.table_html - - -def test_widget_row_count_reflects_actual_data_available( - paginated_bf_df: bf.dataframe.DataFrame, -): - """ - Test that widget row_count reflects the actual data available, - regardless of theoretical limits. - """ - from bigframes.display import TableWidget - - # Set up display options that define a page size. - with bigframes.option_context( - "display.render_mode", "anywidget", "display.max_rows", 2 - ): - widget = TableWidget(paginated_bf_df) - - # The widget should report the total rows in the DataFrame, - # not limited by page_size (which only affects pagination) - assert widget.row_count == EXPECTED_ROW_COUNT - assert widget.page_size == 2 # Respects the display option - - -def test_widget_with_unknown_row_count_should_auto_navigate_to_last_page( - session: bf.Session, -): - """ - Given a widget with unknown row count (row_count=None), when a user - navigates beyond the available data and all data is loaded, then the - widget should automatically navigate back to the last valid page. - """ - from bigframes.display import TableWidget - - # Create a small DataFrame with known content - test_data = pd.DataFrame( - { - "id": [0, 1, 2, 3, 4], - "value": ["row_0", "row_1", "row_2", "row_3", "row_4"], - } - ) - bf_df = session.read_pandas(test_data) - - with bigframes.option_context( - "display.render_mode", "anywidget", "display.max_rows", 2 - ): - widget = TableWidget(bf_df) - - # Manually set row_count to None to simulate unknown total - widget.row_count = None - - # Navigate to a page beyond available data (page 10) - # With page_size=2 and 5 rows, valid pages are 0, 1, 2 - widget.page = 10 - - # Force data loading by accessing table_html - _ = widget.table_html - - # After all data is loaded, widget should auto-navigate to last valid page - # Last valid page = ceil(5 / 2) - 1 = 2 - assert widget.page == 2 - - # Verify the displayed content is the last page - html = widget.table_html - assert "row_4" in html # Last row should be visible - assert "row_0" not in html # First row should not be visible - - -def test_widget_with_unknown_row_count_should_set_none_state_for_frontend( - session: bf.Session, -): - """ - Given a widget with unknown row count, its `row_count` traitlet should be - `None`, which signals the frontend to display 'Page X of many'. - """ - from bigframes.display import TableWidget - - test_data = pd.DataFrame( - { - "id": [0, 1, 2], - "value": ["a", "b", "c"], - } - ) - bf_df = session.read_pandas(test_data) - - with bigframes.option_context( - "display.render_mode", "anywidget", "display.max_rows", 2 - ): - widget = TableWidget(bf_df) - - # Set row_count to None - widget.row_count = None - - # Verify row_count is None (not 0) - assert widget.row_count is None - - # The widget should still function normally - assert widget.page == 0 - assert widget.page_size == 2 - - # Force data loading by accessing table_html. This also ensures that - # rendering does not raise an exception. - _ = widget.table_html - - -def test_widget_with_unknown_row_count_should_allow_forward_navigation( - session: bf.Session, -): - """ - Given a widget with unknown row count, users should be able to navigate - forward until they reach the end of available data. - """ - from bigframes.display import TableWidget - - test_data = pd.DataFrame( - { - "id": [0, 1, 2, 3, 4, 5], - "value": ["p0_r0", "p0_r1", "p1_r0", "p1_r1", "p2_r0", "p2_r1"], - } - ) - bf_df = session.read_pandas(test_data) - - with bigframes.option_context( - "display.render_mode", "anywidget", "display.max_rows", 2 - ): - widget = TableWidget(bf_df) - widget.row_count = None - - # Navigate to page 1 - widget.page = 1 - html = widget.table_html - assert "p1_r0" in html - assert "p1_r1" in html - - # Navigate to page 2 - widget.page = 2 - html = widget.table_html - assert "p2_r0" in html - assert "p2_r1" in html - - # Navigate beyond available data (page 5) - widget.page = 5 - _ = widget.table_html - - # Should auto-navigate back to last valid page (page 2) - assert widget.page == 2 - - -def test_widget_with_unknown_row_count_empty_dataframe( - session: bf.Session, -): - """ - Given an empty DataFrame with unknown row count, the widget should - stay on page 0 and display empty content. - """ - from bigframes.display import TableWidget - - empty_data = pd.DataFrame(columns=["id", "value"]) - bf_df = session.read_pandas(empty_data) - - with bigframes.option_context("display.render_mode", "anywidget"): - widget = TableWidget(bf_df) - widget.row_count = None - - # Attempt to navigate to page 5 - widget.page = 5 - _ = widget.table_html - - # Should stay on page 0 for empty DataFrame - assert widget.page == 0 - - -def test_widget_sort_should_sort_ascending_on_first_click( - table_widget, paginated_pandas_df: pd.DataFrame -): - """ - Given a widget, when a column header is clicked for the first time, - then the data should be sorted by that column in ascending order. - """ - table_widget.sort_context = [{"column": "id", "ascending": True}] - - expected_slice = paginated_pandas_df.sort_values("id", ascending=True).iloc[0:2] - html = table_widget.table_html - - _assert_html_matches_pandas_slice(html, expected_slice, paginated_pandas_df) - - -def test_widget_sort_should_sort_descending_on_second_click( - table_widget, paginated_pandas_df: pd.DataFrame -): - """ - Given a widget sorted by a column, when the same column header is clicked again, - then the data should be sorted by that column in descending order. - """ - table_widget.sort_context = [{"column": "id", "ascending": True}] - - # Second click - table_widget.sort_context = [{"column": "id", "ascending": False}] - - expected_slice = paginated_pandas_df.sort_values("id", ascending=False).iloc[0:2] - html = table_widget.table_html - - _assert_html_matches_pandas_slice(html, expected_slice, paginated_pandas_df) - - -def test_widget_sort_should_switch_column_and_sort_ascending( - table_widget, paginated_pandas_df: pd.DataFrame -): - """ - Given a widget sorted by a column, when a different column header is clicked, - then the data should be sorted by the new column in ascending order. - """ - table_widget.sort_context = [{"column": "id", "ascending": True}] - - # Click on a different column - table_widget.sort_context = [{"column": "value", "ascending": True}] - - expected_slice = paginated_pandas_df.sort_values("value", ascending=True).iloc[0:2] - html = table_widget.table_html - - _assert_html_matches_pandas_slice(html, expected_slice, paginated_pandas_df) - - -def test_widget_sort_should_be_maintained_after_pagination( - table_widget, paginated_pandas_df: pd.DataFrame -): - """ - Given a sorted widget, when the user navigates to the next page, - then the sorting should be maintained. - """ - table_widget.sort_context = [{"column": "id", "ascending": True}] - - # Go to the second page - table_widget.page = 1 - - expected_slice = paginated_pandas_df.sort_values("id", ascending=True).iloc[2:4] - html = table_widget.table_html - - _assert_html_matches_pandas_slice(html, expected_slice, paginated_pandas_df) - - -def test_widget_sort_should_reset_on_page_size_change( - table_widget, paginated_pandas_df: pd.DataFrame -): - """ - Given a sorted widget, when the page size is changed, - then the sorting should be reset. - """ - table_widget.sort_context = [{"column": "id", "ascending": True}] - - table_widget.page_size = 3 - - # Sorting is not reset in the backend, but the view should be of the unsorted df - expected_slice = paginated_pandas_df.iloc[0:3] - html = table_widget.table_html - - _assert_html_matches_pandas_slice(html, expected_slice, paginated_pandas_df) - - -@pytest.fixture(scope="module") -def integer_column_df(session): - """Create a DataFrame with integer column labels.""" - pandas_df = pd.DataFrame([[0, 1], [2, 3]], columns=pd.Index([1, 2])) - return session.read_pandas(pandas_df) - - -@pytest.fixture(scope="module") -def multiindex_column_df(session): - """Create a DataFrame with MultiIndex column labels.""" - pandas_df = pd.DataFrame( - { - "foo": ["one", "one", "one", "two", "two", "two"], - "bar": ["A", "B", "C", "A", "B", "C"], - "baz": [1, 2, 3, 4, 5, 6], - "zoo": ["x", "y", "z", "q", "w", "t"], - } - ) - df = session.read_pandas(pandas_df) - # The session is attached to `df` through the constructor. - # We can pass it to the pivoted DataFrame. - pdf = df.pivot(index="foo", columns="bar", values=["baz", "zoo"]) - return pdf - - -def test_table_widget_integer_columns_disables_sorting(integer_column_df): - """ - Given a DataFrame with integer column labels, the widget should - disable sorting. - """ - from bigframes.display import TableWidget - - widget = TableWidget(integer_column_df) - assert widget.orderable_columns == [] - - -def test_table_widget_multiindex_columns_disables_sorting(multiindex_column_df): - """ - Given a DataFrame with a MultiIndex for columns, the widget should - disable sorting. - """ - from bigframes.display import TableWidget - - widget = TableWidget(multiindex_column_df) - assert widget.orderable_columns == [] - - -def test_repr_mimebundle_should_fallback_to_html_if_anywidget_is_unavailable( - paginated_bf_df: bf.dataframe.DataFrame, -): - """ - Test that _repr_mimebundle_ falls back to static html when anywidget is not available. - """ - with bigframes.option_context( - "display.render_mode", "anywidget", "display.max_rows", 2 - ): - # Mock the ANYWIDGET_INSTALLED flag to simulate absence of anywidget - with mock.patch("bigframes.display.anywidget._ANYWIDGET_INSTALLED", False): - bundle = paginated_bf_df._repr_mimebundle_() - assert "application/vnd.jupyter.widget-view+json" not in bundle - assert "text/html" in bundle - html = bundle["text/html"] - assert "page_3_row_2" in html - assert "page_3_row_1" in html - assert "page_1_row_1" not in html - - -def test_repr_mimebundle_should_return_widget_view_if_anywidget_is_available( - paginated_bf_df: bf.dataframe.DataFrame, -): - """ - Test that _repr_mimebundle_ returns a widget view when anywidget is available. - """ - with bigframes.option_context("display.render_mode", "anywidget"): - bundle = paginated_bf_df._repr_mimebundle_() - assert isinstance(bundle, tuple) - data, metadata = bundle - assert "application/vnd.jupyter.widget-view+json" in data - assert "text/html" in data - assert "text/plain" in data - - -def test_repr_in_anywidget_mode_should_not_be_deferred( - paginated_bf_df: bf.dataframe.DataFrame, -): - """ - Test that repr(df) is not deferred in anywidget mode. - This is to ensure that print(df) works as expected. - """ - with bigframes.option_context("display.render_mode", "anywidget"): - representation = repr(paginated_bf_df) - assert "Computation deferred" not in representation - assert "page_1_row_1" in representation - - -def test_dataframe_repr_mimebundle_should_return_widget_with_metadata_in_anywidget_mode( - monkeypatch: pytest.MonkeyPatch, - session: bigframes.Session, # Add session as a fixture -): - """Test that _repr_mimebundle_ returns a widget view with metadata when anywidget is available.""" - with bigframes.option_context("display.render_mode", "anywidget"): - # Create a real DataFrame object (or a mock that behaves like one minimally) - # for _repr_mimebundle_ to operate on. - test_df = bigframes.dataframe.DataFrame( - pd.DataFrame({"col1": [1, 2], "col2": [3, 4]}), session=session - ) - - mock_get_anywidget_bundle_return_value: tuple[ - dict[str, Any], dict[str, Any] - ] = ( - { - "application/vnd.jupyter.widget-view+json": {"model_id": "123"}, - "text/html": "
My Table HTML
", - "text/plain": "My Table Plain Text", - }, - { - "application/vnd.jupyter.widget-view+json": { - "colab": {"custom_widget_manager": {}} - } - }, - ) - - # Patch the class method directly - with mock.patch( - "bigframes.display.html.get_anywidget_bundle", - return_value=mock_get_anywidget_bundle_return_value, - ): - result = test_df._repr_mimebundle_() - - assert isinstance(result, tuple) - data, metadata = result - assert "application/vnd.jupyter.widget-view+json" in data - assert "text/html" in data - assert "text/plain" in data - assert "application/vnd.jupyter.widget-view+json" in metadata - assert "colab" in metadata["application/vnd.jupyter.widget-view+json"] - - -@pytest.fixture(scope="module") -def custom_index_pandas_df() -> pd.DataFrame: - """Create a DataFrame with a custom named index for testing.""" - test_data = pd.DataFrame( - { - "value_a": [10, 20, 30, 40, 50, 60], - "value_b": ["a", "b", "c", "d", "e", "f"], - } - ) - test_data.index = pd.Index( - ["row_1", "row_2", "row_3", "row_4", "row_5", "row_6"], name="custom_idx" - ) - return test_data - - -@pytest.fixture(scope="module") -def custom_index_bf_df( - session: bf.Session, custom_index_pandas_df: pd.DataFrame -) -> bf.dataframe.DataFrame: - return session.read_pandas(custom_index_pandas_df) - - -@pytest.fixture(scope="module") -def multiindex_pandas_df() -> pd.DataFrame: - """Create a DataFrame with MultiIndex for testing.""" - test_data = pd.DataFrame( - { - "value": [100, 200, 300, 400, 500, 600], - "category": ["X", "Y", "Z", "X", "Y", "Z"], - } - ) - test_data.index = pd.MultiIndex.from_arrays( - [ - ["group_A", "group_A", "group_A", "group_B", "group_B", "group_B"], - [1, 2, 3, 1, 2, 3], - ], - names=["group", "item"], - ) - return test_data - - -@pytest.fixture(scope="module") -def multiindex_bf_df( - session: bf.Session, multiindex_pandas_df: pd.DataFrame -) -> bf.dataframe.DataFrame: - return session.read_pandas(multiindex_pandas_df) - - -def test_widget_with_default_index_should_display_index_column_with_empty_header( - paginated_bf_df: bf.dataframe.DataFrame, -): - """ - Given a DataFrame with a default index, when the TableWidget is rendered, - then an index column should be visible with an empty header. - """ - import re - - from bigframes.display.anywidget import TableWidget - - with bf.option_context("display.render_mode", "anywidget", "display.max_rows", 2): - widget = TableWidget(paginated_bf_df) - html = widget.table_html - - # The header for the index should be present but empty, matching the - # internal rendering logic. - thead = html.split("")[1].split("")[0] - # Find the first header cell and check that its content div is empty. - match = re.search(r"]*>]*>([^<]*)", thead) - assert match is not None, "Could not find table header cell in output." - assert match.group(1) == "", ( - f"Expected empty index header, but found: {match.group(1)}" - ) - - -def test_widget_with_custom_index_should_display_index_column( - custom_index_bf_df: bf.dataframe.DataFrame, -): - """ - Given a DataFrame with a custom named index, when rendered, - then the index column and first page of rows should be visible. - """ - from bigframes.display.anywidget import TableWidget - - with bf.option_context("display.render_mode", "anywidget", "display.max_rows", 2): - widget = TableWidget(custom_index_bf_df) - html = widget.table_html - - assert "custom_idx" in html - assert "row_1" in html - assert "row_2" in html - assert "row_3" not in html # Verify pagination is working - assert "row_4" not in html - - -def test_widget_with_custom_index_pagination_preserves_index( - custom_index_bf_df: bf.dataframe.DataFrame, -): - """ - Given a DataFrame with a custom index, when navigating to the second page, - then the second page's index values should be visible. - """ - from bigframes.display.anywidget import TableWidget - - with bf.option_context("display.render_mode", "anywidget", "display.max_rows", 2): - widget = TableWidget(custom_index_bf_df) - - widget.page = 1 # Navigate to page 2 - html = widget.table_html - - assert "row_3" in html - assert "row_4" in html - assert "row_1" not in html # Verify page 1 content is gone - assert "row_2" not in html - - -def test_widget_with_custom_index_matches_pandas_output( - custom_index_bf_df: bf.dataframe.DataFrame, -): - """ - Given a DataFrame with a custom index and max_rows=3, the widget's HTML - output should contain the first three index values. - """ - from bigframes.display.anywidget import TableWidget - - with bf.option_context("display.render_mode", "anywidget", "display.max_rows", 3): - widget = TableWidget(custom_index_bf_df) - html = widget.table_html - - assert "row_1" in html - assert "row_2" in html - assert "row_3" in html - assert "row_4" not in html # Verify it respects max_rows - - -# TODO(b/438181139): Add tests for custom multiindex -# This may not be necessary for the SQL Cell use case but should be -# considered for completeness. - - -def test_series_anywidget_integration_with_notebook_display( - paginated_bf_df: bf.dataframe.DataFrame, -): - """Test Series display integration in Jupyter-like environment.""" - pytest.importorskip("anywidget") - - with bf.option_context("display.render_mode", "anywidget"): - series = paginated_bf_df["value"] - - # Test the full display pipeline - from IPython.display import display as ipython_display - - # This should work without errors - ipython_display(series) - - -def test_series_different_data_types_anywidget(session: bf.Session): - """Test Series with different data types in anywidget mode.""" - pytest.importorskip("anywidget") - - # Create Series with different types - test_data = pd.DataFrame( - { - "string_col": ["a", "b", "c"], - "int_col": [1, 2, 3], - "float_col": [1.1, 2.2, 3.3], - "bool_col": [True, False, True], - } - ) - bf_df = session.read_pandas(test_data) - - with bf.option_context("display.render_mode", "anywidget"): - for col_name in test_data.columns: - series = bf_df[col_name] - widget = bigframes.display.TableWidget(series.to_frame()) - assert widget.row_count == 3 diff --git a/tests/system/small/test_bq_sessions.py b/tests/system/small/test_bq_sessions.py deleted file mode 100644 index 99d2dfece3b..00000000000 --- a/tests/system/small/test_bq_sessions.py +++ /dev/null @@ -1,87 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import time -from concurrent.futures import ThreadPoolExecutor - -import google -import google.api_core.exceptions -import pytest -from google.cloud import bigquery - -import bigframes.core.events -from bigframes.session import bigquery_session - -TEST_SCHEMA = [ - bigquery.SchemaField("bool field", "BOOLEAN"), - bigquery.SchemaField("string field", "STRING"), - bigquery.SchemaField("float array_field", "FLOAT", mode="REPEATED"), - bigquery.SchemaField( - "struct field", - "RECORD", - fields=(bigquery.SchemaField("int subfield", "INTEGER"),), - ), -] - - -@pytest.fixture -def session_resource_manager( - bigquery_client, -) -> bigquery_session.SessionResourceManager: - return bigquery_session.SessionResourceManager( - bigquery_client, "US", publisher=bigframes.core.events.Publisher() - ) - - -def test_bq_session_create_temp_table_clustered(bigquery_client: bigquery.Client): - session_resource_manager = bigquery_session.SessionResourceManager( - bigquery_client, "US", publisher=bigframes.core.events.Publisher() - ) - cluster_cols = ["string field", "bool field"] - - session_table_ref = session_resource_manager.create_temp_table( - TEST_SCHEMA, cluster_cols=cluster_cols - ) - session_resource_manager._keep_session_alive() - - result_table = bigquery_client.get_table(session_table_ref) - assert result_table.schema == TEST_SCHEMA - assert result_table.clustering_fields == cluster_cols - - session_resource_manager.close() - with pytest.raises(google.api_core.exceptions.NotFound): - # It may take time for the underlying tables to get cleaned up after - # closing the session, so wait at least 1 minute to check. - for _ in range(6): - bigquery_client.get_table(session_table_ref) - time.sleep(10) - - -def test_bq_session_create_multi_temp_tables(bigquery_client: bigquery.Client): - session_resource_manager = bigquery_session.SessionResourceManager( - bigquery_client, "US", publisher=bigframes.core.events.Publisher() - ) - - def create_table(): - return session_resource_manager.create_temp_table(TEST_SCHEMA) - - with ThreadPoolExecutor() as executor: - results = [executor.submit(create_table) for i in range(10)] - - for future in results: - table = future.result() - result_table = bigquery_client.get_table(table) - assert result_table.schema == TEST_SCHEMA - - session_resource_manager.close() diff --git a/tests/system/small/test_dataframe.py b/tests/system/small/test_dataframe.py index a109c33ffa6..3d7ba867ea4 100644 --- a/tests/system/small/test_dataframe.py +++ b/tests/system/small/test_dataframe.py @@ -12,12 +12,10 @@ # See the License for the specific language governing permissions and # limitations under the License. -import io import operator -import sys import tempfile import typing -from typing import Dict, List, Tuple +from typing import Tuple import geopandas as gpd # type: ignore import numpy as np @@ -28,128 +26,30 @@ import bigframes import bigframes._config.display_options as display_options -import bigframes.core.indexes as bf_indexes import bigframes.dataframe as dataframe -import bigframes.dtypes as dtypes -import bigframes.pandas as bpd import bigframes.series as series -import bigframes.testing -from bigframes.testing.utils import ( - assert_dfs_equivalent, - assert_frame_equal, - assert_series_equal, - assert_series_equivalent, +from tests.system.utils import ( + assert_pandas_df_equal_ignore_ordering, + assert_series_equal_ignoring_order, ) def test_df_construct_copy(scalars_dfs): columns = ["int64_col", "string_col", "float64_col"] scalars_df, scalars_pandas_df = scalars_dfs - # Make the mapping from label to col_id non-trivial - bf_df = scalars_df.copy() - bf_df["int64_col"] = bf_df["int64_col"] / 2 - pd_df = scalars_pandas_df.copy() - pd_df["int64_col"] = pd_df["int64_col"] / 2 - - bf_result = dataframe.DataFrame(bf_df, columns=columns).to_pandas() - - pd_result = pd.DataFrame(pd_df, columns=columns) + bf_result = dataframe.DataFrame(scalars_df, columns=columns).to_pandas() + pd_result = pd.DataFrame(scalars_pandas_df, columns=columns) pandas.testing.assert_frame_equal(bf_result, pd_result) -def test_df_construct_pandas_default(scalars_dfs): - # This should trigger the inlined codepath - columns = [ - "int64_too", - "int64_col", - "float64_col", - "bool_col", - "string_col", - "date_col", - "datetime_col", - "numeric_col", - "float64_col", - "time_col", - "timestamp_col", - ] +def test_df_construct_pandas(scalars_dfs): + columns = ["int64_too", "int64_col", "float64_col", "bool_col", "string_col"] _, scalars_pandas_df = scalars_dfs bf_result = dataframe.DataFrame(scalars_pandas_df, columns=columns).to_pandas() pd_result = pd.DataFrame(scalars_pandas_df, columns=columns) pandas.testing.assert_frame_equal(bf_result, pd_result) -@pytest.mark.parametrize( - ("write_engine"), - [ - ("bigquery_inline"), - ("bigquery_load"), - ("bigquery_streaming"), - # TODO(b/502298527): Reenable bigquery_write test - # ("bigquery_write"), - ], -) -def test_read_pandas_all_nice_types( - session: bigframes.Session, scalars_pandas_df_index: pd.DataFrame, write_engine -): - bf_result = session.read_pandas( - scalars_pandas_df_index, write_engine=write_engine - ).to_pandas() - pandas.testing.assert_frame_equal(bf_result, scalars_pandas_df_index) - - -def test_df_construct_large_strings(): - data = [["hello", "w" + "o" * 50000 + "rld"]] - bf_result = dataframe.DataFrame(data).to_pandas() - pd_result = pd.DataFrame(data, dtype=pd.StringDtype(storage="pyarrow")) - pandas.testing.assert_frame_equal(bf_result, pd_result, check_index_type=False) - - -def test_df_construct_pandas_load_job(scalars_dfs_maybe_ordered): - # This should trigger the inlined codepath - columns = [ - "int64_too", - "int64_col", - "float64_col", - "bool_col", - "string_col", - "date_col", - "datetime_col", - "numeric_col", - "float64_col", - "time_col", - "timestamp_col", - "geography_col", - ] - _, scalars_pandas_df = scalars_dfs_maybe_ordered - bf_result = dataframe.DataFrame(scalars_pandas_df, columns=columns) - pd_result = pd.DataFrame(scalars_pandas_df, columns=columns) - assert_dfs_equivalent(pd_result, bf_result) - - -def test_df_construct_structs(session): - pd_frame = pd.Series( - [ - {"version": 1, "project": "pandas"}, - {"version": 2, "project": "pandas"}, - {"version": 1, "project": "numpy"}, - ] - ).to_frame() - bf_series = session.read_pandas(pd_frame) - bigframes.testing.utils.assert_frame_equal( - bf_series.to_pandas(), pd_frame, check_index_type=False, check_dtype=False - ) - - -def test_df_construct_local_concat_pd(scalars_pandas_df_index, session): - pd_df = pd.concat([scalars_pandas_df_index, scalars_pandas_df_index]) - - bf_df = session.read_pandas(pd_df) - - bigframes.testing.utils.assert_frame_equal( - bf_df.to_pandas(), pd_df, check_index_type=False, check_dtype=False - ) - - def test_df_construct_pandas_set_dtype(scalars_dfs): columns = [ "int64_too", @@ -165,17 +65,17 @@ def test_df_construct_pandas_set_dtype(scalars_dfs): pandas.testing.assert_frame_equal(bf_result, pd_result) -def test_df_construct_from_series(scalars_dfs_maybe_ordered): - scalars_df, scalars_pandas_df = scalars_dfs_maybe_ordered +def test_df_construct_from_series(scalars_dfs): + scalars_df, scalars_pandas_df = scalars_dfs bf_result = dataframe.DataFrame( {"a": scalars_df["int64_col"], "b": scalars_df["string_col"]}, dtype="string[pyarrow]", - ) + ).to_pandas() pd_result = pd.DataFrame( {"a": scalars_pandas_df["int64_col"], "b": scalars_pandas_df["string_col"]}, dtype="string[pyarrow]", ) - assert_dfs_equivalent(pd_result, bf_result) + pandas.testing.assert_frame_equal(bf_result, pd_result) def test_df_construct_from_dict(): @@ -192,57 +92,13 @@ def test_df_construct_from_dict(): ) -@pytest.mark.parametrize( - ("json_type"), - [ - pytest.param(dtypes.JSON_DTYPE), - pytest.param("json"), - ], -) -def test_df_construct_w_json_dtype(json_type): - data = [ - "1", - "false", - '["a", {"b": 1}, null]', - None, - ] - df = dataframe.DataFrame({"json_col": data}, dtype=json_type) - - assert df["json_col"].dtype == dtypes.JSON_DTYPE - assert df["json_col"][1] == "false" - - -def test_df_construct_inline_respects_location(reset_default_session_and_location): - # Note: This starts a thread-local session. - with bpd.option_context("bigquery.location", "europe-west1"): - df = bpd.DataFrame([[1, 2, 3], [4, 5, 6]]) - df.to_gbq() - assert df.query_job is not None - table = bpd.get_global_session().bqclient.get_table(df.query_job.destination) - - assert table.location == "europe-west1" - - -def test_df_construct_dtype(): - data = { - "int_col": [1, 2, 3], - "string_col": ["1.1", "2.0", "3.5"], - "float_col": [1.0, 2.0, 3.0], - } - dtype = pd.StringDtype(storage="pyarrow") - bf_result = dataframe.DataFrame(data, dtype=dtype) - pd_result = pd.DataFrame(data, dtype=dtype) - pd_result.index = pd_result.index.astype("Int64") - pandas.testing.assert_frame_equal(bf_result.to_pandas(), pd_result) - - def test_get_column(scalars_dfs): scalars_df, scalars_pandas_df = scalars_dfs col_name = "int64_col" series = scalars_df[col_name] bf_result = series.to_pandas() pd_result = scalars_pandas_df[col_name] - assert_series_equal(bf_result, pd_result) + assert_series_equal_ignoring_order(bf_result, pd_result) def test_get_column_nonstring(scalars_dfs): @@ -250,22 +106,7 @@ def test_get_column_nonstring(scalars_dfs): series = scalars_df.rename(columns={"int64_col": 123.1})[123.1] bf_result = series.to_pandas() pd_result = scalars_pandas_df.rename(columns={"int64_col": 123.1})[123.1] - assert_series_equal(bf_result, pd_result) - - -@pytest.mark.parametrize( - "row_slice", - [ - (slice(1, 7, 2)), - (slice(1, 7, None)), - (slice(None, -3, None)), - ], -) -def test_get_rows_with_slice(scalars_dfs, row_slice): - scalars_df, scalars_pandas_df = scalars_dfs - bf_result = scalars_df[row_slice].to_pandas() - pd_result = scalars_pandas_df[row_slice] - assert_frame_equal(bf_result, pd_result) + assert_series_equal_ignoring_order(bf_result, pd_result) def test_hasattr(scalars_dfs): @@ -275,24 +116,15 @@ def test_hasattr(scalars_dfs): assert not hasattr(scalars_df, "not_exist") -@pytest.mark.parametrize( - ("ordered"), - [ - (True), - (False), - ], -) -def test_head_with_custom_column_labels( - scalars_df_index, scalars_pandas_df_index, ordered -): +def test_head_with_custom_column_labels(scalars_df_index, scalars_pandas_df_index): rename_mapping = { "int64_col": "Integer Column", "string_col": "言語列", } bf_df = scalars_df_index.rename(columns=rename_mapping).head(3) - bf_result = bf_df.to_pandas(ordered=ordered) + bf_result = bf_df.to_pandas() pd_result = scalars_pandas_df_index.rename(columns=rename_mapping).head(3) - assert_frame_equal(bf_result, pd_result, ignore_order=not ordered) + pandas.testing.assert_frame_equal(bf_result, pd_result) def test_tail_with_custom_column_labels(scalars_df_index, scalars_pandas_df_index): @@ -315,13 +147,15 @@ def test_tail_with_custom_column_labels(scalars_df_index, scalars_pandas_df_inde ], ) def test_df_nlargest(scalars_df_index, scalars_pandas_df_index, keep): - bf_result = scalars_df_index.nlargest(3, ["bool_col", "int64_too"], keep=keep) + bf_result = scalars_df_index.nlargest( + 3, ["bool_col", "int64_too"], keep=keep + ).to_pandas() pd_result = scalars_pandas_df_index.nlargest( 3, ["bool_col", "int64_too"], keep=keep ) - bigframes.testing.utils.assert_frame_equal( - bf_result.to_pandas(), + pd.testing.assert_frame_equal( + bf_result, pd_result, ) @@ -335,11 +169,11 @@ def test_df_nlargest(scalars_df_index, scalars_pandas_df_index, keep): ], ) def test_df_nsmallest(scalars_df_index, scalars_pandas_df_index, keep): - bf_result = scalars_df_index.nsmallest(6, ["bool_col"], keep=keep) + bf_result = scalars_df_index.nsmallest(6, ["bool_col"], keep=keep).to_pandas() pd_result = scalars_pandas_df_index.nsmallest(6, ["bool_col"], keep=keep) - bigframes.testing.utils.assert_frame_equal( - bf_result.to_pandas(), + pd.testing.assert_frame_equal( + bf_result, pd_result, ) @@ -349,7 +183,7 @@ def test_get_column_by_attr(scalars_dfs): series = scalars_df.int64_col bf_result = series.to_pandas() pd_result = scalars_pandas_df.int64_col - assert_series_equal(bf_result, pd_result) + assert_series_equal_ignoring_order(bf_result, pd_result) def test_get_columns(scalars_dfs): @@ -357,7 +191,7 @@ def test_get_columns(scalars_dfs): col_names = ["bool_col", "float64_col", "int64_col"] df_subset = scalars_df.get(col_names) df_pandas = df_subset.to_pandas() - bigframes.testing.utils.assert_index_equal( + pd.testing.assert_index_equal( df_pandas.columns, scalars_pandas_df[col_names].columns ) @@ -369,238 +203,11 @@ def test_get_columns_default(scalars_dfs): assert result == "default_val" -@pytest.mark.parametrize( - ("loc", "column", "value", "allow_duplicates"), - [ - (0, 666, 2, False), - (5, "float64_col", 2.2, True), - (13, "rowindex_2", [8, 7, 6, 5, 4, 3, 2, 1, 0], True), - pytest.param( - 14, - "test", - 2, - False, - marks=pytest.mark.xfail( - raises=IndexError, - ), - ), - pytest.param( - 12, - "int64_col", - 2, - False, - marks=pytest.mark.xfail( - raises=ValueError, - ), - ), - ], -) -def test_insert(scalars_dfs, loc, column, value, allow_duplicates): - scalars_df, scalars_pandas_df = scalars_dfs - # insert works inplace, so will influence other tests. - # make a copy to avoid inplace changes. - bf_df = scalars_df.copy() - pd_df = scalars_pandas_df.copy() - bf_df.insert(loc, column, value, allow_duplicates) - pd_df.insert(loc, column, value, allow_duplicates) - - bigframes.testing.utils.assert_frame_equal( - bf_df.to_pandas(), pd_df, check_dtype=False - ) - - -def test_mask_series_cond(scalars_df_index, scalars_pandas_df_index): - cond_bf = scalars_df_index["int64_col"] > 0 - cond_pd = scalars_pandas_df_index["int64_col"] > 0 - - bf_df = scalars_df_index[["int64_too", "int64_col", "float64_col"]] - pd_df = scalars_pandas_df_index[["int64_too", "int64_col", "float64_col"]] - bf_result = bf_df.mask(cond_bf, bf_df + 1).to_pandas() - pd_result = pd_df.mask(cond_pd, pd_df + 1) - pandas.testing.assert_frame_equal(bf_result, pd_result) - - -def test_mask_callable(scalars_df_index, scalars_pandas_df_index): - def is_positive(x): - return x > 0 - - bf_df = scalars_df_index[["int64_too", "int64_col", "float64_col"]] - pd_df = scalars_pandas_df_index[["int64_too", "int64_col", "float64_col"]] - bf_result = bf_df.mask(cond=is_positive, other=lambda x: x + 1).to_pandas() - pd_result = pd_df.mask(cond=is_positive, other=lambda x: x + 1) - - pandas.testing.assert_frame_equal(bf_result, pd_result) - - -def test_where_multi_column(scalars_df_index, scalars_pandas_df_index): - # Test when a dataframe has multi-columns. - columns = ["int64_col", "float64_col"] - dataframe_bf = scalars_df_index[columns] - - dataframe_bf.columns = pd.MultiIndex.from_tuples( - [("str1", 1), ("str2", 2)], names=["STR", "INT"] - ) - cond_bf = dataframe_bf["str1"] > 0 - - with pytest.raises(NotImplementedError) as context: - dataframe_bf.where(cond_bf).to_pandas() - assert ( - str(context.value) - == "The dataframe.where() method does not support multi-column." - ) - - -def test_where_series_cond(scalars_df_index, scalars_pandas_df_index): - # Condition is dataframe, other is None (as default). - cond_bf = scalars_df_index["int64_col"] > 0 - cond_pd = scalars_pandas_df_index["int64_col"] > 0 - bf_result = scalars_df_index.where(cond_bf).to_pandas() - pd_result = scalars_pandas_df_index.where(cond_pd) - pandas.testing.assert_frame_equal(bf_result, pd_result) - - -def test_where_series_cond_const_other(scalars_df_index, scalars_pandas_df_index): - # Condition is a series, other is a constant. - columns = ["int64_col", "float64_col"] - dataframe_bf = scalars_df_index[columns] - dataframe_pd = scalars_pandas_df_index[columns] - dataframe_bf.columns.name = "test_name" - dataframe_pd.columns.name = "test_name" - - cond_bf = dataframe_bf["int64_col"] > 0 - cond_pd = dataframe_pd["int64_col"] > 0 - other = 0 - - bf_result = dataframe_bf.where(cond_bf, other).to_pandas() - pd_result = dataframe_pd.where(cond_pd, other) - pandas.testing.assert_frame_equal(bf_result, pd_result) - - -def test_where_series_cond_dataframe_other(scalars_df_index, scalars_pandas_df_index): - # Condition is a series, other is a dataframe. - columns = ["int64_col", "float64_col"] - dataframe_bf = scalars_df_index[columns] - dataframe_pd = scalars_pandas_df_index[columns] - - cond_bf = dataframe_bf["int64_col"] > 0 - cond_pd = dataframe_pd["int64_col"] > 0 - other_bf = -dataframe_bf - other_pd = -dataframe_pd - - bf_result = dataframe_bf.where(cond_bf, other_bf).to_pandas() - pd_result = dataframe_pd.where(cond_pd, other_pd) - pandas.testing.assert_frame_equal(bf_result, pd_result) - - -def test_where_dataframe_cond(scalars_df_index, scalars_pandas_df_index): - # Condition is a dataframe, other is None. - columns = ["int64_col", "float64_col"] - dataframe_bf = scalars_df_index[columns] - dataframe_pd = scalars_pandas_df_index[columns] - - cond_bf = dataframe_bf > 0 - cond_pd = dataframe_pd > 0 - - bf_result = dataframe_bf.where(cond_bf, None).to_pandas() - pd_result = dataframe_pd.where(cond_pd, None) - pandas.testing.assert_frame_equal(bf_result, pd_result) - - -def test_where_dataframe_cond_const_other(scalars_df_index, scalars_pandas_df_index): - # Condition is a dataframe, other is a constant. - columns = ["int64_col", "float64_col"] - dataframe_bf = scalars_df_index[columns] - dataframe_pd = scalars_pandas_df_index[columns] - - cond_bf = dataframe_bf > 0 - cond_pd = dataframe_pd > 0 - other_bf = 10 - other_pd = 10 - - bf_result = dataframe_bf.where(cond_bf, other_bf).to_pandas() - pd_result = dataframe_pd.where(cond_pd, other_pd) - pandas.testing.assert_frame_equal(bf_result, pd_result) - - -def test_where_dataframe_cond_dataframe_other( - scalars_df_index, scalars_pandas_df_index -): - # Condition is a dataframe, other is a dataframe. - columns = ["int64_col", "float64_col"] - dataframe_bf = scalars_df_index[columns] - dataframe_pd = scalars_pandas_df_index[columns] - - cond_bf = dataframe_bf > 0 - cond_pd = dataframe_pd > 0 - other_bf = dataframe_bf * 2 - other_pd = dataframe_pd * 2 - - bf_result = dataframe_bf.where(cond_bf, other_bf).to_pandas() - pd_result = dataframe_pd.where(cond_pd, other_pd) - pandas.testing.assert_frame_equal(bf_result, pd_result) - - -def test_where_callable_cond_constant_other(scalars_df_index, scalars_pandas_df_index): - # Condition is callable, other is a constant. - columns = ["int64_col", "float64_col"] - dataframe_bf = scalars_df_index[columns] - dataframe_pd = scalars_pandas_df_index[columns] - - other = 10 - - bf_result = dataframe_bf.where(lambda x: x > 0, other).to_pandas() - pd_result = dataframe_pd.where(lambda x: x > 0, other) - pandas.testing.assert_frame_equal(bf_result, pd_result) - - -def test_where_dataframe_cond_callable_other(scalars_df_index, scalars_pandas_df_index): - # Condition is a dataframe, other is callable. - columns = ["int64_col", "float64_col"] - dataframe_bf = scalars_df_index[columns] - dataframe_pd = scalars_pandas_df_index[columns] - - cond_bf = dataframe_bf > 0 - cond_pd = dataframe_pd > 0 - - def func(x): - return x * 2 - - bf_result = dataframe_bf.where(cond_bf, func).to_pandas() - pd_result = dataframe_pd.where(cond_pd, func) - pandas.testing.assert_frame_equal(bf_result, pd_result) - - -def test_where_callable_cond_callable_other(scalars_df_index, scalars_pandas_df_index): - # Condition is callable, other is callable too. - columns = ["int64_col", "float64_col"] - dataframe_bf = scalars_df_index[columns] - dataframe_pd = scalars_pandas_df_index[columns] - - def func(x): - return x["int64_col"] > 0 - - bf_result = dataframe_bf.where(func, lambda x: x * 2).to_pandas() - pd_result = dataframe_pd.where(func, lambda x: x * 2) - pandas.testing.assert_frame_equal(bf_result, pd_result) - - -def test_where_series_other(scalars_df_index): - # When other is a series, throw an error. - columns = ["int64_col", "float64_col"] - dataframe_bf = scalars_df_index[columns] - - with pytest.raises( - ValueError, - match="Seires is not a supported replacement type!", - ): - dataframe_bf.where(dataframe_bf > 0, dataframe_bf["int64_col"]) - - def test_drop_column(scalars_dfs): scalars_df, scalars_pandas_df = scalars_dfs col_name = "int64_col" df_pandas = scalars_df.drop(columns=col_name).to_pandas() - bigframes.testing.utils.assert_index_equal( + pd.testing.assert_index_equal( df_pandas.columns, scalars_pandas_df.drop(columns=col_name).columns ) @@ -609,7 +216,7 @@ def test_drop_columns(scalars_dfs): scalars_df, scalars_pandas_df = scalars_dfs col_names = ["int64_col", "geography_col", "time_col"] df_pandas = scalars_df.drop(columns=col_names).to_pandas() - bigframes.testing.utils.assert_index_equal( + pd.testing.assert_index_equal( df_pandas.columns, scalars_pandas_df.drop(columns=col_names).columns ) @@ -621,7 +228,7 @@ def test_drop_labels_axis_1(scalars_dfs): pd_result = scalars_pandas_df.drop(labels=labels, axis=1) bf_result = scalars_df.drop(labels=labels, axis=1).to_pandas() - bigframes.testing.utils.assert_frame_equal(pd_result, bf_result) + pd.testing.assert_frame_equal(pd_result, bf_result) def test_drop_with_custom_column_labels(scalars_dfs): @@ -639,113 +246,7 @@ def test_drop_with_custom_column_labels(scalars_dfs): pd_result = scalars_pandas_df.rename(columns=rename_mapping).drop( columns=dropped_columns ) - assert_frame_equal(bf_result, pd_result) - - -def test_df_memory_usage(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - - pd_result = scalars_pandas_df.memory_usage() - bf_result = scalars_df.memory_usage() - - bigframes.testing.utils.assert_series_equal(pd_result, bf_result, rtol=1.5) - - -def test_df_info(scalars_dfs): - expected = ( - "\n" - "Index: 9 entries, 0 to 8\n" - "Data columns (total 14 columns):\n" - " # Column Non-Null Count Dtype\n" - "--- ------------- ---------------- ------------------------------\n" - " 0 bool_col 8 non-null boolean\n" - " 1 bytes_col 6 non-null binary[pyarrow]\n" - " 2 date_col 7 non-null date32[day][pyarrow]\n" - " 3 datetime_col 6 non-null timestamp[us][pyarrow]\n" - " 4 geography_col 4 non-null geometry\n" - " 5 int64_col 8 non-null Int64\n" - " 6 int64_too 9 non-null Int64\n" - " 7 numeric_col 6 non-null decimal128(38, 9)[pyarrow]\n" - " 8 float64_col 7 non-null Float64\n" - " 9 rowindex_2 9 non-null Int64\n" - " 10 string_col 8 non-null string\n" - " 11 time_col 6 non-null time64[us][pyarrow]\n" - " 12 timestamp_col 6 non-null timestamp[us, tz=UTC][pyarrow]\n" - " 13 duration_col 7 non-null duration[us][pyarrow]\n" - "dtypes: Float64(1), Int64(3), binary[pyarrow](1), boolean(1), date32[day][pyarrow](1), decimal128(38, 9)[pyarrow](1), duration[us][pyarrow](1), geometry(1), string(1), time64[us][pyarrow](1), timestamp[us, tz=UTC][pyarrow](1), timestamp[us][pyarrow](1)\n" - "memory usage: 1341 bytes\n" - ) - scalars_df, _ = scalars_dfs - - bf_result = io.StringIO() - scalars_df.info(buf=bf_result) - - assert expected == bf_result.getvalue() - - -def test_df_info_no_rows(session): - expected = ( - "\n" - "Index: 0 entries\n" - "Data columns (total 1 columns):\n" - " # Column Non-Null Count Dtype\n" - "--- -------- ---------------- -------\n" - " 0 col 0 non-null Float64\n" - "dtypes: Float64(1)\n" - "memory usage: 0 bytes\n" - ) - df = session.DataFrame({"col": []}) - - bf_result = io.StringIO() - df.info(buf=bf_result) - - assert expected == bf_result.getvalue() - - -def test_df_info_no_cols(session): - expected = ( - "\n" - "Index: 3 entries, 1 to 3\n" - "Empty DataFrame\n" - ) - df = session.DataFrame({}, index=[1, 2, 3]) - - bf_result = io.StringIO() - df.info(buf=bf_result) - - assert expected == bf_result.getvalue() - - -def test_df_info_no_cols_no_rows(session): - expected = ( - "\nIndex: 0 entries\nEmpty DataFrame\n" - ) - df = session.DataFrame({}) - - bf_result = io.StringIO() - df.info(buf=bf_result) - - assert expected == bf_result.getvalue() - - -@pytest.mark.parametrize( - ("include", "exclude"), - [ - ("Int64", None), - (["int"], None), - ("number", None), - ([pd.Int64Dtype(), pd.BooleanDtype()], None), - (None, [pd.Int64Dtype(), pd.BooleanDtype()]), - ("Int64", ["boolean"]), - ], -) -def test_select_dtypes(scalars_dfs, include, exclude): - scalars_df, scalars_pandas_df = scalars_dfs - - pd_result = scalars_pandas_df.select_dtypes(include=include, exclude=exclude) - bf_result = scalars_df.select_dtypes(include=include, exclude=exclude).to_pandas() - - bigframes.testing.utils.assert_frame_equal(pd_result, bf_result) + assert_pandas_df_equal_ignore_ordering(bf_result, pd_result) def test_drop_index(scalars_dfs): @@ -754,7 +255,7 @@ def test_drop_index(scalars_dfs): pd_result = scalars_pandas_df.drop(index=[4, 1, 2]) bf_result = scalars_df.drop(index=[4, 1, 2]).to_pandas() - bigframes.testing.utils.assert_frame_equal(pd_result, bf_result) + pd.testing.assert_frame_equal(pd_result, bf_result) def test_drop_pandas_index(scalars_dfs): @@ -764,7 +265,7 @@ def test_drop_pandas_index(scalars_dfs): pd_result = scalars_pandas_df.drop(index=drop_index) bf_result = scalars_df.drop(index=drop_index).to_pandas() - bigframes.testing.utils.assert_frame_equal(pd_result, bf_result) + pd.testing.assert_frame_equal(pd_result, bf_result) def test_drop_bigframes_index(scalars_dfs): @@ -775,12 +276,10 @@ def test_drop_bigframes_index(scalars_dfs): pd_result = scalars_pandas_df.drop(index=drop_pandas_index) bf_result = scalars_df.drop(index=drop_index).to_pandas() - bigframes.testing.utils.assert_frame_equal(pd_result, bf_result) + pd.testing.assert_frame_equal(pd_result, bf_result) def test_drop_bigframes_index_with_na(scalars_dfs): - if pd.__version__.startswith("3"): - pytest.skip("Pandas 3.0 doesn't doesn't support drop with pd.NA values") scalars_df, scalars_pandas_df = scalars_dfs scalars_df = scalars_df.copy() scalars_pandas_df = scalars_pandas_df.copy() @@ -792,12 +291,10 @@ def test_drop_bigframes_index_with_na(scalars_dfs): pd_result = scalars_pandas_df.drop(index=drop_pandas_index) # drop_pandas_index) bf_result = scalars_df.drop(index=drop_index).to_pandas() - bigframes.testing.utils.assert_frame_equal(pd_result, bf_result) + pd.testing.assert_frame_equal(pd_result, bf_result) def test_drop_bigframes_multiindex(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 scalars_df = scalars_df.copy() scalars_pandas_df = scalars_pandas_df.copy() @@ -813,7 +310,7 @@ def test_drop_bigframes_multiindex(scalars_dfs): bf_result = scalars_df.drop(index=drop_index).to_pandas() pd_result = scalars_pandas_df.drop(index=drop_pandas_index) - bigframes.testing.utils.assert_frame_equal(pd_result, bf_result) + pd.testing.assert_frame_equal(pd_result, bf_result) def test_drop_labels_axis_0(scalars_dfs): @@ -822,7 +319,7 @@ def test_drop_labels_axis_0(scalars_dfs): pd_result = scalars_pandas_df.drop(labels=[4, 1, 2], axis=0) bf_result = scalars_df.drop(labels=[4, 1, 2], axis=0).to_pandas() - bigframes.testing.utils.assert_frame_equal(pd_result, bf_result) + pd.testing.assert_frame_equal(pd_result, bf_result) def test_drop_index_and_columns(scalars_dfs): @@ -831,77 +328,18 @@ def test_drop_index_and_columns(scalars_dfs): pd_result = scalars_pandas_df.drop(index=[4, 1, 2], columns="int64_col") bf_result = scalars_df.drop(index=[4, 1, 2], columns="int64_col").to_pandas() - bigframes.testing.utils.assert_frame_equal(pd_result, bf_result) + pd.testing.assert_frame_equal(pd_result, bf_result) def test_rename(scalars_dfs): scalars_df, scalars_pandas_df = scalars_dfs col_name_dict = {"bool_col": 1.2345} df_pandas = scalars_df.rename(columns=col_name_dict).to_pandas() - bigframes.testing.utils.assert_index_equal( + pd.testing.assert_index_equal( df_pandas.columns, scalars_pandas_df.rename(columns=col_name_dict).columns ) -def test_df_peek(scalars_dfs_maybe_ordered): - scalars_df, scalars_pandas_df = scalars_dfs_maybe_ordered - - peek_result = scalars_df.peek(n=3, force=False, allow_large_results=True) - - bigframes.testing.utils.assert_index_equal( - scalars_pandas_df.columns, peek_result.columns - ) - assert len(peek_result) == 3 - - -def test_df_peek_with_large_results_not_allowed(scalars_dfs_maybe_ordered): - scalars_df, scalars_pandas_df = scalars_dfs_maybe_ordered - - peek_result = scalars_df.peek(n=3, force=False, allow_large_results=False) - - bigframes.testing.utils.assert_index_equal( - scalars_pandas_df.columns, peek_result.columns - ) - assert len(peek_result) == 3 - - -def test_df_peek_filtered(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - peek_result = scalars_df[scalars_df.int64_col != 0].peek(n=3, force=False) - bigframes.testing.utils.assert_index_equal( - scalars_pandas_df.columns, peek_result.columns - ) - assert len(peek_result) == 3 - - -def test_df_peek_exception(scalars_dfs): - scalars_df, _ = scalars_dfs - - with pytest.raises(ValueError): - # Window ops aren't compatible with efficient peeking - scalars_df[["int64_col", "int64_too"]].cumsum().peek(n=3, force=False) - - -def test_df_peek_force_default(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - peek_result = scalars_df[["int64_col", "int64_too"]].cumsum().peek(n=3) - bigframes.testing.utils.assert_index_equal( - scalars_pandas_df[["int64_col", "int64_too"]].columns, peek_result.columns - ) - assert len(peek_result) == 3 - - -def test_df_peek_reset_index(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - peek_result = ( - scalars_df[["int64_col", "int64_too"]].reset_index(drop=True).peek(n=3) - ) - bigframes.testing.utils.assert_index_equal( - scalars_pandas_df[["int64_col", "int64_too"]].columns, peek_result.columns - ) - assert len(peek_result) == 3 - - def test_repr_w_all_rows(scalars_dfs): scalars_df, scalars_pandas_df = scalars_dfs @@ -911,6 +349,13 @@ def test_repr_w_all_rows(scalars_dfs): scalars_df = scalars_df.drop(columns=["numeric_col"]) scalars_pandas_df = scalars_pandas_df.drop(columns=["numeric_col"]) + if scalars_pandas_df.index.name is None: + # Note: Not quite the same as no index / default index, but hopefully + # simulates it well enough while being consistent enough for string + # comparison to work. + scalars_df = scalars_df.set_index("rowindex", drop=False).sort_index() + scalars_df.index.name = None + # When there are 10 or fewer rows, the outputs should be identical. actual = repr(scalars_df.head(10)) @@ -920,81 +365,15 @@ def test_repr_w_all_rows(scalars_dfs): assert actual == expected -def test_join_repr(scalars_dfs_maybe_ordered): - scalars_df, scalars_pandas_df = scalars_dfs_maybe_ordered - - scalars_df = ( - scalars_df[["int64_col"]] - .join(scalars_df.set_index("int64_col")[["int64_too"]]) - .sort_index() - ) - scalars_pandas_df = ( - scalars_pandas_df[["int64_col"]] - .join(scalars_pandas_df.set_index("int64_col")[["int64_too"]]) - .sort_index() - ) - # Pandas join result index name seems to depend on the index values in a way that bigframes can't match exactly - scalars_pandas_df.index.name = None - - actual = repr(scalars_df) - - with display_options.pandas_repr(bigframes.options.display): - expected = repr(scalars_pandas_df) - - assert actual == expected - - -def test_repr_w_display_options(scalars_dfs, session): - scalars_df, _ = scalars_dfs - # get a pandas df of the expected format - df, _ = scalars_df._block.to_pandas() - pandas_df = df.set_axis(scalars_df._block.column_labels, axis=1) - pandas_df.index.name = scalars_df.index.name - - history_pre = session.execution_history().to_dataframe() - queries_pre = ( - len(history_pre[history_pre["job_type"] == "query"]) - if "job_type" in history_pre.columns - else 0 - ) - - with bigframes.option_context( - "display.max_rows", 10, "display.max_columns", 5, "display.max_colwidth", 10 - ): - # When there are 10 or fewer rows, the outputs should be identical except for the extra note. - actual = scalars_df.head(10).__repr__() - - history_post = session.execution_history().to_dataframe() - queries_post = len(history_post[history_post["job_type"] == "query"]) - - with display_options.pandas_repr(bigframes.options.display): - pandas_repr = pandas_df.head(10).__repr__() - - assert actual == pandas_repr - assert (queries_post - queries_pre) <= 2 - - -def test_mimebundle_html_repr_w_all_rows(scalars_dfs, session): +def test_repr_html_w_all_rows(scalars_dfs): scalars_df, _ = scalars_dfs # get a pandas df of the expected format df, _ = scalars_df._block.to_pandas() pandas_df = df.set_axis(scalars_df._block.column_labels, axis=1) pandas_df.index.name = scalars_df.index.name - history_pre = session.execution_history().to_dataframe() - queries_pre = ( - len(history_pre[history_pre["job_type"] == "query"]) - if "job_type" in history_pre.columns - else 0 - ) - # When there are 10 or fewer rows, the outputs should be identical except for the extra note. - bundle = scalars_df.head(10)._repr_mimebundle_() - actual = bundle["text/html"] - - history_post = session.execution_history().to_dataframe() - queries_post = len(history_post[history_post["job_type"] == "query"]) - + actual = scalars_df.head(10)._repr_html_() with display_options.pandas_repr(bigframes.options.display): pandas_repr = pandas_df.head(10)._repr_html_() @@ -1003,14 +382,13 @@ def test_mimebundle_html_repr_w_all_rows(scalars_dfs, session): + f"[{len(pandas_df.index)} rows x {len(pandas_df.columns)} columns in total]" ) assert actual == expected - assert (queries_post - queries_pre) <= 2 def test_df_column_name_with_space(scalars_dfs): scalars_df, scalars_pandas_df = scalars_dfs col_name_dict = {"bool_col": "bool col"} df_pandas = scalars_df.rename(columns=col_name_dict).to_pandas() - bigframes.testing.utils.assert_index_equal( + pd.testing.assert_index_equal( df_pandas.columns, scalars_pandas_df.rename(columns=col_name_dict).columns ) @@ -1019,7 +397,7 @@ def test_df_column_name_duplicate(scalars_dfs): scalars_df, scalars_pandas_df = scalars_dfs col_name_dict = {"int64_too": "int64_col"} df_pandas = scalars_df.rename(columns=col_name_dict).to_pandas() - bigframes.testing.utils.assert_index_equal( + pd.testing.assert_index_equal( df_pandas.columns, scalars_pandas_df.rename(columns=col_name_dict).columns ) @@ -1030,25 +408,7 @@ def test_get_df_column_name_duplicate(scalars_dfs): bf_result = scalars_df.rename(columns=col_name_dict)["int64_col"].to_pandas() pd_result = scalars_pandas_df.rename(columns=col_name_dict)["int64_col"] - bigframes.testing.utils.assert_index_equal(bf_result.columns, pd_result.columns) - - -@pytest.mark.parametrize( - ("indices", "axis"), - [ - ([1, 3, 5], 0), - ([2, 4, 6], 1), - ([1, -3, -5, -6], "index"), - ([-2, -4, -6], "columns"), - ], -) -def test_take_df(scalars_dfs, indices, axis): - scalars_df, scalars_pandas_df = scalars_dfs - - bf_result = scalars_df.take(indices, axis=axis).to_pandas() - pd_result = scalars_pandas_df.take(indices, axis=axis) - - assert_frame_equal(bf_result, pd_result) + pd.testing.assert_index_equal(bf_result.columns, pd_result.columns) def test_filter_df(scalars_dfs): @@ -1060,79 +420,20 @@ def test_filter_df(scalars_dfs): pd_bool_series = scalars_pandas_df["bool_col"] pd_result = scalars_pandas_df[pd_bool_series] - assert_frame_equal(bf_result, pd_result) - - -def test_read_gbq_direct_to_batches_row_count(unordered_session): - df = unordered_session.read_gbq("bigquery-public-data.usa_names.usa_1910_2013") - iter = df.to_pandas_batches() - assert iter.total_rows == 5552452 - - -def test_df_to_pandas_batches(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - - capped_unfiltered_batches = scalars_df.to_pandas_batches(page_size=2, max_results=6) - bf_bool_series = scalars_df["bool_col"] - filtered_batches = scalars_df[bf_bool_series].to_pandas_batches() - - pd_bool_series = scalars_pandas_df["bool_col"] - pd_result = scalars_pandas_df[pd_bool_series] - - assert 6 == capped_unfiltered_batches.total_rows - assert len(pd_result) == filtered_batches.total_rows - assert_frame_equal(pd.concat(filtered_batches), pd_result) + assert_pandas_df_equal_ignore_ordering(bf_result, pd_result) -@pytest.mark.parametrize( - ("literal", "expected_dtype"), - ( - pytest.param( - 2, - dtypes.INT_DTYPE, - id="INT64", - ), - # ==================================================================== - # NULL values - # - # These are regression tests for b/428999884. It needs to be possible to - # set a column to NULL with a desired type (not just the pandas default - # of float64). - # ==================================================================== - pytest.param(None, dtypes.FLOAT_DTYPE, id="NULL-None"), - pytest.param( - pa.scalar(None, type=pa.int64()), - dtypes.INT_DTYPE, - id="NULL-pyarrow-TIMESTAMP", - ), - pytest.param( - pa.scalar(None, type=pa.timestamp("us", tz="UTC")), - dtypes.TIMESTAMP_DTYPE, - id="NULL-pyarrow-TIMESTAMP", - ), - pytest.param( - pa.scalar(None, type=pa.timestamp("us")), - dtypes.DATETIME_DTYPE, - id="NULL-pyarrow-DATETIME", - ), - ), -) -def test_assign_new_column_w_literal(scalars_dfs, literal, expected_dtype): +def test_assign_new_column(scalars_dfs): scalars_df, scalars_pandas_df = scalars_dfs - df = scalars_df.assign(new_col=literal) + kwargs = {"new_col": 2} + df = scalars_df.assign(**kwargs) bf_result = df.to_pandas() + pd_result = scalars_pandas_df.assign(**kwargs) - new_col_pd = literal - if isinstance(literal, pa.Scalar): - # PyArrow integer scalars aren't yet supported in pandas Int64Dtype. - new_col_pd = literal.as_py() - - # Pandas might not pick the same dtype as BigFrames, but it should at least - # be castable to it. - pd_result = scalars_pandas_df.assign(new_col=new_col_pd) - pd_result["new_col"] = pd_result["new_col"].astype(expected_dtype) + # Convert default pandas dtypes `int64` to match BigQuery DataFrames dtypes. + pd_result["new_col"] = pd_result["new_col"].astype("Int64") - assert_frame_equal(bf_result, pd_result) + assert_pandas_df_equal_ignore_ordering(bf_result, pd_result) def test_assign_new_column_w_loc(scalars_dfs): @@ -1147,29 +448,22 @@ def test_assign_new_column_w_loc(scalars_dfs): # Convert default pandas dtypes `int64` to match BigQuery DataFrames dtypes. pd_result["new_col"] = pd_result["new_col"].astype("Int64") - bigframes.testing.utils.assert_frame_equal(bf_result, pd_result) + pd.testing.assert_frame_equal(bf_result, pd_result) -@pytest.mark.parametrize( - ("scalar",), - [ - (2.1,), - (None,), - ], -) -def test_assign_new_column_w_setitem(scalars_dfs, scalar): +def test_assign_new_column_w_setitem(scalars_dfs): scalars_df, scalars_pandas_df = scalars_dfs bf_df = scalars_df.copy() pd_df = scalars_pandas_df.copy() - bf_df["new_col"] = scalar - pd_df["new_col"] = scalar + bf_df["new_col"] = 2 + pd_df["new_col"] = 2 bf_result = bf_df.to_pandas() pd_result = pd_df - # Convert default pandas dtypes `float64` to match BigQuery DataFrames dtypes. - pd_result["new_col"] = pd_result["new_col"].astype("Float64") + # Convert default pandas dtypes `int64` to match BigQuery DataFrames dtypes. + pd_result["new_col"] = pd_result["new_col"].astype("Int64") - bigframes.testing.utils.assert_frame_equal(bf_result, pd_result) + pd.testing.assert_frame_equal(bf_result, pd_result) def test_assign_new_column_w_setitem_dataframe(scalars_dfs): @@ -1182,7 +476,7 @@ def test_assign_new_column_w_setitem_dataframe(scalars_dfs): # Convert default pandas dtypes `int64` to match BigQuery DataFrames dtypes. pd_df["int64_col"] = pd_df["int64_col"].astype("Int64") - bigframes.testing.utils.assert_frame_equal(bf_df.to_pandas(), pd_df) + pd.testing.assert_frame_equal(bf_df.to_pandas(), pd_df) def test_assign_new_column_w_setitem_dataframe_error(scalars_dfs): @@ -1208,7 +502,7 @@ def test_assign_new_column_w_setitem_list(scalars_dfs): # Convert default pandas dtypes `int64` to match BigQuery DataFrames dtypes. pd_result["new_col"] = pd_result["new_col"].astype("Int64") - bigframes.testing.utils.assert_frame_equal(bf_result, pd_result) + pd.testing.assert_frame_equal(bf_result, pd_result) def test_assign_new_column_w_setitem_list_repeated(scalars_dfs): @@ -1226,7 +520,7 @@ def test_assign_new_column_w_setitem_list_repeated(scalars_dfs): pd_result["new_col"] = pd_result["new_col"].astype("Int64") pd_result["new_col_2"] = pd_result["new_col_2"].astype("Int64") - bigframes.testing.utils.assert_frame_equal(bf_result, pd_result) + pd.testing.assert_frame_equal(bf_result, pd_result) def test_assign_new_column_w_setitem_list_custom_index(scalars_dfs): @@ -1246,7 +540,7 @@ def test_assign_new_column_w_setitem_list_custom_index(scalars_dfs): # Convert default pandas dtypes `int64` to match BigQuery DataFrames dtypes. pd_result["new_col"] = pd_result["new_col"].astype("Int64") - bigframes.testing.utils.assert_frame_equal(bf_result, pd_result) + pd.testing.assert_frame_equal(bf_result, pd_result) def test_assign_new_column_w_setitem_list_error(scalars_dfs): @@ -1260,76 +554,6 @@ def test_assign_new_column_w_setitem_list_error(scalars_dfs): bf_df["new_col"] = [1, 2, 3] -@pytest.mark.parametrize( - ("key", "value"), - [ - pytest.param(["int64_col", "int64_too"], 1, id="scalar_to_existing_column"), - pytest.param( - ["int64_col", "int64_too"], [1, 2], id="sequence_to_existing_column" - ), - pytest.param( - ["int64_col", "new_col"], [1, 2], id="sequence_to_partial_new_column" - ), - pytest.param( - ["new_col", "new_col_too"], [1, 2], id="sequence_to_full_new_column" - ), - pytest.param( - pd.Index(("new_col", "new_col_too")), - [1, 2], - id="sequence_to_full_new_column_as_index", - ), - ], -) -def test_setitem_multicolumn_with_literals(scalars_dfs, key, value): - scalars_df, scalars_pandas_df = scalars_dfs - bf_result = scalars_df.copy() - pd_result = scalars_pandas_df.copy() - - bf_result[key] = value - pd_result[key] = value - - bigframes.testing.utils.assert_frame_equal( - pd_result, bf_result.to_pandas(), check_dtype=False - ) - - -def test_setitem_multicolumn_with_literals_different_lengths_raise_error(scalars_dfs): - scalars_df, _ = scalars_dfs - bf_result = scalars_df.copy() - - with pytest.raises(ValueError): - bf_result[["int64_col", "int64_too"]] = [1] - - -def test_setitem_multicolumn_with_dataframes(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - bf_result = scalars_df.copy() - pd_result = scalars_pandas_df.copy() - - bf_result[["int64_col", "int64_too"]] = bf_result[["int64_too", "int64_col"]] / 2 - pd_result[["int64_col", "int64_too"]] = pd_result[["int64_too", "int64_col"]] / 2 - - bigframes.testing.utils.assert_frame_equal( - pd_result, bf_result.to_pandas(), check_dtype=False - ) - - -def test_setitem_multicolumn_with_dataframes_series_on_rhs_raise_error(scalars_dfs): - scalars_df, _ = scalars_dfs - bf_result = scalars_df.copy() - - with pytest.raises(ValueError): - bf_result[["int64_col", "int64_too"]] = bf_result["int64_col"] / 2 - - -def test_setitem_multicolumn_with_dataframes_different_lengths_raise_error(scalars_dfs): - scalars_df, _ = scalars_dfs - bf_result = scalars_df.copy() - - with pytest.raises(ValueError): - bf_result[["int64_col"]] = bf_result[["int64_col", "int64_too"]] / 2 - - def test_assign_existing_column(scalars_dfs): scalars_df, scalars_pandas_df = scalars_dfs kwargs = {"int64_col": 2} @@ -1340,7 +564,7 @@ def test_assign_existing_column(scalars_dfs): # Convert default pandas dtypes `int64` to match BigQuery DataFrames dtypes. pd_result["int64_col"] = pd_result["int64_col"].astype("Int64") - assert_frame_equal(bf_result, pd_result) + assert_pandas_df_equal_ignore_ordering(bf_result, pd_result) def test_assign_listlike_to_empty_df(session): @@ -1352,15 +576,14 @@ def test_assign_listlike_to_empty_df(session): pd_result["new_col"] = pd_result["new_col"].astype("Int64") pd_result.index = pd_result.index.astype("Int64") - assert_frame_equal(bf_result.to_pandas(), pd_result) + assert_pandas_df_equal_ignore_ordering(bf_result.to_pandas(), pd_result) def test_assign_to_empty_df_multiindex_error(session): empty_df = dataframe.DataFrame(session=session) empty_pandas_df = pd.DataFrame() - - empty_df["empty_col_1"] = typing.cast(series.Series, []) - empty_df["empty_col_2"] = typing.cast(series.Series, []) + empty_df["empty_col_1"] = [] + empty_df["empty_col_2"] = [] empty_pandas_df["empty_col_1"] = [] empty_pandas_df["empty_col_2"] = [] empty_df = empty_df.set_index(["empty_col_1", "empty_col_2"]) @@ -1372,21 +595,14 @@ def test_assign_to_empty_df_multiindex_error(session): empty_pandas_df.assign(new_col=[1, 2, 3, 4, 5, 6, 7, 8, 9]) -@pytest.mark.parametrize( - ("ordered"), - [ - (True), - (False), - ], -) -def test_assign_series(scalars_dfs, ordered): +def test_assign_series(scalars_dfs): scalars_df, scalars_pandas_df = scalars_dfs column_name = "int64_col" df = scalars_df.assign(new_col=scalars_df[column_name]) - bf_result = df.to_pandas(ordered=ordered) + bf_result = df.to_pandas() pd_result = scalars_pandas_df.assign(new_col=scalars_pandas_df[column_name]) - assert_frame_equal(bf_result, pd_result, ignore_order=not ordered) + assert_pandas_df_equal_ignore_ordering(bf_result, pd_result) def test_assign_series_overwrite(scalars_dfs): @@ -1398,7 +614,7 @@ def test_assign_series_overwrite(scalars_dfs): **{column_name: scalars_pandas_df[column_name] + 3} ) - assert_frame_equal(bf_result, pd_result) + assert_pandas_df_equal_ignore_ordering(bf_result, pd_result) def test_assign_sequential(scalars_dfs): @@ -1413,7 +629,7 @@ def test_assign_sequential(scalars_dfs): pd_result["new_col"] = pd_result["new_col"].astype("Int64") pd_result["new_col2"] = pd_result["new_col2"].astype("Int64") - assert_frame_equal(bf_result, pd_result) + assert_pandas_df_equal_ignore_ordering(bf_result, pd_result) # Require an index so that the self-join is consistent each time. @@ -1447,7 +663,7 @@ def test_assign_different_df( new_col=scalars_pandas_df_index[column_name] ) - assert_frame_equal(bf_result, pd_result) + assert_pandas_df_equal_ignore_ordering(bf_result, pd_result) def test_assign_different_df_w_loc( @@ -1466,7 +682,7 @@ def test_assign_different_df_w_loc( # Convert default pandas dtypes `int64` to match BigQuery DataFrames dtypes. pd_result["int64_col"] = pd_result["int64_col"].astype("Int64") - bigframes.testing.utils.assert_frame_equal(bf_result, pd_result) + pd.testing.assert_frame_equal(bf_result, pd_result) def test_assign_different_df_w_setitem( @@ -1485,7 +701,7 @@ def test_assign_different_df_w_setitem( # Convert default pandas dtypes `int64` to match BigQuery DataFrames dtypes. pd_result["int64_col"] = pd_result["int64_col"].astype("Int64") - bigframes.testing.utils.assert_frame_equal(bf_result, pd_result) + pd.testing.assert_frame_equal(bf_result, pd_result) def test_assign_callable_lambda(scalars_dfs): @@ -1498,79 +714,28 @@ def test_assign_callable_lambda(scalars_dfs): # Convert default pandas dtypes `int64` to match BigQuery DataFrames dtypes. pd_result["new_col"] = pd_result["new_col"].astype("Int64") - assert_frame_equal(bf_result, pd_result) + assert_pandas_df_equal_ignore_ordering(bf_result, pd_result) @pytest.mark.parametrize( - ("axis", "how", "ignore_index", "subset"), + ("axis", "how", "ignore_index"), [ - (0, "any", False, None), - (0, "any", True, None), - (0, "all", False, ["bool_col", "time_col"]), - (0, "any", False, ["bool_col", "time_col"]), - (0, "all", False, "time_col"), - (1, "any", False, None), - (1, "all", False, None), + (0, "any", False), + (0, "any", True), + (1, "any", False), + (1, "all", False), ], ) -def test_df_dropna_by_how(scalars_dfs, axis, how, ignore_index, subset): - # TODO: supply a reason why this isn't compatible with pandas 1.x - pytest.importorskip("pandas", minversion="2.0.0") +def test_df_dropna(scalars_dfs, axis, how, ignore_index): + if pd.__version__.startswith("1."): + pytest.skip("ignore_index parameter not supported in pandas 1.x.") scalars_df, scalars_pandas_df = scalars_dfs - df = scalars_df.dropna(axis=axis, how=how, ignore_index=ignore_index, subset=subset) + df = scalars_df.dropna(axis=axis, how=how, ignore_index=ignore_index) bf_result = df.to_pandas() - pd_result = scalars_pandas_df.dropna( - axis=axis, how=how, ignore_index=ignore_index, subset=subset - ) - - # Pandas uses int64 instead of Int64 (nullable) dtype. - pd_result.index = pd_result.index.astype(pd.Int64Dtype()) - pandas.testing.assert_frame_equal(bf_result, pd_result) - - -@pytest.mark.parametrize( - ("axis", "ignore_index", "subset", "thresh"), - [ - (0, False, None, 2), - (0, True, None, 3), - (1, False, None, 2), - ], -) -def test_df_dropna_by_thresh(scalars_dfs, axis, ignore_index, subset, thresh): - """ - Tests that dropna correctly keeps rows/columns with a minimum number - of non-null values. - """ - # 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 + pd_result = scalars_pandas_df.dropna(axis=axis, how=how, ignore_index=ignore_index) - df_result = scalars_df.dropna( - axis=axis, thresh=thresh, ignore_index=ignore_index, subset=subset - ) - pd_result = scalars_pandas_df.dropna( - axis=axis, thresh=thresh, ignore_index=ignore_index, subset=subset - ) - - bf_result = df_result.to_pandas() # Pandas uses int64 instead of Int64 (nullable) dtype. pd_result.index = pd_result.index.astype(pd.Int64Dtype()) - bigframes.testing.utils.assert_frame_equal(bf_result, pd_result) - - -def test_df_dropna_range_columns(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 - scalars_df = scalars_df.copy() - scalars_pandas_df = scalars_pandas_df.copy() - scalars_df.columns = pandas.RangeIndex(0, len(scalars_df.columns)) - scalars_pandas_df.columns = pandas.RangeIndex(0, len(scalars_pandas_df.columns)) - - df = scalars_df.dropna() - bf_result = df.to_pandas() - pd_result = scalars_pandas_df.dropna() - pandas.testing.assert_frame_equal(bf_result, pd_result) @@ -1590,64 +755,13 @@ def test_df_interpolate(scalars_dfs): ) -@pytest.mark.parametrize( - "col, fill_value", - [ - (["int64_col", "float64_col"], 3), - (["string_col"], "A"), - (["datetime_col"], pd.Timestamp("2023-01-01")), - ], -) -def test_df_fillna(scalars_dfs, col, fill_value): - scalars_df, scalars_pandas_df = scalars_dfs - bf_result = scalars_df[col].fillna(fill_value).to_pandas() - pd_result = scalars_pandas_df[col].fillna(fill_value) - - bigframes.testing.utils.assert_frame_equal(bf_result, pd_result, check_dtype=False) - - -def test_df_replace_scalar_scalar(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - bf_result = scalars_df.replace(555.555, 3).to_pandas() - pd_result = scalars_pandas_df.replace(555.555, 3) - - # pandas has narrower result types as they are determined dynamically - bigframes.testing.utils.assert_frame_equal(pd_result, bf_result, check_dtype=False) - - -def test_df_replace_regex_scalar(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - bf_result = scalars_df.replace("^H.l", "Howdy, Planet!", regex=True).to_pandas() - pd_result = scalars_pandas_df.replace("^H.l", "Howdy, Planet!", regex=True) - - bigframes.testing.utils.assert_frame_equal( - pd_result, - bf_result, - ) - - -def test_df_replace_list_scalar(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - bf_result = scalars_df.replace([555.555, 3.2], 3).to_pandas() - pd_result = scalars_pandas_df.replace([555.555, 3.2], 3) - - # pandas has narrower result types as they are determined dynamically - bigframes.testing.utils.assert_frame_equal( - pd_result, - bf_result, - check_dtype=False, - ) - - -def test_df_replace_value_dict(scalars_dfs): +def test_df_fillna(scalars_dfs): scalars_df, scalars_pandas_df = scalars_dfs - bf_result = scalars_df.replace(1, {"int64_col": 100, "int64_too": 200}).to_pandas() - pd_result = scalars_pandas_df.replace(1, {"int64_col": 100, "int64_too": 200}) + df = scalars_df[["int64_col", "float64_col"]].fillna(3) + bf_result = df.to_pandas() + pd_result = scalars_pandas_df[["int64_col", "float64_col"]].fillna(3) - bigframes.testing.utils.assert_frame_equal( - pd_result, - bf_result, - ) + pandas.testing.assert_frame_equal(bf_result, pd_result) def test_df_ffill(scalars_dfs): @@ -1717,31 +831,6 @@ def test_apply_series_scalar_callable( pandas.testing.assert_series_equal(bf_result, pd_result) -def test_df_pipe( - scalars_df_index, - scalars_pandas_df_index, -): - columns = ["int64_too", "int64_col"] - - def foo(x: int, y: int, df): - return (df + x) % y - - bf_result = ( - scalars_df_index[columns] - .pipe((foo, "df"), x=7, y=9) - .pipe(lambda x: x**2) - .to_pandas() - ) - - pd_result = ( - scalars_pandas_df_index[columns] - .pipe((foo, "df"), x=7, y=9) - .pipe(lambda x: x**2) - ) - - pandas.testing.assert_frame_equal(bf_result, pd_result) - - def test_df_keys( scalars_df_index, scalars_pandas_df_index, @@ -1763,10 +852,6 @@ def test_iterrows( scalars_df_index, scalars_pandas_df_index, ): - # TODO: supply a reason why this isn't compatible with pandas 1.x - pytest.importorskip("pandas", minversion="2.0.0") - scalars_df_index = scalars_df_index.add_suffix("_suffix", axis=1) - scalars_pandas_df_index = scalars_pandas_df_index.add_suffix("_suffix", axis=1) for (bf_index, bf_series), (pd_index, pd_series) in zip( scalars_df_index.iterrows(), scalars_pandas_df_index.iterrows() ): @@ -1795,7 +880,7 @@ def test_itertuples(scalars_df_index, index, name): assert bf_tuple == pd_tuple -def test_df_isin_list_w_null(scalars_dfs): +def test_df_isin_list(scalars_dfs): scalars_df, scalars_pandas_df = scalars_dfs values = ["Hello, World!", 55555, 2.51, pd.NA, True] bf_result = ( @@ -1810,21 +895,6 @@ def test_df_isin_list_w_null(scalars_dfs): pandas.testing.assert_frame_equal(bf_result, pd_result.astype("boolean")) -def test_df_isin_list_wo_null(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - values = ["Hello, World!", 55555, 2.51, True] - bf_result = ( - scalars_df[["int64_col", "float64_col", "string_col", "bool_col"]] - .isin(values) - .to_pandas() - ) - pd_result = scalars_pandas_df[ - ["int64_col", "float64_col", "string_col", "bool_col"] - ].isin(values) - - pandas.testing.assert_frame_equal(bf_result, pd_result.astype("boolean")) - - def test_df_isin_dict(scalars_dfs): scalars_df, scalars_pandas_df = scalars_dfs values = { @@ -1844,28 +914,6 @@ def test_df_isin_dict(scalars_dfs): pandas.testing.assert_frame_equal(bf_result, pd_result.astype("boolean")) -def test_df_cross_merge(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - left_columns = ["int64_col", "float64_col", "rowindex_2"] - right_columns = ["int64_col", "bool_col", "string_col", "rowindex_2"] - - left = scalars_df[left_columns] - # Offset the rows somewhat so that outer join can have an effect. - right = scalars_df[right_columns].assign(rowindex_2=scalars_df["rowindex_2"] + 2) - - bf_result = left.merge(right, "cross").to_pandas() - - pd_result = scalars_pandas_df[left_columns].merge( - scalars_pandas_df[right_columns].assign( - rowindex_2=scalars_pandas_df["rowindex_2"] + 2 - ), - "cross", - ) - bigframes.testing.utils.assert_frame_equal( - bf_result, pd_result, check_index_type=False - ) - - @pytest.mark.parametrize( ("merge_how",), [ @@ -1897,7 +945,7 @@ def test_df_merge(scalars_dfs, merge_how): sort=True, ) - assert_frame_equal(bf_result, pd_result, ignore_order=True, check_index_type=False) + assert_pandas_df_equal_ignore_ordering(bf_result, pd_result) @pytest.mark.parametrize( @@ -1930,7 +978,7 @@ def test_df_merge_multi_key(scalars_dfs, left_on, right_on): sort=True, ) - assert_frame_equal(bf_result, pd_result, ignore_order=True, check_index_type=False) + assert_pandas_df_equal_ignore_ordering(bf_result, pd_result) @pytest.mark.parametrize( @@ -1960,7 +1008,7 @@ def test_merge_custom_col_name(scalars_dfs, merge_how): pandas_right_df = scalars_pandas_df[right_columns] pd_result = pandas_left_df.merge(pandas_right_df, merge_how, on, sort=True) - assert_frame_equal(bf_result, pd_result, ignore_order=True, check_index_type=False) + assert_pandas_df_equal_ignore_ordering(bf_result, pd_result) @pytest.mark.parametrize( @@ -1993,79 +1041,37 @@ def test_merge_left_on_right_on(scalars_dfs, merge_how): sort=True, ) - assert_frame_equal(bf_result, pd_result, ignore_order=True, check_index_type=False) - - -def test_self_merge_self_w_on_args(): - data = { - "A": pd.Series([1, 2, 3], dtype="Int64"), - "B": pd.Series([1, 2, 3], dtype="Int64"), - "C": pd.Series([100, 200, 300], dtype="Int64"), - "D": pd.Series(["alpha", "beta", "gamma"], dtype="string[pyarrow]"), - } - df = pd.DataFrame(data) - - df1 = df[["A", "C"]] - df2 = df[["B", "C", "D"]] - pd_result = df1.merge(df2, left_on=["A", "C"], right_on=["B", "C"], how="inner") - - bf_df = bpd.DataFrame(data) - - bf_df1 = bf_df[["A", "C"]] - bf_df2 = bf_df[["B", "C", "D"]] - bf_result = bf_df1.merge( - bf_df2, left_on=["A", "C"], right_on=["B", "C"], how="inner" - ).to_pandas() - bigframes.testing.utils.assert_frame_equal( - bf_result, pd_result, check_index_type=False - ) - - -@pytest.mark.parametrize( - ("decimals",), - [ - (2,), - ({"float64_col": 0, "bool_col": 1, "int64_too": -3},), - ({},), - ], -) -def test_dataframe_round(scalars_dfs, decimals): - if pd.__version__.startswith("1."): - pytest.skip("Rounding doesn't work as expected in pandas 1.x") - scalars_df, scalars_pandas_df = scalars_dfs - - bf_result = scalars_df.round(decimals).to_pandas() - pd_result = scalars_pandas_df.round(decimals) - - assert_frame_equal(bf_result, pd_result) + assert_pandas_df_equal_ignore_ordering(bf_result, pd_result) def test_get_dtypes(scalars_df_default_index): dtypes = scalars_df_default_index.dtypes - dtypes_dict: Dict[str, bigframes.dtypes.Dtype] = { - "bool_col": pd.BooleanDtype(), - "bytes_col": pd.ArrowDtype(pa.binary()), - "date_col": pd.ArrowDtype(pa.date32()), - "datetime_col": pd.ArrowDtype(pa.timestamp("us")), - "geography_col": gpd.array.GeometryDtype(), - "int64_col": pd.Int64Dtype(), - "int64_too": pd.Int64Dtype(), - "numeric_col": pd.ArrowDtype(pa.decimal128(38, 9)), - "float64_col": pd.Float64Dtype(), - "rowindex": pd.Int64Dtype(), - "rowindex_2": pd.Int64Dtype(), - "string_col": pd.StringDtype(storage="pyarrow"), - "time_col": pd.ArrowDtype(pa.time64("us")), - "timestamp_col": pd.ArrowDtype(pa.timestamp("us", tz="UTC")), - "duration_col": pd.ArrowDtype(pa.duration("us")), - } - bigframes.testing.utils.assert_series_equal( + pd.testing.assert_series_equal( dtypes, - pd.Series(dtypes_dict), + pd.Series( + { + "bool_col": pd.BooleanDtype(), + "bytes_col": np.dtype("O"), + "date_col": pd.ArrowDtype(pa.date32()), + "datetime_col": pd.ArrowDtype(pa.timestamp("us")), + "geography_col": gpd.array.GeometryDtype(), + "int64_col": pd.Int64Dtype(), + "int64_too": pd.Int64Dtype(), + "numeric_col": np.dtype("O"), + "float64_col": pd.Float64Dtype(), + "rowindex": pd.Int64Dtype(), + "rowindex_2": pd.Int64Dtype(), + "string_col": pd.StringDtype(storage="pyarrow"), + "time_col": pd.ArrowDtype(pa.time64("us")), + "timestamp_col": pd.ArrowDtype(pa.timestamp("us", tz="UTC")), + } + ), ) -def test_get_dtypes_array_struct_query(session): +def test_get_dtypes_array_struct(session): + """We may upgrade struct and array to proper arrow dtype support in future. For now, + we return python objects""" df = session.read_gbq( """SELECT [1, 3, 2] AS array_column, @@ -2075,11 +1081,11 @@ def test_get_dtypes_array_struct_query(session): ) dtypes = df.dtypes - bigframes.testing.utils.assert_series_equal( + pd.testing.assert_series_equal( dtypes, pd.Series( { - "array_column": pd.ArrowDtype(pa.list_(pa.int64())), + "array_column": np.dtype("O"), "struct_column": pd.ArrowDtype( pa.struct( [ @@ -2093,58 +1099,6 @@ def test_get_dtypes_array_struct_query(session): ) -def test_get_dtypes_array_struct_table(nested_df): - dtypes = nested_df.dtypes - bigframes.testing.utils.assert_series_equal( - dtypes, - pd.Series( - { - "customer_id": pd.StringDtype(storage="pyarrow"), - "day": pd.ArrowDtype(pa.date32()), - "flag": pd.Int64Dtype(), - "label": pd.ArrowDtype( - pa.struct( - [ - ("key", pa.string()), - ("value", pa.string()), - ] - ), - ), - "event_sequence": pd.ArrowDtype( - pa.list_( - pa.struct( - [ - pa.field( - "data", - pa.list_( - pa.struct( - [ - ("value", pa.float64()), - ("key", pa.string()), - ], - ), - ), - nullable=False, - ), - ("timestamp", pa.timestamp("us", "UTC")), - ("category", pa.string()), - ] - ), - ), - ), - "address": pd.ArrowDtype( - pa.struct( - [ - ("street", pa.string()), - ("city", pa.string()), - ] - ), - ), - } - ), - ) - - def test_shape(scalars_dfs): scalars_df, scalars_pandas_df = scalars_dfs bf_result = scalars_df.shape @@ -2153,30 +1107,6 @@ def test_shape(scalars_dfs): assert bf_result == pd_result -@pytest.mark.parametrize( - "reference_table, test_table", - [ - ( - "bigframes-dev.bigframes_tests_sys.base_table", - "bigframes-dev.bigframes_tests_sys.base_table_mat_view", - ), - ( - "bigframes-dev.bigframes_tests_sys.base_table", - "bigframes-dev.bigframes_tests_sys.base_table_view", - ), - ( - "bigframes-dev.bigframes_tests_sys.csv_native_table", - "bigframes-dev.bigframes_tests_sys.csv_external_table", - ), - ], -) -def test_view_and_external_table_shape(session, reference_table, test_table): - reference_df = session.read_gbq(reference_table) - test_df = session.read_gbq(test_table) - - assert test_df.shape == reference_df.shape - - def test_len(scalars_dfs): scalars_df, scalars_pandas_df = scalars_dfs bf_result = len(scalars_df) @@ -2185,30 +1115,6 @@ def test_len(scalars_dfs): assert bf_result == pd_result -@pytest.mark.parametrize( - ("n_rows",), - [ - (50,), - (10000,), - ], -) -@pytest.mark.parametrize( - "write_engine", - # TODO(b/502298527): Reenable bigquery_write test - ["bigquery_load", "bigquery_streaming"], -) -def test_df_len_local(session, n_rows, write_engine): - assert ( - len( - session.read_pandas( - pd.DataFrame(np.random.randint(1, 7, n_rows), columns=["one"]), - write_engine=write_engine, - ) - ) - == n_rows - ) - - def test_size(scalars_dfs): scalars_df, scalars_pandas_df = scalars_dfs bf_result = scalars_df.size @@ -2286,52 +1192,6 @@ def test_reset_index(scalars_df_index, scalars_pandas_df_index, drop): pandas.testing.assert_frame_equal(bf_result, pd_result) -def test_reset_index_allow_duplicates(scalars_df_index, scalars_pandas_df_index): - scalars_df_index = scalars_df_index.copy() - scalars_df_index.index.name = "int64_col" - df = scalars_df_index.reset_index(allow_duplicates=True, drop=False) - assert df.index.name is None - - bf_result = df.to_pandas() - - scalars_pandas_df_index = scalars_pandas_df_index.copy() - scalars_pandas_df_index.index.name = "int64_col" - pd_result = scalars_pandas_df_index.reset_index(allow_duplicates=True, drop=False) - - # Pandas uses int64 instead of Int64 (nullable) dtype. - pd_result.index = pd_result.index.astype(pd.Int64Dtype()) - - # reset_index should maintain the original ordering. - pandas.testing.assert_frame_equal(bf_result, pd_result) - - -def test_reset_index_duplicates_error(scalars_df_index): - scalars_df_index = scalars_df_index.copy() - scalars_df_index.index.name = "int64_col" - with pytest.raises(ValueError): - scalars_df_index.reset_index(allow_duplicates=False, drop=False) - - -@pytest.mark.parametrize( - ("drop",), - ((True,), (False,)), -) -def test_reset_index_inplace(scalars_df_index, scalars_pandas_df_index, drop): - df = scalars_df_index.copy() - df.reset_index(drop=drop, inplace=True) - assert df.index.name is None - - bf_result = df.to_pandas() - pd_result = scalars_pandas_df_index.copy() - pd_result.reset_index(drop=drop, inplace=True) - - # Pandas uses int64 instead of Int64 (nullable) dtype. - pd_result.index = pd_result.index.astype(pd.Int64Dtype()) - - # reset_index should maintain the original ordering. - pandas.testing.assert_frame_equal(bf_result, pd_result) - - def test_reset_index_then_filter( scalars_df_index, scalars_pandas_df_index, @@ -2477,78 +1337,25 @@ def test_set_index_key_error(scalars_dfs): ("na_position",), (("first",), ("last",)), ) -@pytest.mark.parametrize( - ("axis",), - ((0,), ("columns",)), -) -def test_sort_index(scalars_dfs, ascending, na_position, axis): +def test_sort_index(scalars_dfs, ascending, na_position): index_column = "int64_col" scalars_df, scalars_pandas_df = scalars_dfs df = scalars_df.set_index(index_column) - bf_result = df.sort_index( - ascending=ascending, na_position=na_position, axis=axis - ).to_pandas() + bf_result = df.sort_index(ascending=ascending, na_position=na_position).to_pandas() pd_result = scalars_pandas_df.set_index(index_column).sort_index( - ascending=ascending, na_position=na_position, axis=axis + ascending=ascending, na_position=na_position ) pandas.testing.assert_frame_equal(bf_result, pd_result) -def test_dataframe_sort_index_inplace(scalars_dfs): - index_column = "int64_col" +def test_df_abs(scalars_dfs): scalars_df, scalars_pandas_df = scalars_dfs - df = scalars_df.copy().set_index(index_column) - df.sort_index(ascending=False, inplace=True) - bf_result = df.to_pandas() - - pd_result = scalars_pandas_df.set_index(index_column).sort_index(ascending=False) - pandas.testing.assert_frame_equal(bf_result, pd_result) - - -def test_df_abs(scalars_dfs_maybe_ordered): - scalars_df, scalars_pandas_df = scalars_dfs_maybe_ordered columns = ["int64_col", "int64_too", "float64_col"] - bf_result = scalars_df[columns].abs() + bf_result = scalars_df[columns].abs().to_pandas() pd_result = scalars_pandas_df[columns].abs() - assert_dfs_equivalent(pd_result, bf_result) - - -def test_df_pos(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - bf_result = (+scalars_df[["int64_col", "numeric_col"]]).to_pandas() - pd_result = +scalars_pandas_df[["int64_col", "numeric_col"]] - - assert_frame_equal(pd_result, bf_result) - - -def test_df_neg(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - bf_result = (-scalars_df[["int64_col", "numeric_col"]]).to_pandas() - pd_result = -scalars_pandas_df[["int64_col", "numeric_col"]] - - assert_frame_equal(pd_result, bf_result) - - -def test_df__abs__(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - bf_result = ( - abs(scalars_df[["int64_col", "numeric_col", "float64_col"]]) - ).to_pandas() - pd_result = abs(scalars_pandas_df[["int64_col", "numeric_col", "float64_col"]]) - - assert_frame_equal(pd_result, bf_result) - - -def test_df_invert(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - columns = ["int64_col", "bool_col"] - - bf_result = (~scalars_df[columns]).to_pandas() - pd_result = ~scalars_pandas_df[columns] - - assert_frame_equal(bf_result, pd_result) + assert_pandas_df_equal_ignore_ordering(bf_result, pd_result) def test_df_isnull(scalars_dfs): @@ -2565,7 +1372,7 @@ def test_df_isnull(scalars_dfs): pd_result["string_col"] = pd_result["string_col"].astype(pd.BooleanDtype()) pd_result["bool_col"] = pd_result["bool_col"].astype(pd.BooleanDtype()) - assert_frame_equal(bf_result, pd_result) + assert_pandas_df_equal_ignore_ordering(bf_result, pd_result) def test_df_notnull(scalars_dfs): @@ -2582,7 +1389,7 @@ def test_df_notnull(scalars_dfs): pd_result["string_col"] = pd_result["string_col"].astype(pd.BooleanDtype()) pd_result["bool_col"] = pd_result["bool_col"].astype(pd.BooleanDtype()) - assert_frame_equal(bf_result, pd_result) + assert_pandas_df_equal_ignore_ordering(bf_result, pd_result) @pytest.mark.parametrize( @@ -2634,7 +1441,7 @@ def test_combine( ) # Some dtype inconsistency for all-NULL columns - bigframes.testing.utils.assert_frame_equal(bf_result, pd_result, check_dtype=False) + pd.testing.assert_frame_equal(bf_result, pd_result, check_dtype=False) @pytest.mark.parametrize( @@ -2653,10 +1460,8 @@ def test_combine( def test_df_update(overwrite, filter_func): if pd.__version__.startswith("1."): pytest.skip("dtype handled differently in pandas 1.x.") - - index1: pandas.Index = pandas.Index([1, 2, 3, 4], dtype="Int64") - - index2: pandas.Index = pandas.Index([1, 2, 4, 5], dtype="Int64") + index1 = pandas.Index([1, 2, 3, 4], dtype="Int64") + index2 = pandas.Index([1, 2, 4, 5], dtype="Int64") pd_df1 = pandas.DataFrame( {"a": [1, None, 3, 4], "b": [5, 6, None, 8]}, dtype="Int64", index=index1 ) @@ -2672,7 +1477,7 @@ def test_df_update(overwrite, filter_func): bf_df1.update(bf_df2, overwrite=overwrite, filter_func=filter_func) pd_df1.update(pd_df2, overwrite=overwrite, filter_func=filter_func) - bigframes.testing.utils.assert_frame_equal(bf_df1.to_pandas(), pd_df1) + pd.testing.assert_frame_equal(bf_df1.to_pandas(), pd_df1) def test_df_idxmin(): @@ -2684,7 +1489,7 @@ def test_df_idxmin(): bf_result = bf_df.idxmin().to_pandas() pd_result = pd_df.idxmin() - bigframes.testing.utils.assert_series_equal( + pd.testing.assert_series_equal( bf_result, pd_result, check_index_type=False, check_dtype=False ) @@ -2698,7 +1503,7 @@ def test_df_idxmax(): bf_result = bf_df.idxmax().to_pandas() pd_result = pd_df.idxmax() - bigframes.testing.utils.assert_series_equal( + pd.testing.assert_series_equal( bf_result, pd_result, check_index_type=False, check_dtype=False ) @@ -2716,9 +1521,8 @@ def test_df_idxmax(): ], ) def test_df_align(join, axis): - index1: pandas.Index = pandas.Index([1, 2, 3, 4], dtype="Int64") - - index2: pandas.Index = pandas.Index([1, 2, 4, 5], dtype="Int64") + index1 = pandas.Index([1, 2, 3, 4], dtype="Int64") + index2 = pandas.Index([1, 2, 4, 5], dtype="Int64") pd_df1 = pandas.DataFrame( {"a": [1, None, 3, 4], "b": [5, 6, None, 8]}, dtype="Int64", index=index1 ) @@ -2735,15 +1539,8 @@ def test_df_align(join, axis): pd_result1, pd_result2 = pd_df1.align(pd_df2, join=join, axis=axis) # Don't check dtype as pandas does unnecessary float conversion - assert isinstance(bf_result1, dataframe.DataFrame) and isinstance( - bf_result2, dataframe.DataFrame - ) - bigframes.testing.utils.assert_frame_equal( - bf_result1.to_pandas(), pd_result1, check_dtype=False - ) - bigframes.testing.utils.assert_frame_equal( - bf_result2.to_pandas(), pd_result2, check_dtype=False - ) + pd.testing.assert_frame_equal(bf_result1.to_pandas(), pd_result1, check_dtype=False) + pd.testing.assert_frame_equal(bf_result2.to_pandas(), pd_result2, check_dtype=False) def test_combine_first( @@ -2768,153 +1565,7 @@ def test_combine_first( pd_result = pd_df_a.combine_first(pd_df_b) # Some dtype inconsistency for all-NULL columns - bigframes.testing.utils.assert_frame_equal(bf_result, pd_result, check_dtype=False) - - -@pytest.mark.parametrize( - ("columns", "numeric_only"), - [ - (["bool_col", "int64_col", "float64_col"], True), - (["bool_col", "int64_col", "float64_col"], False), - (["bool_col", "int64_col", "float64_col", "string_col"], True), - pytest.param( - ["bool_col", "int64_col", "float64_col", "string_col"], - False, - marks=pytest.mark.xfail( - raises=NotImplementedError, - ), - ), - ], -) -def test_df_corr_w_numeric_only(scalars_dfs_maybe_ordered, columns, numeric_only): - scalars_df, scalars_pandas_df = scalars_dfs_maybe_ordered - - bf_result = scalars_df[columns].corr(numeric_only=numeric_only).to_pandas() - pd_result = scalars_pandas_df[columns].corr(numeric_only=numeric_only) - - # BigFrames and Pandas differ in their data type handling: - # - Column types: BigFrames uses Float64, Pandas uses float64. - # - Index types: BigFrames uses strign, Pandas uses object. - bigframes.testing.utils.assert_index_equal(bf_result.columns, pd_result.columns) - # Only check row order in ordered mode. - bigframes.testing.utils.assert_frame_equal( - bf_result, - pd_result, - check_dtype=False, - check_index_type=False, - check_like=~scalars_df._block.session._strictly_ordered, - ) - - -def test_df_corr_w_invalid_parameters(scalars_dfs): - columns = ["int64_too", "int64_col", "float64_col"] - scalars_df, _ = scalars_dfs - - with pytest.raises(NotImplementedError): - scalars_df[columns].corr(method="kendall") - - with pytest.raises(NotImplementedError): - scalars_df[columns].corr(min_periods=1) - - -@pytest.mark.parametrize( - ("columns", "numeric_only"), - [ - (["bool_col", "int64_col", "float64_col"], True), - (["bool_col", "int64_col", "float64_col"], False), - (["bool_col", "int64_col", "float64_col", "string_col"], True), - pytest.param( - ["bool_col", "int64_col", "float64_col", "string_col"], - False, - marks=pytest.mark.xfail( - raises=NotImplementedError, - ), - ), - ], -) -def test_cov_w_numeric_only(scalars_dfs_maybe_ordered, columns, numeric_only): - scalars_df, scalars_pandas_df = scalars_dfs_maybe_ordered - bf_result = scalars_df[columns].cov(numeric_only=numeric_only).to_pandas() - pd_result = scalars_pandas_df[columns].cov(numeric_only=numeric_only) - # BigFrames and Pandas differ in their data type handling: - # - Column types: BigFrames uses Float64, Pandas uses float64. - # - Index types: BigFrames uses strign, Pandas uses object. - bigframes.testing.utils.assert_index_equal(bf_result.columns, pd_result.columns) - # Only check row order in ordered mode. - bigframes.testing.utils.assert_frame_equal( - bf_result, - pd_result, - check_dtype=False, - check_index_type=False, - check_like=~scalars_df._block.session._strictly_ordered, - ) - - -def test_df_corrwith_df(scalars_dfs_maybe_ordered): - scalars_df, scalars_pandas_df = scalars_dfs_maybe_ordered - - l_cols = ["int64_col", "float64_col", "int64_too"] - r_cols = ["int64_too", "float64_col"] - - bf_result = scalars_df[l_cols].corrwith(scalars_df[r_cols]).to_pandas() - pd_result = scalars_pandas_df[l_cols].corrwith(scalars_pandas_df[r_cols]) - - # BigFrames and Pandas differ in their data type handling: - # - Column types: BigFrames uses Float64, Pandas uses float64. - # - Index types: BigFrames uses strign, Pandas uses object. - bigframes.testing.utils.assert_series_equal( - bf_result, pd_result, check_dtype=False, check_index_type=False - ) - - -def test_df_corrwith_df_numeric_only(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - - l_cols = ["int64_col", "float64_col", "int64_too", "string_col"] - r_cols = ["int64_too", "float64_col", "bool_col"] - - bf_result = ( - scalars_df[l_cols].corrwith(scalars_df[r_cols], numeric_only=True).to_pandas() - ) - pd_result = scalars_pandas_df[l_cols].corrwith( - scalars_pandas_df[r_cols], numeric_only=True - ) - - # BigFrames and Pandas differ in their data type handling: - # - Column types: BigFrames uses Float64, Pandas uses float64. - # - Index types: BigFrames uses strign, Pandas uses object. - bigframes.testing.utils.assert_series_equal( - bf_result, pd_result, check_dtype=False, check_index_type=False - ) - - -def test_df_corrwith_df_non_numeric_error(scalars_dfs): - scalars_df, _ = scalars_dfs - - l_cols = ["int64_col", "float64_col", "int64_too", "string_col"] - r_cols = ["int64_too", "float64_col", "bool_col"] - - with pytest.raises(NotImplementedError): - scalars_df[l_cols].corrwith(scalars_df[r_cols], numeric_only=False) - - -def test_df_corrwith_series(scalars_dfs_maybe_ordered): - # 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_maybe_ordered - - l_cols = ["int64_col", "float64_col", "int64_too"] - r_col = "float64_col" - - bf_result = scalars_df[l_cols].corrwith(scalars_df[r_col]).to_pandas() - pd_result = scalars_pandas_df[l_cols].corrwith(scalars_pandas_df[r_col]) - - # BigFrames and Pandas differ in their data type handling: - # - Column types: BigFrames uses Float64, Pandas uses float64. - # - Index types: BigFrames uses strign, Pandas uses object. - bigframes.testing.utils.assert_series_equal( - bf_result, pd_result, check_dtype=False, check_index_type=False - ) + pd.testing.assert_frame_equal(bf_result, pd_result, check_dtype=False) @pytest.mark.parametrize( @@ -2958,23 +1609,7 @@ def test_scalar_binop(scalars_dfs, op, other_scalar, reverse_operands): bf_result = maybe_reversed_op(scalars_df[columns], other_scalar).to_pandas() pd_result = maybe_reversed_op(scalars_pandas_df[columns], other_scalar) - assert_frame_equal(bf_result, pd_result) - - -def test_dataframe_string_radd_const(scalars_dfs): - pytest.importorskip( - "pandas", - minversion="2.0.0", - reason="PyArrow string addition requires pandas 2.0+", - ) - - scalars_df, scalars_pandas_df = scalars_dfs - columns = ["string_col", "string_col"] - - bf_result = ("prefix" + scalars_df[columns]).to_pandas() - pd_result = "prefix" + scalars_pandas_df[columns] - - assert_frame_equal(bf_result, pd_result) + assert_pandas_df_equal_ignore_ordering(bf_result, pd_result) @pytest.mark.parametrize(("other_scalar"), [1, -2]) @@ -2986,13 +1621,13 @@ def test_mod(scalars_dfs, other_scalar): bf_result = (scalars_df[["int64_col", "int64_too"]] % other_scalar).to_pandas() pd_result = scalars_pandas_df[["int64_col", "int64_too"]] % other_scalar - assert_frame_equal(bf_result, pd_result) + assert_pandas_df_equal_ignore_ordering(bf_result, pd_result) def test_scalar_binop_str_exception(scalars_dfs): scalars_df, _ = scalars_dfs columns = ["string_col"] - with pytest.raises(TypeError, match="Cannot add dtypes"): + with pytest.raises(TypeError): (scalars_df[columns] + 1).to_pandas() @@ -3042,116 +1677,7 @@ def test_series_binop_axis_index( bf_result = op(scalars_df[df_columns], scalars_df[series_column]).to_pandas() pd_result = op(scalars_pandas_df[df_columns], scalars_pandas_df[series_column]) - assert_frame_equal(bf_result, pd_result) - - -@pytest.mark.parametrize( - ("input"), - [ - ((1000, 2000, 3000)), - (pd.Index([1000, 2000, 3000])), - (pd.Series((1000, 2000), index=["int64_too", "float64_col"])), - ], - ids=[ - "tuple", - "pd_index", - "pd_series", - ], -) -def test_listlike_binop_axis_1_in_memory_data(scalars_dfs, input): - # 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 - - df_columns = ["int64_col", "float64_col", "int64_too"] - - bf_result = scalars_df[df_columns].add(input, axis=1).to_pandas() - if hasattr(input, "to_pandas"): - input = input.to_pandas() - pd_result = scalars_pandas_df[df_columns].add(input, axis=1) - - assert_frame_equal(bf_result, pd_result, check_dtype=False) - - -def test_df_reverse_binop_pandas(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 - - pd_series = pd.Series([100, 200, 300]) - - df_columns = ["int64_col", "float64_col", "int64_too"] - - bf_result = pd_series + scalars_df[df_columns].to_pandas() - pd_result = pd_series + scalars_pandas_df[df_columns] - - assert_frame_equal(bf_result, pd_result, check_dtype=False) - - -def test_listlike_binop_axis_1_bf_index(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - - df_columns = ["int64_col", "float64_col", "int64_too"] - - bf_result = ( - scalars_df[df_columns] - .add(bf_indexes.Index([1000, 2000, 3000]), axis=1) - .to_pandas() - ) - pd_result = scalars_pandas_df[df_columns].add(pd.Index([1000, 2000, 3000]), axis=1) - - assert_frame_equal(bf_result, pd_result, check_dtype=False) - - -def test_binop_with_self_aggregate(scalars_dfs_maybe_ordered): - scalars_df, scalars_pandas_df = scalars_dfs_maybe_ordered - - df_columns = ["int64_col", "float64_col", "int64_too"] - - history_before = scalars_df._session.execution_history().to_dataframe() - queries_before = ( - len(history_before[history_before["job_type"] == "query"]) - if "job_type" in history_before.columns - else 0 - ) - - bf_df = scalars_df[df_columns] - bf_result = (bf_df - bf_df.mean()).to_pandas() - - history_after = scalars_df._session.execution_history().to_dataframe() - queries_after = len(history_after[history_after["job_type"] == "query"]) - - pd_df = scalars_pandas_df[df_columns] - pd_result = pd_df - pd_df.mean() - - assert (queries_after - queries_before) == 1 - assert_frame_equal(bf_result, pd_result, check_dtype=False) - - -def test_binop_with_self_aggregate_w_index_reset(scalars_dfs_maybe_ordered): - scalars_df, scalars_pandas_df = scalars_dfs_maybe_ordered - - df_columns = ["int64_col", "float64_col", "int64_too"] - - history_before = scalars_df._session.execution_history().to_dataframe() - queries_before = ( - len(history_before[history_before["job_type"] == "query"]) - if "job_type" in history_before.columns - else 0 - ) - - bf_df = scalars_df[df_columns].reset_index(drop=True) - bf_result = (bf_df - bf_df.mean()).to_pandas() - - history_after = scalars_df._session.execution_history().to_dataframe() - queries_after = len(history_after[history_after["job_type"] == "query"]) - - pd_df = scalars_pandas_df[df_columns].reset_index(drop=True) - pd_result = pd_df - pd_df.mean() - - assert (queries_after - queries_before) == 1 - pd_result.index = pd_result.index.astype("Int64") - assert_frame_equal(bf_result, pd_result, check_dtype=False, check_index_type=False) + assert_pandas_df_equal_ignore_ordering(bf_result, pd_result) @pytest.mark.parametrize( @@ -3193,19 +1719,12 @@ def test_binop_df_df_binary_op( pd_result = pd_df_a - pd_df_b # Some dtype inconsistency for all-NULL columns - bigframes.testing.utils.assert_frame_equal(bf_result, pd_result, check_dtype=False) + pd.testing.assert_frame_equal(bf_result, pd_result, check_dtype=False) # Differnt table will only work for explicit index, since default index orders are arbitrary. -@pytest.mark.parametrize( - ("ordered"), - [ - (True), - (False), - ], -) def test_series_binop_add_different_table( - scalars_df_index, scalars_pandas_df_index, scalars_df_2_index, ordered + scalars_df_index, scalars_pandas_df_index, scalars_df_2_index ): df_columns = ["int64_col", "float64_col"] series_column = "int64_too" @@ -3213,61 +1732,39 @@ def test_series_binop_add_different_table( bf_result = ( scalars_df_index[df_columns] .add(scalars_df_2_index[series_column], axis="index") - .to_pandas(ordered=ordered) + .to_pandas() ) pd_result = scalars_pandas_df_index[df_columns].add( scalars_pandas_df_index[series_column], axis="index" ) - assert_frame_equal(bf_result, pd_result, ignore_order=not ordered) + assert_pandas_df_equal_ignore_ordering(bf_result, pd_result) # TODO(garrettwu): Test series binop with different index all_joins = pytest.mark.parametrize( ("how",), - (("outer",), ("left",), ("right",), ("inner",), ("cross",)), + ( + ("outer",), + ("left",), + ("right",), + ("inner",), + ), ) @all_joins -def test_join_same_table(scalars_dfs_maybe_ordered, how): - bf_df, pd_df = scalars_dfs_maybe_ordered +def test_join_same_table(scalars_dfs, how): + bf_df, pd_df = scalars_dfs bf_df_a = bf_df.set_index("int64_too")[["string_col", "int64_col"]] - bf_df_a = bf_df_a.sort_index() - bf_df_b = bf_df.set_index("int64_too")[["float64_col"]] - bf_df_b = bf_df_b[bf_df_b.float64_col > 0] - bf_df_b = bf_df_b.sort_values("float64_col") - bf_result = bf_df_a.join(bf_df_b, how=how).to_pandas() - - pd_df_a = pd_df.set_index("int64_too")[["string_col", "int64_col"]].sort_index() - pd_df_a = pd_df_a.sort_index() - + pd_df_a = pd_df.set_index("int64_too")[["string_col", "int64_col"]] pd_df_b = pd_df.set_index("int64_too")[["float64_col"]] - pd_df_b = pd_df_b[pd_df_b.float64_col > 0] - pd_df_b = pd_df_b.sort_values("float64_col") - pd_result = pd_df_a.join(pd_df_b, how=how) - - assert_frame_equal(bf_result, pd_result, ignore_order=True) - - -def test_join_incompatible_key_type_error(scalars_dfs): - bf_df, _ = scalars_dfs - - bf_df_a = bf_df.set_index("int64_too")[["string_col", "int64_col"]] - bf_df_a = bf_df_a.sort_index() - - bf_df_b = bf_df.set_index("date_col")[["float64_col"]] - bf_df_b = bf_df_b[bf_df_b.float64_col > 0] - bf_df_b = bf_df_b.sort_values("float64_col") - - with pytest.raises(TypeError): - # joining incompatible date, int columns - bf_df_a.join(bf_df_b, how="left") + assert_pandas_df_equal_ignore_ordering(bf_result, pd_result) @all_joins @@ -3280,107 +1777,15 @@ def test_join_different_table( pd_df_a = scalars_pandas_df_index[["string_col", "int64_col"]] pd_df_b = scalars_pandas_df_index.dropna()[["float64_col"]] pd_result = pd_df_a.join(pd_df_b, how=how) - assert_frame_equal(bf_result, pd_result, ignore_order=True) + assert_pandas_df_equal_ignore_ordering(bf_result, pd_result) -@all_joins -def test_join_different_table_with_duplicate_column_name( - scalars_df_index, scalars_pandas_df_index, how -): - bf_df_a = scalars_df_index[["string_col", "int64_col", "int64_too"]].rename( - columns={"int64_too": "int64_col"} - ) - bf_df_b = scalars_df_index.dropna()[ - ["string_col", "int64_col", "int64_too"] - ].rename(columns={"int64_too": "int64_col"}) - bf_result = bf_df_a.join(bf_df_b, how=how, lsuffix="_l", rsuffix="_r").to_pandas() - pd_df_a = scalars_pandas_df_index[["string_col", "int64_col", "int64_too"]].rename( - columns={"int64_too": "int64_col"} - ) - pd_df_b = scalars_pandas_df_index.dropna()[ - ["string_col", "int64_col", "int64_too"] - ].rename(columns={"int64_too": "int64_col"}) - pd_result = pd_df_a.join(pd_df_b, how=how, lsuffix="_l", rsuffix="_r") - - # Ensure no inplace changes - bigframes.testing.utils.assert_index_equal(bf_df_a.columns, pd_df_a.columns) - bigframes.testing.utils.assert_index_equal(bf_df_b.index.to_pandas(), pd_df_b.index) - bigframes.testing.utils.assert_frame_equal( - bf_result, pd_result, check_index_type=False - ) - - -@all_joins -def test_join_param_on_with_duplicate_column_name_not_on_col( - scalars_df_index, scalars_pandas_df_index, how -): - # This test is for duplicate column names, but the 'on' column is not duplicated. - if how == "cross": - return - bf_df_a = scalars_df_index[ - ["string_col", "datetime_col", "timestamp_col", "int64_too"] - ].rename(columns={"timestamp_col": "datetime_col"}) - bf_df_b = scalars_df_index.dropna()[ - ["string_col", "datetime_col", "timestamp_col"] - ].rename(columns={"timestamp_col": "datetime_col"}) - bf_result = bf_df_a.join( - bf_df_b, on="int64_too", how=how, lsuffix="_l", rsuffix="_r" - ).to_pandas() - pd_df_a = scalars_pandas_df_index[ - ["string_col", "datetime_col", "timestamp_col", "int64_too"] - ].rename(columns={"timestamp_col": "datetime_col"}) - pd_df_b = scalars_pandas_df_index.dropna()[ - ["string_col", "datetime_col", "timestamp_col"] - ].rename(columns={"timestamp_col": "datetime_col"}) - pd_result = pd_df_a.join( - pd_df_b, on="int64_too", how=how, lsuffix="_l", rsuffix="_r" - ) - bigframes.testing.utils.assert_frame_equal( - bf_result.sort_index(), - pd_result.sort_index(), - check_like=True, - check_index_type=False, - check_names=False, - ) - bigframes.testing.utils.assert_index_equal(bf_result.columns, pd_result.columns) - - -@pytest.mark.skipif( - pandas.__version__.startswith("1."), reason="bad left join in pandas 1.x" -) -@all_joins -def test_join_param_on_with_duplicate_column_name_on_col( - scalars_df_index, scalars_pandas_df_index, how -): - # This test is for duplicate column names, and the 'on' column is duplicated. - if how == "cross": - return - bf_df_a = scalars_df_index[ - ["string_col", "datetime_col", "timestamp_col", "int64_too"] - ].rename(columns={"timestamp_col": "datetime_col"}) - bf_df_b = scalars_df_index.dropna()[ - ["string_col", "datetime_col", "timestamp_col", "int64_too"] - ].rename(columns={"timestamp_col": "datetime_col"}) - bf_result = bf_df_a.join( - bf_df_b, on="int64_too", how=how, lsuffix="_l", rsuffix="_r" - ).to_pandas() - pd_df_a = scalars_pandas_df_index[ - ["string_col", "datetime_col", "timestamp_col", "int64_too"] - ].rename(columns={"timestamp_col": "datetime_col"}) - pd_df_b = scalars_pandas_df_index.dropna()[ - ["string_col", "datetime_col", "timestamp_col", "int64_too"] - ].rename(columns={"timestamp_col": "datetime_col"}) - pd_result = pd_df_a.join( - pd_df_b, on="int64_too", how=how, lsuffix="_l", rsuffix="_r" - ) - bigframes.testing.utils.assert_frame_equal( - bf_result.sort_index(), - pd_result.sort_index(), - check_like=True, - check_index_type=False, - check_names=False, - ) - bigframes.testing.utils.assert_index_equal(bf_result.columns, pd_result.columns) +def test_join_duplicate_columns_raises_not_implemented(scalars_dfs): + scalars_df, _ = scalars_dfs + df_a = scalars_df[["string_col", "float64_col"]] + df_b = scalars_df[["float64_col"]] + with pytest.raises(NotImplementedError): + df_a.join(df_b, how="outer").to_pandas() @all_joins @@ -3390,39 +1795,13 @@ def test_join_param_on(scalars_dfs, how): bf_df_a = bf_df[["string_col", "int64_col", "rowindex_2"]] bf_df_a = bf_df_a.assign(rowindex_2=bf_df_a["rowindex_2"] + 2) bf_df_b = bf_df[["float64_col"]] + bf_result = bf_df_a.join(bf_df_b, on="rowindex_2", how=how).to_pandas() - if how == "cross": - with pytest.raises(ValueError): - bf_df_a.join(bf_df_b, on="rowindex_2", how=how) - else: - bf_result = bf_df_a.join(bf_df_b, on="rowindex_2", how=how).to_pandas() - - pd_df_a = pd_df[["string_col", "int64_col", "rowindex_2"]] - pd_df_a = pd_df_a.assign(rowindex_2=pd_df_a["rowindex_2"] + 2) - pd_df_b = pd_df[["float64_col"]] - pd_result = pd_df_a.join(pd_df_b, on="rowindex_2", how=how) - assert_frame_equal(bf_result, pd_result, ignore_order=True) - - -@all_joins -def test_df_join_series(scalars_dfs, how): - bf_df, pd_df = scalars_dfs - - bf_df_a = bf_df[["string_col", "int64_col", "rowindex_2"]] - bf_df_a = bf_df_a.assign(rowindex_2=bf_df_a["rowindex_2"] + 2) - bf_series_b = bf_df["float64_col"] - - if how == "cross": - with pytest.raises(ValueError): - bf_df_a.join(bf_series_b, on="rowindex_2", how=how) - else: - bf_result = bf_df_a.join(bf_series_b, on="rowindex_2", how=how).to_pandas() - - pd_df_a = pd_df[["string_col", "int64_col", "rowindex_2"]] - pd_df_a = pd_df_a.assign(rowindex_2=pd_df_a["rowindex_2"] + 2) - pd_series_b = pd_df["float64_col"] - pd_result = pd_df_a.join(pd_series_b, on="rowindex_2", how=how) - assert_frame_equal(bf_result, pd_result, ignore_order=True) + pd_df_a = pd_df[["string_col", "int64_col", "rowindex_2"]] + pd_df_a = pd_df_a.assign(rowindex_2=pd_df_a["rowindex_2"] + 2) + pd_df_b = pd_df[["float64_col"]] + pd_result = pd_df_a.join(pd_df_b, on="rowindex_2", how=how) + assert_pandas_df_equal_ignore_ordering(bf_result, pd_result) @pytest.mark.parametrize( @@ -3452,37 +1831,6 @@ def test_dataframe_sort_values( ) -@pytest.mark.parametrize( - ("by", "ascending", "na_position"), - [ - ("int64_col", True, "first"), - (["bool_col", "int64_col"], True, "last"), - ], -) -def test_dataframe_sort_values_inplace( - scalars_df_index, scalars_pandas_df_index, by, ascending, na_position -): - # Test needs values to be unique - bf_sorted = scalars_df_index.copy() - bf_sorted.sort_values( - by, ascending=ascending, na_position=na_position, inplace=True - ) - bf_result = bf_sorted.to_pandas() - pd_result = scalars_pandas_df_index.sort_values( - by, ascending=ascending, na_position=na_position - ) - - pandas.testing.assert_frame_equal( - bf_result, - pd_result, - ) - - -def test_dataframe_sort_values_invalid_input(scalars_df_index): - with pytest.raises(KeyError): - scalars_df_index.sort_values(by=scalars_df_index["int64_col"]) - - def test_dataframe_sort_values_stable(scalars_df_index, scalars_pandas_df_index): bf_result = ( scalars_df_index.sort_values("int64_col", kind="stable") @@ -3525,7 +1873,7 @@ def test_dataframe_numeric_analytic_op( bf_series = operator(scalars_df_index[columns]) pd_series = operator(scalars_pandas_df_index[columns]) bf_result = bf_series.to_pandas() - bigframes.testing.utils.assert_frame_equal(pd_series, bf_result, check_dtype=False) + pd.testing.assert_frame_equal(pd_series, bf_result, check_dtype=False) @pytest.mark.parametrize( @@ -3550,7 +1898,7 @@ def test_dataframe_general_analytic_op( bf_series = operator(scalars_df_index[col_names]) pd_series = operator(scalars_pandas_df_index[col_names]) bf_result = bf_series.to_pandas() - bigframes.testing.utils.assert_frame_equal( + pd.testing.assert_frame_equal( pd_series, bf_result, ) @@ -3568,7 +1916,7 @@ def test_dataframe_diff(scalars_df_index, scalars_pandas_df_index, periods): col_names = ["int64_too", "float64_col", "int64_col"] bf_result = scalars_df_index[col_names].diff(periods=periods).to_pandas() pd_result = scalars_pandas_df_index[col_names].diff(periods=periods) - bigframes.testing.utils.assert_frame_equal( + pd.testing.assert_frame_equal( pd_result, bf_result, ) @@ -3585,9 +1933,8 @@ def test_dataframe_diff(scalars_df_index, scalars_pandas_df_index, periods): def test_dataframe_pct_change(scalars_df_index, scalars_pandas_df_index, periods): col_names = ["int64_too", "float64_col", "int64_col"] bf_result = scalars_df_index[col_names].pct_change(periods=periods).to_pandas() - # pandas 3.0 does not automatically ffill anymore - pd_result = scalars_pandas_df_index[col_names].ffill().pct_change(periods=periods) - bigframes.testing.utils.assert_frame_equal( + pd_result = scalars_pandas_df_index[col_names].pct_change(periods=periods) + pd.testing.assert_frame_equal( pd_result, bf_result, ) @@ -3596,37 +1943,15 @@ def test_dataframe_pct_change(scalars_df_index, scalars_pandas_df_index, periods def test_dataframe_agg_single_string(scalars_dfs): numeric_cols = ["int64_col", "int64_too", "float64_col"] scalars_df, scalars_pandas_df = scalars_dfs - bf_result = scalars_df[numeric_cols].agg("sum").to_pandas() pd_result = scalars_pandas_df[numeric_cols].agg("sum") - assert bf_result.dtype == "Float64" - bigframes.testing.utils.assert_series_equal( - pd_result, bf_result, check_dtype=False, check_index_type=False - ) - - -@pytest.mark.parametrize( - ("agg",), - ( - ("sum",), - ("size",), - ), -) -def test_dataframe_agg_int_single_string(scalars_dfs, agg): - numeric_cols = ["int64_col", "int64_too", "bool_col"] - scalars_df, scalars_pandas_df = scalars_dfs - - bf_result = scalars_df[numeric_cols].agg(agg).to_pandas() - pd_result = scalars_pandas_df[numeric_cols].agg(agg) - - assert bf_result.dtype == "Int64" - bigframes.testing.utils.assert_series_equal( - pd_result, bf_result, check_dtype=False, check_index_type=False - ) + # Pandas may produce narrower numeric types, but bigframes always produces Float64 + pd_result = pd_result.astype("Float64") + pd.testing.assert_series_equal(pd_result, bf_result, check_index_type=False) -def test_dataframe_agg_multi_string(scalars_dfs_maybe_ordered): +def test_dataframe_agg_multi_string(scalars_dfs): numeric_cols = ["int64_col", "int64_too", "float64_col"] aggregations = [ "sum", @@ -3639,8 +1964,8 @@ def test_dataframe_agg_multi_string(scalars_dfs_maybe_ordered): "nunique", "count", ] - scalars_df, scalars_pandas_df = scalars_dfs_maybe_ordered - bf_result = scalars_df[numeric_cols].agg(aggregations) + scalars_df, scalars_pandas_df = scalars_dfs + bf_result = scalars_df[numeric_cols].agg(aggregations).to_pandas() pd_result = scalars_pandas_df[numeric_cols].agg(aggregations) # Pandas may produce narrower numeric types, but bigframes always produces Float64 @@ -3651,7 +1976,7 @@ def test_dataframe_agg_multi_string(scalars_dfs_maybe_ordered): bf_result = bf_result.drop(labels=["median"]) pd_result = pd_result.drop(labels=["median"]) - assert_dfs_equivalent(pd_result, bf_result, check_index_type=False) + pd.testing.assert_frame_equal(pd_result, bf_result, check_index_type=False) # Double-check that median is at least plausible. assert ( @@ -3659,79 +1984,40 @@ def test_dataframe_agg_multi_string(scalars_dfs_maybe_ordered): ).all() -def test_dataframe_agg_int_multi_string(scalars_dfs): - numeric_cols = ["int64_col", "int64_too", "bool_col"] - aggregations = [ - "sum", - "nunique", - "count", - "size", - ] +def test_df_describe(scalars_dfs): scalars_df, scalars_pandas_df = scalars_dfs - bf_result = scalars_df[numeric_cols].agg(aggregations).to_pandas() - pd_result = scalars_pandas_df[numeric_cols].agg(aggregations) - - for dtype in bf_result.dtypes: - assert dtype == "Int64" - - # Pandas may produce narrower numeric types - # Pandas has object index type - bigframes.testing.utils.assert_frame_equal( - pd_result, bf_result, check_dtype=False, check_index_type=False - ) - - -def test_df_transpose(): - # Include some floats to ensure type coercion - values = [[0, 3.5, True], [1, 4.5, False], [2, 6.5, None]] - # Test complex case of both axes being multi-indices with non-unique elements - - columns: pandas.Index = pd.Index( - ["A", "B", "A"], dtype=pd.StringDtype(storage="pyarrow") - ) - columns_multi = pd.MultiIndex.from_arrays([columns, columns], names=["c1", "c2"]) + # pyarrows time columns fail in pandas + unsupported_columns = ["datetime_col", "timestamp_col", "time_col", "date_col"] + bf_result = scalars_df.describe().to_pandas() - index: pandas.Index = pd.Index( - ["b", "a", "a"], dtype=pd.StringDtype(storage="pyarrow") - ) - rows_multi = pd.MultiIndex.from_arrays([index, index], names=["r1", "r2"]) - - pd_df = pandas.DataFrame(values, index=rows_multi, columns=columns_multi) - bf_df = dataframe.DataFrame(values, index=rows_multi, columns=columns_multi) - - pd_result = pd_df.T - bf_result = bf_df.T.to_pandas() - - assert_frame_equal(pd_result, bf_result, check_dtype=False, nulls_are_nan=True) # type: ignore + 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") -def test_df_transpose_error(): - with pytest.raises(TypeError, match="Cannot coerce.*to a common type."): - dataframe.DataFrame([[1, "hello"], [2, "world"]]).transpose() + # 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%"]) -def test_df_transpose_repeated_uses_cache(): - bf_df = dataframe.DataFrame([[1, 2.5], [2, 3.5]]) - pd_df = pandas.DataFrame([[1, 2.5], [2, 3.5]]) - # Transposing many times so that operation will fail from complexity if not using cache - for i in range(10): - # Cache still works even with simple scalar binop - bf_df = bf_df.transpose() + i - pd_df = pd_df.transpose() + i + pd.testing.assert_frame_equal(pd_result, bf_result, check_index_type=False) - bigframes.testing.utils.assert_frame_equal( - pd_df, bf_df.to_pandas(), check_dtype=False, 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( - ("ordered"), - [ - (True), - (False), - ], -) -def test_df_stack(scalars_dfs, ordered): +def test_df_stack(scalars_dfs): if pandas.__version__.startswith("1.") or pandas.__version__.startswith("2.0"): pytest.skip("pandas <2.1 uses different stack implementation") scalars_df, scalars_pandas_df = scalars_dfs @@ -3741,13 +2027,11 @@ def test_df_stack(scalars_dfs, ordered): # Can only stack identically-typed columns columns = ["int64_col", "int64_too", "rowindex_2"] - bf_result = scalars_df[columns].stack().to_pandas(ordered=ordered) + bf_result = scalars_df[columns].stack().to_pandas() pd_result = scalars_pandas_df[columns].stack(future_stack=True) # Pandas produces NaN, where bq dataframes produces pd.NA - assert_series_equal( - bf_result, pd_result, check_dtype=False, ignore_order=not ordered - ) + pd.testing.assert_series_equal(bf_result, pd_result, check_dtype=False) def test_df_melt_default(scalars_dfs): @@ -3762,11 +2046,8 @@ def test_df_melt_default(scalars_dfs): pd_result = scalars_pandas_df[columns].melt() # Pandas produces int64 index, Bigframes produces Int64 (nullable) - bigframes.testing.utils.assert_frame_equal( - bf_result, - pd_result, - check_index_type=False, - check_dtype=False, + pd.testing.assert_frame_equal( + bf_result, pd_result, check_index_type=False, check_dtype=False ) @@ -3791,19 +2072,12 @@ def test_df_melt_parameterized(scalars_dfs): ) # Pandas produces int64 index, Bigframes produces Int64 (nullable) - bigframes.testing.utils.assert_frame_equal( + pd.testing.assert_frame_equal( bf_result, pd_result, check_index_type=False, check_dtype=False ) -@pytest.mark.parametrize( - ("ordered"), - [ - (True), - (False), - ], -) -def test_df_unstack(scalars_dfs, ordered): +def test_df_unstack(scalars_dfs): scalars_df, scalars_pandas_df = scalars_dfs # To match bigquery dataframes scalars_pandas_df = scalars_pandas_df.copy() @@ -3816,13 +2090,11 @@ def test_df_unstack(scalars_dfs, ordered): ] # unstack on mono-index produces series - bf_result = scalars_df[columns].unstack().to_pandas(ordered=ordered) + bf_result = scalars_df[columns].unstack().to_pandas() pd_result = scalars_pandas_df[columns].unstack() # Pandas produces NaN, where bq dataframes produces pd.NA - assert_series_equal( - bf_result, pd_result, check_dtype=False, ignore_order=not ordered - ) + pd.testing.assert_series_equal(bf_result, pd_result, check_dtype=False) @pytest.mark.parametrize( @@ -3842,9 +2114,7 @@ def test_df_pivot(scalars_dfs, values, index, columns): pd_result = scalars_pandas_df.pivot(values=values, index=index, columns=columns) # Pandas produces NaN, where bq dataframes produces pd.NA - bf_result = bf_result.fillna(float("nan")) - pd_result = pd_result.fillna(float("nan")) - bigframes.testing.utils.assert_frame_equal(bf_result, pd_result, check_dtype=False) + pd.testing.assert_frame_equal(bf_result, pd_result, check_dtype=False) @pytest.mark.parametrize( @@ -3865,50 +2135,7 @@ def test_df_pivot_hockey(hockey_df, hockey_pandas_df, values, index, columns): ) # Pandas produces NaN, where bq dataframes produces pd.NA - bigframes.testing.utils.assert_frame_equal(bf_result, pd_result, check_dtype=False) - - -@pytest.mark.parametrize( - ("values", "index", "columns", "aggfunc", "fill_value"), - [ - (("culmen_length_mm", "body_mass_g"), "species", "sex", "std", 1.0), - ( - ["body_mass_g", "culmen_length_mm"], - ("species", "island"), - "sex", - "sum", - None, - ), - ("body_mass_g", "sex", ["island", "species"], "mean", None), - ("culmen_depth_mm", "island", "species", "max", -1), - ], -) -def test_df_pivot_table( - penguins_df_default_index, - penguins_pandas_df_default_index, - values, - index, - columns, - aggfunc, - fill_value, -): - bf_result = penguins_df_default_index.pivot_table( - values=values, - index=index, - columns=columns, - aggfunc=aggfunc, - fill_value=fill_value, - ).to_pandas() - pd_result = penguins_pandas_df_default_index.pivot_table( - values=values, - index=index, - columns=columns, - aggfunc=aggfunc, - fill_value=fill_value, - ) - bigframes.testing.utils.assert_frame_equal( - bf_result, pd_result, check_dtype=False, check_column_type=False - ) + pd.testing.assert_frame_equal(bf_result, pd_result, check_dtype=False) def test_ipython_key_completions_with_drop(scalars_dfs): @@ -3983,15 +2210,6 @@ def test__dir__with_rename(scalars_dfs): assert "drop" in results -def test_loc_select_columns_w_repeats(scalars_df_index, scalars_pandas_df_index): - bf_result = scalars_df_index[["int64_col", "int64_col", "int64_too"]].to_pandas() - pd_result = scalars_pandas_df_index[["int64_col", "int64_col", "int64_too"]] - bigframes.testing.utils.assert_frame_equal( - bf_result, - pd_result, - ) - - @pytest.mark.parametrize( ("start", "stop", "step"), [ @@ -4010,25 +2228,7 @@ def test_loc_select_columns_w_repeats(scalars_df_index, scalars_pandas_df_index) def test_iloc_slice(scalars_df_index, scalars_pandas_df_index, start, stop, step): bf_result = scalars_df_index.iloc[start:stop:step].to_pandas() pd_result = scalars_pandas_df_index.iloc[start:stop:step] - bigframes.testing.utils.assert_frame_equal( - bf_result, - pd_result, - ) - - -@pytest.mark.parametrize( - ("start", "stop", "step"), - [ - (0, 0, None), - ], -) -def test_iloc_slice_after_cache( - scalars_df_index, scalars_pandas_df_index, start, stop, step -): - scalars_df_index.cache() - bf_result = scalars_df_index.iloc[start:stop:step].to_pandas() - pd_result = scalars_pandas_df_index.iloc[start:stop:step] - bigframes.testing.utils.assert_frame_equal( + pd.testing.assert_frame_equal( bf_result, pd_result, ) @@ -4039,18 +2239,14 @@ def test_iloc_slice_zero_step(scalars_df_index): scalars_df_index.iloc[0:0:0] -@pytest.mark.parametrize( - ("ordered"), - [ - (True), - (False), - ], -) -def test_iloc_slice_nested(scalars_df_index, scalars_pandas_df_index, ordered): - bf_result = scalars_df_index.iloc[1:].iloc[1:].to_pandas(ordered=ordered) +def test_iloc_slice_nested(scalars_df_index, scalars_pandas_df_index): + bf_result = scalars_df_index.iloc[1:].iloc[1:].to_pandas() pd_result = scalars_pandas_df_index.iloc[1:].iloc[1:] - assert_frame_equal(bf_result, pd_result, ignore_order=not ordered) + pd.testing.assert_frame_equal( + bf_result, + pd_result, + ) @pytest.mark.parametrize( @@ -4061,7 +2257,7 @@ def test_iloc_single_integer(scalars_df_index, scalars_pandas_df_index, index): bf_result = scalars_df_index.iloc[index] pd_result = scalars_pandas_df_index.iloc[index] - bigframes.testing.utils.assert_series_equal( + pd.testing.assert_series_equal( bf_result, pd_result, ) @@ -4078,24 +2274,6 @@ def test_iloc_tuple(scalars_df_index, scalars_pandas_df_index, index): assert bf_result == pd_result -@pytest.mark.parametrize( - "index", - [(slice(None), [1, 2, 3]), (slice(1, 7, 2), [2, 5, 3])], -) -def test_iloc_tuple_multi_columns(scalars_df_index, scalars_pandas_df_index, index): - bf_result = scalars_df_index.iloc[index].to_pandas() - pd_result = scalars_pandas_df_index.iloc[index] - - bigframes.testing.utils.assert_frame_equal(bf_result, pd_result) - - -def test_iloc_tuple_multi_columns_single_row(scalars_df_index, scalars_pandas_df_index): - index = (2, [2, 1, 3, -4]) - bf_result = scalars_df_index.iloc[index] - pd_result = scalars_pandas_df_index.iloc[index] - bigframes.testing.utils.assert_series_equal(bf_result, pd_result) - - @pytest.mark.parametrize( ("index", "error"), [ @@ -4138,7 +2316,9 @@ def test_iat_errors(scalars_df_index, scalars_pandas_df_index, index, error): scalars_df_index.iat[index] -def test_iloc_single_integer_out_of_bound_error(scalars_df_index): +def test_iloc_single_integer_out_of_bound_error( + scalars_df_index, scalars_pandas_df_index +): with pytest.raises(IndexError, match="single positional indexer is out-of-bounds"): scalars_df_index.iloc[99] @@ -4147,18 +2327,7 @@ def test_loc_bool_series(scalars_df_index, scalars_pandas_df_index): bf_result = scalars_df_index.loc[scalars_df_index.bool_col].to_pandas() pd_result = scalars_pandas_df_index.loc[scalars_pandas_df_index.bool_col] - bigframes.testing.utils.assert_frame_equal( - bf_result, - pd_result, - ) - - -def test_loc_list_select_rows_and_columns(scalars_df_index, scalars_pandas_df_index): - idx_list = [0, 3, 5] - bf_result = scalars_df_index.loc[idx_list, ["bool_col", "int64_col"]].to_pandas() - pd_result = scalars_pandas_df_index.loc[idx_list, ["bool_col", "int64_col"]] - - bigframes.testing.utils.assert_frame_equal( + pd.testing.assert_frame_equal( bf_result, pd_result, ) @@ -4167,41 +2336,7 @@ def test_loc_list_select_rows_and_columns(scalars_df_index, scalars_pandas_df_in def test_loc_select_column(scalars_df_index, scalars_pandas_df_index): bf_result = scalars_df_index.loc[:, "int64_col"].to_pandas() pd_result = scalars_pandas_df_index.loc[:, "int64_col"] - bigframes.testing.utils.assert_series_equal( - bf_result, - pd_result, - ) - - -def test_loc_select_with_column_condition(scalars_df_index, scalars_pandas_df_index): - bf_result = scalars_df_index.loc[:, scalars_df_index.dtypes == "Int64"].to_pandas() - pd_result = scalars_pandas_df_index.loc[ - :, scalars_pandas_df_index.dtypes == "Int64" - ] - bigframes.testing.utils.assert_frame_equal( - bf_result, - pd_result, - ) - - -def test_loc_select_with_column_condition_bf_series( - scalars_df_index, scalars_pandas_df_index -): - # (b/347072677) GEOGRAPH type doesn't support DISTINCT op - columns = [ - item for item in scalars_pandas_df_index.columns if item != "geography_col" - ] - scalars_df_index = scalars_df_index[columns] - scalars_pandas_df_index = scalars_pandas_df_index[columns] - - size_half = len(scalars_pandas_df_index) / 2 - bf_result = scalars_df_index.loc[ - :, scalars_df_index.nunique() > size_half - ].to_pandas() - pd_result = scalars_pandas_df_index.loc[ - :, scalars_pandas_df_index.nunique() > size_half - ] - bigframes.testing.utils.assert_frame_equal( + pd.testing.assert_series_equal( bf_result, pd_result, ) @@ -4215,7 +2350,7 @@ def test_loc_single_index_with_duplicate(scalars_df_index, scalars_pandas_df_ind index = "Hello, World!" bf_result = scalars_df_index.loc[index] pd_result = scalars_pandas_df_index.loc[index] - bigframes.testing.utils.assert_frame_equal( + pd.testing.assert_frame_equal( bf_result.to_pandas(), pd_result, ) @@ -4227,7 +2362,7 @@ def test_loc_single_index_no_duplicate(scalars_df_index, scalars_pandas_df_index index = -2345 bf_result = scalars_df_index.loc[index] pd_result = scalars_pandas_df_index.loc[index] - bigframes.testing.utils.assert_series_equal( + pd.testing.assert_series_equal( bf_result, pd_result, ) @@ -4241,7 +2376,7 @@ def test_at_with_duplicate(scalars_df_index, scalars_pandas_df_index): index = "Hello, World!" bf_result = scalars_df_index.at[index, "int64_too"] pd_result = scalars_pandas_df_index.at[index, "int64_too"] - bigframes.testing.utils.assert_series_equal( + pd.testing.assert_series_equal( bf_result.to_pandas(), pd_result, ) @@ -4263,40 +2398,32 @@ def test_loc_setitem_bool_series_scalar_new_col(scalars_dfs): bf_df.loc[bf_df["int64_too"] == 0, "new_col"] = 99 pd_df.loc[pd_df["int64_too"] == 0, "new_col"] = 99 - # pandas uses float64 instead + # pandas type difference pd_df["new_col"] = pd_df["new_col"].astype("Float64") - bigframes.testing.utils.assert_frame_equal( + pd.testing.assert_frame_equal( bf_df.to_pandas(), pd_df, ) -@pytest.mark.parametrize( - ("col", "value"), - [ - ("string_col", "hello"), - ("int64_col", 3), - ("float64_col", 3.5), - ], -) -def test_loc_setitem_bool_series_scalar_existing_col(scalars_dfs, col, value): +def test_loc_setitem_bool_series_scalar_existing_col(scalars_dfs): if pd.__version__.startswith("1."): pytest.skip("this loc overload not supported in pandas 1.x.") scalars_df, scalars_pandas_df = scalars_dfs bf_df = scalars_df.copy() pd_df = scalars_pandas_df.copy() - bf_df.loc[bf_df["int64_too"] == 1, col] = value - pd_df.loc[pd_df["int64_too"] == 1, col] = value + bf_df.loc[bf_df["int64_too"] == 1, "string_col"] = "hello" + pd_df.loc[pd_df["int64_too"] == 1, "string_col"] = "hello" - bigframes.testing.utils.assert_frame_equal( + pd.testing.assert_frame_equal( bf_df.to_pandas(), pd_df, ) -def test_loc_setitem_bool_series_scalar_error(scalars_dfs): +def test_loc_setitem_bool_series_scalar_type_error(scalars_dfs): if pd.__version__.startswith("1."): pytest.skip("this loc overload not supported in pandas 1.x.") @@ -4304,98 +2431,36 @@ def test_loc_setitem_bool_series_scalar_error(scalars_dfs): bf_df = scalars_df.copy() pd_df = scalars_pandas_df.copy() - with pytest.raises(Exception): + with pytest.raises(TypeError): bf_df.loc[bf_df["int64_too"] == 1, "string_col"] = 99 - with pytest.raises(Exception): + with pytest.raises(TypeError): pd_df.loc[pd_df["int64_too"] == 1, "string_col"] = 99 @pytest.mark.parametrize( - ("col", "op"), - [ - # Int aggregates - pytest.param("int64_col", lambda x: x.sum(), id="int-sum"), - pytest.param("int64_col", lambda x: x.min(), id="int-min"), - pytest.param("int64_col", lambda x: x.max(), id="int-max"), - pytest.param("int64_col", lambda x: x.count(), id="int-count"), - pytest.param("int64_col", lambda x: x.nunique(), id="int-nunique"), - # Float aggregates - pytest.param("float64_col", lambda x: x.count(), id="float-count"), - pytest.param("float64_col", lambda x: x.nunique(), id="float-nunique"), - # Bool aggregates - pytest.param("bool_col", lambda x: x.sum(), id="bool-sum"), - pytest.param("bool_col", lambda x: x.count(), id="bool-count"), - pytest.param("bool_col", lambda x: x.nunique(), id="bool-nunique"), - # String aggregates - pytest.param("string_col", lambda x: x.count(), id="string-count"), - pytest.param("string_col", lambda x: x.nunique(), id="string-nunique"), - ], -) -def test_dataframe_aggregate_int(scalars_df_index, scalars_pandas_df_index, col, op): - bf_result = op(scalars_df_index[[col]]).to_pandas() - pd_result = op(scalars_pandas_df_index[[col]]) - - # Check dtype separately - assert bf_result.dtype == "Int64" - # Is otherwise "object" dtype - pd_result.index = pd_result.index.astype("string[pyarrow]") - # Pandas may produce narrower numeric types - assert_series_equal(pd_result, bf_result, check_dtype=False, check_index_type=False) - - -@pytest.mark.parametrize( - ("col", "op"), - [ - pytest.param("bool_col", lambda x: x.min(), id="bool-min"), - pytest.param("bool_col", lambda x: x.max(), id="bool-max"), - ], -) -def test_dataframe_aggregate_bool(scalars_df_index, scalars_pandas_df_index, col, op): - bf_result = op(scalars_df_index[[col]]).to_pandas() - pd_result = op(scalars_pandas_df_index[[col]]) - - # Check dtype separately - assert bf_result.dtype == "boolean" - - # Pandas may produce narrower numeric types - # Pandas has object index type - pd_result.index = pd_result.index.astype("string[pyarrow]") - assert_series_equal(pd_result, bf_result, check_dtype=False, check_index_type=False) - - -@pytest.mark.parametrize( - ("op", "bf_dtype"), + ("op"), [ - (lambda x: x.sum(numeric_only=True), "Float64"), - (lambda x: x.mean(numeric_only=True), "Float64"), - (lambda x: x.min(numeric_only=True), "Float64"), - (lambda x: x.max(numeric_only=True), "Float64"), - (lambda x: x.std(numeric_only=True), "Float64"), - (lambda x: x.var(numeric_only=True), "Float64"), - (lambda x: x.count(numeric_only=False), "Int64"), - (lambda x: x.nunique(), "Int64"), + (lambda x: x.sum(numeric_only=True)), + (lambda x: x.mean(numeric_only=True)), + (lambda x: x.min(numeric_only=True)), + (lambda x: x.max(numeric_only=True)), + (lambda x: x.std(numeric_only=True)), + (lambda x: x.var(numeric_only=True)), + (lambda x: x.count(numeric_only=False)), + (lambda x: x.nunique()), ], ids=["sum", "mean", "min", "max", "std", "var", "count", "nunique"], ) -def test_dataframe_aggregates(scalars_dfs_maybe_ordered, op, bf_dtype): - scalars_df_index, scalars_pandas_df_index = scalars_dfs_maybe_ordered +def test_dataframe_aggregates(scalars_df_index, scalars_pandas_df_index, op): col_names = ["int64_too", "float64_col", "string_col", "int64_col", "bool_col"] bf_series = op(scalars_df_index[col_names]) - bf_result = bf_series - pd_result = op(scalars_pandas_df_index[col_names]) - - # Check dtype separately - assert bf_result.dtype == bf_dtype + pd_series = op(scalars_pandas_df_index[col_names]) + bf_result = bf_series.to_pandas() # Pandas may produce narrower numeric types, but bigframes always produces Float64 + pd_series = pd_series.astype("Float64") # Pandas has object index type - pd_result.index = pd_result.index.astype("string[pyarrow]") - assert_series_equivalent( - pd_result, - bf_result, - check_dtype=False, - check_index_type=False, - ) + pd.testing.assert_series_equal(pd_series, bf_result, check_index_type=False) @pytest.mark.parametrize( @@ -4415,8 +2480,10 @@ def test_dataframe_aggregates_axis_1(scalars_df_index, scalars_pandas_df_index, bf_result = op(scalars_df_index[col_names]).to_pandas() pd_result = op(scalars_pandas_df_index[col_names]) + # Pandas may produce narrower numeric types, but bigframes always produces Float64 + pd_result = pd_result.astype("Float64") # Pandas has object index type - assert_series_equal(pd_result, bf_result, check_index_type=False, check_dtype=False) + pd.testing.assert_series_equal(pd_result, bf_result, check_index_type=False) def test_dataframe_aggregates_median(scalars_df_index, scalars_pandas_df_index): @@ -4434,33 +2501,6 @@ def test_dataframe_aggregates_median(scalars_df_index, scalars_pandas_df_index): ) -def test_dataframe_aggregates_quantile_mono(scalars_df_index, scalars_pandas_df_index): - q = 0.45 - col_names = ["int64_too", "int64_col", "float64_col"] - bf_result = scalars_df_index[col_names].quantile(q=q).to_pandas() - pd_result = scalars_pandas_df_index[col_names].quantile(q=q) - - # Pandas may produce narrower numeric types, but bigframes always produces Float64 - pd_result = pd_result.astype("Float64") - - bigframes.testing.utils.assert_series_equal( - bf_result, pd_result, check_index_type=False - ) - - -def test_dataframe_aggregates_quantile_multi(scalars_df_index, scalars_pandas_df_index): - q = [0, 0.33, 0.67, 1.0] - col_names = ["int64_too", "int64_col", "float64_col"] - bf_result = scalars_df_index[col_names].quantile(q=q).to_pandas() - pd_result = scalars_pandas_df_index[col_names].quantile(q=q) - - # Pandas may produce narrower numeric types, but bigframes always produces Float64 - pd_result = pd_result.astype("Float64") - pd_result.index = pd_result.index.astype("Float64") - - bigframes.testing.utils.assert_frame_equal(bf_result, pd_result) - - @pytest.mark.parametrize( ("op"), [ @@ -4483,10 +2523,8 @@ def test_dataframe_bool_aggregates(scalars_df_index, scalars_pandas_df_index, op pd_series = op(scalars_pandas_df_index).astype("boolean") bf_result = bf_series.to_pandas() - pd_series.index = pd_series.index.astype(bf_result.index.dtype) - bigframes.testing.utils.assert_series_equal( - pd_series, bf_result, check_index_type=False - ) + # Pandas has object index type + pd.testing.assert_series_equal(pd_series, bf_result, check_index_type=False) def test_dataframe_prod(scalars_df_index, scalars_pandas_df_index): @@ -4498,9 +2536,7 @@ def test_dataframe_prod(scalars_df_index, scalars_pandas_df_index): # Pandas may produce narrower numeric types, but bigframes always produces Float64 pd_series = pd_series.astype("Float64") # Pandas has object index type - bigframes.testing.utils.assert_series_equal( - pd_series, bf_result, check_index_type=False - ) + pd.testing.assert_series_equal(pd_series, bf_result, check_index_type=False) def test_df_skew_too_few_values(scalars_dfs): @@ -4512,30 +2548,19 @@ def test_df_skew_too_few_values(scalars_dfs): # Pandas may produce narrower numeric types, but bigframes always produces Float64 pd_result = pd_result.astype("Float64") - bigframes.testing.utils.assert_series_equal( - pd_result, bf_result, check_index_type=False - ) + pd.testing.assert_series_equal(pd_result, bf_result, check_index_type=False) -@pytest.mark.parametrize( - ("ordered"), - [ - (True), - (False), - ], -) -def test_df_skew(scalars_dfs, ordered): +def test_df_skew(scalars_dfs): columns = ["float64_col", "int64_col"] scalars_df, scalars_pandas_df = scalars_dfs - bf_result = scalars_df[columns].skew().to_pandas(ordered=ordered) + bf_result = scalars_df[columns].skew().to_pandas() pd_result = scalars_pandas_df[columns].skew() # Pandas may produce narrower numeric types, but bigframes always produces Float64 pd_result = pd_result.astype("Float64") - assert_series_equal( - pd_result, bf_result, check_index_type=False, ignore_order=not ordered - ) + pd.testing.assert_series_equal(pd_result, bf_result, check_index_type=False) def test_df_kurt_too_few_values(scalars_dfs): @@ -4547,9 +2572,7 @@ def test_df_kurt_too_few_values(scalars_dfs): # Pandas may produce narrower numeric types, but bigframes always produces Float64 pd_result = pd_result.astype("Float64") - bigframes.testing.utils.assert_series_equal( - pd_result, bf_result, check_index_type=False - ) + pd.testing.assert_series_equal(pd_result, bf_result, check_index_type=False) def test_df_kurt(scalars_dfs): @@ -4561,9 +2584,7 @@ def test_df_kurt(scalars_dfs): # Pandas may produce narrower numeric types, but bigframes always produces Float64 pd_result = pd_result.astype("Float64") - bigframes.testing.utils.assert_series_equal( - pd_result, bf_result, check_index_type=False - ) + pd.testing.assert_series_equal(pd_result, bf_result, check_index_type=False) @pytest.mark.parametrize( @@ -4583,7 +2604,7 @@ def test_df_kurt(scalars_dfs): "n_default", ], ) -def test_df_to_pandas_sample(scalars_dfs, frac, n, random_state): +def test_sample(scalars_dfs, frac, n, random_state): scalars_df, _ = scalars_dfs df = scalars_df.sample(frac=frac, n=n, random_state=random_state) bf_result = df.to_pandas() @@ -4594,15 +2615,7 @@ def test_df_to_pandas_sample(scalars_dfs, frac, n, random_state): assert bf_result.shape[1] == scalars_df.shape[1] -def test_df_to_pandas_sample_determinism(penguins_df_default_index): - df = penguins_df_default_index.sample(n=100, random_state=12345).head(15) - bf_result = df.to_pandas() - bf_result2 = df.to_pandas() - - pandas.testing.assert_frame_equal(bf_result, bf_result2) - - -def test_df_to_pandas_sample_raises_value_error(scalars_dfs): +def test_sample_raises_value_error(scalars_dfs): scalars_df, _ = scalars_dfs with pytest.raises( ValueError, match="Only one of 'n' or 'frac' parameter can be specified." @@ -4610,28 +2623,6 @@ def test_df_to_pandas_sample_raises_value_error(scalars_dfs): scalars_df.sample(frac=0.5, n=4) -def test_sample_args_sort(scalars_dfs): - scalars_df, _ = scalars_dfs - index = [4, 3, 2, 5, 1, 0] - scalars_df = scalars_df.iloc[index] - - kwargs = {"frac": 1.0, "random_state": 333} - - df = scalars_df.sample(**kwargs).to_pandas() - assert df.index.values != index - assert df.index.values != sorted(index) - - df = scalars_df.sample(sort="random", **kwargs).to_pandas() - assert df.index.values != index - assert df.index.values != sorted(index) - - df = scalars_df.sample(sort=True, **kwargs).to_pandas() - assert df.index.values == sorted(index) - - df = scalars_df.sample(sort=False, **kwargs).to_pandas() - assert df.index.values == index - - @pytest.mark.parametrize( ("axis",), [ @@ -4647,7 +2638,7 @@ def test_df_add_prefix(scalars_df_index, scalars_pandas_df_index, axis): pd_result = scalars_pandas_df_index.add_prefix("prefix_", axis) - bigframes.testing.utils.assert_frame_equal( + pd.testing.assert_frame_equal( bf_result, pd_result, check_index_type=False, @@ -4668,19 +2659,13 @@ def test_df_add_suffix(scalars_df_index, scalars_pandas_df_index, axis): pd_result = scalars_pandas_df_index.add_suffix("_suffix", axis) - bigframes.testing.utils.assert_frame_equal( + pd.testing.assert_frame_equal( bf_result, pd_result, check_index_type=False, ) -def test_df_astype_error_error(session): - input = pd.DataFrame(["hello", "world", "3.11", "4000"]) - with pytest.raises(ValueError): - session.read_pandas(input).astype("Float64", errors="bad_value") - - def test_df_columns_filter_items(scalars_df_index, scalars_pandas_df_index): if pd.__version__.startswith("2.0") or pd.__version__.startswith("1."): pytest.skip("pandas filter items behavior different pre-2.1") @@ -4688,7 +2673,7 @@ def test_df_columns_filter_items(scalars_df_index, scalars_pandas_df_index): pd_result = scalars_pandas_df_index.filter(items=["string_col", "int64_col"]) # Ignore column ordering as pandas order differently depending on version - bigframes.testing.utils.assert_frame_equal( + pd.testing.assert_frame_equal( bf_result.sort_index(axis=1), pd_result.sort_index(axis=1), ) @@ -4699,7 +2684,7 @@ def test_df_columns_filter_like(scalars_df_index, scalars_pandas_df_index): pd_result = scalars_pandas_df_index.filter(like="64_col") - bigframes.testing.utils.assert_frame_equal( + pd.testing.assert_frame_equal( bf_result, pd_result, ) @@ -4710,7 +2695,7 @@ def test_df_columns_filter_regex(scalars_df_index, scalars_pandas_df_index): pd_result = scalars_pandas_df_index.filter(regex="^[^_]+$") - bigframes.testing.utils.assert_frame_equal( + pd.testing.assert_frame_equal( bf_result, pd_result, ) @@ -4726,10 +2711,9 @@ def test_df_rows_filter_items(scalars_df_index, scalars_pandas_df_index): # Pandas uses int64 instead of Int64 (nullable) dtype. pd_result.index = pd_result.index.astype(pd.Int64Dtype()) # Ignore ordering as pandas order differently depending on version - assert_frame_equal( + assert_pandas_df_equal_ignore_ordering( bf_result, pd_result, - ignore_order=True, check_names=False, ) @@ -4742,7 +2726,7 @@ def test_df_rows_filter_like(scalars_df_index, scalars_pandas_df_index): pd_result = scalars_pandas_df_index.filter(like="ello", axis=0) - bigframes.testing.utils.assert_frame_equal( + pd.testing.assert_frame_equal( bf_result, pd_result, ) @@ -4756,23 +2740,22 @@ def test_df_rows_filter_regex(scalars_df_index, scalars_pandas_df_index): pd_result = scalars_pandas_df_index.filter(regex="^[GH].*", axis=0) - bigframes.testing.utils.assert_frame_equal( + pd.testing.assert_frame_equal( bf_result, pd_result, ) -def test_df_reindex_rows_list(scalars_dfs_maybe_ordered): - scalars_df_index, scalars_pandas_df_index = scalars_dfs_maybe_ordered - bf_result = scalars_df_index.reindex(index=[5, 1, 3, 99, 1]) +def test_df_reindex_rows_list(scalars_df_index, scalars_pandas_df_index): + bf_result = scalars_df_index.reindex(index=[5, 1, 3, 99, 1]).to_pandas() pd_result = scalars_pandas_df_index.reindex(index=[5, 1, 3, 99, 1]) # Pandas uses int64 instead of Int64 (nullable) dtype. pd_result.index = pd_result.index.astype(pd.Int64Dtype()) - assert_dfs_equivalent( - pd_result, + pd.testing.assert_frame_equal( bf_result, + pd_result, ) @@ -4787,7 +2770,7 @@ def test_df_reindex_rows_index(scalars_df_index, scalars_pandas_df_index): # Pandas uses int64 instead of Int64 (nullable) dtype. pd_result.index = pd_result.index.astype(pd.Int64Dtype()) - bigframes.testing.utils.assert_frame_equal( + pd.testing.assert_frame_equal( bf_result, pd_result, ) @@ -4812,22 +2795,7 @@ def test_df_reindex_columns(scalars_df_index, scalars_pandas_df_index): # Pandas uses float64 as default for newly created empty column, bf uses Float64 pd_result.not_a_col = pd_result.not_a_col.astype(pandas.Float64Dtype()) - bigframes.testing.utils.assert_frame_equal( - bf_result, - pd_result, - ) - - -def test_df_reindex_columns_with_same_order(scalars_df_index, scalars_pandas_df_index): - # First, make sure the two dataframes have the same columns in order. - columns = ["int64_col", "int64_too"] - bf = scalars_df_index[columns] - pd_df = scalars_pandas_df_index[columns] - - bf_result = bf.reindex(columns=columns).to_pandas() - pd_result = pd_df.reindex(columns=columns) - - bigframes.testing.utils.assert_frame_equal( + pd.testing.assert_frame_equal( bf_result, pd_result, ) @@ -4917,7 +2885,7 @@ def test_df_reindex_like(scalars_df_index, scalars_pandas_df_index): pd_result.index = pd_result.index.astype(pd.Int64Dtype()) # Pandas uses float64 as default for newly created empty column, bf uses Float64 pd_result.not_a_col = pd_result.not_a_col.astype(pandas.Float64Dtype()) - bigframes.testing.utils.assert_frame_equal( + pd.testing.assert_frame_equal( bf_result, pd_result, ) @@ -4928,7 +2896,7 @@ def test_df_values(scalars_df_index, scalars_pandas_df_index): pd_result = scalars_pandas_df_index.values # Numpy isn't equipped to compare non-numeric objects, so convert back to dataframe - bigframes.testing.utils.assert_frame_equal( + pd.testing.assert_frame_equal( pd.DataFrame(bf_result), pd.DataFrame(pd_result), check_dtype=False ) @@ -4938,7 +2906,7 @@ def test_df_to_numpy(scalars_df_index, scalars_pandas_df_index): pd_result = scalars_pandas_df_index.to_numpy() # Numpy isn't equipped to compare non-numeric objects, so convert back to dataframe - bigframes.testing.utils.assert_frame_equal( + pd.testing.assert_frame_equal( pd.DataFrame(bf_result), pd.DataFrame(pd_result), check_dtype=False ) @@ -4948,89 +2916,22 @@ def test_df___array__(scalars_df_index, scalars_pandas_df_index): pd_result = scalars_pandas_df_index.__array__() # Numpy isn't equipped to compare non-numeric objects, so convert back to dataframe - bigframes.testing.utils.assert_frame_equal( + pd.testing.assert_frame_equal( pd.DataFrame(bf_result), pd.DataFrame(pd_result), check_dtype=False ) -@pytest.mark.parametrize( - ("key",), - [ - ("hello",), - (2,), - ("int64_col",), - (None,), - ], -) -def test_df_contains(scalars_df_index, scalars_pandas_df_index, key): - bf_result = key in scalars_df_index - pd_result = key in scalars_pandas_df_index - - assert bf_result == pd_result - - -def test_df_getattr_attribute_error_when_pandas_has(scalars_df_index): - # swapaxes is implemented in pandas but not in bigframes +def test_getattr_attribute_error_when_pandas_has(scalars_df_index): + # asof is implemented in pandas but not in bigframes with pytest.raises(AttributeError): - scalars_df_index.swapaxes() + scalars_df_index.asof() -def test_df_getattr_attribute_error(scalars_df_index): +def test_getattr_attribute_error(scalars_df_index): with pytest.raises(AttributeError): scalars_df_index.not_a_method() -def test_df_getattr_axes(): - df = dataframe.DataFrame( - [[1, 1, 1], [1, 1, 1]], columns=["index", "columns", "my_column"] - ) - assert isinstance(df.index, bigframes.core.indexes.Index) - assert isinstance(df.columns, pandas.Index) - assert isinstance(df.my_column, series.Series) - - -def test_df_setattr_index(): - pd_df = pandas.DataFrame( - [[1, 1, 1], [1, 1, 1]], columns=["index", "columns", "my_column"] - ) - bf_df = dataframe.DataFrame(pd_df) - - pd_df.index = pandas.Index([4, 5]) - bf_df.index = [4, 5] - - assert_frame_equal( - pd_df, bf_df.to_pandas(), check_index_type=False, check_dtype=False - ) - - -def test_df_setattr_columns(): - pd_df = pandas.DataFrame( - [[1, 1, 1], [1, 1, 1]], columns=["index", "columns", "my_column"] - ) - bf_df = dataframe.DataFrame(pd_df) - - pd_df.columns = typing.cast(pandas.Index, pandas.Index([4, 5, 6])) - - bf_df.columns = pandas.Index([4, 5, 6]) - - assert_frame_equal( - pd_df, bf_df.to_pandas(), check_index_type=False, check_dtype=False - ) - - -def test_df_setattr_modify_column(): - pd_df = pandas.DataFrame( - [[1, 1, 1], [1, 1, 1]], columns=["index", "columns", "my_column"] - ) - bf_df = dataframe.DataFrame(pd_df) - pd_df.my_column = [4, 5] - bf_df.my_column = [4, 5] - - assert_frame_equal( - pd_df, bf_df.to_pandas(), check_index_type=False, check_dtype=False - ) - - def test_loc_list_string_index(scalars_df_index, scalars_pandas_df_index): index_list = scalars_pandas_df_index.string_col.iloc[[0, 1, 1, 5]].values @@ -5040,7 +2941,7 @@ def test_loc_list_string_index(scalars_df_index, scalars_pandas_df_index): bf_result = scalars_df_index.loc[index_list].to_pandas() pd_result = scalars_pandas_df_index.loc[index_list] - bigframes.testing.utils.assert_frame_equal( + pd.testing.assert_frame_equal( bf_result, pd_result, ) @@ -5052,14 +2953,13 @@ def test_loc_list_integer_index(scalars_df_index, scalars_pandas_df_index): bf_result = scalars_df_index.loc[index_list] pd_result = scalars_pandas_df_index.loc[index_list] - bigframes.testing.utils.assert_frame_equal( + pd.testing.assert_frame_equal( bf_result.to_pandas(), pd_result, ) -def test_loc_list_multiindex(scalars_dfs_maybe_ordered): - scalars_df_index, scalars_pandas_df_index = scalars_dfs_maybe_ordered +def test_loc_list_multiindex(scalars_df_index, scalars_pandas_df_index): scalars_df_multiindex = scalars_df_index.set_index(["string_col", "int64_col"]) scalars_pandas_df_multiindex = scalars_pandas_df_index.set_index( ["string_col", "int64_col"] @@ -5069,45 +2969,19 @@ def test_loc_list_multiindex(scalars_dfs_maybe_ordered): bf_result = scalars_df_multiindex.loc[index_list] pd_result = scalars_pandas_df_multiindex.loc[index_list] - assert_dfs_equivalent( - pd_result, - bf_result, - ) - - -@pytest.mark.parametrize( - "index_list", - [ - [0, 1, 2, 3, 4, 4], - [0, 0, 0, 5, 4, 7, -2, -5, 3], - [-1, -2, -3, -4, -5, -5], - ], -) -def test_iloc_list(scalars_df_index, scalars_pandas_df_index, index_list): - bf_result = scalars_df_index.iloc[index_list] - pd_result = scalars_pandas_df_index.iloc[index_list] - - bigframes.testing.utils.assert_frame_equal( + pd.testing.assert_frame_equal( bf_result.to_pandas(), pd_result, ) -@pytest.mark.parametrize( - "index_list", - [ - [0, 1, 2, 3, 4, 4], - [0, 0, 0, 5, 4, 7, -2, -5, 3], - [-1, -2, -3, -4, -5, -5], - ], -) -def test_iloc_list_partial_ordering( - scalars_df_partial_ordering, scalars_pandas_df_index, index_list -): - bf_result = scalars_df_partial_ordering.iloc[index_list] +def test_iloc_list(scalars_df_index, scalars_pandas_df_index): + index_list = [0, 0, 0, 5, 4, 7] + + bf_result = scalars_df_index.iloc[index_list] pd_result = scalars_pandas_df_index.iloc[index_list] - bigframes.testing.utils.assert_frame_equal( + pd.testing.assert_frame_equal( bf_result.to_pandas(), pd_result, ) @@ -5125,14 +2999,14 @@ def test_iloc_list_multiindex(scalars_dfs): bf_result = scalars_df.iloc[index_list] pd_result = scalars_pandas_df.iloc[index_list] - bigframes.testing.utils.assert_frame_equal( + pd.testing.assert_frame_equal( bf_result.to_pandas(), pd_result, ) def test_iloc_empty_list(scalars_df_index, scalars_pandas_df_index): - index_list: List[int] = [] + index_list = [] bf_result = scalars_df_index.iloc[index_list] pd_result = scalars_pandas_df_index.iloc[index_list] @@ -5145,7 +3019,7 @@ def test_rename_axis(scalars_df_index, scalars_pandas_df_index): bf_result = scalars_df_index.rename_axis("newindexname") pd_result = scalars_pandas_df_index.rename_axis("newindexname") - bigframes.testing.utils.assert_frame_equal( + pd.testing.assert_frame_equal( bf_result.to_pandas(), pd_result, ) @@ -5155,7 +3029,7 @@ def test_rename_axis_nonstring(scalars_df_index, scalars_pandas_df_index): bf_result = scalars_df_index.rename_axis((4,)) pd_result = scalars_pandas_df_index.rename_axis((4,)) - bigframes.testing.utils.assert_frame_equal( + pd.testing.assert_frame_equal( bf_result.to_pandas(), pd_result, ) @@ -5171,7 +3045,7 @@ def test_loc_bf_series_string_index(scalars_df_index, scalars_pandas_df_index): bf_result = scalars_df_index.loc[bf_string_series] pd_result = scalars_pandas_df_index.loc[pd_string_series] - bigframes.testing.utils.assert_frame_equal( + pd.testing.assert_frame_equal( bf_result.to_pandas(), pd_result, ) @@ -5189,7 +3063,7 @@ def test_loc_bf_series_multiindex(scalars_df_index, scalars_pandas_df_index): bf_result = scalars_df_multiindex.loc[bf_string_series] pd_result = scalars_pandas_df_multiindex.loc[pd_string_series] - bigframes.testing.utils.assert_frame_equal( + pd.testing.assert_frame_equal( bf_result.to_pandas(), pd_result, ) @@ -5202,7 +3076,7 @@ def test_loc_bf_index_integer_index(scalars_df_index, scalars_pandas_df_index): bf_result = scalars_df_index.loc[bf_index] pd_result = scalars_pandas_df_index.loc[pd_index] - bigframes.testing.utils.assert_frame_equal( + pd.testing.assert_frame_equal( bf_result.to_pandas(), pd_result, ) @@ -5222,7 +3096,7 @@ def test_loc_bf_index_integer_index_renamed_col( bf_result = scalars_df_index.loc[bf_index] pd_result = scalars_pandas_df_index.loc[pd_index] - bigframes.testing.utils.assert_frame_equal( + pd.testing.assert_frame_equal( bf_result.to_pandas(), pd_result, ) @@ -5246,38 +3120,11 @@ def test_loc_bf_index_integer_index_renamed_col( ) def test_df_drop_duplicates(scalars_df_index, scalars_pandas_df_index, keep, subset): columns = ["bool_col", "int64_too", "int64_col"] - bf_df = scalars_df_index[columns].drop_duplicates(subset, keep=keep).to_pandas() - pd_df = scalars_pandas_df_index[columns].drop_duplicates(subset, keep=keep) - bigframes.testing.utils.assert_frame_equal( - pd_df, - bf_df, - ) - - -@pytest.mark.parametrize( - ("keep",), - [ - ("first",), - ("last",), - (False,), - ], -) -def test_df_drop_duplicates_w_json(json_df, keep): - bf_df = json_df.drop_duplicates(keep=keep).to_pandas() - - # drop_duplicates relies on pa.compute.dictionary_encode, which is incompatible - # with Arrow string extension types. Temporary conversion to standard Pandas - # strings is required. - json_pandas_df = json_df.to_pandas() - json_pandas_df["json_col"] = json_pandas_df["json_col"].astype( - pd.StringDtype(storage="pyarrow") - ) - - pd_df = json_pandas_df.drop_duplicates(keep=keep) - pd_df["json_col"] = pd_df["json_col"].astype(dtypes.JSON_DTYPE) - bigframes.testing.utils.assert_frame_equal( - pd_df, - bf_df, + bf_series = scalars_df_index[columns].drop_duplicates(subset, keep=keep).to_pandas() + pd_series = scalars_pandas_df_index[columns].drop_duplicates(subset, keep=keep) + pd.testing.assert_frame_equal( + pd_series, + bf_series, ) @@ -5300,47 +3147,7 @@ def test_df_duplicated(scalars_df_index, scalars_pandas_df_index, keep, subset): columns = ["bool_col", "int64_too", "int64_col"] bf_series = scalars_df_index[columns].duplicated(subset, keep=keep).to_pandas() pd_series = scalars_pandas_df_index[columns].duplicated(subset, keep=keep) - bigframes.testing.utils.assert_series_equal(pd_series, bf_series, check_dtype=False) - - -def test_df_from_dict_columns_orient(): - data = {"a": [1, 2], "b": [3.3, 2.4]} - bf_result = dataframe.DataFrame.from_dict(data, orient="columns").to_pandas() - pd_result = pd.DataFrame.from_dict(data, orient="columns") - assert_frame_equal(pd_result, bf_result, check_dtype=False, check_index_type=False) - - -def test_df_from_dict_index_orient(): - data = {"a": [1, 2], "b": [3.3, 2.4]} - bf_result = dataframe.DataFrame.from_dict( - data, orient="index", columns=["col1", "col2"] - ).to_pandas() - pd_result = pd.DataFrame.from_dict(data, orient="index", columns=["col1", "col2"]) - assert_frame_equal(pd_result, bf_result, check_dtype=False, check_index_type=False) - - -def test_df_from_dict_tight_orient(): - data = { - "index": [("i1", "i2"), ("i3", "i4")], - "columns": ["col1", "col2"], - "data": [[1, 2.6], [3, 4.5]], - "index_names": ["in1", "in2"], - "column_names": ["column_axis"], - } - - bf_result = dataframe.DataFrame.from_dict(data, orient="tight").to_pandas() - pd_result = pd.DataFrame.from_dict(data, orient="tight") - assert_frame_equal(pd_result, bf_result, check_dtype=False, check_index_type=False) - - -def test_df_from_records(): - records = ((1, "a"), (2.5, "b"), (3.3, "c"), (4.9, "d")) - - bf_result = dataframe.DataFrame.from_records( - records, columns=["c1", "c2"] - ).to_pandas() - pd_result = pd.DataFrame.from_records(records, columns=["c1", "c2"]) - assert_frame_equal(pd_result, bf_result, check_dtype=False, check_index_type=False) + pd.testing.assert_series_equal(pd_series, bf_series, check_dtype=False) def test_df_to_dict(scalars_df_index, scalars_pandas_df_index): @@ -5353,10 +3160,7 @@ def test_df_to_dict(scalars_df_index, scalars_pandas_df_index): def test_df_to_excel(scalars_df_index, scalars_pandas_df_index): unsupported = ["timestamp_col"] - with ( - tempfile.TemporaryFile() as bf_result_file, - tempfile.TemporaryFile() as pd_result_file, - ): + with tempfile.TemporaryFile() as bf_result_file, tempfile.TemporaryFile() as pd_result_file: scalars_df_index.drop(columns=unsupported).to_excel(bf_result_file) scalars_pandas_df_index.drop(columns=unsupported).to_excel(pd_result_file) bf_result = bf_result_file.read() @@ -5373,88 +3177,6 @@ def test_df_to_latex(scalars_df_index, scalars_pandas_df_index): assert bf_result == pd_result -def test_df_to_json_local_str(scalars_df_index, scalars_pandas_df_index): - # pandas 3.0 bugged for serializing date col - bf_result = scalars_df_index.drop(columns="date_col").to_json() - # default_handler for arrow types that have no default conversion - pd_result = scalars_pandas_df_index.drop(columns="date_col").to_json( - default_handler=str - ) - - assert bf_result == pd_result - - -def test_df_to_json_local_file(scalars_df_index, scalars_pandas_df_index): - # TODO: supply a reason why this isn't compatible with pandas 1.x - pytest.importorskip("pandas", minversion="2.0.0") - # duration not fully supported at pandas level - scalars_df_index = scalars_df_index.drop(columns="duration_col") - scalars_pandas_df_index = scalars_pandas_df_index.drop(columns="duration_col") - with ( - tempfile.TemporaryFile() as bf_result_file, - tempfile.TemporaryFile() as pd_result_file, - ): - scalars_df_index.to_json(bf_result_file, orient="table") - # default_handler for arrow types that have no default conversion - scalars_pandas_df_index.to_json( - pd_result_file, orient="table", default_handler=str - ) - - bf_result = bf_result_file.read() - pd_result = pd_result_file.read() - - assert bf_result == pd_result - - -def test_df_to_csv_local_str(scalars_df_index, scalars_pandas_df_index): - bf_result = scalars_df_index.to_csv() - # default_handler for arrow types that have no default conversion - pd_result = scalars_pandas_df_index.to_csv() - - assert bf_result == pd_result - - -def test_df_to_csv_local_file(scalars_df_index, scalars_pandas_df_index): - with ( - tempfile.TemporaryFile() as bf_result_file, - tempfile.TemporaryFile() as pd_result_file, - ): - scalars_df_index.to_csv(bf_result_file) - scalars_pandas_df_index.to_csv(pd_result_file) - - bf_result = bf_result_file.read() - pd_result = pd_result_file.read() - - assert bf_result == pd_result - - -def test_df_to_parquet_local_bytes(scalars_df_index, scalars_pandas_df_index): - # GEOGRAPHY not supported in parquet export. - unsupported = ["geography_col"] - - bf_result = scalars_df_index.drop(columns=unsupported).to_parquet() - # default_handler for arrow types that have no default conversion - pd_result = scalars_pandas_df_index.drop(columns=unsupported).to_parquet() - - assert bf_result == pd_result - - -def test_df_to_parquet_local_file(scalars_df_index, scalars_pandas_df_index): - # GEOGRAPHY not supported in parquet export. - unsupported = ["geography_col"] - with ( - tempfile.TemporaryFile() as bf_result_file, - tempfile.TemporaryFile() as pd_result_file, - ): - scalars_df_index.drop(columns=unsupported).to_parquet(bf_result_file) - scalars_pandas_df_index.drop(columns=unsupported).to_parquet(pd_result_file) - - bf_result = bf_result_file.read() - pd_result = pd_result_file.read() - - assert bf_result == pd_result - - def test_df_to_records(scalars_df_index, scalars_pandas_df_index): unsupported = ["numeric_col"] bf_result = scalars_df_index.drop(columns=unsupported).to_records() @@ -5474,15 +3196,6 @@ def test_df_to_string(scalars_df_index, scalars_pandas_df_index): assert bf_result == pd_result -def test_df_to_html(scalars_df_index, scalars_pandas_df_index): - unsupported = ["numeric_col"] # formatted differently - - bf_result = scalars_df_index.drop(columns=unsupported).to_html() - pd_result = scalars_pandas_df_index.drop(columns=unsupported).to_html() - - assert bf_result == pd_result - - def test_df_to_markdown(scalars_df_index, scalars_pandas_df_index): # Nulls have bug from tabulate https://github.com/astanin/python-tabulate/issues/231 bf_result = scalars_df_index.dropna().to_markdown() @@ -5492,14 +3205,11 @@ def test_df_to_markdown(scalars_df_index, scalars_pandas_df_index): def test_df_to_pickle(scalars_df_index, scalars_pandas_df_index): - with ( - tempfile.TemporaryFile() as bf_result_file, - tempfile.TemporaryFile() as pd_result_file, - ): + with tempfile.TemporaryFile() as bf_result_file, tempfile.TemporaryFile() as pd_result_file: scalars_df_index.to_pickle(bf_result_file) scalars_pandas_df_index.to_pickle(pd_result_file) bf_result = bf_result_file.read() - pd_result = pd_result_file.read() + pd_result = bf_result_file.read() assert bf_result == pd_result @@ -5513,7 +3223,6 @@ def test_df_to_orc(scalars_df_index, scalars_pandas_df_index): "time_col", "timestamp_col", "geography_col", - "duration_col", ] bf_result_file = tempfile.TemporaryFile() @@ -5528,46 +3237,6 @@ def test_df_to_orc(scalars_df_index, scalars_pandas_df_index): assert bf_result == pd_result -@pytest.mark.parametrize( - ("expr",), - [ - ("new_col = int64_col + int64_too",), - ("new_col = (rowindex > 3) | bool_col",), - ("int64_too = bool_col\nnew_col2 = rowindex",), - ], -) -def test_df_eval(scalars_dfs, expr): - # 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 - - bf_result = scalars_df.eval(expr).to_pandas() - pd_result = scalars_pandas_df.eval(expr) - - bigframes.testing.utils.assert_frame_equal(bf_result, pd_result) - - -@pytest.mark.parametrize( - ("expr",), - [ - ("int64_col > int64_too",), - ("bool_col",), - ("((int64_col - int64_too) % @local_var) == 0",), - ], -) -def test_df_query(scalars_dfs, expr): - # TODO: supply a reason why this isn't compatible with pandas 1.x - pytest.importorskip("pandas", minversion="2.0.0") - # local_var is referenced in expressions - local_var = 3 # NOQA - scalars_df, scalars_pandas_df = scalars_dfs - - bf_result = scalars_df.query(expr).to_pandas() - pd_result = scalars_pandas_df.query(expr) - - bigframes.testing.utils.assert_frame_equal(bf_result, pd_result) - - @pytest.mark.parametrize( ("subset", "normalize", "ascending", "dropna"), [ @@ -5577,8 +3246,6 @@ def test_df_query(scalars_dfs, expr): ], ) def test_df_value_counts(scalars_dfs, subset, normalize, ascending, dropna): - if pd.__version__.startswith("1."): - pytest.skip("pandas 1.x produces different column labels.") scalars_df, scalars_pandas_df = scalars_dfs bf_result = ( @@ -5590,25 +3257,28 @@ def test_df_value_counts(scalars_dfs, subset, normalize, ascending, dropna): subset, normalize=normalize, ascending=ascending, dropna=dropna ) - bigframes.testing.utils.assert_series_equal( - bf_result, - pd_result, - check_dtype=False, - check_index_type=False, - ignore_order=True, # different pandas versions inconsistent for tie-handling + # Older pandas version may not have these values, bigframes tries to emulate 2.0+ + pd_result.name = "count" + pd_result.index.names = bf_result.index.names + + pd.testing.assert_series_equal( + bf_result, pd_result, check_dtype=False, check_index_type=False ) @pytest.mark.parametrize( - ("na_option", "method", "ascending", "numeric_only", "pct"), + ("na_option", "method", "ascending", "numeric_only"), [ - ("keep", "average", True, True, True), - ("top", "min", False, False, False), - ("bottom", "max", False, False, True), - ("top", "first", False, False, False), - ("bottom", "dense", False, False, True), + ("keep", "average", True, True), + ("top", "min", False, False), + ("bottom", "max", False, False), + ("top", "first", False, False), + ("bottom", "dense", False, False), ], ) +@pytest.mark.skipif( + True, reason="Blocked by possible pandas rank() regression (b/283278923)" +) def test_df_rank_with_nulls( scalars_df_index, scalars_pandas_df_index, @@ -5616,7 +3286,6 @@ def test_df_rank_with_nulls( method, ascending, numeric_only, - pct, ): unsupported_columns = ["geography_col"] bf_result = ( @@ -5626,7 +3295,6 @@ def test_df_rank_with_nulls( method=method, ascending=ascending, numeric_only=numeric_only, - pct=pct, ) .to_pandas() ) @@ -5637,12 +3305,11 @@ def test_df_rank_with_nulls( method=method, ascending=ascending, numeric_only=numeric_only, - pct=pct, ) .astype(pd.Float64Dtype()) ) - bigframes.testing.utils.assert_frame_equal( + pd.testing.assert_frame_equal( bf_result, pd_result, ) @@ -5654,16 +3321,14 @@ def test_df_bool_interpretation_error(scalars_df_index): def test_query_job_setters(scalars_df_default_index: dataframe.DataFrame): - # if allow_large_results=False, might not create query job - with bigframes.option_context("compute.allow_large_results", True): - job_ids = set() - repr(scalars_df_default_index) - assert scalars_df_default_index.query_job is not None - job_ids.add(scalars_df_default_index.query_job.job_id) - scalars_df_default_index.to_pandas(allow_large_results=True) - job_ids.add(scalars_df_default_index.query_job.job_id) + job_ids = set() + repr(scalars_df_default_index) + assert scalars_df_default_index.query_job is not None + job_ids.add(scalars_df_default_index.query_job.job_id) + scalars_df_default_index.to_pandas() + job_ids.add(scalars_df_default_index.query_job.job_id) - assert len(job_ids) == 2 + assert len(job_ids) == 2 def test_df_cached(scalars_df_index): @@ -5672,58 +3337,10 @@ def test_df_cached(scalars_df_index): ) df = df[df["rowindex_2"] % 2 == 0] - df_cached_copy = df.cache() + df_cached_copy = df._cached() pandas.testing.assert_frame_equal(df.to_pandas(), df_cached_copy.to_pandas()) -def test_df_cached_many_index_cols(scalars_df_index): - index_cols = [ - "int64_too", - "time_col", - "int64_col", - "bool_col", - "date_col", - "timestamp_col", - "string_col", - ] - df = scalars_df_index.set_index(index_cols) - df = df[df["rowindex_2"] % 2 == 0] - - df_cached_copy = df.cache() - pandas.testing.assert_frame_equal(df.to_pandas(), df_cached_copy.to_pandas()) - - -def test_assign_after_binop_row_joins(): - pd_df = pd.DataFrame( - { - "idx1": [1, 1, 1, 1, 2, 2, 2, 2], - "idx2": [10, 10, 20, 20, 10, 10, 20, 20], - "metric1": [10, 14, 2, 13, 6, 2, 9, 5], - "metric2": [25, -3, 8, 2, -1, 0, 0, -4], - }, - dtype=pd.Int64Dtype(), - ).set_index(["idx1", "idx2"]) - bf_df = dataframe.DataFrame(pd_df) - - # Expect implicit joiner to be used, preserving input cardinality rather than getting relational join - bf_df["metric_diff"] = bf_df.metric1 - bf_df.metric2 - pd_df["metric_diff"] = pd_df.metric1 - pd_df.metric2 - - assert_frame_equal(bf_df.to_pandas(), pd_df) - - -def test_df_cache_with_implicit_join(scalars_df_index): - """expectation is that cache will be used, but no explicit join will be performed""" - df = scalars_df_index[["int64_col", "int64_too"]].sort_index().reset_index() + 3 - df.cache() - bf_result = df + (df * 2) - sql = bf_result.sql - - # Very crude asserts, want sql to not use join and not use base table, only reference cached table - assert "JOIN" not in sql - assert "bigframes_testing" not in sql - - def test_df_dot_inline(session): df1 = pd.DataFrame([[1, 2, 3], [2, 5, 7]]) df2 = pd.DataFrame([[2, 4, 8], [1, 5, 10], [3, 6, 9]]) @@ -5739,7 +3356,7 @@ def test_df_dot_inline(session): pd_result[name] = pd_result[name].astype(pd.Int64Dtype()) pd_result.index = pd_result.index.astype(pd.Int64Dtype()) - bigframes.testing.utils.assert_frame_equal( + pd.testing.assert_frame_equal( bf_result, pd_result, ) @@ -5756,7 +3373,7 @@ def test_df_dot( for name in pd_result.columns: pd_result[name] = pd_result[name].astype(pd.Int64Dtype()) - bigframes.testing.utils.assert_frame_equal( + pd.testing.assert_frame_equal( bf_result, pd_result, ) @@ -5773,30 +3390,7 @@ def test_df_dot_operator( for name in pd_result.columns: pd_result[name] = pd_result[name].astype(pd.Int64Dtype()) - bigframes.testing.utils.assert_frame_equal( - bf_result, - pd_result, - ) - - -def test_df_dot_series_inline(): - left = [[1, 2, 3], [2, 5, 7]] - right = [2, 1, 3] - - bf1 = dataframe.DataFrame(left) - bf2 = series.Series(right) - bf_result = bf1.dot(bf2).to_pandas() - - df1 = pd.DataFrame(left) - df2 = pd.Series(right) - pd_result = df1.dot(df2) - - # Patch pandas dtypes for testing parity - # Pandas result is int64 instead of Int64 (nullable) dtype. - pd_result = pd_result.astype(pd.Int64Dtype()) - pd_result.index = pd_result.index.astype(pd.Int64Dtype()) - - bigframes.testing.utils.assert_series_equal( + pd.testing.assert_frame_equal( bf_result, pd_result, ) @@ -5812,7 +3406,7 @@ def test_df_dot_series( # Pandas result is object instead of Int64 (nullable) dtype. pd_result = pd_result.astype(pd.Int64Dtype()) - bigframes.testing.utils.assert_series_equal( + pd.testing.assert_series_equal( bf_result, pd_result, ) @@ -5828,559 +3422,7 @@ def test_df_dot_operator_series( # Pandas result is object instead of Int64 (nullable) dtype. pd_result = pd_result.astype(pd.Int64Dtype()) - bigframes.testing.utils.assert_series_equal( + pd.testing.assert_series_equal( bf_result, pd_result, ) - - -def test_recursion_limit(scalars_df_index): - scalars_df_index = scalars_df_index[["int64_too", "int64_col", "float64_col"]] - for i in range(250): - scalars_df_index = scalars_df_index + 4 - scalars_df_index.to_pandas() - - -@pytest.mark.skipif( - reason="b/366477265: Skip until query complexity error can be reliably triggered." -) -def test_query_complexity_error(scalars_df_index): - # This test requires automatic caching/query decomposition to be turned off - bf_df = scalars_df_index - for _ in range(8): - bf_df = bf_df.merge(bf_df, on="int64_col").head(30) - bf_df = bf_df[bf_df.columns[:20]] - - with pytest.raises( - bigframes.exceptions.QueryComplexityError, match=r"Try using DataFrame\.cache" - ): - bf_df.to_pandas() - - -def test_query_complexity_repeated_joins( - scalars_df_index, scalars_pandas_df_index, with_multiquery_execution -): - pd_df = scalars_pandas_df_index - bf_df = scalars_df_index - for _ in range(8): - # recursively join, resuling in 2^8 - 1 = 255 joins - pd_df = pd_df.merge(pd_df, on="int64_col").head(30) - pd_df = pd_df[pd_df.columns[:20]] - bf_df = bf_df.merge(bf_df, on="int64_col").head(30) - bf_df = bf_df[bf_df.columns[:20]] - - bf_result = bf_df.to_pandas() - pd_result = pd_df - assert_frame_equal(bf_result, pd_result, check_index_type=False) - - -def test_query_complexity_repeated_subtrees( - scalars_df_index, scalars_pandas_df_index, with_multiquery_execution -): - # Recursively union the data, if fully inlined has 10^5 identical root tables. - pd_df = scalars_pandas_df_index - bf_df = scalars_df_index - for _ in range(5): - pd_df = pd.concat(10 * [pd_df]).head(5) - bf_df = bpd.concat(10 * [bf_df]).head(5) - bf_result = bf_df.to_pandas() - pd_result = pd_df - assert_frame_equal(bf_result, pd_result) - - -@pytest.mark.skipif( - sys.version_info >= (3, 12), - # See: https://github.com/python/cpython/issues/112282 - reason="setrecursionlimit has no effect on the Python C stack since Python 3.12.", -) -def test_query_complexity_repeated_analytic(scalars_df_index, scalars_pandas_df_index): - bf_df = scalars_df_index[["int64_col", "int64_too"]] - pd_df = scalars_pandas_df_index[["int64_col", "int64_too"]] - # Uses LAG analytic operator, each in a new SELECT - for _ in range(50): - bf_df = bf_df.diff() - pd_df = pd_df.diff() - bf_result = bf_df.to_pandas() - pd_result = pd_df - assert_frame_equal(bf_result, pd_result) - - -def test_to_gbq_and_create_dataset(session, scalars_df_index, dataset_id_not_created): - dataset_id = dataset_id_not_created - destination_table = f"{dataset_id}.scalars_df" - - result_table = scalars_df_index.to_gbq(destination_table) - assert ( - result_table == destination_table - if destination_table - else result_table is not None - ) - - loaded_scalars_df_index = session.read_gbq(result_table) - assert not loaded_scalars_df_index.empty - - -def test_read_gbq_to_pandas_no_exec(unordered_session: bigframes.Session): - metrics = unordered_session._metrics - execs_pre = metrics.execution_count - df = unordered_session.read_gbq("bigquery-public-data.ml_datasets.penguins") - df.to_pandas() - execs_post = metrics.execution_count - assert df.shape == (344, 7) - assert execs_pre == execs_post - - -def test_to_gbq_table_labels(scalars_df_index): - destination_table = "bigframes-dev.bigframes_tests_sys.table_labels" - result_table = scalars_df_index.to_gbq( - destination_table, labels={"test": "labels"}, if_exists="replace" - ) - client = scalars_df_index._session.bqclient - table = client.get_table(result_table) - assert table.labels - assert table.labels["test"] == "labels" - - -def test_to_gbq_obj_ref_persists(session): - # Test that saving and loading an Object Reference retains its dtype - import uuid - - import google.cloud.bigquery - - sql = """ - SELECT STRUCT('gs://cloud-samples-data/vision/ocr/sign.jpg' AS uri, CAST(NULL AS STRING) AS version, CAST(NULL AS STRING) AS authorizer, PARSE_JSON('{}') AS details) AS uris - """ - df_init = session.read_gbq(sql) - - tmp_table_id = f"bigframes-dev.bigframes_tests_sys.tmp_obj_ref_{uuid.uuid4().hex}" - df_init.to_gbq(tmp_table_id, if_exists="replace") - - client = session.bqclient - table = client.get_table(tmp_table_id) - schema = list(table.schema) - for i, field in enumerate(schema): - if field.name == "uris": - schema[i] = google.cloud.bigquery.SchemaField( - name=field.name, - field_type=field.field_type, - mode=field.mode, - description="bigframes_dtype: OBJ_REF_DTYPE", - fields=field.fields, - ) - break - table.schema = schema - client.update_table(table, ["schema"]) - - bdf = session.read_gbq(tmp_table_id) - - destination_table = "bigframes-dev.bigframes_tests_sys.test_obj_ref_persistence" - bdf.to_gbq(destination_table, if_exists="replace") - - loaded_df = session.read_gbq(destination_table) - assert loaded_df["uris"].dtype == dtypes.OBJ_REF_DTYPE - - -@pytest.mark.parametrize( - ("col_names", "ignore_index"), - [ - pytest.param(["A"], False, id="one_array_false"), - pytest.param(["A"], True, id="one_array_true"), - pytest.param(["B"], False, id="one_float_false"), - pytest.param(["B"], True, id="one_float_true"), - pytest.param(["A", "C"], False, id="two_arrays_false"), - pytest.param(["A", "C"], True, id="two_arrays_true"), - ], -) -def test_dataframe_explode(col_names, ignore_index, session): - data = { - "A": [[0, 1, 2], [], [3, 4]], - "B": 3, - "C": [["a", "b", "c"], np.nan, ["d", "e"]], - } - - df = bpd.DataFrame(data, session=session) - pd_df = df.to_pandas() - pd_result = pd_df.explode(col_names, ignore_index=ignore_index) - bf_result = df.explode(col_names, ignore_index=ignore_index) - - history_pre = session.execution_history().to_dataframe() - queries_pre = ( - len(history_pre[history_pre["job_type"] == "query"]) - if "job_type" in history_pre.columns - else 0 - ) - - bf_materialized = bf_result.to_pandas() - - history_post = session.execution_history().to_dataframe() - queries_post = len(history_post[history_post["job_type"] == "query"]) - - bigframes.testing.utils.assert_frame_equal( - bf_materialized, - pd_result, - check_index_type=False, - check_dtype=False, - ) - # we test this property on this method in particular as compilation - # is non-deterministic and won't use the query cache as implemented - assert (queries_post - queries_pre) <= 1 - - -@pytest.mark.parametrize( - ("ignore_index", "ordered"), - [ - pytest.param(True, True, id="include_index_ordered"), - pytest.param(True, False, id="include_index_unordered"), - pytest.param(False, True, id="ignore_index_ordered"), - ], -) -def test_dataframe_explode_reserve_order(ignore_index, ordered): - data = { - "a": [np.random.randint(0, 10, 10) for _ in range(10)], - "b": [np.random.randint(0, 10, 10) for _ in range(10)], - } - df = bpd.DataFrame(data) - pd_df = pd.DataFrame(data) - - res = df.explode(["a", "b"], ignore_index=ignore_index).to_pandas(ordered=ordered) - pd_res = pd_df.explode(["a", "b"], ignore_index=ignore_index).astype( - pd.Int64Dtype() - ) - bigframes.testing.utils.assert_frame_equal( - res if ordered else res.sort_index(), - pd_res, - check_index_type=False, - ) - - -@pytest.mark.parametrize( - ("col_names"), - [ - pytest.param([], id="empty", marks=pytest.mark.xfail(raises=ValueError)), - pytest.param( - ["A", "A"], id="duplicate", marks=pytest.mark.xfail(raises=ValueError) - ), - pytest.param("unknown", id="unknown", marks=pytest.mark.xfail(raises=KeyError)), - ], -) -def test_dataframe_explode_xfail(col_names): - df = bpd.DataFrame({"A": [[0, 1, 2], [], [3, 4]]}) - df.explode(col_names) - - -@pytest.mark.parametrize( - ("on", "rule", "origin"), - [ - pytest.param("datetime_col", "100D", "start"), - pytest.param("datetime_col", "30W", "start"), - pytest.param("datetime_col", "5M", "epoch"), - pytest.param("datetime_col", "3Q", "start_day"), - pytest.param("datetime_col", "3YE", "start"), - ], -) -def test_resample_with_column( - scalars_df_index, scalars_pandas_df_index, on, rule, origin -): - # TODO: supply a reason why this isn't compatible with pandas 1.x - pytest.importorskip("pandas", minversion="2.2.0") - # TODO: supply a reason why this isn't compatible with pandas 1.x - if pandas.__version__.startswith("3"): - pytest.skip( - "pandas 3.0 behavior diverges for day offsets: https://github.com/pandas-dev/pandas/pull/61985" - ) - bf_result = ( - scalars_df_index.resample(rule=rule, on=on, origin=origin)[ - ["int64_col", "int64_too"] - ] - .max() - .to_pandas() - ) - pd_result = scalars_pandas_df_index.resample(rule=rule, on=on, origin=origin)[ - ["int64_col", "int64_too"] - ].max() - # TODO: (b/484364312) - pd_result.index.names = bf_result.index.names - bigframes.testing.utils.assert_frame_equal( - bf_result, pd_result, check_dtype=False, check_index_type=False - ) - - -@pytest.mark.parametrize("index_col", ["timestamp_col", "datetime_col"]) -@pytest.mark.parametrize( - ("index_append", "level"), - [(True, 1), (False, None), (False, 0)], -) -@pytest.mark.parametrize( - "rule", - [ - # TODO(tswast): support timedeltas and dataoffsets. - # TODO(tswast): support bins that default to "right". - "100d", - "1200h", - ], -) -# TODO(tswast): support "right" -@pytest.mark.parametrize("closed", ["left", None]) -# TODO(tswast): support "right" -@pytest.mark.parametrize("label", ["left", None]) -@pytest.mark.parametrize( - "origin", - ["epoch", "start", "start_day"], # TODO(tswast): support end, end_day. -) -def test_resample_with_index( - scalars_df_index, - scalars_pandas_df_index, - index_append, - level, - index_col, - rule, - closed, - origin, - label, -): - # TODO: supply a reason why this isn't compatible with pandas 1.x - if rule == "100d" and pandas.__version__.startswith("3"): - pytest.skip( - "pandas 3.0 behavior diverges for day offsets: https://github.com/pandas-dev/pandas/pull/61985" - ) - pytest.importorskip("pandas", minversion="2.0.0") - scalars_df_index = scalars_df_index.set_index(index_col, append=index_append) - scalars_pandas_df_index = scalars_pandas_df_index.set_index( - index_col, append=index_append - ) - bf_result = ( - scalars_df_index[["int64_col", "int64_too"]] - .resample(rule=rule, level=level, closed=closed, origin=origin, label=label) - .min() - .to_pandas() - ) - pd_result = ( - scalars_pandas_df_index[["int64_col", "int64_too"]] - .resample(rule=rule, level=level, closed=closed, origin=origin, label=label) - .min() - ) - # TODO: (b/484364312) - pd_result.index.names = bf_result.index.names - assert_frame_equal(bf_result, pd_result) - - -@pytest.mark.parametrize( - ("rule", "origin", "data"), - [ - ( - "5h", - "epoch", - { - "timestamp_col": pd.date_range( - start="2021-01-01 13:00:00", periods=30, freq="1h" - ), - "int64_col": range(30), - "int64_too": range(10, 40), - }, - ), - ( - "75min", - "start_day", - { - "timestamp_col": pd.date_range( - start="2021-01-01 13:00:00", periods=30, freq="10min" - ), - "int64_col": range(30), - "int64_too": range(10, 40), - }, - ), - ( - "7s", - "epoch", - { - "timestamp_col": pd.date_range( - start="2021-01-01 13:00:00", periods=30, freq="1s" - ), - "int64_col": range(30), - "int64_too": range(10, 40), - }, - ), - ], -) -def test_resample_start_time(rule, origin, data): - # TODO: supply a reason why this isn't compatible with pandas 1.x - pytest.importorskip("pandas", minversion="2.0.0") - col = "timestamp_col" - scalars_df_index = bpd.DataFrame(data).set_index(col) - scalars_pandas_df_index = pd.DataFrame(data).set_index(col) - scalars_pandas_df_index.index.name = None - - bf_result = scalars_df_index.resample(rule=rule, origin=origin).min().to_pandas() - - pd_result = scalars_pandas_df_index.resample(rule=rule, origin=origin).min() - - # TODO: (b/484364312) - pd_result.index.names = bf_result.index.names - bigframes.testing.utils.assert_frame_equal( - bf_result, pd_result, check_dtype=False, check_index_type=False - ) - - -@pytest.mark.parametrize( - "dtype", - [ - pytest.param("string[pyarrow]", id="type-string"), - pytest.param(pd.StringDtype(storage="pyarrow"), id="type-literal"), - pytest.param( - {"bool_col": "string[pyarrow]", "int64_col": pd.Float64Dtype()}, - id="multiple-types", - ), - ], -) -def test_df_astype(scalars_dfs, dtype): - bf_df, pd_df = scalars_dfs - target_cols = ["bool_col", "int64_col"] - bf_df = bf_df[target_cols] - pd_df = pd_df[target_cols] - - bf_result = bf_df.astype(dtype).to_pandas() - pd_result = pd_df.astype(dtype) - - bigframes.testing.utils.assert_frame_equal( - bf_result, pd_result, check_index_type=False - ) - - -def test_df_astype_python_types(scalars_dfs): - bf_df, pd_df = scalars_dfs - target_cols = ["bool_col", "int64_col"] - bf_df = bf_df[target_cols] - pd_df = pd_df[target_cols] - - bf_result = bf_df.astype({"bool_col": str, "int64_col": float}).to_pandas() - pd_result = pd_df.astype( - {"bool_col": "string[pyarrow]", "int64_col": pd.Float64Dtype()} - ) - - bigframes.testing.utils.assert_frame_equal( - bf_result, pd_result, check_index_type=False - ) - - -def test_astype_invalid_type_fail(scalars_dfs): - bf_df, _ = scalars_dfs - - with pytest.raises(TypeError, match=r".*Share your use case with.*"): - bf_df.astype(123) - - -def test_agg_with_dict_lists_strings(scalars_dfs): - bf_df, pd_df = scalars_dfs - agg_funcs = { - "int64_too": ["min", "max"], - "int64_col": ["min", "count"], - } - - bf_result = bf_df.agg(agg_funcs).to_pandas() - pd_result = pd_df.agg(agg_funcs) - - bigframes.testing.utils.assert_frame_equal( - bf_result, pd_result, check_dtype=False, check_index_type=False - ) - - -@pytest.mark.skipif( - pandas.__version__.startswith("3"), - # See: https://github.com/python/cpython/issues/112282 - reason="pandas 3.0 miscaculates variance", -) -def test_agg_with_dict_lists_callables(scalars_dfs): - bf_df, pd_df = scalars_dfs - agg_funcs = { - "int64_too": [np.min, np.max], - "int64_col": [np.min, np.var], - } - - bf_result = bf_df.agg(agg_funcs).to_pandas() - pd_result = pd_df.agg(agg_funcs) - - bigframes.testing.utils.assert_frame_equal( - bf_result, pd_result, check_dtype=False, check_index_type=False - ) - - -def test_agg_with_dict_list_and_str(scalars_dfs): - bf_df, pd_df = scalars_dfs - agg_funcs = { - "int64_too": ["min", "max"], - "int64_col": "sum", - } - - bf_result = bf_df.agg(agg_funcs).to_pandas() - pd_result = pd_df.agg(agg_funcs) - - bigframes.testing.utils.assert_frame_equal( - bf_result, pd_result, check_dtype=False, check_index_type=False - ) - - -def test_agg_with_dict_strs(scalars_dfs): - bf_df, pd_df = scalars_dfs - agg_funcs = { - "int64_too": "min", - "int64_col": "sum", - "float64_col": "max", - } - - bf_result = bf_df.agg(agg_funcs).to_pandas() - pd_result = pd_df.agg(agg_funcs) - pd_result.index = pd_result.index.astype("string[pyarrow]") - - bigframes.testing.utils.assert_series_equal( - bf_result, pd_result, check_dtype=False, check_index_type=False - ) - - -def test_df_agg_with_builtins(scalars_dfs): - bf_df, pd_df = scalars_dfs - - bf_result = ( - bf_df[["int64_col", "bool_col"]] - .dropna() - .groupby(bf_df.int64_too % 2) - .agg({"int64_col": [len, sum, min, max, list], "bool_col": [all, any, max]}) - .to_pandas() - ) - pd_result = ( - pd_df[["int64_col", "bool_col"]] - .dropna() - .groupby(pd_df.int64_too % 2) - .agg({"int64_col": [len, sum, min, max, list], "bool_col": [all, any, max]}) - ) - - bigframes.testing.utils.assert_frame_equal( - bf_result, pd_result, check_dtype=False, check_index_type=False - ) - - -def test_agg_with_dict_containing_non_existing_col_raise_key_error(scalars_dfs): - bf_df, _ = scalars_dfs - agg_funcs = { - "int64_too": ["min", "max"], - "nonexisting_col": ["count"], - } - - with pytest.raises(KeyError): - bf_df.agg(agg_funcs) - - -def test_empty_agg_projection_succeeds(): - # Tests that the compiler generates a SELECT 1 fallback for empty aggregations, - # protecting against BigQuery syntax errors when both groups and metrics are empty. - import importlib - - bq = importlib.import_module( - "bigframes_vendored.ibis.backends.sql.compilers.bigquery" - ) - sg = importlib.import_module("bigframes_vendored.sqlglot") - - compiler = bq.BigQueryCompiler() - res = compiler.visit_Aggregate( - "op", parent=sg.table("parent_table"), groups=[], metrics=[] - ) - assert "SELECT 1" in res.sql() diff --git a/tests/system/small/test_dataframe_io.py b/tests/system/small/test_dataframe_io.py index ef21e929afa..8f5d706f621 100644 --- a/tests/system/small/test_dataframe_io.py +++ b/tests/system/small/test_dataframe_io.py @@ -12,187 +12,28 @@ # See the License for the specific language governing permissions and # limitations under the License. -import typing from typing import Tuple import google.api_core.exceptions -import numpy -import numpy.testing import pandas as pd import pyarrow as pa import pytest -from google.cloud import bigquery - -import bigframes -import bigframes.dataframe -import bigframes.dtypes as dtypes -import bigframes.enums -import bigframes.features -import bigframes.pandas as bpd -import bigframes.testing -from bigframes.testing import utils - -pandas_gbq = pytest.importorskip("pandas_gbq") - - -def test_sql_executes(scalars_df_default_index, bigquery_client): - """Test that DataFrame.sql returns executable SQL. - - DF.sql is used in public documentation such as - https://cloud.google.com/blog/products/data-analytics/using-bigquery-dataframes-with-carto-geospatial-tools - as a way to pass a DataFrame on to carto without executing the SQL - immediately. - - Make sure that this SQL can be run outside of BigQuery DataFrames (assuming - similar credentials / access to the referenced tables). - """ - # Do some operations to make for more complex SQL. - df = ( - scalars_df_default_index.drop(columns=["geography_col", "duration_col"]) - .groupby("string_col") - .max() - ) - df.index.name = None # Don't include unnamed indexes. - query = df.sql - - bf_result = df.to_pandas().sort_values("rowindex").reset_index(drop=True) - bq_result = ( - bigquery_client.query_and_wait(query) - .to_dataframe() - .sort_values("rowindex") - .reset_index(drop=True) - ) - bq_result["bytes_col"] = bq_result["bytes_col"].astype(dtypes.BYTES_DTYPE) - bigframes.testing.utils.assert_frame_equal(bf_result, bq_result, check_dtype=False) - - -def test_sql_executes_and_includes_named_index( - scalars_df_default_index, bigquery_client -): - """Test that DataFrame.sql returns executable SQL. - - DF.sql is used in public documentation such as - https://cloud.google.com/blog/products/data-analytics/using-bigquery-dataframes-with-carto-geospatial-tools - as a way to pass a DataFrame on to carto without executing the SQL - immediately. - - Make sure that this SQL can be run outside of BigQuery DataFrames (assuming - similar credentials / access to the referenced tables). - """ - # Do some operations to make for more complex SQL. - df = ( - scalars_df_default_index.drop(columns=["geography_col", "duration_col"]) - .groupby("string_col") - .max() - ) - query = df.sql - - bf_result = df.to_pandas().sort_values("rowindex") - bq_result = ( - bigquery_client.query_and_wait(query) - .to_dataframe() - .set_index("string_col") - .sort_values("rowindex") - ) - bq_result["bytes_col"] = bq_result["bytes_col"].astype(dtypes.BYTES_DTYPE) - bigframes.testing.utils.assert_frame_equal( - bf_result, bq_result, check_dtype=False, check_index_type=False - ) - - -def test_sql_executes_and_includes_named_multiindex( - scalars_df_default_index, bigquery_client -): - """Test that DataFrame.sql returns executable SQL. - - DF.sql is used in public documentation such as - https://cloud.google.com/blog/products/data-analytics/using-bigquery-dataframes-with-carto-geospatial-tools - as a way to pass a DataFrame on to carto without executing the SQL - immediately. - - Make sure that this SQL can be run outside of BigQuery DataFrames (assuming - similar credentials / access to the referenced tables). - """ - # Do some operations to make for more complex SQL. - df = ( - scalars_df_default_index.drop(columns=["geography_col", "duration_col"]) - .groupby(["string_col", "bool_col"]) - .max() - ) - query = df.sql - - bf_result = df.to_pandas().sort_values("rowindex") - bq_result = ( - bigquery_client.query_and_wait(query) - .to_dataframe() - .set_index(["string_col", "bool_col"]) - .sort_values("rowindex") - ) - bq_result["bytes_col"] = bq_result["bytes_col"].astype(dtypes.BYTES_DTYPE) - bigframes.testing.utils.assert_frame_equal( - bf_result, bq_result, check_dtype=False, check_index_type=False - ) - - -def test_to_arrow(scalars_df_default_index, scalars_pandas_df_default_index): - """Verify to_arrow() APIs returns the expected data.""" - expected = pa.Table.from_pandas( - scalars_pandas_df_default_index.drop(columns=["geography_col"]) - ) - with pytest.warns( - bigframes.exceptions.PreviewWarning, - match="to_arrow", - ): - actual = scalars_df_default_index.drop(columns=["geography_col"]).to_arrow() - - # Make string_col match type. Otherwise, pa.Table.from_pandas uses - # LargeStringArray. LargeStringArray is unnecessary because our strings are - # less than 2 GB. - expected = expected.set_column( - expected.column_names.index("string_col"), - pa.field("string_col", pa.string()), - expected["string_col"].cast(pa.string()), - ) - - # Note: the final .equals assertion covers all these checks, but these - # finer-grained assertions are easier to debug. - assert actual.column_names == expected.column_names - for column in actual.column_names: - assert actual[column].equals(expected[column]) - assert actual.equals(expected) +from tests.system.utils import ( + assert_pandas_df_equal_ignore_ordering, + convert_pandas_dtypes, +) +try: + import pandas_gbq # type: ignore +except ImportError: + pandas_gbq = None -def test_to_arrow_multiindex(scalars_df_index, scalars_pandas_df_index): - scalars_df_multiindex = scalars_df_index.set_index(["string_col", "int64_col"]) - scalars_pandas_df_multiindex = scalars_pandas_df_index.set_index( - ["string_col", "int64_col"] - ) - expected = pa.Table.from_pandas( - scalars_pandas_df_multiindex.drop(columns=["geography_col"]) - ) - - with pytest.warns( - bigframes.exceptions.PreviewWarning, - match="to_arrow", - ): - actual = scalars_df_multiindex.drop(columns=["geography_col"]).to_arrow() - - # Make string_col match type. Otherwise, pa.Table.from_pandas uses - # LargeStringArray. LargeStringArray is unnecessary because our strings are - # less than 2 GB. - expected = expected.set_column( - expected.column_names.index("string_col"), - pa.field("string_col", pa.string()), - expected["string_col"].cast(pa.string()), - ) +import typing - # Note: the final .equals assertion covers all these checks, but these - # finer-grained assertions are easier to debug. - assert actual.column_names == expected.column_names - for column in actual.column_names: - assert actual[column].equals(expected[column]) - assert actual.equals(expected) +import bigframes +import bigframes.dataframe +import bigframes.pandas as bpd def test_to_pandas_w_correct_dtypes(scalars_df_default_index): @@ -218,14 +59,7 @@ def test_to_pandas_array_struct_correct_result(session): result = df.to_pandas() expected = pd.DataFrame( { - "array_column": pd.Series( - [[1, 3, 2]], - dtype=( - pd.ArrowDtype(pa.list_(pa.int64())) - if bigframes.features.PANDAS_VERSIONS.is_arrow_list_dtype_usable - else "object" - ), - ), + "array_column": [[1, 3, 2]], "struct_column": pd.Series( [{"string_field": "a", "float_field": 1.2}], dtype=pd.ArrowDtype( @@ -249,97 +83,6 @@ def test_to_pandas_array_struct_correct_result(session): ) -def test_to_pandas_override_global_option(scalars_df_index): - # Direct call to_pandas uses global default setting (allow_large_results=True), - # table has 'bqdf' prefix. - with bigframes.option_context("compute.allow_large_results", True): - scalars_df_index.to_pandas() - table_id = scalars_df_index._query_job.destination.table_id - assert table_id is not None - - # When allow_large_results=False, a query_job object should not be created. - # Therefore, the table_id should remain unchanged. - scalars_df_index.to_pandas(allow_large_results=False) - assert scalars_df_index._query_job.destination.table_id == table_id - - -def test_to_pandas_downsampling_option_override(session): - df = session.read_gbq("bigframes-dev.bigframes_tests_sys.batting") - download_size = 1 - - with pytest.warns( - UserWarning, match="The data size .* exceeds the maximum download limit" - ): - # limits only apply for allow_large_result=True - df = df.to_pandas( - max_download_size=download_size, - sampling_method="head", - allow_large_results=True, - ) - - total_memory_bytes = df.memory_usage(deep=True).sum() - total_memory_mb = total_memory_bytes / (1024 * 1024) - assert total_memory_mb == pytest.approx(download_size, rel=0.5) - - -@pytest.mark.parametrize( - ("kwargs", "message"), - [ - pytest.param( - {"sampling_method": "head"}, - r"DEPRECATED[\S\s]*sampling_method[\S\s]*DataFrame.sample", - id="sampling_method", - ), - pytest.param( - {"random_state": 10}, - r"DEPRECATED[\S\s]*random_state[\S\s]*DataFrame.sample", - id="random_state", - ), - pytest.param( - {"max_download_size": 10}, - r"DEPRECATED[\S\s]*max_download_size[\S\s]*DataFrame.to_pandas_batches", - id="max_download_size", - ), - ], -) -def test_to_pandas_warns_deprecated_parameters(scalars_df_index, kwargs, message): - with pytest.warns(FutureWarning, match=message): - scalars_df_index.to_pandas( - # limits only apply for allow_large_result=True - allow_large_results=True, - **kwargs, - ) - - -def test_to_pandas_dry_run(session, scalars_pandas_df_multi_index): - bf_df = session.read_pandas(scalars_pandas_df_multi_index) - - result = bf_df.to_pandas(dry_run=True) - - assert isinstance(result, pd.Series) - assert len(result) > 0 - - -def test_to_arrow_override_global_option(scalars_df_index): - # Direct call to_arrow uses global default setting (allow_large_results=True), - with bigframes.option_context("compute.allow_large_results", True): - scalars_df_index.to_arrow() - table_id = scalars_df_index._query_job.destination.table_id - assert table_id is not None - - # When allow_large_results=False, a query_job object should not be created. - # Therefore, the table_id should remain unchanged. - scalars_df_index.to_arrow(allow_large_results=False) - assert scalars_df_index._query_job.destination.table_id == table_id - - -def test_to_pandas_batches_populates_total_bytes_processed(scalars_df_default_index): - batches = scalars_df_default_index.sort_values( - "int64_col" - ).to_pandas_batches() # Do a sort to force query execution. - assert batches.total_bytes_processed > 0 - - def test_to_pandas_batches_w_correct_dtypes(scalars_df_default_index): """Verify to_pandas_batches() APIs returns the expected dtypes.""" expected = scalars_df_default_index.dtypes @@ -348,141 +91,9 @@ def test_to_pandas_batches_w_correct_dtypes(scalars_df_default_index): pd.testing.assert_series_equal(actual, expected) -def test_to_pandas_batches_w_empty_dataframe(session): - """Verify to_pandas_batches() APIs returns at least one DataFrame. - - See b/428918844 for additional context. - """ - empty = bpd.DataFrame( - { - "idx1": [], - "idx2": [], - "col1": pd.Series([], dtype="string[pyarrow]"), - "col2": pd.Series([], dtype="Int64"), - }, - session=session, - ).set_index(["idx1", "idx2"], drop=True) - - results = list(empty.to_pandas_batches()) - assert len(results) == 1 - assert list(results[0].index.names) == ["idx1", "idx2"] - assert list(results[0].columns) == ["col1", "col2"] - bigframes.testing.utils.assert_series_equal(results[0].dtypes, empty.dtypes) - - -@pytest.mark.skipif( - bigframes.features.PANDAS_VERSIONS.is_arrow_list_dtype_usable, - reason="Test for pandas 1.x behavior only", -) -def test_to_pandas_batches_preserves_dtypes_for_populated_nested_json_pandas1(session): - """Verifies to_pandas_batches() preserves dtypes for nested JSON in pandas 1.x.""" - sql = """ - SELECT - 0 AS id, - [JSON '{"a":1}', JSON '{"b":2}'] AS json_array, - STRUCT(JSON '{"x":1}' AS json_field, 'test' AS str_field) AS json_struct - """ - df = session.read_gbq(sql, index_col="id") - batches = list(df.to_pandas_batches()) - - assert batches[0].dtypes["json_array"] == "object" - assert isinstance(batches[0].dtypes["json_struct"], pd.ArrowDtype) - - -@pytest.mark.skipif( - not bigframes.features.PANDAS_VERSIONS.is_arrow_list_dtype_usable, - reason="Test for pandas 2.x behavior only", -) -def test_to_pandas_batches_preserves_dtypes_for_populated_nested_json_pandas2(session): - """Verifies to_pandas_batches() preserves dtypes for nested JSON in pandas 2.x.""" - sql = """ - SELECT - 0 AS id, - [JSON '{"a":1}', JSON '{"b":2}'] AS json_array, - STRUCT(JSON '{"x":1}' AS json_field, 'test' AS str_field) AS json_struct - """ - df = session.read_gbq(sql, index_col="id") - batches = list(df.to_pandas_batches()) - - assert isinstance(batches[0].dtypes["json_array"], pd.ArrowDtype) - assert isinstance(batches[0].dtypes["json_array"].pyarrow_dtype, pa.ListType) - assert isinstance(batches[0].dtypes["json_struct"], pd.ArrowDtype) - - -@pytest.mark.skipif( - bigframes.features.PANDAS_VERSIONS.is_arrow_list_dtype_usable, - reason="Test for pandas 1.x behavior only", -) -def test_to_pandas_batches_should_not_error_on_empty_nested_json_pandas1(session): - """Verify to_pandas_batches() works with empty nested JSON types in pandas 1.x.""" - - sql = """ - SELECT - 1 AS id, - [] AS json_array, - STRUCT(NULL AS json_field, 'test2' AS str_field) AS json_struct - """ - df = session.read_gbq(sql, index_col="id") - - # The main point: this should not raise an error - batches = list(df.to_pandas_batches()) - assert sum(len(b) for b in batches) == 1 - - assert batches[0].dtypes["json_array"] == "object" - assert isinstance(batches[0].dtypes["json_struct"], pd.ArrowDtype) - - -@pytest.mark.skipif( - not bigframes.features.PANDAS_VERSIONS.is_arrow_list_dtype_usable, - reason="Test for pandas 2.x behavior only", -) -def test_to_pandas_batches_should_not_error_on_empty_nested_json_pandas2(session): - """Verify to_pandas_batches() works with empty nested JSON types in pandas 2.x.""" - - sql = """ - SELECT - 1 AS id, - [] AS json_array, - STRUCT(NULL AS json_field, 'test2' AS str_field) AS json_struct - """ - df = session.read_gbq(sql, index_col="id") - - # The main point: this should not raise an error - batches = list(df.to_pandas_batches()) - assert sum(len(b) for b in batches) == 1 - - assert isinstance(batches[0].dtypes["json_array"], pd.ArrowDtype) - assert isinstance(batches[0].dtypes["json_struct"], pd.ArrowDtype) - assert isinstance(batches[0].dtypes["json_struct"].pyarrow_dtype, pa.StructType) - - -@pytest.mark.parametrize("allow_large_results", (True, False)) -def test_to_pandas_batches_w_page_size_and_max_results(session, allow_large_results): - """Verify to_pandas_batches() APIs returns the expected page size. - - Regression test for b/407521010. - """ - bf_df = session.read_gbq( - "bigquery-public-data.usa_names.usa_1910_2013", - index_col=bigframes.enums.DefaultIndexKind.NULL, - ) - expected_column_count = len(bf_df.columns) - - batch_count = 0 - for pd_df in bf_df.to_pandas_batches( - page_size=42, allow_large_results=allow_large_results, max_results=42 * 3 - ): - batch_row_count, batch_column_count = pd_df.shape - batch_count += 1 - assert batch_column_count == expected_column_count - assert batch_row_count == 42 - - assert batch_count == 3 - - @pytest.mark.parametrize( - ("index",), - [(True,), (False,)], + ("index"), + [True, False], ) def test_to_csv_index( scalars_dfs: Tuple[bigframes.dataframe.DataFrame, pd.DataFrame], @@ -494,9 +105,12 @@ def test_to_csv_index( """Test the `to_csv` API with the `index` parameter.""" scalars_df, scalars_pandas_df = scalars_dfs index_col = None - path = gcs_folder + f"test_index_df_to_csv_index_{index}*.csv" - if index: - index_col = typing.cast(str, scalars_df.index.name) + if scalars_df.index.name is not None: + path = gcs_folder + f"test_index_df_to_csv_index_{index}*.csv" + if index: + index_col = typing.cast(str, scalars_df.index.name) + else: + path = gcs_folder + f"test_default_index_df_to_csv_index_{index}*.csv" # TODO(swast): Support "date_format" parameter and make sure our # DATETIME/TIMESTAMP column export is the same format as pandas by default. @@ -508,20 +122,18 @@ def test_to_csv_index( dtype = scalars_df.reset_index().dtypes.to_dict() dtype.pop("geography_col") dtype.pop("rowindex") - # read_csv will decode into bytes, numeric inproperly, convert_pandas_dtypes will encode properly from string - dtype.pop("bytes_col") - dtype.pop("numeric_col") gcs_df = pd.read_csv( - utils.get_first_file_from_wildcard(path), + path, dtype=dtype, date_format={"timestamp_col": "YYYY-MM-DD HH:MM:SS Z"}, index_col=index_col, ) - utils.convert_pandas_dtypes(gcs_df, bytes_col=True) + convert_pandas_dtypes(gcs_df, bytes_col=True) gcs_df.index.name = scalars_df.index.name scalars_pandas_df = scalars_pandas_df.copy() scalars_pandas_df.index = scalars_pandas_df.index.astype("int64") + # Ordering should be maintained for tables smaller than 1 GB. pd.testing.assert_frame_equal(gcs_df, scalars_pandas_df) @@ -547,17 +159,14 @@ def test_to_csv_tabs( dtype = scalars_df.reset_index().dtypes.to_dict() dtype.pop("geography_col") dtype.pop("rowindex") - # read_csv will decode into bytes, numeric inproperly, convert_pandas_dtypes will encode properly from string - dtype.pop("bytes_col") - dtype.pop("numeric_col") gcs_df = pd.read_csv( - utils.get_first_file_from_wildcard(path), + path, sep="\t", dtype=dtype, date_format={"timestamp_col": "YYYY-MM-DD HH:MM:SS Z"}, index_col=index_col, ) - utils.convert_pandas_dtypes(gcs_df, bytes_col=True) + convert_pandas_dtypes(gcs_df, bytes_col=True) gcs_df.index.name = scalars_df.index.name scalars_pandas_df = scalars_pandas_df.copy() @@ -571,8 +180,8 @@ def test_to_csv_tabs( ("index"), [True, False], ) -@pytest.mark.skipif(pandas_gbq is None, reason="required by pandas_gbq.read_gbq") -def test_to_gbq_w_index(scalars_dfs, dataset_id, index): +@pytest.mark.skipif(pandas_gbq is None, reason="required by pd.read_gbq") +def test_to_gbq_index(scalars_dfs, dataset_id, index): """Test the `to_gbq` API with the `index` parameter.""" scalars_df, scalars_pandas_df = scalars_dfs destination_table = f"{dataset_id}.test_index_df_to_gbq_{index}" @@ -584,356 +193,61 @@ def test_to_gbq_w_index(scalars_dfs, dataset_id, index): index_col = None df_in.to_gbq(destination_table, if_exists="replace", index=index) - df_out = pandas_gbq.read_gbq(destination_table, index_col=index_col) + df_out = pd.read_gbq(destination_table, index_col=index_col) if index: df_out = df_out.sort_index() else: df_out = df_out.sort_values("rowindex_2").reset_index(drop=True) - utils.convert_pandas_dtypes(df_out, bytes_col=False) - # pandas_gbq.read_gbq interprets bytes_col as object, reconvert to pyarrow binary - df_out["bytes_col"] = df_out["bytes_col"].astype(pd.ArrowDtype(pa.binary())) + convert_pandas_dtypes(df_out, bytes_col=False) expected = scalars_pandas_df.copy() expected.index.name = index_col pd.testing.assert_frame_equal(df_out, expected, check_index_type=False) -def test_to_gbq_if_exists_is_fail(scalars_dfs, dataset_id): - scalars_df, scalars_pandas_df = scalars_dfs - destination_table = f"{dataset_id}.test_to_gbq_if_exists_is_fails" - scalars_df.to_gbq(destination_table) - - gcs_df = pandas_gbq.read_gbq(destination_table, index_col="rowindex") - assert len(gcs_df) == len(scalars_pandas_df) - pd.testing.assert_index_equal(gcs_df.columns, scalars_pandas_df.columns) - - # Test default value is "fails" - with pytest.raises(ValueError, match="Table already exists"): - scalars_df.to_gbq(destination_table) - - with pytest.raises(ValueError, match="Table already exists"): - scalars_df.to_gbq(destination_table, if_exists="fail") - - -def test_to_gbq_if_exists_is_replace(scalars_dfs, dataset_id): - scalars_df, scalars_pandas_df = scalars_dfs - destination_table = f"{dataset_id}.test_to_gbq_if_exists_is_replace" - scalars_df.to_gbq(destination_table) - - gcs_df = pandas_gbq.read_gbq(destination_table, index_col="rowindex") - assert len(gcs_df) == len(scalars_pandas_df) - pd.testing.assert_index_equal(gcs_df.columns, scalars_pandas_df.columns) - - # When replacing a table with same schema - scalars_df.to_gbq(destination_table, if_exists="replace") - gcs_df = pandas_gbq.read_gbq(destination_table, index_col="rowindex") - assert len(gcs_df) == len(scalars_pandas_df) - pd.testing.assert_index_equal(gcs_df.columns, scalars_pandas_df.columns) - - # When replacing a table with same schema but different column order - reordered_df = scalars_df[scalars_df.columns[::-1]] - reordered_df.to_gbq(destination_table, if_exists="replace") - gcs_df = pandas_gbq.read_gbq(destination_table, index_col="rowindex") - assert len(gcs_df) == len(scalars_pandas_df) - pd.testing.assert_index_equal(gcs_df.columns, reordered_df.columns) - - # When replacing a table with different schema - partitial_scalars_df = scalars_df.drop(columns=["string_col"]) - partitial_scalars_df.to_gbq(destination_table, if_exists="replace") - gcs_df = pandas_gbq.read_gbq(destination_table, index_col="rowindex") - assert len(gcs_df) == len(partitial_scalars_df) - pd.testing.assert_index_equal(gcs_df.columns, partitial_scalars_df.columns) - - -def test_to_gbq_if_exists_is_append(scalars_dfs, dataset_id): - scalars_df, scalars_pandas_df = scalars_dfs - destination_table = f"{dataset_id}.test_to_gbq_if_exists_is_append" - scalars_df.to_gbq(destination_table) - - gcs_df = pandas_gbq.read_gbq(destination_table, index_col="rowindex") - assert len(gcs_df) == len(scalars_pandas_df) - pd.testing.assert_index_equal(gcs_df.columns, scalars_pandas_df.columns) - - # When appending to a table with same schema - scalars_df.to_gbq(destination_table, if_exists="append") - gcs_df = pandas_gbq.read_gbq(destination_table, index_col="rowindex") - assert len(gcs_df) == 2 * len(scalars_pandas_df) - pd.testing.assert_index_equal(gcs_df.columns, scalars_pandas_df.columns) - - # When appending to a table with different schema - partitial_scalars_df = scalars_df.drop(columns=["string_col"]) - partitial_scalars_df.to_gbq(destination_table, if_exists="append") - gcs_df = pandas_gbq.read_gbq(destination_table, index_col="rowindex") - assert len(gcs_df) == 3 * len(partitial_scalars_df) - pd.testing.assert_index_equal(gcs_df.columns, scalars_df.columns) - - -def test_to_gbq_w_duplicate_column_names( - scalars_df_index, scalars_pandas_df_index, dataset_id -): - """Test the `to_gbq` API when dealing with duplicate column names.""" - destination_table = f"{dataset_id}.test_to_gbq_w_duplicate_column_names" - - # Renaming 'int64_too' to 'int64_col', which will result in 'int64_too' - # becoming 'int64_col_1' after deduplication. - scalars_df_index = scalars_df_index.rename(columns={"int64_too": "int64_col"}) - scalars_df_index.to_gbq(destination_table, if_exists="replace") - - bf_result = bpd.read_gbq(destination_table, index_col="rowindex").to_pandas() - - pd.testing.assert_series_equal( - scalars_pandas_df_index["int64_col"], bf_result["int64_col"] - ) - pd.testing.assert_series_equal( - scalars_pandas_df_index["int64_too"], - bf_result["int64_col_1"], - check_names=False, - ) - - -def test_to_gbq_w_protected_column_names( - scalars_df_index, scalars_pandas_df_index, dataset_id -): - """ - Column names can't use any of the following prefixes: - - * _TABLE_ - * _FILE_ - * _PARTITION - * _ROW_TIMESTAMP - * __ROOT__ - * _COLIDENTIFIER - - See: https://cloud.google.com/bigquery/docs/schemas#column_names - """ - destination_table = f"{dataset_id}.test_to_gbq_w_protected_column_names" - - scalars_df_index = scalars_df_index.rename( - columns={ - "bool_col": "_Table_Suffix", - "bytes_col": "_file_path", - "date_col": "_PARTITIONDATE", - "datetime_col": "_ROW_TIMESTAMP", - "int64_col": "__ROOT__", - "int64_too": "_COLIDENTIFIER", - "numeric_col": "COLIDENTIFIER", # Create a collision at serialization time. - } - )[ - [ - "_Table_Suffix", - "_file_path", - "_PARTITIONDATE", - "_ROW_TIMESTAMP", - "__ROOT__", - "_COLIDENTIFIER", - "COLIDENTIFIER", - ] - ] - scalars_df_index.to_gbq(destination_table, if_exists="replace") - - bf_result = bpd.read_gbq(destination_table, index_col="rowindex").to_pandas() - - # Leading _ characters are removed to make these columns valid in BigQuery. - expected = scalars_pandas_df_index.rename( - columns={ - "bool_col": "Table_Suffix", - "bytes_col": "file_path", - "date_col": "PARTITIONDATE", - "datetime_col": "ROW_TIMESTAMP", - "int64_col": "ROOT__", - "int64_too": "COLIDENTIFIER", - "numeric_col": "COLIDENTIFIER_1", - } - )[ - [ - "Table_Suffix", - "file_path", - "PARTITIONDATE", - "ROW_TIMESTAMP", - "ROOT__", - "COLIDENTIFIER", - "COLIDENTIFIER_1", - ] - ] - - pd.testing.assert_frame_equal(bf_result, expected) - - -def test_to_gbq_w_flexible_column_names( - scalars_df_index, dataset_id: str, bigquery_client -): - """Test the `to_gbq` API when dealing with flexible column names. - - This test is for BigQuery-backed storage nodes. - - See: https://cloud.google.com/bigquery/docs/schemas#flexible-column-names - """ - destination_table = f"{dataset_id}.test_to_gbq_w_flexible_column_names" - renamed_columns = { - # First column in Japanese (tests unicode). - "bool_col": "最初のカラム", - "bytes_col": "col with space", - # Dots aren't allowed in BigQuery column names, so these should be translated - "date_col": "col.with.dots", - "datetime_col": "col-with-hyphens", - "geography_col": "1start_with_number", - "int64_col": "col_with_underscore", - # Just numbers. - "int64_too": "123", - } - bf_df = scalars_df_index[renamed_columns.keys()].rename(columns=renamed_columns) - assert list(bf_df.columns) == list(renamed_columns.values()) - bf_df.to_gbq(destination_table, index=False) - - table = bigquery_client.get_table(destination_table) - columns = [field.name for field in table.schema] - assert columns == [ - "最初のカラム", - "col with space", - # Dots aren't allowed in BigQuery column names, so these should be translated - "col_with_dots", - "col-with-hyphens", - "1start_with_number", - "col_with_underscore", - "123", - ] - - -def test_to_gbq_w_flexible_column_names_local_node( - session, dataset_id: str, bigquery_client -): - """Test the `to_gbq` API when dealing with flexible column names. - - This test is for local nodes, e.g. read_pandas(), since those may go through - a different code path compared to data that starts in BigQuery. - - See: https://cloud.google.com/bigquery/docs/schemas#flexible-column-names - """ - destination_table = f"{dataset_id}.test_to_gbq_w_flexible_column_names_local_node" - - data = { - # First column in Japanese (tests unicode). - "最初のカラム": [1, 2, 3], - "col with space": [4, 5, 6], - # Dots aren't allowed in BigQuery column names, so these should be translated - "col.with.dots": [7, 8, 9], - "col-with-hyphens": [10, 11, 12], - "1start_with_number": [13, 14, 15], - "col_with_underscore": [16, 17, 18], - "123": [19, 20, 21], - } - pd_df = pd.DataFrame(data) - assert list(pd_df.columns) == list(data.keys()) - bf_df = session.read_pandas(pd_df) - assert list(bf_df.columns) == list(data.keys()) - bf_df.to_gbq(destination_table, index=False) - - table = bigquery_client.get_table(destination_table) - columns = [field.name for field in table.schema] - assert columns == [ - "最初のカラム", - "col with space", - # Dots aren't allowed in BigQuery column names, so these should be translated - "col_with_dots", - "col-with-hyphens", - "1start_with_number", - "col_with_underscore", - "123", - ] - - -def test_to_gbq_w_None_column_names( - scalars_df_index, scalars_pandas_df_index, dataset_id -): - """Test the `to_gbq` API with None as a column name.""" - destination_table = f"{dataset_id}.test_to_gbq_w_none_column_names" - - # pandas 3.0 str datatypes produces nan instead of None, so cast to object - # scalars_df_index.columns = scalars_df_index.columns.astype(object) - scalars_df_index = scalars_df_index.rename(columns={"int64_too": None}) - scalars_df_index.to_gbq(destination_table, if_exists="replace") - - bf_result = bpd.read_gbq(destination_table, index_col="rowindex").to_pandas() - - pd.testing.assert_series_equal( - scalars_pandas_df_index["int64_col"], bf_result["int64_col"] - ) - pd.testing.assert_series_equal( - scalars_pandas_df_index["int64_too"], - bf_result["bigframes_unnamed_column"], - check_names=False, - ) - - @pytest.mark.parametrize( - "clustering_columns", + ("if_exists", "expected_index"), [ - pytest.param(["int64_col", "geography_col"]), + pytest.param("replace", 1), + pytest.param("append", 2), pytest.param( - ["float64_col"], - marks=pytest.mark.xfail(raises=google.api_core.exceptions.BadRequest), + "fail", + 0, + marks=pytest.mark.xfail( + raises=google.api_core.exceptions.Conflict, + ), ), pytest.param( - ["int64_col", "int64_col"], - marks=pytest.mark.xfail(raises=ValueError), + "unknown", + 0, + marks=pytest.mark.xfail( + raises=ValueError, + ), ), ], ) -def test_to_gbq_w_clustering( +@pytest.mark.skipif(pandas_gbq is None, reason="required by pd.read_gbq") +def test_to_gbq_if_exists( scalars_df_default_index, + scalars_pandas_df_default_index, dataset_id, - bigquery_client, - clustering_columns, -): - """Test the `to_gbq` API for creating clustered tables.""" - destination_table = ( - f"{dataset_id}.test_to_gbq_clustering_{'_'.join(clustering_columns)}" - ) - - scalars_df_default_index.to_gbq( - destination_table, clustering_columns=clustering_columns - ) - table = bigquery_client.get_table(destination_table) - - assert list(table.clustering_fields) == clustering_columns - assert table.expires is None - - -def test_to_gbq_w_clustering_no_destination( - scalars_df_default_index, - bigquery_client, + if_exists, + expected_index, ): - """Test the `to_gbq` API for creating clustered tables without destination.""" - clustering_columns = ["int64_col", "geography_col"] - destination_table = scalars_df_default_index.to_gbq( - clustering_columns=clustering_columns - ) - table = bigquery_client.get_table(destination_table) - - assert list(table.clustering_fields) == clustering_columns - assert table.expires is not None - + """Test the `to_gbq` API with the `if_exists` parameter.""" + destination_table = f"{dataset_id}.test_to_gbq_if_exists_{if_exists}" -def test_to_gbq_w_clustering_existing_table( - scalars_df_default_index, - dataset_id, - bigquery_client, -): - destination_table = f"{dataset_id}.test_to_gbq_w_clustering_existing_table" scalars_df_default_index.to_gbq(destination_table) + scalars_df_default_index.to_gbq(destination_table, if_exists=if_exists) - table = bigquery_client.get_table(destination_table) - assert table.clustering_fields is None - assert table.expires is None - - with pytest.raises(ValueError, match="Table clustering fields cannot be changed"): - clustering_columns = ["int64_col"] - scalars_df_default_index.to_gbq( - destination_table, - if_exists="replace", - clustering_columns=clustering_columns, - ) + gcs_df = pd.read_gbq(destination_table) + assert len(gcs_df.index) == expected_index * len( + scalars_pandas_df_default_index.index + ) + pd.testing.assert_index_equal( + gcs_df.columns, scalars_pandas_df_default_index.columns + ) def test_to_gbq_w_invalid_destination_table(scalars_df_index): @@ -941,119 +255,6 @@ def test_to_gbq_w_invalid_destination_table(scalars_df_index): scalars_df_index.to_gbq("table_id") -def test_to_gbq_w_json(bigquery_client): - """Test the `to_gbq` API can get a JSON column.""" - s1 = bpd.Series([1, 2, 3, 4]) - s2 = bpd.Series( - ['"a"', "1", "false", '["a", {"b": 1}]', '{"c": [1, 2, 3]}'], - dtype=dtypes.JSON_DTYPE, - ) - - df = bpd.DataFrame({"id": s1, "json_col": s2}) - destination_table = df.to_gbq() - table = bigquery_client.get_table(destination_table) - - assert table.schema[1].name == "json_col" - assert table.schema[1].field_type == "JSON" - - -def test_to_gbq_with_timedelta(bigquery_client, dataset_id): - destination_table = f"{dataset_id}.test_to_gbq_with_timedelta" - s1 = bpd.Series([1, 2, 3, 4]) - s2 = bpd.to_timedelta(bpd.Series([1, 2, 3, 4]), unit="s") - df = bpd.DataFrame({"id": s1, "timedelta_col": s2}) - - df.to_gbq(destination_table) - table = bigquery_client.get_table(destination_table) - - assert table.schema[1].name == "timedelta_col" - assert table.schema[1].field_type == "INTEGER" - assert dtypes.TIMEDELTA_DESCRIPTION_TAG in table.schema[1].description - - -def test_gbq_round_trip_with_timedelta(session, dataset_id): - destination_table = f"{dataset_id}.test_gbq_roundtrip_with_timedelta" - df = pd.DataFrame( - { - "col_1": [1], - "col_2": [pd.Timedelta(1, "s")], - "col_3": [1.1], - } - ) - bpd.DataFrame(df).to_gbq(destination_table) - - result = session.read_gbq(destination_table) - - assert result["col_1"].dtype == dtypes.INT_DTYPE - assert result["col_2"].dtype == dtypes.TIMEDELTA_DTYPE - assert result["col_3"].dtype == dtypes.FLOAT_DTYPE - - -def test_to_gbq_timedelta_tag_ignored_when_appending(bigquery_client, dataset_id): - # First, create a table - destination_table = f"{dataset_id}.test_to_gbq_timedelta_tag_ignored_when_appending" - schema = [bigquery.SchemaField("my_col", "INTEGER")] - bigquery_client.create_table(bigquery.Table(destination_table, schema)) - - # Then, append to that table with timedelta values - df = pd.DataFrame( - { - "my_col": [pd.Timedelta(1, "s")], - } - ) - bpd.DataFrame(df).to_gbq(destination_table, if_exists="append") - - table = bigquery_client.get_table(destination_table) - assert table.schema[0].name == "my_col" - assert table.schema[0].field_type == "INTEGER" - assert table.schema[0].description is None - - -def test_to_gbq_obj_ref(session, dataset_id: str, bigquery_client): - import uuid - - import google.cloud.bigquery - - destination_table = f"{dataset_id}.test_to_gbq_obj_ref" - sql = """ - SELECT STRUCT('gs://cloud-samples-data/vision/ocr/sign.jpg' AS uri, CAST(NULL AS STRING) AS version, CAST(NULL AS STRING) AS authorizer, PARSE_JSON('{}') AS details) AS uri_col - """ - df_init = session.read_gbq(sql) - - tmp_table_id = f"{dataset_id}.tmp_obj_ref_{uuid.uuid4().hex}" - df_init.to_gbq(tmp_table_id, if_exists="replace") - - client = session.bqclient - table = client.get_table(tmp_table_id) - schema = list(table.schema) - for i, field in enumerate(schema): - if field.name == "uri_col": - schema[i] = google.cloud.bigquery.SchemaField( - name=field.name, - field_type=field.field_type, - mode=field.mode, - description="bigframes_dtype: OBJ_REF_DTYPE", - fields=field.fields, - ) - break - table.schema = schema - client.update_table(table, ["schema"]) - - df = session.read_gbq(tmp_table_id) - df = df.rename(columns={"uri_col": "obj_ref_col"}) - - df.to_gbq(destination_table, if_exists="replace") - - table = bigquery_client.get_table(destination_table) - obj_ref_field = next(f for f in table.schema if f.name == "obj_ref_col") - assert obj_ref_field.field_type == "RECORD" - assert obj_ref_field.description == "bigframes_dtype: OBJ_REF_DTYPE" - - reloaded_df = session.read_gbq(destination_table) - assert reloaded_df["obj_ref_col"].dtype == dtypes.OBJ_REF_DTYPE - assert len(reloaded_df) == 1 - - @pytest.mark.parametrize( ("index"), [True, False], @@ -1063,8 +264,11 @@ def test_to_json_index_invalid_orient( gcs_folder: str, index: bool, ): - scalars_df, _ = scalars_dfs - path = gcs_folder + f"test_index_df_to_json_index_{index}*.jsonl" + scalars_df, scalars_pandas_df = scalars_dfs + if scalars_df.index.name is not None: + path = gcs_folder + f"test_index_df_to_json_index_{index}*.jsonl" + else: + path = gcs_folder + f"test_default_index_df_to_json_index_{index}*.jsonl" with pytest.raises(ValueError): scalars_df.to_json(path, index=index, lines=True) @@ -1078,8 +282,11 @@ def test_to_json_index_invalid_lines( gcs_folder: str, index: bool, ): - scalars_df, _ = scalars_dfs - path = gcs_folder + f"test_index_df_to_json_index_{index}.jsonl" + scalars_df, scalars_pandas_df = scalars_dfs + if scalars_df.index.name is not None: + path = gcs_folder + f"test_index_df_to_json_index_{index}.jsonl" + else: + path = gcs_folder + f"test_default_index_df_to_json_index_{index}.jsonl" with pytest.raises(NotImplementedError): scalars_df.to_json(path, index=index) @@ -1093,21 +300,18 @@ def test_to_json_index_records_orient( gcs_folder: str, index: bool, ): - """Test the `to_json` API with the `index` parameter. - - Uses the scalable options orient='records' and lines=True. - """ + """Test the `to_json` API with the `index` parameter.""" scalars_df, scalars_pandas_df = scalars_dfs - path = gcs_folder + f"test_index_df_to_json_index_{index}*.jsonl" + if scalars_df.index.name is not None: + path = gcs_folder + f"test_index_df_to_json_index_{index}*.jsonl" + else: + path = gcs_folder + f"test_default_index_df_to_json_index_{index}*.jsonl" + """ Test the `to_json` API with `orient` is `records` and `lines` is True""" scalars_df.to_json(path, index=index, orient="records", lines=True) - gcs_df = pd.read_json( - utils.get_first_file_from_wildcard(path), - lines=True, - convert_dates=["datetime_col"], - ) - utils.convert_pandas_dtypes(gcs_df, bytes_col=True) + gcs_df = pd.read_json(path, lines=True, convert_dates=["datetime_col"]) + convert_pandas_dtypes(gcs_df, bytes_col=True) if index and scalars_df.index.name is not None: gcs_df = gcs_df.set_index(scalars_df.index.name) @@ -1130,7 +334,11 @@ def test_to_parquet_index(scalars_dfs, gcs_folder, index): """Test the `to_parquet` API with the `index` parameter.""" scalars_df, scalars_pandas_df = scalars_dfs scalars_pandas_df = scalars_pandas_df.copy() - path = gcs_folder + f"test_index_df_to_parquet_{index}*.parquet" + + if scalars_df.index.name is not None: + path = gcs_folder + f"test_index_df_to_parquet_{index}*.parquet" + else: + path = gcs_folder + f"test_default_index_df_to_parquet_{index}*.parquet" # TODO(b/268693993): Type GEOGRAPHY is not currently supported for parquet. scalars_df = scalars_df.drop(columns="geography_col") @@ -1141,8 +349,8 @@ def test_to_parquet_index(scalars_dfs, gcs_folder, index): # table. scalars_df.to_parquet(path, index=index) - gcs_df = pd.read_parquet(utils.get_first_file_from_wildcard(path)) - utils.convert_pandas_dtypes(gcs_df, bytes_col=False) + gcs_df = pd.read_parquet(path.replace("*", "000000000000")) + convert_pandas_dtypes(gcs_df, bytes_col=False) if index and scalars_df.index.name is not None: gcs_df = gcs_df.set_index(scalars_df.index.name) @@ -1154,9 +362,7 @@ def test_to_parquet_index(scalars_dfs, gcs_folder, index): scalars_pandas_df.index = scalars_pandas_df.index.astype("Int64") # Ordering should be maintained for tables smaller than 1 GB. - pd.testing.assert_frame_equal( - gcs_df.drop("bytes_col", axis=1), scalars_pandas_df.drop("bytes_col", axis=1) - ) + pd.testing.assert_frame_equal(gcs_df, scalars_pandas_df) def test_to_sql_query_unnamed_index_included( @@ -1164,19 +370,17 @@ def test_to_sql_query_unnamed_index_included( scalars_df_default_index: bpd.DataFrame, scalars_pandas_df_default_index: pd.DataFrame, ): - bf_df = scalars_df_default_index.reset_index(drop=True).drop(columns="duration_col") + bf_df = scalars_df_default_index.reset_index(drop=True) sql, idx_ids, idx_labels = bf_df._to_sql_query(include_index=True) assert len(idx_labels) == 1 assert len(idx_ids) == 1 assert idx_labels[0] is None assert idx_ids[0].startswith("bigframes") - pd_df = scalars_pandas_df_default_index.reset_index(drop=True).drop( - columns="duration_col" - ) + pd_df = scalars_pandas_df_default_index.reset_index(drop=True) roundtrip = session.read_gbq(sql, index_col=idx_ids) roundtrip.index.names = [None] - utils.assert_frame_equal(roundtrip.to_pandas(), pd_df, check_index_type=False) + assert_pandas_df_equal_ignore_ordering(roundtrip.to_pandas(), pd_df) def test_to_sql_query_named_index_included( @@ -1184,20 +388,16 @@ def test_to_sql_query_named_index_included( scalars_df_default_index: bpd.DataFrame, scalars_pandas_df_default_index: pd.DataFrame, ): - bf_df = scalars_df_default_index.set_index("rowindex_2", drop=True).drop( - columns="duration_col" - ) + bf_df = scalars_df_default_index.set_index("rowindex_2", drop=True) sql, idx_ids, idx_labels = bf_df._to_sql_query(include_index=True) assert len(idx_labels) == 1 assert len(idx_ids) == 1 assert idx_labels[0] == "rowindex_2" assert idx_ids[0] == "rowindex_2" - pd_df = scalars_pandas_df_default_index.set_index("rowindex_2", drop=True).drop( - columns="duration_col" - ) + pd_df = scalars_pandas_df_default_index.set_index("rowindex_2", drop=True) roundtrip = session.read_gbq(sql, index_col=idx_ids) - utils.assert_frame_equal(roundtrip.to_pandas(), pd_df) + assert_pandas_df_equal_ignore_ordering(roundtrip.to_pandas(), pd_df) def test_to_sql_query_unnamed_index_excluded( @@ -1205,18 +405,14 @@ def test_to_sql_query_unnamed_index_excluded( scalars_df_default_index: bpd.DataFrame, scalars_pandas_df_default_index: pd.DataFrame, ): - bf_df = scalars_df_default_index.reset_index(drop=True).drop(columns="duration_col") + bf_df = scalars_df_default_index.reset_index(drop=True) sql, idx_ids, idx_labels = bf_df._to_sql_query(include_index=False) assert len(idx_labels) == 0 assert len(idx_ids) == 0 - pd_df = scalars_pandas_df_default_index.reset_index(drop=True).drop( - columns="duration_col" - ) + pd_df = scalars_pandas_df_default_index.reset_index(drop=True) roundtrip = session.read_gbq(sql) - utils.assert_frame_equal( - roundtrip.to_pandas(), pd_df, check_index_type=False, ignore_order=True - ) + assert_pandas_df_equal_ignore_ordering(roundtrip.to_pandas(), pd_df) def test_to_sql_query_named_index_excluded( @@ -1224,28 +420,13 @@ def test_to_sql_query_named_index_excluded( scalars_df_default_index: bpd.DataFrame, scalars_pandas_df_default_index: pd.DataFrame, ): - bf_df = scalars_df_default_index.set_index("rowindex_2", drop=True).drop( - columns="duration_col" - ) + bf_df = scalars_df_default_index.set_index("rowindex_2", drop=True) sql, idx_ids, idx_labels = bf_df._to_sql_query(include_index=False) assert len(idx_labels) == 0 assert len(idx_ids) == 0 - pd_df = ( - scalars_pandas_df_default_index.set_index("rowindex_2", drop=True) - .reset_index(drop=True) - .drop(columns="duration_col") - ) + pd_df = scalars_pandas_df_default_index.set_index( + "rowindex_2", drop=True + ).reset_index(drop=True) roundtrip = session.read_gbq(sql) - utils.assert_frame_equal( - roundtrip.to_pandas(), pd_df, check_index_type=False, ignore_order=True - ) - - -def test_to_numpy(scalars_dfs): - bf_df, pd_df = scalars_dfs - - bf_result = numpy.array(bf_df[["int64_too"]], dtype="int64") - pd_result = numpy.array(pd_df[["int64_too"]], dtype="int64") - - numpy.testing.assert_array_equal(bf_result, pd_result) + assert_pandas_df_equal_ignore_ordering(roundtrip.to_pandas(), pd_df) diff --git a/tests/system/small/test_encryption.py b/tests/system/small/test_encryption.py deleted file mode 100644 index db87184371b..00000000000 --- a/tests/system/small/test_encryption.py +++ /dev/null @@ -1,267 +0,0 @@ -# Copyright 2023 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 random - -import pandas -import pytest -from google.cloud import bigquery - -import bigframes -import bigframes.ml.linear_model -from bigframes.testing import utils - - -@pytest.fixture(scope="module") -def bq_cmek() -> str: - """Customer managed encryption key to encrypt BigQuery data at rest. - - This is of the form projects/PROJECT_ID/locations/LOCATION/keyRings/KEY_RING/cryptoKeys/KEY - - See https://cloud.google.com/bigquery/docs/customer-managed-encryption for steps. - """ - - # NOTE: This key is manually set up through the cloud console - # TODO(shobs): Automate the the key creation during the test. This will - # require extra IAM privileges for the test runner. - return "projects/bigframes-dev-perf/locations/us/keyRings/bigframesKeyRing/cryptoKeys/bigframesKey" - - -@pytest.fixture(scope="module") -def session_with_bq_cmek(bq_cmek) -> bigframes.Session: - # allow_large_results = False might not create table, and therefore no encryption config - session = bigframes.Session(bigframes.BigQueryOptions(kms_key_name=bq_cmek)) - - return session - - -def _assert_bq_table_is_encrypted( - df: bigframes.dataframe.DataFrame, - cmek: str, - session: bigframes.Session, -): - # Materialize the data in BQ - df.to_gbq() - - # The df should be backed by a query job with intended encryption on the result table - assert df.query_job is not None - assert df.query_job.destination_encryption_configuration.kms_key_name.startswith( - cmek - ) - - # The result table should exist with the intended encryption - table = session.bqclient.get_table(df.query_job.destination) - assert table.encryption_configuration.kms_key_name == cmek - - -def test_session_query_job(bq_cmek, session_with_bq_cmek): - if not bq_cmek: # pragma: NO COVER - pytest.skip("no cmek set for testing") # pragma: NO COVER - - query_job = session_with_bq_cmek._loader._start_query_with_job( - "SELECT 123", job_config=bigquery.QueryJobConfig(use_query_cache=False) - ) - query_job.result() - - assert query_job.destination_encryption_configuration.kms_key_name.startswith( - bq_cmek - ) - - # The result table should exist with the intended encryption - table = session_with_bq_cmek.bqclient.get_table(query_job.destination) - assert table.encryption_configuration.kms_key_name == bq_cmek - - -def test_read_gbq(bq_cmek, session_with_bq_cmek, scalars_table_id): - if not bq_cmek: # pragma: NO COVER - pytest.skip("no cmek set for testing") # pragma: NO COVER - - # Read the BQ table - df = session_with_bq_cmek.read_gbq(scalars_table_id) - - # Assert encryption - _assert_bq_table_is_encrypted(df, bq_cmek, session_with_bq_cmek) - - -def test_df_apis(bq_cmek, session_with_bq_cmek, scalars_table_id): - if not bq_cmek: # pragma: NO COVER - pytest.skip("no cmek set for testing") # pragma: NO COVER - - # Read a BQ table and assert encryption - df = session_with_bq_cmek.read_gbq(scalars_table_id) - - # Perform a few dataframe operations and assert encryption - df1 = df.dropna() - _assert_bq_table_is_encrypted(df1, bq_cmek, session_with_bq_cmek) - - df2 = df1.head() - _assert_bq_table_is_encrypted(df2, bq_cmek, session_with_bq_cmek) - - -@pytest.mark.parametrize( - "engine", - [ - pytest.param("bigquery", id="bq_engine"), - pytest.param( - None, - id="default_engine", - ), - ], -) -def test_read_csv_gcs( - bq_cmek, session_with_bq_cmek, scalars_df_index, gcs_folder, engine -): - if not bq_cmek: # pragma: NO COVER - pytest.skip("no cmek set for testing") # pragma: NO COVER - - # Let's make the source data non-deterministic so that the test doesn't run - # into a BQ caching path - df = scalars_df_index.copy() - df["int_random"] = random.randint(0, 1_000_000_000) - - # Export the dataframe to a csv in gcs - write_path = gcs_folder + "test_read_csv_gcs_bigquery_engine*.csv" - read_path = ( - utils.get_first_file_from_wildcard(write_path) if engine is None else write_path - ) - df.to_csv(write_path) - - # Read the gcs csv - df = session_with_bq_cmek.read_csv(read_path, engine=engine) - - # Assert encryption - _assert_bq_table_is_encrypted(df, bq_cmek, session_with_bq_cmek) - - -def test_to_gbq(bq_cmek, session_with_bq_cmek, scalars_table_id): - if not bq_cmek: # pragma: NO COVER - pytest.skip("no cmek set for testing") # pragma: NO COVER - - # Read a BQ table and assert encryption - df = session_with_bq_cmek.read_gbq(scalars_table_id) - _assert_bq_table_is_encrypted(df, bq_cmek, session_with_bq_cmek) - - # Modify the dataframe and assert encryption - df = df.dropna().head() - _assert_bq_table_is_encrypted(df, bq_cmek, session_with_bq_cmek) - - # Write the result to BQ and assert encryption - output_table_id = df.to_gbq() - output_table = session_with_bq_cmek.bqclient.get_table(output_table_id) - assert output_table.encryption_configuration.kms_key_name == bq_cmek - - # Write the result to BQ custom table and assert encryption - session_with_bq_cmek.bqclient.get_table(output_table_id) - output_table_ref = session_with_bq_cmek._anon_dataset_manager.allocate_temp_table() - output_table_id = str(output_table_ref) - df.to_gbq(output_table_id) - output_table = session_with_bq_cmek.bqclient.get_table(output_table_id) - assert output_table.encryption_configuration.kms_key_name == bq_cmek - - # Lastly, assert that the encryption is not because of any default set at - # the dataset level - output_table_dataset = session_with_bq_cmek.bqclient.get_dataset( - output_table.dataset_id - ) - assert output_table_dataset.default_encryption_configuration is None - - -def test_read_pandas(bq_cmek, session_with_bq_cmek): - if not bq_cmek: # pragma: NO COVER - pytest.skip("no cmek set for testing") # pragma: NO COVER - - # Read a pandas dataframe - df = session_with_bq_cmek.read_pandas( - pandas.DataFrame([random.randint(0, 1_000_000_000)]) - ) - - # Assert encryption - _assert_bq_table_is_encrypted(df, bq_cmek, session_with_bq_cmek) - - -def test_read_pandas_large(bq_cmek, session_with_bq_cmek): - if not bq_cmek: # pragma: NO COVER - pytest.skip("no cmek set for testing") # pragma: NO COVER - - # Read a pandas dataframe large enough to trigger a BQ load job - df = session_with_bq_cmek.read_pandas(pandas.DataFrame(range(10_000))) - - # Assert encryption - _assert_bq_table_is_encrypted(df, bq_cmek, session_with_bq_cmek) - - -def test_kms_encryption_bqml(bq_cmek, session_with_bq_cmek, penguins_table_id): - if not bq_cmek: # pragma: NO COVER - pytest.skip("no cmek set for testing") # pragma: NO COVER - - model = bigframes.ml.linear_model.LinearRegression() - df = session_with_bq_cmek.read_gbq(penguins_table_id).dropna() - X_train = df[ - [ - "species", - "island", - "culmen_length_mm", - "culmen_depth_mm", - "flipper_length_mm", - "sex", - ] - ] - y_train = df[["body_mass_g"]] - model.fit(X_train, y_train) - - assert model is not None - assert model._bqml_model is not None - assert model._bqml_model.model.encryption_configuration is not None - assert model._bqml_model.model.encryption_configuration.kms_key_name == bq_cmek - - # Assert that model exists in BQ with intended encryption - model_bq = session_with_bq_cmek.bqclient.get_model(model._bqml_model.model_name) - assert model_bq.encryption_configuration.kms_key_name == bq_cmek - - # Explicitly save the model to a destination and assert that encryption holds - model_ref = model._bqml_model_factory._create_model_ref( - session_with_bq_cmek._anonymous_dataset - ) - model_ref_full_name = ( - f"{model_ref.project}.{model_ref.dataset_id}.{model_ref.model_id}" - ) - new_model = model.to_gbq(model_ref_full_name) - assert new_model._bqml_model is not None - assert new_model._bqml_model.model.encryption_configuration is not None - assert new_model._bqml_model.model.encryption_configuration.kms_key_name == bq_cmek - - # Assert that model exists in BQ with intended encryption - model_bq = session_with_bq_cmek.bqclient.get_model(new_model._bqml_model.model_name) - assert model_bq.encryption_configuration.kms_key_name == bq_cmek - - # Assert that model registration keeps the encryption - # Note that model registration only creates an entry (metadata) to be - # included in the Vertex AI Model Registry. See for more details - # https://cloud.google.com/bigquery/docs/update_vertex#add-existing. - # When use deploys the model to an endpoint from the Model Registry then - # they can specify an encryption key to further protect the artifacts at - # rest on the Vertex AI side. See for more details: - # https://cloud.google.com/vertex-ai/docs/general/deployment#deploy_a_model_to_an_endpoint, - # https://cloud.google.com/vertex-ai/docs/general/cmek#create_resources_with_the_kms_key. - # bigframes.ml does not provide any API for the model deployment. - model_registered = new_model.register() - assert model_registered._bqml_model is not None - assert model_registered._bqml_model.model.encryption_configuration is not None - assert ( - model_registered._bqml_model.model.encryption_configuration.kms_key_name - == bq_cmek - ) - model_bq = session_with_bq_cmek.bqclient.get_model(new_model._bqml_model.model_name) - assert model_bq.encryption_configuration.kms_key_name == bq_cmek diff --git a/tests/system/small/test_groupby.py b/tests/system/small/test_groupby.py index 8dde3146434..05154f7ab7d 100644 --- a/tests/system/small/test_groupby.py +++ b/tests/system/small/test_groupby.py @@ -12,16 +12,10 @@ # See the License for the specific language governing permissions and # limitations under the License. -import numpy as np import pandas as pd import pytest import bigframes.pandas as bpd -import bigframes.testing.utils - -# ================= -# DataFrame.groupby -# ================= @pytest.mark.parametrize( @@ -51,25 +45,7 @@ def test_dataframe_groupby_numeric_aggregate( pd_result = operator(scalars_pandas_df_index[col_names].groupby("string_col")) bf_result_computed = bf_result.to_pandas() # Pandas std function produces float64, not matching Float64 from bigframes - bigframes.testing.utils.assert_frame_equal( - pd_result, bf_result_computed, check_dtype=False - ) - - -def test_dataframe_groupby_head(scalars_df_index, scalars_pandas_df_index): - col_names = ["int64_too", "float64_col", "int64_col", "bool_col", "string_col"] - bf_result = scalars_df_index[col_names].groupby("bool_col").head(2).to_pandas() - pd_result = scalars_pandas_df_index[col_names].groupby("bool_col").head(2) - bigframes.testing.utils.assert_frame_equal(pd_result, bf_result, check_dtype=False) - - -def test_dataframe_groupby_len(scalars_df_index, scalars_pandas_df_index): - col_names = ["int64_too", "float64_col", "int64_col", "bool_col", "string_col"] - - bf_result = len(scalars_df_index[col_names].groupby("bool_col")) - pd_result = len(scalars_pandas_df_index[col_names].groupby("bool_col")) - - assert bf_result == pd_result + pd.testing.assert_frame_equal(pd_result, bf_result_computed, check_dtype=False) def test_dataframe_groupby_median(scalars_df_index, scalars_pandas_df_index): @@ -88,75 +64,15 @@ def test_dataframe_groupby_median(scalars_df_index, scalars_pandas_df_index): assert ((pd_min <= bf_result_computed) & (bf_result_computed <= pd_max)).all().all() -@pytest.mark.parametrize( - ("q"), - [ - ([0.2, 0.4, 0.6, 0.8]), - (0.11), - ], -) -def test_dataframe_groupby_quantile(scalars_df_index, scalars_pandas_df_index, q): - col_names = ["int64_too", "float64_col", "int64_col", "string_col"] - bf_result = ( - scalars_df_index[col_names].groupby("string_col").quantile(q) - ).to_pandas() - pd_result = scalars_pandas_df_index[col_names].groupby("string_col").quantile(q) - bigframes.testing.utils.assert_frame_equal( - pd_result, bf_result, check_dtype=False, check_index_type=False - ) - - -@pytest.mark.parametrize( - ("na_option", "method", "ascending", "pct"), - [ - ( - "keep", - "average", - True, - False, - ), - ("top", "min", False, False), - ("bottom", "max", False, False), - ("top", "first", False, True), - ("bottom", "dense", False, True), - ], -) -def test_dataframe_groupby_rank( - scalars_df_index, scalars_pandas_df_index, na_option, method, ascending, pct -): - # TODO: supply a reason why this isn't compatible with pandas 1.x - pytest.importorskip("pandas", minversion="2.2.0") - col_names = ["int64_too", "float64_col", "int64_col", "string_col"] - bf_result = ( - scalars_df_index[col_names] - .groupby("string_col") - .rank(na_option=na_option, method=method, ascending=ascending, pct=pct) - ).to_pandas() - pd_result = ( - ( - scalars_pandas_df_index[col_names] - .groupby("string_col") - .rank(na_option=na_option, method=method, ascending=ascending, pct=pct) - ) - .astype("float64") - .astype("Float64") - ) - bigframes.testing.utils.assert_frame_equal( - pd_result, bf_result, check_dtype=False, check_index_type=False - ) - - @pytest.mark.parametrize( ("operator"), [ (lambda x: x.count()), - (lambda x: x.nunique()), (lambda x: x.any()), (lambda x: x.all()), ], ids=[ "count", - "nunique", "any", "all", ], @@ -169,130 +85,50 @@ def test_dataframe_groupby_aggregate( pd_result = operator(scalars_pandas_df_index[col_names].groupby("string_col")) bf_result_computed = bf_result.to_pandas() - bigframes.testing.utils.assert_frame_equal( - pd_result, bf_result_computed, check_dtype=False - ) - + pd.testing.assert_frame_equal(pd_result, bf_result_computed, check_dtype=False) -def test_dataframe_groupby_corr(scalars_df_index, scalars_pandas_df_index): - col_names = ["int64_too", "float64_col", "int64_col", "bool_col"] - bf_result = scalars_df_index[col_names].groupby("bool_col").corr().to_pandas() - pd_result = scalars_pandas_df_index[col_names].groupby("bool_col").corr() - bigframes.testing.utils.assert_frame_equal( - pd_result, bf_result, check_dtype=False, check_index_type=False - ) - - -def test_dataframe_groupby_cov(scalars_df_index, scalars_pandas_df_index): - col_names = ["int64_too", "float64_col", "int64_col", "bool_col"] - bf_result = scalars_df_index[col_names].groupby("bool_col").cov().to_pandas() - pd_result = scalars_pandas_df_index[col_names].groupby("bool_col").cov() - - bigframes.testing.utils.assert_frame_equal( - pd_result, bf_result, check_dtype=False, check_index_type=False - ) - - -@pytest.mark.parametrize( - ("ordered"), - [ - (True), - (False), - ], -) -def test_dataframe_groupby_agg_string( - scalars_df_index, scalars_pandas_df_index, ordered -): +def test_dataframe_groupby_agg_string(scalars_df_index, scalars_pandas_df_index): col_names = ["int64_too", "float64_col", "int64_col", "bool_col", "string_col"] bf_result = scalars_df_index[col_names].groupby("string_col").agg("count") pd_result = scalars_pandas_df_index[col_names].groupby("string_col").agg("count") - bf_result_computed = bf_result.to_pandas(ordered=ordered) - - bigframes.testing.utils.assert_frame_equal( - pd_result, bf_result_computed, check_dtype=False, ignore_order=not ordered - ) - - -def test_dataframe_groupby_agg_size_string(scalars_df_index, scalars_pandas_df_index): - col_names = ["int64_too", "float64_col", "int64_col", "bool_col", "string_col"] - bf_result = scalars_df_index[col_names].groupby("string_col").agg("size") - pd_result = scalars_pandas_df_index[col_names].groupby("string_col").agg("size") + bf_result_computed = bf_result.to_pandas() - bigframes.testing.utils.assert_series_equal( - pd_result, bf_result.to_pandas(), check_dtype=False + pd.testing.assert_frame_equal( + pd_result, + bf_result_computed, + check_dtype=False, ) def test_dataframe_groupby_agg_list(scalars_df_index, scalars_pandas_df_index): col_names = ["int64_too", "float64_col", "int64_col", "bool_col", "string_col"] - bf_result = ( - scalars_df_index[col_names].groupby("string_col").agg(["count", np.min, "size"]) - ) + bf_result = scalars_df_index[col_names].groupby("string_col").agg(["count", "min"]) pd_result = ( - scalars_pandas_df_index[col_names] - .groupby("string_col") - .agg(["count", np.min, "size"]) + scalars_pandas_df_index[col_names].groupby("string_col").agg(["count", "min"]) ) bf_result_computed = bf_result.to_pandas() - # some inconsistency between versions, so normalize to bigframes behavior - pd_result = pd_result.rename({"amin": "min"}, axis="columns") - bf_result_computed = bf_result_computed.rename({"amin": "min"}, axis="columns") - bigframes.testing.utils.assert_frame_equal( - pd_result, bf_result_computed, check_dtype=False, check_index_type=False - ) - - -def test_dataframe_groupby_agg_list_w_column_multi_index( - scalars_df_index, scalars_pandas_df_index -): - columns = ["int64_too", "string_col", "bool_col"] - multi_columns = pd.MultiIndex.from_tuples(zip(["a", "b", "a"], columns)) - bf_df = scalars_df_index[columns].copy() - bf_df.columns = multi_columns - pd_df = scalars_pandas_df_index[columns].copy() - pd_df.columns = multi_columns - - bf_result = bf_df.groupby(level=0).agg(["count", np.min, "size"]) - pd_result = pd_df.groupby(level=0).agg(["count", np.min, "size"]) - - bf_result_computed = bf_result.to_pandas() - bigframes.testing.utils.assert_frame_equal( - pd_result, bf_result_computed, check_dtype=False - ) + pd.testing.assert_frame_equal(pd_result, bf_result_computed, check_dtype=False) -@pytest.mark.parametrize( - ("as_index"), - [ - (True), - (False), - ], -) def test_dataframe_groupby_agg_dict_with_list( - scalars_df_index, scalars_pandas_df_index, as_index + scalars_df_index, scalars_pandas_df_index ): col_names = ["int64_too", "float64_col", "int64_col", "bool_col", "string_col"] bf_result = ( scalars_df_index[col_names] - .groupby("string_col", as_index=as_index) - .agg( - {"int64_too": [np.mean, np.max], "string_col": "count", "bool_col": "size"} - ) + .groupby("string_col") + .agg({"int64_too": ["mean", "max"], "string_col": "count"}) ) pd_result = ( scalars_pandas_df_index[col_names] - .groupby("string_col", as_index=as_index) - .agg( - {"int64_too": [np.mean, np.max], "string_col": "count", "bool_col": "size"} - ) + .groupby("string_col") + .agg({"int64_too": ["mean", "max"], "string_col": "count"}) ) bf_result_computed = bf_result.to_pandas() - bigframes.testing.utils.assert_frame_equal( - pd_result, bf_result_computed, check_dtype=False, check_index_type=False - ) + pd.testing.assert_frame_equal(pd_result, bf_result_computed, check_dtype=False) def test_dataframe_groupby_agg_dict_no_lists(scalars_df_index, scalars_pandas_df_index): @@ -300,18 +136,16 @@ def test_dataframe_groupby_agg_dict_no_lists(scalars_df_index, scalars_pandas_df bf_result = ( scalars_df_index[col_names] .groupby("string_col") - .agg({"int64_too": np.mean, "string_col": "count"}) + .agg({"int64_too": "mean", "string_col": "count"}) ) pd_result = ( scalars_pandas_df_index[col_names] .groupby("string_col") - .agg({"int64_too": np.mean, "string_col": "count"}) + .agg({"int64_too": "mean", "string_col": "count"}) ) bf_result_computed = bf_result.to_pandas() - bigframes.testing.utils.assert_frame_equal( - pd_result, bf_result_computed, check_dtype=False - ) + pd.testing.assert_frame_equal(pd_result, bf_result_computed, check_dtype=False) def test_dataframe_groupby_agg_named(scalars_df_index, scalars_pandas_df_index): @@ -320,7 +154,7 @@ def test_dataframe_groupby_agg_named(scalars_df_index, scalars_pandas_df_index): scalars_df_index[col_names] .groupby("string_col") .agg( - agg1=bpd.NamedAgg("int64_too", np.sum), + agg1=bpd.NamedAgg("int64_too", "sum"), agg2=bpd.NamedAgg("float64_col", "max"), ) ) @@ -328,52 +162,12 @@ def test_dataframe_groupby_agg_named(scalars_df_index, scalars_pandas_df_index): scalars_pandas_df_index[col_names] .groupby("string_col") .agg( - agg1=pd.NamedAgg("int64_too", np.sum), - agg2=pd.NamedAgg("float64_col", "max"), + agg1=pd.NamedAgg("int64_too", "sum"), agg2=pd.NamedAgg("float64_col", "max") ) ) bf_result_computed = bf_result.to_pandas() - bigframes.testing.utils.assert_frame_equal( - pd_result, bf_result_computed, check_dtype=False - ) - - -def test_dataframe_groupby_agg_kw_tuples(scalars_df_index, scalars_pandas_df_index): - col_names = ["int64_too", "float64_col", "int64_col", "bool_col", "string_col"] - bf_result = ( - scalars_df_index[col_names] - .groupby("string_col") - .agg( - agg1=("int64_too", np.sum), - agg2=("float64_col", "max"), - ) - ) - pd_result = ( - scalars_pandas_df_index[col_names] - .groupby("string_col") - .agg(agg1=("int64_too", np.sum), agg2=("float64_col", "max")) - ) - bf_result_computed = bf_result.to_pandas() - - bigframes.testing.utils.assert_frame_equal( - pd_result, bf_result_computed, check_dtype=False - ) - - -@pytest.mark.parametrize( - ("kwargs"), - [ - ({"hello": "world"}), - ({"too_many_fields": ("one", "two", "three")}), - ], -) -def test_dataframe_groupby_agg_kw_error(scalars_df_index, kwargs): - col_names = ["int64_too", "float64_col", "int64_col", "bool_col", "string_col"] - with pytest.raises( - TypeError, match=r"kwargs values must be 2-tuples of column, aggfunc" - ): - (scalars_df_index[col_names].groupby("string_col").agg(**kwargs)) + pd.testing.assert_frame_equal(pd_result, bf_result_computed, check_dtype=False) @pytest.mark.parametrize( @@ -403,21 +197,20 @@ def test_dataframe_groupby_multi_sum( # BigQuery DataFrames default indices use nullable Int64 always pd_series.index = pd_series.index.astype("Int64") - bigframes.testing.utils.assert_frame_equal( + pd.testing.assert_frame_equal( pd_series, bf_result, ) @pytest.mark.parametrize( - ("operator", "dropna"), + ("operator"), [ - (lambda x: x.cumsum(numeric_only=True), True), - (lambda x: x.cummax(numeric_only=True), True), - (lambda x: x.cummin(numeric_only=True), False), - # Pre-pandas 2.2 doesn't always proeduce float. - (lambda x: x.cumprod().astype("Float64"), False), - (lambda x: x.shift(periods=2), True), + (lambda x: x.cumsum(numeric_only=True)), + (lambda x: x.cummax(numeric_only=True)), + (lambda x: x.cummin(numeric_only=True)), + (lambda x: x.cumprod()), + (lambda x: x.shift(periods=2)), ], ids=[ "cumsum", @@ -428,70 +221,31 @@ def test_dataframe_groupby_multi_sum( ], ) def test_dataframe_groupby_analytic( - scalars_df_index, - scalars_pandas_df_index, - operator, - dropna, + scalars_df_index, scalars_pandas_df_index, operator ): col_names = ["float64_col", "int64_col", "bool_col", "string_col"] - bf_result = operator( - scalars_df_index[col_names].groupby("string_col", dropna=dropna) - ) - pd_result = operator( - scalars_pandas_df_index[col_names].groupby("string_col", dropna=dropna) - ) + bf_result = operator(scalars_df_index[col_names].groupby("string_col")) + pd_result = operator(scalars_pandas_df_index[col_names].groupby("string_col")) bf_result_computed = bf_result.to_pandas() - bigframes.testing.utils.assert_frame_equal( - pd_result, bf_result_computed, check_dtype=False - ) - + pd.testing.assert_frame_equal(pd_result, bf_result_computed, check_dtype=False) -@pytest.mark.parametrize( - ("ascending", "dropna"), - [ - (True, True), - (False, False), - ], -) -def test_dataframe_groupby_cumcount( - scalars_df_index, scalars_pandas_df_index, ascending, dropna -): - bf_result = scalars_df_index.groupby("string_col", dropna=dropna).cumcount( - ascending - ) - pd_result = scalars_pandas_df_index.groupby("string_col", dropna=dropna).cumcount( - ascending - ) - bf_result_computed = bf_result.to_pandas() - bigframes.testing.utils.assert_series_equal( - pd_result, bf_result_computed, check_dtype=False - ) +def test_series_groupby_skew(scalars_df_index, scalars_pandas_df_index): + bf_result = scalars_df_index.groupby("bool_col")["int64_too"].skew().to_pandas() + pd_result = scalars_pandas_df_index.groupby("bool_col")["int64_too"].skew() + pd.testing.assert_series_equal(pd_result, bf_result, check_dtype=False) -def test_dataframe_groupby_size_as_index_false( - scalars_df_index, scalars_pandas_df_index -): - bf_result = scalars_df_index.groupby("string_col", as_index=False).size() - bf_result_computed = bf_result.to_pandas() - pd_result = scalars_pandas_df_index.groupby("string_col", as_index=False).size() - bigframes.testing.utils.assert_frame_equal( - pd_result, bf_result_computed, check_dtype=False, check_index_type=False +def test_series_groupby_kurt(scalars_df_index, scalars_pandas_df_index): + bf_result = scalars_df_index.groupby("bool_col")["int64_too"].kurt().to_pandas() + # Pandas doesn't have groupby.kurt yet: https://github.com/pandas-dev/pandas/issues/40139 + pd_result = scalars_pandas_df_index.groupby("bool_col")["int64_too"].apply( + pd.Series.kurt ) - -def test_dataframe_groupby_size_as_index_true( - scalars_df_index, scalars_pandas_df_index -): - bf_result = scalars_df_index.groupby("string_col", as_index=True).size() - pd_result = scalars_pandas_df_index.groupby("string_col", as_index=True).size() - bf_result_computed = bf_result.to_pandas() - - bigframes.testing.utils.assert_series_equal( - pd_result, bf_result_computed, check_dtype=False - ) + pd.testing.assert_series_equal(pd_result, bf_result, check_dtype=False) def test_dataframe_groupby_skew(scalars_df_index, scalars_pandas_df_index): @@ -499,38 +253,30 @@ def test_dataframe_groupby_skew(scalars_df_index, scalars_pandas_df_index): bf_result = scalars_df_index[col_names].groupby("bool_col").skew().to_pandas() pd_result = scalars_pandas_df_index[col_names].groupby("bool_col").skew() - bigframes.testing.utils.assert_frame_equal(pd_result, bf_result, check_dtype=False) + pd.testing.assert_frame_equal(pd_result, bf_result, check_dtype=False) -@pytest.mark.skipif( - not pd.__version__.startswith("3"), - reason="groupby.kurt not supported on legacy pandas versions", -) def test_dataframe_groupby_kurt(scalars_df_index, scalars_pandas_df_index): col_names = ["float64_col", "int64_col", "bool_col"] bf_result = scalars_df_index[col_names].groupby("bool_col").kurt().to_pandas() # Pandas doesn't have groupby.kurt yet: https://github.com/pandas-dev/pandas/issues/40139 - pd_result = scalars_pandas_df_index[col_names].groupby("bool_col").kurt() + pd_result = ( + scalars_pandas_df_index[col_names] + .groupby("bool_col") + .apply(pd.Series.kurt) + .drop("bool_col", axis=1) + ) - bigframes.testing.utils.assert_frame_equal(pd_result, bf_result, check_dtype=False) + pd.testing.assert_frame_equal(pd_result, bf_result, check_dtype=False) -@pytest.mark.parametrize( - ("ordered"), - [ - (True), - (False), - ], -) -def test_dataframe_groupby_diff(scalars_df_index, scalars_pandas_df_index, ordered): +def test_dataframe_groupby_diff(scalars_df_index, scalars_pandas_df_index): col_names = ["float64_col", "int64_col", "string_col"] bf_result = scalars_df_index[col_names].groupby("string_col").diff(-1) pd_result = scalars_pandas_df_index[col_names].groupby("string_col").diff(-1) - bf_result_computed = bf_result.to_pandas(ordered=ordered) + bf_result_computed = bf_result.to_pandas() - bigframes.testing.utils.assert_frame_equal( - pd_result, bf_result_computed, check_dtype=False, ignore_order=not ordered - ) + pd.testing.assert_frame_equal(pd_result, bf_result_computed, check_dtype=False) def test_dataframe_groupby_getitem( @@ -545,23 +291,7 @@ def test_dataframe_groupby_getitem( scalars_pandas_df_index[col_names].groupby("string_col")["int64_col"].min() ) - bigframes.testing.utils.assert_series_equal(pd_result, bf_result, check_dtype=False) - - -def test_dataframe_groupby_getitem_error( - scalars_df_index, - scalars_pandas_df_index, -): - col_names = ["float64_col", "int64_col", "bool_col", "string_col"] - with pytest.raises( - KeyError, match=r"Columns not found: 'not_in_group'. Did you mean 'string_col'?" - ): - ( - scalars_df_index[col_names] - .groupby("bool_col")["not_in_group"] - .min() - .to_pandas() - ) + pd.testing.assert_series_equal(pd_result, bf_result, check_dtype=False) def test_dataframe_groupby_getitem_list( @@ -576,178 +306,23 @@ def test_dataframe_groupby_getitem_list( scalars_pandas_df_index[col_names].groupby("string_col")[col_names].min() ) - bigframes.testing.utils.assert_frame_equal(pd_result, bf_result, check_dtype=False) - - -def test_dataframe_groupby_getitem_list_error( - scalars_df_index, - scalars_pandas_df_index, -): - col_names = ["float64_col", "int64_col", "bool_col", "string_col"] - with pytest.raises( - KeyError, - match=r"Columns not found: 'col1', 'float'. Did you mean 'bool_col', 'float64_col'?", - ): - ( - scalars_df_index[col_names] - .groupby("string_col")["col1", "float"] - .min() - .to_pandas() - ) - - -def test_dataframe_groupby_nonnumeric_with_mean(): - df = pd.DataFrame( - { - "key1": ["a", "a", "a", "b"], - "key2": ["a", "a", "c", "c"], - "key3": [1, 2, 3, 4], - "key4": [1.6, 2, 3, 4], - } - ) - pd_result = df.groupby(["key1", "key2"]).mean() - - bf_result = bpd.DataFrame(df).groupby(["key1", "key2"]).mean().to_pandas() + pd.testing.assert_frame_equal(pd_result, bf_result, check_dtype=False) - bigframes.testing.utils.assert_frame_equal( - pd_result, bf_result, check_index_type=False, check_dtype=False - ) - -@pytest.mark.skipif( - pd.__version__.startswith("3"), - reason="value_counts behavior change b/485962498", -) -@pytest.mark.parametrize( - ("subset", "normalize", "ascending", "dropna", "as_index"), - [ - (None, True, True, True, True), - (["int64_too", "int64_col"], False, False, False, False), - ], -) -def test_dataframe_groupby_value_counts( - scalars_df_index, - scalars_pandas_df_index, - subset, - normalize, - ascending, - dropna, - as_index, -): - if pd.__version__.startswith("1."): - pytest.skip("pandas 1.x produces different column labels.") - col_names = ["float64_col", "int64_col", "bool_col", "int64_too"] +def test_series_groupby_agg_string(scalars_df_index, scalars_pandas_df_index): bf_result = ( - scalars_df_index[col_names] - .groupby("bool_col", as_index=as_index) - .value_counts( - subset=subset, normalize=normalize, ascending=ascending, dropna=dropna - ) - .to_pandas() - ) - pd_result = ( - scalars_pandas_df_index[col_names] - .groupby("bool_col", as_index=as_index) - .value_counts( - subset=subset, normalize=normalize, ascending=ascending, dropna=dropna - ) - ) - - if as_index: - bigframes.testing.utils.assert_series_equal( - pd_result, bf_result, check_dtype=False - ) - else: - pd_result.index = pd_result.index.astype("Int64") - bigframes.testing.utils.assert_frame_equal( - pd_result, bf_result, check_dtype=False - ) - - -@pytest.mark.parametrize( - ("numeric_only", "min_count"), - [ - (False, 4), - (True, 0), - ], -) -def test_dataframe_groupby_first( - scalars_df_index, scalars_pandas_df_index, numeric_only, min_count -): - # min_count seems to not work properly on older pandas - pytest.importorskip("pandas", minversion="2.0.0") - # bytes, dates not handling min_count properly in pandas - bf_result = ( - scalars_df_index.drop(columns=["bytes_col", "date_col"]) - .groupby(scalars_df_index.int64_col % 2) - .first(numeric_only=numeric_only, min_count=min_count) - ).to_pandas() - pd_result = ( - scalars_pandas_df_index.drop(columns=["bytes_col", "date_col"]) - .groupby(scalars_pandas_df_index.int64_col % 2) - .first(numeric_only=numeric_only, min_count=min_count) - ) - bigframes.testing.utils.assert_frame_equal( - pd_result, - bf_result, - ) - - -@pytest.mark.parametrize( - ("numeric_only", "min_count"), - [ - (True, 2), - (False, -1), - ], -) -def test_dataframe_groupby_last( - scalars_df_index, scalars_pandas_df_index, numeric_only, min_count -): - bf_result = ( - scalars_df_index.groupby(scalars_df_index.int64_col % 2).last( - numeric_only=numeric_only, min_count=min_count - ) - ).to_pandas() - pd_result = scalars_pandas_df_index.groupby( - scalars_pandas_df_index.int64_col % 2 - ).last(numeric_only=numeric_only, min_count=min_count) - bigframes.testing.utils.assert_frame_equal( - pd_result, - bf_result, - ) - - -# ============== -# Series.groupby -# ============== - - -def test_series_groupby_len(scalars_df_index, scalars_pandas_df_index): - bf_result = len(scalars_df_index.groupby("bool_col")["int64_col"]) - pd_result = len(scalars_pandas_df_index.groupby("bool_col")["int64_col"]) - - assert bf_result == pd_result - - -@pytest.mark.parametrize( - ("agg"), - [ - ("count"), - ("size"), - ], -) -def test_series_groupby_agg_string(scalars_df_index, scalars_pandas_df_index, agg): - bf_result = ( - scalars_df_index["int64_col"].groupby(scalars_df_index["string_col"]).agg(agg) + scalars_df_index["int64_col"] + .groupby(scalars_df_index["string_col"]) + .agg("count") ) pd_result = ( scalars_pandas_df_index["int64_col"] .groupby(scalars_pandas_df_index["string_col"]) - .agg(agg) + .agg("count") ) bf_result_computed = bf_result.to_pandas() - bigframes.testing.utils.assert_series_equal( + pd.testing.assert_series_equal( pd_result, bf_result_computed, check_dtype=False, check_names=False ) @@ -756,319 +331,15 @@ def test_series_groupby_agg_list(scalars_df_index, scalars_pandas_df_index): bf_result = ( scalars_df_index["int64_col"] .groupby(scalars_df_index["string_col"]) - .agg(["sum", np.mean, "size"]) + .agg(["sum", "mean"]) ) pd_result = ( scalars_pandas_df_index["int64_col"] .groupby(scalars_pandas_df_index["string_col"]) - .agg(["sum", np.mean, "size"]) + .agg(["sum", "mean"]) ) bf_result_computed = bf_result.to_pandas() - bigframes.testing.utils.assert_frame_equal( + pd.testing.assert_frame_equal( pd_result, bf_result_computed, check_dtype=False, check_names=False ) - - -@pytest.mark.parametrize( - ("na_option", "method", "ascending", "pct"), - [ - ("keep", "average", True, False), - ( - "top", - "min", - False, - True, - ), - ( - "bottom", - "max", - False, - True, - ), - ( - "top", - "first", - False, - True, - ), - ( - "bottom", - "dense", - False, - False, - ), - ], -) -def test_series_groupby_rank( - scalars_df_index, scalars_pandas_df_index, na_option, method, ascending, pct -): - # TODO: supply a reason why this isn't compatible with pandas 1.x - pytest.importorskip("pandas", minversion="2.0.0") - col_names = ["int64_col", "string_col"] - bf_result = ( - scalars_df_index[col_names] - .groupby("string_col")["int64_col"] - .rank(na_option=na_option, method=method, ascending=ascending, pct=pct) - ).to_pandas() - pd_result = ( - ( - scalars_pandas_df_index[col_names] - .groupby("string_col")["int64_col"] - .rank(na_option=na_option, method=method, ascending=ascending, pct=pct) - ) - .astype("float64") - .astype("Float64") - ) - bigframes.testing.utils.assert_series_equal( - pd_result, bf_result, check_dtype=False, check_index_type=False - ) - - -@pytest.mark.parametrize("dropna", [True, False]) -def test_series_groupby_head(scalars_df_index, scalars_pandas_df_index, dropna): - bf_result = ( - scalars_df_index.groupby("bool_col", dropna=dropna)["int64_too"] - .head(1) - .to_pandas() - ) - pd_result = scalars_pandas_df_index.groupby("bool_col", dropna=dropna)[ - "int64_too" - ].head(1) - bigframes.testing.utils.assert_series_equal(pd_result, bf_result, check_dtype=False) - - -def test_series_groupby_kurt(scalars_df_index, scalars_pandas_df_index): - bf_result = ( - scalars_df_index["int64_too"] - .groupby(scalars_df_index["bool_col"]) - .kurt() - .to_pandas() - ) - # Pandas doesn't have groupby.kurt yet: https://github.com/pandas-dev/pandas/issues/40139 - pd_result = scalars_pandas_df_index.groupby("bool_col")["int64_too"].apply( - pd.Series.kurt - ) - - bigframes.testing.utils.assert_series_equal(pd_result, bf_result, check_dtype=False) - - -def test_series_groupby_size(scalars_df_index, scalars_pandas_df_index): - bf_result = ( - scalars_df_index["int64_too"].groupby(scalars_df_index["bool_col"]).size() - ) - pd_result = ( - scalars_pandas_df_index["int64_too"] - .groupby(scalars_pandas_df_index["bool_col"]) - .size() - ) - bf_result_computed = bf_result.to_pandas() - - bigframes.testing.utils.assert_series_equal( - pd_result, bf_result_computed, check_dtype=False - ) - - -def test_series_groupby_skew(scalars_df_index, scalars_pandas_df_index): - bf_result = ( - scalars_df_index["int64_too"] - .groupby(scalars_df_index["bool_col"]) - .skew() - .to_pandas() - ) - pd_result = ( - scalars_pandas_df_index["int64_too"] - .groupby(scalars_pandas_df_index["bool_col"]) - .skew() - ) - - bigframes.testing.utils.assert_series_equal(pd_result, bf_result, check_dtype=False) - - -@pytest.mark.parametrize( - ("q"), - [ - ([0.2, 0.4, 0.6, 0.8]), - (0.11), - ], -) -def test_series_groupby_quantile(scalars_df_index, scalars_pandas_df_index, q): - bf_result = ( - scalars_df_index.groupby("string_col")["int64_col"].quantile(q) - ).to_pandas() - pd_result = scalars_pandas_df_index.groupby("string_col")["int64_col"].quantile(q) - bigframes.testing.utils.assert_series_equal( - pd_result, bf_result, check_dtype=False, check_index_type=False - ) - - -@pytest.mark.skipif( - pd.__version__.startswith("3"), - reason="Pandas 3 change value_counts behavior", -) -@pytest.mark.parametrize( - ("normalize", "ascending", "dropna"), - [ - ( - True, - True, - True, - ), - ( - False, - False, - False, - ), - ], -) -def test_series_groupby_value_counts( - scalars_df_index, - scalars_pandas_df_index, - normalize, - ascending, - dropna, -): - if pd.__version__.startswith("1."): - pytest.skip("pandas 1.x produces different column labels.") - bf_result = ( - scalars_df_index.groupby("bool_col")["string_col"] - .value_counts(normalize=normalize, ascending=ascending, dropna=dropna) - .to_pandas() - ) - pd_result = scalars_pandas_df_index.groupby("bool_col")["string_col"].value_counts( - normalize=normalize, ascending=ascending, dropna=dropna - ) - bigframes.testing.utils.assert_series_equal(pd_result, bf_result, check_dtype=False) - - -@pytest.mark.parametrize( - ("numeric_only", "min_count"), - [ - (True, 2), - (False, -1), - ], -) -def test_series_groupby_first( - scalars_df_index, scalars_pandas_df_index, numeric_only, min_count -): - bf_result = ( - scalars_df_index.groupby("string_col")["int64_col"].first( - numeric_only=numeric_only, min_count=min_count - ) - ).to_pandas() - pd_result = scalars_pandas_df_index.groupby("string_col")["int64_col"].first( - numeric_only=numeric_only, min_count=min_count - ) - bigframes.testing.utils.assert_series_equal( - pd_result, - bf_result, - ) - - -@pytest.mark.parametrize( - ("numeric_only", "min_count"), - [ - (False, 4), - (True, 0), - ], -) -def test_series_groupby_last( - scalars_df_index, scalars_pandas_df_index, numeric_only, min_count -): - bf_result = ( - scalars_df_index.groupby("string_col")["int64_col"].last( - numeric_only=numeric_only, min_count=min_count - ) - ).to_pandas() - pd_result = scalars_pandas_df_index.groupby("string_col")["int64_col"].last( - numeric_only=numeric_only, min_count=min_count - ) - bigframes.testing.utils.assert_series_equal(pd_result, bf_result) - - -def test_series_groupby_agg_transpile_system(scalars_df_index, scalars_pandas_df_index): - def custom_agg(s): - return s.sum() - s.mean() - - bf_df = scalars_df_index.dropna(subset=["int64_col", "bool_col"]) - pd_df = scalars_pandas_df_index.dropna(subset=["int64_col", "bool_col"]) - - with bpd.option_context("experiments.enable_python_transpiler", True): - bf_result = bf_df.groupby("bool_col")["int64_col"].agg(custom_agg).to_pandas() - pd_result = pd_df.groupby("bool_col")["int64_col"].agg(custom_agg) - - bigframes.testing.utils.assert_series_equal(pd_result, bf_result, check_dtype=False) - - -def test_dataframe_groupby_agg_transpile_system( - scalars_df_index, scalars_pandas_df_index -): - def custom_agg(s): - return (s.max() - s.min()) / s.count() - - bf_df = scalars_df_index.dropna(subset=["int64_col", "int64_too", "bool_col"]) - pd_df = scalars_pandas_df_index.dropna( - subset=["int64_col", "int64_too", "bool_col"] - ) - - with bpd.option_context("experiments.enable_python_transpiler", True): - bf_result = ( - bf_df[["int64_col", "int64_too", "bool_col"]] - .groupby("bool_col") - .agg(custom_agg) - .to_pandas() - ) - pd_result = ( - pd_df[["int64_col", "int64_too", "bool_col"]] - .groupby("bool_col") - .agg(custom_agg) - ) - - bigframes.testing.utils.assert_frame_equal(pd_result, bf_result, check_dtype=False) - - -def test_series_groupby_transform_transpile_system( - scalars_df_index, scalars_pandas_df_index -): - def custom_transform(s): - return s - s.mean() - - bf_df = scalars_df_index.dropna(subset=["int64_col", "bool_col"]) - pd_df = scalars_pandas_df_index.dropna(subset=["int64_col", "bool_col"]) - - with bpd.option_context("experiments.enable_python_transpiler", True): - bf_result = ( - bf_df.groupby("bool_col")["int64_col"] - .transform(custom_transform) - .to_pandas() - ) - pd_result = pd_df.groupby("bool_col")["int64_col"].transform(custom_transform) - - bigframes.testing.utils.assert_series_equal(pd_result, bf_result, check_dtype=False) - - -def test_dataframe_groupby_transform_transpile_system( - scalars_df_index, scalars_pandas_df_index -): - def custom_transform(s): - return (s - s.min()) / (s.max() - s.min()) - - bf_df = scalars_df_index.dropna(subset=["int64_col", "int64_too", "bool_col"]) - pd_df = scalars_pandas_df_index.dropna( - subset=["int64_col", "int64_too", "bool_col"] - ) - - with bpd.option_context("experiments.enable_python_transpiler", True): - bf_result = ( - bf_df[["int64_col", "int64_too", "bool_col"]] - .groupby("bool_col") - .transform(custom_transform) - .to_pandas() - ) - pd_result = ( - pd_df[["int64_col", "int64_too", "bool_col"]] - .groupby("bool_col") - .transform(custom_transform) - ) - - bigframes.testing.utils.assert_frame_equal(pd_result, bf_result, check_dtype=False) diff --git a/tests/system/small/test_ibis.py b/tests/system/small/test_ibis.py new file mode 100644 index 00000000000..58b78e00481 --- /dev/null +++ b/tests/system/small/test_ibis.py @@ -0,0 +1,39 @@ +# Copyright 2023 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. + +"""Tests for monkeypatched ibis code.""" + +import ibis.expr.types as ibis_types + +import bigframes +import third_party.bigframes_vendored.ibis.expr.operations as vendored_ibis_ops + + +def test_approximate_quantiles(session: bigframes.Session, scalars_table_id: str): + num_bins = 3 + ibis_client = session.ibis_client + _, dataset, table_id = scalars_table_id.split(".") + ibis_table: ibis_types.Table = ibis_client.table(table_id, database=dataset) + ibis_column: ibis_types.NumericColumn = ibis_table["int64_col"] + quantiles: ibis_types.ArrayScalar = vendored_ibis_ops.ApproximateMultiQuantile( # type: ignore + ibis_column, num_bins=num_bins + ).to_expr() + value = quantiles[1] + num_edges = quantiles.length() + + sql = ibis_client.compile(value) + num_edges_result = num_edges.to_pandas() + + assert "APPROX_QUANTILES" in sql + assert num_edges_result == num_bins + 1 diff --git a/tests/system/small/test_index.py b/tests/system/small/test_index.py index 26ac609b3c6..f7fa0f0855e 100644 --- a/tests/system/small/test_index.py +++ b/tests/system/small/test_index.py @@ -12,178 +12,11 @@ # See the License for the specific language governing permissions and # limitations under the License. -import re - import numpy import pandas as pd import pytest -import bigframes.pandas as bpd -from bigframes import dtypes -from bigframes.testing.utils import assert_pandas_index_equal_ignore_index_type - - -def test_index_construct_from_list(): - bf_result = bpd.Index( - [3, 14, 159], dtype=pd.Int64Dtype(), name="my_index" - ).to_pandas() - - pd_result: pd.Index = pd.Index([3, 14, 159], dtype=pd.Int64Dtype(), name="my_index") - pd.testing.assert_index_equal(bf_result, pd_result) - - -@pytest.mark.parametrize("key, expected_loc", [("a", 0), ("b", 1), ("c", 2)]) -def test_get_loc_should_return_int_for_unique_index(key, expected_loc): - """Behavior: get_loc on a unique index returns an integer position.""" - # The pandas result is used as the known-correct value. - # We assert our implementation matches it and the expected type. - bf_index = bpd.Index(["a", "b", "c"]) - - result = bf_index.get_loc(key) - - assert result == expected_loc - assert isinstance(result, int) - - -def test_get_loc_should_return_slice_for_monotonic_duplicates(): - """Behavior: get_loc on a monotonic string index with duplicates returns a slice.""" - bf_index = bpd.Index(["a", "b", "b", "c"]) - pd_index = pd.Index(["a", "b", "b", "c"]) - - bf_result = bf_index.get_loc("b") - pd_result = pd_index.get_loc("b") - - assert isinstance(bf_result, slice) - assert bf_result == pd_result # Should be slice(1, 3, None) - - -def test_get_loc_should_return_slice_for_monotonic_numeric_duplicates(): - """Behavior: get_loc on a monotonic numeric index with duplicates returns a slice.""" - bf_index = bpd.Index([1, 2, 2, 3]) - pd_index = pd.Index([1, 2, 2, 3]) - - bf_result = bf_index.get_loc(2) - pd_result = pd_index.get_loc(2) - - assert isinstance(bf_result, slice) - assert bf_result == pd_result # Should be slice(1, 3, None) - - -def test_get_loc_should_return_mask_for_non_monotonic_duplicates(): - """Behavior: get_loc on a non-monotonic string index returns a boolean array.""" - bf_index = bpd.Index(["a", "b", "c", "b"]) - pd_index = pd.Index(["a", "b", "c", "b"]) - - pd_result = pd_index.get_loc("b") - bf_result = bf_index.get_loc("b") - - assert not isinstance(bf_result, (int, slice)) - - if hasattr(bf_result, "to_numpy"): - bf_array = bf_result.to_numpy() - else: - bf_array = bf_result.to_pandas().to_numpy() - numpy.testing.assert_array_equal(bf_array, pd_result) - - -def test_get_loc_should_return_mask_for_non_monotonic_numeric_duplicates(): - """Behavior: get_loc on a non-monotonic numeric index returns a boolean array.""" - bf_index = bpd.Index([1, 2, 3, 2]) - pd_index = pd.Index([1, 2, 3, 2]) - - pd_result = pd_index.get_loc(2) - bf_result = bf_index.get_loc(2) - - assert not isinstance(bf_result, (int, slice)) - - if hasattr(bf_result, "to_numpy"): - bf_array = bf_result.to_numpy() - else: - bf_array = bf_result.to_pandas().to_numpy() - numpy.testing.assert_array_equal(bf_array, pd_result) - - -def test_get_loc_should_raise_error_for_missing_key(): - """Behavior: get_loc raises KeyError when a string key is not found.""" - bf_index = bpd.Index(["a", "b", "c"]) - - with pytest.raises(KeyError): - bf_index.get_loc("d") - - -def test_get_loc_should_raise_error_for_missing_numeric_key(): - """Behavior: get_loc raises KeyError when a numeric key is not found.""" - bf_index = bpd.Index([1, 2, 3]) - - with pytest.raises(KeyError): - bf_index.get_loc(4) - - -def test_get_loc_should_work_for_single_element_index(): - """Behavior: get_loc on a single-element index returns 0.""" - assert bpd.Index(["a"]).get_loc("a") == pd.Index(["a"]).get_loc("a") - - -def test_get_loc_should_return_slice_when_all_elements_are_duplicates(): - """Behavior: get_loc returns a full slice if all elements match the key.""" - bf_index = bpd.Index(["a", "a", "a"]) - pd_index = pd.Index(["a", "a", "a"]) - - bf_result = bf_index.get_loc("a") - pd_result = pd_index.get_loc("a") - - assert isinstance(bf_result, slice) - assert bf_result == pd_result # Should be slice(0, 3, None) - - -def test_index_construct_from_series(): - bf_result = bpd.Index( - bpd.Series([3, 14, 159], dtype=pd.Float64Dtype(), name="series_name"), - name="index_name", - dtype=pd.Int64Dtype(), - ).to_pandas() - pd_result: pd.Index = pd.Index( - pd.Series([3, 14, 159], dtype=pd.Float64Dtype(), name="series_name"), - name="index_name", - dtype=pd.Int64Dtype(), - ) - pd.testing.assert_index_equal(bf_result, pd_result) - - -def test_index_construct_from_index(): - bf_index_input = bpd.Index( - [3, 14, 159], dtype=pd.Float64Dtype(), name="series_name" - ) - bf_result = bpd.Index( - bf_index_input, dtype=pd.Int64Dtype(), name="index_name" - ).to_pandas() - pd_index_input: pd.Index = pd.Index( - [3, 14, 159], dtype=pd.Float64Dtype(), name="series_name" - ) - pd_result: pd.Index = pd.Index( - pd_index_input, dtype=pd.Int64Dtype(), name="index_name" - ) - pd.testing.assert_index_equal(bf_result, pd_result) - - -@pytest.mark.parametrize( - ("json_type"), - [ - pytest.param(dtypes.JSON_DTYPE), - pytest.param("json"), - ], -) -def test_index_construct_w_json_dtype(json_type): - data = [ - "1", - "false", - '["a", {"b": 1}, null]', - None, - ] - index = bpd.Index(data, dtype=json_type) - - assert index.dtype == dtypes.JSON_DTYPE - assert index[1] == "false" +from tests.system.utils import assert_pandas_index_equal_ignore_index_type def test_get_index(scalars_df_index, scalars_pandas_df_index): @@ -200,10 +33,6 @@ def test_index_has_duplicates(scalars_df_index, scalars_pandas_df_index): assert bf_result == pd_result -def test_index_empty_has_duplicates(): - assert not bpd.Index([]).has_duplicates - - def test_index_values(scalars_df_index, scalars_pandas_df_index): bf_result = scalars_df_index.index.values pd_result = scalars_pandas_df_index.index.values @@ -251,18 +80,6 @@ def test_index_astype(scalars_df_index, scalars_pandas_df_index): pd.testing.assert_index_equal(bf_result, pd_result) -def test_index_astype_python(scalars_df_index, scalars_pandas_df_index): - bf_result = scalars_df_index.set_index("int64_col").index.astype(float).to_pandas() - pd_result = scalars_pandas_df_index.set_index("int64_col").index.astype("Float64") - pd.testing.assert_index_equal(bf_result, pd_result) - - -def test_index_astype_error_error(session): - input = pd.Index(["hello", "world", "3.11", "4000"]) - with pytest.raises(ValueError): - session.read_pandas(input).astype("Float64", errors="bad_value") - - def test_index_any(scalars_df_index, scalars_pandas_df_index): bf_result = scalars_df_index.set_index("int64_col").index.any() pd_result = scalars_pandas_df_index.set_index("int64_col").index.any() @@ -423,43 +240,6 @@ def test_index_value_counts(scalars_df_index, scalars_pandas_df_index): pd.testing.assert_series_equal(bf_result, pd_result, check_dtype=False) -@pytest.mark.parametrize( - ("level",), - [ - ("int64_too",), - ("rowindex_2",), - (1,), - ], -) -def test_index_get_level_values(scalars_df_index, scalars_pandas_df_index, level): - bf_result = ( - scalars_df_index.set_index(["int64_too", "rowindex_2"]) - .index.get_level_values(level) - .to_pandas() - ) - pd_result = scalars_pandas_df_index.set_index( - ["int64_too", "rowindex_2"] - ).index.get_level_values(level) - - pd.testing.assert_index_equal(bf_result, pd_result) - - -def test_index_to_series( - scalars_df_index, - scalars_pandas_df_index, -): - bf_result = ( - scalars_df_index.set_index(["int64_too"]) - .index.to_series(index=scalars_df_index["float64_col"], name="new_name") - .to_pandas() - ) - pd_result = scalars_pandas_df_index.set_index(["int64_too"]).index.to_series( - index=scalars_pandas_df_index["float64_col"], name="new_name" - ) - - pd.testing.assert_series_equal(bf_result, pd_result) - - @pytest.mark.parametrize( ("how",), [ @@ -502,222 +282,15 @@ def test_index_drop_duplicates(scalars_df_index, scalars_pandas_df_index, keep): ) -@pytest.mark.parametrize( - ("key",), - [("hello",), (2,), (123123321,), (2.0,), (False,), ((2,),), (pd.NA,)], -) -def test_index_contains(scalars_df_index, scalars_pandas_df_index, key): - col_name = "int64_col" - bf_result = key in scalars_df_index.set_index(col_name).index - pd_result = key in scalars_pandas_df_index.set_index(col_name).index - - assert bf_result == pd_result - - -def test_index_isin_list(scalars_df_index, scalars_pandas_df_index): - col_name = "int64_col" +def test_index_isin(scalars_df_index, scalars_pandas_df_index): bf_series = ( - scalars_df_index.set_index(col_name).index.isin([2, 55555, 4]).to_pandas() + scalars_df_index.set_index("int64_col").index.isin([2, 55555, 4]).to_pandas() ) - pd_result_array = scalars_pandas_df_index.set_index(col_name).index.isin( + pd_result_array = scalars_pandas_df_index.set_index("int64_col").index.isin( [2, 55555, 4] ) pd.testing.assert_index_equal( - pd.Index(pd_result_array).set_names(col_name), + pd.Index(pd_result_array), bf_series, + check_names=False, ) - - -def test_index_isin_bf_series(scalars_df_index, scalars_pandas_df_index, session): - col_name = "int64_col" - bf_series = ( - scalars_df_index.set_index(col_name) - .index.isin(bpd.Series([2, 55555, 4], session=session)) - .to_pandas() - ) - pd_result_array = scalars_pandas_df_index.set_index(col_name).index.isin( - [2, 55555, 4] - ) - pd.testing.assert_index_equal( - pd.Index(pd_result_array).set_names(col_name), - bf_series, - ) - - -def test_index_isin_bf_index(scalars_df_index, scalars_pandas_df_index, session): - col_name = "int64_col" - bf_series = ( - scalars_df_index.set_index(col_name) - .index.isin(bpd.Index([2, 55555, 4], session=session)) - .to_pandas() - ) - pd_result_array = scalars_pandas_df_index.set_index(col_name).index.isin( - [2, 55555, 4] - ) - pd.testing.assert_index_equal( - pd.Index(pd_result_array).set_names(col_name), - bf_series, - ) - - -def test_multiindex_name_is_none(session): - df = pd.DataFrame( - { - "A": [0, 0, 0, 1, 1, 1], - "B": ["x", "y", "z", "x", "y", "z"], - "C": [123, 345, 789, -123, -345, -789], - "D": ["a", "b", "c", "d", "e", "f"], - }, - ) - index = session.read_pandas(df).set_index(["A", "B"]).index - assert index.name is None - - -def test_multiindex_names_not_none(session): - df = pd.DataFrame( - { - "A": [0, 0, 0, 1, 1, 1], - "B": ["x", "y", "z", "x", "y", "z"], - "C": [123, 345, 789, -123, -345, -789], - "D": ["a", "b", "c", "d", "e", "f"], - }, - ) - index = session.read_pandas(df).set_index(["A", "B"]).index - assert tuple(index.names) == ("A", "B") - - -def test_multiindex_repr_includes_all_names(session): - df = pd.DataFrame( - { - "A": [0, 0, 0, 1, 1, 1], - "B": ["x", "y", "z", "x", "y", "z"], - "C": [123, 345, 789, -123, -345, -789], - "D": ["a", "b", "c", "d", "e", "f"], - }, - ) - index = session.read_pandas(df).set_index(["A", "B"]).index - assert "names=['A', 'B']" in repr(index) - - -def test_index_item(session): - # Test with a single item - bf_idx_single = bpd.Index([42], session=session) - pd_idx_single = pd.Index([42]) - assert bf_idx_single.item() == pd_idx_single.item() - - -def test_index_item_with_multiple(session): - # Test with multiple items - bf_idx_multiple = bpd.Index([1, 2, 3], session=session) - pd_idx_multiple = pd.Index([1, 2, 3]) - - try: - pd_idx_multiple.item() - except ValueError as e: - expected_message = str(e) - else: - raise AssertionError("Expected ValueError from pandas, but didn't get one") - - with pytest.raises(ValueError, match=re.escape(expected_message)): - bf_idx_multiple.item() - - -def test_index_item_with_empty(session): - # Test with an empty Index - bf_idx_empty = bpd.Index([], dtype="Int64", session=session) - pd_idx_empty: pd.Index = pd.Index([], dtype="Int64") - - try: - pd_idx_empty.item() - except ValueError as e: - expected_message = str(e) - else: - raise AssertionError("Expected ValueError from pandas, but didn't get one") - - with pytest.raises(ValueError, match=re.escape(expected_message)): - bf_idx_empty.item() - - -def test_index_to_list(scalars_df_index, scalars_pandas_df_index): - bf_result = scalars_df_index.index.to_list() - pd_result = scalars_pandas_df_index.index.to_list() - assert bf_result == pd_result - - -@pytest.mark.parametrize( - ("key", "value"), - [ - (0, "string_value"), - (1, 42), - ("label", None), - (-1, 3.14), - ], -) -def test_index_setitem_different_types(scalars_dfs, key, value): - """Tests that custom Index setitem raises TypeError.""" - scalars_df, _ = scalars_dfs - index = scalars_df.index - - with pytest.raises(TypeError, match="Index does not support mutable operations"): - index[key] = value - - -def test_custom_index_setitem_error(): - """Tests that custom Index setitem raises TypeError.""" - custom_index = bpd.Index([1, 2, 3, 4, 5], name="custom") - - with pytest.raises(TypeError, match="Index does not support mutable operations"): - custom_index[2] = 999 - - -def test_index_eq_const(scalars_df_index, scalars_pandas_df_index): - bf_result = (scalars_df_index.index == 3).to_pandas() - pd_result = scalars_pandas_df_index.index == 3 - assert bf_result == pd.Index(pd_result) - - -def test_index_eq_aligned_index(scalars_df_index, scalars_pandas_df_index): - bf_result = ( - bpd.Index(scalars_df_index.int64_col) - == bpd.Index(scalars_df_index.int64_col.abs()) - ).to_pandas() - pd_result = pd.Index(scalars_pandas_df_index.int64_col) == pd.Index( - scalars_pandas_df_index.int64_col.abs() - ) - assert bf_result == pd.Index(pd_result) - - -def test_index_str_accessor_unary(scalars_df_index, scalars_pandas_df_index): - bf_index = scalars_df_index.set_index("string_col").index - pd_index = scalars_pandas_df_index.set_index("string_col").index - - bf_result = bf_index.str.pad(30, side="both", fillchar="~").to_pandas() - pd_result = pd_index.str.pad(30, side="both", fillchar="~") - - pd.testing.assert_index_equal(bf_result, pd_result) - - -def test_index_str_accessor_binary(scalars_df_index, scalars_pandas_df_index): - if pd.__version__.startswith("1."): - pytest.skip("doesn't work in pandas 1.x.") - bf_index = scalars_df_index.set_index("string_col").index - pd_index = scalars_pandas_df_index.set_index("string_col").index - - bf_result = bf_index.str.cat(bf_index.str[:4]).to_pandas() - pd_result = pd_index.str.cat(pd_index.str[:4]) - - pd.testing.assert_index_equal(bf_result, pd_result) - - -@pytest.mark.parametrize( - ("pat"), - [(r"(ell)(lo)"), (r"(?Ph..)"), (r"(?Pe.*o)([g-l]+)")], -) -def test_index_str_extract(scalars_df_index, scalars_pandas_df_index, pat): - bf_index = scalars_df_index.set_index("string_col").index - pd_index = scalars_pandas_df_index.set_index("string_col").index - - bf_result = bf_index.str.extract(pat).to_pandas() - pd_result = pd_index.str.extract(pat) - - pd.testing.assert_frame_equal(pd_result, bf_result, check_index_type=False) diff --git a/tests/system/small/test_index_io.py b/tests/system/small/test_index_io.py deleted file mode 100644 index b4d7c06da52..00000000000 --- a/tests/system/small/test_index_io.py +++ /dev/null @@ -1,56 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -import pandas as pd - -import bigframes - - -def test_to_pandas_override_global_option(scalars_df_index): - with bigframes.option_context("compute.allow_large_results", True): - bf_index = scalars_df_index.index - - # Direct call to_pandas uses global default setting (allow_large_results=True), - bf_index.to_pandas() - table_id = bf_index._query_job.destination.table_id - assert table_id is not None - - # When allow_large_results=False, a query_job object should not be created. - # Therefore, the table_id should remain unchanged. - bf_index.to_pandas(allow_large_results=False) - assert bf_index._query_job.destination.table_id == table_id - - -def test_to_pandas_dry_run(scalars_df_index): - index = scalars_df_index.index - - result = index.to_pandas(dry_run=True) - - assert isinstance(result, pd.Series) - assert len(result) > 0 - - -def test_to_numpy_override_global_option(scalars_df_index): - with bigframes.option_context("compute.allow_large_results", True): - bf_index = scalars_df_index.index - - # Direct call to_numpy uses global default setting (allow_large_results=True), - # table has 'bqdf' prefix. - bf_index.to_numpy() - table_id = bf_index._query_job.destination.table_id - assert table_id is not None - - # When allow_large_results=False, a query_job object should not be created. - # Therefore, the table_id should remain unchanged. - bf_index.to_numpy(allow_large_results=False) - assert bf_index._query_job.destination.table_id == table_id diff --git a/tests/system/small/test_ipython.py b/tests/system/small/test_ipython.py index 2d233907181..be98ce00674 100644 --- a/tests/system/small/test_ipython.py +++ b/tests/system/small/test_ipython.py @@ -26,4 +26,4 @@ def test_repr_cache(scalars_df_index): results = display_formatter.format(test_df) assert results[0].keys() == {"text/plain", "text/html"} assert test_df._block.retrieve_repr_request_results.cache_info().misses >= 1 - assert test_df._block.retrieve_repr_request_results.cache_info().hits == 0 + assert test_df._block.retrieve_repr_request_results.cache_info().hits >= 1 diff --git a/tests/system/small/test_large_local_data.py b/tests/system/small/test_large_local_data.py deleted file mode 100644 index 39885ea853c..00000000000 --- a/tests/system/small/test_large_local_data.py +++ /dev/null @@ -1,55 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import numpy as np -import pandas as pd -import pytest - -import bigframes -from bigframes.testing.utils import assert_frame_equal - -large_dataframe = pd.DataFrame(np.random.rand(10000, 10), dtype="Float64") -large_dataframe.index = large_dataframe.index.astype("Int64") - - -def test_read_pandas_defer_noop(session: bigframes.Session): - pytest.importorskip("pandas", minversion="2.0.0") - bf_df = session.read_pandas(large_dataframe, write_engine="_deferred") - - assert_frame_equal(large_dataframe, bf_df.to_pandas()) - - -def test_read_pandas_defer_cumsum(session: bigframes.Session): - pytest.importorskip("pandas", minversion="2.0.0") - bf_df = session.read_pandas(large_dataframe, write_engine="_deferred") - bf_df = bf_df.cumsum() - - assert_frame_equal(large_dataframe.cumsum(), bf_df.to_pandas()) - - -def test_read_pandas_defer_cache_cumsum_cumsum(session: bigframes.Session): - pytest.importorskip("pandas", minversion="2.0.0") - bf_df = session.read_pandas(large_dataframe, write_engine="_deferred") - bf_df = bf_df.cumsum().cache().cumsum() - - assert_frame_equal(large_dataframe.cumsum().cumsum(), bf_df.to_pandas()) - - -def test_read_pandas_defer_peek(session: bigframes.Session): - pytest.importorskip("pandas", minversion="2.0.0") - bf_df = session.read_pandas(large_dataframe, write_engine="_deferred") - bf_result = bf_df.peek(15) - - assert len(bf_result) == 15 - assert_frame_equal(large_dataframe.loc[bf_result.index], bf_result) diff --git a/tests/system/small/test_magics.py b/tests/system/small/test_magics.py deleted file mode 100644 index eac0f233f98..00000000000 --- a/tests/system/small/test_magics.py +++ /dev/null @@ -1,100 +0,0 @@ -# Copyright 2026 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 as pd -import pytest - -import bigframes -import bigframes.pandas as bpd - -IPython = pytest.importorskip("IPython") - - -MAGIC_NAME = "bqsql" - - -@pytest.fixture(scope="module") -def ip(): - """Provides a persistent IPython shell instance for the test session.""" - from IPython.testing.globalipapp import get_ipython - - shell = get_ipython() - shell.extension_manager.load_extension("bigframes") - return shell - - -def test_magic_select_lit_to_var(ip): - bigframes.close_session() - - line = "dst_var" - cell_body = "SELECT 3" - - ip.run_cell_magic(MAGIC_NAME, line, cell_body) - - assert "dst_var" in ip.user_ns - result_df = ip.user_ns["dst_var"] - assert result_df.shape == (1, 1) - assert result_df.to_pandas().iloc[0, 0] == 3 - - -def test_magic_select_lit_dry_run(ip): - bigframes.close_session() - - line = "dst_var --dry_run" - cell_body = "SELECT 3" - - ip.run_cell_magic(MAGIC_NAME, line, cell_body) - - assert "dst_var" in ip.user_ns - result_df = ip.user_ns["dst_var"] - assert result_df.totalBytesProcessed == 0 - - -def test_magic_select_lit_display(ip): - from IPython.utils.capture import capture_output - - bigframes.close_session() - - cell_body = "SELECT 3" - - with capture_output() as io: - ip.run_cell_magic(MAGIC_NAME, "", cell_body) - assert len(io.outputs) > 0 - # Check that the output has data, regardless of the format (html, plain, etc) - available_formats = io.outputs[0].data.keys() - assert len(available_formats) > 0 - - -def test_magic_select_interpolate(ip): - bigframes.close_session() - df = bpd.read_pandas( - pd.DataFrame({"col_a": [1, 2, 3, 4, 5, 6], "col_b": [1, 2, 1, 3, 1, 2]}) - ) - const_val = 1 - - ip.push({"df": df, "const_val": const_val}) - - query = """ - SELECT - SUM(col_a) AS total - FROM - {df} - WHERE col_b={const_val} - """ - - ip.run_cell_magic(MAGIC_NAME, "dst_var", query) - - assert "dst_var" in ip.user_ns - result_df = ip.user_ns["dst_var"] - assert result_df.shape == (1, 1) - assert result_df.loc[0, "total"] == 9 diff --git a/tests/system/small/test_multiindex.py b/tests/system/small/test_multiindex.py index 18368fc5126..bc35f633fdb 100644 --- a/tests/system/small/test_multiindex.py +++ b/tests/system/small/test_multiindex.py @@ -12,85 +12,11 @@ # See the License for the specific language governing permissions and # limitations under the License. -import numpy as np import pandas import pytest import bigframes.pandas as bpd -import bigframes.testing.utils - -# Sample MultiIndex for testing DataFrames where() method. -_MULTI_INDEX = pandas.MultiIndex.from_tuples( - [ - (0, "a"), - (1, "b"), - (2, "c"), - (0, "d"), - (1, "e"), - (2, "f"), - (0, "g"), - (1, "h"), - (2, "i"), - ], - names=["A", "B"], -) - - -def test_multi_index_from_arrays(): - bf_idx = bpd.MultiIndex.from_arrays( - [ - pandas.Index([4, 99], dtype=pandas.Int64Dtype()), - pandas.Index( - [" Hello, World!", "_some_new_string"], - dtype=pandas.StringDtype(storage="pyarrow"), - ), - ], - names=[" 1index 1", "_1index 2"], - ) - pd_idx = pandas.MultiIndex.from_arrays( - [ - pandas.Index([4, 99], dtype=pandas.Int64Dtype()), - pandas.Index( - [" Hello, World!", "_some_new_string"], - dtype=pandas.StringDtype(storage="pyarrow"), - ), - ], - names=[" 1index 1", "_1index 2"], - ) - assert bf_idx.names == pd_idx.names - bigframes.testing.utils.assert_index_equal(bf_idx.to_pandas(), pd_idx) - - -def test_read_pandas_multi_index_axes(): - # TODO: supply a reason why this isn't compatible with pandas 1.x - pytest.importorskip("pandas", minversion="2.0.0") - index = pandas.MultiIndex.from_arrays( - [ - pandas.Index([4, 99], dtype=pandas.Int64Dtype()), - pandas.Index( - [" Hello, World!", "_some_new_string"], - dtype=pandas.StringDtype(storage="pyarrow"), - ), - ], - names=[" 1index 1", "_1index 2"], - ) - columns = pandas.MultiIndex.from_arrays( - [ - pandas.Index([6, 87], dtype=pandas.Int64Dtype()), - pandas.Index( - [" Bonjour le monde!", "_une_chaîne_de_caractères"], - dtype=pandas.StringDtype(storage="pyarrow"), - ), - ], - names=[" 1columns 1", "_1new_index 2"], - ) - pandas_df = pandas.DataFrame( - [[1, 2], [3, 4]], index=index, columns=columns, dtype=pandas.Int64Dtype() - ) - bf_df = bpd.DataFrame(pandas_df) - bf_df_computed = bf_df.to_pandas() - - bigframes.testing.utils.assert_frame_equal(bf_df_computed, pandas_df) +from tests.system.utils import assert_pandas_df_equal_ignore_ordering # Row Multi-index tests @@ -98,71 +24,21 @@ def test_set_multi_index(scalars_df_index, scalars_pandas_df_index): bf_result = scalars_df_index.set_index(["bool_col", "int64_too"]).to_pandas() pd_result = scalars_pandas_df_index.set_index(["bool_col", "int64_too"]) - bigframes.testing.utils.assert_frame_equal(bf_result, pd_result) + pandas.testing.assert_frame_equal(bf_result, pd_result) -@pytest.mark.parametrize( - ("level", "drop"), - [ - (None, True), - (None, False), - (1, True), - ("bool_col", True), - (["float64_col", "int64_too"], True), - ([2, 0], False), - (0, True), - ], -) -def test_df_reset_multi_index(scalars_df_index, scalars_pandas_df_index, level, drop): +def test_reset_multi_index(scalars_df_index, scalars_pandas_df_index): bf_result = ( - scalars_df_index.set_index(["bool_col", "int64_too", "float64_col"]) - .reset_index(level=level, drop=drop) - .to_pandas() + scalars_df_index.set_index(["bool_col", "int64_too"]).reset_index().to_pandas() ) pd_result = scalars_pandas_df_index.set_index( - ["bool_col", "int64_too", "float64_col"] - ).reset_index(level=level, drop=drop) + ["bool_col", "int64_too"] + ).reset_index() # Pandas uses int64 instead of Int64 (nullable) dtype. - if pd_result.index.dtype != bf_result.index.dtype: - pd_result.index = pd_result.index.astype(bf_result.index.dtype) - - bigframes.testing.utils.assert_frame_equal(bf_result, pd_result) - - -@pytest.mark.parametrize( - ("level", "drop"), - [ - (None, True), - (None, False), - (1, True), - ("bool_col", True), - (["float64_col", "int64_too"], True), - ([2, 0], False), - ], -) -def test_series_reset_multi_index( - scalars_df_index, scalars_pandas_df_index, level, drop -): - bf_result = ( - scalars_df_index.set_index(["bool_col", "int64_too", "float64_col"])[ - "string_col" - ] - .reset_index(level=level, drop=drop) - .to_pandas() - ) - pd_result = scalars_pandas_df_index.set_index( - ["bool_col", "int64_too", "float64_col"] - )["string_col"].reset_index(level=level, drop=drop) - - # Pandas uses int64 instead of Int64 (nullable) dtype. - if pd_result.index.dtype != bf_result.index.dtype: - pd_result.index = pd_result.index.astype(pandas.Int64Dtype()) + pd_result.index = pd_result.index.astype(pandas.Int64Dtype()) - if drop: - bigframes.testing.utils.assert_series_equal(bf_result, pd_result) - else: - bigframes.testing.utils.assert_frame_equal(bf_result, pd_result) + pandas.testing.assert_frame_equal(bf_result, pd_result) def test_series_multi_index_idxmin(scalars_df_index, scalars_pandas_df_index): @@ -187,7 +63,7 @@ def test_binop_series_series_matching_multi_indices( bf_result = bf_left["int64_col"] + bf_right["int64_too"] pd_result = pd_left["int64_col"] + pd_right["int64_too"] - bigframes.testing.utils.assert_series_equal( + pandas.testing.assert_series_equal( bf_result.sort_index().to_pandas(), pd_result.sort_index() ) @@ -203,7 +79,7 @@ def test_binop_df_series_matching_multi_indices( bf_result = bf_left[["int64_col", "int64_too"]].add(bf_right["int64_too"], axis=0) pd_result = pd_left[["int64_col", "int64_too"]].add(pd_right["int64_too"], axis=0) - bigframes.testing.utils.assert_frame_equal( + pandas.testing.assert_frame_equal( bf_result.sort_index().to_pandas(), pd_result.sort_index() ) @@ -217,7 +93,7 @@ def test_binop_multi_index_mono_index(scalars_df_index, scalars_pandas_df_index) bf_result = bf_left["int64_col"] + bf_right["int64_too"] pd_result = pd_left["int64_col"] + pd_right["int64_too"] - bigframes.testing.utils.assert_series_equal(bf_result.to_pandas(), pd_result) + pandas.testing.assert_series_equal(bf_result.to_pandas(), pd_result) def test_binop_overlapping_multi_indices(scalars_df_index, scalars_pandas_df_index): @@ -229,7 +105,7 @@ def test_binop_overlapping_multi_indices(scalars_df_index, scalars_pandas_df_ind bf_result = bf_left["int64_col"] + bf_right["int64_too"] pd_result = pd_left["int64_col"] + pd_right["int64_too"] - bigframes.testing.utils.assert_series_equal( + pandas.testing.assert_series_equal( bf_result.sort_index().to_pandas(), pd_result.sort_index() ) @@ -245,7 +121,7 @@ def test_concat_compatible_multi_indices(scalars_df_index, scalars_pandas_df_ind bf_result = bpd.concat([bf_left, bf_right]) pd_result = pandas.concat([pd_left, pd_right]) - bigframes.testing.utils.assert_frame_equal(bf_result.to_pandas(), pd_result) + pandas.testing.assert_frame_equal(bf_result.to_pandas(), pd_result) def test_concat_multi_indices_ignore_index(scalars_df_index, scalars_pandas_df_index): @@ -260,35 +136,16 @@ def test_concat_multi_indices_ignore_index(scalars_df_index, scalars_pandas_df_i # Pandas uses int64 instead of Int64 (nullable) dtype. pd_result.index = pd_result.index.astype(pandas.Int64Dtype()) - bigframes.testing.utils.assert_frame_equal(bf_result.to_pandas(), pd_result) + pandas.testing.assert_frame_equal(bf_result.to_pandas(), pd_result) -@pytest.mark.parametrize( - ("key"), - [ - (2), - ([2, 0]), - ([(2, "capitalize, This "), (-2345, "Hello, World!")]), - ], -) -def test_multi_index_loc_multi_row(scalars_df_index, scalars_pandas_df_index, key): +def test_multi_index_loc(scalars_df_index, scalars_pandas_df_index): bf_result = ( - scalars_df_index.set_index(["int64_too", "string_col"]).loc[key].to_pandas() + scalars_df_index.set_index(["int64_too", "bool_col"]).loc[[2, 0]].to_pandas() ) - pd_result = scalars_pandas_df_index.set_index(["int64_too", "string_col"]).loc[key] - - bigframes.testing.utils.assert_frame_equal(bf_result, pd_result) + pd_result = scalars_pandas_df_index.set_index(["int64_too", "bool_col"]).loc[[2, 0]] - -def test_multi_index_loc_single_row(scalars_df_index, scalars_pandas_df_index): - bf_result = scalars_df_index.set_index(["int64_too", "string_col"]).loc[ - (2, "capitalize, This ") - ] - pd_result = scalars_pandas_df_index.set_index(["int64_too", "string_col"]).loc[ - (2, "capitalize, This ") - ] - - bigframes.testing.utils.assert_series_equal(bf_result, pd_result) + pandas.testing.assert_frame_equal(bf_result, pd_result) def test_multi_index_getitem_bool(scalars_df_index, scalars_pandas_df_index): @@ -298,7 +155,7 @@ def test_multi_index_getitem_bool(scalars_df_index, scalars_pandas_df_index): bf_result = bf_frame[bf_frame["int64_col"] > 0].to_pandas() pd_result = pd_frame[pd_frame["int64_col"] > 0] - bigframes.testing.utils.assert_frame_equal(bf_result, pd_result) + pandas.testing.assert_frame_equal(bf_result, pd_result) @pytest.mark.parametrize( @@ -318,7 +175,7 @@ def test_df_multi_index_droplevel(scalars_df_index, scalars_pandas_df_index, lev bf_result = bf_frame.droplevel(level).to_pandas() pd_result = pd_frame.droplevel(level) - bigframes.testing.utils.assert_frame_equal(bf_result, pd_result) + pandas.testing.assert_frame_equal(bf_result, pd_result) @pytest.mark.parametrize( @@ -338,7 +195,7 @@ def test_series_multi_index_droplevel(scalars_df_index, scalars_pandas_df_index, bf_result = bf_frame["string_col"].droplevel(level).to_pandas() pd_result = pd_frame["string_col"].droplevel(level) - bigframes.testing.utils.assert_series_equal(bf_result, pd_result) + pandas.testing.assert_series_equal(bf_result, pd_result) @pytest.mark.parametrize( @@ -347,7 +204,6 @@ def test_series_multi_index_droplevel(scalars_df_index, scalars_pandas_df_index, (1, 0), ([0, 1], 0), ([True, None], 1), - ((0, True), None), ], ) def test_multi_index_drop(scalars_df_index, scalars_pandas_df_index, labels, level): @@ -357,7 +213,7 @@ def test_multi_index_drop(scalars_df_index, scalars_pandas_df_index, labels, lev bf_result = bf_frame.drop(labels=labels, axis="index", level=level).to_pandas() pd_result = pd_frame.drop(labels=labels, axis="index", level=level) - bigframes.testing.utils.assert_frame_equal(bf_result, pd_result) + pandas.testing.assert_frame_equal(bf_result, pd_result) @pytest.mark.parametrize( @@ -382,7 +238,7 @@ def test_df_multi_index_reorder_levels( bf_result = bf_frame.reorder_levels(order).to_pandas() pd_result = pd_frame.reorder_levels(order) - bigframes.testing.utils.assert_frame_equal(bf_result, pd_result) + pandas.testing.assert_frame_equal(bf_result, pd_result) @pytest.mark.parametrize( @@ -407,7 +263,7 @@ def test_series_multi_index_reorder_levels( bf_result = bf_frame["string_col"].reorder_levels(order).to_pandas() pd_result = pd_frame["string_col"].reorder_levels(order) - bigframes.testing.utils.assert_series_equal(bf_result, pd_result) + pandas.testing.assert_series_equal(bf_result, pd_result) def test_df_multi_index_swaplevel(scalars_df_index, scalars_pandas_df_index): @@ -417,7 +273,7 @@ def test_df_multi_index_swaplevel(scalars_df_index, scalars_pandas_df_index): bf_result = bf_frame.swaplevel().to_pandas() pd_result = pd_frame.swaplevel() - bigframes.testing.utils.assert_frame_equal(bf_result, pd_result) + pandas.testing.assert_frame_equal(bf_result, pd_result) def test_series_multi_index_swaplevel(scalars_df_index, scalars_pandas_df_index): @@ -427,7 +283,7 @@ def test_series_multi_index_swaplevel(scalars_df_index, scalars_pandas_df_index) bf_result = bf_frame["string_col"].swaplevel(0, 2).to_pandas() pd_result = pd_frame["string_col"].swaplevel(0, 2) - bigframes.testing.utils.assert_series_equal(bf_result, pd_result) + pandas.testing.assert_series_equal(bf_result, pd_result) def test_multi_index_series_groupby(scalars_df_index, scalars_pandas_df_index): @@ -443,7 +299,7 @@ def test_multi_index_series_groupby(scalars_df_index, scalars_pandas_df_index): pd_frame["float64_col"].groupby([pd_frame.int64_col % 2, "bool_col"]).mean() ) - bigframes.testing.utils.assert_series_equal(bf_result, pd_result) + pandas.testing.assert_series_equal(bf_result, pd_result) @pytest.mark.parametrize( @@ -470,7 +326,7 @@ def test_multi_index_series_groupby_level( .mean() ) - bigframes.testing.utils.assert_series_equal(bf_result, pd_result) + pandas.testing.assert_series_equal(bf_result, pd_result) def test_multi_index_dataframe_groupby(scalars_df_index, scalars_pandas_df_index): @@ -485,7 +341,7 @@ def test_multi_index_dataframe_groupby(scalars_df_index, scalars_pandas_df_index numeric_only=True ) - bigframes.testing.utils.assert_frame_equal(bf_result, pd_result) + pandas.testing.assert_frame_equal(bf_result, pd_result) @pytest.mark.parametrize( @@ -500,30 +356,20 @@ def test_multi_index_dataframe_groupby(scalars_df_index, scalars_pandas_df_index def test_multi_index_dataframe_groupby_level_aggregate( scalars_df_index, scalars_pandas_df_index, level, as_index ): - index_cols = ["int64_too", "bool_col"] bf_result = ( - scalars_df_index.set_index(index_cols) + scalars_df_index.set_index(["int64_too", "bool_col"]) .groupby(level=level, as_index=as_index) .mean(numeric_only=True) .to_pandas() ) pd_result = ( - scalars_pandas_df_index.set_index(index_cols) + scalars_pandas_df_index.set_index(["int64_too", "bool_col"]) .groupby(level=level, as_index=as_index) .mean(numeric_only=True) ) - # For as_index=False, pandas will drop index levels used as groupings - # In the future, it will include this in the result, bigframes already does this behavior - if not pandas.__version__.startswith("3"): - if not as_index: - for col in index_cols: - if col in bf_result.columns: - bf_result = bf_result.drop(col, axis=1) # Pandas will have int64 index, while bigquery will have Int64 when resetting - bigframes.testing.utils.assert_frame_equal( - bf_result, pd_result, check_index_type=False - ) + pandas.testing.assert_frame_equal(bf_result, pd_result, check_index_type=False) @pytest.mark.parametrize( @@ -541,22 +387,19 @@ def test_multi_index_dataframe_groupby_level_aggregate( def test_multi_index_dataframe_groupby_level_analytic( scalars_df_index, scalars_pandas_df_index, level, as_index ): - # Drop "numeric_col" as pandas doesn't support numerics for grouped window function bf_result = ( - scalars_df_index.drop("numeric_col", axis=1) - .set_index(["int64_too", "bool_col"]) + scalars_df_index.set_index(["int64_too", "bool_col"]) .groupby(level=level, as_index=as_index, dropna=False) .cumsum(numeric_only=True) .to_pandas() ) pd_result = ( - scalars_pandas_df_index.drop("numeric_col", axis=1) - .set_index(["int64_too", "bool_col"]) + scalars_pandas_df_index.set_index(["int64_too", "bool_col"]) .groupby(level=level, as_index=as_index, dropna=False) .cumsum(numeric_only=True) ) - bigframes.testing.utils.assert_frame_equal(bf_result, pd_result, check_dtype=False) + pandas.testing.assert_frame_equal(bf_result, pd_result, check_dtype=False) all_joins = pytest.mark.parametrize( @@ -586,7 +429,7 @@ def test_multi_index_dataframe_join(scalars_dfs, how): (["bool_col", "rowindex_2"]) )[["float64_col"]] pd_result = pd_df_a.join(pd_df_b, how=how) - bigframes.testing.utils.assert_frame_equal(bf_result, pd_result, ignore_order=True) + assert_pandas_df_equal_ignore_ordering(bf_result, pd_result) @all_joins @@ -607,141 +450,7 @@ def test_multi_index_dataframe_join_on(scalars_dfs, how): pd_df_a = pd_df_a.assign(rowindex_2=pd_df_a["rowindex_2"] + 2) pd_df_b = pd_df[["float64_col"]] pd_result = pd_df_a.join(pd_df_b, on="rowindex_2", how=how) - bigframes.testing.utils.assert_frame_equal(bf_result, pd_result, ignore_order=True) - - -def test_multi_index_dataframe_where_series_cond_none_other( - scalars_df_index, scalars_pandas_df_index -): - columns = ["int64_col", "float64_col"] - - # Create multi-index dataframe. - dataframe_bf = bpd.DataFrame( - scalars_df_index[columns].values, - index=_MULTI_INDEX, - columns=scalars_df_index[columns].columns, - ) - dataframe_pd = pandas.DataFrame( - scalars_pandas_df_index[columns].values, - index=_MULTI_INDEX, - columns=scalars_pandas_df_index[columns].columns, - ) - dataframe_bf.columns.name = "test_name" - dataframe_pd.columns.name = "test_name" - - # When condition is series and other is None. - series_cond_bf = dataframe_bf["int64_col"] > 0 - series_cond_pd = dataframe_pd["int64_col"] > 0 - - bf_result = dataframe_bf.where(series_cond_bf).to_pandas() - pd_result = dataframe_pd.where(series_cond_pd) - bigframes.testing.utils.assert_frame_equal( - bf_result, - pd_result, - check_index_type=False, - check_dtype=False, - ) - # Assert the index is still MultiIndex after the operation. - assert isinstance(bf_result.index, pandas.MultiIndex), "Expected a MultiIndex" - assert isinstance(pd_result.index, pandas.MultiIndex), "Expected a MultiIndex" - - -def test_multi_index_dataframe_where_series_cond_dataframe_other( - scalars_df_index, scalars_pandas_df_index -): - columns = ["int64_col", "int64_too"] - - # Create multi-index dataframe. - dataframe_bf = bpd.DataFrame( - scalars_df_index[columns].values, - index=_MULTI_INDEX, - columns=scalars_df_index[columns].columns, - ) - dataframe_pd = pandas.DataFrame( - scalars_pandas_df_index[columns].values, - index=_MULTI_INDEX, - columns=scalars_pandas_df_index[columns].columns, - ) - - # When condition is series and other is dataframe. - series_cond_bf = dataframe_bf["int64_col"] > 1000.0 - series_cond_pd = dataframe_pd["int64_col"] > 1000.0 - dataframe_other_bf = dataframe_bf * 100.0 - dataframe_other_pd = dataframe_pd * 100.0 - - bf_result = dataframe_bf.where(series_cond_bf, dataframe_other_bf).to_pandas() - pd_result = dataframe_pd.where(series_cond_pd, dataframe_other_pd) - bigframes.testing.utils.assert_frame_equal( - bf_result, - pd_result, - check_index_type=False, - check_dtype=False, - ) - - -def test_multi_index_dataframe_where_dataframe_cond_constant_other( - scalars_df_index, scalars_pandas_df_index -): - columns = ["int64_col", "float64_col"] - - # Create multi-index dataframe. - dataframe_bf = bpd.DataFrame( - scalars_df_index[columns].values, - index=_MULTI_INDEX, - columns=scalars_df_index[columns].columns, - ) - dataframe_pd = pandas.DataFrame( - scalars_pandas_df_index[columns].values, - index=_MULTI_INDEX, - columns=scalars_pandas_df_index[columns].columns, - ) - - # When condition is dataframe and other is a constant. - dataframe_cond_bf = dataframe_bf > 0 - dataframe_cond_pd = dataframe_pd > 0 - other = 0 - - bf_result = dataframe_bf.where(dataframe_cond_bf, other).to_pandas() - pd_result = dataframe_pd.where(dataframe_cond_pd, other) - bigframes.testing.utils.assert_frame_equal( - bf_result, - pd_result, - check_index_type=False, - check_dtype=False, - ) - - -def test_multi_index_dataframe_where_dataframe_cond_dataframe_other( - scalars_df_index, scalars_pandas_df_index -): - columns = ["int64_col", "int64_too", "float64_col"] - - # Create multi-index dataframe. - dataframe_bf = bpd.DataFrame( - scalars_df_index[columns].values, - index=_MULTI_INDEX, - columns=scalars_df_index[columns].columns, - ) - dataframe_pd = pandas.DataFrame( - scalars_pandas_df_index[columns].values, - index=_MULTI_INDEX, - columns=scalars_pandas_df_index[columns].columns, - ) - - # When condition is dataframe and other is dataframe. - dataframe_cond_bf = dataframe_bf < 1000.0 - dataframe_cond_pd = dataframe_pd < 1000.0 - dataframe_other_bf = dataframe_bf * -1.0 - dataframe_other_pd = dataframe_pd * -1.0 - - bf_result = dataframe_bf.where(dataframe_cond_bf, dataframe_other_bf).to_pandas() - pd_result = dataframe_pd.where(dataframe_cond_pd, dataframe_other_pd) - bigframes.testing.utils.assert_frame_equal( - bf_result, - pd_result, - check_index_type=False, - check_dtype=False, - ) + assert_pandas_df_equal_ignore_ordering(bf_result, pd_result) @pytest.mark.parametrize( @@ -768,7 +477,7 @@ def test_multi_index_series_groupby_level_aggregate( .mean() ) - bigframes.testing.utils.assert_series_equal(bf_result, pd_result, check_dtype=False) + pandas.testing.assert_series_equal(bf_result, pd_result, check_dtype=False) @pytest.mark.parametrize( @@ -795,7 +504,7 @@ def test_multi_index_series_groupby_level_analytic( .cumsum() ) - bigframes.testing.utils.assert_series_equal(bf_result, pd_result, check_dtype=False) + pandas.testing.assert_series_equal(bf_result, pd_result, check_dtype=False) def test_multi_index_series_rename_dict_same_type( @@ -810,7 +519,7 @@ def test_multi_index_series_rename_dict_same_type( "string_col" ].rename({1: 100, 2: 200}) - bigframes.testing.utils.assert_series_equal( + pandas.testing.assert_series_equal( bf_result, pd_result, check_dtype=False, check_index_type=False ) @@ -828,7 +537,7 @@ def test_multi_index_df_reindex(scalars_df_index, scalars_pandas_df_index): pd_result = scalars_pandas_df_index.set_index(["rowindex_2", "string_col"]).reindex( index=new_index ) - bigframes.testing.utils.assert_frame_equal( + pandas.testing.assert_frame_equal( bf_result, pd_result, check_dtype=False, check_index_type=False ) @@ -846,15 +555,15 @@ def test_column_multi_index_getitem(scalars_df_index, scalars_pandas_df_index): bf_a = bf_df["a"].to_pandas() pd_a = pd_df["a"] - bigframes.testing.utils.assert_frame_equal(bf_a, pd_a) + pandas.testing.assert_frame_equal(bf_a, pd_a) bf_b = bf_df["b"].to_pandas() pd_b = pd_df["b"] - bigframes.testing.utils.assert_frame_equal(bf_b, pd_b) + pandas.testing.assert_frame_equal(bf_b, pd_b) bf_fullkey = bf_df[("a", "int64_too")].to_pandas() pd_fullkey = pd_df[("a", "int64_too")] - bigframes.testing.utils.assert_series_equal(bf_fullkey, pd_fullkey) + pandas.testing.assert_series_equal(bf_fullkey, pd_fullkey) def test_column_multi_index_concat(scalars_df_index, scalars_pandas_df_index): @@ -879,7 +588,7 @@ def test_column_multi_index_concat(scalars_df_index, scalars_pandas_df_index): bf_result = bpd.concat([bf_df1, bf_df2, bf_df1]).to_pandas() pd_result = pandas.concat([pd_df1, pd_df2, pd_df1]) - bigframes.testing.utils.assert_frame_equal(bf_result, pd_result) + pandas.testing.assert_frame_equal(bf_result, pd_result) def test_column_multi_index_drop(scalars_df_index, scalars_pandas_df_index): @@ -892,7 +601,7 @@ def test_column_multi_index_drop(scalars_df_index, scalars_pandas_df_index): bf_a = bf_df.drop(("a", "int64_too"), axis=1).to_pandas() pd_a = pd_df.drop(("a", "int64_too"), axis=1) - bigframes.testing.utils.assert_frame_equal(bf_a, pd_a) + pandas.testing.assert_frame_equal(bf_a, pd_a) @pytest.mark.parametrize( @@ -916,7 +625,7 @@ def test_column_multi_index_assign(scalars_df_index, scalars_pandas_df_index, ke pd_result = pd_df.assign(**kwargs) # Pandas assign results in non-nullable dtype - bigframes.testing.utils.assert_frame_equal(bf_result, pd_result, check_dtype=False) + pandas.testing.assert_frame_equal(bf_result, pd_result, check_dtype=False) def test_column_multi_index_rename(scalars_df_index, scalars_pandas_df_index): @@ -930,37 +639,23 @@ def test_column_multi_index_rename(scalars_df_index, scalars_pandas_df_index): bf_result = bf_df.rename(columns={"b": "c"}).to_pandas() pd_result = pd_df.rename(columns={"b": "c"}) - bigframes.testing.utils.assert_frame_equal(bf_result, pd_result) + pandas.testing.assert_frame_equal(bf_result, pd_result) -@pytest.mark.parametrize( - ("names", "col_fill", "col_level"), - [ - (None, "", "l2"), - (("new_name"), "fill", 1), - ("new_name", "fill", 0), - ], -) -def test_column_multi_index_reset_index( - scalars_df_index, scalars_pandas_df_index, names, col_fill, col_level -): +def test_column_multi_index_reset_index(scalars_df_index, scalars_pandas_df_index): columns = ["int64_too", "int64_col", "float64_col"] - multi_columns = pandas.MultiIndex.from_tuples( - zip(["a", "b", "a"], ["a", "b", "b"]), names=["l1", "l2"] - ) + multi_columns = pandas.MultiIndex.from_tuples(zip(["a", "b", "a"], ["a", "b", "b"])) bf_df = scalars_df_index[columns].copy() bf_df.columns = multi_columns pd_df = scalars_pandas_df_index[columns].copy() pd_df.columns = multi_columns - bf_result = bf_df.reset_index( - names=names, col_fill=col_fill, col_level=col_level - ).to_pandas() - pd_result = pd_df.reset_index(names=names, col_fill=col_fill, col_level=col_level) + bf_result = bf_df.reset_index().to_pandas() + pd_result = pd_df.reset_index() # Pandas uses int64 instead of Int64 (nullable) dtype. pd_result.index = pd_result.index.astype(pandas.Int64Dtype()) - bigframes.testing.utils.assert_frame_equal(bf_result, pd_result) + pandas.testing.assert_frame_equal(bf_result, pd_result) def test_column_multi_index_binary_op(scalars_df_index, scalars_pandas_df_index): @@ -974,28 +669,7 @@ def test_column_multi_index_binary_op(scalars_df_index, scalars_pandas_df_index) bf_result = (bf_df[("a", "a")] + 3).to_pandas() pd_result = pd_df[("a", "a")] + 3 - bigframes.testing.utils.assert_series_equal(bf_result, pd_result) - - -def test_column_multi_index_any(): - # TODO: supply a reason why this isn't compatible with pandas 1.x - pytest.importorskip("pandas", minversion="2.0.0") - columns = pandas.MultiIndex.from_tuples( - [("col0", "col00"), ("col0", "col00"), ("col1", "col11")] - ) - pd_df = pandas.DataFrame( - [[0, 1, 2], [0, 1, 2], [0, 1, 2], [0, 1, 2]], columns=columns - ) - bf_df = bpd.DataFrame(pd_df) - - pd_result = pd_df.isna().any() - bf_result = bf_df.isna().any().to_pandas() - - bigframes.testing.utils.assert_frame_equal( - bf_result.reset_index(drop=False), - pd_result.reset_index(drop=False), - check_dtype=False, - ) + pandas.testing.assert_series_equal(bf_result, pd_result) def test_column_multi_index_agg(scalars_df_index, scalars_pandas_df_index): @@ -1011,9 +685,7 @@ def test_column_multi_index_agg(scalars_df_index, scalars_pandas_df_index): # Pandas may produce narrower numeric types, but bigframes always produces Float64 pd_result = pd_result.astype("Float64") - bigframes.testing.utils.assert_frame_equal( - bf_result, pd_result, check_index_type=False - ) + pandas.testing.assert_frame_equal(bf_result, pd_result, check_index_type=False) def test_column_multi_index_prefix_suffix(scalars_df_index, scalars_pandas_df_index): @@ -1027,7 +699,7 @@ def test_column_multi_index_prefix_suffix(scalars_df_index, scalars_pandas_df_in bf_result = bf_df.add_prefix("prefixed_").add_suffix("_suffixed").to_pandas() pd_result = pd_df.add_prefix("prefixed_").add_suffix("_suffixed") - bigframes.testing.utils.assert_frame_equal(bf_result, pd_result) + pandas.testing.assert_frame_equal(bf_result, pd_result) def test_column_multi_index_cumsum(scalars_df_index, scalars_pandas_df_index): @@ -1043,7 +715,7 @@ def test_column_multi_index_cumsum(scalars_df_index, scalars_pandas_df_index): bf_result = bf_df.cumsum().to_pandas() pd_result = pd_df.cumsum() - bigframes.testing.utils.assert_frame_equal(bf_result, pd_result, check_dtype=False) + pandas.testing.assert_frame_equal(bf_result, pd_result, check_dtype=False) @pytest.mark.parametrize( @@ -1075,8 +747,7 @@ def test_column_multi_index_stack(level): # Pandas produces NaN, where bq dataframes produces pd.NA # Column ordering seems to depend on pandas version - assert isinstance(pd_result, pandas.DataFrame) - bigframes.testing.utils.assert_frame_equal( + pandas.testing.assert_frame_equal( bf_result, pd_result, check_dtype=False, check_index_type=False ) @@ -1104,16 +775,16 @@ def test_column_multi_index_melt(): pd_result = pd_df.melt() # BigFrames uses different string and int types, but values are identical - bigframes.testing.utils.assert_frame_equal( + pandas.testing.assert_frame_equal( bf_result, pd_result, check_index_type=False, check_dtype=False ) def test_column_multi_index_unstack(scalars_df_index, scalars_pandas_df_index): columns = ["int64_too", "int64_col", "rowindex_2"] - level1: pandas.Index = pandas.Index(["b", "a", "b"], dtype="string[pyarrow]") + level1 = pandas.Index(["b", "a", "b"], dtype="string[pyarrow]") # Need resulting column to be pyarrow string rather than object dtype - level2: pandas.Index = pandas.Index(["a", "b", "b"], dtype="string[pyarrow]") + level2 = pandas.Index(["a", "b", "b"], dtype="string[pyarrow]") multi_columns = pandas.MultiIndex.from_arrays([level1, level2]) bf_df = scalars_df_index[columns].copy() bf_df.columns = multi_columns @@ -1126,53 +797,26 @@ def test_column_multi_index_unstack(scalars_df_index, scalars_pandas_df_index): # Pandas produces NaN, where bq dataframes produces pd.NA # Column ordering seems to depend on pandas version - bigframes.testing.utils.assert_series_equal(bf_result, pd_result, check_dtype=False) - - -def test_corr_w_multi_index(scalars_df_index, scalars_pandas_df_index): - columns = ["int64_too", "float64_col", "int64_col"] - multi_columns = pandas.MultiIndex.from_tuples( - zip(["a", "b", "b"], [1, 2, 2]), names=[None, "level_2"] - ) - - bf = scalars_df_index[columns].copy() - bf.columns = multi_columns - - pd_df = scalars_pandas_df_index[columns].copy() - pd_df.columns = multi_columns + pandas.testing.assert_series_equal(bf_result, pd_result, check_dtype=False) - bf_result = bf.corr(numeric_only=True).to_pandas() - pd_result = pd_df.corr(numeric_only=True) - - # BigFrames and Pandas differ in their data type handling: - # - Column types: BigFrames uses Float64, Pandas uses float64. - # - Index types: BigFrames uses strign, Pandas uses object. - bigframes.testing.utils.assert_frame_equal( - bf_result, pd_result, check_dtype=False, check_index_type=False - ) - - -def test_cov_w_multi_index(scalars_df_index, scalars_pandas_df_index): - columns = ["int64_too", "float64_col", "int64_col"] - multi_columns = pandas.MultiIndex.from_tuples( - zip(["a", "b", "b"], [1, 2, 2]), names=["level_1", None] - ) - - bf = scalars_df_index[columns].copy() - bf.columns = multi_columns +@pytest.mark.skip(reason="Pandas fails in newer versions.") +def test_column_multi_index_w_na_stack(scalars_df_index, scalars_pandas_df_index): + columns = ["int64_too", "int64_col", "rowindex_2"] + level1 = pandas.Index(["b", pandas.NA, pandas.NA]) + # Need resulting column to be pyarrow string rather than object dtype + level2 = pandas.Index([pandas.NA, "b", "b"], dtype="string[pyarrow]") + multi_columns = pandas.MultiIndex.from_arrays([level1, level2]) + bf_df = scalars_df_index[columns].copy() + bf_df.columns = multi_columns pd_df = scalars_pandas_df_index[columns].copy() pd_df.columns = multi_columns - bf_result = bf.cov(numeric_only=True).to_pandas() - pd_result = pd_df.cov(numeric_only=True) + bf_result = bf_df.stack().to_pandas() + pd_result = pd_df.stack() - # BigFrames and Pandas differ in their data type handling: - # - Column types: BigFrames uses Float64, Pandas uses float64. - # - Index types: BigFrames uses string, Pandas uses object. - bigframes.testing.utils.assert_frame_equal( - bf_result, pd_result, check_dtype=False, check_index_type=False - ) + # Pandas produces NaN, where bq dataframes produces pd.NA + pandas.testing.assert_frame_equal(bf_result, pd_result, check_dtype=False) @pytest.mark.parametrize( @@ -1249,7 +893,7 @@ def test_column_multi_index_droplevel(scalars_df_index, scalars_pandas_df_index) bf_result = bf_df.droplevel(1, axis=1).to_pandas() pd_result = pd_df.droplevel(1, axis=1) - bigframes.testing.utils.assert_frame_equal(bf_result, pd_result) + pandas.testing.assert_frame_equal(bf_result, pd_result) def test_df_column_multi_index_reindex(scalars_df_index, scalars_pandas_df_index): @@ -1271,7 +915,7 @@ def test_df_column_multi_index_reindex(scalars_df_index, scalars_pandas_df_index # Pandas uses float64 as default for newly created empty column, bf uses Float64 pd_result[("z", "a")] = pd_result[("z", "a")].astype(pandas.Float64Dtype()) - bigframes.testing.utils.assert_frame_equal( + pandas.testing.assert_frame_equal( bf_result, pd_result, ) @@ -1290,7 +934,7 @@ def test_column_multi_index_reorder_levels(scalars_df_index, scalars_pandas_df_i bf_result = bf_df.reorder_levels([-2, -1, 0], axis=1).to_pandas() pd_result = pd_df.reorder_levels([-2, -1, 0], axis=1) - bigframes.testing.utils.assert_frame_equal(bf_result, pd_result) + pandas.testing.assert_frame_equal(bf_result, pd_result) @pytest.mark.parametrize( @@ -1307,7 +951,7 @@ def test_df_multi_index_unstack(hockey_df, hockey_pandas_df, level): ["team_name", "position"], append=True ).unstack(level=level) - bigframes.testing.utils.assert_frame_equal(bf_result, pd_result, check_dtype=False) + pandas.testing.assert_frame_equal(bf_result, pd_result, check_dtype=False) @pytest.mark.parametrize( @@ -1324,7 +968,7 @@ def test_series_multi_index_unstack(hockey_df, hockey_pandas_df, level): "number" ].unstack(level=level) - bigframes.testing.utils.assert_frame_equal(bf_result, pd_result, check_dtype=False) + pandas.testing.assert_frame_equal(bf_result, pd_result, check_dtype=False) def test_column_multi_index_swaplevel(scalars_df_index, scalars_pandas_df_index): @@ -1340,7 +984,7 @@ def test_column_multi_index_swaplevel(scalars_df_index, scalars_pandas_df_index) bf_result = bf_df.swaplevel(-3, -1, axis=1).to_pandas() pd_result = pd_df.swaplevel(-3, -1, axis=1) - bigframes.testing.utils.assert_frame_equal(bf_result, pd_result) + pandas.testing.assert_frame_equal(bf_result, pd_result) def test_df_multi_index_dot_not_supported(): @@ -1401,123 +1045,3 @@ def test_column_multi_index_dot_not_supported(): NotImplementedError, match="Multi-level column input is not supported" ): bf1 @ bf2 - - -def test_explode_w_column_multi_index(): - data = [[[1, 1], np.nan, [3, 3]], [[2], [5], []]] - multi_level_columns = pandas.MultiIndex.from_arrays( - [["col0", "col0", "col1"], ["col00", "col01", "col11"]] - ) - - df = bpd.DataFrame(data, columns=multi_level_columns) - pd_df = df.to_pandas() - - assert isinstance(pd_df, pandas.DataFrame) - assert isinstance(pd_df["col0"], pandas.DataFrame) - bigframes.testing.utils.assert_frame_equal( - df["col0"].explode("col00").to_pandas(), - pd_df["col0"].explode("col00"), - check_dtype=False, - check_index_type=False, - ) - - -def test_explode_w_multi_index(): - data = [[[1, 1], np.nan, [3, 3]], [[2], [5], []]] - columns = ["col00", "col01", "col11"] - multi_index = pandas.MultiIndex.from_frame( - pandas.DataFrame({"idx0": [5, 1], "idx1": ["z", "x"]}) - ) - - df = bpd.DataFrame(data, index=multi_index, columns=columns) - pd_df = df.to_pandas() - - bigframes.testing.utils.assert_frame_equal( - df.explode("col00").to_pandas(), - pd_df.explode("col00"), - check_dtype=False, - check_index_type=False, - ) - - -def test_column_multi_index_w_na_stack(scalars_df_index, scalars_pandas_df_index): - columns = ["int64_too", "int64_col", "rowindex_2"] - level1 = pandas.Index(["b", "c", "d"]) - # Need resulting column to be pyarrow string rather than object dtype - level2: pandas.Index = pandas.Index([None, "b", "b"], dtype="string[pyarrow]") - multi_columns = pandas.MultiIndex.from_arrays([level1, level2]) - bf_df = scalars_df_index[columns].copy() - bf_df.columns = multi_columns - pd_df = scalars_pandas_df_index[columns].copy() - pd_df.columns = multi_columns - - pd_result = pd_df.stack() - bf_result = bf_df.stack().to_pandas() - - # Pandas produces pd.NA, where bq dataframes produces NaN - pd_result["c"] = pd_result["c"].replace(pandas.NA, np.nan) - bigframes.testing.utils.assert_frame_equal(bf_result, pd_result, check_dtype=False) - - -@pytest.mark.parametrize( - ("key",), - [ - ("hello",), - (2,), - (123123321,), - (2.0,), - (pandas.NA,), - (False,), - ((2,),), - ((2, False),), - ((2.0, False),), - ((2, True),), - ], -) -def test_multi_index_contains(scalars_df_index, scalars_pandas_df_index, key): - col_name = ["int64_col", "bool_col"] - bf_result = key in scalars_df_index.set_index(col_name).index - pd_result = key in scalars_pandas_df_index.set_index(col_name).index - - assert bf_result == pd_result - - -def test_multiindex_eq_const(scalars_df_index, scalars_pandas_df_index): - col_name = ["int64_col", "bool_col"] - bf_result = scalars_df_index.set_index(col_name).index == (2, False) - pd_result = scalars_pandas_df_index.set_index(col_name).index == (2, False) - - bigframes.testing.utils.assert_index_equal( - pandas.Index(pd_result, dtype="boolean"), bf_result.to_pandas() - ) - - -def test_count_empty_multiindex_columns(session): - df = pandas.DataFrame( - [], index=[1, 2], columns=pandas.MultiIndex.from_tuples([], names=["a", "b"]) - ) - bdf = session.read_pandas(df) - - # count() operation unpivots columns, triggering the empty MultiIndex bug internally - count_df = bdf.count() - - # The local fix ensures that empty unpivoted columns generate properly typed NULLs - # rather than failing syntax validation downstream in BigQuery. - # We compile to `.sql` to verify it succeeds locally without evaluating on BigQuery natively. - _ = count_df.to_frame().sql - - # Assert structural layout is correct - assert count_df.index.nlevels == 2 - assert list(count_df.index.names) == ["a", "b"] - - -def test_dataframe_melt_multiindex(session): - # Tests that `melt` operations via count do not cause MultiIndex drops in Arrow - df = pandas.DataFrame({"A": [1], "B": ["string"], "C": [3]}) - df.columns = pandas.MultiIndex.from_tuples( - [("Group1", "A"), ("Group2", "B"), ("Group1", "C")] - ) - bdf = session.read_pandas(df) - - count_df = bdf.count().to_pandas() - assert count_df.shape[0] == 3 diff --git a/tests/system/small/test_null_index.py b/tests/system/small/test_null_index.py deleted file mode 100644 index eb9dc114dde..00000000000 --- a/tests/system/small/test_null_index.py +++ /dev/null @@ -1,435 +0,0 @@ -# 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. -# 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 io - -import pandas as pd -import pytest - -import bigframes.exceptions -import bigframes.pandas as bpd - - -def test_null_index_to_gbq(session, scalars_df_null_index, dataset_id_not_created): - dataset_id = dataset_id_not_created - destination_table = f"{dataset_id}.scalars_df_unindexed" - - result_table = scalars_df_null_index.to_gbq( - destination_table, clustering_columns=["int64_col"] - ) - assert ( - result_table == destination_table - if destination_table - else result_table is not None - ) - - loaded_scalars_df_index = session.read_gbq(result_table) - assert not loaded_scalars_df_index.empty - - -def test_null_index_materialize(scalars_df_null_index, scalars_pandas_df_default_index): - bf_result = scalars_df_null_index.to_pandas() - pd.testing.assert_frame_equal( - bf_result, scalars_pandas_df_default_index, check_index_type=False - ) - - -def test_null_index_info(scalars_df_null_index): - expected = ( - "\n" - "NullIndex\n" - "Data columns (total 14 columns):\n" - " # Column Non-Null Count Dtype\n" - "--- ------------- ---------------- ------------------------------\n" - " 0 bool_col 8 non-null boolean\n" - " 1 bytes_col 6 non-null binary[pyarrow]\n" - " 2 date_col 7 non-null date32[day][pyarrow]\n" - " 3 datetime_col 6 non-null timestamp[us][pyarrow]\n" - " 4 geography_col 4 non-null geometry\n" - " 5 int64_col 8 non-null Int64\n" - " 6 int64_too 9 non-null Int64\n" - " 7 numeric_col 6 non-null decimal128(38, 9)[pyarrow]\n" - " 8 float64_col 7 non-null Float64\n" - " 9 rowindex_2 9 non-null Int64\n" - " 10 string_col 8 non-null string\n" - " 11 time_col 6 non-null time64[us][pyarrow]\n" - " 12 timestamp_col 6 non-null timestamp[us, tz=UTC][pyarrow]\n" - " 13 duration_col 7 non-null duration[us][pyarrow]\n" - "dtypes: Float64(1), Int64(3), binary[pyarrow](1), boolean(1), date32[day][pyarrow](1), decimal128(38, 9)[pyarrow](1), duration[us][pyarrow](1), geometry(1), string(1), time64[us][pyarrow](1), timestamp[us, tz=UTC][pyarrow](1), timestamp[us][pyarrow](1)\n" - "memory usage: 1269 bytes\n" - ) - - bf_result = io.StringIO() - - scalars_df_null_index.drop(columns="rowindex").info(buf=bf_result) - - assert expected == bf_result.getvalue() - - -def test_null_index_series_repr(scalars_df_null_index, scalars_pandas_df_default_index): - bf_result = scalars_df_null_index["int64_too"].head(5).__repr__() - pd_result = ( - scalars_pandas_df_default_index["int64_too"] - .head(5) - .to_string(dtype=True, index=False, length=False, name=True) - ) - assert bf_result == pd_result - - -def test_null_index_dataframe_repr( - scalars_df_null_index, scalars_pandas_df_default_index -): - bf_result = scalars_df_null_index[["int64_too", "int64_col"]].head(5).__repr__() - pd_result = ( - scalars_pandas_df_default_index[["int64_too", "int64_col"]] - .head(5) - .to_string(index=False) - ) - assert bf_result == pd_result + "\n\n[5 rows x 2 columns]" - - -def test_null_index_reset_index(scalars_df_null_index, scalars_pandas_df_default_index): - bf_result = scalars_df_null_index.reset_index().to_pandas() - pd_result = scalars_pandas_df_default_index.reset_index(drop=True) - pd.testing.assert_frame_equal(bf_result, pd_result, check_index_type=False) - - -def test_null_index_set_index(scalars_df_null_index, scalars_pandas_df_default_index): - bf_result = scalars_df_null_index.set_index("int64_col").to_pandas() - pd_result = scalars_pandas_df_default_index.set_index("int64_col") - pd.testing.assert_frame_equal(bf_result, pd_result) - - -def test_null_index_concat(scalars_df_null_index, scalars_pandas_df_default_index): - bf_result = bpd.concat( - [scalars_df_null_index, scalars_df_null_index], axis=0 - ).to_pandas() - pd_result = pd.concat( - [scalars_pandas_df_default_index, scalars_pandas_df_default_index], axis=0 - ) - pd.testing.assert_frame_equal(bf_result, pd_result.reset_index(drop=True)) - - -def test_null_index_aggregate(scalars_df_null_index, scalars_pandas_df_default_index): - bf_result = scalars_df_null_index.count().to_pandas() - pd_result = scalars_pandas_df_default_index.count() - - pd_result.index = pd_result.index.astype("string[pyarrow]") - - pd.testing.assert_series_equal( - bf_result, pd_result, check_dtype=False, check_index_type=False - ) - - -def test_null_index_binop_series_axis_0( - scalars_df_null_index, scalars_pandas_df_default_index -): - bf_result = ( - scalars_df_null_index[["int64_col", "int64_too"]] - .add(scalars_df_null_index["int64_col"], axis=0) - .to_pandas() - ) - pd_result = scalars_pandas_df_default_index[["int64_col", "int64_too"]].add( - scalars_pandas_df_default_index.int64_col, axis=0 - ) - - pd.testing.assert_frame_equal( - bf_result, pd_result, check_dtype=False, check_index_type=False - ) - - -def test_null_index_groupby_aggregate( - scalars_df_null_index, scalars_pandas_df_default_index -): - bf_result = scalars_df_null_index.groupby("int64_col").count().to_pandas() - pd_result = scalars_pandas_df_default_index.groupby("int64_col").count() - - pd.testing.assert_frame_equal(bf_result, pd_result, check_dtype=False) - - -def test_null_index_analytic(scalars_df_null_index, scalars_pandas_df_default_index): - # TODO: supply a reason why this isn't compatible with pandas 1.x - pytest.importorskip("pandas", minversion="2.0.0") - bf_result = scalars_df_null_index["int64_col"].cumsum().to_pandas() - pd_result = scalars_pandas_df_default_index["int64_col"].cumsum() - pd.testing.assert_series_equal( - bf_result, pd_result.reset_index(drop=True), check_dtype=False - ) - - -def test_null_index_groupby_analytic( - scalars_df_null_index, scalars_pandas_df_default_index -): - bf_result = ( - scalars_df_null_index.groupby("bool_col")["int64_col"].cummax().to_pandas() - ) - pd_result = scalars_pandas_df_default_index.groupby("bool_col")[ - "int64_col" - ].cummax() - pd.testing.assert_series_equal( - bf_result, pd_result.reset_index(drop=True), check_dtype=False - ) - - -def test_null_index_merge_left_null_index_object( - scalars_df_null_index, scalars_df_default_index, scalars_pandas_df_default_index -): - df1 = scalars_df_null_index[scalars_df_null_index["int64_col"] > 0] - df1_pd = scalars_pandas_df_default_index[ - scalars_pandas_df_default_index["int64_col"] > 0 - ] - assert not df1._has_index - df2 = scalars_df_default_index[scalars_df_default_index["int64_col"] <= 55555] - df2_pd = scalars_pandas_df_default_index[ - scalars_pandas_df_default_index["int64_col"] <= 55555 - ] - assert df2._has_index - - got = df1.merge(df2, how="inner", on="bool_col") - expected = df1_pd.merge(df2_pd, how="inner", on="bool_col") - - # Combining any NULL index object should result in a NULL index. - # This keeps us from generating an index if the user joins a large - # BigQuery table against small local data, for example. - assert not got._has_index - assert got.shape == expected.shape - - -@pytest.mark.parametrize( - ("expr",), - [ - ("new_col = int64_col + int64_too",), - ("new_col = (rowindex > 3) | bool_col",), - ("int64_too = bool_col\nnew_col2 = rowindex",), - ], -) -def test_null_index_df_eval( - scalars_df_null_index, scalars_pandas_df_default_index, expr -): - # TODO: supply a reason why this isn't compatible with pandas 1.x - pytest.importorskip("pandas", minversion="2.0.0") - - bf_result = scalars_df_null_index.eval(expr).to_pandas() - pd_result = scalars_pandas_df_default_index.eval(expr) - - pd.testing.assert_frame_equal(bf_result, pd_result, check_index_type=False) - - -def test_null_index_merge_right_null_index_object( - scalars_df_null_index, scalars_df_default_index, scalars_pandas_df_default_index -): - df1 = scalars_df_default_index[scalars_df_default_index["int64_col"] > 0] - df1_pd = scalars_pandas_df_default_index[ - scalars_pandas_df_default_index["int64_col"] > 0 - ] - assert df1._has_index - df2 = scalars_df_null_index[scalars_df_null_index["int64_col"] <= 55555] - df2_pd = scalars_pandas_df_default_index[ - scalars_pandas_df_default_index["int64_col"] <= 55555 - ] - assert not df2._has_index - - got = df1.merge(df2, how="left", on="bool_col") - expected = df1_pd.merge(df2_pd, how="left", on="bool_col") - - # Combining any NULL index object should result in a NULL index. - # This keeps us from generating an index if the user joins a large - # BigQuery table against small local data, for example. - assert not got._has_index - assert got.shape == expected.shape - - -def test_null_index_merge_two_null_index_objects( - scalars_df_null_index, scalars_pandas_df_default_index -): - df1 = scalars_df_null_index[scalars_df_null_index["int64_col"] > 0] - df1_pd = scalars_pandas_df_default_index[ - scalars_pandas_df_default_index["int64_col"] > 0 - ] - assert not df1._has_index - df2 = scalars_df_null_index[scalars_df_null_index["int64_col"] <= 55555] - df2_pd = scalars_pandas_df_default_index[ - scalars_pandas_df_default_index["int64_col"] <= 55555 - ] - assert not df2._has_index - - got = df1.merge(df2, how="outer", on="bool_col") - expected = df1_pd.merge(df2_pd, how="outer", on="bool_col") - - assert not got._has_index - assert got.shape == expected.shape - - -def test_null_index_stack(scalars_df_null_index, scalars_pandas_df_default_index): - # TODO: supply a reason why this isn't compatible with pandas 1.x - pytest.importorskip("pandas", minversion="2.0.0") - stacking_cols = ["int64_col", "int64_too"] - bf_result = scalars_df_null_index[stacking_cols].stack().to_pandas() - pd_result = ( - scalars_pandas_df_default_index[stacking_cols] - .stack(future_stack=True) - .droplevel(level=0, axis=0) - ) - pd_result.index = pd_result.index.astype(bf_result.index.dtype) - - pd.testing.assert_series_equal( - bf_result, - pd_result, - check_dtype=False, - ) - - -def test_null_index_series_self_join( - scalars_df_null_index, scalars_pandas_df_default_index -): - bf_result = scalars_df_null_index[["int64_col"]].join( - scalars_df_null_index[["int64_too"]] - ) - pd_result = scalars_pandas_df_default_index[["int64_col"]].join( - scalars_pandas_df_default_index[["int64_too"]] - ) - pd.testing.assert_frame_equal( - bf_result.to_pandas(), pd_result.reset_index(drop=True), check_dtype=False - ) - - -def test_null_index_series_self_join_on( - scalars_df_null_index, scalars_pandas_df_default_index -): - # caller doesn't need index, but do need index on arg to join with 'on' - bf_result = scalars_df_null_index[["int64_col", "string_col"]].join( - scalars_df_null_index[["int64_too", "bool_col"]].set_index("int64_too"), - on="int64_col", - ) - pd_result = scalars_pandas_df_default_index[["int64_col", "string_col"]].join( - scalars_pandas_df_default_index[["int64_too", "bool_col"]].set_index( - "int64_too" - ), - on="int64_col", - ) - pd.testing.assert_frame_equal( - bf_result.to_pandas(), pd_result.reset_index(drop=True), check_dtype=False - ) - - -def test_null_index_series_self_aligns( - scalars_df_null_index, scalars_pandas_df_default_index -): - bf_result = scalars_df_null_index["int64_col"] + scalars_df_null_index["int64_too"] - pd_result = ( - scalars_pandas_df_default_index["int64_col"] - + scalars_pandas_df_default_index["int64_too"] - ) - pd.testing.assert_series_equal( - bf_result.to_pandas(), pd_result.reset_index(drop=True), check_dtype=False - ) - - -def test_null_index_df_self_aligns( - scalars_df_null_index, scalars_pandas_df_default_index -): - bf_result = ( - scalars_df_null_index[["int64_col", "float64_col"]] - + scalars_df_null_index[["int64_col", "float64_col"]] - ) - pd_result = ( - scalars_pandas_df_default_index[["int64_col", "float64_col"]] - + scalars_pandas_df_default_index[["int64_col", "float64_col"]] - ) - pd.testing.assert_frame_equal( - bf_result.to_pandas(), pd_result.reset_index(drop=True), check_dtype=False - ) - - -def test_null_index_setitem(scalars_df_null_index, scalars_pandas_df_default_index): - bf_result = scalars_df_null_index.copy() - bf_result["new_col"] = ( - scalars_df_null_index["int64_col"] + scalars_df_null_index["float64_col"] - ) - pd_result = scalars_pandas_df_default_index.copy() - pd_result["new_col"] = ( - scalars_pandas_df_default_index["int64_col"] - + scalars_pandas_df_default_index["float64_col"] - ) - pd.testing.assert_frame_equal( - bf_result.to_pandas(), pd_result.reset_index(drop=True), check_dtype=False - ) - - -def test_null_index_df_concat(scalars_df_null_index, scalars_pandas_df_default_index): - bf_result = bpd.concat([scalars_df_null_index, scalars_df_null_index]) - pd_result = pd.concat( - [scalars_pandas_df_default_index, scalars_pandas_df_default_index] - ) - pd.testing.assert_frame_equal( - bf_result.to_pandas(), pd_result.reset_index(drop=True), check_dtype=False - ) - - -def test_null_index_map_dict_input( - scalars_df_null_index, scalars_pandas_df_default_index -): - local_map = dict() - # construct a local map, incomplete to cover behavior - for s in scalars_pandas_df_default_index.string_col[:-3]: - if isinstance(s, str): - local_map[s] = ord(s[0]) - - pd_result = scalars_pandas_df_default_index.string_col.map(local_map) - pd_result = pd_result.astype("Int64") # pandas type differences - bf_result = scalars_df_null_index.string_col.map(local_map) - - pd.testing.assert_series_equal( - bf_result.to_pandas(), pd_result.reset_index(drop=True), check_dtype=False - ) - - -def test_null_index_align_error(scalars_df_null_index): - with pytest.raises(bigframes.exceptions.NullIndexError): - _ = ( - scalars_df_null_index["int64_col"] - + scalars_df_null_index["int64_col"].cumsum()[ - scalars_df_null_index["int64_col"] > 3 - ] - ) - - -def test_null_index_loc_error(scalars_df_null_index): - with pytest.raises(bigframes.exceptions.NullIndexError): - scalars_df_null_index["int64_col"].loc[1] - - -def test_null_index_at_error(scalars_df_null_index): - with pytest.raises(bigframes.exceptions.NullIndexError): - scalars_df_null_index["int64_col"].at[1] - - -def test_null_index_idxmin_error(scalars_df_null_index): - with pytest.raises(bigframes.exceptions.NullIndexError): - scalars_df_null_index[["int64_col", "int64_too"]].idxmin() - - -def test_null_index_index_property(scalars_df_null_index): - with pytest.raises(bigframes.exceptions.NullIndexError): - _ = scalars_df_null_index.index - - -def test_null_index_transpose(scalars_df_null_index): - with pytest.raises(bigframes.exceptions.NullIndexError): - _ = scalars_df_null_index.T - - -def test_null_index_contains(scalars_df_null_index): - assert 3 not in scalars_df_null_index diff --git a/tests/system/small/test_numpy.py b/tests/system/small/test_numpy.py index 774f72bef4a..5c2a93ec394 100644 --- a/tests/system/small/test_numpy.py +++ b/tests/system/small/test_numpy.py @@ -16,8 +16,6 @@ import pandas as pd import pytest -import bigframes.testing.utils - @pytest.mark.parametrize( ("opname",), @@ -39,17 +37,13 @@ ("log10",), ("sqrt",), ("abs",), - ("isnan",), - ("isfinite",), ], ) def test_series_ufuncs(floats_pd, floats_bf, opname): bf_result = getattr(np, opname)(floats_bf).to_pandas() pd_result = getattr(np, opname)(floats_pd) - bigframes.testing.utils.assert_series_equal( - bf_result, pd_result, nulls_are_nan=True - ) + pd.testing.assert_series_equal(bf_result, pd_result) @pytest.mark.parametrize( @@ -62,10 +56,6 @@ def test_series_ufuncs(floats_pd, floats_bf, opname): ("log10",), ("sqrt",), ("abs",), - ("floor",), - ("ceil",), - ("expm1",), - ("log1p",), ], ) def test_df_ufuncs(scalars_dfs, opname): @@ -76,14 +66,7 @@ def test_df_ufuncs(scalars_dfs, opname): ).to_pandas() pd_result = getattr(np, opname)(scalars_pandas_df[["float64_col", "int64_col"]]) - # In NumPy versions 2 and later, `np.floor` and `np.ceil` now produce integer - # outputs for the "int64_col" column. - if opname in ["floor", "ceil"] and isinstance( - pd_result["int64_col"].dtypes, pd.Int64Dtype - ): - pd_result["int64_col"] = pd_result["int64_col"].astype(pd.Float64Dtype()) - - bigframes.testing.utils.assert_frame_equal(bf_result, pd_result, nulls_are_nan=True) + pd.testing.assert_frame_equal(bf_result, pd_result) @pytest.mark.parametrize( @@ -96,25 +79,16 @@ def test_df_ufuncs(scalars_dfs, opname): ("power",), ], ) -def test_df_binary_ufuncs(scalars_dfs, opname): - scalars_df, scalars_pandas_df = scalars_dfs - op = getattr(np, opname) - - bf_result = op(scalars_df[["float64_col", "int64_col"]], 5.1).to_pandas() - pd_result = op(scalars_pandas_df[["float64_col", "int64_col"]], 5.1) - - bigframes.testing.utils.assert_frame_equal(bf_result, pd_result, nulls_are_nan=True) +def test_series_binary_ufuncs(floats_product_pd, floats_product_bf, opname): + bf_result = getattr(np, opname)( + floats_product_bf.float64_col_x, floats_product_bf.float64_col_y + ).to_pandas() + pd_result = getattr(np, opname)( + floats_product_pd.float64_col_x, floats_product_pd.float64_col_y + ) + pd.testing.assert_series_equal(bf_result, pd_result) -# Operations tested here don't work on full dataframe in numpy+pandas -# Maybe because of nullable dtypes? -@pytest.mark.parametrize( - ("x", "y"), - [ - ("int64_col", "int64_col"), - ("float64_col", "int64_col"), - ], -) @pytest.mark.parametrize( ("opname",), [ @@ -122,23 +96,21 @@ def test_df_binary_ufuncs(scalars_dfs, opname): ("subtract",), ("multiply",), ("divide",), - ("arctan2",), - ("minimum",), - ("maximum",), + ("power",), ], ) -def test_series_binary_ufuncs(scalars_dfs, x, y, opname): +def test_df_binary_ufuncs(scalars_dfs, opname): scalars_df, scalars_pandas_df = scalars_dfs - op = getattr(np, opname) - - bf_result = op(scalars_df[x], scalars_df[y]).to_pandas() - pd_result = op(scalars_pandas_df[x], scalars_pandas_df[y]) - - bigframes.testing.utils.assert_series_equal( - bf_result, pd_result, nulls_are_nan=True + bf_result = getattr(np, opname)( + scalars_df[["float64_col", "int64_col"]], 5.1 + ).to_pandas() + pd_result = getattr(np, opname)( + scalars_pandas_df[["float64_col", "int64_col"]], 5.1 ) + pd.testing.assert_frame_equal(bf_result, pd_result) + def test_series_binary_ufuncs_reverse(scalars_dfs): scalars_df, scalars_pandas_df = scalars_dfs @@ -147,9 +119,7 @@ def test_series_binary_ufuncs_reverse(scalars_dfs): bf_result = np.subtract(5.1, scalars_df["int64_col"]).to_pandas() pd_result = np.subtract(5.1, scalars_pandas_df["int64_col"]) - bigframes.testing.utils.assert_series_equal( - bf_result, pd_result, nulls_are_nan=True - ) + pd.testing.assert_series_equal(bf_result, pd_result) def test_df_binary_ufuncs_reverse(scalars_dfs): @@ -162,4 +132,4 @@ def test_df_binary_ufuncs_reverse(scalars_dfs): scalars_pandas_df[["float64_col", "int64_col"]], ) - bigframes.testing.utils.assert_frame_equal(bf_result, pd_result, nulls_are_nan=True) + pd.testing.assert_frame_equal(bf_result, pd_result) diff --git a/tests/system/small/test_pandas.py b/tests/system/small/test_pandas.py index 356e498021b..0292ebd2069 100644 --- a/tests/system/small/test_pandas.py +++ b/tests/system/small/test_pandas.py @@ -12,123 +12,22 @@ # See the License for the specific language governing permissions and # limitations under the License. -import typing -from datetime import datetime - import pandas as pd -import pyarrow as pa import pytest -import pytz import bigframes.pandas as bpd -import bigframes.testing -from bigframes.testing.utils import assert_frame_equal, assert_series_equal +from tests.system.utils import assert_pandas_df_equal_ignore_ordering -@pytest.mark.parametrize( - ("ordered"), - [ - (True), - (False), - ], -) -def test_concat_dataframe(scalars_dfs, ordered): +def test_concat_dataframe(scalars_dfs): scalars_df, scalars_pandas_df = scalars_dfs bf_result = bpd.concat(11 * [scalars_df]) - bf_result = bf_result.to_pandas(ordered=ordered) + bf_result = bf_result.to_pandas() pd_result = pd.concat(11 * [scalars_pandas_df]) - assert_frame_equal(bf_result, pd_result, ignore_order=not ordered) - - -def test_concat_dataframe_w_struct_cols(nested_structs_df, nested_structs_pandas_df): - """Avoid regressions for internal issue 407107482""" - empty_bf_df = bpd.DataFrame(session=nested_structs_df._block.session) - bf_result = bpd.concat((empty_bf_df, nested_structs_df), ignore_index=True) - bf_result = bf_result.to_pandas() - pd_result = pd.concat((pd.DataFrame(), nested_structs_pandas_df), ignore_index=True) - pd_result.index = pd_result.index.astype("Int64") pd.testing.assert_frame_equal(bf_result, pd_result) -def test_nested_structs_dtypes_and_edge_cases(nested_structs_df): - """Explicitly verify dtypes and edge case values for all supported types.""" - import datetime as dt - import decimal - - import numpy as np - import pandas as pd - - import bigframes.dtypes as bfd - - # 1. Verify BigFrames dtypes - expected_bf_dtypes = { - "person": nested_structs_df["person"].dtype, - "bool_col": bfd.BOOL_DTYPE, - "int64_col": bfd.INT_DTYPE, - "float64_col": bfd.FLOAT_DTYPE, - "string_col": bfd.STRING_DTYPE, - "json_col": bfd.JSON_DTYPE, - "date_col": bfd.DATE_DTYPE, - "time_col": bfd.TIME_DTYPE, - "datetime_col": bfd.DATETIME_DTYPE, - "timestamp_col": bfd.TIMESTAMP_DTYPE, - "bytes_col": bfd.BYTES_DTYPE, - "numeric_col": bfd.NUMERIC_DTYPE, - "bignumeric_col": bfd.BIGNUMERIC_DTYPE, - "geography_col": bfd.GEO_DTYPE, - "duration_col": bfd.TIMEDELTA_DTYPE, - } - - for col_name, expected_dtype in expected_bf_dtypes.items(): - assert nested_structs_df[col_name].dtype == expected_dtype, ( - f"Dtype mismatch for {col_name}" - ) - - # 2. Convert to pandas for value assertions - pd_df = nested_structs_df.to_pandas() - - # Verify we have 6 rows - assert len(pd_df) == 6 - - # Row 1: Normal typical values - assert pd_df.loc[1, "bool_col"] == True - assert pd_df.loc[1, "int64_col"] == 123456789 - assert pd_df.loc[1, "float64_col"] == 1.25 - assert pd_df.loc[1, "string_col"] == "Hello World" - assert pd_df.loc[1, "json_col"] == '{"a":1,"b":[1,2]}' - assert pd_df.loc[1, "date_col"] == dt.date(2026, 6, 24) - - # Row 2: Min bounds / negative infinity - assert pd_df.loc[2, "int64_col"] == -9223372036854775808 - assert pd_df.loc[2, "float64_col"] == float("-inf") - assert pd_df.loc[2, "numeric_col"] == decimal.Decimal( - "-99999999999999999999999999999.999999999" - ) - - # Row 3: Max bounds / infinity - assert pd_df.loc[3, "int64_col"] == 9223372036854775807 - assert pd_df.loc[3, "float64_col"] == float("inf") - - # Row 4: SQL NULLs (omitted keys) - assert pd.isna(pd_df.loc[4, "bool_col"]) - assert pd.isna(pd_df.loc[4, "int64_col"]) - assert pd.isna(pd_df.loc[4, "float64_col"]) - assert pd.isna(pd_df.loc[4, "json_col"]) - assert pd.isna(pd_df.loc[4, "geography_col"]) - - # Row 5: Special edge cases (NaN, empty, multiline) - assert np.isnan(pd_df.loc[5, "float64_col"]) - assert pd_df.loc[5, "float64_col"] is not pd.NA - assert not pd_df["float64_col"].isna().loc[5] - assert pd_df.loc[5, "string_col"] == 'Line 1\nLine 2\n"Quotes"' - assert pd_df.loc[5, "bytes_col"] == b"\x00" - - # Row 6: JSON null literal - assert pd_df.loc[6, "json_col"] == "null" - assert not pd_df["json_col"].isna().loc[6] - - def test_concat_series(scalars_dfs): scalars_df, scalars_pandas_df = scalars_dfs bf_result = bpd.concat( @@ -143,7 +42,7 @@ def test_concat_series(scalars_dfs): ] ) - bigframes.testing.utils.assert_series_equal(bf_result, pd_result) + pd.testing.assert_series_equal(bf_result, pd_result) @pytest.mark.parametrize( @@ -180,7 +79,7 @@ def test_get_dummies_dataframe(scalars_dfs, kwargs): # dtype argument above is needed for pandas v1 only # adjust for expected dtype differences - for column_name, type_name in zip(pd_result.columns, pd_result.dtypes): + for (column_name, type_name) in zip(pd_result.columns, pd_result.dtypes): if type_name == "bool": pd_result[column_name] = pd_result[column_name].astype("boolean") @@ -194,30 +93,22 @@ def test_get_dummies_dataframe_duplicate_labels(scalars_dfs): scalars_df, scalars_pandas_df = scalars_dfs scalars_renamed_df = scalars_df.rename( - columns={ - "int64_too": "int64_col", - "float64_col": "dup_col", - "string_col": "dup_col", - } + columns={"int64_too": "int64_col", "float64_col": None, "string_col": None} ) scalars_renamed_pandas_df = scalars_pandas_df.rename( - columns={ - "int64_too": "int64_col", - "float64_col": "dup_col", - "string_col": "dup_col", - } + columns={"int64_too": "int64_col", "float64_col": None, "string_col": None} ) bf_result = bpd.get_dummies( - scalars_renamed_df, columns=["int64_col", "dup_col"], dtype=bool + scalars_renamed_df, columns=["int64_col", None], dtype=bool ) pd_result = pd.get_dummies( - scalars_renamed_pandas_df, columns=["int64_col", "dup_col"], dtype=bool + scalars_renamed_pandas_df, columns=["int64_col", None], dtype=bool ) # dtype argument above is needed for pandas v1 only # adjust for expected dtype differences - for column_name, type_name in zip(pd_result.columns, pd_result.dtypes): + for (column_name, type_name) in zip(pd_result.columns, pd_result.dtypes): if type_name == "bool": pd_result[column_name] = pd_result[column_name].astype("boolean") @@ -234,8 +125,8 @@ def test_get_dummies_series(scalars_dfs): # dtype argument above is needed for pandas v1 only # adjust for expected dtype differences - for column_name, type_name in zip(pd_result.columns, pd_result.dtypes): - if type_name == "bool": # pragma: NO COVER + for (column_name, type_name) in zip(pd_result.columns, pd_result.dtypes): + if type_name == "bool": pd_result[column_name] = pd_result[column_name].astype("boolean") pd_result.columns = pd_result.columns.astype(object) @@ -255,8 +146,8 @@ def test_get_dummies_series_nameless(scalars_dfs): # dtype argument above is needed for pandas v1 only # adjust for expected dtype differences - for column_name, type_name in zip(pd_result.columns, pd_result.dtypes): - if type_name == "bool": # pragma: NO COVER + for (column_name, type_name) in zip(pd_result.columns, pd_result.dtypes): + if type_name == "bool": pd_result[column_name] = pd_result[column_name].astype("boolean") pd_result.columns = pd_result.columns.astype(object) @@ -287,38 +178,6 @@ def test_concat_dataframe_mismatched_columns(scalars_dfs, how): pd.testing.assert_frame_equal(bf_result, pd_result) -def test_concat_dataframe_upcasting(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - - bf_input1 = scalars_df[["int64_col", "float64_col", "int64_too"]].set_index( - "int64_col", drop=True - ) - bf_input1.columns = ["a", "b"] - bf_input2 = scalars_df[["int64_too", "int64_col", "float64_col"]].set_index( - "float64_col", drop=True - ) - bf_input2.columns = ["a", "b"] - bf_result = bpd.concat([bf_input1, bf_input2], join="outer") - bf_result = bf_result.to_pandas() - - bf_input1 = ( - scalars_pandas_df[["int64_col", "float64_col", "int64_too"]] - .set_index("int64_col", drop=True) - .set_axis(["a", "b"], axis=1) - ) - bf_input2 = ( - scalars_pandas_df[["int64_too", "int64_col", "float64_col"]] - .set_index("float64_col", drop=True) - .set_axis(["a", "b"], axis=1) - ) - pd_result = pd.concat( - [bf_input1, bf_input2], - join="outer", - ) - - pd.testing.assert_frame_equal(bf_result, pd_result) - - @pytest.mark.parametrize( ("how",), [ @@ -393,7 +252,7 @@ def test_merge(scalars_dfs, merge_how): sort=True, ) - assert_frame_equal(bf_result, pd_result, ignore_order=True) + assert_pandas_df_equal_ignore_ordering(bf_result, pd_result) @pytest.mark.parametrize( @@ -427,28 +286,7 @@ def test_merge_left_on_right_on(scalars_dfs, merge_how): sort=True, ) - assert_frame_equal(bf_result, pd_result, ignore_order=True) - - -def test_merge_cross(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - left_columns = ["int64_col", "float64_col", "int64_too"] - right_columns = ["int64_col", "bool_col", "string_col", "rowindex_2"] - - left = scalars_df[left_columns] - right = scalars_df[right_columns] - - df = bpd.merge(left, right, "cross", sort=True) - bf_result = df.to_pandas() - - pd_result = pd.merge( - scalars_pandas_df[left_columns], - scalars_pandas_df[right_columns], - "cross", - sort=True, - ) - - pd.testing.assert_frame_equal(bf_result, pd_result, check_index_type=False) + assert_pandas_df_equal_ignore_ordering(bf_result, pd_result) @pytest.mark.parametrize( @@ -482,365 +320,21 @@ def test_merge_series(scalars_dfs, merge_how): sort=True, ) - assert_frame_equal(bf_result, pd_result, ignore_order=True) - - -def test_merge_w_common_columns(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - left_columns = ["int64_col", "int64_too"] - right_columns = ["int64_col", "bool_col"] - - df = bpd.merge( - scalars_df[left_columns], scalars_df[right_columns], "inner", sort=True - ) - - pd_result = pd.merge( - scalars_pandas_df[left_columns], - scalars_pandas_df[right_columns], - "inner", - sort=True, - ) - assert_frame_equal(df.to_pandas(), pd_result, ignore_order=True) - - -def test_merge_raises_error_when_no_common_columns(scalars_dfs): - scalars_df, _ = scalars_dfs - left_columns = ["float64_col", "int64_too"] - right_columns = ["int64_col", "bool_col"] - - left = scalars_df[left_columns] - right = scalars_df[right_columns] - - with pytest.raises( - ValueError, - match="No common columns to perform merge on.", - ): - bpd.merge(left, right, "inner") - - -def test_merge_raises_error_when_left_right_on_set(scalars_dfs): - scalars_df, _ = scalars_dfs - left_columns = ["int64_col", "int64_too"] - right_columns = ["int64_col", "bool_col"] - - left = scalars_df[left_columns] - right = scalars_df[right_columns] - - with pytest.raises(ValueError): - bpd.merge( - left, - right, - "inner", - left_on="int64_too", - right_on="int64_col", - on="int64_col", - ) - - -def test_crosstab_aligned_series(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - - pd_result = pd.crosstab( - scalars_pandas_df["int64_col"], scalars_pandas_df["int64_too"] - ) - bf_result = bpd.crosstab( - scalars_df["int64_col"], scalars_df["int64_too"] - ).to_pandas() - - assert_frame_equal(bf_result, pd_result, check_dtype=False) - - -def test_crosstab_nondefault_func(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - - pd_result = pd.crosstab( - scalars_pandas_df["int64_col"], - scalars_pandas_df["int64_too"], - values=scalars_pandas_df["float64_col"], - aggfunc="mean", - ) - bf_result = bpd.crosstab( - scalars_df["int64_col"], - scalars_df["int64_too"], - values=scalars_df["float64_col"], - aggfunc="mean", - ).to_pandas() - - assert_frame_equal(bf_result, pd_result, check_dtype=False) - - -def test_crosstab_multi_cols(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - - pd_result = pd.crosstab( - [scalars_pandas_df["int64_col"], scalars_pandas_df["bool_col"]], - [scalars_pandas_df["int64_too"], scalars_pandas_df["string_col"]], - rownames=["a", "b"], - colnames=["c", "d"], - ) - bf_result = bpd.crosstab( - [scalars_df["int64_col"], scalars_df["bool_col"]], - [scalars_df["int64_too"], scalars_df["string_col"]], - rownames=["a", "b"], - colnames=["c", "d"], - ).to_pandas() - - assert_frame_equal(bf_result, pd_result, check_dtype=False) - - -def test_crosstab_unaligned_series(scalars_dfs, session): - scalars_df, scalars_pandas_df = scalars_dfs - other_pd_series = pd.Series( - [10, 20, 10, 30, 10], index=[5, 4, 1, 2, 3], dtype="Int64", name="nums" - ) - other_bf_series = session.Series( - [10, 20, 10, 30, 10], index=[5, 4, 1, 2, 3], name="nums" - ) - - pd_result = pd.crosstab(scalars_pandas_df["int64_col"], other_pd_series) - bf_result = bpd.crosstab(scalars_df["int64_col"], other_bf_series).to_pandas() - - assert_frame_equal(bf_result, pd_result, check_dtype=False) - - -def _convert_pandas_category(pd_s: pd.Series): - """ - Transforms a pandas Series with Categorical dtype into a bigframes-compatible - Series representing intervals." - """ - # When `labels=False` - if pd.api.types.is_integer_dtype(pd_s.dtype) or pd.api.types.is_float_dtype( - pd_s.dtype - ): - return pd_s.astype("Int64") - - if not isinstance(pd_s.dtype, pd.CategoricalDtype): - raise ValueError( - f"Input must be a pandas Series with categorical data: {pd_s.dtype}" - ) - - if pd.api.types.is_object_dtype( - pd_s.cat.categories.dtype - ) or pd.api.types.is_string_dtype(pd_s.cat.categories.dtype): - return pd_s.astype(pd.StringDtype(storage="pyarrow")) - - if not isinstance(pd_s.cat.categories.dtype, pd.IntervalDtype): - raise ValueError( - f"Must be a IntervalDtype with categorical data: {pd_s.cat.categories.dtype}" - ) - - if pd_s.cat.categories.dtype.closed == "left": # type: ignore - left_key = "left_inclusive" - right_key = "right_exclusive" - else: - left_key = "left_exclusive" - right_key = "right_inclusive" - - subtype = pd_s.cat.categories.dtype.subtype # type: ignore - if pd.api.types.is_float_dtype(subtype): # type: ignore - interval_dtype = pa.float64() - elif pd.api.types.is_integer_dtype(subtype): # type: ignore - interval_dtype = pa.int64() - else: - raise ValueError(f"Unknown category type: {subtype}") - - dtype = pd.ArrowDtype( - pa.struct( - [ - pa.field(left_key, interval_dtype, nullable=True), - pa.field(right_key, interval_dtype, nullable=True), - ] - ) - ) - - if len(pd_s.dtype.categories) == 0: - data = [pd.NA] * len(pd_s) - else: - data = [ - {left_key: interval.left, right_key: interval.right} # type: ignore - if pd.notna(val) - else pd.NA - for val, interval in zip(pd_s, pd_s.cat.categories[pd_s.cat.codes]) # type: ignore - ] - - return pd.Series( - data=data, - name=pd_s.name, - dtype=dtype, - index=pd_s.index.astype("Int64"), - ) - - -def test_cut_for_array(): - """Avoid regressions for internal issue 329866195""" - sc = [30, 80, 40, 90, 60, 45, 95, 75, 55, 100, 65, 85] - x = [20, 40, 60, 80, 100] - - pd_result: pd.Series = pd.Series(pd.cut(sc, x)) - bf_result = bpd.cut(sc, x) - - pd_result = _convert_pandas_category(pd_result) - bigframes.testing.utils.assert_series_equal(bf_result.to_pandas(), pd_result) - - -@pytest.mark.parametrize( - ("right", "labels"), - [ - pytest.param(True, None, id="right_w_none_labels"), - pytest.param(True, False, id="right_w_false_labels"), - pytest.param(False, None, id="left_w_none_labels"), - pytest.param(False, False, id="left_w_false_labels"), - ], -) -def test_cut_by_int_bins(scalars_dfs, labels, right): - scalars_df, scalars_pandas_df = scalars_dfs - - pd_result = pd.cut(scalars_pandas_df["float64_col"], 5, labels=labels, right=right) - bf_result = bpd.cut(scalars_df["float64_col"], 5, labels=labels, right=right) - - pd_result = _convert_pandas_category(pd_result) - bigframes.testing.utils.assert_series_equal(bf_result.to_pandas(), pd_result) - - -def test_cut_by_int_bins_w_labels(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - - labels = ["A", "B", "C", "D", "E"] - pd_result = pd.cut(scalars_pandas_df["float64_col"], 5, labels=labels) - bf_result = bpd.cut(scalars_df["float64_col"], 5, labels=labels) - - pd_result = _convert_pandas_category(pd_result) - bigframes.testing.utils.assert_series_equal(bf_result.to_pandas(), pd_result) - - -@pytest.mark.parametrize( - ("breaks", "right", "labels"), - [ - pytest.param( - [0, 5, 10, 15, 20, 100, 1000], - True, - None, - id="int_breaks_w_right_closed_and_none_labels", - ), - pytest.param( - [0, 5, 10, 15, 20, 100, 1000], - False, - False, - id="int_breaks_w_left_closed_and_false_labels", - ), - pytest.param( - [0.5, 10.5, 15.5, 20.5, 100.5, 1000.5], - False, - None, - id="float_breaks_w_left_closed_and_none_labels", - ), - pytest.param( - [0, 5, 10.5, 15.5, 20, 100, 1000.5], - True, - False, - id="mixed_types_breaks_w_right_closed_and_false_labels", - ), - ], -) -def test_cut_by_numeric_breaks(scalars_dfs, breaks, right, labels): - scalars_df, scalars_pandas_df = scalars_dfs - - pd_result = pd.cut( - scalars_pandas_df["float64_col"], breaks, right=right, labels=labels - ) - bf_result = bpd.cut( - scalars_df["float64_col"], breaks, right=right, labels=labels - ).to_pandas() - - pd_result_converted = _convert_pandas_category(pd_result) - bigframes.testing.utils.assert_series_equal(bf_result, pd_result_converted) + assert_pandas_df_equal_ignore_ordering(bf_result, pd_result) -def test_cut_by_numeric_breaks_w_labels(scalars_dfs): +def test_cut(scalars_dfs): scalars_df, scalars_pandas_df = scalars_dfs - bins = [0, 5, 10, 15, 20] - labels = ["A", "B", "C", "D"] - pd_result = pd.cut(scalars_pandas_df["float64_col"], bins, labels=labels) - bf_result = bpd.cut(scalars_df["float64_col"], bins, labels=labels) + pd_result = pd.cut(scalars_pandas_df["float64_col"], 5, labels=False) + bf_result = bpd.cut(scalars_df["float64_col"], 5, labels=False) - pd_result = _convert_pandas_category(pd_result) - bigframes.testing.utils.assert_series_equal(bf_result.to_pandas(), pd_result) + # make sure the result is a supported dtype + assert bf_result.dtype == bpd.Int64Dtype() - -@pytest.mark.parametrize( - ("bins", "right", "labels"), - [ - pytest.param( - [(-5, 2), (2, 3), (-3000, -10)], True, None, id="tuple_right_w_none_labels" - ), - pytest.param( - [(-5, 2), (2, 3), (-3000, -10)], - False, - False, - id="tuple_left_w_false_labels", - ), - pytest.param( - pd.IntervalIndex.from_tuples([(1, 2), (2, 3), (4, 5)]), - True, - False, - id="interval_right_w_none_labels", - ), - pytest.param( - pd.IntervalIndex.from_tuples([(1, 2), (2, 3), (4, 5)]), - False, - None, - id="interval_left_w_false_labels", - ), - ], -) -def test_cut_by_interval_bins(scalars_dfs, bins, right, labels): - scalars_df, scalars_pandas_df = scalars_dfs - bf_result = bpd.cut( - scalars_df["int64_too"], bins, labels=labels, right=right - ).to_pandas() - - if isinstance(bins, list): - bins = pd.IntervalIndex.from_tuples(bins) - pd_result = pd.cut(scalars_pandas_df["int64_too"], bins, labels=labels, right=right) - - pd_result_converted = _convert_pandas_category(pd_result) - bigframes.testing.utils.assert_series_equal(bf_result, pd_result_converted) - - -def test_cut_by_interval_bins_w_labels(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - - bins = pd.IntervalIndex.from_tuples([(1, 2), (2, 3), (4, 5)]) - labels = ["A", "B", "C", "D", "E"] - pd_result = pd.cut(scalars_pandas_df["float64_col"], bins, labels=labels) - bf_result = bpd.cut(scalars_df["float64_col"], bins, labels=labels) - - pd_result = _convert_pandas_category(pd_result) - bigframes.testing.utils.assert_series_equal(bf_result.to_pandas(), pd_result) - - -@pytest.mark.parametrize( - ("bins", "labels"), - [ - pytest.param([], None, id="empty_breaks"), - pytest.param([1], False, id="single_int_breaks"), - pytest.param(pd.IntervalIndex.from_tuples([]), None, id="empty_interval_index"), - ], -) -def test_cut_by_edge_cases_bins(scalars_dfs, bins, labels): - scalars_df, scalars_pandas_df = scalars_dfs - bf_result = bpd.cut(scalars_df["int64_too"], bins, labels=labels).to_pandas() - pd_result = pd.cut(scalars_pandas_df["int64_too"], bins, labels=labels) - - pd_result_converted = _convert_pandas_category(pd_result) - bigframes.testing.utils.assert_series_equal(bf_result, pd_result_converted) - - -def test_cut_empty_array_raises_error(): - bf_df = bpd.Series([]) - with pytest.raises(ValueError, match="Cannot cut empty array"): - bpd.cut(bf_df, bins=5) + bf_result = bf_result.to_pandas() + pd_result = pd_result.astype("Int64") + pd.testing.assert_series_equal(bf_result, pd_result) @pytest.mark.parametrize( @@ -861,339 +355,8 @@ def test_qcut(scalars_dfs, q): scalars_pandas_df["float64_col"], q, labels=False, duplicates="drop" ) bf_result = bpd.qcut(scalars_df["float64_col"], q, labels=False, duplicates="drop") - pd_result = pd_result.astype("Int64") - - bigframes.testing.utils.assert_series_equal(bf_result.to_pandas(), pd_result) - - -@pytest.mark.parametrize( - ("arg", "utc", "unit", "format"), - [ - (173872738, False, None, None), - (32787983.23, True, "s", None), - ("2023-01-01", False, None, "%Y-%m-%d"), - (datetime(2023, 1, 1, 12, 0), False, None, None), - ], -) -def test_to_datetime_scalar(arg, utc, unit, format): - bf_result = bpd.to_datetime(arg, utc=utc, unit=unit, format=format) - pd_result = pd.to_datetime(arg, utc=utc, unit=unit, format=format) - - assert bf_result == pd_result - - -@pytest.mark.parametrize( - ("arg", "utc", "unit", "format"), - [ - ([173872738], False, None, None), - ([32787983.23], True, "s", None), - ( - [datetime(2023, 1, 1, 12, 0, tzinfo=pytz.timezone("America/New_York"))], - True, - None, - None, - ), - (["2023-01-01"], True, None, "%Y-%m-%d"), - (["2023-02-01T15:00:00+07:22"], True, None, None), - (["01-31-2023 14:30 -0800"], True, None, "%m-%d-%Y %H:%M %z"), - (["01-31-2023 14:00", "02-01-2023 15:00"], True, None, "%m-%d-%Y %H:%M"), - ], -) -def test_to_datetime_iterable(arg, utc, unit, format): - bf_result = ( - bpd.to_datetime(arg, utc=utc, unit=unit, format=format) - .to_pandas() - .astype("datetime64[ns, UTC]" if utc else "datetime64[ns]") - ) - pd_result = ( - pd.Series(pd.to_datetime(arg, utc=utc, unit=unit, format=format)) - .dt.floor("us") - .astype("datetime64[ns, UTC]" if utc else "datetime64[ns]") - ) - bigframes.testing.utils.assert_series_equal( - bf_result, pd_result, check_index_type=False, check_names=False - ) - - -def test_to_datetime_series(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - col = "int64_too" - bf_result = ( - bpd.to_datetime(scalars_df[col], unit="s").to_pandas().astype("datetime64[s]") - ) - pd_result = pd.Series(pd.to_datetime(scalars_pandas_df[col], unit="s")).astype( - "datetime64[s]" - ) - bigframes.testing.utils.assert_series_equal( - bf_result, pd_result, check_index_type=False, check_names=False - ) - - -@pytest.mark.parametrize( - ("arg", "unit"), - [ - ([1, 2, 3], "W"), - ([1, 2, 3], "d"), - ([1, 2, 3], "D"), - ([1, 2, 3], "h"), - ([1, 2, 3], "m"), - ([20242330, 25244685, 34324234], "s"), - ([20242330000, 25244685000, 34324234000], "ms"), - ([20242330000000, 25244685000000, 34324234000000], "us"), - ([20242330000000000, 25244685000000000, 34324234000000000], "ns"), - ], -) -def test_to_datetime_unit_param(arg, unit): - bf_result = bpd.to_datetime(arg, unit=unit).to_pandas().astype("datetime64[ns]") - pd_result = ( - pd.Series(pd.to_datetime(arg, unit=unit)) - .dt.floor("us") - .astype("datetime64[ns]") - ) - bigframes.testing.utils.assert_series_equal( - bf_result, pd_result, check_index_type=False, check_names=False - ) + bf_result = bf_result.to_pandas() + pd_result = pd_result.astype("Int64") -@pytest.mark.parametrize( - ("arg", "utc", "format"), - [ - ([20230110, 20230101, 20230101], False, "%Y%m%d"), - ([201301.01], False, "%Y%m.%d"), - (["2023-01-10", "2023-01-20", "2023-01-01"], True, "%Y-%m-%d"), - (["2014-08-15 07:19"], True, "%Y-%m-%d %H:%M"), - ], -) -def test_to_datetime_format_param(arg, utc, format): - bf_result = ( - bpd.to_datetime(arg, utc=utc, format=format) - .to_pandas() - .astype("datetime64[ns, UTC]" if utc else "datetime64[ns]") - ) - pd_result = ( - pd.Series(pd.to_datetime(arg, utc=utc, format=format)) - .dt.floor("us") - .astype("datetime64[ns, UTC]" if utc else "datetime64[ns]") - ) - bigframes.testing.utils.assert_series_equal( - bf_result, pd_result, check_index_type=False, check_names=False - ) - - -@pytest.mark.parametrize( - ("arg", "utc", "output_in_utc", "format"), - [ - ( - ["2014-08-15 08:15:12", "2011-08-15 08:15:12", "2015-08-15 08:15:12"], - False, - False, - None, - ), - ( - [ - "2008-12-25 05:30:00Z", - "2008-12-25 05:30:00-00:00", - "2008-12-25 05:30:00+00:00", - "2008-12-25 05:30:00-0000", - "2008-12-25 05:30:00+0000", - "2008-12-25 05:30:00-00", - "2008-12-25 05:30:00+00", - ], - False, - True, - None, - ), - ( - ["2014-08-15 08:15:12", "2011-08-15 08:15:12", "2015-08-15 08:15:12"], - True, - True, - "%Y-%m-%d %H:%M:%S", - ), - ( - [ - "2014-08-15 08:15:12+05:00", - "2011-08-15 08:15:12+05:00", - "2015-08-15 08:15:12+05:00", - ], - True, - True, - None, - ), - ], -) -def test_to_datetime_string_inputs(arg, utc, output_in_utc, format): - normalized_type = "datetime64[ns, UTC]" if output_in_utc else "datetime64[ns]" - - bf_result = ( - bpd.to_datetime(arg, utc=utc, format=format).to_pandas().astype(normalized_type) - ) - pd_result = ( - pd.Series(pd.to_datetime(arg, utc=utc, format=format)) - .dt.floor("us") - .astype(normalized_type) - ) - - bigframes.testing.utils.assert_series_equal( - bf_result, pd_result, check_index_type=False, check_names=False - ) - - -@pytest.mark.parametrize( - ("arg", "utc", "output_in_utc"), - [ - ( - [datetime(2023, 1, 1, 12, 0), datetime(2023, 2, 1, 12, 0)], - False, - False, - ), - ( - [datetime(2023, 1, 1, 12, 0), datetime(2023, 2, 1, 12, 0)], - True, - True, - ), - ( - [ - datetime(2023, 1, 1, 12, 0, tzinfo=pytz.timezone("UTC")), - datetime(2023, 1, 1, 12, 0, tzinfo=pytz.timezone("UTC")), - ], - True, - True, - ), - ( - [ - datetime(2023, 1, 1, 12, 0, tzinfo=pytz.timezone("America/New_York")), - datetime(2023, 1, 1, 12, 0, tzinfo=pytz.timezone("UTC")), - ], - True, - True, - ), - ], -) -def test_to_datetime_timestamp_inputs(arg, utc, output_in_utc): - normalized_type = "datetime64[ns, UTC]" if output_in_utc else "datetime64[ns]" - - bf_result = bpd.to_datetime(arg, utc=utc).to_pandas().astype(normalized_type) - pd_result = ( - pd.Series(pd.to_datetime(arg, utc=utc)).dt.floor("us").astype(normalized_type) - ) - - bigframes.testing.utils.assert_series_equal( - bf_result, pd_result, check_index_type=False, check_names=False - ) - - -@pytest.mark.parametrize( - "unit", - [ - "W", - "w", - "D", - "d", - "days", - "day", - "hours", - "hour", - "hr", - "h", - "m", - "minute", - "min", - "minutes", - "s", - "seconds", - "sec", - "second", - "ms", - "milliseconds", - "millisecond", - "milli", - "millis", - "us", - "microseconds", - "microsecond", - "µs", - "micro", - "micros", - ], -) -def test_to_timedelta_with_bf_integer_series(session, unit): - bf_series = bpd.Series([1, 2, 3], session=session) - pd_series = pd.Series([1, 2, 3]) - - actual_result = ( - typing.cast(bpd.Series, bpd.to_timedelta(bf_series, unit)) - .to_pandas() - .astype("timedelta64[ns]") - ) - - expected_result = pd.to_timedelta(pd_series, unit).astype("timedelta64[ns]") - assert_series_equal(actual_result, expected_result, check_index_type=False) - - -def test_to_timedelta_with_bf_float_series_value_rounded_down(session): - bf_series = bpd.Series([1.2, 2.9], session=session) - - actual_result = ( - typing.cast(bpd.Series, bpd.to_timedelta(bf_series, "us")) - .to_pandas() - .astype("timedelta64[ns]") - ) - - expected_result = pd.Series([pd.Timedelta(1, "us"), pd.Timedelta(2, "us")]).astype( - "timedelta64[ns]" - ) - bigframes.testing.utils.assert_series_equal( - actual_result, expected_result, check_index_type=False - ) - - -@pytest.mark.parametrize( - "input", - [ - pytest.param([1, 2, 3], id="list"), - pytest.param((1, 2, 3), id="tuple"), - pytest.param(pd.Series([1, 2, 3]), id="pandas-series"), - ], -) -def test_to_timedelta_with_list_like_input(session, input): - actual_result = ( - typing.cast(bpd.Series, bpd.to_timedelta(input, "s", session=session)) - .to_pandas() - .astype("timedelta64[ns]") - ) - - expected_result = pd.Series(pd.to_timedelta(input, "s")).astype("timedelta64[ns]") - bigframes.testing.utils.assert_series_equal( - actual_result, expected_result, check_index_type=False - ) - - -@pytest.mark.parametrize( - "unit", - ["Y", "M", "whatever"], -) -def test_to_timedelta_with_bf_series_invalid_unit(session, unit): - bf_series = bpd.Series([1, 2, 3], session=session) - - with pytest.raises(TypeError): - bpd.to_timedelta(bf_series, unit) - - -@pytest.mark.parametrize("input", [1, 1.2, "1s"]) -def test_to_timedelta_non_bf_series(input): - assert bpd.to_timedelta(input) == pd.to_timedelta(input) - - -def test_to_timedelta_on_timedelta_series__should_be_no_op(scalars_dfs): - bf_df, pd_df = scalars_dfs - bf_series = bpd.to_timedelta(bf_df["int64_too"], unit="us") - pd_series = pd.to_timedelta(pd_df["int64_too"], unit="us") - - actual_result = ( - bpd.to_timedelta(bf_series, unit="s").to_pandas().astype("timedelta64[ns]") - ) - - expected_result = pd.to_timedelta(pd_series, unit="s").astype("timedelta64[ns]") - bigframes.testing.utils.assert_series_equal( - actual_result, expected_result, check_index_type=False - ) + pd.testing.assert_series_equal(bf_result, pd_result) diff --git a/tests/system/small/test_pandas_options.py b/tests/system/small/test_pandas_options.py index a9ec4355f07..ca67710d4ea 100644 --- a/tests/system/small/test_pandas_options.py +++ b/tests/system/small/test_pandas_options.py @@ -13,18 +13,23 @@ # limitations under the License. import datetime -import re -import warnings from unittest import mock import google.api_core.exceptions -import pandas.testing +import google.auth +import google.auth.exceptions import pytest -import bigframes.exceptions +import bigframes.core.global_session import bigframes.pandas as bpd +@pytest.fixture(autouse=True) +def reset_default_session_and_location(): + bpd.close_session() + bpd.options.bigquery.location = None + + @pytest.mark.parametrize( ("read_method", "query_prefix"), [ @@ -48,7 +53,6 @@ def test_read_gbq_start_sets_session_location( dataset_id_permanent, read_method, query_prefix, - reset_default_session_and_location, ): # Form query as a table name or a SQL depending on the test scenario query_tokyo = test_data_tables_tokyo["scalars"] @@ -61,12 +65,8 @@ def test_read_gbq_start_sets_session_location( assert not bpd.options.bigquery.location # Starting user journey with read_gbq* should work for a table in any - # location, in this case tokyo. - with warnings.catch_warnings(): - # Since the query refers to a specific location, no warning should be - # raised. - warnings.simplefilter("error", bigframes.exceptions.DefaultLocationWarning) - df = read_method(query_tokyo) + # location, in this case tokyo + df = read_method(query_tokyo) assert df is not None # Now bigquery options location should be set to tokyo @@ -74,14 +74,12 @@ def test_read_gbq_start_sets_session_location( # Now read_gbq* from another location should fail with pytest.raises( - (google.api_core.exceptions.NotFound, ValueError), + google.api_core.exceptions.NotFound, match=dataset_id_permanent, ): read_method(query) - # Close the global session to start over. - # Note: This is a thread-local operation because of the - # reset_default_session_and_location fixture above. + # Close global session to start over bpd.close_session() # There should still be the previous location set in the bigquery options @@ -101,7 +99,7 @@ def test_read_gbq_start_sets_session_location( # Now read_gbq* from another location should fail with pytest.raises( - (google.api_core.exceptions.NotFound, ValueError), + google.api_core.exceptions.NotFound, match=dataset_id_permanent_tokyo, ): read_method(query_tokyo) @@ -129,7 +127,6 @@ def test_read_gbq_after_session_start_must_comply_with_default_location( dataset_id_permanent_tokyo, read_method, query_prefix, - reset_default_session_and_location, ): # Form query as a table name or a SQL depending on the test scenario query_tokyo = test_data_tables_tokyo["scalars"] @@ -143,16 +140,12 @@ def test_read_gbq_after_session_start_must_comply_with_default_location( # Starting user journey with anything other than read_gbq*, such as # read_pandas would bind the session to default location US - with pytest.warns( - bigframes.exceptions.DefaultLocationWarning, - match=re.escape("using location US for the session"), - ): - df = bpd.read_pandas(scalars_pandas_df_index) + df = bpd.read_pandas(scalars_pandas_df_index) assert df is not None # Doing read_gbq* from a table in another location should fail with pytest.raises( - (google.api_core.exceptions.NotFound, ValueError), + google.api_core.exceptions.NotFound, match=dataset_id_permanent_tokyo, ): read_method(query_tokyo) @@ -183,7 +176,6 @@ def test_read_gbq_must_comply_with_set_location_US( dataset_id_permanent_tokyo, read_method, query_prefix, - reset_default_session_and_location, ): # Form query as a table name or a SQL depending on the test scenario query_tokyo = test_data_tables_tokyo["scalars"] @@ -201,7 +193,7 @@ def test_read_gbq_must_comply_with_set_location_US( # Starting user journey with read_gbq* from another location should fail with pytest.raises( - (google.api_core.exceptions.NotFound, ValueError), + google.api_core.exceptions.NotFound, match=dataset_id_permanent_tokyo, ): read_method(query_tokyo) @@ -234,7 +226,6 @@ def test_read_gbq_must_comply_with_set_location_non_US( dataset_id_permanent, read_method, query_prefix, - reset_default_session_and_location, ): # Form query as a table name or a SQL depending on the test scenario query_tokyo = test_data_tables_tokyo["scalars"] @@ -252,7 +243,7 @@ def test_read_gbq_must_comply_with_set_location_non_US( # Starting user journey with read_gbq* from another location should fail with pytest.raises( - (google.api_core.exceptions.NotFound, ValueError), + google.api_core.exceptions.NotFound, match=dataset_id_permanent, ): read_method(query) @@ -263,43 +254,27 @@ def test_read_gbq_must_comply_with_set_location_non_US( assert df is not None -def test_credentials_need_reauthentication( - monkeypatch, reset_default_session_and_location -): +def test_close_session_after_credentials_need_reauthentication(monkeypatch): # Use a simple test query to verify that default session works to interact - # with BQ. + # with BQ test_query = "SELECT 1" + # Confirm that default session has BQ client with valid credentials + session = bpd.get_global_session() + assert session.bqclient._credentials.valid + # Confirm that default session works as usual df = bpd.read_gbq(test_query) assert df is not None - # Call get_global_session() *after* read_gbq so that our location detection - # has a chance to work. - session = bpd.get_global_session() - assert session.bqclient._http.credentials.valid - - # We look at the thread-local session because of the - # reset_default_session_and_location fixture and that this test mutates - # state that might otherwise be used by tests running in parallel. - current_session = ( - bigframes.core.global_session._global_session_state.thread_local_session - ) - assert current_session is not None - - # Force a temp table to be created, so there is something to cleanup. - current_session._anon_dataset_manager.create_temp_table(schema=()) - with monkeypatch.context() as m: # Simulate expired credentials to trigger the credential refresh flow - m.setattr( - session.bqclient._http.credentials, "expiry", datetime.datetime.utcnow() - ) - assert not session.bqclient._http.credentials.valid + m.setattr(session.bqclient._credentials, "expiry", datetime.datetime.utcnow()) + assert not session.bqclient._credentials.valid # Simulate an exception during the credential refresh flow m.setattr( - session.bqclient._http.credentials, + session.bqclient._credentials, "refresh", mock.Mock(side_effect=google.auth.exceptions.RefreshError()), ) @@ -313,64 +288,10 @@ def test_credentials_need_reauthentication( with pytest.raises(google.auth.exceptions.RefreshError): bpd.read_gbq(test_query) - with warnings.catch_warnings(record=True) as warned: - bpd.close_session() # CleanupFailedWarning: can't clean up - - # The test forces a failure during cleanup and asserts that one or more warning is generated - # when/if multiple temp tables might have been left over. - assert len(warned) >= 1 - assert warned[0].category == bigframes.exceptions.CleanupFailedWarning - - assert ( - bigframes.core.global_session._global_session_state.thread_local_session - is None - ) + # Now verify that closing the session works + bpd.close_session() + assert bigframes.core.global_session._global_session is None # 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_polars_execution.py b/tests/system/small/test_polars_execution.py deleted file mode 100644 index fad8d9dba2f..00000000000 --- a/tests/system/small/test_polars_execution.py +++ /dev/null @@ -1,97 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -import math - -import pytest - -import bigframes -import bigframes.bigquery -from bigframes.testing.utils import assert_frame_equal - -polars = pytest.importorskip("polars") - - -@pytest.fixture(scope="module") -def session_w_polars(): - context = bigframes.BigQueryOptions(location="US", enable_polars_execution=True) - session = bigframes.Session(context=context) - yield session - session.close() # close generated session at cleanup time - - -def test_polar_execution_sorted(session_w_polars, scalars_pandas_df_index): - execution_count_before = session_w_polars._metrics.execution_count - bf_df = session_w_polars.read_pandas(scalars_pandas_df_index) - - pd_result = scalars_pandas_df_index.sort_index(ascending=False)[ - ["int64_too", "bool_col"] - ] - bf_result = bf_df.sort_index(ascending=False)[["int64_too", "bool_col"]].to_pandas() - - assert session_w_polars._metrics.execution_count == execution_count_before + 1 - assert_frame_equal(bf_result, pd_result) - - -def test_polar_execution_sorted_filtered(session_w_polars, scalars_pandas_df_index): - execution_count_before = session_w_polars._metrics.execution_count - bf_df = session_w_polars.read_pandas(scalars_pandas_df_index) - - pd_result = scalars_pandas_df_index.sort_index(ascending=False).dropna( - subset=["int64_col", "string_col"] - ) - bf_result = ( - bf_df.sort_index(ascending=False) - .dropna(subset=["int64_col", "string_col"]) - .to_pandas() - ) - - assert session_w_polars._metrics.execution_count == execution_count_before + 1 - assert_frame_equal(bf_result, pd_result) - - -def test_polar_execution_unsupported_sql_fallback( - session_w_polars, scalars_pandas_df_index -): - execution_count_before = session_w_polars._metrics.execution_count - bf_df = session_w_polars.read_pandas(scalars_pandas_df_index) - - bf_df["geo_area"] = bigframes.bigquery.st_length(bf_df.geography_col) - bf_result = bf_df.to_pandas() - - # geo fns not supported by polar engine yet, so falls back to bq execution - assert session_w_polars._metrics.execution_count == (execution_count_before + 2) - assert math.isclose(bf_result.geo_area.sum(), 70.52332050, rel_tol=0.00001) - - -def test_polars_execution_history(session_w_polars): - import pandas as pd - - # Create a small local DataFrame - pdf = pd.DataFrame({"col_a": [1, 2, 3], "col_b": ["x", "y", "z"]}) - - # Read simple local data - df = session_w_polars.read_pandas(pdf) - - # Trigger execution - _ = df.to_pandas() - - # Verify the execution history captured the local job - history = session_w_polars.execution_history().to_dataframe() - - # Verify we have at least one job and logged as polars - assert len(history) > 0 - last_job = history.iloc[-1] - - assert last_job["job_type"] == "polars" - assert last_job["status"] == "DONE" diff --git a/tests/system/small/test_progress_bar.py b/tests/system/small/test_progress_bar.py index a179e18332a..30ea63b483a 100644 --- a/tests/system/small/test_progress_bar.py +++ b/tests/system/small/test_progress_bar.py @@ -15,59 +15,47 @@ import re import tempfile -import numpy as np import pandas as pd -import pytest import bigframes as bf import bigframes.formatting_helpers as formatting_helpers -from bigframes.session import MAX_INLINE_DF_BYTES -job_load_message_regex = r"Query" -EXPECTED_DRY_RUN_MESSAGE = "Computation deferred. Computation will process" +job_load_message_regex = r"\w+ job [\w-]+ is \w+\." def test_progress_bar_dataframe( penguins_df_default_index: bf.dataframe.DataFrame, capsys ): + bf.options.display.progress_bar = "terminal" capsys.readouterr() # clear output - - with bf.option_context("display.progress_bar", "terminal"): - penguins_df_default_index.to_pandas(allow_large_results=True) + penguins_df_default_index.to_pandas() assert_loading_msg_exist(capsys.readouterr().out) assert penguins_df_default_index.query_job is not None def test_progress_bar_series(penguins_df_default_index: bf.dataframe.DataFrame, capsys): + bf.options.display.progress_bar = "terminal" series = penguins_df_default_index["body_mass_g"].head(10) capsys.readouterr() # clear output - - with bf.option_context("display.progress_bar", "terminal"): - series.to_pandas(allow_large_results=True) + series.to_pandas() assert_loading_msg_exist(capsys.readouterr().out) assert series.query_job is not None def test_progress_bar_scalar(penguins_df_default_index: bf.dataframe.DataFrame, capsys): + bf.options.display.progress_bar = "terminal" capsys.readouterr() # clear output - - with bf.option_context("display.progress_bar", "terminal"): - penguins_df_default_index["body_mass_g"].head(10).mean() + penguins_df_default_index["body_mass_g"].head(10).mean() assert_loading_msg_exist(capsys.readouterr().out) -def test_progress_bar_scalar_allow_large_results( - penguins_df_default_index: bf.dataframe.DataFrame, capsys -): +def test_progress_bar_read_gbq(session: bf.Session, penguins_table_id: str, capsys): + bf.options.display.progress_bar = "terminal" capsys.readouterr() # clear output - - with bf.option_context( - "display.progress_bar", "terminal", "compute.allow_large_results", "True" - ): - penguins_df_default_index["body_mass_g"].head(10).mean() + session.read_gbq(penguins_table_id) assert_loading_msg_exist(capsys.readouterr().out) @@ -75,11 +63,10 @@ def test_progress_bar_scalar_allow_large_results( def test_progress_bar_extract_jobs( penguins_df_default_index: bf.dataframe.DataFrame, gcs_folder, capsys ): + bf.options.display.progress_bar = "terminal" path = gcs_folder + "test_read_csv_progress_bar*.csv" capsys.readouterr() # clear output - - with bf.option_context("display.progress_bar", "terminal"): - penguins_df_default_index.to_csv(path) + penguins_df_default_index.to_csv(path) assert_loading_msg_exist(capsys.readouterr().out) @@ -87,54 +74,53 @@ def test_progress_bar_extract_jobs( def test_progress_bar_load_jobs( session: bf.Session, penguins_pandas_df_default_index: pd.DataFrame, capsys ): - # repeat the DF to be big enough to trigger the load job. - df = penguins_pandas_df_default_index - while len(df) < MAX_INLINE_DF_BYTES: - df = pd.DataFrame(np.repeat(df.values, 2, axis=0)) - - with ( - bf.option_context("display.progress_bar", "terminal"), - tempfile.TemporaryDirectory() as dir, - ): + bf.options.display.progress_bar = "terminal" + with tempfile.TemporaryDirectory() as dir: path = dir + "/test_read_csv_progress_bar*.csv" - df.to_csv(path, index=False) + penguins_pandas_df_default_index.to_csv(path, index=False) capsys.readouterr() # clear output session.read_csv(path) - assert_loading_msg_exist(capsys.readouterr().out, pattern="Load") - - -def test_progress_bar_uniqueness_check(session: bf.Session, capsys): - # Ensure strictly_ordered is True (default) to trigger uniqueness check - assert session._strictly_ordered - - capsys.readouterr() # clear output - - with bf.option_context("display.progress_bar", "terminal"): - # Read a table and specify a non-unique index_col to trigger the check. - # We use a public table to make it a "real" test. - session.read_gbq_table( - "bigquery-public-data.ml_datasets.penguins", - index_col="island", - ) - assert_loading_msg_exist(capsys.readouterr().out) -def assert_loading_msg_exist(capstdout: str, pattern=job_load_message_regex): - num_loading_msg = 0 - lines = capstdout.split("\n") +def assert_loading_msg_exist(capystOut: str, pattern=job_load_message_regex): + numLoadingMsg = 0 + lines = capystOut.split("\n") lines = [line for line in lines if len(line) > 0] assert len(lines) > 0 for line in lines: - if re.search(pattern, line) is not None: - num_loading_msg += 1 - assert num_loading_msg > 0 + if re.match(pattern, line) is not None: + numLoadingMsg += 1 + assert numLoadingMsg > 0 + + +def test_query_job_repr_html(penguins_df_default_index: bf.dataframe.DataFrame): + bf.options.display.progress_bar = "terminal" + penguins_df_default_index._block._expr.session.bqclient.default_query_job_config.use_query_cache = ( + False + ) + penguins_df_default_index.to_pandas() + query_job_repr = formatting_helpers.repr_query_job_html( + penguins_df_default_index.query_job + ).value + string_checks = [ + "Job Id", + "Destination Table", + "Slot Time", + "Bytes Processed", + "Cache hit", + ] + for string in string_checks: + assert string in query_job_repr def test_query_job_repr(penguins_df_default_index: bf.dataframe.DataFrame): - penguins_df_default_index.to_pandas(allow_large_results=True) + penguins_df_default_index._block._expr.session.bqclient.default_query_job_config.use_query_cache = ( + False + ) + penguins_df_default_index.to_pandas() query_job_repr = formatting_helpers.repr_query_job( penguins_df_default_index.query_job ) @@ -149,39 +135,11 @@ def test_query_job_repr(penguins_df_default_index: bf.dataframe.DataFrame): assert string in query_job_repr -def test_query_job_dry_run_dataframe(penguins_df_default_index: bf.dataframe.DataFrame): - with bf.option_context("display.repr_mode", "deferred"): - df_result = repr(penguins_df_default_index) - assert EXPECTED_DRY_RUN_MESSAGE in df_result - - -def test_query_job_dry_run_index(penguins_df_default_index: bf.dataframe.DataFrame): - with bf.option_context("display.repr_mode", "deferred"): - index_result = repr(penguins_df_default_index.index) - assert EXPECTED_DRY_RUN_MESSAGE in index_result - - -def test_query_job_dry_run_series(penguins_df_default_index: bf.dataframe.DataFrame): +def test_query_job_dry_run(penguins_df_default_index: bf.dataframe.DataFrame, capsys): 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.render_mode", "anywidget"): - actual_repr = repr(penguins_df_default_index) - assert "species" in actual_repr - assert "island" in actual_repr - assert "[344 rows x 7 columns]" in actual_repr - - -def test_repr_anywidget_index(penguins_df_default_index: bf.dataframe.DataFrame): - pytest.importorskip("anywidget") - with bf.option_context("display.render_mode", "anywidget"): - index = penguins_df_default_index.index - actual_repr = repr(index) - # In non-interactive environments, should still get a useful summary. - assert "Index" in actual_repr - assert "0, 1, 2, 3, 4" in actual_repr - assert "dtype='Int64'" in actual_repr + repr(penguins_df_default_index) + repr(penguins_df_default_index["body_mass_g"]) + lines = capsys.readouterr().out.split("\n") + lines = filter(None, lines) + for line in lines: + assert "Computation deferred. Computation will process" in line diff --git a/tests/system/small/test_remote_function.py b/tests/system/small/test_remote_function.py new file mode 100644 index 00000000000..89907a53dfc --- /dev/null +++ b/tests/system/small/test_remote_function.py @@ -0,0 +1,646 @@ +# Copyright 2023 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 google.cloud import bigquery +import pandas as pd +import pytest + +import bigframes +from bigframes import remote_function as rf +from tests.system.utils import assert_pandas_df_equal_ignore_ordering + + +@pytest.fixture(scope="module") +def bq_cf_connection() -> str: + """Pre-created BQ connection to invoke cloud function for bigframes-dev + $ bq show --connection --location=us --project_id=bigframes-dev bigframes-rf-conn + """ + return "bigframes-rf-conn" + + +@pytest.fixture(scope="module") +def bq_cf_connection_location() -> str: + """Pre-created BQ connection to invoke cloud function for bigframes-dev + $ bq show --connection --location=us --project_id=bigframes-dev bigframes-rf-conn + """ + return "us.bigframes-rf-conn" + + +@pytest.fixture(scope="module") +def bq_cf_connection_location_mismatched() -> str: + """Pre-created BQ connection to invoke cloud function for bigframes-dev + $ bq show --connection --location=eu --project_id=bigframes-dev bigframes-rf-conn + """ + return "eu.bigframes-rf-conn" + + +@pytest.fixture(scope="module") +def bq_cf_connection_location_project() -> str: + """Pre-created BQ connection to invoke cloud function for bigframes-dev + $ bq show --connection --location=us --project_id=bigframes-dev bigframes-rf-conn + """ + return "bigframes-dev.us.bigframes-rf-conn" + + +@pytest.fixture(scope="module") +def bq_cf_connection_location_project_mismatched() -> str: + """Pre-created BQ connection to invoke cloud function for bigframes-dev + $ bq show --connection --location=eu --project_id=bigframes-metrics bigframes-rf-conn + """ + return "bigframes-metrics.eu.bigframes-rf-conn" + + +@pytest.fixture(scope="module") +def session_with_bq_connection_and_permanent_dataset( + bq_cf_connection, dataset_id_permanent +) -> bigframes.Session: + session = bigframes.Session( + bigframes.BigQueryOptions(bq_connection=bq_cf_connection) + ) + session._session_dataset = bigquery.Dataset(dataset_id_permanent) + return session + + +@pytest.mark.flaky(retries=2, delay=120) +def test_remote_function_direct_no_session_param( + bigquery_client, + bigqueryconnection_client, + cloudfunctions_client, + resourcemanager_client, + scalars_dfs, + dataset_id_permanent, + bq_cf_connection, +): + @rf.remote_function( + [int], + int, + bigquery_client=bigquery_client, + bigquery_connection_client=bigqueryconnection_client, + cloud_functions_client=cloudfunctions_client, + resource_manager_client=resourcemanager_client, + dataset=dataset_id_permanent, + bigquery_connection=bq_cf_connection, + # See e2e tests for tests that actually deploy the Cloud Function. + reuse=True, + ) + def square(x): + return x * x + + assert square.bigframes_remote_function + assert square.bigframes_cloud_function + + scalars_df, scalars_pandas_df = scalars_dfs + + bf_int64_col = scalars_df["int64_col"] + bf_int64_col_filter = bf_int64_col.notnull() + bf_int64_col_filtered = bf_int64_col[bf_int64_col_filter] + bf_result_col = bf_int64_col_filtered.apply(square) + bf_result = ( + bf_int64_col_filtered.to_frame().assign(result=bf_result_col).to_pandas() + ) + + pd_int64_col = scalars_pandas_df["int64_col"] + pd_int64_col_filter = pd_int64_col.notnull() + pd_int64_col_filtered = pd_int64_col[pd_int64_col_filter] + pd_result_col = pd_int64_col_filtered.apply(lambda x: x * x) + # TODO(shobs): Figure why pandas .apply() changes the dtype, i.e. + # pd_int64_col_filtered.dtype is Int64Dtype() + # pd_int64_col_filtered.apply(lambda x: x * x).dtype is int64. + # For this test let's force the pandas dtype to be same as bigframes' dtype. + pd_result_col = pd_result_col.astype(pd.Int64Dtype()) + pd_result = pd_int64_col_filtered.to_frame().assign(result=pd_result_col) + + assert_pandas_df_equal_ignore_ordering(bf_result, pd_result) + + +@pytest.mark.flaky(retries=2, delay=120) +def test_remote_function_direct_no_session_param_location_specified( + bigquery_client, + bigqueryconnection_client, + cloudfunctions_client, + resourcemanager_client, + scalars_dfs, + dataset_id_permanent, + bq_cf_connection_location, +): + @rf.remote_function( + [int], + int, + bigquery_client=bigquery_client, + bigquery_connection_client=bigqueryconnection_client, + cloud_functions_client=cloudfunctions_client, + resource_manager_client=resourcemanager_client, + dataset=dataset_id_permanent, + bigquery_connection=bq_cf_connection_location, + # See e2e tests for tests that actually deploy the Cloud Function. + reuse=True, + ) + def square(x): + return x * x + + scalars_df, scalars_pandas_df = scalars_dfs + + bf_int64_col = scalars_df["int64_col"] + bf_int64_col_filter = bf_int64_col.notnull() + bf_int64_col_filtered = bf_int64_col[bf_int64_col_filter] + bf_result_col = bf_int64_col_filtered.apply(square) + bf_result = ( + bf_int64_col_filtered.to_frame().assign(result=bf_result_col).to_pandas() + ) + + pd_int64_col = scalars_pandas_df["int64_col"] + pd_int64_col_filter = pd_int64_col.notnull() + pd_int64_col_filtered = pd_int64_col[pd_int64_col_filter] + pd_result_col = pd_int64_col_filtered.apply(lambda x: x * x) + # TODO(shobs): Figure why pandas .apply() changes the dtype, i.e. + # pd_int64_col_filtered.dtype is Int64Dtype() + # pd_int64_col_filtered.apply(lambda x: x * x).dtype is int64. + # For this test let's force the pandas dtype to be same as bigframes' dtype. + pd_result_col = pd_result_col.astype(pd.Int64Dtype()) + pd_result = pd_int64_col_filtered.to_frame().assign(result=pd_result_col) + + assert_pandas_df_equal_ignore_ordering(bf_result, pd_result) + + +@pytest.mark.flaky(retries=2, delay=120) +def test_remote_function_direct_no_session_param_location_mismatched( + bigquery_client, + bigqueryconnection_client, + cloudfunctions_client, + resourcemanager_client, + dataset_id_permanent, + bq_cf_connection_location_mismatched, +): + with pytest.raises(ValueError): + + @rf.remote_function( + [int], + int, + bigquery_client=bigquery_client, + bigquery_connection_client=bigqueryconnection_client, + cloud_functions_client=cloudfunctions_client, + resource_manager_client=resourcemanager_client, + dataset=dataset_id_permanent, + bigquery_connection=bq_cf_connection_location_mismatched, + # See e2e tests for tests that actually deploy the Cloud Function. + reuse=True, + ) + def square(x): + return x * x + + +@pytest.mark.flaky(retries=2, delay=120) +def test_remote_function_direct_no_session_param_location_project_specified( + bigquery_client, + bigqueryconnection_client, + cloudfunctions_client, + resourcemanager_client, + scalars_dfs, + dataset_id_permanent, + bq_cf_connection_location_project, +): + @rf.remote_function( + [int], + int, + bigquery_client=bigquery_client, + bigquery_connection_client=bigqueryconnection_client, + cloud_functions_client=cloudfunctions_client, + resource_manager_client=resourcemanager_client, + dataset=dataset_id_permanent, + bigquery_connection=bq_cf_connection_location_project, + # See e2e tests for tests that actually deploy the Cloud Function. + reuse=True, + ) + def square(x): + return x * x + + scalars_df, scalars_pandas_df = scalars_dfs + + bf_int64_col = scalars_df["int64_col"] + bf_int64_col_filter = bf_int64_col.notnull() + bf_int64_col_filtered = bf_int64_col[bf_int64_col_filter] + bf_result_col = bf_int64_col_filtered.apply(square) + bf_result = ( + bf_int64_col_filtered.to_frame().assign(result=bf_result_col).to_pandas() + ) + + pd_int64_col = scalars_pandas_df["int64_col"] + pd_int64_col_filter = pd_int64_col.notnull() + pd_int64_col_filtered = pd_int64_col[pd_int64_col_filter] + pd_result_col = pd_int64_col_filtered.apply(lambda x: x * x) + # TODO(shobs): Figure why pandas .apply() changes the dtype, i.e. + # pd_int64_col_filtered.dtype is Int64Dtype() + # pd_int64_col_filtered.apply(lambda x: x * x).dtype is int64. + # For this test let's force the pandas dtype to be same as bigframes' dtype. + pd_result_col = pd_result_col.astype(pd.Int64Dtype()) + pd_result = pd_int64_col_filtered.to_frame().assign(result=pd_result_col) + + assert_pandas_df_equal_ignore_ordering(bf_result, pd_result) + + +@pytest.mark.flaky(retries=2, delay=120) +def test_remote_function_direct_no_session_param_project_mismatched( + bigquery_client, + bigqueryconnection_client, + cloudfunctions_client, + resourcemanager_client, + dataset_id_permanent, + bq_cf_connection_location_project_mismatched, +): + with pytest.raises(ValueError): + + @rf.remote_function( + [int], + int, + bigquery_client=bigquery_client, + bigquery_connection_client=bigqueryconnection_client, + cloud_functions_client=cloudfunctions_client, + resource_manager_client=resourcemanager_client, + dataset=dataset_id_permanent, + bigquery_connection=bq_cf_connection_location_project_mismatched, + # See e2e tests for tests that actually deploy the Cloud Function. + reuse=True, + ) + def square(x): + return x * x + + +@pytest.mark.flaky(retries=2, delay=120) +def test_remote_function_direct_session_param( + session_with_bq_connection_and_permanent_dataset, scalars_dfs +): + @rf.remote_function( + [int], + int, + session=session_with_bq_connection_and_permanent_dataset, + ) + def square(x): + return x * x + + scalars_df, scalars_pandas_df = scalars_dfs + + bf_int64_col = scalars_df["int64_col"] + bf_int64_col_filter = bf_int64_col.notnull() + bf_int64_col_filtered = bf_int64_col[bf_int64_col_filter] + bf_result_col = bf_int64_col_filtered.apply(square) + bf_result = ( + bf_int64_col_filtered.to_frame().assign(result=bf_result_col).to_pandas() + ) + + pd_int64_col = scalars_pandas_df["int64_col"] + pd_int64_col_filter = pd_int64_col.notnull() + pd_int64_col_filtered = pd_int64_col[pd_int64_col_filter] + pd_result_col = pd_int64_col_filtered.apply(lambda x: x * x) + # TODO(shobs): Figure why pandas .apply() changes the dtype, i.e. + # pd_int64_col_filtered.dtype is Int64Dtype() + # pd_int64_col_filtered.apply(lambda x: x * x).dtype is int64. + # For this test let's force the pandas dtype to be same as bigframes' dtype. + pd_result_col = pd_result_col.astype(pd.Int64Dtype()) + pd_result = pd_int64_col_filtered.to_frame().assign(result=pd_result_col) + + assert_pandas_df_equal_ignore_ordering(bf_result, pd_result) + + +@pytest.mark.flaky(retries=2, delay=120) +def test_remote_function_via_session_default( + session_with_bq_connection_and_permanent_dataset, scalars_dfs +): + # Session has bigquery connection initialized via context. Without an + # explicit dataset the default dataset from the session would be used. + # Without an explicit bigquery connection, the one present in Session set + # through the explicit BigQueryOptions would be used. Without an explicit `reuse` + # the default behavior of reuse=True will take effect. Please note that the + # udf is same as the one used in other tests in this file so the underlying + # cloud function would be common and quickly reused. + @session_with_bq_connection_and_permanent_dataset.remote_function([int], int) + def square(x): + return x * x + + scalars_df, scalars_pandas_df = scalars_dfs + + bf_int64_col = scalars_df["int64_col"] + bf_int64_col_filter = bf_int64_col.notnull() + bf_int64_col_filtered = bf_int64_col[bf_int64_col_filter] + bf_result_col = bf_int64_col_filtered.apply(square) + bf_result = ( + bf_int64_col_filtered.to_frame().assign(result=bf_result_col).to_pandas() + ) + + pd_int64_col = scalars_pandas_df["int64_col"] + pd_int64_col_filter = pd_int64_col.notnull() + pd_int64_col_filtered = pd_int64_col[pd_int64_col_filter] + pd_result_col = pd_int64_col_filtered.apply(lambda x: x * x) + # TODO(shobs): Figure why pandas .apply() changes the dtype, i.e. + # pd_int64_col_filtered.dtype is Int64Dtype() + # pd_int64_col_filtered.apply(lambda x: x * x).dtype is int64. + # For this test let's force the pandas dtype to be same as bigframes' dtype. + pd_result_col = pd_result_col.astype(pd.Int64Dtype()) + pd_result = pd_int64_col_filtered.to_frame().assign(result=pd_result_col) + + assert_pandas_df_equal_ignore_ordering(bf_result, pd_result) + + +@pytest.mark.flaky(retries=2, delay=120) +def test_remote_function_via_session_with_overrides( + session, scalars_dfs, dataset_id_permanent, bq_cf_connection +): + @session.remote_function( + [int], + int, + dataset_id_permanent, + bq_cf_connection, + # See e2e tests for tests that actually deploy the Cloud Function. + reuse=True, + ) + def square(x): + return x * x + + scalars_df, scalars_pandas_df = scalars_dfs + + bf_int64_col = scalars_df["int64_col"] + bf_int64_col_filter = bf_int64_col.notnull() + bf_int64_col_filtered = bf_int64_col[bf_int64_col_filter] + bf_result_col = bf_int64_col_filtered.apply(square) + bf_result = ( + bf_int64_col_filtered.to_frame().assign(result=bf_result_col).to_pandas() + ) + + pd_int64_col = scalars_pandas_df["int64_col"] + pd_int64_col_filter = pd_int64_col.notnull() + pd_int64_col_filtered = pd_int64_col[pd_int64_col_filter] + pd_result_col = pd_int64_col_filtered.apply(lambda x: x * x) + # TODO(shobs): Figure why pandas .apply() changes the dtype, i.e. + # pd_int64_col_filtered.dtype is Int64Dtype() + # pd_int64_col_filtered.apply(lambda x: x * x).dtype is int64. + # For this test let's force the pandas dtype to be same as bigframes' dtype. + pd_result_col = pd_result_col.astype(pd.Int64Dtype()) + pd_result = pd_int64_col_filtered.to_frame().assign(result=pd_result_col) + + assert_pandas_df_equal_ignore_ordering(bf_result, pd_result) + + +@pytest.mark.flaky(retries=2, delay=120) +def test_dataframe_applymap( + session_with_bq_connection_and_permanent_dataset, scalars_dfs +): + def add_one(x): + return x + 1 + + remote_add_one = session_with_bq_connection_and_permanent_dataset.remote_function( + [int], int + )(add_one) + + scalars_df, scalars_pandas_df = scalars_dfs + int64_cols = ["int64_col", "int64_too"] + + bf_int64_df = scalars_df[int64_cols] + bf_int64_df_filtered = bf_int64_df.dropna() + bf_result = bf_int64_df_filtered.applymap(remote_add_one).to_pandas() + + pd_int64_df = scalars_pandas_df[int64_cols] + pd_int64_df_filtered = pd_int64_df.dropna() + pd_result = pd_int64_df_filtered.applymap(add_one) + # TODO(shobs): Figure why pandas .applymap() changes the dtype, i.e. + # pd_int64_df_filtered.dtype is Int64Dtype() + # pd_int64_df_filtered.applymap(lambda x: x).dtype is int64. + # For this test let's force the pandas dtype to be same as input. + for col in pd_result: + pd_result[col] = pd_result[col].astype(pd_int64_df_filtered[col].dtype) + + assert_pandas_df_equal_ignore_ordering(bf_result, pd_result) + + +@pytest.mark.flaky(retries=2, delay=120) +def test_dataframe_applymap_na_ignore( + session_with_bq_connection_and_permanent_dataset, scalars_dfs +): + def add_one(x): + return x + 1 + + remote_add_one = session_with_bq_connection_and_permanent_dataset.remote_function( + [int], int + )(add_one) + + scalars_df, scalars_pandas_df = scalars_dfs + int64_cols = ["int64_col", "int64_too"] + + bf_int64_df = scalars_df[int64_cols] + bf_result = bf_int64_df.applymap(remote_add_one, na_action="ignore").to_pandas() + + pd_int64_df = scalars_pandas_df[int64_cols] + pd_result = pd_int64_df.applymap(add_one, na_action="ignore") + # TODO(shobs): Figure why pandas .applymap() changes the dtype, i.e. + # pd_int64_df_filtered.dtype is Int64Dtype() + # pd_int64_df_filtered.applymap(lambda x: x).dtype is int64. + # For this test let's force the pandas dtype to be same as input. + for col in pd_result: + pd_result[col] = pd_result[col].astype(pd_int64_df[col].dtype) + + assert_pandas_df_equal_ignore_ordering(bf_result, pd_result) + + +@pytest.mark.flaky(retries=2, delay=120) +def test_series_map(session_with_bq_connection_and_permanent_dataset, scalars_dfs): + def add_one(x): + return x + 1 + + remote_add_one = session_with_bq_connection_and_permanent_dataset.remote_function( + [int], int + )(add_one) + + scalars_df, scalars_pandas_df = scalars_dfs + + bf_result = scalars_df.int64_too.map(remote_add_one).to_pandas() + pd_result = scalars_pandas_df.int64_too.map(add_one) + pd_result = pd_result.astype("Int64") # pandas type differences + + pd.testing.assert_series_equal( + bf_result, + pd_result, + ) + + +@pytest.mark.flaky(retries=2, delay=120) +def test_read_gbq_function_detects_invalid_function(bigquery_client, dataset_id): + dataset_ref = bigquery.DatasetReference.from_string(dataset_id) + with pytest.raises(ValueError) as e: + rf.read_gbq_function( + str(dataset_ref.routine("not_a_function")), + bigquery_client=bigquery_client, + ) + + assert "Unknown function" in str(e.value) + + +@pytest.mark.flaky(retries=2, delay=120) +def test_read_gbq_function_like_original( + bigquery_client, + bigqueryconnection_client, + cloudfunctions_client, + resourcemanager_client, + scalars_df_index, + dataset_id_permanent, + bq_cf_connection, +): + @rf.remote_function( + [int], + int, + bigquery_client=bigquery_client, + bigquery_connection_client=bigqueryconnection_client, + dataset=dataset_id_permanent, + cloud_functions_client=cloudfunctions_client, + resource_manager_client=resourcemanager_client, + bigquery_connection=bq_cf_connection, + reuse=True, + ) + def square1(x): + return x * x + + square2 = rf.read_gbq_function( + function_name=square1.bigframes_remote_function, + bigquery_client=bigquery_client, + ) + + # The newly-created function (square1) should have a remote function AND a + # cloud function associated with it, while the read-back version (square2) + # should only have a remote function. + assert square1.bigframes_remote_function + assert square1.bigframes_cloud_function + + assert square2.bigframes_remote_function + assert not hasattr(square2, "bigframes_cloud_function") + + # They should point to the same function. + assert square1.bigframes_remote_function == square2.bigframes_remote_function + + # The result of applying them should be the same. + int64_col = scalars_df_index["int64_col"] + int64_col_filter = int64_col.notnull() + int64_col_filtered = int64_col[int64_col_filter] + + s1_result_col = int64_col_filtered.apply(square1) + s1_result = int64_col_filtered.to_frame().assign(result=s1_result_col) + + s2_result_col = int64_col_filtered.apply(square2) + s2_result = int64_col_filtered.to_frame().assign(result=s2_result_col) + + assert_pandas_df_equal_ignore_ordering(s1_result.to_pandas(), s2_result.to_pandas()) + + +@pytest.mark.flaky(retries=2, delay=120) +def test_read_gbq_function_reads_udfs(bigquery_client, dataset_id): + dataset_ref = bigquery.DatasetReference.from_string(dataset_id) + arg = bigquery.RoutineArgument( + name="x", + data_type=bigquery.StandardSqlDataType(bigquery.StandardSqlTypeNames.INT64), + ) + sql_routine = bigquery.Routine( + dataset_ref.routine("square_sql"), + body="x * x", + arguments=[arg], + return_type=bigquery.StandardSqlDataType(bigquery.StandardSqlTypeNames.INT64), + type_=bigquery.RoutineType.SCALAR_FUNCTION, + ) + js_routine = bigquery.Routine( + dataset_ref.routine("square_js"), + body="return x * x", + language="JAVASCRIPT", + arguments=[arg], + return_type=bigquery.StandardSqlDataType(bigquery.StandardSqlTypeNames.INT64), + type_=bigquery.RoutineType.SCALAR_FUNCTION, + ) + + for routine in (sql_routine, js_routine): + # Create the routine in BigQuery and read it back using read_gbq_function. + bigquery_client.create_routine(routine, exists_ok=True) + square = rf.read_gbq_function( + str(routine.reference), bigquery_client=bigquery_client + ) + + # It should point to the named routine and yield the expected results. + assert square.bigframes_remote_function == str(routine.reference) + + src = {"x": [-5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5]} + + routine_ref_str = rf.routine_ref_to_string_for_query(routine.reference) + direct_sql = " UNION ALL ".join( + [f"SELECT {x} AS x, {routine_ref_str}({x}) AS y" for x in src["x"]] + ) + direct_df = bigquery_client.query(direct_sql).to_dataframe() + + indirect_df = bigframes.dataframe.DataFrame(src) + indirect_df = indirect_df.assign(y=indirect_df.x.apply(square)) + indirect_df = indirect_df.to_pandas() + + assert_pandas_df_equal_ignore_ordering(direct_df, indirect_df) + + +@pytest.mark.flaky(retries=2, delay=120) +def test_read_gbq_function_enforces_explicit_types(bigquery_client, dataset_id): + dataset_ref = bigquery.DatasetReference.from_string(dataset_id) + typed_arg = bigquery.RoutineArgument( + name="x", + data_type=bigquery.StandardSqlDataType(bigquery.StandardSqlTypeNames.INT64), + ) + untyped_arg = bigquery.RoutineArgument( + name="x", + kind="ANY_TYPE", # With this kind, data_type not required for SQL functions. + ) + + both_types_specified = bigquery.Routine( + dataset_ref.routine("both_types_specified"), + body="x * x", + arguments=[typed_arg], + return_type=bigquery.StandardSqlDataType(bigquery.StandardSqlTypeNames.INT64), + type_=bigquery.RoutineType.SCALAR_FUNCTION, + ) + only_return_type_specified = bigquery.Routine( + dataset_ref.routine("only_return_type_specified"), + body="x * x", + arguments=[untyped_arg], + return_type=bigquery.StandardSqlDataType(bigquery.StandardSqlTypeNames.INT64), + type_=bigquery.RoutineType.SCALAR_FUNCTION, + ) + only_arg_type_specified = bigquery.Routine( + dataset_ref.routine("only_arg_type_specified"), + body="x * x", + arguments=[typed_arg], + type_=bigquery.RoutineType.SCALAR_FUNCTION, + ) + neither_type_specified = bigquery.Routine( + dataset_ref.routine("neither_type_specified"), + body="x * x", + arguments=[untyped_arg], + type_=bigquery.RoutineType.SCALAR_FUNCTION, + ) + + bigquery_client.create_routine(both_types_specified, exists_ok=True) + bigquery_client.create_routine(only_return_type_specified, exists_ok=True) + bigquery_client.create_routine(only_arg_type_specified, exists_ok=True) + bigquery_client.create_routine(neither_type_specified, exists_ok=True) + + rf.read_gbq_function( + str(both_types_specified.reference), bigquery_client=bigquery_client + ) + rf.read_gbq_function( + str(only_return_type_specified.reference), bigquery_client=bigquery_client + ) + with pytest.raises(ValueError): + rf.read_gbq_function( + str(only_arg_type_specified.reference), bigquery_client=bigquery_client + ) + with pytest.raises(ValueError): + rf.read_gbq_function( + str(neither_type_specified.reference), bigquery_client=bigquery_client + ) diff --git a/tests/system/small/test_series.py b/tests/system/small/test_series.py index 2e80b75c0b4..6bb5c4755ca 100644 --- a/tests/system/small/test_series.py +++ b/tests/system/small/test_series.py @@ -12,32 +12,21 @@ # See the License for the specific language governing permissions and # limitations under the License. -import datetime as dt -import json import math import re import tempfile -import db_dtypes # type: ignore import geopandas as gpd # type: ignore -import google.api_core.exceptions import numpy import pandas as pd import pyarrow as pa # type: ignore import pytest -import shapely.geometry # type: ignore -from packaging.version import Version -import bigframes.dtypes as dtypes -import bigframes.features import bigframes.pandas import bigframes.series as series -import bigframes.testing -import bigframes.testing.utils -from bigframes.testing.utils import ( - assert_frame_equal, - assert_series_equal, - get_first_file_from_wildcard, +from tests.system.utils import ( + assert_pandas_df_equal_ignore_ordering, + assert_series_equal_ignoring_order, ) @@ -49,70 +38,7 @@ def test_series_construct_copy(scalars_dfs): pd_result = pd.Series( scalars_pandas_df["int64_col"], name="test_series", dtype="Float64" ) - bigframes.testing.utils.assert_series_equal(bf_result, pd_result) - - -def test_series_construct_nullable_ints(): - bf_result = series.Series( - [1, 3, bigframes.pandas.NA], index=[0, 4, bigframes.pandas.NA] - ).to_pandas() - - # TODO(b/340885567): fix type error - expected_index = pd.Index( # type: ignore - [0, 4, None], - dtype=pd.Int64Dtype(), - ) - expected = pd.Series([1, 3, pd.NA], dtype=pd.Int64Dtype(), index=expected_index) - - bigframes.testing.utils.assert_series_equal(bf_result, expected) - - -def test_series_construct_timestamps(): - datetimes = [ - dt.datetime(2020, 1, 20, 20, 20, 20, 20), - dt.datetime(2019, 1, 20, 20, 20, 20, 20), - None, - ] - bf_result = series.Series(datetimes).to_pandas() - pd_result = pd.Series(datetimes, dtype=pd.ArrowDtype(pa.timestamp("us"))) - - bigframes.testing.utils.assert_series_equal( - bf_result, pd_result, check_index_type=False - ) - - -def test_series_construct_copy_with_index(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - bf_result = series.Series( - scalars_df["int64_col"], - name="test_series", - dtype="Float64", - index=scalars_df["int64_too"], - ).to_pandas() - pd_result = pd.Series( - scalars_pandas_df["int64_col"], - name="test_series", - dtype="Float64", - index=scalars_pandas_df["int64_too"], - ) - bigframes.testing.utils.assert_series_equal(bf_result, pd_result) - - -def test_series_construct_copy_index(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - bf_result = series.Series( - scalars_df.index, - name="test_series", - dtype="Float64", - index=scalars_df["int64_too"], - ).to_pandas() - pd_result = pd.Series( - scalars_pandas_df.index, - name="test_series", - dtype="Float64", - index=scalars_pandas_df["int64_too"], - ) - bigframes.testing.utils.assert_series_equal(bf_result, pd_result) + pd.testing.assert_series_equal(bf_result, pd_result) def test_series_construct_pandas(scalars_dfs): @@ -124,7 +50,7 @@ def test_series_construct_pandas(scalars_dfs): scalars_pandas_df["int64_col"], name="test_series", dtype="Float64" ) assert bf_result.shape == pd_result.shape - bigframes.testing.utils.assert_series_equal(bf_result.to_pandas(), pd_result) + pd.testing.assert_series_equal(bf_result.to_pandas(), pd_result) def test_series_construct_from_list(): @@ -134,86 +60,7 @@ def test_series_construct_from_list(): # BigQuery DataFrame default indices use nullable Int64 always pd_result.index = pd_result.index.astype("Int64") - bigframes.testing.utils.assert_series_equal(bf_result, pd_result) - - -def test_series_construct_reindex(): - bf_result = series.Series( - series.Series({1: 10, 2: 30, 3: 30}), index=[3, 2], dtype="Int64" - ).to_pandas() - pd_result = pd.Series(pd.Series({1: 10, 2: 30, 3: 30}), index=[3, 2], dtype="Int64") - - # BigQuery DataFrame default indices use nullable Int64 always - pd_result.index = pd_result.index.astype("Int64") - bigframes.testing.utils.assert_series_equal(bf_result, pd_result) - - -def test_series_construct_from_list_w_index(): - bf_result = series.Series( - [1, 1, 2, 3, 5, 8, 13], index=[10, 20, 30, 40, 50, 60, 70], dtype="Int64" - ).to_pandas() - pd_result = pd.Series( - [1, 1, 2, 3, 5, 8, 13], index=[10, 20, 30, 40, 50, 60, 70], dtype="Int64" - ) - - # BigQuery DataFrame default indices use nullable Int64 always - pd_result.index = pd_result.index.astype("Int64") - - bigframes.testing.utils.assert_series_equal(bf_result, pd_result) - - -def test_series_construct_empty(session: bigframes.Session): - bf_series: series.Series = series.Series(session=session) - pd_series: pd.Series = pd.Series() - - bf_result = bf_series.empty - pd_result = pd_series.empty - - assert pd_result - assert bf_result == pd_result - - -def test_series_construct_scalar_no_index(): - bf_result = series.Series("hello world", dtype="string[pyarrow]").to_pandas() - pd_result = pd.Series("hello world", dtype="string[pyarrow]") - - # BigQuery DataFrame default indices use nullable Int64 always - pd_result.index = pd_result.index.astype("Int64") - - bigframes.testing.utils.assert_series_equal(bf_result, pd_result) - - -def test_series_construct_scalar_w_index(): - bf_result = series.Series( - "hello world", dtype="string[pyarrow]", index=[0, 2, 1] - ).to_pandas() - pd_result = pd.Series("hello world", dtype="string[pyarrow]", index=[0, 2, 1]) - - # BigQuery DataFrame default indices use nullable Int64 always - pd_result.index = pd_result.index.astype("Int64") - - bigframes.testing.utils.assert_series_equal(bf_result, pd_result) - - -def test_series_construct_nan(): - bf_result = series.Series(numpy.nan).to_pandas() - pd_result = pd.Series(numpy.nan) - - pd_result.index = pd_result.index.astype("Int64") - pd_result = pd_result.astype("Float64") - - bigframes.testing.utils.assert_series_equal(bf_result, pd_result) - - -def test_series_construct_scalar_w_bf_index(): - bf_result = series.Series( - "hello", index=bigframes.pandas.Index([1, 2, 3]) - ).to_pandas() - pd_result = pd.Series("hello", index=pd.Index([1, 2, 3], dtype="Int64")) - - pd_result = pd_result.astype("string[pyarrow]") - - bigframes.testing.utils.assert_series_equal(bf_result, pd_result) + pd.testing.assert_series_equal(bf_result, pd_result) def test_series_construct_from_list_escaped_strings(): @@ -229,198 +76,7 @@ def test_series_construct_from_list_escaped_strings(): # BigQuery DataFrame default indices use nullable Int64 always pd_result.index = pd_result.index.astype("Int64") - bigframes.testing.utils.assert_series_equal(bf_result.to_pandas(), pd_result) - - -def test_series_construct_geodata(): - pd_series = pd.Series( - [ - shapely.geometry.Point(1, 1), - shapely.geometry.Point(2, 2), - shapely.geometry.Point(3, 3), - ], - dtype=gpd.array.GeometryDtype(), - ) - - series = bigframes.pandas.Series(pd_series) - - bigframes.testing.utils.assert_series_equal( - pd_series, series.to_pandas(), check_index_type=False - ) - - -@pytest.mark.parametrize( - ("dtype"), - [ - pytest.param(pd.Int64Dtype(), id="int"), - pytest.param(pd.Float64Dtype(), id="float"), - pytest.param(pd.StringDtype(storage="pyarrow"), id="string"), - ], -) -def test_series_construct_w_dtype(dtype): - data = [1, 2, 3] - expected = pd.Series(data, dtype=dtype) - expected.index = expected.index.astype("Int64") - series = bigframes.pandas.Series(data, dtype=dtype) - bigframes.testing.utils.assert_series_equal(series.to_pandas(), expected) - - -def test_series_construct_w_dtype_for_struct(): - # The data shows the struct fields are disordered and correctly handled during - # construction. - data = [ - {"a": 1, "c": "pandas", "b": dt.datetime(2020, 1, 20, 20, 20, 20, 20)}, - {"a": 2, "c": "pandas", "b": dt.datetime(2019, 1, 20, 20, 20, 20, 20)}, - {"a": 1, "c": "numpy", "b": None}, - ] - dtype = pd.ArrowDtype( - pa.struct([("a", pa.int64()), ("c", pa.string()), ("b", pa.timestamp("us"))]) - ) - series = bigframes.pandas.Series(data, dtype=dtype) - expected = pd.Series(data, dtype=dtype) - expected.index = expected.index.astype("Int64") - bigframes.testing.utils.assert_series_equal(series.to_pandas(), expected) - - -def test_series_construct_w_dtype_for_array_string(): - data = [["1", "2", "3"], [], ["4", "5"]] - dtype = pd.ArrowDtype(pa.list_(pa.string())) - series = bigframes.pandas.Series(data, dtype=dtype) - expected = pd.Series(data, dtype=dtype) - expected.index = expected.index.astype("Int64") - - # Skip dtype check due to internal issue b/321013333. This issue causes array types - # to be converted to the `object` dtype when calling `to_pandas()`, resulting in - # a mismatch with the expected Pandas type. - if bigframes.features.PANDAS_VERSIONS.is_arrow_list_dtype_usable: - check_dtype = True - else: - check_dtype = False - - bigframes.testing.utils.assert_series_equal( - series.to_pandas(), expected, check_dtype=check_dtype - ) - - -def test_series_construct_w_dtype_for_array_struct(): - data = [[{"a": 1, "c": "aa"}, {"a": 2, "c": "bb"}], [], [{"a": 3, "c": "cc"}]] - dtype = pd.ArrowDtype(pa.list_(pa.struct([("a", pa.int64()), ("c", pa.string())]))) - series = bigframes.pandas.Series(data, dtype=dtype) - expected = pd.Series(data, dtype=dtype) - expected.index = expected.index.astype("Int64") - - # Skip dtype check due to internal issue b/321013333. This issue causes array types - # to be converted to the `object` dtype when calling `to_pandas()`, resulting in - # a mismatch with the expected Pandas type. - if bigframes.features.PANDAS_VERSIONS.is_arrow_list_dtype_usable: - check_dtype = True - else: - check_dtype = False - - bigframes.testing.utils.assert_series_equal( - series.to_pandas(), expected, check_dtype=check_dtype - ) - - -def test_series_construct_local_unordered_has_sequential_index(unordered_session): - series = bigframes.pandas.Series( - ["Sun", "Mon", "Tues", "Wed", "Thurs", "Fri", "Sat"], session=unordered_session - ) - expected: pd.Index = pd.Index([0, 1, 2, 3, 4, 5, 6], dtype=pd.Int64Dtype()) - bigframes.testing.utils.assert_index_equal(series.index.to_pandas(), expected) - - -@pytest.mark.parametrize( - ("json_type"), - [ - pytest.param(dtypes.JSON_DTYPE), - pytest.param("json"), - ], -) -def test_series_construct_w_json_dtype(json_type): - data = [ - "1", - '"str"', - "false", - '["a", {"b": 1}, null]', - None, - '{"a": {"b": [1, 2, 3], "c": true}}', - ] - s = bigframes.pandas.Series(data, dtype=json_type) - - assert s.dtype == dtypes.JSON_DTYPE - assert s[0] == "1" - assert s[1] == '"str"' - assert s[2] == "false" - assert s[3] == '["a",{"b":1},null]' - assert pd.isna(s[4]) - assert s[5] == '{"a":{"b":[1,2,3],"c":true}}' - - -def test_series_construct_w_nested_json_dtype(): - list_data = [ - [{"key": "1"}], - [{"key": None}], - [{"key": '["1","3","5"]'}], - [{"key": '{"a":1,"b":["x","y"],"c":{"x":[],"z":false}}'}], - ] - pa_array = pa.array(list_data, type=pa.list_(pa.struct([("key", pa.string())]))) - - db_json_arrow_dtype = db_dtypes.JSONArrowType() - s = bigframes.pandas.Series( - pd.arrays.ArrowExtensionArray(pa_array), # type: ignore - dtype=pd.ArrowDtype( - pa.list_(pa.struct([("key", db_json_arrow_dtype)])), - ), - ) - - assert s[0][0]["key"] == "1" - assert not s[1][0]["key"] - assert s[2][0]["key"] == '["1","3","5"]' - assert s[3][0]["key"] == '{"a":1,"b":["x","y"],"c":{"x":[],"z":false}}' - - # Test with pyarrow.json_(pa.string()) if available. - if hasattr(pa, "JsonType"): - pyarrow_json_dtype = pa.json_(pa.string()) - s2 = bigframes.pandas.Series( - pd.arrays.ArrowExtensionArray(pa_array), # type: ignore - dtype=pd.ArrowDtype( - pa.list_(pa.struct([("key", pyarrow_json_dtype)])), - ), - ) - - bigframes.testing.utils.assert_series_equal(s.to_pandas(), s2.to_pandas()) - - -def test_series_keys(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - bf_result = scalars_df["int64_col"].keys().to_pandas() - pd_result = scalars_pandas_df["int64_col"].keys() - bigframes.testing.utils.assert_index_equal(bf_result, pd_result) - - -@pytest.mark.parametrize( - ["data", "index"], - [ - (["a", "b", "c"], None), - ([1, 2, 3], ["a", "b", "c"]), - ([1, 2, None], ["a", "b", "c"]), - ([1, 2, 3], [pd.NA, "b", "c"]), - ([numpy.nan, 2, 3], ["a", "b", "c"]), - ], -) -def test_series_items(data, index): - bf_series = series.Series(data, index=index) - pd_series = pd.Series(data, index=index) - - for (bf_index, bf_value), (pd_index, pd_value) in zip( - bf_series.items(), pd_series.items() - ): - # TODO(jialuo): Remove the if conditions after b/373699458 is addressed. - if not pd.isna(bf_index) or not pd.isna(pd_index): - assert bf_index == pd_index - if not pd.isna(bf_value) or not pd.isna(pd_value): - assert bf_value == pd_value + pd.testing.assert_series_equal(bf_result.to_pandas(), pd_result) @pytest.mark.parametrize( @@ -428,14 +84,14 @@ def test_series_items(data, index): [ ("bool_col", pd.BooleanDtype()), # TODO(swast): Use a more efficient type. - ("bytes_col", pd.ArrowDtype(pa.binary())), + ("bytes_col", numpy.dtype("object")), ("date_col", pd.ArrowDtype(pa.date32())), ("datetime_col", pd.ArrowDtype(pa.timestamp("us"))), ("float64_col", pd.Float64Dtype()), ("geography_col", gpd.array.GeometryDtype()), ("int64_col", pd.Int64Dtype()), # TODO(swast): Use a more efficient type. - ("numeric_col", pd.ArrowDtype(pa.decimal128(38, 9))), + ("numeric_col", numpy.dtype("object")), ("int64_too", pd.Int64Dtype()), ("string_col", pd.StringDtype(storage="pyarrow")), ("time_col", pd.ArrowDtype(pa.time64("us"))), @@ -450,35 +106,12 @@ def test_get_column(scalars_dfs, col_name, expected_dtype): assert series_pandas.shape[0] == scalars_pandas_df.shape[0] -def test_get_column_w_json(json_df, json_pandas_df): - series = json_df["json_col"] - series_pandas = series.to_pandas() - assert series.dtype == pd.ArrowDtype(db_dtypes.JSONArrowType()) - assert series_pandas.shape[0] == json_pandas_df.shape[0] - - def test_series_get_column_default(scalars_dfs): scalars_df, _ = scalars_dfs result = scalars_df.get(123123123123123, "default_val") assert result == "default_val" -@pytest.mark.parametrize( - ("key",), - [ - ("hello",), - (2,), - ("int64_col",), - (None,), - ], -) -def test_series_contains(scalars_df_index, scalars_pandas_df_index, key): - bf_result = key in scalars_df_index["int64_col"] - pd_result = key in scalars_pandas_df_index["int64_col"] - - assert bf_result == pd_result - - def test_series_equals_identical(scalars_df_index, scalars_pandas_df_index): bf_result = scalars_df_index.int64_col.equals(scalars_df_index.int64_col) pd_result = scalars_pandas_df_index.int64_col.equals( @@ -541,23 +174,13 @@ def test_series___getitem__(scalars_dfs, index_col, key): scalars_pandas_df = scalars_pandas_df.set_index(index_col, drop=False) bf_result = scalars_df[col_name][key] pd_result = scalars_pandas_df[col_name][key] - bigframes.testing.utils.assert_series_equal(bf_result.to_pandas(), pd_result) + pd.testing.assert_series_equal(bf_result.to_pandas(), pd_result) -@pytest.mark.parametrize( - ("key",), - ( - (-2,), - (-1,), - (0,), - (1,), - ), -) -def test_series___getitem___with_int_key(scalars_dfs, key): - if pd.__version__.startswith("3."): - pytest.skip("pandas 3.0 dropped getitem with int key") +def test_series___getitem___with_int_key(scalars_dfs): col_name = "int64_too" index_col = "string_col" + key = 2 scalars_df, scalars_pandas_df = scalars_dfs scalars_df = scalars_df.set_index(index_col, drop=False) scalars_pandas_df = scalars_pandas_df.set_index(index_col, drop=False) @@ -575,69 +198,6 @@ def test_series___getitem___with_default_index(scalars_dfs): assert bf_result == pd_result -@pytest.mark.parametrize( - ("index_col", "key", "value"), - ( - ("int64_too", 2, "new_string_value"), - ("string_col", "Hello, World!", "updated_value"), - ("int64_too", 0, None), - ), -) -def test_series___setitem__(scalars_dfs, index_col, key, value): - col_name = "string_col" - scalars_df, scalars_pandas_df = scalars_dfs - scalars_df = scalars_df.set_index(index_col, drop=False) - scalars_pandas_df = scalars_pandas_df.set_index(index_col, drop=False) - - bf_series = scalars_df[col_name] - pd_series = scalars_pandas_df[col_name].copy() - - bf_series[key] = value - pd_series[key] = value - - bigframes.testing.utils.assert_series_equal(bf_series.to_pandas(), pd_series) - - -@pytest.mark.parametrize( - ("key", "value"), - ( - (0, 999), - (1, 888), - (0, None), - (-2345, 777), - ), -) -def test_series___setitem___with_int_key_numeric(scalars_dfs, key, value): - col_name = "int64_col" - index_col = "int64_too" - scalars_df, scalars_pandas_df = scalars_dfs - scalars_df = scalars_df.set_index(index_col, drop=False) - scalars_pandas_df = scalars_pandas_df.set_index(index_col, drop=False) - - bf_series = scalars_df[col_name] - pd_series = scalars_pandas_df[col_name].copy() - - bf_series[key] = value - pd_series[key] = value - - bigframes.testing.utils.assert_series_equal(bf_series.to_pandas(), pd_series) - - -def test_series___setitem___with_default_index(scalars_dfs): - col_name = "float64_col" - key = 2 - value = 123.456 - scalars_df, scalars_pandas_df = scalars_dfs - - bf_series = scalars_df[col_name] - pd_series = scalars_pandas_df[col_name].copy() - - bf_series[key] = value - pd_series[key] = value - - assert bf_series.to_pandas().iloc[key] == pd_series.iloc[key] - - @pytest.mark.parametrize( ("col_name",), ( @@ -650,52 +210,7 @@ def test_abs(scalars_dfs, col_name): bf_result = scalars_df[col_name].abs().to_pandas() pd_result = scalars_pandas_df[col_name].abs() - assert_series_equal(pd_result, bf_result) - - -@pytest.mark.parametrize( - ("col_name",), - ( - ("float64_col",), - ("int64_too",), - ), -) -def test_series_pos(scalars_dfs, col_name): - scalars_df, scalars_pandas_df = scalars_dfs - bf_result = (+scalars_df[col_name]).to_pandas() - pd_result = +scalars_pandas_df[col_name] - - assert_series_equal(pd_result, bf_result) - - -@pytest.mark.parametrize( - ("col_name",), - ( - ("float64_col",), - ("int64_too",), - ), -) -def test_series_neg(scalars_dfs, col_name): - scalars_df, scalars_pandas_df = scalars_dfs - bf_result = (-scalars_df[col_name]).to_pandas() - pd_result = -scalars_pandas_df[col_name] - - assert_series_equal(pd_result, bf_result) - - -@pytest.mark.parametrize( - ("col_name",), - ( - ("bool_col",), - ("int64_col",), - ), -) -def test_series_invert(scalars_dfs, col_name): - scalars_df, scalars_pandas_df = scalars_dfs - bf_result = (~scalars_df[col_name]).to_pandas() - pd_result = ~scalars_pandas_df[col_name] - - assert_series_equal(pd_result, bf_result) + assert_series_equal_ignoring_order(pd_result, bf_result) def test_fillna(scalars_dfs): @@ -703,7 +218,7 @@ def test_fillna(scalars_dfs): col_name = "string_col" bf_result = scalars_df[col_name].fillna("Missing").to_pandas() pd_result = scalars_pandas_df[col_name].fillna("Missing") - assert_series_equal( + assert_series_equal_ignoring_order( pd_result, bf_result, ) @@ -717,7 +232,7 @@ def test_series_replace_scalar_scalar(scalars_dfs): ) pd_result = scalars_pandas_df[col_name].replace("Hello, World!", "Howdy, Planet!") - bigframes.testing.utils.assert_series_equal( + pd.testing.assert_series_equal( pd_result, bf_result, ) @@ -733,7 +248,7 @@ def test_series_replace_regex_scalar(scalars_dfs): "^H.l", "Howdy, Planet!", regex=True ) - bigframes.testing.utils.assert_series_equal( + pd.testing.assert_series_equal( pd_result, bf_result, ) @@ -751,82 +266,35 @@ def test_series_replace_list_scalar(scalars_dfs): ["Hello, World!", "T"], "Howdy, Planet!" ) - bigframes.testing.utils.assert_series_equal( - pd_result, - bf_result, - ) - - -def test_series_replace_nans_with_pd_na(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - col_name = "string_col" - bf_result = scalars_df[col_name].replace({pd.NA: "UNKNOWN"}).to_pandas() - pd_result = scalars_pandas_df[col_name].replace({pd.NA: "UNKNOWN"}) - - bigframes.testing.utils.assert_series_equal( - pd_result, - bf_result, - ) - - -@pytest.mark.parametrize( - ("replacement_dict",), - ( - ({"Hello, World!": "Howdy, Planet!", "T": "R"},), - ({},), - ({0: "Hello, World!"},), - ), - ids=[ - "non-empty", - "empty", - "off-type", - ], -) -def test_series_replace_dict(scalars_dfs, replacement_dict): - scalars_df, scalars_pandas_df = scalars_dfs - col_name = "string_col" - bf_result = scalars_df[col_name].replace(replacement_dict).to_pandas() - pd_result = scalars_pandas_df[col_name].replace(replacement_dict) - - bigframes.testing.utils.assert_series_equal( + pd.testing.assert_series_equal( pd_result, bf_result, ) @pytest.mark.parametrize( - ("method",), + ("values",), ( - ("linear",), - ("values",), - ("slinear",), - ("nearest",), - ("zero",), - ("pad",), + ([None, 1, 2, None, None, 16, None],), + ([None, None, 3.6, None],), + ([403.2, None, 352.1, None, None, 111.9],), ), ) -def test_series_interpolate(method): - pytest.importorskip("scipy") - if method == "pad" and pd.__version__.startswith("3."): - pytest.skip("pandas 3.0 dropped method='pad'") - - values = [None, 1, 2, None, None, 16, None] - index = [-3.2, 11.4, 3.56, 4, 4.32, 5.55, 76.8] - pd_series = pd.Series(values, index) +def test_series_interpolate(values): + pd_series = pd.Series(values) bf_series = series.Series(pd_series) # Pandas can only interpolate on "float64" columns # https://github.com/pandas-dev/pandas/issues/40252 - pd_result = pd_series.astype("float64").interpolate(method=method) - bf_result = bf_series.interpolate(method=method).to_pandas() + pd_result = pd_series.astype("float64").interpolate() + bf_result = bf_series.interpolate().to_pandas() # pd uses non-null types, while bf uses nullable types - assert_series_equal( + pd.testing.assert_series_equal( pd_result, bf_result, check_index_type=False, check_dtype=False, - nulls_are_nan=True, ) @@ -844,37 +312,18 @@ def test_series_dropna(scalars_dfs, ignore_index): col_name = "string_col" bf_result = scalars_df[col_name].dropna(ignore_index=ignore_index).to_pandas() pd_result = scalars_pandas_df[col_name].dropna(ignore_index=ignore_index) - bigframes.testing.utils.assert_series_equal( - pd_result, bf_result, check_index_type=False - ) + pd.testing.assert_series_equal(pd_result, bf_result, check_index_type=False) -@pytest.mark.parametrize( - ("agg",), - ( - ("sum",), - ("size",), - ), -) -def test_series_agg_single_string(scalars_dfs, agg): +def test_series_agg_single_string(scalars_dfs): scalars_df, scalars_pandas_df = scalars_dfs - bf_result = scalars_df["int64_col"].agg(agg) - pd_result = scalars_pandas_df["int64_col"].agg(agg) + bf_result = scalars_df["int64_col"].agg("sum") + pd_result = scalars_pandas_df["int64_col"].agg("sum") assert math.isclose(pd_result, bf_result) def test_series_agg_multi_string(scalars_dfs): - aggregations = [ - "sum", - "mean", - "std", - "var", - "min", - "max", - "nunique", - "count", - "size", - ] + aggregations = ["sum", "mean", "std", "var", "min", "max", "nunique", "count"] scalars_df, scalars_pandas_df = scalars_dfs bf_result = scalars_df["int64_col"].agg(aggregations).to_pandas() pd_result = scalars_pandas_df["int64_col"].agg(aggregations) @@ -882,9 +331,7 @@ def test_series_agg_multi_string(scalars_dfs): # Pandas may produce narrower numeric types, but bigframes always produces Float64 pd_result = pd_result.astype("Float64") - bigframes.testing.utils.assert_series_equal( - pd_result, bf_result, check_index_type=False - ) + pd.testing.assert_series_equal(pd_result, bf_result, check_index_type=False) @pytest.mark.parametrize( @@ -1001,7 +448,7 @@ def test_mode_stat(scalars_df_index, scalars_pandas_df_index, col_name): ## Mode implicitly resets index, and bigframes default indices use nullable Int64 pd_result.index = pd_result.index.astype("Int64") - bigframes.testing.utils.assert_series_equal( + pd.testing.assert_series_equal( bf_result, pd_result, ) @@ -1044,8 +491,7 @@ def test_series_int_int_operators_scalar( bf_result = maybe_reversed_op(scalars_df["int64_col"], other_scalar).to_pandas() pd_result = maybe_reversed_op(scalars_pandas_df["int64_col"], other_scalar) - # don't check dtype, as pandas is a bit unstable here across versions, esp floordiv - assert_series_equal(pd_result, bf_result, check_dtype=False) + assert_series_equal_ignoring_order(pd_result, bf_result) def test_series_pow_scalar(scalars_dfs): @@ -1054,7 +500,7 @@ def test_series_pow_scalar(scalars_dfs): bf_result = (scalars_df["int64_col"] ** 2).to_pandas() pd_result = scalars_pandas_df["int64_col"] ** 2 - assert_series_equal(pd_result, bf_result) + assert_series_equal_ignoring_order(pd_result, bf_result) def test_series_pow_scalar_reverse(scalars_dfs): @@ -1063,7 +509,7 @@ def test_series_pow_scalar_reverse(scalars_dfs): bf_result = (0.8 ** scalars_df["int64_col"]).to_pandas() pd_result = 0.8 ** scalars_pandas_df["int64_col"] - assert_series_equal(pd_result, bf_result) + assert_series_equal_ignoring_order(pd_result, bf_result) @pytest.mark.parametrize( @@ -1071,12 +517,10 @@ def test_series_pow_scalar_reverse(scalars_dfs): [ (lambda x, y: x & y), (lambda x, y: x | y), - (lambda x, y: x ^ y), ], ids=[ "and", "or", - "xor", ], ) @pytest.mark.parametrize(("other_scalar"), [True, False, pd.NA]) @@ -1091,7 +535,7 @@ def test_series_bool_bool_operators_scalar( bf_result = maybe_reversed_op(scalars_df["bool_col"], other_scalar).to_pandas() pd_result = maybe_reversed_op(scalars_pandas_df["bool_col"], other_scalar) - assert_series_equal(pd_result.astype(pd.BooleanDtype()), bf_result) + assert_series_equal_ignoring_order(pd_result.astype(pd.BooleanDtype()), bf_result) @pytest.mark.parametrize( @@ -1109,7 +553,6 @@ def test_series_bool_bool_operators_scalar( (lambda x, y: x // y), (lambda x, y: x & y), (lambda x, y: x | y), - (lambda x, y: x ^ y), ], ids=[ "add", @@ -1124,14 +567,13 @@ def test_series_bool_bool_operators_scalar( "floordivide", "bitwise_and", "bitwise_or", - "bitwise_xor", ], ) def test_series_int_int_operators_series(scalars_dfs, operator): scalars_df, scalars_pandas_df = scalars_dfs bf_result = operator(scalars_df["int64_col"], scalars_df["int64_too"]).to_pandas() pd_result = operator(scalars_pandas_df["int64_col"], scalars_pandas_df["int64_too"]) - assert_series_equal(pd_result, bf_result) + assert_series_equal_ignoring_order(pd_result, bf_result) @pytest.mark.parametrize( @@ -1169,12 +611,12 @@ def test_mods(scalars_dfs, col_x, col_y, method): else: bf_result = bf_series.astype("Float64").to_pandas() pd_result = getattr(scalars_pandas_df[col_x], method)(scalars_pandas_df[col_y]) - bigframes.testing.utils.assert_series_equal(pd_result, bf_result) + pd.testing.assert_series_equal(pd_result, bf_result) # We work around a pandas bug that doesn't handle correlating nullable dtypes by doing this # manually with dumb self-correlation instead of parameterized as test_mods is above. -def test_series_corr(scalars_dfs): +def test_corr(scalars_dfs): scalars_df, scalars_pandas_df = scalars_dfs bf_result = scalars_df["int64_too"].corr(scalars_df["int64_too"]) pd_result = ( @@ -1185,26 +627,6 @@ def test_series_corr(scalars_dfs): assert math.isclose(pd_result, bf_result) -def test_series_autocorr(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 - bf_result = scalars_df["float64_col"].autocorr(2) - pd_result = scalars_pandas_df["float64_col"].autocorr(2) - assert math.isclose(pd_result, bf_result) - - -def test_series_cov(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - bf_result = scalars_df["int64_too"].cov(scalars_df["int64_too"]) - pd_result = ( - scalars_pandas_df["int64_too"] - .astype("int64") - .cov(scalars_pandas_df["int64_too"].astype("int64")) - ) - assert math.isclose(pd_result, bf_result) - - @pytest.mark.parametrize( ("col_x",), [ @@ -1233,12 +655,19 @@ def test_divmods_series(scalars_dfs, col_x, col_y, method): scalars_pandas_df[col_y] ) # BigQuery's mod functions return NUMERIC values for non-INT64 inputs. - bigframes.testing.utils.assert_series_equal( - pd_div_result, bf_div_result.to_pandas(), check_dtype=False - ) - bigframes.testing.utils.assert_series_equal( - pd_mod_result, bf_mod_result.to_pandas(), check_dtype=False - ) + if bf_div_result.dtype == pd.Int64Dtype(): + pd.testing.assert_series_equal(pd_div_result, bf_div_result.to_pandas()) + else: + pd.testing.assert_series_equal( + pd_div_result, bf_div_result.astype("Float64").to_pandas() + ) + + if bf_mod_result.dtype == pd.Int64Dtype(): + pd.testing.assert_series_equal(pd_mod_result, bf_mod_result.to_pandas()) + else: + pd.testing.assert_series_equal( + pd_mod_result, bf_mod_result.astype("Float64").to_pandas() + ) @pytest.mark.parametrize( @@ -1268,20 +697,16 @@ def test_divmods_scalars(scalars_dfs, col_x, other, method): pd_div_result, pd_mod_result = getattr(scalars_pandas_df[col_x], method)(other) # BigQuery's mod functions return NUMERIC values for non-INT64 inputs. if bf_div_result.dtype == pd.Int64Dtype(): - bigframes.testing.utils.assert_series_equal( - pd_div_result, bf_div_result.to_pandas(), check_dtype=False - ) + pd.testing.assert_series_equal(pd_div_result, bf_div_result.to_pandas()) else: - bigframes.testing.utils.assert_series_equal( + pd.testing.assert_series_equal( pd_div_result, bf_div_result.astype("Float64").to_pandas() ) if bf_mod_result.dtype == pd.Int64Dtype(): - bigframes.testing.utils.assert_series_equal( - pd_div_result, bf_div_result.to_pandas(), check_dtype=False - ) + pd.testing.assert_series_equal(pd_mod_result, bf_mod_result.to_pandas()) else: - bigframes.testing.utils.assert_series_equal( + pd.testing.assert_series_equal( pd_mod_result, bf_mod_result.astype("Float64").to_pandas() ) @@ -1298,7 +723,7 @@ def test_series_add_scalar(scalars_dfs, other): bf_result = (scalars_df["float64_col"] + other).to_pandas() pd_result = scalars_pandas_df["float64_col"] + other - assert_series_equal(pd_result, bf_result) + assert_series_equal_ignoring_order(pd_result, bf_result) @pytest.mark.parametrize( @@ -1314,7 +739,7 @@ def test_series_add_bigframes_series(scalars_dfs, left_col, right_col): bf_result = (scalars_df[left_col] + scalars_df[right_col]).to_pandas() pd_result = scalars_pandas_df[left_col] + scalars_pandas_df[right_col] - assert_series_equal(pd_result, bf_result) + assert_series_equal_ignoring_order(pd_result, bf_result) @pytest.mark.parametrize( @@ -1336,7 +761,7 @@ def test_series_add_bigframes_series_nested( scalars_pandas_df[left_col] + scalars_pandas_df[right_col] ) + scalars_pandas_df[righter_col] - assert_series_equal(pd_result, bf_result) + assert_series_equal_ignoring_order(pd_result, bf_result) def test_series_add_different_table_default_index( @@ -1354,9 +779,7 @@ def test_series_add_different_table_default_index( + scalars_df_2_default_index["float64_col"].to_pandas() ) # TODO(swast): Can remove sort_index() when there's default ordering. - bigframes.testing.utils.assert_series_equal( - bf_result.sort_index(), pd_result.sort_index() - ) + pd.testing.assert_series_equal(bf_result.sort_index(), pd_result.sort_index()) def test_series_add_different_table_with_index( @@ -1367,7 +790,7 @@ def test_series_add_different_table_with_index( # When index values are unique, we can emulate with values from the same # DataFrame. pd_result = scalars_pandas_df["float64_col"] + scalars_pandas_df["int64_col"] - bigframes.testing.utils.assert_series_equal(bf_result.to_pandas(), pd_result) + pd.testing.assert_series_equal(bf_result.to_pandas(), pd_result) def test_reset_index_drop(scalars_df_index, scalars_pandas_df_index): @@ -1386,45 +809,7 @@ def test_reset_index_drop(scalars_df_index, scalars_pandas_df_index): # BigQuery DataFrames default indices use nullable Int64 always pd_result.index = pd_result.index.astype("Int64") - bigframes.testing.utils.assert_series_equal(bf_result.to_pandas(), pd_result) - - -def test_series_reset_index_allow_duplicates(scalars_df_index, scalars_pandas_df_index): - bf_series = scalars_df_index["int64_col"].copy() - bf_series.index.name = "int64_col" - df = bf_series.reset_index(allow_duplicates=True, drop=False) - assert df.index.name is None - - bf_result = df.to_pandas() - - pd_series = scalars_pandas_df_index["int64_col"].copy() - pd_series.index.name = "int64_col" - pd_result = pd_series.reset_index(allow_duplicates=True, drop=False) - - # Pandas uses int64 instead of Int64 (nullable) dtype. - pd_result.index = pd_result.index.astype(pd.Int64Dtype()) - - # reset_index should maintain the original ordering. - bigframes.testing.utils.assert_frame_equal(bf_result, pd_result) - - -def test_series_reset_index_duplicates_error(scalars_df_index): - scalars_df_index = scalars_df_index["int64_col"].copy() - scalars_df_index.index.name = "int64_col" - with pytest.raises(ValueError): - scalars_df_index.reset_index(allow_duplicates=False, drop=False) - - -def test_series_reset_index_inplace(scalars_df_index, scalars_pandas_df_index): - bf_result = scalars_df_index.sort_index(ascending=False)["float64_col"] - bf_result.reset_index(drop=True, inplace=True) - pd_result = scalars_pandas_df_index.sort_index(ascending=False)["float64_col"] - pd_result.reset_index(drop=True, inplace=True) - - # BigQuery DataFrames default indices use nullable Int64 always - pd_result.index = pd_result.index.astype("Int64") - - bigframes.testing.utils.assert_series_equal(bf_result.to_pandas(), pd_result) + pd.testing.assert_series_equal(bf_result.to_pandas(), pd_result) @pytest.mark.parametrize( @@ -1451,7 +836,18 @@ def test_reset_index_no_drop(scalars_df_index, scalars_pandas_df_index, name): # BigQuery DataFrames default indices use nullable Int64 always pd_result.index = pd_result.index.astype("Int64") - bigframes.testing.utils.assert_frame_equal(bf_result.to_pandas(), pd_result) + pd.testing.assert_frame_equal(bf_result.to_pandas(), pd_result) + + +def test_series_add_pandas_series_not_implemented(scalars_dfs): + scalars_df, _ = scalars_dfs + with pytest.raises(NotImplementedError): + ( + scalars_df["float64_col"] + + pd.Series( + [1, 1, 1, 1], + ) + ).to_pandas() def test_copy(scalars_df_index, scalars_pandas_df_index): @@ -1468,7 +864,7 @@ def test_copy(scalars_df_index, scalars_pandas_df_index): pd_series.loc[0] = 3.4 assert bf_copy.to_pandas().loc[0] != bf_series.to_pandas().loc[0] - bigframes.testing.utils.assert_series_equal(bf_copy.to_pandas(), pd_copy) + pd.testing.assert_series_equal(bf_copy.to_pandas(), pd_copy) def test_isin_raise_error(scalars_df_index, scalars_pandas_df_index): @@ -1509,119 +905,12 @@ def test_isin(scalars_dfs, col_name, test_set): scalars_df, scalars_pandas_df = scalars_dfs bf_result = scalars_df[col_name].isin(test_set).to_pandas() pd_result = scalars_pandas_df[col_name].isin(test_set).astype("boolean") - bigframes.testing.utils.assert_series_equal( - pd_result, - bf_result, - ) - - -@pytest.mark.parametrize( - ( - "col_name", - "test_set", - ), - [ - ( - "int64_col", - [314159, 2.0, 3, pd.NA], - ), - ( - "int64_col", - [2, 55555, 4], - ), - ( - "float64_col", - [-123.456, 1.25, pd.NA], - ), - ( - "int64_too", - [1, 2, pd.NA], - ), - ( - "string_col", - ["Hello, World!", "Hi", "こんにちは"], - ), - ], -) -def test_isin_bigframes_values(scalars_dfs, col_name, test_set, session): - scalars_df, scalars_pandas_df = scalars_dfs - bf_result = ( - scalars_df[col_name].isin(series.Series(test_set, session=session)).to_pandas() - ) - pd_result = scalars_pandas_df[col_name].isin(test_set).astype("boolean") - bigframes.testing.utils.assert_series_equal( + pd.testing.assert_series_equal( pd_result, bf_result, ) -def test_isin_bigframes_index(scalars_dfs, session): - scalars_df, scalars_pandas_df = scalars_dfs - bf_result = ( - scalars_df["string_col"] - .isin( - bigframes.pandas.Index( - ["Hello, World!", "Hi", "こんにちは"], session=session - ) - ) - .to_pandas() - ) - pd_result = ( - scalars_pandas_df["string_col"] - .isin(pd.Index(["Hello, World!", "Hi", "こんにちは"])) - .astype("boolean") - ) - bigframes.testing.utils.assert_series_equal( - pd_result, - bf_result, - ) - - -@pytest.mark.parametrize( - ( - "col_name", - "test_set", - ), - [ - ( - "int64_col", - [314159, 2.0, 3, pd.NA], - ), - ( - "int64_col", - [2, 55555, 4], - ), - ( - "float64_col", - [-123.456, 1.25, pd.NA], - ), - ( - "int64_too", - [1, 2, pd.NA], - ), - ( - "string_col", - ["Hello, World!", "Hi", "こんにちは"], - ), - ], -) -def test_isin_bigframes_values_as_predicate( - scalars_dfs_maybe_ordered, col_name, test_set -): - scalars_df, scalars_pandas_df = scalars_dfs_maybe_ordered - bf_predicate = scalars_df[col_name].isin( - series.Series(test_set, session=scalars_df._session) - ) - bf_result = scalars_df[bf_predicate].to_pandas() - pd_predicate = scalars_pandas_df[col_name].isin(test_set) - pd_result = scalars_pandas_df[pd_predicate] - - bigframes.testing.utils.assert_frame_equal( - pd_result.reset_index(), - bf_result.reset_index(), - ) - - def test_isnull(scalars_dfs): scalars_df, scalars_pandas_df = scalars_dfs col_name = "float64_col" @@ -1630,7 +919,7 @@ def test_isnull(scalars_dfs): # One of dtype mismatches to be documented. Here, the `bf_series.dtype` is `BooleanDtype` but # the `pd_series.dtype` is `bool`. - assert_series_equal(pd_series.astype(pd.BooleanDtype()), bf_series) + assert_series_equal_ignoring_order(pd_series.astype(pd.BooleanDtype()), bf_series) def test_notnull(scalars_dfs): @@ -1641,7 +930,7 @@ def test_notnull(scalars_dfs): # One of dtype mismatches to be documented. Here, the `bf_series.dtype` is `BooleanDtype` but # the `pd_series.dtype` is `bool`. - assert_series_equal(pd_series.astype(pd.BooleanDtype()), bf_series) + assert_series_equal_ignoring_order(pd_series.astype(pd.BooleanDtype()), bf_series) def test_round(scalars_dfs): @@ -1650,7 +939,7 @@ def test_round(scalars_dfs): bf_result = scalars_df[col_name].round().to_pandas() pd_result = scalars_pandas_df[col_name].round() - assert_series_equal(pd_result, bf_result) + assert_series_equal_ignoring_order(pd_result, bf_result) def test_eq_scalar(scalars_dfs): @@ -1659,7 +948,7 @@ def test_eq_scalar(scalars_dfs): bf_result = scalars_df[col_name].eq(0).to_pandas() pd_result = scalars_pandas_df[col_name].eq(0) - assert_series_equal(pd_result, bf_result) + assert_series_equal_ignoring_order(pd_result, bf_result) def test_eq_wider_type_scalar(scalars_dfs): @@ -1668,7 +957,7 @@ def test_eq_wider_type_scalar(scalars_dfs): bf_result = scalars_df[col_name].eq(1.0).to_pandas() pd_result = scalars_pandas_df[col_name].eq(1.0) - assert_series_equal(pd_result, bf_result) + assert_series_equal_ignoring_order(pd_result, bf_result) def test_ne_scalar(scalars_dfs): @@ -1677,7 +966,7 @@ def test_ne_scalar(scalars_dfs): bf_result = (scalars_df[col_name] != 0).to_pandas() pd_result = scalars_pandas_df[col_name] != 0 - assert_series_equal(pd_result, bf_result) + assert_series_equal_ignoring_order(pd_result, bf_result) def test_eq_int_scalar(scalars_dfs): @@ -1686,7 +975,7 @@ def test_eq_int_scalar(scalars_dfs): bf_result = (scalars_df[col_name] == 0).to_pandas() pd_result = scalars_pandas_df[col_name] == 0 - assert_series_equal(pd_result, bf_result) + assert_series_equal_ignoring_order(pd_result, bf_result) @pytest.mark.parametrize( @@ -1705,7 +994,7 @@ def test_eq_same_type_series(scalars_dfs, col_name): # One of dtype mismatches to be documented. Here, the `bf_series.dtype` is `BooleanDtype` but # the `pd_series.dtype` is `bool`. - assert_series_equal(pd_result.astype(pd.BooleanDtype()), bf_result) + assert_series_equal_ignoring_order(pd_result.astype(pd.BooleanDtype()), bf_result) def test_loc_setitem_cell(scalars_df_index, scalars_pandas_df_index): @@ -1717,10 +1006,10 @@ def test_loc_setitem_cell(scalars_df_index, scalars_pandas_df_index): pd_series.loc[2] = "This value isn't in the test data." bf_result = bf_series.to_pandas() pd_result = pd_series - bigframes.testing.utils.assert_series_equal(bf_result, pd_result) + pd.testing.assert_series_equal(bf_result, pd_result) # Per Copy-on-Write semantics, other references to the original DataFrame # should remain unchanged. - bigframes.testing.utils.assert_series_equal(bf_original.to_pandas(), pd_original) + pd.testing.assert_series_equal(bf_original.to_pandas(), pd_original) def test_at_setitem_row_label_scalar(scalars_dfs): @@ -1731,7 +1020,7 @@ def test_at_setitem_row_label_scalar(scalars_dfs): pd_series.at[1] = 1000 bf_result = bf_series.to_pandas() pd_result = pd_series.astype("Int64") - bigframes.testing.utils.assert_series_equal(bf_result, pd_result) + pd.testing.assert_series_equal(bf_result, pd_result) def test_ne_obj_series(scalars_dfs): @@ -1742,7 +1031,7 @@ def test_ne_obj_series(scalars_dfs): # One of dtype mismatches to be documented. Here, the `bf_series.dtype` is `BooleanDtype` but # the `pd_series.dtype` is `bool`. - assert_series_equal(pd_result.astype(pd.BooleanDtype()), bf_result) + assert_series_equal_ignoring_order(pd_result.astype(pd.BooleanDtype()), bf_result) def test_indexing_using_unselected_series(scalars_dfs): @@ -1751,7 +1040,7 @@ def test_indexing_using_unselected_series(scalars_dfs): bf_result = scalars_df[col_name][scalars_df["int64_too"].eq(0)].to_pandas() pd_result = scalars_pandas_df[col_name][scalars_pandas_df["int64_too"].eq(0)] - assert_series_equal( + assert_series_equal_ignoring_order( pd_result, bf_result, ) @@ -1767,29 +1056,12 @@ def test_indexing_using_selected_series(scalars_dfs): scalars_pandas_df["string_col"].eq("Hello, World!") ] - assert_series_equal( + assert_series_equal_ignoring_order( pd_result, bf_result, ) -@pytest.mark.parametrize( - ("indices"), - [ - ([1, 3, 5]), - ([5, -3, -5, -6]), - ([-2, -4, -6]), - ], -) -def test_take(scalars_dfs, indices): - scalars_df, scalars_pandas_df = scalars_dfs - - bf_result = scalars_df.take(indices).to_pandas() - pd_result = scalars_pandas_df.take(indices) - - assert_frame_equal(bf_result, pd_result) - - def test_nested_filter(scalars_dfs): scalars_df, scalars_pandas_df = scalars_dfs string_col = scalars_df["string_col"] @@ -1806,7 +1078,7 @@ def test_nested_filter(scalars_dfs): ) # Convert from nullable bool to nonnullable bool usable as indexer pd_result = pd_string_col[pd_int64_too == 0][~pd_bool_col] - assert_series_equal( + assert_series_equal_ignoring_order( pd_result, bf_result, ) @@ -1825,7 +1097,7 @@ def test_binop_repeated_application_does_row_identity_joins(scalars_dfs): bf_result = bf_series.to_pandas() pd_result = pd_series - assert_series_equal( + assert_series_equal_ignoring_order( bf_result, pd_result, ) @@ -1847,9 +1119,10 @@ def test_binop_opposite_filters(scalars_dfs): pd_bool_col = scalars_pandas_df["bool_col"] pd_result = pd_int64_col1[pd_bool_col] + pd_int64_col2[pd_bool_col.__invert__()] - # Passes with ignore_order=False only with some dependency sets - # TODO: Determine desired behavior and make test more strict - assert_series_equal(bf_result, pd_result, ignore_order=True) + assert_series_equal_ignoring_order( + bf_result, + pd_result, + ) def test_binop_left_filtered(scalars_dfs): @@ -1864,9 +1137,10 @@ def test_binop_left_filtered(scalars_dfs): pd_bool_col = scalars_pandas_df["bool_col"] pd_result = pd_int64_col[pd_bool_col] + pd_float64_col - # Passes with ignore_order=False only with some dependency sets - # TODO: Determine desired behavior and make test more strict - assert_series_equal(bf_result, pd_result, ignore_order=True) + assert_series_equal_ignoring_order( + bf_result, + pd_result, + ) def test_binop_right_filtered(scalars_dfs): @@ -1881,94 +1155,12 @@ def test_binop_right_filtered(scalars_dfs): pd_bool_col = scalars_pandas_df["bool_col"] pd_result = pd_float64_col + pd_int64_col[pd_bool_col] - assert_series_equal( - bf_result, - pd_result, - ) - - -@pytest.mark.parametrize( - ("other",), - [ - ([-1.4, 2.3, None],), - (pd.Index([-1.4, 2.3, None]),), - (pd.Series([-1.4, 2.3, None], index=[44, 2, 1]),), - ], -) -def test_series_binop_w_other_types(scalars_dfs, other): - # 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 - - bf_result = (scalars_df["int64_col"].head(3) + other).to_pandas() - pd_result = scalars_pandas_df["int64_col"].head(3) + other - - if isinstance(other, pd.Series): - # pandas 3.0 preserves series name, bigframe, earlier pandas do not - pd_result.index.name = bf_result.index.name - - assert_series_equal( - bf_result, - pd_result, - ) - - -@pytest.mark.parametrize( - ("other",), - [ - ([-1.4, 2.3, None],), - (pd.Index([-1.4, 2.3, None]),), - (pd.Series([-1.4, 2.3, None], index=[44, 2, 1]),), - ], -) -def test_series_reverse_binop_w_other_types(scalars_dfs, other): - # 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 - - bf_result = (other + scalars_df["int64_col"].head(3)).to_pandas() - pd_result = other + scalars_pandas_df["int64_col"].head(3) - - assert_series_equal( - bf_result, - pd_result, - ) - - -def test_series_combine_first(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 - int64_col = scalars_df["int64_col"].head(7) - float64_col = scalars_df["float64_col"].tail(7) - bf_result = int64_col.combine_first(float64_col).to_pandas() - - pd_int64_col = scalars_pandas_df["int64_col"].head(7) - pd_float64_col = scalars_pandas_df["float64_col"].tail(7) - pd_result = pd_int64_col.combine_first(pd_float64_col) - - assert_series_equal( + assert_series_equal_ignoring_order( bf_result, pd_result, ) -def test_series_update(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - int64_col = scalars_df["int64_col"].head(7) - float64_col = scalars_df["float64_col"].tail(7).copy() - float64_col.update(int64_col) - - pd_int64_col = scalars_pandas_df["int64_col"].head(7) - pd_float64_col = scalars_pandas_df["float64_col"].tail(7).copy() - pd_float64_col.update(pd_int64_col) - - assert_series_equal( - float64_col.to_pandas(), - pd_float64_col, - ) - - def test_mean(scalars_dfs): scalars_df, scalars_pandas_df = scalars_dfs col_name = "int64_col" @@ -1977,68 +1169,25 @@ def test_mean(scalars_dfs): assert math.isclose(pd_result, bf_result) -@pytest.mark.parametrize( - ("col_name"), - [ - "int64_col", - # Non-numeric column - "bytes_col", - "date_col", - "datetime_col", - "time_col", - "timestamp_col", - "string_col", - ], -) -def test_median(scalars_dfs, col_name): +def test_median(scalars_dfs): scalars_df, scalars_pandas_df = scalars_dfs - bf_result = scalars_df[col_name].median(exact=False) + col_name = "int64_col" + bf_result = scalars_df[col_name].median() pd_max = scalars_pandas_df[col_name].max() pd_min = scalars_pandas_df[col_name].min() # Median is approximate, so just check for plausibility. assert pd_min < bf_result < pd_max -def test_median_exact(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - col_name = "int64_col" - bf_result = scalars_df[col_name].median() - pd_result = scalars_pandas_df[col_name].median() - assert math.isclose(pd_result, bf_result) - - -def test_series_quantile(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - col_name = "int64_col" - bf_series = scalars_df[col_name] - pd_series = scalars_pandas_df[col_name] - - pd_result = pd_series.quantile([0.0, 0.4, 0.6, 1.0]) - bf_result = bf_series.quantile([0.0, 0.4, 0.6, 1.0]) - bigframes.testing.utils.assert_series_equal( - pd_result, bf_result.to_pandas(), check_dtype=False, check_index_type=False - ) - - -def test_numeric_literal(scalars_dfs): - scalars_df, _ = scalars_dfs - col_name = "numeric_col" - assert scalars_df[col_name].dtype == pd.ArrowDtype(pa.decimal128(38, 9)) - bf_result = scalars_df[col_name] + 42 - assert bf_result.size == scalars_df[col_name].size - assert bf_result.dtype == pd.ArrowDtype(pa.decimal128(38, 9)) - - -def test_series_small_repr(scalars_dfs): +def test_repr(scalars_dfs): scalars_df, scalars_pandas_df = scalars_dfs + if scalars_pandas_df.index.name != "rowindex": + pytest.skip("Require index & ordering for consistent repr.") col_name = "int64_col" bf_series = scalars_df[col_name] pd_series = scalars_pandas_df[col_name] - with bigframes.pandas.option_context("display.repr_mode", "head"): - assert repr(bf_series) == pd_series.to_string( - length=False, dtype=True, name=True - ) + assert repr(bf_series) == repr(pd_series) def test_sum(scalars_dfs): @@ -2064,7 +1213,7 @@ def test_cumprod(scalars_dfs): col_name = "float64_col" bf_result = scalars_df[col_name].cumprod() pd_result = scalars_pandas_df[col_name].cumprod() - bigframes.testing.utils.assert_series_equal( + pd.testing.assert_series_equal( pd_result, bf_result.to_pandas(), ) @@ -2105,19 +1254,13 @@ def test_any(scalars_dfs): def test_groupby_sum(scalars_dfs): scalars_df, scalars_pandas_df = scalars_dfs col_name = "int64_too" - bf_series = ( - scalars_df[col_name] - .groupby([scalars_df["bool_col"], ~scalars_df["bool_col"]]) - .sum() - ) + bf_series = scalars_df[col_name].groupby(scalars_df["string_col"]).sum() pd_series = ( - scalars_pandas_df[col_name] - .groupby([scalars_pandas_df["bool_col"], ~scalars_pandas_df["bool_col"]]) - .sum() + scalars_pandas_df[col_name].groupby(scalars_pandas_df["string_col"]).sum() ) # TODO(swast): Update groupby to use index based on group by key(s). bf_result = bf_series.to_pandas() - assert_series_equal( + assert_series_equal_ignoring_order( pd_series, bf_result, check_exact=False, @@ -2135,7 +1278,7 @@ def test_groupby_std(scalars_dfs): .astype(pd.Float64Dtype()) ) bf_result = bf_series.to_pandas() - assert_series_equal( + assert_series_equal_ignoring_order( pd_series, bf_result, check_exact=False, @@ -2150,7 +1293,7 @@ def test_groupby_var(scalars_dfs): scalars_pandas_df[col_name].groupby(scalars_pandas_df["string_col"]).var() ) bf_result = bf_series.to_pandas() - assert_series_equal( + assert_series_equal_ignoring_order( pd_series, bf_result, check_exact=False, @@ -2161,11 +1304,13 @@ def test_groupby_level_sum(scalars_dfs): # TODO(tbergeron): Use a non-unique index once that becomes possible in tests scalars_df, scalars_pandas_df = scalars_dfs col_name = "int64_too" + if scalars_pandas_df.index.name != "rowindex": + pytest.skip("Require index for groupby level.") bf_series = scalars_df[col_name].groupby(level=0).sum() pd_series = scalars_pandas_df[col_name].groupby(level=0).sum() # TODO(swast): Update groupby to use index based on group by key(s). - bigframes.testing.utils.assert_series_equal( + pd.testing.assert_series_equal( pd_series.sort_index(), bf_series.to_pandas().sort_index(), ) @@ -2175,11 +1320,13 @@ def test_groupby_level_list_sum(scalars_dfs): # TODO(tbergeron): Use a non-unique index once that becomes possible in tests scalars_df, scalars_pandas_df = scalars_dfs col_name = "int64_too" + if scalars_pandas_df.index.name != "rowindex": + pytest.skip("Require index for groupby level.") bf_series = scalars_df[col_name].groupby(level=["rowindex"]).sum() pd_series = scalars_pandas_df[col_name].groupby(level=["rowindex"]).sum() # TODO(swast): Update groupby to use index based on group by key(s). - bigframes.testing.utils.assert_series_equal( + pd.testing.assert_series_equal( pd_series.sort_index(), bf_series.to_pandas().sort_index(), ) @@ -2198,37 +1345,17 @@ def test_groupby_mean(scalars_dfs): ) # TODO(swast): Update groupby to use index based on group by key(s). bf_result = bf_series.to_pandas() - assert_series_equal( + assert_series_equal_ignoring_order( pd_series, bf_result, ) -def test_groupby_median_exact(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - col_name = "int64_too" - bf_result = ( - scalars_df[col_name].groupby(scalars_df["string_col"], dropna=False).median() - ) - pd_result = ( - scalars_pandas_df[col_name] - .groupby(scalars_pandas_df["string_col"], dropna=False) - .median() - ) - - assert_series_equal( - pd_result, - bf_result.to_pandas(), - ) - - -def test_groupby_median_inexact(scalars_dfs): +def test_groupby_median(scalars_dfs): scalars_df, scalars_pandas_df = scalars_dfs col_name = "int64_too" bf_series = ( - scalars_df[col_name] - .groupby(scalars_df["string_col"], dropna=False) - .median(exact=False) + scalars_df[col_name].groupby(scalars_df["string_col"], dropna=False).median() ) pd_max = ( scalars_pandas_df[col_name] @@ -2253,10 +1380,10 @@ def test_groupby_prod(scalars_dfs): bf_series = scalars_df[col_name].groupby(scalars_df["int64_col"]).prod() pd_series = ( scalars_pandas_df[col_name].groupby(scalars_pandas_df["int64_col"]).prod() - ).astype(pd.Float64Dtype()) + ) # TODO(swast): Update groupby to use index based on group by key(s). bf_result = bf_series.to_pandas() - assert_series_equal( + assert_series_equal_ignoring_order( pd_series, bf_result, ) @@ -2269,8 +1396,7 @@ def test_groupby_prod(scalars_dfs): (lambda x: x.cumcount()), (lambda x: x.cummin()), (lambda x: x.cummax()), - # Pandas 2.2 casts to cumprod to float. - (lambda x: x.cumprod().astype("Float64")), + (lambda x: x.cumprod()), (lambda x: x.diff()), (lambda x: x.shift(2)), (lambda x: x.shift(-2)), @@ -2294,25 +1420,18 @@ def test_groupby_window_ops(scalars_df_index, scalars_pandas_df_index, operator) ).to_pandas() pd_series = operator( scalars_pandas_df_index[col_name].groupby(scalars_pandas_df_index[group_key]) - ).astype(bf_series.dtype) - - bigframes.testing.utils.assert_series_equal( + ).astype(pd.Int64Dtype()) + pd.testing.assert_series_equal( pd_series, bf_series, ) -@pytest.mark.parametrize( - ("label", "col_name"), - [ - (0, "bool_col"), - (1, "int64_col"), - ], -) -def test_drop_label(scalars_df_index, scalars_pandas_df_index, label, col_name): - bf_series = scalars_df_index[col_name].drop(label).to_pandas() - pd_series = scalars_pandas_df_index[col_name].drop(label) - bigframes.testing.utils.assert_series_equal( +def test_drop_label(scalars_df_index, scalars_pandas_df_index): + col_name = "int64_col" + bf_series = scalars_df_index[col_name].drop(1).to_pandas() + pd_series = scalars_pandas_df_index[col_name].drop(1) + pd.testing.assert_series_equal( pd_series, bf_series, ) @@ -2322,7 +1441,7 @@ def test_drop_label_list(scalars_df_index, scalars_pandas_df_index): col_name = "int64_col" bf_series = scalars_df_index[col_name].drop([1, 3]).to_pandas() pd_series = scalars_pandas_df_index[col_name].drop([1, 3]) - bigframes.testing.utils.assert_series_equal( + pd.testing.assert_series_equal( pd_series, bf_series, ) @@ -2346,7 +1465,7 @@ def test_drop_label_list(scalars_df_index, scalars_pandas_df_index): def test_drop_duplicates(scalars_df_index, scalars_pandas_df_index, keep, col_name): bf_series = scalars_df_index[col_name].drop_duplicates(keep=keep).to_pandas() pd_series = scalars_pandas_df_index[col_name].drop_duplicates(keep=keep) - bigframes.testing.utils.assert_series_equal( + pd.testing.assert_series_equal( pd_series, bf_series, ) @@ -2360,7 +1479,7 @@ def test_drop_duplicates(scalars_df_index, scalars_pandas_df_index, keep, col_na ], ) def test_unique(scalars_df_index, scalars_pandas_df_index, col_name): - bf_uniq = scalars_df_index[col_name].unique().to_numpy(na_value=None) + bf_uniq = scalars_df_index[col_name].unique().to_numpy() pd_uniq = scalars_pandas_df_index[col_name].unique() numpy.array_equal(pd_uniq, bf_uniq) @@ -2383,7 +1502,7 @@ def test_unique(scalars_df_index, scalars_pandas_df_index, col_name): def test_duplicated(scalars_df_index, scalars_pandas_df_index, keep, col_name): bf_series = scalars_df_index[col_name].duplicated(keep=keep).to_pandas() pd_series = scalars_pandas_df_index[col_name].duplicated(keep=keep) - bigframes.testing.utils.assert_series_equal(pd_series, bf_series, check_dtype=False) + pd.testing.assert_series_equal(pd_series, bf_series, check_dtype=False) def test_shape(scalars_dfs): @@ -2413,24 +1532,6 @@ def test_size(scalars_dfs): assert pd_result == bf_result -def test_series_hasnans_true(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - - bf_result = scalars_df["string_col"].hasnans - pd_result = scalars_pandas_df["string_col"].hasnans - - assert pd_result == bf_result - - -def test_series_hasnans_false(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - - bf_result = scalars_df["string_col"].dropna().hasnans - pd_result = scalars_pandas_df["string_col"].dropna().hasnans - - assert pd_result == bf_result - - def test_empty_false(scalars_dfs): scalars_df, scalars_pandas_df = scalars_dfs @@ -2454,19 +1555,15 @@ def test_empty_true_row_filter(scalars_dfs): assert pd_result == bf_result -def test_series_names(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - - bf_result = scalars_df["string_col"].copy() - bf_result.index.name = "new index name" - bf_result.name = "new series name" +def test_empty_true_memtable(session: bigframes.Session): + bf_series: series.Series = series.Series(session=session) + pd_series: pd.Series = pd.Series() - pd_result = scalars_pandas_df["string_col"].copy() - pd_result.index.name = "new index name" - pd_result.name = "new series name" + bf_result = bf_series.empty + pd_result = pd_series.empty - assert pd_result.name == bf_result.name - assert pd_result.index.name == bf_result.index.name + assert pd_result + assert bf_result == pd_result def test_dtype(scalars_dfs): @@ -2490,10 +1587,13 @@ def test_dtypes(scalars_dfs): def test_head(scalars_dfs): scalars_df, scalars_pandas_df = scalars_dfs + if scalars_df.index.name is None: + pytest.skip("Require explicit index for offset ops.") + bf_result = scalars_df["string_col"].head(2).to_pandas() pd_result = scalars_pandas_df["string_col"].head(2) - assert_series_equal( + assert_series_equal_ignoring_order( pd_result, bf_result, ) @@ -2502,10 +1602,13 @@ def test_head(scalars_dfs): def test_tail(scalars_dfs): scalars_df, scalars_pandas_df = scalars_dfs + if scalars_df.index.name is None: + pytest.skip("Require explicit index for offset ops.") + bf_result = scalars_df["string_col"].tail(2).to_pandas() pd_result = scalars_pandas_df["string_col"].tail(2) - assert_series_equal( + assert_series_equal_ignoring_order( pd_result, bf_result, ) @@ -2514,10 +1617,13 @@ def test_tail(scalars_dfs): def test_head_then_scalar_operation(scalars_dfs): scalars_df, scalars_pandas_df = scalars_dfs + if scalars_df.index.name is None: + pytest.skip("Require explicit index for offset ops.") + bf_result = (scalars_df["float64_col"].head(1) + 4).to_pandas() pd_result = scalars_pandas_df["float64_col"].head(1) + 4 - bigframes.testing.utils.assert_series_equal( + pd.testing.assert_series_equal( bf_result, pd_result, ) @@ -2526,6 +1632,9 @@ def test_head_then_scalar_operation(scalars_dfs): def test_head_then_series_operation(scalars_dfs): scalars_df, scalars_pandas_df = scalars_dfs + if scalars_df.index.name is None: + pytest.skip("Require explicit index for offset ops.") + bf_result = ( scalars_df["float64_col"].head(4) + scalars_df["float64_col"].head(2) ).to_pandas() @@ -2533,107 +1642,19 @@ def test_head_then_series_operation(scalars_dfs): "float64_col" ].head(2) - bigframes.testing.utils.assert_series_equal( + pd.testing.assert_series_equal( bf_result, pd_result, ) -def test_series_peek(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - - peek_result = scalars_df["float64_col"].peek(n=3, force=False) - - bigframes.testing.utils.assert_series_equal( - peek_result, - scalars_pandas_df["float64_col"].reindex_like(peek_result), - ) - assert len(peek_result) == 3 - - -def test_series_peek_with_large_results_not_allowed(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - - session = scalars_df._block.session - slot_millis_sum = session.slot_millis_sum - peek_result = scalars_df["float64_col"].peek( - n=3, force=False, allow_large_results=False - ) - - # The metrics won't be fully updated when we call query_and_wait. - print(session.slot_millis_sum - slot_millis_sum) - assert session.slot_millis_sum - slot_millis_sum < 500 - bigframes.testing.utils.assert_series_equal( - peek_result, - scalars_pandas_df["float64_col"].reindex_like(peek_result), - ) - assert len(peek_result) == 3 - - -def test_series_peek_multi_index(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - bf_series = scalars_df.set_index(["string_col", "bool_col"])["float64_col"] - bf_series.name = ("2-part", "name") - pd_series = scalars_pandas_df.set_index(["string_col", "bool_col"])["float64_col"] - pd_series.name = ("2-part", "name") - peek_result = bf_series.peek(n=3, force=False) - bigframes.testing.utils.assert_series_equal( - peek_result, - pd_series.reindex_like(peek_result), - ) - - -def test_series_peek_filtered(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - peek_result = scalars_df[scalars_df.int64_col > 0]["float64_col"].peek( - n=3, force=False - ) - pd_result = scalars_pandas_df[scalars_pandas_df.int64_col > 0]["float64_col"] - bigframes.testing.utils.assert_series_equal( - peek_result, - pd_result.reindex_like(peek_result), - ) - - -def test_series_peek_force(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 - - cumsum_df = scalars_df[["int64_col", "int64_too"]].cumsum() - df_filtered = cumsum_df[cumsum_df.int64_col > 0]["int64_too"] - peek_result = df_filtered.peek(n=3, force=True) - pd_cumsum_df = scalars_pandas_df[["int64_col", "int64_too"]].cumsum() - pd_result = pd_cumsum_df[pd_cumsum_df.int64_col > 0]["int64_too"] - bigframes.testing.utils.assert_series_equal( - peek_result, - pd_result.reindex_like(peek_result), - ) - - -def test_series_peek_force_float(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 - - cumsum_df = scalars_df[["int64_col", "float64_col"]].cumsum() - df_filtered = cumsum_df[cumsum_df.float64_col > 0]["float64_col"] - peek_result = df_filtered.peek(n=3, force=True) - pd_cumsum_df = scalars_pandas_df[["int64_col", "float64_col"]].cumsum() - pd_result = pd_cumsum_df[pd_cumsum_df.float64_col > 0]["float64_col"] - bigframes.testing.utils.assert_series_equal( - peek_result, - pd_result.reindex_like(peek_result), - ) - - def test_shift(scalars_df_index, scalars_pandas_df_index): col_name = "int64_col" bf_result = scalars_df_index[col_name].shift().to_pandas() # cumsum does not behave well on nullable ints in pandas, produces object type and never ignores NA pd_result = scalars_pandas_df_index[col_name].shift().astype(pd.Int64Dtype()) - bigframes.testing.utils.assert_series_equal( + pd.testing.assert_series_equal( bf_result, pd_result, ) @@ -2644,7 +1665,7 @@ def test_series_ffill(scalars_df_index, scalars_pandas_df_index): bf_result = scalars_df_index[col_name].ffill(limit=1).to_pandas() pd_result = scalars_pandas_df_index[col_name].ffill(limit=1) - bigframes.testing.utils.assert_series_equal( + pd.testing.assert_series_equal( bf_result, pd_result, ) @@ -2655,7 +1676,7 @@ def test_series_bfill(scalars_df_index, scalars_pandas_df_index): bf_result = scalars_df_index[col_name].bfill(limit=2).to_pandas() pd_result = scalars_pandas_df_index[col_name].bfill(limit=2) - bigframes.testing.utils.assert_series_equal( + pd.testing.assert_series_equal( bf_result, pd_result, ) @@ -2670,7 +1691,7 @@ def test_cumsum_int(scalars_df_index, scalars_pandas_df_index): # cumsum does not behave well on nullable ints in pandas, produces object type and never ignores NA pd_result = scalars_pandas_df_index[col_name].cumsum().astype(pd.Int64Dtype()) - bigframes.testing.utils.assert_series_equal( + pd.testing.assert_series_equal( bf_result, pd_result, ) @@ -2691,7 +1712,45 @@ def test_cumsum_int_ordered(scalars_df_index, scalars_pandas_df_index): .astype(pd.Int64Dtype()) ) - bigframes.testing.utils.assert_series_equal( + pd.testing.assert_series_equal( + bf_result, + pd_result, + ) + + +@pytest.mark.parametrize( + ("na_option",), + [ + ("keep",), + ("top",), + ("bottom",), + ], +) +@pytest.mark.parametrize( + ("method",), + [ + ("average",), + ("min",), + ("max",), + ("first",), + ("dense",), + ], +) +@pytest.mark.skipif( + True, reason="Blocked by possible pandas rank() regression (b/283278923)" +) +def test_rank_with_nulls(scalars_df_index, scalars_pandas_df_index, na_option, method): + col_name = "bool_col" + bf_result = ( + scalars_df_index[col_name].rank(na_option=na_option, method=method).to_pandas() + ) + pd_result = ( + scalars_pandas_df_index[col_name] + .rank(na_option=na_option, method=method) + .astype(pd.Float64Dtype()) + ) + + pd.testing.assert_series_equal( bf_result, pd_result, ) @@ -2710,7 +1769,7 @@ def test_series_nlargest(scalars_df_index, scalars_pandas_df_index, keep): bf_result = scalars_df_index[col_name].nlargest(4, keep=keep).to_pandas() pd_result = scalars_pandas_df_index[col_name].nlargest(4, keep=keep) - bigframes.testing.utils.assert_series_equal( + pd.testing.assert_series_equal( bf_result, pd_result, ) @@ -2733,7 +1792,7 @@ def test_diff(scalars_df_index, scalars_pandas_df_index, periods): .astype(pd.Int64Dtype()) ) - bigframes.testing.utils.assert_series_equal( + pd.testing.assert_series_equal( bf_result, pd_result, ) @@ -2750,9 +1809,9 @@ def test_diff(scalars_df_index, scalars_pandas_df_index, periods): def test_series_pct_change(scalars_df_index, scalars_pandas_df_index, periods): bf_result = scalars_df_index["int64_col"].pct_change(periods=periods).to_pandas() # cumsum does not behave well on nullable ints in pandas, produces object type and never ignores NA - pd_result = scalars_pandas_df_index["int64_col"].ffill().pct_change(periods=periods) + pd_result = scalars_pandas_df_index["int64_col"].pct_change(periods=periods) - bigframes.testing.utils.assert_series_equal( + pd.testing.assert_series_equal( bf_result, pd_result, ) @@ -2771,56 +1830,18 @@ def test_series_nsmallest(scalars_df_index, scalars_pandas_df_index, keep): bf_result = scalars_df_index[col_name].nsmallest(2, keep=keep).to_pandas() pd_result = scalars_pandas_df_index[col_name].nsmallest(2, keep=keep) - bigframes.testing.utils.assert_series_equal( + pd.testing.assert_series_equal( bf_result, pd_result, ) -@pytest.mark.parametrize( - ("na_option", "method", "ascending", "numeric_only", "pct"), - [ - ("keep", "average", True, True, False), - ("top", "min", False, False, True), - ("bottom", "max", False, False, False), - ("top", "first", False, False, True), - ("bottom", "dense", False, False, False), - ], -) -def test_series_rank( - scalars_df_index, - scalars_pandas_df_index, - na_option, - method, - ascending, - numeric_only, - pct, -): +def test_rank_ints(scalars_df_index, scalars_pandas_df_index): col_name = "int64_too" - bf_result = ( - scalars_df_index[col_name] - .rank( - na_option=na_option, - method=method, - ascending=ascending, - numeric_only=numeric_only, - pct=pct, - ) - .to_pandas() - ) - pd_result = ( - scalars_pandas_df_index[col_name] - .rank( - na_option=na_option, - method=method, - ascending=ascending, - numeric_only=numeric_only, - pct=pct, - ) - .astype(pd.Float64Dtype()) - ) + bf_result = scalars_df_index[col_name].rank().to_pandas() + pd_result = scalars_pandas_df_index[col_name].rank().astype(pd.Float64Dtype()) - bigframes.testing.utils.assert_series_equal( + pd.testing.assert_series_equal( bf_result, pd_result, ) @@ -2832,7 +1853,7 @@ def test_cast_float_to_int(scalars_df_index, scalars_pandas_df_index): # cumsum does not behave well on nullable floats in pandas, produces object type and never ignores NA pd_result = scalars_pandas_df_index[col_name].astype(pd.Int64Dtype()) - bigframes.testing.utils.assert_series_equal( + pd.testing.assert_series_equal( bf_result, pd_result, ) @@ -2844,7 +1865,7 @@ def test_cast_float_to_bool(scalars_df_index, scalars_pandas_df_index): # cumsum does not behave well on nullable floats in pandas, produces object type and never ignores NA pd_result = scalars_pandas_df_index[col_name].astype(pd.BooleanDtype()) - bigframes.testing.utils.assert_series_equal( + pd.testing.assert_series_equal( bf_result, pd_result, ) @@ -2862,36 +1883,7 @@ def test_cumsum_nested(scalars_df_index, scalars_pandas_df_index): .astype(pd.Float64Dtype()) ) - bigframes.testing.utils.assert_series_equal( - bf_result, - pd_result, - ) - - -def test_nested_analytic_ops_align(scalars_df_index, scalars_pandas_df_index): - # TODO: supply a reason why this isn't compatible with pandas 1.x - pytest.importorskip("pandas", minversion="2.0.0") - col_name = "float64_col" - # set non-unique index to check implicit alignment - bf_series = scalars_df_index.set_index("bool_col")[col_name].fillna(0.0) - pd_series = scalars_pandas_df_index.set_index("bool_col")[col_name].fillna(0.0) - - bf_result = ( - (bf_series + 5) - + (bf_series.cumsum().cumsum().cumsum() + bf_series.rolling(window=3).mean()) - + bf_series.expanding().max() - ).to_pandas() - # cumsum does not behave well on nullable ints in pandas, produces object type and never ignores NA - pd_result = ( - (pd_series + 5) - + ( - pd_series.cumsum().cumsum().cumsum().astype(pd.Float64Dtype()) - + pd_series.rolling(window=3).mean() - ) - + pd_series.expanding().max() - ) - - bigframes.testing.utils.assert_series_equal( + pd.testing.assert_series_equal( bf_result, pd_result, ) @@ -2907,7 +1899,7 @@ def test_cumsum_int_filtered(scalars_df_index, scalars_pandas_df_index): # cumsum does not behave well on nullable ints in pandas, produces object type and never ignores NA pd_result = pd_col[pd_col > -2].cumsum().astype(pd.Int64Dtype()) - bigframes.testing.utils.assert_series_equal( + pd.testing.assert_series_equal( bf_result, pd_result, ) @@ -2919,7 +1911,7 @@ def test_cumsum_float(scalars_df_index, scalars_pandas_df_index): # cumsum does not behave well on nullable floats in pandas, produces object type and never ignores NA pd_result = scalars_pandas_df_index[col_name].cumsum().astype(pd.Float64Dtype()) - bigframes.testing.utils.assert_series_equal( + pd.testing.assert_series_equal( bf_result, pd_result, ) @@ -2930,7 +1922,7 @@ def test_cummin_int(scalars_df_index, scalars_pandas_df_index): bf_result = scalars_df_index[col_name].cummin().to_pandas() pd_result = scalars_pandas_df_index[col_name].cummin() - bigframes.testing.utils.assert_series_equal( + pd.testing.assert_series_equal( bf_result, pd_result, ) @@ -2941,67 +1933,30 @@ def test_cummax_int(scalars_df_index, scalars_pandas_df_index): bf_result = scalars_df_index[col_name].cummax().to_pandas() pd_result = scalars_pandas_df_index[col_name].cummax() - bigframes.testing.utils.assert_series_equal( + pd.testing.assert_series_equal( bf_result, pd_result, ) -@pytest.mark.parametrize( - ("kwargs"), - [ - {}, - {"normalize": True}, - {"ascending": True}, - ], - ids=[ - "default", - "normalize", - "ascending", - ], -) -def test_value_counts(scalars_dfs, kwargs): - if pd.__version__.startswith("1."): - pytest.skip("pandas 1.x produces different column labels.") +def test_value_counts(scalars_dfs): scalars_df, scalars_pandas_df = scalars_dfs col_name = "int64_too" - # Pandas `value_counts` can produce non-deterministic results with tied counts. - # Remove duplicates to enforce a consistent output. - s = scalars_df[col_name].drop(0) - pd_s = scalars_pandas_df[col_name].drop(0) - - bf_result = s.value_counts(**kwargs).to_pandas() - pd_result = pd_s.value_counts(**kwargs) - - bigframes.testing.utils.assert_series_equal( - bf_result, - pd_result, - ) - - -def test_value_counts_with_na(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - col_name = "int64_col" - - bf_result = scalars_df[col_name].value_counts(dropna=False).to_pandas() - pd_result = scalars_pandas_df[col_name].value_counts(dropna=False) + bf_result = scalars_df[col_name].value_counts().to_pandas() + pd_result = scalars_pandas_df[col_name].value_counts() # Older pandas version may not have these values, bigframes tries to emulate 2.0+ pd_result.name = "count" pd_result.index.name = col_name - assert_series_equal( + pd.testing.assert_series_equal( bf_result, pd_result, - # bigframes values_counts does not honor ordering in the original data - ignore_order=True, ) def test_value_counts_w_cut(scalars_dfs): - if pd.__version__.startswith("1."): - pytest.skip("value_counts results different in pandas 1.x.") scalars_df, scalars_pandas_df = scalars_dfs col_name = "int64_col" @@ -3010,19 +1965,23 @@ def test_value_counts_w_cut(scalars_dfs): bf_result = bf_cut.value_counts().to_pandas() pd_result = pd_cut.value_counts() + # Older pandas version may not have these values, bigframes tries to emulate 2.0+ + pd_result.name = "count" + pd_result.index.name = col_name pd_result.index = pd_result.index.astype(pd.Int64Dtype()) - bigframes.testing.utils.assert_series_equal( + pd.testing.assert_series_equal( bf_result, pd_result.astype(pd.Int64Dtype()), ) def test_iloc_nested(scalars_df_index, scalars_pandas_df_index): + bf_result = scalars_df_index["string_col"].iloc[1:].iloc[1:].to_pandas() pd_result = scalars_pandas_df_index["string_col"].iloc[1:].iloc[1:] - bigframes.testing.utils.assert_series_equal( + pd.testing.assert_series_equal( bf_result, pd_result, ) @@ -3051,7 +2010,7 @@ def test_iloc_nested(scalars_df_index, scalars_pandas_df_index): def test_series_iloc(scalars_df_index, scalars_pandas_df_index, start, stop, step): bf_result = scalars_df_index["string_col"].iloc[start:stop:step].to_pandas() pd_result = scalars_pandas_df_index["string_col"].iloc[start:stop:step] - bigframes.testing.utils.assert_series_equal( + pd.testing.assert_series_equal( bf_result, pd_result, ) @@ -3087,7 +2046,7 @@ def test_series_add_prefix(scalars_df_index, scalars_pandas_df_index): pd_result = scalars_pandas_df_index["int64_too"].add_prefix("prefix_") # Index will be object type in pandas, string type in bigframes, but same values - bigframes.testing.utils.assert_series_equal( + pd.testing.assert_series_equal( bf_result, pd_result, check_index_type=False, @@ -3100,7 +2059,7 @@ def test_series_add_suffix(scalars_df_index, scalars_pandas_df_index): pd_result = scalars_pandas_df_index["int64_too"].add_suffix("_suffix") # Index will be object type in pandas, string type in bigframes, but same values - bigframes.testing.utils.assert_series_equal( + pd.testing.assert_series_equal( bf_result, pd_result, check_index_type=False, @@ -3117,7 +2076,11 @@ def test_series_filter_items(scalars_df_index, scalars_pandas_df_index): # Pandas uses int64 instead of Int64 (nullable) dtype. pd_result.index = pd_result.index.astype(pd.Int64Dtype()) # Ignore ordering as pandas order differently depending on version - assert_series_equal(bf_result, pd_result, check_names=False, ignore_order=True) + assert_series_equal_ignoring_order( + bf_result, + pd_result, + check_names=False, + ) def test_series_filter_like(scalars_df_index, scalars_pandas_df_index): @@ -3128,7 +2091,7 @@ def test_series_filter_like(scalars_df_index, scalars_pandas_df_index): pd_result = scalars_pandas_df_index["float64_col"].filter(like="ello") - bigframes.testing.utils.assert_series_equal( + pd.testing.assert_series_equal( bf_result, pd_result, ) @@ -3142,7 +2105,7 @@ def test_series_filter_regex(scalars_df_index, scalars_pandas_df_index): pd_result = scalars_pandas_df_index["float64_col"].filter(regex="^[GH].*") - bigframes.testing.utils.assert_series_equal( + pd.testing.assert_series_equal( bf_result, pd_result, ) @@ -3157,7 +2120,7 @@ def test_series_reindex(scalars_df_index, scalars_pandas_df_index): # Pandas uses int64 instead of Int64 (nullable) dtype. pd_result.index = pd_result.index.astype(pd.Int64Dtype()) - bigframes.testing.utils.assert_series_equal( + pd.testing.assert_series_equal( bf_result, pd_result, ) @@ -3184,7 +2147,7 @@ def test_series_reindex_like(scalars_df_index, scalars_pandas_df_index): # Pandas uses int64 instead of Int64 (nullable) dtype. pd_result.index = pd_result.index.astype(pd.Int64Dtype()) - bigframes.testing.utils.assert_series_equal( + pd.testing.assert_series_equal( bf_result, pd_result, ) @@ -3200,7 +2163,7 @@ def test_where_with_series(scalars_df_index, scalars_pandas_df_index): scalars_pandas_df_index["bool_col"], scalars_pandas_df_index["int64_too"] ) - bigframes.testing.utils.assert_series_equal( + pd.testing.assert_series_equal( bf_result, pd_result, ) @@ -3225,7 +2188,7 @@ def test_where_with_different_indices(scalars_df_index, scalars_pandas_df_index) ) ) - bigframes.testing.utils.assert_series_equal( + pd.testing.assert_series_equal( bf_result, pd_result, ) @@ -3239,62 +2202,27 @@ def test_where_with_default(scalars_df_index, scalars_pandas_df_index): scalars_pandas_df_index["bool_col"] ) - bigframes.testing.utils.assert_series_equal( - bf_result, - pd_result, - ) - - -def test_where_with_callable(scalars_df_index, scalars_pandas_df_index): - def _is_positive(x): - return x > 0 - - # Both cond and other are callable. - bf_result = ( - scalars_df_index["int64_col"] - .where(cond=_is_positive, other=lambda x: x * 10) - .to_pandas() - ) - pd_result = scalars_pandas_df_index["int64_col"].where( - cond=_is_positive, other=lambda x: x * 10 - ) - - bigframes.testing.utils.assert_series_equal( + pd.testing.assert_series_equal( bf_result, pd_result, ) -@pytest.mark.parametrize( - ("ordered"), - [ - (True), - (False), - ], -) -def test_clip(scalars_df_index, scalars_pandas_df_index, ordered): +def test_clip(scalars_df_index, scalars_pandas_df_index): col_bf = scalars_df_index["int64_col"] lower_bf = scalars_df_index["int64_too"] - 1 upper_bf = scalars_df_index["int64_too"] + 1 - bf_result = col_bf.clip(lower_bf, upper_bf).to_pandas(ordered=ordered) + bf_result = col_bf.clip(lower_bf, upper_bf).to_pandas() col_pd = scalars_pandas_df_index["int64_col"] lower_pd = scalars_pandas_df_index["int64_too"] - 1 upper_pd = scalars_pandas_df_index["int64_too"] + 1 pd_result = col_pd.clip(lower_pd, upper_pd) - assert_series_equal(bf_result, pd_result, ignore_order=not ordered) - - -def test_clip_int_with_float_bounds(scalars_df_index, scalars_pandas_df_index): - col_bf = scalars_df_index["int64_too"] - bf_result = col_bf.clip(-100, 3.14151593).to_pandas() - - col_pd = scalars_pandas_df_index["int64_too"] - # pandas doesn't work with Int64 and clip with floats - pd_result = col_pd.astype("int64").clip(-100, 3.14151593).astype("Float64") - - assert_series_equal(bf_result, pd_result) + pd.testing.assert_series_equal( + bf_result, + pd_result, + ) def test_clip_filtered_two_sided(scalars_df_index, scalars_pandas_df_index): @@ -3308,7 +2236,7 @@ def test_clip_filtered_two_sided(scalars_df_index, scalars_pandas_df_index): upper_pd = scalars_pandas_df_index["int64_too"].iloc[:5] + 1 pd_result = col_pd.clip(lower_pd, upper_pd) - bigframes.testing.utils.assert_series_equal( + pd.testing.assert_series_equal( bf_result, pd_result, ) @@ -3323,7 +2251,7 @@ def test_clip_filtered_one_sided(scalars_df_index, scalars_pandas_df_index): lower_pd = scalars_pandas_df_index["int64_too"].iloc[2:] - 1 pd_result = col_pd.clip(lower_pd, None) - bigframes.testing.utils.assert_series_equal( + pd.testing.assert_series_equal( bf_result, pd_result, ) @@ -3353,128 +2281,33 @@ def test_between(scalars_df_index, scalars_pandas_df_index, left, right, inclusi ) pd_result = scalars_pandas_df_index["int64_col"].between(left, right, inclusive) - bigframes.testing.utils.assert_series_equal( + pd.testing.assert_series_equal( bf_result, pd_result.astype(pd.BooleanDtype()), ) -def test_series_case_when(scalars_dfs_maybe_ordered): - pytest.importorskip( - "pandas", - minversion="2.2.0", - reason="case_when added in pandas 2.2.0", - ) - scalars_df, scalars_pandas_df = scalars_dfs_maybe_ordered - - bf_series = scalars_df["int64_col"] - pd_series = scalars_pandas_df["int64_col"] - - # TODO(tswast): pandas case_when appears to assume True when a value is - # null. I suspect this should be considered a bug in pandas. - - # Generate 150 conditions to test case_when with a large number of conditions - bf_conditions = ( - [((bf_series > 645).fillna(True), bf_series - 1)] - + [((bf_series > (-100 + i * 5)).fillna(True), i) for i in range(148, 0, -1)] - + [((bf_series <= -100).fillna(True), pd.NA)] - ) - - pd_conditions = ( - [((pd_series > 645), pd_series - 1)] - + [((pd_series > (-100 + i * 5)), i) for i in range(148, 0, -1)] - + [(pd_series <= -100, pd.NA)] - ) - - assert len(bf_conditions) == 150 - - bf_result = bf_series.case_when(bf_conditions).to_pandas() - pd_result = pd_series.case_when(pd_conditions) - - bigframes.testing.utils.assert_series_equal( - bf_result, - pd_result.astype(pd.Int64Dtype()), - ) - - -def test_series_case_when_change_type(scalars_dfs_maybe_ordered): - pytest.importorskip( - "pandas", - minversion="2.2.0", - reason="case_when added in pandas 2.2.0", - ) - scalars_df, scalars_pandas_df = scalars_dfs_maybe_ordered - - bf_series = scalars_df["int64_col"] - pd_series = scalars_pandas_df["int64_col"] - - # TODO(tswast): pandas case_when appears to assume True when a value is - # null. I suspect this should be considered a bug in pandas. - - bf_conditions = [ - ((bf_series > 645).fillna(True), scalars_df["string_col"]), - ((bf_series <= -100).fillna(True), pd.NA), - (True, "not_found"), - ] - - pd_conditions = [ - ((pd_series > 645).fillna(True), scalars_pandas_df["string_col"]), - ((pd_series <= -100).fillna(True), pd.NA), - # pandas currently fails if both the condition and the value are literals. - ([True] * len(pd_series), ["not_found"] * len(pd_series)), - ] - - bf_result = bf_series.case_when(bf_conditions).to_pandas() - pd_result = pd_series.case_when(pd_conditions) - - bigframes.testing.utils.assert_series_equal( - bf_result, - pd_result.astype("string[pyarrow]"), - ) - - def test_to_frame(scalars_dfs): scalars_df, scalars_pandas_df = scalars_dfs bf_result = scalars_df["int64_col"].to_frame().to_pandas() pd_result = scalars_pandas_df["int64_col"].to_frame() - assert_frame_equal(bf_result, pd_result) - - -def test_to_frame_no_name(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - - bf_result = scalars_df["int64_col"].rename(None).to_frame().to_pandas() - pd_result = scalars_pandas_df["int64_col"].rename(None).to_frame() + assert_pandas_df_equal_ignore_ordering(bf_result, pd_result) - assert_frame_equal(bf_result, pd_result) +def test_to_json(scalars_df_index, scalars_pandas_df_index): + bf_result = scalars_df_index["int64_col"].to_json() + pd_result = scalars_pandas_df_index["int64_col"].to_json() -def test_to_json(gcs_folder, scalars_df_index, scalars_pandas_df_index): - path = gcs_folder + "test_series_to_json*.jsonl" - scalars_df_index["int64_col"].to_json(path, lines=True, orient="records") - gcs_df = pd.read_json(get_first_file_from_wildcard(path), lines=True) - - bigframes.testing.utils.assert_series_equal( - gcs_df["int64_col"].astype(pd.Int64Dtype()), - scalars_pandas_df_index["int64_col"], - check_dtype=False, - check_index=False, - ) + assert bf_result == pd_result -def test_to_csv(gcs_folder, scalars_df_index, scalars_pandas_df_index): - path = gcs_folder + "test_series_to_csv*.csv" - scalars_df_index["int64_col"].to_csv(path) - gcs_df = pd.read_csv(get_first_file_from_wildcard(path)) +def test_to_csv(scalars_df_index, scalars_pandas_df_index): + bf_result = scalars_df_index["int64_col"].to_csv() + pd_result = scalars_pandas_df_index["int64_col"].to_csv() - bigframes.testing.utils.assert_series_equal( - gcs_df["int64_col"].astype(pd.Int64Dtype()), - scalars_pandas_df_index["int64_col"], - check_dtype=False, - check_index=False, - ) + assert bf_result == pd_result def test_to_latex(scalars_df_index, scalars_pandas_df_index): @@ -3484,51 +2317,6 @@ def test_to_latex(scalars_df_index, scalars_pandas_df_index): assert bf_result == pd_result -def test_series_to_json_local_str(scalars_df_index, scalars_pandas_df_index): - bf_result = scalars_df_index.int64_col.to_json() - pd_result = scalars_pandas_df_index.int64_col.to_json() - - assert bf_result == pd_result - - -def test_series_to_json_local_file(scalars_df_index, scalars_pandas_df_index): - # TODO: supply a reason why this isn't compatible with pandas 1.x - pytest.importorskip("pandas", minversion="2.0.0") - with ( - tempfile.TemporaryFile() as bf_result_file, - tempfile.TemporaryFile() as pd_result_file, - ): - scalars_df_index.int64_col.to_json(bf_result_file) - scalars_pandas_df_index.int64_col.to_json(pd_result_file) - - bf_result = bf_result_file.read() - pd_result = pd_result_file.read() - - assert bf_result == pd_result - - -def test_series_to_csv_local_str(scalars_df_index, scalars_pandas_df_index): - bf_result = scalars_df_index.int64_col.to_csv() - # default_handler for arrow types that have no default conversion - pd_result = scalars_pandas_df_index.int64_col.to_csv() - - assert bf_result == pd_result - - -def test_series_to_csv_local_file(scalars_df_index, scalars_pandas_df_index): - with ( - tempfile.TemporaryFile() as bf_result_file, - tempfile.TemporaryFile() as pd_result_file, - ): - scalars_df_index.int64_col.to_csv(bf_result_file) - scalars_pandas_df_index.int64_col.to_csv(pd_result_file) - - bf_result = bf_result_file.read() - pd_result = pd_result_file.read() - - assert bf_result == pd_result - - def test_to_dict(scalars_df_index, scalars_pandas_df_index): bf_result = scalars_df_index["int64_too"].to_dict() @@ -3604,7 +2392,7 @@ def test_series_values(scalars_df_index, scalars_pandas_df_index): pd_result = scalars_pandas_df_index["int64_too"].values # Numpy isn't equipped to compare non-numeric objects, so convert back to dataframe - bigframes.testing.utils.assert_series_equal( + pd.testing.assert_series_equal( pd.Series(bf_result), pd.Series(pd_result), check_dtype=False ) @@ -3637,20 +2425,7 @@ def test_sort_values(scalars_df_index, scalars_pandas_df_index, ascending, na_po ascending=ascending, na_position=na_position ) - bigframes.testing.utils.assert_series_equal( - bf_result, - pd_result, - ) - - -def test_series_sort_values_inplace(scalars_df_index, scalars_pandas_df_index): - # Test needs values to be unique - bf_series = scalars_df_index["int64_col"].copy() - bf_series.sort_values(ascending=False, inplace=True) - bf_result = bf_series.to_pandas() - pd_result = scalars_pandas_df_index["int64_col"].sort_values(ascending=False) - - bigframes.testing.utils.assert_series_equal( + pd.testing.assert_series_equal( bf_result, pd_result, ) @@ -3669,19 +2444,7 @@ def test_sort_index(scalars_df_index, scalars_pandas_df_index, ascending): ) pd_result = scalars_pandas_df_index["int64_too"].sort_index(ascending=ascending) - bigframes.testing.utils.assert_series_equal( - bf_result, - pd_result, - ) - - -def test_series_sort_index_inplace(scalars_df_index, scalars_pandas_df_index): - bf_series = scalars_df_index["int64_too"].copy() - bf_series.sort_index(ascending=False, inplace=True) - bf_result = bf_series.to_pandas() - pd_result = scalars_pandas_df_index["int64_too"].sort_index(ascending=False) - - bigframes.testing.utils.assert_series_equal( + pd.testing.assert_series_equal( bf_result, pd_result, ) @@ -3698,7 +2461,7 @@ def test_mask_default_value(scalars_dfs): pd_col_masked = pd_col.mask(pd_col % 2 == 1) pd_result = pd_col.to_frame().assign(int64_col_masked=pd_col_masked) - assert_frame_equal(bf_result, pd_result) + assert_pandas_df_equal_ignore_ordering(bf_result, pd_result) def test_mask_custom_value(scalars_dfs): @@ -3716,75 +2479,9 @@ def test_mask_custom_value(scalars_dfs): # odd so should be left as is, but it is being masked in pandas. # Accidentally the bigframes bahavior matches, but it should be updated # after the resolution of https://github.com/pandas-dev/pandas/issues/52955 - assert_frame_equal(bf_result, pd_result) - - -def test_mask_with_callable(scalars_df_index, scalars_pandas_df_index): - def _ten_times(x): - return x * 10 - - # Both cond and other are callable. - bf_result = ( - scalars_df_index["int64_col"] - .mask(cond=lambda x: x > 0, other=_ten_times) - .to_pandas() - ) - pd_result = scalars_pandas_df_index["int64_col"].mask( - cond=lambda x: x > 0, other=_ten_times - ) - - bigframes.testing.utils.assert_series_equal( - bf_result, - pd_result, - ) - - -@pytest.mark.parametrize( - ("lambda_",), - [ - pytest.param(lambda x: x > 0), - pytest.param( - lambda x: True if x > 0 else False, - marks=pytest.mark.xfail( - raises=ValueError, - ), - ), - ], - ids=[ - "lambda_arithmatic", - "lambda_arbitrary", - ], -) -def test_mask_lambda(scalars_dfs, lambda_): - scalars_df, scalars_pandas_df = scalars_dfs - - bf_col = scalars_df["int64_col"] - bf_result = bf_col.mask(lambda_).to_pandas() - - pd_col = scalars_pandas_df["int64_col"] - pd_result = pd_col.mask(lambda_) + assert_pandas_df_equal_ignore_ordering(bf_result, pd_result) - # ignore dtype check, which are Int64 and object respectively - assert_series_equal(bf_result, pd_result, check_dtype=False) - -def test_mask_simple_udf(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - - def foo(x): - return x < 1000000 - - bf_col = scalars_df["int64_col"] - bf_result = bf_col.mask(foo).to_pandas() - - pd_col = scalars_pandas_df["int64_col"] - pd_result = pd_col.mask(foo) - - # ignore dtype check, which are Int64 and object respectively - assert_series_equal(bf_result, pd_result, check_dtype=False) - - -@pytest.mark.parametrize("errors", ["raise", "null"]) @pytest.mark.parametrize( ("column", "to_type"), [ @@ -3793,40 +2490,15 @@ def foo(x): ("int64_col", pd.Float64Dtype()), ("int64_col", "string[pyarrow]"), ("int64_col", "boolean"), - ("int64_col", pd.ArrowDtype(pa.decimal128(38, 9))), - ("int64_col", pd.ArrowDtype(pa.decimal256(76, 38))), - ("int64_col", pd.ArrowDtype(pa.timestamp("us"))), - ("int64_col", pd.ArrowDtype(pa.timestamp("us", tz="UTC"))), - ("int64_col", "time64[us][pyarrow]"), - ("int64_col", pd.ArrowDtype(db_dtypes.JSONArrowType())), ("bool_col", "Int64"), ("bool_col", "string[pyarrow]"), - ("bool_col", "Float64"), - ("bool_col", pd.ArrowDtype(db_dtypes.JSONArrowType())), - ("string_col", "binary[pyarrow]"), - ("bytes_col", "string[pyarrow]"), # pandas actually doesn't let folks convert to/from naive timestamp and # raises a deprecation warning to use tz_localize/tz_convert instead, # but BigQuery always stores values as UTC and doesn't have to deal # with timezone conversions, so we'll allow it. - ("timestamp_col", "date32[day][pyarrow]"), - ("timestamp_col", "time64[us][pyarrow]"), ("timestamp_col", pd.ArrowDtype(pa.timestamp("us"))), - ("datetime_col", "date32[day][pyarrow]"), - pytest.param( - "datetime_col", - "string[pyarrow]", - marks=pytest.mark.skipif( - pd.__version__.startswith("2.2"), - reason="pandas 2.2 uses T as date/time separator whereas earlier versions use space", - ), - ), - ("datetime_col", "time64[us][pyarrow]"), ("datetime_col", pd.ArrowDtype(pa.timestamp("us", tz="UTC"))), ("date_col", "string[pyarrow]"), - ("date_col", pd.ArrowDtype(pa.timestamp("us"))), - ("date_col", pd.ArrowDtype(pa.timestamp("us", tz="UTC"))), - ("time_col", "string[pyarrow]"), # TODO(bmil): fix Ibis bug: BigQuery backend rounds to nearest int # ("float64_col", "Int64"), # TODO(bmil): decide whether to fix Ibis bug: BigQuery backend @@ -3837,312 +2509,33 @@ def foo(x): # https://cloud.google.com/bigquery/docs/reference/standard-sql/conversion_functions ], ) -def test_astype(scalars_df_index, scalars_pandas_df_index, column, to_type, errors): - # TODO: supply a reason why this isn't compatible with pandas 1.x - pytest.importorskip("pandas", minversion="2.0.0") - bf_result = scalars_df_index[column].astype(to_type, errors=errors).to_pandas() +def test_astype(scalars_df_index, scalars_pandas_df_index, column, to_type): + bf_result = scalars_df_index[column].astype(to_type).to_pandas() pd_result = scalars_pandas_df_index[column].astype(to_type) - bigframes.testing.utils.assert_series_equal(bf_result, pd_result) + pd.testing.assert_series_equal(bf_result, pd_result) -def test_series_astype_python(session): - input = pd.Series(["hello", "world", "3.11", "4000"]) - exepcted = pd.Series( - [None, None, 3.11, 4000], - dtype="Float64", - index=pd.Index([0, 1, 2, 3], dtype="Int64"), - ) - result = session.read_pandas(input).astype(float, errors="null").to_pandas() - bigframes.testing.utils.assert_series_equal(result, exepcted) +def test_string_astype_int(): + pd_series = pd.Series(["4", "-7", "0", " -03"]) + bf_series = series.Series(pd_series) + pd_result = pd_series.astype("Int64") + bf_result = bf_series.astype("Int64").to_pandas() -def test_astype_safe(session): - input = pd.Series(["hello", "world", "3.11", "4000"]) - exepcted = pd.Series( - [None, None, 3.11, 4000], - dtype="Float64", - index=pd.Index([0, 1, 2, 3], dtype="Int64"), - ) - result = session.read_pandas(input).astype("Float64", errors="null").to_pandas() - bigframes.testing.utils.assert_series_equal(result, exepcted) - - -def test_series_astype_w_invalid_error(session): - input = pd.Series(["hello", "world", "3.11", "4000"]) - with pytest.raises(ValueError): - session.read_pandas(input).astype("Float64", errors="bad_value") - - -def test_astype_numeric_to_int(scalars_df_index, scalars_pandas_df_index): - # TODO: supply a reason why this isn't compatible with pandas 1.x - pytest.importorskip("pandas", minversion="2.0.0") - column = "numeric_col" - to_type = "Int64" - bf_result = scalars_df_index[column].astype(to_type).to_pandas() - # Truncate to int to avoid TypeError - pd_result = ( - scalars_pandas_df_index[column] - .apply(lambda x: None if pd.isna(x) else math.trunc(x)) - .astype(to_type) - ) - bigframes.testing.utils.assert_series_equal(bf_result, pd_result) - - -@pytest.mark.parametrize( - ("column", "to_type"), - [ - ("timestamp_col", "int64[pyarrow]"), - ("datetime_col", "int64[pyarrow]"), - ("time_col", "int64[pyarrow]"), - ], -) -def test_date_time_astype_int( - scalars_df_index, scalars_pandas_df_index, column, to_type -): - # TODO: supply a reason why this isn't compatible with pandas 1.x - pytest.importorskip("pandas", minversion="2.0.0") - bf_result = scalars_df_index[column].astype(to_type).to_pandas() - pd_result = scalars_pandas_df_index[column].astype(to_type) - bigframes.testing.utils.assert_series_equal(bf_result, pd_result, check_dtype=False) - assert bf_result.dtype == "Int64" + pd.testing.assert_series_equal(bf_result, pd_result, check_index_type=False) -def test_string_astype_int(session): - pd_series = pd.Series(["4", "-7", "0", "-03"]) - bf_series = series.Series(pd_series, session=session) - - pd_result = pd_series.astype("Int64") - bf_result = bf_series.astype("Int64").to_pandas() - - bigframes.testing.utils.assert_series_equal( - bf_result, pd_result, check_index_type=False - ) - - -def test_string_astype_float(session): +def test_string_astype_float(): pd_series = pd.Series( - ["1", "-1", "-0", "000", "-03.235", "naN", "-inf", "INf", ".33", "7.235e-8"] + ["1", "-1", "-0", "000", " -03.235", "naN", "-inf", "INf", ".33", "7.235e-8"] ) - bf_series = series.Series(pd_series, session=session) + bf_series = series.Series(pd_series) pd_result = pd_series.astype("Float64") bf_result = bf_series.astype("Float64").to_pandas() - bigframes.testing.utils.assert_series_equal( - bf_result, pd_result, check_index_type=False - ) - - -def test_string_astype_date(session): - if int(pa.__version__.split(".")[0]) < 15: - pytest.skip( - "Avoid pyarrow.lib.ArrowNotImplementedError: " - "Unsupported cast from string to date32 using function cast_date32." - ) - - pd_series = pd.Series(["2014-08-15", "2215-08-15", "2016-02-29"]).astype( - pd.ArrowDtype(pa.string()) - ) - - bf_series = series.Series(pd_series, session=session) - - # TODO(b/340885567): fix type error - pd_result = pd_series.astype("date32[day][pyarrow]") # type: ignore - bf_result = bf_series.astype("date32[day][pyarrow]").to_pandas() - - bigframes.testing.utils.assert_series_equal( - bf_result, pd_result, check_index_type=False - ) - - -def test_string_astype_datetime(session): - pd_series = pd.Series( - ["2014-08-15 08:15:12", "2015-08-15 08:15:12.654754", "2016-02-29 00:00:00"] - ).astype(pd.ArrowDtype(pa.string())) - - bf_series = series.Series(pd_series, session=session) - - pd_result = pd_series.astype(pd.ArrowDtype(pa.timestamp("us"))) - bf_result = bf_series.astype(pd.ArrowDtype(pa.timestamp("us"))).to_pandas() - - bigframes.testing.utils.assert_series_equal( - bf_result, pd_result, check_index_type=False - ) - - -def test_string_astype_timestamp(session): - pd_series = pd.Series( - [ - "2014-08-15 08:15:12+00:00", - "2015-08-15 08:15:12.654754+05:00", - "2016-02-29 00:00:00+08:00", - ] - ).astype(pd.ArrowDtype(pa.string())) - - bf_series = series.Series(pd_series, session=session) - - pd_result = pd_series.astype(pd.ArrowDtype(pa.timestamp("us", tz="UTC"))) - bf_result = bf_series.astype( - pd.ArrowDtype(pa.timestamp("us", tz="UTC")) - ).to_pandas() - - bigframes.testing.utils.assert_series_equal( - bf_result, pd_result, check_index_type=False - ) - - -def test_timestamp_astype_string(session): - bf_series = series.Series( - [ - "2014-08-15 08:15:12+00:00", - "2015-08-15 08:15:12.654754+05:00", - "2016-02-29 00:00:00+08:00", - ], - session=session, - ).astype(pd.ArrowDtype(pa.timestamp("us", tz="UTC"))) - - expected_result = pd.Series( - [ - "2014-08-15 08:15:12+00", - "2015-08-15 03:15:12.654754+00", - "2016-02-28 16:00:00+00", - ] - ) - bf_result = bf_series.astype(pa.string()).to_pandas() - - bigframes.testing.utils.assert_series_equal( - bf_result, expected_result, check_index_type=False, check_dtype=False - ) - assert bf_result.dtype == "string[pyarrow]" - - -@pytest.mark.parametrize("errors", ["raise", "null"]) -def test_float_astype_json(errors, session): - data = ["1.25", "2500000000.1", None, "-12323.24"] - bf_series = series.Series(data, dtype=dtypes.FLOAT_DTYPE, session=session) - - bf_result = bf_series.astype(dtypes.JSON_DTYPE, errors=errors) - assert bf_result.dtype == dtypes.JSON_DTYPE - bf_result_pandas = bf_result.to_pandas() - - expected_data = [float(x) if x is not None else None for x in data] - expected_result = pd.Series(expected_data, dtype=dtypes.JSON_DTYPE) - expected_result.index = expected_result.index.astype("Int64") - bigframes.testing.utils.assert_series_equal(bf_result_pandas, expected_result) - - -def test_float_astype_json_str(session): - data = ["1.25", "2500000000.1", None, "-12323.24"] - bf_series = series.Series(data, dtype=dtypes.FLOAT_DTYPE, session=session) - - bf_result = bf_series.astype("json") - assert bf_result.dtype == dtypes.JSON_DTYPE - - expected_data = [float(x) if x is not None else None for x in data] - expected_result = pd.Series(expected_data, dtype=dtypes.JSON_DTYPE) - expected_result.index = expected_result.index.astype("Int64") - bigframes.testing.utils.assert_series_equal(bf_result.to_pandas(), expected_result) - - -@pytest.mark.parametrize("errors", ["raise", "null"]) -def test_string_astype_json(errors, session): - data = [ - "1", - None, - '["1","3","5"]', - '{"a":1,"b":["x","y"],"c":{"x":[],"z":false}}', - ] - bf_series = series.Series(data, dtype=dtypes.STRING_DTYPE, session=session) - - bf_result = bf_series.astype(dtypes.JSON_DTYPE, errors=errors) - assert bf_result.dtype == dtypes.JSON_DTYPE - - pd_result = bf_series.to_pandas().astype(dtypes.JSON_DTYPE) - bigframes.testing.utils.assert_series_equal(bf_result.to_pandas(), pd_result) - - -def test_string_astype_json_in_safe_mode(session): - data = ["this is not a valid json string"] - bf_series = series.Series(data, dtype=dtypes.STRING_DTYPE, session=session) - bf_result = bf_series.astype(dtypes.JSON_DTYPE, errors="null") - assert bf_result.dtype == dtypes.JSON_DTYPE - - expected = pd.Series([None], dtype=dtypes.JSON_DTYPE) - expected.index = expected.index.astype("Int64") - bigframes.testing.utils.assert_series_equal(bf_result.to_pandas(), expected) - - -def test_string_astype_json_raise_error(session): - data = ["this is not a valid json string"] - bf_series = series.Series(data, dtype=dtypes.STRING_DTYPE, session=session) - with pytest.raises( - google.api_core.exceptions.BadRequest, - match="syntax error while parsing value", - ): - bf_series.astype(dtypes.JSON_DTYPE, errors="raise").to_pandas() - - -@pytest.mark.parametrize("errors", ["raise", "null"]) -@pytest.mark.parametrize( - ("data", "to_type"), - [ - pytest.param(["1", "10.0", None], dtypes.INT_DTYPE, id="to_int"), - pytest.param(["0.0001", "2500000000", None], dtypes.FLOAT_DTYPE, id="to_float"), - pytest.param(["true", "false", None], dtypes.BOOL_DTYPE, id="to_bool"), - pytest.param(['"str"', None], dtypes.STRING_DTYPE, id="to_string"), - pytest.param( - ['"str"', None], - dtypes.TIME_DTYPE, - id="invalid", - marks=pytest.mark.xfail(raises=TypeError), - ), - ], -) -def test_json_astype_others(data, to_type, errors, session): - bf_series = series.Series(data, dtype=dtypes.JSON_DTYPE, session=session) - - bf_result = bf_series.astype(to_type, errors=errors) - assert bf_result.dtype == to_type - - load_data = [json.loads(item) if item is not None else None for item in data] - expected = pd.Series(load_data, dtype=to_type) - expected.index = expected.index.astype("Int64") - bigframes.testing.utils.assert_series_equal(bf_result.to_pandas(), expected) - - -@pytest.mark.parametrize( - ("data", "to_type"), - [ - pytest.param(["10.2", None], dtypes.INT_DTYPE, id="to_int"), - pytest.param(["false", None], dtypes.FLOAT_DTYPE, id="to_float"), - pytest.param(["10.2", None], dtypes.BOOL_DTYPE, id="to_bool"), - pytest.param(["true", None], dtypes.STRING_DTYPE, id="to_string"), - ], -) -def test_json_astype_others_raise_error(data, to_type, session): - bf_series = series.Series(data, dtype=dtypes.JSON_DTYPE, session=session) - with pytest.raises(google.api_core.exceptions.BadRequest): - bf_series.astype(to_type, errors="raise").to_pandas() - - -@pytest.mark.parametrize( - ("data", "to_type"), - [ - pytest.param(["10.2", None], dtypes.INT_DTYPE, id="to_int"), - pytest.param(["false", None], dtypes.FLOAT_DTYPE, id="to_float"), - pytest.param(["10.2", None], dtypes.BOOL_DTYPE, id="to_bool"), - pytest.param(["true", None], dtypes.STRING_DTYPE, id="to_string"), - ], -) -def test_json_astype_others_in_safe_mode(data, to_type, session): - bf_series = series.Series(data, dtype=dtypes.JSON_DTYPE, session=session) - bf_result = bf_series.astype(to_type, errors="null") - assert bf_result.dtype == to_type - - expected = pd.Series([None, None], dtype=to_type) - expected.index = expected.index.astype("Int64") - bigframes.testing.utils.assert_series_equal(bf_result.to_pandas(), expected) + pd.testing.assert_series_equal(bf_result, pd_result, check_index_type=False) @pytest.mark.parametrize( @@ -4156,7 +2549,9 @@ def test_iloc_single_integer(scalars_df_index, scalars_pandas_df_index, index): assert bf_result == pd_result -def test_iloc_single_integer_out_of_bound_error(scalars_df_index): +def test_iloc_single_integer_out_of_bound_error( + scalars_df_index, scalars_pandas_df_index +): with pytest.raises(IndexError, match="single positional indexer is out-of-bounds"): scalars_df_index.string_col.iloc[99] @@ -4165,7 +2560,7 @@ def test_loc_bool_series_explicit_index(scalars_df_index, scalars_pandas_df_inde bf_result = scalars_df_index.string_col.loc[scalars_df_index.bool_col].to_pandas() pd_result = scalars_pandas_df_index.string_col.loc[scalars_pandas_df_index.bool_col] - bigframes.testing.utils.assert_series_equal( + pd.testing.assert_series_equal( bf_result, pd_result, ) @@ -4181,7 +2576,7 @@ def test_loc_bool_series_default_index( scalars_pandas_df_default_index.bool_col ] - assert_frame_equal( + assert_pandas_df_equal_ignore_ordering( bf_result.to_frame(), pd_result.to_frame(), ) @@ -4226,7 +2621,7 @@ def test_rename(scalars_df_index, scalars_pandas_df_index): bf_result = scalars_df_index.string_col.rename("newname") pd_result = scalars_pandas_df_index.string_col.rename("newname") - bigframes.testing.utils.assert_series_equal( + pd.testing.assert_series_equal( bf_result.to_pandas(), pd_result, ) @@ -4236,7 +2631,7 @@ def test_rename_nonstring(scalars_df_index, scalars_pandas_df_index): bf_result = scalars_df_index.string_col.rename((4, 2)) pd_result = scalars_pandas_df_index.string_col.rename((4, 2)) - bigframes.testing.utils.assert_series_equal( + pd.testing.assert_series_equal( bf_result.to_pandas(), pd_result, ) @@ -4248,7 +2643,7 @@ def test_rename_dict_same_type(scalars_df_index, scalars_pandas_df_index): pd_result.index = pd_result.index.astype("Int64") - bigframes.testing.utils.assert_series_equal( + pd.testing.assert_series_equal( bf_result.to_pandas(), pd_result, ) @@ -4258,7 +2653,7 @@ def test_rename_axis(scalars_df_index, scalars_pandas_df_index): bf_result = scalars_df_index.string_col.rename_axis("newindexname") pd_result = scalars_pandas_df_index.string_col.rename_axis("newindexname") - bigframes.testing.utils.assert_series_equal( + pd.testing.assert_series_equal( bf_result.to_pandas(), pd_result, ) @@ -4275,7 +2670,7 @@ def test_loc_list_string_index(scalars_df_index, scalars_pandas_df_index): bf_result = scalars_df_index.string_col.loc[index_list] pd_result = scalars_pandas_df_index.string_col.loc[index_list] - bigframes.testing.utils.assert_series_equal( + pd.testing.assert_series_equal( bf_result.to_pandas(), pd_result, ) @@ -4287,7 +2682,7 @@ def test_loc_list_integer_index(scalars_df_index, scalars_pandas_df_index): bf_result = scalars_df_index.bool_col.loc[index_list] pd_result = scalars_pandas_df_index.bool_col.loc[index_list] - bigframes.testing.utils.assert_series_equal( + pd.testing.assert_series_equal( bf_result.to_pandas(), pd_result, ) @@ -4303,7 +2698,7 @@ def test_loc_list_multiindex(scalars_df_index, scalars_pandas_df_index): bf_result = scalars_df_multiindex.int64_too.loc[index_list] pd_result = scalars_pandas_df_multiindex.int64_too.loc[index_list] - bigframes.testing.utils.assert_series_equal( + pd.testing.assert_series_equal( bf_result.to_pandas(), pd_result, ) @@ -4315,7 +2710,7 @@ def test_iloc_list(scalars_df_index, scalars_pandas_df_index): bf_result = scalars_df_index.string_col.iloc[index_list] pd_result = scalars_pandas_df_index.string_col.iloc[index_list] - bigframes.testing.utils.assert_series_equal( + pd.testing.assert_series_equal( bf_result.to_pandas(), pd_result, ) @@ -4329,7 +2724,7 @@ def test_iloc_list_nameless(scalars_df_index, scalars_pandas_df_index): pd_series = scalars_pandas_df_index.string_col.rename(None) pd_result = pd_series.iloc[index_list] - bigframes.testing.utils.assert_series_equal( + pd.testing.assert_series_equal( bf_result.to_pandas(), pd_result, ) @@ -4344,7 +2739,7 @@ def test_loc_list_nameless(scalars_df_index, scalars_pandas_df_index): pd_series = scalars_pandas_df_index.string_col.rename(None) pd_result = pd_series.loc[index_list] - bigframes.testing.utils.assert_series_equal( + pd.testing.assert_series_equal( bf_result.to_pandas(), pd_result, ) @@ -4360,7 +2755,7 @@ def test_loc_bf_series_string_index(scalars_df_index, scalars_pandas_df_index): bf_result = scalars_df_index.date_col.loc[bf_string_series] pd_result = scalars_pandas_df_index.date_col.loc[pd_string_series] - bigframes.testing.utils.assert_series_equal( + pd.testing.assert_series_equal( bf_result.to_pandas(), pd_result, ) @@ -4378,7 +2773,7 @@ def test_loc_bf_series_multiindex(scalars_df_index, scalars_pandas_df_index): bf_result = scalars_df_multiindex.int64_too.loc[bf_string_series] pd_result = scalars_pandas_df_multiindex.int64_too.loc[pd_string_series] - bigframes.testing.utils.assert_series_equal( + pd.testing.assert_series_equal( bf_result.to_pandas(), pd_result, ) @@ -4391,7 +2786,7 @@ def test_loc_bf_index_integer_index(scalars_df_index, scalars_pandas_df_index): bf_result = scalars_df_index.date_col.loc[bf_index] pd_result = scalars_pandas_df_index.date_col.loc[pd_index] - bigframes.testing.utils.assert_series_equal( + pd.testing.assert_series_equal( bf_result.to_pandas(), pd_result, ) @@ -4405,7 +2800,7 @@ def test_loc_single_index_with_duplicate(scalars_df_index, scalars_pandas_df_ind index = "Hello, World!" bf_result = scalars_df_index.date_col.loc[index] pd_result = scalars_pandas_df_index.date_col.loc[index] - bigframes.testing.utils.assert_series_equal( + pd.testing.assert_series_equal( bf_result.to_pandas(), pd_result, ) @@ -4426,17 +2821,15 @@ def test_series_bool_interpretation_error(scalars_df_index): def test_query_job_setters(scalars_dfs): - # if allow_large_results=False, might not create query job - with bigframes.option_context("compute.allow_large_results", True): - job_ids = set() - df, _ = scalars_dfs - series = df["int64_col"] - assert series.query_job is not None - repr(series) - job_ids.add(series.query_job.job_id) - series.to_pandas() - job_ids.add(series.query_job.job_id) - assert len(job_ids) == 2 + job_ids = set() + df, _ = scalars_dfs + series = df["int64_col"] + assert series.query_job is not None + repr(series) + job_ids.add(series.query_job.job_id) + series.to_pandas() + job_ids.add(series.query_job.job_id) + assert len(job_ids) == 2 @pytest.mark.parametrize( @@ -4450,9 +2843,9 @@ def test_query_job_setters(scalars_dfs): ([1, 1, 1, 1, 1],), ], ) -def test_is_monotonic_increasing(series_input, session): - scalars_df = series.Series(series_input, dtype=pd.Int64Dtype(), session=session) - scalars_pandas_df = pd.Series(series_input, dtype=pd.Int64Dtype()) +def test_is_monotonic_increasing(series_input): + scalars_df = series.Series(series_input) + scalars_pandas_df = pd.Series(series_input) assert ( scalars_df.is_monotonic_increasing == scalars_pandas_df.is_monotonic_increasing ) @@ -4469,8 +2862,8 @@ def test_is_monotonic_increasing(series_input, session): ([1, 1, 1, 1, 1],), ], ) -def test_is_monotonic_decreasing(series_input, session): - scalars_df = series.Series(series_input, session=session) +def test_is_monotonic_decreasing(series_input): + scalars_df = series.Series(series_input) scalars_pandas_df = pd.Series(series_input) assert ( scalars_df.is_monotonic_decreasing == scalars_pandas_df.is_monotonic_decreasing @@ -4490,7 +2883,7 @@ def test_map_dict_input(scalars_dfs): pd_result = pd_result.astype("Int64") # pandas type differences bf_result = scalars_df.string_col.map(local_map) - bigframes.testing.utils.assert_series_equal( + pd.testing.assert_series_equal( bf_result.to_pandas(), pd_result, ) @@ -4509,7 +2902,7 @@ def test_map_series_input(scalars_dfs): pd_result = scalars_pandas_df.int64_too.map(pd_map_series) bf_result = scalars_df.int64_too.map(bf_map_series) - bigframes.testing.utils.assert_series_equal( + pd.testing.assert_series_equal( bf_result.to_pandas(), pd_result, ) @@ -4529,472 +2922,3 @@ def test_map_series_input_duplicates_error(scalars_dfs): scalars_pandas_df.int64_too.map(pd_map_series) with pytest.raises(pd.errors.InvalidIndexError): scalars_df.int64_too.map(bf_map_series, verify_integrity=True) - - -@pytest.mark.parametrize( - ("frac", "n", "random_state"), - [ - (None, 4, None), - (0.5, None, None), - (None, 4, 10), - (0.5, None, 10), - (None, None, None), - ], - ids=[ - "n_wo_random_state", - "frac_wo_random_state", - "n_w_random_state", - "frac_w_random_state", - "n_default", - ], -) -def test_sample(scalars_dfs, frac, n, random_state): - scalars_df, _ = scalars_dfs - df = scalars_df.int64_col.sample(frac=frac, n=n, random_state=random_state) - bf_result = df.to_pandas() - - n = 1 if n is None else n - expected_sample_size = round(frac * scalars_df.shape[0]) if frac is not None else n - assert bf_result.shape[0] == expected_sample_size - - -def test_series_iter( - scalars_df_index, - scalars_pandas_df_index, -): - for bf_i, pd_i in zip( - scalars_df_index["int64_too"], scalars_pandas_df_index["int64_too"] - ): - assert bf_i == pd_i - - -@pytest.mark.parametrize( - ( - "col", - "lambda_", - ), - [ - pytest.param("int64_col", lambda x: x * x + x + 1), - pytest.param("int64_col", lambda x: x % 2 == 1), - pytest.param("string_col", lambda x: x + "_suffix"), - ], - ids=[ - "lambda_int_int", - "lambda_int_bool", - "lambda_str_str", - ], -) -def test_apply_lambda(scalars_dfs, col, lambda_): - scalars_df, scalars_pandas_df = scalars_dfs - - bf_col = scalars_df[col] - - # Can't be applied to BigFrames Series without by_row=False - with pytest.raises(ValueError, match="by_row=False"): - bf_col.apply(lambda_) - - bf_result = bf_col.apply(lambda_, by_row=False).to_pandas() - - pd_col = scalars_pandas_df[col] - if pd.__version__[:3] in ("2.2", "2.3", "3.0"): - pd_result = pd_col.apply(lambda_, by_row=False) - else: - pd_result = pd_col.apply(lambda_) - - # ignore dtype check, which are Int64 and object respectively - # Some columns implicitly convert to floating point. Use check_exact=False to ensure we're "close enough" - assert_series_equal( - bf_result, pd_result, check_dtype=False, check_exact=False, rtol=0.001 - ) - - -@pytest.mark.parametrize( - ("ufunc",), - [ - pytest.param(numpy.log), - pytest.param(numpy.sqrt), - pytest.param(numpy.sin), - ], - ids=[ - "log", - "sqrt", - "sin", - ], -) -def test_apply_numpy_ufunc(scalars_dfs, ufunc): - scalars_df, scalars_pandas_df = scalars_dfs - - bf_col = scalars_df["int64_col"] - - # Can't be applied to BigFrames Series without by_row=False - with pytest.raises(ValueError, match="by_row=False"): - bf_col.apply(ufunc) - - bf_result = bf_col.apply(ufunc, by_row=False).to_pandas() - - pd_col = scalars_pandas_df["int64_col"] - pd_result = pd_col.apply(ufunc) - - assert_series_equal(bf_result, pd_result) - - -@pytest.mark.parametrize( - ("ufunc",), - [ - pytest.param(numpy.add), - pytest.param(numpy.divide), - ], - ids=[ - "add", - "divide", - ], -) -def test_combine_series_ufunc(scalars_dfs, ufunc): - scalars_df, scalars_pandas_df = scalars_dfs - - bf_col = scalars_df["int64_col"].dropna() - bf_result = bf_col.combine(bf_col, ufunc).to_pandas() - - pd_col = scalars_pandas_df["int64_col"].dropna() - pd_result = pd_col.combine(pd_col, ufunc) - - assert_series_equal(bf_result, pd_result, check_dtype=False) - - -def test_combine_scalar_ufunc(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - - bf_col = scalars_df["int64_col"].dropna() - bf_result = bf_col.combine(2.5, numpy.add).to_pandas() - - pd_col = scalars_pandas_df["int64_col"].dropna() - pd_result = pd_col.combine(2.5, numpy.add) - - assert_series_equal(bf_result, pd_result, check_dtype=False) - - -def test_apply_simple_udf(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - - def foo(x): - return x * x + 2 * x + 3 - - bf_col = scalars_df["int64_col"] - - # Can't be applied to BigFrames Series without by_row=False - with pytest.raises(ValueError, match="by_row=False"): - bf_col.apply(foo) - - bf_result = bf_col.apply(foo, by_row=False).to_pandas() - - pd_col = scalars_pandas_df["int64_col"] - - if pd.__version__[:3] in ("2.2", "2.3", "3.0"): - pd_result = pd_col.apply(foo, by_row=False) - else: - pd_result = pd_col.apply(foo) - - # ignore dtype check, which are Int64 and object respectively - # Some columns implicitly convert to floating point. Use check_exact=False to ensure we're "close enough" - assert_series_equal( - bf_result, pd_result, check_dtype=False, check_exact=False, rtol=0.001 - ) - - -@pytest.mark.parametrize( - ("col", "lambda_", "exception"), - [ - pytest.param("int64_col", {1: 2, 3: 4}, ValueError), - pytest.param("int64_col", numpy.square, TypeError), - pytest.param("string_col", lambda x: x.capitalize(), AttributeError), - ], - ids=[ - "not_callable", - "numpy_ufunc", - "custom_lambda", - ], -) -def test_apply_not_supported(scalars_dfs, col, lambda_, exception): - scalars_df, _ = scalars_dfs - - bf_col = scalars_df[col] - with pytest.raises(exception): - bf_col.apply(lambda_, by_row=False) - - -def test_series_pipe( - scalars_df_index, - scalars_pandas_df_index, -): - column = "int64_too" - - def foo(x: int, y: int, df): - return (df + x) % y - - bf_result = ( - scalars_df_index[column] - .pipe((foo, "df"), x=7, y=9) - .pipe(lambda x: x**2) - .to_pandas() - ) - - pd_result = ( - scalars_pandas_df_index[column].pipe((foo, "df"), x=7, y=9).pipe(lambda x: x**2) - ) - - assert_series_equal(bf_result, pd_result) - - -@pytest.mark.parametrize( - ("data"), - [ - pytest.param([1, 2, 3], id="int"), - pytest.param([[1, 2, 3], [], numpy.nan, [3, 4]], id="int_array"), - pytest.param( - [["A", "AA", "AAA"], ["BB", "B"], numpy.nan, [], ["C"]], id="string_array" - ), - pytest.param( - [ - {"A": {"x": 1.0}, "B": "b"}, - {"A": {"y": 2.0}, "B": "bb"}, - {"A": {"z": 4.0}}, - {}, - numpy.nan, - ], - id="struct_array", - ), - ], -) -def test_series_explode(data): - s = bigframes.pandas.Series(data) - pd_s = s.to_pandas() - bigframes.testing.utils.assert_series_equal( - s.explode().to_pandas(), - pd_s.explode(), - check_index_type=False, - check_dtype=False, - ) - - -@pytest.mark.parametrize( - ("index", "ignore_index"), - [ - pytest.param(None, True, id="default_index"), - pytest.param(None, False, id="ignore_default_index"), - pytest.param([5, 1, 3, 2], True, id="unordered_index"), - pytest.param([5, 1, 3, 2], False, id="ignore_unordered_index"), - pytest.param(["z", "x", "a", "b"], True, id="str_index"), - pytest.param(["z", "x", "a", "b"], False, id="ignore_str_index"), - pytest.param( - pd.Index(["z", "x", "a", "b"], name="idx"), True, id="str_named_index" - ), - pytest.param( - pd.Index(["z", "x", "a", "b"], name="idx"), - False, - id="ignore_str_named_index", - ), - pytest.param( - pd.MultiIndex.from_frame( - pd.DataFrame({"idx0": [5, 1, 3, 2], "idx1": ["z", "x", "a", "b"]}) - ), - True, - id="multi_index", - ), - pytest.param( - pd.MultiIndex.from_frame( - pd.DataFrame({"idx0": [5, 1, 3, 2], "idx1": ["z", "x", "a", "b"]}) - ), - False, - id="ignore_multi_index", - ), - ], -) -def test_series_explode_w_index(index, ignore_index): - data = [[], [200.0, 23.12], [4.5, -9.0], [1.0]] - s = bigframes.pandas.Series(data, index=index) - pd_s = pd.Series(data, index=index) - # TODO(b/340885567): fix type error - bigframes.testing.utils.assert_series_equal( - s.explode(ignore_index=ignore_index).to_pandas(), # type: ignore - pd_s.explode(ignore_index=ignore_index).astype(pd.Float64Dtype()), # type: ignore - check_index_type=False, - ) - - -@pytest.mark.parametrize( - ("ignore_index", "ordered"), - [ - pytest.param(True, True, id="include_index_ordered"), - pytest.param(True, False, id="include_index_unordered"), - pytest.param(False, True, id="ignore_index_ordered"), - ], -) -def test_series_explode_reserve_order(ignore_index, ordered): - data = [numpy.random.randint(0, 10, 10) for _ in range(10)] - s = bigframes.pandas.Series(data) - pd_s = pd.Series(data) - - # TODO(b/340885567): fix type error - res = s.explode(ignore_index=ignore_index).to_pandas(ordered=ordered) # type: ignore - # TODO(b/340885567): fix type error - pd_res = pd_s.explode(ignore_index=ignore_index).astype(pd.Int64Dtype()) # type: ignore - pd_res.index = pd_res.index.astype(pd.Int64Dtype()) - bigframes.testing.utils.assert_series_equal( - res if ordered else res.sort_index(), - pd_res, - ) - - -def test_series_explode_w_aggregate(): - data = [[1, 2, 3], [], numpy.nan, [3, 4]] - s = bigframes.pandas.Series(data) - pd_s = pd.Series(data) - assert s.explode().sum() == pd_s.explode().sum() - - -def test_series_construct_empty_array(): - # TODO: supply a reason why this isn't compatible with pandas 1.x - pytest.importorskip("pandas", minversion="2.0.0") - s = bigframes.pandas.Series([[]]) - expected = pd.Series( - [[]], - dtype=pd.ArrowDtype(pa.list_(pa.float64())), - index=pd.Index([0], dtype=pd.Int64Dtype()), - ) - bigframes.testing.utils.assert_series_equal( - expected, - s.to_pandas(), - ) - - -@pytest.mark.parametrize( - ("data"), - [ - pytest.param(numpy.nan, id="null"), - pytest.param([numpy.nan], id="null_array"), - pytest.param([[]], id="empty_array"), - pytest.param([numpy.nan, []], id="null_and_empty_array"), - ], -) -def test_series_explode_null(data): - s = bigframes.pandas.Series(data) - bigframes.testing.utils.assert_series_equal( - s.explode().to_pandas(), - s.to_pandas().explode(), - check_dtype=False, - ) - - -@pytest.mark.parametrize( - ("append", "level", "col", "rule"), - [ - pytest.param(False, None, "timestamp_col", "75D"), - pytest.param(True, 1, "timestamp_col", "25W"), - pytest.param(False, None, "datetime_col", "3ME"), - pytest.param(True, "timestamp_col", "timestamp_col", "1YE"), - ], -) -def test_resample(scalars_df_index, scalars_pandas_df_index, append, level, col, rule): - # TODO: supply a reason why this isn't compatible with pandas 1.x - pytest.importorskip("pandas", minversion="2.0.0") - scalars_df_index = scalars_df_index.set_index(col, append=append)["int64_col"] - scalars_pandas_df_index = scalars_pandas_df_index.set_index(col, append=append)[ - "int64_col" - ] - bf_result = scalars_df_index.resample(rule=rule, level=level).min().to_pandas() - pd_result = scalars_pandas_df_index.resample(rule=rule, level=level).min() - # TODO: (b/484364312) - pd_result.index.names = bf_result.index.names - bigframes.testing.utils.assert_series_equal(bf_result, pd_result) - - -def test_series_struct_get_field_by_attribute( - nested_structs_df, nested_structs_pandas_df -): - if Version(pd.__version__) < Version("2.2.0"): - pytest.skip("struct accessor is not supported before pandas 2.2") - - bf_series = nested_structs_df["person"] - df_series = nested_structs_pandas_df["person"] - - bigframes.testing.utils.assert_series_equal( - bf_series.address.city.to_pandas(), - df_series.struct.field("address").struct.field("city"), - check_dtype=False, - check_index=False, - ) - bigframes.testing.utils.assert_series_equal( - bf_series.address.country.to_pandas(), - df_series.struct.field("address").struct.field("country"), - check_dtype=False, - check_index=False, - ) - - -def test_series_struct_fields_in_dir(nested_structs_df): - series = nested_structs_df["person"] - - assert "age" in dir(series) - assert "address" in dir(series) - assert "city" in dir(series.address) - assert "country" in dir(series.address) - - -def test_series_struct_class_attributes_shadow_struct_fields(nested_structs_df): - series = nested_structs_df["person"] - - assert series.name == "person" - - -def test_series_to_pandas_dry_run(scalars_df_index): - bf_series = scalars_df_index["int64_col"] - - result = bf_series.to_pandas(dry_run=True) - - assert isinstance(result, pd.Series) - assert len(result) > 0 - - -def test_series_item(session): - # Test with a single item - bf_s_single = bigframes.pandas.Series([42], session=session) - pd_s_single = pd.Series([42]) - assert bf_s_single.item() == pd_s_single.item() - - -def test_series_item_with_multiple(session): - # Test with multiple items - bf_s_multiple = bigframes.pandas.Series([1, 2, 3], session=session) - pd_s_multiple = pd.Series([1, 2, 3]) - - try: - pd_s_multiple.item() - except ValueError as e: - expected_message = str(e) - else: - raise AssertionError("Expected ValueError from pandas, but didn't get one") - - with pytest.raises(ValueError, match=re.escape(expected_message)): - bf_s_multiple.item() - - -def test_series_item_with_empty(session): - # Test with an empty Series - bf_s_empty = bigframes.pandas.Series([], dtype="Int64", session=session) - pd_s_empty = pd.Series([], dtype="Int64") - - try: - pd_s_empty.item() - except ValueError as e: - expected_message = str(e) - else: - raise AssertionError("Expected ValueError from pandas, but didn't get one") - - with pytest.raises(ValueError, match=re.escape(expected_message)): - bf_s_empty.item() - - -def test_series_sql(session): - s = bigframes.pandas.Series([], session=session) - - assert len(s.sql) > 0 diff --git a/tests/system/small/test_series_io.py b/tests/system/small/test_series_io.py deleted file mode 100644 index 83c2de70cae..00000000000 --- a/tests/system/small/test_series_io.py +++ /dev/null @@ -1,136 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -import numpy -import numpy.testing -import pandas as pd -import pytest - -import bigframes -import bigframes.series - - -def test_to_pandas_override_global_option(scalars_df_index): - with bigframes.option_context("compute.allow_large_results", True): - bf_series = scalars_df_index["int64_col"] - - # Direct call to_pandas uses global default setting (allow_large_results=True) - bf_series.to_pandas() - table_id = bf_series._query_job.destination.table_id - assert table_id is not None - - session = bf_series._block.session - - history_before = session.execution_history().to_dataframe() - queries_before = ( - len(history_before[history_before["job_type"] == "query"]) - if "job_type" in history_before.columns - else 0 - ) - - # When allow_large_results=False, a query_job object should not be created. - # Therefore, the table_id should remain unchanged. - bf_series.to_pandas(allow_large_results=False) - assert bf_series._query_job.destination.table_id == table_id - - history_after = session.execution_history().to_dataframe() - queries_after = len(history_after[history_after["job_type"] == "query"]) - - assert (queries_after - queries_before) == 1 - - -@pytest.mark.parametrize( - ("kwargs", "message"), - [ - pytest.param( - {"sampling_method": "head"}, - r"DEPRECATED[\S\s]*sampling_method[\S\s]*Series.sample", - id="sampling_method", - ), - pytest.param( - {"random_state": 10}, - r"DEPRECATED[\S\s]*random_state[\S\s]*Series.sample", - id="random_state", - ), - pytest.param( - {"max_download_size": 10}, - r"DEPRECATED[\S\s]*max_download_size[\S\s]*Series.to_pandas_batches", - id="max_download_size", - ), - ], -) -def test_to_pandas_warns_deprecated_parameters(scalars_df_index, kwargs, message): - s: bigframes.series.Series = scalars_df_index["int64_col"] - with pytest.warns(FutureWarning, match=message): - s.to_pandas( - # limits only apply for allow_large_result=True - allow_large_results=True, - **kwargs, - ) - - -@pytest.mark.parametrize( - ("page_size", "max_results", "allow_large_results"), - [ - pytest.param(None, None, True), - pytest.param(2, None, False), - pytest.param(None, 1, True), - pytest.param(2, 5, False), - pytest.param(3, 6, True), - pytest.param(3, 100, False), - pytest.param(100, 100, True), - ], -) -def test_to_pandas_batches(scalars_dfs, page_size, max_results, allow_large_results): - scalars_df, scalars_pandas_df = scalars_dfs - bf_series = scalars_df["int64_col"] - pd_series = scalars_pandas_df["int64_col"] - - total_rows = 0 - expected_total_rows = ( - min(max_results, len(pd_series)) if max_results else len(pd_series) - ) - - hit_last_page = False - for s in bf_series.to_pandas_batches( - page_size=page_size, - max_results=max_results, - allow_large_results=allow_large_results, - ): - assert not hit_last_page - - actual_rows = s.shape[0] - expected_rows = ( - min(page_size, expected_total_rows) if page_size else expected_total_rows - ) - - assert actual_rows <= expected_rows - if actual_rows < expected_rows: - assert page_size - hit_last_page = True - - pd.testing.assert_series_equal( - s, pd_series[total_rows : total_rows + actual_rows] - ) - total_rows += actual_rows - - assert total_rows == expected_total_rows - - -def test_to_numpy(scalars_dfs): - bf_df, pd_df = scalars_dfs - - bf_result = numpy.array(bf_df["int64_too"], dtype="int64") - pd_result = numpy.array(pd_df["int64_too"], dtype="int64") - - numpy.testing.assert_array_equal(bf_result, pd_result) diff --git a/tests/system/small/test_session.py b/tests/system/small/test_session.py index 76788da8a11..bf72e444eba 100644 --- a/tests/system/small/test_session.py +++ b/tests/system/small/test_session.py @@ -11,97 +11,27 @@ # 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 io -import json import random -import re import tempfile import textwrap -import time import typing -import warnings -from typing import List, Optional, Sequence +from typing import List -import bigframes_vendored.pandas.io.gbq as vendored_pandas_gbq -import db_dtypes # type:ignore -import google +import google.api_core.exceptions import google.cloud.bigquery as bigquery import numpy as np import pandas as pd -import pandas.arrays as arrays -import pyarrow as pa import pytest import bigframes +import bigframes.core.indexes.index import bigframes.dataframe import bigframes.dtypes import bigframes.ml.linear_model -import bigframes.session.execution_spec -import bigframes.testing -from bigframes.testing import utils - -all_write_engines = pytest.mark.parametrize( - "write_engine", - [ - "default", - "bigquery_inline", - "bigquery_load", - "bigquery_streaming", - # TODO(b/502298527): Reenable bigquery_write test - # "bigquery_write", - ], -) - - -@pytest.fixture(scope="module") -def df_and_local_csv(scalars_df_index): - # The auto detects of BigQuery load job have restrictions to detect the bytes, - # datetime, numeric and geometry types, so they're skipped here. - drop_columns = [ - "bytes_col", - "datetime_col", - "numeric_col", - "geography_col", - "duration_col", - ] - scalars_df_index = scalars_df_index.drop(columns=drop_columns) - - with tempfile.TemporaryDirectory() as dir: - # Prepares local CSV file for reading - path = dir + "/test_read_csv_w_local_csv.csv" - scalars_df_index.to_csv(path, index=True) - yield scalars_df_index, path - - -@pytest.fixture(scope="module") -def df_and_gcs_csv(scalars_df_index, gcs_folder): - # The auto detects of BigQuery load job have restrictions to detect the bytes, - # datetime, numeric and geometry types, so they're skipped here. - drop_columns = [ - "bytes_col", - "datetime_col", - "numeric_col", - "geography_col", - "duration_col", - ] - scalars_df_index = scalars_df_index.drop(columns=drop_columns) - - path = gcs_folder + "test_read_csv_w_gcs_csv*.csv" - read_path = utils.get_first_file_from_wildcard(path) - scalars_df_index.to_csv(path, index=True) - return scalars_df_index, read_path - -@pytest.fixture(scope="module") -def df_and_gcs_csv_for_two_columns(scalars_df_index, gcs_folder): - # Some tests require only two columns to be present in the CSV file. - selected_cols = ["bool_col", "int64_col"] - scalars_df_index = scalars_df_index[selected_cols] - - path = gcs_folder + "df_and_gcs_csv_for_two_columns*.csv" - read_path = utils.get_first_file_from_wildcard(path) - scalars_df_index.to_csv(path, index=True) - return scalars_df_index, read_path +FIRST_FILE = "000000000000" def test_read_gbq_tokyo( @@ -111,23 +41,17 @@ def test_read_gbq_tokyo( tokyo_location: str, ): df = session_tokyo.read_gbq(scalars_table_tokyo, index_col=["rowindex"]) - df.sort_index(inplace=True) + result = df.sort_index().to_pandas() expected = scalars_pandas_df_index - exec_result = session_tokyo._executor.execute( - df._block.expr, - bigframes.session.execution_spec.ExecutionSpec( - destination_spec=bigframes.session.execution_spec.EphemeralTableSpec() - ), - ) - assert exec_result.query_job is not None - assert exec_result.query_job.location == tokyo_location + _, query_job = df._block.expr.start_query() + assert query_job.location == tokyo_location - assert len(expected) == exec_result.batches().approx_total_rows + pd.testing.assert_frame_equal(result, expected) @pytest.mark.parametrize( - ("query_or_table", "columns"), + ("query_or_table", "col_order"), [ pytest.param( "{scalars_table_id}", ["bool_col", "int64_col"], id="two_cols_in_table" @@ -143,48 +67,27 @@ def test_read_gbq_tokyo( ["my_strings"], id="one_cols_in_query", ), + pytest.param( + "{scalars_table_id}", + ["unknown"], + marks=pytest.mark.xfail( + raises=ValueError, + reason="Column `unknown` not found in this table.", + ), + id="unknown_col", + ), ], ) -def test_read_gbq_w_columns( +def test_read_gbq_w_col_order( session: bigframes.Session, scalars_table_id: str, query_or_table: str, - columns: List[str], + col_order: List[str], ): df = session.read_gbq( - query_or_table.format(scalars_table_id=scalars_table_id), columns=columns + query_or_table.format(scalars_table_id=scalars_table_id), col_order=col_order ) - assert df.columns.tolist() == columns - - -def test_read_gbq_w_unknown_column( - session: bigframes.Session, - scalars_table_id: str, -): - with pytest.raises( - ValueError, - match=re.escape("Column 'int63_col' is not found. Did you mean 'int64_col'?"), - ): - session.read_gbq( - scalars_table_id, - columns=["string_col", "int63_col", "bool_col"], - ) - - -def test_read_gbq_w_unknown_index_col( - session: bigframes.Session, - scalars_table_id: str, -): - with pytest.raises( - ValueError, - match=re.escape( - "Column 'int64_two' of `index_col` not found in this table. Did you mean 'int64_too'?" - ), - ): - session.read_gbq( - scalars_table_id, - index_col=["int64_col", "int64_two"], - ) + assert df.columns.tolist() == col_order @pytest.mark.parametrize( @@ -201,10 +104,9 @@ def test_read_gbq_w_unknown_index_col( CONCAT(t.string_col, "_2") AS my_strings, t.int64_col > 0 AS my_bools, FROM `{scalars_table_id}` AS t - ORDER BY my_strings """, ["my_strings"], - id="string_index_w_order_by", + id="string_index", ), pytest.param( "SELECT GENERATE_UUID() AS uuid, 0 AS my_value FROM UNNEST(GENERATE_ARRAY(1, 20))", @@ -327,69 +229,31 @@ def test_read_gbq_w_anonymous_query_results_table(session: bigframes.Session): df = session.read_gbq(destination, index_col="name") result = df.to_pandas() expected.index = expected.index.astype(result.index.dtype) - bigframes.testing.utils.assert_frame_equal(result, expected, check_dtype=False) + pd.testing.assert_frame_equal(result, expected, check_dtype=False) def test_read_gbq_w_primary_keys_table( session: bigframes.Session, usa_names_grouped_table: bigquery.Table ): - # Validate that the table we're querying has a primary key. table = usa_names_grouped_table - table_constraints = table.table_constraints - assert table_constraints is not None - primary_key = table_constraints.primary_key - assert primary_key is not None - primary_keys = primary_key.columns + # TODO(b/305264153): Use public properties to fetch primary keys once + # added to google-cloud-bigquery. + primary_keys = ( + table._properties.get("tableConstraints", {}) + .get("primaryKey", {}) + .get("columns") + ) assert len(primary_keys) != 0 df = session.read_gbq(f"{table.project}.{table.dataset_id}.{table.table_id}") result = df.head(100).to_pandas() - # Verify that primary keys are used as the index. - assert list(result.index.names) == list(primary_keys) - # Verify that the DataFrame is already sorted by primary keys. sorted_result = result.sort_values(primary_keys) - bigframes.testing.utils.assert_frame_equal(result, sorted_result) + pd.testing.assert_frame_equal(result, sorted_result) # Verify that we're working from a snapshot rather than a copy of the table. - assert "FOR SYSTEM_TIME AS OF" in df.sql - - -def test_read_gbq_w_primary_keys_table_and_filters( - session: bigframes.Session, usa_names_grouped_table: bigquery.Table -): - """ - Verify fix for internal issue 338039517, where using filters didn't use the - primary keys for indexing / ordering. - """ - # Validate that the table we're querying has a primary key. - table = usa_names_grouped_table - table_constraints = table.table_constraints - assert table_constraints is not None - primary_key = table_constraints.primary_key - assert primary_key is not None - primary_keys = primary_key.columns - assert len(primary_keys) != 0 - - df = session.read_gbq( - f"{table.project}.{table.dataset_id}.{table.table_id}", - filters=typing.cast( - vendored_pandas_gbq.FiltersType, - [ - ("name", "LIKE", "W%"), - ("total_people", ">", 100), - ], - ), - ) - result = df.to_pandas() - - # Verify that primary keys are used as the index. - assert list(result.index.names) == list(primary_keys) - - # Verify that the DataFrame is already sorted by primary keys. - sorted_result = result.sort_values(primary_keys) - bigframes.testing.utils.assert_frame_equal(result, sorted_result) + assert "FOR SYSTEM_TIME AS OF TIMESTAMP" in df.sql @pytest.mark.parametrize( @@ -431,432 +295,18 @@ def test_read_gbq_w_max_results( assert bf_result.shape[0] == max_results -@pytest.mark.parametrize( - ("sql_template", "expected_statement_type"), - ( - pytest.param( - """ - CREATE OR REPLACE TABLE `{dataset_id}.test_read_gbq_w_ddl` ( - `col_a` INT64, - `col_b` STRING - ); - """, - "CREATE_TABLE", - id="ddl-create-table", - ), - pytest.param( - # From https://cloud.google.com/bigquery/docs/boosted-tree-classifier-tutorial - """ - CREATE OR REPLACE VIEW `{dataset_id}.test_read_gbq_w_create_view` - AS - SELECT - age, - workclass, - marital_status, - education_num, - occupation, - hours_per_week, - income_bracket, - CASE - WHEN MOD(functional_weight, 10) < 8 THEN 'training' - WHEN MOD(functional_weight, 10) = 8 THEN 'evaluation' - WHEN MOD(functional_weight, 10) = 9 THEN 'prediction' - END AS dataframe - FROM - `bigquery-public-data.ml_datasets.census_adult_income`; - """, - "CREATE_VIEW", - id="ddl-create-view", - ), - pytest.param( - """ - CREATE OR REPLACE TABLE `{dataset_id}.test_read_gbq_w_dml` ( - `col_a` INT64, - `col_b` STRING - ); - - INSERT INTO `{dataset_id}.test_read_gbq_w_dml` - VALUES (123, 'hello world'); - """, - "SCRIPT", - id="dml", - ), - ), -) -def test_read_gbq_w_script_no_select( - session, dataset_id: str, sql_template: str, expected_statement_type: str -): - df = session.read_gbq(sql_template.format(dataset_id=dataset_id)).to_pandas() - assert df["statement_type"][0] == expected_statement_type - - -def test_read_gbq_twice_with_same_timestamp(session, penguins_table_id): - df1 = session.read_gbq(penguins_table_id) - time.sleep(1) - df2 = session.read_gbq(penguins_table_id) - df1.columns = [ - "species1", - "island1", - "culmen_length_mm1", - "culmen_depth_mm1", - "flipper_length_mm1", - "body_mass_g1", - "sex1", - ] - df3 = df1.join(df2) - assert df3 is not None - - -@pytest.mark.parametrize( - "source_table", - [ - # Wildcard tables - "bigquery-public-data.noaa_gsod.gsod194*", - # Materialized views - "bigframes-dev.bigframes_tests_sys.base_table_mat_view", - ], -) -def test_read_gbq_warns_time_travel_disabled(session, source_table): - with warnings.catch_warnings(record=True) as warned: - session.read_gbq(source_table, use_cache=False) - assert len(warned) == 1 - assert warned[0].category == bigframes.exceptions.TimeTravelDisabledWarning - - -def test_read_gbq_w_ambigous_name( - session: bigframes.Session, -): - # Ensure read_gbq works when table and column share a name - df = ( - session.read_gbq("bigframes-dev.bigframes_tests_sys.ambiguous_name") - .sort_values("x", ascending=False) - .reset_index(drop=True) - .to_pandas() - ) - pd_df = pd.DataFrame({"x": [2, 1], "ambiguous_name": [20, 10]}) - bigframes.testing.utils.assert_frame_equal( - df, pd_df, check_dtype=False, check_index_type=False - ) - - -def test_read_gbq_table_clustered_with_filter(session: bigframes.Session): - df = session.read_gbq_table( - "bigquery-public-data.cloud_storage_geo_index.landsat_index", - filters=typing.cast( - vendored_pandas_gbq.FiltersType, - [[("sensor_id", "LIKE", "OLI%")], [("sensor_id", "LIKE", "%TIRS")]], - ), - columns=["sensor_id"], - ) - sensors = df.groupby(["sensor_id"]).agg("count").to_pandas(ordered=False) - assert "OLI" in sensors.index - assert "TIRS" in sensors.index - assert "OLI_TIRS" in sensors.index - - -_GSOD_ALL_TABLES = "bigquery-public-data.noaa_gsod.gsod*" -_GSOD_1930S = "bigquery-public-data.noaa_gsod.gsod193*" - - -@pytest.mark.parametrize( - "api_method", - # Test that both methods work as there's a risk that read_gbq / - # read_gbq_table makes for an infinite loop. Table reads can convert to - # queries and read_gbq reads from tables. - ["read_gbq", "read_gbq_table"], -) -@pytest.mark.parametrize( - ("filters", "table_id", "index_col", "columns", "max_results"), - [ - pytest.param( - [("_table_suffix", ">=", "1930"), ("_table_suffix", "<=", "1939")], - _GSOD_ALL_TABLES, - ["stn", "wban", "year", "mo", "da"], - ["temp", "max", "min"], - 100, - id="all", - ), - pytest.param( - (), # filters - _GSOD_1930S, - (), # index_col - ["temp", "max", "min"], - None, # max_results - id="columns", - ), - pytest.param( - [("_table_suffix", ">=", "1930"), ("_table_suffix", "<=", "1939")], - _GSOD_ALL_TABLES, - (), # index_col, - (), # columns - None, # max_results - id="filters", - ), - pytest.param( - (), # filters - _GSOD_1930S, - ["stn", "wban", "year", "mo", "da"], - (), # columns - None, # max_results - id="index_col", - ), - pytest.param( - (), # filters - _GSOD_1930S, - (), # index_col - (), # columns - 100, # max_results - id="max_results", - ), - ], -) -def test_read_gbq_wildcard( - session: bigframes.Session, - api_method: str, - filters, - table_id: str, - index_col: Sequence[str], - columns: Sequence[str], - max_results: Optional[int], -): - table_metadata = session.bqclient.get_table(table_id) - method = getattr(session, api_method) - df = method( - table_id, - filters=filters, - index_col=index_col, - columns=columns, - max_results=max_results, - ) - num_rows, num_columns = df.shape - - if index_col: - assert list(df.index.names) == list(index_col) - else: - assert df.index.name is None - - expected_columns = ( - columns - if columns - else [ - field.name - for field in table_metadata.schema - if field.name not in index_col and field.name not in columns - ] - ) - assert list(df.columns) == expected_columns - assert num_rows > 0 - assert num_columns == len(expected_columns) - - -@pytest.mark.parametrize( - ("config"), - [ - { - "query": { - "useQueryCache": True, - "maximumBytesBilled": "1000000000", - "timeoutMs": 120_000, - } - }, - pytest.param( - {"query": {"useQueryCache": True, "timeoutMs": 50}}, - marks=pytest.mark.xfail( - raises=google.api_core.exceptions.BadRequest, - reason="Expected failure due to timeout being set too short.", - ), - ), - pytest.param( - {"query": {"useQueryCache": False, "maximumBytesBilled": "100"}}, - marks=pytest.mark.xfail( - raises=google.api_core.exceptions.BadRequest, - reason="Expected failure when the query exceeds the maximum bytes billed limit.", - ), - ), - ], -) -def test_read_gbq_with_configuration( - session: bigframes.Session, scalars_table_id: str, config: dict -): - query = f"""SELECT - t.float64_col * 2 AS my_floats, - CONCAT(t.string_col, "_2") AS my_strings, - t.int64_col > 0 AS my_bools, - FROM `{scalars_table_id}` AS t - """ - - df = session.read_gbq(query, configuration=config) - - assert df.shape == (9, 3) - - -def test_read_gbq_with_custom_global_labels( - session: bigframes.Session, scalars_table_id: str -): - # Ensure we use thread-local variables to avoid conflicts with parallel tests. - with bigframes.option_context("compute.extra_query_labels", {}): - bigframes.options.compute.assign_extra_query_labels(test1=1, test2="abc") - bigframes.options.compute.extra_query_labels["test3"] = False - - query_job = session.read_gbq(scalars_table_id).query_job - - # No real job created from read_gbq, so we should expect 0 labels - assert query_job is not None - assert query_job.labels == {} - # No labels outside of the option_context. - assert len(bigframes.options.compute.extra_query_labels) == 0 - - -def test_read_gbq_external_table(session: bigframes.Session): - # Verify the table is external to ensure it hasn't been altered - external_table_id = "bigframes-dev.bigframes_tests_sys.parquet_external_table" - external_table = session.bqclient.get_table(external_table_id) - assert external_table.table_type == "EXTERNAL" - - df = session.read_gbq(external_table_id) - - assert list(df.columns) == ["idx", "s1", "s2", "s3", "s4", "i1", "f1", "i2", "f2"] - assert df["i1"].max() == 99 - - -def test_read_gbq_w_json(session): - sql = """ - SELECT 0 AS id, JSON_OBJECT('boolean', True) AS json_col, - UNION ALL - SELECT 1, JSON_OBJECT('int', 100), - UNION ALL - SELECT 2, JSON_OBJECT('float', 0.98), - UNION ALL - SELECT 3, JSON_OBJECT('string', 'hello world'), - UNION ALL - SELECT 4, JSON_OBJECT('array', [8, 9, 10]), - UNION ALL - SELECT 5, JSON_OBJECT('null', null), - UNION ALL - SELECT 6, JSON_OBJECT('b', 2, 'a', 1), - UNION ALL - SELECT - 7, - JSON_OBJECT( - 'dict', - JSON_OBJECT( - 'int', 1, - 'array', [JSON_OBJECT('foo', 1), JSON_OBJECT('bar', 'hello')] - ) - ), - """ - df = session.read_gbq(sql, index_col="id") - - assert df.dtypes["json_col"] == pd.ArrowDtype(db_dtypes.JSONArrowType()) - - assert df["json_col"][0] == '{"boolean":true}' - assert df["json_col"][1] == '{"int":100}' - assert df["json_col"][2] == '{"float":0.98}' - assert df["json_col"][3] == '{"string":"hello world"}' - assert df["json_col"][4] == '{"array":[8,9,10]}' - assert df["json_col"][5] == '{"null":null}' - - # Verifies JSON strings preserve array order, regardless of dictionary key order. - assert df["json_col"][6] == '{"a":1,"b":2}' - assert df["json_col"][7] == '{"dict":{"array":[{"foo":1},{"bar":"hello"}],"int":1}}' - - -def test_read_gbq_w_json_and_compare_w_pandas_json(session): - df = session.read_gbq("SELECT JSON_OBJECT('foo', 10, 'bar', TRUE) AS json_col") - assert df.dtypes["json_col"] == pd.ArrowDtype(db_dtypes.JSONArrowType()) +def test_read_gbq_w_script_no_select(session, dataset_id: str): + ddl = f""" + CREATE TABLE `{dataset_id}.test_read_gbq_w_ddl` ( + `col_a` INT64, + `col_b` STRING + ); - # These JSON strings are compatible with BigQuery's JSON storage, - pd_df = pd.DataFrame( - {"json_col": ['{"bar":true,"foo":10}']}, - dtype=pd.ArrowDtype(db_dtypes.JSONArrowType()), - ) - pd_df.index = pd_df.index.astype("Int64") - bigframes.testing.utils.assert_series_equal(df.dtypes, pd_df.dtypes) - bigframes.testing.utils.assert_series_equal( - df["json_col"].to_pandas(), pd_df["json_col"] - ) - - -def test_read_gbq_w_json_in_struct(session): - """Avoid regressions for internal issue 381148539.""" - sql = """ - SELECT 0 AS id, STRUCT(JSON_OBJECT('boolean', True) AS data, 1 AS number) AS struct_col - UNION ALL - SELECT 1, STRUCT(JSON_OBJECT('int', 100), 2), - UNION ALL - SELECT 2, STRUCT(JSON_OBJECT('float', 0.98), 3), - UNION ALL - SELECT 3, STRUCT(JSON_OBJECT('string', 'hello world'), 4), - UNION ALL - SELECT 4, STRUCT(JSON_OBJECT('array', [8, 9, 10]), 5), - UNION ALL - SELECT 5, STRUCT(JSON_OBJECT('null', null), 6), - UNION ALL - SELECT - 6, - STRUCT(JSON_OBJECT( - 'dict', - JSON_OBJECT( - 'int', 1, - 'array', [JSON_OBJECT('foo', 1), JSON_OBJECT('bar', 'hello')] - ) - ), 7), - """ - df = session.read_gbq(sql, index_col="id") - - assert isinstance(df.dtypes["struct_col"], pd.ArrowDtype) - assert isinstance(df.dtypes["struct_col"].pyarrow_dtype, pa.StructType) - - data = df["struct_col"].struct.field("data") - assert data.dtype == pd.ArrowDtype(db_dtypes.JSONArrowType()) - - assert data[0] == '{"boolean":true}' - assert data[1] == '{"int":100}' - assert data[2] == '{"float":0.98}' - assert data[3] == '{"string":"hello world"}' - assert data[4] == '{"array":[8,9,10]}' - assert data[5] == '{"null":null}' - assert data[6] == '{"dict":{"array":[{"foo":1},{"bar":"hello"}],"int":1}}' - - -def test_read_gbq_w_json_in_array(session): - sql = """ - SELECT - 0 AS id, - [ - JSON_OBJECT('boolean', True), - JSON_OBJECT('int', 100), - JSON_OBJECT('float', 0.98), - JSON_OBJECT('string', 'hello world'), - JSON_OBJECT('array', [8, 9, 10]), - JSON_OBJECT('null', null), - JSON_OBJECT( - 'dict', - JSON_OBJECT( - 'int', 1, - 'array', [JSON_OBJECT('bar', 'hello'), JSON_OBJECT('foo', 1)] - ) - ) - ] AS array_col, + INSERT INTO `{dataset_id}.test_read_gbq_w_ddl` + VALUES (123, 'hello world'); """ - df = session.read_gbq(sql, index_col="id") - - assert isinstance(df.dtypes["array_col"], pd.ArrowDtype) - assert isinstance(df.dtypes["array_col"].pyarrow_dtype, pa.ListType) - - data = df["array_col"] - assert data.list.len()[0] == 7 - assert data.list[0].dtype == pd.ArrowDtype(db_dtypes.JSONArrowType()) - - assert data[0] == [ - '{"boolean":true}', - '{"int":100}', - '{"float":0.98}', - '{"string":"hello world"}', - '{"array":[8,9,10]}', - '{"null":null}', - '{"dict":{"array":[{"bar":"hello"},{"foo":1}],"int":1}}', - ] + df = session.read_gbq(ddl).to_pandas() + assert df["statement_type"][0] == "SCRIPT" def test_read_gbq_model(session, penguins_linear_model_name): @@ -872,40 +322,7 @@ def test_read_pandas(session, scalars_dfs): result = df.to_pandas() expected = scalars_pandas_df - bigframes.testing.utils.assert_frame_equal(result, expected) - - -def test_read_pandas_series(session): - idx: pd.Index = pd.Index([2, 7, 1, 2, 8], dtype=pd.Int64Dtype()) - pd_series = pd.Series([3, 1, 4, 1, 5], dtype=pd.Int64Dtype(), index=idx) - bf_series = session.read_pandas(pd_series) - - bigframes.testing.utils.assert_series_equal(bf_series.to_pandas(), pd_series) - - -def test_read_pandas_index(session): - pd_idx: pd.Index = pd.Index([2, 7, 1, 2, 8], dtype=pd.Int64Dtype()) - bf_idx = session.read_pandas(pd_idx) - - bigframes.testing.utils.assert_index_equal(bf_idx.to_pandas(), pd_idx) - - -def test_read_pandas_w_unsupported_mixed_dtype(session): - with pytest.raises(ValueError, match="Could not convert"): - session.read_pandas(pd.DataFrame({"a": [1, "hello"]})) - - -def test_read_pandas_inline_respects_location(): - options = bigframes.BigQueryOptions(location="europe-west1") - session = bigframes.Session(options) - - df = session.read_pandas(pd.DataFrame([[1, 2, 3], [4, 5, 6]])) - df.to_gbq() - - assert df.query_job is not None - - table = session.bqclient.get_table(df.query_job.destination) - assert table.location == "europe-west1" + pd.testing.assert_frame_equal(result, expected) def test_read_pandas_col_label_w_space(session: bigframes.Session): @@ -917,7 +334,7 @@ def test_read_pandas_col_label_w_space(session: bigframes.Session): ) result = session.read_pandas(expected).to_pandas() - bigframes.testing.utils.assert_frame_equal( + pd.testing.assert_frame_equal( result, expected, check_index_type=False, check_dtype=False ) @@ -925,7 +342,7 @@ def test_read_pandas_col_label_w_space(session: bigframes.Session): def test_read_pandas_multi_index(session, scalars_pandas_df_multi_index): df = session.read_pandas(scalars_pandas_df_multi_index) result = df.to_pandas() - bigframes.testing.utils.assert_frame_equal(result, scalars_pandas_df_multi_index) + pd.testing.assert_frame_equal(result, scalars_pandas_df_multi_index) def test_read_pandas_rowid_exists_adds_suffix(session, scalars_pandas_df_default_index): @@ -933,9 +350,7 @@ def test_read_pandas_rowid_exists_adds_suffix(session, scalars_pandas_df_default pandas_df["rowid"] = np.arange(pandas_df.shape[0]) df_roundtrip = session.read_pandas(pandas_df).to_pandas() - bigframes.testing.utils.assert_frame_equal( - df_roundtrip, pandas_df, check_dtype=False - ) + pd.testing.assert_frame_equal(df_roundtrip, pandas_df, check_dtype=False) def test_read_pandas_tokyo( @@ -944,877 +359,418 @@ def test_read_pandas_tokyo( tokyo_location: str, ): df = session_tokyo.read_pandas(scalars_pandas_df_index) - df.to_gbq() + result = df.to_pandas() expected = scalars_pandas_df_index - result = session_tokyo._executor.execute( - df._block.expr, - bigframes.session.execution_spec.ExecutionSpec( - destination_spec=bigframes.session.execution_spec.EphemeralTableSpec() - ), - ) - assert result.query_job is not None - assert result.query_job.location == tokyo_location + _, query_job = df._block.expr.start_query() + assert query_job.location == tokyo_location - assert len(expected) == result.batches().approx_total_rows + pd.testing.assert_frame_equal(result, expected) -@all_write_engines -def test_read_pandas_timedelta_dataframes(session, write_engine): - pytest.importorskip( - "pandas", - minversion="2.0.0", - reason="old versions don't support local casting to arrow duration", +def test_read_csv_gcs_default_engine(session, scalars_dfs, gcs_folder): + scalars_df, _ = scalars_dfs + if scalars_df.index.name is not None: + path = gcs_folder + "test_read_csv_gcs_default_engine_w_index*.csv" + else: + path = gcs_folder + "test_read_csv_gcs_default_engine_wo_index*.csv" + read_path = path.replace("*", FIRST_FILE) + scalars_df.to_csv(path, index=False) + dtype = scalars_df.dtypes.to_dict() + dtype.pop("geography_col") + df = session.read_csv( + read_path, + # Convert default pandas dtypes to match BigQuery DataFrames dtypes. + dtype=dtype, ) - pandas_df = pd.DataFrame({"my_col": pd.to_timedelta([1, 2, 3], unit="d")}) - actual_result = session.read_pandas( - pandas_df, write_engine=write_engine - ).to_pandas() - expected_result = pandas_df.astype(bigframes.dtypes.TIMEDELTA_DTYPE) - expected_result.index = expected_result.index.astype(bigframes.dtypes.INT_DTYPE) + # TODO(chelsealin): If we serialize the index, can more easily compare values. + pd.testing.assert_index_equal(df.columns, scalars_df.columns) - bigframes.testing.utils.assert_frame_equal(actual_result, expected_result) - - -@all_write_engines -def test_read_pandas_timedelta_series(session, write_engine): - expected_series = pd.Series(pd.to_timedelta([1, 2, 3], unit="d")).astype( - "timedelta64[ns]" - ) - - actual_result = ( - session.read_pandas(expected_series, write_engine=write_engine) - .to_pandas() - .astype("timedelta64[ns]") - ) + # The auto detects of BigQuery load job have restrictions to detect the bytes, + # numeric and geometry types, so they're skipped here. + df = df.drop(columns=["bytes_col", "numeric_col", "geography_col"]) + scalars_df = scalars_df.drop(columns=["bytes_col", "numeric_col", "geography_col"]) + assert df.shape[0] == scalars_df.shape[0] + pd.testing.assert_series_equal(df.dtypes, scalars_df.dtypes) - bigframes.testing.utils.assert_series_equal( - actual_result, expected_series, check_index_type=False - ) +def test_read_csv_gcs_bq_engine(session, scalars_dfs, gcs_folder): + scalars_df, _ = scalars_dfs + if scalars_df.index.name is not None: + path = gcs_folder + "test_read_csv_gcs_bq_engine_w_index*.csv" + else: + path = gcs_folder + "test_read_csv_gcs_bq_engine_wo_index*.csv" + scalars_df.to_csv(path, index=False) + df = session.read_csv(path, engine="bigquery") -@all_write_engines -def test_read_pandas_timedelta_index(session, write_engine): - expected_index = pd.to_timedelta([1, 2, 3], unit="d").astype( - "timedelta64[ns]" - ) # to_timedelta returns an index + # TODO(chelsealin): If we serialize the index, can more easily compare values. + pd.testing.assert_index_equal(df.columns, scalars_df.columns) - actual_result = ( - session.read_pandas(expected_index, write_engine=write_engine) - .to_pandas() - .astype("timedelta64[ns]") + # The auto detects of BigQuery load job have restrictions to detect the bytes, + # datetime, numeric and geometry types, so they're skipped here. + df = df.drop(columns=["bytes_col", "datetime_col", "numeric_col", "geography_col"]) + scalars_df = scalars_df.drop( + columns=["bytes_col", "datetime_col", "numeric_col", "geography_col"] ) + assert df.shape[0] == scalars_df.shape[0] + pd.testing.assert_series_equal(df.dtypes, scalars_df.dtypes) - bigframes.testing.utils.assert_index_equal(actual_result, expected_index) +@pytest.mark.parametrize( + "sep", + [ + pytest.param(",", id="default_sep"), + pytest.param("\t", id="custom_sep"), + ], +) +def test_read_csv_local_default_engine(session, scalars_dfs, sep): + scalars_df, scalars_pandas_df = scalars_dfs + with tempfile.TemporaryDirectory() as dir: + path = dir + "/test_read_csv_local_default_engine.csv" + # Using the pandas to_csv method because the BQ one does not support local write. + scalars_pandas_df.to_csv(path, index=False, sep=sep) + dtype = scalars_df.dtypes.to_dict() + dtype.pop("geography_col") + df = session.read_csv( + path, + sep=sep, + # Convert default pandas dtypes to match BigQuery DataFrames dtypes. + dtype=dtype, + ) -@all_write_engines -def test_read_pandas_json_dataframes(session, write_engine): - json_data = [ - "1", - None, - '["1","3","5"]', - '{"a":1,"b":["x","y"],"c":{"x":[],"z":false}}', - ] - expected_df = pd.DataFrame( - {"my_col": pd.Series(json_data, dtype=bigframes.dtypes.JSON_DTYPE)} - ) + # TODO(chelsealin): If we serialize the index, can more easily compare values. + pd.testing.assert_index_equal(df.columns, scalars_df.columns) - actual_result = session.read_pandas( - expected_df, write_engine=write_engine - ).to_pandas() + # The auto detects of BigQuery load job have restrictions to detect the bytes, + # numeric and geometry types, so they're skipped here. + df = df.drop(columns=["bytes_col", "numeric_col", "geography_col"]) + scalars_df = scalars_df.drop( + columns=["bytes_col", "numeric_col", "geography_col"] + ) + assert df.shape[0] == scalars_df.shape[0] + pd.testing.assert_series_equal(df.dtypes, scalars_df.dtypes) - bigframes.testing.utils.assert_frame_equal( - actual_result, expected_df, check_index_type=False - ) +@pytest.mark.parametrize( + "sep", + [ + pytest.param(",", id="default_sep"), + pytest.param("\t", id="custom_sep"), + ], +) +def test_read_csv_local_bq_engine(session, scalars_dfs, sep): + scalars_df, scalars_pandas_df = scalars_dfs + with tempfile.TemporaryDirectory() as dir: + path = dir + "/test_read_csv_local_bq_engine.csv" + # Using the pandas to_csv method because the BQ one does not support local write. + scalars_pandas_df.to_csv(path, index=False, sep=sep) + df = session.read_csv(path, engine="bigquery", sep=sep) -@all_write_engines -def test_read_pandas_json_series(session, write_engine): - json_data = [ - "1", - None, - '[1,"3",null,{"a":null}]', - '{"a":1,"b":["x","y"],"c":{"x":[],"y":null,"z":false}}', - ] - expected_series = pd.Series(json_data, dtype=bigframes.dtypes.JSON_DTYPE) - - actual_result = session.read_pandas( - expected_series, write_engine=write_engine - ).to_pandas() - bigframes.testing.utils.assert_series_equal( - actual_result, expected_series, check_index_type=False - ) + # TODO(chelsealin): If we serialize the index, can more easily compare values. + pd.testing.assert_index_equal(df.columns, scalars_df.columns) + # The auto detects of BigQuery load job have restrictions to detect the bytes, + # datetime, numeric and geometry types, so they're skipped here. + df = df.drop( + columns=["bytes_col", "datetime_col", "numeric_col", "geography_col"] + ) + scalars_df = scalars_df.drop( + columns=["bytes_col", "datetime_col", "numeric_col", "geography_col"] + ) + assert df.shape[0] == scalars_df.shape[0] + pd.testing.assert_series_equal(df.dtypes, scalars_df.dtypes) -@all_write_engines -def test_read_pandas_json_series_w_invalid_json(session, write_engine): - json_data = [ - "False", # Should be "false" - ] - pd_s = pd.Series(json_data, dtype=bigframes.dtypes.JSON_DTYPE) - with pytest.raises(json.JSONDecodeError): - session.read_pandas(pd_s, write_engine=write_engine) +def test_read_csv_localbuffer_bq_engine(session, scalars_dfs): + scalars_df, scalars_pandas_df = scalars_dfs + with tempfile.TemporaryDirectory() as dir: + path = dir + "/test_read_csv_local_bq_engine.csv" + # Using the pandas to_csv method because the BQ one does not support local write. + scalars_pandas_df.to_csv(path, index=False) + with open(path, "rb") as buffer: + df = session.read_csv(buffer, engine="bigquery") + # TODO(chelsealin): If we serialize the index, can more easily compare values. + pd.testing.assert_index_equal(df.columns, scalars_df.columns) -@all_write_engines -def test_read_pandas_json_index(session, write_engine): - json_data = [ - "1", - None, - '["1","3","5"]', - '{"a":1,"b":["x","y"],"c":{"x":[],"z":false}}', - ] - expected_index: pd.Index = pd.Index(json_data, dtype=bigframes.dtypes.JSON_DTYPE) - actual_result = session.read_pandas( - expected_index, write_engine=write_engine - ).to_pandas() - bigframes.testing.utils.assert_index_equal(actual_result, expected_index) + # The auto detects of BigQuery load job have restrictions to detect the bytes, + # datetime, numeric and geometry types, so they're skipped here. + df = df.drop( + columns=["bytes_col", "datetime_col", "numeric_col", "geography_col"] + ) + scalars_df = scalars_df.drop( + columns=["bytes_col", "datetime_col", "numeric_col", "geography_col"] + ) + assert df.shape[0] == scalars_df.shape[0] + pd.testing.assert_series_equal(df.dtypes, scalars_df.dtypes) @pytest.mark.parametrize( - ("write_engine"), + ("kwargs", "match"), [ - pytest.param("bigquery_load"), - ], -) -def test_read_pandas_w_nested_json_fails(session, write_engine): - data = [ - [{"json_field": "1"}], - [{"json_field": None}], - [{"json_field": '["1","3","5"]'}], - [{"json_field": '{"a":1,"b":["x","y"],"c":{"x":[],"z":false}}'}], - ] - # PyArrow currently lacks support for creating structs or lists containing extension types. - # See issue: https://github.com/apache/arrow/issues/45262 - pa_array = pa.array(data, type=pa.list_(pa.struct([("json_field", pa.string())]))) - pd_s = pd.Series( - arrays.ArrowExtensionArray(pa_array), # type: ignore - dtype=pd.ArrowDtype( - pa.list_(pa.struct([("json_field", bigframes.dtypes.JSON_ARROW_TYPE)])) + pytest.param( + {"engine": "bigquery", "names": []}, + "BigQuery engine does not support these arguments", + id="with_names", ), - ) - with pytest.raises( - NotImplementedError, match="Nested JSON types are currently unsupported" - ): - session.read_pandas(pd_s, write_engine=write_engine) - - -@pytest.mark.parametrize( - ("write_engine"), - [ - pytest.param("default"), - pytest.param("bigquery_inline"), - pytest.param("bigquery_streaming"), - # TODO(b/502298527): Reenable bigquery_write test - # pytest.param("bigquery_write"), - ], -) -def test_read_pandas_w_nested_json(session, write_engine): - # TODO: supply a reason why this isn't compatible with pandas 1.x - pytest.importorskip("pandas", minversion="2.0.0") - data = [ - [{"json_field": "1"}], - [{"json_field": None}], - [{"json_field": '["1","3","5"]'}], - [{"json_field": '{"a":1,"b":["x","y"],"c":{"x":[],"z":false}}'}], - ] - pa_array = pa.array(data, type=pa.list_(pa.struct([("json_field", pa.string())]))) - pd_s = pd.Series( - arrays.ArrowExtensionArray(pa_array), # type: ignore - dtype=pd.ArrowDtype( - pa.list_(pa.struct([("json_field", bigframes.dtypes.JSON_ARROW_TYPE)])) + pytest.param( + {"engine": "bigquery", "dtype": {}}, + "BigQuery engine does not support these arguments", + id="with_dtype", ), - ) - bq_s = ( - session.read_pandas(pd_s, write_engine=write_engine) - .to_pandas() - .reset_index(drop=True) - ) - bigframes.testing.utils.assert_series_equal(bq_s, pd_s) - - -@pytest.mark.parametrize( - ("write_engine"), - [ - pytest.param("default"), - pytest.param("bigquery_inline"), - pytest.param("bigquery_load"), - pytest.param("bigquery_streaming"), - ], -) -def test_read_pandas_w_nested_invalid_json(session, write_engine): - # TODO: supply a reason why this isn't compatible with pandas 1.x - pytest.importorskip("pandas", minversion="2.0.0") - data = [ - [{"json_field": "NULL"}], # Should be "null" - ] - pa_array = pa.array(data, type=pa.list_(pa.struct([("json_field", pa.string())]))) - pd_s = pd.Series( - arrays.ArrowExtensionArray(pa_array), # type: ignore - dtype=pd.ArrowDtype( - pa.list_(pa.struct([("json_field", bigframes.dtypes.JSON_ARROW_TYPE)])) + pytest.param( + {"engine": "bigquery", "index_col": False}, + "BigQuery engine only supports a single column name for `index_col`.", + id="with_index_col_false", ), - ) - - with pytest.raises(json.JSONDecodeError): - session.read_pandas(pd_s, write_engine=write_engine) - - -@pytest.mark.parametrize( - ("write_engine"), - [ - pytest.param("bigquery_load"), - ], -) -def test_read_pandas_w_nested_json_index_fails(session, write_engine): - data = [ - [{"json_field": "1"}], - [{"json_field": None}], - [{"json_field": '["1","3","5"]'}], - [{"json_field": '{"a":1,"b":["x","y"],"c":{"x":[],"z":false}}'}], - ] - # PyArrow currently lacks support for creating structs or lists containing extension types. - # See issue: https://github.com/apache/arrow/issues/45262 - pa_array = pa.array(data, type=pa.list_(pa.struct([("json_field", pa.string())]))) - pd_idx: pd.Index = pd.Index( - arrays.ArrowExtensionArray(pa_array), # type: ignore - dtype=pd.ArrowDtype( - pa.list_(pa.struct([("json_field", bigframes.dtypes.JSON_ARROW_TYPE)])) + pytest.param( + {"engine": "bigquery", "index_col": 5}, + "BigQuery engine only supports a single column name for `index_col`.", + id="with_index_col_not_str", ), - ) - with pytest.raises( - NotImplementedError, match="Nested JSON types are currently unsupported" - ): - session.read_pandas(pd_idx, write_engine=write_engine) - - -@pytest.mark.parametrize( - ("write_engine"), - [ - pytest.param("default"), - pytest.param("bigquery_inline"), - pytest.param("bigquery_streaming"), - # TODO(b/502298527): Reenable bigquery_write test - # pytest.param("bigquery_write"), - ], -) -def test_read_pandas_w_nested_json_index(session, write_engine): - # TODO: supply a reason why this isn't compatible with pandas 1.x - pytest.importorskip("pandas", minversion="2.0.0") - data = [ - [{"json_field": "1"}], - [{"json_field": None}], - [{"json_field": '["1","3","5"]'}], - [{"json_field": '{"a":1,"b":["x","y"],"c":{"x":[],"z":false}}'}], - ] - pa_array = pa.array(data, type=pa.list_(pa.struct([("name", pa.string())]))) - pd_idx: pd.Index = pd.Index( - arrays.ArrowExtensionArray(pa_array), # type: ignore - dtype=pd.ArrowDtype( - pa.list_(pa.struct([("name", bigframes.dtypes.JSON_ARROW_TYPE)])) + pytest.param( + {"engine": "bigquery", "usecols": [1, 2]}, + "BigQuery engine only supports an iterable of strings for `usecols`.", + id="with_usecols_invalid", + ), + pytest.param( + {"engine": "bigquery", "encoding": "ASCII"}, + "BigQuery engine only supports the following encodings", + id="with_encoding_invalid", ), - ) - bq_idx = session.read_pandas(pd_idx, write_engine=write_engine).to_pandas() - bigframes.testing.utils.assert_index_equal(bq_idx, pd_idx) - - -@all_write_engines -def test_read_csv_for_gcs_file_w_write_engine(session, df_and_gcs_csv, write_engine): - scalars_df, path = df_and_gcs_csv - - # Compares results for pandas and bigframes engines - pd_df = session.read_csv( - path, - index_col="rowindex", - write_engine=write_engine, - dtype=scalars_df.dtypes.to_dict(), - ) - bigframes.testing.utils.assert_frame_equal( - pd_df.to_pandas(), scalars_df.to_pandas() - ) - - if write_engine in ("default", "bigquery_load"): - bf_df = session.read_csv( - path, engine="bigquery", index_col="rowindex", write_engine=write_engine - ) - bigframes.testing.utils.assert_frame_equal(bf_df.to_pandas(), pd_df.to_pandas()) - - -@pytest.mark.parametrize( - "sep", - [ - pytest.param(",", id="default_sep"), - pytest.param("\t", id="custom_sep"), - ], -) -def test_read_csv_for_local_file_w_sep(session, df_and_local_csv, sep): - scalars_df, _ = df_and_local_csv - - with tempfile.TemporaryDirectory() as dir: - # Prepares local CSV file for reading - path = dir + "/test_read_csv_for_local_file_w_sep.csv" - scalars_df.to_csv(path, index=True, sep=sep) - - # Compares results for pandas and bigframes engines - with open(path, "rb") as buffer: - bf_df = session.read_csv( - buffer, engine="bigquery", index_col="rowindex", sep=sep - ) - with open(path, "rb") as buffer: - # Convert default pandas dtypes to match BigQuery DataFrames dtypes. - pd_df = session.read_csv( - buffer, index_col="rowindex", sep=sep, dtype=scalars_df.dtypes.to_dict() - ) - bigframes.testing.utils.assert_frame_equal( - bf_df.to_pandas(), scalars_df.to_pandas() - ) - bigframes.testing.utils.assert_frame_equal(bf_df.to_pandas(), pd_df.to_pandas()) - - -@pytest.mark.parametrize( - "index_col", - [ - pytest.param(None, id="none"), - pytest.param(False, id="false"), - pytest.param([], id="empty_list"), - ], -) -def test_read_csv_for_index_col_w_false(session, df_and_local_csv, index_col): - # Compares results for pandas and bigframes engines - scalars_df, path = df_and_local_csv - with open(path, "rb") as buffer: - bf_df = session.read_csv( - buffer, - engine="bigquery", - index_col=index_col, - ) - with open(path, "rb") as buffer: - # Convert default pandas dtypes to match BigQuery DataFrames dtypes. - pd_df = session.read_csv( - buffer, index_col=index_col, dtype=scalars_df.dtypes.to_dict() - ) - - assert bf_df.shape == pd_df.shape - - # BigFrames requires `sort_index()` because BigQuery doesn't preserve row IDs - # (b/280889935) or guarantee row ordering. - bf_df = bf_df.set_index("rowindex").sort_index() - pd_df = pd_df.set_index("rowindex") - bigframes.testing.utils.assert_frame_equal(bf_df.to_pandas(), pd_df.to_pandas()) - - -@pytest.mark.parametrize( - "index_col", - [ - pytest.param("rowindex", id="single_str"), - pytest.param(["rowindex", "bool_col"], id="multi_str"), - pytest.param(0, id="single_int"), - pytest.param([0, 2], id="multi_int"), - pytest.param([0, "bool_col"], id="mix_types"), ], ) -def test_read_csv_for_index_col(session, df_and_gcs_csv, index_col): - scalars_pandas_df, path = df_and_gcs_csv - bf_df = session.read_csv(path, engine="bigquery", index_col=index_col) - - # Convert default pandas dtypes to match BigQuery DataFrames dtypes. - pd_df = session.read_csv( - path, index_col=index_col, dtype=scalars_pandas_df.dtypes.to_dict() - ) - - assert bf_df.shape == pd_df.shape - bigframes.testing.utils.assert_frame_equal(bf_df.to_pandas(), pd_df.to_pandas()) +def test_read_csv_bq_engine_throws_not_implemented_error(session, kwargs, match): + with pytest.raises(NotImplementedError, match=match): + session.read_csv("", **kwargs) @pytest.mark.parametrize( - ("index_col", "error_type", "error_msg"), + ("kwargs", "match"), [ pytest.param( - True, ValueError, "The value of index_col couldn't be 'True'", id="true" - ), - pytest.param(100, ValueError, "out of bounds", id="single_int"), - pytest.param([0, 200], ValueError, "out of bounds", id="multi_int"), - pytest.param( - [0.1], TypeError, "it must contain either strings", id="invalid_iterable" + {"chunksize": 5}, + "'chunksize' and 'iterator' arguments are not supported.", + id="with_chunksize", ), pytest.param( - 3.14, TypeError, "Unsupported type for index_col", id="unsupported_type" + {"iterator": True}, + "'chunksize' and 'iterator' arguments are not supported.", + id="with_iterator", ), ], ) -def test_read_csv_raises_error_for_invalid_index_col( - session, df_and_gcs_csv, index_col, error_type, error_msg +def test_read_csv_default_engine_throws_not_implemented_error( + session, + scalars_df_index, + gcs_folder, + kwargs, + match, ): - _, path = df_and_gcs_csv - with pytest.raises( - error_type, - match=error_msg, - ): - session.read_csv(path, engine="bigquery", index_col=index_col) - - -def test_read_csv_for_gcs_wildcard_path(session, df_and_gcs_csv): - scalars_pandas_df, path = df_and_gcs_csv - path = path.replace(".csv", "*.csv") - - index_col = "rowindex" - bf_df = session.read_csv(path, engine="bigquery", index_col=index_col) - - # Convert default pandas dtypes to match BigQuery DataFrames dtypes. - # Also, `expand=True` is needed to read from wildcard paths. See details: - # https://github.com/fsspec/gcsfs/issues/616, - if not pd.__version__.startswith("1."): - storage_options = {"expand": True} - else: - storage_options = None - pd_df = session.read_csv( - path, - index_col=index_col, - dtype=scalars_pandas_df.dtypes.to_dict(), - storage_options=storage_options, + path = ( + gcs_folder + + "test_read_csv_gcs_default_engine_throws_not_implemented_error*.csv" ) + read_path = path.replace("*", FIRST_FILE) + scalars_df_index.to_csv(path) + with pytest.raises(NotImplementedError, match=match): + session.read_csv(read_path, **kwargs) - assert bf_df.shape == pd_df.shape - assert bf_df.columns.tolist() == pd_df.columns.tolist() - bigframes.testing.utils.assert_frame_equal(bf_df.to_pandas(), pd_df.to_pandas()) - - -def test_read_csv_for_names(session, df_and_gcs_csv_for_two_columns): - _, path = df_and_gcs_csv_for_two_columns - - names = ["a", "b", "c"] - bf_df = session.read_csv(path, engine="bigquery", names=names) - # Convert default pandas dtypes to match BigQuery DataFrames dtypes. - pd_df = session.read_csv(path, names=names, dtype=bf_df.dtypes.to_dict()) +def test_read_csv_gcs_default_engine_w_header(session, scalars_df_index, gcs_folder): + path = gcs_folder + "test_read_csv_gcs_default_engine_w_header*.csv" + read_path = path.replace("*", FIRST_FILE) + scalars_df_index.to_csv(path) - assert bf_df.shape == pd_df.shape - assert bf_df.columns.tolist() == pd_df.columns.tolist() + # Skips header=N rows, normally considers the N+1th row as the header, but overridden by + # passing the `names` argument. In this case, pandas will skip the N+1th row too, take + # the column names from `names`, and begin reading data from the N+2th row. + df = session.read_csv( + read_path, + header=2, + names=scalars_df_index.columns.to_list(), + ) + assert df.shape[0] == scalars_df_index.shape[0] - 2 + assert len(df.columns) == len(scalars_df_index.columns) - # BigFrames requires `sort_index()` because BigQuery doesn't preserve row IDs - # (b/280889935) or guarantee row ordering. - bf_df = bf_df.set_index(names[0]).sort_index() - pd_df = pd_df.set_index(names[0]) - bigframes.testing.utils.assert_frame_equal(bf_df.to_pandas(), pd_df.to_pandas()) +def test_read_csv_gcs_bq_engine_w_header(session, scalars_df_index, gcs_folder): + path = gcs_folder + "test_read_csv_gcs_bq_engine_w_header*.csv" + scalars_df_index.to_csv(path, index=False) -def test_read_csv_for_names_more_than_columns_can_raise_error( - session, df_and_gcs_csv_for_two_columns -): - _, path = df_and_gcs_csv_for_two_columns - names = ["a", "b", "c", "d"] - with pytest.raises( - ValueError, - match="Too many columns specified: expected 3 and found 4", - ): - session.read_csv(path, engine="bigquery", names=names) + # Skip the header and the first 2 data rows. Note that one line of header + # also got added while writing the csv through `to_csv`, so we would have to + # pass headers=3 in the `read_csv` to skip reading the header and two rows. + # Without provided schema, the column names would be like `bool_field_0`, + # `string_field_1` and etc. + df = session.read_csv(path, header=3, engine="bigquery") + assert df.shape[0] == scalars_df_index.shape[0] - 2 + assert len(df.columns) == len(scalars_df_index.columns) -def test_read_csv_for_names_less_than_columns(session, df_and_gcs_csv_for_two_columns): - _, path = df_and_gcs_csv_for_two_columns +def test_read_csv_local_default_engine_w_header(session, scalars_pandas_df_index): + with tempfile.TemporaryDirectory() as dir: + path = dir + "/test_read_csv_local_default_engine_w_header.csv" + # Using the pandas to_csv method because the BQ one does not support local write. + scalars_pandas_df_index.to_csv(path, index=False) - names = ["b", "c"] - bf_df = session.read_csv(path, engine="bigquery", names=names) + # Skips header=N rows. Normally row N+1 would be the header now, but overridden by + # passing the `names` argument. In this case, pandas will skip row N+1 too, infer + # the column names from `names`, and begin reading data from row N+2. + df = session.read_csv( + path, + header=2, + names=scalars_pandas_df_index.columns.to_list(), + ) + assert df.shape[0] == scalars_pandas_df_index.shape[0] - 2 + assert len(df.columns) == len(scalars_pandas_df_index.columns) - # Convert default pandas dtypes to match BigQuery DataFrames dtypes. - pd_df = session.read_csv(path, names=names, dtype=bf_df.dtypes.to_dict()) - assert bf_df.shape == pd_df.shape - assert bf_df.columns.tolist() == pd_df.columns.tolist() +def test_read_csv_local_bq_engine_w_header(session, scalars_pandas_df_index): + with tempfile.TemporaryDirectory() as dir: + path = dir + "/test_read_csv_local_bq_engine_w_header.csv" + # Using the pandas to_csv method because the BQ one does not support local write. + scalars_pandas_df_index.to_csv(path, index=False) - # Pandas's index name is None, while BigFrames's index name is "rowindex". - pd_df.index.name = "rowindex" - bigframes.testing.utils.assert_frame_equal(bf_df.to_pandas(), pd_df.to_pandas()) + # Skip the header and the first 2 data rows. Note that one line of + # header also got added while writing the csv through `to_csv`, so we + # would have to pass headers=3 in the `read_csv` to skip reading the + # header and two rows. Without provided schema, the column names would + # be like `bool_field_0`, `string_field_1` and etc. + df = session.read_csv(path, header=3, engine="bigquery") + assert df.shape[0] == scalars_pandas_df_index.shape[0] - 2 + assert len(df.columns) == len(scalars_pandas_df_index.columns) -def test_read_csv_for_names_less_than_columns_raise_error_when_index_col_set( - session, df_and_gcs_csv_for_two_columns +def test_read_csv_gcs_default_engine_w_index_col_name( + session, scalars_df_default_index, gcs_folder ): - _, path = df_and_gcs_csv_for_two_columns + path = gcs_folder + "test_read_csv_gcs_default_engine_w_index_col_name*.csv" + read_path = path.replace("*", FIRST_FILE) + scalars_df_default_index.to_csv(path) - names = ["b", "c"] - with pytest.raises( - KeyError, - match="ensure the number of `names` matches the number of columns in your data.", - ): - session.read_csv(path, engine="bigquery", names=names, index_col="rowindex") + df = session.read_csv(read_path, index_col="rowindex") + scalars_df_default_index = scalars_df_default_index.set_index( + "rowindex" + ).sort_index() + pd.testing.assert_index_equal(df.columns, scalars_df_default_index.columns) + assert df.index.name == "rowindex" -@pytest.mark.parametrize( - "index_col", - [ - pytest.param("a", id="single_str"), - pytest.param(["a", "b"], id="multi_str"), - pytest.param(0, id="single_int"), - ], -) -def test_read_csv_for_names_and_index_col( - session, df_and_gcs_csv_for_two_columns, index_col +def test_read_csv_gcs_default_engine_w_index_col_index( + session, scalars_df_default_index, gcs_folder ): - _, path = df_and_gcs_csv_for_two_columns - names = ["a", "b", "c"] - bf_df = session.read_csv(path, engine="bigquery", index_col=index_col, names=names) - - # Convert default pandas dtypes to match BigQuery DataFrames dtypes. - pd_df = session.read_csv( - path, index_col=index_col, names=names, dtype=bf_df.dtypes.to_dict() - ) + path = gcs_folder + "test_read_csv_gcs_default_engine_w_index_col_index*.csv" + read_path = path.replace("*", FIRST_FILE) + scalars_df_default_index.to_csv(path) - assert bf_df.shape == pd_df.shape - assert bf_df.columns.tolist() == pd_df.columns.tolist() - bigframes.testing.utils.assert_frame_equal( - bf_df.to_pandas(), pd_df.to_pandas(), check_index_type=False - ) + index_col = scalars_df_default_index.columns.to_list().index("rowindex") + df = session.read_csv(read_path, index_col=index_col) + scalars_df_default_index = scalars_df_default_index.set_index( + "rowindex" + ).sort_index() + pd.testing.assert_index_equal(df.columns, scalars_df_default_index.columns) + assert df.index.name == "rowindex" -@pytest.mark.parametrize( - "usecols", - [ - pytest.param(["a", "b", "c"], id="same"), - pytest.param(["a", "c"], id="less_than_names"), - ], -) -def test_read_csv_for_names_and_usecols( - session, usecols, df_and_gcs_csv_for_two_columns +def test_read_csv_local_default_engine_w_index_col_name( + session, scalars_pandas_df_default_index ): - _, path = df_and_gcs_csv_for_two_columns - - names = ["a", "b", "c"] - bf_df = session.read_csv(path, engine="bigquery", names=names, usecols=usecols) - - # Convert default pandas dtypes to match BigQuery DataFrames dtypes. - pd_df = session.read_csv( - path, names=names, usecols=usecols, dtype=bf_df.dtypes.to_dict() - ) - - assert bf_df.shape == pd_df.shape - assert bf_df.columns.tolist() == pd_df.columns.tolist() - - # BigFrames requires `sort_index()` because BigQuery doesn't preserve row IDs - # (b/280889935) or guarantee row ordering. - bf_df = bf_df.set_index(names[0]).sort_index() - pd_df = pd_df.set_index(names[0]) - bigframes.testing.utils.assert_frame_equal(bf_df.to_pandas(), pd_df.to_pandas()) + with tempfile.TemporaryDirectory() as dir: + path = dir + "/test_read_csv_local_default_engine_w_index_col_name" + # Using the pandas to_csv method because the BQ one does not support local write. + scalars_pandas_df_default_index.to_csv(path, index=False) + + df = session.read_csv(path, index_col="rowindex") + scalars_pandas_df_default_index = scalars_pandas_df_default_index.set_index( + "rowindex" + ).sort_index() + pd.testing.assert_index_equal( + df.columns, scalars_pandas_df_default_index.columns + ) + assert df.index.name == "rowindex" -def test_read_csv_for_names_and_invalid_usecols( - session, df_and_gcs_csv_for_two_columns +def test_read_csv_local_default_engine_w_index_col_index( + session, scalars_pandas_df_default_index ): - _, path = df_and_gcs_csv_for_two_columns - - names = ["a", "b", "c"] - usecols = ["a", "X"] - with pytest.raises( - ValueError, - match=re.escape("Column 'X' is not found. "), - ): - session.read_csv(path, engine="bigquery", names=names, usecols=usecols) + with tempfile.TemporaryDirectory() as dir: + path = dir + "/test_read_csv_local_default_engine_w_index_col_index" + # Using the pandas to_csv method because the BQ one does not support local write. + scalars_pandas_df_default_index.to_csv(path, index=False) + + index_col = scalars_pandas_df_default_index.columns.to_list().index("rowindex") + df = session.read_csv(path, index_col=index_col) + scalars_pandas_df_default_index = scalars_pandas_df_default_index.set_index( + "rowindex" + ).sort_index() + pd.testing.assert_index_equal( + df.columns, scalars_pandas_df_default_index.columns + ) + assert df.index.name == "rowindex" @pytest.mark.parametrize( - ("usecols", "index_col"), + "engine", [ - pytest.param(["a", "b", "c"], "a", id="same"), - pytest.param(["a", "b", "c"], ["a", "b"], id="same_two_index"), - pytest.param(["a", "c"], 0, id="less_than_names"), + pytest.param("bigquery", id="bq_engine"), + pytest.param(None, id="default_engine"), ], ) -def test_read_csv_for_names_and_usecols_and_indexcol( - session, usecols, index_col, df_and_gcs_csv_for_two_columns -): - _, path = df_and_gcs_csv_for_two_columns - - names = ["a", "b", "c"] - bf_df = session.read_csv( - path, engine="bigquery", names=names, usecols=usecols, index_col=index_col - ) - - # Convert default pandas dtypes to match BigQuery DataFrames dtypes. - pd_df = session.read_csv( - path, - names=names, - usecols=usecols, - index_col=index_col, - dtype=bf_df.reset_index().dtypes.to_dict(), - ) - - assert bf_df.shape == pd_df.shape - assert bf_df.columns.tolist() == pd_df.columns.tolist() - - bigframes.testing.utils.assert_frame_equal(bf_df.to_pandas(), pd_df.to_pandas()) - - -def test_read_csv_for_names_less_than_columns_and_same_usecols( - session, df_and_gcs_csv_for_two_columns -): - _, path = df_and_gcs_csv_for_two_columns - names = ["a", "c"] - usecols = ["a", "c"] - bf_df = session.read_csv(path, engine="bigquery", names=names, usecols=usecols) - - # Convert default pandas dtypes to match BigQuery DataFrames dtypes. - pd_df = session.read_csv( - path, names=names, usecols=usecols, dtype=bf_df.dtypes.to_dict() - ) - - assert bf_df.shape == pd_df.shape - assert bf_df.columns.tolist() == pd_df.columns.tolist() - - # BigFrames requires `sort_index()` because BigQuery doesn't preserve row IDs - # (b/280889935) or guarantee row ordering. - bf_df = bf_df.set_index(names[0]).sort_index() - pd_df = pd_df.set_index(names[0]) - bigframes.testing.utils.assert_frame_equal(bf_df.to_pandas(), pd_df.to_pandas()) - - -def test_read_csv_for_names_less_than_columns_and_mismatched_usecols( - session, df_and_gcs_csv_for_two_columns -): - _, path = df_and_gcs_csv_for_two_columns - names = ["a", "b"] - usecols = ["a"] - with pytest.raises( - ValueError, - match=re.escape("Number of passed names did not match number"), - ): - session.read_csv(path, engine="bigquery", names=names, usecols=usecols) - - -def test_read_csv_for_names_less_than_columns_and_different_usecols( - session, df_and_gcs_csv_for_two_columns -): - _, path = df_and_gcs_csv_for_two_columns - names = ["a", "b"] - usecols = ["a", "c"] - with pytest.raises( - ValueError, - match=re.escape("Usecols do not match columns"), - ): - session.read_csv(path, engine="bigquery", names=names, usecols=usecols) - - -def test_read_csv_for_dtype(session, df_and_gcs_csv_for_two_columns): - _, path = df_and_gcs_csv_for_two_columns - - dtype = {"bool_col": pd.BooleanDtype(), "int64_col": pd.Float64Dtype()} - bf_df = session.read_csv(path, engine="bigquery", dtype=dtype) - - # Convert default pandas dtypes to match BigQuery DataFrames dtypes. - pd_df = session.read_csv(path, dtype=dtype) - - assert bf_df.shape == pd_df.shape - assert bf_df.columns.tolist() == pd_df.columns.tolist() - - # BigFrames requires `sort_index()` because BigQuery doesn't preserve row IDs - # (b/280889935) or guarantee row ordering. - bf_df = bf_df.set_index("rowindex").sort_index() - pd_df = pd_df.set_index("rowindex") - bigframes.testing.utils.assert_frame_equal(bf_df.to_pandas(), pd_df.to_pandas()) - - -def test_read_csv_for_dtype_w_names(session, df_and_gcs_csv_for_two_columns): - _, path = df_and_gcs_csv_for_two_columns - - names = ["a", "b", "c"] - dtype = {"b": pd.BooleanDtype(), "c": pd.Float64Dtype()} - bf_df = session.read_csv(path, engine="bigquery", names=names, dtype=dtype) - - # Convert default pandas dtypes to match BigQuery DataFrames dtypes. - pd_df = session.read_csv(path, names=names, dtype=dtype) - - assert bf_df.shape == pd_df.shape - assert bf_df.columns.tolist() == pd_df.columns.tolist() +def test_read_csv_gcs_w_usecols(session, scalars_df_index, gcs_folder, engine): + path = gcs_folder + "test_read_csv_gcs_w_usecols" + path = path + "_default_engine*.csv" if engine is None else path + "_bq_engine*.csv" + read_path = path.replace("*", FIRST_FILE) if engine is None else path + scalars_df_index.to_csv(path) - # BigFrames requires `sort_index()` because BigQuery doesn't preserve row IDs - # (b/280889935) or guarantee row ordering. - bf_df = bf_df.set_index("a").sort_index() - pd_df = pd_df.set_index("a") - bigframes.testing.utils.assert_frame_equal(bf_df.to_pandas(), pd_df.to_pandas()) + # df should only have 1 column which is bool_col. + df = session.read_csv(read_path, usecols=["bool_col"], engine=engine) + assert len(df.columns) == 1 @pytest.mark.parametrize( - ("kwargs", "match"), + "engine", [ - pytest.param( - {"chunksize": 5}, - "'chunksize' and 'iterator' arguments are not supported.", - id="with_chunksize", - ), - pytest.param( - {"iterator": True}, - "'chunksize' and 'iterator' arguments are not supported.", - id="with_iterator", - ), + pytest.param("bigquery", id="bq_engine"), + pytest.param(None, id="default_engine"), ], ) -def test_read_csv_default_engine_throws_not_implemented_error( - session, - scalars_df_index, - gcs_folder, - kwargs, - match, -): - path = ( - gcs_folder - + "test_read_csv_gcs_default_engine_throws_not_implemented_error*.csv" - ) - read_path = utils.get_first_file_from_wildcard(path) - scalars_df_index.to_csv(path) - with pytest.raises(NotImplementedError, match=match): - session.read_csv(read_path, **kwargs) - - -@pytest.mark.parametrize( - "header", - [0, 1, 5], -) -def test_read_csv_for_gcs_file_w_header(session, df_and_gcs_csv, header): - # Compares results for pandas and bigframes engines - scalars_df, path = df_and_gcs_csv - bf_df = session.read_csv(path, engine="bigquery", index_col=False, header=header) - pd_df = session.read_csv( - path, index_col=False, header=header, dtype=scalars_df.dtypes.to_dict() - ) - - # b/408461403: workaround the issue where the slice does not work for DataFrame. - expected_df = session.read_pandas(scalars_df.to_pandas()[header:]) - - assert pd_df.shape[0] == expected_df.shape[0] - assert bf_df.shape[0] == pd_df.shape[0] - - # We use a default index because of index_col=False, so the previous index - # column is just loaded as a column. - assert len(pd_df.columns) == len(expected_df.columns) + 1 - assert len(bf_df.columns) == len(pd_df.columns) - - # When `header > 0`, pandas and BigFrames may handle column naming differently. - # Pandas uses the literal content of the specified header row for column names, - # regardless of what it is. BigQuery, however, might generate default names based - # on data type (e.g.,bool_field_0,string_field_1, etc.). - if header == 0: - # BigFrames requires `sort_index()` because BigQuery doesn't preserve row IDs - # (b/280889935) or guarantee row ordering. - bf_df = bf_df.set_index("rowindex").sort_index() - pd_df = pd_df.set_index("rowindex") - bigframes.testing.utils.assert_frame_equal( - bf_df.to_pandas(), scalars_df.to_pandas() - ) - bigframes.testing.utils.assert_frame_equal(bf_df.to_pandas(), pd_df.to_pandas()) - - -def test_read_csv_w_usecols(session, df_and_local_csv): - # Compares results for pandas and bigframes engines - scalars_df, path = df_and_local_csv - usecols = ["rowindex", "bool_col"] - with open(path, "rb") as buffer: - bf_df = session.read_csv( - buffer, - engine="bigquery", - usecols=usecols, - ) - with open(path, "rb") as buffer: - # Convert default pandas dtypes to match BigQuery DataFrames dtypes. - pd_df = session.read_csv( - buffer, - usecols=usecols, - dtype=scalars_df[["bool_col"]].dtypes.to_dict(), - ) - - assert bf_df.shape == pd_df.shape - assert bf_df.columns.tolist() == pd_df.columns.tolist() - - # BigFrames requires `sort_index()` because BigQuery doesn't preserve row IDs - # (b/280889935) or guarantee row ordering. - bf_df = bf_df.set_index("rowindex").sort_index() - pd_df = pd_df.set_index("rowindex") - bigframes.testing.utils.assert_frame_equal(bf_df.to_pandas(), pd_df.to_pandas()) - - -def test_read_csv_w_usecols_and_indexcol(session, df_and_local_csv): - # Compares results for pandas and bigframes engines - scalars_df, path = df_and_local_csv - usecols = ["rowindex", "bool_col"] - with open(path, "rb") as buffer: - bf_df = session.read_csv( - buffer, - engine="bigquery", - usecols=usecols, - index_col="rowindex", - ) - with open(path, "rb") as buffer: - # Convert default pandas dtypes to match BigQuery DataFrames dtypes. - pd_df = session.read_csv( - buffer, - usecols=usecols, - index_col="rowindex", - dtype=scalars_df[["bool_col"]].dtypes.to_dict(), - ) - - assert bf_df.shape == pd_df.shape - assert bf_df.columns.tolist() == pd_df.columns.tolist() - - bigframes.testing.utils.assert_frame_equal(bf_df.to_pandas(), pd_df.to_pandas()) - +def test_read_csv_local_w_usecols(session, scalars_pandas_df_index, engine): + with tempfile.TemporaryDirectory() as dir: + path = dir + "/test_read_csv_local_w_usecols.csv" + # Using the pandas to_csv method because the BQ one does not support local write. + scalars_pandas_df_index.to_csv(path, index=False) -def test_read_csv_w_indexcol_not_in_usecols(session, df_and_local_csv): - _, path = df_and_local_csv - with open(path, "rb") as buffer: - with pytest.raises( - ValueError, - match=re.escape("The specified index column(s) were not found"), - ): - session.read_csv( - buffer, - engine="bigquery", - usecols=["bool_col"], - index_col="rowindex", - ) + # df should only have 1 column which is bool_col. + df = session.read_csv(path, usecols=["bool_col"], engine=engine) + assert len(df.columns) == 1 @pytest.mark.parametrize( "engine", [ - pytest.param( - "bigquery", - id="bq_engine", - marks=pytest.mark.xfail( - raises=NotImplementedError, - ), - ), + pytest.param("bigquery", id="bq_engine"), pytest.param(None, id="default_engine"), ], ) -def test_read_csv_for_others_files(session, engine): - uri = "https://raw.githubusercontent.com/googleapis/python-bigquery-dataframes/main/tests/data/people.csv" - df = session.read_csv(uri, engine=engine) - assert len(df.columns) == 3 - - -def test_read_csv_local_w_encoding(session, penguins_pandas_df_default_index): +def test_read_csv_local_w_encoding(session, penguins_pandas_df_default_index, engine): with tempfile.TemporaryDirectory() as dir: path = dir + "/test_read_csv_local_w_encoding.csv" # Using the pandas to_csv method because the BQ one does not support local write. - penguins_pandas_df_default_index.index.name = "rowindex" - penguins_pandas_df_default_index.to_csv(path, index=True, encoding="ISO-8859-1") + penguins_pandas_df_default_index.to_csv( + path, index=False, encoding="ISO-8859-1" + ) # File can only be read using the same character encoding as when written. - pd_df = session.read_csv( - path, - index_col="rowindex", - encoding="ISO-8859-1", - dtype=penguins_pandas_df_default_index.dtypes.to_dict(), - ) + df = session.read_csv(path, engine=engine, encoding="ISO-8859-1") - bf_df = session.read_csv( - path, engine="bigquery", index_col="rowindex", encoding="ISO-8859-1" + # TODO(chelsealin): If we serialize the index, can more easily compare values. + pd.testing.assert_index_equal( + df.columns, penguins_pandas_df_default_index.columns ) - bigframes.testing.utils.assert_frame_equal( - bf_df.to_pandas(), penguins_pandas_df_default_index - ) - bigframes.testing.utils.assert_frame_equal(bf_df.to_pandas(), pd_df.to_pandas()) + + assert df.shape[0] == penguins_pandas_df_default_index.shape[0] def test_read_pickle_local(session, penguins_pandas_df_default_index, tmp_path): @@ -1823,9 +779,7 @@ def test_read_pickle_local(session, penguins_pandas_df_default_index, tmp_path): penguins_pandas_df_default_index.to_pickle(path) df = session.read_pickle(path) - bigframes.testing.utils.assert_frame_equal( - penguins_pandas_df_default_index, df.to_pandas() - ) + pd.testing.assert_frame_equal(penguins_pandas_df_default_index, df.to_pandas()) def test_read_pickle_buffer(session, penguins_pandas_df_default_index): @@ -1834,9 +788,7 @@ def test_read_pickle_buffer(session, penguins_pandas_df_default_index): buffer.seek(0) df = session.read_pickle(buffer) - bigframes.testing.utils.assert_frame_equal( - penguins_pandas_df_default_index, df.to_pandas() - ) + pd.testing.assert_frame_equal(penguins_pandas_df_default_index, df.to_pandas()) def test_read_pickle_series_buffer(session): @@ -1855,66 +807,26 @@ def test_read_pickle_gcs(session, penguins_pandas_df_default_index, gcs_folder): penguins_pandas_df_default_index.to_pickle(path) df = session.read_pickle(path) - bigframes.testing.utils.assert_frame_equal( - penguins_pandas_df_default_index, df.to_pandas() - ) + pd.testing.assert_frame_equal(penguins_pandas_df_default_index, df.to_pandas()) -@pytest.mark.parametrize( - ("engine", "filename"), - ( - pytest.param( - "auto", - "000000000000.parquet", - id="auto", - ), - pytest.param( - "pyarrow", - "000000000000.parquet", - id="pyarrow", - ), - pytest.param( - "bigquery", - "000000000000.parquet", - id="bigquery", - ), - pytest.param( - "bigquery", - "*.parquet", - id="bigquery_wildcard", - ), - pytest.param( - "auto", - "*.parquet", - id="auto_wildcard", - marks=pytest.mark.xfail( - raises=ValueError, - ), - ), - ), -) -def test_read_parquet_gcs( - session: bigframes.Session, scalars_dfs, gcs_folder, engine, filename -): +def test_read_parquet_gcs(session: bigframes.Session, scalars_dfs, gcs_folder): scalars_df, _ = scalars_dfs # Include wildcard so that multiple files can be written/read if > 1 GB. # https://cloud.google.com/bigquery/docs/exporting-data#exporting_data_into_one_or_more_files - write_path = gcs_folder + test_read_parquet_gcs.__name__ + "*.parquet" - read_path = gcs_folder + test_read_parquet_gcs.__name__ + filename - + path = gcs_folder + test_read_parquet_gcs.__name__ + "*.parquet" df_in: bigframes.dataframe.DataFrame = scalars_df.copy() # GEOGRAPHY not supported in parquet export. df_in = df_in.drop(columns="geography_col") # Make sure we can also serialize the order. df_write = df_in.reset_index(drop=False) df_write.index.name = f"ordering_id_{random.randrange(1_000_000)}" - df_write.to_parquet(write_path, index=True) + df_write.to_parquet(path, index=True) df_out = ( - session.read_parquet(read_path, engine=engine) + session.read_parquet(path) # Restore order. - .set_index(df_write.index.name) - .sort_index() + .set_index(df_write.index.name).sort_index() # Restore index. .set_index(typing.cast(str, df_in.index.name)) ) @@ -1922,141 +834,14 @@ def test_read_parquet_gcs( # DATETIME gets loaded as TIMESTAMP in parquet. See: # https://cloud.google.com/bigquery/docs/exporting-data#parquet_export_details df_out = df_out.assign( - datetime_col=df_out["datetime_col"].astype("timestamp[us][pyarrow]"), - timestamp_col=df_out["timestamp_col"].astype("timestamp[us, tz=UTC][pyarrow]"), - duration_col=df_out["duration_col"].astype("duration[us][pyarrow]"), + datetime_col=df_out["datetime_col"].astype("timestamp[us][pyarrow]") ) # Make sure we actually have at least some values before comparing. assert df_out.size != 0 pd_df_in = df_in.to_pandas() pd_df_out = df_out.to_pandas() - bigframes.testing.utils.assert_frame_equal(pd_df_in, pd_df_out) - - -@pytest.mark.parametrize( - ("engine", "filename"), - ( - pytest.param( - "bigquery", - "000000000000.orc", - id="bigquery", - ), - pytest.param( - "auto", - "000000000000.orc", - id="auto", - ), - pytest.param( - "pyarrow", - "000000000000.orc", - id="pyarrow", - ), - pytest.param( - "bigquery", - "*.orc", - id="bigquery_wildcard", - ), - pytest.param( - "auto", - "*.orc", - id="auto_wildcard", - marks=pytest.mark.xfail( - raises=ValueError, - ), - ), - ), -) -def test_read_orc_gcs( - session: bigframes.Session, scalars_dfs, gcs_folder, engine, filename -): - pytest.importorskip( - "pandas", - minversion="2.0.0", - reason="pandas<2 does not handle nullable int columns well", - ) - scalars_df, _ = scalars_dfs - write_path = gcs_folder + test_read_orc_gcs.__name__ + "000000000000.orc" - read_path = gcs_folder + test_read_orc_gcs.__name__ + filename - - df_in: bigframes.dataframe.DataFrame = scalars_df.copy() - df_in = df_in.drop( - columns=[ - "geography_col", - "time_col", - "datetime_col", - "duration_col", - "timestamp_col", - ] - ) - df_write = df_in.reset_index(drop=False) - df_write.index.name = f"ordering_id_{random.randrange(1_000_000)}" - df_write.to_orc(write_path) - - df_out = ( - session.read_orc(read_path, engine=engine) - .set_index(df_write.index.name) - .sort_index() - .set_index(typing.cast(str, df_in.index.name)) - ) - - assert df_out.size != 0 - pd_df_in = df_in.to_pandas() - pd_df_out = df_out.to_pandas() - bigframes.testing.utils.assert_frame_equal(pd_df_in, pd_df_out) - - -@pytest.mark.parametrize( - ("engine", "filename"), - ( - pytest.param( - "bigquery", - "000000000000.avro", - id="bigquery", - ), - pytest.param( - "bigquery", - "*.avro", - id="bigquery_wildcard", - ), - ), -) -def test_read_avro_gcs( - session: bigframes.Session, scalars_dfs, gcs_folder, engine, filename -): - scalars_df, _ = scalars_dfs - write_uri = gcs_folder + test_read_avro_gcs.__name__ + "*.avro" - read_uri = gcs_folder + test_read_avro_gcs.__name__ + filename - - df_in: bigframes.dataframe.DataFrame = scalars_df.copy() - # datetime round-trips back as str in avro - df_in = df_in.drop(columns=["geography_col", "duration_col", "datetime_col"]) - df_write = df_in.reset_index(drop=False) - index_name = f"ordering_id_{random.randrange(1_000_000)}" - df_write.index.name = index_name - - # Create a BigQuery table - table_id = df_write.to_gbq() - - # Extract to GCS as Avro - client = session.bqclient - extract_job_config = bigquery.ExtractJobConfig() - extract_job_config.destination_format = "AVRO" - extract_job_config.use_avro_logical_types = True - - client.extract_table(table_id, write_uri, job_config=extract_job_config).result() - - df_out = ( - session.read_avro(read_uri, engine=engine) - .set_index(index_name) - .sort_index() - .set_index(typing.cast(str, df_in.index.name)) - ) - - assert df_out.size != 0 - pd_df_in = df_in.to_pandas() - pd_df_out = df_out.to_pandas() - bigframes.testing.utils.assert_frame_equal(pd_df_in, pd_df_out) + pd.testing.assert_frame_equal(pd_df_in, pd_df_out) @pytest.mark.parametrize( @@ -2088,10 +873,9 @@ def test_read_parquet_gcs_compressed( df_write.to_parquet(path, compression=compression, index=True) df_out = ( - session.read_parquet(path, engine="bigquery") + session.read_parquet(path) # Restore order. - .set_index(df_write.index.name) - .sort_index() + .set_index(df_write.index.name).sort_index() # Restore index. .set_index(typing.cast(str, df_in.index.name)) ) @@ -2099,15 +883,14 @@ def test_read_parquet_gcs_compressed( # DATETIME gets loaded as TIMESTAMP in parquet. See: # https://cloud.google.com/bigquery/docs/exporting-data#parquet_export_details df_out = df_out.assign( - datetime_col=df_out["datetime_col"].astype("timestamp[us][pyarrow]"), - duration_col=df_out["duration_col"].astype("duration[us][pyarrow]"), + datetime_col=df_out["datetime_col"].astype("timestamp[us][pyarrow]") ) # Make sure we actually have at least some values before comparing. assert df_out.size != 0 pd_df_in = df_in.to_pandas() pd_df_out = df_out.to_pandas() - bigframes.testing.utils.assert_frame_equal(pd_df_in, pd_df_out) + pd.testing.assert_frame_equal(pd_df_in, pd_df_out) @pytest.mark.parametrize( @@ -2147,37 +930,23 @@ def test_read_parquet_gcs_compression_not_supported( def test_read_json_gcs_bq_engine(session, scalars_dfs, gcs_folder): scalars_df, _ = scalars_dfs path = gcs_folder + "test_read_json_gcs_bq_engine_w_index*.json" - read_path = utils.get_first_file_from_wildcard(path) + read_path = path.replace("*", FIRST_FILE) scalars_df.to_json(path, index=False, lines=True, orient="records") df = session.read_json(read_path, lines=True, orient="records", engine="bigquery") # The auto detects of BigQuery load job does not preserve any ordering of columns for json. - bigframes.testing.utils.assert_index_equal( + pd.testing.assert_index_equal( df.columns.sort_values(), scalars_df.columns.sort_values() ) # The auto detects of BigQuery load job have restrictions to detect the bytes, # datetime, numeric and geometry types, so they're skipped here. - df = df.drop( - columns=[ - "bytes_col", - "datetime_col", - "numeric_col", - "geography_col", - "duration_col", - ] - ) + df = df.drop(columns=["bytes_col", "datetime_col", "numeric_col", "geography_col"]) scalars_df = scalars_df.drop( - columns=[ - "bytes_col", - "datetime_col", - "numeric_col", - "geography_col", - "duration_col", - ] + columns=["bytes_col", "datetime_col", "numeric_col", "geography_col"] ) assert df.shape[0] == scalars_df.shape[0] - bigframes.testing.utils.assert_series_equal( + pd.testing.assert_series_equal( df.dtypes.sort_index(), scalars_df.dtypes.sort_index() ) @@ -2185,7 +954,7 @@ def test_read_json_gcs_bq_engine(session, scalars_dfs, gcs_folder): def test_read_json_gcs_default_engine(session, scalars_dfs, gcs_folder): scalars_df, _ = scalars_dfs path = gcs_folder + "test_read_json_gcs_default_engine_w_index*.json" - read_path = utils.get_first_file_from_wildcard(path) + read_path = path.replace("*", FIRST_FILE) scalars_df.to_json( path, index=False, @@ -2203,187 +972,39 @@ def test_read_json_gcs_default_engine(session, scalars_dfs, gcs_folder): orient="records", ) - bigframes.testing.utils.assert_index_equal(df.columns, scalars_df.columns) + pd.testing.assert_index_equal(df.columns, scalars_df.columns) # The auto detects of BigQuery load job have restrictions to detect the bytes, # numeric and geometry types, so they're skipped here. - df = df.drop(columns=["bytes_col", "numeric_col", "geography_col", "duration_col"]) - scalars_df = scalars_df.drop( - columns=["bytes_col", "numeric_col", "geography_col", "duration_col"] - ) + df = df.drop(columns=["bytes_col", "numeric_col", "geography_col"]) + scalars_df = scalars_df.drop(columns=["bytes_col", "numeric_col", "geography_col"]) # pandas read_json does not respect the dtype overrides for these columns df = df.drop(columns=["date_col", "datetime_col", "time_col"]) scalars_df = scalars_df.drop(columns=["date_col", "datetime_col", "time_col"]) assert df.shape[0] == scalars_df.shape[0] - bigframes.testing.utils.assert_series_equal(df.dtypes, scalars_df.dtypes) - - -@pytest.mark.parametrize( - ("query_or_table", "index_col", "columns"), - [ - pytest.param( - "{scalars_table_id}", - ("int64_col", "string_col", "int64_col"), - ("float64_col", "bool_col"), - id="table_input_index_col_dup", - marks=pytest.mark.xfail( - raises=ValueError, - reason="ValueError: Duplicate names within 'index_col'.", - strict=True, - ), - ), - pytest.param( - """SELECT int64_col, string_col, float64_col, bool_col - FROM `{scalars_table_id}`""", - ("int64_col",), - ("string_col", "float64_col", "string_col"), - id="query_input_columns_dup", - marks=pytest.mark.xfail( - raises=ValueError, - reason="ValueError: Duplicate names within 'columns'.", - strict=True, - ), - ), - pytest.param( - "{scalars_table_id}", - ("int64_col", "string_col"), - ("float64_col", "string_col", "bool_col"), - id="table_input_cross_dup", - marks=pytest.mark.xfail( - raises=ValueError, - reason="ValueError: Overlap between 'index_col' and 'columns'.", - strict=True, - ), - ), - ], -) -def test_read_gbq_duplicate_columns_xfail( - session: bigframes.Session, - scalars_table_id: str, - query_or_table: str, - index_col: tuple, - columns: tuple, -): - session.read_gbq( - query_or_table.format(scalars_table_id=scalars_table_id), - index_col=index_col, - columns=columns, - ) - - -def test_read_gbq_with_table_ref_dry_run(scalars_table_id, session): - result = session.read_gbq(scalars_table_id, dry_run=True) - - assert isinstance(result, pd.Series) - _assert_table_dry_run_stats_are_valid(result) - - -def test_read_gbq_with_query_dry_run(scalars_table_id, session): - query = f"SELECT * FROM {scalars_table_id} LIMIT 10;" - result = session.read_gbq(query, dry_run=True) - - assert isinstance(result, pd.Series) - _assert_query_dry_run_stats_are_valid(result) - - -def test_read_gbq_dry_run_with_column_and_index(scalars_table_id, session): - query = f"SELECT * FROM {scalars_table_id} LIMIT 10;" - result = session.read_gbq( - query, dry_run=True, columns=["int64_col", "float64_col"], index_col="int64_too" - ) - - assert isinstance(result, pd.Series) - _assert_query_dry_run_stats_are_valid(result) - assert result["columnCount"] == 2 - assert result["columnDtypes"] == { - "int64_col": pd.Int64Dtype(), - "float64_col": pd.Float64Dtype(), - } - assert result["indexLevel"] == 1 - assert result["indexDtypes"] == [pd.Int64Dtype()] - + pd.testing.assert_series_equal(df.dtypes, scalars_df.dtypes) -def test_read_gbq_table_dry_run(scalars_table_id, session): - result = session.read_gbq_table(scalars_table_id, dry_run=True) - assert isinstance(result, pd.Series) - _assert_table_dry_run_stats_are_valid(result) +def test_session_id(session): + assert session._session_id is not None + # BQ client always runs query within the opened session. + query_job = session.bqclient.query("SELECT 1") + assert query_job.session_info.session_id == session._session_id -def test_read_gbq_table_dry_run_with_max_results(scalars_table_id, session): - result = session.read_gbq_table(scalars_table_id, dry_run=True, max_results=100) + # TODO(chelsealin): Verify the session id can be binded with a load job. - assert isinstance(result, pd.Series) - _assert_query_dry_run_stats_are_valid(result) +@pytest.mark.flaky(retries=2) +def test_to_close_session(): + session = bigframes.Session() + assert session._session_id is not None + session.close() + assert session._session_id is None -def test_read_gbq_query_dry_run(scalars_table_id, session): - query = f"SELECT * FROM {scalars_table_id} LIMIT 10;" - result = session.read_gbq_query(query, dry_run=True) - - assert isinstance(result, pd.Series) - _assert_query_dry_run_stats_are_valid(result) - - -def test_block_dry_run_includes_local_data(session): - df1 = bigframes.dataframe.DataFrame({"col_1": [1, 2, 3]}, session=session) - df2 = bigframes.dataframe.DataFrame({"col_2": [1, 2, 3]}, session=session) - - result = df1.merge(df2, how="cross").to_pandas(dry_run=True) - - assert isinstance(result, pd.Series) - _assert_query_dry_run_stats_are_valid(result) - assert result["totalBytesProcessed"] > 0 - assert ( - df1.to_pandas(dry_run=True)["totalBytesProcessed"] - + df2.to_pandas(dry_run=True)["totalBytesProcessed"] - == result["totalBytesProcessed"] - ) - - -def _assert_query_dry_run_stats_are_valid(result: pd.Series): - expected_index = pd.Index( - [ - "columnCount", - "columnDtypes", - "indexLevel", - "indexDtypes", - "bigquerySchema", - "projectId", - "location", - "jobType", - "dispatchedSql", - "destinationTable", - "useLegacySql", - "referencedTables", - "totalBytesProcessed", - "cacheHit", - "statementType", - "creationTime", - ] - ) - - bigframes.testing.utils.assert_index_equal(result.index, expected_index) - assert result["columnCount"] + result["indexLevel"] > 0 - - -def _assert_table_dry_run_stats_are_valid(result: pd.Series): - expected_index = pd.Index( - [ - "isQuery", - "columnCount", - "columnDtypes", - "bigquerySchema", - "numBytes", - "numRows", - "location", - "type", - "creationTime", - "lastModifiedTime", - ] - ) - - bigframes.testing.utils.assert_index_equal(result.index, expected_index) - assert result["columnCount"] == len(result["columnDtypes"]) + # Session has expired and is no longer available. + with pytest.raises(google.api_core.exceptions.BadRequest): + query_job = session.bqclient.query("SELECT 1") + query_job.result() # blocks until finished diff --git a/tests/system/small/test_session_as_bpd.py b/tests/system/small/test_session_as_bpd.py deleted file mode 100644 index e280c551cbd..00000000000 --- a/tests/system/small/test_session_as_bpd.py +++ /dev/null @@ -1,154 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -"""Check that bpd and Session can be used interchangablely.""" - -from __future__ import annotations - -from typing import cast - -import numpy as np -import pandas.testing - -import bigframes.pandas as bpd -import bigframes.session - - -def test_cut(session: bigframes.session.Session): - sc = [30, 80, 40, 90, 60, 45, 95, 75, 55, 100, 65, 85] - x = [20, 40, 60, 80, 100] - - bpd_result = bpd.cut(sc, x) - session_result = session.cut(sc, x) - - global_session = bpd.get_global_session() - assert global_session is not session - assert bpd_result._session is global_session - assert session_result._session is session - - bpd_pd = bpd_result.to_pandas() - session_pd = session_result.to_pandas() - pandas.testing.assert_series_equal(bpd_pd, session_pd) - - -def test_dataframe(session: bigframes.session.Session): - data = {"col": ["local", None, "data"]} - - bpd_result = bpd.DataFrame(data) - session_result = session.DataFrame(data) - - global_session = bpd.get_global_session() - assert global_session is not session - assert bpd_result._session is global_session - assert session_result._session is session - - bpd_pd = bpd_result.to_pandas() - session_pd = session_result.to_pandas() - pandas.testing.assert_frame_equal(bpd_pd, session_pd) - - -def test_multiindex_from_arrays(session: bigframes.session.Session): - arrays = [[1, 1, 2, 2], ["red", "blue", "red", "blue"]] - - bpd_result = bpd.MultiIndex.from_arrays(arrays, names=("number", "color")) - session_result = session.MultiIndex.from_arrays(arrays, names=("number", "color")) - - global_session = bpd.get_global_session() - assert global_session is not session - assert bpd_result._session is global_session - assert session_result._session is session - - bpd_pd = bpd_result.to_pandas() - session_pd = session_result.to_pandas() - pandas.testing.assert_index_equal(bpd_pd, session_pd) - - -def test_multiindex_from_tuples(session: bigframes.session.Session): - tuples = [(1, "red"), (1, "blue"), (2, "red"), (2, "blue")] - - bpd_result = bpd.MultiIndex.from_tuples(tuples, names=("number", "color")) - session_result = session.MultiIndex.from_tuples(tuples, names=("number", "color")) - - global_session = bpd.get_global_session() - assert global_session is not session - assert bpd_result._session is global_session - assert session_result._session is session - - bpd_pd = bpd_result.to_pandas() - session_pd = session_result.to_pandas() - pandas.testing.assert_index_equal(bpd_pd, session_pd) - - -def test_index(session: bigframes.session.Session): - index = [1, 2, 3] - - bpd_result = bpd.Index(index) - session_result = session.Index(index) - - global_session = bpd.get_global_session() - assert global_session is not session - assert bpd_result._session is global_session - assert session_result._session is session - - bpd_pd = bpd_result.to_pandas() - session_pd = session_result.to_pandas() - pandas.testing.assert_index_equal(bpd_pd, session_pd) - - -def test_series(session: bigframes.session.Session): - series = [1, 2, 3] - - bpd_result = bpd.Series(series) - session_result = session.Series(series) - - global_session = bpd.get_global_session() - assert global_session is not session - assert bpd_result._session is global_session - assert session_result._session is session - - bpd_pd = bpd_result.to_pandas() - session_pd = session_result.to_pandas() - pandas.testing.assert_series_equal(bpd_pd, session_pd) - - -def test_to_datetime(session: bigframes.session.Session): - datetimes = ["2018-10-26 12:00:00", "2018-10-26 13:00:15"] - - bpd_result = bpd.to_datetime(datetimes) - session_result = cast(bpd.Series, session.to_datetime(datetimes)) - - global_session = bpd.get_global_session() - assert global_session is not session - assert bpd_result._session is global_session - assert session_result._session is session - - bpd_pd = bpd_result.to_pandas() - session_pd = session_result.to_pandas() - pandas.testing.assert_series_equal(bpd_pd, session_pd) - - -def test_to_timedelta(session: bigframes.session.Session): - offsets = np.arange(5) - - bpd_result = bpd.to_timedelta(offsets, unit="s") - session_result = session.to_timedelta(offsets, unit="s") - - global_session = bpd.get_global_session() - assert global_session is not session - assert bpd_result._session is global_session - assert session_result._session is session - - bpd_pd = bpd_result.to_pandas() - session_pd = session_result.to_pandas() - pandas.testing.assert_series_equal(bpd_pd, session_pd) diff --git a/tests/system/small/test_unordered.py b/tests/system/small/test_unordered.py deleted file mode 100644 index c8db041fec2..00000000000 --- a/tests/system/small/test_unordered.py +++ /dev/null @@ -1,295 +0,0 @@ -# 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. -# 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 warnings - -import pandas as pd -import pyarrow as pa -import pytest - -import bigframes.exceptions -import bigframes.pandas as bpd -from bigframes.testing.utils import assert_frame_equal, assert_series_equal - - -def test_unordered_mode_sql_no_hash(unordered_session): - bf_df = unordered_session.read_gbq( - "bigquery-public-data.ethereum_blockchain.blocks" - ) - sql = bf_df.sql - assert "ORDER BY".casefold() not in sql.casefold() - assert "farm_fingerprint".casefold() not in sql.casefold() - - -def test_unordered_mode_job_label(unordered_session): - pd_df = pd.DataFrame({"a": [1, 2, 3], "b": [4, 5, 6]}, dtype=pd.Int64Dtype()) - df = bpd.DataFrame(pd_df, session=unordered_session) - df.to_gbq() - job_labels = df.query_job.labels # type:ignore - assert "bigframes-mode" in job_labels - assert job_labels["bigframes-mode"] == "unordered" - - -def test_unordered_mode_cache_aggregate(unordered_session): - pd_df = pd.DataFrame({"a": [1, 2, 3], "b": [4, 5, 6]}, dtype=pd.Int64Dtype()) - df = bpd.DataFrame(pd_df, session=unordered_session) - mean_diff = df - df.mean() - mean_diff.cache() - bf_result = mean_diff.to_pandas(ordered=False) - pd_result = pd_df - pd_df.mean() - - assert_frame_equal(bf_result, pd_result, ignore_order=True) # type: ignore - - -def test_unordered_mode_series_peek(unordered_session): - pd_series = pd.Series([1, 2, 3, 4, 5, 6], dtype=pd.Int64Dtype()) - bf_series = bpd.Series(pd_series, session=unordered_session) - pd_result = pd_series.groupby(pd_series % 4).sum() - bf_peek = bf_series.groupby(bf_series % 4).sum().peek(2) - - assert_series_equal(bf_peek, pd_result.reindex(bf_peek.index)) - - -def test_unordered_mode_single_aggregate(unordered_session): - pd_df = pd.DataFrame({"a": [1, 2, 3], "b": [4, 5, 6]}, dtype=pd.Int64Dtype()) - bf_df = bpd.DataFrame(pd_df, session=unordered_session) - - assert bf_df.a.mean() == pd_df.a.mean() - - -def test_unordered_mode_print(unordered_session): - pd_df = pd.DataFrame({"a": [1, 2, 3], "b": [4, 5, 6]}, dtype=pd.Int64Dtype()) - df = bpd.DataFrame(pd_df, session=unordered_session).cache() - print(df) - - -def test_unordered_mode_read_gbq(unordered_session): - # TODO: supply a reason why this isn't compatible with pandas 1.x - pytest.importorskip("pandas", minversion="2.0.0") - df = unordered_session.read_gbq( - """SELECT - [1, 3, 2] AS array_column, - STRUCT( - "a" AS string_field, - 1.2 AS float_field) AS struct_column""" - ) - expected = pd.DataFrame( - { - "array_column": pd.Series( - [[1, 3, 2]], - dtype=(pd.ArrowDtype(pa.list_(pa.int64()))), - ), - "struct_column": pd.Series( - [{"string_field": "a", "float_field": 1.2}], - dtype=pd.ArrowDtype( - pa.struct( - [ - ("string_field", pa.string()), - ("float_field", pa.float64()), - ] - ) - ), - ), - } - ) - # Don't need ignore_order as there is only 1 row - assert_frame_equal(df.to_pandas(), expected, check_index_type=False) - - -@pytest.mark.parametrize( - ("keep"), - [ - pytest.param( - "first", - ), - pytest.param( - False, - ), - ], -) -def test_unordered_drop_duplicates(unordered_session, keep): - pd_df = pd.DataFrame({"a": [1, 1, 3], "b": [4, 4, 6]}, dtype=pd.Int64Dtype()) - bf_df = bpd.DataFrame(pd_df, session=unordered_session) - - bf_result = bf_df.drop_duplicates(keep=keep) - pd_result = pd_df.drop_duplicates(keep=keep) - - assert_frame_equal(bf_result.to_pandas(), pd_result, ignore_order=True) - - -def test_unordered_reset_index(unordered_session): - pd_df = pd.DataFrame({"a": [1, 1, 3], "b": [4, 4, 6]}, dtype=pd.Int64Dtype()) - bf_df = bpd.DataFrame(pd_df, session=unordered_session) - - bf_result = bf_df.set_index("b").reset_index(drop=False) - pd_result = pd_df.set_index("b").reset_index(drop=False) - - assert_frame_equal(bf_result.to_pandas(), pd_result) - - -def test_unordered_merge(unordered_session): - pd_df = pd.DataFrame( - {"a": [1, 1, 3], "b": [4, 4, 6], "c": [1, 2, 3]}, dtype=pd.Int64Dtype() - ) - bf_df = bpd.DataFrame(pd_df, session=unordered_session) - - bf_result = bf_df.merge(bf_df, left_on="a", right_on="c") - pd_result = pd_df.merge(pd_df, left_on="a", right_on="c") - - assert_frame_equal(bf_result.to_pandas(), pd_result, ignore_order=True) - - -def test_unordered_drop_duplicates_ambiguous(unordered_session): - pd_df = pd.DataFrame( - {"a": [1, 1, 1], "b": [4, 4, 6], "c": [1, 1, 3]}, dtype=pd.Int64Dtype() - ) - bf_df = bpd.DataFrame(pd_df, session=unordered_session) - - # merge first to discard original ordering - bf_result = ( - bf_df.merge(bf_df, left_on="a", right_on="c") - .sort_values("c_y") - .drop_duplicates() - ) - pd_result = ( - pd_df.merge(pd_df, left_on="a", right_on="c") - .sort_values("c_y") - .drop_duplicates() - ) - - assert_frame_equal(bf_result.to_pandas(), pd_result, ignore_order=True) - - -def test_unordered_mode_cache_preserves_order(unordered_session): - pd_df = pd.DataFrame( - {"a": [1, 2, 3, 4, 5, 6], "b": [4, 5, 9, 3, 1, 6]}, dtype=pd.Int64Dtype() - ) - pd_df.index = pd_df.index.astype(pd.Int64Dtype()) - df = bpd.DataFrame(pd_df, session=unordered_session) - sorted_df = df.sort_values("b").cache() - bf_result = sorted_df.to_pandas() - pd_result = pd_df.sort_values("b") - - # B is unique so unstrict order mode result here should be equivalent to strictly ordered - assert_frame_equal(bf_result, pd_result, ignore_order=False) - - -def test_unordered_mode_no_ordering_error(unordered_session): - pd_df = pd.DataFrame( - {"a": [1, 2, 3, 4, 5, 1], "b": [4, 5, 9, 3, 1, 6]}, dtype=pd.Int64Dtype() - ) - pd_df.index = pd_df.index.astype(pd.Int64Dtype()) - df = bpd.DataFrame(pd_df, session=unordered_session) - - with pytest.raises(bigframes.exceptions.OrderRequiredError): - df.merge(df, on="a").head(3) - - -def test_unordered_mode_allows_ambiguity(unordered_session): - pd_df = pd.DataFrame( - {"a": [1, 2, 3, 4, 5, 1], "b": [4, 5, 9, 3, 1, 6]}, dtype=pd.Int64Dtype() - ) - pd_df.index = pd_df.index.astype(pd.Int64Dtype()) - df = bpd.DataFrame(pd_df, session=unordered_session) - df.merge(df, on="a").sort_values("b_x").head(3) - - -def test_unordered_mode_no_ambiguity_warning(unordered_session): - pd_df = pd.DataFrame( - {"a": [1, 2, 3, 4, 5, 1], "b": [4, 5, 9, 3, 1, 6]}, dtype=pd.Int64Dtype() - ) - pd_df.index = pd_df.index.astype(pd.Int64Dtype()) - df = bpd.DataFrame(pd_df, session=unordered_session) - - with warnings.catch_warnings(): - warnings.simplefilter("error") - df.groupby("a").head(3) - - -@pytest.mark.parametrize( - ("rule", "origin", "data"), - [ - ( - "5h", - "epoch", - { - "timestamp_col": pd.date_range( - start="2021-01-01 13:00:00", periods=30, freq="1h" - ), - "int64_col": range(30), - "int64_too": range(10, 40), - }, - ), - ( - "5h", - "epoch", - { - "timestamp_col": pd.DatetimeIndex( - pd.date_range( - start="2021-01-01 13:00:00", periods=15, freq="1h" - ).tolist() - + pd.date_range( - start="2021-01-01 13:00:00", periods=15, freq="1h" - ).tolist() - ), - "int64_col": range(30), - "int64_too": range(10, 40), - }, - ), - ], -) -def test_resample_with_index(unordered_session, rule, origin, data): - # TODO: supply a reason why this isn't compatible with pandas 1.x - pytest.importorskip("pandas", minversion="2.0.0") - col = "timestamp_col" - scalars_df_index = bpd.DataFrame(data, session=unordered_session).set_index(col) - scalars_pandas_df_index = pd.DataFrame(data).set_index(col) - scalars_pandas_df_index.index.name = None - - bf_result = scalars_df_index.resample(rule=rule, origin=origin).min() - pd_result = scalars_pandas_df_index.resample(rule=rule, origin=origin).min() - - assert isinstance(bf_result.index, bpd.DatetimeIndex) - assert isinstance(pd_result.index, pd.DatetimeIndex) - # TODO: (b/484364312) - pd_result.index.name = bf_result.index.name - assert_frame_equal( - bf_result.to_pandas(), - pd_result, - check_index_type=False, - check_dtype=False, - ) - - -@pytest.mark.parametrize( - ("values", "index", "columns"), - [ - ("int64_col", "int64_too", ["string_col"]), - (["int64_col"], "int64_too", ["string_col"]), - (["int64_col", "float64_col"], "int64_too", ["string_col"]), - ], -) -def test_unordered_df_pivot( - scalars_df_unordered, scalars_pandas_df_index, values, index, columns -): - bf_result = scalars_df_unordered.pivot( - values=values, index=index, columns=columns - ).to_pandas() - pd_result = scalars_pandas_df_index.pivot( - values=values, index=index, columns=columns - ) - - # Pandas produces NaN, where bq dataframes produces pd.NA - bf_result = bf_result.fillna(float("nan")) - pd_result = pd_result.fillna(float("nan")) - assert_frame_equal(bf_result, pd_result, check_dtype=False) diff --git a/tests/system/small/test_window.py b/tests/system/small/test_window.py index a70a676e84d..2b9ec1a3c0f 100644 --- a/tests/system/small/test_window.py +++ b/tests/system/small/test_window.py @@ -12,157 +12,9 @@ # See the License for the specific language governing permissions and # limitations under the License. -import datetime - -import numpy as np import pandas as pd import pytest -import bigframes.testing.utils -from bigframes import dtypes - - -@pytest.fixture(scope="module") -def rows_rolling_dfs(scalars_dfs): - bf_df, pd_df = scalars_dfs - - target_cols = ["int64_too", "float64_col", "int64_col"] - - return bf_df[target_cols], pd_df[target_cols] - - -@pytest.fixture(scope="module") -def range_rolling_dfs(session): - values = np.arange(20) - pd_df = pd.DataFrame( - { - "ts_col": pd.Timestamp("20250101", tz="UTC") + pd.to_timedelta(values, "s"), - "int_col": values % 4, - "float_col": values / 2, - } - ) - - bf_df = session.read_pandas(pd_df) - - return bf_df, pd_df - - -@pytest.fixture(scope="module") -def rows_rolling_series(scalars_dfs): - bf_df, pd_df = scalars_dfs - target_col = "int64_too" - - return bf_df[target_col], pd_df[target_col] - - -@pytest.mark.parametrize("closed", ["left", "right", "both", "neither"]) -def test_dataframe_rolling_closed_param(rows_rolling_dfs, closed): - bf_df, pd_df = rows_rolling_dfs - - actual_result = bf_df.rolling(window=3, closed=closed).sum().to_pandas() - - expected_result = pd_df.rolling(window=3, closed=closed).sum() - bigframes.testing.utils.assert_frame_equal( - actual_result, expected_result, check_dtype=False - ) - - -@pytest.mark.parametrize("closed", ["left", "right", "both", "neither"]) -def test_dataframe_groupby_rolling_closed_param(rows_rolling_dfs, closed): - bf_df, pd_df = rows_rolling_dfs - # Need to specify column subset for comparison due to b/406841327 - check_columns = ["float64_col", "int64_col"] - - actual_result = ( - bf_df.groupby(bf_df["int64_too"] % 2) - .rolling(window=3, closed=closed) - .sum() - .to_pandas() - ) - - expected_result = ( - pd_df.groupby(pd_df["int64_too"] % 2).rolling(window=3, closed=closed).sum() - ) - bigframes.testing.utils.assert_frame_equal( - actual_result[check_columns], expected_result, check_dtype=False - ) - - -def test_dataframe_rolling_on(rows_rolling_dfs): - bf_df, pd_df = rows_rolling_dfs - - actual_result = bf_df.rolling(window=3, on="int64_too").sum().to_pandas() - - expected_result = pd_df.rolling(window=3, on="int64_too").sum() - bigframes.testing.utils.assert_frame_equal( - actual_result, expected_result, check_dtype=False - ) - - -def test_dataframe_rolling_on_invalid_column_raise_error(rows_rolling_dfs): - bf_df, _ = rows_rolling_dfs - - with pytest.raises(ValueError): - bf_df.rolling(window=3, on="whatever").sum() - - -def test_dataframe_groupby_rolling_on(rows_rolling_dfs): - bf_df, pd_df = rows_rolling_dfs - # Need to specify column subset for comparison due to b/406841327 - check_columns = ["float64_col", "int64_col"] - - actual_result = ( - bf_df.groupby(bf_df["int64_too"] % 2) - .rolling(window=3, on="float64_col") - .sum() - .to_pandas() - ) - - expected_result = ( - pd_df.groupby(pd_df["int64_too"] % 2).rolling(window=3, on="float64_col").sum() - ) - bigframes.testing.utils.assert_frame_equal( - actual_result[check_columns], expected_result, check_dtype=False - ) - - -def test_dataframe_groupby_rolling_on_invalid_column_raise_error(rows_rolling_dfs): - bf_df, _ = rows_rolling_dfs - - with pytest.raises(ValueError): - bf_df.groupby(level=0).rolling(window=3, on="whatever").sum() - - -@pytest.mark.parametrize("closed", ["left", "right", "both", "neither"]) -def test_series_rolling_closed_param(rows_rolling_series, closed): - bf_series, df_series = rows_rolling_series - - actual_result = bf_series.rolling(window=3, closed=closed).sum().to_pandas() - - expected_result = df_series.rolling(window=3, closed=closed).sum() - bigframes.testing.utils.assert_series_equal( - actual_result, expected_result, check_dtype=False - ) - - -@pytest.mark.parametrize("closed", ["left", "right", "both", "neither"]) -def test_series_groupby_rolling_closed_param(rows_rolling_series, closed): - bf_series, df_series = rows_rolling_series - - actual_result = ( - bf_series.groupby(bf_series % 2) - .rolling(window=3, closed=closed) - .sum() - .to_pandas() - ) - - expected_result = ( - df_series.groupby(df_series % 2).rolling(window=3, closed=closed).sum() - ) - bigframes.testing.utils.assert_series_equal( - actual_result, expected_result, check_dtype=False - ) - @pytest.mark.parametrize( ("windowing"), @@ -189,14 +41,19 @@ def test_series_groupby_rolling_closed_param(rows_rolling_series, closed): pytest.param(lambda x: x.var(), id="var"), ], ) -def test_series_window_agg_ops(rows_rolling_series, windowing, agg_op): - bf_series, pd_series = rows_rolling_series +def test_series_window_agg_ops( + scalars_df_index, scalars_pandas_df_index, windowing, agg_op +): + col_name = "int64_too" + bf_series = agg_op(windowing(scalars_df_index[col_name])).to_pandas() + pd_series = agg_op(windowing(scalars_pandas_df_index[col_name])) - actual_result = agg_op(windowing(bf_series)).to_pandas() + # Pandas always converts to float64, even for min/max/count, which is not desired + pd_series = pd_series.astype(bf_series.dtype) - expected_result = agg_op(windowing(pd_series)) - bigframes.testing.utils.assert_series_equal( - expected_result, actual_result, check_dtype=False + pd.testing.assert_series_equal( + pd_series, + bf_series, ) @@ -226,256 +83,13 @@ def test_series_window_agg_ops(rows_rolling_series, windowing, agg_op): pytest.param(lambda x: x.var(), id="var"), ], ) -def test_dataframe_window_agg_ops(scalars_dfs, windowing, agg_op): - bf_df, pd_df = scalars_dfs - target_columns = ["int64_too", "float64_col", "bool_col"] - index_column = "bool_col" - bf_df = bf_df[target_columns].set_index(index_column) - pd_df = pd_df[target_columns].set_index(index_column) - - bf_result = agg_op(windowing(bf_df)).to_pandas() - - pd_result = agg_op(windowing(pd_df)) - bigframes.testing.utils.assert_frame_equal(pd_result, bf_result, check_dtype=False) - - -@pytest.mark.parametrize( - ("windowing"), - [ - pytest.param(lambda x: x.expanding(), id="expanding"), - pytest.param(lambda x: x.rolling(3, min_periods=3), id="rolling"), - pytest.param( - lambda x: x.groupby(level=0).rolling(3, min_periods=3), id="rollinggroupby" - ), - pytest.param( - lambda x: x.groupby("int64_too").expanding(min_periods=2), - id="expandinggroupby", - ), - ], -) -@pytest.mark.parametrize( - ("func"), - [ - pytest.param("sum", id="sum_by_name"), - pytest.param(np.sum, id="sum_by_by_np"), - pytest.param([np.sum, np.mean], id="list_of_funcs"), - pytest.param( - {"int64_col": np.sum, "float64_col": "mean"}, id="dict_of_single_funcs" - ), - pytest.param( - {"int64_col": np.sum, "float64_col": ["mean", np.max]}, - id="dict_of_lists_and_single_funcs", - ), - ], -) -def test_dataframe_window_agg_func(scalars_dfs, windowing, func): - if pd.__version__.startswith("3"): - pytest.skip( - "pandas 3.0 bugged for this case 'Length of values (8) does not match length of index (9)'" - ) - bf_df, pd_df = scalars_dfs - target_columns = ["int64_too", "float64_col", "bool_col", "int64_col"] - index_column = "bool_col" - bf_df = bf_df[target_columns].set_index(index_column) - pd_df = pd_df[target_columns].set_index(index_column) - - bf_result = windowing(bf_df).agg(func).to_pandas() - - pd_result = windowing(pd_df).agg(func) - - bigframes.testing.utils.assert_frame_equal(pd_result, bf_result, check_dtype=False) - - -def test_series_window_agg_single_func(scalars_dfs): - bf_df, pd_df = scalars_dfs - index_column = "bool_col" - bf_series = bf_df.set_index(index_column).int64_too - pd_series = pd_df.set_index(index_column).int64_too - - bf_result = bf_series.expanding().agg("sum").to_pandas() - - pd_result = pd_series.expanding().agg("sum") - - bigframes.testing.utils.assert_series_equal(pd_result, bf_result, check_dtype=False) - - -def test_series_window_agg_multi_func(scalars_dfs): - bf_df, pd_df = scalars_dfs - index_column = "bool_col" - bf_series = bf_df.set_index(index_column).int64_too - pd_series = pd_df.set_index(index_column).int64_too - - bf_result = bf_series.expanding().agg(["sum", np.mean]).to_pandas() - - pd_result = pd_series.expanding().agg(["sum", np.mean]) - - bigframes.testing.utils.assert_frame_equal(pd_result, bf_result, check_dtype=False) - - -@pytest.mark.parametrize("closed", ["left", "right", "both", "neither"]) -@pytest.mark.parametrize( - "window", # skipped numpy timedelta because Pandas does not support it. - [pd.Timedelta("3s"), datetime.timedelta(seconds=3), "3s"], -) -@pytest.mark.parametrize("ascending", [True, False]) -def test_series_range_rolling(range_rolling_dfs, window, closed, ascending): - bf_df, pd_df = range_rolling_dfs - bf_series = bf_df.set_index("ts_col")["int_col"] - pd_series = pd_df.set_index("ts_col")["int_col"] - - actual_result = ( - bf_series.sort_index(ascending=ascending) - .rolling(window=window, closed=closed) - .min() - .to_pandas() - ) - - expected_result = ( - pd_series.sort_index(ascending=ascending) - .rolling(window=window, closed=closed) - .min() - ) - bigframes.testing.utils.assert_series_equal( - actual_result, expected_result, check_dtype=False, check_index=False - ) - - -def test_series_groupby_range_rolling(range_rolling_dfs): - bf_df, pd_df = range_rolling_dfs - bf_series = bf_df.set_index("ts_col")["int_col"] - pd_series = pd_df.set_index("ts_col")["int_col"] - - actual_result = ( - bf_series.sort_index() - .groupby(bf_series % 2 == 0) - .rolling(window="3s") - .min() - .to_pandas() - ) - - expected_result = ( - pd_series.sort_index().groupby(pd_series % 2 == 0).rolling(window="3s").min() - ) - bigframes.testing.utils.assert_series_equal( - actual_result, expected_result, check_dtype=False, check_index=False - ) - - -@pytest.mark.parametrize("closed", ["left", "right", "both", "neither"]) -@pytest.mark.parametrize( - "window", # skipped numpy timedelta because Pandas does not support it. - [pd.Timedelta("3s"), datetime.timedelta(seconds=3), "3s"], -) -@pytest.mark.parametrize("ascending", [True, False]) -def test_dataframe_range_rolling(range_rolling_dfs, window, closed, ascending): - bf_df, pd_df = range_rolling_dfs - bf_df = bf_df.set_index("ts_col") - pd_df = pd_df.set_index("ts_col") - - actual_result = ( - bf_df.sort_index(ascending=ascending) - .rolling(window=window, closed=closed) - .min() - .to_pandas() - ) - - expected_result = ( - pd_df.sort_index(ascending=ascending) - .rolling(window=window, closed=closed) - .min() - ) - # Need to cast Pandas index type. Otherwise it uses DatetimeIndex that - # does not exist in BigFrame - expected_result.index = expected_result.index.astype(dtypes.TIMESTAMP_DTYPE) - bigframes.testing.utils.assert_frame_equal( - actual_result, - expected_result, - check_dtype=False, - ) - - -def test_dataframe_range_rolling_on(range_rolling_dfs): - bf_df, pd_df = range_rolling_dfs - on = "ts_col" - - actual_result = bf_df.sort_values(on).rolling(window="3s", on=on).min().to_pandas() - - expected_result = pd_df.sort_values(on).rolling(window="3s", on=on).min() - # Need to specify the column order because Pandas (seemingly) - # re-arranges columns alphabetically - cols = ["ts_col", "int_col", "float_col"] - bigframes.testing.utils.assert_frame_equal( - actual_result[cols], - expected_result[cols], - check_dtype=False, - check_index_type=False, - ) - - -def test_dataframe_groupby_range_rolling(range_rolling_dfs): - bf_df, pd_df = range_rolling_dfs - on = "ts_col" - - actual_result = ( - bf_df.sort_values(on) - .groupby("int_col") - .rolling(window="3s", on=on) - .min() - .to_pandas() - ) - - expected_result = ( - pd_df.sort_values(on).groupby("int_col").rolling(window="3s", on=on).min() - ) - expected_result.index = expected_result.index.set_names("index", level=1) - bigframes.testing.utils.assert_frame_equal( - actual_result, - expected_result, - check_dtype=False, - check_index_type=False, - ) - - -def test_range_rolling_order_info_lookup(range_rolling_dfs): - bf_df, pd_df = range_rolling_dfs - - actual_result = ( - bf_df.set_index("ts_col") - .sort_index(ascending=False)["int_col"] - .isin(bf_df["int_col"]) - .rolling(window="3s") - .count() - .to_pandas() - ) - - expected_result = ( - pd_df.set_index("ts_col") - .sort_index(ascending=False)["int_col"] - .isin(pd_df["int_col"]) - .rolling(window="3s") - .count() - ) - bigframes.testing.utils.assert_series_equal( - actual_result, expected_result, check_dtype=False, check_index=False - ) - - -def test_range_rolling_unsupported_index_type_raise_error(range_rolling_dfs): - bf_df, _ = range_rolling_dfs - - with pytest.raises(ValueError): - bf_df["int_col"].sort_index().rolling(window="3s") - - -def test_range_rolling_unsorted_index_raise_error(range_rolling_dfs): - bf_df, _ = range_rolling_dfs - - with pytest.raises(ValueError): - bf_df.set_index("ts_col")["int_col"].rolling(window="3s") - - -def test_range_rolling_unsorted_column_raise_error(range_rolling_dfs): - bf_df, _ = range_rolling_dfs - - with pytest.raises(ValueError): - bf_df.rolling(window="3s", on="ts_col") +def test_dataframe_window_agg_ops( + scalars_df_index, scalars_pandas_df_index, windowing, agg_op +): + scalars_df_index = scalars_df_index.set_index("bool_col") + scalars_pandas_df_index = scalars_pandas_df_index.set_index("bool_col") + col_names = ["int64_too", "float64_col"] + bf_result = agg_op(windowing(scalars_df_index[col_names])).to_pandas() + pd_result = agg_op(windowing(scalars_pandas_df_index[col_names])) + + pd.testing.assert_frame_equal(pd_result, bf_result, check_dtype=False) diff --git a/tests/system/utils.py b/tests/system/utils.py new file mode 100644 index 00000000000..e2daf3b8bf0 --- /dev/null +++ b/tests/system/utils.py @@ -0,0 +1,141 @@ +# Copyright 2023 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 base64 +import decimal + +import geopandas as gpd # type: ignore +import numpy as np +import pandas as pd +import pyarrow as pa # type: ignore + + +def assert_pandas_df_equal_ignore_ordering(df0, df1, **kwargs): + # Sort by a column to get consistent results. + if df0.index.name != "rowindex": + df0 = df0.sort_values( + list(df0.columns.drop("geography_col", errors="ignore")) + ).reset_index(drop=True) + df1 = df1.sort_values( + list(df1.columns.drop("geography_col", errors="ignore")) + ).reset_index(drop=True) + else: + df0 = df0.sort_index() + df1 = df1.sort_index() + + pd.testing.assert_frame_equal(df0, df1, **kwargs) + + +def assert_series_equal_ignoring_order(left: pd.Series, right: pd.Series, **kwargs): + if left.index.name is None: + left = left.sort_values().reset_index(drop=True) + right = right.sort_values().reset_index(drop=True) + else: + left = left.sort_index() + right = right.sort_index() + + pd.testing.assert_series_equal(left, right, **kwargs) + + +def _standardize_index(idx): + return pd.Index(list(idx), name=idx.name) + + +def assert_pandas_index_equal_ignore_index_type(idx0, idx1): + idx0 = _standardize_index(idx0) + idx1 = _standardize_index(idx1) + + pd.testing.assert_index_equal(idx0, idx1) + + +def convert_pandas_dtypes(df: pd.DataFrame, bytes_col: bool): + """Convert pandas dataframe dtypes compatible with bigframes dataframe.""" + + # TODO(chelsealin): updates the function to accept dtypes as input rather than + # hard-code the column names here. + + # Convert basic types columns + df["bool_col"] = df["bool_col"].astype(pd.BooleanDtype()) + df["int64_col"] = df["int64_col"].astype(pd.Int64Dtype()) + df["int64_too"] = df["int64_too"].astype(pd.Int64Dtype()) + df["float64_col"] = df["float64_col"].astype(pd.Float64Dtype()) + df["string_col"] = df["string_col"].astype(pd.StringDtype(storage="pyarrow")) + + if "rowindex" in df.columns: + df["rowindex"] = df["rowindex"].astype(pd.Int64Dtype()) + if "rowindex_2" in df.columns: + df["rowindex_2"] = df["rowindex_2"].astype(pd.Int64Dtype()) + + # Convert time types columns. The `astype` works for Pandas 2.0 but hits an assert + # error at Pandas 1.5. Hence, we have to convert to arrow table and convert back + # to pandas dataframe. + if not isinstance(df["date_col"].dtype, pd.ArrowDtype): + df["date_col"] = pd.to_datetime(df["date_col"], format="%Y-%m-%d") + arrow_table = pa.Table.from_pandas( + pd.DataFrame(df, columns=["date_col"]), + schema=pa.schema([("date_col", pa.date32())]), + ) + df["date_col"] = arrow_table.to_pandas(types_mapper=pd.ArrowDtype)["date_col"] + + if not isinstance(df["datetime_col"].dtype, pd.ArrowDtype): + df["datetime_col"] = pd.to_datetime( + df["datetime_col"], format="%Y-%m-%d %H:%M:%S" + ) + arrow_table = pa.Table.from_pandas( + pd.DataFrame(df, columns=["datetime_col"]), + schema=pa.schema([("datetime_col", pa.timestamp("us"))]), + ) + df["datetime_col"] = arrow_table.to_pandas(types_mapper=pd.ArrowDtype)[ + "datetime_col" + ] + + if not isinstance(df["time_col"].dtype, pd.ArrowDtype): + df["time_col"] = pd.to_datetime(df["time_col"], format="%H:%M:%S.%f") + arrow_table = pa.Table.from_pandas( + pd.DataFrame(df, columns=["time_col"]), + schema=pa.schema([("time_col", pa.time64("us"))]), + ) + df["time_col"] = arrow_table.to_pandas(types_mapper=pd.ArrowDtype)["time_col"] + + if not isinstance(df["timestamp_col"].dtype, pd.ArrowDtype): + df["timestamp_col"] = pd.to_datetime( + df["timestamp_col"], format="%Y-%m-%d %H:%M:%S.%f%Z" + ) + arrow_table = pa.Table.from_pandas( + pd.DataFrame(df, columns=["timestamp_col"]), + schema=pa.schema([("timestamp_col", pa.timestamp("us", tz="UTC"))]), + ) + df["timestamp_col"] = arrow_table.to_pandas(types_mapper=pd.ArrowDtype)[ + "timestamp_col" + ] + + # Convert geography types columns. + if "geography_col" in df.columns: + df["geography_col"] = df["geography_col"].astype( + pd.StringDtype(storage="pyarrow") + ) + df["geography_col"] = gpd.GeoSeries.from_wkt( + df["geography_col"].replace({np.nan: None}) + ) + + # Convert bytes types column. + if bytes_col: + df["bytes_col"] = df["bytes_col"].apply( + lambda value: base64.b64decode(value) if not pd.isnull(value) else value + ) + + # Convert numeric types column. + df["numeric_col"] = df["numeric_col"].apply( + lambda value: decimal.Decimal(str(value)) if value else None # type: ignore + ) diff --git a/tests/unit/_config/test_bigquery_options.py b/tests/unit/_config/test_bigquery_options.py index 0c51abfd95c..e5b6cfe2f1b 100644 --- a/tests/unit/_config/test_bigquery_options.py +++ b/tests/unit/_config/test_bigquery_options.py @@ -13,15 +13,10 @@ # limitations under the License. import re -import warnings -from unittest import mock -import google.auth.credentials import pytest -import bigframes import bigframes._config.bigquery_options as bigquery_options -import bigframes.exceptions @pytest.mark.parametrize( @@ -34,11 +29,6 @@ ("project", "my-project", "my-other-project"), ("bq_connection", "path/to/connection/1", "path/to/connection/2"), ("use_regional_endpoints", False, True), - ("kms_key_name", "kms/key/name/1", "kms/key/name/2"), - ("skip_bq_connection_check", False, True), - ("client_endpoints_override", {}, {"bqclient": "endpoint_address"}), - ("ordering_mode", "strict", "partial"), - ("requests_transport_adapters", object(), object()), ], ) def test_setter_raises_if_session_started(attribute, original_value, new_value): @@ -58,153 +48,30 @@ def test_setter_raises_if_session_started(attribute, original_value, new_value): assert getattr(options, attribute) is not new_value -def test_location_set_us_twice(): - """This test ensures the fix for b/423220936 is working as expected.""" - options = bigquery_options.BigQueryOptions() - setattr(options, "location", "us") - assert getattr(options, "location") == "US" - - options._session_started = True - - setattr(options, "location", "us") - assert getattr(options, "location") == "US" - - @pytest.mark.parametrize( [ "attribute", - "original_value", ], [ - ("application_name", "test-partner"), - ("location", "us-east1"), - ("project", "my-project"), - ("bq_connection", "path/to/connection/1"), - ("use_regional_endpoints", True), - ("kms_key_name", "kms/key/name/1"), - ("skip_bq_connection_check", True), - ("client_endpoints_override", {"bqclient": "endpoint_address"}), - ("ordering_mode", "partial"), + (attribute,) + for attribute in [ + "application_name", + "credentials", + "location", + "project", + "bq_connection", + "use_regional_endpoints", + ] ], ) -def test_setter_if_session_started_but_setting_the_same_value( - attribute, original_value -): +def test_setter_if_session_started_but_setting_the_same_value(attribute): options = bigquery_options.BigQueryOptions() - setattr(options, attribute, original_value) - assert getattr(options, attribute) == original_value + original_object = object() + setattr(options, attribute, original_object) + assert getattr(options, attribute) is original_object # This should work fine since we're setting the same value as before. options._session_started = True - setattr(options, attribute, original_value) - - assert getattr(options, attribute) == original_value - - -def test_setter_if_session_started_but_setting_the_same_credentials_object(): - options = bigquery_options.BigQueryOptions() - original_object = mock.create_autospec( - google.auth.credentials.Credentials, instance=True - ) - options.credentials = original_object - assert options.credentials is original_object - - # This should work fine since we're setting the same value as before. - options._session_started = True - options.credentials = original_object - assert options.credentials is original_object - - -@pytest.mark.parametrize( - [ - "valid_location", - ], - [ - (None,), - ("us-central1",), - ("us-Central1",), - ("US-CENTRAL1",), - ("US",), - ("us",), - ], -) -def test_location_set_to_valid_no_warning(valid_location): - # test setting location through constructor - def set_location_in_constructor(): - bigquery_options.BigQueryOptions(location=valid_location) - - # test setting location property - def set_location_property(): - options = bigquery_options.BigQueryOptions() - options.location = valid_location - - for op in [set_location_in_constructor, set_location_property]: - # Ensure that no warnings are emitted. - # https://docs.pytest.org/en/7.0.x/how-to/capture-warnings.html#additional-use-cases-of-warnings-in-tests - with warnings.catch_warnings(): - # Turn matching UnknownLocationWarning into exceptions. - # https://docs.python.org/3/library/warnings.html#warning-filter - warnings.simplefilter( - "error", category=bigframes.exceptions.UnknownLocationWarning - ) - op() - - -@pytest.mark.parametrize( - [ - "invalid_location", - "possibility", - ], - [ - # Test with common mistakes, see article. - # https://en.wikipedia.org/wiki/Edit_distance#Formal_definition_and_properties - # Substitution - ("us-wist3", "us-west3"), - # Insertion - ("us-central-1", "us-central1"), - # Deletion - ("asia-suth2", "asia-south2"), - ], -) -def test_location_set_to_invalid_warning(invalid_location, possibility): - # test setting location through constructor - def set_location_in_constructor(): - bigquery_options.BigQueryOptions(location=invalid_location) - - # test setting location property - def set_location_property(): - options = bigquery_options.BigQueryOptions() - options.location = invalid_location - - for op in [set_location_in_constructor, set_location_property]: - with warnings.catch_warnings(record=True) as w: - op() - - assert issubclass( - w[0].category, bigframes.exceptions.UnknownLocationWarning - ) - assert ( - f"The location '{invalid_location}' is set to an unknown value. " - in str(w[0].message) - ) - # The message might contain newlines added by textwrap.fill. - assert possibility in str(w[0].message).replace("\n", "") - - -def test_client_endpoints_override_set_shows_warning(): - options = bigquery_options.BigQueryOptions() - - with pytest.warns(UserWarning): - options.client_endpoints_override = {"bqclient": "endpoint_address"} - - -def test_default_options(): - options = bigquery_options.BigQueryOptions() - - assert options.allow_large_results is False - assert options.ordering_mode == "strict" + setattr(options, attribute, original_object) - # We should default to None as an indicator that the user hasn't set these - # explicitly. See internal issue b/445731915. - assert options.credentials is None - assert options.project is None + assert getattr(options, attribute) is original_object diff --git a/tests/unit/_config/test_compute_options.py b/tests/unit/_config/test_compute_options.py deleted file mode 100644 index e06eb76c374..00000000000 --- a/tests/unit/_config/test_compute_options.py +++ /dev/null @@ -1,22 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import bigframes._config as config - - -def test_default_options(): - options = config.compute_options.ComputeOptions() - - assert options.allow_large_results is None - assert config.options._allow_large_results is False diff --git a/tests/unit/_config/test_experiment_options.py b/tests/unit/_config/test_experiment_options.py deleted file mode 100644 index 0d66b2156ab..00000000000 --- a/tests/unit/_config/test_experiment_options.py +++ /dev/null @@ -1,32 +0,0 @@ -# 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. -# 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 - -import bigframes._config.experiment_options as experiment_options - - -def test_sql_compiler_default_stable(): - options = experiment_options.ExperimentOptions() - - assert options.sql_compiler == "stable" - - -def test_sql_compiler_set_experimental_shows_warning(): - options = experiment_options.ExperimentOptions() - - with pytest.warns(FutureWarning): - options.sql_compiler = "experimental" - - assert options.sql_compiler == "experimental" diff --git a/tests/unit/_config/test_threaded_options.py b/tests/unit/_config/test_threaded_options.py deleted file mode 100644 index b16a3550bc0..00000000000 --- a/tests/unit/_config/test_threaded_options.py +++ /dev/null @@ -1,41 +0,0 @@ -# Copyright 2023 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 threading - -import bigframes._config - - -def test_mutate_options_threaded(): - options = bigframes._config.Options() - options.display.max_rows = 50 - result_dict = {"this_before": options.display.max_rows} - - def mutate_options_threaded(options, result_dict): - result_dict["other_before"] = options.display.max_rows - - options.display.max_rows = 100 - result_dict["other_after"] = options.display.max_rows - - thread = threading.Thread( - target=(lambda: mutate_options_threaded(options, result_dict)) - ) - thread.start() - thread.join(1) - result_dict["this_after"] = options.display.max_rows - - assert result_dict["this_before"] == 50 - assert result_dict["this_after"] == 50 - assert result_dict["other_before"] == 10 - assert result_dict["other_after"] == 100 diff --git a/tests/unit/_tools/__init__.py b/tests/unit/_tools/__init__.py deleted file mode 100644 index 378d15c4be6..00000000000 --- a/tests/unit/_tools/__init__.py +++ /dev/null @@ -1,19 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -"""Tests for helper methods for processing Python objects with minimal dependencies. - -Please keep the dependencies used in this subpackage to a minimum to avoid the -risk of circular dependencies. -""" diff --git a/tests/unit/_tools/test_strings.py b/tests/unit/_tools/test_strings.py deleted file mode 100644 index 9c83df25568..00000000000 --- a/tests/unit/_tools/test_strings.py +++ /dev/null @@ -1,149 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -"""Tests for helper methods for processing strings with minimal dependencies. - -Please keep the dependencies used in this subpackage to a minimum to avoid the -risk of circular dependencies. -""" - -import base64 -import random -import sys -import uuid - -import pytest - -from bigframes._tools import strings - -# To stress test some unicode comparisons. -# https://stackoverflow.com/a/39682429/101923 -ALL_UNICODE_CHARS = "".join(chr(i) for i in range(32, 0x110000) if chr(i).isprintable()) -RANDOM_STRINGS = ( - pytest.param(str(uuid.uuid4()), id="uuid4"), - pytest.param(hex(random.randint(0, sys.maxsize)), id="hex"), - pytest.param( - base64.b64encode( - "".join(random.choice(ALL_UNICODE_CHARS) for _ in range(100)).encode( - "utf-8" - ) - ).decode("utf-8"), - id="base64", - ), - pytest.param( - "".join(random.choice(ALL_UNICODE_CHARS) for _ in range(8)), id="unicode8" - ), - pytest.param( - "".join(random.choice(ALL_UNICODE_CHARS) for _ in range(64)), id="unicode64" - ), -) - - -def random_char_not_equal(avoid: str): - random_char = avoid - while random_char == avoid: - random_char = random.choice(ALL_UNICODE_CHARS) - return random_char - - -def random_deletion(original: str): - """original string with one character removed""" - char_index = random.randrange(len(original)) - return original[:char_index] + original[char_index + 1 :] - - -def random_insertion(original: str): - char_index = random.randrange(len(original)) - random_char = random.choice(ALL_UNICODE_CHARS) - return original[: char_index + 1] + random_char + original[char_index + 1 :] - - -@pytest.mark.parametrize( - ("left", "right", "expected"), - ( - ("", "", 0), - ("abc", "abc", 0), - # Deletions - ("abcxyz", "abc", 3), - ("xyzabc", "abc", 3), - ("AXYZBC", "ABC", 3), - ("AXYZBC", "XYZ", 3), - # Insertions - ("abc", "abcxyz", 3), - ("abc", "xyzabc", 3), - # Substitutions - ("abc", "aBc", 1), - ("abcxyz", "aBcXyZ", 3), - # Combinations - ("abcdefxyz", "abcExyzα", 4), - ), -) -def test_levenshtein_distance(left: str, right: str, expected: int): - assert strings.levenshtein_distance(left, right) == expected - - -@pytest.mark.parametrize(("random_string",), RANDOM_STRINGS) -def test_levenshtein_distance_equal_strings(random_string: str): - """Mini fuzz test with different strings.""" - assert strings.levenshtein_distance(random_string, random_string) == 0 - - -@pytest.mark.parametrize(("random_string",), RANDOM_STRINGS) -def test_levenshtein_distance_random_deletion(random_string: str): - """Mini fuzz test with different strings.""" - - num_deleted = random.randrange(1, min(10, len(random_string))) - assert 1 <= num_deleted < len(random_string) - - deleted = random_string - for _ in range(num_deleted): - deleted = random_deletion(deleted) - - assert deleted != random_string - assert len(deleted) == len(random_string) - num_deleted - assert strings.levenshtein_distance(random_string, deleted) == num_deleted - - -@pytest.mark.parametrize(("random_string",), RANDOM_STRINGS) -def test_levenshtein_distance_random_insertion(random_string: str): - """Mini fuzz test with different strings.""" - - num_inserted = random.randrange(1, min(10, len(random_string))) - assert 1 <= num_inserted < len(random_string) - - inserted = random_string - for _ in range(num_inserted): - inserted = random_insertion(inserted) - - assert inserted != random_string - assert len(inserted) == len(random_string) + num_inserted - assert strings.levenshtein_distance(random_string, inserted) == num_inserted - - -@pytest.mark.parametrize(("random_string",), RANDOM_STRINGS) -def test_levenshtein_distance_random_substitution(random_string: str): - """Mini fuzz test with different strings. - - Note: we don't do multiple substitutions here to avoid accidentally - substituting the same character twice. - """ - char_index = random.randrange(len(random_string)) - replaced_char = random_string[char_index] - random_char = random_char_not_equal(replaced_char) - substituted = ( - random_string[:char_index] + random_char + random_string[char_index + 1 :] - ) - assert substituted != random_string - assert len(substituted) == len(random_string) - assert strings.levenshtein_distance(random_string, substituted) == 1 diff --git a/tests/unit/bigquery/__init__.py b/tests/unit/bigquery/__init__.py deleted file mode 100644 index 0a2669d7a25..00000000000 --- a/tests/unit/bigquery/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. diff --git a/tests/unit/bigquery/generated/__init__.py b/tests/unit/bigquery/generated/__init__.py deleted file mode 100644 index 58d482ea386..00000000000 --- a/tests/unit/bigquery/generated/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -# Copyright 2026 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/unit/bigquery/generated/global_namespace/__init__.py b/tests/unit/bigquery/generated/global_namespace/__init__.py deleted file mode 100644 index 58d482ea386..00000000000 --- a/tests/unit/bigquery/generated/global_namespace/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -# Copyright 2026 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/unit/bigquery/generated/global_namespace/test_aead_encryption.py b/tests/unit/bigquery/generated/global_namespace/test_aead_encryption.py deleted file mode 100644 index 818151952ff..00000000000 --- a/tests/unit/bigquery/generated/global_namespace/test_aead_encryption.py +++ /dev/null @@ -1,101 +0,0 @@ -# Copyright 2026 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. -# -# DO NOT MODIFY THIS FILE DIRECTLY. -# This file was generated from: scripts/data/sql-functions/global_namespace/aead_encryption.yaml -# by the script: scripts/generate_bigframes_bigquery.py - -import bigframes.bigquery as bbq -import bigframes.core.col -import bigframes.core.expression as ex -import bigframes.operations.googlesql.global_namespace.aead_encryption as aead_encryption_op -import bigframes.pandas as bpd - - -def test_deterministic_decrypt_bytes_expression(): - # Call the function with col() expressions - result = bbq.deterministic_decrypt_bytes( - bpd.col("keyset"), - bpd.col("ciphertext"), - bpd.col("additional_data"), - ) - - # Verify result is a col Expression - assert isinstance(result, bigframes.core.col.Expression) - - # Verify the internal expression structure - expr = result._value - assert isinstance(expr, ex.OpExpression) - assert expr.op == aead_encryption_op._DETERMINISTIC_DECRYPT_BYTES_OP - - # Verify arguments are free variables matching the names - assert len(expr.inputs) == 3 - assert isinstance(expr.inputs[0], ex.UnboundVariableExpression) - assert expr.inputs[0].id == "keyset" - assert isinstance(expr.inputs[1], ex.UnboundVariableExpression) - assert expr.inputs[1].id == "ciphertext" - assert isinstance(expr.inputs[2], ex.UnboundVariableExpression) - assert expr.inputs[2].id == "additional_data" - - -def test_deterministic_decrypt_string_expression(): - # Call the function with col() expressions - result = bbq.deterministic_decrypt_string( - bpd.col("keyset"), - bpd.col("ciphertext"), - bpd.col("additional_data"), - ) - - # Verify result is a col Expression - assert isinstance(result, bigframes.core.col.Expression) - - # Verify the internal expression structure - expr = result._value - assert isinstance(expr, ex.OpExpression) - assert expr.op == aead_encryption_op._DETERMINISTIC_DECRYPT_STRING_OP - - # Verify arguments are free variables matching the names - assert len(expr.inputs) == 3 - assert isinstance(expr.inputs[0], ex.UnboundVariableExpression) - assert expr.inputs[0].id == "keyset" - assert isinstance(expr.inputs[1], ex.UnboundVariableExpression) - assert expr.inputs[1].id == "ciphertext" - assert isinstance(expr.inputs[2], ex.UnboundVariableExpression) - assert expr.inputs[2].id == "additional_data" - - -def test_deterministic_encrypt_expression(): - # Call the function with col() expressions - result = bbq.deterministic_encrypt( - bpd.col("keyset"), - bpd.col("plaintext"), - bpd.col("additional_data"), - ) - - # Verify result is a col Expression - assert isinstance(result, bigframes.core.col.Expression) - - # Verify the internal expression structure - expr = result._value - assert isinstance(expr, ex.OpExpression) - assert expr.op == aead_encryption_op._DETERMINISTIC_ENCRYPT_OP - - # Verify arguments are free variables matching the names - assert len(expr.inputs) == 3 - assert isinstance(expr.inputs[0], ex.UnboundVariableExpression) - assert expr.inputs[0].id == "keyset" - assert isinstance(expr.inputs[1], ex.UnboundVariableExpression) - assert expr.inputs[1].id == "plaintext" - assert isinstance(expr.inputs[2], ex.UnboundVariableExpression) - assert expr.inputs[2].id == "additional_data" diff --git a/tests/unit/bigquery/generated/global_namespace/test_array.py b/tests/unit/bigquery/generated/global_namespace/test_array.py deleted file mode 100644 index 56b85386902..00000000000 --- a/tests/unit/bigquery/generated/global_namespace/test_array.py +++ /dev/null @@ -1,339 +0,0 @@ -# Copyright 2026 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. -# -# DO NOT MODIFY THIS FILE DIRECTLY. -# This file was generated from: scripts/data/sql-functions/global_namespace/array.yaml -# by the script: scripts/generate_bigframes_bigquery.py - -import bigframes.bigquery as bbq -import bigframes.core.col -import bigframes.core.expression as ex -import bigframes.operations.googlesql.global_namespace.array as array_op -import bigframes.pandas as bpd - - -def test_array_concat_expression(): - # Call the function with col() expressions - result = bbq.array_concat( - bpd.col("array_expression_1"), - bpd.col("array_expression_2"), - ) - - # Verify result is a col Expression - assert isinstance(result, bigframes.core.col.Expression) - - # Verify the internal expression structure - expr = result._value - assert isinstance(expr, ex.OpExpression) - assert expr.op == array_op._ARRAY_CONCAT_OP - - # Verify arguments are free variables matching the names - assert len(expr.inputs) == 2 - assert isinstance(expr.inputs[0], ex.UnboundVariableExpression) - assert expr.inputs[0].id == "array_expression_1" - assert isinstance(expr.inputs[1], ex.UnboundVariableExpression) - assert expr.inputs[1].id == "array_expression_2" - - -def test_array_first_expression(): - # Call the function with col() expressions - result = bbq.array_first( - bpd.col("array_expression"), - ) - - # Verify result is a col Expression - assert isinstance(result, bigframes.core.col.Expression) - - # Verify the internal expression structure - expr = result._value - assert isinstance(expr, ex.OpExpression) - assert expr.op == array_op._ARRAY_FIRST_OP - - # Verify arguments are free variables matching the names - assert len(expr.inputs) == 1 - assert isinstance(expr.inputs[0], ex.UnboundVariableExpression) - assert expr.inputs[0].id == "array_expression" - - -def test_array_first_n_expression(): - # Call the function with col() expressions - result = bbq.array_first_n( - bpd.col("input_array"), - bpd.col("n"), - ) - - # Verify result is a col Expression - assert isinstance(result, bigframes.core.col.Expression) - - # Verify the internal expression structure - expr = result._value - assert isinstance(expr, ex.OpExpression) - assert expr.op == array_op._ARRAY_FIRST_N_OP - - # Verify arguments are free variables matching the names - assert len(expr.inputs) == 2 - assert isinstance(expr.inputs[0], ex.UnboundVariableExpression) - assert expr.inputs[0].id == "input_array" - assert isinstance(expr.inputs[1], ex.UnboundVariableExpression) - assert expr.inputs[1].id == "n" - - -def test_array_includes_expression(): - # Call the function with col() expressions - result = bbq.array_includes( - bpd.col("array_to_search"), - bpd.col("search_value"), - ) - - # Verify result is a col Expression - assert isinstance(result, bigframes.core.col.Expression) - - # Verify the internal expression structure - expr = result._value - assert isinstance(expr, ex.OpExpression) - assert expr.op == array_op._ARRAY_INCLUDES_OP - - # Verify arguments are free variables matching the names - assert len(expr.inputs) == 2 - assert isinstance(expr.inputs[0], ex.UnboundVariableExpression) - assert expr.inputs[0].id == "array_to_search" - assert isinstance(expr.inputs[1], ex.UnboundVariableExpression) - assert expr.inputs[1].id == "search_value" - - -def test_array_includes_all_expression(): - # Call the function with col() expressions - result = bbq.array_includes_all( - bpd.col("array_to_search"), - bpd.col("search_values"), - ) - - # Verify result is a col Expression - assert isinstance(result, bigframes.core.col.Expression) - - # Verify the internal expression structure - expr = result._value - assert isinstance(expr, ex.OpExpression) - assert expr.op == array_op._ARRAY_INCLUDES_ALL_OP - - # Verify arguments are free variables matching the names - assert len(expr.inputs) == 2 - assert isinstance(expr.inputs[0], ex.UnboundVariableExpression) - assert expr.inputs[0].id == "array_to_search" - assert isinstance(expr.inputs[1], ex.UnboundVariableExpression) - assert expr.inputs[1].id == "search_values" - - -def test_array_includes_any_expression(): - # Call the function with col() expressions - result = bbq.array_includes_any( - bpd.col("array_to_search"), - bpd.col("search_values"), - ) - - # Verify result is a col Expression - assert isinstance(result, bigframes.core.col.Expression) - - # Verify the internal expression structure - expr = result._value - assert isinstance(expr, ex.OpExpression) - assert expr.op == array_op._ARRAY_INCLUDES_ANY_OP - - # Verify arguments are free variables matching the names - assert len(expr.inputs) == 2 - assert isinstance(expr.inputs[0], ex.UnboundVariableExpression) - assert expr.inputs[0].id == "array_to_search" - assert isinstance(expr.inputs[1], ex.UnboundVariableExpression) - assert expr.inputs[1].id == "search_values" - - -def test_array_is_distinct_expression(): - # Call the function with col() expressions - result = bbq.array_is_distinct( - bpd.col("array_expression"), - ) - - # Verify result is a col Expression - assert isinstance(result, bigframes.core.col.Expression) - - # Verify the internal expression structure - expr = result._value - assert isinstance(expr, ex.OpExpression) - assert expr.op == array_op._ARRAY_IS_DISTINCT_OP - - # Verify arguments are free variables matching the names - assert len(expr.inputs) == 1 - assert isinstance(expr.inputs[0], ex.UnboundVariableExpression) - assert expr.inputs[0].id == "array_expression" - - -def test_array_last_expression(): - # Call the function with col() expressions - result = bbq.array_last( - bpd.col("array_expression"), - ) - - # Verify result is a col Expression - assert isinstance(result, bigframes.core.col.Expression) - - # Verify the internal expression structure - expr = result._value - assert isinstance(expr, ex.OpExpression) - assert expr.op == array_op._ARRAY_LAST_OP - - # Verify arguments are free variables matching the names - assert len(expr.inputs) == 1 - assert isinstance(expr.inputs[0], ex.UnboundVariableExpression) - assert expr.inputs[0].id == "array_expression" - - -def test_array_length_expression(): - # Call the function with col() expressions - result = bbq.array_length( - bpd.col("series"), - ) - - # Verify result is a col Expression - assert isinstance(result, bigframes.core.col.Expression) - - # Verify the internal expression structure - expr = result._value - assert isinstance(expr, ex.OpExpression) - assert expr.op == array_op._ARRAY_LENGTH_OP - - # Verify arguments are free variables matching the names - assert len(expr.inputs) == 1 - assert isinstance(expr.inputs[0], ex.UnboundVariableExpression) - assert expr.inputs[0].id == "series" - - -def test_array_reverse_expression(): - # Call the function with col() expressions - result = bbq.array_reverse( - bpd.col("value"), - ) - - # Verify result is a col Expression - assert isinstance(result, bigframes.core.col.Expression) - - # Verify the internal expression structure - expr = result._value - assert isinstance(expr, ex.OpExpression) - assert expr.op == array_op._ARRAY_REVERSE_OP - - # Verify arguments are free variables matching the names - assert len(expr.inputs) == 1 - assert isinstance(expr.inputs[0], ex.UnboundVariableExpression) - assert expr.inputs[0].id == "value" - - -def test_array_slice_expression(): - # Call the function with col() expressions - result = bbq.array_slice( - bpd.col("array_to_slice"), - bpd.col("start_offset"), - bpd.col("end_offset"), - ) - - # Verify result is a col Expression - assert isinstance(result, bigframes.core.col.Expression) - - # Verify the internal expression structure - expr = result._value - assert isinstance(expr, ex.OpExpression) - assert expr.op == array_op._ARRAY_SLICE_OP - - # Verify arguments are free variables matching the names - assert len(expr.inputs) == 3 - assert isinstance(expr.inputs[0], ex.UnboundVariableExpression) - assert expr.inputs[0].id == "array_to_slice" - assert isinstance(expr.inputs[1], ex.UnboundVariableExpression) - assert expr.inputs[1].id == "start_offset" - assert isinstance(expr.inputs[2], ex.UnboundVariableExpression) - assert expr.inputs[2].id == "end_offset" - - -def test_array_to_string_expression(): - # Call the function with col() expressions - result = bbq.array_to_string( - bpd.col("series"), - bpd.col("delimiter"), - bpd.col("null_text"), - ) - - # Verify result is a col Expression - assert isinstance(result, bigframes.core.col.Expression) - - # Verify the internal expression structure - expr = result._value - assert isinstance(expr, ex.OpExpression) - assert expr.op == array_op._ARRAY_TO_STRING_OP - - # Verify arguments are free variables matching the names - assert len(expr.inputs) == 3 - assert isinstance(expr.inputs[0], ex.UnboundVariableExpression) - assert expr.inputs[0].id == "series" - assert isinstance(expr.inputs[1], ex.UnboundVariableExpression) - assert expr.inputs[1].id == "delimiter" - assert isinstance(expr.inputs[2], ex.UnboundVariableExpression) - assert expr.inputs[2].id == "null_text" - - -def test_flatten_expression(): - # Call the function with col() expressions - result = bbq.flatten( - bpd.col("array_to_flatten"), - bpd.col("depth"), - ) - - # Verify result is a col Expression - assert isinstance(result, bigframes.core.col.Expression) - - # Verify the internal expression structure - expr = result._value - assert isinstance(expr, ex.OpExpression) - assert expr.op == array_op._FLATTEN_OP - - # Verify arguments are free variables matching the names - assert len(expr.inputs) == 2 - assert isinstance(expr.inputs[0], ex.UnboundVariableExpression) - assert expr.inputs[0].id == "array_to_flatten" - assert isinstance(expr.inputs[1], ex.UnboundVariableExpression) - assert expr.inputs[1].id == "depth" - - -def test_generate_array_expression(): - # Call the function with col() expressions - result = bbq.generate_array( - bpd.col("start_expression"), - bpd.col("end_expression"), - bpd.col("step_expression"), - ) - - # Verify result is a col Expression - assert isinstance(result, bigframes.core.col.Expression) - - # Verify the internal expression structure - expr = result._value - assert isinstance(expr, ex.OpExpression) - assert expr.op == array_op._GENERATE_ARRAY_OP - - # Verify arguments are free variables matching the names - assert len(expr.inputs) == 3 - assert isinstance(expr.inputs[0], ex.UnboundVariableExpression) - assert expr.inputs[0].id == "start_expression" - assert isinstance(expr.inputs[1], ex.UnboundVariableExpression) - assert expr.inputs[1].id == "end_expression" - assert isinstance(expr.inputs[2], ex.UnboundVariableExpression) - assert expr.inputs[2].id == "step_expression" diff --git a/tests/unit/bigquery/generated/global_namespace/test_bit.py b/tests/unit/bigquery/generated/global_namespace/test_bit.py deleted file mode 100644 index 2cccafc0643..00000000000 --- a/tests/unit/bigquery/generated/global_namespace/test_bit.py +++ /dev/null @@ -1,43 +0,0 @@ -# Copyright 2026 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. -# -# DO NOT MODIFY THIS FILE DIRECTLY. -# This file was generated from: scripts/data/sql-functions/global_namespace/bit.yaml -# by the script: scripts/generate_bigframes_bigquery.py - -import bigframes.bigquery as bbq -import bigframes.core.col -import bigframes.core.expression as ex -import bigframes.operations.googlesql.global_namespace.bit as bit_op -import bigframes.pandas as bpd - - -def test_bit_count_expression(): - # Call the function with col() expressions - result = bbq.bit_count( - bpd.col("expression"), - ) - - # Verify result is a col Expression - assert isinstance(result, bigframes.core.col.Expression) - - # Verify the internal expression structure - expr = result._value - assert isinstance(expr, ex.OpExpression) - assert expr.op == bit_op._BIT_COUNT_OP - - # Verify arguments are free variables matching the names - assert len(expr.inputs) == 1 - assert isinstance(expr.inputs[0], ex.UnboundVariableExpression) - assert expr.inputs[0].id == "expression" diff --git a/tests/unit/bigquery/generated/global_namespace/test_conversion.py b/tests/unit/bigquery/generated/global_namespace/test_conversion.py deleted file mode 100644 index 84dfc02465c..00000000000 --- a/tests/unit/bigquery/generated/global_namespace/test_conversion.py +++ /dev/null @@ -1,172 +0,0 @@ -# Copyright 2026 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. -# -# DO NOT MODIFY THIS FILE DIRECTLY. -# This file was generated from: scripts/data/sql-functions/global_namespace/conversion.yaml -# by the script: scripts/generate_bigframes_bigquery.py - -import bigframes.bigquery as bbq -import bigframes.core.col -import bigframes.core.expression as ex -import bigframes.operations.googlesql.global_namespace.conversion as conversion_op -import bigframes.pandas as bpd - - -def test_bool__expression(): - # Call the function with col() expressions - result = bbq.bool_( - bpd.col("json_string_expression"), - ) - - # Verify result is a col Expression - assert isinstance(result, bigframes.core.col.Expression) - - # Verify the internal expression structure - expr = result._value - assert isinstance(expr, ex.OpExpression) - assert expr.op == conversion_op._BOOL_OP - - # Verify arguments are free variables matching the names - assert len(expr.inputs) == 1 - assert isinstance(expr.inputs[0], ex.UnboundVariableExpression) - assert expr.inputs[0].id == "json_string_expression" - - -def test_double_expression(): - # Call the function with col() expressions - result = bbq.double( - bpd.col("json_string_expression"), - bpd.col("wide_number_mode"), - ) - - # Verify result is a col Expression - assert isinstance(result, bigframes.core.col.Expression) - - # Verify the internal expression structure - expr = result._value - assert isinstance(expr, ex.OpExpression) - assert expr.op == conversion_op._DOUBLE_OP - - # Verify arguments are free variables matching the names - assert len(expr.inputs) == 2 - assert isinstance(expr.inputs[0], ex.UnboundVariableExpression) - assert expr.inputs[0].id == "json_string_expression" - assert isinstance(expr.inputs[1], ex.UnboundVariableExpression) - assert expr.inputs[1].id == "wide_number_mode" - - -def test_float64_expression(): - # Call the function with col() expressions - result = bbq.float64( - bpd.col("json_string_expression"), - bpd.col("wide_number_mode"), - ) - - # Verify result is a col Expression - assert isinstance(result, bigframes.core.col.Expression) - - # Verify the internal expression structure - expr = result._value - assert isinstance(expr, ex.OpExpression) - assert expr.op == conversion_op._FLOAT64_OP - - # Verify arguments are free variables matching the names - assert len(expr.inputs) == 2 - assert isinstance(expr.inputs[0], ex.UnboundVariableExpression) - assert expr.inputs[0].id == "json_string_expression" - assert isinstance(expr.inputs[1], ex.UnboundVariableExpression) - assert expr.inputs[1].id == "wide_number_mode" - - -def test_int64_expression(): - # Call the function with col() expressions - result = bbq.int64( - bpd.col("json_string_expression"), - ) - - # Verify result is a col Expression - assert isinstance(result, bigframes.core.col.Expression) - - # Verify the internal expression structure - expr = result._value - assert isinstance(expr, ex.OpExpression) - assert expr.op == conversion_op._INT64_OP - - # Verify arguments are free variables matching the names - assert len(expr.inputs) == 1 - assert isinstance(expr.inputs[0], ex.UnboundVariableExpression) - assert expr.inputs[0].id == "json_string_expression" - - -def test_parse_bignumeric_expression(): - # Call the function with col() expressions - result = bbq.parse_bignumeric( - bpd.col("string_expression"), - ) - - # Verify result is a col Expression - assert isinstance(result, bigframes.core.col.Expression) - - # Verify the internal expression structure - expr = result._value - assert isinstance(expr, ex.OpExpression) - assert expr.op == conversion_op._PARSE_BIGNUMERIC_OP - - # Verify arguments are free variables matching the names - assert len(expr.inputs) == 1 - assert isinstance(expr.inputs[0], ex.UnboundVariableExpression) - assert expr.inputs[0].id == "string_expression" - - -def test_parse_numeric_expression(): - # Call the function with col() expressions - result = bbq.parse_numeric( - bpd.col("string_expression"), - ) - - # Verify result is a col Expression - assert isinstance(result, bigframes.core.col.Expression) - - # Verify the internal expression structure - expr = result._value - assert isinstance(expr, ex.OpExpression) - assert expr.op == conversion_op._PARSE_NUMERIC_OP - - # Verify arguments are free variables matching the names - assert len(expr.inputs) == 1 - assert isinstance(expr.inputs[0], ex.UnboundVariableExpression) - assert expr.inputs[0].id == "string_expression" - - -def test_string_expression(): - # Call the function with col() expressions - result = bbq.string( - bpd.col("expression"), - bpd.col("timezone"), - ) - - # Verify result is a col Expression - assert isinstance(result, bigframes.core.col.Expression) - - # Verify the internal expression structure - expr = result._value - assert isinstance(expr, ex.OpExpression) - assert expr.op == conversion_op._STRING_OP - - # Verify arguments are free variables matching the names - assert len(expr.inputs) == 2 - assert isinstance(expr.inputs[0], ex.UnboundVariableExpression) - assert expr.inputs[0].id == "expression" - assert isinstance(expr.inputs[1], ex.UnboundVariableExpression) - assert expr.inputs[1].id == "timezone" diff --git a/tests/unit/bigquery/generated/global_namespace/test_date.py b/tests/unit/bigquery/generated/global_namespace/test_date.py deleted file mode 100644 index 6484208584f..00000000000 --- a/tests/unit/bigquery/generated/global_namespace/test_date.py +++ /dev/null @@ -1,340 +0,0 @@ -# Copyright 2026 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. -# -# DO NOT MODIFY THIS FILE DIRECTLY. -# This file was generated from: scripts/data/sql-functions/global_namespace/date.yaml -# by the script: scripts/generate_bigframes_bigquery.py - -import bigframes.bigquery as bbq -import bigframes.core.col -import bigframes.core.expression as ex -import bigframes.operations.googlesql.global_namespace.date as date_op -import bigframes.pandas as bpd - - -def test_current_date_expression(): - # Call the function with col() expressions - result = bbq.current_date( - bpd.col("time_zone_expression"), - ) - - # Verify result is a col Expression - assert isinstance(result, bigframes.core.col.Expression) - - # Verify the internal expression structure - expr = result._value - assert isinstance(expr, ex.OpExpression) - assert expr.op == date_op._CURRENT_DATE_OP - - # Verify arguments are free variables matching the names - assert len(expr.inputs) == 1 - assert isinstance(expr.inputs[0], ex.UnboundVariableExpression) - assert expr.inputs[0].id == "time_zone_expression" - - -def test_date_expression(): - # Call the function with col() expressions - result = bbq.date( - bpd.col("expression"), - bpd.col("time_zone_expression"), - bpd.col("year"), - bpd.col("month"), - bpd.col("day"), - ) - - # Verify result is a col Expression - assert isinstance(result, bigframes.core.col.Expression) - - # Verify the internal expression structure - expr = result._value - assert isinstance(expr, ex.OpExpression) - assert expr.op == date_op._DATE_OP - - # Verify arguments are free variables matching the names - assert len(expr.inputs) == 5 - assert isinstance(expr.inputs[0], ex.UnboundVariableExpression) - assert expr.inputs[0].id == "expression" - assert isinstance(expr.inputs[1], ex.UnboundVariableExpression) - assert expr.inputs[1].id == "time_zone_expression" - assert isinstance(expr.inputs[2], ex.UnboundVariableExpression) - assert expr.inputs[2].id == "year" - assert isinstance(expr.inputs[3], ex.UnboundVariableExpression) - assert expr.inputs[3].id == "month" - assert isinstance(expr.inputs[4], ex.UnboundVariableExpression) - assert expr.inputs[4].id == "day" - - -def test_date_add_expression(): - # Call the function with col() expressions - result = bbq.date_add( - bpd.col("date_expression"), - bpd.col("int64_expression"), - bpd.col("date_part"), - ) - - # Verify result is a col Expression - assert isinstance(result, bigframes.core.col.Expression) - - # Verify the internal expression structure - expr = result._value - assert isinstance(expr, ex.OpExpression) - assert expr.op == date_op._DATE_ADD_OP - - # Verify arguments are free variables matching the names - assert len(expr.inputs) == 3 - assert isinstance(expr.inputs[0], ex.UnboundVariableExpression) - assert expr.inputs[0].id == "date_expression" - assert isinstance(expr.inputs[1], ex.UnboundVariableExpression) - assert expr.inputs[1].id == "int64_expression" - assert isinstance(expr.inputs[2], ex.UnboundVariableExpression) - assert expr.inputs[2].id == "date_part" - - -def test_date_diff_expression(): - # Call the function with col() expressions - result = bbq.date_diff( - bpd.col("end_date"), - bpd.col("start_date"), - bpd.col("granularity"), - ) - - # Verify result is a col Expression - assert isinstance(result, bigframes.core.col.Expression) - - # Verify the internal expression structure - expr = result._value - assert isinstance(expr, ex.OpExpression) - assert expr.op == date_op._DATE_DIFF_OP - - # Verify arguments are free variables matching the names - assert len(expr.inputs) == 3 - assert isinstance(expr.inputs[0], ex.UnboundVariableExpression) - assert expr.inputs[0].id == "end_date" - assert isinstance(expr.inputs[1], ex.UnboundVariableExpression) - assert expr.inputs[1].id == "start_date" - assert isinstance(expr.inputs[2], ex.UnboundVariableExpression) - assert expr.inputs[2].id == "granularity" - - -def test_date_from_unix_date_expression(): - # Call the function with col() expressions - result = bbq.date_from_unix_date( - bpd.col("int64_expression"), - ) - - # Verify result is a col Expression - assert isinstance(result, bigframes.core.col.Expression) - - # Verify the internal expression structure - expr = result._value - assert isinstance(expr, ex.OpExpression) - assert expr.op == date_op._DATE_FROM_UNIX_DATE_OP - - # Verify arguments are free variables matching the names - assert len(expr.inputs) == 1 - assert isinstance(expr.inputs[0], ex.UnboundVariableExpression) - assert expr.inputs[0].id == "int64_expression" - - -def test_date_sub_expression(): - # Call the function with col() expressions - result = bbq.date_sub( - bpd.col("date_expression"), - bpd.col("int64_expression"), - bpd.col("date_part"), - ) - - # Verify result is a col Expression - assert isinstance(result, bigframes.core.col.Expression) - - # Verify the internal expression structure - expr = result._value - assert isinstance(expr, ex.OpExpression) - assert expr.op == date_op._DATE_SUB_OP - - # Verify arguments are free variables matching the names - assert len(expr.inputs) == 3 - assert isinstance(expr.inputs[0], ex.UnboundVariableExpression) - assert expr.inputs[0].id == "date_expression" - assert isinstance(expr.inputs[1], ex.UnboundVariableExpression) - assert expr.inputs[1].id == "int64_expression" - assert isinstance(expr.inputs[2], ex.UnboundVariableExpression) - assert expr.inputs[2].id == "date_part" - - -def test_date_trunc_expression(): - # Call the function with col() expressions - result = bbq.date_trunc( - bpd.col("date_value"), - bpd.col("granularity"), - ) - - # Verify result is a col Expression - assert isinstance(result, bigframes.core.col.Expression) - - # Verify the internal expression structure - expr = result._value - assert isinstance(expr, ex.OpExpression) - assert expr.op == date_op._DATE_TRUNC_OP - - # Verify arguments are free variables matching the names - assert len(expr.inputs) == 2 - assert isinstance(expr.inputs[0], ex.UnboundVariableExpression) - assert expr.inputs[0].id == "date_value" - assert isinstance(expr.inputs[1], ex.UnboundVariableExpression) - assert expr.inputs[1].id == "granularity" - - -def test_extract_expression(): - # Call the function with col() expressions - result = bbq.extract( - bpd.col("date_expression"), - bpd.col("part"), - bpd.col("time_zone"), - ) - - # Verify result is a col Expression - assert isinstance(result, bigframes.core.col.Expression) - - # Verify the internal expression structure - expr = result._value - assert isinstance(expr, ex.OpExpression) - assert expr.op == date_op._EXTRACT_OP - - # Verify arguments are free variables matching the names - assert len(expr.inputs) == 3 - assert isinstance(expr.inputs[0], ex.UnboundVariableExpression) - assert expr.inputs[0].id == "date_expression" - assert isinstance(expr.inputs[1], ex.UnboundVariableExpression) - assert expr.inputs[1].id == "part" - assert isinstance(expr.inputs[2], ex.UnboundVariableExpression) - assert expr.inputs[2].id == "time_zone" - - -def test_format_date_expression(): - # Call the function with col() expressions - result = bbq.format_date( - bpd.col("format_string"), - bpd.col("date_expr"), - ) - - # Verify result is a col Expression - assert isinstance(result, bigframes.core.col.Expression) - - # Verify the internal expression structure - expr = result._value - assert isinstance(expr, ex.OpExpression) - assert expr.op == date_op._FORMAT_DATE_OP - - # Verify arguments are free variables matching the names - assert len(expr.inputs) == 2 - assert isinstance(expr.inputs[0], ex.UnboundVariableExpression) - assert expr.inputs[0].id == "format_string" - assert isinstance(expr.inputs[1], ex.UnboundVariableExpression) - assert expr.inputs[1].id == "date_expr" - - -def test_generate_date_array_expression(): - # Call the function with col() expressions - result = bbq.generate_date_array( - bpd.col("start_date"), - bpd.col("end_date"), - bpd.col("int64_expression"), - bpd.col("date_part"), - ) - - # Verify result is a col Expression - assert isinstance(result, bigframes.core.col.Expression) - - # Verify the internal expression structure - expr = result._value - assert isinstance(expr, ex.OpExpression) - assert expr.op == date_op._GENERATE_DATE_ARRAY_OP - - # Verify arguments are free variables matching the names - assert len(expr.inputs) == 4 - assert isinstance(expr.inputs[0], ex.UnboundVariableExpression) - assert expr.inputs[0].id == "start_date" - assert isinstance(expr.inputs[1], ex.UnboundVariableExpression) - assert expr.inputs[1].id == "end_date" - assert isinstance(expr.inputs[2], ex.UnboundVariableExpression) - assert expr.inputs[2].id == "int64_expression" - assert isinstance(expr.inputs[3], ex.UnboundVariableExpression) - assert expr.inputs[3].id == "date_part" - - -def test_last_day_expression(): - # Call the function with col() expressions - result = bbq.last_day( - bpd.col("date_expression"), - bpd.col("date_part"), - ) - - # Verify result is a col Expression - assert isinstance(result, bigframes.core.col.Expression) - - # Verify the internal expression structure - expr = result._value - assert isinstance(expr, ex.OpExpression) - assert expr.op == date_op._LAST_DAY_OP - - # Verify arguments are free variables matching the names - assert len(expr.inputs) == 2 - assert isinstance(expr.inputs[0], ex.UnboundVariableExpression) - assert expr.inputs[0].id == "date_expression" - assert isinstance(expr.inputs[1], ex.UnboundVariableExpression) - assert expr.inputs[1].id == "date_part" - - -def test_parse_date_expression(): - # Call the function with col() expressions - result = bbq.parse_date( - bpd.col("format_string"), - bpd.col("date_string"), - ) - - # Verify result is a col Expression - assert isinstance(result, bigframes.core.col.Expression) - - # Verify the internal expression structure - expr = result._value - assert isinstance(expr, ex.OpExpression) - assert expr.op == date_op._PARSE_DATE_OP - - # Verify arguments are free variables matching the names - assert len(expr.inputs) == 2 - assert isinstance(expr.inputs[0], ex.UnboundVariableExpression) - assert expr.inputs[0].id == "format_string" - assert isinstance(expr.inputs[1], ex.UnboundVariableExpression) - assert expr.inputs[1].id == "date_string" - - -def test_unix_date_expression(): - # Call the function with col() expressions - result = bbq.unix_date( - bpd.col("date_expression"), - ) - - # Verify result is a col Expression - assert isinstance(result, bigframes.core.col.Expression) - - # Verify the internal expression structure - expr = result._value - assert isinstance(expr, ex.OpExpression) - assert expr.op == date_op._UNIX_DATE_OP - - # Verify arguments are free variables matching the names - assert len(expr.inputs) == 1 - assert isinstance(expr.inputs[0], ex.UnboundVariableExpression) - assert expr.inputs[0].id == "date_expression" diff --git a/tests/unit/bigquery/generated/test_aead.py b/tests/unit/bigquery/generated/test_aead.py deleted file mode 100644 index ce728b41899..00000000000 --- a/tests/unit/bigquery/generated/test_aead.py +++ /dev/null @@ -1,101 +0,0 @@ -# Copyright 2026 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. -# -# DO NOT MODIFY THIS FILE DIRECTLY. -# This file was generated from: scripts/data/sql-functions/aead.yaml -# by the script: scripts/generate_bigframes_bigquery.py - -import bigframes.bigquery as bbq -import bigframes.core.col -import bigframes.core.expression as ex -import bigframes.operations.googlesql.aead as aead_op -import bigframes.pandas as bpd - - -def test_decrypt_bytes_expression(): - # Call the function with col() expressions - result = bbq.aead.decrypt_bytes( - bpd.col("keyset"), - bpd.col("ciphertext"), - bpd.col("additional_data"), - ) - - # Verify result is a col Expression - assert isinstance(result, bigframes.core.col.Expression) - - # Verify the internal expression structure - expr = result._value - assert isinstance(expr, ex.OpExpression) - assert expr.op == aead_op._DECRYPT_BYTES_OP - - # Verify arguments are free variables matching the names - assert len(expr.inputs) == 3 - assert isinstance(expr.inputs[0], ex.UnboundVariableExpression) - assert expr.inputs[0].id == "keyset" - assert isinstance(expr.inputs[1], ex.UnboundVariableExpression) - assert expr.inputs[1].id == "ciphertext" - assert isinstance(expr.inputs[2], ex.UnboundVariableExpression) - assert expr.inputs[2].id == "additional_data" - - -def test_decrypt_string_expression(): - # Call the function with col() expressions - result = bbq.aead.decrypt_string( - bpd.col("keyset"), - bpd.col("ciphertext"), - bpd.col("additional_data"), - ) - - # Verify result is a col Expression - assert isinstance(result, bigframes.core.col.Expression) - - # Verify the internal expression structure - expr = result._value - assert isinstance(expr, ex.OpExpression) - assert expr.op == aead_op._DECRYPT_STRING_OP - - # Verify arguments are free variables matching the names - assert len(expr.inputs) == 3 - assert isinstance(expr.inputs[0], ex.UnboundVariableExpression) - assert expr.inputs[0].id == "keyset" - assert isinstance(expr.inputs[1], ex.UnboundVariableExpression) - assert expr.inputs[1].id == "ciphertext" - assert isinstance(expr.inputs[2], ex.UnboundVariableExpression) - assert expr.inputs[2].id == "additional_data" - - -def test_encrypt_expression(): - # Call the function with col() expressions - result = bbq.aead.encrypt( - bpd.col("keyset"), - bpd.col("plaintext"), - bpd.col("additional_data"), - ) - - # Verify result is a col Expression - assert isinstance(result, bigframes.core.col.Expression) - - # Verify the internal expression structure - expr = result._value - assert isinstance(expr, ex.OpExpression) - assert expr.op == aead_op._ENCRYPT_OP - - # Verify arguments are free variables matching the names - assert len(expr.inputs) == 3 - assert isinstance(expr.inputs[0], ex.UnboundVariableExpression) - assert expr.inputs[0].id == "keyset" - assert isinstance(expr.inputs[1], ex.UnboundVariableExpression) - assert expr.inputs[1].id == "plaintext" - assert isinstance(expr.inputs[2], ex.UnboundVariableExpression) - assert expr.inputs[2].id == "additional_data" diff --git a/tests/unit/bigquery/test_ai.py b/tests/unit/bigquery/test_ai.py deleted file mode 100644 index 2cb876d39a5..00000000000 --- a/tests/unit/bigquery/test_ai.py +++ /dev/null @@ -1,319 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from unittest import mock - -import pandas as pd -import pytest - -import bigframes.bigquery as bbq -import bigframes.dataframe -import bigframes.series -import bigframes.session - - -@pytest.fixture -def mock_session(): - return mock.create_autospec(spec=bigframes.session.Session) - - -@pytest.fixture -def mock_dataframe(mock_session): - df = mock.create_autospec(spec=bigframes.dataframe.DataFrame) - df._session = mock_session - df.sql = "SELECT * FROM my_table" - df._to_sql_query.return_value = ("SELECT * FROM my_table", None, None) - return df - - -@pytest.fixture -def mock_embedding_series(mock_session): - series = mock.create_autospec(spec=bigframes.series.Series) - series._session = mock_session - # Mock to_frame to return a mock dataframe - df = mock.create_autospec(spec=bigframes.dataframe.DataFrame) - df._session = mock_session - df.sql = "SELECT my_col AS content FROM my_table" - df._to_sql_query.return_value = ( - "SELECT my_col AS content FROM my_table", - None, - None, - ) - series.copy.return_value = series - series.to_frame.return_value = df - return series - - -@pytest.fixture -def mock_text_series(mock_session): - series = mock.create_autospec(spec=bigframes.series.Series) - series._session = mock_session - # Mock to_frame to return a mock dataframe - df = mock.create_autospec(spec=bigframes.dataframe.DataFrame) - df._session = mock_session - df.sql = "SELECT my_col AS prompt FROM my_table" - df._to_sql_query.return_value = ( - "SELECT my_col AS prompt FROM my_table", - None, - None, - ) - series.copy.return_value = series - series.to_frame.return_value = df - return series - - -def test_generate_embedding_with_dataframe(mock_dataframe, mock_session): - model_name = "project.dataset.model" - - bbq.ai.generate_embedding( - model_name, - mock_dataframe, - output_dimensionality=256, - ) - - mock_session.read_gbq_query.assert_called_once() - query = mock_session.read_gbq_query.call_args[0][0] - - # Normalize whitespace for comparison - query = " ".join(query.split()) - - expected_part_1 = "SELECT * FROM AI.GENERATE_EMBEDDING(" - expected_part_2 = f"MODEL `{model_name}`," - expected_part_3 = "(SELECT * FROM my_table)," - expected_part_4 = "STRUCT(256 AS `OUTPUT_DIMENSIONALITY`)" - - assert expected_part_1 in query - assert expected_part_2 in query - assert expected_part_3 in query - assert expected_part_4 in query - - -def test_generate_embedding_with_series(mock_embedding_series, mock_session): - model_name = "project.dataset.model" - - bbq.ai.generate_embedding( - model_name, - mock_embedding_series, - start_second=0.0, - end_second=10.0, - interval_seconds=5.0, - ) - - mock_session.read_gbq_query.assert_called_once() - query = mock_session.read_gbq_query.call_args[0][0] - query = " ".join(query.split()) - - assert f"MODEL `{model_name}`" in query - assert "(SELECT my_col AS content FROM my_table)" in query - assert ( - "STRUCT(0.0 AS `START_SECOND`, 10.0 AS `END_SECOND`, 5.0 AS `INTERVAL_SECONDS`)" - in query - ) - - -def test_generate_embedding_defaults(mock_dataframe, mock_session): - model_name = "project.dataset.model" - - bbq.ai.generate_embedding( - model_name, - mock_dataframe, - ) - - mock_session.read_gbq_query.assert_called_once() - query = mock_session.read_gbq_query.call_args[0][0] - query = " ".join(query.split()) - - assert f"MODEL `{model_name}`" in query - assert "STRUCT()" in query - - -@mock.patch("bigframes.pandas.read_pandas") -def test_generate_embedding_with_pandas_dataframe( - read_pandas_mock, mock_dataframe, mock_session -): - # This tests that pandas input path works and calls read_pandas - model_name = "project.dataset.model" - - # Mock return value of read_pandas to be a BigFrames DataFrame - read_pandas_mock.return_value = mock_dataframe - - pandas_df = pd.DataFrame({"content": ["test"]}) - - bbq.ai.generate_embedding( - model_name, - pandas_df, - ) - - read_pandas_mock.assert_called_once() - # Check that read_pandas was called with something (the pandas df) - assert read_pandas_mock.call_args[0][0] is pandas_df - - mock_session.read_gbq_query.assert_called_once() - - -def test_generate_text_with_dataframe(mock_dataframe, mock_session): - model_name = "project.dataset.model" - - bbq.ai.generate_text( - model_name, - mock_dataframe, - max_output_tokens=256, - ) - - mock_session.read_gbq_query.assert_called_once() - query = mock_session.read_gbq_query.call_args[0][0] - - # Normalize whitespace for comparison - query = " ".join(query.split()) - - expected_part_1 = "SELECT * FROM AI.GENERATE_TEXT(" - expected_part_2 = f"MODEL `{model_name}`," - expected_part_3 = "(SELECT * FROM my_table)," - expected_part_4 = "STRUCT(256 AS `MAX_OUTPUT_TOKENS`)" - - assert expected_part_1 in query - assert expected_part_2 in query - assert expected_part_3 in query - assert expected_part_4 in query - - -def test_generate_text_with_series(mock_text_series, mock_session): - model_name = "project.dataset.model" - - bbq.ai.generate_text( - model_name, - mock_text_series, - ) - - mock_session.read_gbq_query.assert_called_once() - query = mock_session.read_gbq_query.call_args[0][0] - query = " ".join(query.split()) - - assert f"MODEL `{model_name}`" in query - assert "(SELECT my_col AS prompt FROM my_table)" in query - - -def test_generate_text_defaults(mock_dataframe, mock_session): - model_name = "project.dataset.model" - - bbq.ai.generate_text( - model_name, - mock_dataframe, - ) - - mock_session.read_gbq_query.assert_called_once() - query = mock_session.read_gbq_query.call_args[0][0] - query = " ".join(query.split()) - - assert f"MODEL `{model_name}`" in query - assert "STRUCT()" in query - - -def test_generate_table_with_dataframe(mock_dataframe, mock_session): - model_name = "project.dataset.model" - - bbq.ai.generate_table( - model_name, - mock_dataframe, - output_schema="col1 STRING, col2 INT64", - ) - - mock_session.read_gbq_query.assert_called_once() - query = mock_session.read_gbq_query.call_args[0][0] - - # Normalize whitespace for comparison - query = " ".join(query.split()) - - expected_part_1 = "SELECT * FROM AI.GENERATE_TABLE(" - expected_part_2 = f"MODEL `{model_name}`," - expected_part_3 = "(SELECT * FROM my_table)," - expected_part_4 = "STRUCT('col1 STRING, col2 INT64' AS `output_schema`)" - - assert expected_part_1 in query - assert expected_part_2 in query - assert expected_part_3 in query - assert expected_part_4 in query - - -def test_generate_table_with_options(mock_dataframe, mock_session): - model_name = "project.dataset.model" - - bbq.ai.generate_table( - model_name, - mock_dataframe, - output_schema="col1 STRING", - temperature=0.5, - max_output_tokens=100, - ) - - mock_session.read_gbq_query.assert_called_once() - query = mock_session.read_gbq_query.call_args[0][0] - query = " ".join(query.split()) - - assert f"MODEL `{model_name}`" in query - assert "(SELECT * FROM my_table)" in query - assert ( - "STRUCT('col1 STRING' AS `output_schema`, 0.5 AS `temperature`, 100 AS `max_output_tokens`)" - in query - ) - - -def test_generate_table_with_mapping_schema(mock_dataframe, mock_session): - model_name = "project.dataset.model" - - bbq.ai.generate_table( - model_name, - mock_dataframe, - output_schema={"col1": "STRING", "col2": "INT64"}, - ) - - mock_session.read_gbq_query.assert_called_once() - query = mock_session.read_gbq_query.call_args[0][0] - - # Normalize whitespace for comparison - query = " ".join(query.split()) - - expected_part_1 = "SELECT * FROM AI.GENERATE_TABLE(" - expected_part_2 = f"MODEL `{model_name}`," - expected_part_3 = "(SELECT * FROM my_table)," - expected_part_4 = "STRUCT('col1 STRING, col2 INT64' AS `output_schema`)" - - assert expected_part_1 in query - assert expected_part_2 in query - assert expected_part_3 in query - assert expected_part_4 in query - - -@mock.patch("bigframes.pandas.read_pandas") -def test_generate_text_with_pandas_dataframe( - read_pandas_mock, mock_dataframe, mock_session -): - # This tests that pandas input path works and calls read_pandas - model_name = "project.dataset.model" - - # Mock return value of read_pandas to be a BigFrames DataFrame - read_pandas_mock.return_value = mock_dataframe - - pandas_df = pd.DataFrame({"content": ["test"]}) - - bbq.ai.generate_text( - model_name, - pandas_df, - ) - - read_pandas_mock.assert_called_once() - # Check that read_pandas was called with something (the pandas df) - assert read_pandas_mock.call_args[0][0] is pandas_df - - mock_session.read_gbq_query.assert_called_once() diff --git a/tests/unit/bigquery/test_json.py b/tests/unit/bigquery/test_json.py deleted file mode 100644 index d9beea26db4..00000000000 --- a/tests/unit/bigquery/test_json.py +++ /dev/null @@ -1,26 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import unittest.mock as mock - -import pytest - -import bigframes.bigquery as bbq -import bigframes.pandas as bpd - - -def test_json_set_w_invalid_json_path_value_pairs(): - mock_series = mock.create_autospec(bpd.pandas.Series, instance=True) - with pytest.raises(ValueError, match="Incorrect format"): - bbq.json_set(mock_series, json_path_value_pairs=[("$.a", 1, 100)]) # type: ignore diff --git a/tests/unit/bigquery/test_mathematical.py b/tests/unit/bigquery/test_mathematical.py deleted file mode 100644 index f0cb16ae145..00000000000 --- a/tests/unit/bigquery/test_mathematical.py +++ /dev/null @@ -1,33 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import 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.GoogleSqlScalarOp) - assert op.sql_name == "RAND" - assert op.output_type() == dtypes.FLOAT_DTYPE - assert not op.is_deterministic - assert len(node.inputs) == 0 diff --git a/tests/unit/bigquery/test_ml.py b/tests/unit/bigquery/test_ml.py deleted file mode 100644 index a68133225d4..00000000000 --- a/tests/unit/bigquery/test_ml.py +++ /dev/null @@ -1,215 +0,0 @@ -# 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. -# 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 unittest import mock - -import pandas as pd -import pytest - -import bigframes.bigquery._operations.ml as ml_ops -import bigframes.session - - -@pytest.fixture -def mock_session(): - return mock.create_autospec(spec=bigframes.session.Session) - - -MODEL_SERIES = pd.Series( - { - "modelReference": { - "projectId": "test-project", - "datasetId": "test-dataset", - "modelId": "test-model", - } - } -) - -MODEL_NAME = "test-project.test-dataset.test-model" - - -@mock.patch("bigframes.bigquery._operations.ml._get_model_metadata") -@mock.patch("bigframes.pandas.read_pandas") -def test_create_model_with_pandas_dataframe( - read_pandas_mock, _get_model_metadata_mock, mock_session -): - df = pd.DataFrame({"col1": [1, 2, 3]}) - read_pandas_mock.return_value._to_sql_query.return_value = ( - "SELECT * FROM `pandas_df`", - [], - [], - ) - ml_ops.create_model("model_name", training_data=df, session=mock_session) - read_pandas_mock.assert_called_once() - mock_session.read_gbq_query.assert_called_once() - generated_sql = mock_session.read_gbq_query.call_args[0][0] - assert "CREATE MODEL `model_name`" in generated_sql - assert "AS SELECT * FROM `pandas_df`" in generated_sql - - -@mock.patch("bigframes.pandas.read_gbq_query") -@mock.patch("bigframes.pandas.read_pandas") -def test_evaluate_with_pandas_dataframe(read_pandas_mock, read_gbq_query_mock): - df = pd.DataFrame({"col1": [1, 2, 3]}) - read_pandas_mock.return_value._to_sql_query.return_value = ( - "SELECT * FROM `pandas_df`", - [], - [], - ) - ml_ops.evaluate(MODEL_SERIES, input_=df) - read_pandas_mock.assert_called_once() - read_gbq_query_mock.assert_called_once() - generated_sql = read_gbq_query_mock.call_args[0][0] - assert "ML.EVALUATE" in generated_sql - assert f"MODEL `{MODEL_NAME}`" in generated_sql - assert "(SELECT * FROM `pandas_df`)" in generated_sql - - -@mock.patch("bigframes.pandas.read_gbq_query") -@mock.patch("bigframes.pandas.read_pandas") -def test_predict_with_pandas_dataframe(read_pandas_mock, read_gbq_query_mock): - df = pd.DataFrame({"col1": [1, 2, 3]}) - read_pandas_mock.return_value._to_sql_query.return_value = ( - "SELECT * FROM `pandas_df`", - [], - [], - ) - ml_ops.predict(MODEL_SERIES, input_=df) - read_pandas_mock.assert_called_once() - read_gbq_query_mock.assert_called_once() - generated_sql = read_gbq_query_mock.call_args[0][0] - assert "ML.PREDICT" in generated_sql - assert f"MODEL `{MODEL_NAME}`" in generated_sql - assert "(SELECT * FROM `pandas_df`)" in generated_sql - - -@mock.patch("bigframes.pandas.read_gbq_query") -@mock.patch("bigframes.pandas.read_pandas") -def test_explain_predict_with_pandas_dataframe(read_pandas_mock, read_gbq_query_mock): - df = pd.DataFrame({"col1": [1, 2, 3]}) - read_pandas_mock.return_value._to_sql_query.return_value = ( - "SELECT * FROM `pandas_df`", - [], - [], - ) - ml_ops.explain_predict(MODEL_SERIES, input_=df) - read_pandas_mock.assert_called_once() - read_gbq_query_mock.assert_called_once() - generated_sql = read_gbq_query_mock.call_args[0][0] - assert "ML.EXPLAIN_PREDICT" in generated_sql - assert f"MODEL `{MODEL_NAME}`" in generated_sql - assert "(SELECT * FROM `pandas_df`)" in generated_sql - - -@mock.patch("bigframes.pandas.read_gbq_query") -def test_global_explain_with_pandas_series_model(read_gbq_query_mock): - ml_ops.global_explain(MODEL_SERIES) - read_gbq_query_mock.assert_called_once() - generated_sql = read_gbq_query_mock.call_args[0][0] - assert "ML.GLOBAL_EXPLAIN" in generated_sql - assert f"MODEL `{MODEL_NAME}`" in generated_sql - - -@mock.patch("bigframes.pandas.read_gbq_query") -@mock.patch("bigframes.pandas.read_pandas") -def test_transform_with_pandas_dataframe(read_pandas_mock, read_gbq_query_mock): - df = pd.DataFrame({"col1": [1, 2, 3]}) - read_pandas_mock.return_value._to_sql_query.return_value = ( - "SELECT * FROM `pandas_df`", - [], - [], - ) - ml_ops.transform(MODEL_SERIES, input_=df) - read_pandas_mock.assert_called_once() - read_gbq_query_mock.assert_called_once() - generated_sql = read_gbq_query_mock.call_args[0][0] - assert "ML.TRANSFORM" in generated_sql - assert f"MODEL `{MODEL_NAME}`" in generated_sql - assert "(SELECT * FROM `pandas_df`)" in generated_sql - - -@mock.patch("bigframes.pandas.read_gbq_query") -@mock.patch("bigframes.pandas.read_pandas") -def test_generate_text_with_pandas_dataframe(read_pandas_mock, read_gbq_query_mock): - df = pd.DataFrame({"col1": [1, 2, 3]}) - read_pandas_mock.return_value._to_sql_query.return_value = ( - "SELECT * FROM `pandas_df`", - [], - [], - ) - ml_ops.generate_text( - MODEL_SERIES, - input_=df, - temperature=0.5, - max_output_tokens=128, - top_k=20, - top_p=0.9, - flatten_json_output=True, - stop_sequences=["a", "b"], - ground_with_google_search=True, - request_type="TYPE", - ) - read_pandas_mock.assert_called_once() - read_gbq_query_mock.assert_called_once() - generated_sql = read_gbq_query_mock.call_args[0][0] - assert "ML.GENERATE_TEXT" in generated_sql - assert f"MODEL `{MODEL_NAME}`" in generated_sql - assert "(SELECT * FROM `pandas_df`)" in generated_sql - assert "STRUCT(\n 0.5 AS `temperature`" in generated_sql - assert "128 AS `max_output_tokens`" in generated_sql - assert "20 AS `top_k`" in generated_sql - assert "0.9 AS `top_p`" in generated_sql - assert "TRUE AS `flatten_json_output`" in generated_sql - assert "['a', 'b'] AS `stop_sequences`" in generated_sql - assert "TRUE AS `ground_with_google_search`" in generated_sql - assert "'TYPE' AS `request_type`" in generated_sql - - -@mock.patch("bigframes.pandas.read_gbq_query") -def test_get_insights(read_gbq_query_mock): - ml_ops.get_insights(MODEL_SERIES) - read_gbq_query_mock.assert_called_once() - generated_sql = read_gbq_query_mock.call_args[0][0] - assert "ML.GET_INSIGHTS" in generated_sql - assert f"MODEL `{MODEL_NAME}`" in generated_sql - - -@mock.patch("bigframes.pandas.read_gbq_query") -@mock.patch("bigframes.pandas.read_pandas") -def test_generate_embedding_with_pandas_dataframe( - read_pandas_mock, read_gbq_query_mock -): - df = pd.DataFrame({"col1": [1, 2, 3]}) - read_pandas_mock.return_value._to_sql_query.return_value = ( - "SELECT * FROM `pandas_df`", - [], - [], - ) - ml_ops.generate_embedding( - MODEL_SERIES, - input_=df, - flatten_json_output=True, - task_type="RETRIEVAL_DOCUMENT", - output_dimensionality=256, - ) - read_pandas_mock.assert_called_once() - read_gbq_query_mock.assert_called_once() - generated_sql = read_gbq_query_mock.call_args[0][0] - assert "ML.GENERATE_EMBEDDING" in generated_sql - assert f"MODEL `{MODEL_NAME}`" in generated_sql - assert "(SELECT * FROM `pandas_df`)" in generated_sql - assert "STRUCT(\n TRUE AS `flatten_json_output`" in generated_sql - assert "'RETRIEVAL_DOCUMENT' AS `task_type`" in generated_sql - assert "256 AS `output_dimensionality`" in generated_sql diff --git a/tests/unit/bigquery/test_obj.py b/tests/unit/bigquery/test_obj.py deleted file mode 100644 index 9eac234b8bc..00000000000 --- a/tests/unit/bigquery/test_obj.py +++ /dev/null @@ -1,125 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import datetime -from unittest import mock - -import bigframes.bigquery.obj as obj -import bigframes.operations as ops -import bigframes.series - - -def create_mock_series(): - result = mock.create_autospec(bigframes.series.Series, instance=True) - result.copy.return_value = result - return result - - -def test_fetch_metadata_op_structure(): - op = ops.obj_fetch_metadata_op - assert op.name == "obj_fetch_metadata" - - -def test_get_access_url_op_structure(): - op = ops.ObjGetAccessUrl(mode="r") - assert op.name == "obj_get_access_url" - assert op.mode == "r" - assert op.duration is None - - -def test_get_access_url_with_duration_op_structure(): - op = ops.ObjGetAccessUrl(mode="rw", duration=3600000000) - assert op.name == "obj_get_access_url" - assert op.mode == "rw" - assert op.duration == 3600000000 - - -def test_make_ref_op_structure(): - op = ops.obj_make_ref_op - assert op.name == "obj_make_ref" - - -def test_make_ref_json_op_structure(): - op = ops.obj_make_ref_json_op - assert op.name == "obj_make_ref_json" - - -def test_fetch_metadata_calls_apply_unary_op(): - series = create_mock_series() - - obj.fetch_metadata(series) - - series._apply_unary_op.assert_called_once() - args, _ = series._apply_unary_op.call_args - assert args[0] == ops.obj_fetch_metadata_op - - -def test_get_access_url_calls_apply_unary_op_without_duration(): - series = create_mock_series() - - obj.get_access_url(series, mode="r") - - series._apply_unary_op.assert_called_once() - args, _ = series._apply_unary_op.call_args - assert isinstance(args[0], ops.ObjGetAccessUrl) - assert args[0].mode == "r" - assert args[0].duration is None - - -def test_get_access_url_calls_apply_unary_op_with_duration(): - series = create_mock_series() - duration = datetime.timedelta(hours=1) - - obj.get_access_url(series, mode="rw", duration=duration) - - series._apply_unary_op.assert_called_once() - args, _ = series._apply_unary_op.call_args - assert isinstance(args[0], ops.ObjGetAccessUrl) - assert args[0].mode == "rw" - # 1 hour = 3600 seconds = 3600 * 1000 * 1000 microseconds - assert args[0].duration == 3600000000 - - -def test_make_ref_calls_apply_binary_op_with_authorizer(): - uri = create_mock_series() - auth = create_mock_series() - - obj.make_ref(uri, authorizer=auth) - - uri._apply_binary_op.assert_called_once() - args, _ = uri._apply_binary_op.call_args - assert args[0] == auth - assert args[1] == ops.obj_make_ref_op - - -def test_make_ref_calls_apply_binary_op_with_authorizer_string(): - uri = create_mock_series() - auth = "us.bigframes-test-connection" - - obj.make_ref(uri, authorizer=auth) - - uri._apply_binary_op.assert_called_once() - args, _ = uri._apply_binary_op.call_args - assert args[0] == auth - assert args[1] == ops.obj_make_ref_op - - -def test_make_ref_calls_apply_unary_op_without_authorizer(): - json_val = create_mock_series() - - obj.make_ref(json_val) - - json_val._apply_unary_op.assert_called_once() - args, _ = json_val._apply_unary_op.call_args - assert args[0] == ops.obj_make_ref_json_op diff --git a/tests/unit/conftest.py b/tests/unit/conftest.py deleted file mode 100644 index d880fe54242..00000000000 --- a/tests/unit/conftest.py +++ /dev/null @@ -1,316 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import datetime -import json -import pathlib -import typing - -import numpy as np -import pandas as pd -import pyarrow as pa -import pytest -from google.cloud import bigquery - -import bigframes.core as core -import bigframes.pandas as bpd -import bigframes.testing.mocks as mocks -import bigframes.testing.utils -from bigframes import dtypes - -CURRENT_DIR = pathlib.Path(__file__).parent -DATA_DIR = CURRENT_DIR.parent / "data" - - -@pytest.fixture(scope="session") -def polars_session(): - pytest.importorskip("polars") - - from bigframes.testing import polars_session - - return polars_session.TestSession() - - -def _create_compiler_session(table_name, table_schema): - """Helper function to create a compiler session.""" - from bigframes.testing import compiler_session - - anonymous_dataset = bigquery.DatasetReference.from_string( - "bigframes-dev.sqlglot_test" - ) - session = mocks.create_bigquery_session( - table_name=table_name, - table_schema=table_schema, - anonymous_dataset=anonymous_dataset, - ) - session._executor = compiler_session.SQLCompilerExecutor() - return session - - -@pytest.fixture(scope="session") -def compiler_session(scalar_types_table_schema): - """Compiler session for scalar types.""" - return _create_compiler_session("scalar_types", scalar_types_table_schema) - - -@pytest.fixture(scope="session") -def compiler_session_w_repeated_types(repeated_types_table_schema): - """Compiler session for repeated data types.""" - return _create_compiler_session("repeated_types", repeated_types_table_schema) - - -@pytest.fixture(scope="session") -def compiler_session_w_nested_structs_types(nested_structs_types_table_schema): - """Compiler session for nested STRUCT data types.""" - return _create_compiler_session( - "nested_structs_types", nested_structs_types_table_schema - ) - - -@pytest.fixture(scope="session") -def compiler_session_w_json_types(json_types_table_schema): - """Compiler session for JSON data types.""" - return _create_compiler_session("json_types", json_types_table_schema) - - -@pytest.fixture(scope="session") -def scalar_types_table_schema() -> typing.Sequence[bigquery.SchemaField]: - return [ - 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("int64_too", "INTEGER"), - bigquery.SchemaField("numeric_col", "NUMERIC"), - bigquery.SchemaField("float64_col", "FLOAT"), - bigquery.SchemaField("rowindex", "INTEGER"), - bigquery.SchemaField("rowindex_2", "INTEGER", mode="REQUIRED"), - bigquery.SchemaField("string_col", "STRING"), - bigquery.SchemaField("time_col", "TIME"), - bigquery.SchemaField("timestamp_col", "TIMESTAMP"), - bigquery.SchemaField("duration_col", "INTEGER"), - ] - - -@pytest.fixture(scope="session") -def scalar_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._loader.read_gbq_table( - "bigframes-dev.sqlglot_test.scalar_types", - enable_snapshot=False, - ) - bf_df = bf_df.set_index("rowindex", drop=False) - return bf_df - - -@pytest.fixture(scope="session") -def scalar_types_pandas_df() -> pd.DataFrame: - """Returns a pandas DataFrame containing all scalar types and using the `rowindex` - column as the index.""" - # TODO: add tests for empty dataframes - df = pd.read_json( - DATA_DIR / "scalars.jsonl", - lines=True, - ) - bigframes.testing.utils.convert_pandas_dtypes(df, bytes_col=True) - - df = df.set_index("rowindex", drop=False) - return df - - -@pytest.fixture(scope="module") -def scalar_types_array_value( - scalar_types_pandas_df: pd.DataFrame, compiler_session: bigframes.Session -) -> core.ArrayValue: - managed_data_source = core.local_data.ManagedArrowTable.from_pandas( - scalar_types_pandas_df - ) - return core.ArrayValue.from_managed(managed_data_source, compiler_session) - - -@pytest.fixture(scope="session") -def nested_structs_types_table_schema() -> typing.Sequence[bigquery.SchemaField]: - return [ - bigquery.SchemaField("id", "INTEGER"), - bigquery.SchemaField( - "people", - "RECORD", - fields=[ - bigquery.SchemaField("name", "STRING"), - bigquery.SchemaField("age", "INTEGER"), - bigquery.SchemaField( - "address", - "RECORD", - fields=[ - bigquery.SchemaField("city", "STRING"), - bigquery.SchemaField("country", "STRING"), - ], - ), - ], - ), - ] - - -@pytest.fixture(scope="session") -def nested_structs_types_df(compiler_session_w_nested_structs_types) -> bpd.DataFrame: - """Returns a BigFrames DataFrame containing all scalar types and using the `rowindex` - column as the index.""" - bf_df = compiler_session_w_nested_structs_types._loader.read_gbq_table( - "bigframes-dev.sqlglot_test.nested_structs_types", - enable_snapshot=False, - ) - bf_df = bf_df.set_index("id", drop=False) - return bf_df - - -@pytest.fixture(scope="session") -def nested_structs_pandas_df() -> pd.DataFrame: - """Returns a pandas DataFrame containing STRUCT types and using the `id` - column as the index.""" - - df = pd.read_json( - DATA_DIR / "nested_structs.jsonl", - lines=True, - ) - df = df.set_index("id") - - address_struct_schema = pa.struct( - [pa.field("city", pa.string()), pa.field("country", pa.string())] - ) - person_struct_schema = pa.struct( - [ - pa.field("name", pa.string()), - pa.field("age", pa.int64()), - pa.field("address", address_struct_schema), - ] - ) - df["person"] = df["person"].astype(pd.ArrowDtype(person_struct_schema)) - - def to_json_str(val): - if val is None or (isinstance(val, float) and np.isnan(val)): - return None - return json.dumps(val) - - df["json_col"] = df["json_col"].apply(to_json_str).astype(dtypes.JSON_DTYPE) - - # timestamp_col - def parse_timestamp(val): - if pd.isna(val): - return None - if isinstance(val, str): - return datetime.datetime.fromisoformat(val.replace("Z", "+00:00")) - if hasattr(val, "to_pydatetime"): - return val.to_pydatetime() - return val - - timestamp_vals = [parse_timestamp(x) for x in df["timestamp_col"]] - timestamp_arr = pa.array(timestamp_vals, type=dtypes.TIMESTAMP_DTYPE.pyarrow_dtype) - df["timestamp_col"] = pd.Series( - timestamp_arr, index=df.index, dtype=dtypes.TIMESTAMP_DTYPE - ) - - return df - - -@pytest.fixture(scope="session") -def repeated_types_table_schema() -> typing.Sequence[bigquery.SchemaField]: - return [ - bigquery.SchemaField("rowindex", "INTEGER"), - bigquery.SchemaField("int_list_col", "INTEGER", "REPEATED"), - bigquery.SchemaField("bool_list_col", "BOOLEAN", "REPEATED"), - bigquery.SchemaField("float_list_col", "FLOAT", "REPEATED"), - bigquery.SchemaField("date_list_col", "DATE", "REPEATED"), - bigquery.SchemaField("date_time_list_col", "DATETIME", "REPEATED"), - bigquery.SchemaField("numeric_list_col", "NUMERIC", "REPEATED"), - bigquery.SchemaField("string_list_col", "STRING", "REPEATED"), - ] - - -@pytest.fixture(scope="session") -def repeated_types_df(compiler_session_w_repeated_types) -> bpd.DataFrame: - """Returns a BigFrames DataFrame containing all scalar types and using the `rowindex` - column as the index.""" - bf_df = compiler_session_w_repeated_types._loader.read_gbq_table( - "bigframes-dev.sqlglot_test.repeated_types", - enable_snapshot=False, - ) - bf_df = bf_df.set_index("rowindex", drop=False) - return bf_df - - -@pytest.fixture(scope="session") -def repeated_types_pandas_df() -> pd.DataFrame: - """Returns a pandas DataFrame containing LIST types and using the `rowindex` - column as the index.""" - - df = pd.read_json( - DATA_DIR / "repeated.jsonl", - lines=True, - ) - # TODO: add dtype conversion here if needed. - df = df.set_index("rowindex") - return df - - -@pytest.fixture(scope="session") -def json_types_table_schema() -> typing.Sequence[bigquery.SchemaField]: - return [ - bigquery.SchemaField("rowindex", "INTEGER"), - bigquery.SchemaField("json_col", "JSON"), - ] - - -@pytest.fixture(scope="session") -def json_types_df(compiler_session_w_json_types) -> bpd.DataFrame: - """Returns a BigFrames DataFrame containing JSON types and using the `rowindex` - column as the index.""" - bf_df = compiler_session_w_json_types._loader.read_gbq_table( - "bigframes-dev.sqlglot_test.json_types", - enable_snapshot=False, - ) - # TODO(b/427305807): Why `drop=False` will produce two "rowindex" columns? - bf_df = bf_df.set_index("rowindex", drop=True) - return bf_df - - -@pytest.fixture(scope="session") -def json_pandas_df() -> pd.DataFrame: - """Returns a pandas DataFrame containing JSON types and using the `rowindex` - column as the index.""" - json_data = [ - "null", - "true", - "100", - "0.98", - '"a string"', - "[]", - "[1, 2, 3]", - '[{"a": 1}, {"a": 2}, {"a": null}, {}]', - '"100"', - '{"date": "2024-07-16"}', - '{"int_value": 2, "null_filed": null}', - '{"list_data": [10, 20, 30]}', - ] - df = pd.DataFrame( - { - "rowindex": pd.Series(range(len(json_data)), dtype=dtypes.INT_DTYPE), - "json_col": pd.Series(json_data, dtype=dtypes.JSON_DTYPE), - }, - ) - # TODO(b/427305807): Why `drop=False` will produce two "rowindex" columns? - df = df.set_index("rowindex", drop=True) - return df diff --git a/tests/unit/core/compile/__init__.py b/tests/unit/core/compile/__init__.py deleted file mode 100644 index 6d5e14bcf4a..00000000000 --- a/tests/unit/core/compile/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -# 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. -# 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/unit/core/compile/sqlglot/__init__.py b/tests/unit/core/compile/sqlglot/__init__.py deleted file mode 100644 index 0a2669d7a25..00000000000 --- a/tests/unit/core/compile/sqlglot/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. diff --git a/tests/unit/core/compile/sqlglot/aggregations/__init__.py b/tests/unit/core/compile/sqlglot/aggregations/__init__.py deleted file mode 100644 index 0a2669d7a25..00000000000 --- a/tests/unit/core/compile/sqlglot/aggregations/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. diff --git a/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_binary_compiler/test_corr/out.sql b/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_binary_compiler/test_corr/out.sql deleted file mode 100644 index fb930323dbd..00000000000 --- a/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_binary_compiler/test_corr/out.sql +++ /dev/null @@ -1,13 +0,0 @@ -WITH `bfcte_0` AS ( - SELECT - `int64_col`, - `float64_col` - FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` -), `bfcte_1` AS ( - SELECT - CORR(`int64_col`, `float64_col`) AS `bfcol_2` - FROM `bfcte_0` -) -SELECT - `bfcol_2` AS `corr_col` -FROM `bfcte_1` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_binary_compiler/test_cov/out.sql b/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_binary_compiler/test_cov/out.sql deleted file mode 100644 index 92b8ea4d3ab..00000000000 --- a/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_binary_compiler/test_cov/out.sql +++ /dev/null @@ -1,13 +0,0 @@ -WITH `bfcte_0` AS ( - SELECT - `int64_col`, - `float64_col` - FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` -), `bfcte_1` AS ( - SELECT - COVAR_SAMP(`int64_col`, `float64_col`) AS `bfcol_2` - FROM `bfcte_0` -) -SELECT - `bfcol_2` AS `cov_col` -FROM `bfcte_1` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_nullary_compiler/test_row_number/out.sql b/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_nullary_compiler/test_row_number/out.sql deleted file mode 100644 index 7056c8b0af3..00000000000 --- a/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_nullary_compiler/test_row_number/out.sql +++ /dev/null @@ -1,3 +0,0 @@ -SELECT - ROW_NUMBER() OVER () - 1 AS `row_number` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_nullary_compiler/test_row_number_with_window/out.sql b/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_nullary_compiler/test_row_number_with_window/out.sql deleted file mode 100644 index 8efea4b51bc..00000000000 --- a/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_nullary_compiler/test_row_number_with_window/out.sql +++ /dev/null @@ -1,3 +0,0 @@ -SELECT - ROW_NUMBER() OVER (ORDER BY `int64_col` ASC NULLS LAST) - 1 AS `row_number` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_nullary_compiler/test_size/out.sql b/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_nullary_compiler/test_size/out.sql deleted file mode 100644 index 4d67203ecc6..00000000000 --- a/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_nullary_compiler/test_size/out.sql +++ /dev/null @@ -1,12 +0,0 @@ -WITH `bfcte_0` AS ( - SELECT - * - FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` -), `bfcte_1` AS ( - SELECT - COUNT(1) AS `bfcol_32` - FROM `bfcte_0` -) -SELECT - `bfcol_32` AS `size` -FROM `bfcte_1` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_ordered_unary_compiler/test_array_agg/out.sql b/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_ordered_unary_compiler/test_array_agg/out.sql deleted file mode 100644 index f929970a227..00000000000 --- a/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_ordered_unary_compiler/test_array_agg/out.sql +++ /dev/null @@ -1,12 +0,0 @@ -WITH `bfcte_0` AS ( - SELECT - `int64_col` - FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` -), `bfcte_1` AS ( - SELECT - ARRAY_AGG(`int64_col` IGNORE NULLS ORDER BY `int64_col` IS NULL ASC, `int64_col` ASC) AS `bfcol_1` - FROM `bfcte_0` -) -SELECT - `bfcol_1` AS `int64_col` -FROM `bfcte_1` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_ordered_unary_compiler/test_string_agg/out.sql b/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_ordered_unary_compiler/test_string_agg/out.sql deleted file mode 100644 index 7e697719b36..00000000000 --- a/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_ordered_unary_compiler/test_string_agg/out.sql +++ /dev/null @@ -1,18 +0,0 @@ -WITH `bfcte_0` AS ( - SELECT - `string_col` - FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` -), `bfcte_1` AS ( - SELECT - COALESCE( - STRING_AGG(`string_col`, ',' - ORDER BY - `string_col` IS NULL ASC, - `string_col` ASC), - '' - ) AS `bfcol_1` - FROM `bfcte_0` -) -SELECT - `bfcol_1` AS `string_col` -FROM `bfcte_1` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_all/out.sql b/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_all/out.sql deleted file mode 100644 index dc1f6fb4f79..00000000000 --- a/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_all/out.sql +++ /dev/null @@ -1,15 +0,0 @@ -WITH `bfcte_0` AS ( - SELECT - `bool_col`, - `int64_col` - FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` -), `bfcte_1` AS ( - SELECT - COALESCE(LOGICAL_AND(`bool_col`), TRUE) AS `bfcol_2`, - COALESCE(LOGICAL_AND(`int64_col` <> 0), TRUE) AS `bfcol_3` - FROM `bfcte_0` -) -SELECT - `bfcol_2` AS `bool_col`, - `bfcol_3` AS `int64_col` -FROM `bfcte_1` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_all_w_window/out.sql b/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_all_w_window/out.sql deleted file mode 100644 index 7e4c9d6c3c9..00000000000 --- a/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_all_w_window/out.sql +++ /dev/null @@ -1,3 +0,0 @@ -SELECT - COALESCE(LOGICAL_AND(`bool_col`) OVER (), TRUE) AS `agg_bool` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_any/out.sql b/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_any/out.sql deleted file mode 100644 index 8ae589fb09f..00000000000 --- a/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_any/out.sql +++ /dev/null @@ -1,15 +0,0 @@ -WITH `bfcte_0` AS ( - SELECT - `bool_col`, - `int64_col` - FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` -), `bfcte_1` AS ( - SELECT - COALESCE(LOGICAL_OR(`bool_col`), FALSE) AS `bfcol_2`, - COALESCE(LOGICAL_OR(`int64_col` <> 0), FALSE) AS `bfcol_3` - FROM `bfcte_0` -) -SELECT - `bfcol_2` AS `bool_col`, - `bfcol_3` AS `int64_col` -FROM `bfcte_1` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_any_value/out.sql b/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_any_value/out.sql deleted file mode 100644 index e8556018852..00000000000 --- a/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_any_value/out.sql +++ /dev/null @@ -1,12 +0,0 @@ -WITH `bfcte_0` AS ( - SELECT - `int64_col` - FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` -), `bfcte_1` AS ( - SELECT - ANY_VALUE(`int64_col`) AS `bfcol_1` - FROM `bfcte_0` -) -SELECT - `bfcol_1` AS `int64_col` -FROM `bfcte_1` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_any_value/window_out.sql b/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_any_value/window_out.sql deleted file mode 100644 index 020d7603b98..00000000000 --- a/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_any_value/window_out.sql +++ /dev/null @@ -1,3 +0,0 @@ -SELECT - ANY_VALUE(`int64_col`) OVER () AS `agg_int64` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_any_value/window_partition_out.sql b/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_any_value/window_partition_out.sql deleted file mode 100644 index 577c5929b91..00000000000 --- a/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_any_value/window_partition_out.sql +++ /dev/null @@ -1,3 +0,0 @@ -SELECT - ANY_VALUE(`int64_col`) OVER (PARTITION BY `string_col`) AS `agg_int64` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_any_w_window/out.sql b/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_any_w_window/out.sql deleted file mode 100644 index 33045c4b70d..00000000000 --- a/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_any_w_window/out.sql +++ /dev/null @@ -1,3 +0,0 @@ -SELECT - COALESCE(LOGICAL_OR(`bool_col`) OVER (), FALSE) AS `agg_bool` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_approx_quartiles/out.sql b/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_approx_quartiles/out.sql deleted file mode 100644 index e2a119499f2..00000000000 --- a/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_approx_quartiles/out.sql +++ /dev/null @@ -1,16 +0,0 @@ -WITH `bfcte_0` AS ( - SELECT - `int64_col` - FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` -), `bfcte_1` AS ( - SELECT - APPROX_QUANTILES(`int64_col`, 4)[OFFSET(1)] AS `bfcol_1`, - APPROX_QUANTILES(`int64_col`, 4)[OFFSET(2)] AS `bfcol_2`, - APPROX_QUANTILES(`int64_col`, 4)[OFFSET(3)] AS `bfcol_3` - FROM `bfcte_0` -) -SELECT - `bfcol_1` AS `q1`, - `bfcol_2` AS `q2`, - `bfcol_3` AS `q3` -FROM `bfcte_1` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_approx_top_count/out.sql b/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_approx_top_count/out.sql deleted file mode 100644 index 1c391c6691f..00000000000 --- a/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_approx_top_count/out.sql +++ /dev/null @@ -1,12 +0,0 @@ -WITH `bfcte_0` AS ( - SELECT - `int64_col` - FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` -), `bfcte_1` AS ( - SELECT - APPROX_TOP_COUNT(`int64_col`, 10) AS `bfcol_1` - FROM `bfcte_0` -) -SELECT - `bfcol_1` AS `int64_col` -FROM `bfcte_1` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_count/out.sql b/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_count/out.sql deleted file mode 100644 index 61f073b7dc8..00000000000 --- a/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_count/out.sql +++ /dev/null @@ -1,12 +0,0 @@ -WITH `bfcte_0` AS ( - SELECT - `int64_col` - FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` -), `bfcte_1` AS ( - SELECT - COUNT(`int64_col`) AS `bfcol_1` - FROM `bfcte_0` -) -SELECT - `bfcol_1` AS `int64_col` -FROM `bfcte_1` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_count/window_out.sql b/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_count/window_out.sql deleted file mode 100644 index e46b49e7e48..00000000000 --- a/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_count/window_out.sql +++ /dev/null @@ -1,3 +0,0 @@ -SELECT - COUNT(`int64_col`) OVER () AS `agg_int64` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_count/window_partition_out.sql b/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_count/window_partition_out.sql deleted file mode 100644 index 98088d97dfc..00000000000 --- a/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_count/window_partition_out.sql +++ /dev/null @@ -1,3 +0,0 @@ -SELECT - COUNT(`int64_col`) OVER (PARTITION BY `string_col`) AS `agg_int64` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_cut/int_bins.sql b/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_cut/int_bins.sql deleted file mode 100644 index ac5525fe63f..00000000000 --- a/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_cut/int_bins.sql +++ /dev/null @@ -1,47 +0,0 @@ -SELECT - CASE - WHEN `int64_col` <= MIN(`int64_col`) OVER () + ( - 1 * IEEE_DIVIDE(MAX(`int64_col`) OVER () - MIN(`int64_col`) OVER (), 3) - ) - THEN STRUCT( - ( - MIN(`int64_col`) OVER () + ( - 0 * IEEE_DIVIDE(MAX(`int64_col`) OVER () - MIN(`int64_col`) OVER (), 3) - ) - ) - ( - ( - MAX(`int64_col`) OVER () - MIN(`int64_col`) OVER () - ) * 0.001 - ) AS `left_exclusive`, - MIN(`int64_col`) OVER () + ( - 1 * IEEE_DIVIDE(MAX(`int64_col`) OVER () - MIN(`int64_col`) OVER (), 3) - ) + 0 AS `right_inclusive` - ) - WHEN `int64_col` <= MIN(`int64_col`) OVER () + ( - 2 * IEEE_DIVIDE(MAX(`int64_col`) OVER () - MIN(`int64_col`) OVER (), 3) - ) - THEN STRUCT( - ( - MIN(`int64_col`) OVER () + ( - 1 * IEEE_DIVIDE(MAX(`int64_col`) OVER () - MIN(`int64_col`) OVER (), 3) - ) - ) - 0 AS `left_exclusive`, - MIN(`int64_col`) OVER () + ( - 2 * IEEE_DIVIDE(MAX(`int64_col`) OVER () - MIN(`int64_col`) OVER (), 3) - ) + 0 AS `right_inclusive` - ) - WHEN ( - `int64_col` - ) IS NOT NULL - THEN STRUCT( - ( - MIN(`int64_col`) OVER () + ( - 2 * IEEE_DIVIDE(MAX(`int64_col`) OVER () - MIN(`int64_col`) OVER (), 3) - ) - ) - 0 AS `left_exclusive`, - MIN(`int64_col`) OVER () + ( - 3 * IEEE_DIVIDE(MAX(`int64_col`) OVER () - MIN(`int64_col`) OVER (), 3) - ) + 0 AS `right_inclusive` - ) - END AS `int_bins` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_cut/int_bins_labels.sql b/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_cut/int_bins_labels.sql deleted file mode 100644 index 94e9f57b28e..00000000000 --- a/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_cut/int_bins_labels.sql +++ /dev/null @@ -1,16 +0,0 @@ -SELECT - CASE - WHEN `int64_col` < MIN(`int64_col`) OVER () + ( - 1 * IEEE_DIVIDE(MAX(`int64_col`) OVER () - MIN(`int64_col`) OVER (), 3) - ) - THEN 'a' - WHEN `int64_col` < MIN(`int64_col`) OVER () + ( - 2 * IEEE_DIVIDE(MAX(`int64_col`) OVER () - MIN(`int64_col`) OVER (), 3) - ) - THEN 'b' - WHEN ( - `int64_col` - ) IS NOT NULL - THEN 'c' - END AS `int_bins_labels` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_cut/interval_bins.sql b/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_cut/interval_bins.sql deleted file mode 100644 index 10f9778f55e..00000000000 --- a/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_cut/interval_bins.sql +++ /dev/null @@ -1,8 +0,0 @@ -SELECT - CASE - WHEN `int64_col` > 0 AND `int64_col` <= 1 - THEN STRUCT(0 AS `left_exclusive`, 1 AS `right_inclusive`) - WHEN `int64_col` > 1 AND `int64_col` <= 2 - THEN STRUCT(1 AS `left_exclusive`, 2 AS `right_inclusive`) - END AS `interval_bins` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_cut/interval_bins_labels.sql b/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_cut/interval_bins_labels.sql deleted file mode 100644 index 247c71a6349..00000000000 --- a/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_cut/interval_bins_labels.sql +++ /dev/null @@ -1,8 +0,0 @@ -SELECT - CASE - WHEN `int64_col` > 0 AND `int64_col` <= 1 - THEN 0 - WHEN `int64_col` > 1 AND `int64_col` <= 2 - THEN 1 - END AS `interval_bins_labels` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_dense_rank/out.sql b/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_dense_rank/out.sql deleted file mode 100644 index 95f53752c34..00000000000 --- a/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_dense_rank/out.sql +++ /dev/null @@ -1,3 +0,0 @@ -SELECT - DENSE_RANK() OVER (ORDER BY `int64_col` DESC) AS `agg_int64` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_diff_w_bool/out.sql b/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_diff_w_bool/out.sql deleted file mode 100644 index 592f3e240a4..00000000000 --- a/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_diff_w_bool/out.sql +++ /dev/null @@ -1,3 +0,0 @@ -SELECT - `bool_col` <> LAG(`bool_col`, 1) OVER (ORDER BY `bool_col` DESC) AS `diff_bool` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_diff_w_date/out.sql b/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_diff_w_date/out.sql deleted file mode 100644 index 4b41355d948..00000000000 --- a/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_diff_w_date/out.sql +++ /dev/null @@ -1,5 +0,0 @@ -SELECT - CAST(FLOOR( - DATE_DIFF(`date_col`, LAG(`date_col`, 1) OVER (ORDER BY `date_col` ASC NULLS LAST), DAY) * 86400000000 - ) AS INT64) AS `diff_date` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_diff_w_datetime/out.sql b/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_diff_w_datetime/out.sql deleted file mode 100644 index 866f49b1ed4..00000000000 --- a/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_diff_w_datetime/out.sql +++ /dev/null @@ -1,7 +0,0 @@ -SELECT - DATETIME_DIFF( - `datetime_col`, - LAG(`datetime_col`, 1) OVER (ORDER BY `datetime_col` ASC NULLS LAST), - MICROSECOND - ) AS `diff_datetime` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_diff_w_int/out.sql b/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_diff_w_int/out.sql deleted file mode 100644 index 4c8a0880f3b..00000000000 --- a/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_diff_w_int/out.sql +++ /dev/null @@ -1,3 +0,0 @@ -SELECT - `int64_col` - LAG(`int64_col`, 1) OVER (ORDER BY `int64_col` ASC NULLS LAST) AS `diff_int` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_diff_w_timestamp/out.sql b/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_diff_w_timestamp/out.sql deleted file mode 100644 index 364f6b69d84..00000000000 --- a/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_diff_w_timestamp/out.sql +++ /dev/null @@ -1,7 +0,0 @@ -SELECT - TIMESTAMP_DIFF( - `timestamp_col`, - LAG(`timestamp_col`, 1) OVER (ORDER BY `timestamp_col` DESC), - MICROSECOND - ) AS `diff_timestamp` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_first/out.sql b/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_first/out.sql deleted file mode 100644 index 86aedff91d1..00000000000 --- a/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_first/out.sql +++ /dev/null @@ -1,6 +0,0 @@ -SELECT - FIRST_VALUE(`int64_col`) OVER ( - ORDER BY `int64_col` DESC - ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING - ) AS `agg_int64` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_first_non_null/out.sql b/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_first_non_null/out.sql deleted file mode 100644 index b7851a350ed..00000000000 --- a/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_first_non_null/out.sql +++ /dev/null @@ -1,6 +0,0 @@ -SELECT - FIRST_VALUE(`int64_col` IGNORE NULLS) OVER ( - ORDER BY `int64_col` ASC NULLS LAST - ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING - ) AS `agg_int64` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_last/out.sql b/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_last/out.sql deleted file mode 100644 index d0bb802c333..00000000000 --- a/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_last/out.sql +++ /dev/null @@ -1,6 +0,0 @@ -SELECT - LAST_VALUE(`int64_col`) OVER ( - ORDER BY `int64_col` DESC - ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING - ) AS `agg_int64` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_last_non_null/out.sql b/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_last_non_null/out.sql deleted file mode 100644 index 39d063a3c99..00000000000 --- a/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_last_non_null/out.sql +++ /dev/null @@ -1,6 +0,0 @@ -SELECT - LAST_VALUE(`int64_col` IGNORE NULLS) OVER ( - ORDER BY `int64_col` ASC NULLS LAST - ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING - ) AS `agg_int64` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_max/out.sql b/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_max/out.sql deleted file mode 100644 index 7e01c2c7187..00000000000 --- a/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_max/out.sql +++ /dev/null @@ -1,12 +0,0 @@ -WITH `bfcte_0` AS ( - SELECT - `int64_col` - FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` -), `bfcte_1` AS ( - SELECT - MAX(`int64_col`) AS `bfcol_1` - FROM `bfcte_0` -) -SELECT - `bfcol_1` AS `int64_col` -FROM `bfcte_1` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_max/window_out.sql b/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_max/window_out.sql deleted file mode 100644 index d6dec51cdb4..00000000000 --- a/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_max/window_out.sql +++ /dev/null @@ -1,3 +0,0 @@ -SELECT - MAX(`int64_col`) OVER () AS `agg_int64` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_max/window_partition_out.sql b/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_max/window_partition_out.sql deleted file mode 100644 index a35a64a8e5f..00000000000 --- a/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_max/window_partition_out.sql +++ /dev/null @@ -1,3 +0,0 @@ -SELECT - MAX(`int64_col`) OVER (PARTITION BY `string_col`) AS `agg_int64` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_mean/out.sql b/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_mean/out.sql deleted file mode 100644 index 94287fc432b..00000000000 --- a/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_mean/out.sql +++ /dev/null @@ -1,23 +0,0 @@ -WITH `bfcte_0` AS ( - SELECT - `bool_col`, - `int64_col`, - `duration_col`, - `int64_col` AS `bfcol_6`, - `bool_col` AS `bfcol_7`, - `duration_col` AS `bfcol_8` - FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` -), `bfcte_1` AS ( - SELECT - AVG(`bfcol_6`) AS `bfcol_12`, - AVG(CAST(`bfcol_7` AS INT64)) AS `bfcol_13`, - CAST(FLOOR(AVG(`bfcol_8`)) AS INT64) AS `bfcol_14`, - CAST(FLOOR(AVG(`bfcol_6`)) AS INT64) AS `bfcol_15` - FROM `bfcte_0` -) -SELECT - `bfcol_12` AS `int64_col`, - `bfcol_13` AS `bool_col`, - `bfcol_14` AS `duration_col`, - `bfcol_15` AS `int64_col_w_floor` -FROM `bfcte_1` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_mean/window_out.sql b/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_mean/window_out.sql deleted file mode 100644 index 3443cd2a680..00000000000 --- a/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_mean/window_out.sql +++ /dev/null @@ -1,3 +0,0 @@ -SELECT - AVG(`int64_col`) OVER () AS `agg_int64` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_mean/window_partition_out.sql b/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_mean/window_partition_out.sql deleted file mode 100644 index b94b84ddb81..00000000000 --- a/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_mean/window_partition_out.sql +++ /dev/null @@ -1,3 +0,0 @@ -SELECT - AVG(`int64_col`) OVER (PARTITION BY `string_col`) AS `agg_int64` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_median/out.sql b/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_median/out.sql deleted file mode 100644 index 7d1215163f8..00000000000 --- a/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_median/out.sql +++ /dev/null @@ -1,18 +0,0 @@ -WITH `bfcte_0` AS ( - SELECT - `date_col`, - `int64_col`, - `string_col` - FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` -), `bfcte_1` AS ( - SELECT - APPROX_QUANTILES(`int64_col`, 2)[OFFSET(1)] AS `bfcol_3`, - APPROX_QUANTILES(`date_col`, 2)[OFFSET(1)] AS `bfcol_4`, - APPROX_QUANTILES(`string_col`, 2)[OFFSET(1)] AS `bfcol_5` - FROM `bfcte_0` -) -SELECT - `bfcol_3` AS `int64_col`, - `bfcol_4` AS `date_col`, - `bfcol_5` AS `string_col` -FROM `bfcte_1` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_min/out.sql b/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_min/out.sql deleted file mode 100644 index 144c07d7010..00000000000 --- a/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_min/out.sql +++ /dev/null @@ -1,12 +0,0 @@ -WITH `bfcte_0` AS ( - SELECT - `int64_col` - FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` -), `bfcte_1` AS ( - SELECT - MIN(`int64_col`) AS `bfcol_1` - FROM `bfcte_0` -) -SELECT - `bfcol_1` AS `int64_col` -FROM `bfcte_1` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_min/window_out.sql b/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_min/window_out.sql deleted file mode 100644 index 031c19eff16..00000000000 --- a/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_min/window_out.sql +++ /dev/null @@ -1,3 +0,0 @@ -SELECT - MIN(`int64_col`) OVER () AS `agg_int64` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_min/window_partition_out.sql b/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_min/window_partition_out.sql deleted file mode 100644 index 2de5bd5f717..00000000000 --- a/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_min/window_partition_out.sql +++ /dev/null @@ -1,3 +0,0 @@ -SELECT - MIN(`int64_col`) OVER (PARTITION BY `string_col`) AS `agg_int64` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_nunique/out.sql b/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_nunique/out.sql deleted file mode 100644 index e0cc1a2eac5..00000000000 --- a/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_nunique/out.sql +++ /dev/null @@ -1,12 +0,0 @@ -WITH `bfcte_0` AS ( - SELECT - `int64_col` - FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` -), `bfcte_1` AS ( - SELECT - COUNT(DISTINCT `int64_col`) AS `bfcol_1` - FROM `bfcte_0` -) -SELECT - `bfcol_1` AS `int64_col` -FROM `bfcte_1` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_pop_var/out.sql b/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_pop_var/out.sql deleted file mode 100644 index b855c791182..00000000000 --- a/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_pop_var/out.sql +++ /dev/null @@ -1,15 +0,0 @@ -WITH `bfcte_0` AS ( - SELECT - `bool_col`, - `int64_col` - FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` -), `bfcte_1` AS ( - SELECT - VAR_POP(`int64_col`) AS `bfcol_4`, - VAR_POP(CAST(`bool_col` AS INT64)) AS `bfcol_5` - FROM `bfcte_0` -) -SELECT - `bfcol_4` AS `int64_col`, - `bfcol_5` AS `bool_col` -FROM `bfcte_1` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_pop_var/window_out.sql b/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_pop_var/window_out.sql deleted file mode 100644 index 3bfaedd3953..00000000000 --- a/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_pop_var/window_out.sql +++ /dev/null @@ -1,3 +0,0 @@ -SELECT - VAR_POP(`int64_col`) OVER () AS `agg_int64` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_product/out.sql b/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_product/out.sql deleted file mode 100644 index 33204f2ff56..00000000000 --- a/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_product/out.sql +++ /dev/null @@ -1,16 +0,0 @@ -WITH `bfcte_0` AS ( - SELECT - `int64_col` - FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` -), `bfcte_1` AS ( - SELECT - CASE - WHEN LOGICAL_OR(`int64_col` = 0) - THEN 0 - ELSE POWER(2, SUM(IF(`int64_col` = 0, 0, LOG(ABS(`int64_col`), 2)))) * POWER(-1, MOD(SUM(CASE WHEN SIGN(`int64_col`) = -1 THEN 1 ELSE 0 END), 2)) - END AS `bfcol_1` - FROM `bfcte_0` -) -SELECT - `bfcol_1` AS `int64_col` -FROM `bfcte_1` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_product/window_partition_out.sql b/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_product/window_partition_out.sql deleted file mode 100644 index 532349d3599..00000000000 --- a/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_product/window_partition_out.sql +++ /dev/null @@ -1,16 +0,0 @@ -SELECT - CASE - WHEN LOGICAL_OR(`int64_col` = 0) OVER (PARTITION BY `string_col`) - THEN 0 - ELSE POWER( - 2, - SUM(IF(`int64_col` = 0, 0, LOG(ABS(`int64_col`), 2))) OVER (PARTITION BY `string_col`) - ) * POWER( - -1, - MOD( - SUM(CASE WHEN SIGN(`int64_col`) = -1 THEN 1 ELSE 0 END) OVER (PARTITION BY `string_col`), - 2 - ) - ) - END AS `agg_int64` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_qcut/out.sql b/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_qcut/out.sql deleted file mode 100644 index cb1541d083b..00000000000 --- a/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_qcut/out.sql +++ /dev/null @@ -1,51 +0,0 @@ -SELECT - `rowindex`, - `int64_col`, - IF( - ( - `int64_col` - ) IS NOT NULL, - IF( - `int64_col` IS NULL, - NULL, - CAST(GREATEST( - CEIL( - PERCENT_RANK() OVER (PARTITION BY ( - `int64_col` - ) IS NOT NULL ORDER BY `int64_col` ASC) * 4 - ) - 1, - 0 - ) AS INT64) - ), - NULL - ) AS `qcut_w_int`, - IF( - ( - `int64_col` - ) IS NOT NULL, - CASE - WHEN PERCENT_RANK() OVER (PARTITION BY ( - `int64_col` - ) IS NOT NULL ORDER BY `int64_col` ASC) < 0 - THEN NULL - WHEN PERCENT_RANK() OVER (PARTITION BY ( - `int64_col` - ) IS NOT NULL ORDER BY `int64_col` ASC) <= 0.25 - THEN 0 - WHEN PERCENT_RANK() OVER (PARTITION BY ( - `int64_col` - ) IS NOT NULL ORDER BY `int64_col` ASC) <= 0.5 - THEN 1 - WHEN PERCENT_RANK() OVER (PARTITION BY ( - `int64_col` - ) IS NOT NULL ORDER BY `int64_col` ASC) <= 0.75 - THEN 2 - WHEN PERCENT_RANK() OVER (PARTITION BY ( - `int64_col` - ) IS NOT NULL ORDER BY `int64_col` ASC) <= 1 - THEN 3 - ELSE NULL - END, - NULL - ) AS `qcut_w_list` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_quantile/out.sql b/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_quantile/out.sql deleted file mode 100644 index 656d01ea2e5..00000000000 --- a/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_quantile/out.sql +++ /dev/null @@ -1,17 +0,0 @@ -WITH `bfcte_0` AS ( - SELECT - `bool_col`, - `int64_col` - FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` -), `bfcte_1` AS ( - SELECT - PERCENTILE_CONT(`int64_col`, 0.5) OVER () AS `bfcol_4`, - PERCENTILE_CONT(CAST(`bool_col` AS INT64), 0.5) OVER () AS `bfcol_5`, - CAST(FLOOR(PERCENTILE_CONT(`int64_col`, 0.5) OVER ()) AS INT64) AS `bfcol_6` - FROM `bfcte_0` -) -SELECT - `bfcol_4` AS `int64`, - `bfcol_5` AS `bool`, - `bfcol_6` AS `int64_w_floor` -FROM `bfcte_1` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_rank/out.sql b/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_rank/out.sql deleted file mode 100644 index 2170d6cdcf7..00000000000 --- a/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_rank/out.sql +++ /dev/null @@ -1,3 +0,0 @@ -SELECT - RANK() OVER (ORDER BY `int64_col` DESC NULLS FIRST) AS `agg_int64` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_shift/lag.sql b/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_shift/lag.sql deleted file mode 100644 index 2bea343497f..00000000000 --- a/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_shift/lag.sql +++ /dev/null @@ -1,3 +0,0 @@ -SELECT - LAG(`int64_col`, 1) OVER (ORDER BY `int64_col` ASC) AS `lag` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_shift/lead.sql b/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_shift/lead.sql deleted file mode 100644 index 5055f443718..00000000000 --- a/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_shift/lead.sql +++ /dev/null @@ -1,3 +0,0 @@ -SELECT - LEAD(`int64_col`, 1) OVER (ORDER BY `int64_col` ASC) AS `lead` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_shift/noop.sql b/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_shift/noop.sql deleted file mode 100644 index 65af6af7c79..00000000000 --- a/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_shift/noop.sql +++ /dev/null @@ -1,3 +0,0 @@ -SELECT - `int64_col` AS `noop` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_size_unary/out.sql b/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_size_unary/out.sql deleted file mode 100644 index fffb4831b95..00000000000 --- a/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_size_unary/out.sql +++ /dev/null @@ -1,12 +0,0 @@ -WITH `bfcte_0` AS ( - SELECT - `float64_col` AS `bfcol_0` - FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` -), `bfcte_1` AS ( - SELECT - COUNT(1) AS `bfcol_1` - FROM `bfcte_0` -) -SELECT - `bfcol_1` AS `float64_col` -FROM `bfcte_1` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_std/out.sql b/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_std/out.sql deleted file mode 100644 index e3c3d7b5253..00000000000 --- a/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_std/out.sql +++ /dev/null @@ -1,23 +0,0 @@ -WITH `bfcte_0` AS ( - SELECT - `bool_col`, - `int64_col`, - `duration_col`, - `int64_col` AS `bfcol_6`, - `bool_col` AS `bfcol_7`, - `duration_col` AS `bfcol_8` - FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` -), `bfcte_1` AS ( - SELECT - STDDEV(`bfcol_6`) AS `bfcol_12`, - STDDEV(CAST(`bfcol_7` AS INT64)) AS `bfcol_13`, - CAST(FLOOR(STDDEV(`bfcol_8`)) AS INT64) AS `bfcol_14`, - CAST(FLOOR(STDDEV(`bfcol_6`)) AS INT64) AS `bfcol_15` - FROM `bfcte_0` -) -SELECT - `bfcol_12` AS `int64_col`, - `bfcol_13` AS `bool_col`, - `bfcol_14` AS `duration_col`, - `bfcol_15` AS `int64_col_w_floor` -FROM `bfcte_1` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_std/window_out.sql b/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_std/window_out.sql deleted file mode 100644 index 225dd5acf66..00000000000 --- a/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_std/window_out.sql +++ /dev/null @@ -1,3 +0,0 @@ -SELECT - STDDEV(`int64_col`) OVER () AS `agg_int64` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_sum/out.sql b/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_sum/out.sql deleted file mode 100644 index c67eef9da34..00000000000 --- a/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_sum/out.sql +++ /dev/null @@ -1,15 +0,0 @@ -WITH `bfcte_0` AS ( - SELECT - `bool_col`, - `int64_col` - FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` -), `bfcte_1` AS ( - SELECT - COALESCE(SUM(`int64_col`), 0) AS `bfcol_4`, - COALESCE(SUM(CAST(`bool_col` AS INT64)), 0) AS `bfcol_5` - FROM `bfcte_0` -) -SELECT - `bfcol_4` AS `int64_col`, - `bfcol_5` AS `bool_col` -FROM `bfcte_1` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_sum/window_out.sql b/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_sum/window_out.sql deleted file mode 100644 index ea5a12edfb5..00000000000 --- a/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_sum/window_out.sql +++ /dev/null @@ -1,3 +0,0 @@ -SELECT - COALESCE(SUM(`int64_col`) OVER (), 0) AS `agg_int64` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_sum/window_partition_out.sql b/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_sum/window_partition_out.sql deleted file mode 100644 index ec6083b1a9d..00000000000 --- a/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_sum/window_partition_out.sql +++ /dev/null @@ -1,3 +0,0 @@ -SELECT - COALESCE(SUM(`int64_col`) OVER (PARTITION BY `string_col`), 0) AS `agg_int64` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_var/out.sql b/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_var/out.sql deleted file mode 100644 index b35d67c1ce1..00000000000 --- a/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_var/out.sql +++ /dev/null @@ -1,15 +0,0 @@ -WITH `bfcte_0` AS ( - SELECT - `bool_col`, - `int64_col` - FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` -), `bfcte_1` AS ( - SELECT - VARIANCE(`int64_col`) AS `bfcol_4`, - VARIANCE(CAST(`bool_col` AS INT64)) AS `bfcol_5` - FROM `bfcte_0` -) -SELECT - `bfcol_4` AS `int64_col`, - `bfcol_5` AS `bool_col` -FROM `bfcte_1` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_var/window_out.sql b/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_var/window_out.sql deleted file mode 100644 index e33797d02fb..00000000000 --- a/tests/unit/core/compile/sqlglot/aggregations/snapshots/test_unary_compiler/test_var/window_out.sql +++ /dev/null @@ -1,3 +0,0 @@ -SELECT - VARIANCE(`int64_col`) OVER () AS `agg_int64` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/aggregations/test_binary_compiler.py b/tests/unit/core/compile/sqlglot/aggregations/test_binary_compiler.py deleted file mode 100644 index 11f5cd6bad8..00000000000 --- a/tests/unit/core/compile/sqlglot/aggregations/test_binary_compiler.py +++ /dev/null @@ -1,54 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import typing - -import pytest - -import bigframes.pandas as bpd -from bigframes.core import agg_expressions as agg_exprs -from bigframes.core import array_value, identifiers, nodes -from bigframes.operations import aggregations as agg_ops - -pytest.importorskip("pytest_snapshot") - - -def _apply_binary_agg_ops( - obj: bpd.DataFrame, - ops_list: typing.Sequence[agg_exprs.BinaryAggregation], - new_names: typing.Sequence[str], -) -> str: - aggs = [(op, identifiers.ColumnId(name)) for op, name in zip(ops_list, new_names)] - - agg_node = nodes.AggregateNode(obj._block.expr.node, aggregations=tuple(aggs)) - result = array_value.ArrayValue(agg_node) - - sql = result.session._executor.to_sql(result, enable_cache=False) - return sql - - -def test_corr(scalar_types_df: bpd.DataFrame, snapshot): - bf_df = scalar_types_df[["int64_col", "float64_col"]] - agg_expr = agg_ops.CorrOp().as_expr("int64_col", "float64_col") - sql = _apply_binary_agg_ops(bf_df, [agg_expr], ["corr_col"]) - - snapshot.assert_match(sql, "out.sql") - - -def test_cov(scalar_types_df: bpd.DataFrame, snapshot): - bf_df = scalar_types_df[["int64_col", "float64_col"]] - agg_expr = agg_ops.CovOp().as_expr("int64_col", "float64_col") - sql = _apply_binary_agg_ops(bf_df, [agg_expr], ["cov_col"]) - - snapshot.assert_match(sql, "out.sql") diff --git a/tests/unit/core/compile/sqlglot/aggregations/test_nullary_compiler.py b/tests/unit/core/compile/sqlglot/aggregations/test_nullary_compiler.py deleted file mode 100644 index 0ce8437b904..00000000000 --- a/tests/unit/core/compile/sqlglot/aggregations/test_nullary_compiler.py +++ /dev/null @@ -1,84 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import typing - -import pytest - -import bigframes.pandas as bpd -from bigframes.core import agg_expressions as agg_exprs -from bigframes.core import array_value, identifiers, nodes, ordering, window_spec -from bigframes.operations import aggregations as agg_ops - -pytest.importorskip("pytest_snapshot") - - -def _apply_nullary_agg_ops( - obj: bpd.DataFrame, - ops_list: typing.Sequence[agg_exprs.NullaryAggregation], - new_names: typing.Sequence[str], -) -> str: - aggs = [(op, identifiers.ColumnId(name)) for op, name in zip(ops_list, new_names)] - - agg_node = nodes.AggregateNode(obj._block.expr.node, aggregations=tuple(aggs)) - result = array_value.ArrayValue(agg_node) - - sql = result.session._executor.to_sql(result, enable_cache=False) - return sql - - -def _apply_nullary_window_op( - obj: bpd.DataFrame, - op: agg_exprs.NullaryAggregation, - window_spec: window_spec.WindowSpec, - new_name: str, -) -> str: - win_node = nodes.WindowOpNode( - obj._block.expr.node, - agg_exprs=(nodes.ColumnDef(op, identifiers.ColumnId(new_name)),), - window_spec=window_spec, - ) - result = array_value.ArrayValue(win_node).select_columns([new_name]) - - sql = result.session._executor.to_sql(result, enable_cache=False) - return sql - - -def test_size(scalar_types_df: bpd.DataFrame, snapshot): - bf_df = scalar_types_df - agg_expr = agg_ops.SizeOp().as_expr() - sql = _apply_nullary_agg_ops(bf_df, [agg_expr], ["size"]) - - snapshot.assert_match(sql, "out.sql") - - -def test_row_number(scalar_types_df: bpd.DataFrame, snapshot): - bf_df = scalar_types_df - agg_expr = agg_exprs.NullaryAggregation(agg_ops.RowNumberOp()) - window = window_spec.WindowSpec() - sql = _apply_nullary_window_op(bf_df, agg_expr, window, "row_number") - - snapshot.assert_match(sql, "out.sql") - - -def test_row_number_with_window(scalar_types_df: bpd.DataFrame, snapshot): - col_name = "int64_col" - bf_df = scalar_types_df[[col_name, "int64_too"]] - agg_expr = agg_exprs.NullaryAggregation(agg_ops.RowNumberOp()) - - window = window_spec.WindowSpec(ordering=(ordering.ascending_over(col_name),)) - # window = window_spec.unbound(ordering=(ordering.ascending_over(col_name),ordering.ascending_over("int64_too"))) - sql = _apply_nullary_window_op(bf_df, agg_expr, window, "row_number") - - snapshot.assert_match(sql, "out.sql") diff --git a/tests/unit/core/compile/sqlglot/aggregations/test_op_registration.py b/tests/unit/core/compile/sqlglot/aggregations/test_op_registration.py deleted file mode 100644 index 9306bbf6559..00000000000 --- a/tests/unit/core/compile/sqlglot/aggregations/test_op_registration.py +++ /dev/null @@ -1,64 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import pytest -from bigframes_vendored.sqlglot import expressions as sge - -from bigframes.core.compile.sqlglot.aggregations import op_registration -from bigframes.operations import aggregations as agg_ops - - -def test_register_then_get(): - reg = op_registration.OpRegistration() - input = sge.to_identifier("A") - op = agg_ops.SizeOp() - - @reg.register(agg_ops.SizeOp) - def test_func(op: agg_ops.SizeOp, input: sge.Expression) -> sge.Expression: - return input - - assert reg[agg_ops.SizeOp()](op, input) == test_func(op, input) - - -def test_register_function_first_argument_is_not_agg_op_raise_error(): - reg = op_registration.OpRegistration() - - @reg.register(agg_ops.SizeOp) - def test_func(input: sge.Expression) -> sge.Expression: - return input - - with pytest.raises( - ValueError, match=r".*first parameter must be a window operator.*" - ): - test_func(sge.to_identifier("A")) - - -def test_register_already_registered_raise_error(): - reg = op_registration.OpRegistration() - - @reg.register(agg_ops.SizeOp) - def test_func1(op, input): - return input - - with pytest.raises(ValueError, match=r".*is already registered.*"): - - @reg.register(agg_ops.SizeOp) - def test_func2(op, input): - return input - - -def test_getitem_not_registered_raise_error(): - reg = op_registration.OpRegistration() - with pytest.raises(ValueError, match=r".*is not registered.*"): - _ = reg[agg_ops.SizeOp()] diff --git a/tests/unit/core/compile/sqlglot/aggregations/test_ordered_unary_compiler.py b/tests/unit/core/compile/sqlglot/aggregations/test_ordered_unary_compiler.py deleted file mode 100644 index dd8912a452b..00000000000 --- a/tests/unit/core/compile/sqlglot/aggregations/test_ordered_unary_compiler.py +++ /dev/null @@ -1,67 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import typing - -import pytest - -import bigframes.pandas as bpd -from bigframes.core import agg_expressions as agg_exprs -from bigframes.core import array_value, identifiers, nodes, ordering -from bigframes.operations import aggregations as agg_ops - -pytest.importorskip("pytest_snapshot") - - -def _apply_ordered_unary_agg_ops( - obj: bpd.DataFrame, - ops_list: typing.Sequence[agg_exprs.UnaryAggregation], - new_names: typing.Sequence[str], - ordering_args: typing.Sequence[str], -) -> str: - ordering_exprs = tuple(ordering.ascending_over(arg) for arg in ordering_args) - aggs = [(op, identifiers.ColumnId(name)) for op, name in zip(ops_list, new_names)] - - agg_node = nodes.AggregateNode( - obj._block.expr.node, - aggregations=tuple(aggs), - by_column_ids=(), - order_by=ordering_exprs, - ) - result = array_value.ArrayValue(agg_node) - - sql = result.session._executor.to_sql(result, enable_cache=False) - return sql - - -def test_array_agg(scalar_types_df: bpd.DataFrame, snapshot): - col_name = "int64_col" - bf_df = scalar_types_df[[col_name]] - agg_expr = agg_ops.ArrayAggOp().as_expr(col_name) - sql = _apply_ordered_unary_agg_ops( - bf_df, [agg_expr], [col_name], ordering_args=[col_name] - ) - - snapshot.assert_match(sql, "out.sql") - - -def test_string_agg(scalar_types_df: bpd.DataFrame, snapshot): - col_name = "string_col" - bf_df = scalar_types_df[[col_name]] - agg_expr = agg_ops.StringAggOp(sep=",").as_expr(col_name) - sql = _apply_ordered_unary_agg_ops( - bf_df, [agg_expr], [col_name], ordering_args=[col_name] - ) - - snapshot.assert_match(sql, "out.sql") diff --git a/tests/unit/core/compile/sqlglot/aggregations/test_unary_compiler.py b/tests/unit/core/compile/sqlglot/aggregations/test_unary_compiler.py deleted file mode 100644 index 7c827cd6dc1..00000000000 --- a/tests/unit/core/compile/sqlglot/aggregations/test_unary_compiler.py +++ /dev/null @@ -1,635 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import typing - -import pytest - -import bigframes.pandas as bpd -from bigframes.core import agg_expressions as agg_exprs -from bigframes.core import ( - array_value, - expression, - identifiers, - nodes, - ordering, - window_spec, -) -from bigframes.operations import aggregations as agg_ops - -pytest.importorskip("pytest_snapshot") - - -def _apply_unary_agg_ops( - obj: bpd.DataFrame, - ops_list: typing.Sequence[agg_exprs.UnaryAggregation], - new_names: typing.Sequence[str], -) -> str: - aggs = [(op, identifiers.ColumnId(name)) for op, name in zip(ops_list, new_names)] - - agg_node = nodes.AggregateNode(obj._block.expr.node, aggregations=tuple(aggs)) - result = array_value.ArrayValue(agg_node) - - sql = result.session._executor.to_sql(result, enable_cache=False) - return sql - - -def _apply_unary_window_op( - obj: bpd.DataFrame, - op: agg_exprs.UnaryAggregation, - window_spec: window_spec.WindowSpec, - new_name: str, -) -> str: - win_node = nodes.WindowOpNode( - obj._block.expr.node, - agg_exprs=(nodes.ColumnDef(op, identifiers.ColumnId(new_name)),), - window_spec=window_spec, - ) - result = array_value.ArrayValue(win_node).select_columns([new_name]) - - sql = result.session._executor.to_sql(result, enable_cache=False) - return sql - - -def test_all(scalar_types_df: bpd.DataFrame, snapshot): - bf_df = scalar_types_df[["bool_col", "int64_col"]] - ops_map = { - "bool_col": agg_ops.AllOp().as_expr("bool_col"), - "int64_col": agg_ops.AllOp().as_expr("int64_col"), - } - sql = _apply_unary_agg_ops(bf_df, list(ops_map.values()), list(ops_map.keys())) - - snapshot.assert_match(sql, "out.sql") - - -def test_all_w_window(scalar_types_df: bpd.DataFrame, snapshot): - col_name = "bool_col" - bf_df = scalar_types_df[[col_name]] - agg_expr = agg_ops.AllOp().as_expr(col_name) - - # Window tests - window = window_spec.WindowSpec(ordering=(ordering.ascending_over(col_name),)) - sql_window = _apply_unary_window_op(bf_df, agg_expr, window, "agg_bool") - snapshot.assert_match(sql_window, "out.sql") - - -def test_any(scalar_types_df: bpd.DataFrame, snapshot): - bf_df = scalar_types_df[["bool_col", "int64_col"]] - ops_map = { - "bool_col": agg_ops.AnyOp().as_expr("bool_col"), - "int64_col": agg_ops.AnyOp().as_expr("int64_col"), - } - sql = _apply_unary_agg_ops(bf_df, list(ops_map.values()), list(ops_map.keys())) - - snapshot.assert_match(sql, "out.sql") - - -def test_any_w_window(scalar_types_df: bpd.DataFrame, snapshot): - col_name = "bool_col" - bf_df = scalar_types_df[[col_name]] - agg_expr = agg_ops.AnyOp().as_expr(col_name) - - # Window tests - window = window_spec.WindowSpec(ordering=(ordering.ascending_over(col_name),)) - sql_window = _apply_unary_window_op(bf_df, agg_expr, window, "agg_bool") - snapshot.assert_match(sql_window, "out.sql") - - -def test_approx_quartiles(scalar_types_df: bpd.DataFrame, snapshot): - col_name = "int64_col" - bf_df = scalar_types_df[[col_name]] - agg_ops_map = { - "q1": agg_ops.ApproxQuartilesOp(quartile=1).as_expr(col_name), - "q2": agg_ops.ApproxQuartilesOp(quartile=2).as_expr(col_name), - "q3": agg_ops.ApproxQuartilesOp(quartile=3).as_expr(col_name), - } - sql = _apply_unary_agg_ops( - bf_df, list(agg_ops_map.values()), list(agg_ops_map.keys()) - ) - - snapshot.assert_match(sql, "out.sql") - - -def test_approx_top_count(scalar_types_df: bpd.DataFrame, snapshot): - col_name = "int64_col" - bf_df = scalar_types_df[[col_name]] - agg_expr = agg_ops.ApproxTopCountOp(number=10).as_expr(col_name) - sql = _apply_unary_agg_ops(bf_df, [agg_expr], [col_name]) - - snapshot.assert_match(sql, "out.sql") - - -def test_any_value(scalar_types_df: bpd.DataFrame, snapshot): - col_name = "int64_col" - bf_df = scalar_types_df[[col_name]] - agg_expr = agg_ops.AnyValueOp().as_expr(col_name) - sql = _apply_unary_agg_ops(bf_df, [agg_expr], [col_name]) - - snapshot.assert_match(sql, "out.sql") - - # Window tests - window = window_spec.WindowSpec(ordering=(ordering.descending_over(col_name),)) - sql_window = _apply_unary_window_op(bf_df, agg_expr, window, "agg_int64") - snapshot.assert_match(sql_window, "window_out.sql") - - bf_df_str = scalar_types_df[[col_name, "string_col"]] - window_partition = window_spec.WindowSpec( - grouping_keys=(expression.deref("string_col"),), - ordering=(ordering.ascending_over(col_name),), - ) - sql_window_partition = _apply_unary_window_op( - bf_df_str, agg_expr, window_partition, "agg_int64" - ) - snapshot.assert_match(sql_window_partition, "window_partition_out.sql") - - -def test_count(scalar_types_df: bpd.DataFrame, snapshot): - col_name = "int64_col" - bf_df = scalar_types_df[[col_name]] - agg_expr = agg_ops.CountOp().as_expr(col_name) - sql = _apply_unary_agg_ops(bf_df, [agg_expr], [col_name]) - - snapshot.assert_match(sql, "out.sql") - - # Window tests - window = window_spec.WindowSpec(ordering=(ordering.ascending_over(col_name),)) - sql_window = _apply_unary_window_op(bf_df, agg_expr, window, "agg_int64") - snapshot.assert_match(sql_window, "window_out.sql") - - bf_df_str = scalar_types_df[[col_name, "string_col"]] - window_partition = window_spec.WindowSpec( - grouping_keys=(expression.deref("string_col"),), - ordering=(ordering.descending_over(col_name),), - ) - sql_window_partition = _apply_unary_window_op( - bf_df_str, agg_expr, window_partition, "agg_int64" - ) - snapshot.assert_match(sql_window_partition, "window_partition_out.sql") - - -def test_cut(scalar_types_df: bpd.DataFrame, snapshot): - col_name = "int64_col" - bf_df = scalar_types_df[[col_name]] - agg_ops_map = { - "int_bins": agg_exprs.UnaryAggregation( - agg_ops.CutOp(bins=3, right=True, labels=None), expression.deref(col_name) - ), - "interval_bins": agg_exprs.UnaryAggregation( - agg_ops.CutOp(bins=((0, 1), (1, 2)), right=True, labels=None), - expression.deref(col_name), - ), - "int_bins_labels": agg_exprs.UnaryAggregation( - agg_ops.CutOp(bins=3, labels=("a", "b", "c"), right=False), - expression.deref(col_name), - ), - "interval_bins_labels": agg_exprs.UnaryAggregation( - agg_ops.CutOp(bins=((0, 1), (1, 2)), labels=False, right=True), - expression.deref(col_name), - ), - } - window = window_spec.WindowSpec() - - # Loop through the aggregation map items - for test_name, agg_expr in agg_ops_map.items(): - sql = _apply_unary_window_op(bf_df, agg_expr, window, test_name) - - snapshot.assert_match(sql, f"{test_name}.sql") - - -def test_dense_rank(scalar_types_df: bpd.DataFrame, snapshot): - col_name = "int64_col" - bf_df = scalar_types_df[[col_name]] - agg_expr = agg_exprs.UnaryAggregation( - agg_ops.DenseRankOp(), expression.deref(col_name) - ) - window = window_spec.WindowSpec(ordering=(ordering.descending_over(col_name),)) - sql = _apply_unary_window_op(bf_df, agg_expr, window, "agg_int64") - - snapshot.assert_match(sql, "out.sql") - - -def test_diff_w_int(scalar_types_df: bpd.DataFrame, snapshot): - # Test integer - int_col = "int64_col" - bf_df_int = scalar_types_df[[int_col]] - window = window_spec.WindowSpec(ordering=(ordering.ascending_over(int_col),)) - int_op = agg_exprs.UnaryAggregation( - agg_ops.DiffOp(periods=1), expression.deref(int_col) - ) - int_sql = _apply_unary_window_op(bf_df_int, int_op, window, "diff_int") - snapshot.assert_match(int_sql, "out.sql") - - -def test_diff_w_bool(scalar_types_df: bpd.DataFrame, snapshot): - bool_col = "bool_col" - bf_df_bool = scalar_types_df[[bool_col]] - window = window_spec.WindowSpec(ordering=(ordering.descending_over(bool_col),)) - bool_op = agg_exprs.UnaryAggregation( - agg_ops.DiffOp(periods=1), expression.deref(bool_col) - ) - bool_sql = _apply_unary_window_op(bf_df_bool, bool_op, window, "diff_bool") - snapshot.assert_match(bool_sql, "out.sql") - - -def test_diff_w_datetime(scalar_types_df: bpd.DataFrame, snapshot): - col_name = "datetime_col" - bf_df_date = scalar_types_df[[col_name]] - window = window_spec.WindowSpec(ordering=(ordering.ascending_over(col_name),)) - op = agg_exprs.UnaryAggregation( - agg_ops.DiffOp(periods=1), expression.deref(col_name) - ) - sql = _apply_unary_window_op(bf_df_date, op, window, "diff_datetime") - snapshot.assert_match(sql, "out.sql") - - -def test_diff_w_date(scalar_types_df: bpd.DataFrame, snapshot): - col_name = "date_col" - bf_df_date = scalar_types_df[[col_name]] - window = window_spec.WindowSpec(ordering=(ordering.ascending_over(col_name),)) - op = agg_exprs.UnaryAggregation( - agg_ops.DiffOp(periods=1), expression.deref(col_name) - ) - sql = _apply_unary_window_op(bf_df_date, op, window, "diff_date") - snapshot.assert_match(sql, "out.sql") - - -def test_diff_w_timestamp(scalar_types_df: bpd.DataFrame, snapshot): - col_name = "timestamp_col" - bf_df_timestamp = scalar_types_df[[col_name]] - window = window_spec.WindowSpec(ordering=(ordering.descending_over(col_name),)) - op = agg_exprs.UnaryAggregation( - agg_ops.DiffOp(periods=1), expression.deref(col_name) - ) - sql = _apply_unary_window_op(bf_df_timestamp, op, window, "diff_timestamp") - snapshot.assert_match(sql, "out.sql") - - -def test_first(scalar_types_df: bpd.DataFrame, snapshot): - col_name = "int64_col" - bf_df = scalar_types_df[[col_name]] - agg_expr = agg_exprs.UnaryAggregation(agg_ops.FirstOp(), expression.deref(col_name)) - window = window_spec.WindowSpec(ordering=(ordering.descending_over(col_name),)) - sql = _apply_unary_window_op(bf_df, agg_expr, window, "agg_int64") - - snapshot.assert_match(sql, "out.sql") - - -def test_first_non_null(scalar_types_df: bpd.DataFrame, snapshot): - col_name = "int64_col" - bf_df = scalar_types_df[[col_name]] - agg_expr = agg_exprs.UnaryAggregation( - agg_ops.FirstNonNullOp(), expression.deref(col_name) - ) - window = window_spec.WindowSpec(ordering=(ordering.ascending_over(col_name),)) - sql = _apply_unary_window_op(bf_df, agg_expr, window, "agg_int64") - - snapshot.assert_match(sql, "out.sql") - - -def test_last(scalar_types_df: bpd.DataFrame, snapshot): - col_name = "int64_col" - bf_df = scalar_types_df[[col_name]] - agg_expr = agg_exprs.UnaryAggregation(agg_ops.LastOp(), expression.deref(col_name)) - window = window_spec.WindowSpec(ordering=(ordering.descending_over(col_name),)) - sql = _apply_unary_window_op(bf_df, agg_expr, window, "agg_int64") - - snapshot.assert_match(sql, "out.sql") - - -def test_last_non_null(scalar_types_df: bpd.DataFrame, snapshot): - col_name = "int64_col" - bf_df = scalar_types_df[[col_name]] - agg_expr = agg_exprs.UnaryAggregation( - agg_ops.LastNonNullOp(), expression.deref(col_name) - ) - window = window_spec.WindowSpec(ordering=(ordering.ascending_over(col_name),)) - sql = _apply_unary_window_op(bf_df, agg_expr, window, "agg_int64") - - snapshot.assert_match(sql, "out.sql") - - -def test_max(scalar_types_df: bpd.DataFrame, snapshot): - col_name = "int64_col" - bf_df = scalar_types_df[[col_name]] - agg_expr = agg_ops.MaxOp().as_expr(col_name) - sql = _apply_unary_agg_ops(bf_df, [agg_expr], [col_name]) - - snapshot.assert_match(sql, "out.sql") - - # Window tests - window = window_spec.WindowSpec(ordering=(ordering.ascending_over(col_name),)) - sql_window = _apply_unary_window_op(bf_df, agg_expr, window, "agg_int64") - snapshot.assert_match(sql_window, "window_out.sql") - - bf_df_str = scalar_types_df[[col_name, "string_col"]] - window_partition = window_spec.WindowSpec( - grouping_keys=(expression.deref("string_col"),), - ordering=(ordering.descending_over(col_name),), - ) - sql_window_partition = _apply_unary_window_op( - bf_df_str, agg_expr, window_partition, "agg_int64" - ) - snapshot.assert_match(sql_window_partition, "window_partition_out.sql") - - -def test_mean(scalar_types_df: bpd.DataFrame, snapshot): - col_names = ["int64_col", "bool_col", "duration_col"] - bf_df = scalar_types_df[col_names] - bf_df["duration_col"] = bpd.to_timedelta(bf_df["duration_col"], unit="us") - - # The `to_timedelta` creates a new mapping for the column id. - col_names.insert(0, "rowindex") - name2id = { - col_name: col_id - for col_name, col_id in zip(col_names, bf_df._block.expr.column_ids) - } - - agg_ops_map = { - "int64_col": agg_ops.MeanOp().as_expr(name2id["int64_col"]), - "bool_col": agg_ops.MeanOp().as_expr(name2id["bool_col"]), - "duration_col": agg_ops.MeanOp().as_expr(name2id["duration_col"]), - "int64_col_w_floor": agg_ops.MeanOp(should_floor_result=True).as_expr( - name2id["int64_col"] - ), - } - sql = _apply_unary_agg_ops( - bf_df, list(agg_ops_map.values()), list(agg_ops_map.keys()) - ) - - snapshot.assert_match(sql, "out.sql") - - # Window tests - col_name = "int64_col" - bf_df_int = scalar_types_df[[col_name]] - agg_expr = agg_ops.MeanOp().as_expr(col_name) - window = window_spec.WindowSpec(ordering=(ordering.descending_over(col_name),)) - sql_window = _apply_unary_window_op(bf_df_int, agg_expr, window, "agg_int64") - snapshot.assert_match(sql_window, "window_out.sql") - - bf_df_str = scalar_types_df[[col_name, "string_col"]] - window_partition = window_spec.WindowSpec( - grouping_keys=(expression.deref("string_col"),), - ordering=(ordering.ascending_over(col_name),), - ) - sql_window_partition = _apply_unary_window_op( - bf_df_str, agg_expr, window_partition, "agg_int64" - ) - snapshot.assert_match(sql_window_partition, "window_partition_out.sql") - - -def test_median(scalar_types_df: bpd.DataFrame, snapshot): - bf_df = scalar_types_df - ops_map = { - "int64_col": agg_ops.MedianOp().as_expr("int64_col"), - "date_col": agg_ops.MedianOp().as_expr("date_col"), - "string_col": agg_ops.MedianOp().as_expr("string_col"), - } - sql = _apply_unary_agg_ops(bf_df, list(ops_map.values()), list(ops_map.keys())) - - snapshot.assert_match(sql, "out.sql") - - -def test_min(scalar_types_df: bpd.DataFrame, snapshot): - col_name = "int64_col" - bf_df = scalar_types_df[[col_name]] - agg_expr = agg_ops.MinOp().as_expr(col_name) - sql = _apply_unary_agg_ops(bf_df, [agg_expr], [col_name]) - - snapshot.assert_match(sql, "out.sql") - - # Window tests - window = window_spec.WindowSpec(ordering=(ordering.ascending_over(col_name),)) - sql_window = _apply_unary_window_op(bf_df, agg_expr, window, "agg_int64") - snapshot.assert_match(sql_window, "window_out.sql") - - bf_df_str = scalar_types_df[[col_name, "string_col"]] - window_partition = window_spec.WindowSpec( - grouping_keys=(expression.deref("string_col"),), - ordering=(ordering.descending_over(col_name),), - ) - sql_window_partition = _apply_unary_window_op( - bf_df_str, agg_expr, window_partition, "agg_int64" - ) - snapshot.assert_match(sql_window_partition, "window_partition_out.sql") - - -def test_nunique(scalar_types_df: bpd.DataFrame, snapshot): - col_name = "int64_col" - bf_df = scalar_types_df[[col_name]] - agg_expr = agg_ops.NuniqueOp().as_expr(col_name) - sql = _apply_unary_agg_ops(bf_df, [agg_expr], [col_name]) - - snapshot.assert_match(sql, "out.sql") - - -def test_pop_var(scalar_types_df: bpd.DataFrame, snapshot): - col_names = ["int64_col", "bool_col"] - bf_df = scalar_types_df[col_names] - - agg_ops_map = { - "int64_col": agg_ops.PopVarOp().as_expr("int64_col"), - "bool_col": agg_ops.PopVarOp().as_expr("bool_col"), - } - sql = _apply_unary_agg_ops( - bf_df, list(agg_ops_map.values()), list(agg_ops_map.keys()) - ) - snapshot.assert_match(sql, "out.sql") - - # Window tests - col_name = "int64_col" - bf_df_int = scalar_types_df[[col_name]] - agg_expr = agg_ops.PopVarOp().as_expr(col_name) - window = window_spec.WindowSpec(ordering=(ordering.descending_over(col_name),)) - sql_window = _apply_unary_window_op(bf_df_int, agg_expr, window, "agg_int64") - snapshot.assert_match(sql_window, "window_out.sql") - - -def test_product(scalar_types_df: bpd.DataFrame, snapshot): - col_name = "int64_col" - bf_df = scalar_types_df[[col_name]] - agg_expr = agg_ops.ProductOp().as_expr(col_name) - sql = _apply_unary_agg_ops(bf_df, [agg_expr], [col_name]) - - snapshot.assert_match(sql, "out.sql") - - bf_df_str = scalar_types_df[[col_name, "string_col"]] - window_partition = window_spec.WindowSpec( - grouping_keys=(expression.deref("string_col"),), - ) - sql_window_partition = _apply_unary_window_op( - bf_df_str, agg_expr, window_partition, "agg_int64" - ) - - snapshot.assert_match(sql_window_partition, "window_partition_out.sql") - - -def test_qcut(scalar_types_df: bpd.DataFrame, snapshot): - col_name = "int64_col" - bf = scalar_types_df[[col_name]] - bf["qcut_w_int"] = bpd.qcut(bf[col_name], q=4, labels=False, duplicates="drop") - - q_list = tuple([0, 0.25, 0.5, 0.75, 1]) - bf["qcut_w_list"] = bpd.qcut( - scalar_types_df[col_name], - q=q_list, - labels=False, - duplicates="drop", - ) - - snapshot.assert_match(bf.sql, "out.sql") - - -def test_quantile(scalar_types_df: bpd.DataFrame, snapshot): - bf_df = scalar_types_df[["int64_col", "bool_col"]] - agg_ops_map = { - "int64": agg_ops.QuantileOp(q=0.5).as_expr("int64_col"), - "bool": agg_ops.QuantileOp(q=0.5).as_expr("bool_col"), - "int64_w_floor": agg_ops.QuantileOp(q=0.5, should_floor_result=True).as_expr( - "int64_col" - ), - } - sql = _apply_unary_agg_ops( - bf_df, list(agg_ops_map.values()), list(agg_ops_map.keys()) - ) - - snapshot.assert_match(sql, "out.sql") - - -def test_rank(scalar_types_df: bpd.DataFrame, snapshot): - col_name = "int64_col" - bf_df = scalar_types_df[[col_name]] - agg_expr = agg_exprs.UnaryAggregation(agg_ops.RankOp(), expression.deref(col_name)) - - window = window_spec.WindowSpec( - ordering=(ordering.descending_over(col_name, nulls_last=False),) - ) - sql = _apply_unary_window_op(bf_df, agg_expr, window, "agg_int64") - - snapshot.assert_match(sql, "out.sql") - - -def test_shift(scalar_types_df: bpd.DataFrame, snapshot): - col_name = "int64_col" - bf_df = scalar_types_df[[col_name]] - window = window_spec.WindowSpec( - ordering=(ordering.ascending_over(col_name, nulls_last=False),) - ) - - # Test lag - lag_op = agg_exprs.UnaryAggregation( - agg_ops.ShiftOp(periods=1), expression.deref(col_name) - ) - lag_sql = _apply_unary_window_op(bf_df, lag_op, window, "lag") - snapshot.assert_match(lag_sql, "lag.sql") - - # Test lead - lead_op = agg_exprs.UnaryAggregation( - agg_ops.ShiftOp(periods=-1), expression.deref(col_name) - ) - lead_sql = _apply_unary_window_op(bf_df, lead_op, window, "lead") - snapshot.assert_match(lead_sql, "lead.sql") - - # Test no-op - noop_op = agg_exprs.UnaryAggregation( - agg_ops.ShiftOp(periods=0), expression.deref(col_name) - ) - noop_sql = _apply_unary_window_op(bf_df, noop_op, window, "noop") - snapshot.assert_match(noop_sql, "noop.sql") - - -def test_std(scalar_types_df: bpd.DataFrame, snapshot): - col_names = ["int64_col", "bool_col", "duration_col"] - bf_df = scalar_types_df[col_names] - bf_df["duration_col"] = bpd.to_timedelta(bf_df["duration_col"], unit="us") - - # The `to_timedelta` creates a new mapping for the column id. - col_names.insert(0, "rowindex") - name2id = { - col_name: col_id - for col_name, col_id in zip(col_names, bf_df._block.expr.column_ids) - } - - agg_ops_map = { - "int64_col": agg_ops.StdOp().as_expr(name2id["int64_col"]), - "bool_col": agg_ops.StdOp().as_expr(name2id["bool_col"]), - "duration_col": agg_ops.StdOp().as_expr(name2id["duration_col"]), - "int64_col_w_floor": agg_ops.StdOp(should_floor_result=True).as_expr( - name2id["int64_col"] - ), - } - sql = _apply_unary_agg_ops( - bf_df, list(agg_ops_map.values()), list(agg_ops_map.keys()) - ) - snapshot.assert_match(sql, "out.sql") - - # Window tests - col_name = "int64_col" - bf_df_int = scalar_types_df[[col_name]] - agg_expr = agg_ops.StdOp().as_expr(col_name) - window = window_spec.WindowSpec(ordering=(ordering.descending_over(col_name),)) - sql_window = _apply_unary_window_op(bf_df_int, agg_expr, window, "agg_int64") - snapshot.assert_match(sql_window, "window_out.sql") - - -def test_sum(scalar_types_df: bpd.DataFrame, snapshot): - bf_df = scalar_types_df[["int64_col", "bool_col"]] - agg_ops_map = { - "int64_col": agg_ops.SumOp().as_expr("int64_col"), - "bool_col": agg_ops.SumOp().as_expr("bool_col"), - } - sql = _apply_unary_agg_ops( - bf_df, list(agg_ops_map.values()), list(agg_ops_map.keys()) - ) - - snapshot.assert_match(sql, "out.sql") - - # Window tests - col_name = "int64_col" - bf_df_int = scalar_types_df[[col_name]] - agg_expr = agg_ops.SumOp().as_expr(col_name) - window = window_spec.WindowSpec(ordering=(ordering.descending_over(col_name),)) - sql_window = _apply_unary_window_op(bf_df_int, agg_expr, window, "agg_int64") - snapshot.assert_match(sql_window, "window_out.sql") - - bf_df_str = scalar_types_df[[col_name, "string_col"]] - window_partition = window_spec.WindowSpec( - grouping_keys=(expression.deref("string_col"),), - ordering=(ordering.ascending_over(col_name),), - ) - sql_window_partition = _apply_unary_window_op( - bf_df_str, agg_expr, window_partition, "agg_int64" - ) - snapshot.assert_match(sql_window_partition, "window_partition_out.sql") - - -def test_var(scalar_types_df: bpd.DataFrame, snapshot): - col_names = ["int64_col", "bool_col"] - bf_df = scalar_types_df[col_names] - - agg_ops_map = { - "int64_col": agg_ops.VarOp().as_expr("int64_col"), - "bool_col": agg_ops.VarOp().as_expr("bool_col"), - } - sql = _apply_unary_agg_ops( - bf_df, list(agg_ops_map.values()), list(agg_ops_map.keys()) - ) - snapshot.assert_match(sql, "out.sql") - - # Window tests - col_name = "int64_col" - bf_df_int = scalar_types_df[[col_name]] - agg_expr = agg_ops.VarOp().as_expr(col_name) - window = window_spec.WindowSpec(ordering=(ordering.descending_over(col_name),)) - sql_window = _apply_unary_window_op(bf_df_int, agg_expr, window, "agg_int64") - snapshot.assert_match(sql_window, "window_out.sql") diff --git a/tests/unit/core/compile/sqlglot/aggregations/test_windows.py b/tests/unit/core/compile/sqlglot/aggregations/test_windows.py deleted file mode 100644 index 98d0452c9a7..00000000000 --- a/tests/unit/core/compile/sqlglot/aggregations/test_windows.py +++ /dev/null @@ -1,178 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import unittest - -import bigframes_vendored.sqlglot.expressions as sge -import pandas as pd -import pytest - -import bigframes.core.expression as ex -import bigframes.core.identifiers as ids -import bigframes.core.ordering as ordering -from bigframes import dtypes -from bigframes.core import window_spec -from bigframes.core.compile.sqlglot.aggregations.windows import ( - apply_window_if_present, - get_window_order_by, -) - - -class WindowsTest(unittest.TestCase): - def test_get_window_order_by_empty(self): - self.assertIsNone(get_window_order_by(tuple())) - - def test_get_window_order_by(self): - result = get_window_order_by((ordering.OrderingExpression(ex.deref("col1")),)) - self.assertEqual( - sge.Order(expressions=result).sql(dialect="bigquery"), - "ORDER BY `col1` ASC NULLS LAST", - ) - - def test_get_window_order_by_override_nulls(self): - result = get_window_order_by( - (ordering.OrderingExpression(ex.deref("col1")),), - override_null_order=True, - ) - self.assertEqual( - sge.Order(expressions=result).sql(dialect="bigquery"), - "ORDER BY `col1` IS NULL ASC NULLS LAST, `col1` ASC NULLS LAST", - ) - - def test_get_window_order_by_override_nulls_desc(self): - result = get_window_order_by( - ( - ordering.OrderingExpression( - ex.deref("col1"), - direction=ordering.OrderingDirection.DESC, - na_last=False, - ), - ), - override_null_order=True, - ) - self.assertEqual( - sge.Order(expressions=result).sql(dialect="bigquery"), - "ORDER BY `col1` IS NULL DESC NULLS FIRST, `col1` DESC NULLS FIRST", - ) - - def test_apply_window_if_present_no_window(self): - value = sge.func( - "SUM", sge.Column(this=sge.to_identifier("col_0", quoted=True)) - ) - result = apply_window_if_present(value) - self.assertEqual(result, value) - - def test_apply_window_if_present_row_bounded_no_ordering_raises(self): - with pytest.raises( - ValueError, match="No ordering provided for ordered analytic function" - ): - apply_window_if_present( - sge.Var(this="value"), - window_spec.WindowSpec( - bounds=window_spec.RowsWindowBounds(start=-1, end=1) - ), - ) - - def test_apply_window_if_present_grouping_no_ordering(self): - result = apply_window_if_present( - sge.Var(this="value"), - window_spec.WindowSpec( - grouping_keys=( - ex.ResolvedDerefOp( - ids.ColumnId("col1"), - dtype=dtypes.STRING_DTYPE, - is_nullable=True, - ), - ex.ResolvedDerefOp( - ids.ColumnId("col2"), - dtype=dtypes.FLOAT_DTYPE, - is_nullable=True, - ), - ex.ResolvedDerefOp( - ids.ColumnId("col3"), - dtype=dtypes.JSON_DTYPE, - is_nullable=True, - ), - ex.ResolvedDerefOp( - ids.ColumnId("col4"), - dtype=dtypes.GEO_DTYPE, - is_nullable=True, - ), - ), - ), - ) - self.assertEqual( - result.sql(dialect="bigquery"), - "value OVER (PARTITION BY `col1`, CAST(`col2` AS STRING), TO_JSON_STRING(`col3`), ST_ASBINARY(`col4`))", - ) - - def test_apply_window_if_present_range_bounded(self): - result = apply_window_if_present( - sge.Var(this="value"), - window_spec.WindowSpec( - ordering=(ordering.OrderingExpression(ex.deref("col1")),), - bounds=window_spec.RangeWindowBounds(start=None, end=pd.Timedelta(0)), - ), - ) - self.assertEqual( - result.sql(dialect="bigquery"), - "value OVER (ORDER BY `col1` ASC RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)", - ) - - def test_apply_window_if_present_range_bounded_timedelta(self): - result = apply_window_if_present( - sge.Var(this="value"), - window_spec.WindowSpec( - ordering=(ordering.OrderingExpression(ex.deref("col1")),), - bounds=window_spec.RangeWindowBounds( - start=pd.Timedelta(days=-1), end=pd.Timedelta(hours=12) - ), - ), - ) - self.assertEqual( - result.sql(dialect="bigquery"), - "value OVER (ORDER BY `col1` ASC RANGE BETWEEN 86400000000 PRECEDING AND 43200000000 FOLLOWING)", - ) - - def test_apply_window_if_present_all_params(self): - result = apply_window_if_present( - sge.Var(this="value"), - window_spec.WindowSpec( - grouping_keys=( - ex.ResolvedDerefOp( - ids.ColumnId("col1"), - dtype=dtypes.STRING_DTYPE, - is_nullable=True, - ), - ), - ordering=( - ordering.OrderingExpression( - ex.ResolvedDerefOp( - ids.ColumnId("col2"), - dtype=dtypes.STRING_DTYPE, - is_nullable=True, - ) - ), - ), - bounds=window_spec.RowsWindowBounds(start=-1, end=0), - ), - ) - self.assertEqual( - result.sql(dialect="bigquery"), - "value OVER (PARTITION BY `col1` ORDER BY `col2` ASC NULLS LAST ROWS BETWEEN 1 PRECEDING AND CURRENT ROW)", - ) - - -if __name__ == "__main__": - unittest.main() diff --git a/tests/unit/core/compile/sqlglot/expressions/__init__.py b/tests/unit/core/compile/sqlglot/expressions/__init__.py deleted file mode 100644 index 0a2669d7a25..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_ai_ops/test_ai_classify/None/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_ai_ops/test_ai_classify/None/out.sql deleted file mode 100644 index fc29d96cc1a..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_ai_ops/test_ai_classify/None/out.sql +++ /dev/null @@ -1,3 +0,0 @@ -SELECT - AI.CLASSIFY(input => STRUCT(`string_col`), categories => ['greeting', 'rejection']) AS `result` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_ai_ops/test_ai_classify/bigframes-dev.us.bigframes-default-connection/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_ai_ops/test_ai_classify/bigframes-dev.us.bigframes-default-connection/out.sql deleted file mode 100644 index 969b946725b..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_ai_ops/test_ai_classify/bigframes-dev.us.bigframes-default-connection/out.sql +++ /dev/null @@ -1,7 +0,0 @@ -SELECT - AI.CLASSIFY( - input => STRUCT(`string_col`), - categories => ['greeting', 'rejection'], - connection_id => 'bigframes-dev.us.bigframes-default-connection' - ) AS `result` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_ai_ops/test_ai_classify_multi_with_list_examples/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_ai_ops/test_ai_classify_multi_with_list_examples/out.sql deleted file mode 100644 index 74078e98606..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_ai_ops/test_ai_classify_multi_with_list_examples/out.sql +++ /dev/null @@ -1,8 +0,0 @@ -SELECT - AI.CLASSIFY( - input => STRUCT(`string_col`), - categories => ['greeting', 'rejection'], - examples => [('hi', ['greeting', 'positive']), ('bye', ['rejection', 'negative'])], - output_mode => 'multi' - ) AS `result` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_ai_ops/test_ai_classify_with_output_mode/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_ai_ops/test_ai_classify_with_output_mode/out.sql deleted file mode 100644 index 08d7476d77f..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_ai_ops/test_ai_classify_with_output_mode/out.sql +++ /dev/null @@ -1,7 +0,0 @@ -SELECT - AI.CLASSIFY( - input => STRUCT(`string_col`), - categories => ['greeting', 'rejection'], - output_mode => 'multi' - ) AS `result` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_ai_ops/test_ai_classify_with_params/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_ai_ops/test_ai_classify_with_params/out.sql deleted file mode 100644 index 30542740a2d..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_ai_ops/test_ai_classify_with_params/out.sql +++ /dev/null @@ -1,9 +0,0 @@ -SELECT - AI.CLASSIFY( - input => STRUCT(`string_col`), - categories => ['greeting', 'rejection'], - examples => [('hi', 'greeting'), ('bye', 'rejection')], - endpoint => 'gemini-2.5-flash', - max_error_ratio => 0.1 - ) AS `result` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_ai_ops/test_ai_embed/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_ai_ops/test_ai_embed/out.sql deleted file mode 100644 index 9c18a7cd532..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_ai_ops/test_ai_embed/out.sql +++ /dev/null @@ -1,3 +0,0 @@ -SELECT - AI.EMBED(`string_col`, endpoint => 'text-embedding-005') AS `result` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_ai_ops/test_ai_embed_with_connection_id/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_ai_ops/test_ai_embed_with_connection_id/out.sql deleted file mode 100644 index 0968a101b22..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_ai_ops/test_ai_embed_with_connection_id/out.sql +++ /dev/null @@ -1,7 +0,0 @@ -SELECT - AI.EMBED( - `string_col`, - endpoint => 'text-embedding-005', - connection_id => 'bigframes-dev.us.bigframes-default-connection' - ) AS `result` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_ai_ops/test_ai_embed_with_model/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_ai_ops/test_ai_embed_with_model/out.sql deleted file mode 100644 index 4c3c76f87b6..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_ai_ops/test_ai_embed_with_model/out.sql +++ /dev/null @@ -1,3 +0,0 @@ -SELECT - AI.EMBED(`string_col`, model => 'embeddinggemma-300m') AS `result` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_ai_ops/test_ai_embed_with_model_param_and_title/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_ai_ops/test_ai_embed_with_model_param_and_title/out.sql deleted file mode 100644 index 873db838682..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_ai_ops/test_ai_embed_with_model_param_and_title/out.sql +++ /dev/null @@ -1,9 +0,0 @@ -SELECT - AI.EMBED( - `string_col`, - endpoint => 'text-embedding-005', - task_type => 'retrieval_document', - title => 'My Document', - model_params => JSON '{"outputDimensionality": 256}' - ) AS `result` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_ai_ops/test_ai_embed_with_task_type_and_title/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_ai_ops/test_ai_embed_with_task_type_and_title/out.sql deleted file mode 100644 index 9e4db995871..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_ai_ops/test_ai_embed_with_task_type_and_title/out.sql +++ /dev/null @@ -1,9 +0,0 @@ -SELECT - AI.EMBED( - `string_col`, - endpoint => 'text-embedding-005', - task_type => 'RETRIEVAL_DOCUMENT', - title => 'My Document', - model_params => JSON '{"outputDimensionality": 256}' - ) AS `result` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_ai_ops/test_ai_generate/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_ai_ops/test_ai_generate/out.sql deleted file mode 100644 index 622782fa7d6..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_ai_ops/test_ai_generate/out.sql +++ /dev/null @@ -1,7 +0,0 @@ -SELECT - AI.GENERATE( - prompt => STRUCT(`string_col`, ' is the same as ', `string_col`), - endpoint => 'gemini-2.5-flash', - request_type => 'SHARED' - ) AS `result` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_ai_ops/test_ai_generate_bool/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_ai_ops/test_ai_generate_bool/out.sql deleted file mode 100644 index a71bce037a5..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_ai_ops/test_ai_generate_bool/out.sql +++ /dev/null @@ -1,6 +0,0 @@ -SELECT - AI.GENERATE_BOOL( - prompt => STRUCT(`string_col`, ' is the same as ', `string_col`), - endpoint => 'gemini-2.5-flash' - ) AS `result` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_ai_ops/test_ai_generate_bool_with_connection_id/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_ai_ops/test_ai_generate_bool_with_connection_id/out.sql deleted file mode 100644 index db1ec378aaf..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_ai_ops/test_ai_generate_bool_with_connection_id/out.sql +++ /dev/null @@ -1,7 +0,0 @@ -SELECT - AI.GENERATE_BOOL( - prompt => STRUCT(`string_col`, ' is the same as ', `string_col`), - connection_id => 'bigframes-dev.us.bigframes-default-connection', - endpoint => 'gemini-2.5-flash' - ) AS `result` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_ai_ops/test_ai_generate_bool_with_model_param/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_ai_ops/test_ai_generate_bool_with_model_param/out.sql deleted file mode 100644 index 76af8833e63..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_ai_ops/test_ai_generate_bool_with_model_param/out.sql +++ /dev/null @@ -1,6 +0,0 @@ -SELECT - AI.GENERATE_BOOL( - prompt => STRUCT(`string_col`, ' is the same as ', `string_col`), - model_params => JSON '{}' - ) AS `result` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_ai_ops/test_ai_generate_double/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_ai_ops/test_ai_generate_double/out.sql deleted file mode 100644 index 1cef7568798..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_ai_ops/test_ai_generate_double/out.sql +++ /dev/null @@ -1,6 +0,0 @@ -SELECT - AI.GENERATE_DOUBLE( - prompt => STRUCT(`string_col`, ' is the same as ', `string_col`), - endpoint => 'gemini-2.5-flash' - ) AS `result` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_ai_ops/test_ai_generate_double_with_connection_id/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_ai_ops/test_ai_generate_double_with_connection_id/out.sql deleted file mode 100644 index d0088721e38..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_ai_ops/test_ai_generate_double_with_connection_id/out.sql +++ /dev/null @@ -1,7 +0,0 @@ -SELECT - AI.GENERATE_DOUBLE( - prompt => STRUCT(`string_col`, ' is the same as ', `string_col`), - connection_id => 'bigframes-dev.us.bigframes-default-connection', - endpoint => 'gemini-2.5-flash' - ) AS `result` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_ai_ops/test_ai_generate_double_with_model_param/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_ai_ops/test_ai_generate_double_with_model_param/out.sql deleted file mode 100644 index 2b50e05b7fe..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_ai_ops/test_ai_generate_double_with_model_param/out.sql +++ /dev/null @@ -1,6 +0,0 @@ -SELECT - AI.GENERATE_DOUBLE( - prompt => STRUCT(`string_col`, ' is the same as ', `string_col`), - model_params => JSON '{}' - ) AS `result` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_ai_ops/test_ai_generate_int/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_ai_ops/test_ai_generate_int/out.sql deleted file mode 100644 index 9ef143c8b9e..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_ai_ops/test_ai_generate_int/out.sql +++ /dev/null @@ -1,6 +0,0 @@ -SELECT - AI.GENERATE_INT( - prompt => STRUCT(`string_col`, ' is the same as ', `string_col`), - endpoint => 'gemini-2.5-flash' - ) AS `result` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_ai_ops/test_ai_generate_int_with_connection_id/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_ai_ops/test_ai_generate_int_with_connection_id/out.sql deleted file mode 100644 index 3fa3e8cc05e..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_ai_ops/test_ai_generate_int_with_connection_id/out.sql +++ /dev/null @@ -1,7 +0,0 @@ -SELECT - AI.GENERATE_INT( - prompt => STRUCT(`string_col`, ' is the same as ', `string_col`), - connection_id => 'bigframes-dev.us.bigframes-default-connection', - endpoint => 'gemini-2.5-flash' - ) AS `result` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_ai_ops/test_ai_generate_int_with_model_param/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_ai_ops/test_ai_generate_int_with_model_param/out.sql deleted file mode 100644 index 18adea8a062..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_ai_ops/test_ai_generate_int_with_model_param/out.sql +++ /dev/null @@ -1,6 +0,0 @@ -SELECT - AI.GENERATE_INT( - prompt => STRUCT(`string_col`, ' is the same as ', `string_col`), - model_params => JSON '{}' - ) AS `result` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_ai_ops/test_ai_generate_with_connection_id/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_ai_ops/test_ai_generate_with_connection_id/out.sql deleted file mode 100644 index 14604cfc8df..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_ai_ops/test_ai_generate_with_connection_id/out.sql +++ /dev/null @@ -1,7 +0,0 @@ -SELECT - AI.GENERATE( - prompt => STRUCT(`string_col`, ' is the same as ', `string_col`), - connection_id => 'bigframes-dev.us.bigframes-default-connection', - endpoint => 'gemini-2.5-flash' - ) AS `result` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_ai_ops/test_ai_generate_with_model_param/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_ai_ops/test_ai_generate_with_model_param/out.sql deleted file mode 100644 index 090a42d889f..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_ai_ops/test_ai_generate_with_model_param/out.sql +++ /dev/null @@ -1,6 +0,0 @@ -SELECT - AI.GENERATE( - prompt => STRUCT(`string_col`, ' is the same as ', `string_col`), - model_params => JSON '{}' - ) AS `result` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_ai_ops/test_ai_generate_with_output_schema/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_ai_ops/test_ai_generate_with_output_schema/out.sql deleted file mode 100644 index 31c179e7b01..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_ai_ops/test_ai_generate_with_output_schema/out.sql +++ /dev/null @@ -1,7 +0,0 @@ -SELECT - AI.GENERATE( - prompt => STRUCT(`string_col`, ' is the same as ', `string_col`), - endpoint => 'gemini-2.5-flash', - output_schema => 'x INT64, y FLOAT64' - ) AS `result` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_ai_ops/test_ai_if/None/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_ai_ops/test_ai_if/None/out.sql deleted file mode 100644 index 59cf1c02a35..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_ai_ops/test_ai_if/None/out.sql +++ /dev/null @@ -1,7 +0,0 @@ -SELECT - AI.IF( - prompt => STRUCT(`string_col`, ' is the same as ', `string_col`), - optimization_mode => 'MINIMIZE_COST', - max_error_ratio => 0.5 - ) AS `result` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_ai_ops/test_ai_if/bigframes-dev.us.bigframes-default-connection/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_ai_ops/test_ai_if/bigframes-dev.us.bigframes-default-connection/out.sql deleted file mode 100644 index 0f26ab3c6ea..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_ai_ops/test_ai_if/bigframes-dev.us.bigframes-default-connection/out.sql +++ /dev/null @@ -1,8 +0,0 @@ -SELECT - AI.IF( - prompt => STRUCT(`string_col`, ' is the same as ', `string_col`), - connection_id => 'bigframes-dev.us.bigframes-default-connection', - optimization_mode => 'MINIMIZE_COST', - max_error_ratio => 0.5 - ) AS `result` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_ai_ops/test_ai_if_with_endpoint/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_ai_ops/test_ai_if_with_endpoint/out.sql deleted file mode 100644 index 4dd910528a4..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_ai_ops/test_ai_if_with_endpoint/out.sql +++ /dev/null @@ -1,6 +0,0 @@ -SELECT - AI.IF( - prompt => STRUCT(`string_col`, ' is the same as ', `string_col`), - endpoint => 'gemini-2.5-flash' - ) AS `result` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_ai_ops/test_ai_score/None/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_ai_ops/test_ai_score/None/out.sql deleted file mode 100644 index 37590eec4f0..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_ai_ops/test_ai_score/None/out.sql +++ /dev/null @@ -1,3 +0,0 @@ -SELECT - AI.SCORE(prompt => STRUCT(`string_col`, ' is the same as ', `string_col`)) AS `result` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_ai_ops/test_ai_score/bigframes-dev.us.bigframes-default-connection/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_ai_ops/test_ai_score/bigframes-dev.us.bigframes-default-connection/out.sql deleted file mode 100644 index 696c7e9f318..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_ai_ops/test_ai_score/bigframes-dev.us.bigframes-default-connection/out.sql +++ /dev/null @@ -1,6 +0,0 @@ -SELECT - AI.SCORE( - prompt => STRUCT(`string_col`, ' is the same as ', `string_col`), - connection_id => 'bigframes-dev.us.bigframes-default-connection' - ) AS `result` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_ai_ops/test_ai_score_with_endpoint_and_max_error_ratio/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_ai_ops/test_ai_score_with_endpoint_and_max_error_ratio/out.sql deleted file mode 100644 index a802e5a396b..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_ai_ops/test_ai_score_with_endpoint_and_max_error_ratio/out.sql +++ /dev/null @@ -1,7 +0,0 @@ -SELECT - AI.SCORE( - prompt => STRUCT(`string_col`, ' is the same as ', `string_col`), - endpoint => 'gemini-2.5-flash', - max_error_ratio => 0.5 - ) AS `result` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_ai_ops/test_ai_similarity/None/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_ai_ops/test_ai_similarity/None/out.sql deleted file mode 100644 index 1df70aaf18e..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_ai_ops/test_ai_similarity/None/out.sql +++ /dev/null @@ -1,3 +0,0 @@ -SELECT - AI.SIMILARITY(content1 => `string_col`, content2 => `string_col`, endpoint => 'text-embedding-005') AS `result` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_ai_ops/test_ai_similarity/bigframes-dev.us.bigframes-default-connection/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_ai_ops/test_ai_similarity/bigframes-dev.us.bigframes-default-connection/out.sql deleted file mode 100644 index db57188ffa0..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_ai_ops/test_ai_similarity/bigframes-dev.us.bigframes-default-connection/out.sql +++ /dev/null @@ -1,8 +0,0 @@ -SELECT - AI.SIMILARITY( - content1 => `string_col`, - content2 => `string_col`, - endpoint => 'text-embedding-005', - connection_id => 'bigframes-dev.us.bigframes-default-connection' - ) AS `result` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_ai_ops/test_ai_similarity_with_model/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_ai_ops/test_ai_similarity_with_model/out.sql deleted file mode 100644 index 704f9f94491..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_ai_ops/test_ai_similarity_with_model/out.sql +++ /dev/null @@ -1,3 +0,0 @@ -SELECT - AI.SIMILARITY(content1 => `string_col`, content2 => `string_col`, model => 'embeddinggemma-300m') AS `result` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_ai_ops/test_ai_similarity_with_model_param/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_ai_ops/test_ai_similarity_with_model_param/out.sql deleted file mode 100644 index 5173ac43bd9..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_ai_ops/test_ai_similarity_with_model_param/out.sql +++ /dev/null @@ -1,8 +0,0 @@ -SELECT - AI.SIMILARITY( - content1 => `string_col`, - content2 => `string_col`, - endpoint => 'text-embedding-005', - model_params => JSON '{"outputDimensionality": 256}' - ) AS `result` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_array_ops/test_array_index/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_array_ops/test_array_index/out.sql deleted file mode 100644 index a1f089424a1..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_array_ops/test_array_index/out.sql +++ /dev/null @@ -1,4 +0,0 @@ -SELECT - IF(SUBSTRING(`string_col`, 2, 1) <> '', SUBSTRING(`string_col`, 2, 1), NULL) AS `string_index`, - [`int64_col`, `int64_too`][SAFE_OFFSET(1)] AS `array_index` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_array_ops/test_array_reduce_op/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_array_ops/test_array_reduce_op/out.sql deleted file mode 100644 index 1053ec1c2c6..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_array_ops/test_array_reduce_op/out.sql +++ /dev/null @@ -1,27 +0,0 @@ -SELECT - ( - SELECT - COALESCE(SUM(bf_arr_reduce_uid), 0) - FROM UNNEST(`float_list_col`) AS bf_arr_reduce_uid - ) AS `sum_float`, - ( - SELECT - STDDEV(bf_arr_reduce_uid) - FROM UNNEST(`float_list_col`) AS bf_arr_reduce_uid - ) AS `std_float`, - ( - SELECT - COUNT(bf_arr_reduce_uid) - FROM UNNEST(`string_list_col`) AS bf_arr_reduce_uid - ) AS `count_str`, - ( - SELECT - COALESCE(LOGICAL_OR(bf_arr_reduce_uid), FALSE) - FROM UNNEST(`bool_list_col`) AS bf_arr_reduce_uid - ) AS `any_bool`, - ( - SELECT - ARRAY_AGG(bf_arr_reduce_uid IGNORE NULLS) - FROM UNNEST(`string_list_col`) AS bf_arr_reduce_uid - ) AS `array_agg_str` -FROM `bigframes-dev`.`sqlglot_test`.`repeated_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_array_ops/test_array_slice/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_array_ops/test_array_slice/out.sql deleted file mode 100644 index ffec3b8e934..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_array_ops/test_array_slice/out.sql +++ /dev/null @@ -1,17 +0,0 @@ -SELECT - SUBSTRING(`string_col`, 2, 4) AS `string_slice`, - ARRAY( - SELECT - el - FROM UNNEST([`int64_col`, `int64_too`]) AS el WITH OFFSET AS slice_idx - WHERE - slice_idx >= 1 - ) AS `slice_only_start`, - ARRAY( - SELECT - el - FROM UNNEST([`int64_col`, `int64_too`]) AS el WITH OFFSET AS slice_idx - WHERE - slice_idx >= 1 AND slice_idx < 5 - ) AS `slice_start_stop` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_array_ops/test_array_to_string/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_array_ops/test_array_to_string/out.sql deleted file mode 100644 index 27587771506..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_array_ops/test_array_to_string/out.sql +++ /dev/null @@ -1,3 +0,0 @@ -SELECT - ARRAY_TO_STRING(`string_list_col`, '.') AS `string_list_col` -FROM `bigframes-dev`.`sqlglot_test`.`repeated_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_array_ops/test_to_array_op/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_array_ops/test_to_array_op/out.sql deleted file mode 100644 index f7d8d748b4a..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_array_ops/test_to_array_op/out.sql +++ /dev/null @@ -1,10 +0,0 @@ -SELECT - [COALESCE(`bool_col`, FALSE)] AS `bool_col`, - [COALESCE(`int64_col`, 0)] AS `int64_col`, - [COALESCE(`string_col`, ''), COALESCE(`string_col`, '')] AS `strs_col`, - [ - COALESCE(`int64_col`, 0), - CAST(COALESCE(`bool_col`, FALSE) AS INT64), - COALESCE(`float64_col`, 0.0) - ] AS `numeric_col` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_array_ops/test_to_array_with_subquery_expression/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_array_ops/test_to_array_with_subquery_expression/out.sql deleted file mode 100644 index 63dfcec026b..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_array_ops/test_to_array_with_subquery_expression/out.sql +++ /dev/null @@ -1,12 +0,0 @@ -SELECT - [ - COALESCE( - ( - SELECT - COALESCE(SUM(bf_arr_reduce_uid), 0) - FROM UNNEST(`float_list_col`) AS bf_arr_reduce_uid - ), - 0.0 - ) - ] AS `arr_subquery_coalesce` -FROM `bigframes-dev`.`sqlglot_test`.`repeated_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_bool_ops/test_and_op/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_bool_ops/test_and_op/out.sql deleted file mode 100644 index d6f6587ead9..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_bool_ops/test_and_op/out.sql +++ /dev/null @@ -1,9 +0,0 @@ -SELECT - `rowindex`, - `bool_col`, - `int64_col`, - `int64_col` & `int64_col` AS `int_and_int`, - `bool_col` AND `bool_col` AS `bool_and_bool`, - IF(`bool_col` = FALSE, `bool_col`, NULL) AS `bool_and_null`, - IF(`bool_col` = FALSE, `bool_col`, NULL) AS `null_and_bool` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_bool_ops/test_or_op/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_bool_ops/test_or_op/out.sql deleted file mode 100644 index dad4cee9d0b..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_bool_ops/test_or_op/out.sql +++ /dev/null @@ -1,9 +0,0 @@ -SELECT - `rowindex`, - `bool_col`, - `int64_col`, - `int64_col` | `int64_col` AS `int_and_int`, - `bool_col` OR `bool_col` AS `bool_and_bool`, - IF(`bool_col` = TRUE, `bool_col`, NULL) AS `bool_and_null`, - IF(`bool_col` = TRUE, `bool_col`, NULL) AS `null_and_bool` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_bool_ops/test_xor_op/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_bool_ops/test_xor_op/out.sql deleted file mode 100644 index 4be3b9f94ad..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_bool_ops/test_xor_op/out.sql +++ /dev/null @@ -1,23 +0,0 @@ -SELECT - `rowindex`, - `bool_col`, - `int64_col`, - `int64_col` ^ `int64_col` AS `int_and_int`, - ( - `bool_col` AND NOT `bool_col` - ) OR ( - NOT `bool_col` AND `bool_col` - ) AS `bool_and_bool`, - ( - `bool_col` AND NOT CAST(NULL AS BOOLEAN) - ) - OR ( - NOT `bool_col` AND CAST(NULL AS BOOLEAN) - ) AS `bool_and_null`, - ( - `bool_col` AND NOT CAST(NULL AS BOOLEAN) - ) - OR ( - NOT `bool_col` AND CAST(NULL AS BOOLEAN) - ) AS `null_and_bool` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_comparison_ops/test_eq_null_match/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_comparison_ops/test_eq_null_match/out.sql deleted file mode 100644 index 3d23b8576ec..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_comparison_ops/test_eq_null_match/out.sql +++ /dev/null @@ -1,3 +0,0 @@ -SELECT - COALESCE(CAST(`int64_col` AS STRING), '$NULL_SENTINEL$') = COALESCE(CAST(CAST(`bool_col` AS INT64) AS STRING), '$NULL_SENTINEL$') AS `int64_col` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_comparison_ops/test_eq_numeric/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_comparison_ops/test_eq_numeric/out.sql deleted file mode 100644 index 7827731881e..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_comparison_ops/test_eq_numeric/out.sql +++ /dev/null @@ -1,11 +0,0 @@ -SELECT - `rowindex`, - `int64_col`, - `bool_col`, - `int64_col` = `int64_col` AS `int_eq_int`, - `int64_col` = 1 AS `int_eq_1`, - `int64_col` IS NULL AS `int_eq_null`, - `int64_col` IS NULL AS `null_eq_int`, - `int64_col` = CAST(`bool_col` AS INT64) AS `int_eq_bool`, - CAST(`bool_col` AS INT64) = `int64_col` AS `bool_eq_int` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_comparison_ops/test_ge_numeric/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_comparison_ops/test_ge_numeric/out.sql deleted file mode 100644 index 5903cf03699..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_comparison_ops/test_ge_numeric/out.sql +++ /dev/null @@ -1,10 +0,0 @@ -SELECT - `rowindex`, - `int64_col`, - `bool_col`, - `int64_col` >= `int64_col` AS `int_ge_int`, - `int64_col` >= 1 AS `int_ge_1`, - NULL AS `null_ge_int`, - `int64_col` >= CAST(`bool_col` AS INT64) AS `int_ge_bool`, - CAST(`bool_col` AS INT64) >= `int64_col` AS `bool_ge_int` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_comparison_ops/test_gt_numeric/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_comparison_ops/test_gt_numeric/out.sql deleted file mode 100644 index 42bf029240f..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_comparison_ops/test_gt_numeric/out.sql +++ /dev/null @@ -1,10 +0,0 @@ -SELECT - `rowindex`, - `int64_col`, - `bool_col`, - `int64_col` > `int64_col` AS `int_gt_int`, - `int64_col` > 1 AS `int_gt_1`, - NULL AS `null_gt_int`, - `int64_col` > CAST(`bool_col` AS INT64) AS `int_gt_bool`, - CAST(`bool_col` AS INT64) > `int64_col` AS `bool_gt_int` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_comparison_ops/test_is_in/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_comparison_ops/test_is_in/out.sql deleted file mode 100644 index 308e6f9cbd7..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_comparison_ops/test_is_in/out.sql +++ /dev/null @@ -1,19 +0,0 @@ -SELECT - COALESCE(`bool_col` IN (TRUE, FALSE), FALSE) AS `bools`, - COALESCE(`int64_col` IN (1, 2, 3), FALSE) AS `ints`, - `int64_col` IS NULL AS `ints_w_null`, - COALESCE(`int64_col` IN (1.0, 2.0, 3.0), FALSE) AS `floats`, - FALSE AS `strings`, - COALESCE(`int64_col` IN (2.5, 3, 1e-10, CAST('Infinity' AS FLOAT64), NULL, 0), FALSE) AS `mixed`, - FALSE AS `empty`, - FALSE AS `empty_wo_match_nulls`, - COALESCE(`int64_col` IN (123456), FALSE) AS `ints_wo_match_nulls`, - ( - `float64_col` IS NULL - ) OR `float64_col` IN (1, 2, 3) AS `float_in_ints`, - ( - `int64_col` IS NULL - ) OR `int64_col` IN (2) AS `mixed_with_null`, - COALESCE(CAST(`bool_col` AS INT64) IN (1, 2.5), FALSE) AS `bool_in_mixed`, - `int64_col` IS NULL AS `only_null_match` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_comparison_ops/test_le_numeric/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_comparison_ops/test_le_numeric/out.sql deleted file mode 100644 index c6c86510102..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_comparison_ops/test_le_numeric/out.sql +++ /dev/null @@ -1,10 +0,0 @@ -SELECT - `rowindex`, - `int64_col`, - `bool_col`, - `int64_col` <= `int64_col` AS `int_le_int`, - `int64_col` <= 1 AS `int_le_1`, - NULL AS `null_le_int`, - `int64_col` <= CAST(`bool_col` AS INT64) AS `int_le_bool`, - CAST(`bool_col` AS INT64) <= `int64_col` AS `bool_le_int` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_comparison_ops/test_lt_numeric/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_comparison_ops/test_lt_numeric/out.sql deleted file mode 100644 index ec5c317a8e5..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_comparison_ops/test_lt_numeric/out.sql +++ /dev/null @@ -1,10 +0,0 @@ -SELECT - `rowindex`, - `int64_col`, - `bool_col`, - `int64_col` < `int64_col` AS `int_lt_int`, - `int64_col` < 1 AS `int_lt_1`, - NULL AS `null_lt_int`, - `int64_col` < CAST(`bool_col` AS INT64) AS `int_lt_bool`, - CAST(`bool_col` AS INT64) < `int64_col` AS `bool_lt_int` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_comparison_ops/test_maximum_op/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_comparison_ops/test_maximum_op/out.sql deleted file mode 100644 index a469fa47cf1..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_comparison_ops/test_maximum_op/out.sql +++ /dev/null @@ -1,3 +0,0 @@ -SELECT - GREATEST(`int64_col`, `float64_col`) AS `int64_col` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_comparison_ops/test_minimum_op/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_comparison_ops/test_minimum_op/out.sql deleted file mode 100644 index ea82af979a3..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_comparison_ops/test_minimum_op/out.sql +++ /dev/null @@ -1,3 +0,0 @@ -SELECT - LEAST(`int64_col`, `float64_col`) AS `int64_col` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_comparison_ops/test_ne_numeric/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_comparison_ops/test_ne_numeric/out.sql deleted file mode 100644 index 448a6146294..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_comparison_ops/test_ne_numeric/out.sql +++ /dev/null @@ -1,15 +0,0 @@ -SELECT - `rowindex`, - `int64_col`, - `bool_col`, - `int64_col` <> `int64_col` AS `int_ne_int`, - `int64_col` <> 1 AS `int_ne_1`, - ( - `int64_col` - ) IS NOT NULL AS `int_ne_null`, - ( - `int64_col` - ) IS NOT NULL AS `null_ne_int`, - `int64_col` <> CAST(`bool_col` AS INT64) AS `int_ne_bool`, - CAST(`bool_col` AS INT64) <> `int64_col` AS `bool_ne_int` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_datetime_ops/test_add_timedelta/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_datetime_ops/test_add_timedelta/out.sql deleted file mode 100644 index b1ccf096cfa..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_datetime_ops/test_add_timedelta/out.sql +++ /dev/null @@ -1,10 +0,0 @@ -SELECT - `rowindex`, - `timestamp_col`, - `date_col`, - TIMESTAMP_ADD(CAST(`date_col` AS DATETIME), INTERVAL 86400000000 MICROSECOND) AS `date_add_timedelta`, - TIMESTAMP_ADD(`timestamp_col`, INTERVAL 86400000000 MICROSECOND) AS `timestamp_add_timedelta`, - TIMESTAMP_ADD(CAST(`date_col` AS DATETIME), INTERVAL 86400000000 MICROSECOND) AS `timedelta_add_date`, - TIMESTAMP_ADD(`timestamp_col`, INTERVAL 86400000000 MICROSECOND) AS `timedelta_add_timestamp`, - 172800000000 AS `timedelta_add_timedelta` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_datetime_ops/test_date/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_datetime_ops/test_date/out.sql deleted file mode 100644 index eb0d2f11049..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_datetime_ops/test_date/out.sql +++ /dev/null @@ -1,3 +0,0 @@ -SELECT - DATE(`timestamp_col`) AS `timestamp_col` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_datetime_ops/test_datetime_to_integer_label/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_datetime_ops/test_datetime_to_integer_label/out.sql deleted file mode 100644 index 4b0696386c1..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_datetime_ops/test_datetime_to_integer_label/out.sql +++ /dev/null @@ -1,76 +0,0 @@ -SELECT - CAST(FLOOR( - IEEE_DIVIDE( - UNIX_MICROS(CAST(`datetime_col` AS TIMESTAMP)) - UNIX_MICROS(CAST(`timestamp_col` AS TIMESTAMP)), - 86400000000 - ) - ) AS INT64) AS `fixed_freq`, - CAST(FLOOR(IEEE_DIVIDE(UNIX_MICROS(CAST(`datetime_col` AS TIMESTAMP)) - 0, 86400000000)) AS INT64) AS `origin_epoch`, - CAST(FLOOR( - IEEE_DIVIDE( - UNIX_MICROS(CAST(`datetime_col` AS TIMESTAMP)) - UNIX_MICROS(CAST(CAST(`timestamp_col` AS DATE) AS TIMESTAMP)), - 86400000000 - ) - ) AS INT64) AS `origin_start_day`, - CASE - WHEN UNIX_MICROS( - CAST(TIMESTAMP_TRUNC(`datetime_col`, WEEK(MONDAY)) + INTERVAL 6 DAY AS TIMESTAMP) - ) = UNIX_MICROS( - CAST(TIMESTAMP_TRUNC(`timestamp_col`, WEEK(MONDAY)) + INTERVAL 6 DAY AS TIMESTAMP) - ) - THEN 0 - ELSE CAST(FLOOR( - IEEE_DIVIDE( - UNIX_MICROS( - CAST(TIMESTAMP_TRUNC(`datetime_col`, WEEK(MONDAY)) + INTERVAL 6 DAY AS TIMESTAMP) - ) - UNIX_MICROS( - CAST(TIMESTAMP_TRUNC(`timestamp_col`, WEEK(MONDAY)) + INTERVAL 6 DAY AS TIMESTAMP) - ) - 1, - 604800000000 - ) - ) AS INT64) + 1 - END AS `non_fixed_freq_weekly`, - CASE - WHEN ( - EXTRACT(YEAR FROM `datetime_col`) * 12 + EXTRACT(MONTH FROM `datetime_col`) - 1 - ) = ( - EXTRACT(YEAR FROM `timestamp_col`) * 12 + EXTRACT(MONTH FROM `timestamp_col`) - 1 - ) - THEN 0 - ELSE CAST(FLOOR( - IEEE_DIVIDE( - ( - EXTRACT(YEAR FROM `datetime_col`) * 12 + EXTRACT(MONTH FROM `datetime_col`) - 1 - ) - ( - EXTRACT(YEAR FROM `timestamp_col`) * 12 + EXTRACT(MONTH FROM `timestamp_col`) - 1 - ) - 1, - 1 - ) - ) AS INT64) + 1 - END AS `non_fixed_freq_monthly`, - CASE - WHEN ( - EXTRACT(YEAR FROM `datetime_col`) * 4 + EXTRACT(QUARTER FROM `datetime_col`) - 1 - ) = ( - EXTRACT(YEAR FROM `timestamp_col`) * 4 + EXTRACT(QUARTER FROM `timestamp_col`) - 1 - ) - THEN 0 - ELSE CAST(FLOOR( - IEEE_DIVIDE( - ( - EXTRACT(YEAR FROM `datetime_col`) * 4 + EXTRACT(QUARTER FROM `datetime_col`) - 1 - ) - ( - EXTRACT(YEAR FROM `timestamp_col`) * 4 + EXTRACT(QUARTER FROM `timestamp_col`) - 1 - ) - 1, - 1 - ) - ) AS INT64) + 1 - END AS `non_fixed_freq_quarterly`, - CASE - WHEN EXTRACT(YEAR FROM `datetime_col`) = EXTRACT(YEAR FROM `timestamp_col`) - THEN 0 - ELSE CAST(FLOOR( - IEEE_DIVIDE(EXTRACT(YEAR FROM `datetime_col`) - EXTRACT(YEAR FROM `timestamp_col`) - 1, 1) - ) AS INT64) + 1 - END AS `non_fixed_freq_yearly` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_datetime_ops/test_day/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_datetime_ops/test_day/out.sql deleted file mode 100644 index b9c030cb53e..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_datetime_ops/test_day/out.sql +++ /dev/null @@ -1,3 +0,0 @@ -SELECT - EXTRACT(DAY FROM `timestamp_col`) AS `timestamp_col` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_datetime_ops/test_dayofweek/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_datetime_ops/test_dayofweek/out.sql deleted file mode 100644 index a25d520d804..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_datetime_ops/test_dayofweek/out.sql +++ /dev/null @@ -1,5 +0,0 @@ -SELECT - CAST(MOD(EXTRACT(DAYOFWEEK FROM `datetime_col`) + 5, 7) AS INT64) AS `datetime_col`, - CAST(MOD(EXTRACT(DAYOFWEEK FROM `timestamp_col`) + 5, 7) AS INT64) AS `timestamp_col`, - CAST(MOD(EXTRACT(DAYOFWEEK FROM `date_col`) + 5, 7) AS INT64) AS `date_col` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_datetime_ops/test_dayofyear/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_datetime_ops/test_dayofyear/out.sql deleted file mode 100644 index 87a410911b5..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_datetime_ops/test_dayofyear/out.sql +++ /dev/null @@ -1,3 +0,0 @@ -SELECT - EXTRACT(DAYOFYEAR FROM `timestamp_col`) AS `timestamp_col` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_datetime_ops/test_floor_dt/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_datetime_ops/test_floor_dt/out.sql deleted file mode 100644 index 49fb8fe5749..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_datetime_ops/test_floor_dt/out.sql +++ /dev/null @@ -1,14 +0,0 @@ -SELECT - TIMESTAMP_TRUNC(`timestamp_col`, MICROSECOND) AS `timestamp_col_us`, - TIMESTAMP_TRUNC(`timestamp_col`, MILLISECOND) AS `timestamp_col_ms`, - TIMESTAMP_TRUNC(`timestamp_col`, SECOND) AS `timestamp_col_s`, - TIMESTAMP_TRUNC(`timestamp_col`, MINUTE) AS `timestamp_col_min`, - TIMESTAMP_TRUNC(`timestamp_col`, HOUR) AS `timestamp_col_h`, - TIMESTAMP_TRUNC(`timestamp_col`, DAY) AS `timestamp_col_D`, - TIMESTAMP_TRUNC(`timestamp_col`, WEEK(MONDAY)) AS `timestamp_col_W`, - TIMESTAMP_TRUNC(`timestamp_col`, MONTH) AS `timestamp_col_M`, - TIMESTAMP_TRUNC(`timestamp_col`, QUARTER) AS `timestamp_col_Q`, - TIMESTAMP_TRUNC(`timestamp_col`, YEAR) AS `timestamp_col_Y`, - TIMESTAMP_TRUNC(`datetime_col`, MICROSECOND) AS `datetime_col_q`, - TIMESTAMP_TRUNC(`datetime_col`, MICROSECOND) AS `datetime_col_us` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_datetime_ops/test_hour/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_datetime_ops/test_hour/out.sql deleted file mode 100644 index e971057f527..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_datetime_ops/test_hour/out.sql +++ /dev/null @@ -1,3 +0,0 @@ -SELECT - EXTRACT(HOUR FROM `timestamp_col`) AS `timestamp_col` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_datetime_ops/test_integer_label_to_datetime/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_datetime_ops/test_integer_label_to_datetime/out.sql deleted file mode 100644 index 2a1bd0e2e21..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_datetime_ops/test_integer_label_to_datetime/out.sql +++ /dev/null @@ -1,58 +0,0 @@ -WITH `bfcte_0` AS ( - SELECT - `rowindex`, - `timestamp_col` - FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` -), `bfcte_1` AS ( - SELECT - *, - CAST(TIMESTAMP_MICROS( - CAST(CAST(`rowindex` AS BIGNUMERIC) * 86400000000 + CAST(UNIX_MICROS(CAST(`timestamp_col` AS TIMESTAMP)) AS BIGNUMERIC) AS INT64) - ) AS TIMESTAMP) AS `bfcol_2`, - CAST(DATETIME( - CASE - WHEN ( - MOD( - `rowindex` * 1 + EXTRACT(YEAR FROM `timestamp_col`) * 4 + EXTRACT(QUARTER FROM `timestamp_col`) - 1, - 4 - ) + 1 - ) * 3 = 12 - THEN CAST(FLOOR( - IEEE_DIVIDE( - `rowindex` * 1 + EXTRACT(YEAR FROM `timestamp_col`) * 4 + EXTRACT(QUARTER FROM `timestamp_col`) - 1, - 4 - ) - ) AS INT64) + 1 - ELSE CAST(FLOOR( - IEEE_DIVIDE( - `rowindex` * 1 + EXTRACT(YEAR FROM `timestamp_col`) * 4 + EXTRACT(QUARTER FROM `timestamp_col`) - 1, - 4 - ) - ) AS INT64) - END, - CASE - WHEN ( - MOD( - `rowindex` * 1 + EXTRACT(YEAR FROM `timestamp_col`) * 4 + EXTRACT(QUARTER FROM `timestamp_col`) - 1, - 4 - ) + 1 - ) * 3 = 12 - THEN 1 - ELSE ( - MOD( - `rowindex` * 1 + EXTRACT(YEAR FROM `timestamp_col`) * 4 + EXTRACT(QUARTER FROM `timestamp_col`) - 1, - 4 - ) + 1 - ) * 3 + 1 - END, - 1, - 0, - 0, - 0 - ) - INTERVAL 1 DAY AS TIMESTAMP) AS `bfcol_3` - FROM `bfcte_0` -) -SELECT - `bfcol_2` AS `fixed_freq`, - `bfcol_3` AS `non_fixed_freq` -FROM `bfcte_1` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_datetime_ops/test_integer_label_to_datetime_fixed/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_datetime_ops/test_integer_label_to_datetime_fixed/out.sql deleted file mode 100644 index 244bd88deb7..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_datetime_ops/test_integer_label_to_datetime_fixed/out.sql +++ /dev/null @@ -1,5 +0,0 @@ -SELECT - CAST(TIMESTAMP_MICROS( - CAST(CAST(`rowindex` AS BIGNUMERIC) * 86400000000 + CAST(UNIX_MICROS(CAST(`timestamp_col` AS TIMESTAMP)) AS BIGNUMERIC) AS INT64) - ) AS TIMESTAMP) AS `fixed_freq` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_datetime_ops/test_integer_label_to_datetime_month/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_datetime_ops/test_integer_label_to_datetime_month/out.sql deleted file mode 100644 index 1ece688b91f..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_datetime_ops/test_integer_label_to_datetime_month/out.sql +++ /dev/null @@ -1,39 +0,0 @@ -SELECT - CAST(TIMESTAMP( - DATETIME( - CASE - WHEN MOD( - `rowindex` * 1 + EXTRACT(YEAR FROM `timestamp_col`) * 12 + EXTRACT(MONTH FROM `timestamp_col`) - 1, - 12 - ) + 1 = 12 - THEN CAST(FLOOR( - IEEE_DIVIDE( - `rowindex` * 1 + EXTRACT(YEAR FROM `timestamp_col`) * 12 + EXTRACT(MONTH FROM `timestamp_col`) - 1, - 12 - ) - ) AS INT64) + 1 - ELSE CAST(FLOOR( - IEEE_DIVIDE( - `rowindex` * 1 + EXTRACT(YEAR FROM `timestamp_col`) * 12 + EXTRACT(MONTH FROM `timestamp_col`) - 1, - 12 - ) - ) AS INT64) - END, - CASE - WHEN MOD( - `rowindex` * 1 + EXTRACT(YEAR FROM `timestamp_col`) * 12 + EXTRACT(MONTH FROM `timestamp_col`) - 1, - 12 - ) + 1 = 12 - THEN 1 - ELSE MOD( - `rowindex` * 1 + EXTRACT(YEAR FROM `timestamp_col`) * 12 + EXTRACT(MONTH FROM `timestamp_col`) - 1, - 12 - ) + 1 + 1 - END, - 1, - 0, - 0, - 0 - ) - ) - INTERVAL 1 DAY AS TIMESTAMP) AS `non_fixed_freq_monthly` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_datetime_ops/test_integer_label_to_datetime_quarter/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_datetime_ops/test_integer_label_to_datetime_quarter/out.sql deleted file mode 100644 index 683b26be91b..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_datetime_ops/test_integer_label_to_datetime_quarter/out.sql +++ /dev/null @@ -1,43 +0,0 @@ -SELECT - CAST(DATETIME( - CASE - WHEN ( - MOD( - `rowindex` * 1 + EXTRACT(YEAR FROM `timestamp_col`) * 4 + EXTRACT(QUARTER FROM `timestamp_col`) - 1, - 4 - ) + 1 - ) * 3 = 12 - THEN CAST(FLOOR( - IEEE_DIVIDE( - `rowindex` * 1 + EXTRACT(YEAR FROM `timestamp_col`) * 4 + EXTRACT(QUARTER FROM `timestamp_col`) - 1, - 4 - ) - ) AS INT64) + 1 - ELSE CAST(FLOOR( - IEEE_DIVIDE( - `rowindex` * 1 + EXTRACT(YEAR FROM `timestamp_col`) * 4 + EXTRACT(QUARTER FROM `timestamp_col`) - 1, - 4 - ) - ) AS INT64) - END, - CASE - WHEN ( - MOD( - `rowindex` * 1 + EXTRACT(YEAR FROM `timestamp_col`) * 4 + EXTRACT(QUARTER FROM `timestamp_col`) - 1, - 4 - ) + 1 - ) * 3 = 12 - THEN 1 - ELSE ( - MOD( - `rowindex` * 1 + EXTRACT(YEAR FROM `timestamp_col`) * 4 + EXTRACT(QUARTER FROM `timestamp_col`) - 1, - 4 - ) + 1 - ) * 3 + 1 - END, - 1, - 0, - 0, - 0 - ) - INTERVAL 1 DAY AS TIMESTAMP) AS `non_fixed_freq` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_datetime_ops/test_integer_label_to_datetime_week/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_datetime_ops/test_integer_label_to_datetime_week/out.sql deleted file mode 100644 index 6196e6976b0..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_datetime_ops/test_integer_label_to_datetime_week/out.sql +++ /dev/null @@ -1,7 +0,0 @@ -SELECT - CAST(TIMESTAMP_MICROS( - CAST(CAST(`rowindex` AS BIGNUMERIC) * 604800000000 + CAST(UNIX_MICROS( - TIMESTAMP_TRUNC(CAST(`timestamp_col` AS TIMESTAMP), WEEK(MONDAY)) + INTERVAL 6 DAY - ) AS BIGNUMERIC) AS INT64) - ) AS TIMESTAMP) AS `non_fixed_freq_weekly` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_datetime_ops/test_integer_label_to_datetime_year/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_datetime_ops/test_integer_label_to_datetime_year/out.sql deleted file mode 100644 index e0d05ec5b4b..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_datetime_ops/test_integer_label_to_datetime_year/out.sql +++ /dev/null @@ -1,3 +0,0 @@ -SELECT - CAST(TIMESTAMP(DATETIME(`rowindex` * 1 + EXTRACT(YEAR FROM `timestamp_col`) + 1, 1, 1, 0, 0, 0)) - INTERVAL 1 DAY AS TIMESTAMP) AS `non_fixed_freq_yearly` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_datetime_ops/test_iso_day/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_datetime_ops/test_iso_day/out.sql deleted file mode 100644 index bf7dfea7378..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_datetime_ops/test_iso_day/out.sql +++ /dev/null @@ -1,3 +0,0 @@ -SELECT - CAST(MOD(EXTRACT(DAYOFWEEK FROM `timestamp_col`) + 5, 7) AS INT64) + 1 AS `timestamp_col` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_datetime_ops/test_iso_week/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_datetime_ops/test_iso_week/out.sql deleted file mode 100644 index ce231592164..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_datetime_ops/test_iso_week/out.sql +++ /dev/null @@ -1,3 +0,0 @@ -SELECT - EXTRACT(ISOWEEK FROM `timestamp_col`) AS `timestamp_col` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_datetime_ops/test_iso_year/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_datetime_ops/test_iso_year/out.sql deleted file mode 100644 index aea4bec4371..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_datetime_ops/test_iso_year/out.sql +++ /dev/null @@ -1,3 +0,0 @@ -SELECT - EXTRACT(ISOYEAR FROM `timestamp_col`) AS `timestamp_col` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_datetime_ops/test_minute/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_datetime_ops/test_minute/out.sql deleted file mode 100644 index ed1ffcee104..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_datetime_ops/test_minute/out.sql +++ /dev/null @@ -1,3 +0,0 @@ -SELECT - EXTRACT(MINUTE FROM `timestamp_col`) AS `timestamp_col` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_datetime_ops/test_month/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_datetime_ops/test_month/out.sql deleted file mode 100644 index 8defb0312e9..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_datetime_ops/test_month/out.sql +++ /dev/null @@ -1,3 +0,0 @@ -SELECT - EXTRACT(MONTH FROM `timestamp_col`) AS `timestamp_col` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_datetime_ops/test_normalize/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_datetime_ops/test_normalize/out.sql deleted file mode 100644 index 0ae08c77ad0..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_datetime_ops/test_normalize/out.sql +++ /dev/null @@ -1,3 +0,0 @@ -SELECT - TIMESTAMP_TRUNC(`timestamp_col`, DAY) AS `timestamp_col` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_datetime_ops/test_quarter/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_datetime_ops/test_quarter/out.sql deleted file mode 100644 index 9426f685855..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_datetime_ops/test_quarter/out.sql +++ /dev/null @@ -1,3 +0,0 @@ -SELECT - EXTRACT(QUARTER FROM `timestamp_col`) AS `timestamp_col` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_datetime_ops/test_second/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_datetime_ops/test_second/out.sql deleted file mode 100644 index 953a0ff762a..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_datetime_ops/test_second/out.sql +++ /dev/null @@ -1,3 +0,0 @@ -SELECT - EXTRACT(SECOND FROM `timestamp_col`) AS `timestamp_col` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_datetime_ops/test_strftime/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_datetime_ops/test_strftime/out.sql deleted file mode 100644 index 308c040640d..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_datetime_ops/test_strftime/out.sql +++ /dev/null @@ -1,6 +0,0 @@ -SELECT - FORMAT_DATE('%Y-%m-%d', `date_col`) AS `date_col`, - FORMAT_DATETIME('%Y-%m-%d', `datetime_col`) AS `datetime_col`, - FORMAT_TIME('%Y-%m-%d', `time_col`) AS `time_col`, - FORMAT_TIMESTAMP('%Y-%m-%d', `timestamp_col`) AS `timestamp_col` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_datetime_ops/test_sub_timedelta/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_datetime_ops/test_sub_timedelta/out.sql deleted file mode 100644 index 5c8b130d59d..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_datetime_ops/test_sub_timedelta/out.sql +++ /dev/null @@ -1,11 +0,0 @@ -SELECT - `rowindex`, - `timestamp_col`, - `duration_col`, - `date_col`, - TIMESTAMP_SUB(CAST(`date_col` AS DATETIME), INTERVAL `duration_col` MICROSECOND) AS `date_sub_timedelta`, - TIMESTAMP_SUB(`timestamp_col`, INTERVAL `duration_col` MICROSECOND) AS `timestamp_sub_timedelta`, - TIMESTAMP_DIFF(CAST(`date_col` AS DATETIME), CAST(`date_col` AS DATETIME), MICROSECOND) AS `timestamp_sub_date`, - TIMESTAMP_DIFF(`timestamp_col`, `timestamp_col`, MICROSECOND) AS `date_sub_timestamp`, - `duration_col` - `duration_col` AS `timedelta_sub_timedelta` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_datetime_ops/test_time/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_datetime_ops/test_time/out.sql deleted file mode 100644 index e46ca373909..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_datetime_ops/test_time/out.sql +++ /dev/null @@ -1,3 +0,0 @@ -SELECT - TIME(`timestamp_col`) AS `timestamp_col` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_datetime_ops/test_to_datetime/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_datetime_ops/test_to_datetime/out.sql deleted file mode 100644 index 50142f20ba5..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_datetime_ops/test_to_datetime/out.sql +++ /dev/null @@ -1,7 +0,0 @@ -SELECT - DATETIME(TIMESTAMP_MICROS(CAST(TRUNC(`int64_col` * 0.001) AS INT64)), 'UTC') AS `int64_col`, - SAFE_CAST(`string_col` AS DATETIME) AS `string_col`, - DATETIME(TIMESTAMP_MICROS(CAST(TRUNC(`float64_col` * 0.001) AS INT64)), 'UTC') AS `float64_col`, - DATETIME(`timestamp_col`, 'UTC') AS `timestamp_col`, - SAFE_CAST(`string_col` AS DATETIME) AS `string_col_fmt` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_datetime_ops/test_to_timestamp/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_datetime_ops/test_to_timestamp/out.sql deleted file mode 100644 index e0fb530cc6d..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_datetime_ops/test_to_timestamp/out.sql +++ /dev/null @@ -1,10 +0,0 @@ -SELECT - CAST(TIMESTAMP_MICROS(CAST(TRUNC(`int64_col` * 0.001) AS INT64)) AS TIMESTAMP) AS `int64_col`, - CAST(TIMESTAMP_MICROS(CAST(TRUNC(`float64_col` * 0.001) AS INT64)) AS TIMESTAMP) AS `float64_col`, - CAST(TIMESTAMP_MICROS(CAST(TRUNC(`int64_col` * 1000000) AS INT64)) AS TIMESTAMP) AS `int64_col_s`, - CAST(TIMESTAMP_MICROS(CAST(TRUNC(`int64_col` * 1000) AS INT64)) AS TIMESTAMP) AS `int64_col_ms`, - CAST(TIMESTAMP_MICROS(CAST(TRUNC(`int64_col`) AS INT64)) AS TIMESTAMP) AS `int64_col_us`, - CAST(TIMESTAMP_MICROS(CAST(TRUNC(`int64_col` * 0.001) AS INT64)) AS TIMESTAMP) AS `int64_col_ns`, - TIMESTAMP(`datetime_col`) AS `datetime_col`, - PARSE_TIMESTAMP('%Y-%m-%d', `string_col`, 'UTC') AS `string_col_fmt` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_datetime_ops/test_unix_micros/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_datetime_ops/test_unix_micros/out.sql deleted file mode 100644 index a212164e6ce..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_datetime_ops/test_unix_micros/out.sql +++ /dev/null @@ -1,3 +0,0 @@ -SELECT - UNIX_MICROS(`timestamp_col`) AS `timestamp_col` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_datetime_ops/test_unix_millis/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_datetime_ops/test_unix_millis/out.sql deleted file mode 100644 index 8df5ad956a3..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_datetime_ops/test_unix_millis/out.sql +++ /dev/null @@ -1,3 +0,0 @@ -SELECT - UNIX_MILLIS(`timestamp_col`) AS `timestamp_col` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_datetime_ops/test_unix_seconds/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_datetime_ops/test_unix_seconds/out.sql deleted file mode 100644 index 7344ca82949..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_datetime_ops/test_unix_seconds/out.sql +++ /dev/null @@ -1,3 +0,0 @@ -SELECT - UNIX_SECONDS(`timestamp_col`) AS `timestamp_col` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_datetime_ops/test_year/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_datetime_ops/test_year/out.sql deleted file mode 100644 index f1a1d7085ef..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_datetime_ops/test_year/out.sql +++ /dev/null @@ -1,3 +0,0 @@ -SELECT - EXTRACT(YEAR FROM `timestamp_col`) AS `timestamp_col` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_generic_ops/test_astype_bool/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_generic_ops/test_astype_bool/out.sql deleted file mode 100644 index 2f75cf4cf7f..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_generic_ops/test_astype_bool/out.sql +++ /dev/null @@ -1,5 +0,0 @@ -SELECT - `bool_col`, - `float64_col` <> 0 AS `float64_col`, - `float64_col` <> 0 AS `float64_w_safe` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_generic_ops/test_astype_float/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_generic_ops/test_astype_float/out.sql deleted file mode 100644 index 7f7bd86084e..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_generic_ops/test_astype_float/out.sql +++ /dev/null @@ -1,5 +0,0 @@ -SELECT - CAST(CAST(`bool_col` AS INT64) AS FLOAT64) AS `bool_col`, - CAST('1.34235e4' AS FLOAT64) AS `str_const`, - SAFE_CAST(SAFE_CAST(`bool_col` AS INT64) AS FLOAT64) AS `bool_w_safe` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_generic_ops/test_astype_from_json/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_generic_ops/test_astype_from_json/out.sql deleted file mode 100644 index c9450a92800..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_generic_ops/test_astype_from_json/out.sql +++ /dev/null @@ -1,7 +0,0 @@ -SELECT - SAFE.INT64(`json_col`) AS `int64_col`, - SAFE.FLOAT64(`json_col`) AS `float64_col`, - SAFE.BOOL(`json_col`) AS `bool_col`, - SAFE.STRING(`json_col`) AS `string_col`, - SAFE.INT64(`json_col`) AS `int64_w_safe` -FROM `bigframes-dev`.`sqlglot_test`.`json_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_generic_ops/test_astype_int/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_generic_ops/test_astype_int/out.sql deleted file mode 100644 index 8d44c674dc9..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_generic_ops/test_astype_int/out.sql +++ /dev/null @@ -1,11 +0,0 @@ -SELECT - UNIX_MICROS(CAST(`datetime_col` AS TIMESTAMP)) AS `datetime_col`, - UNIX_MICROS(SAFE_CAST(`datetime_col` AS TIMESTAMP)) AS `datetime_w_safe`, - TIME_DIFF(CAST(`time_col` AS TIME), '00:00:00', MICROSECOND) AS `time_col`, - TIME_DIFF(SAFE_CAST(`time_col` AS TIME), '00:00:00', MICROSECOND) AS `time_w_safe`, - UNIX_MICROS(`timestamp_col`) AS `timestamp_col`, - CAST(TRUNC(`numeric_col`) AS INT64) AS `numeric_col`, - CAST(TRUNC(`float64_col`) AS INT64) AS `float64_col`, - SAFE_CAST(TRUNC(`float64_col`) AS INT64) AS `float64_w_safe`, - CAST('100' AS INT64) AS `str_const` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_generic_ops/test_astype_json/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_generic_ops/test_astype_json/out.sql deleted file mode 100644 index b62cee83a91..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_generic_ops/test_astype_json/out.sql +++ /dev/null @@ -1,8 +0,0 @@ -SELECT - PARSE_JSON(CAST(`int64_col` AS STRING)) AS `int64_col`, - PARSE_JSON(CAST(`float64_col` AS STRING)) AS `float64_col`, - PARSE_JSON(CAST(`bool_col` AS STRING)) AS `bool_col`, - PARSE_JSON(`string_col`) AS `string_col`, - PARSE_JSON(CAST(`bool_col` AS STRING)) AS `bool_w_safe`, - SAFE.PARSE_JSON(`string_col`) AS `string_w_safe` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_generic_ops/test_astype_string/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_generic_ops/test_astype_string/out.sql deleted file mode 100644 index 174f18d9823..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_generic_ops/test_astype_string/out.sql +++ /dev/null @@ -1,5 +0,0 @@ -SELECT - CAST(`int64_col` AS STRING) AS `int64_col`, - INITCAP(CAST(`bool_col` AS STRING)) AS `bool_col`, - INITCAP(SAFE_CAST(`bool_col` AS STRING)) AS `bool_w_safe` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_generic_ops/test_astype_time_like/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_generic_ops/test_astype_time_like/out.sql deleted file mode 100644 index f50505592bb..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_generic_ops/test_astype_time_like/out.sql +++ /dev/null @@ -1,6 +0,0 @@ -SELECT - CAST(TIMESTAMP_MICROS(`int64_col`) AS DATETIME) AS `int64_to_datetime`, - CAST(TIMESTAMP_MICROS(`int64_col`) AS TIME) AS `int64_to_time`, - CAST(TIMESTAMP_MICROS(`int64_col`) AS TIMESTAMP) AS `int64_to_timestamp`, - SAFE_CAST(TIMESTAMP_MICROS(`int64_col`) AS TIME) AS `int64_to_time_safe` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_generic_ops/test_binary_remote_function_op/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_generic_ops/test_binary_remote_function_op/out.sql deleted file mode 100644 index 29f9d69cb25..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_generic_ops/test_binary_remote_function_op/out.sql +++ /dev/null @@ -1,3 +0,0 @@ -SELECT - `my_project`.`my_dataset`.`my_routine`(`int64_col`, `float64_col`) AS `int64_col` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_generic_ops/test_case_when_op/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_generic_ops/test_case_when_op/out.sql deleted file mode 100644 index 58e901fecc0..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_generic_ops/test_case_when_op/out.sql +++ /dev/null @@ -1,13 +0,0 @@ -SELECT - CASE WHEN `bool_col` THEN `int64_col` END AS `single_case`, - CASE WHEN `bool_col` THEN `int64_col` WHEN `bool_col` THEN `int64_too` END AS `double_case`, - CASE WHEN `bool_col` THEN `bool_col` WHEN `bool_col` THEN `bool_col` END AS `bool_types_case`, - CASE - WHEN `bool_col` - THEN `int64_col` - WHEN `bool_col` - THEN CAST(`bool_col` AS INT64) - WHEN `bool_col` - THEN `float64_col` - END AS `mixed_types_cast` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_generic_ops/test_clip/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_generic_ops/test_clip/out.sql deleted file mode 100644 index bbfeb304181..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_generic_ops/test_clip/out.sql +++ /dev/null @@ -1,3 +0,0 @@ -SELECT - GREATEST(LEAST(`rowindex`, `int64_too`), `int64_col`) AS `result_col` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_generic_ops/test_coalesce/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_generic_ops/test_coalesce/out.sql deleted file mode 100644 index 4f88ec71d88..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_generic_ops/test_coalesce/out.sql +++ /dev/null @@ -1,4 +0,0 @@ -SELECT - `int64_col`, - COALESCE(`int64_too`, `int64_col`) AS `int64_too` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_generic_ops/test_fillna/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_generic_ops/test_fillna/out.sql deleted file mode 100644 index ae6f975da5a..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_generic_ops/test_fillna/out.sql +++ /dev/null @@ -1,3 +0,0 @@ -SELECT - COALESCE(`int64_col`, `float64_col`) AS `int64_col` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_generic_ops/test_hash/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_generic_ops/test_hash/out.sql deleted file mode 100644 index b1afe9db39b..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_generic_ops/test_hash/out.sql +++ /dev/null @@ -1,3 +0,0 @@ -SELECT - FARM_FINGERPRINT(`string_col`) AS `string_col` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_generic_ops/test_invert/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_generic_ops/test_invert/out.sql deleted file mode 100644 index 5cd1b15a776..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_generic_ops/test_invert/out.sql +++ /dev/null @@ -1,11 +0,0 @@ -SELECT - ~( - `int64_col` - ) AS `int64_col`, - ~( - `bytes_col` - ) AS `bytes_col`, - NOT ( - `bool_col` - ) AS `bool_col` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_generic_ops/test_isnull/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_generic_ops/test_isnull/out.sql deleted file mode 100644 index cfe38ae3600..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_generic_ops/test_isnull/out.sql +++ /dev/null @@ -1,5 +0,0 @@ -SELECT - ( - `float64_col` - ) IS NULL AS `float64_col` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_generic_ops/test_map/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_generic_ops/test_map/out.sql deleted file mode 100644 index 3b1d0446b3b..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_generic_ops/test_map/out.sql +++ /dev/null @@ -1,9 +0,0 @@ -SELECT - CASE - WHEN `string_col` = 'value1' - THEN 'mapped1' - WHEN `string_col` IS NULL - THEN 'UNKNOWN' - ELSE `string_col` - END AS `string_col` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_generic_ops/test_nary_remote_function_op/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_generic_ops/test_nary_remote_function_op/out.sql deleted file mode 100644 index a1977d809f7..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_generic_ops/test_nary_remote_function_op/out.sql +++ /dev/null @@ -1,3 +0,0 @@ -SELECT - `my_project`.`my_dataset`.`my_routine`(`int64_col`, `float64_col`, `string_col`) AS `int64_col` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_generic_ops/test_notnull/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_generic_ops/test_notnull/out.sql deleted file mode 100644 index 97b9f54f429..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_generic_ops/test_notnull/out.sql +++ /dev/null @@ -1,5 +0,0 @@ -SELECT - ( - `float64_col` - ) IS NOT NULL AS `float64_col` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_generic_ops/test_remote_function_op/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_generic_ops/test_remote_function_op/out.sql deleted file mode 100644 index a1977d809f7..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_generic_ops/test_remote_function_op/out.sql +++ /dev/null @@ -1,3 +0,0 @@ -SELECT - `my_project`.`my_dataset`.`my_routine`(`int64_col`, `float64_col`, `string_col`) AS `int64_col` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_generic_ops/test_row_key/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_generic_ops/test_row_key/out.sql deleted file mode 100644 index f5bf9b3b6ee..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_generic_ops/test_row_key/out.sql +++ /dev/null @@ -1,46 +0,0 @@ -SELECT - CONCAT( - CAST(FARM_FINGERPRINT( - CONCAT( - CONCAT('\\', REPLACE(COALESCE(CAST(`rowindex` AS STRING), ''), '\\', '\\\\')), - CONCAT('\\', REPLACE(COALESCE(CAST(`bool_col` AS STRING), ''), '\\', '\\\\')), - CONCAT('\\', REPLACE(COALESCE(CAST(`bytes_col` AS STRING), ''), '\\', '\\\\')), - CONCAT('\\', REPLACE(COALESCE(CAST(`date_col` AS STRING), ''), '\\', '\\\\')), - CONCAT('\\', REPLACE(COALESCE(CAST(`datetime_col` AS STRING), ''), '\\', '\\\\')), - CONCAT('\\', REPLACE(COALESCE(ST_ASTEXT(`geography_col`), ''), '\\', '\\\\')), - CONCAT('\\', REPLACE(COALESCE(CAST(`int64_col` AS STRING), ''), '\\', '\\\\')), - CONCAT('\\', REPLACE(COALESCE(CAST(`int64_too` AS STRING), ''), '\\', '\\\\')), - CONCAT('\\', REPLACE(COALESCE(CAST(`numeric_col` AS STRING), ''), '\\', '\\\\')), - CONCAT('\\', REPLACE(COALESCE(CAST(`float64_col` AS STRING), ''), '\\', '\\\\')), - CONCAT('\\', REPLACE(COALESCE(CAST(`rowindex` AS STRING), ''), '\\', '\\\\')), - CONCAT('\\', REPLACE(COALESCE(CAST(`rowindex_2` AS STRING), ''), '\\', '\\\\')), - CONCAT('\\', REPLACE(COALESCE(`string_col`, ''), '\\', '\\\\')), - CONCAT('\\', REPLACE(COALESCE(CAST(`time_col` AS STRING), ''), '\\', '\\\\')), - CONCAT('\\', REPLACE(COALESCE(CAST(`timestamp_col` AS STRING), ''), '\\', '\\\\')), - CONCAT('\\', REPLACE(COALESCE(CAST(`duration_col` AS STRING), ''), '\\', '\\\\')) - ) - ) AS STRING), - CAST(FARM_FINGERPRINT( - CONCAT( - CONCAT('\\', REPLACE(COALESCE(CAST(`rowindex` AS STRING), ''), '\\', '\\\\')), - CONCAT('\\', REPLACE(COALESCE(CAST(`bool_col` AS STRING), ''), '\\', '\\\\')), - CONCAT('\\', REPLACE(COALESCE(CAST(`bytes_col` AS STRING), ''), '\\', '\\\\')), - CONCAT('\\', REPLACE(COALESCE(CAST(`date_col` AS STRING), ''), '\\', '\\\\')), - CONCAT('\\', REPLACE(COALESCE(CAST(`datetime_col` AS STRING), ''), '\\', '\\\\')), - CONCAT('\\', REPLACE(COALESCE(ST_ASTEXT(`geography_col`), ''), '\\', '\\\\')), - CONCAT('\\', REPLACE(COALESCE(CAST(`int64_col` AS STRING), ''), '\\', '\\\\')), - CONCAT('\\', REPLACE(COALESCE(CAST(`int64_too` AS STRING), ''), '\\', '\\\\')), - CONCAT('\\', REPLACE(COALESCE(CAST(`numeric_col` AS STRING), ''), '\\', '\\\\')), - CONCAT('\\', REPLACE(COALESCE(CAST(`float64_col` AS STRING), ''), '\\', '\\\\')), - CONCAT('\\', REPLACE(COALESCE(CAST(`rowindex` AS STRING), ''), '\\', '\\\\')), - CONCAT('\\', REPLACE(COALESCE(CAST(`rowindex_2` AS STRING), ''), '\\', '\\\\')), - CONCAT('\\', REPLACE(COALESCE(`string_col`, ''), '\\', '\\\\')), - CONCAT('\\', REPLACE(COALESCE(CAST(`time_col` AS STRING), ''), '\\', '\\\\')), - CONCAT('\\', REPLACE(COALESCE(CAST(`timestamp_col` AS STRING), ''), '\\', '\\\\')), - CONCAT('\\', REPLACE(COALESCE(CAST(`duration_col` AS STRING), ''), '\\', '\\\\')), - '_' - ) - ) AS STRING), - CAST(RAND() AS STRING) - ) AS `row_key` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_generic_ops/test_sql_scalar_op/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_generic_ops/test_sql_scalar_op/out.sql deleted file mode 100644 index 8f50ff28ca4..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_generic_ops/test_sql_scalar_op/out.sql +++ /dev/null @@ -1,3 +0,0 @@ -SELECT - CAST(`bool_col` AS INT64) + BYTE_LENGTH(`bytes_col`) AS `bool_col` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_generic_ops/test_to_json/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_generic_ops/test_to_json/out.sql deleted file mode 100644 index 86d6f0e9fbb..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_generic_ops/test_to_json/out.sql +++ /dev/null @@ -1,8 +0,0 @@ -SELECT - IF(`int64_col` IS NULL, NULL, TO_JSON(`int64_col`)) AS `int64_col`, - IF(`float64_col` IS NULL, NULL, TO_JSON(`float64_col`)) AS `float64_col`, - IF(`bool_col` IS NULL, NULL, TO_JSON(`bool_col`)) AS `bool_col`, - SAFE.PARSE_JSON(`string_col`) AS `string_col`, - IF(`bool_col` IS NULL, NULL, TO_JSON(`bool_col`)) AS `bool_w_safe`, - SAFE.PARSE_JSON(`string_col`) AS `string_w_safe` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_generic_ops/test_where/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_generic_ops/test_where/out.sql deleted file mode 100644 index 1ca3b009898..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_generic_ops/test_where/out.sql +++ /dev/null @@ -1,3 +0,0 @@ -SELECT - IF(`bool_col`, `int64_col`, `float64_col`) AS `result_col` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_geo_ops/test_geo_area/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_geo_ops/test_geo_area/out.sql deleted file mode 100644 index 78c786b036e..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_geo_ops/test_geo_area/out.sql +++ /dev/null @@ -1,3 +0,0 @@ -SELECT - ST_AREA(`geography_col`) AS `geography_col` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_geo_ops/test_geo_st_astext/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_geo_ops/test_geo_st_astext/out.sql deleted file mode 100644 index 526c0c37d7e..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_geo_ops/test_geo_st_astext/out.sql +++ /dev/null @@ -1,3 +0,0 @@ -SELECT - ST_ASTEXT(`geography_col`) AS `geography_col` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_geo_ops/test_geo_st_boundary/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_geo_ops/test_geo_st_boundary/out.sql deleted file mode 100644 index 4bf43469cf5..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_geo_ops/test_geo_st_boundary/out.sql +++ /dev/null @@ -1,3 +0,0 @@ -SELECT - ST_BOUNDARY(`geography_col`) AS `geography_col` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_geo_ops/test_geo_st_buffer/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_geo_ops/test_geo_st_buffer/out.sql deleted file mode 100644 index 40669569fbb..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_geo_ops/test_geo_st_buffer/out.sql +++ /dev/null @@ -1,3 +0,0 @@ -SELECT - ST_BUFFER(`geography_col`, 1.0, 8.0, FALSE) AS `geography_col` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_geo_ops/test_geo_st_centroid/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_geo_ops/test_geo_st_centroid/out.sql deleted file mode 100644 index accd33bd627..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_geo_ops/test_geo_st_centroid/out.sql +++ /dev/null @@ -1,3 +0,0 @@ -SELECT - ST_CENTROID(`geography_col`) AS `geography_col` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_geo_ops/test_geo_st_convexhull/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_geo_ops/test_geo_st_convexhull/out.sql deleted file mode 100644 index e4a718d42a9..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_geo_ops/test_geo_st_convexhull/out.sql +++ /dev/null @@ -1,3 +0,0 @@ -SELECT - ST_CONVEXHULL(`geography_col`) AS `geography_col` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_geo_ops/test_geo_st_difference/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_geo_ops/test_geo_st_difference/out.sql deleted file mode 100644 index 2a17ef1c7c8..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_geo_ops/test_geo_st_difference/out.sql +++ /dev/null @@ -1,3 +0,0 @@ -SELECT - ST_DIFFERENCE(`geography_col`, `geography_col`) AS `geography_col` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_geo_ops/test_geo_st_distance/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_geo_ops/test_geo_st_distance/out.sql deleted file mode 100644 index 4c55ddd0824..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_geo_ops/test_geo_st_distance/out.sql +++ /dev/null @@ -1,4 +0,0 @@ -SELECT - ST_DISTANCE(`geography_col`, `geography_col`, TRUE) AS `spheroid`, - ST_DISTANCE(`geography_col`, `geography_col`, FALSE) AS `no_spheroid` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_geo_ops/test_geo_st_geogfromtext/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_geo_ops/test_geo_st_geogfromtext/out.sql deleted file mode 100644 index db62766d4c9..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_geo_ops/test_geo_st_geogfromtext/out.sql +++ /dev/null @@ -1,3 +0,0 @@ -SELECT - SAFE.ST_GEOGFROMTEXT(`string_col`) AS `string_col` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_geo_ops/test_geo_st_geogpoint/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_geo_ops/test_geo_st_geogpoint/out.sql deleted file mode 100644 index 3299ef0bd2d..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_geo_ops/test_geo_st_geogpoint/out.sql +++ /dev/null @@ -1,3 +0,0 @@ -SELECT - ST_GEOGPOINT(`rowindex`, `rowindex_2`) AS `rowindex` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_geo_ops/test_geo_st_intersection/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_geo_ops/test_geo_st_intersection/out.sql deleted file mode 100644 index a615ddf042e..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_geo_ops/test_geo_st_intersection/out.sql +++ /dev/null @@ -1,3 +0,0 @@ -SELECT - ST_INTERSECTION(`geography_col`, `geography_col`) AS `geography_col` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_geo_ops/test_geo_st_isclosed/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_geo_ops/test_geo_st_isclosed/out.sql deleted file mode 100644 index 4f04e70b569..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_geo_ops/test_geo_st_isclosed/out.sql +++ /dev/null @@ -1,3 +0,0 @@ -SELECT - ST_ISCLOSED(`geography_col`) AS `geography_col` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_geo_ops/test_geo_st_length/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_geo_ops/test_geo_st_length/out.sql deleted file mode 100644 index ee64b20ca46..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_geo_ops/test_geo_st_length/out.sql +++ /dev/null @@ -1,3 +0,0 @@ -SELECT - ST_LENGTH(`geography_col`) AS `geography_col` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_geo_ops/test_geo_x/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_geo_ops/test_geo_x/out.sql deleted file mode 100644 index c1ab623d9a1..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_geo_ops/test_geo_x/out.sql +++ /dev/null @@ -1,3 +0,0 @@ -SELECT - ST_X(`geography_col`) AS `geography_col` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_geo_ops/test_geo_y/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_geo_ops/test_geo_y/out.sql deleted file mode 100644 index e7575606e6e..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_geo_ops/test_geo_y/out.sql +++ /dev/null @@ -1,3 +0,0 @@ -SELECT - ST_Y(`geography_col`) AS `geography_col` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_json_ops/test_json_extract/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_json_ops/test_json_extract/out.sql deleted file mode 100644 index 7a7ad2f394d..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_json_ops/test_json_extract/out.sql +++ /dev/null @@ -1,3 +0,0 @@ -SELECT - JSON_EXTRACT(`json_col`, '$') AS `json_col` -FROM `bigframes-dev`.`sqlglot_test`.`json_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_json_ops/test_json_extract_array/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_json_ops/test_json_extract_array/out.sql deleted file mode 100644 index f2c4cd72985..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_json_ops/test_json_extract_array/out.sql +++ /dev/null @@ -1,3 +0,0 @@ -SELECT - JSON_EXTRACT_ARRAY(`json_col`, '$') AS `json_col` -FROM `bigframes-dev`.`sqlglot_test`.`json_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_json_ops/test_json_extract_string_array/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_json_ops/test_json_extract_string_array/out.sql deleted file mode 100644 index 61e8bae8a32..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_json_ops/test_json_extract_string_array/out.sql +++ /dev/null @@ -1,3 +0,0 @@ -SELECT - JSON_EXTRACT_STRING_ARRAY(`json_col`, '$') AS `json_col` -FROM `bigframes-dev`.`sqlglot_test`.`json_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_json_ops/test_json_keys/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_json_ops/test_json_keys/out.sql deleted file mode 100644 index 78004c1180c..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_json_ops/test_json_keys/out.sql +++ /dev/null @@ -1,4 +0,0 @@ -SELECT - JSON_KEYS(`json_col`, NULL) AS `json_keys`, - JSON_KEYS(`json_col`, 2) AS `json_keys_w_max_depth` -FROM `bigframes-dev`.`sqlglot_test`.`json_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_json_ops/test_json_query/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_json_ops/test_json_query/out.sql deleted file mode 100644 index 8aa312e9d75..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_json_ops/test_json_query/out.sql +++ /dev/null @@ -1,3 +0,0 @@ -SELECT - JSON_QUERY(`json_col`, '$') AS `json_col` -FROM `bigframes-dev`.`sqlglot_test`.`json_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_json_ops/test_json_query_array/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_json_ops/test_json_query_array/out.sql deleted file mode 100644 index 898068fe595..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_json_ops/test_json_query_array/out.sql +++ /dev/null @@ -1,3 +0,0 @@ -SELECT - JSON_QUERY_ARRAY(`json_col`, '$') AS `json_col` -FROM `bigframes-dev`.`sqlglot_test`.`json_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_json_ops/test_json_set/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_json_ops/test_json_set/out.sql deleted file mode 100644 index e515d5fdc3b..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_json_ops/test_json_set/out.sql +++ /dev/null @@ -1,3 +0,0 @@ -SELECT - JSON_SET(`json_col`, '$.a', 100) AS `json_col` -FROM `bigframes-dev`.`sqlglot_test`.`json_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_json_ops/test_json_value/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_json_ops/test_json_value/out.sql deleted file mode 100644 index c9a73ae1942..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_json_ops/test_json_value/out.sql +++ /dev/null @@ -1,3 +0,0 @@ -SELECT - JSON_VALUE(`json_col`, '$') AS `json_col` -FROM `bigframes-dev`.`sqlglot_test`.`json_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_json_ops/test_json_value_array/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_json_ops/test_json_value_array/out.sql deleted file mode 100644 index 8250c02934e..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_json_ops/test_json_value_array/out.sql +++ /dev/null @@ -1,3 +0,0 @@ -SELECT - JSON_VALUE_ARRAY(`json_col`, '$') AS `json_col` -FROM `bigframes-dev`.`sqlglot_test`.`json_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_json_ops/test_parse_json/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_json_ops/test_parse_json/out.sql deleted file mode 100644 index 55a195edf20..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_json_ops/test_parse_json/out.sql +++ /dev/null @@ -1,3 +0,0 @@ -SELECT - PARSE_JSON(`string_col`) AS `string_col` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_json_ops/test_to_json/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_json_ops/test_to_json/out.sql deleted file mode 100644 index 0545577e27f..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_json_ops/test_to_json/out.sql +++ /dev/null @@ -1,3 +0,0 @@ -SELECT - SAFE.PARSE_JSON(`string_col`) AS `string_col` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_json_ops/test_to_json_string/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_json_ops/test_to_json_string/out.sql deleted file mode 100644 index 62886c26ed9..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_json_ops/test_to_json_string/out.sql +++ /dev/null @@ -1,3 +0,0 @@ -SELECT - TO_JSON_STRING(`json_col`) AS `json_col` -FROM `bigframes-dev`.`sqlglot_test`.`json_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_literals/test_float_literals/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_literals/test_float_literals/out.sql deleted file mode 100644 index 030e733edd7..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_literals/test_float_literals/out.sql +++ /dev/null @@ -1,8 +0,0 @@ -SELECT - CAST('Infinity' AS FLOAT64) AS `inf`, - CAST('-Infinity' AS FLOAT64) AS `ninf`, - NULL AS `nan`, - -0.0 AS `neg_zero`, - 1e-05 AS `0.00001`, - 1e-10 AS `1E-10` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_numeric_ops/test_abs/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_numeric_ops/test_abs/out.sql deleted file mode 100644 index bc53c60895a..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_numeric_ops/test_abs/out.sql +++ /dev/null @@ -1,3 +0,0 @@ -SELECT - ABS(`float64_col`) AS `float64_col` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_numeric_ops/test_add_numeric/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_numeric_ops/test_add_numeric/out.sql deleted file mode 100644 index 3aa06fe16e3..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_numeric_ops/test_add_numeric/out.sql +++ /dev/null @@ -1,10 +0,0 @@ -SELECT - `rowindex`, - `int64_col`, - `bool_col`, - `int64_col` + `int64_col` AS `int_add_int`, - `int64_col` + 1 AS `int_add_1`, - NULL AS `int_add_null`, - `int64_col` + CAST(`bool_col` AS INT64) AS `int_add_bool`, - CAST(`bool_col` AS INT64) + `int64_col` AS `bool_add_int` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_numeric_ops/test_add_string/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_numeric_ops/test_add_string/out.sql deleted file mode 100644 index cf4051464b7..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_numeric_ops/test_add_string/out.sql +++ /dev/null @@ -1,3 +0,0 @@ -SELECT - CONCAT(`string_col`, 'a') AS `string_col` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_numeric_ops/test_add_timedelta/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_numeric_ops/test_add_timedelta/out.sql deleted file mode 100644 index b1ccf096cfa..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_numeric_ops/test_add_timedelta/out.sql +++ /dev/null @@ -1,10 +0,0 @@ -SELECT - `rowindex`, - `timestamp_col`, - `date_col`, - TIMESTAMP_ADD(CAST(`date_col` AS DATETIME), INTERVAL 86400000000 MICROSECOND) AS `date_add_timedelta`, - TIMESTAMP_ADD(`timestamp_col`, INTERVAL 86400000000 MICROSECOND) AS `timestamp_add_timedelta`, - TIMESTAMP_ADD(CAST(`date_col` AS DATETIME), INTERVAL 86400000000 MICROSECOND) AS `timedelta_add_date`, - TIMESTAMP_ADD(`timestamp_col`, INTERVAL 86400000000 MICROSECOND) AS `timedelta_add_timestamp`, - 172800000000 AS `timedelta_add_timedelta` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_numeric_ops/test_arccos/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_numeric_ops/test_arccos/out.sql deleted file mode 100644 index d00086dfde8..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_numeric_ops/test_arccos/out.sql +++ /dev/null @@ -1,7 +0,0 @@ -SELECT - CASE - WHEN ABS(`float64_col`) > 1 - THEN CAST('NaN' AS FLOAT64) - ELSE ACOS(`float64_col`) - END AS `float64_col` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_numeric_ops/test_arccosh/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_numeric_ops/test_arccosh/out.sql deleted file mode 100644 index f1a04757a0a..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_numeric_ops/test_arccosh/out.sql +++ /dev/null @@ -1,7 +0,0 @@ -SELECT - CASE - WHEN `float64_col` < 1 - THEN CAST('NaN' AS FLOAT64) - ELSE ACOSH(`float64_col`) - END AS `float64_col` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_numeric_ops/test_arcsin/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_numeric_ops/test_arcsin/out.sql deleted file mode 100644 index eff8f1f5007..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_numeric_ops/test_arcsin/out.sql +++ /dev/null @@ -1,7 +0,0 @@ -SELECT - CASE - WHEN ABS(`float64_col`) > 1 - THEN CAST('NaN' AS FLOAT64) - ELSE ASIN(`float64_col`) - END AS `float64_col` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_numeric_ops/test_arcsinh/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_numeric_ops/test_arcsinh/out.sql deleted file mode 100644 index 557407f09ee..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_numeric_ops/test_arcsinh/out.sql +++ /dev/null @@ -1,3 +0,0 @@ -SELECT - ASINH(`float64_col`) AS `float64_col` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_numeric_ops/test_arctan/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_numeric_ops/test_arctan/out.sql deleted file mode 100644 index d99b62f2cdb..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_numeric_ops/test_arctan/out.sql +++ /dev/null @@ -1,3 +0,0 @@ -SELECT - ATAN(`float64_col`) AS `float64_col` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_numeric_ops/test_arctan2/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_numeric_ops/test_arctan2/out.sql deleted file mode 100644 index 463896e981f..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_numeric_ops/test_arctan2/out.sql +++ /dev/null @@ -1,4 +0,0 @@ -SELECT - ATAN2(`int64_col`, `float64_col`) AS `int64_col`, - ATAN2(CAST(`bool_col` AS INT64), `float64_col`) AS `bool_col` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_numeric_ops/test_arctanh/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_numeric_ops/test_arctanh/out.sql deleted file mode 100644 index 9b016071480..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_numeric_ops/test_arctanh/out.sql +++ /dev/null @@ -1,9 +0,0 @@ -SELECT - CASE - WHEN ABS(`float64_col`) < 1 - THEN ATANH(`float64_col`) - WHEN ABS(`float64_col`) > 1 - THEN CAST('NaN' AS FLOAT64) - ELSE CAST('Infinity' AS FLOAT64) * `float64_col` - END AS `float64_col` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_numeric_ops/test_ceil/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_numeric_ops/test_ceil/out.sql deleted file mode 100644 index f69ae7f2760..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_numeric_ops/test_ceil/out.sql +++ /dev/null @@ -1,3 +0,0 @@ -SELECT - CEIL(`float64_col`) AS `float64_col` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_numeric_ops/test_cos/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_numeric_ops/test_cos/out.sql deleted file mode 100644 index 427dfbb9a93..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_numeric_ops/test_cos/out.sql +++ /dev/null @@ -1,3 +0,0 @@ -SELECT - COS(`float64_col`) AS `float64_col` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_numeric_ops/test_cosh/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_numeric_ops/test_cosh/out.sql deleted file mode 100644 index 0f119c254f0..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_numeric_ops/test_cosh/out.sql +++ /dev/null @@ -1,7 +0,0 @@ -SELECT - CASE - WHEN ABS(`float64_col`) > 709.78 - THEN CAST('Infinity' AS FLOAT64) - ELSE COSH(`float64_col`) - END AS `float64_col` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_numeric_ops/test_cosine_distance/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_numeric_ops/test_cosine_distance/out.sql deleted file mode 100644 index 1c482fc8a78..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_numeric_ops/test_cosine_distance/out.sql +++ /dev/null @@ -1,4 +0,0 @@ -SELECT - ML.DISTANCE(`int_list_col`, `int_list_col`, 'COSINE') AS `int_list_col`, - ML.DISTANCE(`float_list_col`, `float_list_col`, 'COSINE') AS `float_list_col` -FROM `bigframes-dev`.`sqlglot_test`.`repeated_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_numeric_ops/test_div_numeric/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_numeric_ops/test_div_numeric/out.sql deleted file mode 100644 index e2ccf96410a..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_numeric_ops/test_div_numeric/out.sql +++ /dev/null @@ -1,15 +0,0 @@ -SELECT - `rowindex`, - `int64_col`, - `bool_col`, - `float64_col`, - IEEE_DIVIDE(`int64_col`, `int64_col`) AS `int_div_int`, - IEEE_DIVIDE(`int64_col`, 1) AS `int_div_1`, - IEEE_DIVIDE(`int64_col`, 0.0) AS `int_div_0`, - NULL AS `int_div_null`, - IEEE_DIVIDE(`int64_col`, `float64_col`) AS `int_div_float`, - IEEE_DIVIDE(`float64_col`, `int64_col`) AS `float_div_int`, - IEEE_DIVIDE(`float64_col`, 0.0) AS `float_div_0`, - IEEE_DIVIDE(`int64_col`, CAST(`bool_col` AS INT64)) AS `int_div_bool`, - IEEE_DIVIDE(CAST(`bool_col` AS INT64), `int64_col`) AS `bool_div_int` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_numeric_ops/test_div_timedelta/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_numeric_ops/test_div_timedelta/out.sql deleted file mode 100644 index a733ed81278..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_numeric_ops/test_div_timedelta/out.sql +++ /dev/null @@ -1,10 +0,0 @@ -SELECT - `rowindex`, - `timestamp_col`, - `int64_col`, - CAST(IF( - IEEE_DIVIDE(86400000000, `int64_col`) > 0, - FLOOR(IEEE_DIVIDE(86400000000, `int64_col`)), - CEIL(IEEE_DIVIDE(86400000000, `int64_col`)) - ) AS INT64) AS `timedelta_div_numeric` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_numeric_ops/test_euclidean_distance/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_numeric_ops/test_euclidean_distance/out.sql deleted file mode 100644 index 349d78584a9..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_numeric_ops/test_euclidean_distance/out.sql +++ /dev/null @@ -1,4 +0,0 @@ -SELECT - ML.DISTANCE(`int_list_col`, `int_list_col`, 'EUCLIDEAN') AS `int_list_col`, - ML.DISTANCE(`numeric_list_col`, `numeric_list_col`, 'EUCLIDEAN') AS `numeric_list_col` -FROM `bigframes-dev`.`sqlglot_test`.`repeated_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_numeric_ops/test_exp/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_numeric_ops/test_exp/out.sql deleted file mode 100644 index 178282ca087..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_numeric_ops/test_exp/out.sql +++ /dev/null @@ -1,7 +0,0 @@ -SELECT - CASE - WHEN `float64_col` > 709.78 - THEN CAST('Infinity' AS FLOAT64) - ELSE EXP(`float64_col`) - END AS `float64_col` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_numeric_ops/test_expm1/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_numeric_ops/test_expm1/out.sql deleted file mode 100644 index 6c896448f24..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_numeric_ops/test_expm1/out.sql +++ /dev/null @@ -1,3 +0,0 @@ -SELECT - IF(`float64_col` > 709.78, CAST('Infinity' AS FLOAT64), EXP(`float64_col`) - 1) AS `float64_col` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_numeric_ops/test_floor/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_numeric_ops/test_floor/out.sql deleted file mode 100644 index 31b715623cb..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_numeric_ops/test_floor/out.sql +++ /dev/null @@ -1,3 +0,0 @@ -SELECT - FLOOR(`float64_col`) AS `float64_col` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_numeric_ops/test_floordiv_numeric/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_numeric_ops/test_floordiv_numeric/out.sql deleted file mode 100644 index 8307b1b8ada..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_numeric_ops/test_floordiv_numeric/out.sql +++ /dev/null @@ -1,48 +0,0 @@ -SELECT - `rowindex`, - `int64_col`, - `bool_col`, - `float64_col`, - CASE - WHEN `int64_col` = CAST(0 AS INT64) - THEN CAST(0 AS INT64) * `int64_col` - ELSE CAST(FLOOR(IEEE_DIVIDE(`int64_col`, `int64_col`)) AS INT64) - END AS `int_div_int`, - CASE - WHEN 1 = CAST(0 AS INT64) - THEN CAST(0 AS INT64) * `int64_col` - ELSE CAST(FLOOR(IEEE_DIVIDE(`int64_col`, 1)) AS INT64) - END AS `int_div_1`, - CASE - WHEN 0.0 = CAST(0 AS INT64) - THEN CAST('Infinity' AS FLOAT64) * `int64_col` - ELSE CAST(FLOOR(IEEE_DIVIDE(`int64_col`, 0.0)) AS INT64) - END AS `int_div_0`, - NULL AS `int_div_null`, - CASE - WHEN `float64_col` = CAST(0 AS INT64) - THEN CAST('Infinity' AS FLOAT64) * `int64_col` - ELSE CAST(FLOOR(IEEE_DIVIDE(`int64_col`, `float64_col`)) AS INT64) - END AS `int_div_float`, - CASE - WHEN `int64_col` = CAST(0 AS INT64) - THEN CAST('Infinity' AS FLOAT64) * `float64_col` - ELSE CAST(FLOOR(IEEE_DIVIDE(`float64_col`, `int64_col`)) AS INT64) - END AS `float_div_int`, - CASE - WHEN 0.0 = CAST(0 AS INT64) - THEN CAST('Infinity' AS FLOAT64) * `float64_col` - ELSE CAST(FLOOR(IEEE_DIVIDE(`float64_col`, 0.0)) AS INT64) - END AS `float_div_0`, - NULL AS `float_div_null`, - CASE - WHEN CAST(`bool_col` AS INT64) = CAST(0 AS INT64) - THEN CAST(0 AS INT64) * `int64_col` - ELSE CAST(FLOOR(IEEE_DIVIDE(`int64_col`, CAST(`bool_col` AS INT64))) AS INT64) - END AS `int_div_bool`, - CASE - WHEN `int64_col` = CAST(0 AS INT64) - THEN CAST(0 AS INT64) * CAST(`bool_col` AS INT64) - ELSE CAST(FLOOR(IEEE_DIVIDE(CAST(`bool_col` AS INT64), `int64_col`)) AS INT64) - END AS `bool_div_int` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_numeric_ops/test_floordiv_timedelta/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_numeric_ops/test_floordiv_timedelta/out.sql deleted file mode 100644 index 4d978991eb5..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_numeric_ops/test_floordiv_timedelta/out.sql +++ /dev/null @@ -1,6 +0,0 @@ -SELECT - `rowindex`, - `timestamp_col`, - `date_col`, - 43200000000 AS `timedelta_div_numeric` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_numeric_ops/test_isfinite/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_numeric_ops/test_isfinite/out.sql deleted file mode 100644 index 54cbe2dd689..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_numeric_ops/test_isfinite/out.sql +++ /dev/null @@ -1,3 +0,0 @@ -SELECT - NOT IS_INF(`float64_col`) OR IS_NAN(`float64_col`) AS `float64_col` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_numeric_ops/test_ln/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_numeric_ops/test_ln/out.sql deleted file mode 100644 index 53ab88b7fc3..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_numeric_ops/test_ln/out.sql +++ /dev/null @@ -1,11 +0,0 @@ -SELECT - CASE - WHEN `float64_col` IS NULL - THEN NULL - WHEN `float64_col` > 0 - THEN LN(`float64_col`) - WHEN `float64_col` < 0 - THEN CAST('NaN' AS FLOAT64) - ELSE CAST('-Infinity' AS FLOAT64) - END AS `float64_col` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_numeric_ops/test_log10/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_numeric_ops/test_log10/out.sql deleted file mode 100644 index 2037649332f..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_numeric_ops/test_log10/out.sql +++ /dev/null @@ -1,11 +0,0 @@ -SELECT - CASE - WHEN `float64_col` IS NULL - THEN NULL - WHEN `float64_col` > 0 - THEN LOG(`float64_col`, 10) - WHEN `float64_col` < 0 - THEN CAST('NaN' AS FLOAT64) - ELSE CAST('-Infinity' AS FLOAT64) - END AS `float64_col` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_numeric_ops/test_log1p/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_numeric_ops/test_log1p/out.sql deleted file mode 100644 index f7ddf4c223f..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_numeric_ops/test_log1p/out.sql +++ /dev/null @@ -1,11 +0,0 @@ -SELECT - CASE - WHEN `float64_col` IS NULL - THEN NULL - WHEN `float64_col` > -1 - THEN LN(1 + `float64_col`) - WHEN `float64_col` < -1 - THEN CAST('NaN' AS FLOAT64) - ELSE CAST('-Infinity' AS FLOAT64) - END AS `float64_col` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_numeric_ops/test_manhattan_distance/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_numeric_ops/test_manhattan_distance/out.sql deleted file mode 100644 index b6132a9fd6e..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_numeric_ops/test_manhattan_distance/out.sql +++ /dev/null @@ -1,4 +0,0 @@ -SELECT - ML.DISTANCE(`float_list_col`, `float_list_col`, 'MANHATTAN') AS `float_list_col`, - ML.DISTANCE(`numeric_list_col`, `numeric_list_col`, 'MANHATTAN') AS `numeric_list_col` -FROM `bigframes-dev`.`sqlglot_test`.`repeated_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_numeric_ops/test_mod_numeric/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_numeric_ops/test_mod_numeric/out.sql deleted file mode 100644 index 78107415b43..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_numeric_ops/test_mod_numeric/out.sql +++ /dev/null @@ -1,194 +0,0 @@ -SELECT - `rowindex`, - `int64_col`, - `float64_col`, - CASE - WHEN `int64_col` = CAST(0 AS INT64) - THEN CAST(0 AS INT64) * `int64_col` - WHEN `int64_col` < CAST(0 AS INT64) - AND ( - MOD(`int64_col`, `int64_col`) - ) > CAST(0 AS INT64) - THEN `int64_col` + ( - MOD(`int64_col`, `int64_col`) - ) - WHEN `int64_col` > CAST(0 AS INT64) - AND ( - MOD(`int64_col`, `int64_col`) - ) < CAST(0 AS INT64) - THEN `int64_col` + ( - MOD(`int64_col`, `int64_col`) - ) - ELSE MOD(`int64_col`, `int64_col`) - END AS `int_mod_int`, - CASE - WHEN -( - `int64_col` - ) = CAST(0 AS INT64) - THEN CAST(0 AS INT64) * `int64_col` - WHEN -( - `int64_col` - ) < CAST(0 AS INT64) - AND ( - MOD(`int64_col`, -( - `int64_col` - )) - ) > CAST(0 AS INT64) - THEN -( - `int64_col` - ) + ( - MOD(`int64_col`, -( - `int64_col` - )) - ) - WHEN -( - `int64_col` - ) > CAST(0 AS INT64) - AND ( - MOD(`int64_col`, -( - `int64_col` - )) - ) < CAST(0 AS INT64) - THEN -( - `int64_col` - ) + ( - MOD(`int64_col`, -( - `int64_col` - )) - ) - ELSE MOD(`int64_col`, -( - `int64_col` - )) - END AS `int_mod_int_neg`, - CASE - WHEN 1 = CAST(0 AS INT64) - THEN CAST(0 AS INT64) * `int64_col` - WHEN 1 < CAST(0 AS INT64) AND ( - MOD(`int64_col`, 1) - ) > CAST(0 AS INT64) - THEN 1 + ( - MOD(`int64_col`, 1) - ) - WHEN 1 > CAST(0 AS INT64) AND ( - MOD(`int64_col`, 1) - ) < CAST(0 AS INT64) - THEN 1 + ( - MOD(`int64_col`, 1) - ) - ELSE MOD(`int64_col`, 1) - END AS `int_mod_1`, - CASE - WHEN 0 = CAST(0 AS INT64) - THEN CAST(0 AS INT64) * `int64_col` - WHEN 0 < CAST(0 AS INT64) AND ( - MOD(`int64_col`, 0) - ) > CAST(0 AS INT64) - THEN 0 + ( - MOD(`int64_col`, 0) - ) - WHEN 0 > CAST(0 AS INT64) AND ( - MOD(`int64_col`, 0) - ) < CAST(0 AS INT64) - THEN 0 + ( - MOD(`int64_col`, 0) - ) - ELSE MOD(`int64_col`, 0) - END AS `int_mod_0`, - CASE - WHEN CAST(`float64_col` AS BIGNUMERIC) = CAST(0 AS INT64) - THEN CAST('NaN' AS FLOAT64) * CAST(`float64_col` AS BIGNUMERIC) - WHEN CAST(`float64_col` AS BIGNUMERIC) < CAST(0 AS INT64) - AND ( - MOD(CAST(`float64_col` AS BIGNUMERIC), CAST(`float64_col` AS BIGNUMERIC)) - ) > CAST(0 AS INT64) - THEN CAST(`float64_col` AS BIGNUMERIC) + ( - MOD(CAST(`float64_col` AS BIGNUMERIC), CAST(`float64_col` AS BIGNUMERIC)) - ) - WHEN CAST(`float64_col` AS BIGNUMERIC) > CAST(0 AS INT64) - AND ( - MOD(CAST(`float64_col` AS BIGNUMERIC), CAST(`float64_col` AS BIGNUMERIC)) - ) < CAST(0 AS INT64) - THEN CAST(`float64_col` AS BIGNUMERIC) + ( - MOD(CAST(`float64_col` AS BIGNUMERIC), CAST(`float64_col` AS BIGNUMERIC)) - ) - ELSE MOD(CAST(`float64_col` AS BIGNUMERIC), CAST(`float64_col` AS BIGNUMERIC)) - END AS `float_mod_float`, - CASE - WHEN CAST(-( - `float64_col` - ) AS BIGNUMERIC) = CAST(0 AS INT64) - THEN CAST('NaN' AS FLOAT64) * CAST(`float64_col` AS BIGNUMERIC) - WHEN CAST(-( - `float64_col` - ) AS BIGNUMERIC) < CAST(0 AS INT64) - AND ( - MOD(CAST(`float64_col` AS BIGNUMERIC), CAST(-( - `float64_col` - ) AS BIGNUMERIC)) - ) > CAST(0 AS INT64) - THEN CAST(-( - `float64_col` - ) AS BIGNUMERIC) + ( - MOD(CAST(`float64_col` AS BIGNUMERIC), CAST(-( - `float64_col` - ) AS BIGNUMERIC)) - ) - WHEN CAST(-( - `float64_col` - ) AS BIGNUMERIC) > CAST(0 AS INT64) - AND ( - MOD(CAST(`float64_col` AS BIGNUMERIC), CAST(-( - `float64_col` - ) AS BIGNUMERIC)) - ) < CAST(0 AS INT64) - THEN CAST(-( - `float64_col` - ) AS BIGNUMERIC) + ( - MOD(CAST(`float64_col` AS BIGNUMERIC), CAST(-( - `float64_col` - ) AS BIGNUMERIC)) - ) - ELSE MOD(CAST(`float64_col` AS BIGNUMERIC), CAST(-( - `float64_col` - ) AS BIGNUMERIC)) - END AS `float_mod_float_neg`, - CASE - WHEN CAST(1 AS BIGNUMERIC) = CAST(0 AS INT64) - THEN CAST('NaN' AS FLOAT64) * CAST(`float64_col` AS BIGNUMERIC) - WHEN CAST(1 AS BIGNUMERIC) < CAST(0 AS INT64) - AND ( - MOD(CAST(`float64_col` AS BIGNUMERIC), CAST(1 AS BIGNUMERIC)) - ) > CAST(0 AS INT64) - THEN CAST(1 AS BIGNUMERIC) + ( - MOD(CAST(`float64_col` AS BIGNUMERIC), CAST(1 AS BIGNUMERIC)) - ) - WHEN CAST(1 AS BIGNUMERIC) > CAST(0 AS INT64) - AND ( - MOD(CAST(`float64_col` AS BIGNUMERIC), CAST(1 AS BIGNUMERIC)) - ) < CAST(0 AS INT64) - THEN CAST(1 AS BIGNUMERIC) + ( - MOD(CAST(`float64_col` AS BIGNUMERIC), CAST(1 AS BIGNUMERIC)) - ) - ELSE MOD(CAST(`float64_col` AS BIGNUMERIC), CAST(1 AS BIGNUMERIC)) - END AS `float_mod_1`, - CASE - WHEN CAST(0 AS BIGNUMERIC) = CAST(0 AS INT64) - THEN CAST('NaN' AS FLOAT64) * CAST(`float64_col` AS BIGNUMERIC) - WHEN CAST(0 AS BIGNUMERIC) < CAST(0 AS INT64) - AND ( - MOD(CAST(`float64_col` AS BIGNUMERIC), CAST(0 AS BIGNUMERIC)) - ) > CAST(0 AS INT64) - THEN CAST(0 AS BIGNUMERIC) + ( - MOD(CAST(`float64_col` AS BIGNUMERIC), CAST(0 AS BIGNUMERIC)) - ) - WHEN CAST(0 AS BIGNUMERIC) > CAST(0 AS INT64) - AND ( - MOD(CAST(`float64_col` AS BIGNUMERIC), CAST(0 AS BIGNUMERIC)) - ) < CAST(0 AS INT64) - THEN CAST(0 AS BIGNUMERIC) + ( - MOD(CAST(`float64_col` AS BIGNUMERIC), CAST(0 AS BIGNUMERIC)) - ) - ELSE MOD(CAST(`float64_col` AS BIGNUMERIC), CAST(0 AS BIGNUMERIC)) - END AS `float_mod_0`, - NULL AS `float_mod_null` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_numeric_ops/test_mul_numeric/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_numeric_ops/test_mul_numeric/out.sql deleted file mode 100644 index ebe8d571d65..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_numeric_ops/test_mul_numeric/out.sql +++ /dev/null @@ -1,10 +0,0 @@ -SELECT - `rowindex`, - `int64_col`, - `bool_col`, - `int64_col` * `int64_col` AS `int_mul_int`, - `int64_col` * 1 AS `int_mul_1`, - NULL AS `int_mul_null`, - `int64_col` * CAST(`bool_col` AS INT64) AS `int_mul_bool`, - CAST(`bool_col` AS INT64) * `int64_col` AS `bool_mul_int` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_numeric_ops/test_mul_timedelta/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_numeric_ops/test_mul_timedelta/out.sql deleted file mode 100644 index 8285d1e7d4a..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_numeric_ops/test_mul_timedelta/out.sql +++ /dev/null @@ -1,16 +0,0 @@ -SELECT - `rowindex`, - `timestamp_col`, - `int64_col`, - `duration_col`, - CAST(IF( - `duration_col` * `int64_col` > 0, - FLOOR(`duration_col` * `int64_col`), - CEIL(`duration_col` * `int64_col`) - ) AS INT64) AS `timedelta_mul_numeric`, - CAST(IF( - `int64_col` * `duration_col` > 0, - FLOOR(`int64_col` * `duration_col`), - CEIL(`int64_col` * `duration_col`) - ) AS INT64) AS `numeric_mul_timedelta` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_numeric_ops/test_neg/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_numeric_ops/test_neg/out.sql deleted file mode 100644 index 13a9f3f6734..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_numeric_ops/test_neg/out.sql +++ /dev/null @@ -1,5 +0,0 @@ -SELECT - -( - `float64_col` - ) AS `float64_col` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_numeric_ops/test_pos/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_numeric_ops/test_pos/out.sql deleted file mode 100644 index 1890218bdd9..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_numeric_ops/test_pos/out.sql +++ /dev/null @@ -1,3 +0,0 @@ -SELECT - `float64_col` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_numeric_ops/test_pow/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_numeric_ops/test_pow/out.sql deleted file mode 100644 index 7202903ebe3..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_numeric_ops/test_pow/out.sql +++ /dev/null @@ -1,247 +0,0 @@ -SELECT - `rowindex`, - `int64_col`, - `float64_col`, - CASE - WHEN `int64_col` <> 0 AND `int64_col` * LN(ABS(`int64_col`)) > 43.66827237527655 - THEN NULL - ELSE CAST(POWER(CAST(`int64_col` AS NUMERIC), `int64_col`) AS INT64) - END AS `int_pow_int`, - CASE - WHEN `float64_col` = CAST(0 AS INT64) - THEN 1 - WHEN `int64_col` = 1 - THEN 1 - WHEN `int64_col` = CAST(0 AS INT64) AND `float64_col` < CAST(0 AS INT64) - THEN CAST('Infinity' AS FLOAT64) - WHEN ABS(`int64_col`) = CAST('Infinity' AS FLOAT64) - THEN POWER( - `int64_col`, - CASE - WHEN ABS(`float64_col`) > 9007199254740992 - THEN CAST('Infinity' AS FLOAT64) * SIGN(`float64_col`) - ELSE `float64_col` - END - ) - WHEN ABS(`float64_col`) > 9007199254740992 - THEN POWER( - `int64_col`, - CASE - WHEN ABS(`float64_col`) > 9007199254740992 - THEN CAST('Infinity' AS FLOAT64) * SIGN(`float64_col`) - ELSE `float64_col` - END - ) - WHEN `int64_col` < CAST(0 AS INT64) - AND NOT ( - CAST(`float64_col` AS INT64) = `float64_col` - ) - THEN CAST('NaN' AS FLOAT64) - WHEN `int64_col` <> CAST(0 AS INT64) AND `float64_col` * LN(ABS(`int64_col`)) > 709.78 - THEN CAST('Infinity' AS FLOAT64) * CASE - WHEN `int64_col` < CAST(0 AS INT64) AND MOD(CAST(`float64_col` AS INT64), 2) = 1 - THEN -1 - ELSE 1 - END - ELSE POWER( - `int64_col`, - CASE - WHEN ABS(`float64_col`) > 9007199254740992 - THEN CAST('Infinity' AS FLOAT64) * SIGN(`float64_col`) - ELSE `float64_col` - END - ) - END AS `int_pow_float`, - CASE - WHEN `int64_col` = CAST(0 AS INT64) - THEN 1 - WHEN `float64_col` = 1 - THEN 1 - WHEN `float64_col` = CAST(0 AS INT64) AND `int64_col` < CAST(0 AS INT64) - THEN CAST('Infinity' AS FLOAT64) - WHEN ABS(`float64_col`) = CAST('Infinity' AS FLOAT64) - THEN POWER( - `float64_col`, - CASE - WHEN ABS(`int64_col`) > 9007199254740992 - THEN CAST('Infinity' AS FLOAT64) * SIGN(`int64_col`) - ELSE `int64_col` - END - ) - WHEN ABS(`int64_col`) > 9007199254740992 - THEN POWER( - `float64_col`, - CASE - WHEN ABS(`int64_col`) > 9007199254740992 - THEN CAST('Infinity' AS FLOAT64) * SIGN(`int64_col`) - ELSE `int64_col` - END - ) - WHEN `float64_col` < CAST(0 AS INT64) - AND NOT ( - CAST(`int64_col` AS INT64) = `int64_col` - ) - THEN CAST('NaN' AS FLOAT64) - WHEN `float64_col` <> CAST(0 AS INT64) - AND `int64_col` * LN(ABS(`float64_col`)) > 709.78 - THEN CAST('Infinity' AS FLOAT64) * CASE - WHEN `float64_col` < CAST(0 AS INT64) AND MOD(CAST(`int64_col` AS INT64), 2) = 1 - THEN -1 - ELSE 1 - END - ELSE POWER( - `float64_col`, - CASE - WHEN ABS(`int64_col`) > 9007199254740992 - THEN CAST('Infinity' AS FLOAT64) * SIGN(`int64_col`) - ELSE `int64_col` - END - ) - END AS `float_pow_int`, - CASE - WHEN `float64_col` = CAST(0 AS INT64) - THEN 1 - WHEN `float64_col` = 1 - THEN 1 - WHEN `float64_col` = CAST(0 AS INT64) AND `float64_col` < CAST(0 AS INT64) - THEN CAST('Infinity' AS FLOAT64) - WHEN ABS(`float64_col`) = CAST('Infinity' AS FLOAT64) - THEN POWER( - `float64_col`, - CASE - WHEN ABS(`float64_col`) > 9007199254740992 - THEN CAST('Infinity' AS FLOAT64) * SIGN(`float64_col`) - ELSE `float64_col` - END - ) - WHEN ABS(`float64_col`) > 9007199254740992 - THEN POWER( - `float64_col`, - CASE - WHEN ABS(`float64_col`) > 9007199254740992 - THEN CAST('Infinity' AS FLOAT64) * SIGN(`float64_col`) - ELSE `float64_col` - END - ) - WHEN `float64_col` < CAST(0 AS INT64) - AND NOT ( - CAST(`float64_col` AS INT64) = `float64_col` - ) - THEN CAST('NaN' AS FLOAT64) - WHEN `float64_col` <> CAST(0 AS INT64) - AND `float64_col` * LN(ABS(`float64_col`)) > 709.78 - THEN CAST('Infinity' AS FLOAT64) * CASE - WHEN `float64_col` < CAST(0 AS INT64) AND MOD(CAST(`float64_col` AS INT64), 2) = 1 - THEN -1 - ELSE 1 - END - ELSE POWER( - `float64_col`, - CASE - WHEN ABS(`float64_col`) > 9007199254740992 - THEN CAST('Infinity' AS FLOAT64) * SIGN(`float64_col`) - ELSE `float64_col` - END - ) - END AS `float_pow_float`, - CASE - WHEN `int64_col` <> 0 AND 0 * LN(ABS(`int64_col`)) > 43.66827237527655 - THEN NULL - ELSE CAST(POWER(CAST(`int64_col` AS NUMERIC), 0) AS INT64) - END AS `int_pow_0`, - CASE - WHEN 0 = CAST(0 AS INT64) - THEN 1 - WHEN `float64_col` = 1 - THEN 1 - WHEN `float64_col` = CAST(0 AS INT64) AND 0 < CAST(0 AS INT64) - THEN CAST('Infinity' AS FLOAT64) - WHEN ABS(`float64_col`) = CAST('Infinity' AS FLOAT64) - THEN POWER( - `float64_col`, - CASE - WHEN ABS(0) > 9007199254740992 - THEN CAST('Infinity' AS FLOAT64) * SIGN(0) - ELSE 0 - END - ) - WHEN ABS(0) > 9007199254740992 - THEN POWER( - `float64_col`, - CASE - WHEN ABS(0) > 9007199254740992 - THEN CAST('Infinity' AS FLOAT64) * SIGN(0) - ELSE 0 - END - ) - WHEN `float64_col` < CAST(0 AS INT64) AND NOT ( - CAST(0 AS INT64) = 0 - ) - THEN CAST('NaN' AS FLOAT64) - WHEN `float64_col` <> CAST(0 AS INT64) AND 0 * LN(ABS(`float64_col`)) > 709.78 - THEN CAST('Infinity' AS FLOAT64) * CASE - WHEN `float64_col` < CAST(0 AS INT64) AND MOD(CAST(0 AS INT64), 2) = 1 - THEN -1 - ELSE 1 - END - ELSE POWER( - `float64_col`, - CASE - WHEN ABS(0) > 9007199254740992 - THEN CAST('Infinity' AS FLOAT64) * SIGN(0) - ELSE 0 - END - ) - END AS `float_pow_0`, - CASE - WHEN `int64_col` <> 0 AND 1 * LN(ABS(`int64_col`)) > 43.66827237527655 - THEN NULL - ELSE CAST(POWER(CAST(`int64_col` AS NUMERIC), 1) AS INT64) - END AS `int_pow_1`, - CASE - WHEN 1 = CAST(0 AS INT64) - THEN 1 - WHEN `float64_col` = 1 - THEN 1 - WHEN `float64_col` = CAST(0 AS INT64) AND 1 < CAST(0 AS INT64) - THEN CAST('Infinity' AS FLOAT64) - WHEN ABS(`float64_col`) = CAST('Infinity' AS FLOAT64) - THEN POWER( - `float64_col`, - CASE - WHEN ABS(1) > 9007199254740992 - THEN CAST('Infinity' AS FLOAT64) * SIGN(1) - ELSE 1 - END - ) - WHEN ABS(1) > 9007199254740992 - THEN POWER( - `float64_col`, - CASE - WHEN ABS(1) > 9007199254740992 - THEN CAST('Infinity' AS FLOAT64) * SIGN(1) - ELSE 1 - END - ) - WHEN `float64_col` < CAST(0 AS INT64) AND NOT ( - CAST(1 AS INT64) = 1 - ) - THEN CAST('NaN' AS FLOAT64) - WHEN `float64_col` <> CAST(0 AS INT64) AND 1 * LN(ABS(`float64_col`)) > 709.78 - THEN CAST('Infinity' AS FLOAT64) * CASE - WHEN `float64_col` < CAST(0 AS INT64) AND MOD(CAST(1 AS INT64), 2) = 1 - THEN -1 - ELSE 1 - END - ELSE POWER( - `float64_col`, - CASE - WHEN ABS(1) > 9007199254740992 - THEN CAST('Infinity' AS FLOAT64) * SIGN(1) - ELSE 1 - END - ) - END AS `float_pow_1`, - NULL AS `float_pow_null`, - NULL AS `null_pow_float` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_numeric_ops/test_round/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_numeric_ops/test_round/out.sql deleted file mode 100644 index 9ac8e1065b5..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_numeric_ops/test_round/out.sql +++ /dev/null @@ -1,11 +0,0 @@ -SELECT - `rowindex`, - `int64_col`, - `float64_col`, - CAST(ROUND(`int64_col`, 0) AS INT64) AS `int_round_0`, - CAST(ROUND(`int64_col`, 1) AS INT64) AS `int_round_1`, - CAST(ROUND(`int64_col`, -1) AS INT64) AS `int_round_m1`, - ROUND(`float64_col`, 0) AS `float_round_0`, - ROUND(`float64_col`, 1) AS `float_round_1`, - ROUND(`float64_col`, -1) AS `float_round_m1` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_numeric_ops/test_sin/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_numeric_ops/test_sin/out.sql deleted file mode 100644 index ddc7cfab6ce..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_numeric_ops/test_sin/out.sql +++ /dev/null @@ -1,3 +0,0 @@ -SELECT - SIN(`float64_col`) AS `float64_col` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_numeric_ops/test_sinh/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_numeric_ops/test_sinh/out.sql deleted file mode 100644 index a1d71a7a065..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_numeric_ops/test_sinh/out.sql +++ /dev/null @@ -1,7 +0,0 @@ -SELECT - CASE - WHEN ABS(`float64_col`) > 709.78 - THEN SIGN(`float64_col`) * CAST('Infinity' AS FLOAT64) - ELSE SINH(`float64_col`) - END AS `float64_col` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_numeric_ops/test_sqrt/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_numeric_ops/test_sqrt/out.sql deleted file mode 100644 index 6162a69d571..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_numeric_ops/test_sqrt/out.sql +++ /dev/null @@ -1,3 +0,0 @@ -SELECT - CASE WHEN `float64_col` < 0 THEN CAST('NaN' AS FLOAT64) ELSE SQRT(`float64_col`) END AS `float64_col` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_numeric_ops/test_sub_numeric/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_numeric_ops/test_sub_numeric/out.sql deleted file mode 100644 index c1d0350a664..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_numeric_ops/test_sub_numeric/out.sql +++ /dev/null @@ -1,10 +0,0 @@ -SELECT - `rowindex`, - `int64_col`, - `bool_col`, - `int64_col` - `int64_col` AS `int_sub_int`, - `int64_col` - 1 AS `int_sub_1`, - NULL AS `int_sub_null`, - `int64_col` - CAST(`bool_col` AS INT64) AS `int_sub_bool`, - CAST(`bool_col` AS INT64) - `int64_col` AS `bool_sub_int` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_numeric_ops/test_sub_timedelta/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_numeric_ops/test_sub_timedelta/out.sql deleted file mode 100644 index 5c8b130d59d..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_numeric_ops/test_sub_timedelta/out.sql +++ /dev/null @@ -1,11 +0,0 @@ -SELECT - `rowindex`, - `timestamp_col`, - `duration_col`, - `date_col`, - TIMESTAMP_SUB(CAST(`date_col` AS DATETIME), INTERVAL `duration_col` MICROSECOND) AS `date_sub_timedelta`, - TIMESTAMP_SUB(`timestamp_col`, INTERVAL `duration_col` MICROSECOND) AS `timestamp_sub_timedelta`, - TIMESTAMP_DIFF(CAST(`date_col` AS DATETIME), CAST(`date_col` AS DATETIME), MICROSECOND) AS `timestamp_sub_date`, - TIMESTAMP_DIFF(`timestamp_col`, `timestamp_col`, MICROSECOND) AS `date_sub_timestamp`, - `duration_col` - `duration_col` AS `timedelta_sub_timedelta` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_numeric_ops/test_tan/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_numeric_ops/test_tan/out.sql deleted file mode 100644 index 138b5f84a3b..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_numeric_ops/test_tan/out.sql +++ /dev/null @@ -1,3 +0,0 @@ -SELECT - TAN(`float64_col`) AS `float64_col` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_numeric_ops/test_tanh/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_numeric_ops/test_tanh/out.sql deleted file mode 100644 index c5db31c9579..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_numeric_ops/test_tanh/out.sql +++ /dev/null @@ -1,3 +0,0 @@ -SELECT - TANH(`float64_col`) AS `float64_col` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_numeric_ops/test_unsafe_pow_op/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_numeric_ops/test_unsafe_pow_op/out.sql deleted file mode 100644 index 0795b64a209..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_numeric_ops/test_unsafe_pow_op/out.sql +++ /dev/null @@ -1,14 +0,0 @@ -SELECT - POWER(`int64_col`, `int64_col`) AS `int_pow_int`, - POWER(`int64_col`, `float64_col`) AS `int_pow_float`, - POWER(`float64_col`, `int64_col`) AS `float_pow_int`, - POWER(`float64_col`, `float64_col`) AS `float_pow_float`, - POWER(`int64_col`, CAST(`bool_col` AS INT64)) AS `int_pow_bool`, - POWER(CAST(`bool_col` AS INT64), `int64_col`) AS `bool_pow_int` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` -WHERE - ( - `int64_col` >= 0 - ) AND ( - `int64_col` <= 10 - ) \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_string_ops/test_add_string/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_string_ops/test_add_string/out.sql deleted file mode 100644 index cf4051464b7..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_string_ops/test_add_string/out.sql +++ /dev/null @@ -1,3 +0,0 @@ -SELECT - CONCAT(`string_col`, 'a') AS `string_col` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_string_ops/test_capitalize/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_string_ops/test_capitalize/out.sql deleted file mode 100644 index d11ce9b9934..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_string_ops/test_capitalize/out.sql +++ /dev/null @@ -1,3 +0,0 @@ -SELECT - INITCAP(`string_col`, '') AS `string_col` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_string_ops/test_endswith/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_string_ops/test_endswith/out.sql deleted file mode 100644 index 0295af27992..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_string_ops/test_endswith/out.sql +++ /dev/null @@ -1,5 +0,0 @@ -SELECT - ENDS_WITH(`string_col`, 'ab') AS `single`, - ENDS_WITH(`string_col`, 'ab') OR ENDS_WITH(`string_col`, 'cd') AS `double`, - FALSE AS `empty` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_string_ops/test_isalnum/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_string_ops/test_isalnum/out.sql deleted file mode 100644 index 7654299c79e..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_string_ops/test_isalnum/out.sql +++ /dev/null @@ -1,3 +0,0 @@ -SELECT - REGEXP_CONTAINS(`string_col`, '^(\\p{N}|\\p{L})+$') AS `string_col` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_string_ops/test_isalpha/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_string_ops/test_isalpha/out.sql deleted file mode 100644 index 33a08ff054d..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_string_ops/test_isalpha/out.sql +++ /dev/null @@ -1,3 +0,0 @@ -SELECT - REGEXP_CONTAINS(`string_col`, '^\\p{L}+$') AS `string_col` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_string_ops/test_isdecimal/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_string_ops/test_isdecimal/out.sql deleted file mode 100644 index 7f266cbf604..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_string_ops/test_isdecimal/out.sql +++ /dev/null @@ -1,3 +0,0 @@ -SELECT - REGEXP_CONTAINS(`string_col`, '^(\\p{Nd})+$') AS `string_col` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_string_ops/test_isdigit/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_string_ops/test_isdigit/out.sql deleted file mode 100644 index 9134d035153..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_string_ops/test_isdigit/out.sql +++ /dev/null @@ -1,6 +0,0 @@ -SELECT - REGEXP_CONTAINS( - `string_col`, - '^[\\p{Nd}\\x{00B9}\\x{00B2}\\x{00B3}\\x{2070}\\x{2074}-\\x{2079}\\x{2080}-\\x{2089}]+$' - ) AS `string_col` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_string_ops/test_islower/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_string_ops/test_islower/out.sql deleted file mode 100644 index bce92035e5c..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_string_ops/test_islower/out.sql +++ /dev/null @@ -1,3 +0,0 @@ -SELECT - LOWER(`string_col`) = `string_col` AND UPPER(`string_col`) <> `string_col` AS `string_col` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_string_ops/test_isnumeric/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_string_ops/test_isnumeric/out.sql deleted file mode 100644 index 82baa081f54..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_string_ops/test_isnumeric/out.sql +++ /dev/null @@ -1,3 +0,0 @@ -SELECT - REGEXP_CONTAINS(`string_col`, '^\\pN+$') AS `string_col` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_string_ops/test_isspace/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_string_ops/test_isspace/out.sql deleted file mode 100644 index 2b44a592d93..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_string_ops/test_isspace/out.sql +++ /dev/null @@ -1,3 +0,0 @@ -SELECT - REGEXP_CONTAINS(`string_col`, '^\\s+$') AS `string_col` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_string_ops/test_isupper/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_string_ops/test_isupper/out.sql deleted file mode 100644 index 17ac14ac53f..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_string_ops/test_isupper/out.sql +++ /dev/null @@ -1,3 +0,0 @@ -SELECT - UPPER(`string_col`) = `string_col` AND LOWER(`string_col`) <> `string_col` AS `string_col` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_string_ops/test_len/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_string_ops/test_len/out.sql deleted file mode 100644 index cff109d09dc..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_string_ops/test_len/out.sql +++ /dev/null @@ -1,3 +0,0 @@ -SELECT - LENGTH(`string_col`) AS `string_col` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_string_ops/test_len_w_array/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_string_ops/test_len_w_array/out.sql deleted file mode 100644 index 1862deb6015..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_string_ops/test_len_w_array/out.sql +++ /dev/null @@ -1,3 +0,0 @@ -SELECT - ARRAY_LENGTH(`int_list_col`) AS `int_list_col` -FROM `bigframes-dev`.`sqlglot_test`.`repeated_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_string_ops/test_lower/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_string_ops/test_lower/out.sql deleted file mode 100644 index 851de35ecb0..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_string_ops/test_lower/out.sql +++ /dev/null @@ -1,3 +0,0 @@ -SELECT - LOWER(`string_col`) AS `string_col` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_string_ops/test_lstrip/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_string_ops/test_lstrip/out.sql deleted file mode 100644 index 4023605f8c9..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_string_ops/test_lstrip/out.sql +++ /dev/null @@ -1,3 +0,0 @@ -SELECT - LTRIM(`string_col`, ' ') AS `string_col` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_string_ops/test_regex_replace_str/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_string_ops/test_regex_replace_str/out.sql deleted file mode 100644 index 4728c961ab3..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_string_ops/test_regex_replace_str/out.sql +++ /dev/null @@ -1,3 +0,0 @@ -SELECT - REGEXP_REPLACE(`string_col`, 'e', 'a') AS `string_col` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_string_ops/test_replace_str/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_string_ops/test_replace_str/out.sql deleted file mode 100644 index 154af44b500..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_string_ops/test_replace_str/out.sql +++ /dev/null @@ -1,3 +0,0 @@ -SELECT - REPLACE(`string_col`, 'e', 'a') AS `string_col` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_string_ops/test_reverse/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_string_ops/test_reverse/out.sql deleted file mode 100644 index 97bf57f79e1..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_string_ops/test_reverse/out.sql +++ /dev/null @@ -1,3 +0,0 @@ -SELECT - REVERSE(`string_col`) AS `string_col` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_string_ops/test_rstrip/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_string_ops/test_rstrip/out.sql deleted file mode 100644 index c3d25b56e24..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_string_ops/test_rstrip/out.sql +++ /dev/null @@ -1,3 +0,0 @@ -SELECT - RTRIM(`string_col`, ' ') AS `string_col` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_string_ops/test_startswith/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_string_ops/test_startswith/out.sql deleted file mode 100644 index 760d3db7745..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_string_ops/test_startswith/out.sql +++ /dev/null @@ -1,5 +0,0 @@ -SELECT - STARTS_WITH(`string_col`, 'ab') AS `single`, - STARTS_WITH(`string_col`, 'ab') OR STARTS_WITH(`string_col`, 'cd') AS `double`, - FALSE AS `empty` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_string_ops/test_str_contains/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_string_ops/test_str_contains/out.sql deleted file mode 100644 index 9071a252bc1..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_string_ops/test_str_contains/out.sql +++ /dev/null @@ -1,3 +0,0 @@ -SELECT - `string_col` LIKE '%e%' AS `string_col` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_string_ops/test_str_contains_regex/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_string_ops/test_str_contains_regex/out.sql deleted file mode 100644 index 958f9af6f39..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_string_ops/test_str_contains_regex/out.sql +++ /dev/null @@ -1,3 +0,0 @@ -SELECT - REGEXP_CONTAINS(`string_col`, 'e') AS `string_col` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_string_ops/test_str_extract/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_string_ops/test_str_extract/out.sql deleted file mode 100644 index a87f5d9836d..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_string_ops/test_str_extract/out.sql +++ /dev/null @@ -1,12 +0,0 @@ -SELECT - IF( - REGEXP_CONTAINS(`string_col`, '([a-z]*)'), - REGEXP_REPLACE(`string_col`, CONCAT('.*?(', '([a-z]*)', ').*'), '\\1'), - NULL - ) AS `zero`, - IF( - REGEXP_CONTAINS(`string_col`, '([a-z]*)'), - REGEXP_REPLACE(`string_col`, CONCAT('.*?', '([a-z]*)', '.*'), '\\1'), - NULL - ) AS `one` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_string_ops/test_str_find/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_string_ops/test_str_find/out.sql deleted file mode 100644 index cf21fb8234a..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_string_ops/test_str_find/out.sql +++ /dev/null @@ -1,6 +0,0 @@ -SELECT - INSTR(`string_col`, 'e', 1) - 1 AS `none_none`, - INSTR(`string_col`, 'e', 3) - 1 AS `start_none`, - INSTR(SUBSTRING(`string_col`, 1, 5), 'e') - 1 AS `none_end`, - INSTR(SUBSTRING(`string_col`, 3, 3), 'e') - 1 AS `start_end` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_string_ops/test_str_get/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_string_ops/test_str_get/out.sql deleted file mode 100644 index b4c7c504fc9..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_string_ops/test_str_get/out.sql +++ /dev/null @@ -1,3 +0,0 @@ -SELECT - IF(SUBSTRING(`string_col`, 2, 1) <> '', SUBSTRING(`string_col`, 2, 1), NULL) AS `string_col` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_string_ops/test_str_pad/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_string_ops/test_str_pad/out.sql deleted file mode 100644 index 29766cca6c1..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_string_ops/test_str_pad/out.sql +++ /dev/null @@ -1,13 +0,0 @@ -SELECT - LPAD(`string_col`, GREATEST(LENGTH(`string_col`), 10), '-') AS `left`, - RPAD(`string_col`, GREATEST(LENGTH(`string_col`), 10), '-') AS `right`, - RPAD( - LPAD( - `string_col`, - CAST(FLOOR(SAFE_DIVIDE(GREATEST(LENGTH(`string_col`), 10) - LENGTH(`string_col`), 2)) AS INT64) + LENGTH(`string_col`), - '-' - ), - GREATEST(LENGTH(`string_col`), 10), - '-' - ) AS `both` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_string_ops/test_str_repeat/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_string_ops/test_str_repeat/out.sql deleted file mode 100644 index ed3d06ed35a..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_string_ops/test_str_repeat/out.sql +++ /dev/null @@ -1,3 +0,0 @@ -SELECT - REPEAT(`string_col`, 2) AS `string_col` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_string_ops/test_str_slice/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_string_ops/test_str_slice/out.sql deleted file mode 100644 index f011480ad30..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_string_ops/test_str_slice/out.sql +++ /dev/null @@ -1,18 +0,0 @@ -SELECT - SUBSTRING(`string_col`, 2, 2) AS `1_3`, - SUBSTRING(`string_col`, 1, 3) AS `none_3`, - SUBSTRING(`string_col`, 2) AS `1_none`, - SUBSTRING(`string_col`, -3) AS `m3_none`, - SUBSTRING(`string_col`, 1, GREATEST(0, LENGTH(`string_col`) + -3)) AS `none_m3`, - SUBSTRING( - `string_col`, - GREATEST(1, LENGTH(`string_col`) + -4), - GREATEST(0, LENGTH(`string_col`) + -3) - GREATEST(0, LENGTH(`string_col`) + -5) - ) AS `m5_m3`, - SUBSTRING(`string_col`, 2, GREATEST(0, LENGTH(`string_col`) + -4)) AS `1_m3`, - SUBSTRING( - `string_col`, - GREATEST(1, LENGTH(`string_col`) + -2), - GREATEST(0, 5 - GREATEST(0, LENGTH(`string_col`) + -3)) - ) AS `m3_5` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_string_ops/test_strconcat/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_string_ops/test_strconcat/out.sql deleted file mode 100644 index cf4051464b7..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_string_ops/test_strconcat/out.sql +++ /dev/null @@ -1,3 +0,0 @@ -SELECT - CONCAT(`string_col`, 'a') AS `string_col` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_string_ops/test_string_split/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_string_ops/test_string_split/out.sql deleted file mode 100644 index 5145d6686a8..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_string_ops/test_string_split/out.sql +++ /dev/null @@ -1,3 +0,0 @@ -SELECT - SPLIT(`string_col`, ',') AS `string_col` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_string_ops/test_strip/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_string_ops/test_strip/out.sql deleted file mode 100644 index e07185292bb..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_string_ops/test_strip/out.sql +++ /dev/null @@ -1,3 +0,0 @@ -SELECT - TRIM(`string_col`, ' ') AS `string_col` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_string_ops/test_upper/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_string_ops/test_upper/out.sql deleted file mode 100644 index 88bd78bd095..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_string_ops/test_upper/out.sql +++ /dev/null @@ -1,3 +0,0 @@ -SELECT - UPPER(`string_col`) AS `string_col` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_string_ops/test_zfill/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_string_ops/test_zfill/out.sql deleted file mode 100644 index 818d2907add..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_string_ops/test_zfill/out.sql +++ /dev/null @@ -1,7 +0,0 @@ -SELECT - CASE - WHEN STARTS_WITH(`string_col`, '-') - THEN CONCAT('-', LPAD(SUBSTRING(`string_col`, 2), GREATEST(LENGTH(`string_col`), 10) - 1, '0')) - ELSE LPAD(`string_col`, GREATEST(LENGTH(`string_col`), 10), '0') - END AS `string_col` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_struct_ops/test_struct_field/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_struct_ops/test_struct_field/out.sql deleted file mode 100644 index 6c3760aa36e..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_struct_ops/test_struct_field/out.sql +++ /dev/null @@ -1,4 +0,0 @@ -SELECT - `people`.`name` AS `string`, - `people`.`name` AS `int` -FROM `bigframes-dev`.`sqlglot_test`.`nested_structs_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_struct_ops/test_struct_op/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_struct_ops/test_struct_op/out.sql deleted file mode 100644 index 3549149609a..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_struct_ops/test_struct_op/out.sql +++ /dev/null @@ -1,8 +0,0 @@ -SELECT - STRUCT( - `bool_col` AS bool_col, - `int64_col` AS int64_col, - `float64_col` AS float64_col, - `string_col` AS string_col - ) AS `result_col` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_timedelta_ops/test_timedelta_floor/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_timedelta_ops/test_timedelta_floor/out.sql deleted file mode 100644 index 6eb6f8e989d..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_timedelta_ops/test_timedelta_floor/out.sql +++ /dev/null @@ -1,3 +0,0 @@ -SELECT - FLOOR(`int64_col`) AS `int64_col` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_timedelta_ops/test_to_timedelta/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_timedelta_ops/test_to_timedelta/out.sql deleted file mode 100644 index 59aa3d9b0b3..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_timedelta_ops/test_to_timedelta/out.sql +++ /dev/null @@ -1,9 +0,0 @@ -SELECT - `rowindex`, - `int64_col`, - `float64_col`, - `int64_col` AS `duration_us`, - CAST(FLOOR(`float64_col` * 1000000) AS INT64) AS `duration_s`, - `int64_col` * 3600000000 AS `duration_w`, - `int64_col` AS `duration_on_duration` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/test_ai_ops.py b/tests/unit/core/compile/sqlglot/expressions/test_ai_ops.py deleted file mode 100644 index 57c52490860..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/test_ai_ops.py +++ /dev/null @@ -1,477 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import json - -import pytest - -from bigframes import dataframe -from bigframes import operations as ops -from bigframes.testing import utils - -pytest.importorskip("pytest_snapshot") - -CONNECTION_ID = "bigframes-dev.us.bigframes-default-connection" - - -def test_ai_generate(scalar_types_df: dataframe.DataFrame, snapshot): - col_name = "string_col" - - op = ops.AIGenerate( - prompt_context=(None, " is the same as ", None), - endpoint="gemini-2.5-flash", - request_type="SHARED", - ) - - sql = utils._apply_ops_to_sql( - scalar_types_df, [op.as_expr(col_name, col_name)], ["result"] - ) - - snapshot.assert_match(sql, "out.sql") - - -def test_ai_generate_with_connection_id(scalar_types_df: dataframe.DataFrame, snapshot): - col_name = "string_col" - - op = ops.AIGenerate( - prompt_context=(None, " is the same as ", None), - connection_id=CONNECTION_ID, - endpoint="gemini-2.5-flash", - ) - - sql = utils._apply_ops_to_sql( - scalar_types_df, [op.as_expr(col_name, col_name)], ["result"] - ) - - snapshot.assert_match(sql, "out.sql") - - -def test_ai_generate_with_output_schema(scalar_types_df: dataframe.DataFrame, snapshot): - col_name = "string_col" - - op = ops.AIGenerate( - prompt_context=(None, " is the same as ", None), - connection_id=None, - endpoint="gemini-2.5-flash", - output_schema="x INT64, y FLOAT64", - ) - - sql = utils._apply_ops_to_sql( - scalar_types_df, [op.as_expr(col_name, col_name)], ["result"] - ) - - snapshot.assert_match(sql, "out.sql") - - -def test_ai_generate_with_model_param(scalar_types_df: dataframe.DataFrame, snapshot): - col_name = "string_col" - - op = ops.AIGenerate( - prompt_context=(None, " is the same as ", None), - model_params=json.dumps(dict()), - ) - - sql = utils._apply_ops_to_sql( - scalar_types_df, [op.as_expr(col_name, col_name)], ["result"] - ) - - snapshot.assert_match(sql, "out.sql") - - -def test_ai_generate_bool(scalar_types_df: dataframe.DataFrame, snapshot): - col_name = "string_col" - - op = ops.AIGenerateBool( - prompt_context=(None, " is the same as ", None), - endpoint="gemini-2.5-flash", - ) - - sql = utils._apply_ops_to_sql( - scalar_types_df, [op.as_expr(col_name, col_name)], ["result"] - ) - - snapshot.assert_match(sql, "out.sql") - - -def test_ai_generate_bool_with_connection_id( - scalar_types_df: dataframe.DataFrame, snapshot -): - col_name = "string_col" - - op = ops.AIGenerateBool( - prompt_context=(None, " is the same as ", None), - connection_id=CONNECTION_ID, - endpoint="gemini-2.5-flash", - ) - - sql = utils._apply_ops_to_sql( - scalar_types_df, [op.as_expr(col_name, col_name)], ["result"] - ) - - snapshot.assert_match(sql, "out.sql") - - -def test_ai_generate_bool_with_model_param( - scalar_types_df: dataframe.DataFrame, snapshot -): - col_name = "string_col" - - op = ops.AIGenerateBool( - prompt_context=(None, " is the same as ", None), - model_params=json.dumps(dict()), - ) - - sql = utils._apply_ops_to_sql( - scalar_types_df, [op.as_expr(col_name, col_name)], ["result"] - ) - - snapshot.assert_match(sql, "out.sql") - - -def test_ai_generate_int(scalar_types_df: dataframe.DataFrame, snapshot): - col_name = "string_col" - - op = ops.AIGenerateInt( - # The prompt does not make semantic sense but we only care about syntax correctness. - prompt_context=(None, " is the same as ", None), - endpoint="gemini-2.5-flash", - ) - - sql = utils._apply_ops_to_sql( - scalar_types_df, [op.as_expr(col_name, col_name)], ["result"] - ) - - snapshot.assert_match(sql, "out.sql") - - -def test_ai_generate_int_with_connection_id( - scalar_types_df: dataframe.DataFrame, snapshot -): - col_name = "string_col" - - op = ops.AIGenerateInt( - # The prompt does not make semantic sense but we only care about syntax correctness. - prompt_context=(None, " is the same as ", None), - connection_id=CONNECTION_ID, - endpoint="gemini-2.5-flash", - ) - - sql = utils._apply_ops_to_sql( - scalar_types_df, [op.as_expr(col_name, col_name)], ["result"] - ) - - snapshot.assert_match(sql, "out.sql") - - -def test_ai_generate_int_with_model_param( - scalar_types_df: dataframe.DataFrame, snapshot -): - col_name = "string_col" - - op = ops.AIGenerateInt( - # The prompt does not make semantic sense but we only care about syntax correctness. - prompt_context=(None, " is the same as ", None), - model_params=json.dumps(dict()), - ) - - sql = utils._apply_ops_to_sql( - scalar_types_df, [op.as_expr(col_name, col_name)], ["result"] - ) - - snapshot.assert_match(sql, "out.sql") - - -def test_ai_generate_double(scalar_types_df: dataframe.DataFrame, snapshot): - col_name = "string_col" - - op = ops.AIGenerateDouble( - # The prompt does not make semantic sense but we only care about syntax correctness. - prompt_context=(None, " is the same as ", None), - endpoint="gemini-2.5-flash", - ) - - sql = utils._apply_ops_to_sql( - scalar_types_df, [op.as_expr(col_name, col_name)], ["result"] - ) - - snapshot.assert_match(sql, "out.sql") - - -def test_ai_generate_double_with_connection_id( - scalar_types_df: dataframe.DataFrame, snapshot -): - col_name = "string_col" - - op = ops.AIGenerateDouble( - # The prompt does not make semantic sense but we only care about syntax correctness. - prompt_context=(None, " is the same as ", None), - connection_id=CONNECTION_ID, - endpoint="gemini-2.5-flash", - ) - - sql = utils._apply_ops_to_sql( - scalar_types_df, [op.as_expr(col_name, col_name)], ["result"] - ) - - snapshot.assert_match(sql, "out.sql") - - -def test_ai_generate_double_with_model_param( - scalar_types_df: dataframe.DataFrame, snapshot -): - col_name = "string_col" - - op = ops.AIGenerateDouble( - # The prompt does not make semantic sense but we only care about syntax correctness. - prompt_context=(None, " is the same as ", None), - model_params=json.dumps(dict()), - ) - - sql = utils._apply_ops_to_sql( - scalar_types_df, [op.as_expr(col_name, col_name)], ["result"] - ) - - snapshot.assert_match(sql, "out.sql") - - -def test_ai_embed(scalar_types_df: dataframe.DataFrame, snapshot): - col_name = "string_col" - - op = ops.AIEmbed( - endpoint="text-embedding-005", - ) - - sql = utils._apply_ops_to_sql(scalar_types_df, [op.as_expr(col_name)], ["result"]) - - snapshot.assert_match(sql, "out.sql") - - -def test_ai_embed_with_connection_id(scalar_types_df: dataframe.DataFrame, snapshot): - col_name = "string_col" - - op = ops.AIEmbed( - endpoint="text-embedding-005", - connection_id=CONNECTION_ID, - ) - - sql = utils._apply_ops_to_sql(scalar_types_df, [op.as_expr(col_name)], ["result"]) - - snapshot.assert_match(sql, "out.sql") - - -def test_ai_embed_with_model(scalar_types_df: dataframe.DataFrame, snapshot): - col_name = "string_col" - - op = ops.AIEmbed( - model="embeddinggemma-300m", - ) - - sql = utils._apply_ops_to_sql(scalar_types_df, [op.as_expr(col_name)], ["result"]) - - snapshot.assert_match(sql, "out.sql") - - -def test_ai_embed_with_task_type_and_title( - scalar_types_df: dataframe.DataFrame, snapshot -): - col_name = "string_col" - - op = ops.AIEmbed( - endpoint="text-embedding-005", - task_type="RETRIEVAL_DOCUMENT", - title="My Document", - model_params=json.dumps({"outputDimensionality": 256}), - ) - - sql = utils._apply_ops_to_sql(scalar_types_df, [op.as_expr(col_name)], ["result"]) - - snapshot.assert_match(sql, "out.sql") - - -@pytest.mark.parametrize("connection_id", [None, CONNECTION_ID]) -def test_ai_if(scalar_types_df: dataframe.DataFrame, snapshot, connection_id): - col_name = "string_col" - - op = ops.AIIf( - prompt_context=(None, " is the same as ", None), - connection_id=connection_id, - optimization_mode="MINIMIZE_COST", - max_error_ratio=0.5, - ) - - sql = utils._apply_ops_to_sql( - scalar_types_df, [op.as_expr(col_name, col_name)], ["result"] - ) - - snapshot.assert_match(sql, "out.sql") - - -def test_ai_if_with_endpoint(scalar_types_df: dataframe.DataFrame, snapshot): - col_name = "string_col" - - op = ops.AIIf( - prompt_context=(None, " is the same as ", None), - endpoint="gemini-2.5-flash", - ) - - sql = utils._apply_ops_to_sql( - scalar_types_df, [op.as_expr(col_name, col_name)], ["result"] - ) - - snapshot.assert_match(sql, "out.sql") - - -@pytest.mark.parametrize("connection_id", [None, CONNECTION_ID]) -def test_ai_classify(scalar_types_df: dataframe.DataFrame, snapshot, connection_id): - col_name = "string_col" - - op = ops.AIClassify( - prompt_context=(None,), - categories=("greeting", "rejection"), - connection_id=connection_id, - ) - - sql = utils._apply_ops_to_sql(scalar_types_df, [op.as_expr(col_name)], ["result"]) - - snapshot.assert_match(sql, "out.sql") - - -def test_ai_classify_with_params(scalar_types_df: dataframe.DataFrame, snapshot): - col_name = "string_col" - - op = ops.AIClassify( - prompt_context=(None,), - categories=("greeting", "rejection"), - examples=(("hi", "greeting"), ("bye", "rejection")), - endpoint="gemini-2.5-flash", - max_error_ratio=0.1, - ) - - sql = utils._apply_ops_to_sql(scalar_types_df, [op.as_expr(col_name)], ["result"]) - - snapshot.assert_match(sql, "out.sql") - - -def test_ai_classify_with_output_mode(scalar_types_df: dataframe.DataFrame, snapshot): - col_name = "string_col" - - op = ops.AIClassify( - prompt_context=(None,), - categories=("greeting", "rejection"), - output_mode="multi", - ) - - sql = utils._apply_ops_to_sql(scalar_types_df, [op.as_expr(col_name)], ["result"]) - - snapshot.assert_match(sql, "out.sql") - - -def test_ai_classify_multi_with_list_examples( - scalar_types_df: dataframe.DataFrame, snapshot -): - col_name = "string_col" - - examples = ( - ("hi", ("greeting", "positive")), - ("bye", ("rejection", "negative")), - ) - op = ops.AIClassify( - prompt_context=(None,), - categories=("greeting", "rejection"), - examples=examples, - output_mode="multi", - ) - - sql = utils._apply_ops_to_sql(scalar_types_df, [op.as_expr(col_name)], ["result"]) - - snapshot.assert_match(sql, "out.sql") - - -@pytest.mark.parametrize("connection_id", [None, CONNECTION_ID]) -def test_ai_score(scalar_types_df: dataframe.DataFrame, snapshot, connection_id): - col_name = "string_col" - - op = ops.AIScore( - prompt_context=(None, " is the same as ", None), - connection_id=connection_id, - ) - - sql = utils._apply_ops_to_sql( - scalar_types_df, [op.as_expr(col_name, col_name)], ["result"] - ) - - snapshot.assert_match(sql, "out.sql") - - -def test_ai_score_with_endpoint_and_max_error_ratio( - scalar_types_df: dataframe.DataFrame, snapshot -): - col_name = "string_col" - - op = ops.AIScore( - prompt_context=(None, " is the same as ", None), - endpoint="gemini-2.5-flash", - max_error_ratio=0.5, - ) - - sql = utils._apply_ops_to_sql( - scalar_types_df, [op.as_expr(col_name, col_name)], ["result"] - ) - - snapshot.assert_match(sql, "out.sql") - - -@pytest.mark.parametrize("connection_id", [None, CONNECTION_ID]) -def test_ai_similarity(scalar_types_df: dataframe.DataFrame, snapshot, connection_id): - col_name = "string_col" - - op = ops.AISimilarity( - endpoint="text-embedding-005", - connection_id=connection_id, - ) - - sql = utils._apply_ops_to_sql( - scalar_types_df, [op.as_expr(col_name, col_name)], ["result"] - ) - - snapshot.assert_match(sql, "out.sql") - - -def test_ai_similarity_with_model(scalar_types_df: dataframe.DataFrame, snapshot): - col_name = "string_col" - - op = ops.AISimilarity( - model="embeddinggemma-300m", - ) - - sql = utils._apply_ops_to_sql( - scalar_types_df, [op.as_expr(col_name, col_name)], ["result"] - ) - - snapshot.assert_match(sql, "out.sql") - - -def test_ai_similarity_with_model_param(scalar_types_df: dataframe.DataFrame, snapshot): - col_name = "string_col" - - op = ops.AISimilarity( - endpoint="text-embedding-005", - model_params=json.dumps({"outputDimensionality": 256}), - ) - - sql = utils._apply_ops_to_sql( - scalar_types_df, [op.as_expr(col_name, col_name)], ["result"] - ) - - snapshot.assert_match(sql, "out.sql") diff --git a/tests/unit/core/compile/sqlglot/expressions/test_array_ops.py b/tests/unit/core/compile/sqlglot/expressions/test_array_ops.py deleted file mode 100644 index 1b358b3a3b1..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/test_array_ops.py +++ /dev/null @@ -1,117 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the \"License\"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an \"AS IS\" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import pytest - -import bigframes.operations.aggregations as agg_ops -import bigframes.pandas as bpd -from bigframes import operations as ops -from bigframes.core import expression -from bigframes.testing import utils - -pytest.importorskip("pytest_snapshot") - - -def test_array_to_string(repeated_types_df: bpd.DataFrame, snapshot): - col_name = "string_list_col" - bf_df = repeated_types_df[[col_name]] - sql = utils._apply_ops_to_sql( - bf_df, [ops.ArrayToStringOp(delimiter=".").as_expr(col_name)], [col_name] - ) - - snapshot.assert_match(sql, "out.sql") - - -def test_array_index(scalar_types_df: bpd.DataFrame, snapshot): - ops_map = { - "string_index": ops.GetItemOp(key=1).as_expr("string_col"), - "array_index": expression.OpExpression( - ops.GetItemOp(key=1), - (ops.ToArrayOp().as_expr("int64_col", "int64_too"),), - ), - } - - sql = utils._apply_ops_to_sql( - scalar_types_df, list(ops_map.values()), list(ops_map.keys()) - ) - snapshot.assert_match(sql, "out.sql") - - -def test_array_reduce_op(repeated_types_df: bpd.DataFrame, snapshot): - ops_map = { - "sum_float": ops.ArrayReduceOp(agg_ops.SumOp()).as_expr("float_list_col"), - "std_float": ops.ArrayReduceOp(agg_ops.StdOp()).as_expr("float_list_col"), - "count_str": ops.ArrayReduceOp(agg_ops.CountOp()).as_expr("string_list_col"), - "any_bool": ops.ArrayReduceOp(agg_ops.AnyOp()).as_expr("bool_list_col"), - "array_agg_str": ops.ArrayReduceOp(agg_ops.ArrayAggOp()).as_expr( - "string_list_col" - ), - } - - sql = utils._apply_ops_to_sql( - repeated_types_df, list(ops_map.values()), list(ops_map.keys()) - ) - snapshot.assert_match(sql, "out.sql") - - -def test_array_slice(scalar_types_df: bpd.DataFrame, snapshot): - array_expr = ops.ToArrayOp().as_expr("int64_col", "int64_too") - ops_map = { - "string_slice": ops.ArraySliceOp(start=1, stop=5).as_expr("string_col"), - "slice_only_start": expression.OpExpression( - ops.ArraySliceOp(start=1, stop=None), - (array_expr,), - ), - "slice_start_stop": expression.OpExpression( - ops.ArraySliceOp(start=1, stop=5), - (array_expr,), - ), - } - - sql = utils._apply_ops_to_sql( - scalar_types_df, list(ops_map.values()), list(ops_map.keys()) - ) - snapshot.assert_match(sql, "out.sql") - - -def test_to_array_op(scalar_types_df: bpd.DataFrame, snapshot): - bf_df = scalar_types_df[["int64_col", "bool_col", "float64_col", "string_col"]] - # Bigquery won't allow you to materialize arrays with null, so use non-nullable - int64_non_null = ops.coalesce_op.as_expr("int64_col", expression.const(0)) - bool_col_non_null = ops.coalesce_op.as_expr("bool_col", expression.const(False)) - float_col_non_null = ops.coalesce_op.as_expr("float64_col", expression.const(0.0)) - string_col_non_null = ops.coalesce_op.as_expr("string_col", expression.const("")) - - ops_map = { - "bool_col": ops.ToArrayOp().as_expr(bool_col_non_null), - "int64_col": ops.ToArrayOp().as_expr(int64_non_null), - "strs_col": ops.ToArrayOp().as_expr(string_col_non_null, string_col_non_null), - "numeric_col": ops.ToArrayOp().as_expr( - int64_non_null, bool_col_non_null, float_col_non_null - ), - } - - sql = utils._apply_ops_to_sql(bf_df, list(ops_map.values()), list(ops_map.keys())) - snapshot.assert_match(sql, "out.sql") - - -def test_to_array_with_subquery_expression(repeated_types_df: bpd.DataFrame, snapshot): - reduced = ops.ArrayReduceOp(agg_ops.SumOp()).as_expr("float_list_col") - coalesced_reduced = ops.coalesce_op.as_expr(reduced, expression.const(0.0)) - array_expr = ops.ToArrayOp().as_expr(coalesced_reduced) - - sql = utils._apply_ops_to_sql( - repeated_types_df, [array_expr], ["arr_subquery_coalesce"] - ) - snapshot.assert_match(sql, "out.sql") diff --git a/tests/unit/core/compile/sqlglot/expressions/test_bool_ops.py b/tests/unit/core/compile/sqlglot/expressions/test_bool_ops.py deleted file mode 100644 index bd51ea905a2..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/test_bool_ops.py +++ /dev/null @@ -1,50 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import pandas as pd -import pytest - -import bigframes.pandas as bpd - -pytest.importorskip("pytest_snapshot") - - -def test_and_op(scalar_types_df: bpd.DataFrame, snapshot): - bf_df = scalar_types_df[["bool_col", "int64_col"]] - - bf_df["int_and_int"] = bf_df["int64_col"] & bf_df["int64_col"] - bf_df["bool_and_bool"] = bf_df["bool_col"] & bf_df["bool_col"] - bf_df["bool_and_null"] = bf_df["bool_col"] & pd.NA # type: ignore - bf_df["null_and_bool"] = pd.NA & bf_df["bool_col"] # type: ignore - snapshot.assert_match(bf_df.sql, "out.sql") - - -def test_or_op(scalar_types_df: bpd.DataFrame, snapshot): - bf_df = scalar_types_df[["bool_col", "int64_col"]] - - bf_df["int_and_int"] = bf_df["int64_col"] | bf_df["int64_col"] - bf_df["bool_and_bool"] = bf_df["bool_col"] | bf_df["bool_col"] - bf_df["bool_and_null"] = bf_df["bool_col"] | pd.NA # type: ignore - bf_df["null_and_bool"] = pd.NA | bf_df["bool_col"] # type: ignore - snapshot.assert_match(bf_df.sql, "out.sql") - - -def test_xor_op(scalar_types_df: bpd.DataFrame, snapshot): - bf_df = scalar_types_df[["bool_col", "int64_col"]] - - bf_df["int_and_int"] = bf_df["int64_col"] ^ bf_df["int64_col"] - bf_df["bool_and_bool"] = bf_df["bool_col"] ^ bf_df["bool_col"] - bf_df["bool_and_null"] = bf_df["bool_col"] ^ pd.NA # type: ignore - bf_df["null_and_bool"] = pd.NA ^ bf_df["bool_col"] # type: ignore - snapshot.assert_match(bf_df.sql, "out.sql") diff --git a/tests/unit/core/compile/sqlglot/expressions/test_comparison_ops.py b/tests/unit/core/compile/sqlglot/expressions/test_comparison_ops.py deleted file mode 100644 index 73aceaedeeb..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/test_comparison_ops.py +++ /dev/null @@ -1,167 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import pandas as pd -import pytest - -import bigframes.pandas as bpd -from bigframes import operations as ops -from bigframes.testing import utils - -pytest.importorskip("pytest_snapshot") - - -def test_is_in(scalar_types_df: bpd.DataFrame, snapshot): - bool_col = "bool_col" - int_col = "int64_col" - float_col = "float64_col" - bf_df = scalar_types_df[[bool_col, int_col, float_col]] - ops_map = { - "bools": ops.IsInOp(values=(True, False)).as_expr(bool_col), - "ints": ops.IsInOp(values=(1, 2, 3)).as_expr(int_col), - "ints_w_null": ops.IsInOp(values=(None, pd.NA)).as_expr(int_col), - "floats": ops.IsInOp(values=(1.0, 2.0, 3.0), match_nulls=False).as_expr( - int_col - ), - "strings": ops.IsInOp(values=("1.0", "2.0")).as_expr(int_col), - "mixed": ops.IsInOp( - values=( - "1.0", - 2.5, - 3, - 1e-10, - float("inf"), - float("nan"), - 0, - ) - ).as_expr(int_col), - "empty": ops.IsInOp(values=()).as_expr(int_col), - "empty_wo_match_nulls": ops.IsInOp(values=(), match_nulls=False).as_expr( - int_col - ), - "ints_wo_match_nulls": ops.IsInOp( - values=(None, 123456), match_nulls=False - ).as_expr(int_col), - "float_in_ints": ops.IsInOp(values=(1, 2, 3, None)).as_expr(float_col), - "mixed_with_null": ops.IsInOp( - values=("1.0", 2, None), match_nulls=True - ).as_expr(int_col), - "bool_in_mixed": ops.IsInOp(values=(1, 2.5)).as_expr(bool_col), - "only_null_match": ops.IsInOp(values=(None,), match_nulls=True).as_expr( - int_col - ), - } - - sql = utils._apply_ops_to_sql(bf_df, list(ops_map.values()), list(ops_map.keys())) - snapshot.assert_match(sql, "out.sql") - - -def test_eq_null_match(scalar_types_df: bpd.DataFrame, snapshot): - bf_df = scalar_types_df[["int64_col", "bool_col"]] - sql = utils._apply_binary_op(bf_df, ops.eq_null_match_op, "int64_col", "bool_col") - snapshot.assert_match(sql, "out.sql") - - -def test_eq_numeric(scalar_types_df: bpd.DataFrame, snapshot): - bf_df = scalar_types_df[["int64_col", "bool_col"]] - - bf_df["int_eq_int"] = bf_df["int64_col"] == bf_df["int64_col"] - bf_df["int_eq_1"] = bf_df["int64_col"] == 1 - bf_df["int_eq_null"] = bf_df["int64_col"] == pd.NA - bf_df["null_eq_int"] = pd.NA == bf_df["int64_col"] - - bf_df["int_eq_bool"] = bf_df["int64_col"] == bf_df["bool_col"] - bf_df["bool_eq_int"] = bf_df["bool_col"] == bf_df["int64_col"] - - snapshot.assert_match(bf_df.sql, "out.sql") - - -def test_gt_numeric(scalar_types_df: bpd.DataFrame, snapshot): - bf_df = scalar_types_df[["int64_col", "bool_col"]] - - bf_df["int_gt_int"] = bf_df["int64_col"] > bf_df["int64_col"] - bf_df["int_gt_1"] = bf_df["int64_col"] > 1 - bf_df["null_gt_int"] = pd.NA > bf_df["int64_col"] - - bf_df["int_gt_bool"] = bf_df["int64_col"] > bf_df["bool_col"] - bf_df["bool_gt_int"] = bf_df["bool_col"] > bf_df["int64_col"] - - snapshot.assert_match(bf_df.sql, "out.sql") - - -def test_ge_numeric(scalar_types_df: bpd.DataFrame, snapshot): - bf_df = scalar_types_df[["int64_col", "bool_col"]] - - bf_df["int_ge_int"] = bf_df["int64_col"] >= bf_df["int64_col"] - bf_df["int_ge_1"] = bf_df["int64_col"] >= 1 - bf_df["null_ge_int"] = pd.NA >= bf_df["int64_col"] - - bf_df["int_ge_bool"] = bf_df["int64_col"] >= bf_df["bool_col"] - bf_df["bool_ge_int"] = bf_df["bool_col"] >= bf_df["int64_col"] - - snapshot.assert_match(bf_df.sql, "out.sql") - - -def test_lt_numeric(scalar_types_df: bpd.DataFrame, snapshot): - bf_df = scalar_types_df[["int64_col", "bool_col"]] - - bf_df["int_lt_int"] = bf_df["int64_col"] < bf_df["int64_col"] - bf_df["int_lt_1"] = bf_df["int64_col"] < 1 - bf_df["null_lt_int"] = pd.NA < bf_df["int64_col"] - - bf_df["int_lt_bool"] = bf_df["int64_col"] < bf_df["bool_col"] - bf_df["bool_lt_int"] = bf_df["bool_col"] < bf_df["int64_col"] - - snapshot.assert_match(bf_df.sql, "out.sql") - - -def test_le_numeric(scalar_types_df: bpd.DataFrame, snapshot): - bf_df = scalar_types_df[["int64_col", "bool_col"]] - - bf_df["int_le_int"] = bf_df["int64_col"] <= bf_df["int64_col"] - bf_df["int_le_1"] = bf_df["int64_col"] <= 1 - bf_df["null_le_int"] = pd.NA <= bf_df["int64_col"] - - bf_df["int_le_bool"] = bf_df["int64_col"] <= bf_df["bool_col"] - bf_df["bool_le_int"] = bf_df["bool_col"] <= bf_df["int64_col"] - - snapshot.assert_match(bf_df.sql, "out.sql") - - -def test_maximum_op(scalar_types_df: bpd.DataFrame, snapshot): - bf_df = scalar_types_df[["int64_col", "float64_col"]] - sql = utils._apply_binary_op(bf_df, ops.maximum_op, "int64_col", "float64_col") - - snapshot.assert_match(sql, "out.sql") - - -def test_minimum_op(scalar_types_df: bpd.DataFrame, snapshot): - bf_df = scalar_types_df[["int64_col", "float64_col"]] - sql = utils._apply_binary_op(bf_df, ops.minimum_op, "int64_col", "float64_col") - - snapshot.assert_match(sql, "out.sql") - - -def test_ne_numeric(scalar_types_df: bpd.DataFrame, snapshot): - bf_df = scalar_types_df[["int64_col", "bool_col"]] - - bf_df["int_ne_int"] = bf_df["int64_col"] != bf_df["int64_col"] - bf_df["int_ne_1"] = bf_df["int64_col"] != 1 - bf_df["int_ne_null"] = bf_df["int64_col"] != pd.NA - bf_df["null_ne_int"] = pd.NA != bf_df["int64_col"] - - bf_df["int_ne_bool"] = bf_df["int64_col"] != bf_df["bool_col"] - bf_df["bool_ne_int"] = bf_df["bool_col"] != bf_df["int64_col"] - - snapshot.assert_match(bf_df.sql, "out.sql") diff --git a/tests/unit/core/compile/sqlglot/expressions/test_datetime_ops.py b/tests/unit/core/compile/sqlglot/expressions/test_datetime_ops.py deleted file mode 100644 index e86059b160a..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/test_datetime_ops.py +++ /dev/null @@ -1,404 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import pandas as pd -import pytest - -import bigframes.pandas as bpd -from bigframes import operations as ops -from bigframes.testing import utils - -pytest.importorskip("pytest_snapshot") - - -def test_date(scalar_types_df: bpd.DataFrame, snapshot): - col_name = "timestamp_col" - bf_df = scalar_types_df[[col_name]] - sql = utils._apply_ops_to_sql(bf_df, [ops.date_op.as_expr(col_name)], [col_name]) - - snapshot.assert_match(sql, "out.sql") - - -def test_day(scalar_types_df: bpd.DataFrame, snapshot): - col_name = "timestamp_col" - bf_df = scalar_types_df[[col_name]] - sql = utils._apply_ops_to_sql(bf_df, [ops.day_op.as_expr(col_name)], [col_name]) - - snapshot.assert_match(sql, "out.sql") - - -def test_dayofweek(scalar_types_df: bpd.DataFrame, snapshot): - col_names = ["datetime_col", "timestamp_col", "date_col"] - bf_df = scalar_types_df[col_names] - ops_map = {col_name: ops.dayofweek_op.as_expr(col_name) for col_name in col_names} - - sql = utils._apply_ops_to_sql(bf_df, list(ops_map.values()), list(ops_map.keys())) - snapshot.assert_match(sql, "out.sql") - - -def test_dayofyear(scalar_types_df: bpd.DataFrame, snapshot): - col_name = "timestamp_col" - bf_df = scalar_types_df[[col_name]] - sql = utils._apply_ops_to_sql( - bf_df, [ops.dayofyear_op.as_expr(col_name)], [col_name] - ) - - snapshot.assert_match(sql, "out.sql") - - -def test_datetime_to_integer_label(scalar_types_df: bpd.DataFrame, snapshot): - col_names = ["datetime_col", "timestamp_col"] - bf_df = scalar_types_df[col_names] - ops_map = { - "fixed_freq": ops.DatetimeToIntegerLabelOp( - freq=pd.tseries.offsets.Day(), # type: ignore[arg-type] - origin="start", - closed="left", # type: ignore - ).as_expr("datetime_col", "timestamp_col"), - "origin_epoch": ops.DatetimeToIntegerLabelOp( - freq=pd.tseries.offsets.Day(), # type: ignore[arg-type] - origin="epoch", - closed="left", # type: ignore - ).as_expr("datetime_col", "timestamp_col"), - "origin_start_day": ops.DatetimeToIntegerLabelOp( - freq=pd.tseries.offsets.Day(), # type: ignore[arg-type] - origin="start_day", - closed="left", # type: ignore - ).as_expr("datetime_col", "timestamp_col"), - "non_fixed_freq_weekly": ops.DatetimeToIntegerLabelOp( - freq=pd.tseries.offsets.Week(weekday=6), # type: ignore[arg-type] - origin="start", - closed="left", # type: ignore - ).as_expr("datetime_col", "timestamp_col"), - "non_fixed_freq_monthly": ops.DatetimeToIntegerLabelOp( - freq=pd.tseries.offsets.MonthEnd(), # type: ignore[arg-type] - origin="start", - closed="left", # type: ignore - ).as_expr("datetime_col", "timestamp_col"), - "non_fixed_freq_quarterly": ops.DatetimeToIntegerLabelOp( - freq=pd.tseries.offsets.QuarterEnd(startingMonth=12), # type: ignore[arg-type] - origin="start", - closed="left", # type: ignore - ).as_expr("datetime_col", "timestamp_col"), - "non_fixed_freq_yearly": ops.DatetimeToIntegerLabelOp( - freq=pd.tseries.offsets.YearEnd(), # type: ignore[arg-type] - origin="start", - closed="left", # type: ignore - ).as_expr("datetime_col", "timestamp_col"), - } - - sql = utils._apply_ops_to_sql(bf_df, list(ops_map.values()), list(ops_map.keys())) - snapshot.assert_match(sql, "out.sql") - - -def test_floor_dt(scalar_types_df: bpd.DataFrame, snapshot): - col_names = ["datetime_col", "timestamp_col", "date_col"] - bf_df = scalar_types_df[col_names] - ops_map = { - "timestamp_col_us": ops.FloorDtOp("us").as_expr("timestamp_col"), - "timestamp_col_ms": ops.FloorDtOp("ms").as_expr("timestamp_col"), - "timestamp_col_s": ops.FloorDtOp("s").as_expr("timestamp_col"), - "timestamp_col_min": ops.FloorDtOp("min").as_expr("timestamp_col"), - "timestamp_col_h": ops.FloorDtOp("h").as_expr("timestamp_col"), - "timestamp_col_D": ops.FloorDtOp("D").as_expr("timestamp_col"), - "timestamp_col_W": ops.FloorDtOp("W").as_expr("timestamp_col"), - "timestamp_col_M": ops.FloorDtOp("M").as_expr("timestamp_col"), - "timestamp_col_Q": ops.FloorDtOp("Q").as_expr("timestamp_col"), - "timestamp_col_Y": ops.FloorDtOp("Y").as_expr("timestamp_col"), - "datetime_col_q": ops.FloorDtOp("us").as_expr("datetime_col"), - "datetime_col_us": ops.FloorDtOp("us").as_expr("datetime_col"), - } - - sql = utils._apply_ops_to_sql(bf_df, list(ops_map.values()), list(ops_map.keys())) - snapshot.assert_match(sql, "out.sql") - - -def test_floor_dt_op_invalid_freq(scalar_types_df: bpd.DataFrame): - col_name = "timestamp_col" - bf_df = scalar_types_df[[col_name]] - with pytest.raises( - NotImplementedError, match="Unsupported freq paramater: invalid" - ): - utils._apply_ops_to_sql( - bf_df, - [ops.FloorDtOp(freq="invalid").as_expr(col_name)], # type:ignore - [col_name], - ) - - -def test_hour(scalar_types_df: bpd.DataFrame, snapshot): - col_name = "timestamp_col" - bf_df = scalar_types_df[[col_name]] - sql = utils._apply_ops_to_sql(bf_df, [ops.hour_op.as_expr(col_name)], [col_name]) - - snapshot.assert_match(sql, "out.sql") - - -def test_minute(scalar_types_df: bpd.DataFrame, snapshot): - col_name = "timestamp_col" - bf_df = scalar_types_df[[col_name]] - sql = utils._apply_ops_to_sql(bf_df, [ops.minute_op.as_expr(col_name)], [col_name]) - - snapshot.assert_match(sql, "out.sql") - - -def test_month(scalar_types_df: bpd.DataFrame, snapshot): - col_name = "timestamp_col" - bf_df = scalar_types_df[[col_name]] - sql = utils._apply_ops_to_sql(bf_df, [ops.month_op.as_expr(col_name)], [col_name]) - - snapshot.assert_match(sql, "out.sql") - - -def test_normalize(scalar_types_df: bpd.DataFrame, snapshot): - col_name = "timestamp_col" - bf_df = scalar_types_df[[col_name]] - sql = utils._apply_ops_to_sql( - bf_df, [ops.normalize_op.as_expr(col_name)], [col_name] - ) - - snapshot.assert_match(sql, "out.sql") - - -def test_quarter(scalar_types_df: bpd.DataFrame, snapshot): - col_name = "timestamp_col" - bf_df = scalar_types_df[[col_name]] - sql = utils._apply_ops_to_sql(bf_df, [ops.quarter_op.as_expr(col_name)], [col_name]) - - snapshot.assert_match(sql, "out.sql") - - -def test_second(scalar_types_df: bpd.DataFrame, snapshot): - col_name = "timestamp_col" - bf_df = scalar_types_df[[col_name]] - sql = utils._apply_ops_to_sql(bf_df, [ops.second_op.as_expr(col_name)], [col_name]) - - snapshot.assert_match(sql, "out.sql") - - -def test_strftime(scalar_types_df: bpd.DataFrame, snapshot): - bf_df = scalar_types_df[["timestamp_col", "datetime_col", "date_col", "time_col"]] - ops_map = { - "date_col": ops.StrftimeOp("%Y-%m-%d").as_expr("date_col"), - "datetime_col": ops.StrftimeOp("%Y-%m-%d").as_expr("datetime_col"), - "time_col": ops.StrftimeOp("%Y-%m-%d").as_expr("time_col"), - "timestamp_col": ops.StrftimeOp("%Y-%m-%d").as_expr("timestamp_col"), - } - - sql = utils._apply_ops_to_sql(bf_df, list(ops_map.values()), list(ops_map.keys())) - snapshot.assert_match(sql, "out.sql") - - -def test_time(scalar_types_df: bpd.DataFrame, snapshot): - col_name = "timestamp_col" - bf_df = scalar_types_df[[col_name]] - sql = utils._apply_ops_to_sql(bf_df, [ops.time_op.as_expr(col_name)], [col_name]) - - snapshot.assert_match(sql, "out.sql") - - -def test_to_datetime(scalar_types_df: bpd.DataFrame, snapshot): - col_names = ["int64_col", "string_col", "float64_col", "timestamp_col"] - bf_df = scalar_types_df[col_names] - ops_map = {col_name: ops.ToDatetimeOp().as_expr(col_name) for col_name in col_names} - ops_map["string_col_fmt"] = ops.ToDatetimeOp(format="%Y-%m-%d").as_expr( - "string_col" - ) - - sql = utils._apply_ops_to_sql(bf_df, list(ops_map.values()), list(ops_map.keys())) - snapshot.assert_match(sql + "\n", "out.sql") - - -def test_to_timestamp(scalar_types_df: bpd.DataFrame, snapshot): - bf_df = scalar_types_df[["int64_col", "string_col", "float64_col", "datetime_col"]] - ops_map = { - "int64_col": ops.ToTimestampOp().as_expr("int64_col"), - "float64_col": ops.ToTimestampOp().as_expr("float64_col"), - "int64_col_s": ops.ToTimestampOp(unit="s").as_expr("int64_col"), - "int64_col_ms": ops.ToTimestampOp(unit="ms").as_expr("int64_col"), - "int64_col_us": ops.ToTimestampOp(unit="us").as_expr("int64_col"), - "int64_col_ns": ops.ToTimestampOp(unit="ns").as_expr("int64_col"), - "datetime_col": ops.ToTimestampOp().as_expr("datetime_col"), - "string_col_fmt": ops.ToTimestampOp(format="%Y-%m-%d").as_expr("string_col"), - } - - sql = utils._apply_ops_to_sql(bf_df, list(ops_map.values()), list(ops_map.keys())) - snapshot.assert_match(sql, "out.sql") - - -def test_unix_micros(scalar_types_df: bpd.DataFrame, snapshot): - col_name = "timestamp_col" - bf_df = scalar_types_df[[col_name]] - sql = utils._apply_ops_to_sql( - bf_df, [ops.UnixMicros().as_expr(col_name)], [col_name] - ) - - snapshot.assert_match(sql, "out.sql") - - -def test_unix_millis(scalar_types_df: bpd.DataFrame, snapshot): - col_name = "timestamp_col" - bf_df = scalar_types_df[[col_name]] - sql = utils._apply_ops_to_sql( - bf_df, [ops.UnixMillis().as_expr(col_name)], [col_name] - ) - - snapshot.assert_match(sql, "out.sql") - - -def test_unix_seconds(scalar_types_df: bpd.DataFrame, snapshot): - col_name = "timestamp_col" - bf_df = scalar_types_df[[col_name]] - sql = utils._apply_ops_to_sql( - bf_df, [ops.UnixSeconds().as_expr(col_name)], [col_name] - ) - - snapshot.assert_match(sql, "out.sql") - - -def test_year(scalar_types_df: bpd.DataFrame, snapshot): - col_name = "timestamp_col" - bf_df = scalar_types_df[[col_name]] - sql = utils._apply_ops_to_sql(bf_df, [ops.year_op.as_expr(col_name)], [col_name]) - - snapshot.assert_match(sql, "out.sql") - - -def test_iso_day(scalar_types_df: bpd.DataFrame, snapshot): - col_name = "timestamp_col" - bf_df = scalar_types_df[[col_name]] - sql = utils._apply_ops_to_sql(bf_df, [ops.iso_day_op.as_expr(col_name)], [col_name]) - - snapshot.assert_match(sql, "out.sql") - - -def test_iso_week(scalar_types_df: bpd.DataFrame, snapshot): - col_name = "timestamp_col" - bf_df = scalar_types_df[[col_name]] - sql = utils._apply_ops_to_sql( - bf_df, [ops.iso_week_op.as_expr(col_name)], [col_name] - ) - - snapshot.assert_match(sql, "out.sql") - - -def test_iso_year(scalar_types_df: bpd.DataFrame, snapshot): - col_name = "timestamp_col" - bf_df = scalar_types_df[[col_name]] - sql = utils._apply_ops_to_sql( - bf_df, [ops.iso_year_op.as_expr(col_name)], [col_name] - ) - - snapshot.assert_match(sql, "out.sql") - - -def test_add_timedelta(scalar_types_df: bpd.DataFrame, snapshot): - bf_df = scalar_types_df[["timestamp_col", "date_col"]] - timedelta = pd.Timedelta(1, unit="d") - - bf_df["date_add_timedelta"] = bf_df["date_col"] + timedelta - bf_df["timestamp_add_timedelta"] = bf_df["timestamp_col"] + timedelta - bf_df["timedelta_add_date"] = timedelta + bf_df["date_col"] - bf_df["timedelta_add_timestamp"] = timedelta + bf_df["timestamp_col"] - bf_df["timedelta_add_timedelta"] = timedelta + timedelta - - snapshot.assert_match(bf_df.sql, "out.sql") - - -def test_sub_timedelta(scalar_types_df: bpd.DataFrame, snapshot): - bf_df = scalar_types_df[["timestamp_col", "duration_col", "date_col"]] - bf_df["duration_col"] = bpd.to_timedelta(bf_df["duration_col"], unit="us") - - bf_df["date_sub_timedelta"] = bf_df["date_col"] - bf_df["duration_col"] - bf_df["timestamp_sub_timedelta"] = bf_df["timestamp_col"] - bf_df["duration_col"] - bf_df["timestamp_sub_date"] = bf_df["date_col"] - bf_df["date_col"] - bf_df["date_sub_timestamp"] = bf_df["timestamp_col"] - bf_df["timestamp_col"] - bf_df["timedelta_sub_timedelta"] = bf_df["duration_col"] - bf_df["duration_col"] - - snapshot.assert_match(bf_df.sql, "out.sql") - - -def test_integer_label_to_datetime_fixed(scalar_types_df: bpd.DataFrame, snapshot): - col_names = ["rowindex", "timestamp_col"] - bf_df = scalar_types_df[col_names] - ops_map = { - "fixed_freq": ops.IntegerLabelToDatetimeOp( - freq=pd.tseries.offsets.Day(), # type: ignore[arg-type] - origin="start", - label="left", # type: ignore - ).as_expr("rowindex", "timestamp_col"), - } - - sql = utils._apply_ops_to_sql(bf_df, list(ops_map.values()), list(ops_map.keys())) - snapshot.assert_match(sql, "out.sql") - - -def test_integer_label_to_datetime_week(scalar_types_df: bpd.DataFrame, snapshot): - col_names = ["rowindex", "timestamp_col"] - bf_df = scalar_types_df[col_names] - ops_map = { - "non_fixed_freq_weekly": ops.IntegerLabelToDatetimeOp( - freq=pd.tseries.offsets.Week(weekday=6), # type: ignore[arg-type] - origin="start", - label="left", # type: ignore - ).as_expr("rowindex", "timestamp_col"), - } - - sql = utils._apply_ops_to_sql(bf_df, list(ops_map.values()), list(ops_map.keys())) - snapshot.assert_match(sql, "out.sql") - - -def test_integer_label_to_datetime_month(scalar_types_df: bpd.DataFrame, snapshot): - col_names = ["rowindex", "timestamp_col"] - bf_df = scalar_types_df[col_names] - ops_map = { - "non_fixed_freq_monthly": ops.IntegerLabelToDatetimeOp( - freq=pd.tseries.offsets.MonthEnd(), # type: ignore - origin="start", - label="left", - ).as_expr("rowindex", "timestamp_col"), - } - - sql = utils._apply_ops_to_sql(bf_df, list(ops_map.values()), list(ops_map.keys())) - snapshot.assert_match(sql, "out.sql") - - -def test_integer_label_to_datetime_quarter(scalar_types_df: bpd.DataFrame, snapshot): - col_names = ["rowindex", "timestamp_col"] - bf_df = scalar_types_df[col_names] - ops_map = { - "non_fixed_freq": ops.IntegerLabelToDatetimeOp( - freq=pd.tseries.offsets.QuarterEnd(startingMonth=12), # type: ignore - origin="start", - label="left", - ).as_expr("rowindex", "timestamp_col"), - } - - sql = utils._apply_ops_to_sql(bf_df, list(ops_map.values()), list(ops_map.keys())) - snapshot.assert_match(sql, "out.sql") - - -def test_integer_label_to_datetime_year(scalar_types_df: bpd.DataFrame, snapshot): - col_names = ["rowindex", "timestamp_col"] - bf_df = scalar_types_df[col_names] - ops_map = { - "non_fixed_freq_yearly": ops.IntegerLabelToDatetimeOp( - freq=pd.tseries.offsets.YearEnd(month=12), # type: ignore - origin="start", - label="left", - ).as_expr("rowindex", "timestamp_col"), - } - - sql = utils._apply_ops_to_sql(bf_df, list(ops_map.values()), list(ops_map.keys())) - snapshot.assert_match(sql, "out.sql") diff --git a/tests/unit/core/compile/sqlglot/expressions/test_generic_ops.py b/tests/unit/core/compile/sqlglot/expressions/test_generic_ops.py deleted file mode 100644 index e3669e1b0ed..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/test_generic_ops.py +++ /dev/null @@ -1,357 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import pandas as pd -import pytest -from google.cloud import bigquery - -import bigframes.pandas as bpd -from bigframes import dtypes -from bigframes import operations as ops -from bigframes.core import expression as ex -from bigframes.functions import udf_def -from bigframes.testing import utils - -pytest.importorskip("pytest_snapshot") - - -def test_astype_int(scalar_types_df: bpd.DataFrame, snapshot): - bf_df = scalar_types_df - to_type = dtypes.INT_DTYPE - - ops_map = { - "datetime_col": ops.AsTypeOp(to_type=to_type).as_expr("datetime_col"), - "datetime_w_safe": ops.AsTypeOp(to_type=to_type, safe=True).as_expr( - "datetime_col" - ), - "time_col": ops.AsTypeOp(to_type=to_type).as_expr("time_col"), - "time_w_safe": ops.AsTypeOp(to_type=to_type, safe=True).as_expr("time_col"), - "timestamp_col": ops.AsTypeOp(to_type=to_type).as_expr("timestamp_col"), - "numeric_col": ops.AsTypeOp(to_type=to_type).as_expr("numeric_col"), - "float64_col": ops.AsTypeOp(to_type=to_type).as_expr("float64_col"), - "float64_w_safe": ops.AsTypeOp(to_type=to_type, safe=True).as_expr( - "float64_col" - ), - "str_const": ops.AsTypeOp(to_type=to_type).as_expr(ex.const("100")), - } - - sql = utils._apply_ops_to_sql(bf_df, list(ops_map.values()), list(ops_map.keys())) - snapshot.assert_match(sql, "out.sql") - - -def test_astype_float(scalar_types_df: bpd.DataFrame, snapshot): - bf_df = scalar_types_df - to_type = dtypes.FLOAT_DTYPE - - ops_map = { - "bool_col": ops.AsTypeOp(to_type=to_type).as_expr("bool_col"), - "str_const": ops.AsTypeOp(to_type=to_type).as_expr(ex.const("1.34235e4")), - "bool_w_safe": ops.AsTypeOp(to_type=to_type, safe=True).as_expr("bool_col"), - } - sql = utils._apply_ops_to_sql(bf_df, list(ops_map.values()), list(ops_map.keys())) - snapshot.assert_match(sql + "\n", "out.sql") - - -def test_astype_bool(scalar_types_df: bpd.DataFrame, snapshot): - bf_df = scalar_types_df - to_type = dtypes.BOOL_DTYPE - - ops_map = { - "bool_col": ops.AsTypeOp(to_type=to_type).as_expr("bool_col"), - "float64_col": ops.AsTypeOp(to_type=to_type).as_expr("float64_col"), - "float64_w_safe": ops.AsTypeOp(to_type=to_type, safe=True).as_expr( - "float64_col" - ), - } - sql = utils._apply_ops_to_sql(bf_df, list(ops_map.values()), list(ops_map.keys())) - snapshot.assert_match(sql, "out.sql") - - -def test_astype_time_like(scalar_types_df: bpd.DataFrame, snapshot): - bf_df = scalar_types_df - - ops_map = { - "int64_to_datetime": ops.AsTypeOp(to_type=dtypes.DATETIME_DTYPE).as_expr( - "int64_col" - ), - "int64_to_time": ops.AsTypeOp(to_type=dtypes.TIME_DTYPE).as_expr("int64_col"), - "int64_to_timestamp": ops.AsTypeOp(to_type=dtypes.TIMESTAMP_DTYPE).as_expr( - "int64_col" - ), - "int64_to_time_safe": ops.AsTypeOp( - to_type=dtypes.TIME_DTYPE, safe=True - ).as_expr("int64_col"), - } - sql = utils._apply_ops_to_sql(bf_df, list(ops_map.values()), list(ops_map.keys())) - snapshot.assert_match(sql, "out.sql") - - -def test_astype_string(scalar_types_df: bpd.DataFrame, snapshot): - bf_df = scalar_types_df - to_type = dtypes.STRING_DTYPE - - ops_map = { - "int64_col": ops.AsTypeOp(to_type=to_type).as_expr("int64_col"), - "bool_col": ops.AsTypeOp(to_type=to_type).as_expr("bool_col"), - "bool_w_safe": ops.AsTypeOp(to_type=to_type, safe=True).as_expr("bool_col"), - } - sql = utils._apply_ops_to_sql(bf_df, list(ops_map.values()), list(ops_map.keys())) - snapshot.assert_match(sql + "\n", "out.sql") - - -def test_to_json(scalar_types_df: bpd.DataFrame, snapshot): - bf_df = scalar_types_df - - ops_map = { - "int64_col": ops.ToJSON().as_expr("int64_col"), - "float64_col": ops.ToJSON().as_expr("float64_col"), - "bool_col": ops.ToJSON().as_expr("bool_col"), - "string_col": ops.ToJSON().as_expr("string_col"), - "bool_w_safe": ops.ToJSON(safe=True).as_expr("bool_col"), - "string_w_safe": ops.ToJSON(safe=True).as_expr("string_col"), - } - sql = utils._apply_ops_to_sql(bf_df, list(ops_map.values()), list(ops_map.keys())) - snapshot.assert_match(sql, "out.sql") - - -def test_astype_from_json(json_types_df: bpd.DataFrame, snapshot): - bf_df = json_types_df - - ops_map = { - "int64_col": ops.JSONDecode(to_type=dtypes.INT_DTYPE).as_expr("json_col"), - "float64_col": ops.JSONDecode(to_type=dtypes.FLOAT_DTYPE).as_expr("json_col"), - "bool_col": ops.JSONDecode(to_type=dtypes.BOOL_DTYPE).as_expr("json_col"), - "string_col": ops.JSONDecode(to_type=dtypes.STRING_DTYPE).as_expr("json_col"), - "int64_w_safe": ops.JSONDecode(to_type=dtypes.INT_DTYPE, safe=True).as_expr( - "json_col" - ), - } - sql = utils._apply_ops_to_sql(bf_df, list(ops_map.values()), list(ops_map.keys())) - snapshot.assert_match(sql, "out.sql") - - -def test_tojson_invalid(scalar_types_df: bpd.DataFrame, json_types_df: bpd.DataFrame): - # Test invalid cast to JSON - with pytest.raises(TypeError): - ops_map_to = { - "datetime_to_json": ops.ToJSON().as_expr("datetime_col"), - } - utils._apply_ops_to_sql( - scalar_types_df, list(ops_map_to.values()), list(ops_map_to.keys()) - ) - - # Test invalid cast from JSON - with pytest.raises(TypeError): - ops_map_from = { - "json_to_datetime": ops.JSONDecode(to_type=dtypes.DATETIME_DTYPE).as_expr( - "json_col" - ), - } - utils._apply_ops_to_sql( - json_types_df, list(ops_map_from.values()), list(ops_map_from.keys()) - ) - - -def test_remote_function_op(scalar_types_df: bpd.DataFrame, snapshot): - bf_df = scalar_types_df[["int64_col", "float64_col", "string_col"]] - op = ops.RemoteFunctionOp( - function_def=udf_def.BigqueryUdf( - routine_ref=bigquery.RoutineReference.from_string( - "my_project.my_dataset.my_routine" - ), - signature=udf_def.UdfSignature( - inputs=( - udf_def.UdfArg( - "x", - udf_def.DirectScalarType(int), - ), - udf_def.UdfArg( - "y", - udf_def.DirectScalarType(float), - ), - udf_def.UdfArg( - "z", - udf_def.DirectScalarType(str), - ), - ), - output=udf_def.DirectScalarType(float), - ), - ) - ) - sql = utils._apply_nary_op(bf_df, op, "int64_col", "float64_col", "string_col") - snapshot.assert_match(sql, "out.sql") - - -def test_case_when_op(scalar_types_df: bpd.DataFrame, snapshot): - ops_map = { - "single_case": ops.case_when_op.as_expr( - "bool_col", - "int64_col", - ), - "double_case": ops.case_when_op.as_expr( - "bool_col", - "int64_col", - "bool_col", - "int64_too", - ), - "bool_types_case": ops.case_when_op.as_expr( - "bool_col", - "bool_col", - "bool_col", - "bool_col", - ), - "mixed_types_cast": ops.case_when_op.as_expr( - "bool_col", - "int64_col", - "bool_col", - "bool_col", - "bool_col", - "float64_col", - ), - } - - array_value = scalar_types_df._block.expr - result, col_ids = array_value.compute_values(list(ops_map.values())) - - # Rename columns for deterministic golden SQL results. - assert len(col_ids) == len(ops_map.keys()) - result = result.rename_columns( - {col_id: key for col_id, key in zip(col_ids, ops_map.keys())} - ).select_columns(list(ops_map.keys())) - - sql = result.session._executor.to_sql(result, enable_cache=False) - snapshot.assert_match(sql, "out.sql") - - -def test_coalesce(scalar_types_df: bpd.DataFrame, snapshot): - bf_df = scalar_types_df[["int64_col", "int64_too"]] - - sql = utils._apply_ops_to_sql( - bf_df, - [ - ops.coalesce_op.as_expr("int64_col", "int64_col"), - ops.coalesce_op.as_expr("int64_too", "int64_col"), - ], - ["int64_col", "int64_too"], - ) - snapshot.assert_match(sql, "out.sql") - - -def test_clip(scalar_types_df: bpd.DataFrame, snapshot): - op_expr = ops.clip_op.as_expr("rowindex", "int64_col", "int64_too") - - array_value = scalar_types_df._block.expr - result, col_ids = array_value.compute_values([op_expr]) - - # Rename columns for deterministic golden SQL results. - assert len(col_ids) == 1 - result = result.rename_columns({col_ids[0]: "result_col"}).select_columns( - ["result_col"] - ) - - sql = result.session._executor.to_sql(result, enable_cache=False) - snapshot.assert_match(sql, "out.sql") - - -def test_fillna(scalar_types_df: bpd.DataFrame, snapshot): - bf_df = scalar_types_df[["int64_col", "float64_col"]] - sql = utils._apply_binary_op(bf_df, ops.fillna_op, "int64_col", "float64_col") - snapshot.assert_match(sql, "out.sql") - - -def test_hash(scalar_types_df: bpd.DataFrame, snapshot): - col_name = "string_col" - bf_df = scalar_types_df[[col_name]] - sql = utils._apply_ops_to_sql(bf_df, [ops.hash_op.as_expr(col_name)], [col_name]) - - snapshot.assert_match(sql, "out.sql") - - -def test_invert(scalar_types_df: bpd.DataFrame, snapshot): - bf_df = scalar_types_df[["int64_col", "bytes_col", "bool_col"]] - ops_map = { - "int64_col": ops.invert_op.as_expr("int64_col"), - "bytes_col": ops.invert_op.as_expr("bytes_col"), - "bool_col": ops.invert_op.as_expr("bool_col"), - } - sql = utils._apply_ops_to_sql(bf_df, list(ops_map.values()), list(ops_map.keys())) - - snapshot.assert_match(sql, "out.sql") - - -def test_isnull(scalar_types_df: bpd.DataFrame, snapshot): - col_name = "float64_col" - bf_df = scalar_types_df[[col_name]] - sql = utils._apply_ops_to_sql(bf_df, [ops.isnull_op.as_expr(col_name)], [col_name]) - - snapshot.assert_match(sql, "out.sql") - - -def test_notnull(scalar_types_df: bpd.DataFrame, snapshot): - col_name = "float64_col" - bf_df = scalar_types_df[[col_name]] - sql = utils._apply_ops_to_sql(bf_df, [ops.notnull_op.as_expr(col_name)], [col_name]) - - snapshot.assert_match(sql, "out.sql") - - -def test_row_key(scalar_types_df: bpd.DataFrame, snapshot): - column_ids = (col for col in scalar_types_df._block.expr.column_ids) - sql = utils._apply_ops_to_sql( - scalar_types_df, [ops.RowKey().as_expr(*column_ids)], ["row_key"] - ) - snapshot.assert_match(sql, "out.sql") - - -def test_sql_scalar_op(scalar_types_df: bpd.DataFrame, snapshot): - bf_df = scalar_types_df[["bool_col", "bytes_col"]] - sql = utils._apply_nary_op( - bf_df, - ops.SqlScalarOp(dtypes.INT_DTYPE, "CAST({0} AS INT64) + BYTE_LENGTH({1})"), - "bool_col", - "bytes_col", - ) - snapshot.assert_match(sql, "out.sql") - - -def test_map(scalar_types_df: bpd.DataFrame, snapshot): - col_name = "string_col" - bf_df = scalar_types_df[[col_name]] - sql = utils._apply_ops_to_sql( - bf_df, - [ - ops.MapOp(mappings=(("value1", "mapped1"), (pd.NA, "UNKNOWN"))).as_expr( - col_name - ) - ], - [col_name], - ) - - snapshot.assert_match(sql, "out.sql") - - -def test_where(scalar_types_df: bpd.DataFrame, snapshot): - op_expr = ops.where_op.as_expr("int64_col", "bool_col", "float64_col") - - array_value = scalar_types_df._block.expr - result, col_ids = array_value.compute_values([op_expr]) - - # Rename columns for deterministic golden SQL results. - assert len(col_ids) == 1 - result = result.rename_columns({col_ids[0]: "result_col"}).select_columns( - ["result_col"] - ) - - sql = result.session._executor.to_sql(result, enable_cache=False) - snapshot.assert_match(sql, "out.sql") diff --git a/tests/unit/core/compile/sqlglot/expressions/test_geo_ops.py b/tests/unit/core/compile/sqlglot/expressions/test_geo_ops.py deleted file mode 100644 index 85e374c76db..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/test_geo_ops.py +++ /dev/null @@ -1,148 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import pytest - -import bigframes.pandas as bpd -from bigframes import operations as ops -from bigframes.testing import utils - -pytest.importorskip("pytest_snapshot") - - -def test_geo_st_astext(scalar_types_df: bpd.DataFrame, snapshot): - col_name = "geography_col" - bf_df = scalar_types_df[[col_name]] - sql = utils._apply_ops_to_sql( - bf_df, [ops.geo_st_astext_op.as_expr(col_name)], [col_name] - ) - - snapshot.assert_match(sql, "out.sql") - - -def test_geo_st_boundary(scalar_types_df: bpd.DataFrame, snapshot): - col_name = "geography_col" - bf_df = scalar_types_df[[col_name]] - sql = utils._apply_ops_to_sql( - bf_df, [ops.geo_st_boundary_op.as_expr(col_name)], [col_name] - ) - - snapshot.assert_match(sql, "out.sql") - - -def test_geo_st_buffer(scalar_types_df: bpd.DataFrame, snapshot): - col_name = "geography_col" - bf_df = scalar_types_df[[col_name]] - sql = utils._apply_ops_to_sql( - bf_df, [ops.GeoStBufferOp(1.0, 8.0, False).as_expr(col_name)], [col_name] - ) - - snapshot.assert_match(sql, "out.sql") - - -def test_geo_st_convexhull(scalar_types_df: bpd.DataFrame, snapshot): - col_name = "geography_col" - bf_df = scalar_types_df[[col_name]] - sql = utils._apply_ops_to_sql( - bf_df, [ops.geo_st_convexhull_op.as_expr(col_name)], [col_name] - ) - - snapshot.assert_match(sql, "out.sql") - - -def test_geo_st_distance(scalar_types_df: bpd.DataFrame, snapshot): - col_name = "geography_col" - bf_df = scalar_types_df[[col_name]] - - sql = utils._apply_ops_to_sql( - bf_df, - [ - ops.GeoStDistanceOp(use_spheroid=True).as_expr(col_name, col_name), - ops.GeoStDistanceOp(use_spheroid=False).as_expr(col_name, col_name), - ], - ["spheroid", "no_spheroid"], - ) - snapshot.assert_match(sql, "out.sql") - - -def test_geo_st_difference(scalar_types_df: bpd.DataFrame, snapshot): - col_name = "geography_col" - bf_df = scalar_types_df[[col_name]] - sql = utils._apply_binary_op(bf_df, ops.geo_st_difference_op, col_name, col_name) - - snapshot.assert_match(sql, "out.sql") - - -def test_geo_st_geogfromtext(scalar_types_df: bpd.DataFrame, snapshot): - col_name = "string_col" - bf_df = scalar_types_df[[col_name]] - sql = utils._apply_ops_to_sql( - bf_df, [ops.geo_st_geogfromtext_op.as_expr(col_name)], [col_name] - ) - - snapshot.assert_match(sql, "out.sql") - - -def test_geo_st_geogpoint(scalar_types_df: bpd.DataFrame, snapshot): - col_names = ["rowindex", "rowindex_2"] - bf_df = scalar_types_df[col_names] - sql = utils._apply_binary_op( - bf_df, ops.geo_st_geogpoint_op, col_names[0], col_names[1] - ) - - snapshot.assert_match(sql, "out.sql") - - -def test_geo_st_intersection(scalar_types_df: bpd.DataFrame, snapshot): - col_name = "geography_col" - bf_df = scalar_types_df[[col_name]] - sql = utils._apply_binary_op(bf_df, ops.geo_st_intersection_op, col_name, col_name) - - snapshot.assert_match(sql, "out.sql") - - -def test_geo_st_isclosed(scalar_types_df: bpd.DataFrame, snapshot): - col_name = "geography_col" - bf_df = scalar_types_df[[col_name]] - sql = utils._apply_ops_to_sql( - bf_df, [ops.geo_st_isclosed_op.as_expr(col_name)], [col_name] - ) - - snapshot.assert_match(sql, "out.sql") - - -def test_geo_st_length(scalar_types_df: bpd.DataFrame, snapshot): - col_name = "geography_col" - bf_df = scalar_types_df[[col_name]] - sql = utils._apply_ops_to_sql( - bf_df, [ops.GeoStLengthOp(True).as_expr(col_name)], [col_name] - ) - - snapshot.assert_match(sql, "out.sql") - - -def test_geo_x(scalar_types_df: bpd.DataFrame, snapshot): - col_name = "geography_col" - bf_df = scalar_types_df[[col_name]] - sql = utils._apply_ops_to_sql(bf_df, [ops.geo_x_op.as_expr(col_name)], [col_name]) - - snapshot.assert_match(sql, "out.sql") - - -def test_geo_y(scalar_types_df: bpd.DataFrame, snapshot): - col_name = "geography_col" - bf_df = scalar_types_df[[col_name]] - sql = utils._apply_ops_to_sql(bf_df, [ops.geo_y_op.as_expr(col_name)], [col_name]) - - snapshot.assert_match(sql, "out.sql") diff --git a/tests/unit/core/compile/sqlglot/expressions/test_json_ops.py b/tests/unit/core/compile/sqlglot/expressions/test_json_ops.py deleted file mode 100644 index 69eb8681abc..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/test_json_ops.py +++ /dev/null @@ -1,142 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import pytest - -import bigframes.core.expression as ex -import bigframes.pandas as bpd -from bigframes import operations as ops -from bigframes.testing import utils - -pytest.importorskip("pytest_snapshot") - - -def test_json_extract(json_types_df: bpd.DataFrame, snapshot): - col_name = "json_col" - bf_df = json_types_df[[col_name]] - sql = utils._apply_ops_to_sql( - bf_df, [ops.JSONExtract(json_path="$").as_expr(col_name)], [col_name] - ) - - snapshot.assert_match(sql, "out.sql") - - -def test_json_extract_array(json_types_df: bpd.DataFrame, snapshot): - col_name = "json_col" - bf_df = json_types_df[[col_name]] - sql = utils._apply_ops_to_sql( - bf_df, [ops.JSONExtractArray(json_path="$").as_expr(col_name)], [col_name] - ) - - snapshot.assert_match(sql, "out.sql") - - -def test_json_extract_string_array(json_types_df: bpd.DataFrame, snapshot): - col_name = "json_col" - bf_df = json_types_df[[col_name]] - sql = utils._apply_ops_to_sql( - bf_df, [ops.JSONExtractStringArray(json_path="$").as_expr(col_name)], [col_name] - ) - - snapshot.assert_match(sql, "out.sql") - - -def test_json_keys(json_types_df: bpd.DataFrame, snapshot): - col_name = "json_col" - bf_df = json_types_df[[col_name]] - - ops_map = { - "json_keys": ops.JSONKeys().as_expr(col_name), - "json_keys_w_max_depth": ops.JSONKeys(max_depth=2).as_expr(col_name), - } - - sql = utils._apply_ops_to_sql(bf_df, list(ops_map.values()), list(ops_map.keys())) - snapshot.assert_match(sql, "out.sql") - - -def test_json_query(json_types_df: bpd.DataFrame, snapshot): - col_name = "json_col" - bf_df = json_types_df[[col_name]] - sql = utils._apply_ops_to_sql( - bf_df, [ops.JSONQuery(json_path="$").as_expr(col_name)], [col_name] - ) - - snapshot.assert_match(sql, "out.sql") - - -def test_json_query_array(json_types_df: bpd.DataFrame, snapshot): - col_name = "json_col" - bf_df = json_types_df[[col_name]] - sql = utils._apply_ops_to_sql( - bf_df, [ops.JSONQueryArray(json_path="$").as_expr(col_name)], [col_name] - ) - - snapshot.assert_match(sql, "out.sql") - - -def test_json_value(json_types_df: bpd.DataFrame, snapshot): - col_name = "json_col" - bf_df = json_types_df[[col_name]] - sql = utils._apply_ops_to_sql( - bf_df, [ops.JSONValue(json_path="$").as_expr(col_name)], [col_name] - ) - - snapshot.assert_match(sql, "out.sql") - - -def test_json_value_array(json_types_df: bpd.DataFrame, snapshot): - col_name = "json_col" - bf_df = json_types_df[[col_name]] - sql = utils._apply_ops_to_sql( - bf_df, [ops.JSONValueArray(json_path="$").as_expr(col_name)], [col_name] - ) - - snapshot.assert_match(sql, "out.sql") - - -def test_parse_json(scalar_types_df: bpd.DataFrame, snapshot): - col_name = "string_col" - bf_df = scalar_types_df[[col_name]] - sql = utils._apply_ops_to_sql( - bf_df, [ops.ParseJSON().as_expr(col_name)], [col_name] - ) - - snapshot.assert_match(sql, "out.sql") - - -def test_to_json(scalar_types_df: bpd.DataFrame, snapshot): - col_name = "string_col" - bf_df = scalar_types_df[[col_name]] - sql = utils._apply_ops_to_sql(bf_df, [ops.ToJSON().as_expr(col_name)], [col_name]) - - snapshot.assert_match(sql, "out.sql") - - -def test_to_json_string(json_types_df: bpd.DataFrame, snapshot): - col_name = "json_col" - bf_df = json_types_df[[col_name]] - sql = utils._apply_ops_to_sql( - bf_df, [ops.ToJSONString().as_expr(col_name)], [col_name] - ) - - snapshot.assert_match(sql, "out.sql") - - -def test_json_set(json_types_df: bpd.DataFrame, snapshot): - bf_df = json_types_df[["json_col"]] - sql = utils._apply_binary_op( - bf_df, ops.JSONSet(json_path="$.a"), "json_col", ex.const(100) - ) - - snapshot.assert_match(sql, "out.sql") diff --git a/tests/unit/core/compile/sqlglot/expressions/test_literals.py b/tests/unit/core/compile/sqlglot/expressions/test_literals.py deleted file mode 100644 index aa0d7a1e5b1..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/test_literals.py +++ /dev/null @@ -1,35 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import pytest - -import bigframes.core.expression as ex -import bigframes.pandas as bpd -from bigframes.testing import utils - -pytest.importorskip("pytest_snapshot") - - -def test_float_literals(scalar_types_df: bpd.DataFrame, snapshot): - bf_df = scalar_types_df[["float64_col"]] - ops_map = { - "inf": ex.const(float("inf")), - "ninf": ex.const(float("-inf")), - "nan": ex.const(float("nan")), - "neg_zero": ex.const(-0.0), - "0.00001": ex.const(0.00001), - "1E-10": ex.const(1e-10), - } - sql = utils._apply_ops_to_sql(bf_df, list(ops_map.values()), list(ops_map.keys())) - snapshot.assert_match(sql, "out.sql") diff --git a/tests/unit/core/compile/sqlglot/expressions/test_numeric_ops.py b/tests/unit/core/compile/sqlglot/expressions/test_numeric_ops.py deleted file mode 100644 index b0442f6992e..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/test_numeric_ops.py +++ /dev/null @@ -1,513 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import pandas as pd -import pytest - -import bigframes.core.expression as ex -import bigframes.pandas as bpd -from bigframes import operations as ops -from bigframes.operations import numeric_ops -from bigframes.testing import utils - -pytest.importorskip("pytest_snapshot") - - -def test_arccosh(scalar_types_df: bpd.DataFrame, snapshot): - col_name = "float64_col" - bf_df = scalar_types_df[[col_name]] - sql = utils._apply_ops_to_sql(bf_df, [ops.arccosh_op.as_expr(col_name)], [col_name]) - - snapshot.assert_match(sql, "out.sql") - - -def test_arccos(scalar_types_df: bpd.DataFrame, snapshot): - col_name = "float64_col" - bf_df = scalar_types_df[[col_name]] - sql = utils._apply_ops_to_sql(bf_df, [ops.arccos_op.as_expr(col_name)], [col_name]) - - snapshot.assert_match(sql, "out.sql") - - -def test_arcsin(scalar_types_df: bpd.DataFrame, snapshot): - col_name = "float64_col" - bf_df = scalar_types_df[[col_name]] - sql = utils._apply_ops_to_sql(bf_df, [ops.arcsin_op.as_expr(col_name)], [col_name]) - - snapshot.assert_match(sql, "out.sql") - - -def test_arcsinh(scalar_types_df: bpd.DataFrame, snapshot): - col_name = "float64_col" - bf_df = scalar_types_df[[col_name]] - sql = utils._apply_ops_to_sql(bf_df, [ops.arcsinh_op.as_expr(col_name)], [col_name]) - - snapshot.assert_match(sql, "out.sql") - - -def test_arctan2(scalar_types_df: bpd.DataFrame, snapshot): - bf_df = scalar_types_df[["int64_col", "float64_col", "bool_col"]] - - sql = utils._apply_ops_to_sql( - bf_df, - [ - ops.arctan2_op.as_expr("int64_col", "float64_col"), - ops.arctan2_op.as_expr("bool_col", "float64_col"), - ], - ["int64_col", "bool_col"], - ) - snapshot.assert_match(sql, "out.sql") - - -def test_arctan(scalar_types_df: bpd.DataFrame, snapshot): - col_name = "float64_col" - bf_df = scalar_types_df[[col_name]] - sql = utils._apply_ops_to_sql(bf_df, [ops.arctan_op.as_expr(col_name)], [col_name]) - - snapshot.assert_match(sql, "out.sql") - - -def test_arctanh(scalar_types_df: bpd.DataFrame, snapshot): - col_name = "float64_col" - bf_df = scalar_types_df[[col_name]] - sql = utils._apply_ops_to_sql(bf_df, [ops.arctanh_op.as_expr(col_name)], [col_name]) - - snapshot.assert_match(sql, "out.sql") - - -def test_abs(scalar_types_df: bpd.DataFrame, snapshot): - col_name = "float64_col" - bf_df = scalar_types_df[[col_name]] - sql = utils._apply_ops_to_sql(bf_df, [ops.abs_op.as_expr(col_name)], [col_name]) - - snapshot.assert_match(sql, "out.sql") - - -def test_ceil(scalar_types_df: bpd.DataFrame, snapshot): - col_name = "float64_col" - bf_df = scalar_types_df[[col_name]] - sql = utils._apply_ops_to_sql(bf_df, [ops.ceil_op.as_expr(col_name)], [col_name]) - - snapshot.assert_match(sql, "out.sql") - - -def test_cos(scalar_types_df: bpd.DataFrame, snapshot): - col_name = "float64_col" - bf_df = scalar_types_df[[col_name]] - sql = utils._apply_ops_to_sql(bf_df, [ops.cos_op.as_expr(col_name)], [col_name]) - - snapshot.assert_match(sql, "out.sql") - - -def test_cosh(scalar_types_df: bpd.DataFrame, snapshot): - col_name = "float64_col" - bf_df = scalar_types_df[[col_name]] - sql = utils._apply_ops_to_sql(bf_df, [ops.cosh_op.as_expr(col_name)], [col_name]) - - snapshot.assert_match(sql, "out.sql") - - -def test_cosine_distance(repeated_types_df: bpd.DataFrame, snapshot): - col_names = ["int_list_col", "float_list_col"] - bf_df = repeated_types_df[col_names] - - sql = utils._apply_ops_to_sql( - bf_df, - [ - ops.cosine_distance_op.as_expr("int_list_col", "int_list_col"), - ops.cosine_distance_op.as_expr("float_list_col", "float_list_col"), - ], - ["int_list_col", "float_list_col"], - ) - snapshot.assert_match(sql, "out.sql") - - -def test_exp(scalar_types_df: bpd.DataFrame, snapshot): - col_name = "float64_col" - bf_df = scalar_types_df[[col_name]] - sql = utils._apply_ops_to_sql(bf_df, [ops.exp_op.as_expr(col_name)], [col_name]) - - snapshot.assert_match(sql, "out.sql") - - -def test_expm1(scalar_types_df: bpd.DataFrame, snapshot): - col_name = "float64_col" - bf_df = scalar_types_df[[col_name]] - sql = utils._apply_ops_to_sql(bf_df, [ops.expm1_op.as_expr(col_name)], [col_name]) - - snapshot.assert_match(sql, "out.sql") - - -def test_floor(scalar_types_df: bpd.DataFrame, snapshot): - col_name = "float64_col" - bf_df = scalar_types_df[[col_name]] - sql = utils._apply_ops_to_sql(bf_df, [ops.floor_op.as_expr(col_name)], [col_name]) - - snapshot.assert_match(sql, "out.sql") - - -def test_isfinite(scalar_types_df: bpd.DataFrame, snapshot): - col_name = "float64_col" - bf_df = scalar_types_df[[col_name]] - sql = utils._apply_ops_to_sql( - bf_df, [numeric_ops.isfinite_op.as_expr(col_name)], [col_name] - ) - - snapshot.assert_match(sql, "out.sql") - - -def test_ln(scalar_types_df: bpd.DataFrame, snapshot): - col_name = "float64_col" - bf_df = scalar_types_df[[col_name]] - sql = utils._apply_ops_to_sql(bf_df, [ops.ln_op.as_expr(col_name)], [col_name]) - - snapshot.assert_match(sql, "out.sql") - - -def test_log10(scalar_types_df: bpd.DataFrame, snapshot): - col_name = "float64_col" - bf_df = scalar_types_df[[col_name]] - sql = utils._apply_ops_to_sql(bf_df, [ops.log10_op.as_expr(col_name)], [col_name]) - - snapshot.assert_match(sql, "out.sql") - - -def test_log1p(scalar_types_df: bpd.DataFrame, snapshot): - col_name = "float64_col" - bf_df = scalar_types_df[[col_name]] - sql = utils._apply_ops_to_sql(bf_df, [ops.log1p_op.as_expr(col_name)], [col_name]) - - snapshot.assert_match(sql, "out.sql") - - -def test_neg(scalar_types_df: bpd.DataFrame, snapshot): - col_name = "float64_col" - bf_df = scalar_types_df[[col_name]] - sql = utils._apply_ops_to_sql(bf_df, [ops.neg_op.as_expr(col_name)], [col_name]) - - snapshot.assert_match(sql, "out.sql") - - -def test_pos(scalar_types_df: bpd.DataFrame, snapshot): - col_name = "float64_col" - bf_df = scalar_types_df[[col_name]] - sql = utils._apply_ops_to_sql(bf_df, [ops.pos_op.as_expr(col_name)], [col_name]) - - snapshot.assert_match(sql, "out.sql") - - -def test_pow(scalar_types_df: bpd.DataFrame, snapshot): - bf_df = scalar_types_df[["int64_col", "float64_col"]] - - bf_df["int_pow_int"] = bf_df["int64_col"] ** bf_df["int64_col"] - bf_df["int_pow_float"] = bf_df["int64_col"] ** bf_df["float64_col"] - bf_df["float_pow_int"] = bf_df["float64_col"] ** bf_df["int64_col"] - bf_df["float_pow_float"] = bf_df["float64_col"] ** bf_df["float64_col"] - - bf_df["int_pow_0"] = bf_df["int64_col"] ** 0 - bf_df["float_pow_0"] = bf_df["float64_col"] ** 0 - bf_df["int_pow_1"] = bf_df["int64_col"] ** 1 - bf_df["float_pow_1"] = bf_df["float64_col"] ** 1 - - bf_df["float_pow_null"] = bf_df["float64_col"] ** pd.NA - bf_df["null_pow_float"] = pd.NA ** bf_df["float64_col"] - - snapshot.assert_match(bf_df.sql, "out.sql") - - -def test_round(scalar_types_df: bpd.DataFrame, snapshot): - bf_df = scalar_types_df[["int64_col", "float64_col"]] - - bf_df["int_round_0"] = bf_df["int64_col"].round(0) - bf_df["int_round_1"] = bf_df["int64_col"].round(1) - bf_df["int_round_m1"] = bf_df["int64_col"].round(-1) - - bf_df["float_round_0"] = bf_df["float64_col"].round(0) - bf_df["float_round_1"] = bf_df["float64_col"].round(1) - bf_df["float_round_m1"] = bf_df["float64_col"].round(-1) - - snapshot.assert_match(bf_df.sql, "out.sql") - - -def test_sqrt(scalar_types_df: bpd.DataFrame, snapshot): - col_name = "float64_col" - bf_df = scalar_types_df[[col_name]] - sql = utils._apply_ops_to_sql(bf_df, [ops.sqrt_op.as_expr(col_name)], [col_name]) - - snapshot.assert_match(sql, "out.sql") - - -def test_sin(scalar_types_df: bpd.DataFrame, snapshot): - col_name = "float64_col" - bf_df = scalar_types_df[[col_name]] - sql = utils._apply_ops_to_sql(bf_df, [ops.sin_op.as_expr(col_name)], [col_name]) - - snapshot.assert_match(sql, "out.sql") - - -def test_sinh(scalar_types_df: bpd.DataFrame, snapshot): - col_name = "float64_col" - bf_df = scalar_types_df[[col_name]] - sql = utils._apply_ops_to_sql(bf_df, [ops.sinh_op.as_expr(col_name)], [col_name]) - - snapshot.assert_match(sql, "out.sql") - - -def test_tan(scalar_types_df: bpd.DataFrame, snapshot): - col_name = "float64_col" - bf_df = scalar_types_df[[col_name]] - sql = utils._apply_ops_to_sql(bf_df, [ops.tan_op.as_expr(col_name)], [col_name]) - - snapshot.assert_match(sql, "out.sql") - - -def test_tanh(scalar_types_df: bpd.DataFrame, snapshot): - col_name = "float64_col" - bf_df = scalar_types_df[[col_name]] - sql = utils._apply_ops_to_sql(bf_df, [ops.tanh_op.as_expr(col_name)], [col_name]) - - snapshot.assert_match(sql, "out.sql") - - -def test_add_numeric(scalar_types_df: bpd.DataFrame, snapshot): - bf_df = scalar_types_df[["int64_col", "bool_col"]] - - bf_df["int_add_int"] = bf_df["int64_col"] + bf_df["int64_col"] - bf_df["int_add_1"] = bf_df["int64_col"] + 1 - bf_df["int_add_null"] = bf_df["int64_col"] + pd.NA - - bf_df["int_add_bool"] = bf_df["int64_col"] + bf_df["bool_col"] - bf_df["bool_add_int"] = bf_df["bool_col"] + bf_df["int64_col"] - - snapshot.assert_match(bf_df.sql, "out.sql") - - -def test_add_string(scalar_types_df: bpd.DataFrame, snapshot): - bf_df = scalar_types_df[["string_col"]] - sql = utils._apply_binary_op(bf_df, ops.add_op, "string_col", ex.const("a")) - - snapshot.assert_match(sql, "out.sql") - - -def test_add_timedelta(scalar_types_df: bpd.DataFrame, snapshot): - bf_df = scalar_types_df[["timestamp_col", "date_col"]] - timedelta = pd.Timedelta(1, unit="d") - - bf_df["date_add_timedelta"] = bf_df["date_col"] + timedelta - bf_df["timestamp_add_timedelta"] = bf_df["timestamp_col"] + timedelta - bf_df["timedelta_add_date"] = timedelta + bf_df["date_col"] - bf_df["timedelta_add_timestamp"] = timedelta + bf_df["timestamp_col"] - bf_df["timedelta_add_timedelta"] = timedelta + timedelta - - snapshot.assert_match(bf_df.sql, "out.sql") - - -def test_add_unsupported_raises(scalar_types_df: bpd.DataFrame): - with pytest.raises(TypeError): - utils._apply_binary_op(scalar_types_df, ops.add_op, "timestamp_col", "date_col") - - with pytest.raises(TypeError): - utils._apply_binary_op(scalar_types_df, ops.add_op, "int64_col", "string_col") - - -def test_div_numeric(scalar_types_df: bpd.DataFrame, snapshot): - bf_df = scalar_types_df[["int64_col", "bool_col", "float64_col"]] - - bf_df["int_div_int"] = bf_df["int64_col"] / bf_df["int64_col"] - bf_df["int_div_1"] = bf_df["int64_col"] / 1 - bf_df["int_div_0"] = bf_df["int64_col"] / 0.0 - bf_df["int_div_null"] = bf_df["int64_col"] / pd.NA - - bf_df["int_div_float"] = bf_df["int64_col"] / bf_df["float64_col"] - bf_df["float_div_int"] = bf_df["float64_col"] / bf_df["int64_col"] - bf_df["float_div_0"] = bf_df["float64_col"] / 0.0 - - bf_df["int_div_bool"] = bf_df["int64_col"] / bf_df["bool_col"] - bf_df["bool_div_int"] = bf_df["bool_col"] / bf_df["int64_col"] - - snapshot.assert_match(bf_df.sql, "out.sql") - - -def test_div_timedelta(scalar_types_df: bpd.DataFrame, snapshot): - bf_df = scalar_types_df[["timestamp_col", "int64_col"]] - timedelta = pd.Timedelta(1, unit="d") - bf_df["timedelta_div_numeric"] = timedelta / bf_df["int64_col"] - - snapshot.assert_match(bf_df.sql, "out.sql") - - -def test_euclidean_distance(repeated_types_df: bpd.DataFrame, snapshot): - col_names = ["int_list_col", "numeric_list_col"] - bf_df = repeated_types_df[col_names] - - sql = utils._apply_ops_to_sql( - bf_df, - [ - ops.euclidean_distance_op.as_expr("int_list_col", "int_list_col"), - ops.euclidean_distance_op.as_expr("numeric_list_col", "numeric_list_col"), - ], - ["int_list_col", "numeric_list_col"], - ) - snapshot.assert_match(sql, "out.sql") - - -def test_floordiv_numeric(scalar_types_df: bpd.DataFrame, snapshot): - bf_df = scalar_types_df[["int64_col", "bool_col", "float64_col"]] - - bf_df["int_div_int"] = bf_df["int64_col"] // bf_df["int64_col"] - bf_df["int_div_1"] = bf_df["int64_col"] // 1 - bf_df["int_div_0"] = bf_df["int64_col"] // 0.0 - bf_df["int_div_null"] = bf_df["int64_col"] // pd.NA - - bf_df["int_div_float"] = bf_df["int64_col"] // bf_df["float64_col"] - bf_df["float_div_int"] = bf_df["float64_col"] // bf_df["int64_col"] - bf_df["float_div_0"] = bf_df["float64_col"] // 0.0 - bf_df["float_div_null"] = bf_df["float64_col"] // pd.NA - - bf_df["int_div_bool"] = bf_df["int64_col"] // bf_df["bool_col"] - bf_df["bool_div_int"] = bf_df["bool_col"] // bf_df["int64_col"] - - snapshot.assert_match(bf_df.sql, "out.sql") - - -def test_floordiv_timedelta(scalar_types_df: bpd.DataFrame, snapshot): - bf_df = scalar_types_df[["timestamp_col", "date_col"]] - timedelta = pd.Timedelta(1, unit="d") - - bf_df["timedelta_div_numeric"] = timedelta // 2 - - snapshot.assert_match(bf_df.sql, "out.sql") - - -def test_manhattan_distance(repeated_types_df: bpd.DataFrame, snapshot): - col_names = ["float_list_col", "numeric_list_col"] - bf_df = repeated_types_df[col_names] - - sql = utils._apply_ops_to_sql( - bf_df, - [ - ops.manhattan_distance_op.as_expr("float_list_col", "float_list_col"), - ops.manhattan_distance_op.as_expr("numeric_list_col", "numeric_list_col"), - ], - ["float_list_col", "numeric_list_col"], - ) - snapshot.assert_match(sql, "out.sql") - - -def test_mul_numeric(scalar_types_df: bpd.DataFrame, snapshot): - bf_df = scalar_types_df[["int64_col", "bool_col"]] - - bf_df["int_mul_int"] = bf_df["int64_col"] * bf_df["int64_col"] - bf_df["int_mul_1"] = bf_df["int64_col"] * 1 - bf_df["int_mul_null"] = bf_df["int64_col"] * pd.NA - - bf_df["int_mul_bool"] = bf_df["int64_col"] * bf_df["bool_col"] - bf_df["bool_mul_int"] = bf_df["bool_col"] * bf_df["int64_col"] - - snapshot.assert_match(bf_df.sql, "out.sql") - - -def test_mul_timedelta(scalar_types_df: bpd.DataFrame, snapshot): - bf_df = scalar_types_df[["timestamp_col", "int64_col", "duration_col"]] - bf_df["duration_col"] = bpd.to_timedelta(bf_df["duration_col"], unit="us") - - bf_df["timedelta_mul_numeric"] = bf_df["duration_col"] * bf_df["int64_col"] - bf_df["numeric_mul_timedelta"] = bf_df["int64_col"] * bf_df["duration_col"] - - snapshot.assert_match(bf_df.sql, "out.sql") - - -def test_mod_numeric(scalar_types_df: bpd.DataFrame, snapshot): - bf_df = scalar_types_df[["int64_col", "float64_col"]] - - bf_df["int_mod_int"] = bf_df["int64_col"] % bf_df["int64_col"] - bf_df["int_mod_int_neg"] = bf_df["int64_col"] % -bf_df["int64_col"] - bf_df["int_mod_1"] = bf_df["int64_col"] % 1 - bf_df["int_mod_0"] = bf_df["int64_col"] % 0 - - bf_df["float_mod_float"] = bf_df["float64_col"] % bf_df["float64_col"] - bf_df["float_mod_float_neg"] = bf_df["float64_col"] % -bf_df["float64_col"] - bf_df["float_mod_1"] = bf_df["float64_col"] % 1 - bf_df["float_mod_0"] = bf_df["float64_col"] % 0 - - bf_df["float_mod_null"] = bf_df["float64_col"] % pd.NA - - snapshot.assert_match(bf_df.sql, "out.sql") - - -def test_sub_numeric(scalar_types_df: bpd.DataFrame, snapshot): - bf_df = scalar_types_df[["int64_col", "bool_col"]] - - bf_df["int_sub_int"] = bf_df["int64_col"] - bf_df["int64_col"] - bf_df["int_sub_1"] = bf_df["int64_col"] - 1 - bf_df["int_sub_null"] = bf_df["int64_col"] - pd.NA - - bf_df["int_sub_bool"] = bf_df["int64_col"] - bf_df["bool_col"] - bf_df["bool_sub_int"] = bf_df["bool_col"] - bf_df["int64_col"] - - snapshot.assert_match(bf_df.sql, "out.sql") - - -def test_sub_timedelta(scalar_types_df: bpd.DataFrame, snapshot): - bf_df = scalar_types_df[["timestamp_col", "duration_col", "date_col"]] - bf_df["duration_col"] = bpd.to_timedelta(bf_df["duration_col"], unit="us") - - bf_df["date_sub_timedelta"] = bf_df["date_col"] - bf_df["duration_col"] - bf_df["timestamp_sub_timedelta"] = bf_df["timestamp_col"] - bf_df["duration_col"] - bf_df["timestamp_sub_date"] = bf_df["date_col"] - bf_df["date_col"] - bf_df["date_sub_timestamp"] = bf_df["timestamp_col"] - bf_df["timestamp_col"] - bf_df["timedelta_sub_timedelta"] = bf_df["duration_col"] - bf_df["duration_col"] - - snapshot.assert_match(bf_df.sql, "out.sql") - - -def test_sub_unsupported_raises(scalar_types_df: bpd.DataFrame): - with pytest.raises(TypeError): - utils._apply_binary_op(scalar_types_df, ops.sub_op, "string_col", "string_col") - - with pytest.raises(TypeError): - utils._apply_binary_op(scalar_types_df, ops.sub_op, "int64_col", "string_col") - - -def test_unsafe_pow_op(scalar_types_df: bpd.DataFrame, snapshot): - # Choose certain row so the sql execution won't fail even with unsafe_pow_op. - bf_df = scalar_types_df[ - (scalar_types_df["int64_col"] >= 0) & (scalar_types_df["int64_col"] <= 10) - ] - bf_df = bf_df[["int64_col", "float64_col", "bool_col"]] - - int64_col_id = bf_df["int64_col"]._value_column - float64_col_id = bf_df["float64_col"]._value_column - bool_col_id = bf_df["bool_col"]._value_column - - sql = utils._apply_ops_to_sql( - bf_df, - [ - ops.unsafe_pow_op.as_expr(int64_col_id, int64_col_id), - ops.unsafe_pow_op.as_expr(int64_col_id, float64_col_id), - ops.unsafe_pow_op.as_expr(float64_col_id, int64_col_id), - ops.unsafe_pow_op.as_expr(float64_col_id, float64_col_id), - ops.unsafe_pow_op.as_expr(int64_col_id, bool_col_id), - ops.unsafe_pow_op.as_expr(bool_col_id, int64_col_id), - ], - [ - "int_pow_int", - "int_pow_float", - "float_pow_int", - "float_pow_float", - "int_pow_bool", - "bool_pow_int", - ], - ) - snapshot.assert_match(sql, "out.sql") diff --git a/tests/unit/core/compile/sqlglot/expressions/test_string_ops.py b/tests/unit/core/compile/sqlglot/expressions/test_string_ops.py deleted file mode 100644 index 67efcfb08ca..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/test_string_ops.py +++ /dev/null @@ -1,339 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import pytest - -import bigframes.core.expression as ex -import bigframes.pandas as bpd -from bigframes import operations as ops -from bigframes.testing import utils - -pytest.importorskip("pytest_snapshot") - - -def test_capitalize(scalar_types_df: bpd.DataFrame, snapshot): - col_name = "string_col" - bf_df = scalar_types_df[[col_name]] - sql = utils._apply_ops_to_sql( - bf_df, [ops.capitalize_op.as_expr(col_name)], [col_name] - ) - - snapshot.assert_match(sql, "out.sql") - - -def test_endswith(scalar_types_df: bpd.DataFrame, snapshot): - col_name = "string_col" - bf_df = scalar_types_df[[col_name]] - ops_map = { - "single": ops.EndsWithOp(pat=("ab",)).as_expr(col_name), - "double": ops.EndsWithOp(pat=("ab", "cd")).as_expr(col_name), - "empty": ops.EndsWithOp(pat=()).as_expr(col_name), - } - sql = utils._apply_ops_to_sql(bf_df, list(ops_map.values()), list(ops_map.keys())) - snapshot.assert_match(sql, "out.sql") - - -def test_isalnum(scalar_types_df: bpd.DataFrame, snapshot): - col_name = "string_col" - bf_df = scalar_types_df[[col_name]] - sql = utils._apply_ops_to_sql(bf_df, [ops.isalnum_op.as_expr(col_name)], [col_name]) - - snapshot.assert_match(sql, "out.sql") - - -def test_isalpha(scalar_types_df: bpd.DataFrame, snapshot): - col_name = "string_col" - bf_df = scalar_types_df[[col_name]] - sql = utils._apply_ops_to_sql(bf_df, [ops.isalpha_op.as_expr(col_name)], [col_name]) - - snapshot.assert_match(sql, "out.sql") - - -def test_isdecimal(scalar_types_df: bpd.DataFrame, snapshot): - col_name = "string_col" - bf_df = scalar_types_df[[col_name]] - sql = utils._apply_ops_to_sql( - bf_df, [ops.isdecimal_op.as_expr(col_name)], [col_name] - ) - - snapshot.assert_match(sql, "out.sql") - - -def test_isdigit(scalar_types_df: bpd.DataFrame, snapshot): - col_name = "string_col" - bf_df = scalar_types_df[[col_name]] - sql = utils._apply_ops_to_sql(bf_df, [ops.isdigit_op.as_expr(col_name)], [col_name]) - - snapshot.assert_match(sql, "out.sql") - - -def test_islower(scalar_types_df: bpd.DataFrame, snapshot): - col_name = "string_col" - bf_df = scalar_types_df[[col_name]] - sql = utils._apply_ops_to_sql(bf_df, [ops.islower_op.as_expr(col_name)], [col_name]) - - snapshot.assert_match(sql, "out.sql") - - -def test_isnumeric(scalar_types_df: bpd.DataFrame, snapshot): - col_name = "string_col" - bf_df = scalar_types_df[[col_name]] - sql = utils._apply_ops_to_sql( - bf_df, [ops.isnumeric_op.as_expr(col_name)], [col_name] - ) - - snapshot.assert_match(sql, "out.sql") - - -def test_isspace(scalar_types_df: bpd.DataFrame, snapshot): - col_name = "string_col" - bf_df = scalar_types_df[[col_name]] - sql = utils._apply_ops_to_sql(bf_df, [ops.isspace_op.as_expr(col_name)], [col_name]) - - snapshot.assert_match(sql, "out.sql") - - -def test_isupper(scalar_types_df: bpd.DataFrame, snapshot): - col_name = "string_col" - bf_df = scalar_types_df[[col_name]] - sql = utils._apply_ops_to_sql(bf_df, [ops.isupper_op.as_expr(col_name)], [col_name]) - - snapshot.assert_match(sql, "out.sql") - - -def test_len(scalar_types_df: bpd.DataFrame, snapshot): - col_name = "string_col" - bf_df = scalar_types_df[[col_name]] - sql = utils._apply_ops_to_sql(bf_df, [ops.len_op.as_expr(col_name)], [col_name]) - - snapshot.assert_match(sql, "out.sql") - - -def test_len_w_array(repeated_types_df: bpd.DataFrame, snapshot): - col_name = "int_list_col" - bf_df = repeated_types_df[[col_name]] - sql = utils._apply_ops_to_sql(bf_df, [ops.len_op.as_expr(col_name)], [col_name]) - - snapshot.assert_match(sql, "out.sql") - - -def test_lower(scalar_types_df: bpd.DataFrame, snapshot): - col_name = "string_col" - bf_df = scalar_types_df[[col_name]] - sql = utils._apply_ops_to_sql(bf_df, [ops.lower_op.as_expr(col_name)], [col_name]) - - snapshot.assert_match(sql, "out.sql") - - -def test_lstrip(scalar_types_df: bpd.DataFrame, snapshot): - col_name = "string_col" - bf_df = scalar_types_df[[col_name]] - sql = utils._apply_ops_to_sql( - bf_df, [ops.StrLstripOp(" ").as_expr(col_name)], [col_name] - ) - - snapshot.assert_match(sql, "out.sql") - - -def test_replace_str(scalar_types_df: bpd.DataFrame, snapshot): - col_name = "string_col" - bf_df = scalar_types_df[[col_name]] - sql = utils._apply_ops_to_sql( - bf_df, [ops.ReplaceStrOp("e", "a").as_expr(col_name)], [col_name] - ) - snapshot.assert_match(sql, "out.sql") - - -def test_regex_replace_str(scalar_types_df: bpd.DataFrame, snapshot): - col_name = "string_col" - bf_df = scalar_types_df[[col_name]] - sql = utils._apply_ops_to_sql( - bf_df, [ops.RegexReplaceStrOp(r"e", "a").as_expr(col_name)], [col_name] - ) - snapshot.assert_match(sql, "out.sql") - - -def test_reverse(scalar_types_df: bpd.DataFrame, snapshot): - col_name = "string_col" - bf_df = scalar_types_df[[col_name]] - sql = utils._apply_ops_to_sql(bf_df, [ops.reverse_op.as_expr(col_name)], [col_name]) - - snapshot.assert_match(sql, "out.sql") - - -def test_rstrip(scalar_types_df: bpd.DataFrame, snapshot): - col_name = "string_col" - bf_df = scalar_types_df[[col_name]] - sql = utils._apply_ops_to_sql( - bf_df, [ops.StrRstripOp(" ").as_expr(col_name)], [col_name] - ) - - snapshot.assert_match(sql, "out.sql") - - -def test_startswith(scalar_types_df: bpd.DataFrame, snapshot): - col_name = "string_col" - bf_df = scalar_types_df[[col_name]] - ops_map = { - "single": ops.StartsWithOp(pat=("ab",)).as_expr(col_name), - "double": ops.StartsWithOp(pat=("ab", "cd")).as_expr(col_name), - "empty": ops.StartsWithOp(pat=()).as_expr(col_name), - } - sql = utils._apply_ops_to_sql(bf_df, list(ops_map.values()), list(ops_map.keys())) - snapshot.assert_match(sql, "out.sql") - - -def test_str_get(scalar_types_df: bpd.DataFrame, snapshot): - col_name = "string_col" - bf_df = scalar_types_df[[col_name]] - sql = utils._apply_ops_to_sql( - bf_df, [ops.GetItemOp(1).as_expr(col_name)], [col_name] - ) - - snapshot.assert_match(sql, "out.sql") - - -def test_str_pad(scalar_types_df: bpd.DataFrame, snapshot): - col_name = "string_col" - bf_df = scalar_types_df[[col_name]] - ops_map = { - "left": ops.StrPadOp(length=10, fillchar="-", side="left").as_expr(col_name), - "right": ops.StrPadOp(length=10, fillchar="-", side="right").as_expr(col_name), - "both": ops.StrPadOp(length=10, fillchar="-", side="both").as_expr(col_name), - } - sql = utils._apply_ops_to_sql(bf_df, list(ops_map.values()), list(ops_map.keys())) - snapshot.assert_match(sql, "out.sql") - - -def test_str_slice(scalar_types_df: bpd.DataFrame, snapshot): - col_name = "string_col" - bf_df = scalar_types_df[[col_name]] - ops_map = { - "1_3": ops.StrSliceOp(1, 3).as_expr(col_name), - "none_3": ops.StrSliceOp(None, 3).as_expr(col_name), - "1_none": ops.StrSliceOp(1, None).as_expr(col_name), - "m3_none": ops.StrSliceOp(-3, None).as_expr(col_name), - "none_m3": ops.StrSliceOp(None, -3).as_expr(col_name), - "m5_m3": ops.StrSliceOp(-5, -3).as_expr(col_name), - "1_m3": ops.StrSliceOp(1, -3).as_expr(col_name), - "m3_5": ops.StrSliceOp(-3, 5).as_expr(col_name), - } - sql = utils._apply_ops_to_sql(bf_df, list(ops_map.values()), list(ops_map.keys())) - - snapshot.assert_match(sql, "out.sql") - - -def test_strip(scalar_types_df: bpd.DataFrame, snapshot): - col_name = "string_col" - bf_df = scalar_types_df[[col_name]] - sql = utils._apply_ops_to_sql( - bf_df, [ops.StrStripOp(" ").as_expr(col_name)], [col_name] - ) - - snapshot.assert_match(sql, "out.sql") - - -def test_str_contains(scalar_types_df: bpd.DataFrame, snapshot): - col_name = "string_col" - bf_df = scalar_types_df[[col_name]] - sql = utils._apply_ops_to_sql( - bf_df, [ops.StrContainsOp("e").as_expr(col_name)], [col_name] - ) - - snapshot.assert_match(sql, "out.sql") - - -def test_str_contains_regex(scalar_types_df: bpd.DataFrame, snapshot): - col_name = "string_col" - bf_df = scalar_types_df[[col_name]] - sql = utils._apply_ops_to_sql( - bf_df, [ops.StrContainsRegexOp("e").as_expr(col_name)], [col_name] - ) - - snapshot.assert_match(sql, "out.sql") - - -def test_str_extract(scalar_types_df: bpd.DataFrame, snapshot): - col_name = "string_col" - bf_df = scalar_types_df[[col_name]] - ops_map = { - "zero": ops.StrExtractOp(r"([a-z]*)", 0).as_expr(col_name), - "one": ops.StrExtractOp(r"([a-z]*)", 1).as_expr(col_name), - } - sql = utils._apply_ops_to_sql(bf_df, list(ops_map.values()), list(ops_map.keys())) - - snapshot.assert_match(sql, "out.sql") - - -def test_str_repeat(scalar_types_df: bpd.DataFrame, snapshot): - col_name = "string_col" - bf_df = scalar_types_df[[col_name]] - sql = utils._apply_ops_to_sql( - bf_df, [ops.StrRepeatOp(2).as_expr(col_name)], [col_name] - ) - snapshot.assert_match(sql, "out.sql") - - -def test_str_find(scalar_types_df: bpd.DataFrame, snapshot): - col_name = "string_col" - bf_df = scalar_types_df[[col_name]] - ops_map = { - "none_none": ops.StrFindOp("e", start=None, end=None).as_expr(col_name), - "start_none": ops.StrFindOp("e", start=2, end=None).as_expr(col_name), - "none_end": ops.StrFindOp("e", start=None, end=5).as_expr(col_name), - "start_end": ops.StrFindOp("e", start=2, end=5).as_expr(col_name), - } - sql = utils._apply_ops_to_sql(bf_df, list(ops_map.values()), list(ops_map.keys())) - - snapshot.assert_match(sql, "out.sql") - - -def test_string_split(scalar_types_df: bpd.DataFrame, snapshot): - col_name = "string_col" - bf_df = scalar_types_df[[col_name]] - sql = utils._apply_ops_to_sql( - bf_df, [ops.StringSplitOp(pat=",").as_expr(col_name)], [col_name] - ) - snapshot.assert_match(sql, "out.sql") - - -def test_upper(scalar_types_df: bpd.DataFrame, snapshot): - col_name = "string_col" - bf_df = scalar_types_df[[col_name]] - sql = utils._apply_ops_to_sql(bf_df, [ops.upper_op.as_expr(col_name)], [col_name]) - - snapshot.assert_match(sql, "out.sql") - - -def test_zfill(scalar_types_df: bpd.DataFrame, snapshot): - col_name = "string_col" - bf_df = scalar_types_df[[col_name]] - sql = utils._apply_ops_to_sql( - bf_df, [ops.ZfillOp(width=10).as_expr(col_name)], [col_name] - ) - snapshot.assert_match(sql, "out.sql") - - -def test_add_string(scalar_types_df: bpd.DataFrame, snapshot): - bf_df = scalar_types_df[["string_col"]] - sql = utils._apply_binary_op(bf_df, ops.add_op, "string_col", ex.const("a")) - - snapshot.assert_match(sql, "out.sql") - - -def test_strconcat(scalar_types_df: bpd.DataFrame, snapshot): - bf_df = scalar_types_df[["string_col"]] - sql = utils._apply_binary_op(bf_df, ops.strconcat_op, "string_col", ex.const("a")) - - snapshot.assert_match(sql, "out.sql") diff --git a/tests/unit/core/compile/sqlglot/expressions/test_struct_ops.py b/tests/unit/core/compile/sqlglot/expressions/test_struct_ops.py deleted file mode 100644 index 5e1f3d505cb..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/test_struct_ops.py +++ /dev/null @@ -1,68 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import typing - -import pytest - -import bigframes.pandas as bpd -from bigframes import operations as ops -from bigframes.core import expression as ex -from bigframes.testing import utils - -pytest.importorskip("pytest_snapshot") - - -def _apply_nary_op( - obj: bpd.DataFrame, - op: ops.NaryOp, - *args: typing.Union[str, ex.Expression], -) -> str: - """Applies a nary op to the given DataFrame and return the SQL representing - the resulting DataFrame.""" - array_value = obj._block.expr - op_expr = op.as_expr(*args) - result, col_ids = array_value.compute_values([op_expr]) - - # Rename columns for deterministic golden SQL results. - assert len(col_ids) == 1 - result = result.rename_columns({col_ids[0]: "result_col"}).select_columns( - ["result_col"] - ) - - sql = result.session._executor.to_sql(result, enable_cache=False) - return sql - - -def test_struct_field(nested_structs_types_df: bpd.DataFrame, snapshot): - col_name = "people" - bf_df = nested_structs_types_df[[col_name]] - - ops_map = { - # When a name string is provided. - "string": ops.StructFieldOp("name").as_expr(col_name), - # When an index integer is provided. - "int": ops.StructFieldOp(0).as_expr(col_name), - } - sql = utils._apply_ops_to_sql(bf_df, list(ops_map.values()), list(ops_map.keys())) - - snapshot.assert_match(sql, "out.sql") - - -def test_struct_op(scalar_types_df: bpd.DataFrame, snapshot): - bf_df = scalar_types_df[["bool_col", "int64_col", "float64_col", "string_col"]] - op = ops.StructOp(column_names=tuple(bf_df.columns.tolist())) - sql = _apply_nary_op(bf_df, op, *bf_df.columns.tolist()) - - snapshot.assert_match(sql, "out.sql") diff --git a/tests/unit/core/compile/sqlglot/expressions/test_timedelta_ops.py b/tests/unit/core/compile/sqlglot/expressions/test_timedelta_ops.py deleted file mode 100644 index ae1f6d017c4..00000000000 --- a/tests/unit/core/compile/sqlglot/expressions/test_timedelta_ops.py +++ /dev/null @@ -1,41 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import pytest - -import bigframes.pandas as bpd -from bigframes import operations as ops -from bigframes.testing import utils - -pytest.importorskip("pytest_snapshot") - - -def test_to_timedelta(scalar_types_df: bpd.DataFrame, snapshot): - bf_df = scalar_types_df[["int64_col", "float64_col"]] - bf_df["duration_us"] = bpd.to_timedelta(bf_df["int64_col"], "us") - bf_df["duration_s"] = bpd.to_timedelta(bf_df["float64_col"], "s") - bf_df["duration_w"] = bpd.to_timedelta(bf_df["int64_col"], "h") - bf_df["duration_on_duration"] = bpd.to_timedelta(bf_df["duration_us"], "ms") - - snapshot.assert_match(bf_df.sql, "out.sql") - - -def test_timedelta_floor(scalar_types_df: bpd.DataFrame, snapshot): - col_name = "int64_col" - bf_df = scalar_types_df[[col_name]] - sql = utils._apply_ops_to_sql( - bf_df, [ops.timedelta_floor_op.as_expr(col_name)], [col_name] - ) - - snapshot.assert_match(sql, "out.sql") diff --git a/tests/unit/core/compile/sqlglot/snapshots/test_compile_aggregate/test_compile_aggregate/out.sql b/tests/unit/core/compile/sqlglot/snapshots/test_compile_aggregate/test_compile_aggregate/out.sql deleted file mode 100644 index cfd9c7c87f0..00000000000 --- a/tests/unit/core/compile/sqlglot/snapshots/test_compile_aggregate/test_compile_aggregate/out.sql +++ /dev/null @@ -1,23 +0,0 @@ -WITH `bfcte_0` AS ( - SELECT - `bool_col`, - `int64_too`, - `int64_too` AS `bfcol_2`, - `bool_col` AS `bfcol_3` - FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` -), `bfcte_1` AS ( - SELECT - `bfcol_3`, - COALESCE(SUM(`bfcol_2`), 0) AS `bfcol_6` - FROM `bfcte_0` - WHERE - NOT `bfcol_3` IS NULL - GROUP BY - `bfcol_3` -) -SELECT - `bfcol_3` AS `bool_col`, - `bfcol_6` AS `int64_too` -FROM `bfcte_1` -ORDER BY - `bfcol_3` ASC NULLS LAST \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/snapshots/test_compile_aggregate/test_compile_aggregate_wo_dropna/out.sql b/tests/unit/core/compile/sqlglot/snapshots/test_compile_aggregate/test_compile_aggregate_wo_dropna/out.sql deleted file mode 100644 index e71099d82e6..00000000000 --- a/tests/unit/core/compile/sqlglot/snapshots/test_compile_aggregate/test_compile_aggregate_wo_dropna/out.sql +++ /dev/null @@ -1,21 +0,0 @@ -WITH `bfcte_0` AS ( - SELECT - `bool_col`, - `int64_too`, - `int64_too` AS `bfcol_2`, - `bool_col` AS `bfcol_3` - FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` -), `bfcte_1` AS ( - SELECT - `bfcol_3`, - COALESCE(SUM(`bfcol_2`), 0) AS `bfcol_6` - FROM `bfcte_0` - GROUP BY - `bfcol_3` -) -SELECT - `bfcol_3` AS `bool_col`, - `bfcol_6` AS `int64_too` -FROM `bfcte_1` -ORDER BY - `bfcol_3` ASC NULLS LAST \ No newline at end of file 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 deleted file mode 100644 index 48614357865..00000000000 --- a/tests/unit/core/compile/sqlglot/snapshots/test_compile_concat/test_compile_concat/out.sql +++ /dev/null @@ -1,48 +0,0 @@ -WITH `bfcte_0` AS ( - SELECT - `rowindex` AS `bfcol_3`, - `rowindex` AS `bfcol_4`, - `int64_col` AS `bfcol_5`, - `string_col` AS `bfcol_6` - FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` -), `bfcte_1` AS ( - SELECT - `bfcol_17` AS `bfcol_23`, - `bfcol_18` AS `bfcol_24`, - `bfcol_19` AS `bfcol_25`, - `bfcol_20` AS `bfcol_26`, - `bfcol_21` AS `bfcol_27`, - `bfcol_22` AS `bfcol_28` - FROM ( - ( - SELECT - `bfcol_3` AS `bfcol_17`, - `bfcol_4` AS `bfcol_18`, - `bfcol_5` AS `bfcol_19`, - `bfcol_6` AS `bfcol_20`, - 0 AS `bfcol_21`, - ROW_NUMBER() OVER () - 1 AS `bfcol_22` - FROM `bfcte_0` - ) - UNION ALL - ( - SELECT - `bfcol_3` AS `bfcol_11`, - `bfcol_4` AS `bfcol_12`, - `bfcol_5` AS `bfcol_13`, - `bfcol_6` AS `bfcol_14`, - 1 AS `bfcol_15`, - ROW_NUMBER() OVER () - 1 AS `bfcol_16` - FROM `bfcte_0` - ) - ) -) -SELECT - `bfcol_23` AS `rowindex`, - `bfcol_24` AS `rowindex_1`, - `bfcol_25` AS `int64_col`, - `bfcol_26` AS `string_col` -FROM `bfcte_1` -ORDER BY - `bfcol_27` ASC NULLS LAST, - `bfcol_28` ASC NULLS LAST \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/snapshots/test_compile_concat/test_compile_concat_filter_sorted/out.sql b/tests/unit/core/compile/sqlglot/snapshots/test_compile_concat/test_compile_concat_filter_sorted/out.sql deleted file mode 100644 index 477a47036ae..00000000000 --- a/tests/unit/core/compile/sqlglot/snapshots/test_compile_concat/test_compile_concat_filter_sorted/out.sql +++ /dev/null @@ -1,63 +0,0 @@ -WITH `bfcte_0` AS ( - SELECT - `float64_col` AS `bfcol_7`, - `int64_too` AS `bfcol_8` - FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` - WHERE - `bool_col` -), `bfcte_1` AS ( - SELECT - `float64_col` AS `bfcol_5`, - `int64_col` AS `bfcol_6` - FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` -), `bfcte_2` AS ( - SELECT - `bfcol_21` AS `bfcol_33`, - `bfcol_22` AS `bfcol_34`, - `bfcol_23` AS `bfcol_35`, - `bfcol_24` AS `bfcol_36` - FROM ( - ( - SELECT - `bfcol_5` AS `bfcol_21`, - `bfcol_6` AS `bfcol_22`, - 0 AS `bfcol_23`, - ROW_NUMBER() OVER (ORDER BY `bfcol_6` ASC NULLS LAST) - 1 AS `bfcol_24` - FROM `bfcte_1` - ) - UNION ALL - ( - SELECT - `bfcol_7` AS `bfcol_29`, - `bfcol_8` AS `bfcol_30`, - 1 AS `bfcol_31`, - ROW_NUMBER() OVER () - 1 AS `bfcol_32` - FROM `bfcte_0` - ) - UNION ALL - ( - SELECT - `bfcol_5` AS `bfcol_17`, - `bfcol_6` AS `bfcol_18`, - 2 AS `bfcol_19`, - ROW_NUMBER() OVER (ORDER BY `bfcol_6` ASC NULLS LAST) - 1 AS `bfcol_20` - FROM `bfcte_1` - ) - UNION ALL - ( - SELECT - `bfcol_7` AS `bfcol_25`, - `bfcol_8` AS `bfcol_26`, - 3 AS `bfcol_27`, - ROW_NUMBER() OVER () - 1 AS `bfcol_28` - FROM `bfcte_0` - ) - ) -) -SELECT - `bfcol_33` AS `float64_col`, - `bfcol_34` AS `int64_col` -FROM `bfcte_2` -ORDER BY - `bfcol_35` ASC NULLS LAST, - `bfcol_36` ASC NULLS LAST \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/snapshots/test_compile_explode/test_compile_explode_dataframe/out.sql b/tests/unit/core/compile/sqlglot/snapshots/test_compile_explode/test_compile_explode_dataframe/out.sql deleted file mode 100644 index e2a80e201bb..00000000000 --- a/tests/unit/core/compile/sqlglot/snapshots/test_compile_explode/test_compile_explode_dataframe/out.sql +++ /dev/null @@ -1,21 +0,0 @@ -WITH `bfcte_0` AS ( - SELECT - `rowindex`, - `int_list_col`, - `string_list_col` - FROM `bigframes-dev`.`sqlglot_test`.`repeated_types` AS `bft_0` -), `bfcte_1` AS ( - SELECT - * - REPLACE (`int_list_col`[SAFE_OFFSET(`bfcol_13`)] AS `int_list_col`, `string_list_col`[SAFE_OFFSET(`bfcol_13`)] AS `string_list_col`) - FROM `bfcte_0` - LEFT JOIN UNNEST(GENERATE_ARRAY(0, LEAST(ARRAY_LENGTH(`int_list_col`) - 1, ARRAY_LENGTH(`string_list_col`) - 1))) AS `bfcol_13` WITH OFFSET AS `bfcol_7` -) -SELECT - `rowindex`, - `rowindex` AS `rowindex_1`, - `int_list_col`, - `string_list_col` -FROM `bfcte_1` -ORDER BY - `bfcol_7` ASC NULLS LAST \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/snapshots/test_compile_explode/test_compile_explode_series/out.sql b/tests/unit/core/compile/sqlglot/snapshots/test_compile_explode/test_compile_explode_series/out.sql deleted file mode 100644 index 03ac4d0e03a..00000000000 --- a/tests/unit/core/compile/sqlglot/snapshots/test_compile_explode/test_compile_explode_series/out.sql +++ /dev/null @@ -1,18 +0,0 @@ -WITH `bfcte_0` AS ( - SELECT - `rowindex`, - `int_list_col` - FROM `bigframes-dev`.`sqlglot_test`.`repeated_types` AS `bft_0` -), `bfcte_1` AS ( - SELECT - * - REPLACE (`bfcol_8` AS `int_list_col`) - FROM `bfcte_0` - LEFT JOIN UNNEST(`int_list_col`) AS `bfcol_8` WITH OFFSET AS `bfcol_4` -) -SELECT - `rowindex`, - `int_list_col` -FROM `bfcte_1` -ORDER BY - `bfcol_4` ASC NULLS LAST \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/snapshots/test_compile_filter/test_compile_filter/out.sql b/tests/unit/core/compile/sqlglot/snapshots/test_compile_filter/test_compile_filter/out.sql deleted file mode 100644 index 3e367c1e1e2..00000000000 --- a/tests/unit/core/compile/sqlglot/snapshots/test_compile_filter/test_compile_filter/out.sql +++ /dev/null @@ -1,7 +0,0 @@ -SELECT - `rowindex`, - `rowindex` AS `rowindex_1`, - `int64_col` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` -WHERE - `rowindex` >= 1 \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/snapshots/test_compile_fromrange/test_compile_fromrange/out.sql b/tests/unit/core/compile/sqlglot/snapshots/test_compile_fromrange/test_compile_fromrange/out.sql deleted file mode 100644 index 4f4e2496498..00000000000 --- a/tests/unit/core/compile/sqlglot/snapshots/test_compile_fromrange/test_compile_fromrange/out.sql +++ /dev/null @@ -1,75 +0,0 @@ -WITH `bfcte_0` AS ( - SELECT - * - FROM UNNEST(ARRAY>[STRUCT(CAST('2021-01-01T13:00:00' AS DATETIME)), STRUCT(CAST('2021-01-01T13:00:01' AS DATETIME)), STRUCT(CAST('2021-01-01T13:00:02' AS DATETIME)), STRUCT(CAST('2021-01-01T13:00:03' AS DATETIME)), STRUCT(CAST('2021-01-01T13:00:04' AS DATETIME)), STRUCT(CAST('2021-01-01T13:00:05' AS DATETIME)), STRUCT(CAST('2021-01-01T13:00:06' AS DATETIME)), STRUCT(CAST('2021-01-01T13:00:07' AS DATETIME)), STRUCT(CAST('2021-01-01T13:00:08' AS DATETIME)), STRUCT(CAST('2021-01-01T13:00:09' AS DATETIME)), STRUCT(CAST('2021-01-01T13:00:10' AS DATETIME)), STRUCT(CAST('2021-01-01T13:00:11' AS DATETIME)), STRUCT(CAST('2021-01-01T13:00:12' AS DATETIME)), STRUCT(CAST('2021-01-01T13:00:13' AS DATETIME)), STRUCT(CAST('2021-01-01T13:00:14' AS DATETIME)), STRUCT(CAST('2021-01-01T13:00:15' AS DATETIME)), STRUCT(CAST('2021-01-01T13:00:16' AS DATETIME)), STRUCT(CAST('2021-01-01T13:00:17' AS DATETIME)), STRUCT(CAST('2021-01-01T13:00:18' AS DATETIME)), STRUCT(CAST('2021-01-01T13:00:19' AS DATETIME)), STRUCT(CAST('2021-01-01T13:00:20' AS DATETIME)), STRUCT(CAST('2021-01-01T13:00:21' AS DATETIME)), STRUCT(CAST('2021-01-01T13:00:22' AS DATETIME)), STRUCT(CAST('2021-01-01T13:00:23' AS DATETIME)), STRUCT(CAST('2021-01-01T13:00:24' AS DATETIME)), STRUCT(CAST('2021-01-01T13:00:25' AS DATETIME)), STRUCT(CAST('2021-01-01T13:00:26' AS DATETIME)), STRUCT(CAST('2021-01-01T13:00:27' AS DATETIME)), STRUCT(CAST('2021-01-01T13:00:28' AS DATETIME)), STRUCT(CAST('2021-01-01T13:00:29' AS DATETIME))]) -), `bfcte_1` AS ( - SELECT - * - FROM UNNEST(ARRAY>[STRUCT(CAST('2021-01-01T13:00:00' AS DATETIME), 0, 10), STRUCT(CAST('2021-01-01T13:00:01' AS DATETIME), 1, 11), STRUCT(CAST('2021-01-01T13:00:02' AS DATETIME), 2, 12), STRUCT(CAST('2021-01-01T13:00:03' AS DATETIME), 3, 13), STRUCT(CAST('2021-01-01T13:00:04' AS DATETIME), 4, 14), STRUCT(CAST('2021-01-01T13:00:05' AS DATETIME), 5, 15), STRUCT(CAST('2021-01-01T13:00:06' AS DATETIME), 6, 16), STRUCT(CAST('2021-01-01T13:00:07' AS DATETIME), 7, 17), STRUCT(CAST('2021-01-01T13:00:08' AS DATETIME), 8, 18), STRUCT(CAST('2021-01-01T13:00:09' AS DATETIME), 9, 19), STRUCT(CAST('2021-01-01T13:00:10' AS DATETIME), 10, 20), STRUCT(CAST('2021-01-01T13:00:11' AS DATETIME), 11, 21), STRUCT(CAST('2021-01-01T13:00:12' AS DATETIME), 12, 22), STRUCT(CAST('2021-01-01T13:00:13' AS DATETIME), 13, 23), STRUCT(CAST('2021-01-01T13:00:14' AS DATETIME), 14, 24), STRUCT(CAST('2021-01-01T13:00:15' AS DATETIME), 15, 25), STRUCT(CAST('2021-01-01T13:00:16' AS DATETIME), 16, 26), STRUCT(CAST('2021-01-01T13:00:17' AS DATETIME), 17, 27), STRUCT(CAST('2021-01-01T13:00:18' AS DATETIME), 18, 28), STRUCT(CAST('2021-01-01T13:00:19' AS DATETIME), 19, 29), STRUCT(CAST('2021-01-01T13:00:20' AS DATETIME), 20, 30), STRUCT(CAST('2021-01-01T13:00:21' AS DATETIME), 21, 31), STRUCT(CAST('2021-01-01T13:00:22' AS DATETIME), 22, 32), STRUCT(CAST('2021-01-01T13:00:23' AS DATETIME), 23, 33), STRUCT(CAST('2021-01-01T13:00:24' AS DATETIME), 24, 34), STRUCT(CAST('2021-01-01T13:00:25' AS DATETIME), 25, 35), STRUCT(CAST('2021-01-01T13:00:26' AS DATETIME), 26, 36), STRUCT(CAST('2021-01-01T13:00:27' AS DATETIME), 27, 37), STRUCT(CAST('2021-01-01T13:00:28' AS DATETIME), 28, 38), STRUCT(CAST('2021-01-01T13:00:29' AS DATETIME), 29, 39)]) -), `bfcte_2` AS ( - SELECT - `bfcol_0` AS `bfcol_4` - FROM `bfcte_0` -), `bfcte_3` AS ( - SELECT - `bfcol_1` AS `bfcol_5`, - `bfcol_2` AS `bfcol_6`, - `bfcol_3` AS `bfcol_7` - FROM `bfcte_1` -), `bfcte_4` AS ( - SELECT - MIN(`bfcol_4`) AS `bfcol_8` - FROM `bfcte_2` -), `bfcte_5` AS ( - SELECT - `bfcol_6` AS `bfcol_11`, - `bfcol_7` AS `bfcol_12`, - CAST(FLOOR( - IEEE_DIVIDE( - UNIX_MICROS(CAST(`bfcol_5` AS TIMESTAMP)) - UNIX_MICROS(CAST(CAST(`bfcol_8` AS DATE) AS TIMESTAMP)), - 7000000 - ) - ) AS INT64) AS `bfcol_13` - FROM `bfcte_3` - CROSS JOIN `bfcte_4` -), `bfcte_6` AS ( - SELECT - CAST(FLOOR( - IEEE_DIVIDE( - UNIX_MICROS(CAST(`bfcol_4` AS TIMESTAMP)) - UNIX_MICROS(CAST(CAST(`bfcol_8` AS DATE) AS TIMESTAMP)), - 7000000 - ) - ) AS INT64) AS `bfcol_14` - FROM `bfcte_2` - CROSS JOIN `bfcte_4` -), `bfcte_7` AS ( - SELECT - MAX(`bfcol_14`) AS `bfcol_15` - FROM `bfcte_6` -), `bfcte_8` AS ( - SELECT - MIN(`bfcol_14`) AS `bfcol_16` - FROM `bfcte_6` -), `bfcte_9` AS ( - SELECT - `bfcol_27` AS `bfcol_17` - FROM `bfcte_8` - CROSS JOIN `bfcte_7` - CROSS JOIN UNNEST(GENERATE_ARRAY(`bfcol_16`, `bfcol_15`, 1)) AS `bfcol_27` -) -SELECT - CAST(TIMESTAMP_MICROS( - CAST(CAST(`bfcol_17` AS BIGNUMERIC) * 7000000 + CAST(UNIX_MICROS(CAST(CAST(`bfcol_8` AS DATE) AS TIMESTAMP)) AS BIGNUMERIC) AS INT64) - ) AS DATETIME) AS `timestamp_col`, - `bfcol_11` AS `int64_col`, - `bfcol_12` AS `int64_too` -FROM ( - SELECT - * - FROM `bfcte_9` - CROSS JOIN `bfcte_4` -) -LEFT JOIN `bfcte_5` - ON `bfcol_17` = `bfcol_13` -ORDER BY - `bfcol_17` ASC NULLS LAST diff --git a/tests/unit/core/compile/sqlglot/snapshots/test_compile_geo/test_st_regionstats/out.sql b/tests/unit/core/compile/sqlglot/snapshots/test_compile_geo/test_st_regionstats/out.sql deleted file mode 100644 index 457436e98c4..00000000000 --- a/tests/unit/core/compile/sqlglot/snapshots/test_compile_geo/test_st_regionstats/out.sql +++ /dev/null @@ -1,51 +0,0 @@ -WITH `bfcte_0` AS ( - SELECT - * - FROM UNNEST(ARRAY>[STRUCT('POINT(1 1)', 0)]) -) -SELECT - ST_REGIONSTATS( - `bfcol_0`, - 'ee://some/raster/uri', - band => 'band1', - include => 'some equation', - options => JSON '{"scale": 100}' - ).`min`, - ST_REGIONSTATS( - `bfcol_0`, - 'ee://some/raster/uri', - band => 'band1', - include => 'some equation', - options => JSON '{"scale": 100}' - ).`max`, - ST_REGIONSTATS( - `bfcol_0`, - 'ee://some/raster/uri', - band => 'band1', - include => 'some equation', - options => JSON '{"scale": 100}' - ).`sum`, - ST_REGIONSTATS( - `bfcol_0`, - 'ee://some/raster/uri', - band => 'band1', - include => 'some equation', - options => JSON '{"scale": 100}' - ).`count`, - ST_REGIONSTATS( - `bfcol_0`, - 'ee://some/raster/uri', - band => 'band1', - include => 'some equation', - options => JSON '{"scale": 100}' - ).`mean`, - ST_REGIONSTATS( - `bfcol_0`, - 'ee://some/raster/uri', - band => 'band1', - include => 'some equation', - options => JSON '{"scale": 100}' - ).`area` -FROM `bfcte_0` -ORDER BY - `bfcol_1` ASC NULLS LAST \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/snapshots/test_compile_geo/test_st_regionstats_without_optional_args/out.sql b/tests/unit/core/compile/sqlglot/snapshots/test_compile_geo/test_st_regionstats_without_optional_args/out.sql deleted file mode 100644 index 410909d80c5..00000000000 --- a/tests/unit/core/compile/sqlglot/snapshots/test_compile_geo/test_st_regionstats_without_optional_args/out.sql +++ /dev/null @@ -1,15 +0,0 @@ -WITH `bfcte_0` AS ( - SELECT - * - FROM UNNEST(ARRAY>[STRUCT('POINT(1 1)', 0)]) -) -SELECT - ST_REGIONSTATS(`bfcol_0`, 'ee://some/raster/uri').`min`, - ST_REGIONSTATS(`bfcol_0`, 'ee://some/raster/uri').`max`, - ST_REGIONSTATS(`bfcol_0`, 'ee://some/raster/uri').`sum`, - ST_REGIONSTATS(`bfcol_0`, 'ee://some/raster/uri').`count`, - ST_REGIONSTATS(`bfcol_0`, 'ee://some/raster/uri').`mean`, - ST_REGIONSTATS(`bfcol_0`, 'ee://some/raster/uri').`area` -FROM `bfcte_0` -ORDER BY - `bfcol_1` ASC NULLS LAST \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/snapshots/test_compile_geo/test_st_simplify/out.sql b/tests/unit/core/compile/sqlglot/snapshots/test_compile_geo/test_st_simplify/out.sql deleted file mode 100644 index 177cb5292b3..00000000000 --- a/tests/unit/core/compile/sqlglot/snapshots/test_compile_geo/test_st_simplify/out.sql +++ /dev/null @@ -1,10 +0,0 @@ -WITH `bfcte_0` AS ( - SELECT - * - FROM UNNEST(ARRAY>[STRUCT(ST_GEOGFROMTEXT('LINESTRING(0 0, 1 1, 2 0)'), 0)]) -) -SELECT - ST_SIMPLIFY(`bfcol_0`, 123.125) AS `0` -FROM `bfcte_0` -ORDER BY - `bfcol_1` ASC NULLS LAST \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/snapshots/test_compile_isin/test_compile_isin/out.sql b/tests/unit/core/compile/sqlglot/snapshots/test_compile_isin/test_compile_isin/out.sql deleted file mode 100644 index a062ec30e44..00000000000 --- a/tests/unit/core/compile/sqlglot/snapshots/test_compile_isin/test_compile_isin/out.sql +++ /dev/null @@ -1,36 +0,0 @@ -WITH `bfcte_0` AS ( - SELECT - `int64_too` - FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` -), `bfcte_1` AS ( - SELECT - `rowindex` AS `bfcol_3`, - `int64_col` AS `bfcol_4` - FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` -), `bfcte_2` AS ( - SELECT - `int64_too` - FROM `bfcte_0` - GROUP BY - `int64_too` -), `bfcte_3` AS ( - SELECT - `int64_too` AS `bfcol_0` - FROM `bfcte_2` -), `bfcte_4` AS ( - SELECT - *, - EXISTS( - SELECT - 1 - FROM `bfcte_3` - WHERE - COALESCE(`bfcol_4`, 0) = COALESCE(`bfcol_0`, 0) - AND COALESCE(`bfcol_4`, 1) = COALESCE(`bfcol_0`, 1) - ) AS `bfcol_5` - FROM `bfcte_1` -) -SELECT - `bfcol_3` AS `rowindex`, - `bfcol_5` AS `int64_col` -FROM `bfcte_4` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/snapshots/test_compile_isin/test_compile_isin_not_nullable/out.sql b/tests/unit/core/compile/sqlglot/snapshots/test_compile_isin/test_compile_isin_not_nullable/out.sql deleted file mode 100644 index 81c83dee6c9..00000000000 --- a/tests/unit/core/compile/sqlglot/snapshots/test_compile_isin/test_compile_isin_not_nullable/out.sql +++ /dev/null @@ -1,33 +0,0 @@ -WITH `bfcte_0` AS ( - SELECT - `rowindex_2` - FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` -), `bfcte_1` AS ( - SELECT - `rowindex` AS `bfcol_3`, - `rowindex_2` AS `bfcol_4` - FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` -), `bfcte_2` AS ( - SELECT - `rowindex_2` - FROM `bfcte_0` - GROUP BY - `rowindex_2` -), `bfcte_3` AS ( - SELECT - `rowindex_2` AS `bfcol_0` - FROM `bfcte_2` -), `bfcte_4` AS ( - SELECT - *, - COALESCE(`bfcol_4` IN (( - SELECT - * - FROM `bfcte_3` - )), FALSE) AS `bfcol_5` - FROM `bfcte_1` -) -SELECT - `bfcol_3` AS `rowindex`, - `bfcol_5` AS `rowindex_2` -FROM `bfcte_4` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/snapshots/test_compile_join/test_compile_join/out.sql b/tests/unit/core/compile/sqlglot/snapshots/test_compile_join/test_compile_join/out.sql deleted file mode 100644 index cac57d0c8c8..00000000000 --- a/tests/unit/core/compile/sqlglot/snapshots/test_compile_join/test_compile_join/out.sql +++ /dev/null @@ -1,18 +0,0 @@ -WITH `bfcte_0` AS ( - SELECT - `int64_col` AS `bfcol_4`, - `int64_too` AS `bfcol_5` - FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` -), `bfcte_1` AS ( - SELECT - `rowindex` AS `bfcol_6`, - `int64_col` AS `bfcol_7` - FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` -) -SELECT - `bfcol_7` AS `int64_col`, - `bfcol_5` AS `int64_too` -FROM `bfcte_1` -LEFT JOIN `bfcte_0` - ON COALESCE(`bfcol_6`, 0) = COALESCE(`bfcol_4`, 0) - AND COALESCE(`bfcol_6`, 1) = COALESCE(`bfcol_4`, 1) \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/snapshots/test_compile_join/test_compile_join_w_on/bool_col/out.sql b/tests/unit/core/compile/sqlglot/snapshots/test_compile_join/test_compile_join_w_on/bool_col/out.sql deleted file mode 100644 index 5042f91cd95..00000000000 --- a/tests/unit/core/compile/sqlglot/snapshots/test_compile_join/test_compile_join_w_on/bool_col/out.sql +++ /dev/null @@ -1,24 +0,0 @@ -WITH `bfcte_0` AS ( - SELECT - `bool_col` AS `bfcol_0`, - `rowindex` AS `bfcol_1` - FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` -), `bfcte_1` AS ( - SELECT - `bfcol_1` AS `bfcol_2`, - `bfcol_0` AS `bfcol_3` - FROM `bfcte_0` -), `bfcte_2` AS ( - SELECT - `bfcol_1` AS `bfcol_4`, - `bfcol_0` AS `bfcol_5` - FROM `bfcte_0` -) -SELECT - `bfcol_4` AS `rowindex_x`, - `bfcol_5` AS `bool_col`, - `bfcol_2` AS `rowindex_y` -FROM `bfcte_2` -INNER JOIN `bfcte_1` - ON COALESCE(CAST(`bfcol_5` AS STRING), '0') = COALESCE(CAST(`bfcol_3` AS STRING), '0') - AND COALESCE(CAST(`bfcol_5` AS STRING), '1') = COALESCE(CAST(`bfcol_3` AS STRING), '1') \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/snapshots/test_compile_join/test_compile_join_w_on/float64_col/out.sql b/tests/unit/core/compile/sqlglot/snapshots/test_compile_join/test_compile_join_w_on/float64_col/out.sql deleted file mode 100644 index 544fedadc5b..00000000000 --- a/tests/unit/core/compile/sqlglot/snapshots/test_compile_join/test_compile_join_w_on/float64_col/out.sql +++ /dev/null @@ -1,24 +0,0 @@ -WITH `bfcte_0` AS ( - SELECT - `float64_col` AS `bfcol_0`, - `rowindex` AS `bfcol_1` - FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` -), `bfcte_1` AS ( - SELECT - `bfcol_1` AS `bfcol_2`, - `bfcol_0` AS `bfcol_3` - FROM `bfcte_0` -), `bfcte_2` AS ( - SELECT - `bfcol_1` AS `bfcol_4`, - `bfcol_0` AS `bfcol_5` - FROM `bfcte_0` -) -SELECT - `bfcol_4` AS `rowindex_x`, - `bfcol_5` AS `float64_col`, - `bfcol_2` AS `rowindex_y` -FROM `bfcte_2` -INNER JOIN `bfcte_1` - ON IF(IS_NAN(`bfcol_5`), 2.0, COALESCE(`bfcol_5`, 0.0)) = IF(IS_NAN(`bfcol_3`), 2.0, COALESCE(`bfcol_3`, 0.0)) - AND IF(IS_NAN(`bfcol_5`), 3, COALESCE(`bfcol_5`, 1.0)) = IF(IS_NAN(`bfcol_3`), 3, COALESCE(`bfcol_3`, 1.0)) \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/snapshots/test_compile_join/test_compile_join_w_on/int64_col/out.sql b/tests/unit/core/compile/sqlglot/snapshots/test_compile_join/test_compile_join_w_on/int64_col/out.sql deleted file mode 100644 index 05b9ceec4de..00000000000 --- a/tests/unit/core/compile/sqlglot/snapshots/test_compile_join/test_compile_join_w_on/int64_col/out.sql +++ /dev/null @@ -1,24 +0,0 @@ -WITH `bfcte_0` AS ( - SELECT - `int64_col` AS `bfcol_0`, - `rowindex` AS `bfcol_1` - FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` -), `bfcte_1` AS ( - SELECT - `bfcol_1` AS `bfcol_2`, - `bfcol_0` AS `bfcol_3` - FROM `bfcte_0` -), `bfcte_2` AS ( - SELECT - `bfcol_1` AS `bfcol_4`, - `bfcol_0` AS `bfcol_5` - FROM `bfcte_0` -) -SELECT - `bfcol_4` AS `rowindex_x`, - `bfcol_5` AS `int64_col`, - `bfcol_2` AS `rowindex_y` -FROM `bfcte_2` -INNER JOIN `bfcte_1` - ON COALESCE(`bfcol_5`, 0) = COALESCE(`bfcol_3`, 0) - AND COALESCE(`bfcol_5`, 1) = COALESCE(`bfcol_3`, 1) \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/snapshots/test_compile_join/test_compile_join_w_on/numeric_col/out.sql b/tests/unit/core/compile/sqlglot/snapshots/test_compile_join/test_compile_join_w_on/numeric_col/out.sql deleted file mode 100644 index 2e0114593e7..00000000000 --- a/tests/unit/core/compile/sqlglot/snapshots/test_compile_join/test_compile_join_w_on/numeric_col/out.sql +++ /dev/null @@ -1,24 +0,0 @@ -WITH `bfcte_0` AS ( - SELECT - `numeric_col` AS `bfcol_0`, - `rowindex` AS `bfcol_1` - FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` -), `bfcte_1` AS ( - SELECT - `bfcol_1` AS `bfcol_2`, - `bfcol_0` AS `bfcol_3` - FROM `bfcte_0` -), `bfcte_2` AS ( - SELECT - `bfcol_1` AS `bfcol_4`, - `bfcol_0` AS `bfcol_5` - FROM `bfcte_0` -) -SELECT - `bfcol_4` AS `rowindex_x`, - `bfcol_5` AS `numeric_col`, - `bfcol_2` AS `rowindex_y` -FROM `bfcte_2` -INNER JOIN `bfcte_1` - ON COALESCE(`bfcol_5`, CAST(0 AS NUMERIC)) = COALESCE(`bfcol_3`, CAST(0 AS NUMERIC)) - AND COALESCE(`bfcol_5`, CAST(1 AS NUMERIC)) = COALESCE(`bfcol_3`, CAST(1 AS NUMERIC)) \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/snapshots/test_compile_join/test_compile_join_w_on/string_col/out.sql b/tests/unit/core/compile/sqlglot/snapshots/test_compile_join/test_compile_join_w_on/string_col/out.sql deleted file mode 100644 index 36aad503435..00000000000 --- a/tests/unit/core/compile/sqlglot/snapshots/test_compile_join/test_compile_join_w_on/string_col/out.sql +++ /dev/null @@ -1,19 +0,0 @@ -WITH `bfcte_0` AS ( - SELECT - `rowindex` AS `bfcol_0`, - `string_col` AS `bfcol_1` - FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` -), `bfcte_1` AS ( - SELECT - `bfcol_0` AS `bfcol_2`, - `bfcol_1` AS `bfcol_3` - FROM `bfcte_0` -) -SELECT - `bfcol_0` AS `rowindex_x`, - `bfcol_1` AS `string_col`, - `bfcol_2` AS `rowindex_y` -FROM `bfcte_0` -INNER JOIN `bfcte_1` - ON COALESCE(CAST(`bfcol_1` AS STRING), '0') = COALESCE(CAST(`bfcol_3` AS STRING), '0') - AND COALESCE(CAST(`bfcol_1` AS STRING), '1') = COALESCE(CAST(`bfcol_3` AS STRING), '1') \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/snapshots/test_compile_join/test_compile_join_w_on/time_col/out.sql b/tests/unit/core/compile/sqlglot/snapshots/test_compile_join/test_compile_join_w_on/time_col/out.sql deleted file mode 100644 index b945a1cbf38..00000000000 --- a/tests/unit/core/compile/sqlglot/snapshots/test_compile_join/test_compile_join_w_on/time_col/out.sql +++ /dev/null @@ -1,19 +0,0 @@ -WITH `bfcte_0` AS ( - SELECT - `rowindex` AS `bfcol_0`, - `time_col` AS `bfcol_1` - FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` -), `bfcte_1` AS ( - SELECT - `bfcol_0` AS `bfcol_2`, - `bfcol_1` AS `bfcol_3` - FROM `bfcte_0` -) -SELECT - `bfcol_0` AS `rowindex_x`, - `bfcol_1` AS `time_col`, - `bfcol_2` AS `rowindex_y` -FROM `bfcte_0` -INNER JOIN `bfcte_1` - ON COALESCE(CAST(`bfcol_1` AS STRING), '0') = COALESCE(CAST(`bfcol_3` AS STRING), '0') - AND COALESCE(CAST(`bfcol_1` AS STRING), '1') = COALESCE(CAST(`bfcol_3` AS STRING), '1') \ 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 deleted file mode 100644 index 3f819800e51..00000000000 --- a/tests/unit/core/compile/sqlglot/snapshots/test_compile_projection/test_compile_projection/out.sql +++ /dev/null @@ -1,26 +0,0 @@ -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_2`, - `bfcol_0` AS `bfcol_3` - FROM `bfcte_0` -), `bfcte_2` AS ( - SELECT - *, - `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_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_random_sample/test_compile_random_sample/out.sql b/tests/unit/core/compile/sqlglot/snapshots/test_compile_random_sample/test_compile_random_sample/out.sql deleted file mode 100644 index 73879aa65df..00000000000 --- a/tests/unit/core/compile/sqlglot/snapshots/test_compile_random_sample/test_compile_random_sample/out.sql +++ /dev/null @@ -1,183 +0,0 @@ -WITH `bfcte_0` AS ( - SELECT - * - FROM UNNEST(ARRAY>[STRUCT( - TRUE, - CAST(b'Hello, World!' AS BYTES), - CAST('2021-07-21' AS DATE), - CAST('2021-07-21T11:39:45' AS DATETIME), - ST_GEOGFROMTEXT('POINT(-122.0838511 37.3860517)'), - 123456789, - 0, - CAST(1.234567890 AS NUMERIC), - 1.25, - 0, - 0, - 'Hello, World!', - CAST('11:41:43.076160' AS TIME), - CAST('2021-07-21T17:43:43.945289+00:00' AS TIMESTAMP), - 4, - 0 - ), STRUCT( - FALSE, - CAST(b'\xe3\x81\x93\xe3\x82\x93\xe3\x81\xab\xe3\x81\xa1\xe3\x81\xaf' AS BYTES), - CAST('1991-02-03' AS DATE), - CAST('1991-01-02T03:45:06' AS DATETIME), - ST_GEOGFROMTEXT('POINT(-71.104 42.315)'), - -987654321, - 1, - CAST(1.234567890 AS NUMERIC), - 2.51, - 1, - 1, - 'こんにちは', - CAST('11:14:34.701606' AS TIME), - CAST('2021-07-21T17:43:43.945289+00:00' AS TIMESTAMP), - -1000000, - 1 - ), STRUCT( - TRUE, - CAST(b'\xc2\xa1Hola Mundo!' AS BYTES), - CAST('2023-03-01' AS DATE), - CAST('2023-03-01T10:55:13' AS DATETIME), - ST_GEOGFROMTEXT('POINT(-0.124474760143016 51.5007826749545)'), - 314159, - 0, - CAST(101.101010100 AS NUMERIC), - 25000000000.0, - 2, - 2, - ' ¡Hola Mundo! ', - CAST('23:59:59.999999' AS TIME), - CAST('2023-03-01T10:55:13.250125+00:00' AS TIMESTAMP), - 0, - 2 - ), STRUCT( - CAST(NULL AS BOOLEAN), - CAST(NULL AS BYTES), - CAST(NULL AS DATE), - CAST(NULL AS DATETIME), - CAST(NULL AS GEOGRAPHY), - CAST(NULL AS INT64), - 1, - CAST(NULL AS NUMERIC), - CAST(NULL AS FLOAT64), - 3, - 3, - CAST(NULL AS STRING), - CAST(NULL AS TIME), - CAST(NULL AS TIMESTAMP), - CAST(NULL AS INT64), - 3 - ), STRUCT( - FALSE, - CAST(b'\xe3\x81\x93\xe3\x82\x93\xe3\x81\xab\xe3\x81\xa1\xe3\x81\xaf' AS BYTES), - CAST('2021-07-21' AS DATE), - CAST(NULL AS DATETIME), - CAST(NULL AS GEOGRAPHY), - -234892, - -2345, - CAST(NULL AS NUMERIC), - CAST(NULL AS FLOAT64), - 4, - 4, - 'Hello, World!', - CAST(NULL AS TIME), - CAST(NULL AS TIMESTAMP), - 31540000000000, - 4 - ), STRUCT( - FALSE, - CAST(b'G\xc3\xbcten Tag' AS BYTES), - CAST('1980-03-14' AS DATE), - CAST('1980-03-14T15:16:17' AS DATETIME), - CAST(NULL AS GEOGRAPHY), - 55555, - 0, - CAST(5.555555000 AS NUMERIC), - 555.555, - 5, - 5, - 'Güten Tag!', - CAST('15:16:17.181921' AS TIME), - CAST('1980-03-14T15:16:17.181921+00:00' AS TIMESTAMP), - 4, - 5 - ), STRUCT( - TRUE, - CAST(b'Hello\tBigFrames!\x07' AS BYTES), - CAST('2023-05-23' AS DATE), - CAST('2023-05-23T11:37:01' AS DATETIME), - ST_GEOGFROMTEXT('LINESTRING(-0.127959 51.507728, -0.127026 51.507473)'), - 101202303, - 2, - CAST(-10.090807000 AS NUMERIC), - -123.456, - 6, - 6, - 'capitalize, This ', - CAST('01:02:03.456789' AS TIME), - CAST('2023-05-23T11:42:55.000001+00:00' AS TIMESTAMP), - CAST(NULL AS INT64), - 6 - ), STRUCT( - TRUE, - CAST(NULL AS BYTES), - CAST('2038-01-20' AS DATE), - CAST('2038-01-19T03:14:08' AS DATETIME), - CAST(NULL AS GEOGRAPHY), - -214748367, - 2, - CAST(11111111.100000000 AS NUMERIC), - 42.42, - 7, - 7, - ' سلام', - CAST('12:00:00.000001' AS TIME), - CAST('2038-01-19T03:14:17.999999+00:00' AS TIMESTAMP), - 4, - 7 - ), STRUCT( - FALSE, - CAST(NULL AS BYTES), - CAST(NULL AS DATE), - CAST(NULL AS DATETIME), - CAST(NULL AS GEOGRAPHY), - 2, - 1, - CAST(NULL AS NUMERIC), - 6.87, - 8, - 8, - 'T', - CAST(NULL AS TIME), - CAST(NULL AS TIMESTAMP), - 432000000000, - 8 - )]) -) -SELECT - `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`, - `bfcol_10` AS `rowindex_2`, - `bfcol_11` AS `string_col`, - `bfcol_12` AS `time_col`, - `bfcol_13` AS `timestamp_col`, - `bfcol_14` AS `duration_col` -FROM ( - SELECT - * - FROM `bfcte_0` - WHERE - RAND() < 0.1 -) -ORDER BY - `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/out.sql b/tests/unit/core/compile/sqlglot/snapshots/test_compile_readlocal/test_compile_readlocal/out.sql deleted file mode 100644 index 2b080b0b7ce..00000000000 --- a/tests/unit/core/compile/sqlglot/snapshots/test_compile_readlocal/test_compile_readlocal/out.sql +++ /dev/null @@ -1,187 +0,0 @@ -WITH `bfcte_0` AS ( - SELECT - * - FROM UNNEST(ARRAY>[STRUCT( - 0, - TRUE, - CAST(b'Hello, World!' AS BYTES), - CAST('2021-07-21' AS DATE), - CAST('2021-07-21T11:39:45' AS DATETIME), - ST_GEOGFROMTEXT('POINT(-122.0838511 37.3860517)'), - 123456789, - 0, - CAST(1.234567890 AS NUMERIC), - 1.25, - 0, - 0, - 'Hello, World!', - CAST('11:41:43.076160' AS TIME), - CAST('2021-07-21T17:43:43.945289+00:00' AS TIMESTAMP), - 4, - 0 - ), STRUCT( - 1, - FALSE, - CAST(b'\xe3\x81\x93\xe3\x82\x93\xe3\x81\xab\xe3\x81\xa1\xe3\x81\xaf' AS BYTES), - CAST('1991-02-03' AS DATE), - CAST('1991-01-02T03:45:06' AS DATETIME), - ST_GEOGFROMTEXT('POINT(-71.104 42.315)'), - -987654321, - 1, - CAST(1.234567890 AS NUMERIC), - 2.51, - 1, - 1, - 'こんにちは', - CAST('11:14:34.701606' AS TIME), - CAST('2021-07-21T17:43:43.945289+00:00' AS TIMESTAMP), - -1000000, - 1 - ), STRUCT( - 2, - TRUE, - CAST(b'\xc2\xa1Hola Mundo!' AS BYTES), - CAST('2023-03-01' AS DATE), - CAST('2023-03-01T10:55:13' AS DATETIME), - ST_GEOGFROMTEXT('POINT(-0.124474760143016 51.5007826749545)'), - 314159, - 0, - CAST(101.101010100 AS NUMERIC), - 25000000000.0, - 2, - 2, - ' ¡Hola Mundo! ', - CAST('23:59:59.999999' AS TIME), - CAST('2023-03-01T10:55:13.250125+00:00' AS TIMESTAMP), - 0, - 2 - ), STRUCT( - 3, - CAST(NULL AS BOOLEAN), - CAST(NULL AS BYTES), - CAST(NULL AS DATE), - CAST(NULL AS DATETIME), - CAST(NULL AS GEOGRAPHY), - CAST(NULL AS INT64), - 1, - CAST(NULL AS NUMERIC), - CAST(NULL AS FLOAT64), - 3, - 3, - CAST(NULL AS STRING), - CAST(NULL AS TIME), - CAST(NULL AS TIMESTAMP), - CAST(NULL AS INT64), - 3 - ), STRUCT( - 4, - FALSE, - CAST(b'\xe3\x81\x93\xe3\x82\x93\xe3\x81\xab\xe3\x81\xa1\xe3\x81\xaf' AS BYTES), - CAST('2021-07-21' AS DATE), - CAST(NULL AS DATETIME), - CAST(NULL AS GEOGRAPHY), - -234892, - -2345, - CAST(NULL AS NUMERIC), - CAST(NULL AS FLOAT64), - 4, - 4, - 'Hello, World!', - CAST(NULL AS TIME), - CAST(NULL AS TIMESTAMP), - 31540000000000, - 4 - ), STRUCT( - 5, - FALSE, - CAST(b'G\xc3\xbcten Tag' AS BYTES), - CAST('1980-03-14' AS DATE), - CAST('1980-03-14T15:16:17' AS DATETIME), - CAST(NULL AS GEOGRAPHY), - 55555, - 0, - CAST(5.555555000 AS NUMERIC), - 555.555, - 5, - 5, - 'Güten Tag!', - CAST('15:16:17.181921' AS TIME), - CAST('1980-03-14T15:16:17.181921+00:00' AS TIMESTAMP), - 4, - 5 - ), STRUCT( - 6, - TRUE, - CAST(b'Hello\tBigFrames!\x07' AS BYTES), - CAST('2023-05-23' AS DATE), - CAST('2023-05-23T11:37:01' AS DATETIME), - ST_GEOGFROMTEXT('LINESTRING(-0.127959 51.507728, -0.127026 51.507473)'), - 101202303, - 2, - CAST(-10.090807000 AS NUMERIC), - -123.456, - 6, - 6, - 'capitalize, This ', - CAST('01:02:03.456789' AS TIME), - CAST('2023-05-23T11:42:55.000001+00:00' AS TIMESTAMP), - CAST(NULL AS INT64), - 6 - ), STRUCT( - 7, - TRUE, - CAST(NULL AS BYTES), - CAST('2038-01-20' AS DATE), - CAST('2038-01-19T03:14:08' AS DATETIME), - CAST(NULL AS GEOGRAPHY), - -214748367, - 2, - CAST(11111111.100000000 AS NUMERIC), - 42.42, - 7, - 7, - ' سلام', - CAST('12:00:00.000001' AS TIME), - CAST('2038-01-19T03:14:17.999999+00:00' AS TIMESTAMP), - 4, - 7 - ), STRUCT( - 8, - FALSE, - CAST(NULL AS BYTES), - CAST(NULL AS DATE), - CAST(NULL AS DATETIME), - CAST(NULL AS GEOGRAPHY), - 2, - 1, - CAST(NULL AS NUMERIC), - 6.87, - 8, - 8, - 'T', - CAST(NULL AS TIME), - CAST(NULL AS TIMESTAMP), - 432000000000, - 8 - )]) -) -SELECT - `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`, - `bfcol_15` AS `duration_col` -FROM `bfcte_0` -ORDER BY - `bfcol_16` 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 deleted file mode 100644 index 4e21266b87b..00000000000 --- a/tests/unit/core/compile/sqlglot/snapshots/test_compile_readlocal/test_compile_readlocal_w_json_df/out.sql +++ /dev/null @@ -1,11 +0,0 @@ -WITH `bfcte_0` AS ( - SELECT - * - FROM UNNEST(ARRAY>[STRUCT(0, PARSE_JSON('null'), 0), STRUCT(1, PARSE_JSON('true'), 1), STRUCT(2, PARSE_JSON('100'), 2), STRUCT(3, PARSE_JSON('0.98'), 3), STRUCT(4, PARSE_JSON('"a string"'), 4), STRUCT(5, PARSE_JSON('[]'), 5), STRUCT(6, PARSE_JSON('[1,2,3]'), 6), STRUCT(7, PARSE_JSON('[{"a":1},{"a":2},{"a":null},{}]'), 7), STRUCT(8, PARSE_JSON('"100"'), 8), STRUCT(9, PARSE_JSON('{"date":"2024-07-16"}'), 9), STRUCT(10, PARSE_JSON('{"int_value":2,"null_filed":null}'), 10), STRUCT(11, PARSE_JSON('{"list_data":[10,20,30]}'), 11)]) -) -SELECT - `bfcol_0` AS `rowindex`, - `bfcol_1` AS `json_col` -FROM `bfcte_0` -ORDER BY - `bfcol_2` 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 deleted file mode 100644 index 923476aafd4..00000000000 --- a/tests/unit/core/compile/sqlglot/snapshots/test_compile_readlocal/test_compile_readlocal_w_lists_df/out.sql +++ /dev/null @@ -1,47 +0,0 @@ -WITH `bfcte_0` AS ( - SELECT - * - FROM UNNEST(ARRAY, `bfcol_2` ARRAY, `bfcol_3` ARRAY, `bfcol_4` ARRAY, `bfcol_5` ARRAY, `bfcol_6` ARRAY, `bfcol_7` ARRAY, `bfcol_8` INT64>>[STRUCT( - 0, - [1], - [TRUE], - [1.2, 2.3], - ['2021-07-21'], - ['2021-07-21 11:39:45'], - [1.2, 2.3, 3.4], - ['abc', 'de', 'f'], - 0 - ), STRUCT( - 1, - [1, 2], - [TRUE, FALSE], - [1.1], - ['2021-07-21', '1987-03-28'], - ['1999-03-14 17:22:00'], - [5.5, 2.3], - ['a', 'bc', 'de'], - 1 - ), STRUCT( - 2, - [1, 2, 3], - [TRUE], - [0.5, -1.9, 2.3], - ['2017-08-01', '2004-11-22'], - ['1979-06-03 03:20:45'], - [1.7000000000000002], - ['', 'a'], - 2 - )]) -) -SELECT - `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_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_special_values/out.sql b/tests/unit/core/compile/sqlglot/snapshots/test_compile_readlocal/test_compile_readlocal_w_special_values/out.sql deleted file mode 100644 index ba5e0c8f1cf..00000000000 --- a/tests/unit/core/compile/sqlglot/snapshots/test_compile_readlocal/test_compile_readlocal_w_special_values/out.sql +++ /dev/null @@ -1,25 +0,0 @@ -WITH `bfcte_0` AS ( - SELECT - * - FROM UNNEST(ARRAY, `bfcol_5` STRUCT, `bfcol_6` ARRAY, `bfcol_7` INT64>>[STRUCT( - CAST(NULL AS FLOAT64), - CAST('Infinity' AS FLOAT64), - CAST('-Infinity' AS FLOAT64), - CAST(NULL AS FLOAT64), - CAST(NULL AS STRUCT), - STRUCT(CAST(NULL AS INT64) AS `foo`), - ARRAY[], - 0 - ), STRUCT(1.0, 1.0, 1.0, 1.0, STRUCT(1 AS `foo`), STRUCT(1 AS `foo`), [1, 2], 1), STRUCT(2.0, 2.0, 2.0, 2.0, STRUCT(2 AS `foo`), STRUCT(2 AS `foo`), [3, 4], 2)]) -) -SELECT - `bfcol_0` AS `col_none`, - `bfcol_1` AS `col_inf`, - `bfcol_2` AS `col_neginf`, - `bfcol_3` AS `col_nan`, - `bfcol_4` AS `col_struct_none`, - `bfcol_5` AS `col_struct_w_none`, - `bfcol_6` AS `col_list_none` -FROM `bfcte_0` -ORDER BY - `bfcol_7` 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 deleted file mode 100644 index 58a01635b7d..00000000000 --- a/tests/unit/core/compile/sqlglot/snapshots/test_compile_readlocal/test_compile_readlocal_w_structs_df/out.sql +++ /dev/null @@ -1,145 +0,0 @@ -WITH `bfcte_0` AS ( - SELECT - * - FROM UNNEST(ARRAY>, `bfcol_2` FLOAT64, `bfcol_3` FLOAT64, `bfcol_4` FLOAT64, `bfcol_5` STRING, `bfcol_6` JSON, `bfcol_7` STRING, `bfcol_8` STRING, `bfcol_9` STRING, `bfcol_10` TIMESTAMP, `bfcol_11` STRING, `bfcol_12` FLOAT64, `bfcol_13` FLOAT64, `bfcol_14` STRING, `bfcol_15` FLOAT64, `bfcol_16` INT64>>[STRUCT( - 1, - STRUCT( - 'Alice' AS `name`, - 30 AS `age`, - STRUCT('New York' AS `city`, 'USA' AS `country`) AS `address` - ), - 1.0, - 123456789.0, - 1.25, - 'Hello World', - PARSE_JSON('{"a":1,"b":[1,2]}'), - '2026-06-24', - '12:34:56.789012', - '2026-06-24 12:34:56.789012', - CAST('2026-06-24T12:34:56.789012+00:00' AS TIMESTAMP), - 'SGVsbG8=', - 123456.789, - 123456.78901234567, - 'POINT(30 10)', - 1000.0, - 0 - ), STRUCT( - 2, - STRUCT('' AS `name`, -1 AS `age`, STRUCT('' AS `city`, '' AS `country`) AS `address`), - 0.0, - -9.223372036854776e+18, - CAST('-Infinity' AS FLOAT64), - '', - PARSE_JSON('{}'), - '0001-01-01', - '00:00:00', - '0001-01-02 00:00:00', - CAST('0001-01-02T00:00:00+00:00' AS TIMESTAMP), - '', - -1e+29, - -1e+38, - 'POINT(0 0)', - -9223372036854776.0, - 1 - ), STRUCT( - 3, - STRUCT( - 'Very Long Name...' AS `name`, - 150 AS `age`, - STRUCT('City' AS `city`, 'Country' AS `country`) AS `address` - ), - 1.0, - 9.223372036854776e+18, - CAST('Infinity' AS FLOAT64), - 'Unicode: 🚀 Spark ✨', - PARSE_JSON('{"max":true,"nested":{"val":999}}'), - '9999-12-31', - '23:59:59.999999', - '9999-12-31 23:59:59.999999', - CAST('9999-12-31T23:59:59.999999+00:00' AS TIMESTAMP), - 'dmVyeSBsb25nIGJ5dGVzIHZhbHVl', - 1e+29, - 1e+38, - 'POLYGON((0 0, 10 0, 10 10, 0 10, 0 0))', - 9223372036854776.0, - 2 - ), STRUCT( - 4, - CAST(NULL AS STRUCT>), - CAST(NULL AS FLOAT64), - CAST(NULL AS FLOAT64), - CAST(NULL AS FLOAT64), - CAST(NULL AS STRING), - CAST(NULL AS JSON), - CAST(NULL AS STRING), - CAST(NULL AS STRING), - CAST(NULL AS STRING), - CAST(NULL AS TIMESTAMP), - CAST(NULL AS STRING), - CAST(NULL AS FLOAT64), - CAST(NULL AS FLOAT64), - CAST(NULL AS STRING), - CAST(NULL AS FLOAT64), - 3 - ), STRUCT( - 5, - STRUCT( - 'Bob' AS `name`, - 0 AS `age`, - CAST(NULL AS STRUCT) AS `address` - ), - 0.0, - 0.0, - CAST(NULL AS FLOAT64), - 'Line 1\nLine 2\n"Quotes"', - PARSE_JSON('[1,"two",null]'), - '1970-01-01', - '12:00:00', - '1970-01-01 12:00:00', - CAST('1970-01-01T12:00:00+00:00' AS TIMESTAMP), - 'AA==', - 0.0, - 0.0, - 'LINESTRING(0 0, 1 1, 2 2)', - 0.0, - 4 - ), STRUCT( - 6, - CAST(NULL AS STRUCT>), - CAST(NULL AS FLOAT64), - CAST(NULL AS FLOAT64), - CAST(NULL AS FLOAT64), - CAST(NULL AS STRING), - CAST(NULL AS JSON), - CAST(NULL AS STRING), - CAST(NULL AS STRING), - CAST(NULL AS STRING), - CAST(NULL AS TIMESTAMP), - CAST(NULL AS STRING), - CAST(NULL AS FLOAT64), - CAST(NULL AS FLOAT64), - CAST(NULL AS STRING), - CAST(NULL AS FLOAT64), - 5 - )]) -) -SELECT - `bfcol_0` AS `id`, - `bfcol_1` AS `person`, - `bfcol_2` AS `bool_col`, - `bfcol_3` AS `int64_col`, - `bfcol_4` AS `float64_col`, - `bfcol_5` AS `string_col`, - `bfcol_6` AS `json_col`, - `bfcol_7` AS `date_col`, - `bfcol_8` AS `time_col`, - `bfcol_9` AS `datetime_col`, - `bfcol_10` AS `timestamp_col`, - `bfcol_11` AS `bytes_col`, - `bfcol_12` AS `numeric_col`, - `bfcol_13` AS `bignumeric_col`, - `bfcol_14` AS `geography_col`, - `bfcol_15` AS `duration_col` -FROM `bfcte_0` -ORDER BY - `bfcol_16` ASC NULLS LAST \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/snapshots/test_compile_readtable/test_compile_astype_aliases/out.sql b/tests/unit/core/compile/sqlglot/snapshots/test_compile_readtable/test_compile_astype_aliases/out.sql deleted file mode 100644 index cd056c650fd..00000000000 --- a/tests/unit/core/compile/sqlglot/snapshots/test_compile_readtable/test_compile_astype_aliases/out.sql +++ /dev/null @@ -1,5 +0,0 @@ -SELECT - `rowindex`, - CAST(`timestamp_col` AS STRING) AS `timestamp_col`, - CAST(`int64_col` AS FLOAT64) AS `int64_col` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` 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 deleted file mode 100644 index 626ef80d518..00000000000 --- a/tests/unit/core/compile/sqlglot/snapshots/test_compile_readtable/test_compile_readtable/out.sql +++ /dev/null @@ -1,18 +0,0 @@ -SELECT - `rowindex`, - `bool_col`, - `bytes_col`, - `date_col`, - `datetime_col`, - `geography_col`, - `int64_col`, - `int64_too`, - `numeric_col`, - `float64_col`, - `rowindex` AS `rowindex_1`, - `rowindex_2`, - `string_col`, - `time_col`, - `timestamp_col`, - `duration_col` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/snapshots/test_compile_readtable/test_compile_readtable_w_columns_filters/out.sql b/tests/unit/core/compile/sqlglot/snapshots/test_compile_readtable/test_compile_readtable_w_columns_filters/out.sql deleted file mode 100644 index 4d1b822245c..00000000000 --- a/tests/unit/core/compile/sqlglot/snapshots/test_compile_readtable/test_compile_readtable_w_columns_filters/out.sql +++ /dev/null @@ -1,7 +0,0 @@ -SELECT - `rowindex`, - `int64_col`, - `string_col` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` -WHERE - `rowindex` > 0 AND `string_col` IN ('Hello, World!') \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/snapshots/test_compile_readtable/test_compile_readtable_w_json_types/out.sql b/tests/unit/core/compile/sqlglot/snapshots/test_compile_readtable/test_compile_readtable_w_json_types/out.sql deleted file mode 100644 index 054e850fd36..00000000000 --- a/tests/unit/core/compile/sqlglot/snapshots/test_compile_readtable/test_compile_readtable_w_json_types/out.sql +++ /dev/null @@ -1,3 +0,0 @@ -SELECT - * -FROM `bigframes-dev`.`sqlglot_test`.`json_types` AS `bft_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 deleted file mode 100644 index ff4f0656b12..00000000000 --- a/tests/unit/core/compile/sqlglot/snapshots/test_compile_readtable/test_compile_readtable_w_limit/out.sql +++ /dev/null @@ -1,7 +0,0 @@ -SELECT - `rowindex`, - `int64_col` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` -ORDER BY - `rowindex` ASC NULLS LAST -LIMIT 10 \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/snapshots/test_compile_readtable/test_compile_readtable_w_nested_structs_types/out.sql b/tests/unit/core/compile/sqlglot/snapshots/test_compile_readtable/test_compile_readtable_w_nested_structs_types/out.sql deleted file mode 100644 index f75fa6f722c..00000000000 --- a/tests/unit/core/compile/sqlglot/snapshots/test_compile_readtable/test_compile_readtable_w_nested_structs_types/out.sql +++ /dev/null @@ -1,5 +0,0 @@ -SELECT - `id`, - `id` AS `id_1`, - `people` -FROM `bigframes-dev`.`sqlglot_test`.`nested_structs_types` AS `bft_0` \ No newline at end of file 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 deleted file mode 100644 index 7e6ddfd568f..00000000000 --- a/tests/unit/core/compile/sqlglot/snapshots/test_compile_readtable/test_compile_readtable_w_ordering/out.sql +++ /dev/null @@ -1,6 +0,0 @@ -SELECT - `rowindex`, - `int64_col` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` -ORDER BY - `int64_col` ASC NULLS LAST \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/snapshots/test_compile_readtable/test_compile_readtable_w_repeated_types/out.sql b/tests/unit/core/compile/sqlglot/snapshots/test_compile_readtable/test_compile_readtable_w_repeated_types/out.sql deleted file mode 100644 index 34b02b5209b..00000000000 --- a/tests/unit/core/compile/sqlglot/snapshots/test_compile_readtable/test_compile_readtable_w_repeated_types/out.sql +++ /dev/null @@ -1,11 +0,0 @@ -SELECT - `rowindex`, - `rowindex` AS `rowindex_1`, - `int_list_col`, - `bool_list_col`, - `float_list_col`, - `date_list_col`, - `date_time_list_col`, - `numeric_list_col`, - `string_list_col` -FROM `bigframes-dev`.`sqlglot_test`.`repeated_types` AS `bft_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/snapshots/test_compile_readtable/test_compile_readtable_w_system_time/out.sql b/tests/unit/core/compile/sqlglot/snapshots/test_compile_readtable/test_compile_readtable_w_system_time/out.sql deleted file mode 100644 index dcd40d78485..00000000000 --- a/tests/unit/core/compile/sqlglot/snapshots/test_compile_readtable/test_compile_readtable_w_system_time/out.sql +++ /dev/null @@ -1,3 +0,0 @@ -SELECT - * -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` FOR SYSTEM_TIME AS OF '2025-11-09T03:04:05.678901+00:00' \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/snapshots/test_compile_window/test_compile_window_w_groupby_rolling/out.sql b/tests/unit/core/compile/sqlglot/snapshots/test_compile_window/test_compile_window_w_groupby_rolling/out.sql deleted file mode 100644 index 1051a0fb4c1..00000000000 --- a/tests/unit/core/compile/sqlglot/snapshots/test_compile_window/test_compile_window_w_groupby_rolling/out.sql +++ /dev/null @@ -1,55 +0,0 @@ -SELECT - `bool_col`, - `rowindex`, - CASE - WHEN COALESCE( - SUM(CAST(( - `bool_col` - ) IS NOT NULL AS INT64)) OVER ( - PARTITION BY `bool_col` - ORDER BY `bool_col` ASC NULLS LAST, `rowindex` ASC NULLS LAST - ROWS BETWEEN 3 PRECEDING AND CURRENT ROW - ), - 0 - ) < 3 - THEN NULL - WHEN TRUE - THEN COALESCE( - SUM(CAST(`bool_col` AS INT64)) OVER ( - PARTITION BY `bool_col` - ORDER BY `bool_col` ASC NULLS LAST, `rowindex` ASC NULLS LAST - ROWS BETWEEN 3 PRECEDING AND CURRENT ROW - ), - 0 - ) - END AS `bool_col_1`, - CASE - WHEN COALESCE( - SUM(CAST(( - `int64_col` - ) IS NOT NULL AS INT64)) OVER ( - PARTITION BY `bool_col` - ORDER BY `bool_col` ASC NULLS LAST, `rowindex` ASC NULLS LAST - ROWS BETWEEN 3 PRECEDING AND CURRENT ROW - ), - 0 - ) < 3 - THEN NULL - WHEN TRUE - THEN COALESCE( - SUM(`int64_col`) OVER ( - PARTITION BY `bool_col` - ORDER BY `bool_col` ASC NULLS LAST, `rowindex` ASC NULLS LAST - ROWS BETWEEN 3 PRECEDING AND CURRENT ROW - ), - 0 - ) - END AS `int64_col` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` -WHERE - ( - `bool_col` - ) IS NOT NULL -ORDER BY - `bool_col` ASC NULLS LAST, - `rowindex` ASC NULLS LAST \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/snapshots/test_compile_window/test_compile_window_w_range_rolling/out.sql b/tests/unit/core/compile/sqlglot/snapshots/test_compile_window/test_compile_window_w_range_rolling/out.sql deleted file mode 100644 index 887e7e9212d..00000000000 --- a/tests/unit/core/compile/sqlglot/snapshots/test_compile_window/test_compile_window_w_range_rolling/out.sql +++ /dev/null @@ -1,31 +0,0 @@ -WITH `bfcte_0` AS ( - SELECT - * - FROM UNNEST(ARRAY>[STRUCT(CAST('2025-01-01T00:00:00+00:00' AS TIMESTAMP), 0, 0), STRUCT(CAST('2025-01-01T00:00:01+00:00' AS TIMESTAMP), 1, 1), STRUCT(CAST('2025-01-01T00:00:02+00:00' AS TIMESTAMP), 2, 2), STRUCT(CAST('2025-01-01T00:00:03+00:00' AS TIMESTAMP), 3, 3), STRUCT(CAST('2025-01-01T00:00:04+00:00' AS TIMESTAMP), 0, 4), STRUCT(CAST('2025-01-01T00:00:05+00:00' AS TIMESTAMP), 1, 5), STRUCT(CAST('2025-01-01T00:00:06+00:00' AS TIMESTAMP), 2, 6), STRUCT(CAST('2025-01-01T00:00:07+00:00' AS TIMESTAMP), 3, 7), STRUCT(CAST('2025-01-01T00:00:08+00:00' AS TIMESTAMP), 0, 8), STRUCT(CAST('2025-01-01T00:00:09+00:00' AS TIMESTAMP), 1, 9), STRUCT(CAST('2025-01-01T00:00:10+00:00' AS TIMESTAMP), 2, 10), STRUCT(CAST('2025-01-01T00:00:11+00:00' AS TIMESTAMP), 3, 11), STRUCT(CAST('2025-01-01T00:00:12+00:00' AS TIMESTAMP), 0, 12), STRUCT(CAST('2025-01-01T00:00:13+00:00' AS TIMESTAMP), 1, 13), STRUCT(CAST('2025-01-01T00:00:14+00:00' AS TIMESTAMP), 2, 14), STRUCT(CAST('2025-01-01T00:00:15+00:00' AS TIMESTAMP), 3, 15), STRUCT(CAST('2025-01-01T00:00:16+00:00' AS TIMESTAMP), 0, 16), STRUCT(CAST('2025-01-01T00:00:17+00:00' AS TIMESTAMP), 1, 17), STRUCT(CAST('2025-01-01T00:00:18+00:00' AS TIMESTAMP), 2, 18), STRUCT(CAST('2025-01-01T00:00:19+00:00' AS TIMESTAMP), 3, 19)]) -) -SELECT - `bfcol_0` AS `ts_col`, - CASE - WHEN COALESCE( - SUM(CAST(( - `bfcol_1` - ) IS NOT NULL AS INT64)) OVER ( - ORDER BY UNIX_MICROS(`bfcol_0`) ASC - RANGE BETWEEN 2999999 PRECEDING AND CURRENT ROW - ), - 0 - ) < 1 - THEN NULL - WHEN TRUE - THEN COALESCE( - SUM(`bfcol_1`) OVER ( - ORDER BY UNIX_MICROS(`bfcol_0`) ASC - RANGE BETWEEN 2999999 PRECEDING AND CURRENT ROW - ), - 0 - ) - END AS `int_col` -FROM `bfcte_0` -ORDER BY - `bfcol_0` ASC NULLS LAST, - `bfcol_2` ASC NULLS LAST \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/snapshots/test_compile_window/test_compile_window_w_skips_nulls_op/out.sql b/tests/unit/core/compile/sqlglot/snapshots/test_compile_window/test_compile_window_w_skips_nulls_op/out.sql deleted file mode 100644 index 21bb8d5f088..00000000000 --- a/tests/unit/core/compile/sqlglot/snapshots/test_compile_window/test_compile_window_w_skips_nulls_op/out.sql +++ /dev/null @@ -1,19 +0,0 @@ -SELECT - `rowindex`, - CASE - WHEN COALESCE( - SUM(CAST(( - `int64_col` - ) IS NOT NULL AS INT64)) OVER (ORDER BY `rowindex` ASC NULLS LAST ROWS BETWEEN 2 PRECEDING AND CURRENT ROW), - 0 - ) < 3 - THEN NULL - WHEN TRUE - THEN COALESCE( - SUM(`int64_col`) OVER (ORDER BY `rowindex` ASC NULLS LAST ROWS BETWEEN 2 PRECEDING AND CURRENT ROW), - 0 - ) - END AS `int64_col` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` -ORDER BY - `rowindex` ASC NULLS LAST \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/snapshots/test_compile_window/test_compile_window_wo_skips_nulls_op/out.sql b/tests/unit/core/compile/sqlglot/snapshots/test_compile_window/test_compile_window_wo_skips_nulls_op/out.sql deleted file mode 100644 index 6ae1fffab7a..00000000000 --- a/tests/unit/core/compile/sqlglot/snapshots/test_compile_window/test_compile_window_wo_skips_nulls_op/out.sql +++ /dev/null @@ -1,13 +0,0 @@ -SELECT - `rowindex`, - CASE - WHEN COUNT(( - `int64_col` - ) IS NOT NULL) OVER (ORDER BY `rowindex` ASC NULLS LAST ROWS BETWEEN 4 PRECEDING AND CURRENT ROW) < 5 - THEN NULL - WHEN TRUE - THEN COUNT(`int64_col`) OVER (ORDER BY `rowindex` ASC NULLS LAST ROWS BETWEEN 4 PRECEDING AND CURRENT ROW) - END AS `int64_col` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` -ORDER BY - `rowindex` ASC NULLS LAST \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/snapshots/test_dataframe_accessor/test_bigframes_sql_scalar/out.sql b/tests/unit/core/compile/sqlglot/snapshots/test_dataframe_accessor/test_bigframes_sql_scalar/out.sql deleted file mode 100644 index 80b3137b0b5..00000000000 --- a/tests/unit/core/compile/sqlglot/snapshots/test_dataframe_accessor/test_bigframes_sql_scalar/out.sql +++ /dev/null @@ -1,4 +0,0 @@ -SELECT - `rowindex`, - ROUND(`int64_col` + `int64_too`) AS `0` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` diff --git a/tests/unit/core/compile/sqlglot/snapshots/test_dataframe_accessor/test_sql_scalar/out.sql b/tests/unit/core/compile/sqlglot/snapshots/test_dataframe_accessor/test_sql_scalar/out.sql deleted file mode 100644 index 80b3137b0b5..00000000000 --- a/tests/unit/core/compile/sqlglot/snapshots/test_dataframe_accessor/test_sql_scalar/out.sql +++ /dev/null @@ -1,4 +0,0 @@ -SELECT - `rowindex`, - ROUND(`int64_col` + `int64_too`) AS `0` -FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0` diff --git a/tests/unit/core/compile/sqlglot/sql/snapshots/test_ddl/test_create_external_table/out.sql b/tests/unit/core/compile/sqlglot/sql/snapshots/test_ddl/test_create_external_table/out.sql deleted file mode 100644 index 867282de0e7..00000000000 --- a/tests/unit/core/compile/sqlglot/sql/snapshots/test_ddl/test_create_external_table/out.sql +++ /dev/null @@ -1,7 +0,0 @@ -CREATE EXTERNAL TABLE `my-project.my_dataset.my_table` ( - `col1` INT64, - `col2` STRING -) OPTIONS ( - format='CSV', - uris=['gs://bucket/path*'] -) \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/sql/snapshots/test_ddl/test_create_external_table_all_options/out.sql b/tests/unit/core/compile/sqlglot/sql/snapshots/test_ddl/test_create_external_table_all_options/out.sql deleted file mode 100644 index a08ddf5ee5d..00000000000 --- a/tests/unit/core/compile/sqlglot/sql/snapshots/test_ddl/test_create_external_table_all_options/out.sql +++ /dev/null @@ -1,10 +0,0 @@ -CREATE OR REPLACE EXTERNAL TABLE `my-project.my_dataset.my_table` ( - `col1` INT64, - `col2` STRING -) WITH CONNECTION `my-connection` WITH PARTITION COLUMNS ( - `part1` DATE, - `part2` STRING -) OPTIONS ( - format='CSV', - uris=['gs://bucket/path*'] -) \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/sql/snapshots/test_ddl/test_create_external_table_if_not_exists/out.sql b/tests/unit/core/compile/sqlglot/sql/snapshots/test_ddl/test_create_external_table_if_not_exists/out.sql deleted file mode 100644 index e05a553317b..00000000000 --- a/tests/unit/core/compile/sqlglot/sql/snapshots/test_ddl/test_create_external_table_if_not_exists/out.sql +++ /dev/null @@ -1,6 +0,0 @@ -CREATE EXTERNAL TABLE IF NOT EXISTS `my-project.my_dataset.my_table` ( - `col1` INT64 -) OPTIONS ( - format='CSV', - uris=['gs://bucket/path*'] -) \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/sql/snapshots/test_ddl/test_load_data_all_options/out.sql b/tests/unit/core/compile/sqlglot/sql/snapshots/test_ddl/test_load_data_all_options/out.sql deleted file mode 100644 index 781019a0680..00000000000 --- a/tests/unit/core/compile/sqlglot/sql/snapshots/test_ddl/test_load_data_all_options/out.sql +++ /dev/null @@ -1,10 +0,0 @@ -LOAD DATA OVERWRITE INTO `my-project.my_dataset.my_table` ( - `col1` INT64, - `col2` STRING -) PARTITION BY `date_col` CLUSTER BY - `cluster_col` OPTIONS ( - description='my table' -) FROM FILES (format='CSV', uris=['gs://bucket/path*']) WITH PARTITION COLUMNS ( - `part1` DATE, - `part2` STRING -) WITH CONNECTION `my-connection` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/sql/snapshots/test_ddl/test_load_data_minimal/out.sql b/tests/unit/core/compile/sqlglot/sql/snapshots/test_ddl/test_load_data_minimal/out.sql deleted file mode 100644 index c5f66003257..00000000000 --- a/tests/unit/core/compile/sqlglot/sql/snapshots/test_ddl/test_load_data_minimal/out.sql +++ /dev/null @@ -1 +0,0 @@ -LOAD DATA INTO `my-project.my_dataset.my_table` FROM FILES (format='CSV', uris=['gs://bucket/path*']) \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/sql/snapshots/test_dml/test_insert_from_select/out.sql b/tests/unit/core/compile/sqlglot/sql/snapshots/test_dml/test_insert_from_select/out.sql deleted file mode 100644 index e2e9225c9f7..00000000000 --- a/tests/unit/core/compile/sqlglot/sql/snapshots/test_dml/test_insert_from_select/out.sql +++ /dev/null @@ -1,6 +0,0 @@ -INSERT INTO `bigframes-dev`.`sqlglot_test`.`dest_table` -( - SELECT - * - FROM `source_table` -) \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/sql/snapshots/test_dml/test_insert_from_table/out.sql b/tests/unit/core/compile/sqlglot/sql/snapshots/test_dml/test_insert_from_table/out.sql deleted file mode 100644 index 2486d8d0a3b..00000000000 --- a/tests/unit/core/compile/sqlglot/sql/snapshots/test_dml/test_insert_from_table/out.sql +++ /dev/null @@ -1,2 +0,0 @@ -INSERT INTO `bigframes-dev`.`sqlglot_test`.`dest_table` -`source_table` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/sql/snapshots/test_dml/test_replace_from_select/out.sql b/tests/unit/core/compile/sqlglot/sql/snapshots/test_dml/test_replace_from_select/out.sql deleted file mode 100644 index c4f43f390ed..00000000000 --- a/tests/unit/core/compile/sqlglot/sql/snapshots/test_dml/test_replace_from_select/out.sql +++ /dev/null @@ -1,9 +0,0 @@ -MERGE INTO `bigframes-dev`.`sqlglot_test`.`dest_table` -USING ( - SELECT - * - FROM `source_table` -) -ON FALSE -WHEN NOT MATCHED BY SOURCE THEN DELETE -WHEN NOT MATCHED THEN INSERT ROW \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/sql/snapshots/test_dml/test_replace_from_table/out.sql b/tests/unit/core/compile/sqlglot/sql/snapshots/test_dml/test_replace_from_table/out.sql deleted file mode 100644 index bfc1532ca2d..00000000000 --- a/tests/unit/core/compile/sqlglot/sql/snapshots/test_dml/test_replace_from_table/out.sql +++ /dev/null @@ -1,5 +0,0 @@ -MERGE INTO `bigframes-dev`.`sqlglot_test`.`dest_table` -USING `source_table` -ON FALSE -WHEN NOT MATCHED BY SOURCE THEN DELETE -WHEN NOT MATCHED THEN INSERT ROW \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/sql/test_base.py b/tests/unit/core/compile/sqlglot/sql/test_base.py deleted file mode 100644 index 617f3636d40..00000000000 --- a/tests/unit/core/compile/sqlglot/sql/test_base.py +++ /dev/null @@ -1,173 +0,0 @@ -# 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. -# 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 datetime -import decimal -import re - -import numpy as np -import pandas as pd -import pyarrow as pa -import pytest -import shapely.geometry # type: ignore - -import bigframes.core.compile.sqlglot.sql.base as sql - - -@pytest.mark.parametrize( - ("value", "expected_pattern"), - ( - pytest.param(None, "NULL", id="null"), - pytest.param(True, "TRUE", id="true"), - pytest.param(False, "FALSE", id="false"), - pytest.param(123, "123", id="int"), - pytest.param(123.75, "123.75", id="float"), - pytest.param("abc", "'abc'", id="string"), - pytest.param( - b"\x01\x02\x03ABC", "CAST(b'\\x01\\x02\\x03ABC' AS BYTES)", id="bytes" - ), - pytest.param( - decimal.Decimal("123.75"), "CAST(123.75 AS NUMERIC)", id="decimal" - ), - pytest.param( - datetime.date(2025, 1, 1), "CAST('2025-01-01' AS DATE)", id="date" - ), - pytest.param( - datetime.datetime(2025, 1, 2, 3, 45, 6, 789123), - "CAST('2025-01-02T03:45:06.789123' AS DATETIME)", - id="datetime", - ), - pytest.param( - datetime.time(12, 34, 56, 789123), - "CAST('12:34:56.789123' AS TIME)", - id="time", - ), - pytest.param( - datetime.datetime( - 2025, 1, 2, 3, 45, 6, 789123, tzinfo=datetime.timezone.utc - ), - "CAST('2025-01-02T03:45:06.789123+00:00' AS TIMESTAMP)", - id="timestamp", - ), - pytest.param(np.int64(123), "123", id="np_int64"), - pytest.param(np.float64(123.75), "123.75", id="np_float64"), - pytest.param(float("inf"), "CAST('Infinity' AS FLOAT64)", id="inf"), - pytest.param(float("-inf"), "CAST('-Infinity' AS FLOAT64)", id="neg_inf"), - pytest.param(float("nan"), "NULL", id="nan"), - pytest.param(pd.NA, "NULL", id="pd_na"), - pytest.param(datetime.timedelta(seconds=1), "1000000", id="timedelta"), - pytest.param("POINT (0 1)", "'POINT (0 1)'", id="string_geo"), - ), -) -def test_literal(value, expected_pattern): - got = sql.to_sql(sql.literal(value)) - assert got == expected_pattern - - -def test_literal_for_geo(): - value = shapely.geometry.Point(0, 1) - expected_pattern = r"ST_GEOGFROMTEXT\('POINT \(0[.]?0* 1[.]?0*\)'\)" - got = sql.to_sql(sql.literal(value)) - assert re.match(expected_pattern, got) is not None - - -@pytest.mark.parametrize( - ("value", "dtype", "expected"), - ( - pytest.param( - decimal.Decimal("1.23"), - sql.dtypes.BIGNUMERIC_DTYPE, - "CAST(1.23 AS BIGNUMERIC)", - id="bignumeric", - ), - pytest.param( - [], - pd.ArrowDtype(pa.list_(pa.int64())), - "ARRAY[]", - id="empty_array", - ), - pytest.param( - {"a": 1, "b": "hello"}, - pd.ArrowDtype(pa.struct([("a", pa.int64()), ("b", pa.string())])), - "STRUCT(1 AS `a`, 'hello' AS `b`)", - id="struct", - ), - pytest.param( - float("nan"), - sql.dtypes.FLOAT_DTYPE, - "CAST('NaN' AS FLOAT64)", - id="explicit_nan", - ), - pytest.param( - pa.scalar(123, type=pa.int64()), - None, - "123", - id="pa_scalar_int", - ), - pytest.param( - pa.scalar(None, type=pa.int64()), - None, - "CAST(NULL AS INT64)", - id="pa_scalar_null", - ), - pytest.param( - {"a": 10}, - sql.dtypes.JSON_DTYPE, - "PARSE_JSON('{\\'a\\': 10}')", - id="json", - ), - ), -) -def test_literal_explicit_dtype(value, dtype, expected): - got = sql.to_sql(sql.literal(value, dtype=dtype)) - assert got == expected - - -@pytest.mark.parametrize( - ("value", "expected"), - ( - pytest.param([True, False], "[TRUE, FALSE]", id="bool"), - pytest.param([123, 456], "[123, 456]", id="int"), - pytest.param( - [123.75, 456.78, float("nan"), float("inf"), float("-inf")], - "[\n 123.75,\n 456.78,\n CAST('NaN' AS FLOAT64),\n CAST('Infinity' AS FLOAT64),\n CAST('-Infinity' AS FLOAT64)\n]", - id="float", - ), - pytest.param( - [b"\x01\x02\x03ABC", b"\x01\x02\x03ABC"], - "[CAST(b'\\x01\\x02\\x03ABC' AS BYTES), CAST(b'\\x01\\x02\\x03ABC' AS BYTES)]", - id="bytes", - ), - pytest.param( - [datetime.date(2025, 1, 1), datetime.date(2025, 1, 1)], - "[CAST('2025-01-01' AS DATE), CAST('2025-01-01' AS DATE)]", - id="date", - ), - ), -) -def test_literal_for_list(value: list, expected: str): - got = sql.to_sql(sql.literal(value)) - assert got == expected - - -def test_literal_null_type(): - import unittest.mock as mock - - mock_dtype = mock.Mock() - with mock.patch( - "bigframes.core.compile.sqlglot.sql.base.sgt.from_bigframes_dtype", - return_value="NULL", - ): - got = sql.to_sql(sql.literal(None, dtype=mock_dtype)) - assert got == "NULL" diff --git a/tests/unit/core/compile/sqlglot/sql/test_ddl.py b/tests/unit/core/compile/sqlglot/sql/test_ddl.py deleted file mode 100644 index 48080cd6b9c..00000000000 --- a/tests/unit/core/compile/sqlglot/sql/test_ddl.py +++ /dev/null @@ -1,87 +0,0 @@ -# Copyright 2026 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 unittest import mock - -import pytest - -import bigframes.bigquery -import bigframes.core.compile.sqlglot.sql as sql -import bigframes.session - -pytest.importorskip("pytest_snapshot") - - -@pytest.fixture -def mock_session(): - return mock.create_autospec(spec=bigframes.session.Session) - - -def test_load_data_minimal(snapshot): - expr = sql.load_data( - "my-project.my_dataset.my_table", - from_files_options={"format": "CSV", "uris": ["gs://bucket/path*"]}, - ) - snapshot.assert_match(sql.to_sql(expr), "out.sql") - - -def test_load_data_all_options(snapshot): - expr = sql.load_data( - "my-project.my_dataset.my_table", - write_disposition="OVERWRITE", - columns={"col1": "INT64", "col2": "STRING"}, - partition_by=["date_col"], - cluster_by=["cluster_col"], - table_options={"description": "my table"}, - from_files_options={"format": "CSV", "uris": ["gs://bucket/path*"]}, - with_partition_columns={"part1": "DATE", "part2": "STRING"}, - connection_name="my-connection", - ) - snapshot.assert_match(sql.to_sql(expr), "out.sql") - - -@mock.patch("bigframes.bigquery._operations.table._get_table_metadata") -def test_create_external_table(get_table_metadata_mock, mock_session, snapshot): - bigframes.bigquery.create_external_table( - "my-project.my_dataset.my_table", - columns={"col1": "INT64", "col2": "STRING"}, - options={"format": "CSV", "uris": ["gs://bucket/path*"]}, - session=mock_session, - ) - mock_session.read_gbq_query.assert_called_once() - generated_sql = mock_session.read_gbq_query.call_args[0][0] - snapshot.assert_match(generated_sql, "out.sql") - get_table_metadata_mock.assert_called_once() - - -def test_create_external_table_all_options(snapshot): - expr = sql.create_external_table( - "my-project.my_dataset.my_table", - replace=True, - columns={"col1": "INT64", "col2": "STRING"}, - partition_columns={"part1": "DATE", "part2": "STRING"}, - connection_name="my-connection", - options={"format": "CSV", "uris": ["gs://bucket/path*"]}, - ) - snapshot.assert_match(sql.to_sql(expr), "out.sql") - - -def test_create_external_table_if_not_exists(snapshot): - expr = sql.create_external_table( - "my-project.my_dataset.my_table", - if_not_exists=True, - columns={"col1": "INT64"}, - options={"format": "CSV", "uris": ["gs://bucket/path*"]}, - ) - snapshot.assert_match(sql.to_sql(expr), "out.sql") diff --git a/tests/unit/core/compile/sqlglot/sql/test_dml.py b/tests/unit/core/compile/sqlglot/sql/test_dml.py deleted file mode 100644 index 99f10892d90..00000000000 --- a/tests/unit/core/compile/sqlglot/sql/test_dml.py +++ /dev/null @@ -1,73 +0,0 @@ -# Copyright 2026 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_vendored.sqlglot.expressions as sge -import pytest -from google.cloud import bigquery - -from bigframes.core.compile.sqlglot.sql import base, dml - -pytest.importorskip("pytest_snapshot") - - -def test_insert_from_select(snapshot): - query = sge.select("*").from_( - sge.Table(this=sge.Identifier(this="source_table", quoted=True)) - ) - destination = bigquery.TableReference.from_string( - "bigframes-dev.sqlglot_test.dest_table" - ) - - expr = dml.insert(query, destination) - sql = base.to_sql(expr) - - snapshot.assert_match(sql, "out.sql") - - -def test_insert_from_table(snapshot): - query = sge.Table(this=sge.Identifier(this="source_table", quoted=True)) - destination = bigquery.TableReference.from_string( - "bigframes-dev.sqlglot_test.dest_table" - ) - - expr = dml.insert(query, destination) - sql = base.to_sql(expr) - - snapshot.assert_match(sql, "out.sql") - - -def test_replace_from_select(snapshot): - query = sge.select("*").from_( - sge.Table(this=sge.Identifier(this="source_table", quoted=True)) - ) - destination = bigquery.TableReference.from_string( - "bigframes-dev.sqlglot_test.dest_table" - ) - - expr = dml.replace(query, destination) - sql = base.to_sql(expr) - - snapshot.assert_match(sql, "out.sql") - - -def test_replace_from_table(snapshot): - query = sge.Table(this=sge.Identifier(this="source_table", quoted=True)) - destination = bigquery.TableReference.from_string( - "bigframes-dev.sqlglot_test.dest_table" - ) - - expr = dml.replace(query, destination) - sql = base.to_sql(expr) - - snapshot.assert_match(sql, "out.sql") diff --git a/tests/unit/core/compile/sqlglot/test_compile_aggregate.py b/tests/unit/core/compile/sqlglot/test_compile_aggregate.py deleted file mode 100644 index d59c5e50687..00000000000 --- a/tests/unit/core/compile/sqlglot/test_compile_aggregate.py +++ /dev/null @@ -1,33 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import pytest - -import bigframes.pandas as bpd - -pytest.importorskip("pytest_snapshot") - - -def test_compile_aggregate(scalar_types_df: bpd.DataFrame, snapshot): - result = scalar_types_df["int64_too"].groupby(scalar_types_df["bool_col"]).sum() - snapshot.assert_match(result.to_frame().sql, "out.sql") - - -def test_compile_aggregate_wo_dropna(scalar_types_df: bpd.DataFrame, snapshot): - result = ( - scalar_types_df["int64_too"] - .groupby(scalar_types_df["bool_col"], dropna=False) - .sum() - ) - snapshot.assert_match(result.to_frame().sql, "out.sql") diff --git a/tests/unit/core/compile/sqlglot/test_compile_concat.py b/tests/unit/core/compile/sqlglot/test_compile_concat.py deleted file mode 100644 index d13da8ec570..00000000000 --- a/tests/unit/core/compile/sqlglot/test_compile_concat.py +++ /dev/null @@ -1,48 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import pytest - -import bigframes.pandas as bpd -from bigframes.core import ordering - -pytest.importorskip("pytest_snapshot") - - -def test_compile_concat(scalar_types_df: bpd.DataFrame, snapshot): - # TODO: concat two same dataframes, which SQL does not get reused. - df1 = scalar_types_df[["rowindex", "int64_col", "string_col"]] - concat_df = bpd.concat([df1, df1]) - snapshot.assert_match(concat_df.sql, "out.sql") - - -def test_compile_concat_filter_sorted(scalar_types_df: bpd.DataFrame, snapshot): - scalars_array_value = scalar_types_df._block.expr - input_1 = scalars_array_value.select_columns(["float64_col", "int64_col"]).order_by( - [ordering.ascending_over("int64_col")] - ) - input_2 = scalars_array_value.filter_by_id("bool_col").select_columns( - ["float64_col", "int64_too"] - ) - - result = input_1.concat([input_2, input_1, input_2]) - - new_names = ["float64_col", "int64_col"] - col_ids = { - old_name: new_name for old_name, new_name in zip(result.column_ids, new_names) - } - result = result.rename_columns(col_ids).select_columns(new_names) - - sql = result.session._executor.to_sql(result, enable_cache=False) - snapshot.assert_match(sql, "out.sql") diff --git a/tests/unit/core/compile/sqlglot/test_compile_explode.py b/tests/unit/core/compile/sqlglot/test_compile_explode.py deleted file mode 100644 index 34adbbd23ab..00000000000 --- a/tests/unit/core/compile/sqlglot/test_compile_explode.py +++ /dev/null @@ -1,31 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import pytest - -import bigframes.pandas as bpd - -pytest.importorskip("pytest_snapshot") - - -# TODO: check order by with offset -def test_compile_explode_series(repeated_types_df: bpd.DataFrame, snapshot): - s = repeated_types_df["int_list_col"].explode() - snapshot.assert_match(s.to_frame().sql, "out.sql") - - -def test_compile_explode_dataframe(repeated_types_df: bpd.DataFrame, snapshot): - exploded_columns = ["int_list_col", "string_list_col"] - df = repeated_types_df[["rowindex", *exploded_columns]].explode(exploded_columns) - snapshot.assert_match(df.sql, "out.sql") diff --git a/tests/unit/core/compile/sqlglot/test_compile_filter.py b/tests/unit/core/compile/sqlglot/test_compile_filter.py deleted file mode 100644 index 0afb5eb45b9..00000000000 --- a/tests/unit/core/compile/sqlglot/test_compile_filter.py +++ /dev/null @@ -1,25 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import pytest - -import bigframes.pandas as bpd - -pytest.importorskip("pytest_snapshot") - - -def test_compile_filter(scalar_types_df: bpd.DataFrame, snapshot): - bf_df = scalar_types_df[["rowindex", "int64_col"]] - bf_filter = bf_df[bf_df["rowindex"] >= 1] - snapshot.assert_match(bf_filter.sql, "out.sql") diff --git a/tests/unit/core/compile/sqlglot/test_compile_fromrange.py b/tests/unit/core/compile/sqlglot/test_compile_fromrange.py deleted file mode 100644 index 8c25ca0310c..00000000000 --- a/tests/unit/core/compile/sqlglot/test_compile_fromrange.py +++ /dev/null @@ -1,35 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import pandas as pd -import pytest - -import bigframes.pandas as bpd - -pytest.importorskip("pytest_snapshot") - - -def test_compile_fromrange(compiler_session, snapshot): - data = { - "timestamp_col": pd.date_range( - start="2021-01-01 13:00:00", periods=30, freq="1s" - ), - "int64_col": range(30), - "int64_too": range(10, 40), - } - df = bpd.DataFrame(data, session=compiler_session).set_index("timestamp_col") - sql, _, _ = df.resample(rule="7s")._block.to_sql_query( - include_index=True, enable_cache=False - ) - snapshot.assert_match(sql.strip() + "\n", "out.sql") diff --git a/tests/unit/core/compile/sqlglot/test_compile_geo.py b/tests/unit/core/compile/sqlglot/test_compile_geo.py deleted file mode 100644 index 4aad2dfa315..00000000000 --- a/tests/unit/core/compile/sqlglot/test_compile_geo.py +++ /dev/null @@ -1,55 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import pytest -from shapely.geometry import LineString # type: ignore - -import bigframes.bigquery as bbq -import bigframes.geopandas as gpd - -pytest.importorskip("pytest_snapshot") - - -def test_st_regionstats(compiler_session, snapshot): - geos = gpd.GeoSeries(["POINT(1 1)"], session=compiler_session) - result = bbq.st_regionstats( - geos, - "ee://some/raster/uri", - band="band1", - include="some equation", - options={"scale": 100}, - ) - assert "area" in result.struct.dtypes.index - snapshot.assert_match(result.struct.explode().sql, "out.sql") - - -def test_st_regionstats_without_optional_args(compiler_session, snapshot): - geos = gpd.GeoSeries(["POINT(1 1)"], session=compiler_session) - result = bbq.st_regionstats( - geos, - "ee://some/raster/uri", - ) - assert "area" in result.struct.dtypes.index - snapshot.assert_match(result.struct.explode().sql, "out.sql") - - -def test_st_simplify(compiler_session, snapshot): - geos = gpd.GeoSeries( - [LineString([(0, 0), (1, 1), (2, 0)])], session=compiler_session - ) - result = bbq.st_simplify( - geos, - tolerance_meters=123.125, - ) - snapshot.assert_match(result.to_frame().sql, "out.sql") diff --git a/tests/unit/core/compile/sqlglot/test_compile_isin.py b/tests/unit/core/compile/sqlglot/test_compile_isin.py deleted file mode 100644 index 8b3e7f7291f..00000000000 --- a/tests/unit/core/compile/sqlglot/test_compile_isin.py +++ /dev/null @@ -1,31 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import pytest - -import bigframes.pandas as bpd - -pytest.importorskip("pytest_snapshot") - - -def test_compile_isin(scalar_types_df: bpd.DataFrame, snapshot): - bf_isin = scalar_types_df["int64_col"].isin(scalar_types_df["int64_too"]).to_frame() - snapshot.assert_match(bf_isin.sql, "out.sql") - - -def test_compile_isin_not_nullable(scalar_types_df: bpd.DataFrame, snapshot): - bf_isin = ( - scalar_types_df["rowindex_2"].isin(scalar_types_df["rowindex_2"]).to_frame() - ) - snapshot.assert_match(bf_isin.sql, "out.sql") diff --git a/tests/unit/core/compile/sqlglot/test_compile_join.py b/tests/unit/core/compile/sqlglot/test_compile_join.py deleted file mode 100644 index ac016eec020..00000000000 --- a/tests/unit/core/compile/sqlglot/test_compile_join.py +++ /dev/null @@ -1,61 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import pytest - -import bigframes.pandas as bpd - -pytest.importorskip("pytest_snapshot") - - -def test_compile_join(scalar_types_df: bpd.DataFrame, snapshot): - left = scalar_types_df[["int64_col"]] - right = scalar_types_df.set_index("int64_col")[["int64_too"]] - join = left.join(right) - snapshot.assert_match(join.sql, "out.sql") - - -def test_compile_join_w_how(scalar_types_df: bpd.DataFrame): - left = scalar_types_df[["int64_col"]] - right = scalar_types_df.set_index("int64_col")[["int64_too"]] - - join_sql = left.join(right, how="left").sql - assert "LEFT JOIN" in join_sql - assert "ON" in join_sql - - join_sql = left.join(right, how="right").sql - assert "RIGHT JOIN" in join_sql - assert "ON" in join_sql - - join_sql = left.join(right, how="outer").sql - assert "FULL OUTER JOIN" in join_sql - assert "ON" in join_sql - - join_sql = left.join(right, how="inner").sql - assert "INNER JOIN" in join_sql - assert "ON" in join_sql - - join_sql = left.merge(right, how="cross").sql - assert "CROSS JOIN" in join_sql - assert "ON" not in join_sql - - -@pytest.mark.parametrize( - ("on"), - ["bool_col", "int64_col", "float64_col", "string_col", "time_col", "numeric_col"], -) -def test_compile_join_w_on(scalar_types_df: bpd.DataFrame, on: str, snapshot): - df = scalar_types_df[["rowindex", on]] - merge = df.merge(df, left_on=on, right_on=on) - snapshot.assert_match(merge.sql, "out.sql") diff --git a/tests/unit/core/compile/sqlglot/test_compile_random_sample.py b/tests/unit/core/compile/sqlglot/test_compile_random_sample.py deleted file mode 100644 index 6aec633238c..00000000000 --- a/tests/unit/core/compile/sqlglot/test_compile_random_sample.py +++ /dev/null @@ -1,35 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import pytest - -import bigframes.core as core -import bigframes.core.compile as compile -from bigframes.core import nodes - -pytest.importorskip("pytest_snapshot") - - -def test_compile_random_sample( - scalar_types_array_value: core.ArrayValue, - snapshot, -): - """This test verifies the SQL compilation of a RandomSampleNode. - - Because BigFrames doesn't expose a public API for creating a random sample - operation, this test constructs the node directly and then compiles it to SQL. - """ - node = nodes.RandomSampleNode(scalar_types_array_value.node, fraction=0.1) - sql = compile.sqlglot.compile_sql(compile.CompileRequest(node, sort_rows=True)).sql - snapshot.assert_match(sql, "out.sql") diff --git a/tests/unit/core/compile/sqlglot/test_compile_readlocal.py b/tests/unit/core/compile/sqlglot/test_compile_readlocal.py deleted file mode 100644 index 03a8b39d9a0..00000000000 --- a/tests/unit/core/compile/sqlglot/test_compile_readlocal.py +++ /dev/null @@ -1,78 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import numpy as np -import pandas as pd -import pytest - -import bigframes -import bigframes.pandas as bpd - -pytest.importorskip("pytest_snapshot") - - -def test_compile_readlocal( - scalar_types_pandas_df: pd.DataFrame, compiler_session: bigframes.Session, snapshot -): - bf_df = bpd.DataFrame(scalar_types_pandas_df, session=compiler_session) - snapshot.assert_match(bf_df.sql, "out.sql") - - -def test_compile_readlocal_w_structs_df( - nested_structs_pandas_df: pd.DataFrame, - compiler_session_w_nested_structs_types: bigframes.Session, - snapshot, -): - bf_df = bpd.DataFrame( - nested_structs_pandas_df, session=compiler_session_w_nested_structs_types - ) - snapshot.assert_match(bf_df.sql, "out.sql") - - -def test_compile_readlocal_w_lists_df( - repeated_types_pandas_df: pd.DataFrame, - compiler_session_w_repeated_types: bigframes.Session, - snapshot, -): - bf_df = bpd.DataFrame( - repeated_types_pandas_df, session=compiler_session_w_repeated_types - ) - snapshot.assert_match(bf_df.sql, "out.sql") - - -def test_compile_readlocal_w_json_df( - json_pandas_df: pd.DataFrame, - compiler_session_w_json_types: bigframes.Session, - snapshot, -): - bf_df = bpd.DataFrame(json_pandas_df, session=compiler_session_w_json_types) - snapshot.assert_match(bf_df.sql, "out.sql") - - -def test_compile_readlocal_w_special_values( - compiler_session: bigframes.Session, snapshot -): - df = pd.DataFrame( - { - "col_none": [None, 1, 2], - "col_inf": [np.inf, 1.0, 2.0], - "col_neginf": [-np.inf, 1.0, 2.0], - "col_nan": [np.nan, 1.0, 2.0], - "col_struct_none": [None, {"foo": 1}, {"foo": 2}], - "col_struct_w_none": [{"foo": None}, {"foo": 1}, {"foo": 2}], - "col_list_none": [None, [1, 2], [3, 4]], - } - ) - bf_df = bpd.DataFrame(df, session=compiler_session) - snapshot.assert_match(bf_df.sql, "out.sql") diff --git a/tests/unit/core/compile/sqlglot/test_compile_readtable.py b/tests/unit/core/compile/sqlglot/test_compile_readtable.py deleted file mode 100644 index 0f2058f21f6..00000000000 --- a/tests/unit/core/compile/sqlglot/test_compile_readtable.py +++ /dev/null @@ -1,94 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import datetime - -import google.cloud.bigquery as bigquery -import pytest - -import bigframes.pandas as bpd -from bigframes.core import bq_data - -pytest.importorskip("pytest_snapshot") - - -def test_compile_readtable(scalar_types_df: bpd.DataFrame, snapshot): - snapshot.assert_match(scalar_types_df.sql, "out.sql") - - -def test_compile_readtable_w_repeated_types(repeated_types_df: bpd.DataFrame, snapshot): - snapshot.assert_match(repeated_types_df.sql, "out.sql") - - -def test_compile_readtable_w_nested_structs_types( - nested_structs_types_df: bpd.DataFrame, snapshot -): - snapshot.assert_match(nested_structs_types_df.sql, "out.sql") - - -def test_compile_readtable_w_json_types(json_types_df: bpd.DataFrame, snapshot): - snapshot.assert_match(json_types_df.sql, "out.sql") - - -def test_compile_readtable_w_ordering(scalar_types_df: bpd.DataFrame, snapshot): - bf_df = scalar_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(scalar_types_df: bpd.DataFrame, snapshot): - bf_df = scalar_types_df[["int64_col"]] - bf_df = bf_df.sort_index().head(10) - snapshot.assert_match(bf_df.sql, "out.sql") - - -def test_compile_readtable_w_system_time( - compiler_session, scalar_types_table_schema, snapshot -): - table_ref = bigquery.TableReference( - bigquery.DatasetReference("bigframes-dev", "sqlglot_test"), - "scalar_types", - ) - table = bigquery.Table(table_ref, tuple(scalar_types_table_schema)) - table._properties["location"] = compiler_session._location - compiler_session._loader._df_snapshot[str(table_ref)] = ( - datetime.datetime(2025, 11, 9, 3, 4, 5, 678901, tzinfo=datetime.timezone.utc), - bq_data.GbqNativeTable.from_table(table), - ) - bf_df = compiler_session.read_gbq_table(str(table_ref)) - snapshot.assert_match(bf_df.sql, "out.sql") - - -def test_compile_readtable_w_columns_filters(compiler_session, snapshot): - columns = ["rowindex", "int64_col", "string_col"] - filters = [("rowindex", ">", 0), ("string_col", "in", ["Hello, World!"])] - bf_df = compiler_session._loader.read_gbq_table( - "bigframes-dev.sqlglot_test.scalar_types", - enable_snapshot=False, - columns=columns, - filters=filters, - ) - snapshot.assert_match(bf_df.sql, "out.sql") - - -def test_compile_astype_aliases(scalar_types_df: bpd.DataFrame, snapshot): - # Test case for issue #17394 (CAST columns lose their aliases) - bf_df = scalar_types_df[["timestamp_col", "int64_col"]] - result = bf_df.astype( - { - "timestamp_col": "string[pyarrow]", - "int64_col": "Float64", - } - ) - snapshot.assert_match(result.sql + "\n", "out.sql") diff --git a/tests/unit/core/compile/sqlglot/test_compile_window.py b/tests/unit/core/compile/sqlglot/test_compile_window.py deleted file mode 100644 index 1602ec2c478..00000000000 --- a/tests/unit/core/compile/sqlglot/test_compile_window.py +++ /dev/null @@ -1,61 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import numpy as np -import pandas as pd -import pytest - -import bigframes.pandas as bpd - -pytest.importorskip("pytest_snapshot") - - -def test_compile_window_w_skips_nulls_op(scalar_types_df: bpd.DataFrame, snapshot): - bf_df = scalar_types_df[["int64_col"]].sort_index() - # The SumOp's skips_nulls is True - result = bf_df.rolling(window=3).sum() - snapshot.assert_match(result.sql, "out.sql") - - -def test_compile_window_wo_skips_nulls_op(scalar_types_df: bpd.DataFrame, snapshot): - bf_df = scalar_types_df[["int64_col"]].sort_index() - # The CountOp's skips_nulls is False - result = bf_df.rolling(window=5).count() - snapshot.assert_match(result.sql, "out.sql") - - -def test_compile_window_w_groupby_rolling(scalar_types_df: bpd.DataFrame, snapshot): - bf_df = scalar_types_df[["bool_col", "int64_col"]].sort_index() - result = ( - bf_df.groupby(scalar_types_df["bool_col"]) - .rolling(window=3, closed="both") - .sum() - ) - snapshot.assert_match(result.sql, "out.sql") - - -def test_compile_window_w_range_rolling(compiler_session, snapshot): - # TODO: use `duration_col` instead. - values = np.arange(20) - pd_df = pd.DataFrame( - { - "ts_col": pd.Timestamp("20250101", tz="UTC") + pd.to_timedelta(values, "s"), - "int_col": values % 4, - "float_col": values / 2, - } - ) - bf_df = compiler_session.read_pandas(pd_df) - bf_series = bf_df.set_index("ts_col")["int_col"].sort_index() - result = bf_series.rolling(window="3s").sum() - snapshot.assert_match(result.to_frame().sql, "out.sql") diff --git a/tests/unit/core/compile/sqlglot/test_dataframe_accessor.py b/tests/unit/core/compile/sqlglot/test_dataframe_accessor.py deleted file mode 100644 index e430f566497..00000000000 --- a/tests/unit/core/compile/sqlglot/test_dataframe_accessor.py +++ /dev/null @@ -1,64 +0,0 @@ -# Copyright 2026 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import unittest.mock as mock - -import pandas as pd -import pytest - -import bigframes.pandas as bpd -import bigframes.session - -pytest.importorskip("pytest_snapshot") - -# Only test on the latest pandas since column naming behavior is slightly -# different across versions, e.g. unnamed vs 0 for unnamed Series. -pytest.importorskip("pandas", minversion="3.0.0") - - -def test_sql_scalar(scalar_types_df: bpd.DataFrame, snapshot, monkeypatch): - session = mock.create_autospec(bigframes.session.Session) - session.read_pandas.return_value = scalar_types_df - - def to_pandas(series, *, ordered): - assert ordered is True - sql, _, _ = series.to_frame()._to_sql_query(include_index=True) - return sql - - monkeypatch.setattr(bpd.Series, "to_pandas", to_pandas) - - df = pd.DataFrame({"int64_col": [1, 2], "int64_too": [3, 4]}) - result = df.bigquery.sql_scalar( - "ROUND({int64_col} + {int64_too})", - output_dtype=pd.Int64Dtype(), - session=session, - ) - - session.read_pandas.assert_called_once() - snapshot.assert_match(result.strip() + "\n", "out.sql") - - -def test_bigframes_sql_scalar(scalar_types_df: bpd.DataFrame, snapshot): - session = mock.create_autospec(bigframes.session.Session) - - result = scalar_types_df.bigquery.sql_scalar( - "ROUND({int64_col} + {int64_too})", - output_dtype=pd.Int64Dtype(), - session=session, - ) - - session.read_pandas.assert_not_called() - # Bigframes implementation returns a bigframes.series.Series - sql, _, _ = result.to_frame()._to_sql_query(include_index=True) - snapshot.assert_match(sql.strip() + "\n", "out.sql") diff --git a/tests/unit/core/compile/sqlglot/test_scalar_compiler.py b/tests/unit/core/compile/sqlglot/test_scalar_compiler.py deleted file mode 100644 index d8a59420452..00000000000 --- a/tests/unit/core/compile/sqlglot/test_scalar_compiler.py +++ /dev/null @@ -1,226 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import unittest.mock as mock - -import bigframes_vendored.sqlglot.expressions as sge -import pytest - -import bigframes.core.compile.sqlglot.expression_compiler as expression_compiler -import bigframes.operations as ops -from bigframes.core.compile.sqlglot.expressions.typed_expr import TypedExpr - - -def test_register_unary_op(): - compiler = expression_compiler.ExpressionCompiler() - - class MockUnaryOp(ops.UnaryOp): - name = "mock_unary_op" - - mock_op = MockUnaryOp() - mock_impl = mock.Mock() - - @compiler.register_unary_op(mock_op) - def _(expr: TypedExpr) -> sge.Expression: - mock_impl(expr) - return sge.Identifier(this="output") - - arg = TypedExpr(sge.Identifier(this="input"), "string") - result = compiler.compile_row_op(mock_op, [arg]) - assert result == sge.Identifier(this="output") - mock_impl.assert_called_once_with(arg) - - -def test_register_unary_op_pass_op(): - compiler = expression_compiler.ExpressionCompiler() - - class MockUnaryOp(ops.UnaryOp): - name = "mock_unary_op_pass_op" - - mock_op = MockUnaryOp() - mock_impl = mock.Mock() - - @compiler.register_unary_op(mock_op, pass_op=True) - def _(expr: TypedExpr, op: ops.UnaryOp) -> sge.Expression: - mock_impl(expr, op) - return sge.Identifier(this="output") - - arg = TypedExpr(sge.Identifier(this="input"), "string") - result = compiler.compile_row_op(mock_op, [arg]) - assert result == sge.Identifier(this="output") - mock_impl.assert_called_once_with(arg, mock_op) - - -def test_register_binary_op(): - compiler = expression_compiler.ExpressionCompiler() - - class MockBinaryOp(ops.BinaryOp): - name = "mock_binary_op" - - mock_op = MockBinaryOp() - mock_impl = mock.Mock() - - @compiler.register_binary_op(mock_op) - def _(left: TypedExpr, right: TypedExpr) -> sge.Expression: - mock_impl(left, right) - return sge.Identifier(this="output") - - arg1 = TypedExpr(sge.Identifier(this="input1"), "string") - arg2 = TypedExpr(sge.Identifier(this="input2"), "string") - result = compiler.compile_row_op(mock_op, [arg1, arg2]) - assert result == sge.Identifier(this="output") - mock_impl.assert_called_once_with(arg1, arg2) - - -def test_register_binary_op_pass_on(): - compiler = expression_compiler.ExpressionCompiler() - - class MockBinaryOp(ops.BinaryOp): - name = "mock_binary_op_pass_op" - - mock_op = MockBinaryOp() - mock_impl = mock.Mock() - - @compiler.register_binary_op(mock_op, pass_op=True) - def _(left: TypedExpr, right: TypedExpr, op: ops.BinaryOp) -> sge.Expression: - mock_impl(left, right, op) - return sge.Identifier(this="output") - - arg1 = TypedExpr(sge.Identifier(this="input1"), "string") - arg2 = TypedExpr(sge.Identifier(this="input2"), "string") - result = compiler.compile_row_op(mock_op, [arg1, arg2]) - assert result == sge.Identifier(this="output") - mock_impl.assert_called_once_with(arg1, arg2, mock_op) - - -def test_register_ternary_op(): - compiler = expression_compiler.ExpressionCompiler() - - class MockTernaryOp(ops.TernaryOp): - name = "mock_ternary_op" - - mock_op = MockTernaryOp() - mock_impl = mock.Mock() - - @compiler.register_ternary_op(mock_op) - def _(arg1: TypedExpr, arg2: TypedExpr, arg3: TypedExpr) -> sge.Expression: - mock_impl(arg1, arg2, arg3) - return sge.Identifier(this="output") - - arg1 = TypedExpr(sge.Identifier(this="input1"), "string") - arg2 = TypedExpr(sge.Identifier(this="input2"), "string") - arg3 = TypedExpr(sge.Identifier(this="input3"), "string") - result = compiler.compile_row_op(mock_op, [arg1, arg2, arg3]) - assert result == sge.Identifier(this="output") - mock_impl.assert_called_once_with(arg1, arg2, arg3) - - -def test_register_nary_op(): - compiler = expression_compiler.ExpressionCompiler() - - class MockNaryOp(ops.NaryOp): - name = "mock_nary_op" - - mock_op = MockNaryOp() - mock_impl = mock.Mock() - - @compiler.register_nary_op(mock_op) - def _(*args: TypedExpr) -> sge.Expression: - mock_impl(*args) - return sge.Identifier(this="output") - - arg1 = TypedExpr(sge.Identifier(this="input1"), "string") - arg2 = TypedExpr(sge.Identifier(this="input2"), "string") - result = compiler.compile_row_op(mock_op, [arg1, arg2]) - assert result == sge.Identifier(this="output") - mock_impl.assert_called_once_with(arg1, arg2) - - -def test_register_nary_op_pass_on(): - compiler = expression_compiler.ExpressionCompiler() - - class MockNaryOp(ops.NaryOp): - name = "mock_nary_op_pass_op" - - mock_op = MockNaryOp() - mock_impl = mock.Mock() - - @compiler.register_nary_op(mock_op, pass_op=True) - def _(*args: TypedExpr, op: ops.NaryOp) -> sge.Expression: - mock_impl(*args, op=op) - return sge.Identifier(this="output") - - arg1 = TypedExpr(sge.Identifier(this="input1"), "string") - arg2 = TypedExpr(sge.Identifier(this="input2"), "string") - arg3 = TypedExpr(sge.Identifier(this="input3"), "string") - arg4 = TypedExpr(sge.Identifier(this="input4"), "string") - result = compiler.compile_row_op(mock_op, [arg1, arg2, arg3, arg4]) - assert result == sge.Identifier(this="output") - mock_impl.assert_called_once_with(arg1, arg2, arg3, arg4, op=mock_op) - - -def test_binary_op_parentheses(): - compiler = expression_compiler.ExpressionCompiler() - - class MockAddOp(ops.BinaryOp): - name = "mock_add_op" - - class MockMulOp(ops.BinaryOp): - name = "mock_mul_op" - - add_op = MockAddOp() - mul_op = MockMulOp() - - @compiler.register_binary_op(add_op) - def _(left: TypedExpr, right: TypedExpr) -> sge.Expression: - return sge.Add(this=left.expr, expression=right.expr) - - @compiler.register_binary_op(mul_op) - def _(left: TypedExpr, right: TypedExpr) -> sge.Expression: - return sge.Mul(this=left.expr, expression=right.expr) - - a = TypedExpr(sge.Identifier(this="a"), "int") - b = TypedExpr(sge.Identifier(this="b"), "int") - c = TypedExpr(sge.Identifier(this="c"), "int") - - # (a + b) * c - add_expr = compiler.compile_row_op(add_op, [a, b]) - add_typed_expr = TypedExpr(add_expr, "int") - result1 = compiler.compile_row_op(mul_op, [add_typed_expr, c]) - assert result1.sql() == "(a + b) * c" - - # a * (b + c) - add_expr_2 = compiler.compile_row_op(add_op, [b, c]) - add_typed_expr_2 = TypedExpr(add_expr_2, "int") - result2 = compiler.compile_row_op(mul_op, [a, add_typed_expr_2]) - assert result2.sql() == "a * (b + c)" - - -def test_register_duplicate_op_raises(): - compiler = expression_compiler.ExpressionCompiler() - - class MockUnaryOp(ops.UnaryOp): - name = "mock_unary_op_duplicate" - - mock_op = MockUnaryOp() - - @compiler.register_unary_op(mock_op) - def _(expr: TypedExpr) -> sge.Expression: - return sge.Identifier(this="output") - - with pytest.raises(ValueError): - - @compiler.register_unary_op(mock_op) - def _(expr: TypedExpr) -> sge.Expression: - return sge.Identifier(this="output2") diff --git a/tests/unit/core/compile/sqlglot/test_sqlglot_types.py b/tests/unit/core/compile/sqlglot/test_sqlglot_types.py deleted file mode 100644 index 5c2d84383d7..00000000000 --- a/tests/unit/core/compile/sqlglot/test_sqlglot_types.py +++ /dev/null @@ -1,64 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import pandas as pd -import pyarrow as pa - -import bigframes.core.compile.sqlglot.sqlglot_types as sgt -import bigframes.dtypes as dtypes - - -def test_from_bigframes_simple_dtypes(): - assert sgt.from_bigframes_dtype(dtypes.INT_DTYPE) == "INT64" - assert sgt.from_bigframes_dtype(dtypes.FLOAT_DTYPE) == "FLOAT64" - assert sgt.from_bigframes_dtype(dtypes.STRING_DTYPE) == "STRING" - assert sgt.from_bigframes_dtype(dtypes.BOOL_DTYPE) == "BOOLEAN" - assert sgt.from_bigframes_dtype(dtypes.DATE_DTYPE) == "DATE" - assert sgt.from_bigframes_dtype(dtypes.TIME_DTYPE) == "TIME" - assert sgt.from_bigframes_dtype(dtypes.DATETIME_DTYPE) == "DATETIME" - assert sgt.from_bigframes_dtype(dtypes.TIMESTAMP_DTYPE) == "TIMESTAMP" - assert sgt.from_bigframes_dtype(dtypes.BYTES_DTYPE) == "BYTES" - assert sgt.from_bigframes_dtype(dtypes.NUMERIC_DTYPE) == "NUMERIC" - assert sgt.from_bigframes_dtype(dtypes.BIGNUMERIC_DTYPE) == "BIGNUMERIC" - assert sgt.from_bigframes_dtype(dtypes.JSON_DTYPE) == "JSON" - assert sgt.from_bigframes_dtype(dtypes.GEO_DTYPE) == "GEOGRAPHY" - - -def test_from_bigframes_struct_dtypes(): - fields = [pa.field("int_col", pa.int64()), pa.field("bool_col", pa.bool_())] - struct_type = pd.ArrowDtype(pa.struct(fields)) - expected = "STRUCT" - assert sgt.from_bigframes_dtype(struct_type) == expected - - -def test_from_bigframes_array_dtypes(): - int_array_type = pd.ArrowDtype(pa.list_(pa.int64())) - assert sgt.from_bigframes_dtype(int_array_type) == "ARRAY" - - string_array_type = pd.ArrowDtype(pa.list_(pa.string())) - assert sgt.from_bigframes_dtype(string_array_type) == "ARRAY" - - -def test_from_bigframes_multi_nested_dtypes(): - fields = [ - pa.field("string_col", pa.string()), - pa.field("date_col", pa.date32()), - pa.field("array_col", pa.list_(pa.timestamp("us"))), - ] - array_type = pd.ArrowDtype(pa.list_(pa.struct(fields))) - - expected = ( - "ARRAY>>" - ) - assert sgt.from_bigframes_dtype(array_type) == expected diff --git a/tests/unit/core/compile/sqlglot/tpch/conftest.py b/tests/unit/core/compile/sqlglot/tpch/conftest.py deleted file mode 100644 index b351b6988eb..00000000000 --- a/tests/unit/core/compile/sqlglot/tpch/conftest.py +++ /dev/null @@ -1,165 +0,0 @@ -# Copyright 2026 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 datetime -import functools -import unittest.mock as mock - -import pytest -from google.cloud import bigquery - -import bigframes.testing.mocks as mocks -from bigframes.testing import compiler_session - -freezegun = pytest.importorskip("freezegun") - -PROJECT_NAME = "bigframes-dev-perf" -DATASET_NAME = "tpch_0001t" -LOCATION_NAME = "test-region" - -TPCH_SCHEMAS = { - "LINEITEM": [ - bigquery.SchemaField("L_ORDERKEY", "INTEGER", mode="REQUIRED"), - bigquery.SchemaField("L_PARTKEY", "INTEGER", mode="REQUIRED"), - bigquery.SchemaField("L_SUPPKEY", "INTEGER", mode="REQUIRED"), - bigquery.SchemaField("L_LINENUMBER", "INTEGER", mode="REQUIRED"), - bigquery.SchemaField("L_QUANTITY", "FLOAT", mode="REQUIRED"), - bigquery.SchemaField("L_EXTENDEDPRICE", "FLOAT", mode="REQUIRED"), - bigquery.SchemaField("L_DISCOUNT", "FLOAT", mode="REQUIRED"), - bigquery.SchemaField("L_TAX", "FLOAT", mode="REQUIRED"), - bigquery.SchemaField("L_RETURNFLAG", "STRING", mode="REQUIRED"), - bigquery.SchemaField("L_LINESTATUS", "STRING", mode="REQUIRED"), - bigquery.SchemaField("L_SHIPDATE", "DATE", mode="REQUIRED"), - bigquery.SchemaField("L_COMMITDATE", "DATE", mode="REQUIRED"), - bigquery.SchemaField("L_RECEIPTDATE", "DATE", mode="REQUIRED"), - bigquery.SchemaField("L_SHIPINSTRUCT", "STRING", mode="REQUIRED"), - bigquery.SchemaField("L_SHIPMODE", "STRING", mode="REQUIRED"), - bigquery.SchemaField("L_COMMENT", "STRING"), - ], - "ORDERS": [ - bigquery.SchemaField("O_ORDERKEY", "INTEGER", mode="REQUIRED"), - bigquery.SchemaField("O_CUSTKEY", "INTEGER", mode="REQUIRED"), - bigquery.SchemaField("O_ORDERSTATUS", "STRING", mode="REQUIRED"), - bigquery.SchemaField("O_TOTALPRICE", "FLOAT", mode="REQUIRED"), - bigquery.SchemaField("O_ORDERDATE", "DATE", mode="REQUIRED"), - bigquery.SchemaField("O_ORDERPRIORITY", "STRING", mode="REQUIRED"), - bigquery.SchemaField("O_CLERK", "STRING", mode="REQUIRED"), - bigquery.SchemaField("O_SHIPPRIORITY", "INTEGER", mode="REQUIRED"), - bigquery.SchemaField("O_COMMENT", "STRING"), - ], - "PART": [ - bigquery.SchemaField("P_PARTKEY", "INTEGER", mode="REQUIRED"), - bigquery.SchemaField("P_NAME", "STRING", mode="REQUIRED"), - bigquery.SchemaField("P_MFGR", "STRING", mode="REQUIRED"), - bigquery.SchemaField("P_BRAND", "STRING", mode="REQUIRED"), - bigquery.SchemaField("P_TYPE", "STRING", mode="REQUIRED"), - bigquery.SchemaField("P_SIZE", "INTEGER", mode="REQUIRED"), - bigquery.SchemaField("P_CONTAINER", "STRING", mode="REQUIRED"), - bigquery.SchemaField("P_RETAILPRICE", "FLOAT", mode="REQUIRED"), - bigquery.SchemaField("P_COMMENT", "STRING"), - ], - "SUPPLIER": [ - bigquery.SchemaField("S_SUPPKEY", "INTEGER", mode="REQUIRED"), - bigquery.SchemaField("S_NAME", "STRING", mode="REQUIRED"), - bigquery.SchemaField("S_ADDRESS", "STRING", mode="REQUIRED"), - bigquery.SchemaField("S_NATIONKEY", "INTEGER", mode="REQUIRED"), - bigquery.SchemaField("S_PHONE", "STRING", mode="REQUIRED"), - bigquery.SchemaField("S_ACCTBAL", "FLOAT", mode="REQUIRED"), - bigquery.SchemaField("S_COMMENT", "STRING"), - ], - "PARTSUPP": [ - bigquery.SchemaField("PS_PARTKEY", "INTEGER", mode="REQUIRED"), - bigquery.SchemaField("PS_SUPPKEY", "INTEGER", mode="REQUIRED"), - bigquery.SchemaField("PS_AVAILQTY", "INTEGER", mode="REQUIRED"), - bigquery.SchemaField("PS_SUPPLYCOST", "FLOAT", mode="REQUIRED"), - bigquery.SchemaField("PS_COMMENT", "STRING"), - ], - "CUSTOMER": [ - bigquery.SchemaField("C_CUSTKEY", "INTEGER", mode="REQUIRED"), - bigquery.SchemaField("C_NAME", "STRING", mode="REQUIRED"), - bigquery.SchemaField("C_ADDRESS", "STRING", mode="REQUIRED"), - bigquery.SchemaField("C_NATIONKEY", "INTEGER", mode="REQUIRED"), - bigquery.SchemaField("C_PHONE", "STRING", mode="REQUIRED"), - bigquery.SchemaField("C_ACCTBAL", "FLOAT", mode="REQUIRED"), - bigquery.SchemaField("C_MKTSEGMENT", "STRING", mode="REQUIRED"), - bigquery.SchemaField("C_COMMENT", "STRING"), - ], - "NATION": [ - bigquery.SchemaField("N_NATIONKEY", "INTEGER", mode="REQUIRED"), - bigquery.SchemaField("N_NAME", "STRING", mode="REQUIRED"), - bigquery.SchemaField("N_REGIONKEY", "INTEGER", mode="REQUIRED"), - bigquery.SchemaField("N_COMMENT", "STRING"), - ], - "REGION": [ - bigquery.SchemaField("R_REGIONKEY", "INTEGER", mode="REQUIRED"), - bigquery.SchemaField("R_NAME", "STRING", mode="REQUIRED"), - bigquery.SchemaField("R_COMMENT", "STRING"), - ], -} - - -def _create_mock_bqclient(): - """Helper function to create a compiler session.""" - - bqclient = mock.create_autospec(bigquery.Client, instance=True) - bqclient.project = DATASET_NAME - bqclient.location = LOCATION_NAME - table_create_time = datetime.datetime.now() - - def get_table_mock(table_ref): - if isinstance(table_ref, str): - table_ref = bigquery.TableReference.from_string(table_ref) - - table_id = table_ref.table_id - schema = TPCH_SCHEMAS.get(table_id, []) - - table = mock.create_autospec(bigquery.Table, instance=True) - table._properties = {} - type(table).created = mock.PropertyMock(return_value=table_create_time) - type(table).location = mock.PropertyMock(return_value=LOCATION_NAME) - type(table).schema = mock.PropertyMock(return_value=schema) - type(table).project = table_ref.project - type(table).dataset_id = table_ref.dataset_id - type(table).table_id = table_id - type(table).num_rows = mock.PropertyMock(return_value=1000000000) - return table - - bqclient.get_table.side_effect = get_table_mock - return bqclient - - -@pytest.fixture(scope="session") -def tpch_session(): - anonymous_dataset = bigquery.DatasetReference.from_string( - f"{PROJECT_NAME}.{DATASET_NAME}" - ) - session = mocks.create_bigquery_session( - bqclient=_create_mock_bqclient(), - anonymous_dataset=anonymous_dataset, - ) - - # Disable snapshotting for TPC-H tests to keep snapshots clean - original_read_gbq_table = session._loader.read_gbq_table - - @functools.wraps(original_read_gbq_table) - def read_gbq_table_no_snapshot(*args, **kwargs): - kwargs["enable_snapshot"] = False - return original_read_gbq_table(*args, **kwargs) - - session._executor = compiler_session.SQLCompilerExecutor() - - with mock.patch.object( - session._loader, "read_gbq_table", new=read_gbq_table_no_snapshot - ): - yield session diff --git a/tests/unit/core/compile/sqlglot/tpch/snapshots/test_tpch/test_tpch_query/1/out.sql b/tests/unit/core/compile/sqlglot/tpch/snapshots/test_tpch/test_tpch_query/1/out.sql deleted file mode 100644 index 84ed65ec174..00000000000 --- a/tests/unit/core/compile/sqlglot/tpch/snapshots/test_tpch/test_tpch_query/1/out.sql +++ /dev/null @@ -1,75 +0,0 @@ -WITH `bfcte_0` AS ( - SELECT - `L_QUANTITY`, - `L_EXTENDEDPRICE`, - `L_DISCOUNT`, - `L_TAX`, - `L_RETURNFLAG`, - `L_LINESTATUS`, - `L_SHIPDATE`, - `L_QUANTITY` AS `bfcol_7`, - `L_EXTENDEDPRICE` AS `bfcol_8`, - `L_DISCOUNT` AS `bfcol_9`, - `L_TAX` AS `bfcol_10`, - `L_RETURNFLAG` AS `bfcol_11`, - `L_LINESTATUS` AS `bfcol_12`, - `L_SHIPDATE` <= CAST('1998-09-02' AS DATE) AS `bfcol_13`, - `L_QUANTITY` AS `bfcol_27`, - `L_EXTENDEDPRICE` AS `bfcol_28`, - `L_DISCOUNT` AS `bfcol_29`, - `L_TAX` AS `bfcol_30`, - `L_RETURNFLAG` AS `bfcol_31`, - `L_LINESTATUS` AS `bfcol_32`, - `L_EXTENDEDPRICE` * ( - 1.0 - `L_DISCOUNT` - ) AS `bfcol_33`, - `L_QUANTITY` AS `bfcol_41`, - `L_EXTENDEDPRICE` AS `bfcol_42`, - `L_DISCOUNT` AS `bfcol_43`, - `L_RETURNFLAG` AS `bfcol_44`, - `L_LINESTATUS` AS `bfcol_45`, - `L_EXTENDEDPRICE` * ( - 1.0 - `L_DISCOUNT` - ) AS `bfcol_46`, - ( - `L_EXTENDEDPRICE` * ( - 1.0 - `L_DISCOUNT` - ) - ) * ( - 1.0 + `L_TAX` - ) AS `bfcol_47` - FROM `bigframes-dev-perf`.`tpch_0001t`.`LINEITEM` AS `bft_0` - WHERE - `L_SHIPDATE` <= CAST('1998-09-02' AS DATE) -), `bfcte_1` AS ( - SELECT - `bfcol_44`, - `bfcol_45`, - COALESCE(SUM(`bfcol_41`), 0) AS `bfcol_55`, - COALESCE(SUM(`bfcol_42`), 0) AS `bfcol_56`, - COALESCE(SUM(`bfcol_46`), 0) AS `bfcol_57`, - COALESCE(SUM(`bfcol_47`), 0) AS `bfcol_58`, - AVG(`bfcol_41`) AS `bfcol_59`, - AVG(`bfcol_42`) AS `bfcol_60`, - AVG(`bfcol_43`) AS `bfcol_61`, - COUNT(`bfcol_41`) AS `bfcol_62` - FROM `bfcte_0` - GROUP BY - `bfcol_44`, - `bfcol_45` -) -SELECT - `bfcol_44` AS `L_RETURNFLAG`, - `bfcol_45` AS `L_LINESTATUS`, - `bfcol_55` AS `SUM_QTY`, - `bfcol_56` AS `SUM_BASE_PRICE`, - `bfcol_57` AS `SUM_DISC_PRICE`, - `bfcol_58` AS `SUM_CHARGE`, - `bfcol_59` AS `AVG_QTY`, - `bfcol_60` AS `AVG_PRICE`, - `bfcol_61` AS `AVG_DISC`, - `bfcol_62` AS `COUNT_ORDER` -FROM `bfcte_1` -ORDER BY - `bfcol_44` ASC NULLS LAST, - `bfcol_45` ASC NULLS LAST \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/tpch/snapshots/test_tpch/test_tpch_query/10/out.sql b/tests/unit/core/compile/sqlglot/tpch/snapshots/test_tpch/test_tpch_query/10/out.sql deleted file mode 100644 index 39dc2484342..00000000000 --- a/tests/unit/core/compile/sqlglot/tpch/snapshots/test_tpch/test_tpch_query/10/out.sql +++ /dev/null @@ -1,162 +0,0 @@ -WITH `bfcte_0` AS ( - SELECT - `N_NATIONKEY` AS `bfcol_0`, - `N_NAME` AS `bfcol_1` - FROM `bigframes-dev-perf`.`tpch_0001t`.`NATION` AS `bft_3` -), `bfcte_1` AS ( - SELECT - `L_ORDERKEY` AS `bfcol_2`, - `L_EXTENDEDPRICE` AS `bfcol_3`, - `L_DISCOUNT` AS `bfcol_4`, - `L_RETURNFLAG` AS `bfcol_5` - FROM `bigframes-dev-perf`.`tpch_0001t`.`LINEITEM` AS `bft_2` -), `bfcte_2` AS ( - SELECT - `O_ORDERKEY` AS `bfcol_6`, - `O_CUSTKEY` AS `bfcol_7`, - `O_ORDERDATE` AS `bfcol_8` - FROM `bigframes-dev-perf`.`tpch_0001t`.`ORDERS` AS `bft_1` -), `bfcte_3` AS ( - SELECT - `C_CUSTKEY` AS `bfcol_9`, - `C_NAME` AS `bfcol_10`, - `C_ADDRESS` AS `bfcol_11`, - `C_NATIONKEY` AS `bfcol_12`, - `C_PHONE` AS `bfcol_13`, - `C_ACCTBAL` AS `bfcol_14`, - `C_COMMENT` AS `bfcol_15` - FROM `bigframes-dev-perf`.`tpch_0001t`.`CUSTOMER` AS `bft_0` -), `bfcte_4` AS ( - SELECT - `bfcol_9` AS `bfcol_16`, - `bfcol_10` AS `bfcol_17`, - `bfcol_11` AS `bfcol_18`, - `bfcol_12` AS `bfcol_19`, - `bfcol_13` AS `bfcol_20`, - `bfcol_14` AS `bfcol_21`, - `bfcol_15` AS `bfcol_22`, - `bfcol_6` AS `bfcol_23`, - `bfcol_8` AS `bfcol_24` - FROM `bfcte_3` - INNER JOIN `bfcte_2` - ON `bfcol_9` = `bfcol_7` -), `bfcte_5` AS ( - SELECT - `bfcol_16` AS `bfcol_25`, - `bfcol_17` AS `bfcol_26`, - `bfcol_18` AS `bfcol_27`, - `bfcol_19` AS `bfcol_28`, - `bfcol_20` AS `bfcol_29`, - `bfcol_21` AS `bfcol_30`, - `bfcol_22` AS `bfcol_31`, - `bfcol_24` AS `bfcol_32`, - `bfcol_3` AS `bfcol_33`, - `bfcol_4` AS `bfcol_34`, - `bfcol_5` AS `bfcol_35` - FROM `bfcte_4` - INNER JOIN `bfcte_1` - ON `bfcol_23` = `bfcol_2` -), `bfcte_6` AS ( - SELECT - `bfcol_25`, - `bfcol_26`, - `bfcol_27`, - `bfcol_28`, - `bfcol_29`, - `bfcol_30`, - `bfcol_31`, - `bfcol_32`, - `bfcol_33`, - `bfcol_34`, - `bfcol_35`, - `bfcol_0`, - `bfcol_1`, - `bfcol_25` AS `bfcol_47`, - `bfcol_26` AS `bfcol_48`, - `bfcol_27` AS `bfcol_49`, - `bfcol_29` AS `bfcol_50`, - `bfcol_30` AS `bfcol_51`, - `bfcol_31` AS `bfcol_52`, - `bfcol_33` AS `bfcol_53`, - `bfcol_34` AS `bfcol_54`, - `bfcol_1` AS `bfcol_55`, - ( - ( - `bfcol_32` >= CAST('1993-10-01' AS DATE) - ) - AND ( - `bfcol_32` < CAST('1994-01-01' AS DATE) - ) - ) - AND ( - `bfcol_35` = 'R' - ) AS `bfcol_56`, - `bfcol_25` AS `bfcol_76`, - `bfcol_26` AS `bfcol_77`, - `bfcol_27` AS `bfcol_78`, - `bfcol_29` AS `bfcol_79`, - `bfcol_30` AS `bfcol_80`, - `bfcol_31` AS `bfcol_81`, - `bfcol_1` AS `bfcol_82`, - ROUND(( - `bfcol_33` * ( - 1 - `bfcol_34` - ) - ), 2) AS `bfcol_83` - FROM `bfcte_5` - INNER JOIN `bfcte_0` - ON `bfcol_28` = `bfcol_0` - WHERE - ( - ( - `bfcol_32` >= CAST('1993-10-01' AS DATE) - ) - AND ( - `bfcol_32` < CAST('1994-01-01' AS DATE) - ) - ) - AND ( - `bfcol_35` = 'R' - ) -), `bfcte_7` AS ( - SELECT - `bfcol_76`, - `bfcol_77`, - `bfcol_80`, - `bfcol_79`, - `bfcol_82`, - `bfcol_78`, - `bfcol_81`, - COALESCE(SUM(`bfcol_83`), 0) AS `bfcol_92` - FROM `bfcte_6` - WHERE - NOT `bfcol_81` IS NULL - GROUP BY - `bfcol_76`, - `bfcol_77`, - `bfcol_80`, - `bfcol_79`, - `bfcol_82`, - `bfcol_78`, - `bfcol_81` -) -SELECT - `bfcol_76` AS `C_CUSTKEY`, - `bfcol_77` AS `C_NAME`, - `bfcol_92` AS `REVENUE`, - `bfcol_80` AS `C_ACCTBAL`, - `bfcol_82` AS `N_NAME`, - `bfcol_78` AS `C_ADDRESS`, - `bfcol_79` AS `C_PHONE`, - `bfcol_81` AS `C_COMMENT` -FROM `bfcte_7` -ORDER BY - `bfcol_92` DESC, - `bfcol_76` ASC NULLS LAST, - `bfcol_77` ASC NULLS LAST, - `bfcol_80` ASC NULLS LAST, - `bfcol_79` ASC NULLS LAST, - `bfcol_82` ASC NULLS LAST, - `bfcol_78` ASC NULLS LAST, - `bfcol_81` ASC NULLS LAST -LIMIT 20 \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/tpch/snapshots/test_tpch/test_tpch_query/11/out.sql b/tests/unit/core/compile/sqlglot/tpch/snapshots/test_tpch/test_tpch_query/11/out.sql deleted file mode 100644 index 31a357be7c0..00000000000 --- a/tests/unit/core/compile/sqlglot/tpch/snapshots/test_tpch/test_tpch_query/11/out.sql +++ /dev/null @@ -1,115 +0,0 @@ -WITH `bfcte_0` AS ( - SELECT - * - FROM UNNEST(ARRAY>[STRUCT(0.0, 0, 0)]) -), `bfcte_1` AS ( - SELECT - `PS_SUPPKEY` AS `bfcol_0`, - `PS_AVAILQTY` AS `bfcol_1`, - `PS_SUPPLYCOST` AS `bfcol_2` - FROM `bigframes-dev-perf`.`tpch_0001t`.`PARTSUPP` AS `bft_2` -), `bfcte_2` AS ( - SELECT - `PS_PARTKEY` AS `bfcol_10`, - `PS_SUPPKEY` AS `bfcol_11`, - `PS_AVAILQTY` AS `bfcol_12`, - `PS_SUPPLYCOST` AS `bfcol_13` - FROM `bigframes-dev-perf`.`tpch_0001t`.`PARTSUPP` AS `bft_2` -), `bfcte_3` AS ( - SELECT - `S_SUPPKEY` AS `bfcol_3`, - `S_NATIONKEY` AS `bfcol_4` - FROM `bigframes-dev-perf`.`tpch_0001t`.`SUPPLIER` AS `bft_1` -), `bfcte_4` AS ( - SELECT - `N_NATIONKEY` AS `bfcol_18` - FROM `bigframes-dev-perf`.`tpch_0001t`.`NATION` AS `bft_0` - WHERE - `N_NAME` = 'GERMANY' -), `bfcte_5` AS ( - SELECT - `bfcol_3` AS `bfcol_19` - FROM `bfcte_4` - INNER JOIN `bfcte_3` - ON `bfcol_18` = `bfcol_4` -), `bfcte_6` AS ( - SELECT - `bfcol_19`, - `bfcol_0`, - `bfcol_1`, - `bfcol_2`, - `bfcol_1` AS `bfcol_25`, - `bfcol_2` AS `bfcol_26`, - `bfcol_2` AS `bfcol_33`, - `bfcol_1` AS `bfcol_34`, - `bfcol_2` * `bfcol_1` AS `bfcol_40` - FROM `bfcte_5` - INNER JOIN `bfcte_1` - ON `bfcol_19` = `bfcol_0` -), `bfcte_7` AS ( - SELECT - `bfcol_19`, - `bfcol_10`, - `bfcol_11`, - `bfcol_12`, - `bfcol_13`, - `bfcol_10` AS `bfcol_27`, - `bfcol_13` * `bfcol_12` AS `bfcol_28` - FROM `bfcte_5` - INNER JOIN `bfcte_2` - ON `bfcol_19` = `bfcol_11` -), `bfcte_8` AS ( - SELECT - COALESCE(SUM(`bfcol_40`), 0) AS `bfcol_44` - FROM `bfcte_6` -), `bfcte_9` AS ( - SELECT - `bfcol_27`, - COALESCE(SUM(`bfcol_28`), 0) AS `bfcol_35` - FROM `bfcte_7` - GROUP BY - `bfcol_27` -), `bfcte_10` AS ( - SELECT - `bfcol_44`, - 0 AS `bfcol_45` - FROM `bfcte_8` -), `bfcte_11` AS ( - SELECT - `bfcol_27` AS `bfcol_41`, - ROUND(`bfcol_35`, 2) AS `bfcol_42` - FROM `bfcte_9` -), `bfcte_12` AS ( - SELECT - `bfcol_7`, - `bfcol_8`, - `bfcol_9`, - `bfcol_44`, - `bfcol_45`, - CASE WHEN `bfcol_9` = 0 THEN `bfcol_44` END AS `bfcol_46`, - IF(`bfcol_45` = 0, CASE WHEN `bfcol_9` = 0 THEN `bfcol_44` END, NULL) AS `bfcol_51` - FROM `bfcte_0` - CROSS JOIN `bfcte_10` -), `bfcte_13` AS ( - SELECT - `bfcol_7`, - `bfcol_8`, - ANY_VALUE(`bfcol_51`) AS `bfcol_55` - FROM `bfcte_12` - GROUP BY - `bfcol_7`, - `bfcol_8` -), `bfcte_14` AS ( - SELECT - `bfcol_55` * 0.0001 AS `bfcol_58` - FROM `bfcte_13` -) -SELECT - `bfcol_41` AS `PS_PARTKEY`, - `bfcol_42` AS `VALUE` -FROM `bfcte_11` -CROSS JOIN `bfcte_14` -WHERE - `bfcol_42` > `bfcol_58` -ORDER BY - `bfcol_42` DESC \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/tpch/snapshots/test_tpch/test_tpch_query/12/out.sql b/tests/unit/core/compile/sqlglot/tpch/snapshots/test_tpch/test_tpch_query/12/out.sql deleted file mode 100644 index d5ab954a20b..00000000000 --- a/tests/unit/core/compile/sqlglot/tpch/snapshots/test_tpch/test_tpch_query/12/out.sql +++ /dev/null @@ -1,90 +0,0 @@ -WITH `bfcte_0` AS ( - SELECT - `L_ORDERKEY` AS `bfcol_0`, - `L_SHIPDATE` AS `bfcol_1`, - `L_COMMITDATE` AS `bfcol_2`, - `L_RECEIPTDATE` AS `bfcol_3`, - `L_SHIPMODE` AS `bfcol_4` - FROM `bigframes-dev-perf`.`tpch_0001t`.`LINEITEM` AS `bft_1` -), `bfcte_1` AS ( - SELECT - `O_ORDERKEY` AS `bfcol_5`, - `O_ORDERPRIORITY` AS `bfcol_6` - FROM `bigframes-dev-perf`.`tpch_0001t`.`ORDERS` AS `bft_0` -), `bfcte_2` AS ( - SELECT - `bfcol_5`, - `bfcol_6`, - `bfcol_0`, - `bfcol_1`, - `bfcol_2`, - `bfcol_3`, - `bfcol_4`, - `bfcol_6` AS `bfcol_12`, - `bfcol_4` AS `bfcol_13`, - ( - ( - ( - COALESCE(COALESCE(`bfcol_4` IN ('MAIL', 'SHIP'), FALSE), FALSE) - AND ( - `bfcol_2` < `bfcol_3` - ) - ) - AND ( - `bfcol_1` < `bfcol_2` - ) - ) - AND ( - `bfcol_3` >= CAST('1994-01-01' AS DATE) - ) - ) - AND ( - `bfcol_3` < CAST('1995-01-01' AS DATE) - ) AS `bfcol_14`, - `bfcol_6` AS `bfcol_20`, - `bfcol_4` AS `bfcol_21`, - CAST(COALESCE(COALESCE(`bfcol_6` IN ('1-URGENT', '2-HIGH'), FALSE), FALSE) AS INT64) AS `bfcol_22`, - `bfcol_4` AS `bfcol_26`, - CAST(COALESCE(COALESCE(`bfcol_6` IN ('1-URGENT', '2-HIGH'), FALSE), FALSE) AS INT64) AS `bfcol_27`, - CAST(NOT ( - COALESCE(COALESCE(`bfcol_6` IN ('1-URGENT', '2-HIGH'), FALSE), FALSE) - ) AS INT64) AS `bfcol_28` - FROM `bfcte_1` - INNER JOIN `bfcte_0` - ON `bfcol_5` = `bfcol_0` - WHERE - ( - ( - ( - COALESCE(COALESCE(`bfcol_4` IN ('MAIL', 'SHIP'), FALSE), FALSE) - AND ( - `bfcol_2` < `bfcol_3` - ) - ) - AND ( - `bfcol_1` < `bfcol_2` - ) - ) - AND ( - `bfcol_3` >= CAST('1994-01-01' AS DATE) - ) - ) - AND ( - `bfcol_3` < CAST('1995-01-01' AS DATE) - ) -), `bfcte_3` AS ( - SELECT - `bfcol_26`, - COALESCE(SUM(`bfcol_27`), 0) AS `bfcol_32`, - COALESCE(SUM(`bfcol_28`), 0) AS `bfcol_33` - FROM `bfcte_2` - GROUP BY - `bfcol_26` -) -SELECT - `bfcol_26` AS `L_SHIPMODE`, - `bfcol_32` AS `HIGH_LINE_COUNT`, - `bfcol_33` AS `LOW_LINE_COUNT` -FROM `bfcte_3` -ORDER BY - `bfcol_26` ASC NULLS LAST \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/tpch/snapshots/test_tpch/test_tpch_query/13/out.sql b/tests/unit/core/compile/sqlglot/tpch/snapshots/test_tpch/test_tpch_query/13/out.sql deleted file mode 100644 index 6aab2b4fec7..00000000000 --- a/tests/unit/core/compile/sqlglot/tpch/snapshots/test_tpch/test_tpch_query/13/out.sql +++ /dev/null @@ -1,39 +0,0 @@ -WITH `bfcte_0` AS ( - SELECT - `O_ORDERKEY` AS `bfcol_10`, - `O_CUSTKEY` AS `bfcol_11` - FROM `bigframes-dev-perf`.`tpch_0001t`.`ORDERS` AS `bft_1` - WHERE - NOT ( - REGEXP_CONTAINS(`O_COMMENT`, 'special.*requests') - ) -), `bfcte_1` AS ( - SELECT - `C_CUSTKEY` AS `bfcol_3` - FROM `bigframes-dev-perf`.`tpch_0001t`.`CUSTOMER` AS `bft_0` -), `bfcte_2` AS ( - SELECT - `bfcol_3`, - COUNT(`bfcol_10`) AS `bfcol_14` - FROM `bfcte_1` - LEFT JOIN `bfcte_0` - ON `bfcol_3` = `bfcol_11` - GROUP BY - `bfcol_3` -), `bfcte_3` AS ( - SELECT - `bfcol_14`, - COUNT(1) AS `bfcol_16` - FROM `bfcte_2` - WHERE - NOT `bfcol_14` IS NULL - GROUP BY - `bfcol_14` -) -SELECT - `bfcol_14` AS `C_COUNT`, - `bfcol_16` AS `CUSTDIST` -FROM `bfcte_3` -ORDER BY - `bfcol_16` DESC, - `bfcol_14` DESC \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/tpch/snapshots/test_tpch/test_tpch_query/14/out.sql b/tests/unit/core/compile/sqlglot/tpch/snapshots/test_tpch/test_tpch_query/14/out.sql deleted file mode 100644 index bde638a0f4c..00000000000 --- a/tests/unit/core/compile/sqlglot/tpch/snapshots/test_tpch/test_tpch_query/14/out.sql +++ /dev/null @@ -1,164 +0,0 @@ -WITH `bfcte_0` AS ( - SELECT - * - FROM UNNEST(ARRAY>[STRUCT('TEMP', 0, 0)]) -), `bfcte_1` AS ( - SELECT - * - FROM UNNEST(ARRAY>[STRUCT('TEMP', 0, 0)]) -), `bfcte_2` AS ( - SELECT - `L_PARTKEY` AS `bfcol_0`, - `L_EXTENDEDPRICE` AS `bfcol_1`, - `L_DISCOUNT` AS `bfcol_2`, - `L_SHIPDATE` AS `bfcol_3` - FROM `bigframes-dev-perf`.`tpch_0001t`.`LINEITEM` AS `bft_1` -), `bfcte_3` AS ( - SELECT - `P_PARTKEY` AS `bfcol_4` - FROM `bigframes-dev-perf`.`tpch_0001t`.`PART` AS `bft_0` -), `bfcte_4` AS ( - SELECT - `P_PARTKEY` AS `bfcol_8`, - `P_TYPE` AS `bfcol_9` - FROM `bigframes-dev-perf`.`tpch_0001t`.`PART` AS `bft_0` -), `bfcte_5` AS ( - SELECT - `bfcol_4`, - `bfcol_0`, - `bfcol_1`, - `bfcol_2`, - `bfcol_3`, - `bfcol_1` AS `bfcol_20`, - `bfcol_2` AS `bfcol_21`, - ( - `bfcol_3` >= CAST('1995-09-01' AS DATE) - ) - AND ( - `bfcol_3` < CAST('1995-10-01' AS DATE) - ) AS `bfcol_22`, - `bfcol_1` AS `bfcol_39`, - `bfcol_2` AS `bfcol_40`, - `bfcol_1` AS `bfcol_45`, - 1 - `bfcol_2` AS `bfcol_46`, - `bfcol_1` * ( - 1 - `bfcol_2` - ) AS `bfcol_51` - FROM `bfcte_3` - INNER JOIN `bfcte_2` - ON `bfcol_4` = `bfcol_0` - WHERE - ( - `bfcol_3` >= CAST('1995-09-01' AS DATE) - ) - AND ( - `bfcol_3` < CAST('1995-10-01' AS DATE) - ) -), `bfcte_6` AS ( - SELECT - `bfcol_8`, - `bfcol_9`, - `bfcol_0`, - `bfcol_1`, - `bfcol_2`, - `bfcol_3`, - `bfcol_9` AS `bfcol_23`, - `bfcol_1` AS `bfcol_24`, - `bfcol_2` AS `bfcol_25`, - ( - `bfcol_3` >= CAST('1995-09-01' AS DATE) - ) - AND ( - `bfcol_3` < CAST('1995-10-01' AS DATE) - ) AS `bfcol_26`, - ( - `bfcol_1` * ( - 1 - `bfcol_2` - ) - ) * CAST(REGEXP_CONTAINS(`bfcol_9`, 'PROMO') AS INT64) AS `bfcol_41` - FROM `bfcte_4` - INNER JOIN `bfcte_2` - ON `bfcol_8` = `bfcol_0` - WHERE - ( - `bfcol_3` >= CAST('1995-09-01' AS DATE) - ) - AND ( - `bfcol_3` < CAST('1995-10-01' AS DATE) - ) -), `bfcte_7` AS ( - SELECT - COALESCE(SUM(`bfcol_51`), 0) AS `bfcol_54` - FROM `bfcte_5` -), `bfcte_8` AS ( - SELECT - COALESCE(SUM(`bfcol_41`), 0) AS `bfcol_47` - FROM `bfcte_6` -), `bfcte_9` AS ( - SELECT - `bfcol_54`, - 0 AS `bfcol_59` - FROM `bfcte_7` -), `bfcte_10` AS ( - SELECT - `bfcol_47`, - 0 AS `bfcol_50` - FROM `bfcte_8` -), `bfcte_11` AS ( - SELECT - `bfcol_5`, - `bfcol_6`, - `bfcol_7`, - `bfcol_54`, - `bfcol_59`, - CASE WHEN `bfcol_7` = 0 THEN `bfcol_54` END AS `bfcol_64`, - IF(`bfcol_59` = 0, CASE WHEN `bfcol_7` = 0 THEN `bfcol_54` END, NULL) AS `bfcol_72` - FROM `bfcte_0` - CROSS JOIN `bfcte_9` -), `bfcte_12` AS ( - SELECT - `bfcol_10`, - `bfcol_11`, - `bfcol_12`, - `bfcol_47`, - `bfcol_50`, - CASE WHEN `bfcol_12` = 0 THEN `bfcol_47` END AS `bfcol_53`, - IF(`bfcol_50` = 0, CASE WHEN `bfcol_12` = 0 THEN `bfcol_47` END, NULL) AS `bfcol_60` - FROM `bfcte_1` - CROSS JOIN `bfcte_10` -), `bfcte_13` AS ( - SELECT - `bfcol_5`, - `bfcol_6`, - ANY_VALUE(`bfcol_72`) AS `bfcol_79` - FROM `bfcte_11` - GROUP BY - `bfcol_5`, - `bfcol_6` -), `bfcte_14` AS ( - SELECT - `bfcol_10`, - `bfcol_11`, - ANY_VALUE(`bfcol_60`) AS `bfcol_65` - FROM `bfcte_12` - GROUP BY - `bfcol_10`, - `bfcol_11` -), `bfcte_15` AS ( - SELECT - `bfcol_5` AS `bfcol_80`, - `bfcol_79` AS `bfcol_81` - FROM `bfcte_13` -), `bfcte_16` AS ( - SELECT - `bfcol_10` AS `bfcol_77`, - 100.0 * `bfcol_65` AS `bfcol_78` - FROM `bfcte_14` -) -SELECT - ROUND(IEEE_DIVIDE(`bfcol_78`, `bfcol_81`), 2) AS `PROMO_REVENUE` -FROM `bfcte_16` -FULL OUTER JOIN `bfcte_15` - ON `bfcol_77` = `bfcol_80` -ORDER BY - COALESCE(`bfcol_77`, `bfcol_80`) ASC NULLS LAST \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/tpch/snapshots/test_tpch/test_tpch_query/15/out.sql b/tests/unit/core/compile/sqlglot/tpch/snapshots/test_tpch/test_tpch_query/15/out.sql deleted file mode 100644 index e3cc2bd9743..00000000000 --- a/tests/unit/core/compile/sqlglot/tpch/snapshots/test_tpch/test_tpch_query/15/out.sql +++ /dev/null @@ -1,112 +0,0 @@ -WITH `bfcte_0` AS ( - SELECT - * - FROM UNNEST(ARRAY>[STRUCT('TOTAL_REVENUE', 0, 0)]) -), `bfcte_1` AS ( - SELECT - `L_SUPPKEY`, - `L_EXTENDEDPRICE`, - `L_DISCOUNT`, - `L_SHIPDATE`, - `L_SUPPKEY` AS `bfcol_12`, - `L_EXTENDEDPRICE` AS `bfcol_13`, - `L_DISCOUNT` AS `bfcol_14`, - ( - `L_SHIPDATE` >= CAST('1996-01-01' AS DATE) - ) - AND ( - `L_SHIPDATE` < CAST('1996-04-01' AS DATE) - ) AS `bfcol_15`, - `L_SUPPKEY` AS `bfcol_23`, - `L_EXTENDEDPRICE` * ( - 1 - `L_DISCOUNT` - ) AS `bfcol_24` - FROM `bigframes-dev-perf`.`tpch_0001t`.`LINEITEM` AS `bft_1` - WHERE - ( - `L_SHIPDATE` >= CAST('1996-01-01' AS DATE) - ) - AND ( - `L_SHIPDATE` < CAST('1996-04-01' AS DATE) - ) -), `bfcte_2` AS ( - SELECT - `S_SUPPKEY` AS `bfcol_4` - FROM `bigframes-dev-perf`.`tpch_0001t`.`SUPPLIER` AS `bft_0` -), `bfcte_3` AS ( - SELECT - `S_SUPPKEY` AS `bfcol_8`, - `S_NAME` AS `bfcol_9`, - `S_ADDRESS` AS `bfcol_10`, - `S_PHONE` AS `bfcol_11` - FROM `bigframes-dev-perf`.`tpch_0001t`.`SUPPLIER` AS `bft_0` -), `bfcte_4` AS ( - SELECT - `bfcol_23`, - COALESCE(SUM(`bfcol_24`), 0) AS `bfcol_27` - FROM `bfcte_1` - GROUP BY - `bfcol_23` -), `bfcte_5` AS ( - SELECT - `bfcol_23` AS `bfcol_30`, - ROUND(`bfcol_27`, 2) AS `bfcol_31` - FROM `bfcte_4` -), `bfcte_6` AS ( - SELECT - MAX(`bfcol_31`) AS `bfcol_38` - FROM `bfcte_2` - INNER JOIN `bfcte_5` - ON `bfcol_4` = `bfcol_30` -), `bfcte_7` AS ( - SELECT - `bfcol_8` AS `bfcol_33`, - `bfcol_9` AS `bfcol_34`, - `bfcol_10` AS `bfcol_35`, - `bfcol_11` AS `bfcol_36`, - `bfcol_31` AS `bfcol_37` - FROM `bfcte_3` - INNER JOIN `bfcte_5` - ON `bfcol_8` = `bfcol_30` -), `bfcte_8` AS ( - SELECT - `bfcol_38`, - 0 AS `bfcol_39` - FROM `bfcte_6` -), `bfcte_9` AS ( - SELECT - `bfcol_5`, - `bfcol_6`, - `bfcol_7`, - `bfcol_38`, - `bfcol_39`, - CASE WHEN `bfcol_7` = 0 THEN `bfcol_38` END AS `bfcol_40`, - IF(`bfcol_39` = 0, CASE WHEN `bfcol_7` = 0 THEN `bfcol_38` END, NULL) AS `bfcol_45` - FROM `bfcte_0` - CROSS JOIN `bfcte_8` -), `bfcte_10` AS ( - SELECT - `bfcol_5`, - `bfcol_6`, - ANY_VALUE(`bfcol_45`) AS `bfcol_49` - FROM `bfcte_9` - GROUP BY - `bfcol_5`, - `bfcol_6` -), `bfcte_11` AS ( - SELECT - `bfcol_49` AS `bfcol_50` - FROM `bfcte_10` -) -SELECT - `bfcol_33` AS `S_SUPPKEY`, - `bfcol_34` AS `S_NAME`, - `bfcol_35` AS `S_ADDRESS`, - `bfcol_36` AS `S_PHONE`, - `bfcol_37` AS `TOTAL_REVENUE` -FROM `bfcte_7` -CROSS JOIN `bfcte_11` -WHERE - `bfcol_37` = `bfcol_50` -ORDER BY - `bfcol_33` ASC NULLS LAST \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/tpch/snapshots/test_tpch/test_tpch_query/16/out.sql b/tests/unit/core/compile/sqlglot/tpch/snapshots/test_tpch/test_tpch_query/16/out.sql deleted file mode 100644 index 228d51a76c7..00000000000 --- a/tests/unit/core/compile/sqlglot/tpch/snapshots/test_tpch/test_tpch_query/16/out.sql +++ /dev/null @@ -1,88 +0,0 @@ -WITH `bfcte_0` AS ( - SELECT - `S_SUPPKEY`, - `S_COMMENT`, - `S_SUPPKEY` AS `bfcol_8`, - NOT ( - REGEXP_CONTAINS(`S_COMMENT`, 'Customer.*Complaints') - ) AS `bfcol_9` - FROM `bigframes-dev-perf`.`tpch_0001t`.`SUPPLIER` AS `bft_2` - WHERE - NOT ( - REGEXP_CONTAINS(`S_COMMENT`, 'Customer.*Complaints') - ) -), `bfcte_1` AS ( - SELECT - `PS_PARTKEY` AS `bfcol_2`, - `PS_SUPPKEY` AS `bfcol_3` - FROM `bigframes-dev-perf`.`tpch_0001t`.`PARTSUPP` AS `bft_1` -), `bfcte_2` AS ( - SELECT - `P_PARTKEY` AS `bfcol_4`, - `P_BRAND` AS `bfcol_5`, - `P_TYPE` AS `bfcol_6`, - `P_SIZE` AS `bfcol_7` - FROM `bigframes-dev-perf`.`tpch_0001t`.`PART` AS `bft_0` -), `bfcte_3` AS ( - SELECT - `bfcol_8` - FROM `bfcte_0` - GROUP BY - `bfcol_8` -), `bfcte_4` AS ( - SELECT - `bfcol_5` AS `bfcol_55`, - `bfcol_6` AS `bfcol_56`, - `bfcol_7` AS `bfcol_57`, - `bfcol_3` AS `bfcol_58` - FROM `bfcte_2` - INNER JOIN `bfcte_1` - ON `bfcol_4` = `bfcol_2` - WHERE - `bfcol_5` <> 'Brand#45' - AND NOT ( - REGEXP_CONTAINS(`bfcol_6`, 'MEDIUM POLISHED') - ) - AND COALESCE(COALESCE(`bfcol_7` IN (49, 14, 23, 45, 19, 3, 36, 9), FALSE), FALSE) -), `bfcte_5` AS ( - SELECT - `bfcol_8` AS `bfcol_21` - FROM `bfcte_3` -), `bfcte_6` AS ( - SELECT - *, - COALESCE(`bfcol_58` IN (( - SELECT - * - FROM `bfcte_5` - )), FALSE) AS `bfcol_59` - FROM `bfcte_4` -), `bfcte_7` AS ( - SELECT - * - FROM `bfcte_6` - WHERE - `bfcol_59` -), `bfcte_8` AS ( - SELECT - `bfcol_55`, - `bfcol_56`, - `bfcol_57`, - COUNT(DISTINCT `bfcol_58`) AS `bfcol_69` - FROM `bfcte_7` - GROUP BY - `bfcol_55`, - `bfcol_56`, - `bfcol_57` -) -SELECT - `bfcol_55` AS `P_BRAND`, - `bfcol_56` AS `P_TYPE`, - `bfcol_57` AS `P_SIZE`, - `bfcol_69` AS `SUPPLIER_CNT` -FROM `bfcte_8` -ORDER BY - `bfcol_69` DESC, - `bfcol_55` ASC NULLS LAST, - `bfcol_56` ASC NULLS LAST, - `bfcol_57` ASC NULLS LAST \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/tpch/snapshots/test_tpch/test_tpch_query/17/out.sql b/tests/unit/core/compile/sqlglot/tpch/snapshots/test_tpch/test_tpch_query/17/out.sql deleted file mode 100644 index 40aacf917f1..00000000000 --- a/tests/unit/core/compile/sqlglot/tpch/snapshots/test_tpch/test_tpch_query/17/out.sql +++ /dev/null @@ -1,97 +0,0 @@ -WITH `bfcte_0` AS ( - SELECT - * - FROM UNNEST(ARRAY>[STRUCT('L_EXTENDEDPRICE', 0, 0)]) -), `bfcte_1` AS ( - SELECT - `P_PARTKEY` AS `bfcol_15` - FROM `bigframes-dev-perf`.`tpch_0001t`.`PART` AS `bft_1` - WHERE - ( - `P_BRAND` = 'Brand#23' - ) AND ( - `P_CONTAINER` = 'MED BOX' - ) -), `bfcte_2` AS ( - SELECT - `L_PARTKEY` AS `bfcol_3`, - `L_QUANTITY` AS `bfcol_4`, - `L_EXTENDEDPRICE` AS `bfcol_5` - FROM `bigframes-dev-perf`.`tpch_0001t`.`LINEITEM` AS `bft_0` -), `bfcte_3` AS ( - SELECT - `L_PARTKEY` AS `bfcol_6`, - `L_QUANTITY` AS `bfcol_7` - FROM `bigframes-dev-perf`.`tpch_0001t`.`LINEITEM` AS `bft_0` -), `bfcte_4` AS ( - SELECT - `bfcol_4` AS `bfcol_16`, - `bfcol_5` AS `bfcol_17`, - `bfcol_15` AS `bfcol_18` - FROM `bfcte_2` - RIGHT JOIN `bfcte_1` - ON `bfcol_3` = `bfcol_15` -), `bfcte_5` AS ( - SELECT - `bfcol_15`, - AVG(`bfcol_7`) AS `bfcol_21` - FROM `bfcte_3` - RIGHT JOIN `bfcte_1` - ON `bfcol_6` = `bfcol_15` - GROUP BY - `bfcol_15` -), `bfcte_6` AS ( - SELECT - `bfcol_15` AS `bfcol_24`, - `bfcol_21` * 0.2 AS `bfcol_25` - FROM `bfcte_5` -), `bfcte_7` AS ( - SELECT - `bfcol_24`, - `bfcol_25`, - `bfcol_16`, - `bfcol_17`, - `bfcol_18`, - `bfcol_17` AS `bfcol_29`, - `bfcol_16` < `bfcol_25` AS `bfcol_30` - FROM `bfcte_6` - INNER JOIN `bfcte_4` - ON `bfcol_24` = `bfcol_18` - WHERE - `bfcol_16` < `bfcol_25` -), `bfcte_8` AS ( - SELECT - COALESCE(SUM(`bfcol_29`), 0) AS `bfcol_34` - FROM `bfcte_7` -), `bfcte_9` AS ( - SELECT - `bfcol_34`, - 0 AS `bfcol_35` - FROM `bfcte_8` -), `bfcte_10` AS ( - SELECT - `bfcol_8`, - `bfcol_9`, - `bfcol_10`, - `bfcol_34`, - `bfcol_35`, - CASE WHEN `bfcol_10` = 0 THEN `bfcol_34` END AS `bfcol_36`, - IF(`bfcol_35` = 0, CASE WHEN `bfcol_10` = 0 THEN `bfcol_34` END, NULL) AS `bfcol_41` - FROM `bfcte_0` - CROSS JOIN `bfcte_9` -), `bfcte_11` AS ( - SELECT - `bfcol_8`, - `bfcol_9`, - ANY_VALUE(`bfcol_41`) AS `bfcol_45` - FROM `bfcte_10` - GROUP BY - `bfcol_8`, - `bfcol_9` -) -SELECT - ROUND(IEEE_DIVIDE(`bfcol_45`, 7.0), 2) AS `AVG_YEARLY` -FROM `bfcte_11` -ORDER BY - `bfcol_9` ASC NULLS LAST, - `bfcol_8` ASC NULLS LAST \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/tpch/snapshots/test_tpch/test_tpch_query/18/out.sql b/tests/unit/core/compile/sqlglot/tpch/snapshots/test_tpch/test_tpch_query/18/out.sql deleted file mode 100644 index 6fcdb343940..00000000000 --- a/tests/unit/core/compile/sqlglot/tpch/snapshots/test_tpch/test_tpch_query/18/out.sql +++ /dev/null @@ -1,104 +0,0 @@ -WITH `bfcte_0` AS ( - SELECT - `C_CUSTKEY` AS `bfcol_0`, - `C_NAME` AS `bfcol_1` - FROM `bigframes-dev-perf`.`tpch_0001t`.`CUSTOMER` AS `bft_2` -), `bfcte_1` AS ( - SELECT - `L_ORDERKEY` AS `bfcol_2`, - `L_QUANTITY` AS `bfcol_3` - FROM `bigframes-dev-perf`.`tpch_0001t`.`LINEITEM` AS `bft_1` -), `bfcte_2` AS ( - SELECT - `O_ORDERKEY` AS `bfcol_4`, - `O_CUSTKEY` AS `bfcol_5`, - `O_TOTALPRICE` AS `bfcol_6`, - `O_ORDERDATE` AS `bfcol_7` - FROM `bigframes-dev-perf`.`tpch_0001t`.`ORDERS` AS `bft_0` -), `bfcte_3` AS ( - SELECT - `bfcol_2`, - COALESCE(SUM(`bfcol_3`), 0) AS `bfcol_8` - FROM `bfcte_1` - GROUP BY - `bfcol_2` -), `bfcte_4` AS ( - SELECT - `bfcol_2`, - `bfcol_8`, - `bfcol_2` AS `bfcol_9`, - `bfcol_8` > 300 AS `bfcol_10` - FROM `bfcte_3` - WHERE - `bfcol_8` > 300 -), `bfcte_5` AS ( - SELECT - `bfcol_9` - FROM `bfcte_4` - GROUP BY - `bfcol_9` -), `bfcte_6` AS ( - SELECT - `bfcol_9` AS `bfcol_13` - FROM `bfcte_5` -), `bfcte_7` AS ( - SELECT - *, - COALESCE(`bfcol_4` IN (( - SELECT - * - FROM `bfcte_6` - )), FALSE) AS `bfcol_14` - FROM `bfcte_2` -), `bfcte_8` AS ( - SELECT - `bfcol_4` AS `bfcol_20`, - `bfcol_5` AS `bfcol_21`, - `bfcol_6` AS `bfcol_22`, - `bfcol_7` AS `bfcol_23` - FROM `bfcte_7` - WHERE - `bfcol_14` -), `bfcte_9` AS ( - SELECT - `bfcol_20` AS `bfcol_24`, - `bfcol_21` AS `bfcol_25`, - `bfcol_22` AS `bfcol_26`, - `bfcol_23` AS `bfcol_27`, - `bfcol_3` AS `bfcol_28` - FROM `bfcte_8` - INNER JOIN `bfcte_1` - ON `bfcol_20` = `bfcol_2` -), `bfcte_10` AS ( - SELECT - `bfcol_1`, - `bfcol_0`, - `bfcol_24`, - `bfcol_27`, - `bfcol_26`, - COALESCE(SUM(`bfcol_28`), 0) AS `bfcol_35` - FROM `bfcte_9` - INNER JOIN `bfcte_0` - ON `bfcol_25` = `bfcol_0` - GROUP BY - `bfcol_1`, - `bfcol_0`, - `bfcol_24`, - `bfcol_27`, - `bfcol_26` -) -SELECT - `bfcol_1` AS `C_NAME`, - `bfcol_0` AS `C_CUSTKEY`, - `bfcol_24` AS `O_ORDERKEY`, - `bfcol_27` AS `O_ORDERDAT`, - `bfcol_26` AS `O_TOTALPRICE`, - `bfcol_35` AS `COL6` -FROM `bfcte_10` -ORDER BY - `bfcol_26` DESC, - `bfcol_27` ASC NULLS LAST, - `bfcol_1` ASC NULLS LAST, - `bfcol_0` ASC NULLS LAST, - `bfcol_24` ASC NULLS LAST -LIMIT 100 \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/tpch/snapshots/test_tpch/test_tpch_query/19/out.sql b/tests/unit/core/compile/sqlglot/tpch/snapshots/test_tpch/test_tpch_query/19/out.sql deleted file mode 100644 index e7b817ecfd9..00000000000 --- a/tests/unit/core/compile/sqlglot/tpch/snapshots/test_tpch/test_tpch_query/19/out.sql +++ /dev/null @@ -1,226 +0,0 @@ -WITH `bfcte_0` AS ( - SELECT - * - FROM UNNEST(ARRAY>[STRUCT(0)]) -), `bfcte_1` AS ( - SELECT - `L_PARTKEY` AS `bfcol_1`, - `L_QUANTITY` AS `bfcol_2`, - `L_EXTENDEDPRICE` AS `bfcol_3`, - `L_DISCOUNT` AS `bfcol_4`, - `L_SHIPINSTRUCT` AS `bfcol_5`, - `L_SHIPMODE` AS `bfcol_6` - FROM `bigframes-dev-perf`.`tpch_0001t`.`LINEITEM` AS `bft_1` -), `bfcte_2` AS ( - SELECT - `P_PARTKEY` AS `bfcol_7`, - `P_BRAND` AS `bfcol_8`, - `P_SIZE` AS `bfcol_9`, - `P_CONTAINER` AS `bfcol_10` - FROM `bigframes-dev-perf`.`tpch_0001t`.`PART` AS `bft_0` -), `bfcte_3` AS ( - SELECT - `bfcol_7`, - `bfcol_8`, - `bfcol_9`, - `bfcol_10`, - `bfcol_1`, - `bfcol_2`, - `bfcol_3`, - `bfcol_4`, - `bfcol_5`, - `bfcol_6`, - `bfcol_3` AS `bfcol_19`, - `bfcol_4` AS `bfcol_20`, - ( - COALESCE(COALESCE(`bfcol_6` IN ('AIR', 'AIR REG'), FALSE), FALSE) - AND ( - `bfcol_5` = 'DELIVER IN PERSON' - ) - ) - AND ( - ( - ( - ( - ( - ( - `bfcol_8` = 'Brand#12' - ) - AND COALESCE(COALESCE(`bfcol_10` IN ('SM CASE', 'SM BOX', 'SM PACK', 'SM PKG'), FALSE), FALSE) - ) - AND ( - ( - `bfcol_2` >= 1 - ) AND ( - `bfcol_2` <= 11 - ) - ) - ) - AND ( - ( - `bfcol_9` >= 1 - ) AND ( - `bfcol_9` <= 5 - ) - ) - ) - OR ( - ( - ( - ( - `bfcol_8` = 'Brand#23' - ) - AND COALESCE( - COALESCE(`bfcol_10` IN ('MED BAG', 'MED BOX', 'MED PKG', 'MED PACK'), FALSE), - FALSE - ) - ) - AND ( - ( - `bfcol_2` >= 10 - ) AND ( - `bfcol_2` <= 20 - ) - ) - ) - AND ( - ( - `bfcol_9` >= 1 - ) AND ( - `bfcol_9` <= 10 - ) - ) - ) - ) - OR ( - ( - ( - ( - `bfcol_8` = 'Brand#34' - ) - AND COALESCE(COALESCE(`bfcol_10` IN ('LG CASE', 'LG BOX', 'LG PACK', 'LG PKG'), FALSE), FALSE) - ) - AND ( - ( - `bfcol_2` >= 20 - ) AND ( - `bfcol_2` <= 30 - ) - ) - ) - AND ( - ( - `bfcol_9` >= 1 - ) AND ( - `bfcol_9` <= 15 - ) - ) - ) - ) AS `bfcol_21`, - `bfcol_3` AS `bfcol_27`, - 1 - `bfcol_4` AS `bfcol_28`, - `bfcol_3` * ( - 1 - `bfcol_4` - ) AS `bfcol_31` - FROM `bfcte_2` - INNER JOIN `bfcte_1` - ON `bfcol_7` = `bfcol_1` - WHERE - ( - COALESCE(COALESCE(`bfcol_6` IN ('AIR', 'AIR REG'), FALSE), FALSE) - AND ( - `bfcol_5` = 'DELIVER IN PERSON' - ) - ) - AND ( - ( - ( - ( - ( - ( - `bfcol_8` = 'Brand#12' - ) - AND COALESCE(COALESCE(`bfcol_10` IN ('SM CASE', 'SM BOX', 'SM PACK', 'SM PKG'), FALSE), FALSE) - ) - AND ( - ( - `bfcol_2` >= 1 - ) AND ( - `bfcol_2` <= 11 - ) - ) - ) - AND ( - ( - `bfcol_9` >= 1 - ) AND ( - `bfcol_9` <= 5 - ) - ) - ) - OR ( - ( - ( - ( - `bfcol_8` = 'Brand#23' - ) - AND COALESCE( - COALESCE(`bfcol_10` IN ('MED BAG', 'MED BOX', 'MED PKG', 'MED PACK'), FALSE), - FALSE - ) - ) - AND ( - ( - `bfcol_2` >= 10 - ) AND ( - `bfcol_2` <= 20 - ) - ) - ) - AND ( - ( - `bfcol_9` >= 1 - ) AND ( - `bfcol_9` <= 10 - ) - ) - ) - ) - OR ( - ( - ( - ( - `bfcol_8` = 'Brand#34' - ) - AND COALESCE(COALESCE(`bfcol_10` IN ('LG CASE', 'LG BOX', 'LG PACK', 'LG PKG'), FALSE), FALSE) - ) - AND ( - ( - `bfcol_2` >= 20 - ) AND ( - `bfcol_2` <= 30 - ) - ) - ) - AND ( - ( - `bfcol_9` >= 1 - ) AND ( - `bfcol_9` <= 15 - ) - ) - ) - ) -), `bfcte_4` AS ( - SELECT - COALESCE(SUM(`bfcol_31`), 0) AS `bfcol_33` - FROM `bfcte_3` -), `bfcte_5` AS ( - SELECT - * - FROM `bfcte_4` -) -SELECT - CASE WHEN `bfcol_0` = 0 THEN `bfcol_33` END AS `REVENUE` -FROM `bfcte_5` -CROSS JOIN `bfcte_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/tpch/snapshots/test_tpch/test_tpch_query/2/out.sql b/tests/unit/core/compile/sqlglot/tpch/snapshots/test_tpch/test_tpch_query/2/out.sql deleted file mode 100644 index ae7be6a71da..00000000000 --- a/tests/unit/core/compile/sqlglot/tpch/snapshots/test_tpch/test_tpch_query/2/out.sql +++ /dev/null @@ -1,197 +0,0 @@ -WITH `bfcte_0` AS ( - SELECT - `R_REGIONKEY` AS `bfcol_0`, - `R_NAME` AS `bfcol_1` - FROM `bigframes-dev-perf`.`tpch_0001t`.`REGION` AS `bft_4` -), `bfcte_1` AS ( - SELECT - `N_NATIONKEY` AS `bfcol_2`, - `N_NAME` AS `bfcol_3`, - `N_REGIONKEY` AS `bfcol_4` - FROM `bigframes-dev-perf`.`tpch_0001t`.`NATION` AS `bft_3` -), `bfcte_2` AS ( - SELECT - `N_NATIONKEY` AS `bfcol_19`, - `N_REGIONKEY` AS `bfcol_20` - FROM `bigframes-dev-perf`.`tpch_0001t`.`NATION` AS `bft_3` -), `bfcte_3` AS ( - SELECT - `S_SUPPKEY` AS `bfcol_5`, - `S_NAME` AS `bfcol_6`, - `S_ADDRESS` AS `bfcol_7`, - `S_NATIONKEY` AS `bfcol_8`, - `S_PHONE` AS `bfcol_9`, - `S_ACCTBAL` AS `bfcol_10`, - `S_COMMENT` AS `bfcol_11` - FROM `bigframes-dev-perf`.`tpch_0001t`.`SUPPLIER` AS `bft_2` -), `bfcte_4` AS ( - SELECT - `S_SUPPKEY` AS `bfcol_21`, - `S_NATIONKEY` AS `bfcol_22` - FROM `bigframes-dev-perf`.`tpch_0001t`.`SUPPLIER` AS `bft_2` -), `bfcte_5` AS ( - SELECT - `PS_PARTKEY` AS `bfcol_12`, - `PS_SUPPKEY` AS `bfcol_13`, - `PS_SUPPLYCOST` AS `bfcol_14` - FROM `bigframes-dev-perf`.`tpch_0001t`.`PARTSUPP` AS `bft_1` -), `bfcte_6` AS ( - SELECT - `P_PARTKEY` AS `bfcol_15`, - `P_MFGR` AS `bfcol_16`, - `P_TYPE` AS `bfcol_17`, - `P_SIZE` AS `bfcol_18` - FROM `bigframes-dev-perf`.`tpch_0001t`.`PART` AS `bft_0` -), `bfcte_7` AS ( - SELECT - `P_PARTKEY` AS `bfcol_23`, - `P_TYPE` AS `bfcol_24`, - `P_SIZE` AS `bfcol_25` - FROM `bigframes-dev-perf`.`tpch_0001t`.`PART` AS `bft_0` -), `bfcte_8` AS ( - SELECT - `bfcol_15` AS `bfcol_26`, - `bfcol_16` AS `bfcol_27`, - `bfcol_17` AS `bfcol_28`, - `bfcol_18` AS `bfcol_29`, - `bfcol_13` AS `bfcol_30`, - `bfcol_14` AS `bfcol_31` - FROM `bfcte_6` - INNER JOIN `bfcte_5` - ON `bfcol_15` = `bfcol_12` -), `bfcte_9` AS ( - SELECT - `bfcol_23` AS `bfcol_32`, - `bfcol_24` AS `bfcol_33`, - `bfcol_25` AS `bfcol_34`, - `bfcol_13` AS `bfcol_35`, - `bfcol_14` AS `bfcol_36` - FROM `bfcte_7` - INNER JOIN `bfcte_5` - ON `bfcol_23` = `bfcol_12` -), `bfcte_10` AS ( - SELECT - `bfcol_26` AS `bfcol_37`, - `bfcol_27` AS `bfcol_38`, - `bfcol_28` AS `bfcol_39`, - `bfcol_29` AS `bfcol_40`, - `bfcol_31` AS `bfcol_41`, - `bfcol_6` AS `bfcol_42`, - `bfcol_7` AS `bfcol_43`, - `bfcol_8` AS `bfcol_44`, - `bfcol_9` AS `bfcol_45`, - `bfcol_10` AS `bfcol_46`, - `bfcol_11` AS `bfcol_47` - FROM `bfcte_8` - INNER JOIN `bfcte_3` - ON `bfcol_30` = `bfcol_5` -), `bfcte_11` AS ( - SELECT - `bfcol_32` AS `bfcol_48`, - `bfcol_33` AS `bfcol_49`, - `bfcol_34` AS `bfcol_50`, - `bfcol_36` AS `bfcol_51`, - `bfcol_22` AS `bfcol_52` - FROM `bfcte_9` - INNER JOIN `bfcte_4` - ON `bfcol_35` = `bfcol_21` -), `bfcte_12` AS ( - SELECT - `bfcol_37` AS `bfcol_53`, - `bfcol_38` AS `bfcol_54`, - `bfcol_39` AS `bfcol_55`, - `bfcol_40` AS `bfcol_56`, - `bfcol_41` AS `bfcol_57`, - `bfcol_42` AS `bfcol_58`, - `bfcol_43` AS `bfcol_59`, - `bfcol_45` AS `bfcol_60`, - `bfcol_46` AS `bfcol_61`, - `bfcol_47` AS `bfcol_62`, - `bfcol_3` AS `bfcol_63`, - `bfcol_4` AS `bfcol_64` - FROM `bfcte_10` - INNER JOIN `bfcte_1` - ON `bfcol_44` = `bfcol_2` -), `bfcte_13` AS ( - SELECT - `bfcol_48` AS `bfcol_65`, - `bfcol_49` AS `bfcol_66`, - `bfcol_50` AS `bfcol_67`, - `bfcol_51` AS `bfcol_68`, - `bfcol_20` AS `bfcol_69` - FROM `bfcte_11` - INNER JOIN `bfcte_2` - ON `bfcol_52` = `bfcol_19` -), `bfcte_14` AS ( - SELECT - `bfcol_53` AS `bfcol_205`, - `bfcol_54` AS `bfcol_206`, - `bfcol_57` AS `bfcol_207`, - `bfcol_58` AS `bfcol_208`, - `bfcol_59` AS `bfcol_209`, - `bfcol_60` AS `bfcol_210`, - `bfcol_61` AS `bfcol_211`, - `bfcol_62` AS `bfcol_212`, - `bfcol_63` AS `bfcol_213` - FROM `bfcte_12` - INNER JOIN `bfcte_0` - ON `bfcol_64` = `bfcol_0` - WHERE - `bfcol_56` = 15 AND ENDS_WITH(`bfcol_55`, 'BRASS') AND `bfcol_1` = 'EUROPE' -), `bfcte_15` AS ( - SELECT - `bfcol_65`, - `bfcol_66`, - `bfcol_67`, - `bfcol_68`, - `bfcol_69`, - `bfcol_0`, - `bfcol_1`, - `bfcol_65` AS `bfcol_99`, - `bfcol_66` AS `bfcol_100`, - `bfcol_68` AS `bfcol_101`, - `bfcol_1` AS `bfcol_102`, - `bfcol_67` = 15 AS `bfcol_103`, - `bfcol_65` AS `bfcol_147`, - `bfcol_68` AS `bfcol_148`, - `bfcol_1` AS `bfcol_149`, - ENDS_WITH(`bfcol_66`, 'BRASS') AS `bfcol_150`, - `bfcol_65` AS `bfcol_189`, - `bfcol_68` AS `bfcol_190`, - `bfcol_1` = 'EUROPE' AS `bfcol_191` - FROM `bfcte_13` - INNER JOIN `bfcte_0` - ON `bfcol_69` = `bfcol_0` - WHERE - `bfcol_67` = 15 AND ENDS_WITH(`bfcol_66`, 'BRASS') AND `bfcol_1` = 'EUROPE' -), `bfcte_16` AS ( - SELECT - `bfcol_189`, - MIN(`bfcol_190`) AS `bfcol_216` - FROM `bfcte_15` - GROUP BY - `bfcol_189` -), `bfcte_17` AS ( - SELECT - `bfcol_189` AS `bfcol_214`, - `bfcol_216` - FROM `bfcte_16` -) -SELECT - `bfcol_211` AS `S_ACCTBAL`, - `bfcol_208` AS `S_NAME`, - `bfcol_213` AS `N_NAME`, - `bfcol_214` AS `P_PARTKEY`, - `bfcol_206` AS `P_MFGR`, - `bfcol_209` AS `S_ADDRESS`, - `bfcol_210` AS `S_PHONE`, - `bfcol_212` AS `S_COMMENT` -FROM `bfcte_17` -INNER JOIN `bfcte_14` - ON `bfcol_214` = `bfcol_205` AND `bfcol_216` = `bfcol_207` -ORDER BY - `bfcol_211` DESC, - `bfcol_213` ASC NULLS LAST, - `bfcol_208` ASC NULLS LAST, - `bfcol_214` ASC NULLS LAST -LIMIT 100 \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/tpch/snapshots/test_tpch/test_tpch_query/20/out.sql b/tests/unit/core/compile/sqlglot/tpch/snapshots/test_tpch/test_tpch_query/20/out.sql deleted file mode 100644 index 197588f5c84..00000000000 --- a/tests/unit/core/compile/sqlglot/tpch/snapshots/test_tpch/test_tpch_query/20/out.sql +++ /dev/null @@ -1,144 +0,0 @@ -WITH `bfcte_0` AS ( - SELECT - `P_PARTKEY`, - `P_NAME`, - `P_PARTKEY` AS `bfcol_15`, - STARTS_WITH(`P_NAME`, 'forest') AS `bfcol_16` - FROM `bigframes-dev-perf`.`tpch_0001t`.`PART` AS `bft_4` - WHERE - STARTS_WITH(`P_NAME`, 'forest') -), `bfcte_1` AS ( - SELECT - `PS_PARTKEY` AS `bfcol_2`, - `PS_SUPPKEY` AS `bfcol_3`, - `PS_AVAILQTY` AS `bfcol_4` - FROM `bigframes-dev-perf`.`tpch_0001t`.`PARTSUPP` AS `bft_3` -), `bfcte_2` AS ( - SELECT - `L_PARTKEY`, - `L_SUPPKEY`, - `L_QUANTITY`, - `L_SHIPDATE`, - `L_PARTKEY` AS `bfcol_17`, - `L_SUPPKEY` AS `bfcol_18`, - `L_QUANTITY` AS `bfcol_19`, - ( - `L_SHIPDATE` >= CAST('1994-01-01' AS DATE) - ) - AND ( - `L_SHIPDATE` < CAST('1995-01-01' AS DATE) - ) AS `bfcol_20` - FROM `bigframes-dev-perf`.`tpch_0001t`.`LINEITEM` AS `bft_2` - WHERE - ( - `L_SHIPDATE` >= CAST('1994-01-01' AS DATE) - ) - AND ( - `L_SHIPDATE` < CAST('1995-01-01' AS DATE) - ) -), `bfcte_3` AS ( - SELECT - `N_NATIONKEY` AS `bfcol_35` - FROM `bigframes-dev-perf`.`tpch_0001t`.`NATION` AS `bft_1` - WHERE - `N_NAME` = 'CANADA' -), `bfcte_4` AS ( - SELECT - `S_SUPPKEY` AS `bfcol_11`, - `S_NAME` AS `bfcol_12`, - `S_ADDRESS` AS `bfcol_13`, - `S_NATIONKEY` AS `bfcol_14` - FROM `bigframes-dev-perf`.`tpch_0001t`.`SUPPLIER` AS `bft_0` -), `bfcte_5` AS ( - SELECT - `bfcol_15` - FROM `bfcte_0` - GROUP BY - `bfcol_15` -), `bfcte_6` AS ( - SELECT - `bfcol_17`, - `bfcol_18`, - COALESCE(SUM(`bfcol_19`), 0) AS `bfcol_36` - FROM `bfcte_2` - GROUP BY - `bfcol_17`, - `bfcol_18` -), `bfcte_7` AS ( - SELECT - `bfcol_11` AS `bfcol_41`, - `bfcol_12` AS `bfcol_42`, - `bfcol_13` AS `bfcol_43` - FROM `bfcte_4` - INNER JOIN `bfcte_3` - ON `bfcol_14` = `bfcol_35` -), `bfcte_8` AS ( - SELECT - `bfcol_15` AS `bfcol_31` - FROM `bfcte_5` -), `bfcte_9` AS ( - SELECT - `bfcol_17` AS `bfcol_48`, - `bfcol_18` AS `bfcol_49`, - `bfcol_36` * 0.5 AS `bfcol_50` - FROM `bfcte_6` -), `bfcte_10` AS ( - SELECT - *, - COALESCE(`bfcol_2` IN (( - SELECT - * - FROM `bfcte_8` - )), FALSE) AS `bfcol_37` - FROM `bfcte_1` -), `bfcte_11` AS ( - SELECT - `bfcol_2` AS `bfcol_51`, - `bfcol_3` AS `bfcol_52`, - `bfcol_4` AS `bfcol_53` - FROM `bfcte_10` - WHERE - `bfcol_37` -), `bfcte_12` AS ( - SELECT - `bfcol_48`, - `bfcol_49`, - `bfcol_50`, - `bfcol_51`, - `bfcol_52`, - `bfcol_53`, - `bfcol_52` AS `bfcol_57`, - `bfcol_53` > `bfcol_50` AS `bfcol_58` - FROM `bfcte_9` - INNER JOIN `bfcte_11` - ON `bfcol_49` = `bfcol_52` AND `bfcol_48` = `bfcol_51` - WHERE - `bfcol_53` > `bfcol_50` -), `bfcte_13` AS ( - SELECT - `bfcol_57` - FROM `bfcte_12` - GROUP BY - `bfcol_57` -), `bfcte_14` AS ( - SELECT - `bfcol_57` AS `bfcol_61` - FROM `bfcte_13` -), `bfcte_15` AS ( - SELECT - *, - COALESCE(`bfcol_41` IN (( - SELECT - * - FROM `bfcte_14` - )), FALSE) AS `bfcol_62` - FROM `bfcte_7` -) -SELECT - `bfcol_42` AS `S_NAME`, - `bfcol_43` AS `S_ADDRESS` -FROM `bfcte_15` -WHERE - `bfcol_62` -ORDER BY - `bfcol_42` ASC NULLS LAST \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/tpch/snapshots/test_tpch/test_tpch_query/21/out.sql b/tests/unit/core/compile/sqlglot/tpch/snapshots/test_tpch/test_tpch_query/21/out.sql deleted file mode 100644 index 0caf29ca617..00000000000 --- a/tests/unit/core/compile/sqlglot/tpch/snapshots/test_tpch/test_tpch_query/21/out.sql +++ /dev/null @@ -1,142 +0,0 @@ -WITH `bfcte_0` AS ( - SELECT - `O_ORDERKEY` AS `bfcol_0`, - `O_ORDERSTATUS` AS `bfcol_1` - FROM `bigframes-dev-perf`.`tpch_0001t`.`ORDERS` AS `bft_3` -), `bfcte_1` AS ( - SELECT - `N_NATIONKEY` AS `bfcol_2`, - `N_NAME` AS `bfcol_3` - FROM `bigframes-dev-perf`.`tpch_0001t`.`NATION` AS `bft_2` -), `bfcte_2` AS ( - SELECT - `S_SUPPKEY` AS `bfcol_4`, - `S_NAME` AS `bfcol_5`, - `S_NATIONKEY` AS `bfcol_6` - FROM `bigframes-dev-perf`.`tpch_0001t`.`SUPPLIER` AS `bft_1` -), `bfcte_3` AS ( - SELECT - `L_ORDERKEY` AS `bfcol_30`, - `L_SUPPKEY` AS `bfcol_31` - FROM `bigframes-dev-perf`.`tpch_0001t`.`LINEITEM` AS `bft_0` - WHERE - `L_RECEIPTDATE` > `L_COMMITDATE` -), `bfcte_4` AS ( - SELECT - `L_ORDERKEY` - FROM `bigframes-dev-perf`.`tpch_0001t`.`LINEITEM` AS `bft_0` -), `bfcte_5` AS ( - SELECT - `L_ORDERKEY` AS `bfcol_32` - FROM `bigframes-dev-perf`.`tpch_0001t`.`LINEITEM` AS `bft_0` - WHERE - `L_RECEIPTDATE` > `L_COMMITDATE` -), `bfcte_6` AS ( - SELECT - `L_ORDERKEY`, - COUNT(1) AS `bfcol_18` - FROM `bfcte_4` - GROUP BY - `L_ORDERKEY` -), `bfcte_7` AS ( - SELECT - `L_ORDERKEY` AS `bfcol_33` - FROM `bfcte_6` - WHERE - `bfcol_18` > 1 -), `bfcte_8` AS ( - SELECT - `bfcol_33` AS `bfcol_34`, - `bfcol_31` AS `bfcol_35` - FROM `bfcte_7` - INNER JOIN `bfcte_3` - ON `bfcol_33` = `bfcol_30` -), `bfcte_9` AS ( - SELECT - `bfcol_33`, - COUNT(1) AS `bfcol_37` - FROM `bfcte_7` - INNER JOIN `bfcte_5` - ON `bfcol_33` = `bfcol_32` - GROUP BY - `bfcol_33` -), `bfcte_10` AS ( - SELECT - `bfcol_33` AS `bfcol_36`, - `bfcol_37` - FROM `bfcte_9` -), `bfcte_11` AS ( - SELECT - `bfcol_36` AS `bfcol_38`, - `bfcol_37` AS `bfcol_39`, - `bfcol_35` AS `bfcol_40` - FROM `bfcte_10` - INNER JOIN `bfcte_8` - ON `bfcol_36` = `bfcol_34` -), `bfcte_12` AS ( - SELECT - `bfcol_38` AS `bfcol_41`, - `bfcol_39` AS `bfcol_42`, - `bfcol_5` AS `bfcol_43`, - `bfcol_6` AS `bfcol_44` - FROM `bfcte_11` - INNER JOIN `bfcte_2` - ON `bfcol_40` = `bfcol_4` -), `bfcte_13` AS ( - SELECT - `bfcol_41` AS `bfcol_45`, - `bfcol_42` AS `bfcol_46`, - `bfcol_43` AS `bfcol_47`, - `bfcol_3` AS `bfcol_48` - FROM `bfcte_12` - INNER JOIN `bfcte_1` - ON `bfcol_44` = `bfcol_2` -), `bfcte_14` AS ( - SELECT - `bfcol_45`, - `bfcol_46`, - `bfcol_47`, - `bfcol_48`, - `bfcol_0`, - `bfcol_1`, - `bfcol_47` AS `bfcol_53`, - ( - ( - `bfcol_46` = 1 - ) AND ( - `bfcol_48` = 'SAUDI ARABIA' - ) - ) - AND ( - `bfcol_1` = 'F' - ) AS `bfcol_54` - FROM `bfcte_13` - INNER JOIN `bfcte_0` - ON `bfcol_45` = `bfcol_0` - WHERE - ( - ( - `bfcol_46` = 1 - ) AND ( - `bfcol_48` = 'SAUDI ARABIA' - ) - ) - AND ( - `bfcol_1` = 'F' - ) -), `bfcte_15` AS ( - SELECT - `bfcol_53`, - COUNT(1) AS `bfcol_58` - FROM `bfcte_14` - GROUP BY - `bfcol_53` -) -SELECT - `bfcol_53` AS `S_NAME`, - `bfcol_58` AS `NUMWAIT` -FROM `bfcte_15` -ORDER BY - `bfcol_58` DESC, - `bfcol_53` ASC NULLS LAST -LIMIT 100 \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/tpch/snapshots/test_tpch/test_tpch_query/22/out.sql b/tests/unit/core/compile/sqlglot/tpch/snapshots/test_tpch/test_tpch_query/22/out.sql deleted file mode 100644 index 5ab22d3cdaf..00000000000 --- a/tests/unit/core/compile/sqlglot/tpch/snapshots/test_tpch/test_tpch_query/22/out.sql +++ /dev/null @@ -1,132 +0,0 @@ -WITH `bfcte_0` AS ( - SELECT - * - FROM UNNEST(ARRAY>[STRUCT('C_ACCTBAL', 0, 0)]) -), `bfcte_1` AS ( - SELECT - `O_CUSTKEY` - FROM `bigframes-dev-perf`.`tpch_0001t`.`ORDERS` AS `bft_1` -), `bfcte_2` AS ( - SELECT - `C_PHONE`, - `C_ACCTBAL`, - `C_ACCTBAL` AS `bfcol_9`, - SUBSTRING(`C_PHONE`, 1, 2) AS `bfcol_10`, - `C_ACCTBAL` AS `bfcol_19`, - COALESCE( - COALESCE(SUBSTRING(`C_PHONE`, 1, 2) IN ('13', '31', '23', '29', '30', '18', '17'), FALSE), - FALSE - ) AS `bfcol_20`, - `C_ACCTBAL` AS `bfcol_35`, - `C_ACCTBAL` > 0.0 AS `bfcol_36` - FROM `bigframes-dev-perf`.`tpch_0001t`.`CUSTOMER` AS `bft_0` - WHERE - COALESCE( - COALESCE(SUBSTRING(`C_PHONE`, 1, 2) IN ('13', '31', '23', '29', '30', '18', '17'), FALSE), - FALSE - ) - AND `C_ACCTBAL` > 0.0 -), `bfcte_3` AS ( - SELECT - `C_CUSTKEY` AS `bfcol_32`, - `C_ACCTBAL` AS `bfcol_33`, - SUBSTRING(`C_PHONE`, 1, 2) AS `bfcol_34` - FROM `bigframes-dev-perf`.`tpch_0001t`.`CUSTOMER` AS `bft_0` - WHERE - COALESCE( - COALESCE(SUBSTRING(`C_PHONE`, 1, 2) IN ('13', '31', '23', '29', '30', '18', '17'), FALSE), - FALSE - ) -), `bfcte_4` AS ( - SELECT - `O_CUSTKEY` - FROM `bfcte_1` - GROUP BY - `O_CUSTKEY` -), `bfcte_5` AS ( - SELECT - AVG(`bfcol_35`) AS `bfcol_40` - FROM `bfcte_2` -), `bfcte_6` AS ( - SELECT - `O_CUSTKEY` AS `bfcol_0` - FROM `bfcte_4` -), `bfcte_7` AS ( - SELECT - `bfcol_40`, - 0 AS `bfcol_41` - FROM `bfcte_5` -), `bfcte_8` AS ( - SELECT - `bfcol_3`, - `bfcol_4`, - `bfcol_5`, - `bfcol_40`, - `bfcol_41`, - CASE WHEN `bfcol_5` = 0 THEN `bfcol_40` END AS `bfcol_42`, - IF(`bfcol_41` = 0, CASE WHEN `bfcol_5` = 0 THEN `bfcol_40` END, NULL) AS `bfcol_47` - FROM `bfcte_0` - CROSS JOIN `bfcte_7` -), `bfcte_9` AS ( - SELECT - `bfcol_3`, - `bfcol_4`, - ANY_VALUE(`bfcol_47`) AS `bfcol_51` - FROM `bfcte_8` - GROUP BY - `bfcol_3`, - `bfcol_4` -), `bfcte_10` AS ( - SELECT - `bfcol_51` AS `bfcol_52` - FROM `bfcte_9` -), `bfcte_11` AS ( - SELECT - `bfcol_32` AS `bfcol_61`, - `bfcol_33` AS `bfcol_62`, - `bfcol_34` AS `bfcol_63` - FROM `bfcte_3` - CROSS JOIN `bfcte_10` - WHERE - `bfcol_33` > `bfcol_52` -), `bfcte_12` AS ( - SELECT - *, - COALESCE(`bfcol_61` IN (( - SELECT - * - FROM `bfcte_6` - )), FALSE) AS `bfcol_64` - FROM `bfcte_11` -), `bfcte_13` AS ( - SELECT - `bfcol_61`, - `bfcol_62`, - `bfcol_63`, - `bfcol_64`, - NOT ( - `bfcol_64` - ) AS `bfcol_65` - FROM `bfcte_12` - WHERE - NOT ( - `bfcol_64` - ) -), `bfcte_14` AS ( - SELECT - `bfcol_63`, - COUNT(`bfcol_61`) AS `bfcol_73`, - COALESCE(SUM(`bfcol_62`), 0) AS `bfcol_74` - FROM `bfcte_13` - WHERE - NOT `bfcol_63` IS NULL - GROUP BY - `bfcol_63` -) -SELECT - `bfcol_63` AS `CNTRYCODE`, - `bfcol_73` AS `NUMCUST`, - `bfcol_74` AS `TOTACCTBAL` -FROM `bfcte_14` -ORDER BY - `bfcol_63` ASC NULLS LAST \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/tpch/snapshots/test_tpch/test_tpch_query/3/out.sql b/tests/unit/core/compile/sqlglot/tpch/snapshots/test_tpch/test_tpch_query/3/out.sql deleted file mode 100644 index 71779e1adf9..00000000000 --- a/tests/unit/core/compile/sqlglot/tpch/snapshots/test_tpch/test_tpch_query/3/out.sql +++ /dev/null @@ -1,76 +0,0 @@ -WITH `bfcte_0` AS ( - SELECT - `O_ORDERKEY` AS `bfcol_32`, - `O_CUSTKEY` AS `bfcol_33`, - `O_ORDERDATE` AS `bfcol_34`, - `O_SHIPPRIORITY` AS `bfcol_35` - FROM `bigframes-dev-perf`.`tpch_0001t`.`ORDERS` AS `bft_2` - WHERE - `O_ORDERDATE` < CAST('1995-03-15' AS DATE) -), `bfcte_1` AS ( - SELECT - `L_ORDERKEY` AS `bfcol_36`, - `L_EXTENDEDPRICE` AS `bfcol_37`, - `L_DISCOUNT` AS `bfcol_38` - FROM `bigframes-dev-perf`.`tpch_0001t`.`LINEITEM` AS `bft_1` - WHERE - `L_SHIPDATE` > CAST('1995-03-15' AS DATE) -), `bfcte_2` AS ( - SELECT - `C_CUSTKEY` AS `bfcol_39` - FROM `bigframes-dev-perf`.`tpch_0001t`.`CUSTOMER` AS `bft_0` - WHERE - `C_MKTSEGMENT` = 'BUILDING' -), `bfcte_3` AS ( - SELECT - `bfcol_37` AS `bfcol_40`, - `bfcol_38` AS `bfcol_41`, - `bfcol_32` AS `bfcol_42`, - `bfcol_33` AS `bfcol_43`, - `bfcol_34` AS `bfcol_44`, - `bfcol_35` AS `bfcol_45` - FROM `bfcte_1` - INNER JOIN `bfcte_0` - ON `bfcol_36` = `bfcol_32` -), `bfcte_4` AS ( - SELECT - `bfcol_39`, - `bfcol_40`, - `bfcol_41`, - `bfcol_42`, - `bfcol_43`, - `bfcol_44`, - `bfcol_45`, - `bfcol_42` AS `bfcol_51`, - `bfcol_44` AS `bfcol_52`, - `bfcol_45` AS `bfcol_53`, - `bfcol_40` * ( - 1 - `bfcol_41` - ) AS `bfcol_54` - FROM `bfcte_2` - INNER JOIN `bfcte_3` - ON `bfcol_39` = `bfcol_43` -), `bfcte_5` AS ( - SELECT - `bfcol_51`, - `bfcol_52`, - `bfcol_53`, - COALESCE(SUM(`bfcol_54`), 0) AS `bfcol_59` - FROM `bfcte_4` - GROUP BY - `bfcol_51`, - `bfcol_52`, - `bfcol_53` -) -SELECT - `bfcol_51` AS `L_ORDERKEY`, - `bfcol_59` AS `REVENUE`, - `bfcol_52` AS `O_ORDERDATE`, - `bfcol_53` AS `O_SHIPPRIORITY` -FROM `bfcte_5` -ORDER BY - `bfcol_59` DESC, - `bfcol_52` ASC NULLS LAST, - `bfcol_51` ASC NULLS LAST, - `bfcol_53` ASC NULLS LAST -LIMIT 10 \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/tpch/snapshots/test_tpch/test_tpch_query/4/out.sql b/tests/unit/core/compile/sqlglot/tpch/snapshots/test_tpch/test_tpch_query/4/out.sql deleted file mode 100644 index 3235239710e..00000000000 --- a/tests/unit/core/compile/sqlglot/tpch/snapshots/test_tpch/test_tpch_query/4/out.sql +++ /dev/null @@ -1,67 +0,0 @@ -WITH `bfcte_0` AS ( - SELECT - `O_ORDERKEY` AS `bfcol_0`, - `O_ORDERDATE` AS `bfcol_1`, - `O_ORDERPRIORITY` AS `bfcol_2` - FROM `bigframes-dev-perf`.`tpch_0001t`.`ORDERS` AS `bft_1` -), `bfcte_1` AS ( - SELECT - `L_ORDERKEY` AS `bfcol_3`, - `L_COMMITDATE` AS `bfcol_4`, - `L_RECEIPTDATE` AS `bfcol_5` - FROM `bigframes-dev-perf`.`tpch_0001t`.`LINEITEM` AS `bft_0` -), `bfcte_2` AS ( - SELECT - `bfcol_3`, - `bfcol_4`, - `bfcol_5`, - `bfcol_0`, - `bfcol_1`, - `bfcol_2`, - `bfcol_3` AS `bfcol_11`, - `bfcol_4` AS `bfcol_12`, - `bfcol_5` AS `bfcol_13`, - `bfcol_2` AS `bfcol_14`, - ( - `bfcol_1` >= CAST('1993-07-01' AS DATE) - ) - AND ( - `bfcol_1` < CAST('1993-10-01' AS DATE) - ) AS `bfcol_15`, - `bfcol_3` AS `bfcol_25`, - `bfcol_2` AS `bfcol_26`, - `bfcol_4` < `bfcol_5` AS `bfcol_27` - FROM `bfcte_1` - INNER JOIN `bfcte_0` - ON `bfcol_3` = `bfcol_0` - WHERE - ( - `bfcol_1` >= CAST('1993-07-01' AS DATE) - ) - AND ( - `bfcol_1` < CAST('1993-10-01' AS DATE) - ) - AND `bfcol_4` < `bfcol_5` -), `bfcte_3` AS ( - SELECT - `bfcol_26`, - `bfcol_25`, - COUNT(1) AS `bfcol_33` - FROM `bfcte_2` - GROUP BY - `bfcol_26`, - `bfcol_25` -), `bfcte_4` AS ( - SELECT - `bfcol_26`, - COUNT(`bfcol_25`) AS `bfcol_36` - FROM `bfcte_3` - GROUP BY - `bfcol_26` -) -SELECT - `bfcol_26` AS `O_ORDERPRIORITY`, - `bfcol_36` AS `ORDER_COUNT` -FROM `bfcte_4` -ORDER BY - `bfcol_26` ASC NULLS LAST \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/tpch/snapshots/test_tpch/test_tpch_query/5/out.sql b/tests/unit/core/compile/sqlglot/tpch/snapshots/test_tpch/test_tpch_query/5/out.sql deleted file mode 100644 index 5b707ce59ac..00000000000 --- a/tests/unit/core/compile/sqlglot/tpch/snapshots/test_tpch/test_tpch_query/5/out.sql +++ /dev/null @@ -1,91 +0,0 @@ -WITH `bfcte_0` AS ( - SELECT - `S_SUPPKEY` AS `bfcol_0`, - `S_NATIONKEY` AS `bfcol_1` - FROM `bigframes-dev-perf`.`tpch_0001t`.`SUPPLIER` AS `bft_5` -), `bfcte_1` AS ( - SELECT - `C_CUSTKEY` AS `bfcol_2`, - `C_NATIONKEY` AS `bfcol_3` - FROM `bigframes-dev-perf`.`tpch_0001t`.`CUSTOMER` AS `bft_4` -), `bfcte_2` AS ( - SELECT - `N_NATIONKEY` AS `bfcol_4`, - `N_NAME` AS `bfcol_5`, - `N_REGIONKEY` AS `bfcol_6` - FROM `bigframes-dev-perf`.`tpch_0001t`.`NATION` AS `bft_3` -), `bfcte_3` AS ( - SELECT - `R_REGIONKEY` AS `bfcol_32` - FROM `bigframes-dev-perf`.`tpch_0001t`.`REGION` AS `bft_2` - WHERE - `R_NAME` = 'ASIA' -), `bfcte_4` AS ( - SELECT - `O_ORDERKEY` AS `bfcol_33`, - `O_CUSTKEY` AS `bfcol_34` - FROM `bigframes-dev-perf`.`tpch_0001t`.`ORDERS` AS `bft_1` - WHERE - ( - `O_ORDERDATE` >= CAST('1994-01-01' AS DATE) - ) - AND ( - `O_ORDERDATE` < CAST('1995-01-01' AS DATE) - ) -), `bfcte_5` AS ( - SELECT - `L_ORDERKEY` AS `bfcol_29`, - `L_SUPPKEY` AS `bfcol_30`, - `L_EXTENDEDPRICE` * ( - 1.0 - `L_DISCOUNT` - ) AS `bfcol_31` - FROM `bigframes-dev-perf`.`tpch_0001t`.`LINEITEM` AS `bft_0` -), `bfcte_6` AS ( - SELECT - `bfcol_4` AS `bfcol_35`, - `bfcol_5` AS `bfcol_36` - FROM `bfcte_3` - INNER JOIN `bfcte_2` - ON `bfcol_32` = `bfcol_6` -), `bfcte_7` AS ( - SELECT - `bfcol_35` AS `bfcol_37`, - `bfcol_36` AS `bfcol_38`, - `bfcol_2` AS `bfcol_39` - FROM `bfcte_6` - INNER JOIN `bfcte_1` - ON `bfcol_35` = `bfcol_3` -), `bfcte_8` AS ( - SELECT - `bfcol_33` AS `bfcol_40`, - `bfcol_37` AS `bfcol_41`, - `bfcol_38` AS `bfcol_42` - FROM `bfcte_4` - INNER JOIN `bfcte_7` - ON `bfcol_34` = `bfcol_39` -), `bfcte_9` AS ( - SELECT - `bfcol_30` AS `bfcol_43`, - `bfcol_31` AS `bfcol_44`, - `bfcol_41` AS `bfcol_45`, - `bfcol_42` AS `bfcol_46` - FROM `bfcte_5` - INNER JOIN `bfcte_8` - ON `bfcol_29` = `bfcol_40` -), `bfcte_10` AS ( - SELECT - `bfcol_46`, - COALESCE(SUM(`bfcol_44`), 0) AS `bfcol_49` - FROM `bfcte_9` - INNER JOIN `bfcte_0` - ON `bfcol_43` = `bfcol_0` AND `bfcol_45` = `bfcol_1` - GROUP BY - `bfcol_46` -) -SELECT - `bfcol_46` AS `N_NAME`, - `bfcol_49` AS `REVENUE` -FROM `bfcte_10` -ORDER BY - `bfcol_49` DESC, - `bfcol_46` ASC NULLS LAST \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/tpch/snapshots/test_tpch/test_tpch_query/6/out.sql b/tests/unit/core/compile/sqlglot/tpch/snapshots/test_tpch/test_tpch_query/6/out.sql deleted file mode 100644 index 3544fd18e48..00000000000 --- a/tests/unit/core/compile/sqlglot/tpch/snapshots/test_tpch/test_tpch_query/6/out.sql +++ /dev/null @@ -1,61 +0,0 @@ -WITH `bfcte_0` AS ( - SELECT - * - FROM UNNEST(ARRAY>[STRUCT(0)]) -), `bfcte_1` AS ( - SELECT - `L_QUANTITY`, - `L_EXTENDEDPRICE`, - `L_DISCOUNT`, - `L_SHIPDATE`, - `L_QUANTITY` AS `bfcol_5`, - `L_EXTENDEDPRICE` AS `bfcol_6`, - `L_DISCOUNT` AS `bfcol_7`, - ( - `L_SHIPDATE` >= CAST('1994-01-01' AS DATE) - ) - AND ( - `L_SHIPDATE` < CAST('1995-01-01' AS DATE) - ) AS `bfcol_8`, - `L_QUANTITY` AS `bfcol_16`, - `L_EXTENDEDPRICE` AS `bfcol_17`, - `L_DISCOUNT` AS `bfcol_18`, - ( - `L_DISCOUNT` >= 0.05 - ) AND ( - `L_DISCOUNT` <= 0.07 - ) AS `bfcol_19`, - `L_EXTENDEDPRICE` AS `bfcol_27`, - `L_DISCOUNT` AS `bfcol_28`, - `L_QUANTITY` < 24 AS `bfcol_29`, - `L_EXTENDEDPRICE` AS `bfcol_35`, - `L_DISCOUNT` AS `bfcol_36`, - `L_EXTENDEDPRICE` * `L_DISCOUNT` AS `bfcol_39` - FROM `bigframes-dev-perf`.`tpch_0001t`.`LINEITEM` AS `bft_0` - WHERE - ( - `L_SHIPDATE` >= CAST('1994-01-01' AS DATE) - ) - AND ( - `L_SHIPDATE` < CAST('1995-01-01' AS DATE) - ) - AND ( - `L_DISCOUNT` >= 0.05 - ) - AND ( - `L_DISCOUNT` <= 0.07 - ) - AND `L_QUANTITY` < 24 -), `bfcte_2` AS ( - SELECT - COALESCE(SUM(`bfcol_39`), 0) AS `bfcol_41` - FROM `bfcte_1` -), `bfcte_3` AS ( - SELECT - * - FROM `bfcte_2` -) -SELECT - CASE WHEN `bfcol_0` = 0 THEN `bfcol_41` END AS `REVENUE` -FROM `bfcte_3` -CROSS JOIN `bfcte_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/tpch/snapshots/test_tpch/test_tpch_query/7/out.sql b/tests/unit/core/compile/sqlglot/tpch/snapshots/test_tpch/test_tpch_query/7/out.sql deleted file mode 100644 index f180ca1b3ea..00000000000 --- a/tests/unit/core/compile/sqlglot/tpch/snapshots/test_tpch/test_tpch_query/7/out.sql +++ /dev/null @@ -1,138 +0,0 @@ -WITH `bfcte_0` AS ( - SELECT - `N_NATIONKEY` AS `bfcol_22`, - `N_NAME` AS `bfcol_23`, - COALESCE(COALESCE(`N_NAME` IN ('FRANCE', 'GERMANY'), FALSE), FALSE) AS `bfcol_24` - FROM `bigframes-dev-perf`.`tpch_0001t`.`NATION` AS `bft_4` - WHERE - COALESCE(COALESCE(`N_NAME` IN ('FRANCE', 'GERMANY'), FALSE), FALSE) -), `bfcte_1` AS ( - SELECT - `S_SUPPKEY` AS `bfcol_2`, - `S_NATIONKEY` AS `bfcol_3` - FROM `bigframes-dev-perf`.`tpch_0001t`.`SUPPLIER` AS `bft_3` -), `bfcte_2` AS ( - SELECT - `L_ORDERKEY` AS `bfcol_31`, - `L_SUPPKEY` AS `bfcol_32`, - `L_EXTENDEDPRICE` AS `bfcol_33`, - `L_DISCOUNT` AS `bfcol_34`, - `L_SHIPDATE` AS `bfcol_35` - FROM `bigframes-dev-perf`.`tpch_0001t`.`LINEITEM` AS `bft_2` - WHERE - ( - `L_SHIPDATE` >= CAST('1995-01-01' AS DATE) - ) - AND ( - `L_SHIPDATE` <= CAST('1996-12-31' AS DATE) - ) -), `bfcte_3` AS ( - SELECT - `O_ORDERKEY` AS `bfcol_9`, - `O_CUSTKEY` AS `bfcol_10` - FROM `bigframes-dev-perf`.`tpch_0001t`.`ORDERS` AS `bft_1` -), `bfcte_4` AS ( - SELECT - `C_CUSTKEY` AS `bfcol_11`, - `C_NATIONKEY` AS `bfcol_12` - FROM `bigframes-dev-perf`.`tpch_0001t`.`CUSTOMER` AS `bft_0` -), `bfcte_5` AS ( - SELECT - `bfcol_22` AS `bfcol_36`, - `bfcol_23` AS `bfcol_37` - FROM `bfcte_0` -), `bfcte_6` AS ( - SELECT - `bfcol_22` AS `bfcol_38`, - `bfcol_23` AS `bfcol_39` - FROM `bfcte_0` -), `bfcte_7` AS ( - SELECT - `bfcol_11` AS `bfcol_40`, - `bfcol_39` AS `bfcol_41` - FROM `bfcte_4` - INNER JOIN `bfcte_6` - ON `bfcol_12` = `bfcol_38` -), `bfcte_8` AS ( - SELECT - `bfcol_41` AS `bfcol_42`, - `bfcol_9` AS `bfcol_43` - FROM `bfcte_7` - INNER JOIN `bfcte_3` - ON `bfcol_40` = `bfcol_10` -), `bfcte_9` AS ( - SELECT - `bfcol_42` AS `bfcol_44`, - `bfcol_32` AS `bfcol_45`, - `bfcol_33` AS `bfcol_46`, - `bfcol_34` AS `bfcol_47`, - `bfcol_35` AS `bfcol_48` - FROM `bfcte_8` - INNER JOIN `bfcte_2` - ON `bfcol_43` = `bfcol_31` -), `bfcte_10` AS ( - SELECT - `bfcol_44` AS `bfcol_49`, - `bfcol_46` AS `bfcol_50`, - `bfcol_47` AS `bfcol_51`, - `bfcol_48` AS `bfcol_52`, - `bfcol_3` AS `bfcol_53` - FROM `bfcte_9` - INNER JOIN `bfcte_1` - ON `bfcol_45` = `bfcol_2` -), `bfcte_11` AS ( - SELECT - `bfcol_49`, - `bfcol_50`, - `bfcol_51`, - `bfcol_52`, - `bfcol_53`, - `bfcol_36`, - `bfcol_37`, - `bfcol_49` AS `bfcol_59`, - `bfcol_50` AS `bfcol_60`, - `bfcol_51` AS `bfcol_61`, - `bfcol_52` AS `bfcol_62`, - `bfcol_37` AS `bfcol_63`, - `bfcol_49` <> `bfcol_37` AS `bfcol_64`, - `bfcol_49` AS `bfcol_76`, - `bfcol_52` AS `bfcol_77`, - `bfcol_37` AS `bfcol_78`, - `bfcol_50` * ( - 1.0 - `bfcol_51` - ) AS `bfcol_79`, - `bfcol_49` AS `bfcol_84`, - `bfcol_37` AS `bfcol_85`, - `bfcol_50` * ( - 1.0 - `bfcol_51` - ) AS `bfcol_86`, - EXTRACT(YEAR FROM `bfcol_52`) AS `bfcol_87` - FROM `bfcte_10` - INNER JOIN `bfcte_5` - ON `bfcol_53` = `bfcol_36` - WHERE - `bfcol_49` <> `bfcol_37` -), `bfcte_12` AS ( - SELECT - `bfcol_85`, - `bfcol_84`, - `bfcol_87`, - COALESCE(SUM(`bfcol_86`), 0) AS `bfcol_92` - FROM `bfcte_11` - WHERE - NOT `bfcol_87` IS NULL - GROUP BY - `bfcol_85`, - `bfcol_84`, - `bfcol_87` -) -SELECT - `bfcol_85` AS `SUPP_NATION`, - `bfcol_84` AS `CUST_NATION`, - `bfcol_87` AS `L_YEAR`, - `bfcol_92` AS `REVENUE` -FROM `bfcte_12` -ORDER BY - `bfcol_85` ASC NULLS LAST, - `bfcol_84` ASC NULLS LAST, - `bfcol_87` ASC NULLS LAST \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/tpch/snapshots/test_tpch/test_tpch_query/8/out.sql b/tests/unit/core/compile/sqlglot/tpch/snapshots/test_tpch/test_tpch_query/8/out.sql deleted file mode 100644 index edaf51f18b8..00000000000 --- a/tests/unit/core/compile/sqlglot/tpch/snapshots/test_tpch/test_tpch_query/8/out.sql +++ /dev/null @@ -1,186 +0,0 @@ -WITH `bfcte_0` AS ( - SELECT - `N_NATIONKEY` AS `bfcol_0`, - `N_NAME` AS `bfcol_1` - FROM `bigframes-dev-perf`.`tpch_0001t`.`NATION` AS `bft_6` -), `bfcte_1` AS ( - SELECT - `N_NATIONKEY` AS `bfcol_4`, - `N_REGIONKEY` AS `bfcol_5` - FROM `bigframes-dev-perf`.`tpch_0001t`.`NATION` AS `bft_6` -), `bfcte_2` AS ( - SELECT - `R_REGIONKEY` AS `bfcol_2`, - `R_NAME` AS `bfcol_3` - FROM `bigframes-dev-perf`.`tpch_0001t`.`REGION` AS `bft_5` -), `bfcte_3` AS ( - SELECT - `C_CUSTKEY` AS `bfcol_6`, - `C_NATIONKEY` AS `bfcol_7` - FROM `bigframes-dev-perf`.`tpch_0001t`.`CUSTOMER` AS `bft_4` -), `bfcte_4` AS ( - SELECT - `O_ORDERKEY` AS `bfcol_8`, - `O_CUSTKEY` AS `bfcol_9`, - `O_ORDERDATE` AS `bfcol_10` - FROM `bigframes-dev-perf`.`tpch_0001t`.`ORDERS` AS `bft_3` -), `bfcte_5` AS ( - SELECT - `S_SUPPKEY` AS `bfcol_11`, - `S_NATIONKEY` AS `bfcol_12` - FROM `bigframes-dev-perf`.`tpch_0001t`.`SUPPLIER` AS `bft_2` -), `bfcte_6` AS ( - SELECT - `L_ORDERKEY` AS `bfcol_13`, - `L_PARTKEY` AS `bfcol_14`, - `L_SUPPKEY` AS `bfcol_15`, - `L_EXTENDEDPRICE` AS `bfcol_16`, - `L_DISCOUNT` AS `bfcol_17` - FROM `bigframes-dev-perf`.`tpch_0001t`.`LINEITEM` AS `bft_1` -), `bfcte_7` AS ( - SELECT - `P_PARTKEY` AS `bfcol_18`, - `P_TYPE` AS `bfcol_19` - FROM `bigframes-dev-perf`.`tpch_0001t`.`PART` AS `bft_0` -), `bfcte_8` AS ( - SELECT - `bfcol_19` AS `bfcol_20`, - `bfcol_13` AS `bfcol_21`, - `bfcol_15` AS `bfcol_22`, - `bfcol_16` AS `bfcol_23`, - `bfcol_17` AS `bfcol_24` - FROM `bfcte_7` - INNER JOIN `bfcte_6` - ON `bfcol_18` = `bfcol_14` -), `bfcte_9` AS ( - SELECT - `bfcol_20` AS `bfcol_25`, - `bfcol_21` AS `bfcol_26`, - `bfcol_23` AS `bfcol_27`, - `bfcol_24` AS `bfcol_28`, - `bfcol_12` AS `bfcol_29` - FROM `bfcte_8` - INNER JOIN `bfcte_5` - ON `bfcol_22` = `bfcol_11` -), `bfcte_10` AS ( - SELECT - `bfcol_25` AS `bfcol_30`, - `bfcol_27` AS `bfcol_31`, - `bfcol_28` AS `bfcol_32`, - `bfcol_29` AS `bfcol_33`, - `bfcol_9` AS `bfcol_34`, - `bfcol_10` AS `bfcol_35` - FROM `bfcte_9` - INNER JOIN `bfcte_4` - ON `bfcol_26` = `bfcol_8` -), `bfcte_11` AS ( - SELECT - `bfcol_30` AS `bfcol_36`, - `bfcol_31` AS `bfcol_37`, - `bfcol_32` AS `bfcol_38`, - `bfcol_33` AS `bfcol_39`, - `bfcol_35` AS `bfcol_40`, - `bfcol_7` AS `bfcol_41` - FROM `bfcte_10` - INNER JOIN `bfcte_3` - ON `bfcol_34` = `bfcol_6` -), `bfcte_12` AS ( - SELECT - `bfcol_36` AS `bfcol_42`, - `bfcol_37` AS `bfcol_43`, - `bfcol_38` AS `bfcol_44`, - `bfcol_39` AS `bfcol_45`, - `bfcol_40` AS `bfcol_46`, - `bfcol_5` AS `bfcol_47` - FROM `bfcte_11` - INNER JOIN `bfcte_1` - ON `bfcol_41` = `bfcol_4` -), `bfcte_13` AS ( - SELECT - `bfcol_42` AS `bfcol_66`, - `bfcol_43` AS `bfcol_67`, - `bfcol_44` AS `bfcol_68`, - `bfcol_45` AS `bfcol_69`, - `bfcol_46` AS `bfcol_70` - FROM `bfcte_12` - INNER JOIN `bfcte_2` - ON `bfcol_47` = `bfcol_2` - WHERE - `bfcol_3` = 'AMERICA' -), `bfcte_14` AS ( - SELECT - `bfcol_66`, - `bfcol_67`, - `bfcol_68`, - `bfcol_69`, - `bfcol_70`, - `bfcol_0`, - `bfcol_1`, - `bfcol_66` AS `bfcol_76`, - `bfcol_67` AS `bfcol_77`, - `bfcol_68` AS `bfcol_78`, - `bfcol_70` AS `bfcol_79`, - `bfcol_1` AS `bfcol_80`, - ( - `bfcol_70` >= CAST('1995-01-01' AS DATE) - ) - AND ( - `bfcol_70` <= CAST('1996-12-31' AS DATE) - ) AS `bfcol_81`, - `bfcol_67` AS `bfcol_93`, - `bfcol_68` AS `bfcol_94`, - `bfcol_70` AS `bfcol_95`, - `bfcol_1` AS `bfcol_96`, - `bfcol_66` = 'ECONOMY ANODIZED STEEL' AS `bfcol_97`, - `bfcol_67` AS `bfcol_107`, - `bfcol_68` AS `bfcol_108`, - `bfcol_1` AS `bfcol_109`, - EXTRACT(YEAR FROM `bfcol_70`) AS `bfcol_110`, - `bfcol_1` AS `bfcol_115`, - EXTRACT(YEAR FROM `bfcol_70`) AS `bfcol_116`, - `bfcol_67` * ( - 1.0 - `bfcol_68` - ) AS `bfcol_117`, - EXTRACT(YEAR FROM `bfcol_70`) AS `bfcol_121`, - `bfcol_67` * ( - 1.0 - `bfcol_68` - ) AS `bfcol_122`, - IF(`bfcol_1` = 'BRAZIL', `bfcol_67` * ( - 1.0 - `bfcol_68` - ), 0) AS `bfcol_123`, - EXTRACT(YEAR FROM `bfcol_70`) AS `bfcol_127`, - IF(`bfcol_1` = 'BRAZIL', `bfcol_67` * ( - 1.0 - `bfcol_68` - ), 0) AS `bfcol_128`, - `bfcol_67` * ( - 1.0 - `bfcol_68` - ) AS `bfcol_129` - FROM `bfcte_13` - INNER JOIN `bfcte_0` - ON `bfcol_69` = `bfcol_0` - WHERE - ( - `bfcol_70` >= CAST('1995-01-01' AS DATE) - ) - AND ( - `bfcol_70` <= CAST('1996-12-31' AS DATE) - ) - AND `bfcol_66` = 'ECONOMY ANODIZED STEEL' -), `bfcte_15` AS ( - SELECT - `bfcol_127`, - COALESCE(SUM(`bfcol_128`), 0) AS `bfcol_133`, - COALESCE(SUM(`bfcol_129`), 0) AS `bfcol_134` - FROM `bfcte_14` - WHERE - NOT `bfcol_127` IS NULL - GROUP BY - `bfcol_127` -) -SELECT - `bfcol_127` AS `O_YEAR`, - ROUND(IEEE_DIVIDE(`bfcol_133`, `bfcol_134`), 2) AS `MKT_SHARE` -FROM `bfcte_15` -ORDER BY - `bfcol_127` ASC NULLS LAST, - `bfcol_127` ASC NULLS LAST \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/tpch/snapshots/test_tpch/test_tpch_query/9/out.sql b/tests/unit/core/compile/sqlglot/tpch/snapshots/test_tpch/test_tpch_query/9/out.sql deleted file mode 100644 index 949d45e8dae..00000000000 --- a/tests/unit/core/compile/sqlglot/tpch/snapshots/test_tpch/test_tpch_query/9/out.sql +++ /dev/null @@ -1,143 +0,0 @@ -WITH `bfcte_0` AS ( - SELECT - `N_NATIONKEY` AS `bfcol_0`, - `N_NAME` AS `bfcol_1` - FROM `bigframes-dev-perf`.`tpch_0001t`.`NATION` AS `bft_5` -), `bfcte_1` AS ( - SELECT - `O_ORDERKEY` AS `bfcol_2`, - `O_ORDERDATE` AS `bfcol_3` - FROM `bigframes-dev-perf`.`tpch_0001t`.`ORDERS` AS `bft_4` -), `bfcte_2` AS ( - SELECT - `S_SUPPKEY` AS `bfcol_4`, - `S_NATIONKEY` AS `bfcol_5` - FROM `bigframes-dev-perf`.`tpch_0001t`.`SUPPLIER` AS `bft_3` -), `bfcte_3` AS ( - SELECT - `PS_PARTKEY` AS `bfcol_6`, - `PS_SUPPKEY` AS `bfcol_7`, - `PS_SUPPLYCOST` AS `bfcol_8` - FROM `bigframes-dev-perf`.`tpch_0001t`.`PARTSUPP` AS `bft_2` -), `bfcte_4` AS ( - SELECT - `L_ORDERKEY` AS `bfcol_9`, - `L_PARTKEY` AS `bfcol_10`, - `L_SUPPKEY` AS `bfcol_11`, - `L_QUANTITY` AS `bfcol_12`, - `L_EXTENDEDPRICE` AS `bfcol_13`, - `L_DISCOUNT` AS `bfcol_14` - FROM `bigframes-dev-perf`.`tpch_0001t`.`LINEITEM` AS `bft_1` -), `bfcte_5` AS ( - SELECT - `P_PARTKEY` AS `bfcol_15`, - `P_NAME` AS `bfcol_16` - FROM `bigframes-dev-perf`.`tpch_0001t`.`PART` AS `bft_0` -), `bfcte_6` AS ( - SELECT - `bfcol_16` AS `bfcol_17`, - `bfcol_9` AS `bfcol_18`, - `bfcol_10` AS `bfcol_19`, - `bfcol_11` AS `bfcol_20`, - `bfcol_12` AS `bfcol_21`, - `bfcol_13` AS `bfcol_22`, - `bfcol_14` AS `bfcol_23` - FROM `bfcte_5` - INNER JOIN `bfcte_4` - ON `bfcol_15` = `bfcol_10` -), `bfcte_7` AS ( - SELECT - `bfcol_17` AS `bfcol_24`, - `bfcol_18` AS `bfcol_25`, - `bfcol_20` AS `bfcol_26`, - `bfcol_21` AS `bfcol_27`, - `bfcol_22` AS `bfcol_28`, - `bfcol_23` AS `bfcol_29`, - `bfcol_8` AS `bfcol_30` - FROM `bfcte_6` - INNER JOIN `bfcte_3` - ON `bfcol_20` = `bfcol_7` AND `bfcol_19` = `bfcol_6` -), `bfcte_8` AS ( - SELECT - `bfcol_24` AS `bfcol_31`, - `bfcol_25` AS `bfcol_32`, - `bfcol_27` AS `bfcol_33`, - `bfcol_28` AS `bfcol_34`, - `bfcol_29` AS `bfcol_35`, - `bfcol_30` AS `bfcol_36`, - `bfcol_5` AS `bfcol_37` - FROM `bfcte_7` - INNER JOIN `bfcte_2` - ON `bfcol_26` = `bfcol_4` -), `bfcte_9` AS ( - SELECT - `bfcol_31` AS `bfcol_38`, - `bfcol_33` AS `bfcol_39`, - `bfcol_34` AS `bfcol_40`, - `bfcol_35` AS `bfcol_41`, - `bfcol_36` AS `bfcol_42`, - `bfcol_37` AS `bfcol_43`, - `bfcol_3` AS `bfcol_44` - FROM `bfcte_8` - INNER JOIN `bfcte_1` - ON `bfcol_32` = `bfcol_2` -), `bfcte_10` AS ( - SELECT - `bfcol_38`, - `bfcol_39`, - `bfcol_40`, - `bfcol_41`, - `bfcol_42`, - `bfcol_43`, - `bfcol_44`, - `bfcol_0`, - `bfcol_1`, - `bfcol_39` AS `bfcol_52`, - `bfcol_40` AS `bfcol_53`, - `bfcol_41` AS `bfcol_54`, - `bfcol_42` AS `bfcol_55`, - `bfcol_44` AS `bfcol_56`, - `bfcol_1` AS `bfcol_57`, - REGEXP_CONTAINS(`bfcol_38`, 'green') AS `bfcol_58`, - `bfcol_39` AS `bfcol_72`, - `bfcol_40` AS `bfcol_73`, - `bfcol_41` AS `bfcol_74`, - `bfcol_42` AS `bfcol_75`, - `bfcol_1` AS `bfcol_76`, - EXTRACT(YEAR FROM `bfcol_44`) AS `bfcol_77`, - `bfcol_1` AS `bfcol_84`, - EXTRACT(YEAR FROM `bfcol_44`) AS `bfcol_85`, - ( - `bfcol_40` * ( - 1 - `bfcol_41` - ) - ) - ( - `bfcol_42` * `bfcol_39` - ) AS `bfcol_86` - FROM `bfcte_9` - INNER JOIN `bfcte_0` - ON `bfcol_43` = `bfcol_0` - WHERE - REGEXP_CONTAINS(`bfcol_38`, 'green') -), `bfcte_11` AS ( - SELECT - `bfcol_84`, - `bfcol_85`, - COALESCE(SUM(`bfcol_86`), 0) AS `bfcol_90` - FROM `bfcte_10` - WHERE - NOT `bfcol_85` IS NULL - GROUP BY - `bfcol_84`, - `bfcol_85` -) -SELECT - `bfcol_84` AS `NATION`, - `bfcol_85` AS `O_YEAR`, - ROUND(`bfcol_90`, 2) AS `SUM_PROFIT` -FROM `bfcte_11` -ORDER BY - `bfcol_84` ASC NULLS LAST, - `bfcol_85` DESC, - `bfcol_84` ASC NULLS LAST, - `bfcol_85` ASC NULLS LAST \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/tpch/test_tpch.py b/tests/unit/core/compile/sqlglot/tpch/test_tpch.py deleted file mode 100644 index 8988a5512f2..00000000000 --- a/tests/unit/core/compile/sqlglot/tpch/test_tpch.py +++ /dev/null @@ -1,47 +0,0 @@ -# Copyright 2026 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 re - -import pytest - -freezegun = pytest.importorskip("freezegun") -pytest.importorskip("pytest_snapshot") - - -@pytest.mark.parametrize("query_num", range(1, 23)) -def test_tpch_query(tpch_session, query_num, snapshot): - project_id = "bigframes-dev-perf" - dataset_id = "tpch_0001t" - - query_file_path = f"third_party/bigframes_vendored/tpch/queries/q{query_num}.py" - - with open(query_file_path, "r") as f: - query_code = f.read() - - # We want to capture the result dataframe instead of running next(result.to_pandas_batches(...)) - modified_code = re.sub( - r"next\((\w+)\.to_pandas_batches\((.*?)\)\)", - r"return \1", - query_code, - ) - - exec_globals = {} # type: ignore[var-annotated] - exec(modified_code, exec_globals) - q_func = exec_globals["q"] - - result = q_func(project_id, dataset_id, tpch_session) - - # result should be a DataFrame - snapshot.assert_match(result.sql, "out.sql") diff --git a/tests/unit/core/logging/__init__.py b/tests/unit/core/logging/__init__.py deleted file mode 100644 index 58d482ea386..00000000000 --- a/tests/unit/core/logging/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -# Copyright 2026 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/unit/core/logging/test_data_types.py b/tests/unit/core/logging/test_data_types.py deleted file mode 100644 index 09b3429f00d..00000000000 --- a/tests/unit/core/logging/test_data_types.py +++ /dev/null @@ -1,54 +0,0 @@ -# Copyright 2026 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 as pd -import pyarrow as pa -import pytest - -from bigframes import dtypes -from bigframes.core.logging import data_types - -UNKNOWN_TYPE = pd.ArrowDtype(pa.time64("ns")) - -PA_STRUCT_TYPE = pa.struct([("city", pa.string()), ("pop", pa.int64())]) - -PA_LIST_TYPE = pa.list_(pa.int64()) - - -@pytest.mark.parametrize( - ("dtype", "expected_mask"), - [ - (None, 0), - (UNKNOWN_TYPE, 1 << 0), - (dtypes.INT_DTYPE, 1 << 1), - (dtypes.FLOAT_DTYPE, 1 << 2), - (dtypes.BOOL_DTYPE, 1 << 3), - (dtypes.STRING_DTYPE, 1 << 4), - (dtypes.BYTES_DTYPE, 1 << 5), - (dtypes.DATE_DTYPE, 1 << 6), - (dtypes.TIME_DTYPE, 1 << 7), - (dtypes.DATETIME_DTYPE, 1 << 8), - (dtypes.TIMESTAMP_DTYPE, 1 << 9), - (dtypes.TIMEDELTA_DTYPE, 1 << 10), - (dtypes.NUMERIC_DTYPE, 1 << 11), - (dtypes.BIGNUMERIC_DTYPE, 1 << 12), - (dtypes.GEO_DTYPE, 1 << 13), - (dtypes.JSON_DTYPE, 1 << 14), - (pd.ArrowDtype(PA_STRUCT_TYPE), 1 << 15), - (pd.ArrowDtype(PA_LIST_TYPE), 1 << 16), - (dtypes.OBJ_REF_DTYPE, (1 << 15) | (1 << 17)), - ], -) -def test_get_dtype_mask(dtype, expected_mask): - assert data_types._get_dtype_mask(dtype) == expected_mask diff --git a/tests/unit/core/logging/test_log_adapter.py b/tests/unit/core/logging/test_log_adapter.py deleted file mode 100644 index 0722ef62a29..00000000000 --- a/tests/unit/core/logging/test_log_adapter.py +++ /dev/null @@ -1,265 +0,0 @@ -# Copyright 2023 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 unittest import mock - -import pytest -from google.cloud import bigquery - -from bigframes.core.logging import log_adapter - -# The limit is 64 (https://cloud.google.com/bigquery/docs/labels-intro#requirements), -# but leave a few spare for internal labels to be added. -# See internal issue 386825477. -MAX_LABELS_COUNT = 56 - - -@pytest.fixture -def mock_bqclient(): - mock_bqclient = mock.create_autospec(spec=bigquery.Client) - return mock_bqclient - - -@pytest.fixture -def test_instance(): - # Create a simple class for testing - @log_adapter.class_logger - class TestClass: - def method1(self): - pass - - def method2(self): - self.method3() - - def method3(self): - pass - - @log_adapter.log_name_override("override_name") - def method4(self): - pass - - @property - def my_field(self): - return 0 - - return TestClass() - - -@pytest.fixture -def test_method(): - @log_adapter.method_logger - def method1(): - pass - - return method1 - - -@pytest.fixture -def test_method_w_custom_base(): - def method1(): - pass - - _decorated_method = log_adapter.method_logger(method1, custom_base_name="pandas") - - return _decorated_method - - -def test_class_attribute_logging(test_instance): - test_instance.method1() - test_instance.method2() - test_instance.method4() - - # Check if the methods were added to the _api_methods list - api_methods = log_adapter.get_and_reset_api_methods() - assert "testclass-method1" in api_methods - assert "testclass-method2" in api_methods - assert "testclass-method3" not in api_methods - assert "testclass-method4" not in api_methods - assert "testclass-override_name" in api_methods - - -def test_method_logging(test_method): - test_method() - api_methods = log_adapter.get_and_reset_api_methods() - assert "locals-method1" in api_methods - - -def test_method_logging_with_custom_base_name(test_method_w_custom_base): - test_method_w_custom_base() - api_methods = log_adapter.get_and_reset_api_methods() - assert "pandas-method1" in api_methods - - -def test_method_logging_with_custom_base__logger_as_decorator(): - @log_adapter.method_logger(custom_base_name="pandas") - def my_method(): - pass - - my_method() - - api_methods = log_adapter.get_and_reset_api_methods() - assert "pandas-my_method" in api_methods - - -def test_property_logging(test_instance): - test_instance.my_field - - # Check if the properties were added to the _api_methods list - api_methods = log_adapter.get_and_reset_api_methods() - assert "testclass-my_field" in api_methods - - -def test_add_api_method_limit(test_instance): - # Ensure that add_api_method correctly adds a method to _api_methods - for i in range(70): - test_instance.method2() - assert len(log_adapter._api_methods) == MAX_LABELS_COUNT - - -def test_get_and_reset_api_methods(test_instance): - # Ensure that get_and_reset_api_methods returns a copy and resets the list - test_instance.method1() - test_instance.method2() - previous_methods = log_adapter.get_and_reset_api_methods() - assert previous_methods is not None - assert log_adapter._api_methods == [] - - -@pytest.mark.parametrize( - ("class_name", "method_name", "args", "kwargs", "task", "expected_labels"), - ( - ( - "DataFrame", - "resample", - ["a", "b", "c"], - {"aa": "bb", "rule": "1s"}, - log_adapter.PANDAS_API_TRACKING_TASK, - { - "task": log_adapter.PANDAS_API_TRACKING_TASK, - "class_name": "dataframe", - "method_name": "resample", - "args_count": 3, - "kwargs_0": "rule", - }, - ), - ( - "Series", - "resample", - [], - {"aa": "bb", "rule": "1s"}, - log_adapter.PANDAS_PARAM_TRACKING_TASK, - { - "task": log_adapter.PANDAS_PARAM_TRACKING_TASK, - "class_name": "series", - "method_name": "resample", - "args_count": 0, - "kwargs_0": "rule", - }, - ), - ( - "DataFrame", - "resample", - [], - {"aa": "bb"}, - log_adapter.PANDAS_API_TRACKING_TASK, - { - "task": log_adapter.PANDAS_API_TRACKING_TASK, - "class_name": "dataframe", - "method_name": "resample", - "args_count": 0, - }, - ), - ( - "DataFrame", - "resample", - [], - {}, - log_adapter.PANDAS_API_TRACKING_TASK, - { - "task": log_adapter.PANDAS_API_TRACKING_TASK, - "class_name": "dataframe", - "method_name": "resample", - "args_count": 0, - }, - ), - ( - "pandas", - "concat", - [[None, None]], - {"axis": 1}, - log_adapter.PANDAS_API_TRACKING_TASK, - { - "task": log_adapter.PANDAS_API_TRACKING_TASK, - "class_name": "pandas", - "method_name": "concat", - "args_count": 1, - "kwargs_0": "axis", - }, - ), - ), -) -def test_submit_pandas_labels( - mock_bqclient, class_name, method_name, args, kwargs, task, expected_labels -): - log_adapter.submit_pandas_labels( - mock_bqclient, class_name, method_name, args, kwargs, task - ) - - mock_bqclient.query.assert_called_once() - - query_call_args = mock_bqclient.query.call_args_list[0] - labels = query_call_args[1]["job_config"].labels - assert labels == expected_labels - - -def test_submit_pandas_labels_without_valid_params_for_param_logging(mock_bqclient): - log_adapter.submit_pandas_labels( - mock_bqclient, - "Series", - "resample", - task=log_adapter.PANDAS_PARAM_TRACKING_TASK, - ) - - # For param tracking task without kwargs, we won't submit labels - mock_bqclient.query.assert_not_called() - - -@pytest.mark.parametrize( - ("class_name", "method_name"), - ( - ("Series", "_repr_latex_"), - ( - "DataFrame", - # __call__ should be excluded. - # It's implemented on the pd.DataFrame class but not pd.DataFrame instances. - "__call__", - ), - ( - "Series", - # __call__ should be excluded. - # It's implemented on the pd.Series class but not pd.Series instances. - "__call__", - ), - ), -) -def test_submit_pandas_labels_with_internal_method( - mock_bqclient, class_name, method_name -): - log_adapter.submit_pandas_labels( - mock_bqclient, - class_name, - method_name, - task=log_adapter.PANDAS_API_TRACKING_TASK, - ) - mock_bqclient.query.assert_not_called() diff --git a/tests/unit/core/rewrite/conftest.py b/tests/unit/core/rewrite/conftest.py deleted file mode 100644 index ab168427f29..00000000000 --- a/tests/unit/core/rewrite/conftest.py +++ /dev/null @@ -1,84 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -import unittest.mock as mock - -import google.cloud.bigquery -import pytest - -import bigframes -import bigframes.core as core -from bigframes.core import bq_data - -TABLE_REF = google.cloud.bigquery.TableReference.from_string("project.dataset.table") -SCHEMA = ( - google.cloud.bigquery.SchemaField("col_a", "INTEGER"), - google.cloud.bigquery.SchemaField("col_b", "INTEGER"), -) -TABLE = google.cloud.bigquery.Table( - table_ref=TABLE_REF, - schema=SCHEMA, -) -FAKE_SESSION = mock.create_autospec(bigframes.Session, instance=True) -type(FAKE_SESSION)._strictly_ordered = mock.PropertyMock(return_value=True) - - -@pytest.fixture -def table(): - table_ref = google.cloud.bigquery.TableReference.from_string( - "project.dataset.table" - ) - schema = ( - google.cloud.bigquery.SchemaField("col_a", "INTEGER"), - google.cloud.bigquery.SchemaField("col_b", "INTEGER"), - ) - return google.cloud.bigquery.Table( - table_ref=table_ref, - schema=schema, - ) - - -@pytest.fixture -def table_too(): - table_ref = google.cloud.bigquery.TableReference.from_string( - "project.dataset.table_too" - ) - schema = ( - google.cloud.bigquery.SchemaField("col_a", "INTEGER"), - google.cloud.bigquery.SchemaField("col_c", "INTEGER"), - ) - return google.cloud.bigquery.Table( - table_ref=table_ref, - schema=schema, - ) - - -@pytest.fixture -def fake_session(): - return FAKE_SESSION - - -@pytest.fixture -def leaf(fake_session, table): - return core.ArrayValue.from_table( - session=fake_session, - table=bq_data.GbqNativeTable.from_table(table), - ).node - - -@pytest.fixture -def leaf_too(fake_session, table_too): - return core.ArrayValue.from_table( - session=fake_session, - table=bq_data.GbqNativeTable.from_table(table_too), - ).node diff --git a/tests/unit/core/rewrite/test_identifiers.py b/tests/unit/core/rewrite/test_identifiers.py deleted file mode 100644 index 4d4609bb0fa..00000000000 --- a/tests/unit/core/rewrite/test_identifiers.py +++ /dev/null @@ -1,203 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -import typing - -import bigframes.core as core -import bigframes.core.agg_expressions as agg_ex -import bigframes.core.expression as ex -import bigframes.core.identifiers as identifiers -import bigframes.core.nodes as nodes -import bigframes.core.rewrite.identifiers as id_rewrite -import bigframes.operations.aggregations as agg_ops -from bigframes.core import bq_data - - -def test_remap_variables_single_node(leaf): - node = leaf - id_generator = (identifiers.ColumnId(f"id_{i}") for i in range(100)) - new_node, mapping = id_rewrite.remap_variables(node, id_generator) - assert new_node is not node - assert len(mapping) == 2 - assert set(mapping.keys()) == {f.id for f in node.fields} - assert set(mapping.values()) == { - identifiers.ColumnId("id_0"), - identifiers.ColumnId("id_1"), - } - - -def test_remap_variables_projection(leaf): - node = nodes.ProjectionNode( - leaf, - ( - ( - core.expression.DerefOp(leaf.fields[0].id), - identifiers.ColumnId("new_col"), - ), - ), - ) - id_generator = (identifiers.ColumnId(f"id_{i}") for i in range(100)) - new_node, mapping = id_rewrite.remap_variables(node, id_generator) - assert new_node is not node - assert len(mapping) == 3 - assert set(mapping.keys()) == {f.id for f in node.fields} - assert set(mapping.values()) == {identifiers.ColumnId(f"id_{i}") for i in range(3)} - - -def test_remap_variables_aggregate(leaf): - # Aggregation: sum(col_a) AS sum_a - # Group by nothing - agg_op = agg_ex.UnaryAggregation( - op=agg_ops.sum_op, - arg=ex.DerefOp(leaf.fields[0].id), - ) - node = nodes.AggregateNode( - child=leaf, - aggregations=((agg_op, identifiers.ColumnId("sum_a")),), - by_column_ids=(), - ) - - id_generator = (identifiers.ColumnId(f"id_{i}") for i in range(100)) - _, mapping = id_rewrite.remap_variables(node, id_generator) - - # leaf has 2 columns: col_a, col_b - # AggregateNode defines 1 column: sum_a - # Output of AggregateNode should only be sum_a - assert len(mapping) == 1 - assert identifiers.ColumnId("sum_a") in mapping - - -def test_remap_variables_aggregate_with_grouping(leaf): - # Aggregation: sum(col_b) AS sum_b - # Group by col_a - agg_op = agg_ex.UnaryAggregation( - op=agg_ops.sum_op, - arg=ex.DerefOp(leaf.fields[1].id), - ) - node = nodes.AggregateNode( - child=leaf, - aggregations=((agg_op, identifiers.ColumnId("sum_b")),), - by_column_ids=(ex.DerefOp(leaf.fields[0].id),), - ) - - id_generator = (identifiers.ColumnId(f"id_{i}") for i in range(100)) - _, mapping = id_rewrite.remap_variables(node, id_generator) - - # Output should have 2 columns: col_a (grouping) and sum_b (agg) - assert len(mapping) == 2 - assert leaf.fields[0].id in mapping - assert identifiers.ColumnId("sum_b") in mapping - - -def test_remap_variables_nested_join_stability(leaf, fake_session, table): - # Create two more distinct leaf nodes - leaf2_uncached = core.ArrayValue.from_table( - session=fake_session, - table=bq_data.GbqNativeTable.from_table(table), - ).node - leaf2 = leaf2_uncached.remap_vars( - { - field.id: identifiers.ColumnId(f"leaf2_{field.id.name}") - for field in leaf2_uncached.fields - } - ) - leaf3_uncached = core.ArrayValue.from_table( - session=fake_session, - table=bq_data.GbqNativeTable.from_table(table), - ).node - leaf3 = leaf3_uncached.remap_vars( - { - field.id: identifiers.ColumnId(f"leaf3_{field.id.name}") - for field in leaf3_uncached.fields - } - ) - - # Create a nested join: (leaf JOIN leaf2) JOIN leaf3 - inner_join = nodes.JoinNode( - left_child=leaf, - right_child=leaf2, - conditions=( - ( - core.expression.DerefOp(leaf.fields[0].id), - core.expression.DerefOp(leaf2.fields[0].id), - ), - ), - type="inner", - propogate_order=False, - nulls_equal=True, - ) - outer_join = nodes.JoinNode( - left_child=inner_join, - right_child=leaf3, - conditions=( - ( - core.expression.DerefOp(inner_join.fields[0].id), - core.expression.DerefOp(leaf3.fields[0].id), - ), - ), - type="inner", - propogate_order=False, - nulls_equal=True, - ) - - # Run remap_variables twice and assert stability - id_generator1 = (identifiers.ColumnId(f"id_{i}") for i in range(100)) - new_node1, mapping1 = id_rewrite.remap_variables(outer_join, id_generator1) - - id_generator2 = (identifiers.ColumnId(f"id_{i}") for i in range(100)) - new_node2, mapping2 = id_rewrite.remap_variables(outer_join, id_generator2) - - assert new_node1 == new_node2 - assert mapping1 == mapping2 - - -def test_remap_variables_concat_self_stability(leaf): - # Create a concat node with the same child twice - node = nodes.ConcatNode( - children=(leaf, leaf), - output_ids=( - identifiers.ColumnId("concat_a"), - identifiers.ColumnId("concat_b"), - ), - ) - - # Run remap_variables twice and assert stability - id_generator1 = (identifiers.ColumnId(f"id_{i}") for i in range(100)) - new_node1, mapping1 = id_rewrite.remap_variables(node, id_generator1) - - id_generator2 = (identifiers.ColumnId(f"id_{i}") for i in range(100)) - new_node2, mapping2 = id_rewrite.remap_variables(node, id_generator2) - - assert new_node1 == new_node2 - assert mapping1 == mapping2 - - -def test_remap_variables_in_node_converts_dag_to_tree(leaf, leaf_too): - # Create an InNode with the same child twice, should create a tree from a DAG - right = nodes.SelectionNode( - leaf_too, (nodes.AliasedRef.identity(identifiers.ColumnId("col_a")),) - ) - node = nodes.InNode( - left_child=leaf, - right_child=right, - left_col=ex.DerefOp(identifiers.ColumnId("col_a")), - indicator_col=identifiers.ColumnId("indicator"), - ) - - id_generator = (identifiers.ColumnId(f"id_{i}") for i in range(100)) - new_node, _ = id_rewrite.remap_variables(node, id_generator) - new_node = typing.cast(nodes.InNode, new_node) - - left_col_id = new_node.left_col.id.name - new_node.validate_tree() - assert left_col_id.startswith("id_") diff --git a/tests/unit/core/rewrite/test_slices.py b/tests/unit/core/rewrite/test_slices.py deleted file mode 100644 index 6d49ffb80a7..00000000000 --- a/tests/unit/core/rewrite/test_slices.py +++ /dev/null @@ -1,34 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -import bigframes.core.nodes as nodes -import bigframes.core.rewrite.slices - - -def test_rewrite_noop_slice(leaf): - slice = nodes.SliceNode(leaf, None, None) - result = bigframes.core.rewrite.slices.rewrite_slice(slice) - assert result == leaf - - -def test_rewrite_reverse_slice(leaf): - slice = nodes.SliceNode(leaf, None, None, -1) - result = bigframes.core.rewrite.slices.rewrite_slice(slice) - assert result == nodes.ReversedNode(leaf) - - -def test_rewrite_filter_slice(leaf): - slice = nodes.SliceNode(leaf, None, 2) - result = bigframes.core.rewrite.slices.rewrite_slice(slice) - assert list(result.fields) == list(leaf.fields) - assert isinstance(result.child, nodes.FilterNode) diff --git a/tests/unit/core/sql/snapshots/test_ml/test_create_model_basic/create_model_basic.sql b/tests/unit/core/sql/snapshots/test_ml/test_create_model_basic/create_model_basic.sql deleted file mode 100644 index 9affd870e3e..00000000000 --- a/tests/unit/core/sql/snapshots/test_ml/test_create_model_basic/create_model_basic.sql +++ /dev/null @@ -1,3 +0,0 @@ -CREATE MODEL `my_project.my_dataset.my_model` -OPTIONS(model_type = 'LINEAR_REG', input_label_cols = ['label']) -AS SELECT * FROM my_table diff --git a/tests/unit/core/sql/snapshots/test_ml/test_create_model_hparam_tuning/create_model_hparam_tuning.sql b/tests/unit/core/sql/snapshots/test_ml/test_create_model_hparam_tuning/create_model_hparam_tuning.sql deleted file mode 100644 index c7ed32e54fc..00000000000 --- a/tests/unit/core/sql/snapshots/test_ml/test_create_model_hparam_tuning/create_model_hparam_tuning.sql +++ /dev/null @@ -1,3 +0,0 @@ -CREATE MODEL `my_model` -OPTIONS(model_type = 'LINEAR_REG', learn_rate = HPARAM_RANGE(0.0001, 1.0), optimizer = HPARAM_CANDIDATES(['ADAGRAD', 'SGD'])) -AS SELECT * FROM t diff --git a/tests/unit/core/sql/snapshots/test_ml/test_create_model_if_not_exists/create_model_if_not_exists.sql b/tests/unit/core/sql/snapshots/test_ml/test_create_model_if_not_exists/create_model_if_not_exists.sql deleted file mode 100644 index b67ea139673..00000000000 --- a/tests/unit/core/sql/snapshots/test_ml/test_create_model_if_not_exists/create_model_if_not_exists.sql +++ /dev/null @@ -1,3 +0,0 @@ -CREATE MODEL IF NOT EXISTS `my_model` -OPTIONS(model_type = 'KMEANS') -AS SELECT * FROM t diff --git a/tests/unit/core/sql/snapshots/test_ml/test_create_model_list_option/create_model_list_option.sql b/tests/unit/core/sql/snapshots/test_ml/test_create_model_list_option/create_model_list_option.sql deleted file mode 100644 index 723a4b037d8..00000000000 --- a/tests/unit/core/sql/snapshots/test_ml/test_create_model_list_option/create_model_list_option.sql +++ /dev/null @@ -1,3 +0,0 @@ -CREATE MODEL `my_model` -OPTIONS(hidden_units = [32, 16], dropout = 0.2) -AS SELECT * FROM t diff --git a/tests/unit/core/sql/snapshots/test_ml/test_create_model_remote/create_model_remote.sql b/tests/unit/core/sql/snapshots/test_ml/test_create_model_remote/create_model_remote.sql deleted file mode 100644 index 878afe0823b..00000000000 --- a/tests/unit/core/sql/snapshots/test_ml/test_create_model_remote/create_model_remote.sql +++ /dev/null @@ -1,5 +0,0 @@ -CREATE MODEL `my_remote_model` -INPUT (prompt STRING) -OUTPUT (content STRING) -REMOTE WITH CONNECTION `my_project.us.my_connection` -OPTIONS(endpoint = 'gemini-pro') diff --git a/tests/unit/core/sql/snapshots/test_ml/test_create_model_remote_default/create_model_remote_default.sql b/tests/unit/core/sql/snapshots/test_ml/test_create_model_remote_default/create_model_remote_default.sql deleted file mode 100644 index 9bbea44259b..00000000000 --- a/tests/unit/core/sql/snapshots/test_ml/test_create_model_remote_default/create_model_remote_default.sql +++ /dev/null @@ -1,3 +0,0 @@ -CREATE MODEL `my_remote_model` -REMOTE WITH CONNECTION DEFAULT -OPTIONS(endpoint = 'gemini-pro') diff --git a/tests/unit/core/sql/snapshots/test_ml/test_create_model_replace/create_model_replace.sql b/tests/unit/core/sql/snapshots/test_ml/test_create_model_replace/create_model_replace.sql deleted file mode 100644 index 7fe9d492da3..00000000000 --- a/tests/unit/core/sql/snapshots/test_ml/test_create_model_replace/create_model_replace.sql +++ /dev/null @@ -1,3 +0,0 @@ -CREATE OR REPLACE MODEL `my_model` -OPTIONS(model_type = 'LOGISTIC_REG') -AS SELECT * FROM t diff --git a/tests/unit/core/sql/snapshots/test_ml/test_create_model_training_data_and_holiday/create_model_training_data_and_holiday.sql b/tests/unit/core/sql/snapshots/test_ml/test_create_model_training_data_and_holiday/create_model_training_data_and_holiday.sql deleted file mode 100644 index da7b6ba6724..00000000000 --- a/tests/unit/core/sql/snapshots/test_ml/test_create_model_training_data_and_holiday/create_model_training_data_and_holiday.sql +++ /dev/null @@ -1,5 +0,0 @@ -CREATE MODEL `my_arima_model` -OPTIONS(model_type = 'ARIMA_PLUS') -AS ( - training_data AS (SELECT * FROM sales), custom_holiday AS (SELECT * FROM holidays) -) \ No newline at end of file diff --git a/tests/unit/core/sql/snapshots/test_ml/test_create_model_transform/create_model_transform.sql b/tests/unit/core/sql/snapshots/test_ml/test_create_model_transform/create_model_transform.sql deleted file mode 100644 index e460400be23..00000000000 --- a/tests/unit/core/sql/snapshots/test_ml/test_create_model_transform/create_model_transform.sql +++ /dev/null @@ -1,4 +0,0 @@ -CREATE MODEL `my_model` -TRANSFORM (ML.STANDARD_SCALER(c1) OVER() AS c1_scaled, c2) -OPTIONS(model_type = 'LINEAR_REG') -AS SELECT c1, c2, label FROM t diff --git a/tests/unit/core/sql/snapshots/test_ml/test_evaluate_model_basic/evaluate_model_basic.sql b/tests/unit/core/sql/snapshots/test_ml/test_evaluate_model_basic/evaluate_model_basic.sql deleted file mode 100644 index 5889e342e4d..00000000000 --- a/tests/unit/core/sql/snapshots/test_ml/test_evaluate_model_basic/evaluate_model_basic.sql +++ /dev/null @@ -1 +0,0 @@ -SELECT * FROM ML.EVALUATE(MODEL `my_project.my_dataset.my_model`) diff --git a/tests/unit/core/sql/snapshots/test_ml/test_evaluate_model_with_options/evaluate_model_with_options.sql b/tests/unit/core/sql/snapshots/test_ml/test_evaluate_model_with_options/evaluate_model_with_options.sql deleted file mode 100644 index cdb66bbf0e1..00000000000 --- a/tests/unit/core/sql/snapshots/test_ml/test_evaluate_model_with_options/evaluate_model_with_options.sql +++ /dev/null @@ -1 +0,0 @@ -SELECT * FROM ML.EVALUATE(MODEL `my_model`, STRUCT(FALSE AS `perform_aggregation`, 10 AS `horizon`, 0.95 AS `confidence_level`)) diff --git a/tests/unit/core/sql/snapshots/test_ml/test_evaluate_model_with_table/evaluate_model_with_table.sql b/tests/unit/core/sql/snapshots/test_ml/test_evaluate_model_with_table/evaluate_model_with_table.sql deleted file mode 100644 index e1d4fdecd62..00000000000 --- a/tests/unit/core/sql/snapshots/test_ml/test_evaluate_model_with_table/evaluate_model_with_table.sql +++ /dev/null @@ -1 +0,0 @@ -SELECT * FROM ML.EVALUATE(MODEL `my_project.my_dataset.my_model`, (SELECT * FROM evaluation_data)) diff --git a/tests/unit/core/sql/snapshots/test_ml/test_explain_predict_model_basic/explain_predict_model_basic.sql b/tests/unit/core/sql/snapshots/test_ml/test_explain_predict_model_basic/explain_predict_model_basic.sql deleted file mode 100644 index 1d755b34ddd..00000000000 --- a/tests/unit/core/sql/snapshots/test_ml/test_explain_predict_model_basic/explain_predict_model_basic.sql +++ /dev/null @@ -1 +0,0 @@ -SELECT * FROM ML.EXPLAIN_PREDICT(MODEL `my_project.my_dataset.my_model`, (SELECT * FROM new_data)) diff --git a/tests/unit/core/sql/snapshots/test_ml/test_explain_predict_model_with_options/explain_predict_model_with_options.sql b/tests/unit/core/sql/snapshots/test_ml/test_explain_predict_model_with_options/explain_predict_model_with_options.sql deleted file mode 100644 index 7569463ea2d..00000000000 --- a/tests/unit/core/sql/snapshots/test_ml/test_explain_predict_model_with_options/explain_predict_model_with_options.sql +++ /dev/null @@ -1 +0,0 @@ -SELECT * FROM ML.EXPLAIN_PREDICT(MODEL `my_model`, (SELECT * FROM new_data), STRUCT(5 AS `top_k_features`)) diff --git a/tests/unit/core/sql/snapshots/test_ml/test_generate_embedding_model_basic/generate_embedding_model_basic.sql b/tests/unit/core/sql/snapshots/test_ml/test_generate_embedding_model_basic/generate_embedding_model_basic.sql deleted file mode 100644 index 7294f1655f7..00000000000 --- a/tests/unit/core/sql/snapshots/test_ml/test_generate_embedding_model_basic/generate_embedding_model_basic.sql +++ /dev/null @@ -1 +0,0 @@ -SELECT * FROM ML.GENERATE_EMBEDDING(MODEL `my_project.my_dataset.my_model`, (SELECT * FROM new_data)) diff --git a/tests/unit/core/sql/snapshots/test_ml/test_generate_embedding_model_with_options/generate_embedding_model_with_options.sql b/tests/unit/core/sql/snapshots/test_ml/test_generate_embedding_model_with_options/generate_embedding_model_with_options.sql deleted file mode 100644 index 3be957079cf..00000000000 --- a/tests/unit/core/sql/snapshots/test_ml/test_generate_embedding_model_with_options/generate_embedding_model_with_options.sql +++ /dev/null @@ -1,5 +0,0 @@ -SELECT * FROM ML.GENERATE_EMBEDDING(MODEL `my_project.my_dataset.my_model`, (SELECT * FROM new_data), STRUCT( - TRUE AS `flatten_json_output`, - 'RETRIEVAL_DOCUMENT' AS `task_type`, - 256 AS `output_dimensionality` -)) diff --git a/tests/unit/core/sql/snapshots/test_ml/test_generate_text_model_basic/generate_text_model_basic.sql b/tests/unit/core/sql/snapshots/test_ml/test_generate_text_model_basic/generate_text_model_basic.sql deleted file mode 100644 index 9d986876448..00000000000 --- a/tests/unit/core/sql/snapshots/test_ml/test_generate_text_model_basic/generate_text_model_basic.sql +++ /dev/null @@ -1 +0,0 @@ -SELECT * FROM ML.GENERATE_TEXT(MODEL `my_project.my_dataset.my_model`, (SELECT * FROM new_data)) diff --git a/tests/unit/core/sql/snapshots/test_ml/test_generate_text_model_with_options/generate_text_model_with_options.sql b/tests/unit/core/sql/snapshots/test_ml/test_generate_text_model_with_options/generate_text_model_with_options.sql deleted file mode 100644 index 0ea26747287..00000000000 --- a/tests/unit/core/sql/snapshots/test_ml/test_generate_text_model_with_options/generate_text_model_with_options.sql +++ /dev/null @@ -1,10 +0,0 @@ -SELECT * FROM ML.GENERATE_TEXT(MODEL `my_project.my_dataset.my_model`, (SELECT * FROM new_data), STRUCT( - 0.5 AS `temperature`, - 128 AS `max_output_tokens`, - 20 AS `top_k`, - 0.9 AS `top_p`, - TRUE AS `flatten_json_output`, - ['a', 'b'] AS `stop_sequences`, - TRUE AS `ground_with_google_search`, - 'TYPE' AS `request_type` -)) diff --git a/tests/unit/core/sql/snapshots/test_ml/test_get_insights_model_basic/get_insights_model_basic.sql b/tests/unit/core/sql/snapshots/test_ml/test_get_insights_model_basic/get_insights_model_basic.sql deleted file mode 100644 index a3f2680c179..00000000000 --- a/tests/unit/core/sql/snapshots/test_ml/test_get_insights_model_basic/get_insights_model_basic.sql +++ /dev/null @@ -1 +0,0 @@ -SELECT * FROM ML.GET_INSIGHTS(MODEL `my_project.my_dataset.my_model`) diff --git a/tests/unit/core/sql/snapshots/test_ml/test_global_explain_model_basic/global_explain_model_basic.sql b/tests/unit/core/sql/snapshots/test_ml/test_global_explain_model_basic/global_explain_model_basic.sql deleted file mode 100644 index 4fc8250dab2..00000000000 --- a/tests/unit/core/sql/snapshots/test_ml/test_global_explain_model_basic/global_explain_model_basic.sql +++ /dev/null @@ -1 +0,0 @@ -SELECT * FROM ML.GLOBAL_EXPLAIN(MODEL `my_project.my_dataset.my_model`) diff --git a/tests/unit/core/sql/snapshots/test_ml/test_global_explain_model_with_options/global_explain_model_with_options.sql b/tests/unit/core/sql/snapshots/test_ml/test_global_explain_model_with_options/global_explain_model_with_options.sql deleted file mode 100644 index 396648aa1db..00000000000 --- a/tests/unit/core/sql/snapshots/test_ml/test_global_explain_model_with_options/global_explain_model_with_options.sql +++ /dev/null @@ -1 +0,0 @@ -SELECT * FROM ML.GLOBAL_EXPLAIN(MODEL `my_model`, STRUCT(TRUE AS `class_level_explain`)) diff --git a/tests/unit/core/sql/snapshots/test_ml/test_predict_model_basic/predict_model_basic.sql b/tests/unit/core/sql/snapshots/test_ml/test_predict_model_basic/predict_model_basic.sql deleted file mode 100644 index a1ac0b2b459..00000000000 --- a/tests/unit/core/sql/snapshots/test_ml/test_predict_model_basic/predict_model_basic.sql +++ /dev/null @@ -1 +0,0 @@ -SELECT * FROM ML.PREDICT(MODEL `my_project.my_dataset.my_model`, (SELECT * FROM new_data)) diff --git a/tests/unit/core/sql/snapshots/test_ml/test_predict_model_with_options/predict_model_with_options.sql b/tests/unit/core/sql/snapshots/test_ml/test_predict_model_with_options/predict_model_with_options.sql deleted file mode 100644 index e19f39eebba..00000000000 --- a/tests/unit/core/sql/snapshots/test_ml/test_predict_model_with_options/predict_model_with_options.sql +++ /dev/null @@ -1 +0,0 @@ -SELECT * FROM ML.PREDICT(MODEL `my_model`, (SELECT * FROM new_data), STRUCT(TRUE AS `keep_original_columns`)) diff --git a/tests/unit/core/sql/snapshots/test_ml/test_transform_model_basic/transform_model_basic.sql b/tests/unit/core/sql/snapshots/test_ml/test_transform_model_basic/transform_model_basic.sql deleted file mode 100644 index e6cedc16477..00000000000 --- a/tests/unit/core/sql/snapshots/test_ml/test_transform_model_basic/transform_model_basic.sql +++ /dev/null @@ -1 +0,0 @@ -SELECT * FROM ML.TRANSFORM(MODEL `my_project.my_dataset.my_model`, (SELECT * FROM new_data)) diff --git a/tests/unit/core/sql/test_ml.py b/tests/unit/core/sql/test_ml.py deleted file mode 100644 index a03d8cd805a..00000000000 --- a/tests/unit/core/sql/test_ml.py +++ /dev/null @@ -1,243 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import pytest - -import bigframes.bigquery as bbq -import bigframes.core.sql.ml - -pytest.importorskip("pytest_snapshot") - - -def test_create_model_basic(snapshot): - sql = bigframes.core.sql.ml.create_model_ddl( - model_name="my_project.my_dataset.my_model", - options={"model_type": "LINEAR_REG", "input_label_cols": ["label"]}, - training_data="SELECT * FROM my_table", - ) - snapshot.assert_match(sql, "create_model_basic.sql") - - -def test_create_model_replace(snapshot): - sql = bigframes.core.sql.ml.create_model_ddl( - model_name="my_model", - replace=True, - options={"model_type": "LOGISTIC_REG"}, - training_data="SELECT * FROM t", - ) - snapshot.assert_match(sql, "create_model_replace.sql") - - -def test_create_model_if_not_exists(snapshot): - sql = bigframes.core.sql.ml.create_model_ddl( - model_name="my_model", - if_not_exists=True, - options={"model_type": "KMEANS"}, - training_data="SELECT * FROM t", - ) - snapshot.assert_match(sql, "create_model_if_not_exists.sql") - - -def test_create_model_transform(snapshot): - sql = bigframes.core.sql.ml.create_model_ddl( - model_name="my_model", - transform=["ML.STANDARD_SCALER(c1) OVER() AS c1_scaled", "c2"], - options={"model_type": "LINEAR_REG"}, - training_data="SELECT c1, c2, label FROM t", - ) - snapshot.assert_match(sql, "create_model_transform.sql") - - -def test_create_model_remote(snapshot): - sql = bigframes.core.sql.ml.create_model_ddl( - model_name="my_remote_model", - connection_name="my_project.us.my_connection", - options={"endpoint": "gemini-pro"}, - input_schema={"prompt": "STRING"}, - output_schema={"content": "STRING"}, - ) - snapshot.assert_match(sql, "create_model_remote.sql") - - -def test_create_model_remote_default(snapshot): - sql = bigframes.core.sql.ml.create_model_ddl( - model_name="my_remote_model", - connection_name="DEFAULT", - options={"endpoint": "gemini-pro"}, - ) - snapshot.assert_match(sql, "create_model_remote_default.sql") - - -def test_create_model_training_data_and_holiday(snapshot): - sql = bigframes.core.sql.ml.create_model_ddl( - model_name="my_arima_model", - options={"model_type": "ARIMA_PLUS"}, - training_data="SELECT * FROM sales", - custom_holiday="SELECT * FROM holidays", - ) - snapshot.assert_match(sql, "create_model_training_data_and_holiday.sql") - - -def test_create_model_list_option(snapshot): - sql = bigframes.core.sql.ml.create_model_ddl( - model_name="my_model", - options={"hidden_units": [32, 16], "dropout": 0.2}, - training_data="SELECT * FROM t", - ) - snapshot.assert_match(sql, "create_model_list_option.sql") - - -def test_create_model_hparam_tuning(snapshot): - sql = bigframes.core.sql.ml.create_model_ddl( - model_name="my_model", - options={ - "model_type": "LINEAR_REG", - "learn_rate": bbq.hparam_range(0.0001, 1.0), - "optimizer": bbq.hparam_candidates(["ADAGRAD", "SGD"]), - }, - training_data="SELECT * FROM t", - ) - snapshot.assert_match(sql, "create_model_hparam_tuning.sql") - - -def test_evaluate_model_basic(snapshot): - sql = bigframes.core.sql.ml.evaluate( - model_name="my_project.my_dataset.my_model", - ) - snapshot.assert_match(sql, "evaluate_model_basic.sql") - - -def test_evaluate_model_with_table(snapshot): - sql = bigframes.core.sql.ml.evaluate( - model_name="my_project.my_dataset.my_model", - table="SELECT * FROM evaluation_data", - ) - snapshot.assert_match(sql, "evaluate_model_with_table.sql") - - -def test_evaluate_model_with_options(snapshot): - sql = bigframes.core.sql.ml.evaluate( - model_name="my_model", - perform_aggregation=False, - horizon=10, - confidence_level=0.95, - ) - snapshot.assert_match(sql, "evaluate_model_with_options.sql") - - -def test_predict_model_basic(snapshot): - sql = bigframes.core.sql.ml.predict( - model_name="my_project.my_dataset.my_model", - table="SELECT * FROM new_data", - ) - snapshot.assert_match(sql, "predict_model_basic.sql") - - -def test_predict_model_with_options(snapshot): - sql = bigframes.core.sql.ml.predict( - model_name="my_model", - table="SELECT * FROM new_data", - keep_original_columns=True, - ) - snapshot.assert_match(sql, "predict_model_with_options.sql") - - -def test_explain_predict_model_basic(snapshot): - sql = bigframes.core.sql.ml.explain_predict( - model_name="my_project.my_dataset.my_model", - table="SELECT * FROM new_data", - ) - snapshot.assert_match(sql, "explain_predict_model_basic.sql") - - -def test_explain_predict_model_with_options(snapshot): - sql = bigframes.core.sql.ml.explain_predict( - model_name="my_model", - table="SELECT * FROM new_data", - top_k_features=5, - ) - snapshot.assert_match(sql, "explain_predict_model_with_options.sql") - - -def test_global_explain_model_basic(snapshot): - sql = bigframes.core.sql.ml.global_explain( - model_name="my_project.my_dataset.my_model", - ) - snapshot.assert_match(sql, "global_explain_model_basic.sql") - - -def test_global_explain_model_with_options(snapshot): - sql = bigframes.core.sql.ml.global_explain( - model_name="my_model", - class_level_explain=True, - ) - snapshot.assert_match(sql, "global_explain_model_with_options.sql") - - -def test_transform_model_basic(snapshot): - sql = bigframes.core.sql.ml.transform( - model_name="my_project.my_dataset.my_model", - table="SELECT * FROM new_data", - ) - snapshot.assert_match(sql, "transform_model_basic.sql") - - -def test_generate_text_model_basic(snapshot): - sql = bigframes.core.sql.ml.generate_text( - model_name="my_project.my_dataset.my_model", - table="SELECT * FROM new_data", - ) - snapshot.assert_match(sql, "generate_text_model_basic.sql") - - -def test_generate_text_model_with_options(snapshot): - sql = bigframes.core.sql.ml.generate_text( - model_name="my_project.my_dataset.my_model", - table="SELECT * FROM new_data", - temperature=0.5, - max_output_tokens=128, - top_k=20, - top_p=0.9, - flatten_json_output=True, - stop_sequences=["a", "b"], - ground_with_google_search=True, - request_type="TYPE", - ) - snapshot.assert_match(sql, "generate_text_model_with_options.sql") - - -def test_get_insights_model_basic(snapshot): - sql = bigframes.core.sql.ml.get_insights( - model_name="my_project.my_dataset.my_model", - ) - snapshot.assert_match(sql, "get_insights_model_basic.sql") - - -def test_generate_embedding_model_basic(snapshot): - sql = bigframes.core.sql.ml.generate_embedding( - model_name="my_project.my_dataset.my_model", - table="SELECT * FROM new_data", - ) - snapshot.assert_match(sql, "generate_embedding_model_basic.sql") - - -def test_generate_embedding_model_with_options(snapshot): - sql = bigframes.core.sql.ml.generate_embedding( - model_name="my_project.my_dataset.my_model", - table="SELECT * FROM new_data", - flatten_json_output=True, - task_type="RETRIEVAL_DOCUMENT", - output_dimensionality=256, - ) - snapshot.assert_match(sql, "generate_embedding_model_with_options.sql") diff --git a/tests/unit/core/test_bf_utils.py b/tests/unit/core/test_bf_utils.py index 6fb796329f5..fc34f35d9c2 100644 --- a/tests/unit/core/test_bf_utils.py +++ b/tests/unit/core/test_bf_utils.py @@ -12,12 +12,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -import datetime - -import numpy as np -import pandas as pd -import pytest - from bigframes.core import utils @@ -31,8 +25,8 @@ def test_get_standardized_ids_columns(): "0", utils.UNNAMED_COLUMN_ID, "duplicate", - "duplicate_1", - "with space", + "duplicate.1", + "with_space", ] assert idx_ids == [] @@ -41,15 +35,15 @@ def test_get_standardized_ids_indexes(): col_labels = ["duplicate"] idx_labels = ["string", 0, None, "duplicate", "duplicate", "with space"] - col_ids, idx_ids = utils.get_standardized_ids(col_labels, idx_labels, strict=True) + col_ids, idx_ids = utils.get_standardized_ids(col_labels, idx_labels) - assert col_ids == ["duplicate_2"] + assert col_ids == ["duplicate.2"] assert idx_ids == [ "string", - "_0", + "0", utils.UNNAMED_INDEX_ID, "duplicate", - "duplicate_1", + "duplicate.1", "with_space", ] @@ -59,16 +53,4 @@ def test_get_standardized_ids_tuple(): col_ids, _ = utils.get_standardized_ids(col_labels) - assert col_ids == ["_'foo'_ 1_", "_'foo'_ 2_", "_'bar'_ 1_"] - - -@pytest.mark.parametrize( - "input", - [ - datetime.timedelta(days=2, hours=3, seconds=4, milliseconds=5, microseconds=6), - pd.Timedelta("2d3h4s5ms6us"), - np.timedelta64(pd.Timedelta("2d3h4s5ms6us")), - ], -) -def test_timedelta_to_micros(input): - assert utils.timedelta_to_micros(input) == 183604005006 + assert col_ids == ["('foo',_1)", "('foo',_2)", "('bar',_1)"] diff --git a/tests/unit/core/test_blocks.py b/tests/unit/core/test_blocks.py index 7c06bedfd3d..86715d090cb 100644 --- a/tests/unit/core/test_blocks.py +++ b/tests/unit/core/test_blocks.py @@ -12,15 +12,11 @@ # See the License for the specific language governing permissions and # limitations under the License. -from unittest import mock - import pandas import pandas.testing import pytest -import bigframes import bigframes.core.blocks as blocks -import bigframes.session.bq_caching_executor @pytest.mark.parametrize( @@ -78,23 +74,9 @@ ) def test_block_from_local(data): expected = pandas.DataFrame(data) - mock_session = mock.create_autospec(spec=bigframes.Session) - mock_executor = mock.create_autospec( - spec=bigframes.session.bq_caching_executor.BigQueryCachingExecutor - ) - - # hard-coded the returned dimension of the session for that each of the test case contains 3 rows. - mock_session._executor = mock_executor - block = blocks.Block.from_local(pandas.DataFrame(data), mock_session) + block = blocks.block_from_local(data) pandas.testing.assert_index_equal(block.column_labels, expected.columns) - assert tuple(block.index.names) == tuple(expected.index.names) - - -def test_block_compute_dry_run__raises_error_when_sampling_is_enabled(): - mock_session = mock.create_autospec(spec=bigframes.Session) - block = blocks.Block.from_local(pandas.DataFrame(), mock_session) - - with pytest.raises(NotImplementedError): - block._compute_dry_run(sampling_method="UNIFORM") + assert tuple(block.index_labels) == tuple(expected.index.names) + assert block.shape == expected.shape diff --git a/tests/unit/core/test_bytecode.py b/tests/unit/core/test_bytecode.py deleted file mode 100644 index 036e3f00e8f..00000000000 --- a/tests/unit/core/test_bytecode.py +++ /dev/null @@ -1,82 +0,0 @@ -# Copyright 2026 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 math - -import pytest - -import bigframes.core.expression as ex -import bigframes.operations as ops -from bigframes.core.bytecode import py_to_expression - - -def test_py_to_expression_simple_arithmetic(): - func = lambda x: x + 1 - expr = py_to_expression(func) - assert expr is not None - - expected = ops.add_op.as_expr(ex.free_var("x"), ex.const(1)) - assert expr == expected - - -def test_py_to_expression_math_function(): - func = lambda x: math.sin(x) - expr = py_to_expression(func) - assert expr is not None - - expected = ops.numeric_ops.sin_op.as_expr(ex.free_var("x")) - assert expr == expected - - -def test_py_to_expression_negation(): - func = lambda x: -x - expr = py_to_expression(func) - assert expr is not None - - expected = ops.numeric_ops.neg_op.as_expr(ex.free_var("x")) - assert expr == expected - - -def test_py_to_expression_comparison(): - func = lambda x, y: x == y - expr = py_to_expression(func) - assert expr is not None - - expected = ops.comparison_ops.eq_op.as_expr(ex.free_var("x"), ex.free_var("y")) - assert expr == expected - - -def test_py_to_expression_unsupported(): - # Control flow or unsupported structures should return None - def func_with_loop(x): - res = 0 - for val in range(int(x)): - res += val - return res - - with pytest.raises(ValueError): - py_to_expression(func_with_loop) - - -global_none_val = None - - -def test_py_to_expression_global_none(): - # Test resolving a global variable explicitly set to None - func = lambda x: x == global_none_val - expr = py_to_expression(func) - assert expr is not None - - expected = ops.comparison_ops.eq_op.as_expr(ex.free_var("x"), ex.const(None)) - assert expr == expected diff --git a/tests/unit/core/test_expression.py b/tests/unit/core/test_expression.py deleted file mode 100644 index 68fc3a2b540..00000000000 --- a/tests/unit/core/test_expression.py +++ /dev/null @@ -1,118 +0,0 @@ -# Copyright 2023 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 typing - -import pytest - -import bigframes.core.expression as ex -import bigframes.core.identifiers as ids -import bigframes.dtypes as dtypes -import bigframes.operations as ops -from bigframes.core import field - - -def test_simple_expression_dtype(): - expression = ops.add_op.as_expr("a", "b") - field_bindings = _create_field_bindings( - {"a": dtypes.INT_DTYPE, "b": dtypes.INT_DTYPE} - ) - - result = ex.bind_schema_fields(expression, field_bindings) - - _assert_output_type(result, dtypes.INT_DTYPE) - - -def test_nested_expression_dtype(): - expression = ops.add_op.as_expr( - "a", ops.abs_op.as_expr(ops.sub_op.as_expr("b", ex.const(3.14))) - ) - field_bindings = _create_field_bindings( - {"a": dtypes.INT_DTYPE, "b": dtypes.INT_DTYPE} - ) - - result = ex.bind_schema_fields(expression, field_bindings) - - _assert_output_type(result, dtypes.FLOAT_DTYPE) - - -def test_where_op_dtype(): - expression = ops.where_op.as_expr(ex.const(3), ex.const(True), ex.const(None)) - - _assert_output_type(expression, dtypes.INT_DTYPE) - - -def test_astype_op_dtype(): - expression = ops.AsTypeOp(dtypes.INT_DTYPE).as_expr(ex.const(3.14159)) - - _assert_output_type(expression, dtypes.INT_DTYPE) - - -def test_deref_op_dtype_unavailable(): - expression = ex.deref("mycol") - - assert not expression.is_resolved - with pytest.raises(ValueError): - expression.output_type - - -def test_deref_op_dtype_resolution(): - expression = ex.deref("mycol") - field_bindings = _create_field_bindings({"mycol": dtypes.STRING_DTYPE}) - - result = ex.bind_schema_fields(expression, field_bindings) - - _assert_output_type(result, dtypes.STRING_DTYPE) - - -def test_field_ref_expr_dtype_resolution_short_circuit(): - expression = ex.ResolvedDerefOp( - id=ids.ColumnId("mycol"), dtype=dtypes.INT_DTYPE, is_nullable=True - ) - field_bindings = _create_field_bindings({"anotherCol": dtypes.STRING_DTYPE}) - - result = ex.bind_schema_fields(expression, field_bindings) - - _assert_output_type(result, dtypes.INT_DTYPE) - - -def test_nested_expression_dtypes_are_cached(): - expression = ops.add_op.as_expr(ex.deref("left_col"), ex.deref("right_col")) - field_bindings = _create_field_bindings( - { - "right_col": dtypes.INT_DTYPE, - "left_col": dtypes.FLOAT_DTYPE, - } - ) - - result = ex.bind_schema_fields(expression, field_bindings) - - _assert_output_type(result, dtypes.FLOAT_DTYPE) - assert isinstance(result, ex.OpExpression) - _assert_output_type(result.inputs[0], dtypes.FLOAT_DTYPE) - _assert_output_type(result.inputs[1], dtypes.INT_DTYPE) - - -def _create_field_bindings( - col_dtypes: typing.Dict[str, dtypes.Dtype], -) -> typing.Dict[ids.ColumnId, field.Field]: - return { - ids.ColumnId(col): field.Field(ids.ColumnId(col), dtype) - for col, dtype in col_dtypes.items() - } - - -def _assert_output_type(expr: ex.Expression, dtype: dtypes.Dtype): - assert expr.is_resolved - assert expr.output_type == dtype diff --git a/tests/unit/core/test_googlesql.py b/tests/unit/core/test_googlesql.py deleted file mode 100644 index e83391e4b33..00000000000 --- a/tests/unit/core/test_googlesql.py +++ /dev/null @@ -1,268 +0,0 @@ -# Copyright 2026 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import unittest.mock as mock - -import pandas as pd - -import bigframes.core.col as col -import bigframes.core.expression as ex -import bigframes.core.global_session -import bigframes.core.googlesql as core_googlesql -import bigframes.series as series -from bigframes.operations import googlesql -from bigframes.testing import mocks - -# Define a test op -_TEST_OP = googlesql.GoogleSqlScalarOp( - "TEST_OP", - args=(googlesql.ArgSpec(), googlesql.ArgSpec()), - signature=lambda *args: None, -) - - -def test_apply_googlesql_scalar_op_expressions(): - # Only expressions - result = core_googlesql.apply_googlesql_scalar_op( - _TEST_OP, - col.col("a"), - col.col("b"), - ) - assert isinstance(result, col.Expression) - - -def test_apply_googlesql_scalar_op_pandas_series_global_session(monkeypatch): - # Setup mock session - session = mocks.create_bigquery_session() - monkeypatch.setattr(bigframes.core.global_session, "_global_session", session) - bigframes.options.bigquery._session_started = True - - # Create a real-ish Series to return from read_pandas - df = mocks.create_dataframe(monkeypatch, session=session, data={"col": [1, 2, 3]}) - bf_series = df["col"] - - # Mock read_pandas on the session - mock_read_pandas = mock.MagicMock(return_value=bf_series) - session.read_pandas = mock_read_pandas # type: ignore - - # Mock _apply_nary_op on Series class to avoid real compilation/execution - mock_apply_nary_op = mock.MagicMock(return_value=bf_series) - monkeypatch.setattr(series.Series, "_apply_nary_op", mock_apply_nary_op) - - pd_series = pd.Series([1, 2, 3]) - - # Call the function with a pandas Series and a literal - result = core_googlesql.apply_googlesql_scalar_op(_TEST_OP, pd_series, 42) - - # Verify read_pandas was called on the global session - mock_read_pandas.assert_called_once_with(pd_series) - - # Verify _apply_nary_op was called on the converted series - mock_apply_nary_op.assert_called_once() - # First arg to _apply_nary_op is the op, second is the processed_args - assert mock_apply_nary_op.call_args[0][0] == _TEST_OP - # processed_args should contain the converted bf_series and the literal 42 - processed_args = mock_apply_nary_op.call_args[0][1] - assert processed_args[0] is bf_series - assert processed_args[1] == 42 - - # Verify result is a Series - assert isinstance(result, series.Series) - - -def test_apply_googlesql_scalar_op_pandas_series_with_bf_series(monkeypatch): - # Setup mock session 1 (global) and session 2 (associated with bf_series) - global_session = mocks.create_bigquery_session(session_id="global") - monkeypatch.setattr( - bigframes.core.global_session, "_global_session", global_session - ) - bigframes.options.bigquery._session_started = True - - bf_session = mocks.create_bigquery_session(session_id="bf_session") - - # Create a bf_series associated with bf_session - df = mocks.create_dataframe( - monkeypatch, session=bf_session, data={"col": [1, 2, 3]} - ) - bf_series = df["col"] - - assert bf_series._session == bf_session - - # Mock read_pandas on both sessions - mock_global_read_pandas = mock.MagicMock() - global_session.read_pandas = mock_global_read_pandas # type: ignore - - mock_bf_read_pandas = mock.MagicMock(return_value=bf_series) - bf_session.read_pandas = mock_bf_read_pandas # type: ignore - - # Mock _apply_nary_op - mock_apply_nary_op = mock.MagicMock(return_value=bf_series) - monkeypatch.setattr(series.Series, "_apply_nary_op", mock_apply_nary_op) - - pd_series = pd.Series([1, 2, 3]) - - # Call with both pandas Series and BigFrames Series - result = core_googlesql.apply_googlesql_scalar_op(_TEST_OP, pd_series, bf_series) - - # Verify read_pandas was called on bf_session, NOT global_session - mock_bf_read_pandas.assert_called_once_with(pd_series) - mock_global_read_pandas.assert_not_called() - - # Verify _apply_nary_op was called - mock_apply_nary_op.assert_called_once() - processed_args = mock_apply_nary_op.call_args[0][1] - # Both arguments to the op should now be BigFrames Series - assert processed_args[0] is bf_series - assert processed_args[1] is bf_series - - assert isinstance(result, series.Series) - - -def test_apply_googlesql_scalar_op_mixed_args(monkeypatch): - session = mocks.create_bigquery_session() - monkeypatch.setattr(bigframes.core.global_session, "_global_session", session) - bigframes.options.bigquery._session_started = True - - df = mocks.create_dataframe(monkeypatch, session=session, data={"col": [1, 2, 3]}) - bf_series = df["col"] - - mock_read_pandas = mock.MagicMock(return_value=bf_series) - session.read_pandas = mock_read_pandas # type: ignore - - mock_apply_nary_op = mock.MagicMock(return_value=bf_series) - monkeypatch.setattr(series.Series, "_apply_nary_op", mock_apply_nary_op) - - pd_series = pd.Series([1, 2, 3]) - expr = col.Expression(ex.const(10)) - - # Call with pandas Series, Expression, and Literal - result = core_googlesql.apply_googlesql_scalar_op(_TEST_OP, pd_series, expr, 42) - - # Verify pandas Series was converted - mock_read_pandas.assert_called_once_with(pd_series) - - # Verify _apply_nary_op was called - mock_apply_nary_op.assert_called_once() - processed_args = mock_apply_nary_op.call_args[0][1] - - # Processed args should be: - # 1. bf_series (converted from pd_series) - # 2. A new Series (projected from the expression onto bf_series' block) - # 3. Literal 42 - assert isinstance(processed_args[0], series.Series) - assert processed_args[0] is bf_series - - assert isinstance(processed_args[1], series.Series) - assert processed_args[1] is not bf_series - - assert processed_args[2] == 42 - - assert isinstance(result, series.Series) - - -def test_apply_googlesql_scalar_op_pandas_series_with_bf_dataframe(monkeypatch): - # Setup mock session 2 (associated with bf_dataframe) - bf_session = mocks.create_bigquery_session(session_id="bf_session") - - # Create a bf_dataframe associated with bf_session - bf_dataframe = mocks.create_dataframe( - monkeypatch, session=bf_session, data={"col": [1, 2, 3]} - ) - bf_series = bf_dataframe["col"] - - # Setup mock session 1 (global) AFTER creating the dataframe - global_session = mocks.create_bigquery_session(session_id="global") - monkeypatch.setattr( - bigframes.core.global_session, "_global_session", global_session - ) - bigframes.options.bigquery._session_started = True - - assert bf_dataframe._session == bf_session - - # Mock read_pandas on both sessions - mock_global_read_pandas = mock.MagicMock() - global_session.read_pandas = mock_global_read_pandas # type: ignore - - mock_bf_read_pandas = mock.MagicMock(return_value=bf_series) - bf_session.read_pandas = mock_bf_read_pandas # type: ignore - - # Mock _apply_nary_op - mock_apply_nary_op = mock.MagicMock(return_value=bf_series) - monkeypatch.setattr(series.Series, "_apply_nary_op", mock_apply_nary_op) - - pd_series = pd.Series([1, 2, 3]) - - # Call with pandas Series and BigFrames DataFrame - result = core_googlesql.apply_googlesql_scalar_op(_TEST_OP, pd_series, bf_dataframe) - - # Verify read_pandas was called on bf_session, NOT global_session - mock_bf_read_pandas.assert_called_once_with(pd_series) - mock_global_read_pandas.assert_not_called() - - # Verify _apply_nary_op was called - mock_apply_nary_op.assert_called_once() - processed_args = mock_apply_nary_op.call_args[0][1] - assert processed_args[0] is bf_series - assert processed_args[1] is bf_dataframe - - assert isinstance(result, series.Series) - - -def test_apply_googlesql_scalar_op_pandas_series_with_bf_index(monkeypatch): - # Setup mock session 2 (associated with bf_index) - bf_session = mocks.create_bigquery_session(session_id="bf_session") - - # Create a bf_dataframe associated with bf_session to get an index - bf_dataframe = mocks.create_dataframe( - monkeypatch, session=bf_session, data={"col": [1, 2, 3]} - ) - bf_index = bf_dataframe.index - bf_series = bf_dataframe["col"] - - # Setup mock session 1 (global) AFTER creating the dataframe - global_session = mocks.create_bigquery_session(session_id="global") - monkeypatch.setattr( - bigframes.core.global_session, "_global_session", global_session - ) - bigframes.options.bigquery._session_started = True - - assert bf_index._session == bf_session - - # Mock read_pandas on both sessions - mock_global_read_pandas = mock.MagicMock() - global_session.read_pandas = mock_global_read_pandas # type: ignore - - mock_bf_read_pandas = mock.MagicMock(return_value=bf_series) - bf_session.read_pandas = mock_bf_read_pandas # type: ignore - - # Mock _apply_nary_op - mock_apply_nary_op = mock.MagicMock(return_value=bf_series) - monkeypatch.setattr(series.Series, "_apply_nary_op", mock_apply_nary_op) - - pd_series = pd.Series([1, 2, 3]) - - # Call with pandas Series and BigFrames Index - result = core_googlesql.apply_googlesql_scalar_op(_TEST_OP, pd_series, bf_index) - - # Verify read_pandas was called on bf_session, NOT global_session - mock_bf_read_pandas.assert_called_once_with(pd_series) - mock_global_read_pandas.assert_not_called() - - # Verify _apply_nary_op was called - mock_apply_nary_op.assert_called_once() - processed_args = mock_apply_nary_op.call_args[0][1] - assert processed_args[0] is bf_series - assert processed_args[1] is bf_index - - assert isinstance(result, series.Series) diff --git a/tests/unit/core/test_groupby.py b/tests/unit/core/test_groupby.py deleted file mode 100644 index b23199da331..00000000000 --- a/tests/unit/core/test_groupby.py +++ /dev/null @@ -1,264 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import pandas as pd -import pandas.testing -import pytest - -import bigframes.core.utils as utils -import bigframes.pandas as bpd -import bigframes.testing.utils - -pytest.importorskip("polars") -pytest.importorskip("pandas", minversion="2.0.0") - - -def test_groupby_df_iter_by_key_singular(polars_session): - pd_df = pd.DataFrame({"colA": ["a", "a", "b", "c", "c"], "colB": [1, 2, 3, 4, 5]}) - bf_df = bpd.DataFrame(pd_df, session=polars_session) - - for bf_group, pd_group in zip(bf_df.groupby("colA"), pd_df.groupby("colA")): # type: ignore - bf_key, bf_group_df = bf_group - bf_result = bf_group_df.to_pandas() - pd_key, pd_result = pd_group - assert bf_key == pd_key - bigframes.testing.utils.assert_frame_equal( - bf_result, pd_result, check_dtype=False, check_index_type=False - ) - - -def test_groupby_df_iter_by_key_list(polars_session): - pd_df = pd.DataFrame({"colA": ["a", "a", "b", "c", "c"], "colB": [1, 2, 3, 4, 5]}) - bf_df = bpd.DataFrame(pd_df, session=polars_session) - - for bf_group, pd_group in zip(bf_df.groupby(["colA"]), pd_df.groupby(["colA"])): # type: ignore - bf_key, bf_group_df = bf_group - bf_result = bf_group_df.to_pandas() - pd_key, pd_result = pd_group - assert bf_key == pd_key - bigframes.testing.utils.assert_frame_equal( - bf_result, pd_result, check_dtype=False, check_index_type=False - ) - - -def test_groupby_df_iter_by_key_list_multiple(polars_session): - pd_df = pd.DataFrame( - { - "colA": ["a", "a", "b", "c", "c"], - "colB": [1, 2, 3, 4, 5], - "colC": [True, False, True, False, True], - } - ) - bf_df = bpd.DataFrame(pd_df, session=polars_session) - - for bf_group, pd_group in zip( # type: ignore - bf_df.groupby(["colA", "colB"]), pd_df.groupby(["colA", "colB"]) - ): - bf_key, bf_group_df = bf_group - bf_result = bf_group_df.to_pandas() - pd_key, pd_result = pd_group - assert bf_key == pd_key - bigframes.testing.utils.assert_frame_equal( - bf_result, pd_result, check_dtype=False, check_index_type=False - ) - - -def test_groupby_df_iter_by_level_singular(polars_session): - pd_df = pd.DataFrame( - {"colA": ["a", "a", "b", "c", "c"], "colB": [1, 2, 3, 4, 5]} - ).set_index("colA") - bf_df = bpd.DataFrame(pd_df, session=polars_session) - - for bf_group, pd_group in zip(bf_df.groupby(level=0), pd_df.groupby(level=0)): # type: ignore - bf_key, bf_group_df = bf_group - bf_result = bf_group_df.to_pandas() - pd_key, pd_result = pd_group - assert bf_key == pd_key - bigframes.testing.utils.assert_frame_equal( - bf_result, pd_result, check_dtype=False, check_index_type=False - ) - - -def test_groupby_df_iter_by_level_list_one_item(polars_session): - pd_df = pd.DataFrame( - {"colA": ["a", "a", "b", "c", "c"], "colB": [1, 2, 3, 4, 5]} - ).set_index("colA") - bf_df = bpd.DataFrame(pd_df, session=polars_session) - - for bf_group, pd_group in zip(bf_df.groupby(level=[0]), pd_df.groupby(level=[0])): # type: ignore - bf_key, bf_group_df = bf_group - bf_result = bf_group_df.to_pandas() - pd_key, pd_result = pd_group - - # In pandas 2.x, we get a warning from pandas: "Creating a Groupby - # object with a length-1 list-like level parameter will yield indexes - # as tuples in a future version. To keep indexes as scalars, create - # Groupby objects with a scalar level parameter instead. - if utils.is_list_like(pd_key): - assert bf_key == tuple(pd_key) - else: - assert bf_key == (pd_key,) - bigframes.testing.utils.assert_frame_equal( - bf_result, pd_result, check_dtype=False, check_index_type=False - ) - - -def test_groupby_df_iter_by_level_list_multiple(polars_session): - pd_df = pd.DataFrame( - { - "colA": ["a", "a", "b", "c", "c"], - "colB": [1, 2, 3, 4, 5], - "colC": [True, False, True, False, True], - } - ).set_index(["colA", "colB"]) - bf_df = bpd.DataFrame(pd_df, session=polars_session) - - for bf_group, pd_group in zip( # type: ignore - bf_df.groupby(level=[0, 1]), pd_df.groupby(level=[0, 1]) - ): - bf_key, bf_group_df = bf_group - bf_result = bf_group_df.to_pandas() - pd_key, pd_result = pd_group - assert bf_key == pd_key - bigframes.testing.utils.assert_frame_equal( - bf_result, pd_result, check_dtype=False, check_index_type=False - ) - - -def test_groupby_series_iter_by_level_singular(polars_session): - series_index = ["a", "a", "b"] - pd_series = pd.Series([1, 2, 3], index=series_index) - bf_series = bpd.Series(pd_series, session=polars_session) - bf_series.name = pd_series.name - - for bf_group, pd_group in zip( # type: ignore - bf_series.groupby(level=0), pd_series.groupby(level=0) - ): - bf_key, bf_group_series = bf_group - bf_result = bf_group_series.to_pandas() - pd_key, pd_result = pd_group - assert bf_key == pd_key - bigframes.testing.utils.assert_series_equal( - bf_result, pd_result, check_dtype=False, check_index_type=False - ) - - -def test_groupby_series_iter_by_level_list_one_item(polars_session): - series_index = ["a", "a", "b"] - pd_series = pd.Series([1, 2, 3], index=series_index) - bf_series = bpd.Series(pd_series, session=polars_session) - bf_series.name = pd_series.name - - for bf_group, pd_group in zip( # type: ignore - bf_series.groupby(level=[0]), pd_series.groupby(level=[0]) - ): - bf_key, bf_group_series = bf_group - bf_result = bf_group_series.to_pandas() - pd_key, pd_result = pd_group - - # In pandas 2.x, we get a warning from pandas: "Creating a Groupby - # object with a length-1 list-like level parameter will yield indexes - # as tuples in a future version. To keep indexes as scalars, create - # Groupby objects with a scalar level parameter instead. - if utils.is_list_like(pd_key): - assert bf_key == tuple(pd_key) - else: - assert bf_key == (pd_key,) - bigframes.testing.utils.assert_series_equal( - bf_result, pd_result, check_dtype=False, check_index_type=False - ) - - -def test_groupby_series_iter_by_level_list_multiple(polars_session): - pd_df = pd.DataFrame( - { - "colA": ["a", "a", "b", "c", "c"], - "colB": [1, 2, 3, 4, 5], - "colC": [True, False, True, False, True], - } - ).set_index(["colA", "colB"]) - pd_series = pd_df["colC"] - bf_df = bpd.DataFrame(pd_df, session=polars_session) - bf_series = bf_df["colC"] - - for bf_group, pd_group in zip( # type: ignore - bf_series.groupby(level=[0, 1]), pd_series.groupby(level=[0, 1]) - ): - bf_key, bf_group_df = bf_group - bf_result = bf_group_df.to_pandas() - pd_key, pd_result = pd_group - assert bf_key == pd_key - bigframes.testing.utils.assert_series_equal( - bf_result, pd_result, check_dtype=False, check_index_type=False - ) - - -def test_groupby_series_iter_by_series(polars_session): - pd_groups = pd.Series(["a", "a", "b"]) - bf_groups = bpd.Series(pd_groups, session=polars_session) - pd_series = pd.Series([1, 2, 3]) - bf_series = bpd.Series(pd_series, session=polars_session) - bf_series.name = pd_series.name - - for bf_group, pd_group in zip( # type: ignore - bf_series.groupby(bf_groups), pd_series.groupby(pd_groups) - ): - bf_key, bf_group_series = bf_group - bf_result = bf_group_series.to_pandas() - pd_key, pd_result = pd_group - assert bf_key == pd_key - bigframes.testing.utils.assert_series_equal( - bf_result, pd_result, check_dtype=False, check_index_type=False - ) - - -def test_groupby_series_iter_by_series_list_one_item(polars_session): - pd_groups = pd.Series(["a", "a", "b"]) - bf_groups = bpd.Series(pd_groups, session=polars_session) - pd_series = pd.Series([1, 2, 3]) - bf_series = bpd.Series(pd_series, session=polars_session) - bf_series.name = pd_series.name - - for bf_group, pd_group in zip( # type: ignore - bf_series.groupby([bf_groups]), pd_series.groupby([pd_groups]) - ): - bf_key, bf_group_series = bf_group - bf_result = bf_group_series.to_pandas() - pd_key, pd_result = pd_group - assert bf_key == pd_key - bigframes.testing.utils.assert_series_equal( - bf_result, pd_result, check_dtype=False, check_index_type=False - ) - - -def test_groupby_series_iter_by_series_list_multiple(polars_session): - pd_group_a = pd.Series(["a", "a", "b", "c", "c"]) - bf_group_a = bpd.Series(pd_group_a, session=polars_session) - pd_group_b = pd.Series([0, 0, 0, 1, 1]) - bf_group_b = bpd.Series(pd_group_b, session=polars_session) - pd_series = pd.Series([1, 2, 3, 4, 5]) - bf_series = bpd.Series(pd_series, session=polars_session) - bf_series.name = pd_series.name - - for bf_group, pd_group in zip( # type: ignore - bf_series.groupby([bf_group_a, bf_group_b]), - pd_series.groupby([pd_group_a, pd_group_b]), - ): - bf_key, bf_group_series = bf_group - bf_result = bf_group_series.to_pandas() - pd_key, pd_result = pd_group - assert bf_key == pd_key - bigframes.testing.utils.assert_series_equal( - bf_result, pd_result, check_dtype=False, check_index_type=False - ) diff --git a/tests/unit/core/test_guid.py b/tests/unit/core/test_guid.py deleted file mode 100644 index c7334848eed..00000000000 --- a/tests/unit/core/test_guid.py +++ /dev/null @@ -1,41 +0,0 @@ -import types -import unittest - -from bigframes.core.guid import SequentialUIDGenerator - - -class TestSequentialUIDGenerator(unittest.TestCase): - def test_get_uid_stream_returns_generator(self): - generator = SequentialUIDGenerator() - stream = generator.get_uid_stream("prefix") - self.assertIsInstance(stream, types.GeneratorType) - - def test_generator_yields_correct_uids(self): - generator = SequentialUIDGenerator() - stream = generator.get_uid_stream("prefix") - self.assertEqual(next(stream), "prefix0") - self.assertEqual(next(stream), "prefix1") - self.assertEqual(next(stream), "prefix2") - - def test_generator_yields_different_uids_for_different_prefixes(self): - generator = SequentialUIDGenerator() - stream_a = generator.get_uid_stream("prefixA") - stream_b = generator.get_uid_stream("prefixB") - self.assertEqual(next(stream_a), "prefixA0") - self.assertEqual(next(stream_b), "prefixB0") - self.assertEqual(next(stream_a), "prefixA1") - self.assertEqual(next(stream_b), "prefixB1") - - def test_multiple_calls_continue_generation(self): - generator = SequentialUIDGenerator() - stream1 = generator.get_uid_stream("prefix") - self.assertEqual(next(stream1), "prefix0") - self.assertEqual(next(stream1), "prefix1") - - stream2 = generator.get_uid_stream("prefix") - self.assertEqual(next(stream2), "prefix2") - self.assertEqual(next(stream2), "prefix3") - - -if __name__ == "__main__": - unittest.main() diff --git a/tests/unit/core/test_ibis_types.py b/tests/unit/core/test_ibis_types.py deleted file mode 100644 index 427e726179e..00000000000 --- a/tests/unit/core/test_ibis_types.py +++ /dev/null @@ -1,250 +0,0 @@ -# Copyright 2023 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_vendored.ibis.backends.bigquery.datatypes as ibis_bq_types -import bigframes_vendored.ibis.expr.datatypes as ibis_dtypes -import bigframes_vendored.ibis.expr.types as ibis_types -import geopandas as gpd # type: ignore -import numpy as np -import pandas as pd -import pyarrow as pa # type: ignore -import pytest - -import bigframes.core.compile.ibis_types -import bigframes.dtypes - - -@pytest.mark.parametrize( - ["ibis_dtype", "bigframes_dtype"], - [ - # TODO(bmil): Add ARRAY, INTERVAL, STRUCT to cover all the standard - # BigQuery data types as they appear in Ibis: - # https://cloud.google.com/bigquery/docs/reference/standard-sql/data-types - pytest.param( - ibis_dtypes.Decimal(precision=76, scale=38, nullable=True), - pd.ArrowDtype(pa.decimal256(76, 38)), - id="bignumeric", - ), - pytest.param(ibis_dtypes.boolean, pd.BooleanDtype(), id="bool"), - pytest.param(ibis_dtypes.binary, pd.ArrowDtype(pa.binary()), id="bytes"), - pytest.param(ibis_dtypes.date, pd.ArrowDtype(pa.date32()), id="date"), - pytest.param( - ibis_dtypes.Timestamp(), pd.ArrowDtype(pa.timestamp("us")), id="datetime" - ), - pytest.param(ibis_dtypes.float64, pd.Float64Dtype(), id="float"), - pytest.param( - ibis_dtypes.GeoSpatial(geotype="geography", srid=4326, nullable=True), - gpd.array.GeometryDtype(), - id="geography", - ), - pytest.param(ibis_dtypes.int8, pd.Int64Dtype(), id="int8-as-int64"), - pytest.param(ibis_dtypes.int64, pd.Int64Dtype(), id="int64"), - # TODO(tswast): custom dtype (or at least string dtype) for JSON objects - pytest.param( - ibis_dtypes.Decimal(precision=38, scale=9, nullable=True), - pd.ArrowDtype(pa.decimal128(38, 9)), - id="numeric", - ), - pytest.param( - ibis_dtypes.string, pd.StringDtype(storage="pyarrow"), id="string" - ), - pytest.param(ibis_dtypes.time, pd.ArrowDtype(pa.time64("us")), id="time"), - pytest.param( - ibis_dtypes.Timestamp(timezone="UTC"), - pd.ArrowDtype(pa.timestamp("us", tz="UTC")), # type: ignore - id="timestamp", - ), - ], -) -def test_ibis_dtype_converts(ibis_dtype, bigframes_dtype): - """Test all the Ibis data types needed to read BigQuery tables""" - result = bigframes.core.compile.ibis_types.ibis_dtype_to_bigframes_dtype(ibis_dtype) - assert result == bigframes_dtype - - -def test_ibis_timestamp_pst_raises_unexpected_datatype(): - """BigQuery timestamp only supports UTC time""" - with pytest.raises(ValueError, match="'PST'"): - bigframes.core.compile.ibis_types.ibis_dtype_to_bigframes_dtype( - ibis_dtypes.Timestamp(timezone="PST") - ) - - -def test_ibis_float32_raises_unexpected_datatype(): - """Other Ibis types not read from BigQuery are not expected""" - with pytest.raises(ValueError, match="Unexpected Ibis data type"): - bigframes.core.compile.ibis_types.ibis_dtype_to_bigframes_dtype( - ibis_dtypes.float32 - ) - - -IBIS_ARROW_DTYPES = ( - (ibis_dtypes.boolean, pa.bool_()), - (ibis_dtypes.date, pa.date32()), - (ibis_dtypes.Timestamp(), pa.timestamp("us")), - (ibis_dtypes.float64, pa.float64()), - ( - ibis_dtypes.Timestamp(timezone="UTC"), - pa.timestamp("us", tz="UTC"), - ), - ( - ibis_dtypes.Struct.from_tuples( - [ - ("name", ibis_dtypes.string()), - ("version", ibis_dtypes.int64()), - ] - ), - pa.struct( - [ - ("name", pa.string()), - ("version", pa.int64()), - ] - ), - ), - ( - ibis_dtypes.Struct.from_tuples( - [ - ( - "nested", - ibis_dtypes.Struct.from_tuples( - [ - ("field", ibis_dtypes.string()), - ] - ), - ), - ] - ), - pa.struct( - [ - ( - "nested", - pa.struct( - [ - ("field", pa.string()), - ] - ), - ), - ] - ), - ), -) - - -@pytest.mark.parametrize(("ibis_dtype", "arrow_dtype"), IBIS_ARROW_DTYPES) -def test_arrow_dtype_to_ibis_dtype(ibis_dtype, arrow_dtype): - result = bigframes.core.compile.ibis_types._arrow_dtype_to_ibis_dtype(arrow_dtype) - assert result == ibis_dtype - - -@pytest.mark.parametrize(("ibis_dtype", "arrow_dtype"), IBIS_ARROW_DTYPES) -def test_ibis_dtype_to_arrow_dtype(ibis_dtype, arrow_dtype): - result = bigframes.core.compile.ibis_types._ibis_dtype_to_arrow_dtype(ibis_dtype) - assert result == arrow_dtype - - -@pytest.mark.parametrize( - ("ibis_dtype", "bigquery_type"), - [(ibis_dtypes.String(), "STRING"), (ibis_dtypes.String(nullable=False), "STRING")], -) -def test_ibis_dtype_to_bigquery_type(ibis_dtype, bigquery_type): - result = ibis_bq_types.BigQueryType.from_ibis(ibis_dtype) - assert result == bigquery_type - - -@pytest.mark.parametrize( - ["bigframes_dtype", "ibis_dtype"], - [ - # This test covers all dtypes that BigQuery DataFrames can exactly map to Ibis - (pd.BooleanDtype(), ibis_dtypes.boolean), - (pd.ArrowDtype(pa.date32()), ibis_dtypes.date), - (pd.ArrowDtype(pa.timestamp("us")), ibis_dtypes.Timestamp()), - (pd.Float64Dtype(), ibis_dtypes.float64), - (pd.Int64Dtype(), ibis_dtypes.int64), - (pd.StringDtype(storage="pyarrow"), ibis_dtypes.string), - (pd.ArrowDtype(pa.time64("us")), ibis_dtypes.time), - ( - pd.ArrowDtype(pa.timestamp("us", tz="UTC")), # type: ignore - ibis_dtypes.Timestamp(timezone="UTC"), - ), - ], - ids=[ - "boolean", - "date", - "datetime", - "float", - "int", - "string", - "time", - "timestamp", - ], -) -def test_bigframes_dtype_converts(ibis_dtype, bigframes_dtype): - """Test all the Ibis data types needed to read BigQuery tables""" - result = bigframes.core.compile.ibis_types.bigframes_dtype_to_ibis_dtype( - bigframes_dtype - ) - assert result == ibis_dtype - - -@pytest.mark.parametrize( - ["bigframes_dtype_str", "ibis_dtype"], - [ - # This test covers all dtypes that BigQuery DataFrames can exactly map to Ibis - ("boolean", ibis_dtypes.boolean), - ("date32[day][pyarrow]", ibis_dtypes.date), - ("timestamp[us][pyarrow]", ibis_dtypes.Timestamp()), - ("Float64", ibis_dtypes.float64), - ("Int64", ibis_dtypes.int64), - ("string[pyarrow]", ibis_dtypes.string), - ("time64[us][pyarrow]", ibis_dtypes.time), - ( - "timestamp[us, tz=UTC][pyarrow]", - ibis_dtypes.Timestamp(timezone="UTC"), - ), - # Special case - "string" is acceptable for "string[pyarrow]" - ("string", ibis_dtypes.string), - ], -) -def test_bigframes_string_dtype_converts(ibis_dtype, bigframes_dtype_str): - """Test all the Ibis data types needed to read BigQuery tables""" - result = bigframes.core.compile.ibis_types.bigframes_dtype_to_ibis_dtype( - bigframes.dtypes.bigframes_type(bigframes_dtype_str) - ) - assert result == ibis_dtype - - -def test_unsupported_dtype_raises_unexpected_datatype(): - """Incompatible dtypes should fail when passed into BigQuery DataFrames""" - with pytest.raises(ValueError, match="Datatype has no ibis type mapping"): - bigframes.core.compile.ibis_types.bigframes_dtype_to_ibis_dtype(np.float32) - - -def test_unsupported_dtype_str_raises_unexpected_datatype(): - """Incompatible dtypes should fail when passed into BigQuery DataFrames""" - with pytest.raises(ValueError, match="Datatype has no ibis type mapping"): - bigframes.core.compile.ibis_types.bigframes_dtype_to_ibis_dtype("int64") - - -@pytest.mark.parametrize( - ["literal", "ibis_scalar"], - [ - (True, ibis_types.literal(True, ibis_dtypes.boolean)), - (5, ibis_types.literal(5, ibis_dtypes.int64)), - (-33.2, ibis_types.literal(-33.2, ibis_dtypes.float64)), - ], -) -def test_literal_to_ibis_scalar_converts(literal, ibis_scalar): - assert bigframes.core.compile.ibis_types.literal_to_ibis_scalar(literal).equals( - ibis_scalar - ) diff --git a/tests/unit/core/test_indexes.py b/tests/unit/core/test_indexes.py deleted file mode 100644 index 6e739c9dc98..00000000000 --- a/tests/unit/core/test_indexes.py +++ /dev/null @@ -1,39 +0,0 @@ -# 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. -# 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.core.indexes - - -def test_index_repr_with_uninitialized_object(): - """Ensures Index.__init__ can be paused in a visual debugger without crashing. - - Regression test for https://github.com/googleapis/python-bigquery-dataframes/issues/728 - """ - # Avoid calling __init__ to simulate pausing __init__ in a debugger. - # https://stackoverflow.com/a/6384982/101923 - index = object.__new__(bigframes.core.indexes.Index) - got = repr(index) - assert "Index" in got - - -def test_multiindex_repr_with_uninitialized_object(): - """Ensures MultiIndex.__init__ can be paused in a visual debugger without crashing. - - Regression test for https://github.com/googleapis/python-bigquery-dataframes/issues/728 - """ - # Avoid calling __init__ to simulate pausing __init__ in a debugger. - # https://stackoverflow.com/a/6384982/101923 - index = object.__new__(bigframes.core.indexes.MultiIndex) - got = repr(index) - assert "MultiIndex" in got diff --git a/tests/unit/core/test_pyarrow_utils.py b/tests/unit/core/test_pyarrow_utils.py deleted file mode 100644 index 155c36d268e..00000000000 --- a/tests/unit/core/test_pyarrow_utils.py +++ /dev/null @@ -1,65 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import itertools - -import numpy as np -import pyarrow as pa -import pytest - -from bigframes.core import pyarrow_utils - -PA_TABLE = pa.table({f"col_{i}": np.random.rand(1000) for i in range(10)}) - -# 17, 3, 929 coprime -N = 17 -MANY_SMALL_BATCHES = PA_TABLE.to_batches(max_chunksize=3) -FEW_BIG_BATCHES = PA_TABLE.to_batches(max_chunksize=929) - - -@pytest.mark.parametrize( - ["batches", "page_size"], - [ - (MANY_SMALL_BATCHES, N), - (FEW_BIG_BATCHES, N), - ], -) -def test_chunk_by_row_count(batches, page_size): - results = list(pyarrow_utils.chunk_by_row_count(batches, page_size=page_size)) - - for i, batches in enumerate(results): - if i != len(results) - 1: - assert sum(map(lambda x: x.num_rows, batches)) == page_size - else: - # final page can be smaller - assert sum(map(lambda x: x.num_rows, batches)) <= page_size - - reconstructed = pa.Table.from_batches(itertools.chain.from_iterable(results)) - assert reconstructed.equals(PA_TABLE) - - -@pytest.mark.parametrize( - ["batches", "max_rows"], - [ - (MANY_SMALL_BATCHES, N), - (FEW_BIG_BATCHES, N), - ], -) -def test_truncate_pyarrow_iterable(batches, max_rows): - results = list( - pyarrow_utils.truncate_pyarrow_iterable(batches, max_results=max_rows) - ) - - reconstructed = pa.Table.from_batches(results) - assert reconstructed.equals(PA_TABLE.slice(length=max_rows)) diff --git a/tests/unit/core/test_pyformat.py b/tests/unit/core/test_pyformat.py deleted file mode 100644 index 239a59237f6..00000000000 --- a/tests/unit/core/test_pyformat.py +++ /dev/null @@ -1,698 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://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. - -"""Tests for the pyformat feature.""" - -# TODO(tswast): consolidate with pandas-gbq and bigquery-magics. See: -# https://github.com/googleapis/python-bigquery-magics/blob/main/tests/unit/bigquery/test_pyformat.py - -from __future__ import annotations - -import datetime -import decimal -from typing import Any, Dict, List - -import db_dtypes # type: ignore -import geopandas # type: ignore -import google.cloud.bigquery -import google.cloud.bigquery.table -import numpy -import pandas -import pyarrow -import pytest -import shapely.geometry # type: ignore - -from bigframes.core import pyformat -from bigframes.testing import mocks - - -@pytest.fixture -def session(): - return mocks.create_bigquery_session() - - -@pytest.mark.parametrize( - ("sql_template", "expected"), - ( - ( - "{my_project}.{my_dataset}.{my_table}", - ["my_project", "my_dataset", "my_table"], - ), - ( - "{{not a format variable}}", - [], - ), - ), -) -def test_parse_fields(sql_template: str, expected: List[str]): - fields = pyformat._parse_fields(sql_template) - fields.sort() - expected.sort() - assert fields == expected - - -def test_get_error_context_at_pos_invalid_pos(): - assert pyformat.get_error_context_at_pos("SELECT 1", -1) == "" - assert pyformat.get_error_context_at_pos("SELECT 1", 100) == "" - - -def test_get_error_context_at_pos_single_line(): - sql = "SELECT {foo}" - # pos of '{' is 7 - context = pyformat.get_error_context_at_pos(sql, 7) - expected = " 1: SELECT {foo}\n ^" - assert context == expected - - -def test_get_error_context_at_pos_multi_line(): - sql = "SELECT 1\nFROM my_table\nWHERE col = {foo}\nAND active = True\nLIMIT 10" - # Lines: - # 1: SELECT 1 (len 9 including \n) - # 2: FROM my_table (len 14 including \n) -> total 23 - # 3: WHERE col = {foo} -> '{' is at 23 + 12 = 35 - - context = pyformat.get_error_context_at_pos(sql, 35) - expected = ( - " 1: SELECT 1\n" - " 2: FROM my_table\n" - " 3: WHERE col = {foo}\n" - " ^\n" - " 4: AND active = True\n" - " 5: LIMIT 10" - ) - assert context == expected - - -def test_get_error_context_at_pos_multi_line_limits(): - # Test that it only shows at most 2 lines before and 2 lines after - sql = ( - "LINE 1\n" - "LINE 2\n" - "LINE 3\n" - "LINE 4\n" - "LINE 5\n" - "TARGET {foo}\n" - "LINE 7\n" - "LINE 8\n" - "LINE 9\n" - "LINE 10" - ) - # Line lengths: - # LINE 1\n (7) - # LINE 2\n (7) -> 14 - # LINE 3\n (7) -> 21 - # LINE 4\n (7) -> 28 - # LINE 5\n (7) -> 35 - # TARGET {foo}\n -> '{' is at 35 + 7 = 42 - - context = pyformat.get_error_context_at_pos(sql, 42) - expected = ( - " 4: LINE 4\n" - " 5: LINE 5\n" - " 6: TARGET {foo}\n" - " ^\n" - " 7: LINE 7\n" - " 8: LINE 8" - ) - assert context == expected - - -def test_pyformat_with_unsupported_type_raises_typeerror(session): - pyformat_args = {"my_object": object()} - sql = "SELECT {my_object}" - - with pytest.raises(TypeError, match="my_object has unsupported type: "): - pyformat.pyformat(sql, pyformat_args=pyformat_args, session=session) - - -def test_pyformat_with_missing_variable_raises_valueerror(session): - pyformat_args: Dict[str, Any] = {} - sql = "SELECT {my_object}" - - with pytest.raises(ValueError) as exc_info: - pyformat.pyformat(sql, pyformat_args=pyformat_args, session=session) - - err_msg = str(exc_info.value) - assert "Undetected variable 'my_object' in SQL template" in err_msg - assert "Did you mean to escape '{' and '}'" in err_msg - assert " 1: SELECT {my_object}" in err_msg - assert " ^" in err_msg - - -def test_pyformat_with_unescaped_braces_raises_valueerror_with_context(session): - pyformat_args = {"active": True} - sql = """SELECT * FROM my_table -WHERE json_col = { "generation_config": { "temperature": 0.9 } } -AND active = {active} -""" - - with pytest.raises(ValueError) as exc_info: - pyformat.pyformat(sql, pyformat_args=pyformat_args, session=session) - - err_msg = str(exc_info.value) - assert "Undetected variable ' \"generation_config\"' in SQL template" in err_msg - assert "Did you mean to escape '{' and '}'" in err_msg - # The triple quote string starts with SELECT immediately, so lines are: - # 1: SELECT * FROM my_table - # 2: WHERE json_col = { "generation_config": { "temperature": 0.9 } } - # 3: AND active = {active} - assert " 1: SELECT * FROM my_table" in err_msg - assert ( - ' 2: WHERE json_col = { "generation_config": { "temperature": 0.9 } }' - in err_msg - ) - assert " ^" in err_msg - assert " 3: AND active = {active}" in err_msg - - -@pytest.mark.parametrize( - ("sql_template", "expected_error"), - ( - pytest.param( - "SELECT {foo", - "expected '}' before end of string", - id="missing_closing_brace", - ), - pytest.param( - "SELECT foo}", - "Single '}' encountered in format string", - id="missing_opening_brace", - ), - ), -) -def test_pyformat_with_malformed_template_raises_valueerror( - session, sql_template: str, expected_error: str -): - pyformat_args: Dict[str, Any] = {} - - # Case 1: Single '{' (unmatched) - with pytest.raises(ValueError) as exc_info: - pyformat.pyformat(sql_template, pyformat_args=pyformat_args, session=session) - - error_message = str(exc_info.value) - assert "Failed to parse SQL template" in error_message - assert "Did you mean to escape '{' and '}'" in error_message - assert expected_error in error_message - - -def test_pyformat_with_no_variables(session): - pyformat_args: Dict[str, Any] = {} - sql = "SELECT '{{escaped curly brackets}}'" - expected_sql = "SELECT '{escaped curly brackets}'" - got_sql = pyformat.pyformat(sql, pyformat_args=pyformat_args, session=session) - assert got_sql == expected_sql - - -@pytest.mark.parametrize( - ("df_pd", "expected_struct"), - ( - pytest.param( - pandas.DataFrame(), - "STRUCT<>", - id="empty", - ), - pytest.param( - # Empty columns default to floating point, just like pandas. - pandas.DataFrame({"empty column": []}), - "STRUCT<`empty column` FLOAT64>", - id="empty column", - ), - # Regression tests for b/428190014. - # - # Test every BigQuery type we support, especially those where the legacy - # SQL type name differs from the GoogleSQL type name. - # - # See: - # https://cloud.google.com/bigquery/docs/reference/standard-sql/data-types - # and compare to the legacy types at - # https://cloud.google.com/bigquery/docs/data-types - # - # Test these against the real BigQuery dry run API in - # tests/system/small/pandas/io/api/test_read_gbq_colab.py - pytest.param( - pandas.DataFrame( - { - "ints": pandas.Series( - [[1], [2], [3]], - dtype=pandas.ArrowDtype(pyarrow.list_(pyarrow.int64())), - ), - "floats": pandas.Series( - [[1.0], [2.0], [3.0]], - dtype=pandas.ArrowDtype(pyarrow.list_(pyarrow.float64())), - ), - } - ), - "STRUCT<`ints` ARRAY, `floats` ARRAY>", - id="arrays", - ), - pytest.param( - pandas.DataFrame( - { - "bool": pandas.Series([True, False, True], dtype="bool"), - "boolean": pandas.Series([True, None, True], dtype="boolean"), - "object": pandas.Series([True, None, True], dtype="object"), - "arrow": pandas.Series( - [True, None, True], dtype=pandas.ArrowDtype(pyarrow.bool_()) - ), - } - ), - "STRUCT<`bool` BOOL, `boolean` BOOL, `object` BOOL, `arrow` BOOL>", - id="bools", - ), - pytest.param( - pandas.DataFrame( - { - "bytes": pandas.Series([b"a", b"b", b"c"], dtype=numpy.bytes_), - "object": pandas.Series([b"a", None, b"c"], dtype="object"), - "arrow": pandas.Series( - [b"a", None, b"c"], dtype=pandas.ArrowDtype(pyarrow.binary()) - ), - } - ), - "STRUCT<`bytes` BYTES, `object` BYTES, `arrow` BYTES>", - id="bytes", - ), - pytest.param( - pandas.DataFrame( - { - "object": pandas.Series( - [ - datetime.date(2023, 11, 23), - None, - datetime.date(1970, 1, 1), - ], - dtype="object", - ), - "arrow": pandas.Series( - [ - datetime.date(2023, 11, 23), - None, - datetime.date(1970, 1, 1), - ], - dtype=pandas.ArrowDtype(pyarrow.date32()), - ), - } - ), - "STRUCT<`object` DATE, `arrow` DATE>", - id="dates", - ), - pytest.param( - pandas.DataFrame( - { - "object": pandas.Series( - [ - datetime.datetime(2023, 11, 23, 13, 14, 15), - None, - datetime.datetime(1970, 1, 1, 0, 0, 0), - ], - dtype="object", - ), - "datetime64": pandas.Series( - [ - datetime.datetime(2023, 11, 23, 13, 14, 15), - None, - datetime.datetime(1970, 1, 1, 0, 0, 0), - ], - dtype="datetime64[us]", - ), - "arrow": pandas.Series( - [ - datetime.datetime(2023, 11, 23, 13, 14, 15), - None, - datetime.datetime(1970, 1, 1, 0, 0, 0), - ], - dtype=pandas.ArrowDtype(pyarrow.timestamp("us")), - ), - } - ), - "STRUCT<`object` DATETIME, `datetime64` DATETIME, `arrow` DATETIME>", - id="datetimes", - ), - pytest.param( - pandas.DataFrame( - { - "object": pandas.Series( - [ - shapely.geometry.Point(145.0, -37.8), - None, - shapely.geometry.Point(-122.3, 47.6), - ], - dtype="object", - ), - "geopandas": geopandas.GeoSeries( - [ - shapely.geometry.Point(145.0, -37.8), - None, - shapely.geometry.Point(-122.3, 47.6), - ] - ), - } - ), - "STRUCT<`object` GEOGRAPHY, `geopandas` GEOGRAPHY>", - id="geographys", - ), - # TODO(tswast): Add INTERVAL once BigFrames supports it. - pytest.param( - pandas.DataFrame( - { - # TODO(tswast): Is there an equivalent object type we can use here? - # TODO(tswast): Add built-in Arrow extension type - "db_dtypes": pandas.Series( - ["{}", None, "123"], - dtype=pandas.ArrowDtype(db_dtypes.JSONArrowType()), - ), - } - ), - "STRUCT<`db_dtypes` JSON>", - id="jsons", - ), - pytest.param( - pandas.DataFrame( - { - "int64": pandas.Series([1, 2, 3], dtype="int64"), - "Int64": pandas.Series([1, None, 3], dtype="Int64"), - "object": pandas.Series([1, None, 3], dtype="object"), - "arrow": pandas.Series( - [1, None, 3], dtype=pandas.ArrowDtype(pyarrow.int64()) - ), - } - ), - "STRUCT<`int64` INT64, `Int64` INT64, `object` INT64, `arrow` INT64>", - id="ints", - ), - pytest.param( - pandas.DataFrame( - { - "object": pandas.Series( - [decimal.Decimal("1.23"), None, decimal.Decimal("4.56")], - dtype="object", - ), - "arrow": pandas.Series( - [decimal.Decimal("1.23"), None, decimal.Decimal("4.56")], - dtype=pandas.ArrowDtype(pyarrow.decimal128(38, 9)), - ), - } - ), - "STRUCT<`object` NUMERIC, `arrow` NUMERIC>", - id="numerics", - ), - pytest.param( - pandas.DataFrame( - { - # TODO(tswast): Add object type for BIGNUMERIC. Can bigframes disambiguate? - "arrow": pandas.Series( - [decimal.Decimal("1.23"), None, decimal.Decimal("4.56")], - dtype=pandas.ArrowDtype(pyarrow.decimal256(76, 38)), - ), - } - ), - "STRUCT<`arrow` BIGNUMERIC>", - id="bignumerics", - ), - pytest.param( - pandas.DataFrame( - { - "float64": pandas.Series([1.23, None, 4.56], dtype="float64"), - "Float64": pandas.Series([1.23, None, 4.56], dtype="Float64"), - "object": pandas.Series([1.23, None, 4.56], dtype="object"), - "arrow": pandas.Series( - [1.23, None, 4.56], dtype=pandas.ArrowDtype(pyarrow.float64()) - ), - } - ), - "STRUCT<`float64` FLOAT64, `Float64` FLOAT64, `object` FLOAT64, `arrow` FLOAT64>", - id="floats", - ), - # TODO(tswast): Add RANGE once BigFrames supports it. - pytest.param( - pandas.DataFrame( - { - "string": pandas.Series(["a", "b", "c"], dtype="string[python]"), - "object": pandas.Series(["a", None, "c"], dtype="object"), - "arrow": pandas.Series(["a", None, "c"], dtype="string[pyarrow]"), - } - ), - "STRUCT<`string` STRING, `object` STRING, `arrow` STRING>", - id="strings", - ), - pytest.param( - pandas.DataFrame( - { - # TODO(tswast): Add object type for STRUCT? How to tell apart from JSON? - "arrow": pandas.Series( - [{"a": 1, "b": 1.0, "c": "c"}], - dtype=pandas.ArrowDtype( - pyarrow.struct( - [ - ("a", pyarrow.int64()), - ("b", pyarrow.float64()), - ("c", pyarrow.string()), - ] - ) - ), - ), - } - ), - "STRUCT<`arrow` STRUCT<`a` INT64, `b` FLOAT64, `c` STRING>>", - id="structs", - ), - pytest.param( - pandas.DataFrame( - { - "object": pandas.Series( - [ - datetime.time(0, 0, 0), - None, - datetime.time(13, 7, 11), - ], - dtype="object", - ), - "arrow": pandas.Series( - [ - datetime.time(0, 0, 0), - None, - datetime.time(13, 7, 11), - ], - dtype=pandas.ArrowDtype(pyarrow.time64("us")), - ), - } - ), - "STRUCT<`object` TIME, `arrow` TIME>", - id="times", - ), - pytest.param( - pandas.DataFrame( - { - "object": pandas.Series( - [ - datetime.datetime( - 2023, 11, 23, 13, 14, 15, tzinfo=datetime.timezone.utc - ), - None, - datetime.datetime( - 1970, 1, 1, 0, 0, 0, tzinfo=datetime.timezone.utc - ), - ], - dtype="object", - ), - "datetime64": pandas.Series( - [ - datetime.datetime(2023, 11, 23, 13, 14, 15), - None, - datetime.datetime(1970, 1, 1, 0, 0, 0), - ], - dtype="datetime64[us]", - ).dt.tz_localize("UTC"), - "arrow": pandas.Series( - [ - datetime.datetime( - 2023, 11, 23, 13, 14, 15, tzinfo=datetime.timezone.utc - ), - None, - datetime.datetime( - 1970, 1, 1, 0, 0, 0, tzinfo=datetime.timezone.utc - ), - ], - dtype=pandas.ArrowDtype(pyarrow.timestamp("us", "UTC")), - ), - } - ), - "STRUCT<`object` TIMESTAMP, `datetime64` TIMESTAMP, `arrow` TIMESTAMP>", - id="timestamps", - ), - # More complicated edge cases: - pytest.param( - pandas.DataFrame( - { - "array of struct col": [ - [{"subfield": {"subsubfield": 1}, "subfield2": 2}], - ], - } - ), - "STRUCT<`array of struct col` ARRAY, `subfield2` INT64>>>", - id="array_of_structs", - ), - pytest.param( - pandas.DataFrame({"c1": [1, 2, 3], "c2": ["a", "b", "c"]}).rename( - columns={"c1": "c", "c2": "c"} - ), - "STRUCT<`c` INT64, `c_1` STRING>", - id="duplicate_column_names", - ), - ), -) -def test_pyformat_with_pandas_dataframe_dry_run_no_session(df_pd, expected_struct): - pyformat_args: Dict[str, Any] = {"my_pandas_df": df_pd} - sql = "SELECT * FROM {my_pandas_df}" - expected_sql = f"SELECT * FROM UNNEST(ARRAY<{expected_struct}>[])" - got_sql = pyformat.pyformat( - sql, pyformat_args=pyformat_args, dry_run=True, session=None - ) - assert got_sql == expected_sql - - -def test_pyformat_with_pandas_dataframe_not_dry_run_no_session_raises_valueerror(): - pyformat_args: Dict[str, Any] = {"my_pandas_df": pandas.DataFrame()} - sql = "SELECT * FROM {my_pandas_df}" - - with pytest.raises(ValueError, match="my_pandas_df"): - pyformat.pyformat(sql, pyformat_args=pyformat_args) - - -def test_pyformat_with_query_string_replaces_variables(session): - pyformat_args = { - "my_string": "`my_table`", - "max_value": 2.25, - "year": 2025, - "null_value": None, - # Unreferenced values of unsupported type shouldn't cause issues. - "my_object": object(), - } - - sql = """ - SELECT {year} - year AS age, - @myparam AS myparam, - '{{my_string}}' AS escaped_string, - * - FROM {my_string} - WHERE height < {max_value} - """.strip() - - expected_sql = """ - SELECT 2025 - year AS age, - @myparam AS myparam, - '{my_string}' AS escaped_string, - * - FROM `my_table` - WHERE height < 2.25 - """.strip() - - got_sql = pyformat.pyformat(sql, pyformat_args=pyformat_args, session=session) - assert got_sql == expected_sql - - -@pytest.mark.parametrize( - ("table", "expected_sql"), - ( - ( - google.cloud.bigquery.Table("my-project.my_dataset.my_table"), - "SELECT * FROM `my-project`.`my_dataset`.`my_table`", - ), - ( - google.cloud.bigquery.TableReference( - google.cloud.bigquery.DatasetReference("some-project", "some_dataset"), - "some_table", - ), - "SELECT * FROM `some-project`.`some_dataset`.`some_table`", - ), - ( - google.cloud.bigquery.table.TableListItem( - { - "tableReference": { - "projectId": "ListedProject", - "datasetId": "ListedDataset", - "tableId": "ListedTable", - } - } - ), - "SELECT * FROM `ListedProject`.`ListedDataset`.`ListedTable`", - ), - ( - google.cloud.bigquery.TableReference( - google.cloud.bigquery.DatasetReference( - "my-project", "my-catalog.my-namespace" - ), - "my-table", - ), - "SELECT * FROM `my-project`.`my-catalog`.`my-namespace`.`my-table`", - ), - ), -) -def test_pyformat_with_table_replaces_variables(table, expected_sql, session=session): - pyformat_args = { - "table": table, - # Unreferenced values of unsupported type shouldn't cause issues. - "my_object": object(), - } - sql = "SELECT * FROM {table}" - got_sql = pyformat.pyformat(sql, pyformat_args=pyformat_args, session=session) - assert got_sql == expected_sql - - -def test_pyformat_with_bigframes_dataframe_biglake_table(session): - # Create a real BigFrames DataFrame that points to a BigLake table. - import bigframes.core.array_value as array_value - import bigframes.core.blocks as blocks - import bigframes.core.bq_data as bq_data - import bigframes.dataframe - - # Define the BigLake table - project_id = "my-project" - catalog_id = "my-catalog" - namespace_id = "my-namespace" - table_id = "my-table" - schema = (google.cloud.bigquery.SchemaField("col", "INTEGER"),) - - biglake_table = bq_data.BiglakeIcebergTable( - project_id=project_id, - catalog_id=catalog_id, - namespace_id=namespace_id, - table_id=table_id, - physical_schema=schema, - cluster_cols=(), - metadata=bq_data.TableMetadata( - location=bq_data.BigQueryRegion("us-central1"), - type="TABLE", - ), - ) - - # ArrayValue.from_table is what read_gbq uses. - av = array_value.ArrayValue.from_table(biglake_table, session) - block = blocks.Block(av, index_columns=[], column_labels=["col"]) - df = bigframes.dataframe.DataFrame(block) - - pyformat_args = {"df": df} - sql = "SELECT * FROM {df}" - - got_sql = pyformat.pyformat(sql, pyformat_args=pyformat_args, session=session) - - # For BigLake, we now expect a SUBQUERY, not a view reference. - # The subquery should have correctly quoted 4-part ID. - assert "SELECT" in got_sql - assert project_id in got_sql - assert catalog_id in got_sql - assert namespace_id in got_sql - assert table_id in got_sql - assert got_sql.startswith("SELECT * FROM (SELECT") - assert got_sql.endswith(")") diff --git a/tests/unit/core/test_slices.py b/tests/unit/core/test_slices.py deleted file mode 100644 index 745db45eab6..00000000000 --- a/tests/unit/core/test_slices.py +++ /dev/null @@ -1,61 +0,0 @@ -# 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. -# 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 - -import bigframes.core.slices as slices - - -@pytest.mark.parametrize( - ["slice", "input_rows", "expected"], - [ - ((1, 2, 3), 3, 1), - ((-3, 400, None), 401, 2), - ((5, 505, None), 300, 295), - ((1, 10, 4), 10, 3), - ((1, 9, 4), 10, 2), - ((-1, -10, -4), 10, 3), - ((-1, -10, 4), 10, 0), - ((99, 100, 1), 9, 0), - ], -) -def test_slice_row_count(slice, input_rows, expected): - assert expected == slices.slice_output_rows(slice, input_rows) - - -@pytest.mark.parametrize( - ["slice", "input_rows", "expected"], - [ - ((1, 2, 3), 3, (1, 2, 3)), - ((-3, 400, None), 401, (-3, 400, None)), - ((5, 505, None), 300, (5, None, None)), - ((99, 100, 1), 9, (99, None, None)), - ], -) -def test_remove_unused_parts(slice, input_rows, expected): - assert expected == slices.remove_unused_parts(slice, input_rows) - - -@pytest.mark.parametrize( - ["slice", "input_rows", "expected"], - [ - ((1, 2, 3), 3, (1, 2, 3)), - ((-3, 400, None), 401, (398, 400, 1)), - ((5, 505, None), 300, (5, 300, 1)), - ((None, None, None), 300, (0, None, 1)), - ((None, None, -1), 300, (299, None, -1)), - ], -) -def test_to_forward_offsets(slice, input_rows, expected): - assert expected == slices.to_forward_offsets(slice, input_rows) diff --git a/tests/unit/core/test_sql.py b/tests/unit/core/test_sql.py deleted file mode 100644 index 04ebb28764d..00000000000 --- a/tests/unit/core/test_sql.py +++ /dev/null @@ -1,66 +0,0 @@ -# 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. -# 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 bigframes.core import sql - - -def test_create_vector_search_sql_simple(): - result_query = sql.create_vector_search_sql( - sql_string="SELECT embedding FROM my_embeddings_table WHERE id = 1", - base_table="my_base_table", - column_to_search="my_embedding_column", - ) - assert ( - result_query - == """ - SELECT - query.*, - base.*, - distance, - FROM VECTOR_SEARCH(TABLE `my_base_table`, -'my_embedding_column', -(SELECT embedding FROM my_embeddings_table WHERE id = 1)) - """ - ) - - -def test_create_vector_search_sql_all_named_parameters(): - result_query = sql.create_vector_search_sql( - sql_string="SELECT embedding FROM my_embeddings_table WHERE id = 1", - base_table="my_base_table", - column_to_search="my_embedding_column", - query_column_to_search="another_embedding_column", - top_k=10, - distance_type="cosine", - options={ - "fraction_lists_to_search": 0.1, - "use_brute_force": False, - }, - ) - assert ( - result_query - == """ - SELECT - query.*, - base.*, - distance, - FROM VECTOR_SEARCH(TABLE `my_base_table`, -'my_embedding_column', -(SELECT embedding FROM my_embeddings_table WHERE id = 1), -query_column_to_search => 'another_embedding_column', -top_k=> 10, -distance_type => 'cosine', -options => '{"fraction_lists_to_search": 0.1, "use_brute_force": false}') - """ - ) diff --git a/tests/unit/core/test_windowspec.py b/tests/unit/core/test_windowspec.py deleted file mode 100644 index b9de7641363..00000000000 --- a/tests/unit/core/test_windowspec.py +++ /dev/null @@ -1,50 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import pytest - -from bigframes.core import window_spec - - -@pytest.mark.parametrize(("start", "end"), [(-1, -2), (1, -2), (2, 1)]) -def test_invalid_rows_window_boundary_raise_error(start, end): - with pytest.raises(ValueError): - window_spec.RowsWindowBounds(start, end) - - -@pytest.mark.parametrize(("start", "end"), [(-1, -2), (1, -2), (2, 1)]) -def test_invalid_range_window_boundary_raise_error(start, end): - with pytest.raises(ValueError): - window_spec.RangeWindowBounds(start, end) - - -@pytest.mark.parametrize( - ("window", "closed", "start", "end"), - [ - pytest.param(3, "left", -3, -1, id="left"), - pytest.param(3, "right", -2, 0, id="right"), - pytest.param(3, "neither", -2, -1, id="neither"), - pytest.param(3, "both", -3, 0, id="both"), - ], -) -def test_rows_window_bounds_from_window_size(window, closed, start, end): - actual_result = window_spec.RowsWindowBounds.from_window_size(window, closed) - - expected_result = window_spec.RowsWindowBounds(start, end) - assert actual_result == expected_result - - -def test_rows_window_bounds_from_window_size_invalid_closed_raise_error(): - with pytest.raises(ValueError): - window_spec.RowsWindowBounds.from_window_size(3, "whatever") # type:ignore diff --git a/tests/unit/core/tools/__init__.py b/tests/unit/core/tools/__init__.py deleted file mode 100644 index 0a2669d7a25..00000000000 --- a/tests/unit/core/tools/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. diff --git a/tests/unit/core/tools/test_bigquery_schema.py b/tests/unit/core/tools/test_bigquery_schema.py deleted file mode 100644 index 2b6693c13e6..00000000000 --- a/tests/unit/core/tools/test_bigquery_schema.py +++ /dev/null @@ -1,191 +0,0 @@ -import pytest -from google.cloud import bigquery - -from bigframes.core.tools import bigquery_schema - - -# --- Tests for _type_to_sql --- -@pytest.mark.parametrize( - "field, expected_sql", - [ - # Simple types - # Note: the REST API will return Legacy SQL data types, but we need to - # map to GoogleSQL. See internal issue b/428190014. - (bigquery.SchemaField("test_field", "INTEGER"), "INT64"), - (bigquery.SchemaField("test_field", "STRING"), "STRING"), - (bigquery.SchemaField("test_field", "BOOLEAN"), "BOOL"), - # RECORD/STRUCT types with nested fields directly - ( - bigquery.SchemaField( - "test_field", - "RECORD", - fields=(bigquery.SchemaField("sub_field", "STRING"),), - ), - "STRUCT<`sub_field` STRING>", - ), - ( - bigquery.SchemaField( - "test_field", - "STRUCT", - fields=( - bigquery.SchemaField("sub_field", "INTEGER"), - bigquery.SchemaField("another", "BOOLEAN"), - ), - ), - "STRUCT<`sub_field` INT64, `another` BOOL>", - ), - # Array is handled by _field_to_sql, instead. - (bigquery.SchemaField("test_field", "NUMERIC", mode="REPEATED"), "NUMERIC"), - ( - bigquery.SchemaField( - "test_field", - "RECORD", - mode="REPEATED", - fields=(bigquery.SchemaField("sub_field", "STRING"),), - ), - "STRUCT<`sub_field` STRING>", - ), - ], -) -def test_type_to_sql(field, expected_sql): - assert bigquery_schema._type_to_sql(field) == expected_sql - - -# --- Tests for _field_to_sql --- -@pytest.mark.parametrize( - "field, expected_sql", - [ - # Simple field - # Note: the REST API will return Legacy SQL data types, but we need to - # map to GoogleSQL. See internal issue b/428190014. - (bigquery.SchemaField("id", "INTEGER", "NULLABLE"), "`id` INT64"), - (bigquery.SchemaField("name", "STRING", "NULLABLE"), "`name` STRING"), - # Repeated field - (bigquery.SchemaField("tags", "STRING", "REPEATED"), "`tags` ARRAY"), - # Repeated RECORD - ( - bigquery.SchemaField( - "addresses", - "RECORD", - "REPEATED", - fields=( - bigquery.SchemaField("street", "STRING"), - bigquery.SchemaField("zip", "INTEGER"), - ), - ), - "`addresses` ARRAY>", - ), - # Simple STRUCT - ( - bigquery.SchemaField( - "person", - "STRUCT", - "NULLABLE", - fields=( - bigquery.SchemaField("age", "INTEGER"), - bigquery.SchemaField("city", "STRING"), - ), - ), - "`person` STRUCT<`age` INT64, `city` STRING>", - ), - ], -) -def test_field_to_sql(field, expected_sql): - assert bigquery_schema._field_to_sql(field) == expected_sql - - -# --- Tests for _to_struct --- -@pytest.mark.parametrize( - "bqschema, expected_sql", - [ - # Empty schema - ((), "STRUCT<>"), - # Simple fields - ( - ( - bigquery.SchemaField("id", "INTEGER"), - bigquery.SchemaField("name", "STRING"), - ), - "STRUCT<`id` INT64, `name` STRING>", - ), - # Nested RECORD/STRUCT - ( - ( - bigquery.SchemaField("item_id", "INTEGER"), - bigquery.SchemaField( - "details", - "RECORD", - "NULLABLE", - fields=( - bigquery.SchemaField("price", "NUMERIC"), - bigquery.SchemaField("currency", "STRING"), - ), - ), - ), - "STRUCT<`item_id` INT64, `details` STRUCT<`price` NUMERIC, `currency` STRING>>", - ), - # Repeated field - ( - ( - bigquery.SchemaField("user_id", "STRING"), - bigquery.SchemaField("emails", "STRING", "REPEATED"), - ), - "STRUCT<`user_id` STRING, `emails` ARRAY>", - ), - # Mixed types including complex nested repeated - ( - ( - bigquery.SchemaField("event_name", "STRING"), - bigquery.SchemaField( - "participants", - "RECORD", - "REPEATED", - fields=( - bigquery.SchemaField("p_id", "INTEGER"), - bigquery.SchemaField("roles", "STRING", "REPEATED"), - ), - ), - bigquery.SchemaField("timestamp", "TIMESTAMP"), - ), - "STRUCT<`event_name` STRING, `participants` ARRAY>>, `timestamp` TIMESTAMP>", - ), - ], -) -def test_to_struct(bqschema, expected_sql): - assert bigquery_schema._to_struct(bqschema) == expected_sql - - -# --- Tests for to_sql_dry_run --- -@pytest.mark.parametrize( - "bqschema, expected_sql", - [ - # Empty schema - ((), "UNNEST(ARRAY>[])"), - # Simple schema - ( - ( - bigquery.SchemaField("id", "INTEGER"), - bigquery.SchemaField("name", "STRING"), - ), - "UNNEST(ARRAY>[])", - ), - # Complex schema with nested and repeated fields - ( - ( - bigquery.SchemaField("order_id", "STRING"), - bigquery.SchemaField( - "items", - "RECORD", - "REPEATED", - fields=( - bigquery.SchemaField("item_name", "STRING"), - bigquery.SchemaField("quantity", "INTEGER"), - ), - ), - ), - "UNNEST(ARRAY>>>[])", - ), - ], -) -def test_to_sql_dry_run(bqschema, expected_sql): - assert bigquery_schema.to_sql_dry_run(bqschema) == expected_sql diff --git a/tests/unit/core/tools/test_datetimes.py b/tests/unit/core/tools/test_datetimes.py deleted file mode 100644 index 96a6b14ef8d..00000000000 --- a/tests/unit/core/tools/test_datetimes.py +++ /dev/null @@ -1,43 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from typing import cast -from unittest import mock - -import bigframes.core.tools.datetimes -import bigframes.dtypes -import bigframes.pandas -import bigframes.testing.mocks - - -def test_to_datetime_with_series_and_format_doesnt_cache(monkeypatch): - df = bigframes.testing.mocks.create_dataframe(monkeypatch) - series = mock.Mock(spec=bigframes.pandas.Series, wraps=df["col"]) - dt_series = cast( - bigframes.pandas.Series, - bigframes.core.tools.datetimes.to_datetime(series, format="%Y%m%d"), - ) - series._cached.assert_not_called() - assert dt_series.dtype == bigframes.dtypes.DATETIME_DTYPE - - -def test_to_datetime_with_series_and_format_utc_doesnt_cache(monkeypatch): - df = bigframes.testing.mocks.create_dataframe(monkeypatch) - series = mock.Mock(spec=bigframes.pandas.Series, wraps=df["col"]) - dt_series = cast( - bigframes.pandas.Series, - bigframes.core.tools.datetimes.to_datetime(series, format="%Y%m%d", utc=True), - ) - series._cached.assert_not_called() - assert dt_series.dtype == bigframes.dtypes.TIMESTAMP_DTYPE diff --git a/tests/unit/display/test_anywidget.py b/tests/unit/display/test_anywidget.py deleted file mode 100644 index 25f19cf495c..00000000000 --- a/tests/unit/display/test_anywidget.py +++ /dev/null @@ -1,543 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import signal -import unittest.mock as mock - -import pandas as pd -import pyarrow as pa -import pytest - -import bigframes - -# Skip if anywidget/traitlets not installed, though they should be in the dev env -pytest.importorskip("anywidget") -pytest.importorskip("traitlets") - -import bigframes.dataframe -import bigframes.dtypes -import bigframes.series -from bigframes.display.anywidget import TableWidget - - -def test_navigation_to_invalid_page_resets_to_valid_page_without_deadlock(): - """ - Given a widget on a page beyond available data, when navigating, - then it should reset to the last valid page without deadlock. - """ - mock_df = mock.create_autospec(bigframes.dataframe.DataFrame, instance=True) - mock_df.columns = ["col1"] - mock_df.dtypes = {"col1": "object"} - - mock_block = mock.Mock() - mock_block.has_index = False - mock_df._block = mock_block - - # We mock _initial_load to avoid complex setup - with ( - mock.patch.object(TableWidget, "_initial_load"), - bigframes.option_context( - "display.render_mode", "anywidget", "display.max_rows", 10 - ), - ): - widget = TableWidget(mock_df) - - # Simulate "loaded data but unknown total rows" state - widget.page_size = 10 - widget.row_count = None - widget._all_data_loaded = True - - # Populate cache with 1 page of data (10 rows). Page 0 is valid, page 1+ are invalid. - widget._cached_batches = [pd.DataFrame({"col1": range(10)})] - - # Mark initial load as complete so observers fire - widget._initial_load_complete = True - - # Setup timeout to fail fast if deadlock occurs - # signal.SIGALRM is not available on Windows - has_sigalrm = hasattr(signal, "SIGALRM") - if has_sigalrm: - - def handler(signum, frame): - raise TimeoutError("Deadlock detected!") - - signal.signal(signal.SIGALRM, handler) - signal.alarm(2) # 2 seconds timeout - - try: - # Trigger navigation to page 5 (invalid), which should reset to page 0 - widget.page = 5 - - assert widget.page == 0 - - finally: - if has_sigalrm: - signal.alarm(0) - - -def test_css_contains_dark_mode_selectors(): - """Test that the CSS for dark mode is loaded with all required selectors.""" - mock_df = mock.create_autospec(bigframes.dataframe.DataFrame, instance=True) - # mock_df.columns and mock_df.dtypes are needed for __init__ - mock_df.columns = ["col1"] - mock_df.dtypes = {"col1": "object"} - - # Mock _block to avoid AttributeError during _set_table_html - mock_block = mock.Mock() - mock_block.has_index = False - mock_df._block = mock_block - - with mock.patch.object(TableWidget, "_initial_load"): - widget = TableWidget(mock_df) - css = widget._css - assert "@media (prefers-color-scheme: dark)" in css - assert 'html[theme="dark"]' in css - assert 'body[data-theme="dark"]' in css - - -@pytest.fixture -def mock_df(): - """A mock DataFrame that can be used in multiple tests.""" - df = mock.create_autospec(bigframes.dataframe.DataFrame, instance=True) - df.columns = ["col1", "col2"] - df.dtypes = {"col1": "int64", "col2": "int64"} - - mock_block = mock.Mock() - mock_block.has_index = False - df._block = mock_block - - # Mock to_pandas_batches to return empty iterator or simple data - batch_df = pd.DataFrame({"col1": [1, 2], "col2": [3, 4]}) - batches = mock.MagicMock() - batches.__iter__.return_value = iter([batch_df]) - batches.total_rows = 2 - df.to_pandas_batches.return_value = batches - - # Mock sort_values to return self (for chaining) - df.sort_values.return_value = df - - return df - - -def test_sorting_single_column(mock_df): - """Test that the widget can be sorted by a single column.""" - with bigframes.option_context("display.render_mode", "anywidget"): - widget = TableWidget(mock_df) - - # Verify initial state - assert widget.sort_context == [] - - # Apply sort - widget.sort_context = [{"column": "col1", "ascending": True}] - - # This should trigger _sort_changed -> _set_table_html - # which calls df.sort_values - - mock_df.sort_values.assert_called_with(by=["col1"], ascending=[True]) - - -def test_sorting_multi_column(mock_df): - """Test that the widget can be sorted by multiple columns.""" - with bigframes.option_context("display.render_mode", "anywidget"): - widget = TableWidget(mock_df) - - # Apply multi-column sort - widget.sort_context = [ - {"column": "col1", "ascending": True}, - {"column": "col2", "ascending": False}, - ] - - mock_df.sort_values.assert_called_with(by=["col1", "col2"], ascending=[True, False]) - - -def test_page_size_change_resets_sort(mock_df): - """Test that changing the page size resets the sorting.""" - with bigframes.option_context("display.render_mode", "anywidget"): - widget = TableWidget(mock_df) - - # Set sort state - widget.sort_context = [{"column": "col1", "ascending": True}] - - # Change page size - widget.page_size = 50 - - # Sort should be reset - assert widget.sort_context == [] - - # to_pandas_batches called again (reset) - assert mock_df.to_pandas_batches.call_count >= 2 - - -def test_cell_execution_count_propagation(mock_df): - """Test that the captured cell_execution_count is propagated to to_pandas_batches.""" - with ( - mock.patch("bigframes.core.utils.get_ipython_execution_count", return_value=42), - bigframes.option_context("display.render_mode", "anywidget"), - ): - widget = TableWidget(mock_df) - - assert widget._cell_execution_count == 42 - - mock_df.to_pandas_batches.assert_called_with( - page_size=widget.page_size, - cell_execution_count=42, - ) - - -def test_json_column_converted_to_string_for_display(polars_session): - series = bigframes.series.Series( - ['{"a": 1}', '{"b": 2}'], - dtype=bigframes.dtypes.JSON_DTYPE, - session=polars_session, - ) - df = series.to_frame("col_json") - - result = df._prepare_display_df() - - assert result["col_json"].dtype == bigframes.dtypes.STRING_DTYPE - - -def test_struct_column_with_nested_json_converted_to_string_for_display( - polars_session, -): - if not hasattr(pa, "json_"): - pytest.skip(reason=f"pyarrow=={pa.__version__} does not support json_") - - # Arrange - json_type = pa.json_(storage_type=pa.utf8()) - json_data = pa.array(['{"a": 1}'], type=json_type) - string_data = pa.array(["hello"], type=pa.string()) - struct_data = pa.StructArray.from_arrays( - [string_data, json_data], names=["field1", "field2"] - ) - nested_data = pa.table([struct_data], names=["nested"]) - df = polars_session.read_arrow(nested_data) - exploded = df["nested"].struct.explode() - # Ensure that we are actually using the JSON dtype in this test. - assert exploded["field2"].dtype == bigframes.dtypes.JSON_DTYPE - - # Act - result = df._prepare_display_df() - - # Assert - assert result["nested"].dtype == bigframes.dtypes.STRING_DTYPE - - -@pytest.fixture -def mock_df_deferred(): - with mock.patch("bigframes.display.anywidget._ANYWIDGET_INSTALLED", True): - df = mock.Mock(spec=bigframes.dataframe.DataFrame) - df.shape = (100, 4) - df.columns = ["A", "B", "C", "D"] - df.dtypes = { - "A": bigframes.dtypes.INT_DTYPE, - "B": bigframes.dtypes.STRING_DTYPE, - "C": bigframes.dtypes.FLOAT_DTYPE, - "D": bigframes.dtypes.BOOL_DTYPE, - } - - df.to_pandas_batches.return_value = iter( - [pd.DataFrame({"A": [1], "B": ["a"], "C": [1.0], "D": [True]})] - ) - - df.sort_values.return_value = df - - df._block = mock.Mock() - df._block.has_index = False - df._prepare_display_df.return_value = df - - yield df - - -@pytest.fixture -def mock_deferred_df(): - from bigframes.session.deferred import DeferredBigQueryDataFrame - - with mock.patch("bigframes.display.anywidget._ANYWIDGET_INSTALLED", True): - # We create a mock that subclasses DeferredBigQueryDataFrame so isinstance passes - class MockDeferredBigQueryDataFrame(DeferredBigQueryDataFrame): - def __init__(self): - pass - - df = mock.MagicMock(spec=MockDeferredBigQueryDataFrame) - df.__class__ = DeferredBigQueryDataFrame # type: ignore[assignment] - yield df - - -def test_init_raises_if_anywidget_not_installed(): - with ( - mock.patch("bigframes.display.anywidget._ANYWIDGET_INSTALLED", False), - pytest.raises(ImportError), - ): - from bigframes.display.anywidget import TableWidget - - TableWidget(mock.Mock()) - - -def test_init_initializes_attributes(mock_df_deferred): - from bigframes.display.anywidget import TableWidget - - with ( - bigframes.option_context("display.render_mode", "anywidget"), - mock.patch.object(TableWidget, "_initial_load"), - ): - widget = TableWidget(mock_df_deferred) - - assert widget._dataframe is mock_df_deferred - assert widget.page == 0 - assert widget.page_size > 0 - assert widget.orderable_columns == [ - "A", - "B", - "C", - "D", - ] - - -def test_init_calls_initial_load(mock_df_deferred): - from bigframes.display.anywidget import TableWidget - - with mock.patch.object(TableWidget, "_initial_load") as mock_load: - TableWidget(mock_df_deferred) - mock_load.assert_called_once() - - -def test_validate_page_clamping(mock_df_deferred): - from bigframes.display.anywidget import TableWidget - - with mock.patch.object(TableWidget, "_initial_load"): - widget = TableWidget(mock_df_deferred) - widget.row_count = 100 - widget.page_size = 10 - - widget.page = 5 - assert widget.page == 5 - - with pytest.raises(ValueError): - widget.page = -1 - - widget.page = 100 - assert widget.page == 9 - - -def test_validate_page_size(mock_df_deferred): - from bigframes.display.anywidget import TableWidget - - with ( - bigframes.option_context("display.render_mode", "anywidget"), - mock.patch.object(TableWidget, "_initial_load"), - ): - widget = TableWidget(mock_df_deferred) - - widget.page_size = 50 - assert widget.page_size == 50 - - original_size = widget.page_size - widget.page_size = -5 - assert widget.page_size == original_size - - widget.page_size = 10000 - assert widget.page_size == 1000 - - -def test_page_size_change_resets_page_and_sort(mock_df_deferred): - from bigframes.display.anywidget import TableWidget - - with mock.patch.object(TableWidget, "_initial_load"): - widget = TableWidget(mock_df_deferred) - widget._initial_load_complete = True - widget.page = 5 - widget.sort_context = [{"column": "A", "ascending": True}] - - widget.page_size = 20 - - assert widget.page == 0 - assert widget.sort_context == [] - - -def test_page_size_change_resets_batches(mock_df_deferred): - from bigframes.display.anywidget import TableWidget - - with mock.patch.object(TableWidget, "_initial_load"): - widget = TableWidget(mock_df_deferred) - widget._initial_load_complete = True - - widget.page_size = 50 - - mock_df_deferred.to_pandas_batches.assert_called() - - -def test_sort_change_resets_batches(mock_df_deferred): - from bigframes.display.anywidget import TableWidget - - with ( - bigframes.option_context("display.render_mode", "anywidget"), - mock.patch.object(TableWidget, "_initial_load"), - ): - widget = TableWidget(mock_df_deferred) - widget._initial_load_complete = True - - mock_df_deferred.to_pandas_batches.reset_mock() - - widget.sort_context = [{"column": "B", "ascending": False}] - - assert mock_df_deferred.to_pandas_batches.call_count >= 1 - - -def test_deferred_mode_initialization(mock_deferred_df): - from bigframes.display.anywidget import TableWidget - - with mock.patch.object(TableWidget, "_initial_load") as mock_load: - widget = TableWidget(mock_deferred_df) - - assert widget.is_deferred_mode is True - mock_load.assert_not_called() - - -def test_deferred_mode_execution(mock_deferred_df, mock_df_deferred): - from bigframes.display.anywidget import TableWidget - - mock_deferred_df.execute.return_value = mock_df_deferred - - widget = TableWidget(mock_deferred_df) - - assert widget.is_deferred_mode is True - - import bigframes - - with bigframes.option_context( - "display.render_mode", bigframes.options.display.render_mode - ): - widget.start_execution = True - - thread = getattr(widget, "_execution_thread", None) - if thread is not None: - thread.join(timeout=5) - - mock_deferred_df.execute.assert_called_once() - mock_df_deferred.to_pandas_batches.assert_called_once() - assert widget.is_deferred_mode is False - - -def test_deferred_mode_execution_updates_table_html(mock_deferred_df, mock_df_deferred): - from bigframes.display.anywidget import TableWidget - - mock_deferred_df.execute.return_value = mock_df_deferred - - batches = mock.MagicMock() - batch_df = pd.DataFrame({"A": [1], "B": ["a"], "C": [1.0], "D": [True]}) - batches.__iter__.return_value = iter([batch_df]) - batches.total_rows = 1 - mock_df_deferred.to_pandas_batches.return_value = batches - - with bigframes.option_context("display.render_mode", "anywidget"): - widget = TableWidget(mock_deferred_df) - widget.is_deferred_mode = True - widget._deferred_dataframe = mock_deferred_df - assert widget.table_html == "" - - widget.start_execution = True - thread = getattr(widget, "_execution_thread", None) - if thread is not None: - thread.join(timeout=5) - - assert widget.is_deferred_mode is False - assert widget.table_html != "" - assert "table" in widget.table_html - - -def test_deferred_mode_execution_error(mock_deferred_df): - from bigframes.display.anywidget import TableWidget - - mock_deferred_df.execute.side_effect = RuntimeError("Query Failed") - - with mock.patch.object(TableWidget, "_initial_load"): - widget = TableWidget(mock_deferred_df) - - import bigframes - - with bigframes.option_context( - "display.render_mode", bigframes.options.display.render_mode - ): - widget.start_execution = True - - thread = getattr(widget, "_execution_thread", None) - if thread is not None: - thread.join(timeout=5) - - assert widget.is_deferred_mode is True - assert widget._error_message == "Query Failed" - - -def test_deferred_mode_execution_does_not_reset_page_on_navigation( - mock_deferred_df, mock_df_deferred -): - from bigframes.display.anywidget import TableWidget - - mock_deferred_df.execute.return_value = mock_df_deferred - - batches = mock.MagicMock() - batch_df = pd.DataFrame({"A": [1], "B": ["a"], "C": [1.0], "D": [True]}) - batches.__iter__.return_value = iter([batch_df]) - batches.total_rows = 50 - mock_df_deferred.to_pandas_batches.return_value = batches - - with bigframes.option_context("display.render_mode", "anywidget"): - widget = TableWidget(mock_deferred_df) - widget.page_size = 10 - widget.start_execution = True - - thread = getattr(widget, "_execution_thread", None) - if thread is not None: - thread.join(timeout=5) - - assert widget.page == 0 - widget.page = 1 - assert widget.page == 1 - - -def test_deferred_mode_execution_in_colab(mock_deferred_df, mock_df_deferred): - import sys - - from bigframes.display.anywidget import TableWidget - - mock_deferred_df.execute.return_value = mock_df_deferred - - batches = mock.MagicMock() - batch_df = pd.DataFrame({"A": [1], "B": ["a"], "C": [1.0], "D": [True]}) - batches.__iter__.return_value = iter([batch_df]) - batches.total_rows = 1 - mock_df_deferred.to_pandas_batches.return_value = batches - - with ( - mock.patch.dict(sys.modules, {"google.colab": mock.MagicMock()}), - bigframes.option_context("display.render_mode", "anywidget"), - ): - widget = TableWidget(mock_deferred_df) - widget.is_deferred_mode = True - - widget.start_execution = True - - thread = getattr(widget, "_execution_thread", None) - if thread is not None: - thread.join(timeout=5) - - assert widget.is_deferred_mode is True - assert widget.table_html == "" - - # Simulate frontend ping callback - widget.ping = 1 - - assert widget.is_deferred_mode is False - assert widget.table_html != "" diff --git a/tests/unit/display/test_html.py b/tests/unit/display/test_html.py deleted file mode 100644 index 239033861a4..00000000000 --- a/tests/unit/display/test_html.py +++ /dev/null @@ -1,222 +0,0 @@ -# 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. -# 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 datetime -from unittest.mock import Mock, patch - -import pandas as pd -import pyarrow as pa -import pytest - -import bigframes as bf -import bigframes.display.html as bf_html - - -@pytest.mark.parametrize( - ("data", "expected_alignments", "expected_strings"), - [ - pytest.param( - { - "string_col": ["a", "b", "c"], - "int_col": [1, 2, 3], - "float_col": [1.1, 2.2, 3.3], - "bool_col": [True, False, True], - }, - { - "string_col": "left", - "int_col": "right", - "float_col": "right", - "bool_col": "left", - }, - ["1.100000", "2.200000", "3.300000"], - id="scalars", - ), - pytest.param( - { - "timestamp_col": pa.array( - [ - datetime.datetime.fromisoformat(value) - for value in [ - "2024-01-01 00:00:00", - "2024-01-01 00:00:01", - "2024-01-01 00:00:02", - ] - ], - pa.timestamp("us", tz="UTC"), - ), - "datetime_col": pa.array( - [ - datetime.datetime.fromisoformat(value) - for value in [ - "2027-06-05 04:03:02.001", - "2027-01-01 00:00:01", - "2027-01-01 00:00:02", - ] - ], - pa.timestamp("us"), - ), - "date_col": pa.array( - [ - datetime.date(1999, 1, 1), - datetime.date(1999, 1, 2), - datetime.date(1999, 1, 3), - ], - pa.date32(), - ), - "time_col": pa.array( - [ - datetime.time(11, 11, 0), - datetime.time(11, 11, 1), - datetime.time(11, 11, 2), - ], - pa.time64("us"), - ), - }, - { - "timestamp_col": "left", - "datetime_col": "left", - "date_col": "left", - "time_col": "left", - }, - [ - "2024-01-01 00:00:00", - "2027-06-05 04:03:02.001", - "1999-01-01", - "11:11:01", - ], - id="datetimes", - ), - pytest.param( - { - "array_col": pd.Series( - [[1, 2, 3], [4, 5, 6], [7, 8, 9]], - dtype=pd.ArrowDtype(pa.list_(pa.int64())), - ), - }, - { - "array_col": "left", - }, - ["[1, 2, 3]", "[4, 5, 6]", "[7, 8, 9]"], - id="array", - ), - pytest.param( - { - "struct_col": pd.Series( - [{"v": 1}, {"v": 2}, {"v": 3}], - dtype=pd.ArrowDtype(pa.struct([("v", pa.int64())])), - ), - }, - { - "struct_col": "left", - }, - ["{'v': 1}", "{'v': 2}", "{'v': 3}"], - id="struct", - ), - ], -) -def test_render_html_alignment_and_precision( - data, expected_alignments, expected_strings -): - df = pd.DataFrame(data) - html = bf_html.render_html(dataframe=df, table_id="test-table") - - for align in expected_alignments.values(): - assert f'class="cell-align-{align}"' in html - - for expected_string in expected_strings: - assert expected_string in html - - -def test_render_html_precision(): - data = {"float_col": [3.14159265]} - df = pd.DataFrame(data) - - with bf.option_context("display.precision", 4): - html = bf_html.render_html(dataframe=df, table_id="test-table") - assert "3.1416" in html - - # Make sure we reset to default - html = bf_html.render_html(dataframe=df, table_id="test-table") - assert "3.141593" in html - - -def test_render_html_max_columns_truncation(): - # Create a DataFrame with 10 columns - data = {f"col_{i}": [i] for i in range(10)} - df = pd.DataFrame(data) - - # Test max_columns=4 - # max_columns=4 -> 2 left, 2 right. col_0, col_1 ... col_8, col_9 - html = bf_html.render_html(dataframe=df, table_id="test", max_columns=4) - - assert "col_0" in html - assert "col_1" in html - assert "col_2" not in html - assert "col_7" not in html - assert "col_8" in html - assert "col_9" in html - assert "..." in html - - # Test max_columns=3 - # 3 // 2 = 1. Left: col_0. Right: 3 - 1 = 2. col_8, col_9. - # Total displayed: col_0, ..., col_8, col_9. (3 data cols + 1 ellipsis) - html = bf_html.render_html(dataframe=df, table_id="test", max_columns=3) - assert "col_0" in html - assert "col_1" not in html - assert "col_7" not in html - assert "col_8" in html - assert "col_9" in html - - # Test max_columns=1 - # 1 // 2 = 0. Left: []. Right: 1. col_9. - # Total: ..., col_9. - html = bf_html.render_html(dataframe=df, table_id="test", max_columns=1) - assert "col_0" not in html - assert "col_8" not in html - assert "col_9" in html - assert "..." in html - - -def test_repr_mimebundle_head(): - mock_df = Mock() - mock_df.columns = ["col1"] - - mock_df._prepare_display_df.return_value = mock_df - - # Mock the call to retrieve_repr_request_results - pandas_df = pd.DataFrame({"col1": [1, 2, 3]}) - mock_df._block.retrieve_repr_request_results.return_value = ( - pandas_df, - 3, - Mock(), # query_job - ) - - # Mock _get_obj_metadata - with ( - patch("bigframes.display.html._get_obj_metadata", return_value=(False, False)), - patch( - "bigframes.display.html.create_html_representation", return_value="" - ) as mock_create_html, - patch( - "bigframes.display.plaintext.create_text_representation", - return_value="text", - ) as mock_create_text, - ): - bundle = bf_html.repr_mimebundle_head(mock_df) - - assert bundle == {"text/html": "", "text/plain": "text"} - mock_df._prepare_display_df.assert_called_once() - mock_df._block.retrieve_repr_request_results.assert_called_once() - mock_create_html.assert_called_once() - mock_create_text.assert_called_once() diff --git a/tests/unit/display/test_render_mode.py b/tests/unit/display/test_render_mode.py deleted file mode 100644 index 478bfd30eaf..00000000000 --- a/tests/unit/display/test_render_mode.py +++ /dev/null @@ -1,120 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import unittest.mock as mock - -import pytest - -import bigframes.display.html as bf_html -import bigframes.pandas as bpd - - -def test_render_mode_options(): - assert bpd.options.display.render_mode == "html" - - with bpd.option_context("display.render_mode", "plaintext"): - assert bpd.options.display.render_mode == "plaintext" - - with bpd.option_context("display.render_mode", "html"): - assert bpd.options.display.render_mode == "html" - - with bpd.option_context("display.render_mode", "anywidget"): - assert bpd.options.display.render_mode == "anywidget" - - -def test_repr_mimebundle_selection_logic(): - mock_obj = mock.Mock() - - # Mocking dependencies - with ( - mock.patch("bigframes.display.html.repr_mimebundle_head") as mock_head, - mock.patch("bigframes.display.html.get_anywidget_bundle") as mock_anywidget, - mock.patch("bigframes.display.html.repr_mimebundle_deferred") as mock_deferred, - ): - mock_head.side_effect = lambda obj: {"text/plain": "plain", "text/html": "html"} - mock_anywidget.return_value = ( - { - "application/vnd.jupyter.widget-view+json": {}, - "text/plain": "plain", - "text/html": "html", - }, - {}, - ) - mock_deferred.return_value = {"text/plain": "deferred"} - - # Test deferred repr_mode when anywidget is available - with bpd.option_context("display.repr_mode", "deferred"): - bundle = bf_html.repr_mimebundle(mock_obj) - assert "application/vnd.jupyter.widget-view+json" in bundle[0] - mock_anywidget.assert_called_once() - mock_deferred.assert_not_called() - - mock_anywidget.reset_mock() - - # Test fallback to static deferred repr when anywidget fails - mock_anywidget.side_effect = Exception("Anywidget failed") - with ( - bpd.option_context("display.repr_mode", "deferred"), - pytest.warns(UserWarning, match="Anywidget mode is not available"), - ): - bundle = bf_html.repr_mimebundle(mock_obj) - assert bundle == {"text/plain": "deferred"} - mock_deferred.assert_called_once() - - mock_anywidget.side_effect = None - mock_deferred.reset_mock() - mock_anywidget.reset_mock() - - # Test plaintext render_mode - with bpd.option_context("display.render_mode", "plaintext"): - bundle = bf_html.repr_mimebundle(mock_obj) - assert "text/plain" in bundle - assert "text/html" not in bundle - mock_head.assert_called_once() - - mock_head.reset_mock() - - # Test html render_mode - with bpd.option_context("display.render_mode", "html"): - bundle = bf_html.repr_mimebundle(mock_obj) - assert "text/plain" in bundle - assert "text/html" in bundle - mock_head.assert_called_once() - - mock_head.reset_mock() - - # Test anywidget render_mode - with bpd.option_context("display.render_mode", "anywidget"): - bundle = bf_html.repr_mimebundle(mock_obj) - assert "application/vnd.jupyter.widget-view+json" in bundle[0] - mock_anywidget.assert_called_once() - mock_head.assert_not_called() - - mock_anywidget.reset_mock() - - # Test anywidget repr_mode (backward compatibility) - with bpd.option_context("display.repr_mode", "anywidget"): - bundle = bf_html.repr_mimebundle(mock_obj) - assert "application/vnd.jupyter.widget-view+json" in bundle[0] - mock_anywidget.assert_called_once() - mock_head.assert_not_called() - - mock_anywidget.reset_mock() - - # Test default render_mode (should be "html") - bundle = bf_html.repr_mimebundle(mock_obj) - assert "text/plain" in bundle - assert "text/html" in bundle - mock_head.assert_called_once() - mock_anywidget.assert_not_called() diff --git a/tests/unit/extensions/__init__.py b/tests/unit/extensions/__init__.py deleted file mode 100644 index 58d482ea386..00000000000 --- a/tests/unit/extensions/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -# Copyright 2026 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/unit/extensions/bigframes/__init__.py b/tests/unit/extensions/bigframes/__init__.py deleted file mode 100644 index 58d482ea386..00000000000 --- a/tests/unit/extensions/bigframes/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -# Copyright 2026 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/unit/extensions/bigframes/test_series_accessor.py b/tests/unit/extensions/bigframes/test_series_accessor.py deleted file mode 100644 index 4c74b60a1a0..00000000000 --- a/tests/unit/extensions/bigframes/test_series_accessor.py +++ /dev/null @@ -1,78 +0,0 @@ -# Copyright 2026 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 typing import cast -from unittest.mock import MagicMock, patch - -import pytest - -import bigframes.series as series -from bigframes.testing import mocks - - -def test_bigframes_series_has_accessor(monkeypatch: pytest.MonkeyPatch): - # Arrange - from bigframes.extensions.bigframes.series_accessor import ( - BigframesBigQuerySeriesAccessor, - ) - - bf_df = mocks.create_dataframe(monkeypatch, data={"col": [1, 2]}) - bf_series = cast(series.Series, bf_df["col"]) - - # Act - has_bq = hasattr(bf_series, "bigquery") - bq_obj = bf_series.bigquery - - # Assert - assert has_bq - assert isinstance(bq_obj, BigframesBigQuerySeriesAccessor) - - -@patch("bigframes.operations.googlesql.global_namespace.array.array_length") -def test_bigframes_series_accessor_global_routing( - mock_array_length, monkeypatch: pytest.MonkeyPatch -): - # Arrange - bf_df = mocks.create_dataframe(monkeypatch, data={"col": [[1, 2], [3, 4, 5]]}) - bf_series = cast(series.Series, bf_df["col"]) - mock_result_series = MagicMock() - mock_array_length.return_value = mock_result_series - - # Act - result = bf_series.bigquery.array_length() - - # Assert - mock_array_length.assert_called_once_with(bf_series) - assert result is mock_result_series - - -@patch("bigframes.operations.googlesql.aead.encrypt") -def test_bigframes_series_accessor_namespaced_routing( - mock_encrypt, monkeypatch: pytest.MonkeyPatch -): - # Arrange - bf_df = mocks.create_dataframe(monkeypatch, data={"keyset": [b"key1", b"key2"]}) - keyset_series = cast(series.Series, bf_df["keyset"]) - mock_result_series = MagicMock() - mock_encrypt.return_value = mock_result_series - - plaintext = "my secret" - additional_data = "context" - - # Act - result = keyset_series.bigquery.aead.encrypt(plaintext, additional_data) - - # Assert - mock_encrypt.assert_called_once_with(keyset_series, plaintext, additional_data) - assert result is mock_result_series diff --git a/tests/unit/extensions/core/__init__.py b/tests/unit/extensions/core/__init__.py deleted file mode 100644 index 58d482ea386..00000000000 --- a/tests/unit/extensions/core/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -# Copyright 2026 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/unit/extensions/core/test_dataframe_accessor.py b/tests/unit/extensions/core/test_dataframe_accessor.py deleted file mode 100644 index c207070bb15..00000000000 --- a/tests/unit/extensions/core/test_dataframe_accessor.py +++ /dev/null @@ -1,525 +0,0 @@ -# Copyright 2026 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import unittest.mock as mock - -import pandas as pd - -import bigframes.bigquery.ai -import bigframes.pandas as bpd -import bigframes.session - - -def test_ai_forecast(monkeypatch): - session = mock.create_autospec(bigframes.session.Session) - bf_df = mock.create_autospec(bpd.DataFrame) - session.read_pandas.return_value = bf_df - - mock_forecast = mock.MagicMock() - forecast_result_df = mock.create_autospec(bpd.DataFrame) - mock_forecast.return_value = forecast_result_df - expected_result = mock.create_autospec(pd.DataFrame) - forecast_result_df.to_pandas.return_value = expected_result - - monkeypatch.setattr(bigframes.bigquery.ai, "forecast", mock_forecast) - - df = pd.DataFrame({"date": ["2020-01-01"], "value": [1.0]}) - actual_result = df.bigquery.ai.forecast( - timestamp_col="date", - data_col="value", - horizon=5, - session=session, - ) - - session.read_pandas.assert_called_once() - - mock_forecast.assert_called_once_with( - bf_df, - timestamp_col="date", - data_col="value", - model="TimesFM 2.0", - id_cols=None, - horizon=5, - confidence_level=0.95, - context_window=None, - output_historical_time_series=False, - ) - forecast_result_df.to_pandas.assert_called_once() - assert actual_result is expected_result - - -def test_bigframes_ai_forecast(scalar_types_df: bpd.DataFrame, monkeypatch): - session = mock.create_autospec(bigframes.session.Session) - forecast_result = mock.create_autospec(bpd.DataFrame) - mock_forecast = mock.MagicMock() - mock_forecast.return_value = forecast_result - - monkeypatch.setattr(bigframes.bigquery.ai, "forecast", mock_forecast) - - actual_result = scalar_types_df.bigquery.ai.forecast( - timestamp_col="date", - data_col="value", - horizon=5, - session=session, - ) - - session.read_pandas.assert_not_called() - mock_forecast.assert_called_once() - args, kwargs = mock_forecast.call_args - assert args[0] is scalar_types_df - assert kwargs == { - "timestamp_col": "date", - "data_col": "value", - "model": "TimesFM 2.0", - "id_cols": None, - "horizon": 5, - "confidence_level": 0.95, - "context_window": None, - "output_historical_time_series": False, - } - # BigFrames accessor returns the bf_df directly without calling to_pandas - forecast_result.to_pandas.assert_not_called() - assert actual_result is forecast_result - - -def test_ai_generate(monkeypatch): - mock_generate = mock.MagicMock() - result_series = mock.create_autospec(bpd.Series) - mock_generate.return_value = result_series - expected_result = mock.create_autospec(pd.Series) - result_series.to_pandas.return_value = expected_result - - monkeypatch.setattr(bigframes.bigquery.ai, "generate", mock_generate) - - prompt = mock.create_autospec(pd.Series) - df = pd.DataFrame({"text_input": ["Is this a positive review?"]}) - actual_result = df.bigquery.ai.generate( - prompt, - connection_id="conn", - endpoint="endpoint", - request_type="dedicated", - model_params={"temp": 0.5}, - output_schema={"res": "STRING"}, - ) - - mock_generate.assert_called_once_with( - prompt, - connection_id="conn", - endpoint="endpoint", - request_type="dedicated", - model_params={"temp": 0.5}, - output_schema={"res": "STRING"}, - ) - result_series.to_pandas.assert_called_once() - assert actual_result is expected_result - - -def test_bigframes_ai_generate(scalar_types_df: bpd.DataFrame, monkeypatch): - bf_series = mock.create_autospec(bpd.Series) - result_series = mock.create_autospec(bpd.Series) - - mock_generate = mock.MagicMock() - mock_generate.return_value = result_series - - monkeypatch.setattr(bigframes.bigquery.ai, "generate", mock_generate) - - actual_result = scalar_types_df.bigquery.ai.generate( - bf_series, - connection_id="conn", - endpoint="endpoint", - request_type="dedicated", - model_params={"temp": 0.5}, - output_schema={"res": "STRING"}, - ) - - mock_generate.assert_called_once() - args, kwargs = mock_generate.call_args - assert args[0] is bf_series - assert kwargs == { - "connection_id": "conn", - "endpoint": "endpoint", - "request_type": "dedicated", - "model_params": {"temp": 0.5}, - "output_schema": {"res": "STRING"}, - } - result_series.to_pandas.assert_not_called() - assert actual_result is result_series - - -def test_ai_generate_bool(monkeypatch): - mock_generate_bool = mock.MagicMock() - result_series = mock.create_autospec(bpd.Series) - mock_generate_bool.return_value = result_series - expected_result = mock.create_autospec(pd.Series) - result_series.to_pandas.return_value = expected_result - - monkeypatch.setattr(bigframes.bigquery.ai, "generate_bool", mock_generate_bool) - - prompt = mock.create_autospec(pd.Series) - df = pd.DataFrame({"text_input": ["Is this a positive review?"]}) - actual_result = df.bigquery.ai.generate_bool( - prompt, - connection_id="conn", - endpoint="endpoint", - request_type="dedicated", - model_params={"temp": 0.5}, - ) - - mock_generate_bool.assert_called_once_with( - prompt, - connection_id="conn", - endpoint="endpoint", - request_type="dedicated", - model_params={"temp": 0.5}, - ) - result_series.to_pandas.assert_called_once() - assert actual_result is expected_result - - -def test_bigframes_ai_generate_bool(scalar_types_df: bpd.DataFrame, monkeypatch): - bf_series = mock.create_autospec(bpd.Series) - result_series = mock.create_autospec(bpd.Series) - - mock_generate_bool = mock.MagicMock() - mock_generate_bool.return_value = result_series - - monkeypatch.setattr(bigframes.bigquery.ai, "generate_bool", mock_generate_bool) - - actual_result = scalar_types_df.bigquery.ai.generate_bool( - bf_series, - connection_id="conn", - endpoint="endpoint", - request_type="dedicated", - model_params={"temp": 0.5}, - ) - - mock_generate_bool.assert_called_once() - args, kwargs = mock_generate_bool.call_args - assert args[0] is bf_series - assert kwargs == { - "connection_id": "conn", - "endpoint": "endpoint", - "request_type": "dedicated", - "model_params": {"temp": 0.5}, - } - result_series.to_pandas.assert_not_called() - assert actual_result is result_series - - -def test_ai_generate_int(monkeypatch): - mock_generate_int = mock.MagicMock() - result_series = mock.create_autospec(bpd.Series) - mock_generate_int.return_value = result_series - expected_result = mock.create_autospec(pd.Series) - result_series.to_pandas.return_value = expected_result - - monkeypatch.setattr(bigframes.bigquery.ai, "generate_int", mock_generate_int) - - prompt = mock.create_autospec(pd.Series) - df = pd.DataFrame({"text_input": ["How many legs?"]}) - actual_result = df.bigquery.ai.generate_int( - prompt, - connection_id="conn", - endpoint="endpoint", - request_type="dedicated", - model_params={"temp": 0.5}, - ) - - mock_generate_int.assert_called_once_with( - prompt, - connection_id="conn", - endpoint="endpoint", - request_type="dedicated", - model_params={"temp": 0.5}, - ) - result_series.to_pandas.assert_called_once() - assert actual_result is expected_result - - -def test_bigframes_ai_generate_int(scalar_types_df: bpd.DataFrame, monkeypatch): - bf_series = mock.create_autospec(bpd.Series) - result_series = mock.create_autospec(bpd.Series) - - mock_generate_int = mock.MagicMock() - mock_generate_int.return_value = result_series - - monkeypatch.setattr(bigframes.bigquery.ai, "generate_int", mock_generate_int) - - actual_result = scalar_types_df.bigquery.ai.generate_int( - bf_series, - connection_id="conn", - endpoint="endpoint", - request_type="dedicated", - model_params={"temp": 0.5}, - ) - - mock_generate_int.assert_called_once() - args, kwargs = mock_generate_int.call_args - assert args[0] is bf_series - assert kwargs == { - "connection_id": "conn", - "endpoint": "endpoint", - "request_type": "dedicated", - "model_params": {"temp": 0.5}, - } - result_series.to_pandas.assert_not_called() - assert actual_result is result_series - - -def test_ai_generate_double(monkeypatch): - mock_generate_double = mock.MagicMock() - result_series = mock.create_autospec(bpd.Series) - mock_generate_double.return_value = result_series - expected_result = mock.create_autospec(pd.Series) - result_series.to_pandas.return_value = expected_result - - monkeypatch.setattr(bigframes.bigquery.ai, "generate_double", mock_generate_double) - - prompt = mock.create_autospec(pd.Series) - df = pd.DataFrame({"text_input": ["How tall?"]}) - actual_result = df.bigquery.ai.generate_double( - prompt, - connection_id="conn", - endpoint="endpoint", - request_type="dedicated", - model_params={"temp": 0.5}, - ) - - mock_generate_double.assert_called_once_with( - prompt, - connection_id="conn", - endpoint="endpoint", - request_type="dedicated", - model_params={"temp": 0.5}, - ) - result_series.to_pandas.assert_called_once() - assert actual_result is expected_result - - -def test_bigframes_ai_generate_double(scalar_types_df: bpd.DataFrame, monkeypatch): - bf_series = mock.create_autospec(bpd.Series) - result_series = mock.create_autospec(bpd.Series) - - mock_generate_double = mock.MagicMock() - mock_generate_double.return_value = result_series - - monkeypatch.setattr(bigframes.bigquery.ai, "generate_double", mock_generate_double) - - actual_result = scalar_types_df.bigquery.ai.generate_double( - bf_series, - connection_id="conn", - endpoint="endpoint", - request_type="dedicated", - model_params={"temp": 0.5}, - ) - - mock_generate_double.assert_called_once() - args, kwargs = mock_generate_double.call_args - assert args[0] is bf_series - assert kwargs == { - "connection_id": "conn", - "endpoint": "endpoint", - "request_type": "dedicated", - "model_params": {"temp": 0.5}, - } - result_series.to_pandas.assert_not_called() - assert actual_result is result_series - - -def test_ai_classify(monkeypatch): - mock_classify = mock.MagicMock() - result_series = mock.create_autospec(bpd.Series) - mock_classify.return_value = result_series - expected_result = mock.create_autospec(pd.Series) - result_series.to_pandas.return_value = expected_result - - monkeypatch.setattr(bigframes.bigquery.ai, "classify", mock_classify) - - input_prompt = mock.create_autospec(pd.Series) - df = pd.DataFrame({"text_input": ["Is this a positive review?"]}) - actual_result = df.bigquery.ai.classify( - input_prompt, - categories=["Mammal", "Fish"], - examples=[("Cat", "Mammal")], - connection_id="conn", - endpoint="endpoint", - output_mode="single", - optimization_mode="minimize_cost", - max_error_ratio=0.1, - ) - - mock_classify.assert_called_once_with( - input_prompt, - ["Mammal", "Fish"], - examples=[("Cat", "Mammal")], - connection_id="conn", - endpoint="endpoint", - output_mode="single", - optimization_mode="minimize_cost", - max_error_ratio=0.1, - ) - result_series.to_pandas.assert_called_once() - assert actual_result is expected_result - - -def test_bigframes_ai_classify(scalar_types_df: bpd.DataFrame, monkeypatch): - bf_series = mock.create_autospec(bpd.Series) - result_series = mock.create_autospec(bpd.Series) - - mock_classify = mock.MagicMock() - mock_classify.return_value = result_series - - monkeypatch.setattr(bigframes.bigquery.ai, "classify", mock_classify) - - actual_result = scalar_types_df.bigquery.ai.classify( - bf_series, - categories=["Mammal", "Fish"], - examples=[("Cat", "Mammal")], - connection_id="conn", - endpoint="endpoint", - output_mode="single", - optimization_mode="minimize_cost", - max_error_ratio=0.1, - ) - - mock_classify.assert_called_once() - args, kwargs = mock_classify.call_args - assert args[0] is bf_series - assert args[1] == ["Mammal", "Fish"] - assert kwargs == { - "examples": [("Cat", "Mammal")], - "connection_id": "conn", - "endpoint": "endpoint", - "output_mode": "single", - "optimization_mode": "minimize_cost", - "max_error_ratio": 0.1, - } - result_series.to_pandas.assert_not_called() - assert actual_result is result_series - - -def test_ai_if(monkeypatch): - mock_if = mock.MagicMock() - result_series = mock.create_autospec(bpd.Series) - mock_if.return_value = result_series - expected_result = mock.create_autospec(pd.Series) - result_series.to_pandas.return_value = expected_result - - monkeypatch.setattr(bigframes.bigquery.ai, "if_", mock_if) - - prompt = mock.create_autospec(pd.Series) - df = pd.DataFrame({"text_input": ["Is this a positive review?"]}) - actual_result = df.bigquery.ai.if_( - prompt, - connection_id="conn", - endpoint="endpoint", - optimization_mode="minimize_cost", - max_error_ratio=0.1, - ) - - mock_if.assert_called_once_with( - prompt, - connection_id="conn", - endpoint="endpoint", - optimization_mode="minimize_cost", - max_error_ratio=0.1, - ) - result_series.to_pandas.assert_called_once() - assert actual_result is expected_result - - -def test_bigframes_ai_if(scalar_types_df: bpd.DataFrame, monkeypatch): - bf_series = mock.create_autospec(bpd.Series) - result_series = mock.create_autospec(bpd.Series) - - mock_if = mock.MagicMock() - mock_if.return_value = result_series - - monkeypatch.setattr(bigframes.bigquery.ai, "if_", mock_if) - - actual_result = scalar_types_df.bigquery.ai.if_( - bf_series, - connection_id="conn", - endpoint="endpoint", - optimization_mode="minimize_cost", - max_error_ratio=0.1, - ) - - mock_if.assert_called_once() - args, kwargs = mock_if.call_args - assert args[0] is bf_series - assert kwargs == { - "connection_id": "conn", - "endpoint": "endpoint", - "optimization_mode": "minimize_cost", - "max_error_ratio": 0.1, - } - result_series.to_pandas.assert_not_called() - assert actual_result is result_series - - -def test_ai_score(monkeypatch): - mock_score = mock.MagicMock() - result_series = mock.create_autospec(bpd.Series) - mock_score.return_value = result_series - expected_result = mock.create_autospec(pd.Series) - result_series.to_pandas.return_value = expected_result - - monkeypatch.setattr(bigframes.bigquery.ai, "score", mock_score) - - prompt = mock.create_autospec(pd.Series) - df = pd.DataFrame({"text_input": ["Is this a positive review?"]}) - actual_result = df.bigquery.ai.score( - prompt, - connection_id="conn", - endpoint="endpoint", - max_error_ratio=0.1, - ) - - mock_score.assert_called_once_with( - prompt, - connection_id="conn", - endpoint="endpoint", - max_error_ratio=0.1, - ) - result_series.to_pandas.assert_called_once() - assert actual_result is expected_result - - -def test_bigframes_ai_score(scalar_types_df: bpd.DataFrame, monkeypatch): - bf_series = mock.create_autospec(bpd.Series) - result_series = mock.create_autospec(bpd.Series) - - mock_score = mock.MagicMock() - mock_score.return_value = result_series - - monkeypatch.setattr(bigframes.bigquery.ai, "score", mock_score) - - actual_result = scalar_types_df.bigquery.ai.score( - bf_series, - connection_id="conn", - endpoint="endpoint", - max_error_ratio=0.1, - ) - - mock_score.assert_called_once() - args, kwargs = mock_score.call_args - assert args[0] is bf_series - assert kwargs == { - "connection_id": "conn", - "endpoint": "endpoint", - "max_error_ratio": 0.1, - } - result_series.to_pandas.assert_not_called() - assert actual_result is result_series diff --git a/tests/unit/extensions/core/test_series_mixins.py b/tests/unit/extensions/core/test_series_mixins.py deleted file mode 100644 index c6e7e4078b1..00000000000 --- a/tests/unit/extensions/core/test_series_mixins.py +++ /dev/null @@ -1,384 +0,0 @@ -# Copyright 2026 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import unittest.mock as mock - -import pandas as pd - -import bigframes.bigquery.ai -import bigframes.pandas as bpd -import bigframes.session - - -def test_ai_generate_embedding(monkeypatch): - session = mock.create_autospec(bigframes.session.Session) - bf_series = mock.create_autospec(bpd.Series) - session.read_pandas.return_value = bf_series - - mock_generate_embedding = mock.MagicMock() - result_df = mock.create_autospec(bpd.DataFrame) - mock_generate_embedding.return_value = result_df - expected_result = mock.create_autospec(pd.DataFrame) - result_df.to_pandas.return_value = expected_result - - monkeypatch.setattr( - bigframes.bigquery.ai, "generate_embedding", mock_generate_embedding - ) - - series = pd.Series(["apple"], name="content") - actual_result = series.bigquery.ai.generate_embedding( # type: ignore - model="my_model", - output_dimensionality=256, - task_type="retrieval_document", - start_second=1.0, - end_second=2.0, - interval_seconds=3.0, - trial_id=4, - session=session, - ) - - session.read_pandas.assert_called_once() - mock_generate_embedding.assert_called_once_with( - "my_model", - bf_series, - output_dimensionality=256, - task_type="retrieval_document", - start_second=1.0, - end_second=2.0, - interval_seconds=3.0, - trial_id=4, - ) - result_df.to_pandas.assert_called_once() - assert actual_result is expected_result - - -def test_bigframes_ai_generate_embedding(scalar_types_df: bpd.DataFrame, monkeypatch): - session = mock.create_autospec(bigframes.session.Session) - result_df = mock.create_autospec(bpd.DataFrame) - - mock_generate_embedding = mock.MagicMock() - mock_generate_embedding.return_value = result_df - - monkeypatch.setattr( - bigframes.bigquery.ai, "generate_embedding", mock_generate_embedding - ) - - scalar_types_series = scalar_types_df["string_col"] - actual_result = scalar_types_series.bigquery.ai.generate_embedding( - model="my_model", - output_dimensionality=256, - session=session, - ) - - session.read_pandas.assert_not_called() - mock_generate_embedding.assert_called_once() - args, kwargs = mock_generate_embedding.call_args - assert args[0] == "my_model" - assert args[1] is scalar_types_series - assert kwargs == { - "output_dimensionality": 256, - "task_type": None, - "start_second": None, - "end_second": None, - "interval_seconds": None, - "trial_id": None, - } - result_df.to_pandas.assert_not_called() - assert actual_result is result_df - - -def test_ai_generate_text(monkeypatch): - session = mock.create_autospec(bigframes.session.Session) - bf_series = mock.create_autospec(bpd.Series) - session.read_pandas.return_value = bf_series - - mock_generate_text = mock.MagicMock() - result_df = mock.create_autospec(bpd.DataFrame) - mock_generate_text.return_value = result_df - expected_result = mock.create_autospec(pd.DataFrame) - result_df.to_pandas.return_value = expected_result - - monkeypatch.setattr(bigframes.bigquery.ai, "generate_text", mock_generate_text) - - series = pd.Series(["write a poem"], name="prompt") - actual_result = series.bigquery.ai.generate_text( # type: ignore - model="my_model", - temperature=0.7, - max_output_tokens=100, - top_k=50, - top_p=0.9, - stop_sequences=["\n"], - ground_with_google_search=True, - request_type="dedicated", - session=session, - ) - - session.read_pandas.assert_called_once() - mock_generate_text.assert_called_once_with( - "my_model", - bf_series, - temperature=0.7, - max_output_tokens=100, - top_k=50, - top_p=0.9, - stop_sequences=["\n"], - ground_with_google_search=True, - request_type="dedicated", - ) - result_df.to_pandas.assert_called_once() - assert actual_result is expected_result - - -def test_bigframes_ai_generate_text(scalar_types_df: bpd.DataFrame, monkeypatch): - session = mock.create_autospec(bigframes.session.Session) - result_df = mock.create_autospec(bpd.DataFrame) - - mock_generate_text = mock.MagicMock() - mock_generate_text.return_value = result_df - - monkeypatch.setattr(bigframes.bigquery.ai, "generate_text", mock_generate_text) - - scalar_types_series = scalar_types_df["string_col"] - actual_result = scalar_types_series.bigquery.ai.generate_text( - model="my_model", - temperature=0.7, - session=session, - ) - - session.read_pandas.assert_not_called() - mock_generate_text.assert_called_once() - args, kwargs = mock_generate_text.call_args - assert args[0] == "my_model" - assert args[1] is scalar_types_series - assert kwargs == { - "temperature": 0.7, - "max_output_tokens": None, - "top_k": None, - "top_p": None, - "stop_sequences": None, - "ground_with_google_search": None, - "request_type": None, - } - result_df.to_pandas.assert_not_called() - assert actual_result is result_df - - -def test_ai_generate_table(monkeypatch): - session = mock.create_autospec(bigframes.session.Session) - bf_series = mock.create_autospec(bpd.Series) - session.read_pandas.return_value = bf_series - - mock_generate_table = mock.MagicMock() - result_df = mock.create_autospec(bpd.DataFrame) - mock_generate_table.return_value = result_df - expected_result = mock.create_autospec(pd.DataFrame) - result_df.to_pandas.return_value = expected_result - - monkeypatch.setattr(bigframes.bigquery.ai, "generate_table", mock_generate_table) - - series = pd.Series(["generate something"], name="prompt") - actual_result = series.bigquery.ai.generate_table( # type: ignore - model="my_model", - output_schema="category STRING", - temperature=0.7, - top_p=0.9, - max_output_tokens=100, - stop_sequences=["\n"], - request_type="dedicated", - session=session, - ) - - session.read_pandas.assert_called_once() - mock_generate_table.assert_called_once_with( - "my_model", - bf_series, - output_schema="category STRING", - temperature=0.7, - top_p=0.9, - max_output_tokens=100, - stop_sequences=["\n"], - request_type="dedicated", - ) - result_df.to_pandas.assert_called_once() - assert actual_result is expected_result - - -def test_bigframes_ai_generate_table(scalar_types_df: bpd.DataFrame, monkeypatch): - session = mock.create_autospec(bigframes.session.Session) - result_df = mock.create_autospec(bpd.DataFrame) - - mock_generate_table = mock.MagicMock() - mock_generate_table.return_value = result_df - - monkeypatch.setattr(bigframes.bigquery.ai, "generate_table", mock_generate_table) - - scalar_types_series = scalar_types_df["string_col"] - actual_result = scalar_types_series.bigquery.ai.generate_table( - model="my_model", - output_schema="category STRING", - temperature=0.7, - session=session, - ) - - session.read_pandas.assert_not_called() - mock_generate_table.assert_called_once() - args, kwargs = mock_generate_table.call_args - assert args[0] == "my_model" - assert args[1] is scalar_types_series - assert kwargs == { - "output_schema": "category STRING", - "temperature": 0.7, - "top_p": None, - "max_output_tokens": None, - "stop_sequences": None, - "request_type": None, - } - result_df.to_pandas.assert_not_called() - assert actual_result is result_df - - -def test_ai_embed(monkeypatch): - session = mock.create_autospec(bigframes.session.Session) - bf_series = mock.create_autospec(bpd.Series) - session.read_pandas.return_value = bf_series - - mock_embed = mock.MagicMock() - result_series = mock.create_autospec(bpd.Series) - mock_embed.return_value = result_series - expected_result = mock.create_autospec(pd.Series) - result_series.to_pandas.return_value = expected_result - - monkeypatch.setattr(bigframes.bigquery.ai, "embed", mock_embed) - - series = pd.Series(["hello world"], name="content") - actual_result = series.bigquery.ai.embed( # type: ignore - endpoint="my_endpoint", - model="my_model", - task_type="retrieval_query", - title="my_title", - model_params={"key": "val"}, - connection_id="my_connection", - session=session, - ) - - session.read_pandas.assert_called_once() - mock_embed.assert_called_once_with( - bf_series, - endpoint="my_endpoint", - model="my_model", - task_type="retrieval_query", - title="my_title", - model_params={"key": "val"}, - connection_id="my_connection", - ) - result_series.to_pandas.assert_called_once() - assert actual_result is expected_result - - -def test_bigframes_ai_embed(scalar_types_df: bpd.DataFrame, monkeypatch): - session = mock.create_autospec(bigframes.session.Session) - result_series = mock.create_autospec(bpd.Series) - - mock_embed = mock.MagicMock() - mock_embed.return_value = result_series - - monkeypatch.setattr(bigframes.bigquery.ai, "embed", mock_embed) - - scalar_types_series = scalar_types_df["string_col"] - actual_result = scalar_types_series.bigquery.ai.embed( - endpoint="my_endpoint", - session=session, - ) - - session.read_pandas.assert_not_called() - mock_embed.assert_called_once() - args, kwargs = mock_embed.call_args - assert args[0] is scalar_types_series - assert kwargs == { - "endpoint": "my_endpoint", - "model": None, - "task_type": None, - "title": None, - "model_params": None, - "connection_id": None, - } - result_series.to_pandas.assert_not_called() - assert actual_result is result_series - - -def test_ai_similarity(monkeypatch): - session = mock.create_autospec(bigframes.session.Session) - bf_series = mock.create_autospec(bpd.Series) - session.read_pandas.return_value = bf_series - - mock_similarity = mock.MagicMock() - result_series = mock.create_autospec(bpd.Series) - mock_similarity.return_value = result_series - expected_result = mock.create_autospec(pd.Series) - result_series.to_pandas.return_value = expected_result - - monkeypatch.setattr(bigframes.bigquery.ai, "similarity", mock_similarity) - - series = pd.Series(["apple"], name="content") - actual_result = series.bigquery.ai.similarity( # type: ignore - "banana", - endpoint="my_endpoint", - model="my_model", - model_params={"key": "val"}, - connection_id="my_connection", - session=session, - ) - - session.read_pandas.assert_called_once() - mock_similarity.assert_called_once_with( - bf_series, - "banana", - endpoint="my_endpoint", - model="my_model", - model_params={"key": "val"}, - connection_id="my_connection", - ) - result_series.to_pandas.assert_called_once() - assert actual_result is expected_result - - -def test_bigframes_ai_similarity(scalar_types_df: bpd.DataFrame, monkeypatch): - session = mock.create_autospec(bigframes.session.Session) - result_series = mock.create_autospec(bpd.Series) - - mock_similarity = mock.MagicMock() - mock_similarity.return_value = result_series - - monkeypatch.setattr(bigframes.bigquery.ai, "similarity", mock_similarity) - - scalar_types_series = scalar_types_df["string_col"] - actual_result = scalar_types_series.bigquery.ai.similarity( - "other_text", - endpoint="my_endpoint", - session=session, - ) - - session.read_pandas.assert_not_called() - mock_similarity.assert_called_once() - args, kwargs = mock_similarity.call_args - assert args[0] is scalar_types_series - assert args[1] == "other_text" - assert kwargs == { - "endpoint": "my_endpoint", - "model": None, - "model_params": None, - "connection_id": None, - } - result_series.to_pandas.assert_not_called() - assert actual_result is result_series diff --git a/tests/unit/extensions/pandas/__init__.py b/tests/unit/extensions/pandas/__init__.py deleted file mode 100644 index 58d482ea386..00000000000 --- a/tests/unit/extensions/pandas/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -# Copyright 2026 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/unit/extensions/pandas/test_registration.py b/tests/unit/extensions/pandas/test_registration.py deleted file mode 100644 index 7007d6f9f2f..00000000000 --- a/tests/unit/extensions/pandas/test_registration.py +++ /dev/null @@ -1,29 +0,0 @@ -# Copyright 2026 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 as pd - -# Importing bigframes registers the accessor. -import bigframes # noqa: F401 - - -def test_bigframes_import_registers_accessor(): - df = pd.DataFrame({"a": [1]}) - # If bigframes was imported, df.bigquery should exist - assert hasattr(df, "bigquery") - from bigframes.extensions.pandas.dataframe_accessor import ( - PandasBigQueryDataFrameAccessor, - ) - - assert isinstance(df.bigquery, PandasBigQueryDataFrameAccessor) diff --git a/tests/unit/extensions/pandas/test_series_accessor.py b/tests/unit/extensions/pandas/test_series_accessor.py deleted file mode 100644 index bfb68323f6d..00000000000 --- a/tests/unit/extensions/pandas/test_series_accessor.py +++ /dev/null @@ -1,136 +0,0 @@ -# Copyright 2026 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 unittest.mock import MagicMock, patch - -import pandas as pd - -import bigframes # noqa: F401 registers pandas extensions -import bigframes.series as series - - -def test_pandas_series_registers_accessor(): - # Arrange - from bigframes.extensions.pandas.series_accessor import ( - PandasBigQuerySeriesAccessor, - ) - - s = pd.Series([1, 2]) - - # Act - has_bq = hasattr(s, "bigquery") - bq_obj = s.bigquery - - # Assert - assert has_bq - assert isinstance(bq_obj, PandasBigQuerySeriesAccessor) - - -@patch("bigframes.operations.googlesql.global_namespace.array.array_length") -def test_pandas_series_accessor_global_routing(mock_array_length): - # Arrange - mock_bf_series = MagicMock() - mock_bf_series.to_pandas.return_value = pd.Series([2, 3]) - mock_array_length.return_value = mock_bf_series - mock_session = MagicMock() - mock_bf_self = MagicMock() - mock_session.read_pandas.return_value = mock_bf_self - - s = pd.Series([[1, 2], [3, 4, 5]]) - - # Act - result = s.bigquery.array_length(session=mock_session) - - # Assert - mock_session.read_pandas.assert_called_once_with(s) - mock_array_length.assert_called_once_with(mock_bf_self) - mock_bf_series.to_pandas.assert_called_once_with(ordered=True) - pd.testing.assert_series_equal(result, pd.Series([2, 3])) - - -@patch("bigframes.operations.googlesql.aead.encrypt") -def test_pandas_series_accessor_namespaced_routing(mock_encrypt): - # Arrange - mock_bf_series = MagicMock() - mock_bf_series.to_pandas.return_value = pd.Series([b"encrypted1", b"encrypted2"]) - mock_encrypt.return_value = mock_bf_series - mock_session = MagicMock() - mock_bf_self = MagicMock() - mock_session.read_pandas.return_value = mock_bf_self - - keyset_series = pd.Series([b"key1", b"key2"]) - plaintext = "my secret" - additional_data = "context" - - # Act - result = keyset_series.bigquery.aead.encrypt( # type: ignore - plaintext, additional_data, session=mock_session - ) - - # Assert - mock_session.read_pandas.assert_called_once_with(keyset_series) - mock_encrypt.assert_called_once_with(mock_bf_self, plaintext, additional_data) - mock_bf_series.to_pandas.assert_called_once_with(ordered=True) - pd.testing.assert_series_equal(result, pd.Series([b"encrypted1", b"encrypted2"])) - - -@patch("bigframes.operations.googlesql.global_namespace.array.array_concat") -def test_pandas_series_accessor_global_routing_uses_series_session(mock_array_concat): - # Arrange - mock_bf_series = MagicMock() - mock_bf_series.to_pandas.return_value = pd.Series([[1, 2, 3, 4]]) - mock_array_concat.return_value = mock_bf_series - mock_session = MagicMock() - mock_bf_other = MagicMock(spec=series.Series) - mock_bf_other._session = mock_session - mock_bf_self = MagicMock() - mock_session.read_pandas.return_value = mock_bf_self - s = pd.Series([[1, 2]]) - - # Act - result = s.bigquery.array_concat(mock_bf_other) - - # Assert - assert result is not None - mock_session.read_pandas.assert_called_once_with(s) - mock_array_concat.assert_called_once_with(mock_bf_self, mock_bf_other) - - -@patch("bigframes.operations.googlesql.aead.encrypt") -def test_pandas_series_accessor_namespaced_routing_uses_series_session( - mock_encrypt, -): - # Arrange - mock_bf_series = MagicMock() - mock_bf_series.to_pandas.return_value = pd.Series([b"encrypted1", b"encrypted2"]) - mock_encrypt.return_value = mock_bf_series - mock_session = MagicMock() - mock_bf_plaintext = MagicMock(spec=series.Series) - mock_bf_plaintext._session = mock_session - mock_bf_self = MagicMock() - mock_session.read_pandas.return_value = mock_bf_self - keyset_series = pd.Series([b"key1", b"key2"]) - additional_data = "context" - - # Act - result = keyset_series.bigquery.aead.encrypt( # type: ignore - mock_bf_plaintext, additional_data - ) - - # Assert - assert result is not None - mock_session.read_pandas.assert_called_once_with(keyset_series) - mock_encrypt.assert_called_once_with( - mock_bf_self, mock_bf_plaintext, additional_data - ) diff --git a/tests/unit/functions/__init__.py b/tests/unit/functions/__init__.py deleted file mode 100644 index 0a2669d7a25..00000000000 --- a/tests/unit/functions/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. diff --git a/tests/unit/functions/test_function_template.py b/tests/unit/functions/test_function_template.py deleted file mode 100644 index 11db01ed9ee..00000000000 --- a/tests/unit/functions/test_function_template.py +++ /dev/null @@ -1,193 +0,0 @@ -# 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. -# 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 json - -import pandas as pd -import pandas.testing -import pyarrow -import pytest - -import bigframes.dtypes -import bigframes.functions.function_template as bff_template - -HELLO_WORLD_BASE64_BYTES = b"SGVsbG8sIFdvcmxkIQ==" -HELLO_WORLD_BASE64_STR = "SGVsbG8sIFdvcmxkIQ==" - - -@pytest.mark.parametrize( - ["type_", "json_value", "expected"], - ( - pytest.param( - # Type names should match those in BigQueryType.from_ibis in - # third_party/bigframes_vendored/ibis/backends/bigquery/datatypes.py - "BOOLEAN", - True, - True, - ), - pytest.param( - "BYTES", - HELLO_WORLD_BASE64_STR, - b"Hello, World!", - ), - pytest.param( - "FLOAT64", - 1.25, - 1.25, - ), - pytest.param( - "INT64", - 123, - 123, - ), - pytest.param( - "STRING", - "Hello, World!", - "Hello, World!", - ), - ), -) -def test_convert_from_bq_json(type_, json_value, expected): - got = bff_template.convert_from_bq_json(type_, json_value) - assert got == expected - - -@pytest.mark.parametrize( - "type_", - [ - # Type names should match those in BigQueryType.from_ibis in - # third_party/bigframes_vendored/ibis/backends/bigquery/datatypes.py - "BOOLEAN", - "BYTES", - "FLOAT64", - "INT64", - "STRING", - ], -) -def test_convert_from_bq_json_none(type_): - got = bff_template.convert_from_bq_json(type_, None) - assert got is None - - -@pytest.mark.parametrize( - ["type_", "value", "expected"], - ( - pytest.param( - # Type names should match those in BigQueryType.from_ibis in - # third_party/bigframes_vendored/ibis/backends/bigquery/datatypes.py - "BOOLEAN", - True, - True, - ), - pytest.param( - "BYTES", - b"Hello, World!", - HELLO_WORLD_BASE64_STR, - ), - pytest.param( - "FLOAT64", - 1.25, - 1.25, - ), - pytest.param( - "INT64", - 123, - 123, - ), - pytest.param( - "STRING", - "Hello, World!", - "Hello, World!", - ), - ), -) -def test_convert_to_bq_json(type_, value, expected): - got = bff_template.convert_to_bq_json(type_, value) - assert got == expected - - -@pytest.mark.parametrize( - "type_", - [ - # Type names should match those in BigQueryType.from_ibis in - # third_party/bigframes_vendored/ibis/backends/bigquery/datatypes.py - "BOOLEAN", - "BYTES", - "FLOAT64", - "INT64", - "STRING", - ], -) -def test_convert_to_bq_json_none(type_): - got = bff_template.convert_to_bq_json(type_, None) - assert got is None - - -@pytest.mark.parametrize( - ["row_json", "expected"], - ( - pytest.param( - json.dumps( - { - "names": ["'my-index'", "'col1'", "'col2'", "'col3'"], - "types": ["string", "Int64", "Int64", "Int64"], - "values": ["my-index-value", "1", None, "-1"], - "indexlength": 1, - "dtype": "Int64", - } - ), - pd.Series( - [1, pd.NA, -1], - dtype="Int64", - index=["col1", "col2", "col3"], - name="my-index-value", - ), - id="int64-string-index", - ), - pytest.param( - json.dumps( - { - "names": ["'col1'", "'col2'", "'col3'"], - "types": ["binary[pyarrow]", "binary[pyarrow]", "binary[pyarrow]"], - "values": [HELLO_WORLD_BASE64_STR, "dGVzdDI=", "dGVzdDM="], - "indexlength": 0, - "dtype": "binary[pyarrow]", - } - ), - pd.Series( - [b"Hello, World!", b"test2", b"test3"], - dtype=pd.ArrowDtype(pyarrow.binary()), - index=["col1", "col2", "col3"], - name=(), - ), - id="binary-no-index", - ), - ), -) -def test_get_pd_series(row_json, expected): - got = bff_template.get_pd_series(row_json) - pandas.testing.assert_series_equal(got, expected) - - -def test_get_pd_series_converter_dtypes(): - """Ensures the string format of the dtype doesn't change from that expected by get_pd_series.""" - - # Keep in sync with value_converters in get_pd_series. - # NOTE: Any change here is a red flag that there has been a breaking change - # that will affect deployed axis=1 remote functions. - assert str(bigframes.dtypes.BOOL_DTYPE) == "boolean" - assert str(bigframes.dtypes.BYTES_DTYPE) == "binary[pyarrow]" - assert str(bigframes.dtypes.FLOAT_DTYPE) == "Float64" - assert str(bigframes.dtypes.INT_DTYPE) == "Int64" - assert str(bigframes.dtypes.STRING_DTYPE) == "string" diff --git a/tests/unit/functions/test_function_typing.py b/tests/unit/functions/test_function_typing.py deleted file mode 100644 index 46ae19555aa..00000000000 --- a/tests/unit/functions/test_function_typing.py +++ /dev/null @@ -1,50 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import datetime -import decimal - -import pytest - -from bigframes.functions import function_typing - - -def test_unsupported_type_error_init_with_dict(): - err = function_typing.UnsupportedTypeError( - decimal.Decimal, {int: "INT64", float: "FLOAT64"} - ) - - message = str(err) - - assert "Decimal" in message - assert "float, int" in message - - -def test_unsupported_type_error_init_with_set(): - err = function_typing.UnsupportedTypeError(decimal.Decimal, {int, float}) - - message = str(err) - - assert "Decimal" in message - assert "float, int" in message - - -def test_sdk_type_from_python_type_raises_unsupported_type_error(): - with pytest.raises(function_typing.UnsupportedTypeError) as excinfo: - function_typing.sdk_type_from_python_type(datetime.datetime) - - message = str(excinfo.value) - - assert "datetime" in message - assert "bool, bytes, float, int, str" in message diff --git a/tests/unit/functions/test_remote_function.py b/tests/unit/functions/test_remote_function.py deleted file mode 100644 index 19de301790d..00000000000 --- a/tests/unit/functions/test_remote_function.py +++ /dev/null @@ -1,76 +0,0 @@ -# Copyright 2023 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.testing import mocks - - -def test_missing_input_types(): - session = mocks.create_bigquery_session() - remote_function_decorator = session._function_session.remote_function( - cloud_function_service_account="default" - ) - - def function_without_parameter_annotations(myparam) -> str: - return str(myparam) - - assert function_without_parameter_annotations(42) == "42" - - with pytest.raises( - ValueError, - match="'input_types' was not set .* 'myparam' is missing a type annotation", - ): - remote_function_decorator(function_without_parameter_annotations) - - -def test_missing_output_type(): - session = mocks.create_bigquery_session() - remote_function_decorator = session._function_session.remote_function( - cloud_function_service_account="default" - ) - - def function_without_return_annotation(myparam: int): - return str(myparam) - - assert function_without_return_annotation(42) == "42" - - with pytest.raises( - ValueError, - match="'output_type' was not set .* missing a return type annotation", - ): - remote_function_decorator(function_without_return_annotation) - - -def test_deploy_udf(): - session = mocks.create_bigquery_session() - - def my_remote_func(x: int) -> int: - return x * 2 - - deployed = session.deploy_udf(my_remote_func) - - assert deployed.udf_def is not None - - -def test_deploy_udf_with_name(): - session = mocks.create_bigquery_session() - - def my_remote_func(x: int) -> int: - return x * 2 - - deployed = session.deploy_udf(my_remote_func, name="my_custom_name") - - # Test that the function would have been deployed somewhere. - assert "my_custom_name" in deployed.bigframes_bigquery_function diff --git a/tests/unit/functions/test_remote_function_utils.py b/tests/unit/functions/test_remote_function_utils.py deleted file mode 100644 index cbdb289e265..00000000000 --- a/tests/unit/functions/test_remote_function_utils.py +++ /dev/null @@ -1,375 +0,0 @@ -# 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. -# 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 inspect -import sys -from unittest.mock import patch - -import bigframes_vendored.constants as constants -import pytest - -from bigframes.functions import _utils, function_typing - - -@pytest.mark.parametrize( - ("input_location", "expected_cf_region"), - [ - ("us", "us-central1"), - ("eu", "europe-west1"), - ("US-east4", "us-east4"), - ], -) -def test_gcf_location_from_bq_location(input_location, expected_cf_region): - """Tests getting cloud function locations for various BigQuery locations.""" - gcf_location = _utils.gcf_location_from_bq_location(input_location) - - assert gcf_location == expected_cf_region - - -@patch("bigframes.functions._utils.numpy.__version__", "1.24.4") -@patch("bigframes.functions._utils.pyarrow.__version__", "14.0.1") -@patch("bigframes.functions._utils.pandas.__version__", "2.0.3") -@patch("bigframes.functions._utils.cloudpickle.__version__", "2.2.1") -def test_get_updated_package_requirements_is_row_processor_with_versions(): - """Tests with is_row_processor=True and specific versions.""" - expected = [ - "cloudpickle==2.2.1", - "numpy==1.24.4", - "pandas==2.0.3", - "pyarrow==14.0.1", - ] - result = _utils.get_updated_package_requirements(is_row_processor=True) - - assert result == expected - - -@patch("bigframes.functions._utils.warnings.warn") -@patch("bigframes.functions._utils.cloudpickle.__version__", "2.2.1") -def test_get_updated_package_requirements_ignore_version(mock_warn): - """ - Tests with is_row_processor=True and ignore_package_version=True. - Should add packages without versions and raise a warning. - """ - expected = ["cloudpickle==2.2.1", "numpy", "pandas", "pyarrow"] - result = _utils.get_updated_package_requirements( - is_row_processor=True, ignore_package_version=True - ) - - assert result == expected - # Verify that a warning was issued. - mock_warn.assert_called_once() - - -@patch("bigframes.functions._utils.numpy.__version__", "1.24.4") -@patch("bigframes.functions._utils.pyarrow.__version__", "14.0.1") -@patch("bigframes.functions._utils.pandas.__version__", "2.0.3") -def test_get_updated_package_requirements_capture_references_false(): - """ - Tests with capture_references=False. - Should not add cloudpickle but should add others if requested. - """ - # Case 1: Only capture_references=False. - result_1 = _utils.get_updated_package_requirements(capture_references=False) - - assert len(result_1) == 0 - - # Case 2: capture_references=False but is_row_processor=True. - expected_2 = ["numpy==1.24.4", "pandas==2.0.3", "pyarrow==14.0.1"] - result_2 = _utils.get_updated_package_requirements( - is_row_processor=True, capture_references=False - ) - - assert result_2 == expected_2 - - -@patch("bigframes.functions._utils.numpy.__version__", "1.24.4") -@patch("bigframes.functions._utils.pyarrow.__version__", "14.0.1") -@patch("bigframes.functions._utils.pandas.__version__", "2.0.3") -@patch("bigframes.functions._utils.cloudpickle.__version__", "2.2.1") -def test_get_updated_package_requirements_non_overlapping_packages(): - """Tests providing an initial list of packages that do not overlap.""" - initial_packages = ["scikit-learn==1.3.0", "xgboost"] - expected = [ - "cloudpickle==2.2.1", - "numpy==1.24.4", - "pandas==2.0.3", - "pyarrow==14.0.1", - "scikit-learn==1.3.0", - "xgboost", - ] - result = _utils.get_updated_package_requirements( - package_requirements=initial_packages, is_row_processor=True - ) - - assert result == expected - - -@patch("bigframes.functions._utils.numpy.__version__", "1.24.4") -@patch("bigframes.functions._utils.pyarrow.__version__", "14.0.1") -@patch("bigframes.functions._utils.pandas.__version__", "2.0.3") -@patch("bigframes.functions._utils.cloudpickle.__version__", "2.2.1") -def test_get_updated_package_requirements_overlapping_packages(): - """Tests that packages are not added if they already exist.""" - # The function should respect the pre-existing pandas version. - initial_packages = ["pandas==1.5.3", "numpy"] - expected = [ - "cloudpickle==2.2.1", - "numpy", - "pandas==1.5.3", - "pyarrow==14.0.1", - ] - result = _utils.get_updated_package_requirements( - package_requirements=initial_packages, is_row_processor=True - ) - - assert result == expected - - -@patch("bigframes.functions._utils.cloudpickle.__version__", "2.2.1") -def test_get_updated_package_requirements_with_existing_cloudpickle(): - """Tests that cloudpickle is not added if it already exists.""" - initial_packages = ["cloudpickle==2.0.0"] - expected = ["cloudpickle==2.0.0"] - result = _utils.get_updated_package_requirements( - package_requirements=initial_packages - ) - - assert result == expected - - -# Dynamically generate expected python versions for the test -_major = sys.version_info.major -_minor = sys.version_info.minor -_compat_version = f"python{_major}{_minor}" -_standard_version = f"python-{_major}.{_minor}" - - -@pytest.mark.parametrize( - "is_compat, expected_version", - [ - (True, _compat_version), - (False, _standard_version), - ], -) -def test_get_python_version(is_compat, expected_version): - """Tests the python version for both standard and compat modes.""" - result = _utils.get_python_version(is_compat=is_compat) - assert result == expected_version - - -def test_package_existed_helper(): - """Tests the _package_existed helper function directly.""" - reqs = ["pandas==1.0", "numpy", "scikit-learn>=1.2.0"] - - # Exact match - assert _utils._package_existed(reqs, "pandas==1.0") - # Different version - assert _utils._package_existed(reqs, "pandas==2.0") - # No version specified - assert _utils._package_existed(reqs, "numpy") - # Not in list - assert not _utils._package_existed(reqs, "xgboost") - # Empty list - assert not _utils._package_existed([], "pandas") - - -# Helper functions for signature inspection tests -def _func_one_arg_annotated(x: int) -> int: - """A function with one annotated arg and an annotated return type.""" - return x - - -def _func_one_arg_unannotated(x): - """A function with one unannotated arg and no return type annotation.""" - return x - - -def _func_two_args_annotated(x: int, y: str): - """A function with two annotated args and no return type annotation.""" - return f"{x}{y}" - - -def _func_two_args_unannotated(x, y): - """A function with two unannotated args and no return type annotation.""" - return f"{x}{y}" - - -def test_has_conflict_input_type_too_few_inputs(): - """Tests conflict when there are fewer input types than parameters.""" - signature = inspect.signature(_func_one_arg_annotated) - assert _utils.has_conflict_input_type(signature, input_types=[]) - - -def test_has_conflict_input_type_too_many_inputs(): - """Tests conflict when there are more input types than parameters.""" - signature = inspect.signature(_func_one_arg_annotated) - assert _utils.has_conflict_input_type(signature, input_types=[int, str]) - - -def test_has_conflict_input_type_type_mismatch(): - """Tests has_conflict_input_type with a conflicting type annotation.""" - signature = inspect.signature(_func_two_args_annotated) - - # The second type (bool) conflicts with the annotation (str). - assert _utils.has_conflict_input_type(signature, input_types=[int, bool]) - - -def test_has_conflict_input_type_no_conflict_annotated(): - """Tests that a matching, annotated signature is compatible.""" - signature = inspect.signature(_func_two_args_annotated) - assert not _utils.has_conflict_input_type(signature, input_types=[int, str]) - - -def test_has_conflict_input_type_no_conflict_unannotated(): - """Tests that a signature with no annotations is always compatible.""" - signature = inspect.signature(_func_two_args_unannotated) - assert not _utils.has_conflict_input_type(signature, input_types=[int, float]) - - -def test_has_conflict_output_type_no_conflict(): - """Tests has_conflict_output_type with type annotation.""" - signature = inspect.signature(_func_one_arg_annotated) - - assert _utils.has_conflict_output_type(signature, output_type=float) - assert not _utils.has_conflict_output_type(signature, output_type=int) - - -def test_has_conflict_output_type_no_annotation(): - """Tests has_conflict_output_type without type annotation.""" - signature = inspect.signature(_func_one_arg_unannotated) - - assert not _utils.has_conflict_output_type(signature, output_type=int) - assert not _utils.has_conflict_output_type(signature, output_type=float) - - -@pytest.mark.parametrize( - ["metadata_options", "metadata_string"], - ( - pytest.param( - {}, - '{"value": {}}', - id="empty", - ), - pytest.param( - {"python_output_type": None}, - '{"value": {}}', - id="None", - ), - pytest.param( - {"python_output_type": list[bool]}, - '{"value": {"python_array_output_type": "bool"}}', - id="list-bool", - ), - pytest.param( - {"python_output_type": list[float]}, - '{"value": {"python_array_output_type": "float"}}', - id="list-float", - ), - pytest.param( - {"python_output_type": list[int]}, - '{"value": {"python_array_output_type": "int"}}', - id="list-int", - ), - pytest.param( - {"python_output_type": list[str]}, - '{"value": {"python_array_output_type": "str"}}', - id="list-str", - ), - ), -) -def test_get_bigframes_metadata(metadata_options, metadata_string): - assert _utils.get_bigframes_metadata(**metadata_options) == metadata_string - - -@pytest.mark.parametrize( - ["output_type"], - ( - pytest.param(bool), - pytest.param(bytes), - pytest.param(float), - pytest.param(int), - pytest.param(str), - pytest.param(list), - pytest.param(list[bytes], id="list-bytes"), - ), -) -def test_get_bigframes_metadata_array_type_not_serializable(output_type): - with pytest.raises(ValueError) as context: - _utils.get_bigframes_metadata(python_output_type=output_type) - - assert str(context.value) == ( - f"python_output_type {output_type} is not serializable. {constants.FEEDBACK_LINK}" - ) - - -@pytest.mark.parametrize( - ["metadata_string", "python_output_type"], - ( - pytest.param( - None, - None, - id="None", - ), - pytest.param( - "", - None, - id="empty", - ), - pytest.param( - "{}", - None, - id="empty-dict", - ), - pytest.param( - '{"value": {}}', - None, - id="empty-value", - ), - pytest.param( - '{"value": {"python_array_output_type": "bool"}}', - list[bool], - id="list-bool", - ), - pytest.param( - '{"value": {"python_array_output_type": "float"}}', - list[float], - id="list-float", - ), - pytest.param( - '{"value": {"python_array_output_type": "int"}}', - list[int], - id="list-int", - ), - pytest.param( - '{"value": {"python_array_output_type": "str"}}', - list[str], - id="list-str", - ), - ), -) -def test_get_python_output_type_from_bigframes_metadata( - metadata_string, python_output_type -): - assert ( - _utils.get_python_output_type_from_bigframes_metadata(metadata_string) - == python_output_type - ) - - -def test_metadata_roundtrip_supported_array_types(): - for array_of in function_typing.RF_SUPPORTED_ARRAY_OUTPUT_PYTHON_TYPES: - ser = _utils.get_bigframes_metadata(python_output_type=list[array_of]) # type: ignore - deser = _utils.get_python_output_type_from_bigframes_metadata(ser) - - assert deser == list[array_of] # type: ignore diff --git a/tests/unit/ml/test_api_primitives.py b/tests/unit/ml/test_api_primitives.py index dd2ceff1432..da77a180a8d 100644 --- a/tests/unit/ml/test_api_primitives.py +++ b/tests/unit/ml/test_api_primitives.py @@ -13,6 +13,8 @@ # limitations under the License. import pytest +import sklearn.decomposition as sklearn_decomposition # type: ignore +import sklearn.linear_model as sklearn_linear_model # type: ignore import bigframes.ml.decomposition import bigframes.ml.linear_model @@ -28,14 +30,12 @@ def test_base_estimator_repr(): estimator = bigframes.ml.linear_model.LinearRegression(fit_intercept=True) assert estimator.__repr__() == "LinearRegression()" - # TODO(b/340891292): fix type error - pca_estimator = bigframes.ml.decomposition.PCA(n_components=7) - assert pca_estimator.__repr__() == "PCA(n_components=7)" + estimator = bigframes.ml.decomposition.PCA(n_components=7) + assert estimator.__repr__() == "PCA(n_components=7)" +@pytest.mark.skipif(sklearn_linear_model is None, reason="requires sklearn") def test_base_estimator_repr_matches_sklearn(): - sklearn_decomposition = pytest.importorskip("sklearn.decomposition") - sklearn_linear_model = pytest.importorskip("sklearn.linear_model") estimator = bigframes.ml.linear_model.LinearRegression() sklearn_estimator = sklearn_linear_model.LinearRegression() assert estimator.__repr__() == sklearn_estimator.__repr__() @@ -48,7 +48,6 @@ def test_base_estimator_repr_matches_sklearn(): sklearn_estimator = sklearn_linear_model.LinearRegression(fit_intercept=True) assert estimator.__repr__() == sklearn_estimator.__repr__() - # TODO(b/340891292): fix type error - pca_estimator = bigframes.ml.decomposition.PCA(n_components=7) + estimator = bigframes.ml.decomposition.PCA(n_components=7) sklearn_estimator = sklearn_decomposition.PCA(n_components=7) - assert pca_estimator.__repr__() == sklearn_estimator.__repr__() + assert estimator.__repr__() == sklearn_estimator.__repr__() diff --git a/tests/unit/ml/test_compose.py b/tests/unit/ml/test_compose.py index 7779bafadfa..60dcc75b63c 100644 --- a/tests/unit/ml/test_compose.py +++ b/tests/unit/ml/test_compose.py @@ -11,15 +11,11 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -from unittest import mock -import pytest -from google.cloud import bigquery +import sklearn.compose as sklearn_compose # type: ignore +import sklearn.preprocessing as sklearn_preprocessing # type: ignore -import bigframes.pandas as bpd from bigframes.ml import compose, preprocessing -from bigframes.ml.compose import ColumnTransformer, SQLScalarColumnTransformer -from bigframes.ml.core import BqmlModel def test_columntransformer_init_expectedtransforms(): @@ -117,8 +113,6 @@ def test_columntransformer_repr(): def test_columntransformer_repr_matches_sklearn(): - sklearn_compose = pytest.importorskip("sklearn.compose") - sklearn_preprocessing = pytest.importorskip("sklearn.preprocessing") bf_column_transformer = compose.ColumnTransformer( [ ( @@ -179,404 +173,3 @@ def test_columntransformer_repr_matches_sklearn(): ) assert bf_column_transformer.__repr__() == sk_column_transformer.__repr__() - - -@pytest.fixture(scope="session") -def mock_X(): - mock_df = mock.create_autospec(spec=bpd.DataFrame) - return mock_df - - -def test_columntransformer_init_with_sqltransformers(): - ident_transformer = SQLScalarColumnTransformer("{0}", target_column="ident_{0}") - len1_transformer = SQLScalarColumnTransformer( - "CASE WHEN {0} IS NULL THEN -2 ELSE LENGTH({0}) END", target_column="len1_{0}" - ) - len2_transformer = SQLScalarColumnTransformer( - "CASE WHEN {0} IS NULL THEN 99 ELSE LENGTH({0}) END", target_column="len2_{0}" - ) - label_transformer = preprocessing.LabelEncoder() - column_transformer = compose.ColumnTransformer( - [ - ( - "ident_trafo", - ident_transformer, - ["culmen_length_mm", "flipper_length_mm"], - ), - ("len1_trafo", len1_transformer, ["species"]), - ("len2_trafo", len2_transformer, ["species"]), - ("label", label_transformer, "species"), - ] - ) - - assert column_transformer.transformers_ == [ - ("ident_trafo", ident_transformer, "culmen_length_mm"), - ("ident_trafo", ident_transformer, "flipper_length_mm"), - ("len1_trafo", len1_transformer, "species"), - ("len2_trafo", len2_transformer, "species"), - ("label", label_transformer, "species"), - ] - - -def test_columntransformer_repr_sqltransformers(): - ident_transformer = SQLScalarColumnTransformer("{0}", target_column="ident_{0}") - len1_transformer = SQLScalarColumnTransformer( - "CASE WHEN {0} IS NULL THEN -2 ELSE LENGTH({0}) END", target_column="len1_{0}" - ) - len2_transformer = SQLScalarColumnTransformer( - "CASE WHEN {0} IS NULL THEN 99 ELSE LENGTH({0}) END", target_column="len2_{0}" - ) - label_transformer = preprocessing.LabelEncoder() - column_transformer = compose.ColumnTransformer( - [ - ( - "ident_trafo", - ident_transformer, - ["culmen_length_mm", "flipper_length_mm"], - ), - ("len1_trafo", len1_transformer, ["species"]), - ("len2_trafo", len2_transformer, ["species"]), - ("label", label_transformer, "species"), - ] - ) - - expected = """ColumnTransformer(transformers=[('ident_trafo', - SQLScalarColumnTransformer(sql='{0}', target_column='ident_{0}'), - ['culmen_length_mm', 'flipper_length_mm']), - ('len1_trafo', - SQLScalarColumnTransformer(sql='CASE WHEN {0} IS NULL THEN -2 ELSE LENGTH({0}) END', target_column='len1_{0}'), - ['species']), - ('len2_trafo', - SQLScalarColumnTransformer(sql='CASE WHEN {0} IS NULL THEN 99 ELSE LENGTH({0}) END', target_column='len2_{0}'), - ['species']), - ('label', LabelEncoder(), 'species')])""" - actual = column_transformer.__repr__() - assert expected == actual - - -def test_customtransformer_compile_sql(mock_X): - ident_trafo = SQLScalarColumnTransformer("{0}", target_column="ident_{0}") - sqls = ident_trafo._compile_to_sql(X=mock_X, columns=["col1", "col2"]) - assert sqls == [ - "`col1` AS `ident_col1`", - "`col2` AS `ident_col2`", - ] - - len1_trafo = SQLScalarColumnTransformer( - "CASE WHEN {0} IS NULL THEN -5 ELSE LENGTH({0}) END", target_column="len1_{0}" - ) - sqls = len1_trafo._compile_to_sql(X=mock_X, columns=["col1", "col2"]) - assert sqls == [ - "CASE WHEN `col1` IS NULL THEN -5 ELSE LENGTH(`col1`) END AS `len1_col1`", - "CASE WHEN `col2` IS NULL THEN -5 ELSE LENGTH(`col2`) END AS `len1_col2`", - ] - - len2_trafo = SQLScalarColumnTransformer( - "CASE WHEN {0} IS NULL THEN 99 ELSE LENGTH({0}) END", target_column="len2_{0}" - ) - sqls = len2_trafo._compile_to_sql(X=mock_X, columns=["col1", "col2"]) - assert sqls == [ - "CASE WHEN `col1` IS NULL THEN 99 ELSE LENGTH(`col1`) END AS `len2_col1`", - "CASE WHEN `col2` IS NULL THEN 99 ELSE LENGTH(`col2`) END AS `len2_col2`", - ] - - -def create_bq_model_mock(monkeypatch, transform_columns, feature_columns=None): - properties = {"transformColumns": transform_columns} - mock_bq_model = bigquery.Model("model_project.model_dataset.model_id") - type(mock_bq_model)._properties = mock.PropertyMock(return_value=properties) - if feature_columns: - result = [ - bigquery.standard_sql.StandardSqlField(col, None) for col in feature_columns - ] - monkeypatch.setattr( - type(mock_bq_model), - "feature_columns", - mock.PropertyMock(return_value=result), - ) - - return mock_bq_model - - -@pytest.fixture -def bq_model_good(monkeypatch): - return create_bq_model_mock( - monkeypatch, - [ - { - "name": "ident_culmen_length_mm", - "type": {"typeKind": "INT64"}, - "transformSql": "culmen_length_mm /*CT.IDENT()*/", - }, - { - "name": "ident_flipper_length_mm", - "type": {"typeKind": "INT64"}, - "transformSql": "flipper_length_mm /*CT.IDENT()*/", - }, - { - "name": "len1_species", - "type": {"typeKind": "INT64"}, - "transformSql": "CASE WHEN species IS NULL THEN -5 ELSE LENGTH(species) END /*CT.LEN1()*/", - }, - { - "name": "len2_species", - "type": {"typeKind": "INT64"}, - "transformSql": "CASE WHEN species IS NULL THEN 99 ELSE LENGTH(species) END /*CT.LEN2([99])*/", - }, - { - "name": "labelencoded_county", - "type": {"typeKind": "INT64"}, - "transformSql": "ML.LABEL_ENCODER(county, 1000000, 0) OVER()", - }, - { - "name": "labelencoded_species", - "type": {"typeKind": "INT64"}, - "transformSql": "ML.LABEL_ENCODER(species, 1000000, 0) OVER()", - }, - ], - ) - - -@pytest.fixture -def bq_model_merge(monkeypatch): - return create_bq_model_mock( - monkeypatch, - [ - { - "name": "labelencoded_county", - "type": {"typeKind": "INT64"}, - "transformSql": "ML.LABEL_ENCODER(county, 1000000, 0) OVER()", - }, - { - "name": "labelencoded_species", - "type": {"typeKind": "INT64"}, - "transformSql": "ML.LABEL_ENCODER(species, 1000000, 0) OVER()", - }, - ], - ["county", "species"], - ) - - -@pytest.fixture -def bq_model_no_merge(monkeypatch): - return create_bq_model_mock( - monkeypatch, - [ - { - "name": "ident_culmen_length_mm", - "type": {"typeKind": "INT64"}, - "transformSql": "culmen_length_mm /*CT.IDENT()*/", - } - ], - ["culmen_length_mm"], - ) - - -@pytest.fixture -def bq_model_unknown_ML(monkeypatch): - return create_bq_model_mock( - monkeypatch, - [ - { - "name": "unknownml_culmen_length_mm", - "type": {"typeKind": "INT64"}, - "transformSql": "ML.UNKNOWN(culmen_length_mm)", - }, - { - "name": "labelencoded_county", - "type": {"typeKind": "INT64"}, - "transformSql": "ML.LABEL_ENCODER(county, 1000000, 0) OVER()", - }, - ], - ) - - -@pytest.fixture -def bq_model_flexnames(monkeypatch): - return create_bq_model_mock( - monkeypatch, - [ - { - "name": "Flex Name culmen_length_mm", - "type": {"typeKind": "INT64"}, - "transformSql": "culmen_length_mm", - }, - { - "name": "transformed_Culmen Length MM", - "type": {"typeKind": "INT64"}, - "transformSql": "`Culmen Length MM`*/", - }, - # test workaround for bug in get_model - { - "name": "Flex Name flipper_length_mm", - "type": {"typeKind": "INT64"}, - "transformSql": "flipper_length_mm AS `Flex Name flipper_length_mm`", - }, - { - "name": "transformed_Flipper Length MM", - "type": {"typeKind": "INT64"}, - "transformSql": "`Flipper Length MM` AS `transformed_Flipper Length MM`*/", - }, - ], - ) - - -def test_columntransformer_extract_from_bq_model_good(bq_model_good): - col_trans = ColumnTransformer._extract_from_bq_model(bq_model_good) - assert len(col_trans.transformers) == 6 - # normalize the representation for string comparing - col_trans.transformers.sort(key=lambda trafo: str(trafo)) - actual = col_trans.__repr__() - expected = """ColumnTransformer(transformers=[('label_encoder', - LabelEncoder(max_categories=1000001, - min_frequency=0), - 'county'), - ('label_encoder', - LabelEncoder(max_categories=1000001, - min_frequency=0), - 'species'), - ('sql_scalar_column_transformer', - SQLScalarColumnTransformer(sql='CASE WHEN species IS NULL THEN -5 ELSE LENGTH(species) END /*CT.LEN1()*/', target_column='len1_species'), - '?len1_species'), - ('sql_scalar_column_transformer', - SQLScalarColumnTransformer(sql='CASE WHEN species IS NULL THEN 99 ELSE LENGTH(species) END /*CT.LEN2([99])*/', target_column='len2_species'), - '?len2_species'), - ('sql_scalar_column_transformer', - SQLScalarColumnTransformer(sql='culmen_length_mm /*CT.IDENT()*/', target_column='ident_culmen_length_mm'), - '?ident_culmen_length_mm'), - ('sql_scalar_column_transformer', - SQLScalarColumnTransformer(sql='flipper_length_mm /*CT.IDENT()*/', target_column='ident_flipper_length_mm'), - '?ident_flipper_length_mm')])""" - assert expected == actual - - -def test_columntransformer_extract_from_bq_model_merge(bq_model_merge): - col_trans = ColumnTransformer._extract_from_bq_model(bq_model_merge) - assert isinstance(col_trans, ColumnTransformer) - merged_col_trans = col_trans._merge(bq_model_merge) - assert isinstance(merged_col_trans, preprocessing.LabelEncoder) - assert ( - merged_col_trans.__repr__() - == """LabelEncoder(max_categories=1000001, min_frequency=0)""" - ) - assert merged_col_trans._output_names == [ - "labelencoded_county", - "labelencoded_species", - ] - - -def test_columntransformer_extract_from_bq_model_no_merge(bq_model_no_merge): - col_trans = ColumnTransformer._extract_from_bq_model(bq_model_no_merge) - merged_col_trans = col_trans._merge(bq_model_no_merge) - assert isinstance(merged_col_trans, ColumnTransformer) - expected = """ColumnTransformer(transformers=[('sql_scalar_column_transformer', - SQLScalarColumnTransformer(sql='culmen_length_mm /*CT.IDENT()*/', target_column='ident_culmen_length_mm'), - '?ident_culmen_length_mm')])""" - actual = merged_col_trans.__repr__() - assert expected == actual - - -def test_columntransformer_extract_from_bq_model_unknown_ML(bq_model_unknown_ML): - try: - _ = ColumnTransformer._extract_from_bq_model(bq_model_unknown_ML) - assert False - except NotImplementedError as e: - assert "Unsupported transformer type" in e.args[0] - - -def test_columntransformer_extract_output_names(bq_model_good): - class BQMLModel(BqmlModel): - def __init__(self, bq_model): - self._model = bq_model - - col_trans = ColumnTransformer._extract_from_bq_model(bq_model_good) - col_trans._bqml_model = BQMLModel(bq_model_good) - col_trans._extract_output_names() - assert col_trans._output_names == [ - "ident_culmen_length_mm", - "ident_flipper_length_mm", - "len1_species", - "len2_species", - "labelencoded_county", - "labelencoded_species", - ] - - -def test_columntransformer_compile_to_sql(mock_X): - ident_transformer = SQLScalarColumnTransformer("{0}", target_column="ident_{0}") - len1_transformer = SQLScalarColumnTransformer( - "CASE WHEN {0} IS NULL THEN -2 ELSE LENGTH({0}) END", target_column="len1_{0}" - ) - len2_transformer = SQLScalarColumnTransformer( - "CASE WHEN {0} IS NULL THEN 99 ELSE LENGTH({0}) END", target_column="len2_{0}" - ) - label_transformer = preprocessing.LabelEncoder() - column_transformer = compose.ColumnTransformer( - [ - ( - "ident_trafo", - ident_transformer, - ["culmen_length_mm", "flipper_length_mm"], - ), - ("len1_trafo", len1_transformer, ["species"]), - ("len2_trafo", len2_transformer, ["species"]), - ("label", label_transformer, "species"), - ] - ) - sqls = column_transformer._compile_to_sql(mock_X) - assert sqls == [ - "`culmen_length_mm` AS `ident_culmen_length_mm`", - "`flipper_length_mm` AS `ident_flipper_length_mm`", - "CASE WHEN `species` IS NULL THEN -2 ELSE LENGTH(`species`) END AS `len1_species`", - "CASE WHEN `species` IS NULL THEN 99 ELSE LENGTH(`species`) END AS `len2_species`", - "ML.LABEL_ENCODER(`species`, 1000000, 0) OVER() AS `labelencoded_species`", - ] - - -def test_columntransformer_flexible_column_names(mock_X): - ident_transformer = SQLScalarColumnTransformer("{0}", target_column="ident {0}") - len1_transformer = SQLScalarColumnTransformer( - "CASE WHEN {0} IS NULL THEN -2 ELSE LENGTH({0}) END", target_column="len1_{0}" - ) - len2_transformer = SQLScalarColumnTransformer( - "CASE WHEN {0} IS NULL THEN 99 ELSE LENGTH({0}) END", target_column="len2_{0}" - ) - column_transformer = compose.ColumnTransformer( - [ - ( - "ident_trafo", - ident_transformer, - ["culmen_length_mm", "flipper_length_mm"], - ), - ("len1_trafo", len1_transformer, ["species shortname"]), - ("len2_trafo", len2_transformer, ["species longname"]), - ] - ) - sqls = column_transformer._compile_to_sql(mock_X) - assert sqls == [ - "`culmen_length_mm` AS `ident culmen_length_mm`", - "`flipper_length_mm` AS `ident flipper_length_mm`", - "CASE WHEN `species shortname` IS NULL THEN -2 ELSE LENGTH(`species shortname`) END AS `len1_species shortname`", - "CASE WHEN `species longname` IS NULL THEN 99 ELSE LENGTH(`species longname`) END AS `len2_species longname`", - ] - - -def test_columntransformer_extract_from_bq_model_flexnames(bq_model_flexnames): - col_trans = ColumnTransformer._extract_from_bq_model(bq_model_flexnames) - assert len(col_trans.transformers) == 4 - # normalize the representation for string comparing - col_trans.transformers.sort(key=lambda trafo: str(trafo)) - actual = col_trans.__repr__() - expected = """ColumnTransformer(transformers=[('sql_scalar_column_transformer', - SQLScalarColumnTransformer(sql='`Culmen Length MM`*/', target_column='transformed_Culmen Length MM'), - '?transformed_Culmen Length MM'), - ('sql_scalar_column_transformer', - SQLScalarColumnTransformer(sql='`Flipper Length MM` AS `transformed_Flipper Length MM`*/', target_column='transformed_Flipper Length MM'), - '?transformed_Flipper Length MM'), - ('sql_scalar_column_transformer', - SQLScalarColumnTransformer(sql='culmen_length_mm', target_column='Flex Name culmen_length_mm'), - '?Flex Name culmen_length_mm'), - ('sql_scalar_column_transformer', - SQLScalarColumnTransformer(sql='flipper_length_mm', target_column='Flex Name flipper_length_mm'), - '?Flex Name flipper_length_mm')])""" - assert expected == actual diff --git a/tests/unit/ml/test_forecasting.py b/tests/unit/ml/test_forecasting.py deleted file mode 100644 index 3bbf4c777e0..00000000000 --- a/tests/unit/ml/test_forecasting.py +++ /dev/null @@ -1,58 +0,0 @@ -# Copyright 2023 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 re - -import pytest - -from bigframes.ml import forecasting - - -def test_predict_explain_low_confidence_level(): - confidence_level = -0.5 - - model = forecasting.ARIMAPlus() - - with pytest.raises( - ValueError, - match=re.escape( - f"confidence_level must be [0.0, 1.0), but is {confidence_level}." - ), - ): - model.predict_explain(horizon=4, confidence_level=confidence_level) - - -def test_predict_high_explain_confidence_level(): - confidence_level = 2.1 - - model = forecasting.ARIMAPlus() - - with pytest.raises( - ValueError, - match=re.escape( - f"confidence_level must be [0.0, 1.0), but is {confidence_level}." - ), - ): - model.predict_explain(horizon=4, confidence_level=confidence_level) - - -def test_predict_explain_low_horizon(): - horizon = -1 - - model = forecasting.ARIMAPlus() - - with pytest.raises( - ValueError, match=f"horizon must be at least 1, but is {horizon}." - ): - model.predict_explain(horizon=horizon, confidence_level=0.9) diff --git a/tests/unit/ml/test_golden_sql.py b/tests/unit/ml/test_golden_sql.py index 7babf476117..3ca7e144a53 100644 --- a/tests/unit/ml/test_golden_sql.py +++ b/tests/unit/ml/test_golden_sql.py @@ -14,60 +14,30 @@ from unittest import mock +from google.cloud import bigquery import pandas as pd import pytest -from google.cloud import bigquery +import pytest_mock import bigframes -import bigframes.ml.core +from bigframes.ml import core, linear_model import bigframes.pandas as bpd -from bigframes.ml import core, decomposition, linear_model - -TEMP_MODEL_ID = bigquery.ModelReference.from_string( - "test-project._anon123.temp_model_id" -) @pytest.fixture def mock_session(): mock_session = mock.create_autospec(spec=bigframes.Session) - mock_session._anonymous_dataset = bigquery.DatasetReference( - TEMP_MODEL_ID.project, TEMP_MODEL_ID.dataset_id - ) - mock_session._bq_kms_key_name = None - mock_session._metrics = None - - query_job = mock.create_autospec(bigquery.QueryJob) - type(query_job).destination = mock.PropertyMock( - return_value=bigquery.TableReference( - mock_session._anonymous_dataset, TEMP_MODEL_ID.model_id - ) - ) - mock_session._start_query_ml_ddl.return_value = (None, query_job) + # return values we don't care about, but need to provide to continue the program when calling session._start_query() + mock_session._start_query.return_value = (None, mock.MagicMock()) return mock_session @pytest.fixture -def bqml_model_factory(monkeypatch): - monkeypatch.setattr( - bigframes.ml.core.BqmlModelFactory, - "_create_model_ref", - mock.Mock(return_value=TEMP_MODEL_ID), - ) - bqml_model_factory = core.BqmlModelFactory() - - return bqml_model_factory - - -@pytest.fixture -def mock_y(mock_session): +def mock_y(): mock_y = mock.create_autospec(spec=bpd.DataFrame) - mock_y._session = mock_session mock_y.columns = pd.Index(["input_column_label"]) - mock_y.cache.return_value = mock_y - mock_y.copy.return_value = mock_y return mock_y @@ -81,30 +51,25 @@ def mock_X(mock_y, mock_session): ["index_column_id"], ["index_column_label"], ) - type(mock_X).sql = mock.PropertyMock(return_value="input_X_sql_property") - mock_X.reset_index(drop=True).cache().sql = "input_X_no_index_sql" mock_X.join(mock_y).sql = "input_X_y_sql" - mock_X.join(mock_y).cache.return_value = mock_X.join(mock_y) mock_X.join(mock_y)._to_sql_query.return_value = ( "input_X_y_sql", ["index_column_id"], ["index_column_label"], ) - mock_X.join(mock_y).reset_index(drop=True).sql = "input_X_y_no_index_sql" - mock_X.join(mock_y).reset_index(drop=True).cache.return_value = mock_X.join( - mock_y - ).reset_index(drop=True) - mock_X.join(mock_y).reset_index(drop=True)._to_sql_query.return_value = ( - "input_X_y_no_index_sql", - ["index_column_id"], - ["index_column_label"], - ) + return mock_X - mock_X.cache.return_value = mock_X - mock_X.copy.return_value = mock_X - return mock_X +@pytest.fixture +def bqml_model_factory(mocker: pytest_mock.MockerFixture): + mocker.patch( + "bigframes.ml.core.BqmlModelFactory._create_temp_model_id", + return_value="temp_model_id", + ) + bqml_model_factory = core.BqmlModelFactory() + + return bqml_model_factory @pytest.fixture @@ -123,8 +88,8 @@ def test_linear_regression_default_fit( model._bqml_model_factory = bqml_model_factory model.fit(mock_X, mock_y) - mock_session._start_query_ml_ddl.assert_called_once_with( - "CREATE OR REPLACE MODEL `test-project`.`_anon123`.`temp_model_id`\nOPTIONS(\n model_type='LINEAR_REG',\n data_split_method='NO_SPLIT',\n optimize_strategy='auto_strategy',\n fit_intercept=TRUE,\n l2_reg=0.0,\n max_iterations=20,\n learn_rate_strategy='line_search',\n min_rel_progress=0.01,\n calculate_p_values=FALSE,\n enable_global_explain=FALSE,\n INPUT_LABEL_COLS=['input_column_label'])\nAS input_X_y_no_index_sql" + mock_session._start_query.assert_called_once_with( + 'CREATE TEMP MODEL `temp_model_id`\nOPTIONS(\n model_type="LINEAR_REG",\n data_split_method="NO_SPLIT",\n optimize_strategy="normal_equation",\n fit_intercept=True,\n l2_reg=0.0,\n max_iterations=20,\n learn_rate_strategy="line_search",\n early_stop=True,\n min_rel_progress=0.01,\n ls_init_learn_rate=0.1,\n calculate_p_values=False,\n enable_global_explain=False,\n INPUT_LABEL_COLS=["input_column_label"])\nAS input_X_y_sql' ) @@ -133,8 +98,8 @@ def test_linear_regression_params_fit(bqml_model_factory, mock_session, mock_X, model._bqml_model_factory = bqml_model_factory model.fit(mock_X, mock_y) - mock_session._start_query_ml_ddl.assert_called_once_with( - "CREATE OR REPLACE MODEL `test-project`.`_anon123`.`temp_model_id`\nOPTIONS(\n model_type='LINEAR_REG',\n data_split_method='NO_SPLIT',\n optimize_strategy='auto_strategy',\n fit_intercept=FALSE,\n l2_reg=0.0,\n max_iterations=20,\n learn_rate_strategy='line_search',\n min_rel_progress=0.01,\n calculate_p_values=FALSE,\n enable_global_explain=FALSE,\n INPUT_LABEL_COLS=['input_column_label'])\nAS input_X_y_no_index_sql" + mock_session._start_query.assert_called_once_with( + 'CREATE TEMP MODEL `temp_model_id`\nOPTIONS(\n model_type="LINEAR_REG",\n data_split_method="NO_SPLIT",\n optimize_strategy="normal_equation",\n fit_intercept=False,\n l2_reg=0.0,\n max_iterations=20,\n learn_rate_strategy="line_search",\n early_stop=True,\n min_rel_progress=0.01,\n ls_init_learn_rate=0.1,\n calculate_p_values=False,\n enable_global_explain=False,\n INPUT_LABEL_COLS=["input_column_label"])\nAS input_X_y_sql' ) @@ -143,10 +108,9 @@ def test_linear_regression_predict(mock_session, bqml_model, mock_X): model._bqml_model = bqml_model model.predict(mock_X) - mock_session.read_gbq_query.assert_called_once_with( - "SELECT * FROM ML.PREDICT(MODEL `model_project`.`model_dataset`.`model_id`,\n (input_X_sql))", + mock_session.read_gbq.assert_called_once_with( + "SELECT * FROM ML.PREDICT(MODEL `model_project.model_dataset.model_id`,\n (input_X_sql))", index_col=["index_column_id"], - allow_large_results=True, ) @@ -155,9 +119,8 @@ def test_linear_regression_score(mock_session, bqml_model, mock_X, mock_y): model._bqml_model = bqml_model model.score(mock_X, mock_y) - mock_session.read_gbq_query.assert_called_once_with( - "SELECT * FROM ML.EVALUATE(MODEL `model_project`.`model_dataset`.`model_id`,\n (input_X_y_sql))", - allow_large_results=True, + mock_session.read_gbq.assert_called_once_with( + "SELECT * FROM ML.EVALUATE(MODEL `model_project.model_dataset.model_id`,\n (input_X_y_sql))" ) @@ -168,8 +131,8 @@ def test_logistic_regression_default_fit( model._bqml_model_factory = bqml_model_factory model.fit(mock_X, mock_y) - mock_session._start_query_ml_ddl.assert_called_once_with( - "CREATE OR REPLACE MODEL `test-project`.`_anon123`.`temp_model_id`\nOPTIONS(\n model_type='LOGISTIC_REG',\n data_split_method='NO_SPLIT',\n fit_intercept=TRUE,\n auto_class_weights=FALSE,\n optimize_strategy='auto_strategy',\n l2_reg=0.0,\n max_iterations=20,\n learn_rate_strategy='line_search',\n min_rel_progress=0.01,\n calculate_p_values=FALSE,\n enable_global_explain=FALSE,\n INPUT_LABEL_COLS=['input_column_label'])\nAS input_X_y_no_index_sql", + mock_session._start_query.assert_called_once_with( + 'CREATE TEMP MODEL `temp_model_id`\nOPTIONS(\n model_type="LOGISTIC_REG",\n data_split_method="NO_SPLIT",\n fit_intercept=True,\n auto_class_weights=False,\n INPUT_LABEL_COLS=["input_column_label"])\nAS input_X_y_sql' ) @@ -177,21 +140,13 @@ def test_logistic_regression_params_fit( bqml_model_factory, mock_session, mock_X, mock_y ): model = linear_model.LogisticRegression( - fit_intercept=False, - class_weight="balanced", - l2_reg=0.2, - tol=0.02, - l1_reg=0.2, - max_iterations=30, - optimize_strategy="batch_gradient_descent", - learning_rate_strategy="constant", - learning_rate=0.2, + fit_intercept=False, class_weights="balanced" ) model._bqml_model_factory = bqml_model_factory model.fit(mock_X, mock_y) - mock_session._start_query_ml_ddl.assert_called_once_with( - "CREATE OR REPLACE MODEL `test-project`.`_anon123`.`temp_model_id`\nOPTIONS(\n model_type='LOGISTIC_REG',\n data_split_method='NO_SPLIT',\n fit_intercept=FALSE,\n auto_class_weights=TRUE,\n optimize_strategy='batch_gradient_descent',\n l2_reg=0.2,\n max_iterations=30,\n learn_rate_strategy='constant',\n min_rel_progress=0.02,\n calculate_p_values=FALSE,\n enable_global_explain=FALSE,\n l1_reg=0.2,\n learn_rate=0.2,\n INPUT_LABEL_COLS=['input_column_label'])\nAS input_X_y_no_index_sql" + mock_session._start_query.assert_called_once_with( + 'CREATE TEMP MODEL `temp_model_id`\nOPTIONS(\n model_type="LOGISTIC_REG",\n data_split_method="NO_SPLIT",\n fit_intercept=False,\n auto_class_weights=True,\n INPUT_LABEL_COLS=["input_column_label"])\nAS input_X_y_sql' ) @@ -200,10 +155,9 @@ def test_logistic_regression_predict(mock_session, bqml_model, mock_X): model._bqml_model = bqml_model model.predict(mock_X) - mock_session.read_gbq_query.assert_called_once_with( - "SELECT * FROM ML.PREDICT(MODEL `model_project`.`model_dataset`.`model_id`,\n (input_X_sql))", + mock_session.read_gbq.assert_called_once_with( + "SELECT * FROM ML.PREDICT(MODEL `model_project.model_dataset.model_id`,\n (input_X_sql))", index_col=["index_column_id"], - allow_large_results=True, ) @@ -212,77 +166,6 @@ def test_logistic_regression_score(mock_session, bqml_model, mock_X, mock_y): model._bqml_model = bqml_model model.score(mock_X, mock_y) - mock_session.read_gbq_query.assert_called_once_with( - "SELECT * FROM ML.EVALUATE(MODEL `model_project`.`model_dataset`.`model_id`,\n (input_X_y_sql))", - allow_large_results=True, - ) - - -def test_decomposition_mf_default_fit(bqml_model_factory, mock_session, mock_X): - model = decomposition.MatrixFactorization( - num_factors=34, - feedback_type="explicit", - user_col="user_id", - item_col="item_col", - rating_col="rating_col", - l2_reg=9.83, - ) - model._bqml_model_factory = bqml_model_factory - model.fit(mock_X) - - mock_session._start_query_ml_ddl.assert_called_once_with( - "CREATE OR REPLACE MODEL `test-project`.`_anon123`.`temp_model_id`\nOPTIONS(\n model_type='matrix_factorization',\n feedback_type='explicit',\n user_col='user_id',\n item_col='item_col',\n rating_col='rating_col',\n l2_reg=9.83,\n num_factors=34)\nAS input_X_no_index_sql" - ) - - -def test_decomposition_mf_predict(mock_session, bqml_model, mock_X): - model = decomposition.MatrixFactorization( - num_factors=34, - feedback_type="explicit", - user_col="user_id", - item_col="item_col", - rating_col="rating_col", - l2_reg=9.83, - ) - model._bqml_model = bqml_model - model.predict(mock_X) - - mock_session.read_gbq_query.assert_called_once_with( - "SELECT * FROM ML.RECOMMEND(MODEL `model_project`.`model_dataset`.`model_id`,\n (input_X_sql))", - index_col=["index_column_id"], - allow_large_results=True, - ) - - -def test_decomposition_mf_score(mock_session, bqml_model): - model = decomposition.MatrixFactorization( - num_factors=34, - feedback_type="explicit", - user_col="user_id", - item_col="item_col", - rating_col="rating_col", - l2_reg=9.83, - ) - model._bqml_model = bqml_model - model.score() - mock_session.read_gbq_query.assert_called_once_with( - "SELECT * FROM ML.EVALUATE(MODEL `model_project`.`model_dataset`.`model_id`)", - allow_large_results=True, - ) - - -def test_decomposition_mf_score_with_x(mock_session, bqml_model, mock_X): - model = decomposition.MatrixFactorization( - num_factors=34, - feedback_type="explicit", - user_col="user_id", - item_col="item_col", - rating_col="rating_col", - l2_reg=9.83, - ) - model._bqml_model = bqml_model - model.score(mock_X) - mock_session.read_gbq_query.assert_called_once_with( - "SELECT * FROM ML.EVALUATE(MODEL `model_project`.`model_dataset`.`model_id`,\n (input_X_sql_property))", - allow_large_results=True, + mock_session.read_gbq.assert_called_once_with( + "SELECT * FROM ML.EVALUATE(MODEL `model_project.model_dataset.model_id`,\n (input_X_y_sql))" ) diff --git a/tests/unit/ml/test_matrix_factorization.py b/tests/unit/ml/test_matrix_factorization.py deleted file mode 100644 index 92691ba9d4a..00000000000 --- a/tests/unit/ml/test_matrix_factorization.py +++ /dev/null @@ -1,182 +0,0 @@ -# Copyright 2023 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.ml import decomposition - - -def test_decomposition_mf_model(): - model = decomposition.MatrixFactorization( - num_factors=16, - feedback_type="implicit", - user_col="user_id", - item_col="item_col", - rating_col="rating_col", - l2_reg=9, - ) - assert model.num_factors == 16 - assert model.feedback_type == "implicit" - assert model.user_col == "user_id" - assert model.item_col == "item_col" - assert model.rating_col == "rating_col" - - -def test_decomposition_mf_feedback_type_explicit(): - model = decomposition.MatrixFactorization( - num_factors=16, - feedback_type="explicit", - user_col="user_id", - item_col="item_col", - rating_col="rating_col", - l2_reg=9.83, - ) - assert model.feedback_type == "explicit" - - -def test_decomposition_mf_invalid_feedback_type_raises(): - feedback_type = "explimp" - with pytest.raises( - ValueError, - match="Expected feedback_type to be `explicit` or `implicit`.", - ): - decomposition.MatrixFactorization( - # Intentionally pass in the wrong type. This will fail if the user is using - # a type checker, but we can't assume that everyone is doing so, especially - # not in notebook environments. - num_factors=16, - feedback_type=feedback_type, # type: ignore - user_col="user_id", - item_col="item_col", - rating_col="rating_col", - l2_reg=9.83, - ) - - -def test_decomposition_mf_num_factors_low(): - model = decomposition.MatrixFactorization( - num_factors=0, - feedback_type="explicit", - user_col="user_id", - item_col="item_col", - rating_col="rating_col", - l2_reg=9.83, - ) - assert model.num_factors == 0 - - -def test_decomposition_mf_negative_num_factors_raises(): - num_factors = -2 - with pytest.raises( - ValueError, - match=f"Expected num_factors to be a positive integer, but got {num_factors}.", - ): - decomposition.MatrixFactorization( - num_factors=num_factors, # type: ignore - feedback_type="explicit", - user_col="user_id", - item_col="item_col", - rating_col="rating_col", - l2_reg=9.83, - ) - - -def test_decomposition_mf_invalid_num_factors_raises(): - num_factors = 0.5 - with pytest.raises( - TypeError, - match=f"Expected num_factors to be an int, but got {type(num_factors)}.", - ): - decomposition.MatrixFactorization( - num_factors=num_factors, # type: ignore - feedback_type="explicit", - user_col="user_id", - item_col="item_col", - rating_col="rating_col", - l2_reg=9.83, - ) - - -def test_decomposition_mf_invalid_user_col_raises(): - user_col = 123 - with pytest.raises( - TypeError, match=f"Expected user_col to be a str, but got {type(user_col)}." - ): - decomposition.MatrixFactorization( - num_factors=16, - feedback_type="explicit", - user_col=user_col, # type: ignore - item_col="item_col", - rating_col="rating_col", - l2_reg=9.83, - ) - - -def test_decomposition_mf_invalid_item_col_raises(): - item_col = 123 - with pytest.raises( - TypeError, match=f"Expected item_col to be STR, but got {type(item_col)}." - ): - decomposition.MatrixFactorization( - num_factors=16, - feedback_type="explicit", - user_col="user_id", - item_col=item_col, # type: ignore - rating_col="rating_col", - l2_reg=9.83, - ) - - -def test_decomposition_mf_invalid_rating_col_raises(): - rating_col = 4 - with pytest.raises( - TypeError, match=f"Expected rating_col to be a str, but got {type(rating_col)}." - ): - decomposition.MatrixFactorization( - num_factors=16, - feedback_type="explicit", - user_col="user_id", - item_col="item_col", - rating_col=rating_col, # type: ignore - l2_reg=9.83, - ) - - -def test_decomposition_mf_l2_reg(): - model = decomposition.MatrixFactorization( - num_factors=16, - feedback_type="explicit", - user_col="user_id", - item_col="item_col", - rating_col="rating_col", - l2_reg=6.02, # type: ignore - ) - assert model.l2_reg == 6.02 - - -def test_decomposition_mf_invalid_l2_reg_raises(): - l2_reg = "6.02" - with pytest.raises( - TypeError, - match=f"Expected l2_reg to be a float or int, but got {type(l2_reg)}.", - ): - decomposition.MatrixFactorization( - num_factors=16, - feedback_type="explicit", - user_col="user_id", - item_col="item_col", - rating_col="rating_col", - l2_reg=l2_reg, # type: ignore - ) diff --git a/tests/unit/ml/test_pipeline.py b/tests/unit/ml/test_pipeline.py index beebb9f2821..ed5c621b1df 100644 --- a/tests/unit/ml/test_pipeline.py +++ b/tests/unit/ml/test_pipeline.py @@ -13,6 +13,10 @@ # limitations under the License. import pytest +import sklearn.compose as sklearn_compose # type: ignore +import sklearn.linear_model as sklearn_linear_model # type: ignore +import sklearn.pipeline as sklearn_pipeline # type: ignore +import sklearn.preprocessing as sklearn_preprocessing # type: ignore from bigframes.ml import compose, forecasting, linear_model, pipeline, preprocessing @@ -53,11 +57,8 @@ def test_pipeline_repr(): ) +@pytest.mark.skipif(sklearn_pipeline is None, reason="requires sklearn") def test_pipeline_repr_matches_sklearn(): - sklearn_compose = pytest.importorskip("sklearn.compose") - sklearn_linear_model = pytest.importorskip("sklearn.linear_model") - sklearn_pipeline = pytest.importorskip("sklearn.pipeline") - sklearn_preprocessing = pytest.importorskip("sklearn.preprocessing") bf_pl = pipeline.Pipeline( [ ( diff --git a/tests/unit/ml/test_sql.py b/tests/unit/ml/test_sql.py index d605b571f3e..34a02edd424 100644 --- a/tests/unit/ml/test_sql.py +++ b/tests/unit/ml/test_sql.py @@ -14,7 +14,6 @@ from unittest import mock -import google.cloud.bigquery as bigquery import pytest import bigframes.ml.sql as ml_sql @@ -28,15 +27,13 @@ def base_sql_generator() -> ml_sql.BaseSqlGenerator: @pytest.fixture(scope="session") def model_creation_sql_generator() -> ml_sql.ModelCreationSqlGenerator: - return ml_sql.ModelCreationSqlGenerator() + return ml_sql.ModelCreationSqlGenerator(model_id="my_model_id") @pytest.fixture(scope="session") def model_manipulation_sql_generator() -> ml_sql.ModelManipulationSqlGenerator: return ml_sql.ModelManipulationSqlGenerator( - model_ref=bigquery.ModelReference.from_string( - "my_project_id.my_dataset_id.my_model_id" - ) + model_name="my_project_id.my_dataset_id.my_model_id" ) @@ -49,30 +46,20 @@ def mock_df(): return mock_df -def test_ml_arima_coefficients( - model_manipulation_sql_generator: ml_sql.ModelManipulationSqlGenerator, -): - sql = model_manipulation_sql_generator.ml_arima_coefficients() - assert ( - sql - == """SELECT * FROM ML.ARIMA_COEFFICIENTS(MODEL `my_project_id`.`my_dataset_id`.`my_model_id`)""" - ) - - -def test_options_correct(base_sql_generator: ml_sql.BaseSqlGenerator): +def test_options_produces_correct_sql(base_sql_generator: ml_sql.BaseSqlGenerator): sql = base_sql_generator.options( model_type="lin_reg", input_label_cols=["col_a"], l1_reg=0.6 ) assert ( sql == """OPTIONS( - model_type='lin_reg', - input_label_cols=['col_a'], + model_type="lin_reg", + input_label_cols=["col_a"], l1_reg=0.6)""" ) -def test_transform_correct(base_sql_generator: ml_sql.BaseSqlGenerator): +def test_transform_produces_correct_sql(base_sql_generator: ml_sql.BaseSqlGenerator): sql = base_sql_generator.transform( "ML.STANDARD_SCALER(col_a) OVER(col_a) AS scaled_col_a", "ML.ONE_HOT_ENCODER(col_b) OVER(col_b) AS encoded_col_b", @@ -87,131 +74,76 @@ def test_transform_correct(base_sql_generator: ml_sql.BaseSqlGenerator): ) -def test_standard_scaler_correct( +def test_standard_scaler_produces_correct_sql( base_sql_generator: ml_sql.BaseSqlGenerator, ): sql = base_sql_generator.ml_standard_scaler("col_a", "scaled_col_a") - assert sql == "ML.STANDARD_SCALER(`col_a`) OVER() AS `scaled_col_a`" + assert sql == "ML.STANDARD_SCALER(col_a) OVER() AS scaled_col_a" -def test_max_abs_scaler_correct( +def test_max_abs_scaler_produces_correct_sql( base_sql_generator: ml_sql.BaseSqlGenerator, ): sql = base_sql_generator.ml_max_abs_scaler("col_a", "scaled_col_a") - assert sql == "ML.MAX_ABS_SCALER(`col_a`) OVER() AS `scaled_col_a`" + assert sql == "ML.MAX_ABS_SCALER(col_a) OVER() AS scaled_col_a" -def test_min_max_scaler_correct( +def test_min_max_scaler_produces_correct_sql( base_sql_generator: ml_sql.BaseSqlGenerator, ): sql = base_sql_generator.ml_min_max_scaler("col_a", "scaled_col_a") - assert sql == "ML.MIN_MAX_SCALER(`col_a`) OVER() AS `scaled_col_a`" + assert sql == "ML.MIN_MAX_SCALER(col_a) OVER() AS scaled_col_a" -def test_imputer_correct( - base_sql_generator: ml_sql.BaseSqlGenerator, -): - sql = base_sql_generator.ml_imputer("col_a", "mean", "scaled_col_a") - assert sql == "ML.IMPUTER(`col_a`, 'mean') OVER() AS `scaled_col_a`" - - -def test_k_bins_discretizer_correct( +def test_k_bins_discretizer_produces_correct_sql( base_sql_generator: ml_sql.BaseSqlGenerator, ): sql = base_sql_generator.ml_bucketize("col_a", [1, 2, 3, 4], "scaled_col_a") - assert sql == "ML.BUCKETIZE(`col_a`, [1, 2, 3, 4], FALSE) AS `scaled_col_a`" - - -def test_k_bins_discretizer_quantile_correct( - base_sql_generator: ml_sql.BaseSqlGenerator, -): - sql = base_sql_generator.ml_quantile_bucketize("col_a", 5, "scaled_col_a") - assert sql == "ML.QUANTILE_BUCKETIZE(`col_a`, 5) OVER() AS `scaled_col_a`" + assert sql == "ML.BUCKETIZE(col_a, [1, 2, 3, 4], FALSE) AS scaled_col_a" -def test_one_hot_encoder_correct( +def test_one_hot_encoder_produces_correct_sql( base_sql_generator: ml_sql.BaseSqlGenerator, ): sql = base_sql_generator.ml_one_hot_encoder( "col_a", "none", 1000000, 0, "encoded_col_a" ) assert ( - sql - == "ML.ONE_HOT_ENCODER(`col_a`, 'none', 1000000, 0) OVER() AS `encoded_col_a`" + sql == "ML.ONE_HOT_ENCODER(col_a, 'none', 1000000, 0) OVER() AS encoded_col_a" ) -def test_label_encoder_correct( +def test_label_encoder_produces_correct_sql( base_sql_generator: ml_sql.BaseSqlGenerator, ): sql = base_sql_generator.ml_label_encoder("col_a", 1000000, 0, "encoded_col_a") - assert sql == "ML.LABEL_ENCODER(`col_a`, 1000000, 0) OVER() AS `encoded_col_a`" - - -def test_polynomial_expand( - base_sql_generator: ml_sql.BaseSqlGenerator, -): - sql = base_sql_generator.ml_polynomial_expand(["col_a", "col_b"], 2, "poly_exp") - 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)""" - ) + assert sql == "ML.LABEL_ENCODER(col_a, 1000000, 0) OVER() AS encoded_col_a" -def test_create_model_correct( +def test_create_model_produces_correct_sql( model_creation_sql_generator: ml_sql.ModelCreationSqlGenerator, mock_df: bpd.DataFrame, ): sql = model_creation_sql_generator.create_model( - source_sql=mock_df.sql, - model_ref=bigquery.ModelReference.from_string( - "test-proj._anonXYZ.create_model_correct_sql" - ), + source_df=mock_df, options={"option_key1": "option_value1", "option_key2": 2}, ) assert ( sql - == """CREATE OR REPLACE MODEL `test-proj`.`_anonXYZ`.`create_model_correct_sql` + == """CREATE TEMP MODEL `my_model_id` OPTIONS( - option_key1='option_value1', + option_key1="option_value1", option_key2=2) AS input_X_y_sql""" ) -def test_create_model_transform_correct( +def test_create_model_transform_produces_correct_sql( model_creation_sql_generator: ml_sql.ModelCreationSqlGenerator, mock_df: bpd.DataFrame, ): sql = model_creation_sql_generator.create_model( - source_sql=mock_df.sql, - model_ref=bigquery.ModelReference.from_string( - "test-proj._anonXYZ.create_model_transform" - ), + source_df=mock_df, options={"option_key1": "option_value1", "option_key2": 2}, transforms=[ "ML.STANDARD_SCALER(col_a) OVER(col_a) AS scaled_col_a", @@ -220,124 +152,45 @@ def test_create_model_transform_correct( ) assert ( sql - == """CREATE OR REPLACE MODEL `test-proj`.`_anonXYZ`.`create_model_transform` + == """CREATE TEMP MODEL `my_model_id` TRANSFORM( ML.STANDARD_SCALER(col_a) OVER(col_a) AS scaled_col_a, ML.ONE_HOT_ENCODER(col_b) OVER(col_b) AS encoded_col_b) OPTIONS( - option_key1='option_value1', + option_key1="option_value1", option_key2=2) AS input_X_y_sql""" ) -def test_create_llm_remote_model_correct( - model_creation_sql_generator: ml_sql.ModelCreationSqlGenerator, - mock_df: bpd.DataFrame, -): - sql = model_creation_sql_generator.create_llm_remote_model( - source_sql=mock_df.sql, - connection_name="my_project.us.my_connection", - model_ref=bigquery.ModelReference.from_string( - "test-proj._anonXYZ.create_remote_model" - ), - options={"option_key1": "option_value1", "option_key2": 2}, - ) - assert ( - sql - == """CREATE OR REPLACE MODEL `test-proj`.`_anonXYZ`.`create_remote_model` -REMOTE WITH CONNECTION `my_project.us.my_connection` -OPTIONS( - option_key1='option_value1', - option_key2=2) -AS input_X_y_sql""" - ) - - -def test_create_remote_model_correct( - model_creation_sql_generator: ml_sql.ModelCreationSqlGenerator, -): - sql = model_creation_sql_generator.create_remote_model( - connection_name="my_project.us.my_connection", - model_ref=bigquery.ModelReference.from_string( - "test-proj._anonXYZ.create_remote_model" - ), - options={"option_key1": "option_value1", "option_key2": 2}, - ) - assert ( - sql - == """CREATE OR REPLACE MODEL `test-proj`.`_anonXYZ`.`create_remote_model` -REMOTE WITH CONNECTION `my_project.us.my_connection` -OPTIONS( - option_key1='option_value1', - option_key2=2)""" - ) - - -def test_create_remote_model_with_params_correct( +def test_create_remote_model_produces_correct_sql( model_creation_sql_generator: ml_sql.ModelCreationSqlGenerator, ): sql = model_creation_sql_generator.create_remote_model( connection_name="my_project.us.my_connection", - model_ref=bigquery.ModelReference.from_string( - "test-proj._anonXYZ.create_remote_model" - ), - input={"column1": "int64"}, - output={"result": "array"}, options={"option_key1": "option_value1", "option_key2": 2}, ) assert ( sql - == """CREATE OR REPLACE MODEL `test-proj`.`_anonXYZ`.`create_remote_model` -INPUT( - `column1` int64) -OUTPUT( - `result` array) + == """CREATE TEMP MODEL `my_model_id` REMOTE WITH CONNECTION `my_project.us.my_connection` OPTIONS( - option_key1='option_value1', + option_key1="option_value1", option_key2=2)""" ) -def test_create_imported_model_correct( +def test_create_imported_model_produces_correct_sql( model_creation_sql_generator: ml_sql.ModelCreationSqlGenerator, ): sql = model_creation_sql_generator.create_imported_model( - model_ref=bigquery.ModelReference.from_string( - "test-proj._anonXYZ.create_imported_model" - ), options={"option_key1": "option_value1", "option_key2": 2}, ) assert ( sql - == """CREATE OR REPLACE MODEL `test-proj`.`_anonXYZ`.`create_imported_model` + == """CREATE TEMP MODEL `my_model_id` OPTIONS( - option_key1='option_value1', - option_key2=2)""" - ) - - -def test_create_xgboost_imported_model_produces_correct_sql( - model_creation_sql_generator: ml_sql.ModelCreationSqlGenerator, -): - sql = model_creation_sql_generator.create_xgboost_imported_model( - model_ref=bigquery.ModelReference.from_string( - "test-proj._anonXYZ.create_xgboost_imported_model" - ), - input={"column1": "int64"}, - output={"result": "array"}, - options={"option_key1": "option_value1", "option_key2": 2}, - ) - assert ( - sql - == """CREATE OR REPLACE MODEL `test-proj`.`_anonXYZ`.`create_xgboost_imported_model` -INPUT( - `column1` int64) -OUTPUT( - `result` array) -OPTIONS( - option_key1='option_value1', + option_key1="option_value1", option_key2=2)""" ) @@ -350,182 +203,106 @@ def test_alter_model_correct_sql( ) assert ( sql - == """ALTER MODEL `my_project_id`.`my_dataset_id`.`my_model_id` + == """ALTER MODEL `my_project_id.my_dataset_id.my_model_id` SET OPTIONS( - option_key1='option_value1', + option_key1="option_value1", option_key2=2)""" ) -def test_ml_predict_correct( +def test_ml_predict_produces_correct_sql( model_manipulation_sql_generator: ml_sql.ModelManipulationSqlGenerator, mock_df: bpd.DataFrame, ): - sql = model_manipulation_sql_generator.ml_predict(source_sql=mock_df.sql) + sql = model_manipulation_sql_generator.ml_predict(source_df=mock_df) assert ( sql - == """SELECT * FROM ML.PREDICT(MODEL `my_project_id`.`my_dataset_id`.`my_model_id`, - (input_X_y_sql))""" + == """SELECT * FROM ML.PREDICT(MODEL `my_project_id.my_dataset_id.my_model_id`, + (input_X_sql))""" ) -def test_ml_llm_evaluate_correct( +def test_ml_evaluate_produces_correct_sql( model_manipulation_sql_generator: ml_sql.ModelManipulationSqlGenerator, mock_df: bpd.DataFrame, ): - sql = model_manipulation_sql_generator.ml_llm_evaluate( - source_sql=mock_df.sql, task_type="CLASSIFICATION" - ) - assert ( - sql - == """SELECT * FROM ML.EVALUATE(MODEL `my_project_id`.`my_dataset_id`.`my_model_id`, - (input_X_y_sql), STRUCT("CLASSIFICATION" AS task_type))""" - ) - - -def test_ml_evaluate_correct( - model_manipulation_sql_generator: ml_sql.ModelManipulationSqlGenerator, - mock_df: bpd.DataFrame, -): - sql = model_manipulation_sql_generator.ml_evaluate(source_sql=mock_df.sql) - assert ( - sql - == """SELECT * FROM ML.EVALUATE(MODEL `my_project_id`.`my_dataset_id`.`my_model_id`, - (input_X_y_sql))""" - ) - - -def test_ml_arima_evaluate_correct( - model_manipulation_sql_generator: ml_sql.ModelManipulationSqlGenerator, -): - sql = model_manipulation_sql_generator.ml_arima_evaluate( - show_all_candidate_models=True - ) + sql = model_manipulation_sql_generator.ml_evaluate(source_df=mock_df) assert ( sql - == """SELECT * FROM ML.ARIMA_EVALUATE(MODEL `my_project_id`.`my_dataset_id`.`my_model_id`, - STRUCT(True AS show_all_candidate_models))""" + == """SELECT * FROM ML.EVALUATE(MODEL `my_project_id.my_dataset_id.my_model_id`, + (input_X_sql))""" ) -def test_ml_evaluate_no_source_correct( +def test_ml_evaluate_no_source_produces_correct_sql( model_manipulation_sql_generator: ml_sql.ModelManipulationSqlGenerator, ): sql = model_manipulation_sql_generator.ml_evaluate() assert ( sql - == """SELECT * FROM ML.EVALUATE(MODEL `my_project_id`.`my_dataset_id`.`my_model_id`)""" + == """SELECT * FROM ML.EVALUATE(MODEL `my_project_id.my_dataset_id.my_model_id`)""" ) -def test_ml_centroids_correct( +def test_ml_centroids_produces_correct_sql( model_manipulation_sql_generator: ml_sql.ModelManipulationSqlGenerator, ): sql = model_manipulation_sql_generator.ml_centroids() assert ( sql - == """SELECT * FROM ML.CENTROIDS(MODEL `my_project_id`.`my_dataset_id`.`my_model_id`)""" + == """SELECT * FROM ML.CENTROIDS(MODEL `my_project_id.my_dataset_id.my_model_id`)""" ) -def test_ml_forecast_correct_sql( - model_manipulation_sql_generator: ml_sql.ModelManipulationSqlGenerator, -): - sql = model_manipulation_sql_generator.ml_forecast( - struct_options={"option_key1": 1, "option_key2": 2.2}, - ) - assert ( - sql - == """SELECT * FROM ML.FORECAST(MODEL `my_project_id`.`my_dataset_id`.`my_model_id`, - STRUCT( - 1 AS `option_key1`, - 2.2 AS `option_key2`))""" - ) - - -def test_ml_generate_text_correct( +def test_ml_generate_text_produces_correct_sql( model_manipulation_sql_generator: ml_sql.ModelManipulationSqlGenerator, mock_df: bpd.DataFrame, ): sql = model_manipulation_sql_generator.ml_generate_text( - source_sql=mock_df.sql, - struct_options={"option_key1": 1, "option_key2": 2.2}, - ) - assert ( - sql - == """SELECT * FROM ML.GENERATE_TEXT(MODEL `my_project_id`.`my_dataset_id`.`my_model_id`, - (input_X_y_sql), STRUCT( - 1 AS `option_key1`, - 2.2 AS `option_key2`))""" - ) - - -def test_ml_generate_embedding_correct( - model_manipulation_sql_generator: ml_sql.ModelManipulationSqlGenerator, - mock_df: bpd.DataFrame, -): - sql = model_manipulation_sql_generator.ml_generate_embedding( - source_sql=mock_df.sql, + source_df=mock_df, struct_options={"option_key1": 1, "option_key2": 2.2}, ) assert ( sql - == """SELECT * FROM ML.GENERATE_EMBEDDING(MODEL `my_project_id`.`my_dataset_id`.`my_model_id`, - (input_X_y_sql), STRUCT( - 1 AS `option_key1`, - 2.2 AS `option_key2`))""" - ) - - -def test_ml_explain_predict_correct( - model_manipulation_sql_generator: ml_sql.ModelManipulationSqlGenerator, - mock_df: bpd.DataFrame, -): - sql = model_manipulation_sql_generator.ml_explain_predict( - source_sql=mock_df.sql, - struct_options={"option_key1": 1, "option_key2": 2.25}, - ) - assert ( - sql - == """SELECT * FROM ML.EXPLAIN_PREDICT(MODEL `my_project_id`.`my_dataset_id`.`my_model_id`, - (input_X_y_sql), STRUCT( - 1 AS `option_key1`, - 2.25 AS `option_key2`))""" + == """SELECT * FROM ML.GENERATE_TEXT(MODEL `my_project_id.my_dataset_id.my_model_id`, + (input_X_sql), STRUCT( + 1 AS option_key1, + 2.2 AS option_key2))""" ) -def test_ml_detect_anomalies_correct_sql( +def test_ml_generate_text_embedding_produces_correct_sql( model_manipulation_sql_generator: ml_sql.ModelManipulationSqlGenerator, mock_df: bpd.DataFrame, ): - sql = model_manipulation_sql_generator.ml_detect_anomalies( - source_sql=mock_df.sql, + sql = model_manipulation_sql_generator.ml_generate_text_embedding( + source_df=mock_df, struct_options={"option_key1": 1, "option_key2": 2.2}, ) assert ( sql - == """SELECT * FROM ML.DETECT_ANOMALIES(MODEL `my_project_id`.`my_dataset_id`.`my_model_id`, - STRUCT( - 1 AS `option_key1`, - 2.2 AS `option_key2`), (input_X_y_sql))""" + == """SELECT * FROM ML.GENERATE_TEXT_EMBEDDING(MODEL `my_project_id.my_dataset_id.my_model_id`, + (input_X_sql), STRUCT( + 1 AS option_key1, + 2.2 AS option_key2))""" ) -def test_ml_principal_components_correct( +def test_ml_principal_components_produces_correct_sql( model_manipulation_sql_generator: ml_sql.ModelManipulationSqlGenerator, ): sql = model_manipulation_sql_generator.ml_principal_components() assert ( sql - == """SELECT * FROM ML.PRINCIPAL_COMPONENTS(MODEL `my_project_id`.`my_dataset_id`.`my_model_id`)""" + == """SELECT * FROM ML.PRINCIPAL_COMPONENTS(MODEL `my_project_id.my_dataset_id.my_model_id`)""" ) -def test_ml_principal_component_info_correct( +def test_ml_principal_component_info_produces_correct_sql( model_manipulation_sql_generator: ml_sql.ModelManipulationSqlGenerator, ): sql = model_manipulation_sql_generator.ml_principal_component_info() assert ( sql - == """SELECT * FROM ML.PRINCIPAL_COMPONENT_INFO(MODEL `my_project_id`.`my_dataset_id`.`my_model_id`)""" + == """SELECT * FROM ML.PRINCIPAL_COMPONENT_INFO(MODEL `my_project_id.my_dataset_id.my_model_id`)""" ) diff --git a/tests/unit/operations/__init__.py b/tests/unit/operations/__init__.py deleted file mode 100644 index 6d5e14bcf4a..00000000000 --- a/tests/unit/operations/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -# 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. -# 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/unit/operations/test_output_schemas.py b/tests/unit/operations/test_output_schemas.py deleted file mode 100644 index 204078a5ceb..00000000000 --- a/tests/unit/operations/test_output_schemas.py +++ /dev/null @@ -1,99 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import pyarrow as pa -import pytest - -from bigframes.operations import output_schemas - - -@pytest.mark.parametrize( - ("sql", "expected"), - [ - ("INT64", pa.int64()), - (" INT64 ", pa.int64()), - ("int64", pa.int64()), - ("FLOAT64", pa.float64()), - ("STRING", pa.string()), - ("BOOL", pa.bool_()), - ("ARRAY", pa.list_(pa.int64())), - ( - "STRUCT", - pa.struct((pa.field("x", pa.int64()), pa.field("y", pa.float64()))), - ), - ( - "STRUCT< x INT64, y FLOAT64>", - pa.struct((pa.field("x", pa.int64()), pa.field("y", pa.float64()))), - ), - ( - "STRUCT", - pa.struct((pa.field("x", pa.float64()), pa.field("y", pa.int64()))), - ), - ( - "ARRAY>", - pa.list_(pa.struct((pa.field("x", pa.int64()), pa.field("y", pa.int64())))), - ), - ( - "STRUCT, x ARRAY>", - pa.struct( - ( - pa.field("x", pa.list_(pa.float64())), - pa.field( - "y", - pa.struct( - (pa.field("a", pa.bool_()), pa.field("b", pa.string())) - ), - ), - ) - ), - ), - ], -) -def test_parse_sql_to_pyarrow_dtype(sql, expected): - assert output_schemas.parse_sql_type(sql) == expected - - -@pytest.mark.parametrize( - "sql", - [ - "a INT64", - "ARRAY<>", - "ARRAYARRAYSTRUCT<>", - "DATE", - "STRUCT", - "ARRAY>", - ], -) -def test_parse_sql_to_pyarrow_dtype_invalid_input_raies_error(sql): - with pytest.raises(ValueError): - output_schemas.parse_sql_type(sql) - - -@pytest.mark.parametrize( - ("sql", "expected"), - [ - ("x INT64", (pa.field("x", pa.int64()),)), - ( - "x INT64, y FLOAT64", - (pa.field("x", pa.int64()), pa.field("y", pa.float64())), - ), - ( - "y FLOAT64, x INT64", - (pa.field("x", pa.int64()), pa.field("y", pa.float64())), - ), - ], -) -def test_parse_sql_fields(sql, expected): - assert output_schemas.parse_sql_fields(sql) == expected diff --git a/tests/unit/pandas/io/test_api.py b/tests/unit/pandas/io/test_api.py deleted file mode 100644 index dbdf427d91b..00000000000 --- a/tests/unit/pandas/io/test_api.py +++ /dev/null @@ -1,129 +0,0 @@ -# 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. -# 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 unittest import mock - -import google.cloud.bigquery -import pytest - -import bigframes._config.auth -import bigframes.dataframe -import bigframes.pandas -import bigframes.pandas.io.api as bf_io_api -import bigframes.session -import bigframes.session.clients - -# _read_gbq_colab requires the polars engine. -pytest.importorskip("polars") - - -@mock.patch( - "bigframes.pandas.io.api._set_default_session_location_if_possible_deferred_query" -) -@mock.patch("bigframes.core.global_session.with_default_session") -def test_read_gbq_colab_dry_run_doesnt_call_set_location( - mock_with_default_session, mock_set_location -): - """ - Ensure that we don't bind to a location too early. If it's a dry run, the - user might not be done typing. - """ - mock_df = mock.create_autospec(bigframes.dataframe.DataFrame) - mock_with_default_session.return_value = mock_df - - query_or_table = "SELECT {param1} AS param1" - sample_pyformat_args = {"param1": "value1"} - bf_io_api._read_gbq_colab( - query_or_table, pyformat_args=sample_pyformat_args, dry_run=True - ) - - mock_set_location.assert_not_called() - - -@mock.patch("bigframes._config.auth.pydata_google_auth.default") -@mock.patch("bigframes.core.global_session.with_default_session") -def test_read_gbq_colab_dry_run_doesnt_authenticate_multiple_times( - mock_with_default_session, mock_get_credentials, monkeypatch -): - """ - Ensure that we authenticate too often, which is an expensive operation, - performance-wise (2+ seconds). - """ - bigframes.pandas.close_session() - - mock_get_credentials.return_value = (mock.Mock(), "unit-test-project") - mock_create_bq_client = mock.Mock() - mock_bq_client = mock.create_autospec(google.cloud.bigquery.Client, instance=True) - mock_create_bq_client.return_value = mock_bq_client - mock_query_job = mock.create_autospec(google.cloud.bigquery.QueryJob, instance=True) - type(mock_query_job).schema = mock.PropertyMock(return_value=[]) - mock_query_job._properties = {} - mock_bq_client.query.return_value = mock_query_job - monkeypatch.setattr( - bigframes.session.clients.ClientsProvider, - "_create_bigquery_client", - mock_create_bq_client, - ) - mock_df = mock.create_autospec(bigframes.dataframe.DataFrame) - mock_with_default_session.return_value = mock_df - - bigframes._config.auth._cached_credentials = None - query_or_table = "SELECT {param1} AS param1" - sample_pyformat_args = {"param1": "value1"} - bf_io_api._read_gbq_colab( - query_or_table, pyformat_args=sample_pyformat_args, dry_run=True - ) - - mock_get_credentials.assert_called() - mock_with_default_session.assert_not_called() - mock_get_credentials.reset_mock() - - # Repeat the operation so that the credentials would have have been cached. - bf_io_api._read_gbq_colab( - query_or_table, pyformat_args=sample_pyformat_args, dry_run=True - ) - mock_get_credentials.assert_not_called() - - -@mock.patch( - "bigframes.pandas.io.api._set_default_session_location_if_possible_deferred_query" -) -@mock.patch("bigframes.core.global_session.with_default_session") -def test_read_gbq_colab_calls_set_location( - mock_with_default_session, mock_set_location -): - # Configure the mock for with_default_session to return a DataFrame mock - mock_df = mock.create_autospec(bigframes.dataframe.DataFrame) - mock_with_default_session.return_value = mock_df - - query_or_table = "SELECT {param1} AS param1" - sample_pyformat_args = {"param1": "'value1'"} - result = bf_io_api._read_gbq_colab( - query_or_table, pyformat_args=sample_pyformat_args, dry_run=False - ) - - # Make sure that we format the SQL first to prevent syntax errors. - formatted_query = "SELECT 'value1' AS param1" - mock_set_location.assert_called_once() - args, _ = mock_set_location.call_args - assert formatted_query == args[0]() - mock_with_default_session.assert_called_once() - - # Check the actual arguments passed to with_default_session - args, kwargs = mock_with_default_session.call_args - assert args[0] == bigframes.session.Session._read_gbq_colab - assert args[1] == query_or_table - assert kwargs["pyformat_args"] == sample_pyformat_args - assert not kwargs["dry_run"] - assert isinstance(result, bigframes.dataframe.DataFrame) diff --git a/tests/unit/resources.py b/tests/unit/resources.py new file mode 100644 index 00000000000..8fc8acd1759 --- /dev/null +++ b/tests/unit/resources.py @@ -0,0 +1,113 @@ +# Copyright 2023 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 typing import Dict, List, Optional +import unittest.mock as mock + +import google.auth.credentials +import google.cloud.bigquery +import ibis +import pandas +import pytest + +import bigframes +import bigframes.core as core +import bigframes.core.ordering +import bigframes.dataframe +import bigframes.session.clients + +"""Utilities for creating test resources.""" + + +def create_bigquery_session( + bqclient: Optional[mock.Mock] = None, + session_id: str = "abcxyz", + anonymous_dataset: Optional[google.cloud.bigquery.DatasetReference] = None, +) -> bigframes.Session: + credentials = mock.create_autospec( + google.auth.credentials.Credentials, instance=True + ) + + if bqclient is None: + bqclient = mock.create_autospec(google.cloud.bigquery.Client, instance=True) + bqclient.project = "test-project" + + if anonymous_dataset is None: + anonymous_dataset = google.cloud.bigquery.DatasetReference( + "test-project", + "test_dataset", + ) + + query_job = mock.create_autospec(google.cloud.bigquery.QueryJob) + type(query_job).destination = mock.PropertyMock( + return_value=anonymous_dataset.table("test_table"), + ) + type(query_job).session_info = google.cloud.bigquery.SessionInfo( + {"sessionInfo": {"sessionId": session_id}}, + ) + bqclient.query.return_value = query_job + + clients_provider = mock.create_autospec(bigframes.session.clients.ClientsProvider) + type(clients_provider).bqclient = mock.PropertyMock(return_value=bqclient) + clients_provider._credentials = credentials + + bqoptions = bigframes.BigQueryOptions( + credentials=credentials, location="test-region" + ) + session = bigframes.Session(context=bqoptions, clients_provider=clients_provider) + session._session_id = session_id + return session + + +def create_dataframe( + monkeypatch: pytest.MonkeyPatch, session: Optional[bigframes.Session] = None +) -> bigframes.dataframe.DataFrame: + if session is None: + session = create_bigquery_session() + + # Since this may create a ReadLocalNode, the session we explicitly pass in + # might not actually be used. Mock out the global session, too. + monkeypatch.setattr(bigframes.core.global_session, "_global_session", session) + bigframes.options.bigquery._session_started = True + return bigframes.dataframe.DataFrame({}, session=session) + + +def create_pandas_session(tables: Dict[str, pandas.DataFrame]) -> bigframes.Session: + # TODO(tswast): Refactor to make helper available for all tests. Consider + # providing a proper "local Session" for use by downstream developers. + session = mock.create_autospec(bigframes.Session, instance=True) + ibis_client = ibis.pandas.connect(tables) + type(session).ibis_client = mock.PropertyMock(return_value=ibis_client) + return session + + +def create_arrayvalue( + df: pandas.DataFrame, total_ordering_columns: List[str] +) -> core.ArrayValue: + session = create_pandas_session({"test_table": df}) + ibis_table = session.ibis_client.table("test_table") + columns = tuple(ibis_table[key] for key in ibis_table.columns) + ordering = bigframes.core.ordering.ExpressionOrdering( + tuple( + [core.OrderingColumnReference(column) for column in total_ordering_columns] + ), + total_ordering_columns=frozenset(total_ordering_columns), + ) + return core.ArrayValue.from_ibis( + session=session, + table=ibis_table, + columns=columns, + hidden_ordering_columns=(), + ordering=ordering, + ) diff --git a/tests/unit/session/test_clients.py b/tests/unit/session/test_clients.py index 0de6c75e01b..f1b2a5045a3 100644 --- a/tests/unit/session/test_clients.py +++ b/tests/unit/session/test_clients.py @@ -12,25 +12,25 @@ # See the License for the specific language governing permissions and # limitations under the License. -import os -import pathlib -import tempfile +from typing import Optional import unittest.mock as mock -from typing import Optional, cast +import google.api_core.client_info +import google.api_core.client_options +import google.api_core.exceptions +import google.api_core.gapic_v1.client_info import google.auth.credentials import google.cloud.bigquery import google.cloud.bigquery_connection_v1 import google.cloud.bigquery_storage_v1 import google.cloud.functions_v2 import google.cloud.resourcemanager_v3 -import requests.adapters import bigframes.session.clients as clients import bigframes.version -def create_clients_provider(application_name: Optional[str] = None, **kwargs): +def create_clients_provider(application_name: Optional[str] = None): credentials = mock.create_autospec(google.auth.credentials.Credentials) return clients.ClientsProvider( project="test-project", @@ -38,16 +38,12 @@ def create_clients_provider(application_name: Optional[str] = None, **kwargs): use_regional_endpoints=False, credentials=credentials, application_name=application_name, - bq_kms_key_name="projects/my-project/locations/us/keyRings/myKeyRing/cryptoKeys/myKey", - **kwargs, ) def monkeypatch_client_constructors(monkeypatch): bqclient = mock.create_autospec(google.cloud.bigquery.Client) bqclient.return_value = bqclient - # Assume we have a new client library in the unit tests. - bqclient.default_job_creation_mode = None # type: ignore monkeypatch.setattr(google.cloud.bigquery, "Client", bqclient) bqconnectionclient = mock.create_autospec( @@ -85,11 +81,6 @@ def monkeypatch_client_constructors(monkeypatch): ) -def assert_bqclient_sets_default_job_creation_mode(provider: clients.ClientsProvider): - bqclient = provider.bqclient - assert bqclient.default_job_creation_mode == "JOB_CREATION_OPTIONAL" - - def assert_constructed_w_user_agent(mock_client: mock.Mock, expected_user_agent: str): assert ( expected_user_agent @@ -107,51 +98,6 @@ def assert_clients_w_user_agent( assert_constructed_w_user_agent(provider.resourcemanagerclient, expected_user_agent) -def assert_constructed_wo_user_agent( - mock_client: mock.Mock, not_expected_user_agent: str -): - assert ( - not_expected_user_agent - not in mock_client.call_args.kwargs["client_info"].to_user_agent() - ) - - -def assert_clients_wo_user_agent( - provider: clients.ClientsProvider, not_expected_user_agent: str -): - assert_constructed_wo_user_agent(provider.bqclient, not_expected_user_agent) - assert_constructed_wo_user_agent( - provider.bqconnectionclient, not_expected_user_agent - ) - assert_constructed_wo_user_agent( - provider.bqstoragereadclient, not_expected_user_agent - ) - assert_constructed_wo_user_agent( - provider.cloudfunctionsclient, not_expected_user_agent - ) - assert_constructed_wo_user_agent( - provider.resourcemanagerclient, not_expected_user_agent - ) - - -def test_requests_transport_adapters_pool_maxsize(monkeypatch): - monkeypatch_client_constructors(monkeypatch) - requests_transport_adapters = ( - ("http://", requests.adapters.HTTPAdapter(pool_maxsize=123)), - ("https://", requests.adapters.HTTPAdapter(pool_maxsize=123)), - ) # doctest: +SKIP - provider = create_clients_provider( - requests_transport_adapters=requests_transport_adapters - ) - - _, kwargs = cast(mock.Mock, provider.bqclient).call_args - requests_session = kwargs.get("_http") - adapter: requests.adapters.HTTPAdapter = requests_session.get_adapter( - "https://bigquery.googleapis.com/" - ) - assert adapter._pool_maxsize == 123 # type: ignore - - def test_user_agent_default(monkeypatch): monkeypatch_client_constructors(monkeypatch) provider = create_clients_provider(application_name=None) @@ -166,113 +112,3 @@ def test_user_agent_custom(monkeypatch): # We still need to include attribution to bigframes, even if there's also a # partner using the package. assert_clients_w_user_agent(provider, f"bigframes/{bigframes.version.__version__}") - - -@mock.patch.dict(os.environ, {}, clear=True) -def test_user_agent_not_in_vscode(monkeypatch): - monkeypatch_client_constructors(monkeypatch) - provider = create_clients_provider() - assert_clients_wo_user_agent(provider, "vscode") - assert_clients_wo_user_agent(provider, "googlecloudtools.cloudcode") - - # We still need to include attribution to bigframes - assert_clients_w_user_agent(provider, f"bigframes/{bigframes.version.__version__}") - - -@mock.patch.dict(os.environ, {"VSCODE_PID": "12345"}, clear=True) -def test_user_agent_in_vscode(monkeypatch): - monkeypatch_client_constructors(monkeypatch) - - with tempfile.TemporaryDirectory() as tmpdir: - user_home = pathlib.Path(tmpdir) - with mock.patch("pathlib.Path.home", return_value=user_home): - provider = create_clients_provider() - assert_clients_w_user_agent(provider, "vscode") - assert_clients_wo_user_agent(provider, "googlecloudtools.cloudcode") - - # We still need to include attribution to bigframes - assert_clients_w_user_agent( - provider, f"bigframes/{bigframes.version.__version__}" - ) - - -@mock.patch.dict(os.environ, {"VSCODE_PID": "12345"}, clear=True) -def test_user_agent_in_vscode_w_extension(monkeypatch): - monkeypatch_client_constructors(monkeypatch) - - with tempfile.TemporaryDirectory() as tmpdir: - user_home = pathlib.Path(tmpdir) - extension_dir = ( - user_home / ".vscode" / "extensions" / "googlecloudtools.cloudcode-0.12" - ) - extension_config = extension_dir / "package.json" - - # originally extension config does not exist - assert not extension_config.exists() - - # simulate extension installation by creating extension config on disk - extension_dir.mkdir(parents=True) - with open(extension_config, "w") as f: - f.write("{}") - - with mock.patch("pathlib.Path.home", return_value=user_home): - provider = create_clients_provider() - assert_clients_w_user_agent(provider, "vscode") - assert_clients_w_user_agent(provider, "googlecloudtools.cloudcode") - - # We still need to include attribution to bigframes - assert_clients_w_user_agent( - provider, f"bigframes/{bigframes.version.__version__}" - ) - - -@mock.patch.dict(os.environ, {}, clear=True) -def test_user_agent_not_in_jupyter(monkeypatch): - monkeypatch_client_constructors(monkeypatch) - provider = create_clients_provider() - assert_clients_wo_user_agent(provider, "jupyter") - assert_clients_wo_user_agent(provider, "bigquery_jupyter_plugin") - - # We still need to include attribution to bigframes - assert_clients_w_user_agent(provider, f"bigframes/{bigframes.version.__version__}") - - -@mock.patch.dict(os.environ, {"JPY_PARENT_PID": "12345"}, clear=True) -def test_user_agent_in_jupyter(monkeypatch): - monkeypatch_client_constructors(monkeypatch) - provider = create_clients_provider() - assert_clients_w_user_agent(provider, "jupyter") - assert_clients_wo_user_agent(provider, "bigquery_jupyter_plugin") - - # We still need to include attribution to bigframes - assert_clients_w_user_agent(provider, f"bigframes/{bigframes.version.__version__}") - - -@mock.patch.dict(os.environ, {"JPY_PARENT_PID": "12345"}, clear=True) -def test_user_agent_in_jupyter_with_plugin(monkeypatch): - monkeypatch_client_constructors(monkeypatch) - - def custom_import_module_side_effect(name, package=None): - if name == "bigquery_jupyter_plugin": - return mock.MagicMock() - else: - import importlib - - return importlib.import_module(name, package) - - assert isinstance( - custom_import_module_side_effect("bigquery_jupyter_plugin"), mock.MagicMock - ) - assert custom_import_module_side_effect("bigframes") is bigframes - - with mock.patch( - "importlib.import_module", side_effect=custom_import_module_side_effect - ): - provider = create_clients_provider() - assert_clients_w_user_agent(provider, "jupyter") - assert_clients_w_user_agent(provider, "bigquery_jupyter_plugin") - - # We still need to include attribution to bigframes - assert_clients_w_user_agent( - provider, f"bigframes/{bigframes.version.__version__}" - ) diff --git a/tests/unit/session/test_io_arrow.py b/tests/unit/session/test_io_arrow.py deleted file mode 100644 index d5266220d9a..00000000000 --- a/tests/unit/session/test_io_arrow.py +++ /dev/null @@ -1,133 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import datetime - -import pyarrow as pa -import pytest - -import bigframes.pandas as bpd -from bigframes.testing import mocks - - -@pytest.fixture(scope="module") -def session(): - # Use the mock session from bigframes.testing - return mocks.create_bigquery_session() - - -def test_read_arrow_empty_table(session): - empty_table = pa.Table.from_pydict( - { - "col_a": pa.array([], type=pa.int64()), - "col_b": pa.array([], type=pa.string()), - } - ) - df = session.read_arrow(empty_table) - assert isinstance(df, bpd.DataFrame) - assert df.shape == (0, 2) - assert list(df.columns) == ["col_a", "col_b"] - pd_df = df.to_pandas() - assert pd_df.empty - assert list(pd_df.columns) == ["col_a", "col_b"] - assert pd_df["col_a"].dtype == "Int64" - assert pd_df["col_b"].dtype == "string[pyarrow]" - - -@pytest.mark.parametrize( - "data,arrow_type,expected_bq_type_kind", - [ - ([1, 2], pa.int8(), "INTEGER"), - ([1, 2], pa.int16(), "INTEGER"), - ([1, 2], pa.int32(), "INTEGER"), - ([1, 2], pa.int64(), "INTEGER"), - ([1.0, 2.0], pa.float32(), "FLOAT"), - ([1.0, 2.0], pa.float64(), "FLOAT"), - ([True, False], pa.bool_(), "BOOLEAN"), - (["a", "b"], pa.string(), "STRING"), - (["a", "b"], pa.large_string(), "STRING"), - ([b"a", b"b"], pa.binary(), "BYTES"), - ([b"a", b"b"], pa.large_binary(), "BYTES"), - ( - [ - pa.scalar(1000, type=pa.duration("s")), - pa.scalar(2000, type=pa.duration("s")), - ], - pa.duration("s"), - "INTEGER", - ), - ([datetime.date(2023, 1, 1)], pa.date32(), "DATE"), - ( - [datetime.datetime(2023, 1, 1, 12, 0, 0, tzinfo=datetime.timezone.utc)], - pa.timestamp("s", tz="UTC"), - "TIMESTAMP", - ), - ( - [datetime.datetime(2023, 1, 1, 12, 0, 0, tzinfo=datetime.timezone.utc)], - pa.timestamp("ms", tz="UTC"), - "TIMESTAMP", - ), - ( - [datetime.datetime(2023, 1, 1, 12, 0, 0, tzinfo=datetime.timezone.utc)], - pa.timestamp("us", tz="UTC"), - "TIMESTAMP", - ), - ([datetime.time(12, 34, 56, 789000)], pa.time64("us"), "TIME"), - ], -) -def test_read_arrow_type_mappings(session, data, arrow_type, expected_bq_type_kind): - """ - Tests that various arrow types are mapped to the expected BigQuery types. - This is an indirect check via the resulting DataFrame's schema. - """ - pa_table = pa.Table.from_arrays([pa.array(data, type=arrow_type)], names=["col"]) - df = session.read_arrow(pa_table) - - bigquery_schema = df._block.expr.schema.to_bigquery() - assert len(bigquery_schema) == 2 # offsets + value - field = bigquery_schema[-1] - assert field.field_type.upper() == expected_bq_type_kind - - # Also check pandas dtype after conversion for good measure - pd_df = df.to_pandas() - assert pd_df["col"].shape == (len(data),) - - -def test_read_arrow_list_type(session): - pa_table = pa.Table.from_arrays( - [pa.array([[1, 2], [3, 4, 5]], type=pa.list_(pa.int64()))], names=["list_col"] - ) - df = session.read_arrow(pa_table) - - bigquery_schema = df._block.expr.schema.to_bigquery() - assert len(bigquery_schema) == 2 # offsets + value - field = bigquery_schema[-1] - assert field.mode.upper() == "REPEATED" - assert field.field_type.upper() == "INTEGER" - - -def test_read_arrow_struct_type(session): - struct_type = pa.struct([("a", pa.int64()), ("b", pa.string())]) - pa_table = pa.Table.from_arrays( - [pa.array([{"a": 1, "b": "x"}, {"a": 2, "b": "y"}], type=struct_type)], - names=["struct_col"], - ) - df = session.read_arrow(pa_table) - - bigquery_schema = df._block.expr.schema.to_bigquery() - assert len(bigquery_schema) == 2 # offsets + value - field = bigquery_schema[-1] - assert field.field_type.upper() == "RECORD" - assert field.fields[0].name == "a" - assert field.fields[1].name == "b" diff --git a/tests/unit/session/test_io_bigquery.py b/tests/unit/session/test_io_bigquery.py index e6fa7a901ec..03470208e42 100644 --- a/tests/unit/session/test_io_bigquery.py +++ b/tests/unit/session/test_io_bigquery.py @@ -13,238 +13,57 @@ # limitations under the License. import datetime -import re -from typing import Iterable, Optional -from unittest import mock +from typing import Iterable +import unittest.mock as mock import google.cloud.bigquery as bigquery -import google.cloud.bigquery.job -import google.cloud.bigquery.table import pytest -import bigframes -import bigframes.core.events -import bigframes.pandas as bpd import bigframes.session._io.bigquery -import bigframes.session._io.bigquery as io_bq -from bigframes.core.logging import log_adapter -from bigframes.testing import mocks -@pytest.fixture(scope="function") -def mock_bq_client(): - mock_client = mock.create_autospec(bigquery.Client) - mock_query_job = mock.create_autospec(bigquery.QueryJob) - mock_row_iterator = mock.create_autospec(google.cloud.bigquery.table.RowIterator) - - mock_query_job.result.return_value = mock_row_iterator - - mock_destination = bigquery.DatasetReference( - project="mock_project", dataset_id="mock_dataset" - ) - mock_query_job.destination = mock_destination - - mock_client.query.return_value = mock_query_job - - return mock_client - - -def test_create_job_configs_labels_is_none(): - api_methods = ["agg", "series-mode"] - labels = io_bq.create_job_configs_labels( - job_configs_labels=None, api_methods=api_methods - ) - expected_dict = {"bigframes-api": "agg", "recent-bigframes-api-0": "series-mode"} - assert labels is not None - assert labels == expected_dict - - -def test_create_job_configs_labels_always_includes_bigframes_api(): - labels = io_bq.create_job_configs_labels(None, []) - assert labels == { - "bigframes-api": "unknown", - } - - -def test_create_job_configs_labels_length_limit_not_met(): - cur_labels = { - "source": "bigquery-dataframes-temp", - } - api_methods = ["agg", "series-mode"] - labels = io_bq.create_job_configs_labels( - job_configs_labels=cur_labels, api_methods=api_methods - ) - expected_dict = { - "source": "bigquery-dataframes-temp", - "bigframes-api": "agg", - "recent-bigframes-api-0": "series-mode", - } - assert labels is not None - assert len(labels) == 3 - assert labels == expected_dict - - -def test_create_job_configs_labels_log_adaptor_call_method_under_length_limit(): - log_adapter.get_and_reset_api_methods() - cur_labels = { - "source": "bigquery-dataframes-temp", - } - api_methods = [ - "dataframe-columns", - "dataframe-max", - "dataframe-head", - "dataframe-__init__", - ] - - labels = io_bq.create_job_configs_labels( - job_configs_labels=cur_labels, api_methods=api_methods +def test_create_snapshot_sql_doesnt_timetravel_anonymous_datasets(): + table_ref = bigquery.TableReference.from_string( + "my-test-project._e8166e0cdb.anonbb92cd" ) - expected_labels = { - "source": "bigquery-dataframes-temp", - "bigframes-api": "dataframe-columns", - "recent-bigframes-api-0": "dataframe-max", - "recent-bigframes-api-1": "dataframe-head", - "recent-bigframes-api-2": "dataframe-__init__", - } - # Asserts that all items in expected_labels are present in labels - assert labels.items() >= expected_labels.items() - -def test_create_job_configs_labels_length_limit_met_and_labels_is_none(): - log_adapter.get_and_reset_api_methods() - # Test running methods more than the labels' length limit - api_methods = list(["dataframe-head"] * 100) - - with bpd.option_context("compute.extra_query_labels", {}): - labels = io_bq.create_job_configs_labels( - job_configs_labels=None, api_methods=api_methods - ) - assert labels is not None - assert len(labels) == log_adapter.MAX_LABELS_COUNT - assert "dataframe-head" in labels.values() - - -def test_create_job_configs_labels_length_limit_met(): - log_adapter.get_and_reset_api_methods() - cur_labels = { - "bigframes-api": "read_pandas", - "source": "bigquery-dataframes-temp", - } - for i in range(53): - key = f"bigframes-api-test-{i}" - value = f"test{i}" - cur_labels[key] = value - # If cur_labels length is 62, we can only add one label from api_methods - # Test running two methods - api_methods = ["dataframe-max", "dataframe-head"] - - with bpd.option_context("compute.extra_query_labels", {}): - labels = io_bq.create_job_configs_labels( - job_configs_labels=cur_labels, api_methods=api_methods - ) - - assert labels is not None - assert "dataframe-max" in labels.values() - assert "dataframe-head" not in labels.values() - assert "bigframes-api" in labels.keys() - assert "source" in labels.keys() - - -def test_add_and_trim_labels_length_limit_met(): - log_adapter.get_and_reset_api_methods() - cur_labels = { - "bigframes-api": "read_pandas", - "source": "bigquery-dataframes-temp", - } - for i in range(10): - key = f"bigframes-api-test-{i}" - value = f"test{i}" - cur_labels[key] = value - - df = bpd.DataFrame( - {"col1": [1, 2], "col2": [3, 4]}, session=mocks.create_bigquery_session() + sql = bigframes.session._io.bigquery.create_snapshot_sql( + table_ref, datetime.datetime.now(datetime.timezone.utc) ) - job_config = google.cloud.bigquery.job.QueryJobConfig() - job_config.labels = cur_labels + # Anonymous query results tables don't support time travel. + assert "SYSTEM_TIME" not in sql - df.max() - for _ in range(52): - df.head() + # Need fully-qualified table name. + assert "`my-test-project`.`_e8166e0cdb`.`anonbb92cd`" in sql - io_bq.add_and_trim_labels(job_config=job_config, session=df._session) - assert job_config.labels is not None - assert len(job_config.labels) == 56 - assert "dataframe-max" not in job_config.labels.values() - assert "dataframe-head" in job_config.labels.values() - assert "bigframes-api" in job_config.labels.keys() - assert "source" in job_config.labels.keys() +def test_create_snapshot_sql_doesnt_timetravel_session_tables(): + table_ref = bigquery.TableReference.from_string("my-test-project._session.abcdefg") -@pytest.mark.parametrize( - ("timeout", "api_name"), - [(None, None), (30.0, "test_api")], -) -def test_start_query_with_job_labels_length_limit_met( - mock_bq_client: bigquery.Client, timeout: Optional[float], api_name -): - sql = "select * from abc" - cur_labels = { - "bigframes-api": "read_pandas", - "source": "bigquery-dataframes-temp", - } - for i in range(10): - key = f"bigframes-api-test-{i}" - value = f"test{i}" - cur_labels[key] = value - - df = bpd.DataFrame( - {"col1": [1, 2], "col2": [3, 4]}, session=mocks.create_bigquery_session() + sql = bigframes.session._io.bigquery.create_snapshot_sql( + table_ref, datetime.datetime.now(datetime.timezone.utc) ) - job_config = google.cloud.bigquery.job.QueryJobConfig() - job_config.labels = cur_labels - - df.max() - for _ in range(52): - df.head() + # We aren't modifying _SESSION tables, so don't use time travel. + assert "SYSTEM_TIME" not in sql - io_bq.start_query_with_job( - mock_bq_client, - sql, - job_config=job_config, - location=None, - project=None, - timeout=timeout, - metrics=None, - publisher=bigframes.core.events.Publisher(), - session=df._session, - ) - - assert job_config.labels is not None - assert len(job_config.labels) == 56 - assert "dataframe-max" not in job_config.labels.values() - assert "dataframe-head" in job_config.labels.values() - assert "bigframes-api" in job_config.labels.keys() - assert "source" in job_config.labels.keys() + # Don't need the project ID for _SESSION tables. + assert "my-test-project" not in sql def test_create_temp_table_default_expiration(): """Make sure the created table has an expiration.""" + bqclient = mock.create_autospec(bigquery.Client) + dataset = bigquery.DatasetReference("test-project", "test_dataset") expiration = datetime.datetime( 2023, 11, 2, 13, 44, 55, 678901, datetime.timezone.utc ) - session = mocks.create_bigquery_session() - table_ref = bigquery.TableReference.from_string( - "test-project.test_dataset.bqdf_new_random_table" - ) - bigframes.session._io.bigquery.create_temp_table( - session.bqclient, table_ref, expiration - ) + bigframes.session._io.bigquery.create_temp_table(bqclient, dataset, expiration) - session.bqclient.create_table.assert_called_once() - call_args = session.bqclient.create_table.call_args + bqclient.create_table.assert_called_once() + call_args = bqclient.create_table.call_args table = call_args.args[0] assert table.project == "test-project" assert table.dataset_id == "test_dataset" @@ -306,139 +125,5 @@ def test_create_temp_table_default_expiration(): ), ) def test_bq_schema_to_sql(schema: Iterable[bigquery.SchemaField], expected: str): - sql = io_bq.bq_schema_to_sql(schema) + sql = bigframes.session._io.bigquery.bq_schema_to_sql(schema) assert sql == expected - - -@pytest.mark.parametrize( - ( - "query_or_table", - "columns", - "filters", - "max_results", - "time_travel_timestamp", - "expected_output", - ), - [ - pytest.param( - "test_table", - ["row_index", "string_col"], - [ - (("rowindex", "not in", [0, 6]),), - (("string_col", "in", ["Hello, World!", "こんにちは"]),), - ], - 123, # max_results, - datetime.datetime( - 2024, 5, 14, 12, 42, 36, 125125, tzinfo=datetime.timezone.utc - ), - ( - "SELECT `_bf_source`.`row_index`, `_bf_source`.`string_col` FROM `test_table` AS _bf_source " - "FOR SYSTEM_TIME AS OF CAST('2024-05-14T12:42:36.125125+00:00' AS TIMESTAMP) " - "WHERE `rowindex` NOT IN (0, 6) OR `string_col` IN ('Hello, World!', " - "'こんにちは') LIMIT 123" - ), - id="table-all_params-filter_or_operation", - ), - pytest.param( - ( - """SELECT - rowindex, - string_col, - FROM `test_table` AS t - """ - ), - ["rowindex", "string_col"], - [ - ("rowindex", "<", 4), - ("string_col", "==", "Hello, World!"), - ], - 123, # max_results, - datetime.datetime( - 2024, 5, 14, 12, 42, 36, 125125, tzinfo=datetime.timezone.utc - ), - ( - """SELECT `_bf_source`.`rowindex`, `_bf_source`.`string_col` FROM (SELECT - rowindex, - string_col, - FROM `test_table` AS t - ) AS _bf_source """ - "FOR SYSTEM_TIME AS OF CAST('2024-05-14T12:42:36.125125+00:00' AS TIMESTAMP) " - "WHERE `rowindex` < 4 AND `string_col` = 'Hello, World!' " - "LIMIT 123" - ), - id="subquery-all_params-filter_and_operation", - ), - pytest.param( - "test_table", - ["col_a", "col_b"], - [], - None, # max_results - None, # time_travel_timestampe - "SELECT `_bf_source`.`col_a`, `_bf_source`.`col_b` FROM `test_table` AS _bf_source", - id="table-columns", - ), - pytest.param( - "test_table", - [], - [("date_col", ">", "2022-10-20")], - None, # max_results - None, # time_travel_timestampe - "SELECT * FROM `test_table` AS _bf_source WHERE `date_col` > '2022-10-20'", - id="table-filter", - ), - pytest.param( - "test_table*", - [], - [], - None, # max_results - None, # time_travel_timestampe - "SELECT * FROM `test_table*` AS _bf_source", - id="wildcard-no_params", - ), - pytest.param( - "test_table*", - [], - [("_TABLE_SUFFIX", ">", "2022-10-20")], - None, # max_results - None, # time_travel_timestampe - "SELECT * FROM `test_table*` AS _bf_source WHERE `_TABLE_SUFFIX` > '2022-10-20'", - id="wildcard-filter", - ), - ], -) -def test_to_query( - query_or_table, - columns, - filters, - max_results, - time_travel_timestamp, - expected_output, -): - query = io_bq.to_query( - query_or_table, - columns=columns, - sql_predicate=io_bq.compile_filters(filters), - max_results=max_results, - time_travel_timestamp=time_travel_timestamp, - ) - assert query == expected_output - - -@pytest.mark.parametrize( - ("filters", "expected_message"), - ( - pytest.param( - ["date_col", ">", "2022-10-20"], - "Elements of filters must be tuples of length 3, but got 'd'", - ), - ), -) -def test_to_query_fails_with_bad_filters(filters, expected_message): - with pytest.raises(ValueError, match=re.escape(expected_message)): - io_bq.to_query( - "test_table", - columns=(), - sql_predicate=io_bq.compile_filters(filters), - max_results=None, - time_travel_timestamp=None, - ) diff --git a/tests/unit/session/test_io_pandas.py b/tests/unit/session/test_io_pandas.py index f4141ec8a23..0f6f5dae03b 100644 --- a/tests/unit/session/test_io_pandas.py +++ b/tests/unit/session/test_io_pandas.py @@ -13,8 +13,6 @@ # limitations under the License. import datetime -import re -import unittest.mock as mock from typing import Dict, Union import geopandas # type: ignore @@ -25,30 +23,7 @@ import pyarrow # type: ignore import pytest -import bigframes.core.schema -import bigframes.features -import bigframes.pandas import bigframes.session._io.pandas -from bigframes.testing import mocks - -_LIST_OF_SCALARS = [ - [1, 2, 3], - [], - [4, 5, 6], -] -_LIST_OF_STRUCTS = [ - [ - {"version": 1, "package": "numpy"}, - {"version": 2, "package": "pandas"}, - {"version": 3, "package": "pyarrow"}, - ], - [], - [ - {"version": 4, "package": "awkward-pandas"}, - {"version": 5, "package": "cyberpandas"}, - {"version": 6, "package": "geopandas"}, - ], -] @pytest.mark.parametrize( @@ -209,111 +184,6 @@ ), id="arrow-dtypes", ), - pytest.param( - pyarrow.Table.from_pydict( - { - "listofscalars": pyarrow.array( - _LIST_OF_SCALARS, - type=pyarrow.list_(pyarrow.int64()), - ), - "listofstructs": pyarrow.array( - _LIST_OF_STRUCTS, - type=pyarrow.list_( - pyarrow.struct( - [ - ("version", pyarrow.int64()), - ("package", pyarrow.string()), - ] - ) - ), - ), - }, - ), - { - "listofscalars": pandas.ArrowDtype(pyarrow.list_(pyarrow.int64())), - "listofstructs": pandas.ArrowDtype( - pyarrow.list_( - pyarrow.struct( - [ - ("version", pyarrow.int64()), - ("package", pyarrow.string()), - ] - ) - ) - ), - }, - pandas.DataFrame( - { - "listofscalars": pandas.Series(_LIST_OF_SCALARS, dtype="object"), - "listofstructs": pandas.Series(_LIST_OF_STRUCTS, dtype="object"), - }, - ), - marks=pytest.mark.skipif( - bigframes.features.PANDAS_VERSIONS.is_arrow_list_dtype_usable, - reason="no need to use object dtype for ARRAY in pandas 2.x", - ), - id="nested-dtypes-pandas-1-x", - ), - pytest.param( - pyarrow.Table.from_pydict( - { - "listofscalars": pyarrow.array( - _LIST_OF_SCALARS, - type=pyarrow.list_(pyarrow.int64()), - ), - "listofstructs": pyarrow.array( - _LIST_OF_STRUCTS, - type=pyarrow.list_( - pyarrow.struct( - [ - ("version", pyarrow.int64()), - ("package", pyarrow.string()), - ] - ) - ), - ), - }, - ), - { - "listofscalars": pandas.ArrowDtype(pyarrow.list_(pyarrow.int64())), - "listofstructs": pandas.ArrowDtype( - pyarrow.list_( - pyarrow.struct( - [ - ("version", pyarrow.int64()), - ("package", pyarrow.string()), - ] - ) - ) - ), - }, - pandas.DataFrame( - { - "listofscalars": pandas.Series( - _LIST_OF_SCALARS, - dtype=pandas.ArrowDtype(pyarrow.list_(pyarrow.int64())), - ), - "listofstructs": pandas.Series( - _LIST_OF_STRUCTS, - dtype=pandas.ArrowDtype( - pyarrow.list_( - pyarrow.struct( - [ - ("version", pyarrow.int64()), - ("package", pyarrow.string()), - ] - ) - ) - ), - ), - }, - ), - marks=pytest.mark.skipif( - not bigframes.features.PANDAS_VERSIONS.is_arrow_list_dtype_usable, - reason="Arrow list type broken in pandas 1.x", - ), - id="nested-dtypes-pandas-2-x", - ), pytest.param( pyarrow.Table.from_pydict( { @@ -445,13 +315,7 @@ def test_arrow_to_pandas( dtypes: Dict, expected: pandas.DataFrame, ): - schema = bigframes.core.schema.ArraySchema( - tuple( - bigframes.core.schema.SchemaItem(name, dtype) - for name, dtype in dtypes.items() - ) - ) - actual = bigframes.session._io.pandas.arrow_to_pandas(arrow_table, schema) + actual = bigframes.session._io.pandas.arrow_to_pandas(arrow_table, dtypes) pandas.testing.assert_series_equal(actual.dtypes, expected.dtypes) # assert_frame_equal is converting to numpy internally, which causes some @@ -484,21 +348,5 @@ def test_arrow_to_pandas( def test_arrow_to_pandas_wrong_size_dtypes( arrow_table: Union[pyarrow.Table, pyarrow.RecordBatch], dtypes: Dict ): - schema = bigframes.core.schema.ArraySchema( - tuple( - bigframes.core.schema.SchemaItem(name, dtype) - for name, dtype in dtypes.items() - ) - ) - with pytest.raises(ValueError, match=f"Number of types {len(schema)}"): - bigframes.session._io.pandas.arrow_to_pandas(arrow_table, schema) - - -def test_read_pandas_with_bigframes_dataframe(): - session = mocks.create_bigquery_session() - df = mock.create_autospec(bigframes.pandas.DataFrame, instance=True) - - with pytest.raises( - ValueError, match=re.escape("read_pandas() expects a pandas.DataFrame") - ): - session.read_pandas(df) + with pytest.raises(ValueError, match=f"Number of types {len(dtypes)}"): + bigframes.session._io.pandas.arrow_to_pandas(arrow_table, dtypes) diff --git a/tests/unit/session/test_local_scan_executor.py b/tests/unit/session/test_local_scan_executor.py deleted file mode 100644 index 66dcdf590ce..00000000000 --- a/tests/unit/session/test_local_scan_executor.py +++ /dev/null @@ -1,107 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -from __future__ import annotations - -import asyncio - -import pyarrow -import pytest - -from bigframes.core import identifiers, local_data, nodes -from bigframes.session import execution_spec, local_scan_executor -from bigframes.testing import mocks - -SPEC = execution_spec.ExecutionSpec( - ordered=True, -) - - -@pytest.fixture -def object_under_test(): - return local_scan_executor.LocalScanExecutor() - - -def create_read_local_node(arrow_table: pyarrow.Table): - session = mocks.create_bigquery_session() - local_data_source = local_data.ManagedArrowTable.from_pyarrow(arrow_table) - return nodes.ReadLocalNode( - local_data_source=local_data_source, - session=session, - scan_list=nodes.ScanList( - items=tuple( - nodes.ScanItem( - id=identifiers.ColumnId(column_name), - source_id=column_name, - ) - for column_name in arrow_table.column_names - ), - ), - ) - - -@pytest.mark.parametrize( - ("start", "stop", "expected_rows"), - ( - # No-op slices. - (None, None, 10), - (0, None, 10), - (None, 10, 10), - # Slices equivalent to limits. - (None, 7, 7), - (0, 3, 3), - ), -) -def test_local_scan_executor_with_slice(start, stop, expected_rows, object_under_test): - pyarrow_table = pyarrow.Table.from_pydict( - { - "rowindex": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], - "letters": ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j"], - } - ) - assert pyarrow_table.num_rows == 10 - - local_node = create_read_local_node(pyarrow_table) - plan = nodes.SliceNode( - child=local_node, - start=start, - stop=stop, - ) - - result = asyncio.run(object_under_test.execute(plan, SPEC)) - result_table = pyarrow.Table.from_batches(result.batches().arrow_batches) - assert result_table.num_rows == expected_rows - - -@pytest.mark.parametrize( - ("start", "stop", "step"), - ( - (-1, None, 1), - (None, -1, 1), - (None, None, 2), - (None, None, -1), - (4, None, 6), - (1, 9, 8), - ), -) -def test_local_scan_executor_with_slice_unsupported_inputs( - start, stop, step, object_under_test -): - local_node = create_read_local_node(pyarrow.Table.from_pydict({"col": [1, 2, 3]})) - plan = nodes.SliceNode( - child=local_node, - start=start, - stop=stop, - step=step, - ) - assert asyncio.run(object_under_test.execute(plan, SPEC)) is None diff --git a/tests/unit/session/test_metrics.py b/tests/unit/session/test_metrics.py deleted file mode 100644 index 4e550b1c77a..00000000000 --- a/tests/unit/session/test_metrics.py +++ /dev/null @@ -1,302 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import datetime -import os -import unittest.mock - -import google.cloud.bigquery as bigquery -import pytest - -import bigframes.session.metrics as metrics - -NOW = datetime.datetime.now(datetime.timezone.utc) - - -def test_count_job_stats_with_row_iterator(): - row_iterator = unittest.mock.create_autospec( - bigquery.table.RowIterator, instance=True - ) - row_iterator.total_bytes_processed = 1024 - row_iterator.query = "SELECT * FROM table" - row_iterator.slot_millis = 1234 - execution_metrics = metrics.ExecutionMetrics() - execution_metrics.count_job_stats(row_iterator=row_iterator) - - assert execution_metrics.execution_count == 1 - assert execution_metrics.bytes_processed == 1024 - assert execution_metrics.query_char_count == 19 - assert execution_metrics.slot_millis == 1234 - - -def test_count_job_stats_with_row_iterator_missing_stats(): - row_iterator = unittest.mock.create_autospec( - bigquery.table.RowIterator, instance=True - ) - # Simulate properties not being present on the object - del row_iterator.total_bytes_processed - del row_iterator.query - del row_iterator.slot_millis - execution_metrics = metrics.ExecutionMetrics() - execution_metrics.count_job_stats(row_iterator=row_iterator) - - assert execution_metrics.execution_count == 1 - assert execution_metrics.bytes_processed == 0 - assert execution_metrics.query_char_count == 0 - assert execution_metrics.slot_millis == 0 - - -def test_count_job_stats_with_row_iterator_none_stats(): - row_iterator = unittest.mock.create_autospec( - bigquery.table.RowIterator, instance=True - ) - row_iterator.total_bytes_processed = None - row_iterator.query = None - row_iterator.slot_millis = None - execution_metrics = metrics.ExecutionMetrics() - execution_metrics.count_job_stats(row_iterator=row_iterator) - - assert execution_metrics.execution_count == 1 - assert execution_metrics.bytes_processed == 0 - assert execution_metrics.query_char_count == 0 - assert execution_metrics.slot_millis == 0 - - -def test_count_job_stats_with_dry_run(): - query_job = unittest.mock.create_autospec(bigquery.QueryJob, instance=True) - query_job.configuration.dry_run = True - query_job.query = "SELECT * FROM table" - execution_metrics = metrics.ExecutionMetrics() - execution_metrics.count_job_stats(query_job=query_job) - - # Dry run jobs shouldn't count as "executed" - assert execution_metrics.execution_count == 0 - assert execution_metrics.bytes_processed == 0 - assert execution_metrics.query_char_count == 0 - assert execution_metrics.slot_millis == 0 - - -def test_count_job_stats_with_valid_job(): - query_job = unittest.mock.create_autospec(bigquery.QueryJob, instance=True) - query_job.configuration.dry_run = False - query_job.query = "SELECT * FROM table" - query_job.total_bytes_processed = 2048 - query_job.slot_millis = 5678 - query_job.created = NOW - query_job.ended = NOW + datetime.timedelta(seconds=2) - execution_metrics = metrics.ExecutionMetrics() - execution_metrics.count_job_stats(query_job=query_job) - - assert execution_metrics.execution_count == 1 - assert execution_metrics.bytes_processed == 2048 - assert execution_metrics.query_char_count == 19 - assert execution_metrics.slot_millis == 5678 - assert execution_metrics.execution_secs == pytest.approx(2.0) - - -def test_count_job_stats_with_cached_job(): - query_job = unittest.mock.create_autospec(bigquery.QueryJob, instance=True) - query_job.configuration.dry_run = False - query_job.query = "SELECT * FROM table" - # Cache hit jobs don't have total_bytes_processed or slot_millis - query_job.total_bytes_processed = None - query_job.slot_millis = None - query_job.created = NOW - query_job.ended = NOW + datetime.timedelta(seconds=1) - execution_metrics = metrics.ExecutionMetrics() - execution_metrics.count_job_stats(query_job=query_job) - - assert execution_metrics.execution_count == 1 - assert execution_metrics.bytes_processed == 0 - assert execution_metrics.query_char_count == 19 - assert execution_metrics.slot_millis == 0 - assert execution_metrics.execution_secs == pytest.approx(1.0) - - -def test_count_job_stats_with_unsupported_job(): - query_job = unittest.mock.create_autospec(bigquery.QueryJob, instance=True) - query_job.configuration.dry_run = False - query_job.query = "SELECT * FROM table" - # Some jobs, such as scripts, don't have these properties. - query_job.total_bytes_processed = None - query_job.slot_millis = None - query_job.created = None - query_job.ended = None - execution_metrics = metrics.ExecutionMetrics() - execution_metrics.count_job_stats(query_job=query_job) - - # Don't count jobs if we can't get performance stats. - assert execution_metrics.execution_count == 0 - assert execution_metrics.bytes_processed == 0 - assert execution_metrics.query_char_count == 0 - assert execution_metrics.slot_millis == 0 - assert execution_metrics.execution_secs == pytest.approx(0.0) - - -def test_get_performance_stats_with_valid_job(): - query_job = unittest.mock.create_autospec(bigquery.QueryJob, instance=True) - query_job.configuration.dry_run = False - query_job.query = "SELECT * FROM table" - query_job.total_bytes_processed = 2048 - query_job.slot_millis = 5678 - query_job.created = NOW - query_job.ended = NOW + datetime.timedelta(seconds=2) - stats = metrics.get_performance_stats(query_job) - assert stats is not None - query_char_count, bytes_processed, slot_millis, exec_seconds = stats - assert query_char_count == 19 - assert bytes_processed == 2048 - assert slot_millis == 5678 - assert exec_seconds == pytest.approx(2.0) - - -def test_get_performance_stats_with_dry_run(): - query_job = unittest.mock.create_autospec(bigquery.QueryJob, instance=True) - query_job.configuration.dry_run = True - stats = metrics.get_performance_stats(query_job) - assert stats is None - - -def test_get_performance_stats_with_missing_timestamps(): - query_job = unittest.mock.create_autospec(bigquery.QueryJob, instance=True) - query_job.configuration.dry_run = False - query_job.created = None - query_job.ended = NOW - stats = metrics.get_performance_stats(query_job) - assert stats is None - - query_job.created = NOW - query_job.ended = None - stats = metrics.get_performance_stats(query_job) - assert stats is None - - -def test_get_performance_stats_with_mocked_types(): - query_job = unittest.mock.create_autospec(bigquery.QueryJob, instance=True) - query_job.configuration.dry_run = False - query_job.created = NOW - query_job.ended = NOW - query_job.total_bytes_processed = unittest.mock.Mock() - query_job.slot_millis = 123 - stats = metrics.get_performance_stats(query_job) - assert stats is None - - query_job.total_bytes_processed = 123 - query_job.slot_millis = unittest.mock.Mock() - stats = metrics.get_performance_stats(query_job) - assert stats is None - - -@pytest.fixture -def mock_environ(monkeypatch): - """Fixture to mock os.environ.""" - monkeypatch.setenv(metrics.LOGGING_NAME_ENV_VAR, "my_test_case") - - -def test_write_stats_to_disk_writes_files(tmp_path, mock_environ): - os.chdir(tmp_path) - test_name = os.environ[metrics.LOGGING_NAME_ENV_VAR] - metrics.write_stats_to_disk( - query_char_count=100, - bytes_processed=200, - slot_millis=300, - exec_seconds=1.23, - ) - - slot_file = tmp_path / (test_name + ".slotmillis") - assert slot_file.exists() - with open(slot_file) as f: - assert f.read() == "300\n" - - exec_time_file = tmp_path / (test_name + ".bq_exec_time_seconds") - assert exec_time_file.exists() - with open(exec_time_file) as f: - assert f.read() == "1.23\n" - - query_char_count_file = tmp_path / (test_name + ".query_char_count") - assert query_char_count_file.exists() - with open(query_char_count_file) as f: - assert f.read() == "100\n" - - bytes_file = tmp_path / (test_name + ".bytesprocessed") - assert bytes_file.exists() - with open(bytes_file) as f: - assert f.read() == "200\n" - - -def test_write_stats_to_disk_no_env_var(tmp_path, monkeypatch): - monkeypatch.delenv(metrics.LOGGING_NAME_ENV_VAR, raising=False) - os.chdir(tmp_path) - metrics.write_stats_to_disk( - query_char_count=100, - bytes_processed=200, - slot_millis=300, - exec_seconds=1.23, - ) - assert len(list(tmp_path.iterdir())) == 0 - - -def test_on_event_with_local_execute_result(): - import bigframes.core.events - from bigframes.session.executor import LocalExecuteResult - - # fmt: off - local_result = unittest.mock.create_autospec( - LocalExecuteResult, instance=True - ) - # fmt: on - local_result.total_bytes_processed = 1024 - - event = bigframes.core.events.ExecutionFinished(result=local_result) - envelope = bigframes.core.events.EventEnvelope(event) - execution_metrics = metrics.ExecutionMetrics() - execution_metrics.on_event(envelope) - - assert execution_metrics.execution_count == 1 - assert len(execution_metrics.jobs) == 1 - assert execution_metrics.jobs[0].job_type == "polars" - assert execution_metrics.jobs[0].status == "DONE" - assert execution_metrics.jobs[0].total_bytes_processed == 1024 - - -def test_count_job_stats_with_explicit_cell_execution_count(): - row_iterator = unittest.mock.create_autospec( - bigquery.table.RowIterator, instance=True - ) - row_iterator.total_bytes_processed = 1024 - row_iterator.query = "SELECT * FROM table" - row_iterator.slot_millis = 1234 - execution_metrics = metrics.ExecutionMetrics() - execution_metrics.count_job_stats( - row_iterator=row_iterator, cell_execution_count=42 - ) - - assert len(execution_metrics.jobs) == 1 - assert execution_metrics.jobs[0].cell_execution_count == 42 - - -def test_on_event_with_explicit_cell_execution_count(): - import bigframes.core.events - from bigframes.session.executor import LocalExecuteResult - - local_result = unittest.mock.create_autospec(LocalExecuteResult, instance=True) - local_result.total_bytes_processed = 1024 - - event = bigframes.core.events.ExecutionFinished(result=local_result) - envelope = bigframes.core.events.EventEnvelope(event=event, cell_execution_count=42) - execution_metrics = metrics.ExecutionMetrics() - execution_metrics.on_event(envelope) - - assert len(execution_metrics.jobs) == 1 - assert execution_metrics.jobs[0].cell_execution_count == 42 diff --git a/tests/unit/session/test_proxy_executor.py b/tests/unit/session/test_proxy_executor.py deleted file mode 100644 index c20fd57236b..00000000000 --- a/tests/unit/session/test_proxy_executor.py +++ /dev/null @@ -1,198 +0,0 @@ -# Copyright 2026 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 unittest import mock - -import google.cloud.bigquery as bigquery -import google.cloud.exceptions -import pytest - -import bigframes -from bigframes.session.proxy_executor import DualCompilerProxyExecutor - - -@pytest.fixture -def mock_executor(): - bqclient = mock.create_autospec(bigquery.Client) - bqclient.project = "test-project" - storage_manager = mock.Mock() - bqstoragereadclient = mock.Mock() - loader = mock.Mock() - publisher = mock.Mock() - function_manager = mock.Mock() - return DualCompilerProxyExecutor( - bqclient, - storage_manager, - bqstoragereadclient, - loader, - publisher=publisher, - function_manager=function_manager, - ) - - -def test_execute_legacy_routes_to_ibis(mock_executor, monkeypatch): - array_value = mock.Mock(spec=bigframes.core.ArrayValue) - execution_spec = mock.Mock(spec=bigframes.session.execution_spec.ExecutionSpec) - execution_spec.with_bq_labels.return_value = execution_spec - - mock_executor._ibis_executor = mock.Mock() - mock_executor._sqlglot_executor = mock.Mock() - - monkeypatch.setattr(bigframes.options.experiments, "sql_compiler", "legacy") - mock_executor.execute(array_value, execution_spec) - - execution_spec.with_bq_labels.assert_called_once_with( - {"bigframes-compiler": "ibis"} - ) - mock_executor._ibis_executor.execute.assert_called_once_with( - array_value, execution_spec - ) - mock_executor._sqlglot_executor.execute.assert_not_called() - - -def test_execute_experimental_routes_to_sqlglot(mock_executor, monkeypatch): - array_value = mock.Mock(spec=bigframes.core.ArrayValue) - execution_spec = mock.Mock(spec=bigframes.session.execution_spec.ExecutionSpec) - execution_spec.with_bq_labels.return_value = execution_spec - - mock_executor._ibis_executor = mock.Mock() - mock_executor._sqlglot_executor = mock.Mock() - - monkeypatch.setattr(bigframes.options.experiments, "sql_compiler", "experimental") - mock_executor.execute(array_value, execution_spec) - - execution_spec.with_bq_labels.assert_called_once_with( - {"bigframes-compiler": "sqlglot"} - ) - mock_executor._sqlglot_executor.execute.assert_called_once_with( - array_value, execution_spec - ) - mock_executor._ibis_executor.execute.assert_not_called() - - -def test_execute_stable_routes_to_sqlglot_success(mock_executor, monkeypatch): - array_value = mock.Mock(spec=bigframes.core.ArrayValue) - execution_spec = mock.Mock(spec=bigframes.session.execution_spec.ExecutionSpec) - execution_spec.with_bq_labels.return_value = execution_spec - - mock_executor._ibis_executor = mock.Mock() - mock_executor._sqlglot_executor = mock.Mock() - - monkeypatch.setattr(bigframes.options.experiments, "sql_compiler", "stable") - with mock.patch("uuid.uuid1") as mock_uuid: - mock_uuid.return_value.hex = "1234567890123456" - mock_executor.execute(array_value, execution_spec) - - execution_spec.with_bq_labels.assert_called_once_with( - {"bigframes-compiler": "sqlglot-123456789012"} - ) - mock_executor._sqlglot_executor.execute.assert_called_once_with( - array_value, execution_spec - ) - mock_executor._ibis_executor.execute.assert_not_called() - - -def test_execute_stable_routes_to_sqlglot_fallback_to_ibis(mock_executor, monkeypatch): - array_value = mock.Mock(spec=bigframes.core.ArrayValue) - execution_spec = mock.Mock(spec=bigframes.session.execution_spec.ExecutionSpec) - - spec_sqlglot = mock.Mock(spec=bigframes.session.execution_spec.ExecutionSpec) - spec_ibis = mock.Mock(spec=bigframes.session.execution_spec.ExecutionSpec) - execution_spec.with_bq_labels.side_effect = [spec_sqlglot, spec_ibis] - - mock_executor._ibis_executor = mock.Mock() - mock_executor._sqlglot_executor = mock.Mock() - - mock_executor._sqlglot_executor.execute.side_effect = ( - google.cloud.exceptions.BadRequest("test error") - ) - - monkeypatch.setattr(bigframes.options.experiments, "sql_compiler", "stable") - with mock.patch("uuid.uuid1") as mock_uuid: - mock_uuid.return_value.hex = "1234567890123456" - with pytest.warns( - UserWarning, match="Compiler ID 123456789012: Exception on sqlglot" - ): - mock_executor.execute(array_value, execution_spec) - - execution_spec.with_bq_labels.assert_has_calls( - [ - mock.call({"bigframes-compiler": "sqlglot-123456789012"}), - mock.call({"bigframes-compiler": "ibis-123456789012"}), - ] - ) - - mock_executor._sqlglot_executor.execute.assert_called_once_with( - array_value, spec_sqlglot - ) - mock_executor._ibis_executor.execute.assert_called_once_with(array_value, spec_ibis) - - -def test_cached_legacy_routes_to_ibis(mock_executor, monkeypatch): - array_value = mock.Mock(spec=bigframes.core.ArrayValue) - config = mock.Mock() - - mock_executor._ibis_executor = mock.Mock() - mock_executor._sqlglot_executor = mock.Mock() - - monkeypatch.setattr(bigframes.options.experiments, "sql_compiler", "legacy") - mock_executor.cached(array_value, config=config) - - mock_executor._ibis_executor.cached.assert_called_once_with( - array_value, config=config - ) - mock_executor._sqlglot_executor.cached.assert_not_called() - - -def test_cached_experimental_routes_to_sqlglot(mock_executor, monkeypatch): - array_value = mock.Mock(spec=bigframes.core.ArrayValue) - config = mock.Mock() - - mock_executor._ibis_executor = mock.Mock() - mock_executor._sqlglot_executor = mock.Mock() - - monkeypatch.setattr(bigframes.options.experiments, "sql_compiler", "experimental") - mock_executor.cached(array_value, config=config) - - mock_executor._sqlglot_executor.cached.assert_called_once_with( - array_value, config=config - ) - mock_executor._ibis_executor.cached.assert_not_called() - - -def test_cached_stable_routes_to_sqlglot_fallback_to_ibis(mock_executor, monkeypatch): - array_value = mock.Mock(spec=bigframes.core.ArrayValue) - config = mock.Mock() - - mock_executor._ibis_executor = mock.Mock() - mock_executor._sqlglot_executor = mock.Mock() - - mock_executor._sqlglot_executor.cached.side_effect = ( - google.cloud.exceptions.BadRequest("test error") - ) - - monkeypatch.setattr(bigframes.options.experiments, "sql_compiler", "stable") - with mock.patch("uuid.uuid1") as mock_uuid: - mock_uuid.return_value.hex = "1234567890123456" - with pytest.warns( - UserWarning, match="Compiler ID 123456789012: Exception on sqlglot" - ): - mock_executor.cached(array_value, config=config) - - mock_executor._sqlglot_executor.cached.assert_called_once_with( - array_value, config=config - ) - mock_executor._ibis_executor.cached.assert_called_once_with( - array_value, config=config - ) diff --git a/tests/unit/session/test_read_gbq_colab.py b/tests/unit/session/test_read_gbq_colab.py deleted file mode 100644 index fc4181b6a2b..00000000000 --- a/tests/unit/session/test_read_gbq_colab.py +++ /dev/null @@ -1,246 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -"""Unit tests for read_gbq_colab helper functions.""" - -import itertools -import textwrap -from unittest import mock - -import numpy -import pandas -import pytest -from google.cloud import bigquery - -from bigframes.testing import mocks - - -def test_read_gbq_colab_includes_label(): - """Make sure we can tell direct colab usage apart from regular read_gbq usage.""" - bqclient = mock.create_autospec(bigquery.Client, instance=True) - bqclient.project = "proj" - session = mocks.create_bigquery_session(bqclient=bqclient) - _ = session._read_gbq_colab("SELECT 'read-gbq-colab-test'") - - label_values = [] - for kall in itertools.chain( - bqclient.query_and_wait.call_args_list, - bqclient._query_and_wait_bigframes.call_args_list, - bqclient.query.call_args_list, - ): - job_config = kall.kwargs.get("job_config") - if job_config is None: - continue - label_values.extend(job_config.labels.values()) - - assert "session-read_gbq_colab" in label_values - - -def test_read_gbq_colab_includes_label_in_anywidget_mode(): - """Make sure read_gbq_colab label is preserved in recent-bigframes-api labels in anywidget mode.""" - pytest.importorskip("anywidget") - pytest.importorskip("traitlets") - - import bigframes - import bigframes.display.html as bf_html - - bqclient = mock.create_autospec(bigquery.Client, instance=True) - bqclient.project = "proj" - session = mocks.create_bigquery_session(bqclient=bqclient) - df = session._read_gbq_colab("SELECT 'read-gbq-colab-test'") - - with bigframes.option_context("display.render_mode", "anywidget"): - _ = bf_html.get_anywidget_bundle(df) - - label_values = [] - for kall in itertools.chain( - bqclient.query_and_wait.call_args_list, - bqclient._query_and_wait_bigframes.call_args_list, - bqclient.query.call_args_list, - ): - job_config = kall.kwargs.get("job_config") - if job_config is None: - continue - label_values.extend(job_config.labels.values()) - - assert "session-read_gbq_colab" in label_values - - -@pytest.mark.parametrize("dry_run", [True, False]) -def test_read_gbq_colab_includes_formatted_values_in_dry_run(monkeypatch, dry_run): - bqclient = mock.create_autospec(bigquery.Client, instance=True) - bqclient.project = "proj" - session = mocks.create_bigquery_session(bqclient=bqclient) - bf_df = mocks.create_dataframe(monkeypatch, session=session) - session._create_temp_table = mock.Mock( # type: ignore - return_value=bigquery.TableReference.from_string("proj.dset.temp_table") - ) - session._create_temp_view = mock.Mock( # type: ignore - return_value=bigquery.TableReference.from_string("proj.dset.temp_view") - ) - - # To avoid trouble with get_table() calls getting out of sync with mock - # "uploaded" data, make sure this is small enough to inline in the SQL as a - # view. - pd_df = pandas.DataFrame({"rowindex": numpy.arange(3), "value": numpy.arange(3)}) - - pyformat_args = { - "some_integer": 123, - "some_string": "some_column", - "bf_df": bf_df, - "pd_df": pd_df, - # This is not a supported type, but ignored if not referenced. - "some_object": object(), - } - - _ = session._read_gbq_colab( - textwrap.dedent( - """ - SELECT {some_integer} as some_integer, - {some_string} as some_string, - '{{escaped}}' as escaped - FROM {bf_df} AS bf_df - FULL OUTER JOIN {pd_df} AS pd_df - ON bf_df.rowindex = pd_df.rowindex - """ - ), - pyformat_args=pyformat_args, - dry_run=dry_run, - ) - expected = textwrap.dedent( - f""" - SELECT 123 as some_integer, - some_column as some_string, - '{{escaped}}' as escaped - FROM `proj`.`dset`.`temp_{"table" if dry_run else "view"}` AS bf_df - FULL OUTER JOIN `proj`.`dset`.`temp_{"table" if dry_run else "view"}` AS pd_df - ON bf_df.rowindex = pd_df.rowindex - """ - ) - - # This should be the most recent query. - query = session._queries[-1] # type: ignore - config = session._job_configs[-1] # type: ignore - - if dry_run: - assert config.dry_run - else: - # Allow for any "False-y" value. - assert not config.dry_run - - assert query.strip() == expected.strip() - - -def test_read_gbq_colab_doesnt_set_destination_table(): - """For best performance, we don't try to workaround the 10 GB query results limitation.""" - session = mocks.create_bigquery_session() - - _ = session._read_gbq_colab("SELECT 'my-test-query';") - queries = session._queries # type: ignore - configs = session._job_configs # type: ignore - - for query, config in zip(queries, configs): - if query == "SELECT 'my-test-query';" and not config.dry_run: - break - - assert query == "SELECT 'my-test-query';" - assert config.destination is None - - -def test_read_gbq_colab_with_callback(): - """Make sure callback receives events during execution.""" - session = mocks.create_bigquery_session() - callback = mock.Mock() - - _ = session._read_gbq_colab("SELECT 'my-test-query';", callback=callback) - - assert callback.call_count > 0 - - -def test_read_gbq_colab_filters_by_cell(): - """Verify that callbacks are scoped to individual executions.""" - session = mocks.create_bigquery_session() - callback1 = mock.Mock() - callback2 = mock.Mock() - - _ = session._read_gbq_colab("SELECT 'cell_1_query';", callback=callback1) - callback1_initial_count = callback1.call_count - - _ = session._read_gbq_colab("SELECT 'cell_2_query';", callback=callback2) - - # Verify callback1 was automatically unsubscribed upon completion - # of the first query. - assert callback1.call_count == callback1_initial_count - assert callback2.call_count > 0 - - -def test_execution_history_filtering(): - """Verify that execution_history can be filtered by job_ids or events.""" - from bigframes.session import metrics - - session = mocks.create_bigquery_session() - - job1 = metrics.JobMetadata(job_id="job_1", job_type="query", query="SELECT 1") - job2 = metrics.JobMetadata(job_id="job_2", job_type="query", query="SELECT 2") - session._metrics.jobs.extend([job1, job2]) - - history_job1 = session.execution_history(job_ids=["job_1"]).to_dataframe() - assert len(history_job1) == 1 - assert history_job1.iloc[0]["job_id"] == "job_1" - - event2 = mock.Mock() - event2.job_id = "job_2" - history_job2 = session.execution_history(events=[event2]).to_dataframe() - assert len(history_job2) == 1 - assert history_job2.iloc[0]["job_id"] == "job_2" - - -def test_execution_history_returns_all_executions_by_default(): - """Verify that execution_history returns all executions by default.""" - from bigframes.session import metrics - - session = mocks.create_bigquery_session() - job1 = metrics.JobMetadata( - job_id="job_1", job_type="query", query="SELECT 1", cell_execution_count=10 - ) - job2 = metrics.JobMetadata( - job_id="job_2", job_type="query", query="SELECT 2", cell_execution_count=20 - ) - session._metrics.jobs.extend([job1, job2]) - - history = session.execution_history().to_dataframe() - - assert len(history) == 2 - - -def test_execution_history_filters_by_notebook_cell_when_all_cells_is_false(): - """Verify that execution_history filters to the current cell when all_cells is False.""" - from bigframes.session import metrics - - session = mocks.create_bigquery_session() - job1 = metrics.JobMetadata( - job_id="job_1", job_type="query", query="SELECT 1", cell_execution_count=10 - ) - job2 = metrics.JobMetadata( - job_id="job_2", job_type="query", query="SELECT 2", cell_execution_count=20 - ) - session._metrics.jobs.extend([job1, job2]) - - with mock.patch( - "bigframes.core.utils.get_ipython_execution_count", return_value=20 - ): - history = session.execution_history(all_cells=False).to_dataframe() - - assert len(history) == 1 - assert history.iloc[0]["job_id"] == "job_2" diff --git a/tests/unit/session/test_read_gbq_query.py b/tests/unit/session/test_read_gbq_query.py deleted file mode 100644 index d078c64af72..00000000000 --- a/tests/unit/session/test_read_gbq_query.py +++ /dev/null @@ -1,38 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -"""Unit tests for read_gbq_query functions.""" - -from bigframes.testing import mocks - - -def test_read_gbq_query_sets_destination_table(): - """Workaround the 10 GB query results limitation by setting a destination table. - - See internal issue b/303057336. - """ - # Use partial ordering mode to skip column uniqueness checks. - session = mocks.create_bigquery_session(ordering_mode="partial") - - _ = session.read_gbq_query("SELECT 'my-test-query';", allow_large_results=True) - queries = session._queries # type: ignore - configs = session._job_configs # type: ignore - - for query, config in zip(queries, configs): - if query == "SELECT 'my-test-query';" and not config.dry_run: - break - - assert query == "SELECT 'my-test-query';" - assert config.destination is not None - session.close() diff --git a/tests/unit/session/test_read_gbq_table.py b/tests/unit/session/test_read_gbq_table.py deleted file mode 100644 index 97ac0efb753..00000000000 --- a/tests/unit/session/test_read_gbq_table.py +++ /dev/null @@ -1,191 +0,0 @@ -# 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. -# 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. - -"""Unit tests for read_gbq_table helper functions.""" - -import unittest.mock as mock -import warnings - -import google.cloud.bigquery -import pytest - -import bigframes.enums -import bigframes.exceptions -import bigframes.session._io.bigquery.read_gbq_table as bf_read_gbq_table -from bigframes.core import bq_data -from bigframes.testing import mocks - - -@pytest.mark.parametrize( - ("index_cols", "primary_keys", "expected"), - ( - (["col1", "col2"], ["col1", "col2", "col3"], ("col1", "col2", "col3")), - ( - ["col1", "col2", "col3"], - ["col1", "col2", "col3"], - ("col1", "col2", "col3"), - ), - ( - ["col2", "col3", "col1"], - [ - "col3", - "col2", - ], - ("col2", "col3"), - ), - (["col1", "col2"], [], ()), - ([], ["col1", "col2", "col3"], ("col1", "col2", "col3")), - ([], [], ()), - ), -) -def test_infer_unique_columns(index_cols, primary_keys, expected): - """If a primary key is set on the table, we use that as the index column - by default, no error should be raised in this case. - - See internal issue 335727141. - """ - table = google.cloud.bigquery.Table.from_api_repr( - { - "tableReference": { - "projectId": "my-project", - "datasetId": "my_dataset", - "tableId": "my_table", - }, - "clustering": { - "fields": ["col1", "col2"], - }, - }, - ) - table.schema = ( - google.cloud.bigquery.SchemaField("col1", "INT64"), - google.cloud.bigquery.SchemaField("col2", "INT64"), - google.cloud.bigquery.SchemaField("col3", "INT64"), - google.cloud.bigquery.SchemaField("col4", "INT64"), - ) - - # TODO(b/305264153): use setter for table_constraints in client library - # when available. - table._properties["tableConstraints"] = { - "primaryKey": { - "columns": primary_keys, - }, - } - - result = bf_read_gbq_table.infer_unique_columns( - bq_data.GbqNativeTable.from_table(table), index_cols - ) - - assert result == expected - - -@pytest.mark.parametrize( - ("index_cols", "values_distinct", "expected"), - ( - ( - ["col1", "col2", "col3"], - True, - ("col1", "col2", "col3"), - ), - ( - ["col2", "col3", "col1"], - True, - ("col2", "col3", "col1"), - ), - (["col1", "col2"], False, ()), - ([], False, ()), - ), -) -def test_check_if_index_columns_are_unique(index_cols, values_distinct, expected): - table = google.cloud.bigquery.Table.from_api_repr( - { - "tableReference": { - "projectId": "my-project", - "datasetId": "my_dataset", - "tableId": "my_table", - }, - "clustering": { - "fields": ["col1", "col2"], - }, - }, - ) - table.schema = ( - google.cloud.bigquery.SchemaField("col1", "INT64"), - google.cloud.bigquery.SchemaField("col2", "INT64"), - google.cloud.bigquery.SchemaField("col3", "INT64"), - google.cloud.bigquery.SchemaField("col4", "INT64"), - ) - - bqclient = mock.create_autospec(google.cloud.bigquery.Client, instance=True) - bqclient.project = "test-project" - session = mocks.create_bigquery_session( - bqclient=bqclient, table_schema=table.schema - ) - - # Mock bqclient _after_ creating session to override its mocks. - bqclient.get_table.return_value = table - bqclient._query_and_wait_bigframes.side_effect = None - bqclient._query_and_wait_bigframes.return_value = ( - {"total_count": 3, "distinct_count": 3 if values_distinct else 2}, - ) - - table._properties["location"] = session._location - - result = bf_read_gbq_table.check_if_index_columns_are_unique( - bqclient=bqclient, - table=bq_data.GbqNativeTable.from_table(table), - index_cols=index_cols, - publisher=session._publisher, - ) - - assert result == expected - - -def test_get_index_cols_warns_if_clustered_but_sequential_index(): - table = google.cloud.bigquery.Table.from_api_repr( - { - "tableReference": { - "projectId": "my-project", - "datasetId": "my_dataset", - "tableId": "my_table", - }, - "clustering": { - "fields": ["col1", "col2"], - }, - }, - ) - table.schema = ( - google.cloud.bigquery.SchemaField("col1", "INT64"), - google.cloud.bigquery.SchemaField("col2", "INT64"), - google.cloud.bigquery.SchemaField("col3", "INT64"), - google.cloud.bigquery.SchemaField("col4", "INT64"), - ) - - with pytest.warns(bigframes.exceptions.DefaultIndexWarning, match="is clustered"): - bf_read_gbq_table.get_index_cols( - bq_data.GbqNativeTable.from_table(table), - index_col=(), - default_index_type=bigframes.enums.DefaultIndexKind.SEQUENTIAL_INT64, - ) - - # Ensure that we don't raise if using a NULL index by default, such as in - # partial ordering mode. See: internal issue b/356872356. - with warnings.catch_warnings(): - warnings.simplefilter( - "error", category=bigframes.exceptions.DefaultIndexWarning - ) - bf_read_gbq_table.get_index_cols( - bq_data.GbqNativeTable.from_table(table), - index_col=(), - default_index_type=bigframes.enums.DefaultIndexKind.NULL, - ) diff --git a/tests/unit/session/test_session.py b/tests/unit/session/test_session.py index a6c8446967e..18fd42e0f31 100644 --- a/tests/unit/session/test_session.py +++ b/tests/unit/session/test_session.py @@ -12,510 +12,41 @@ # See the License for the specific language governing permissions and # limitations under the License. -import copy -import datetime import os -import re -import warnings from unittest import mock import google.api_core.exceptions -import google.cloud.bigquery -import pandas as pd import pytest import bigframes -import bigframes.enums -import bigframes.exceptions -from bigframes import version -from bigframes.core import bq_data -from bigframes.testing import mocks -TABLE_REFERENCE = { - "projectId": "my-project", - "datasetId": "my_dataset", - "tableId": "my_table", -} -SCHEMA = { - "fields": [ - {"name": "col1", "type": "INTEGER"}, - {"name": "col2", "type": "INTEGER"}, - {"name": "col3", "type": "INTEGER"}, - {"name": "col4", "type": "INTEGER"}, - ] -} -CLUSTERED_OR_PARTITIONED_TABLES = [ - pytest.param( - google.cloud.bigquery.Table.from_api_repr( - { - "tableReference": TABLE_REFERENCE, - "clustering": { - "fields": ["col1", "col2"], - }, - "schema": SCHEMA, - }, - ), - id="clustered", - ), - pytest.param( - google.cloud.bigquery.Table.from_api_repr( - { - "tableReference": TABLE_REFERENCE, - "rangePartitioning": { - "field": "col1", - "range": { - "start": 1, - "end": 100, - "interval": 1, - }, - }, - "schema": SCHEMA, - }, - ), - id="range-partitioned", - ), - pytest.param( - google.cloud.bigquery.Table.from_api_repr( - { - "tableReference": TABLE_REFERENCE, - "timePartitioning": { - "type": "MONTH", - "field": "col1", - }, - "schema": SCHEMA, - }, - ), - id="time-partitioned", - ), - pytest.param( - google.cloud.bigquery.Table.from_api_repr( - { - "tableReference": TABLE_REFERENCE, - "clustering": { - "fields": ["col1", "col2"], - }, - "timePartitioning": { - "type": "MONTH", - "field": "col1", - }, - "schema": SCHEMA, - }, - ), - id="time-partitioned-and-clustered", - ), -] - - -@pytest.mark.parametrize( - ("kwargs", "match"), - [ - pytest.param( - {"engine": "bigquery", "usecols": [1, 2]}, - "BigQuery engine only supports an iterable of strings for `usecols`.", - id="with_usecols_invalid", - ), - pytest.param( - {"engine": "bigquery", "encoding": "ASCII"}, - "BigQuery engine only supports the following encodings", - id="with_encoding_invalid", - ), - ], -) -def test_read_csv_w_bq_engine_raises_error(kwargs, match): - session = mocks.create_bigquery_session() - - with pytest.raises(NotImplementedError, match=match): - session.read_csv("", **kwargs) - - -@pytest.mark.parametrize( - ("engine",), - ( - ("c",), - ("python",), - ("pyarrow",), - ("python-fwf",), - ), -) -def test_read_csv_w_pandas_engines_raises_error_for_sequential_int64_index_col(engine): - session = mocks.create_bigquery_session() - - with pytest.raises(NotImplementedError, match="index_col"): - session.read_csv( - "path/to/csv.csv", - engine=engine, - index_col=bigframes.enums.DefaultIndexKind.SEQUENTIAL_INT64, - ) - - -@pytest.mark.parametrize( - ("kwargs"), - [ - pytest.param({"chunksize": 5}, id="with_chunksize"), - pytest.param({"iterator": True}, id="with_iterator"), - ], -) -def test_read_csv_w_pandas_engines_raises_error_for_unsupported_args(kwargs): - session = mocks.create_bigquery_session() - with pytest.raises( - NotImplementedError, - match="'chunksize' and 'iterator' arguments are not supported.", - ): - session.read_csv("path/to/csv.csv", **kwargs) - - -@pytest.mark.parametrize( - ("engine", "write_engine"), - ( - # Can't use bigquery parsing if parsing the data locally to upload. - ("bigquery", "bigquery_streaming"), - ("bigquery", "bigquery_inline"), - # No local parsing engines are compatible with bigquery_external_table. - (None, "bigquery_external_table"), - ("c", "bigquery_external_table"), - ("pyarrow", "bigquery_external_table"), - ("python", "bigquery_external_table"), - ("python-fwf", "bigquery_external_table"), - ), -) -def test_read_csv_with_incompatible_write_engine(engine, write_engine): - session = mocks.create_bigquery_session() - - with pytest.raises( - NotImplementedError, - match=re.escape( - f"Can't use parsing engine={repr(engine)} with write_engine={repr(write_engine)}, which" - ), - ): - session.read_csv( - "gs://cloud-samples-data/bigquery/us-states/us-states.csv", - engine=engine, - write_engine=write_engine, - ) - - -@pytest.mark.parametrize( - ("names", "error_message"), - ( - pytest.param("abc", "Names should be an ordered collection."), - pytest.param({"a", "b", "c"}, "Names should be an ordered collection."), - pytest.param(["a", "a"], "Duplicated names are not allowed."), - ), -) -def test_read_csv_w_bigquery_engine_raises_error_for_invalid_names( - names, error_message -): - session = mocks.create_bigquery_session() - - with pytest.raises(ValueError, match=error_message): - session.read_csv("path/to/csv.csv", engine="bigquery", names=names) - - -def test_read_csv_w_bigquery_engine_raises_error_for_invalid_dtypes(): - session = mocks.create_bigquery_session() - - with pytest.raises(ValueError, match="dtype should be a dict-like object."): - session.read_csv( - "path/to/csv.csv", - engine="bigquery", - dtype=["a", "b", "c"], # type: ignore[arg-type] - ) +from .. import resources @pytest.mark.parametrize("missing_parts_table_id", [(""), ("table")]) def test_read_gbq_missing_parts(missing_parts_table_id): - session = mocks.create_bigquery_session() + session = resources.create_bigquery_session() with pytest.raises(ValueError): session.read_gbq(missing_parts_table_id) -def test_read_gbq_cached_table(): - session = mocks.create_bigquery_session() - table_ref = google.cloud.bigquery.TableReference( - google.cloud.bigquery.DatasetReference("my-project", "my_dataset"), - "my_table", - ) - table = google.cloud.bigquery.Table( - table_ref, (google.cloud.bigquery.SchemaField("col", "INTEGER"),) - ) - table._properties["location"] = session._location - table._properties["numRows"] = "1000000000" - table._properties["type"] = "TABLE" - session._loader._df_snapshot[str(table_ref)] = ( - datetime.datetime(1999, 1, 2, 3, 4, 5, 678901, tzinfo=datetime.timezone.utc), - bq_data.GbqNativeTable.from_table(table), - ) - - session.bqclient._query_and_wait_bigframes = mock.MagicMock( - return_value=({"total_count": 3, "distinct_count": 2},) - ) - session.bqclient.get_table.return_value = table - - with pytest.warns( - bigframes.exceptions.TimeTravelCacheWarning, match=re.escape("use_cache=False") - ): - df = session.read_gbq("my-project.my_dataset.my_table") - - assert "1999-01-02T03:04:05.678901" in df.sql - - -def test_read_gbq_cached_table_doesnt_warn_for_anonymous_tables_and_doesnt_include_time_travel(): - session = mocks.create_bigquery_session() - table_ref = google.cloud.bigquery.TableReference( - google.cloud.bigquery.DatasetReference("my-project", "_anonymous_dataset"), - "my_table", - ) - table = google.cloud.bigquery.Table( - table_ref, (google.cloud.bigquery.SchemaField("col", "INTEGER"),) - ) - table._properties["location"] = session._location - table._properties["numRows"] = "1000000000" - table._properties["location"] = session._location - table._properties["type"] = "TABLE" - session._loader._df_snapshot[str(table_ref)] = ( - datetime.datetime(1999, 1, 2, 3, 4, 5, 678901, tzinfo=datetime.timezone.utc), - bq_data.GbqNativeTable.from_table(table), - ) - - session.bqclient._query_and_wait_bigframes = mock.MagicMock( - return_value=({"total_count": 3, "distinct_count": 2},) - ) - session.bqclient.get_table.return_value = table - - with warnings.catch_warnings(): - warnings.simplefilter( - "error", category=bigframes.exceptions.TimeTravelCacheWarning - ) - df = session.read_gbq("my-project._anonymous_dataset.my_table") - - assert "1999-01-02T03:04:05.678901" not in df.sql - - -@pytest.mark.parametrize("table", CLUSTERED_OR_PARTITIONED_TABLES) -def test_default_index_warning_raised_by_read_gbq(table): - """Because of the windowing operation to create a default index, row - filters can't push down to the clustering column. - - Raise an exception in this case so that the user is directed to supply a - unique index column or filter if possible. - - See internal issue 335727141. - """ - table = copy.deepcopy(table) - bqclient = mock.create_autospec(google.cloud.bigquery.Client, instance=True) - bqclient.project = "test-project" - bqclient.get_table.return_value = table - bqclient._query_and_wait_bigframes.return_value = ( - {"total_count": 3, "distinct_count": 2}, - ) - session = mocks.create_bigquery_session( - bqclient=bqclient, - # DefaultIndexWarning is only relevant for strict mode. - ordering_mode="strict", - ) - table._properties["location"] = session._location - - with pytest.warns(bigframes.exceptions.DefaultIndexWarning): - session.read_gbq("my-project.my_dataset.my_table") - - -@pytest.mark.parametrize("table", CLUSTERED_OR_PARTITIONED_TABLES) -def test_default_index_warning_not_raised_by_read_gbq_index_col_sequential_int64( - table, -): - """Because of the windowing operation to create a default index, row - filters can't push down to the clustering column. - - Allow people to use the default index only if they explicitly request it. - - See internal issue 335727141. - """ - table = copy.deepcopy(table) - bqclient = mock.create_autospec(google.cloud.bigquery.Client, instance=True) - bqclient.project = "test-project" - bqclient.get_table.return_value = table - bqclient._query_and_wait_bigframes.return_value = ( - {"total_count": 4, "distinct_count": 3}, - ) - session = mocks.create_bigquery_session( - bqclient=bqclient, - # DefaultIndexWarning is only relevant for strict mode. - ordering_mode="strict", - ) - table._properties["location"] = session._location - - # No warnings raised because we set the option allowing the default indexes. - with warnings.catch_warnings(): - warnings.simplefilter("error", bigframes.exceptions.DefaultIndexWarning) - df = session.read_gbq( - "my-project.my_dataset.my_table", - index_col=bigframes.enums.DefaultIndexKind.SEQUENTIAL_INT64, - ) - - # We expect a window operation because we specificaly requested a sequential index and named it. - df.index.name = "named_index" - generated_sql = df.sql.casefold() - assert "OVER".casefold() in generated_sql - assert "ROW_NUMBER()".casefold() in generated_sql - - -@pytest.mark.parametrize( - ("total_count", "distinct_count"), - ( - (0, 0), - (123, 123), - # Should still have a positive effect, even if the index is not unique. - (123, 111), - ), -) -@pytest.mark.parametrize("table", CLUSTERED_OR_PARTITIONED_TABLES) -def test_default_index_warning_not_raised_by_read_gbq_index_col_columns( - total_count, - distinct_count, - table, -): - table = copy.deepcopy(table) - table.schema = ( - google.cloud.bigquery.SchemaField("idx_1", "INT64"), - google.cloud.bigquery.SchemaField("idx_2", "INT64"), - google.cloud.bigquery.SchemaField("col_1", "INT64"), - google.cloud.bigquery.SchemaField("col_2", "INT64"), - ) - - bqclient = mock.create_autospec(google.cloud.bigquery.Client, instance=True) - bqclient.project = "test-project" - bqclient.get_table.return_value = table - bqclient._query_and_wait_bigframes.return_value = ( - {"total_count": total_count, "distinct_count": distinct_count}, - ) - session = mocks.create_bigquery_session( - bqclient=bqclient, - table_schema=table.schema, - # DefaultIndexWarning is only relevant for strict mode. - ordering_mode="strict", - ) - table._properties["location"] = session._location - - # No warning raised because there are columns to use as the index. - with warnings.catch_warnings(): - warnings.simplefilter("error", bigframes.exceptions.DefaultIndexWarning) - df = session.read_gbq( - "my-project.my_dataset.my_table", index_col=("idx_1", "idx_2") - ) - - # There should be no analytic operators to prevent row filtering pushdown. - assert "OVER" not in df.sql - assert tuple(df.index.names) == ("idx_1", "idx_2") - - -@pytest.mark.parametrize("table", CLUSTERED_OR_PARTITIONED_TABLES) -def test_default_index_warning_not_raised_by_read_gbq_primary_key(table): - """If a primary key is set on the table, we use that as the index column - by default, no error should be raised in this case. - - See internal issue 335727141. - """ - table = copy.deepcopy(table) - table.schema = ( - google.cloud.bigquery.SchemaField("pk_1", "INT64"), - google.cloud.bigquery.SchemaField("pk_2", "INT64"), - google.cloud.bigquery.SchemaField("col_1", "INT64"), - google.cloud.bigquery.SchemaField("col_2", "INT64"), - ) - - # TODO(b/305264153): use setter for table_constraints in client library - # when available. - table._properties["tableConstraints"] = { - "primaryKey": { - "columns": ["pk_1", "pk_2"], - }, - } - bqclient = mock.create_autospec(google.cloud.bigquery.Client, instance=True) - bqclient.project = "test-project" - bqclient.get_table.return_value = table - session = mocks.create_bigquery_session( - bqclient=bqclient, - table_schema=table.schema, - # DefaultIndexWarning is only relevant for strict mode. - ordering_mode="strict", - ) - table._properties["location"] = session._location - - # No warning raised because there is a primary key to use as the index. - with warnings.catch_warnings(): - warnings.simplefilter("error", bigframes.exceptions.DefaultIndexWarning) - df = session.read_gbq("my-project.my_dataset.my_table") - - # There should be no analytic operators to prevent row filtering pushdown. - assert "OVER" not in df.sql - assert tuple(df.index.names) == ("pk_1", "pk_2") - - @pytest.mark.parametrize( "not_found_table_id", [("unknown.dataset.table"), ("project.unknown.table"), ("project.dataset.unknown")], ) -def test_read_gbq_not_found_tables(not_found_table_id): +def test_read_gdb_not_found_tables(not_found_table_id): bqclient = mock.create_autospec(google.cloud.bigquery.Client, instance=True) bqclient.project = "test-project" bqclient.get_table.side_effect = google.api_core.exceptions.NotFound( "table not found" ) - session = mocks.create_bigquery_session(bqclient=bqclient) + session = resources.create_bigquery_session(bqclient=bqclient) with pytest.raises(google.api_core.exceptions.NotFound): session.read_gbq(not_found_table_id) -@pytest.mark.parametrize( - ("api_name", "query_or_table"), - [ - ("read_gbq", "project.dataset.table"), - ("read_gbq_table", "project.dataset.table"), - ("read_gbq", "SELECT * FROM project.dataset.table"), - ("read_gbq_query", "SELECT * FROM project.dataset.table"), - ], - ids=[ - "read_gbq_on_table", - "read_gbq_table", - "read_gbq_on_query", - "read_gbq_query", - ], -) -def test_read_gbq_external_table_no_drive_access(api_name, query_or_table): - session = mocks.create_bigquery_session() - session_query_mock = session.bqclient.query - - def query_mock(query, *args, **kwargs): - if query.lstrip().startswith("SELECT *"): - raise google.api_core.exceptions.Forbidden( - "Access Denied: BigQuery BigQuery: Permission denied while getting Drive credentials." - ) - - return session_query_mock(query, *args, **kwargs) - - session.bqclient.query_and_wait = query_mock - session.bqclient._query_and_wait_bigframes = query_mock - - def get_table_mock(table_ref): - table = google.cloud.bigquery.Table( - table_ref, (google.cloud.bigquery.SchemaField("col", "INTEGER"),) - ) - table._properties["numRows"] = 1000000000 - table._properties["location"] = session._location - return table - - session.bqclient.get_table = get_table_mock - - api = getattr(session, api_name) - with pytest.raises( - google.api_core.exceptions.Forbidden, - match="Check https://cloud.google.com/bigquery/docs/query-drive-data#Google_Drive_permissions.", - ): - api(query_or_table).to_pandas() - - @mock.patch.dict(os.environ, {}, clear=True) def test_session_init_fails_with_no_project(): with pytest.raises( @@ -526,36 +57,3 @@ def test_session_init_fails_with_no_project(): credentials=mock.Mock(spec=google.auth.credentials.Credentials) ) ) - - -def test_session_init_warns_if_bf_version_is_too_old(monkeypatch): - release_date = datetime.datetime.strptime(version.__release_date__, "%Y-%m-%d") - current_date = release_date + datetime.timedelta(days=366) - - class FakeDatetime(datetime.datetime): - @classmethod - def today(cls): - return current_date - - monkeypatch.setattr(datetime, "datetime", FakeDatetime) - - with pytest.warns(bigframes.exceptions.ObsoleteVersionWarning): - mocks.create_bigquery_session() - - -@mock.patch("bigframes.constants.MAX_INLINE_BYTES", 1) -def test_read_pandas_inline_exceeds_limit_raises_error(): - session = mocks.create_bigquery_session() - pd_df = pd.DataFrame([[1, 2, 3], [4, 5, 6]]) - with pytest.raises( - ValueError, - match=r"DataFrame size \(.* bytes\) exceeds the maximum allowed for inline data \(1 bytes\)\.", - ): - session.read_pandas(pd_df, write_engine="bigquery_inline") - - -def test_read_pandas_inline_w_interval_type_raises_error(): - session = mocks.create_bigquery_session() - df = pd.DataFrame(pd.arrays.IntervalArray.from_breaks([0, 10, 20, 30, 40, 50])) - with pytest.raises(TypeError): - session.read_pandas(df, write_engine="bigquery_inline") diff --git a/tests/unit/session/test_time.py b/tests/unit/session/test_time.py deleted file mode 100644 index 39a231c3cef..00000000000 --- a/tests/unit/session/test_time.py +++ /dev/null @@ -1,70 +0,0 @@ -# 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. -# 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 datetime -import unittest.mock as mock - -import google.cloud.bigquery -import pytest - -import bigframes.session.time - -INITIAL_BQ_TIME = datetime.datetime( - year=2020, - month=4, - day=24, - hour=8, - minute=55, - second=29, - tzinfo=datetime.timezone.utc, -) - - -@pytest.fixture() -def bq_client(): - bqclient = mock.create_autospec(google.cloud.bigquery.Client, instance=True) - - def query_and_wait_mock(query, *args, **kwargs): - if query.startswith("SELECT CURRENT_TIMESTAMP()"): - return iter([[INITIAL_BQ_TIME]]) - else: - return ValueError(f"mock cannot handle query : {query}") - - bqclient.query_and_wait = query_and_wait_mock - return bqclient - - -def test_bqsyncedclock_get_time(bq_client): - freezegun = pytest.importorskip("freezegun") - - # this initial local time is actually irrelevant, only the ticks matter - initial_local_datetime = datetime.datetime( - year=1, month=7, day=12, hour=15, minute=6, second=3 - ) - - with freezegun.freeze_time(initial_local_datetime) as frozen_datetime: - clock = bigframes.session.time.BigQuerySyncedClock(bq_client) - - t1 = clock.get_time() - assert t1 == INITIAL_BQ_TIME - - frozen_datetime.tick(datetime.timedelta(seconds=3)) - t2 = clock.get_time() - assert t2 == INITIAL_BQ_TIME + datetime.timedelta(seconds=3) - - frozen_datetime.tick(datetime.timedelta(seconds=23529385)) - t3 = clock.get_time() - assert t3 == INITIAL_BQ_TIME + datetime.timedelta( - seconds=3 - ) + datetime.timedelta(seconds=23529385) diff --git a/tests/unit/test_clients.py b/tests/unit/test_clients.py index 08d111b8662..f89cc21397c 100644 --- a/tests/unit/test_clients.py +++ b/tests/unit/test_clients.py @@ -12,84 +12,38 @@ # See the License for the specific language governing permissions and # limitations under the License. -from unittest import mock - import pytest -from google.cloud import bigquery_connection_v1, resourcemanager_v3 -from google.iam.v1 import policy_pb2 from bigframes import clients -def test_get_canonical_bq_connection_id_connection_id_only(): - connection_id = clients.get_canonical_bq_connection_id( +def test_get_connection_name_full_connection_id(): + connection_name = clients.BqConnectionManager.resolve_full_connection_name( "connection-id", default_project="default-project", default_location="us" ) - assert connection_id == "default-project.us.connection-id" + assert connection_name == "default-project.us.connection-id" -def test_get_canonical_bq_connection_id_location_and_connection_id(): - connection_id = clients.get_canonical_bq_connection_id( +def test_get_connection_name_full_location_connection_id(): + connection_name = clients.BqConnectionManager.resolve_full_connection_name( "eu.connection-id", default_project="default-project", default_location="us" ) - assert connection_id == "default-project.eu.connection-id" + assert connection_name == "default-project.eu.connection-id" -def test_get_canonical_bq_connection_id_already_canonical(): - connection_id = clients.get_canonical_bq_connection_id( +def test_get_connection_name_full_all(): + connection_name = clients.BqConnectionManager.resolve_full_connection_name( "my-project.eu.connection-id", default_project="default-project", default_location="us", ) - assert connection_id == "my-project.eu.connection-id" + assert connection_name == "my-project.eu.connection-id" -def test_get_canonical_bq_connection_id_invalid(): - with pytest.raises(ValueError, match="Invalid connection id format"): - clients.get_canonical_bq_connection_id( +def test_get_connection_name_full_raise_value_error(): + with pytest.raises(ValueError): + clients.BqConnectionManager.resolve_full_connection_name( "my-project.eu.connection-id.extra_field", default_project="default-project", default_location="us", ) - - -def test_get_canonical_bq_connection_id_valid_path(): - connection_id = clients.get_canonical_bq_connection_id( - "projects/project_id/locations/northamerica-northeast1/connections/connection-id", - default_project="default-project", - default_location="us", - ) - assert connection_id == "project_id.northamerica-northeast1.connection-id" - - -def test_get_canonical_bq_connection_id_invalid_path(): - with pytest.raises(ValueError, match="Invalid connection id format"): - clients.get_canonical_bq_connection_id( - "/projects/project_id/locations/northamerica-northeast1/connections/connection-id", - default_project="default-project", - default_location="us", - ) - - -def test_ensure_iam_binding(): - bq_connection_client = mock.create_autospec( - bigquery_connection_v1.ConnectionServiceClient, instance=True - ) - resource_manager_client = mock.create_autospec( - resourcemanager_v3.ProjectsClient, instance=True - ) - resource_manager_client.get_iam_policy.return_value = policy_pb2.Policy( - bindings=[ - policy_pb2.Binding( - role="roles/test.role1", members=["serviceAccount:serviceAccount1"] - ) - ] - ) - bq_connection_manager = clients.BqConnectionManager( - bq_connection_client, resource_manager_client - ) - bq_connection_manager._IAM_WAIT_SECONDS = 0 # no need to wait in test - bq_connection_manager._ensure_iam_binding( - "test-project", "serviceAccount2", "roles/test.role2" - ) - resource_manager_client.set_iam_policy.assert_called_once() diff --git a/tests/unit/test_col.py b/tests/unit/test_col.py deleted file mode 100644 index c8caf9136c0..00000000000 --- a/tests/unit/test_col.py +++ /dev/null @@ -1,269 +0,0 @@ -# Copyright 2026 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 operator -import pathlib -from typing import Generator - -import numpy as np -import pandas as pd -import pytest - -import bigframes -import bigframes.pandas as bpd -from bigframes.testing.utils import assert_frame_equal, convert_pandas_dtypes - -pytest.importorskip("polars") -pytest.importorskip("pandas", minversion="3.0.0") - - -CURRENT_DIR = pathlib.Path(__file__).parent -DATA_DIR = CURRENT_DIR.parent / "data" - - -@pytest.fixture(scope="module", autouse=True) -def session() -> Generator[bigframes.Session, None, None]: - import bigframes.core.global_session - from bigframes.testing import polars_session - - session = polars_session.TestSession() - with bigframes.core.global_session._GlobalSessionContext(session): - yield session - - -@pytest.fixture(scope="module") -def scalars_pandas_df_index() -> pd.DataFrame: - """pd.DataFrame pointing at test data.""" - - df = pd.read_json( - DATA_DIR / "scalars.jsonl", - lines=True, - ) - convert_pandas_dtypes(df, bytes_col=True) - - df = df.set_index("rowindex", drop=False) - df.index.name = None - return df.set_index("rowindex").sort_index() - - -@pytest.fixture(scope="module") -def scalars_df_index( - session: bigframes.Session, scalars_pandas_df_index -) -> bpd.DataFrame: - return session.read_pandas(scalars_pandas_df_index) - - -@pytest.fixture(scope="module") -def scalars_df_2_index( - session: bigframes.Session, scalars_pandas_df_index -) -> bpd.DataFrame: - return session.read_pandas(scalars_pandas_df_index) - - -@pytest.fixture(scope="module") -def scalars_dfs( - scalars_df_index, - scalars_pandas_df_index, -): - return scalars_df_index, scalars_pandas_df_index - - -@pytest.mark.parametrize( - ("op",), - [ - (operator.invert,), - ], -) -def test_pd_col_unary_operators(scalars_dfs, op): - scalars_df, scalars_pandas_df = scalars_dfs - bf_kwargs = { - "result": op(bpd.col("bool_col")), - } - pd_kwargs = { - "result": op(pd.col("bool_col")), # type: ignore - } - df = scalars_df.assign(**bf_kwargs) - - bf_result = df.to_pandas() - pd_result = scalars_pandas_df.assign(**pd_kwargs) - - assert_frame_equal(bf_result, pd_result) - - -@pytest.mark.parametrize( - ("op"), - [ - (lambda x: x.sum()), - (lambda x: x.mean()), - (lambda x: x.min()), - (lambda x: x.max()), - (lambda x: x.std()), - (lambda x: x.var()), - ], - ids=[ - "sum", - "mean", - "min", - "max", - "std", - "var", - ], -) -def test_pd_col_aggregate_op(scalars_dfs, op): - scalars_df, scalars_pandas_df = scalars_dfs - bf_kwargs = { - "result": op(bpd.col("float64_col")), - } - pd_kwargs = { - "result": op(pd.col("float64_col")), # type: ignore - } - df = scalars_df.assign(**bf_kwargs) - - bf_result = df.to_pandas() - pd_result = scalars_pandas_df.assign(**pd_kwargs) - - assert_frame_equal(bf_result, pd_result) - - -def test_pd_col_aggregate_of_aggregate(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - bf_kwargs = { - "result": (bpd.col("int64_col") - bpd.col("int64_col").mean()).mean(), - } - pd_kwargs = { - "result": (pd.col("int64_col") - pd.col("int64_col").mean()).mean(), # type: ignore - } - df = scalars_df.assign(**bf_kwargs) - - bf_result = df.to_pandas() - pd_result = scalars_pandas_df.assign(**pd_kwargs) - - assert_frame_equal(bf_result, pd_result) - - -@pytest.mark.parametrize( - ("op",), - [ - (operator.add,), - (operator.sub,), - (operator.mul,), - (operator.truediv,), - (operator.floordiv,), - (operator.gt,), - (operator.lt,), - (operator.ge,), - (operator.le,), - (operator.eq,), - (operator.mod,), - ], -) -def test_pd_col_binary_operators(scalars_dfs, op): - scalars_df, scalars_pandas_df = scalars_dfs - bf_kwargs = { - "result": op(bpd.col("float64_col"), 2.4), - "reverse_result": op(2.4, bpd.col("float64_col")), - } - pd_kwargs = { - "result": op(pd.col("float64_col"), 2.4), # type: ignore - "reverse_result": op(2.4, pd.col("float64_col")), # type: ignore - } - df = scalars_df.assign(**bf_kwargs) - - bf_result = df.to_pandas() - pd_result = scalars_pandas_df.assign(**pd_kwargs) - - assert_frame_equal(bf_result, pd_result) - - -@pytest.mark.parametrize( - ("op",), - [ - (operator.and_,), - (operator.or_,), - (operator.xor,), - ], -) -def test_pd_col_binary_bool_operators(scalars_dfs, op): - scalars_df, scalars_pandas_df = scalars_dfs - bf_kwargs = { - "result": op(bpd.col("bool_col"), True), - "reverse_result": op(False, bpd.col("bool_col")), - } - pd_kwargs = { - "result": op(pd.col("bool_col"), True), # type: ignore - "reverse_result": op(False, pd.col("bool_col")), # type: ignore - } - df = scalars_df.assign(**bf_kwargs) - - bf_result = df.to_pandas() - pd_result = scalars_pandas_df.assign(**pd_kwargs) - - assert_frame_equal(bf_result, pd_result) - - -def test_loc_with_pd_col(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - - bf_result = scalars_df.loc[bpd.col("float64_col") > 4].to_pandas() - pd_result = scalars_pandas_df.loc[pd.col("float64_col") > 4] # type: ignore - - assert_frame_equal(bf_result, pd_result) - - -def test_getitem_with_pd_col(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - - bf_result = scalars_df[bpd.col("float64_col") > 4].to_pandas() - pd_result = scalars_pandas_df[pd.col("float64_col") > 4] # type: ignore - - assert_frame_equal(bf_result, pd_result) - - -def test_col_str_accessor(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - - bf_result = scalars_df.assign(result=bpd.col("string_col").str.lower()).to_pandas() - pd_result = scalars_pandas_df.assign(result=pd.col("string_col").str.lower()) # type: ignore - - assert_frame_equal(bf_result, pd_result) - - -def test_col_dt_accessor(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - - bf_result = scalars_df.assign(result=bpd.col("date_col").dt.year).to_pandas() - pd_result = scalars_pandas_df.assign(result=pd.col("date_col").dt.year) # type: ignore - - # int64[pyarrow] vs Int64 - assert_frame_equal(bf_result, pd_result, check_dtype=False) - - -def test_col_numpy_ufunc(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - - bf_result = scalars_df.assign( - sqrt=np.sqrt(bpd.col("float64_col")), # type: ignore - add_const=np.add(bpd.col("float64_col"), 2.4), # type: ignore - radd_const=np.add(2.4, bpd.col("float64_col")), # type: ignore - add_cols=np.add(bpd.col("float64_col"), bpd.col("int64_col")), # type: ignore - ).to_pandas() - pd_result = scalars_pandas_df.assign( - sqrt=np.sqrt(pd.col("float64_col")), # type: ignore - add_const=np.add(pd.col("float64_col"), 2.4), # type: ignore - radd_const=np.add(2.4, pd.col("float64_col")), # type: ignore - add_cols=np.add(pd.col("float64_col"), pd.col("int64_col")), # type: ignore - ) - - # int64[pyarrow] vs Int64 - assert_frame_equal(bf_result, pd_result, check_dtype=False) diff --git a/tests/unit/test_compute_options.py b/tests/unit/test_compute_options.py new file mode 100644 index 00000000000..499a0a5fefa --- /dev/null +++ b/tests/unit/test_compute_options.py @@ -0,0 +1,30 @@ +# Copyright 2023 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 as bf + +from . import resources + + +def test_maximum_bytes_option(): + session = resources.create_bigquery_session() + num_query_calls = 0 + with bf.option_context("compute.maximum_bytes_billed", 10000): + # clear initial method calls + session.bqclient.method_calls = [] + session._start_query("query") + for call in session.bqclient.method_calls: + _, _, kwargs = call + num_query_calls += 1 + assert kwargs["job_config"].maximum_bytes_billed == 10000 + assert num_query_calls > 0 diff --git a/tests/unit/test_constants.py b/tests/unit/test_constants.py deleted file mode 100644 index 4e11419077f..00000000000 --- a/tests/unit/test_constants.py +++ /dev/null @@ -1,23 +0,0 @@ -# Copyright 2023 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_vendored.constants - -import bigframes.version - - -def test_feedback_link_includes_version(): - version = bigframes.version.__version__ - assert len(version) > 0 - assert version in bigframes_vendored.constants.FEEDBACK_LINK diff --git a/tests/unit/test_core.py b/tests/unit/test_core.py new file mode 100644 index 00000000000..d9672b2635b --- /dev/null +++ b/tests/unit/test_core.py @@ -0,0 +1,221 @@ +# Copyright 2023 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 ibis.expr.types as ibis_types +import pandas + +import bigframes.core as core +import bigframes.core.ordering +import bigframes.operations as ops +import bigframes.operations.aggregations as agg_ops + +from . import resources + + +def test_arrayvalue_constructor_from_ibis_table_adds_all_columns(): + session = resources.create_pandas_session( + { + "test_table": pandas.DataFrame( + { + "col1": [1, 2, 3], + "not_included": [True, False, True], + "col2": ["a", "b", "c"], + "col3": [0.1, 0.2, 0.3], + } + ) + } + ) + ibis_table = session.ibis_client.table("test_table") + columns = (ibis_table["col1"], ibis_table["col2"], ibis_table["col3"]) + ordering = bigframes.core.ordering.ExpressionOrdering( + tuple([core.OrderingColumnReference("col1")]), + total_ordering_columns=frozenset(["col1"]), + ) + actual = core.ArrayValue.from_ibis( + session=session, + table=ibis_table, + columns=columns, + ordering=ordering, + hidden_ordering_columns=(), + ) + assert actual.compile()._table is ibis_table + assert len(actual.column_ids) == 3 + + +def test_arrayvalue_with_get_column_type(): + value = resources.create_arrayvalue( + pandas.DataFrame( + { + "col1": [1, 2, 3], + "col2": ["a", "b", "c"], + "col3": [0.1, 0.2, 0.3], + } + ), + total_ordering_columns=["col1"], + ) + col1_type = value.get_column_type("col1") + col2_type = value.get_column_type("col2") + col3_type = value.get_column_type("col3") + assert isinstance(col1_type, pandas.Int64Dtype) + assert isinstance(col2_type, pandas.StringDtype) + assert isinstance(col3_type, pandas.Float64Dtype) + + +def test_arrayvalue_with_get_column(): + value = resources.create_arrayvalue( + pandas.DataFrame( + { + "col1": [1, 2, 3], + "col2": ["a", "b", "c"], + "col3": [0.1, 0.2, 0.3], + } + ), + total_ordering_columns=["col1"], + ) + col1 = value.compile()._get_ibis_column("col1") + assert isinstance(col1, ibis_types.Value) + assert col1.get_name() == "col1" + assert col1.type().is_int64() + + +def test_arrayvalues_to_ibis_expr_with_get_column(): + value = resources.create_arrayvalue( + pandas.DataFrame( + { + "col1": [1, 2, 3], + "col2": ["a", "b", "c"], + "col3": [0.1, 0.2, 0.3], + } + ), + total_ordering_columns=["col1"], + ) + expr = value.compile()._get_ibis_column("col1") + assert expr.get_name() == "col1" + assert expr.type().is_int64() + + +def test_arrayvalues_to_ibis_expr_with_concat(): + value = resources.create_arrayvalue( + pandas.DataFrame( + { + "col1": [1, 2, 3], + "col2": ["a", "b", "c"], + "col3": [0.1, 0.2, 0.3], + } + ), + total_ordering_columns=["col1"], + ) + expr = value.concat([value]) + actual = expr.compile()._to_ibis_expr("unordered") + assert len(actual.columns) == 3 + # TODO(ashleyxu, b/299631930): test out the union expression + assert actual.columns[0] == "column_0" + assert actual.columns[1] == "column_1" + assert actual.columns[2] == "column_2" + + +def test_arrayvalues_to_ibis_expr_with_project_unary_op(): + value = resources.create_arrayvalue( + pandas.DataFrame( + { + "col1": [1, 2, 3], + "col2": ["a", "b", "c"], + "col3": [0.1, 0.2, 0.3], + } + ), + total_ordering_columns=["col1"], + ) + expr = value.project_unary_op("col1", ops.AsTypeOp("string")).compile() + assert value.compile().columns[0].type().is_int64() + assert expr.columns[0].type().is_string() + + +def test_arrayvalues_to_ibis_expr_with_project_binary_op(): + value = resources.create_arrayvalue( + pandas.DataFrame( + { + "col1": [1, 2, 3], + "col2": [0.2, 0.3, 0.4], + "col3": [0.1, 0.2, 0.3], + } + ), + total_ordering_columns=["col1"], + ) + expr = value.project_binary_op("col2", "col3", ops.add_op, "col4").compile() + assert expr.columns[3].type().is_float64() + actual = expr._to_ibis_expr("unordered") + assert len(expr.columns) == 4 + assert actual.columns[3] == "col4" + + +def test_arrayvalues_to_ibis_expr_with_project_ternary_op(): + value = resources.create_arrayvalue( + pandas.DataFrame( + { + "col1": [1, 2, 3], + "col2": [0.2, 0.3, 0.4], + "col3": [True, False, False], + "col4": [0.1, 0.2, 0.3], + } + ), + total_ordering_columns=["col1"], + ) + expr = value.project_ternary_op( + "col2", "col3", "col4", ops.where_op, "col5" + ).compile() + assert expr.columns[4].type().is_float64() + actual = expr._to_ibis_expr("unordered") + assert len(expr.columns) == 5 + assert actual.columns[4] == "col5" + + +def test_arrayvalue_to_ibis_expr_with_aggregate(): + value = resources.create_arrayvalue( + pandas.DataFrame( + { + "col1": [1, 2, 3], + "col2": ["a", "b", "c"], + "col3": [0.1, 0.2, 0.3], + } + ), + total_ordering_columns=["col1"], + ) + expr = value.aggregate( + aggregations=(("col1", agg_ops.sum_op, "col4"),), + by_column_ids=["col1"], + dropna=False, + ).compile() + actual = expr._to_ibis_expr("unordered") + assert len(expr.columns) == 2 + assert actual.columns[0] == "col1" + assert actual.columns[1] == "col4" + assert expr.columns[1].type().is_int64() + + +def test_arrayvalue_to_ibis_expr_with_corr_aggregate(): + value = resources.create_arrayvalue( + pandas.DataFrame( + { + "col1": [1, 2, 3], + "col2": ["a", "b", "c"], + "col3": [0.1, 0.2, 0.3], + } + ), + total_ordering_columns=["col1"], + ) + expr = value.corr_aggregate(corr_aggregations=[("col1", "col3", "col4")]).compile() + actual = expr._to_ibis_expr("unordered") + assert len(expr.columns) == 1 + assert actual.columns[0] == "col4" + assert expr.columns[0].type().is_float64() diff --git a/tests/unit/test_daemon.py b/tests/unit/test_daemon.py deleted file mode 100644 index 6b3acd7d7dc..00000000000 --- a/tests/unit/test_daemon.py +++ /dev/null @@ -1,42 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import datetime -import time -from unittest.mock import MagicMock - -from bigframes.session.bigquery_session import RecurringTaskDaemon - - -def test_recurring_task_daemon_calls(): - mock_task = MagicMock() - daemon = RecurringTaskDaemon( - task=mock_task, frequency=datetime.timedelta(seconds=0.1) - ) - daemon.start() - time.sleep(1.0) - daemon.stop() - time.sleep(0.5) - # be lenient, but number of calls should be in this ballpark regardless of scheduling hiccups - assert mock_task.call_count > 6 - assert mock_task.call_count < 12 - - -def test_recurring_task_daemon_never_started(): - mock_task = MagicMock() - _ = RecurringTaskDaemon( - task=mock_task, frequency=datetime.timedelta(seconds=0.0001) - ) - time.sleep(0.1) - assert mock_task.call_count == 0 diff --git a/tests/unit/test_dataframe.py b/tests/unit/test_dataframe.py index d045bf7c3fc..17a82908893 100644 --- a/tests/unit/test_dataframe.py +++ b/tests/unit/test_dataframe.py @@ -13,115 +13,20 @@ # limitations under the License. import google.cloud.bigquery -import pandas as pd import pytest -import bigframes.dataframe -import bigframes.session -from bigframes.testing import mocks - - -def test_dataframe_dropna_axis_1_subset_not_implememented( - monkeypatch: pytest.MonkeyPatch, -): - dataframe = mocks.create_dataframe(monkeypatch) - - with pytest.raises(NotImplementedError, match="subset"): - dataframe.dropna(axis=1, subset=["col1", "col2"]) - - -def test_dataframe_repr_with_uninitialized_object(): - """Ensures DataFrame.__init__ can be paused in a visual debugger without crashing. - - Regression test for https://github.com/googleapis/python-bigquery-dataframes/issues/728 - """ - # Avoid calling __init__ to simulate pausing __init__ in a debugger. - # https://stackoverflow.com/a/6384982/101923 - dataframe = bigframes.dataframe.DataFrame.__new__(bigframes.dataframe.DataFrame) - got = repr(dataframe) - assert "DataFrame" in got - - -@pytest.mark.parametrize( - "rule", - [ - pd.DateOffset(weeks=1), - pd.Timedelta(hours=8), - # According to - # https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.resample.html - # these all default to "right" for closed and label, which isn't yet supported. - "ME", - "YE", - "QE", - "BME", - "BA", - "BQE", - "W", - ], -) -def test_dataframe_rule_not_implememented( - monkeypatch: pytest.MonkeyPatch, - rule, -): - dataframe = mocks.create_dataframe(monkeypatch) - - with pytest.raises(NotImplementedError, match="rule"): - dataframe.resample(rule=rule) - - -def test_dataframe_closed_not_implememented( - monkeypatch: pytest.MonkeyPatch, -): - dataframe = mocks.create_dataframe(monkeypatch) - - with pytest.raises(NotImplementedError, match="Only closed='left'"): - dataframe.resample(rule="1d", closed="right") - - -def test_dataframe_label_not_implememented( - monkeypatch: pytest.MonkeyPatch, -): - dataframe = mocks.create_dataframe(monkeypatch) - - with pytest.raises(NotImplementedError, match="Only label='left'"): - dataframe.resample(rule="1d", label="right") - - -@pytest.mark.parametrize( - "origin", - [ - "end", - "end_day", - ], -) -def test_dataframe_origin_not_implememented( - monkeypatch: pytest.MonkeyPatch, - origin, -): - dataframe = mocks.create_dataframe(monkeypatch) - - with pytest.raises(NotImplementedError, match="origin"): - dataframe.resample(rule="1d", origin=origin) - - -def test_dataframe_setattr_with_uninitialized_object(): - """Ensures DataFrame can be subclassed without trying to set attributes as columns.""" - # Avoid calling __init__ since it might be called later in a subclass. - # https://stackoverflow.com/a/6384982/101923 - dataframe = bigframes.dataframe.DataFrame.__new__(bigframes.dataframe.DataFrame) - dataframe.lineage = "my-test-value" - assert dataframe.lineage == "my-test-value" # Should just be a regular attribute. +from . import resources def test_dataframe_to_gbq_invalid_destination(monkeypatch: pytest.MonkeyPatch): - dataframe = mocks.create_dataframe(monkeypatch) + dataframe = resources.create_dataframe(monkeypatch) with pytest.raises(ValueError, match="no_dataset_or_project"): dataframe.to_gbq("no_dataset_or_project") def test_dataframe_to_gbq_invalid_if_exists(monkeypatch: pytest.MonkeyPatch): - dataframe = mocks.create_dataframe(monkeypatch) + dataframe = resources.create_dataframe(monkeypatch) with pytest.raises(ValueError, match="notreallyanoption"): # Even though the type is annotated with the literals we accept, users @@ -133,7 +38,7 @@ def test_dataframe_to_gbq_invalid_if_exists(monkeypatch: pytest.MonkeyPatch): def test_dataframe_to_gbq_invalid_if_exists_no_destination( monkeypatch: pytest.MonkeyPatch, ): - dataframe = mocks.create_dataframe(monkeypatch) + dataframe = resources.create_dataframe(monkeypatch) with pytest.raises(ValueError, match="append"): dataframe.to_gbq(if_exists="append") @@ -146,80 +51,9 @@ def test_dataframe_to_gbq_writes_to_anonymous_dataset( anonymous_dataset = google.cloud.bigquery.DatasetReference.from_string( anonymous_dataset_id ) - session = mocks.create_bigquery_session(anonymous_dataset=anonymous_dataset) - dataframe = mocks.create_dataframe(monkeypatch, session=session) + session = resources.create_bigquery_session(anonymous_dataset=anonymous_dataset) + dataframe = resources.create_dataframe(monkeypatch, session=session) destination = dataframe.to_gbq() assert destination.startswith(anonymous_dataset_id) - - -def test_dataframe_rename_columns(monkeypatch: pytest.MonkeyPatch): - dataframe = mocks.create_dataframe( - monkeypatch, data={"col1": [], "col2": [], "col3": []} - ) - assert dataframe.columns.to_list() == ["col1", "col2", "col3"] - renamed = dataframe.rename(columns={"col1": "a", "col2": "b", "col3": "c"}) - assert renamed.columns.to_list() == ["a", "b", "c"] - - -def test_dataframe_rename_columns_inplace_returns_none(monkeypatch: pytest.MonkeyPatch): - dataframe = mocks.create_dataframe( - monkeypatch, data={"col1": [], "col2": [], "col3": []} - ) - assert dataframe.columns.to_list() == ["col1", "col2", "col3"] - assert ( - dataframe.rename(columns={"col1": "a", "col2": "b", "col3": "c"}, inplace=True) - is None - ) - assert dataframe.columns.to_list() == ["a", "b", "c"] - - -def test_dataframe_rename_axis(monkeypatch: pytest.MonkeyPatch): - dataframe = mocks.create_dataframe( - monkeypatch, data={"index1": [], "index2": [], "col1": [], "col2": []} - ).set_index(["index1", "index2"]) - assert list(dataframe.index.names) == ["index1", "index2"] - renamed = dataframe.rename_axis(["a", "b"]) - assert list(renamed.index.names) == ["a", "b"] - - -def test_dataframe_rename_axis_inplace_returns_none(monkeypatch: pytest.MonkeyPatch): - dataframe = mocks.create_dataframe( - monkeypatch, data={"index1": [], "index2": [], "col1": [], "col2": []} - ).set_index(["index1", "index2"]) - assert list(dataframe.index.names) == ["index1", "index2"] - assert dataframe.rename_axis(["a", "b"], inplace=True) is None - assert list(dataframe.index.names) == ["a", "b"] - - -def test_dataframe_drop_columns_inplace_returns_none(monkeypatch: pytest.MonkeyPatch): - dataframe = mocks.create_dataframe( - monkeypatch, data={"col1": [1], "col2": [2], "col3": [3]} - ) - assert dataframe.columns.to_list() == ["col1", "col2", "col3"] - assert dataframe.drop(columns=["col1", "col3"], inplace=True) is None - assert dataframe.columns.to_list() == ["col2"] - - -def test_dataframe_drop_index_inplace_returns_none( - # Drop index depends on the actual data, not just metadata, so use the - # local engine for more robust testing. - polars_session: bigframes.session.Session, -): - dataframe = polars_session.read_pandas( - pd.DataFrame({"col1": [1, 2, 3], "index_col": [0, 1, 2]}).set_index("index_col") - ) - assert dataframe.index.to_list() == [0, 1, 2] - assert dataframe.drop(index=[0, 2], inplace=True) is None - assert dataframe.index.to_list() == [1] - - -def test_dataframe_drop_columns_returns_new_dataframe(monkeypatch: pytest.MonkeyPatch): - dataframe = mocks.create_dataframe( - monkeypatch, data={"col1": [1], "col2": [2], "col3": [3]} - ) - assert dataframe.columns.to_list() == ["col1", "col2", "col3"] - new_dataframe = dataframe.drop(columns=["col1", "col3"]) - assert dataframe.columns.to_list() == ["col1", "col2", "col3"] - assert new_dataframe.columns.to_list() == ["col2"] diff --git a/tests/unit/test_dataframe_io.py b/tests/unit/test_dataframe_io.py deleted file mode 100644 index f2c02413963..00000000000 --- a/tests/unit/test_dataframe_io.py +++ /dev/null @@ -1,56 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from unittest import mock - -import pytest - -from bigframes.testing import mocks - - -@pytest.fixture -def mock_df(monkeypatch: pytest.MonkeyPatch): - dataframe = mocks.create_dataframe(monkeypatch) - monkeypatch.setattr(dataframe, "to_pandas", mock.Mock()) - return dataframe - - -@pytest.mark.parametrize( - "api_name, kwargs", - [ - ("to_csv", {"allow_large_results": True}), - ("to_json", {"allow_large_results": True}), - ("to_numpy", {"allow_large_results": True}), - ("to_parquet", {"allow_large_results": True}), - ("to_dict", {"allow_large_results": True}), - ("to_excel", {"excel_writer": "abc", "allow_large_results": True}), - ("to_latex", {"allow_large_results": True}), - ("to_records", {"allow_large_results": True}), - ("to_string", {"allow_large_results": True}), - ("to_html", {"allow_large_results": True}), - ("to_markdown", {"allow_large_results": True}), - ("to_pickle", {"path": "abc", "allow_large_results": True}), - ("to_orc", {"allow_large_results": True}), - ], -) -def test_dataframe_to_pandas(mock_df, api_name, kwargs): - getattr(mock_df, api_name)(**kwargs) - mock_df.to_pandas.assert_called_once_with( - allow_large_results=kwargs["allow_large_results"] - ) - - -def test_to_gbq_if_exists_invalid(mock_df): - with pytest.raises(ValueError, match="Got invalid value 'invalid' for if_exists."): - mock_df.to_gbq("a.b.c", if_exists="invalid") diff --git a/tests/unit/test_dataframe_polars.py b/tests/unit/test_dataframe_polars.py deleted file mode 100644 index c2dc979b71e..00000000000 --- a/tests/unit/test_dataframe_polars.py +++ /dev/null @@ -1,4535 +0,0 @@ -# Copyright 2023 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 io -import operator -import pathlib -import tempfile -import typing -from typing import Generator, List, Tuple - -import numpy as np -import pandas as pd -import pandas.testing -import pytest - -import bigframes -import bigframes._config.display_options as display_options -import bigframes.core.indexes as bf_indexes -import bigframes.dataframe as dataframe -import bigframes.pandas as bpd -import bigframes.series as series -from bigframes.testing.utils import ( - assert_dfs_equivalent, - assert_frame_equal, - assert_series_equal, - assert_series_equivalent, - convert_pandas_dtypes, -) - -pytest.importorskip("polars") -pytest.importorskip("pandas", minversion="2.0.0") - -CURRENT_DIR = pathlib.Path(__file__).parent -DATA_DIR = CURRENT_DIR.parent / "data" - - -@pytest.fixture(scope="module", autouse=True) -def session() -> Generator[bigframes.Session, None, None]: - import bigframes.core.global_session - from bigframes.testing import polars_session - - session = polars_session.TestSession() - with bigframes.core.global_session._GlobalSessionContext(session): - yield session - - -@pytest.fixture(scope="module") -def scalars_pandas_df_index() -> pd.DataFrame: - """pd.DataFrame pointing at test data.""" - - df = pd.read_json( - DATA_DIR / "scalars.jsonl", - lines=True, - ) - convert_pandas_dtypes(df, bytes_col=True) - - df = df.set_index("rowindex", drop=False) - df.index.name = None - return df.set_index("rowindex").sort_index() - - -@pytest.fixture(scope="module") -def scalars_df_index( - session: bigframes.Session, scalars_pandas_df_index -) -> bpd.DataFrame: - return session.read_pandas(scalars_pandas_df_index) - - -@pytest.fixture(scope="module") -def scalars_df_2_index( - session: bigframes.Session, scalars_pandas_df_index -) -> bpd.DataFrame: - return session.read_pandas(scalars_pandas_df_index) - - -@pytest.fixture(scope="module") -def scalars_dfs( - scalars_df_index, - scalars_pandas_df_index, -): - return scalars_df_index, scalars_pandas_df_index - - -def test_df_construct_copy(scalars_dfs): - columns = ["int64_col", "string_col", "float64_col"] - scalars_df, scalars_pandas_df = scalars_dfs - # Make the mapping from label to col_id non-trivial - bf_df = scalars_df.copy() - bf_df["int64_col"] = bf_df["int64_col"] / 2 - pd_df = scalars_pandas_df.copy() - pd_df["int64_col"] = pd_df["int64_col"] / 2 - - bf_result = dataframe.DataFrame(bf_df, columns=columns).to_pandas() - - pd_result = pd.DataFrame(pd_df, columns=columns) - pandas.testing.assert_frame_equal(bf_result, pd_result) - - -def test_df_construct_pandas_default(scalars_dfs): - # This should trigger the inlined codepath - columns = [ - "int64_too", - "int64_col", - "float64_col", - "bool_col", - "string_col", - "date_col", - "datetime_col", - "numeric_col", - "float64_col", - "time_col", - "timestamp_col", - ] - _, scalars_pandas_df = scalars_dfs - bf_result = dataframe.DataFrame(scalars_pandas_df, columns=columns).to_pandas() - pd_result = pd.DataFrame(scalars_pandas_df, columns=columns) - pandas.testing.assert_frame_equal(bf_result, pd_result) - - -def test_df_construct_structs(session): - pd_frame = pd.Series( - [ - {"version": 1, "project": "pandas"}, - {"version": 2, "project": "pandas"}, - {"version": 1, "project": "numpy"}, - ] - ).to_frame() - bf_series = session.read_pandas(pd_frame) - pd.testing.assert_frame_equal( - bf_series.to_pandas(), pd_frame, check_index_type=False, check_dtype=False - ) - - -def test_df_construct_pandas_set_dtype(scalars_dfs): - columns = [ - "int64_too", - "int64_col", - "float64_col", - "bool_col", - ] - _, scalars_pandas_df = scalars_dfs - bf_result = dataframe.DataFrame( - scalars_pandas_df, columns=columns, dtype="Float64" - ).to_pandas() - pd_result = pd.DataFrame(scalars_pandas_df, columns=columns, dtype="Float64") - pandas.testing.assert_frame_equal(bf_result, pd_result) - - -def test_df_construct_from_series(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - bf_result = dataframe.DataFrame( - {"a": scalars_df["int64_col"], "b": scalars_df["string_col"]}, - dtype="string[pyarrow]", - ) - pd_result = pd.DataFrame( - {"a": scalars_pandas_df["int64_col"], "b": scalars_pandas_df["string_col"]}, - dtype="string[pyarrow]", - ) - assert_dfs_equivalent(pd_result, bf_result) - - -def test_df_construct_from_dict(): - input_dict = { - "Animal": ["Falcon", "Falcon", "Parrot", "Parrot"], - # With a space in column name. We use standardized SQL schema ids to solve the problem that BQ schema doesn't support column names with spaces. b/296751058 - "Max Speed": [380.0, 370.0, 24.0, 26.0], - } - bf_result = dataframe.DataFrame(input_dict).to_pandas() - pd_result = pd.DataFrame(input_dict) - - pandas.testing.assert_frame_equal( - bf_result, pd_result, check_dtype=False, check_index_type=False - ) - - -def test_df_construct_dtype(): - data = { - "int_col": [1, 2, 3], - "string_col": ["1.1", "2.0", "3.5"], - "float_col": [1.0, 2.0, 3.0], - } - dtype = pd.StringDtype(storage="pyarrow") - bf_result = dataframe.DataFrame(data, dtype=dtype) - pd_result = pd.DataFrame(data, dtype=dtype) - pd_result.index = pd_result.index.astype("Int64") - pandas.testing.assert_frame_equal(bf_result.to_pandas(), pd_result) - - -def test_get_column(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - col_name = "int64_col" - series = scalars_df[col_name] - bf_result = series.to_pandas() - pd_result = scalars_pandas_df[col_name] - assert_series_equal(bf_result, pd_result) - - -def test_get_column_nonstring(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - series = scalars_df.rename(columns={"int64_col": 123.1})[123.1] - bf_result = series.to_pandas() - pd_result = scalars_pandas_df.rename(columns={"int64_col": 123.1})[123.1] - assert_series_equal(bf_result, pd_result) - - -@pytest.mark.parametrize( - "row_slice", - [ - (slice(1, 7, 2)), - (slice(1, 7, None)), - (slice(None, -3, None)), - ], -) -def test_get_rows_with_slice(scalars_dfs, row_slice): - scalars_df, scalars_pandas_df = scalars_dfs - bf_result = scalars_df[row_slice].to_pandas() - pd_result = scalars_pandas_df[row_slice] - assert_frame_equal(bf_result, pd_result) - - -def test_hasattr(scalars_dfs): - scalars_df, _ = scalars_dfs - assert hasattr(scalars_df, "int64_col") - assert hasattr(scalars_df, "head") - assert not hasattr(scalars_df, "not_exist") - - -@pytest.mark.parametrize( - ("ordered"), - [ - (True), - (False), - ], -) -def test_head_with_custom_column_labels( - scalars_df_index, scalars_pandas_df_index, ordered -): - rename_mapping = { - "int64_col": "Integer Column", - "string_col": "言語列", - } - bf_df = scalars_df_index.rename(columns=rename_mapping).head(3) - bf_result = bf_df.to_pandas(ordered=ordered) - pd_result = scalars_pandas_df_index.rename(columns=rename_mapping).head(3) - assert_frame_equal(bf_result, pd_result, ignore_order=not ordered) - - -def test_tail_with_custom_column_labels(scalars_df_index, scalars_pandas_df_index): - rename_mapping = { - "int64_col": "Integer Column", - "string_col": "言語列", - } - bf_df = scalars_df_index.rename(columns=rename_mapping).tail(3) - bf_result = bf_df.to_pandas() - pd_result = scalars_pandas_df_index.rename(columns=rename_mapping).tail(3) - pandas.testing.assert_frame_equal(bf_result, pd_result) - - -def test_get_column_by_attr(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - series = scalars_df.int64_col - bf_result = series.to_pandas() - pd_result = scalars_pandas_df.int64_col - assert_series_equal(bf_result, pd_result) - - -def test_get_columns(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - col_names = ["bool_col", "float64_col", "int64_col"] - df_subset = scalars_df.get(col_names) - df_pandas = df_subset.to_pandas() - pd.testing.assert_index_equal( - df_pandas.columns, scalars_pandas_df[col_names].columns - ) - - -def test_get_columns_default(scalars_dfs): - scalars_df, _ = scalars_dfs - col_names = ["not", "column", "names"] - result = scalars_df.get(col_names, "default_val") - assert result == "default_val" - - -@pytest.mark.parametrize( - ("loc", "column", "value", "allow_duplicates"), - [ - (0, 666, 2, False), - (5, "float64_col", 2.2, True), - (13, "rowindex_2", [8, 7, 6, 5, 4, 3, 2, 1, 0], True), - pytest.param( - 14, - "test", - 2, - False, - marks=pytest.mark.xfail( - raises=IndexError, - ), - ), - pytest.param( - 12, - "int64_col", - 2, - False, - marks=pytest.mark.xfail( - raises=ValueError, - ), - ), - ], -) -def test_insert(scalars_dfs, loc, column, value, allow_duplicates): - scalars_df, scalars_pandas_df = scalars_dfs - # insert works inplace, so will influence other tests. - # make a copy to avoid inplace changes. - bf_df = scalars_df.copy() - pd_df = scalars_pandas_df.copy() - bf_df.insert(loc, column, value, allow_duplicates) - pd_df.insert(loc, column, value, allow_duplicates) - - pd.testing.assert_frame_equal(bf_df.to_pandas(), pd_df, check_dtype=False) - - -def test_where_series_cond(scalars_df_index, scalars_pandas_df_index): - # Condition is dataframe, other is None (as default). - cond_bf = scalars_df_index["int64_col"] > 0 - cond_pd = scalars_pandas_df_index["int64_col"] > 0 - bf_result = scalars_df_index.where(cond_bf).to_pandas() - pd_result = scalars_pandas_df_index.where(cond_pd) - pandas.testing.assert_frame_equal(bf_result, pd_result) - - -def test_mask_series_cond(scalars_df_index, scalars_pandas_df_index): - cond_bf = scalars_df_index["int64_col"] > 0 - cond_pd = scalars_pandas_df_index["int64_col"] > 0 - - bf_df = scalars_df_index[["int64_too", "int64_col", "float64_col"]] - pd_df = scalars_pandas_df_index[["int64_too", "int64_col", "float64_col"]] - bf_result = bf_df.mask(cond_bf, bf_df + 1).to_pandas() - pd_result = pd_df.mask(cond_pd, pd_df + 1) - pandas.testing.assert_frame_equal(bf_result, pd_result) - - -def test_where_series_multi_index(scalars_df_index, scalars_pandas_df_index): - # Test when a dataframe has multi-index or multi-columns. - columns = ["int64_col", "float64_col"] - dataframe_bf = scalars_df_index[columns] - - dataframe_bf.columns = pd.MultiIndex.from_tuples( - [("str1", 1), ("str2", 2)], names=["STR", "INT"] - ) - cond_bf = dataframe_bf["str1"] > 0 - - with pytest.raises(NotImplementedError) as context: - dataframe_bf.where(cond_bf).to_pandas() - assert ( - str(context.value) - == "The dataframe.where() method does not support multi-column." - ) - - -def test_where_series_cond_const_other(scalars_df_index, scalars_pandas_df_index): - # Condition is a series, other is a constant. - columns = ["int64_col", "float64_col"] - dataframe_bf = scalars_df_index[columns] - dataframe_pd = scalars_pandas_df_index[columns] - dataframe_bf.columns.name = "test_name" - dataframe_pd.columns.name = "test_name" - - cond_bf = dataframe_bf["int64_col"] > 0 - cond_pd = dataframe_pd["int64_col"] > 0 - other = 0 - - bf_result = dataframe_bf.where(cond_bf, other).to_pandas() - pd_result = dataframe_pd.where(cond_pd, other) - pandas.testing.assert_frame_equal(bf_result, pd_result) - - -def test_where_series_cond_dataframe_other(scalars_df_index, scalars_pandas_df_index): - # Condition is a series, other is a dataframe. - columns = ["int64_col", "float64_col"] - dataframe_bf = scalars_df_index[columns] - dataframe_pd = scalars_pandas_df_index[columns] - - cond_bf = dataframe_bf["int64_col"] > 0 - cond_pd = dataframe_pd["int64_col"] > 0 - other_bf = -dataframe_bf - other_pd = -dataframe_pd - - bf_result = dataframe_bf.where(cond_bf, other_bf).to_pandas() - pd_result = dataframe_pd.where(cond_pd, other_pd) - pandas.testing.assert_frame_equal(bf_result, pd_result) - - -def test_where_dataframe_cond(scalars_df_index, scalars_pandas_df_index): - # Condition is a dataframe, other is None. - columns = ["int64_col", "float64_col"] - dataframe_bf = scalars_df_index[columns] - dataframe_pd = scalars_pandas_df_index[columns] - - cond_bf = dataframe_bf > 0 - cond_pd = dataframe_pd > 0 - - bf_result = dataframe_bf.where(cond_bf, None).to_pandas() - pd_result = dataframe_pd.where(cond_pd, None) - pandas.testing.assert_frame_equal(bf_result, pd_result) - - -def test_where_dataframe_cond_const_other(scalars_df_index, scalars_pandas_df_index): - # Condition is a dataframe, other is a constant. - columns = ["int64_col", "float64_col"] - dataframe_bf = scalars_df_index[columns] - dataframe_pd = scalars_pandas_df_index[columns] - - cond_bf = dataframe_bf > 0 - cond_pd = dataframe_pd > 0 - other_bf = 10 - other_pd = 10 - - bf_result = dataframe_bf.where(cond_bf, other_bf).to_pandas() - pd_result = dataframe_pd.where(cond_pd, other_pd) - pandas.testing.assert_frame_equal(bf_result, pd_result) - - -def test_where_dataframe_cond_dataframe_other( - scalars_df_index, scalars_pandas_df_index -): - # Condition is a dataframe, other is a dataframe. - columns = ["int64_col", "float64_col"] - dataframe_bf = scalars_df_index[columns] - dataframe_pd = scalars_pandas_df_index[columns] - - cond_bf = dataframe_bf > 0 - cond_pd = dataframe_pd > 0 - other_bf = dataframe_bf * 2 - other_pd = dataframe_pd * 2 - - bf_result = dataframe_bf.where(cond_bf, other_bf).to_pandas() - pd_result = dataframe_pd.where(cond_pd, other_pd) - pandas.testing.assert_frame_equal(bf_result, pd_result) - - -def test_drop_column(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - col_name = "int64_col" - df_pandas = scalars_df.drop(columns=col_name).to_pandas() - pd.testing.assert_index_equal( - df_pandas.columns, scalars_pandas_df.drop(columns=col_name).columns - ) - - -def test_drop_columns(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - col_names = ["int64_col", "geography_col", "time_col"] - df_pandas = scalars_df.drop(columns=col_names).to_pandas() - pd.testing.assert_index_equal( - df_pandas.columns, scalars_pandas_df.drop(columns=col_names).columns - ) - - -def test_drop_labels_axis_1(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - labels = ["int64_col", "geography_col", "time_col"] - - pd_result = scalars_pandas_df.drop(labels=labels, axis=1) - bf_result = scalars_df.drop(labels=labels, axis=1).to_pandas() - - pd.testing.assert_frame_equal(pd_result, bf_result) - - -def test_drop_with_custom_column_labels(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - rename_mapping = { - "int64_col": "Integer Column", - "string_col": "言語列", - } - dropped_columns = [ - "言語列", - "timestamp_col", - ] - bf_df = scalars_df.rename(columns=rename_mapping).drop(columns=dropped_columns) - bf_result = bf_df.to_pandas() - pd_result = scalars_pandas_df.rename(columns=rename_mapping).drop( - columns=dropped_columns - ) - assert_frame_equal(bf_result, pd_result) - - -def test_df_memory_usage(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - - pd_result = scalars_pandas_df.memory_usage() - bf_result = scalars_df.memory_usage() - - pd.testing.assert_series_equal(pd_result, bf_result, rtol=1.5) - - -def test_df_info(scalars_dfs): - expected = ( - "\n" - "Index: 9 entries, 0 to 8\n" - "Data columns (total 14 columns):\n" - " # Column Non-Null Count Dtype\n" - "--- ------------- ---------------- ------------------------------\n" - " 0 bool_col 8 non-null boolean\n" - " 1 bytes_col 6 non-null binary[pyarrow]\n" - " 2 date_col 7 non-null date32[day][pyarrow]\n" - " 3 datetime_col 6 non-null timestamp[us][pyarrow]\n" - " 4 geography_col 4 non-null geometry\n" - " 5 int64_col 8 non-null Int64\n" - " 6 int64_too 9 non-null Int64\n" - " 7 numeric_col 6 non-null decimal128(38, 9)[pyarrow]\n" - " 8 float64_col 7 non-null Float64\n" - " 9 rowindex_2 9 non-null Int64\n" - " 10 string_col 8 non-null string\n" - " 11 time_col 6 non-null time64[us][pyarrow]\n" - " 12 timestamp_col 6 non-null timestamp[us, tz=UTC][pyarrow]\n" - " 13 duration_col 7 non-null duration[us][pyarrow]\n" - "dtypes: Float64(1), Int64(3), binary[pyarrow](1), boolean(1), date32[day][pyarrow](1), decimal128(38, 9)[pyarrow](1), duration[us][pyarrow](1), geometry(1), string(1), time64[us][pyarrow](1), timestamp[us, tz=UTC][pyarrow](1), timestamp[us][pyarrow](1)\n" - "memory usage: 1341 bytes\n" - ) - - scalars_df, _ = scalars_dfs - bf_result = io.StringIO() - - scalars_df.info(buf=bf_result) - - assert expected == bf_result.getvalue() - - -@pytest.mark.parametrize( - ("include", "exclude"), - [ - ("Int64", None), - (["int"], None), - ("number", None), - ([pd.Int64Dtype(), pd.BooleanDtype()], None), - (None, [pd.Int64Dtype(), pd.BooleanDtype()]), - ("Int64", ["boolean"]), - ], -) -def test_select_dtypes(scalars_dfs, include, exclude): - scalars_df, scalars_pandas_df = scalars_dfs - - pd_result = scalars_pandas_df.select_dtypes(include=include, exclude=exclude) - bf_result = scalars_df.select_dtypes(include=include, exclude=exclude).to_pandas() - - pd.testing.assert_frame_equal(pd_result, bf_result) - - -def test_drop_index(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - - pd_result = scalars_pandas_df.drop(index=[4, 1, 2]) - bf_result = scalars_df.drop(index=[4, 1, 2]).to_pandas() - - pd.testing.assert_frame_equal(pd_result, bf_result) - - -def test_drop_pandas_index(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - drop_index = scalars_pandas_df.iloc[[4, 1, 2]].index - - pd_result = scalars_pandas_df.drop(index=drop_index) - bf_result = scalars_df.drop(index=drop_index).to_pandas() - - pd.testing.assert_frame_equal(pd_result, bf_result) - - -def test_drop_bigframes_index(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - drop_index = scalars_df.loc[[4, 1, 2]].index - drop_pandas_index = scalars_pandas_df.loc[[4, 1, 2]].index - - pd_result = scalars_pandas_df.drop(index=drop_pandas_index) - bf_result = scalars_df.drop(index=drop_index).to_pandas() - - pd.testing.assert_frame_equal(pd_result, bf_result) - - -def test_drop_bigframes_index_with_na(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - scalars_df = scalars_df.copy() - scalars_pandas_df = scalars_pandas_df.copy() - scalars_df = scalars_df.set_index("bytes_col") - scalars_pandas_df = scalars_pandas_df.set_index("bytes_col") - drop_index = scalars_df.iloc[[2, 5]].index - drop_pandas_index = scalars_pandas_df.iloc[[2, 5]].index - - pd_result = scalars_pandas_df.drop(index=drop_pandas_index) # drop_pandas_index) - bf_result = scalars_df.drop(index=drop_index).to_pandas() - - pd.testing.assert_frame_equal(pd_result, bf_result) - - -def test_drop_bigframes_multiindex(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 - scalars_df = scalars_df.copy() - scalars_pandas_df = scalars_pandas_df.copy() - sub_df = scalars_df.iloc[[4, 1, 2]] - sub_pandas_df = scalars_pandas_df.iloc[[4, 1, 2]] - sub_df = sub_df.set_index(["bytes_col", "numeric_col"]) - sub_pandas_df = sub_pandas_df.set_index(["bytes_col", "numeric_col"]) - drop_index = sub_df.index - drop_pandas_index = sub_pandas_df.index - - scalars_df = scalars_df.set_index(["bytes_col", "numeric_col"]) - scalars_pandas_df = scalars_pandas_df.set_index(["bytes_col", "numeric_col"]) - bf_result = scalars_df.drop(index=drop_index).to_pandas() - pd_result = scalars_pandas_df.drop(index=drop_pandas_index) - - pd.testing.assert_frame_equal(pd_result, bf_result) - - -def test_drop_labels_axis_0(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - - pd_result = scalars_pandas_df.drop(labels=[4, 1, 2], axis=0) - bf_result = scalars_df.drop(labels=[4, 1, 2], axis=0).to_pandas() - - pd.testing.assert_frame_equal(pd_result, bf_result) - - -def test_drop_index_and_columns(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - - pd_result = scalars_pandas_df.drop(index=[4, 1, 2], columns="int64_col") - bf_result = scalars_df.drop(index=[4, 1, 2], columns="int64_col").to_pandas() - - pd.testing.assert_frame_equal(pd_result, bf_result) - - -def test_rename(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - col_name_dict = {"bool_col": 1.2345} - df_pandas = scalars_df.rename(columns=col_name_dict).to_pandas() - pd.testing.assert_index_equal( - df_pandas.columns, scalars_pandas_df.rename(columns=col_name_dict).columns - ) - - -def test_df_peek(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - - peek_result = scalars_df.peek(n=3, force=False, allow_large_results=True) - - pd.testing.assert_index_equal(scalars_pandas_df.columns, peek_result.columns) - assert len(peek_result) == 3 - - -def test_df_peek_with_large_results_not_allowed(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - - peek_result = scalars_df.peek(n=3, force=False, allow_large_results=False) - - pd.testing.assert_index_equal(scalars_pandas_df.columns, peek_result.columns) - assert len(peek_result) == 3 - - -def test_df_peek_filtered(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - peek_result = scalars_df[scalars_df.int64_col != 0].peek(n=3, force=False) - pd.testing.assert_index_equal(scalars_pandas_df.columns, peek_result.columns) - assert len(peek_result) == 3 - - -def test_df_peek_force_default(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - peek_result = scalars_df[["int64_col", "int64_too"]].cumsum().peek(n=3) - pd.testing.assert_index_equal( - scalars_pandas_df[["int64_col", "int64_too"]].columns, peek_result.columns - ) - assert len(peek_result) == 3 - - -def test_df_peek_reset_index(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - peek_result = ( - scalars_df[["int64_col", "int64_too"]].reset_index(drop=True).peek(n=3) - ) - pd.testing.assert_index_equal( - scalars_pandas_df[["int64_col", "int64_too"]].columns, peek_result.columns - ) - assert len(peek_result) == 3 - - -def test_repr_w_all_rows(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - - # Remove columns with flaky formatting, like NUMERIC columns (which use the - # object dtype). Also makes a copy so that mutating the index name doesn't - # break other tests. - scalars_df = scalars_df.drop(columns=["numeric_col"]) - scalars_pandas_df = scalars_pandas_df.drop(columns=["numeric_col"]) - - # When there are 10 or fewer rows, the outputs should be identical. - actual = repr(scalars_df.head(10)) - - with display_options.pandas_repr(bigframes.options.display): - expected = repr(scalars_pandas_df.head(10)) - - assert actual == expected - - -def test_join_repr(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - - scalars_df = ( - scalars_df[["int64_col"]] - .join(scalars_df.set_index("int64_col")[["int64_too"]]) - .sort_index() - ) - scalars_pandas_df = ( - scalars_pandas_df[["int64_col"]] - .join(scalars_pandas_df.set_index("int64_col")[["int64_too"]]) - .sort_index() - ) - # Pandas join result index name seems to depend on the index values in a way that bigframes can't match exactly - scalars_pandas_df.index.name = None - - actual = repr(scalars_df) - - with display_options.pandas_repr(bigframes.options.display): - expected = repr(scalars_pandas_df) - - assert actual == expected - - -def test_mimebundle_html_repr_w_all_rows(scalars_dfs, session): - scalars_df, _ = scalars_dfs - # get a pandas df of the expected format - df, _ = scalars_df._block.to_pandas() - pandas_df = df.set_axis(scalars_df._block.column_labels, axis=1) - pandas_df.index.name = scalars_df.index.name - - # When there are 10 or fewer rows, the outputs should be identical except for the extra note. - bundle = scalars_df.head(10)._repr_mimebundle_() - actual = bundle["text/html"] - - with display_options.pandas_repr(bigframes.options.display): - pandas_repr = pandas_df.head(10)._repr_html_() - - expected = ( - pandas_repr - + f"[{len(pandas_df.index)} rows x {len(pandas_df.columns)} columns in total]" - ) - assert actual == expected - - -def test_df_column_name_with_space(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - col_name_dict = {"bool_col": "bool col"} - df_pandas = scalars_df.rename(columns=col_name_dict).to_pandas() - pd.testing.assert_index_equal( - df_pandas.columns, scalars_pandas_df.rename(columns=col_name_dict).columns - ) - - -def test_df_column_name_duplicate(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - col_name_dict = {"int64_too": "int64_col"} - df_pandas = scalars_df.rename(columns=col_name_dict).to_pandas() - pd.testing.assert_index_equal( - df_pandas.columns, scalars_pandas_df.rename(columns=col_name_dict).columns - ) - - -def test_get_df_column_name_duplicate(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - col_name_dict = {"int64_too": "int64_col"} - - bf_result = scalars_df.rename(columns=col_name_dict)["int64_col"].to_pandas() - pd_result = scalars_pandas_df.rename(columns=col_name_dict)["int64_col"] - pd.testing.assert_index_equal(bf_result.columns, pd_result.columns) - - -@pytest.mark.parametrize( - ("indices", "axis"), - [ - ([1, 3, 5], 0), - ([2, 4, 6], 1), - ([1, -3, -5, -6], "index"), - ([-2, -4, -6], "columns"), - ], -) -def test_take_df(scalars_dfs, indices, axis): - scalars_df, scalars_pandas_df = scalars_dfs - - bf_result = scalars_df.take(indices, axis=axis).to_pandas() - pd_result = scalars_pandas_df.take(indices, axis=axis) - - assert_frame_equal(bf_result, pd_result) - - -def test_filter_df(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - - bf_bool_series = scalars_df["bool_col"] - bf_result = scalars_df[bf_bool_series].to_pandas() - - pd_bool_series = scalars_pandas_df["bool_col"] - pd_result = scalars_pandas_df[pd_bool_series] - - assert_frame_equal(bf_result, pd_result) - - -def test_assign_new_column(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - kwargs = {"new_col": 2} - df = scalars_df.assign(**kwargs) - bf_result = df.to_pandas() - pd_result = scalars_pandas_df.assign(**kwargs) - - # Convert default pandas dtypes `int64` to match BigQuery DataFrames dtypes. - pd_result["new_col"] = pd_result["new_col"].astype("Int64") - - assert_frame_equal(bf_result, pd_result) - - -def test_assign_using_pd_col(scalars_dfs): - if pd.__version__.startswith("1.") or pd.__version__.startswith("2."): - pytest.skip("col expression interface only supported for pandas 3+") - scalars_df, scalars_pandas_df = scalars_dfs - bf_kwargs = { - "new_col_1": 4 - bpd.col("int64_col"), - "new_col_2": bpd.col("int64_col") / (bpd.col("float64_col") * 0.5), - } - pd_kwargs = { - "new_col_1": 4 - pd.col("int64_col"), # type: ignore - "new_col_2": pd.col("int64_col") / (pd.col("float64_col") * 0.5), # type: ignore - } - - df = scalars_df.assign(**bf_kwargs) - bf_result = df.to_pandas() - pd_result = scalars_pandas_df.assign(**pd_kwargs) - - assert_frame_equal(bf_result, pd_result) - - -def test_assign_new_column_w_loc(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - bf_df = scalars_df.copy() - pd_df = scalars_pandas_df.copy() - bf_df.loc[:, "new_col"] = 2 - pd_df.loc[:, "new_col"] = 2 - bf_result = bf_df.to_pandas() - pd_result = pd_df - - # Convert default pandas dtypes `int64` to match BigQuery DataFrames dtypes. - pd_result["new_col"] = pd_result["new_col"].astype("Int64") - - pd.testing.assert_frame_equal(bf_result, pd_result) - - -@pytest.mark.parametrize( - ("scalar",), - [ - (2.1,), - (None,), - ], -) -def test_assign_new_column_w_setitem(scalars_dfs, scalar): - scalars_df, scalars_pandas_df = scalars_dfs - bf_df = scalars_df.copy() - pd_df = scalars_pandas_df.copy() - bf_df["new_col"] = scalar - pd_df["new_col"] = scalar - bf_result = bf_df.to_pandas() - pd_result = pd_df - - # Convert default pandas dtypes `float64` to match BigQuery DataFrames dtypes. - pd_result["new_col"] = pd_result["new_col"].astype("Float64") - - pd.testing.assert_frame_equal(bf_result, pd_result) - - -def test_assign_new_column_w_setitem_dataframe(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - bf_df = scalars_df.copy() - pd_df = scalars_pandas_df.copy() - bf_df["int64_col"] = bf_df["int64_too"].to_frame() - pd_df["int64_col"] = pd_df["int64_too"].to_frame() - - # Convert default pandas dtypes `int64` to match BigQuery DataFrames dtypes. - pd_df["int64_col"] = pd_df["int64_col"].astype("Int64") - - pd.testing.assert_frame_equal(bf_df.to_pandas(), pd_df) - - -def test_assign_new_column_w_setitem_dataframe_error(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - bf_df = scalars_df.copy() - pd_df = scalars_pandas_df.copy() - - with pytest.raises(ValueError): - bf_df["impossible_col"] = bf_df[["int64_too", "string_col"]] - with pytest.raises(ValueError): - pd_df["impossible_col"] = pd_df[["int64_too", "string_col"]] - - -def test_assign_new_column_w_setitem_list(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - bf_df = scalars_df.copy() - pd_df = scalars_pandas_df.copy() - bf_df["new_col"] = [9, 8, 7, 6, 5, 4, 3, 2, 1] - pd_df["new_col"] = [9, 8, 7, 6, 5, 4, 3, 2, 1] - bf_result = bf_df.to_pandas() - pd_result = pd_df - - # Convert default pandas dtypes `int64` to match BigQuery DataFrames dtypes. - pd_result["new_col"] = pd_result["new_col"].astype("Int64") - - pd.testing.assert_frame_equal(bf_result, pd_result) - - -def test_assign_new_column_w_setitem_list_repeated(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - bf_df = scalars_df.copy() - pd_df = scalars_pandas_df.copy() - bf_df["new_col"] = [9, 8, 7, 6, 5, 4, 3, 2, 1] - pd_df["new_col"] = [9, 8, 7, 6, 5, 4, 3, 2, 1] - bf_df["new_col_2"] = [1, 3, 2, 5, 4, 7, 6, 9, 8] - pd_df["new_col_2"] = [1, 3, 2, 5, 4, 7, 6, 9, 8] - bf_result = bf_df.to_pandas() - pd_result = pd_df - - # Convert default pandas dtypes `int64` to match BigQuery DataFrames dtypes. - pd_result["new_col"] = pd_result["new_col"].astype("Int64") - pd_result["new_col_2"] = pd_result["new_col_2"].astype("Int64") - - pd.testing.assert_frame_equal(bf_result, pd_result) - - -def test_assign_new_column_w_setitem_list_custom_index(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - bf_df = scalars_df.copy() - pd_df = scalars_pandas_df.copy() - - # set the custom index - pd_df = pd_df.set_index(["string_col", "int64_col"]) - bf_df = bf_df.set_index(["string_col", "int64_col"]) - - bf_df["new_col"] = [9, 8, 7, 6, 5, 4, 3, 2, 1] - pd_df["new_col"] = [9, 8, 7, 6, 5, 4, 3, 2, 1] - bf_result = bf_df.to_pandas() - pd_result = pd_df - - # Convert default pandas dtypes `int64` to match BigQuery DataFrames dtypes. - pd_result["new_col"] = pd_result["new_col"].astype("Int64") - - pd.testing.assert_frame_equal(bf_result, pd_result) - - -def test_assign_new_column_w_setitem_list_error(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - bf_df = scalars_df.copy() - pd_df = scalars_pandas_df.copy() - - with pytest.raises(ValueError): - pd_df["new_col"] = [1, 2, 3] # should be len 9, is 3 - with pytest.raises(ValueError): - bf_df["new_col"] = [1, 2, 3] - - -def test_assign_existing_column(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - kwargs = {"int64_col": 2} - df = scalars_df.assign(**kwargs) - bf_result = df.to_pandas() - pd_result = scalars_pandas_df.assign(**kwargs) - - # Convert default pandas dtypes `int64` to match BigQuery DataFrames dtypes. - pd_result["int64_col"] = pd_result["int64_col"].astype("Int64") - - assert_frame_equal(bf_result, pd_result) - - -def test_assign_listlike_to_empty_df(session): - empty_df = dataframe.DataFrame(session=session) - empty_pandas_df = pd.DataFrame() - - bf_result = empty_df.assign(new_col=[1, 2, 3]) - pd_result = empty_pandas_df.assign(new_col=[1, 2, 3]) - - pd_result["new_col"] = pd_result["new_col"].astype("Int64") - pd_result.index = pd_result.index.astype("Int64") - assert_frame_equal(bf_result.to_pandas(), pd_result) - - -def test_assign_to_empty_df_multiindex_error(session): - empty_df = dataframe.DataFrame(session=session) - empty_pandas_df = pd.DataFrame() - - empty_df["empty_col_1"] = typing.cast(series.Series, []) - empty_df["empty_col_2"] = typing.cast(series.Series, []) - empty_pandas_df["empty_col_1"] = [] - empty_pandas_df["empty_col_2"] = [] - empty_df = empty_df.set_index(["empty_col_1", "empty_col_2"]) - empty_pandas_df = empty_pandas_df.set_index(["empty_col_1", "empty_col_2"]) - - with pytest.raises(ValueError): - empty_df.assign(new_col=[1, 2, 3, 4, 5, 6, 7, 8, 9]) - with pytest.raises(ValueError): - empty_pandas_df.assign(new_col=[1, 2, 3, 4, 5, 6, 7, 8, 9]) - - -@pytest.mark.parametrize( - ("ordered"), - [ - (True), - (False), - ], -) -def test_assign_series(scalars_dfs, ordered): - scalars_df, scalars_pandas_df = scalars_dfs - column_name = "int64_col" - df = scalars_df.assign(new_col=scalars_df[column_name]) - bf_result = df.to_pandas(ordered=ordered) - pd_result = scalars_pandas_df.assign(new_col=scalars_pandas_df[column_name]) - - assert_frame_equal(bf_result, pd_result, ignore_order=not ordered) - - -def test_assign_series_overwrite(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - column_name = "int64_col" - df = scalars_df.assign(**{column_name: scalars_df[column_name] + 3}) - bf_result = df.to_pandas() - pd_result = scalars_pandas_df.assign( - **{column_name: scalars_pandas_df[column_name] + 3} - ) - - assert_frame_equal(bf_result, pd_result) - - -def test_assign_sequential(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - kwargs = {"int64_col": 2, "new_col": 3, "new_col2": 4} - df = scalars_df.assign(**kwargs) - bf_result = df.to_pandas() - pd_result = scalars_pandas_df.assign(**kwargs) - - # Convert default pandas dtypes `int64` to match BigQuery DataFrames dtypes. - pd_result["int64_col"] = pd_result["int64_col"].astype("Int64") - pd_result["new_col"] = pd_result["new_col"].astype("Int64") - pd_result["new_col2"] = pd_result["new_col2"].astype("Int64") - - assert_frame_equal(bf_result, pd_result) - - -# Require an index so that the self-join is consistent each time. -def test_assign_same_table_different_index_performs_self_join( - scalars_df_index, scalars_pandas_df_index -): - column_name = "int64_col" - bf_df = scalars_df_index.assign( - alternative_index=scalars_df_index["rowindex_2"] + 2 - ) - pd_df = scalars_pandas_df_index.assign( - alternative_index=scalars_pandas_df_index["rowindex_2"] + 2 - ) - bf_df_2 = bf_df.set_index("alternative_index") - pd_df_2 = pd_df.set_index("alternative_index") - bf_result = bf_df.assign(new_col=bf_df_2[column_name] * 10).to_pandas() - pd_result = pd_df.assign(new_col=pd_df_2[column_name] * 10) - - pandas.testing.assert_frame_equal(bf_result, pd_result) - - -# Different table expression must have Index -def test_assign_different_df( - scalars_df_index, scalars_df_2_index, scalars_pandas_df_index -): - column_name = "int64_col" - df = scalars_df_index.assign(new_col=scalars_df_2_index[column_name]) - bf_result = df.to_pandas() - # Doesn't matter to pandas if it comes from the same DF or a different DF. - pd_result = scalars_pandas_df_index.assign( - new_col=scalars_pandas_df_index[column_name] - ) - - assert_frame_equal(bf_result, pd_result) - - -def test_assign_different_df_w_loc( - scalars_df_index, scalars_df_2_index, scalars_pandas_df_index -): - bf_df = scalars_df_index.copy() - bf_df2 = scalars_df_2_index.copy() - pd_df = scalars_pandas_df_index.copy() - assert "int64_col" in bf_df.columns - assert "int64_col" in pd_df.columns - bf_df.loc[:, "int64_col"] = bf_df2.loc[:, "int64_col"] + 1 - pd_df.loc[:, "int64_col"] = pd_df.loc[:, "int64_col"] + 1 - bf_result = bf_df.to_pandas() - pd_result = pd_df - - # Convert default pandas dtypes `int64` to match BigQuery DataFrames dtypes. - pd_result["int64_col"] = pd_result["int64_col"].astype("Int64") - - pd.testing.assert_frame_equal(bf_result, pd_result) - - -def test_assign_different_df_w_setitem( - scalars_df_index, scalars_df_2_index, scalars_pandas_df_index -): - bf_df = scalars_df_index.copy() - bf_df2 = scalars_df_2_index.copy() - pd_df = scalars_pandas_df_index.copy() - assert "int64_col" in bf_df.columns - assert "int64_col" in pd_df.columns - bf_df["int64_col"] = bf_df2["int64_col"] + 1 - pd_df["int64_col"] = pd_df["int64_col"] + 1 - bf_result = bf_df.to_pandas() - pd_result = pd_df - - # Convert default pandas dtypes `int64` to match BigQuery DataFrames dtypes. - pd_result["int64_col"] = pd_result["int64_col"].astype("Int64") - - pd.testing.assert_frame_equal(bf_result, pd_result) - - -def test_assign_callable_lambda(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - kwargs = {"new_col": lambda x: x["int64_col"] + x["int64_too"]} - df = scalars_df.assign(**kwargs) - bf_result = df.to_pandas() - pd_result = scalars_pandas_df.assign(**kwargs) - - # Convert default pandas dtypes `int64` to match BigQuery DataFrames dtypes. - pd_result["new_col"] = pd_result["new_col"].astype("Int64") - - assert_frame_equal(bf_result, pd_result) - - -@pytest.mark.parametrize( - ("axis", "how", "ignore_index", "subset"), - [ - (0, "any", False, None), - (0, "any", True, None), - (0, "all", False, ["bool_col", "time_col"]), - (0, "any", False, ["bool_col", "time_col"]), - (0, "all", False, "time_col"), - (1, "any", False, None), - (1, "all", False, None), - ], -) -def test_df_dropna(scalars_dfs, axis, how, ignore_index, subset): - # 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 - df = scalars_df.dropna(axis=axis, how=how, ignore_index=ignore_index, subset=subset) - bf_result = df.to_pandas() - pd_result = scalars_pandas_df.dropna( - axis=axis, how=how, ignore_index=ignore_index, subset=subset - ) - - # Pandas uses int64 instead of Int64 (nullable) dtype. - pd_result.index = pd_result.index.astype(pd.Int64Dtype()) - pandas.testing.assert_frame_equal(bf_result, pd_result) - - -def test_df_dropna_range_columns(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 - scalars_df = scalars_df.copy() - scalars_pandas_df = scalars_pandas_df.copy() - scalars_df.columns = pandas.RangeIndex(0, len(scalars_df.columns)) - scalars_pandas_df.columns = pandas.RangeIndex(0, len(scalars_pandas_df.columns)) - - df = scalars_df.dropna() - bf_result = df.to_pandas() - pd_result = scalars_pandas_df.dropna() - - pandas.testing.assert_frame_equal(bf_result, pd_result) - - -def test_df_interpolate(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - columns = ["int64_col", "int64_too", "float64_col"] - bf_result = scalars_df[columns].interpolate().to_pandas() - # Pandas can only interpolate on "float64" columns - # https://github.com/pandas-dev/pandas/issues/40252 - pd_result = scalars_pandas_df[columns].astype("float64").interpolate() - - pandas.testing.assert_frame_equal( - bf_result, - pd_result, - check_index_type=False, - check_dtype=False, - ) - - -@pytest.mark.parametrize( - "col, fill_value", - [ - (["int64_col", "float64_col"], 3), - (["string_col"], "A"), - (["datetime_col"], pd.Timestamp("2023-01-01")), - ], -) -def test_df_fillna(scalars_dfs, col, fill_value): - scalars_df, scalars_pandas_df = scalars_dfs - bf_result = scalars_df[col].fillna(fill_value).to_pandas() - pd_result = scalars_pandas_df[col].fillna(fill_value) - - pd.testing.assert_frame_equal(bf_result, pd_result, check_dtype=False) - - -@pytest.mark.skip("b/436316698 unit test failed for python 3.12") -def test_df_ffill(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - bf_result = scalars_df[["int64_col", "float64_col"]].ffill(limit=1).to_pandas() - pd_result = scalars_pandas_df[["int64_col", "float64_col"]].ffill(limit=1) - - pandas.testing.assert_frame_equal(bf_result, pd_result) - - -def test_df_bfill(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - bf_result = scalars_df[["int64_col", "float64_col"]].bfill().to_pandas() - pd_result = scalars_pandas_df[["int64_col", "float64_col"]].bfill() - - pandas.testing.assert_frame_equal(bf_result, pd_result) - - -def test_apply_series_series_callable( - scalars_df_index, - scalars_pandas_df_index, -): - columns = ["int64_too", "int64_col"] - - def foo(series, arg1, arg2, *, kwarg1=0, kwarg2=0): - return series**2 + (arg1 * arg2 % 4) + (kwarg1 * kwarg2 % 7) - - bf_result = ( - scalars_df_index[columns] - .apply(foo, args=(33, 61), kwarg1=52, kwarg2=21) - .to_pandas() - ) - - pd_result = scalars_pandas_df_index[columns].apply( - foo, args=(33, 61), kwarg1=52, kwarg2=21 - ) - - pandas.testing.assert_frame_equal(bf_result, pd_result) - - -def test_apply_series_listlike_callable( - scalars_df_index, - scalars_pandas_df_index, -): - columns = ["int64_too", "int64_col"] - bf_result = ( - scalars_df_index[columns].apply(lambda x: [len(x), x.min(), 24]).to_pandas() - ) - - pd_result = scalars_pandas_df_index[columns].apply(lambda x: [len(x), x.min(), 24]) - - # Convert default pandas dtypes `int64` to match BigQuery DataFrames dtypes. - pd_result.index = pd_result.index.astype("Int64") - pd_result = pd_result.astype("Int64") - pandas.testing.assert_frame_equal(bf_result, pd_result) - - -def test_apply_series_scalar_callable( - scalars_df_index, - scalars_pandas_df_index, -): - columns = ["int64_too", "int64_col"] - bf_result = scalars_df_index[columns].apply(lambda x: x.sum()) - - pd_result = scalars_pandas_df_index[columns].apply(lambda x: x.sum()) - - pandas.testing.assert_series_equal(bf_result, pd_result) - - -def test_df_map_with_udf(session): - df = bpd.DataFrame({"x": [1, 2, None, 4], "y": [5, None, 7, 8]}, dtype="Int64") - - @session.udf() - def foo(row: pd.Series) -> int: - if pd.isna(row["x"]) or pd.isna(row["y"]): - return -1 - return int(row["x"] * row["y"]) - - bf_result = df.apply(foo, axis=1).to_pandas() - pd_result = pd.Series([5, -1, -1, 32]) - assert_series_equal(bf_result, pd_result, check_dtype=False) - - -def test_df_apply_complex_udf(session): - df = bpd.DataFrame( - {"x": [1, 2, 3], "y": ["a", "b", "c"]}, - index=["row0", "row1", "row2"], - ) - - @session.udf() - def foo(row: pd.Series) -> str: - idx = str(row.name) - items_str = ";".join(f"{k}={v}" for k, v in row.items()) - return f"({idx}) -> {items_str}" - - bf_result = df.apply(foo, axis=1).to_pandas() - - pd_df = pd.DataFrame( - {"x": [1, 2, 3], "y": ["a", "b", "c"]}, - index=["row0", "row1", "row2"], - ) - - def pd_foo(row): - idx = str(row.name) - items_str = ";".join(f"{k}={v}" for k, v in row.items()) - return f"({idx}) -> {items_str}" - - pd_result = pd_df.apply(pd_foo, axis=1) - - assert_series_equal(bf_result, pd_result, check_dtype=False, check_index_type=False) - - -def test_df_pipe( - scalars_df_index, - scalars_pandas_df_index, -): - columns = ["int64_too", "int64_col"] - - def foo(x: int, y: int, df): - return (df + x) % y - - bf_result = ( - scalars_df_index[columns] - .pipe((foo, "df"), x=7, y=9) - .pipe(lambda x: x**2) - .to_pandas() - ) - - pd_result = ( - scalars_pandas_df_index[columns] - .pipe((foo, "df"), x=7, y=9) - .pipe(lambda x: x**2) - ) - - pandas.testing.assert_frame_equal(bf_result, pd_result) - - -def test_df_keys( - scalars_df_index, - scalars_pandas_df_index, -): - pandas.testing.assert_index_equal( - scalars_df_index.keys(), scalars_pandas_df_index.keys() - ) - - -def test_df_iter( - scalars_df_index, - scalars_pandas_df_index, -): - for bf_i, df_i in zip(scalars_df_index, scalars_pandas_df_index): - assert bf_i == df_i - - -def test_iterrows( - scalars_df_index, - scalars_pandas_df_index, -): - # TODO: supply a reason why this isn't compatible with pandas 1.x - pytest.importorskip("pandas", minversion="2.0.0") - scalars_df_index = scalars_df_index.add_suffix("_suffix", axis=1) - scalars_pandas_df_index = scalars_pandas_df_index.add_suffix("_suffix", axis=1) - for (bf_index, bf_series), (pd_index, pd_series) in zip( - scalars_df_index.iterrows(), scalars_pandas_df_index.iterrows() - ): - assert bf_index == pd_index - pandas.testing.assert_series_equal(bf_series, pd_series) - - -@pytest.mark.parametrize( - ( - "index", - "name", - ), - [ - ( - True, - "my_df", - ), - (False, None), - ], -) -def test_itertuples(scalars_df_index, index, name): - # Numeric has slightly different representation as a result of conversions. - bf_tuples = scalars_df_index.itertuples(index, name) - pd_tuples = scalars_df_index.to_pandas().itertuples(index, name) - for bf_tuple, pd_tuple in zip(bf_tuples, pd_tuples): - assert bf_tuple == pd_tuple - - -def test_df_cross_merge(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - left_columns = ["int64_col", "float64_col", "rowindex_2"] - right_columns = ["int64_col", "bool_col", "string_col", "rowindex_2"] - - left = scalars_df[left_columns] - # Offset the rows somewhat so that outer join can have an effect. - right = scalars_df[right_columns].assign(rowindex_2=scalars_df["rowindex_2"] + 2) - - bf_result = left.merge(right, "cross").to_pandas() - - pd_result = scalars_pandas_df[left_columns].merge( - scalars_pandas_df[right_columns].assign( - rowindex_2=scalars_pandas_df["rowindex_2"] + 2 - ), - "cross", - ) - pd.testing.assert_frame_equal(bf_result, pd_result, check_index_type=False) - - -@pytest.mark.parametrize( - ("merge_how",), - [ - ("inner",), - ("outer",), - ("left",), - ("right",), - ], -) -def test_df_merge(scalars_dfs, merge_how): - scalars_df, scalars_pandas_df = scalars_dfs - on = "rowindex_2" - left_columns = ["int64_col", "float64_col", "rowindex_2"] - right_columns = ["int64_col", "bool_col", "string_col", "rowindex_2"] - - left = scalars_df[left_columns] - # Offset the rows somewhat so that outer join can have an effect. - right = scalars_df[right_columns].assign(rowindex_2=scalars_df["rowindex_2"] + 2) - - df = left.merge(right, merge_how, on, sort=True) - bf_result = df.to_pandas() - - pd_result = scalars_pandas_df[left_columns].merge( - scalars_pandas_df[right_columns].assign( - rowindex_2=scalars_pandas_df["rowindex_2"] + 2 - ), - merge_how, - on, - sort=True, - ) - - assert_frame_equal(bf_result, pd_result, ignore_order=True, check_index_type=False) - - -@pytest.mark.parametrize( - ("left_on", "right_on"), - [ - (["int64_col", "rowindex_2"], ["int64_col", "rowindex_2"]), - (["rowindex_2", "int64_col"], ["int64_col", "rowindex_2"]), - # Polars engine is currently strict on join key types - # (["rowindex_2", "float64_col"], ["int64_col", "rowindex_2"]), - ], -) -def test_df_merge_multi_key(scalars_dfs, left_on, right_on): - scalars_df, scalars_pandas_df = scalars_dfs - left_columns = ["int64_col", "float64_col", "rowindex_2"] - right_columns = ["int64_col", "bool_col", "string_col", "rowindex_2"] - - left = scalars_df[left_columns] - # Offset the rows somewhat so that outer join can have an effect. - right = scalars_df[right_columns].assign(rowindex_2=scalars_df["rowindex_2"] + 2) - - df = left.merge(right, "outer", left_on=left_on, right_on=right_on, sort=True) - bf_result = df.to_pandas() - - pd_result = scalars_pandas_df[left_columns].merge( - scalars_pandas_df[right_columns].assign( - rowindex_2=scalars_pandas_df["rowindex_2"] + 2 - ), - "outer", - left_on=left_on, - right_on=right_on, - sort=True, - ) - - assert_frame_equal(bf_result, pd_result, ignore_order=True, check_index_type=False) - - -@pytest.mark.parametrize( - ("merge_how",), - [ - ("inner",), - ("outer",), - ("left",), - ("right",), - ], -) -def test_merge_custom_col_name(scalars_dfs, merge_how): - scalars_df, scalars_pandas_df = scalars_dfs - left_columns = ["int64_col", "float64_col"] - right_columns = ["int64_col", "bool_col", "string_col"] - on = "int64_col" - rename_columns = {"float64_col": "f64_col"} - - left = scalars_df[left_columns] - left = left.rename(columns=rename_columns) - right = scalars_df[right_columns] - df = left.merge(right, merge_how, on, sort=True) - bf_result = df.to_pandas() - - pandas_left_df = scalars_pandas_df[left_columns] - pandas_left_df = pandas_left_df.rename(columns=rename_columns) - pandas_right_df = scalars_pandas_df[right_columns] - pd_result = pandas_left_df.merge(pandas_right_df, merge_how, on, sort=True) - - assert_frame_equal(bf_result, pd_result, ignore_order=True, check_index_type=False) - - -@pytest.mark.parametrize( - ("merge_how",), - [ - ("inner",), - ("outer",), - ("left",), - ("right",), - ], -) -def test_merge_left_on_right_on(scalars_dfs, merge_how): - scalars_df, scalars_pandas_df = scalars_dfs - left_columns = ["int64_col", "float64_col", "int64_too"] - right_columns = ["int64_col", "bool_col", "string_col", "rowindex_2"] - - left = scalars_df[left_columns] - right = scalars_df[right_columns] - - df = left.merge( - right, merge_how, left_on="int64_too", right_on="rowindex_2", sort=True - ) - bf_result = df.to_pandas() - - pd_result = scalars_pandas_df[left_columns].merge( - scalars_pandas_df[right_columns], - merge_how, - left_on="int64_too", - right_on="rowindex_2", - sort=True, - ) - - assert_frame_equal(bf_result, pd_result, ignore_order=True, check_index_type=False) - - -def test_shape(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - bf_result = scalars_df.shape - pd_result = scalars_pandas_df.shape - - assert bf_result == pd_result - - -def test_len(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - bf_result = len(scalars_df) - pd_result = len(scalars_pandas_df) - - assert bf_result == pd_result - - -@pytest.mark.parametrize( - ("n_rows",), - [ - (50,), - (10000,), - ], -) -def test_df_len_local(session, n_rows): - assert ( - len( - session.read_pandas( - pd.DataFrame(np.random.randint(1, 7, n_rows), columns=["one"]), - ) - ) - == n_rows - ) - - -def test_size(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - bf_result = scalars_df.size - pd_result = scalars_pandas_df.size - - assert bf_result == pd_result - - -def test_ndim(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - bf_result = scalars_df.ndim - pd_result = scalars_pandas_df.ndim - - assert bf_result == pd_result - - -def test_empty_false(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - - bf_result = scalars_df.empty - pd_result = scalars_pandas_df.empty - - assert bf_result == pd_result - - -def test_empty_true_column_filter(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - - bf_result = scalars_df[[]].empty - pd_result = scalars_pandas_df[[]].empty - - assert bf_result == pd_result - - -def test_empty_true_row_filter(scalars_dfs: Tuple[dataframe.DataFrame, pd.DataFrame]): - scalars_df, scalars_pandas_df = scalars_dfs - bf_bool: series.Series = typing.cast(series.Series, scalars_df["bool_col"]) - pd_bool: pd.Series = scalars_pandas_df["bool_col"] - bf_false = bf_bool.notna() & (bf_bool != bf_bool) - pd_false = pd_bool.notna() & (pd_bool != pd_bool) - - bf_result = scalars_df[bf_false].empty - pd_result = scalars_pandas_df[pd_false].empty - - assert pd_result - assert bf_result == pd_result - - -def test_empty_true_memtable(session: bigframes.Session): - bf_df = dataframe.DataFrame(session=session) - pd_df = pd.DataFrame() - - bf_result = bf_df.empty - pd_result = pd_df.empty - - assert pd_result - assert bf_result == pd_result - - -@pytest.mark.parametrize( - ("drop",), - ((True,), (False,)), -) -def test_reset_index(scalars_df_index, scalars_pandas_df_index, drop): - df = scalars_df_index.reset_index(drop=drop) - assert df.index.name is None - - bf_result = df.to_pandas() - pd_result = scalars_pandas_df_index.reset_index(drop=drop) - - # Pandas uses int64 instead of Int64 (nullable) dtype. - pd_result.index = pd_result.index.astype(pd.Int64Dtype()) - - # reset_index should maintain the original ordering. - pandas.testing.assert_frame_equal(bf_result, pd_result) - - -def test_reset_index_then_filter( - scalars_df_index, - scalars_pandas_df_index, -): - bf_filter = scalars_df_index["bool_col"].fillna(True) - bf_df = scalars_df_index.reset_index()[bf_filter] - bf_result = bf_df.to_pandas() - pd_filter = scalars_pandas_df_index["bool_col"].fillna(True) - pd_result = scalars_pandas_df_index.reset_index()[pd_filter] - - # Pandas uses int64 instead of Int64 (nullable) dtype. - pd_result.index = pd_result.index.astype(pd.Int64Dtype()) - - # reset_index should maintain the original ordering and index keys - # post-filter will have gaps. - pandas.testing.assert_frame_equal(bf_result, pd_result) - - -def test_reset_index_with_unnamed_index( - scalars_df_index, - scalars_pandas_df_index, -): - scalars_df_index = scalars_df_index.copy() - scalars_pandas_df_index = scalars_pandas_df_index.copy() - - scalars_df_index.index.name = None - scalars_pandas_df_index.index.name = None - df = scalars_df_index.reset_index(drop=False) - assert df.index.name is None - - # reset_index(drop=False) creates a new column "index". - assert df.columns[0] == "index" - - bf_result = df.to_pandas() - pd_result = scalars_pandas_df_index.reset_index(drop=False) - - # Pandas uses int64 instead of Int64 (nullable) dtype. - pd_result.index = pd_result.index.astype(pd.Int64Dtype()) - - # reset_index should maintain the original ordering. - pandas.testing.assert_frame_equal(bf_result, pd_result) - - -def test_reset_index_with_unnamed_multiindex(session): - bf_df = dataframe.DataFrame( - ([1, 2, 3], [2, 5, 7]), - index=pd.MultiIndex.from_tuples([("a", "aa"), ("a", "aa")]), - session=session, - ) - pd_df = pd.DataFrame( - ([1, 2, 3], [2, 5, 7]), - index=pd.MultiIndex.from_tuples([("a", "aa"), ("a", "aa")]), - ) - - bf_df = bf_df.reset_index() - pd_df = pd_df.reset_index() - - assert pd_df.columns[0] == "level_0" - assert bf_df.columns[0] == "level_0" - assert pd_df.columns[1] == "level_1" - assert bf_df.columns[1] == "level_1" - - -def test_reset_index_with_unnamed_index_and_index_column( - scalars_df_index, - scalars_pandas_df_index, -): - scalars_df_index = scalars_df_index.copy() - scalars_pandas_df_index = scalars_pandas_df_index.copy() - - scalars_df_index.index.name = None - scalars_pandas_df_index.index.name = None - df = scalars_df_index.assign(index=scalars_df_index["int64_col"]).reset_index( - drop=False - ) - assert df.index.name is None - - # reset_index(drop=False) creates a new column "level_0" if the "index" column already exists. - assert df.columns[0] == "level_0" - - bf_result = df.to_pandas() - pd_result = scalars_pandas_df_index.assign( - index=scalars_pandas_df_index["int64_col"] - ).reset_index(drop=False) - - # Pandas uses int64 instead of Int64 (nullable) dtype. - pd_result.index = pd_result.index.astype(pd.Int64Dtype()) - - # reset_index should maintain the original ordering. - pandas.testing.assert_frame_equal(bf_result, pd_result) - - -@pytest.mark.parametrize( - ("drop",), - ( - (True,), - (False,), - ), -) -@pytest.mark.parametrize( - ("append",), - ( - (True,), - (False,), - ), -) -@pytest.mark.parametrize( - ("index_column",), - (("int64_too",), ("string_col",), ("timestamp_col",)), -) -def test_set_index(scalars_dfs, index_column, drop, append): - scalars_df, scalars_pandas_df = scalars_dfs - df = scalars_df.set_index(index_column, append=append, drop=drop) - bf_result = df.to_pandas() - pd_result = scalars_pandas_df.set_index(index_column, append=append, drop=drop) - - # Sort to disambiguate when there are duplicate index labels. - # Note: Doesn't use assert_pandas_df_equal_ignore_ordering because we get - # "ValueError: 'timestamp_col' is both an index level and a column label, - # which is ambiguous" when trying to sort by a column with the same name as - # the index. - bf_result = bf_result.sort_values("rowindex_2") - pd_result = pd_result.sort_values("rowindex_2") - - pandas.testing.assert_frame_equal(bf_result, pd_result) - - -def test_set_index_key_error(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - with pytest.raises(KeyError): - scalars_pandas_df.set_index(["not_a_col"]) - with pytest.raises(KeyError): - scalars_df.set_index(["not_a_col"]) - - -@pytest.mark.parametrize( - ("ascending",), - ((True,), (False,)), -) -@pytest.mark.parametrize( - ("na_position",), - (("first",), ("last",)), -) -@pytest.mark.parametrize( - ("axis",), - ((0,), ("columns",)), -) -def test_sort_index(scalars_dfs, ascending, na_position, axis): - index_column = "int64_col" - scalars_df, scalars_pandas_df = scalars_dfs - df = scalars_df.set_index(index_column) - bf_result = df.sort_index( - ascending=ascending, na_position=na_position, axis=axis - ).to_pandas() - pd_result = scalars_pandas_df.set_index(index_column).sort_index( - ascending=ascending, na_position=na_position, axis=axis - ) - pandas.testing.assert_frame_equal(bf_result, pd_result) - - -def test_dataframe_sort_index_inplace(scalars_dfs): - index_column = "int64_col" - scalars_df, scalars_pandas_df = scalars_dfs - df = scalars_df.copy().set_index(index_column) - df.sort_index(ascending=False, inplace=True) - bf_result = df.to_pandas() - - pd_result = scalars_pandas_df.set_index(index_column).sort_index(ascending=False) - pandas.testing.assert_frame_equal(bf_result, pd_result) - - -def test_df_abs(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - columns = ["int64_col", "int64_too", "float64_col"] - - bf_result = scalars_df[columns].abs() - pd_result = scalars_pandas_df[columns].abs() - - assert_dfs_equivalent(pd_result, bf_result) - - -def test_df_pos(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - bf_result = (+scalars_df[["int64_col", "numeric_col"]]).to_pandas() - pd_result = +scalars_pandas_df[["int64_col", "numeric_col"]] - - assert_frame_equal(pd_result, bf_result) - - -def test_df_neg(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - bf_result = (-scalars_df[["int64_col", "numeric_col"]]).to_pandas() - pd_result = -scalars_pandas_df[["int64_col", "numeric_col"]] - - assert_frame_equal(pd_result, bf_result) - - -def test_df_invert(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - columns = ["int64_col", "bool_col"] - - bf_result = (~scalars_df[columns]).to_pandas() - pd_result = ~scalars_pandas_df[columns] - - assert_frame_equal(bf_result, pd_result) - - -def test_df_isnull(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - - columns = ["int64_col", "int64_too", "string_col", "bool_col"] - bf_result = scalars_df[columns].isnull().to_pandas() - pd_result = scalars_pandas_df[columns].isnull() - - # One of dtype mismatches to be documented. Here, the `bf_result.dtype` is - # `BooleanDtype` but the `pd_result.dtype` is `bool`. - pd_result["int64_col"] = pd_result["int64_col"].astype(pd.BooleanDtype()) - pd_result["int64_too"] = pd_result["int64_too"].astype(pd.BooleanDtype()) - pd_result["string_col"] = pd_result["string_col"].astype(pd.BooleanDtype()) - pd_result["bool_col"] = pd_result["bool_col"].astype(pd.BooleanDtype()) - - assert_frame_equal(bf_result, pd_result) - - -def test_df_notnull(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - - columns = ["int64_col", "int64_too", "string_col", "bool_col"] - bf_result = scalars_df[columns].notnull().to_pandas() - pd_result = scalars_pandas_df[columns].notnull() - - # One of dtype mismatches to be documented. Here, the `bf_result.dtype` is - # `BooleanDtype` but the `pd_result.dtype` is `bool`. - pd_result["int64_col"] = pd_result["int64_col"].astype(pd.BooleanDtype()) - pd_result["int64_too"] = pd_result["int64_too"].astype(pd.BooleanDtype()) - pd_result["string_col"] = pd_result["string_col"].astype(pd.BooleanDtype()) - pd_result["bool_col"] = pd_result["bool_col"].astype(pd.BooleanDtype()) - - assert_frame_equal(bf_result, pd_result) - - -@pytest.mark.parametrize( - ("left_labels", "right_labels", "overwrite", "fill_value"), - [ - (["a", "b", "c"], ["c", "a", "b"], True, None), - (["a", "b", "c"], ["c", "a", "b"], False, None), - (["a", "b", "c"], ["a", "b", "c"], False, 2), - ], - ids=[ - "one_one_match_overwrite", - "one_one_match_no_overwrite", - "exact_match", - ], -) -def test_combine( - scalars_df_index, - scalars_df_2_index, - scalars_pandas_df_index, - left_labels, - right_labels, - overwrite, - fill_value, -): - if pd.__version__.startswith("1."): - pytest.skip("pd.NA vs NaN not handled well in pandas 1.x.") - columns = ["int64_too", "int64_col", "float64_col"] - - bf_df_a = scalars_df_index[columns] - bf_df_a.columns = left_labels - bf_df_b = scalars_df_2_index[columns] - bf_df_b.columns = right_labels - bf_result = bf_df_a.combine( - bf_df_b, - lambda x, y: x**2 + 2 * x * y + y**2, - overwrite=overwrite, - fill_value=fill_value, - ).to_pandas() - - pd_df_a = scalars_pandas_df_index[columns] - pd_df_a.columns = left_labels - pd_df_b = scalars_pandas_df_index[columns] - pd_df_b.columns = right_labels - pd_result = pd_df_a.combine( - pd_df_b, - lambda x, y: x**2 + 2 * x * y + y**2, - overwrite=overwrite, - fill_value=fill_value, - ) - - # Some dtype inconsistency for all-NULL columns - pd.testing.assert_frame_equal(bf_result, pd_result, check_dtype=False) - - -@pytest.mark.parametrize( - ("overwrite", "filter_func"), - [ - (True, None), - (False, None), - (True, lambda x: x.isna() | (x % 2 == 0)), - ], - ids=[ - "default", - "overwritefalse", - "customfilter", - ], -) -def test_df_update(overwrite, filter_func): - if pd.__version__.startswith("1."): - pytest.skip("dtype handled differently in pandas 1.x.") - - index1: pandas.Index = pandas.Index([1, 2, 3, 4], dtype="Int64") - - index2: pandas.Index = pandas.Index([1, 2, 4, 5], dtype="Int64") - pd_df1 = pandas.DataFrame( - {"a": [1, None, 3, 4], "b": [5, 6, None, 8]}, dtype="Int64", index=index1 - ) - pd_df2 = pandas.DataFrame( - {"a": [None, 20, 30, 40], "c": [90, None, 110, 120]}, - dtype="Int64", - index=index2, - ) - - bf_df1 = dataframe.DataFrame(pd_df1) - bf_df2 = dataframe.DataFrame(pd_df2) - - bf_df1.update(bf_df2, overwrite=overwrite, filter_func=filter_func) - pd_df1.update(pd_df2, overwrite=overwrite, filter_func=filter_func) - - pd.testing.assert_frame_equal(bf_df1.to_pandas(), pd_df1) - - -def test_df_idxmin(): - pd_df = pd.DataFrame( - {"a": [1, 2, 3], "b": [7, None, 3], "c": [4, 4, 4]}, index=["x", "y", "z"] - ) - bf_df = dataframe.DataFrame(pd_df) - - bf_result = bf_df.idxmin().to_pandas() - pd_result = pd_df.idxmin() - - pd.testing.assert_series_equal( - bf_result, pd_result, check_index_type=False, check_dtype=False - ) - - -def test_df_idxmax(): - pd_df = pd.DataFrame( - {"a": [1, 2, 3], "b": [7, None, 3], "c": [4, 4, 4]}, index=["x", "y", "z"] - ) - bf_df = dataframe.DataFrame(pd_df) - - bf_result = bf_df.idxmax().to_pandas() - pd_result = pd_df.idxmax() - - pd.testing.assert_series_equal( - bf_result, pd_result, check_index_type=False, check_dtype=False - ) - - -@pytest.mark.parametrize( - ("join", "axis"), - [ - ("outer", None), - ("outer", 0), - ("outer", 1), - ("left", 0), - ("right", 1), - ("inner", None), - ("inner", 1), - ], -) -def test_df_align(join, axis): - index1: pandas.Index = pandas.Index([1, 2, 3, 4], dtype="Int64") - - index2: pandas.Index = pandas.Index([1, 2, 4, 5], dtype="Int64") - pd_df1 = pandas.DataFrame( - {"a": [1, None, 3, 4], "b": [5, 6, None, 8]}, dtype="Int64", index=index1 - ) - pd_df2 = pandas.DataFrame( - {"a": [None, 20, 30, 40], "c": [90, None, 110, 120]}, - dtype="Int64", - index=index2, - ) - - bf_df1 = dataframe.DataFrame(pd_df1) - bf_df2 = dataframe.DataFrame(pd_df2) - - bf_result1, bf_result2 = bf_df1.align(bf_df2, join=join, axis=axis) - pd_result1, pd_result2 = pd_df1.align(pd_df2, join=join, axis=axis) - - # Don't check dtype as pandas does unnecessary float conversion - assert isinstance(bf_result1, dataframe.DataFrame) and isinstance( - bf_result2, dataframe.DataFrame - ) - pd.testing.assert_frame_equal(bf_result1.to_pandas(), pd_result1, check_dtype=False) - pd.testing.assert_frame_equal(bf_result2.to_pandas(), pd_result2, check_dtype=False) - - -def test_combine_first( - scalars_df_index, - scalars_df_2_index, - scalars_pandas_df_index, -): - if pd.__version__.startswith("1."): - pytest.skip("pd.NA vs NaN not handled well in pandas 1.x.") - columns = ["int64_too", "int64_col", "float64_col"] - - bf_df_a = scalars_df_index[columns].iloc[0:6] - bf_df_a.columns = ["a", "b", "c"] - bf_df_b = scalars_df_2_index[columns].iloc[2:8] - bf_df_b.columns = ["b", "a", "d"] - bf_result = bf_df_a.combine_first(bf_df_b).to_pandas() - - pd_df_a = scalars_pandas_df_index[columns].iloc[0:6] - pd_df_a.columns = ["a", "b", "c"] - pd_df_b = scalars_pandas_df_index[columns].iloc[2:8] - pd_df_b.columns = ["b", "a", "d"] - pd_result = pd_df_a.combine_first(pd_df_b) - - # Some dtype inconsistency for all-NULL columns - pd.testing.assert_frame_equal(bf_result, pd_result, check_dtype=False) - - -def test_df_corr_w_invalid_parameters(scalars_dfs): - columns = ["int64_too", "int64_col", "float64_col"] - scalars_df, _ = scalars_dfs - - with pytest.raises(NotImplementedError): - scalars_df[columns].corr(method="kendall") - - with pytest.raises(NotImplementedError): - scalars_df[columns].corr(min_periods=1) - - -@pytest.mark.parametrize( - ("columns", "numeric_only"), - [ - (["bool_col", "int64_col", "float64_col"], True), - (["bool_col", "int64_col", "float64_col"], False), - (["bool_col", "int64_col", "float64_col", "string_col"], True), - pytest.param( - ["bool_col", "int64_col", "float64_col", "string_col"], - False, - marks=pytest.mark.xfail( - raises=NotImplementedError, - ), - ), - ], -) -def test_cov_w_numeric_only(scalars_dfs, columns, numeric_only): - scalars_df, scalars_pandas_df = scalars_dfs - bf_result = scalars_df[columns].cov(numeric_only=numeric_only).to_pandas() - pd_result = scalars_pandas_df[columns].cov(numeric_only=numeric_only) - # BigFrames and Pandas differ in their data type handling: - # - Column types: BigFrames uses Float64, Pandas uses float64. - # - Index types: BigFrames uses strign, Pandas uses object. - pd.testing.assert_index_equal(bf_result.columns, pd_result.columns) - # Only check row order in ordered mode. - pd.testing.assert_frame_equal( - bf_result, - pd_result, - check_dtype=False, - check_index_type=False, - check_like=~scalars_df._block.session._strictly_ordered, - ) - - -def test_df_corrwith_df(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - - l_cols = ["int64_col", "float64_col", "int64_too"] - r_cols = ["int64_too", "float64_col"] - - bf_result = scalars_df[l_cols].corrwith(scalars_df[r_cols]).to_pandas() - pd_result = scalars_pandas_df[l_cols].corrwith(scalars_pandas_df[r_cols]) - - # BigFrames and Pandas differ in their data type handling: - # - Column types: BigFrames uses Float64, Pandas uses float64. - # - Index types: BigFrames uses strign, Pandas uses object. - pd.testing.assert_series_equal( - bf_result, pd_result, check_dtype=False, check_index_type=False - ) - - -def test_df_corrwith_df_numeric_only(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - - l_cols = ["int64_col", "float64_col", "int64_too", "string_col"] - r_cols = ["int64_too", "float64_col", "bool_col"] - - bf_result = ( - scalars_df[l_cols].corrwith(scalars_df[r_cols], numeric_only=True).to_pandas() - ) - pd_result = scalars_pandas_df[l_cols].corrwith( - scalars_pandas_df[r_cols], numeric_only=True - ) - - # BigFrames and Pandas differ in their data type handling: - # - Column types: BigFrames uses Float64, Pandas uses float64. - # - Index types: BigFrames uses strign, Pandas uses object. - pd.testing.assert_series_equal( - bf_result, pd_result, check_dtype=False, check_index_type=False - ) - - -def test_df_corrwith_df_non_numeric_error(scalars_dfs): - scalars_df, _ = scalars_dfs - - l_cols = ["int64_col", "float64_col", "int64_too", "string_col"] - r_cols = ["int64_too", "float64_col", "bool_col"] - - with pytest.raises(NotImplementedError): - scalars_df[l_cols].corrwith(scalars_df[r_cols], numeric_only=False) - - -def test_df_corrwith_series(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 - - l_cols = ["int64_col", "float64_col", "int64_too"] - r_col = "float64_col" - - bf_result = scalars_df[l_cols].corrwith(scalars_df[r_col]).to_pandas() - pd_result = scalars_pandas_df[l_cols].corrwith(scalars_pandas_df[r_col]) - - # BigFrames and Pandas differ in their data type handling: - # - Column types: BigFrames uses Float64, Pandas uses float64. - # - Index types: BigFrames uses strign, Pandas uses object. - pd.testing.assert_series_equal( - bf_result, pd_result, check_dtype=False, check_index_type=False - ) - - -@pytest.mark.parametrize( - ("op"), - [ - operator.add, - operator.sub, - operator.mul, - operator.truediv, - operator.floordiv, - operator.eq, - operator.ne, - operator.gt, - operator.ge, - operator.lt, - operator.le, - ], - ids=[ - "add", - "subtract", - "multiply", - "true_divide", - "floor_divide", - "eq", - "ne", - "gt", - "ge", - "lt", - "le", - ], -) -# TODO(garrettwu): deal with NA values -@pytest.mark.parametrize(("other_scalar"), [1, 2.5, 0, 0.0]) -@pytest.mark.parametrize(("reverse_operands"), [True, False]) -def test_scalar_binop(scalars_dfs, op, other_scalar, reverse_operands): - scalars_df, scalars_pandas_df = scalars_dfs - columns = ["int64_col", "float64_col"] - - maybe_reversed_op = (lambda x, y: op(y, x)) if reverse_operands else op - - bf_result = maybe_reversed_op(scalars_df[columns], other_scalar).to_pandas() - pd_result = maybe_reversed_op(scalars_pandas_df[columns], other_scalar) - - assert_frame_equal(bf_result, pd_result) - - -@pytest.mark.parametrize(("other_scalar"), [1, -2]) -def test_mod(scalars_dfs, other_scalar): - # Zero case excluded as pandas produces 0 result for Int64 inputs rather than NA/NaN. - # This is likely a pandas bug as mod 0 is undefined in other dtypes, and most programming languages. - scalars_df, scalars_pandas_df = scalars_dfs - - bf_result = (scalars_df[["int64_col", "int64_too"]] % other_scalar).to_pandas() - pd_result = scalars_pandas_df[["int64_col", "int64_too"]] % other_scalar - - assert_frame_equal(bf_result, pd_result) - - -def test_scalar_binop_str_exception(scalars_dfs): - scalars_df, _ = scalars_dfs - columns = ["string_col"] - with pytest.raises(TypeError, match="Cannot add dtypes"): - (scalars_df[columns] + 1).to_pandas() - - -@pytest.mark.parametrize( - ("op"), - [ - (lambda x, y: x.add(y, axis="index")), - (lambda x, y: x.radd(y, axis="index")), - (lambda x, y: x.sub(y, axis="index")), - (lambda x, y: x.rsub(y, axis="index")), - (lambda x, y: x.mul(y, axis="index")), - (lambda x, y: x.rmul(y, axis="index")), - (lambda x, y: x.truediv(y, axis="index")), - (lambda x, y: x.rtruediv(y, axis="index")), - (lambda x, y: x.floordiv(y, axis="index")), - (lambda x, y: x.floordiv(y, axis="index")), - (lambda x, y: x.gt(y, axis="index")), - (lambda x, y: x.ge(y, axis="index")), - (lambda x, y: x.lt(y, axis="index")), - (lambda x, y: x.le(y, axis="index")), - ], - ids=[ - "add", - "radd", - "sub", - "rsub", - "mul", - "rmul", - "truediv", - "rtruediv", - "floordiv", - "rfloordiv", - "gt", - "ge", - "lt", - "le", - ], -) -def test_series_binop_axis_index( - scalars_dfs, - op, -): - scalars_df, scalars_pandas_df = scalars_dfs - df_columns = ["int64_col", "float64_col"] - series_column = "int64_too" - - bf_result = op(scalars_df[df_columns], scalars_df[series_column]).to_pandas() - pd_result = op(scalars_pandas_df[df_columns], scalars_pandas_df[series_column]) - - assert_frame_equal(bf_result, pd_result) - - -@pytest.mark.parametrize( - ("input"), - [ - ((1000, 2000, 3000)), - (pd.Index([1000, 2000, 3000])), - (pd.Series((1000, 2000), index=["int64_too", "float64_col"])), - ], - ids=[ - "tuple", - "pd_index", - "pd_series", - ], -) -def test_listlike_binop_axis_1_in_memory_data(scalars_dfs, input): - # 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 - - df_columns = ["int64_col", "float64_col", "int64_too"] - - bf_result = scalars_df[df_columns].add(input, axis=1).to_pandas() - if hasattr(input, "to_pandas"): - input = input.to_pandas() - pd_result = scalars_pandas_df[df_columns].add(input, axis=1) - - assert_frame_equal(bf_result, pd_result, check_dtype=False) - - -def test_df_reverse_binop_pandas(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 - - pd_series = pd.Series([100, 200, 300]) - - df_columns = ["int64_col", "float64_col", "int64_too"] - - bf_result = pd_series + scalars_df[df_columns].to_pandas() - pd_result = pd_series + scalars_pandas_df[df_columns] - - assert_frame_equal(bf_result, pd_result, check_dtype=False) - - -def test_listlike_binop_axis_1_bf_index(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - - df_columns = ["int64_col", "float64_col", "int64_too"] - - bf_result = ( - scalars_df[df_columns] - .add(bf_indexes.Index([1000, 2000, 3000]), axis=1) - .to_pandas() - ) - pd_result = scalars_pandas_df[df_columns].add(pd.Index([1000, 2000, 3000]), axis=1) - - assert_frame_equal(bf_result, pd_result, check_dtype=False) - - -def test_binop_with_self_aggregate(session, scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - - df_columns = ["int64_col", "float64_col", "int64_too"] - - bf_df = scalars_df[df_columns] - bf_result = (bf_df - bf_df.mean()).to_pandas() - - pd_df = scalars_pandas_df[df_columns] - pd_result = pd_df - pd_df.mean() - - assert_frame_equal(bf_result, pd_result, check_dtype=False) - - -@pytest.mark.parametrize( - ("left_labels", "right_labels"), - [ - (["a", "a", "b"], ["c", "c", "d"]), - (["a", "b", "c"], ["c", "a", "b"]), - (["a", "c", "c"], ["c", "a", "c"]), - (["a", "b", "c"], ["a", "b", "c"]), - ], - ids=[ - "no_overlap", - "one_one_match", - "multi_match", - "exact_match", - ], -) -def test_binop_df_df_binary_op( - scalars_df_index, - scalars_df_2_index, - scalars_pandas_df_index, - left_labels, - right_labels, -): - if pd.__version__.startswith("1."): - pytest.skip("pd.NA vs NaN not handled well in pandas 1.x.") - columns = ["int64_too", "int64_col", "float64_col"] - - bf_df_a = scalars_df_index[columns] - bf_df_a.columns = left_labels - bf_df_b = scalars_df_2_index[columns] - bf_df_b.columns = right_labels - bf_result = (bf_df_a - bf_df_b).to_pandas() - - pd_df_a = scalars_pandas_df_index[columns] - pd_df_a.columns = left_labels - pd_df_b = scalars_pandas_df_index[columns] - pd_df_b.columns = right_labels - pd_result = pd_df_a - pd_df_b - - # Some dtype inconsistency for all-NULL columns - pd.testing.assert_frame_equal(bf_result, pd_result, check_dtype=False) - - -# Differnt table will only work for explicit index, since default index orders are arbitrary. -@pytest.mark.parametrize( - ("ordered"), - [ - (True), - (False), - ], -) -def test_series_binop_add_different_table( - scalars_df_index, scalars_pandas_df_index, scalars_df_2_index, ordered -): - df_columns = ["int64_col", "float64_col"] - series_column = "int64_too" - - bf_result = ( - scalars_df_index[df_columns] - .add(scalars_df_2_index[series_column], axis="index") - .to_pandas(ordered=ordered) - ) - pd_result = scalars_pandas_df_index[df_columns].add( - scalars_pandas_df_index[series_column], axis="index" - ) - - assert_frame_equal(bf_result, pd_result, ignore_order=not ordered) - - -# TODO(garrettwu): Test series binop with different index - -all_joins = pytest.mark.parametrize( - ("how",), - (("outer",), ("left",), ("right",), ("inner",), ("cross",)), -) - - -@all_joins -def test_join_same_table(scalars_dfs, how): - bf_df, pd_df = scalars_dfs - if not bf_df._session._strictly_ordered and how == "cross": - pytest.skip("Cross join not supported in partial ordering mode.") - - bf_df_a = bf_df.set_index("int64_too")[["string_col", "int64_col"]] - bf_df_a = bf_df_a.sort_index() - - bf_df_b = bf_df.set_index("int64_too")[["float64_col"]] - bf_df_b = bf_df_b[bf_df_b.float64_col > 0] - bf_df_b = bf_df_b.sort_values("float64_col") - - bf_result = bf_df_a.join(bf_df_b, how=how).to_pandas() - - pd_df_a = pd_df.set_index("int64_too")[["string_col", "int64_col"]].sort_index() - pd_df_a = pd_df_a.sort_index() - - pd_df_b = pd_df.set_index("int64_too")[["float64_col"]] - pd_df_b = pd_df_b[pd_df_b.float64_col > 0] - pd_df_b = pd_df_b.sort_values("float64_col") - - pd_result = pd_df_a.join(pd_df_b, how=how) - - assert_frame_equal(bf_result, pd_result, ignore_order=True) - - -@all_joins -def test_join_different_table( - scalars_df_index, scalars_df_2_index, scalars_pandas_df_index, how -): - bf_df_a = scalars_df_index[["string_col", "int64_col"]] - bf_df_b = scalars_df_2_index.dropna()[["float64_col"]] - bf_result = bf_df_a.join(bf_df_b, how=how).to_pandas() - pd_df_a = scalars_pandas_df_index[["string_col", "int64_col"]] - pd_df_b = scalars_pandas_df_index.dropna()[["float64_col"]] - pd_result = pd_df_a.join(pd_df_b, how=how) - assert_frame_equal(bf_result, pd_result, ignore_order=True) - - -@all_joins -def test_join_raise_when_param_on_duplicate_with_column(scalars_df_index, how): - if how == "cross": - return - bf_df_a = scalars_df_index[["string_col", "int64_col"]].rename( - columns={"int64_col": "string_col"} - ) - bf_df_b = scalars_df_index.dropna()["string_col"] - with pytest.raises( - ValueError, match="The column label 'string_col' is not unique." - ): - bf_df_a.join(bf_df_b, on="string_col", how=how, lsuffix="_l", rsuffix="_r") - - -def test_join_duplicate_columns_raises_value_error(scalars_dfs): - scalars_df, _ = scalars_dfs - df_a = scalars_df[["string_col", "float64_col"]] - df_b = scalars_df[["float64_col"]] - with pytest.raises(ValueError, match="columns overlap but no suffix specified"): - df_a.join(df_b, how="outer") - - -@all_joins -def test_join_param_on_duplicate_with_index_raises_value_error(scalars_df_index, how): - if how == "cross": - return - bf_df_a = scalars_df_index[["string_col"]] - bf_df_a.index.name = "string_col" - bf_df_b = scalars_df_index.dropna()["string_col"] - with pytest.raises( - ValueError, - match="'string_col' is both an index level and a column label, which is ambiguous.", - ): - bf_df_a.join(bf_df_b, on="string_col", how=how, lsuffix="_l", rsuffix="_r") - - -@all_joins -def test_join_param_on(scalars_dfs, how): - bf_df, pd_df = scalars_dfs - - bf_df_a = bf_df[["string_col", "int64_col", "rowindex_2"]] - bf_df_a = bf_df_a.assign(rowindex_2=bf_df_a["rowindex_2"] + 2) - bf_df_b = bf_df[["float64_col"]] - - if how == "cross": - with pytest.raises(ValueError, match="'on' is not supported for cross join."): - bf_df_a.join(bf_df_b, on="rowindex_2", how=how) - else: - bf_result = bf_df_a.join(bf_df_b, on="rowindex_2", how=how).to_pandas() - - pd_df_a = pd_df[["string_col", "int64_col", "rowindex_2"]] - pd_df_a = pd_df_a.assign(rowindex_2=pd_df_a["rowindex_2"] + 2) - pd_df_b = pd_df[["float64_col"]] - pd_result = pd_df_a.join(pd_df_b, on="rowindex_2", how=how) - assert_frame_equal(bf_result, pd_result, ignore_order=True) - - -@all_joins -def test_df_join_series(scalars_dfs, how): - bf_df, pd_df = scalars_dfs - - bf_df_a = bf_df[["string_col", "int64_col", "rowindex_2"]] - bf_df_a = bf_df_a.assign(rowindex_2=bf_df_a["rowindex_2"] + 2) - bf_series_b = bf_df["float64_col"] - - if how == "cross": - with pytest.raises(ValueError): - bf_df_a.join(bf_series_b, on="rowindex_2", how=how) - else: - bf_result = bf_df_a.join(bf_series_b, on="rowindex_2", how=how).to_pandas() - - pd_df_a = pd_df[["string_col", "int64_col", "rowindex_2"]] - pd_df_a = pd_df_a.assign(rowindex_2=pd_df_a["rowindex_2"] + 2) - pd_series_b = pd_df["float64_col"] - pd_result = pd_df_a.join(pd_series_b, on="rowindex_2", how=how) - assert_frame_equal(bf_result, pd_result, ignore_order=True) - - -@pytest.mark.parametrize( - ("by", "ascending", "na_position"), - [ - ("int64_col", True, "first"), - (["bool_col", "int64_col"], True, "last"), - ("int64_col", False, "first"), - (["bool_col", "int64_col"], [False, True], "last"), - (["bool_col", "int64_col"], [True, False], "first"), - ], -) -def test_dataframe_sort_values( - scalars_df_index, scalars_pandas_df_index, by, ascending, na_position -): - # Test needs values to be unique - bf_result = scalars_df_index.sort_values( - by, ascending=ascending, na_position=na_position - ).to_pandas() - pd_result = scalars_pandas_df_index.sort_values( - by, ascending=ascending, na_position=na_position - ) - - pandas.testing.assert_frame_equal( - bf_result, - pd_result, - ) - - -@pytest.mark.parametrize( - ("by", "ascending", "na_position"), - [ - ("int64_col", True, "first"), - (["bool_col", "int64_col"], True, "last"), - ], -) -def test_dataframe_sort_values_inplace( - scalars_df_index, scalars_pandas_df_index, by, ascending, na_position -): - # Test needs values to be unique - bf_sorted = scalars_df_index.copy() - bf_sorted.sort_values( - by, ascending=ascending, na_position=na_position, inplace=True - ) - bf_result = bf_sorted.to_pandas() - pd_result = scalars_pandas_df_index.sort_values( - by, ascending=ascending, na_position=na_position - ) - - pandas.testing.assert_frame_equal( - bf_result, - pd_result, - ) - - -def test_dataframe_sort_values_invalid_input(scalars_df_index): - with pytest.raises(KeyError): - scalars_df_index.sort_values(by=scalars_df_index["int64_col"]) - - -def test_dataframe_sort_values_stable(scalars_df_index, scalars_pandas_df_index): - bf_result = ( - scalars_df_index.sort_values("int64_col", kind="stable") - .sort_values("bool_col", kind="stable") - .to_pandas() - ) - pd_result = scalars_pandas_df_index.sort_values( - "int64_col", kind="stable" - ).sort_values("bool_col", kind="stable") - - pandas.testing.assert_frame_equal( - bf_result, - pd_result, - ) - - -@pytest.mark.parametrize( - ("operator", "columns"), - [ - pytest.param(lambda x: x.cumsum(), ["float64_col", "int64_too"]), - # pytest.param(lambda x: x.cumprod(), ["float64_col", "int64_too"]), - pytest.param( - lambda x: x.cumprod(), - ["string_col"], - marks=pytest.mark.xfail( - raises=ValueError, - ), - ), - ], - ids=[ - "cumsum", - # "cumprod", - "non-numeric", - ], -) -def test_dataframe_numeric_analytic_op( - scalars_df_index, scalars_pandas_df_index, operator, columns -): - # TODO: Add nullable ints (pandas 1.x has poor behavior on these) - bf_series = operator(scalars_df_index[columns]) - pd_series = operator(scalars_pandas_df_index[columns]) - bf_result = bf_series.to_pandas() - pd.testing.assert_frame_equal(pd_series, bf_result, check_dtype=False) - - -@pytest.mark.parametrize( - ("operator"), - [ - (lambda x: x.cummin()), - (lambda x: x.cummax()), - (lambda x: x.shift(2)), - (lambda x: x.shift(-2)), - ], - ids=[ - "cummin", - "cummax", - "shiftpostive", - "shiftnegative", - ], -) -def test_dataframe_general_analytic_op( - scalars_df_index, scalars_pandas_df_index, operator -): - col_names = ["int64_too", "float64_col", "int64_col", "bool_col"] - bf_series = operator(scalars_df_index[col_names]) - pd_series = operator(scalars_pandas_df_index[col_names]) - bf_result = bf_series.to_pandas() - pd.testing.assert_frame_equal( - pd_series, - bf_result, - ) - - -@pytest.mark.parametrize( - ("periods",), - [ - (1,), - (2,), - (-1,), - ], -) -def test_dataframe_diff(scalars_df_index, scalars_pandas_df_index, periods): - col_names = ["int64_too", "float64_col", "int64_col"] - bf_result = scalars_df_index[col_names].diff(periods=periods).to_pandas() - pd_result = scalars_pandas_df_index[col_names].diff(periods=periods) - pd.testing.assert_frame_equal( - pd_result, - bf_result, - ) - - -@pytest.mark.parametrize( - ("periods",), - [ - (1,), - (2,), - (-1,), - ], -) -def test_dataframe_pct_change(scalars_df_index, scalars_pandas_df_index, periods): - col_names = ["int64_too", "float64_col", "int64_col"] - bf_result = scalars_df_index[col_names].pct_change(periods=periods).to_pandas() - # pandas 3.0 does not automatically ffill anymore - pd_result = scalars_pandas_df_index[col_names].ffill().pct_change(periods=periods) - assert_frame_equal( - pd_result, - bf_result, - nulls_are_nan=True, - ) - - -def test_dataframe_agg_single_string(scalars_dfs): - numeric_cols = ["int64_col", "int64_too", "float64_col"] - scalars_df, scalars_pandas_df = scalars_dfs - - bf_result = scalars_df[numeric_cols].agg("sum").to_pandas() - pd_result = scalars_pandas_df[numeric_cols].agg("sum") - - assert bf_result.dtype == "Float64" - pd.testing.assert_series_equal( - pd_result, bf_result, check_dtype=False, check_index_type=False - ) - - -@pytest.mark.parametrize( - ("agg",), - ( - ("sum",), - ("size",), - ), -) -def test_dataframe_agg_int_single_string(scalars_dfs, agg): - numeric_cols = ["int64_col", "int64_too", "bool_col"] - scalars_df, scalars_pandas_df = scalars_dfs - - bf_result = scalars_df[numeric_cols].agg(agg).to_pandas() - pd_result = scalars_pandas_df[numeric_cols].agg(agg) - - assert bf_result.dtype == "Int64" - pd.testing.assert_series_equal( - pd_result, bf_result, check_dtype=False, check_index_type=False - ) - - -def test_dataframe_agg_multi_string(scalars_dfs): - numeric_cols = ["int64_col", "int64_too", "float64_col"] - aggregations = [ - "sum", - "mean", - "median", - "std", - "var", - "min", - "max", - "nunique", - "count", - ] - scalars_df, scalars_pandas_df = scalars_dfs - bf_result = scalars_df[numeric_cols].agg(aggregations) - pd_result = scalars_pandas_df[numeric_cols].agg(aggregations) - - # Pandas may produce narrower numeric types, but bigframes always produces Float64 - pd_result = pd_result.astype("Float64") - - # Drop median, as it's an approximation. - bf_median = bf_result.loc["median", :] - bf_result = bf_result.drop(labels=["median"]) - pd_result = pd_result.drop(labels=["median"]) - - assert_dfs_equivalent(pd_result, bf_result, check_index_type=False) - - # Double-check that median is at least plausible. - assert ( - (bf_result.loc["min", :] <= bf_median) & (bf_median <= bf_result.loc["max", :]) - ).all() - - -def test_dataframe_agg_int_multi_string(scalars_dfs): - numeric_cols = ["int64_col", "int64_too", "bool_col"] - aggregations = [ - "sum", - "nunique", - "count", - "size", - ] - scalars_df, scalars_pandas_df = scalars_dfs - bf_result = scalars_df[numeric_cols].agg(aggregations).to_pandas() - pd_result = scalars_pandas_df[numeric_cols].agg(aggregations) - - for dtype in bf_result.dtypes: - assert dtype == "Int64" - - # Pandas may produce narrower numeric types - # Pandas has object index type - pd.testing.assert_frame_equal( - pd_result, bf_result, check_dtype=False, check_index_type=False - ) - - -def test_df_transpose(): - # Include some floats to ensure type coercion - values = [[0, 3.5, True], [1, 4.5, False], [2, 6.5, None]] - # Test complex case of both axes being multi-indices with non-unique elements - - columns: pandas.Index = pd.Index( - ["A", "B", "A"], dtype=pd.StringDtype(storage="pyarrow") - ) - columns_multi = pd.MultiIndex.from_arrays([columns, columns], names=["c1", "c2"]) - - index: pandas.Index = pd.Index( - ["b", "a", "a"], dtype=pd.StringDtype(storage="pyarrow") - ) - rows_multi = pd.MultiIndex.from_arrays([index, index], names=["r1", "r2"]) - - pd_df = pandas.DataFrame(values, index=rows_multi, columns=columns_multi) - bf_df = dataframe.DataFrame(values, index=rows_multi, columns=columns_multi) - - pd_result = pd_df.T - bf_result = bf_df.T.to_pandas() - - assert_frame_equal(pd_result, bf_result, check_dtype=False, nulls_are_nan=True) - - -def test_df_transpose_error(): - with pytest.raises(TypeError, match="Cannot coerce.*to a common type."): - dataframe.DataFrame([[1, "hello"], [2, "world"]]).transpose() - - -def test_df_transpose_repeated_uses_cache(): - bf_df = dataframe.DataFrame([[1, 2.5], [2, 3.5]]) - pd_df = pandas.DataFrame([[1, 2.5], [2, 3.5]]) - # Transposing many times so that operation will fail from complexity if not using cache - for i in range(10): - # Cache still works even with simple scalar binop - bf_df = bf_df.transpose() + i - pd_df = pd_df.transpose() + i - - pd.testing.assert_frame_equal( - pd_df, bf_df.to_pandas(), check_dtype=False, check_index_type=False - ) - - -def test_df_stack(scalars_dfs): - if pandas.__version__.startswith("1.") or pandas.__version__.startswith("2.0"): - pytest.skip("pandas <2.1 uses different stack implementation") - scalars_df, scalars_pandas_df = scalars_dfs - # To match bigquery dataframes - scalars_pandas_df = scalars_pandas_df.copy() - scalars_pandas_df.columns = scalars_pandas_df.columns.astype("string[pyarrow]") - # Can only stack identically-typed columns - columns = ["int64_col", "int64_too", "rowindex_2"] - - bf_result = scalars_df[columns].stack().to_pandas() - pd_result = scalars_pandas_df[columns].stack(future_stack=True) - - # Pandas produces NaN, where bq dataframes produces pd.NA - assert_series_equal(bf_result, pd_result, check_dtype=False) - - -def test_df_melt_default(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - # To match bigquery dataframes - scalars_pandas_df = scalars_pandas_df.copy() - scalars_pandas_df.columns = scalars_pandas_df.columns.astype("string[pyarrow]") - # Can only stack identically-typed columns - columns = ["int64_col", "int64_too", "rowindex_2"] - - bf_result = scalars_df[columns].melt().to_pandas() - pd_result = scalars_pandas_df[columns].melt() - - # Pandas produces int64 index, Bigframes produces Int64 (nullable) - pd.testing.assert_frame_equal( - bf_result, - pd_result, - check_index_type=False, - check_dtype=False, - ) - - -def test_df_melt_parameterized(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - # To match bigquery dataframes - scalars_pandas_df = scalars_pandas_df.copy() - scalars_pandas_df.columns = scalars_pandas_df.columns.astype("string[pyarrow]") - # Can only stack identically-typed columns - - bf_result = scalars_df.melt( - var_name="alice", - value_name="bob", - id_vars=["string_col"], - value_vars=["int64_col", "int64_too"], - ).to_pandas() - pd_result = scalars_pandas_df.melt( - var_name="alice", - value_name="bob", - id_vars=["string_col"], - value_vars=["int64_col", "int64_too"], - ) - - # Pandas produces int64 index, Bigframes produces Int64 (nullable) - pd.testing.assert_frame_equal( - bf_result, pd_result, check_index_type=False, check_dtype=False - ) - - -@pytest.mark.parametrize( - ("ordered"), - [ - (True), - (False), - ], -) -def test_df_unstack(scalars_dfs, ordered): - scalars_df, scalars_pandas_df = scalars_dfs - # To match bigquery dataframes - scalars_pandas_df = scalars_pandas_df.copy() - scalars_pandas_df.columns = scalars_pandas_df.columns.astype("string[pyarrow]") - # Can only stack identically-typed columns - columns = [ - "rowindex_2", - "int64_col", - "int64_too", - ] - - # unstack on mono-index produces series - bf_result = scalars_df[columns].unstack().to_pandas(ordered=ordered) - pd_result = scalars_pandas_df[columns].unstack() - - # Pandas produces NaN, where bq dataframes produces pd.NA - assert_series_equal( - bf_result, pd_result, check_dtype=False, ignore_order=not ordered - ) - - -def test_ipython_key_completions_with_drop(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - col_names = "string_col" - bf_dataframe = scalars_df.drop(columns=col_names) - pd_dataframe = scalars_pandas_df.drop(columns=col_names) - expected = pd_dataframe.columns.tolist() - - results = bf_dataframe._ipython_key_completions_() - - assert col_names not in results - assert results == expected - # _ipython_key_completions_ is called with square brackets - # so only column names are relevant with tab completion - assert "to_gbq" not in results - assert "merge" not in results - assert "drop" not in results - - -def test_ipython_key_completions_with_rename(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - col_name_dict = {"string_col": "a_renamed_column"} - bf_dataframe = scalars_df.rename(columns=col_name_dict) - pd_dataframe = scalars_pandas_df.rename(columns=col_name_dict) - expected = pd_dataframe.columns.tolist() - - results = bf_dataframe._ipython_key_completions_() - - assert "string_col" not in results - assert "a_renamed_column" in results - assert results == expected - # _ipython_key_completions_ is called with square brackets - # so only column names are relevant with tab completion - assert "to_gbq" not in results - assert "merge" not in results - assert "drop" not in results - - -def test__dir__with_drop(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - col_names = "string_col" - bf_dataframe = scalars_df.drop(columns=col_names) - pd_dataframe = scalars_pandas_df.drop(columns=col_names) - expected = pd_dataframe.columns.tolist() - - results = dir(bf_dataframe) - - assert col_names not in results - assert frozenset(expected) <= frozenset(results) - # __dir__ is called with a '.' and displays all methods, columns names, etc. - assert "to_gbq" in results - assert "merge" in results - assert "drop" in results - - -def test__dir__with_rename(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - col_name_dict = {"string_col": "a_renamed_column"} - bf_dataframe = scalars_df.rename(columns=col_name_dict) - pd_dataframe = scalars_pandas_df.rename(columns=col_name_dict) - expected = pd_dataframe.columns.tolist() - - results = dir(bf_dataframe) - - assert "string_col" not in results - assert "a_renamed_column" in results - assert frozenset(expected) <= frozenset(results) - # __dir__ is called with a '.' and displays all methods, columns names, etc. - assert "to_gbq" in results - assert "merge" in results - assert "drop" in results - - -@pytest.mark.parametrize( - ("start", "stop", "step"), - [ - (0, 0, None), - (None, None, None), - (1, None, None), - (None, 4, None), - (None, None, 2), - (None, 50000000000, 1), - (5, 4, None), - (3, None, 2), - (1, 7, 2), - (1, 7, 50000000000), - ], -) -def test_iloc_slice(scalars_df_index, scalars_pandas_df_index, start, stop, step): - bf_result = scalars_df_index.iloc[start:stop:step].to_pandas() - pd_result = scalars_pandas_df_index.iloc[start:stop:step] - pd.testing.assert_frame_equal( - bf_result, - pd_result, - ) - - -def test_iloc_slice_zero_step(scalars_df_index): - with pytest.raises(ValueError): - scalars_df_index.iloc[0:0:0] - - -@pytest.mark.parametrize( - ("ordered"), - [ - (True), - (False), - ], -) -def test_iloc_slice_nested(scalars_df_index, scalars_pandas_df_index, ordered): - bf_result = scalars_df_index.iloc[1:].iloc[1:].to_pandas(ordered=ordered) - pd_result = scalars_pandas_df_index.iloc[1:].iloc[1:] - - assert_frame_equal(bf_result, pd_result, ignore_order=not ordered) - - -@pytest.mark.parametrize( - "index", - [0, 5, -2, (2,)], -) -def test_iloc_single_integer(scalars_df_index, scalars_pandas_df_index, index): - bf_result = scalars_df_index.iloc[index] - pd_result = scalars_pandas_df_index.iloc[index] - - pd.testing.assert_series_equal( - bf_result, - pd_result, - ) - - -@pytest.mark.parametrize( - "index", - [(2, 5), (5, 0), (0, 0)], -) -def test_iloc_tuple(scalars_df_index, scalars_pandas_df_index, index): - bf_result = scalars_df_index.iloc[index] - pd_result = scalars_pandas_df_index.iloc[index] - - assert bf_result == pd_result - - -@pytest.mark.parametrize( - "index", - [(slice(None), [1, 2, 3]), (slice(1, 7, 2), [2, 5, 3])], -) -def test_iloc_tuple_multi_columns(scalars_df_index, scalars_pandas_df_index, index): - bf_result = scalars_df_index.iloc[index].to_pandas() - pd_result = scalars_pandas_df_index.iloc[index] - - pd.testing.assert_frame_equal(bf_result, pd_result) - - -def test_iloc_tuple_multi_columns_single_row(scalars_df_index, scalars_pandas_df_index): - index = (2, [2, 1, 3, -4]) - bf_result = scalars_df_index.iloc[index] - pd_result = scalars_pandas_df_index.iloc[index] - pd.testing.assert_series_equal(bf_result, pd_result) - - -@pytest.mark.parametrize( - ("index", "error"), - [ - ((1, 1, 1), pd.errors.IndexingError), - (("asd", "asd", "asd"), pd.errors.IndexingError), - (("asd"), TypeError), - ], -) -def test_iloc_tuple_errors(scalars_df_index, scalars_pandas_df_index, index, error): - with pytest.raises(error): - scalars_df_index.iloc[index] - with pytest.raises(error): - scalars_pandas_df_index.iloc[index] - - -@pytest.mark.parametrize( - "index", - [(2, 5), (5, 0), (0, 0)], -) -def test_iat(scalars_df_index, scalars_pandas_df_index, index): - bf_result = scalars_df_index.iat[index] - pd_result = scalars_pandas_df_index.iat[index] - - assert bf_result == pd_result - - -@pytest.mark.parametrize( - ("index", "error"), - [ - (0, TypeError), - ("asd", ValueError), - ((1, 2, 3), TypeError), - (("asd", "asd"), ValueError), - ], -) -def test_iat_errors(scalars_df_index, scalars_pandas_df_index, index, error): - with pytest.raises(error): - scalars_pandas_df_index.iat[index] - with pytest.raises(error): - scalars_df_index.iat[index] - - -def test_iloc_single_integer_out_of_bound_error( - scalars_df_index, scalars_pandas_df_index -): - with pytest.raises(IndexError, match="single positional indexer is out-of-bounds"): - scalars_df_index.iloc[99] - - -def test_loc_bool_series(scalars_df_index, scalars_pandas_df_index): - bf_result = scalars_df_index.loc[scalars_df_index.bool_col].to_pandas() - pd_result = scalars_pandas_df_index.loc[scalars_pandas_df_index.bool_col] - - pd.testing.assert_frame_equal( - bf_result, - pd_result, - ) - - -def test_loc_select_column(scalars_df_index, scalars_pandas_df_index): - bf_result = scalars_df_index.loc[:, "int64_col"].to_pandas() - pd_result = scalars_pandas_df_index.loc[:, "int64_col"] - pd.testing.assert_series_equal( - bf_result, - pd_result, - ) - - -def test_loc_select_with_column_condition(scalars_df_index, scalars_pandas_df_index): - bf_result = scalars_df_index.loc[:, scalars_df_index.dtypes == "Int64"].to_pandas() - pd_result = scalars_pandas_df_index.loc[ - :, scalars_pandas_df_index.dtypes == "Int64" - ] - pd.testing.assert_frame_equal( - bf_result, - pd_result, - ) - - -def test_loc_select_with_column_condition_bf_series( - scalars_df_index, scalars_pandas_df_index -): - # (b/347072677) GEOGRAPH type doesn't support DISTINCT op - columns = [ - item for item in scalars_pandas_df_index.columns if item != "geography_col" - ] - scalars_df_index = scalars_df_index[columns] - scalars_pandas_df_index = scalars_pandas_df_index[columns] - - size_half = len(scalars_pandas_df_index) / 2 - bf_result = scalars_df_index.loc[ - :, scalars_df_index.nunique() > size_half - ].to_pandas() - pd_result = scalars_pandas_df_index.loc[ - :, scalars_pandas_df_index.nunique() > size_half - ] - pd.testing.assert_frame_equal( - bf_result, - pd_result, - ) - - -def test_loc_single_index_with_duplicate(scalars_df_index, scalars_pandas_df_index): - scalars_df_index = scalars_df_index.set_index("string_col", drop=False) - scalars_pandas_df_index = scalars_pandas_df_index.set_index( - "string_col", drop=False - ) - index = "Hello, World!" - bf_result = scalars_df_index.loc[index] - pd_result = scalars_pandas_df_index.loc[index] - pd.testing.assert_frame_equal( - bf_result.to_pandas(), - pd_result, - ) - - -def test_loc_single_index_no_duplicate(scalars_df_index, scalars_pandas_df_index): - scalars_df_index = scalars_df_index.set_index("int64_too", drop=False) - scalars_pandas_df_index = scalars_pandas_df_index.set_index("int64_too", drop=False) - index = -2345 - bf_result = scalars_df_index.loc[index] - pd_result = scalars_pandas_df_index.loc[index] - pd.testing.assert_series_equal( - bf_result, - pd_result, - ) - - -def test_at_with_duplicate(scalars_df_index, scalars_pandas_df_index): - scalars_df_index = scalars_df_index.set_index("string_col", drop=False) - scalars_pandas_df_index = scalars_pandas_df_index.set_index( - "string_col", drop=False - ) - index = "Hello, World!" - bf_result = scalars_df_index.at[index, "int64_too"] - pd_result = scalars_pandas_df_index.at[index, "int64_too"] - pd.testing.assert_series_equal( - bf_result.to_pandas(), - pd_result, - ) - - -def test_at_no_duplicate(scalars_df_index, scalars_pandas_df_index): - scalars_df_index = scalars_df_index.set_index("int64_too", drop=False) - scalars_pandas_df_index = scalars_pandas_df_index.set_index("int64_too", drop=False) - index = -2345 - bf_result = scalars_df_index.at[index, "string_col"] - pd_result = scalars_pandas_df_index.at[index, "string_col"] - assert bf_result == pd_result - - -def test_loc_setitem_bool_series_scalar_new_col(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - bf_df = scalars_df.copy() - pd_df = scalars_pandas_df.copy() - bf_df.loc[bf_df["int64_too"] == 0, "new_col"] = 99 - pd_df.loc[pd_df["int64_too"] == 0, "new_col"] = 99 - - # pandas uses float64 instead - pd_df["new_col"] = pd_df["new_col"].astype("Float64") - - pd.testing.assert_frame_equal( - bf_df.to_pandas(), - pd_df, - ) - - -@pytest.mark.parametrize( - ("col", "value"), - [ - ("string_col", "hello"), - ("int64_col", 3), - ("float64_col", 3.5), - ], -) -def test_loc_setitem_bool_series_scalar_existing_col(scalars_dfs, col, value): - if pd.__version__.startswith("1."): - pytest.skip("this loc overload not supported in pandas 1.x.") - - scalars_df, scalars_pandas_df = scalars_dfs - bf_df = scalars_df.copy() - pd_df = scalars_pandas_df.copy() - bf_df.loc[bf_df["int64_too"] == 1, col] = value - pd_df.loc[pd_df["int64_too"] == 1, col] = value - - pd.testing.assert_frame_equal( - bf_df.to_pandas(), - pd_df, - ) - - -def test_loc_setitem_bool_series_scalar_error(scalars_dfs): - if pd.__version__.startswith("1."): - pytest.skip("this loc overload not supported in pandas 1.x.") - - scalars_df, scalars_pandas_df = scalars_dfs - bf_df = scalars_df.copy() - pd_df = scalars_pandas_df.copy() - - with pytest.raises(Exception): - bf_df.loc[bf_df["int64_too"] == 1, "string_col"] = 99 - with pytest.raises(Exception): - pd_df.loc[pd_df["int64_too"] == 1, "string_col"] = 99 - - -@pytest.mark.parametrize( - ("col", "op"), - [ - # Int aggregates - pytest.param("int64_col", lambda x: x.sum(), id="int-sum"), - pytest.param("int64_col", lambda x: x.min(), id="int-min"), - pytest.param("int64_col", lambda x: x.max(), id="int-max"), - pytest.param("int64_col", lambda x: x.count(), id="int-count"), - pytest.param("int64_col", lambda x: x.nunique(), id="int-nunique"), - # Float aggregates - pytest.param("float64_col", lambda x: x.count(), id="float-count"), - pytest.param("float64_col", lambda x: x.nunique(), id="float-nunique"), - # Bool aggregates - pytest.param("bool_col", lambda x: x.sum(), id="bool-sum"), - pytest.param("bool_col", lambda x: x.count(), id="bool-count"), - pytest.param("bool_col", lambda x: x.nunique(), id="bool-nunique"), - # String aggregates - pytest.param("string_col", lambda x: x.count(), id="string-count"), - pytest.param("string_col", lambda x: x.nunique(), id="string-nunique"), - ], -) -def test_dataframe_aggregate_int(scalars_df_index, scalars_pandas_df_index, col, op): - bf_result = op(scalars_df_index[[col]]).to_pandas() - pd_result = op(scalars_pandas_df_index[[col]]) - - # Check dtype separately - assert bf_result.dtype == "Int64" - # Is otherwise "object" dtype - pd_result.index = pd_result.index.astype("string[pyarrow]") - # Pandas may produce narrower numeric types - assert_series_equal(pd_result, bf_result, check_dtype=False, check_index_type=False) - - -@pytest.mark.parametrize( - ("col", "op"), - [ - pytest.param("bool_col", lambda x: x.min(), id="bool-min"), - pytest.param("bool_col", lambda x: x.max(), id="bool-max"), - ], -) -def test_dataframe_aggregate_bool(scalars_df_index, scalars_pandas_df_index, col, op): - bf_result = op(scalars_df_index[[col]]).to_pandas() - pd_result = op(scalars_pandas_df_index[[col]]) - - # Check dtype separately - assert bf_result.dtype == "boolean" - - # Pandas may produce narrower numeric types - # Pandas has object index type - pd_result.index = pd_result.index.astype("string[pyarrow]") - assert_series_equal(pd_result, bf_result, check_dtype=False, check_index_type=False) - - -@pytest.mark.parametrize( - ("op", "bf_dtype"), - [ - (lambda x: x.sum(numeric_only=True), "Float64"), - (lambda x: x.mean(numeric_only=True), "Float64"), - (lambda x: x.min(numeric_only=True), "Float64"), - (lambda x: x.max(numeric_only=True), "Float64"), - (lambda x: x.std(numeric_only=True), "Float64"), - (lambda x: x.var(numeric_only=True), "Float64"), - (lambda x: x.count(numeric_only=False), "Int64"), - (lambda x: x.nunique(), "Int64"), - ], - ids=["sum", "mean", "min", "max", "std", "var", "count", "nunique"], -) -def test_dataframe_aggregates(scalars_dfs, op, bf_dtype): - scalars_df_index, scalars_pandas_df_index = scalars_dfs - col_names = ["int64_too", "float64_col", "string_col", "int64_col", "bool_col"] - bf_series = op(scalars_df_index[col_names]) - bf_result = bf_series - pd_result = op(scalars_pandas_df_index[col_names]) - - # Check dtype separately - assert bf_result.dtype == bf_dtype - - # Pandas may produce narrower numeric types, but bigframes always produces Float64 - # Pandas has object index type - pd_result.index = pd_result.index.astype("string[pyarrow]") - assert_series_equivalent( - pd_result, - bf_result, - check_dtype=False, - check_index_type=False, - ) - - -@pytest.mark.parametrize( - ("op"), - [ - (lambda x: x.sum(axis=1, numeric_only=True)), - (lambda x: x.mean(axis=1, numeric_only=True)), - (lambda x: x.min(axis=1, numeric_only=True)), - (lambda x: x.max(axis=1, numeric_only=True)), - (lambda x: x.std(axis=1, numeric_only=True)), - (lambda x: x.var(axis=1, numeric_only=True)), - ], - ids=["sum", "mean", "min", "max", "std", "var"], -) -def test_dataframe_aggregates_axis_1(scalars_df_index, scalars_pandas_df_index, op): - col_names = ["int64_too", "int64_col", "float64_col", "bool_col", "string_col"] - bf_result = op(scalars_df_index[col_names]).to_pandas() - pd_result = op(scalars_pandas_df_index[col_names]) - - # Pandas may produce narrower numeric types, but bigframes always produces Float64 - # Pandas has object index type - assert_series_equal(pd_result, bf_result, check_index_type=False, check_dtype=False) - - -@pytest.mark.parametrize( - ("op"), - [ - (lambda x: x.all(bool_only=True)), - (lambda x: x.any(bool_only=True)), - (lambda x: x.all(axis=1, bool_only=True)), - (lambda x: x.any(axis=1, bool_only=True)), - ], - ids=["all_axis0", "any_axis0", "all_axis1", "any_axis1"], -) -def test_dataframe_bool_aggregates(scalars_df_index, scalars_pandas_df_index, op): - # Pandas will drop nullable 'boolean' dtype so we convert first to bool, then cast back later - scalars_df_index = scalars_df_index.assign( - bool_col=scalars_df_index.bool_col.fillna(False) - ) - scalars_pandas_df_index = scalars_pandas_df_index.assign( - bool_col=scalars_pandas_df_index.bool_col.fillna(False).astype("bool") - ) - bf_series = op(scalars_df_index) - pd_series = op(scalars_pandas_df_index).astype("boolean") - bf_result = bf_series.to_pandas() - - pd_series.index = pd_series.index.astype(bf_result.index.dtype) - pd.testing.assert_series_equal(pd_series, bf_result, check_index_type=False) - - -def test_dataframe_prod(scalars_df_index, scalars_pandas_df_index): - col_names = ["int64_too", "float64_col"] - bf_series = scalars_df_index[col_names].prod() - pd_series = scalars_pandas_df_index[col_names].prod() - bf_result = bf_series.to_pandas() - - # Pandas may produce narrower numeric types, but bigframes always produces Float64 - pd_series = pd_series.astype("Float64") - # Pandas has object index type - pd.testing.assert_series_equal(pd_series, bf_result, check_index_type=False) - - -def test_df_skew_too_few_values(scalars_dfs): - columns = ["float64_col", "int64_col"] - scalars_df, scalars_pandas_df = scalars_dfs - bf_result = scalars_df[columns].head(2).skew().to_pandas() - pd_result = scalars_pandas_df[columns].head(2).skew() - - # Pandas may produce narrower numeric types, but bigframes always produces Float64 - pd_result = pd_result.astype("Float64") - - pd.testing.assert_series_equal(pd_result, bf_result, check_index_type=False) - - -@pytest.mark.parametrize( - ("ordered"), - [ - (True), - (False), - ], -) -def test_df_skew(scalars_dfs, ordered): - columns = ["float64_col", "int64_col"] - scalars_df, scalars_pandas_df = scalars_dfs - bf_result = scalars_df[columns].skew().to_pandas(ordered=ordered) - pd_result = scalars_pandas_df[columns].skew() - - # Pandas may produce narrower numeric types, but bigframes always produces Float64 - pd_result = pd_result.astype("Float64") - - assert_series_equal( - pd_result, bf_result, check_index_type=False, ignore_order=not ordered - ) - - -def test_df_kurt_too_few_values(scalars_dfs): - columns = ["float64_col", "int64_col"] - scalars_df, scalars_pandas_df = scalars_dfs - bf_result = scalars_df[columns].head(2).kurt().to_pandas() - pd_result = scalars_pandas_df[columns].head(2).kurt() - - # Pandas may produce narrower numeric types, but bigframes always produces Float64 - pd_result = pd_result.astype("Float64") - - pd.testing.assert_series_equal(pd_result, bf_result, check_index_type=False) - - -def test_df_kurt(scalars_dfs): - columns = ["float64_col", "int64_col"] - scalars_df, scalars_pandas_df = scalars_dfs - bf_result = scalars_df[columns].kurt().to_pandas() - pd_result = scalars_pandas_df[columns].kurt() - - # Pandas may produce narrower numeric types, but bigframes always produces Float64 - pd_result = pd_result.astype("Float64") - - pd.testing.assert_series_equal(pd_result, bf_result, check_index_type=False) - - -def test_sample_raises_value_error(scalars_dfs): - scalars_df, _ = scalars_dfs - with pytest.raises( - ValueError, match="Only one of 'n' or 'frac' parameter can be specified." - ): - scalars_df.sample(frac=0.5, n=4) - - -@pytest.mark.parametrize( - ("axis",), - [ - (None,), - (0,), - (1,), - ], -) -def test_df_add_prefix(scalars_df_index, scalars_pandas_df_index, axis): - if pd.__version__.startswith("1."): - pytest.skip("add_prefix axis parameter not supported in pandas 1.x.") - bf_result = scalars_df_index.add_prefix("prefix_", axis).to_pandas() - - pd_result = scalars_pandas_df_index.add_prefix("prefix_", axis) - - pd.testing.assert_frame_equal( - bf_result, - pd_result, - check_index_type=False, - ) - - -@pytest.mark.parametrize( - ("axis",), - [ - (0,), - (1,), - ], -) -def test_df_add_suffix(scalars_df_index, scalars_pandas_df_index, axis): - if pd.__version__.startswith("1."): - pytest.skip("add_prefix axis parameter not supported in pandas 1.x.") - bf_result = scalars_df_index.add_suffix("_suffix", axis).to_pandas() - - pd_result = scalars_pandas_df_index.add_suffix("_suffix", axis) - - pd.testing.assert_frame_equal( - bf_result, - pd_result, - check_index_type=False, - ) - - -def test_df_astype_error_error(session): - input = pd.DataFrame(["hello", "world", "3.11", "4000"]) - with pytest.raises(ValueError): - session.read_pandas(input).astype("Float64", errors="bad_value") - - -def test_df_columns_filter_items(scalars_df_index, scalars_pandas_df_index): - if pd.__version__.startswith("2.0") or pd.__version__.startswith("1."): - pytest.skip("pandas filter items behavior different pre-2.1") - bf_result = scalars_df_index.filter(items=["string_col", "int64_col"]).to_pandas() - - pd_result = scalars_pandas_df_index.filter(items=["string_col", "int64_col"]) - # Ignore column ordering as pandas order differently depending on version - pd.testing.assert_frame_equal( - bf_result.sort_index(axis=1), - pd_result.sort_index(axis=1), - ) - - -def test_df_columns_filter_like(scalars_df_index, scalars_pandas_df_index): - bf_result = scalars_df_index.filter(like="64_col").to_pandas() - - pd_result = scalars_pandas_df_index.filter(like="64_col") - - pd.testing.assert_frame_equal( - bf_result, - pd_result, - ) - - -def test_df_columns_filter_regex(scalars_df_index, scalars_pandas_df_index): - bf_result = scalars_df_index.filter(regex="^[^_]+$").to_pandas() - - pd_result = scalars_pandas_df_index.filter(regex="^[^_]+$") - - pd.testing.assert_frame_equal( - bf_result, - pd_result, - ) - - -def test_df_reindex_rows_list(scalars_dfs): - scalars_df_index, scalars_pandas_df_index = scalars_dfs - bf_result = scalars_df_index.reindex(index=[5, 1, 3, 99, 1]) - - pd_result = scalars_pandas_df_index.reindex(index=[5, 1, 3, 99, 1]) - - # Pandas uses int64 instead of Int64 (nullable) dtype. - pd_result.index = pd_result.index.astype(pd.Int64Dtype()) - assert_dfs_equivalent( - pd_result, - bf_result, - ) - - -def test_df_reindex_rows_index(scalars_df_index, scalars_pandas_df_index): - bf_result = scalars_df_index.reindex( - index=pd.Index([5, 1, 3, 99, 1], name="newname") - ).to_pandas() - - pd_result = scalars_pandas_df_index.reindex( - index=pd.Index([5, 1, 3, 99, 1], name="newname") - ) - - # Pandas uses int64 instead of Int64 (nullable) dtype. - pd_result.index = pd_result.index.astype(pd.Int64Dtype()) - pd.testing.assert_frame_equal( - bf_result, - pd_result, - ) - - -def test_df_reindex_nonunique(scalars_df_index): - with pytest.raises(ValueError): - # int64_too is non-unique - scalars_df_index.set_index("int64_too").reindex( - index=[5, 1, 3, 99, 1], validate=True - ) - - -def test_df_reindex_columns(scalars_df_index, scalars_pandas_df_index): - bf_result = scalars_df_index.reindex( - columns=["not_a_col", "int64_col", "int64_too"] - ).to_pandas() - - pd_result = scalars_pandas_df_index.reindex( - columns=["not_a_col", "int64_col", "int64_too"] - ) - - # Pandas uses float64 as default for newly created empty column, bf uses Float64 - pd_result.not_a_col = pd_result.not_a_col.astype(pandas.Float64Dtype()) - pd.testing.assert_frame_equal( - bf_result, - pd_result, - ) - - -def test_df_reindex_columns_with_same_order(scalars_df_index, scalars_pandas_df_index): - # First, make sure the two dataframes have the same columns in order. - columns = ["int64_col", "int64_too"] - bf = scalars_df_index[columns] - pd_df = scalars_pandas_df_index[columns] - - bf_result = bf.reindex(columns=columns).to_pandas() - pd_result = pd_df.reindex(columns=columns) - - pd.testing.assert_frame_equal( - bf_result, - pd_result, - ) - - -def test_df_equals_identical(scalars_df_index, scalars_pandas_df_index): - unsupported = [ - "geography_col", - ] - scalars_df_index = scalars_df_index.drop(columns=unsupported) - scalars_pandas_df_index = scalars_pandas_df_index.drop(columns=unsupported) - - bf_result = scalars_df_index.equals(scalars_df_index) - pd_result = scalars_pandas_df_index.equals(scalars_pandas_df_index) - - assert pd_result == bf_result - - -def test_df_equals_series(scalars_df_index, scalars_pandas_df_index): - bf_result = scalars_df_index[["int64_col"]].equals(scalars_df_index["int64_col"]) - pd_result = scalars_pandas_df_index[["int64_col"]].equals( - scalars_pandas_df_index["int64_col"] - ) - - assert pd_result == bf_result - - -def test_df_equals_different_dtype(scalars_df_index, scalars_pandas_df_index): - columns = ["int64_col", "int64_too"] - scalars_df_index = scalars_df_index[columns] - scalars_pandas_df_index = scalars_pandas_df_index[columns] - - bf_modified = scalars_df_index.copy() - bf_modified = bf_modified.astype("Float64") - - pd_modified = scalars_pandas_df_index.copy() - pd_modified = pd_modified.astype("Float64") - - bf_result = scalars_df_index.equals(bf_modified) - pd_result = scalars_pandas_df_index.equals(pd_modified) - - assert pd_result == bf_result - - -def test_df_equals_different_values(scalars_df_index, scalars_pandas_df_index): - columns = ["int64_col", "int64_too"] - scalars_df_index = scalars_df_index[columns] - scalars_pandas_df_index = scalars_pandas_df_index[columns] - - bf_modified = scalars_df_index.copy() - bf_modified["int64_col"] = bf_modified.int64_col + 1 - - pd_modified = scalars_pandas_df_index.copy() - pd_modified["int64_col"] = pd_modified.int64_col + 1 - - bf_result = scalars_df_index.equals(bf_modified) - pd_result = scalars_pandas_df_index.equals(pd_modified) - - assert pd_result == bf_result - - -def test_df_equals_extra_column(scalars_df_index, scalars_pandas_df_index): - columns = ["int64_col", "int64_too"] - more_columns = ["int64_col", "int64_too", "float64_col"] - - bf_result = scalars_df_index[columns].equals(scalars_df_index[more_columns]) - pd_result = scalars_pandas_df_index[columns].equals( - scalars_pandas_df_index[more_columns] - ) - - assert pd_result == bf_result - - -def test_df_reindex_like(scalars_df_index, scalars_pandas_df_index): - reindex_target_bf = scalars_df_index.reindex( - columns=["not_a_col", "int64_col", "int64_too"], index=[5, 1, 3, 99, 1] - ) - bf_result = scalars_df_index.reindex_like(reindex_target_bf).to_pandas() - - reindex_target_pd = scalars_pandas_df_index.reindex( - columns=["not_a_col", "int64_col", "int64_too"], index=[5, 1, 3, 99, 1] - ) - pd_result = scalars_pandas_df_index.reindex_like(reindex_target_pd) - - # Pandas uses float64 as default for newly created empty column, bf uses Float64 - # Pandas uses int64 instead of Int64 (nullable) dtype. - pd_result.index = pd_result.index.astype(pd.Int64Dtype()) - # Pandas uses float64 as default for newly created empty column, bf uses Float64 - pd_result.not_a_col = pd_result.not_a_col.astype(pandas.Float64Dtype()) - pd.testing.assert_frame_equal( - bf_result, - pd_result, - ) - - -def test_df_values(scalars_df_index, scalars_pandas_df_index): - bf_result = scalars_df_index.values - - pd_result = scalars_pandas_df_index.values - # Numpy isn't equipped to compare non-numeric objects, so convert back to dataframe - pd.testing.assert_frame_equal( - pd.DataFrame(bf_result), pd.DataFrame(pd_result), check_dtype=False - ) - - -def test_df_to_numpy(scalars_df_index, scalars_pandas_df_index): - bf_result = scalars_df_index.to_numpy() - - pd_result = scalars_pandas_df_index.to_numpy() - # Numpy isn't equipped to compare non-numeric objects, so convert back to dataframe - pd.testing.assert_frame_equal( - pd.DataFrame(bf_result), pd.DataFrame(pd_result), check_dtype=False - ) - - -def test_df___array__(scalars_df_index, scalars_pandas_df_index): - bf_result = scalars_df_index.__array__() - - pd_result = scalars_pandas_df_index.__array__() - # Numpy isn't equipped to compare non-numeric objects, so convert back to dataframe - pd.testing.assert_frame_equal( - pd.DataFrame(bf_result), pd.DataFrame(pd_result), check_dtype=False - ) - - -def test_df_getattr_attribute_error_when_pandas_has(scalars_df_index): - # swapaxes is implemented in pandas but not in bigframes - with pytest.raises(AttributeError): - scalars_df_index.swapaxes() - - -def test_df_getattr_attribute_error(scalars_df_index): - with pytest.raises(AttributeError): - scalars_df_index.not_a_method() - - -def test_df_getattr_axes(): - df = dataframe.DataFrame( - [[1, 1, 1], [1, 1, 1]], columns=["index", "columns", "my_column"] - ) - assert isinstance(df.index, bigframes.core.indexes.Index) - assert isinstance(df.columns, pandas.Index) - assert isinstance(df.my_column, series.Series) - - -def test_df_setattr_index(): - pd_df = pandas.DataFrame( - [[1, 1, 1], [1, 1, 1]], columns=["index", "columns", "my_column"] - ) - bf_df = dataframe.DataFrame(pd_df) - - pd_df.index = pandas.Index([4, 5]) - bf_df.index = [4, 5] - - assert_frame_equal( - pd_df, bf_df.to_pandas(), check_index_type=False, check_dtype=False - ) - - -def test_df_setattr_columns(): - pd_df = pandas.DataFrame( - [[1, 1, 1], [1, 1, 1]], columns=["index", "columns", "my_column"] - ) - bf_df = dataframe.DataFrame(pd_df) - - pd_df.columns = typing.cast(pandas.Index, pandas.Index([4, 5, 6])) - - bf_df.columns = pandas.Index([4, 5, 6]) - - assert_frame_equal( - pd_df, bf_df.to_pandas(), check_index_type=False, check_dtype=False - ) - - -def test_df_setattr_modify_column(): - pd_df = pandas.DataFrame( - [[1, 1, 1], [1, 1, 1]], columns=["index", "columns", "my_column"] - ) - bf_df = dataframe.DataFrame(pd_df) - pd_df.my_column = [4, 5] - bf_df.my_column = [4, 5] - - assert_frame_equal( - pd_df, bf_df.to_pandas(), check_index_type=False, check_dtype=False - ) - - -def test_loc_list_string_index(scalars_df_index, scalars_pandas_df_index): - index_list = scalars_pandas_df_index.string_col.iloc[[0, 1, 1, 5]].values - - scalars_df_index = scalars_df_index.set_index("string_col") - scalars_pandas_df_index = scalars_pandas_df_index.set_index("string_col") - - bf_result = scalars_df_index.loc[index_list].to_pandas() - pd_result = scalars_pandas_df_index.loc[index_list] - - pd.testing.assert_frame_equal( - bf_result, - pd_result, - ) - - -def test_loc_list_integer_index(scalars_df_index, scalars_pandas_df_index): - index_list = [3, 2, 1, 3, 2, 1] - - bf_result = scalars_df_index.loc[index_list] - pd_result = scalars_pandas_df_index.loc[index_list] - - pd.testing.assert_frame_equal( - bf_result.to_pandas(), - pd_result, - ) - - -def test_loc_list_multiindex(scalars_dfs): - scalars_df_index, scalars_pandas_df_index = scalars_dfs - scalars_df_multiindex = scalars_df_index.set_index(["string_col", "int64_col"]) - scalars_pandas_df_multiindex = scalars_pandas_df_index.set_index( - ["string_col", "int64_col"] - ) - index_list = [("Hello, World!", -234892), ("Hello, World!", 123456789)] - - bf_result = scalars_df_multiindex.loc[index_list] - pd_result = scalars_pandas_df_multiindex.loc[index_list] - - assert_dfs_equivalent( - pd_result, - bf_result, - ) - - -@pytest.mark.parametrize( - "index_list", - [ - [0, 1, 2, 3, 4, 4], - [0, 0, 0, 5, 4, 7, -2, -5, 3], - [-1, -2, -3, -4, -5, -5], - ], -) -def test_iloc_list(scalars_df_index, scalars_pandas_df_index, index_list): - bf_result = scalars_df_index.iloc[index_list] - pd_result = scalars_pandas_df_index.iloc[index_list] - - pd.testing.assert_frame_equal( - bf_result.to_pandas(), - pd_result, - ) - - -def test_iloc_list_multiindex(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - scalars_df = scalars_df.copy() - scalars_pandas_df = scalars_pandas_df.copy() - scalars_df = scalars_df.set_index(["bytes_col", "numeric_col"]) - scalars_pandas_df = scalars_pandas_df.set_index(["bytes_col", "numeric_col"]) - - index_list = [0, 0, 0, 5, 4, 7] - - bf_result = scalars_df.iloc[index_list] - pd_result = scalars_pandas_df.iloc[index_list] - - pd.testing.assert_frame_equal( - bf_result.to_pandas(), - pd_result, - ) - - -def test_iloc_empty_list(scalars_df_index, scalars_pandas_df_index): - index_list: List[int] = [] - - bf_result = scalars_df_index.iloc[index_list] - pd_result = scalars_pandas_df_index.iloc[index_list] - - bf_result = bf_result.to_pandas() - assert bf_result.shape == pd_result.shape # types are known to be different - - -def test_rename_axis(scalars_df_index, scalars_pandas_df_index): - bf_result = scalars_df_index.rename_axis("newindexname") - pd_result = scalars_pandas_df_index.rename_axis("newindexname") - - pd.testing.assert_frame_equal( - bf_result.to_pandas(), - pd_result, - ) - - -def test_rename_axis_nonstring(scalars_df_index, scalars_pandas_df_index): - bf_result = scalars_df_index.rename_axis((4,)) - pd_result = scalars_pandas_df_index.rename_axis((4,)) - - pd.testing.assert_frame_equal( - bf_result.to_pandas(), - pd_result, - ) - - -def test_loc_bf_series_string_index(scalars_df_index, scalars_pandas_df_index): - pd_string_series = scalars_pandas_df_index.string_col.iloc[[0, 5, 1, 1, 5]] - bf_string_series = scalars_df_index.string_col.iloc[[0, 5, 1, 1, 5]] - - scalars_df_index = scalars_df_index.set_index("string_col") - scalars_pandas_df_index = scalars_pandas_df_index.set_index("string_col") - - bf_result = scalars_df_index.loc[bf_string_series] - pd_result = scalars_pandas_df_index.loc[pd_string_series] - - pd.testing.assert_frame_equal( - bf_result.to_pandas(), - pd_result, - ) - - -def test_loc_bf_series_multiindex(scalars_df_index, scalars_pandas_df_index): - pd_string_series = scalars_pandas_df_index.string_col.iloc[[0, 5, 1, 1, 5]] - bf_string_series = scalars_df_index.string_col.iloc[[0, 5, 1, 1, 5]] - - scalars_df_multiindex = scalars_df_index.set_index(["string_col", "int64_col"]) - scalars_pandas_df_multiindex = scalars_pandas_df_index.set_index( - ["string_col", "int64_col"] - ) - - bf_result = scalars_df_multiindex.loc[bf_string_series] - pd_result = scalars_pandas_df_multiindex.loc[pd_string_series] - - pd.testing.assert_frame_equal( - bf_result.to_pandas(), - pd_result, - ) - - -def test_loc_bf_index_integer_index(scalars_df_index, scalars_pandas_df_index): - pd_index = scalars_pandas_df_index.iloc[[0, 5, 1, 1, 5]].index - bf_index = scalars_df_index.iloc[[0, 5, 1, 1, 5]].index - - bf_result = scalars_df_index.loc[bf_index] - pd_result = scalars_pandas_df_index.loc[pd_index] - - pd.testing.assert_frame_equal( - bf_result.to_pandas(), - pd_result, - ) - - -def test_loc_bf_index_integer_index_renamed_col( - scalars_df_index, scalars_pandas_df_index -): - scalars_df_index = scalars_df_index.rename(columns={"int64_col": "rename"}) - scalars_pandas_df_index = scalars_pandas_df_index.rename( - columns={"int64_col": "rename"} - ) - - pd_index = scalars_pandas_df_index.iloc[[0, 5, 1, 1, 5]].index - bf_index = scalars_df_index.iloc[[0, 5, 1, 1, 5]].index - - bf_result = scalars_df_index.loc[bf_index] - pd_result = scalars_pandas_df_index.loc[pd_index] - - pd.testing.assert_frame_equal( - bf_result.to_pandas(), - pd_result, - ) - - -@pytest.mark.parametrize( - ("subset"), - [ - None, - "bool_col", - ["bool_col", "int64_too"], - ], -) -@pytest.mark.parametrize( - ("keep",), - [ - (False,), - ], -) -def test_df_drop_duplicates(scalars_df_index, scalars_pandas_df_index, keep, subset): - columns = ["bool_col", "int64_too", "int64_col"] - bf_df = scalars_df_index[columns].drop_duplicates(subset, keep=keep).to_pandas() - pd_df = scalars_pandas_df_index[columns].drop_duplicates(subset, keep=keep) - pd.testing.assert_frame_equal( - pd_df, - bf_df, - ) - - -@pytest.mark.parametrize( - ("subset"), - [ - None, - ["bool_col"], - ], -) -@pytest.mark.parametrize( - ("keep",), - [ - (False,), - ], -) -def test_df_duplicated(scalars_df_index, scalars_pandas_df_index, keep, subset): - columns = ["bool_col", "int64_too", "int64_col"] - bf_series = scalars_df_index[columns].duplicated(subset, keep=keep).to_pandas() - pd_series = scalars_pandas_df_index[columns].duplicated(subset, keep=keep) - pd.testing.assert_series_equal(pd_series, bf_series, check_dtype=False) - - -def test_df_from_dict_columns_orient(): - data = {"a": [1, 2], "b": [3.3, 2.4]} - bf_result = dataframe.DataFrame.from_dict(data, orient="columns").to_pandas() - pd_result = pd.DataFrame.from_dict(data, orient="columns") - assert_frame_equal(pd_result, bf_result, check_dtype=False, check_index_type=False) - - -def test_df_from_dict_index_orient(): - data = {"a": [1, 2], "b": [3.3, 2.4]} - bf_result = dataframe.DataFrame.from_dict( - data, orient="index", columns=["col1", "col2"] - ).to_pandas() - pd_result = pd.DataFrame.from_dict(data, orient="index", columns=["col1", "col2"]) - assert_frame_equal(pd_result, bf_result, check_dtype=False, check_index_type=False) - - -def test_df_from_dict_tight_orient(): - data = { - "index": [("i1", "i2"), ("i3", "i4")], - "columns": ["col1", "col2"], - "data": [[1, 2.6], [3, 4.5]], - "index_names": ["in1", "in2"], - "column_names": ["column_axis"], - } - - bf_result = dataframe.DataFrame.from_dict(data, orient="tight").to_pandas() - pd_result = pd.DataFrame.from_dict(data, orient="tight") - assert_frame_equal(pd_result, bf_result, check_dtype=False, check_index_type=False) - - -def test_df_from_records(): - records = ((1, "a"), (2.5, "b"), (3.3, "c"), (4.9, "d")) - - bf_result = dataframe.DataFrame.from_records( - records, columns=["c1", "c2"] - ).to_pandas() - pd_result = pd.DataFrame.from_records(records, columns=["c1", "c2"]) - assert_frame_equal(pd_result, bf_result, check_dtype=False, check_index_type=False) - - -def test_df_to_dict(scalars_df_index, scalars_pandas_df_index): - unsupported = ["numeric_col"] # formatted differently - bf_result = scalars_df_index.drop(columns=unsupported).to_dict() - pd_result = scalars_pandas_df_index.drop(columns=unsupported).to_dict() - - assert bf_result == pd_result - - -def test_df_to_json_local_str(scalars_df_index, scalars_pandas_df_index): - # pandas 3.0 bugged for serializing date col - bf_result = scalars_df_index.drop(columns="date_col").to_json() - # default_handler for arrow types that have no default conversion - pd_result = scalars_pandas_df_index.drop(columns="date_col").to_json( - default_handler=str - ) - - assert bf_result == pd_result - - -def test_df_to_json_local_file(scalars_df_index, scalars_pandas_df_index): - # TODO: supply a reason why this isn't compatible with pandas 1.x - pytest.importorskip("pandas", minversion="2.0.0") - # duration not fully supported at pandas level - scalars_df_index = scalars_df_index.drop(columns="duration_col") - scalars_pandas_df_index = scalars_pandas_df_index.drop(columns="duration_col") - with ( - tempfile.TemporaryFile() as bf_result_file, - tempfile.TemporaryFile() as pd_result_file, - ): - scalars_df_index.to_json(bf_result_file, orient="table") - # default_handler for arrow types that have no default conversion - scalars_pandas_df_index.to_json( - pd_result_file, orient="table", default_handler=str - ) - - bf_result = bf_result_file.read() - pd_result = pd_result_file.read() - - assert bf_result == pd_result - - -def test_df_to_csv_local_str(scalars_df_index, scalars_pandas_df_index): - bf_result = scalars_df_index.to_csv() - # default_handler for arrow types that have no default conversion - pd_result = scalars_pandas_df_index.to_csv() - - assert bf_result == pd_result - - -def test_df_to_csv_local_file(scalars_df_index, scalars_pandas_df_index): - with ( - tempfile.TemporaryFile() as bf_result_file, - tempfile.TemporaryFile() as pd_result_file, - ): - scalars_df_index.to_csv(bf_result_file) - scalars_pandas_df_index.to_csv(pd_result_file) - - bf_result = bf_result_file.read() - pd_result = pd_result_file.read() - - assert bf_result == pd_result - - -def test_df_to_parquet_local_bytes(scalars_df_index, scalars_pandas_df_index): - # GEOGRAPHY not supported in parquet export. - unsupported = ["geography_col"] - - bf_result = scalars_df_index.drop(columns=unsupported).to_parquet() - # default_handler for arrow types that have no default conversion - pd_result = scalars_pandas_df_index.drop(columns=unsupported).to_parquet() - - assert bf_result == pd_result - - -def test_df_to_parquet_local_file(scalars_df_index, scalars_pandas_df_index): - # GEOGRAPHY not supported in parquet export. - unsupported = ["geography_col"] - with ( - tempfile.TemporaryFile() as bf_result_file, - tempfile.TemporaryFile() as pd_result_file, - ): - scalars_df_index.drop(columns=unsupported).to_parquet(bf_result_file) - scalars_pandas_df_index.drop(columns=unsupported).to_parquet(pd_result_file) - - bf_result = bf_result_file.read() - pd_result = pd_result_file.read() - - assert bf_result == pd_result - - -def test_df_to_records(scalars_df_index, scalars_pandas_df_index): - unsupported = ["numeric_col"] - bf_result = scalars_df_index.drop(columns=unsupported).to_records() - pd_result = scalars_pandas_df_index.drop(columns=unsupported).to_records() - - for bfi, pdi in zip(bf_result, pd_result): - for bfj, pdj in zip(bfi, pdi): - assert pd.isna(bfj) and pd.isna(pdj) or bfj == pdj - - -def test_df_to_string(scalars_df_index, scalars_pandas_df_index): - unsupported = ["numeric_col"] # formatted differently - - bf_result = scalars_df_index.drop(columns=unsupported).to_string() - pd_result = scalars_pandas_df_index.drop(columns=unsupported).to_string() - - assert bf_result == pd_result - - -def test_df_to_html(scalars_df_index, scalars_pandas_df_index): - unsupported = ["numeric_col"] # formatted differently - - bf_result = scalars_df_index.drop(columns=unsupported).to_html() - pd_result = scalars_pandas_df_index.drop(columns=unsupported).to_html() - - assert bf_result == pd_result - - -def test_df_to_markdown(scalars_df_index, scalars_pandas_df_index): - # Nulls have bug from tabulate https://github.com/astanin/python-tabulate/issues/231 - bf_result = scalars_df_index.dropna().to_markdown() - pd_result = scalars_pandas_df_index.dropna().to_markdown() - - assert bf_result == pd_result - - -def test_df_to_pickle(scalars_df_index, scalars_pandas_df_index): - with ( - tempfile.TemporaryFile() as bf_result_file, - tempfile.TemporaryFile() as pd_result_file, - ): - scalars_df_index.to_pickle(bf_result_file) - scalars_pandas_df_index.to_pickle(pd_result_file) - bf_result = bf_result_file.read() - pd_result = pd_result_file.read() - - assert bf_result == pd_result - - -def test_df_to_orc(scalars_df_index, scalars_pandas_df_index): - pytest.importorskip("pyarrow.orc") - unsupported = [ - "numeric_col", - "bytes_col", - "date_col", - "datetime_col", - "time_col", - "timestamp_col", - "geography_col", - "duration_col", - ] - - bf_result_file = tempfile.TemporaryFile() - pd_result_file = tempfile.TemporaryFile() - scalars_df_index.drop(columns=unsupported).to_orc(bf_result_file) - scalars_pandas_df_index.drop(columns=unsupported).reset_index().to_orc( - pd_result_file - ) - bf_result = bf_result_file.read() - pd_result = bf_result_file.read() - - assert bf_result == pd_result - - -@pytest.mark.parametrize( - ("expr",), - [ - ("new_col = int64_col + int64_too",), - ("new_col = (rowindex > 3) | bool_col",), - ("int64_too = bool_col\nnew_col2 = rowindex",), - ], -) -def test_df_eval(scalars_dfs, expr): - # 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 - - bf_result = scalars_df.eval(expr).to_pandas() - pd_result = scalars_pandas_df.eval(expr) - - pd.testing.assert_frame_equal(bf_result, pd_result) - - -@pytest.mark.parametrize( - ("expr",), - [ - ("int64_col > int64_too",), - ("bool_col",), - ("((int64_col - int64_too) % @local_var) == 0",), - ], -) -def test_df_query(scalars_dfs, expr): - # TODO: supply a reason why this isn't compatible with pandas 1.x - pytest.importorskip("pandas", minversion="2.0.0") - # local_var is referenced in expressions - local_var = 3 # NOQA - scalars_df, scalars_pandas_df = scalars_dfs - - bf_result = scalars_df.query(expr).to_pandas() - pd_result = scalars_pandas_df.query(expr) - - pd.testing.assert_frame_equal(bf_result, pd_result) - - -@pytest.mark.parametrize( - ("subset", "normalize", "ascending", "dropna"), - [ - (None, False, False, False), - (None, True, True, True), - ("bool_col", True, False, True), - ], -) -def test_df_value_counts(scalars_dfs, subset, normalize, ascending, dropna): - if pd.__version__.startswith("1."): - pytest.skip("pandas 1.x produces different column labels.") - scalars_df, scalars_pandas_df = scalars_dfs - - bf_result = ( - scalars_df[["string_col", "bool_col"]] - .value_counts(subset, normalize=normalize, ascending=ascending, dropna=dropna) - .to_pandas() - ) - pd_result = scalars_pandas_df[["string_col", "bool_col"]].value_counts( - subset, normalize=normalize, ascending=ascending, dropna=dropna - ) - - assert_series_equal( - bf_result, - pd_result, - check_dtype=False, - check_index_type=False, - # different pandas versions inconsistent for tie-handling - ignore_order=True, - ) - - -def test_df_bool_interpretation_error(scalars_df_index): - with pytest.raises(ValueError): - True if scalars_df_index else False - - -def test_assign_after_binop_row_joins(): - pd_df = pd.DataFrame( - { - "idx1": [1, 1, 1, 1, 2, 2, 2, 2], - "idx2": [10, 10, 20, 20, 10, 10, 20, 20], - "metric1": [10, 14, 2, 13, 6, 2, 9, 5], - "metric2": [25, -3, 8, 2, -1, 0, 0, -4], - }, - dtype=pd.Int64Dtype(), - ).set_index(["idx1", "idx2"]) - bf_df = dataframe.DataFrame(pd_df) - - # Expect implicit joiner to be used, preserving input cardinality rather than getting relational join - bf_df["metric_diff"] = bf_df.metric1 - bf_df.metric2 - pd_df["metric_diff"] = pd_df.metric1 - pd_df.metric2 - - assert_frame_equal(bf_df.to_pandas(), pd_df) - - -def test_df_dot_inline(session): - df1 = pd.DataFrame([[1, 2, 3], [2, 5, 7]]) - df2 = pd.DataFrame([[2, 4, 8], [1, 5, 10], [3, 6, 9]]) - - bf1 = session.read_pandas(df1) - bf2 = session.read_pandas(df2) - bf_result = bf1.dot(bf2).to_pandas() - pd_result = df1.dot(df2) - - # Patch pandas dtypes for testing parity - # Pandas uses int64 instead of Int64 (nullable) dtype. - for name in pd_result.columns: - pd_result[name] = pd_result[name].astype(pd.Int64Dtype()) - pd_result.index = pd_result.index.astype(pd.Int64Dtype()) - - pd.testing.assert_frame_equal( - bf_result, - pd_result, - ) - - -def test_df_dot_series_inline(): - left = [[1, 2, 3], [2, 5, 7]] - right = [2, 1, 3] - - bf1 = dataframe.DataFrame(left) - bf2 = series.Series(right) - bf_result = bf1.dot(bf2).to_pandas() - - df1 = pd.DataFrame(left) - df2 = pd.Series(right) - pd_result = df1.dot(df2) - - # Patch pandas dtypes for testing parity - # Pandas result is int64 instead of Int64 (nullable) dtype. - pd_result = pd_result.astype(pd.Int64Dtype()) - pd_result.index = pd_result.index.astype(pd.Int64Dtype()) - - pd.testing.assert_series_equal( - bf_result, - pd_result, - ) - - -@pytest.mark.parametrize( - ("col_names", "ignore_index"), - [ - pytest.param(["A"], False, id="one_array_false"), - pytest.param(["A"], True, id="one_array_true"), - pytest.param(["B"], False, id="one_float_false"), - pytest.param(["B"], True, id="one_float_true"), - pytest.param(["A", "C"], False, id="two_arrays_false"), - pytest.param(["A", "C"], True, id="two_arrays_true"), - ], -) -def test_dataframe_explode(col_names, ignore_index, session): - data = { - "A": [[0, 1, 2], [], [3, 4]], - "B": 3, - "C": [["a", "b", "c"], np.nan, ["d", "e"]], - } - - df = bpd.DataFrame(data, session=session) - pd_df = df.to_pandas() - pd_result = pd_df.explode(col_names, ignore_index=ignore_index) - bf_result = df.explode(col_names, ignore_index=ignore_index) - - # Check that to_pandas() results in at most a single query execution - bf_materialized = bf_result.to_pandas() - - pd.testing.assert_frame_equal( - bf_materialized, - pd_result, - check_index_type=False, - check_dtype=False, - ) - - -@pytest.mark.parametrize( - ("ignore_index", "ordered"), - [ - pytest.param(True, True, id="include_index_ordered"), - pytest.param(True, False, id="include_index_unordered"), - pytest.param(False, True, id="ignore_index_ordered"), - ], -) -def test_dataframe_explode_reserve_order(session, ignore_index, ordered): - data = { - "a": [np.random.randint(0, 10, 10) for _ in range(10)], - "b": [np.random.randint(0, 10, 10) for _ in range(10)], - } - df = bpd.DataFrame(data) - pd_df = pd.DataFrame(data) - - res = df.explode(["a", "b"], ignore_index=ignore_index).to_pandas(ordered=ordered) - pd_res = pd_df.explode(["a", "b"], ignore_index=ignore_index).astype( - pd.Int64Dtype() - ) - pd.testing.assert_frame_equal( - res if ordered else res.sort_index(), - pd_res, - check_index_type=False, - ) - - -@pytest.mark.parametrize( - ("col_names"), - [ - pytest.param([], id="empty", marks=pytest.mark.xfail(raises=ValueError)), - pytest.param( - ["A", "A"], id="duplicate", marks=pytest.mark.xfail(raises=ValueError) - ), - pytest.param("unknown", id="unknown", marks=pytest.mark.xfail(raises=KeyError)), - ], -) -def test_dataframe_explode_xfail(col_names): - df = bpd.DataFrame({"A": [[0, 1, 2], [], [3, 4]]}) - df.explode(col_names) - - -def test_recursion_limit_unit(scalars_df_index): - scalars_df_index = scalars_df_index[["int64_too", "int64_col", "float64_col"]] - for i in range(250): - scalars_df_index = scalars_df_index + 4 - scalars_df_index.to_pandas() diff --git a/tests/unit/test_dtypes.py b/tests/unit/test_dtypes.py index bb2b57d4090..6ceaaf911b9 100644 --- a/tests/unit/test_dtypes.py +++ b/tests/unit/test_dtypes.py @@ -1,4 +1,4 @@ -# Copyright 2025 Google LLC +# Copyright 2023 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -12,70 +12,240 @@ # See the License for the specific language governing permissions and # limitations under the License. -import db_dtypes # type: ignore +import geopandas as gpd # type: ignore +import ibis +import ibis.expr.datatypes as ibis_dtypes +import numpy as np +import pandas as pd import pyarrow as pa # type: ignore import pytest -import shapely.geometry # type: ignore import bigframes.dtypes @pytest.mark.parametrize( - ["python_type", "expected_dtype"], + ["ibis_dtype", "bigframes_dtype"], [ - (bool, bigframes.dtypes.BOOL_DTYPE), - (int, bigframes.dtypes.INT_DTYPE), - (str, bigframes.dtypes.STRING_DTYPE), - (shapely.geometry.Point, bigframes.dtypes.GEO_DTYPE), - (shapely.geometry.Polygon, bigframes.dtypes.GEO_DTYPE), - (shapely.geometry.base.BaseGeometry, bigframes.dtypes.GEO_DTYPE), + # TODO(bmil): Add ARRAY, INTERVAL, STRUCT to cover all the standard + # BigQuery data types as they appear in Ibis: + # https://cloud.google.com/bigquery/docs/reference/standard-sql/data-types + pytest.param( + ibis_dtypes.Decimal(precision=76, scale=38, nullable=True), + np.dtype("O"), + id="bignumeric", + ), + pytest.param(ibis_dtypes.boolean, pd.BooleanDtype(), id="bool"), + pytest.param(ibis_dtypes.binary, np.dtype("O"), id="bytes"), + pytest.param(ibis_dtypes.date, pd.ArrowDtype(pa.date32()), id="date"), + pytest.param( + ibis_dtypes.Timestamp(), pd.ArrowDtype(pa.timestamp("us")), id="datetime" + ), + pytest.param(ibis_dtypes.float64, pd.Float64Dtype(), id="float"), + pytest.param( + ibis_dtypes.GeoSpatial(geotype="geography", srid=4326, nullable=True), + gpd.array.GeometryDtype(), + id="geography", + ), + pytest.param(ibis_dtypes.int8, pd.Int64Dtype(), id="int8-as-int64"), + pytest.param(ibis_dtypes.int64, pd.Int64Dtype(), id="int64"), + # TODO(tswast): custom dtype (or at least string dtype) for JSON objects + pytest.param(ibis_dtypes.json, np.dtype("O"), id="json"), + pytest.param( + ibis_dtypes.Decimal(precision=38, scale=9, nullable=True), + np.dtype("O"), + id="numeric", + ), + pytest.param( + ibis_dtypes.string, pd.StringDtype(storage="pyarrow"), id="string" + ), + pytest.param(ibis_dtypes.time, pd.ArrowDtype(pa.time64("us")), id="time"), + pytest.param( + ibis_dtypes.Timestamp(timezone="UTC"), + pd.ArrowDtype(pa.timestamp("us", tz="UTC")), # type: ignore + id="timestamp", + ), ], ) -def test_bigframes_type_supports_python_types(python_type, expected_dtype): - got_dtype = bigframes.dtypes.bigframes_type(python_type) - assert got_dtype == expected_dtype +def test_ibis_dtype_converts(ibis_dtype, bigframes_dtype): + """Test all the Ibis data types needed to read BigQuery tables""" + result = bigframes.dtypes.ibis_dtype_to_bigframes_dtype(ibis_dtype) + assert result == bigframes_dtype + + +def test_ibis_timestamp_pst_raises_unexpected_datatype(): + """BigQuery timestamp only supports UTC time""" + with pytest.raises(ValueError, match="Unexpected Ibis data type"): + bigframes.dtypes.ibis_dtype_to_bigframes_dtype( + ibis_dtypes.Timestamp(timezone="PST") + ) + + +def test_ibis_float32_raises_unexpected_datatype(): + """Other Ibis types not read from BigQuery are not expected""" + with pytest.raises(ValueError, match="Unexpected Ibis data type"): + bigframes.dtypes.ibis_dtype_to_bigframes_dtype(ibis_dtypes.float32) + + +IBIS_ARROW_DTYPES = ( + (ibis_dtypes.boolean, pa.bool_()), + (ibis_dtypes.date, pa.date32()), + (ibis_dtypes.Timestamp(), pa.timestamp("us")), + (ibis_dtypes.float64, pa.float64()), + ( + ibis_dtypes.Timestamp(timezone="UTC"), + pa.timestamp("us", tz="UTC"), + ), + ( + ibis_dtypes.Struct.from_tuples( + [ + ("name", ibis_dtypes.string()), + ("version", ibis_dtypes.int64()), + ] + ), + pa.struct( + [ + ("name", pa.string()), + ("version", pa.int64()), + ] + ), + ), + ( + ibis_dtypes.Struct.from_tuples( + [ + ( + "nested", + ibis_dtypes.Struct.from_tuples( + [ + ("field", ibis_dtypes.string()), + ] + ), + ), + ] + ), + pa.struct( + [ + ( + "nested", + pa.struct( + [ + ("field", pa.string()), + ] + ), + ), + ] + ), + ), +) + + +@pytest.mark.parametrize(("ibis_dtype", "arrow_dtype"), IBIS_ARROW_DTYPES) +def test_arrow_dtype_to_ibis_dtype(ibis_dtype, arrow_dtype): + result = bigframes.dtypes.arrow_dtype_to_ibis_dtype(arrow_dtype) + assert result == ibis_dtype + + +@pytest.mark.parametrize(("ibis_dtype", "arrow_dtype"), IBIS_ARROW_DTYPES) +def test_ibis_dtype_to_arrow_dtype(ibis_dtype, arrow_dtype): + result = bigframes.dtypes.ibis_dtype_to_arrow_dtype(ibis_dtype) + assert result == arrow_dtype @pytest.mark.parametrize( - ["scalar", "expected_dtype"], + ["bigframes_dtype", "ibis_dtype"], [ - (pa.scalar(1_000_000_000, type=pa.int64()), bigframes.dtypes.INT_DTYPE), - (pa.scalar(True, type=pa.bool_()), bigframes.dtypes.BOOL_DTYPE), - (pa.scalar("hello", type=pa.string()), bigframes.dtypes.STRING_DTYPE), - # Support NULL scalars. - (pa.scalar(None, type=pa.int64()), bigframes.dtypes.INT_DTYPE), - (pa.scalar(None, type=pa.bool_()), bigframes.dtypes.BOOL_DTYPE), - (pa.scalar(None, type=pa.string()), bigframes.dtypes.STRING_DTYPE), + # This test covers all dtypes that BigQuery DataFrames can exactly map to Ibis + (pd.BooleanDtype(), ibis_dtypes.boolean), + (pd.ArrowDtype(pa.date32()), ibis_dtypes.date), + (pd.ArrowDtype(pa.timestamp("us")), ibis_dtypes.Timestamp()), + (pd.Float64Dtype(), ibis_dtypes.float64), + (pd.Int64Dtype(), ibis_dtypes.int64), + (pd.StringDtype(storage="pyarrow"), ibis_dtypes.string), + (pd.ArrowDtype(pa.time64("us")), ibis_dtypes.time), + ( + pd.ArrowDtype(pa.timestamp("us", tz="UTC")), # type: ignore + ibis_dtypes.Timestamp(timezone="UTC"), + ), + ], + ids=[ + "boolean", + "date", + "datetime", + "float", + "int", + "string", + "time", + "timestamp", ], ) -def test_infer_literal_type_arrow_scalar(scalar, expected_dtype): - assert bigframes.dtypes.infer_literal_type(scalar) == expected_dtype +def test_bigframes_dtype_converts(ibis_dtype, bigframes_dtype): + """Test all the Ibis data types needed to read BigQuery tables""" + result = bigframes.dtypes.bigframes_dtype_to_ibis_dtype(bigframes_dtype) + assert result == ibis_dtype @pytest.mark.parametrize( - ["type_", "expected"], + ["bigframes_dtype_str", "ibis_dtype"], [ - (pa.int64(), False), - (db_dtypes.JSONArrowType(), True), - (pa.struct([("int", pa.int64()), ("str", pa.string())]), False), - (pa.struct([("int", pa.int64()), ("json", db_dtypes.JSONArrowType())]), True), - (pa.list_(pa.int64()), False), - (pa.list_(db_dtypes.JSONArrowType()), True), + # This test covers all dtypes that BigQuery DataFrames can exactly map to Ibis + ("boolean", ibis_dtypes.boolean), + ("date32[day][pyarrow]", ibis_dtypes.date), + ("timestamp[us][pyarrow]", ibis_dtypes.Timestamp()), + ("Float64", ibis_dtypes.float64), + ("Int64", ibis_dtypes.int64), + ("string[pyarrow]", ibis_dtypes.string), + ("time64[us][pyarrow]", ibis_dtypes.time), ( - pa.list_( - pa.struct([("int", pa.int64()), ("json", db_dtypes.JSONArrowType())]) - ), - True, + "timestamp[us, tz=UTC][pyarrow]", + ibis_dtypes.Timestamp(timezone="UTC"), ), + # Special case - "string" is acceptable for "string[pyarrow]" + ("string", ibis_dtypes.string), ], ) -def test_contains_db_dtypes_json_arrow_type(type_, expected): - assert bigframes.dtypes.contains_db_dtypes_json_arrow_type(type_) == expected +def test_bigframes_string_dtype_converts(ibis_dtype, bigframes_dtype_str): + """Test all the Ibis data types needed to read BigQuery tables""" + result = bigframes.dtypes.bigframes_dtype_to_ibis_dtype(bigframes_dtype_str) + assert result == ibis_dtype + + +def test_unsupported_dtype_raises_unexpected_datatype(): + """Incompatible dtypes should fail when passed into BigQuery DataFrames""" + with pytest.raises(ValueError, match="Unexpected data type"): + bigframes.dtypes.bigframes_dtype_to_ibis_dtype(np.float32) + + +def test_unsupported_dtype_str_raises_unexpected_datatype(): + """Incompatible dtypes should fail when passed into BigQuery DataFrames""" + with pytest.raises(ValueError, match="Unexpected data type"): + bigframes.dtypes.bigframes_dtype_to_ibis_dtype("int64") + + +@pytest.mark.parametrize( + ["literal", "ibis_scalar"], + [ + (True, ibis.literal(True, ibis_dtypes.boolean)), + (5, ibis.literal(5, ibis_dtypes.int64)), + (-33.2, ibis.literal(-33.2, ibis_dtypes.float64)), + ], +) +def test_literal_to_ibis_scalar_converts(literal, ibis_scalar): + assert bigframes.dtypes.literal_to_ibis_scalar(literal).equals(ibis_scalar) + + +def test_literal_to_ibis_scalar_throws_on_incompatible_literal(): + with pytest.raises( + ValueError, + ): + bigframes.dtypes.literal_to_ibis_scalar({"mykey": "myval"}) + + +def test_remote_function_io_types_are_supported_bigframes_types(): + from ibis.expr.datatypes.core import dtype as python_type_to_bigquery_type + from bigframes.remote_function import ( + SUPPORTED_IO_PYTHON_TYPES as rf_supported_io_types, + ) -def test_convert_to_schema_field_list_description(): - bf_dtype = bigframes.dtypes.OBJ_REF_DTYPE - list_bf_dtype = bigframes.dtypes.list_type(bf_dtype) - field = bigframes.dtypes.convert_to_schema_field("my_list", list_bf_dtype) - assert field.description == "bigframes_dtype: OBJ_REF_DTYPE" - assert field.mode == "REPEATED" + for python_type in rf_supported_io_types: + ibis_type = python_type_to_bigquery_type(python_type) + assert ibis_type in bigframes.dtypes.IBIS_TO_BIGFRAMES diff --git a/tests/unit/test_features.py b/tests/unit/test_features.py deleted file mode 100644 index 20642aec343..00000000000 --- a/tests/unit/test_features.py +++ /dev/null @@ -1,49 +0,0 @@ -# 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. -# 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 -import pytest - -import bigframes.features - - -def test_pandas_installed_version_returns_cached(): - versions = bigframes.features.PandasVersions() - versions._installed_version = object() - assert versions.installed_version is versions._installed_version - - -def test_pandas_installed_version_returns_parsed_version(monkeypatch): - versions = bigframes.features.PandasVersions() - monkeypatch.setattr(pandas, "__version__", "1.2.3") - major, minor, micro = versions.installed_version - assert major == "1" - assert minor == "2" - assert micro == "3" - - -@pytest.mark.parametrize( - ("version", "expected"), - ( - ("1.2.3", False), - ("1.5.3", False), - ("2.0.0", True), - ("2.2.3", True), - ("3.0.0", True), - ), -) -def test_pandas_is_arrow_list_dtype_usable(version, expected): - versions = bigframes.features.PandasVersions() - versions._installed_version = version.split(".") - assert versions.is_arrow_list_dtype_usable == expected diff --git a/tests/unit/test_formatting_helper.py b/tests/unit/test_formatting_helper.py new file mode 100644 index 00000000000..ea29869e824 --- /dev/null +++ b/tests/unit/test_formatting_helper.py @@ -0,0 +1,17 @@ +import pytest + +import bigframes.formatting_helpers as formatter + + +@pytest.mark.parametrize( + "test_input, expected", [(None, "N/A"), ("string", "N/A"), (100000, "100.0 kB")] +) +def test_get_formatted_bytes(test_input, expected): + assert formatter.get_formatted_bytes(test_input) == expected + + +@pytest.mark.parametrize( + "test_input, expected", [(None, None), ("string", "string"), (100000, "a minute")] +) +def test_get_formatted_time(test_input, expected): + assert formatter.get_formatted_time(test_input) == expected diff --git a/tests/unit/test_formatting_helpers.py b/tests/unit/test_formatting_helpers.py index 8917f540501..9db9b372e21 100644 --- a/tests/unit/test_formatting_helpers.py +++ b/tests/unit/test_formatting_helpers.py @@ -14,14 +14,12 @@ import unittest.mock as mock -import bigframes_vendored.constants as constants import google.api_core.exceptions as api_core_exceptions import google.cloud.bigquery as bigquery import pytest -import bigframes.core.events as bfevents +import bigframes.constants as constants import bigframes.formatting_helpers as formatting_helpers -import bigframes.version def test_wait_for_query_job_error_includes_feedback_link(): @@ -31,7 +29,7 @@ def test_wait_for_query_job_error_includes_feedback_link(): ) with pytest.raises(api_core_exceptions.BadRequest) as cap_exc: - formatting_helpers.wait_for_job(mock_query_job) + formatting_helpers.wait_for_query_job(mock_query_job) cap_exc.match("Test message 123.") cap_exc.match(constants.FEEDBACK_LINK) @@ -46,195 +44,3 @@ def test_wait_for_job_error_includes_feedback_link(): cap_exc.match("Test message 123.") cap_exc.match(constants.FEEDBACK_LINK) - - -def test_wait_for_job_error_includes_version(): - mock_job = mock.create_autospec(bigquery.LoadJob) - mock_job.result.side_effect = api_core_exceptions.BadRequest("Test message 123.") - - with pytest.raises(api_core_exceptions.BadRequest) as cap_exc: - formatting_helpers.wait_for_job(mock_job) - - cap_exc.match("Test message 123.") - cap_exc.match(bigframes.version.__version__) - - -@pytest.mark.parametrize( - "test_input, expected", [(None, "N/A"), ("string", "N/A"), (100000, "100.0 kB")] -) -def test_get_formatted_bytes(test_input, expected): - assert formatting_helpers.get_formatted_bytes(test_input) == expected - - -@pytest.mark.parametrize( - "test_input, expected", [(None, None), ("string", "string"), (66000, "a minute")] -) -def test_get_formatted_time(test_input, expected): - assert formatting_helpers.get_formatted_time(test_input) == expected - - -def test_render_bqquery_sent_event_html(): - event = bfevents.BigQuerySentEvent( - query="SELECT * FROM my_table", - job_id="my-job-id", - location="us-central1", - billing_project="my-project", - ) - html = formatting_helpers.render_bqquery_sent_event_html(event) - assert "SELECT * FROM my_table" in html - assert "my-job-id" in html - assert "us-central1" in html - assert "my-project" in html - assert "
" in html - - -def test_render_bqquery_sent_event_plaintext(): - event = bfevents.BigQuerySentEvent( - query="SELECT * FROM my_table", - job_id="my-job-id", - location="us-central1", - billing_project="my-project", - ) - text = formatting_helpers.render_bqquery_sent_event_plaintext(event) - assert "my-job-id" in text - assert "us-central1" in text - assert "my-project" in text - assert "SELECT * FROM my_table" not in text - - -def test_render_bqquery_retry_event_html(): - event = bfevents.BigQueryRetryEvent( - query="SELECT * FROM my_table", - job_id="my-job-id", - location="us-central1", - billing_project="my-project", - ) - html = formatting_helpers.render_bqquery_retry_event_html(event) - assert "Retrying query" in html - assert "SELECT * FROM my_table" in html - assert "my-job-id" in html - assert "us-central1" in html - assert "my-project" in html - assert "
" in html - - -def test_render_bqquery_retry_event_plaintext(): - event = bfevents.BigQueryRetryEvent( - query="SELECT * FROM my_table", - job_id="my-job-id", - location="us-central1", - billing_project="my-project", - ) - text = formatting_helpers.render_bqquery_retry_event_plaintext(event) - assert "Retrying query" in text - assert "my-job-id" in text - assert "us-central1" in text - assert "my-project" in text - assert "SELECT * FROM my_table" not in text - - -def test_render_bqquery_received_event_html(): - mock_plan_entry = mock.create_autospec( - bigquery.job.query.QueryPlanEntry, instance=True - ) - mock_plan_entry.__str__.return_value = "mocked plan" - event = bfevents.BigQueryReceivedEvent( - job_id="my-job-id", - location="us-central1", - billing_project="my-project", - state="RUNNING", - query_plan=[mock_plan_entry], - ) - html = formatting_helpers.render_bqquery_received_event_html(event) - assert "Query" in html - assert "my-job-id" in html - assert "is RUNNING" in html - assert "
" in html - assert "mocked plan" in html - - -def test_render_bqquery_received_event_plaintext(): - event = bfevents.BigQueryReceivedEvent( - job_id="my-job-id", - location="us-central1", - billing_project="my-project", - state="RUNNING", - query_plan=[], - ) - text = formatting_helpers.render_bqquery_received_event_plaintext(event) - assert "Query" in text - assert "my-job-id" in text - assert "is RUNNING" in text - assert "Query Plan" not in text - - -def test_render_bqquery_finished_event_html(): - event = bfevents.BigQueryFinishedEvent( - job_id="my-job-id", - location="us-central1", - billing_project="my-project", - total_bytes_processed=1000, - slot_millis=2000, - ) - html = formatting_helpers.render_bqquery_finished_event_html(event) - assert "Query" in html - assert "my-job-id" in html - assert "processed 1.0 kB" in html - assert "2 seconds of slot time" in html - - -def test_render_bqquery_finished_event_plaintext(): - event = bfevents.BigQueryFinishedEvent( - job_id="my-job-id", - location="us-central1", - billing_project="my-project", - total_bytes_processed=1000, - slot_millis=2000, - ) - text = formatting_helpers.render_bqquery_finished_event_plaintext(event) - assert "Query" in text - assert "my-job-id" in text - assert "finished" in text - assert "1.0 kB processed" in text - assert "Slot time: 2 seconds" in text - - -def test_get_job_url(): - job_id = "my-job-id" - location = "us-central1" - project_id = "my-project" - expected_url = ( - f"https://console.cloud.google.com/bigquery?project={project_id}" - f"&j=bq:{location}:{job_id}&page=queryresults" - ) - - actual_url = formatting_helpers.get_job_url( - job_id=job_id, location=location, project_id=project_id - ) - assert actual_url == expected_url - - -def test_progress_callback_falls_back_to_global(): - event = bfevents.BigQuerySentEvent( - query="SELECT * FROM my_table", - ) - envelope = bfevents.EventEnvelope(event=event, progress_bar=bfevents._DEFAULT) - - with mock.patch("bigframes._config.options.display.progress_bar", "terminal"): - with mock.patch("bigframes.formatting_helpers.in_ipython", return_value=False): - with mock.patch("builtins.print") as mock_print: - formatting_helpers.create_progress_callback()(envelope) - mock_print.assert_called_once() - - -def test_progress_callback_respects_envelope_progress_bar(): - event = bfevents.BigQuerySentEvent( - query="SELECT * FROM my_table", - ) - envelope = bfevents.EventEnvelope(event=event, progress_bar=None) - - with mock.patch("bigframes._config.options.display.progress_bar", "terminal"): - with mock.patch("bigframes.formatting_helpers.in_ipython", return_value=False): - with mock.patch("builtins.print") as mock_print: - formatting_helpers.create_progress_callback()(envelope) - mock_print.assert_not_called() diff --git a/tests/unit/test_groupby_transpile.py b/tests/unit/test_groupby_transpile.py deleted file mode 100644 index 4f841bc1b2d..00000000000 --- a/tests/unit/test_groupby_transpile.py +++ /dev/null @@ -1,200 +0,0 @@ -# Copyright 2026 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 pathlib import Path - -import pandas as pd -import pytest -from pandas.testing import assert_frame_equal, assert_series_equal - -import bigframes -import bigframes.core.global_session -import bigframes.pandas as bpd -from bigframes.testing.utils import convert_pandas_dtypes - -pytest.importorskip("polars") -pytest.importorskip("pandas", minversion="2.0.0") - -CURRENT_DIR = Path(__file__).parent -DATA_DIR = CURRENT_DIR.parent / "data" - - -@pytest.fixture(scope="module") -def scalars_pandas_df_index(): - df = pd.read_json( - DATA_DIR / "scalars.jsonl", - lines=True, - ) - convert_pandas_dtypes(df, bytes_col=True) - - df = df.set_index("rowindex", drop=False) - df.index.name = None - return df.set_index("rowindex").sort_index() - - -@pytest.fixture(scope="module", autouse=True) -def session(): - # import inline to allow polars importorskip to happen first - from bigframes.testing import polars_session - - with bpd.option_context("experiments.enable_python_transpiler", True): - session = polars_session.TestSession() - with bigframes.core.global_session._GlobalSessionContext(session): - yield session - - -@pytest.fixture(scope="module") -def scalars_df_index( - session: bigframes.Session, scalars_pandas_df_index -) -> bpd.DataFrame: - return session.read_pandas(scalars_pandas_df_index) - - -# Tests for groupby.agg custom lambdas - - -def test_series_groupby_agg_transpile(scalars_df_index, scalars_pandas_df_index): - def custom_agg(s): - return s.sum() - s.mean() - - bf_df = scalars_df_index.dropna(subset=["int64_col", "bool_col"]) - pd_df = scalars_pandas_df_index.dropna(subset=["int64_col", "bool_col"]) - - bf_result = bf_df.groupby("bool_col")["int64_col"].agg(custom_agg).to_pandas() - pd_result = pd_df.groupby("bool_col")["int64_col"].agg(custom_agg) - - assert_series_equal(bf_result, pd_result, check_dtype=False) - - -def test_dataframe_groupby_agg_func_transpile( - scalars_df_index, scalars_pandas_df_index -): - def custom_agg(s): - return (s.max() - s.min()) / s.count() - - bf_df = scalars_df_index.dropna(subset=["int64_col", "int64_too", "bool_col"]) - pd_df = scalars_pandas_df_index.dropna( - subset=["int64_col", "int64_too", "bool_col"] - ) - - bf_result = ( - bf_df[["int64_col", "int64_too", "bool_col"]] - .groupby("bool_col") - .agg(custom_agg) - .to_pandas() - ) - pd_result = ( - pd_df[["int64_col", "int64_too", "bool_col"]] - .groupby("bool_col") - .agg(custom_agg) - ) - - assert_frame_equal(bf_result, pd_result, check_dtype=False) - - -def test_dataframe_groupby_agg_dict_transpile( - scalars_df_index, scalars_pandas_df_index -): - def custom_agg1(s): - return s.sum() - s.mean() - - def custom_agg2(s): - return s.max() - s.min() - - bf_df = scalars_df_index.dropna(subset=["int64_col", "int64_too", "bool_col"]) - pd_df = scalars_pandas_df_index.dropna( - subset=["int64_col", "int64_too", "bool_col"] - ) - - bf_result = ( - bf_df.groupby("bool_col") - .agg({"int64_col": custom_agg1, "int64_too": custom_agg2}) - .to_pandas() - ) - pd_result = pd_df.groupby("bool_col").agg( - {"int64_col": custom_agg1, "int64_too": custom_agg2} - ) - - assert_frame_equal(bf_result, pd_result, check_dtype=False) - - -def test_dataframe_groupby_agg_list_transpile( - scalars_df_index, scalars_pandas_df_index -): - def custom_agg1(s): - return s.sum() - s.mean() - - def custom_agg2(s): - return s.max() - s.min() - - bf_df = scalars_df_index.dropna(subset=["int64_col", "bool_col"]) - pd_df = scalars_pandas_df_index.dropna(subset=["int64_col", "bool_col"]) - - bf_result = ( - bf_df[["int64_col", "bool_col"]] - .groupby("bool_col") - .agg([custom_agg1, custom_agg2]) - .to_pandas() - ) - pd_result = ( - pd_df[["int64_col", "bool_col"]] - .groupby("bool_col") - .agg([custom_agg1, custom_agg2]) - ) - - assert_frame_equal(bf_result, pd_result, check_dtype=False) - - -# Tests for groupby.transform broadcasting lambdas - - -def test_series_groupby_transform_transpile(scalars_df_index, scalars_pandas_df_index): - def custom_transform(s): - return s - s.mean() - - bf_df = scalars_df_index.dropna(subset=["int64_col", "bool_col"]) - pd_df = scalars_pandas_df_index.dropna(subset=["int64_col", "bool_col"]) - - bf_result = ( - bf_df.groupby("bool_col")["int64_col"].transform(custom_transform).to_pandas() - ) - pd_result = pd_df.groupby("bool_col")["int64_col"].transform(custom_transform) - - assert_series_equal(bf_result, pd_result, check_dtype=False) - - -def test_dataframe_groupby_transform_transpile( - scalars_df_index, scalars_pandas_df_index -): - def custom_transform(s): - return (s - s.min()) / (s.max() - s.min()) - - bf_df = scalars_df_index.dropna(subset=["int64_col", "int64_too", "bool_col"]) - pd_df = scalars_pandas_df_index.dropna( - subset=["int64_col", "int64_too", "bool_col"] - ) - - bf_result = ( - bf_df[["int64_col", "int64_too", "bool_col"]] - .groupby("bool_col") - .transform(custom_transform) - .to_pandas() - ) - pd_result = ( - pd_df[["int64_col", "int64_too", "bool_col"]] - .groupby("bool_col") - .transform(custom_transform) - ) - - assert_frame_equal(bf_result, pd_result, check_dtype=False) diff --git a/tests/unit/test_iloc_getitem.py b/tests/unit/test_iloc_getitem.py deleted file mode 100644 index 7f030a16c92..00000000000 --- a/tests/unit/test_iloc_getitem.py +++ /dev/null @@ -1,297 +0,0 @@ -# Copyright 2026 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 typing import Generator - -import numpy as np -import pandas as pd -import pyarrow as pa -import pytest - -import bigframes -import bigframes.pandas as bpd -from bigframes.testing.utils import assert_frame_equal, assert_series_equal - -pytest.importorskip("polars") - - -@pytest.fixture(scope="module", autouse=True) -def session() -> Generator[bigframes.Session, None, None]: - import bigframes.core.global_session - from bigframes.testing import polars_session - - session = polars_session.TestSession() - with bigframes.core.global_session._GlobalSessionContext(session): - yield session - - -@pytest.fixture -def sample_df() -> bpd.DataFrame: - pd_df = pd.DataFrame( - { - "A": [1, 2, 3], - "B": [4, 5, 6], - "C": [7, 8, 9], - } - ) - return bpd.read_pandas(pd_df) - - -@pytest.fixture -def unordered_sample_df( - sample_df: bpd.DataFrame, -) -> Generator[bpd.DataFrame, None, None]: - session = sample_df._session - original_strictly_ordered = session._strictly_ordered - original_allow_ambiguity = session._allow_ambiguity - - try: - session._strictly_ordered = False - session._allow_ambiguity = True - - import unittest.mock as mock - - with ( - mock.patch.object( - type(sample_df._block.expr), - "order_ambiguous", - new_callable=mock.PropertyMock, - ) as mock_ambiguous, - mock.patch.object( - type(sample_df._block), - "explicitly_ordered", - new_callable=mock.PropertyMock, - ) as mock_explicit, - ): - mock_ambiguous.return_value = True - mock_explicit.return_value = False - yield sample_df - finally: - session._strictly_ordered = original_strictly_ordered - session._allow_ambiguity = original_allow_ambiguity - - -@pytest.fixture -def duplicate_columns_df() -> bpd.DataFrame: - pd_df = pd.DataFrame( - [[1, 2, 3], [4, 5, 6], [7, 8, 9]], - columns=["A", "B", "A"], - ) - return bpd.read_pandas(pd_df) - - -def test_iloc_getitem_column_single_integer(sample_df): - bf_df = sample_df - pd_df = sample_df.to_pandas() - - bf_result = bf_df.iloc[:, 1].to_pandas() - pd_result = pd_df.iloc[:, 1] - - assert_series_equal(bf_result, pd_result) - - -def test_iloc_getitem_column_numpy_scalar(sample_df): - bf_df = sample_df - pd_df = sample_df.to_pandas() - - bf_result = bf_df.iloc[:, np.int64(1)].to_pandas() - pd_result = pd_df.iloc[:, np.int64(1)] - - assert_series_equal(bf_result, pd_result) - - -def test_iloc_getitem_columns_numpy_array(sample_df): - bf_df = sample_df - pd_df = sample_df.to_pandas() - - bf_result = bf_df.iloc[:, np.array([0, 2], dtype=np.int64)].to_pandas() - pd_result = pd_df.iloc[:, np.array([0, 2], dtype=np.int64)] - - assert_frame_equal(bf_result, pd_result) - - -def test_iloc_getitem_column_pyarrow_scalar(sample_df): - bf_df = sample_df - pd_df = sample_df.to_pandas() - - bf_result = bf_df.iloc[:, pa.scalar(1, type=pa.int64())].to_pandas() - pd_result = pd_df.iloc[:, 1] - - assert_series_equal(bf_result, pd_result) - - -def test_iloc_getitem_columns_pyarrow_array(sample_df): - bf_df = sample_df - pd_df = sample_df.to_pandas() - - bf_result = bf_df.iloc[:, pa.array([0, 2], type=pa.int64())].to_pandas() - pd_result = pd_df.iloc[:, pa.array([0, 2], type=pa.int64())] - - assert_frame_equal(bf_result, pd_result) - - -def test_iloc_getitem_row_numpy_scalar(sample_df): - bf_df = sample_df - pd_df = sample_df.to_pandas() - - bf_result = bf_df.iloc[np.int64(1)] - pd_result = pd_df.iloc[np.int64(1)] - - assert_series_equal(bf_result, pd_result) - - -def test_iloc_getitem_rows_numpy_array(sample_df): - bf_df = sample_df - pd_df = sample_df.to_pandas() - - bf_result = bf_df.iloc[np.array([0, 2], dtype=np.int64)].to_pandas() - pd_result = pd_df.iloc[np.array([0, 2], dtype=np.int64)] - - assert_frame_equal(bf_result, pd_result) - - -def test_iloc_getitem_row_pyarrow_scalar(sample_df): - bf_df = sample_df - pd_df = sample_df.to_pandas() - - bf_result = bf_df.iloc[pa.scalar(1, type=pa.int64())] - pd_result = pd_df.iloc[1] - - assert_series_equal(bf_result, pd_result) - - -def test_iloc_getitem_rows_pyarrow_array(sample_df): - bf_df = sample_df - pd_df = sample_df.to_pandas() - - bf_result = bf_df.iloc[pa.array([0, 2], type=pa.int64())].to_pandas() - pd_result = pd_df.iloc[pa.array([0, 2], type=pa.int64())] - - assert_frame_equal(bf_result, pd_result) - - -@pytest.mark.parametrize( - ["key", "value", "expected_error"], - [ - pytest.param((slice(None), 1), None, None, id="col_index"), - pytest.param((slice(0, None), 1), None, None, id="col_index_slice_0_none"), - pytest.param( - (slice(None, None, 1), 1), None, None, id="col_index_slice_none_none_1" - ), - pytest.param( - (slice(1, None), 1), - None, - bigframes.exceptions.OrderRequiredError, - id="col_index_slice_1_none", - ), - pytest.param( - (slice(None, 2), 1), - None, - bigframes.exceptions.OrderRequiredError, - id="col_index_slice_none_2", - ), - pytest.param((slice(None), 1), 99, None, id="col_setitem"), - pytest.param( - (1, slice(None)), - None, - bigframes.exceptions.OrderRequiredError, - id="row_index_slice", - ), - pytest.param( - 1, - None, - bigframes.exceptions.OrderRequiredError, - id="single_row_index", - ), - ], -) -def test_iloc_getitem_unordered(unordered_sample_df, key, value, expected_error): - if value is not None: - bf_df = unordered_sample_df.copy() - bf_df.iloc[key] = value - elif expected_error is not None: - with pytest.raises(expected_error): - unordered_sample_df.iloc[key] - else: - unordered_sample_df.iloc[key] - - -def test_iloc_getitem_duplicate_columns_single_integer(duplicate_columns_df): - bf_df = duplicate_columns_df - pd_df = duplicate_columns_df.to_pandas() - - bf_result = bf_df.iloc[:, 2].to_pandas() - pd_result = pd_df.iloc[:, 2] - - assert_series_equal(bf_result, pd_result) - - -def test_iloc_getitem_duplicate_columns_list_integer(duplicate_columns_df): - bf_df = duplicate_columns_df - pd_df = duplicate_columns_df.to_pandas() - - bf_result = bf_df.iloc[:, [0, 2]].to_pandas() - pd_result = pd_df.iloc[:, [0, 2]] - - assert_frame_equal(bf_result, pd_result) - - -def test_iloc_getitem_duplicate_columns_slice(duplicate_columns_df): - bf_df = duplicate_columns_df - pd_df = duplicate_columns_df.to_pandas() - - bf_result = bf_df.iloc[:, 1:3].to_pandas() - pd_result = pd_df.iloc[:, 1:3] - - assert_frame_equal(bf_result, pd_result) - - -def test_iloc_getitem_duplicate_columns_numpy_scalar(duplicate_columns_df): - bf_df = duplicate_columns_df - pd_df = duplicate_columns_df.to_pandas() - - bf_result = bf_df.iloc[:, np.int64(2)].to_pandas() - pd_result = pd_df.iloc[:, np.int64(2)] - - assert_series_equal(bf_result, pd_result) - - -def test_iloc_getitem_duplicate_columns_numpy_array(duplicate_columns_df): - bf_df = duplicate_columns_df - pd_df = duplicate_columns_df.to_pandas() - - bf_result = bf_df.iloc[:, np.array([0, 2], dtype=np.int64)].to_pandas() - pd_result = pd_df.iloc[:, np.array([0, 2], dtype=np.int64)] - - assert_frame_equal(bf_result, pd_result) - - -def test_iloc_getitem_duplicate_columns_pyarrow_scalar(duplicate_columns_df): - bf_df = duplicate_columns_df - pd_df = duplicate_columns_df.to_pandas() - - bf_result = bf_df.iloc[:, pa.scalar(2, type=pa.int64())].to_pandas() - pd_result = pd_df.iloc[:, 2] - - assert_series_equal(bf_result, pd_result) - - -def test_iloc_getitem_duplicate_columns_pyarrow_array(duplicate_columns_df): - bf_df = duplicate_columns_df - pd_df = duplicate_columns_df.to_pandas() - - bf_result = bf_df.iloc[:, pa.array([0, 2], type=pa.int64())].to_pandas() - pd_result = pd_df.iloc[:, pa.array([0, 2], type=pa.int64())] - - assert_frame_equal(bf_result, pd_result) diff --git a/tests/unit/test_iloc_setitem.py b/tests/unit/test_iloc_setitem.py deleted file mode 100644 index 98c515fd4ee..00000000000 --- a/tests/unit/test_iloc_setitem.py +++ /dev/null @@ -1,245 +0,0 @@ -# Copyright 2026 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 typing import Generator - -import numpy as np -import pandas as pd -import pyarrow as pa -import pytest - -import bigframes -import bigframes.pandas as bpd -from bigframes.testing.utils import assert_frame_equal - -pytest.importorskip("polars") - - -@pytest.fixture(scope="module", autouse=True) -def session() -> Generator[bigframes.Session, None, None]: - import bigframes.core.global_session - from bigframes.testing import polars_session - - session = polars_session.TestSession() - with bigframes.core.global_session._GlobalSessionContext(session): - yield session - - -@pytest.fixture -def sample_df() -> bpd.DataFrame: - pd_df = pd.DataFrame( - { - "A": [1, 2, 3], - "B": [4, 5, 6], - "C": [7, 8, 9], - } - ) - return bpd.read_pandas(pd_df) - - -def test_iloc_setitem_column_single_integer(sample_df): - bf_df = sample_df.copy() - pd_df = sample_df.to_pandas() - - bf_df.iloc[:, 1] = 99 - pd_df.iloc[:, 1] = 99 - - assert_frame_equal(bf_df.to_pandas(), pd_df) - - -def test_iloc_setitem_column_single_integer_negative(sample_df): - bf_df = sample_df.copy() - pd_df = sample_df.to_pandas() - - bf_df.iloc[:, -1] = 99 - pd_df.iloc[:, -1] = 99 - - assert_frame_equal(bf_df.to_pandas(), pd_df) - - -def test_iloc_setitem_columns_list_integer(sample_df): - bf_df = sample_df.copy() - pd_df = sample_df.to_pandas() - - bf_df.iloc[:, [0, 2]] = [99, 88] - pd_df.iloc[:, [0, 2]] = [99, 88] - - assert_frame_equal(bf_df.to_pandas(), pd_df) - - -def test_iloc_setitem_columns_slice(sample_df): - bf_df = sample_df.copy() - pd_df = sample_df.to_pandas() - - bf_df.iloc[:, 0:2] = 99 - pd_df.iloc[:, 0:2] = 99 - - assert_frame_equal(bf_df.to_pandas(), pd_df) - - -def test_iloc_setitem_columns_boolean_mask(sample_df): - bf_df = sample_df.copy() - pd_df = sample_df.to_pandas() - - mask = [True, False, True] - bf_df.iloc[:, mask] = 99 - pd_df.iloc[:, np.array(mask)] = 99 - - assert_frame_equal(bf_df.to_pandas(), pd_df) - - -def test_iloc_setitem_columns_dataframe(sample_df): - bf_df = sample_df.copy() - pd_df = sample_df.to_pandas() - - value_df = bpd.DataFrame({"B": [99, 88, 77], "C": [66, 55, 44]}) - bf_df.iloc[:, 1:3] = value_df - pd_df.iloc[:, 1:3] = value_df.to_pandas() - - assert_frame_equal(bf_df.to_pandas(), pd_df) - - -def test_iloc_setitem_column_numpy_scalar(sample_df): - bf_df = sample_df.copy() - pd_df = sample_df.to_pandas() - - bf_df.iloc[:, np.int64(1)] = 99 - pd_df.iloc[:, np.int64(1)] = 99 - - assert_frame_equal(bf_df.to_pandas(), pd_df) - - -def test_iloc_setitem_columns_numpy_array(sample_df): - bf_df = sample_df.copy() - pd_df = sample_df.to_pandas() - - bf_df.iloc[:, np.array([0, 2], dtype=np.int64)] = [99, 88] - pd_df.iloc[:, np.array([0, 2], dtype=np.int64)] = [99, 88] - - assert_frame_equal(bf_df.to_pandas(), pd_df) - - -def test_iloc_setitem_column_pyarrow_scalar(sample_df): - bf_df = sample_df.copy() - pd_df = sample_df.to_pandas() - - bf_df.iloc[:, pa.scalar(1, type=pa.int64())] = 99 - pd_df.iloc[:, 1] = 99 - - assert_frame_equal(bf_df.to_pandas(), pd_df) - - -def test_iloc_setitem_columns_pyarrow_array(sample_df): - bf_df = sample_df.copy() - pd_df = sample_df.to_pandas() - - bf_df.iloc[:, pa.array([0, 2], type=pa.int64())] = [99, 88] - pd_df.iloc[:, pa.array([0, 2], type=pa.int64())] = [99, 88] - - assert_frame_equal(bf_df.to_pandas(), pd_df) - - -@pytest.mark.parametrize( - ["key", "expected_error"], - [ - pytest.param((slice(None), 3), IndexError, id="out_of_bounds_positive"), - pytest.param((slice(None), -4), IndexError, id="out_of_bounds_negative"), - pytest.param((0, 1), NotImplementedError, id="invalid_row_indexer"), - pytest.param((slice(None), "B"), TypeError, id="invalid_col_indexer_type"), - ], -) -def test_iloc_setitem_column_errors(sample_df, key, expected_error): - bf_df = sample_df.copy() - - with pytest.raises(expected_error): - bf_df.iloc[key] = 99 - - -@pytest.fixture -def duplicate_columns_df() -> bpd.DataFrame: - pd_df = pd.DataFrame( - [[1, 2, 3], [4, 5, 6], [7, 8, 9]], - columns=["A", "B", "A"], - ) - return bpd.read_pandas(pd_df) - - -def test_iloc_setitem_duplicate_columns_single_integer(duplicate_columns_df): - bf_df = duplicate_columns_df.copy() - pd_df = duplicate_columns_df.to_pandas() - - bf_df.iloc[:, 2] = 99 - pd_df.iloc[:, 2] = 99 - - assert_frame_equal(bf_df.to_pandas(), pd_df) - - -def test_iloc_setitem_duplicate_columns_list_integer(duplicate_columns_df): - bf_df = duplicate_columns_df.copy() - pd_df = duplicate_columns_df.to_pandas() - - bf_df.iloc[:, [0, 2]] = [99, 88] - pd_df.iloc[:, [0, 2]] = [99, 88] - - assert_frame_equal(bf_df.to_pandas(), pd_df) - - -def test_iloc_setitem_duplicate_columns_slice(duplicate_columns_df): - bf_df = duplicate_columns_df.copy() - pd_df = duplicate_columns_df.to_pandas() - - bf_df.iloc[:, 1:3] = 99 - pd_df.iloc[:, 1:3] = 99 - - assert_frame_equal(bf_df.to_pandas(), pd_df) - - -def test_iloc_setitem_duplicate_columns_numpy_scalar(duplicate_columns_df): - bf_df = duplicate_columns_df.copy() - pd_df = duplicate_columns_df.to_pandas() - - bf_df.iloc[:, np.int64(2)] = 99 - pd_df.iloc[:, np.int64(2)] = 99 - - assert_frame_equal(bf_df.to_pandas(), pd_df) - - -def test_iloc_setitem_duplicate_columns_numpy_array(duplicate_columns_df): - bf_df = duplicate_columns_df.copy() - pd_df = duplicate_columns_df.to_pandas() - - bf_df.iloc[:, np.array([0, 2], dtype=np.int64)] = [99, 88] - pd_df.iloc[:, np.array([0, 2], dtype=np.int64)] = [99, 88] - - assert_frame_equal(bf_df.to_pandas(), pd_df) - - -def test_iloc_setitem_duplicate_columns_pyarrow_scalar(duplicate_columns_df): - bf_df = duplicate_columns_df.copy() - pd_df = duplicate_columns_df.to_pandas() - - bf_df.iloc[:, pa.scalar(2, type=pa.int64())] = 99 - pd_df.iloc[:, 2] = 99 - - assert_frame_equal(bf_df.to_pandas(), pd_df) - - -def test_iloc_setitem_duplicate_columns_pyarrow_array(duplicate_columns_df): - bf_df = duplicate_columns_df.copy() - pd_df = duplicate_columns_df.to_pandas() - - bf_df.iloc[:, pa.array([0, 2], type=pa.int64())] = [99, 88] - pd_df.iloc[:, pa.array([0, 2], type=pa.int64())] = [99, 88] - - assert_frame_equal(bf_df.to_pandas(), pd_df) diff --git a/tests/unit/test_index.py b/tests/unit/test_index.py deleted file mode 100644 index b875d56e7a0..00000000000 --- a/tests/unit/test_index.py +++ /dev/null @@ -1,51 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import pandas as pd -import pytest - -from bigframes.testing import mocks - - -def test_index_rename(monkeypatch: pytest.MonkeyPatch): - dataframe = mocks.create_dataframe( - monkeypatch, data={"idx": [], "col": []} - ).set_index("idx") - index = dataframe.index - assert index.name == "idx" - renamed = index.rename("my_index_name") - assert renamed.name == "my_index_name" - - -def test_index_rename_inplace_returns_none(monkeypatch: pytest.MonkeyPatch): - dataframe = mocks.create_dataframe( - monkeypatch, data={"idx": [], "col": []} - ).set_index("idx") - index = dataframe.index - assert index.name == "idx" - assert index.rename("my_index_name", inplace=True) is None - - # Make sure the linked DataFrame is updated, too. - assert dataframe.index.name == "my_index_name" - assert index.name == "my_index_name" - - -def test_index_to_list(monkeypatch: pytest.MonkeyPatch): - pd_index = pd.Index([1, 2, 3], name="my_index") - df = mocks.create_dataframe( - monkeypatch, - data={"my_index": [1, 2, 3]}, - ).set_index("my_index") - bf_index = df.index - assert bf_index.to_list() == pd_index.to_list() diff --git a/tests/unit/test_interchange.py b/tests/unit/test_interchange.py deleted file mode 100644 index 87f6c91e237..00000000000 --- a/tests/unit/test_interchange.py +++ /dev/null @@ -1,108 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import pathlib -from typing import Generator - -import pandas as pd -import pandas.api.interchange as pd_interchange -import pandas.testing -import pytest - -import bigframes -import bigframes.pandas as bpd -from bigframes.testing.utils import convert_pandas_dtypes - -pytest.importorskip("polars") -pytest.importorskip("pandas", minversion="2.0.0") - -CURRENT_DIR = pathlib.Path(__file__).parent -DATA_DIR = CURRENT_DIR.parent / "data" - - -@pytest.fixture(scope="module", autouse=True) -def session() -> Generator[bigframes.Session, None, None]: - import bigframes.core.global_session - from bigframes.testing import polars_session - - session = polars_session.TestSession() - with bigframes.core.global_session._GlobalSessionContext(session): - yield session - - -@pytest.fixture(scope="module") -def scalars_pandas_df_index() -> pd.DataFrame: - """pd.DataFrame pointing at test data.""" - - df = pd.read_json( - DATA_DIR / "scalars.jsonl", - lines=True, - ) - convert_pandas_dtypes(df, bytes_col=True) - - df = df.set_index("rowindex", drop=False) - df.index.name = None - return df.set_index("rowindex").sort_index() - - -def test_interchange_df_logical_properties(session): - df = bpd.DataFrame({"a": [1, 2, 3], 2: [4, 5, 6]}, session=session) - interchange_df = df.__dataframe__() - assert interchange_df.num_columns() == 2 - assert interchange_df.num_rows() == 3 - assert interchange_df.column_names() == ["a", "2"] - - -def test_interchange_column_logical_properties(session): - df = bpd.DataFrame( - { - "nums": [1, 2, 3, None, None], - "animals": ["cat", "dog", "mouse", "horse", "turtle"], - }, - session=session, - ) - interchange_df = df.__dataframe__() - - assert interchange_df.get_column_by_name("nums").size() == 5 - assert interchange_df.get_column(0).null_count == 2 - - assert interchange_df.get_column_by_name("animals").size() == 5 - assert interchange_df.get_column(1).null_count == 0 - - -def test_interchange_to_pandas(session, scalars_pandas_df_index): - # A few limitations: - # 1) Limited datatype support - # 2) Pandas converts null to NaN/False, rather than use nullable or pyarrow types - # 3) Indices aren't preserved by interchange format - unsupported_cols = [ - "bytes_col", - "date_col", - "numeric_col", - "time_col", - "duration_col", - "geography_col", - ] - scalars_pandas_df_index = scalars_pandas_df_index.drop(columns=unsupported_cols) - scalars_pandas_df_index = scalars_pandas_df_index.bfill().ffill() - bf_df = session.read_pandas(scalars_pandas_df_index) - - from_ix = pd_interchange.from_dataframe(bf_df) - - # interchange format does not include index, so just reset both indices before comparison - pandas.testing.assert_frame_equal( - scalars_pandas_df_index.reset_index(drop=True), - from_ix.reset_index(drop=True), - check_dtype=False, - ) diff --git a/tests/unit/test_local_data.py b/tests/unit/test_local_data.py deleted file mode 100644 index 1537c896fb2..00000000000 --- a/tests/unit/test_local_data.py +++ /dev/null @@ -1,186 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -import pandas as pd -import pandas.testing -import pyarrow as pa - -from bigframes import dtypes -from bigframes.core import local_data - -pd_data = pd.DataFrame( - { - "ints": [10, 20, 30, 40, 50], - "nested_ints": [[1, 2], [], [3, 4, 5], [], [20, 30]], - "structs": [{"a": 100}, None, {}, {"b": 200}, {"b": 300}], - } -) - -pd_data_normalized = pd.DataFrame( - { - "ints": pd.Series([10, 20, 30, 40, 50], dtype=dtypes.INT_DTYPE), - "nested_ints": pd.Series( - [[1, 2], [], [3, 4, 5], [], [20, 30]], - dtype=pd.ArrowDtype(pa.list_(pa.int64())), - ), - "structs": pd.Series( - [{"a": 100}, None, {}, {"b": 200}, {"b": 300}], - dtype=pd.ArrowDtype(pa.struct({"a": pa.int64(), "b": pa.int64()})), - ), - } -) - - -def test_local_data_well_formed_round_trip(): - local_entry = local_data.ManagedArrowTable.from_pandas(pd_data) - result = pd.DataFrame(local_entry.itertuples(), columns=pd_data.columns) - result = result.assign( - **{ - col: result[col].astype(pd_data_normalized[col].dtype) - for col in pd_data_normalized.columns - } - ) - pandas.testing.assert_frame_equal(pd_data_normalized, result, check_dtype=False) - - -def test_local_data_small_sizes_round_trip(): - pyarrow_version = int(pa.__version__.split(".")[0]) - - int8s = [126, 127, -127, -128, 0, 1, -1] - uint8s = [254, 255, 1, 0, 128, 129, 127] - int16s = [32766, 32767, -32766, -32767, 0, 1, -1] - uint16s = [65534, 65535, 1, 0, 32768, 32769, 32767] - int32s = [2**31 - 2, 2**31 - 1, -(2**31) + 1, -(2**31), 0, 1, -1] - uint32s = [2**32 - 2, 2**32 - 1, 1, 0, 2**31, 2**31 + 1, 2**31 - 1] - float16s = [ - # Test some edge cases from: - # https://en.wikipedia.org/wiki/Half-precision_floating-point_format#Precision_limitations - float.fromhex("0x1.0p-24"), # (2 ** -24).hex() - float.fromhex("-0x1.0p-24"), - float.fromhex("0x1.ffcp-13"), # ((2 ** -12) - (2 ** -23)).hex() - float.fromhex("-0x1.ffcp-13"), - 0, - float.fromhex("0x1.ffcp+14"), # (32768.0 - 16).hex() - float.fromhex("-0x1.ffcp+14"), - ] - float32s = [ - # Test some edge cases from: - # https://en.wikipedia.org/wiki/Single-precision_floating-point_format#Notable_single-precision_cases - # and - # https://en.wikipedia.org/wiki/Single-precision_floating-point_format#Precision_limitations_on_decimal_values_(between_1_and_16777216) - float.fromhex("0x1.0p-149"), # (2 ** -149).hex() - float.fromhex("-0x1.0p-149"), # (2 ** -149).hex() - float.fromhex("0x1.fffffep-1"), # (1.0 - (2 ** -24)).hex() - float.fromhex("-0x1.fffffep-1"), - 0, - float.fromhex("0x1.fffffcp-127"), # ((2 ** -126) * (1 - 2 ** -23)).hex() - float.fromhex("-0x1.fffffcp-127"), # ((2 ** -126) * (1 - 2 ** -23)).hex() - ] - small_data = { - "int8": pd.Series(int8s, dtype=pd.Int8Dtype()), - "int16": pd.Series(int16s, dtype=pd.Int16Dtype()), - "int32": pd.Series(int32s, dtype=pd.Int32Dtype()), - "uint8": pd.Series(uint8s, dtype=pd.UInt8Dtype()), - "uint16": pd.Series(uint16s, dtype=pd.UInt16Dtype()), - "uint32": pd.Series(uint32s, dtype=pd.UInt32Dtype()), - "float32": pd.Series(float32s, dtype="float32"), - } - expected_data = { - "int8": pd.Series(int8s, dtype=pd.Int64Dtype()), - "int16": pd.Series(int16s, dtype=pd.Int64Dtype()), - "int32": pd.Series(int32s, dtype=pd.Int64Dtype()), - "uint8": pd.Series(uint8s, dtype=pd.Int64Dtype()), - "uint16": pd.Series(uint16s, dtype=pd.Int64Dtype()), - "uint32": pd.Series(uint32s, dtype=pd.Int64Dtype()), - "float32": pd.Series(float32s, dtype=pd.Float64Dtype()), - } - - # Casting from float16 added in version 16. - # https://arrow.apache.org/blog/2024/04/20/16.0.0-release/#:~:text=Enhancements,New%20Features - if pyarrow_version >= 16: - small_data["float16"] = pd.Series(float16s, dtype="float16") - expected_data["float16"] = pd.Series(float16s, dtype=pd.Float64Dtype()) - - small_pd = pd.DataFrame(small_data) - local_entry = local_data.ManagedArrowTable.from_pandas(small_pd) - result = pd.DataFrame(local_entry.itertuples(), columns=small_pd.columns) - - expected = pd.DataFrame(expected_data) - pandas.testing.assert_frame_equal(expected, result, check_dtype=False) - - -def test_local_data_well_formed_round_trip_chunked(): - pa_table = pa.Table.from_pandas(pd_data, preserve_index=False) - as_rechunked_pyarrow = pa.Table.from_batches(pa_table.to_batches(max_chunksize=2)) - local_entry = local_data.ManagedArrowTable.from_pyarrow(as_rechunked_pyarrow) - result = pd.DataFrame(local_entry.itertuples(), columns=pd_data.columns) - result = result.assign( - **{ - col: result[col].astype(pd_data_normalized[col].dtype) - for col in pd_data_normalized.columns - } - ) - pandas.testing.assert_frame_equal(pd_data_normalized, result, check_dtype=False) - - -def test_local_data_well_formed_round_trip_sliced(): - pa_table = pa.Table.from_pandas(pd_data, preserve_index=False) - as_rechunked_pyarrow = pa.Table.from_batches(pa_table.slice(0, 4).to_batches()) - local_entry = local_data.ManagedArrowTable.from_pyarrow(as_rechunked_pyarrow) - result = pd.DataFrame(local_entry.itertuples(), columns=pd_data.columns) - result = result.assign( - **{ - col: result[col].astype(pd_data_normalized[col].dtype) - for col in pd_data_normalized.columns - } - ) - pandas.testing.assert_frame_equal( - pd_data_normalized[0:4].reset_index(drop=True), - result.reset_index(drop=True), - check_dtype=False, - ) - - -def test_local_data_equal_self(): - local_entry = local_data.ManagedArrowTable.from_pandas(pd_data) - assert local_entry == local_entry - assert hash(local_entry) == hash(local_entry) - - -def test_local_data_not_equal_other(): - local_entry = local_data.ManagedArrowTable.from_pandas(pd_data) - local_entry2 = local_data.ManagedArrowTable.from_pandas(pd_data[::2]) - assert local_entry != local_entry2 - assert hash(local_entry) != hash(local_entry2) - - -def test_local_data_itertuples_struct_none(): - pd_data = pd.DataFrame( - { - "structs": [{"a": 100}, None, {"b": 200}, {"b": 300}], - } - ) - local_entry = local_data.ManagedArrowTable.from_pandas(pd_data) - result = list(local_entry.itertuples()) - assert result[1][0] is None - - -def test_local_data_itertuples_list_none(): - pd_data = pd.DataFrame( - { - "lists": [[1, 2], None, [3, 4]], - } - ) - local_entry = local_data.ManagedArrowTable.from_pandas(pd_data) - result = list(local_entry.itertuples()) - assert result[1][0] == [] diff --git a/tests/unit/test_local_engine.py b/tests/unit/test_local_engine.py deleted file mode 100644 index fe5052771f2..00000000000 --- a/tests/unit/test_local_engine.py +++ /dev/null @@ -1,240 +0,0 @@ -# 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. -# 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 as pd -import pandas.testing -import pyarrow as pa -import pytest - -import bigframes -import bigframes.pandas as bpd -from bigframes.testing.utils import assert_frame_equal, assert_series_equal - -pytest.importorskip("polars") -pytest.importorskip("pandas", minversion="2.0.0") - - -@pytest.fixture(scope="module") -def small_inline_frame() -> pd.DataFrame: - df = pd.DataFrame( - { - "int1": pd.Series([1, 2, 3], dtype="Int64"), - "int2": pd.Series([-10, 20, 30], dtype="Int64"), - "bools": pd.Series([True, None, False], dtype="boolean"), - "strings": pd.Series(["b", "aa", "ccc"], dtype="string[pyarrow]"), - "intLists": pd.Series( - [[1, 2, 3], [4, 5, 6, 7], []], - dtype=pd.ArrowDtype(pa.list_(pa.int64())), - ), - }, - ) - df.index = df.index.astype("Int64") - return df - - -def test_polars_local_engine_series(polars_session: bigframes.Session): - bf_series = bpd.Series([1, 2, 3], session=polars_session) - pd_series = pd.Series([1, 2, 3], dtype=bf_series.dtype) - bf_result = bf_series.to_pandas() - pd_result = pd_series - assert_series_equal(bf_result, pd_result, check_index_type=False) - - -def test_polars_local_engine_add( - small_inline_frame: pd.DataFrame, polars_session: bigframes.Session -): - pd_df = small_inline_frame - bf_df = bpd.DataFrame(pd_df, session=polars_session) - - bf_result = (bf_df["int1"] + bf_df["int2"]).to_pandas() - pd_result = pd_df.int1 + pd_df.int2 - pandas.testing.assert_series_equal(bf_result, pd_result) - - -def test_polars_local_engine_order_by(small_inline_frame: pd.DataFrame, polars_session): - pd_df = small_inline_frame - bf_df = bpd.DataFrame(pd_df, session=polars_session) - - bf_result = bf_df.sort_values("strings").to_pandas() - pd_result = pd_df.sort_values("strings") - pandas.testing.assert_frame_equal(bf_result, pd_result) - - -def test_polars_local_engine_filter(small_inline_frame: pd.DataFrame, polars_session): - pd_df = small_inline_frame - bf_df = bpd.DataFrame(pd_df, session=polars_session) - - bf_result = bf_df[bf_df["int2"] >= 1].to_pandas() - pd_result = pd_df[pd_df["int2"] >= 1] # type: ignore - assert_frame_equal(bf_result, pd_result) - - -def test_polars_local_engine_series_rename_with_mapping(polars_session): - pd_series = pd.Series( - ["a", "b", "c"], index=[1, 2, 3], dtype="string[pyarrow]", name="test_name" - ) - bf_series = bpd.Series(pd_series, session=polars_session) - - bf_result = bf_series.rename({1: 100, 2: 200, 3: 300}).to_pandas() - pd_result = pd_series.rename({1: 100, 2: 200, 3: 300}) - # pd default index is int64, bf is Int64 - assert_series_equal(bf_result, pd_result, check_index_type=False) - - -def test_polars_local_engine_series_rename_with_mapping_inplace(polars_session): - pd_series = pd.Series( - ["a", "b", "c"], index=[1, 2, 3], dtype="string[pyarrow]", name="test_name" - ) - bf_series = bpd.Series(pd_series, session=polars_session) - - pd_series.rename({1: 100, 2: 200, 3: 300}, inplace=True) - assert bf_series.rename({1: 100, 2: 200, 3: 300}, inplace=True) is None - - bf_result = bf_series.to_pandas() - pd_result = pd_series - # pd default index is int64, bf is Int64 - assert_series_equal(bf_result, pd_result, check_index_type=False) - - -def test_polars_local_engine_reset_index( - small_inline_frame: pd.DataFrame, polars_session -): - pd_df = small_inline_frame - bf_df = bpd.DataFrame(pd_df, session=polars_session) - - bf_result = bf_df.reset_index().to_pandas() - pd_result = pd_df.reset_index() - # pd default index is int64, bf is Int64 - pandas.testing.assert_frame_equal(bf_result, pd_result, check_index_type=False) - - -def test_polars_local_engine_join_binop(polars_session): - pd_df_1 = pd.DataFrame({"colA": [1, None, 3], "colB": [3, 1, 2]}, index=[1, 2, 3]) - pd_df_2 = pd.DataFrame( - {"colA": [100, 200, 300], "colB": [30, 10, 40]}, index=[2, 1, 4] - ) - bf_df_1 = bpd.DataFrame(pd_df_1, session=polars_session) - bf_df_2 = bpd.DataFrame(pd_df_2, session=polars_session) - - bf_result = (bf_df_1 + bf_df_2).to_pandas() - pd_result = pd_df_1 + pd_df_2 - # Sort since different join ordering - assert_frame_equal( - bf_result.sort_index(), - pd_result.sort_index(), - check_dtype=False, - check_index_type=False, - nulls_are_nan=True, - ) - - -@pytest.mark.parametrize( - "join_type", - ["inner", "left", "right", "outer"], -) -def test_polars_local_engine_joins(join_type, polars_session): - pd_df_1 = pd.DataFrame( - {"colA": [1, None, 3], "colB": [3, 1, 2]}, index=[1, 2, 3], dtype="Int64" - ) - pd_df_2 = pd.DataFrame( - {"colC": [100, 200, 300], "colD": [30, 10, 40]}, index=[2, 1, 4], dtype="Int64" - ) - bf_df_1 = bpd.DataFrame(pd_df_1, session=polars_session) - bf_df_2 = bpd.DataFrame(pd_df_2, session=polars_session) - - bf_result = bf_df_1.join(bf_df_2, how=join_type).to_pandas() - pd_result = pd_df_1.join(pd_df_2, how=join_type) - # Sort by index because ordering logic isn't same as pandas - pandas.testing.assert_frame_equal( - bf_result.sort_index(), pd_result.sort_index(), check_index_type=False - ) - - -def test_polars_local_engine_agg(polars_session): - pd_df = pd.DataFrame( - {"colA": [True, False, True, False, True], "colB": [1, 2, 3, 4, 5]} - ) - bf_df = bpd.DataFrame(pd_df, session=polars_session) - - bf_result = bf_df.agg(["sum", "count"]).to_pandas() - pd_result = pd_df.agg(["sum", "count"]) - # local engine appears to produce uint32 - pandas.testing.assert_frame_equal( - bf_result, # type: ignore[arg-type] - pd_result, - check_dtype=False, - check_index_type=False, - ) - - -def test_polars_local_engine_groupby_sum(polars_session): - pd_df = pd.DataFrame( - {"colA": [True, False, True, False, True], "colB": [1, 2, 3, 4, 5]} - ) - bf_df = bpd.DataFrame(pd_df, session=polars_session) - - bf_result = bf_df.groupby("colA").sum().to_pandas() - pd_result = pd_df.groupby("colA").sum() - pandas.testing.assert_frame_equal( - bf_result, pd_result, check_dtype=False, check_index_type=False - ) - - -def test_polars_local_engine_cumsum(small_inline_frame, polars_session): - pd_df = small_inline_frame[["int1", "int2"]] - bf_df = bpd.DataFrame(pd_df, session=polars_session) - - bf_result = bf_df.cumsum().to_pandas() - pd_result = pd_df.cumsum() - pandas.testing.assert_frame_equal(bf_result, pd_result) - - -def test_polars_local_engine_explode(small_inline_frame, polars_session): - pd_df = small_inline_frame - bf_df = bpd.DataFrame(pd_df, session=polars_session) - - bf_result = bf_df.explode(["intLists"]).to_pandas() - pd_result = pd_df.explode(["intLists"]) - pandas.testing.assert_frame_equal(bf_result, pd_result, check_dtype=False) - - -@pytest.mark.parametrize( - ("start", "stop", "step"), - [ - (1, None, None), - (None, 4, None), - (None, None, 2), - (None, 50_000_000_000, 1), - (5, 4, None), - (3, None, 2), - (1, 7, 2), - (1, 7, 50_000_000_000), - (-1, -7, -2), - (None, -7, -2), - (-1, None, -2), - (-7, -1, 2), - (-7, -1, None), - (-7, 7, None), - (7, -7, -2), - ], -) -def test_polars_local_engine_slice( - small_inline_frame, polars_session, start, stop, step -): - pd_df = small_inline_frame - bf_df = bpd.DataFrame(pd_df, session=polars_session) - - bf_result = bf_df.iloc[start:stop:step].to_pandas() - pd_result = pd_df.iloc[start:stop:step] - pandas.testing.assert_frame_equal(bf_result, pd_result, check_dtype=False) diff --git a/tests/unit/test_notebook.py b/tests/unit/test_notebook.py deleted file mode 100644 index 3feacd52b29..00000000000 --- a/tests/unit/test_notebook.py +++ /dev/null @@ -1,26 +0,0 @@ -# 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. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import pathlib - -REPO_ROOT = pathlib.Path(__file__).parent.parent.parent - - -def test_template_notebook_exists(): - # This notebook is meant for being used as a BigFrames usage template and - # could be dynamically linked in places such as BQ Studio and IDE extensions. - # Let's make sure it exists in the well known path. - assert ( - REPO_ROOT / "notebooks" / "getting_started" / "bq_dataframes_template.ipynb" - ).exists() diff --git a/tests/unit/test_pandas.py b/tests/unit/test_pandas.py index c85d92e024d..70c5441c685 100644 --- a/tests/unit/test_pandas.py +++ b/tests/unit/test_pandas.py @@ -14,16 +14,20 @@ import inspect import re +import sys import unittest.mock as mock +import google.api_core.exceptions +import google.cloud.bigquery import pandas as pd import pytest import bigframes.core.global_session -import bigframes.dataframe import bigframes.pandas as bpd import bigframes.session +from . import resources + leading_whitespace = re.compile(r"^\s+", flags=re.MULTILINE) @@ -34,10 +38,6 @@ def all_session_methods(): if not attribute.startswith("_") ) session_attributes.remove("close") - # streaming isn't in pandas - session_attributes.remove("read_gbq_table_streaming") - # execution_history is in base namespace, not pandas - session_attributes.remove("execution_history") for attribute in sorted(session_attributes): session_method = getattr(bigframes.session.Session, attribute) @@ -53,6 +53,11 @@ def all_session_methods(): [(method_name,) for method_name in all_session_methods()], ) def test_method_matches_session(method_name: str): + if sys.version_info <= (3, 10): + pytest.skip( + "Need Python 3.10 to reconcile deferred annotations." + ) # pragma: no cover + session_method = getattr(bigframes.session.Session, method_name) session_doc = inspect.getdoc(session_method) assert session_doc is not None, "docstrings are required" @@ -60,119 +65,86 @@ def test_method_matches_session(method_name: str): pandas_method = getattr(bigframes.pandas, method_name) pandas_doc = inspect.getdoc(pandas_method) assert pandas_doc is not None, "docstrings are required" - - pandas_doc_stripped = re.sub(leading_whitespace, "", pandas_doc) - session_doc_stripped = re.sub(leading_whitespace, "", session_doc) - assert ( - pandas_doc_stripped == session_doc_stripped - or ":`bigframes.pandas" in session_doc_stripped + assert re.sub(leading_whitespace, "", pandas_doc) == re.sub( + leading_whitespace, "", session_doc ) # Add `eval_str = True` so that deferred annotations are turned into their # corresponding type objects. Need Python 3.10 for eval_str parameter. - session_signature = inspect.signature( - session_method, - eval_str=True, - globals={**vars(bigframes.session), **{"dataframe": bigframes.dataframe}}, - ) - session_args = [ - # Kind includes position, which will be an offset. - parameter.replace(kind=inspect.Parameter.POSITIONAL_ONLY) - for parameter in session_signature.parameters.values() - # Don't include the first parameter, which is `self: Session` - ][1:] + session_signature = inspect.signature(session_method, eval_str=True) pandas_signature = inspect.signature(pandas_method, eval_str=True) - pandas_args = [ + assert [ # Kind includes position, which will be an offset. parameter.replace(kind=inspect.Parameter.POSITIONAL_ONLY) for parameter in pandas_signature.parameters.values() - ] - assert session_args == pandas_args or ["args", "kwargs"] == [ - parameter.name for parameter in session_args + ] == [ + # Kind includes position, which will be an offset. + parameter.replace(kind=inspect.Parameter.POSITIONAL_ONLY) + for parameter in session_signature.parameters.values() + # Don't include the first parameter, which is `self: Session` + ][ + 1: ] assert pandas_signature.return_annotation == session_signature.return_annotation -@pytest.mark.parametrize( - ("bins", "labels", "error_message"), - [ - pytest.param( - 5, - True, - "Bin labels must either be False, None or passed in as a list-like argument", - id="true", - ), - pytest.param( - 5, - 1.5, - "Bin labels must either be False, None or passed in as a list-like argument", - id="invalid_types", - ), - pytest.param( - 2, - ["A"], - "must be same as the value of bins", - id="int_bins_mismatch", - ), - pytest.param( - [1, 2, 3], - ["A"], - "must be same as the number of bin edges", - id="iterator_bins_mismatch", - ), - ], -) -def test_cut_raises_with_invalid_labels(bins: int, labels, error_message: str): - mock_series = mock.create_autospec(bigframes.pandas.Series, instance=True) - mock_series.__len__.return_value = 5 - with pytest.raises(ValueError, match=error_message): - bigframes.pandas.cut(mock_series, bins, labels=labels) - - -def test_cut_raises_with_unsupported_labels(): - mock_series = mock.create_autospec(bigframes.pandas.Series, instance=True) - labels = [1, 2] - with pytest.raises( - NotImplementedError, match=r".*only iterables of strings are supported.*" - ): - bigframes.pandas.cut(mock_series, 2, labels=labels) # type: ignore +def test_cut_raises_with_labels(): + with pytest.raises(NotImplementedError, match="Only labels=False"): + mock_series = mock.create_autospec(bigframes.pandas.Series, instance=True) + bigframes.pandas.cut(mock_series, 4, labels=["a", "b", "c", "d"]) @pytest.mark.parametrize( - ("bins", "error_message"), - [ - pytest.param(1.5, "`bins` must be an integer or interable.", id="float"), - pytest.param(0, "`bins` should be a positive integer.", id="zero_int"), - pytest.param(-1, "`bins` should be a positive integer.", id="neg_int"), - pytest.param( - ["notabreak"], - "`bins` iterable should contain tuples or numerics", - id="iterable_w_wrong_type", - ), - pytest.param( - [10, 3], - "left side of interval must be <= right side", - id="decreased_breaks", - ), - pytest.param( - [(1, 10), (2, 25)], - "Overlapping IntervalIndex is not accepted.", - id="overlapping_intervals", - ), - ], + ("bins",), + ( + (0,), + (-1,), + ), ) -def test_cut_raises_with_invalid_bins(bins: int, error_message: str): - mock_series = mock.create_autospec(bigframes.pandas.Series, instance=True) - mock_series.__len__.return_value = 5 - - with pytest.raises(ValueError, match=error_message): +def test_cut_raises_with_invalid_bins(bins: int): + with pytest.raises(ValueError, match="`bins` should be a positive integer."): + mock_series = mock.create_autospec(bigframes.pandas.Series, instance=True) bigframes.pandas.cut(mock_series, bins, labels=False) def test_pandas_attribute(): - assert pd.NA is pd.NA + assert bpd.NA is pd.NA assert bpd.BooleanDtype is pd.BooleanDtype assert bpd.Float64Dtype is pd.Float64Dtype assert bpd.Int64Dtype is pd.Int64Dtype assert bpd.StringDtype is pd.StringDtype assert bpd.ArrowDtype is pd.ArrowDtype + + +def test_close_session_after_bq_session_ended(monkeypatch: pytest.MonkeyPatch): + bqclient = mock.create_autospec(google.cloud.bigquery.Client, instance=True) + bqclient.project = "test-project" + session = resources.create_bigquery_session( + bqclient=bqclient, session_id="JUST_A_TEST" + ) + + # Simulate that the session has already expired. + # Note: this needs to be done after the Session is constructed, as the + # initializer sends a query to start the BigQuery Session. + query_job = mock.create_autospec(google.cloud.bigquery.QueryJob, instance=True) + query_job.result.side_effect = google.api_core.exceptions.BadRequest( + "Session JUST_A_TEST has expired and is no longer available." + ) + bqclient.query.return_value = query_job + + # Simulate that the session has already started. + monkeypatch.setattr(bigframes.core.global_session, "_global_session", session) + bpd.options.bigquery._session_started = True + + # Confirm that as a result bigframes.pandas interface is unusable + with pytest.raises( + google.api_core.exceptions.BadRequest, + match="Session JUST_A_TEST has expired and is no longer available.", + ): + bpd.read_gbq("SELECT 'ABC'") + + # Even though the query to stop the session raises an exception, we should + # still be able to close it without raising an error to the user. + bpd.close_session() + assert "CALL BQ.ABORT_SESSION('JUST_A_TEST')" in bqclient.query.call_args.args[0] + assert bigframes.core.global_session._global_session is None diff --git a/tests/unit/test_planner.py b/tests/unit/test_planner.py deleted file mode 100644 index 36a568a4165..00000000000 --- a/tests/unit/test_planner.py +++ /dev/null @@ -1,117 +0,0 @@ -# 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. -# 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 unittest.mock as mock - -import google.cloud.bigquery -import pandas as pd - -import bigframes.core as core -import bigframes.core.bq_data -import bigframes.core.expression as ex -import bigframes.core.identifiers as ids -import bigframes.operations as ops -import bigframes.session.planner as planner - -TABLE_REF = google.cloud.bigquery.TableReference.from_string("project.dataset.table") -SCHEMA = ( - google.cloud.bigquery.SchemaField("col_a", "INTEGER"), - google.cloud.bigquery.SchemaField("col_b", "INTEGER"), -) -TABLE = google.cloud.bigquery.Table( - table_ref=TABLE_REF, - schema=SCHEMA, -) -FAKE_SESSION = mock.create_autospec(bigframes.Session, instance=True) -type(FAKE_SESSION)._strictly_ordered = mock.PropertyMock(return_value=True) -LEAF: core.ArrayValue = core.ArrayValue.from_table( - session=FAKE_SESSION, - table=bigframes.core.bq_data.GbqNativeTable.from_table(TABLE), -) - - -def test_session_aware_caching_project_filter(): - """ - Test that if a node is filtered by a column, the node is cached pre-filter and clustered by the filter column. - """ - session_objects = [LEAF, LEAF.create_constant(4, pd.Int64Dtype())[0]] - target, _ = LEAF.create_constant(4, pd.Int64Dtype()) - target = target.filter(ops.gt_op.as_expr("col_a", ex.const(3))) - result, cluster_cols = planner.session_aware_cache_plan( - target.node, [obj.node for obj in session_objects] - ) - assert result == LEAF.node - assert cluster_cols == [ids.ColumnId("col_a")] - - -def test_session_aware_caching_project_multi_filter(): - """ - Test that if a node is filtered by multiple columns, all of them are in the cluster cols - """ - obj1 = LEAF - obj2, _ = LEAF.create_constant(4, pd.Int64Dtype()) - session_objects = [obj1, obj2] - predicate_1a = ops.gt_op.as_expr("col_a", ex.const(3)) - predicate_1b = ops.lt_op.as_expr("col_a", ex.const(55)) - predicate_1 = ops.and_op.as_expr(predicate_1a, predicate_1b) - predicate_3 = ops.eq_op.as_expr("col_b", ex.const(1)) - target = ( - LEAF.filter(predicate_1) - .create_constant(4, pd.Int64Dtype())[0] - .filter(predicate_3) - ) - result, cluster_cols = planner.session_aware_cache_plan( - target.node, [obj.node for obj in session_objects] - ) - assert result == LEAF.node - assert cluster_cols == [ids.ColumnId("col_a"), ids.ColumnId("col_b")] - - -def test_session_aware_caching_unusable_filter(): - """ - Test that if a node is filtered by multiple columns in the same comparison, the node is cached pre-filter and not clustered by either column. - - Most filters with multiple column references cannot be used for scan pruning, as they cannot be converted to fixed value ranges. - """ - session_objects = [LEAF, LEAF.create_constant(4, pd.Int64Dtype())[0]] - target = LEAF.create_constant(4, pd.Int64Dtype())[0].filter( - ops.gt_op.as_expr("col_a", "col_b") - ) - result, cluster_cols = planner.session_aware_cache_plan( - target.node, [obj.node for obj in session_objects] - ) - assert result == LEAF.node - assert cluster_cols == [] - - -def test_session_aware_caching_fork_after_window_op(): - """ - Test that caching happens only after an windowed operation, but before filtering, projecting. - - Windowing is expensive, so caching should always compute the window function, in order to avoid later recomputation. - """ - leaf_with_offsets = LEAF.promote_offsets()[0] - other = leaf_with_offsets.create_constant(5, pd.Int64Dtype())[0] - target = leaf_with_offsets.create_constant(4, pd.Int64Dtype())[0].filter( - ops.eq_op.as_expr("col_a", ops.add_op.as_expr(ex.const(4), ex.const(3))) - ) - result, cluster_cols = planner.session_aware_cache_plan( - target.node, - [ - other.node, - ], - ) - assert result == leaf_with_offsets.node - assert cluster_cols == [ids.ColumnId("col_a")] diff --git a/tests/unit/test_py_udf.py b/tests/unit/test_py_udf.py deleted file mode 100644 index dcd6fa28658..00000000000 --- a/tests/unit/test_py_udf.py +++ /dev/null @@ -1,761 +0,0 @@ -# Copyright 2023 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import pathlib -from typing import Generator - -import numpy as np -import pandas as pd -import pandas.testing -import pyarrow as pa -import pytest - -import bigframes -import bigframes.core.global_session -import bigframes.pandas as bpd -from bigframes.core.bytecode import py_to_expression -from bigframes.testing.utils import ( - assert_frame_equal, - assert_series_equal, - convert_pandas_dtypes, -) - -pytest.importorskip("polars") -pytest.importorskip("pandas", minversion="2.0.0") - -CURRENT_DIR = pathlib.Path(__file__).parent -DATA_DIR = CURRENT_DIR.parent / "data" - - -@pytest.fixture(scope="module", autouse=True) -def session() -> Generator[bigframes.Session, None, None]: - # import inline to allow polars importorskip to happen first - from bigframes.testing import polars_session - - with bpd.option_context("experiments.enable_python_transpiler", True): - session = polars_session.TestSession() - with bigframes.core.global_session._GlobalSessionContext(session): - yield session - - -@pytest.fixture(scope="module") -def scalars_pandas_df_index() -> pd.DataFrame: - """pd.DataFrame pointing at test data.""" - - df = pd.read_json( - DATA_DIR / "scalars.jsonl", - lines=True, - ) - convert_pandas_dtypes(df, bytes_col=True) - - df = df.set_index("rowindex", drop=False) - df.index.name = None - return df.set_index("rowindex").sort_index() - - -@pytest.fixture(scope="module") -def scalars_df_index( - session: bigframes.Session, scalars_pandas_df_index -) -> bpd.DataFrame: - return session.read_pandas(scalars_pandas_df_index) - - -@pytest.fixture(scope="module") -def scalars_dfs( - scalars_df_index, - scalars_pandas_df_index, -): - return scalars_df_index, scalars_pandas_df_index - - -def test_dataframe_map_transpile( - scalars_df_index, - scalars_pandas_df_index, -): - columns = ["int64_too", "int64_col"] - - def foo(input): - return input * 3 + 12 - - bf_result = scalars_df_index[columns].map(foo, na_action="ignore").to_pandas() - - pd_result = ( - scalars_pandas_df_index[columns].map(foo, na_action="ignore").astype("Int64") - ) - - assert_frame_equal(bf_result, pd_result) - - -def test_dataframe_apply_axis_1_transpile( - scalars_df_index, - scalars_pandas_df_index, -): - columns = ["int64_too", "int64_col"] - - def foo(input): - return input.int64_too + input.int64_col - - bf_result = scalars_df_index[columns].apply(foo, axis=1).to_pandas() - - pd_result = scalars_pandas_df_index[columns].apply(foo, axis=1).astype("Int64") - - assert_series_equal(bf_result, pd_result) - - -def test_series_combine_transpile( - scalars_df_index, - scalars_pandas_df_index, -): - def which_smaller(left, right): - return (left * right) + 3 - - bf_result = ( - scalars_df_index["int64_too"] - .combine(scalars_df_index["int64_col"], which_smaller) - .to_pandas() - ) - - pd_result = scalars_pandas_df_index["int64_too"].combine( - scalars_pandas_df_index["int64_col"], which_smaller - ) - - assert_series_equal(bf_result, pd_result) - - -def test_dataframe_apply_axis_1_transpile_with_defaults( - scalars_df_index, - scalars_pandas_df_index, -): - columns = ["int64_too", "int64_col"] - - def foo(input, x=10, y=5): - return input.int64_too + input.int64_col + x + y - - bf_result = scalars_df_index[columns].apply(foo, axis=1).to_pandas() - pd_result = scalars_pandas_df_index[columns].apply(foo, axis=1).astype("Int64") - - assert_series_equal(bf_result, pd_result) - - -def test_dataframe_apply_axis_1_transpile_with_args( - scalars_df_index, - scalars_pandas_df_index, -): - columns = ["int64_too", "int64_col"] - - def foo(input, x, y=5): - return input.int64_too + input.int64_col + x + y - - bf_result = ( - scalars_df_index[columns].apply(foo, axis=1, args=(12,), y=20).to_pandas() - ) - pd_result = ( - scalars_pandas_df_index[columns] - .apply(foo, axis=1, args=(12,), y=20) - .astype("Int64") - ) - - assert_series_equal(bf_result, pd_result) - - -def test_dataframe_apply_axis_1_transpile_invalid_bindings( - scalars_df_index, -): - columns = ["int64_too", "int64_col"] - - def foo(input, x, y=5): - return input.int64_too + input.int64_col + x + y - - # 1. Unexpected keyword argument - with pytest.raises(TypeError, match="unexpected keyword argument 'z'"): - scalars_df_index[columns].apply(foo, axis=1, args=(10,), z=20) - - # 2. Multiple values for keyword argument 'x' - with pytest.raises(TypeError, match="multiple values for argument 'x'"): - scalars_df_index[columns].apply(foo, axis=1, args=(10,), x=20) - - # 3. Too many positional arguments - with pytest.raises(TypeError, match="too many positional arguments"): - scalars_df_index[columns].apply(foo, axis=1, args=(10, 20, 30)) - - # 4. Missing required argument 'x' - with pytest.raises(TypeError, match="missing a required argument: 'x'"): - scalars_df_index[columns].apply(foo, axis=1) - - -def test_series_apply_transpile( - scalars_df_index, - scalars_pandas_df_index, -): - def foo(x, y=10): - return x * 2 + y - - bf_result = scalars_df_index["int64_col"].apply(foo, args=(5,)).to_pandas() - pd_result = ( - scalars_pandas_df_index["int64_col"].apply(foo, args=(5,)).astype("Int64") - ) - - assert_series_equal(bf_result, pd_result) - - -def test_series_apply_transpile_invalid_bindings( - scalars_df_index, -): - def foo(x, y): - return x + y - - # Too many positional args: foo takes 2 args (x, y), we pass self and 2 more args (total 3 positional) - with pytest.raises( - TypeError, match="too many positional arguments: expected 2, got 3" - ): - scalars_df_index["int64_col"].apply(foo, args=(10, 20)) - - # Missing required argument: foo takes 2 args, we only pass self (so y is missing) - with pytest.raises(TypeError, match="missing required argument: 'y'"): - scalars_df_index["int64_col"].apply(foo) - - -def test_transpilation_unsupported_ops_raise( - scalars_df_index, -): - def foo_with_loop(x): - total = 0 - for i in range(x): - total += i - return total - - with pytest.raises(ValueError): - scalars_df_index["int64_col"].apply(foo_with_loop) - - -def my_foo(x: int): - return x + 1 - - -def test_local_series_apply_simple(scalars_df_index, scalars_pandas_df_index): - bf_result = scalars_df_index["int64_col"].apply(my_foo).to_pandas() - pd_result = scalars_pandas_df_index["int64_col"].apply(my_foo) - - assert_series_equal(bf_result, pd_result, check_dtype=False) - - -def my_numpy_foo(x: int): - return np.add(x, x) * (np.cos(x) - np.sin(3)) - - -def test_local_series_apply_w_numpy(scalars_df_index, scalars_pandas_df_index): - bf_result = scalars_df_index["int64_col"].apply(my_numpy_foo).to_pandas() - pd_result = scalars_pandas_df_index["int64_col"].apply(my_numpy_foo) - - assert_series_equal(bf_result, pd_result, check_dtype=False) - - -def test_local_series_apply_w_simple_lamdba(scalars_df_index, scalars_pandas_df_index): - bf_result = scalars_df_index["int64_col"].apply(lambda x: x + 3).to_pandas() - pd_result = scalars_pandas_df_index["int64_col"].apply(lambda x: x + 3) - - assert_series_equal(bf_result, pd_result, check_dtype=False) - - -def test_local_series_apply_w_ternary_lamdba(scalars_df_index, scalars_pandas_df_index): - bf_result = ( - scalars_df_index["int64_col"] - .apply(lambda x: "positive" if x > 0 else "negative") - .to_pandas() - ) - pd_result = scalars_pandas_df_index["int64_col"].apply( - lambda x: "positive" if x > 0 else "negative" - ) - - assert_series_equal(bf_result, pd_result, check_dtype=False) - - -def test_local_series_apply_w_nested_fizzbuzz(session): - # challenging: closure, multiple exits, mutating variables - foo_div = 3 - buzz_div = 5 - pd_series = pd.Series( - range(20), - dtype="Int64", - index=pd.Index(range(20), dtype="Int64"), - name="integers", - ) - bf_series = bpd.Series(pd_series, session=session) - - def fizzbuzz(x): - if (x % 3) and (x % 5): - return str(x) - val = "" - if (x % foo_div) == 0: - val += "fizz" - if (x % buzz_div) == 0: - val += "buzz" - return val - - bf_result = bf_series.apply(fizzbuzz).to_pandas() - pd_result = pd_series.apply(fizzbuzz).astype(pd.StringDtype(storage="pyarrow")) - - assert_series_equal(bf_result, pd_result, check_dtype=False) - - -def test_local_dataframe_apply_w_ternary_lamdba( - scalars_df_index, scalars_pandas_df_index -): - bf_result = scalars_df_index.apply( - lambda x: x.int64_col if x.rowindex_2 > 5 else x.float64_col, axis=1 - ).to_pandas() - pd_result = scalars_pandas_df_index.apply( - lambda x: x.int64_col if x.rowindex_2 > 5 else x.float64_col, axis=1 - ).astype("Float64") - - assert_series_equal(bf_result, pd_result, check_dtype=False) - - -def test_local_series_apply_w_nested_ifs(scalars_df_index, scalars_pandas_df_index): - def nested_ifs(x): - if x > 0: - if x > 100: - return x * 10 - else: - return x * 2 - else: - if x < -100: - return x * 20 - return x * -1 - - bf_result = scalars_df_index["int64_col"].apply(nested_ifs).to_pandas() - pd_result = scalars_pandas_df_index["int64_col"].apply(nested_ifs) - - assert_series_equal(bf_result, pd_result, check_dtype=False) - - -def test_local_series_apply_w_elif(scalars_df_index, scalars_pandas_df_index): - def elif_fn(x): - if x > 100: - return 1 - elif x > 50: - return 2 - elif x > 0: - return 3 - else: - return 4 - - bf_result = scalars_df_index["int64_col"].apply(elif_fn).to_pandas() - pd_result = scalars_pandas_df_index["int64_col"].apply(elif_fn) - - assert_series_equal(bf_result, pd_result, check_dtype=False) - - -def test_local_series_apply_w_logical_not(scalars_df_index, scalars_pandas_df_index): - def logical_not_fn(x): - if not (x > 0): - return -x - return x - - bf_result = scalars_df_index["int64_col"].apply(logical_not_fn).to_pandas() - pd_result = scalars_pandas_df_index["int64_col"].apply(logical_not_fn) - - assert_series_equal(bf_result, pd_result, check_dtype=False) - - -def test_local_series_apply_w_short_circuit(scalars_df_index, scalars_pandas_df_index): - def short_circuit(x): - if (x > 0 and x < 100) or x == 55555: - return 1 - return 0 - - bf_result = scalars_df_index["int64_col"].apply(short_circuit).to_pandas() - pd_result = scalars_pandas_df_index["int64_col"].apply(short_circuit) - - assert_series_equal(bf_result, pd_result, check_dtype=False) - - -def test_local_series_apply_w_var_assignments( - scalars_df_index, scalars_pandas_df_index -): - def var_assign(x): - val = x - if x > 0: - val = val + 10 - if val > 100: - val = val * 2 - else: - val = val - 10 - return val - - bf_result = scalars_df_index["int64_col"].apply(var_assign).to_pandas() - pd_result = scalars_pandas_df_index["int64_col"].apply(var_assign) - - assert_series_equal(bf_result, pd_result, check_dtype=False) - - -def test_local_series_apply_w_logical_and_val( - scalars_df_index, scalars_pandas_df_index -): - def logical_and_val(x): - return (x % 3) and 100 - - bf_result = ( - scalars_df_index["int64_col"].dropna().apply(logical_and_val).to_pandas() - ) - pd_result = scalars_pandas_df_index["int64_col"].dropna().apply(logical_and_val) - - assert_series_equal(bf_result, pd_result, check_dtype=False) - - -def test_local_series_apply_w_logical_or_val(scalars_df_index, scalars_pandas_df_index): - def logical_or_val(x): - return (x % 3) or 200 - - bf_result = scalars_df_index["int64_col"].dropna().apply(logical_or_val).to_pandas() - pd_result = scalars_pandas_df_index["int64_col"].dropna().apply(logical_or_val) - - assert_series_equal(bf_result, pd_result, check_dtype=False) - - -def test_local_series_apply_w_logical_and_mixed( - scalars_df_index, -): - def logical_and_mixed(x): - return (x % 3) and "hello" - - with pytest.raises(TypeError, match="Cannot coerce"): - scalars_df_index["int64_col"].apply(logical_and_mixed) - - -def test_local_series_apply_w_logical_not_val( - scalars_df_index, scalars_pandas_df_index -): - def logical_not_val(x): - return not x - - bf_result = scalars_df_index["bool_col"].dropna().apply(logical_not_val).to_pandas() - pd_result = scalars_pandas_df_index["bool_col"].dropna().apply(logical_not_val) - - assert_series_equal(bf_result, pd_result, check_dtype=False) - - -def test_local_series_apply_w_compare_chain(scalars_df_index, scalars_pandas_df_index): - def compare_chain(x): - return 0 < x < 1000 - - bf_result = scalars_df_index["int64_col"].dropna().apply(compare_chain).to_pandas() - pd_result = scalars_pandas_df_index["int64_col"].dropna().apply(compare_chain) - - assert_series_equal(bf_result, pd_result, check_dtype=False) - - -def test_dataframe_apply_axis_1_with_integer_subscript( - scalars_df_index, scalars_pandas_df_index -): - columns = ["int64_too", "int64_col"] - bf_df = scalars_df_index[columns].rename(columns={"int64_too": 0, "int64_col": 1}) - pd_df = scalars_pandas_df_index[columns].rename( - columns={"int64_too": 0, "int64_col": 1} - ) - - def foo(input): - return input[0] + input[1] - - bf_result = bf_df.apply(foo, axis=1).to_pandas() - pd_result = pd_df.apply(foo, axis=1).astype("Int64") - - assert_series_equal(bf_result, pd_result) - - -def test_dataframe_apply_axis_1_with_invalid_subscript_raises( - scalars_df_index, -): - columns = ["int64_too", "int64_col"] - - def foo_invalid_label(input): - return input["non_existent_column"] - - with pytest.raises(KeyError, match="non_existent_column"): - scalars_df_index[columns].apply(foo_invalid_label, axis=1) - - -def test_series_map_with_struct_subscript(session): - # Struct setup - struct_pa_type = pa.struct([("str_field", pa.string()), ("int_field", pa.int64())]) - pd_struct_series = pd.Series( - pa.array([{"str_field": "hello", "int_field": 1}], struct_pa_type), - dtype=pd.ArrowDtype(struct_pa_type), - ) - bf_struct_series = bpd.Series(pd_struct_series, session=session) - - # Struct subscripting in UDF - def get_struct_val(x): - return x["str_field"] - - bf_struct_res = bf_struct_series.map(get_struct_val).to_pandas() - pd_struct_res: pd.Series = pd_struct_series.map(get_struct_val) - assert_series_equal(bf_struct_res, pd_struct_res, check_dtype=False) - - -def test_series_map_with_array_subscript(session): - # Array setup - array_pa_type = pa.list_(pa.int64()) - pd_array_series = pd.Series( - pa.array([[10, 20]], array_pa_type), - dtype=pd.ArrowDtype(array_pa_type), - ) - bf_array_series = bpd.Series(pd_array_series, session=session) - - # Array subscripting in UDF - def get_array_val(x): - return x[1] - - bf_array_res = bf_array_series.map(get_array_val).to_pandas() - pd_array_res: pd.Series = pd_array_series.map(get_array_val) - assert_series_equal(bf_array_res, pd_array_res, check_dtype=False) - - -def test_series_map_with_string_subscript(session): - # String setup - pd_string_series = pd.Series(["hello", "world"]) - bf_string_series = bpd.Series(pd_string_series, session=session) - - # String subscripting in UDF - def get_string_val(x): - return x[1] - - bf_string_res = bf_string_series.map(get_string_val).to_pandas() - pd_string_res = pd_string_series.map(get_string_val) # type: ignore - assert_series_equal(bf_string_res, pd_string_res, check_dtype=False) - - -def test_dataframe_apply_axis_1_with_dynamic_subscript_raises( - scalars_df_index, -): - columns = ["int64_too", "int64_col"] - - def foo_dynamic(input): - return input[input[0]] - - with pytest.raises( - NotImplementedError, match="Dynamic column lookup is not supported" - ): - scalars_df_index[columns].apply(foo_dynamic, axis=1) - - -def test_dataframe_apply_axis_1_with_dynamic_array_subscript(session): - array_pa_type = pa.list_(pa.int64()) - pd_df = pd.DataFrame( - { - "array_col": pd.Series( - pa.array([[10, 20], [30, 40, 50], [60]], array_pa_type), - dtype=pd.ArrowDtype(array_pa_type), - ), - "index_col": pd.Series([1, 2, 0], dtype="Int64"), - } - ) - bf_df = bpd.DataFrame(pd_df, session=session) - - def foo(row): - return row["array_col"][row["index_col"]] - - bf_result = bf_df.apply(foo, axis=1).to_pandas() - pd_result = pd_df.apply(foo, axis=1).astype("Int64") - - assert_series_equal(bf_result, pd_result) - - -def test_series_apply_fstrings(session): - pd_series = pd.Series(["apple", "banana", None], dtype="string") - bf_series = bpd.Series(pd_series, session=session) - - def format_udf(x): - if x is None: - return "Null value" - return f"Fruit: {x}!" - - bf_result = bf_series.apply(format_udf).to_pandas() - pd_result = pd.Series( - ["Fruit: apple!", "Fruit: banana!", "Null value"], dtype="string" - ) - assert_series_equal(bf_result, pd_result, check_dtype=False) - - -def test_series_apply_nullity_jumps(session): - pd_series = pd.Series([10, None, 20], dtype="Int64") - bf_series = bpd.Series(pd_series, session=session) - - def nullity_udf(x): - if x is None: - return "Absent" - if x is not None: - return "Present" - return "Unknown" - - bf_result = bf_series.apply(nullity_udf).to_pandas() - pd_result = pd.Series(["Present", "Absent", "Present"], dtype="string") - assert_series_equal(bf_result, pd_result, check_dtype=False) - - -def test_series_apply_string_ops(session): - pd_series = pd.Series(["hello world", "BigFrames", "123a"], dtype="string") - bf_series = bpd.Series(pd_series, session=session) - - def str_udf(x): - if x is None: - return None - return x.upper() + " " + x.lower() + " " + str.upper(x) + " " + x.capitalize() - - bf_result = bf_series.apply(str_udf).to_pandas() - pd_result = pd.Series( - [ - "HELLO WORLD hello world HELLO WORLD Hello world", - "BIGFRAMES bigframes BIGFRAMES Bigframes", - "123A 123a 123A 123a", - ], - dtype="string", - ) - assert_series_equal(bf_result, pd_result, check_dtype=False) - - -def test_series_apply_string_predicates(session): - pd_series = pd.Series( - ["hello world", "abc123", "123", "HELLO!", None], dtype="string" - ) - bf_series = bpd.Series(pd_series, session=session) - - def predicates_udf(x): - if x is None: - return None - return f"{x.islower()}_{x.isupper()}" - - bf_result = bf_series.apply(predicates_udf).to_pandas() - pd_result = pd.Series( - ["True_False", "True_False", "False_False", "False_True", None], dtype="string" - ) - assert_series_equal(bf_result, pd_result, check_dtype=False) - - -def test_fstring_multiple_placeholders(session): - pd_series = pd.Series(["apple", "banana"], dtype="string") - bf_series = bpd.Series(pd_series, session=session) - - def multiple_placeholders(x): - return f"{x} and {x.upper()}!" - - bf_res = bf_series.apply(multiple_placeholders).to_pandas() - pd_res = pd.Series(["apple and APPLE!", "banana and BANANA!"], dtype="string") - assert_series_equal(bf_res, pd_res, check_dtype=False) - - -def test_fstring_empty(session): - pd_series = pd.Series(["apple", "banana"], dtype="string") - bf_series = bpd.Series(pd_series, session=session) - - def empty_fstring(x): - return "" - - bf_res = bf_series.apply(empty_fstring).to_pandas() - pd_res = pd.Series(["", ""], dtype="string") - assert_series_equal(bf_res, pd_res, check_dtype=False) - - -def test_fstring_consecutive_placeholders(session): - pd_series = pd.Series(["apple", "banana"], dtype="string") - bf_series = bpd.Series(pd_series, session=session) - - def consecutive_placeholders(x): - return f"{x}{x.upper()}" - - bf_res = bf_series.apply(consecutive_placeholders).to_pandas() - pd_res = pd.Series(["appleAPPLE", "bananaBANANA"], dtype="string") - assert_series_equal(bf_res, pd_res, check_dtype=False) - - -def test_fstring_with_specifier_raises(): - def format_with_spec(x): - return f"{x:2d}" - - with pytest.raises( - NotImplementedError, match="Formatting with specifier is not supported" - ): - py_to_expression(format_with_spec) - - -def test_fstring_with_repr_raises(): - def format_with_repr(x): - return f"{x!r}" - - with pytest.raises( - NotImplementedError, - match="repr\\(\\) and ascii\\(\\) conversions are not supported", - ): - py_to_expression(format_with_repr) - - -def test_fstring_with_ascii_raises(): - def format_with_ascii(x): - return f"{x!a}" - - with pytest.raises( - NotImplementedError, - match="repr\\(\\) and ascii\\(\\) conversions are not supported", - ): - py_to_expression(format_with_ascii) - - -def test_identity_unsupported_raises(): - def is_true_udf(x): - return x is True - - with pytest.raises( - NotImplementedError, - match="Identity comparison \\(is/is not\\) is only supported for None", - ): - py_to_expression(is_true_udf) - - -def test_fstring_int_input(session): - pd_int_series = pd.Series([10, 20, None], dtype="Int64") - bf_int_series = bpd.Series(pd_int_series, session=session) - bf_res_int = bf_int_series.apply(lambda x: f"val: {x}").to_pandas() - pd_res_int = pd.Series(["val: 10", "val: 20", None], dtype="string") - assert_series_equal(bf_res_int, pd_res_int, check_dtype=False) - - -def test_fstring_float_input(session): - pd_float_series = pd.Series([1.5, 2.75], dtype="Float64") - bf_float_series = bpd.Series(pd_float_series, session=session) - bf_res_float = bf_float_series.apply(lambda x: f"val: {x}").to_pandas() - pd_res_float = pd.Series(["val: 1.5", "val: 2.75"], dtype="string") - assert_series_equal(bf_res_float, pd_res_float, check_dtype=False) - - -def test_fstring_bool_input(session): - pd_bool_series = pd.Series([True, False], dtype="boolean") - bf_bool_series = bpd.Series(pd_bool_series, session=session) - bf_res_bool = bf_bool_series.apply(lambda x: f"val: {x}").to_pandas() - pd_res_bool = pd.Series(["val: True", "val: False"], dtype="string") - assert_series_equal(bf_res_bool, pd_res_bool, check_dtype=False) - - -def test_fstring_list_input_raises(session): - array_pa_type = pa.list_(pa.int64()) - pd_series = pd.Series( - pa.array([[10, 20]], array_pa_type), - dtype=pd.ArrowDtype(array_pa_type), - ) - bf_series = bpd.Series(pd_series, session=session) - - def udf_with_list(x): - return f"list: {x}" - - with pytest.raises((TypeError, ValueError)): - bf_series.apply(udf_with_list) diff --git a/tests/unit/test_remote_function.py b/tests/unit/test_remote_function.py new file mode 100644 index 00000000000..540f4020d36 --- /dev/null +++ b/tests/unit/test_remote_function.py @@ -0,0 +1,28 @@ +# Copyright 2023 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 ibis.backends.bigquery import datatypes as bq_types +from ibis.expr import datatypes as ibis_types + +from bigframes import remote_function as rf + + +def test_supported_types_correspond(): + # The same types should be representable by the supported Python and BigQuery types. + ibis_types_from_python = {ibis_types.dtype(t) for t in rf.SUPPORTED_IO_PYTHON_TYPES} + ibis_types_from_bigquery = { + bq_types.BigQueryType.to_ibis(tk) for tk in rf.SUPPORTED_IO_BIGQUERY_TYPEKINDS + } + + assert ibis_types_from_python == ibis_types_from_bigquery diff --git a/tests/unit/test_sequences.py b/tests/unit/test_sequences.py deleted file mode 100644 index d901670b9b8..00000000000 --- a/tests/unit/test_sequences.py +++ /dev/null @@ -1,55 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from __future__ import annotations - -import itertools -from typing import Sequence - -import pytest - -from bigframes.core import sequences - -LARGE_LIST = list(range(100, 500)) -SMALL_LIST = list(range(1, 5)) -CHAINED_LIST = sequences.ChainedSequence([SMALL_LIST for i in range(100)]) - - -def _build_reference(*parts): - return tuple(itertools.chain(*parts)) - - -def _check_equivalence(expected: Sequence, actual: Sequence): - assert len(expected) == len(actual) - assert tuple(expected) == tuple(actual) - assert expected[10:1:-2] == actual[10:1:-2] - if len(expected) > 0: - assert expected[len(expected) - 1] == expected[len(actual) - 1] - - -@pytest.mark.parametrize( - ("parts",), - [ - ([],), - ([[]],), - ([[0, 1, 2]],), - ([LARGE_LIST, SMALL_LIST, LARGE_LIST],), - ([SMALL_LIST * 100],), - ([CHAINED_LIST, LARGE_LIST, CHAINED_LIST, SMALL_LIST],), - ], -) -def test_init_chained_sequence_single_slist(parts): - value = sequences.ChainedSequence(*parts) - expected = _build_reference(*parts) - _check_equivalence(expected, value) diff --git a/tests/unit/test_series.py b/tests/unit/test_series.py deleted file mode 100644 index 8a083d7e4ae..00000000000 --- a/tests/unit/test_series.py +++ /dev/null @@ -1,64 +0,0 @@ -# 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. -# 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 typing import cast - -import pytest - -import bigframes.series -from bigframes.testing import mocks - - -def test_series_rename(monkeypatch: pytest.MonkeyPatch): - series = cast(bigframes.series.Series, mocks.create_dataframe(monkeypatch)["col"]) - assert series.name == "col" - renamed = series.rename("renamed_col") - assert renamed.name == "renamed_col" - - -def test_series_rename_inplace_returns_none(monkeypatch: pytest.MonkeyPatch): - series = cast(bigframes.series.Series, mocks.create_dataframe(monkeypatch)["col"]) - assert series.name == "col" - assert series.rename("renamed_col", inplace=True) is None - assert series.name == "renamed_col" - - -def test_series_rename_axis(monkeypatch: pytest.MonkeyPatch): - series = mocks.create_dataframe( - monkeypatch, data={"index1": [], "index2": [], "col1": [], "col2": []} - ).set_index(["index1", "index2"])["col1"] - assert list(series.index.names) == ["index1", "index2"] - renamed = series.rename_axis(["a", "b"]) - assert list(renamed.index.names) == ["a", "b"] - - -def test_series_rename_axis_inplace_returns_none(monkeypatch: pytest.MonkeyPatch): - series = mocks.create_dataframe( - monkeypatch, data={"index1": [], "index2": [], "col1": [], "col2": []} - ).set_index(["index1", "index2"])["col1"] - assert list(series.index.names) == ["index1", "index2"] - assert series.rename_axis(["a", "b"], inplace=True) is None - assert list(series.index.names) == ["a", "b"] - - -def test_series_repr_with_uninitialized_object(): - """Ensures Series.__init__ can be paused in a visual debugger without crashing. - - Regression test for https://github.com/googleapis/python-bigquery-dataframes/issues/728 - """ - # Avoid calling __init__ to simulate pausing __init__ in a debugger. - # https://stackoverflow.com/a/6384982/101923 - series = bigframes.series.Series.__new__(bigframes.series.Series) - got = repr(series) - assert "Series" in got diff --git a/tests/unit/test_series_io.py b/tests/unit/test_series_io.py deleted file mode 100644 index bb0ea150535..00000000000 --- a/tests/unit/test_series_io.py +++ /dev/null @@ -1,50 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from unittest import mock - -import pytest - -from bigframes.testing import mocks - - -@pytest.fixture -def mock_series(monkeypatch: pytest.MonkeyPatch): - dataframe = mocks.create_dataframe(monkeypatch) - series = dataframe["col"] - monkeypatch.setattr(series, "to_pandas", mock.Mock()) - return series - - -@pytest.mark.parametrize( - "api_name, kwargs", - [ - ("to_csv", {"allow_large_results": True}), - ("to_dict", {"allow_large_results": True}), - ("to_excel", {"excel_writer": "abc", "allow_large_results": True}), - ("to_json", {"allow_large_results": True}), - ("to_latex", {"allow_large_results": True}), - ("to_list", {"allow_large_results": True}), - ("to_markdown", {"allow_large_results": True}), - ("to_numpy", {"allow_large_results": True}), - ("to_pickle", {"path": "abc", "allow_large_results": True}), - ("to_string", {"allow_large_results": True}), - ("to_xarray", {"allow_large_results": True}), - ], -) -def test_series_allow_large_results_param_passing(mock_series, api_name, kwargs): - getattr(mock_series, api_name)(**kwargs) - mock_series.to_pandas.assert_called_once_with( - allow_large_results=kwargs["allow_large_results"] - ) diff --git a/tests/unit/test_series_polars.py b/tests/unit/test_series_polars.py deleted file mode 100644 index 8b6d97d8b4b..00000000000 --- a/tests/unit/test_series_polars.py +++ /dev/null @@ -1,5201 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import datetime as dt -import json -import math -import operator -import pathlib -import re -import tempfile -from typing import Generator - -import db_dtypes # type: ignore -import geopandas as gpd # type: ignore -import google.api_core.exceptions -import numpy -import pandas as pd -import pyarrow as pa # type: ignore -import pytest -import shapely.geometry # type: ignore -from packaging.version import Version - -import bigframes -import bigframes.dtypes as dtypes -import bigframes.features -import bigframes.pandas -import bigframes.pandas as bpd -import bigframes.series as series -from bigframes.testing.utils import ( - assert_frame_equal, - assert_series_equal, - convert_pandas_dtypes, - get_first_file_from_wildcard, - pandas_major_version, -) - -pytest.importorskip("polars") -pytest.importorskip("pandas", minversion="2.0.0") - -CURRENT_DIR = pathlib.Path(__file__).parent -DATA_DIR = CURRENT_DIR.parent / "data" - - -@pytest.fixture(scope="module", autouse=True) -def session() -> Generator[bigframes.Session, None, None]: - import bigframes.core.global_session - from bigframes.testing import polars_session - - session = polars_session.TestSession() - with bigframes.core.global_session._GlobalSessionContext(session): - yield session - - -@pytest.fixture(scope="module") -def scalars_pandas_df_index() -> pd.DataFrame: - """pd.DataFrame pointing at test data.""" - - df = pd.read_json( - DATA_DIR / "scalars.jsonl", - lines=True, - ) - convert_pandas_dtypes(df, bytes_col=True) - - df = df.set_index("rowindex", drop=False) - df.index.name = None - return df.set_index("rowindex").sort_index() - - -@pytest.fixture(scope="module") -def scalars_df_default_index( - session: bigframes.Session, scalars_pandas_df_index -) -> bpd.DataFrame: - return session.read_pandas(scalars_pandas_df_index).reset_index(drop=False) - - -@pytest.fixture(scope="module") -def scalars_df_2_default_index( - session: bigframes.Session, scalars_pandas_df_index -) -> bpd.DataFrame: - return session.read_pandas(scalars_pandas_df_index).reset_index(drop=False) - - -@pytest.fixture(scope="module") -def scalars_df_index( - session: bigframes.Session, scalars_pandas_df_index -) -> bpd.DataFrame: - return session.read_pandas(scalars_pandas_df_index) - - -@pytest.fixture(scope="module") -def scalars_df_2_index( - session: bigframes.Session, scalars_pandas_df_index -) -> bpd.DataFrame: - return session.read_pandas(scalars_pandas_df_index) - - -@pytest.fixture(scope="module") -def scalars_dfs( - scalars_df_index, - scalars_pandas_df_index, -): - return scalars_df_index, scalars_pandas_df_index - - -def test_series_construct_copy(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - bf_result = series.Series( - scalars_df["int64_col"], name="test_series", dtype="Float64" - ).to_pandas() - pd_result = pd.Series( - scalars_pandas_df["int64_col"], name="test_series", dtype="Float64" - ) - pd.testing.assert_series_equal(bf_result, pd_result) - - -def test_series_construct_nullable_ints(): - bf_result = series.Series( - [1, 3, bigframes.pandas.NA], index=[0, 4, bigframes.pandas.NA] - ).to_pandas() - - # TODO(b/340885567): fix type error - expected_index = pd.Index( # type: ignore - [0, 4, None], - dtype=pd.Int64Dtype(), - ) - expected = pd.Series([1, 3, pd.NA], dtype=pd.Int64Dtype(), index=expected_index) - - pd.testing.assert_series_equal(bf_result, expected) - - -def test_series_construct_timestamps(): - datetimes = [ - dt.datetime(2020, 1, 20, 20, 20, 20, 20), - dt.datetime(2019, 1, 20, 20, 20, 20, 20), - None, - ] - bf_result = series.Series(datetimes).to_pandas() - pd_result = pd.Series(datetimes, dtype=pd.ArrowDtype(pa.timestamp("us"))) - - assert_series_equal(bf_result, pd_result, check_index_type=False) - - -def test_series_construct_copy_with_index(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - bf_result = series.Series( - scalars_df["int64_col"], - name="test_series", - dtype="Float64", - index=scalars_df["int64_too"], - ).to_pandas() - pd_result = pd.Series( - scalars_pandas_df["int64_col"], - name="test_series", - dtype="Float64", - index=scalars_pandas_df["int64_too"], - ) - pd.testing.assert_series_equal(bf_result, pd_result) - - -def test_series_construct_copy_index(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - bf_result = series.Series( - scalars_df.index, - name="test_series", - dtype="Float64", - index=scalars_df["int64_too"], - ).to_pandas() - pd_result = pd.Series( - scalars_pandas_df.index, - name="test_series", - dtype="Float64", - index=scalars_pandas_df["int64_too"], - ) - pd.testing.assert_series_equal(bf_result, pd_result) - - -def test_series_construct_pandas(scalars_dfs): - _, scalars_pandas_df = scalars_dfs - bf_result = series.Series( - scalars_pandas_df["int64_col"], name="test_series", dtype="Float64" - ) - pd_result = pd.Series( - scalars_pandas_df["int64_col"], name="test_series", dtype="Float64" - ) - assert bf_result.shape == pd_result.shape - pd.testing.assert_series_equal(bf_result.to_pandas(), pd_result) - - -def test_series_construct_from_list(): - bf_result = series.Series([1, 1, 2, 3, 5, 8, 13], dtype="Int64").to_pandas() - pd_result = pd.Series([1, 1, 2, 3, 5, 8, 13], dtype="Int64") - - # BigQuery DataFrame default indices use nullable Int64 always - pd_result.index = pd_result.index.astype("Int64") - - pd.testing.assert_series_equal(bf_result, pd_result) - - -def test_series_construct_reindex(): - bf_result = series.Series( - series.Series({1: 10, 2: 30, 3: 30}), index=[3, 2], dtype="Int64" - ).to_pandas() - pd_result = pd.Series(pd.Series({1: 10, 2: 30, 3: 30}), index=[3, 2], dtype="Int64") - - # BigQuery DataFrame default indices use nullable Int64 always - pd_result.index = pd_result.index.astype("Int64") - pd.testing.assert_series_equal(bf_result, pd_result) - - -def test_series_construct_from_list_w_index(): - bf_result = series.Series( - [1, 1, 2, 3, 5, 8, 13], index=[10, 20, 30, 40, 50, 60, 70], dtype="Int64" - ).to_pandas() - pd_result = pd.Series( - [1, 1, 2, 3, 5, 8, 13], index=[10, 20, 30, 40, 50, 60, 70], dtype="Int64" - ) - - # BigQuery DataFrame default indices use nullable Int64 always - pd_result.index = pd_result.index.astype("Int64") - - pd.testing.assert_series_equal(bf_result, pd_result) - - -def test_series_construct_empty(session: bigframes.Session): - bf_series: series.Series = series.Series(session=session) - pd_series: pd.Series = pd.Series() - - bf_result = bf_series.empty - pd_result = pd_series.empty - - assert pd_result - assert bf_result == pd_result - - -def test_series_construct_scalar_no_index(): - bf_result = series.Series("hello world", dtype="string[pyarrow]").to_pandas() - pd_result = pd.Series("hello world", dtype="string[pyarrow]") - - # BigQuery DataFrame default indices use nullable Int64 always - pd_result.index = pd_result.index.astype("Int64") - - pd.testing.assert_series_equal(bf_result, pd_result) - - -def test_series_construct_scalar_w_index(): - bf_result = series.Series( - "hello world", dtype="string[pyarrow]", index=[0, 2, 1] - ).to_pandas() - pd_result = pd.Series("hello world", dtype="string[pyarrow]", index=[0, 2, 1]) - - # BigQuery DataFrame default indices use nullable Int64 always - pd_result.index = pd_result.index.astype("Int64") - - pd.testing.assert_series_equal(bf_result, pd_result) - - -def test_series_construct_nan(): - bf_result = series.Series(numpy.nan).to_pandas() - pd_result = pd.Series(numpy.nan) - - pd_result.index = pd_result.index.astype("Int64") - pd_result = pd_result.astype("Float64") - - pd.testing.assert_series_equal(bf_result, pd_result) - - -def test_series_construct_scalar_w_bf_index(): - bf_result = series.Series( - "hello", index=bigframes.pandas.Index([1, 2, 3]) - ).to_pandas() - pd_result = pd.Series("hello", index=pd.Index([1, 2, 3], dtype="Int64")) - - pd_result = pd_result.astype("string[pyarrow]") - - pd.testing.assert_series_equal(bf_result, pd_result) - - -def test_series_construct_from_list_escaped_strings(): - """Check that special characters are supported.""" - strings = [ - "string\nwith\nnewline", - "string\twith\ttabs", - "string\\with\\backslashes", - ] - bf_result = series.Series(strings, name="test_series", dtype="string[pyarrow]") - pd_result = pd.Series(strings, name="test_series", dtype="string[pyarrow]") - - # BigQuery DataFrame default indices use nullable Int64 always - pd_result.index = pd_result.index.astype("Int64") - - pd.testing.assert_series_equal(bf_result.to_pandas(), pd_result) - - -def test_series_construct_geodata(): - pd_series = pd.Series( - [ - shapely.geometry.Point(1, 1), - shapely.geometry.Point(2, 2), - shapely.geometry.Point(3, 3), - ], - dtype=gpd.array.GeometryDtype(), - ) - - series = bigframes.pandas.Series(pd_series) - - assert_series_equal(pd_series, series.to_pandas(), check_index_type=False) - - -@pytest.mark.parametrize( - ("dtype"), - [ - pytest.param(pd.Int64Dtype(), id="int"), - pytest.param(pd.Float64Dtype(), id="float"), - pytest.param(pd.StringDtype(storage="pyarrow"), id="string"), - ], -) -def test_series_construct_w_dtype(dtype): - data = [1, 2, 3] - expected = pd.Series(data, dtype=dtype) - expected.index = expected.index.astype("Int64") - series = bigframes.pandas.Series(data, dtype=dtype) - pd.testing.assert_series_equal(series.to_pandas(), expected) - - -def test_series_construct_w_dtype_for_struct(): - # The data shows the struct fields are disordered and correctly handled during - # construction. - data = [ - {"a": 1, "c": "pandas", "b": dt.datetime(2020, 1, 20, 20, 20, 20, 20)}, - {"a": 2, "c": "pandas", "b": dt.datetime(2019, 1, 20, 20, 20, 20, 20)}, - {"a": 1, "c": "numpy", "b": None}, - ] - dtype = pd.ArrowDtype( - pa.struct([("a", pa.int64()), ("c", pa.string()), ("b", pa.timestamp("us"))]) - ) - series = bigframes.pandas.Series(data, dtype=dtype) - expected = pd.Series(data, dtype=dtype) - expected.index = expected.index.astype("Int64") - pd.testing.assert_series_equal(series.to_pandas(), expected) - - -def test_series_construct_w_dtype_for_array_string(): - data = [["1", "2", "3"], [], ["4", "5"]] - dtype = pd.ArrowDtype(pa.list_(pa.string())) - series = bigframes.pandas.Series(data, dtype=dtype) - expected = pd.Series(data, dtype=dtype) - expected.index = expected.index.astype("Int64") - - # Skip dtype check due to internal issue b/321013333. This issue causes array types - # to be converted to the `object` dtype when calling `to_pandas()`, resulting in - # a mismatch with the expected Pandas type. - if bigframes.features.PANDAS_VERSIONS.is_arrow_list_dtype_usable: - check_dtype = True - else: - check_dtype = False - - pd.testing.assert_series_equal( - series.to_pandas(), expected, check_dtype=check_dtype - ) - - -def test_series_construct_w_dtype_for_array_struct(): - data = [[{"a": 1, "c": "aa"}, {"a": 2, "c": "bb"}], [], [{"a": 3, "c": "cc"}]] - dtype = pd.ArrowDtype(pa.list_(pa.struct([("a", pa.int64()), ("c", pa.string())]))) - series = bigframes.pandas.Series(data, dtype=dtype) - expected = pd.Series(data, dtype=dtype) - expected.index = expected.index.astype("Int64") - - # Skip dtype check due to internal issue b/321013333. This issue causes array types - # to be converted to the `object` dtype when calling `to_pandas()`, resulting in - # a mismatch with the expected Pandas type. - if bigframes.features.PANDAS_VERSIONS.is_arrow_list_dtype_usable: - check_dtype = True - else: - check_dtype = False - - pd.testing.assert_series_equal( - series.to_pandas(), expected, check_dtype=check_dtype - ) - - -def test_series_construct_local_unordered_has_sequential_index(session): - series = bigframes.pandas.Series( - ["Sun", "Mon", "Tues", "Wed", "Thurs", "Fri", "Sat"], session=session - ) - expected: pd.Index = pd.Index([0, 1, 2, 3, 4, 5, 6], dtype=pd.Int64Dtype()) - pd.testing.assert_index_equal(series.index.to_pandas(), expected) - - -@pytest.mark.parametrize( - ("json_type"), - [ - pytest.param(dtypes.JSON_DTYPE), - pytest.param("json"), - ], -) -def test_series_construct_w_json_dtype(json_type): - data = [ - "1", - '"str"', - "false", - '["a", {"b": 1}, null]', - None, - '{"a": {"b": [1, 2, 3], "c": true}}', - ] - s = bigframes.pandas.Series(data, dtype=json_type) - - assert s.dtype == dtypes.JSON_DTYPE - assert s[0] == "1" - assert s[1] == '"str"' - assert s[2] == "false" - assert s[3] == '["a",{"b":1},null]' - assert pd.isna(s[4]) - assert s[5] == '{"a":{"b":[1,2,3],"c":true}}' - - -def test_series_keys(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - bf_result = scalars_df["int64_col"].keys().to_pandas() - pd_result = scalars_pandas_df["int64_col"].keys() - pd.testing.assert_index_equal(bf_result, pd_result) - - -@pytest.mark.parametrize( - ["data", "index"], - [ - (["a", "b", "c"], None), - ([1, 2, 3], ["a", "b", "c"]), - ([1, 2, None], ["a", "b", "c"]), - ([1, 2, 3], [pd.NA, "b", "c"]), - ([numpy.nan, 2, 3], ["a", "b", "c"]), - ], -) -def test_series_items(data, index): - bf_series = series.Series(data, index=index) - pd_series = pd.Series(data, index=index) - - for (bf_index, bf_value), (pd_index, pd_value) in zip( - bf_series.items(), pd_series.items() - ): - # TODO(jialuo): Remove the if conditions after b/373699458 is addressed. - if not pd.isna(bf_index) or not pd.isna(pd_index): - assert bf_index == pd_index - if not pd.isna(bf_value) or not pd.isna(pd_value): - assert bf_value == pd_value - - -@pytest.mark.parametrize( - ["col_name", "expected_dtype"], - [ - ("bool_col", pd.BooleanDtype()), - # TODO(swast): Use a more efficient type. - ("bytes_col", pd.ArrowDtype(pa.binary())), - ("date_col", pd.ArrowDtype(pa.date32())), - ("datetime_col", pd.ArrowDtype(pa.timestamp("us"))), - ("float64_col", pd.Float64Dtype()), - ("geography_col", gpd.array.GeometryDtype()), - ("int64_col", pd.Int64Dtype()), - # TODO(swast): Use a more efficient type. - ("numeric_col", pd.ArrowDtype(pa.decimal128(38, 9))), - ("int64_too", pd.Int64Dtype()), - ("string_col", pd.StringDtype(storage="pyarrow")), - ("time_col", pd.ArrowDtype(pa.time64("us"))), - ("timestamp_col", pd.ArrowDtype(pa.timestamp("us", tz="UTC"))), - ], -) -def test_get_column(scalars_dfs, col_name, expected_dtype): - scalars_df, scalars_pandas_df = scalars_dfs - series = scalars_df[col_name] - series_pandas = series.to_pandas() - assert series_pandas.dtype == expected_dtype - assert series_pandas.shape[0] == scalars_pandas_df.shape[0] - - -def test_series_get_column_default(scalars_dfs): - scalars_df, _ = scalars_dfs - result = scalars_df.get(123123123123123, "default_val") - assert result == "default_val" - - -@pytest.mark.parametrize( - ("key",), - [ - ("hello",), - (2,), - ("int64_col",), - (None,), - ], -) -def test_series_contains(scalars_df_index, scalars_pandas_df_index, key): - bf_result = key in scalars_df_index["int64_col"] - pd_result = key in scalars_pandas_df_index["int64_col"] - - assert bf_result == pd_result - - -def test_series_equals_identical(scalars_df_index, scalars_pandas_df_index): - bf_result = scalars_df_index.int64_col.equals(scalars_df_index.int64_col) - pd_result = scalars_pandas_df_index.int64_col.equals( - scalars_pandas_df_index.int64_col - ) - - assert pd_result == bf_result - - -def test_series_equals_df(scalars_df_index, scalars_pandas_df_index): - bf_result = scalars_df_index["int64_col"].equals(scalars_df_index[["int64_col"]]) - pd_result = scalars_pandas_df_index["int64_col"].equals( - scalars_pandas_df_index[["int64_col"]] - ) - - assert pd_result == bf_result - - -def test_series_equals_different_dtype(scalars_df_index, scalars_pandas_df_index): - bf_series = scalars_df_index["int64_col"] - pd_series = scalars_pandas_df_index["int64_col"] - - bf_result = bf_series.equals(bf_series.astype("Float64")) - pd_result = pd_series.equals(pd_series.astype("Float64")) - - assert pd_result == bf_result - - -def test_series_equals_different_values(scalars_df_index, scalars_pandas_df_index): - bf_series = scalars_df_index["int64_col"] - pd_series = scalars_pandas_df_index["int64_col"] - - bf_result = bf_series.equals(bf_series + 1) - pd_result = pd_series.equals(pd_series + 1) - - assert pd_result == bf_result - - -def test_series_get_with_default_index(scalars_dfs): - col_name = "float64_col" - key = 2 - scalars_df, scalars_pandas_df = scalars_dfs - bf_result = scalars_df[col_name].get(key) - pd_result = scalars_pandas_df[col_name].get(key) - assert bf_result == pd_result - - -@pytest.mark.parametrize( - ("index_col", "key"), - ( - ("int64_too", 2), - ("string_col", "Hello, World!"), - ("int64_too", slice(2, 6)), - ), -) -def test_series___getitem__(scalars_dfs, index_col, key): - col_name = "float64_col" - scalars_df, scalars_pandas_df = scalars_dfs - scalars_df = scalars_df.set_index(index_col, drop=False) - scalars_pandas_df = scalars_pandas_df.set_index(index_col, drop=False) - bf_result = scalars_df[col_name][key] - pd_result = scalars_pandas_df[col_name][key] - pd.testing.assert_series_equal(bf_result.to_pandas(), pd_result) - - -@pytest.mark.parametrize( - ("key",), - ( - (-2,), - (-1,), - (0,), - (1,), - ), -) -def test_series___getitem___with_int_key(scalars_dfs, key): - if pd.__version__.startswith("3."): - pytest.skip("pandas 3.0 dropped getitem with int key") - col_name = "int64_too" - index_col = "string_col" - scalars_df, scalars_pandas_df = scalars_dfs - scalars_df = scalars_df.set_index(index_col, drop=False) - scalars_pandas_df = scalars_pandas_df.set_index(index_col, drop=False) - bf_result = scalars_df[col_name][key] - pd_result = scalars_pandas_df[col_name][key] - assert bf_result == pd_result - - -def test_series___getitem___with_default_index(scalars_dfs): - col_name = "float64_col" - key = 2 - scalars_df, scalars_pandas_df = scalars_dfs - bf_result = scalars_df[col_name][key] - pd_result = scalars_pandas_df[col_name][key] - assert bf_result == pd_result - - -@pytest.mark.parametrize( - ("index_col", "key", "value"), - ( - ("int64_too", 2, "new_string_value"), - ("string_col", "Hello, World!", "updated_value"), - ("int64_too", 0, None), - ), -) -def test_series___setitem__(scalars_dfs, index_col, key, value): - col_name = "string_col" - scalars_df, scalars_pandas_df = scalars_dfs - scalars_df = scalars_df.set_index(index_col, drop=False) - scalars_pandas_df = scalars_pandas_df.set_index(index_col, drop=False) - - bf_series = scalars_df[col_name] - pd_series = scalars_pandas_df[col_name].copy() - - bf_series[key] = value - pd_series[key] = value - - pd.testing.assert_series_equal(bf_series.to_pandas(), pd_series) - - -@pytest.mark.parametrize( - ("key", "value"), - ( - (0, 999), - (1, 888), - (0, None), - (-2345, 777), - ), -) -def test_series___setitem___with_int_key_numeric(scalars_dfs, key, value): - col_name = "int64_col" - index_col = "int64_too" - scalars_df, scalars_pandas_df = scalars_dfs - scalars_df = scalars_df.set_index(index_col, drop=False) - scalars_pandas_df = scalars_pandas_df.set_index(index_col, drop=False) - - bf_series = scalars_df[col_name] - pd_series = scalars_pandas_df[col_name].copy() - - bf_series[key] = value - pd_series[key] = value - - pd.testing.assert_series_equal(bf_series.to_pandas(), pd_series) - - -def test_series___setitem___with_default_index(scalars_dfs): - col_name = "float64_col" - key = 2 - value = 123.456 - scalars_df, scalars_pandas_df = scalars_dfs - - bf_series = scalars_df[col_name] - pd_series = scalars_pandas_df[col_name].copy() - - bf_series[key] = value - pd_series[key] = value - - assert bf_series.to_pandas().iloc[key] == pd_series.iloc[key] - - -@pytest.mark.parametrize( - ("col_name",), - ( - ("float64_col",), - ("int64_too",), - ), -) -def test_abs(scalars_dfs, col_name): - scalars_df, scalars_pandas_df = scalars_dfs - bf_result = scalars_df[col_name].abs().to_pandas() - pd_result = scalars_pandas_df[col_name].abs() - - assert_series_equal(pd_result, bf_result) - - -@pytest.mark.parametrize( - ("col_name",), - ( - ("float64_col",), - ("int64_too",), - ), -) -def test_series_pos(scalars_dfs, col_name): - scalars_df, scalars_pandas_df = scalars_dfs - bf_result = (+scalars_df[col_name]).to_pandas() - pd_result = +scalars_pandas_df[col_name] - - assert_series_equal(pd_result, bf_result) - - -@pytest.mark.parametrize( - ("col_name",), - ( - ("float64_col",), - ("int64_too",), - ), -) -def test_series_neg(scalars_dfs, col_name): - scalars_df, scalars_pandas_df = scalars_dfs - bf_result = (-scalars_df[col_name]).to_pandas() - pd_result = -scalars_pandas_df[col_name] - - assert_series_equal(pd_result, bf_result) - - -@pytest.mark.parametrize( - ("col_name",), - ( - ("bool_col",), - ("int64_col",), - ), -) -def test_series_invert(scalars_dfs, col_name): - scalars_df, scalars_pandas_df = scalars_dfs - bf_result = (~scalars_df[col_name]).to_pandas() - pd_result = ~scalars_pandas_df[col_name] - - assert_series_equal(pd_result, bf_result) - - -def test_fillna(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - col_name = "string_col" - bf_result = scalars_df[col_name].fillna("Missing").to_pandas() - pd_result = scalars_pandas_df[col_name].fillna("Missing") - assert_series_equal( - pd_result, - bf_result, - ) - - -def test_series_replace_scalar_scalar(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - col_name = "string_col" - bf_result = ( - scalars_df[col_name].replace("Hello, World!", "Howdy, Planet!").to_pandas() - ) - pd_result = scalars_pandas_df[col_name].replace("Hello, World!", "Howdy, Planet!") - - pd.testing.assert_series_equal( - pd_result, - bf_result, - ) - - -def test_series_replace_list_scalar(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - col_name = "string_col" - bf_result = ( - scalars_df[col_name] - .replace(["Hello, World!", "T"], "Howdy, Planet!") - .to_pandas() - ) - pd_result = scalars_pandas_df[col_name].replace( - ["Hello, World!", "T"], "Howdy, Planet!" - ) - - pd.testing.assert_series_equal( - pd_result, - bf_result, - ) - - -@pytest.mark.parametrize( - ("replacement_dict",), - (({},),), - ids=[ - "empty", - ], -) -def test_series_replace_dict(scalars_dfs, replacement_dict): - scalars_df, scalars_pandas_df = scalars_dfs - col_name = "string_col" - bf_result = scalars_df[col_name].replace(replacement_dict).to_pandas() - pd_result = scalars_pandas_df[col_name].replace(replacement_dict) - - pd.testing.assert_series_equal( - pd_result, - bf_result, - ) - - -@pytest.mark.parametrize( - ("method",), - ( - ("linear",), - ("values",), - ("slinear",), - ("nearest",), - ("zero",), - ("pad",), - ), -) -def test_series_interpolate(method): - pytest.importorskip("scipy") - if method == "pad" and pd.__version__.startswith("3."): - pytest.skip("pandas 3.0 dropped method='pad'") - - values = [None, 1, 2, None, None, 16, None] - index = [-3.2, 11.4, 3.56, 4, 4.32, 5.55, 76.8] - pd_series = pd.Series(values, index) - bf_series = series.Series(pd_series) - - # Pandas can only interpolate on "float64" columns - # https://github.com/pandas-dev/pandas/issues/40252 - pd_result = pd_series.astype("float64").interpolate(method=method) - bf_result = bf_series.interpolate(method=method).to_pandas() - - # pd uses non-null types, while bf uses nullable types - assert_series_equal( - pd_result, - bf_result, - check_index_type=False, - check_dtype=False, - nulls_are_nan=True, - ) - - -@pytest.mark.parametrize( - ("ignore_index",), - ( - (True,), - (False,), - ), -) -def test_series_dropna(scalars_dfs, ignore_index): - if pd.__version__.startswith("1."): - pytest.skip("ignore_index parameter not supported in pandas 1.x.") - scalars_df, scalars_pandas_df = scalars_dfs - col_name = "string_col" - bf_result = scalars_df[col_name].dropna(ignore_index=ignore_index).to_pandas() - pd_result = scalars_pandas_df[col_name].dropna(ignore_index=ignore_index) - assert_series_equal(pd_result, bf_result, check_index_type=False) - - -@pytest.mark.parametrize( - ("agg",), - ( - ("sum",), - ("size",), - ), -) -def test_series_agg_single_string(scalars_dfs, agg): - scalars_df, scalars_pandas_df = scalars_dfs - bf_result = scalars_df["int64_col"].agg(agg) - pd_result = scalars_pandas_df["int64_col"].agg(agg) - assert math.isclose(pd_result, bf_result) - - -def test_series_agg_multi_string(scalars_dfs): - aggregations = [ - "sum", - "mean", - "std", - "var", - "min", - "max", - "nunique", - "count", - "size", - ] - scalars_df, scalars_pandas_df = scalars_dfs - bf_result = scalars_df["int64_col"].agg(aggregations).to_pandas() - pd_result = scalars_pandas_df["int64_col"].agg(aggregations) - - # Pandas may produce narrower numeric types, but bigframes always produces Float64 - pd_result = pd_result.astype("Float64") - - pd.testing.assert_series_equal(pd_result, bf_result, check_index_type=False) - - -@pytest.mark.parametrize( - ("col_name",), - ( - ("string_col",), - ("int64_col",), - ), -) -def test_max(scalars_dfs, col_name): - scalars_df, scalars_pandas_df = scalars_dfs - bf_result = scalars_df[col_name].max() - pd_result = scalars_pandas_df[col_name].max() - assert pd_result == bf_result - - -@pytest.mark.parametrize( - ("col_name",), - ( - ("string_col",), - ("int64_col",), - ), -) -def test_min(scalars_dfs, col_name): - scalars_df, scalars_pandas_df = scalars_dfs - bf_result = scalars_df[col_name].min() - pd_result = scalars_pandas_df[col_name].min() - assert pd_result == bf_result - - -@pytest.mark.parametrize( - ("col_name",), - ( - ("float64_col",), - ("int64_col",), - ), -) -def test_std(scalars_dfs, col_name): - scalars_df, scalars_pandas_df = scalars_dfs - bf_result = scalars_df[col_name].std() - pd_result = scalars_pandas_df[col_name].std() - assert math.isclose(pd_result, bf_result) - - -@pytest.mark.parametrize( - ("col_name",), - ( - ("float64_col",), - ("int64_col",), - ), -) -def test_kurt(scalars_dfs, col_name): - scalars_df, scalars_pandas_df = scalars_dfs - bf_result = scalars_df[col_name].kurt() - pd_result = scalars_pandas_df[col_name].kurt() - assert math.isclose(pd_result, bf_result) - - -@pytest.mark.parametrize( - ("col_name",), - ( - ("float64_col",), - ("int64_col",), - ), -) -def test_skew(scalars_dfs, col_name): - scalars_df, scalars_pandas_df = scalars_dfs - bf_result = scalars_df[col_name].skew() - pd_result = scalars_pandas_df[col_name].skew() - assert math.isclose(pd_result, bf_result) - - -def test_skew_undefined(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - bf_result = scalars_df["int64_col"].iloc[:2].skew() - pd_result = scalars_pandas_df["int64_col"].iloc[:2].skew() - # both should be pd.NA - assert pd_result is bf_result - - -def test_kurt_undefined(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - bf_result = scalars_df["int64_col"].iloc[:3].kurt() - pd_result = scalars_pandas_df["int64_col"].iloc[:3].kurt() - # both should be pd.NA - assert pd_result is bf_result - - -@pytest.mark.parametrize( - ("col_name",), - ( - ("float64_col",), - ("int64_col",), - ), -) -def test_var(scalars_dfs, col_name): - scalars_df, scalars_pandas_df = scalars_dfs - bf_result = scalars_df[col_name].var() - pd_result = scalars_pandas_df[col_name].var() - assert math.isclose(pd_result, bf_result) - - -@pytest.mark.parametrize( - ("col_name",), - ( - ("bool_col",), - ("int64_col",), - ), -) -def test_mode_stat(scalars_df_index, scalars_pandas_df_index, col_name): - bf_result = scalars_df_index[col_name].mode().to_pandas() - pd_result = scalars_pandas_df_index[col_name].mode() - - ## Mode implicitly resets index, and bigframes default indices use nullable Int64 - pd_result.index = pd_result.index.astype("Int64") - - pd.testing.assert_series_equal( - bf_result, - pd_result, - ) - - -@pytest.mark.parametrize( - ("operator"), - [ - (lambda x, y: x + y), - (lambda x, y: x - y), - (lambda x, y: x * y), - (lambda x, y: x / y), - (lambda x, y: x // y), - (lambda x, y: x < y), - (lambda x, y: x > y), - (lambda x, y: x <= y), - (lambda x, y: x >= y), - ], - ids=[ - "add", - "subtract", - "multiply", - "divide", - "floordivide", - "less_than", - "greater_than", - "less_than_equal", - "greater_than_equal", - ], -) -@pytest.mark.parametrize( - ("other_scalar"), - [ - -1, - 0, - 14, - # TODO(tswast): Support pd.NA, - ], -) -@pytest.mark.parametrize(("reverse_operands"), [True, False]) -def test_series_int_int_operators_scalar( - scalars_dfs, operator, other_scalar, reverse_operands -): - scalars_df, scalars_pandas_df = scalars_dfs - - maybe_reversed_op = (lambda x, y: operator(y, x)) if reverse_operands else operator - - bf_result = maybe_reversed_op(scalars_df["int64_col"], other_scalar).to_pandas() - pd_result = maybe_reversed_op(scalars_pandas_df["int64_col"], other_scalar) - - # don't check dtype, as pandas is a bit unstable here across versions, esp floordiv - assert_series_equal(pd_result, bf_result, check_dtype=False) - - -def test_series_pow_scalar(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - - bf_result = (scalars_df["int64_col"] ** 2).to_pandas() - pd_result = scalars_pandas_df["int64_col"] ** 2 - - assert_series_equal(pd_result, bf_result) - - -def test_series_pow_scalar_reverse(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - - bf_result = (0.8 ** scalars_df["int64_col"]).to_pandas() - pd_result = 0.8 ** scalars_pandas_df["int64_col"] - - assert_series_equal(pd_result, bf_result) - - -@pytest.mark.parametrize( - ("operator"), - [ - (lambda x, y: x & y), - (lambda x, y: x | y), - (lambda x, y: x ^ y), - ], - ids=[ - "and", - "or", - "xor", - ], -) -@pytest.mark.parametrize( - ("other_scalar"), - [ - True, - False, - pytest.param( - pd.NA, - marks=[ - pytest.mark.skip( - reason="https://github.com/pola-rs/polars/issues/24809" - ) - ], - id="NULL", - ), - ], -) -@pytest.mark.parametrize(("reverse_operands"), [True, False]) -def test_series_bool_bool_operators_scalar( - scalars_dfs, operator, other_scalar, reverse_operands -): - scalars_df, scalars_pandas_df = scalars_dfs - - maybe_reversed_op = (lambda x, y: operator(y, x)) if reverse_operands else operator - - bf_result = maybe_reversed_op(scalars_df["bool_col"], other_scalar).to_pandas() - pd_result = maybe_reversed_op(scalars_pandas_df["bool_col"], other_scalar) - - assert_series_equal(pd_result.astype(pd.BooleanDtype()), bf_result) - - -@pytest.mark.parametrize( - ("operator"), - [ - (lambda x, y: x + y), - (lambda x, y: x - y), - (lambda x, y: x * y), - (lambda x, y: x / y), - (lambda x, y: x < y), - (lambda x, y: x > y), - (lambda x, y: x <= y), - (lambda x, y: x >= y), - (lambda x, y: x % y), - (lambda x, y: x // y), - (lambda x, y: x & y), - (lambda x, y: x | y), - (lambda x, y: x ^ y), - ], - ids=[ - "add", - "subtract", - "multiply", - "divide", - "less_than", - "greater_than", - "less_than_equal", - "greater_than_equal", - "modulo", - "floordivide", - "bitwise_and", - "bitwise_or", - "bitwise_xor", - ], -) -def test_series_int_int_operators_series(scalars_dfs, operator): - scalars_df, scalars_pandas_df = scalars_dfs - bf_result = operator(scalars_df["int64_col"], scalars_df["int64_too"]).to_pandas() - pd_result = operator(scalars_pandas_df["int64_col"], scalars_pandas_df["int64_too"]) - assert_series_equal(pd_result, bf_result) - - -@pytest.mark.parametrize( - ("col_x",), - [ - ("int64_col",), - ("int64_too",), - ("float64_col",), - ], -) -@pytest.mark.parametrize( - ("col_y",), - [ - ("int64_col",), - ("int64_too",), - ("float64_col",), - ], -) -@pytest.mark.parametrize( - ("method",), - [ - ("mod",), - ("rmod",), - ], -) -def test_mods(scalars_dfs, col_x, col_y, method): - scalars_df, scalars_pandas_df = scalars_dfs - x_bf = scalars_df[col_x] - y_bf = scalars_df[col_y] - bf_series = getattr(x_bf, method)(y_bf) - # BigQuery's mod functions return [BIG]NUMERIC values unless both arguments are integers. - # https://cloud.google.com/bigquery/docs/reference/standard-sql/mathematical_functions#mod - if x_bf.dtype == pd.Int64Dtype() and y_bf.dtype == pd.Int64Dtype(): - bf_result = bf_series.to_pandas() - else: - bf_result = bf_series.astype("Float64").to_pandas() - pd_result = getattr(scalars_pandas_df[col_x], method)(scalars_pandas_df[col_y]) - assert_series_equal(pd_result, bf_result, nulls_are_nan=True) - - -# We work around a pandas bug that doesn't handle correlating nullable dtypes by doing this -# manually with dumb self-correlation instead of parameterized as test_mods is above. -def test_series_corr(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - bf_result = scalars_df["int64_too"].corr(scalars_df["int64_too"]) - pd_result = ( - scalars_pandas_df["int64_too"] - .astype("int64") - .corr(scalars_pandas_df["int64_too"].astype("int64")) - ) - assert math.isclose(pd_result, bf_result) - - -def test_series_autocorr(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 - bf_result = scalars_df["float64_col"].autocorr(2) - pd_result = scalars_pandas_df["float64_col"].autocorr(2) - assert math.isclose(pd_result, bf_result) - - -def test_series_cov(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - bf_result = scalars_df["int64_too"].cov(scalars_df["int64_too"]) - pd_result = ( - scalars_pandas_df["int64_too"] - .astype("int64") - .cov(scalars_pandas_df["int64_too"].astype("int64")) - ) - assert math.isclose(pd_result, bf_result) - - -@pytest.mark.parametrize( - ("col_x",), - [ - ("int64_col",), - ("float64_col",), - ], -) -@pytest.mark.parametrize( - ("col_y",), - [ - ("int64_col",), - ("float64_col",), - ], -) -@pytest.mark.parametrize( - ("method",), - [ - ("divmod",), - ("rdivmod",), - ], -) -def test_divmods_series(scalars_dfs, col_x, col_y, method): - scalars_df, scalars_pandas_df = scalars_dfs - bf_div_result, bf_mod_result = getattr(scalars_df[col_x], method)(scalars_df[col_y]) - pd_div_result, pd_mod_result = getattr(scalars_pandas_df[col_x], method)( - scalars_pandas_df[col_y] - ) - # BigQuery's mod functions return NUMERIC values for non-INT64 inputs. - if bf_div_result.dtype == pd.Int64Dtype(): - pd.testing.assert_series_equal(pd_div_result, bf_div_result.to_pandas()) - else: - pd.testing.assert_series_equal( - pd_div_result, bf_div_result.astype("Float64").to_pandas() - ) - - if bf_mod_result.dtype == pd.Int64Dtype(): - pd.testing.assert_series_equal(pd_mod_result, bf_mod_result.to_pandas()) - else: - pd.testing.assert_series_equal( - pd_mod_result, bf_mod_result.astype("Float64").to_pandas() - ) - - -@pytest.mark.parametrize( - ("col_x",), - [ - ("int64_col",), - ("float64_col",), - ], -) -@pytest.mark.parametrize( - ("other",), - [ - (-1000,), - (678,), - ], -) -@pytest.mark.parametrize( - ("method",), - [ - ("divmod",), - ("rdivmod",), - ], -) -def test_divmods_scalars(scalars_dfs, col_x, other, method): - scalars_df, scalars_pandas_df = scalars_dfs - bf_div_result, bf_mod_result = getattr(scalars_df[col_x], method)(other) - pd_div_result, pd_mod_result = getattr(scalars_pandas_df[col_x], method)(other) - # BigQuery's mod functions return NUMERIC values for non-INT64 inputs. - if bf_div_result.dtype == pd.Int64Dtype(): - pd.testing.assert_series_equal(pd_div_result, bf_div_result.to_pandas()) - else: - pd.testing.assert_series_equal( - pd_div_result, bf_div_result.astype("Float64").to_pandas() - ) - - if bf_mod_result.dtype == pd.Int64Dtype(): - pd.testing.assert_series_equal(pd_mod_result, bf_mod_result.to_pandas()) - else: - pd.testing.assert_series_equal( - pd_mod_result, bf_mod_result.astype("Float64").to_pandas() - ) - - -@pytest.mark.parametrize( - ("other",), - [ - (3,), - (-6.2,), - ], -) -def test_series_add_scalar(scalars_dfs, other): - scalars_df, scalars_pandas_df = scalars_dfs - bf_result = (scalars_df["float64_col"] + other).to_pandas() - pd_result = scalars_pandas_df["float64_col"] + other - - assert_series_equal(pd_result, bf_result) - - -@pytest.mark.parametrize( - ("left_col", "right_col"), - [ - ("float64_col", "float64_col"), - ("int64_col", "float64_col"), - ("int64_col", "int64_too"), - ], -) -def test_series_add_bigframes_series(scalars_dfs, left_col, right_col): - scalars_df, scalars_pandas_df = scalars_dfs - bf_result = (scalars_df[left_col] + scalars_df[right_col]).to_pandas() - pd_result = scalars_pandas_df[left_col] + scalars_pandas_df[right_col] - - assert_series_equal(pd_result, bf_result) - - -@pytest.mark.parametrize( - ("left_col", "right_col", "righter_col"), - [ - ("float64_col", "float64_col", "float64_col"), - ("int64_col", "int64_col", "int64_col"), - ], -) -def test_series_add_bigframes_series_nested( - scalars_dfs, left_col, right_col, righter_col -): - """Test that we can correctly add multiple times.""" - scalars_df, scalars_pandas_df = scalars_dfs - bf_result = ( - (scalars_df[left_col] + scalars_df[right_col]) + scalars_df[righter_col] - ).to_pandas() - pd_result = ( - scalars_pandas_df[left_col] + scalars_pandas_df[right_col] - ) + scalars_pandas_df[righter_col] - - assert_series_equal(pd_result, bf_result) - - -def test_series_add_different_table_default_index( - scalars_df_default_index, - scalars_df_2_default_index, -): - bf_result = ( - scalars_df_default_index["float64_col"] - + scalars_df_2_default_index["float64_col"] - ).to_pandas() - pd_result = ( - # Default index may not have a well defined order, but it should at - # least be consistent across to_pandas() calls. - scalars_df_default_index["float64_col"].to_pandas() - + scalars_df_2_default_index["float64_col"].to_pandas() - ) - # TODO(swast): Can remove sort_index() when there's default ordering. - pd.testing.assert_series_equal(bf_result.sort_index(), pd_result.sort_index()) - - -def test_series_add_different_table_with_index( - scalars_df_index, scalars_df_2_index, scalars_pandas_df_index -): - scalars_pandas_df = scalars_pandas_df_index - bf_result = scalars_df_index["float64_col"] + scalars_df_2_index["int64_col"] - # When index values are unique, we can emulate with values from the same - # DataFrame. - pd_result = scalars_pandas_df["float64_col"] + scalars_pandas_df["int64_col"] - pd.testing.assert_series_equal(bf_result.to_pandas(), pd_result) - - -def test_reset_index_drop(scalars_df_index, scalars_pandas_df_index): - scalars_pandas_df = scalars_pandas_df_index - bf_result = ( - scalars_df_index["float64_col"] - .sort_index(ascending=False) - .reset_index(drop=True) - ).iloc[::2] - pd_result = ( - scalars_pandas_df["float64_col"] - .sort_index(ascending=False) - .reset_index(drop=True) - ).iloc[::2] - - # BigQuery DataFrames default indices use nullable Int64 always - pd_result.index = pd_result.index.astype("Int64") - - pd.testing.assert_series_equal(bf_result.to_pandas(), pd_result) - - -def test_series_reset_index_allow_duplicates(scalars_df_index, scalars_pandas_df_index): - bf_series = scalars_df_index["int64_col"].copy() - bf_series.index.name = "int64_col" - df = bf_series.reset_index(allow_duplicates=True, drop=False) - assert df.index.name is None - - bf_result = df.to_pandas() - - pd_series = scalars_pandas_df_index["int64_col"].copy() - pd_series.index.name = "int64_col" - pd_result = pd_series.reset_index(allow_duplicates=True, drop=False) - - # Pandas uses int64 instead of Int64 (nullable) dtype. - pd_result.index = pd_result.index.astype(pd.Int64Dtype()) - - # reset_index should maintain the original ordering. - pd.testing.assert_frame_equal(bf_result, pd_result) - - -def test_series_reset_index_duplicates_error(scalars_df_index): - scalars_df_index = scalars_df_index["int64_col"].copy() - scalars_df_index.index.name = "int64_col" - with pytest.raises(ValueError): - scalars_df_index.reset_index(allow_duplicates=False, drop=False) - - -def test_series_reset_index_inplace(scalars_df_index, scalars_pandas_df_index): - bf_result = scalars_df_index.sort_index(ascending=False)["float64_col"] - bf_result.reset_index(drop=True, inplace=True) - pd_result = scalars_pandas_df_index.sort_index(ascending=False)["float64_col"] - pd_result.reset_index(drop=True, inplace=True) - - # BigQuery DataFrames default indices use nullable Int64 always - pd_result.index = pd_result.index.astype("Int64") - - pd.testing.assert_series_equal(bf_result.to_pandas(), pd_result) - - -@pytest.mark.parametrize( - ("name",), - [ - ("some_name",), - (None,), - ], -) -def test_reset_index_no_drop(scalars_df_index, scalars_pandas_df_index, name): - scalars_pandas_df = scalars_pandas_df_index - kw_args = {"name": name} if name else {} - bf_result = ( - scalars_df_index["float64_col"] - .sort_index(ascending=False) - .reset_index(drop=False, **kw_args) - ) - pd_result = ( - scalars_pandas_df["float64_col"] - .sort_index(ascending=False) - .reset_index(drop=False, **kw_args) - ) - - # BigQuery DataFrames default indices use nullable Int64 always - pd_result.index = pd_result.index.astype("Int64") - - pd.testing.assert_frame_equal(bf_result.to_pandas(), pd_result) - - -def test_copy(scalars_df_index, scalars_pandas_df_index): - col_name = "float64_col" - # Expect mutation on original not to effect_copy - bf_series = scalars_df_index[col_name].copy() - bf_copy = bf_series.copy() - bf_copy.loc[0] = 5.6 - bf_series.loc[0] = 3.4 - - pd_series = scalars_pandas_df_index[col_name].copy() - pd_copy = pd_series.copy() - pd_copy.loc[0] = 5.6 - pd_series.loc[0] = 3.4 - - assert bf_copy.to_pandas().loc[0] != bf_series.to_pandas().loc[0] - pd.testing.assert_series_equal(bf_copy.to_pandas(), pd_copy) - - -def test_isin_raise_error(scalars_df_index, scalars_pandas_df_index): - col_name = "int64_too" - with pytest.raises(TypeError): - scalars_df_index[col_name].isin("whatever").to_pandas() - - -@pytest.mark.parametrize( - ( - "col_name", - "test_set", - ), - [ - ( - "int64_col", - [314159, 2.0, 3, pd.NA], - ), - ( - "int64_col", - [2, 55555, 4], - ), - ( - "float64_col", - [-123.456, 1.25, pd.NA], - ), - ( - "int64_too", - [1, 2, pd.NA], - ), - ( - "string_col", - ["Hello, World!", "Hi", "こんにちは"], - ), - ], -) -def test_isin(scalars_dfs, col_name, test_set): - scalars_df, scalars_pandas_df = scalars_dfs - bf_result = scalars_df[col_name].isin(test_set).to_pandas() - pd_result = scalars_pandas_df[col_name].isin(test_set).astype("boolean") - pd.testing.assert_series_equal( - pd_result, - bf_result, - ) - - -@pytest.mark.parametrize( - ( - "col_name", - "test_set", - ), - [ - ( - "int64_col", - [314159, 2.0, 3, pd.NA], - ), - ( - "int64_col", - [2, 55555, 4], - ), - ( - "float64_col", - [-123.456, 1.25, pd.NA], - ), - ( - "int64_too", - [1, 2, pd.NA], - ), - ( - "string_col", - ["Hello, World!", "Hi", "こんにちは"], - ), - ], -) -def test_isin_bigframes_values(scalars_dfs, col_name, test_set, session): - scalars_df, scalars_pandas_df = scalars_dfs - bf_result = ( - scalars_df[col_name].isin(series.Series(test_set, session=session)).to_pandas() - ) - pd_result = scalars_pandas_df[col_name].isin(test_set).astype("boolean") - pd.testing.assert_series_equal( - pd_result, - bf_result, - ) - - -def test_isin_bigframes_index(scalars_dfs, session): - scalars_df, scalars_pandas_df = scalars_dfs - bf_result = ( - scalars_df["string_col"] - .isin( - bigframes.pandas.Index( - ["Hello, World!", "Hi", "こんにちは"], session=session - ) - ) - .to_pandas() - ) - pd_result = ( - scalars_pandas_df["string_col"] - .isin(pd.Index(["Hello, World!", "Hi", "こんにちは"])) - .astype("boolean") - ) - pd.testing.assert_series_equal( - pd_result, - bf_result, - ) - - -@pytest.mark.skip(reason="fixture 'scalars_dfs_maybe_ordered' not found") -@pytest.mark.parametrize( - ( - "col_name", - "test_set", - ), - [ - ( - "int64_col", - [314159, 2.0, 3, pd.NA], - ), - ( - "int64_col", - [2, 55555, 4], - ), - ( - "float64_col", - [-123.456, 1.25, pd.NA], - ), - ( - "int64_too", - [1, 2, pd.NA], - ), - ( - "string_col", - ["Hello, World!", "Hi", "こんにちは"], - ), - ], -) -def test_isin_bigframes_values_as_predicate( - scalars_dfs_maybe_ordered, col_name, test_set -): - scalars_df, scalars_pandas_df = scalars_dfs_maybe_ordered - bf_predicate = scalars_df[col_name].isin( - series.Series(test_set, session=scalars_df._session) - ) - bf_result = scalars_df[bf_predicate].to_pandas() - pd_predicate = scalars_pandas_df[col_name].isin(test_set) - pd_result = scalars_pandas_df[pd_predicate] - - pd.testing.assert_frame_equal( - pd_result.reset_index(), - bf_result.reset_index(), - ) - - -def test_isnull(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - col_name = "float64_col" - bf_series = scalars_df[col_name].isnull().to_pandas() - pd_series = scalars_pandas_df[col_name].isnull() - - # One of dtype mismatches to be documented. Here, the `bf_series.dtype` is `BooleanDtype` but - # the `pd_series.dtype` is `bool`. - assert_series_equal(pd_series.astype(pd.BooleanDtype()), bf_series) - - -def test_notnull(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - col_name = "string_col" - bf_series = scalars_df[col_name].notnull().to_pandas() - pd_series = scalars_pandas_df[col_name].notnull() - - # One of dtype mismatches to be documented. Here, the `bf_series.dtype` is `BooleanDtype` but - # the `pd_series.dtype` is `bool`. - assert_series_equal(pd_series.astype(pd.BooleanDtype()), bf_series) - - -def test_eq_scalar(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - col_name = "int64_too" - bf_result = scalars_df[col_name].eq(0).to_pandas() - pd_result = scalars_pandas_df[col_name].eq(0) - - assert_series_equal(pd_result, bf_result) - - -def test_eq_wider_type_scalar(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - col_name = "int64_too" - bf_result = scalars_df[col_name].eq(1.0).to_pandas() - pd_result = scalars_pandas_df[col_name].eq(1.0) - - assert_series_equal(pd_result, bf_result) - - -def test_ne_scalar(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - col_name = "int64_too" - bf_result = (scalars_df[col_name] != 0).to_pandas() - pd_result = scalars_pandas_df[col_name] != 0 - - assert_series_equal(pd_result, bf_result) - - -def test_eq_int_scalar(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - col_name = "int64_too" - bf_result = (scalars_df[col_name] == 0).to_pandas() - pd_result = scalars_pandas_df[col_name] == 0 - - assert_series_equal(pd_result, bf_result) - - -@pytest.mark.parametrize( - ("col_name",), - ( - ("string_col",), - ("float64_col",), - ("int64_too",), - ), -) -def test_eq_same_type_series(scalars_dfs, col_name): - scalars_df, scalars_pandas_df = scalars_dfs - col_name = "string_col" - bf_result = (scalars_df[col_name] == scalars_df[col_name]).to_pandas() - pd_result = scalars_pandas_df[col_name] == scalars_pandas_df[col_name] - - # One of dtype mismatches to be documented. Here, the `bf_series.dtype` is `BooleanDtype` but - # the `pd_series.dtype` is `bool`. - assert_series_equal(pd_result.astype(pd.BooleanDtype()), bf_result) - - -def test_loc_setitem_cell(scalars_df_index, scalars_pandas_df_index): - bf_original = scalars_df_index["string_col"] - bf_series = scalars_df_index["string_col"] - pd_original = scalars_pandas_df_index["string_col"] - pd_series = scalars_pandas_df_index["string_col"].copy() - bf_series.loc[2] = "This value isn't in the test data." - pd_series.loc[2] = "This value isn't in the test data." - bf_result = bf_series.to_pandas() - pd_result = pd_series - pd.testing.assert_series_equal(bf_result, pd_result) - # Per Copy-on-Write semantics, other references to the original DataFrame - # should remain unchanged. - pd.testing.assert_series_equal(bf_original.to_pandas(), pd_original) - - -def test_at_setitem_row_label_scalar(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - bf_series = scalars_df["int64_col"] - pd_series = scalars_pandas_df["int64_col"].copy() - bf_series.at[1] = 1000 - pd_series.at[1] = 1000 - bf_result = bf_series.to_pandas() - pd_result = pd_series.astype("Int64") - pd.testing.assert_series_equal(bf_result, pd_result) - - -def test_ne_obj_series(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - col_name = "string_col" - bf_result = (scalars_df[col_name] != scalars_df[col_name]).to_pandas() - pd_result = scalars_pandas_df[col_name] != scalars_pandas_df[col_name] - - # One of dtype mismatches to be documented. Here, the `bf_series.dtype` is `BooleanDtype` but - # the `pd_series.dtype` is `bool`. - assert_series_equal(pd_result.astype(pd.BooleanDtype()), bf_result) - - -def test_indexing_using_unselected_series(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - col_name = "string_col" - bf_result = scalars_df[col_name][scalars_df["int64_too"].eq(0)].to_pandas() - pd_result = scalars_pandas_df[col_name][scalars_pandas_df["int64_too"].eq(0)] - - assert_series_equal( - pd_result, - bf_result, - ) - - -def test_indexing_using_selected_series(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - col_name = "string_col" - bf_result = scalars_df[col_name][ - scalars_df["string_col"].eq("Hello, World!") - ].to_pandas() - pd_result = scalars_pandas_df[col_name][ - scalars_pandas_df["string_col"].eq("Hello, World!") - ] - - assert_series_equal( - pd_result, - bf_result, - ) - - -@pytest.mark.parametrize( - ("indices"), - [ - ([1, 3, 5]), - ([5, -3, -5, -6]), - ([-2, -4, -6]), - ], -) -def test_take(scalars_dfs, indices): - scalars_df, scalars_pandas_df = scalars_dfs - - bf_result = scalars_df.take(indices).to_pandas() - pd_result = scalars_pandas_df.take(indices) - - assert_frame_equal(bf_result, pd_result) - - -def test_nested_filter(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - string_col = scalars_df["string_col"] - int64_too = scalars_df["int64_too"] - bool_col = scalars_df["bool_col"] == bool( - True - ) # Convert from nullable bool to nonnullable bool usable as indexer - bf_result = string_col[int64_too == 0][~bool_col].to_pandas() - - pd_string_col = scalars_pandas_df["string_col"] - pd_int64_too = scalars_pandas_df["int64_too"] - pd_bool_col = scalars_pandas_df["bool_col"] == bool( - True - ) # Convert from nullable bool to nonnullable bool usable as indexer - pd_result = pd_string_col[pd_int64_too == 0][~pd_bool_col] - - assert_series_equal( - pd_result, - bf_result, - ) - - -def test_binop_opposite_filters(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - int64_col1 = scalars_df["int64_col"] - int64_col2 = scalars_df["int64_col"] - bool_col = scalars_df["bool_col"] - bf_result = (int64_col1[bool_col] + int64_col2[bool_col.__invert__()]).to_pandas() - - pd_int64_col1 = scalars_pandas_df["int64_col"] - pd_int64_col2 = scalars_pandas_df["int64_col"] - pd_bool_col = scalars_pandas_df["bool_col"] - pd_result = pd_int64_col1[pd_bool_col] + pd_int64_col2[pd_bool_col.__invert__()] - - # Passes with ignore_order=False only with some dependency sets - # TODO: Determine desired behavior and make test more strict - assert_series_equal(bf_result, pd_result, ignore_order=True) - - -def test_binop_left_filtered(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - int64_col = scalars_df["int64_col"] - float64_col = scalars_df["float64_col"] - bool_col = scalars_df["bool_col"] - bf_result = (int64_col[bool_col] + float64_col).to_pandas() - - pd_int64_col = scalars_pandas_df["int64_col"] - pd_float64_col = scalars_pandas_df["float64_col"] - pd_bool_col = scalars_pandas_df["bool_col"] - pd_result = pd_int64_col[pd_bool_col] + pd_float64_col - - # Passes with ignore_order=False only with some dependency sets - # TODO: Determine desired behavior and make test more strict - assert_series_equal(bf_result, pd_result, ignore_order=True) - - -def test_binop_right_filtered(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - int64_col = scalars_df["int64_col"] - float64_col = scalars_df["float64_col"] - bool_col = scalars_df["bool_col"] - bf_result = (float64_col + int64_col[bool_col]).to_pandas() - - pd_int64_col = scalars_pandas_df["int64_col"] - pd_float64_col = scalars_pandas_df["float64_col"] - pd_bool_col = scalars_pandas_df["bool_col"] - pd_result = pd_float64_col + pd_int64_col[pd_bool_col] - - assert_series_equal( - bf_result, - pd_result, - ) - - -@pytest.mark.parametrize( - ("other",), - [ - ([-1.4, 2.3, None],), - (pd.Index([-1.4, 2.3, None]),), - (pd.Series([-1.4, 2.3, None], index=[44, 2, 1]),), - ], -) -def test_series_binop_w_other_types(scalars_dfs, other): - # 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 - - bf_result = (scalars_df["int64_col"].head(3) + other).to_pandas() - pd_result = scalars_pandas_df["int64_col"].head(3) + other - - if isinstance(other, pd.Series): - # pandas 3.0 preserves series name, bigframe, earlier pandas do not - pd_result.index.name = bf_result.index.name - - assert_series_equal( - bf_result, - pd_result, - ) - - -@pytest.mark.parametrize( - ("other",), - [ - ([-1.4, 2.3, None],), - (pd.Index([-1.4, 2.3, None]),), - (pd.Series([-1.4, 2.3, None], index=[44, 2, 1]),), - ], -) -def test_series_reverse_binop_w_other_types(scalars_dfs, other): - # 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 - - bf_result = (other + scalars_df["int64_col"].head(3)).to_pandas() - pd_result = other + scalars_pandas_df["int64_col"].head(3) - - assert_series_equal( - bf_result, - pd_result, - ) - - -def test_series_combine_first(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 - int64_col = scalars_df["int64_col"].head(7) - float64_col = scalars_df["float64_col"].tail(7) - bf_result = int64_col.combine_first(float64_col).to_pandas() - - pd_int64_col = scalars_pandas_df["int64_col"].head(7) - pd_float64_col = scalars_pandas_df["float64_col"].tail(7) - pd_result = pd_int64_col.combine_first(pd_float64_col) - - assert_series_equal( - bf_result, - pd_result, - ) - - -def test_series_update(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - int64_col = scalars_df["int64_col"].head(7) - float64_col = scalars_df["float64_col"].tail(7).copy() - float64_col.update(int64_col) - - pd_int64_col = scalars_pandas_df["int64_col"].head(7) - pd_float64_col = scalars_pandas_df["float64_col"].tail(7).copy() - pd_float64_col.update(pd_int64_col) - - assert_series_equal( - float64_col.to_pandas(), - pd_float64_col, - ) - - -def test_mean(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - col_name = "int64_col" - bf_result = scalars_df[col_name].mean() - pd_result = scalars_pandas_df[col_name].mean() - assert math.isclose(pd_result, bf_result) - - -@pytest.mark.parametrize( - ("col_name"), - [ - pytest.param( - "int64_col", - marks=[ - pytest.mark.skip( - reason="pyarrow.lib.ArrowInvalid: Float value 27778.500000 was truncated converting to int64" - ) - ], - ), - # Non-numeric column - pytest.param( - "bytes_col", - marks=[ - pytest.mark.skip( - reason="polars.exceptions.InvalidOperationError: `median` operation not supported for dtype `binary`" - ) - ], - ), - "date_col", - "datetime_col", - pytest.param( - "time_col", - marks=[ - pytest.mark.skip( - reason="pyarrow.lib.ArrowInvalid: Casting from time64[ns] to time64[us] would lose data: 42651538080500" - ) - ], - ), - "timestamp_col", - pytest.param( - "string_col", - marks=[ - pytest.mark.skip( - reason="polars.exceptions.InvalidOperationError: `median` operation not supported for dtype `str`" - ) - ], - ), - ], -) -def test_median(scalars_dfs, col_name): - scalars_df, scalars_pandas_df = scalars_dfs - bf_result = scalars_df[col_name].median(exact=False) - pd_max = scalars_pandas_df[col_name].max() - pd_min = scalars_pandas_df[col_name].min() - # Median is approximate, so just check for plausibility. - assert pd_min < bf_result < pd_max - - -def test_numeric_literal(scalars_dfs): - scalars_df, _ = scalars_dfs - col_name = "numeric_col" - assert scalars_df[col_name].dtype == pd.ArrowDtype(pa.decimal128(38, 9)) - bf_result = scalars_df[col_name] + 42 - assert bf_result.size == scalars_df[col_name].size - assert bf_result.dtype == pd.ArrowDtype(pa.decimal128(38, 9)) - - -def test_series_small_repr(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - - col_name = "int64_col" - bf_series = scalars_df[col_name] - pd_series = scalars_pandas_df[col_name] - with bigframes.pandas.option_context("display.repr_mode", "head"): - assert repr(bf_series) == pd_series.to_string( - length=False, dtype=True, name=True - ) - - -def test_sum(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - col_name = "int64_col" - bf_result = scalars_df[col_name].sum() - pd_result = scalars_pandas_df[col_name].sum() - assert pd_result == bf_result - - -def test_product(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - col_name = "float64_col" - bf_result = scalars_df[col_name].product() - pd_result = scalars_pandas_df[col_name].product() - assert math.isclose(pd_result, bf_result) - - -def test_cumprod(scalars_dfs): - if pd.__version__.startswith("1."): - pytest.skip("Series.cumprod NA mask are different in pandas 1.x.") - scalars_df, scalars_pandas_df = scalars_dfs - col_name = "float64_col" - bf_result = scalars_df[col_name].cumprod() - pd_result = scalars_pandas_df[col_name].cumprod() - pd.testing.assert_series_equal( - pd_result, - bf_result.to_pandas(), - ) - - -def test_count(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - col_name = "int64_col" - bf_result = scalars_df[col_name].count() - pd_result = scalars_pandas_df[col_name].count() - assert pd_result == bf_result - - -def test_nunique(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - col_name = "int64_col" - bf_result = (scalars_df[col_name] % 3).nunique() - pd_result = (scalars_pandas_df[col_name] % 3).nunique() - assert pd_result == bf_result - - -def test_all(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - col_name = "int64_col" - bf_result = scalars_df[col_name].all() - pd_result = scalars_pandas_df[col_name].all() - assert pd_result == bf_result - - -def test_any(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - col_name = "int64_col" - bf_result = scalars_df[col_name].any() - pd_result = scalars_pandas_df[col_name].any() - assert pd_result == bf_result - - -def test_groupby_sum(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - col_name = "int64_too" - bf_series = ( - scalars_df[col_name] - .groupby([scalars_df["bool_col"], ~scalars_df["bool_col"]]) - .sum() - ) - pd_series = ( - scalars_pandas_df[col_name] - .groupby([scalars_pandas_df["bool_col"], ~scalars_pandas_df["bool_col"]]) - .sum() - ) - # TODO(swast): Update groupby to use index based on group by key(s). - bf_result = bf_series.to_pandas() - assert_series_equal( - pd_series, - bf_result, - check_exact=False, - ) - - -def test_groupby_std(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - col_name = "int64_too" - bf_series = scalars_df[col_name].groupby(scalars_df["string_col"]).std() - pd_series = ( - scalars_pandas_df[col_name] - .groupby(scalars_pandas_df["string_col"]) - .std() - .astype(pd.Float64Dtype()) - ) - bf_result = bf_series.to_pandas() - assert_series_equal( - pd_series, - bf_result, - check_exact=False, - ) - - -def test_groupby_var(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - col_name = "int64_too" - bf_series = scalars_df[col_name].groupby(scalars_df["string_col"]).var() - pd_series = ( - scalars_pandas_df[col_name].groupby(scalars_pandas_df["string_col"]).var() - ) - bf_result = bf_series.to_pandas() - assert_series_equal( - pd_series, - bf_result, - check_exact=False, - ) - - -def test_groupby_level_sum(scalars_dfs): - # TODO(tbergeron): Use a non-unique index once that becomes possible in tests - scalars_df, scalars_pandas_df = scalars_dfs - col_name = "int64_too" - - bf_series = scalars_df[col_name].groupby(level=0).sum() - pd_series = scalars_pandas_df[col_name].groupby(level=0).sum() - # TODO(swast): Update groupby to use index based on group by key(s). - pd.testing.assert_series_equal( - pd_series.sort_index(), - bf_series.to_pandas().sort_index(), - ) - - -def test_groupby_level_list_sum(scalars_dfs): - # TODO(tbergeron): Use a non-unique index once that becomes possible in tests - scalars_df, scalars_pandas_df = scalars_dfs - col_name = "int64_too" - - bf_series = scalars_df[col_name].groupby(level=["rowindex"]).sum() - pd_series = scalars_pandas_df[col_name].groupby(level=["rowindex"]).sum() - # TODO(swast): Update groupby to use index based on group by key(s). - pd.testing.assert_series_equal( - pd_series.sort_index(), - bf_series.to_pandas().sort_index(), - ) - - -def test_groupby_mean(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - col_name = "int64_too" - bf_series = ( - scalars_df[col_name].groupby(scalars_df["string_col"], dropna=False).mean() - ) - pd_series = ( - scalars_pandas_df[col_name] - .groupby(scalars_pandas_df["string_col"], dropna=False) - .mean() - ) - # TODO(swast): Update groupby to use index based on group by key(s). - bf_result = bf_series.to_pandas() - assert_series_equal( - pd_series, - bf_result, - ) - - -@pytest.mark.skip( - reason="Aggregate op QuantileOp(q=0.5, should_floor_result=False) not yet supported in polars engine." -) -def test_groupby_median_exact(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - col_name = "int64_too" - bf_result = ( - scalars_df[col_name].groupby(scalars_df["string_col"], dropna=False).median() - ) - pd_result = ( - scalars_pandas_df[col_name] - .groupby(scalars_pandas_df["string_col"], dropna=False) - .median() - ) - - assert_series_equal( - pd_result, - bf_result.to_pandas(), - ) - - -@pytest.mark.skip( - reason="pyarrow.lib.ArrowInvalid: Float value -1172.500000 was truncated converting to int64" -) -def test_groupby_median_inexact(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - col_name = "int64_too" - bf_series = ( - scalars_df[col_name] - .groupby(scalars_df["string_col"], dropna=False) - .median(exact=False) - ) - pd_max = ( - scalars_pandas_df[col_name] - .groupby(scalars_pandas_df["string_col"], dropna=False) - .max() - ) - pd_min = ( - scalars_pandas_df[col_name] - .groupby(scalars_pandas_df["string_col"], dropna=False) - .min() - ) - # TODO(swast): Update groupby to use index based on group by key(s). - bf_result = bf_series.to_pandas() - - # Median is approximate, so just check that it's plausible. - assert ((pd_min <= bf_result) & (bf_result <= pd_max)).all() - - -def test_groupby_prod(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - col_name = "int64_too" - bf_series = scalars_df[col_name].groupby(scalars_df["int64_col"]).prod() - pd_series = ( - scalars_pandas_df[col_name].groupby(scalars_pandas_df["int64_col"]).prod() - ).astype(pd.Float64Dtype()) - # TODO(swast): Update groupby to use index based on group by key(s). - bf_result = bf_series.to_pandas() - assert_series_equal( - pd_series, - bf_result, - ) - - -@pytest.mark.skip(reason="AssertionError: Series are different") -@pytest.mark.parametrize( - ("operator"), - [ - (lambda x: x.cumsum()), - (lambda x: x.cumcount()), - (lambda x: x.cummin()), - (lambda x: x.cummax()), - # Pandas 2.2 casts to cumprod to float. - (lambda x: x.cumprod().astype("Float64")), - (lambda x: x.diff()), - (lambda x: x.shift(2)), - (lambda x: x.shift(-2)), - ], - ids=[ - "cumsum", - "cumcount", - "cummin", - "cummax", - "cumprod", - "diff", - "shiftpostive", - "shiftnegative", - ], -) -def test_groupby_window_ops(scalars_df_index, scalars_pandas_df_index, operator): - col_name = "int64_col" - group_key = "int64_too" # has some duplicates values, good for grouping - bf_series = ( - operator(scalars_df_index[col_name].groupby(scalars_df_index[group_key])) - ).to_pandas() - pd_series = operator( - scalars_pandas_df_index[col_name].groupby(scalars_pandas_df_index[group_key]) - ).astype(bf_series.dtype) - - pd.testing.assert_series_equal( - pd_series, - bf_series, - ) - - -@pytest.mark.parametrize( - ("label", "col_name"), - [ - (0, "bool_col"), - (1, "int64_col"), - ], -) -def test_drop_label(scalars_df_index, scalars_pandas_df_index, label, col_name): - bf_series = scalars_df_index[col_name].drop(label).to_pandas() - pd_series = scalars_pandas_df_index[col_name].drop(label) - pd.testing.assert_series_equal( - pd_series, - bf_series, - ) - - -def test_drop_label_list(scalars_df_index, scalars_pandas_df_index): - col_name = "int64_col" - bf_series = scalars_df_index[col_name].drop([1, 3]).to_pandas() - pd_series = scalars_pandas_df_index[col_name].drop([1, 3]) - pd.testing.assert_series_equal( - pd_series, - bf_series, - ) - - -@pytest.mark.skip(reason="AssertionError: Series.index are different") -@pytest.mark.parametrize( - ("col_name",), - [ - ("bool_col",), - ("int64_too",), - ], -) -@pytest.mark.parametrize( - ("keep",), - [ - ("first",), - ("last",), - (False,), - ], -) -def test_drop_duplicates(scalars_df_index, scalars_pandas_df_index, keep, col_name): - bf_series = scalars_df_index[col_name].drop_duplicates(keep=keep).to_pandas() - pd_series = scalars_pandas_df_index[col_name].drop_duplicates(keep=keep) - pd.testing.assert_series_equal( - pd_series, - bf_series, - ) - - -@pytest.mark.skip(reason="TypeError: boolean value of NA is ambiguous") -@pytest.mark.parametrize( - ("col_name",), - [ - ("bool_col",), - ("int64_too",), - ], -) -def test_unique(scalars_df_index, scalars_pandas_df_index, col_name): - bf_uniq = scalars_df_index[col_name].unique().to_numpy(na_value=None) - pd_uniq = scalars_pandas_df_index[col_name].unique() - numpy.array_equal(pd_uniq, bf_uniq) - - -@pytest.mark.skip(reason="AssertionError: Series are different") -@pytest.mark.parametrize( - ("col_name",), - [ - ("bool_col",), - ("int64_too",), - ], -) -@pytest.mark.parametrize( - ("keep",), - [ - ("first",), - ("last",), - (False,), - ], -) -def test_duplicated(scalars_df_index, scalars_pandas_df_index, keep, col_name): - bf_series = scalars_df_index[col_name].duplicated(keep=keep).to_pandas() - pd_series = scalars_pandas_df_index[col_name].duplicated(keep=keep) - pd.testing.assert_series_equal(pd_series, bf_series, check_dtype=False) - - -def test_shape(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - - bf_result = scalars_df["string_col"].shape - pd_result = scalars_pandas_df["string_col"].shape - - assert pd_result == bf_result - - -def test_len(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - - bf_result = len(scalars_df["string_col"]) - pd_result = len(scalars_pandas_df["string_col"]) - - assert pd_result == bf_result - - -def test_size(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - - bf_result = scalars_df["string_col"].size - pd_result = scalars_pandas_df["string_col"].size - - assert pd_result == bf_result - - -def test_series_hasnans_true(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - - bf_result = scalars_df["string_col"].hasnans - pd_result = scalars_pandas_df["string_col"].hasnans - - assert pd_result == bf_result - - -def test_series_hasnans_false(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - - bf_result = scalars_df["string_col"].dropna().hasnans - pd_result = scalars_pandas_df["string_col"].dropna().hasnans - - assert pd_result == bf_result - - -def test_empty_false(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - - bf_result = scalars_df["string_col"].empty - pd_result = scalars_pandas_df["string_col"].empty - - assert pd_result == bf_result - - -def test_empty_true_row_filter(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - - bf_result = scalars_df["string_col"][ - scalars_df["string_col"] == "won't find this" - ].empty - pd_result = scalars_pandas_df["string_col"][ - scalars_pandas_df["string_col"] == "won't find this" - ].empty - - assert pd_result - assert pd_result == bf_result - - -def test_series_names(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - - bf_result = scalars_df["string_col"].copy() - bf_result.index.name = "new index name" - bf_result.name = "new series name" - - pd_result = scalars_pandas_df["string_col"].copy() - pd_result.index.name = "new index name" - pd_result.name = "new series name" - - assert pd_result.name == bf_result.name - assert pd_result.index.name == bf_result.index.name - - -def test_dtype(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - - bf_result = scalars_df["string_col"].dtype - pd_result = scalars_pandas_df["string_col"].dtype - - assert pd_result == bf_result - - -def test_dtypes(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - - bf_result = scalars_df["int64_col"].dtypes - pd_result = scalars_pandas_df["int64_col"].dtypes - - assert pd_result == bf_result - - -def test_head(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - - bf_result = scalars_df["string_col"].head(2).to_pandas() - pd_result = scalars_pandas_df["string_col"].head(2) - - assert_series_equal( - pd_result, - bf_result, - ) - - -def test_tail(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - - bf_result = scalars_df["string_col"].tail(2).to_pandas() - pd_result = scalars_pandas_df["string_col"].tail(2) - - assert_series_equal( - pd_result, - bf_result, - ) - - -def test_head_then_scalar_operation(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - - bf_result = (scalars_df["float64_col"].head(1) + 4).to_pandas() - pd_result = scalars_pandas_df["float64_col"].head(1) + 4 - - pd.testing.assert_series_equal( - bf_result, - pd_result, - ) - - -def test_head_then_series_operation(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - - bf_result = ( - scalars_df["float64_col"].head(4) + scalars_df["float64_col"].head(2) - ).to_pandas() - pd_result = scalars_pandas_df["float64_col"].head(4) + scalars_pandas_df[ - "float64_col" - ].head(2) - - pd.testing.assert_series_equal( - bf_result, - pd_result, - ) - - -def test_series_peek(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - - peek_result = scalars_df["float64_col"].peek(n=3, force=False) - - pd.testing.assert_series_equal( - peek_result, - scalars_pandas_df["float64_col"].reindex_like(peek_result), - ) - assert len(peek_result) == 3 - - -def test_series_peek_with_large_results_not_allowed(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - - session = scalars_df._block.session - slot_millis_sum = session.slot_millis_sum - peek_result = scalars_df["float64_col"].peek( - n=3, force=False, allow_large_results=False - ) - - # The metrics won't be fully updated when we call query_and_wait. - print(session.slot_millis_sum - slot_millis_sum) - assert session.slot_millis_sum - slot_millis_sum < 500 - pd.testing.assert_series_equal( - peek_result, - scalars_pandas_df["float64_col"].reindex_like(peek_result), - ) - assert len(peek_result) == 3 - - -def test_series_peek_multi_index(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - bf_series = scalars_df.set_index(["string_col", "bool_col"])["float64_col"] - bf_series.name = ("2-part", "name") - pd_series = scalars_pandas_df.set_index(["string_col", "bool_col"])["float64_col"] - pd_series.name = ("2-part", "name") - peek_result = bf_series.peek(n=3, force=False) - pd.testing.assert_series_equal( - peek_result, - pd_series.reindex_like(peek_result), - ) - - -def test_series_peek_filtered(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - peek_result = scalars_df[scalars_df.int64_col > 0]["float64_col"].peek( - n=3, force=False - ) - pd_result = scalars_pandas_df[scalars_pandas_df.int64_col > 0]["float64_col"] - pd.testing.assert_series_equal( - peek_result, - pd_result.reindex_like(peek_result), - ) - - -def test_series_peek_force(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 - - cumsum_df = scalars_df[["int64_col", "int64_too"]].cumsum() - df_filtered = cumsum_df[cumsum_df.int64_col > 0]["int64_too"] - peek_result = df_filtered.peek(n=3, force=True) - pd_cumsum_df = scalars_pandas_df[["int64_col", "int64_too"]].cumsum() - pd_result = pd_cumsum_df[pd_cumsum_df.int64_col > 0]["int64_too"] - pd.testing.assert_series_equal( - peek_result, - pd_result.reindex_like(peek_result), - ) - - -def test_series_peek_force_float(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 - - cumsum_df = scalars_df[["int64_col", "float64_col"]].cumsum() - df_filtered = cumsum_df[cumsum_df.float64_col > 0]["float64_col"] - peek_result = df_filtered.peek(n=3, force=True) - pd_cumsum_df = scalars_pandas_df[["int64_col", "float64_col"]].cumsum() - pd_result = pd_cumsum_df[pd_cumsum_df.float64_col > 0]["float64_col"] - pd.testing.assert_series_equal( - peek_result, - pd_result.reindex_like(peek_result), - ) - - -def test_shift(scalars_df_index, scalars_pandas_df_index): - col_name = "int64_col" - bf_result = scalars_df_index[col_name].shift().to_pandas() - # cumsum does not behave well on nullable ints in pandas, produces object type and never ignores NA - pd_result = scalars_pandas_df_index[col_name].shift().astype(pd.Int64Dtype()) - - pd.testing.assert_series_equal( - bf_result, - pd_result, - ) - - -def test_series_ffill(scalars_df_index, scalars_pandas_df_index): - col_name = "numeric_col" - bf_result = scalars_df_index[col_name].ffill(limit=1).to_pandas() - pd_result = scalars_pandas_df_index[col_name].ffill(limit=1) - - pd.testing.assert_series_equal( - bf_result, - pd_result, - ) - - -def test_series_bfill(scalars_df_index, scalars_pandas_df_index): - col_name = "numeric_col" - bf_result = scalars_df_index[col_name].bfill(limit=2).to_pandas() - pd_result = scalars_pandas_df_index[col_name].bfill(limit=2) - - pd.testing.assert_series_equal( - bf_result, - pd_result, - ) - - -def test_cumsum_int(scalars_df_index, scalars_pandas_df_index): - if pd.__version__.startswith("1."): - pytest.skip("Series.cumsum NA mask are different in pandas 1.x.") - - col_name = "int64_col" - bf_result = scalars_df_index[col_name].cumsum().to_pandas() - # cumsum does not behave well on nullable ints in pandas, produces object type and never ignores NA - pd_result = scalars_pandas_df_index[col_name].cumsum().astype(pd.Int64Dtype()) - - pd.testing.assert_series_equal( - bf_result, - pd_result, - ) - - -def test_cumsum_int_ordered(scalars_df_index, scalars_pandas_df_index): - if pd.__version__.startswith("1."): - pytest.skip("Series.cumsum NA mask are different in pandas 1.x.") - - col_name = "int64_col" - bf_result = ( - scalars_df_index.sort_values(by="rowindex_2")[col_name].cumsum().to_pandas() - ) - # cumsum does not behave well on nullable ints in pandas, produces object type and never ignores NA - pd_result = ( - scalars_pandas_df_index.sort_values(by="rowindex_2")[col_name] - .cumsum() - .astype(pd.Int64Dtype()) - ) - - pd.testing.assert_series_equal( - bf_result, - pd_result, - ) - - -@pytest.mark.skip( - reason="NotImplementedError: Aggregate op RankOp() not yet supported in polars engine." -) -@pytest.mark.parametrize( - ("keep",), - [ - ("first",), - ("last",), - ("all",), - ], -) -def test_series_nlargest(scalars_df_index, scalars_pandas_df_index, keep): - col_name = "bool_col" - bf_result = scalars_df_index[col_name].nlargest(4, keep=keep).to_pandas() - pd_result = scalars_pandas_df_index[col_name].nlargest(4, keep=keep) - - pd.testing.assert_series_equal( - bf_result, - pd_result, - ) - - -@pytest.mark.parametrize( - ("periods",), - [ - (1,), - (2,), - (-1,), - ], -) -def test_diff(scalars_df_index, scalars_pandas_df_index, periods): - bf_result = scalars_df_index["int64_col"].diff(periods=periods).to_pandas() - # cumsum does not behave well on nullable ints in pandas, produces object type and never ignores NA - pd_result = ( - scalars_pandas_df_index["int64_col"] - .diff(periods=periods) - .astype(pd.Int64Dtype()) - ) - - pd.testing.assert_series_equal( - bf_result, - pd_result, - ) - - -@pytest.mark.parametrize( - ("periods",), - [ - (1,), - (2,), - (-1,), - ], -) -def test_series_pct_change(scalars_df_index, scalars_pandas_df_index, periods): - bf_result = scalars_df_index["int64_col"].pct_change(periods=periods).to_pandas() - # cumsum does not behave well on nullable ints in pandas, produces object type and never ignores NA - pd_result = scalars_pandas_df_index["int64_col"].ffill().pct_change(periods=periods) - - assert_series_equal(bf_result, pd_result, nulls_are_nan=True) - - -@pytest.mark.skip( - reason="NotImplementedError: Aggregate op RankOp() not yet supported in polars engine." -) -@pytest.mark.parametrize( - ("keep",), - [ - ("first",), - ("last",), - ("all",), - ], -) -def test_series_nsmallest(scalars_df_index, scalars_pandas_df_index, keep): - col_name = "bool_col" - bf_result = scalars_df_index[col_name].nsmallest(2, keep=keep).to_pandas() - pd_result = scalars_pandas_df_index[col_name].nsmallest(2, keep=keep) - - pd.testing.assert_series_equal( - bf_result, - pd_result, - ) - - -@pytest.mark.skip( - reason="NotImplementedError: Aggregate op DenseRankOp() not yet supported in polars engine." -) -@pytest.mark.parametrize( - ("na_option", "method", "ascending", "numeric_only", "pct"), - [ - ("keep", "average", True, True, False), - ("top", "min", False, False, True), - ("bottom", "max", False, False, False), - ("top", "first", False, False, True), - ("bottom", "dense", False, False, False), - ], -) -def test_series_rank( - scalars_df_index, - scalars_pandas_df_index, - na_option, - method, - ascending, - numeric_only, - pct, -): - col_name = "int64_too" - bf_result = ( - scalars_df_index[col_name] - .rank( - na_option=na_option, - method=method, - ascending=ascending, - numeric_only=numeric_only, - pct=pct, - ) - .to_pandas() - ) - pd_result = ( - scalars_pandas_df_index[col_name] - .rank( - na_option=na_option, - method=method, - ascending=ascending, - numeric_only=numeric_only, - pct=pct, - ) - .astype(pd.Float64Dtype()) - ) - - pd.testing.assert_series_equal( - bf_result, - pd_result, - ) - - -def test_cast_float_to_int(scalars_df_index, scalars_pandas_df_index): - col_name = "float64_col" - bf_result = scalars_df_index[col_name].astype(pd.Int64Dtype()).to_pandas() - # cumsum does not behave well on nullable floats in pandas, produces object type and never ignores NA - pd_result = scalars_pandas_df_index[col_name].astype(pd.Int64Dtype()) - - pd.testing.assert_series_equal( - bf_result, - pd_result, - ) - - -def test_cast_float_to_bool(scalars_df_index, scalars_pandas_df_index): - col_name = "float64_col" - bf_result = scalars_df_index[col_name].astype(pd.BooleanDtype()).to_pandas() - # cumsum does not behave well on nullable floats in pandas, produces object type and never ignores NA - pd_result = scalars_pandas_df_index[col_name].astype(pd.BooleanDtype()) - - pd.testing.assert_series_equal( - bf_result, - pd_result, - ) - - -def test_cumsum_nested(scalars_df_index, scalars_pandas_df_index): - col_name = "float64_col" - bf_result = scalars_df_index[col_name].cumsum().cumsum().cumsum().to_pandas() - # cumsum does not behave well on nullable ints in pandas, produces object type and never ignores NA - pd_result = ( - scalars_pandas_df_index[col_name] - .cumsum() - .cumsum() - .cumsum() - .astype(pd.Float64Dtype()) - ) - - pd.testing.assert_series_equal( - bf_result, - pd_result, - ) - - -@pytest.mark.skip( - reason="NotImplementedError: min_period not yet supported for polars engine" -) -def test_nested_analytic_ops_align(scalars_df_index, scalars_pandas_df_index): - # TODO: supply a reason why this isn't compatible with pandas 1.x - pytest.importorskip("pandas", minversion="2.0.0") - col_name = "float64_col" - # set non-unique index to check implicit alignment - bf_series = scalars_df_index.set_index("bool_col")[col_name].fillna(0.0) - pd_series = scalars_pandas_df_index.set_index("bool_col")[col_name].fillna(0.0) - - bf_result = ( - (bf_series + 5) - + (bf_series.cumsum().cumsum().cumsum() + bf_series.rolling(window=3).mean()) - + bf_series.expanding().max() - ).to_pandas() - # cumsum does not behave well on nullable ints in pandas, produces object type and never ignores NA - pd_result = ( - (pd_series + 5) - + ( - pd_series.cumsum().cumsum().cumsum().astype(pd.Float64Dtype()) - + pd_series.rolling(window=3).mean() - ) - + pd_series.expanding().max() - ) - - pd.testing.assert_series_equal( - bf_result, - pd_result, - ) - - -def test_cumsum_int_filtered(scalars_df_index, scalars_pandas_df_index): - col_name = "int64_col" - - bf_col = scalars_df_index[col_name] - bf_result = bf_col[bf_col > -2].cumsum().to_pandas() - - pd_col = scalars_pandas_df_index[col_name] - # cumsum does not behave well on nullable ints in pandas, produces object type and never ignores NA - pd_result = pd_col[pd_col > -2].cumsum().astype(pd.Int64Dtype()) - - pd.testing.assert_series_equal( - bf_result, - pd_result, - ) - - -def test_cumsum_float(scalars_df_index, scalars_pandas_df_index): - col_name = "float64_col" - bf_result = scalars_df_index[col_name].cumsum().to_pandas() - # cumsum does not behave well on nullable floats in pandas, produces object type and never ignores NA - pd_result = scalars_pandas_df_index[col_name].cumsum().astype(pd.Float64Dtype()) - - pd.testing.assert_series_equal( - bf_result, - pd_result, - ) - - -def test_cummin_int(scalars_df_index, scalars_pandas_df_index): - col_name = "int64_col" - bf_result = scalars_df_index[col_name].cummin().to_pandas() - pd_result = scalars_pandas_df_index[col_name].cummin() - - pd.testing.assert_series_equal( - bf_result, - pd_result, - ) - - -def test_cummax_int(scalars_df_index, scalars_pandas_df_index): - col_name = "int64_col" - bf_result = scalars_df_index[col_name].cummax().to_pandas() - pd_result = scalars_pandas_df_index[col_name].cummax() - - pd.testing.assert_series_equal( - bf_result, - pd_result, - ) - - -@pytest.mark.parametrize( - ("kwargs"), - [ - {}, - {"normalize": True}, - {"ascending": True}, - ], - ids=[ - "default", - "normalize", - "ascending", - ], -) -def test_value_counts(scalars_dfs, kwargs): - if pd.__version__.startswith("1."): - pytest.skip("pandas 1.x produces different column labels.") - scalars_df, scalars_pandas_df = scalars_dfs - col_name = "int64_too" - - # Pandas `value_counts` can produce non-deterministic results with tied counts. - # Remove duplicates to enforce a consistent output. - s = scalars_df[col_name].drop(0) - pd_s = scalars_pandas_df[col_name].drop(0) - - bf_result = s.value_counts(**kwargs).to_pandas() - pd_result = pd_s.value_counts(**kwargs) - - pd.testing.assert_series_equal( - bf_result, - pd_result, - ) - - -def test_value_counts_with_na(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - col_name = "int64_col" - - bf_result = scalars_df[col_name].value_counts(dropna=False).to_pandas() - pd_result = scalars_pandas_df[col_name].value_counts(dropna=False) - - # Older pandas version may not have these values, bigframes tries to emulate 2.0+ - pd_result.name = "count" - pd_result.index.name = col_name - - assert_series_equal( - bf_result, - pd_result, - # bigframes values_counts does not honor ordering in the original data - ignore_order=True, - ) - - -@pytest.mark.skip( - reason="NotImplementedError: Aggregate op CutOp(bins=3, right=True, labels=False) not yet supported in polars engine." -) -def test_value_counts_w_cut(scalars_dfs): - if pd.__version__.startswith("1."): - pytest.skip("value_counts results different in pandas 1.x.") - scalars_df, scalars_pandas_df = scalars_dfs - col_name = "int64_col" - - bf_cut = bigframes.pandas.cut(scalars_df[col_name], 3, labels=False) - pd_cut = pd.cut(scalars_pandas_df[col_name], 3, labels=False) - - bf_result = bf_cut.value_counts().to_pandas() - pd_result = pd_cut.value_counts() - pd_result.index = pd_result.index.astype(pd.Int64Dtype()) - - pd.testing.assert_series_equal( - bf_result, - pd_result.astype(pd.Int64Dtype()), - ) - - -def test_iloc_nested(scalars_df_index, scalars_pandas_df_index): - bf_result = scalars_df_index["string_col"].iloc[1:].iloc[1:].to_pandas() - pd_result = scalars_pandas_df_index["string_col"].iloc[1:].iloc[1:] - - pd.testing.assert_series_equal( - bf_result, - pd_result, - ) - - -@pytest.mark.parametrize( - ("start", "stop", "step"), - [ - (1, None, None), - (None, 4, None), - (None, None, 2), - (None, 50000000000, 1), - (5, 4, None), - (3, None, 2), - (1, 7, 2), - (1, 7, 50000000000), - (-1, -7, -2), - (None, -7, -2), - (-1, None, -2), - (-7, -1, 2), - (-7, -1, None), - (-7, 7, None), - (7, -7, -2), - ], -) -def test_series_iloc(scalars_df_index, scalars_pandas_df_index, start, stop, step): - bf_result = scalars_df_index["string_col"].iloc[start:stop:step].to_pandas() - pd_result = scalars_pandas_df_index["string_col"].iloc[start:stop:step] - pd.testing.assert_series_equal( - bf_result, - pd_result, - ) - - -def test_at(scalars_df_index, scalars_pandas_df_index): - scalars_df_index = scalars_df_index.set_index("int64_too", drop=False) - scalars_pandas_df_index = scalars_pandas_df_index.set_index("int64_too", drop=False) - index = -2345 - bf_result = scalars_df_index["string_col"].at[index] - pd_result = scalars_pandas_df_index["string_col"].at[index] - - assert bf_result == pd_result - - -def test_iat(scalars_df_index, scalars_pandas_df_index): - bf_result = scalars_df_index["int64_too"].iat[3] - pd_result = scalars_pandas_df_index["int64_too"].iat[3] - - assert bf_result == pd_result - - -def test_iat_error(scalars_df_index, scalars_pandas_df_index): - with pytest.raises(ValueError): - scalars_pandas_df_index["int64_too"].iat["asd"] - with pytest.raises(ValueError): - scalars_df_index["int64_too"].iat["asd"] - - -def test_series_add_prefix(scalars_df_index, scalars_pandas_df_index): - bf_result = scalars_df_index["int64_too"].add_prefix("prefix_").to_pandas() - - pd_result = scalars_pandas_df_index["int64_too"].add_prefix("prefix_") - - # Index will be object type in pandas, string type in bigframes, but same values - pd.testing.assert_series_equal( - bf_result, - pd_result, - check_index_type=False, - ) - - -def test_series_add_suffix(scalars_df_index, scalars_pandas_df_index): - bf_result = scalars_df_index["int64_too"].add_suffix("_suffix").to_pandas() - - pd_result = scalars_pandas_df_index["int64_too"].add_suffix("_suffix") - - # Index will be object type in pandas, string type in bigframes, but same values - pd.testing.assert_series_equal( - bf_result, - pd_result, - check_index_type=False, - ) - - -def test_series_filter_items(scalars_df_index, scalars_pandas_df_index): - if pd.__version__.startswith("2.0") or pd.__version__.startswith("1."): - pytest.skip("pandas filter items behavior different pre-2.1") - bf_result = scalars_df_index["float64_col"].filter(items=[5, 1, 3]).to_pandas() - - pd_result = scalars_pandas_df_index["float64_col"].filter(items=[5, 1, 3]) - - # Pandas uses int64 instead of Int64 (nullable) dtype. - pd_result.index = pd_result.index.astype(pd.Int64Dtype()) - # Ignore ordering as pandas order differently depending on version - assert_series_equal(bf_result, pd_result, check_names=False, ignore_order=True) - - -def test_series_filter_like(scalars_df_index, scalars_pandas_df_index): - scalars_df_index = scalars_df_index.copy().set_index("string_col") - scalars_pandas_df_index = scalars_pandas_df_index.copy().set_index("string_col") - - bf_result = scalars_df_index["float64_col"].filter(like="ello").to_pandas() - - pd_result = scalars_pandas_df_index["float64_col"].filter(like="ello") - - pd.testing.assert_series_equal( - bf_result, - pd_result, - ) - - -def test_series_filter_regex(scalars_df_index, scalars_pandas_df_index): - scalars_df_index = scalars_df_index.copy().set_index("string_col") - scalars_pandas_df_index = scalars_pandas_df_index.copy().set_index("string_col") - - bf_result = scalars_df_index["float64_col"].filter(regex="^[GH].*").to_pandas() - - pd_result = scalars_pandas_df_index["float64_col"].filter(regex="^[GH].*") - - pd.testing.assert_series_equal( - bf_result, - pd_result, - ) - - -def test_series_reindex(scalars_df_index, scalars_pandas_df_index): - bf_result = ( - scalars_df_index["float64_col"].reindex(index=[5, 1, 3, 99, 1]).to_pandas() - ) - - pd_result = scalars_pandas_df_index["float64_col"].reindex(index=[5, 1, 3, 99, 1]) - - # Pandas uses int64 instead of Int64 (nullable) dtype. - pd_result.index = pd_result.index.astype(pd.Int64Dtype()) - pd.testing.assert_series_equal( - bf_result, - pd_result, - ) - - -def test_series_reindex_nonunique(scalars_df_index): - with pytest.raises(ValueError): - # int64_too is non-unique - scalars_df_index.set_index("int64_too")["float64_col"].reindex( - index=[5, 1, 3, 99, 1], validate=True - ) - - -def test_series_reindex_like(scalars_df_index, scalars_pandas_df_index): - bf_reindex_target = scalars_df_index["float64_col"].reindex(index=[5, 1, 3, 99, 1]) - bf_result = ( - scalars_df_index["int64_too"].reindex_like(bf_reindex_target).to_pandas() - ) - - pd_reindex_target = scalars_pandas_df_index["float64_col"].reindex( - index=[5, 1, 3, 99, 1] - ) - pd_result = scalars_pandas_df_index["int64_too"].reindex_like(pd_reindex_target) - - # Pandas uses int64 instead of Int64 (nullable) dtype. - pd_result.index = pd_result.index.astype(pd.Int64Dtype()) - pd.testing.assert_series_equal( - bf_result, - pd_result, - ) - - -def test_where_with_series(scalars_df_index, scalars_pandas_df_index): - bf_result = ( - scalars_df_index["int64_col"] - .where(scalars_df_index["bool_col"], scalars_df_index["int64_too"]) - .to_pandas() - ) - pd_result = scalars_pandas_df_index["int64_col"].where( - scalars_pandas_df_index["bool_col"], scalars_pandas_df_index["int64_too"] - ) - - pd.testing.assert_series_equal( - bf_result, - pd_result, - ) - - -def test_where_with_different_indices(scalars_df_index, scalars_pandas_df_index): - bf_result = ( - scalars_df_index["int64_col"] - .iloc[::2] - .where( - scalars_df_index["bool_col"].iloc[2:], - scalars_df_index["int64_too"].iloc[:5], - ) - .to_pandas() - ) - pd_result = ( - scalars_pandas_df_index["int64_col"] - .iloc[::2] - .where( - scalars_pandas_df_index["bool_col"].iloc[2:], - scalars_pandas_df_index["int64_too"].iloc[:5], - ) - ) - - pd.testing.assert_series_equal( - bf_result, - pd_result, - ) - - -def test_where_with_default(scalars_df_index, scalars_pandas_df_index): - bf_result = ( - scalars_df_index["int64_col"].where(scalars_df_index["bool_col"]).to_pandas() - ) - pd_result = scalars_pandas_df_index["int64_col"].where( - scalars_pandas_df_index["bool_col"] - ) - - pd.testing.assert_series_equal( - bf_result, - pd_result, - ) - - -def test_where_with_callable(scalars_df_index, scalars_pandas_df_index): - def _is_positive(x): - return x > 0 - - # Both cond and other are callable. - bf_result = ( - scalars_df_index["int64_col"] - .where(cond=_is_positive, other=lambda x: x * 10) - .to_pandas() - ) - pd_result = scalars_pandas_df_index["int64_col"].where( - cond=_is_positive, other=lambda x: x * 10 - ) - - pd.testing.assert_series_equal( - bf_result, - pd_result, - ) - - -@pytest.mark.skip( - reason="NotImplementedError: Polars compiler hasn't implemented ClipOp()" -) -@pytest.mark.parametrize( - ("ordered"), - [ - (True), - (False), - ], -) -def test_clip(scalars_df_index, scalars_pandas_df_index, ordered): - col_bf = scalars_df_index["int64_col"] - lower_bf = scalars_df_index["int64_too"] - 1 - upper_bf = scalars_df_index["int64_too"] + 1 - bf_result = col_bf.clip(lower_bf, upper_bf).to_pandas(ordered=ordered) - - col_pd = scalars_pandas_df_index["int64_col"] - lower_pd = scalars_pandas_df_index["int64_too"] - 1 - upper_pd = scalars_pandas_df_index["int64_too"] + 1 - pd_result = col_pd.clip(lower_pd, upper_pd) - - assert_series_equal(bf_result, pd_result, ignore_order=not ordered) - - -@pytest.mark.skip( - reason="NotImplementedError: Polars compiler hasn't implemented ClipOp()" -) -def test_clip_int_with_float_bounds(scalars_df_index, scalars_pandas_df_index): - col_bf = scalars_df_index["int64_too"] - bf_result = col_bf.clip(-100, 3.14151593).to_pandas() - - col_pd = scalars_pandas_df_index["int64_too"] - # pandas doesn't work with Int64 and clip with floats - pd_result = col_pd.astype("int64").clip(-100, 3.14151593).astype("Float64") - - assert_series_equal(bf_result, pd_result) - - -@pytest.mark.skip( - reason="NotImplementedError: Polars compiler hasn't implemented ClipOp()" -) -def test_clip_filtered_two_sided(scalars_df_index, scalars_pandas_df_index): - col_bf = scalars_df_index["int64_col"].iloc[::2] - lower_bf = scalars_df_index["int64_too"].iloc[2:] - 1 - upper_bf = scalars_df_index["int64_too"].iloc[:5] + 1 - bf_result = col_bf.clip(lower_bf, upper_bf).to_pandas() - - col_pd = scalars_pandas_df_index["int64_col"].iloc[::2] - lower_pd = scalars_pandas_df_index["int64_too"].iloc[2:] - 1 - upper_pd = scalars_pandas_df_index["int64_too"].iloc[:5] + 1 - pd_result = col_pd.clip(lower_pd, upper_pd) - - pd.testing.assert_series_equal( - bf_result, - pd_result, - ) - - -@pytest.mark.skip( - reason="NotImplementedError: Polars compiler hasn't implemented maximum()" -) -def test_clip_filtered_one_sided(scalars_df_index, scalars_pandas_df_index): - col_bf = scalars_df_index["int64_col"].iloc[::2] - lower_bf = scalars_df_index["int64_too"].iloc[2:] - 1 - bf_result = col_bf.clip(lower_bf, None).to_pandas() - - col_pd = scalars_pandas_df_index["int64_col"].iloc[::2] - lower_pd = scalars_pandas_df_index["int64_too"].iloc[2:] - 1 - pd_result = col_pd.clip(lower_pd, None) - - pd.testing.assert_series_equal( - bf_result, - pd_result, - ) - - -def test_dot(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - bf_result = scalars_df["int64_too"] @ scalars_df["int64_too"] - - pd_result = scalars_pandas_df["int64_too"] @ scalars_pandas_df["int64_too"] - - assert bf_result == pd_result - - -@pytest.mark.parametrize( - ("left", "right", "inclusive"), - [ - (-234892, 55555, "left"), - (-234892, 55555, "both"), - (-234892, 55555, "neither"), - (-234892, 55555, "right"), - ], -) -def test_between(scalars_df_index, scalars_pandas_df_index, left, right, inclusive): - bf_result = ( - scalars_df_index["int64_col"].between(left, right, inclusive).to_pandas() - ) - pd_result = scalars_pandas_df_index["int64_col"].between(left, right, inclusive) - - pd.testing.assert_series_equal( - bf_result, - pd_result.astype(pd.BooleanDtype()), - ) - - -@pytest.mark.skip(reason="fixture 'scalars_dfs_maybe_ordered' not found") -def test_series_case_when(scalars_dfs_maybe_ordered): - pytest.importorskip( - "pandas", - minversion="2.2.0", - reason="case_when added in pandas 2.2.0", - ) - scalars_df, scalars_pandas_df = scalars_dfs_maybe_ordered - - bf_series = scalars_df["int64_col"] - pd_series = scalars_pandas_df["int64_col"] - - # TODO(tswast): pandas case_when appears to assume True when a value is - # null. I suspect this should be considered a bug in pandas. - - # Generate 150 conditions to test case_when with a large number of conditions - bf_conditions = ( - [((bf_series > 645).fillna(True), bf_series - 1)] - + [((bf_series > (-100 + i * 5)).fillna(True), i) for i in range(148, 0, -1)] - + [((bf_series <= -100).fillna(True), pd.NA)] - ) - - pd_conditions = ( - [((pd_series > 645), pd_series - 1)] - + [((pd_series > (-100 + i * 5)), i) for i in range(148, 0, -1)] - + [(pd_series <= -100, pd.NA)] - ) - - assert len(bf_conditions) == 150 - - bf_result = bf_series.case_when(bf_conditions).to_pandas() - pd_result = pd_series.case_when(pd_conditions) - - pd.testing.assert_series_equal( - bf_result, - pd_result.astype(pd.Int64Dtype()), - ) - - -@pytest.mark.skip(reason="fixture 'scalars_dfs_maybe_ordered' not found") -def test_series_case_when_change_type(scalars_dfs_maybe_ordered): - pytest.importorskip( - "pandas", - minversion="2.2.0", - reason="case_when added in pandas 2.2.0", - ) - scalars_df, scalars_pandas_df = scalars_dfs_maybe_ordered - - bf_series = scalars_df["int64_col"] - pd_series = scalars_pandas_df["int64_col"] - - # TODO(tswast): pandas case_when appears to assume True when a value is - # null. I suspect this should be considered a bug in pandas. - - bf_conditions = [ - ((bf_series > 645).fillna(True), scalars_df["string_col"]), - ((bf_series <= -100).fillna(True), pd.NA), - (True, "not_found"), - ] - - pd_conditions = [ - ((pd_series > 645).fillna(True), scalars_pandas_df["string_col"]), - ((pd_series <= -100).fillna(True), pd.NA), - # pandas currently fails if both the condition and the value are literals. - ([True] * len(pd_series), ["not_found"] * len(pd_series)), - ] - - bf_result = bf_series.case_when(bf_conditions).to_pandas() - pd_result = pd_series.case_when(pd_conditions) - - pd.testing.assert_series_equal( - bf_result, - pd_result.astype("string[pyarrow]"), - ) - - -def test_to_frame(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - - bf_result = scalars_df["int64_col"].to_frame().to_pandas() - pd_result = scalars_pandas_df["int64_col"].to_frame() - - assert_frame_equal(bf_result, pd_result) - - -def test_to_frame_no_name(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - - bf_result = scalars_df["int64_col"].rename(None).to_frame().to_pandas() - pd_result = scalars_pandas_df["int64_col"].rename(None).to_frame() - - assert_frame_equal(bf_result, pd_result) - - -@pytest.mark.skip(reason="fixture 'gcs_folder' not found") -def test_to_json(gcs_folder, scalars_df_index, scalars_pandas_df_index): - path = gcs_folder + "test_series_to_json*.jsonl" - scalars_df_index["int64_col"].to_json(path, lines=True, orient="records") - gcs_df = pd.read_json(get_first_file_from_wildcard(path), lines=True) - - pd.testing.assert_series_equal( - gcs_df["int64_col"].astype(pd.Int64Dtype()), - scalars_pandas_df_index["int64_col"], - check_dtype=False, - check_index=False, - ) - - -@pytest.mark.skip(reason="fixture 'gcs_folder' not found") -def test_to_csv(gcs_folder, scalars_df_index, scalars_pandas_df_index): - path = gcs_folder + "test_series_to_csv*.csv" - scalars_df_index["int64_col"].to_csv(path) - gcs_df = pd.read_csv(get_first_file_from_wildcard(path)) - - pd.testing.assert_series_equal( - gcs_df["int64_col"].astype(pd.Int64Dtype()), - scalars_pandas_df_index["int64_col"], - check_dtype=False, - check_index=False, - ) - - -def test_to_latex(scalars_df_index, scalars_pandas_df_index): - pytest.importorskip("jinja2") - bf_result = scalars_df_index["int64_col"].to_latex() - pd_result = scalars_pandas_df_index["int64_col"].to_latex() - - assert bf_result == pd_result - - -def test_series_to_json_local_str(scalars_df_index, scalars_pandas_df_index): - bf_result = scalars_df_index.int64_col.to_json() - pd_result = scalars_pandas_df_index.int64_col.to_json() - - assert bf_result == pd_result - - -def test_series_to_json_local_file(scalars_df_index, scalars_pandas_df_index): - # TODO: supply a reason why this isn't compatible with pandas 1.x - pytest.importorskip("pandas", minversion="2.0.0") - with ( - tempfile.TemporaryFile() as bf_result_file, - tempfile.TemporaryFile() as pd_result_file, - ): - scalars_df_index.int64_col.to_json(bf_result_file) - scalars_pandas_df_index.int64_col.to_json(pd_result_file) - - bf_result = bf_result_file.read() - pd_result = pd_result_file.read() - - assert bf_result == pd_result - - -def test_series_to_csv_local_str(scalars_df_index, scalars_pandas_df_index): - bf_result = scalars_df_index.int64_col.to_csv() - # default_handler for arrow types that have no default conversion - pd_result = scalars_pandas_df_index.int64_col.to_csv() - - assert bf_result == pd_result - - -def test_series_to_csv_local_file(scalars_df_index, scalars_pandas_df_index): - with ( - tempfile.TemporaryFile() as bf_result_file, - tempfile.TemporaryFile() as pd_result_file, - ): - scalars_df_index.int64_col.to_csv(bf_result_file) - scalars_pandas_df_index.int64_col.to_csv(pd_result_file) - - bf_result = bf_result_file.read() - pd_result = pd_result_file.read() - - assert bf_result == pd_result - - -def test_to_dict(scalars_df_index, scalars_pandas_df_index): - bf_result = scalars_df_index["int64_too"].to_dict() - - pd_result = scalars_pandas_df_index["int64_too"].to_dict() - - assert bf_result == pd_result - - -def test_to_excel(scalars_df_index, scalars_pandas_df_index): - pytest.importorskip("openpyxl") - bf_result_file = tempfile.TemporaryFile() - pd_result_file = tempfile.TemporaryFile() - scalars_df_index["int64_too"].to_excel(bf_result_file) - scalars_pandas_df_index["int64_too"].to_excel(pd_result_file) - bf_result = bf_result_file.read() - pd_result = bf_result_file.read() - - assert bf_result == pd_result - - -def test_to_pickle(scalars_df_index, scalars_pandas_df_index): - bf_result_file = tempfile.TemporaryFile() - pd_result_file = tempfile.TemporaryFile() - scalars_df_index["int64_too"].to_pickle(bf_result_file) - scalars_pandas_df_index["int64_too"].to_pickle(pd_result_file) - bf_result = bf_result_file.read() - pd_result = bf_result_file.read() - - assert bf_result == pd_result - - -def test_to_string(scalars_df_index, scalars_pandas_df_index): - bf_result = scalars_df_index["int64_too"].to_string() - - pd_result = scalars_pandas_df_index["int64_too"].to_string() - - assert bf_result == pd_result - - -def test_to_list(scalars_df_index, scalars_pandas_df_index): - bf_result = scalars_df_index["int64_too"].to_list() - - pd_result = scalars_pandas_df_index["int64_too"].to_list() - - assert bf_result == pd_result - - -def test_to_numpy(scalars_df_index, scalars_pandas_df_index): - bf_result = scalars_df_index["int64_too"].to_numpy() - - pd_result = scalars_pandas_df_index["int64_too"].to_numpy() - - assert (bf_result == pd_result).all() - - -def test_to_xarray(scalars_df_index, scalars_pandas_df_index): - pytest.importorskip("xarray") - bf_result = scalars_df_index["int64_too"].to_xarray() - - pd_result = scalars_pandas_df_index["int64_too"].to_xarray() - - assert bf_result.equals(pd_result) - - -def test_to_markdown(scalars_df_index, scalars_pandas_df_index): - bf_result = scalars_df_index["int64_too"].to_markdown() - - pd_result = scalars_pandas_df_index["int64_too"].to_markdown() - - assert bf_result == pd_result - - -def test_series_values(scalars_df_index, scalars_pandas_df_index): - bf_result = scalars_df_index["int64_too"].values - - pd_result = scalars_pandas_df_index["int64_too"].values - # Numpy isn't equipped to compare non-numeric objects, so convert back to dataframe - pd.testing.assert_series_equal( - pd.Series(bf_result), pd.Series(pd_result), check_dtype=False - ) - - -def test_series___array__(scalars_df_index, scalars_pandas_df_index): - bf_result = scalars_df_index["float64_col"].__array__() - - pd_result = scalars_pandas_df_index["float64_col"].__array__() - # Numpy isn't equipped to compare non-numeric objects, so convert back to dataframe - numpy.array_equal(bf_result, pd_result) - - -@pytest.mark.parametrize( - ("ascending", "na_position"), - [ - (True, "first"), - (True, "last"), - (False, "first"), - (False, "last"), - ], -) -def test_sort_values(scalars_df_index, scalars_pandas_df_index, ascending, na_position): - # Test needs values to be unique - bf_result = ( - scalars_df_index["int64_col"] - .sort_values(ascending=ascending, na_position=na_position) - .to_pandas() - ) - pd_result = scalars_pandas_df_index["int64_col"].sort_values( - ascending=ascending, na_position=na_position - ) - - pd.testing.assert_series_equal( - bf_result, - pd_result, - ) - - -def test_series_sort_values_inplace(scalars_df_index, scalars_pandas_df_index): - # Test needs values to be unique - bf_series = scalars_df_index["int64_col"].copy() - bf_series.sort_values(ascending=False, inplace=True) - bf_result = bf_series.to_pandas() - pd_result = scalars_pandas_df_index["int64_col"].sort_values(ascending=False) - - pd.testing.assert_series_equal( - bf_result, - pd_result, - ) - - -@pytest.mark.parametrize( - ("ascending"), - [ - (True,), - (False,), - ], -) -def test_sort_index(scalars_df_index, scalars_pandas_df_index, ascending): - bf_result = ( - scalars_df_index["int64_too"].sort_index(ascending=ascending).to_pandas() - ) - pd_result = scalars_pandas_df_index["int64_too"].sort_index(ascending=ascending) - - pd.testing.assert_series_equal( - bf_result, - pd_result, - ) - - -def test_series_sort_index_inplace(scalars_df_index, scalars_pandas_df_index): - bf_series = scalars_df_index["int64_too"].copy() - bf_series.sort_index(ascending=False, inplace=True) - bf_result = bf_series.to_pandas() - pd_result = scalars_pandas_df_index["int64_too"].sort_index(ascending=False) - - pd.testing.assert_series_equal( - bf_result, - pd_result, - ) - - -def test_mask_default_value(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - - bf_col = scalars_df["int64_col"] - bf_col_masked = bf_col.mask(bf_col % 2 == 1) - bf_result = bf_col.to_frame().assign(int64_col_masked=bf_col_masked).to_pandas() - - pd_col = scalars_pandas_df["int64_col"] - pd_col_masked = pd_col.mask(pd_col % 2 == 1) - pd_result = pd_col.to_frame().assign(int64_col_masked=pd_col_masked) - - assert_frame_equal(bf_result, pd_result) - - -def test_mask_custom_value(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - - bf_col = scalars_df["int64_col"] - bf_col_masked = bf_col.mask(bf_col % 2 == 1, -1) - bf_result = bf_col.to_frame().assign(int64_col_masked=bf_col_masked).to_pandas() - - pd_col = scalars_pandas_df["int64_col"] - pd_col_masked = pd_col.mask(pd_col % 2 == 1, -1) - pd_result = pd_col.to_frame().assign(int64_col_masked=pd_col_masked) - - # TODO(shobs): There is a pd.NA value in the original series, which is not - # odd so should be left as is, but it is being masked in pandas. - # Accidentally the bigframes bahavior matches, but it should be updated - # after the resolution of https://github.com/pandas-dev/pandas/issues/52955 - assert_frame_equal(bf_result, pd_result) - - -def test_mask_with_callable(scalars_df_index, scalars_pandas_df_index): - def _ten_times(x): - return x * 10 - - # Both cond and other are callable. - bf_result = ( - scalars_df_index["int64_col"] - .mask(cond=lambda x: x > 0, other=_ten_times) - .to_pandas() - ) - pd_result = scalars_pandas_df_index["int64_col"].mask( - cond=lambda x: x > 0, other=_ten_times - ) - - pd.testing.assert_series_equal( - bf_result, - pd_result, - ) - - -@pytest.mark.parametrize( - ("lambda_",), - [ - pytest.param(lambda x: x > 0), - pytest.param( - lambda x: True if x > 0 else False, - marks=pytest.mark.xfail( - raises=ValueError, - ), - ), - ], - ids=[ - "lambda_arithmatic", - "lambda_arbitrary", - ], -) -def test_mask_lambda(scalars_dfs, lambda_): - scalars_df, scalars_pandas_df = scalars_dfs - - bf_col = scalars_df["int64_col"] - bf_result = bf_col.mask(lambda_).to_pandas() - - pd_col = scalars_pandas_df["int64_col"] - pd_result = pd_col.mask(lambda_) - - # ignore dtype check, which are Int64 and object respectively - assert_series_equal(bf_result, pd_result, check_dtype=False) - - -def test_mask_simple_udf(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - - def foo(x): - return x < 1000000 - - bf_col = scalars_df["int64_col"] - bf_result = bf_col.mask(foo).to_pandas() - - pd_col = scalars_pandas_df["int64_col"] - pd_result = pd_col.mask(foo) - - # ignore dtype check, which are Int64 and object respectively - assert_series_equal(bf_result, pd_result, check_dtype=False) - - -@pytest.mark.skip( - reason="polars.exceptions.InvalidOperationError: decimal precision should be <= 38 & >= 1" -) -@pytest.mark.parametrize("errors", ["raise", "null"]) -@pytest.mark.parametrize( - ("column", "to_type"), - [ - ("int64_col", "Float64"), - ("int64_col", "Int64"), # No-op - ("int64_col", pd.Float64Dtype()), - ("int64_col", "string[pyarrow]"), - ("int64_col", "boolean"), - ("int64_col", pd.ArrowDtype(pa.decimal128(38, 9))), - ("int64_col", pd.ArrowDtype(pa.decimal256(76, 38))), - ("int64_col", pd.ArrowDtype(pa.timestamp("us"))), - ("int64_col", pd.ArrowDtype(pa.timestamp("us", tz="UTC"))), - ("int64_col", "time64[us][pyarrow]"), - ("int64_col", pd.ArrowDtype(db_dtypes.JSONArrowType())), - ("bool_col", "Int64"), - ("bool_col", "string[pyarrow]"), - ("bool_col", "Float64"), - ("bool_col", pd.ArrowDtype(db_dtypes.JSONArrowType())), - ("string_col", "binary[pyarrow]"), - ("bytes_col", "string[pyarrow]"), - # pandas actually doesn't let folks convert to/from naive timestamp and - # raises a deprecation warning to use tz_localize/tz_convert instead, - # but BigQuery always stores values as UTC and doesn't have to deal - # with timezone conversions, so we'll allow it. - ("timestamp_col", "date32[day][pyarrow]"), - ("timestamp_col", "time64[us][pyarrow]"), - ("timestamp_col", pd.ArrowDtype(pa.timestamp("us"))), - ("datetime_col", "date32[day][pyarrow]"), - pytest.param( - "datetime_col", - "string[pyarrow]", - marks=pytest.mark.skipif( - pd.__version__.startswith("2.2"), - reason="pandas 2.2 uses T as date/time separator whereas earlier versions use space", - ), - ), - ("datetime_col", "time64[us][pyarrow]"), - ("datetime_col", pd.ArrowDtype(pa.timestamp("us", tz="UTC"))), - ("date_col", "string[pyarrow]"), - ("date_col", pd.ArrowDtype(pa.timestamp("us"))), - ("date_col", pd.ArrowDtype(pa.timestamp("us", tz="UTC"))), - ("time_col", "string[pyarrow]"), - # TODO(bmil): fix Ibis bug: BigQuery backend rounds to nearest int - # ("float64_col", "Int64"), - # TODO(bmil): decide whether to fix Ibis bug: BigQuery backend - # formats floats with no decimal places if they have no fractional - # part, and does not switch to scientific notation for > 10^15 - # ("float64_col", "string[pyarrow]") - # TODO(bmil): add any other compatible conversions per - # https://cloud.google.com/bigquery/docs/reference/standard-sql/conversion_functions - ], -) -def test_astype(scalars_df_index, scalars_pandas_df_index, column, to_type, errors): - # TODO: supply a reason why this isn't compatible with pandas 1.x - pytest.importorskip("pandas", minversion="2.0.0") - bf_result = scalars_df_index[column].astype(to_type, errors=errors).to_pandas() - pd_result = scalars_pandas_df_index[column].astype(to_type) - pd.testing.assert_series_equal(bf_result, pd_result) - - -@pytest.mark.skip( - reason="AttributeError: 'DataFrame' object has no attribute 'dtype'. Did you mean: 'dtypes'?" -) -def test_series_astype_python(session): - input = pd.Series(["hello", "world", "3.11", "4000"]) - exepcted = pd.Series( - [None, None, 3.11, 4000], - dtype="Float64", - index=pd.Index([0, 1, 2, 3], dtype="Int64"), - ) - result = session.read_pandas(input).astype(float, errors="null").to_pandas() - pd.testing.assert_series_equal(result, exepcted) - - -@pytest.mark.skip( - reason="AttributeError: 'DataFrame' object has no attribute 'dtype'. Did you mean: 'dtypes'?" -) -def test_astype_safe(session): - input = pd.Series(["hello", "world", "3.11", "4000"]) - exepcted = pd.Series( - [None, None, 3.11, 4000], - dtype="Float64", - index=pd.Index([0, 1, 2, 3], dtype="Int64"), - ) - result = session.read_pandas(input).astype("Float64", errors="null").to_pandas() - pd.testing.assert_series_equal(result, exepcted) - - -def test_series_astype_w_invalid_error(session): - input = pd.Series(["hello", "world", "3.11", "4000"]) - with pytest.raises(ValueError): - session.read_pandas(input).astype("Float64", errors="bad_value") - - -@pytest.mark.parametrize( - ("column", "to_type"), - [ - ("timestamp_col", "int64[pyarrow]"), - ("datetime_col", "int64[pyarrow]"), - ("time_col", "int64[pyarrow]"), - ], -) -def test_date_time_astype_int( - scalars_df_index, scalars_pandas_df_index, column, to_type -): - # TODO: supply a reason why this isn't compatible with pandas 1.x - pytest.importorskip("pandas", minversion="2.0.0") - bf_result = scalars_df_index[column].astype(to_type).to_pandas() - pd_result = scalars_pandas_df_index[column].astype(to_type) - pd.testing.assert_series_equal(bf_result, pd_result, check_dtype=False) - assert bf_result.dtype == "Int64" - - -@pytest.mark.skip( - reason="polars.exceptions.InvalidOperationError: conversion from `str` to `i64` failed in column 'column_0' for 1 out of 4 values: [' -03']" -) -def test_string_astype_int(): - pd_series = pd.Series(["4", "-7", "0", " -03"]) - bf_series = series.Series(pd_series) - - pd_result = pd_series.astype("Int64") - bf_result = bf_series.astype("Int64").to_pandas() - - pd.testing.assert_series_equal(bf_result, pd_result, check_index_type=False) - - -@pytest.mark.skip( - reason="polars.exceptions.InvalidOperationError: conversion from `str` to `f64` failed in column 'column_0' for 1 out of 10 values: [' -03.235']" -) -def test_string_astype_float(): - pd_series = pd.Series( - ["1", "-1", "-0", "000", " -03.235", "naN", "-inf", "INf", ".33", "7.235e-8"] - ) - - bf_series = series.Series(pd_series) - - pd_result = pd_series.astype("Float64") - bf_result = bf_series.astype("Float64").to_pandas() - - pd.testing.assert_series_equal(bf_result, pd_result, check_index_type=False) - - -def test_string_astype_date(): - if int(pa.__version__.split(".")[0]) < 15: - pytest.skip( - "Avoid pyarrow.lib.ArrowNotImplementedError: " - "Unsupported cast from string to date32 using function cast_date32." - ) - - pd_series = pd.Series(["2014-08-15", "2215-08-15", "2016-02-29"]).astype( - pd.ArrowDtype(pa.string()) - ) - - bf_series = series.Series(pd_series) - - # TODO(b/340885567): fix type error - pd_result = pd_series.astype("date32[day][pyarrow]") # type: ignore - bf_result = bf_series.astype("date32[day][pyarrow]").to_pandas() - - assert_series_equal(bf_result, pd_result, check_index_type=False) - - -def test_string_astype_datetime(): - pd_series = pd.Series( - ["2014-08-15 08:15:12", "2015-08-15 08:15:12.654754", "2016-02-29 00:00:00"] - ).astype(pd.ArrowDtype(pa.string())) - - bf_series = series.Series(pd_series) - - pd_result = pd_series.astype(pd.ArrowDtype(pa.timestamp("us"))) - bf_result = bf_series.astype(pd.ArrowDtype(pa.timestamp("us"))).to_pandas() - - assert_series_equal(bf_result, pd_result, check_index_type=False) - - -def test_string_astype_timestamp(): - pd_series = pd.Series( - [ - "2014-08-15 08:15:12+00:00", - "2015-08-15 08:15:12.654754+05:00", - "2016-02-29 00:00:00+08:00", - ] - ).astype(pd.ArrowDtype(pa.string())) - - bf_series = series.Series(pd_series) - - pd_result = pd_series.astype(pd.ArrowDtype(pa.timestamp("us", tz="UTC"))) - bf_result = bf_series.astype( - pd.ArrowDtype(pa.timestamp("us", tz="UTC")) - ).to_pandas() - - assert_series_equal(bf_result, pd_result, check_index_type=False) - - -@pytest.mark.skip(reason="AssertionError: Series are different") -def test_timestamp_astype_string(): - bf_series = series.Series( - [ - "2014-08-15 08:15:12+00:00", - "2015-08-15 08:15:12.654754+05:00", - "2016-02-29 00:00:00+08:00", - ] - ).astype(pd.ArrowDtype(pa.timestamp("us", tz="UTC"))) - - expected_result = pd.Series( - [ - "2014-08-15 08:15:12+00", - "2015-08-15 03:15:12.654754+00", - "2016-02-28 16:00:00+00", - ] - ) - bf_result = bf_series.astype(pa.string()).to_pandas() - - pd.testing.assert_series_equal( - bf_result, expected_result, check_index_type=False, check_dtype=False - ) - assert bf_result.dtype == "string[pyarrow]" - - -@pytest.mark.skip(reason="AssertionError: Series are different") -@pytest.mark.parametrize("errors", ["raise", "null"]) -def test_float_astype_json(errors): - data = ["1.25", "2500000000", None, "-12323.24"] - bf_series = series.Series(data, dtype=dtypes.FLOAT_DTYPE) - - bf_result = bf_series.astype(dtypes.JSON_DTYPE, errors=errors) - assert bf_result.dtype == dtypes.JSON_DTYPE - - expected_result = pd.Series(data, dtype=dtypes.JSON_DTYPE) - expected_result.index = expected_result.index.astype("Int64") - pd.testing.assert_series_equal(bf_result.to_pandas(), expected_result) - - -@pytest.mark.skip(reason="AssertionError: Series are different") -def test_float_astype_json_str(): - data = ["1.25", "2500000000", None, "-12323.24"] - bf_series = series.Series(data, dtype=dtypes.FLOAT_DTYPE) - - bf_result = bf_series.astype("json") - assert bf_result.dtype == dtypes.JSON_DTYPE - - expected_result = pd.Series(data, dtype=dtypes.JSON_DTYPE) - expected_result.index = expected_result.index.astype("Int64") - pd.testing.assert_series_equal(bf_result.to_pandas(), expected_result) - - -@pytest.mark.parametrize("errors", ["raise", "null"]) -def test_string_astype_json(errors): - data = [ - "1", - None, - '["1","3","5"]', - '{"a":1,"b":["x","y"],"c":{"x":[],"z":false}}', - ] - bf_series = series.Series(data, dtype=dtypes.STRING_DTYPE) - - bf_result = bf_series.astype(dtypes.JSON_DTYPE, errors=errors) - assert bf_result.dtype == dtypes.JSON_DTYPE - - pd_result = bf_series.to_pandas().astype(dtypes.JSON_DTYPE) - pd.testing.assert_series_equal(bf_result.to_pandas(), pd_result) - - -@pytest.mark.skip(reason="AssertionError: Series NA mask are different") -def test_string_astype_json_in_safe_mode(): - data = ["this is not a valid json string"] - bf_series = series.Series(data, dtype=dtypes.STRING_DTYPE) - bf_result = bf_series.astype(dtypes.JSON_DTYPE, errors="null") - assert bf_result.dtype == dtypes.JSON_DTYPE - - expected = pd.Series([None], dtype=dtypes.JSON_DTYPE) - expected.index = expected.index.astype("Int64") - pd.testing.assert_series_equal(bf_result.to_pandas(), expected) - - -@pytest.mark.skip( - reason="Failed: DID NOT RAISE " -) -def test_string_astype_json_raise_error(): - data = ["this is not a valid json string"] - bf_series = series.Series(data, dtype=dtypes.STRING_DTYPE) - with pytest.raises( - google.api_core.exceptions.BadRequest, - match="syntax error while parsing value", - ): - bf_series.astype(dtypes.JSON_DTYPE, errors="raise").to_pandas() - - -@pytest.mark.parametrize("errors", ["raise", "null"]) -@pytest.mark.parametrize( - ("data", "to_type"), - [ - pytest.param(["1", "10.0", None], dtypes.INT_DTYPE, id="to_int"), - pytest.param(["0.0001", "2500000000", None], dtypes.FLOAT_DTYPE, id="to_float"), - pytest.param(["true", "false", None], dtypes.BOOL_DTYPE, id="to_bool"), - pytest.param(['"str"', None], dtypes.STRING_DTYPE, id="to_string"), - pytest.param( - ['"str"', None], - dtypes.TIME_DTYPE, - id="invalid", - marks=pytest.mark.xfail(raises=TypeError), - ), - ], -) -def test_json_astype_others(data, to_type, errors): - bf_series = series.Series(data, dtype=dtypes.JSON_DTYPE) - - bf_result = bf_series.astype(to_type, errors=errors) - assert bf_result.dtype == to_type - - load_data = [json.loads(item) if item is not None else None for item in data] - expected = pd.Series(load_data, dtype=to_type) - expected.index = expected.index.astype("Int64") - pd.testing.assert_series_equal(bf_result.to_pandas(), expected) - - -@pytest.mark.skip( - reason="Failed: DID NOT RAISE " -) -@pytest.mark.parametrize( - ("data", "to_type"), - [ - pytest.param(["10.2", None], dtypes.INT_DTYPE, id="to_int"), - pytest.param(["false", None], dtypes.FLOAT_DTYPE, id="to_float"), - pytest.param(["10.2", None], dtypes.BOOL_DTYPE, id="to_bool"), - pytest.param(["true", None], dtypes.STRING_DTYPE, id="to_string"), - ], -) -def test_json_astype_others_raise_error(data, to_type): - bf_series = series.Series(data, dtype=dtypes.JSON_DTYPE) - with pytest.raises(google.api_core.exceptions.BadRequest): - bf_series.astype(to_type, errors="raise").to_pandas() - - -@pytest.mark.skip(reason="AssertionError: Series NA mask are different") -@pytest.mark.parametrize( - ("data", "to_type"), - [ - pytest.param(["10.2", None], dtypes.INT_DTYPE, id="to_int"), - pytest.param(["false", None], dtypes.FLOAT_DTYPE, id="to_float"), - pytest.param(["10.2", None], dtypes.BOOL_DTYPE, id="to_bool"), - pytest.param(["true", None], dtypes.STRING_DTYPE, id="to_string"), - ], -) -def test_json_astype_others_in_safe_mode(data, to_type): - bf_series = series.Series(data, dtype=dtypes.JSON_DTYPE) - bf_result = bf_series.astype(to_type, errors="null") - assert bf_result.dtype == to_type - - expected = pd.Series([None, None], dtype=to_type) - expected.index = expected.index.astype("Int64") - pd.testing.assert_series_equal(bf_result.to_pandas(), expected) - - -@pytest.mark.parametrize( - "index", - [0, 5, -2], -) -def test_iloc_single_integer(scalars_df_index, scalars_pandas_df_index, index): - bf_result = scalars_df_index.string_col.iloc[index] - pd_result = scalars_pandas_df_index.string_col.iloc[index] - - assert bf_result == pd_result - - -def test_iloc_single_integer_out_of_bound_error(scalars_df_index): - with pytest.raises(IndexError, match="single positional indexer is out-of-bounds"): - scalars_df_index.string_col.iloc[99] - - -def test_loc_bool_series_explicit_index(scalars_df_index, scalars_pandas_df_index): - bf_result = scalars_df_index.string_col.loc[scalars_df_index.bool_col].to_pandas() - pd_result = scalars_pandas_df_index.string_col.loc[scalars_pandas_df_index.bool_col] - - pd.testing.assert_series_equal( - bf_result, - pd_result, - ) - - -@pytest.mark.skip(reason="fixture 'scalars_pandas_df_default_index' not found") -def test_loc_bool_series_default_index( - scalars_df_default_index, scalars_pandas_df_default_index -): - bf_result = scalars_df_default_index.string_col.loc[ - scalars_df_default_index.bool_col - ].to_pandas() - pd_result = scalars_pandas_df_default_index.string_col.loc[ - scalars_pandas_df_default_index.bool_col - ] - - assert_frame_equal( - bf_result.to_frame(), - pd_result.to_frame(), - ) - - -def test_argmin(scalars_df_index, scalars_pandas_df_index): - bf_result = scalars_df_index.string_col.argmin() - pd_result = scalars_pandas_df_index.string_col.argmin() - assert bf_result == pd_result - - -def test_argmax(scalars_df_index, scalars_pandas_df_index): - bf_result = scalars_df_index.int64_too.argmax() - pd_result = scalars_pandas_df_index.int64_too.argmax() - assert bf_result == pd_result - - -def test_series_idxmin(scalars_df_index, scalars_pandas_df_index): - bf_result = scalars_df_index.string_col.idxmin() - pd_result = scalars_pandas_df_index.string_col.idxmin() - assert bf_result == pd_result - - -def test_series_idxmax(scalars_df_index, scalars_pandas_df_index): - bf_result = scalars_df_index.int64_too.idxmax() - pd_result = scalars_pandas_df_index.int64_too.idxmax() - assert bf_result == pd_result - - -def test_getattr_attribute_error_when_pandas_has(scalars_df_index): - # asof is implemented in pandas but not in bigframes - with pytest.raises(AttributeError): - scalars_df_index.string_col.asof() - - -def test_getattr_attribute_error(scalars_df_index): - with pytest.raises(AttributeError): - scalars_df_index.string_col.not_a_method() - - -def test_rename(scalars_df_index, scalars_pandas_df_index): - bf_result = scalars_df_index.string_col.rename("newname") - pd_result = scalars_pandas_df_index.string_col.rename("newname") - - pd.testing.assert_series_equal( - bf_result.to_pandas(), - pd_result, - ) - - -def test_rename_nonstring(scalars_df_index, scalars_pandas_df_index): - bf_result = scalars_df_index.string_col.rename((4, 2)) - pd_result = scalars_pandas_df_index.string_col.rename((4, 2)) - - pd.testing.assert_series_equal( - bf_result.to_pandas(), - pd_result, - ) - - -def test_rename_dict_same_type(scalars_df_index, scalars_pandas_df_index): - bf_result = scalars_df_index.string_col.rename({1: 100, 2: 200}) - pd_result = scalars_pandas_df_index.string_col.rename({1: 100, 2: 200}) - - pd_result.index = pd_result.index.astype("Int64") - - pd.testing.assert_series_equal( - bf_result.to_pandas(), - pd_result, - ) - - -def test_rename_axis(scalars_df_index, scalars_pandas_df_index): - bf_result = scalars_df_index.string_col.rename_axis("newindexname") - pd_result = scalars_pandas_df_index.string_col.rename_axis("newindexname") - - pd.testing.assert_series_equal( - bf_result.to_pandas(), - pd_result, - ) - - -def test_loc_list_string_index(scalars_df_index, scalars_pandas_df_index): - index_list = scalars_pandas_df_index.string_col.iloc[[0, 1, 1, 5]].values - - scalars_df_index = scalars_df_index.set_index("string_col", drop=False) - scalars_pandas_df_index = scalars_pandas_df_index.set_index( - "string_col", drop=False - ) - - bf_result = scalars_df_index.string_col.loc[index_list] - pd_result = scalars_pandas_df_index.string_col.loc[index_list] - - pd.testing.assert_series_equal( - bf_result.to_pandas(), - pd_result, - ) - - -def test_loc_list_integer_index(scalars_df_index, scalars_pandas_df_index): - index_list = [3, 2, 1, 3, 2, 1] - - bf_result = scalars_df_index.bool_col.loc[index_list] - pd_result = scalars_pandas_df_index.bool_col.loc[index_list] - - pd.testing.assert_series_equal( - bf_result.to_pandas(), - pd_result, - ) - - -def test_loc_list_multiindex(scalars_df_index, scalars_pandas_df_index): - scalars_df_multiindex = scalars_df_index.set_index(["string_col", "int64_col"]) - scalars_pandas_df_multiindex = scalars_pandas_df_index.set_index( - ["string_col", "int64_col"] - ) - index_list = [("Hello, World!", -234892), ("Hello, World!", 123456789)] - - bf_result = scalars_df_multiindex.int64_too.loc[index_list] - pd_result = scalars_pandas_df_multiindex.int64_too.loc[index_list] - - pd.testing.assert_series_equal( - bf_result.to_pandas(), - pd_result, - ) - - -def test_iloc_list(scalars_df_index, scalars_pandas_df_index): - index_list = [0, 0, 0, 5, 4, 7] - - bf_result = scalars_df_index.string_col.iloc[index_list] - pd_result = scalars_pandas_df_index.string_col.iloc[index_list] - - pd.testing.assert_series_equal( - bf_result.to_pandas(), - pd_result, - ) - - -def test_iloc_list_nameless(scalars_df_index, scalars_pandas_df_index): - index_list = [0, 0, 0, 5, 4, 7] - - bf_series = scalars_df_index.string_col.rename(None) - bf_result = bf_series.iloc[index_list] - pd_series = scalars_pandas_df_index.string_col.rename(None) - pd_result = pd_series.iloc[index_list] - - pd.testing.assert_series_equal( - bf_result.to_pandas(), - pd_result, - ) - - -def test_loc_list_nameless(scalars_df_index, scalars_pandas_df_index): - index_list = [0, 0, 0, 5, 4, 7] - - bf_series = scalars_df_index.string_col.rename(None) - bf_result = bf_series.loc[index_list] - - pd_series = scalars_pandas_df_index.string_col.rename(None) - pd_result = pd_series.loc[index_list] - - pd.testing.assert_series_equal( - bf_result.to_pandas(), - pd_result, - ) - - -def test_loc_bf_series_string_index(scalars_df_index, scalars_pandas_df_index): - pd_string_series = scalars_pandas_df_index.string_col.iloc[[0, 5, 1, 1, 5]] - bf_string_series = scalars_df_index.string_col.iloc[[0, 5, 1, 1, 5]] - - scalars_df_index = scalars_df_index.set_index("string_col") - scalars_pandas_df_index = scalars_pandas_df_index.set_index("string_col") - - bf_result = scalars_df_index.date_col.loc[bf_string_series] - pd_result = scalars_pandas_df_index.date_col.loc[pd_string_series] - - pd.testing.assert_series_equal( - bf_result.to_pandas(), - pd_result, - ) - - -def test_loc_bf_series_multiindex(scalars_df_index, scalars_pandas_df_index): - pd_string_series = scalars_pandas_df_index.string_col.iloc[[0, 5, 1, 1, 5]] - bf_string_series = scalars_df_index.string_col.iloc[[0, 5, 1, 1, 5]] - - scalars_df_multiindex = scalars_df_index.set_index(["string_col", "int64_col"]) - scalars_pandas_df_multiindex = scalars_pandas_df_index.set_index( - ["string_col", "int64_col"] - ) - - bf_result = scalars_df_multiindex.int64_too.loc[bf_string_series] - pd_result = scalars_pandas_df_multiindex.int64_too.loc[pd_string_series] - - pd.testing.assert_series_equal( - bf_result.to_pandas(), - pd_result, - ) - - -def test_loc_bf_index_integer_index(scalars_df_index, scalars_pandas_df_index): - pd_index = scalars_pandas_df_index.iloc[[0, 5, 1, 1, 5]].index - bf_index = scalars_df_index.iloc[[0, 5, 1, 1, 5]].index - - bf_result = scalars_df_index.date_col.loc[bf_index] - pd_result = scalars_pandas_df_index.date_col.loc[pd_index] - - pd.testing.assert_series_equal( - bf_result.to_pandas(), - pd_result, - ) - - -def test_loc_single_index_with_duplicate(scalars_df_index, scalars_pandas_df_index): - scalars_df_index = scalars_df_index.set_index("string_col", drop=False) - scalars_pandas_df_index = scalars_pandas_df_index.set_index( - "string_col", drop=False - ) - index = "Hello, World!" - bf_result = scalars_df_index.date_col.loc[index] - pd_result = scalars_pandas_df_index.date_col.loc[index] - pd.testing.assert_series_equal( - bf_result.to_pandas(), - pd_result, - ) - - -def test_loc_single_index_no_duplicate(scalars_df_index, scalars_pandas_df_index): - scalars_df_index = scalars_df_index.set_index("int64_too", drop=False) - scalars_pandas_df_index = scalars_pandas_df_index.set_index("int64_too", drop=False) - index = -2345 - bf_result = scalars_df_index.date_col.loc[index] - pd_result = scalars_pandas_df_index.date_col.loc[index] - assert bf_result == pd_result - - -def test_series_bool_interpretation_error(scalars_df_index): - with pytest.raises(ValueError): - True if scalars_df_index["string_col"] else False - - -@pytest.mark.skip( - reason="NotImplementedError: dry_run not implemented for this executor" -) -def test_query_job_setters(scalars_dfs): - # if allow_large_results=False, might not create query job - with bigframes.option_context("compute.allow_large_results", True): - job_ids = set() - df, _ = scalars_dfs - series = df["int64_col"] - assert series.query_job is not None - repr(series) - job_ids.add(series.query_job.job_id) - series.to_pandas() - job_ids.add(series.query_job.job_id) - assert len(job_ids) == 2 - - -@pytest.mark.parametrize( - ("series_input",), - [ - ([1, 2, 3, 4, 5],), - ([1, 1, 3, 5, 5],), - ([1, pd.NA, 4, 5, 5],), - ([1, 3, 2, 5, 4],), - ([pd.NA, pd.NA],), - ([1, 1, 1, 1, 1],), - ], -) -def test_is_monotonic_increasing(series_input): - scalars_df = series.Series(series_input, dtype=pd.Int64Dtype()) - scalars_pandas_df = pd.Series(series_input, dtype=pd.Int64Dtype()) - assert ( - scalars_df.is_monotonic_increasing == scalars_pandas_df.is_monotonic_increasing - ) - - -@pytest.mark.parametrize( - ("series_input",), - [ - ([1],), - ([5, 4, 3, 2, 1],), - ([5, 5, 3, 1, 1],), - ([1, pd.NA, 4, 5, 5],), - ([5, pd.NA, 4, 2, 1],), - ([1, 1, 1, 1, 1],), - ], -) -def test_is_monotonic_decreasing(series_input): - scalars_df = series.Series(series_input) - scalars_pandas_df = pd.Series(series_input) - assert ( - scalars_df.is_monotonic_decreasing == scalars_pandas_df.is_monotonic_decreasing - ) - - -def test_map_dict_input(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - - local_map = dict() - # construct a local map, incomplete to cover behavior - for s in scalars_pandas_df.string_col[:-3]: - if isinstance(s, str): - local_map[s] = ord(s[0]) - - pd_result = scalars_pandas_df.string_col.map(local_map) - pd_result = pd_result.astype("Int64") # pandas type differences - bf_result = scalars_df.string_col.map(local_map) - - pd.testing.assert_series_equal( - bf_result.to_pandas(), - pd_result, - ) - - -def test_map_series_input(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - - new_index = scalars_pandas_df.int64_too.drop_duplicates() - pd_map_series = scalars_pandas_df.string_col.iloc[0 : len(new_index)] - pd_map_series.index = new_index - bf_map_series = series.Series( - pd_map_series, session=scalars_df._get_block().expr.session - ) - - pd_result = scalars_pandas_df.int64_too.map(pd_map_series) - bf_result = scalars_df.int64_too.map(bf_map_series) - - pd.testing.assert_series_equal( - bf_result.to_pandas(), - pd_result, - ) - - -def test_map_series_input_duplicates_error(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - - new_index = scalars_pandas_df.int64_too - pd_map_series = scalars_pandas_df.string_col.iloc[0 : len(new_index)] - pd_map_series.index = new_index - bf_map_series = series.Series( - pd_map_series, session=scalars_df._get_block().expr.session - ) - - with pytest.raises(pd.errors.InvalidIndexError): - scalars_pandas_df.int64_too.map(pd_map_series) - with pytest.raises(pd.errors.InvalidIndexError): - scalars_df.int64_too.map(bf_map_series, verify_integrity=True) - - -def test_series_map_with_udf(session): - series = bpd.Series([1, 2, None, 4], dtype="Int64") - - @session.udf(input_types=[int], output_type=int) - def foo(x): - if x is None: - return -1 - return x * 2 - - bf_result = series.map(foo).to_pandas() - pd_result = pd.Series([2, 4, -1, 8]) - assert_series_equal(bf_result, pd_result, check_dtype=False) - - -@pytest.mark.skip( - reason="NotImplementedError: Polars compiler hasn't implemented hash()" -) -@pytest.mark.parametrize( - ("frac", "n", "random_state"), - [ - (None, 4, None), - (0.5, None, None), - (None, 4, 10), - (0.5, None, 10), - (None, None, None), - ], - ids=[ - "n_wo_random_state", - "frac_wo_random_state", - "n_w_random_state", - "frac_w_random_state", - "n_default", - ], -) -def test_sample(scalars_dfs, frac, n, random_state): - scalars_df, _ = scalars_dfs - df = scalars_df.int64_col.sample(frac=frac, n=n, random_state=random_state) - bf_result = df.to_pandas() - - n = 1 if n is None else n - expected_sample_size = round(frac * scalars_df.shape[0]) if frac is not None else n - assert bf_result.shape[0] == expected_sample_size - - -def test_series_iter( - scalars_df_index, - scalars_pandas_df_index, -): - for bf_i, pd_i in zip( - scalars_df_index["int64_too"], scalars_pandas_df_index["int64_too"] - ): - assert bf_i == pd_i - - -@pytest.mark.parametrize( - ( - "col", - "lambda_", - ), - [ - pytest.param("int64_col", lambda x: x * x + x + 1), - pytest.param("int64_col", lambda x: x % 2 == 1), - pytest.param("string_col", lambda x: x + "_suffix"), - ], - ids=[ - "lambda_int_int", - "lambda_int_bool", - "lambda_str_str", - ], -) -def test_apply_lambda(scalars_dfs, col, lambda_): - scalars_df, scalars_pandas_df = scalars_dfs - - bf_col = scalars_df[col] - - # Can't be applied to BigFrames Series without by_row=False - with pytest.raises(ValueError, match="by_row=False"): - bf_col.apply(lambda_) - - bf_result = bf_col.apply(lambda_, by_row=False).to_pandas() - - pd_col = scalars_pandas_df[col] - if pd.__version__[:3] in ("2.2", "2.3") or pandas_major_version() >= 3: - pd_result = pd_col.apply(lambda_, by_row=False) - else: - pd_result = pd_col.apply(lambda_) - - # ignore dtype check, which are Int64 and object respectively - # Some columns implicitly convert to floating point. Use check_exact=False to ensure we're "close enough" - assert_series_equal( - bf_result, - pd_result, - check_dtype=False, - check_exact=False, - rtol=0.001, - nulls_are_nan=True, - ) - - -@pytest.mark.parametrize( - ("ufunc",), - [ - pytest.param(numpy.cos, id="cos"), - pytest.param(numpy.log, id="log"), - pytest.param(numpy.log10, id="log10"), - pytest.param(numpy.log1p, id="log1p"), - pytest.param(numpy.sqrt, id="sqrt"), - pytest.param(numpy.sin, id="sin"), - ], -) -def test_apply_numpy_ufunc(scalars_dfs, ufunc): - scalars_df, scalars_pandas_df = scalars_dfs - - bf_col = scalars_df["int64_col"] - - # Can't be applied to BigFrames Series without by_row=False - with pytest.raises(ValueError, match="by_row=False"): - bf_col.apply(ufunc) - - bf_result = bf_col.apply(ufunc, by_row=False).to_pandas() - - pd_col = scalars_pandas_df["int64_col"] - pd_result = pd_col.apply(ufunc) - - assert_series_equal(bf_result, pd_result) - - -@pytest.mark.parametrize( - ("ufunc",), - [ - pytest.param(math.log), - pytest.param(math.log10), - pytest.param(math.sin), - pytest.param(math.cos), - pytest.param(math.tan), - pytest.param(math.sinh), - pytest.param(math.cosh), - pytest.param(math.tanh), - pytest.param(math.asin), - pytest.param(math.acos), - pytest.param(math.atan), - pytest.param(abs), - ], -) -@pytest.mark.parametrize( - ("col",), - [pytest.param("float64_col"), pytest.param("int64_col")], -) -def test_series_apply_python_numeric_fns(scalars_dfs, ufunc, col): - scalars_df, scalars_pandas_df = scalars_dfs - - bf_col = scalars_df[col] - bf_result = bf_col.apply(ufunc).to_pandas() - - pd_col = scalars_pandas_df[col] - - def wrapped(x): - try: - return ufunc(x) - except ValueError: - return pd.NA - except OverflowError: - if ufunc == math.sinh and x < 0: - return float("-inf") - return float("inf") - - pd_result = pd_col.apply(wrapped) - - assert_series_equal(bf_result, pd_result, check_dtype=False, nulls_are_nan=True) - - -@pytest.mark.parametrize( - ("ufunc",), - [ - pytest.param(str.upper), - pytest.param(str.lower), - pytest.param(len), - ], -) -def test_series_apply_python_string_fns(scalars_dfs, ufunc): - scalars_df, scalars_pandas_df = scalars_dfs - - bf_col = scalars_df["string_col"] - bf_result = bf_col.apply(ufunc).to_pandas() - - pd_col = scalars_pandas_df["string_col"] - - def wrapped(x): - return ufunc(x) if isinstance(x, str) else None - - pd_result = pd_col.apply(wrapped) - - assert_series_equal(bf_result, pd_result, check_dtype=False) - - -@pytest.mark.parametrize( - ("ufunc",), - [ - pytest.param(numpy.add), - pytest.param(numpy.divide), - ], - ids=[ - "add", - "divide", - ], -) -def test_combine_series_ufunc(scalars_dfs, ufunc): - scalars_df, scalars_pandas_df = scalars_dfs - - bf_col = scalars_df["int64_col"].dropna() - bf_result = bf_col.combine(bf_col, ufunc).to_pandas() - - pd_col = scalars_pandas_df["int64_col"].dropna() - pd_result = pd_col.combine(pd_col, ufunc) - - assert_series_equal(bf_result, pd_result, check_dtype=False) - - -@pytest.mark.parametrize( - ("func",), - [ - pytest.param(operator.add), - pytest.param(operator.truediv), - ], - ids=[ - "add", - "divide", - ], -) -def test_combine_series_pyfunc(scalars_dfs, func): - scalars_df, scalars_pandas_df = scalars_dfs - - bf_col = scalars_df["int64_col"].dropna() - bf_result = bf_col.combine(bf_col, func).to_pandas() - - pd_col = scalars_pandas_df["int64_col"].dropna() - pd_result = pd_col.combine(pd_col, func) - - assert_series_equal(bf_result, pd_result, check_dtype=False) - - -def test_combine_scalar_ufunc(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - - bf_col = scalars_df["int64_col"].dropna() - bf_result = bf_col.combine(2.5, numpy.add).to_pandas() - - pd_col = scalars_pandas_df["int64_col"].dropna() - pd_result = pd_col.combine(2.5, numpy.add) - - assert_series_equal(bf_result, pd_result, check_dtype=False) - - -def test_apply_simple_udf(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - - def foo(x): - return x * x + 2 * x + 3 - - bf_col = scalars_df["int64_col"] - - # Can't be applied to BigFrames Series without by_row=False - with pytest.raises(ValueError, match="by_row=False"): - bf_col.apply(foo) - - bf_result = bf_col.apply(foo, by_row=False).to_pandas() - - pd_col = scalars_pandas_df["int64_col"] - - if pd.__version__[:3] in ("2.2", "2.3"): - pd_result = pd_col.apply(foo, by_row=False) - else: - pd_result = pd_col.apply(foo) - - # ignore dtype check, which are Int64 and object respectively - # Some columns implicitly convert to floating point. Use check_exact=False to ensure we're "close enough" - assert_series_equal( - bf_result, - pd_result, - check_dtype=False, - check_exact=False, - rtol=0.001, - nulls_are_nan=True, - ) - - -@pytest.mark.parametrize( - ("col", "lambda_", "exception"), - [ - pytest.param("int64_col", {1: 2, 3: 4}, ValueError), - pytest.param("int64_col", numpy.square, TypeError), - pytest.param("string_col", lambda x: x.capitalize(), AttributeError), - ], - ids=[ - "not_callable", - "numpy_ufunc", - "custom_lambda", - ], -) -def test_apply_not_supported(scalars_dfs, col, lambda_, exception): - scalars_df, _ = scalars_dfs - - bf_col = scalars_df[col] - with pytest.raises(exception): - bf_col.apply(lambda_, by_row=False) - - -def test_series_pipe( - scalars_df_index, - scalars_pandas_df_index, -): - column = "int64_too" - - def foo(x: int, y: int, df): - return (df + x) % y - - bf_result = ( - scalars_df_index[column] - .pipe((foo, "df"), x=7, y=9) - .pipe(lambda x: x**2) - .to_pandas() - ) - - pd_result = ( - scalars_pandas_df_index[column].pipe((foo, "df"), x=7, y=9).pipe(lambda x: x**2) - ) - - assert_series_equal(bf_result, pd_result) - - -@pytest.mark.parametrize( - ("data"), - [ - pytest.param([1, 2, 3], id="int"), - pytest.param([[1, 2, 3], [], numpy.nan, [3, 4]], id="int_array"), - pytest.param( - [["A", "AA", "AAA"], ["BB", "B"], numpy.nan, [], ["C"]], id="string_array" - ), - pytest.param( - [ - {"A": {"x": 1.0}, "B": "b"}, - {"A": {"y": 2.0}, "B": "bb"}, - {"A": {"z": 4.0}}, - {}, - numpy.nan, - ], - id="struct_array", - ), - ], -) -def test_series_explode(data): - s = bigframes.pandas.Series(data) - pd_s = s.to_pandas() - pd.testing.assert_series_equal( - s.explode().to_pandas(), - pd_s.explode(), - check_index_type=False, - check_dtype=False, - ) - - -@pytest.mark.parametrize( - ("index", "ignore_index"), - [ - pytest.param(None, True, id="default_index"), - pytest.param(None, False, id="ignore_default_index"), - pytest.param([5, 1, 3, 2], True, id="unordered_index"), - pytest.param([5, 1, 3, 2], False, id="ignore_unordered_index"), - pytest.param(["z", "x", "a", "b"], True, id="str_index"), - pytest.param(["z", "x", "a", "b"], False, id="ignore_str_index"), - pytest.param( - pd.Index(["z", "x", "a", "b"], name="idx"), True, id="str_named_index" - ), - pytest.param( - pd.Index(["z", "x", "a", "b"], name="idx"), - False, - id="ignore_str_named_index", - ), - pytest.param( - pd.MultiIndex.from_frame( - pd.DataFrame({"idx0": [5, 1, 3, 2], "idx1": ["z", "x", "a", "b"]}) - ), - True, - id="multi_index", - ), - pytest.param( - pd.MultiIndex.from_frame( - pd.DataFrame({"idx0": [5, 1, 3, 2], "idx1": ["z", "x", "a", "b"]}) - ), - False, - id="ignore_multi_index", - ), - ], -) -def test_series_explode_w_index(index, ignore_index): - data = [[], [200.0, 23.12], [4.5, -9.0], [1.0]] - s = bigframes.pandas.Series(data, index=index) - pd_s = pd.Series(data, index=index) - # TODO(b/340885567): fix type error - assert_series_equal( - s.explode(ignore_index=ignore_index).to_pandas(), # type: ignore - pd_s.explode(ignore_index=ignore_index).astype(pd.Float64Dtype()), # type: ignore - check_index_type=False, - ) - - -@pytest.mark.parametrize( - ("ignore_index", "ordered"), - [ - pytest.param(True, True, id="include_index_ordered"), - pytest.param(True, False, id="include_index_unordered"), - pytest.param(False, True, id="ignore_index_ordered"), - ], -) -def test_series_explode_reserve_order(ignore_index, ordered): - data = [numpy.random.randint(0, 10, 10) for _ in range(10)] - s = bigframes.pandas.Series(data) - pd_s = pd.Series(data) - - # TODO(b/340885567): fix type error - res = s.explode(ignore_index=ignore_index).to_pandas(ordered=ordered) # type: ignore - # TODO(b/340885567): fix type error - pd_res = pd_s.explode(ignore_index=ignore_index).astype(pd.Int64Dtype()) # type: ignore - pd_res.index = pd_res.index.astype(pd.Int64Dtype()) - pd.testing.assert_series_equal( - res if ordered else res.sort_index(), - pd_res, - ) - - -def test_series_explode_w_aggregate(): - data = [[1, 2, 3], [], numpy.nan, [3, 4]] - s = bigframes.pandas.Series(data) - pd_s = pd.Series(data) - assert s.explode().sum() == pd_s.explode().sum() - - -def test_series_construct_empty_array(): - # TODO: supply a reason why this isn't compatible with pandas 1.x - pytest.importorskip("pandas", minversion="2.0.0") - s = bigframes.pandas.Series([[]]) - expected = pd.Series( - [[]], - dtype=pd.ArrowDtype(pa.list_(pa.float64())), - index=pd.Index([0], dtype=pd.Int64Dtype()), - ) - pd.testing.assert_series_equal( - expected, - s.to_pandas(), - ) - - -@pytest.mark.parametrize( - ("data"), - [ - pytest.param(numpy.nan, id="null"), - pytest.param([numpy.nan], id="null_array"), - pytest.param([[]], id="empty_array"), - pytest.param([numpy.nan, []], id="null_and_empty_array"), - ], -) -def test_series_explode_null(data): - s = bigframes.pandas.Series(data) - pd.testing.assert_series_equal( - s.explode().to_pandas(), - s.to_pandas().explode(), - check_dtype=False, - ) - - -@pytest.mark.skip( - reason="NotImplementedError: Polars compiler hasn't implemented IntegerLabelToDatetimeOp(freq=<75 * Days>, label=None, origin='start_day')" -) -@pytest.mark.parametrize( - ("append", "level", "col", "rule"), - [ - pytest.param(False, None, "timestamp_col", "75D"), - pytest.param(True, 1, "timestamp_col", "25W"), - pytest.param(False, None, "datetime_col", "3ME"), - pytest.param(True, "timestamp_col", "timestamp_col", "1YE"), - ], -) -def test_resample(scalars_df_index, scalars_pandas_df_index, append, level, col, rule): - # TODO: supply a reason why this isn't compatible with pandas 1.x - pytest.importorskip("pandas", minversion="2.0.0") - scalars_df_index = scalars_df_index.set_index(col, append=append)["int64_col"] - scalars_pandas_df_index = scalars_pandas_df_index.set_index(col, append=append)[ - "int64_col" - ] - bf_result = scalars_df_index.resample(rule=rule, level=level).min().to_pandas() - pd_result = scalars_pandas_df_index.resample(rule=rule, level=level).min() - pd.testing.assert_series_equal(bf_result, pd_result) - - -@pytest.mark.skip(reason="fixture 'nested_structs_df' not found") -def test_series_struct_get_field_by_attribute( - nested_structs_df, nested_structs_pandas_df -): - if Version(pd.__version__) < Version("2.2.0"): - pytest.skip("struct accessor is not supported before pandas 2.2") - - bf_series = nested_structs_df["person"] - df_series = nested_structs_pandas_df["person"] - - pd.testing.assert_series_equal( - bf_series.address.city.to_pandas(), - df_series.struct.field("address").struct.field("city"), - check_dtype=False, - check_index=False, - ) - pd.testing.assert_series_equal( - bf_series.address.country.to_pandas(), - df_series.struct.field("address").struct.field("country"), - check_dtype=False, - check_index=False, - ) - - -@pytest.mark.skip(reason="fixture 'nested_structs_df' not found") -def test_series_struct_fields_in_dir(nested_structs_df): - series = nested_structs_df["person"] - - assert "age" in dir(series) - assert "address" in dir(series) - assert "city" in dir(series.address) - assert "country" in dir(series.address) - - -@pytest.mark.skip(reason="fixture 'nested_structs_df' not found") -def test_series_struct_class_attributes_shadow_struct_fields(nested_structs_df): - series = nested_structs_df["person"] - - assert series.name == "person" - - -@pytest.mark.skip( - reason="NotImplementedError: dry_run not implemented for this executor" -) -def test_series_to_pandas_dry_run(scalars_df_index): - bf_series = scalars_df_index["int64_col"] - - result = bf_series.to_pandas(dry_run=True) - - assert isinstance(result, pd.Series) - assert len(result) > 0 - - -def test_series_item(session): - # Test with a single item - bf_s_single = bigframes.pandas.Series([42], session=session) - pd_s_single = pd.Series([42]) - assert bf_s_single.item() == pd_s_single.item() - - -def test_series_item_with_multiple(session): - # Test with multiple items - bf_s_multiple = bigframes.pandas.Series([1, 2, 3], session=session) - pd_s_multiple = pd.Series([1, 2, 3]) - - try: - pd_s_multiple.item() - except ValueError as e: - expected_message = str(e) - else: - raise AssertionError("Expected ValueError from pandas, but didn't get one") - - with pytest.raises(ValueError, match=re.escape(expected_message)): - bf_s_multiple.item() - - -def test_series_item_with_empty(session): - # Test with an empty Series - bf_s_empty = bigframes.pandas.Series([], dtype="Int64", session=session) - pd_s_empty = pd.Series([], dtype="Int64") - - try: - pd_s_empty.item() - except ValueError as e: - expected_message = str(e) - else: - raise AssertionError("Expected ValueError from pandas, but didn't get one") - - with pytest.raises(ValueError, match=re.escape(expected_message)): - bf_s_empty.item() - - -def test_series_dt_total_seconds(scalars_df_index, scalars_pandas_df_index): - bf_result = scalars_df_index["duration_col"].dt.total_seconds().to_pandas() - - pd_result = scalars_pandas_df_index["duration_col"].dt.total_seconds() - - # Index will be object type in pandas, string type in bigframes, but same values - pd.testing.assert_series_equal( - bf_result, - pd_result, - check_index_type=False, - # bigframes uses Float64, newer pandas may use double[pyarrow] - check_dtype=False, - ) - - -def test_series_where_with_expression(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - s1 = scalars_df["float64_col"] - s2 = scalars_df["bool_col"] - - bf_result = s1.where(s2, bpd.col("bool_col")).to_pandas() - - s1_pd = scalars_pandas_df["float64_col"] - s2_pd = scalars_pandas_df["bool_col"] - - pd_result = s1_pd.where(s2_pd, s2_pd) - - pd.testing.assert_series_equal(bf_result, pd_result, check_dtype=False) - - -def test_series_expression_unbound_fails(scalars_dfs): - scalars_df, _ = scalars_dfs - s1 = scalars_df["float64_col"] - s2 = scalars_df["bool_col"] - - with pytest.raises(ValueError, match="remains unbound"): - s1.where(s2, bpd.col("non_existent_column")) - - -def test_series_where_with_expression_resolving_to_self(scalars_dfs): - scalars_df, scalars_pandas_df = scalars_dfs - s1 = scalars_df["float64_col"] - s2 = scalars_df["bool_col"] - - bf_result = s1.where(s2, bpd.col("float64_col")).to_pandas() - - s1_pd = scalars_pandas_df["float64_col"] - s2_pd = scalars_pandas_df["bool_col"] - - pd_result = s1_pd.where(s2_pd, s1_pd) - - pd.testing.assert_series_equal(bf_result, pd_result, check_dtype=False) diff --git a/tests/unit/test_series_struct.py b/tests/unit/test_series_struct.py deleted file mode 100644 index f99d5859a56..00000000000 --- a/tests/unit/test_series_struct.py +++ /dev/null @@ -1,138 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from __future__ import annotations - -import pathlib -from typing import TYPE_CHECKING, Generator - -import pandas as pd -import pandas.testing -import pyarrow as pa # type: ignore -import pytest - -import bigframes - -if TYPE_CHECKING: - from bigframes.testing import polars_session - -pytest.importorskip("polars") -pytest.importorskip("pandas", minversion="2.2.0") - -CURRENT_DIR = pathlib.Path(__file__).parent -DATA_DIR = CURRENT_DIR.parent / "data" - - -@pytest.fixture(scope="module", autouse=True) -def session() -> Generator[bigframes.Session, None, None]: - import bigframes.core.global_session - from bigframes.testing import polars_session - - session = polars_session.TestSession() - with bigframes.core.global_session._GlobalSessionContext(session): - yield session - - -@pytest.fixture -def struct_df(session: polars_session.TestSession): - pa_type = pa.struct( - [ - ("str_field", pa.string()), - ("int_field", pa.int64()), - ] - ) - return session.DataFrame( - { - "struct_col": pd.Series( - pa.array( - [ - { - "str_field": "my string", - "int_field": 1, - }, - { - "str_field": None, - "int_field": 2, - }, - { - "str_field": "another string", - "int_field": None, - }, - { - "str_field": "some string", - "int_field": 3, - }, - ], - pa_type, - ), - dtype=pd.ArrowDtype(pa_type), - ), - } - ) - - -@pytest.fixture -def struct_series(struct_df): - return struct_df["struct_col"] - - -def test_struct_dtypes(struct_series): - bf_series = struct_series - pd_series = struct_series.to_pandas() - assert isinstance(pd_series.dtype, pd.ArrowDtype) - - bf_result = bf_series.struct.dtypes - pd_result = pd_series.struct.dtypes - - pandas.testing.assert_series_equal(bf_result, pd_result) - - -@pytest.mark.parametrize( - ("field_name", "common_dtype"), - ( - ("str_field", "string[pyarrow]"), - ("int_field", "int64[pyarrow]"), - # TODO(tswast): Support referencing fields by number, too. - ), -) -def test_struct_field(struct_series, field_name, common_dtype): - bf_series = struct_series - pd_series = struct_series.to_pandas() - assert isinstance(pd_series.dtype, pd.ArrowDtype) - - bf_result = bf_series.struct.field(field_name).to_pandas() - pd_result = pd_series.struct.field(field_name) - - # TODO(tswast): if/when we support arrowdtype for int/string, we can remove - # this cast. - bf_result = bf_result.astype(common_dtype) - pd_result = pd_result.astype(common_dtype) - - pandas.testing.assert_series_equal(bf_result, pd_result) - - -def test_struct_explode(struct_series): - bf_series = struct_series - pd_series = struct_series.to_pandas() - assert isinstance(pd_series.dtype, pd.ArrowDtype) - - bf_result = bf_series.struct.explode().to_pandas() - pd_result = pd_series.struct.explode() - - pandas.testing.assert_frame_equal( - bf_result, - pd_result, - # TODO(tswast): remove if/when we support arrowdtype for int/string. - check_dtype=False, - ) diff --git a/third_party/bigframes_vendored/constants.py b/third_party/bigframes_vendored/constants.py deleted file mode 100644 index aa331483a9c..00000000000 --- a/third_party/bigframes_vendored/constants.py +++ /dev/null @@ -1,60 +0,0 @@ -# Copyright 2023 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. - -"""Constants used across BigQuery DataFrames and bigframes_vendored. - -This module should not depend on any others in the package. -""" - -import typing -from typing import Literal - -import bigframes_vendored.version - -FEEDBACK_LINK = ( - "Share your use case with the BigQuery DataFrames team at the " - "https://bit.ly/bigframes-feedback survey. " - f"You are currently running BigFrames version {bigframes_vendored.version.__version__}." -) - -ABSTRACT_METHOD_ERROR_MESSAGE = ( - "Abstract method. You have likely encountered a bug. " - "Please share this stacktrace and how you reached it with the BigQuery DataFrames team. " - f"{FEEDBACK_LINK}" -) - -WRITE_ENGINE_TEMPLATE = ( - "Can't use parsing engine={engine} with write_engine={write_engine}, which " -) -WRITE_ENGINE_REQUIRES_LOCAL_ENGINE_TEMPLATE = ( - WRITE_ENGINE_TEMPLATE + "requires a local parsing engine. " + FEEDBACK_LINK -) -WRITE_ENGINE_REQUIRES_BIGQUERY_ENGINE_TEMPLATE = ( - WRITE_ENGINE_TEMPLATE - + "requires the engine='bigquery' parsing engine. " - + FEEDBACK_LINK -) - -WriteEngineType = Literal[ - "default", - "bigquery_inline", - "bigquery_load", - "bigquery_streaming", - "bigquery_write", - "_deferred", -] -VALID_WRITE_ENGINES = typing.get_args(WriteEngineType) - -DEFAULT_SORT_KIND = "stable" -STABLE_SORT_KINDS = ("stable", "mergesort") diff --git a/third_party/bigframes_vendored/cpython/_pprint.py b/third_party/bigframes_vendored/cpython/_pprint.py index 62450985816..617c14df0d9 100644 --- a/third_party/bigframes_vendored/cpython/_pprint.py +++ b/third_party/bigframes_vendored/cpython/_pprint.py @@ -70,11 +70,11 @@ # - removed global get_config, set _changed_only=True # - replace is_scalar_nan with isinstance(x, numbers.Real) and math.isnan +from collections import OrderedDict import inspect import math import numbers import pprint -from collections import OrderedDict from bigframes.ml.base import BaseEstimator @@ -110,7 +110,6 @@ def has_changed(k, v): # try to avoid calling repr on nested estimators if isinstance(v, BaseEstimator) and v.__class__ != init_params[k].__class__: return True - # Use repr as a last resort. It may be expensive. def is_scalar_nan(x): return isinstance(x, numbers.Real) and math.isnan(x) diff --git a/third_party/bigframes_vendored/db_benchmark/LICENSE b/third_party/bigframes_vendored/db_benchmark/LICENSE deleted file mode 100644 index a612ad9813b..00000000000 --- a/third_party/bigframes_vendored/db_benchmark/LICENSE +++ /dev/null @@ -1,373 +0,0 @@ -Mozilla Public License Version 2.0 -================================== - -1. Definitions --------------- - -1.1. "Contributor" - means each individual or legal entity that creates, contributes to - the creation of, or owns Covered Software. - -1.2. "Contributor Version" - means the combination of the Contributions of others (if any) used - by a Contributor and that particular Contributor's Contribution. - -1.3. "Contribution" - means Covered Software of a particular Contributor. - -1.4. "Covered Software" - means Source Code Form to which the initial Contributor has attached - the notice in Exhibit A, the Executable Form of such Source Code - Form, and Modifications of such Source Code Form, in each case - including portions thereof. - -1.5. "Incompatible With Secondary Licenses" - means - - (a) that the initial Contributor has attached the notice described - in Exhibit B to the Covered Software; or - - (b) that the Covered Software was made available under the terms of - version 1.1 or earlier of the License, but not also under the - terms of a Secondary License. - -1.6. "Executable Form" - means any form of the work other than Source Code Form. - -1.7. "Larger Work" - means a work that combines Covered Software with other material, in - a separate file or files, that is not Covered Software. - -1.8. "License" - means this document. - -1.9. "Licensable" - means having the right to grant, to the maximum extent possible, - whether at the time of the initial grant or subsequently, any and - all of the rights conveyed by this License. - -1.10. "Modifications" - means any of the following: - - (a) any file in Source Code Form that results from an addition to, - deletion from, or modification of the contents of Covered - Software; or - - (b) any new file in Source Code Form that contains any Covered - Software. - -1.11. "Patent Claims" of a Contributor - means any patent claim(s), including without limitation, method, - process, and apparatus claims, in any patent Licensable by such - Contributor that would be infringed, but for the grant of the - License, by the making, using, selling, offering for sale, having - made, import, or transfer of either its Contributions or its - Contributor Version. - -1.12. "Secondary License" - means either the GNU General Public License, Version 2.0, the GNU - Lesser General Public License, Version 2.1, the GNU Affero General - Public License, Version 3.0, or any later versions of those - licenses. - -1.13. "Source Code Form" - means the form of the work preferred for making modifications. - -1.14. "You" (or "Your") - means an individual or a legal entity exercising rights under this - License. For legal entities, "You" includes any entity that - controls, is controlled by, or is under common control with You. For - purposes of this definition, "control" means (a) the power, direct - or indirect, to cause the direction or management of such entity, - whether by contract or otherwise, or (b) ownership of more than - fifty percent (50%) of the outstanding shares or beneficial - ownership of such entity. - -2. License Grants and Conditions --------------------------------- - -2.1. Grants - -Each Contributor hereby grants You a world-wide, royalty-free, -non-exclusive license: - -(a) under intellectual property rights (other than patent or trademark) - Licensable by such Contributor to use, reproduce, make available, - modify, display, perform, distribute, and otherwise exploit its - Contributions, either on an unmodified basis, with Modifications, or - as part of a Larger Work; and - -(b) under Patent Claims of such Contributor to make, use, sell, offer - for sale, have made, import, and otherwise transfer either its - Contributions or its Contributor Version. - -2.2. Effective Date - -The licenses granted in Section 2.1 with respect to any Contribution -become effective for each Contribution on the date the Contributor first -distributes such Contribution. - -2.3. Limitations on Grant Scope - -The licenses granted in this Section 2 are the only rights granted under -this License. No additional rights or licenses will be implied from the -distribution or licensing of Covered Software under this License. -Notwithstanding Section 2.1(b) above, no patent license is granted by a -Contributor: - -(a) for any code that a Contributor has removed from Covered Software; - or - -(b) for infringements caused by: (i) Your and any other third party's - modifications of Covered Software, or (ii) the combination of its - Contributions with other software (except as part of its Contributor - Version); or - -(c) under Patent Claims infringed by Covered Software in the absence of - its Contributions. - -This License does not grant any rights in the trademarks, service marks, -or logos of any Contributor (except as may be necessary to comply with -the notice requirements in Section 3.4). - -2.4. Subsequent Licenses - -No Contributor makes additional grants as a result of Your choice to -distribute the Covered Software under a subsequent version of this -License (see Section 10.2) or under the terms of a Secondary License (if -permitted under the terms of Section 3.3). - -2.5. Representation - -Each Contributor represents that the Contributor believes its -Contributions are its original creation(s) or it has sufficient rights -to grant the rights to its Contributions conveyed by this License. - -2.6. Fair Use - -This License is not intended to limit any rights You have under -applicable copyright doctrines of fair use, fair dealing, or other -equivalents. - -2.7. Conditions - -Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted -in Section 2.1. - -3. Responsibilities -------------------- - -3.1. Distribution of Source Form - -All distribution of Covered Software in Source Code Form, including any -Modifications that You create or to which You contribute, must be under -the terms of this License. You must inform recipients that the Source -Code Form of the Covered Software is governed by the terms of this -License, and how they can obtain a copy of this License. You may not -attempt to alter or restrict the recipients' rights in the Source Code -Form. - -3.2. Distribution of Executable Form - -If You distribute Covered Software in Executable Form then: - -(a) such Covered Software must also be made available in Source Code - Form, as described in Section 3.1, and You must inform recipients of - the Executable Form how they can obtain a copy of such Source Code - Form by reasonable means in a timely manner, at a charge no more - than the cost of distribution to the recipient; and - -(b) You may distribute such Executable Form under the terms of this - License, or sublicense it under different terms, provided that the - license for the Executable Form does not attempt to limit or alter - the recipients' rights in the Source Code Form under this License. - -3.3. Distribution of a Larger Work - -You may create and distribute a Larger Work under terms of Your choice, -provided that You also comply with the requirements of this License for -the Covered Software. If the Larger Work is a combination of Covered -Software with a work governed by one or more Secondary Licenses, and the -Covered Software is not Incompatible With Secondary Licenses, this -License permits You to additionally distribute such Covered Software -under the terms of such Secondary License(s), so that the recipient of -the Larger Work may, at their option, further distribute the Covered -Software under the terms of either this License or such Secondary -License(s). - -3.4. Notices - -You may not remove or alter the substance of any license notices -(including copyright notices, patent notices, disclaimers of warranty, -or limitations of liability) contained within the Source Code Form of -the Covered Software, except that You may alter any license notices to -the extent required to remedy known factual inaccuracies. - -3.5. Application of Additional Terms - -You may choose to offer, and to charge a fee for, warranty, support, -indemnity or liability obligations to one or more recipients of Covered -Software. However, You may do so only on Your own behalf, and not on -behalf of any Contributor. You must make it absolutely clear that any -such warranty, support, indemnity, or liability obligation is offered by -You alone, and You hereby agree to indemnify every Contributor for any -liability incurred by such Contributor as a result of warranty, support, -indemnity or liability terms You offer. You may include additional -disclaimers of warranty and limitations of liability specific to any -jurisdiction. - -4. Inability to Comply Due to Statute or Regulation ---------------------------------------------------- - -If it is impossible for You to comply with any of the terms of this -License with respect to some or all of the Covered Software due to -statute, judicial order, or regulation then You must: (a) comply with -the terms of this License to the maximum extent possible; and (b) -describe the limitations and the code they affect. Such description must -be placed in a text file included with all distributions of the Covered -Software under this License. Except to the extent prohibited by statute -or regulation, such description must be sufficiently detailed for a -recipient of ordinary skill to be able to understand it. - -5. Termination --------------- - -5.1. The rights granted under this License will terminate automatically -if You fail to comply with any of its terms. However, if You become -compliant, then the rights granted under this License from a particular -Contributor are reinstated (a) provisionally, unless and until such -Contributor explicitly and finally terminates Your grants, and (b) on an -ongoing basis, if such Contributor fails to notify You of the -non-compliance by some reasonable means prior to 60 days after You have -come back into compliance. Moreover, Your grants from a particular -Contributor are reinstated on an ongoing basis if such Contributor -notifies You of the non-compliance by some reasonable means, this is the -first time You have received notice of non-compliance with this License -from such Contributor, and You become compliant prior to 30 days after -Your receipt of the notice. - -5.2. If You initiate litigation against any entity by asserting a patent -infringement claim (excluding declaratory judgment actions, -counter-claims, and cross-claims) alleging that a Contributor Version -directly or indirectly infringes any patent, then the rights granted to -You by any and all Contributors for the Covered Software under Section -2.1 of this License shall terminate. - -5.3. In the event of termination under Sections 5.1 or 5.2 above, all -end user license agreements (excluding distributors and resellers) which -have been validly granted by You or Your distributors under this License -prior to termination shall survive termination. - -************************************************************************ -* * -* 6. Disclaimer of Warranty * -* ------------------------- * -* * -* Covered Software is provided under this License on an "as is" * -* basis, without warranty of any kind, either expressed, implied, or * -* statutory, including, without limitation, warranties that the * -* Covered Software is free of defects, merchantable, fit for a * -* particular purpose or non-infringing. The entire risk as to the * -* quality and performance of the Covered Software is with You. * -* Should any Covered Software prove defective in any respect, You * -* (not any Contributor) assume the cost of any necessary servicing, * -* repair, or correction. This disclaimer of warranty constitutes an * -* essential part of this License. No use of any Covered Software is * -* authorized under this License except under this disclaimer. * -* * -************************************************************************ - -************************************************************************ -* * -* 7. Limitation of Liability * -* -------------------------- * -* * -* Under no circumstances and under no legal theory, whether tort * -* (including negligence), contract, or otherwise, shall any * -* Contributor, or anyone who distributes Covered Software as * -* permitted above, be liable to You for any direct, indirect, * -* special, incidental, or consequential damages of any character * -* including, without limitation, damages for lost profits, loss of * -* goodwill, work stoppage, computer failure or malfunction, or any * -* and all other commercial damages or losses, even if such party * -* shall have been informed of the possibility of such damages. This * -* limitation of liability shall not apply to liability for death or * -* personal injury resulting from such party's negligence to the * -* extent applicable law prohibits such limitation. Some * -* jurisdictions do not allow the exclusion or limitation of * -* incidental or consequential damages, so this exclusion and * -* limitation may not apply to You. * -* * -************************************************************************ - -8. Litigation -------------- - -Any litigation relating to this License may be brought only in the -courts of a jurisdiction where the defendant maintains its principal -place of business and such litigation shall be governed by laws of that -jurisdiction, without reference to its conflict-of-law provisions. -Nothing in this Section shall prevent a party's ability to bring -cross-claims or counter-claims. - -9. Miscellaneous ----------------- - -This License represents the complete agreement concerning the subject -matter hereof. If any provision of this License is held to be -unenforceable, such provision shall be reformed only to the extent -necessary to make it enforceable. Any law or regulation which provides -that the language of a contract shall be construed against the drafter -shall not be used to construe this License against a Contributor. - -10. Versions of the License ---------------------------- - -10.1. New Versions - -Mozilla Foundation is the license steward. Except as provided in Section -10.3, no one other than the license steward has the right to modify or -publish new versions of this License. Each version will be given a -distinguishing version number. - -10.2. Effect of New Versions - -You may distribute the Covered Software under the terms of the version -of the License under which You originally received the Covered Software, -or under the terms of any subsequent version published by the license -steward. - -10.3. Modified Versions - -If you create software not governed by this License, and you want to -create a new license for such software, you may create and use a -modified version of this License if you rename the license and remove -any references to the name of the license steward (except to note that -such modified license differs from this License). - -10.4. Distributing Source Code Form that is Incompatible With Secondary -Licenses - -If You choose to distribute Source Code Form that is Incompatible With -Secondary Licenses under the terms of this version of the License, the -notice described in Exhibit B of this License must be attached. - -Exhibit A - Source Code Form License Notice -------------------------------------------- - - This Source Code Form is subject to the terms of the Mozilla Public - License, v. 2.0. If a copy of the MPL was not distributed with this - file, You can obtain one at http://mozilla.org/MPL/2.0/. - -If it is not possible or desirable to put the notice in a particular -file, then You may include the notice in a location (such as a LICENSE -file in a relevant directory) where a recipient would be likely to look -for such a notice. - -You may add additional accurate notices of copyright ownership. - -Exhibit B - "Incompatible With Secondary Licenses" Notice ---------------------------------------------------------- - - This Source Code Form is "Incompatible With Secondary Licenses", as - defined by the Mozilla Public License, v. 2.0. diff --git a/third_party/bigframes_vendored/db_benchmark/METADATA b/third_party/bigframes_vendored/db_benchmark/METADATA deleted file mode 100644 index 6163ac69b79..00000000000 --- a/third_party/bigframes_vendored/db_benchmark/METADATA +++ /dev/null @@ -1,18 +0,0 @@ -name: "db-benchmark" -description: - "This repository contains a reproducible benchmarking suite for evaluating " - "database-like operations in single-node environments. It assesses " - "scalability across varying data volumes and complexities." - -third_party { - identifier { - type: "Git" - value: "https://github.com/h2oai/db-benchmark" - primary_source: true - version: "Latest Commit on Main Branch as of Access" - } - version: "Latest Commit on Main Branch as of Access" - last_upgrade_date { year: 2024 month: 7 day: 12 } - license_type: RECIPROCAL - local_modifications: "Modified the queries to test and benchmark the BigFrames project" -} diff --git a/third_party/bigframes_vendored/db_benchmark/README.md b/third_party/bigframes_vendored/db_benchmark/README.md deleted file mode 100644 index aba227b0ebf..00000000000 --- a/third_party/bigframes_vendored/db_benchmark/README.md +++ /dev/null @@ -1,76 +0,0 @@ -Repository for reproducible benchmarking of database-like operations in single-node environment. -Benchmark report is available at [h2oai.github.io/db-benchmark](https://h2oai.github.io/db-benchmark). -We focused mainly on portability and reproducibility. Benchmark is routinely re-run to present up-to-date timings. Most of solutions used are automatically upgraded to their stable or development versions. -This benchmark is meant to compare scalability both in data volume and data complexity. -Contribution and feedback are very welcome! - -# Tasks - - - [x] groupby - - [x] join - - [x] groupby2014 - -# Solutions - - - [x] [dask](https://github.com/dask/dask) - - [x] [data.table](https://github.com/Rdatatable/data.table) - - [x] [dplyr](https://github.com/tidyverse/dplyr) - - [x] [DataFrames.jl](https://github.com/JuliaData/DataFrames.jl) - - [x] [pandas](https://github.com/pandas-dev/pandas) - - [x] [(py)datatable](https://github.com/h2oai/datatable) - - [x] [spark](https://github.com/apache/spark) - - [x] [cuDF](https://github.com/rapidsai/cudf) - - [x] [ClickHouse](https://github.com/yandex/ClickHouse) - - [x] [Polars](https://github.com/ritchie46/polars) - - [x] [Arrow](https://github.com/apache/arrow) - - [x] [DuckDB](https://github.com/duckdb/duckdb) - -More solutions has been proposed. Status of those can be tracked in issues tracker of our project repository by using [_new solution_](https://github.com/h2oai/db-benchmark/issues?q=is%3Aissue+is%3Aopen+label%3A%22new+solution%22) label. - -# Reproduce - -## Batch benchmark run - -- edit `path.env` and set `julia` and `java` paths -- if solution uses python create new `virtualenv` as `$solution/py-$solution`, example for `pandas` use `virtualenv pandas/py-pandas --python=/usr/bin/python3.6` -- install every solution, follow `$solution/setup-$solution.sh` scripts -- edit `run.conf` to define solutions and tasks to benchmark -- generate data, for `groupby` use `Rscript _data/groupby-datagen.R 1e7 1e2 0 0` to create `G1_1e7_1e2_0_0.csv`, re-save to binary format where needed (see below), create `data` directory and keep all data files there -- edit `_control/data.csv` to define data sizes to benchmark using `active` flag -- ensure SWAP is disabled and ClickHouse server is not yet running -- start benchmark with `./run.sh` - -## Single solution benchmark - -- install solution software - - for python we recommend to use `virtualenv` for better isolation - - for R ensure that library is installed in a solution subdirectory, so that `library("dplyr", lib.loc="./dplyr/r-dplyr")` or `library("data.table", lib.loc="./datatable/r-datatable")` works - - note that some solutions may require another to be installed to speed-up csv data load, for example, `dplyr` requires `data.table` and similarly `pandas` requires (py)`datatable` -- generate data using `_data/*-datagen.R` scripts, for example, `Rscript _data/groupby-datagen.R 1e7 1e2 0 0` creates `G1_1e7_1e2_0_0.csv`, put data files in `data` directory -- run benchmark for a single solution using `./_launcher/solution.R --solution=data.table --task=groupby --nrow=1e7` -- run other data cases by passing extra parameters `--k=1e2 --na=0 --sort=0` -- use `--quiet=true` to suppress script's output and print timings only, using `--print=question,run,time_sec` specify columns to be printed to console, to print all use `--print=*` -- use `--out=time.csv` to write timings to a file rather than console - -## Running script interactively - -- install software in expected location, details above -- ensure data name to be used in env var below is present in `./data` dir -- source python virtual environment if needed -- call `SRC_DATANAME=G1_1e7_1e2_0_0 R`, if desired replace `R` with `python` or `julia` -- proceed pasting code from benchmark script - -## Extra care needed - -- `cudf` uses `conda` instead of `virtualenv` - -# Example environment - -- setting up r3-8xlarge: 244GB RAM, 32 cores: [Amazon EC2 for beginners](https://github.com/Rdatatable/data.table/wiki/Amazon-EC2-for-beginners) -- (slightly outdated) full reproduce script on clean Ubuntu 16.04: [_utils/repro.sh](https://github.com/h2oai/db-benchmark/blob/master/_utils/repro.sh) - -# Acknowledgment - -Timings for some solutions might be missing for particular data sizes or questions. Some functions are not yet implemented in all solutions so we were unable to answer all questions in all solutions. Some solutions might also run out of memory when running benchmark script which results the process to be killed by OS. Lastly we also added timeout for single benchmark script to run, once timeout value is reached script is terminated. -Please check [_exceptions_](https://github.com/h2oai/db-benchmark/issues?q=is%3Aissue+is%3Aopen+label%3Aexceptions) label in our repository for a list of issues/defects in solutions, that makes us unable to provide all timings. -There is also [_no documentation_](https://github.com/h2oai/db-benchmark/labels/no%20documentation) label that lists issues that are blocked by missing documentation in solutions we are benchmarking. diff --git a/third_party/bigframes_vendored/db_benchmark/__init__.py b/third_party/bigframes_vendored/db_benchmark/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/third_party/bigframes_vendored/db_benchmark/groupby_queries.py b/third_party/bigframes_vendored/db_benchmark/groupby_queries.py deleted file mode 100644 index 7758496db59..00000000000 --- a/third_party/bigframes_vendored/db_benchmark/groupby_queries.py +++ /dev/null @@ -1,123 +0,0 @@ -# Contains code from https://github.com/duckdblabs/db-benchmark/blob/master/pandas/groupby-pandas.py - -import bigframes -import bigframes.session - - -def q1(project_id: str, dataset_id: str, table_id: str, session: bigframes.Session): - print("Groupby benchmark 1: sum v1 by id1") - - x = session.read_gbq(f"{project_id}.{dataset_id}.{table_id}") - - ans = x.groupby("id1", as_index=False, dropna=False).agg({"v1": "sum"}) - print(ans.shape) - chk = [ans["v1"].sum()] - print(chk) - - -def q2(project_id: str, dataset_id: str, table_id: str, session: bigframes.Session): - print("Groupby benchmark 2: sum v1 by id1:id2") - - x = session.read_gbq(f"{project_id}.{dataset_id}.{table_id}") - - ans = x.groupby(["id1", "id2"], as_index=False, dropna=False).agg({"v1": "sum"}) - print(ans.shape) - chk = [ans["v1"].sum()] - print(chk) - - -def q3(project_id: str, dataset_id: str, table_id: str, session: bigframes.Session): - print("Groupby benchmark 3: sum v1 mean v3 by id3") - - x = session.read_gbq(f"{project_id}.{dataset_id}.{table_id}") - - ans = x.groupby("id3", as_index=False, dropna=False).agg( - {"v1": "sum", "v3": "mean"} - ) - print(ans.shape) - chk = [ans["v1"].sum(), ans["v3"].sum()] - print(chk) - - -def q4(project_id: str, dataset_id: str, table_id: str, session: bigframes.Session): - print("Groupby benchmark 4: mean v1:v3 by id4") - - x = session.read_gbq(f"{project_id}.{dataset_id}.{table_id}") - - ans = x.groupby("id4", as_index=False, dropna=False).agg( - {"v1": "mean", "v2": "mean", "v3": "mean"} - ) - print(ans.shape) - chk = [ans["v1"].sum(), ans["v2"].sum(), ans["v3"].sum()] - print(chk) - - -def q5(project_id: str, dataset_id: str, table_id: str, session: bigframes.Session): - print("Groupby benchmark 5: sum v1:v3 by id6") - - x = session.read_gbq(f"{project_id}.{dataset_id}.{table_id}") - - ans = x.groupby("id6", as_index=False, dropna=False).agg( - {"v1": "sum", "v2": "sum", "v3": "sum"} - ) - print(ans.shape) - chk = [ans["v1"].sum(), ans["v2"].sum(), ans["v3"].sum()] - print(chk) - - -def q6(project_id: str, dataset_id: str, table_id: str, session: bigframes.Session): - print("Groupby benchmark 6: median v3 sd v3 by id4 id5") - - x = session.read_gbq(f"{project_id}.{dataset_id}.{table_id}") - - ans = x.groupby(["id4", "id5"], as_index=False, dropna=False).agg( - {"v3": ["median", "std"]} - ) - print(ans.shape) - chk = [ans["v3"]["median"].sum(), ans["v3"]["std"].sum()] - print(chk) - - -def q7(project_id: str, dataset_id: str, table_id: str, session: bigframes.Session): - print("Groupby benchmark 7: max v1 - min v2 by id3") - - x = session.read_gbq(f"{project_id}.{dataset_id}.{table_id}") - - ans = ( - x.groupby("id3", as_index=False, dropna=False) - .agg({"v1": "max", "v2": "min"}) - .assign(range_v1_v2=lambda x: x["v1"] - x["v2"])[["id3", "range_v1_v2"]] - ) - print(ans.shape) - chk = [ans["range_v1_v2"].sum()] - print(chk) - - -def q8(project_id: str, dataset_id: str, table_id: str, session: bigframes.Session): - print("Groupby benchmark 8: largest two v3 by id6") - - x = session.read_gbq(f"{project_id}.{dataset_id}.{table_id}") - - ans = ( - x[~x["v3"].isna()][["id6", "v3"]] - .sort_values("v3", ascending=False) - .groupby("id6", as_index=False, dropna=False) - .head(2) - ) - ans = ans.reset_index(drop=True) - print(ans.shape) - chk = [ans["v3"].sum()] - print(chk) - - -def q10(project_id: str, dataset_id: str, table_id: str, session: bigframes.Session): - print("Groupby benchmark 10: sum v3 count by id1:id6") - - x = session.read_gbq(f"{project_id}.{dataset_id}.{table_id}") - - ans = x.groupby( - ["id1", "id2", "id3", "id4", "id5", "id6"], as_index=False, dropna=False - ).agg({"v3": "sum", "v1": "size"}) - print(ans.shape) - chk = [ans["v3"].sum(), ans["v1"].sum()] - print(chk) diff --git a/third_party/bigframes_vendored/db_benchmark/join_queries.py b/third_party/bigframes_vendored/db_benchmark/join_queries.py deleted file mode 100644 index f0073436c02..00000000000 --- a/third_party/bigframes_vendored/db_benchmark/join_queries.py +++ /dev/null @@ -1,91 +0,0 @@ -# Contains code from https://github.com/duckdblabs/db-benchmark/blob/master/pandas/join-pandas.py -# and https://github.com/duckdblabs/db-benchmark/blob/main/_helpers/helpers.py - -import bigframes - - -def q1(project_id: str, dataset_id: str, table_id: str, session: bigframes.Session): - print("Join benchmark 1: small inner on int") - - x = session.read_gbq(f"{project_id}.{dataset_id}.{table_id}") - small = session.read_gbq( - f"{project_id}.{dataset_id}.{_get_join_table_id(table_id, 'small')}" - ) - - ans = x.merge(small, on="id1") - print(ans.shape) - - chk = [ans["v1"].sum(), ans["v2"].sum()] - print(chk) - - -def q2(project_id: str, dataset_id: str, table_id: str, session: bigframes.Session): - print("Join benchmark 2: medium inner on int") - - x = session.read_gbq(f"{project_id}.{dataset_id}.{table_id}") - medium = session.read_gbq( - f"{project_id}.{dataset_id}.{_get_join_table_id(table_id, 'medium')}" - ) - - ans = x.merge(medium, on="id2") - print(ans.shape) - - chk = [ans["v1"].sum(), ans["v2"].sum()] - print(chk) - - -def q3(project_id: str, dataset_id: str, table_id: str, session: bigframes.Session): - print("Join benchmark 3: medium outer on int") - - x = session.read_gbq(f"{project_id}.{dataset_id}.{table_id}") - medium = session.read_gbq( - f"{project_id}.{dataset_id}.{_get_join_table_id(table_id, 'medium')}" - ) - - ans = x.merge(medium, how="left", on="id2") - print(ans.shape) - - chk = [ans["v1"].sum(), ans["v2"].sum()] - print(chk) - - -def q4(project_id: str, dataset_id: str, table_id: str, session: bigframes.Session): - print("Join benchmark 4: medium inner on factor") - - x = session.read_gbq(f"{project_id}.{dataset_id}.{table_id}") - medium = session.read_gbq( - f"{project_id}.{dataset_id}.{_get_join_table_id(table_id, 'medium')}" - ) - - ans = x.merge(medium, on="id5") - print(ans.shape) - - chk = [ans["v1"].sum(), ans["v2"].sum()] - print(chk) - - -def q5(project_id: str, dataset_id: str, table_id: str, session: bigframes.Session): - print("Join benchmark 5: big inner on int") - - x = session.read_gbq(f"{project_id}.{dataset_id}.{table_id}") - big = session.read_gbq( - f"{project_id}.{dataset_id}.{_get_join_table_id(table_id, 'big')}" - ) - - ans = x.merge(big, on="id3") - print(ans.shape) - - chk = [ans["v1"].sum(), ans["v2"].sum()] - print(chk) - - -def _get_join_table_id(table_id, join_size): - x_n = int(float(table_id.split("_")[1])) - - if join_size == "small": - y_n = "{:.0e}".format(x_n / 1e6) - elif join_size == "medium": - y_n = "{:.0e}".format(x_n / 1e3) - else: - y_n = "{:.0e}".format(x_n) - return table_id.replace("NA", y_n).replace("+0", "") diff --git a/third_party/bigframes_vendored/db_benchmark/sort_queries.py b/third_party/bigframes_vendored/db_benchmark/sort_queries.py deleted file mode 100644 index bbaf46cf279..00000000000 --- a/third_party/bigframes_vendored/db_benchmark/sort_queries.py +++ /dev/null @@ -1,18 +0,0 @@ -# Contains code from https://github.com/duckdblabs/db-benchmark/blob/master/pandas/sort-pandas.py - -import bigframes -import bigframes.session - - -def q1( - project_id: str, dataset_id: str, table_id: str, session: bigframes.Session -) -> None: - print("Sort benchmark 1: sort by int id2") - - x = session.read_gbq(f"{project_id}.{dataset_id}.{table_id}") - - ans = x.sort_values("id2") - print(ans.shape) - - chk = [ans["v1"].sum()] - print(chk) diff --git a/third_party/bigframes_vendored/geopandas/LICENSE.txt b/third_party/bigframes_vendored/geopandas/LICENSE.txt deleted file mode 100644 index 028603be208..00000000000 --- a/third_party/bigframes_vendored/geopandas/LICENSE.txt +++ /dev/null @@ -1,25 +0,0 @@ -Copyright (c) 2013-2022, GeoPandas developers. -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - * Neither the name of GeoPandas nor the names of its contributors may - be used to endorse or promote products derived from this software without - specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR -ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON -ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/third_party/bigframes_vendored/geopandas/geoseries.py b/third_party/bigframes_vendored/geopandas/geoseries.py deleted file mode 100644 index 6b56a318011..00000000000 --- a/third_party/bigframes_vendored/geopandas/geoseries.py +++ /dev/null @@ -1,528 +0,0 @@ -# contains code from https://github.com/geopandas/geopandas/blob/main/geopandas/geoseries.py -from __future__ import annotations - -from typing import TYPE_CHECKING - -from bigframes import constants - -if TYPE_CHECKING: - import bigframes.series - - -class GeoSeries: - """ - A Series object designed to store geometry objects. - - **Examples:** - - >>> import bigframes.geopandas - >>> import bigframes.pandas as bpd - >>> from shapely.geometry import Point - - >>> s = bigframes.geopandas.GeoSeries([Point(1, 1), Point(2, 2), Point(3, 3)]) - >>> s - 0 POINT (1 1) - 1 POINT (2 2) - 2 POINT (3 3) - dtype: geometry - - Args: - data (array-like, dict, scalar value, bigframes.pandas.Series): - The geometries to store in the GeoSeries. - index (array-like, pandas.Index, bigframes.pandas.Index): - The index for the GeoSeries. - kwargs (dict): - Additional arguments passed to the Series constructor, - e.g. ``name``. - """ - - # GeoSeries.area overrides Series.area with something totally different. - # Ignore this type error, as we are trying to be as close to geopandas as - # we can. - @property - def area(self, crs=None) -> bigframes.series.Series: # type: ignore - """[Not Implemented] Use ``bigframes.bigquery.st_area(series)``, - instead to return the area in square meters. - - In GeoPandas, this returns a Series containing the area of each geometry - in the GeoSeries expressed in the units of the CRS. - - Args: - crs (optional): - Coordinate Reference System of the geometry objects. Can be - anything accepted by pyproj.CRS.from_user_input(), such as an - authority string (eg “EPSG:4326”) or a WKT string. - - Returns: - bigframes.pandas.Series: - Series of float representing the areas. - - Raises: - NotImplementedError: - GeoSeries.area is not supported. Use bigframes.bigquery.st_area(series), instead. - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - @property - def x(self) -> bigframes.series.Series: - """Return the x location of point geometries in a GeoSeries - - **Examples:** - - >>> import bigframes.pandas as bpd - >>> import geopandas.array - >>> import shapely.geometry - - >>> series = bpd.Series( - ... [shapely.geometry.Point(1, 2), shapely.geometry.Point(2, 3), shapely.geometry.Point(3, 4)], - ... dtype=geopandas.array.GeometryDtype() - ... ) - >>> series.geo.x - 0 1.0 - 1 2.0 - 2 3.0 - dtype: Float64 - - Returns: - bigframes.pandas.Series: - Return the x location (longitude) of point geometries. - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - @property - def y(self) -> bigframes.series.Series: - """Return the y location of point geometries in a GeoSeries - - **Examples:** - - >>> import bigframes.pandas as bpd - >>> import geopandas.array - >>> import shapely.geometry - - >>> series = bpd.Series( - ... [shapely.geometry.Point(1, 2), shapely.geometry.Point(2, 3), shapely.geometry.Point(3, 4)], - ... dtype=geopandas.array.GeometryDtype() - ... ) - >>> series.geo.y - 0 2.0 - 1 3.0 - 2 4.0 - dtype: Float64 - - Returns: - bigframes.pandas.Series: - Return the y location (latitude) of point geometries. - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - @property - def boundary(self) -> bigframes.geopandas.GeoSeries: - """ - Returns a GeoSeries of lower dimensional objects representing each - geometry's set-theoretic boundary. - - **Examples:** - - >>> import bigframes.pandas as bpd - >>> import geopandas.array - >>> import shapely.geometry - - >>> from shapely.geometry import Polygon, LineString, Point - >>> s = geopandas.GeoSeries( - ... [ - ... Polygon([(0, 0), (1, 1), (0, 1)]), - ... LineString([(0, 0), (1, 1), (1, 0)]), - ... Point(0, 0), - ... ] - ... ) - >>> s - 0 POLYGON ((0 0, 1 1, 0 1, 0 0)) - 1 LINESTRING (0 0, 1 1, 1 0) - 2 POINT (0 0) - dtype: geometry - - >>> s.boundary - 0 LINESTRING (0 0, 1 1, 0 1, 0 0) - 1 MULTIPOINT ((0 0), (1 0)) - 2 GEOMETRYCOLLECTION EMPTY - dtype: geometry - - Returns: - bigframes.geopandas.GeoSeries: - A GeoSeries of lower dimensional objects representing each - geometry's set-theoretic boundary - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - @classmethod - def from_xy(cls, x, y, index=None, **kwargs) -> bigframes.geopandas.GeoSeries: - """ - Alternate constructor to create a GeoSeries of Point geometries from - lists or arrays of x, y coordinates. - - In case of geographic coordinates, it is assumed that longitude is - captured by x coordinates and latitude by y. - - **Examples:** - - >>> import bigframes.pandas as bpd - >>> import bigframes.geopandas - - >>> x = [2.5, 5, -3.0] - >>> y = [0.5, 1, 1.5] - - >>> s = bigframes.geopandas.GeoSeries.from_xy(x, y) - >>> s - 0 POINT (2.5 0.5) - 1 POINT (5 1) - 2 POINT (-3 1.5) - dtype: geometry - - Args: - x, y (array-like): - longitude is x coordinates and latitude y coordinates. - - index (array-like or Index, optional): - The index for the GeoSeries. If not given and all coordinate - inputs are Series with an equal index, that index is used. - - **kwargs: - Additional arguments passed to the Series constructor, e.g. `name`. - - Returns: - bigframes.geopandas.GeoSeries: - A GeoSeries of Point geometries. - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - @classmethod - def from_wkt(cls, data, index=None) -> bigframes.geopandas.GeoSeries: - """ - Alternate constructor to create a GeoSeries from a list or array of - WKT objects. - - **Examples:** - - >>> import bigframes as bpd - >>> import bigframes.geopandas - - >>> wkts = [ - ... 'POINT (1 1)', - ... 'POINT (2 2)', - ... 'POINT (3 3)', - ... ] - >>> s = bigframes.geopandas.GeoSeries.from_wkt(wkts) - >>> s - 0 POINT (1 1) - 1 POINT (2 2) - 2 POINT (3 3) - dtype: geometry - - Args: - data (array-like): - Series, list, or array of WKT objects. - - index (array-like or Index, optional): - The index for the GeoSeries. - - Returns: - bigframes.geopandas.GeoSeries: - A GeoSeries of geometries. - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - def to_wkt(self) -> bigframes.series.Series: - """ - Convert GeoSeries geometries to WKT - - **Examples:** - - >>> import bigframes as bpd - >>> import bigframes.geopandas - >>> from shapely.geometry import Point - - >>> s = bigframes.geopandas.GeoSeries([Point(1, 1), Point(2, 2), Point(3, 3)]) - >>> s - 0 POINT (1 1) - 1 POINT (2 2) - 2 POINT (3 3) - dtype: geometry - - >>> s.to_wkt() - 0 POINT(1 1) - 1 POINT(2 2) - 2 POINT(3 3) - dtype: string - - Returns: - bigframes.series.Series: - WKT representations of the geometries. - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - def difference(self: GeoSeries, other: GeoSeries) -> GeoSeries: # type: ignore - """ - Returns a GeoSeries of the points in each aligned geometry that are not - in other. - - The operation works on a 1-to-1 row-wise manner. - - **Examples:** - - >>> import bigframes as bpd - >>> import bigframes.geopandas - >>> from shapely.geometry import Polygon, LineString, Point - - We can check two GeoSeries against each other, row by row: - - >>> s1 = bigframes.geopandas.GeoSeries( - ... [ - ... Polygon([(0, 0), (2, 2), (0, 2)]), - ... Polygon([(0, 0), (2, 2), (0, 2)]), - ... LineString([(0, 0), (2, 2)]), - ... LineString([(2, 0), (0, 2)]), - ... Point(0, 1), - ... ], - ... ) - >>> s2 = bigframes.geopandas.GeoSeries( - ... [ - ... Polygon([(0, 0), (1, 1), (0, 1)]), - ... LineString([(1, 0), (1, 3)]), - ... LineString([(2, 0), (0, 2)]), - ... Point(1, 1), - ... Point(0, 1), - ... ], - ... index=range(1, 6), - ... ) - - >>> s1 - 0 POLYGON ((0 0, 2 2, 0 2, 0 0)) - 1 POLYGON ((0 0, 2 2, 0 2, 0 0)) - 2 LINESTRING (0 0, 2 2) - 3 LINESTRING (2 0, 0 2) - 4 POINT (0 1) - dtype: geometry - - >>> s2 - 1 POLYGON ((0 0, 1 1, 0 1, 0 0)) - 2 LINESTRING (1 0, 1 3) - 3 LINESTRING (2 0, 0 2) - 4 POINT (1 1) - 5 POINT (0 1) - dtype: geometry - - >>> s1.difference(s2) - 0 None - 1 POLYGON ((0.99954 1, 2 2, 0 2, 0 1, 0.99954 1)) - 2 LINESTRING (0 0, 1 1.00046, 2 2) - 3 GEOMETRYCOLLECTION EMPTY - 4 POINT (0 1) - 5 None - dtype: geometry - - We can also check difference of single shapely geometries: - - >>> polygon_s1 = bigframes.geopandas.GeoSeries( - ... [ - ... Polygon([(0, 0), (10, 0), (10, 10), (0, 0)]) - ... ] - ... ) - >>> polygon_s2 = bigframes.geopandas.GeoSeries( - ... [ - ... Polygon([(4, 2), (6, 2), (8, 6), (4, 2)]) - ... ] - ... ) - - >>> polygon_s1 - 0 POLYGON ((0 0, 10 0, 10 10, 0 0)) - dtype: geometry - - >>> polygon_s2 - 0 POLYGON ((4 2, 6 2, 8 6, 4 2)) - dtype: geometry - - >>> polygon_s1.difference(polygon_s2) - 0 POLYGON ((0 0, 10 0, 10 10, 0 0), (8 6, 6 2, 4... - dtype: geometry - - Additionally, we can check difference of a GeoSeries against a single shapely geometry: - - >>> s1.difference(polygon_s2) - 0 POLYGON ((0 0, 2 2, 0 2, 0 0)) - 1 None - 2 None - 3 None - 4 None - dtype: geometry - - Args: - other (bigframes.geopandas.GeoSeries or geometric object): - The GeoSeries (elementwise) or geometric object to find the - difference to. - - Returns: - bigframes.geopandas.GeoSeries: - A GeoSeries of the points in each aligned geometry that are not - in other. - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - def distance(self: GeoSeries, other: GeoSeries) -> bigframes.series.Series: - """ - [Not Implemented] Use ``bigframes.bigquery.st_distance(series, other)`` - instead to return the shorted distance between two - ``GEOGRAPHY`` objects in meters. - - In GeoPandas, this returns a Series of the distances between each - aligned geometry in the expressed in the units of the CRS. - - Args: - other: - The Geoseries (elementwise) or geometric object to find the distance to. - - Returns: - bigframes.pandas.Series: - Series of float representing the distances. - - Raises: - NotImplementedError: - GeoSeries.distance is not supported. Use - ``bigframes.bigquery.st_distance(series, other)``, instead. - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - def intersection(self: GeoSeries, other: GeoSeries) -> GeoSeries: # type: ignore - """ - Returns a GeoSeries of the intersection of points in each aligned - geometry with other. - - The operation works on a 1-to-1 row-wise manner. - - **Examples:** - - >>> import bigframes as bpd - >>> import bigframes.geopandas - >>> from shapely.geometry import Polygon, LineString, Point - - We can check two GeoSeries against each other, row by row. - - >>> s1 = bigframes.geopandas.GeoSeries( - ... [ - ... Polygon([(0, 0), (2, 2), (0, 2)]), - ... Polygon([(0, 0), (2, 2), (0, 2)]), - ... LineString([(0, 0), (2, 2)]), - ... LineString([(2, 0), (0, 2)]), - ... Point(0, 1), - ... ], - ... ) - >>> s2 = bigframes.geopandas.GeoSeries( - ... [ - ... Polygon([(0, 0), (1, 1), (0, 1)]), - ... LineString([(1, 0), (1, 3)]), - ... LineString([(2, 0), (0, 2)]), - ... Point(1, 1), - ... Point(0, 1), - ... ], - ... index=range(1, 6), - ... ) - - >>> s1 - 0 POLYGON ((0 0, 2 2, 0 2, 0 0)) - 1 POLYGON ((0 0, 2 2, 0 2, 0 0)) - 2 LINESTRING (0 0, 2 2) - 3 LINESTRING (2 0, 0 2) - 4 POINT (0 1) - dtype: geometry - - >>> s2 - 1 POLYGON ((0 0, 1 1, 0 1, 0 0)) - 2 LINESTRING (1 0, 1 3) - 3 LINESTRING (2 0, 0 2) - 4 POINT (1 1) - 5 POINT (0 1) - dtype: geometry - - >>> s1.intersection(s2) - 0 None - 1 POLYGON ((0 0, 0.99954 1, 0 1, 0 0)) - 2 POINT (1 1.00046) - 3 LINESTRING (2 0, 0 2) - 4 GEOMETRYCOLLECTION EMPTY - 5 None - dtype: geometry - - - We can also do intersection of each geometry and a single shapely geometry: - - >>> s1.intersection(bigframes.geopandas.GeoSeries([Polygon([(0, 0), (1, 1), (0, 1)])])) - 0 POLYGON ((0 0, 0.99954 1, 0 1, 0 0)) - 1 None - 2 None - 3 None - 4 None - dtype: geometry - - - Args: - other (GeoSeries or geometric object): - The Geoseries (elementwise) or geometric object to find the - intersection with. - - Returns: - bigframes.geopandas.GeoSeries: - The Geoseries (elementwise) of the intersection of points in - each aligned geometry with other. - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - @property - def is_closed(self: GeoSeries) -> bigframes.series.Series: - """ - [Not Implemented] Use ``bigframes.bigquery.st_isclosed(series)`` - instead to return a boolean indicating if a shape is closed. - - In GeoPandas, this returns a Series of booleans with value True if a - LineString's or LinearRing's first and last points are equal. - - Returns False for any other geometry type. - - Returns: - bigframes.pandas.Series: - Series of booleans. - - Raises: - NotImplementedError: - GeoSeries.is_closed is not supported. Use - ``bigframes.bigquery.st_isclosed(series)``, instead. - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - def simplify( - self, tolerance: float, preserve_topology: bool = True - ) -> bigframes.series.Series: # type: ignore - """[Not Implemented] Use ``bigframes.bigquery.st_simplify(series, tolerance_meters)``, - instead to set the tolerance in meters. - - In GeoPandas, this returns a GeoSeries containing a simplified - representation of each geometry. - - Args: - tolerance (float): - All parts of a simplified geometry will be no more than - tolerance distance from the original. It has the same units as - the coordinate reference system of the GeoSeries. For example, - using tolerance=100 in a projected CRS with meters as units - means a distance of 100 meters in reality. - preserve_topology (bool): - Default True. False uses a quicker algorithm, but may produce - self-intersecting or otherwise invalid geometries. - - Returns: - bigframes.geopandas.GeoSeries: - Series of simplified geometries. - - Raises: - NotImplementedError: - GeoSeries.simplify is not supported. Use bigframes.bigquery.st_simplify(series, tolerance_meters), instead. - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) diff --git a/third_party/bigframes_vendored/google_cloud_bigquery/_pandas_helpers.py b/third_party/bigframes_vendored/google_cloud_bigquery/_pandas_helpers.py index 3e35b1382e3..5e2a7a7ef0f 100644 --- a/third_party/bigframes_vendored/google_cloud_bigquery/_pandas_helpers.py +++ b/third_party/bigframes_vendored/google_cloud_bigquery/_pandas_helpers.py @@ -17,7 +17,6 @@ import warnings -import db_dtypes import google.cloud.bigquery.schema as schema import pyarrow @@ -62,7 +61,6 @@ def pyarrow_timestamp(): "TIME": pyarrow_time, "TIMESTAMP": pyarrow_timestamp, "BIGNUMERIC": pyarrow_bignumeric, - "JSON": db_dtypes.JSONArrowType, } ARROW_SCALAR_IDS_TO_BQ = { # https://arrow.apache.org/docs/python/api/datatypes.html#type-classes diff --git a/third_party/bigframes_vendored/google_cloud_bigquery/retry.py b/third_party/bigframes_vendored/google_cloud_bigquery/retry.py deleted file mode 100644 index 9117d7aa569..00000000000 --- a/third_party/bigframes_vendored/google_cloud_bigquery/retry.py +++ /dev/null @@ -1,220 +0,0 @@ -# Original: https://github.com/googleapis/python-bigquery/blob/main/google/cloud/bigquery/retry.py -# Copyright 2018 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 google.api_core.future.polling -import requests.exceptions -from google.api_core import exceptions, retry -from google.auth import exceptions as auth_exceptions # type: ignore - -_RETRYABLE_REASONS = frozenset( - ["rateLimitExceeded", "backendError", "internalError", "badGateway"] -) - -_UNSTRUCTURED_RETRYABLE_TYPES = ( - ConnectionError, - exceptions.TooManyRequests, - exceptions.InternalServerError, - exceptions.BadGateway, - exceptions.ServiceUnavailable, - requests.exceptions.ChunkedEncodingError, - requests.exceptions.ConnectionError, - requests.exceptions.Timeout, - auth_exceptions.TransportError, -) - -_MINUTE_IN_SECONDS = 60.0 -_HOUR_IN_SECONDS = 60.0 * _MINUTE_IN_SECONDS -_DEFAULT_RETRY_DEADLINE = 10.0 * _MINUTE_IN_SECONDS - -# Ambiguous errors (e.g. internalError, backendError, rateLimitExceeded) retry -# until the full `_DEFAULT_RETRY_DEADLINE`. This is because the -# `jobs.getQueryResults` REST API translates a job failure into an HTTP error. -# -# TODO(https://github.com/googleapis/python-bigquery/issues/1903): Investigate -# if we can fail early for ambiguous errors in `QueryJob.result()`'s call to -# the `jobs.getQueryResult` API. -# -# We need `_DEFAULT_JOB_DEADLINE` to be some multiple of -# `_DEFAULT_RETRY_DEADLINE` to allow for a few retries after the retry -# timeout is reached. -# -# Note: This multiple should actually be a multiple of -# (2 * _DEFAULT_RETRY_DEADLINE). After an ambiguous exception, the first -# call from `job_retry()` refreshes the job state without actually restarting -# the query. The second `job_retry()` actually restarts the query. For a more -# detailed explanation, see the comments where we set `restart_query_job = True` -# in `QueryJob.result()`'s inner `is_job_done()` function. -_DEFAULT_JOB_DEADLINE = 2.0 * (2.0 * _DEFAULT_RETRY_DEADLINE) - - -def _should_retry(exc): - """Predicate for determining when to retry. - - We retry if and only if the 'reason' is 'backendError' - or 'rateLimitExceeded'. - """ - if not hasattr(exc, "errors") or len(exc.errors) == 0: - # Check for unstructured error returns, e.g. from GFE - return isinstance(exc, _UNSTRUCTURED_RETRYABLE_TYPES) - - reason = exc.errors[0]["reason"] - return reason in _RETRYABLE_REASONS - - -DEFAULT_RETRY = retry.Retry(predicate=_should_retry, deadline=_DEFAULT_RETRY_DEADLINE) -"""The default retry object. - -Any method with a ``retry`` parameter will be retried automatically, -with reasonable defaults. To disable retry, pass ``retry=None``. -To modify the default retry behavior, call a ``with_XXX`` method -on ``DEFAULT_RETRY``. For example, to change the deadline to 30 seconds, -pass ``retry=bigquery.DEFAULT_RETRY.with_deadline(30)``. -""" - - -def _should_retry_get_job_conflict(exc): - """Predicate for determining when to retry a jobs.get call after a conflict error. - - Sometimes we get a 404 after a Conflict. In this case, we - have pretty high confidence that by retrying the 404, we'll - (hopefully) eventually recover the job. - https://github.com/googleapis/python-bigquery/issues/2134 - - Note: we may be able to extend this to user-specified predicates - after https://github.com/googleapis/python-api-core/issues/796 - to tweak existing Retry object predicates. - """ - return isinstance(exc, exceptions.NotFound) or _should_retry(exc) - - -# Pick a deadline smaller than our other deadlines since we want to timeout -# before those expire. -_DEFAULT_GET_JOB_CONFLICT_DEADLINE = _DEFAULT_RETRY_DEADLINE / 3.0 -_DEFAULT_GET_JOB_CONFLICT_RETRY = retry.Retry( - predicate=_should_retry_get_job_conflict, - deadline=_DEFAULT_GET_JOB_CONFLICT_DEADLINE, -) -"""Private, may be removed in future.""" - - -# Note: Take care when updating DEFAULT_TIMEOUT to anything but None. We -# briefly had a default timeout, but even setting it at more than twice the -# theoretical server-side default timeout of 2 minutes was not enough for -# complex queries. See: -# https://github.com/googleapis/python-bigquery/issues/970#issuecomment-921934647 -DEFAULT_TIMEOUT = None -"""The default API timeout. - -This is the time to wait per request. To adjust the total wait time, set a -deadline on the retry object. -""" - -job_retry_reasons = ( - "rateLimitExceeded", - "backendError", - "internalError", - "jobBackendError", - "jobInternalError", - "jobRateLimitExceeded", -) - - -def _job_should_retry(exc): - # Sometimes we have ambiguous errors, such as 'backendError' which could - # be due to an API problem or a job problem. For these, make sure we retry - # our is_job_done() function. - # - # Note: This won't restart the job unless we know for sure it's because of - # the job status and set restart_query_job = True in that loop. This means - # that we might end up calling this predicate twice for the same job - # but from different paths: (1) from jobs.getQueryResults RetryError and - # (2) from translating the job error from the body of a jobs.get response. - # - # Note: If we start retrying job types other than queries where we don't - # call the problematic getQueryResults API to check the status, we need - # to provide a different predicate, as there shouldn't be ambiguous - # errors in those cases. - if isinstance(exc, exceptions.RetryError): - exc = exc.cause - - # Per https://github.com/googleapis/python-bigquery/issues/1929, sometimes - # retriable errors make their way here. Because of the separate - # `restart_query_job` logic to make sure we aren't restarting non-failed - # jobs, it should be safe to continue and not totally fail our attempt at - # waiting for the query to complete. - if _should_retry(exc): - return True - - if not hasattr(exc, "errors") or len(exc.errors) == 0: - return False - - reason = exc.errors[0]["reason"] - return reason in job_retry_reasons - - -DEFAULT_JOB_RETRY = retry.Retry( - predicate=_job_should_retry, deadline=_DEFAULT_JOB_DEADLINE -) -""" -The default job retry object. -""" - - -DEFAULT_ML_JOB_RETRY = retry.Retry( - predicate=_job_should_retry, deadline=_HOUR_IN_SECONDS -) -""" -The default job retry object for AI/ML jobs. - -Such jobs can take a long time to fail. See: b/436586523. -""" - - -def _query_job_insert_should_retry(exc): - # Per https://github.com/googleapis/python-bigquery/issues/2134, sometimes - # we get a 404 error. In this case, if we get this far, assume that the job - # doesn't actually exist and try again. We can't add 404 to the default - # job_retry because that happens for errors like "this table does not - # exist", which probably won't resolve with a retry. - if isinstance(exc, exceptions.RetryError): - exc = exc.cause - - if isinstance(exc, exceptions.NotFound): - message = exc.message - # Don't try to retry table/dataset not found, just job not found. - # The URL contains jobs, so use whitespace to disambiguate. - return message is not None and " job" in message.lower() - - return _job_should_retry(exc) - - -_DEFAULT_QUERY_JOB_INSERT_RETRY = retry.Retry( - predicate=_query_job_insert_should_retry, - # jobs.insert doesn't wait for the job to complete, so we don't need the - # long _DEFAULT_JOB_DEADLINE for this part. - deadline=_DEFAULT_RETRY_DEADLINE, -) -"""Private, may be removed in future.""" - - -DEFAULT_GET_JOB_TIMEOUT = 128 -""" -Default timeout for Client.get_job(). -""" - -POLLING_DEFAULT_VALUE = google.api_core.future.polling.PollingFuture._DEFAULT_VALUE -""" -Default value defined in google.api_core.future.polling.PollingFuture. -""" diff --git a/third_party/bigframes_vendored/google_cloud_bigquery/tests/unit/test_pandas_helpers.py b/third_party/bigframes_vendored/google_cloud_bigquery/tests/unit/test_pandas_helpers.py index c87444f41e2..dc4a09cc541 100644 --- a/third_party/bigframes_vendored/google_cloud_bigquery/tests/unit/test_pandas_helpers.py +++ b/third_party/bigframes_vendored/google_cloud_bigquery/tests/unit/test_pandas_helpers.py @@ -16,16 +16,16 @@ import functools import warnings +from google.cloud.bigquery import schema import pyarrow import pyarrow.parquet import pyarrow.types import pytest -from google.cloud.bigquery import schema @pytest.fixture def module_under_test(): - from bigframes_vendored.google_cloud_bigquery import _pandas_helpers + from third_party.bigframes_vendored.google_cloud_bigquery import _pandas_helpers return _pandas_helpers diff --git a/third_party/bigframes_vendored/ibis/README.md b/third_party/bigframes_vendored/ibis/README.md index fa8224214f4..8a00750e920 100644 --- a/third_party/bigframes_vendored/ibis/README.md +++ b/third_party/bigframes_vendored/ibis/README.md @@ -1,6 +1,7 @@ # Ibis [![Documentation Status](https://img.shields.io/badge/docs-docs.ibis--project.org-blue.svg)](http://ibis-project.org) +[![Anaconda-Server Badge](https://anaconda.org/conda-forge/ibis-framework/badges/version.svg)](https://anaconda.org/conda-forge/ibis-framework) [![PyPI](https://img.shields.io/pypi/v/ibis-framework.svg)](https://pypi.org/project/ibis-framework) [![Build status](https://github.com/ibis-project/ibis/actions/workflows/ibis-main.yml/badge.svg)](https://github.com/ibis-project/ibis/actions/workflows/ibis-main.yml?query=branch%3Amaster) [![Build status](https://github.com/ibis-project/ibis/actions/workflows/ibis-backends.yml/badge.svg)](https://github.com/ibis-project/ibis/actions/workflows/ibis-backends.yml?query=branch%3Amaster) @@ -82,14 +83,28 @@ Install Ibis from PyPI with: pip install 'ibis-framework[duckdb]' ``` +Or from conda-forge with: + +```bash +conda install ibis-framework -c conda-forge +``` + (It’s a common mistake to `pip install ibis`. If you try to use Ibis and get errors early on try uninstalling `ibis` and installing `ibis-framework`) +To discover ibis, we suggest starting with the DuckDB backend (which is included by default in the conda-forge package). The DuckDB backend is performant and fully featured. + To use ibis with other backends, include the backend name in brackets for PyPI: ```bash pip install 'ibis-framework[postgres]' ``` +Or use `ibis-$BACKEND` where `$BACKEND` is the specific backend you want to use when installing from conda-forge: + +```bash +conda install ibis-postgres -c conda-forge +``` + ## Getting Started with Ibis We provide a number of tutorial and example notebooks in the diff --git a/third_party/bigframes_vendored/ibis/__init__.py b/third_party/bigframes_vendored/ibis/__init__.py index 54a896da889..e69de29bb2d 100644 --- a/third_party/bigframes_vendored/ibis/__init__.py +++ b/third_party/bigframes_vendored/ibis/__init__.py @@ -1,109 +0,0 @@ -# Contains code from https://github.com/ibis-project/ibis/blob/9.2.0/ibis/__init__.py - -"""Initialize Ibis module.""" - -from __future__ import annotations - -__version__ = "9.2.0" - -import warnings -from typing import Any - -import bigframes_vendored.ibis.backends.bigquery as bigquery -from bigframes_vendored.ibis import util -from bigframes_vendored.ibis.backends import BaseBackend -from bigframes_vendored.ibis.common.exceptions import IbisError -from bigframes_vendored.ibis.config import options -from bigframes_vendored.ibis.expr import api -from bigframes_vendored.ibis.expr import types as ir -from bigframes_vendored.ibis.expr.api import * # noqa: F403 -from bigframes_vendored.ibis.expr.operations import udf - -__all__ = [ # noqa: PLE0604 - "api", - "ir", - "udf", - "util", - "BaseBackend", - "IbisError", - "options", - *api.__all__, -] - -_KNOWN_BACKENDS = ["heavyai"] - - -def load_backend(name: str) -> BaseBackend: - """Load backends in a lazy way with `ibis.`. - - This also registers the backend options. - - Examples - -------- - >>> import ibis - >>> con = ibis.sqlite.connect(...) - - When accessing the `sqlite` attribute of the `ibis` module, this function - is called, and a backend with the `sqlite` name is tried to load from - the `ibis.backends` entrypoints. If successful, the `ibis.sqlite` - attribute is "cached", so this function is only called the first time. - - """ - backend = bigquery.Backend() - # The first time a backend is loaded, we register its options, and we set - # it as an attribute of `ibis`, so `__getattr__` is not called again for it - backend.register_options() - - # We don't want to expose all the methods on an unconnected backend to the user. - # In lieu of a full redesign, we create a proxy module and add only the methods - # that are valid to call without a connect call. These are: - # - # - connect - # - compile - # - has_operation - # - _from_url - # - _to_sqlglot - # - # We also copy over the docstring from `do_connect` to the proxy `connect` - # method, since that's where all the backend-specific kwargs are currently - # documented. This is all admittedly gross, but it works and doesn't - # require a backend redesign yet. - - def connect(*args, **kwargs): - return backend.connect(*args, **kwargs) - - connect.__doc__ = backend.do_connect.__doc__ - connect.__wrapped__ = backend.do_connect - connect.__module__ = f"bigframes_vendored.ibis.{name}" - - import types - - import bigframes_vendored.ibis - - proxy = types.ModuleType(f"bigframes_vendored.ibis.{name}") - setattr(bigframes_vendored.ibis, name, proxy) - proxy.connect = connect - proxy.compile = backend.compile - proxy.has_operation = backend.has_operation - proxy.name = name - proxy._from_url = backend._from_url - proxy._to_sqlglot = backend._to_sqlglot - # Add any additional methods that should be exposed at the top level - for attr in getattr(backend, "_top_level_methods", ()): - setattr(proxy, attr, getattr(backend, attr)) - - return proxy - - -def __getattr__(name: str) -> Any: - if name == "NA": - warnings.warn( - "The 'ibis.NA' constant is deprecated as of v9.1 and will be removed in a future " - "version. Use 'ibis.null()' instead.", - DeprecationWarning, - stacklevel=2, - ) - - return null() # noqa: F405 - else: - return load_backend(name) diff --git a/third_party/bigframes_vendored/ibis/backends/__init__.py b/third_party/bigframes_vendored/ibis/backends/__init__.py index 0d0feca9d38..e69de29bb2d 100644 --- a/third_party/bigframes_vendored/ibis/backends/__init__.py +++ b/third_party/bigframes_vendored/ibis/backends/__init__.py @@ -1,1431 +0,0 @@ -# Contains code from https://github.com/ibis-project/ibis/blob/9.2.0/ibis/backends/__init__.py - -from __future__ import annotations - -import abc -import collections.abc -import functools -import importlib.metadata -import keyword -import re -import urllib.parse -from pathlib import Path -from typing import TYPE_CHECKING, Any, ClassVar - -import bigframes_vendored.ibis -import bigframes_vendored.ibis.common.exceptions as exc -import bigframes_vendored.ibis.config -import bigframes_vendored.ibis.expr.operations as ops -import bigframes_vendored.ibis.expr.types as ir -from bigframes_vendored.ibis import util -from bigframes_vendored.ibis.common.caching import RefCountedCache - -if TYPE_CHECKING: - from collections.abc import Iterable, Iterator, Mapping, MutableMapping - from urllib.parse import ParseResult - - import bigframes_vendored.sqlglot as sg - import pandas as pd - import polars as pl - import pyarrow as pa - import torch - -__all__ = ("BaseBackend", "connect") - - -class TablesAccessor(collections.abc.Mapping): - """A mapping-like object for accessing tables off a backend. - - Tables may be accessed by name using either index or attribute access: - - Examples - -------- - >>> con = ibis.sqlite.connect("example.db") - >>> people = con.tables["people"] # access via index - >>> people = con.tables.people # access via attribute - - """ - - def __init__(self, backend: BaseBackend): - self._backend = backend - - def __getitem__(self, name) -> ir.Table: - try: - return self._backend.table(name) - except Exception as exc: - raise KeyError(name) from exc - - def __getattr__(self, name) -> ir.Table: - if name.startswith("_"): - raise AttributeError(name) - try: - return self._backend.table(name) - except Exception as exc: - raise AttributeError(name) from exc - - def __iter__(self) -> Iterator[str]: - return iter(sorted(self._backend.list_tables())) - - def __len__(self) -> int: - return len(self._backend.list_tables()) - - def __dir__(self) -> list[str]: - o = set() - o.update(dir(type(self))) - o.update( - name - for name in self._backend.list_tables() - if name.isidentifier() and not keyword.iskeyword(name) - ) - return list(o) - - def __repr__(self) -> str: - tables = self._backend.list_tables() - rows = ["Tables", "------"] - rows.extend(f"- {name}" for name in sorted(tables)) - return "\n".join(rows) - - def _ipython_key_completions_(self) -> list[str]: - return self._backend.list_tables() - - -class _FileIOHandler: - @staticmethod - def _import_pyarrow(): - try: - import pyarrow # noqa: ICN001 - except ImportError: - raise ModuleNotFoundError( - "Exporting to arrow formats requires `pyarrow` but it is not installed" - ) - else: - import pyarrow_hotfix # noqa: F401 - - return pyarrow - - def to_pandas( - self, - expr: ir.Expr, - *, - params: Mapping[ir.Scalar, Any] | None = None, - limit: int | str | None = None, - **kwargs: Any, - ) -> pd.DataFrame | pd.Series | Any: - """Execute an Ibis expression and return a pandas `DataFrame`, `Series`, or scalar. - - ::: {.callout-note} - This method is a wrapper around `execute`. - ::: - - Parameters - ---------- - expr - Ibis expression to execute. - params - Mapping of scalar parameter expressions to value. - limit - An integer to effect a specific row limit. A value of `None` means - "no limit". The default is in `ibis/config.py`. - kwargs - Keyword arguments - - """ - return self.execute(expr, params=params, limit=limit, **kwargs) - - def to_pandas_batches( - self, - expr: ir.Expr, - *, - params: Mapping[ir.Scalar, Any] | None = None, - limit: int | str | None = None, - chunk_size: int = 1_000_000, - **kwargs: Any, - ) -> Iterator[pd.DataFrame | pd.Series | Any]: - """Execute an Ibis expression and return an iterator of pandas `DataFrame`s. - - Parameters - ---------- - expr - Ibis expression to execute. - params - Mapping of scalar parameter expressions to value. - limit - An integer to effect a specific row limit. A value of `None` means - "no limit". The default is in `ibis/config.py`. - chunk_size - Maximum number of rows in each returned `DataFrame` batch. This may have - no effect depending on the backend. - kwargs - Keyword arguments - - Returns - ------- - Iterator[pd.DataFrame] - An iterator of pandas `DataFrame`s. - - """ - from bigframes_vendored.ibis.formats.pandas import PandasData - - orig_expr = expr - expr = expr.as_table() - schema = expr.schema() - yield from ( - orig_expr.__pandas_result__( - PandasData.convert_table(batch.to_pandas(), schema) - ) - for batch in self.to_pyarrow_batches( - expr, params=params, limit=limit, chunk_size=chunk_size, **kwargs - ) - ) - - @util.experimental - def to_pyarrow( - self, - expr: ir.Expr, - *, - params: Mapping[ir.Scalar, Any] | None = None, - limit: int | str | None = None, - **kwargs: Any, - ) -> pa.Table: - """Execute expression and return results in as a pyarrow table. - - This method is eager and will execute the associated expression - immediately. - - Parameters - ---------- - expr - Ibis expression to export to pyarrow - params - Mapping of scalar parameter expressions to value. - limit - An integer to effect a specific row limit. A value of `None` means - "no limit". The default is in `ibis/config.py`. - kwargs - Keyword arguments - - Returns - ------- - Table - A pyarrow table holding the results of the executed expression. - - """ - pa = self._import_pyarrow() - self._run_pre_execute_hooks(expr) - - table_expr = expr.as_table() - schema = table_expr.schema() - arrow_schema = schema.to_pyarrow() - with self.to_pyarrow_batches( - table_expr, params=params, limit=limit, **kwargs - ) as reader: - table = pa.Table.from_batches(reader, schema=arrow_schema) - - return expr.__pyarrow_result__( - table.rename_columns(table_expr.columns).cast(arrow_schema) - ) - - @util.experimental - def to_polars( - self, - expr: ir.Expr, - *, - params: Mapping[ir.Scalar, Any] | None = None, - limit: int | str | None = None, - **kwargs: Any, - ) -> pl.DataFrame: - """Execute expression and return results in as a polars DataFrame. - - This method is eager and will execute the associated expression - immediately. - - Parameters - ---------- - expr - Ibis expression to export to polars. - params - Mapping of scalar parameter expressions to value. - limit - An integer to effect a specific row limit. A value of `None` means - "no limit". The default is in `ibis/config.py`. - kwargs - Keyword arguments - - Returns - ------- - dataframe - A polars DataFrame holding the results of the executed expression. - - """ - import polars as pl - - table = self.to_pyarrow(expr.as_table(), params=params, limit=limit, **kwargs) - return expr.__polars_result__(pl.from_arrow(table)) - - @util.experimental - def to_pyarrow_batches( - self, - expr: ir.Expr, - *, - params: Mapping[ir.Scalar, Any] | None = None, - limit: int | str | None = None, - chunk_size: int = 1_000_000, - **kwargs: Any, - ) -> pa.ipc.RecordBatchReader: - """Execute expression and return a RecordBatchReader. - - This method is eager and will execute the associated expression - immediately. - - Parameters - ---------- - expr - Ibis expression to export to pyarrow - limit - An integer to effect a specific row limit. A value of `None` means - "no limit". The default is in `ibis/config.py`. - params - Mapping of scalar parameter expressions to value. - chunk_size - Maximum number of rows in each returned record batch. - kwargs - Keyword arguments - - Returns - ------- - results - RecordBatchReader - - """ - raise NotImplementedError - - @util.experimental - def to_torch( - self, - expr: ir.Expr, - *, - params: Mapping[ir.Scalar, Any] | None = None, - limit: int | str | None = None, - **kwargs: Any, - ) -> dict[str, torch.Tensor]: - """Execute an expression and return results as a dictionary of torch tensors. - - Parameters - ---------- - expr - Ibis expression to execute. - params - Parameters to substitute into the expression. - limit - An integer to effect a specific row limit. A value of `None` means no limit. - kwargs - Keyword arguments passed into the backend's `to_torch` implementation. - - Returns - ------- - dict[str, torch.Tensor] - A dictionary of torch tensors, keyed by column name. - - """ - import torch - - t = self.to_pyarrow(expr, params=params, limit=limit, **kwargs) - # without .copy() the arrays are read-only and thus writing to them is - # undefined behavior; we can't ignore this warning from torch because - # we're going out of ibis and downstream code can do whatever it wants - # with the data - return { - name: torch.from_numpy(t[name].to_numpy().copy()) for name in t.schema.names - } - - def read_parquet( - self, path: str | Path, table_name: str | None = None, **kwargs: Any - ) -> ir.Table: - """Register a parquet file as a table in the current backend. - - Parameters - ---------- - path - The data source. - table_name - An optional name to use for the created table. This defaults to - a sequentially generated name. - **kwargs - Additional keyword arguments passed to the backend loading function. - - Returns - ------- - ir.Table - The just-registered table - - """ - raise NotImplementedError( - f"{self.name} does not support direct registration of parquet data." - ) - - def read_csv( - self, path: str | Path, table_name: str | None = None, **kwargs: Any - ) -> ir.Table: - """Register a CSV file as a table in the current backend. - - Parameters - ---------- - path - The data source. A string or Path to the CSV file. - table_name - An optional name to use for the created table. This defaults to - a sequentially generated name. - **kwargs - Additional keyword arguments passed to the backend loading function. - - Returns - ------- - ir.Table - The just-registered table - - """ - raise NotImplementedError( - f"{self.name} does not support direct registration of CSV data." - ) - - def read_json( - self, path: str | Path, table_name: str | None = None, **kwargs: Any - ) -> ir.Table: - """Register a JSON file as a table in the current backend. - - Parameters - ---------- - path - The data source. A string or Path to the JSON file. - table_name - An optional name to use for the created table. This defaults to - a sequentially generated name. - **kwargs - Additional keyword arguments passed to the backend loading function. - - Returns - ------- - ir.Table - The just-registered table - - """ - raise NotImplementedError( - f"{self.name} does not support direct registration of JSON data." - ) - - def read_delta( - self, source: str | Path, table_name: str | None = None, **kwargs: Any - ): - """Register a Delta Lake table in the current database. - - Parameters - ---------- - source - The data source. Must be a directory - containing a Delta Lake table. - table_name - An optional name to use for the created table. This defaults to - a sequentially generated name. - **kwargs - Additional keyword arguments passed to the underlying backend or library. - - Returns - ------- - ir.Table - The just-registered table. - - """ - raise NotImplementedError( - f"{self.name} does not support direct registration of DeltaLake tables." - ) - - @util.experimental - def to_parquet( - self, - expr: ir.Table, - path: str | Path, - *, - params: Mapping[ir.Scalar, Any] | None = None, - **kwargs: Any, - ) -> None: - """Write the results of executing the given expression to a parquet file. - - This method is eager and will execute the associated expression - immediately. - - Parameters - ---------- - expr - The ibis expression to execute and persist to parquet. - path - The data source. A string or Path to the parquet file. - params - Mapping of scalar parameter expressions to value. - **kwargs - Additional keyword arguments passed to pyarrow.parquet.ParquetWriter - - https://arrow.apache.org/docs/python/generated/pyarrow.parquet.ParquetWriter.html - - """ - self._import_pyarrow() - import pyarrow.parquet as pq - - with expr.to_pyarrow_batches(params=params) as batch_reader: - with pq.ParquetWriter(path, batch_reader.schema, **kwargs) as writer: - for batch in batch_reader: - writer.write_batch(batch) - - @util.experimental - def to_csv( - self, - expr: ir.Table, - path: str | Path, - *, - params: Mapping[ir.Scalar, Any] | None = None, - **kwargs: Any, - ) -> None: - """Write the results of executing the given expression to a CSV file. - - This method is eager and will execute the associated expression - immediately. - - Parameters - ---------- - expr - The ibis expression to execute and persist to CSV. - path - The data source. A string or Path to the CSV file. - params - Mapping of scalar parameter expressions to value. - kwargs - Additional keyword arguments passed to pyarrow.csv.CSVWriter - - https://arrow.apache.org/docs/python/generated/pyarrow.csv.CSVWriter.html - - """ - self._import_pyarrow() - import pyarrow.csv as pcsv - - with expr.to_pyarrow_batches(params=params) as batch_reader: - with pcsv.CSVWriter(path, batch_reader.schema, **kwargs) as writer: - for batch in batch_reader: - writer.write_batch(batch) - - @util.experimental - def to_delta( - self, - expr: ir.Table, - path: str | Path, - *, - params: Mapping[ir.Scalar, Any] | None = None, - **kwargs: Any, - ) -> None: - """Write the results of executing the given expression to a Delta Lake table. - - This method is eager and will execute the associated expression - immediately. - - Parameters - ---------- - expr - The ibis expression to execute and persist to Delta Lake table. - path - The data source. A string or Path to the Delta Lake table. - params - Mapping of scalar parameter expressions to value. - kwargs - Additional keyword arguments passed to deltalake.writer.write_deltalake method - - """ - try: - from deltalake.writer import write_deltalake - except ImportError: - raise ImportError( - "The deltalake extra is required to use the " - "to_delta method. You can install it using pip:\n\n" - "pip install 'ibis-framework[deltalake]'\n" - ) - - with expr.to_pyarrow_batches(params=params) as batch_reader: - write_deltalake(path, batch_reader, **kwargs) - - -class CanListCatalog(abc.ABC): - @abc.abstractmethod - def list_catalogs(self, like: str | None = None) -> list[str]: - """List existing catalogs in the current connection. - - ::: {.callout-note} - ## Ibis does not use the word `schema` to refer to database hierarchy. - - A collection of `table` is referred to as a `database`. - A collection of `database` is referred to as a `catalog`. - - These terms are mapped onto the corresponding features in each - backend (where available), regardless of whether the backend itself - uses the same terminology. - ::: - - Parameters - ---------- - like - A pattern in Python's regex format to filter returned database - names. - - Returns - ------- - list[str] - The catalog names that exist in the current connection, that match - the `like` pattern if provided. - - """ - - @property - @abc.abstractmethod - def current_catalog(self) -> str: - """The current catalog in use.""" - - -class CanCreateCatalog(CanListCatalog): - @abc.abstractmethod - def create_catalog(self, name: str, force: bool = False) -> None: - """Create a new catalog. - - ::: {.callout-note} - ## Ibis does not use the word `schema` to refer to database hierarchy. - - A collection of `table` is referred to as a `database`. - A collection of `database` is referred to as a `catalog`. - - These terms are mapped onto the corresponding features in each - backend (where available), regardless of whether the backend itself - uses the same terminology. - ::: - - Parameters - ---------- - name - Name of the new catalog. - force - If `False`, an exception is raised if the catalog already exists. - - """ - - @abc.abstractmethod - def drop_catalog(self, name: str, force: bool = False) -> None: - """Drop a catalog with name `name`. - - ::: {.callout-note} - ## Ibis does not use the word `schema` to refer to database hierarchy. - - A collection of `table` is referred to as a `database`. - A collection of `database` is referred to as a `catalog`. - - These terms are mapped onto the corresponding features in each - backend (where available), regardless of whether the backend itself - uses the same terminology. - ::: - - Parameters - ---------- - name - Catalog to drop. - force - If `False`, an exception is raised if the catalog does not exist. - - """ - - -class CanListDatabase(abc.ABC): - @abc.abstractmethod - def list_databases( - self, like: str | None = None, catalog: str | None = None - ) -> list[str]: - """List existing databases in the current connection. - - ::: {.callout-note} - ## Ibis does not use the word `schema` to refer to database hierarchy. - - A collection of `table` is referred to as a `database`. - A collection of `database` is referred to as a `catalog`. - - These terms are mapped onto the corresponding features in each - backend (where available), regardless of whether the backend itself - uses the same terminology. - ::: - - Parameters - ---------- - like - A pattern in Python's regex format to filter returned database - names. - catalog - The catalog to list databases from. If `None`, the current catalog - is searched. - - Returns - ------- - list[str] - The database names that exist in the current connection, that match - the `like` pattern if provided. - - """ - - @property - @abc.abstractmethod - def current_database(self) -> str: - """The current database in use.""" - - -class CanCreateDatabase(CanListDatabase): - @abc.abstractmethod - def create_database( - self, name: str, catalog: str | None = None, force: bool = False - ) -> None: - """Create a database named `name` in `catalog`. - - Parameters - ---------- - name - Name of the database to create. - catalog - Name of the catalog in which to create the database. If `None`, the - current catalog is used. - force - If `False`, an exception is raised if the database exists. - - """ - - @abc.abstractmethod - def drop_database( - self, name: str, catalog: str | None = None, force: bool = False - ) -> None: - """Drop the database with `name` in `catalog`. - - Parameters - ---------- - name - Name of the schema to drop. - catalog - Name of the catalog to drop the database from. If `None`, the - current catalog is used. - force - If `False`, an exception is raised if the database does not exist. - - """ - - -# TODO: remove this for 10.0 -class CanListSchema: - @util.deprecated( - instead="Use `list_databases` instead`", as_of="9.0", removed_in="10.0" - ) - def list_schemas( - self, like: str | None = None, database: str | None = None - ) -> list[str]: - return self.list_databases(like=like, catalog=database) - - @property - @util.deprecated( - instead="Use `Backend.current_database` instead.", - as_of="9.0", - removed_in="10.0", - ) - def current_schema(self) -> str: - return self.current_database - - -class CanCreateSchema(CanListSchema): - @util.deprecated( - instead="Use `create_database` instead", as_of="9.0", removed_in="10.0" - ) - def create_schema( - self, name: str, database: str | None = None, force: bool = False - ) -> None: - self.create_database(name=name, catalog=database, force=force) - - @util.deprecated( - instead="Use `drop_database` instead", as_of="9.0", removed_in="10.0" - ) - def drop_schema( - self, name: str, database: str | None = None, force: bool = False - ) -> None: - self.drop_database(name=name, catalog=database, force=force) - - -class BaseBackend(abc.ABC, _FileIOHandler): - """Base backend class. - - All Ibis backends must subclass this class and implement all the - required methods. - """ - - name: ClassVar[str] - - supports_temporary_tables = False - supports_python_udfs = False - supports_in_memory_tables = True - - def __init__(self, *args, **kwargs): - self._con_args: tuple[Any] = args - self._con_kwargs: dict[str, Any] = kwargs - self._can_reconnect: bool = True - # expression cache - self._query_cache = RefCountedCache( - populate=self._load_into_cache, - lookup=lambda name: self.table(name).op(), - finalize=self._clean_up_cached_table, - ) - - @property - @abc.abstractmethod - def dialect(self) -> sg.Dialect | None: - """The sqlglot dialect for this backend, where applicable. - - Returns None if the backend is not a SQL backend. - """ - - def __getstate__(self): - return dict(_con_args=self._con_args, _con_kwargs=self._con_kwargs) - - def __rich_repr__(self): - yield "name", self.name - - def __hash__(self): - return hash(self.db_identity) - - def __eq__(self, other): - return self.db_identity == other.db_identity - - @functools.cached_property - def db_identity(self) -> str: - """Return the identity of the database. - - Multiple connections to the same - database will return the same value for `db_identity`. - - The default implementation assumes connection parameters uniquely - specify the database. - - Returns - ------- - Hashable - Database identity - - """ - parts = [self.__class__] - parts.extend(self._con_args) - parts.extend(f"{k}={v}" for k, v in self._con_kwargs.items()) - return "_".join(map(str, parts)) - - # TODO(kszucs): this should be a classmethod returning with a new backend - # instance which does instantiate the connection - def connect(self, *args, **kwargs) -> BaseBackend: - """Connect to the database. - - Parameters - ---------- - *args - Mandatory connection parameters, see the docstring of `do_connect` - for details. - **kwargs - Extra connection parameters, see the docstring of `do_connect` for - details. - - Notes - ----- - This creates a new backend instance with saved `args` and `kwargs`, - then calls `reconnect` and finally returns the newly created and - connected backend instance. - - Returns - ------- - BaseBackend - An instance of the backend - - """ - new_backend = self.__class__(*args, **kwargs) - new_backend.reconnect() - return new_backend - - @abc.abstractmethod - def disconnect(self) -> None: - """Close the connection to the backend.""" - - @staticmethod - def _convert_kwargs(kwargs: MutableMapping) -> None: - """Manipulate keyword arguments to `.connect` method.""" - - # TODO(kszucs): should call self.connect(*self._con_args, **self._con_kwargs) - def reconnect(self) -> None: - """Reconnect to the database already configured with connect.""" - if self._can_reconnect: - self.do_connect(*self._con_args, **self._con_kwargs) - else: - raise exc.IbisError("Cannot reconnect to unconfigured {self.name} backend") - - def do_connect(self, *args, **kwargs) -> None: - """Connect to database specified by `args` and `kwargs`.""" - - @staticmethod - def _filter_with_like(values: Iterable[str], like: str | None = None) -> list[str]: - """Filter names with a `like` pattern (regex). - - The methods `list_databases` and `list_tables` accept a `like` - argument, which filters the returned tables with tables that match the - provided pattern. - - We provide this method in the base backend, so backends can use it - instead of reinventing the wheel. - - Parameters - ---------- - values - Iterable of strings to filter - like - Pattern to use for filtering names - - Returns - ------- - list[str] - Names filtered by the `like` pattern. - - """ - if like is None: - return sorted(values) - - pattern = re.compile(like) - return sorted(filter(pattern.findall, values)) - - @abc.abstractmethod - def list_tables( - self, like: str | None = None, database: tuple[str, str] | str | None = None - ) -> list[str]: - """Return the list of table names in the current database. - - For some backends, the tables may be files in a directory, - or other equivalent entities in a SQL database. - - ::: {.callout-note} - ## Ibis does not use the word `schema` to refer to database hierarchy. - - A collection of tables is referred to as a `database`. - A collection of `database` is referred to as a `catalog`. - - These terms are mapped onto the corresponding features in each - backend (where available), regardless of whether the backend itself - uses the same terminology. - ::: - - Parameters - ---------- - like - A pattern in Python's regex format. - database - The database from which to list tables. - If not provided, the current database is used. - For backends that support multi-level table hierarchies, you can - pass in a dotted string path like `"catalog.database"` or a tuple of - strings like `("catalog", "database")`. - - Returns - ------- - list[str] - The list of the table names that match the pattern `like`. - - """ - - @abc.abstractmethod - def table( - self, name: str, database: tuple[str, str] | str | None = None - ) -> ir.Table: - """Construct a table expression. - - ::: {.callout-note} - ## Ibis does not use the word `schema` to refer to database hierarchy. - - A collection of tables is referred to as a `database`. - A collection of `database` is referred to as a `catalog`. - - These terms are mapped onto the corresponding features in each - backend (where available), regardless of whether the backend itself - uses the same terminology. - ::: - - Parameters - ---------- - name - Table name - database - Database name - If not provided, the current database is used. - For backends that support multi-level table hierarchies, you can - pass in a dotted string path like `"catalog.database"` or a tuple of - strings like `("catalog", "database")`. - - Returns - ------- - Table - Table expression - - """ - - @functools.cached_property - def tables(self): - """An accessor for tables in the database. - - Tables may be accessed by name using either index or attribute access: - - Examples - -------- - >>> con = ibis.sqlite.connect("example.db") - >>> people = con.tables["people"] # access via index - >>> people = con.tables.people # access via attribute - - """ - return TablesAccessor(self) - - @property - @abc.abstractmethod - def version(self) -> str: - """Return the version of the backend engine. - - For database servers, return the server version. - - For others such as SQLite and pandas return the version of the - underlying library or application. - - Returns - ------- - str - The backend version - - """ - - @classmethod - def register_options(cls) -> None: - """Register custom backend options.""" - options = bigframes_vendored.ibis.config.options - backend_name = cls.name - try: - backend_options = cls.Options() - except AttributeError: - pass - else: - try: - setattr(options, backend_name, backend_options) - except ValueError as e: - raise exc.BackendConfigurationNotRegistered(backend_name) from e - - def _register_udfs(self, expr: ir.Expr) -> None: - """Register UDFs contained in `expr` with the backend.""" - if self.supports_python_udfs: - raise NotImplementedError(self.name) - - def _register_in_memory_tables(self, expr: ir.Expr) -> None: - for memtable in expr.op().find(ops.InMemoryTable): - self._register_in_memory_table(memtable) - - def _register_in_memory_table(self, op: ops.InMemoryTable): - if self.supports_in_memory_tables: - raise NotImplementedError( - f"{self.name} must implement `_register_in_memory_table` to support in-memory tables" - ) - - def _run_pre_execute_hooks(self, expr: ir.Expr) -> None: - """Backend-specific hooks to run before an expression is executed.""" - self._define_udf_translation_rules(expr) - self._register_udfs(expr) - self._register_in_memory_tables(expr) - - def _define_udf_translation_rules(self, expr: ir.Expr): - if self.supports_python_udfs: - raise NotImplementedError(self.name) - - def compile( - self, - expr: ir.Expr, - params: Mapping[ir.Expr, Any] | None = None, - ) -> Any: - """Compile an expression.""" - return self.compiler.to_sql(expr, params=params) - - def _to_sqlglot(self, expr: ir.Expr, **kwargs) -> sg.exp.Expression: - """Convert an Ibis expression to a sqlglot expression. - - Called by `ibis.to_sql`; gives the backend an opportunity to generate - nicer SQL for human consumption. - """ - raise NotImplementedError(f"Backend '{self.name}' backend doesn't support SQL") - - def execute(self, expr: ir.Expr) -> Any: - """Execute an expression.""" - - @abc.abstractmethod - def create_table( - self, - name: str, - obj: pd.DataFrame | pa.Table | ir.Table | None = None, - *, - schema: bigframes_vendored.ibis.Schema | None = None, - database: str | None = None, - temp: bool = False, - overwrite: bool = False, - ) -> ir.Table: - """Create a new table. - - Parameters - ---------- - name - Name of the new table. - obj - An Ibis table expression or pandas table that will be used to - extract the schema and the data of the new table. If not provided, - `schema` must be given. - schema - The schema for the new table. Only one of `schema` or `obj` can be - provided. - database - Name of the database where the table will be created, if not the - default. - temp - Whether a table is temporary or not - overwrite - Whether to clobber existing data - - Returns - ------- - Table - The table that was created. - - """ - - @abc.abstractmethod - def drop_table( - self, - name: str, - *, - database: str | None = None, - force: bool = False, - ) -> None: - """Drop a table. - - Parameters - ---------- - name - Name of the table to drop. - database - Name of the database where the table exists, if not the default. - force - If `False`, an exception is raised if the table does not exist. - - """ - raise NotImplementedError( - f'Backend "{self.name}" does not implement "drop_table"' - ) - - def rename_table(self, old_name: str, new_name: str) -> None: - """Rename an existing table. - - Parameters - ---------- - old_name - The old name of the table. - new_name - The new name of the table. - - """ - raise NotImplementedError( - f'Backend "{self.name}" does not implement "rename_table"' - ) - - @abc.abstractmethod - def create_view( - self, - name: str, - obj: ir.Table, - *, - database: str | None = None, - overwrite: bool = False, - ) -> ir.Table: - """Create a new view from an expression. - - Parameters - ---------- - name - Name of the new view. - obj - An Ibis table expression that will be used to create the view. - database - Name of the database where the view will be created, if not - provided the database's default is used. - overwrite - Whether to clobber an existing view with the same name - - Returns - ------- - Table - The view that was created. - - """ - - @abc.abstractmethod - def drop_view( - self, name: str, *, database: str | None = None, force: bool = False - ) -> None: - """Drop a view. - - Parameters - ---------- - name - Name of the view to drop. - database - Name of the database where the view exists, if not the default. - force - If `False`, an exception is raised if the view does not exist. - - """ - - @classmethod - def has_operation(cls, operation: type[ops.Value]) -> bool: - """Return whether the backend implements support for `operation`. - - Parameters - ---------- - operation - A class corresponding to an operation. - - Returns - ------- - bool - Whether the backend implements the operation. - - Examples - -------- - >>> import ibis - >>> import ibis.expr.operations as ops - >>> ibis.sqlite.has_operation(ops.ArrayIndex) - False - >>> ibis.postgres.has_operation(ops.ArrayIndex) - True - - """ - raise NotImplementedError( - f"{cls.name} backend has not implemented `has_operation` API" - ) - - def _cached(self, expr: ir.Table): - """Cache the provided expression. - - All subsequent operations on the returned expression will be performed on the cached data. - - Parameters - ---------- - expr - Table expression to cache - - Returns - ------- - Expr - Cached table - - """ - op = expr.op() - if (result := self._query_cache.get(op)) is None: - result = self._query_cache.store(expr) - return ir.CachedTable(result) - - def _release_cached(self, expr: ir.CachedTable) -> None: - """Releases the provided cached expression. - - Parameters - ---------- - expr - Cached expression to release - - """ - self._query_cache.release(expr.op().name) - - def _load_into_cache(self, name, expr): - raise NotImplementedError(self.name) - - def _clean_up_cached_table(self, name): - raise NotImplementedError(self.name) - - def _transpile_sql(self, query: str, *, dialect: str | None = None) -> str: - # only transpile if dialect was passed - if dialect is None: - return query - - import bigframes_vendored.sqlglot as sg - - # only transpile if the backend dialect doesn't match the input dialect - name = self.name - if (output_dialect := self.dialect) is None: - raise NotImplementedError(f"No known sqlglot dialect for backend {name}") - - if dialect != output_dialect: - (query,) = sg.transpile(query, read=dialect, write=output_dialect) - return query - - -@functools.cache -def _get_backend_names(*, exclude: tuple[str] = ()) -> frozenset[str]: - """Return the set of known backend names. - - Parameters - ---------- - exclude - Exclude these backend names from the result - - Notes - ----- - This function returns a frozenset to prevent cache pollution. - - If a `set` is used, then any in-place modifications to the set - are visible to every caller of this function. - - """ - - entrypoints = importlib.metadata.entry_points(group="ibis.backends") - return frozenset(ep.name for ep in entrypoints).difference(exclude) - - -def connect(resource: Path | str, **kwargs: Any) -> BaseBackend: - """Connect to `resource`, inferring the backend automatically. - - The general pattern for `ibis.connect` is - - ```python - con = ibis.connect("backend://connection-parameters") - ``` - - With many backends that looks like - - ```python - con = ibis.connect("backend://user:password@host:port/database") - ``` - - See the connection syntax for each backend for details about URL connection - requirements. - - Parameters - ---------- - resource - A URL or path to the resource to be connected to. - kwargs - Backend specific keyword arguments - - Examples - -------- - Connect to an in-memory DuckDB database: - - >>> import ibis - >>> con = ibis.connect("duckdb://") - - Connect to an on-disk SQLite database: - - >>> con = ibis.connect("sqlite://relative.db") - >>> con = ibis.connect( - ... "sqlite:///absolute/path/to/data.db" - ... ) # quartodoc: +SKIP # doctest: +SKIP - - Connect to a PostgreSQL server: - - >>> con = ibis.connect( - ... "postgres://user:password@hostname:5432" - ... ) # quartodoc: +SKIP # doctest: +SKIP - - Connect to BigQuery: - - >>> con = ibis.connect( - ... "bigquery://my-project/my-dataset" - ... ) # quartodoc: +SKIP # doctest: +SKIP - - """ - url = resource = str(resource) - - if re.match("[A-Za-z]:", url): - # windows path with drive, treat it as a file - url = f"file://{url}" - - parsed = urllib.parse.urlparse(url) - scheme = parsed.scheme or "file" - - kwargs = dict(urllib.parse.parse_qsl(parsed.query)) - - # convert single parameter lists value to single values - for name, value in kwargs.items(): - if len(value) == 1: - kwargs[name] = value[0] - - if scheme == "file": - raise ValueError(f"Don't know how to connect to {resource!r}") - - # Treat `postgres://` and `postgresql://` the same - scheme = scheme.replace("postgresql", "postgres") - - try: - backend = getattr(bigframes_vendored.ibis, scheme) - except AttributeError: - raise ValueError(f"Don't know how to connect to {resource!r}") from None - - return backend._from_url(parsed, **kwargs) - - -class UrlFromPath: - __slots__ = () - - def _from_url(self, url: ParseResult, **kwargs: Any) -> BaseBackend: - """Connect to a backend using a URL `url`. - - Parameters - ---------- - url - URL with which to connect to a backend. - kwargs - Additional keyword arguments - - Returns - ------- - BaseBackend - A backend instance - - """ - netloc = url.netloc - parts = list(filter(None, (netloc, url.path[bool(netloc) :]))) - database = Path(*parts) if parts and parts != [":memory:"] else ":memory:" - if (strdatabase := str(database)).startswith("md:") or strdatabase.startswith( - "motherduck:" - ): - database = strdatabase - elif isinstance(database, Path): - database = database.absolute() - - self._convert_kwargs(kwargs) - return self.connect(database=database, **kwargs) - - -class NoUrl: - __slots__ = () - - name: str - - def _from_url(self, url: ParseResult, **kwargs) -> BaseBackend: - """Connect to the backend with empty url. - - Parameters - ---------- - url : str - The URL with which to connect to the backend. This parameter is not used - in this method but is kept for consistency. - kwargs - Additional keyword arguments. - - Returns - ------- - BaseBackend - A backend instance - - """ - return self.connect(**kwargs) diff --git a/third_party/bigframes_vendored/ibis/backends/bigquery/__init__.py b/third_party/bigframes_vendored/ibis/backends/bigquery/__init__.py index 5a84a6a80fd..e69de29bb2d 100644 --- a/third_party/bigframes_vendored/ibis/backends/bigquery/__init__.py +++ b/third_party/bigframes_vendored/ibis/backends/bigquery/__init__.py @@ -1,1294 +0,0 @@ -# Contains code from https://github.com/ibis-project/ibis/blob/9.2.0/ibis/backends/bigquery/__init__.py - -"""BigQuery public API.""" - -from __future__ import annotations - -import concurrent.futures -import contextlib -import glob -import os -import re -from typing import TYPE_CHECKING, Any, Optional - -import bigframes_vendored.ibis -import bigframes_vendored.ibis.common.exceptions as com -import bigframes_vendored.ibis.expr.datatypes as ibis_dtypes -import bigframes_vendored.ibis.expr.operations as ops -import bigframes_vendored.ibis.expr.schema as sch -import bigframes_vendored.ibis.expr.types as ir -import bigframes_vendored.sqlglot as sg -import bigframes_vendored.sqlglot.expressions as sge -import google.api_core.exceptions -import google.auth.credentials -import google.cloud.bigquery as bq -import google.cloud.bigquery_storage_v1 as bqstorage -import pydata_google_auth -from bigframes_vendored.ibis import util -from bigframes_vendored.ibis.backends import CanCreateDatabase, CanCreateSchema -from bigframes_vendored.ibis.backends.bigquery.client import ( - bigquery_param, - parse_project_and_dataset, - rename_partitioned_column, - schema_from_bigquery_table, -) -from bigframes_vendored.ibis.backends.bigquery.datatypes import BigQuerySchema -from bigframes_vendored.ibis.backends.sql import SQLBackend -from bigframes_vendored.ibis.backends.sql.compilers import BigQueryCompiler -from bigframes_vendored.ibis.backends.sql.datatypes import BigQueryType -from pydata_google_auth import cache - -if TYPE_CHECKING: - from collections.abc import Iterable, Mapping - from pathlib import Path - from urllib.parse import ParseResult - - import pandas as pd - import polars as pl - import pyarrow as pa - - -SCOPES = ["https://www.googleapis.com/auth/bigquery"] -EXTERNAL_DATA_SCOPES = [ - "https://www.googleapis.com/auth/bigquery", - "https://www.googleapis.com/auth/cloud-platform", - "https://www.googleapis.com/auth/drive", -] -CLIENT_ID = "546535678771-gvffde27nd83kfl6qbrnletqvkdmsese.apps.googleusercontent.com" -CLIENT_SECRET = "iU5ohAF2qcqrujegE3hQ1cPt" # noqa: S105 - - -def _create_user_agent(application_name: str) -> str: - user_agent = [] - - if application_name: - user_agent.append(application_name) - - user_agent_default_template = f"ibis/{bigframes_vendored.ibis.__version__}" - user_agent.append(user_agent_default_template) - - return " ".join(user_agent) - - -def _create_client_info(application_name): - from google.api_core.client_info import ClientInfo - - return ClientInfo(user_agent=_create_user_agent(application_name)) - - -def _create_client_info_gapic(application_name): - from google.api_core.gapic_v1.client_info import ClientInfo - - return ClientInfo(user_agent=_create_user_agent(application_name)) - - -_MEMTABLE_PATTERN = re.compile( - r"^_?ibis_(?:[A-Za-z_][A-Za-z_0-9]*)_memtable_[a-z0-9]{26}$" -) - - -def _qualify_memtable( - node: sge.Expression, *, dataset: str | None, project: str | None -) -> sge.Expression: - """Add a BigQuery dataset and project to memtable references.""" - if isinstance(node, sge.Table) and _MEMTABLE_PATTERN.match(node.name) is not None: - node.args["db"] = dataset - node.args["catalog"] = project - # make sure to quote table location - node = _force_quote_table(node) - return node - - -def _remove_null_ordering_from_unsupported_window( - node: sge.Expression, -) -> sge.Expression: - """Remove null ordering in window frame clauses not supported by BigQuery. - - BigQuery has only partial support for NULL FIRST/LAST in RANGE windows so - we remove it from any window frame clause that doesn't support it. - - Here's the support matrix: - - ✅ sum(x) over (order by y desc nulls last) - 🚫 sum(x) over (order by y asc nulls last) - ✅ sum(x) over (order by y asc nulls first) - 🚫 sum(x) over (order by y desc nulls first) - """ - if isinstance(node, sge.Window): - order = node.args.get("order") - if order is not None: - for key in order.args["expressions"]: - kargs = key.args - if kargs.get("desc") is True and kargs.get("nulls_first", False): - kargs["nulls_first"] = False - elif kargs.get("desc") is False and not kargs.setdefault( - "nulls_first", True - ): - kargs["nulls_first"] = True - return node - - -def _force_quote_table(table: sge.Table) -> sge.Table: - """Force quote all the parts of a bigquery path. - - The BigQuery identifier quoting semantics are bonkers - https://cloud.google.com/bigquery/docs/reference/standard-sql/lexical#identifiers - - my-table is OK, but not mydataset.my-table - - mytable-287 is OK, but not mytable-287a - - Just quote everything. - """ - for key in ("this", "db", "catalog"): - if (val := table.args[key]) is not None: - if isinstance(val, sg.exp.Identifier) and not val.quoted: - val.args["quoted"] = True - else: - table.args[key] = sg.to_identifier(val, quoted=True) - return table - - -class Backend(SQLBackend, CanCreateDatabase, CanCreateSchema): - name = "bigquery" - compiler = BigQueryCompiler() - supports_in_memory_tables = True - supports_python_udfs = False - - def __init__(self, *args, **kwargs) -> None: - super().__init__(*args, **kwargs) - self.__session_dataset: bq.DatasetReference | None = None - self._query_cache.lookup = lambda name: self.table( - name, - database=(self._session_dataset.project, self._session_dataset.dataset_id), - ).op() - - @property - def _session_dataset(self): - if self.__session_dataset is None: - self.__session_dataset = self._make_session() - return self.__session_dataset - - def _register_in_memory_table(self, op: ops.InMemoryTable) -> None: - raw_name = op.name - - session_dataset = self._session_dataset - project = session_dataset.project - dataset = session_dataset.dataset_id - - table_ref = bq.TableReference(session_dataset, raw_name) - try: - self.client.get_table(table_ref) - except google.api_core.exceptions.NotFound: - table_id = sg.table( - raw_name, db=dataset, catalog=project, quoted=False - ).sql(dialect=self.name) - bq_schema = BigQuerySchema.from_ibis(op.schema) - load_job = self.client.load_table_from_dataframe( - op.data.to_frame(), - table_id, - job_config=bq.LoadJobConfig( - # fail if the table already exists and contains data - write_disposition=bq.WriteDisposition.WRITE_EMPTY, - schema=bq_schema, - ), - ) - load_job.result() - - def _read_file( - self, - path: str | Path, - *, - table_name: str | None = None, - job_config: bq.LoadJobConfig, - ) -> ir.Table: - self._make_session() - - if table_name is None: - table_name = util.gen_name(f"bq_read_{job_config.source_format}") - - table_ref = self._session_dataset.table(table_name) - - database = self._session_dataset.dataset_id - catalog = self._session_dataset.project - - # drop the table if it exists - # - # we could do this with write_disposition = WRITE_TRUNCATE but then the - # concurrent append jobs aren't possible - # - # dropping the table first means all write_dispositions can be - # WRITE_APPEND - self.drop_table(table_name, database=(catalog, database), force=True) - - if os.path.isdir(path): - raise NotImplementedError("Reading from a directory is not supported.") - elif str(path).startswith("gs://"): - load_job = self.client.load_table_from_uri( - path, table_ref, job_config=job_config - ) - load_job.result() - else: - - def load(file: str) -> None: - with open(file, mode="rb") as f: - load_job = self.client.load_table_from_file( - f, table_ref, job_config=job_config - ) - load_job.result() - - job_config.write_disposition = bq.WriteDisposition.WRITE_APPEND - - with concurrent.futures.ThreadPoolExecutor() as executor: - for fut in concurrent.futures.as_completed( - executor.submit(load, file) for file in glob.glob(str(path)) - ): - fut.result() - - return self.table(table_name, database=(catalog, database)) - - def read_parquet( - self, path: str | Path, table_name: str | None = None, **kwargs: Any - ): - """Read Parquet data into a BigQuery table. - - Parameters - ---------- - path - Path to a Parquet file on GCS or the local filesystem. Globs are supported. - table_name - Optional table name - kwargs - Additional keyword arguments passed to `google.cloud.bigquery.LoadJobConfig`. - - Returns - ------- - Table - An Ibis table expression - - """ - return self._read_file( - path, - table_name=table_name, - job_config=bq.LoadJobConfig( - source_format=bq.SourceFormat.PARQUET, **kwargs - ), - ) - - def read_csv( - self, path: str | Path, table_name: str | None = None, **kwargs: Any - ) -> ir.Table: - """Read CSV data into a BigQuery table. - - Parameters - ---------- - path - Path to a CSV file on GCS or the local filesystem. Globs are supported. - table_name - Optional table name - kwargs - Additional keyword arguments passed to - `google.cloud.bigquery.LoadJobConfig`. - - Returns - ------- - Table - An Ibis table expression - - """ - job_config = bq.LoadJobConfig( - source_format=bq.SourceFormat.CSV, - autodetect=True, - skip_leading_rows=1, - **kwargs, - ) - return self._read_file(path, table_name=table_name, job_config=job_config) - - def read_json( - self, path: str | Path, table_name: str | None = None, **kwargs: Any - ) -> ir.Table: - """Read newline-delimited JSON data into a BigQuery table. - - Parameters - ---------- - path - Path to a newline-delimited JSON file on GCS or the local - filesystem. Globs are supported. - table_name - Optional table name - kwargs - Additional keyword arguments passed to - `google.cloud.bigquery.LoadJobConfig`. - - Returns - ------- - Table - An Ibis table expression - - """ - job_config = bq.LoadJobConfig( - source_format=bq.SourceFormat.NEWLINE_DELIMITED_JSON, - autodetect=True, - **kwargs, - ) - return self._read_file(path, table_name=table_name, job_config=job_config) - - def _from_url(self, url: ParseResult, **kwargs): - return self.connect( - project_id=url.netloc or kwargs.get("project_id", [""])[0], - dataset_id=url.path[1:] or kwargs.get("dataset_id", [""])[0], - **kwargs, - ) - - def do_connect( - self, - project_id: str | None = None, - dataset_id: str = "", - credentials: google.auth.credentials.Credentials | None = None, - application_name: str | None = None, - auth_local_webserver: bool = True, - auth_external_data: bool = False, - auth_cache: str = "default", - partition_column: str | None = "PARTITIONTIME", - client: bq.Client | None = None, - storage_client: bqstorage.BigQueryReadClient | None = None, - location: str | None = None, - ) -> Backend: - """Create a `Backend` for use with Ibis. - - Parameters - ---------- - project_id - A BigQuery project id. - dataset_id - A dataset id that lives inside of the project indicated by - `project_id`. - credentials - Optional credentials. - application_name - A string identifying your application to Google API endpoints. - auth_local_webserver - Use a local webserver for the user authentication. Binds a - webserver to an open port on localhost between 8080 and 8089, - inclusive, to receive authentication token. If not set, defaults to - False, which requests a token via the console. - auth_external_data - Authenticate using additional scopes required to `query external - data sources - `_, - such as Google Sheets, files in Google Cloud Storage, or files in - Google Drive. If not set, defaults to False, which requests the - default BigQuery scopes. - auth_cache - Selects the behavior of the credentials cache. - - ``'default'`` - Reads credentials from disk if available, otherwise - authenticates and caches credentials to disk. - - ``'reauth'`` - Authenticates and caches credentials to disk. - - ``'none'`` - Authenticates and does **not** cache credentials. - - Defaults to ``'default'``. - partition_column - Identifier to use instead of default ``_PARTITIONTIME`` partition - column. Defaults to ``'PARTITIONTIME'``. - client - A ``Client`` from the ``google.cloud.bigquery`` package. If not - set, one is created using the ``project_id`` and ``credentials``. - storage_client - A ``BigQueryReadClient`` from the - ``google.cloud.bigquery_storage_v1`` package. If not set, one is - created using the ``project_id`` and ``credentials``. - location - Default location for BigQuery objects. - - Returns - ------- - Backend - An instance of the BigQuery backend. - - """ - default_project_id = client.project if client is not None else project_id - - # Only need `credentials` to create a `client` and - # `storage_client`, so only one or the other needs to be set. - if (client is None or storage_client is None) and credentials is None: - scopes = SCOPES - if auth_external_data: - scopes = EXTERNAL_DATA_SCOPES - - if auth_cache == "default": - credentials_cache = cache.ReadWriteCredentialsCache( - filename="ibis.json" - ) - elif auth_cache == "reauth": - credentials_cache = cache.WriteOnlyCredentialsCache( - filename="ibis.json" - ) - elif auth_cache == "none": - credentials_cache = cache.NOOP - else: - raise ValueError( - f"Got unexpected value for auth_cache = '{auth_cache}'. " - "Expected one of 'default', 'reauth', or 'none'." - ) - - credentials, default_project_id = pydata_google_auth.default( - scopes, - client_id=CLIENT_ID, - client_secret=CLIENT_SECRET, - credentials_cache=credentials_cache, - use_local_webserver=auth_local_webserver, - ) - - project_id = project_id or default_project_id - - ( - self.data_project, - self.billing_project, - self.dataset, - ) = parse_project_and_dataset(project_id, dataset_id) - - if client is not None: - self.client = client - else: - self.client = bq.Client( - project=self.billing_project, - credentials=credentials, - client_info=_create_client_info(application_name), - location=location, - ) - - if self.client.default_query_job_config is None: - self.client.default_query_job_config = bq.QueryJobConfig() - - self.client.default_query_job_config.use_legacy_sql = False - self.client.default_query_job_config.allow_large_results = True - - if storage_client is not None: - self.storage_client = storage_client - else: - self.storage_client = bqstorage.BigQueryReadClient( - credentials=credentials, - client_info=_create_client_info_gapic(application_name), - ) - - self.partition_column = partition_column - - @util.experimental - @classmethod - def from_connection( - cls, - client: bq.Client, - partition_column: str | None = "PARTITIONTIME", - storage_client: bqstorage.BigQueryReadClient | None = None, - dataset_id: str = "", - ) -> Backend: - """Create a BigQuery `Backend` from an existing ``Client``. - - Parameters - ---------- - client - A `Client` from the `google.cloud.bigquery` package. - partition_column - Identifier to use instead of default `_PARTITIONTIME` partition - column. Defaults to `'PARTITIONTIME'`. - storage_client - A `BigQueryReadClient` from the `google.cloud.bigquery_storage_v1` - package. - dataset_id - A dataset id that lives inside of the project attached to `client`. - """ - return bigframes_vendored.ibis.bigquery.connect( - client=client, - partition_column=partition_column, - storage_client=storage_client, - dataset_id=dataset_id, - ) - - def disconnect(self) -> None: - self.client.close() - - def _parse_project_and_dataset(self, dataset) -> tuple[str, str]: - if isinstance(dataset, sge.Table): - dataset = dataset.sql(self.dialect) - if not dataset and not self.dataset: - raise ValueError("Unable to determine BigQuery dataset.") - project, _, dataset = parse_project_and_dataset( - self.billing_project, - dataset or f"{self.data_project}.{self.dataset}", - ) - return project, dataset - - @property - def project_id(self): - return self.data_project - - @property - def dataset_id(self): - return self.dataset - - def create_database( - self, - name: str, - catalog: str | None = None, - force: bool = False, - collate: str | None = None, - **options: Any, - ) -> None: - properties = [ - sge.Property(this=sg.to_identifier(name), value=sge.convert(value)) - for name, value in (options or {}).items() - ] - - if collate is not None: - properties.append( - sge.CollateProperty(this=sge.convert(collate), default=True) - ) - - stmt = sge.Create( - kind="SCHEMA", - this=sg.table(name, db=catalog), - exists=force, - properties=sge.Properties(expressions=properties), - ) - - self.raw_sql(stmt.sql(self.name)) - - def drop_database( - self, - name: str, - catalog: str | None = None, - force: bool = False, - cascade: bool = False, - ) -> None: - """Drop a BigQuery dataset.""" - stmt = sge.Drop( - kind="SCHEMA", - this=sg.table(name, db=catalog), - exists=force, - cascade=cascade, - ) - - self.raw_sql(stmt.sql(self.name)) - - def table( - self, name: str, database: str | None = None, schema: str | None = None - ) -> ir.Table: - table_loc = self._warn_and_create_table_loc(database, schema) - table = sg.parse_one(f"`{name}`", into=sge.Table, read=self.name) - - # Bigquery, unlike other backends, had existing support for specifying - # table hierarchy in the table name, e.g. con.table("dataset.table_name") - # so here we have an extra layer of disambiguation to handle. - - # Default `catalog` to None unless we've parsed it out of the database/schema kwargs - # Raise if there are path specifications in both the name and as a kwarg - catalog = None if table_loc is None else table_loc.catalog - if table.catalog: - if table_loc is not None and table_loc.catalog: - raise com.IbisInputError( - "Cannot specify catalog both in the table name and as an argument" - ) - else: - catalog = table.catalog - - # Default `db` to None unless we've parsed it out of the database/schema kwargs - db = None if table_loc is None else table_loc.db - if table.db: - if table_loc is not None and table_loc.db: - raise com.IbisInputError( - "Cannot specify database both in the table name and as an argument" - ) - else: - db = table.db - - database = ( - sg.table(None, db=db, catalog=catalog, quoted=False).sql(dialect=self.name) - or None - ) - - project, dataset = self._parse_project_and_dataset(database) - - bq_table = self.client.get_table( - bq.TableReference( - bq.DatasetReference(project=project, dataset_id=dataset), - table.name, - ) - ) - - node = ops.DatabaseTable( - table.name, - # https://cloud.google.com/bigquery/docs/querying-wildcard-tables#filtering_selected_tables_using_table_suffix - schema=schema_from_bigquery_table(bq_table, wildcard=table.name[-1] == "*"), - source=self, - namespace=ops.Namespace(database=dataset, catalog=project), - ) - table_expr = node.to_expr() - return rename_partitioned_column(table_expr, bq_table, self.partition_column) - - def _make_session(self) -> tuple[str, str]: - if (client := getattr(self, "client", None)) is not None: - job_config = bq.QueryJobConfig(use_query_cache=False) - query = client.query( - "SELECT 1", job_config=job_config, project=self.billing_project - ) - query.result() - - return bq.DatasetReference( - project=query.destination.project, - dataset_id=query.destination.dataset_id, - ) - return None - - def _get_schema_using_query(self, query: str) -> sch.Schema: - job = self.client.query( - query, - job_config=bq.QueryJobConfig(dry_run=True, use_query_cache=False), - project=self.billing_project, - ) - return BigQuerySchema.to_ibis(job.schema) - - def _to_sqlglot( - self, - expr: ir.Expr, - limit: str | None = None, - params: Mapping[ir.Expr, Any] | None = None, - **kwargs, - ) -> Any: - """Compile an Ibis expression. - - Parameters - ---------- - expr - Ibis expression - limit - For expressions yielding result sets; retrieve at most this number - of values/rows. Overrides any limit already set on the expression. - params - Named unbound parameters - kwargs - Keyword arguments passed to the compiler - - Returns - ------- - Any - The output of compilation. The type of this value depends on the - backend. - - """ - self._define_udf_translation_rules(expr) - sql = super()._to_sqlglot(expr, limit=limit, params=params, **kwargs) - - query = sql.transform( - _qualify_memtable, - dataset=getattr(self._session_dataset, "dataset_id", None), - project=getattr(self._session_dataset, "project", None), - ).transform(_remove_null_ordering_from_unsupported_window) - return query - - def raw_sql(self, query: str, params=None, page_size: int | None = None): - query_parameters = [ - bigquery_param( - param.type(), - value, - ( - param.get_name() - if not isinstance(op := param.op(), ops.Alias) - else op.arg.name - ), - ) - for param, value in (params or {}).items() - ] - with contextlib.suppress(AttributeError): - query = query.sql(self.dialect) - - job_config = bq.job.QueryJobConfig(query_parameters=query_parameters or []) - return self.client.query_and_wait( - query, - job_config=job_config, - project=self.billing_project, - page_size=page_size, - ) - - @property - def current_catalog(self) -> str: - return self.data_project - - @property - def current_database(self) -> str | None: - return self.dataset - - def compile( - self, expr: ir.Expr, limit: str | None = None, params=None, **kwargs: Any - ): - """Compile an Ibis expression to a SQL string.""" - query = self._to_sqlglot(expr, limit=limit, params=params, **kwargs) - sql = query.sql(dialect=self.name, pretty=True) - self._log(sql) - return sql - - def execute(self, expr, params=None, limit="default", **kwargs): - """Compile and execute the given Ibis expression. - - Compile and execute Ibis expression using this backend client - interface, returning results in-memory in the appropriate object type - - Parameters - ---------- - expr - Ibis expression to execute - limit - Retrieve at most this number of values/rows. Overrides any limit - already set on the expression. - params - Query parameters - kwargs - Extra arguments specific to the backend - - Returns - ------- - pd.DataFrame | pd.Series | scalar - Output from execution - - """ - from bigframes_vendored.ibis.backends.bigquery.converter import ( - BigQueryPandasData, - ) - - self._run_pre_execute_hooks(expr) - - schema = expr.as_table().schema() - bigframes_vendored.ibis.schema( - {"_TABLE_SUFFIX": ibis_dtypes.string()} - ) - - sql = self.compile(expr, limit=limit, params=params, **kwargs) - self._log(sql) - query = self.raw_sql(sql, params=params, **kwargs) - - arrow_t = query.to_arrow( - progress_bar_type=None, bqstorage_client=self.storage_client - ) - - result = BigQueryPandasData.convert_table( - arrow_t.to_pandas(timestamp_as_object=True), schema - ) - - return expr.__pandas_result__(result, schema=schema) - - def insert( - self, - table_name: str, - obj: pd.DataFrame | ir.Table | list | dict, - schema: str | None = None, - database: str | None = None, - overwrite: bool = False, - ): - """Insert data into a table. - - Parameters - ---------- - table_name - The name of the table to which data needs will be inserted - obj - The source data or expression to insert - schema - The name of the schema that the table is located in - database - Name of the attached database that the table is located in. - overwrite - If `True` then replace existing contents of table - - """ - table_loc = self._warn_and_create_table_loc(database, schema) - catalog, db = self._to_catalog_db_tuple(table_loc) - if catalog is None: - catalog = self.current_catalog - if db is None: - db = self.current_database - - return super().insert( - table_name, - obj, - database=(catalog, db), - overwrite=overwrite, - ) - - def to_pyarrow( - self, - expr: ir.Expr, - *, - params: Mapping[ir.Scalar, Any] | None = None, - limit: int | str | None = None, - **kwargs: Any, - ) -> pa.Table: - self._import_pyarrow() - self._register_in_memory_tables(expr) - sql = self.compile(expr, limit=limit, params=params, **kwargs) - self._log(sql) - query = self.raw_sql(sql, params=params, **kwargs) - table = query.to_arrow( - progress_bar_type=None, bqstorage_client=self.storage_client - ) - table = table.rename_columns(list(expr.as_table().schema().names)) - return expr.__pyarrow_result__(table) - - def to_pyarrow_batches( - self, - expr: ir.Expr, - *, - params: Mapping[ir.Scalar, Any] | None = None, - limit: int | str | None = None, - chunk_size: int = 1_000_000, - **kwargs: Any, - ): - pa = self._import_pyarrow() - - schema = expr.as_table().schema() - - self._register_in_memory_tables(expr) - sql = self.compile(expr, limit=limit, params=params, **kwargs) - self._log(sql) - query = self.raw_sql(sql, params=params, page_size=chunk_size, **kwargs) - batch_iter = query.to_arrow_iterable(bqstorage_client=self.storage_client) - return pa.ipc.RecordBatchReader.from_batches(schema.to_pyarrow(), batch_iter) - - def _gen_udf_name(self, name: str, schema: Optional[str]) -> str: - func = ".".join(filter(None, (schema, name))) - if "." in func: - return ".".join(f"`{part}`" for part in func.split(".")) - return func - - def get_schema( - self, - name, - *, - catalog: str | None = None, - database: str | None = None, - ): - table_ref = bq.TableReference( - bq.DatasetReference( - project=catalog or self.data_project, - dataset_id=database or self.current_database, - ), - name, - ) - return schema_from_bigquery_table( - self.client.get_table(table_ref), - # https://cloud.google.com/bigquery/docs/querying-wildcard-tables#filtering_selected_tables_using_table_suffix - wildcard=name[-1] == "*", - ) - - def list_databases( - self, like: str | None = None, catalog: str | None = None - ) -> list[str]: - results = [ - dataset.dataset_id - for dataset in self.client.list_datasets( - project=catalog if catalog is not None else self.data_project - ) - ] - return self._filter_with_like(results, like) - - def list_tables( - self, - like: str | None = None, - database: tuple[str, str] | str | None = None, - schema: str | None = None, - ) -> list[str]: - """List the tables in the database. - - Parameters - ---------- - like - A pattern to use for listing tables. - database - The database location to perform the list against. - - By default uses the current `dataset` (`self.current_database`) and - `project` (`self.current_catalog`). - - To specify a table in a separate BigQuery dataset, you can pass in the - dataset and project as a string `"dataset.project"`, or as a tuple of - strings `("dataset", "project")`. - - ::: {.callout-note} - ## Ibis does not use the word `schema` to refer to database hierarchy. - - A collection of tables is referred to as a `database`. - A collection of `database` is referred to as a `catalog`. - - These terms are mapped onto the corresponding features in each - backend (where available), regardless of whether the backend itself - uses the same terminology. - ::: - schema - [deprecated] The schema (dataset) inside `database` to perform the list against. - """ - table_loc = self._warn_and_create_table_loc(database, schema) - - project, dataset = self._parse_project_and_dataset(table_loc) - dataset_ref = bq.DatasetReference(project, dataset) - result = [table.table_id for table in self.client.list_tables(dataset_ref)] - return self._filter_with_like(result, like) - - def set_database(self, name): - self.data_project, self.dataset = self._parse_project_and_dataset(name) - - @property - def version(self): - return bq.__version__ - - def create_table( - self, - name: str, - obj: ir.Table - | pd.DataFrame - | pa.Table - | pl.DataFrame - | pl.LazyFrame - | None = None, - *, - schema: bigframes_vendored.ibis.Schema | None = None, - database: str | None = None, - temp: bool = False, - overwrite: bool = False, - default_collate: str | None = None, - partition_by: str | None = None, - cluster_by: Iterable[str] | None = None, - options: Mapping[str, Any] | None = None, - ) -> ir.Table: - """Create a table in BigQuery. - - Parameters - ---------- - name - Name of the table to create - obj - The data with which to populate the table; optional, but one of `obj` - or `schema` must be specified - schema - The schema of the table to create; optional, but one of `obj` or - `schema` must be specified - database - The BigQuery *dataset* in which to create the table; optional - temp - Whether the table is temporary - overwrite - If `True`, replace the table if it already exists, otherwise fail if - the table exists - default_collate - Default collation for string columns. See BigQuery's documentation - for more details: https://cloud.google.com/bigquery/docs/reference/standard-sql/collation-concepts - partition_by - Partition the table by the given expression. See BigQuery's documentation - for more details: https://cloud.google.com/bigquery/docs/reference/standard-sql/data-definition-language#partition_expression - cluster_by - List of columns to cluster the table by. See BigQuery's documentation - for more details: https://cloud.google.com/bigquery/docs/reference/standard-sql/data-definition-language#clustering_column_list - options - BigQuery-specific table options; see the BigQuery documentation for - details: https://cloud.google.com/bigquery/docs/reference/standard-sql/data-definition-language#table_option_list - - Returns - ------- - Table - The table that was just created - - """ - if obj is None and schema is None: - raise com.IbisError("One of the `schema` or `obj` parameter is required") - - if isinstance(obj, ir.Table) and schema is not None: - if not schema.equals(obj.schema()): - raise com.IbisTypeError( - "Provided schema and Ibis table schema are incompatible. Please " - "align the two schemas, or provide only one of the two arguments." - ) - - project_id, dataset = self._parse_project_and_dataset(database) - - properties = [] - - if default_collate is not None: - properties.append( - sge.CollateProperty(this=sge.convert(default_collate), default=True) - ) - - if partition_by is not None: - properties.append( - sge.PartitionedByProperty( - this=sge.Tuple( - expressions=list(map(sg.to_identifier, partition_by)) - ) - ) - ) - - if cluster_by is not None: - properties.append( - sge.Cluster(expressions=list(map(sg.to_identifier, cluster_by))) - ) - - properties.extend( - sge.Property(this=sg.to_identifier(name), value=sge.convert(value)) - for name, value in (options or {}).items() - ) - - if obj is not None and not isinstance(obj, ir.Table): - obj = bigframes_vendored.ibis.memtable(obj, schema=schema) - - if obj is not None: - self._register_in_memory_tables(obj) - - if temp: - dataset = self._session_dataset.dataset_id - if database is not None: - raise com.IbisInputError("Cannot specify database for temporary table") - database = self._session_dataset.project - else: - dataset = database or self.current_database - - try: - table = sg.parse_one(name, into=sge.Table, read="bigquery") - except sg.ParseError: - table = sg.table( - name, - db=dataset, - catalog=project_id, - quoted=self.compiler.quoted, - ) - else: - if table.args["db"] is None: - table.args["db"] = dataset - - if table.args["catalog"] is None: - table.args["catalog"] = project_id - - table = _force_quote_table(table) - - column_defs = [ - sge.ColumnDef( - this=sg.to_identifier(name, quoted=self.compiler.quoted), - kind=BigQueryType.from_ibis(typ), - constraints=( - None - if typ.nullable or typ.is_array() - else [sge.ColumnConstraint(kind=sge.NotNullColumnConstraint())] - ), - ) - for name, typ in (schema or {}).items() - ] - - stmt = sge.Create( - kind="TABLE", - this=sge.Schema(this=table, expressions=column_defs or None), - replace=overwrite, - properties=sge.Properties(expressions=properties), - expression=None if obj is None else self.compile(obj), - ) - - sql = stmt.sql(self.name) - - self.raw_sql(sql) - return self.table(table.name, database=(table.catalog, table.db)) - - def drop_table( - self, - name: str, - *, - schema: str | None = None, - database: tuple[str | str] | str | None = None, - force: bool = False, - ) -> None: - table_loc = self._warn_and_create_table_loc(database, schema) - catalog, db = self._to_catalog_db_tuple(table_loc) - stmt = sge.Drop( - kind="TABLE", - this=sg.table( - name, - db=db or self.current_database, - catalog=catalog or self.billing_project, - ), - exists=force, - ) - self.raw_sql(stmt.sql(self.name)) - - def create_view( - self, - name: str, - obj: ir.Table, - *, - schema: str | None = None, - database: str | None = None, - overwrite: bool = False, - ) -> ir.Table: - table_loc = self._warn_and_create_table_loc(database, schema) - catalog, db = self._to_catalog_db_tuple(table_loc) - - stmt = sge.Create( - kind="VIEW", - this=sg.table( - name, - db=db or self.current_database, - catalog=catalog or self.billing_project, - ), - expression=self.compile(obj), - replace=overwrite, - ) - self._register_in_memory_tables(obj) - self.raw_sql(stmt.sql(self.name)) - return self.table(name, database=(catalog, database)) - - def drop_view( - self, - name: str, - *, - schema: str | None = None, - database: str | None = None, - force: bool = False, - ) -> None: - table_loc = self._warn_and_create_table_loc(database, schema) - catalog, db = self._to_catalog_db_tuple(table_loc) - - stmt = sge.Drop( - kind="VIEW", - this=sg.table( - name, - db=db or self.current_database, - catalog=catalog or self.billing_project, - ), - exists=force, - ) - self.raw_sql(stmt.sql(self.name)) - - def _load_into_cache(self, name, expr): - self.create_table(name, expr, schema=expr.schema(), temp=True) - - def _clean_up_cached_table(self, name): - self.drop_table( - name, - database=(self._session_dataset.project, self._session_dataset.dataset_id), - force=True, - ) - - def _register_udfs(self, expr: ir.Expr) -> None: - """No op because UDFs made with CREATE TEMPORARY FUNCTION must be followed by a query.""" - - @contextlib.contextmanager - def _safe_raw_sql(self, *args, **kwargs): - yield self.raw_sql(*args, **kwargs) - - # TODO: remove when the schema kwarg is removed - def _warn_and_create_table_loc(self, database=None, schema=None): - if schema is not None: - self._warn_schema() - if database is not None and schema is not None: - if isinstance(database, str): - table_loc = f"{database}.{schema}" - elif isinstance(database, tuple): - table_loc = database + schema - elif schema is not None: - table_loc = schema - elif database is not None: - table_loc = database - else: - table_loc = None - - table_loc = self._to_sqlglot_table(table_loc) - - if table_loc is not None: - if (sg_cat := table_loc.args["catalog"]) is not None: - sg_cat.args["quoted"] = False - if (sg_db := table_loc.args["db"]) is not None: - sg_db.args["quoted"] = False - - return table_loc - - -def compile(expr, params=None, **kwargs): - """Compile an expression for BigQuery.""" - backend = Backend() - return backend.compile(expr, params=params, **kwargs) - - -def connect( - project_id: str | None = None, - dataset_id: str = "", - credentials: google.auth.credentials.Credentials | None = None, - application_name: str | None = None, - auth_local_webserver: bool = False, - auth_external_data: bool = False, - auth_cache: str = "default", - partition_column: str | None = "PARTITIONTIME", -) -> Backend: - """Create a :class:`Backend` for use with Ibis. - - Parameters - ---------- - project_id - A BigQuery project id. - dataset_id - A dataset id that lives inside of the project indicated by - `project_id`. - credentials - Optional credentials. - application_name - A string identifying your application to Google API endpoints. - auth_local_webserver - Use a local webserver for the user authentication. Binds a - webserver to an open port on localhost between 8080 and 8089, - inclusive, to receive authentication token. If not set, defaults - to False, which requests a token via the console. - auth_external_data - Authenticate using additional scopes required to `query external - data sources - `_, - such as Google Sheets, files in Google Cloud Storage, or files in - Google Drive. If not set, defaults to False, which requests the - default BigQuery scopes. - auth_cache - Selects the behavior of the credentials cache. - - ``'default'`` - Reads credentials from disk if available, otherwise - authenticates and caches credentials to disk. - - ``'reauth'`` - Authenticates and caches credentials to disk. - - ``'none'`` - Authenticates and does **not** cache credentials. - - Defaults to ``'default'``. - partition_column - Identifier to use instead of default ``_PARTITIONTIME`` partition - column. Defaults to ``'PARTITIONTIME'``. - - Returns - ------- - Backend - An instance of the BigQuery backend - - """ - backend = Backend() - return backend.connect( - project_id=project_id, - dataset_id=dataset_id, - credentials=credentials, - application_name=application_name, - auth_local_webserver=auth_local_webserver, - auth_external_data=auth_external_data, - auth_cache=auth_cache, - partition_column=partition_column, - ) - - -__all__ = [ - "Backend", - "compile", - "connect", -] diff --git a/third_party/bigframes_vendored/ibis/backends/bigquery/backend.py b/third_party/bigframes_vendored/ibis/backends/bigquery/backend.py deleted file mode 100644 index 312b284bbb8..00000000000 --- a/third_party/bigframes_vendored/ibis/backends/bigquery/backend.py +++ /dev/null @@ -1,1187 +0,0 @@ -# Contains code from https://github.com/ibis-project/ibis/blob/9.2.0/ibis/backends/bigquery/__init__.py - - -"""BigQuery public API.""" - -from __future__ import annotations - -import concurrent.futures -import contextlib -import glob -import os -from typing import TYPE_CHECKING, Any, Optional - -import bigframes_vendored.ibis -import bigframes_vendored.ibis.backends.sql.compilers as sc -import bigframes_vendored.ibis.common.exceptions as com -import bigframes_vendored.ibis.expr.operations as ops -import bigframes_vendored.ibis.expr.schema as sch -import bigframes_vendored.ibis.expr.types as ir -import bigframes_vendored.sqlglot as sg -import bigframes_vendored.sqlglot.expressions as sge -import google.api_core.exceptions -import google.auth.credentials -import google.cloud.bigquery as bq -import google.cloud.bigquery_storage_v1 as bqstorage -import pydata_google_auth -from bigframes_vendored.ibis import util -from bigframes_vendored.ibis.backends import CanCreateDatabase, CanCreateSchema -from bigframes_vendored.ibis.backends.bigquery.client import ( - bigquery_param, - parse_project_and_dataset, - rename_partitioned_column, - schema_from_bigquery_table, -) -from bigframes_vendored.ibis.backends.bigquery.datatypes import ( - BigQuerySchema, - BigQueryType, -) -from bigframes_vendored.ibis.backends.sql import SQLBackend -from pydata_google_auth import cache - -if TYPE_CHECKING: - from collections.abc import Iterable, Mapping - from pathlib import Path - from urllib.parse import ParseResult - - import pandas as pd - import polars as pl - import pyarrow as pa - - -SCOPES = ["https://www.googleapis.com/auth/bigquery"] -EXTERNAL_DATA_SCOPES = [ - "https://www.googleapis.com/auth/bigquery", - "https://www.googleapis.com/auth/cloud-platform", - "https://www.googleapis.com/auth/drive", -] -CLIENT_ID = "546535678771-gvffde27nd83kfl6qbrnletqvkdmsese.apps.googleusercontent.com" -CLIENT_SECRET = "iU5ohAF2qcqrujegE3hQ1cPt" # noqa: S105 - - -def _create_user_agent(application_name: str) -> str: - user_agent = [] - - if application_name: - user_agent.append(application_name) - - user_agent_default_template = f"ibis/{bigframes_vendored.ibis.__version__}" - user_agent.append(user_agent_default_template) - - return " ".join(user_agent) - - -def _create_client_info(application_name): - from google.api_core.client_info import ClientInfo - - return ClientInfo(user_agent=_create_user_agent(application_name)) - - -def _create_client_info_gapic(application_name): - from google.api_core.gapic_v1.client_info import ClientInfo - - return ClientInfo(user_agent=_create_user_agent(application_name)) - - -def _force_quote_table(table: sge.Table) -> sge.Table: - """Force quote all the parts of a bigquery path. - - https://cloud.google.com/bigquery/docs/reference/standard-sql/lexical#identifiers - - my-table is OK, but not mydataset.my-table - - mytable-287 is OK, but not mytable-287a - - Just quote everything. - """ - for key in ("this", "db", "catalog"): - if (val := table.args[key]) is not None: - if isinstance(val, sg.exp.Identifier) and not val.quoted: - val.args["quoted"] = True - else: - table.args[key] = sg.to_identifier(val, quoted=True) - return table - - -class Backend(SQLBackend, CanCreateDatabase, CanCreateSchema): - name = "bigquery" - compiler = sc.bigquery.compiler - supports_in_memory_tables = True - supports_python_udfs = False - - def __init__(self, *args, **kwargs) -> None: - super().__init__(*args, **kwargs) - self.__session_dataset: bq.DatasetReference | None = None - self._query_cache.lookup = lambda name: self.table( - name, - database=(self._session_dataset.project, self._session_dataset.dataset_id), - ).op() - - @property - def _session_dataset(self): - if self.__session_dataset is None: - self.__session_dataset = self._make_session() - return self.__session_dataset - - def _read_file( - self, - path: str | Path, - *, - table_name: str | None = None, - job_config: bq.LoadJobConfig, - ) -> ir.Table: - self._make_session() - - if table_name is None: - table_name = util.gen_name(f"bq_read_{job_config.source_format}") - - table_ref = self._session_dataset.table(table_name) - - database = self._session_dataset.dataset_id - catalog = self._session_dataset.project - - # drop the table if it exists - # - # we could do this with write_disposition = WRITE_TRUNCATE but then the - # concurrent append jobs aren't possible - # - # dropping the table first means all write_dispositions can be - # WRITE_APPEND - self.drop_table(table_name, database=(catalog, database), force=True) - - if os.path.isdir(path): - raise NotImplementedError("Reading from a directory is not supported.") - elif str(path).startswith("gs://"): - load_job = self.client.load_table_from_uri( - path, table_ref, job_config=job_config - ) - load_job.result() - else: - - def load(file: str) -> None: - with open(file, mode="rb") as f: - load_job = self.client.load_table_from_file( - f, table_ref, job_config=job_config - ) - load_job.result() - - job_config.write_disposition = bq.WriteDisposition.WRITE_APPEND - - with concurrent.futures.ThreadPoolExecutor() as executor: - for fut in concurrent.futures.as_completed( - executor.submit(load, file) for file in glob.glob(str(path)) - ): - fut.result() - - return self.table(table_name, database=(catalog, database)) - - def read_parquet( - self, path: str | Path, table_name: str | None = None, **kwargs: Any - ): - """Read Parquet data into a BigQuery table. - - Parameters - ---------- - path - Path to a Parquet file on GCS or the local filesystem. Globs are supported. - table_name - Optional table name - kwargs - Additional keyword arguments passed to `google.cloud.bigquery.LoadJobConfig`. - - Returns - ------- - Table - An Ibis table expression - - """ - return self._read_file( - path, - table_name=table_name, - job_config=bq.LoadJobConfig( - source_format=bq.SourceFormat.PARQUET, **kwargs - ), - ) - - def read_csv( - self, path: str | Path, table_name: str | None = None, **kwargs: Any - ) -> ir.Table: - """Read CSV data into a BigQuery table. - - Parameters - ---------- - path - Path to a CSV file on GCS or the local filesystem. Globs are supported. - table_name - Optional table name - kwargs - Additional keyword arguments passed to - `google.cloud.bigquery.LoadJobConfig`. - - Returns - ------- - Table - An Ibis table expression - - """ - job_config = bq.LoadJobConfig( - source_format=bq.SourceFormat.CSV, - autodetect=True, - skip_leading_rows=1, - **kwargs, - ) - return self._read_file(path, table_name=table_name, job_config=job_config) - - def read_json( - self, path: str | Path, table_name: str | None = None, **kwargs: Any - ) -> ir.Table: - """Read newline-delimited JSON data into a BigQuery table. - - Parameters - ---------- - path - Path to a newline-delimited JSON file on GCS or the local - filesystem. Globs are supported. - table_name - Optional table name - kwargs - Additional keyword arguments passed to - `google.cloud.bigquery.LoadJobConfig`. - - Returns - ------- - Table - An Ibis table expression - - """ - job_config = bq.LoadJobConfig( - source_format=bq.SourceFormat.NEWLINE_DELIMITED_JSON, - autodetect=True, - **kwargs, - ) - return self._read_file(path, table_name=table_name, job_config=job_config) - - def _from_url(self, url: ParseResult, **kwargs): - return self.connect( - project_id=url.netloc or kwargs.get("project_id", [""])[0], - dataset_id=url.path[1:] or kwargs.get("dataset_id", [""])[0], - **kwargs, - ) - - def do_connect( - self, - project_id: str | None = None, - dataset_id: str = "", - credentials: google.auth.credentials.Credentials | None = None, - application_name: str | None = None, - auth_local_webserver: bool = True, - auth_external_data: bool = False, - auth_cache: str = "default", - partition_column: str | None = "PARTITIONTIME", - client: bq.Client | None = None, - storage_client: bqstorage.BigQueryReadClient | None = None, - location: str | None = None, - ) -> Backend: - """Create a `Backend` for use with Ibis. - - Parameters - ---------- - project_id - A BigQuery project id. - dataset_id - A dataset id that lives inside of the project indicated by - `project_id`. - credentials - Optional credentials. - application_name - A string identifying your application to Google API endpoints. - auth_local_webserver - Use a local webserver for the user authentication. Binds a - webserver to an open port on localhost between 8080 and 8089, - inclusive, to receive authentication token. If not set, defaults to - False, which requests a token via the console. - auth_external_data - Authenticate using additional scopes required to `query external - data sources - `_, - such as Google Sheets, files in Google Cloud Storage, or files in - Google Drive. If not set, defaults to False, which requests the - default BigQuery scopes. - auth_cache - Selects the behavior of the credentials cache. - - `'default'`` - Reads credentials from disk if available, otherwise - authenticates and caches credentials to disk. - - `'reauth'`` - Authenticates and caches credentials to disk. - - `'none'`` - Authenticates and does **not** cache credentials. - - Defaults to `'default'`. - partition_column - Identifier to use instead of default `_PARTITIONTIME` partition - column. Defaults to `'PARTITIONTIME'`. - client - A `Client` from the `google.cloud.bigquery` package. If not - set, one is created using the `project_id` and `credentials`. - storage_client - A `BigQueryReadClient` from the - `google.cloud.bigquery_storage_v1` package. If not set, one is - created using the `project_id` and `credentials`. - location - Default location for BigQuery objects. - - Returns - ------- - Backend - An instance of the BigQuery backend. - - """ - default_project_id = client.project if client is not None else project_id - - # Only need `credentials` to create a `client` and - # `storage_client`, so only one or the other needs to be set. - if (client is None or storage_client is None) and credentials is None: - scopes = SCOPES - if auth_external_data: - scopes = EXTERNAL_DATA_SCOPES - - if auth_cache == "default": - credentials_cache = cache.ReadWriteCredentialsCache( - filename="ibis.json" - ) - elif auth_cache == "reauth": - credentials_cache = cache.WriteOnlyCredentialsCache( - filename="ibis.json" - ) - elif auth_cache == "none": - credentials_cache = cache.NOOP - else: - raise ValueError( - f"Got unexpected value for auth_cache = '{auth_cache}'. " - "Expected one of 'default', 'reauth', or 'none'." - ) - - credentials, default_project_id = pydata_google_auth.default( - scopes, - client_id=CLIENT_ID, - client_secret=CLIENT_SECRET, - credentials_cache=credentials_cache, - use_local_webserver=auth_local_webserver, - ) - - project_id = project_id or default_project_id - - ( - self.data_project, - self.billing_project, - self.dataset, - ) = parse_project_and_dataset(project_id, dataset_id) - - if client is not None: - self.client = client - else: - self.client = bq.Client( - project=self.billing_project, - credentials=credentials, - client_info=_create_client_info(application_name), - location=location, - ) - - if self.client.default_query_job_config is None: - self.client.default_query_job_config = bq.QueryJobConfig() - - self.client.default_query_job_config.use_legacy_sql = False - self.client.default_query_job_config.allow_large_results = True - - if storage_client is not None: - self.storage_client = storage_client - else: - self.storage_client = bqstorage.BigQueryReadClient( - credentials=credentials, - client_info=_create_client_info_gapic(application_name), - ) - - self.partition_column = partition_column - - @util.experimental - @classmethod - def from_connection( - cls, - client: bq.Client, - partition_column: str | None = "PARTITIONTIME", - storage_client: bqstorage.BigQueryReadClient | None = None, - dataset_id: str = "", - ) -> Backend: - """Create a BigQuery `Backend` from an existing `Client`. - - Parameters - ---------- - client - A `Client` from the `google.cloud.bigquery` package. - partition_column - Identifier to use instead of default `_PARTITIONTIME` partition - column. Defaults to `'PARTITIONTIME'`. - storage_client - A `BigQueryReadClient` from the `google.cloud.bigquery_storage_v1` - package. - dataset_id - A dataset id that lives inside of the project attached to `client`. - """ - return bigframes_vendored.ibis.bigquery.connect( - client=client, - partition_column=partition_column, - storage_client=storage_client, - dataset_id=dataset_id, - ) - - def disconnect(self) -> None: - self.client.close() - - def _parse_project_and_dataset(self, dataset) -> tuple[str, str]: - if isinstance(dataset, sge.Table): - dataset = dataset.sql(self.dialect) - if not dataset and not self.dataset: - raise ValueError("Unable to determine BigQuery dataset.") - project, _, dataset = parse_project_and_dataset( - self.billing_project, - dataset or f"{self.data_project}.{self.dataset}", - ) - return project, dataset - - @property - def project_id(self): - return self.data_project - - @property - def dataset_id(self): - return self.dataset - - def create_database( - self, - name: str, - catalog: str | None = None, - force: bool = False, - collate: str | None = None, - **options: Any, - ) -> None: - properties = [ - sge.Property(this=sg.to_identifier(name), value=sge.convert(value)) - for name, value in (options or {}).items() - ] - - if collate is not None: - properties.append( - sge.CollateProperty(this=sge.convert(collate), default=True) - ) - - stmt = sge.Create( - kind="SCHEMA", - this=sg.table(name, db=catalog), - exists=force, - properties=sge.Properties(expressions=properties), - ) - - self.raw_sql(stmt.sql(self.name)) - - def drop_database( - self, - name: str, - catalog: str | None = None, - force: bool = False, - cascade: bool = False, - ) -> None: - """Drop a BigQuery dataset.""" - stmt = sge.Drop( - kind="SCHEMA", - this=sg.table(name, db=catalog), - exists=force, - cascade=cascade, - ) - - self.raw_sql(stmt.sql(self.name)) - - def table( - self, name: str, database: str | None = None, schema: str | None = None - ) -> ir.Table: - table_loc = self._warn_and_create_table_loc(database, schema) - table = sg.parse_one(f"`{name}`", into=sge.Table, read=self.name) - - # Bigquery, unlike other backends, had existing support for specifying - # table hierarchy in the table name, e.g. con.table("dataset.table_name") - # so here we have an extra layer of disambiguation to handle. - - # Default `catalog` to None unless we've parsed it out of the database/schema kwargs - # Raise if there are path specifications in both the name and as a kwarg - catalog = table_loc.args["catalog"] # args access will return None, not '' - if table.catalog: - if table_loc.catalog: - raise com.IbisInputError( - "Cannot specify catalog both in the table name and as an argument" - ) - else: - catalog = table.catalog - - # Default `db` to None unless we've parsed it out of the database/schema kwargs - db = table_loc.args["db"] # args access will return None, not '' - if table.db: - if table_loc.db: - raise com.IbisInputError( - "Cannot specify database both in the table name and as an argument" - ) - else: - db = table.db - - database = ( - sg.table(None, db=db, catalog=catalog, quoted=False).sql(dialect=self.name) - or None - ) - - project, dataset = self._parse_project_and_dataset(database) - - bq_table = self.client.get_table( - bq.TableReference( - bq.DatasetReference(project=project, dataset_id=dataset), - table.name, - ) - ) - - node = ops.DatabaseTable( - table.name, - # https://cloud.google.com/bigquery/docs/querying-wildcard-tables#filtering_selected_tables_using_table_suffix - schema=schema_from_bigquery_table(bq_table, wildcard=table.name[-1] == "*"), - source=self, - namespace=ops.Namespace(database=dataset, catalog=project), - ) - table_expr = node.to_expr() - return rename_partitioned_column(table_expr, bq_table, self.partition_column) - - def _make_session(self) -> tuple[str, str]: - if (client := getattr(self, "client", None)) is not None: - job_config = bq.QueryJobConfig(use_query_cache=False) - query = client.query( - "SELECT 1", job_config=job_config, project=self.billing_project - ) - query.result() - - return bq.DatasetReference( - project=query.destination.project, - dataset_id=query.destination.dataset_id, - ) - return None - - def _get_schema_using_query(self, query: str) -> sch.Schema: - job = self.client.query( - query, - job_config=bq.QueryJobConfig(dry_run=True, use_query_cache=False), - project=self.billing_project, - ) - return BigQuerySchema.to_ibis(job.schema) - - def raw_sql(self, query: str, params=None, page_size: int | None = None): - query_parameters = [ - bigquery_param( - param.type(), - value, - ( - param.get_name() - if not isinstance(op := param.op(), ops.Alias) - else op.arg.name - ), - ) - for param, value in (params or {}).items() - ] - with contextlib.suppress(AttributeError): - query = query.sql(self.dialect) - - job_config = bq.job.QueryJobConfig(query_parameters=query_parameters or []) - return self.client.query_and_wait( - query, - job_config=job_config, - project=self.billing_project, - page_size=page_size, - ) - - @property - def current_catalog(self) -> str: - return self.data_project - - @property - def current_database(self) -> str | None: - return self.dataset - - def compile( - self, - expr: ir.Expr, - limit: str | None = None, - params=None, - pretty: bool = True, - **kwargs: Any, - ): - """Compile an Ibis expression to a SQL string.""" - session_dataset = self._session_dataset - query = self.compiler.to_sqlglot( - expr, - limit=limit, - params=params, - session_dataset_id=getattr(session_dataset, "dataset_id", None), - session_project=getattr(session_dataset, "project", None), - **kwargs, - ) - queries = query if isinstance(query, list) else [query] - sql = ";\n".join(query.sql(self.dialect, pretty=pretty) for query in queries) - self._log(sql) - return sql - - def execute(self, expr, params=None, limit="default", **kwargs): - """Compile and execute the given Ibis expression. - Compile and execute Ibis expression using this backend client - interface, returning results in-memory in the appropriate object type - Parameters - ---------- - expr - Ibis expression to execute - limit - Retrieve at most this number of values/rows. Overrides any limit - already set on the expression. - params - Query parameters - kwargs - Extra arguments specific to the backend - Returns - ------- - pd.DataFrame | pd.Series | scalar - Output from execution - """ - from bigframes_vendored.ibis.backends.bigquery.converter import ( - BigQueryPandasData, - ) - - self._run_pre_execute_hooks(expr) - - schema = expr.as_table().schema() - bigframes_vendored.ibis.schema( - {"_TABLE_SUFFIX": "string"} - ) - - sql = self.compile(expr, limit=limit, params=params, **kwargs) - self._log(sql) - query = self.raw_sql(sql, params=params, **kwargs) - - arrow_t = query.to_arrow( - progress_bar_type=None, bqstorage_client=self.storage_client - ) - - result = BigQueryPandasData.convert_table( - arrow_t.to_pandas(timestamp_as_object=True), schema - ) - - return expr.__pandas_result__(result, schema=schema) - - def insert( - self, - table_name: str, - obj: pd.DataFrame | ir.Table | list | dict, - schema: str | None = None, - database: str | None = None, - overwrite: bool = False, - ): - """Insert data into a table. - - Parameters - ---------- - table_name - The name of the table to which data needs will be inserted - obj - The source data or expression to insert - schema - The name of the schema that the table is located in - database - Name of the attached database that the table is located in. - overwrite - If `True` then replace existing contents of table - - """ - table_loc = self._warn_and_create_table_loc(database, schema) - catalog, db = self._to_catalog_db_tuple(table_loc) - if catalog is None: - catalog = self.current_catalog - if db is None: - db = self.current_database - - return super().insert( - table_name, - obj, - database=(catalog, db), - overwrite=overwrite, - ) - - def to_pyarrow( - self, - expr: ir.Expr, - *, - params: Mapping[ir.Scalar, Any] | None = None, - limit: int | str | None = None, - **kwargs: Any, - ) -> pa.Table: - self._import_pyarrow() - sql = self.compile(expr, limit=limit, params=params, **kwargs) - self._log(sql) - query = self.raw_sql(sql, params=params, **kwargs) - table = query.to_arrow( - progress_bar_type=None, bqstorage_client=self.storage_client - ) - table = table.rename_columns(list(expr.as_table().schema().names)) - return expr.__pyarrow_result__(table) - - def to_pyarrow_batches( - self, - expr: ir.Expr, - *, - params: Mapping[ir.Scalar, Any] | None = None, - limit: int | str | None = None, - chunk_size: int = 1_000_000, - **kwargs: Any, - ): - pa = self._import_pyarrow() - - schema = expr.as_table().schema() - - sql = self.compile(expr, limit=limit, params=params, **kwargs) - self._log(sql) - query = self.raw_sql(sql, params=params, page_size=chunk_size, **kwargs) - batch_iter = query.to_arrow_iterable(bqstorage_client=self.storage_client) - return pa.ipc.RecordBatchReader.from_batches(schema.to_pyarrow(), batch_iter) - - def _gen_udf_name(self, name: str, schema: Optional[str]) -> str: - func = ".".join(filter(None, (schema, name))) - if "." in func: - return ".".join(f"`{part}`" for part in func.split(".")) - return func - - def get_schema( - self, - name, - *, - catalog: str | None = None, - database: str | None = None, - ): - table_ref = bq.TableReference( - bq.DatasetReference( - project=catalog or self.data_project, - dataset_id=database or self.current_database, - ), - name, - ) - return schema_from_bigquery_table( - self.client.get_table(table_ref), - # https://cloud.google.com/bigquery/docs/querying-wildcard-tables#filtering_selected_tables_using_table_suffix - wildcard=name[-1] == "*", - ) - - def list_databases( - self, like: str | None = None, catalog: str | None = None - ) -> list[str]: - results = [ - dataset.dataset_id - for dataset in self.client.list_datasets( - project=catalog if catalog is not None else self.data_project - ) - ] - return self._filter_with_like(results, like) - - def list_tables( - self, - like: str | None = None, - database: tuple[str, str] | str | None = None, - schema: str | None = None, - ) -> list[str]: - """List the tables in the database. - - Parameters - ---------- - like - A pattern to use for listing tables. - database - The database location to perform the list against. - - By default uses the current `dataset` (`self.current_database`) and - `project` (`self.current_catalog`). - - To specify a table in a separate BigQuery dataset, you can pass in the - dataset and project as a string `"dataset.project"`, or as a tuple of - strings `("dataset", "project")`. - - ::: {.callout-note} - ## Ibis does not use the word `schema` to refer to database hierarchy. - - A collection of tables is referred to as a `database`. - A collection of `database` is referred to as a `catalog`. - - These terms are mapped onto the corresponding features in each - backend (where available), regardless of whether the backend itself - uses the same terminology. - ::: - schema - [deprecated] The schema (dataset) inside `database` to perform the list against. - """ - table_loc = self._warn_and_create_table_loc(database, schema) - - project, dataset = self._parse_project_and_dataset(table_loc) - dataset_ref = bq.DatasetReference(project, dataset) - result = [table.table_id for table in self.client.list_tables(dataset_ref)] - return self._filter_with_like(result, like) - - def set_database(self, name): - self.data_project, self.dataset = self._parse_project_and_dataset(name) - - @property - def version(self): - return bq.__version__ - - def create_table( - self, - name: str, - obj: ir.Table - | pd.DataFrame - | pa.Table - | pl.DataFrame - | pl.LazyFrame - | None = None, - *, - schema: sch.SchemaLike | None = None, - database: str | None = None, - temp: bool = False, - overwrite: bool = False, - default_collate: str | None = None, - partition_by: str | None = None, - cluster_by: Iterable[str] | None = None, - options: Mapping[str, Any] | None = None, - ) -> ir.Table: - """Create a table in BigQuery. - - Parameters - ---------- - name - Name of the table to create - obj - The data with which to populate the table; optional, but one of `obj` - or `schema` must be specified - schema - The schema of the table to create; optional, but one of `obj` or - `schema` must be specified - database - The BigQuery *dataset* in which to create the table; optional - temp - Whether the table is temporary - overwrite - If `True`, replace the table if it already exists, otherwise fail if - the table exists - default_collate - Default collation for string columns. See BigQuery's documentation - for more details: https://cloud.google.com/bigquery/docs/reference/standard-sql/collation-concepts - partition_by - Partition the table by the given expression. See BigQuery's documentation - for more details: https://cloud.google.com/bigquery/docs/reference/standard-sql/data-definition-language#partition_expression - cluster_by - List of columns to cluster the table by. See BigQuery's documentation - for more details: https://cloud.google.com/bigquery/docs/reference/standard-sql/data-definition-language#clustering_column_list - options - BigQuery-specific table options; see the BigQuery documentation for - details: https://cloud.google.com/bigquery/docs/reference/standard-sql/data-definition-language#table_option_list - - Returns - ------- - Table - The table that was just created - - """ - if obj is None and schema is None: - raise com.IbisError("One of the `schema` or `obj` parameter is required") - if schema is not None: - schema = bigframes_vendored.ibis.schema(schema) - - if isinstance(obj, ir.Table) and schema is not None: - if not schema.equals(obj.schema()): - raise com.IbisTypeError( - "Provided schema and Ibis table schema are incompatible. Please " - "align the two schemas, or provide only one of the two arguments." - ) - - project_id, dataset = self._parse_project_and_dataset(database) - - properties = [] - - if default_collate is not None: - properties.append( - sge.CollateProperty(this=sge.convert(default_collate), default=True) - ) - - if partition_by is not None: - properties.append( - sge.PartitionedByProperty( - this=sge.Tuple( - expressions=list(map(sg.to_identifier, partition_by)) - ) - ) - ) - - if cluster_by is not None: - properties.append( - sge.Cluster(expressions=list(map(sg.to_identifier, cluster_by))) - ) - - properties.extend( - sge.Property(this=sg.to_identifier(name), value=sge.convert(value)) - for name, value in (options or {}).items() - ) - - if obj is not None and not isinstance(obj, ir.Table): - obj = bigframes_vendored.ibis.memtable(obj, schema=schema) - - if temp: - dataset = self._session_dataset.dataset_id - if database is not None: - raise com.IbisInputError("Cannot specify database for temporary table") - database = self._session_dataset.project - else: - dataset = database or self.current_database - - try: - table = sg.parse_one(name, into=sge.Table, read="bigquery") - except sg.ParseError: - table = sg.table( - name, - db=dataset, - catalog=project_id, - quoted=self.compiler.quoted, - ) - else: - if table.args["db"] is None: - table.args["db"] = dataset - - if table.args["catalog"] is None: - table.args["catalog"] = project_id - - table = _force_quote_table(table) - - column_defs = [ - sge.ColumnDef( - this=sg.to_identifier(name, quoted=self.compiler.quoted), - kind=BigQueryType.from_ibis(typ), - constraints=( - None - if typ.nullable or typ.is_array() - else [sge.ColumnConstraint(kind=sge.NotNullColumnConstraint())] - ), - ) - for name, typ in (schema or {}).items() - ] - - stmt = sge.Create( - kind="TABLE", - this=sge.Schema(this=table, expressions=column_defs or None), - replace=overwrite, - properties=sge.Properties(expressions=properties), - expression=None if obj is None else self.compile(obj), - ) - - sql = stmt.sql(self.name) - - self.raw_sql(sql) - return self.table(table.name, database=(table.catalog, table.db)) - - def drop_table( - self, - name: str, - *, - schema: str | None = None, - database: tuple[str | str] | str | None = None, - force: bool = False, - ) -> None: - table_loc = self._warn_and_create_table_loc(database, schema) - catalog, db = self._to_catalog_db_tuple(table_loc) - stmt = sge.Drop( - kind="TABLE", - this=sg.table( - name, - db=db or self.current_database, - catalog=catalog or self.billing_project, - ), - exists=force, - ) - self.raw_sql(stmt.sql(self.name)) - - def create_view( - self, - name: str, - obj: ir.Table, - *, - schema: str | None = None, - database: str | None = None, - overwrite: bool = False, - ) -> ir.Table: - table_loc = self._warn_and_create_table_loc(database, schema) - catalog, db = self._to_catalog_db_tuple(table_loc) - - stmt = sge.Create( - kind="VIEW", - this=sg.table( - name, - db=db or self.current_database, - catalog=catalog or self.billing_project, - ), - expression=self.compile(obj), - replace=overwrite, - ) - self.raw_sql(stmt.sql(self.name)) - return self.table(name, database=(catalog, database)) - - def drop_view( - self, - name: str, - *, - schema: str | None = None, - database: str | None = None, - force: bool = False, - ) -> None: - table_loc = self._warn_and_create_table_loc(database, schema) - catalog, db = self._to_catalog_db_tuple(table_loc) - - stmt = sge.Drop( - kind="VIEW", - this=sg.table( - name, - db=db or self.current_database, - catalog=catalog or self.billing_project, - ), - exists=force, - ) - self.raw_sql(stmt.sql(self.name)) - - def _drop_cached_table(self, name): - self.drop_table( - name, - database=(self._session_dataset.project, self._session_dataset.dataset_id), - force=True, - ) - - def _register_udfs(self, expr: ir.Expr) -> None: - """No op because UDFs made with CREATE TEMPORARY FUNCTION must be followed by a query.""" - - @contextlib.contextmanager - def _safe_raw_sql(self, *args, **kwargs): - yield self.raw_sql(*args, **kwargs) - - # TODO: remove when the schema kwarg is removed - def _warn_and_create_table_loc(self, database=None, schema=None): - if schema is not None: - self._warn_schema() - if database is not None and schema is not None: - if isinstance(database, str): - table_loc = f"{database}.{schema}" - elif isinstance(database, tuple): - table_loc = database + schema - elif schema is not None: - table_loc = schema - elif database is not None: - table_loc = database - else: - table_loc = None - - table_loc = self._to_sqlglot_table(table_loc) - - if table_loc is not None: - if (sg_cat := table_loc.args["catalog"]) is not None: - sg_cat.args["quoted"] = False - if (sg_db := table_loc.args["db"]) is not None: - sg_db.args["quoted"] = False - - return table_loc - - -def compile(expr, params=None, **kwargs): - """Compile an expression for BigQuery.""" - backend = Backend() - return backend.compile(expr, params=params, **kwargs) - - -def connect( - project_id: str | None = None, - dataset_id: str = "", - credentials: google.auth.credentials.Credentials | None = None, - application_name: str | None = None, - auth_local_webserver: bool = False, - auth_external_data: bool = False, - auth_cache: str = "default", - partition_column: str | None = "PARTITIONTIME", -) -> Backend: - """Create a :class:`Backend` for use with Ibis. - - Parameters - ---------- - project_id - A BigQuery project id. - dataset_id - A dataset id that lives inside of the project indicated by - `project_id`. - credentials - Optional credentials. - application_name - A string identifying your application to Google API endpoints. - auth_local_webserver - Use a local webserver for the user authentication. Binds a - webserver to an open port on localhost between 8080 and 8089, - inclusive, to receive authentication token. If not set, defaults - to False, which requests a token via the console. - auth_external_data - Authenticate using additional scopes required to `query external - data sources - `_, - such as Google Sheets, files in Google Cloud Storage, or files in - Google Drive. If not set, defaults to False, which requests the - default BigQuery scopes. - auth_cache - Selects the behavior of the credentials cache. - - `'default'`` - Reads credentials from disk if available, otherwise - authenticates and caches credentials to disk. - - `'reauth'`` - Authenticates and caches credentials to disk. - - `'none'`` - Authenticates and does **not** cache credentials. - - Defaults to `'default'`. - partition_column - Identifier to use instead of default `_PARTITIONTIME` partition - column. Defaults to `'PARTITIONTIME'`. - - Returns - ------- - Backend - An instance of the BigQuery backend - - """ - backend = Backend() - return backend.connect( - project_id=project_id, - dataset_id=dataset_id, - credentials=credentials, - application_name=application_name, - auth_local_webserver=auth_local_webserver, - auth_external_data=auth_external_data, - auth_cache=auth_cache, - partition_column=partition_column, - ) - - -__all__ = [ - "Backend", - "compile", - "connect", -] diff --git a/third_party/bigframes_vendored/ibis/backends/bigquery/client.py b/third_party/bigframes_vendored/ibis/backends/bigquery/client.py deleted file mode 100644 index 0af2f924cfb..00000000000 --- a/third_party/bigframes_vendored/ibis/backends/bigquery/client.py +++ /dev/null @@ -1,178 +0,0 @@ -# Contains code from https://github.com/ibis-project/ibis/blob/9.2.0/ibis/backends/bigquery/client.py - -"""BigQuery ibis client implementation.""" - -from __future__ import annotations - -import functools - -import bigframes_vendored.ibis.common.exceptions as com -import bigframes_vendored.ibis.expr.datatypes as dt -import google.cloud.bigquery as bq -import pandas as pd -from bigframes_vendored.ibis.backends.bigquery.datatypes import ( - BigQuerySchema, - BigQueryType, -) - -NATIVE_PARTITION_COL = "_PARTITIONTIME" - - -def schema_from_bigquery_table(table, *, wildcard: bool): - schema = BigQuerySchema.to_ibis(table.schema) - - # Check for partitioning information - partition_info = table.time_partitioning - if partition_info is not None: - # We have a partitioned table - partition_field = partition_info.field or NATIVE_PARTITION_COL - # Only add a new column if it's not already a column in the schema - if partition_field not in schema: - schema |= {partition_field: dt.Timestamp(timezone="UTC")} - - if wildcard: - schema |= {"_TABLE_SUFFIX": dt.string} - - return schema - - -@functools.singledispatch -def bigquery_param(dtype, value, name): - raise NotADirectoryError(dtype) - - -@bigquery_param.register -def bq_param_struct(dtype: dt.Struct, value, name): - fields = dtype.fields - field_params = [bigquery_param(fields[k], v, k) for k, v in value.items()] - result = bq.StructQueryParameter(name, *field_params) - return result - - -@bigquery_param.register -def bq_param_array(dtype: dt.Array, value, name): - value_type = dtype.value_type - - try: - bigquery_type = BigQueryType.to_string(value_type) - except NotImplementedError: - raise com.UnsupportedBackendType(dtype) - else: - if isinstance(value_type, dt.Array): - raise TypeError("ARRAY> is not supported in BigQuery") - elif isinstance(value_type, dt.Struct): - query_value = [ - bigquery_param(dtype.value_type, struct, f"element_{i:d}") - for i, struct in enumerate(value) - ] - bigquery_type = "STRUCT" - else: - query_value = value - return bq.ArrayQueryParameter(name, bigquery_type, query_value) - - -@bigquery_param.register -def bq_param_timestamp(_: dt.Timestamp, value, name): - # TODO(phillipc): Not sure if this is the correct way to do this. - timestamp_value = pd.Timestamp(value, tz="UTC").to_pydatetime() - return bq.ScalarQueryParameter(name, "TIMESTAMP", timestamp_value) - - -@bigquery_param.register -def bq_param_string(_: dt.String, value, name): - return bq.ScalarQueryParameter(name, "STRING", value) - - -@bigquery_param.register -def bq_param_integer(_: dt.Integer, value, name): - return bq.ScalarQueryParameter(name, "INT64", value) - - -@bigquery_param.register -def bq_param_double(_: dt.Floating, value, name): - return bq.ScalarQueryParameter(name, "FLOAT64", value) - - -@bigquery_param.register -def bq_param_boolean(_: dt.Boolean, value, name): - return bq.ScalarQueryParameter(name, "BOOL", value) - - -@bigquery_param.register -def bq_param_date(_: dt.Date, value, name): - return bq.ScalarQueryParameter( - name, "DATE", pd.Timestamp(value).to_pydatetime().date() - ) - - -def rename_partitioned_column(table_expr, bq_table, partition_col): - """Rename native partition column to user-defined name.""" - partition_info = bq_table.time_partitioning - - # If we don't have any partition information, the table isn't partitioned - if partition_info is None: - return table_expr - - # If we have a partition, but no "field" field in the table properties, - # then use NATIVE_PARTITION_COL as the default - partition_field = partition_info.field or NATIVE_PARTITION_COL - - # The partition field must be in table_expr columns - assert partition_field in table_expr.columns - - # No renaming if the config option is set to None or the partition field - # is not _PARTITIONTIME - if partition_col is None or partition_field != NATIVE_PARTITION_COL: - return table_expr - return table_expr.rename({partition_col: NATIVE_PARTITION_COL}) - - -def parse_project_and_dataset(project: str, dataset: str = "") -> tuple[str, str, str]: - """Compute the billing project, data project, and dataset if available. - - This function figure out the project id under which queries will run versus - the project of where the data live as well as what dataset to use. - - Parameters - ---------- - project : str - A project name - dataset : Optional[str] - A ``.`` string or just a dataset name - - Examples - -------- - >>> data_project, billing_project, dataset = parse_project_and_dataset( - ... "ibis-gbq", "foo-bar.my_dataset" - ... ) - >>> data_project - 'foo-bar' - >>> billing_project - 'ibis-gbq' - >>> dataset - 'my_dataset' - >>> data_project, billing_project, dataset = parse_project_and_dataset( - ... "ibis-gbq", "my_dataset" - ... ) - >>> data_project - 'ibis-gbq' - >>> billing_project - 'ibis-gbq' - >>> dataset - 'my_dataset' - >>> data_project, billing_project, _dataset = parse_project_and_dataset("ibis-gbq") - >>> data_project - 'ibis-gbq' - - """ - if dataset.count(".") > 1: - raise ValueError( - f"{dataset} is not a BigQuery dataset. More info https://cloud.google.com/bigquery/docs/datasets-intro" - ) - elif dataset.count(".") == 1: - data_project, dataset = dataset.split(".") - billing_project = project - else: - billing_project = data_project = project - - return data_project, billing_project, dataset diff --git a/third_party/bigframes_vendored/ibis/backends/bigquery/converter.py b/third_party/bigframes_vendored/ibis/backends/bigquery/converter.py deleted file mode 100644 index 2afccd454af..00000000000 --- a/third_party/bigframes_vendored/ibis/backends/bigquery/converter.py +++ /dev/null @@ -1,18 +0,0 @@ -# Contains code from https://github.com/ibis-project/ibis/blob/9.2.0/ibis/backends/bigquery/converter.py - -from __future__ import annotations - -from bigframes_vendored.ibis.formats.pandas import PandasData - - -class BigQueryPandasData(PandasData): - @classmethod - def convert_GeoSpatial(cls, s, dtype, pandas_type): - import geopandas as gpd - import shapely as shp - - return gpd.GeoSeries(shp.from_wkt(s)) - - convert_Point = convert_LineString = convert_Polygon = convert_MultiLineString = ( - convert_MultiPoint - ) = convert_MultiPolygon = convert_GeoSpatial diff --git a/third_party/bigframes_vendored/ibis/backends/bigquery/datatypes.py b/third_party/bigframes_vendored/ibis/backends/bigquery/datatypes.py deleted file mode 100644 index aa9ac062a17..00000000000 --- a/third_party/bigframes_vendored/ibis/backends/bigquery/datatypes.py +++ /dev/null @@ -1,181 +0,0 @@ -# Contains code from https://github.com/ibis-project/ibis/blob/9.2.0/ibis/backends/bigquery/datatypes.py - -from __future__ import annotations - -import bigframes_vendored.ibis -import bigframes_vendored.ibis.expr.datatypes as dt -import bigframes_vendored.ibis.expr.schema as sch -import bigframes_vendored.sqlglot as sg -import google.cloud.bigquery as bq -from bigframes_vendored.ibis.formats import SchemaMapper, TypeMapper - -_from_bigquery_types = { - "INT64": dt.Int64, - "INTEGER": dt.Int64, - "FLOAT": dt.Float64, - "FLOAT64": dt.Float64, - "BOOL": dt.Boolean, - "BOOLEAN": dt.Boolean, - "STRING": dt.String, - "DATE": dt.Date, - "TIME": dt.Time, - "BYTES": dt.Binary, - "JSON": dt.JSON, -} - - -class BigQueryType(TypeMapper): - @classmethod - def to_ibis(cls, typ: str, nullable: bool = True) -> dt.DataType: - if typ == "DATETIME": - return dt.Timestamp(timezone=None, nullable=nullable) - elif typ == "TIMESTAMP": - return dt.Timestamp(timezone="UTC", nullable=nullable) - elif typ == "NUMERIC": - return dt.Decimal(38, 9, nullable=nullable) - elif typ == "BIGNUMERIC": - return dt.Decimal(76, 38, nullable=nullable) - elif typ == "GEOGRAPHY": - return dt.GeoSpatial(geotype="geography", srid=4326, nullable=nullable) - else: - try: - return _from_bigquery_types[typ](nullable=nullable) - except KeyError: - raise TypeError(f"Unable to convert BigQuery type to ibis: {typ}") - - @classmethod - def from_ibis(cls, dtype: dt.DataType) -> str: - if dtype.is_floating(): - return "FLOAT64" - elif dtype.is_uint64(): - raise TypeError( - "Conversion from uint64 to BigQuery integer type (int64) is lossy" - ) - elif dtype.is_integer(): - return "INT64" - elif dtype.is_boolean(): - return "BOOLEAN" - elif dtype.is_binary(): - return "BYTES" - elif dtype.is_string(): - return "STRING" - elif dtype.is_date(): - return "DATE" - elif dtype.is_timestamp(): - if dtype.timezone is None: - return "DATETIME" - elif dtype.timezone == "UTC": - return "TIMESTAMP" - else: - raise TypeError( - "BigQuery does not support timestamps with timezones other than 'UTC'" - ) - elif dtype.is_decimal(): - if (dtype.precision, dtype.scale) == (76, 38): - return "BIGNUMERIC" - if (dtype.precision, dtype.scale) in [(38, 9), (None, None)]: - return "NUMERIC" - raise TypeError( - "BigQuery only supports decimal types with precision of 38 and " - f"scale of 9 (NUMERIC) or precision of 76 and scale of 38 (BIGNUMERIC). " - f"Current precision: {dtype.precision}. Current scale: {dtype.scale}" - ) - elif dtype.is_array(): - return f"ARRAY<{cls.from_ibis(dtype.value_type)}>" - elif dtype.is_struct(): - fields = ( - f"{sg.to_identifier(k).sql('bigquery')} {cls.from_ibis(v)}" - for k, v in dtype.fields.items() - ) - return "STRUCT<{}>".format(", ".join(fields)) - elif dtype.is_json(): - return "JSON" - elif dtype.is_geospatial(): - if (dtype.geotype, dtype.srid) == ("geography", 4326): - return "GEOGRAPHY" - raise TypeError( - "BigQuery geography uses points on WGS84 reference ellipsoid." - f"Current geotype: {dtype.geotype}, Current srid: {dtype.srid}" - ) - elif dtype.is_map(): - raise NotImplementedError("Maps are not supported in BigQuery") - else: - return str(dtype).upper() - - -class BigQuerySchema(SchemaMapper): - @classmethod - def from_ibis(cls, schema: sch.Schema) -> list[bq.SchemaField]: - schema_fields = [] - - for name, typ in bigframes_vendored.ibis.schema(schema).items(): - if typ.is_array(): - value_type = typ.value_type - if value_type.is_array(): - raise TypeError("Nested arrays are not supported in BigQuery") - - is_struct = value_type.is_struct() - - field_type = ( - "RECORD" if is_struct else BigQueryType.from_ibis(typ.value_type) - ) - mode = "REPEATED" - fields = cls.from_ibis( - bigframes_vendored.ibis.schema(getattr(value_type, "fields", {})) - ) - elif typ.is_struct(): - field_type = "RECORD" - mode = "NULLABLE" if typ.nullable else "REQUIRED" - fields = cls.from_ibis(bigframes_vendored.ibis.schema(typ.fields)) - else: - field_type = BigQueryType.from_ibis(typ) - mode = "NULLABLE" if typ.nullable else "REQUIRED" - fields = [] - - schema_fields.append( - bq.SchemaField(name, field_type=field_type, mode=mode, fields=fields) - ) - return schema_fields - - @classmethod - def _dtype_from_bigquery_field(cls, field: bq.SchemaField) -> dt.DataType: - typ = field.field_type - if typ == "RECORD": - assert field.fields, "RECORD fields are empty" - fields = {f.name: cls._dtype_from_bigquery_field(f) for f in field.fields} - dtype = dt.Struct(fields) - else: - dtype = BigQueryType.to_ibis(typ) - - mode = field.mode - if mode == "NULLABLE": - return dtype.copy(nullable=True) - elif mode == "REQUIRED": - return dtype.copy(nullable=False) - elif mode == "REPEATED": - # arrays with NULL elements aren't supported - return dt.Array(dtype.copy(nullable=False)) - else: - raise TypeError(f"Unknown BigQuery field.mode: {mode}") - - @classmethod - def to_ibis(cls, fields: list[bq.SchemaField]) -> sch.Schema: - return sch.Schema({f.name: cls._dtype_from_bigquery_field(f) for f in fields}) - - -# TODO(kszucs): we can eliminate this function by making dt.DataType traversible -# using ibis.common.graph.Node, similarly to how we traverse ops.Node instances: -# node.find(types) -def spread_type(dt: dt.DataType): - """Returns a generator that contains all the types in the given type. - - For complex types like set and array, it returns the types of the elements. - """ - if dt.is_array(): - yield from spread_type(dt.value_type) - elif dt.is_struct(): - for type_ in dt.types: - yield from spread_type(type_) - elif dt.is_map(): - raise NotImplementedError("Maps are not supported in BigQuery") - yield dt diff --git a/third_party/bigframes_vendored/ibis/backends/bigquery/registry.py b/third_party/bigframes_vendored/ibis/backends/bigquery/registry.py new file mode 100644 index 00000000000..a4e61ca0f91 --- /dev/null +++ b/third_party/bigframes_vendored/ibis/backends/bigquery/registry.py @@ -0,0 +1,31 @@ +# Contains code from https://github.com/ibis-project/ibis/blob/master/ibis/backends/bigquery/registry.py +"""Module to convert from Ibis expression to SQL string.""" + +from ibis.backends.bigquery.registry import OPERATION_REGISTRY + +import third_party.bigframes_vendored.ibis.expr.operations as vendored_ibis_ops + + +def _approx_quantiles(translator, op: vendored_ibis_ops.ApproximateMultiQuantile): + arg = translator.translate(op.arg) + num_bins = translator.translate(op.num_bins) + return f"APPROX_QUANTILES({arg}, {num_bins})" + + +def _first_non_null_value(translator, op: vendored_ibis_ops.FirstNonNullValue): + arg = translator.translate(op.arg) + return f"FIRST_VALUE({arg} IGNORE NULLS)" + + +def _last_non_null_value(translator, op: vendored_ibis_ops.LastNonNullValue): + arg = translator.translate(op.arg) + return f"LAST_VALUE({arg} IGNORE NULLS)" + + +patched_ops = { + vendored_ibis_ops.ApproximateMultiQuantile: _approx_quantiles, + vendored_ibis_ops.FirstNonNullValue: _first_non_null_value, + vendored_ibis_ops.LastNonNullValue: _last_non_null_value, +} + +OPERATION_REGISTRY.update(patched_ops) diff --git a/third_party/bigframes_vendored/ibis/backends/sql/__init__.py b/third_party/bigframes_vendored/ibis/backends/sql/__init__.py deleted file mode 100644 index 9035bb0755a..00000000000 --- a/third_party/bigframes_vendored/ibis/backends/sql/__init__.py +++ /dev/null @@ -1,650 +0,0 @@ -# Contains code from https://github.com/ibis-project/ibis/blob/9.2.0/ibis/backends/sql/__init__.py - -from __future__ import annotations - -import abc -from functools import partial -from typing import TYPE_CHECKING, Any, ClassVar - -import bigframes_vendored.ibis -import bigframes_vendored.ibis.common.exceptions as exc -import bigframes_vendored.ibis.expr.operations as ops -import bigframes_vendored.ibis.expr.schema as sch -import bigframes_vendored.ibis.expr.types as ir -import bigframes_vendored.sqlglot as sg -import bigframes_vendored.sqlglot.expressions as sge -from bigframes_vendored.ibis import util -from bigframes_vendored.ibis.backends import BaseBackend -from bigframes_vendored.ibis.backends.sql.compilers.base import STAR - -if TYPE_CHECKING: - from collections.abc import Iterable, Mapping - - import pandas as pd - import pyarrow as pa - from bigframes_vendored.ibis.backends.sql.compilers.base import SQLGlotCompiler - from bigframes_vendored.ibis.expr.schema import SchemaLike - - -class _DatabaseSchemaHandler: - """Temporary mixin collecting several helper functions and code snippets. - - Help to 'gracefully' deprecate the use of `schema` as a hierarchical term. - """ - - @staticmethod - def _warn_schema(): - util.warn_deprecated( - name="schema", - as_of="9.0", - removed_in="10.0", - instead="Use the `database` kwarg with one of the following patterns:" - '\ndatabase="database"' - '\ndatabase=("catalog", "database")' - '\ndatabase="catalog.database"', - # TODO: add option for namespace object - ) - - def _warn_and_create_table_loc(self, database=None, schema=None): - if schema is not None: - self._warn_schema() - - if database is not None and schema is not None: - if isinstance(database, str): - table_loc = f"{database}.{schema}" - elif isinstance(database, tuple): - table_loc = database + schema - elif schema is not None: - table_loc = schema - elif database is not None: - table_loc = database - else: - table_loc = None - - table_loc = self._to_sqlglot_table(table_loc) - - return table_loc - - -class SQLBackend(BaseBackend, _DatabaseSchemaHandler): - compiler: ClassVar[SQLGlotCompiler] - name: ClassVar[str] - - _top_level_methods = ("from_connection",) - - @property - def dialect(self) -> sg.Dialect: - return self.compiler.dialect - - @classmethod - def has_operation(cls, operation: type[ops.Value]) -> bool: - compiler = cls.compiler - if operation in compiler.extra_supported_ops: - return True - method = getattr(compiler, f"visit_{operation.__name__}", None) - return method not in ( - None, - compiler.visit_Undefined, - compiler.visit_Unsupported, - ) - - def _fetch_from_cursor(self, cursor, schema: sch.Schema) -> pd.DataFrame: - import pandas as pd - from bigframes_vendored.ibis.formats.pandas import PandasData - - try: - df = pd.DataFrame.from_records( - cursor, columns=schema.names, coerce_float=True - ) - except Exception: - # clean up the cursor if we fail to create the DataFrame - # - # in the sqlite case failing to close the cursor results in - # artificially locked tables - cursor.close() - raise - df = PandasData.convert_table(df, schema) - return df - - def table( - self, - name: str, - schema: str | None = None, - database: tuple[str, str] | str | None = None, - ) -> ir.Table: - """Construct a table expression. - - Parameters - ---------- - name - Table name - schema - [deprecated] Schema name - database - Database name - - Returns - ------- - Table - Table expression - - """ - table_loc = self._warn_and_create_table_loc(database, schema) - - catalog, database = None, None - if table_loc is not None: - catalog = table_loc.catalog or None - database = table_loc.db or None - - table_schema = self.get_schema(name, catalog=catalog, database=database) - return ops.DatabaseTable( - name, - schema=table_schema, - source=self, - namespace=ops.Namespace(catalog=catalog, database=database), - ).to_expr() - - def _to_sqlglot( - self, expr: ir.Expr, *, limit: str | None = None, params=None, **_: Any - ): - """Compile an Ibis expression to a sqlglot object.""" - table_expr = expr.as_table() - - if limit == "default": - limit = bigframes_vendored.ibis.options.sql.default_limit - if limit is not None: - table_expr = table_expr.limit(limit) - - if params is None: - params = {} - - sql = self.compiler.translate(table_expr.op(), params=params) - assert not isinstance(sql, sge.Subquery) - - if isinstance(sql, sge.Table): - sql = sg.select(STAR, copy=False).from_(sql, copy=False) - - assert not isinstance(sql, sge.Subquery) - return sql - - def compile( - self, - expr: ir.Expr, - limit: str | None = None, - params=None, - pretty: bool = False, - **kwargs: Any, - ): - """Compile an Ibis expression to a SQL string.""" - query = self._to_sqlglot(expr, limit=limit, params=params, **kwargs) - sql = query.sql(dialect=self.dialect, pretty=pretty, copy=False) - self._log(sql) - return sql - - def _log(self, sql: str) -> None: - """Log `sql`. - - This method can be implemented by subclasses. Logging occurs when - `ibis.options.verbose` is `True`. - """ - from bigframes_vendored.ibis import util - - util.log(sql) - - def sql( - self, - query: str, - schema: SchemaLike | None = None, - dialect: str | None = None, - ) -> ir.Table: - query = self._transpile_sql(query, dialect=dialect) - if schema is None: - schema = self._get_schema_using_query(query) - return ops.SQLQueryResult( - query, bigframes_vendored.ibis.schema(schema), self - ).to_expr() - - @abc.abstractmethod - def _get_schema_using_query(self, query: str) -> sch.Schema: - """Return an ibis Schema from a backend-specific SQL string. - - Parameters - ---------- - query - Backend-specific SQL string - - Returns - ------- - Schema - The schema inferred from `query` - """ - - def _get_sql_string_view_schema(self, name, table, query) -> sch.Schema: - compiler = self.compiler - dialect = compiler.dialect - - cte = self._to_sqlglot(table) - parsed = sg.parse_one(query, read=dialect) - parsed.args["with"] = cte.args.pop("with", []) - parsed = parsed.with_( - sg.to_identifier(name, quoted=compiler.quoted), as_=cte, dialect=dialect - ) - - sql = parsed.sql(dialect) - return self._get_schema_using_query(sql) - - def create_view( - self, - name: str, - obj: ir.Table, - *, - database: str | None = None, - schema: str | None = None, - overwrite: bool = False, - ) -> ir.Table: - table_loc = self._warn_and_create_table_loc(database, schema) - catalog, db = self._to_catalog_db_tuple(table_loc) - - src = sge.Create( - this=sg.table(name, db=db, catalog=catalog, quoted=self.compiler.quoted), - kind="VIEW", - replace=overwrite, - expression=self.compile(obj), - ) - self._register_in_memory_tables(obj) - with self._safe_raw_sql(src): - pass - return self.table(name, database=(catalog, db)) - - def drop_view( - self, - name: str, - *, - database: str | None = None, - schema: str | None = None, - force: bool = False, - ) -> None: - table_loc = self._warn_and_create_table_loc(database, schema) - catalog, db = self._to_catalog_db_tuple(table_loc) - - src = sge.Drop( - this=sg.table(name, db=db, catalog=catalog, quoted=self.compiler.quoted), - kind="VIEW", - exists=force, - ) - with self._safe_raw_sql(src): - pass - - def _load_into_cache(self, name, expr): - self.create_table(name, expr, schema=expr.schema(), temp=True) - - def _clean_up_cached_table(self, name): - self.drop_table(name, force=True) - - def execute( - self, - expr: ir.Expr, - params: Mapping | None = None, - limit: str | None = "default", - **kwargs: Any, - ) -> Any: - """Execute an expression.""" - - self._run_pre_execute_hooks(expr) - table = expr.as_table() - sql = self.compile(table, params=params, limit=limit, **kwargs) - - schema = table.schema() - - # TODO(kszucs): these methods should be abstractmethods or this default - # implementation should be removed - with self._safe_raw_sql(sql) as cur: - result = self._fetch_from_cursor(cur, schema) - return expr.__pandas_result__(result) - - def drop_table( - self, - name: str, - database: tuple[str, str] | str | None = None, - force: bool = False, - ) -> None: - table_loc = self._warn_and_create_table_loc(database, None) - catalog, db = self._to_catalog_db_tuple(table_loc) - - drop_stmt = sg.exp.Drop( - kind="TABLE", - this=sg.table(name, db=db, catalog=catalog, quoted=self.compiler.quoted), - exists=force, - ) - with self._safe_raw_sql(drop_stmt): - pass - - def _cursor_batches( - self, - expr: ir.Expr, - params: Mapping[ir.Scalar, Any] | None = None, - limit: int | str | None = None, - chunk_size: int = 1 << 20, - ) -> Iterable[list]: - self._run_pre_execute_hooks(expr) - - with self._safe_raw_sql( - self.compile(expr, limit=limit, params=params) - ) as cursor: - while batch := cursor.fetchmany(chunk_size): - yield batch - - @util.experimental - def to_pyarrow_batches( - self, - expr: ir.Expr, - *, - params: Mapping[ir.Scalar, Any] | None = None, - limit: int | str | None = None, - chunk_size: int = 1_000_000, - **_: Any, - ) -> pa.ipc.RecordBatchReader: - """Execute expression and return an iterator of pyarrow record batches. - - This method is eager and will execute the associated expression - immediately. - - Parameters - ---------- - expr - Ibis expression to export to pyarrow - limit - An integer to effect a specific row limit. A value of `None` means - "no limit". The default is in `ibis/config.py`. - params - Mapping of scalar parameter expressions to value. - chunk_size - Maximum number of rows in each returned record batch. - - Returns - ------- - RecordBatchReader - Collection of pyarrow `RecordBatch`s. - - """ - pa = self._import_pyarrow() - - schema = expr.as_table().schema() - array_type = schema.as_struct().to_pyarrow() - arrays = ( - pa.array(map(tuple, batch), type=array_type) - for batch in self._cursor_batches( - expr, params=params, limit=limit, chunk_size=chunk_size - ) - ) - batches = map(pa.RecordBatch.from_struct_array, arrays) - - return pa.ipc.RecordBatchReader.from_batches(schema.to_pyarrow(), batches) - - def insert( - self, - table_name: str, - obj: pd.DataFrame | ir.Table | list | dict, - schema: str | None = None, - database: str | None = None, - overwrite: bool = False, - ) -> None: - """Insert data into a table. - - ::: {.callout-note} - ## Ibis does not use the word `schema` to refer to database hierarchy. - - A collection of `table` is referred to as a `database`. - A collection of `database` is referred to as a `catalog`. - - These terms are mapped onto the corresponding features in each - backend (where available), regardless of whether the backend itself - uses the same terminology. - ::: - - Parameters - ---------- - table_name - The name of the table to which data needs will be inserted - obj - The source data or expression to insert - schema - [deprecated] The name of the schema that the table is located in - database - Name of the attached database that the table is located in. - - For backends that support multi-level table hierarchies, you can - pass in a dotted string path like `"catalog.database"` or a tuple of - strings like `("catalog", "database")`. - overwrite - If `True` then replace existing contents of table - - """ - table_loc = self._warn_and_create_table_loc(database, schema) - catalog, db = self._to_catalog_db_tuple(table_loc) - - if overwrite: - self.truncate_table(table_name, database=(catalog, db)) - - if not isinstance(obj, ir.Table): - obj = bigframes_vendored.ibis.memtable(obj) - - self._run_pre_execute_hooks(obj) - - query = self._build_insert_from_table( - target=table_name, source=obj, db=db, catalog=catalog - ) - - with self._safe_raw_sql(query): - pass - - def _build_insert_from_table( - self, *, target: str, source, db: str | None = None, catalog: str | None = None - ): - compiler = self.compiler - quoted = compiler.quoted - # Compare the columns between the target table and the object to be inserted - # If they don't match, assume auto-generated column names and use positional - # ordering. - source_cols = source.columns - columns = ( - source_cols - if not set(target_cols := self.get_schema(target).names).difference( - source_cols - ) - else target_cols - ) - - query = sge.insert( - expression=self.compile(source), - into=sg.table(target, db=db, catalog=catalog, quoted=quoted), - columns=[sg.to_identifier(col, quoted=quoted) for col in columns], - dialect=compiler.dialect, - ) - return query - - def _build_insert_template( - self, - name, - *, - schema: sch.Schema, - catalog: str | None = None, - columns: bool = False, - placeholder: str = "?", - ) -> str: - """Builds an INSERT INTO table VALUES query string with placeholders. - - Parameters - ---------- - name - Name of the table to insert into - schema - Ibis schema of the table to insert into - catalog - Catalog name of the table to insert into - columns - Whether to render the columns to insert into - placeholder - Placeholder string. Can be a format string with a single `{i}` spec. - - Returns - ------- - str - The query string - """ - quoted = self.compiler.quoted - return sge.insert( - sge.Values( - expressions=[ - sge.Tuple( - expressions=[ - sge.Var(this=placeholder.format(i=i)) - for i in range(len(schema)) - ] - ) - ] - ), - into=sg.table(name, catalog=catalog, quoted=quoted), - columns=( - map(partial(sg.to_identifier, quoted=quoted), schema.keys()) - if columns - else None - ), - ).sql(self.dialect) - - def truncate_table( - self, name: str, database: str | None = None, schema: str | None = None - ) -> None: - """Delete all rows from a table. - - ::: {.callout-note} - ## Ibis does not use the word `schema` to refer to database hierarchy. - - A collection of tables is referred to as a `database`. - A collection of `database` is referred to as a `catalog`. - These terms are mapped onto the corresponding features in each - backend (where available), regardless of whether the backend itself - uses the same terminology. - ::: - - Parameters - ---------- - name - Table name - database - Name of the attached database that the table is located in. - - For backends that support multi-level table hierarchies, you can - pass in a dotted string path like `"catalog.database"` or a tuple of - strings like `("catalog", "database")`. - schema - [deprecated] Schema name - - """ - table_loc = self._warn_and_create_table_loc(database, schema) - catalog, db = self._to_catalog_db_tuple(table_loc) - - ident = sg.table(name, db=db, catalog=catalog, quoted=self.compiler.quoted).sql( - self.dialect - ) - with self._safe_raw_sql(f"TRUNCATE TABLE {ident}"): - pass - - @util.experimental - @classmethod - def from_connection(cls, con: Any, **kwargs: Any) -> BaseBackend: - """Create an Ibis client from an existing connection. - - Parameters - ---------- - con - An existing connection. - **kwargs - Extra arguments to be applied to the newly-created backend. - """ - raise NotImplementedError( - f"{cls.name} backend cannot be constructed from an existing connection" - ) - - def disconnect(self): - # This is part of the Python DB-API specification so should work for - # _most_ sqlglot backends - self.con.close() - - def _compile_builtin_udf(self, udf_node: ops.ScalarUDF | ops.AggUDF) -> None: - """Compile a built-in UDF. No-op by default.""" - - def _compile_python_udf(self, udf_node: ops.ScalarUDF) -> None: - raise NotImplementedError( - f"Python UDFs are not supported in the {self.name} backend" - ) - - def _compile_pyarrow_udf(self, udf_node: ops.ScalarUDF) -> None: - raise NotImplementedError( - f"PyArrow UDFs are not supported in the {self.name} backend" - ) - - def _compile_pandas_udf(self, udf_node: ops.ScalarUDF) -> str: - raise NotImplementedError( - f"pandas UDFs are not supported in the {self.name} backend" - ) - - def _to_catalog_db_tuple(self, table_loc: sge.Table): - if table_loc is None or table_loc == (None, None): - return None, None - - if (sg_cat := table_loc.args["catalog"]) is not None: - sg_cat.args["quoted"] = False - sg_cat = sg_cat.sql(self.name) - if (sg_db := table_loc.args["db"]) is not None: - sg_db.args["quoted"] = False - sg_db = sg_db.sql(self.name) - - return sg_cat, sg_db - - def _to_sqlglot_table(self, database): - if database is None: - return None - elif isinstance(database, (list, tuple)): - if len(database) > 2: - raise ValueError( - "Only database hierarchies of two or fewer levels are supported." - "\nYou can specify ('catalog', 'database')." - ) - elif len(database) == 2: - catalog, database = database - elif len(database) == 1: - database = database[0] - catalog = None - else: - raise ValueError( - f"Malformed database tuple {database} provided" - "\nPlease specify one of:" - '\n("catalog", "database")' - '\n("database",)' - ) - database = sg.exp.Table( - catalog=sg.to_identifier(catalog, quoted=self.compiler.quoted), - db=sg.to_identifier(database, quoted=self.compiler.quoted), - ) - elif isinstance(database, str): - # There is no definition of a sqlglot catalog.database hierarchy outside - # of the standard table expression. - # sqlglot parsing of the string will assume that it's a Table - # so we unpack the arguments into a new sqlglot object, switching - # table (this) -> database (db) and database (db) -> catalog - table = sg.parse_one(database, into=sg.exp.Table, dialect=self.dialect) - if table.args["catalog"] is not None: - raise exc.IbisInputError( - f"Overspecified table hierarchy provided: `{table.sql(self.dialect)}`" - ) - catalog = table.args["db"] - db = table.args["this"] - database = sg.exp.Table(catalog=catalog, db=db) - else: - raise ValueError( - """Invalid database hierarchy format. Please use either dotted - strings ('catalog.database') or tuples ('catalog', 'database').""" - ) - - return database diff --git a/third_party/bigframes_vendored/ibis/backends/sql/compilers/__init__.py b/third_party/bigframes_vendored/ibis/backends/sql/compilers/__init__.py deleted file mode 100644 index 73ae551ae40..00000000000 --- a/third_party/bigframes_vendored/ibis/backends/sql/compilers/__init__.py +++ /dev/null @@ -1,9 +0,0 @@ -# Contains code from https://github.com/ibis-project/ibis/blob/9.2.0/ibis/backends/sql/compilers/__init__.py - -from __future__ import annotations - -__all__ = [ - "BigQueryCompiler", -] - -from bigframes_vendored.ibis.backends.sql.compilers.bigquery import BigQueryCompiler diff --git a/third_party/bigframes_vendored/ibis/backends/sql/compilers/base.py b/third_party/bigframes_vendored/ibis/backends/sql/compilers/base.py deleted file mode 100644 index e6ab427be5e..00000000000 --- a/third_party/bigframes_vendored/ibis/backends/sql/compilers/base.py +++ /dev/null @@ -1,1687 +0,0 @@ -# Contains code from https://github.com/ibis-project/ibis/blob/9.2.0/ibis/backends/sql/compilers/base.py - -from __future__ import annotations - -import abc -import calendar -import itertools -import math -import operator -import string -from functools import partial, reduce -from typing import TYPE_CHECKING, Any, ClassVar - -import bigframes_vendored.ibis.common.exceptions as ibis_exceptions -import bigframes_vendored.ibis.common.patterns as pats -import bigframes_vendored.ibis.expr.datatypes as dt -import bigframes_vendored.ibis.expr.operations as ops -import bigframes_vendored.sqlglot as sg -import bigframes_vendored.sqlglot.expressions as sge -from bigframes_vendored.ibis.backends.sql.rewrites import ( - FirstValue, - LastValue, - add_one_to_nth_value_input, - add_order_by_to_empty_ranking_window_functions, - empty_in_values_right_side, - lower_bucket, - lower_capitalize, - lower_sample, - one_to_zero_index, - sqlize, -) -from bigframes_vendored.ibis.config import options -from bigframes_vendored.ibis.expr.operations.udf import InputType -from bigframes_vendored.ibis.expr.rewrites import lower_stringslice -from public import public - -try: - from bigframes_vendored.sqlglot.expressions import Alter -except ImportError: - from bigframes_vendored.sqlglot.expressions import AlterTable -else: - - def AlterTable(*args, kind="TABLE", **kwargs): - return Alter(*args, kind=kind, **kwargs) - - -if TYPE_CHECKING: - from collections.abc import Callable, Iterable, Mapping - - import bigframes_vendored.ibis.expr.schema as sch - import bigframes_vendored.ibis.expr.types as ir - from bigframes_vendored.ibis.backends.bigquery.datatypes import SqlglotType - - -def get_leaf_classes(op): - for child_class in op.__subclasses__(): - if not child_class.__subclasses__(): - yield child_class - else: - yield from get_leaf_classes(child_class) - - -ALL_OPERATIONS = frozenset(get_leaf_classes(ops.Node)) - - -class AggGen: - """A descriptor for compiling aggregate functions. - - Common cases can be handled by setting configuration flags, - special cases should override the `aggregate` method directly. - - Parameters - ---------- - supports_filter - Whether the backend supports a FILTER clause in the aggregate. - Defaults to False. - supports_order_by - Whether the backend supports an ORDER BY clause in (relevant) - aggregates. Defaults to False. - """ - - class _Accessor: - """An internal type to handle getattr/getitem access.""" - - __slots__ = ("handler", "compiler") - - def __init__(self, handler: Callable, compiler: SQLGlotCompiler): - self.handler = handler - self.compiler = compiler - - def __getattr__(self, name: str) -> Callable: - return partial(self.handler, self.compiler, name) - - __getitem__ = __getattr__ - - __slots__ = ("supports_filter", "supports_order_by") - - def __init__( - self, *, supports_filter: bool = False, supports_order_by: bool = False - ): - self.supports_filter = supports_filter - self.supports_order_by = supports_order_by - - def __get__(self, instance, owner=None): - if instance is None: - return self - - return AggGen._Accessor(self.aggregate, instance) - - def aggregate( - self, - compiler: SQLGlotCompiler, - name: str, - *args: Any, - where: Any = None, - order_by: tuple = (), - ): - """Compile the specified aggregate. - - Parameters - ---------- - compiler - The backend's compiler. - name - The aggregate name (e.g. `"sum"`). - args - Any arguments to pass to the aggregate. - where - An optional column filter to apply before performing the aggregate. - order_by - Optional ordering keys to use to order the rows before performing - the aggregate. - """ - func = compiler.f[name] - - if order_by and not self.supports_order_by: - raise ibis_exceptions.UnsupportedOperationError( - "ordering of order-sensitive aggregations via `order_by` is " - f"not supported for the {compiler.dialect} backend" - ) - - if where is not None and not self.supports_filter: - args = tuple(compiler.if_(where, arg, NULL) for arg in args) - - if order_by and self.supports_order_by: - *rest, last = args - out = func(*rest, sge.Order(this=last, expressions=order_by)) - else: - out = func(*args) - - if where is not None and self.supports_filter: - out = sge.Filter(this=out, expression=sge.Where(this=where)) - - return out - - -class VarGen: - __slots__ = () - - def __getattr__(self, name: str) -> sge.Var: - return sge.Var(this=name) - - def __getitem__(self, key: str) -> sge.Var: - return sge.Var(this=key) - - -class AnonymousFuncGen: - __slots__ = () - - def __getattr__(self, name: str) -> Callable[..., sge.Anonymous]: - return lambda *args: sge.Anonymous( - this=name, expressions=list(map(sge.convert, args)) - ) - - def __getitem__(self, key: str) -> Callable[..., sge.Anonymous]: - return getattr(self, key) - - -class FuncGen: - __slots__ = ("namespace", "anon", "copy") - - def __init__(self, namespace: str | None = None, copy: bool = False) -> None: - self.namespace = namespace - self.anon = AnonymousFuncGen() - self.copy = copy - - def __getattr__(self, name: str) -> Callable[..., sge.Func]: - name = ".".join(filter(None, (self.namespace, name))) - return lambda *args, **kwargs: sg.func( - name, *map(sge.convert, args), **kwargs, copy=self.copy - ) - - def __getitem__(self, key: str) -> Callable[..., sge.Func]: - return getattr(self, key) - - def array(self, *args: Any) -> sge.Array: - if not args: - return sge.Array(expressions=[]) - - first, *rest = args - - if isinstance(first, sge.Select): - assert not rest, ( - "only one argument allowed when `first` is a select statement" - ) - - return sge.Array(expressions=list(map(sge.convert, (first, *rest)))) - - def tuple(self, *args: Any) -> sge.Anonymous: - return self.anon.tuple(*args) - - def exists(self, query: sge.Expression) -> sge.Exists: - return sge.Exists(this=query) - - def concat(self, *args: Any) -> sge.Concat: - return sge.Concat(expressions=list(map(sge.convert, args))) - - def map(self, keys: Iterable, values: Iterable) -> sge.Map: - return sge.Map(keys=keys, values=values) - - -class ColGen: - __slots__ = ("table",) - - def __init__(self, table: str | None = None) -> None: - self.table = table - - def __getattr__(self, name: str) -> sge.Column: - return sg.column(name, table=self.table, copy=False) - - def __getitem__(self, key: str) -> sge.Column: - return sg.column(key, table=self.table, copy=False) - - -C = ColGen() -F = FuncGen() -NULL = sge.Null() -FALSE = sge.false() -TRUE = sge.true() -STAR = sge.Star() - - -def parenthesize_inputs(f): - """Decorate a translation rule to parenthesize inputs.""" - - def wrapper(self, op, *, left, right): - return f( - self, - op, - left=self._add_parens(op.left, left), - right=self._add_parens(op.right, right), - ) - - return wrapper - - -@public -class SQLGlotCompiler(abc.ABC): - __slots__ = "f", "v" - - agg = AggGen() - """A generator for handling aggregate functions""" - - rewrites: tuple[type[pats.Replace], ...] = ( - empty_in_values_right_side, - add_order_by_to_empty_ranking_window_functions, - one_to_zero_index, - add_one_to_nth_value_input, - ) - """A sequence of rewrites to apply to the expression tree before SQL-specific transforms.""" - - post_rewrites: tuple[type[pats.Replace], ...] = () - """A sequence of rewrites to apply to the expression tree after SQL-specific transforms.""" - - no_limit_value: sge.Null | None = None - """The value to use to indicate no limit.""" - - quoted: bool = True - """Whether to always quote identifiers.""" - - copy_func_args: bool = False - """Whether to copy function arguments when generating SQL.""" - - supports_qualify: bool = False - """Whether the backend supports the QUALIFY clause.""" - - NAN: ClassVar[sge.Expression] = sge.Cast( - this=sge.convert("NaN"), to=sge.DataType(this=sge.DataType.Type.DOUBLE) - ) - """Backend's NaN literal.""" - - POS_INF: ClassVar[sge.Expression] = sge.Cast( - this=sge.convert("Inf"), to=sge.DataType(this=sge.DataType.Type.DOUBLE) - ) - """Backend's positive infinity literal.""" - - NEG_INF: ClassVar[sge.Expression] = sge.Cast( - this=sge.convert("-Inf"), to=sge.DataType(this=sge.DataType.Type.DOUBLE) - ) - """Backend's negative infinity literal.""" - - EXTRA_SUPPORTED_OPS: tuple[type[ops.Node], ...] = ( - ops.Project, - ops.Filter, - ops.Sort, - ops.WindowFunction, - ) - """A tuple of ops classes that are supported, but don't have explicit - `visit_*` methods (usually due to being handled by rewrite rules). Used by - `has_operation`""" - - UNSUPPORTED_OPS: tuple[type[ops.Node], ...] = () - """Tuple of operations the backend doesn't support.""" - - LOWERED_OPS: dict[type[ops.Node], pats.Replace | None] = { - ops.Bucket: lower_bucket, - ops.Capitalize: lower_capitalize, - ops.Sample: lower_sample, - ops.StringSlice: lower_stringslice, - } - """A mapping from an operation class to either a rewrite rule for rewriting that - operation to one composed of lower-level operations ("lowering"), or `None` to - remove an existing rewrite rule for that operation added in a base class""" - - SIMPLE_OPS = { - ops.Abs: "abs", - ops.Acos: "acos", - ops.All: "bool_and", - ops.Any: "bool_or", - ops.ApproxCountDistinct: "approx_distinct", - ops.ArgMax: "max_by", - ops.ArgMin: "min_by", - ops.ArrayContains: "array_contains", - ops.ArrayFlatten: "flatten", - ops.ArrayLength: "array_size", - ops.ArraySort: "array_sort", - ops.ArrayStringJoin: "array_to_string", - ops.Asin: "asin", - ops.Atan2: "atan2", - ops.Atan: "atan", - ops.Cos: "cos", - ops.Cot: "cot", - ops.Count: "count", - ops.CumeDist: "cume_dist", - ops.Date: "date", - ops.DateFromYMD: "datefromparts", - ops.Degrees: "degrees", - ops.DenseRank: "dense_rank", - ops.Exp: "exp", - FirstValue: "first_value", - ops.GroupConcat: "group_concat", - ops.IfElse: "if", - ops.IsInf: "isinf", - ops.IsNan: "isnan", - ops.JSONGetItem: "json_extract", - ops.LPad: "lpad", - LastValue: "last_value", - ops.Levenshtein: "levenshtein", - ops.Ln: "ln", - ops.Log10: "log", - ops.Log2: "log2", - ops.Lowercase: "lower", - ops.Map: "map", - ops.Median: "median", - ops.MinRank: "rank", - ops.NTile: "ntile", - ops.NthValue: "nth_value", - ops.NullIf: "nullif", - ops.PercentRank: "percent_rank", - ops.Pi: "pi", - ops.Power: "pow", - ops.RPad: "rpad", - ops.Radians: "radians", - ops.RegexSearch: "regexp_like", - ops.RegexSplit: "regexp_split", - ops.Repeat: "repeat", - ops.Reverse: "reverse", - ops.RowNumber: "row_number", - ops.Sign: "sign", - ops.Sin: "sin", - ops.Sqrt: "sqrt", - ops.StartsWith: "starts_with", - ops.StrRight: "right", - ops.StringAscii: "ascii", - ops.StringContains: "contains", - ops.StringLength: "length", - ops.StringReplace: "replace", - ops.StringSplit: "split", - ops.StringToDate: "str_to_date", - ops.StringToTimestamp: "str_to_time", - ops.Tan: "tan", - ops.Translate: "translate", - ops.Unnest: "explode", - ops.Uppercase: "upper", - } - - BINARY_INFIX_OPS = ( - # Binary operations - ops.Add, - ops.Subtract, - ops.Multiply, - ops.Divide, - ops.Modulus, - ops.Power, - # Comparisons - ops.GreaterEqual, - ops.Greater, - ops.LessEqual, - ops.Less, - ops.Equals, - ops.NotEquals, - # Boolean comparisons - ops.And, - ops.Or, - ops.Xor, - # Bitwise business - ops.BitwiseLeftShift, - ops.BitwiseRightShift, - ops.BitwiseAnd, - ops.BitwiseOr, - ops.BitwiseXor, - # Time arithmetic - ops.DateAdd, - ops.DateSub, - ops.DateDiff, - ops.TimestampAdd, - ops.TimestampSub, - ops.TimestampDiff, - # Interval Marginalia - ops.IntervalAdd, - ops.IntervalMultiply, - ops.IntervalSubtract, - ) - - NEEDS_PARENS = BINARY_INFIX_OPS + (ops.IsNull, ops.NotNull) - - # Constructed dynamically in `__init_subclass__` from their respective - # UPPERCASE values to handle inheritance, do not modify directly here. - extra_supported_ops: ClassVar[frozenset[type[ops.Node]]] = frozenset() - lowered_ops: ClassVar[dict[type[ops.Node], pats.Replace]] = {} - - def __init__(self) -> None: - self.f = FuncGen(copy=self.__class__.copy_func_args) - self.v = VarGen() - - def __init_subclass__(cls, **kwargs): - super().__init_subclass__(**kwargs) - - def methodname(op: type) -> str: - assert isinstance(type(op), type), type(op) - return f"visit_{op.__name__}" - - def make_impl(op, target_name): - assert isinstance(type(op), type), type(op) - - if issubclass(op, ops.Reduction): - - def impl( - self, _, *, _name: str = target_name, where, order_by=(), **kw - ): - return self.agg[_name](*kw.values(), where=where, order_by=order_by) - - else: - - def impl(self, _, *, _name: str = target_name, **kw): - return self.f[_name](*kw.values()) - - return impl - - for op, target_name in cls.SIMPLE_OPS.items(): - setattr(cls, methodname(op), make_impl(op, target_name)) - - # unconditionally raise an exception for unsupported operations - # - # these *must* be defined after SIMPLE_OPS to handle compilers that - # subclass other compilers - for op in cls.UNSUPPORTED_OPS: - # change to visit_Unsupported in a follow up - # TODO: handle geoespatial ops as a separate case? - setattr(cls, methodname(op), cls.visit_Undefined) - - # raise on any remaining unsupported operations - for op in ALL_OPERATIONS: - name = methodname(op) - if not hasattr(cls, name): - setattr(cls, name, cls.visit_Undefined) - - # Amend `lowered_ops` and `extra_supported_ops` using their - # respective UPPERCASE classvar values. - extra_supported_ops = set(cls.extra_supported_ops) - lowered_ops = dict(cls.lowered_ops) - extra_supported_ops.update(cls.EXTRA_SUPPORTED_OPS) - for op_cls, rewrite in cls.LOWERED_OPS.items(): - if rewrite is not None: - lowered_ops[op_cls] = rewrite - extra_supported_ops.add(op_cls) - else: - lowered_ops.pop(op_cls, None) - extra_supported_ops.discard(op_cls) - cls.lowered_ops = lowered_ops - cls.extra_supported_ops = frozenset(extra_supported_ops) - - @property - @abc.abstractmethod - def dialect(self) -> str: - """Backend dialect.""" - - @property - @abc.abstractmethod - def type_mapper(self) -> type[SqlglotType]: - """The type mapper for the backend.""" - - def _compile_builtin_udf(self, udf_node: ops.ScalarUDF) -> None: # noqa: B027 - """No-op.""" - - def _compile_python_udf(self, udf_node: ops.ScalarUDF) -> None: - raise NotImplementedError( - f"Python UDFs are not supported in the {self.dialect} backend" - ) - - def _compile_pyarrow_udf(self, udf_node: ops.ScalarUDF) -> None: - raise NotImplementedError( - f"PyArrow UDFs are not supported in the {self.dialect} backend" - ) - - def _compile_pandas_udf(self, udf_node: ops.ScalarUDF) -> str: - raise NotImplementedError( - f"pandas UDFs are not supported in the {self.dialect} backend" - ) - - # Concrete API - - def if_(self, condition, true, false: sge.Expression | None = None) -> sge.If: - return sge.If( - this=sge.convert(condition), - true=sge.convert(true), - false=None if false is None else sge.convert(false), - ) - - def cast(self, arg, to: dt.DataType, format=None) -> sge.Cast: - return sge.Cast( - this=sge.convert(arg), to=self.type_mapper.from_ibis(to), copy=False - ) - - def _prepare_params(self, params): - result = {} - for param, value in params.items(): - node = param.op() - if isinstance(node, ops.Alias): - node = node.arg - result[node] = value - return result - - def to_sqlglot( - self, - expr: ir.Expr, - *, - limit: str | None = None, - params: Mapping[ir.Expr, Any] | None = None, - ): - import bigframes_vendored.ibis - - table_expr = expr.as_table() - - if limit == "default": - limit = bigframes_vendored.ibis.options.sql.default_limit - if limit is not None: - table_expr = table_expr.limit(limit) - - if params is None: - params = {} - - sql = self.translate(table_expr.op(), params=params) - assert not isinstance(sql, sge.Subquery) - - if isinstance(sql, sge.Table): - sql = sg.select(STAR, copy=False).from_(sql, copy=False) - - assert not isinstance(sql, sge.Subquery) - return sql - - def translate(self, op, *, params: Mapping[ir.Value, Any]) -> sge.Expression: - """Translate an ibis operation to a sqlglot expression. - - Parameters - ---------- - op - An ibis operation - params - A mapping of expressions to concrete values - compiler - An instance of SQLGlotCompiler - translate_rel - Relation node translator - translate_val - Value node translator - - Returns - ------- - sqlglot.expressions.Expression - A sqlglot expression - - """ - # substitute parameters immediately to avoid having to define a - # ScalarParameter translation rule - params = self._prepare_params(params) - if self.lowered_ops: - op = op.replace(reduce(operator.or_, self.lowered_ops.values())) - op, ctes = sqlize( - op, - params=params, - rewrites=self.rewrites, - fuse_selects=options.sql.fuse_selects, - ) - - aliases = {} - counter = itertools.count() - - def fn(node, _, **kwargs): - result = self.visit_node(node, **kwargs) - - # if it's not a relation then we don't need to do anything special - if node is op or not isinstance(node, ops.Relation): - return result - - # alias ops.Views to their explicitly assigned name otherwise generate - alias = node.name if isinstance(node, ops.View) else f"t{next(counter)}" - aliases[node] = alias - - alias = sg.to_identifier(alias, quoted=self.quoted) - if isinstance(result, sge.Subquery): - return result.as_(alias, quoted=self.quoted) - else: - try: - return result.subquery(alias, copy=False) - except AttributeError: - return result.as_(alias, quoted=self.quoted) - - # apply translate rules in topological order - results = op.map(fn) - - # get the root node as a sqlglot select statement - out = results[op] - if isinstance(out, sge.Table): - out = sg.select(STAR, copy=False).from_(out, copy=False) - elif isinstance(out, sge.Subquery): - out = out.this - - # add cte definitions to the select statement - for cte in ctes: - alias = sg.to_identifier(aliases[cte], quoted=self.quoted) - out = out.with_( - alias, as_=results[cte].this, dialect=self.dialect, copy=False - ) - - return out - - def visit_node(self, op: ops.Node, **kwargs): - if isinstance(op, ops.ScalarUDF): - return self.visit_ScalarUDF(op, **kwargs) - elif isinstance(op, ops.AggUDF): - return self.visit_AggUDF(op, **kwargs) - else: - method = getattr(self, f"visit_{type(op).__name__}", None) - if method is not None: - return method(op, **kwargs) - else: - raise ibis_exceptions.OperationNotDefinedError( - f"No translation rule for {type(op).__name__}" - ) - - def visit_Field(self, op, *, rel, name): - return sg.column( - self._gen_valid_name(name), table=rel.alias_or_name, quoted=self.quoted - ) - - def visit_Cast(self, op, *, arg, to): - from_ = op.arg.dtype - - if from_.is_integer() and to.is_interval(): - return self._make_interval(arg, to.unit) - - return self.cast(arg, to) - - def visit_ScalarSubquery(self, op, *, rel): - return rel.this.subquery(copy=False) - - def visit_Alias(self, op, *, arg, name): - return arg - - def visit_Literal(self, op, *, value, dtype): - """Compile a literal value. - - This is the default implementation for compiling literal values. - - Most backends should not need to override this method unless they want - to handle NULL literals as well as every other type of non-null literal - including integers, floating point numbers, decimals, strings, etc. - - The logic here is: - - 1. If the value is None and the type is nullable, return NULL - 1. If the value is None and the type is not nullable, raise an error - 1. Call `visit_NonNullLiteral` method. - 1. If the previous returns `None`, call `visit_DefaultLiteral` method - else return the result of the previous step. - """ - if value is None: - if dtype.is_array(): - # hack: bq arrays are like semi-nullable, but want to treat as non-nullable for simplicity - # instead, use empty array as missing value sentinel - return self.cast(self.f.array(), dtype) - if dtype.nullable: - return NULL if dtype.is_null() else self.cast(NULL, dtype) - raise ibis_exceptions.UnsupportedOperationError( - f"Unsupported NULL for non-nullable type: {dtype!r}" - ) - else: - result = self.visit_NonNullLiteral(op, value=value, dtype=dtype) - if result is None: - return self.visit_DefaultLiteral(op, value=value, dtype=dtype) - return result - - def visit_NonNullLiteral(self, op, *, value, dtype): - """Compile a non-null literal differently than the default implementation. - - Most backends should implement this, but only when they need to handle - some non-null literal differently than the default implementation - (`visit_DefaultLiteral`). - - Return `None` from an override of this method to fall back to - `visit_DefaultLiteral`. - """ - return self.visit_DefaultLiteral(op, value=value, dtype=dtype) - - def visit_DefaultLiteral(self, op, *, value, dtype): - """Compile a literal with a non-null value. - - This is the default implementation for compiling non-null literals. - - Most backends should not need to override this method unless they want - to handle compiling every kind of non-null literal value. - """ - if dtype.is_integer(): - return sge.convert(value) - elif dtype.is_floating(): - if math.isnan(value): - return self.NAN - elif math.isinf(value): - return self.POS_INF if value > 0 else self.NEG_INF - return sge.convert(value) - elif dtype.is_decimal(): - return self.cast(str(value), dtype) - elif dtype.is_interval(): - return sge.Interval( - this=sge.convert(str(value)), - unit=sge.Var(this=dtype.resolution.upper()), - ) - elif dtype.is_boolean(): - return sge.Boolean(this=bool(value)) - elif dtype.is_string(): - return sge.convert(value) - elif dtype.is_inet() or dtype.is_macaddr(): - return sge.convert(str(value)) - elif dtype.is_timestamp() or dtype.is_time(): - return self.cast(value.isoformat(), dtype) - elif dtype.is_date(): - return self.f.datefromparts(value.year, value.month, value.day) - elif dtype.is_array(): - # array type is ambiguous if no elements - value_type = dtype.value_type - values = self.f.array( - *( - self.visit_Literal( - ops.Literal(v, value_type), value=v, dtype=value_type - ) - for v in value - ) - ) - return values if len(value) > 0 else self.cast(values, dtype) - elif dtype.is_map(): - key_type = dtype.key_type - keys = self.f.array( - *( - self.visit_Literal( - ops.Literal(k, key_type), value=k, dtype=key_type - ) - for k in value.keys() - ) - ) - - value_type = dtype.value_type - values = self.f.array( - *( - self.visit_Literal( - ops.Literal(v, value_type), value=v, dtype=value_type - ) - for v in value.values() - ) - ) - - return self.f.map(keys, values) - elif dtype.is_struct(): - items = [ - self.visit_Literal( - ops.Literal(v, field_dtype), value=v, dtype=field_dtype - ).as_(k, quoted=self.quoted) - for field_dtype, (k, v) in zip(dtype.types, value.items()) - ] - return sge.Struct.from_arg_list(items) - elif dtype.is_uuid(): - return self.cast(str(value), dtype) - elif dtype.is_json(): - return sge.JSON(this=sge.convert(str(value))) - elif dtype.is_geospatial(): - wkt = value if isinstance(value, str) else value.wkt - return self.f.st_geogfromtext(wkt) - - raise NotImplementedError(f"Unsupported type: {dtype!r}") - - def visit_BitwiseNot(self, op, *, arg): - return sge.BitwiseNot(this=arg) - - ### Mathematical Calisthenics - - def visit_E(self, op): - return self.f.exp(1) - - def visit_Log(self, op, *, arg, base): - if base is None: - return self.f.ln(arg) - elif str(base) in ("2", "10"): - return self.f[f"log{base}"](arg) - else: - return self.f.ln(arg) / self.f.ln(base) - - def visit_Clip(self, op, *, arg, lower, upper): - if upper is not None: - arg = self.if_(arg.is_(NULL), arg, self.f.least(upper, arg)) - - if lower is not None: - arg = self.if_(arg.is_(NULL), arg, self.f.greatest(lower, arg)) - - return arg - - def visit_FloorDivide(self, op, *, left, right): - return self.cast(self.f.floor(left / right), op.dtype) - - def visit_Ceil(self, op, *, arg): - return self.cast(self.f.ceil(arg), op.dtype) - - def visit_Floor(self, op, *, arg): - return self.cast(self.f.floor(arg), op.dtype) - - def visit_Round(self, op, *, arg, digits): - if digits is not None: - return sge.Round(this=arg, decimals=digits) - return sge.Round(this=arg) - - ### Random Noise - - def visit_RandomScalar(self, op, **kwargs): - return self.f.rand() - - def visit_RandomUUID(self, op, **kwargs): - return self.f.uuid() - - ### Dtype Dysmorphia - - def visit_TryCast(self, op, *, arg, to): - return sge.TryCast(this=arg, to=self.type_mapper.from_ibis(to)) - - ### Comparator Conundrums - - def visit_Between(self, op, *, arg, lower_bound, upper_bound): - return sge.Between(this=arg, low=lower_bound, high=upper_bound) - - def visit_Negate(self, op, *, arg): - return -sge.paren(arg, copy=False) - - def visit_Not(self, op, *, arg): - if isinstance(arg, sge.Filter): - return sge.Filter( - this=sg.not_(arg.this, copy=False), expression=arg.expression - ) - return sg.not_(sge.paren(arg, copy=False)) - - ### Timey McTimeFace - - def visit_Time(self, op, *, arg): - return self.cast(arg, to=dt.time) - - def visit_TimestampNow(self, op): - return sge.CurrentTimestamp() - - def visit_DateNow(self, op): - return sge.CurrentDate() - - def visit_Strftime(self, op, *, arg, format_str): - return sge.TimeToStr(this=arg, format=format_str) - - def visit_ExtractEpochSeconds(self, op, *, arg): - return self.f.epoch(self.cast(arg, dt.timestamp)) - - def visit_ExtractYear(self, op, *, arg): - return self.f.extract(self.v.year, arg) - - def visit_ExtractMonth(self, op, *, arg): - return self.f.extract(self.v.month, arg) - - def visit_ExtractDay(self, op, *, arg): - return self.f.extract(self.v.day, arg) - - def visit_ExtractDayOfYear(self, op, *, arg): - return self.f.extract(self.v.dayofyear, arg) - - def visit_ExtractQuarter(self, op, *, arg): - return self.f.extract(self.v.quarter, arg) - - def visit_ExtractWeekOfYear(self, op, *, arg): - return self.f.extract(self.v.week, arg) - - def visit_ExtractHour(self, op, *, arg): - return self.f.extract(self.v.hour, arg) - - def visit_ExtractMinute(self, op, *, arg): - return self.f.extract(self.v.minute, arg) - - def visit_ExtractSecond(self, op, *, arg): - return self.f.extract(self.v.second, arg) - - def visit_TimestampTruncate(self, op, *, arg, unit): - unit_mapping = { - "Y": "year", - "Q": "quarter", - "M": "month", - "W": "week", - "D": "day", - "h": "hour", - "m": "minute", - "s": "second", - "ms": "ms", - "us": "us", - } - - if (raw_unit := unit_mapping.get(unit.short)) is None: - raise ibis_exceptions.UnsupportedOperationError( - f"Unsupported truncate unit {unit.short!r}" - ) - - return self.f.date_trunc(raw_unit, arg) - - def visit_DateTruncate(self, op, *, arg, unit): - return self.visit_TimestampTruncate(op, arg=arg, unit=unit) - - def visit_TimeTruncate(self, op, *, arg, unit): - return self.visit_TimestampTruncate(op, arg=arg, unit=unit) - - def visit_DayOfWeekIndex(self, op, *, arg): - return (self.f.dayofweek(arg) + 6) % 7 - - def visit_DayOfWeekName(self, op, *, arg): - # day of week number is 0-indexed - # Sunday == 0 - # Saturday == 6 - return sge.Case( - this=(self.f.dayofweek(arg) + 6) % 7, - ifs=list(itertools.starmap(self.if_, enumerate(calendar.day_name))), - ) - - def _make_interval(self, arg, unit): - return sge.Interval(this=arg, unit=self.v[unit.singular]) - - def visit_IntervalFromInteger(self, op, *, arg, unit): - return self._make_interval(arg, unit) - - ### String Instruments - def visit_Strip(self, op, *, arg): - return self.f.trim(arg, string.whitespace) - - def visit_RStrip(self, op, *, arg): - return self.f.rtrim(arg, string.whitespace) - - def visit_LStrip(self, op, *, arg): - return self.f.ltrim(arg, string.whitespace) - - def visit_Substring(self, op, *, arg, start, length): - if isinstance(op.length, ops.Literal) and (value := op.length.value) < 0: - raise ibis_exceptions.IbisInputError( - f"Length parameter must be a non-negative value; got {value}" - ) - start += 1 - start = self.if_(start >= 1, start, start + self.f.length(arg)) - if length is None: - return self.f.substring(arg, start) - return self.f.substring(arg, start, length) - - def visit_StringFind(self, op, *, arg, substr, start, end): - if end is not None: - raise ibis_exceptions.UnsupportedOperationError( - "String find doesn't support `end` argument" - ) - - if start is not None: - arg = self.f.substr(arg, start + 1) - pos = self.f.strpos(arg, substr) - return self.if_(pos > 0, pos + start, 0) - - return self.f.strpos(arg, substr) - - def visit_RegexReplace(self, op, *, arg, pattern, replacement): - return self.f.regexp_replace(arg, pattern, replacement, "g") - - def visit_StringConcat(self, op, *, arg): - return self.f.concat(*arg) - - def visit_StringJoin(self, op, *, sep, arg): - return self.f.concat_ws(sep, *arg) - - def visit_StringSQLLike(self, op, *, arg, pattern, escape): - return arg.like(pattern) - - def visit_StringSQLILike(self, op, *, arg, pattern, escape): - return arg.ilike(pattern) - - ### NULL PLAYER CHARACTER - def visit_IsNull(self, op, *, arg): - return arg.is_(NULL) - - def visit_NotNull(self, op, *, arg): - return self._add_parens(op, arg).is_(sg.not_(NULL, copy=False)) - - def visit_InValues(self, op, *, value, options): - return value.isin(*options) - - ### Counting - - def visit_CountDistinct(self, op, *, arg, where): - return self.agg.count(sge.Distinct(expressions=[arg]), where=where) - - def visit_CountDistinctStar(self, op, *, arg, where): - return self.agg.count(sge.Distinct(expressions=[STAR]), where=where) - - def visit_CountStar(self, op, *, arg, where): - return self.agg.count(STAR, where=where) - - def visit_Sum(self, op, *, arg, where): - if op.arg.dtype.is_boolean(): - arg = self.cast(arg, dt.int32) - return self.agg.sum(arg, where=where) - - def visit_Mean(self, op, *, arg, where): - if op.arg.dtype.is_boolean(): - arg = self.cast(arg, dt.int32) - return self.agg.avg(arg, where=where) - - def visit_Min(self, op, *, arg, where): - if op.arg.dtype.is_boolean(): - return self.agg.bool_and(arg, where=where) - return self.agg.min(arg, where=where) - - def visit_Max(self, op, *, arg, where): - if op.arg.dtype.is_boolean(): - return self.agg.bool_or(arg, where=where) - return self.agg.max(arg, where=where) - - ### Stats - - def visit_VarianceStandardDevCovariance(self, op, *, how, where, **kw): - hows = {"sample": "samp", "pop": "pop"} - funcs = { - ops.Variance: "var", - ops.StandardDev: "stddev", - ops.Covariance: "covar", - } - - args = [] - - for oparg, arg in zip(op.args, kw.values()): - if (arg_dtype := oparg.dtype).is_boolean(): - arg = self.cast(arg, dt.Int32(nullable=arg_dtype.nullable)) - args.append(arg) - - funcname = f"{funcs[type(op)]}_{hows[how]}" - return self.agg[funcname](*args, where=where) - - visit_Variance = visit_StandardDev = visit_Covariance = ( - visit_VarianceStandardDevCovariance - ) - - def visit_SimpleCase(self, op, *, base=None, cases, results, default): - return sge.Case( - this=base, ifs=list(map(self.if_, cases, results)), default=default - ) - - visit_SearchedCase = visit_SimpleCase - - def visit_SqlScalar(self, op, *, sql_template, values, output_type): - # TODO: can we include a string in the sqlglot expression without parsing? - return sg.parse_one( - sql_template.format(*[value.sql(dialect="bigquery") for value in values]), - dialect="bigquery", - ) - - def visit_ExistsSubquery(self, op, *, rel): - select = rel.this.select(1, append=False) - return self.f.exists(select) - - def visit_InSubquery(self, op, *, rel, needle): - query = rel.this - if not isinstance(query, sge.Select): - query = sg.select(STAR).from_(query) - return needle.isin(query=query) - - def visit_Array(self, op, *, exprs): - return self.f.array(*exprs) - - def visit_StructColumn(self, op, *, names, values): - return sge.Struct.from_arg_list( - [value.as_(name, quoted=self.quoted) for name, value in zip(names, values)] - ) - - def visit_StructField(self, op, *, arg, field): - return sge.Dot(this=arg, expression=sg.to_identifier(field, quoted=self.quoted)) - - def visit_IdenticalTo(self, op, *, left, right): - return sge.NullSafeEQ(this=left, expression=right) - - def visit_Greatest(self, op, *, arg): - return self.f.greatest(*arg) - - def visit_Least(self, op, *, arg): - return self.f.least(*arg) - - def visit_Coalesce(self, op, *, arg): - return self.f.coalesce(*arg) - - ### Ordering and window functions - - def visit_SortKey(self, op, *, expr, ascending: bool, nulls_first: bool = False): - return sge.Ordered(this=expr, desc=not ascending, nulls_first=nulls_first) - - def visit_ApproxMedian(self, op, *, arg, where): - return self.agg.approx_quantile(arg, 0.5, where=where) - - def visit_WindowBoundary(self, op, *, value, preceding): - # TODO: bit of a hack to return a dict, but there's no sqlglot expression - # that corresponds to _only_ this information - return {"value": value, "side": "preceding" if preceding else "following"} - - def visit_WindowFunction(self, op, *, how, func, start, end, group_by, order_by): - if start is None: - start = {} - if end is None: - end = {} - - start_value = start.get("value", "UNBOUNDED") - start_side = start.get("side", "PRECEDING") - end_value = end.get("value", "UNBOUNDED") - end_side = end.get("side", "FOLLOWING") - - if getattr(start_value, "this", None) == "0": - start_value = "CURRENT ROW" - start_side = None - - if getattr(end_value, "this", None) == "0": - end_value = "CURRENT ROW" - end_side = None - - spec = sge.WindowSpec( - kind=how.upper(), - start=start_value, - start_side=start_side, - end=end_value, - end_side=end_side, - over="OVER", - ) - order = sge.Order(expressions=order_by) if order_by else None - - spec = self._minimize_spec(op.start, op.end, spec) - - return sge.Window(this=func, partition_by=group_by, order=order, spec=spec) - - @staticmethod - def _minimize_spec(start, end, spec): - return spec - - def visit_LagLead(self, op, *, arg, offset, default): - args = [arg] - - if default is not None: - if offset is None: - offset = 1 - - args.append(offset) - args.append(default) - elif offset is not None: - args.append(offset) - - return self.f[type(op).__name__.lower()](*args) - - visit_Lag = visit_Lead = visit_LagLead - - def visit_Argument(self, op, *, name: str, shape, dtype): - return sg.to_identifier(op.param) - - def visit_RowID(self, op, *, table): - return sg.column( - op.name, table=table.alias_or_name, quoted=self.quoted, copy=False - ) - - # TODO(kszucs): this should be renamed to something UDF related - def __sql_name__(self, op: ops.ScalarUDF | ops.AggUDF) -> str: - # for builtin functions use the exact function name, otherwise use the - # generated name to handle the case of redefinition - funcname = ( - op.__func_name__ - if op.__input_type__ == InputType.BUILTIN - else type(op).__name__ - ) - - # not actually a table, but easier to quote individual namespace - # components this way - namespace = op.__udf_namespace__ - - # Function names prefixed with "SAFE.", such as `SAFE.PARSE_JSON`, - # are typically not quoted. - if funcname.startswith("SAFE."): - return funcname - - return sg.table(funcname, db=namespace.database, catalog=namespace.catalog).sql( - self.dialect - ) - - def visit_ScalarUDF(self, op, **kw): - return self.f[self.__sql_name__(op)](*kw.values()) - - def visit_AggUDF(self, op, *, where, **kw): - return self.agg[self.__sql_name__(op)](*kw.values(), where=where) - - def visit_TimestampDelta(self, op, *, part, left, right): - # dialect is necessary due to sqlglot's default behavior - # of `part` coming last - return sge.DateDiff( - this=left, expression=right, unit=part, dialect=self.dialect - ) - - visit_TimeDelta = visit_DateDelta = visit_TimestampDelta - - def visit_TimestampBucket(self, op, *, arg, interval, offset): - origin = self.f.cast("epoch", self.type_mapper.from_ibis(dt.timestamp)) - if offset is not None: - origin += offset - return self.f.time_bucket(interval, arg, origin) - - def visit_ArrayConcat(self, op, *, arg): - return sge.ArrayConcat(this=arg[0], expressions=list(arg[1:])) - - ## relations - - @staticmethod - def _gen_valid_name(name: str) -> str: - """Generate a valid name for a value expression. - - Override this method if the dialect has restrictions on valid - identifiers even when quoted. - - See the BigQuery backend's implementation for an example. - """ - return name - - def _cleanup_names(self, exprs: Mapping[str, sge.Expression]): - """Compose `_gen_valid_name` and `_dedup_name` to clean up names in projections.""" - - for name, value in exprs.items(): - name = self._gen_valid_name(name) - if isinstance(value, sge.Column) and name == value.name: - # don't alias columns that are already named the same as their alias - yield value - else: - yield value.as_(name, quoted=self.quoted, copy=False) - - def visit_Select(self, op, *, parent, selections, predicates, qualified, sort_keys): - # if we've constructed a useless projection return the parent relation - if not (selections or predicates or qualified or sort_keys): - return parent - - result = parent - - if selections: - # if there are `qualify` predicates then sqlglot adds a hidden - # column to implement the functionality if the dialect doesn't - # support it - # - # using STAR in that case would lead to an extra column, so in that - # case we have to spell out the columns - if op.is_star_selection() and (not qualified or self.supports_qualify): - fields = [STAR] - else: - fields = self._cleanup_names(selections) - result = sg.select(*fields, copy=False).from_(result, copy=False) - - if predicates: - result = result.where(*predicates, copy=False) - - if qualified: - result = result.qualify(*qualified, copy=False) - - if sort_keys: - result = result.order_by(*sort_keys, copy=False) - - return result - - def visit_DummyTable(self, op, *, values): - return sg.select(*self._cleanup_names(values), copy=False) - - def visit_UnboundTable( - self, op, *, name: str, schema: sch.Schema, namespace: ops.Namespace - ) -> sg.Table: - return sg.table( - name, db=namespace.database, catalog=namespace.catalog, quoted=self.quoted - ) - - def visit_InMemoryTable( - self, op, *, name: str, schema: sch.Schema, data - ) -> sg.Table: - return sg.table(name, quoted=self.quoted) - - def visit_DatabaseTable( - self, - op, - *, - name: str, - schema: sch.Schema, - source: Any, - namespace: ops.Namespace, - ) -> sg.Table: - return sg.table( - name, db=namespace.database, catalog=namespace.catalog, quoted=self.quoted - ) - - def visit_SelfReference(self, op, *, parent, identifier): - return parent - - visit_JoinReference = visit_SelfReference - - def visit_JoinChain(self, op, *, first, rest, values): - result = sg.select(*self._cleanup_names(values), copy=False).from_( - first, copy=False - ) - - for link in rest: - if isinstance(link, sge.Alias): - link = link.this - result = result.join(link, copy=False) - return result - - def visit_JoinLink(self, op, *, how, table, predicates): - sides = { - "inner": None, - "left": "left", - "right": "right", - "semi": "left", - "anti": "left", - "cross": None, - "outer": "full", - "asof": "asof", - "any_left": "left", - "any_inner": None, - "positional": None, - } - kinds = { - "any_left": "any", - "any_inner": "any", - "asof": "left", - "inner": "inner", - "left": "outer", - "right": "outer", - "semi": "semi", - "anti": "anti", - "cross": "cross", - "outer": "outer", - "positional": "positional", - } - assert predicates or how in { - "cross", - "positional", - }, "expected non-empty predicates when not a cross join" - on = sg.and_(*predicates) if predicates else None - return sge.Join(this=table, side=sides[how], kind=kinds[how], on=on) - - @staticmethod - def _generate_groups(groups): - return map(sge.convert, range(1, len(groups) + 1)) - - def visit_Aggregate(self, op, *, parent, groups, metrics): - exprs = [] - if groups: - exprs.extend(self._cleanup_names(groups)) - if metrics: - exprs.extend(self._cleanup_names(metrics)) - - if not exprs: - # Empty aggregated projections are invalid in BigQuery - exprs = [sge.Literal.number(1)] - - sel = sg.select(*exprs, copy=False).from_(parent, copy=False) - - if groups: - sel = sel.group_by(*self._generate_groups(groups.values()), copy=False) - - return sel - - @classmethod - def _add_parens(cls, op, sg_expr): - # Patch for https://github.com/ibis-project/ibis/issues/9975 - if isinstance(op, cls.NEEDS_PARENS) or ( - isinstance(op, ops.Alias) and isinstance(op.arg, cls.NEEDS_PARENS) - ): - return sge.paren(sg_expr, copy=False) - return sg_expr - - def visit_Union(self, op, *, left, right, distinct): - if isinstance(left, (sge.Table, sge.Subquery)): - left = sg.select(STAR, copy=False).from_(left, copy=False) - - if isinstance(right, (sge.Table, sge.Subquery)): - right = sg.select(STAR, copy=False).from_(right, copy=False) - - return sg.union( - left.args.get("this", left), - right.args.get("this", right), - distinct=distinct, - copy=False, - ) - - def visit_Intersection(self, op, *, left, right, distinct): - if isinstance(left, (sge.Table, sge.Subquery)): - left = sg.select(STAR, copy=False).from_(left, copy=False) - - if isinstance(right, (sge.Table, sge.Subquery)): - right = sg.select(STAR, copy=False).from_(right, copy=False) - - return sg.intersect( - left.args.get("this", left), - right.args.get("this", right), - distinct=distinct, - copy=False, - ) - - def visit_Difference(self, op, *, left, right, distinct): - if isinstance(left, (sge.Table, sge.Subquery)): - left = sg.select(STAR, copy=False).from_(left, copy=False) - - if isinstance(right, (sge.Table, sge.Subquery)): - right = sg.select(STAR, copy=False).from_(right, copy=False) - - return sg.except_( - left.args.get("this", left), - right.args.get("this", right), - distinct=distinct, - copy=False, - ) - - def visit_Limit(self, op, *, parent, n, offset): - # push limit/offset into subqueries - if isinstance(parent, sge.Subquery) and parent.this.args.get("limit") is None: - result = parent.this.copy() - alias = parent.alias - else: - result = sg.select(STAR, copy=False).from_(parent, copy=False) - alias = None - - if isinstance(n, int): - result = result.limit(n, copy=False) - elif n is not None: - result = result.limit( - sg.select(n, copy=False).from_(parent, copy=False).subquery(copy=False), - copy=False, - ) - else: - assert n is None, n - if self.no_limit_value is not None: - result = result.limit(self.no_limit_value, copy=False) - - assert offset is not None, "offset is None" - - if not isinstance(offset, int): - skip = offset - skip = ( - sg.select(skip, copy=False) - .from_(parent, copy=False) - .subquery(copy=False) - ) - elif not offset: - if alias is not None: - return result.subquery(alias, copy=False) - return result - else: - skip = offset - - result = result.offset(skip, copy=False) - if alias is not None: - return result.subquery(alias, copy=False) - return result - - def visit_Distinct(self, op, *, parent): - return ( - sg.select(STAR, copy=False).distinct(copy=False).from_(parent, copy=False) - ) - - def visit_CTE(self, op, *, parent): - return sg.table(parent.alias_or_name, quoted=self.quoted) - - def visit_View(self, op, *, child, name: str): - if isinstance(child, sge.Table): - child = sg.select(STAR, copy=False).from_(child, copy=False) - else: - child = child.copy() - - if isinstance(child, sge.Subquery): - return child.as_(name, quoted=self.quoted) - else: - try: - return child.subquery(name, copy=False) - except AttributeError: - return child.as_(name, quoted=self.quoted) - - def visit_SQLStringView(self, op, *, query: str, child, schema): - return sg.parse_one(query, read=self.dialect) - - def visit_SQLQueryResult(self, op, *, query, schema, source): - return sg.parse_one(query, dialect=self.dialect).subquery(copy=False) - - def visit_RegexExtract(self, op, *, arg, pattern, index): - return self.f.regexp_extract(arg, pattern, index, dialect=self.dialect) - - @parenthesize_inputs - def visit_Add(self, op, *, left, right): - return sge.Add(this=left, expression=right) - - visit_DateAdd = visit_TimestampAdd = visit_IntervalAdd = visit_Add - - @parenthesize_inputs - def visit_Subtract(self, op, *, left, right): - return sge.Sub(this=left, expression=right) - - visit_DateSub = visit_DateDiff = visit_TimestampSub = visit_TimestampDiff = ( - visit_IntervalSubtract - ) = visit_Subtract - - @parenthesize_inputs - def visit_Multiply(self, op, *, left, right): - return sge.Mul(this=left, expression=right) - - visit_IntervalMultiply = visit_Multiply - - @parenthesize_inputs - def visit_Divide(self, op, *, left, right): - return sge.Div(this=left, expression=right) - - @parenthesize_inputs - def visit_Modulus(self, op, *, left, right): - return sge.Mod(this=left, expression=right) - - @parenthesize_inputs - def visit_Power(self, op, *, left, right): - return sge.Pow(this=left, expression=right) - - @parenthesize_inputs - def visit_GreaterEqual(self, op, *, left, right): - return sge.GTE(this=left, expression=right) - - @parenthesize_inputs - def visit_Greater(self, op, *, left, right): - return sge.GT(this=left, expression=right) - - @parenthesize_inputs - def visit_LessEqual(self, op, *, left, right): - return sge.LTE(this=left, expression=right) - - @parenthesize_inputs - def visit_Less(self, op, *, left, right): - return sge.LT(this=left, expression=right) - - @parenthesize_inputs - def visit_Equals(self, op, *, left, right): - return sge.EQ(this=left, expression=right) - - @parenthesize_inputs - def visit_NotEquals(self, op, *, left, right): - return sge.NEQ(this=left, expression=right) - - @parenthesize_inputs - def visit_And(self, op, *, left, right): - return sge.And(this=left, expression=right) - - @parenthesize_inputs - def visit_Or(self, op, *, left, right): - return sge.Or(this=left, expression=right) - - @parenthesize_inputs - def visit_Xor(self, op, *, left, right): - return sge.Xor(this=left, expression=right) - - @parenthesize_inputs - def visit_BitwiseLeftShift(self, op, *, left, right): - return sge.BitwiseLeftShift(this=left, expression=right) - - @parenthesize_inputs - def visit_BitwiseRightShift(self, op, *, left, right): - return sge.BitwiseRightShift(this=left, expression=right) - - @parenthesize_inputs - def visit_BitwiseAnd(self, op, *, left, right): - return sge.BitwiseAnd(this=left, expression=right) - - @parenthesize_inputs - def visit_BitwiseOr(self, op, *, left, right): - return sge.BitwiseOr(this=left, expression=right) - - @parenthesize_inputs - def visit_BitwiseXor(self, op, *, left, right): - return sge.BitwiseXor(this=left, expression=right) - - def visit_Undefined(self, op, **_): - raise ibis_exceptions.OperationNotDefinedError( - f"Compilation rule for {type(op).__name__!r} operation is not defined" - ) - - def visit_Unsupported(self, op, **_): - raise ibis_exceptions.UnsupportedOperationError( - f"{type(op).__name__!r} operation is not supported in the {self.dialect} backend" - ) - - def visit_DropColumns(self, op, *, parent, columns_to_drop): - # the generated query will be huge for wide tables - # - # TODO: figure out a way to produce an IR that only contains exactly - # what is used - parent_alias = parent.alias_or_name - quoted = self.quoted - columns_to_keep = ( - sg.column(column, table=parent_alias, quoted=quoted) - for column in op.schema.names - ) - return sg.select(*columns_to_keep).from_(parent) - - def add_query_to_expr(self, *, name: str, table: ir.Table, query: str) -> str: - dialect = self.dialect - - compiled_ibis_expr = self.to_sqlglot(table) - - # pull existing CTEs from the compiled Ibis expression and combine them - # with the new query - parsed = reduce( - lambda parsed, cte: parsed.with_(cte.args["alias"], as_=cte.args["this"]), - compiled_ibis_expr.ctes, - sg.parse_one(query, read=dialect), - ) - - # remove all ctes from the compiled expression, since they're now in - # our larger expression - compiled_ibis_expr.args.pop("with", None) - - # add the new str query as a CTE - parsed = parsed.with_( - sg.to_identifier(name, quoted=self.quoted), as_=compiled_ibis_expr - ) - - # generate the SQL string - return parsed.sql(dialect) - - def _make_sample_backwards_compatible(self, *, sample, parent): - # sample was changed to be owned by the table being sampled in 25.17.0 - # - # this is a small workaround for backwards compatibility - if "this" in sample.__class__.arg_types: - sample.args["this"] = parent - else: - parent.args["sample"] = sample - return sg.select(STAR).from_(parent) - - -# `__init_subclass__` is uncalled for subclasses - we manually call it here to -# autogenerate the base class implementations as well. -SQLGlotCompiler.__init_subclass__() diff --git a/third_party/bigframes_vendored/ibis/backends/sql/compilers/bigquery/__init__.py b/third_party/bigframes_vendored/ibis/backends/sql/compilers/bigquery/__init__.py deleted file mode 100644 index e47164f6c46..00000000000 --- a/third_party/bigframes_vendored/ibis/backends/sql/compilers/bigquery/__init__.py +++ /dev/null @@ -1,1220 +0,0 @@ -# Contains code from https://github.com/ibis-project/ibis/blob/main/ibis/backends/sql/compilers/bigquery/__init__.py -"""Module to convert from Ibis expression to SQL string.""" - -from __future__ import annotations - -import datetime -import decimal -import math -import re -from typing import TYPE_CHECKING, Any - -import bigframes_vendored.ibis.backends.bigquery.datatypes as bq_datatypes -import bigframes_vendored.ibis.common.exceptions as ibis_exceptions -import bigframes_vendored.ibis.expr.datatypes as dt -import bigframes_vendored.ibis.expr.operations as ops -import bigframes_vendored.sqlglot as sg -import bigframes_vendored.sqlglot.expressions as sge -import numpy as np -from bigframes_vendored.ibis import util -from bigframes_vendored.ibis.backends.sql.compilers.base import ( - NULL, - STAR, - AggGen, - SQLGlotCompiler, -) -from bigframes_vendored.ibis.backends.sql.datatypes import BigQueryType, BigQueryUDFType -from bigframes_vendored.ibis.backends.sql.rewrites import ( - exclude_unsupported_window_frame_from_ops, - exclude_unsupported_window_frame_from_rank, - exclude_unsupported_window_frame_from_row_number, -) -from bigframes_vendored.ibis.common.temporal import ( - DateUnit, - IntervalUnit, - TimestampUnit, - TimeUnit, -) -from bigframes_vendored.sqlglot.dialects import BigQuery - -if TYPE_CHECKING: - from collections.abc import Mapping - - import bigframes_vendored.ibis.expr.types as ir - - -_NAME_REGEX = re.compile(r'[^!"$()*,./;?@[\\\]^`{}~\n]+') - - -_MEMTABLE_PATTERN = re.compile( - r"^_?ibis_(?:[A-Za-z_][A-Za-z_0-9]*)_memtable_[a-z0-9]{26}$" -) - - -def _qualify_memtable( - node: sge.Expression, *, dataset: str | None, project: str | None -) -> sge.Expression: - """Add a BigQuery dataset and project to memtable references.""" - if isinstance(node, sge.Table) and _MEMTABLE_PATTERN.match(node.name) is not None: - node.args["db"] = dataset - node.args["catalog"] = project - return node - - -def _remove_null_ordering_from_unsupported_window( - node: sge.Expression, -) -> sge.Expression: - """Remove null ordering in window frame clauses not supported by BigQuery. - BigQuery has only partial support for NULL FIRST/LAST in RANGE windows so - we remove it from any window frame clause that doesn't support it. - Here's the support matrix: - ✅ sum(x) over (order by y desc nulls last) - 🚫 sum(x) over (order by y asc nulls last) - ✅ sum(x) over (order by y asc nulls first) - 🚫 sum(x) over (order by y desc nulls first) - """ - if isinstance(node, sge.Window): - order = node.args.get("order") - if order is not None: - for key in order.args["expressions"]: - kargs = key.args - if kargs.get("desc") is True and kargs.get("nulls_first", False): - kargs["nulls_first"] = False - elif kargs.get("desc") is False and not kargs.setdefault( - "nulls_first", True - ): - kargs["nulls_first"] = True - return node - - -class BigQueryCompiler(SQLGlotCompiler): - dialect = BigQuery - type_mapper = BigQueryType - udf_type_mapper = BigQueryUDFType - - agg = AggGen(supports_order_by=True) - - rewrites = ( - exclude_unsupported_window_frame_from_ops, - exclude_unsupported_window_frame_from_row_number, - exclude_unsupported_window_frame_from_rank, - *SQLGlotCompiler.rewrites, - ) - - supports_qualify = True - - UNSUPPORTED_OPS = ( - ops.DateDiff, - ops.ExtractAuthority, - ops.ExtractUserInfo, - ops.FindInSet, - ops.Median, - ops.RegexSplit, - ops.RowID, - ops.TimestampDiff, - ) - - NAN = sge.Cast( - this=sge.convert("NaN"), to=sge.DataType(this=sge.DataType.Type.DOUBLE) - ) - POS_INF = sge.Cast( - this=sge.convert("Infinity"), to=sge.DataType(this=sge.DataType.Type.DOUBLE) - ) - NEG_INF = sge.Cast( - this=sge.convert("-Infinity"), to=sge.DataType(this=sge.DataType.Type.DOUBLE) - ) - - SIMPLE_OPS = { - ops.Arbitrary: "any_value", - ops.StringAscii: "ascii", - ops.BitAnd: "bit_and", - ops.BitOr: "bit_or", - ops.BitXor: "bit_xor", - ops.DateFromYMD: "date", - ops.Divide: "ieee_divide", - ops.EndsWith: "ends_with", - ops.GeoArea: "st_area", - ops.GeoAsBinary: "st_asbinary", - ops.GeoAsText: "st_astext", - ops.GeoAzimuth: "st_azimuth", - ops.GeoBuffer: "st_buffer", - ops.GeoCentroid: "st_centroid", - ops.GeoContains: "st_contains", - ops.GeoCoveredBy: "st_coveredby", - ops.GeoCovers: "st_covers", - ops.GeoDWithin: "st_dwithin", - ops.GeoDifference: "st_difference", - ops.GeoDisjoint: "st_disjoint", - ops.GeoDistance: "st_distance", - ops.GeoEndPoint: "st_endpoint", - ops.GeoEquals: "st_equals", - ops.GeoGeometryType: "st_geometrytype", - ops.GeoIntersection: "st_intersection", - ops.GeoIntersects: "st_intersects", - ops.GeoLength: "st_length", - ops.GeoMaxDistance: "st_maxdistance", - ops.GeoNPoints: "st_numpoints", - ops.GeoPerimeter: "st_perimeter", - ops.GeoPoint: "st_geogpoint", - ops.GeoPointN: "st_pointn", - ops.GeoStartPoint: "st_startpoint", - ops.GeoTouches: "st_touches", - ops.GeoUnaryUnion: "st_union_agg", - ops.GeoUnion: "st_union", - ops.GeoWithin: "st_within", - ops.GeoX: "st_x", - ops.GeoY: "st_y", - ops.Hash: "farm_fingerprint", - ops.IsInf: "is_inf", - ops.IsNan: "is_nan", - ops.Log10: "log10", - ops.LPad: "lpad", - ops.RPad: "rpad", - ops.Levenshtein: "edit_distance", - ops.Modulus: "mod", - ops.RegexReplace: "regexp_replace", - ops.RegexSearch: "regexp_contains", - ops.Time: "time", - ops.TimeFromHMS: "time_from_parts", - ops.TimestampNow: "current_timestamp", - ops.ExtractHost: "net.host", - } - - def to_sqlglot( - self, - expr: ir.Expr, - *, - limit: str | None = None, - params: Mapping[ir.Expr, Any] | None = None, - session_dataset_id: str | None = None, - session_project: str | None = None, - ) -> Any: - """Compile an Ibis expression. - Parameters - ---------- - expr - Ibis expression - limit - For expressions yielding result sets; retrieve at most this number - of values/rows. Overrides any limit already set on the expression. - params - Named unbound parameters - session_dataset_id - Optional dataset ID to qualify memtable references. - session_project - Optional project ID to qualify memtable references. - Returns - ------- - Any - The output of compilation. The type of this value depends on the - backend. - """ - sql = super().to_sqlglot(expr, limit=limit, params=params) - - table_expr = expr.as_table() - geocols = getattr(table_expr.schema(), "geospatial", None) - - result = sql.transform( - _qualify_memtable, - dataset=session_dataset_id, - project=session_project, - ).transform(_remove_null_ordering_from_unsupported_window) - - if geocols: - # if there are any geospatial columns, we have to convert them to WKB, - # so interactive mode knows how to display them - # - # by default bigquery returns data to python as WKT, and there's really - # no point in supporting both if we don't need to. - quoted = self.quoted - result = sg.select( - sge.Star( - replace=[ - self.f.st_asbinary(sg.column(col, quoted=quoted)).as_( - col, quoted=quoted - ) - for col in geocols - ] - ) - ).from_(result.subquery()) - - sources = [] - - for udf_node in table_expr.op().find(ops.ScalarUDF): - compile_func = getattr( - self, f"_compile_{udf_node.__input_type__.name.lower()}_udf" - ) - if sql := compile_func(udf_node): - sources.append(sql) - - if not sources: - return result - - sources.append(result) - return sources - - def visit_BoundingBox(self, op, *, arg): - name = type(op).__name__[len("Geo") :].lower() - return sge.Dot( - this=self.f.st_boundingbox(arg), expression=sg.to_identifier(name) - ) - - visit_GeoXMax = visit_GeoXMin = visit_GeoYMax = visit_GeoYMin = visit_BoundingBox - - def visit_GeoRegionStats(self, op, *, arg, raster_id, band, include, options): - args = [arg, raster_id] - if op.band: - args.append(sge.Kwarg(this="band", expression=band)) - if op.include: - args.append(sge.Kwarg(this="include", expression=include)) - if op.options: - args.append(sge.Kwarg(this="options", expression=options)) - return sge.func("ST_REGIONSTATS", *args) - - def visit_GeoSimplify(self, op, *, arg, tolerance, preserve_collapsed): - if ( - not isinstance(op.preserve_collapsed, ops.Literal) - or op.preserve_collapsed.value - ): - raise ibis_exceptions.UnsupportedOperationError( - "BigQuery simplify does not support preserving collapsed geometries, " - "pass preserve_collapsed=False" - ) - return self.f.st_simplify(arg, tolerance) - - def visit_ApproxMedian(self, op, *, arg, where): - return self.agg.approx_quantiles(arg, 2, where=where)[self.f.offset(1)] - - def visit_Pi(self, op): - return self.f.acos(-1) - - def visit_E(self, op): - return self.f.exp(1) - - def visit_TimeDelta(self, op, *, left, right, part): - return self.f.time_diff(left, right, part, dialect=self.dialect) - - def visit_DateDelta(self, op, *, left, right, part): - return self.f.date_diff(left, right, part, dialect=self.dialect) - - def visit_TimestampDelta(self, op, *, left, right, part): - left_tz = op.left.dtype.timezone - right_tz = op.right.dtype.timezone - - if left_tz is None and right_tz is None: - return self.f.datetime_diff(left, right, part) - elif left_tz is not None and right_tz is not None: - return self.f.timestamp_diff(left, right, part) - - raise ibis_exceptions.UnsupportedOperationError( - "timestamp difference with mixed timezone/timezoneless values is not implemented" - ) - - def visit_GroupConcat(self, op, *, arg, sep, where, order_by): - if where is not None: - arg = self.if_(where, arg, NULL) - - if order_by: - sep = sge.Order(this=sep, expressions=order_by) - - return sge.GroupConcat(this=arg, separator=sep) - - def visit_ApproxQuantile(self, op, *, arg, quantile, where): - if not isinstance(op.quantile, ops.Literal): - raise ibis_exceptions.UnsupportedOperationError( - "quantile must be a literal in BigQuery" - ) - - # BigQuery syntax is `APPROX_QUANTILES(col, resolution)` to return - # `resolution + 1` quantiles array. To handle this, we compute the - # resolution ourselves then restructure the output array as needed. - # To avoid excessive resolution we arbitrarily cap it at 100,000 - - # since these are approximate quantiles anyway this seems fine. - quantiles = util.promote_list(op.quantile.value) - fracs = [decimal.Decimal(str(q)).as_integer_ratio() for q in quantiles] - resolution = min(math.lcm(*(den for _, den in fracs)), 100_000) - indices = [(num * resolution) // den for num, den in fracs] - - if where is not None: - arg = self.if_(where, arg, NULL) - - if not op.arg.dtype.is_floating(): - arg = self.cast(arg, dt.float64) - - array = self.f.approx_quantiles( - arg, sge.IgnoreNulls(this=sge.convert(resolution)) - ) - if isinstance(op, ops.ApproxQuantile): - return array[indices[0]] - - if indices == list(range(resolution + 1)): - return array - else: - return sge.Array(expressions=[array[i] for i in indices]) - - visit_ApproxMultiQuantile = visit_ApproxQuantile - - def visit_FloorDivide(self, op, *, left, right): - return self.cast(self.f.floor(self.f.ieee_divide(left, right)), op.dtype) - - def visit_Log2(self, op, *, arg): - return self.f.log(arg, 2, dialect=self.dialect) - - def visit_Log(self, op, *, arg, base): - if base is None: - return self.f.ln(arg) - return self.f.log(arg, base, dialect=self.dialect) - - def visit_ArrayRepeat(self, op, *, arg, times): - start = step = 1 - array_length = self.f.array_length(arg) - stop = self.f.greatest(times, 0) * array_length - i = sg.to_identifier("i") - idx = self.f.coalesce( - self.f.nullif(self.f.mod(i, array_length), 0), array_length - ) - series = self.f.generate_array(start, stop, step) - return self.f.array( - sg.select(arg[self.f.safe_ordinal(idx)]).from_(self._unnest(series, as_=i)) - ) - - def visit_NthValue(self, op, *, arg, nth): - if not isinstance(op.nth, ops.Literal): - raise ibis_exceptions.UnsupportedOperationError( - f"BigQuery `nth` must be a literal; got {type(op.nth)}" - ) - return self.f.nth_value(arg, nth) - - def visit_StrRight(self, op, *, arg, nchars): - return self.f.substr(arg, -self.f.least(self.f.length(arg), nchars)) - - def visit_StringJoin(self, op, *, arg, sep): - return self.f.array_to_string(self.f.array(*arg), sep) - - def visit_DayOfWeekIndex(self, op, *, arg): - return self.f.mod(self.f.extract(self.v.dayofweek, arg) + 5, 7) - - def visit_DayOfWeekName(self, op, *, arg): - return self.f.initcap(sge.Cast(this=arg, to="STRING FORMAT 'DAY'")) - - def visit_StringToTimestamp(self, op, *, arg, format_str): - if (timezone := op.dtype.timezone) is not None: - return self.f.parse_timestamp(format_str, arg, timezone) - return self.f.parse_datetime(format_str, arg) - - def visit_ArrayCollect(self, op, *, arg, where, order_by, include_null): - if where is not None and include_null: - raise ibis_exceptions.UnsupportedOperationError( - "Combining `include_null=True` and `where` is not supported by bigquery" - ) - out = self.agg.array_agg(arg, where=where, order_by=order_by) - if not include_null: - out = sge.IgnoreNulls(this=out) - return out - - def _neg_idx_to_pos(self, arg, idx): - return self.if_(idx < 0, self.f.array_length(arg) + idx, idx) - - def visit_ArraySlice(self, op, *, arg, start, stop): - index = sg.to_identifier("bq_arr_slice") - cond = [index >= self._neg_idx_to_pos(arg, start)] - - if stop is not None: - cond.append(index < self._neg_idx_to_pos(arg, stop)) - - el = sg.to_identifier("el") - return self.f.array( - sg.select(el).from_(self._unnest(arg, as_=el, offset=index)).where(*cond) - ) - - def visit_ArrayIndex(self, op, *, arg, index): - return arg[self.f.safe_offset(index)] - - def visit_ArrayContains(self, op, *, arg, other): - name = sg.to_identifier(util.gen_name("bq_arr_contains")) - return sge.Exists( - this=sg.select(sge.convert(1)) - .from_(self._unnest(arg, as_=name)) - .where(name.eq(other)) - ) - - def visit_StringContains(self, op, *, haystack, needle): - return self.f.strpos(haystack, needle) > 0 - - def visit_StringFind(self, op, *, arg, substr, start, end): - if start is not None: - raise NotImplementedError( - "`start` not implemented for BigQuery string find" - ) - if end is not None: - raise NotImplementedError("`end` not implemented for BigQuery string find") - return self.f.strpos(arg, substr) - - def visit_TimestampFromYMDHMS( - self, op, *, year, month, day, hours, minutes, seconds - ): - return self.f.anon.DATETIME(year, month, day, hours, minutes, seconds) - - def visit_NonNullLiteral(self, op, *, value, dtype): - if dtype.is_inet() or dtype.is_macaddr(): - return sge.convert(str(value)) - elif dtype.is_timestamp(): - funcname = "DATETIME" if dtype.timezone is None else "TIMESTAMP" - return self.f.anon[funcname](value.isoformat()) - elif dtype.is_date(): - return self.f.date_from_parts(value.year, value.month, value.day) - elif dtype.is_time(): - time = self.f.time_from_parts(value.hour, value.minute, value.second) - if micros := value.microsecond: - # bigquery doesn't support `time(12, 34, 56.789101)`, AKA a - # float seconds specifier, so add any non-zero micros to the - # time value - return sge.TimeAdd( - this=time, expression=sge.convert(micros), unit=self.v.MICROSECOND - ) - return time - elif dtype.is_binary(): - return sge.Cast( - this=sge.convert(value.hex()), - to=sge.DataType(this=sge.DataType.Type.BINARY), - format=sge.convert("HEX"), - ) - elif dtype.is_interval(): - if dtype.unit == IntervalUnit.NANOSECOND: - raise ibis_exceptions.UnsupportedOperationError( - "BigQuery does not support nanosecond intervals" - ) - elif dtype.is_uuid(): - return sge.convert(str(value)) - - elif dtype.is_int64(): - # allows directly using values out of a duration arrow array - if isinstance(value, datetime.timedelta): - value = ( - (value.days * 3600 * 24) + value.seconds - ) * 1_000_000 + value.microseconds - return sge.convert(np.int64(value)) - return None - - def visit_IntervalFromInteger(self, op, *, arg, unit): - if unit == IntervalUnit.NANOSECOND: - raise ibis_exceptions.UnsupportedOperationError( - "BigQuery does not support nanosecond intervals" - ) - return sge.Interval(this=arg, unit=self.v[unit.singular]) - - def visit_Strftime(self, op, *, arg, format_str): - arg_dtype = op.arg.dtype - if arg_dtype.is_timestamp(): - if (timezone := arg_dtype.timezone) is None: - return self.f.format_datetime(format_str, arg) - else: - return self.f.format_timestamp(format_str, arg, timezone) - elif arg_dtype.is_date(): - return self.f.format_date(format_str, arg) - else: - assert arg_dtype.is_time(), arg_dtype - return self.f.format_time(format_str, arg) - - def visit_IntervalMultiply(self, op, *, left, right): - unit = self.v[op.left.dtype.resolution.upper()] - return sge.Interval(this=self.f.extract(unit, left) * right, unit=unit) - - def visit_TimestampFromUNIX(self, op, *, arg, unit): - unit = op.unit - if unit == TimestampUnit.SECOND: - return self.f.timestamp_seconds(arg) - elif unit == TimestampUnit.MILLISECOND: - return self.f.timestamp_millis(arg) - elif unit == TimestampUnit.MICROSECOND: - return self.f.timestamp_micros(arg) - elif unit == TimestampUnit.NANOSECOND: - return self.f.timestamp_micros( - self.cast(self.f.round(arg / 1_000), dt.int64) - ) - else: - raise ibis_exceptions.UnsupportedOperationError( - f"Unit not supported: {unit}" - ) - - def visit_Cast(self, op, *, arg, to): - from_ = op.arg.dtype - if to.is_null(): - return sge.Null() - if arg is NULL or ( - isinstance(arg, sge.Cast) - and getattr(arg, "to", None) is not None - and str(arg.to).upper() == "NULL" - ): - if to.is_struct() or to.is_array(): - return sge.Cast(this=NULL, to=self.type_mapper.from_ibis(to)) - if from_.is_timestamp() and to.is_integer(): - return self.f.unix_micros(arg) - elif from_.is_integer() and to.is_timestamp(): - return self.f.timestamp_seconds(arg) - elif from_.is_interval() and to.is_integer(): - if from_.unit in { - IntervalUnit.WEEK, - IntervalUnit.QUARTER, - IntervalUnit.NANOSECOND, - }: - raise ibis_exceptions.UnsupportedOperationError( - f"BigQuery does not allow extracting date part `{from_.unit}` from intervals" - ) - return self.f.extract(self.v[to.resolution.upper()], arg) - elif (from_.is_floating() or from_.is_decimal()) and to.is_integer(): - return self.cast(self.f.trunc(arg), dt.int64) - return super().visit_Cast(op, arg=arg, to=to) - - def visit_JSONGetItem(self, op, *, arg, index): - return arg[index] - - def visit_UnwrapJSONString(self, op, *, arg): - return self.f.anon["safe.string"](arg) - - def visit_UnwrapJSONInt64(self, op, *, arg): - return self.f.anon["safe.int64"](arg) - - def visit_UnwrapJSONFloat64(self, op, *, arg): - return self.f.anon["safe.float64"](arg) - - def visit_UnwrapJSONBoolean(self, op, *, arg): - return self.f.anon["safe.bool"](arg) - - def visit_ExtractEpochSeconds(self, op, *, arg): - return self.f.unix_seconds(arg) - - def visit_ExtractWeekOfYear(self, op, *, arg): - return self.f.extract(self.v.isoweek, arg) - - def visit_ExtractIsoYear(self, op, *, arg): - return self.f.extract(self.v.isoyear, arg) - - def visit_ExtractMillisecond(self, op, *, arg): - return self.f.extract(self.v.millisecond, arg) - - def visit_ExtractMicrosecond(self, op, *, arg): - return self.f.extract(self.v.microsecond, arg) - - def visit_TimestampTruncate(self, op, *, arg, unit): - if unit == IntervalUnit.NANOSECOND: - raise ibis_exceptions.UnsupportedOperationError( - f"BigQuery does not support truncating {op.arg.dtype} values to unit {unit!r}" - ) - elif unit == IntervalUnit.WEEK: - unit = "WEEK(MONDAY)" - else: - unit = unit.name - return self.f.timestamp_trunc(arg, self.v[unit], dialect=self.dialect) - - def visit_DateTruncate(self, op, *, arg, unit): - if unit == DateUnit.WEEK: - unit = "WEEK(MONDAY)" - else: - unit = unit.name - return self.f.date_trunc(arg, self.v[unit], dialect=self.dialect) - - def visit_TimeTruncate(self, op, *, arg, unit): - if unit == TimeUnit.NANOSECOND: - raise ibis_exceptions.UnsupportedOperationError( - f"BigQuery does not support truncating {op.arg.dtype} values to unit {unit!r}" - ) - else: - unit = unit.name - return self.f.time_trunc(arg, self.v[unit], dialect=self.dialect) - - def _nullifzero(self, step, zero, step_dtype): - if step_dtype.is_interval(): - return self.if_(step.eq(zero), NULL, step) - return self.f.nullif(step, zero) - - def _zero(self, dtype): - if dtype.is_interval(): - return self.f.make_interval() - return sge.convert(0) - - def _sign(self, value, dtype): - if dtype.is_interval(): - zero = self._zero(dtype) - return sge.Case( - ifs=[ - self.if_(value < zero, -1), - self.if_(value.eq(zero), 0), - self.if_(value > zero, 1), - ], - default=NULL, - ) - return self.f.sign(value) - - def _make_range(self, func, start, stop, step, step_dtype): - step_sign = self._sign(step, step_dtype) - delta_sign = self._sign(stop - start, step_dtype) - zero = self._zero(step_dtype) - nullifzero = self._nullifzero(step, zero, step_dtype) - condition = sg.and_(sg.not_(nullifzero.is_(NULL)), step_sign.eq(delta_sign)) - gen_array = func(start, stop, step) - name = sg.to_identifier(util.gen_name("bq_arr_range")) - inner = ( - sg.select(name) - .from_(self._unnest(gen_array, as_=name)) - .where(name.neq(stop)) - ) - return self.if_(condition, self.f.array(inner), self.f.array()) - - def visit_IntegerRange(self, op, *, start, stop, step): - return self._make_range(self.f.generate_array, start, stop, step, op.step.dtype) - - def visit_TimestampRange(self, op, *, start, stop, step): - if op.start.dtype.timezone is None or op.stop.dtype.timezone is None: - raise ibis_exceptions.IbisTypeError( - "Timestamps without timezone values are not supported when generating timestamp ranges" - ) - return self._make_range( - self.f.generate_timestamp_array, start, stop, step, op.step.dtype - ) - - def visit_First(self, op, *, arg, where, order_by, include_null): - if where is not None: - arg = self.if_(where, arg, NULL) - if include_null: - raise ibis_exceptions.UnsupportedOperationError( - "Combining `include_null=True` and `where` is not supported " - "by bigquery" - ) - - if order_by: - arg = sge.Order(this=arg, expressions=order_by) - - if not include_null: - arg = sge.IgnoreNulls(this=arg) - - array = self.f.array_agg(sge.Limit(this=arg, expression=sge.convert(1))) - return array[self.f.safe_offset(0)] - - def visit_Last(self, op, *, arg, where, order_by, include_null): - if where is not None: - arg = self.if_(where, arg, NULL) - if include_null: - raise ibis_exceptions.UnsupportedOperationError( - "Combining `include_null=True` and `where` is not supported " - "by bigquery" - ) - - if order_by: - arg = sge.Order(this=arg, expressions=order_by) - - if not include_null: - arg = sge.IgnoreNulls(this=arg) - - array = self.f.array_reverse(self.f.array_agg(arg)) - return array[self.f.safe_offset(0)] - - def visit_ArrayFilter(self, op, *, arg, body, param): - return self.f.array( - sg.select(param).from_(self._unnest(arg, as_=param)).where(body) - ) - - def visit_ArrayMap(self, op, *, arg, body, param): - return self.f.array(sg.select(body).from_(self._unnest(arg, as_=param))) - - def visit_ArrayReduce(self, op, *, arg, body, param): - return sg.select(body).from_(self._unnest(arg, as_=param)).subquery() - - def visit_ArrayZip(self, op, *, arg): - lengths = [self.f.array_length(arr) - 1 for arr in arg] - idx = sg.to_identifier(util.gen_name("bq_arr_idx")) - indices = self._unnest( - self.f.generate_array(0, self.f.greatest(*lengths)), as_=idx - ) - struct_fields = [ - arr[self.f.safe_offset(idx)].as_(name) - for name, arr in zip(op.dtype.value_type.names, arg) - ] - return self.f.array( - sge.Select(kind="STRUCT", expressions=struct_fields).from_(indices) - ) - - def visit_ArrayPosition(self, op, *, arg, other): - name = sg.to_identifier(util.gen_name("bq_arr")) - idx = sg.to_identifier(util.gen_name("bq_arr_idx")) - unnest = self._unnest(arg, as_=name, offset=idx) - return self.f.coalesce( - sg.select(idx + 1).from_(unnest).where(name.eq(other)).limit(1).subquery(), - 0, - ) - - def _unnest(self, expression, *, as_, offset=None): - alias = sge.TableAlias(columns=[sg.to_identifier(as_)]) - return sge.Unnest(expressions=[expression], alias=alias, offset=offset) - - def visit_ArrayRemove(self, op, *, arg, other): - name = sg.to_identifier(util.gen_name("bq_arr")) - unnest = self._unnest(arg, as_=name) - both_null = sg.and_(name.is_(NULL), other.is_(NULL)) - cond = sg.or_(name.neq(other), both_null) - return self.f.array(sg.select(name).from_(unnest).where(cond)) - - def visit_ArrayDistinct(self, op, *, arg): - name = util.gen_name("bq_arr") - return self.f.array( - sg.select(name).distinct().from_(self._unnest(arg, as_=name)) - ) - - def visit_ArraySort(self, op, *, arg): - name = util.gen_name("bq_arr") - return self.f.array( - sg.select(name).from_(self._unnest(arg, as_=name)).order_by(name) - ) - - def visit_ArrayUnion(self, op, *, left, right): - lname = util.gen_name("bq_arr_left") - rname = util.gen_name("bq_arr_right") - lhs = sg.select(lname).from_(self._unnest(left, as_=lname)) - rhs = sg.select(rname).from_(self._unnest(right, as_=rname)) - return self.f.array(sg.union(lhs, rhs, distinct=True)) - - def visit_ArrayIntersect(self, op, *, left, right): - lname = util.gen_name("bq_arr_left") - rname = util.gen_name("bq_arr_right") - lhs = sg.select(lname).from_(self._unnest(left, as_=lname)) - rhs = sg.select(rname).from_(self._unnest(right, as_=rname)) - return self.f.array(sg.intersect(lhs, rhs, distinct=True)) - - def visit_RegexExtract(self, op, *, arg, pattern, index): - matches = self.f.regexp_contains(arg, pattern) - nonzero_index_replace = self.f.regexp_replace( - arg, - self.f.concat(".*?", pattern, ".*"), - self.f.concat("\\", self.cast(index, dt.string)), - ) - zero_index_replace = self.f.regexp_replace( - arg, self.f.concat(".*?", self.f.concat("(", pattern, ")"), ".*"), "\\1" - ) - extract = self.if_(index.eq(0), zero_index_replace, nonzero_index_replace) - return self.if_(matches, extract, NULL) - - def visit_TimestampAddSub(self, op, *, left, right): - if not isinstance(right, sge.Interval): - raise ibis_exceptions.OperationNotDefinedError( - "BigQuery does not support non-literals on the right side of timestamp add/subtract" - ) - if (unit := op.right.dtype.unit) == IntervalUnit.NANOSECOND: - raise ibis_exceptions.UnsupportedOperationError( - f"BigQuery does not allow binary operation {type(op).__name__} with " - f"INTERVAL offset {unit}" - ) - - opname = type(op).__name__[len("Timestamp") :] - funcname = f"TIMESTAMP_{opname.upper()}" - return self.f.anon[funcname](left, right) - - visit_TimestampAdd = visit_TimestampSub = visit_TimestampAddSub - - def visit_DateAddSub(self, op, *, left, right): - if not isinstance(right, sge.Interval): - raise ibis_exceptions.OperationNotDefinedError( - "BigQuery does not support non-literals on the right side of date add/subtract" - ) - if not (unit := op.right.dtype.unit).is_date(): - raise ibis_exceptions.UnsupportedOperationError( - f"BigQuery does not allow binary operation {type(op).__name__} with " - f"INTERVAL offset {unit}" - ) - opname = type(op).__name__[len("Date") :] - funcname = f"DATE_{opname.upper()}" - return self.f.anon[funcname](left, right) - - visit_DateAdd = visit_DateSub = visit_DateAddSub - - def visit_Covariance(self, op, *, left, right, how, where): - if where is not None: - left = self.if_(where, left, NULL) - right = self.if_(where, right, NULL) - - if op.left.dtype.is_boolean(): - left = self.cast(left, dt.int64) - - if op.right.dtype.is_boolean(): - right = self.cast(right, dt.int64) - - how = op.how[:4].upper() - assert how in ("POP", "SAMP"), 'how not in ("POP", "SAMP")' - return self.agg[f"COVAR_{how}"](left, right, where=where) - - def visit_Correlation(self, op, *, left, right, how, where): - if how == "sample": - raise ValueError(f"Correlation with how={how!r} is not supported.") - - if where is not None: - left = self.if_(where, left, NULL) - right = self.if_(where, right, NULL) - - if op.left.dtype.is_boolean(): - left = self.cast(left, dt.int64) - - if op.right.dtype.is_boolean(): - right = self.cast(right, dt.int64) - - return self.agg.corr(left, right, where=where) - - def visit_TypeOf(self, op, *, arg): - return self._pudf("typeof", arg) - - def visit_Xor(self, op, *, left, right): - return sg.or_(sg.and_(left, sg.not_(right)), sg.and_(sg.not_(left), right)) - - def visit_HashBytes(self, op, *, arg, how): - if how not in ("md5", "sha1", "sha256", "sha512"): - raise NotImplementedError(how) - return self.f[how](arg) - - @staticmethod - def _gen_valid_name(name: str) -> str: - candidate = "_".join(map(str.strip, _NAME_REGEX.findall(name))) or "tmp" - # column names cannot be longer than 300 characters - # - # https://cloud.google.com/bigquery/docs/schemas#column_names - # - # it's easy to rename columns, so raise an exception telling the user - # to do so - # - # we could potentially relax this and support arbitrary-length columns - # by compressing the information using hashing, but there's no reason - # to solve that problem until someone encounters this error and cannot - # rename their columns - limit = 300 - if len(candidate) > limit: - raise ibis_exceptions.IbisError( - f"BigQuery does not allow column names longer than {limit:d} characters. " - "Please rename your columns to have fewer characters." - ) - return candidate - - def visit_CountStar(self, op, *, arg, where): - if where is not None: - return self.f.countif(where) - return self.f.count(STAR) - - def visit_CountDistinctStar(self, op, *, where, arg): - # Bigquery does not support count(distinct a,b,c) or count(distinct (a, b, c)) - # as expressions must be "groupable": - # https://cloud.google.com/bigquery/docs/reference/standard-sql/query-syntax#group_by_grouping_item - # - # Instead, convert the entire expression to a string - # SELECT COUNT(DISTINCT concat(to_json_string(a), to_json_string(b))) - # This works with an array of datatypes which generates a unique string - # https://cloud.google.com/bigquery/docs/reference/standard-sql/json_functions#json_encodings - row = sge.Concat( - expressions=[ - self.f.to_json_string(sg.column(x, quoted=self.quoted)) - for x in op.arg.schema.keys() - ] - ) - if where is not None: - row = self.if_(where, row, NULL) - return self.f.count(sge.Distinct(expressions=[row])) - - def visit_Degrees(self, op, *, arg): - return self._pudf("degrees", arg) - - def visit_Radians(self, op, *, arg): - return self._pudf("radians", arg) - - def visit_CountDistinct(self, op, *, arg, where): - if where is not None: - arg = self.if_(where, arg, NULL) - return self.f.count(sge.Distinct(expressions=[arg])) - - def visit_RandomUUID(self, op, **kwargs): - return self.f.generate_uuid() - - def visit_ExtractFile(self, op, *, arg): - return self._pudf("cw_url_extract_file", arg) - - def visit_ExtractFragment(self, op, *, arg): - return self._pudf("cw_url_extract_fragment", arg) - - def visit_ExtractPath(self, op, *, arg): - return self._pudf("cw_url_extract_path", arg) - - def visit_ExtractProtocol(self, op, *, arg): - return self._pudf("cw_url_extract_protocol", arg) - - def visit_ExtractQuery(self, op, *, arg, key): - if key is not None: - return self._pudf("cw_url_extract_parameter", arg, key) - else: - return self._pudf("cw_url_extract_query", arg) - - def _pudf(self, name, *args): - name = sg.table(name, db="persistent_udfs", catalog="bigquery-public-data").sql( - self.dialect - ) - return self.f[name](*args) - - def visit_DropColumns(self, op, *, parent, columns_to_drop): - quoted = self.quoted - excludes = [sg.column(column, quoted=quoted) for column in columns_to_drop] - star = sge.Star(**{"except": excludes}) - table = sg.to_identifier(parent.alias_or_name, quoted=quoted) - column = sge.Column(this=star, table=table) - return sg.select(column).from_(parent) - - def visit_TableUnnest( - self, op, *, parent, column, offset: str | None, keep_empty: bool - ): - quoted = self.quoted - - column_alias = sg.to_identifier( - util.gen_name("table_unnest_column"), quoted=quoted - ) - - selcols = [] - - table = sg.to_identifier(parent.alias_or_name, quoted=quoted) - - opname = op.column.name - overlaps_with_parent = opname in op.parent.schema - computed_column = column_alias.as_(opname, quoted=quoted) - - # replace the existing column if the unnested column hasn't been - # renamed - # - # e.g., table.unnest("x") - if overlaps_with_parent: - selcols.append( - sge.Column(this=sge.Star(replace=[computed_column]), table=table) - ) - else: - selcols.append(sge.Column(this=STAR, table=table)) - selcols.append(computed_column) - - if offset is not None: - offset = sg.to_identifier(offset, quoted=quoted) - selcols.append(offset) - - unnest = sge.Unnest( - expressions=[column], - alias=sge.TableAlias(columns=[column_alias]), - offset=offset, - ) - return ( - sg.select(*selcols) - .from_(parent) - .join(unnest, join_type="CROSS" if not keep_empty else "LEFT") - ) - - def visit_TimestampBucket(self, op, *, arg, interval, offset): - arg_dtype = op.arg.dtype - if arg_dtype.timezone is not None: - funcname = "timestamp" - else: - funcname = "datetime" - - func = self.f[f"{funcname}_bucket"] - - origin = sge.convert("1970-01-01") - if offset is not None: - origin = self.f.anon[f"{funcname}_add"](origin, offset) - - return func(arg, interval, origin) - - def _array_reduction(self, *, arg, reduction): - name = sg.to_identifier(util.gen_name(f"bq_arr_{reduction}")) - return ( - sg.select(self.f[reduction](name)) - .from_(self._unnest(arg, as_=name)) - .subquery() - ) - - def visit_ArrayMin(self, op, *, arg): - return self._array_reduction(arg=arg, reduction="min") - - def visit_ArrayMax(self, op, *, arg): - return self._array_reduction(arg=arg, reduction="max") - - def visit_ArraySum(self, op, *, arg): - return self._array_reduction(arg=arg, reduction="sum") - - def visit_ArrayMean(self, op, *, arg): - return self._array_reduction(arg=arg, reduction="avg") - - def visit_ArrayAny(self, op, *, arg): - return self._array_reduction(arg=arg, reduction="logical_or") - - def visit_ArrayAll(self, op, *, arg): - return self._array_reduction(arg=arg, reduction="logical_and") - - # Customized ops for bigframes - - def visit_InMemoryTable(self, op, *, name, schema, data): - # Avoid creating temp tables for small data, which is how memtable is - # used in BigQuery DataFrames. Inspired by: - # https://github.com/ibis-project/ibis/blob/efa6fb72bf4c790450d00a926d7bd809dade5902/ibis/backends/druid/compiler.py#L95 - rows = data.to_pyarrow(schema=None).to_pylist() # type: ignore - quoted = self.quoted - columns = [sg.column(col, quoted=quoted) for col in schema.names] - array_expr = sge.DataType( - this=sge.DataType.Type.STRUCT, - expressions=[ - sge.ColumnDef( - this=sge.to_identifier(field, quoted=self.quoted), - kind=bq_datatypes.BigQueryType.from_ibis(type_), - ) - for field, type_ in zip(schema.names, schema.types) - ], - nested=True, - ) - array_values = [ - sge.Struct( - expressions=tuple( - self.visit_Literal(None, value=value, dtype=type_) - for value, type_ in zip(row.values(), schema.types) - ) - ) - for row in rows - ] - expr = sge.Unnest( - expressions=[ - sge.DataType( - this=sge.DataType.Type.ARRAY, - expressions=[array_expr], - nested=True, - values=array_values, - ), - ], - alias=sge.TableAlias( - this=sg.to_identifier(name, quoted=quoted), - columns=columns, - ), - ) - return sg.select(sge.Star()).from_(expr) - - def visit_ArrayAggregate(self, op, *, arg, order_by, where): - if len(order_by) > 0: - expr = sge.Order( - this=arg, - expressions=[ - # Avoid adding NULLS FIRST / NULLS LAST in SQL, which is - # unsupported in ARRAY_AGG by reconstructing the node as - # plain SQL text. - f"({order_column.args['this'].sql(dialect='bigquery')}) {'DESC' if order_column.args.get('desc') else 'ASC'}" - for order_column in order_by - ], - ) - else: - expr = arg - return sge.IgnoreNulls(this=self.agg.array_agg(expr, where=where)) - - def visit_StringAgg(self, op, *, arg, sep, order_by, where): - if len(order_by) > 0: - expr = sge.Order( - this=arg, - expressions=[ - # Avoid adding NULLS FIRST / NULLS LAST in SQL, which is - # unsupported in ARRAY_AGG by reconstructing the node as - # plain SQL text. - f"({order_column.args['this'].sql(dialect='bigquery')}) {'DESC' if order_column.args.get('desc') else 'ASC'}" - for order_column in order_by - ], - ) - else: - expr = arg - return self.agg.string_agg(expr, sep, where=where) - - def visit_AIGenerate(self, op, **kwargs): - return sge.func("AI.GENERATE", *self._compile_ai_args(**kwargs)) - - def visit_AIGenerateBool(self, op, **kwargs): - return sge.func("AI.GENERATE_BOOL", *self._compile_ai_args(**kwargs)) - - def visit_AIGenerateInt(self, op, **kwargs): - return sge.func("AI.GENERATE_INT", *self._compile_ai_args(**kwargs)) - - def visit_AIGenerateDouble(self, op, **kwargs): - return sge.func("AI.GENERATE_DOUBLE", *self._compile_ai_args(**kwargs)) - - def visit_AIEmbed(self, op, **kwargs): - return sge.func("AI.EMBED", *self._compile_ai_args(**kwargs)) - - def visit_AIIf(self, op, **kwargs): - return sge.func("AI.IF", *self._compile_ai_args(**kwargs)) - - def visit_AIClassify(self, op, **kwargs): - return sge.func("AI.CLASSIFY", *self._compile_ai_args(**kwargs)) - - def visit_AIScore(self, op, **kwargs): - return sge.func("AI.SCORE", *self._compile_ai_args(**kwargs)) - - def visit_AISimilarity(self, op, **kwargs): - return sge.func("AI.SIMILARITY", *self._compile_ai_args(**kwargs)) - - def _compile_ai_args(self, **kwargs): - args = [] - - for key, val in kwargs.items(): - if val is None: - continue - - if key == "model_params": - val = sge.JSON(this=val) - - args.append(sge.Kwarg(this=sge.Identifier(this=key), expression=val)) - - return args - - def visit_FirstNonNullValue(self, op, *, arg): - return sge.IgnoreNulls(this=sge.FirstValue(this=arg)) - - def visit_LastNonNullValue(self, op, *, arg): - return sge.IgnoreNulls(this=sge.LastValue(this=arg)) - - def visit_ToJsonString(self, op, *, arg): - return self.f.to_json_string(arg) - - def visit_Quantile(self, op, *, arg, quantile, where): - return sge.PercentileCont(this=arg, expression=quantile) - - def visit_WindowFunction(self, op, *, how, func, start, end, group_by, order_by): - # Patch for https://github.com/ibis-project/ibis/issues/9872 - - if start is None: - start = {} - if end is None: - end = {} - - start_value = start.get("value", "UNBOUNDED") - start_side = start.get("side", "PRECEDING") - end_value = end.get("value", "UNBOUNDED") - end_side = end.get("side", "FOLLOWING") - - if getattr(start_value, "this", None) == "0": - start_value = "CURRENT ROW" - start_side = None - - if getattr(end_value, "this", None) == "0": - end_value = "CURRENT ROW" - end_side = None - - if how != "none": - spec = sge.WindowSpec( - kind=how.upper(), - start=start_value, - start_side=start_side, - end=end_value, - end_side=end_side, - over="OVER", - ) - else: - spec = None - - # If unordered, unbound range window is implicit - if (not order_by) and (not start) and (not end): - spec = None - - order = sge.Order(expressions=order_by) if order_by else None - - return sge.Window(this=func, partition_by=group_by, order=order, spec=spec) - - -compiler = BigQueryCompiler() diff --git a/third_party/bigframes_vendored/ibis/backends/sql/datatypes.py b/third_party/bigframes_vendored/ibis/backends/sql/datatypes.py deleted file mode 100644 index 7a71ecf5efb..00000000000 --- a/third_party/bigframes_vendored/ibis/backends/sql/datatypes.py +++ /dev/null @@ -1,540 +0,0 @@ -# Contains code from https://github.com/ibis-project/ibis/blob/9.2.0/ibis/backends/sql/datatypes.py - -from __future__ import annotations - -from functools import partial -from typing import NoReturn - -import bigframes_vendored.ibis.common.exceptions as com -import bigframes_vendored.ibis.expr.datatypes as dt -import bigframes_vendored.sqlglot as sg -import bigframes_vendored.sqlglot.expressions as sge -from bigframes_vendored.ibis.formats import TypeMapper - -typecode = sge.DataType.Type - -_from_sqlglot_types = { - typecode.BIGDECIMAL: partial(dt.Decimal, 76, 38), - typecode.BIGINT: dt.Int64, - typecode.BINARY: dt.Binary, - typecode.BOOLEAN: dt.Boolean, - typecode.CHAR: dt.String, - typecode.DATE: dt.Date, - typecode.DATE32: dt.Date, - typecode.DOUBLE: dt.Float64, - typecode.ENUM: dt.String, - typecode.ENUM8: dt.String, - typecode.ENUM16: dt.String, - typecode.FLOAT: dt.Float32, - typecode.FIXEDSTRING: dt.String, - typecode.HSTORE: partial(dt.Map, dt.string, dt.string), - typecode.INET: dt.INET, - typecode.INT128: partial(dt.Decimal, 38, 0), - typecode.INT256: partial(dt.Decimal, 76, 0), - typecode.INT: dt.Int32, - typecode.IPADDRESS: dt.INET, - typecode.JSON: dt.JSON, - typecode.JSONB: partial(dt.JSON, binary=True), - typecode.LONGBLOB: dt.Binary, - typecode.LONGTEXT: dt.String, - typecode.MEDIUMBLOB: dt.Binary, - typecode.MEDIUMTEXT: dt.String, - typecode.MONEY: dt.Decimal(19, 4), - typecode.NCHAR: dt.String, - typecode.UUID: dt.UUID, - typecode.NAME: dt.String, - typecode.NULL: dt.Null, - typecode.NVARCHAR: dt.String, - typecode.OBJECT: partial(dt.Map, dt.string, dt.json), - typecode.ROWVERSION: partial(dt.Binary, nullable=False), - typecode.SMALLINT: dt.Int16, - typecode.SMALLMONEY: dt.Decimal(10, 4), - typecode.TEXT: dt.String, - typecode.TIME: dt.Time, - typecode.TIMETZ: dt.Time, - typecode.TINYBLOB: dt.Binary, - typecode.TINYINT: dt.Int8, - typecode.TINYTEXT: dt.String, - typecode.UBIGINT: dt.UInt64, - typecode.UINT: dt.UInt32, - typecode.USMALLINT: dt.UInt16, - typecode.UTINYINT: dt.UInt8, - typecode.UUID: dt.UUID, - typecode.VARBINARY: dt.Binary, - typecode.VARCHAR: dt.String, - typecode.VARIANT: dt.JSON, - typecode.SET: partial(dt.Array, dt.string), - ############################# - # Unsupported sqlglot types # - ############################# - # BIT = auto() # mysql - # BIGSERIAL = auto() - # DATETIME64 = auto() # clickhouse - # ENUM = auto() - # INT4RANGE = auto() - # INT4MULTIRANGE = auto() - # INT8RANGE = auto() - # INT8MULTIRANGE = auto() - # NUMRANGE = auto() - # NUMMULTIRANGE = auto() - # TSRANGE = auto() - # TSMULTIRANGE = auto() - # TSTZRANGE = auto() - # TSTZMULTIRANGE = auto() - # DATERANGE = auto() - # DATEMULTIRANGE = auto() - # HLLSKETCH = auto() - # IMAGE = auto() - # IPPREFIX = auto() - # SERIAL = auto() - # SET = auto() - # SMALLSERIAL = auto() - # SUPER = auto() - # TIMESTAMPLTZ = auto() - # UNKNOWN = auto() # Sentinel value, useful for type annotation - # UINT128 = auto() - # UINT256 = auto() - # USERDEFINED = "USER-DEFINED" - # XML = auto() -} - -_to_sqlglot_types = { - dt.Null: typecode.NULL, - dt.Boolean: typecode.BOOLEAN, - dt.Int8: typecode.TINYINT, - dt.Int16: typecode.SMALLINT, - dt.Int32: typecode.INT, - dt.Int64: typecode.BIGINT, - dt.UInt8: typecode.UTINYINT, - dt.UInt16: typecode.USMALLINT, - dt.UInt32: typecode.UINT, - dt.UInt64: typecode.UBIGINT, - dt.Float16: typecode.FLOAT, - dt.Float32: typecode.FLOAT, - dt.Float64: typecode.DOUBLE, - dt.String: typecode.VARCHAR, - dt.Binary: typecode.VARBINARY, - dt.INET: typecode.INET, - dt.UUID: typecode.UUID, - dt.MACADDR: typecode.VARCHAR, - dt.Date: typecode.DATE, - dt.Time: typecode.TIME, -} - -_geotypes = { - "POINT": dt.Point, - "LINESTRING": dt.LineString, - "POLYGON": dt.Polygon, - "MULTIPOINT": dt.MultiPoint, - "MULTILINESTRING": dt.MultiLineString, - "MULTIPOLYGON": dt.MultiPolygon, -} - - -class SqlglotType(TypeMapper): - dialect: str | None = None - """The dialect this parser is for.""" - - default_nullable = True - """Default nullability when not specified.""" - - default_decimal_precision: int | None = None - """Default decimal precision when not specified.""" - - default_decimal_scale: int | None = None - """Default decimal scale when not specified.""" - - default_temporal_scale: int | None = None - """Default temporal scale when not specified.""" - - default_interval_precision: str | None = None - """Default interval precision when not specified.""" - - unknown_type_strings: dict[str, dt.DataType] = {} - """String to ibis datatype mapping to use when converting unknown types.""" - - @classmethod - def to_ibis(cls, typ: sge.DataType, nullable: bool | None = None) -> dt.DataType: - """Convert a sqlglot type to an ibis type.""" - typecode = typ.this - - # broken sqlglot thing - if isinstance(typecode, sge.Interval): - typ = sge.DataType( - this=sge.DataType.Type.INTERVAL, - expressions=[typecode.unit], - ) - typecode = typ.this - - if method := getattr(cls, f"_from_sqlglot_{typecode.name}", None): - dtype = method(*typ.expressions) - else: - dtype = _from_sqlglot_types[typecode](nullable=cls.default_nullable) - - if nullable is not None: - return dtype.copy(nullable=nullable) - else: - return dtype - - @classmethod - def from_ibis(cls, dtype: dt.DataType) -> sge.DataType: - """Convert an Ibis dtype to an sqlglot dtype.""" - - if method := getattr(cls, f"_from_ibis_{dtype.name}", None): - return method(dtype) - else: - return sge.DataType(this=_to_sqlglot_types[type(dtype)]) - - @classmethod - def from_string(cls, text: str, nullable: bool | None = None) -> dt.DataType: - if dtype := cls.unknown_type_strings.get(text.lower()): - return dtype - - try: - sgtype = sg.parse_one(text, into=sge.DataType, read=cls.dialect) - return cls.to_ibis(sgtype, nullable=nullable) - except sg.errors.ParseError: - # If sqlglot can't parse the type fall back to `dt.unknown` - pass - - return dt.unknown - - @classmethod - def to_string(cls, dtype: dt.DataType) -> str: - return cls.from_ibis(dtype).sql(dialect=cls.dialect) - - @classmethod - def _from_sqlglot_ARRAY(cls, value_type: sge.DataType) -> dt.Array: - return dt.Array(cls.to_ibis(value_type), nullable=cls.default_nullable) - - @classmethod - def _from_sqlglot_MAP( - cls, key_type: sge.DataType, value_type: sge.DataType - ) -> dt.Map: - return dt.Map( - cls.to_ibis(key_type), - cls.to_ibis(value_type), - nullable=cls.default_nullable, - ) - - @classmethod - def _from_sqlglot_STRUCT(cls, *fields: sge.ColumnDef) -> dt.Struct: - types = {} - for i, field in enumerate(fields): - if isinstance(field, sge.ColumnDef): - types[field.name] = cls.to_ibis(field.args["kind"]) - else: - types[f"f{i:d}"] = cls.from_string(str(field)) - return dt.Struct(types, nullable=cls.default_nullable) - - @classmethod - def _from_sqlglot_TIMESTAMP(cls, scale=None) -> dt.Timestamp: - return dt.Timestamp( - scale=cls.default_temporal_scale if scale is None else int(scale.this.this), - nullable=cls.default_nullable, - ) - - @classmethod - def _from_sqlglot_TIMESTAMPTZ(cls, scale=None) -> dt.Timestamp: - return dt.Timestamp( - timezone="UTC", - scale=cls.default_temporal_scale if scale is None else int(scale.this.this), - nullable=cls.default_nullable, - ) - - @classmethod - def _from_sqlglot_TIMESTAMPLTZ(cls, scale=None) -> dt.Timestamp: - return dt.Timestamp( - timezone="UTC", - scale=cls.default_temporal_scale if scale is None else int(scale.this.this), - nullable=cls.default_nullable, - ) - - @classmethod - def _from_sqlglot_TIMESTAMPNTZ(cls, scale=None) -> dt.Timestamp: - return dt.Timestamp( - timezone=None, - scale=cls.default_temporal_scale if scale is None else int(scale.this.this), - nullable=cls.default_nullable, - ) - - @classmethod - def _from_sqlglot_INTERVAL( - cls, precision_or_span: sge.IntervalSpan | None = None - ) -> dt.Interval: - nullable = cls.default_nullable - if precision_or_span is None: - precision_or_span = cls.default_interval_precision - - if isinstance(precision_or_span, str): - return dt.Interval(precision_or_span, nullable=nullable) - elif isinstance(precision_or_span, sge.IntervalSpan): - if (expression := precision_or_span.expression) is not None: - unit = expression.this - else: - unit = precision_or_span.this.this - return dt.Interval(unit=unit, nullable=nullable) - elif isinstance(precision_or_span, sge.Var): - return dt.Interval(unit=precision_or_span.this, nullable=nullable) - elif precision_or_span is None: - raise com.IbisTypeError("Interval precision is None") - else: - raise com.IbisTypeError(precision_or_span) - - @classmethod - def _from_sqlglot_DECIMAL( - cls, - precision: sge.DataTypeParam | None = None, - scale: sge.DataTypeParam | None = None, - ) -> dt.Decimal: - if precision is None: - precision = cls.default_decimal_precision - else: - precision = int(precision.this.this) - - if scale is None: - scale = cls.default_decimal_scale - else: - scale = int(scale.this.this) - - return dt.Decimal(precision, scale, nullable=cls.default_nullable) - - @classmethod - def _from_sqlglot_GEOMETRY( - cls, arg: sge.DataTypeParam | None = None, srid: sge.DataTypeParam | None = None - ) -> sge.DataType: - if arg is not None: - typeclass = _geotypes[arg.this.this] - else: - typeclass = dt.GeoSpatial - if srid is not None: - srid = int(srid.this.this) - return typeclass(geotype="geometry", nullable=cls.default_nullable, srid=srid) - - @classmethod - def _from_sqlglot_GEOGRAPHY( - cls, arg: sge.DataTypeParam | None = None, srid: sge.DataTypeParam | None = None - ) -> sge.DataType: - if arg is not None: - typeclass = _geotypes[arg.this.this] - else: - typeclass = dt.GeoSpatial - if srid is not None: - srid = int(srid.this.this) - return typeclass(geotype="geography", nullable=cls.default_nullable, srid=srid) - - @classmethod - def _from_ibis_JSON(cls, dtype: dt.JSON) -> sge.DataType: - return sge.DataType(this=typecode.JSONB if dtype.binary else typecode.JSON) - - @classmethod - def _from_ibis_Interval(cls, dtype: dt.Interval) -> sge.DataType: - assert dtype.unit is not None, "interval unit cannot be None" - return sge.DataType( - this=typecode.INTERVAL, - expressions=[sge.Var(this=dtype.unit.name)], - ) - - @classmethod - def _from_ibis_Array(cls, dtype: dt.Array) -> sge.DataType: - value_type = cls.from_ibis(dtype.value_type) - return sge.DataType(this=typecode.ARRAY, expressions=[value_type], nested=True) - - @classmethod - def _from_ibis_Map(cls, dtype: dt.Map) -> sge.DataType: - key_type = cls.from_ibis(dtype.key_type) - value_type = cls.from_ibis(dtype.value_type) - return sge.DataType( - this=typecode.MAP, expressions=[key_type, value_type], nested=True - ) - - @classmethod - def _from_ibis_Struct(cls, dtype: dt.Struct) -> sge.DataType: - fields = [ - sge.ColumnDef( - # always quote struct fields to allow reserved words as field names - this=sg.to_identifier(name, quoted=True), - kind=cls.from_ibis(field), - ) - for name, field in dtype.items() - ] - return sge.DataType(this=typecode.STRUCT, expressions=fields, nested=True) - - @classmethod - def _from_ibis_Decimal(cls, dtype: dt.Decimal) -> sge.DataType: - if (precision := dtype.precision) is None: - precision = cls.default_decimal_precision - - if (scale := dtype.scale) is None: - scale = cls.default_decimal_scale - - expressions = [] - - if precision is not None: - expressions.append(sge.DataTypeParam(this=sge.Literal.number(precision))) - - if scale is not None: - if precision is None: - raise com.IbisTypeError( - "Decimal scale cannot be specified without precision" - ) - expressions.append(sge.DataTypeParam(this=sge.Literal.number(scale))) - - return sge.DataType(this=typecode.DECIMAL, expressions=expressions or None) - - @classmethod - def _from_ibis_Timestamp(cls, dtype: dt.Timestamp) -> sge.DataType: - code = typecode.TIMESTAMP if dtype.timezone is None else typecode.TIMESTAMPTZ - if dtype.scale is not None: - scale = sge.DataTypeParam(this=sge.Literal.number(dtype.scale)) - return sge.DataType(this=code, expressions=[scale]) - else: - return sge.DataType(this=code) - - @classmethod - def _from_ibis_GeoSpatial(cls, dtype: dt.GeoSpatial): - expressions = [None] - - if (srid := dtype.srid) is not None: - expressions.append(sge.DataTypeParam(this=sge.convert(srid))) - - this = getattr(typecode, dtype.geotype.upper()) - - return sge.DataType(this=this, expressions=expressions) - - @classmethod - def _from_ibis_SpecificGeometry(cls, dtype: dt.GeoSpatial): - expressions = [ - sge.DataTypeParam(this=sge.Var(this=dtype.__class__.__name__.upper())) - ] - - if (srid := dtype.srid) is not None: - expressions.append(sge.DataTypeParam(this=sge.convert(srid))) - - this = getattr(typecode, dtype.geotype.upper()) - return sge.DataType(this=this, expressions=expressions) - - _from_ibis_Point = _from_ibis_LineString = _from_ibis_Polygon = ( - _from_ibis_MultiLineString - ) = _from_ibis_MultiPoint = _from_ibis_MultiPolygon = _from_ibis_SpecificGeometry - - -class BigQueryType(SqlglotType): - dialect = "bigquery" - - default_decimal_precision = 38 - default_decimal_scale = 9 - - @classmethod - def _from_sqlglot_NUMERIC(cls) -> dt.Decimal: - return dt.Decimal( - cls.default_decimal_precision, - cls.default_decimal_scale, - nullable=cls.default_nullable, - ) - - @classmethod - def _from_sqlglot_BIGNUMERIC(cls) -> dt.Decimal: - return dt.Decimal(76, 38, nullable=cls.default_nullable) - - @classmethod - def _from_sqlglot_DATETIME(cls) -> dt.Timestamp: - return dt.Timestamp(timezone=None, nullable=cls.default_nullable) - - @classmethod - def _from_sqlglot_TIMESTAMP(cls) -> dt.Timestamp: - return dt.Timestamp(timezone=None, nullable=cls.default_nullable) - - @classmethod - def _from_sqlglot_TIMESTAMPTZ(cls) -> dt.Timestamp: - return dt.Timestamp(timezone="UTC", nullable=cls.default_nullable) - - @classmethod - def _from_sqlglot_GEOGRAPHY( - cls, arg: sge.DataTypeParam | None = None, srid: sge.DataTypeParam | None = None - ) -> dt.GeoSpatial: - return dt.GeoSpatial( - geotype="geography", srid=4326, nullable=cls.default_nullable - ) - - @classmethod - def _from_sqlglot_TINYINT(cls) -> dt.Int64: - return dt.Int64(nullable=cls.default_nullable) - - _from_sqlglot_UINT = _from_sqlglot_USMALLINT = _from_sqlglot_UTINYINT = ( - _from_sqlglot_INT - ) = _from_sqlglot_SMALLINT = _from_sqlglot_TINYINT - - @classmethod - def _from_sqlglot_UBIGINT(cls) -> NoReturn: - raise com.UnsupportedBackendType( - "Unsigned BIGINT isn't representable in BigQuery INT64" - ) - - @classmethod - def _from_sqlglot_FLOAT(cls) -> dt.Float64: - return dt.Float64(nullable=cls.default_nullable) - - @classmethod - def _from_sqlglot_MAP(cls) -> NoReturn: - raise com.UnsupportedBackendType("Maps are not supported in BigQuery") - - @classmethod - def _from_ibis_Map(cls, dtype: dt.Map) -> NoReturn: - raise com.UnsupportedBackendType("Maps are not supported in BigQuery") - - @classmethod - def _from_ibis_Timestamp(cls, dtype: dt.Timestamp) -> sge.DataType: - if dtype.timezone is None: - return sge.DataType(this=sge.DataType.Type.DATETIME) - elif dtype.timezone == "UTC": - return sge.DataType(this=sge.DataType.Type.TIMESTAMPTZ) - else: - raise com.UnsupportedBackendType( - "BigQuery does not support timestamps with timezones other than 'UTC'" - ) - - @classmethod - def _from_ibis_Decimal(cls, dtype: dt.Decimal) -> sge.DataType: - precision = dtype.precision - scale = dtype.scale - if (precision, scale) == (76, 38): - return sge.DataType(this=sge.DataType.Type.BIGDECIMAL) - elif (precision, scale) in ((38, 9), (None, None)): - return sge.DataType(this=sge.DataType.Type.DECIMAL) - else: - raise com.UnsupportedBackendType( - "BigQuery only supports decimal types with precision of 38 and " - f"scale of 9 (NUMERIC) or precision of 76 and scale of 38 (BIGNUMERIC). " - f"Current precision: {dtype.precision}. Current scale: {dtype.scale}" - ) - - @classmethod - def _from_ibis_UInt64(cls, dtype: dt.UInt64) -> NoReturn: - raise com.UnsupportedBackendType( - f"Conversion from {dtype} to BigQuery integer type (Int64) is lossy" - ) - - @classmethod - def _from_ibis_UInt32(cls, dtype: dt.UInt32) -> sge.DataType: - return sge.DataType(this=sge.DataType.Type.BIGINT) - - _from_ibis_UInt8 = _from_ibis_UInt16 = _from_ibis_UInt32 - - @classmethod - def _from_ibis_GeoSpatial(cls, dtype: dt.GeoSpatial) -> sge.DataType: - if (dtype.geotype, dtype.srid) == ("geography", 4326): - return sge.DataType(this=sge.DataType.Type.GEOGRAPHY) - else: - raise com.UnsupportedBackendType( - "BigQuery geography uses points on WGS84 reference ellipsoid." - f"Current geotype: {dtype.geotype}, Current srid: {dtype.srid}" - ) - - -class BigQueryUDFType(BigQueryType): - @classmethod - def _from_ibis_Int64(cls, dtype: dt.Int64) -> NoReturn: - raise com.UnsupportedBackendType( - "int64 is not a supported input or output type in BigQuery UDFs; use float64 instead" - ) diff --git a/third_party/bigframes_vendored/ibis/backends/sql/rewrites.py b/third_party/bigframes_vendored/ibis/backends/sql/rewrites.py deleted file mode 100644 index dbdce90517c..00000000000 --- a/third_party/bigframes_vendored/ibis/backends/sql/rewrites.py +++ /dev/null @@ -1,519 +0,0 @@ -# Contains code from https://github.com/ibis-project/ibis/blob/main/ibis/backends/sql/rewrites.py - -"""Lower the ibis expression graph to a SQL-like relational algebra.""" - -from __future__ import annotations - -import operator -from collections.abc import Mapping -from functools import reduce -from typing import TYPE_CHECKING, Any - -import bigframes_vendored.ibis.common.exceptions as ibis_exceptions -import bigframes_vendored.ibis.expr.datatypes as dt -import bigframes_vendored.ibis.expr.operations as ops -import toolz -from bigframes_vendored.ibis.common.annotations import attribute -from bigframes_vendored.ibis.common.collections import FrozenDict # noqa: TCH001 -from bigframes_vendored.ibis.common.deferred import var -from bigframes_vendored.ibis.common.graph import Graph -from bigframes_vendored.ibis.common.patterns import InstanceOf, Object, Pattern, replace -from bigframes_vendored.ibis.common.typing import VarTuple # noqa: TCH001 -from bigframes_vendored.ibis.expr.rewrites import d, p, replace_parameter -from bigframes_vendored.ibis.expr.schema import Schema -from public import public - -if TYPE_CHECKING: - from collections.abc import Sequence - -x = var("x") -y = var("y") - - -@public -class CTE(ops.Relation): - """Common table expression.""" - - parent: ops.Relation - - @attribute - def schema(self): - return self.parent.schema - - @attribute - def values(self): - return self.parent.values - - -@public -class Select(ops.Relation): - """Relation modelled after SQL's SELECT statement.""" - - parent: ops.Relation - selections: FrozenDict[str, ops.Value] = {} - predicates: VarTuple[ops.Value[dt.Boolean]] = () - qualified: VarTuple[ops.Value[dt.Boolean]] = () - sort_keys: VarTuple[ops.SortKey] = () - - def is_star_selection(self): - return tuple(self.values.items()) == tuple(self.parent.fields.items()) - - @attribute - def values(self): - return self.selections - - @attribute - def schema(self): - return Schema({k: v.dtype for k, v in self.selections.items()}) - - -@public -class FirstValue(ops.Analytic): - """Retrieve the first element.""" - - arg: ops.Column[dt.Any] - - @attribute - def dtype(self): - return self.arg.dtype - - -@public -class LastValue(ops.Analytic): - """Retrieve the last element.""" - - arg: ops.Column[dt.Any] - - @attribute - def dtype(self): - return self.arg.dtype - - -# TODO(kszucs): there is a better strategy to rewrite the relational operations -# to Select nodes by wrapping the leaf nodes in a Select node and then merging -# Project, Filter, Sort, etc. incrementally into the Select node. This way we -# can have tighter control over simplification logic. - - -@replace(p.Project) -def project_to_select(_, **kwargs): - """Convert a Project node to a Select node.""" - return Select(_.parent, selections=_.values) - - -def partition_predicates(predicates): - qualified = [] - unqualified = [] - - for predicate in predicates: - if predicate.find(ops.WindowFunction, filter=ops.Value): - qualified.append(predicate) - else: - unqualified.append(predicate) - - return unqualified, qualified - - -@replace(p.Filter) -def filter_to_select(_, **kwargs): - """Convert a Filter node to a Select node.""" - predicates, qualified = partition_predicates(_.predicates) - return Select( - _.parent, selections=_.values, predicates=predicates, qualified=qualified - ) - - -@replace(p.Sort) -def sort_to_select(_, **kwargs): - """Convert a Sort node to a Select node.""" - return Select(_.parent, selections=_.values, sort_keys=_.keys) - - -if hasattr(p, "DropColumns"): - - @replace(p.DropColumns) - def drop_columns_to_select(_, **kwargs): - """Convert a DropColumns node to a Select node.""" - # if we're dropping fewer than 50% of the parent table's columns then the - # compiled query will likely be smaller than if we list everything *NOT* - # being dropped - if len(_.columns_to_drop) < len(_.schema) // 2: - return _ - return Select(_.parent, selections=_.values) - - -if hasattr(p, "FillNull"): - - @replace(p.FillNull) - def fill_null_to_select(_, **kwargs): - """Rewrite FillNull to a Select node.""" - if isinstance(_.replacements, Mapping): - mapping = _.replacements - else: - mapping = { - name: _.replacements - for name, type in _.parent.schema.items() - if type.nullable - } - - if not mapping: - return _.parent - - selections = {} - for name in _.parent.schema.names: - col = ops.Field(_.parent, name) - if (value := mapping.get(name)) is not None: - col = ops.Alias(ops.Coalesce((col, value)), name) - selections[name] = col - - return Select(_.parent, selections=selections) - - -if hasattr(p, "DropNull"): - - @replace(p.DropNull) - def drop_null_to_select(_, **kwargs): - """Rewrite DropNull to a Select node.""" - if _.subset is None: - columns = [ops.Field(_.parent, name) for name in _.parent.schema.names] - else: - columns = _.subset - - if columns: - preds = [ - reduce( - ops.And if _.how == "any" else ops.Or, - [ops.NotNull(c) for c in columns], - ) - ] - elif _.how == "all": - preds = [ops.Literal(False, dtype=dt.bool)] - else: - return _.parent - - return Select(_.parent, selections=_.values, predicates=tuple(preds)) - - -@replace(p.WindowFunction(p.First | p.Last)) -def first_to_firstvalue(_, **kwargs): - """Convert a First or Last node to a FirstValue or LastValue node.""" - if _.func.where is not None: - raise ibis_exceptions.UnsupportedOperationError( - f"`{type(_.func).__name__.lower()}` with `where` is unsupported " - "in a window function" - ) - klass = FirstValue if isinstance(_.func, ops.First) else LastValue - return _.copy(func=klass(_.func.arg)) - - -def complexity(node): - """Assign a complexity score to a node. - - Subsequent projections can be merged into a single projection by replacing - the fields referenced in the outer projection with the computed expressions - from the inner projection. This inlining can result in very complex value - expressions depending on the projections. In order to prevent excessive - inlining, we assign a complexity score to each node. - - The complexity score assigns 1 to each value expression and adds up in the - tree hierarchy unless there is a Field node where we don't add up the - complexity of the referenced relation. This way we treat fields kind of like - reusable variables considering them less complex than they were inlined. - """ - - def accum(node, *args): - if isinstance(node, ops.Field): - return 1 - else: - return 1 + sum(args) - - return node.map_nodes(accum)[node] - - -@replace(Object(Select, Object(Select))) -def merge_select_select(_, **kwargs): - """Merge subsequent Select relations into one. - - This rewrites eliminates `_.parent` by merging the outer and the inner - `predicates`, `sort_keys` and keeping the outer `selections`. All selections - from the inner Select are inlined into the outer Select. - """ - # don't merge if either the outer or the inner select has window functions - blocking = ( - ops.WindowFunction, - ops.ExistsSubquery, - ops.InSubquery, - ops.Unnest, - ops.Impure, - # This is used for remote functions, which we don't want to copy - ops.ScalarUDF, - ) - if _.find_below(blocking, filter=ops.Value): - return _ - if _.parent.find_below(blocking, filter=ops.Value): - return _ - - subs = {ops.Field(_.parent, k): v for k, v in _.parent.values.items()} - selections = {k: v.replace(subs, filter=ops.Value) for k, v in _.selections.items()} - - predicates = tuple(p.replace(subs, filter=ops.Value) for p in _.predicates) - unique_predicates = toolz.unique(_.parent.predicates + predicates) - - qualified = tuple(p.replace(subs, filter=ops.Value) for p in _.qualified) - unique_qualified = toolz.unique(_.parent.qualified + qualified) - - sort_keys = tuple(s.replace(subs, filter=ops.Value) for s in _.sort_keys) - sort_key_exprs = {s.expr for s in sort_keys} - parent_sort_keys = tuple( - k for k in _.parent.sort_keys if k.expr not in sort_key_exprs - ) - unique_sort_keys = sort_keys + parent_sort_keys - - result = Select( - _.parent.parent, - selections=selections, - predicates=unique_predicates, - qualified=unique_qualified, - sort_keys=unique_sort_keys, - ) - return result if complexity(result) <= complexity(_) else _ - - -def extract_ctes(node: ops.Relation) -> set[ops.Relation]: - cte_types = (Select, ops.Aggregate, ops.JoinChain, ops.Set, ops.Limit, ops.Sample) - dont_count = (ops.Field, ops.CountStar, ops.CountDistinctStar) - - g = Graph.from_bfs(node, filter=~InstanceOf(dont_count)) - result = set() - for op, dependents in g.invert().items(): - if isinstance(op, ops.View) or ( - len(dependents) > 1 and isinstance(op, cte_types) - ): - result.add(op) - - return result - - -def sqlize( - node: ops.Node, - params: Mapping[ops.ScalarParameter, Any], - rewrites: Sequence[Pattern] = (), - fuse_selects: bool = True, -) -> tuple[ops.Node, list[ops.Node]]: - """Lower the ibis expression graph to a SQL-like relational algebra. - - Parameters - ---------- - node - The root node of the expression graph. - params - A mapping of scalar parameters to their values. - rewrites - Supplementary rewrites to apply to the expression graph. - fuse_selects - Whether to merge subsequent Select nodes into one where possible. - - Returns - ------- - Tuple of the rewritten expression graph and a list of CTEs. - - """ - assert isinstance(node, ops.Relation) - - # apply the backend specific rewrites - if rewrites: - node = node.replace(reduce(operator.or_, rewrites)) - - # lower the expression graph to a SQL-like relational algebra - context = {"params": params} - replacements = ( - replace_parameter | project_to_select | filter_to_select | sort_to_select - ) - - if hasattr(p, "FillNull"): - replacements = replacements | fill_null_to_select - - if hasattr(p, "DropNull"): - replacements = replacements | drop_null_to_select - - if hasattr(p, "DropColumns"): - replacements = replacements | drop_columns_to_select - - replacements = replacements | first_to_firstvalue - sqlized = node.replace( - replacements, - context=context, - ) - - # squash subsequent Select nodes into one - if fuse_selects: - simplified = sqlized.replace(merge_select_select) - else: - simplified = sqlized - - # extract common table expressions while wrapping them in a CTE node - ctes = extract_ctes(simplified) - - def wrap(node, _, **kwargs): - new = node.__recreate__(kwargs) - return CTE(new) if node in ctes else new - - result = simplified.replace(wrap) - ctes = [cte.parent for cte in result.find(CTE, ordered=True)] - - return result, ctes - - -# supplemental rewrites selectively used on a per-backend basis - - -@replace(p.WindowFunction(func=p.NTile(y), order_by=())) -def add_order_by_to_empty_ranking_window_functions(_, **kwargs): - """Add an ORDER BY clause to rank window functions that don't have one.""" - return _.copy(order_by=(y,)) - - -"""Replace checks against an empty right side with `False`.""" -empty_in_values_right_side = p.InValues(options=()) >> d.Literal(False, dtype=dt.bool) - - -@replace( - p.WindowFunction(p.RankBase | p.NTile) - | p.StringFind - | p.FindInSet - | p.ArrayPosition -) -def one_to_zero_index(_, **kwargs): - """Subtract one from one-index functions.""" - return ops.Subtract(_, 1) - - -@replace(ops.NthValue) -def add_one_to_nth_value_input(_, **kwargs): - if isinstance(_.nth, ops.Literal): - nth = ops.Literal(_.nth.value + 1, dtype=_.nth.dtype) - else: - nth = ops.Add(_.nth, 1) - return _.copy(nth=nth) - - -@replace(p.WindowFunction(order_by=())) -def rewrite_empty_order_by_window(_, **kwargs): - return _.copy(order_by=(ops.NULL,)) - - -@replace(p.WindowFunction(p.RowNumber | p.NTile | p.MinRank | p.DenseRank)) -def exclude_unsupported_window_frame_from_row_number(_, **kwargs): - # These functions do not support window bounds, only an ordering. - # Also, its kind of messy to insert subtract here, should probably be in visitor - return ops.Subtract( - _.copy(how="none", start=None, end=None, order_by=_.order_by or (ops.NULL,)), 1 - ) - - -@replace(p.WindowFunction(p.PercentRank | p.CumeDist, start=None)) -def exclude_unsupported_window_frame_from_rank(_, **kwargs): - # These functions do not support window bounds, only an ordering. - # Also, its kind of messy to insert subtract here, should probably be in visitor - return _.copy(how="none", start=None, end=None, order_by=_.order_by or (ops.NULL,)) - - -@replace(p.WindowFunction(p.Lag | p.Lead, start=None)) -def exclude_unsupported_window_frame_from_ops(_, **kwargs): - # lag/lead dont' support bounds, but do support ordering - return _.copy(how="none", start=None, end=None, order_by=_.order_by or (ops.NULL,)) - - -# Rewrite rules for lowering a high-level operation into one composed of more -# primitive operations. - - -@replace(p.Log2) -def lower_log2(_, **kwargs): - """Rewrite `log2` as `log`.""" - return ops.Log(_.arg, base=2) - - -@replace(p.Log10) -def lower_log10(_, **kwargs): - """Rewrite `log10` as `log`.""" - return ops.Log(_.arg, base=10) - - -@replace(p.Bucket) -def lower_bucket(_, **kwargs): - """Rewrite `Bucket` as `SearchedCase`.""" - cases = [] - results = [] - - if _.closed == "left": - l_cmp = ops.LessEqual - r_cmp = ops.Less - else: - l_cmp = ops.Less - r_cmp = ops.LessEqual - - user_num_buckets = len(_.buckets) - 1 - - bucket_id = 0 - if _.include_under: - if user_num_buckets > 0: - cmp = ops.Less if _.close_extreme else r_cmp - else: - cmp = ops.LessEqual if _.closed == "right" else ops.Less - cases.append(cmp(_.arg, _.buckets[0])) - results.append(bucket_id) - bucket_id += 1 - - for j, (lower, upper) in enumerate(zip(_.buckets, _.buckets[1:])): - if _.close_extreme and ( - (_.closed == "right" and j == 0) - or (_.closed == "left" and j == (user_num_buckets - 1)) - ): - cases.append( - ops.And(ops.LessEqual(lower, _.arg), ops.LessEqual(_.arg, upper)) - ) - results.append(bucket_id) - else: - cases.append(ops.And(l_cmp(lower, _.arg), r_cmp(_.arg, upper))) - results.append(bucket_id) - bucket_id += 1 - - if _.include_over: - if user_num_buckets > 0: - cmp = ops.Less if _.close_extreme else l_cmp - else: - cmp = ops.Less if _.closed == "right" else ops.LessEqual - - cases.append(cmp(_.buckets[-1], _.arg)) - results.append(bucket_id) - bucket_id += 1 - - return ops.SearchedCase( - cases=tuple(cases), results=tuple(results), default=ops.NULL - ) - - -@replace(p.Capitalize) -def lower_capitalize(_, **kwargs): - """Rewrite Capitalize in terms of substring, concat, upper, and lower.""" - first = ops.Uppercase(ops.Substring(_.arg, start=0, length=1)) - # use length instead of length - 1 to avoid backends complaining about - # asking for negative length - # - # there are at most length - 1 characters, so asking for length is fine - rest = ops.Lowercase(ops.Substring(_.arg, start=1, length=ops.StringLength(_.arg))) - return ops.StringConcat((first, rest)) - - -@replace(p.Sample) -def lower_sample(_, **kwargs): - """Rewrite Sample as `t.filter(random() <= fraction)`. - - Errors as unsupported if a `seed` is specified. - """ - if _.seed is not None: - raise ibis_exceptions.UnsupportedOperationError( - "`Table.sample` with a random seed is unsupported" - ) - return ops.Filter(_.parent, (ops.LessEqual(ops.RandomScalar(), _.fraction),)) diff --git a/third_party/bigframes_vendored/ibis/common/__init__.py b/third_party/bigframes_vendored/ibis/common/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/third_party/bigframes_vendored/ibis/common/annotations.py b/third_party/bigframes_vendored/ibis/common/annotations.py deleted file mode 100644 index 9eb0de4ee24..00000000000 --- a/third_party/bigframes_vendored/ibis/common/annotations.py +++ /dev/null @@ -1,651 +0,0 @@ -# Contains code from https://github.com/ibis-project/ibis/blob/9.2.0/ibis/common/annotations.py - -from __future__ import annotations - -import functools -import inspect -import types -from typing import TYPE_CHECKING -from typing import Any as AnyType - -from bigframes_vendored.ibis.common.bases import Immutable, Slotted -from bigframes_vendored.ibis.common.patterns import ( - Any, - FrozenDictOf, - NoMatch, - Option, - Pattern, - TupleOf, -) -from bigframes_vendored.ibis.common.patterns import pattern as ensure_pattern -from bigframes_vendored.ibis.common.typing import format_typehint, get_type_hints - -if TYPE_CHECKING: - from collections.abc import Callable, Sequence - -EMPTY = inspect.Parameter.empty # marker for missing argument -KEYWORD_ONLY = inspect.Parameter.KEYWORD_ONLY -POSITIONAL_ONLY = inspect.Parameter.POSITIONAL_ONLY -POSITIONAL_OR_KEYWORD = inspect.Parameter.POSITIONAL_OR_KEYWORD -VAR_KEYWORD = inspect.Parameter.VAR_KEYWORD -VAR_POSITIONAL = inspect.Parameter.VAR_POSITIONAL - - -_any = Any() - - -class ValidationError(Exception): - __slots__ = () - - -class AttributeValidationError(ValidationError): - __slots__ = ("name", "value", "pattern") - - def __init__(self, name: str, value: AnyType, pattern: Pattern): - self.name = name - self.value = value - self.pattern = pattern - - def __str__(self): - return f"Failed to validate attribute `{self.name}`: {self.value!r} is not {self.pattern.describe()}" - - -class ReturnValidationError(ValidationError): - __slots__ = ("func", "value", "pattern") - - def __init__(self, func: Callable, value: AnyType, pattern: Pattern): - self.func = func - self.value = value - self.pattern = pattern - - def __str__(self): - return f"Failed to validate return value of `{self.func.__name__}`: {self.value!r} is not {self.pattern.describe()}" - - -class SignatureValidationError(ValidationError): - __slots__ = ("msg", "sig", "func", "args", "kwargs", "errors") - - def __init__( - self, - msg: str, - sig: Signature, - func: Callable, - args: tuple[AnyType, ...], - kwargs: dict[str, AnyType], - errors: Sequence[tuple[str, AnyType, Pattern]] = (), - ): - self.msg = msg - self.sig = sig - self.func = func - self.args = args - self.kwargs = kwargs - self.errors = errors - - def __str__(self): - args = tuple(repr(arg) for arg in self.args) - args += tuple(f"{k}={v!r}" for k, v in self.kwargs.items()) - call = f"{self.func.__name__}({', '.join(args)})" - - errors = "" - for name, value, pattern in self.errors: - errors += f"\n `{name}`: {value!r} is not {pattern.describe()}" - - sig = f"{self.func.__name__}{self.sig}" - cause = str(self.__cause__) if self.__cause__ else "" - - return self.msg.format(sig=sig, call=call, cause=cause, errors=errors) - - -class Annotation(Slotted, Immutable): - """Base class for all annotations. - - Annotations are used to mark fields in a class and to validate them. - """ - - __slots__ = ("pattern", "default") - pattern: Pattern - default: AnyType - - def validate(self, name: str, value: AnyType, this: AnyType) -> AnyType: - """Validate the field. - - Parameters - ---------- - name - The name of the attribute. - value - The value of the attribute. - this - The instance of the class the attribute is defined on. - - Returns - ------- - The validated value for the field. - - """ - result = self.pattern.match(value, this) - if result is NoMatch: - raise AttributeValidationError( - name=name, - value=value, - pattern=self.pattern, - ) - return result - - -class Attribute(Annotation): - """Annotation to mark a field in a class. - - An optional pattern can be provider to validate the field every time it - is set. - - Parameters - ---------- - pattern : Pattern, default noop - Pattern to validate the field. - default : Callable, default EMPTY - Callable to compute the default value of the field. - - """ - - def __init__(self, pattern: Pattern = _any, default: AnyType = EMPTY): - super().__init__(pattern=ensure_pattern(pattern), default=default) - - def has_default(self): - """Check if the field has a default value. - - Returns - ------- - bool - - """ - return self.default is not EMPTY - - def get_default(self, name: str, this: AnyType) -> AnyType: - """Get the default value of the field. - - Parameters - ---------- - name - The name of the attribute. - this - The instance of the class the attribute is defined on. - - Returns - ------- - The default value for the field. - - """ - if callable(self.default): - value = self.default(this) - else: - value = self.default - return self.validate(name, value, this) - - def __call__(self, default): - """Needed to support the decorator syntax.""" - return self.__class__(self.pattern, default) - - -class Argument(Annotation): - """Annotation type for all fields which should be passed as arguments. - - Parameters - ---------- - pattern - Optional pattern to validate the argument. - default - Optional default value of the argument. - typehint - Optional typehint of the argument. - kind - Kind of the argument, one of `inspect.Parameter` constants. - Defaults to positional or keyword. - - """ - - __slots__ = ("typehint", "kind") - typehint: AnyType - kind: int - - def __init__( - self, - pattern: Pattern = _any, - default: AnyType = EMPTY, - typehint: type | None = None, - kind: int = POSITIONAL_OR_KEYWORD, - ): - super().__init__( - pattern=ensure_pattern(pattern), - default=default, - typehint=typehint, - kind=kind, - ) - - -def attribute(pattern=_any, default=EMPTY): - """Annotation to mark a field in a class.""" - if default is EMPTY and isinstance(pattern, (types.FunctionType, types.MethodType)): - return Attribute(default=pattern) - else: - return Attribute(pattern, default=default) - - -def argument(pattern=_any, default=EMPTY, typehint=None): - """Annotation type for all fields which should be passed as arguments.""" - return Argument(pattern, default=default, typehint=typehint) - - -def optional(pattern=_any, default=None, typehint=None): - """Annotation to allow and treat `None` values as missing arguments.""" - if pattern is None: - pattern = Option(Any(), default=default) - else: - pattern = Option(pattern, default=default) - return Argument(pattern, default=None, typehint=typehint) - - -def varargs(pattern=_any, typehint=None): - """Annotation to mark a variable length positional arguments.""" - return Argument(TupleOf(pattern), kind=VAR_POSITIONAL, typehint=typehint) - - -def varkwargs(pattern=_any, typehint=None): - """Annotation to mark a variable length keyword arguments.""" - return Argument(FrozenDictOf(_any, pattern), kind=VAR_KEYWORD, typehint=typehint) - - -class Parameter(inspect.Parameter): - """Augmented Parameter class to additionally hold a pattern object.""" - - __slots__ = () - - def __init__(self, name, annotation): - if not isinstance(annotation, Argument): - raise TypeError( - f"annotation must be an instance of Argument, got {annotation}" - ) - super().__init__( - name, - kind=annotation.kind, - default=annotation.default, - annotation=annotation, - ) - - def __str__(self): - formatted = self._name - - if self._annotation is not EMPTY: - typehint = format_typehint(self._annotation.typehint) - formatted = f"{formatted}: {typehint}" - - if self._default is not EMPTY: - if self._annotation is not EMPTY: - formatted = f"{formatted} = {self._default!r}" - else: - formatted = f"{formatted}={self._default!r}" - - if self._kind == VAR_POSITIONAL: - formatted = "*" + formatted - elif self._kind == VAR_KEYWORD: - formatted = "**" + formatted - - return formatted - - -class Signature(inspect.Signature): - """Validatable signature. - - Primarily used in the implementation of `ibis.common.grounds.Annotable`. - """ - - __slots__ = () - - @classmethod - def merge(cls, *signatures, **annotations): - """Merge multiple signatures. - - In addition to concatenating the parameters, it also reorders the - parameters so that optional arguments come after mandatory arguments. - - Parameters - ---------- - *signatures : Signature - Signature instances to merge. - **annotations : dict - Annotations to add to the merged signature. - - Returns - ------- - Signature - - """ - params = {} - for sig in signatures: - params.update(sig.parameters) - - inherited = set(params.keys()) - for name, annot in annotations.items(): - params[name] = Parameter(name, annotation=annot) - - # mandatory fields without default values must precede the optional - # ones in the function signature, the partial ordering will be kept - var_args, var_kwargs = [], [] - new_args, new_kwargs = [], [] - old_args, old_kwargs = [], [] - - for name, param in params.items(): - if param.kind == VAR_POSITIONAL: - if var_args: - raise TypeError("only one variadic *args parameter is allowed") - var_args.append(param) - elif param.kind == VAR_KEYWORD: - if var_kwargs: - raise TypeError("only one variadic **kwargs parameter is allowed") - var_kwargs.append(param) - elif name in inherited: - if param.default is EMPTY: - old_args.append(param) - else: - old_kwargs.append(param) - elif param.default is EMPTY: - new_args.append(param) - else: - new_kwargs.append(param) - - return cls( - old_args + new_args + var_args + new_kwargs + old_kwargs + var_kwargs - ) - - @classmethod - def from_callable(cls, fn, patterns=None, return_pattern=None): - """Create a validateable signature from a callable. - - Parameters - ---------- - fn : Callable - Callable to create a signature from. - patterns : list or dict, default None - Pass patterns to add missing or override existing argument type - annotations. - return_pattern : Pattern, default None - Pattern for the return value of the callable. - - Returns - ------- - Signature - - """ - sig = super().from_callable(fn) - typehints = get_type_hints(fn) - - if patterns is None: - patterns = {} - elif isinstance(patterns, (list, tuple)): - # create a mapping of parameter name to pattern - patterns = dict(zip(sig.parameters.keys(), patterns)) - elif not isinstance(patterns, dict): - raise TypeError(f"patterns must be a list or dict, got {type(patterns)}") - - parameters = [] - for param in sig.parameters.values(): - name = param.name - kind = param.kind - default = param.default - typehint = typehints.get(name) - - if name in patterns: - pattern = patterns[name] - elif typehint is not None: - pattern = Pattern.from_typehint(typehint) - else: - pattern = _any - - if kind is VAR_POSITIONAL: - annot = varargs(pattern, typehint=typehint) - elif kind is VAR_KEYWORD: - annot = varkwargs(pattern, typehint=typehint) - else: - annot = Argument(pattern, kind=kind, default=default, typehint=typehint) - - parameters.append(Parameter(param.name, annot)) - - if return_pattern is not None: - return_annotation = return_pattern - elif (typehint := typehints.get("return")) is not None: - return_annotation = Pattern.from_typehint(typehint) - else: - return_annotation = EMPTY - - return cls(parameters, return_annotation=return_annotation) - - def unbind(self, this: dict[str, Any]) -> tuple[tuple[Any, ...], dict[str, Any]]: - """Reverse bind of the parameters. - - Attempts to reconstructs the original arguments as keyword only arguments. - - Parameters - ---------- - this : Any - Object with attributes matching the signature parameters. - - Returns - ------- - args : (args, kwargs) - Tuple of positional and keyword arguments. - - """ - # does the reverse of bind, but doesn't apply defaults - args: list = [] - kwargs: dict = {} - for name, param in self.parameters.items(): - value = this[name] - if param.kind is POSITIONAL_OR_KEYWORD: - args.append(value) - elif param.kind is VAR_POSITIONAL: - args.extend(value) - elif param.kind is VAR_KEYWORD: - kwargs.update(value) - elif param.kind is KEYWORD_ONLY: - kwargs[name] = value - elif param.kind is POSITIONAL_ONLY: - args.append(value) - else: - raise TypeError(f"unsupported parameter kind {param.kind}") - return tuple(args), kwargs - - def validate(self, func, args, kwargs): - """Validate the arguments against the signature. - - Parameters - ---------- - func : Callable - Callable to validate the arguments for. - args : tuple - Positional arguments. - kwargs : dict - Keyword arguments. - - Returns - ------- - validated : dict - Dictionary of validated arguments. - - """ - try: - bound = self.bind(*args, **kwargs) - bound.apply_defaults() - except TypeError as err: - raise SignatureValidationError( - "{call} {cause}\n\nExpected signature: {sig}", - sig=self, - func=func, - args=args, - kwargs=kwargs, - ) from err - - this, errors = {}, [] - for name, value in bound.arguments.items(): - param = self.parameters[name] - pattern = param.annotation.pattern - - result = pattern.match(value, this) - if result is NoMatch: - errors.append((name, value, pattern)) - else: - this[name] = result - - if errors: - raise SignatureValidationError( - "{call} has failed due to the following errors:{errors}\n\nExpected signature: {sig}", - sig=self, - func=func, - args=args, - kwargs=kwargs, - errors=errors, - ) - - return this - - def validate_nobind(self, func, kwargs): - """Validate the arguments against the signature without binding.""" - this, errors = {}, [] - for name, param in self.parameters.items(): - value = kwargs.get(name, param.default) - if value is EMPTY: - raise TypeError(f"missing required argument `{name!r}`") - - pattern = param.annotation.pattern - result = pattern.match(value, this) - if result is NoMatch: - errors.append((name, value, pattern)) - else: - this[name] = result - - if errors: - raise SignatureValidationError( - "{call} has failed due to the following errors:{errors}\n\nExpected signature: {sig}", - sig=self, - func=func, - args=(), - kwargs=kwargs, - errors=errors, - ) - - return this - - def validate_return(self, func, value): - """Validate the return value of a function. - - Parameters - ---------- - func : Callable - Callable to validate the return value for. - value : Any - Return value of the function. - - Returns - ------- - validated : Any - Validated return value. - - """ - if self.return_annotation is EMPTY: - return value - - result = self.return_annotation.match(value, {}) - if result is NoMatch: - raise ReturnValidationError( - func=func, - value=value, - pattern=self.return_annotation, - ) - - return result - - -def annotated(_1=None, _2=None, _3=None, **kwargs): - """Create functions with arguments validated at runtime. - - There are various ways to apply this decorator: - - 1. With type annotations - - >>> @annotated - ... def foo(x: int, y: str) -> float: - ... return float(x) + float(y) - - 2. With argument patterns passed as keyword arguments - - >>> from ibis.common.patterns import InstanceOf as instance_of - >>> @annotated(x=instance_of(int), y=instance_of(str)) - ... def foo(x, y): - ... return float(x) + float(y) - - 3. With mixing type annotations and patterns where the latter takes precedence - - >>> @annotated(x=instance_of(float)) - ... def foo(x: int, y: str) -> float: - ... return float(x) + float(y) - - 4. With argument patterns passed as a list and/or an optional return pattern - - >>> @annotated([instance_of(int), instance_of(str)], instance_of(float)) - ... def foo(x, y): - ... return float(x) + float(y) - - Parameters - ---------- - *args : Union[ - tuple[Callable], - tuple[list[Pattern], Callable], - tuple[list[Pattern], Pattern, Callable] - ] - Positional arguments. - - If a single callable is passed, it's wrapped with the signature - - If two arguments are passed, the first one is a list of patterns for the - arguments and the second one is the callable to wrap - - If three arguments are passed, the first one is a list of patterns for the - arguments, the second one is a pattern for the return value and the third - one is the callable to wrap - **kwargs : dict[str, Pattern] - Patterns for the arguments. - - Returns - ------- - Callable - - """ - if _1 is None: - return functools.partial(annotated, **kwargs) - elif _2 is None: - if callable(_1): - func, patterns, return_pattern = _1, None, None - else: - return functools.partial(annotated, _1, **kwargs) - elif _3 is None: - if not isinstance(_2, Pattern): - func, patterns, return_pattern = _2, _1, None - else: - return functools.partial(annotated, _1, _2, **kwargs) - else: - func, patterns, return_pattern = _3, _1, _2 - - sig = Signature.from_callable( - func, patterns=patterns or kwargs, return_pattern=return_pattern - ) - - @functools.wraps(func) - def wrapped(*args, **kwargs): - # 1. Validate the passed arguments - values = sig.validate(func, args, kwargs) - # 2. Reconstruction of the original arguments - args, kwargs = sig.unbind(values) - # 3. Call the function with the validated arguments - result = func(*args, **kwargs) - # 4. Validate the return value - return sig.validate_return(func, result) - - wrapped.__signature__ = sig - - return wrapped diff --git a/third_party/bigframes_vendored/ibis/common/bases.py b/third_party/bigframes_vendored/ibis/common/bases.py deleted file mode 100644 index 2d5d798c318..00000000000 --- a/third_party/bigframes_vendored/ibis/common/bases.py +++ /dev/null @@ -1,246 +0,0 @@ -# Contains code from https://github.com/ibis-project/ibis/blob/9.2.0/ibis/common/bases.py - -from __future__ import annotations - -import collections.abc -from abc import abstractmethod -from typing import TYPE_CHECKING, Any -from weakref import WeakValueDictionary - -if TYPE_CHECKING: - from collections.abc import Mapping - - from typing_extensions import Self - - -class AbstractMeta(type): - """Base metaclass for many of the ibis core classes. - - Enforce the subclasses to define a `__slots__` attribute and provide a - `__create__` classmethod to change the instantiation behavior of the class. - - Support abstract methods without extending `abc.ABCMeta`. While it provides - a reduced feature set compared to `abc.ABCMeta` (no way to register virtual - subclasses) but avoids expensive instance checks by enforcing explicit - subclassing. - """ - - __slots__ = () - - def __new__(metacls, clsname, bases, dct, **kwargs): - # enforce slot definitions - dct.setdefault("__slots__", ()) - - # construct the class object - cls = super().__new__(metacls, clsname, bases, dct, **kwargs) - - # calculate abstract methods existing in the class - abstracts = { - name - for name, value in dct.items() - if getattr(value, "__isabstractmethod__", False) - } - for parent in bases: - for name in getattr(parent, "__abstractmethods__", set()): - value = getattr(cls, name, None) - if getattr(value, "__isabstractmethod__", False): - abstracts.add(name) - - # set the abstract methods for the class - cls.__abstractmethods__ = frozenset(abstracts) - - return cls - - def __call__(cls, *args, **kwargs): - """Create a new instance of the class. - - The subclass may override the `__create__` classmethod to change the - instantiation behavior. This is similar to overriding the `__new__` - method, but without conditionally calling the `__init__` based on the - return type. - - Parameters - ---------- - args : tuple - Positional arguments eventually passed to the `__init__` method. - kwargs : dict - Keyword arguments eventually passed to the `__init__` method. - - Returns - ------- - The newly created instance of the class. No extra initialization - - """ - return cls.__create__(*args, **kwargs) - - -class Abstract(metaclass=AbstractMeta): - """Base class for many of the ibis core classes, see `AbstractMeta`.""" - - __slots__ = ("__weakref__",) - __create__ = classmethod(type.__call__) # type: ignore - - -class Immutable(Abstract): - """Prohibit attribute assignment on the instance.""" - - def __copy__(self): - return self - - def __deepcopy__(self, memo): - return self - - def __setattr__(self, name: str, _: Any) -> None: - raise AttributeError( - f"Attribute {name!r} cannot be assigned to immutable instance of " - f"type {type(self)}" - ) - - -class Singleton(Abstract): - """Cache instances of the class based on instantiation arguments.""" - - __instances__: Mapping[Any, Self] = WeakValueDictionary() - - @classmethod - def __create__(cls, *args, **kwargs): - key = (cls, args, tuple(kwargs.items())) - try: - return cls.__instances__[key] - except KeyError: - instance = super().__create__(*args, **kwargs) - cls.__instances__[key] = instance - return instance - - -class Final(Abstract): - """Prohibit subclassing.""" - - def __init_subclass__(cls, **kwargs): - super().__init_subclass__(**kwargs) - cls.__init_subclass__ = cls.__prohibit_inheritance__ - - @classmethod - def __prohibit_inheritance__(cls, **kwargs): - raise TypeError(f"Cannot inherit from final class {cls}") - - -@collections.abc.Hashable.register -class Hashable(Abstract): - @abstractmethod - def __hash__(self) -> int: ... - - -class Comparable(Abstract): - """Enable quick equality comparisons. - - The subclasses must implement the `__equals__` method that returns a boolean - value indicating whether the two instances are equal. This method is called - only if the two instances are of the same type and the result is cached for - future comparisons. - - Since the class holds a global cache of comparison results, it is important - to make sure that the instances are not kept alive longer than necessary. - """ - - __cache__ = {} - - @abstractmethod - def __equals__(self, other) -> bool: ... - - def __eq__(self, other) -> bool: - if self is other: - return True - - # type comparison should be cheap - if type(self) is not type(other): - return False - - id1 = id(self) - id2 = id(other) - try: - return self.__cache__[id1][id2] - except KeyError: - result = self.__equals__(other) - self.__cache__.setdefault(id1, {})[id2] = result - self.__cache__.setdefault(id2, {})[id1] = result - return result - - def __del__(self): - id1 = id(self) - for id2 in self.__cache__.pop(id1, ()): - eqs2 = self.__cache__[id2] - del eqs2[id1] - if not eqs2: - del self.__cache__[id2] - - -class SlottedMeta(AbstractMeta): - def __new__(metacls, clsname, bases, dct, **kwargs): - fields = dct.get("__fields__", dct.get("__slots__", ())) - inherited = (getattr(base, "__fields__", ()) for base in bases) - dct["__fields__"] = sum(inherited, ()) + fields - return super().__new__(metacls, clsname, bases, dct, **kwargs) - - -class Slotted(Abstract, metaclass=SlottedMeta): - """A lightweight alternative to `ibis.common.grounds.Annotable`. - - The class is mostly used to reduce boilerplate code. - """ - - def __init__(self, **kwargs) -> None: - for field in self.__fields__: - object.__setattr__(self, field, kwargs[field]) - - def __eq__(self, other) -> bool: - if self is other: - return True - if type(self) is not type(other): - return NotImplemented - return all(getattr(self, n) == getattr(other, n) for n in self.__fields__) - - def __getstate__(self): - return {k: getattr(self, k) for k in self.__fields__} - - def __setstate__(self, state): - for name, value in state.items(): - object.__setattr__(self, name, value) - - def __repr__(self): - fields = {k: getattr(self, k) for k in self.__fields__} - fieldstring = ", ".join(f"{k}={v!r}" for k, v in fields.items()) - return f"{self.__class__.__name__}({fieldstring})" - - def __rich_repr__(self): - for name in self.__fields__: - yield name, getattr(self, name) - - -class FrozenSlotted(Slotted, Immutable, Hashable): - """A lightweight alternative to `ibis.common.grounds.Concrete`. - - This class is used to create immutable dataclasses with slots and a precomputed - hash value for quicker dictionary lookups. - """ - - __slots__ = ("__precomputed_hash__",) - __fields__ = () - __precomputed_hash__: int - - def __init__(self, **kwargs) -> None: - values = [] - for field in self.__fields__: - values.append(value := kwargs[field]) - object.__setattr__(self, field, value) - hashvalue = hash((self.__class__, tuple(values))) - object.__setattr__(self, "__precomputed_hash__", hashvalue) - - def __setstate__(self, state): - for name, value in state.items(): - object.__setattr__(self, name, value) - hashvalue = hash((self.__class__, tuple(state.values()))) - object.__setattr__(self, "__precomputed_hash__", hashvalue) - - def __hash__(self) -> int: - return self.__precomputed_hash__ diff --git a/third_party/bigframes_vendored/ibis/common/caching.py b/third_party/bigframes_vendored/ibis/common/caching.py deleted file mode 100644 index b4257410e1e..00000000000 --- a/third_party/bigframes_vendored/ibis/common/caching.py +++ /dev/null @@ -1,98 +0,0 @@ -# Contains code from https://github.com/ibis-project/ibis/blob/9.2.0/ibis/common/caching.py - -from __future__ import annotations - -import functools -import sys -from collections import namedtuple -from typing import TYPE_CHECKING, Any -from weakref import finalize, ref - -if TYPE_CHECKING: - from collections.abc import Callable - - -def memoize(func: Callable) -> Callable: - """Memoize a function.""" - cache: dict = {} - - @functools.wraps(func) - def wrapper(*args, **kwargs): - key = (args, tuple(kwargs.items())) - try: - return cache[key] - except KeyError: - result = func(*args, **kwargs) - cache[key] = result - return result - - return wrapper - - -CacheEntry = namedtuple("CacheEntry", ["name", "ref", "finalizer"]) - - -class RefCountedCache: - """A cache with implicitly reference-counted values. - - We could implement `MutableMapping`, but the `__setitem__` implementation - doesn't make sense and the `len` and `__iter__` methods aren't used. - - We can implement that interface if and when we need to. - """ - - def __init__( - self, - *, - populate: Callable[[str, Any], None], - lookup: Callable[[str], Any], - finalize: Callable[[Any], None], - ) -> None: - self.populate = populate - self.lookup = lookup - self.finalize = finalize - - self.cache: dict[Any, CacheEntry] = dict() - - def get(self, key, default=None): - if (entry := self.cache.get(key)) is not None: - op = entry.ref() - return op if op is not None else default - return default - - def __getitem__(self, key): - op = self.cache[key].ref() - if op is None: - raise KeyError(key) - return op - - def store(self, input): - """Compute and store a reference to `key`.""" - from bigframes_vendored.ibis.util import gen_name - - key = input.op() - name = gen_name("cache") - self.populate(name, input) - cached = self.lookup(name) - finalizer = finalize(cached, self._release, key) - - self.cache[key] = CacheEntry(name, ref(cached), finalizer) - - return cached - - def release(self, name: str) -> None: - # Could be sped up with an inverse dictionary - for key, entry in self.cache.items(): - if entry.name == name: - self._release(key) - return - - def _release(self, key) -> None: - entry = self.cache.pop(key) - try: - self.finalize(entry.name) - except Exception: - # suppress exceptions during interpreter shutdown - if not sys.is_finalizing(): - raise - entry.finalizer.detach() diff --git a/third_party/bigframes_vendored/ibis/common/collections.py b/third_party/bigframes_vendored/ibis/common/collections.py deleted file mode 100644 index 718b94235dd..00000000000 --- a/third_party/bigframes_vendored/ibis/common/collections.py +++ /dev/null @@ -1,372 +0,0 @@ -# Contains code from https://github.com/ibis-project/ibis/blob/9.2.0/ibis/common/collections.py - -from __future__ import annotations - -import collections.abc -from abc import abstractmethod -from itertools import tee -from typing import TYPE_CHECKING, Any, Generic, TypeVar - -from bigframes_vendored.ibis.common.bases import Abstract, Hashable -from bigframes_vendored.ibis.common.exceptions import ConflictingValuesError -from public import public - -if TYPE_CHECKING: - from typing_extensions import Self - -K = TypeVar("K", bound=collections.abc.Hashable) -V = TypeVar("V") - - -# The following classes provide an alternative to the `collections.abc` module -# which can be used with `ibis.common.bases` without metaclass conflicts but -# remains compatible with the `collections.abc` module. The main advantage is -# faster `isinstance` checks. - - -@collections.abc.Iterable.register -class Iterable(Abstract, Generic[V]): - """Iterable abstract base class for quicker isinstance checks.""" - - @abstractmethod - def __iter__(self): ... - - -@collections.abc.Reversible.register -class Reversible(Iterable[V]): - """Reverse iterable abstract base class for quicker isinstance checks.""" - - @abstractmethod - def __reversed__(self): ... - - -@collections.abc.Iterator.register -class Iterator(Iterable[V]): - """Iterator abstract base class for quicker isinstance checks.""" - - @abstractmethod - def __next__(self): ... - - def __iter__(self): - return self - - -@collections.abc.Sized.register -class Sized(Abstract): - """Sized abstract base class for quicker isinstance checks.""" - - @abstractmethod - def __len__(self): ... - - -@collections.abc.Container.register -class Container(Abstract, Generic[V]): - """Container abstract base class for quicker isinstance checks.""" - - @abstractmethod - def __contains__(self, x): ... - - -@collections.abc.Collection.register -class Collection(Sized, Iterable[V], Container[V]): - """Collection abstract base class for quicker isinstance checks.""" - - -@collections.abc.Sequence.register -class Sequence(Reversible[V], Collection[V]): - """Sequence abstract base class for quicker isinstance checks.""" - - @abstractmethod - def __getitem__(self, index): ... - - def __iter__(self): - i = 0 - try: - while True: - yield self[i] - i += 1 - except IndexError: - return - - def __contains__(self, value): - return any(v is value or v == value for v in self) - - def __reversed__(self): - for i in reversed(range(len(self))): - yield self[i] - - def index(self, value, start=0, stop=None): - if start is not None and start < 0: - start = max(len(self) + start, 0) - if stop is not None and stop < 0: - stop += len(self) - - i = start - while stop is None or i < stop: - try: - v = self[i] - except IndexError: - break - if v is value or v == value: - return i - i += 1 - raise ValueError - - def count(self, value): - return sum(1 for v in self if v is value or v == value) - - -@collections.abc.Mapping.register -class Mapping(Collection[K], Generic[K, V]): - """Mapping abstract base class for quicker isinstance checks.""" - - @abstractmethod - def __getitem__(self, key): ... - - def get(self, key, default=None): - try: - return self[key] - except KeyError: - return default - - def __contains__(self, key): - try: - self[key] - except KeyError: - return False - else: - return True - - def keys(self): - return collections.abc.KeysView(self) - - def items(self): - return collections.abc.ItemsView(self) - - def values(self): - return collections.abc.ValuesView(self) - - def __eq__(self, other): - if not isinstance(other, collections.abc.Mapping): - return NotImplemented - return dict(self.items()) == dict(other.items()) - - -@public -class MapSet(Mapping[K, V]): - """A mapping that also supports set-like operations. - - It is an altered version of `collections.abc.Mapping` that supports set-like - operations. The `__iter__`, `__len__`, and `__getitem__` methods must be - implemented. - - The set-like operations' other operand must be a `Mapping`. If the two - operands contain common keys but with different values, then the operation - becomes ambiguous and an exception will be raised. - - Examples - -------- - >>> from ibis.common.collections import MapSet - >>> class MyMap(MapSet): - ... __slots__ = ("_data",) - ... - ... def __init__(self, *args, **kwargs): - ... self._data = dict(*args, **kwargs) - ... - ... def __iter__(self): - ... return iter(self._data) - ... - ... def __len__(self): - ... return len(self._data) - ... - ... def __getitem__(self, key): - ... return self._data[key] - ... - ... def __repr__(self): - ... return f"MyMap({repr(self._data)})" - >>> m = MyMap(a=1, b=2) - >>> n = dict(a=1, b=2, c=3) - >>> m <= n - True - >>> m < n - True - >>> n - m - MyMap({'c': 3}) - >>> m & n - MyMap({'a': 1, 'b': 2}) - >>> m | n - MyMap({'a': 1, 'b': 2, 'c': 3}) - - """ - - def _check_conflict(self, other: collections.abc.Mapping) -> set[K]: - # Check if there are conflicting key-value pairs between self and other. - # A key-value pair is conflicting if the key is the same but the value is - # different. - common_keys = self.keys() & other.keys() - conflicts = { - (key, self[key], other[key]) - for key in common_keys - if self[key] != other[key] - } - if conflicts: - raise ConflictingValuesError(conflicts) - return common_keys - - def __ge__(self, other: collections.abc.Mapping) -> bool: - if not isinstance(other, collections.abc.Mapping): - return NotImplemented - common_keys = self._check_conflict(other) - return other.keys() == common_keys - - def __gt__(self, other: collections.abc.Mapping) -> bool: - if not isinstance(other, collections.abc.Mapping): - return NotImplemented - return len(self) > len(other) and self.__ge__(other) - - def __le__(self, other: collections.abc.Mapping) -> bool: - if not isinstance(other, collections.abc.Mapping): - return NotImplemented - common_keys = self._check_conflict(other) - return self.keys() == common_keys - - def __lt__(self, other: collections.abc.Mapping) -> bool: - if not isinstance(other, collections.abc.Mapping): - return NotImplemented - return len(self) < len(other) and self.__le__(other) - - def __and__(self, other: collections.abc.Mapping) -> Self: - if not isinstance(other, collections.abc.Mapping): - return NotImplemented - common_keys = self._check_conflict(other) - intersection = {k: v for k, v in self.items() if k in common_keys} - return self.__class__(intersection) - - def __sub__(self, other: collections.abc.Mapping) -> Self: - if not isinstance(other, collections.abc.Mapping): - return NotImplemented - common_keys = self._check_conflict(other) - difference = {k: v for k, v in self.items() if k not in common_keys} - return self.__class__(difference) - - def __rsub__(self, other: collections.abc.Mapping) -> Self: - if not isinstance(other, collections.abc.Mapping): - return NotImplemented - common_keys = self._check_conflict(other) - difference = {k: v for k, v in other.items() if k not in common_keys} - return self.__class__(difference) - - def __or__(self, other: collections.abc.Mapping) -> Self: - if not isinstance(other, collections.abc.Mapping): - return NotImplemented - self._check_conflict(other) - union = {**self, **other} - return self.__class__(union) - - def __xor__(self, other: collections.abc.Mapping) -> Self: - if not isinstance(other, collections.abc.Mapping): - return NotImplemented - left = self - other - right = other - self - left._check_conflict(right) - union = {**left, **right} - return self.__class__(union) - - def isdisjoint(self, other: collections.abc.Mapping) -> bool: - common_keys = self._check_conflict(other) - return not common_keys - - -@public -class FrozenDict(dict, Mapping[K, V], Hashable): - __slots__ = ("__precomputed_hash__",) - __precomputed_hash__: int - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - hashable = frozenset(self.items()) - object.__setattr__(self, "__precomputed_hash__", hash(hashable)) - - def __hash__(self) -> int: - return self.__precomputed_hash__ - - def __setitem__(self, key: K, value: V) -> None: - raise TypeError( - f"'{self.__class__.__name__}' object does not support item assignment" - ) - - def __setattr__(self, name: str, _: Any) -> None: - raise TypeError(f"Attribute {name!r} cannot be assigned to frozendict") - - def __reduce__(self) -> tuple: - return (self.__class__, (dict(self),)) - - -@public -class FrozenOrderedDict(FrozenDict[K, V]): - def __init__(self, *args, **kwargs): - super(FrozenDict, self).__init__(*args, **kwargs) - hashable = tuple(self.items()) - object.__setattr__(self, "__precomputed_hash__", hash(hashable)) - - def __hash__(self) -> int: - return self.__precomputed_hash__ - - def __eq__(self, other: Any) -> bool: - if not isinstance(other, collections.abc.Mapping): - return NotImplemented - return tuple(self.items()) == tuple(other.items()) - - def __ne__(self, other: Any) -> bool: - return not self == other - - -class RewindableIterator(Iterator[V]): - """Iterator that can be rewound to a checkpoint. - - Examples - -------- - >>> it = RewindableIterator(range(5)) - >>> next(it) - 0 - >>> next(it) - 1 - >>> it.checkpoint() - >>> next(it) - 2 - >>> next(it) - 3 - >>> it.rewind() - >>> next(it) - 2 - >>> next(it) - 3 - >>> next(it) - 4 - - """ - - __slots__ = ("_iterator", "_checkpoint") - - def __init__(self, iterable): - self._iterator = iter(iterable) - self._checkpoint = None - - def __next__(self): - return next(self._iterator) - - def rewind(self): - """Rewind the iterator to the last checkpoint.""" - if self._checkpoint is None: - raise ValueError("No checkpoint to rewind to.") - self._iterator, self._checkpoint = tee(self._checkpoint) - - def checkpoint(self): - """Create a checkpoint of the current iterator state.""" - self._iterator, self._checkpoint = tee(self._iterator) - - -# Need to provide type hint as else a static type checker does not recognize -# that frozendict exists in this module -frozendict: type[FrozenDict] -public(frozendict=FrozenDict) diff --git a/third_party/bigframes_vendored/ibis/common/deferred.py b/third_party/bigframes_vendored/ibis/common/deferred.py deleted file mode 100644 index 70e54be1503..00000000000 --- a/third_party/bigframes_vendored/ibis/common/deferred.py +++ /dev/null @@ -1,629 +0,0 @@ -# Contains code from https://github.com/ibis-project/ibis/blob/9.2.0/ibis/common/deferred.py - -from __future__ import annotations - -import collections.abc -import functools -import inspect -import operator -from abc import abstractmethod -from collections.abc import Callable -from typing import Any, TypeVar, overload - -from bigframes_vendored.ibis.common.bases import ( - Final, - FrozenSlotted, - Hashable, - Immutable, - Slotted, -) -from bigframes_vendored.ibis.common.collections import FrozenDict -from bigframes_vendored.ibis.common.typing import Coercible, CoercionError -from bigframes_vendored.ibis.util import PseudoHashable - - -class Resolver(Coercible, Hashable): - """Specification about constructing a value given a context. - - The context is a dictionary that contains all the captured values and - information relevant for the builder. - - The builder is used in the right hand side of the replace pattern: - `Replace(pattern, builder)`. Semantically when a match occurs for the - replace pattern, the builder is called with the context and the result - of the builder is used as the replacement value. - """ - - @abstractmethod - def resolve(self, context: dict): - """Construct a new object from the context. - - Parameters - ---------- - context - A dictionary containing all the captured values and information - relevant for the deferred. - - Returns - ------- - The constructed object. - - """ - - @abstractmethod - def __eq__(self, other: Resolver) -> bool: ... - - @classmethod - def __coerce__(cls, value): - if isinstance(value, cls): - return value - elif isinstance(value, Deferred): - return value._resolver - else: - raise CoercionError( - f"Cannot coerce {type(value).__name__!r} to {cls.__name__!r}" - ) - - -class Deferred(Slotted, Immutable, Final): - """The user facing wrapper object providing syntactic sugar for deferreds. - - Provides a natural-like syntax for constructing deferred expressions by - overloading all of the available dunder methods including the equality - operator. - - Its sole purpose is to provide a nicer syntax for constructing deferred - expressions, thus it gets unwrapped to the underlying deferred expression - when used by the rest of the library. - - Parameters - ---------- - deferred - The deferred object to provide syntax sugar for. - repr - An optional fixed string to use when repr-ing the deferred expression, - instead of the default. This is useful for complex deferred expressions - where the arguments don't necessarily make sense to be user facing in - the repr. - - """ - - __slots__ = ("_resolver", "_repr") - - def __init__(self, obj, repr=None): - super().__init__(_resolver=resolver(obj), _repr=repr) - - # TODO(kszucs): consider to make this method protected - def resolve(self, _=None, **kwargs): - context = {"_": _, **kwargs} - return self._resolver.resolve(context) - - def __repr__(self): - return repr(self._resolver) if self._repr is None else self._repr - - def __getattr__(self, name): - return Deferred(Attr(self, name)) - - def __iter__(self): - raise TypeError(f"{self.__class__.__name__!r} object is not iterable") - - def __bool__(self): - raise TypeError( - f"The truth value of {self.__class__.__name__} objects is not defined" - ) - - def __getitem__(self, name): - return Deferred(Item(self, name)) - - def __call__(self, *args, **kwargs): - return Deferred(Call(self, *args, **kwargs)) - - def __invert__(self) -> Deferred: - return Deferred(UnaryOperator(operator.invert, self)) - - def __neg__(self) -> Deferred: - return Deferred(UnaryOperator(operator.neg, self)) - - def __add__(self, other: Any) -> Deferred: - return Deferred(BinaryOperator(operator.add, self, other)) - - def __radd__(self, other: Any) -> Deferred: - return Deferred(BinaryOperator(operator.add, other, self)) - - def __sub__(self, other: Any) -> Deferred: - return Deferred(BinaryOperator(operator.sub, self, other)) - - def __rsub__(self, other: Any) -> Deferred: - return Deferred(BinaryOperator(operator.sub, other, self)) - - def __mul__(self, other: Any) -> Deferred: - return Deferred(BinaryOperator(operator.mul, self, other)) - - def __rmul__(self, other: Any) -> Deferred: - return Deferred(BinaryOperator(operator.mul, other, self)) - - def __truediv__(self, other: Any) -> Deferred: - return Deferred(BinaryOperator(operator.truediv, self, other)) - - def __rtruediv__(self, other: Any) -> Deferred: - return Deferred(BinaryOperator(operator.truediv, other, self)) - - def __floordiv__(self, other: Any) -> Deferred: - return Deferred(BinaryOperator(operator.floordiv, self, other)) - - def __rfloordiv__(self, other: Any) -> Deferred: - return Deferred(BinaryOperator(operator.floordiv, other, self)) - - def __pow__(self, other: Any) -> Deferred: - return Deferred(BinaryOperator(operator.pow, self, other)) - - def __rpow__(self, other: Any) -> Deferred: - return Deferred(BinaryOperator(operator.pow, other, self)) - - def __mod__(self, other: Any) -> Deferred: - return Deferred(BinaryOperator(operator.mod, self, other)) - - def __rmod__(self, other: Any) -> Deferred: - return Deferred(BinaryOperator(operator.mod, other, self)) - - def __rshift__(self, other: Any) -> Deferred: - return Deferred(BinaryOperator(operator.rshift, self, other)) - - def __rrshift__(self, other: Any) -> Deferred: - return Deferred(BinaryOperator(operator.rshift, other, self)) - - def __lshift__(self, other: Any) -> Deferred: - return Deferred(BinaryOperator(operator.lshift, self, other)) - - def __rlshift__(self, other: Any) -> Deferred: - return Deferred(BinaryOperator(operator.lshift, other, self)) - - def __eq__(self, other: Any) -> Deferred: # type: ignore - return Deferred(BinaryOperator(operator.eq, self, other)) - - def __ne__(self, other: Any) -> Deferred: # type: ignore - return Deferred(BinaryOperator(operator.ne, self, other)) - - def __lt__(self, other: Any) -> Deferred: - return Deferred(BinaryOperator(operator.lt, self, other)) - - def __le__(self, other: Any) -> Deferred: - return Deferred(BinaryOperator(operator.le, self, other)) - - def __gt__(self, other: Any) -> Deferred: - return Deferred(BinaryOperator(operator.gt, self, other)) - - def __ge__(self, other: Any) -> Deferred: - return Deferred(BinaryOperator(operator.ge, self, other)) - - def __and__(self, other: Any) -> Deferred: - return Deferred(BinaryOperator(operator.and_, self, other)) - - def __rand__(self, other: Any) -> Deferred: - return Deferred(BinaryOperator(operator.and_, other, self)) - - def __or__(self, other: Any) -> Deferred: - return Deferred(BinaryOperator(operator.or_, self, other)) - - def __ror__(self, other: Any) -> Deferred: - return Deferred(BinaryOperator(operator.or_, other, self)) - - def __xor__(self, other: Any) -> Deferred: - return Deferred(BinaryOperator(operator.xor, self, other)) - - def __rxor__(self, other: Any) -> Deferred: - return Deferred(BinaryOperator(operator.xor, other, self)) - - -class Variable(FrozenSlotted, Resolver): - """Retrieve a value from the context. - - Parameters - ---------- - name - The key to retrieve from the state. - - """ - - __slots__ = ("name",) - name: Any - - def __init__(self, name): - super().__init__(name=name) - - def __repr__(self): - return str(self.name) - - def resolve(self, context): - return context[self.name] - - -class Just(FrozenSlotted, Resolver): - """Construct exactly the given value. - - Parameters - ---------- - value - The value to return when the deferred is called. - - """ - - __slots__ = ("value",) - value: Any - - @classmethod - def __create__(cls, value): - if isinstance(value, cls): - return value - elif isinstance(value, (Deferred, Resolver)): - raise TypeError(f"{value} cannot be used as a Just value") - elif isinstance(value, collections.abc.Hashable): - return super().__create__(value) - else: - return JustUnhashable(value) - - def __init__(self, value): - super().__init__(value=value) - - def __repr__(self): - obj = self.value - if hasattr(obj, "__deferred_repr__"): - return obj.__deferred_repr__() - elif callable(obj): - return getattr(obj, "__name__", repr(obj)) - else: - return repr(obj) - - def resolve(self, context): - return self.value - - -class JustUnhashable(FrozenSlotted, Resolver): - """Construct exactly the given unhashable value. - - Parameters - ---------- - value - The value to return when the deferred is called. - - """ - - __slots__ = ("value",) - - def __init__(self, value): - hashable_value = PseudoHashable(value) - super().__init__(value=hashable_value) - - def __repr__(self): - obj = self.value.obj - if hasattr(obj, "__deferred_repr__"): - return obj.__deferred_repr__() - elif callable(obj): - return getattr(obj, "__name__", repr(obj)) - else: - return repr(obj) - - def resolve(self, context): - return self.value.obj - - -class Factory(FrozenSlotted, Resolver): - """Construct a value by calling a function. - - The function is called with two positional arguments: - 1. the value being matched - 2. the context dictionary - - The function must return the constructed value. - - Parameters - ---------- - func - The function to apply. - - """ - - __slots__ = ("func",) - func: Callable - - def __init__(self, func): - assert callable(func) - super().__init__(func=func) - - def resolve(self, context): - return self.func(**context) - - -class Attr(FrozenSlotted, Resolver): - __slots__ = ("obj", "name") - obj: Resolver - name: str - - def __init__(self, obj, name): - super().__init__(obj=resolver(obj), name=resolver(name)) - - def __repr__(self): - if isinstance(self.name, Just): - return f"{self.obj!r}.{self.name.value}" - else: - return f"Attr({self.obj!r}, {self.name!r})" - - def resolve(self, context): - obj = self.obj.resolve(context) - name = self.name.resolve(context) - return getattr(obj, name) - - -class Item(FrozenSlotted, Resolver): - __slots__ = ("obj", "name") - obj: Resolver - name: str - - def __init__(self, obj, name): - super().__init__(obj=resolver(obj), name=resolver(name)) - - def __repr__(self): - if isinstance(self.name, Just): - return f"{self.obj!r}[{self.name.value!r}]" - else: - return f"Item({self.obj!r}, {self.name!r})" - - def resolve(self, context): - obj = self.obj.resolve(context) - name = self.name.resolve(context) - return obj[name] - - -class Call(FrozenSlotted, Resolver): - """Pattern that calls a function with the given arguments. - - Both positional and keyword arguments are coerced into patterns. - - Parameters - ---------- - func - The function to call. - args - The positional argument patterns. - kwargs - The keyword argument patterns. - - """ - - __slots__ = ("func", "args", "kwargs") - func: Resolver - args: tuple[Resolver, ...] - kwargs: dict[str, Resolver] - - def __init__(self, func, *args, **kwargs): - if isinstance(func, Deferred): - func = func._resolver - elif isinstance(func, Resolver): - pass - elif callable(func): - func = Just(func) - else: - raise TypeError(f"Invalid callable {func!r}") - args = tuple(map(resolver, args)) - kwargs = FrozenDict({k: resolver(v) for k, v in kwargs.items()}) - super().__init__(func=func, args=args, kwargs=kwargs) - - def resolve(self, context): - func = self.func.resolve(context) - args = tuple(arg.resolve(context) for arg in self.args) - kwargs = {k: v.resolve(context) for k, v in self.kwargs.items()} - return func(*args, **kwargs) - - def __repr__(self): - func = repr(self.func) - args = ", ".join(map(repr, self.args)) - kwargs = ", ".join(f"{k}={v!r}" for k, v in self.kwargs.items()) - if args and kwargs: - return f"{func}({args}, {kwargs})" - elif args: - return f"{func}({args})" - elif kwargs: - return f"{func}({kwargs})" - else: - return f"{func}()" - - -_operator_symbols = { - operator.add: "+", - operator.sub: "-", - operator.mul: "*", - operator.truediv: "/", - operator.floordiv: "//", - operator.pow: "**", - operator.mod: "%", - operator.eq: "==", - operator.ne: "!=", - operator.lt: "<", - operator.le: "<=", - operator.gt: ">", - operator.ge: ">=", - operator.and_: "&", - operator.or_: "|", - operator.xor: "^", - operator.rshift: ">>", - operator.lshift: "<<", - operator.inv: "~", - operator.neg: "-", - operator.invert: "~", -} - - -class UnaryOperator(FrozenSlotted, Resolver): - __slots__ = ("func", "arg") - func: Callable - arg: Resolver - - def __init__(self, func, arg): - assert func in _operator_symbols - super().__init__(func=func, arg=resolver(arg)) - - def __repr__(self): - symbol = _operator_symbols[self.func] - return f"{symbol}{self.arg!r}" - - def resolve(self, context): - arg = self.arg.resolve(context) - return self.func(arg) - - -class BinaryOperator(FrozenSlotted, Resolver): - __slots__ = ("func", "left", "right") - func: Callable - left: Resolver - right: Resolver - - def __init__(self, func, left, right): - assert func in _operator_symbols - super().__init__(func=func, left=resolver(left), right=resolver(right)) - - def __repr__(self): - symbol = _operator_symbols[self.func] - return f"({self.left!r} {symbol} {self.right!r})" - - def resolve(self, context): - left = self.left.resolve(context) - right = self.right.resolve(context) - return self.func(left, right) - - -class Mapping(FrozenSlotted, Resolver): - __slots__ = ("typ", "values") - - def __init__(self, values): - typ = type(values) - values = FrozenDict({k: resolver(v) for k, v in values.items()}) - super().__init__(typ=typ, values=values) - - def __repr__(self): - items = ", ".join(f"{k!r}: {v!r}" for k, v in self.values.items()) - if self.typ is dict: - return f"{{{items}}}" - else: - return f"{self.typ.__name__}({{{items}}})" - - def resolve(self, context): - items = {k: v.resolve(context) for k, v in self.values.items()} - return self.typ(items) - - -class Sequence(FrozenSlotted, Resolver): - __slots__ = ("typ", "values") - typ: type - - def __init__(self, values): - typ = type(values) - values = tuple(map(resolver, values)) - super().__init__(typ=typ, values=values) - - def __repr__(self): - elems = ", ".join(map(repr, self.values)) - if self.typ is tuple: - return f"({elems})" - elif self.typ is list: - return f"[{elems}]" - else: - return f"{self.typ.__name__}({elems})" - - def resolve(self, context): - return self.typ(v.resolve(context) for v in self.values) - - -def resolver(obj): - if isinstance(obj, Deferred): - return obj._resolver - elif isinstance(obj, Resolver): - return obj - elif isinstance(obj, collections.abc.Mapping): - # allow nesting deferred patterns in dicts - return Mapping(obj) - elif isinstance(obj, collections.abc.Sequence): - # allow nesting deferred patterns in tuples/lists - if isinstance(obj, (str, bytes)): - return Just(obj) - else: - return Sequence(obj) - else: - # the object is used as a constant value - return Just(obj) - - -def deferred(obj): - return Deferred(resolver(obj)) - - -def var(name): - return Deferred(Variable(name)) - - -def const(value): - return Deferred(Just(value)) - - -def _contains_deferred(obj: Any) -> bool: - if isinstance(obj, (Resolver, Deferred)): - return True - elif (typ := type(obj)) in (tuple, list, set): - return any(_contains_deferred(o) for o in obj) - elif typ is dict: - return any(_contains_deferred(o) for o in obj.values()) - return False - - -F = TypeVar("F", bound=Callable) - - -@overload -def deferrable(*, repr: str | None = None) -> Callable[[F], F]: ... - - -@overload -def deferrable(func: F) -> F: ... - - -def deferrable(func=None, *, repr=None): - """Wrap a top-level expr function to support deferred arguments. - - When a deferrable function is called, the args & kwargs are traversed to - look for `Deferred` values (through builtin collections like - `list`/`tuple`/`set`/`dict`). If any `Deferred` arguments are found, then - the result is also `Deferred`. Otherwise the function is called directly. - - Parameters - ---------- - func - A callable to make deferrable - repr - An optional fixed string to use when repr-ing the deferred expression, - instead of the usual. This is useful for complex deferred expressions - where the arguments don't necessarily make sense to be user facing - in the repr. - - """ - - def wrapper(func): - # Parse the signature of func so we can validate deferred calls eagerly, - # erroring for invalid/missing arguments at call time not resolve time. - sig = inspect.signature(func) - - @functools.wraps(func) - def inner(*args, **kwargs): - if _contains_deferred((args, kwargs)): - # Try to bind the arguments now, raising a nice error - # immediately if the function was called incorrectly - sig.bind(*args, **kwargs) - builder = Call(func, *args, **kwargs) - return Deferred(builder, repr=repr) - return func(*args, **kwargs) - - return inner # type: ignore - - return wrapper if func is None else wrapper(func) - - -# reserved variable name for the value being matched -_ = var("_") diff --git a/third_party/bigframes_vendored/ibis/common/dispatch.py b/third_party/bigframes_vendored/ibis/common/dispatch.py deleted file mode 100644 index 9808d1fdb2b..00000000000 --- a/third_party/bigframes_vendored/ibis/common/dispatch.py +++ /dev/null @@ -1,218 +0,0 @@ -# Contains code from https://github.com/ibis-project/ibis/blob/9.2.0/ibis/common/dispatch.py - -from __future__ import annotations - -import abc -import functools -import inspect -import re -import sys -from collections import defaultdict -from typing import Union - -from bigframes_vendored.ibis.common.typing import ( - evaluate_annotations, - get_args, - get_origin, -) -from bigframes_vendored.ibis.util import import_object, unalias_package - -if sys.version_info >= (3, 10): - from types import UnionType -else: - from bigframes_vendored.ibis.common.typing import UnionType - - -def normalize(r: str | re.Pattern): - """Normalize a expression by wrapping it with `'^'` and `'$'`. - - Parameters - ---------- - r - The pattern to normalize. - - Returns - ------- - Pattern - The compiled regex. - - """ - r = getattr(r, "pattern", r) - return re.compile("^" + r.lstrip("^").rstrip("$") + "$") - - -class SingleDispatch: - def __init__(self, func, typ=None): - self.lookup = {} - self.abc_lookup = {} - self.lazy_lookup = defaultdict(dict) - self.func = func - self.add(func, typ) - - def add(self, func, typ=None): - if typ is None: - annots = getattr(func, "__annotations__", {}) - typehints = evaluate_annotations(annots, func.__module__, best_effort=True) - if typehints: - typ, *_ = typehints.values() - if get_origin(typ) in (Union, UnionType): - for t in get_args(typ): - self.add(func, t) - else: - self.add(func, typ) - else: - self.add(func, object) - elif isinstance(typ, tuple): - for t in typ: - self.add(func, t) - elif isinstance(typ, abc.ABCMeta): - if typ in self.abc_lookup: - raise TypeError(f"{typ} is already registered") - self.abc_lookup[typ] = func - elif isinstance(typ, str): - package, rest = typ.split(".", 1) - package = unalias_package(package) - typ = f"{package}.{rest}" - if typ in self.lazy_lookup[package]: - raise TypeError(f"{typ} is already registered") - self.lazy_lookup[package][typ] = func - else: - if typ in self.lookup: - raise TypeError(f"{typ} is already registered") - self.lookup[typ] = func - return func - - def register(self, typ, func=None): - """Register a new implementation for arguments of type `cls`.""" - - def inner(func): - self.add(func, typ) - return func - - return inner if func is None else inner(func) - - def dispatch(self, typ): - """Return the implementation for the given `cls`.""" - for klass in typ.__mro__: - # 1. Check for a concrete implementation - try: - impl = self.lookup[klass] - except KeyError: - pass - else: - if typ is not klass: - # Cache implementation - self.lookup[typ] = impl - return impl - # 2. Check lazy implementations - package = klass.__module__.split(".", 1)[0] - if lazy := self.lazy_lookup.get(package): - # Import all lazy implementations first before registering - # (which should never fail), to ensure an error anywhere - # doesn't result in a half-registered state. - new = {import_object(name): func for name, func in lazy.items()} - self.lookup.update(new) - # drop lazy implementations, idempotent for thread safety - self.lazy_lookup.pop(package, None) - return self.dispatch(typ) - # 3. Check for abcs - for abc_class, impl in self.abc_lookup.items(): - if issubclass(typ, abc_class): - self.lookup[typ] = impl - return impl - raise TypeError(f"Could not find implementation for {typ}") - - def __call__(self, arg, *args, **kwargs): - impl = self.dispatch(type(arg)) - return impl(arg, *args, **kwargs) - - def __get__(self, obj, cls=None): - def _method(*args, **kwargs): - method = self.dispatch(type(args[0])) - method = method.__get__(obj, cls) - return method(*args, **kwargs) - - functools.update_wrapper(_method, self.func) - return _method - - -def lazy_singledispatch(func): - """A `singledispatch` implementation that supports lazily registering implementations.""" - - dispatcher = SingleDispatch(func, object) - - @functools.wraps(func) - def call(arg, *args, **kwargs): - impl = dispatcher.dispatch(type(arg)) - return impl(arg, *args, **kwargs) - - call.dispatch = dispatcher.dispatch - call.register = dispatcher.register - return call - - -class _MultiDict(dict): - """A dictionary that allows multiple values for a single key.""" - - def __setitem__(self, key, value): - if key in self: - self[key].append(value) - else: - super().__setitem__(key, [value]) - - -class DispatchedMeta(type): - """Metaclass that allows multiple implementations of a method to be defined.""" - - def __new__(cls, name, bases, dct): - namespace = {} - for key, value in dct.items(): - if len(value) == 1: - # there is just a single attribute so pick that - namespace[key] = value[0] - elif all(inspect.isfunction(v) for v in value): - # multiple functions are defined with the same name, so create - # a dispatcher function - first, *rest = value - func = SingleDispatch(first) - for impl in rest: - func.add(impl) - namespace[key] = func - elif all(isinstance(v, classmethod) for v in value): - first, *rest = value - func = SingleDispatch(first.__func__) - for impl in rest: - func.add(impl.__func__) - namespace[key] = classmethod(func) - elif all(isinstance(v, staticmethod) for v in value): - first, *rest = value - func = SingleDispatch(first.__func__) - for impl in rest: - func.add(impl.__func__) - namespace[key] = staticmethod(func) - else: - raise TypeError(f"Multiple attributes are defined with name {key}") - - return type.__new__(cls, name, bases, namespace) - - @classmethod - def __prepare__(cls, name, bases): - return _MultiDict() - - -class Dispatched(metaclass=DispatchedMeta): - """Base class supporting multiple implementations of a method. - - Methods with the same name can be defined multiple times. The first method - defined is the default implementation, and subsequent methods are registered - as implementations for specific types of the first argument. - - The constructed methods are equivalent as if they were defined with - `functools.singledispatchmethod` but without the need to use the decorator - syntax. The recommended application of this class is to implement visitor - patterns. - - Besides ordinary methods, classmethods and staticmethods are also supported. - The implementation can be extended to overload multiple arguments by using - `multimethod` instead of `singledispatchmethod` as the dispatcher. - """ diff --git a/third_party/bigframes_vendored/ibis/common/egraph.py b/third_party/bigframes_vendored/ibis/common/egraph.py deleted file mode 100644 index 437e85eda63..00000000000 --- a/third_party/bigframes_vendored/ibis/common/egraph.py +++ /dev/null @@ -1,830 +0,0 @@ -# Contains code from https://github.com/ibis-project/ibis/blob/9.2.0/ibis/common/egraph.py - -from __future__ import annotations - -import collections -import itertools -import math -from collections.abc import Callable, Hashable, Iterable, Iterator, Mapping -from typing import Any, TypeVar - -from bigframes_vendored.ibis.common.bases import FrozenSlotted as Slotted -from bigframes_vendored.ibis.common.graph import Node -from bigframes_vendored.ibis.util import promote_list - -K = TypeVar("K", bound=Hashable) - - -class DisjointSet(Mapping[K, set[K]]): - """Disjoint set data structure. - - Also known as union-find data structure. It is a data structure that keeps - track of a set of elements partitioned into a number of disjoint (non-overlapping) - subsets. It provides near-constant-time operations to add new sets, to merge - existing sets, and to determine whether elements are in the same set. - - Parameters - ---------- - data : - Initial data to add to the disjoint set. - - Examples - -------- - >>> ds = DisjointSet() - >>> ds.add(1) - 1 - >>> ds.add(2) - 2 - >>> ds.add(3) - 3 - >>> ds.union(1, 2) - True - >>> ds.union(2, 3) - True - >>> ds.find(1) - 1 - >>> ds.find(2) - 1 - >>> ds.find(3) - 1 - >>> ds.union(1, 3) - False - - """ - - __slots__ = ("_parents", "_classes") - _parents: dict - _classes: dict - - def __init__(self, data: Iterable[K] | None = None): - self._parents = {} - self._classes = {} - if data is not None: - for id in data: - self.add(id) - - def __contains__(self, id) -> bool: - """Check if the given id is in the disjoint set. - - Parameters - ---------- - id : - The id to check. - - Returns - ------- - ined: - True if the id is in the disjoint set, False otherwise. - - """ - return id in self._parents - - def __getitem__(self, id) -> set[K]: - """Get the set of ids that are in the same class as the given id. - - Parameters - ---------- - id : - The id to get the class for. - - Returns - ------- - class: - The set of ids that are in the same class as the given id, including - the given id. - - """ - id = self._parents[id] - return self._classes[id] - - def __iter__(self) -> Iterator[K]: - """Iterate over the ids in the disjoint set.""" - return iter(self._parents) - - def __len__(self) -> int: - """Get the number of ids in the disjoint set.""" - return len(self._parents) - - def __eq__(self, other: object) -> bool: - """Check if the disjoint set is equal to another disjoint set. - - Parameters - ---------- - other : - The other disjoint set to compare to. - - Returns - ------- - equal: - True if the disjoint sets are equal, False otherwise. - - """ - if not isinstance(other, DisjointSet): - return NotImplemented - return self._parents == other._parents - - def copy(self) -> DisjointSet: - """Make a copy of the disjoint set. - - Returns - ------- - copy: - A copy of the disjoint set. - - """ - ds = DisjointSet() - ds._parents = self._parents.copy() - ds._classes = self._classes.copy() - return ds - - def add(self, id: K) -> K: - """Add a new id to the disjoint set. - - If the id is not in the disjoint set, it will be added to the disjoint set - along with a new class containing only the given id. - - Parameters - ---------- - id : - The id to add to the disjoint set. - - Returns - ------- - id: - The id that was added to the disjoint set. - - """ - if id in self._parents: - return self._parents[id] - self._parents[id] = id - self._classes[id] = {id} - return id - - def find(self, id: K) -> K: - """Find the root of the class that the given id is in. - - Also called as the canonicalized id or the representative id. - - Parameters - ---------- - id : - The id to find the canonicalized id for. - - Returns - ------- - id: - The canonicalized id for the given id. - - """ - return self._parents[id] - - def union(self, id1, id2) -> bool: - """Merge the classes that the given ids are in. - - If the ids are already in the same class, this will return False. Otherwise - it will merge the classes and return True. - - Parameters - ---------- - id1 : - The first id to merge the classes for. - id2 : - The second id to merge the classes for. - - Returns - ------- - merged: - True if the classes were merged, False otherwise. - - """ - # Find the root of each class - id1 = self._parents[id1] - id2 = self._parents[id2] - if id1 == id2: - return False - - # Merge the smaller eclass into the larger one, aka. union-find by size - class1 = self._classes[id1] - class2 = self._classes[id2] - if len(class1) >= len(class2): - id1, id2 = id2, id1 - class1, class2 = class2, class1 - - # Update the parent pointers, this is called path compression but done - # during the union operation to keep the find operation minimal - for id in class1: - self._parents[id] = id2 - - # Do the actual merging and clear the other eclass - class2 |= class1 - class1.clear() - - return True - - def connected(self, id1, id2): - """Check if the given ids are in the same class. - - True if both ids have the same canonicalized id, False otherwise. - - Parameters - ---------- - id1 : - The first id to check. - id2 : - The second id to check. - - Returns - ------- - connected: - True if the ids are connected, False otherwise. - - """ - return self._parents[id1] == self._parents[id2] - - def verify(self): - """Verify that the disjoint set is not corrupted. - - Check that each id's canonicalized id's class. In general corruption - should not happen if the public API is used, but this is a sanity check - to make sure that the internal data structures are not corrupted. - - Returns - ------- - verified: - True if the disjoint set is not corrupted, False otherwise. - - """ - for id in self._parents: - if id not in self._classes[self._parents[id]]: - raise RuntimeError( - f"DisjointSet is corrupted: {id} is not in its class" - ) - - -class Variable(Slotted): - """A named capture in a pattern. - - Parameters - ---------- - name : str - The name of the variable. - - """ - - __slots__ = ("name",) - name: str - - def __init__(self, name: str): - if name is None: - raise ValueError("Variable name cannot be None") - super().__init__(name=name) - - def __repr__(self): - return f"${self.name}" - - def substitute(self, egraph, enode, subst): - """Substitute the variable with the corresponding value in the substitution. - - Parameters - ---------- - egraph : EGraph - The egraph instance. - enode : ENode - The matched enode. - subst : dict - The substitution dictionary. - - Returns - ------- - value : Any - The substituted value. - - """ - return subst[self.name] - - -# Pattern corresponds to a selection which is flattened to a join of selections -class Pattern(Slotted): - """A non-ground term, tree of enodes possibly containing variables. - - This class is used to represent a pattern in a query. The pattern is almost - identical to an ENode, except that it can contain variables. - - Parameters - ---------- - head : type - The head or python type of the ENode to match against. - args : tuple - The arguments of the pattern. The arguments can be enodes, patterns, - variables or leaf values. - name : str, optional - The name of the pattern which is used to refer to it in a rewrite rule. - - """ - - __slots__ = ("head", "args", "name") - head: type - args: tuple - name: str | None - - # TODO(kszucs): consider to raise if the pattern matches none - def __init__(self, head, args, name=None, conditions=None): - # TODO(kszucs): ensure that args are either patterns, variables or leaf values - assert all(not isinstance(arg, (ENode, Node)) for arg in args) - super().__init__(head=head, args=tuple(args), name=name) - - def matches_none(self): - """Evaluate whether the pattern is guaranteed to match nothing. - - This can be evaluated before the matching loop starts, so eventually can - be eliminated from the flattened query. - """ - return len(self.head.__argnames__) != len(self.args) - - def matches_all(self): - """Evaluate whether the pattern is guaranteed to match everything. - - This can be evaluated before the matching loop starts, so eventually can - be eliminated from the flattened query. - """ - return not self.matches_none() and all( - isinstance(arg, Variable) for arg in self.args - ) - - def __repr__(self): - argstring = ", ".join(map(repr, self.args)) - return f"P{self.head.__name__}({argstring})" - - def __rshift__(self, rhs): - """Syntax sugar to create a rewrite rule.""" - return Rewrite(self, rhs) - - def __rmatmul__(self, name): - """Syntax sugar to create a named pattern.""" - return self.__class__(self.head, self.args, name) - - def flatten(self, var=None, counter=None): - """Recursively flatten the pattern to a join of selections. - - `Pattern(Add, (Pattern(Mul, ($x, 1)), $y))` is turned into a join of - selections by introducing auxiliary variables where each selection gets - executed as a dictionary lookup. - - In SQL terms this is equivalent to the following query: - SELECT m.0 AS $x, a.1 AS $y FROM Add a JOIN Mul m ON a.0 = m.id WHERE m.1 = 1 - - Parameters - ---------- - var : Variable - The variable to assign to the flattened pattern. - counter : Iterator[int] - The counter to generate unique variable names for auxiliary variables - connecting the selections. - - Yields - ------ - (var, pattern) : tuple[Variable, Pattern] - The variable and the flattened pattern where the flattened pattern - cannot contain any patterns just variables. - - """ - # TODO(kszucs): convert a pattern to a query object instead by flattening it - counter = counter or itertools.count() - - if var is None: - if self.name is None: - var = Variable(next(counter)) - else: - var = Variable(self.name) - - args = [] - for arg in self.args: - if isinstance(arg, Pattern): - if arg.name is None: - aux = Variable(next(counter)) - else: - aux = Variable(arg.name) - yield from arg.flatten(aux, counter) - args.append(aux) - else: - args.append(arg) - - yield (var, Pattern(self.head, args)) - - def substitute(self, egraph, enode, subst): - """Substitute the variables in the pattern with the corresponding values. - - Parameters - ---------- - egraph : EGraph - The egraph instance. - enode : ENode - The matched enode. - subst : dict - The substitution dictionary. - - Returns - ------- - enode : ENode - The substituted pattern which is a ground term aka. an ENode. - - """ - args = [] - for arg in self.args: - if isinstance(arg, (Variable, Pattern)): - arg = arg.substitute(egraph, enode, subst) - args.append(arg) - return ENode(self.head, tuple(args)) - - -class DynamicApplier(Slotted): - """A dynamic applier which calls a function to compute the result.""" - - __slots__ = ("func",) - func: Callable - - def __init__(self, func): - super().__init__(func=func) - - def substitute(self, egraph, enode, subst): - kwargs = {k: v for k, v in subst.items() if isinstance(k, str)} - result = self.func(egraph, enode, **kwargs) - if not isinstance(result, ENode): - raise TypeError(f"applier must return an ENode, got {type(result)}") - return result - - -class Rewrite(Slotted): - """A rewrite rule which matches a pattern and applies a pattern or a function.""" - - __slots__ = ("matcher", "applier") - matcher: Pattern - applier: Callable | Pattern | Variable - - def __init__(self, matcher, applier): - if callable(applier): - applier = DynamicApplier(applier) - elif not isinstance(applier, (Pattern, Variable)): - raise TypeError( - "applier must be a Pattern or a Variable returning an ENode" - ) - super().__init__(matcher=matcher, applier=applier) - - def __repr__(self): - return f"{self.lhs} >> {self.rhs}" - - -class ENode(Slotted, Node): - """A ground term which is a node in the EGraph, called ENode. - - Parameters - ---------- - head : type - The type of the Node the ENode represents. - args : tuple - The arguments of the ENode which are either ENodes or leaf values. - - """ - - __slots__ = ("head", "args") - head: type - args: tuple - - def __init__(self, head, args): - # TODO(kszucs): ensure that it is a ground term, this check should be removed - assert all(not isinstance(arg, (Pattern, Variable)) for arg in args) - super().__init__(head=head, args=tuple(args)) - - @property - def __argnames__(self): - """Implementation for the `ibis.common.graph.Node` protocol.""" - return self.head.__argnames__ - - @property - def __args__(self): - """Implementation for the `ibis.common.graph.Node` protocol.""" - return self.args - - def __repr__(self): - argstring = ", ".join(map(repr, self.args)) - return f"E{self.head.__name__}({argstring})" - - def __lt__(self, other): - return False - - @classmethod - def from_node(cls, node: Any): - """Convert an `ibis.common.graph.Node` to an `ENode`.""" - - def mapper(node, _, **kwargs): - return cls(node.__class__, kwargs.values()) - - return node.map(mapper)[node] - - def to_node(self): - """Convert the ENode back to an `ibis.common.graph.Node`.""" - - def mapper(node, _, **kwargs): - return node.head(**kwargs) - - return self.map(mapper)[self] - - -# TODO: move every E* into the Egraph so its API only uses Nodes -# TODO: track whether the egraph is saturated or not -# TODO: support parent classes in etables (Join <= InnerJoin) - - -class EGraph: - __slots__ = ("_nodes", "_etables", "_eclasses") - _nodes: dict - _etables: collections.defaultdict - _eclasses: DisjointSet - - def __init__(self): - # store the nodes before converting them to enodes, so we can spare the initial - # node traversal and omit the creation of enodes - self._nodes = {} - # map enode heads to their eclass ids and their arguments, this is required for - # the relational e-matching (Node => dict[type, tuple[Union[ENode, Any], ...]]) - self._etables = collections.defaultdict(dict) - # map enodes to their eclass, this is the heart of the egraph - self._eclasses = DisjointSet() - - def __repr__(self): - return f"EGraph({self._eclasses})" - - def _as_enode(self, node: Node) -> ENode: - """Convert a node to an enode.""" - # order is important here since ENode is a subclass of Node - if isinstance(node, ENode): - return node - elif isinstance(node, Node): - return self._nodes.get(node) or ENode.from_node(node) - else: - raise TypeError(node) - - def add(self, node: Node) -> ENode: - """Add a node to the egraph. - - The node is converted to an enode and added to the egraph. If the enode is - already present in the egraph, then the canonical enode is returned. - - Parameters - ---------- - node : - The node to add to the egraph. - - Returns - ------- - enode : - The canonical enode. - - """ - enode = self._as_enode(node) - if enode in self._eclasses: - return self._eclasses.find(enode) - - args = [] - for arg in enode.args: - if isinstance(arg, ENode): - args.append(self.add(arg)) - else: - args.append(arg) - - enode = ENode(enode.head, args) - self._eclasses.add(enode) - self._etables[enode.head][enode] = tuple(args) - - return enode - - def union(self, node1: Node, node2: Node) -> ENode: - """Union two nodes in the egraph. - - The nodes are converted to enodes which must be present in the egraph. - The eclasses of the nodes are merged and the canonical enode is returned. - - Parameters - ---------- - node1 : - The first node to union. - node2 : - The second node to union. - - Returns - ------- - enode : - The canonical enode. - - """ - enode1 = self._as_enode(node1) - enode2 = self._as_enode(node2) - return self._eclasses.union(enode1, enode2) - - def _match_args(self, args, patargs): - """Match the arguments of an enode against a pattern's arguments. - - An enode matches a pattern if each of the arguments are: - - both leaf values and equal - - both enodes and in the same eclass - - an enode and a variable, in which case the variable gets bound to the enode - - Parameters - ---------- - args : tuple - The arguments of the enode. Since an enode is a ground term, the arguments - are either enodes or leaf values. - patargs : tuple - The arguments of the pattern. Since a pattern is a flat term (flattened - using auxiliary variables), the arguments are either variables or leaf - values. - - Returns - ------- - dict[str, Any] : - The mapping of variable names to enodes or leaf values. - - """ - subst = {} - for arg, patarg in zip(args, patargs): - if isinstance(patarg, Variable): - if isinstance(arg, ENode): - subst[patarg.name] = self._eclasses.find(arg) - else: - subst[patarg.name] = arg - # TODO(kszucs): this is not needed since patarg is either a variable or a - # leaf value due to the pattern flattening, though we may choose to - # support this in the future - # elif isinstance(arg, ENode): - # if self._eclasses.find(arg) != self._eclasses.find(arg): - # return None - elif patarg != arg: - return None - return subst - - def match(self, pattern: Pattern) -> dict[ENode, dict[str, Any]]: - """Match a pattern in the egraph. - - The pattern is converted to a conjunctive query (list of flat patterns) and - matched against the relations represented by the egraph. This is called the - relational e-matching. - - Parameters - ---------- - pattern : - The pattern to match in the egraph. - - Returns - ------- - matches : - A dictionary mapping the matched enodes to their substitutions. - - """ - # patterns could be reordered to match on the most selective one first - patterns = dict(reversed(list(pattern.flatten()))) - if any(pat.matches_none() for pat in patterns.values()): - return {} - - # extract the first pattern - (auxvar, pattern), *rest = patterns.items() - matches = {} - - # match the first pattern and create the initial substitutions - rel = self._etables[pattern.head] - for enode, args in rel.items(): - if (subst := self._match_args(args, pattern.args)) is not None: - subst[auxvar.name] = enode - matches[enode] = subst - - # match the rest of the patterns and extend the substitutions - for auxvar, pattern in rest: - rel = self._etables[pattern.head] - tmp = {} - for enode, subst in matches.items(): - if args := rel.get(subst[auxvar.name]): - if (newsubst := self._match_args(args, pattern.args)) is not None: - tmp[enode] = {**subst, **newsubst} - matches = tmp - - return matches - - def apply(self, rewrites: list[Rewrite]) -> int: - """Apply the given rewrites to the egraph. - - Iteratively match the patterns and apply the rewrites to the graph. The returned - number of changes is the number of eclasses that were merged. This is the - number of changes made to the egraph. The egraph is saturated if the number of - changes is zero. - - Parameters - ---------- - rewrites : - A list of rewrites to apply. - - Returns - ------- - n_changes - The number of changes made to the egraph. - - """ - n_changes = 0 - for rewrite in promote_list(rewrites): - for match, subst in self.match(rewrite.matcher).items(): - enode = rewrite.applier.substitute(self, match, subst) - enode = self.add(enode) - n_changes += self._eclasses.union(match, enode) - return n_changes - - def run(self, rewrites: list[Rewrite], n: int = 10) -> bool: - """Run the match-apply cycles for the given number of iterations. - - Parameters - ---------- - rewrites : - A list of rewrites to apply. - n : - The number of iterations to run. - - Returns - ------- - saturated : - True if the egraph is saturated, False otherwise. - - """ - return any(not self.apply(rewrites) for _i in range(n)) - - # TODO(kszucs): investigate whether the costs and best enodes could be maintained - # during the union operations after each match-apply cycle - def extract(self, node: Node) -> Node: - """Extract a node from the egraph. - - The node is converted to an enode which recursively gets converted to an - enode having the lowest cost according to equivalence classes. Currently - the cost function is hardcoded as the depth of the enode. - - Parameters - ---------- - node : - The node to extract from the egraph. - - Returns - ------- - node : - The extracted node. - - """ - enode = self._as_enode(node) - enode = self._eclasses.find(enode) - costs = {en: (math.inf, None) for en in self._eclasses.keys()} - - def enode_cost(enode): - cost = 1 - for arg in enode.args: - if isinstance(arg, ENode): - cost += costs[arg][0] - else: - cost += 1 - return cost - - changed = True - while changed: - changed = False - for en, enodes in self._eclasses.items(): - new_cost = min((enode_cost(en), en) for en in enodes) - if costs[en][0] != new_cost[0]: - changed = True - costs[en] = new_cost - - def extract(en): - if not isinstance(en, ENode): - return en - best = costs[en][1] - args = tuple(extract(a) for a in best.args) - return best.head(*args) - - return extract(enode) - - def equivalent(self, node1: Node, node2: Node) -> bool: - """Check if two nodes are equivalent. - - The nodes are converted to enodes and checked for equivalence: they are - equivalent if they are in the same equivalence class. - - Parameters - ---------- - node1 : - The first node. - node2 : - The second node. - - Returns - ------- - equivalent : - True if the nodes are equivalent, False otherwise. - - """ - enode1 = self._as_enode(node1) - enode2 = self._as_enode(node2) - enode1 = self._eclasses.find(enode1) - enode2 = self._eclasses.find(enode2) - return enode1 == enode2 diff --git a/third_party/bigframes_vendored/ibis/common/exceptions.py b/third_party/bigframes_vendored/ibis/common/exceptions.py deleted file mode 100644 index 4c6392cfc6a..00000000000 --- a/third_party/bigframes_vendored/ibis/common/exceptions.py +++ /dev/null @@ -1,173 +0,0 @@ -# Contains code from https://github.com/ibis-project/ibis/blob/9.2.0/ibis/common/exceptions.py - -# Copyright 2014 Cloudera Inc. -# -# 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. -"""Module for exceptions.""" - -from __future__ import annotations - -from typing import TYPE_CHECKING, Any - -if TYPE_CHECKING: - from collections.abc import Callable - - -class IbisError(Exception): - """IbisError.""" - - -class InternalError(IbisError): - """InternalError.""" - - -class IntegrityError(IbisError): - """IntegrityError.""" - - -class ExpressionError(IbisError): - """ExpressionError.""" - - -class RelationError(ExpressionError): - """RelationError.""" - - -class TranslationError(IbisError): - """TranslationError.""" - - -class OperationNotDefinedError(TranslationError): - """OperationNotDefinedError.""" - - -class UnsupportedOperationError(TranslationError): - """UnsupportedOperationError.""" - - -class UnsupportedBackendType(TranslationError): - """UnsupportedBackendType.""" - - -class UnboundExpressionError(ValueError, IbisError): - """UnboundExpressionError.""" - - -class IbisInputError(ValueError, IbisError): - """IbisInputError.""" - - -class IbisTypeError(TypeError, IbisError): - """IbisTypeError.""" - - -class InputTypeError(IbisTypeError): - """InputTypeError.""" - - -class UnsupportedArgumentError(IbisError): - """UnsupportedArgumentError.""" - - -class BackendConversionError(IbisError): - """A backend cannot convert an input to its native type.""" - - -class BackendConfigurationNotRegistered(IbisError): - """A backend has options but isn't registered in ibis/config.py.""" - - def __init__(self, backend_name: str) -> None: - super().__init__(backend_name) - - def __str__(self) -> str: - (backend_name,) = self.args - return f"Please register options for the `{backend_name}` backend in ibis/config.py" - - -class DuplicateUDFError(IbisError): - def __init__(self, name: str) -> None: - super().__init__(name) - - def __str__(self) -> str: - (name,) = self.args - return f"More than one function with `{name}` found." - - -class MissingUDFError(IbisError): - def __init__(self, name: str) -> None: - super().__init__(name) - - def __str__(self) -> str: - (name,) = self.args - return f"No user-defined function found with name `{name}`" - - -class AmbiguousUDFError(IbisError): - def __init__(self, name: str) -> None: - super().__init__(name) - - def __str__(self) -> str: - (name,) = self.args - return f"Multiple implementations of function `{name}`. Only one implementation is supported." - - -class MissingReturnAnnotationError(IbisError): - def __init__(self, func_name: str): - super().__init__(func_name) - - def __str__(self): - (func_name,) = self.args - return f"function `{func_name}` has no return type annotation" - - -class MissingParameterAnnotationError(IbisError): - def __init__(self, func_name: str, param_name: str): - super().__init__(func_name, param_name) - - def __str__(self): - func_name, param_name = self.args - return f"parameter `{param_name}` in function `{func_name}` is missing a type annotation" - - -class InvalidDecoratorError(IbisError): - def __init__(self, name: str, lines: str): - super().__init__(name, lines) - - def __str__(self) -> str: - name, lines = self.args - return f"Only the `@udf` decorator is allowed in user-defined function: `{name}`; found lines {lines}" - - -class ConflictingValuesError(ValueError): - """A single key has conflicting values in two different mappings.""" - - def __init__(self, conflicts: set[tuple[Any, Any, Any]]): - self.conflicts = conflicts - msgs = [f" `{key}`: {v1} != {v2}" for key, v1, v2 in conflicts] - msg = "Conflicting values for keys:\n" + "\n".join(msgs) - super().__init__(msg) - - -def mark_as_unsupported(f: Callable) -> Callable: - """Decorate an unsupported method.""" - - # function that raises UnsupportedOperationError - def _mark_as_unsupported(self): - raise UnsupportedOperationError( - f"Method `{f.__name__}` is unsupported by class `{self.__class__.__name__}`." - ) - - _mark_as_unsupported.__doc__ = f.__doc__ - _mark_as_unsupported.__name__ = f.__name__ - - return _mark_as_unsupported diff --git a/third_party/bigframes_vendored/ibis/common/graph.py b/third_party/bigframes_vendored/ibis/common/graph.py deleted file mode 100644 index 9bf13e93ecf..00000000000 --- a/third_party/bigframes_vendored/ibis/common/graph.py +++ /dev/null @@ -1,780 +0,0 @@ -# Contains code from https://github.com/ibis-project/ibis/blob/9.2.0/ibis/common/graph.py - -"""Various traversal utilities for the expression graph.""" - -from __future__ import annotations - -import itertools -from abc import abstractmethod -from collections import deque -from collections.abc import Callable, Iterable, Iterator, KeysView, Mapping, Sequence -from typing import TYPE_CHECKING, Any, Optional, TypeVar, Union - -from bigframes_vendored.ibis.common.bases import Hashable -from bigframes_vendored.ibis.common.patterns import NoMatch, Pattern -from bigframes_vendored.ibis.common.typing import _ClassInfo -from bigframes_vendored.ibis.util import experimental, promote_list - -if TYPE_CHECKING: - from typing_extensions import Self - - N = TypeVar("N") - - -Finder = Callable[["Node"], bool] -FinderLike = Union[Finder, Pattern, _ClassInfo] - -Replacer = Callable[["Node", dict["Node", Any]], "Node"] -ReplacerLike = Union[Replacer, Pattern, Mapping] - - -def _flatten_collections(node: Any) -> Iterator[N]: - """Flatten collections of nodes into a single iterator. - - We treat common collection types inherently traversable (e.g. list, tuple, dict) - but as undesired in a graph representation, so we traverse them implicitly. - - Parameters - ---------- - node - Flattaneble object. - - Returns - ------- - A flat generator of the filtered nodes. - - Examples - -------- - >>> from ibis.common.grounds import Concrete - >>> from ibis.common.graph import Node - >>> - >>> class MyNode(Concrete, Node): - ... number: int - ... string: str - ... children: tuple[Node, ...] - >>> a = MyNode(4, "a", ()) - >>> - >>> b = MyNode(3, "b", ()) - >>> c = MyNode(2, "c", (a, b)) - >>> d = MyNode(1, "d", (c,)) - >>> - >>> assert list(_flatten_collections((c,))) == [c] - >>> assert list(_flatten_collections([a, b, (c, a)])) == [a, b, c, a] - >>> assert list(_flatten_collections([{"b": b, "a": a}])) == [b, a] - - """ - for item in node: - if isinstance(item, Node): - yield item - elif isinstance(item, (tuple, list)): - yield from _flatten_collections(item) - elif isinstance(item, dict): - items = itertools.chain.from_iterable(item.items()) - yield from _flatten_collections(items) - - -def _recursive_lookup(obj: Any, dct: dict) -> Any: - """Recursively replace objects in a nested structure with values from a dict. - - Since we treat common collection types inherently traversable, so we need to - traverse them implicitly and replace the values given a result mapping. - - Parameters - ---------- - obj - Object to replace. - dct - Mapping of objects to replace with their values. - - Returns - ------- - Object with replaced values. - - Examples - -------- - >>> from ibis.common.collections import frozendict - >>> from ibis.common.grounds import Concrete - >>> from ibis.common.graph import Node - >>> - >>> class MyNode(Concrete, Node): - ... number: int - ... string: str - ... children: tuple[Node, ...] - >>> a = MyNode(4, "a", ()) - >>> - >>> b = MyNode(3, "b", ()) - >>> c = MyNode(2, "c", (a, b)) - >>> d = MyNode(1, "d", (c,)) - >>> - >>> dct = {a: "A", b: "B"} - >>> _recursive_lookup(a, dct) - 'A' - >>> _recursive_lookup((a, b), dct) - ('A', 'B') - >>> _recursive_lookup({1: a, 2: b}, dct) - {1: 'A', 2: 'B'} - >>> _recursive_lookup((a, frozendict({1: c})), dct) - ('A', {1: MyNode(number=2, ...)}) - - """ - if isinstance(obj, Node): - return dct.get(obj, obj) - elif isinstance(obj, (tuple, list)): - return tuple(_recursive_lookup(o, dct) for o in obj) - elif isinstance(obj, dict): - return { - _recursive_lookup(k, dct): _recursive_lookup(v, dct) for k, v in obj.items() - } - else: - return obj - - -def _coerce_finder(obj: FinderLike, context: Optional[dict] = None) -> Finder: - """Coerce an object into a callable finder function. - - Parameters - ---------- - obj - A callable accepting the node, a pattern or a type to match on. - context - Optional context to use if the finder is a pattern. - - Returns - ------- - A callable finder function which can be used to match nodes. - - """ - if isinstance(obj, Pattern): - ctx = context or {} - - def fn(node): - return obj.match(node, ctx) is not NoMatch - - elif isinstance(obj, (tuple, type)): - - def fn(node): - return isinstance(node, obj) - - elif callable(obj): - fn = obj - else: - raise TypeError("finder must be callable, type, tuple of types or a pattern") - - return fn - - -def _coerce_replacer(obj: ReplacerLike, context: Optional[dict] = None) -> Replacer: - """Coerce an object into a callable replacer function. - - Parameters - ---------- - obj - A Pattern, a Mapping or a callable which can be fed to `node.map()` - to replace nodes. - context - Optional context to use if the replacer is a pattern. - - Returns - ------- - A callable replacer function which can be used to replace nodes. - - """ - if isinstance(obj, Pattern): - ctx = context or {} - - def fn(node, _, **kwargs): - # need to first reconstruct the node from the possible rewritten - # children, so we can match on the new node containing the rewritten - # child arguments, this way we can propagate the rewritten nodes - # upward in the hierarchy, using a specialized __recreate__ method - # improves the performance by 17% compared node.__class__(**kwargs) - recreated = node.__recreate__(kwargs) - if (result := obj.match(recreated, ctx)) is NoMatch: - return recreated - else: - return result - - elif isinstance(obj, Mapping): - - def fn(node, _, **kwargs): - try: - return obj[node] - except KeyError: - return node.__recreate__(kwargs) - - elif callable(obj): - fn = obj - else: - raise TypeError("replacer must be callable, mapping or a pattern") - - return fn - - -class Node(Hashable): - __slots__ = () - - @classmethod - def __recreate__(cls, kwargs: Any) -> Self: - """Reconstruct the node from the given arguments.""" - return cls(**kwargs) - - @property - @abstractmethod - def __args__(self) -> tuple[Any, ...]: - """Sequence of arguments to traverse.""" - - @property - @abstractmethod - def __argnames__(self) -> tuple[str, ...]: - """Sequence of argument names.""" - - @property - def __children__(self) -> tuple[Node, ...]: - """Sequence of children nodes.""" - return tuple(_flatten_collections(self.__args__)) - - def __rich_repr__(self): - """Support for rich reprerentation of the node.""" - return zip(self.__argnames__, self.__args__) - - def map(self, fn: Callable, filter: Optional[Finder] = None) -> dict[Node, Any]: - """Apply a function to all nodes in the graph. - - The traversal is done in a topological order, so the function receives the - results of its immediate children as keyword arguments. - - Parameters - ---------- - fn - Function to apply to each node. It receives the node as the first argument, - the results as the second and the results of the children as keyword - arguments. - filter - Pattern-like object to filter out nodes from the traversal. The traversal - will only visit nodes that match the given pattern and stop otherwise. - - Returns - ------- - A mapping of nodes to their results. - - """ - results: dict[Node, Any] = {} - - graph, _ = Graph.from_bfs(self, filter=filter).toposort() - for node in graph: - # minor optimization to directly recurse into the children - kwargs = { - k: _recursive_lookup(v, results) - for k, v in zip(node.__argnames__, node.__args__) - } - results[node] = fn(node, results, **kwargs) - - return results - - @experimental - def map_clear(self, fn: Callable, filter: Optional[Finder] = None) -> Any: - """Apply a function to all nodes in the graph more memory efficiently. - - Alternative implementation of `map` to reduce memory usage. While `map` keeps - all the results in memory until the end of the traversal, this method removes - intermediate results as soon as they are not needed anymore. - - Prefer this method over `map` if the results consume significant amount of - memory and if the intermediate results are not needed. - - Parameters - ---------- - fn - Function to apply to each node. It receives the node as the first argument, - the results as the second and the results of the children as keyword - arguments. - filter - Pattern-like object to filter out nodes from the traversal. The traversal - will only visit nodes that match the given pattern and stop otherwise. - - Returns - ------- - In contrast to `map`, this method returns the result of the root node only since - the rest of the results are already discarded. - - """ - results: dict[Node, Any] = {} - - graph, dependents = Graph.from_bfs(self, filter=filter).toposort() - dependents = {k: set(v) for k, v in dependents.items()} - - for node, dependencies in graph.items(): - # minor optimization to directly recurse into the children - kwargs = { - k: _recursive_lookup(v, results) - for k, v in zip(node.__argnames__, node.__args__) - } - results[node] = fn(node, results, **kwargs) - - # remove the results belonging to the dependencies if they are not - # needed by other nodes during the rest of the traversal - for dependency in set(dependencies): - dependents[dependency].remove(node) - if not dependents[dependency]: - del results[dependency] - - return results[self] - - @experimental - def map_nodes(self, fn: Callable, filter: Optional[Finder] = None) -> Any: - """Apply a function to all nodes in the graph more memory efficiently. - - Alternative implementation of `map` passing only node results to the function - as positional arguments. This method is useful for calculations where the - nodes don't need to be reconstructed. - """ - results: dict[Node, Any] = {} - - graph, _ = Graph.from_bfs(self, filter=filter).toposort() - for node, children in graph.items(): - args = _recursive_lookup(children, results) - results[node] = fn(node, *args) - - return results - - # TODO(kszucs): perhaps rename it to find_all() for better clarity - def find( - self, - finder: FinderLike, - filter: Optional[FinderLike] = None, - context: Optional[dict] = None, - ordered: bool = False, - ) -> list[Node]: - """Find all nodes matching a given pattern or type in the graph. - - Allow to match nodes based on the flexible pattern matching system implemented - in the pattern module, but also provide a fast path for matching based on the - type of the node. - - Parameters - ---------- - finder - A type, tuple of types, a pattern or a callable to match upon. - filter - A type, tuple of types, a pattern or a callable to filter out nodes - from the traversal. The traversal will only visit nodes that match - the given filter and stop otherwise. - context - Optional context to use if `finder` or `filter` is a pattern. - ordered - Emit nodes in topological order if `True`. - - Returns - ------- - The list of nodes matching the given pattern. The order of the nodes is - determined by a breadth-first search. - - """ - graph = Graph.from_bfs(self, filter=filter, context=context) - finder = _coerce_finder(finder, context) - if ordered: - graph, _ = graph.toposort() - return [node for node in graph.nodes() if finder(node)] - - @experimental - def find_below( - self, - finder: FinderLike, - filter: Optional[FinderLike] = None, - context: Optional[dict] = None, - ) -> list[Node]: - """Find all nodes below the current node matching a given pattern in the graph. - - A variant of find() that only returns nodes below the current node in the graph. - - Parameters - ---------- - finder - A type, tuple of types, a pattern or a callable to match upon. - filter - A type, tuple of types, a pattern or a callable to filter out nodes - from the traversal. The traversal will only visit nodes that match - the given filter and stop otherwise. - context - Optional context to use if `finder` or `filter` is a pattern. - - Returns - ------- - The list of nodes matching the given pattern. - """ - graph = Graph.from_bfs(self.__children__, filter=filter, context=context) - finder = _coerce_finder(finder, context) - return [node for node in graph.nodes() if finder(node)] - - @experimental - def find_topmost( - self, finder: FinderLike, context: Optional[dict] = None - ) -> list[Node]: - """Find all topmost nodes matching a given pattern in the graph. - - A more advanced version of find, this method stops the traversal at the first - node that matches the given pattern and does not descend into its children. - - Parameters - ---------- - finder - A type, tuple of types, a pattern or a callable to match upon. - context - Optional context to use if `finder` is a pattern. - - Returns - ------- - The list of topmost nodes matching the given pattern. - - """ - seen = set() - queue = deque([self]) - result = [] - finder = _coerce_finder(finder, context) - - while queue: - if (node := queue.popleft()) not in seen: - if finder(node): - result.append(node) - else: - queue.extend(node.__children__) - seen.add(node) - return result - - @experimental - def replace( - self, - replacer: ReplacerLike, - filter: Optional[FinderLike] = None, - context: Optional[dict] = None, - ) -> Any: - """Match and replace nodes in the graph according to a given pattern. - - The pattern matching system is used to match nodes in the graph and replace them - with the results of the pattern. - - Parameters - ---------- - replacer - A `Pattern`, a `Mapping` or a callable which can be fed to - `node.map()` directly to replace nodes. - filter - A type, tuple of types, a pattern or a callable to filter out nodes - from the traversal. The traversal will only visit nodes that match - the given filter and stop otherwise. - context - Optional context to use for the pattern matching. - - Returns - ------- - The root node of the graph with the replaced nodes. - - """ - replacer = _coerce_replacer(replacer, context) - results = self.map(replacer, filter=filter) - return results.get(self, self) - - -class Graph(dict[Node, Sequence[Node]]): - """A mapping-like graph data structure for easier graph traversal and manipulation. - - The data structure is a mapping of nodes to their children. The children are - represented as a sequence of nodes. The graph can be constructed from a root node - using the `from_bfs` or `from_dfs` class methods. - - Parameters - ---------- - mapping : Node or Mapping[Node, Sequence[Node]], default () - Either a root node or a mapping of nodes to their children. - - """ - - def __init__(self, mapping=(), /, **kwargs): - if isinstance(mapping, Node): - mapping = self.from_bfs(mapping) - super().__init__(mapping, **kwargs) - - @classmethod - def from_bfs( - cls, - root: Node, - filter: Optional[FinderLike] = None, - context: Optional[dict] = None, - ) -> Self: - """Construct a graph from a root node using a breadth-first search. - - The traversal is implemented in an iterative fashion using a queue. - - Parameters - ---------- - root - Root node of the graph. - filter - A type, tuple of types, a pattern or a callable to filter out nodes - from the traversal. The traversal will only visit nodes that match - the given filter and stop otherwise. - context - Optional context to use for the pattern matching. - - Returns - ------- - A graph constructed from the root node. - - """ - if filter is None: - return bfs(root) - else: - filter = _coerce_finder(filter, context) - return bfs_while(root, filter=filter) - - @classmethod - def from_dfs( - cls, - root: Node, - filter: Optional[FinderLike] = None, - context: Optional[dict] = None, - ) -> Self: - """Construct a graph from a root node using a depth-first search. - - The traversal is implemented in an iterative fashion using a stack. - - Parameters - ---------- - root - Root node of the graph. - filter - A type, tuple of types, a pattern or a callable to filter out nodes - from the traversal. The traversal will only visit nodes that match - the given filter and stop otherwise. - context - Optional context to use for the pattern matching. - - Returns - ------- - A graph constructed from the root node. - - """ - if filter is None: - return dfs(root) - else: - filter = _coerce_finder(filter, None) - return dfs_while(root, filter=filter) - - def __repr__(self): - return f"{self.__class__.__name__}({super().__repr__()})" - - def nodes(self) -> KeysView[Node]: - """Return all unique nodes in the graph.""" - return self.keys() - - def invert(self) -> Self: - """Invert the data structure. - - The graph originally maps nodes to their children, this method inverts the - mapping to map nodes to their parents. - - Returns - ------- - The inverted graph. - - """ - result: dict[Node, list[Node]] = {node: [] for node in self} - for node, dependencies in self.items(): - for dependency in dependencies: - result[dependency].append(node) - return self.__class__({k: tuple(v) for k, v in result.items()}) - - def toposort(self) -> Self: - """Topologically sort the graph using Kahn's algorithm. - - The graph is sorted in a way that all the dependencies of a node are placed - before the node itself. The graph must not contain any cycles. Especially useful - for mutating the graph in a way that the dependencies of a node are mutated - before the node itself. - - Returns - ------- - The topologically sorted graph. - - """ - dependents = self.invert() - in_degree = {k: len(v) for k, v in self.items()} - - queue = deque(node for node, count in in_degree.items() if not count) - result = self.__class__() - - while queue: - node = queue.popleft() - result[node] = self[node] - - for dependent in dependents[node]: - in_degree[dependent] -= 1 - if not in_degree[dependent]: - queue.append(dependent) - - if any(in_degree.values()): - raise ValueError("cycle detected in the graph") - - return result, dependents - - -# these could be callables instead -proceed = True -halt = False - - -def traverse( - fn: Callable[[Node], tuple[bool | Iterable, Any]], node: Iterable[Node] | Node -) -> Iterator[Any]: - """Utility for generic expression tree traversal. - - Parameters - ---------- - fn - A function applied on each expression. The first element of the tuple controls - the traversal, and the second is the result if its not `None`. - node - The Node expression or a list of expressions. - - """ - nodes = list(_flatten_collections(promote_list(node))) - queue: deque[Node] = deque(reversed(nodes)) - seen: set[Node] = set() - - while queue: - node = queue.pop() - - if node in seen: - continue - seen.add(node) - - control, result = fn(node) - if result is not None: - yield result - - if control is not halt: - if control is proceed: - children = node.__children__ - elif isinstance(control, Iterable): - children = control - else: - raise TypeError( - "First item of the returned tuple must be " - "an instance of boolean or iterable" - ) - - queue.extend(reversed(children)) - - -def bfs(root: Node) -> Graph: - """Construct a graph from a root node using a breadth-first search. - - Parameters - ---------- - root - Root node of the graph. - - Returns - ------- - A graph constructed from the root node. - - """ - # fast path for the default no filter case, according to benchmarks - # this is gives a 10% speedup compared to the filtered version - nodes = _flatten_collections(promote_list(root)) - queue = deque(nodes) - graph = Graph() - - while queue: - if (node := queue.popleft()) not in graph: - children = node.__children__ - graph[node] = children - queue.extend(children) - - return graph - - -def bfs_while(root: Node, filter: Finder) -> Graph: - """Construct a graph from a root node using a breadth-first search. - - Parameters - ---------- - root - Root node of the graph. - filter - A callable which returns a boolean given a node. The traversal will only - visit nodes that match the given filter and stop otherwise. - - Returns - ------- - A graph constructed from the root node. - - """ - nodes = _flatten_collections(promote_list(root)) - queue = deque(node for node in nodes if filter(node)) - graph = Graph() - - while queue: - if (node := queue.popleft()) not in graph: - children = tuple(child for child in node.__children__ if filter(child)) - graph[node] = children - queue.extend(children) - - return graph - - -def dfs(root: Node) -> Graph: - """Construct a graph from a root node using a depth-first search. - - Parameters - ---------- - root - Root node of the graph. - - Returns - ------- - A graph constructed from the root node. - - """ - # fast path for the default no filter case, according to benchmarks - # this is gives a 10% speedup compared to the filtered version - nodes = _flatten_collections(promote_list(root)) - stack = deque(nodes) - graph = {} - - while stack: - if (node := stack.pop()) not in graph: - children = node.__children__ - graph[node] = children - stack.extend(children) - - return Graph(reversed(graph.items())) - - -def dfs_while(root: Node, filter: Finder) -> Graph: - """Construct a graph from a root node using a depth-first search. - - Parameters - ---------- - root - Root node of the graph. - filter - A callable which returns a boolean given a node. The traversal will only - visit nodes that match the given filter and stop otherwise. - - Returns - ------- - A graph constructed from the root node. - - """ - nodes = _flatten_collections(promote_list(root)) - stack = deque(node for node in nodes if filter(node)) - graph = {} - - while stack: - if (node := stack.pop()) not in graph: - children = tuple(child for child in node.__children__ if filter(child)) - graph[node] = children - stack.extend(children) - - return Graph(reversed(graph.items())) diff --git a/third_party/bigframes_vendored/ibis/common/grounds.py b/third_party/bigframes_vendored/ibis/common/grounds.py deleted file mode 100644 index 874e18c4057..00000000000 --- a/third_party/bigframes_vendored/ibis/common/grounds.py +++ /dev/null @@ -1,232 +0,0 @@ -# Contains code from https://github.com/ibis-project/ibis/blob/9.2.0/ibis/common/grounds.py -from __future__ import annotations - -import contextlib -from copy import copy -from typing import Any, ClassVar, Union, get_origin - -from bigframes_vendored.ibis.common.annotations import ( - Annotation, - Argument, - Attribute, - Signature, -) -from bigframes_vendored.ibis.common.bases import ( # noqa: F401 - Abstract, - AbstractMeta, - Comparable, - Final, - Hashable, - Immutable, - Singleton, -) -from bigframes_vendored.ibis.common.collections import FrozenDict # noqa: TCH001 -from bigframes_vendored.ibis.common.patterns import Pattern -from bigframes_vendored.ibis.common.typing import evaluate_annotations -from typing_extensions import Self, dataclass_transform - - -class AnnotableMeta(AbstractMeta): - """Metaclass to turn class annotations into a validatable function signature.""" - - __slots__ = () - - def __new__(metacls, clsname, bases, dct, **kwargs): - # inherit signature from parent classes - signatures, attributes = [], {} - for parent in bases: - with contextlib.suppress(AttributeError): - attributes.update(parent.__attributes__) - with contextlib.suppress(AttributeError): - signatures.append(parent.__signature__) - - # collection type annotations and convert them to patterns - module = dct.get("__module__") - qualname = dct.get("__qualname__") or clsname - annotations = dct.get("__annotations__", {}) - - # TODO(kszucs): pass dct as localns to evaluate_annotations - typehints = evaluate_annotations(annotations, module, clsname) - for name, typehint in typehints.items(): - if get_origin(typehint) is ClassVar: - continue - pattern = Pattern.from_typehint(typehint) - if name in dct: - dct[name] = Argument(pattern, default=dct[name], typehint=typehint) - else: - dct[name] = Argument(pattern, typehint=typehint) - - # collect the newly defined annotations - slots = list(dct.pop("__slots__", [])) - namespace, arguments = {}, {} - for name, attrib in dct.items(): - if isinstance(attrib, Pattern): - arguments[name] = Argument(attrib) - slots.append(name) - elif isinstance(attrib, Argument): - arguments[name] = attrib - slots.append(name) - elif isinstance(attrib, Attribute): - attributes[name] = attrib - slots.append(name) - else: - namespace[name] = attrib - - # merge the annotations with the parent annotations - signature = Signature.merge(*signatures, **arguments) - argnames = tuple(signature.parameters.keys()) - - namespace.update( - __module__=module, - __qualname__=qualname, - __argnames__=argnames, - __attributes__=attributes, - __match_args__=argnames, - __signature__=signature, - __slots__=tuple(slots), - ) - return super().__new__(metacls, clsname, bases, namespace, **kwargs) - - def __or__(self, other): - # required to support `dt.Numeric | dt.Floating` annotation for python<3.10 - return Union[self, other] - - -@dataclass_transform() -class Annotable(Abstract, metaclass=AnnotableMeta): - """Base class for objects with custom validation rules.""" - - __signature__: ClassVar[Signature] - """Signature of the class, containing the Argument annotations.""" - - __attributes__: ClassVar[FrozenDict[str, Annotation]] - """Mapping of the Attribute annotations.""" - - __argnames__: ClassVar[tuple[str, ...]] - """Names of the arguments.""" - - __match_args__: ClassVar[tuple[str, ...]] - """Names of the arguments to be used for pattern matching.""" - - @classmethod - def __create__(cls, *args: Any, **kwargs: Any) -> Self: - # construct the instance by passing only validated keyword arguments - kwargs = cls.__signature__.validate(cls, args, kwargs) - return super().__create__(**kwargs) - - @classmethod - def __recreate__(cls, kwargs: Any) -> Self: - # bypass signature binding by requiring keyword arguments only - kwargs = cls.__signature__.validate_nobind(cls, kwargs) - return super().__create__(**kwargs) - - def __init__(self, **kwargs: Any) -> None: - # set the already validated arguments - for name, value in kwargs.items(): - object.__setattr__(self, name, value) - # initialize the remaining attributes - for name, field in self.__attributes__.items(): - if field.has_default(): - object.__setattr__(self, name, field.get_default(name, self)) - - def __setattr__(self, name, value) -> None: - # first try to look up the argument then the attribute - if param := self.__signature__.parameters.get(name): - value = param.annotation.validate(name, value, self) - # then try to look up the attribute - elif annot := self.__attributes__.get(name): - value = annot.validate(name, value, self) - return super().__setattr__(name, value) - - def __repr__(self) -> str: - args = (f"{n}={getattr(self, n)!r}" for n in self.__argnames__) - argstring = ", ".join(args) - return f"{self.__class__.__name__}({argstring})" - - def __eq__(self, other) -> bool: - # compare types - if type(self) is not type(other): - return NotImplemented - # compare arguments - if self.__args__ != other.__args__: - return False - # compare attributes - for name in self.__attributes__: - if getattr(self, name, None) != getattr(other, name, None): - return False - return True - - @property - def __args__(self) -> tuple[Any, ...]: - return tuple(getattr(self, name) for name in self.__argnames__) - - def copy(self, **overrides: Any) -> Annotable: - """Return a copy of this object with the given overrides. - - Parameters - ---------- - overrides - Argument override values - - Returns - ------- - Annotable - New instance of the copied object - - """ - this = copy(self) - for name, value in overrides.items(): - setattr(this, name, value) - return this - - -class Concrete(Immutable, Comparable, Annotable): - """Opinionated base class for immutable data classes.""" - - __slots__ = ("__args__", "__precomputed_hash__") - - def __init__(self, **kwargs: Any) -> None: - # collect and set the arguments in a single pass - args = [] - for name in self.__argnames__: - value = kwargs[name] - args.append(value) - object.__setattr__(self, name, value) - - # precompute the hash value since the instance is immutable - args = tuple(args) - hashvalue = hash((self.__class__, args)) - object.__setattr__(self, "__args__", args) - object.__setattr__(self, "__precomputed_hash__", hashvalue) - - # initialize the remaining attributes - for name, field in self.__attributes__.items(): - if field.has_default(): - object.__setattr__(self, name, field.get_default(name, self)) - - def __reduce__(self): - # assuming immutability and idempotency of the __init__ method, we can - # reconstruct the instance from the arguments without additional attributes - state = dict(zip(self.__argnames__, self.__args__)) - return (self.__recreate__, (state,)) - - def __hash__(self) -> int: - return self.__precomputed_hash__ - - def __equals__(self, other) -> bool: - return hash(self) == hash(other) and self.__args__ == other.__args__ - - @property - def args(self): - return self.__args__ - - @property - def argnames(self) -> tuple[str, ...]: - return self.__argnames__ - - def copy(self, **overrides) -> Self: - kwargs = dict(zip(self.__argnames__, self.__args__)) - if unknown_args := overrides.keys() - kwargs.keys(): - raise AttributeError(f"Unexpected arguments: {unknown_args}") - kwargs.update(overrides) - return self.__recreate__(kwargs) diff --git a/third_party/bigframes_vendored/ibis/common/numeric.py b/third_party/bigframes_vendored/ibis/common/numeric.py deleted file mode 100644 index ad406ae340a..00000000000 --- a/third_party/bigframes_vendored/ibis/common/numeric.py +++ /dev/null @@ -1,50 +0,0 @@ -# Contains code from https://github.com/ibis-project/ibis/blob/9.2.0/ibis/common/numeric.py - -from __future__ import annotations - -from decimal import Context, Decimal, InvalidOperation - - -def normalize_decimal( - value, - precision: int | None = None, - scale: int | None = None, - strict: bool = True, -): - context = Context(prec=38 if precision is None else precision) - - try: - if isinstance(value, float): - out = Decimal(str(value)) - else: - out = Decimal(value) - except InvalidOperation: - raise TypeError(f"Unable to construct decimal from {value!r}") - - out = out.normalize(context=context) - components = out.as_tuple() - n_digits = len(components.digits) - exponent = components.exponent - - if precision is not None and precision < n_digits: - raise TypeError( - f"Decimal value {value} has too many digits for precision: {precision}" - ) - - if scale is not None: - if strict and exponent < -scale: - raise TypeError( - f"Normalizing {value} with scale {exponent} to scale -{scale} " - "would loose precision" - ) - - other = Decimal(10) ** -scale - try: - out = out.quantize(other, context=context) - except InvalidOperation: - raise TypeError( - f"Unable to normalize {value!r} as decimal with precision {precision} " - f"and scale {scale}" - ) - - return out diff --git a/third_party/bigframes_vendored/ibis/common/patterns.py b/third_party/bigframes_vendored/ibis/common/patterns.py deleted file mode 100644 index 68861aa1908..00000000000 --- a/third_party/bigframes_vendored/ibis/common/patterns.py +++ /dev/null @@ -1,1711 +0,0 @@ -# Contains code from https://github.com/ibis-project/ibis/blob/9.2.0/ibis/common/patterns.py - -from __future__ import annotations - -import math -import numbers -from abc import abstractmethod -from collections.abc import Callable, Mapping, Sequence -from enum import Enum -from inspect import Parameter -from typing import ( - Annotated, - ForwardRef, - Generic, - Literal, - Optional, - TypeVar, - Union, - get_args, - get_origin, -) -from typing import Any as AnyType - -import toolz -from bigframes_vendored.ibis.common.bases import FrozenSlotted as Slotted -from bigframes_vendored.ibis.common.bases import Hashable, Singleton -from bigframes_vendored.ibis.common.collections import ( - FrozenDict, - RewindableIterator, - frozendict, -) -from bigframes_vendored.ibis.common.deferred import ( - Deferred, - Factory, - Resolver, - Variable, - _, # noqa: F401 - resolver, -) -from bigframes_vendored.ibis.common.typing import ( - Coercible, - CoercionError, - Sentinel, - format_typehint, - get_bound_typevars, - get_type_params, -) -from bigframes_vendored.ibis.util import import_object, is_iterable, unalias_package -from typing_extensions import GenericMeta - -T_co = TypeVar("T_co", covariant=True) - - -def as_resolver(obj): - if callable(obj) and not isinstance(obj, Deferred): - return Factory(obj) - else: - return resolver(obj) - - -class NoMatch(metaclass=Sentinel): - """Marker to indicate that a pattern didn't match.""" - - -# TODO(kszucs): have an As[int] or Coerced[int] type in ibis.common.typing which -# would be used to annotate an argument as coercible to int or to a certain type -# without needing for the type to inherit from Coercible -class Pattern(Hashable): - """Base class for all patterns. - - Patterns are used to match values against a given condition. They are extensively - used by other core components of Ibis to validate and/or coerce user inputs. - """ - - @classmethod - def from_typehint(cls, annot: type, allow_coercion: bool = True) -> Pattern: - """Construct a validator from a python type annotation. - - Parameters - ---------- - annot - The typehint annotation to construct the pattern from. This must be - an already evaluated type annotation. - allow_coercion - Whether to use coercion if the typehint is a Coercible type. - - Returns - ------- - A pattern that matches the given type annotation. - - """ - # TODO(kszucs): cache the result of this function - # TODO(kszucs): explore issubclass(typ, SupportsInt) etc. - origin, args = get_origin(annot), get_args(annot) - - if origin is None: - # the typehint is not generic - if annot is Ellipsis or annot is AnyType: - # treat both `Any` and `...` as wildcard - return _any - elif isinstance(annot, type): - # the typehint is a concrete type (e.g. int, str, etc.) - if allow_coercion and issubclass(annot, Coercible): - # the type implements the Coercible protocol so we try to - # coerce the value to the given type rather than checking - return CoercedTo(annot) - else: - return InstanceOf(annot) - elif isinstance(annot, TypeVar): - # if the typehint is a type variable we try to construct a - # validator from it only if it is covariant and has a bound - if not annot.__covariant__: - raise NotImplementedError( - "Only covariant typevars are supported for now" - ) - if annot.__bound__: - return cls.from_typehint(annot.__bound__) - else: - return _any - elif isinstance(annot, Enum): - # for enums we check the value against the enum values - return EqualTo(annot) - elif isinstance(annot, str): - # for strings and forward references we check in a lazy way - return LazyInstanceOf(annot) - elif isinstance(annot, ForwardRef): - return LazyInstanceOf(annot.__forward_arg__) - else: - raise TypeError(f"Cannot create validator from annotation {annot!r}") - elif origin is CoercedTo: - return CoercedTo(args[0]) - elif origin is Literal: - # for literal types we check the value against the literal values - return IsIn(args) - elif origin is Union: - # this is slightly more complicated because we need to handle - # Optional[T] which is Union[T, None] and Union[T1, T2, ...] - *rest, last = args - if last is type(None): - # the typehint is Optional[*rest] which is equivalent to - # Union[*rest, None], so we construct an Option pattern - if len(rest) == 1: - inner = cls.from_typehint(rest[0]) - else: - inner = AnyOf(*map(cls.from_typehint, rest)) - return Option(inner) - else: - # the typehint is Union[*args] so we construct an AnyOf pattern - return AnyOf(*map(cls.from_typehint, args)) - elif origin is Annotated: - # the Annotated typehint can be used to add extra validation logic - # to the typehint, e.g. Annotated[int, Positive], the first argument - # is used for isinstance checks, the rest are applied in conjunction - annot, *extras = args - return AllOf(cls.from_typehint(annot), *extras) - elif origin is Callable: - # the Callable typehint is used to annotate functions, e.g. the - # following typehint annotates a function that takes two integers - # and returns a string: Callable[[int, int], str] - if args: - # callable with args and return typehints construct a special - # CallableWith validator - arg_hints, return_hint = args - arg_patterns = tuple(map(cls.from_typehint, arg_hints)) - return_pattern = cls.from_typehint(return_hint) - return CallableWith(arg_patterns, return_pattern) - else: - # in case of Callable without args we check for the Callable - # protocol only - return InstanceOf(Callable) - elif issubclass(origin, tuple): - # construct validators for the tuple elements, but need to treat - # variadic tuples differently, e.g. tuple[int, ...] is a variadic - # tuple of integers, while tuple[int] is a tuple with a single int - first, *rest = args - if rest == [Ellipsis]: - return TupleOf(cls.from_typehint(first)) - else: - return PatternList(map(cls.from_typehint, args), type=origin) - elif issubclass(origin, Sequence): - # construct a validator for the sequence elements where all elements - # must be of the same type, e.g. Sequence[int] is a sequence of ints - (value_inner,) = map(cls.from_typehint, args) - if allow_coercion and issubclass(origin, Coercible): - return GenericSequenceOf(value_inner, type=origin) - else: - return SequenceOf(value_inner, type=origin) - elif issubclass(origin, Mapping): - # construct a validator for the mapping keys and values, e.g. - # Mapping[str, int] is a mapping with string keys and int values - key_inner, value_inner = map(cls.from_typehint, args) - return MappingOf(key_inner, value_inner, type=origin) - elif isinstance(origin, GenericMeta): - # construct a validator for the generic type, see the specific - # Generic* validators for more details - if allow_coercion and issubclass(origin, Coercible) and args: - return GenericCoercedTo(annot) - else: - return GenericInstanceOf(annot) - else: - raise TypeError( - f"Cannot create validator from annotation {annot!r} {origin!r}" - ) - - @abstractmethod - def match(self, value: AnyType, context: dict[str, AnyType]) -> AnyType: - """Match a value against the pattern. - - Parameters - ---------- - value - The value to match the pattern against. - context - A dictionary providing arbitrary context for the pattern matching. - - Returns - ------- - The result of the pattern matching. If the pattern doesn't match - the value, then it must return the `NoMatch` sentinel value. - - """ - ... - - def describe(self, plural=False): - return f"matching {self!r}" - - @abstractmethod - def __eq__(self, other: Pattern) -> bool: ... - - def __invert__(self) -> Not: - """Syntax sugar for matching the inverse of the pattern.""" - return Not(self) - - def __or__(self, other: Pattern) -> AnyOf: - """Syntax sugar for matching either of the patterns. - - Parameters - ---------- - other - The other pattern to match against. - - Returns - ------- - New pattern that matches if either of the patterns match. - - """ - return AnyOf(self, other) - - def __and__(self, other: Pattern) -> AllOf: - """Syntax sugar for matching both of the patterns. - - Parameters - ---------- - other - The other pattern to match against. - - Returns - ------- - New pattern that matches if both of the patterns match. - - """ - return AllOf(self, other) - - def __rshift__(self, other: Deferred) -> Replace: - """Syntax sugar for replacing a value. - - Parameters - ---------- - other - The deferred to use for constructing the replacement value. - - Returns - ------- - New replace pattern. - - """ - return Replace(self, other) - - def __rmatmul__(self, name: str) -> Capture: - """Syntax sugar for capturing a value. - - Parameters - ---------- - name - The name of the capture. - - Returns - ------- - New capture pattern. - - """ - return Capture(name, self) - - def __iter__(self) -> SomeOf: - yield SomeOf(self) - - -class Is(Slotted, Pattern): - """Pattern that matches a value against a reference value. - - Parameters - ---------- - value - The reference value to match against. - - """ - - __slots__ = ("value",) - value: AnyType - - def match(self, value, context): - if value is self.value: - return value - else: - return NoMatch - - -class Any(Slotted, Singleton, Pattern): - """Pattern that accepts any value, basically a no-op.""" - - def match(self, value, context): - return value - - -_any = Any() - - -class Nothing(Slotted, Singleton, Pattern): - """Pattern that no values.""" - - def match(self, value, context): - return NoMatch - - -class Capture(Slotted, Pattern): - """Pattern that captures a value in the context. - - Parameters - ---------- - pattern - The pattern to match against. - key - The key to use in the context if the pattern matches. - - """ - - __slots__ = ("key", "pattern") - key: AnyType - pattern: Pattern - - def __init__(self, key, pat=_any): - if isinstance(key, (Deferred, Resolver)): - key = as_resolver(key) - if isinstance(key, Variable): - key = key.name - else: - raise TypeError("Only variables can be used as capture keys") - super().__init__(key=key, pattern=pattern(pat)) - - def match(self, value, context): - value = self.pattern.match(value, context) - if value is NoMatch: - return NoMatch - context[self.key] = value - return value - - -class Replace(Slotted, Pattern): - """Pattern that replaces a value with the output of another pattern. - - Parameters - ---------- - matcher - The pattern to match against. - replacer - The deferred to use as a replacement. - - """ - - __slots__ = ("matcher", "replacer") - matcher: Pattern - replacer: Resolver - - def __init__(self, matcher, replacer): - super().__init__(matcher=pattern(matcher), replacer=as_resolver(replacer)) - - def match(self, value, context): - value = self.matcher.match(value, context) - if value is NoMatch: - return NoMatch - # use the `_` reserved variable to record the value being replaced - # in the context, so that it can be used in the replacer pattern - context["_"] = value - return self.replacer.resolve(context) - - -def replace(matcher): - """More convenient syntax for replacing a value with the output of a function.""" - - def decorator(replacer): - return Replace(matcher, replacer) - - return decorator - - -class Check(Slotted, Pattern): - """Pattern that checks a value against a predicate. - - Parameters - ---------- - predicate - The predicate to use. - - """ - - __slots__ = ("predicate",) - predicate: Callable - - @classmethod - def __create__(cls, predicate): - if isinstance(predicate, (Deferred, Resolver)): - return DeferredCheck(predicate) - else: - return super().__create__(predicate) - - def __init__(self, predicate): - assert callable(predicate) - super().__init__(predicate=predicate) - - def describe(self, plural=False): - if plural: - return f"values that satisfy {self.predicate.__name__}()" - else: - return f"a value that satisfies {self.predicate.__name__}()" - - def match(self, value, context): - if self.predicate(value): - return value - else: - return NoMatch - - -class DeferredCheck(Slotted, Pattern): - __slots__ = ("resolver",) - resolver: Resolver - - def __init__(self, obj): - super().__init__(resolver=as_resolver(obj)) - - def describe(self, plural=False): - if plural: - return f"values that satisfy {self.resolver!r}" - else: - return f"a value that satisfies {self.resolver!r}" - - def match(self, value, context): - context["_"] = value - if self.resolver.resolve(context): - return value - else: - return NoMatch - - -class Custom(Slotted, Pattern): - """User defined custom matcher function. - - Parameters - ---------- - func - The function to apply. - - """ - - __slots__ = ("func",) - func: Callable - - def __init__(self, func): - assert callable(func) - super().__init__(func=func) - - def match(self, value, context): - return self.func(value, context) - - -class EqualTo(Slotted, Pattern): - """Pattern that checks a value equals to the given value. - - Parameters - ---------- - value - The value to check against. - - """ - - __slots__ = ("value",) - value: AnyType - - @classmethod - def __create__(cls, value): - if isinstance(value, (Deferred, Resolver)): - return DeferredEqualTo(value) - else: - return super().__create__(value) - - def __init__(self, value): - super().__init__(value=value) - - def match(self, value, context): - if value == self.value: - return value - else: - return NoMatch - - def describe(self, plural=False): - return repr(self.value) - - -class DeferredEqualTo(Slotted, Pattern): - """Pattern that checks a value equals to the given value. - - Parameters - ---------- - value - The value to check against. - - """ - - __slots__ = ("resolver",) - resolver: Resolver - - def __init__(self, obj): - super().__init__(resolver=as_resolver(obj)) - - def match(self, value, context): - context["_"] = value - if value == self.resolver.resolve(context): - return value - else: - return NoMatch - - def describe(self, plural=False): - return repr(self.resolver) - - -class Option(Slotted, Pattern): - """Pattern that matches `None` or a value that passes the inner validator. - - Parameters - ---------- - pattern - The inner pattern to use. - - """ - - __slots__ = ("pattern", "default") - pattern: Pattern - default: AnyType - - def __init__(self, pat, default=None): - super().__init__(pattern=pattern(pat), default=default) - - def describe(self, plural=False): - if plural: - return f"optional {self.pattern.describe(plural=True)}" - else: - return f"either None or {self.pattern.describe(plural=False)}" - - def match(self, value, context): - if value is None: - if self.default is None: - return None - else: - return self.default - else: - return self.pattern.match(value, context) - - -def _describe_type(typ, plural=False): - if isinstance(typ, tuple): - *rest, last = typ - rest = ", ".join(_describe_type(t, plural=plural) for t in rest) - last = _describe_type(last, plural=plural) - return f"{rest} or {last}" if rest else last - - name = format_typehint(typ) - if plural: - return f"{name}s" - elif name[0].lower() in "aeiou": - return f"an {name}" - else: - return f"a {name}" - - -class TypeOf(Slotted, Pattern): - """Pattern that matches a value that is of a given type.""" - - __slots__ = ("type",) - type: type - - def __init__(self, typ): - super().__init__(type=typ) - - def describe(self, plural=False): - return f"exactly {_describe_type(self.type, plural=plural)}" - - def match(self, value, context): - if type(value) is self.type: - return value - else: - return NoMatch - - -class SubclassOf(Slotted, Pattern): - """Pattern that matches a value that is a subclass of a given type. - - Parameters - ---------- - type - The type to check against. - - """ - - __slots__ = ("type",) - - def __init__(self, typ): - super().__init__(type=typ) - - def describe(self, plural=False): - if plural: - return f"subclasses of {self.type.__name__}" - else: - return f"a subclass of {self.type.__name__}" - - def match(self, value, context): - if issubclass(value, self.type): - return value - else: - return NoMatch - - -class InstanceOf(Slotted, Singleton, Pattern): - """Pattern that matches a value that is an instance of a given type. - - Parameters - ---------- - types - The type to check against. - - """ - - __slots__ = ("type",) - type: Any - - def __init__(self, typ): - super().__init__(type=typ) - - def describe(self, plural=False): - return _describe_type(self.type, plural=plural) - - def match(self, value, context): - if isinstance(value, self.type): - return value - else: - return NoMatch - - def __call__(self, *args, **kwargs): - return Object(self.type, *args, **kwargs) - - -class GenericInstanceOf(Slotted, Pattern): - """Pattern that matches a value that is an instance of a given generic type. - - Parameters - ---------- - typ - The type to check against (must be a generic type). - - Examples - -------- - >>> class MyNumber(Generic[T_co]): - ... value: T_co - ... - ... def __init__(self, value: T_co): - ... self.value = value - ... - ... def __eq__(self, other): - ... return type(self) is type(other) and self.value == other.value - >>> p = GenericInstanceOf(MyNumber[int]) - >>> assert p.match(MyNumber(1), {}) == MyNumber(1) - >>> assert p.match(MyNumber(1.0), {}) is NoMatch - >>> - >>> p = GenericInstanceOf(MyNumber[float]) - >>> assert p.match(MyNumber(1.0), {}) == MyNumber(1.0) - >>> assert p.match(MyNumber(1), {}) is NoMatch - - """ - - __slots__ = ("type", "origin", "fields") - origin: type - fields: FrozenDict[str, Pattern] - - def __init__(self, typ): - origin = get_origin(typ) - typevars = get_bound_typevars(typ) - - fields = {} - for var, (attr, type_) in typevars.items(): - if not var.__covariant__: - raise TypeError( - f"Typevar {var} is not covariant, cannot use it in a GenericInstanceOf" - ) - fields[attr] = Pattern.from_typehint(type_, allow_coercion=False) - - super().__init__(type=typ, origin=origin, fields=frozendict(fields)) - - def describe(self, plural=False): - return _describe_type(self.type, plural=plural) - - def match(self, value, context): - if not isinstance(value, self.origin): - return NoMatch - - for name, pattern in self.fields.items(): - attr = getattr(value, name) - if pattern.match(attr, context) is NoMatch: - return NoMatch - - return value - - -class LazyInstanceOf(Slotted, Pattern): - """A version of `InstanceOf` that accepts qualnames instead of imported classes. - - Useful for delaying imports. - - Parameters - ---------- - types - The types to check against. - - """ - - __fields__ = ("qualname", "package") - __slots__ = ("qualname", "package", "loaded") - qualname: str - package: str - loaded: type - - def __init__(self, qualname): - package = unalias_package(qualname.split(".", 1)[0]) - super().__init__(qualname=qualname, package=package) - - def match(self, value, context): - if hasattr(self, "loaded"): - return value if isinstance(value, self.loaded) else NoMatch - - for klass in type(value).__mro__: - package = klass.__module__.split(".", 1)[0] - if package == self.package: - typ = import_object(self.qualname) - object.__setattr__(self, "loaded", typ) - return value if isinstance(value, typ) else NoMatch - - return NoMatch - - -class CoercedTo(Slotted, Pattern, Generic[T_co]): - """Force a value to have a particular Python type. - - If a Coercible subclass is passed, the `__coerce__` method will be used to - coerce the value. Otherwise, the type will be called with the value as the - only argument. - - Parameters - ---------- - type - The type to coerce to. - - """ - - __slots__ = ("type", "func") - type: T_co - - def __init__(self, type): - func = type.__coerce__ if issubclass(type, Coercible) else type - super().__init__(type=type, func=func) - - def describe(self, plural=False): - type = _describe_type(self.type, plural=False) - if plural: - return f"coercibles to {type}" - else: - return f"coercible to {type}" - - def match(self, value, context): - try: - value = self.func(value) - except (TypeError, CoercionError): - return NoMatch - - if isinstance(value, self.type): - return value - else: - return NoMatch - - def __call__(self, *args, **kwargs): - return Object(self.type, *args, **kwargs) - - -class GenericCoercedTo(Slotted, Pattern): - """Force a value to have a particular generic Python type. - - Parameters - ---------- - typ - The type to coerce to. Must be a generic type with bound typevars. - - Examples - -------- - >>> from typing import Generic, TypeVar - >>> - >>> T = TypeVar("T", covariant=True) - >>> - >>> class MyNumber(Coercible, Generic[T]): - ... __slots__ = ("value",) - ... - ... def __init__(self, value): - ... self.value = value - ... - ... def __eq__(self, other): - ... return type(self) is type(other) and self.value == other.value - ... - ... @classmethod - ... def __coerce__(cls, value, T=None): - ... if issubclass(T, int): - ... return cls(int(value)) - ... elif issubclass(T, float): - ... return cls(float(value)) - ... else: - ... raise CoercionError(f"Cannot coerce to {T}") - >>> p = GenericCoercedTo(MyNumber[int]) - >>> assert p.match(3.14, {}) == MyNumber(3) - >>> assert p.match("15", {}) == MyNumber(15) - >>> - >>> p = GenericCoercedTo(MyNumber[float]) - >>> assert p.match(3.14, {}) == MyNumber(3.14) - >>> assert p.match("15", {}) == MyNumber(15.0) - - """ - - __slots__ = ("origin", "params", "checker") - origin: type - params: FrozenDict[str, type] - checker: GenericInstanceOf - - def __init__(self, target): - origin = get_origin(target) - checker = GenericInstanceOf(target) - params = frozendict(get_type_params(target)) - super().__init__(origin=origin, params=params, checker=checker) - - def describe(self, plural=False): - if plural: - return f"coercibles to {self.checker.describe(plural=False)}" - else: - return f"coercible to {self.checker.describe(plural=False)}" - - def match(self, value, context): - try: - value = self.origin.__coerce__(value, **self.params) - except CoercionError: - return NoMatch - - if self.checker.match(value, context) is NoMatch: - return NoMatch - - return value - - -class Not(Slotted, Pattern): - """Pattern that matches a value that does not match a given pattern. - - Parameters - ---------- - pattern - The pattern which the value should not match. - - """ - - __slots__ = ("pattern",) - pattern: Pattern - - def __init__(self, inner): - super().__init__(pattern=pattern(inner)) - - def describe(self, plural=False): - if plural: - return f"anything except {self.pattern.describe(plural=True)}" - else: - return f"anything except {self.pattern.describe(plural=False)}" - - def match(self, value, context): - if self.pattern.match(value, context) is NoMatch: - return value - else: - return NoMatch - - -class AnyOf(Slotted, Pattern): - """Pattern that if any of the given patterns match. - - Parameters - ---------- - patterns - The patterns to match against. The first pattern that matches will be - returned. - - """ - - __slots__ = ("patterns",) - patterns: tuple[Pattern, ...] - - def __init__(self, *pats): - patterns = tuple(map(pattern, pats)) - super().__init__(patterns=patterns) - - def describe(self, plural=False): - *rest, last = self.patterns - rest = ", ".join(p.describe(plural=plural) for p in rest) - last = last.describe(plural=plural) - return f"{rest} or {last}" if rest else last - - def match(self, value, context): - for pattern in self.patterns: - result = pattern.match(value, context) - if result is not NoMatch: - return result - return NoMatch - - -class AllOf(Slotted, Pattern): - """Pattern that matches if all of the given patterns match. - - Parameters - ---------- - patterns - The patterns to match against. The value will be passed through each - pattern in order. The changes applied to the value propagate through the - patterns. - - """ - - __slots__ = ("patterns",) - patterns: tuple[Pattern, ...] - - def __init__(self, *pats): - patterns = tuple(map(pattern, pats)) - super().__init__(patterns=patterns) - - def describe(self, plural=False): - *rest, last = self.patterns - rest = ", ".join(p.describe(plural=plural) for p in rest) - last = last.describe(plural=plural) - return f"{rest} then {last}" if rest else last - - def match(self, value, context): - for pattern in self.patterns: - value = pattern.match(value, context) - if value is NoMatch: - return NoMatch - return value - - -class Length(Slotted, Pattern): - """Pattern that matches if the length of a value is within a given range. - - Parameters - ---------- - exactly - The exact length of the value. If specified, `at_least` and `at_most` - must be None. - at_least - The minimum length of the value. - at_most - The maximum length of the value. - - """ - - __slots__ = ("at_least", "at_most") - at_least: int - at_most: int - - def __init__( - self, - exactly: Optional[int] = None, - at_least: Optional[int] = None, - at_most: Optional[int] = None, - ): - if exactly is not None: - if at_least is not None or at_most is not None: - raise ValueError("Can't specify both exactly and at_least/at_most") - at_least = exactly - at_most = exactly - super().__init__(at_least=at_least, at_most=at_most) - - def describe(self, plural=False): - if self.at_least is not None and self.at_most is not None: - if self.at_least == self.at_most: - return f"with length exactly {self.at_least}" - else: - return f"with length between {self.at_least} and {self.at_most}" - elif self.at_least is not None: - return f"with length at least {self.at_least}" - elif self.at_most is not None: - return f"with length at most {self.at_most}" - else: - return "with any length" - - def match(self, value, context): - length = len(value) - if self.at_least is not None and length < self.at_least: - return NoMatch - if self.at_most is not None and length > self.at_most: - return NoMatch - return value - - -class Between(Slotted, Pattern): - """Match a value between two bounds. - - Parameters - ---------- - lower - The lower bound. - upper - The upper bound. - - """ - - __slots__ = ("lower", "upper") - lower: float - upper: float - - def __init__(self, lower: float = -math.inf, upper: float = math.inf): - super().__init__(lower=lower, upper=upper) - - def match(self, value, context): - if self.lower <= value <= self.upper: - return value - else: - return NoMatch - - -class Contains(Slotted, Pattern): - """Pattern that matches if a value contains a given value. - - Parameters - ---------- - needle - The item that the passed value should contain. - - """ - - __slots__ = ("needle",) - needle: AnyType - - def __init__(self, needle): - super().__init__(needle=needle) - - def describe(self, plural=False): - return f"containing {self.needle!r}" - - def match(self, value, context): - if self.needle in value: - return value - else: - return NoMatch - - -class IsIn(Slotted, Pattern): - """Pattern that matches if a value is in a given set. - - Parameters - ---------- - haystack - The set of values that the passed value should be in. - - """ - - __slots__ = ("haystack",) - haystack: frozenset - - def __init__(self, haystack): - super().__init__(haystack=frozenset(haystack)) - - def describe(self, plural=False): - return f"in {set(self.haystack)!r}" - - def match(self, value, context): - if value in self.haystack: - return value - else: - return NoMatch - - -class SequenceOf(Slotted, Pattern): - """Pattern that matches if all of the items in a sequence match a given pattern. - - Specialization of the more flexible GenericSequenceOf pattern which uses two - additional patterns to possibly coerce the sequence type and to match on - the length of the sequence. - - Parameters - ---------- - item - The pattern to match against each item in the sequence. - type - The type to coerce the sequence to. Defaults to tuple. - - """ - - __slots__ = ("item", "type") - item: Pattern - type: type - - def __init__(self, item, type=list): - super().__init__(item=pattern(item), type=type) - - def describe(self, plural=False): - typ = _describe_type(self.type, plural=plural) - item = self.item.describe(plural=True) - return f"{typ} of {item}" - - def match(self, values, context): - if not is_iterable(values): - return NoMatch - - if self.item == _any: - # optimization to avoid unnecessary iteration - result = values - else: - result = [] - for item in values: - item = self.item.match(item, context) - if item is NoMatch: - return NoMatch - result.append(item) - - return self.type(result) - - -class GenericSequenceOf(Slotted, Pattern): - """Pattern that matches if all of the items in a sequence match a given pattern. - - Parameters - ---------- - item - The pattern to match against each item in the sequence. - type - The type to coerce the sequence to. Defaults to list. - exactly - The exact length of the sequence. - at_least - The minimum length of the sequence. - at_most - The maximum length of the sequence. - - """ - - __slots__ = ("item", "type", "length") - item: Pattern - type: Pattern - length: Length - - def __init__( - self, - item: Pattern, - type: type = list, - exactly: Optional[int] = None, - at_least: Optional[int] = None, - at_most: Optional[int] = None, - ): - item = pattern(item) - type = CoercedTo(type) - length = Length(exactly=exactly, at_least=at_least, at_most=at_most) - super().__init__(item=item, type=type, length=length) - - def match(self, values, context): - if not is_iterable(values): - return NoMatch - - if self.item == _any: - # optimization to avoid unnecessary iteration - result = values - else: - result = [] - for value in values: - value = self.item.match(value, context) - if value is NoMatch: - return NoMatch - result.append(value) - - result = self.type.match(result, context) - if result is NoMatch: - return NoMatch - - return self.length.match(result, context) - - -class GenericMappingOf(Slotted, Pattern): - """Pattern that matches if all of the keys and values match the given patterns. - - Parameters - ---------- - key - The pattern to match the keys against. - value - The pattern to match the values against. - type - The type to coerce the mapping to. Defaults to dict. - - """ - - __slots__ = ("key", "value", "type") - key: Pattern - value: Pattern - type: Pattern - - def __init__(self, key: Pattern, value: Pattern, type: type = dict): - super().__init__(key=pattern(key), value=pattern(value), type=CoercedTo(type)) - - def match(self, value, context): - if not isinstance(value, Mapping): - return NoMatch - - result = {} - for k, v in value.items(): - if (k := self.key.match(k, context)) is NoMatch: - return NoMatch - if (v := self.value.match(v, context)) is NoMatch: - return NoMatch - result[k] = v - - result = self.type.match(result, context) - if result is NoMatch: - return NoMatch - - return result - - -MappingOf = GenericMappingOf - - -class Attrs(Slotted, Pattern): - __slots__ = ("fields",) - fields: FrozenDict[str, Pattern] - - def __init__(self, **fields): - fields = frozendict(toolz.valmap(pattern, fields)) - super().__init__(fields=fields) - - def match(self, value, context): - for attr, pattern in self.fields.items(): - if not hasattr(value, attr): - return NoMatch - - v = getattr(value, attr) - if match(pattern, v, context) is NoMatch: - return NoMatch - - return value - - -class Object(Slotted, Pattern): - """Pattern that matches if the object has the given attributes and they match the given patterns. - - The type must conform the structural pattern matching protocol, e.g. it must have a - __match_args__ attribute that is a tuple of the names of the attributes to match. - - Parameters - ---------- - type - The type of the object. - *args - The positional arguments to match against the attributes of the object. - **kwargs - The keyword arguments to match against the attributes of the object. - - """ - - __slots__ = ("type", "args", "kwargs") - type: Pattern - args: tuple[Pattern, ...] - kwargs: FrozenDict[str, Pattern] - - @classmethod - def __create__(cls, type, *args, **kwargs): - if not args and not kwargs: - return InstanceOf(type) - return super().__create__(type, *args, **kwargs) - - def __init__(self, typ, *args, **kwargs): - if isinstance(typ, type) and len(typ.__match_args__) < len(args): - raise ValueError( - "The type to match has fewer `__match_args__` than the number " - "of positional arguments in the pattern" - ) - typ = pattern(typ) - args = tuple(map(pattern, args)) - kwargs = frozendict(toolz.valmap(pattern, kwargs)) - super().__init__(type=typ, args=args, kwargs=kwargs) - - def match(self, value, context): - if self.type.match(value, context) is NoMatch: - return NoMatch - - # the pattern requirest more positional arguments than the object has - if len(value.__match_args__) < len(self.args): - return NoMatch - patterns = dict(zip(value.__match_args__, self.args)) - patterns.update(self.kwargs) - - fields = {} - changed = False - for name, pattern in patterns.items(): - try: - attr = getattr(value, name) - except AttributeError: - return NoMatch - - result = pattern.match(attr, context) - if result is NoMatch: - return NoMatch - elif result != attr: - changed = True - fields[name] = result - else: - fields[name] = attr - - if changed: - return type(value)(**fields) - else: - return value - - -class Node(Slotted, Pattern): - __slots__ = ("type", "each_arg") - type: Pattern - - def __init__(self, type, each_arg): - super().__init__(type=pattern(type), each_arg=pattern(each_arg)) - - def match(self, value, context): - if self.type.match(value, context) is NoMatch: - return NoMatch - - newargs = {} - changed = False - for name, arg in zip(value.__argnames__, value.__args__): - result = self.each_arg.match(arg, context) - if result is NoMatch: - newargs[name] = arg - else: - newargs[name] = result - changed = True - - if changed: - return value.__class__(**newargs) - else: - return value - - -class CallableWith(Slotted, Pattern): - __slots__ = ("args", "return_") - args: tuple - return_: AnyType - - def __init__(self, args, return_=_any): - super().__init__(args=tuple(args), return_=return_) - - def match(self, value, context): - from bigframes_vendored.ibis.common.annotations import EMPTY, annotated - - if not callable(value): - return NoMatch - - fn = annotated(self.args, self.return_, value) - - has_varargs = False - positional, required_positional = [], [] - for p in fn.__signature__.parameters.values(): - if p.kind in (Parameter.POSITIONAL_ONLY, Parameter.POSITIONAL_OR_KEYWORD): - positional.append(p) - if p.default is EMPTY: - required_positional.append(p) - elif p.kind is Parameter.KEYWORD_ONLY and p.default is EMPTY: - raise TypeError( - "Callable has mandatory keyword-only arguments which cannot be specified" - ) - elif p.kind is Parameter.VAR_POSITIONAL: - has_varargs = True - - if len(required_positional) > len(self.args): - # Callable has more positional arguments than expected") - return NoMatch - elif len(positional) < len(self.args) and not has_varargs: - # Callable has less positional arguments than expected") - return NoMatch - else: - return fn - - -class SomeOf(Slotted, Pattern): - __slots__ = ("pattern", "delimiter") - - @classmethod - def __create__(cls, *args, **kwargs): - if len(args) == 1: - return super().__create__(*args, **kwargs) - else: - return SomeChunksOf(*args, **kwargs) - - def __init__(self, item, **kwargs): - pattern = GenericSequenceOf(item, **kwargs) - delimiter = pattern.item - super().__init__(pattern=pattern, delimiter=delimiter) - - def match(self, values, context): - return self.pattern.match(values, context) - - -class SomeChunksOf(Slotted, Pattern): - """Pattern that unpacks a value into its elements. - - Designed to be used inside a `PatternList` pattern with the `*` syntax. - """ - - __slots__ = ("pattern", "delimiter") - - def __init__(self, *args, **kwargs): - pattern = GenericSequenceOf(PatternList(args), **kwargs) - delimiter = pattern.item.patterns[0] - super().__init__(pattern=pattern, delimiter=delimiter) - - def chunk(self, values, context): - chunk = [] - for item in values: - if self.delimiter.match(item, context) is NoMatch: - chunk.append(item) - else: - if chunk: # only yield if there are items in the chunk - yield chunk - chunk = [item] # start a new chunk with the delimiter - if chunk: - yield chunk - - def match(self, values, context): - chunks = self.chunk(values, context) - result = self.pattern.match(chunks, context) - if result is NoMatch: - return NoMatch - else: - return [el for lst in result for el in lst] - - -def _maybe_unwrap_capture(obj): - return obj.pattern if isinstance(obj, Capture) else obj - - -class PatternList(Slotted, Pattern): - """Pattern that matches if the respective items in a tuple match the given patterns. - - Parameters - ---------- - fields - The patterns to match the respective items in the tuple. - - """ - - __slots__ = ("patterns", "type") - patterns: tuple[Pattern, ...] - type: type - - @classmethod - def __create__(cls, patterns, type=list): - if patterns == (): - return EqualTo(patterns) - - patterns = tuple(map(pattern, patterns)) - for pat in patterns: - pat = _maybe_unwrap_capture(pat) - if isinstance(pat, (SomeOf, SomeChunksOf)): - return VariadicPatternList(patterns, type) - - return super().__create__(patterns, type) - - def __init__(self, patterns, type): - super().__init__(patterns=patterns, type=type) - - def describe(self, plural=False): - patterns = ", ".join(f.describe(plural=False) for f in self.patterns) - if plural: - return f"tuples of ({patterns})" - else: - return f"a tuple of ({patterns})" - - def match(self, values, context): - if not is_iterable(values): - return NoMatch - - if len(values) != len(self.patterns): - return NoMatch - - result = [] - for pattern, value in zip(self.patterns, values): - value = pattern.match(value, context) - if value is NoMatch: - return NoMatch - result.append(value) - - return self.type(result) - - -class VariadicPatternList(Slotted, Pattern): - __slots__ = ("patterns", "type") - patterns: tuple[Pattern, ...] - type: type - - def __init__(self, patterns, type=list): - patterns = tuple(map(pattern, patterns)) - super().__init__(patterns=patterns, type=type) - - def match(self, value, context): - if not self.patterns: - return NoMatch if value else [] - - it = RewindableIterator(value) - result = [] - - following_patterns = self.patterns[1:] + (Nothing(),) - for current, following in zip(self.patterns, following_patterns): - original = current - current = _maybe_unwrap_capture(current) - following = _maybe_unwrap_capture(following) - - if isinstance(current, (SomeOf, SomeChunksOf)): - if isinstance(following, (SomeOf, SomeChunksOf)): - following = following.delimiter - - matches = [] - while True: - it.checkpoint() - try: - item = next(it) - except StopIteration: - break - - res = following.match(item, context) - if res is NoMatch: - matches.append(item) - else: - it.rewind() - break - - res = original.match(matches, context) - if res is NoMatch: - return NoMatch - else: - result.extend(res) - else: - try: - item = next(it) - except StopIteration: - return NoMatch - - res = original.match(item, context) - if res is NoMatch: - return NoMatch - else: - result.append(res) - - return self.type(result) - - -def NoneOf(*args) -> Pattern: - """Match none of the passed patterns.""" - return Not(AnyOf(*args)) - - -def ListOf(pattern): - """Match a list of items matching the given pattern.""" - return SequenceOf(pattern, type=list) - - -def TupleOf(pattern): - """Match a variable-length tuple of items matching the given pattern.""" - return SequenceOf(pattern, type=tuple) - - -def DictOf(key_pattern, value_pattern): - """Match a dictionary with keys and values matching the given patterns.""" - return MappingOf(key_pattern, value_pattern, type=dict) - - -def FrozenDictOf(key_pattern, value_pattern): - """Match a frozendict with keys and values matching the given patterns.""" - return MappingOf(key_pattern, value_pattern, type=frozendict) - - -def pattern(obj: AnyType) -> Pattern: - """Create a pattern from various types. - - Not that if a Coercible type is passed as argument, the constructed pattern - won't attempt to coerce the value during matching. In order to allow type - coercions use `Pattern.from_typehint()` factory method. - - Parameters - ---------- - obj - The object to create a pattern from. Can be a pattern, a type, a callable, - a mapping, an iterable or a value. - - Examples - -------- - >>> assert pattern(Any()) == Any() - >>> assert pattern(int) == InstanceOf(int) - >>> - >>> @pattern - ... def as_int(x, context): - ... return int(x) - >>> - >>> assert as_int.match(1, {}) == 1 - - Returns - ------- - The constructed pattern. - - """ - if obj is Ellipsis: - return _any - elif isinstance(obj, Pattern): - return obj - elif isinstance(obj, (Deferred, Resolver)): - return Capture(obj) - elif isinstance(obj, Mapping): - return EqualTo(FrozenDict(obj)) - elif isinstance(obj, Sequence): - if isinstance(obj, (str, bytes)): - return EqualTo(obj) - else: - return PatternList(obj, type=type(obj)) - elif isinstance(obj, type): - return InstanceOf(obj) - elif get_origin(obj): - return Pattern.from_typehint(obj, allow_coercion=False) - elif callable(obj): - return Custom(obj) - else: - return EqualTo(obj) - - -def match( - pat: Pattern, value: AnyType, context: Optional[dict[str, AnyType]] = None -) -> Any: - """Match a value against a pattern. - - Parameters - ---------- - pat - The pattern to match against. - value - The value to match. - context - Arbitrary mapping of values to be used while matching. - - Returns - ------- - The matched value if the pattern matches, otherwise :obj:`NoMatch`. - - Examples - -------- - >>> assert match(Any(), 1) == 1 - >>> assert match(1, 1) == 1 - >>> assert match(1, 2) is NoMatch - >>> assert match(1, 1, context={"x": 1}) == 1 - >>> assert match(1, 2, context={"x": 1}) is NoMatch - >>> assert match([1, int], [1, 2]) == [1, 2] - >>> assert match([1, int, "a" @ InstanceOf(str)], [1, 2, "three"]) == [ - ... 1, - ... 2, - ... "three", - ... ] - - """ - if context is None: - context = {} - - pat = pattern(pat) - result = pat.match(value, context) - return NoMatch if result is NoMatch else result - - -IsTruish = Check(lambda x: bool(x)) -IsNumber = InstanceOf(numbers.Number) & ~InstanceOf(bool) -IsString = InstanceOf(str) - -As = CoercedTo -Eq = EqualTo -In = IsIn -If = Check -Some = SomeOf diff --git a/third_party/bigframes_vendored/ibis/common/selectors.py b/third_party/bigframes_vendored/ibis/common/selectors.py deleted file mode 100644 index 4bc47622549..00000000000 --- a/third_party/bigframes_vendored/ibis/common/selectors.py +++ /dev/null @@ -1,33 +0,0 @@ -# Contains code from https://github.com/ibis-project/ibis/blob/9.2.0/ibis/common/selectors.py - -from __future__ import annotations - -import abc -from typing import TYPE_CHECKING - -from bigframes_vendored.ibis.common.grounds import Concrete - -if TYPE_CHECKING: - from collections.abc import Sequence - - import bigframes_vendored.ibis.expr.types as ir - - -class Selector(Concrete): - """A column selector.""" - - @abc.abstractmethod - def expand(self, table: ir.Table) -> Sequence[ir.Value]: - """Expand `table` into value expressions that match the selector. - - Parameters - ---------- - table - An ibis table expression - - Returns - ------- - Sequence[Value] - A sequence of value expressions that match the selector - - """ diff --git a/third_party/bigframes_vendored/ibis/common/temporal.py b/third_party/bigframes_vendored/ibis/common/temporal.py deleted file mode 100644 index 68042ad51a9..00000000000 --- a/third_party/bigframes_vendored/ibis/common/temporal.py +++ /dev/null @@ -1,267 +0,0 @@ -# Contains code from https://github.com/ibis-project/ibis/blob/9.2.0/ibis/common/temporal.py - -from __future__ import annotations - -import datetime -import numbers -from decimal import Decimal -from enum import Enum, EnumMeta - -import dateutil.parser -import dateutil.tz -import pytz -from bigframes_vendored.ibis import util -from bigframes_vendored.ibis.common.bases import AbstractMeta -from bigframes_vendored.ibis.common.dispatch import lazy_singledispatch -from bigframes_vendored.ibis.common.patterns import Coercible, CoercionError -from public import public - - -class AbstractEnumMeta(EnumMeta, AbstractMeta): - pass - - -class Unit(Coercible, Enum, metaclass=AbstractEnumMeta): - @classmethod - def __coerce__(cls, value): - if isinstance(value, cls): - return value - else: - return cls.from_string(value) - - @classmethod - def from_string(cls, value): - # TODO(kszucs): perhaps this is not needed anymore - if isinstance(value, Unit): - value = value.value - elif not isinstance(value, str): - raise CoercionError(f"Unable to coerce {value} to {cls.__name__}") - - # first look for aliases - value = cls.aliases().get(value, value) - - # then look for the enum value (unit value) - try: - return cls(value) - except ValueError: - pass - - # then look for the enum name (unit name) - if value.endswith("s"): - value = value[:-1] - try: - return cls[value.upper()] - except KeyError: - raise CoercionError(f"Unable to coerce {value} to {cls.__name__}") - - @classmethod - def aliases(cls): - return {} - - @property - def singular(self) -> str: - return self.name.lower() - - @property - def plural(self) -> str: - return self.singular + "s" - - @property - def short(self) -> str: - return self.value - - -class TemporalUnit(Unit): - @classmethod - def aliases(cls): - return { - "d": "D", - "H": "h", - "HH24": "h", - "J": "D", - "MI": "m", - "q": "Q", - "SYYYY": "Y", - "w": "W", - "y": "Y", - "YY": "Y", - "YYY": "Y", - "YYYY": "Y", - } - - -@public -class DateUnit(TemporalUnit): - YEAR = "Y" - QUARTER = "Q" - MONTH = "M" - WEEK = "W" - DAY = "D" - - -@public -class TimeUnit(TemporalUnit): - HOUR = "h" - MINUTE = "m" - SECOND = "s" - MILLISECOND = "ms" - MICROSECOND = "us" - NANOSECOND = "ns" - - -@public -class TimestampUnit(TemporalUnit): - SECOND = "s" - MILLISECOND = "ms" - MICROSECOND = "us" - NANOSECOND = "ns" - - -@public -class IntervalUnit(TemporalUnit): - YEAR = "Y" - QUARTER = "Q" - MONTH = "M" - WEEK = "W" - DAY = "D" - HOUR = "h" - MINUTE = "m" - SECOND = "s" - MILLISECOND = "ms" - MICROSECOND = "us" - NANOSECOND = "ns" - - def is_date(self) -> bool: - return self.name in DateUnit.__members__ - - def is_time(self) -> bool: - return self.name in TimeUnit.__members__ - - -def normalize_timedelta( - value: datetime.timedelta | numbers.Real, unit: IntervalUnit -) -> datetime.timedelta: - """Normalize a timedelta value to the given unit. - - Parameters - ---------- - value - The value to normalize, either a timedelta or a number. - unit - The unit to normalize to. - - Returns - ------- - The normalized timedelta value. - - Examples - -------- - >>> from datetime import timedelta - >>> normalize_timedelta(1, IntervalUnit.SECOND) - 1 - >>> normalize_timedelta(1, IntervalUnit.DAY) - 1 - >>> normalize_timedelta(timedelta(days=14), IntervalUnit.WEEK) - 2 - >>> normalize_timedelta(timedelta(seconds=3), IntervalUnit.MILLISECOND) - 3000 - >>> normalize_timedelta(timedelta(seconds=3), IntervalUnit.MICROSECOND) - 3000000 - - """ - if isinstance(value, datetime.timedelta): - # datetime.timedelta only stores days, seconds, and microseconds internally - if value.days and not (value.seconds or value.microseconds): - value = util.convert_unit(value.days, "D", unit.short, floor=False) - else: - total_seconds = Decimal(str(value.total_seconds())) - value = util.convert_unit(total_seconds, "s", unit.short, floor=False) - else: - value = Decimal(value) - - # check that value is integral - if value % 1 != 0: - raise ValueError(f"Normalizing {value} to {unit} would lose precision") - - return int(value) - - -def normalize_timezone(tz): - if tz is None: - return None - elif isinstance(tz, str): - if tz == "UTC": - return dateutil.tz.tzutc() - else: - return dateutil.tz.gettz(tz) - elif isinstance(tz, (int, float)): - return datetime.timezone(datetime.timedelta(hours=tz)) - elif isinstance(tz, (dateutil.tz.tzoffset, pytz._FixedOffset)): - # this way we have a proper tzname() output, e.g. "UTC+01:00" - return datetime.timezone(tz.utcoffset(None)) - elif isinstance(tz, datetime.tzinfo): - return tz - else: - raise TypeError(f"Unable to normalize {type(tz)} to timezone") - - -@lazy_singledispatch -def normalize_datetime(value): - raise TypeError(f"Unable to normalize {type(value)} to timestamp") - - -@normalize_datetime.register(str) -def _from_str(value): - lower = value.lower() - if lower == "now": - return datetime.datetime.now() - elif lower == "today": - return datetime.datetime.today() - - try: - value = dateutil.parser.parse(value) - except dateutil.parser.ParserError: - raise TypeError(f"Unable to normalize {value} to timestamp") - return value.replace(tzinfo=normalize_timezone(value.tzinfo)) - - -@normalize_datetime.register(numbers.Number) -def _from_number(value): - return datetime.datetime.fromtimestamp(value, dateutil.tz.UTC) - - -@normalize_datetime.register(datetime.time) -def _from_time(value): - return datetime.datetime.combine(datetime.date.today(), value) - - -@normalize_datetime.register(datetime.date) -def _from_date(value): - return datetime.datetime(year=value.year, month=value.month, day=value.day) - - -@normalize_datetime.register(datetime.datetime) -def _from_datetime(value): - return value.replace(tzinfo=normalize_timezone(value.tzinfo)) - - -@normalize_datetime.register("pandas.Timestamp") -def _from_pandas_timestamp(value): - # TODO(kszucs): it would make sense to preserve nanoseconds precision by - # keeping the pandas.Timestamp object - return value.to_pydatetime() - - -@normalize_datetime.register("numpy.datetime64") -def _from_numpy_datetime64(value): - try: - import pandas as pd - except ImportError: - raise TypeError("Unable to convert np.datetime64 without pandas") - else: - return pd.Timestamp(value).to_pydatetime() - - -@normalize_datetime.register("pyarrow.Scalar") -def _from_pyarrow_scalar(value): - return value.as_py() diff --git a/third_party/bigframes_vendored/ibis/common/typing.py b/third_party/bigframes_vendored/ibis/common/typing.py deleted file mode 100644 index c0c8ff3928c..00000000000 --- a/third_party/bigframes_vendored/ibis/common/typing.py +++ /dev/null @@ -1,281 +0,0 @@ -# Contains code from https://github.com/ibis-project/ibis/blob/9.2.0/ibis/common/typing.py - -from __future__ import annotations - -import inspect -import re -import sys -from abc import abstractmethod -from itertools import zip_longest -from typing import TYPE_CHECKING, Any, Optional, TypeVar, Union, get_args, get_origin -from typing import get_type_hints as _get_type_hints - -from bigframes_vendored.ibis.common.bases import Abstract -from bigframes_vendored.ibis.common.caching import memoize - -if TYPE_CHECKING: - from typing_extensions import Self - -if sys.version_info >= (3, 10): - from types import UnionType - from typing import TypeAlias - - # Keep this alias in sync with unittest.case._ClassInfo - _ClassInfo: TypeAlias = type | UnionType | tuple["_ClassInfo", ...] -else: - from typing_extensions import TypeAlias - - UnionType = object() - _ClassInfo: TypeAlias = Union[type, tuple["_ClassInfo", ...]] - - -T = TypeVar("T") -U = TypeVar("U") - -Namespace = dict[str, Any] -VarTuple = tuple[T, ...] - - -@memoize -def get_type_hints( - obj: Any, - include_extras: bool = True, - include_properties: bool = False, -) -> dict[str, Any]: - """Get type hints for a callable or class. - - Extension of typing.get_type_hints that supports getting type hints for - class properties. - - Parameters - ---------- - obj - Callable or class to get type hints for. - include_extras - Whether to include extra type hints such as Annotated. - include_properties - Whether to include type hints for class properties. - - Returns - ------- - Mapping of parameter or attribute name to type hint. - - """ - try: - hints = _get_type_hints(obj, include_extras=include_extras) - except TypeError: - return {} - - if include_properties: - for name in dir(obj): - attr = getattr(obj, name) - if isinstance(attr, property): - annots = _get_type_hints(attr.fget, include_extras=include_extras) - if return_annot := annots.get("return"): - hints[name] = return_annot - - return hints - - -@memoize -def get_type_params(obj: Any) -> dict[str, type]: - """Get type parameters for a generic class. - - Parameters - ---------- - obj - Generic class to get type parameters for. - - Returns - ------- - Mapping of type parameter name to type. - - Examples - -------- - >>> from typing import Dict, List - >>> class MyList(List[T]): ... - >>> get_type_params(MyList[int]) - {'T': } - >>> class MyDict(Dict[T, U]): ... - >>> get_type_params(MyDict[int, str]) - {'T': , 'U': } - - """ - args = get_args(obj) - origin = get_origin(obj) or obj - bases = getattr(origin, "__orig_bases__", ()) - params = getattr(origin, "__parameters__", ()) - - result = {} - for base in bases: - result.update(get_type_params(base)) - - param_names = (p.__name__ for p in params) - result.update(zip(param_names, args)) - - return result - - -@memoize -def get_bound_typevars(obj: Any) -> dict[TypeVar, tuple[str, type]]: - """Get type variables bound to concrete types for a generic class. - - Parameters - ---------- - obj - Generic class to get type variables for. - - Returns - ------- - Mapping of type variable to attribute name and type. - - Examples - -------- - >>> from typing import Generic - >>> class MyStruct(Generic[T, U]): - ... a: T - ... b: U - >>> get_bound_typevars(MyStruct[int, str]) - {~T: ('a', ), ~U: ('b', )} - >>> - >>> class MyStruct(Generic[T, U]): - ... a: T - ... - ... @property - ... def myprop(self) -> U: ... - >>> get_bound_typevars(MyStruct[float, bytes]) - {~T: ('a', ), ~U: ('myprop', )} - - """ - origin = get_origin(obj) or obj - hints = get_type_hints(origin, include_properties=True) - params = get_type_params(obj) - - result = {} - for attr, typ in hints.items(): - if isinstance(typ, TypeVar): - result[typ] = (attr, params[typ.__name__]) - return result - - -def evaluate_annotations( - annots: dict[str, str], - module_name: str, - class_name: Optional[str] = None, - best_effort: bool = False, -) -> dict[str, Any]: - """Evaluate type annotations that are strings. - - Parameters - ---------- - annots - Type annotations to evaluate. - module_name - The name of the module that the annotations are defined in, hence - providing global scope. - class_name - The name of the class that the annotations are defined in, hence - providing Self type. - best_effort - Whether to ignore errors when evaluating type annotations. - - Returns - ------- - Actual type hints. - - Examples - -------- - >>> annots = {"a": "dict[str, float]", "b": "int"} - >>> evaluate_annotations(annots, __name__) - {'a': dict[str, float], 'b': } - - """ - module = sys.modules.get(module_name, None) - globalns = getattr(module, "__dict__", None) - if class_name is None: - localns = None - else: - localns = dict(Self=f"{module_name}.{class_name}") - - result = {} - for k, v in annots.items(): - if isinstance(v, str): - try: - v = eval(v, globalns, localns) # noqa: S307 - except NameError: - if not best_effort: - raise - result[k] = v - - return result - - -def format_typehint(typ: Any) -> str: - if isinstance(typ, type): - return typ.__name__ - elif isinstance(typ, TypeVar): - if typ.__bound__ is None: - return str(typ) - else: - return format_typehint(typ.__bound__) - else: - # remove the module name from the typehint, including generics - return re.sub(r"(\w+\.)+", "", str(typ)) - - -class DefaultTypeVars: - """Enable using default type variables in generic classes (PEP-0696).""" - - __slots__ = () - - def __class_getitem__(cls, params): - params = params if isinstance(params, tuple) else (params,) - pairs = zip_longest(params, cls.__parameters__) - params = tuple(p.__default__ if t is None else t for t, p in pairs) - return super().__class_getitem__(params) - - -class Sentinel(type): - """Create type-annotable unique objects.""" - - def __new__(cls, name, bases, namespace, **kwargs): - if bases: - raise TypeError("Sentinels cannot be subclassed") - return super().__new__(cls, name, bases, namespace, **kwargs) - - def __call__(self, *args: Any, **kwargs: Any) -> Any: - raise TypeError("Sentinels are not constructible") - - -class CoercionError(Exception): ... - - -class Coercible(Abstract): - """Protocol for defining coercible types. - - Coercible types define a special ``__coerce__`` method that accepts an object - with an instance of the type. Used in conjunction with the ``coerced_to`` - pattern to coerce arguments to a specific type. - """ - - @classmethod - @abstractmethod - def __coerce__(cls, value: Any, **kwargs: Any) -> Self: ... - - -def get_defining_frame(obj): - """Locate the outermost frame where `obj` is defined.""" - for frame_info in inspect.stack()[::-1]: - for var in frame_info.frame.f_locals.values(): - if obj is var: - return frame_info.frame - raise ValueError(f"No defining frame found for {obj}") - - -def get_defining_scope(obj, types=None): - """Get variables in the scope where `expr` is first defined.""" - frame = get_defining_frame(obj) - scope = {**frame.f_globals, **frame.f_locals} - if types is not None: - scope = {k: v for k, v in scope.items() if isinstance(v, types)} - return scope diff --git a/third_party/bigframes_vendored/ibis/config.py b/third_party/bigframes_vendored/ibis/config.py deleted file mode 100644 index 8c2b0b1c718..00000000000 --- a/third_party/bigframes_vendored/ibis/config.py +++ /dev/null @@ -1,192 +0,0 @@ -# Contains code from https://github.com/ibis-project/ibis/blob/9.2.0/ibis/config.py - -from __future__ import annotations - -import contextlib -from collections.abc import Callable # noqa: TCH003 -from typing import Annotated, Any, Optional - -import bigframes_vendored.ibis.common.exceptions as com -from bigframes_vendored.ibis.common.grounds import Annotable -from bigframes_vendored.ibis.common.patterns import Between -from public import public - -PosInt = Annotated[int, Between(lower=0)] - - -class Config(Annotable): - def get(self, key: str) -> Any: - value = self - for field in key.split("."): - value = getattr(value, field) - return value - - def set(self, key: str, value: Any) -> None: - *prefix, key = key.split(".") - conf = self - for field in prefix: - conf = getattr(conf, field) - setattr(conf, key, value) - - @contextlib.contextmanager - def _with_temporary(self, options): - try: - old = {} - for key, value in options.items(): - old[key] = self.get(key) - self.set(key, value) - yield - finally: - for key, value in old.items(): - self.set(key, value) - - def __call__(self, options): - return self._with_temporary(options) - - -class SQL(Config): - """SQL-related options. - - Attributes - ---------- - fuse_selects : bool - Whether to fuse consecutive select queries into a single query where - possible. - default_limit : int | None - Number of rows to be retrieved for a table expression without an - explicit limit. [](`None`) means no limit. - default_dialect : str - Dialect to use for printing SQL when the backend cannot be determined. - - """ - - fuse_selects: bool = True - default_limit: Optional[PosInt] = None - default_dialect: str = "duckdb" - - -class Interactive(Config): - """Options controlling the interactive repr. - - Attributes - ---------- - max_rows : int - Maximum rows to pretty print. - max_columns : int | None - The maximum number of columns to pretty print. If 0 (the default), the - number of columns will be inferred from output console size. Set to - `None` for no limit. - max_length : int - Maximum length for pretty-printed arrays and maps. - max_string : int - Maximum length for pretty-printed strings. - max_depth : int - Maximum depth for nested data types. - show_types : bool - Show the inferred type of value expressions in the interactive repr. - - """ - - max_rows: int = 10 - max_columns: Optional[int] = 0 - max_length: int = 2 - max_string: int = 80 - max_depth: int = 1 - show_types: bool = True - - -class Repr(Config): - """Expression printing options. - - Attributes - ---------- - depth : int - The maximum number of expression nodes to print when repring. - table_columns : int - The number of columns to show in leaf table expressions. - table_rows : int - The number of rows to show for in memory tables. - query_text_length : int - The maximum number of characters to show in the `query` field repr of - SQLQueryResult operations. - show_types : bool - Show the inferred type of value expressions in the repr. - show_variables : bool - Show the variables in the repr instead of generated names. This is - an advanced option and may not work in all scenarios. - interactive : bool - Options controlling the interactive repr. - """ - - depth: Optional[PosInt] = None - table_columns: Optional[PosInt] = None - table_rows: PosInt = 10 - query_text_length: PosInt = 80 - show_types: bool = False - show_variables: bool = False - interactive: Interactive = Interactive() - - -class Options(Config): - """Ibis configuration options. - - Attributes - ---------- - interactive : bool - Show the first few rows of computing an expression when in a repl. - repr : Repr - Options controlling expression printing. - verbose : bool - Run in verbose mode if [](`True`) - verbose_log: Callable[[str], None] | None - A callable to use when logging. - graphviz_repr : bool - Render expressions as GraphViz PNGs when running in a Jupyter notebook. - default_backend : Optional[ibis.backends.BaseBackend] - The default backend to use for execution, defaults to DuckDB if not - set. - sql: SQL - SQL-related options. - clickhouse : Config | None - Clickhouse specific options. - dask : Config | None - Dask specific options. - impala : Config | None - Impala specific options. - pandas : Config | None - Pandas specific options. - pyspark : Config | None - PySpark specific options. - - """ - - interactive: bool = False - repr: Repr = Repr() - verbose: bool = False - verbose_log: Optional[Callable] = None - graphviz_repr: bool = False - default_backend: Optional[Any] = None - sql: SQL = SQL() - clickhouse: Optional[Config] = None - dask: Optional[Config] = None - impala: Optional[Config] = None - pandas: Optional[Config] = None - pyspark: Optional[Config] = None - - -def _default_backend() -> Any: - if (backend := options.default_backend) is not None: - return backend - - raise com.IbisError("You must speficy an backend to use") - - -options = Options() - - -@public -def option_context(key, new_value): - return options({key: new_value}) - - -public(options=options) diff --git a/third_party/bigframes_vendored/ibis/expr/api.py b/third_party/bigframes_vendored/ibis/expr/api.py deleted file mode 100644 index 953ecb2979f..00000000000 --- a/third_party/bigframes_vendored/ibis/expr/api.py +++ /dev/null @@ -1,2473 +0,0 @@ -# Contains code from https://github.com/ibis-project/ibis/blob/9.2.0/ibis/expr/api.py - -"""Ibis expression API definitions.""" - -from __future__ import annotations - -import builtins -import datetime -import functools -import itertools -import numbers -import operator -from collections import Counter -from typing import TYPE_CHECKING, Any, overload - -import bigframes_vendored.ibis.expr.builders as bl -import bigframes_vendored.ibis.expr.datatypes as dt -import bigframes_vendored.ibis.expr.operations as ops -import bigframes_vendored.ibis.expr.schema as sch -import bigframes_vendored.ibis.expr.types as ir -from bigframes_vendored.ibis import selectors, util -from bigframes_vendored.ibis.backends import BaseBackend, connect -from bigframes_vendored.ibis.common.deferred import Deferred, _, deferrable -from bigframes_vendored.ibis.common.dispatch import lazy_singledispatch -from bigframes_vendored.ibis.common.exceptions import IbisInputError -from bigframes_vendored.ibis.common.grounds import Concrete -from bigframes_vendored.ibis.common.temporal import ( - normalize_datetime, - normalize_timezone, -) -from bigframes_vendored.ibis.expr.decompile import decompile -from bigframes_vendored.ibis.expr.schema import Schema -from bigframes_vendored.ibis.expr.sql import parse_sql, to_sql -from bigframes_vendored.ibis.expr.types import ( - Column, - DateValue, - Expr, - Scalar, - Table, - TimestampValue, - TimeValue, - Value, - array, - literal, - map, - null, - struct, -) -from bigframes_vendored.ibis.util import experimental - -if TYPE_CHECKING: - from collections.abc import Iterable, Sequence - from pathlib import Path - - import pandas as pd - import polars as pl - import pyarrow as pa - from bigframes_vendored.ibis.expr.schema import SchemaLike - -__all__ = ( - "Column", - "Deferred", - "Expr", - "Scalar", - "Schema", - "Table", - "Value", - "_", - "aggregate", - "and_", - "array", - "asc", - "case", - "coalesce", - "connect", - "cross_join", - "cume_dist", - "cumulative_window", - "date", - "decompile", - "deferred", - "dense_rank", - "desc", - "difference", - "dtype", - "e", - "following", - "geo_area", - "geo_as_binary", - "geo_as_ewkb", - "geo_as_ewkt", - "geo_as_text", - "geo_azimuth", - "geo_buffer", - "geo_centroid", - "geo_contains", - "geo_contains_properly", - "geo_covered_by", - "geo_covers", - "geo_crosses", - "geo_d_fully_within", - "geo_d_within", - "geo_difference", - "geo_disjoint", - "geo_distance", - "geo_end_point", - "geo_envelope", - "geo_equals", - "geo_geometry_n", - "geo_geometry_type", - "geo_intersection", - "geo_intersects", - "geo_is_valid", - "geo_length", - "geo_line_locate_point", - "geo_line_merge", - "geo_line_substring", - "geo_max_distance", - "geo_n_points", - "geo_n_rings", - "geo_ordering_equals", - "geo_overlaps", - "geo_perimeter", - "geo_point", - "geo_point_n", - "geo_simplify", - "geo_srid", - "geo_start_point", - "geo_touches", - "geo_transform", - "geo_unary_union", - "geo_union", - "geo_within", - "geo_x", - "geo_x_max", - "geo_x_min", - "geo_y", - "geo_y_max", - "geo_y_min", - "get_backend", - "greatest", - "ifelse", - "infer_dtype", - "infer_schema", - "intersect", - "interval", - "join", - "least", - "literal", - "map", - "memtable", - "negate", - "now", - "ntile", - "null", - "or_", - "param", - "parse_sql", - "percent_rank", - "pi", - "preceding", - "random", - "range", - "range_window", - "rank", - "read_csv", - "read_delta", - "read_json", - "read_parquet", - "row_number", - "rows_window", - "schema", - "selectors", - "set_backend", - "struct", - "table", - "time", - "timestamp", - "to_sql", - "today", - "trailing_range_window", - "trailing_window", - "union", - "uuid", - "watermark", - "where", - "window", -) - - -dtype = dt.dtype -infer_dtype = dt.infer -infer_schema = sch.infer -aggregate = ir.Table.aggregate -cross_join = ir.Table.cross_join -join = ir.Table.join -asof_join = ir.Table.asof_join - -e = ops.E().to_expr() -pi = ops.Pi().to_expr() - - -deferred = _ -"""Deferred expression object. - -Use this object to refer to a previous table expression in a chain of -expressions. - -::: {.callout-note} -## `_` may conflict with other idioms in Python - -See https://github.com/ibis-project/ibis/issues/4704 for details. - -Use `from ibis import deferred as ` to assign a different name to -the deferred object builder. - -Another option is to use `ibis._` directly. -::: - -Examples --------- ->>> from ibis import _ ->>> t = ibis.table(dict(key="int", value="float"), name="t") ->>> expr = t.group_by(key=_.key - 1).agg(total=_.value.sum()) ->>> expr.schema() -ibis.Schema { - key int64 - total float64 -} -""" - - -def param(type: dt.DataType) -> ir.Scalar: - """Create a deferred parameter of a given type. - - Parameters - ---------- - type - The type of the unbound parameter, e.g., double, int64, date, etc. - - Returns - ------- - Scalar - A scalar expression backend by a parameter - - Examples - -------- - >>> from datetime import date - >>> import ibis - >>> start = ibis.param("date") - >>> t = ibis.memtable( - ... { - ... "date_col": [date(2013, 1, 1), date(2013, 1, 2), date(2013, 1, 3)], - ... "value": [1.0, 2.0, 3.0], - ... }, - ... ) - >>> expr = t.filter(t.date_col >= start).value.sum() - >>> expr.execute(params={start: date(2013, 1, 1)}) - np.float64(6.0) - >>> expr.execute(params={start: date(2013, 1, 2)}) - np.float64(5.0) - >>> expr.execute(params={start: date(2013, 1, 3)}) - np.float64(3.0) - """ - return ops.ScalarParameter(type).to_expr() - - -def schema( - pairs: SchemaLike | None = None, - names: Iterable[str] | None = None, - types: Iterable[str | dt.DataType] | None = None, -) -> sch.Schema: - """Validate and return a [`Schema`](./schemas.qmd#ibis.expr.schema.Schema) object. - - Parameters - ---------- - pairs - List or dictionary of name, type pairs. Mutually exclusive with `names` - and `types` arguments. - names - Field names. Mutually exclusive with `pairs`. - types - Field types. Mutually exclusive with `pairs`. - - Returns - ------- - Schema - An ibis schema - - Examples - -------- - >>> from ibis import schema, Schema - >>> sc = schema([("foo", "string"), ("bar", "int64"), ("baz", "boolean")]) - >>> sc = schema(names=["foo", "bar", "baz"], types=["string", "int64", "boolean"]) - >>> sc = schema(dict(foo="string")) - >>> sc = schema(Schema(dict(foo="string"))) # no-op - - """ - if pairs is not None: - return sch.schema(pairs) - - # validate lengths of names and types are the same - if len(names) != len(types): - raise ValueError("Schema names and types must have the same length") - - return sch.Schema.from_tuples(zip(names, types)) - - -_table_names = (f"unbound_table_{i:d}" for i in itertools.count()) - - -def table( - schema: SchemaLike | None = None, - name: str | None = None, - catalog: str | None = None, - database: str | None = None, -) -> ir.Table: - """Create a table literal or an abstract table without data. - - Ibis uses the word database to refer to a collection of tables, and the word - catalog to refer to a collection of databases. You can use a combination of - `catalog` and `database` to specify a hierarchical location for table. - - Parameters - ---------- - schema - A schema for the table - name - Name for the table. One is generated if this value is `None`. - catalog - A collection of database. - database - A collection of tables. Required if catalog is not `None`. - - Returns - ------- - Table - A table expression - - Examples - -------- - Create a table with no data backing it - - >>> import ibis - >>> ibis.options.interactive = False - >>> t = ibis.table(schema=dict(a="int", b="string"), name="t") - >>> t - UnboundTable: t - a int64 - b string - - - Create a table with no data backing it in a specific location - - >>> import ibis - >>> ibis.options.interactive = False - >>> t = ibis.table(schema=dict(a="int"), name="t", catalog="cat", database="db") - >>> t - UnboundTable: cat.db.t - a int64 - """ - if name is None: - if isinstance(schema, type): - name = schema.__name__ - else: - name = next(_table_names) - if catalog is not None and database is None: - raise ValueError( - "A catalog-only namespace is invalid in Ibis, " - "please specify a database as well." - ) - - return ops.UnboundTable( - name=name, - schema=schema, - namespace=ops.Namespace(catalog=catalog, database=database), - ).to_expr() - - -def memtable( - data, - *, - columns: Iterable[str] | None = None, - schema: SchemaLike | None = None, - name: str | None = None, -) -> Table: - """Construct an ibis table expression from in-memory data. - - Parameters - ---------- - data - A table-like object (`pandas.DataFrame`, `pyarrow.Table`, or - `polars.DataFrame`), or any data accepted by the `pandas.DataFrame` - constructor (e.g. a list of dicts). - - Note that ibis objects (e.g. `MapValue`) may not be passed in as part - of `data` and will result in an error. - - Do not depend on the underlying storage type (e.g., pyarrow.Table), - it's subject to change across non-major releases. - columns - Optional [](`typing.Iterable`) of [](`str`) column names. If provided, - must match the number of columns in `data`. - schema - Optional [`Schema`](./schemas.qmd#ibis.expr.schema.Schema). - The functions use `data` to infer a schema if not passed. - name - Optional name of the table. - - Returns - ------- - Table - A table expression backed by in-memory data. - - Examples - -------- - >>> import ibis - >>> t = ibis.memtable([{"a": 1}, {"a": 2}]) - >>> t - InMemoryTable - data: - PandasDataFrameProxy: - a - 0 1 - 1 2 - - >>> t = ibis.memtable([{"a": 1, "b": "foo"}, {"a": 2, "b": "baz"}]) - >>> t - InMemoryTable - data: - PandasDataFrameProxy: - a b - 0 1 foo - 1 2 baz - - Create a table literal without column names embedded in the data and pass - `columns` - - >>> t = ibis.memtable([(1, "foo"), (2, "baz")], columns=["a", "b"]) - >>> t - InMemoryTable - data: - PandasDataFrameProxy: - a b - 0 1 foo - 1 2 baz - - Create a table literal without column names embedded in the data. Ibis - generates column names if none are provided. - - >>> t = ibis.memtable([(1, "foo"), (2, "baz")]) - >>> t - InMemoryTable - data: - PandasDataFrameProxy: - col0 col1 - 0 1 foo - 1 2 baz - - """ - if columns is not None and schema is not None: - raise NotImplementedError( - "passing `columns` and schema` is ambiguous; " - "pass one or the other but not both" - ) - return _memtable(data, name=name, schema=schema, columns=columns) - - -@lazy_singledispatch -def _memtable( - data: pd.DataFrame | Any, - *, - columns: Iterable[str] | None = None, - schema: SchemaLike | None = None, - name: str | None = None, -) -> Table: - import pandas as pd - from bigframes_vendored.ibis.formats.pandas import PandasDataFrameProxy - - if not isinstance(data, pd.DataFrame): - df = pd.DataFrame(data, columns=columns) - else: - df = data - - if df.columns.inferred_type != "string": - cols = df.columns - newcols = getattr( - schema, - "names", - (f"col{i:d}" for i in builtins.range(len(cols))), - ) - df = df.rename(columns=dict(zip(cols, newcols))) - - if columns is not None: - if (provided_col := len(columns)) != (exist_col := len(df.columns)): - raise ValueError( - "Provided `columns` must have an entry for each column in `data`.\n" - f"`columns` has {provided_col} elements but `data` has {exist_col} columns." - ) - - df = df.rename(columns=dict(zip(df.columns, columns))) - - # verify that the DataFrame has no duplicate column names because ibis - # doesn't allow that - cols = df.columns - dupes = [name for name, count in Counter(cols).items() if count > 1] - if dupes: - raise IbisInputError( - f"Duplicate column names found in DataFrame when constructing memtable: {dupes}" - ) - - op = ops.InMemoryTable( - name=name if name is not None else util.gen_name("pandas_memtable"), - schema=sch.infer(df) if schema is None else schema, - data=PandasDataFrameProxy(df), - ) - return op.to_expr() - - -@_memtable.register("pyarrow.Table") -def _memtable_from_pyarrow_table( - data: pa.Table, - *, - name: str | None = None, - schema: SchemaLike | None = None, - columns: Iterable[str] | None = None, -): - from bigframes_vendored.ibis.formats.pyarrow import PyArrowTableProxy - - if columns is not None: - assert schema is None, "if `columns` is not `None` then `schema` must be `None`" - schema = sch.Schema(dict(zip(columns, sch.infer(data).values()))) - return ops.InMemoryTable( - name=name if name is not None else util.gen_name("pyarrow_memtable"), - schema=sch.infer(data) if schema is None else schema, - data=PyArrowTableProxy(data), - ).to_expr() - - -@_memtable.register("polars.LazyFrame") -def _memtable_from_polars_lazyframe(data: pl.LazyFrame, **kwargs): - return _memtable_from_polars_dataframe(data.collect(), **kwargs) - - -@_memtable.register("polars.DataFrame") -def _memtable_from_polars_dataframe( - data: pl.DataFrame, - *, - name: str | None = None, - schema: SchemaLike | None = None, - columns: Iterable[str] | None = None, -): - from bigframes_vendored.ibis.formats.polars import PolarsDataFrameProxy - - if columns is not None: - assert schema is None, "if `columns` is not `None` then `schema` must be `None`" - schema = sch.Schema(dict(zip(columns, sch.infer(data).values()))) - return ops.InMemoryTable( - name=name if name is not None else util.gen_name("polars_memtable"), - schema=sch.infer(data) if schema is None else schema, - data=PolarsDataFrameProxy(data), - ).to_expr() - - -def _deferred_method_call(expr, method_name, **kwargs): - method = operator.methodcaller(method_name, **kwargs) - if isinstance(expr, str): - value = _[expr] - elif isinstance(expr, Deferred): - value = expr - elif callable(expr): - value = expr(_) - else: - value = expr - return method(value) - - -def desc(expr: ir.Column | str, nulls_first: bool = False) -> ir.Value: - """Create a descending sort key from `expr` or column name. - - Parameters - ---------- - expr - The expression or column name to use for sorting - nulls_first - Bool to indicate weather to put NULL values first or not. - - See Also - -------- - [`Value.desc()`](./expression-generic.qmd#ibis.expr.types.generic.Value.desc) - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.examples.penguins.fetch() - >>> t[["species", "year"]].order_by(ibis.desc("year")).head() - ┏━━━━━━━━━┳━━━━━━━┓ - ┃ species ┃ year ┃ - ┡━━━━━━━━━╇━━━━━━━┩ - │ string │ int64 │ - ├─────────┼───────┤ - │ Adelie │ 2009 │ - │ Adelie │ 2009 │ - │ Adelie │ 2009 │ - │ Adelie │ 2009 │ - │ Adelie │ 2009 │ - └─────────┴───────┘ - - Returns - ------- - ir.ValueExpr - An expression - - """ - return _deferred_method_call(expr, "desc", nulls_first=nulls_first) - - -def asc(expr: ir.Column | str, nulls_first: bool = False) -> ir.Value: - """Create a ascending sort key from `asc` or column name. - - Parameters - ---------- - expr - The expression or column name to use for sorting - nulls_first - Bool to indicate weather to put NULL values first or not. - - See Also - -------- - [`Value.asc()`](./expression-generic.qmd#ibis.expr.types.generic.Value.asc) - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.examples.penguins.fetch() - >>> t[["species", "year"]].order_by(ibis.asc("year")).head() - ┏━━━━━━━━━┳━━━━━━━┓ - ┃ species ┃ year ┃ - ┡━━━━━━━━━╇━━━━━━━┩ - │ string │ int64 │ - ├─────────┼───────┤ - │ Adelie │ 2007 │ - │ Adelie │ 2007 │ - │ Adelie │ 2007 │ - │ Adelie │ 2007 │ - │ Adelie │ 2007 │ - └─────────┴───────┘ - - Returns - ------- - ir.ValueExpr - An expression - - """ - return _deferred_method_call(expr, "asc", nulls_first=nulls_first) - - -def preceding(value) -> ir.Value: - return ops.WindowBoundary(value, preceding=True).to_expr() - - -def following(value) -> ir.Value: - return ops.WindowBoundary(value, preceding=False).to_expr() - - -def and_(*predicates: ir.BooleanValue) -> ir.BooleanValue: - """Combine multiple predicates using `&`. - - Parameters - ---------- - predicates - Boolean value expressions - - Returns - ------- - BooleanValue - A new predicate that evaluates to True if all composing predicates are - True. If no predicates were provided, returns True. - - """ - if not predicates: - return literal(True) - return functools.reduce(operator.and_, predicates) - - -def or_(*predicates: ir.BooleanValue) -> ir.BooleanValue: - """Combine multiple predicates using `|`. - - Parameters - ---------- - predicates - Boolean value expressions - - Returns - ------- - BooleanValue - A new predicate that evaluates to True if any composing predicates are - True. If no predicates were provided, returns False. - - """ - if not predicates: - return literal(False) - return functools.reduce(operator.or_, predicates) - - -def random() -> ir.FloatingScalar: - """Return a random floating point number in the range [0.0, 1.0). - - Similar to [](`random.random`) in the Python standard library. - - ::: {.callout-note} - ## Repeated use of `random` - - `ibis.random()` will generate a column of distinct random numbers even if - the same instance of `ibis.random()` is re-used. - - When Ibis compiles an expression to SQL, each place where `random` is used - will render as a separate call to the given backend's random number - generator. - - ```python - >>> from ibis.interactive import * - >>> t = ibis.memtable({"a": range(5)}) - >>> r_a = ibis.random() - >>> t.mutate(random_1=r_a, random_2=r_a) # doctest: +SKIP - ┏━━━━━━━┳━━━━━━━━━━┳━━━━━━━━━━┓ - ┃ a ┃ random_1 ┃ random_2 ┃ - ┡━━━━━━━╇━━━━━━━━━━╇━━━━━━━━━━┩ - │ int64 │ float64 │ float64 │ - ├───────┼──────────┼──────────┤ - │ 0 │ 0.191130 │ 0.098715 │ - │ 1 │ 0.255262 │ 0.828454 │ - │ 2 │ 0.011804 │ 0.392275 │ - │ 3 │ 0.309941 │ 0.347300 │ - │ 4 │ 0.482783 │ 0.095562 │ - └───────┴──────────┴──────────┘ - ``` - ::: - - Returns - ------- - FloatingScalar - Random float value expression - - """ - return ops.RandomScalar().to_expr() - - -def uuid() -> ir.UUIDScalar: - """Return a random UUID version 4 value. - - Similar to [('uuid.uuid4`) in the Python standard library. - - Examples - -------- - >>> from ibis.interactive import * - >>> ibis.uuid() # doctest: +SKIP - UUID('e57e927b-aed2-483b-9140-dc32a26cad95') - - Returns - ------- - UUIDScalar - Random UUID value expression - """ - return ops.RandomUUID().to_expr() - - -@overload -def timestamp( - value_or_year: int | ir.IntegerValue | Deferred, - month: int | ir.IntegerValue | Deferred, - day: int | ir.IntegerValue | Deferred, - hour: int | ir.IntegerValue | Deferred, - minute: int | ir.IntegerValue | Deferred, - second: int | ir.IntegerValue | Deferred, - /, - timezone: str | None = None, -) -> TimestampValue: ... - - -@overload -def timestamp(value_or_year: Any, /, timezone: str | None = None) -> TimestampValue: ... - - -@deferrable -def timestamp( - value_or_year, - month=None, - day=None, - hour=None, - minute=None, - second=None, - /, - timezone=None, -): - """Construct a timestamp scalar or column. - - Parameters - ---------- - value_or_year - Either a string value or `datetime.datetime` to coerce to a timestamp, - or an integral value representing the timestamp year component. - month - The timestamp month component; required if `value_or_year` is a year. - day - The timestamp day component; required if `value_or_year` is a year. - hour - The timestamp hour component; required if `value_or_year` is a year. - minute - The timestamp minute component; required if `value_or_year` is a year. - second - The timestamp second component; required if `value_or_year` is a year. - timezone - The timezone name, or none for a timezone-naive timestamp. - - Returns - ------- - TimestampValue - A timestamp expression - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - - Create a timestamp scalar from a string - - >>> ibis.timestamp("2023-01-02T03:04:05") - ┌──────────────────────────────────┐ - │ Timestamp('2023-01-02 03:04:05') │ - └──────────────────────────────────┘ - - Create a timestamp scalar from components - - >>> ibis.timestamp(2023, 1, 2, 3, 4, 5) - ┌──────────────────────────────────┐ - │ Timestamp('2023-01-02 03:04:05') │ - └──────────────────────────────────┘ - - Create a timestamp column from components - - >>> t = ibis.memtable({"y": [2001, 2002], "m": [1, 4], "d": [2, 5], "h": [3, 6]}) - >>> ibis.timestamp(t.y, t.m, t.d, t.h, 0, 0).name("timestamp") - ┏━━━━━━━━━━━━━━━━━━━━━┓ - ┃ timestamp ┃ - ┡━━━━━━━━━━━━━━━━━━━━━┩ - │ timestamp │ - ├─────────────────────┤ - │ 2001-01-02 03:00:00 │ - │ 2002-04-05 06:00:00 │ - └─────────────────────┘ - - """ - args = (value_or_year, month, day, hour, minute, second) - is_ymdhms = any(a is not None for a in args[1:]) - - if is_ymdhms: - if timezone is not None: - raise NotImplementedError( - "Timezone currently not supported when creating a timestamp from components" - ) - return ops.TimestampFromYMDHMS(*args).to_expr() - elif isinstance(value_or_year, (numbers.Real, ir.IntegerValue)): - raise TypeError("Use ibis.literal(...).to_timestamp() instead") - elif isinstance(value_or_year, ir.Expr): - return value_or_year.cast(dt.Timestamp(timezone=timezone)) - else: - value = normalize_datetime(value_or_year) - tzinfo = normalize_timezone(timezone or value.tzinfo) - timezone = tzinfo.tzname(value) if tzinfo is not None else None - return literal(value, type=dt.Timestamp(timezone=timezone)) - - -@overload -def date( - value_or_year: int | ir.IntegerValue | Deferred, - month: int | ir.IntegerValue | Deferred, - day: int | ir.IntegerValue | Deferred, - /, -) -> DateValue: ... - - -@overload -def date(value_or_year: Any, /) -> DateValue: ... - - -@deferrable -def date(value_or_year, month=None, day=None, /): - """Construct a date scalar or column. - - Parameters - ---------- - value_or_year - Either a string value or `datetime.date` to coerce to a date, or - an integral value representing the date year component. - month - The date month component; required if `value_or_year` is a year. - day - The date day component; required if `value_or_year` is a year. - - Returns - ------- - DateValue - A date expression - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - - Create a date scalar from a string - - >>> ibis.date("2023-01-02") - ┌───────────────────────────┐ - │ datetime.date(2023, 1, 2) │ - └───────────────────────────┘ - - Create a date scalar from year, month, and day - - >>> ibis.date(2023, 1, 2) - ┌───────────────────────────┐ - │ datetime.date(2023, 1, 2) │ - └───────────────────────────┘ - - Create a date column from year, month, and day - - >>> t = ibis.memtable(dict(year=[2001, 2002], month=[1, 3], day=[2, 4])) - >>> ibis.date(t.year, t.month, t.day).name("my_date") - ┏━━━━━━━━━━━━┓ - ┃ my_date ┃ - ┡━━━━━━━━━━━━┩ - │ date │ - ├────────────┤ - │ 2001-01-02 │ - │ 2002-03-04 │ - └────────────┘ - - """ - if month is not None or day is not None: - return ops.DateFromYMD(value_or_year, month, day).to_expr() - elif isinstance(value_or_year, ir.Expr): - return value_or_year.cast(dt.date) - else: - return literal(value_or_year, type=dt.date) - - -@overload -def time( - value_or_hour: int | ir.IntegerValue | Deferred, - minute: int | ir.IntegerValue | Deferred, - second: int | ir.IntegerValue | Deferred, - /, -) -> TimeValue: ... - - -@overload -def time(value_or_hour: Any, /) -> TimeValue: ... - - -@deferrable -def time(value_or_hour, minute=None, second=None, /): - """Return a time literal if `value` is coercible to a time. - - Parameters - ---------- - value_or_hour - Either a string value or `datetime.time` to coerce to a time, or - an integral value representing the time hour component. - minute - The time minute component; required if `value_or_hour` is an hour. - second - The time second component; required if `value_or_hour` is an hour. - - Returns - ------- - TimeValue - A time expression - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - - Create a time scalar from a string - - >>> ibis.time("01:02:03") - ┌────────────────────────┐ - │ datetime.time(1, 2, 3) │ - └────────────────────────┘ - - Create a time scalar from hour, minute, and second - - >>> ibis.time(1, 2, 3) - ┌────────────────────────┐ - │ datetime.time(1, 2, 3) │ - └────────────────────────┘ - - Create a time column from hour, minute, and second - - >>> t = ibis.memtable({"h": [1, 4], "m": [2, 5], "s": [3, 6]}) - >>> ibis.time(t.h, t.m, t.s).name("time") - ┏━━━━━━━━━━┓ - ┃ time ┃ - ┡━━━━━━━━━━┩ - │ time │ - ├──────────┤ - │ 01:02:03 │ - │ 04:05:06 │ - └──────────┘ - - """ - if minute is not None or second is not None: - return ops.TimeFromHMS(value_or_hour, minute, second).to_expr() - elif isinstance(value_or_hour, ir.Expr): - return value_or_hour.cast(dt.time) - else: - return literal(value_or_hour, type=dt.time) - - -def interval( - value: int | datetime.timedelta | None = None, - unit: str = "s", - *, - years: int | None = None, - quarters: int | None = None, - months: int | None = None, - weeks: int | None = None, - days: int | None = None, - hours: int | None = None, - minutes: int | None = None, - seconds: int | None = None, - milliseconds: int | None = None, - microseconds: int | None = None, - nanoseconds: int | None = None, -) -> ir.IntervalScalar: - """Return an interval literal expression. - - Parameters - ---------- - value - Interval value. - unit - Unit of `value` - years - Number of years - quarters - Number of quarters - months - Number of months - weeks - Number of weeks - days - Number of days - hours - Number of hours - minutes - Number of minutes - seconds - Number of seconds - milliseconds - Number of milliseconds - microseconds - Number of microseconds - nanoseconds - Number of nanoseconds - - Returns - ------- - IntervalScalar - An interval expression - - """ - keyword_value_unit = [ - ("nanoseconds", nanoseconds, "ns"), - ("microseconds", microseconds, "us"), - ("milliseconds", milliseconds, "ms"), - ("seconds", seconds, "s"), - ("minutes", minutes, "m"), - ("hours", hours, "h"), - ("days", days, "D"), - ("weeks", weeks, "W"), - ("months", months, "M"), - ("quarters", quarters, "Q"), - ("years", years, "Y"), - ] - if value is not None: - for kw, v, _abbrev in keyword_value_unit: - if v is not None: - raise TypeError(f"Cannot provide both 'value' and '{kw}'") - if isinstance(value, datetime.timedelta): - components = [ - (value.microseconds, "us"), - (value.seconds, "s"), - (value.days, "D"), - ] - components = [(v, u) for v, u in components if v] - elif isinstance(value, int): - components = [(value, unit)] - else: - raise TypeError("value must be an integer or timedelta") - else: - components = [(v, u) for _, v, u in keyword_value_unit if v is not None] - - # If no components, default to 0 s - if not components: - components.append((0, "s")) - - intervals = [literal(v, type=dt.Interval(u)) for v, u in components] - return functools.reduce(operator.add, intervals) - - -def case() -> bl.SearchedCaseBuilder: - """Begin constructing a case expression. - - Use the `.when` method on the resulting object followed by `.end` to create a - complete case expression. - - Returns - ------- - SearchedCaseBuilder - A builder object to use for constructing a case expression. - - See Also - -------- - [`Value.case()`](./expression-generic.qmd#ibis.expr.types.generic.Value.case) - - Examples - -------- - >>> import ibis - >>> from ibis import _ - >>> ibis.options.interactive = True - >>> t = ibis.memtable( - ... { - ... "left": [1, 2, 3, 4], - ... "symbol": ["+", "-", "*", "/"], - ... "right": [5, 6, 7, 8], - ... } - ... ) - >>> t.mutate( - ... result=( - ... ibis.case() - ... .when(_.symbol == "+", _.left + _.right) - ... .when(_.symbol == "-", _.left - _.right) - ... .when(_.symbol == "*", _.left * _.right) - ... .when(_.symbol == "/", _.left / _.right) - ... .end() - ... ) - ... ) - ┏━━━━━━━┳━━━━━━━━┳━━━━━━━┳━━━━━━━━━┓ - ┃ left ┃ symbol ┃ right ┃ result ┃ - ┡━━━━━━━╇━━━━━━━━╇━━━━━━━╇━━━━━━━━━┩ - │ int64 │ string │ int64 │ float64 │ - ├───────┼────────┼───────┼─────────┤ - │ 1 │ + │ 5 │ 6.0 │ - │ 2 │ - │ 6 │ -4.0 │ - │ 3 │ * │ 7 │ 21.0 │ - │ 4 │ / │ 8 │ 0.5 │ - └───────┴────────┴───────┴─────────┘ - - """ - return bl.SearchedCaseBuilder() - - -def now() -> ir.TimestampScalar: - """Return an expression that will compute the current timestamp. - - Returns - ------- - TimestampScalar - An expression representing the current timestamp. - - """ - return ops.TimestampNow().to_expr() - - -def today() -> ir.DateScalar: - """Return an expression that will compute the current date. - - Returns - ------- - DateScalar - An expression representing the current date. - - """ - return ops.DateNow().to_expr() - - -def rank() -> ir.IntegerColumn: - """Compute position of first element within each equal-value group in sorted order. - - Equivalent to SQL's `RANK()` window function. - - Returns - ------- - Int64Column - The min rank - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.memtable({"values": [1, 2, 1, 2, 3, 2]}) - >>> t.mutate(rank=ibis.rank().over(order_by=t.values)) - ┏━━━━━━━━┳━━━━━━━┓ - ┃ values ┃ rank ┃ - ┡━━━━━━━━╇━━━━━━━┩ - │ int64 │ int64 │ - ├────────┼───────┤ - │ 1 │ 0 │ - │ 1 │ 0 │ - │ 2 │ 2 │ - │ 2 │ 2 │ - │ 2 │ 2 │ - │ 3 │ 5 │ - └────────┴───────┘ - - """ - return ops.MinRank().to_expr() - - -def dense_rank() -> ir.IntegerColumn: - """Position of first element within each group of equal values. - - Values are returned in sorted order and duplicate values are ignored. - - Equivalent to SQL's `DENSE_RANK()`. - - Returns - ------- - IntegerColumn - The rank - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.memtable({"values": [1, 2, 1, 2, 3, 2]}) - >>> t.mutate(rank=ibis.dense_rank().over(order_by=t.values)) - ┏━━━━━━━━┳━━━━━━━┓ - ┃ values ┃ rank ┃ - ┡━━━━━━━━╇━━━━━━━┩ - │ int64 │ int64 │ - ├────────┼───────┤ - │ 1 │ 0 │ - │ 1 │ 0 │ - │ 2 │ 1 │ - │ 2 │ 1 │ - │ 2 │ 1 │ - │ 3 │ 2 │ - └────────┴───────┘ - - """ - return ops.DenseRank().to_expr() - - -def percent_rank() -> ir.FloatingColumn: - """Return the relative rank of the values in the column. - - Returns - ------- - FloatingColumn - The percent rank - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.memtable({"values": [1, 2, 1, 2, 3, 2]}) - >>> t.mutate(pct_rank=ibis.percent_rank().over(order_by=t.values)) - ┏━━━━━━━━┳━━━━━━━━━━┓ - ┃ values ┃ pct_rank ┃ - ┡━━━━━━━━╇━━━━━━━━━━┩ - │ int64 │ float64 │ - ├────────┼──────────┤ - │ 1 │ 0.0 │ - │ 1 │ 0.0 │ - │ 2 │ 0.4 │ - │ 2 │ 0.4 │ - │ 2 │ 0.4 │ - │ 3 │ 1.0 │ - └────────┴──────────┘ - - """ - return ops.PercentRank().to_expr() - - -def cume_dist() -> ir.FloatingColumn: - """Return the cumulative distribution over a window. - - Returns - ------- - FloatingColumn - The cumulative distribution - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.memtable({"values": [1, 2, 1, 2, 3, 2]}) - >>> t.mutate(dist=ibis.cume_dist().over(order_by=t.values)) - ┏━━━━━━━━┳━━━━━━━━━━┓ - ┃ values ┃ dist ┃ - ┡━━━━━━━━╇━━━━━━━━━━┩ - │ int64 │ float64 │ - ├────────┼──────────┤ - │ 1 │ 0.333333 │ - │ 1 │ 0.333333 │ - │ 2 │ 0.833333 │ - │ 2 │ 0.833333 │ - │ 2 │ 0.833333 │ - │ 3 │ 1.000000 │ - └────────┴──────────┘ - - """ - return ops.CumeDist().to_expr() - - -def ntile(buckets: int | ir.IntegerValue) -> ir.IntegerColumn: - """Return the integer number of a partitioning of the column values. - - Parameters - ---------- - buckets - Number of buckets to partition into - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.memtable({"values": [1, 2, 1, 2, 3, 2]}) - >>> t.mutate(ntile=ibis.ntile(2).over(order_by=t.values)) - ┏━━━━━━━━┳━━━━━━━┓ - ┃ values ┃ ntile ┃ - ┡━━━━━━━━╇━━━━━━━┩ - │ int64 │ int64 │ - ├────────┼───────┤ - │ 1 │ 0 │ - │ 1 │ 0 │ - │ 2 │ 0 │ - │ 2 │ 1 │ - │ 2 │ 1 │ - │ 3 │ 1 │ - └────────┴───────┘ - - """ - return ops.NTile(buckets).to_expr() - - -def row_number() -> ir.IntegerColumn: - """Return an analytic function expression for the current row number. - - ::: {.callout-note} - `row_number` is normalized across backends to start at 0 - ::: - - Returns - ------- - IntegerColumn - A column expression enumerating rows - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.memtable({"values": [1, 2, 1, 2, 3, 2]}) - >>> t.mutate(rownum=ibis.row_number()) - ┏━━━━━━━━┳━━━━━━━━┓ - ┃ values ┃ rownum ┃ - ┡━━━━━━━━╇━━━━━━━━┩ - │ int64 │ int64 │ - ├────────┼────────┤ - │ 1 │ 0 │ - │ 2 │ 1 │ - │ 1 │ 2 │ - │ 2 │ 3 │ - │ 3 │ 4 │ - │ 2 │ 5 │ - └────────┴────────┘ - - """ - return ops.RowNumber().to_expr() - - -def read_csv( - sources: str | Path | Sequence[str | Path], - table_name: str | None = None, - **kwargs: Any, -) -> ir.Table: - """Lazily load a CSV or set of CSVs. - - This function delegates to the `read_csv` method on the current default - backend (DuckDB or `ibis.config.default_backend`). - - Parameters - ---------- - sources - A filesystem path or URL or list of same. Supports CSV and TSV files. - table_name - A name to refer to the table. If not provided, a name will be generated. - kwargs - Backend-specific keyword arguments for the file type. For the DuckDB - backend used by default, please refer to: - - * CSV/TSV: https://duckdb.org/docs/data/csv/overview.html#parameters. - - Returns - ------- - ir.Table - Table expression representing a file - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> lines = '''a,b - ... 1,d - ... 2, - ... ,f - ... ''' - >>> with open("/tmp/lines.csv", mode="w") as f: - ... nbytes = f.write(lines) # nbytes is unused - >>> t = ibis.read_csv("/tmp/lines.csv") - >>> t - ┏━━━━━━━┳━━━━━━━━┓ - ┃ a ┃ b ┃ - ┡━━━━━━━╇━━━━━━━━┩ - │ int64 │ string │ - ├───────┼────────┤ - │ 1 │ d │ - │ 2 │ NULL │ - │ NULL │ f │ - └───────┴────────┘ - - """ - from bigframes_vendored.ibis.config import _default_backend - - con = _default_backend() - return con.read_csv(sources, table_name=table_name, **kwargs) - - -@experimental -def read_json( - sources: str | Path | Sequence[str | Path], - table_name: str | None = None, - **kwargs: Any, -) -> ir.Table: - """Lazily load newline-delimited JSON data. - - This function delegates to the `read_json` method on the current default - backend (DuckDB or `ibis.config.default_backend`). - - Parameters - ---------- - sources - A filesystem path or URL or list of same. - table_name - A name to refer to the table. If not provided, a name will be generated. - kwargs - Backend-specific keyword arguments for the file type. See - https://duckdb.org/docs/extensions/json.html for details. - - Returns - ------- - ir.Table - Table expression representing a file - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> lines = ''' - ... {"a": 1, "b": "d"} - ... {"a": 2, "b": null} - ... {"a": null, "b": "f"} - ... ''' - >>> with open("/tmp/lines.json", mode="w") as f: - ... nbytes = f.write(lines) # nbytes is unused - >>> t = ibis.read_json("/tmp/lines.json") - >>> t - ┏━━━━━━━┳━━━━━━━━┓ - ┃ a ┃ b ┃ - ┡━━━━━━━╇━━━━━━━━┩ - │ int64 │ string │ - ├───────┼────────┤ - │ 1 │ d │ - │ 2 │ NULL │ - │ NULL │ f │ - └───────┴────────┘ - - """ - from bigframes_vendored.ibis.config import _default_backend - - con = _default_backend() - return con.read_json(sources, table_name=table_name, **kwargs) - - -def read_parquet( - sources: str | Path | Sequence[str | Path], - table_name: str | None = None, - **kwargs: Any, -) -> ir.Table: - """Lazily load a parquet file or set of parquet files. - - This function delegates to the `read_parquet` method on the current default - backend (DuckDB or `ibis.config.default_backend`). - - Parameters - ---------- - sources - A filesystem path or URL or list of same. - table_name - A name to refer to the table. If not provided, a name will be generated. - kwargs - Backend-specific keyword arguments for the file type. For the DuckDB - backend used by default, please refer to: - - * Parquet: https://duckdb.org/docs/data/parquet - - Returns - ------- - ir.Table - Table expression representing a file - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> df = pd.DataFrame({"a": [1, 2, 3], "b": list("ghi")}) - >>> df - a b - 0 1 g - 1 2 h - 2 3 i - >>> df.to_parquet("/tmp/data.parquet") - >>> t = ibis.read_parquet("/tmp/data.parquet") - >>> t - ┏━━━━━━━┳━━━━━━━━┓ - ┃ a ┃ b ┃ - ┡━━━━━━━╇━━━━━━━━┩ - │ int64 │ string │ - ├───────┼────────┤ - │ 1 │ g │ - │ 2 │ h │ - │ 3 │ i │ - └───────┴────────┘ - - """ - from bigframes_vendored.ibis.config import _default_backend - - con = _default_backend() - return con.read_parquet(sources, table_name=table_name, **kwargs) - - -def read_delta( - source: str | Path, table_name: str | None = None, **kwargs: Any -) -> ir.Table: - """Lazily load a Delta Lake table. - - Parameters - ---------- - source - A filesystem path or URL. - table_name - A name to refer to the table. If not provided, a name will be generated. - kwargs - Backend-specific keyword arguments for the file type. - - Returns - ------- - ir.Table - Table expression representing a file - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> df = pd.DataFrame({"a": [1, 2, 3], "b": list("ghi")}) - >>> df - a b - 0 1 g - 1 2 h - 2 3 i - >>> import deltalake as dl - >>> dl.write_deltalake("/tmp/data.delta", df, mode="overwrite") - >>> t = ibis.read_delta("/tmp/data.delta") - >>> t - ┏━━━━━━━┳━━━━━━━━┓ - ┃ a ┃ b ┃ - ┡━━━━━━━╇━━━━━━━━┩ - │ int64 │ string │ - ├───────┼────────┤ - │ 1 │ g │ - │ 2 │ h │ - │ 3 │ i │ - └───────┴────────┘ - - """ - from bigframes_vendored.ibis.config import _default_backend - - con = _default_backend() - return con.read_delta(source, table_name=table_name, **kwargs) - - -def set_backend(backend: str | BaseBackend) -> None: - """Set the default Ibis backend. - - Parameters - ---------- - backend - May be a backend name or URL, or an existing backend instance. - - Examples - -------- - You can pass the backend as a name: - - >>> import ibis - >>> ibis.set_backend("polars") - - Or as a URI - - >>> ibis.set_backend( - ... "postgres://user:password@hostname:5432" - ... ) # quartodoc: +SKIP # doctest: +SKIP - - Or as an existing backend instance - - >>> ibis.set_backend(ibis.duckdb.connect()) - - """ - import bigframes_vendored.ibis - - if isinstance(backend, str) and backend.isidentifier(): - try: - backend_type = getattr(bigframes_vendored.ibis, backend) - except AttributeError: - pass - else: - backend = backend_type.connect() - if isinstance(backend, str): - backend = bigframes_vendored.ibis.connect(backend) - - bigframes_vendored.ibis.options.default_backend = backend - - -def get_backend(expr: Expr | None = None) -> BaseBackend: - """Get the current Ibis backend to use for a given expression. - - expr - An expression to get the backend from. If not passed, the default - backend is returned. - - Returns - ------- - BaseBackend - The Ibis backend. - - """ - if expr is None: - from bigframes_vendored.ibis.config import _default_backend - - return _default_backend() - return expr._find_backend(use_default=True) - - -def window( - preceding=None, - following=None, - order_by=None, - group_by=None, - *, - rows=None, - range=None, - between=None, -): - """Create a window clause for use with window functions. - - The `ROWS` window clause includes peer rows based on differences in row - **number** whereas `RANGE` includes rows based on the differences in row - **value** of a single `order_by` expression. - - All window frame bounds are inclusive. - - Parameters - ---------- - preceding - Number of preceding rows in the window - following - Number of following rows in the window - group_by - Grouping key - order_by - Ordering key - rows - Whether to use the `ROWS` window clause - range - Whether to use the `RANGE` window clause - between - Automatically infer the window kind based on the boundaries - - Returns - ------- - Window - A window frame - - """ - has_rows = rows is not None - has_range = range is not None - has_between = between is not None - has_preceding_following = preceding is not None or following is not None - if has_rows + has_range + has_between + has_preceding_following > 1: - raise IbisInputError( - "Must only specify either `rows`, `range`, `between` or `preceding`/`following`" - ) - - builder = bl.LegacyWindowBuilder().group_by(group_by).order_by(order_by) - if has_rows: - return builder.rows(*rows) - elif has_range: - return builder.range(*range) - elif has_between: - return builder.between(*between) - elif has_preceding_following: - return builder.preceding_following(preceding, following) - else: - return builder - - -def rows_window(preceding=None, following=None, group_by=None, order_by=None): - """Create a rows-based window clause for use with window functions. - - This ROWS window clause aggregates rows based upon differences in row - number. - - All window frames / ranges are inclusive. - - Parameters - ---------- - preceding - Number of preceding rows in the window - following - Number of following rows in the window - group_by - Grouping key - order_by - Ordering key - - Returns - ------- - Window - A window frame - - """ - return ( - bl.LegacyWindowBuilder() - .group_by(group_by) - .order_by(order_by) - .preceding_following(preceding, following, how="rows") - ) - - -def range_window(preceding=None, following=None, group_by=None, order_by=None): - """Create a range-based window clause for use with window functions. - - This RANGE window clause aggregates rows based upon differences in the - value of the order-by expression. - - All window frames / ranges are inclusive. - - Parameters - ---------- - preceding - Number of preceding rows in the window - following - Number of following rows in the window - group_by - Grouping key - order_by - Ordering key - - Returns - ------- - Window - A window frame - - """ - return ( - bl.LegacyWindowBuilder() - .group_by(group_by) - .order_by(order_by) - .preceding_following(preceding, following, how="range") - ) - - -def cumulative_window(group_by=None, order_by=None): - """Create a cumulative window for use with window functions. - - All window frames / ranges are inclusive. - - Parameters - ---------- - group_by - Grouping key - order_by - Ordering key - - Returns - ------- - Window - A window frame - - """ - return window(rows=(None, 0), group_by=group_by, order_by=order_by) - - -def trailing_window(preceding, group_by=None, order_by=None): - """Create a trailing window for use with window functions. - - Parameters - ---------- - preceding - The number of preceding rows - group_by - Grouping key - order_by - Ordering key - - Returns - ------- - Window - A window frame - - """ - return window( - preceding=preceding, following=0, group_by=group_by, order_by=order_by - ) - - -def trailing_rows_window(preceding, group_by=None, order_by=None): - """Create a trailing window for use with aggregate window functions. - - Parameters - ---------- - preceding - The number of preceding rows - group_by - Grouping key - order_by - Ordering key - - Returns - ------- - Window - A window frame - - """ - return rows_window( - preceding=preceding, following=0, group_by=group_by, order_by=order_by - ) - - -def trailing_range_window(preceding, order_by, group_by=None): - """Create a trailing range window for use with window functions. - - Parameters - ---------- - preceding - A value expression - order_by - Ordering key - group_by - Grouping key - - Returns - ------- - Window - A window frame - - """ - return range_window( - preceding=preceding, following=0, group_by=group_by, order_by=order_by - ) - - -def union(table: ir.Table, *rest: ir.Table, distinct: bool = False) -> ir.Table: - """Compute the set union of multiple table expressions. - - The input tables must have identical schemas. - - Parameters - ---------- - table - A table expression - *rest - Additional table expressions - distinct - Only return distinct rows - - Returns - ------- - Table - A new table containing the union of all input tables. - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t1 = ibis.memtable({"a": [1, 2]}) - >>> t1 - ┏━━━━━━━┓ - ┃ a ┃ - ┡━━━━━━━┩ - │ int64 │ - ├───────┤ - │ 1 │ - │ 2 │ - └───────┘ - >>> t2 = ibis.memtable({"a": [2, 3]}) - >>> t2 - ┏━━━━━━━┓ - ┃ a ┃ - ┡━━━━━━━┩ - │ int64 │ - ├───────┤ - │ 2 │ - │ 3 │ - └───────┘ - >>> ibis.union(t1, t2) # union all by default - ┏━━━━━━━┓ - ┃ a ┃ - ┡━━━━━━━┩ - │ int64 │ - ├───────┤ - │ 1 │ - │ 2 │ - │ 2 │ - │ 3 │ - └───────┘ - >>> ibis.union(t1, t2, distinct=True).order_by("a") - ┏━━━━━━━┓ - ┃ a ┃ - ┡━━━━━━━┩ - │ int64 │ - ├───────┤ - │ 1 │ - │ 2 │ - │ 3 │ - └───────┘ - - """ - return table.union(*rest, distinct=distinct) if rest else table - - -def intersect(table: ir.Table, *rest: ir.Table, distinct: bool = True) -> ir.Table: - """Compute the set intersection of multiple table expressions. - - The input tables must have identical schemas. - - Parameters - ---------- - table - A table expression - *rest - Additional table expressions - distinct - Only return distinct rows - - Returns - ------- - Table - A new table containing the intersection of all input tables. - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t1 = ibis.memtable({"a": [1, 2]}) - >>> t1 - ┏━━━━━━━┓ - ┃ a ┃ - ┡━━━━━━━┩ - │ int64 │ - ├───────┤ - │ 1 │ - │ 2 │ - └───────┘ - >>> t2 = ibis.memtable({"a": [2, 3]}) - >>> t2 - ┏━━━━━━━┓ - ┃ a ┃ - ┡━━━━━━━┩ - │ int64 │ - ├───────┤ - │ 2 │ - │ 3 │ - └───────┘ - >>> ibis.intersect(t1, t2) - ┏━━━━━━━┓ - ┃ a ┃ - ┡━━━━━━━┩ - │ int64 │ - ├───────┤ - │ 2 │ - └───────┘ - - """ - return table.intersect(*rest, distinct=distinct) if rest else table - - -def difference(table: ir.Table, *rest: ir.Table, distinct: bool = True) -> ir.Table: - """Compute the set difference of multiple table expressions. - - The input tables must have identical schemas. - - Parameters - ---------- - table - A table expression - *rest - Additional table expressions - distinct - Only diff distinct rows not occurring in the calling table - - Returns - ------- - Table - The rows present in `self` that are not present in `tables`. - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t1 = ibis.memtable({"a": [1, 2]}) - >>> t1 - ┏━━━━━━━┓ - ┃ a ┃ - ┡━━━━━━━┩ - │ int64 │ - ├───────┤ - │ 1 │ - │ 2 │ - └───────┘ - >>> t2 = ibis.memtable({"a": [2, 3]}) - >>> t2 - ┏━━━━━━━┓ - ┃ a ┃ - ┡━━━━━━━┩ - │ int64 │ - ├───────┤ - │ 2 │ - │ 3 │ - └───────┘ - >>> ibis.difference(t1, t2) - ┏━━━━━━━┓ - ┃ a ┃ - ┡━━━━━━━┩ - │ int64 │ - ├───────┤ - │ 1 │ - └───────┘ - - """ - return table.difference(*rest, distinct=distinct) if rest else table - - -class Watermark(Concrete): - time_col: str - allowed_delay: ir.IntervalScalar - - -def watermark(time_col: str, allowed_delay: ir.IntervalScalar) -> Watermark: - """Return a watermark object. - - Parameters - ---------- - time_col - The timestamp column that will be used to generate watermarks in event time processing. - allowed_delay - Length of time that events are allowed to be late. - - Returns - ------- - Watermark - A watermark object. - - """ - return Watermark(time_col=time_col, allowed_delay=allowed_delay) - - -@functools.singledispatch -def range(start, stop, step) -> ir.ArrayValue: - """Generate a range of values. - - Integer ranges are supported, as well as timestamp ranges. - - ::: {.callout-note} - `start` is inclusive and `stop` is exclusive, just like Python's builtin - [](`range`). - - When `step` equals 0, however, this function will return an empty array. - - Python's `range` will raise an exception when `step` is zero. - ::: - - Parameters - ---------- - start - Lower bound of the range, inclusive. - stop - Upper bound of the range, exclusive. - step - Step value. Optional, defaults to 1. - - Returns - ------- - ArrayValue - An array of values - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - - Range using only a stop argument - - >>> ibis.range(5) - ┌────────────────┐ - │ [0, 1, ... +3] │ - └────────────────┘ - - Simple range using start and stop - - >>> ibis.range(1, 5) - ┌────────────────┐ - │ [1, 2, ... +2] │ - └────────────────┘ - - Generate an empty range - - >>> ibis.range(0) - ┌────┐ - │ [] │ - └────┘ - - Negative step values are supported - - >>> ibis.range(10, 4, -2) - ┌─────────────────┐ - │ [10, 8, ... +1] │ - └─────────────────┘ - - `ibis.range` behaves the same as Python's range ... - - >>> ibis.range(0, 7, -1) - ┌────┐ - │ [] │ - └────┘ - - ... except when the step is zero, in which case `ibis.range` returns an - empty array - - >>> ibis.range(0, 5, 0) - ┌────┐ - │ [] │ - └────┘ - - Because the resulting expression is array, you can unnest the values - - >>> ibis.range(5).unnest().name("numbers") - ┏━━━━━━━━━┓ - ┃ numbers ┃ - ┡━━━━━━━━━┩ - │ int8 │ - ├─────────┤ - │ 0 │ - │ 1 │ - │ 2 │ - │ 3 │ - │ 4 │ - └─────────┘ - - Timestamp ranges are also supported - - >>> expr = ibis.range("2002-01-01", "2002-02-01", ibis.interval(days=2)).name("ts") - >>> expr - ┌──────────────────────────────────────────┐ - │ [ │ - │ datetime.datetime(2002, 1, 1, 0, 0), │ - │ datetime.datetime(2002, 1, 3, 0, 0), │ - │ ... +14 │ - │ ] │ - └──────────────────────────────────────────┘ - >>> expr.unnest() - ┏━━━━━━━━━━━━━━━━━━━━━┓ - ┃ ts ┃ - ┡━━━━━━━━━━━━━━━━━━━━━┩ - │ timestamp │ - ├─────────────────────┤ - │ 2002-01-01 00:00:00 │ - │ 2002-01-03 00:00:00 │ - │ 2002-01-05 00:00:00 │ - │ 2002-01-07 00:00:00 │ - │ 2002-01-09 00:00:00 │ - │ 2002-01-11 00:00:00 │ - │ 2002-01-13 00:00:00 │ - │ 2002-01-15 00:00:00 │ - │ 2002-01-17 00:00:00 │ - │ 2002-01-19 00:00:00 │ - │ … │ - └─────────────────────┘ - - """ - raise NotImplementedError() - - -@range.register(int) -@range.register(ir.IntegerValue) -def _int_range( - start: int, - stop: int | ir.IntegerValue | None = None, - step: int | ir.IntegerValue | None = None, -) -> ir.ArrayValue: - if stop is None: - stop = start - start = 0 - if step is None: - step = 1 - return ops.IntegerRange(start=start, stop=stop, step=step).to_expr() - - -@range.register(str) -@range.register(datetime.datetime) -@range.register(ir.TimestampValue) -def _timestamp_range( - start: datetime.datetime | ir.TimestampValue | str, - stop: datetime.datetime | ir.TimestampValue | str, - step: datetime.timedelta | ir.IntervalValue, -) -> ir.ArrayValue: - return ops.TimestampRange( - start=normalize_datetime(start) if isinstance(start, str) else start, - stop=normalize_datetime(stop) if isinstance(stop, str) else stop, - step=step, - ).to_expr() - - -def _wrap_deprecated(fn, prefix=""): - """Deprecate the top-level geo function.""" - - @functools.wraps(fn) - def wrapper(self, *args, **kwargs): - if isinstance(self, Deferred): - method = getattr(self, fn.__name__) - return method(*args, **kwargs) - return fn(self, *args, **kwargs) - - wrapper.__module__ = "ibis.expr.api" - wrapper.__qualname__ = wrapper.__name__ = prefix + fn.__name__ - dec = util.deprecated( - instead=f"use the `{fn.__qualname__}` method instead", as_of="7.0" - ) - return dec(wrapper) - - -geo_area = _wrap_deprecated(ir.GeoSpatialValue.area, "geo_") -geo_as_binary = _wrap_deprecated(ir.GeoSpatialValue.as_binary, "geo_") -geo_as_ewkb = _wrap_deprecated(ir.GeoSpatialValue.as_ewkb, "geo_") -geo_as_ewkt = _wrap_deprecated(ir.GeoSpatialValue.as_ewkt, "geo_") -geo_as_text = _wrap_deprecated(ir.GeoSpatialValue.as_text, "geo_") -geo_azimuth = _wrap_deprecated(ir.GeoSpatialValue.azimuth, "geo_") -geo_buffer = _wrap_deprecated(ir.GeoSpatialValue.buffer, "geo_") -geo_centroid = _wrap_deprecated(ir.GeoSpatialValue.centroid, "geo_") -geo_contains = _wrap_deprecated(ir.GeoSpatialValue.contains, "geo_") -geo_contains_properly = _wrap_deprecated(ir.GeoSpatialValue.contains_properly, "geo_") -geo_covers = _wrap_deprecated(ir.GeoSpatialValue.covers, "geo_") -geo_covered_by = _wrap_deprecated(ir.GeoSpatialValue.covered_by, "geo_") -geo_crosses = _wrap_deprecated(ir.GeoSpatialValue.crosses, "geo_") -geo_d_fully_within = _wrap_deprecated(ir.GeoSpatialValue.d_fully_within, "geo_") -geo_difference = _wrap_deprecated(ir.GeoSpatialValue.difference, "geo_") -geo_disjoint = _wrap_deprecated(ir.GeoSpatialValue.disjoint, "geo_") -geo_distance = _wrap_deprecated(ir.GeoSpatialValue.distance, "geo_") -geo_d_within = _wrap_deprecated(ir.GeoSpatialValue.d_within, "geo_") -geo_end_point = _wrap_deprecated(ir.GeoSpatialValue.end_point, "geo_") -geo_envelope = _wrap_deprecated(ir.GeoSpatialValue.envelope, "geo_") -geo_equals = _wrap_deprecated(ir.GeoSpatialValue.geo_equals, "geo_") -geo_geometry_n = _wrap_deprecated(ir.GeoSpatialValue.geometry_n, "geo_") -geo_geometry_type = _wrap_deprecated(ir.GeoSpatialValue.geometry_type, "geo_") -geo_intersection = _wrap_deprecated(ir.GeoSpatialValue.intersection, "geo_") -geo_intersects = _wrap_deprecated(ir.GeoSpatialValue.intersects, "geo_") -geo_is_valid = _wrap_deprecated(ir.GeoSpatialValue.is_valid, "geo_") -geo_line_locate_point = _wrap_deprecated(ir.GeoSpatialValue.line_locate_point, "geo_") -geo_line_merge = _wrap_deprecated(ir.GeoSpatialValue.line_merge, "geo_") -geo_line_substring = _wrap_deprecated(ir.GeoSpatialValue.line_substring, "geo_") -geo_length = _wrap_deprecated(ir.GeoSpatialValue.length, "geo_") -geo_max_distance = _wrap_deprecated(ir.GeoSpatialValue.max_distance, "geo_") -geo_n_points = _wrap_deprecated(ir.GeoSpatialValue.n_points, "geo_") -geo_n_rings = _wrap_deprecated(ir.GeoSpatialValue.n_rings, "geo_") -geo_ordering_equals = _wrap_deprecated(ir.GeoSpatialValue.ordering_equals, "geo_") -geo_overlaps = _wrap_deprecated(ir.GeoSpatialValue.overlaps, "geo_") -geo_perimeter = _wrap_deprecated(ir.GeoSpatialValue.perimeter, "geo_") -geo_point = _wrap_deprecated(ir.NumericValue.point, "geo_") -geo_point_n = _wrap_deprecated(ir.GeoSpatialValue.point_n, "geo_") -geo_set_srid = _wrap_deprecated(ir.GeoSpatialValue.set_srid, "geo_") -geo_simplify = _wrap_deprecated(ir.GeoSpatialValue.simplify, "geo_") -geo_srid = _wrap_deprecated(ir.GeoSpatialValue.srid, "geo_") -geo_start_point = _wrap_deprecated(ir.GeoSpatialValue.start_point, "geo_") -geo_touches = _wrap_deprecated(ir.GeoSpatialValue.touches, "geo_") -geo_transform = _wrap_deprecated(ir.GeoSpatialValue.transform, "geo_") -geo_union = _wrap_deprecated(ir.GeoSpatialValue.union, "geo_") -geo_within = _wrap_deprecated(ir.GeoSpatialValue.within, "geo_") -geo_x = _wrap_deprecated(ir.GeoSpatialValue.x, "geo_") -geo_x_max = _wrap_deprecated(ir.GeoSpatialValue.x_max, "geo_") -geo_x_min = _wrap_deprecated(ir.GeoSpatialValue.x_min, "geo_") -geo_y = _wrap_deprecated(ir.GeoSpatialValue.y, "geo_") -geo_y_max = _wrap_deprecated(ir.GeoSpatialValue.y_max, "geo_") -geo_y_min = _wrap_deprecated(ir.GeoSpatialValue.y_min, "geo_") -geo_unary_union = _wrap_deprecated(ir.GeoSpatialColumn.unary_union, "geo_") -negate = _wrap_deprecated(ir.NumericValue.negate) - - -@deferrable -def ifelse(condition: Any, true_expr: Any, false_expr: Any) -> ir.Value: - """Construct a ternary conditional expression. - - Parameters - ---------- - condition - A boolean expression - true_expr - Expression to return if `condition` evaluates to `True` - false_expr - Expression to return if `condition` evaluates to `False` or `NULL` - - Returns - ------- - Value : ir.Value - The value of `true_expr` if `condition` is `True` else `false_expr` - - See Also - -------- - [`BooleanValue.ifelse()`](./expression-numeric.qmd#ibis.expr.types.logical.BooleanValue.ifelse) - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.memtable({"condition": [True, False, True, None]}) - >>> ibis.ifelse(t.condition, "yes", "no") - ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ IfElse(condition, 'yes', 'no') ┃ - ┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩ - │ string │ - ├────────────────────────────────┤ - │ yes │ - │ no │ - │ yes │ - │ no │ - └────────────────────────────────┘ - - """ - if not isinstance(condition, ir.Value): - condition = literal(condition, type="bool") - elif not condition.type().is_boolean(): - condition = condition.cast(bool) - return condition.ifelse(true_expr, false_expr) - - -@util.deprecated(instead="use `ibis.ifelse` instead", as_of="7.0") -def where(cond, true_expr, false_expr) -> ir.Value: - """Construct a ternary conditional expression. - - Parameters - ---------- - cond - Boolean conditional expression - true_expr - Expression to return if `cond` evaluates to `True` - false_expr - Expression to return if `cond` evaluates to `False` or `NULL` - - Returns - ------- - Value : ir.Value - The value of `true_expr` if `arg` is `True` else `false_expr` - """ - return ifelse(cond, true_expr, false_expr) - - -@deferrable -def coalesce(*args: Any) -> ir.Value: - """Return the first non-null value from `args`. - - Parameters - ---------- - args - Arguments from which to choose the first non-null value - - Returns - ------- - Value - Coalesced expression - - See Also - -------- - [`Value.coalesce()`](#ibis.expr.types.generic.Value.coalesce) - [`Value.fill_null()`](#ibis.expr.types.generic.Value.fill_null) - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> ibis.coalesce(None, 4, 5) - ┌────────────┐ - │ np.int8(4) │ - └────────────┘ - """ - return ops.Coalesce(args).to_expr() - - -@deferrable -def greatest(*args: Any) -> ir.Value: - """Compute the largest value among the supplied arguments. - - Parameters - ---------- - args - Arguments to choose from - - Returns - ------- - Value - Maximum of the passed arguments - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> ibis.greatest(None, 4, 5) - ┌────────────┐ - │ np.int8(5) │ - └────────────┘ - """ - return ops.Greatest(args).to_expr() - - -@deferrable -def least(*args: Any) -> ir.Value: - """Compute the smallest value among the supplied arguments. - - Parameters - ---------- - args - Arguments to choose from - - Returns - ------- - Value - Minimum of the passed arguments - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> ibis.least(None, 4, 5) - ┌────────────┐ - │ np.int8(4) │ - └────────────┘ - """ - return ops.Least(args).to_expr() - - -def omitted() -> ir.Value: - return ops.Omitted().to_expr() diff --git a/third_party/bigframes_vendored/ibis/expr/builders.py b/third_party/bigframes_vendored/ibis/expr/builders.py deleted file mode 100644 index c7b6e538ff5..00000000000 --- a/third_party/bigframes_vendored/ibis/expr/builders.py +++ /dev/null @@ -1,312 +0,0 @@ -# Contains code from https://github.com/ibis-project/ibis/blob/9.2.0/ibis/expr/builder.py - -from __future__ import annotations - -import math -from typing import TYPE_CHECKING, Any, Literal, Optional, Union - -import bigframes_vendored.ibis -import bigframes_vendored.ibis.expr.datatypes as dt -import bigframes_vendored.ibis.expr.operations as ops -import bigframes_vendored.ibis.expr.rules as rlz -import bigframes_vendored.ibis.expr.types as ir -from bigframes_vendored.ibis import util -from bigframes_vendored.ibis.common.annotations import annotated, attribute -from bigframes_vendored.ibis.common.deferred import Deferred, Resolver, deferrable -from bigframes_vendored.ibis.common.exceptions import IbisInputError -from bigframes_vendored.ibis.common.grounds import Concrete -from bigframes_vendored.ibis.common.selectors import Selector # noqa: TCH001 -from bigframes_vendored.ibis.common.typing import VarTuple # noqa: TCH001 - -if TYPE_CHECKING: - from typing_extensions import Self - - -class Builder(Concrete): - pass - - -@deferrable(repr="") -def _finish_searched_case(cases, results, default) -> ir.Value: - """Finish constructing a SearchedCase expression. - - This is split out into a separate function to allow for deferred arguments - to resolve. - """ - return ops.SearchedCase(cases=cases, results=results, default=default).to_expr() - - -class SearchedCaseBuilder(Builder): - """A case builder, used for constructing `ibis.case()` expressions.""" - - cases: VarTuple[Union[Resolver, ops.Value[dt.Boolean]]] = () - results: VarTuple[Union[Resolver, ops.Value]] = () - default: Optional[Union[Resolver, ops.Value]] = None - - def when(self, case_expr: Any, result_expr: Any) -> Self: - """Add a new condition and result to the `CASE` expression. - - Parameters - ---------- - case_expr - Predicate expression to use for this case. - result_expr - Value when the case predicate evaluates to true. - - """ - return self.copy( - cases=self.cases + (case_expr,), results=self.results + (result_expr,) - ) - - def else_(self, result_expr: Any) -> Self: - """Add a default value for the `CASE` expression. - - Parameters - ---------- - result_expr - Value to use when all case predicates evaluate to false. - - """ - return self.copy(default=result_expr) - - def end(self) -> ir.Value | Deferred: - """Finish the `CASE` expression.""" - return _finish_searched_case(self.cases, self.results, self.default) - - -class SimpleCaseBuilder(Builder): - """A case builder, used for constructing `Column.case()` expressions.""" - - base: ops.Value - cases: VarTuple[ops.Value] = () - results: VarTuple[ops.Value] = () - default: Optional[ops.Value] = None - - def when(self, case_expr: Any, result_expr: Any) -> Self: - """Add a new condition and result to the `CASE` expression. - - Parameters - ---------- - case_expr - Expression to equality-compare with base expression. Must be - comparable with the base. - result_expr - Value when the case predicate evaluates to true. - - """ - if not isinstance(case_expr, ir.Value): - case_expr = bigframes_vendored.ibis.literal(case_expr) - if not isinstance(result_expr, ir.Value): - result_expr = bigframes_vendored.ibis.literal(result_expr) - - if not rlz.comparable(self.base, case_expr.op()): - raise TypeError( - f"Base expression {rlz._arg_type_error_format(self.base)} and " - f"case {rlz._arg_type_error_format(case_expr)} are not comparable" - ) - return self.copy( - cases=self.cases + (case_expr,), results=self.results + (result_expr,) - ) - - def else_(self, result_expr: Any) -> Self: - """Add a default value for the `CASE` expression. - - Parameters - ---------- - result_expr - Value to use when all case predicates evaluate to false. - - """ - return self.copy(default=result_expr) - - def end(self) -> ir.Value: - """Finish the `CASE` expression.""" - if (default := self.default) is None: - default = bigframes_vendored.ibis.null().cast( - rlz.highest_precedence_dtype(self.results) - ) - return ops.SimpleCase( - cases=self.cases, results=self.results, default=default, base=self.base - ).to_expr() - - -RowsWindowBoundary = ops.WindowBoundary[dt.Integer] -RangeWindowBoundary = ops.WindowBoundary[dt.Numeric | dt.Interval] - - -class WindowBuilder(Builder): - """An unbound window frame specification. - - Notes - ----- - This class is patterned after SQL window frame clauses. - - Using `None` for `preceding` or `following` indicates an unbounded frame. - - Use 0 for `CURRENT ROW`. - - """ - - how: Literal["rows", "range"] = "rows" - start: Optional[RangeWindowBoundary] = None - end: Optional[RangeWindowBoundary] = None - groupings: VarTuple[Union[str, Resolver, Selector, ops.Value]] = () - orderings: VarTuple[Union[str, Resolver, Selector, ops.SortKey]] = () - - @attribute - def _table(self): - inputs = ( - self.start, - self.end, - *self.groupings, - *self.orderings, - ) - valuerels = (v.relations for v in inputs if isinstance(v, ops.Value)) - relations = frozenset().union(*valuerels) - if len(relations) == 0: - return None - elif len(relations) == 1: - (table,) = relations - return table - else: - raise IbisInputError("Window frame can only depend on a single relation") - - def _maybe_cast_boundaries(self, start, end): - if start and end: - if start.dtype.is_interval() and end.dtype.is_numeric(): - return start, ops.Cast(end.value, start.dtype) - elif start.dtype.is_numeric() and end.dtype.is_interval(): - return ops.Cast(start.value, end.dtype), end - return start, end - - def _determine_how(self, start, end): - if start and not start.dtype.is_integer(): - return self.range - elif end and not end.dtype.is_integer(): - return self.range - else: - return self.rows - - def _validate_boundaries(self, start, end): - start_, end_ = -math.inf, math.inf - if start and isinstance(lit := start.value, ops.Literal): - start_ = -lit.value if start.preceding else lit.value - if end and isinstance(lit := end.value, ops.Literal): - end_ = -lit.value if end.preceding else lit.value - - if start_ > end_: - raise IbisInputError( - "Window frame's start point must be greater than its end point" - ) - - @annotated - def rows( - self, start: Optional[RowsWindowBoundary], end: Optional[RowsWindowBoundary] - ): - self._validate_boundaries(start, end) - start, end = self._maybe_cast_boundaries(start, end) - return self.copy(how="rows", start=start, end=end) - - @annotated - def range( - self, start: Optional[RangeWindowBoundary], end: Optional[RangeWindowBoundary] - ): - self._validate_boundaries(start, end) - start, end = self._maybe_cast_boundaries(start, end) - return self.copy(how="range", start=start, end=end) - - @annotated - def between( - self, start: Optional[RangeWindowBoundary], end: Optional[RangeWindowBoundary] - ): - self._validate_boundaries(start, end) - start, end = self._maybe_cast_boundaries(start, end) - method = self._determine_how(start, end) - return method(start, end) - - def group_by(self, expr) -> Self: - return self.copy(groupings=self.groupings + util.promote_tuple(expr)) - - def order_by(self, expr) -> Self: - return self.copy(orderings=self.orderings + util.promote_tuple(expr)) - - def bind(self, table): - if table is None: - if self._table is None: - raise IbisInputError("Cannot bind window frame without a table") - else: - table = self._table.to_expr() - - return self.copy( - groupings=table.bind(self.groupings), orderings=table.bind(self.orderings) - ) - - -class LegacyWindowBuilder(WindowBuilder): - def _is_negative(self, value): - if value is None: - return False - if isinstance(value, ir.Scalar): - value = value.op().value - return value < 0 - - def preceding_following(self, preceding, following, how=None) -> Self: - preceding_tuple = has_preceding = False - following_tuple = has_following = False - if preceding is not None: - preceding_tuple = isinstance(preceding, tuple) - has_preceding = True - if following is not None: - following_tuple = isinstance(following, tuple) - has_following = True - - if (preceding_tuple and has_following) or (following_tuple and has_preceding): - raise IbisInputError( - "Can only specify one window side when you want an off-center window" - ) - elif preceding_tuple: - start, end = preceding - if end is None: - raise IbisInputError("preceding end point cannot be None") - elif self._is_negative(end): - raise IbisInputError("preceding end point must be non-negative") - elif self._is_negative(start): - raise IbisInputError("preceding start point must be non-negative") - between = ( - None if start is None else ops.WindowBoundary(start, preceding=True), - ops.WindowBoundary(end, preceding=True), - ) - elif following_tuple: - start, end = following - if start is None: - raise IbisInputError("following start point cannot be None") - elif self._is_negative(start): - raise IbisInputError("following start point must be non-negative") - elif self._is_negative(end): - raise IbisInputError("following end point must be non-negative") - between = ( - ops.WindowBoundary(start, preceding=False), - None if end is None else ops.WindowBoundary(end, preceding=False), - ) - elif has_preceding and has_following: - between = ( - ops.WindowBoundary(preceding, preceding=True), - ops.WindowBoundary(following, preceding=False), - ) - elif has_preceding: - if self._is_negative(preceding): - raise IbisInputError("preceding end point must be non-negative") - between = (ops.WindowBoundary(preceding, preceding=True), None) - elif has_following: - if self._is_negative(following): - raise IbisInputError("following end point must be non-negative") - between = (None, ops.WindowBoundary(following, preceding=False)) - - if how is None: - return self.between(*between) - elif how == "rows": - return self.rows(*between) - elif how == "range": - return self.range(*between) - else: - raise ValueError(f"Invalid window frame type: {how}") diff --git a/third_party/bigframes_vendored/ibis/expr/datashape.py b/third_party/bigframes_vendored/ibis/expr/datashape.py deleted file mode 100644 index a13c5461925..00000000000 --- a/third_party/bigframes_vendored/ibis/expr/datashape.py +++ /dev/null @@ -1,70 +0,0 @@ -# Contains code from https://github.com/ibis-project/ibis/blob/9.2.0/ibis/expr/datashape.py - -from __future__ import annotations - -from typing import Any - -from bigframes_vendored.ibis.common.grounds import Singleton -from public import public - - -@public -class DataShape(Singleton): - ndim: int - SCALAR: Scalar - COLUMNAR: Columnar - - def is_scalar(self) -> bool: - return self.ndim == 0 - - def is_columnar(self) -> bool: - return self.ndim == 1 - - def is_tabular(self) -> bool: - return self.ndim == 2 - - def __lt__(self, other: Any) -> bool: - if not isinstance(other, DataShape): - return NotImplemented - return self.ndim < other.ndim - - def __le__(self, other: Any) -> bool: - if not isinstance(other, DataShape): - return NotImplemented - return self.ndim <= other.ndim - - def __eq__(self, other: Any) -> bool: - if not isinstance(other, DataShape): - return NotImplemented - return self.ndim == other.ndim - - def __hash__(self) -> int: - return hash((self.__class__, self.ndim)) - - -@public -class Scalar(DataShape): - ndim = 0 - - -@public -class Columnar(DataShape): - ndim = 1 - - -@public -class Tabular(DataShape): - ndim = 2 - - -# for backward compat -DataShape.SCALAR = Scalar() -DataShape.COLUMNAR = Columnar() -DataShape.TABULAR = Tabular() - -scalar = Scalar() -columnar = Columnar() -tabular = Tabular() - - -public(Any=DataShape) diff --git a/third_party/bigframes_vendored/ibis/expr/datatypes/__init__.py b/third_party/bigframes_vendored/ibis/expr/datatypes/__init__.py deleted file mode 100644 index 2ff4d41ab5d..00000000000 --- a/third_party/bigframes_vendored/ibis/expr/datatypes/__init__.py +++ /dev/null @@ -1,18 +0,0 @@ -# Contains code from https://github.com/ibis-project/ibis/blob/9.2.0/ibis/expr/datatypes/__init__.py - -from __future__ import annotations - -from bigframes_vendored.ibis.expr.datatypes.cast import * # noqa: F403 -from bigframes_vendored.ibis.expr.datatypes.core import * # noqa: F403 -from bigframes_vendored.ibis.expr.datatypes.value import * # noqa: F403 - -halffloat = float16 # noqa: F405 -float = float64 # noqa: F405 -double = float64 # noqa: F405 -int = int64 # noqa: F405 -uint_ = uint64 # noqa: F405 -bool = boolean # noqa: F405 -str = string # noqa: F405 -bytes = binary # noqa: F405 - -validate_type = dtype # noqa: F405 diff --git a/third_party/bigframes_vendored/ibis/expr/datatypes/cast.py b/third_party/bigframes_vendored/ibis/expr/datatypes/cast.py deleted file mode 100644 index e7e18419441..00000000000 --- a/third_party/bigframes_vendored/ibis/expr/datatypes/cast.py +++ /dev/null @@ -1,148 +0,0 @@ -# Contains code from https://github.com/ibis-project/ibis/blob/9.2.0/ibis/expr/datatypes/cast.py - -from __future__ import annotations - -import functools -from typing import TYPE_CHECKING, Any - -import bigframes_vendored.ibis.expr.datatypes.core as dt -from bigframes_vendored.ibis.common.exceptions import IbisTypeError -from public import public - -if TYPE_CHECKING: - from collections.abc import Iterator - - -@public -def cast(source: str | dt.DataType, target: str | dt.DataType, **kwargs) -> dt.DataType: - """Attempts to implicitly cast from source dtype to target dtype.""" - source, target = dt.dtype(source), dt.dtype(target) - - if not source.castable(target, **kwargs): - raise IbisTypeError( - f"Datatype {source} cannot be implicitly casted to {target}" - ) - return target - - -@public -def castable(source: dt.DataType, target: dt.DataType, value: Any = None) -> bool: - """Return whether source ir type is implicitly castable to target.""" - from bigframes_vendored.ibis.expr.datatypes.value import normalizable - - if source == target: - return True - elif source.is_null(): - # The null type is castable to any type, even if the target type is *not* - # nullable. - # - # We handle the promotion of `null + !T -> T` at the `castable` call site. - # - # It might be possible to build a system with a single function that tries - # to promote types and use the exception to indicate castability, but that - # is a deeper refactor to be tackled later. - # - # See https://github.com/ibis-project/ibis/issues/2891 for the bug report - return True - elif target.is_boolean(): - if source.is_boolean(): - return True - elif source.is_integer(): - return value in (0, 1) - else: - return False - elif target.is_integer(): - # TODO(kszucs): ideally unsigned to signed shouldn't be allowed but that - # breaks the integral promotion rule logic in rules.py - if source.is_integer(): - if value is not None: - return normalizable(target, value) - else: - return source.nbytes <= target.nbytes - else: - return False - elif target.is_floating(): - if source.is_floating(): - return source.nbytes <= target.nbytes - else: - return source.is_integer() - elif target.is_decimal(): - if source.is_decimal(): - downcast_precision = ( - source.precision is not None - and target.precision is not None - and source.precision < target.precision - ) - downcast_scale = ( - source.scale is not None - and target.scale is not None - and source.scale < target.scale - ) - return not (downcast_precision or downcast_scale) - else: - return source.is_numeric() - elif target.is_string(): - return source.is_string() or source.is_uuid() - elif target.is_uuid(): - return source.is_uuid() or source.is_string() - elif target.is_date() or target.is_timestamp(): - if source.is_string(): - return value is not None and normalizable(target, value) - else: - return source.is_timestamp() or source.is_date() - elif target.is_interval(): - if source.is_interval(): - return source.unit == target.unit - else: - return source.is_integer() - elif target.is_time(): - if source.is_string(): - return value is not None and normalizable(target, value) - else: - return source.is_time() - elif target.is_json(): - return ( - source.is_json() - or source.is_string() - or source.is_floating() - or source.is_integer() - ) - elif target.is_array(): - return source.is_array() and castable(source.value_type, target.value_type) - elif target.is_map(): - return ( - source.is_map() - and castable(source.key_type, target.key_type) - and castable(source.value_type, target.value_type) - ) - elif target.is_struct(): - return source.is_struct() and all( - castable(source[field], target[field]) for field in target.names - ) - elif target.is_geospatial(): - return source.is_geospatial() or source.is_array() - else: - return isinstance(target, source.__class__) - - -@public -def higher_precedence(left: dt.DataType, right: dt.DataType) -> dt.DataType: - nullable = left.nullable or right.nullable - - if left.castable(right): - return right.copy(nullable=nullable) - elif right.castable(left): - return left.copy(nullable=nullable) - else: - raise IbisTypeError( - f"Cannot compute precedence for `{left}` and `{right}` types" - ) - - -@public -def highest_precedence(dtypes: Iterator[dt.DataType]) -> dt.DataType: - """Compute the highest precedence of `dtypes`.""" - if collected := list(dtypes): - return functools.reduce(higher_precedence, collected) - else: - return dt.null diff --git a/third_party/bigframes_vendored/ibis/expr/datatypes/core.py b/third_party/bigframes_vendored/ibis/expr/datatypes/core.py deleted file mode 100644 index 75bff716626..00000000000 --- a/third_party/bigframes_vendored/ibis/expr/datatypes/core.py +++ /dev/null @@ -1,1118 +0,0 @@ -# Contains code from https://github.com/ibis-project/ibis/blob/9.2.0/ibis/expr/datatypes/core.py - -from __future__ import annotations - -import datetime as pydatetime -import decimal as pydecimal -import numbers -import uuid as pyuuid -from abc import abstractmethod -from collections.abc import Iterable, Iterator, Mapping, Sequence -from numbers import Integral, Real -from typing import ( - Any, - Generic, - Literal, - NamedTuple, - Optional, - TypeVar, - get_args, - get_origin, - get_type_hints, -) - -import toolz -from bigframes_vendored.ibis.common.annotations import attribute -from bigframes_vendored.ibis.common.collections import FrozenOrderedDict, MapSet -from bigframes_vendored.ibis.common.dispatch import lazy_singledispatch -from bigframes_vendored.ibis.common.grounds import Concrete, Singleton -from bigframes_vendored.ibis.common.patterns import Coercible, CoercionError -from bigframes_vendored.ibis.common.temporal import IntervalUnit, TimestampUnit -from public import public -from typing_extensions import Self - - -@lazy_singledispatch -def dtype(value: Any, nullable: bool = True) -> DataType: - """Create a DataType object. - - Parameters - ---------- - value - The object to coerce to an Ibis DataType. Supported inputs include - strings, python type annotations, numpy dtypes, pandas dtypes, and - pyarrow types. - nullable - Whether the type should be nullable. Defaults to True. - - Examples - -------- - >>> import ibis - >>> ibis.dtype("int32") - Int32(nullable=True) - >>> ibis.dtype("array") - Array(value_type=Float64(nullable=True), nullable=True) - - DataType objects may also be created from Python types: - - >>> ibis.dtype(int) - Int64(nullable=True) - >>> ibis.dtype(list[float]) - Array(value_type=Float64(nullable=True), nullable=True) - - Or other type systems, like numpy/pandas/pyarrow types: - - >>> ibis.dtype(pa.int32()) - Int32(nullable=True) - - """ - if isinstance(value, DataType): - return value - else: - return DataType.from_typehint(value) - - -@dtype.register(str) -def from_string(value): - return DataType.from_string(value) - - -@dtype.register("numpy.dtype") -def from_numpy_dtype(value, nullable=True): - return DataType.from_numpy(value, nullable) - - -@dtype.register("pandas.core.dtypes.base.ExtensionDtype") -def from_pandas_extension_dtype(value, nullable=True): - return DataType.from_pandas(value, nullable) - - -@dtype.register("pyarrow.lib.DataType") -def from_pyarrow(value, nullable=True): - return DataType.from_pyarrow(value, nullable) - - -@dtype.register("polars.datatypes.classes.DataTypeClass") -def from_polars(value, nullable=True): - return DataType.from_polars(value, nullable) - - -# lock the dispatcher to prevent new types from being registered -del dtype.register - - -@public -class DataType(Concrete, Coercible): - """Base class for all data types. - - Instances are immutable. - """ - - nullable: bool = True - - @property - @abstractmethod - def scalar(self): ... - - @property - @abstractmethod - def column(self): ... - - # TODO(kszucs): remove it, prefer to use Annotable.__repr__ instead - @property - def _pretty_piece(self) -> str: - return "" - - # TODO(kszucs): should remove it, only used internally - @property - def name(self) -> str: - """Return the name of the data type.""" - return self.__class__.__name__ - - @classmethod - def __coerce__(cls, value, **kwargs): - if isinstance(value, cls): - return value - try: - return dtype(value) - except (TypeError, RuntimeError) as e: - raise CoercionError("Unable to coerce to a DataType") from e - - def __call__(self, **kwargs): - return self.copy(**kwargs) - - def __str__(self) -> str: - prefix = "!" * (not self.nullable) - return f"{prefix}{self.name.lower()}{self._pretty_piece}" - - def equals(self, other): - if not isinstance(other, DataType): - raise TypeError( - f"invalid equality comparison between DataType and {type(other)}" - ) - return self == other - - def cast(self, other, **kwargs): - # TODO(kszucs): remove it or deprecate it? - from bigframes_vendored.ibis.expr.datatypes.cast import cast - - return cast(self, other, **kwargs) - - def castable(self, to, **kwargs) -> bool: - """Check whether this type is castable to another.""" - from bigframes_vendored.ibis.expr.datatypes.cast import castable - - return castable(self, to, **kwargs) - - @classmethod - def from_typehint(cls, typ, nullable=True) -> Self: - origin_type = get_origin(typ) - - if origin_type is None: - if isinstance(typ, type): - if issubclass(typ, Parametric): - raise TypeError( - f"Cannot construct a parametric {typ.__name__} datatype based " - "on the type itself" - ) - elif issubclass(typ, DataType): - return typ(nullable=nullable) - elif typ is type(None): - return null - elif issubclass(typ, bool): - return Boolean(nullable=nullable) - elif issubclass(typ, bytes): - return Binary(nullable=nullable) - elif issubclass(typ, str): - return String(nullable=nullable) - elif issubclass(typ, Integral): - return Int64(nullable=nullable) - elif issubclass(typ, Real): - return Float64(nullable=nullable) - elif issubclass(typ, pydecimal.Decimal): - return Decimal(nullable=nullable) - elif issubclass(typ, pydatetime.datetime): - return Timestamp(nullable=nullable) - elif issubclass(typ, pydatetime.date): - return Date(nullable=nullable) - elif issubclass(typ, pydatetime.time): - return Time(nullable=nullable) - elif issubclass(typ, pydatetime.timedelta): - return Interval(unit="us", nullable=nullable) - elif issubclass(typ, pyuuid.UUID): - return UUID(nullable=nullable) - elif annots := get_type_hints(typ): - return Struct(toolz.valmap(dtype, annots), nullable=nullable) - else: - raise TypeError( - f"Cannot construct an ibis datatype from python type `{typ!r}`" - ) - else: - raise TypeError( - f"Cannot construct an ibis datatype from python value `{typ!r}`" - ) - elif issubclass(origin_type, (Sequence, Array)): - (value_type,) = map(dtype, get_args(typ)) - return Array(value_type) - elif issubclass(origin_type, (Mapping, Map)): - key_type, value_type = map(dtype, get_args(typ)) - return Map(key_type, value_type) - else: - raise TypeError(f"Value {typ!r} is not a valid datatype") - - @classmethod - def from_numpy(cls, numpy_type, nullable=True) -> Self: - """Return the equivalent ibis datatype.""" - from bigframes_vendored.ibis.formats.numpy import NumpyType - - return NumpyType.to_ibis(numpy_type, nullable=nullable) - - @classmethod - def from_pandas(cls, pandas_type, nullable=True) -> Self: - """Return the equivalent ibis datatype.""" - from bigframes_vendored.ibis.formats.pandas import PandasType - - return PandasType.to_ibis(pandas_type, nullable=nullable) - - @classmethod - def from_pyarrow(cls, arrow_type, nullable=True) -> Self: - """Return the equivalent ibis datatype.""" - from bigframes_vendored.ibis.formats.pyarrow import PyArrowType - - return PyArrowType.to_ibis(arrow_type, nullable=nullable) - - @classmethod - def from_polars(cls, polars_type, nullable=True) -> Self: - """Return the equivalent ibis datatype.""" - from bigframes_vendored.ibis.formats.polars import PolarsType - - return PolarsType.to_ibis(polars_type, nullable=nullable) - - def to_numpy(self): - """Return the equivalent numpy datatype.""" - from bigframes_vendored.ibis.formats.numpy import NumpyType - - return NumpyType.from_ibis(self) - - def to_pandas(self): - """Return the equivalent pandas datatype.""" - from bigframes_vendored.ibis.formats.pandas import PandasType - - return PandasType.from_ibis(self) - - def to_pyarrow(self): - """Return the equivalent pyarrow datatype.""" - from bigframes_vendored.ibis.formats.pyarrow import PyArrowType - - return PyArrowType.from_ibis(self) - - def to_polars(self): - """Return the equivalent polars datatype.""" - from bigframes_vendored.ibis.formats.polars import PolarsType - - return PolarsType.from_ibis(self) - - def is_array(self) -> bool: - """Return True if an instance of an Array type.""" - return isinstance(self, Array) - - def is_binary(self) -> bool: - """Return True if an instance of a Binary type.""" - return isinstance(self, Binary) - - def is_boolean(self) -> bool: - """Return True if an instance of a Boolean type.""" - return isinstance(self, Boolean) - - def is_date(self) -> bool: - """Return True if an instance of a Date type.""" - return isinstance(self, Date) - - def is_decimal(self) -> bool: - """Return True if an instance of a Decimal type.""" - return isinstance(self, Decimal) - - def is_enum(self) -> bool: - """Return True if an instance of an Enum type.""" - return isinstance(self, Enum) - - def is_float16(self) -> bool: - """Return True if an instance of a Float16 type.""" - return isinstance(self, Float16) - - def is_float32(self) -> bool: - """Return True if an instance of a Float32 type.""" - return isinstance(self, Float32) - - def is_float64(self) -> bool: - """Return True if an instance of a Float64 type.""" - return isinstance(self, Float64) - - def is_floating(self) -> bool: - """Return True if an instance of any Floating type.""" - return isinstance(self, Floating) - - def is_geospatial(self) -> bool: - """Return True if an instance of a Geospatial type.""" - return isinstance(self, GeoSpatial) - - def is_inet(self) -> bool: - """Return True if an instance of an Inet type.""" - return isinstance(self, INET) - - def is_int16(self) -> bool: - """Return True if an instance of an Int16 type.""" - return isinstance(self, Int16) - - def is_int32(self) -> bool: - """Return True if an instance of an Int32 type.""" - return isinstance(self, Int32) - - def is_int64(self) -> bool: - """Return True if an instance of an Int64 type.""" - return isinstance(self, Int64) - - def is_int8(self) -> bool: - """Return True if an instance of an Int8 type.""" - return isinstance(self, Int8) - - def is_integer(self) -> bool: - """Return True if an instance of any Integer type.""" - return isinstance(self, Integer) - - def is_interval(self) -> bool: - """Return True if an instance of an Interval type.""" - return isinstance(self, Interval) - - def is_json(self) -> bool: - """Return True if an instance of a JSON type.""" - return isinstance(self, JSON) - - def is_linestring(self) -> bool: - """Return True if an instance of a LineString type.""" - return isinstance(self, LineString) - - def is_macaddr(self) -> bool: - """Return True if an instance of a MACADDR type.""" - return isinstance(self, MACADDR) - - def is_map(self) -> bool: - """Return True if an instance of a Map type.""" - return isinstance(self, Map) - - def is_multilinestring(self) -> bool: - """Return True if an instance of a MultiLineString type.""" - return isinstance(self, MultiLineString) - - def is_multipoint(self) -> bool: - """Return True if an instance of a MultiPoint type.""" - return isinstance(self, MultiPoint) - - def is_multipolygon(self) -> bool: - """Return True if an instance of a MultiPolygon type.""" - return isinstance(self, MultiPolygon) - - def is_nested(self) -> bool: - """Return true if an instance of any nested (Array/Map/Struct) type.""" - return isinstance(self, (Array, Map, Struct)) - - def is_null(self) -> bool: - """Return true if an instance of a Null type.""" - return isinstance(self, Null) - - def is_numeric(self) -> bool: - """Return true if an instance of a Numeric type.""" - return isinstance(self, Numeric) - - def is_point(self) -> bool: - """Return true if an instance of a Point type.""" - return isinstance(self, Point) - - def is_polygon(self) -> bool: - """Return true if an instance of a Polygon type.""" - return isinstance(self, Polygon) - - def is_primitive(self) -> bool: - """Return true if an instance of a Primitive type.""" - return isinstance(self, Primitive) - - def is_signed_integer(self) -> bool: - """Return true if an instance of a SignedInteger type.""" - return isinstance(self, SignedInteger) - - def is_string(self) -> bool: - """Return true if an instance of a String type.""" - return isinstance(self, String) - - def is_struct(self) -> bool: - """Return true if an instance of a Struct type.""" - return isinstance(self, Struct) - - def is_temporal(self) -> bool: - """Return true if an instance of a Temporal type.""" - return isinstance(self, Temporal) - - def is_time(self) -> bool: - """Return true if an instance of a Time type.""" - return isinstance(self, Time) - - def is_timestamp(self) -> bool: - """Return true if an instance of a Timestamp type.""" - return isinstance(self, Timestamp) - - def is_uint16(self) -> bool: - """Return true if an instance of a UInt16 type.""" - return isinstance(self, UInt16) - - def is_uint32(self) -> bool: - """Return true if an instance of a UInt32 type.""" - return isinstance(self, UInt32) - - def is_uint64(self) -> bool: - """Return true if an instance of a UInt64 type.""" - return isinstance(self, UInt64) - - def is_uint8(self) -> bool: - """Return true if an instance of a UInt8 type.""" - return isinstance(self, UInt8) - - def is_unknown(self) -> bool: - """Return true if an instance of an Unknown type.""" - return isinstance(self, Unknown) - - def is_unsigned_integer(self) -> bool: - """Return true if an instance of an UnsignedInteger type.""" - return isinstance(self, UnsignedInteger) - - def is_uuid(self) -> bool: - """Return true if an instance of a UUID type.""" - return isinstance(self, UUID) - - def is_variadic(self) -> bool: - """Return true if an instance of a Variadic type.""" - return isinstance(self, Variadic) - - -@public -class Unknown(DataType, Singleton): - """An unknown type.""" - - scalar = "UnknownScalar" - column = "UnknownColumn" - - -@public -class Primitive(DataType, Singleton): - """Values with known size.""" - - -# TODO(kszucs): consider to remove since we don't actually use this information -@public -class Variadic(DataType): - """Values with unknown size.""" - - -@public -class Parametric(DataType): - """Types that can be parameterized.""" - - -@public -class Null(Primitive): - """Null values.""" - - scalar = "NullScalar" - column = "NullColumn" - - -@public -class Boolean(Primitive): - """[](`True`) or [](`False`) values.""" - - scalar = "BooleanScalar" - column = "BooleanColumn" - - -@public -class Bounds(NamedTuple): - """The lower and upper bound of a fixed-size value.""" - - lower: int - upper: int - - def __contains__(self, value: int) -> bool: - return self.lower <= value <= self.upper - - -@public -class Numeric(DataType): - """Numeric types.""" - - -@public -class Integer(Primitive, Numeric): - """Integer values.""" - - scalar = "IntegerScalar" - column = "IntegerColumn" - - @property - @abstractmethod - def nbytes(self) -> int: - """Return the number of bytes used to store values of this type.""" - - -@public -class String(Variadic, Singleton): - """A type representing a string. - - Notes - ----- - Because of differences in the way different backends handle strings, we - cannot assume that strings are UTF-8 encoded. - - """ - - scalar = "StringScalar" - column = "StringColumn" - - -@public -class Binary(Variadic, Singleton): - """A type representing a sequence of bytes. - - Notes - ----- - Some databases treat strings and blobs of equally, and some do not. - - For example, Impala doesn't make a distinction between string and binary - types but PostgreSQL has a `TEXT` type and a `BYTEA` type which are - distinct types that have different behavior. - - """ - - scalar = "BinaryScalar" - column = "BinaryColumn" - - -@public -class Temporal(DataType): - """Data types related to time.""" - - -@public -class Date(Temporal, Primitive): - """Date values.""" - - scalar = "DateScalar" - column = "DateColumn" - - -@public -class Time(Temporal, Primitive): - """Time values.""" - - scalar = "TimeScalar" - column = "TimeColumn" - - -@public -class Timestamp(Temporal, Parametric): - """Timestamp values.""" - - timezone: Optional[str] = None - """The timezone of values of this type.""" - - # Literal[*range(10)] is only supported from 3.11 - scale: Optional[Literal[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]] = None - """The scale of the timestamp if known.""" - - scalar = "TimestampScalar" - column = "TimestampColumn" - - @classmethod - def from_unit(cls, unit, timezone=None, nullable=True): - """Return a timestamp type with the given unit and timezone.""" - unit = TimestampUnit(unit) - if unit == TimestampUnit.SECOND: - scale = 0 - elif unit == TimestampUnit.MILLISECOND: - scale = 3 - elif unit == TimestampUnit.MICROSECOND: - scale = 6 - elif unit == TimestampUnit.NANOSECOND: - scale = 9 - else: - # TODO: remove raise path as it's never triggered - # Timestamp op has a restriction that only the literal - # ints from 0 through 9 can be passed as scale - raise ValueError(f"Invalid unit {unit}") - return cls(scale=scale, timezone=timezone, nullable=nullable) - - @property - def unit(self) -> str: - """Return the unit of the timestamp.""" - if self.scale is None or self.scale == 0: - return TimestampUnit.SECOND - elif 1 <= self.scale <= 3: - return TimestampUnit.MILLISECOND - elif 4 <= self.scale <= 6: - return TimestampUnit.MICROSECOND - elif 7 <= self.scale <= 9: - return TimestampUnit.NANOSECOND - else: - # TODO: remove raise path as it's never triggered - # TimestampUnit, which is a (child of) Enum - # so it'll raise in the Enum class constructor instead - raise ValueError(f"Invalid scale {self.scale}") - - @property - def _pretty_piece(self) -> str: - if self.scale is not None and self.timezone is not None: - return f"('{self.timezone}', {self.scale:d})" - elif self.timezone is not None: - return f"('{self.timezone}')" - elif self.scale is not None: - return f"({self.scale:d})" - else: - return "" - - -@public -class SignedInteger(Integer): - """Signed integer values.""" - - @property - def bounds(self): - exp = self.nbytes * 8 - 1 - upper = (1 << exp) - 1 - return Bounds(lower=~upper, upper=upper) - - -@public -class UnsignedInteger(Integer): - """Unsigned integer values.""" - - @property - def bounds(self): - exp = self.nbytes * 8 - upper = (1 << exp) - 1 - return Bounds(lower=0, upper=upper) - - -@public -class Floating(Primitive, Numeric): - """Floating point values.""" - - scalar = "FloatingScalar" - column = "FloatingColumn" - - @property - @abstractmethod - def nbytes(self) -> int: # pragma: no cover - """Return the number of bytes used to store values of this type.""" - - -@public -class Int8(SignedInteger): - """Signed 8-bit integers.""" - - nbytes = 1 - - -@public -class Int16(SignedInteger): - """Signed 16-bit integers.""" - - nbytes = 2 - - -@public -class Int32(SignedInteger): - """Signed 32-bit integers.""" - - nbytes = 4 - - -@public -class Int64(SignedInteger): - """Signed 64-bit integers.""" - - nbytes = 8 - - -@public -class UInt8(UnsignedInteger): - """Unsigned 8-bit integers.""" - - nbytes = 1 - - -@public -class UInt16(UnsignedInteger): - """Unsigned 16-bit integers.""" - - nbytes = 2 - - -@public -class UInt32(UnsignedInteger): - """Unsigned 32-bit integers.""" - - nbytes = 4 - - -@public -class UInt64(UnsignedInteger): - """Unsigned 64-bit integers.""" - - nbytes = 8 - - -@public -class Float16(Floating): - """16-bit floating point numbers.""" - - nbytes = 2 - - -@public -class Float32(Floating): - """32-bit floating point numbers.""" - - nbytes = 4 - - -@public -class Float64(Floating): - """64-bit floating point numbers.""" - - nbytes = 8 - - -@public -class Decimal(Numeric, Parametric): - """Fixed-precision decimal values.""" - - precision: Optional[int] = None - """The number of decimal places values of this type can hold.""" - - scale: Optional[int] = None - """The number of values after the decimal point.""" - - scalar = "DecimalScalar" - column = "DecimalColumn" - - def __init__( - self, - precision: int | None = None, - scale: int | None = None, - **kwargs: Any, - ) -> None: - if precision is not None: - if not isinstance(precision, numbers.Integral): - raise TypeError( - f"Decimal type precision must be an integer; got {type(precision)}" - ) - if precision < 0: - raise ValueError("Decimal type precision cannot be negative") - if not precision: - raise ValueError("Decimal type precision cannot be zero") - if scale is not None: - if not isinstance(scale, numbers.Integral): - raise TypeError("Decimal type scale must be an integer") - if scale < 0: - raise ValueError("Decimal type scale cannot be negative") - if precision is not None and precision < scale: - raise ValueError( - "Decimal type precision must be greater than or equal to " - f"scale. Got precision={precision:d} and scale={scale:d}" - ) - super().__init__(precision=precision, scale=scale, **kwargs) - - @property - def _pretty_piece(self) -> str: - precision = self.precision - scale = self.scale - if precision is None and scale is None: - return "" - - args = [str(precision) if precision is not None else "_"] - - if scale is not None: - args.append(str(scale)) - - return f"({', '.join(args)})" - - -@public -class Interval(Parametric): - """Interval values.""" - - unit: IntervalUnit - """The time unit of the interval.""" - - scalar = "IntervalScalar" - column = "IntervalColumn" - - @property - def resolution(self): - """The interval unit's name.""" - return self.unit.singular - - @property - def _pretty_piece(self) -> str: - return f"('{self.unit.value}')" - - -@public -class Struct(Parametric, MapSet): - """Structured values.""" - - fields: FrozenOrderedDict[str, DataType] - - scalar = "StructScalar" - column = "StructColumn" - - @classmethod - def from_tuples( - cls, pairs: Iterable[tuple[str, str | DataType]], nullable: bool = True - ) -> Struct: - """Construct a `Struct` type from pairs. - - Parameters - ---------- - pairs - An iterable of pairs of field name and type - nullable - Whether the type is nullable - - Returns - ------- - Struct - Struct data type instance - - """ - return cls(dict(pairs), nullable=nullable) - - @attribute - def names(self) -> tuple[str, ...]: - """Return the names of the struct's fields.""" - return tuple(self.keys()) - - @attribute - def types(self) -> tuple[DataType, ...]: - """Return the types of the struct's fields.""" - return tuple(self.values()) - - def __len__(self) -> int: - return len(self.fields) - - def __iter__(self) -> Iterator[str]: - return iter(self.fields) - - def __getitem__(self, key: str) -> DataType: - return self.fields[key] - - def __repr__(self) -> str: - return f"'{self.name}({list(self.items())}, nullable={self.nullable})" - - @property - def _pretty_piece(self) -> str: - pairs = ", ".join(map("{}: {}".format, self.names, self.types)) - return f"<{pairs}>" - - -T = TypeVar("T", bound=DataType, covariant=True) - - -@public -class Array(Variadic, Parametric, Generic[T]): - """Array values.""" - - value_type: T - - scalar = "ArrayScalar" - column = "ArrayColumn" - - @property - def _pretty_piece(self) -> str: - return f"<{self.value_type}>" - - -K = TypeVar("K", bound=DataType, covariant=True) -V = TypeVar("V", bound=DataType, covariant=True) - - -@public -class Map(Variadic, Parametric, Generic[K, V]): - """Associative array values.""" - - key_type: K - value_type: V - - scalar = "MapScalar" - column = "MapColumn" - - @property - def _pretty_piece(self) -> str: - return f"<{self.key_type}, {self.value_type}>" - - -@public -class JSON(Variadic): - """JSON values.""" - - scalar = "JSONScalar" - column = "JSONColumn" - - binary: bool = False - """True if JSON is stored as binary, e.g., JSONB in PostgreSQL.""" - - @property - def _pretty_piece(self) -> str: - return "b" * self.binary - - -@public -class GeoSpatial(DataType): - """Geospatial values.""" - - geotype: Literal["geography", "geometry"] = "geometry" - """The specific geospatial type.""" - - srid: Optional[int] = None - """The spatial reference identifier.""" - - column = "GeoSpatialColumn" - scalar = "GeoSpatialScalar" - - @property - def _pretty_piece(self) -> str: - piece = "" - if self.geotype is not None: - piece += f":{self.geotype}" - if self.srid is not None: - piece += f";{self.srid}" - return piece - - -@public -class Point(GeoSpatial): - """A point described by two coordinates.""" - - scalar = "PointScalar" - column = "PointColumn" - - -@public -class LineString(GeoSpatial): - """A sequence of 2 or more points.""" - - scalar = "LineStringScalar" - column = "LineStringColumn" - - -@public -class Polygon(GeoSpatial): - """A set of one or more closed line strings. - - The first line string represents the shape (external ring) and the - rest represent holes in that shape (internal rings). - """ - - scalar = "PolygonScalar" - column = "PolygonColumn" - - -@public -class MultiLineString(GeoSpatial): - """A set of one or more line strings.""" - - scalar = "MultiLineStringScalar" - column = "MultiLineStringColumn" - - -@public -class MultiPoint(GeoSpatial): - """A set of one or more points.""" - - scalar = "MultiPointScalar" - column = "MultiPointColumn" - - -@public -class MultiPolygon(GeoSpatial): - """A set of one or more polygons.""" - - scalar = "MultiPolygonScalar" - column = "MultiPolygonColumn" - - -@public -class UUID(DataType): - """A 128-bit number used to identify information in computer systems.""" - - scalar = "UUIDScalar" - column = "UUIDColumn" - - -@public -class MACADDR(DataType): - """Media Access Control (MAC) address of a network interface.""" - - scalar = "MACADDRScalar" - column = "MACADDRColumn" - - -@public -class INET(DataType): - """IP addresses.""" - - scalar = "INETScalar" - column = "INETColumn" - - -# --------------------------------------------------------------------- - -null = Null() -boolean = Boolean() -int8 = Int8() -int16 = Int16() -int32 = Int32() -int64 = Int64() -uint8 = UInt8() -uint16 = UInt16() -uint32 = UInt32() -uint64 = UInt64() -float16 = Float16() -float32 = Float32() -float64 = Float64() -string = String() -binary = Binary() -date = Date() -time = Time() -timestamp = Timestamp() -# geo spatial data type -geometry = GeoSpatial(geotype="geometry") -geography = GeoSpatial(geotype="geography") -point = Point() -linestring = LineString() -polygon = Polygon() -multilinestring = MultiLineString() -multipoint = MultiPoint() -multipolygon = MultiPolygon() -# json -json = JSON(binary=False) -jsonb = JSON(binary=True) -# special string based data type -uuid = UUID() -macaddr = MACADDR() -inet = INET() -decimal = Decimal() -unknown = Unknown() - -Enum = String - - -public( - Any=DataType, - null=null, - boolean=boolean, - int8=int8, - int16=int16, - int32=int32, - int64=int64, - uint8=uint8, - uint16=uint16, - uint32=uint32, - uint64=uint64, - float16=float16, - float32=float32, - float64=float64, - string=string, - binary=binary, - date=date, - time=time, - timestamp=timestamp, - dtype=dtype, - geometry=geometry, - geography=geography, - point=point, - linestring=linestring, - polygon=polygon, - multilinestring=multilinestring, - multipoint=multipoint, - multipolygon=multipolygon, - json=json, - jsonb=jsonb, - uuid=uuid, - macaddr=macaddr, - inet=inet, - decimal=decimal, - unknown=unknown, - Enum=Enum, - Geography=GeoSpatial, - Geometry=GeoSpatial, - Set=Array, -) diff --git a/third_party/bigframes_vendored/ibis/expr/datatypes/value.py b/third_party/bigframes_vendored/ibis/expr/datatypes/value.py deleted file mode 100644 index 5856cb8cf94..00000000000 --- a/third_party/bigframes_vendored/ibis/expr/datatypes/value.py +++ /dev/null @@ -1,391 +0,0 @@ -# Contains code from https://github.com/ibis-project/ibis/blob/9.2.0/ibis/expr/datatypes/value.py - -from __future__ import annotations - -import collections -import datetime -import decimal -import enum -import ipaddress -import json -import uuid -from collections.abc import Mapping, Sequence -from functools import partial -from operator import attrgetter -from typing import Any - -import bigframes_vendored.ibis.expr.datatypes as dt -import pyarrow as pa -import toolz -from bigframes_vendored.ibis.common.collections import frozendict -from bigframes_vendored.ibis.common.dispatch import lazy_singledispatch -from bigframes_vendored.ibis.common.exceptions import IbisTypeError, InputTypeError -from bigframes_vendored.ibis.common.numeric import normalize_decimal -from bigframes_vendored.ibis.common.temporal import ( - IntervalUnit, - normalize_datetime, - normalize_timedelta, - normalize_timezone, -) -from bigframes_vendored.ibis.expr.datatypes.cast import highest_precedence -from public import public - - -@lazy_singledispatch -def infer(value: Any) -> dt.DataType: - """Infer the corresponding ibis dtype for a python object.""" - raise InputTypeError( - f"Unable to infer datatype of value {value!r} with type {type(value)}" - ) - - -# TODO(kszucs): support NamedTuples and dataclasses instead of OrderedDict -# which should trigger infer_map instead -@infer.register(collections.OrderedDict) -def infer_struct(value: Mapping[str, Any]) -> dt.Struct: - """Infer the [`Struct`](./datatypes.qmd#ibis.expr.datatypes.Struct) type of `value`.""" - if not value: - raise TypeError("Empty struct type not supported") - fields = {name: infer(val) for name, val in value.items()} - return dt.Struct(fields) - - -@infer.register(collections.abc.Mapping) -def infer_map(value: Mapping[Any, Any]) -> dt.Map: - """Infer the [`Map`](./datatypes.qmd#ibis.expr.datatypes.Map) type of `value`.""" - if not value: - return dt.Map(dt.null, dt.null) - try: - return dt.Map( - highest_precedence(map(infer, value.keys())), - highest_precedence(map(infer, value.values())), - ) - except IbisTypeError: - return dt.Struct(toolz.valmap(infer, value, factory=type(value))) - - -@infer.register((list, tuple, set, frozenset)) -def infer_list(values: Sequence[Any]) -> dt.Array: - """Infer the [`Array`](./datatypes.qmd#ibis.expr.datatypes.Array) type of `value`.""" - if not values: - return dt.Array(dt.null) - return dt.Array(highest_precedence(map(infer, values))) - - -@infer.register("pyarrow.Scalar") -def infer_pyarrow_scalar(value: "pa.Scalar"): - """Infert the type of a PyArrow Scalar value.""" - import bigframes_vendored.ibis.formats.pyarrow - - return bigframes_vendored.ibis.formats.pyarrow.PyArrowType.to_ibis(value.type) - - -@infer.register(datetime.time) -def infer_time(value: datetime.time) -> dt.Time: - return dt.time - - -@infer.register(datetime.date) -def infer_date(value: datetime.date) -> dt.Date: - return dt.date - - -@infer.register(datetime.datetime) -def infer_timestamp(value: datetime.datetime) -> dt.Timestamp: - if value.tzinfo: - return dt.Timestamp(timezone=str(value.tzinfo)) - else: - return dt.timestamp - - -@infer.register(datetime.timedelta) -def infer_interval(value: datetime.timedelta) -> dt.Interval: - # datetime.timedelta only stores days, seconds, and microseconds internally - if value.days: - if value.seconds or value.microseconds: - raise ValueError( - "Unable to infer interval type from mixed units, " - "use ibis.interval(timedelta) instead" - ) - else: - return dt.Interval(IntervalUnit.DAY) - elif value.seconds: - if value.microseconds: - return dt.Interval(IntervalUnit.MICROSECOND) - else: - return dt.Interval(IntervalUnit.SECOND) - elif value.microseconds: - return dt.Interval(IntervalUnit.MICROSECOND) - else: - raise ValueError("Unable to infer interval type from zero value") - - -@infer.register(str) -def infer_string(value: str) -> dt.String: - return dt.string - - -@infer.register(bytes) -def infer_bytes(value: bytes) -> dt.Binary: - return dt.binary - - -@infer.register(float) -def infer_floating(value: float) -> dt.Float64: - return dt.float64 - - -@infer.register(int) -def infer_integer(value: int, prefer_unsigned: bool = False) -> dt.Integer: - types = (dt.uint8, dt.uint16, dt.uint32, dt.uint64) if prefer_unsigned else () - types += (dt.int8, dt.int16, dt.int32, dt.int64) - for dtype in types: - if dtype.bounds.lower <= value <= dtype.bounds.upper: - return dtype - return dt.uint64 if prefer_unsigned else dt.int64 - - -@infer.register(enum.Enum) -def infer_enum(_: enum.Enum) -> dt.String: - return dt.string - - -@infer.register(decimal.Decimal) -def infer_decimal(value: decimal.Decimal) -> dt.Decimal: - """Infer the [`Decimal`](./datatypes.qmd#ibis.expr.datatypes.Decimal) type of `value`.""" - return dt.decimal - - -@infer.register(bool) -def infer_boolean(value: bool) -> dt.Boolean: - return dt.boolean - - -@infer.register((type(None), dt.Null)) -def infer_null(value: dt.Null | None) -> dt.Null: - return dt.null - - -@infer.register((ipaddress.IPv4Address, ipaddress.IPv6Address)) -def infer_ipaddr( - _: ipaddress.IPv4Address | ipaddress.IPv6Address | None, -) -> dt.INET: - return dt.inet - - -@infer.register("numpy.generic") -def infer_numpy_scalar(value): - from bigframes_vendored.ibis.formats.numpy import NumpyType - - return NumpyType.to_ibis(value.dtype) - - -@infer.register("pandas.Timestamp") -def infer_pandas_timestamp(value): - if value.tz is not None: - return dt.Timestamp(timezone=str(value.tz)) - else: - return dt.timestamp - - -@infer.register("pandas.Timedelta") -def infer_interval_pandas(value) -> dt.Interval: - # pandas Timedelta has more granularity - units = { - "D": "d", - "H": "h", - "h": "h", - "T": "m", - "min": "m", - "S": "s", - "s": "s", - "L": "ms", - "ms": "ms", - "U": "us", - "us": "us", - "N": "ns", - "ns": "ns", - } - unit = units[value.resolution_string] - return dt.Interval(unit) - - -@infer.register("numpy.ndarray") -@infer.register("pandas.Series") -def infer_numpy_array(value): - from bigframes_vendored.ibis.formats.numpy import NumpyType - from bigframes_vendored.ibis.formats.pyarrow import PyArrowData - - if value.dtype.kind == "O": - value_dtype = PyArrowData.infer_column(value) - else: - value_dtype = NumpyType.to_ibis(value.dtype) - - return dt.Array(value_dtype) - - -@infer.register("shapely.geometry.Point") -def infer_shapely_point(value) -> dt.Point: - return dt.point - - -@infer.register("shapely.geometry.LineString") -def infer_shapely_linestring(value) -> dt.LineString: - return dt.linestring - - -@infer.register("shapely.geometry.Polygon") -def infer_shapely_polygon(value) -> dt.Polygon: - return dt.polygon - - -@infer.register("shapely.geometry.MultiLineString") -def infer_shapely_multilinestring(value) -> dt.MultiLineString: - return dt.multilinestring - - -@infer.register("shapely.geometry.MultiPoint") -def infer_shapely_multipoint(value) -> dt.MultiPoint: - return dt.multipoint - - -@infer.register("shapely.geometry.MultiPolygon") -def infer_shapely_multipolygon(value) -> dt.MultiPolygon: - return dt.multipolygon - - -# lock the dispatcher to prevent adding new implementations -del infer.register - - -# TODO(kszucs): should raise ValueError instead of TypeError -def normalize(typ, value): - """Ensure that the Python type underlying a literal resolves to a single type.""" - - if pa is not None and isinstance(value, pa.Scalar): - value = value.as_py() - - dtype = dt.dtype(typ) - if value is None: - if not dtype.nullable: - raise TypeError(f"Cannot convert `None` to non-nullable type {typ!r}") - return None - - if dtype.is_boolean(): - try: - return bool(value) - except ValueError: - raise TypeError(f"Unable to normalize {value!r} to {dtype!r}") - elif dtype.is_integer(): - try: - value = int(value) - except ValueError: - raise TypeError(f"Unable to normalize {value!r} to {dtype!r}") - if value not in dtype.bounds: - raise TypeError( - f"Value {value} is out of bounds for type {dtype!r} " - f"(bounds: {dtype.bounds})" - ) - else: - return value - elif dtype.is_floating(): - try: - return float(value) - except ValueError: - raise TypeError(f"Unable to normalize {value!r} to {dtype!r}") - elif dtype.is_json(): - if isinstance(value, str): - try: - json.loads(value) - except json.JSONDecodeError: - raise TypeError(f"Invalid JSON string: {value!r}") - else: - return value - else: - return json.dumps(value) - elif dtype.is_binary(): - return bytes(value) - elif dtype.is_string() or dtype.is_macaddr() or dtype.is_inet(): - return str(value) - elif dtype.is_decimal(): - return normalize_decimal(value, precision=dtype.precision, scale=dtype.scale) - elif dtype.is_uuid(): - return value if isinstance(value, uuid.UUID) else uuid.UUID(value) - elif dtype.is_array(): - return tuple(normalize(dtype.value_type, item) for item in value) - elif dtype.is_map(): - return frozendict({k: normalize(dtype.value_type, v) for k, v in value.items()}) - elif dtype.is_struct(): - if not isinstance(value, Mapping): - raise TypeError(f"Unable to normalize {dtype} from non-mapping {value!r}") - if missing_keys := (dtype.keys() - value.keys()): - raise TypeError( - f"Unable to normalize {value!r} to {dtype} because of missing keys {missing_keys!r}" - ) - return frozendict({k: normalize(t, value[k]) for k, t in dtype.items()}) - elif dtype.is_geospatial(): - import shapely - import shapely.geometry - - if isinstance(value, (tuple, list)): - if dtype.is_point(): - return shapely.geometry.Point(value) - elif dtype.is_linestring(): - return shapely.geometry.LineString(value) - elif dtype.is_polygon(): - return shapely.geometry.Polygon( - toolz.concat( - map( - attrgetter("coords"), - map(partial(normalize, dt.linestring), value), - ) - ) - ) - elif dtype.is_multipoint(): - return shapely.geometry.MultiPoint( - tuple(map(partial(normalize, dt.point), value)) - ) - elif dtype.is_multilinestring(): - return shapely.geometry.MultiLineString( - tuple(map(partial(normalize, dt.linestring), value)) - ) - elif dtype.is_multipolygon(): - return shapely.geometry.MultiPolygon( - map(partial(normalize, dt.polygon), value) - ) - else: - raise IbisTypeError(f"Unsupported geospatial type: {dtype}") - elif isinstance(value, shapely.geometry.base.BaseGeometry): - return value - else: - return shapely.from_wkt(value) - elif dtype.is_date(): - return normalize_datetime(value).date() - elif dtype.is_time(): - return normalize_datetime(value).time() - elif dtype.is_timestamp(): - value = normalize_datetime(value) - tzinfo = normalize_timezone(dtype.timezone) - if tzinfo is None: - return value - elif value.tzinfo is None or value.tzinfo.utcoffset(value) is None: - return value.replace(tzinfo=tzinfo) - else: - return value.astimezone(tzinfo) - elif dtype.is_interval(): - return normalize_timedelta(value, dtype.unit) - else: - raise TypeError(f"Unable to normalize {value!r} to {dtype!r}") - - -def normalizable(typ, value): - """Check if a value can be normalized to a given type.""" - try: - normalize(typ, value) - except TypeError: - return False - else: - return True - - -public(infer=infer, normalize=normalize) diff --git a/third_party/bigframes_vendored/ibis/expr/decompile.py b/third_party/bigframes_vendored/ibis/expr/decompile.py deleted file mode 100644 index 62913c9fa90..00000000000 --- a/third_party/bigframes_vendored/ibis/expr/decompile.py +++ /dev/null @@ -1,482 +0,0 @@ -# Contains code from https://github.com/ibis-project/ibis/blob/9.2.0/ibis/expr/decompile.py - -from __future__ import annotations - -import collections -import functools -import io -import itertools - -import bigframes_vendored.ibis.expr.datatypes as dt -import bigframes_vendored.ibis.expr.operations as ops -import bigframes_vendored.ibis.expr.types as ibis_types -import bigframes_vendored.ibis.expr.types as ir -from bigframes_vendored.ibis.common.graph import Graph -from bigframes_vendored.ibis.expr.rewrites import simplify -from bigframes_vendored.ibis.util import experimental - -_method_overrides = { - ops.CountDistinct: "nunique", - ops.CountStar: "count", - ops.EndsWith: "endswith", - ops.ExtractDay: "day", - ops.ExtractDayOfYear: "day_of_year", - ops.ExtractEpochSeconds: "epoch_seconds", - ops.ExtractHour: "hour", - ops.ExtractMicrosecond: "microsecond", - ops.ExtractMillisecond: "millisecond", - ops.ExtractMinute: "minute", - ops.ExtractMinute: "minute", - ops.ExtractMonth: "month", - ops.ExtractQuarter: "quarter", - ops.ExtractSecond: "second", - ops.ExtractWeekOfYear: "week_of_year", - ops.ExtractYear: "year", - ops.Intersection: "intersect", - ops.IsNull: "isnull", - ops.Lowercase: "lower", - ops.RegexSearch: "re_search", - ops.StartsWith: "startswith", - ops.StringContains: "contains", - ops.StringSQLILike: "ilike", - ops.StringSQLLike: "like", - ops.TimestampNow: "now", -} - - -def _to_snake_case(camel_case): - """Convert a camelCase string to snake_case.""" - result = list(camel_case[:1].lower()) - for char in camel_case[1:]: - if char.isupper(): - result.append("_") - result.append(char.lower()) - return "".join(result) - - -def _get_method_name(op): - typ = op.__class__ - try: - return _method_overrides[typ] - except KeyError: - return _to_snake_case(typ.__name__) - - -def _maybe_add_parens(op, string): - if isinstance(op, ops.Binary): - return f"({string})" - elif isinstance(string, CallStatement): - return string.args - else: - return string - - -class CallStatement: - def __init__(self, func, args): - self.func = func - self.args = args - - def __str__(self): - return f"{self.func}({self.args})" - - -@functools.singledispatch -def translate(op, *args, **kwargs): - """Translate an ibis operation into a Python expression.""" - raise NotImplementedError(op) - - -@translate.register(ops.Value) -def value(op, *args, **kwargs): - method = _get_method_name(op) - kwargs = [(k, v) for k, v in kwargs.items() if v is not None] - - if args: - this, *args = args - else: - (_, this), *kwargs = kwargs - - # if there is a single keyword argument prefer to pass that as positional - if not args and len(kwargs) == 1: - args = [kwargs[0][1]] - kwargs = [] - - args = ", ".join(map(str, args)) - kwargs = ", ".join(f"{k}={v}" for k, v in kwargs) - parameters = ", ".join(filter(None, [args, kwargs])) - - return f"{this}.{method}({parameters})" - - -@translate.register(ops.ScalarParameter) -def scalar_parameter(op, dtype, counter): - return f"ibis.param({str(dtype)!r})" - - -@translate.register(ops.UnboundTable) -@translate.register(ops.DatabaseTable) -def table(op, schema, name, **kwargs): - fields = dict(zip(schema.names, map(str, schema.types))) - return f"ibis.table(name={name!r}, schema={fields})" - - -def _try_unwrap(stmt): - if len(stmt) == 1: - return stmt[0] - else: - stmt = map(str, stmt) - values = ", ".join(stmt) - return f"[{values}]" - - -def _wrap_alias(values, rendered): - result = [] - for k, v in values.items(): - text = rendered[k] - if v.name != k: - if isinstance(v, ops.Binary): - text = f"({text}).name({k!r})" - else: - text = f"{text}.name({k!r})" - result.append(text) - return result - - -def _inline(args): - return ", ".join(map(str, args)) - - -@translate.register(ops.Project) -def project(op, parent, values): - out = f"{parent}" - if not values: - return out - - values = _wrap_alias(op.values, values) - return f"{out}.select({_inline(values)})" - - -@translate.register(ops.Filter) -def filter_(op, parent, predicates): - out = f"{parent}" - if predicates: - out = f"{out}.filter({_inline(predicates)})" - return out - - -@translate.register(ops.Sort) -def sort(op, parent, keys): - out = f"{parent}" - if keys: - out = f"{out}.order_by({_inline(keys)})" - return out - - -@translate.register(ops.Aggregate) -def aggregation(op, parent, groups, metrics): - groups = _wrap_alias(op.groups, groups) - metrics = _wrap_alias(op.metrics, metrics) - if groups and metrics: - return f"{parent}.aggregate([{_inline(metrics)}], by=[{_inline(groups)}])" - elif metrics: - return f"{parent}.aggregate([{_inline(metrics)}])" - else: - raise ValueError("No metrics to aggregate") - - -@translate.register(ops.Distinct) -def distinct(op, parent): - return f"{parent}.distinct()" - - -@translate.register(ops.DropColumns) -def drop(op, parent, columns_to_drop): - return f"{parent}.drop({_inline(map(repr, columns_to_drop))})" - - -@translate.register(ops.SelfReference) -def self_reference(op, parent, identifier): - return f"{parent}.view()" - - -@translate.register(ops.JoinReference) -def join_reference(op, parent, identifier): - return parent - - -@translate.register(ops.JoinLink) -def join_link(op, table, predicates, how): - return f".{how}_join({table}, {_try_unwrap(predicates)})" - - -@translate.register(ops.JoinChain) -def join(op, first, rest, values): - calls = "".join(rest) - pieces = [f"{first}{calls}"] - if values: - values = _wrap_alias(op.values, values) - pieces.append(f"select({_inline(values)})") - result = ".".join(pieces) - return result - - -@translate.register(ops.Set) -def union(op, left, right, distinct): - method = _get_method_name(op) - if distinct: - return f"{left}.{method}({right}, distinct=True)" - else: - return f"{left}.{method}({right})" - - -@translate.register(ops.Limit) -def limit(op, parent, n, offset): - if offset: - return f"{parent}.limit({n}, {offset})" - else: - return f"{parent}.limit({n})" - - -@translate.register(ops.Field) -def table_column(op, rel, name): - if name.isidentifier(): - return f"{rel}.{name}" - return f"{rel}[{name!r}]" - - -@translate.register(ops.SortKey) -def sort_key(op, expr, ascending, nulls_first): - method = "asc" if ascending else "desc" - call = f"{expr}.{method}" - if nulls_first: - return f"{call}(nulls_first={nulls_first})" - return f"{call}()" - - -@translate.register(ops.Reduction) -def reduction(op, arg, where, **kwargs): - method = _get_method_name(op) - return f"{arg}.{method}()" - - -@translate.register(ops.Alias) -def alias(op, arg, name): - arg = _maybe_add_parens(op.arg, arg) - return f"{arg}.name({name!r})" - - -@translate.register(ops.Constant) -def constant(op, **kwargs): - method = _get_method_name(op) - return f"ibis.{method}()" - - -@translate.register(ops.Literal) -def literal(op, value, dtype): - inferred = ibis_types.literal(value) - - if isinstance(op.dtype, dt.Timestamp): - return f'ibis.timestamp("{value}")' - elif isinstance(op.dtype, dt.Date): - return f"ibis.date({value!r})" - elif isinstance(op.dtype, dt.Interval): - return f"ibis.interval({value!r})" - elif inferred.type() != op.dtype: - return CallStatement("ibis.literal", f"{value!r}, {dtype}") - else: - # prefer plain python literal values if the inferred datatype is the same, - # though this makes rendering method calls on literals more complicated - return CallStatement("ibis.literal", repr(value)) - - -@translate.register(ops.Cast) -def cast(op, arg, to): - return f"{arg}.cast({str(to)!r})" - - -@translate.register(ops.Between) -def between(op, arg, lower_bound, upper_bound): - return f"{arg}.between({lower_bound}, {upper_bound})" - - -@translate.register(ops.IfElse) -def ifelse(op, bool_expr, true_expr, false_null_expr): - return f"{bool_expr}.ifelse({true_expr}, {false_null_expr})" - - -@translate.register(ops.SimpleCase) -@translate.register(ops.SearchedCase) -def switch_case(op, cases, results, default, base=None): - out = f"{base}.case()" if base else "ibis.case()" - - for case, result in zip(cases, results): - out = f"{out}.when({case}, {result})" - - if default is not None: - out = f"{out}.else_({default})" - - return f"{out}.end()" - - -_infix_ops = { - ops.Equals: "==", - ops.NotEquals: "!=", - ops.GreaterEqual: ">=", - ops.Greater: ">", - ops.LessEqual: "<=", - ops.Less: "<", - ops.And: "and", - ops.Or: "or", - ops.Add: "+", - ops.Subtract: "-", - ops.Multiply: "*", - ops.Divide: "/", - ops.Power: "**", - ops.Modulus: "%", - ops.TimestampAdd: "+", - ops.TimestampSub: "-", - ops.TimestampDiff: "-", -} - - -@translate.register(ops.Binary) -def binary(op, left, right): - operator = _infix_ops[type(op)] - left = _maybe_add_parens(op.left, left) - right = _maybe_add_parens(op.right, right) - return f"{left} {operator} {right}" - - -@translate.register(ops.InValues) -def isin(op, value, options): - return f"{value}.isin(({', '.join([str(option) for option in options])}))" - - -class CodeContext: - always_assign = ( - ops.ScalarParameter, - ops.Aggregate, - ops.PhysicalTable, - ops.SelfReference, - ) - - always_ignore = ( - ops.JoinReference, - ops.Field, - dt.Primitive, - dt.Variadic, - dt.Temporal, - ) - shorthands = { - ops.Aggregate: "agg", - ops.Literal: "lit", - ops.ScalarParameter: "param", - ops.Project: "p", - ops.Relation: "r", - ops.Filter: "f", - ops.Sort: "s", - } - - def __init__(self, assign_result_to="result"): - self.assign_result_to = assign_result_to - self._shorthand_counters = collections.defaultdict(itertools.count) - - def variable_for(self, node): - klass = type(node) - if isinstance(node, ops.Relation) and hasattr(node, "name"): - name = node.name - elif klass in self.shorthands: - name = self.shorthands[klass] - else: - name = klass.__name__.lower() - - # increment repeated type names: table, table1, table2, ... - nth = next(self._shorthand_counters[name]) or "" - return f"{name}{nth}" - - def render(self, node, code, n_dependents): - isroot = n_dependents == 0 - ignore = isinstance(node, self.always_ignore) - assign = n_dependents > 1 or isinstance(node, self.always_assign) - - # depending on the conditions return with (output code, node result) pairs - if not code: - return (None, None) - elif isroot: - if self.assign_result_to: - out = f"\n{self.assign_result_to} = {code}\n" - else: - out = str(code) - return (out, code) - elif ignore: - return (None, code) - elif assign: - var = self.variable_for(node) - out = f"{var} = {code}\n" - return (out, var) - else: - return (None, code) - - -@experimental -def decompile( - expr: ir.Expr, - render_import: bool = True, - assign_result_to: str = "result", - format: bool = False, -) -> str: - """Decompile an ibis expression into Python source code. - - Parameters - ---------- - expr - node or expression to decompile - render_import - Whether to add `import ibis` to the result. - assign_result_to - Variable name to store the result at, pass None to avoid assignment. - format - Whether to format the generated code using black code formatter. - - Returns - ------- - str - Equivalent Python source code for `node`. - - """ - if not isinstance(expr, ir.Expr): - raise TypeError(f"Expected ibis expression, got {type(expr).__name__}") - - node = expr.op() - node = simplify(node) - out = io.StringIO() - ctx = CodeContext(assign_result_to=assign_result_to) - dependents = Graph(node).invert() - - def fn(node, _, *args, **kwargs): - code = translate(node, *args, **kwargs) - n_dependents = len(dependents[node]) - - code, result = ctx.render(node, code, n_dependents) - if code: - out.write(code) - - return result - - node.map(fn) - - result = out.getvalue() - if render_import: - result = f"import bigframes_vendored.ibis\n\n\n{result}" - - if format: - try: - import black - except ImportError: - raise ImportError( - "The 'format' option requires the 'black' package to be installed" - ) - - result = black.format_str(result, mode=black.FileMode()) - - return result diff --git a/third_party/bigframes_vendored/ibis/expr/format.py b/third_party/bigframes_vendored/ibis/expr/format.py deleted file mode 100644 index 27eac21ddeb..00000000000 --- a/third_party/bigframes_vendored/ibis/expr/format.py +++ /dev/null @@ -1,373 +0,0 @@ -# Contains code from https://github.com/ibis-project/ibis/blob/9.2.0/ibis/expr/format.py - -from __future__ import annotations - -import functools -import itertools -import textwrap -import types -from collections.abc import Mapping, Sequence -from typing import Optional - -import bigframes_vendored.ibis -import bigframes_vendored.ibis.expr.datatypes as dt -import bigframes_vendored.ibis.expr.operations as ops -from bigframes_vendored.ibis import util -from bigframes_vendored.ibis.common.graph import Node -from public import public - -_infix_ops = { - # comparison operations - ops.Equals: "==", - ops.IdenticalTo: "===", - ops.NotEquals: "!=", - ops.Less: "<", - ops.LessEqual: "<=", - ops.Greater: ">", - ops.GreaterEqual: ">=", - # arithmetic operations - ops.Add: "+", - ops.Subtract: "-", - ops.Multiply: "*", - ops.Divide: "/", - ops.FloorDivide: "//", - ops.Modulus: "%", - ops.Power: "**", - # temporal operations - ops.DateAdd: "+", - ops.DateSub: "-", - ops.DateDiff: "-", - ops.TimeAdd: "+", - ops.TimeSub: "-", - ops.TimeDiff: "-", - ops.TimestampAdd: "+", - ops.TimestampSub: "-", - ops.TimestampDiff: "-", - ops.IntervalAdd: "+", - ops.IntervalSubtract: "-", - ops.IntervalMultiply: "*", - ops.IntervalFloorDivide: "//", - # boolean operators - ops.And: "&", - ops.Or: "|", - ops.Xor: "^", -} - - -def type_info(datatype) -> str: - """Format `datatype` for display next to a column.""" - return f" # {datatype}" * bigframes_vendored.ibis.options.repr.show_types - - -def truncate(pieces: Sequence[str], limit: int) -> list[str]: - if limit < 1: - raise ValueError("limit must be >= 1") - elif limit == 1: - return pieces[-1:] - elif limit >= len(pieces): - return pieces - - first_n = limit // 2 - last_m = limit - first_n - first, last = pieces[:first_n], pieces[-last_m:] - - maxlen = max(*map(len, first), *map(len, last)) - ellipsis = util.VERTICAL_ELLIPSIS.center(maxlen) - - return [*first, ellipsis, *last] - - -def render(obj, indent_level=0, limit_items=None, key_separator=":"): - if isinstance(obj, str): - result = obj - elif isinstance(obj, Mapping): - rendered = {f"{k}{key_separator}": render(v) for k, v in obj.items() if v} - if not rendered: - return "" - maxlen = max(map(len, rendered.keys())) - lines = [f"{k:<{maxlen}} {v}" for k, v in rendered.items()] - if limit_items is not None: - lines = truncate(lines, limit_items) - result = "\n".join(lines) - elif isinstance(obj, Sequence): - lines = tuple(render(item) for item in obj) - if limit_items is not None: - lines = truncate(lines, limit_items) - result = "\n".join(lines) - else: - result = str(obj) - - return util.indent(result, spaces=indent_level * 2) - - -def render_fields(fields, indent_level=0, limit_items=None): - rendered = {k: render(v, 1) for k, v in fields.items() if v} - lines = [f"{k}:\n{v}" for k, v in rendered.items()] - if limit_items is not None: - lines = truncate(lines, limit_items) - result = "\n".join(lines) - return util.indent(result, spaces=indent_level * 2) - - -def render_schema(schema, indent_level=0, limit_items=None): - if not len(schema): - return util.indent("", spaces=indent_level * 2) - if limit_items is None: - limit_items = bigframes_vendored.ibis.options.repr.table_columns - return render(schema, indent_level, limit_items, key_separator="") - - -def inline(obj): - if isinstance(obj, Mapping): - fields = ", ".join(f"{k!r}: {inline(v)}" for k, v in obj.items()) - return f"{{{fields}}}" - elif util.is_iterable(obj): - elems = ", ".join(inline(item) for item in obj) - return f"[{elems}]" - elif isinstance(obj, types.FunctionType): - return obj.__name__ - elif isinstance(obj, dt.DataType): - return str(obj) - else: - return repr(obj) - - -def inline_args(fields, prefer_positional=False): - fields = {k: inline(v) for k, v in fields.items() if v} - - if fields and prefer_positional: - first, *rest = fields.keys() - if not rest: - return fields[first] - elif first in {"arg", "expr"}: - first = fields[first] - rest = (f"{k}={fields[k]}" for k in rest) - return ", ".join((first, *rest)) - - return ", ".join(f"{k}={v}" for k, v in fields.items()) - - -class Rendered(str): - def __repr__(self): - return self - - -@public -def pretty(node: Node, scope: Optional[dict[str, Node]] = None) -> str: - """Pretty print an expression. - - Parameters - ---------- - node - The graph node to pretty print. - scope - A dictionary of expression to name mappings used to intermediate - assignments. If not provided aliases will be generated for each - relation. - - Returns - ------- - str - A pretty printed representation of the expression. - """ - if not isinstance(node, Node): - raise TypeError(f"Expected a graph node, got {type(node)}") - - refs = {} - refcnt = itertools.count() - variables = {v.op(): k for k, v in (scope or {}).items()} - - def mapper(op, _, **kwargs): - result = fmt(op, **kwargs) - if var := variables.get(op): - refs[op] = result - result = var - elif isinstance(op, ops.Relation) and not isinstance(op, ops.JoinReference): - refs[op] = result - result = f"r{next(refcnt)}" - return Rendered(result) - - results = node.map(mapper) - - out = [] - for ref, rendered in refs.items(): - if ref is not node: - out.append(f"{results[ref]} := {rendered}") - - res = refs.get(node, results[node]) - if isinstance(node, ops.Literal): - out.append(res) - elif isinstance(node, ops.Value): - out.append(f"{node.name}: {res}{type_info(node.dtype)}") - else: - out.append(res) - - return "\n\n".join(out) - - -@functools.singledispatch -def fmt(op, **kwargs): - top = f"{op.__class__.__name__}\n" - return top + render_fields(kwargs, 1) - - -@fmt.register(ops.Relation) -def _relation(op, parent=None, **kwargs): - if parent is None: - top = f"{op.__class__.__name__}\n" - else: - top = f"{op.__class__.__name__}[{parent}]\n" - kwargs["schema"] = render_schema(op.schema) - return top + render_fields(kwargs, 1) - - -@fmt.register(ops.PhysicalTable) -def _physical_table(op, name, **kwargs): - schema = render_schema(op.schema, indent_level=1) - return f"{op.__class__.__name__}: {name}\n{schema}" - - -@fmt.register(ops.UnboundTable) -@fmt.register(ops.DatabaseTable) -def _unbound_table(op, name, **kwargs): - schema = render_schema(op.schema, indent_level=1) - name = ".".join(filter(None, op.namespace.args + (name,))) - return f"{op.__class__.__name__}: {name}\n{schema}" - - -@fmt.register(ops.InMemoryTable) -def _in_memory_table(op, data, **kwargs): - import rich.pretty - - name = f"{op.__class__.__name__}\n" - data = rich.pretty.pretty_repr( - op.data, max_length=bigframes_vendored.ibis.options.repr.table_columns - ) - return name + render_fields({"data": data}, 1) - - -@fmt.register(ops.SQLQueryResult) -@fmt.register(ops.SQLStringView) -def _sql_query_result(op, query, **kwargs): - clsname = op.__class__.__name__ - - if isinstance(op, ops.SQLStringView): - child = kwargs["child"] - top = f"{clsname}[{child}]\n" - else: - top = f"{clsname}\n" - - query = textwrap.shorten( - query, - width=bigframes_vendored.ibis.options.repr.query_text_length, - placeholder=f" {util.HORIZONTAL_ELLIPSIS}", - ) - schema = render_schema(op.schema) - return top + render_fields({"query": query, "schema": schema}, 1) - - -@fmt.register(ops.FillNull) -@fmt.register(ops.DropNull) -def _fill_null(op, parent, **kwargs): - name = f"{op.__class__.__name__}[{parent}]\n" - return name + render_fields(kwargs, 1) - - -@fmt.register(ops.Aggregate) -def _aggregate(op, parent, **kwargs): - name = f"{op.__class__.__name__}[{parent}]\n" - return name + render_fields(kwargs, 1) - - -@fmt.register(ops.Sort) -def _sort(op, parent, keys): - name = f"{op.__class__.__name__}[{parent}]\n" - return name + render(keys, 1) - - -@fmt.register(ops.Set) -def _set_op(op, left, right, distinct): - args = [str(left), str(right)] - if op.distinct is not None: - args.append(f"distinct={distinct}") - return f"{op.__class__.__name__}[{', '.join(args)}]" - - -@fmt.register(ops.JoinChain) -def _join_project(op, first, rest, **kwargs): - name = f"{op.__class__.__name__}[{first}]\n" - return name + render(rest, 1) + "\n" + render_fields(kwargs, 1) - - -@fmt.register(ops.Limit) -@fmt.register(ops.Sample) -def _limit(op, parent, **kwargs): - params = inline_args(kwargs) - return f"{op.__class__.__name__}[{parent}, {params}]" - - -@fmt.register(ops.SelfReference) -@fmt.register(ops.Distinct) -def _self_reference(op, parent, **kwargs): - return f"{op.__class__.__name__}[{parent}]" - - -@fmt.register(ops.JoinReference) -def _join_reference(op, parent, **kwargs): - return parent - - -@fmt.register(ops.Literal) -def _literal(op, value, **kwargs): - if op.dtype.is_interval(): - return f"{value!r} {op.dtype.unit.short}" - elif op.dtype.is_array(): - return f"{list(value)!r}" - else: - return f"{value!r}" - - -@fmt.register(ops.Field) -def _relation_field(op, rel, name): - if name.isidentifier(): - return f"{rel}.{name}" - else: - return f"{rel}[{name!r}]" - - -@fmt.register(ops.Value) -def _value(op, **kwargs): - fields = inline_args(kwargs, prefer_positional=True) - return f"{op.__class__.__name__}({fields})" - - -@fmt.register(ops.Alias) -def _alias(op, arg, name): - return arg - - -@fmt.register(ops.Binary) -def _binary(op, left, right): - try: - symbol = _infix_ops[op.__class__] - except KeyError: - return f"{op.__class__.__name__}({left}, {right})" - else: - return f"{left} {symbol} {right}" - - -@fmt.register(ops.ScalarParameter) -def _scalar_parameter(op, dtype, **kwargs): - return f"$({dtype})" - - -@fmt.register(ops.SortKey) -def _sort_key(op, expr, **kwargs): - return f"{'asc' if op.ascending else 'desc'} {expr}" - - -@fmt.register(ops.GeoSpatialBinOp) -def _geo_bin_op(op, left, right, **kwargs): - fields = [left, right, inline_args(kwargs)] - args = ", ".join(f"{field}" for field in fields if field) - return f"{op.__class__.__name__}({args})" diff --git a/third_party/bigframes_vendored/ibis/expr/operations/__init__.py b/third_party/bigframes_vendored/ibis/expr/operations/__init__.py index 90f41a304a4..1612d9c12e5 100644 --- a/third_party/bigframes_vendored/ibis/expr/operations/__init__.py +++ b/third_party/bigframes_vendored/ibis/expr/operations/__init__.py @@ -1,22 +1,5 @@ # Contains code from https://github.com/ibis-project/ibis/blob/master/ibis/expr/operations/__init__.py from __future__ import annotations -from bigframes_vendored.ibis.expr.operations.analytic import * # noqa: F401 F403 -from bigframes_vendored.ibis.expr.operations.arrays import * # noqa: F401 F403 -from bigframes_vendored.ibis.expr.operations.core import * # noqa: F401 F403 -from bigframes_vendored.ibis.expr.operations.generic import * # noqa: F401 F403 -from bigframes_vendored.ibis.expr.operations.geospatial import * # noqa: F401 F403 -from bigframes_vendored.ibis.expr.operations.histograms import * # noqa: F401 F403 -from bigframes_vendored.ibis.expr.operations.json import * # noqa: F401 F403 -from bigframes_vendored.ibis.expr.operations.logical import * # noqa: F401 F403 -from bigframes_vendored.ibis.expr.operations.maps import * # noqa: F401 F403 -from bigframes_vendored.ibis.expr.operations.numeric import * # noqa: F401 F403 -from bigframes_vendored.ibis.expr.operations.reductions import * # noqa: F401 F403 -from bigframes_vendored.ibis.expr.operations.relations import * # noqa: F401 F403 -from bigframes_vendored.ibis.expr.operations.sortkeys import * # noqa: F401 F403 -from bigframes_vendored.ibis.expr.operations.strings import * # noqa: F401 F403 -from bigframes_vendored.ibis.expr.operations.structs import * # noqa: F401 F403 -from bigframes_vendored.ibis.expr.operations.subqueries import * # noqa: F401 F403 -from bigframes_vendored.ibis.expr.operations.temporal import * # noqa: F401 F403 -from bigframes_vendored.ibis.expr.operations.udf import * # noqa: F401 F403 -from bigframes_vendored.ibis.expr.operations.window import * # noqa: F401 F403 +from third_party.bigframes_vendored.ibis.expr.operations.analytic import * # noqa: F403 +from third_party.bigframes_vendored.ibis.expr.operations.reductions import * # noqa: F403 diff --git a/third_party/bigframes_vendored/ibis/expr/operations/ai_ops.py b/third_party/bigframes_vendored/ibis/expr/operations/ai_ops.py deleted file mode 100644 index 9fa043d0bab..00000000000 --- a/third_party/bigframes_vendored/ibis/expr/operations/ai_ops.py +++ /dev/null @@ -1,205 +0,0 @@ -# Contains code from https://github.com/ibis-project/ibis/blob/9.2.0/ibis/expr/operations/maps.py - -"""Operations for working with AI operators.""" - -from __future__ import annotations - -from typing import Optional - -import bigframes_vendored.ibis.expr.datatypes as dt -import bigframes_vendored.ibis.expr.rules as rlz -import pyarrow as pa -from bigframes_vendored.ibis.common.annotations import attribute -from bigframes_vendored.ibis.expr.operations.core import Value -from public import public - -from bigframes.operations import output_schemas - - -@public -class AIGenerate(Value): - """Generate content based on the prompt""" - - prompt: Value - connection_id: Optional[Value[dt.String]] - endpoint: Optional[Value[dt.String]] - request_type: Value[dt.String] - model_params: Optional[Value[dt.String]] - output_schema: Optional[Value[dt.String]] - - shape = rlz.shape_like("prompt") - - @attribute - def dtype(self) -> dt.Struct: - if self.output_schema is None: - output_pa_fields = (pa.field("result", pa.string()),) - else: - output_pa_fields = output_schemas.parse_sql_fields(self.output_schema.value) - - pyarrow_output_type = pa.struct( - ( - *output_pa_fields, - pa.field("full_response", pa.string()), - pa.field("status", pa.string()), - ) - ) - - return dt.Struct.from_pyarrow(pyarrow_output_type) - - -@public -class AIGenerateBool(Value): - """Generate Bool based on the prompt""" - - prompt: Value - connection_id: Optional[Value[dt.String]] - endpoint: Optional[Value[dt.String]] - request_type: Value[dt.String] - model_params: Optional[Value[dt.String]] - - shape = rlz.shape_like("prompt") - - @attribute - def dtype(self) -> dt.Struct: - return dt.Struct.from_tuples( - (("result", dt.bool), ("full_response", dt.string), ("status", dt.string)) - ) - - -@public -class AIGenerateInt(Value): - """Generate integers based on the prompt""" - - prompt: Value - connection_id: Optional[Value[dt.String]] - endpoint: Optional[Value[dt.String]] - request_type: Value[dt.String] - model_params: Optional[Value[dt.String]] - - shape = rlz.shape_like("prompt") - - @attribute - def dtype(self) -> dt.Struct: - return dt.Struct.from_tuples( - (("result", dt.int64), ("full_response", dt.string), ("status", dt.string)) - ) - - -@public -class AIGenerateDouble(Value): - """Generate doubles based on the prompt""" - - prompt: Value - connection_id: Optional[Value[dt.String]] - endpoint: Optional[Value[dt.String]] - request_type: Value[dt.String] - model_params: Optional[Value[dt.String]] - - shape = rlz.shape_like("prompt") - - @attribute - def dtype(self) -> dt.Struct: - return dt.Struct.from_tuples( - ( - ("result", dt.float64), - ("full_response", dt.string), - ("status", dt.string), - ) - ) - - -@public -class AIEmbed(Value): - """Create embeddings from text or image data.""" - - content: Value - connection_id: Optional[Value[dt.String]] - endpoint: Optional[Value[dt.String]] - model: Optional[Value[dt.String]] - task_type: Optional[Value[dt.String]] - title: Optional[Value[dt.String]] - model_params: Optional[Value[dt.String]] - - shape = rlz.shape_like("content") - - @attribute - def dtype(self) -> dt.Struct: - return dt.Struct.from_tuples( - ( - ("result", dt.Array(dt.float64)), - ("status", dt.string), - ) - ) - - -@public -class AIIf(Value): - """Generate True/False based on the prompt""" - - prompt: Value - connection_id: Optional[Value[dt.String]] - endpoint: Optional[Value[dt.String]] - optimization_mode: Optional[Value[dt.String]] - max_error_ratio: Optional[Value[dt.Float64]] - - shape = rlz.shape_like("prompt") - - @attribute - def dtype(self) -> dt.Struct: - return dt.bool - - -@public -class AIClassify(Value): - """Generate categories based on the prompt""" - - input: Value - categories: Value[dt.Array[dt.String]] - examples: Optional[Value] - connection_id: Optional[Value[dt.String]] - endpoint: Optional[Value[dt.String]] - output_mode: Optional[Value[dt.String]] - optimization_mode: Optional[Value[dt.String]] - max_error_ratio: Optional[Value[dt.Float64]] - - shape = rlz.shape_like("input") - - @attribute - def dtype(self) -> dt.DataType: - if self.output_mode is not None: - return dt.Array(dt.string) - return dt.string - - -@public -class AIScore(Value): - """Generate scores based on the prompt""" - - prompt: Value - connection_id: Optional[Value[dt.String]] - endpoint: Optional[Value[dt.String]] - max_error_ratio: Optional[Value[dt.Float64]] - - shape = rlz.shape_like("prompt") - - @attribute - def dtype(self) -> dt.DataType: - return dt.float64 - - -@public -class AISimilarity(Value): - """Calculate the similarity between two contents""" - - content1: Value - content2: Value - endpoint: Optional[Value[dt.String]] - model: Optional[Value[dt.String]] - model_params: Optional[Value[dt.String]] - connection_id: Optional[Value[dt.String]] - - shape = rlz.shape_like("content1") - - @attribute - def dtype(self) -> dt.Struct: - return dt.float64 diff --git a/third_party/bigframes_vendored/ibis/expr/operations/analytic.py b/third_party/bigframes_vendored/ibis/expr/operations/analytic.py index 584fba23f66..038987cac93 100644 --- a/third_party/bigframes_vendored/ibis/expr/operations/analytic.py +++ b/third_party/bigframes_vendored/ibis/expr/operations/analytic.py @@ -1,124 +1,26 @@ -# Contains code from https://github.com/ibis-project/ibis/blob/9.2.0/ibis/expr/operations/analytic.py - -"""Operations for analytic window functions.""" +# Contains code from https://github.com/ibis-project/ibis/blob/master/ibis/expr/operations/analytic.py from __future__ import annotations -from typing import Optional - -import bigframes_vendored.ibis.expr.datashape as ds -import bigframes_vendored.ibis.expr.datatypes as dt -import bigframes_vendored.ibis.expr.operations.udf as ibis_udf -import bigframes_vendored.ibis.expr.rules as rlz -from bigframes_vendored.ibis.expr.operations.core import Column, Scalar, Value -from public import public - - -@public -class Analytic(Value): - """Base class for analytic window function operations.""" - - shape = ds.columnar - - -class ShiftBase(Analytic): - """Base class for shift operations.""" - - arg: Column[dt.Any] - offset: Optional[Value[dt.Integer | dt.Interval]] = None - default: Optional[Value] = None - - dtype = rlz.dtype_like("arg") - - -@public -class Lag(ShiftBase): - """Shift a column forward.""" - - -@public -class Lead(ShiftBase): - """Shift a column backward.""" - - -@public -class RankBase(Analytic): - """Base class for ranking operations.""" - - dtype = dt.int64 - - -@public -class MinRank(RankBase): - pass - - -@public -class DenseRank(RankBase): - pass - +from ibis.expr.operations.analytic import Analytic +import ibis.expr.rules as rlz -@public -class RowNumber(RankBase): - """Compute the row number over a window, starting from 0.""" - -@public -class PercentRank(Analytic): - """Compute the percentile rank over a window.""" - - dtype = dt.double - - -@public -class CumeDist(Analytic): - """Compute the cumulative distribution function of a column over a window.""" - - dtype = dt.double - - -@public -class NTile(Analytic): - """Compute the percentile of a column over a window.""" - - buckets: Scalar[dt.Integer] - - dtype = dt.int64 - - -@public -class NthValue(Analytic): - """Retrieve the Nth element of a column over a window.""" - - arg: Column[dt.Any] - nth: Value[dt.Integer] - - dtype = rlz.dtype_like("arg") - - -public(AnalyticOp=Analytic) - - -# TODO(swast): We can remove this if ibis adds aggregates over scalar values. -# See: https://github.com/ibis-project/ibis/issues/8698 -@public -@ibis_udf.agg.builtin -def count(value: int) -> int: - """Count of a scalar.""" - return 0 # pragma: NO COVER - - -@public class FirstNonNullValue(Analytic): """Retrieve the first element.""" - arg: Column - dtype = rlz.dtype_like("arg") + arg = rlz.column(rlz.any) + output_dtype = rlz.dtype_like("arg") -@public class LastNonNullValue(Analytic): """Retrieve the last element.""" - arg: Column - dtype = rlz.dtype_like("arg") + arg = rlz.column(rlz.any) + output_dtype = rlz.dtype_like("arg") + + +__all__ = [ + "FirstNonNullValue", + "LastNonNullValue", +] diff --git a/third_party/bigframes_vendored/ibis/expr/operations/arrays.py b/third_party/bigframes_vendored/ibis/expr/operations/arrays.py deleted file mode 100644 index 7e10a3e26f9..00000000000 --- a/third_party/bigframes_vendored/ibis/expr/operations/arrays.py +++ /dev/null @@ -1,284 +0,0 @@ -# Contains code from https://github.com/ibis-project/ibis/blob/9.2.0/ibis/expr/operations/arrays.py - -"""Operations for array expressions.""" - -from __future__ import annotations - -from typing import Optional - -import bigframes_vendored.ibis.expr.datashape as ds -import bigframes_vendored.ibis.expr.datatypes as dt -import bigframes_vendored.ibis.expr.rules as rlz -from bigframes_vendored.ibis.common.annotations import attribute -from bigframes_vendored.ibis.common.typing import VarTuple # noqa: TCH001 -from bigframes_vendored.ibis.expr.operations.core import Unary, Value -from public import public - - -@public -class Array(Value): - """Construct an array.""" - - exprs: VarTuple[Value] - - @attribute - def shape(self): - return rlz.highest_precedence_shape(self.exprs) - - @attribute - def dtype(self): - return dt.Array(rlz.highest_precedence_dtype(self.exprs)) - - -@public -class ArrayLength(Unary): - """Compute the length of an array.""" - - arg: Value[dt.Array] - - dtype = dt.int64 - shape = rlz.shape_like("args") - - -@public -class ArraySlice(Value): - """Slice an array element.""" - - arg: Value[dt.Array] - start: Value[dt.Integer] - stop: Optional[Value[dt.Integer]] = None - - dtype = rlz.dtype_like("arg") - shape = rlz.shape_like("args") - - -@public -class ArrayIndex(Value): - """Return the element of an array at some index.""" - - arg: Value[dt.Array] - index: Value[dt.Integer] - - shape = rlz.shape_like("args") - - @attribute - def dtype(self): - return self.arg.dtype.value_type - - -@public -class ArrayConcat(Value): - """Concatenate two or more arrays into a single array.""" - - arg: VarTuple[Value[dt.Array]] - - shape = rlz.shape_like("arg") - - @attribute - def dtype(self): - return dt.Array(dt.highest_precedence(arg.dtype.value_type for arg in self.arg)) - - -@public -class ArrayRepeat(Value): - """Repeat the elements of an array.""" - - arg: Value[dt.Array] - times: Value[dt.Integer] - - dtype = rlz.dtype_like("arg") - shape = rlz.shape_like("args") - - -@public -class ArrayMap(Value): - """Apply a function to every element of an array.""" - - arg: Value[dt.Array] - body: Value - param: str - - shape = rlz.shape_like("arg") - - @attribute - def dtype(self) -> dt.DataType: - return dt.Array(self.body.dtype) - - -@public -class ArrayReduce(Value): - """Apply a function to every element of an array.""" - - arg: Value[dt.Array] - body: Value - param: str - - shape = rlz.shape_like("arg") - - @attribute - def dtype(self) -> dt.DataType: - return self.body.dtype - - -@public -class ArrayFilter(Value): - """Filter array elements with a function.""" - - arg: Value[dt.Array] - body: Value[dt.Boolean] - param: str - - shape = rlz.shape_like("arg") - dtype = rlz.dtype_like("arg") - - -@public -class Unnest(Value): - """Unnest an array value into a column.""" - - arg: Value[dt.Array] - - shape = ds.columnar - - @attribute - def dtype(self): - return self.arg.dtype.value_type - - -@public -class ArrayContains(Value): - """Return whether an array contains a specific value.""" - - arg: Value[dt.Array] - other: Value - - dtype = dt.boolean - shape = rlz.shape_like("args") - - -@public -class ArrayPosition(Value): - """Return the position of a specific value in an array.""" - - arg: Value[dt.Array] - other: Value - - dtype = dt.int64 - shape = rlz.shape_like("args") - - -@public -class ArrayRemove(Value): - """Remove an element from an array.""" - - arg: Value[dt.Array] - other: Value - - dtype = rlz.dtype_like("arg") - shape = rlz.shape_like("args") - - -@public -class ArrayDistinct(Value): - """Return the unique elements of an array.""" - - arg: Value[dt.Array] - - dtype = rlz.dtype_like("arg") - shape = rlz.shape_like("arg") - - -@public -class ArraySort(Value): - """Sort the values of an array.""" - - arg: Value[dt.Array] - - dtype = rlz.dtype_like("arg") - shape = rlz.shape_like("arg") - - -@public -class ArrayUnion(Value): - """Return the union of two arrays.""" - - left: Value[dt.Array] - right: Value[dt.Array] - - dtype = rlz.dtype_like("args") - shape = rlz.shape_like("args") - - -@public -class ArrayIntersect(Value): - """Return the intersection of two arrays.""" - - left: Value[dt.Array] - right: Value[dt.Array] - - dtype = rlz.dtype_like("args") - shape = rlz.shape_like("args") - - -@public -class ArrayZip(Value): - """Zip two or more arrays into an array of structs.""" - - arg: VarTuple[Value[dt.Array]] - - shape = rlz.shape_like("arg") - - @attribute - def dtype(self): - return dt.Array( - dt.Struct( - { - f"f{i:d}": array.dtype.value_type - for i, array in enumerate(self.arg, start=1) - } - ) - ) - - -@public -class ArrayFlatten(Value): - """Flatten a nested array one level. - - The input expression must have at least one level of nesting for flattening - to make sense. - """ - - arg: Value[dt.Array[dt.Array]] - shape = rlz.shape_like("arg") - - @property - def dtype(self): - return self.arg.dtype.value_type - - -class Range(Value): - """Base class for range-generating operations.""" - - shape = rlz.shape_like("args") - - @attribute - def dtype(self) -> dt.DataType: - return dt.Array(dt.highest_precedence((self.start.dtype, self.stop.dtype))) - - -@public -class IntegerRange(Range): - """Produce an array of integers from `start` to `stop`, moving by `step`.""" - - start: Value[dt.Integer] - stop: Value[dt.Integer] - step: Value[dt.Integer] - - -@public -class TimestampRange(Range): - """Produce an array of timestamps from `start` to `stop`, moving by `step`.""" - - start: Value[dt.Timestamp] - stop: Value[dt.Timestamp] - step: Value[dt.Interval] diff --git a/third_party/bigframes_vendored/ibis/expr/operations/core.py b/third_party/bigframes_vendored/ibis/expr/operations/core.py deleted file mode 100644 index ad0bd095b6c..00000000000 --- a/third_party/bigframes_vendored/ibis/expr/operations/core.py +++ /dev/null @@ -1,200 +0,0 @@ -# Contains code from https://github.com/ibis-project/ibis/blob/9.2.0/ibis/expr/operations/core.py - -from __future__ import annotations - -from abc import abstractmethod -from typing import Generic, Optional - -import bigframes_vendored.ibis.expr.datashape as ds -import bigframes_vendored.ibis.expr.datatypes as dt -import bigframes_vendored.ibis.expr.rules as rlz -from bigframes_vendored.ibis.common.annotations import attribute -from bigframes_vendored.ibis.common.graph import Node as Traversable -from bigframes_vendored.ibis.common.grounds import Concrete -from bigframes_vendored.ibis.common.patterns import Coercible, CoercionError -from bigframes_vendored.ibis.common.typing import DefaultTypeVars -from bigframes_vendored.ibis.util import is_iterable -from public import public -from typing_extensions import Any, Self, TypeVar - - -@public -class Node(Concrete, Traversable): - def equals(self, other) -> bool: - if not isinstance(other, Node): - raise TypeError( - f"invalid equality comparison between Node and {type(other)}" - ) - return self == other - - # Avoid custom repr for performance reasons - __repr__ = object.__repr__ - - # TODO(kszucs): hidrate the __children__ traversable attribute - # @attribute - # def __children__(self): - # return super().__children__ - - -T = TypeVar("T", bound=dt.DataType, covariant=True) -S = TypeVar("S", bound=ds.DataShape, default=ds.Any, covariant=True) - - -@public -class Value(Node, Coercible, DefaultTypeVars, Generic[T, S]): - @classmethod - def __coerce__( - cls, value: Any, T: Optional[type] = None, S: Optional[type] = None - ) -> Self: - # note that S=Shape is unused here since the pattern will check the - # shape of the value expression after executing Value.__coerce__() - from bigframes_vendored.ibis.expr.operations.generic import NULL, Literal - from bigframes_vendored.ibis.expr.types import Expr - - if isinstance(value, Expr): - value = value.op() - - if isinstance(value, Value): - if value == NULL: - # treat the NULL literal the same as None to implicitly cast to - # the requested datatype if any - value = None - else: - return value - - if T is dt.Integer: - dtype = dt.infer(int(value)) - elif T is dt.Floating: - dtype = dt.infer(float(value)) - else: - try: - dtype = dt.DataType.from_typehint(T) - except TypeError: - dtype = dt.infer(value) - - try: - return Literal(value, dtype=dtype) - except TypeError: - raise CoercionError(f"Unable to coerce {value!r} to Value[{T!r}]") - - # TODO(kszucs): cover it with tests - # TODO(kszucs): figure out how to represent not named arguments - @property - def name(self) -> str: - names = [] - for arg in self.__args__: - if is_iterable(arg): - elements = [ - element_name - for element in arg - if (element_name := getattr(element, "name", None)) is not None - ] - joined = ", ".join(elements) - fmt = "({})" if len(elements) != 1 else "({},)" - names.append(fmt.format(joined)) - elif (name := getattr(arg, "name", None)) is not None: - names.append(name) - return f"{self.__class__.__name__}({', '.join(names)})" - - @property - @abstractmethod - def dtype(self) -> T: - """Ibis datatype of the produced value expression. - - Returns - ------- - dt.DataType - - """ - - @property - @abstractmethod - def shape(self) -> S: - """Shape of the produced value expression. - - Possible values are: "scalar" and "columnar" - - Returns - ------- - ds.Shape - - """ - - @attribute - def relations(self): - """Set of relations the value node depends on.""" - children = (n.relations for n in self.__children__ if isinstance(n, Value)) - return frozenset().union(*children) - - def to_expr(self): - import bigframes_vendored.ibis.expr.types as ir - - if self.shape.is_columnar(): - typename = self.dtype.column - else: - typename = self.dtype.scalar - - return getattr(ir, typename)(self) - - @property - def omitted(self) -> bool: - return False - - -# convenience aliases -Scalar = Value[T, ds.Scalar] -Column = Value[T, ds.Columnar] - - -@public -class Alias(Value): - arg: Value - name: str - - shape = rlz.shape_like("arg") - dtype = rlz.dtype_like("arg") - - -@public -class Unary(Value): - """A unary operation.""" - - arg: Value - - @attribute - def shape(self) -> ds.DataShape: - return self.arg.shape - - @attribute - def relations(self): - return self.arg.relations - - -@public -class Binary(Value): - """A binary operation.""" - - left: Value - right: Value - - @attribute - def shape(self) -> ds.DataShape: - return max(self.left.shape, self.right.shape) - - @attribute - def relations(self): - return self.left.relations | self.right.relations - - -@public -class Argument(Value): - name: str - shape: ds.DataShape - dtype: dt.DataType - - @attribute - def param(self) -> str: - return f"__ibis_param_{self.name}__" - - -public(ValueOp=Value, UnaryOp=Unary, BinaryOp=Binary, Scalar=Scalar, Column=Column) diff --git a/third_party/bigframes_vendored/ibis/expr/operations/generic.py b/third_party/bigframes_vendored/ibis/expr/operations/generic.py deleted file mode 100644 index cc0caf21b2e..00000000000 --- a/third_party/bigframes_vendored/ibis/expr/operations/generic.py +++ /dev/null @@ -1,352 +0,0 @@ -# Contains code from https://github.com/ibis-project/ibis/blob/9.2.0/ibis/expr/operations/generic.py -"""Generic value operations.""" - -from __future__ import annotations - -import itertools -from typing import Annotated, Any, Optional -from typing import Literal as LiteralType - -import bigframes_vendored.ibis.expr.datashape as ds -import bigframes_vendored.ibis.expr.datatypes as dt -import bigframes_vendored.ibis.expr.rules as rlz -from bigframes_vendored.ibis.common.annotations import attribute -from bigframes_vendored.ibis.common.deferred import Deferred # noqa: TCH001 -from bigframes_vendored.ibis.common.grounds import Singleton -from bigframes_vendored.ibis.common.patterns import InstanceOf, Length # noqa: TCH001 -from bigframes_vendored.ibis.common.typing import VarTuple # noqa: TCH001 -from bigframes_vendored.ibis.expr.operations.core import Scalar, Unary, Value -from bigframes_vendored.ibis.expr.operations.relations import Relation # noqa: TCH001 -from public import public -from typing_extensions import TypeVar - - -@public -class RowID(Value): - """The row number of the returned result.""" - - name = "rowid" - table: Relation - - shape = ds.columnar - dtype = dt.int64 - - @attribute - def relations(self): - return frozenset({self.table}) - - -@public -class Cast(Value): - """Explicitly cast a value to a specific data type.""" - - arg: Value - to: dt.DataType - - shape = rlz.shape_like("arg") - - @property - def name(self): - return f"{self.__class__.__name__}({self.arg.name}, {self.to})" - - @property - def dtype(self): - return self.to - - -@public -class TryCast(Value): - """Try to cast a value to a specific data type.""" - - arg: Value - to: dt.DataType - - shape = rlz.shape_like("arg") - - @property - def dtype(self): - return self.to - - -@public -class TypeOf(Unary): - """Return the _database_ data type of the input expression.""" - - dtype = dt.string - - -@public -class IsNull(Unary): - """Return true if values are null.""" - - dtype = dt.boolean - - -@public -class NotNull(Unary): - """Returns true if values are not null.""" - - dtype = dt.boolean - - -@public -class NullIf(Value): - """Return NULL if an expression equals some specific value.""" - - arg: Value - null_if_expr: Value - - dtype = rlz.dtype_like("args") - shape = rlz.shape_like("args") - - -@public -class Coalesce(Value): - """Return the first non-null expression from a tuple of expressions.""" - - arg: Annotated[VarTuple[Value], Length(at_least=1)] - - shape = rlz.shape_like("arg") - dtype = rlz.dtype_like("arg") - - -@public -class Greatest(Value): - """Return the largest value from a tuple of expressions.""" - - arg: Annotated[VarTuple[Value], Length(at_least=1)] - - shape = rlz.shape_like("arg") - dtype = rlz.dtype_like("arg") - - -@public -class Least(Value): - """Return the smallest value from a tuple of expressions.""" - - arg: Annotated[VarTuple[Value], Length(at_least=1)] - - shape = rlz.shape_like("arg") - dtype = rlz.dtype_like("arg") - - -T = TypeVar("T", bound=dt.DataType, covariant=True) - - -@public -class Literal(Scalar[T]): - """A constant value.""" - - value: Annotated[Any, ~InstanceOf(Deferred)] - dtype: T - - shape = ds.scalar - - def __init__(self, value, dtype): - # normalize ensures that the value is a valid value for the given dtype - value = dt.normalize(dtype, value) - super().__init__(value=value, dtype=dtype) - - @property - def name(self): - if self.dtype.is_interval(): - return f"{self.value!r}{self.dtype.unit.short}" - return repr(self.value) - - -NULL = Literal(None, dt.null) - - -@public -class ScalarParameter(Scalar): - _counter = itertools.count() - - dtype: dt.DataType - counter: Optional[int] = None - - shape = ds.scalar - - def __init__(self, dtype, counter): - if counter is None: - counter = next(self._counter) - super().__init__(dtype=dtype, counter=counter) - - @property - def name(self): - return f"param_{self.counter:d}" - - -@public -class Constant(Scalar, Singleton): - """A function that produces a constant.""" - - shape = ds.scalar - - -@public -class Impure(Value): - pass - - -@public -class OmittedArg(Value): - pass - - -@public -class TimestampNow(Constant): - """Return the current timestamp.""" - - dtype = dt.timestamp - - -@public -class DateNow(Constant): - """Return the current date.""" - - dtype = dt.date - - -@public -class RandomScalar(Impure): - """Return a random scalar between 0 and 1.""" - - dtype = dt.float64 - shape = ds.scalar - - -@public -class RandomUUID(Impure): - """Return a random UUID.""" - - dtype = dt.uuid - shape = ds.scalar - - -@public -class E(Constant): - """The mathematical constant e.""" - - dtype = dt.float64 - - -@public -class Pi(Constant): - """The mathematical constant pi.""" - - dtype = dt.float64 - - -@public -class Hash(Value): - """Return the hash of a value.""" - - arg: Value - - dtype = dt.int64 - shape = rlz.shape_like("arg") - - -@public -class HashBytes(Value): - arg: Value[dt.String | dt.Binary] - how: LiteralType[ - "md5", # noqa: F821 - "MD5", # noqa: F821 - "sha1", # noqa: F821 - "SHA1", # noqa: F821 - "SHA224", # noqa: F821 - "sha256", # noqa: F821 - "SHA256", # noqa: F821 - "sha512", # noqa: F821 - "intHash32", # noqa: F821 - "intHash64", # noqa: F821 - "cityHash64", # noqa: F821 - "sipHash64", # noqa: F821 - "sipHash128", # noqa: F821 - ] - - dtype = dt.binary - shape = rlz.shape_like("arg") - - -@public -class HexDigest(Value): - """Return the hexadecimal digest of a value.""" - - arg: Value[dt.String | dt.Binary] - how: LiteralType[ - "md5", # noqa: F821 - "sha1", # noqa: F821 - "sha256", # noqa: F821 - "sha512", # noqa: F821 - ] - - dtype = dt.str - shape = rlz.shape_like("arg") - - -# TODO(kszucs): we should merge the case operations by making the -# cases, results and default optional arguments like they are in -# api.py -@public -class SimpleCase(Value): - """Simple case statement.""" - - base: Value - cases: VarTuple[Value] - results: VarTuple[Value] - default: Value - - shape = rlz.shape_like("base") - - def __init__(self, cases, results, **kwargs): - assert len(cases) == len(results) - super().__init__(cases=cases, results=results, **kwargs) - - @attribute - def dtype(self): - values = [*self.results, self.default] - return rlz.highest_precedence_dtype(values) - - -@public -class SearchedCase(Value): - """Searched case statement.""" - - cases: VarTuple[Value[dt.Boolean]] - results: VarTuple[Value] - default: Value - - def __init__(self, cases, results, default): - assert len(cases) == len(results) - if default.dtype.is_null(): - default = Cast(default, rlz.highest_precedence_dtype(results)) - super().__init__(cases=cases, results=results, default=default) - - @attribute - def shape(self): - return rlz.highest_precedence_shape((*self.cases, *self.results, self.default)) - - @attribute - def dtype(self): - exprs = [*self.results, self.default] - return rlz.highest_precedence_dtype(exprs) - - -@public -class SqlScalar(Value): - """Inject a SQL string as a scalar value.""" - - sql_template: str - values: VarTuple[Value] - output_type: dt.DataType - - shape = ds.scalar - - @property - def dtype(self): - return self.output_type - - -public(NULL=NULL) diff --git a/third_party/bigframes_vendored/ibis/expr/operations/geospatial.py b/third_party/bigframes_vendored/ibis/expr/operations/geospatial.py deleted file mode 100644 index efe038599a0..00000000000 --- a/third_party/bigframes_vendored/ibis/expr/operations/geospatial.py +++ /dev/null @@ -1,523 +0,0 @@ -# Contains code from https://github.com/ibis-project/ibis/blob/9.2.0/ibis/expr/operations/geospatial.py - -"""Geospatial operations.""" - -from __future__ import annotations - -import bigframes_vendored.ibis.expr.datatypes as dt -from bigframes_vendored.ibis.expr.operations.core import Binary, Unary, Value -from bigframes_vendored.ibis.expr.operations.reductions import Filterable, Reduction -from public import public - - -@public -class GeoSpatialBinOp(Binary): - """Geo Spatial base binary.""" - - left: Value[dt.GeoSpatial] - right: Value[dt.GeoSpatial] - - -@public -class GeoSpatialUnOp(Unary): - """Geo Spatial base unary.""" - - arg: Value[dt.GeoSpatial] - - -@public -class GeoDistance(GeoSpatialBinOp): - """Returns minimum distance between two geospatial operands.""" - - dtype = dt.float64 - - -@public -class GeoContains(GeoSpatialBinOp): - """Check if the first geo spatial data contains the second one.""" - - dtype = dt.boolean - - -@public -class GeoContainsProperly(GeoSpatialBinOp): - """Check if the left value contains the right one, with no shared no boundary points.""" - - dtype = dt.boolean - - -@public -class GeoCovers(GeoSpatialBinOp): - """Check if no point in the right operand is outside that of the left.""" - - dtype = dt.boolean - - -@public -class GeoCoveredBy(GeoSpatialBinOp): - """Check if no point in the left operand is outside that of the right.""" - - dtype = dt.boolean - - -@public -class GeoCrosses(GeoSpatialBinOp): - """Check if the inputs have some but not all interior points in common.""" - - dtype = dt.boolean - - -@public -class GeoDisjoint(GeoSpatialBinOp): - """Check if the Geometries do not spatially intersect.""" - - dtype = dt.boolean - - -@public -class GeoEquals(GeoSpatialBinOp): - """Returns True if the given geometries represent the same geometry.""" - - dtype = dt.boolean - - -@public -class GeoGeometryN(GeoSpatialUnOp): - """Returns the Nth Geometry of a Multi geometry.""" - - n: Value[dt.Integer] - dtype = dt.geometry - - -@public -class GeoGeometryType(GeoSpatialUnOp): - """Returns the type of the geometry.""" - - dtype = dt.string - - -@public -class GeoIntersects(GeoSpatialBinOp): - """Returns True if the Geometries/Geography “spatially intersect in 2D”. - - - (share any portion of space) and False if they don`t (they are Disjoint). - """ - - dtype = dt.boolean - - -@public -class GeoIsValid(GeoSpatialUnOp): - """Returns true if the geometry is well-formed.""" - - dtype = dt.boolean - - -@public -class GeoLineLocatePoint(GeoSpatialBinOp): - """Locate the distance a point falls along the length of a line. - - Returns a float between zero and one representing the location of - the closest point on the linestring to the given point, as a - fraction of the total 2d line length. - """ - - left: Value[dt.LineString] - right: Value[dt.Point] - - dtype = dt.halffloat - - -@public -class GeoLineMerge(GeoSpatialUnOp): - """Merge a MultiLineString into a LineString. - - Returns a (set of) LineString(s) formed by sewing together the - constituent line work of a multilinestring. If a geometry other than - a linestring or multilinestring is given, this will return an empty - geometry collection. - """ - - dtype = dt.geometry - - -@public -class GeoLineSubstring(GeoSpatialUnOp): - """Clip a substring from a LineString. - - Returns a linestring that is a substring of the input one, starting - and ending at the given fractions of the total 2d length. The second - and third arguments are floating point values between zero and one. - This only works with linestrings. - """ - - arg: Value[dt.LineString] - start: Value[dt.Floating] - end: Value[dt.Floating] - - dtype = dt.linestring - - -@public -class GeoOrderingEquals(GeoSpatialBinOp): - """Check if two geometries are equal and have the same point ordering. - - Returns true if the two geometries are equal and the coordinates are - in the same order. - """ - - dtype = dt.boolean - - -@public -class GeoOverlaps(GeoSpatialBinOp): - """Check if the inputs are of the same dimension but are not completely contained by each other.""" - - dtype = dt.boolean - - -@public -class GeoTouches(GeoSpatialBinOp): - """Check if the inputs have at least one point in common but their interiors do not intersect.""" - - dtype = dt.boolean - - -@public -class GeoUnaryUnion(Filterable, Reduction): - """Returns the pointwise union of the geometries in the column.""" - - arg: Value[dt.GeoSpatial] - - dtype = dt.geometry - - -@public -class GeoUnion(GeoSpatialBinOp): - """Returns the pointwise union of the two geometries.""" - - dtype = dt.geometry - - -@public -class GeoArea(GeoSpatialUnOp): - """Area of the geo spatial data.""" - - dtype = dt.float64 - - -@public -class GeoPerimeter(GeoSpatialUnOp): - """Perimeter of the geo spatial data.""" - - dtype = dt.float64 - - -@public -class GeoLength(GeoSpatialUnOp): - """Length of geo spatial data.""" - - dtype = dt.float64 - - -@public -class GeoMaxDistance(GeoSpatialBinOp): - """Returns the 2-dimensional max distance between two geometries in projected units. - - If g1 and g2 is the same geometry the function will return the - distance between the two vertices most far from each other in that - geometry - """ - - dtype = dt.float64 - - -@public -class GeoX(GeoSpatialUnOp): - """Return the X coordinate of the point, or NULL if not available. - - Input must be a point - """ - - dtype = dt.float64 - - -@public -class GeoY(GeoSpatialUnOp): - """Return the Y coordinate of the point, or NULL if not available. - - Input must be a point - """ - - dtype = dt.float64 - - -@public -class GeoXMin(GeoSpatialUnOp): - """Returns Y minima of a bounding box 2d or 3d or a geometry.""" - - dtype = dt.float64 - - -@public -class GeoXMax(GeoSpatialUnOp): - """Returns X maxima of a bounding box 2d or 3d or a geometry.""" - - dtype = dt.float64 - - -@public -class GeoYMin(GeoSpatialUnOp): - """Returns Y minima of a bounding box 2d or 3d or a geometry.""" - - dtype = dt.float64 - - -@public -class GeoYMax(GeoSpatialUnOp): - """Returns Y maxima of a bounding box 2d or 3d or a geometry.""" - - dtype = dt.float64 - - -@public -class GeoStartPoint(GeoSpatialUnOp): - """Return the first point of a `LINESTRING` geometry as a POINT. - - Returns `NULL` if the input is not a LINESTRING. - """ - - dtype = dt.point - - -@public -class GeoEndPoint(GeoSpatialUnOp): - """Return the last point of a `LINESTRING` geometry as a POINT. - - Returns `NULL` if the input is not a LINESTRING. - """ - - dtype = dt.point - - -@public -class GeoPoint(GeoSpatialBinOp): - """Return a point constructed from the input coordinate values. - - Constant coordinates result in construction of a POINT literal. - """ - - left: Value[dt.Numeric] - right: Value[dt.Numeric] - - dtype = dt.point - - -@public -class GeoPointN(GeoSpatialUnOp): - """Return the Nth point in a single linestring in the geometry. - - Negative values are counted backwards from the end of the - LineString, so that -1 is the last point. Returns NULL if there is - no linestring in the geometry - """ - - n: Value[dt.Integer] - dtype = dt.point - - -@public -class GeoNPoints(GeoSpatialUnOp): - """Return the number of points in a geometry.""" - - dtype = dt.int64 - - -@public -class GeoNRings(GeoSpatialUnOp): - """Return the number of rings for polygons or multipolygons. - - Outer rings are counted. - """ - - dtype = dt.int64 - - -@public -class GeoRegionStats(GeoSpatialUnOp): - """Returns results of ST_REGIONSTATS.""" - - raster_id: Value[dt.String] - band: Value[dt.String] - include: Value[dt.String] - options: Value[dt.JSON] - - dtype = dt.Struct( - fields={ - "count": dt.int64, - "min": dt.float64, - "max": dt.float64, - "stdDev": dt.float64, - "sum": dt.float64, - "mean": dt.float64, - "area": dt.float64, - } - ) - - -@public -class GeoSRID(GeoSpatialUnOp): - """Returns the spatial reference identifier for the ST_Geometry.""" - - dtype = dt.int64 - - -@public -class GeoSetSRID(GeoSpatialUnOp): - """Set the spatial reference identifier for the ST_Geometry.""" - - srid: Value[dt.Integer] - - dtype = dt.geometry - - -@public -class GeoBuffer(GeoSpatialUnOp): - """Return all points whose distance from this geometry is less than or equal to `radius`. - - Calculations are in the Spatial Reference System of this geometry. - """ - - radius: Value[dt.Floating] - dtype = dt.geometry - - -@public -class GeoCentroid(GeoSpatialUnOp): - """Returns the geometric center of a geometry.""" - - dtype = dt.point - - -@public -class GeoDFullyWithin(GeoSpatialBinOp): - """Check if the geometries are fully within `distance` of one another.""" - - distance: Value[dt.Floating] - - dtype = dt.boolean - - -@public -class GeoDWithin(GeoSpatialBinOp): - """Check if the geometries are within `distance` of one another.""" - - distance: Value[dt.Floating] - - dtype = dt.boolean - - -@public -class GeoEnvelope(GeoSpatialUnOp): - """The bounding box of the supplied geometry.""" - - dtype = dt.polygon - - -@public -class GeoAzimuth(GeoSpatialBinOp): - """Return the angle in radians from the horizontal of the vector defined by the two inputs. - - Angle is computed clockwise from down-to-up: on the clock: 12=0; - 3=PI/2; 6=PI; 9=3PI/2. - """ - - left: Value[dt.Point] - right: Value[dt.Point] - - dtype = dt.float64 - - -@public -class GeoWithin(GeoSpatialBinOp): - """Returns True if the geometry A is completely inside geometry B.""" - - dtype = dt.boolean - - -@public -class GeoIntersection(GeoSpatialBinOp): - """Return a geometry that represents the point-set intersection of the inputs.""" - - dtype = dt.geometry - - -@public -class GeoDifference(GeoSpatialBinOp): - """Return a geometry that is the delta between the left and right inputs.""" - - dtype = dt.geometry - - -@public -class GeoSimplify(GeoSpatialUnOp): - """Returns a simplified version of the given geometry.""" - - tolerance: Value[dt.Floating] - preserve_collapsed: Value[dt.Boolean] - - dtype = dt.geometry - - -@public -class GeoTransform(GeoSpatialUnOp): - """Returns a transformed version of the given geometry into a new SRID.""" - - srid: Value[dt.Integer] - - dtype = dt.geometry - - -@public -class GeoConvert(GeoSpatialUnOp): - """Returns a transformed version of the given geometry from source crs/srid to a target crs/srid.""" - - source: str - target: str - - dtype = dt.geometry - - -@public -class GeoAsBinary(GeoSpatialUnOp): - """Return the Well-Known Binary (WKB) representation of the input, without SRID meta data.""" - - dtype = dt.binary - - -@public -class GeoAsEWKB(GeoSpatialUnOp): - """Return the Well-Known Binary representation of the input, with SRID meta data.""" - - dtype = dt.binary - - -@public -class GeoAsEWKT(GeoSpatialUnOp): - """Return the Well-Known Text representation of the input, with SRID meta data.""" - - dtype = dt.string - - -@public -class GeoAsText(GeoSpatialUnOp): - """Return the Well-Known Text (WKT) representation of the input, without SRID metadata.""" - - dtype = dt.string - - -@public -class GeoFlipCoordinates(GeoSpatialUnOp): - """Returns a new geometry with the coordinates of the input geometry "flipped" so that x = y and y = x.""" - - dtype = dt.geometry diff --git a/third_party/bigframes_vendored/ibis/expr/operations/histograms.py b/third_party/bigframes_vendored/ibis/expr/operations/histograms.py deleted file mode 100644 index e7487887761..00000000000 --- a/third_party/bigframes_vendored/ibis/expr/operations/histograms.py +++ /dev/null @@ -1,53 +0,0 @@ -# Contains code from https://github.com/ibis-project/ibis/blob/9.2.0/ibis/expr/operations/histograms.py - -"""Operations for computing histograms.""" - -from __future__ import annotations - -import numbers # noqa: TCH003 -from typing import Literal - -import bigframes_vendored.ibis.expr.datashape as ds -import bigframes_vendored.ibis.expr.datatypes as dt -from bigframes_vendored.ibis.common.annotations import ValidationError, attribute -from bigframes_vendored.ibis.common.typing import VarTuple # noqa: TCH001 -from bigframes_vendored.ibis.expr.operations.core import Column, Value -from public import public - - -@public -class Bucket(Value): - """Compute the bucket number of a numeric column.""" - - arg: Column[dt.Numeric | dt.Boolean] - buckets: VarTuple[numbers.Real] - closed: Literal["left", "right"] = "left" - close_extreme: bool = True - include_under: bool = False - include_over: bool = False - - shape = ds.columnar - - @attribute - def dtype(self): - return dt.infer(self.nbuckets) - - def __init__(self, buckets, include_under, include_over, **kwargs): - if not buckets: - raise ValidationError("Must be at least one bucket edge") - elif len(buckets) == 1: - if not include_under or not include_over: - raise ValidationError( - "If one bucket edge provided, must have " - "include_under=True and include_over=True" - ) - super().__init__( - buckets=buckets, - include_under=include_under, - include_over=include_over, - **kwargs, - ) - - @property - def nbuckets(self): - return len(self.buckets) - 1 + self.include_over + self.include_under diff --git a/third_party/bigframes_vendored/ibis/expr/operations/json.py b/third_party/bigframes_vendored/ibis/expr/operations/json.py deleted file mode 100644 index 6b03cb36672..00000000000 --- a/third_party/bigframes_vendored/ibis/expr/operations/json.py +++ /dev/null @@ -1,94 +0,0 @@ -# Contains code from https://github.com/ibis-project/ibis/blob/master/ibis/expr/operations/json.py -"""Operations for working with JSON data.""" - -from __future__ import annotations - -import bigframes_vendored.ibis.expr.datatypes as dt -import bigframes_vendored.ibis.expr.rules as rlz -from bigframes_vendored.ibis.common.annotations import attribute -from bigframes_vendored.ibis.expr.operations import Unary, Value -from public import public - - -@public -class JSONGetItem(Value): - """Get a value from a JSON object or array.""" - - arg: Value[dt.JSON] - index: Value[dt.String | dt.Integer] - - dtype = rlz.dtype_like("arg") - shape = rlz.shape_like("args") - - -@public -class ToJSONArray(Value): - """Convert a value to an array of JSON objects.""" - - arg: Value[dt.JSON] - - shape = rlz.shape_like("arg") - - @attribute - def dtype(self) -> dt.DataType: - return dt.Array(self.arg.dtype) - - -@public -class ToJSONMap(Value): - """Convert a value to a map of string to JSON.""" - - arg: Value[dt.JSON] - - shape = rlz.shape_like("arg") - - @attribute - def dtype(self) -> dt.DataType: - return dt.Map(dt.string, self.arg.dtype) - - -@public -class UnwrapJSONString(Value): - """Unwrap a JSON string into an engine-native string.""" - - arg: Value[dt.JSON] - - dtype = dt.string - shape = rlz.shape_like("arg") - - -@public -class UnwrapJSONInt64(Value): - """Unwrap a JSON number into an engine-native int64.""" - - arg: Value[dt.JSON] - - dtype = dt.int64 - shape = rlz.shape_like("arg") - - -@public -class UnwrapJSONFloat64(Value): - """Unwrap a JSON number into an engine-native float64.""" - - arg: Value[dt.JSON] - - dtype = dt.float64 - shape = rlz.shape_like("arg") - - -@public -class UnwrapJSONBoolean(Value): - """Unwrap a JSON bool into an engine-native bool.""" - - arg: Value[dt.JSON] - - dtype = dt.boolean - shape = rlz.shape_like("arg") - - -# TODO(swast): Remove once supported upstream. -# See: https://github.com/ibis-project/ibis/issues/9542 -@public -class ToJsonString(Unary): - dtype = dt.string diff --git a/third_party/bigframes_vendored/ibis/expr/operations/logical.py b/third_party/bigframes_vendored/ibis/expr/operations/logical.py deleted file mode 100644 index 74ac495642a..00000000000 --- a/third_party/bigframes_vendored/ibis/expr/operations/logical.py +++ /dev/null @@ -1,171 +0,0 @@ -# Contains code from https://github.com/ibis-project/ibis/blob/9.2.0/ibis/expr/operations/logical.py -"""Logical operations.""" - -from __future__ import annotations - -import bigframes_vendored.ibis.expr.datatypes as dt -import bigframes_vendored.ibis.expr.rules as rlz -from bigframes_vendored.ibis.common.annotations import ValidationError, attribute -from bigframes_vendored.ibis.common.exceptions import IbisTypeError -from bigframes_vendored.ibis.common.typing import VarTuple # noqa: TCH001 -from bigframes_vendored.ibis.expr.operations.core import Binary, Unary, Value -from public import public - - -@public -class LogicalBinary(Binary): - """Base class for logical binary operations.""" - - left: Value[dt.Boolean] - right: Value[dt.Boolean] - - dtype = dt.boolean - - -@public -class Not(Unary): - """Logical negation.""" - - arg: Value[dt.Boolean] - - dtype = dt.boolean - - -@public -class And(LogicalBinary): - """Logical AND.""" - - -@public -class Or(LogicalBinary): - """Logical OR.""" - - -@public -class Xor(LogicalBinary): - """Logical XOR.""" - - -@public -class Comparison(Binary): - """Base class for comparison operations.""" - - left: Value - right: Value - - dtype = dt.boolean - - def __init__(self, left, right): - """Construct a comparison operation between `left` and `right`. - - Casting rules for type promotions (for resolving the output type) may - depend on the target backend. - - TODO: how are overflows handled? Can we provide anything useful in - Ibis to help the user avoid them? - """ - if not rlz.comparable(left, right): - raise IbisTypeError( - f"Arguments {rlz._arg_type_error_format(left)} and " - f"{rlz._arg_type_error_format(right)} are not comparable" - ) - super().__init__(left=left, right=right) - - -@public -class Equals(Comparison): - """Equality comparison.""" - - -@public -class NotEquals(Comparison): - """Inequality comparison.""" - - -@public -class GreaterEqual(Comparison): - """Greater than or equal to comparison.""" - - -@public -class Greater(Comparison): - """Greater than comparison.""" - - -@public -class LessEqual(Comparison): - """Less than or equal to comparison.""" - - -@public -class Less(Comparison): - """Less than comparison.""" - - -@public -class IdenticalTo(Comparison): - """Identity comparison. Considers two NULL values **equal**.""" - - -@public -class Between(Value): - """Check if a value is within a range.""" - - arg: Value - lower_bound: Value - upper_bound: Value - - dtype = dt.boolean - shape = rlz.shape_like("args") - - def __init__(self, arg, lower_bound, upper_bound): - if not rlz.comparable(arg, lower_bound): - raise ValidationError( - f"Arguments {rlz._arg_type_error_format(arg)} and " - f"{rlz._arg_type_error_format(lower_bound)} are not comparable" - ) - if not rlz.comparable(arg, upper_bound): - raise ValidationError( - f"Arguments {rlz._arg_type_error_format(arg)} and " - f"{rlz._arg_type_error_format(upper_bound)} are not comparable" - ) - super().__init__(arg=arg, lower_bound=lower_bound, upper_bound=upper_bound) - - -@public -class InValues(Value): - """Check if a value is in a set of values.""" - - value: Value - options: VarTuple[Value] - - dtype = dt.boolean - - @attribute - def shape(self): - args = [self.value, *self.options] - return rlz.highest_precedence_shape(args) - - -@public -class IfElse(Value): - """Ternary case expression. - - Equivalent to - - ```python - bool_expr.case().when(True, true_expr).else_(false_or_null_expr) - ``` - - Many backends implement this as a built-in function. - """ - - bool_expr: Value[dt.Boolean] - true_expr: Value - false_null_expr: Value - - shape = rlz.shape_like("args") - - @attribute - def dtype(self): - return rlz.highest_precedence_dtype([self.true_expr, self.false_null_expr]) diff --git a/third_party/bigframes_vendored/ibis/expr/operations/maps.py b/third_party/bigframes_vendored/ibis/expr/operations/maps.py deleted file mode 100644 index 1111e1e6898..00000000000 --- a/third_party/bigframes_vendored/ibis/expr/operations/maps.py +++ /dev/null @@ -1,101 +0,0 @@ -# Contains code from https://github.com/ibis-project/ibis/blob/9.2.0/ibis/expr/operations/maps.py - -"""Operations for working with maps.""" - -from __future__ import annotations - -import bigframes_vendored.ibis.expr.datatypes as dt -import bigframes_vendored.ibis.expr.rules as rlz -from bigframes_vendored.ibis.common.annotations import attribute -from bigframes_vendored.ibis.expr.operations.core import Unary, Value -from public import public - - -@public -class Map(Value): - """Construct a map.""" - - keys: Value[dt.Array] - values: Value[dt.Array] - - shape = rlz.shape_like("args") - - @attribute - def dtype(self): - return dt.Map( - self.keys.dtype.value_type, - self.values.dtype.value_type, - ) - - -@public -class MapLength(Unary): - """Compute the number of unique keys in a map.""" - - arg: Value[dt.Map] - dtype = dt.int64 - - -@public -class MapGet(Value): - """Get a value from a map by key.""" - - arg: Value[dt.Map] - key: Value - default: Value = None - - shape = rlz.shape_like("args") - - @attribute - def dtype(self): - return dt.higher_precedence(self.default.dtype, self.arg.dtype.value_type) - - -@public -class MapContains(Value): - """Check if a map contains a key.""" - - arg: Value[dt.Map] - key: Value - - shape = rlz.shape_like("args") - dtype = dt.bool - - -@public -class MapKeys(Unary): - """Get the keys of a map as an array.""" - - arg: Value[dt.Map] - - @attribute - def dtype(self): - return dt.Array(self.arg.dtype.key_type) - - -@public -class MapValues(Unary): - """Get the values of a map as an array.""" - - arg: Value[dt.Map] - - @attribute - def dtype(self): - return dt.Array(self.arg.dtype.value_type) - - -@public -class MapMerge(Value): - """Combine two maps into one. - - If a key is present in both maps, the value from the first is kept. - """ - - left: Value[dt.Map] - right: Value[dt.Map] - - shape = rlz.shape_like("args") - dtype = rlz.dtype_like("args") - - -public(MapValueForKey=MapGet, MapValueOrDefaultForKey=MapGet, MapConcat=MapMerge) diff --git a/third_party/bigframes_vendored/ibis/expr/operations/numeric.py b/third_party/bigframes_vendored/ibis/expr/operations/numeric.py deleted file mode 100644 index f4ba57e9d70..00000000000 --- a/third_party/bigframes_vendored/ibis/expr/operations/numeric.py +++ /dev/null @@ -1,374 +0,0 @@ -# Contains code from https://github.com/ibis-project/ibis/blob/9.2.0/ibis/expr/operations/numeric.py - -"""Operations for numeric expressions.""" - -from __future__ import annotations - -import operator -from typing import Optional - -import bigframes_vendored.ibis.expr.datatypes as dt -import bigframes_vendored.ibis.expr.rules as rlz -from bigframes_vendored.ibis import util -from bigframes_vendored.ibis.common.annotations import attribute -from bigframes_vendored.ibis.expr.operations.core import Binary, Unary, Value -from public import public - -Integer = Value[dt.Integer] -SoftNumeric = Value[dt.Numeric | dt.Boolean] -StrictNumeric = Value[dt.Numeric] - - -@public -class NumericBinary(Binary): - left: SoftNumeric - right: SoftNumeric - - -@public -class Add(NumericBinary): - """Add two values.""" - - dtype = rlz.numeric_like("args", operator.add) - - -@public -class Multiply(NumericBinary): - """Multiply two values.""" - - dtype = rlz.numeric_like("args", operator.mul) - - -@public -class Power(NumericBinary): - """Raise the left value to the power of the right value.""" - - @property - def dtype(self): - dtypes = (arg.dtype for arg in self.args) - if util.all_of(dtypes, dt.Integer): - return dt.float64 - else: - return rlz.highest_precedence_dtype(self.args) - - -@public -class Subtract(NumericBinary): - """Subtract the right value from the left value.""" - - dtype = rlz.numeric_like("args", operator.sub) - - -@public -class Divide(NumericBinary): - """Divide the left value by the right value.""" - - dtype = dt.float64 - - -@public -class FloorDivide(Divide): - """Divide the left value by the right value and round down to the nearest integer.""" - - dtype = dt.int64 - - -@public -class Modulus(NumericBinary): - """Return the remainder after the division of the left value by the right value.""" - - dtype = rlz.numeric_like("args", operator.mod) - - -@public -class Negate(Unary): - """Negate the value.""" - - arg: Value[dt.Numeric | dt.Interval] - - dtype = rlz.dtype_like("arg") - - -@public -class IsNan(Unary): - """Check if the value is NaN.""" - - arg: Value[dt.Floating] - - dtype = dt.boolean - - -@public -class IsInf(Unary): - """Check if the value is infinite.""" - - arg: Value[dt.Floating] - - dtype = dt.boolean - - -@public -class Abs(Unary): - """Absolute value.""" - - arg: SoftNumeric - - dtype = rlz.dtype_like("arg") - - -@public -class Ceil(Unary): - """Round up to the nearest integer value greater than or equal to this value.""" - - arg: SoftNumeric - - @property - def dtype(self): - if self.arg.dtype.is_decimal(): - return self.arg.dtype - else: - return dt.int64 - - -@public -class Floor(Unary): - """Round down to the nearest integer value less than or equal to this value.""" - - arg: SoftNumeric - - @property - def dtype(self): - if self.arg.dtype.is_decimal(): - return self.arg.dtype - else: - return dt.int64 - - -@public -class Round(Value): - """Round a value.""" - - arg: StrictNumeric - # TODO(kszucs): the default should be 0 instead of being None - digits: Optional[Integer] = None - - shape = rlz.shape_like("arg") - - @property - def dtype(self): - if self.arg.dtype.is_decimal(): - return self.arg.dtype - else: - return dt.double - - -@public -class Clip(Value): - """Clip a value to a specified range.""" - - arg: StrictNumeric - lower: Optional[StrictNumeric] = None - upper: Optional[StrictNumeric] = None - - dtype = rlz.dtype_like("arg") - shape = rlz.shape_like("arg") - - -@public -class BaseConvert(Value): - """Convert a number from one base to another.""" - - # TODO(kszucs): this should be Integer simply - arg: Value[dt.Integer | dt.String] - from_base: Integer - to_base: Integer - - dtype = dt.string - shape = rlz.shape_like("args") - - -@public -class MathUnary(Unary): - """Base class for unary math operations.""" - - arg: SoftNumeric - - @attribute - def dtype(self): - return dt.higher_precedence(self.arg.dtype, dt.float64) - - -class ExpandingMathUnary(MathUnary): - @attribute - def dtype(self): - if self.arg.dtype.is_decimal(): - return self.arg.dtype - else: - return dt.float64 - - -@public -class Exp(ExpandingMathUnary): - """Exponential function.""" - - -@public -class Sign(Unary): - """Sign of the value.""" - - arg: SoftNumeric - - dtype = rlz.dtype_like("arg") - - -@public -class Sqrt(MathUnary): - """Square root of the value.""" - - -@public -class Logarithm(MathUnary): - """Base class for logarithmic operations.""" - - arg: StrictNumeric - - -@public -class Log(Logarithm): - """Logarithm with a specific base.""" - - base: Optional[StrictNumeric] = None - - -@public -class Ln(Logarithm): - """Natural logarithm.""" - - -@public -class Log2(Logarithm): - """Logarithm base 2.""" - - -@public -class Log10(Logarithm): - """Logarithm base 10.""" - - -@public -class Degrees(ExpandingMathUnary): - """Converts radians to degrees.""" - - -@public -class Radians(MathUnary): - """Converts degrees to radians.""" - - -@public -class TrigonometricUnary(MathUnary): - """Trigonometric base unary.""" - - -@public -class TrigonometricBinary(Binary): - """Trigonometric base binary.""" - - left: SoftNumeric - right: SoftNumeric - - dtype = dt.float64 - - -@public -class Acos(TrigonometricUnary): - """Returns the arc cosine of x.""" - - -@public -class Asin(TrigonometricUnary): - """Returns the arc sine of x.""" - - -@public -class Atan(TrigonometricUnary): - """Returns the arc tangent of x.""" - - -@public -class Atan2(TrigonometricBinary): - """Returns the arc tangent of x and y.""" - - -@public -class Cos(TrigonometricUnary): - """Returns the cosine of x.""" - - -@public -class Cot(TrigonometricUnary): - """Returns the cotangent of x.""" - - -@public -class Sin(TrigonometricUnary): - """Returns the sine of x.""" - - -@public -class Tan(TrigonometricUnary): - """Returns the tangent of x.""" - - -@public -class BitwiseNot(Unary): - """Bitwise NOT operation.""" - - arg: Value[dt.Integer | dt.Binary] - - dtype = rlz.numeric_like("args", operator.invert) - - -@public -class BitwiseBinary(Binary): - """Base class for bitwise binary operations.""" - - left: Integer - right: Integer - - -@public -class BitwiseAnd(BitwiseBinary): - """Bitwise AND operation.""" - - dtype = rlz.numeric_like("args", operator.and_) - - -@public -class BitwiseOr(BitwiseBinary): - """Bitwise OR operation.""" - - dtype = rlz.numeric_like("args", operator.or_) - - -@public -class BitwiseXor(BitwiseBinary): - """Bitwise XOR operation.""" - - dtype = rlz.numeric_like("args", operator.xor) - - -@public -class BitwiseLeftShift(BitwiseBinary): - """Bitwise left shift operation.""" - - shape = rlz.shape_like("args") - dtype = dt.int64 - - -@public -class BitwiseRightShift(BitwiseBinary): - """Bitwise right shift operation.""" - - shape = rlz.shape_like("args") - dtype = dt.int64 diff --git a/third_party/bigframes_vendored/ibis/expr/operations/reductions.py b/third_party/bigframes_vendored/ibis/expr/operations/reductions.py index b739c7048fd..5e6ad9ecf2b 100644 --- a/third_party/bigframes_vendored/ibis/expr/operations/reductions.py +++ b/third_party/bigframes_vendored/ibis/expr/operations/reductions.py @@ -1,420 +1,23 @@ -# Contains code from https://github.com/ibis-project/ibis/blob/9.2.0/ibis/expr/operations/reductions.py - -"""Reduction operations.""" +# Contains code from https://github.com/ibis-project/ibis/blob/master/ibis/expr/operations/reductions.py from __future__ import annotations -from typing import Literal, Optional - -import bigframes_vendored.ibis.expr.datashape as ds -import bigframes_vendored.ibis.expr.datatypes as dt -import bigframes_vendored.ibis.expr.rules as rlz -from bigframes_vendored.ibis.common.annotations import attribute -from bigframes_vendored.ibis.common.typing import VarTuple -from bigframes_vendored.ibis.expr.operations.core import Column, Value -from bigframes_vendored.ibis.expr.operations.relations import Relation # noqa: TCH001 -from public import public - - -@public -class Reduction(Value): - """Base class for reduction operations.""" - - shape = ds.scalar - - -# TODO(kszucs): all reductions all filterable so we could remove Filterable -class Filterable(Value): - where: Optional[Value[dt.Boolean]] = None - - -@public -class Count(Filterable, Reduction): - """Count the number of non-null elements of a column.""" - - arg: Column[dt.Any] - - dtype = dt.int64 - - -@public -class CountStar(Filterable, Reduction): - """Count the number of rows of a relation.""" - - arg: Relation - - dtype = dt.int64 - - @attribute - def relations(self): - return frozenset({self.arg}) - - -@public -class CountDistinctStar(Filterable, Reduction): - """Count the number of distinct rows of a relation.""" - - arg: Relation - - dtype = dt.int64 - - @attribute - def relations(self): - return frozenset({self.arg}) - - -@public -class Arbitrary(Filterable, Reduction): - """Retrieve an arbitrary element. - - Returns a non-null value unless the column is empty or all values are NULL. - """ - - arg: Column[dt.Any] - - dtype = rlz.dtype_like("arg") - - -@public -class First(Filterable, Reduction): - """Retrieve the first element.""" - - arg: Column[dt.Any] - - dtype = rlz.dtype_like("arg") - - -@public -class Last(Filterable, Reduction): - """Retrieve the last element.""" - - arg: Column[dt.Any] - - dtype = rlz.dtype_like("arg") - - -@public -class BitAnd(Filterable, Reduction): - """Aggregate bitwise AND operation. - - All elements in an integer column are ANDed together. - - This can be used to determine which bit flags are set on all elements. - - See Also - -------- - * BigQuery [`BIT_AND`](https://cloud.google.com/bigquery/docs/reference/standard-sql/aggregate_functions#bit_and) - * MySQL [`BIT_AND`](https://dev.mysql.com/doc/refman/5.7/en/aggregate-functions.html#function_bit-and) - """ - - arg: Column[dt.Integer] - - dtype = rlz.dtype_like("arg") - - -@public -class BitOr(Filterable, Reduction): - """Aggregate bitwise OR operation. - - All elements in an integer column are ORed together. This can be used - to determine which bit flags are set on any element. - - See Also - -------- - * BigQuery [`BIT_OR`](https://cloud.google.com/bigquery/docs/reference/standard-sql/aggregate_functions#bit_or) - * MySQL [`BIT_OR`](https://dev.mysql.com/doc/refman/5.7/en/aggregate-functions.html#function_bit-or) - """ - - arg: Column[dt.Integer] - - dtype = rlz.dtype_like("arg") - - -@public -class BitXor(Filterable, Reduction): - """Aggregate bitwise XOR operation. - - All elements in an integer column are XORed together. This can be used - as a parity checksum of element values. - - See Also - -------- - * BigQuery [`BIT_XOR`](https://cloud.google.com/bigquery/docs/reference/standard-sql/aggregate_functions#bit_xor) - * MySQL [`BIT_XOR`](https://dev.mysql.com/doc/refman/5.7/en/aggregate-functions.html#function_bit-xor) - """ - - arg: Column[dt.Integer] - - dtype = rlz.dtype_like("arg") - - -@public -class Sum(Filterable, Reduction): - """Compute the sum of a column.""" - - arg: Column[dt.Numeric | dt.Boolean] - - @attribute - def dtype(self): - dtype = self.arg.dtype - if dtype.is_boolean(): - return dt.int64 - elif dtype.is_integer(): - return dt.int64 - elif dtype.is_unsigned_integer(): - return dt.uint64 - elif dtype.is_floating(): - return dt.float64 - elif dtype.is_decimal(): - return dt.Decimal( - precision=max(dtype.precision, 38) - if dtype.precision is not None - else None, - scale=max(dtype.scale, 2) if dtype.scale is not None else None, - ) - else: - raise TypeError(f"Cannot compute sum of {dtype} values") - - -@public -class Mean(Filterable, Reduction): - """Compute the mean of a column.""" - - arg: Column[dt.Numeric | dt.Boolean] - - @attribute - def dtype(self): - if (dtype := self.arg.dtype).is_boolean(): - return dt.float64 - else: - return dt.higher_precedence(dtype, dt.float64) - - -class QuantileBase(Filterable, Reduction): - arg: Column - - @attribute - def dtype(self): - dtype = self.arg.dtype - if dtype.is_numeric(): - dtype = dt.higher_precedence(dtype, dt.float64) - return dtype - - -@public -class Median(QuantileBase): - """Compute the median of a column.""" - - -@public -class Quantile(QuantileBase): - """Compute the quantile of a column.""" - - quantile: Value[dt.Numeric] - +import ibis.expr.datatypes as dt +from ibis.expr.operations.reductions import Filterable, Reduction +import ibis.expr.rules as rlz -@public -class MultiQuantile(Filterable, Reduction): - """Compute multiple quantiles of a column.""" - arg: Column - quantile: Value[dt.Array[dt.Numeric]] +class ApproximateMultiQuantile(Filterable, Reduction): + """Calculate (approximately) evenly-spaced quantiles. - @attribute - def dtype(self): - dtype = self.arg.dtype - if dtype.is_numeric(): - dtype = dt.higher_precedence(dtype, dt.float64) - return dt.Array(dtype) - - -class VarianceBase(Filterable, Reduction): - """Base class for variance and standard deviation.""" - - arg: Column[dt.Numeric | dt.Boolean] - how: Literal["sample", "pop"] - - @attribute - def dtype(self): - if self.arg.dtype.is_decimal(): - return self.arg.dtype - else: - return dt.float64 - - -@public -class StandardDev(VarianceBase): - """Compute the standard deviation of a column.""" - - -@public -class Variance(VarianceBase): - """Compute the variance of a column.""" - - -@public -class Correlation(Filterable, Reduction): - """Correlation coefficient of two columns.""" - - left: Column[dt.Numeric | dt.Boolean] - right: Column[dt.Numeric | dt.Boolean] - how: Literal["sample", "pop"] = "sample" - - dtype = dt.float64 - - -@public -class Covariance(Filterable, Reduction): - """Covariance of two columns.""" - - left: Column[dt.Numeric | dt.Boolean] - right: Column[dt.Numeric | dt.Boolean] - how: Literal["sample", "pop"] - - dtype = dt.float64 - - -@public -class Mode(Filterable, Reduction): - """Compute the mode of a column.""" - - arg: Column - - dtype = rlz.dtype_like("arg") - - -@public -class Max(Filterable, Reduction): - """Compute the maximum of a column.""" - - arg: Column - - dtype = rlz.dtype_like("arg") - - -@public -class Min(Filterable, Reduction): - """Compute the minimum of a column.""" - - arg: Column - - dtype = rlz.dtype_like("arg") - - -@public -class ArgMax(Filterable, Reduction): - """Compute the index of the maximum value in a column.""" - - arg: Column - key: Column - - dtype = rlz.dtype_like("arg") - - -@public -class ArgMin(Filterable, Reduction): - """Compute the index of the minimum value in a column.""" - - arg: Column - key: Column - - dtype = rlz.dtype_like("arg") - - -@public -class ApproxCountDistinct(Filterable, Reduction): - """Approximate number of unique values.""" - - arg: Column - - # Impala 2.0 and higher returns a DOUBLE - dtype = dt.int64 - - -@public -class ApproxMedian(Filterable, Reduction): - """Compute the approximate median of a set of comparable values.""" - - arg: Column - - dtype = rlz.dtype_like("arg") - - -@public -class GroupConcat(Filterable, Reduction): - """Concatenate strings in a group with a given separator character.""" - - arg: Column - sep: Value[dt.String] - - dtype = dt.string - - -@public -class CountDistinct(Filterable, Reduction): - """Count the number of distinct values in a column.""" - - arg: Column - - dtype = dt.int64 - - -@public -class ArrayCollect(Filterable, Reduction): - """Collect values into an array.""" - - arg: Column - - @attribute - def dtype(self): - return dt.Array(self.arg.dtype) - - -@public -class All(Filterable, Reduction): - """Check if all values in a column are true.""" - - arg: Column[dt.Boolean] - - dtype = dt.boolean - - -@public -class Any(Filterable, Reduction): - """Check if any value in a column is true.""" - - arg: Column[dt.Boolean] - - dtype = dt.boolean - - -@public -class ArrayAggregate(Filterable, Reduction): - """ - Collects the elements of this expression into an ordered array. Similar to - the ibis `ArrayCollect`, but adds `order_by_*` and `distinct_only` parameters. - """ - - arg: Column - order_by: VarTuple[Value] = () - - @attribute - def dtype(self): - return dt.Array(self.arg.dtype) - - -@public -class StringAgg(Filterable, Reduction): - """ - Collects the elements of this expression into a string. Similar to - the ibis `GroupConcat`, but adds `order_by_*` parameter. + See: https://cloud.google.com/bigquery/docs/reference/standard-sql/approximate_aggregate_functions#approx_quantiles """ - arg: Column - sep: Value[dt.String] + arg = rlz.any + num_bins = rlz.value(dt.int64) + output_dtype = dt.Array(dt.float64) - order_by: VarTuple[Value] = () - @attribute - def dtype(self): - return dt.string +__all__ = [ + "ApproximateMultiQuantile", +] diff --git a/third_party/bigframes_vendored/ibis/expr/operations/relations.py b/third_party/bigframes_vendored/ibis/expr/operations/relations.py deleted file mode 100644 index c230cbe20b5..00000000000 --- a/third_party/bigframes_vendored/ibis/expr/operations/relations.py +++ /dev/null @@ -1,521 +0,0 @@ -# Contains code from https://github.com/ibis-project/ibis/blob/9.2.0/ibis/expr/operations/relations.py - -"""Relational operations.""" - -from __future__ import annotations - -import itertools -import typing -from abc import abstractmethod -from typing import Annotated, Any, Literal, Optional, TypeVar - -import bigframes_vendored.ibis.expr.datashape as ds -import bigframes_vendored.ibis.expr.datatypes as dt -from bigframes_vendored.ibis.common.annotations import attribute -from bigframes_vendored.ibis.common.collections import FrozenDict, FrozenOrderedDict -from bigframes_vendored.ibis.common.exceptions import ( - IbisTypeError, - IntegrityError, - RelationError, -) -from bigframes_vendored.ibis.common.grounds import Concrete -from bigframes_vendored.ibis.common.patterns import Between, InstanceOf -from bigframes_vendored.ibis.common.typing import Coercible, VarTuple -from bigframes_vendored.ibis.expr.operations.core import ( - Alias, - Column, - Node, - Scalar, - Value, -) -from bigframes_vendored.ibis.expr.operations.sortkeys import SortKey -from bigframes_vendored.ibis.expr.schema import Schema -from bigframes_vendored.ibis.formats import TableProxy # noqa: TCH001 -from public import public - -T = TypeVar("T") - -Unaliased = Annotated[T, ~InstanceOf(Alias)] -NonSortKey = Annotated[T, ~InstanceOf(SortKey)] - - -@public -class Relation(Node, Coercible): - """Base class for relational operations.""" - - @classmethod - def __coerce__(cls, value): - from bigframes_vendored.ibis.expr.types import Table - - if isinstance(value, Relation): - return value - elif isinstance(value, Table): - return value.op() - else: - raise TypeError(f"Cannot coerce {value!r} to a Relation") - - @property - @abstractmethod - def values(self) -> FrozenOrderedDict[str, Value]: - """A mapping of column names to expressions which build up the relation. - - This attribute is heavily used in rewrites as well as during field - dereferencing in the API layer. The returned expressions must only - originate from parent relations, depending on the relation type. - """ - - @property - @abstractmethod - def schema(self) -> Schema: - """The schema of the relation. - - All relations must have a well-defined schema. - """ - ... - - @property - def fields(self) -> FrozenOrderedDict[str, Column]: - """A mapping of column names to fields of the relation. - - This calculated property shouldn't be overridden in subclasses since it - is mostly used for convenience. - """ - return FrozenOrderedDict({k: Field(self, k) for k in self.schema}) - - def to_expr(self): - from bigframes_vendored.ibis.expr.types import Table - - return Table(self) - - -@public -class Field(Value): - """A field of a relation.""" - - rel: Relation - name: str - - shape = ds.columnar - - def __init__(self, rel, name): - if name not in rel.schema: - columns_formatted = ", ".join(map(repr, rel.schema.names)) - raise IbisTypeError( - f"Column {name!r} is not found in table. " - f"Existing columns: {columns_formatted}." - ) - super().__init__(rel=rel, name=name) - - @attribute - def dtype(self): - return self.rel.schema[self.name] - - @attribute - def relations(self): - return frozenset({self.rel}) - - -def _check_integrity(values, allowed_parents): - for value in values: - for rel in value.relations: - if rel not in allowed_parents: - raise IntegrityError( - f"Cannot add {value!r} to projection, they belong to another relation" - ) - - -@public -class Project(Relation): - """Project a subset of columns from a relation.""" - - parent: Relation - values: FrozenOrderedDict[str, NonSortKey[Unaliased[Value]]] - - def __init__(self, parent, values): - _check_integrity(values.values(), {parent}) - super().__init__(parent=parent, values=values) - - @attribute - def schema(self): - return Schema({k: v.dtype for k, v in self.values.items()}) - - -class Simple(Relation): - parent: Relation - - @attribute - def values(self): - return self.parent.fields - - @attribute - def schema(self): - return self.parent.schema - - -@public -class DropColumns(Relation): - parent: Relation - columns_to_drop: VarTuple[str] - - @attribute - def schema(self): - schema = self.parent.schema.fields.copy() - for column in self.columns_to_drop: - del schema[column] - return Schema(schema) - - @attribute - def values(self): - fields = self.parent.fields.copy() - for column in self.columns_to_drop: - del fields[column] - return fields - - -@public -class Reference(Relation): - _uid_counter = itertools.count() - parent: Relation - identifier: Optional[int] = None - - def __init__(self, parent, identifier): - if identifier is None: - identifier = next(self._uid_counter) - super().__init__(parent=parent, identifier=identifier) - - @attribute - def schema(self): - return self.parent.schema - - -# TODO(kszucs): remove in favor of View -@public -class SelfReference(Reference): - values = FrozenOrderedDict() - - -@public -class JoinReference(Reference): - @attribute - def values(self): - return self.parent.fields - - -JoinKind = Literal[ - "inner", - "left", - "right", - "outer", - "asof", - "semi", - "anti", - "any_inner", - "any_left", - "cross", - "positional", -] - - -@public -class JoinLink(Node): - how: JoinKind - table: Reference - predicates: VarTuple[Value[dt.Boolean]] - - -@public -class JoinChain(Relation): - first: Reference - rest: VarTuple[JoinLink] - values: FrozenOrderedDict[str, Unaliased[Value]] - - def __init__(self, first, rest, values): - allowed_parents = {first} - for join in rest: - if join.table in allowed_parents: - raise IntegrityError( - f"Cannot add {join.table!r} to the join chain, it is already in the chain" - ) - allowed_parents.add(join.table) - _check_integrity(join.predicates, allowed_parents) - _check_integrity(values.values(), allowed_parents) - super().__init__(first=first, rest=rest, values=values) - - @property - def tables(self): - return [self.first] + [link.table for link in self.rest] - - @property - def length(self): - return len(self.rest) + 1 - - @attribute - def schema(self): - return Schema({k: v.dtype.copy(nullable=True) for k, v in self.values.items()}) - - def to_expr(self): - import bigframes_vendored.ibis.expr.types as ir - - return ir.Join(self) - - -@public -class Sort(Simple): - """Sort a table by a set of keys.""" - - keys: VarTuple[SortKey] - - def __init__(self, parent, keys): - _check_integrity(keys, {parent}) - super().__init__(parent=parent, keys=keys) - - -@public -class Filter(Simple): - """Filter a table by a set of predicates.""" - - predicates: VarTuple[Value[dt.Boolean]] - - def __init__(self, parent, predicates): - from bigframes_vendored.ibis.expr.rewrites import ReductionLike - - for pred in predicates: - if pred.find(ReductionLike, filter=Value): - raise IntegrityError( - f"Cannot add {pred!r} to filter, it is a reduction which " - "must be converted to a scalar subquery first" - ) - if pred.relations and parent not in pred.relations: - raise IntegrityError( - f"Cannot add {pred!r} to filter, they belong to another relation" - ) - super().__init__(parent=parent, predicates=predicates) - - -@public -class Limit(Simple): - """Limit and/or offset the number of records in a table.""" - - # TODO(kszucs): dynamic limit should contain ScalarSubqueries rather than - # plain scalar values - n: typing.Union[int, Scalar[dt.Integer], None] = None - offset: typing.Union[int, Scalar[dt.Integer]] = 0 - - -@public -class Aggregate(Relation): - """Aggregate a table by a set of group by columns and metrics.""" - - parent: Relation - groups: FrozenOrderedDict[str, Unaliased[Value]] - metrics: FrozenOrderedDict[str, Unaliased[Scalar]] - - def __init__(self, parent, groups, metrics): - _check_integrity(groups.values(), {parent}) - _check_integrity(metrics.values(), {parent}) - if duplicates := groups.keys() & metrics.keys(): - raise RelationError( - f"Cannot add {duplicates} to aggregate, they are already in the groupby" - ) - super().__init__(parent=parent, groups=groups, metrics=metrics) - - @attribute - def values(self): - return FrozenOrderedDict({**self.groups, **self.metrics}) - - @attribute - def schema(self): - return Schema({k: v.dtype for k, v in self.values.items()}) - - -@public -class Set(Relation): - """Base class for set operations.""" - - left: Relation - right: Relation - distinct: bool = False - values = FrozenOrderedDict() - - def __init__(self, left, right, **kwargs): - if left.schema.names != right.schema.names: - # rewrite so that both sides have the columns in the same order making it - # easier for the backends to implement set operations - cols = {name: Field(right, name) for name in left.schema.names} - right = Project(right, cols) - super().__init__(left=left, right=right, **kwargs) - - @attribute - def schema(self): - dtypes = ( - dt.higher_precedence(ltype, rtype) - for ltype, rtype in zip( - self.left.schema.values(), self.right.schema.values() - ) - ) - return Schema.from_tuples( - (name, coltype) for name, coltype in zip(self.left.schema.names, dtypes) - ) - - -@public -class Union(Set): - """Union two tables.""" - - -@public -class Intersection(Set): - """Intersect two tables.""" - - -@public -class Difference(Set): - """Subtract one table from another.""" - - -@public -class PhysicalTable(Relation): - """Base class for tables with a name.""" - - name: str - values = FrozenOrderedDict() - - -@public -class Namespace(Concrete): - """Object to model namespaces for tables. - - Maps to the concept of database and/or catalog in SQL databases that support - them. - """ - - catalog: Optional[str] = None - database: Optional[str] = None - - -@public -class UnboundTable(PhysicalTable): - """A table that is not bound to a specific backend.""" - - schema: Schema - namespace: Namespace = Namespace() - - -@public -class DatabaseTable(PhysicalTable): - """A table that is bound to a specific backend.""" - - schema: Schema - source: Any - namespace: Namespace = Namespace() - - -@public -class InMemoryTable(PhysicalTable): - """A table whose data is stored in memory.""" - - schema: Schema - data: TableProxy - - -@public -class SQLQueryResult(Relation): - """A table sourced from the result set of a SQL SELECT statement.""" - - query: str - schema: Schema - source: Any - values = FrozenOrderedDict() - - -@public -class View(PhysicalTable): - """A view created from an expression.""" - - # TODO(kszucs): rename it to parent - child: Relation - - @attribute - def schema(self): - return self.child.schema - - -@public -class SQLStringView(Relation): - """A view created from a SQL string.""" - - child: Relation - query: str - schema: Schema - values = FrozenOrderedDict() - - -@public -class DummyTable(Relation): - """A table constructed from literal values.""" - - values: FrozenOrderedDict[str, Value] - - @attribute - def schema(self): - return Schema({k: v.dtype for k, v in self.values.items()}) - - -@public -class FillNull(Simple): - """Fill null values in the table.""" - - replacements: typing.Union[Value[dt.Numeric | dt.String], FrozenDict[str, Any]] - - -@public -class DropNull(Simple): - """Drop null values in the table.""" - - how: typing.Literal["any", "all"] - subset: Optional[VarTuple[Column]] = None - - -@public -class Sample(Simple): - """Sample performs random sampling of records in a table.""" - - fraction: Annotated[float, Between(0, 1)] - method: typing.Literal["row", "block"] - seed: typing.Union[int, None] = None - - -@public -class Distinct(Simple): - """Compute the distinct rows of a table.""" - - -@public -class TableUnnest(Relation): - """Cross join unnest operation.""" - - parent: Relation - column: Value[dt.Array] - offset: typing.Union[str, None] - keep_empty: bool - - @attribute - def values(self): - return self.parent.fields - - @attribute - def schema(self): - column = self.column - offset = self.offset - - base = self.parent.schema.fields.copy() - - base[column.name] = column.dtype.value_type - - if offset is not None: - base[offset] = dt.int64 - - return Schema(base) - - -# TODO(kszucs): support t.select(*t) syntax by implementing Table.__iter__() diff --git a/third_party/bigframes_vendored/ibis/expr/operations/sortkeys.py b/third_party/bigframes_vendored/ibis/expr/operations/sortkeys.py deleted file mode 100644 index f1c5b9820ac..00000000000 --- a/third_party/bigframes_vendored/ibis/expr/operations/sortkeys.py +++ /dev/null @@ -1,43 +0,0 @@ -# Contains code from https://github.com/ibis-project/ibis/blob/9.2.0/ibis/expr/operations/sortkeys.py - -"""Sort key operations.""" - -from __future__ import annotations - -import bigframes_vendored.ibis.expr.rules as rlz -from bigframes_vendored.ibis.expr.operations.core import Value -from public import public - -# TODO(kszucs): move the content of this file to generic.py - - -# TODO(kszucs): consider to limit its shape to Columnar, we could treat random() -# as a columnar operation too -@public -class SortKey(Value): - """A sort key.""" - - # TODO(kszucs): rename expr to arg or something else except expr - expr: Value - ascending: bool = True - nulls_first: bool = False - - dtype = rlz.dtype_like("expr") - shape = rlz.shape_like("expr") - - @classmethod - def __coerce__(cls, key, T=None, S=None): - key = super().__coerce__(key, T=T, S=S) - - if isinstance(key, cls): - return key - else: - return cls(key) - - @property - def name(self) -> str: - return self.expr.name - - @property - def descending(self) -> bool: - return not self.ascending diff --git a/third_party/bigframes_vendored/ibis/expr/operations/strings.py b/third_party/bigframes_vendored/ibis/expr/operations/strings.py deleted file mode 100644 index c2dc151ae07..00000000000 --- a/third_party/bigframes_vendored/ibis/expr/operations/strings.py +++ /dev/null @@ -1,397 +0,0 @@ -# Contains code from https://github.com/ibis-project/ibis/blob/9.2.0/ibis/expr/operations/strings.py - -"""String operations.""" - -from __future__ import annotations - -from typing import Optional - -import bigframes_vendored.ibis.expr.datatypes as dt -import bigframes_vendored.ibis.expr.rules as rlz -from bigframes_vendored.ibis.common.annotations import attribute -from bigframes_vendored.ibis.common.typing import VarTuple # noqa: TCH001 -from bigframes_vendored.ibis.expr.operations.core import Unary, Value -from public import public - - -@public -class StringUnary(Unary): - """Base class for string operations accepting one argument.""" - - arg: Value[dt.String] - - dtype = dt.string - - -@public -class Uppercase(StringUnary): - """Convert a string to uppercase.""" - - -@public -class Lowercase(StringUnary): - """Convert a string to lowercase.""" - - -@public -class Reverse(StringUnary): - """Reverse a string.""" - - -@public -class Strip(StringUnary): - """Strip leading and trailing whitespace.""" - - -@public -class LStrip(StringUnary): - """Strip leading whitespace.""" - - -@public -class RStrip(StringUnary): - """Strip trailing whitespace.""" - - -@public -class Capitalize(StringUnary): - """Capitalize the first letter of a string.""" - - -@public -class Substring(Value): - """Extract a substring from a string.""" - - arg: Value[dt.String] - start: Value[dt.Integer] - length: Optional[Value[dt.Integer]] = None - - dtype = dt.string - shape = rlz.shape_like("args") - - -@public -class StringSlice(Value): - """Extract a substring from a string.""" - - arg: Value[dt.String] - start: Optional[Value[dt.Integer]] = None - end: Optional[Value[dt.Integer]] = None - - dtype = dt.string - shape = rlz.shape_like("args") - - -@public -class StrRight(Value): - """Extract a substring starting from the right of a string.""" - - arg: Value[dt.String] - nchars: Value[dt.Integer] - - shape = rlz.shape_like("args") - dtype = dt.string - - -@public -class Repeat(Value): - """Repeat a string.""" - - arg: Value[dt.String] - times: Value[dt.Integer] - - shape = rlz.shape_like("args") - dtype = dt.string - - -@public -class StringFind(Value): - """Find the position of a substring in a string.""" - - arg: Value[dt.String] - substr: Value[dt.String] - start: Optional[Value[dt.Integer]] = None - end: Optional[Value[dt.Integer]] = None - - shape = rlz.shape_like("args") - dtype = dt.int64 - - -@public -class Translate(Value): - """Translate characters in a string.""" - - arg: Value[dt.String] - from_str: Value[dt.String] - to_str: Value[dt.String] - - shape = rlz.shape_like("args") - dtype = dt.string - - -@public -class LPad(Value): - """Pad a string on the left.""" - - arg: Value[dt.String] - length: Value[dt.Integer] - pad: Optional[Value[dt.String]] = None - - shape = rlz.shape_like("args") - dtype = dt.string - - -@public -class RPad(Value): - """Pad a string on the right.""" - - arg: Value[dt.String] - length: Value[dt.Integer] - pad: Optional[Value[dt.String]] = None - - shape = rlz.shape_like("args") - dtype = dt.string - - -@public -class FindInSet(Value): - """Find the position of a string in a list of comma-separated strings.""" - - needle: Value[dt.String] - values: VarTuple[Value[dt.String]] - - shape = rlz.shape_like("needle") - dtype = dt.int64 - - -@public -class StringJoin(Value): - """Join strings with a separator.""" - - arg: VarTuple[Value[dt.String]] - sep: Value[dt.String] - - dtype = dt.string - - @attribute - def shape(self): - return rlz.highest_precedence_shape((self.sep, *self.arg)) - - -@public -class ArrayStringJoin(Value): - """Join strings in an array with a separator.""" - - arg: Value[dt.Array[dt.String]] - sep: Value[dt.String] - - dtype = dt.string - shape = rlz.shape_like("args") - - -@public -class StartsWith(Value): - """Check if a string starts with another string.""" - - arg: Value[dt.String] - start: Value[dt.String] - - dtype = dt.boolean - shape = rlz.shape_like("args") - - -@public -class EndsWith(Value): - """Check if a string ends with another string.""" - - arg: Value[dt.String] - end: Value[dt.String] - - dtype = dt.boolean - shape = rlz.shape_like("args") - - -@public -class FuzzySearch(Value): - arg: Value[dt.String] - pattern: Value[dt.String] - - dtype = dt.boolean - shape = rlz.shape_like("args") - - -@public -class StringSQLLike(FuzzySearch): - """SQL LIKE string match operation. - - Similar to globbing. - """ - - arg: Value[dt.String] - pattern: Value[dt.String] - escape: Optional[str] = None - - -@public -class StringSQLILike(StringSQLLike): - """Case-insensitive SQL LIKE string match operation. - - Similar to case-insensitive globbing. - """ - - -@public -class RegexSearch(FuzzySearch): - """Search a string with a regular expression.""" - - -@public -class RegexExtract(Value): - """Extract a substring from a string using a regular expression.""" - - arg: Value[dt.String] - pattern: Value[dt.String] - index: Value[dt.Integer] - - shape = rlz.shape_like("args") - dtype = dt.string - - -@public -class RegexSplit(Value): - """Split a string using a regular expression.""" - - arg: Value[dt.String] - pattern: Value[dt.String] - - shape = rlz.shape_like("args") - dtype = dt.Array(dt.string) - - -@public -class RegexReplace(Value): - """Replace a substring in a string using a regular expression.""" - - arg: Value[dt.String] - pattern: Value[dt.String] - replacement: Value[dt.String] - - shape = rlz.shape_like("args") - dtype = dt.string - - -@public -class StringReplace(Value): - """Replace a substring in a string with another string.""" - - arg: Value[dt.String] - pattern: Value[dt.String] - replacement: Value[dt.String] - - shape = rlz.shape_like("args") - dtype = dt.string - - -@public -class StringSplit(Value): - """Split a string using a delimiter.""" - - arg: Value[dt.String] - delimiter: Value[dt.String] - - shape = rlz.shape_like("args") - dtype = dt.Array(dt.string) - - -@public -class StringConcat(Value): - """Concatenate strings.""" - - arg: VarTuple[Value[dt.String]] - - shape = rlz.shape_like("arg") - dtype = rlz.dtype_like("arg") - - -@public -class ExtractURLField(StringUnary): - pass - - -@public -class ExtractProtocol(ExtractURLField): - """Extract the protocol from a URL.""" - - -@public -class ExtractAuthority(ExtractURLField): - """Extract the authority from a URL.""" - - -@public -class ExtractUserInfo(ExtractURLField): - """Extract the user info from a URL.""" - - -@public -class ExtractHost(ExtractURLField): - """Extract the host from a URL.""" - - -@public -class ExtractFile(ExtractURLField): - """Extract the file from a URL.""" - - -@public -class ExtractPath(ExtractURLField): - """Extract the path from a URL.""" - - -@public -class ExtractQuery(ExtractURLField): - """Extract the query from a URL.""" - - key: Optional[Value[dt.String]] = None - - -@public -class ExtractFragment(ExtractURLField): - """Extract the fragment from a URL.""" - - -@public -class StringLength(Unary): - """Compute the length of a string or binary value.""" - - arg: Value[dt.String | dt.Binary] - dtype = dt.int64 - - -@public -class StringAscii(StringUnary): - """Compute the ASCII code of the first character of a string.""" - - dtype = dt.int64 - - -@public -class StringContains(Value): - """Check if a string contains a substring.""" - - haystack: Value[dt.String] - needle: Value[dt.String] - - shape = rlz.shape_like("args") - dtype = dt.bool - - -@public -class Levenshtein(Value): - """Compute the Levenshtein distance between two strings.""" - - left: Value[dt.String] - right: Value[dt.String] - - dtype = dt.int64 - shape = rlz.shape_like("args") diff --git a/third_party/bigframes_vendored/ibis/expr/operations/structs.py b/third_party/bigframes_vendored/ibis/expr/operations/structs.py deleted file mode 100644 index aa26841d9ab..00000000000 --- a/third_party/bigframes_vendored/ibis/expr/operations/structs.py +++ /dev/null @@ -1,62 +0,0 @@ -# Contains code from https://github.com/ibis-project/ibis/blob/9.2.0/ibis/expr/operations/structs.py - -"""Operations for working with structs.""" - -from __future__ import annotations - -import bigframes_vendored.ibis.expr.datatypes as dt -import bigframes_vendored.ibis.expr.rules as rlz -from bigframes_vendored.ibis.common.annotations import ValidationError, attribute -from bigframes_vendored.ibis.common.typing import VarTuple # noqa: TCH001 -from bigframes_vendored.ibis.expr.operations.core import Value -from public import public - - -@public -class StructField(Value): - """Extract a field from a struct value.""" - - arg: Value[dt.Struct] - field: str - - shape = rlz.shape_like("arg") - - @attribute - def dtype(self) -> dt.DataType: - struct_dtype = self.arg.dtype - value_dtype = struct_dtype[self.field] - return value_dtype - - @property - def name(self) -> str: - return self.field - - -@public -class StructColumn(Value): - """Construct a struct column from literals or expressions.""" - - names: VarTuple[str] - values: VarTuple[Value] - - shape = rlz.shape_like("values") - - def __init__(self, names, values): - if len(names) != len(values): - raise ValidationError( - f"Length of names ({len(names)}) does not match length of " - f"values ({len(values)})" - ) - super().__init__(names=names, values=values) - - @property - def name(self) -> str: - pairs = ", ".join( - f"{name!r}: {op.name}" for name, op in zip(self.names, self.values) - ) - return f"{self.__class__.__name__}({{{pairs}}})" - - @attribute - def dtype(self) -> dt.DataType: - dtypes = (value.dtype for value in self.values) - return dt.Struct.from_tuples(zip(self.names, dtypes)) diff --git a/third_party/bigframes_vendored/ibis/expr/operations/subqueries.py b/third_party/bigframes_vendored/ibis/expr/operations/subqueries.py deleted file mode 100644 index c0b95a5d36c..00000000000 --- a/third_party/bigframes_vendored/ibis/expr/operations/subqueries.py +++ /dev/null @@ -1,86 +0,0 @@ -# Contains code from https://github.com/ibis-project/ibis/blob/9.2.0/ibis/expr/operations/subqueries.py -"""Subquery operations.""" - -from __future__ import annotations - -import bigframes_vendored.ibis.expr.datashape as ds -import bigframes_vendored.ibis.expr.datatypes as dt -import bigframes_vendored.ibis.expr.rules as rlz -from bigframes_vendored.ibis.common.annotations import attribute -from bigframes_vendored.ibis.common.exceptions import IntegrityError -from bigframes_vendored.ibis.expr.operations.core import Value -from bigframes_vendored.ibis.expr.operations.relations import Relation # noqa: TCH001 -from public import public - - -@public -class Subquery(Value): - """Base class for subquery operations.""" - - rel: Relation - - @attribute - def relations(self): - return frozenset() - - -@public -class ExistsSubquery(Subquery): - """Check if a subquery returns any rows.""" - - dtype = dt.boolean - shape = ds.columnar - - -@public -class ScalarSubquery(Subquery): - """A subquery that returns a single scalar value.""" - - shape = ds.scalar - - def __init__(self, rel): - if len(rel.schema) != 1: - raise IntegrityError( - "Relation passed to ScalarSubquery() must have exactly one " - f"column, got {len(rel.schema)}" - ) - super().__init__(rel=rel) - - @attribute - def value(self): - (value,) = self.rel.values.values() - return value - - @attribute - def dtype(self): - return self.value.dtype - - -@public -class InSubquery(Subquery): - """Check if a value is in the result of a subquery.""" - - needle: Value - - dtype = dt.boolean - shape = rlz.shape_like("needle") - - def __init__(self, rel, needle): - if len(rel.schema) != 1: - raise IntegrityError( - "Relation passed to InSubquery() must have exactly one " - f"column, got {len(rel.schema)}" - ) - (value,) = rel.values.values() - if not rlz.comparable(value, needle): - raise IntegrityError(f"{needle!r} is not comparable to {value!r}") - super().__init__(rel=rel, needle=needle) - - @attribute - def value(self): - (value,) = self.rel.values.values() - return value - - @attribute - def relations(self): - return self.needle.relations diff --git a/third_party/bigframes_vendored/ibis/expr/operations/temporal.py b/third_party/bigframes_vendored/ibis/expr/operations/temporal.py deleted file mode 100644 index 729b7f14b91..00000000000 --- a/third_party/bigframes_vendored/ibis/expr/operations/temporal.py +++ /dev/null @@ -1,475 +0,0 @@ -# Contains code from https://github.com/ibis-project/ibis/blob/9.2.0/ibis/expr/operations/temporal.py - -"""Temporal operations.""" - -from __future__ import annotations - -import operator -from typing import Annotated, Optional - -import bigframes_vendored.ibis.expr.datatypes as dt -import bigframes_vendored.ibis.expr.rules as rlz -from bigframes_vendored.ibis.common.annotations import attribute -from bigframes_vendored.ibis.common.patterns import As, Attrs -from bigframes_vendored.ibis.common.temporal import ( - DateUnit, - IntervalUnit, - TimestampUnit, - TimeUnit, -) -from bigframes_vendored.ibis.expr.operations.core import Binary, Scalar, Unary, Value -from bigframes_vendored.ibis.expr.operations.logical import Between -from public import public - - -@public -class TimestampTruncate(Value): - """Truncate a timestamp to a specified unit.""" - - arg: Value[dt.Timestamp] - unit: IntervalUnit - - shape = rlz.shape_like("arg") - dtype = dt.timestamp - - -@public -class DateTruncate(Value): - """Truncate a date to a specified unit.""" - - arg: Value[dt.Date] - unit: DateUnit - - shape = rlz.shape_like("arg") - dtype = dt.date - - -@public -class TimeTruncate(Value): - """Truncate a time to a specified unit.""" - - arg: Value[dt.Time] - unit: TimeUnit - - shape = rlz.shape_like("arg") - dtype = dt.time - - -@public -class TimestampBucket(Value): - """Bucketize a timestamp to a specified interval.""" - - arg: Value[dt.Timestamp] - interval: Scalar[dt.Interval] - offset: Optional[Scalar[dt.Interval]] = None - - shape = rlz.shape_like("arg") - dtype = dt.timestamp - - -@public -class Strftime(Value): - """Format a temporal value as a string.""" - - arg: Value[dt.Temporal] - format_str: Value[dt.String] - - shape = rlz.shape_like("args") - dtype = dt.string - - -@public -class StringToTimestamp(Value): - """Convert a string to a timestamp.""" - - arg: Value[dt.String] - format_str: Value[dt.String] - - shape = rlz.shape_like("args") - dtype = dt.Timestamp(timezone="UTC") - - -@public -class StringToDate(Value): - """Convert a string to a date.""" - - arg: Value[dt.String] - format_str: Value[dt.String] - - shape = rlz.shape_like("args") - dtype = dt.date - - -@public -class ExtractTemporalField(Unary): - """Extract a field from a temporal value.""" - - arg: Value[dt.Temporal] - dtype = dt.int64 - - -@public -class ExtractDateField(ExtractTemporalField): - """Extract a field from a date.""" - - arg: Value[dt.Date | dt.Timestamp] - - -@public -class ExtractTimeField(ExtractTemporalField): - """Extract a field from a time.""" - - arg: Value[dt.Time | dt.Timestamp] - - -@public -class ExtractYear(ExtractDateField): - """Extract the year from a date or timestamp.""" - - -@public -class ExtractIsoYear(ExtractDateField): - """Extract the ISO year from a date or timestamp.""" - - -@public -class ExtractMonth(ExtractDateField): - """Extract the month from a date or timestamp.""" - - -@public -class ExtractDay(ExtractDateField): - """Extract the day from a date or timestamp.""" - - -@public -class ExtractDayOfYear(ExtractDateField): - """Extract the day of the year from a date or timestamp.""" - - -@public -class ExtractQuarter(ExtractDateField): - """Extract the quarter from a date or timestamp.""" - - -@public -class ExtractEpochSeconds(ExtractDateField): - """Extract seconds since the UNIX epoch from a date or timestamp.""" - - -@public -class ExtractWeekOfYear(ExtractDateField): - """Extract the week of the year from a date or timestamp.""" - - -@public -class ExtractHour(ExtractTimeField): - """Extract the hour from a time or timestamp.""" - - -@public -class ExtractMinute(ExtractTimeField): - """Extract the minute from a time or timestamp.""" - - -@public -class ExtractSecond(ExtractTimeField): - """Extract the second from a time or timestamp.""" - - -@public -class ExtractMillisecond(ExtractTimeField): - """Extract milliseconds from a time or timestamp.""" - - -@public -class ExtractMicrosecond(ExtractTimeField): - """Extract microseconds from a time or timestamp.""" - - -@public -class DayOfWeekIndex(Unary): - """Extract the index of the day of the week from a date or timestamp.""" - - arg: Value[dt.Date | dt.Timestamp] - - dtype = dt.int16 - - -@public -class DayOfWeekName(Unary): - """Extract the name of the day of the week from a date or timestamp.""" - - arg: Value[dt.Date | dt.Timestamp] - - dtype = dt.string - - -@public -class Time(Unary): - """Extract the time from a timestamp.""" - - dtype = dt.time - - -@public -class Date(Unary): - """Extract the date from a timestamp.""" - - dtype = dt.date - - -@public -class DateFromYMD(Value): - """Construct a date from year, month, and day.""" - - year: Value[dt.Integer] - month: Value[dt.Integer] - day: Value[dt.Integer] - - dtype = dt.date - shape = rlz.shape_like("args") - - -@public -class TimeFromHMS(Value): - """Construct a time from hours, minutes, and seconds.""" - - hours: Value[dt.Integer] - minutes: Value[dt.Integer] - seconds: Value[dt.Integer] - - dtype = dt.time - shape = rlz.shape_like("args") - - -@public -class TimestampFromYMDHMS(Value): - """Construct a timestamp from components.""" - - year: Value[dt.Integer] - month: Value[dt.Integer] - day: Value[dt.Integer] - hours: Value[dt.Integer] - minutes: Value[dt.Integer] - seconds: Value[dt.Integer] - - dtype = dt.timestamp - shape = rlz.shape_like("args") - - -@public -class TimestampFromUNIX(Value): - """Construct a timestamp from a UNIX timestamp.""" - - arg: Value - unit: TimestampUnit - - dtype = dt.timestamp - shape = rlz.shape_like("arg") - - -TimeInterval = Annotated[dt.Interval, Attrs(unit=As(TimeUnit))] -DateInterval = Annotated[dt.Interval, Attrs(unit=As(DateUnit))] - - -@public -class DateAdd(Binary): - """Add an interval to a date.""" - - left: Value[dt.Date] - right: Value[DateInterval] - - dtype = rlz.dtype_like("left") - - -@public -class DateSub(Binary): - """Subtract an interval from a date.""" - - left: Value[dt.Date] - right: Value[DateInterval] - - dtype = rlz.dtype_like("left") - - -@public -class DateDiff(Binary): - """Compute the difference between two dates.""" - - left: Value[dt.Date] - right: Value[dt.Date] - - dtype = dt.Interval("D") - - -@public -class TimeAdd(Binary): - """Add an interval to a time.""" - - left: Value[dt.Time] - right: Value[TimeInterval] - - dtype = rlz.dtype_like("left") - - -@public -class TimeSub(Binary): - """Subtract an interval from a time.""" - - left: Value[dt.Time] - right: Value[TimeInterval] - - dtype = rlz.dtype_like("left") - - -@public -class TimeDiff(Binary): - """Compute the difference between two times.""" - - left: Value[dt.Time] - right: Value[dt.Time] - - dtype = dt.Interval("s") - - -@public -class TimestampAdd(Binary): - """Add an interval to a timestamp.""" - - left: Value[dt.Timestamp] - right: Value[dt.Interval] - - dtype = rlz.dtype_like("left") - - -@public -class TimestampSub(Binary): - """Subtract an interval from a timestamp.""" - - left: Value[dt.Timestamp] - right: Value[dt.Interval] - - dtype = rlz.dtype_like("left") - - -@public -class TimestampDiff(Binary): - """Compute the difference between two timestamps.""" - - left: Value[dt.Timestamp] - right: Value[dt.Timestamp] - - dtype = dt.Interval("s") - - -@public -class IntervalBinary(Binary): - """Base class for interval binary operations.""" - - @attribute - def dtype(self): - interval_unit_args = [ - arg.dtype.unit for arg in (self.left, self.right) if arg.dtype.is_interval() - ] - unit = rlz._promote_interval_resolution(interval_unit_args) - - return self.left.dtype.copy(unit=unit) - - -@public -class IntervalAdd(IntervalBinary): - """Add two intervals.""" - - left: Value[dt.Interval] - right: Value[dt.Interval] - op = operator.add - - -@public -class IntervalSubtract(IntervalBinary): - """Subtract one interval from another.""" - - left: Value[dt.Interval] - right: Value[dt.Interval] - op = operator.sub - - -@public -class IntervalMultiply(IntervalBinary): - """Multiply an interval by a scalar.""" - - left: Value[dt.Interval] - right: Value[dt.Numeric | dt.Boolean] - op = operator.mul - - -@public -class IntervalFloorDivide(IntervalBinary): - """Divide an interval by a scalar, rounding down.""" - - left: Value[dt.Interval] - right: Value[dt.Numeric | dt.Boolean] - op = operator.floordiv - - -@public -class IntervalFromInteger(Value): - """Construct an interval from an integer.""" - - arg: Value[dt.Integer] - unit: IntervalUnit - - shape = rlz.shape_like("arg") - - @attribute - def dtype(self): - return dt.Interval(self.unit) - - @property - def resolution(self): - return self.dtype.resolution - - -@public -class BetweenTime(Between): - """Check if a time is between two bounds.""" - - arg: Value[dt.Time | dt.Timestamp] - lower_bound: Value[dt.Time | dt.String] - upper_bound: Value[dt.Time | dt.String] - - -class TemporalDelta(Value): - """Base class for temporal delta operations.""" - - part: Value[dt.String] - shape = rlz.shape_like("args") - dtype = dt.int64 - - -@public -class TimeDelta(TemporalDelta): - """Compute the difference between two times as integer number of requested units.""" - - left: Value[dt.Time] - right: Value[dt.Time] - - -@public -class DateDelta(TemporalDelta): - """Compute the difference between two dates as integer number of requested units.""" - - left: Value[dt.Date] - right: Value[dt.Date] - - -@public -class TimestampDelta(TemporalDelta): - """Compute the difference between two timestamps as integer number of requested units.""" - - left: Value[dt.Timestamp] - right: Value[dt.Timestamp] - - -public(ExtractTimestampField=ExtractTemporalField) diff --git a/third_party/bigframes_vendored/ibis/expr/operations/udf.py b/third_party/bigframes_vendored/ibis/expr/operations/udf.py deleted file mode 100644 index e3e528ee90d..00000000000 --- a/third_party/bigframes_vendored/ibis/expr/operations/udf.py +++ /dev/null @@ -1,651 +0,0 @@ -# Contains code from https://github.com/ibis-project/ibis/blob/9.2.0/ibis/expr/operations/udf.py - -"""User-defined functions (UDFs) implementation.""" - -from __future__ import annotations - -import abc -import collections -import enum -import functools -import inspect -import itertools -import typing -from typing import TYPE_CHECKING, Any, Optional, TypeVar, overload - -import bigframes_vendored.ibis.common.exceptions as exc -import bigframes_vendored.ibis.expr.datashape as ds -import bigframes_vendored.ibis.expr.datatypes as dt -import bigframes_vendored.ibis.expr.operations.core as core -import bigframes_vendored.ibis.expr.operations.reductions as reductions -import bigframes_vendored.ibis.expr.operations.relations as relations -import bigframes_vendored.ibis.expr.rules as rlz -from bigframes_vendored.ibis import util -from bigframes_vendored.ibis.common.annotations import Argument, attribute -from bigframes_vendored.ibis.common.collections import FrozenDict -from bigframes_vendored.ibis.common.deferred import deferrable -from public import public - -if TYPE_CHECKING: - from collections.abc import Callable, Iterable, MutableMapping - - import bigframes_vendored.ibis.expr.types as ir - - -EMPTY = inspect.Parameter.empty - - -_udf_name_cache: MutableMapping[type[core.Node], Iterable[int]] = ( - collections.defaultdict(itertools.count) -) - - -def _make_udf_name(name: str) -> str: - definition = next(_udf_name_cache[name]) - return f"{name}_{definition:d}" - - -@enum.unique -class InputType(enum.Enum): - BUILTIN = enum.auto() - PANDAS = enum.auto() - PYARROW = enum.auto() - PYTHON = enum.auto() - - -@public -class ScalarUDF(core.Value): - @attribute - def shape(self): - if not (args := getattr(self, "args")): # noqa: B009 - # if a udf builtin takes no args then the shape check will fail - # because there are no arguments to grab the shape of. In that case - # default to a scalar shape - return ds.scalar - else: - args = args if util.is_iterable(args) else [args] - return rlz.highest_precedence_shape(args) - - -@public -class AggUDF(reductions.Reduction): - where: Optional[core.Value[dt.Boolean]] = None - - -def _wrap( - wrapper, - input_type: InputType, - fn: Callable | None = None, - **kwargs: Any, -) -> Callable: - """Wrap a function `fn` with `wrapper`, allowing zero arguments when used as part of a decorator.""" - - def wrap(fn): - return functools.update_wrapper( - deferrable(wrapper(input_type, fn, **kwargs)), fn - ) - - return wrap(fn) if fn is not None else wrap - - -S = TypeVar("S", bound=core.Value) -B = TypeVar("B", bound=core.Value) - - -class _UDF(abc.ABC): - __slots__ = () - - @property - @abc.abstractmethod - def _base(self) -> type[B]: - """Base class of the UDF.""" - - @classmethod - def _make_node( - cls, - fn: Callable, - input_type: InputType, - name: str | None = None, - database: str | None = None, - catalog: str | None = None, - signature: tuple[tuple, Any] | None = None, - param_name_overrides: tuple[str, ...] | None = None, - **kwargs, - ) -> type[S]: - """Construct a scalar user-defined function that is built-in to the backend.""" - if "schema" in kwargs: - raise exc.UnsupportedArgumentError( - """schema` is not a valid argument. - You can use the `catalog` and `database` keywords to specify a UDF location.""" - ) - - if signature is None: - annotations = typing.get_type_hints(fn) - if (return_annotation := annotations.pop("return", None)) is None: - raise exc.MissingReturnAnnotationError(fn) - fields = { - arg_name: Argument( - pattern=rlz.ValueOf(annotations.get(arg_name)), - default=param.default, - typehint=annotations.get(arg_name, Any), - ) - for arg_name, param in inspect.signature(fn).parameters.items() - } - - else: - arg_types, return_annotation = signature - arg_names = param_name_overrides or list(inspect.signature(fn).parameters) - fields = { - arg_name: Argument(pattern=rlz.ValueOf(typ), typehint=typ) - for arg_name, typ in zip(arg_names, arg_types) - } - - func_name = name if name is not None else fn.__name__ - - fields.update( - { - "dtype": dt.dtype(return_annotation), - "__input_type__": input_type, - # must wrap `fn` in a `property` otherwise `fn` is assumed to be a - # method - "__func__": property(fget=lambda _, fn=fn: fn), - "__config__": FrozenDict(kwargs), - "__udf_namespace__": relations.Namespace( - database=database, catalog=catalog - ), - "__module__": fn.__module__, - "__func_name__": func_name, - } - ) - - return type(_make_udf_name(fn.__name__), (cls._base,), fields) - - @classmethod - def _make_wrapper( - cls, input_type: InputType, fn: Callable, **kwargs: Any - ) -> Callable: - node = cls._make_node(fn, input_type, **kwargs) - - @functools.wraps(fn) - def construct(*args: Any, **kwargs: Any) -> ir.Value: - return node(*args, **kwargs).to_expr() - - return construct - - -@public -class scalar(_UDF): - """Scalar user-defined functions. - - ::: {.callout-note} - ## The `scalar` class itself is **not** a public API, its methods are. - ::: - """ - - _base = ScalarUDF - - @overload - @classmethod - def builtin(cls, fn: Callable) -> Callable[..., ir.Value]: ... - - @overload - @classmethod - def builtin( - cls, - *, - name: str | None = None, - database: str | None = None, - catalog: str | None = None, - signature: tuple[tuple[Any, ...], Any] | None = None, - **kwargs: Any, - ) -> Callable[[Callable], Callable[..., ir.Value]]: ... - - @util.experimental - @classmethod - def builtin( - cls, - fn=None, - *, - name=None, - database=None, - catalog=None, - signature=None, - **kwargs, - ): - """Construct a scalar user-defined function that is built-in to the backend. - - Parameters - ---------- - fn - The function to wrap. - name - The name of the UDF in the backend if different from the function name. - database - The database in which the builtin function resides. - catalog - The catalog in which the builtin function resides. - signature - If present, a tuple of the form `((arg0type, arg1type, ...), returntype)`. - For example, a function taking an int and a float and returning a - string would be `((int, float), str)`. If not present, the signature - will be derived from the type annotations of the wrapped function. - - For **builtin** UDFs, only the **return type** annotation is required. - See [the user guide](/how-to/extending/builtin.qmd#input-types) for - more information. - kwargs - Additional backend-specific configuration arguments for the UDF. - - Examples - -------- - >>> import ibis - >>> @ibis.udf.scalar.builtin - ... def hamming(a: str, b: str) -> int: - ... '''Compute the Hamming distance between two strings.''' - >>> expr = hamming("duck", "luck") - >>> con = ibis.connect("duckdb://") - >>> con.execute(expr) - np.int64(1) - - """ - return _wrap( - cls._make_wrapper, - InputType.BUILTIN, - fn, - name=name, - database=database, - catalog=catalog, - signature=signature, - **kwargs, - ) - - @overload - @classmethod - def python(cls, fn: Callable) -> Callable[..., ir.Value]: ... - - @overload - @classmethod - def python( - cls, - *, - name: str | None = None, - database: str | None = None, - catalog: str | None = None, - signature: tuple[tuple[Any, ...], Any] | None = None, - **kwargs: Any, - ) -> Callable[[Callable], Callable[..., ir.Value]]: ... - - @util.experimental - @classmethod - def python( - cls, - fn=None, - *, - name=None, - database=None, - catalog=None, - signature=None, - **kwargs, - ): - """Construct a **non-vectorized** scalar user-defined function that accepts Python scalar values as inputs. - - ::: {.callout-warning collapse="true"} - ## `python` UDFs are likely to be slow - - `python` UDFs are not vectorized: they are executed row by row with one - Python function call per row - - This calling pattern tends to be **much** slower than - [`pandas`](/reference/scalar-udfs.qmd#ibis.expr.operations.udf.scalar.pandas) - or - [`pyarrow`](/reference/scalar-udfs.qmd#ibis.expr.operations.udf.scalar.pyarrow)-based - vectorized UDFs. - ::: - - Parameters - ---------- - fn - The function to wrap. - name - The name of the UDF in the backend if different from the function name. - database - The database in which to create the UDF. - catalog - The catalog in which to create the UDF. - signature - If present, a tuple of the form `((arg0type, arg1type, ...), returntype)`. - For example, a function taking an int and a float and returning a - string would be `((int, float), str)`. If not present, the signature - will be derived from the type annotations of the wrapped function. - kwargs - Additional backend-specific configuration arguments for the UDF. - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.memtable(dict(int_col=[1, 2, 3], str_col=["a", "b", "c"])) - >>> t - ┏━━━━━━━━━┳━━━━━━━━━┓ - ┃ int_col ┃ str_col ┃ - ┡━━━━━━━━━╇━━━━━━━━━┩ - │ int64 │ string │ - ├─────────┼─────────┤ - │ 1 │ a │ - │ 2 │ b │ - │ 3 │ c │ - └─────────┴─────────┘ - >>> @ibis.udf.scalar.python - ... def str_magic(x: str) -> str: - ... return f"{x}_magic" - >>> @ibis.udf.scalar.python - ... def add_one_py(x: int) -> int: - ... return x + 1 - >>> str_magic(t.str_col) - ┏━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ str_magic_0(str_col) ┃ - ┡━━━━━━━━━━━━━━━━━━━━━━┩ - │ string │ - ├──────────────────────┤ - │ a_magic │ - │ b_magic │ - │ c_magic │ - └──────────────────────┘ - >>> add_one_py(t.int_col) - ┏━━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ add_one_py_0(int_col) ┃ - ┡━━━━━━━━━━━━━━━━━━━━━━━┩ - │ int64 │ - ├───────────────────────┤ - │ 2 │ - │ 3 │ - │ 4 │ - └───────────────────────┘ - - See Also - -------- - - [`pandas`](/reference/scalar-udfs.qmd#ibis.expr.operations.udf.scalar.pandas) - - [`pyarrow`](/reference/scalar-udfs.qmd#ibis.expr.operations.udf.scalar.pyarrow) - - """ - return _wrap( - cls._make_wrapper, - InputType.PYTHON, - fn, - name=name, - database=database, - catalog=catalog, - signature=signature, - **kwargs, - ) - - @overload - @classmethod - def pandas(cls, fn: Callable) -> Callable[..., ir.Value]: ... - - @overload - @classmethod - def pandas( - cls, - *, - name: str | None = None, - database: str | None = None, - catalog: str | None = None, - signature: tuple[tuple[Any, ...], Any] | None = None, - **kwargs: Any, - ) -> Callable[[Callable], Callable[..., ir.Value]]: ... - - @util.experimental - @classmethod - def pandas( - cls, - fn=None, - *, - name=None, - database=None, - catalog=None, - signature=None, - **kwargs, - ): - """Construct a **vectorized** scalar user-defined function that accepts pandas Series' as inputs. - - Parameters - ---------- - fn - The function to wrap. - name - The name of the UDF in the backend if different from the function name. - database - The database in which to create the UDF. - catalog - The catalog in which to create the UDF. - signature - If present, a tuple of the form `((arg0type, arg1type, ...), returntype)`. - For example, a function taking an int and a float and returning a - string would be `((int, float), str)`. If not present, the signature - will be derived from the type annotations of the wrapped function. - kwargs - Additional backend-specific configuration arguments for the UDF. - - Examples - -------- - ```python - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.memtable(dict(int_col=[1, 2, 3], str_col=["a", "b", "c"])) - >>> t - ┏━━━━━━━━━┳━━━━━━━━━┓ - ┃ int_col ┃ str_col ┃ - ┡━━━━━━━━━╇━━━━━━━━━┩ - │ int64 │ string │ - ├─────────┼─────────┤ - │ 1 │ a │ - │ 2 │ b │ - │ 3 │ c │ - └─────────┴─────────┘ - >>> @ibis.udf.scalar.pandas - ... def str_cap(x: str) -> str: - ... # note usage of pandas `str` method - ... return x.str.capitalize() - >>> str_cap(t.str_col) # doctest: +SKIP - ┏━━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ string_cap_0(str_col) ┃ - ┡━━━━━━━━━━━━━━━━━━━━━━━┩ - │ string │ - ├───────────────────────┤ - │ A │ - │ B │ - │ C │ - └───────────────────────┘ - ``` - - See Also - -------- - - [`python`](/reference/scalar-udfs.qmd#ibis.expr.operations.udf.scalar.python) - - [`pyarrow`](/reference/scalar-udfs.qmd#ibis.expr.operations.udf.scalar.pyarrow) - - """ - return _wrap( - cls._make_wrapper, - InputType.PANDAS, - fn, - name=name, - database=database, - catalog=catalog, - signature=signature, - **kwargs, - ) - - @overload - @classmethod - def pyarrow(cls, fn: Callable) -> Callable[..., ir.Value]: ... - - @overload - @classmethod - def pyarrow( - cls, - *, - name: str | None = None, - database: str | None = None, - catalog: str | None = None, - signature: tuple[tuple[Any, ...], Any] | None = None, - **kwargs: Any, - ) -> Callable[[Callable], Callable[..., ir.Value]]: ... - - @util.experimental - @classmethod - def pyarrow( - cls, - fn=None, - *, - name=None, - database=None, - catalog=None, - signature=None, - **kwargs, - ): - """Construct a **vectorized** scalar user-defined function that accepts PyArrow Arrays as input. - - Parameters - ---------- - fn - The function to wrap. - name - The name of the UDF in the backend if different from the function name. - database - The database in which to create the UDF. - catalog - The catalog in which to create the UDF. - signature - If present, a tuple of the form `((arg0type, arg1type, ...), returntype)`. - For example, a function taking an int and a float and returning a - string would be `((int, float), str)`. If not present, the signature - will be derived from the type annotations of the wrapped function. - kwargs - Additional backend-specific configuration arguments for the UDF. - - Examples - -------- - >>> import ibis - >>> import pyarrow.compute as pc - >>> from datetime import date - >>> ibis.options.interactive = True - >>> t = ibis.memtable( - ... dict(start_col=[date(2024, 4, 29)], end_col=[date(2025, 4, 29)]), - ... ) - >>> @ibis.udf.scalar.pyarrow - ... def weeks_between(start: date, end: date) -> int: - ... return pc.weeks_between(start, end) - >>> weeks_between(t.start_col, t.end_col) - ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ weeks_between_0(start_col, end_col) ┃ - ┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩ - │ int64 │ - ├─────────────────────────────────────┤ - │ 52 │ - └─────────────────────────────────────┘ - - See Also - -------- - - [`python`](/reference/scalar-udfs.qmd#ibis.expr.operations.udf.scalar.python) - - [`pandas`](/reference/scalar-udfs.qmd#ibis.expr.operations.udf.scalar.pandas) - - """ - return _wrap( - cls._make_wrapper, - InputType.PYARROW, - fn, - name=name, - database=database, - catalog=catalog, - signature=signature, - **kwargs, - ) - - -@public -class agg(_UDF): - """Aggregate user-defined functions. - - ::: {.callout-note} - ## The `agg` class itself is **not** a public API, its methods are. - ::: - """ - - __slots__ = () - - _base = AggUDF - - @overload - @classmethod - def builtin(cls, fn: Callable) -> Callable[..., ir.Value]: ... - - @overload - @classmethod - def builtin( - cls, - *, - name: str | None = None, - database: str | None = None, - catalog: str | None = None, - signature: tuple[tuple[Any, ...], Any] | None = None, - **kwargs: Any, - ) -> Callable[[Callable], Callable[..., ir.Value]]: ... - - @util.experimental - @classmethod - def builtin( - cls, - fn=None, - *, - name=None, - database=None, - catalog=None, - signature=None, - **kwargs, - ): - """Construct an aggregate user-defined function that is built-in to the backend. - - Parameters - ---------- - fn - The function to wrap. - name - The name of the UDF in the backend if different from the function name. - database - The database in which the builtin function resides. - catalog - The catalog in which the builtin function resides. - signature - If present, a tuple of the form `((arg0type, arg1type, ...), returntype)`. - For example, a function taking an int and a float and returning a - string would be `((int, float), str)`. If not present, the signature - will be derived from the type annotations of the wrapped function. - kwargs - Additional backend-specific configuration arguments for the UDF. - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> @ibis.udf.agg.builtin - ... def favg(a: float) -> float: - ... '''Compute the average of a column using Kahan summation.''' - >>> t = ibis.examples.penguins.fetch() - >>> expr = favg(t.bill_length_mm) - >>> expr - ┌──────────────────────────────┐ - │ np.float64(43.9219298245614) │ - └──────────────────────────────┘ - - """ - return _wrap( - cls._make_wrapper, - InputType.BUILTIN, - fn, - name=name, - database=database, - catalog=catalog, - signature=signature, - **kwargs, - ) diff --git a/third_party/bigframes_vendored/ibis/expr/operations/window.py b/third_party/bigframes_vendored/ibis/expr/operations/window.py deleted file mode 100644 index c40c9db2f0b..00000000000 --- a/third_party/bigframes_vendored/ibis/expr/operations/window.py +++ /dev/null @@ -1,115 +0,0 @@ -# Contains code from https://github.com/ibis-project/ibis/blob/9.2.0/ibis/expr/operations/window.py - -"""Window operations.""" - -from __future__ import annotations - -from typing import Literal as LiteralType -from typing import Optional - -import bigframes_vendored.ibis.common.exceptions as com -import bigframes_vendored.ibis.expr.datashape as ds -import bigframes_vendored.ibis.expr.datatypes as dt -import bigframes_vendored.ibis.expr.rules as rlz -from bigframes_vendored.ibis.common.patterns import CoercionError -from bigframes_vendored.ibis.common.typing import VarTuple # noqa: TCH001 -from bigframes_vendored.ibis.expr.operations.analytic import Analytic # noqa: TCH001 -from bigframes_vendored.ibis.expr.operations.core import Column, Value -from bigframes_vendored.ibis.expr.operations.generic import Literal -from bigframes_vendored.ibis.expr.operations.numeric import Negate -from bigframes_vendored.ibis.expr.operations.reductions import Reduction # noqa: TCH001 -from bigframes_vendored.ibis.expr.operations.sortkeys import SortKey # noqa: TCH001 -from public import public -from typing_extensions import TypeVar - -T = TypeVar("T", bound=dt.Numeric | dt.Interval, covariant=True) -S = TypeVar("S", bound=ds.DataShape, default=ds.Any, covariant=True) - - -@public -class WindowBoundary(Value[T, S]): - """Window boundary object.""" - - # TODO(kszucs): consider to prefer Concrete base class here - # pretty similar to SortKey and Alias operations which wrap a single value - value: Value[T, S] - preceding: bool - - @property - def following(self) -> bool: - return not self.preceding - - @property - def shape(self) -> S: - return self.value.shape - - @property - def dtype(self) -> T: - return self.value.dtype - - @classmethod - def __coerce__(cls, value, **kwargs): - arg = super().__coerce__(value, **kwargs) - - if isinstance(arg, cls): - return arg - elif isinstance(arg, Negate): - return cls(arg.arg, preceding=True) - elif isinstance(arg, Literal): - new = arg.copy(value=abs(arg.value)) - return cls(new, preceding=arg.value < 0) - elif isinstance(arg, Value): - return cls(arg, preceding=False) - else: - raise CoercionError(f"Invalid window boundary type: {type(arg)}") - - -@public -class WindowFunction(Value): - """Window function operation.""" - - func: Analytic | Reduction - # none is a hacky way to express that window bounds are not supported (eg row_number()) - how: LiteralType["rows", "range", "none"] = "rows" # noqa: F821 - start: Optional[WindowBoundary[dt.Numeric | dt.Interval]] = None - end: Optional[WindowBoundary[dt.Numeric | dt.Interval]] = None - group_by: VarTuple[Column] = () - order_by: VarTuple[SortKey] = () - - dtype = rlz.dtype_like("func") - shape = ds.columnar - - def __init__(self, how, start, end, **kwargs): - if how == "rows": - if start and not start.dtype.is_integer(): - raise com.IbisTypeError( - "Row-based window frame start boundary must be an integer" - ) - if end and not end.dtype.is_integer(): - raise com.IbisTypeError( - "Row-based window frame end boundary must be an integer" - ) - elif how == "range": - if ( - start - and end - and not ( - (start.dtype.is_interval() and end.dtype.is_interval()) - or (start.dtype.is_numeric() and end.dtype.is_numeric()) - ) - ): - raise com.IbisTypeError( - "Window frame start and end boundaries must have the same datatype" - ) - elif how != "none": - raise com.IbisTypeError( - f"Window frame type must be either 'rows' or 'range', got {how}" - ) - super().__init__(how=how, start=start, end=end, **kwargs) - - @property - def name(self): - return self.func.name - - -public(WindowOp=WindowFunction, Window=WindowFunction) diff --git a/third_party/bigframes_vendored/ibis/expr/rewrites.py b/third_party/bigframes_vendored/ibis/expr/rewrites.py deleted file mode 100644 index 3ec5ea12714..00000000000 --- a/third_party/bigframes_vendored/ibis/expr/rewrites.py +++ /dev/null @@ -1,385 +0,0 @@ -# Contains code from https://github.com/ibis-project/ibis/blob/9.2.0/ibis/expr/rewrites.py - -"""Some common rewrite functions to be shared between backends.""" - -from __future__ import annotations - -from collections import defaultdict - -import bigframes_vendored.ibis.expr.operations as ops -import toolz -from bigframes_vendored.ibis.common.collections import FrozenDict # noqa: TCH001 -from bigframes_vendored.ibis.common.deferred import Item, _, deferred, var -from bigframes_vendored.ibis.common.exceptions import ExpressionError, IbisInputError -from bigframes_vendored.ibis.common.graph import Node as Traversable -from bigframes_vendored.ibis.common.graph import traverse -from bigframes_vendored.ibis.common.grounds import Concrete -from bigframes_vendored.ibis.common.patterns import Check, pattern, replace -from bigframes_vendored.ibis.common.typing import VarTuple # noqa: TCH001 -from bigframes_vendored.ibis.util import Namespace, promote_list - -p = Namespace(pattern, module=ops) -d = Namespace(deferred, module=ops) - - -x = var("x") -y = var("y") -name = var("name") - - -class DerefMap(Concrete, Traversable): - """Trace and replace fields from earlier relations in the hierarchy. - - In order to provide a nice user experience, we need to allow expressions - from earlier relations in the hierarchy. Consider the following example: - - t = ibis.table([('a', 'int64'), ('b', 'string')], name='t') - t1 = t.select([t.a, t.b]) - t2 = t1.filter(t.a > 0) # note that not t1.a is referenced here - t3 = t2.select(t.a) # note that not t2.a is referenced here - - However the relational operations in the IR are strictly enforcing that - the expressions are referencing the immediate parent only. So we need to - track fields upwards the hierarchy to replace `t.a` with `t1.a` and `t2.a` - in the example above. This is called dereferencing. - - Whether we can treat or not a field of a relation semantically equivalent - with a field of an earlier relation in the hierarchy depends on the - `.values` mapping of the relation. Leaf relations, like `t` in the example - above, have an empty `.values` mapping, so we cannot dereference fields - from them. On the other hand a projection, like `t1` in the example above, - has a `.values` mapping like `{'a': t.a, 'b': t.b}`, so we can deduce that - `t1.a` is semantically equivalent with `t.a` and so on. - """ - - """The relations we want the values to point to.""" - rels: VarTuple[ops.Relation] - - """Substitution mapping from values of earlier relations to the fields of `rels`.""" - subs: FrozenDict[ops.Value, ops.Field] - - """Ambiguous field references.""" - ambigs: FrozenDict[ops.Value, VarTuple[ops.Value]] - - @classmethod - def from_targets(cls, rels, extra=None): - """Create a dereference map from a list of target relations. - - Usually a single relation is passed except for joins where multiple - relations are involved. - - Parameters - ---------- - rels : list of ops.Relation - The target relations to dereference to. - extra : dict, optional - Extra substitutions to be added to the dereference map. - - Returns - ------- - DerefMap - """ - rels = promote_list(rels) - mapping = defaultdict(dict) - for rel in rels: - for field in rel.fields.values(): - for value, distance in cls.backtrack(field): - mapping[value][field] = distance - - subs, ambigs = {}, {} - for from_, to in mapping.items(): - mindist = min(to.values()) - minkeys = [k for k, v in to.items() if v == mindist] - # if all the closest fields are from the same relation, then we - # can safely substitute them and we pick the first one arbitrarily - if all(minkeys[0].relations == k.relations for k in minkeys): - subs[from_] = minkeys[0] - else: - ambigs[from_] = minkeys - - if extra is not None: - subs.update(extra) - - return cls(rels, subs, ambigs) - - @classmethod - def backtrack(cls, value): - """Backtrack the field in the relation hierarchy. - - The field is traced back until no modification is made, so only follow - ops.Field nodes not arbitrary values. - - Parameters - ---------- - value : ops.Value - The value to backtrack. - - Yields - ------ - tuple[ops.Field, int] - The value node and the distance from the original value. - """ - distance = 0 - # track down the field in the hierarchy until no modification - # is made so only follow ops.Field nodes not arbitrary values; - while isinstance(value, ops.Field): - yield value, distance - value = value.rel.values.get(value.name) - distance += 1 - if ( - value is not None - and value.relations - and not value.find(ops.Impure, filter=ops.Value) - ): - yield value, distance - - def dereference(self, value): - """Dereference a value to the target relations. - - Also check for ambiguous field references. If a field reference is found - which is marked as ambiguous, then raise an error. - - Parameters - ---------- - value : ops.Value - The value to dereference. - - Returns - ------- - ops.Value - The dereferenced value. - """ - ambigs = value.find(lambda x: x in self.ambigs, filter=ops.Value) - if ambigs: - raise IbisInputError( - f"Ambiguous field reference {ambigs!r} in expression {value!r}" - ) - return value.replace(self.subs, filter=ops.Value) - - -def flatten_predicates(node): - """Yield the expressions corresponding to the `And` nodes of a predicate. - - Examples - -------- - >>> import ibis - >>> t = ibis.table([("a", "int64"), ("b", "string")], name="t") - >>> filt = (t.a == 1) & (t.b == "foo") - >>> predicates = flatten_predicates(filt.op()) - >>> len(predicates) - 2 - >>> predicates[0].to_expr().name("left") - r0 := UnboundTable: t - a int64 - b string - left: r0.a == 1 - >>> predicates[1].to_expr().name("right") - r0 := UnboundTable: t - a int64 - b string - right: r0.b == 'foo' - - """ - - def predicate(node): - if isinstance(node, ops.And): - # proceed and don't yield the node - return True, None - else: - # halt and yield the node - return False, node - - return list(traverse(predicate, node)) - - -@replace(p.Field(p.JoinChain)) -def peel_join_field(_): - return _.rel.values[_.name] - - -@replace(p.ScalarParameter) -def replace_parameter(_, params, **kwargs): - """Replace scalar parameters with their values.""" - return ops.Literal(value=params[_], dtype=_.dtype) - - -@replace(p.StringSlice) -def lower_stringslice(_, **kwargs): - """Rewrite StringSlice in terms of Substring.""" - if _.start is None: - real_start = 0 - else: - real_start = ops.IfElse( - ops.GreaterEqual(_.start, 0), - _.start, - ops.Greatest((0, ops.Add(ops.StringLength(_.arg), _.start))), - ) - - if _.end is None: - real_end = ops.StringLength(_.arg) - else: - real_end = ops.IfElse( - ops.GreaterEqual(_.end, 0), - _.end, - ops.Greatest((0, ops.Add(ops.StringLength(_.arg), _.end))), - ) - - length = ops.Greatest((0, ops.Subtract(real_end, real_start))) - return ops.Substring(_.arg, start=real_start, length=length) - - -@replace(p.Analytic) -def project_wrap_analytic(_, rel): - # Wrap analytic functions in a window function - return ops.WindowFunction(_) - - -@replace(p.Reduction) -def project_wrap_reduction(_, rel): - # Query all the tables that the reduction depends on - if _.relations == {rel}: - # The reduction is fully originating from the `rel`, so turn - # it into a window function of `rel` - return ops.WindowFunction(_) - else: - # 1. The reduction doesn't depend on any table, constructed from - # scalar values, so turn it into a scalar subquery. - # 2. The reduction is originating from `rel` and other tables, - # so this is a correlated scalar subquery. - # 3. The reduction is originating entirely from other tables, - # so this is an uncorrelated scalar subquery. - return ops.ScalarSubquery(_.to_expr().as_table()) - - -def rewrite_project_input(value, relation): - # we need to detect reductions which are either turned into window functions - # or scalar subqueries depending on whether they are originating from the - # relation - return value.replace( - project_wrap_analytic | project_wrap_reduction, - filter=p.Value & ~p.WindowFunction & ~p.ArrayReduce, - context={"rel": relation}, - ) - - -ReductionLike = p.Reduction | p.Field(p.Aggregate(groups={})) - - -@replace(ReductionLike) -def filter_wrap_reduction(_): - # Wrap reductions or fields referencing an aggregation without a group by - - # which are scalar fields - in a scalar subquery. In the latter case we - # use the reduction value from the aggregation. - if isinstance(_, ops.Field): - value = _.rel.values[_.name] - else: - value = _ - return ops.ScalarSubquery(value.to_expr().as_table()) - - -def rewrite_filter_input(value): - return value.replace(filter_wrap_reduction, filter=p.Value & ~p.WindowFunction) - - -@replace(p.Analytic | p.Reduction) -def window_wrap_reduction(_, window): - # Wrap analytic and reduction functions in a window function. Used in the - # value.over() API. - return ops.WindowFunction( - _, - how=window.how, - start=window.start, - end=window.end, - group_by=window.groupings, - order_by=window.orderings, - ) - - -@replace(p.WindowFunction) -def window_merge_frames(_, window): - # Merge window frames, used in the value.over() and groupby.select() APIs. - if _.how != window.how: - raise ExpressionError( - f"Unable to merge {_.how} window with {window.how} window" - ) - elif _.start and window.start and _.start != window.start: - raise ExpressionError( - "Unable to merge windows with conflicting `start` boundary" - ) - elif _.end and window.end and _.end != window.end: - raise ExpressionError("Unable to merge windows with conflicting `end` boundary") - - start = _.start or window.start - end = _.end or window.end - group_by = tuple(toolz.unique(_.group_by + window.groupings)) - - order_keys = {} - for sort_key in window.orderings + _.order_by: - order_keys[sort_key.expr] = sort_key.ascending, sort_key.nulls_first - - order_by = ( - ops.SortKey(expr, ascending=ascending, nulls_first=nulls_first) - for expr, (ascending, nulls_first) in order_keys.items() - ) - return _.copy(start=start, end=end, group_by=group_by, order_by=order_by) - - -def rewrite_window_input(value, window): - context = {"window": window} - # if self is a reduction or analytic function, wrap it in a window function - node = value.replace( - window_wrap_reduction, - filter=p.Value & ~p.WindowFunction, - context=context, - ) - # if self is already a window function, merge the existing window frame - # with the requested window frame - return node.replace(window_merge_frames, filter=p.Value, context=context) - - -# TODO(kszucs): schema comparison should be updated to not distinguish between -# different column order -@replace(p.Project(y @ p.Relation) & Check(_.schema == y.schema)) -def complete_reprojection(_, y): - # TODO(kszucs): this could be moved to the pattern itself but not sure how - # to express it, especially in a shorter way then the following check - for name in _.schema: - if _.values[name] != ops.Field(y, name): - return _ - return y - - -@replace(p.Project(y @ p.Project)) -def subsequent_projects(_, y): - rule = p.Field(y, name) >> Item(y.values, name) - values = {k: v.replace(rule, filter=ops.Value) for k, v in _.values.items()} - return ops.Project(y.parent, values) - - -@replace(p.Filter(y @ p.Filter)) -def subsequent_filters(_, y): - rule = p.Field(y, name) >> d.Field(y.parent, name) - preds = tuple(v.replace(rule, filter=ops.Value) for v in _.predicates) - return ops.Filter(y.parent, y.predicates + preds) - - -@replace(p.Filter(y @ p.Project)) -def reorder_filter_project(_, y): - rule = p.Field(y, name) >> Item(y.values, name) - preds = tuple(v.replace(rule, filter=ops.Value) for v in _.predicates) - - inner = ops.Filter(y.parent, preds) - rule = p.Field(y.parent, name) >> d.Field(inner, name) - projs = {k: v.replace(rule, filter=ops.Value) for k, v in y.values.items()} - - return ops.Project(inner, projs) - - -def simplify(node): - # TODO(kszucs): add a utility to the graph module to do rewrites in multiple - # passes after each other - node = node.replace(reorder_filter_project) - node = node.replace(reorder_filter_project) - node = node.replace(subsequent_projects | subsequent_filters) - node = node.replace(complete_reprojection) - return node diff --git a/third_party/bigframes_vendored/ibis/expr/rules.py b/third_party/bigframes_vendored/ibis/expr/rules.py deleted file mode 100644 index 95050a6a5bc..00000000000 --- a/third_party/bigframes_vendored/ibis/expr/rules.py +++ /dev/null @@ -1,167 +0,0 @@ -# Contains code from https://github.com/ibis-project/ibis/blob/9.2.0/ibis/expr/rules.py - -from __future__ import annotations - -from itertools import product, starmap -from typing import Optional - -import bigframes_vendored.ibis.expr.datatypes as dt -import bigframes_vendored.ibis.expr.operations as ops -from bigframes_vendored.ibis import util -from bigframes_vendored.ibis.common.annotations import attribute -from bigframes_vendored.ibis.common.grounds import Concrete -from bigframes_vendored.ibis.common.patterns import CoercionError, NoMatch, Pattern -from bigframes_vendored.ibis.common.temporal import IntervalUnit -from public import public - - -@public -def highest_precedence_shape(nodes): - return max(node.shape for node in nodes) - - -@public -def highest_precedence_dtype(nodes): - """Return the highest precedence type from the passed expressions. - - Also verifies that there are valid implicit casts between any of the types - and the selected highest precedence type. - This is a thin wrapper around datatypes highest precedence check. - - Parameters - ---------- - nodes : Iterable[ops.Value] - A sequence of Expressions - - Returns - ------- - dtype: DataType - The highest precedence datatype - - """ - return dt.highest_precedence(node.dtype for node in nodes) - - -@public -def castable(source, target): - """Return whether source ir type is implicitly castable to target. - - Based on the underlying datatypes and the value in case of Literals - """ - value = getattr(source, "value", None) - return source.dtype.castable(target.dtype, value=value) - - -@public -def comparable(left, right): - return castable(left, right) or castable(right, left) - - -# --------------------------------------------------------------------- -# Output type functions - - -@public -def dtype_like(name): - @attribute - def dtype(self): - args = getattr(self, name) - args = args if util.is_iterable(args) else [args] - return highest_precedence_dtype(args) - - return dtype - - -@public -def shape_like(name): - @attribute - def shape(self): - args = getattr(self, name) - args = args if util.is_iterable(args) else [args] - args = [a for a in args if a is not None] - return highest_precedence_shape(args) - - return shape - - -# TODO(kszucs): might just use bounds instead of actual literal values -# that could simplify interval binop output_type methods -# TODO(kszucs): pre-generate mapping? - - -def _promote_integral_binop(exprs, op): - bounds, dtypes = [], [] - for arg in exprs: - dtypes.append(arg.dtype) - if isinstance(arg, ops.Literal): - bounds.append([arg.value]) - else: - bounds.append(arg.dtype.bounds) - - all_unsigned = dtypes and util.all_of(dtypes, dt.UnsignedInteger) - # In some cases, the bounding type might be int8, even though neither - # of the types are that small. We want to ensure the containing type is - # _at least_ as large as the smallest type in the expression. - values = starmap(op, product(*bounds)) - dtypes += [dt.infer(v, prefer_unsigned=all_unsigned) for v in values] - - return dt.highest_precedence(dtypes) - - -@public -def numeric_like(name, op): - @attribute - def dtype(self): - args = getattr(self, name) - dtypes = [arg.dtype for arg in args] - if util.all_of(dtypes, dt.Integer): - result = _promote_integral_binop(args, op) - else: - result = highest_precedence_dtype(args) - - return result - - return dtype - - -def _promote_interval_resolution(units: list[IntervalUnit]) -> IntervalUnit: - # Find the smallest unit present in units - for unit in reversed(IntervalUnit): - if unit in units: - return unit - raise AssertionError("unreachable") - - -def _arg_type_error_format(op): - if isinstance(op, ops.Literal): - return f"Literal({op.value}):{op.dtype}" - else: - return f"{op.name}:{op.dtype}" - - -class ValueOf(Concrete, Pattern): - """Match a value of a specific type **instance**. - - This is different from the Value[T] annotations which construct - GenericCoercedTo(Value[T]) validators working with datatype types - rather than instances. - - Parameters - ---------- - dtype : DataType | None - The datatype the constructed Value instance should conform to. - - """ - - dtype: Optional[dt.DataType] = None - - def match(self, value, context): - try: - value = ops.Value.__coerce__(value, self.dtype) - except CoercionError: - return NoMatch - - if self.dtype and not value.dtype.castable(self.dtype): - return NoMatch - - return value diff --git a/third_party/bigframes_vendored/ibis/expr/schema.py b/third_party/bigframes_vendored/ibis/expr/schema.py deleted file mode 100644 index edc704c3664..00000000000 --- a/third_party/bigframes_vendored/ibis/expr/schema.py +++ /dev/null @@ -1,303 +0,0 @@ -# Contains code from https://github.com/ibis-project/ibis/blob/9.2.0/ibis/expr/schema.py - -from __future__ import annotations - -from collections.abc import Iterable, Iterator, Mapping -from typing import TYPE_CHECKING, Any, Union - -import bigframes_vendored.ibis.expr.datatypes as dt -from bigframes_vendored.ibis.common.annotations import attribute -from bigframes_vendored.ibis.common.collections import FrozenOrderedDict, MapSet -from bigframes_vendored.ibis.common.dispatch import lazy_singledispatch -from bigframes_vendored.ibis.common.exceptions import InputTypeError, IntegrityError -from bigframes_vendored.ibis.common.grounds import Concrete -from bigframes_vendored.ibis.common.patterns import Coercible -from bigframes_vendored.ibis.util import indent - -if TYPE_CHECKING: - from typing import TypeAlias - - -class Schema(Concrete, Coercible, MapSet): - """An ordered mapping of str -> [datatype](./datatypes.qmd), used to hold a [Table](./expression-tables.qmd#ibis.expr.tables.Table)'s schema.""" - - fields: FrozenOrderedDict[str, dt.DataType] - """A mapping of [](`str`) to - [`DataType`](./datatypes.qmd#ibis.expr.datatypes.DataType) - objects representing the type of each column.""" - - def __repr__(self) -> str: - space = 2 + max(map(len, self.names), default=0) - return "ibis.Schema {{{}\n}}".format( - indent( - "".join( - f"\n{name.ljust(space)}{type!s}" for name, type in self.items() - ), - 2, - ) - ) - - def __rich_repr__(self): - for name, dtype in self.items(): - yield name, str(dtype) - - def __len__(self) -> int: - return len(self.fields) - - def __iter__(self) -> Iterator[str]: - return iter(self.fields) - - def __getitem__(self, name: str) -> dt.DataType: - return self.fields[name] - - @classmethod - def __coerce__(cls, value) -> Schema: - if isinstance(value, cls): - return value - return schema(value) - - @attribute - def names(self): - return tuple(self.keys()) - - @attribute - def types(self): - return tuple(self.values()) - - @attribute - def _name_locs(self) -> dict[str, int]: - return {v: i for i, v in enumerate(self.names)} - - def equals(self, other: Schema) -> bool: - """Return whether `other` is equal to `self`. - - The order of fields in the schema is taken into account when computing equality. - - Parameters - ---------- - other - Schema to compare `self` to. - - Examples - -------- - >>> import ibis - >>> xy = ibis.schema({"x": int, "y": str}) - >>> xy2 = ibis.schema({"x": int, "y": str}) - >>> yx = ibis.schema({"y": str, "x": int}) - >>> xy_float = ibis.schema({"x": float, "y": str}) - >>> assert xy.equals(xy2) - >>> assert not xy.equals(yx) - >>> assert not xy.equals(xy_float) - """ - if not isinstance(other, Schema): - raise TypeError( - f"invalid equality comparison between Schema and {type(other)}" - ) - return self == other - - @classmethod - def from_tuples( - cls, - values: Iterable[tuple[str, str | dt.DataType]], - ) -> Schema: - """Construct a `Schema` from an iterable of pairs. - - Parameters - ---------- - values - An iterable of pairs of name and type. - - Returns - ------- - Schema - A new schema - - Examples - -------- - >>> import ibis - >>> ibis.Schema.from_tuples([("a", "int"), ("b", "string")]) - ibis.Schema { - a int64 - b string - } - - """ - pairs = list(values) - if len(pairs) == 0: - return cls({}) - - names, types = zip(*pairs) - - # validate unique field names - name_locs = {v: i for i, v in enumerate(names)} - if len(name_locs) < len(names): - duplicate_names = list(names) - for v in name_locs: - duplicate_names.remove(v) - raise IntegrityError(f"Duplicate column name(s): {duplicate_names}") - - # construct the schema - return cls(dict(zip(names, types))) - - @classmethod - def from_numpy(cls, numpy_schema): - """Return the equivalent ibis schema.""" - from bigframes_vendored.ibis.formats.numpy import NumpySchema - - return NumpySchema.to_ibis(numpy_schema) - - @classmethod - def from_pandas(cls, pandas_schema): - """Return the equivalent ibis schema.""" - from bigframes_vendored.ibis.formats.pandas import PandasSchema - - return PandasSchema.to_ibis(pandas_schema) - - @classmethod - def from_pyarrow(cls, pyarrow_schema): - """Return the equivalent ibis schema.""" - from bigframes_vendored.ibis.formats.pyarrow import PyArrowSchema - - return PyArrowSchema.to_ibis(pyarrow_schema) - - @classmethod - def from_polars(cls, polars_schema): - """Return the equivalent ibis schema.""" - from bigframes_vendored.ibis.formats.polars import PolarsSchema - - return PolarsSchema.to_ibis(polars_schema) - - def to_numpy(self): - """Return the equivalent numpy dtypes.""" - from bigframes_vendored.ibis.formats.numpy import NumpySchema - - return NumpySchema.from_ibis(self) - - def to_pandas(self): - """Return the equivalent pandas datatypes.""" - from bigframes_vendored.ibis.formats.pandas import PandasSchema - - return PandasSchema.from_ibis(self) - - def to_pyarrow(self): - """Return the equivalent pyarrow schema.""" - from bigframes_vendored.ibis.formats.pyarrow import PyArrowSchema - - return PyArrowSchema.from_ibis(self) - - def __arrow_c_schema__(self): - return self.to_pyarrow().__arrow_c_schema__() - - def to_polars(self): - """Return the equivalent polars schema.""" - from bigframes_vendored.ibis.formats.polars import PolarsSchema - - return PolarsSchema.from_ibis(self) - - def as_struct(self) -> dt.Struct: - return dt.Struct(self) - - def name_at_position(self, i: int) -> str: - """Return the name of a schema column at position `i`. - - Parameters - ---------- - i - The position of the column - - Returns - ------- - str - The name of the column in the schema at position `i`. - - Examples - -------- - >>> import ibis - >>> sch = ibis.Schema({"a": "int", "b": "string"}) - >>> sch.name_at_position(0) - 'a' - >>> sch.name_at_position(1) - 'b' - - """ - return self.names[i] - - -SchemaLike: TypeAlias = Union[ - Schema, - Mapping[str, Union[str, dt.DataType]], - Iterable[tuple[str, Union[str, dt.DataType]]], -] - - -@lazy_singledispatch -def schema(value: Any) -> Schema: - """Construct ibis schema from schema-like python objects.""" - raise InputTypeError(value) - - -@lazy_singledispatch -def infer(value: Any) -> Schema: - """Infer the corresponding ibis schema for a python object.""" - raise InputTypeError(value) - - -@schema.register(Schema) -def from_schema(s): - return s - - -@schema.register(Mapping) -def from_mapping(d): - return Schema(d) - - -@schema.register(Iterable) -def from_pairs(lst): - return Schema.from_tuples(lst) - - -@schema.register(type) -def from_class(cls): - return Schema(dt.dtype(cls)) - - -@schema.register("pandas.Series") -def from_pandas_series(s): - from bigframes_vendored.ibis.formats.pandas import PandasSchema - - return PandasSchema.to_ibis(s) - - -@schema.register("pyarrow.Schema") -def from_pyarrow_schema(schema): - from bigframes_vendored.ibis.formats.pyarrow import PyArrowSchema - - return PyArrowSchema.to_ibis(schema) - - -@infer.register("pandas.DataFrame") -def infer_pandas_dataframe(df): - from bigframes_vendored.ibis.formats.pandas import PandasData - - return PandasData.infer_table(df) - - -@infer.register("pyarrow.Table") -def infer_pyarrow_table(table): - from bigframes_vendored.ibis.formats.pyarrow import PyArrowSchema - - return PyArrowSchema.to_ibis(table.schema) - - -@infer.register("polars.DataFrame") -@infer.register("polars.LazyFrame") -def infer_polars_dataframe(df): - from bigframes_vendored.ibis.formats.polars import PolarsSchema - - return PolarsSchema.to_ibis(df.collect_schema()) - - -# lock the dispatchers to avoid adding new implementations -del infer.register -del schema.register diff --git a/third_party/bigframes_vendored/ibis/expr/sql.py b/third_party/bigframes_vendored/ibis/expr/sql.py deleted file mode 100644 index f375a7351f1..00000000000 --- a/third_party/bigframes_vendored/ibis/expr/sql.py +++ /dev/null @@ -1,383 +0,0 @@ -# Contains code from https://github.com/ibis-project/ibis/blob/9.2.0/ibis/expr/sql.py - -from __future__ import annotations - -import contextlib -import operator -from functools import singledispatch - -import bigframes_vendored.ibis -import bigframes_vendored.ibis.expr.api as api -import bigframes_vendored.ibis.expr.datatypes as dt -import bigframes_vendored.ibis.expr.schema as sch -import bigframes_vendored.ibis.expr.types as ibis_types -import bigframes_vendored.ibis.expr.types as ir -import bigframes_vendored.sqlglot as sg -import bigframes_vendored.sqlglot.expressions as sge -import bigframes_vendored.sqlglot.optimizer as sgo -import bigframes_vendored.sqlglot.planner as sgp -from bigframes_vendored.ibis.util import experimental -from public import public - - -class Catalog(dict[str, sch.Schema]): - """A catalog of tables and their schemas.""" - - typemap = { - dt.Int8: "tinyint", - dt.Int16: "smallint", - dt.Int32: "int", - dt.Int64: "bigint", - dt.Float16: "halffloat", - dt.Float32: "float", - dt.Float64: "double", - dt.Decimal: "decimal", - dt.Boolean: "boolean", - dt.JSON: "json", - dt.Interval: "interval", - dt.Timestamp: "datetime", - dt.Date: "date", - dt.Binary: "varbinary", - dt.String: "varchar", - dt.Array: "array", - dt.Map: "map", - dt.UUID: "uuid", - dt.Struct: "struct", - } - - def to_sqlglot_dtype(self, dtype: dt.DataType) -> str: - if dtype.is_geospatial(): - return dtype.geotype - else: - default = dtype.__class__.__name__.lower() - return self.typemap.get(type(dtype), default) - - def to_sqlglot_schema(self, schema: sch.Schema) -> dict[str, str]: - return {name: self.to_sqlglot_dtype(dtype) for name, dtype in schema.items()} - - def to_sqlglot(self): - return { - name: self.to_sqlglot_schema(table.schema()) for name, table in self.items() - } - - def overlay(self, step): - updates = {dep.name: convert(dep, catalog=self) for dep in step.dependencies} - - # handle scan aliases: FROM foo AS bar - source = getattr(step, "source", None) - alias = getattr(source, "args", {}).get("alias") - if alias is not None and (source_name := self.get(source.name)) is not None: - self[alias.name] = source_name - - return Catalog({**self, **updates}) - - -@singledispatch -def convert(step, catalog): - raise TypeError(type(step)) - - -@convert.register(sgp.Scan) -def convert_scan(scan, catalog): - catalog = catalog.overlay(scan) - - table = catalog[scan.source.alias_or_name] - - if scan.condition: - pred = convert(scan.condition, catalog=catalog) - table = table.filter(pred) - - if scan.projections: - projs = [convert(proj, catalog=catalog) for proj in scan.projections] - table = table.select(projs) - - if isinstance(scan.limit, int): - table = table.limit(scan.limit) - - return table - - -@convert.register(sgp.Sort) -def convert_sort(sort, catalog): - catalog = catalog.overlay(sort) - - table = catalog[sort.name] - - if sort.key: - keys = [convert(key, catalog=catalog) for key in sort.key] - table = table.order_by(keys) - - if sort.projections: - projs = [convert(proj, catalog=catalog) for proj in sort.projections] - table = table.select(projs) - - return table - - -_join_types = { - "": "inner", - "LEFT": "left", - "RIGHT": "right", -} - - -@convert.register(sgp.Join) -def convert_join(join, catalog): - catalog = catalog.overlay(join) - - left_name = join.name - left_table = catalog[left_name] - - for right_name, desc in join.joins.items(): - right_table = catalog[right_name] - join_kind = _join_types[desc["side"]] - - if desc["join_key"]: - predicate = None - for left_key, right_key in zip(desc["source_key"], desc["join_key"]): - left_key = convert(left_key, catalog=catalog) - right_key = convert(right_key, catalog=catalog) - if predicate is None: - predicate = left_key == right_key - else: - predicate &= left_key == right_key - else: - condition = desc["condition"] - predicate = convert(condition, catalog=catalog) - - left_table = left_table.join(right_table, predicates=predicate, how=join_kind) - - if join.condition: - predicate = convert(join.condition, catalog=catalog) - left_table = left_table.filter(predicate) - - catalog[left_name] = left_table - - return left_table - - -@convert.register(sgp.Aggregate) -def convert_aggregate(agg, catalog): - catalog = catalog.overlay(agg) - - table = catalog[agg.source] - if agg.aggregations: - metrics = [convert(a, catalog=catalog) for a in agg.aggregations] - groups = [convert(g, catalog=catalog) for k, g in agg.group.items()] - table = table.aggregate(metrics, by=groups) - - return table - - -@convert.register(sge.Subquery) -def convert_subquery(subquery, catalog): - tree = sgo.optimize(subquery.this, catalog.to_sqlglot(), rules=sgo.RULES) - plan = sgp.Plan(tree) - return convert(plan.root, catalog=catalog) - - -@convert.register(sge.Literal) -def convert_literal(literal, catalog): - value = literal.this - if literal.is_int: - value = int(value) - elif literal.is_number: - value = float(value) - return ibis_types.literal(value) - - -@convert.register(sge.Boolean) -def convert_boolean(boolean, catalog): - return ibis_types.literal(boolean.this) - - -@convert.register(sge.Alias) -def convert_alias(alias, catalog): - this = convert(alias.this, catalog=catalog) - return this.name(alias.alias) - - -@convert.register(sge.Column) -def convert_column(column, catalog): - table = catalog[column.table] - return table[column.name] - - -@convert.register(sge.Ordered) -def convert_ordered(ordered, catalog): - this = convert(ordered.this, catalog=catalog) - desc = ordered.args["desc"] # not exposed as an attribute - - return api.desc(this) if desc else api.asc(this) - - -_unary_operations = { - sge.Paren: lambda x: x, -} - - -@convert.register(sge.Unary) -def convert_unary(unary, catalog): - op = _unary_operations[type(unary)] - this = convert(unary.this, catalog=catalog) - return op(this) - - -_binary_operations = { - sge.LT: operator.lt, - sge.LTE: operator.le, - sge.GT: operator.gt, - sge.GTE: operator.ge, - sge.EQ: operator.eq, - sge.NEQ: operator.ne, - sge.Add: operator.add, - sge.Sub: operator.sub, - sge.Mul: operator.mul, - sge.Div: operator.truediv, - sge.Pow: operator.pow, - sge.And: operator.and_, - sge.Or: operator.or_, -} - - -@convert.register(sge.Binary) -def convert_binary(binary, catalog): - op = _binary_operations[type(binary)] - this = convert(binary.this, catalog=catalog) - expr = convert(binary.expression, catalog=catalog) - - if isinstance(binary.expression, sge.Subquery): - # expr is a table expression - assert len(expr.columns) == 1 - name = expr.columns[0] - expr = expr[name] - - return op(this, expr) - - -_reduction_methods = { - sge.Max: "max", - sge.Min: "min", - sge.Quantile: "quantile", - sge.Sum: "sum", - sge.Avg: "mean", - sge.Count: "count", -} - - -@convert.register(sge.AggFunc) -def convert_sum(reduction, catalog): - method = _reduction_methods[type(reduction)] - this = convert(reduction.this, catalog=catalog) - return getattr(this, method)() - - -@convert.register(sge.In) -def convert_in(in_, catalog): - this = convert(in_.this, catalog=catalog) - candidates = [convert(expression, catalog) for expression in in_.expressions] - return this.isin(candidates) - - -@public -@experimental -def parse_sql(sqlstring, catalog, dialect=None): - """Parse a SQL string into an Ibis expression. - - Parameters - ---------- - sqlstring : str - SQL string to parse - catalog : dict - A dictionary mapping table names to either schemas or ibis table expressions. - If a schema is passed, a table expression will be created using the schema. - dialect : str, optional - The SQL dialect to use with sqlglot to parse the query string. - - Returns - ------- - expr : ir.Expr - - """ - catalog = Catalog( - {name: api.table(schema, name=name) for name, schema in catalog.items()} - ) - - expr = sg.parse_one(sqlstring, dialect) - tree = sgo.optimize(expr, catalog.to_sqlglot(), rules=sgo.RULES) - plan = sgp.Plan(tree) - - return convert(plan.root, catalog=catalog) - - -class SQLString(str): - """Object to hold a formatted SQL string. - - Syntax highlights in Jupyter notebooks. - """ - - def __repr__(self) -> str: - return f"{self.__class__.__name__}({str(self)!r})" - - def _repr_markdown_(self) -> str: - return f"```sql\n{self!s}\n```" - - def _repr_pretty_(self, p, cycle) -> str: - output = str(self) - try: - from pygments import highlight - from pygments.formatters import TerminalFormatter - from pygments.lexers import SqlLexer - except ImportError: - pass - else: - with contextlib.suppress(Exception): - output = highlight( - code=output, - lexer=SqlLexer(), - formatter=TerminalFormatter(), - ) - - # strip trailing newline - p.text(output.strip()) - - -@public -def to_sql( - expr: ir.Expr, dialect: str | None = None, pretty: bool = True, **kwargs -) -> SQLString: - """Return the formatted SQL string for an expression. - - Parameters - ---------- - expr - Ibis expression. - dialect - SQL dialect to use for compilation. - pretty - Whether to use pretty formatting. - kwargs - Scalar parameters - - Returns - ------- - str - Formatted SQL string - - """ - # try to infer from a non-str expression or if not possible fallback to - # the default pretty dialect for expressions - if dialect is None: - backend = expr._find_backend(use_default=True) - dialect = backend.dialect - else: - try: - backend = getattr(bigframes_vendored.ibis, dialect) - except AttributeError: - raise ValueError(f"Unknown dialect {dialect}") - else: - dialect = getattr(backend, "dialect", dialect) - - sg_expr = backend._to_sqlglot(expr.unbind(), **kwargs) - sql = sg_expr.sql(dialect=dialect, pretty=pretty) - return SQLString(sql) diff --git a/third_party/bigframes_vendored/ibis/expr/types/__init__.py b/third_party/bigframes_vendored/ibis/expr/types/__init__.py deleted file mode 100644 index a3a48ee9359..00000000000 --- a/third_party/bigframes_vendored/ibis/expr/types/__init__.py +++ /dev/null @@ -1,21 +0,0 @@ -# Contains code from https://github.com/ibis-project/ibis/blob/9.2.0/ibis/expr/types/__init__.py - -from __future__ import annotations - -from bigframes_vendored.ibis.expr.types.arrays import * # noqa: F403 -from bigframes_vendored.ibis.expr.types.binary import * # noqa: F403 -from bigframes_vendored.ibis.expr.types.core import * # noqa: F403 -from bigframes_vendored.ibis.expr.types.generic import * # noqa: F403 -from bigframes_vendored.ibis.expr.types.geospatial import * # noqa: F403 -from bigframes_vendored.ibis.expr.types.joins import * # noqa: F403 -from bigframes_vendored.ibis.expr.types.json import * # noqa: F403 -from bigframes_vendored.ibis.expr.types.logical import * # noqa: F403 -from bigframes_vendored.ibis.expr.types.maps import * # noqa: F403 -from bigframes_vendored.ibis.expr.types.numeric import * # noqa: F403 -from bigframes_vendored.ibis.expr.types.relations import * # noqa: F403 -from bigframes_vendored.ibis.expr.types.strings import * # noqa: F403 -from bigframes_vendored.ibis.expr.types.structs import * # noqa: F403 -from bigframes_vendored.ibis.expr.types.temporal import * # noqa: F403 -from bigframes_vendored.ibis.expr.types.uuid import * # noqa: F403 - -# ruff: noqa: I001 diff --git a/third_party/bigframes_vendored/ibis/expr/types/arrays.py b/third_party/bigframes_vendored/ibis/expr/types/arrays.py deleted file mode 100644 index ee11acd56b5..00000000000 --- a/third_party/bigframes_vendored/ibis/expr/types/arrays.py +++ /dev/null @@ -1,1146 +0,0 @@ -# Contains code from https://github.com/ibis-project/ibis/blob/9.2.0/ibis/expr/types/arrays.py - -from __future__ import annotations - -import inspect -from typing import TYPE_CHECKING - -import bigframes_vendored.ibis.expr.operations as ops -from bigframes_vendored.ibis.common.deferred import Deferred, deferrable -from bigframes_vendored.ibis.expr.types.generic import Column, Scalar, Value -from public import public - -if TYPE_CHECKING: - from collections.abc import Callable, Iterable - - import bigframes_vendored.ibis.expr.types as ir - from bigframes_vendored.ibis.expr.types.typing import V - -import bigframes_vendored.ibis.common.exceptions as com - - -@public -class ArrayValue(Value): - """An Array is a variable-length sequence of values of a single type. - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> ibis.memtable({"a": [[1, None, 3], [4], [], None]}) - ┏━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ a ┃ - ┡━━━━━━━━━━━━━━━━━━━━━━┩ - │ array │ - ├──────────────────────┤ - │ [1, None, ... +1] │ - │ [4] │ - │ [] │ - │ NULL │ - └──────────────────────┘ - """ - - def length(self) -> ir.IntegerValue: - """Compute the length of an array. - - Returns - ------- - IntegerValue - The integer length of each element of `self` - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.memtable({"a": [[7, 42], [3], None]}) - >>> t - ┏━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ a ┃ - ┡━━━━━━━━━━━━━━━━━━━━━━┩ - │ array │ - ├──────────────────────┤ - │ [7, 42] │ - │ [3] │ - │ NULL │ - └──────────────────────┘ - >>> t.a.length() - ┏━━━━━━━━━━━━━━━━┓ - ┃ ArrayLength(a) ┃ - ┡━━━━━━━━━━━━━━━━┩ - │ int64 │ - ├────────────────┤ - │ 2 │ - │ 1 │ - │ NULL │ - └────────────────┘ - """ - return ops.ArrayLength(self).to_expr() - - def __getitem__(self, index: int | ir.IntegerValue | slice) -> ir.Value: - """Extract one or more elements of `self`. - - Parameters - ---------- - index - Index into `array` - - Returns - ------- - Value - - If `index` is an [](`int`) or - [`IntegerValue`](./expression-numeric.qmd#ibis.expr.types.IntegerValue) - then the return type is the element type of `self`. - - If `index` is a [](`slice`) then the return type is the same - type as the input. - - Examples - -------- - Extract a single element - - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.memtable({"a": [[7, 42], [3], None]}) - >>> t.a[0] - ┏━━━━━━━━━━━━━━━━━━┓ - ┃ ArrayIndex(a, 0) ┃ - ┡━━━━━━━━━━━━━━━━━━┩ - │ int64 │ - ├──────────────────┤ - │ 7 │ - │ 3 │ - │ NULL │ - └──────────────────┘ - - Extract a range of elements - - >>> t = ibis.memtable({"a": [[7, 42, 72], [3] * 5, None]}) - >>> t - ┏━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ a ┃ - ┡━━━━━━━━━━━━━━━━━━━━━━┩ - │ array │ - ├──────────────────────┤ - │ [7, 42, ... +1] │ - │ [3, 3, ... +3] │ - │ NULL │ - └──────────────────────┘ - >>> t.a[1:2] - ┏━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ ArraySlice(a, 1, 2) ┃ - ┡━━━━━━━━━━━━━━━━━━━━━━┩ - │ array │ - ├──────────────────────┤ - │ [42] │ - │ [3] │ - │ NULL │ - └──────────────────────┘ - """ - if isinstance(index, slice): - start = index.start - stop = index.stop - step = index.step - - if step is not None and step != 1: - raise NotImplementedError("step can only be 1") - - op = ops.ArraySlice(self, start if start is not None else 0, stop) - else: - op = ops.ArrayIndex(self, index) - return op.to_expr() - - def concat(self, other: ArrayValue, *args: ArrayValue) -> ArrayValue: - """Concatenate this array with one or more arrays. - - Parameters - ---------- - other - Other array to concat with `self` - args - Other arrays to concat with `self` - - Returns - ------- - ArrayValue - `self` concatenated with `other` and `args` - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.memtable({"a": [[7], [3], None]}) - >>> t - ┏━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ a ┃ - ┡━━━━━━━━━━━━━━━━━━━━━━┩ - │ array │ - ├──────────────────────┤ - │ [7] │ - │ [3] │ - │ NULL │ - └──────────────────────┘ - >>> t.a.concat(t.a) - ┏━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ ArrayConcat((a, a)) ┃ - ┡━━━━━━━━━━━━━━━━━━━━━━┩ - │ array │ - ├──────────────────────┤ - │ [7, 7] │ - │ [3, 3] │ - │ NULL │ - └──────────────────────┘ - >>> t.a.concat(ibis.literal([4], type="array")) - ┏━━━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ ArrayConcat((a, (4,))) ┃ - ┡━━━━━━━━━━━━━━━━━━━━━━━━┩ - │ array │ - ├────────────────────────┤ - │ [7, 4] │ - │ [3, 4] │ - │ [4] │ - └────────────────────────┘ - - `concat` is also available using the `+` operator - - >>> [1] + t.a - ┏━━━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ ArrayConcat(((1,), a)) ┃ - ┡━━━━━━━━━━━━━━━━━━━━━━━━┩ - │ array │ - ├────────────────────────┤ - │ [1, 7] │ - │ [1, 3] │ - │ [1] │ - └────────────────────────┘ - >>> t.a + [1] - ┏━━━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ ArrayConcat((a, (1,))) ┃ - ┡━━━━━━━━━━━━━━━━━━━━━━━━┩ - │ array │ - ├────────────────────────┤ - │ [7, 1] │ - │ [3, 1] │ - │ [1] │ - └────────────────────────┘ - """ - return ops.ArrayConcat((self, other, *args)).to_expr() - - def __add__(self, other: ArrayValue) -> ArrayValue: - return self.concat(other) - - def __radd__(self, other: ArrayValue) -> ArrayValue: - return ops.ArrayConcat((other, self)).to_expr() - - def repeat(self, n: int | ir.IntegerValue) -> ArrayValue: - """Repeat this array `n` times. - - Parameters - ---------- - n - Number of times to repeat `self`. - - Returns - ------- - ArrayValue - `self` repeated `n` times - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.memtable({"a": [[7], [3], None]}) - >>> t - ┏━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ a ┃ - ┡━━━━━━━━━━━━━━━━━━━━━━┩ - │ array │ - ├──────────────────────┤ - │ [7] │ - │ [3] │ - │ NULL │ - └──────────────────────┘ - >>> t.a.repeat(2) - ┏━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ ArrayRepeat(a, 2) ┃ - ┡━━━━━━━━━━━━━━━━━━━━━━┩ - │ array │ - ├──────────────────────┤ - │ [7, 7] │ - │ [3, 3] │ - │ [] │ - └──────────────────────┘ - - `repeat` is also available using the `*` operator - - >>> 2 * t.a - ┏━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ ArrayRepeat(a, 2) ┃ - ┡━━━━━━━━━━━━━━━━━━━━━━┩ - │ array │ - ├──────────────────────┤ - │ [7, 7] │ - │ [3, 3] │ - │ [] │ - └──────────────────────┘ - """ - return ops.ArrayRepeat(self, n).to_expr() - - __mul__ = __rmul__ = repeat - - def unnest(self) -> ir.Value: - """Unnest an array into a column. - - ::: {.callout-note} - ## Empty arrays and `NULL`s are dropped in the output. - To preserve empty arrays as `NULL`s as well as existing `NULL` values, - use [`Table.unnest`](./expression-tables.qmd#ibis.expr.types.relations.Table.unnest). - ::: - - Returns - ------- - ir.Value - Unnested array - - See Also - -------- - [`Table.unnest`](./expression-tables.qmd#ibis.expr.types.relations.Table.unnest) - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.memtable({"a": [[7, 42], [3, 3], None]}) - >>> t - ┏━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ a ┃ - ┡━━━━━━━━━━━━━━━━━━━━━━┩ - │ array │ - ├──────────────────────┤ - │ [7, 42] │ - │ [3, 3] │ - │ NULL │ - └──────────────────────┘ - >>> t.a.unnest() - ┏━━━━━━━┓ - ┃ a ┃ - ┡━━━━━━━┩ - │ int64 │ - ├───────┤ - │ 7 │ - │ 42 │ - │ 3 │ - │ 3 │ - └───────┘ - """ - expr = ops.Unnest(self).to_expr() - try: - return expr.name(self.get_name()) - except com.ExpressionError: - return expr - - def join(self, sep: str | ir.StringValue) -> ir.StringValue: - """Join the elements of this array expression with `sep`. - - Parameters - ---------- - sep - Separator to use for joining array elements - - Returns - ------- - StringValue - Elements of `self` joined with `sep` - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.memtable({"arr": [["a", "b", "c"], None, [], ["b", None]]}) - >>> t - ┏━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ arr ┃ - ┡━━━━━━━━━━━━━━━━━━━━━━┩ - │ array │ - ├──────────────────────┤ - │ ['a', 'b', ... +1] │ - │ NULL │ - │ [] │ - │ ['b', None] │ - └──────────────────────┘ - >>> t.arr.join("|") - ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ ArrayStringJoin(arr, '|') ┃ - ┡━━━━━━━━━━━━━━━━━━━━━━━━━━━┩ - │ string │ - ├───────────────────────────┤ - │ a|b|c │ - │ NULL │ - │ NULL │ - │ b │ - └───────────────────────────┘ - - See Also - -------- - [`StringValue.join`](./expression-strings.qmd#ibis.expr.types.strings.StringValue.join) - """ - return ops.ArrayStringJoin(self, sep=sep).to_expr() - - def map(self, func: Deferred | Callable[[ir.Value], ir.Value]) -> ir.ArrayValue: - """Apply a `func` or `Deferred` to each element of this array expression. - - Parameters - ---------- - func - Function or `Deferred` to apply to each element of this array. - - Returns - ------- - ArrayValue - `func` applied to every element of this array expression. - - Examples - -------- - >>> import ibis - >>> from ibis import _ - >>> ibis.options.interactive = True - >>> t = ibis.memtable({"a": [[1, None, 2], [4], []]}) - >>> t - ┏━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ a ┃ - ┡━━━━━━━━━━━━━━━━━━━━━━┩ - │ array │ - ├──────────────────────┤ - │ [1, None, ... +1] │ - │ [4] │ - │ [] │ - └──────────────────────┘ - - The most succinct way to use `map` is with `Deferred` expressions: - - >>> t.a.map((_ + 100).cast(float)) - ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ ArrayMap(a, Cast(Add(_, 100), float64)) ┃ - ┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩ - │ array │ - ├─────────────────────────────────────────┤ - │ [101.0, None, ... +1] │ - │ [104.0] │ - │ [] │ - └─────────────────────────────────────────┘ - - You can also use `map` with a lambda function: - - >>> t.a.map(lambda x: (x + 100).cast(float)) - ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ ArrayMap(a, Cast(Add(x, 100), float64)) ┃ - ┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩ - │ array │ - ├─────────────────────────────────────────┤ - │ [101.0, None, ... +1] │ - │ [104.0] │ - │ [] │ - └─────────────────────────────────────────┘ - - `.map()` also supports more complex callables like `functools.partial` - and lambdas with closures - - >>> from functools import partial - >>> def add(x, y): - ... return x + y - >>> add2 = partial(add, y=2) - >>> t.a.map(add2) - ┏━━━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ ArrayMap(a, Add(x, 2)) ┃ - ┡━━━━━━━━━━━━━━━━━━━━━━━━┩ - │ array │ - ├────────────────────────┤ - │ [3, None, ... +1] │ - │ [6] │ - │ [] │ - └────────────────────────┘ - >>> y = 2 - >>> t.a.map(lambda x: x + y) - ┏━━━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ ArrayMap(a, Add(x, 2)) ┃ - ┡━━━━━━━━━━━━━━━━━━━━━━━━┩ - │ array │ - ├────────────────────────┤ - │ [3, None, ... +1] │ - │ [6] │ - │ [] │ - └────────────────────────┘ - """ - if isinstance(func, Deferred): - name = "_" - resolve = func.resolve - elif callable(func): - name = next(iter(inspect.signature(func).parameters.keys())) - resolve = func - else: - raise TypeError( - f"`func` must be a Deferred or Callable, got `{type(func).__name__}`" - ) - - parameter = ops.Argument( - name=name, shape=self.op().shape, dtype=self.type().value_type - ) - body = resolve(parameter.to_expr()) - return ops.ArrayMap(self, param=parameter.param, body=body).to_expr() - - def reduce(self, func: Deferred | Callable[[ir.Value], ir.Value]) -> ir.ArrayValue: - if isinstance(func, Deferred): - name = "_" - resolve = func.resolve - elif callable(func): - name = next(iter(inspect.signature(func).parameters.keys())) - resolve = func - else: - raise TypeError( - f"`func` must be a Deferred or Callable, got `{type(func).__name__}`" - ) - - parameter = ops.Argument( - name=name, shape=self.op().shape, dtype=self.type().value_type - ) - body = resolve(parameter.to_expr()) - return ops.ArrayReduce(self, param=parameter.param, body=body).to_expr() - - def filter( - self, predicate: Deferred | Callable[[ir.Value], bool | ir.BooleanValue] - ) -> ir.ArrayValue: - """Filter array elements using `predicate` function or `Deferred`. - - Parameters - ---------- - predicate - Function or `Deferred` to use to filter array elements - - Returns - ------- - ArrayValue - Array elements filtered using `predicate` - - Examples - -------- - >>> import ibis - >>> from ibis import _ - >>> ibis.options.interactive = True - >>> t = ibis.memtable({"a": [[1, None, 2], [4], []]}) - >>> t - ┏━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ a ┃ - ┡━━━━━━━━━━━━━━━━━━━━━━┩ - │ array │ - ├──────────────────────┤ - │ [1, None, ... +1] │ - │ [4] │ - │ [] │ - └──────────────────────┘ - - The most succinct way to use `filter` is with `Deferred` expressions: - - >>> t.a.filter(_ > 1) - ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ ArrayFilter(a, Greater(_, 1)) ┃ - ┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩ - │ array │ - ├───────────────────────────────┤ - │ [2] │ - │ [4] │ - │ [] │ - └───────────────────────────────┘ - - You can also use `map` with a lambda function: - - >>> t.a.filter(lambda x: x > 1) - ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ ArrayFilter(a, Greater(x, 1)) ┃ - ┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩ - │ array │ - ├───────────────────────────────┤ - │ [2] │ - │ [4] │ - │ [] │ - └───────────────────────────────┘ - - `.filter()` also supports more complex callables like `functools.partial` - and lambdas with closures - - >>> from functools import partial - >>> def gt(x, y): - ... return x > y - >>> gt1 = partial(gt, y=1) - >>> t.a.filter(gt1) - ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ ArrayFilter(a, Greater(x, 1)) ┃ - ┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩ - │ array │ - ├───────────────────────────────┤ - │ [2] │ - │ [4] │ - │ [] │ - └───────────────────────────────┘ - >>> y = 1 - >>> t.a.filter(lambda x: x > y) - ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ ArrayFilter(a, Greater(x, 1)) ┃ - ┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩ - │ array │ - ├───────────────────────────────┤ - │ [2] │ - │ [4] │ - │ [] │ - └───────────────────────────────┘ - """ - if isinstance(predicate, Deferred): - name = "_" - resolve = predicate.resolve - elif callable(predicate): - name = next(iter(inspect.signature(predicate).parameters.keys())) - resolve = predicate - else: - raise TypeError( - f"`predicate` must be a Deferred or Callable, got `{type(predicate).__name__}`" - ) - parameter = ops.Argument( - name=name, - shape=self.op().shape, - dtype=self.type().value_type, - ) - body = resolve(parameter.to_expr()) - return ops.ArrayFilter(self, param=parameter.param, body=body).to_expr() - - def contains(self, other: ir.Value) -> ir.BooleanValue: - """Return whether the array contains `other`. - - Parameters - ---------- - other - Ibis expression to check for existence of in `self` - - Returns - ------- - BooleanValue - Whether `other` is contained in `self` - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.memtable({"arr": [[1], [], [42, 42], None]}) - >>> t - ┏━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ arr ┃ - ┡━━━━━━━━━━━━━━━━━━━━━━┩ - │ array │ - ├──────────────────────┤ - │ [1] │ - │ [] │ - │ [42, 42] │ - │ NULL │ - └──────────────────────┘ - >>> t.arr.contains(42) - ┏━━━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ ArrayContains(arr, 42) ┃ - ┡━━━━━━━━━━━━━━━━━━━━━━━━┩ - │ boolean │ - ├────────────────────────┤ - │ False │ - │ False │ - │ True │ - │ NULL │ - └────────────────────────┘ - >>> t.arr.contains(None) - ┏━━━━━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ ArrayContains(arr, None) ┃ - ┡━━━━━━━━━━━━━━━━━━━━━━━━━━┩ - │ boolean │ - ├──────────────────────────┤ - │ NULL │ - │ NULL │ - │ NULL │ - │ NULL │ - └──────────────────────────┘ - """ - return ops.ArrayContains(self, other).to_expr() - - def index(self, other: ir.Value) -> ir.IntegerValue: - """Return the position of `other` in an array. - - Parameters - ---------- - other - Ibis expression to existence of in `self` - - Returns - ------- - BooleanValue - The position of `other` in `self` - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.memtable({"arr": [[1], [], [42, 42], None]}) - >>> t - ┏━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ arr ┃ - ┡━━━━━━━━━━━━━━━━━━━━━━┩ - │ array │ - ├──────────────────────┤ - │ [1] │ - │ [] │ - │ [42, 42] │ - │ NULL │ - └──────────────────────┘ - >>> t.arr.index(42) - ┏━━━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ ArrayPosition(arr, 42) ┃ - ┡━━━━━━━━━━━━━━━━━━━━━━━━┩ - │ int64 │ - ├────────────────────────┤ - │ -1 │ - │ -1 │ - │ 0 │ - │ NULL │ - └────────────────────────┘ - >>> t.arr.index(800) - ┏━━━━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ ArrayPosition(arr, 800) ┃ - ┡━━━━━━━━━━━━━━━━━━━━━━━━━┩ - │ int64 │ - ├─────────────────────────┤ - │ -1 │ - │ -1 │ - │ -1 │ - │ NULL │ - └─────────────────────────┘ - >>> t.arr.index(None) - ┏━━━━━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ ArrayPosition(arr, None) ┃ - ┡━━━━━━━━━━━━━━━━━━━━━━━━━━┩ - │ int64 │ - ├──────────────────────────┤ - │ NULL │ - │ NULL │ - │ NULL │ - │ NULL │ - └──────────────────────────┘ - """ - return ops.ArrayPosition(self, other).to_expr() - - def remove(self, other: ir.Value) -> ir.ArrayValue: - """Remove `other` from `self`. - - Parameters - ---------- - other - Element to remove from `self`. - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.memtable({"arr": [[3, 2], [], [42, 2], [2, 2], None]}) - >>> t - ┏━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ arr ┃ - ┡━━━━━━━━━━━━━━━━━━━━━━┩ - │ array │ - ├──────────────────────┤ - │ [3, 2] │ - │ [] │ - │ [42, 2] │ - │ [2, 2] │ - │ NULL │ - └──────────────────────┘ - >>> t.arr.remove(2) - ┏━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ ArrayRemove(arr, 2) ┃ - ┡━━━━━━━━━━━━━━━━━━━━━━┩ - │ array │ - ├──────────────────────┤ - │ [3] │ - │ [] │ - │ [42] │ - │ [] │ - │ NULL │ - └──────────────────────┘ - """ - return ops.ArrayRemove(self, other).to_expr() - - def unique(self) -> ir.ArrayValue: - """Return the unique values in an array. - - ::: {.callout-note} - ## Element ordering in array may not be retained. - ::: - - Returns - ------- - ArrayValue - Unique values in an array - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.memtable({"arr": [[1, 3, 3], [], [42, 42, None], None]}) - >>> t.arr.unique() - ┏━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ ArrayDistinct(arr) ┃ - ┡━━━━━━━━━━━━━━━━━━━━━━┩ - │ array │ - ├──────────────────────┤ - │ [3, 1] │ - │ [] │ - │ [42, None] │ - │ NULL │ - └──────────────────────┘ - """ - return ops.ArrayDistinct(self).to_expr() - - def sort(self) -> ir.ArrayValue: - """Sort the elements in an array. - - Returns - ------- - ArrayValue - Sorted values in an array - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.memtable({"arr": [[3, 2], [], [42, 42], None]}) - >>> t - ┏━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ arr ┃ - ┡━━━━━━━━━━━━━━━━━━━━━━┩ - │ array │ - ├──────────────────────┤ - │ [3, 2] │ - │ [] │ - │ [42, 42] │ - │ NULL │ - └──────────────────────┘ - >>> t.arr.sort() - ┏━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ ArraySort(arr) ┃ - ┡━━━━━━━━━━━━━━━━━━━━━━┩ - │ array │ - ├──────────────────────┤ - │ [2, 3] │ - │ [] │ - │ [42, 42] │ - │ NULL │ - └──────────────────────┘ - """ - return ops.ArraySort(self).to_expr() - - def union(self, other: ir.ArrayValue) -> ir.ArrayValue: - """Union two arrays. - - Parameters - ---------- - other - Another array to union with `self` - - Returns - ------- - ArrayValue - Unioned arrays - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.memtable({"arr1": [[3, 2], [], None], "arr2": [[1, 3], [None], [5]]}) - >>> t - ┏━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ arr1 ┃ arr2 ┃ - ┡━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━┩ - │ array │ array │ - ├──────────────────────┼──────────────────────┤ - │ [3, 2] │ [1, 3] │ - │ [] │ [None] │ - │ NULL │ [5] │ - └──────────────────────┴──────────────────────┘ - >>> t.arr1.union(t.arr2) - ┏━━━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ ArrayUnion(arr1, arr2) ┃ - ┡━━━━━━━━━━━━━━━━━━━━━━━━┩ - │ array │ - ├────────────────────────┤ - │ [1, 2, ... +1] │ - │ [None] │ - │ [5] │ - └────────────────────────┘ - >>> t.arr1.union(t.arr2).contains(3) - ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ ArrayContains(ArrayUnion(arr1, arr2), 3) ┃ - ┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩ - │ boolean │ - ├──────────────────────────────────────────┤ - │ True │ - │ False │ - │ False │ - └──────────────────────────────────────────┘ - """ - return ops.ArrayUnion(self, other).to_expr() - - def intersect(self, other: ArrayValue) -> ArrayValue: - """Intersect two arrays. - - Parameters - ---------- - other - Another array to intersect with `self` - - Returns - ------- - ArrayValue - Intersected arrays - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.memtable({"arr1": [[3, 2], [], None], "arr2": [[1, 3], [None], [5]]}) - >>> t - ┏━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ arr1 ┃ arr2 ┃ - ┡━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━┩ - │ array │ array │ - ├──────────────────────┼──────────────────────┤ - │ [3, 2] │ [1, 3] │ - │ [] │ [None] │ - │ NULL │ [5] │ - └──────────────────────┴──────────────────────┘ - >>> t.arr1.intersect(t.arr2) - ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ ArrayIntersect(arr1, arr2) ┃ - ┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩ - │ array │ - ├────────────────────────────┤ - │ [3] │ - │ [] │ - │ NULL │ - └────────────────────────────┘ - """ - return ops.ArrayIntersect(self, other).to_expr() - - def zip(self, other: ArrayValue, *others: ArrayValue) -> ArrayValue: - """Zip two or more arrays together. - - Parameters - ---------- - other - Another array to zip with `self` - others - Additional arrays to zip with `self` - - Returns - ------- - Array - Array of structs where each struct field is an element of each input - array. The fields are named `f1`, `f2`, `f3`, etc. - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> ibis.options.repr.interactive.max_depth = 2 - >>> t = ibis.memtable( - ... { - ... "numbers": [[3, 2], [6, 7], [], None], - ... "strings": [["a", "c"], ["d"], [], ["x", "y"]], - ... } - ... ) - >>> t - ┏━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ numbers ┃ strings ┃ - ┡━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━┩ - │ array │ array │ - ├──────────────────────┼──────────────────────┤ - │ [3, 2] │ ['a', 'c'] │ - │ [6, 7] │ ['d'] │ - │ [] │ [] │ - │ NULL │ ['x', 'y'] │ - └──────────────────────┴──────────────────────┘ - >>> t.numbers.zip(t.strings) - ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ ArrayZip((numbers, strings)) ┃ - ┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩ - │ array> │ - ├───────────────────────────────────────────────┤ - │ [{'f1': 3, 'f2': 'a'}, {'f1': 2, 'f2': 'c'}] │ - │ [{'f1': 6, 'f2': 'd'}, {'f1': 7, 'f2': None}] │ - │ [] │ - │ NULL │ - └───────────────────────────────────────────────┘ - """ - - return ops.ArrayZip((self, other, *others)).to_expr() - - def flatten(self) -> ir.ArrayValue: - """Remove one level of nesting from an array expression. - - Returns - ------- - ArrayValue - Flattened array expression - - Examples - -------- - >>> import ibis - >>> import ibis.selectors as s - >>> from ibis import _ - >>> ibis.options.interactive = True - >>> schema = { - ... "empty": "array>", - ... "happy": "array>", - ... "nulls_only": "array>>>", - ... "mixed_nulls": "array>", - ... } - >>> data = { - ... "empty": [[], [], []], - ... "happy": [[["abc"]], [["bcd"]], [["def"]]], - ... "nulls_only": [None, None, None], - ... "mixed_nulls": [[], None, [None]], - ... } - >>> t = ibis.memtable( - ... pa.Table.from_pydict( - ... data, - ... schema=ibis.schema(schema).to_pyarrow(), - ... ) - ... ) - >>> t - ┏━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━┳━━━┓ - ┃ empty ┃ happy ┃ nulls_only ┃ … ┃ - ┡━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━╇━━━┩ - │ array> │ array> │ array>> t.empty.flatten() - ┏━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ ArrayFlatten(empty) ┃ - ┡━━━━━━━━━━━━━━━━━━━━━━┩ - │ array │ - ├──────────────────────┤ - │ [] │ - │ [] │ - │ [] │ - └──────────────────────┘ - >>> t.happy.flatten() - ┏━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ ArrayFlatten(happy) ┃ - ┡━━━━━━━━━━━━━━━━━━━━━━┩ - │ array │ - ├──────────────────────┤ - │ ['abc'] │ - │ ['bcd'] │ - │ ['def'] │ - └──────────────────────┘ - >>> t.nulls_only.flatten() - ┏━━━━━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ ArrayFlatten(nulls_only) ┃ - ┡━━━━━━━━━━━━━━━━━━━━━━━━━━┩ - │ array>> t.mixed_nulls.flatten() - ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ ArrayFlatten(mixed_nulls) ┃ - ┡━━━━━━━━━━━━━━━━━━━━━━━━━━━┩ - │ array │ - ├───────────────────────────┤ - │ [] │ - │ NULL │ - │ [] │ - └───────────────────────────┘ - >>> t.select(s.across(s.all(), _.flatten())) - ┏━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━┳━━━┓ - ┃ empty ┃ happy ┃ nulls_only ┃ … ┃ - ┡━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━╇━━━┩ - │ array │ array │ array ir.Column: - return ArrayValue.__getitem__(self, index) - - -@public -@deferrable -def array(values: Iterable[V]) -> ArrayValue: - """Create an array expression. - - If any values are [column expressions](../concepts/datatypes.qmd) the - result will be a column. Otherwise the result will be a - [scalar](../concepts/datatypes.qmd). - - Parameters - ---------- - values - An iterable of Ibis expressions or Python literals - - Returns - ------- - ArrayValue - - Examples - -------- - Create an array scalar from scalar values - - >>> import ibis - >>> ibis.options.interactive = True - >>> ibis.array([1.0, None]) - ┌─────────────┐ - │ [1.0, None] │ - └─────────────┘ - - Create an array from column and scalar expressions - - >>> t = ibis.memtable({"a": [1, 2, 3], "b": [4, 5, 6]}) - >>> ibis.array([t.a, 42, ibis.literal(None)]) - ┏━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ Array((a, 42, None)) ┃ - ┡━━━━━━━━━━━━━━━━━━━━━━┩ - │ array │ - ├──────────────────────┤ - │ [1, 42, ... +1] │ - │ [2, 42, ... +1] │ - │ [3, 42, ... +1] │ - └──────────────────────┘ - - >>> ibis.array([t.a, 42 + ibis.literal(5)]) - ┏━━━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ Array((a, Add(5, 42))) ┃ - ┡━━━━━━━━━━━━━━━━━━━━━━━━┩ - │ array │ - ├────────────────────────┤ - │ [1, 47] │ - │ [2, 47] │ - │ [3, 47] │ - └────────────────────────┘ - """ - return ops.Array(tuple(values)).to_expr() diff --git a/third_party/bigframes_vendored/ibis/expr/types/binary.py b/third_party/bigframes_vendored/ibis/expr/types/binary.py deleted file mode 100644 index b89eb6c1f1a..00000000000 --- a/third_party/bigframes_vendored/ibis/expr/types/binary.py +++ /dev/null @@ -1,56 +0,0 @@ -# Contains code from https://github.com/ibis-project/ibis/blob/9.2.0/ibis/expr/types/binary.py - -from __future__ import annotations - -from typing import TYPE_CHECKING, Literal - -if TYPE_CHECKING: - from bigframes_vendored.ibis.expr import types as ir - -import bigframes_vendored.ibis.expr.operations as ops -from bigframes_vendored.ibis.expr.types.generic import Column, Scalar, Value -from public import public - - -@public -class BinaryValue(Value): - def hashbytes( - self, - how: Literal["md5", "sha1", "sha256", "sha512"] = "sha256", - ) -> ir.BinaryValue: - """Compute the binary hash value of `arg`. - - Parameters - ---------- - how - Hash algorithm to use - - Returns - ------- - BinaryValue - Binary expression - """ - return ops.HashBytes(self, how).to_expr() - - def __invert__(self) -> BinaryValue: - return ops.BitwiseNot(self).to_expr() - - def length(self) -> ir.IntegerValue: - """Compute the length of a binary value. - - Returns - ------- - IntegerValue - The length of each binary value in the expression - """ - return ops.StringLength(self).to_expr() - - -@public -class BinaryScalar(Scalar, BinaryValue): - pass - - -@public -class BinaryColumn(Column, BinaryValue): - pass diff --git a/third_party/bigframes_vendored/ibis/expr/types/core.py b/third_party/bigframes_vendored/ibis/expr/types/core.py deleted file mode 100644 index 7a527bbda28..00000000000 --- a/third_party/bigframes_vendored/ibis/expr/types/core.py +++ /dev/null @@ -1,752 +0,0 @@ -# Contains code from https://github.com/ibis-project/ibis/blob/9.2.0/ibis/expr/types/core.py - -from __future__ import annotations - -import contextlib -import os -import webbrowser -from typing import TYPE_CHECKING, Any, NoReturn - -import bigframes_vendored.ibis -import bigframes_vendored.ibis.expr.operations as ops -import pandas as pd -from bigframes_vendored.ibis.common.annotations import ValidationError -from bigframes_vendored.ibis.common.exceptions import IbisError, TranslationError -from bigframes_vendored.ibis.common.grounds import Immutable -from bigframes_vendored.ibis.common.patterns import Coercible, CoercionError -from bigframes_vendored.ibis.common.typing import get_defining_scope -from bigframes_vendored.ibis.config import _default_backend -from bigframes_vendored.ibis.config import options as opts -from bigframes_vendored.ibis.expr.format import pretty -from bigframes_vendored.ibis.expr.types.pretty import to_rich -from bigframes_vendored.ibis.util import experimental -from public import public -from rich.console import Console -from rich.jupyter import JupyterMixin -from rich.text import Text - -if TYPE_CHECKING: - from collections.abc import Iterator, Mapping - from pathlib import Path - - import bigframes_vendored.ibis.expr.types as ir - import polars as pl - import pyarrow as pa - import torch - from bigframes_vendored.ibis.backends import BaseBackend - from bigframes_vendored.ibis.expr.visualize import ( - EdgeAttributeGetter, - NodeAttributeGetter, - ) - - -class _FixedTextJupyterMixin(JupyterMixin): - """JupyterMixin adds a spurious newline to text, this fixes the issue.""" - - def _repr_mimebundle_(self, *args, **kwargs): - bundle = super()._repr_mimebundle_(*args, **kwargs) - bundle["text/plain"] = bundle["text/plain"].rstrip() - return bundle - - -@public -class Expr(Immutable, Coercible): - """Base expression class.""" - - __slots__ = ("_arg",) - _arg: ops.Node - - def _noninteractive_repr(self) -> str: - if bigframes_vendored.ibis.options.repr.show_variables: - scope = get_defining_scope(self, types=Expr) - else: - scope = None - return pretty(self.op(), scope=scope) - - def _interactive_repr(self) -> str: - console = Console(force_terminal=False) - with console.capture() as capture: - try: - console.print(self) - except TranslationError as e: - lines = [ - "Translation to backend failed", - f"Error message: {e!r}", - "Expression repr follows:", - self._noninteractive_repr(), - ] - return "\n".join(lines) - return capture.get().rstrip() - - def __repr__(self) -> str: - if bigframes_vendored.ibis.options.interactive: - return self._interactive_repr() - else: - return self._noninteractive_repr() - - def __rich_console__(self, console: Console, options): - if console.is_jupyter: - # Rich infers a console width in jupyter notebooks, but since - # notebooks can use horizontal scroll bars we don't want to apply a - # limit here. Since rich requires an integer for max_width, we - # choose an arbitrarily large integer bound. Note that we need to - # handle this here rather than in `to_rich`, as this setting - # also needs to be forwarded to `console.render`. - options = options.update(max_width=1_000_000) - console_width = None - else: - console_width = options.max_width - - try: - if opts.interactive: - rich_object = to_rich(self, console_width=console_width) - else: - rich_object = Text(self._noninteractive_repr()) - except Exception as e: - # In IPython exceptions inside of _repr_mimebundle_ are swallowed to - # allow calling several display functions and choosing to display - # the "best" result based on some priority. - # This behavior, though, means that exceptions that bubble up inside of the interactive repr - # are silently caught. - # - # We can't stop the exception from being swallowed, but we can force - # the display of that exception as we do here. - # - # A _very_ annoying caveat is that this exception is _not_ being - # ` raise`d, it is only being printed to the console. This means - # that you cannot "catch" it. - # - # This restriction is only present in IPython, not in other REPLs. - console.print_exception() - raise e - return console.render(rich_object, options=options) - - def __init__(self, arg: ops.Node) -> None: - object.__setattr__(self, "_arg", arg) - - def __iter__(self) -> NoReturn: - raise TypeError(f"{self.__class__.__name__!r} object is not iterable") - - @classmethod - def __coerce__(cls, value): - if isinstance(value, cls): - return value - elif isinstance(value, ops.Node): - return value.to_expr() - else: - raise CoercionError("Unable to coerce value to an expression") - - def __reduce__(self): - return (self.__class__, (self._arg,)) - - def __hash__(self): - return hash((self.__class__, self._arg)) - - def equals(self, other): - """Return whether this expression is _structurally_ equivalent to `other`. - - If you want to produce an equality expression, use `==` syntax. - - Parameters - ---------- - other - Another expression - - Examples - -------- - >>> import ibis - >>> t1 = ibis.table(dict(a="int"), name="t") - >>> t2 = ibis.table(dict(a="int"), name="t") - >>> t1.equals(t2) - True - >>> v = ibis.table(dict(a="string"), name="v") - >>> t1.equals(v) - False - """ - if not isinstance(other, Expr): - raise TypeError( - f"invalid equality comparison between Expr and {type(other)}" - ) - return self._arg.equals(other._arg) - - def __bool__(self) -> bool: - raise ValueError("The truth value of an Ibis expression is not defined") - - __nonzero__ = __bool__ - - def has_name(self): - """Check whether this expression has an explicit name.""" - return hasattr(self._arg, "name") - - def get_name(self): - """Return the name of this expression.""" - return self._arg.name - - def _repr_png_(self) -> bytes | None: - if opts.interactive or not opts.graphviz_repr: - return None - try: - import bigframes_vendored.ibis.expr.visualize as viz - except ImportError: - return None - else: - # Something may go wrong, and we can't error in the notebook - # so fallback to the default text representation. - with contextlib.suppress(Exception): - return viz.to_graph(self).pipe(format="png") - - def visualize( - self, - format: str = "svg", - *, - label_edges: bool = False, - verbose: bool = False, - node_attr: Mapping[str, str] | None = None, - node_attr_getter: NodeAttributeGetter | None = None, - edge_attr: Mapping[str, str] | None = None, - edge_attr_getter: EdgeAttributeGetter | None = None, - ) -> None: - """Visualize an expression as a GraphViz graph in the browser. - - Parameters - ---------- - format - Image output format. These are specified by the ``graphviz`` Python - library. - label_edges - Show operation input names as edge labels - verbose - Print the graphviz DOT code to stderr if [](`True`) - node_attr - Mapping of ``(attribute, value)`` pairs set for all nodes. - Options are specified by the ``graphviz`` Python library. - node_attr_getter - Callback taking a node and returning a mapping of ``(attribute, value)`` pairs - for that node. Options are specified by the ``graphviz`` Python library. - edge_attr - Mapping of ``(attribute, value)`` pairs set for all edges. - Options are specified by the ``graphviz`` Python library. - edge_attr_getter - Callback taking two adjacent nodes and returning a mapping of ``(attribute, value)`` pairs - for the edge between those nodes. Options are specified by the ``graphviz`` Python library. - - Examples - -------- - Open the visualization of an expression in default browser: - - >>> import ibis - >>> import ibis.expr.operations as ops - >>> left = ibis.table(dict(a="int64", b="string"), name="left") - >>> right = ibis.table(dict(b="string", c="int64", d="string"), name="right") - >>> expr = left.inner_join(right, "b").select(left.a, b=right.c, c=right.d) - >>> expr.visualize( - ... format="svg", - ... label_edges=True, - ... node_attr={"fontname": "Roboto Mono", "fontsize": "10"}, - ... node_attr_getter=lambda node: isinstance(node, ops.Field) and {"shape": "oval"}, - ... edge_attr={"fontsize": "8"}, - ... edge_attr_getter=lambda u, v: isinstance(u, ops.Field) and {"color": "red"}, - ... ) # quartodoc: +SKIP # doctest: +SKIP - - Raises - ------ - ImportError - If ``graphviz`` is not installed. - """ - import bigframes_vendored.ibis.expr.visualize as viz - - path = viz.draw( - viz.to_graph( - self, - node_attr=node_attr, - node_attr_getter=node_attr_getter, - edge_attr=edge_attr, - edge_attr_getter=edge_attr_getter, - label_edges=label_edges, - ), - format=format, - verbose=verbose, - ) - webbrowser.open(f"file://{os.path.abspath(path)}") - - def pipe(self, f, *args: Any, **kwargs: Any) -> Expr: - """Compose `f` with `self`. - - Parameters - ---------- - f - If the expression needs to be passed as anything other than the - first argument to the function, pass a tuple with the argument - name. For example, (f, 'data') if the function f expects a 'data' - keyword - args - Positional arguments to `f` - kwargs - Keyword arguments to `f` - - Examples - -------- - >>> import ibis - >>> t = ibis.table([("a", "int64"), ("b", "string")], name="t") - >>> f = lambda a: (a + 1).name("a") - >>> g = lambda a: (a * 2).name("a") - >>> result1 = t.a.pipe(f).pipe(g) - >>> result1 - r0 := UnboundTable: t - a int64 - b string - a: r0.a + 1 * 2 - - >>> result2 = g(f(t.a)) # equivalent to the above - >>> result1.equals(result2) - True - - Returns - ------- - Expr - Result type of passed function - """ - if isinstance(f, tuple): - f, data_keyword = f - kwargs = kwargs.copy() - kwargs[data_keyword] = self - return f(*args, **kwargs) - else: - return f(self, *args, **kwargs) - - def op(self) -> ops.Node: - return self._arg - - def _find_backends(self) -> tuple[list[BaseBackend], bool]: - """Return the possible backends for an expression. - - Returns - ------- - list[BaseBackend] - A list of the backends found. - """ - - backends = set() - has_unbound = False - node_types = (ops.UnboundTable, ops.DatabaseTable, ops.SQLQueryResult) - for table in self.op().find(node_types): - if isinstance(table, ops.UnboundTable): - has_unbound = True - else: - backends.add(table.source) - - return list(backends), has_unbound - - def _find_backend(self, *, use_default: bool = False) -> BaseBackend: - """Find the backend attached to an expression. - - Parameters - ---------- - use_default - If [](`True`) and the default backend isn't set, initialize the - default backend and use that. This should only be set to `True` for - `.execute()`. For other contexts such as compilation, this option - doesn't make sense so the default value is [](`False`). - - Returns - ------- - BaseBackend - A backend that is attached to the expression - """ - backends, has_unbound = self._find_backends() - - if not backends: - if has_unbound: - raise IbisError( - "Expression contains unbound tables and therefore cannot " - "be executed. Use `.execute(expr)` to execute " - "against an explicit backend, or rebuild the expression " - "using bound tables instead." - ) - default = _default_backend() if use_default else None - if default is None: - raise IbisError( - "Expression depends on no backends, and found no default" - ) - return default - - if len(backends) > 1: - raise IbisError("Multiple backends found for this expression") - - return backends[0] - - def execute( - self, - limit: int | str | None = "default", - params: Mapping[ir.Value, Any] | None = None, - **kwargs: Any, - ): - """Execute an expression against its backend if one exists. - - Parameters - ---------- - limit - An integer to effect a specific row limit. A value of `None` means - "no limit". The default is in `ibis/config.py`. - params - Mapping of scalar parameter expressions to value - kwargs - Keyword arguments - """ - return self._find_backend(use_default=True).execute( - self, limit=limit, params=params, **kwargs - ) - - def compile( - self, - limit: int | None = None, - params: Mapping[ir.Value, Any] | None = None, - pretty: bool = False, - ): - """Compile to an execution target. - - Parameters - ---------- - limit - An integer to effect a specific row limit. A value of `None` means - "no limit". The default is in `ibis/config.py`. - params - Mapping of scalar parameter expressions to value - pretty - In case of SQL backends, return a pretty formatted SQL query. - """ - return self._find_backend().compile( - self, limit=limit, params=params, pretty=pretty - ) - - @experimental - def to_pyarrow_batches( - self, - *, - limit: int | str | None = None, - params: Mapping[ir.Value, Any] | None = None, - chunk_size: int = 1_000_000, - **kwargs: Any, - ) -> pa.ipc.RecordBatchReader: - """Execute expression and return a RecordBatchReader. - - This method is eager and will execute the associated expression - immediately. - - Parameters - ---------- - limit - An integer to effect a specific row limit. A value of `None` means - "no limit". The default is in `ibis/config.py`. - params - Mapping of scalar parameter expressions to value. - chunk_size - Maximum number of rows in each returned record batch. - kwargs - Keyword arguments - - Returns - ------- - results - RecordBatchReader - """ - return self._find_backend(use_default=True).to_pyarrow_batches( - self, - params=params, - limit=limit, - chunk_size=chunk_size, - **kwargs, - ) - - @experimental - def to_pyarrow( - self, - *, - params: Mapping[ir.Scalar, Any] | None = None, - limit: int | str | None = None, - **kwargs: Any, - ) -> pa.Table: - """Execute expression and return results in as a pyarrow table. - - This method is eager and will execute the associated expression - immediately. - - Parameters - ---------- - params - Mapping of scalar parameter expressions to value. - limit - An integer to effect a specific row limit. A value of `None` means - "no limit". The default is in `ibis/config.py`. - kwargs - Keyword arguments - - Returns - ------- - Table - A pyarrow table holding the results of the executed expression. - """ - return self._find_backend(use_default=True).to_pyarrow( - self, params=params, limit=limit, **kwargs - ) - - @experimental - def to_polars( - self, - *, - params: Mapping[ir.Scalar, Any] | None = None, - limit: int | str | None = None, - **kwargs: Any, - ) -> pl.DataFrame: - """Execute expression and return results as a polars dataframe. - - This method is eager and will execute the associated expression - immediately. - - Parameters - ---------- - params - Mapping of scalar parameter expressions to value. - limit - An integer to effect a specific row limit. A value of `None` means - "no limit". The default is in `ibis/config.py`. - kwargs - Keyword arguments - - Returns - ------- - DataFrame - A polars dataframe holding the results of the executed expression. - """ - return self._find_backend(use_default=True).to_polars( - self, params=params, limit=limit, **kwargs - ) - - @experimental - def to_pandas_batches( - self, - *, - limit: int | str | None = None, - params: Mapping[ir.Value, Any] | None = None, - chunk_size: int = 1_000_000, - **kwargs: Any, - ) -> Iterator[pd.DataFrame | pd.Series | Any]: - """Execute expression and return an iterator of pandas DataFrames. - - This method is eager and will execute the associated expression - immediately. - - Parameters - ---------- - limit - An integer to effect a specific row limit. A value of `None` means - "no limit". The default is in `ibis/config.py`. - params - Mapping of scalar parameter expressions to value. - chunk_size - Maximum number of rows in each returned `DataFrame``. - kwargs - Keyword arguments - - Returns - ------- - Iterator[pd.DataFrame] - """ - return self._find_backend(use_default=True).to_pandas_batches( - self, - params=params, - limit=limit, - chunk_size=chunk_size, - **kwargs, - ) - - @experimental - def to_parquet( - self, - path: str | Path, - *, - params: Mapping[ir.Scalar, Any] | None = None, - **kwargs: Any, - ) -> None: - """Write the results of executing the given expression to a parquet file. - - This method is eager and will execute the associated expression - immediately. - - Parameters - ---------- - path - The data source. A string or Path to the parquet file. - params - Mapping of scalar parameter expressions to value. - **kwargs - Additional keyword arguments passed to pyarrow.parquet.ParquetWriter - - https://arrow.apache.org/docs/python/generated/pyarrow.parquet.ParquetWriter.html - - Examples - -------- - Write out an expression to a single parquet file. - - >>> import ibis - >>> import tempfile - >>> penguins = ibis.examples.penguins.fetch() - >>> penguins.to_parquet(tempfile.mktemp()) - - Partition on a single column. - - >>> penguins.to_parquet(tempfile.mkdtemp(), partition_by="year") - - Partition on multiple columns. - - >>> penguins.to_parquet(tempfile.mkdtemp(), partition_by=("year", "island")) - - ::: {.callout-note} - ## Hive-partitioned output is currently only supported when using DuckDB - ::: - """ - self._find_backend(use_default=True).to_parquet(self, path, **kwargs) - - @experimental - def to_csv( - self, - path: str | Path, - *, - params: Mapping[ir.Scalar, Any] | None = None, - **kwargs: Any, - ) -> None: - """Write the results of executing the given expression to a CSV file. - - This method is eager and will execute the associated expression - immediately. - - Parameters - ---------- - path - The data source. A string or Path to the CSV file. - params - Mapping of scalar parameter expressions to value. - **kwargs - Additional keyword arguments passed to pyarrow.csv.CSVWriter - - https://arrow.apache.org/docs/python/generated/pyarrow.csv.CSVWriter.html - """ - self._find_backend(use_default=True).to_csv(self, path, **kwargs) - - @experimental - def to_delta( - self, - path: str | Path, - *, - params: Mapping[ir.Scalar, Any] | None = None, - **kwargs: Any, - ) -> None: - """Write the results of executing the given expression to a Delta Lake table. - - This method is eager and will execute the associated expression - immediately. - - Parameters - ---------- - path - The data source. A string or Path to the Delta Lake table directory. - params - Mapping of scalar parameter expressions to value. - **kwargs - Additional keyword arguments passed to deltalake.writer.write_deltalake method - """ - self._find_backend(use_default=True).to_delta(self, path, **kwargs) - - @experimental - def to_torch( - self, - *, - params: Mapping[ir.Scalar, Any] | None = None, - limit: int | str | None = None, - **kwargs: Any, - ) -> dict[str, torch.Tensor]: - """Execute an expression and return results as a dictionary of torch tensors. - - Parameters - ---------- - params - Parameters to substitute into the expression. - limit - An integer to effect a specific row limit. A value of `None` means no limit. - kwargs - Keyword arguments passed into the backend's `to_torch` implementation. - - Returns - ------- - dict[str, torch.Tensor] - A dictionary of torch tensors, keyed by column name. - """ - return self._find_backend(use_default=True).to_torch( - self, params=params, limit=limit, **kwargs - ) - - def unbind(self) -> ir.Table: - """Return an expression built on `UnboundTable` instead of backend-specific objects.""" - from bigframes_vendored.ibis.expr.rewrites import _, d, p - - rule = p.DatabaseTable >> d.UnboundTable( - name=_.name, schema=_.schema, namespace=_.namespace - ) - return self.op().replace(rule).to_expr() - - def as_table(self) -> ir.Table: - """Convert an expression to a table.""" - raise NotImplementedError( - f"{type(self)} expressions cannot be converted into tables" - ) - - def as_scalar(self) -> ir.Scalar: - """Convert an expression to a scalar.""" - raise NotImplementedError( - f"{type(self)} expressions cannot be converted into scalars" - ) - - -def _binop(op_class: type[ops.Binary], left: ir.Value, right: ir.Value) -> ir.Value: - """Try to construct a binary operation. - - Parameters - ---------- - op_class - The `ops.Binary` subclass for the operation - left - Left operand - right - Right operand - - Returns - ------- - ir.Value - A value expression - - Examples - -------- - >>> import ibis - >>> import ibis.expr.operations as ops - >>> expr = _binop(ops.TimeAdd, ibis.time("01:00"), ibis.interval(hours=1)) - >>> expr - TimeAdd(datetime.time(1, 0), 1h): datetime.time(1, 0) + 1 h - >>> _binop(ops.TimeAdd, 1, ibis.interval(hours=1)) - TimeAdd(datetime.time(0, 0, 1), 1h): datetime.time(0, 0, 1) + 1 h - """ - try: - node = op_class(left, right) - except (ValidationError, NotImplementedError): - return NotImplemented - else: - return node.to_expr() - - -def _is_null_literal(value: Any) -> bool: - """Detect whether `value` will be treated by ibis as a null literal.""" - if isinstance(value, Expr): - op = value.op() - return isinstance(op, ops.Literal) and op.value is None - if pd.isna(value): - return True - return False diff --git a/third_party/bigframes_vendored/ibis/expr/types/dataframe_interchange.py b/third_party/bigframes_vendored/ibis/expr/types/dataframe_interchange.py deleted file mode 100644 index a500fc06d79..00000000000 --- a/third_party/bigframes_vendored/ibis/expr/types/dataframe_interchange.py +++ /dev/null @@ -1,177 +0,0 @@ -# Contains code from https://github.com/ibis-project/ibis/blob/9.2.0/ibis/expr/types/dataframe_interchange.py - -from __future__ import annotations - -from functools import cached_property -from typing import TYPE_CHECKING - -if TYPE_CHECKING: - from collections.abc import Sequence - - import bigframes_vendored.ibis.expr.types as ir - import pyarrow as pa - - -class IbisDataFrame: - """An implementation of the dataframe interchange protocol. - - This is a thin shim around the pyarrow implementation to allow for: - - - Accessing a few of the metadata queries without executing the expression. - - Caching the execution on the dataframe object to avoid re-execution if - multiple methods are accessed. - - The dataframe interchange protocol may be found here: - https://data-apis.org/dataframe-protocol/latest/API.html - """ - - def __init__( - self, - table: ir.Table, - nan_as_null: bool = False, - allow_copy: bool = True, - pyarrow_table: pa.Table | None = None, - ): - self._table = table - self._nan_as_null = nan_as_null - self._allow_copy = allow_copy - self._pyarrow_table = pyarrow_table - - @cached_property - def _pyarrow_df(self): - """Returns the pyarrow implementation of the __dataframe__ protocol. - - If the backing ibis Table hasn't been executed yet, this will result - in executing and caching the result. - """ - if self._pyarrow_table is None: - self._pyarrow_table = self._table.to_pyarrow() - return self._pyarrow_table.__dataframe__( - nan_as_null=self._nan_as_null, - allow_copy=self._allow_copy, - ) - - @cached_property - def _empty_pyarrow_df(self): - """A pyarrow implementation of the __dataframe__ protocol for an empty table. - - Used for returning dtype information without executing the backing ibis - expression. - """ - return self._table.schema().to_pyarrow().empty_table().__dataframe__() - - def _get_dtype(self, name): - """Get the dtype info for a column named `name`.""" - return self._empty_pyarrow_df.get_column_by_name(name).dtype - - # These methods may all be handled without executing the query - def num_columns(self): - return len(self._table.columns) - - def column_names(self): - return self._table.columns - - def get_column(self, i: int) -> IbisColumn: - name = self._table.columns[i] - return self.get_column_by_name(name) - - def get_column_by_name(self, name: str) -> IbisColumn: - return IbisColumn(self, name) - - def get_columns(self): - return [IbisColumn(self, name) for name in self._table.columns] - - def select_columns(self, indices: Sequence[int]) -> IbisDataFrame: - names = [self._table.columns[i] for i in indices] - return self.select_columns_by_name(names) - - def select_columns_by_name(self, names: Sequence[str]) -> IbisDataFrame: - names = list(names) - table = self._table.select(names) - if (pyarrow_table := self._pyarrow_table) is not None: - pyarrow_table = pyarrow_table.select(names) - return IbisDataFrame( - table, - nan_as_null=self._nan_as_null, - allow_copy=self._allow_copy, - pyarrow_table=pyarrow_table, - ) - - def __dataframe__( - self, nan_as_null: bool = False, allow_copy: bool = True - ) -> IbisDataFrame: - return IbisDataFrame( - self._table, - nan_as_null=nan_as_null, - allow_copy=allow_copy, - pyarrow_table=self._pyarrow_table, - ) - - # These methods require executing the query - @property - def metadata(self): - return self._pyarrow_df.metadata - - def num_rows(self) -> int | None: - return self._pyarrow_df.num_rows() - - def num_chunks(self) -> int: - return self._pyarrow_df.num_chunks() - - def get_chunks(self, n_chunks: int | None = None): - return self._pyarrow_df.get_chunks(n_chunks=n_chunks) - - -class IbisColumn: - def __init__(self, df: IbisDataFrame, name: str): - self._df = df - self._name = name - - @cached_property - def _pyarrow_col(self): - """Returns the pyarrow implementation of the __dataframe__ protocol's Column type. - - If the backing ibis Table hasn't been executed yet, this will result - in executing and caching the result. - """ - return self._df._pyarrow_df.get_column_by_name(self._name) - - # These methods may all be handled without executing the query - @property - def dtype(self): - return self._df._get_dtype(self._name) - - @property - def describe_categorical(self): - raise TypeError( - "describe_categorical only works on a column with categorical dtype" - ) - - # These methods require executing the query - def size(self): - return self._pyarrow_col.size() - - @property - def offset(self): - return self._pyarrow_col.offset - - @property - def describe_null(self): - return self._pyarrow_col.describe_null - - @property - def null_count(self): - return self._pyarrow_col.null_count - - @property - def metadata(self): - return self._pyarrow_col.metadata - - def num_chunks(self) -> int: - return self._pyarrow_col.num_chunks() - - def get_chunks(self, n_chunks: int | None = None): - return self._pyarrow_col.get_chunks(n_chunks=n_chunks) - - def get_buffers(self): - return self._pyarrow_col.get_buffers() diff --git a/third_party/bigframes_vendored/ibis/expr/types/generic.py b/third_party/bigframes_vendored/ibis/expr/types/generic.py deleted file mode 100644 index 52d07183f66..00000000000 --- a/third_party/bigframes_vendored/ibis/expr/types/generic.py +++ /dev/null @@ -1,2420 +0,0 @@ -# Contains code from https://github.com/ibis-project/ibis/blob/9.2.0/ibis/expr/types/generic.py - -from __future__ import annotations - -from collections.abc import Iterable, Sequence -from typing import TYPE_CHECKING, Any - -import bigframes_vendored.ibis -import bigframes_vendored.ibis.common.exceptions as com -import bigframes_vendored.ibis.expr.builders as bl -import bigframes_vendored.ibis.expr.datatypes as dt -import bigframes_vendored.ibis.expr.operations as ops -from bigframes_vendored.ibis.common.deferred import Deferred, _, deferrable -from bigframes_vendored.ibis.common.grounds import Singleton -from bigframes_vendored.ibis.expr.rewrites import rewrite_window_input -from bigframes_vendored.ibis.expr.types.core import ( - Expr, - _binop, - _FixedTextJupyterMixin, - _is_null_literal, -) -from bigframes_vendored.ibis.expr.types.pretty import to_rich -from bigframes_vendored.ibis.util import deprecated, warn_deprecated -from public import public - -if TYPE_CHECKING: - import bigframes_vendored.ibis.expr.schema as sch - import bigframes_vendored.ibis.expr.types as ir - import pandas as pd - import polars as pl - import pyarrow as pa - import rich.table - from bigframes_vendored.ibis.formats.pyarrow import PyArrowData - - -@public -class Value(Expr): - """Base class for a data generating expression having a known type.""" - - def name(self, name): - """Rename an expression to `name`. - - Parameters - ---------- - name - The new name of the expression - - Returns - ------- - Value - `self` with name `name` - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.memtable({"a": [1, 2]}, name="t") - >>> t.a - ┏━━━━━━━┓ - ┃ a ┃ - ┡━━━━━━━┩ - │ int64 │ - ├───────┤ - │ 1 │ - │ 2 │ - └───────┘ - >>> t.a.name("b") - ┏━━━━━━━┓ - ┃ b ┃ - ┡━━━━━━━┩ - │ int64 │ - ├───────┤ - │ 1 │ - │ 2 │ - └───────┘ - """ - # TODO(kszucs): shouldn't do simplification here, but rather later - # when simplifying the whole operation tree - # the expression's name is idendical to the new one - if self.has_name() and self.get_name() == name: - return self - - if isinstance(self.op(), ops.Alias): - # only keep a single alias operation - op = ops.Alias(arg=self.op().arg, name=name) - else: - op = ops.Alias(arg=self, name=name) - - return op.to_expr() - - # TODO(kszucs): should rename to dtype - def type(self) -> dt.DataType: - """Return the [DataType](./datatypes.qmd) of `self`.""" - return self.op().dtype - - def hash(self) -> ir.IntegerValue: - """Compute an integer hash value. - - ::: {.callout-note} - ## The hashing function used is backend-dependent. - ::: - - Returns - ------- - IntegerValue - The hash value of `self` - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> ibis.literal("hello").hash() # doctest: +SKIP - -4155090522938856779 - """ - return ops.Hash(self).to_expr() - - def cast(self, target_type: Any) -> Value: - """Cast expression to indicated data type. - - Similar to `pandas.Series.astype`. - - Parameters - ---------- - target_type - Type to cast to. Anything accepted by [`ibis.dtype()`](./datatypes.qmd#ibis.dtype) - - Returns - ------- - Value - Casted expression - - See Also - -------- - [`Value.try_cast()`](./expression-generic.qmd#ibis.expr.types.generic.Value.try_cast) - [`ibis.dtype()`](./datatypes.qmd#ibis.dtype) - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> x = ibis.examples.penguins.fetch()["bill_depth_mm"] - >>> x - ┏━━━━━━━━━━━━━━━┓ - ┃ bill_depth_mm ┃ - ┡━━━━━━━━━━━━━━━┩ - │ float64 │ - ├───────────────┤ - │ 18.7 │ - │ 17.4 │ - │ 18.0 │ - │ NULL │ - │ 19.3 │ - │ 20.6 │ - │ 17.8 │ - │ 19.6 │ - │ 18.1 │ - │ 20.2 │ - │ … │ - └───────────────┘ - - python's built-in types can be used - - >>> x.cast(int) - ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ Cast(bill_depth_mm, int64) ┃ - ┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩ - │ int64 │ - ├────────────────────────────┤ - │ 19 │ - │ 17 │ - │ 18 │ - │ NULL │ - │ 19 │ - │ 21 │ - │ 18 │ - │ 20 │ - │ 18 │ - │ 20 │ - │ … │ - └────────────────────────────┘ - - If you make an illegal cast, you won't know until the backend actually - executes it. Consider [`.try_cast()`](#ibis.expr.types.generic.Value.try_cast). - - >>> ibis.literal("a string").cast(int) # doctest: +SKIP - - """ - op = ops.Cast(self, to=target_type) - - if op.to == self.type(): - # noop case if passed type is the same - return self - - if op.to.is_geospatial() and not self.type().is_binary(): - from_geotype = self.type().geotype or "geometry" - to_geotype = op.to.geotype - if from_geotype == to_geotype: - return self - - return op.to_expr() - - def try_cast(self, target_type: Any) -> Value: - """Try cast expression to indicated data type. - - If the cast fails for a row, the value is returned - as null or NaN depending on target_type and backend behavior. - - Parameters - ---------- - target_type - Type to try cast to. Anything accepted by [`ibis.dtype()`](./datatypes.qmd#ibis.dtype) - - Returns - ------- - Value - Casted expression - - See Also - -------- - [`Value.cast()`](./expression-generic.qmd#ibis.expr.types.generic.Value.cast) - [`ibis.dtype()`](./datatypes.qmd#ibis.dtype) - - Examples - -------- - >>> import ibis - >>> from ibis import _ - >>> ibis.options.interactive = True - >>> t = ibis.memtable({"numbers": [1, 2, 3, 4], "strings": ["1.0", "2", "hello", "world"]}) - >>> t - ┏━━━━━━━━━┳━━━━━━━━━┓ - ┃ numbers ┃ strings ┃ - ┡━━━━━━━━━╇━━━━━━━━━┩ - │ int64 │ string │ - ├─────────┼─────────┤ - │ 1 │ 1.0 │ - │ 2 │ 2 │ - │ 3 │ hello │ - │ 4 │ world │ - └─────────┴─────────┘ - >>> t = t.mutate(numbers_to_strings=_.numbers.try_cast("string")) - >>> t = t.mutate(strings_to_numbers=_.strings.try_cast("int")) - >>> t - ┏━━━━━━━━━┳━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━┓ - ┃ numbers ┃ strings ┃ numbers_to_strings ┃ strings_to_numbers ┃ - ┡━━━━━━━━━╇━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━┩ - │ int64 │ string │ string │ int64 │ - ├─────────┼─────────┼────────────────────┼────────────────────┤ - │ 1 │ 1.0 │ 1 │ 1 │ - │ 2 │ 2 │ 2 │ 2 │ - │ 3 │ hello │ 3 │ NULL │ - │ 4 │ world │ 4 │ NULL │ - └─────────┴─────────┴────────────────────┴────────────────────┘ - """ - op = ops.TryCast(self, to=target_type) - - if op.to == self.type(): - # noop case if passed type is the same - return self - - return op.to_expr() - - def coalesce(self, *args: Value) -> Value: - """Return the first non-null value from `args`. - - Parameters - ---------- - args - Arguments from which to choose the first non-null value - - Returns - ------- - Value - Coalesced expression - - See Also - -------- - [`ibis.coalesce()`](./expression-generic.qmd#ibis.coalesce) - [`Value.fill_null()`](./expression-generic.qmd#ibis.expr.types.generic.Value.fill_null) - - Examples - -------- - >>> import ibis - >>> ibis.coalesce(None, 4, 5).name("x") - x: Coalesce(...) - """ - return ops.Coalesce((self, *args)).to_expr() - - @deprecated(as_of="8.0.0", instead="use ibis.greatest(self, rest...) instead") - def greatest(self, *args: ir.Value) -> ir.Value: - return ops.Greatest((self, *args)).to_expr() - - @deprecated(as_of="8.0.0", instead="use ibis.least(self, rest...) instead") - def least(self, *args: ir.Value) -> ir.Value: - return ops.Least((self, *args)).to_expr() - - def typeof(self) -> ir.StringValue: - """Return the string name of the datatype of self. - - The values of the returned strings are necessarily backend dependent. - e.g. duckdb may say "DOUBLE", while sqlite may say "real". - - Returns - ------- - StringValue - A string indicating the type of the value - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> vals = ibis.examples.penguins.fetch().head(5).bill_length_mm - >>> vals - ┏━━━━━━━━━━━━━━━━┓ - ┃ bill_length_mm ┃ - ┡━━━━━━━━━━━━━━━━┩ - │ float64 │ - ├────────────────┤ - │ 39.1 │ - │ 39.5 │ - │ 40.3 │ - │ NULL │ - │ 36.7 │ - └────────────────┘ - >>> vals.typeof() - ┏━━━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ TypeOf(bill_length_mm) ┃ - ┡━━━━━━━━━━━━━━━━━━━━━━━━┩ - │ string │ - ├────────────────────────┤ - │ DOUBLE │ - │ DOUBLE │ - │ DOUBLE │ - │ DOUBLE │ - │ DOUBLE │ - └────────────────────────┘ - - Different backends have different names for their native types - - >>> ibis.duckdb.connect().execute(ibis.literal(5.4).typeof()) - 'DOUBLE' - >>> ibis.sqlite.connect().execute(ibis.literal(5.4).typeof()) - 'real' - """ - return ops.TypeOf(self).to_expr() - - def fill_null(self, fill_value: Scalar) -> Value: - """Replace any null values with the indicated fill value. - - Parameters - ---------- - fill_value - Value with which to replace `NULL` values in `self` - - See Also - -------- - [`Value.coalesce()`](./expression-generic.qmd#ibis.expr.types.generic.Value.coalesce) - [`ibis.coalesce()`](./expression-generic.qmd#ibis.coalesce) - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.examples.penguins.fetch().limit(5) - >>> t.sex - ┏━━━━━━━━┓ - ┃ sex ┃ - ┡━━━━━━━━┩ - │ string │ - ├────────┤ - │ male │ - │ female │ - │ female │ - │ NULL │ - │ female │ - └────────┘ - >>> t.sex.fill_null("unrecorded").name("sex") - ┏━━━━━━━━━━━━┓ - ┃ sex ┃ - ┡━━━━━━━━━━━━┩ - │ string │ - ├────────────┤ - │ male │ - │ female │ - │ female │ - │ unrecorded │ - │ female │ - └────────────┘ - - Returns - ------- - Value - `self` filled with `fill_value` where it is `NULL` - """ - return ops.Coalesce((self, fill_value)).to_expr() - - @deprecated(as_of="9.1", instead="use fill_null instead") - def fillna(self, fill_value: Scalar) -> Value: - """Deprecated - use `fill_null` instead.""" - return self.fill_null(fill_value) - - def nullif(self, null_if_expr: Value) -> Value: - """Set values to null if they equal the values `null_if_expr`. - - Commonly used to avoid divide-by-zero problems by replacing zero with - `NULL` in the divisor. - - Equivalent to `(self == null_if_expr).ifelse(ibis.null(), self)`. - - Parameters - ---------- - null_if_expr - Expression indicating what values should be NULL - - Returns - ------- - Value - Value expression - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> vals = ibis.examples.penguins.fetch().head(5).sex - >>> vals - ┏━━━━━━━━┓ - ┃ sex ┃ - ┡━━━━━━━━┩ - │ string │ - ├────────┤ - │ male │ - │ female │ - │ female │ - │ NULL │ - │ female │ - └────────┘ - >>> vals.nullif("male") - ┏━━━━━━━━━━━━━━━━━━━━━┓ - ┃ NullIf(sex, 'male') ┃ - ┡━━━━━━━━━━━━━━━━━━━━━┩ - │ string │ - ├─────────────────────┤ - │ NULL │ - │ female │ - │ female │ - │ NULL │ - │ female │ - └─────────────────────┘ - """ - return ops.NullIf(self, null_if_expr).to_expr() - - def between( - self, - lower: Value, - upper: Value, - ) -> ir.BooleanValue: - """Check if this expression is between `lower` and `upper`, inclusive. - - Parameters - ---------- - lower - Lower bound, inclusive - upper - Upper bound, inclusive - - Returns - ------- - BooleanValue - Expression indicating membership in the provided range - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.examples.penguins.fetch().limit(5) - >>> t.bill_length_mm.between(35, 38) - ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ Between(bill_length_mm, 35, 38) ┃ - ┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩ - │ boolean │ - ├─────────────────────────────────┤ - │ False │ - │ False │ - │ False │ - │ NULL │ - │ True │ - └─────────────────────────────────┘ - """ - return ops.Between(self, lower, upper).to_expr() - - def isin(self, values: Value | Sequence[Value]) -> ir.BooleanValue: - """Check whether this expression's values are in `values`. - - `NULL` values are propagated in the output. See examples for details. - - Parameters - ---------- - values - Values or expression to check for membership - - Returns - ------- - BooleanValue - Expression indicating membership - - See Also - -------- - [`Value.notin()`](./expression-generic.qmd#ibis.expr.types.generic.Value.notin) - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.memtable({"a": [1, 2, 3], "b": [2, 3, 4]}) - >>> t - ┏━━━━━━━┳━━━━━━━┓ - ┃ a ┃ b ┃ - ┡━━━━━━━╇━━━━━━━┩ - │ int64 │ int64 │ - ├───────┼───────┤ - │ 1 │ 2 │ - │ 2 │ 3 │ - │ 3 │ 4 │ - └───────┴───────┘ - - Check against a literal sequence of values - - >>> t.a.isin([1, 2]) - ┏━━━━━━━━━━━━━━━━━━━━━┓ - ┃ InValues(a, (1, 2)) ┃ - ┡━━━━━━━━━━━━━━━━━━━━━┩ - │ boolean │ - ├─────────────────────┤ - │ True │ - │ True │ - │ False │ - └─────────────────────┘ - - Check against a derived expression - - >>> t.a.isin(t.b + 1) - ┏━━━━━━━━━━━━━━━┓ - ┃ InSubquery(a) ┃ - ┡━━━━━━━━━━━━━━━┩ - │ boolean │ - ├───────────────┤ - │ False │ - │ False │ - │ True │ - └───────────────┘ - - Check against a column from a different table - - >>> t2 = ibis.memtable({"x": [99, 2, 99]}) - >>> t.a.isin(t2.x) - ┏━━━━━━━━━━━━━━━┓ - ┃ InSubquery(a) ┃ - ┡━━━━━━━━━━━━━━━┩ - │ boolean │ - ├───────────────┤ - │ False │ - │ True │ - │ False │ - └───────────────┘ - - `NULL` behavior - - >>> t = ibis.memtable({"x": [1, 2]}) - >>> t.x.isin([1, None]) - ┏━━━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ InValues(x, (1, None)) ┃ - ┡━━━━━━━━━━━━━━━━━━━━━━━━┩ - │ boolean │ - ├────────────────────────┤ - │ True │ - │ NULL │ - └────────────────────────┘ - >>> t = ibis.memtable({"x": [1, None, 2]}) - >>> t.x.isin([1]) - ┏━━━━━━━━━━━━━━━━━━━┓ - ┃ InValues(x, (1,)) ┃ - ┡━━━━━━━━━━━━━━━━━━━┩ - │ boolean │ - ├───────────────────┤ - │ True │ - │ NULL │ - │ False │ - └───────────────────┘ - >>> t.x.isin([3]) - ┏━━━━━━━━━━━━━━━━━━━┓ - ┃ InValues(x, (3,)) ┃ - ┡━━━━━━━━━━━━━━━━━━━┩ - │ boolean │ - ├───────────────────┤ - │ False │ - │ NULL │ - │ False │ - └───────────────────┘ - """ - from bigframes_vendored.ibis.expr.types import ArrayValue - - if isinstance(values, ArrayValue): - return ops.ArrayContains(values, self).to_expr() - elif isinstance(values, Column): - return ops.InSubquery(values.as_table(), needle=self).to_expr() - else: - return ops.InValues(self, values).to_expr() - - def notin(self, values: Value | Sequence[Value]) -> ir.BooleanValue: - """Check whether this expression's values are not in `values`. - - Opposite of [`Value.isin()`](./expression-generic.qmd#ibis.expr.types.generic.Value.isin). - - Parameters - ---------- - values - Values or expression to check for lack of membership - - Returns - ------- - BooleanValue - Whether `self`'s values are not contained in `values` - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.examples.penguins.fetch().limit(5) - >>> t.bill_depth_mm - ┏━━━━━━━━━━━━━━━┓ - ┃ bill_depth_mm ┃ - ┡━━━━━━━━━━━━━━━┩ - │ float64 │ - ├───────────────┤ - │ 18.7 │ - │ 17.4 │ - │ 18.0 │ - │ NULL │ - │ 19.3 │ - └───────────────┘ - >>> t.bill_depth_mm.notin([18.7, 18.1]) - ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ Not(InValues(bill_depth_mm, (18.7, 18.1))) ┃ - ┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩ - │ boolean │ - ├────────────────────────────────────────────┤ - │ False │ - │ True │ - │ True │ - │ NULL │ - │ True │ - └────────────────────────────────────────────┘ - """ - return ~self.isin(values) - - def substitute( - self, - value: Value | dict, - replacement: Value | None = None, - else_: Value | None = None, - ): - """Replace values given in `values` with `replacement`. - - This is similar to the pandas `replace` method. - - Parameters - ---------- - value - Expression or dict. - replacement - If an expression is passed to value, this must be - passed. - else_ - If an original value does not match `value`, then `else_` is used. - The default of `None` means leave the original value unchanged. - - Returns - ------- - Value - Replaced values - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.examples.penguins.fetch() - >>> t.island.value_counts().order_by("island") - ┏━━━━━━━━━━━┳━━━━━━━━━━━━━━┓ - ┃ island ┃ island_count ┃ - ┡━━━━━━━━━━━╇━━━━━━━━━━━━━━┩ - │ string │ int64 │ - ├───────────┼──────────────┤ - │ Biscoe │ 168 │ - │ Dream │ 124 │ - │ Torgersen │ 52 │ - └───────────┴──────────────┘ - >>> t.island.substitute({"Torgersen": "torg", "Biscoe": "bisc"}).name( - ... "island" - ... ).value_counts().order_by("island") - ┏━━━━━━━━┳━━━━━━━━━━━━━━┓ - ┃ island ┃ island_count ┃ - ┡━━━━━━━━╇━━━━━━━━━━━━━━┩ - │ string │ int64 │ - ├────────┼──────────────┤ - │ Dream │ 124 │ - │ bisc │ 168 │ - │ torg │ 52 │ - └────────┴──────────────┘ - """ - if isinstance(value, dict): - expr = bigframes_vendored.ibis.case() - try: - null_replacement = value.pop(None) - except KeyError: - pass - else: - expr = expr.when(self.isnull(), null_replacement) - for k, v in value.items(): - expr = expr.when(self == k, v) - else: - expr = self.case().when(value, replacement) - - return expr.else_(else_ if else_ is not None else self).end() - - def over( - self, - window=None, - *, - rows=None, - range=None, - group_by=None, - order_by=None, - ) -> Value: - """Construct a window expression. - - Parameters - ---------- - window - Window specification - rows - Whether to use the `ROWS` window clause - range - Whether to use the `RANGE` window clause - group_by - Grouping key - order_by - Ordering key - - Returns - ------- - Value - A window function expression - - """ - - if window is None: - window = bigframes_vendored.ibis.window( - rows=rows, - range=range, - group_by=group_by, - order_by=order_by, - ) - elif not isinstance(window, bl.WindowBuilder): - raise com.IbisTypeError("Unexpected window type: {window!r}") - - node = self.op() - if len(node.relations) == 0: - table = None - elif len(node.relations) == 1: - (table,) = node.relations - table = table.to_expr() - else: - raise com.RelationError("Cannot use window with multiple tables") - - @deferrable - def bind(table): - winfunc = rewrite_window_input( - node, window.bind(table) if (table is not None) else window - ) - if winfunc == node: - raise com.IbisTypeError( - "No reduction or analytic function found to construct a window expression" - ) - return winfunc.to_expr() - - try: - return bind(table) - except com.IbisInputError: - return bind(_) - - def isnull(self) -> ir.BooleanValue: - """Return whether this expression is NULL. - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.examples.penguins.fetch().limit(5) - >>> t.bill_depth_mm - ┏━━━━━━━━━━━━━━━┓ - ┃ bill_depth_mm ┃ - ┡━━━━━━━━━━━━━━━┩ - │ float64 │ - ├───────────────┤ - │ 18.7 │ - │ 17.4 │ - │ 18.0 │ - │ NULL │ - │ 19.3 │ - └───────────────┘ - >>> t.bill_depth_mm.isnull() - ┏━━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ IsNull(bill_depth_mm) ┃ - ┡━━━━━━━━━━━━━━━━━━━━━━━┩ - │ boolean │ - ├───────────────────────┤ - │ False │ - │ False │ - │ False │ - │ True │ - │ False │ - └───────────────────────┘ - """ - return ops.IsNull(self).to_expr() - - def notnull(self) -> ir.BooleanValue: - """Return whether this expression is not NULL. - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.examples.penguins.fetch().limit(5) - >>> t.bill_depth_mm - ┏━━━━━━━━━━━━━━━┓ - ┃ bill_depth_mm ┃ - ┡━━━━━━━━━━━━━━━┩ - │ float64 │ - ├───────────────┤ - │ 18.7 │ - │ 17.4 │ - │ 18.0 │ - │ NULL │ - │ 19.3 │ - └───────────────┘ - >>> t.bill_depth_mm.notnull() - ┏━━━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ NotNull(bill_depth_mm) ┃ - ┡━━━━━━━━━━━━━━━━━━━━━━━━┩ - │ boolean │ - ├────────────────────────┤ - │ True │ - │ True │ - │ True │ - │ False │ - │ True │ - └────────────────────────┘ - """ - return ops.NotNull(self).to_expr() - - def case(self) -> bl.SimpleCaseBuilder: - """Create a SimpleCaseBuilder to chain multiple if-else statements. - - Add new search expressions with the `.when()` method. These must be - comparable with this column expression. Conclude by calling `.end()`. - - Returns - ------- - SimpleCaseBuilder - A case builder - - See Also - -------- - [`Value.substitute()`](./expression-generic.qmd#ibis.expr.types.generic.Value.substitute) - [`ibis.cases()`](./expression-generic.qmd#ibis.expr.types.generic.Value.cases) - [`ibis.case()`](./expression-generic.qmd#ibis.case) - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> x = ibis.examples.penguins.fetch().head(5)["sex"] - >>> x - ┏━━━━━━━━┓ - ┃ sex ┃ - ┡━━━━━━━━┩ - │ string │ - ├────────┤ - │ male │ - │ female │ - │ female │ - │ NULL │ - │ female │ - └────────┘ - >>> x.case().when("male", "M").when("female", "F").else_("U").end() - ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ SimpleCase(sex, ('male', 'female'), ('M', 'F'), 'U') ┃ - ┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩ - │ string │ - ├──────────────────────────────────────────────────────┤ - │ M │ - │ F │ - │ F │ - │ U │ - │ F │ - └──────────────────────────────────────────────────────┘ - - Cases not given result in the ELSE case - - >>> x.case().when("male", "M").else_("OTHER").end() - ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ SimpleCase(sex, ('male',), ('M',), 'OTHER') ┃ - ┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩ - │ string │ - ├─────────────────────────────────────────────┤ - │ M │ - │ OTHER │ - │ OTHER │ - │ OTHER │ - │ OTHER │ - └─────────────────────────────────────────────┘ - - If you don't supply an ELSE, then NULL is used - - >>> x.case().when("male", "M").end() - ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ SimpleCase(sex, ('male',), ('M',), Cast(None, string)) ┃ - ┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩ - │ string │ - ├────────────────────────────────────────────────────────┤ - │ M │ - │ NULL │ - │ NULL │ - │ NULL │ - │ NULL │ - └────────────────────────────────────────────────────────┘ - """ - import bigframes_vendored.ibis.expr.builders as bl - - return bl.SimpleCaseBuilder(self.op()) - - def cases( - self, - case_result_pairs: Iterable[tuple[ir.BooleanValue, Value]], - default: Value | None = None, - ) -> Value: - """Create a case expression in one shot. - - Parameters - ---------- - case_result_pairs - Conditional-result pairs - default - Value to return if none of the case conditions are true - - Returns - ------- - Value - Value expression - - See Also - -------- - [`Value.substitute()`](./expression-generic.qmd#ibis.expr.types.generic.Value.substitute) - [`ibis.cases()`](./expression-generic.qmd#ibis.expr.types.generic.Value.cases) - [`ibis.case()`](./expression-generic.qmd#ibis.case) - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.memtable({"values": [1, 2, 1, 2, 3, 2, 4]}) - >>> t - ┏━━━━━━━━┓ - ┃ values ┃ - ┡━━━━━━━━┩ - │ int64 │ - ├────────┤ - │ 1 │ - │ 2 │ - │ 1 │ - │ 2 │ - │ 3 │ - │ 2 │ - │ 4 │ - └────────┘ - >>> number_letter_map = ((1, "a"), (2, "b"), (3, "c")) - >>> t.values.cases(number_letter_map, default="unk").name("replace") - ┏━━━━━━━━━┓ - ┃ replace ┃ - ┡━━━━━━━━━┩ - │ string │ - ├─────────┤ - │ a │ - │ b │ - │ a │ - │ b │ - │ c │ - │ b │ - │ unk │ - └─────────┘ - """ - builder = self.case() - for case, result in case_result_pairs: - builder = builder.when(case, result) - return builder.else_(default).end() - - def collect(self, where: ir.BooleanValue | None = None) -> ir.ArrayScalar: - """Aggregate this expression's elements into an array. - - This function is called `array_agg`, `list_agg`, or `list` in other systems. - - Parameters - ---------- - where - Filter to apply before aggregation - - Returns - ------- - ArrayScalar - Collected array - - Examples - -------- - Basic collect usage - - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.memtable({"key": list("aaabb"), "value": [1, 2, 3, 4, 5]}) - >>> t - ┏━━━━━━━━┳━━━━━━━┓ - ┃ key ┃ value ┃ - ┡━━━━━━━━╇━━━━━━━┩ - │ string │ int64 │ - ├────────┼───────┤ - │ a │ 1 │ - │ a │ 2 │ - │ a │ 3 │ - │ b │ 4 │ - │ b │ 5 │ - └────────┴───────┘ - >>> t.value.collect() - ┌────────────────┐ - │ [1, 2, ... +3] │ - └────────────────┘ - >>> type(t.value.collect()) - - - Collect elements per group - - >>> t.group_by("key").agg(v=lambda t: t.value.collect()).order_by("key") - ┏━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ key ┃ v ┃ - ┡━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━┩ - │ string │ array │ - ├────────┼──────────────────────┤ - │ a │ [1, 2, ... +1] │ - │ b │ [4, 5] │ - └────────┴──────────────────────┘ - - Collect elements per group using a filter - - >>> t.group_by("key").agg(v=lambda t: t.value.collect(where=t.value > 1)).order_by("key") - ┏━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ key ┃ v ┃ - ┡━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━┩ - │ string │ array │ - ├────────┼──────────────────────┤ - │ a │ [2, 3] │ - │ b │ [4, 5] │ - └────────┴──────────────────────┘ - """ - return ops.ArrayCollect(self, where=self._bind_to_parent_table(where)).to_expr() - - def identical_to(self, other: Value) -> ir.BooleanValue: - """Return whether this expression is identical to other. - - Corresponds to `IS NOT DISTINCT FROM` in SQL. - - Parameters - ---------- - other - Expression to compare to - - Returns - ------- - BooleanValue - Whether this expression is not distinct from `other` - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> one = ibis.literal(1) - >>> two = ibis.literal(2) - >>> two.identical_to(one + one) - ┌──────────┐ - │ np.True_ │ - └──────────┘ - """ - try: - return ops.IdenticalTo(self, other).to_expr() - except (com.IbisTypeError, NotImplementedError): - return NotImplemented - - def group_concat( - self, - sep: str = ",", - where: ir.BooleanValue | None = None, - ) -> ir.StringScalar: - """Concatenate values using the indicated separator to produce a string. - - Parameters - ---------- - sep - Separator will be used to join strings - where - Filter expression - - Returns - ------- - StringScalar - Concatenated string expression - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.examples.penguins.fetch().limit(5) - >>> t[["bill_length_mm", "bill_depth_mm"]] - ┏━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓ - ┃ bill_length_mm ┃ bill_depth_mm ┃ - ┡━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩ - │ float64 │ float64 │ - ├────────────────┼───────────────┤ - │ 39.1 │ 18.7 │ - │ 39.5 │ 17.4 │ - │ 40.3 │ 18.0 │ - │ NULL │ NULL │ - │ 36.7 │ 19.3 │ - └────────────────┴───────────────┘ - >>> t.bill_length_mm.group_concat() - ┌───────────────────────┐ - │ '39.1,39.5,40.3,36.7' │ - └───────────────────────┘ - - >>> t.bill_length_mm.group_concat(sep=": ") - ┌──────────────────────────┐ - │ '39.1: 39.5: 40.3: 36.7' │ - └──────────────────────────┘ - - >>> t.bill_length_mm.group_concat(sep=": ", where=t.bill_depth_mm > 18) - ┌──────────────┐ - │ '39.1: 36.7' │ - └──────────────┘ - """ - return ops.GroupConcat( - self, sep=sep, where=self._bind_to_parent_table(where) - ).to_expr() - - def __hash__(self) -> int: - return super().__hash__() - - def __eq__(self, other: Value) -> ir.BooleanValue: - if _is_null_literal(other): - return self.isnull() - elif _is_null_literal(self): - return other.isnull() - return _binop(ops.Equals, self, other) - - def __ne__(self, other: Value) -> ir.BooleanValue: - if _is_null_literal(other): - return self.notnull() - elif _is_null_literal(self): - return other.notnull() - return _binop(ops.NotEquals, self, other) - - def __ge__(self, other: Value) -> ir.BooleanValue: - return _binop(ops.GreaterEqual, self, other) - - def __gt__(self, other: Value) -> ir.BooleanValue: - return _binop(ops.Greater, self, other) - - def __le__(self, other: Value) -> ir.BooleanValue: - return _binop(ops.LessEqual, self, other) - - def __lt__(self, other: Value) -> ir.BooleanValue: - return _binop(ops.Less, self, other) - - def asc(self, nulls_first: bool = False) -> ir.Value: - """Sort an expression ascending.""" - return ops.SortKey(self, ascending=True, nulls_first=nulls_first).to_expr() - - def desc(self, nulls_first: bool = False) -> ir.Value: - """Sort an expression descending.""" - return ops.SortKey(self, ascending=False, nulls_first=nulls_first).to_expr() - - def to_pandas(self, **kwargs) -> pd.Series: - """Convert a column expression to a pandas Series or scalar object. - - Parameters - ---------- - kwargs - Same as keyword arguments to [`execute`](#ibis.expr.types.core.Expr.execute) - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.examples.penguins.fetch().limit(5) - >>> t.to_pandas() - species island bill_length_mm ... body_mass_g sex year - 0 Adelie Torgersen 39.1 ... 3750.0 male 2007 - 1 Adelie Torgersen 39.5 ... 3800.0 female 2007 - 2 Adelie Torgersen 40.3 ... 3250.0 female 2007 - 3 Adelie Torgersen NaN ... NaN None 2007 - 4 Adelie Torgersen 36.7 ... 3450.0 female 2007 - [5 rows x 8 columns] - """ - return self.execute(**kwargs) - - -@public -class Scalar(Value): - def __pyarrow_result__( - self, table: pa.Table, data_mapper: type[PyArrowData] | None = None - ) -> pa.Scalar: - if data_mapper is None: - from bigframes_vendored.ibis.formats.pyarrow import ( - PyArrowData as data_mapper, - ) - - return data_mapper.convert_scalar(table[0][0], self.type()) - - def __pandas_result__( - self, df: pd.DataFrame, *, schema: sch.Schema | None = None - ) -> Any: - from bigframes_vendored.ibis.formats.pandas import PandasData - - return PandasData.convert_scalar( - df, self.type() if schema is None else schema[df.columns[0]] - ) - - def __polars_result__(self, df: pl.DataFrame) -> Any: - from bigframes_vendored.ibis.formats.polars import PolarsData - - return PolarsData.convert_scalar(df, self.type()) - - def as_scalar(self): - """Inform ibis that the expression should be treated as a scalar. - - If the expression is a literal, it will be returned as is. If it depends - on a table, it will be turned to a scalar subquery. - - Returns - ------- - Scalar - A scalar subquery or a literal - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.examples.penguins.fetch() - >>> max_gentoo_weight = t.filter(t.species == "Gentoo").body_mass_g.max() - >>> light_penguins = t.filter(t.body_mass_g < max_gentoo_weight / 2) - >>> light_penguins.species.value_counts().order_by("species") - ┏━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓ - ┃ species ┃ species_count ┃ - ┡━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩ - │ string │ int64 │ - ├───────────┼───────────────┤ - │ Adelie │ 15 │ - │ Chinstrap │ 2 │ - └───────────┴───────────────┘ - """ - parents = self.op().relations - if parents: - return ops.ScalarSubquery(self.as_table()).to_expr() - else: - return self - - def as_table(self) -> ir.Table: - """Promote the scalar expression to a table. - - Returns - ------- - Table - A table expression - - Examples - -------- - Promote an aggregation to a table - - >>> import ibis - >>> import ibis.expr.types as ir - >>> t = ibis.table(dict(a="str"), name="t") - >>> expr = t.a.length().sum().name("len").as_table() - >>> isinstance(expr, ir.Table) - True - - Promote a literal value to a table - - >>> import ibis.expr.types as ir - >>> lit = ibis.literal(1).name("a").as_table() - >>> isinstance(lit, ir.Table) - True - """ - parents = self.op().relations - - if len(parents) == 0: - return ops.DummyTable({self.get_name(): self}).to_expr() - elif len(parents) == 1: - (parent,) = parents - return parent.to_expr().aggregate(self) - else: - raise com.RelationError( - f"The scalar expression {self} cannot be converted to a " - "table expression because it involves multiple base table " - "references" - ) - - def __deferred_repr__(self): - return f"" - - def _repr_html_(self) -> str | None: - return None - - -@public -class Column(Value, _FixedTextJupyterMixin): - # Higher than numpy & dask objects - __array_priority__ = 20 - - __array_ufunc__ = None - - def __getitem__(self, _): - raise TypeError( - f"{self.__class__.__name__!r} is not subscriptable: " - "see https://ibis-project.org/tutorial/ibis-for-pandas-users/#ibis-for-pandas-users for details." - ) - - def __array__(self, dtype=None): - return self.execute().__array__(dtype) - - def preview( - self, - *, - max_rows: int | None = None, - max_length: int | None = None, - max_string: int | None = None, - max_depth: int | None = None, - console_width: int | float | None = None, - ) -> rich.table.Table: - """Print a subset as a single-column Rich Table. - - This is an explicit version of what you get when you inspect - this object in interactive mode, except with this version you - can pass formatting options. The options are the same as those exposed - in `ibis.options.interactive`. - - Parameters - ---------- - max_rows - Maximum number of rows to display - max_length - Maximum length for pretty-printed arrays and maps. - max_string - Maximum length for pretty-printed strings. - max_depth - Maximum depth for nested data types. - console_width - Width of the console in characters. If not specified, the width - will be inferred from the console. - - Examples - -------- - >>> import ibis - >>> t = ibis.examples.penguins.fetch() - >>> t.island.preview(max_rows=3, max_string=5) # doctest: +SKIP - ┏━━━━━━━━┓ - ┃ island ┃ - ┡━━━━━━━━┩ - │ stri… │ - ├────────┤ - │ Torg… │ - │ Torg… │ - │ Torg… │ - │ … │ - └────────┘ - """ - return to_rich( - self, - max_rows=max_rows, - max_length=max_length, - max_string=max_string, - max_depth=max_depth, - console_width=console_width, - ) - - def __pyarrow_result__( - self, table: pa.Table, data_mapper: type[PyArrowData] | None = None - ) -> pa.Array | pa.ChunkedArray: - if data_mapper is None: - from bigframes_vendored.ibis.formats.pyarrow import ( - PyArrowData as data_mapper, - ) - - return data_mapper.convert_column(table[0], self.type()) - - def __pandas_result__( - self, df: pd.DataFrame, *, schema: sch.Schema | None = None - ) -> pd.Series: - from bigframes_vendored.ibis.formats.pandas import PandasData - - assert len(df.columns) == 1, ( - "more than one column when converting columnar result DataFrame to Series" - ) - # in theory we could use df.iloc[:, 0], but there seems to be a bug in - # older geopandas where df.iloc[:, 0] doesn't return the same kind of - # object as df.loc[:, column_name] when df is a GeoDataFrame - # - # the bug is that iloc[:, 0] returns a bare series whereas - # df.loc[:, column_name] returns the special GeoSeries object. - # - # this bug is fixed in later versions of geopandas - (column,) = df.columns - return PandasData.convert_column( - df.loc[:, column], self.type() if schema is None else schema[column] - ) - - def __polars_result__(self, df: pl.DataFrame) -> pl.Series: - from bigframes_vendored.ibis.formats.polars import PolarsData - - return PolarsData.convert_column(df, self.type()) - - def as_scalar(self) -> Scalar: - """Inform ibis that the expression should be treated as a scalar. - - Creates a scalar subquery from the column expression. Since ibis cannot - be sure that the column expression contains only one value, the column - expression is wrapped in a scalar subquery and treated as a scalar. - - Note that the execution of the scalar subquery will fail if the column - expression contains more than one value. - - Returns - ------- - Scalar - A scalar subquery - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.examples.penguins.fetch() - >>> heavy_gentoo = t.filter(t.species == "Gentoo", t.body_mass_g > 6200) - >>> from_that_island = t.filter(t.island == heavy_gentoo.island.as_scalar()) - >>> from_that_island.species.value_counts().order_by("species") - ┏━━━━━━━━━┳━━━━━━━━━━━━━━━┓ - ┃ species ┃ species_count ┃ - ┡━━━━━━━━━╇━━━━━━━━━━━━━━━┩ - │ string │ int64 │ - ├─────────┼───────────────┤ - │ Adelie │ 44 │ - │ Gentoo │ 124 │ - └─────────┴───────────────┘ - """ - return self.as_table().as_scalar() - - def as_table(self) -> ir.Table: - """Promote the expression to a [Table](./expression-tables.qmd#ibis.expr.types.Table). - - Returns - ------- - Table - A table expression - - Examples - -------- - >>> import ibis - >>> t = ibis.table(dict(a="str"), name="t") - >>> expr = t.a.length().name("len").as_table() - >>> expected = t.select(len=t.a.length()) - >>> expr.equals(expected) - True - """ - parents = self.op().relations - values = {self.get_name(): self} - - if len(parents) == 0: - return ops.DummyTable(values).to_expr() - elif len(parents) == 1: - (parent,) = parents - return parent.to_expr().select(self) - else: - raise com.RelationError( - f"Cannot convert {type(self)} expression involving multiple " - "base table references to a projection" - ) - - def _bind_to_parent_table(self, value) -> Value | None: - """Bind an expr to the parent table of `self`.""" - if value is None: - return None - if isinstance(value, (Deferred, str)) or callable(value): - op = self.op() - if len(op.relations) != 1: - # TODO: I don't think this line can ever be hit by a valid - # expression, since it would require a column expression to - # directly depend on multiple tables. Currently some invalid - # expressions (like t1.a.argmin(t2.b)) aren't caught at - # construction time though, so we keep the check in for now. - raise com.RelationError( - f"Unable to bind `{value!r}` - the current expression" - f"depends on multiple tables." - ) - table = next(iter(op.relations)).to_expr() - - if isinstance(value, str): - return table[value] - elif isinstance(value, Deferred): - return value.resolve(table) - else: - value = value(table) - - if not isinstance(value, Value): - return literal(value) - return value - - def __deferred_repr__(self): - return f"" - - def approx_nunique(self, where: ir.BooleanValue | None = None) -> ir.IntegerScalar: - """Return the approximate number of distinct elements in `self`. - - ::: {.callout-note} - ## The result may or may not be exact - - Whether the result is an approximation depends on the backend. - - ::: {.callout-warning} - ## Do not depend on the results being exact - ::: - - ::: - - Parameters - ---------- - where - Filter in values when `where` is `True` - - Returns - ------- - Scalar - An approximate count of the distinct elements of `self` - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.examples.penguins.fetch() - >>> t.body_mass_g.approx_nunique() - ┌──────────────┐ - │ np.int64(94) │ - └──────────────┘ - >>> t.body_mass_g.approx_nunique(where=t.species == "Adelie") - ┌──────────────┐ - │ np.int64(55) │ - └──────────────┘ - """ - return ops.ApproxCountDistinct( - self, where=self._bind_to_parent_table(where) - ).to_expr() - - def approx_median(self, where: ir.BooleanValue | None = None) -> Scalar: - """Return an approximate of the median of `self`. - - ::: {.callout-note} - ## The result may or may not be exact - - Whether the result is an approximation depends on the backend. - - ::: {.callout-warning} - ## Do not depend on the results being exact - ::: - - ::: - - Parameters - ---------- - where - Filter in values when `where` is `True` - - Returns - ------- - Scalar - An approximation of the median of `self` - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.examples.penguins.fetch() - >>> t.body_mass_g.approx_median() - ┌────────────────┐ - │ np.int64(4030) │ - └────────────────┘ - >>> t.body_mass_g.approx_median(where=t.species == "Chinstrap") - ┌────────────────┐ - │ np.int64(3700) │ - └────────────────┘ - """ - return ops.ApproxMedian(self, where=self._bind_to_parent_table(where)).to_expr() - - def mode(self, where: ir.BooleanValue | None = None) -> Scalar: - """Return the mode of a column. - - Parameters - ---------- - where - Filter in values when `where` is `True` - - Returns - ------- - Scalar - The mode of `self` - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.examples.penguins.fetch() - >>> t.body_mass_g.mode() - ┌────────────────┐ - │ np.int64(3800) │ - └────────────────┘ - >>> t.body_mass_g.mode(where=(t.species == "Gentoo") & (t.sex == "male")) - ┌────────────────┐ - │ np.int64(5550) │ - └────────────────┘ - """ - return ops.Mode(self, where=self._bind_to_parent_table(where)).to_expr() - - def max(self, where: ir.BooleanValue | None = None) -> Scalar: - """Return the maximum of a column. - - Parameters - ---------- - where - Filter in values when `where` is `True` - - Returns - ------- - Scalar - The maximum value in `self` - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.examples.penguins.fetch() - >>> t.body_mass_g.max() - ┌────────────────┐ - │ np.int64(6300) │ - └────────────────┘ - >>> t.body_mass_g.max(where=t.species == "Chinstrap") - ┌────────────────┐ - │ np.int64(4800) │ - └────────────────┘ - """ - return ops.Max(self, where=self._bind_to_parent_table(where)).to_expr() - - def min(self, where: ir.BooleanValue | None = None) -> Scalar: - """Return the minimum of a column. - - Parameters - ---------- - where - Filter in values when `where` is `True` - - Returns - ------- - Scalar - The minimum value in `self` - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.examples.penguins.fetch() - >>> t.body_mass_g.min() - ┌────────────────┐ - │ np.int64(2700) │ - └────────────────┘ - >>> t.body_mass_g.min(where=t.species == "Adelie") - ┌────────────────┐ - │ np.int64(2850) │ - └────────────────┘ - """ - return ops.Min(self, where=self._bind_to_parent_table(where)).to_expr() - - def argmax(self, key: ir.Value, where: ir.BooleanValue | None = None) -> Scalar: - """Return the value of `self` that maximizes `key`. - - Parameters - ---------- - key - Key to use for `max` computation. - where - Keep values when `where` is `True` - - Returns - ------- - Scalar - The value of `self` that maximizes `key` - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.examples.penguins.fetch() - >>> t.species.argmax(t.body_mass_g) - ┌──────────┐ - │ 'Gentoo' │ - └──────────┘ - >>> t.species.argmax(t.body_mass_g, where=t.island == "Dream") - ┌─────────────┐ - │ 'Chinstrap' │ - └─────────────┘ - """ - return ops.ArgMax( - self, - key=self._bind_to_parent_table(key), - where=self._bind_to_parent_table(where), - ).to_expr() - - def argmin(self, key: ir.Value, where: ir.BooleanValue | None = None) -> Scalar: - """Return the value of `self` that minimizes `key`. - - Parameters - ---------- - key - Key to use for `min` computation. - where - Keep values when `where` is `True` - - Returns - ------- - Scalar - The value of `self` that minimizes `key` - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.examples.penguins.fetch() - >>> t.species.argmin(t.body_mass_g) - ┌─────────────┐ - │ 'Chinstrap' │ - └─────────────┘ - - >>> t.species.argmin(t.body_mass_g, where=t.island == "Biscoe") - ┌──────────┐ - │ 'Adelie' │ - └──────────┘ - """ - return ops.ArgMin( - self, - key=self._bind_to_parent_table(key), - where=self._bind_to_parent_table(where), - ).to_expr() - - def median(self, where: ir.BooleanValue | None = None) -> Scalar: - """Return the median of the column. - - Parameters - ---------- - where - Optional boolean expression. If given, only the values where - `where` evaluates to true will be considered for the median. - - Returns - ------- - Scalar - Median of the column - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.examples.penguins.fetch() - - Compute the median of `bill_depth_mm` - - >>> t.bill_depth_mm.median() - ┌──────────────────┐ - │ np.float64(17.3) │ - └──────────────────┘ - >>> t.group_by(t.species).agg(median_bill_depth=t.bill_depth_mm.median()).order_by( - ... ibis.desc("median_bill_depth") - ... ) - ┏━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━┓ - ┃ species ┃ median_bill_depth ┃ - ┡━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━┩ - │ string │ float64 │ - ├───────────┼───────────────────┤ - │ Chinstrap │ 18.45 │ - │ Adelie │ 18.40 │ - │ Gentoo │ 15.00 │ - └───────────┴───────────────────┘ - - In addition to numeric types, any orderable non-numeric types such as - strings and dates work with `median`. - - >>> t.group_by(t.island).agg(median_species=t.species.median()).order_by( - ... ibis.desc("median_species") - ... ) - ┏━━━━━━━━━━━┳━━━━━━━━━━━━━━━━┓ - ┃ island ┃ median_species ┃ - ┡━━━━━━━━━━━╇━━━━━━━━━━━━━━━━┩ - │ string │ string │ - ├───────────┼────────────────┤ - │ Biscoe │ Gentoo │ - │ Dream │ Chinstrap │ - │ Torgersen │ Adelie │ - └───────────┴────────────────┘ - """ - return ops.Median(self, where=self._bind_to_parent_table(where)).to_expr() - - def quantile( - self, - quantile: float | ir.NumericValue | Sequence[ir.NumericValue | float], - where: ir.BooleanValue | None = None, - ) -> Scalar: - """Return value at the given quantile. - - The output of this method is a continuous quantile if the input is - numeric, otherwise the output is a discrete quantile. - - Parameters - ---------- - quantile - `0 <= quantile <= 1`, or an array of such values - indicating the quantile or quantiles to compute - where - Boolean filter for input values - - Returns - ------- - Scalar - Quantile of the input - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.examples.penguins.fetch() - - Compute the 99th percentile of `bill_depth` - - >>> t.bill_depth_mm.quantile(0.99) - ┌──────────────────┐ - │ np.float64(21.1) │ - └──────────────────┘ - >>> t.group_by(t.species).agg(p99_bill_depth=t.bill_depth_mm.quantile(0.99)).order_by( - ... ibis.desc("p99_bill_depth") - ... ) - ┏━━━━━━━━━━━┳━━━━━━━━━━━━━━━━┓ - ┃ species ┃ p99_bill_depth ┃ - ┡━━━━━━━━━━━╇━━━━━━━━━━━━━━━━┩ - │ string │ float64 │ - ├───────────┼────────────────┤ - │ Adelie │ 21.200 │ - │ Chinstrap │ 20.733 │ - │ Gentoo │ 17.256 │ - └───────────┴────────────────┘ - - In addition to numeric types, any orderable non-numeric types such as - strings and dates work with `quantile`. - - Let's compute the 99th percentile of the `species` column - - >>> t.group_by(t.island).agg(p99_species=t.species.quantile(0.99)).order_by( - ... ibis.desc("p99_species") - ... ) - ┏━━━━━━━━━━━┳━━━━━━━━━━━━━┓ - ┃ island ┃ p99_species ┃ - ┡━━━━━━━━━━━╇━━━━━━━━━━━━━┩ - │ string │ string │ - ├───────────┼─────────────┤ - │ Biscoe │ Gentoo │ - │ Dream │ Chinstrap │ - │ Torgersen │ Adelie │ - └───────────┴─────────────┘ - """ - if isinstance(quantile, Sequence): - op = ops.MultiQuantile - else: - op = ops.Quantile - return op(self, quantile, where=self._bind_to_parent_table(where)).to_expr() - - def nunique(self, where: ir.BooleanValue | None = None) -> ir.IntegerScalar: - """Compute the number of distinct rows in an expression. - - Parameters - ---------- - where - Filter expression - - Returns - ------- - IntegerScalar - Number of distinct elements in an expression - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.examples.penguins.fetch() - >>> t.body_mass_g.nunique() - ┌──────────────┐ - │ np.int64(94) │ - └──────────────┘ - >>> t.body_mass_g.nunique(where=t.species == "Adelie") - ┌──────────────┐ - │ np.int64(55) │ - └──────────────┘ - """ - return ops.CountDistinct( - self, where=self._bind_to_parent_table(where) - ).to_expr() - - def topk(self, k: int, by: ir.Value | None = None) -> ir.Table: - """Return a "top k" expression. - - Parameters - ---------- - k - Return this number of rows - by - An expression. Defaults to `count`. - - Returns - ------- - Table - A top-k expression - """ - from bigframes_vendored.ibis.expr.types.relations import bind - - try: - (table,) = self.op().relations - except ValueError: - raise com.IbisTypeError("TopK must depend on exactly one table.") - - table = table.to_expr() - - if by is None: - by = lambda t: t.count() - - (metric,) = bind(table, by) - - return table.aggregate(metric, by=[self]).order_by(metric.desc()).limit(k) - - def arbitrary( - self, where: ir.BooleanValue | None = None, how: Any = None - ) -> Scalar: - """Select an arbitrary value in a column. - - Returns an arbitrary (nondeterministic, backend-specific) value from - the column. The value will be non-NULL, except if the column is empty - or all values are NULL. - - Parameters - ---------- - where - A filter expression - how - DEPRECATED - - Returns - ------- - Scalar - An expression - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.memtable({"a": [1, 2, 2], "b": list("aaa"), "c": [4.0, 4.1, 4.2]}) - >>> t - ┏━━━━━━━┳━━━━━━━━┳━━━━━━━━━┓ - ┃ a ┃ b ┃ c ┃ - ┡━━━━━━━╇━━━━━━━━╇━━━━━━━━━┩ - │ int64 │ string │ float64 │ - ├───────┼────────┼─────────┤ - │ 1 │ a │ 4.0 │ - │ 2 │ a │ 4.1 │ - │ 2 │ a │ 4.2 │ - └───────┴────────┴─────────┘ - >>> t.group_by("a").agg(arb=t.b.arbitrary(), c=t.c.sum()).order_by("a") - ┏━━━━━━━┳━━━━━━━━┳━━━━━━━━━┓ - ┃ a ┃ arb ┃ c ┃ - ┡━━━━━━━╇━━━━━━━━╇━━━━━━━━━┩ - │ int64 │ string │ float64 │ - ├───────┼────────┼─────────┤ - │ 1 │ a │ 4.0 │ - │ 2 │ a │ 8.3 │ - └───────┴────────┴─────────┘ - """ - if how is not None: - warn_deprecated( - name="how", - as_of="9.0", - removed_in="10.0", - instead="call `first` or `last` explicitly", - ) - return ops.Arbitrary(self, where=self._bind_to_parent_table(where)).to_expr() - - def count(self, where: ir.BooleanValue | None = None) -> ir.IntegerScalar: - """Compute the number of rows in an expression. - - Parameters - ---------- - where - Filter expression - - Returns - ------- - IntegerScalar - Number of elements in an expression - """ - return ops.Count(self, where=self._bind_to_parent_table(where)).to_expr() - - def value_counts(self) -> ir.Table: - """Compute a frequency table. - - Returns - ------- - Table - Frequency table expression - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.memtable({"chars": char} for char in "aabcddd") - >>> t - ┏━━━━━━━━┓ - ┃ chars ┃ - ┡━━━━━━━━┩ - │ string │ - ├────────┤ - │ a │ - │ a │ - │ b │ - │ c │ - │ d │ - │ d │ - │ d │ - └────────┘ - >>> t.chars.value_counts().order_by("chars") - ┏━━━━━━━━┳━━━━━━━━━━━━━┓ - ┃ chars ┃ chars_count ┃ - ┡━━━━━━━━╇━━━━━━━━━━━━━┩ - │ string │ int64 │ - ├────────┼─────────────┤ - │ a │ 2 │ - │ b │ 1 │ - │ c │ 1 │ - │ d │ 3 │ - └────────┴─────────────┘ - """ - name = self.get_name() - metric = _.count().name(f"{name}_count") - return self.as_table().group_by(name).aggregate(metric) - - def first(self, where: ir.BooleanValue | None = None) -> Value: - """Return the first value of a column. - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.memtable({"chars": ["a", "b", "c", "d"]}) - >>> t - ┏━━━━━━━━┓ - ┃ chars ┃ - ┡━━━━━━━━┩ - │ string │ - ├────────┤ - │ a │ - │ b │ - │ c │ - │ d │ - └────────┘ - >>> t.chars.first() - ┌─────┐ - │ 'a' │ - └─────┘ - >>> t.chars.first(where=t.chars != "a") - ┌─────┐ - │ 'b' │ - └─────┘ - """ - return ops.First(self, where=self._bind_to_parent_table(where)).to_expr() - - def last(self, where: ir.BooleanValue | None = None) -> Value: - """Return the last value of a column. - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.memtable({"chars": ["a", "b", "c", "d"]}) - >>> t - ┏━━━━━━━━┓ - ┃ chars ┃ - ┡━━━━━━━━┩ - │ string │ - ├────────┤ - │ a │ - │ b │ - │ c │ - │ d │ - └────────┘ - >>> t.chars.last() - ┌─────┐ - │ 'd' │ - └─────┘ - >>> t.chars.last(where=t.chars != "d") - ┌─────┐ - │ 'c' │ - └─────┘ - """ - return ops.Last(self, where=self._bind_to_parent_table(where)).to_expr() - - def rank(self) -> ir.IntegerColumn: - """Compute position of first element within each equal-value group in sorted order. - - Equivalent to SQL's `RANK()` window function. - - Returns - ------- - Int64Column - The min rank - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.memtable({"values": [1, 2, 1, 2, 3, 2]}) - >>> t.mutate(rank=t.values.rank()) - ┏━━━━━━━━┳━━━━━━━┓ - ┃ values ┃ rank ┃ - ┡━━━━━━━━╇━━━━━━━┩ - │ int64 │ int64 │ - ├────────┼───────┤ - │ 1 │ 0 │ - │ 1 │ 0 │ - │ 2 │ 2 │ - │ 2 │ 2 │ - │ 2 │ 2 │ - │ 3 │ 5 │ - └────────┴───────┘ - """ - return bigframes_vendored.ibis.rank().over(order_by=self) - - def dense_rank(self) -> ir.IntegerColumn: - """Position of first element within each group of equal values. - - Values are returned in sorted order and duplicate values are ignored. - - Equivalent to SQL's `DENSE_RANK()`. - - Returns - ------- - IntegerColumn - The rank - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.memtable({"values": [1, 2, 1, 2, 3, 2]}) - >>> t.mutate(rank=t.values.dense_rank()) - ┏━━━━━━━━┳━━━━━━━┓ - ┃ values ┃ rank ┃ - ┡━━━━━━━━╇━━━━━━━┩ - │ int64 │ int64 │ - ├────────┼───────┤ - │ 1 │ 0 │ - │ 1 │ 0 │ - │ 2 │ 1 │ - │ 2 │ 1 │ - │ 2 │ 1 │ - │ 3 │ 2 │ - └────────┴───────┘ - """ - return bigframes_vendored.ibis.dense_rank().over(order_by=self) - - def percent_rank(self) -> Column: - """Return the relative rank of the values in the column.""" - return bigframes_vendored.ibis.percent_rank().over(order_by=self) - - def cume_dist(self) -> Column: - """Return the cumulative distribution over a window.""" - return bigframes_vendored.ibis.cume_dist().over(order_by=self) - - def ntile(self, buckets: int | ir.IntegerValue) -> ir.IntegerColumn: - """Return the integer number of a partitioning of the column values. - - Parameters - ---------- - buckets - Number of buckets to partition into - """ - return bigframes_vendored.ibis.ntile(buckets).over(order_by=self) - - def cummin(self, *, where=None, group_by=None, order_by=None) -> Column: - """Return the cumulative min over a window.""" - return self.min(where=where).over( - bigframes_vendored.ibis.cumulative_window( - group_by=group_by, order_by=order_by - ) - ) - - def cummax(self, *, where=None, group_by=None, order_by=None) -> Column: - """Return the cumulative max over a window.""" - return self.max(where=where).over( - bigframes_vendored.ibis.cumulative_window( - group_by=group_by, order_by=order_by - ) - ) - - def lag( - self, - offset: int | ir.IntegerValue | None = None, - default: Value | None = None, - ) -> Column: - """Return the row located at `offset` rows **before** the current row. - - Parameters - ---------- - offset - Index of row to select - default - Value used if no row exists at `offset` - """ - return ops.Lag(self, offset, default).to_expr() - - def lead( - self, - offset: int | ir.IntegerValue | None = None, - default: Value | None = None, - ) -> Column: - """Return the row located at `offset` rows **after** the current row. - - Parameters - ---------- - offset - Index of row to select - default - Value used if no row exists at `offset` - """ - return ops.Lead(self, offset, default).to_expr() - - def nth(self, n: int | ir.IntegerValue) -> Column: - """Return the `n`th value (0-indexed) over a window. - - `.nth(0)` is equivalent to `.first()`. Negative will result in `NULL`. - If the value of `n` is greater than the number of rows in the window, - `NULL` will be returned. - - Parameters - ---------- - n - Desired rank value - - Returns - ------- - Column - The nth value over a window - """ - return ops.NthValue(self, n).to_expr() - - -@public -class UnknownValue(Value): - pass - - -@public -class UnknownScalar(Scalar): - pass - - -@public -class UnknownColumn(Column): - pass - - -@public -class NullValue(Value): - pass - - -@public -class NullScalar(Scalar, NullValue, Singleton): - pass - - -@public -class NullColumn(Column, NullValue): - pass - - -@public -def null(type: dt.DataType | str | None = None) -> Value: - """Create a NULL scalar. - - `NULL`s with an unspecified type are castable and comparable to values, - but lack datatype-specific methods: - - >>> import ibis - >>> ibis.options.interactive = True - >>> ibis.null().upper() - Traceback (most recent call last): - ... - AttributeError: 'NullScalar' object has no attribute 'upper' - >>> ibis.null(str).upper() - ┌──────┐ - │ None │ - └──────┘ - >>> ibis.null(str).upper().isnull() - ┌──────────┐ - │ np.True_ │ - └──────────┘ - """ - if type is None: - type = dt.null - return ops.Literal(None, type).to_expr() - - -@public -def literal(value: Any, type: dt.DataType | str | None = None) -> Scalar: - """Create a scalar expression from a Python value. - - ::: {.callout-tip} - ## Use specific functions for arrays, structs and maps - - Ibis supports literal construction of arrays using the following - functions: - - 1. [`ibis.array`](./expression-collections.qmd#ibis.array) - 1. [`ibis.struct`](./expression-collections.qmd#ibis.struct) - 1. [`ibis.map`](./expression-collections.qmd#ibis.map) - - Constructing these types using `literal` will be deprecated in a future - release. - ::: - - Parameters - ---------- - value - A Python value - type - An instance of [`DataType`](./datatypes.qmd#ibis.expr.datatypes.DataType) or a string - indicating the ibis type of `value`. This parameter can be used - in cases where ibis's type inference isn't sufficient for discovering - the type of `value`. - - Returns - ------- - Scalar - An expression representing a literal value - - Examples - -------- - Construct an integer literal - - >>> import ibis - >>> x = ibis.literal(42) - >>> x.type() - Int8(nullable=True) - - Construct a `float64` literal from an `int` - - >>> y = ibis.literal(42, type="double") - >>> y.type() - Float64(nullable=True) - - Ibis checks for invalid types - - >>> ibis.literal("foobar", type="int64") # quartodoc: +EXPECTED_FAILURE - Traceback (most recent call last): - ... - TypeError: Value 'foobar' cannot be safely coerced to int64 - """ - if isinstance(value, Expr): - node = value.op() - if not isinstance(node, ops.Literal): - raise TypeError(f"Ibis expression {value!r} is not a Literal") - if type is None or node.dtype.castable(dt.dtype(type)): - return value - else: - raise TypeError( - f"Ibis literal {value!r} cannot be safely coerced to datatype {type}" - ) - - dtype = dt.infer(value) if type is None else dt.dtype(type) - return ops.Literal(value, dtype=dtype).to_expr() - - -public( - ValueExpr=Value, - ScalarExpr=Scalar, - ColumnExpr=Column, - AnyValue=Value, - AnyScalar=Scalar, - AnyColumn=Column, -) diff --git a/third_party/bigframes_vendored/ibis/expr/types/geospatial.py b/third_party/bigframes_vendored/ibis/expr/types/geospatial.py deleted file mode 100644 index 298e74d6de5..00000000000 --- a/third_party/bigframes_vendored/ibis/expr/types/geospatial.py +++ /dev/null @@ -1,1757 +0,0 @@ -# Contains code from https://github.com/ibis-project/ibis/blob/9.2.0/ibis/expr/types/geospatial.py - -from __future__ import annotations - -from typing import TYPE_CHECKING - -import bigframes_vendored.ibis.expr.operations as ops -from bigframes_vendored.ibis.expr.types.numeric import ( - NumericColumn, - NumericScalar, - NumericValue, -) -from public import public - -if TYPE_CHECKING: - import bigframes_vendored.ibis.expr.types as ir - - -@public -class GeoSpatialValue(NumericValue): - def area(self) -> ir.FloatingValue: - """Compute the area of a geospatial value. - - Returns - ------- - FloatingValue - The area of `self` - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.examples.zones.fetch() - >>> t.geom.area() - ┏━━━━━━━━━━━━━━━┓ - ┃ GeoArea(geom) ┃ - ┡━━━━━━━━━━━━━━━┩ - │ float64 │ - ├───────────────┤ - │ 7.903953e+07 │ - │ 1.439095e+08 │ - │ 3.168508e+07 │ - │ 8.023733e+06 │ - │ 5.041488e+07 │ - │ 4.093479e+07 │ - │ 3.934104e+07 │ - │ 2.682802e+06 │ - │ 3.416422e+07 │ - │ 4.404143e+07 │ - │ … │ - └───────────────┘ - """ - return ops.GeoArea(self).to_expr() - - def as_binary(self) -> ir.BinaryValue: - """Get the geometry as well-known bytes (WKB) without the SRID data. - - Returns - ------- - BinaryValue - Binary value - """ - return ops.GeoAsBinary(self).to_expr() - - def as_ewkt(self) -> ir.StringValue: - """Get the geometry as well-known text (WKT) with the SRID data. - - Returns - ------- - StringValue - String value - """ - return ops.GeoAsEWKT(self).to_expr() - - def as_text(self) -> ir.StringValue: - """Get the geometry as well-known text (WKT) without the SRID data. - - Returns - ------- - StringValue - String value - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.examples.zones.fetch() - >>> t.geom.as_text() - ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ GeoAsText(geom) ┃ - ┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩ - │ string │ - ├──────────────────────────────────────────────────────────────────────────────┤ - │ POLYGON ((933100.9183527103 192536.08569720192, 933091.0114800561 192572.17… │ - │ MULTIPOLYGON (((1033269.2435912937 172126.0078125, 1033439.6426391453 17088… │ - │ POLYGON ((1026308.7695066631 256767.6975403726, 1026495.5934945047 256638.6… │ - │ POLYGON ((992073.4667968601 203714.07598876953, 992068.6669922024 203711.50… │ - │ POLYGON ((935843.3104932606 144283.33585065603, 936046.5648079664 144173.41… │ - │ POLYGON ((966568.7466657609 158679.85468779504, 966615.255504474 158662.292… │ - │ POLYGON ((1010804.2179628164 218919.64069513977, 1011049.1648243815 218914.… │ - │ POLYGON ((1005482.2763733566 221686.46616631746, 1005304.8982993066 221499.… │ - │ POLYGON ((1043803.993348822 216615.9250395149, 1043849.7083857208 216473.16… │ - │ POLYGON ((1044355.0717166215 190734.32089698315, 1044612.1216432452 190156.… │ - │ … │ - └──────────────────────────────────────────────────────────────────────────────┘ - """ - return ops.GeoAsText(self).to_expr() - - def as_ewkb(self) -> ir.BinaryValue: - """Get the geometry as well-known bytes (WKB) with the SRID data. - - Returns - ------- - BinaryValue - WKB value - """ - return ops.GeoAsEWKB(self).to_expr() - - def contains(self, right: GeoSpatialValue) -> ir.BooleanValue: - """Check if the geometry contains the `right`. - - Parameters - ---------- - right - Right geometry - - Returns - ------- - BooleanValue - Whether `self` contains `right` - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> import shapely - >>> t = ibis.examples.zones.fetch() - >>> p = shapely.geometry.Point(935996.821, 191376.75) # centroid for zone 1 - >>> plit = ibis.literal(p, "geometry") - >>> t.geom.contains(plit).name("contains") - ┏━━━━━━━━━━┓ - ┃ contains ┃ - ┡━━━━━━━━━━┩ - │ boolean │ - ├──────────┤ - │ True │ - │ False │ - │ False │ - │ False │ - │ False │ - │ False │ - │ False │ - │ False │ - │ False │ - │ False │ - │ … │ - └──────────┘ - """ - return ops.GeoContains(self, right).to_expr() - - def contains_properly(self, right: GeoSpatialValue) -> ir.BooleanValue: - """Check if the first geometry contains the second one. - - Excludes common border points. - - Parameters - ---------- - right - Right geometry - - Returns - ------- - BooleanValue - Whether self contains right excluding border points. - """ - return ops.GeoContainsProperly(self, right).to_expr() - - def covers(self, right: GeoSpatialValue) -> ir.BooleanValue: - """Check if the first geometry covers the second one. - - Parameters - ---------- - right - Right geometry - - Returns - ------- - BooleanValue - Whether `self` covers `right` - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> import shapely - >>> t = ibis.examples.zones.fetch() - - Polygon area center in zone 1 - - >>> z1_ctr_buff = shapely.geometry.Point(935996.821, 191376.75).buffer(10) - >>> z1_ctr_buff_lit = ibis.literal(z1_ctr_buff, "geometry") - >>> t.geom.covers(z1_ctr_buff_lit).name("covers") - ┏━━━━━━━━━┓ - ┃ covers ┃ - ┡━━━━━━━━━┩ - │ boolean │ - ├─────────┤ - │ True │ - │ False │ - │ False │ - │ False │ - │ False │ - │ False │ - │ False │ - │ False │ - │ False │ - │ False │ - │ … │ - └─────────┘ - """ - return ops.GeoCovers(self, right).to_expr() - - def covered_by(self, right: GeoSpatialValue) -> ir.BooleanValue: - """Check if the first geometry is covered by the second one. - - Parameters - ---------- - right - Right geometry - - Returns - ------- - BooleanValue - Whether `self` is covered by `right` - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> import shapely - >>> t = ibis.examples.zones.fetch() - - Polygon area center in zone 1 - - >>> pol_big = shapely.geometry.Point(935996.821, 191376.75).buffer(10000) - >>> pol_big_lit = ibis.literal(pol_big, "geometry") - >>> t.geom.covered_by(pol_big_lit).name("covered_by") - ┏━━━━━━━━━━━━┓ - ┃ covered_by ┃ - ┡━━━━━━━━━━━━┩ - │ boolean │ - ├────────────┤ - │ True │ - │ False │ - │ False │ - │ False │ - │ False │ - │ False │ - │ False │ - │ False │ - │ False │ - │ False │ - │ … │ - └────────────┘ - >>> pol_small = shapely.geometry.Point(935996.821, 191376.75).buffer(100) - >>> pol_small_lit = ibis.literal(pol_small, "geometry") - >>> t.geom.covered_by(pol_small_lit).name("covered_by") - ┏━━━━━━━━━━━━┓ - ┃ covered_by ┃ - ┡━━━━━━━━━━━━┩ - │ boolean │ - ├────────────┤ - │ False │ - │ False │ - │ False │ - │ False │ - │ False │ - │ False │ - │ False │ - │ False │ - │ False │ - │ False │ - │ … │ - └────────────┘ - """ - return ops.GeoCoveredBy(self, right).to_expr() - - def crosses(self, right: GeoSpatialValue) -> ir.BooleanValue: - """Check if the geometries have at least one, but not all, interior points in common. - - Parameters - ---------- - right - Right geometry - - Returns - ------- - BooleanValue - Whether `self` and `right` have at least one common interior point. - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> import shapely - >>> t = ibis.examples.zones.fetch() - - Line from center of zone 1 to center of zone 2 - - >>> line = shapely.LineString([[935996.821, 191376.75], [1031085.719, 164018.754]]) - >>> line_lit = ibis.literal(line, "geometry") - >>> t.geom.crosses(line_lit).name("crosses") - ┏━━━━━━━━━┓ - ┃ crosses ┃ - ┡━━━━━━━━━┩ - │ boolean │ - ├─────────┤ - │ True │ - │ True │ - │ False │ - │ False │ - │ False │ - │ False │ - │ False │ - │ False │ - │ False │ - │ False │ - │ … │ - └─────────┘ - >>> t.filter(t.geom.crosses(line_lit))[["zone", "LocationID"]] - ┏━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━┓ - ┃ zone ┃ LocationID ┃ - ┡━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━┩ - │ string │ int32 │ - ├────────────────────────┼────────────┤ - │ Newark Airport │ 1 │ - │ Jamaica Bay │ 2 │ - │ Canarsie │ 39 │ - │ East Flatbush/Farragut │ 71 │ - │ Erasmus │ 85 │ - │ Flatbush/Ditmas Park │ 89 │ - │ Flatlands │ 91 │ - │ Green-Wood Cemetery │ 111 │ - │ Sunset Park West │ 228 │ - │ Windsor Terrace │ 257 │ - └────────────────────────┴────────────┘ - """ - return ops.GeoCrosses(self, right).to_expr() - - def d_fully_within( - self, - right: GeoSpatialValue, - distance: ir.FloatingValue, - ) -> ir.BooleanValue: - """Check if `self` is entirely within `distance` from `right`. - - Parameters - ---------- - right - Right geometry - distance - Distance to check - - Returns - ------- - BooleanValue - Whether `self` is within a specified distance from `right`. - """ - return ops.GeoDFullyWithin(self, right, distance).to_expr() - - def disjoint(self, right: GeoSpatialValue) -> ir.BooleanValue: - """Check if the geometries have no points in common. - - Parameters - ---------- - right - Right geometry - - Returns - ------- - BooleanValue - Whether `self` and `right` are disjoint - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> import shapely - >>> t = ibis.examples.zones.fetch() - >>> p = shapely.geometry.Point(935996.821, 191376.75) # zone 1 centroid - >>> plit = ibis.literal(p, "geometry") - >>> t.geom.disjoint(plit).name("disjoint") - ┏━━━━━━━━━━┓ - ┃ disjoint ┃ - ┡━━━━━━━━━━┩ - │ boolean │ - ├──────────┤ - │ False │ - │ True │ - │ True │ - │ True │ - │ True │ - │ True │ - │ True │ - │ True │ - │ True │ - │ True │ - │ … │ - └──────────┘ - """ - return ops.GeoDisjoint(self, right).to_expr() - - def d_within( - self, - right: GeoSpatialValue, - distance: ir.FloatingValue, - ) -> ir.BooleanValue: - """Check if `self` is partially within `distance` from `right`. - - Parameters - ---------- - right - Right geometry - distance - Distance to check - - Returns - ------- - BooleanValue - Whether `self` is partially within `distance` from `right`. - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> import shapely - >>> t = ibis.examples.zones.fetch() - >>> penn_station = shapely.geometry.Point(986345.399, 211974.446) - >>> penn_lit = ibis.literal(penn_station, "geometry") - - Check zones within 1000ft of Penn Station centroid - - >>> t.geom.d_within(penn_lit, 1000).name("d_within_1000") - ┏━━━━━━━━━━━━━━━┓ - ┃ d_within_1000 ┃ - ┡━━━━━━━━━━━━━━━┩ - │ boolean │ - ├───────────────┤ - │ False │ - │ False │ - │ False │ - │ False │ - │ False │ - │ False │ - │ False │ - │ False │ - │ False │ - │ False │ - │ … │ - └───────────────┘ - >>> t.filter(t.geom.d_within(penn_lit, 1000))[["zone"]] - ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ zone ┃ - ┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩ - │ string │ - ├──────────────────────────────┤ - │ East Chelsea │ - │ Midtown South │ - │ Penn Station/Madison Sq West │ - └──────────────────────────────┘ - """ - return ops.GeoDWithin(self, right, distance).to_expr() - - def geo_equals(self, right: GeoSpatialValue) -> ir.BooleanValue: - """Check if the geometries are equal. - - Parameters - ---------- - right - Right geometry - - Returns - ------- - BooleanValue - Whether `self` equals `right` - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.examples.zones.fetch() - >>> t.geom.geo_equals(t.geom) - ┏━━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ GeoEquals(geom, geom) ┃ - ┡━━━━━━━━━━━━━━━━━━━━━━━┩ - │ boolean │ - ├───────────────────────┤ - │ True │ - │ True │ - │ True │ - │ True │ - │ True │ - │ True │ - │ True │ - │ True │ - │ True │ - │ True │ - │ … │ - └───────────────────────┘ - """ - return ops.GeoEquals(self, right).to_expr() - - def geometry_n(self, n: int | ir.IntegerValue) -> GeoSpatialValue: - """Get the 1-based Nth geometry of a multi geometry. - - Parameters - ---------- - n - Nth geometry index - - Returns - ------- - GeoSpatialValue - Geometry value - """ - return ops.GeoGeometryN(self, n).to_expr() - - def geometry_type(self) -> ir.StringValue: - """Get the type of a geometry. - - Returns - ------- - StringValue - String representing the type of `self`. - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.examples.zones.fetch() - >>> t.geom.geometry_type() - ┏━━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ GeoGeometryType(geom) ┃ - ┡━━━━━━━━━━━━━━━━━━━━━━━┩ - │ string │ - ├───────────────────────┤ - │ POLYGON │ - │ MULTIPOLYGON │ - │ POLYGON │ - │ POLYGON │ - │ POLYGON │ - │ POLYGON │ - │ POLYGON │ - │ POLYGON │ - │ POLYGON │ - │ POLYGON │ - │ … │ - └───────────────────────┘ - """ - return ops.GeoGeometryType(self).to_expr() - - def intersects(self, right: GeoSpatialValue) -> ir.BooleanValue: - """Check if the geometries share any points. - - Parameters - ---------- - right - Right geometry - - Returns - ------- - BooleanValue - Whether `self` intersects `right` - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> import shapely - >>> t = ibis.examples.zones.fetch() - >>> p = shapely.geometry.Point(935996.821, 191376.75) # zone 1 centroid - >>> plit = ibis.literal(p, "geometry") - >>> t.geom.intersects(plit).name("intersects") - ┏━━━━━━━━━━━━┓ - ┃ intersects ┃ - ┡━━━━━━━━━━━━┩ - │ boolean │ - ├────────────┤ - │ True │ - │ False │ - │ False │ - │ False │ - │ False │ - │ False │ - │ False │ - │ False │ - │ False │ - │ False │ - │ … │ - └────────────┘ - """ - return ops.GeoIntersects(self, right).to_expr() - - def is_valid(self) -> ir.BooleanValue: - """Check if the geometry is valid. - - Returns - ------- - BooleanValue - Whether `self` is valid - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.examples.zones.fetch() - >>> t.geom.is_valid() - ┏━━━━━━━━━━━━━━━━━━┓ - ┃ GeoIsValid(geom) ┃ - ┡━━━━━━━━━━━━━━━━━━┩ - │ boolean │ - ├──────────────────┤ - │ True │ - │ True │ - │ True │ - │ True │ - │ True │ - │ True │ - │ True │ - │ True │ - │ True │ - │ True │ - │ … │ - └──────────────────┘ - """ - return ops.GeoIsValid(self).to_expr() - - def ordering_equals(self, right: GeoSpatialValue) -> ir.BooleanValue: - """Check if two geometries are equal and have the same point ordering. - - Returns true if the two geometries are equal and the coordinates - are in the same order. - - Parameters - ---------- - right - Right geometry - - Returns - ------- - BooleanValue - Whether points and orderings are equal. - """ - return ops.GeoOrderingEquals(self, right).to_expr() - - def overlaps(self, right: GeoSpatialValue) -> ir.BooleanValue: - """Check if the geometries share space, have the same dimension, and are not completely contained by each other. - - Parameters - ---------- - right - Right geometry - - Returns - ------- - BooleanValue - Overlaps indicator - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> import shapely - >>> t = ibis.examples.zones.fetch() - - Polygon center in an edge point of zone 1 - - >>> p_edge_buffer = shapely.geometry.Point(933100.918, 192536.086).buffer(100) - >>> buff_lit = ibis.literal(p_edge_buffer, "geometry") - >>> t.geom.overlaps(buff_lit).name("overlaps") - ┏━━━━━━━━━━┓ - ┃ overlaps ┃ - ┡━━━━━━━━━━┩ - │ boolean │ - ├──────────┤ - │ True │ - │ False │ - │ False │ - │ False │ - │ False │ - │ False │ - │ False │ - │ False │ - │ False │ - │ False │ - │ … │ - └──────────┘ - """ - return ops.GeoOverlaps(self, right).to_expr() - - def touches(self, right: GeoSpatialValue) -> ir.BooleanValue: - """Check if the geometries have at least one point in common, but do not intersect. - - Parameters - ---------- - right - Right geometry - - Returns - ------- - BooleanValue - Whether self and right are touching - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> import shapely - >>> t = ibis.examples.zones.fetch() - - Edge point of zone 1 - - >>> p_edge = shapely.geometry.Point(933100.9183527103, 192536.08569720192) - >>> p_edge_lit = ibis.literal(p_edge, "geometry") - >>> t.geom.touches(p_edge_lit).name("touches") - ┏━━━━━━━━━┓ - ┃ touches ┃ - ┡━━━━━━━━━┩ - │ boolean │ - ├─────────┤ - │ True │ - │ False │ - │ False │ - │ False │ - │ False │ - │ False │ - │ False │ - │ False │ - │ False │ - │ False │ - │ … │ - └─────────┘ - """ - return ops.GeoTouches(self, right).to_expr() - - def distance(self, right: GeoSpatialValue) -> ir.FloatingValue: - """Compute the distance between two geospatial expressions. - - Parameters - ---------- - right - Right geometry or geography - - Returns - ------- - FloatingValue - Distance between `self` and `right` - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> import shapely - >>> t = ibis.examples.zones.fetch() - - Penn station zone centroid - - >>> penn_station = shapely.geometry.Point(986345.399, 211974.446) - >>> penn_lit = ibis.literal(penn_station, "geometry") - >>> t.geom.distance(penn_lit).name("distance_penn") - ┏━━━━━━━━━━━━━━━┓ - ┃ distance_penn ┃ - ┡━━━━━━━━━━━━━━━┩ - │ float64 │ - ├───────────────┤ - │ 47224.139856 │ - │ 55992.665470 │ - │ 54850.880098 │ - │ 8011.846870 │ - │ 84371.995209 │ - │ 54196.904809 │ - │ 15965.509896 │ - │ 20566.442476 │ - │ 54070.543584 │ - │ 56994.826531 │ - │ … │ - └───────────────┘ - """ - return ops.GeoDistance(self, right).to_expr() - - def length(self) -> ir.FloatingValue: - """Compute the length of a geospatial expression. - - Returns zero for polygons. - - Returns - ------- - FloatingValue - Length of `self` - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> con = ibis.get_backend() - >>> con.load_extension("spatial") - >>> import shapely - >>> line = shapely.LineString([[0, 0], [1, 0], [1, 1]]) - >>> line_lit = ibis.literal(line, type="geometry") - >>> line_lit.length() - ┌─────────────────┐ - │ np.float64(2.0) │ - └─────────────────┘ - >>> t = ibis.examples.zones.fetch() - >>> t.geom.length() - ┏━━━━━━━━━━━━━━━━━┓ - ┃ GeoLength(geom) ┃ - ┡━━━━━━━━━━━━━━━━━┩ - │ float64 │ - ├─────────────────┤ - │ 0.0 │ - │ 0.0 │ - │ 0.0 │ - │ 0.0 │ - │ 0.0 │ - │ 0.0 │ - │ 0.0 │ - │ 0.0 │ - │ 0.0 │ - │ 0.0 │ - │ … │ - └─────────────────┘ - """ - return ops.GeoLength(self).to_expr() - - def perimeter(self) -> ir.FloatingValue: - """Compute the perimeter of a geospatial expression. - - Returns - ------- - FloatingValue - Perimeter of `self` - """ - return ops.GeoPerimeter(self).to_expr() - - def max_distance(self, right: GeoSpatialValue) -> ir.FloatingValue: - """Returns the 2-dimensional max distance between two geometries in projected units. - - If `self` and `right` are the same geometry the function will return - the distance between the two vertices most far from each other in that - geometry. - - Parameters - ---------- - right - Right geometry - - Returns - ------- - FloatingValue - Maximum distance - """ - return ops.GeoMaxDistance(self, right).to_expr() - - def union(self, right: GeoSpatialValue) -> GeoSpatialValue: - """Merge two geometries into a union geometry. - - Returns the pointwise union of the two geometries. - - Parameters - ---------- - right - Right geometry - - Returns - ------- - GeoSpatialValue - Union of geometries - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> import shapely - >>> t = ibis.examples.zones.fetch() - - Penn station zone centroid - - >>> penn_station = shapely.geometry.Point(986345.399, 211974.446) - >>> penn_lit = ibis.literal(penn_station, "geometry") - >>> t.geom.centroid().union(penn_lit).name("union_centroid_penn") - ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ union_centroid_penn ┃ - ┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩ - │ geospatial:geometry │ - ├──────────────────────────────────────────────────────────────┤ - │ │ - │ │ - │ │ - │ │ - │ │ - │ │ - │ │ - │ │ - │ │ - │ │ - │ … │ - └──────────────────────────────────────────────────────────────┘ - """ - return ops.GeoUnion(self, right).to_expr() - - def x(self) -> ir.FloatingValue: - """Return the X coordinate of `self`, or NULL if not available. - - Input must be a point. - - Returns - ------- - FloatingValue - X coordinate of `self` - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.examples.zones.fetch() - >>> t.geom.centroid().x() - ┏━━━━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ GeoX(GeoCentroid(geom)) ┃ - ┡━━━━━━━━━━━━━━━━━━━━━━━━━┩ - │ float64 │ - ├─────────────────────────┤ - │ 9.359968e+05 │ - │ 1.031086e+06 │ - │ 1.026453e+06 │ - │ 9.906340e+05 │ - │ 9.318714e+05 │ - │ 9.643197e+05 │ - │ 1.006497e+06 │ - │ 1.005552e+06 │ - │ 1.043003e+06 │ - │ 1.042224e+06 │ - │ … │ - └─────────────────────────┘ - """ - return ops.GeoX(self).to_expr() - - def y(self) -> ir.FloatingValue: - """Return the Y coordinate of `self`, or NULL if not available. - - Input must be a point. - - Returns - ------- - FloatingValue - Y coordinate of `self` - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.examples.zones.fetch() - >>> t.geom.centroid().y() - ┏━━━━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ GeoY(GeoCentroid(geom)) ┃ - ┡━━━━━━━━━━━━━━━━━━━━━━━━━┩ - │ float64 │ - ├─────────────────────────┤ - │ 191376.749531 │ - │ 164018.754403 │ - │ 254265.478659 │ - │ 202959.782391 │ - │ 140681.351376 │ - │ 157998.935612 │ - │ 216719.218169 │ - │ 222936.087552 │ - │ 212969.849014 │ - │ 186706.496469 │ - │ … │ - └─────────────────────────┘ - """ - return ops.GeoY(self).to_expr() - - def x_min(self) -> ir.FloatingValue: - """Return the X minima of a geometry. - - Returns - ------- - FloatingValue - X minima - """ - return ops.GeoXMin(self).to_expr() - - def x_max(self) -> ir.FloatingValue: - """Return the X maxima of a geometry. - - Returns - ------- - FloatingValue - X maxima - """ - return ops.GeoXMax(self).to_expr() - - def y_min(self) -> ir.FloatingValue: - """Return the Y minima of a geometry. - - Returns - ------- - FloatingValue - Y minima - """ - return ops.GeoYMin(self).to_expr() - - def y_max(self) -> ir.FloatingValue: - """Return the Y maxima of a geometry. - - Returns - ------- - FloatingValue - Y maxima - """ - return ops.GeoYMax(self).to_expr() - - def start_point(self) -> PointValue: - """Return the first point of a `LINESTRING` geometry as a `POINT`. - - Return `NULL` if the input parameter is not a `LINESTRING` - - Returns - ------- - PointValue - Start point - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> con = ibis.get_backend() - >>> con.load_extension("spatial") - >>> import shapely - >>> line = shapely.LineString([[0, 0], [1, 0], [1, 1]]) - >>> line_lit = ibis.literal(line, type="geometry") - >>> line_lit.start_point() - ┌───────────────┐ - │ │ - └───────────────┘ - """ - return ops.GeoStartPoint(self).to_expr() - - def end_point(self) -> PointValue: - """Return the last point of a `LINESTRING` geometry as a `POINT`. - - Return `NULL` if the input parameter is not a `LINESTRING` - - Returns - ------- - PointValue - End point - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> con = ibis.get_backend() - >>> con.load_extension("spatial") - >>> import shapely - >>> line = shapely.LineString([[0, 0], [1, 0], [1, 1]]) - >>> line_lit = ibis.literal(line, type="geometry") - >>> line_lit.end_point() - ┌───────────────┐ - │ │ - └───────────────┘ - """ - return ops.GeoEndPoint(self).to_expr() - - def point_n(self, n: ir.IntegerValue) -> PointValue: - """Return the Nth point in a single linestring in the geometry. - - Negative values are counted backwards from the end of the LineString, - so that -1 is the last point. Returns NULL if there is no linestring in - the geometry. - - Parameters - ---------- - n - Nth point index - - Returns - ------- - PointValue - Nth point in `self` - """ - return ops.GeoPointN(self, n).to_expr() - - def n_points(self) -> ir.IntegerValue: - """Return the number of points in a geometry. Works for all geometries. - - Returns - ------- - IntegerValue - Number of points - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.examples.zones.fetch() - >>> t.geom.n_points() - ┏━━━━━━━━━━━━━━━━━━┓ - ┃ GeoNPoints(geom) ┃ - ┡━━━━━━━━━━━━━━━━━━┩ - │ int64 │ - ├──────────────────┤ - │ 232 │ - │ 2954 │ - │ 121 │ - │ 88 │ - │ 170 │ - │ 277 │ - │ 182 │ - │ 40 │ - │ 189 │ - │ 157 │ - │ … │ - └──────────────────┘ - """ - return ops.GeoNPoints(self).to_expr() - - def n_rings(self) -> ir.IntegerValue: - """Return the number of rings for polygons and multipolygons. - - Outer rings are counted as well. - - Returns - ------- - IntegerValue - Number of rings - """ - return ops.GeoNRings(self).to_expr() - - def srid(self) -> ir.IntegerValue: - """Return the spatial reference identifier for the ST_Geometry. - - Returns - ------- - IntegerValue - SRID - """ - return ops.GeoSRID(self).to_expr() - - def set_srid(self, srid: ir.IntegerValue) -> GeoSpatialValue: - """Set the spatial reference identifier for the `ST_Geometry`. - - Parameters - ---------- - srid - SRID integer value - - Returns - ------- - GeoSpatialValue - `self` with SRID set to `srid` - """ - return ops.GeoSetSRID(self, srid=srid).to_expr() - - def buffer(self, radius: float | ir.FloatingValue) -> GeoSpatialValue: - """Return all points whose distance from this geometry is less than or equal to `radius`. - - Calculations are in the Spatial Reference System of this Geometry. - - Parameters - ---------- - radius - Floating expression - - Returns - ------- - GeoSpatialValue - Geometry expression - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.examples.zones.fetch() - >>> p = t.x_cent.point(t.y_cent) - >>> p.buffer(10) # note buff.area.mean() ~ pi * r^2 - ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ GeoBuffer(GeoPoint(x_cent, y_cent), 10.0) ┃ - ┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩ - │ geospatial:geometry │ - ├──────────────────────────────────────────────────────────────────────────────┤ - │ │ - │ │ - │ │ - │ │ - │ │ - │ │ - │ │ - │ │ - │ │ - │ │ - │ … │ - └──────────────────────────────────────────────────────────────────────────────┘ - """ - return ops.GeoBuffer(self, radius=radius).to_expr() - - def centroid(self) -> PointValue: - """Returns the centroid of the geometry. - - Returns - ------- - PointValue - The centroid - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.examples.zones.fetch() - >>> t.geom.centroid() - ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ GeoCentroid(geom) ┃ - ┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩ - │ point:geometry │ - ├──────────────────────────────────┤ - │ │ - │ │ - │ │ - │ │ - │ │ - │ │ - │ │ - │ │ - │ │ - │ │ - │ … │ - └──────────────────────────────────┘ - """ - return ops.GeoCentroid(self).to_expr() - - def envelope(self) -> ir.PolygonValue: - """Returns a geometry representing the bounding box of `self`. - - Returns - ------- - PolygonValue - A polygon - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.examples.zones.fetch() - >>> t.geom.envelope() - ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ GeoEnvelope(geom) ┃ - ┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩ - │ polygon:geometry │ - ├──────────────────────────────────────────────────────────────────────────────┤ - │ │ - │ │ - │ │ - │ │ - │ │ - │ │ - │ │ - │ │ - │ │ - │ │ - │ … │ - └──────────────────────────────────────────────────────────────────────────────┘ - """ - return ops.GeoEnvelope(self).to_expr() - - def within(self, right: GeoSpatialValue) -> ir.BooleanValue: - """Check if the first geometry is completely inside of the second. - - Parameters - ---------- - right - Right geometry - - Returns - ------- - BooleanValue - Whether `self` is in `right`. - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> import shapely - >>> t = ibis.examples.zones.fetch() - >>> penn_station_buff = shapely.geometry.Point(986345.399, 211974.446).buffer(5000) - >>> penn_lit = ibis.literal(penn_station_buff, "geometry") - >>> t.filter(t.geom.within(penn_lit))["zone"] - ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ zone ┃ - ┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩ - │ string │ - ├──────────────────────────────┤ - │ East Chelsea │ - │ Flatiron │ - │ Garment District │ - │ Midtown South │ - │ Penn Station/Madison Sq West │ - └──────────────────────────────┘ - """ - return ops.GeoWithin(self, right).to_expr() - - def azimuth(self, right: GeoSpatialValue) -> ir.FloatingValue: - """Return the angle in radians from the horizontal of the vector defined by the inputs. - - Angle is computed clockwise from down-to-up on the clock: 12=0; 3=PI/2; 6=PI; 9=3PI/2. - - Parameters - ---------- - right - Right geometry - - Returns - ------- - FloatingValue - azimuth - """ - return ops.GeoAzimuth(self, right).to_expr() - - def intersection(self, right: GeoSpatialValue) -> GeoSpatialValue: - """Return the intersection of two geometries. - - Parameters - ---------- - right - Right geometry - - Returns - ------- - GeoSpatialValue - Intersection of `self` and `right` - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.examples.zones.fetch() - >>> t.geom.intersection(t.geom.centroid()) - ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ GeoIntersection(geom, GeoCentroid(geom)) ┃ - ┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩ - │ geospatial:geometry │ - ├──────────────────────────────────────────┤ - │ │ - │ │ - │ │ - │ │ - │ │ - │ │ - │ │ - │ │ - │ │ - │ │ - │ … │ - └──────────────────────────────────────────┘ - """ - return ops.GeoIntersection(self, right).to_expr() - - def difference(self, right: GeoSpatialValue) -> GeoSpatialValue: - """Return the difference of two geometries. - - Parameters - ---------- - right - Right geometry - - Returns - ------- - GeoSpatialValue - Difference of `self` and `right` - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.examples.zones.fetch() - >>> t.geom.difference(t.geom.centroid()) - ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ GeoDifference(geom, GeoCentroid(geom)) ┃ - ┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩ - │ geospatial:geometry │ - ├──────────────────────────────────────────────────────────────────────────────┤ - │ │ - │ │ - │ │ - │ │ - │ │ - │ │ - │ │ - │ │ - │ │ - │ │ - │ … │ - └──────────────────────────────────────────────────────────────────────────────┘ - """ - return ops.GeoDifference(self, right).to_expr() - - def simplify( - self, - tolerance: ir.FloatingValue, - preserve_collapsed: ir.BooleanValue, - ) -> GeoSpatialValue: - """Simplify a given geometry. - - Parameters - ---------- - tolerance - Tolerance - preserve_collapsed - Whether to preserve collapsed geometries - - Returns - ------- - GeoSpatialValue - Simplified geometry - """ - return ops.GeoSimplify(self, tolerance, preserve_collapsed).to_expr() - - def transform(self, srid: ir.IntegerValue) -> GeoSpatialValue: - """Transform a geometry into a new SRID. - - Parameters - ---------- - srid - Integer expression - - Returns - ------- - GeoSpatialValue - Transformed geometry - """ - return ops.GeoTransform(self, srid).to_expr() - - def convert( - self, source: ir.StringValue, target: ir.StringValue | ir.IntegerValue - ) -> GeoSpatialValue: - """Transform a geometry into a new SRID (CRS). - - Coordinates are assumed to always be XY (Longitude-Latitude). - - Parameters - ---------- - source - CRS/SRID of input geometry - target - Target CRS/SRID - - Returns - ------- - GeoSpatialValue - Transformed geometry - - See Also - -------- - [`flip_coordinates`](#ibis.expr.types.geospatial.GeoSpatialValue.flip_coordinates) - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.examples.zones.fetch() - - Data is originally in epsg:2263 - - >>> t.geom.convert("EPSG:2263", "EPSG:4326") - ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ GeoConvert(geom) ┃ - ┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩ - │ geospatial:geometry │ - ├──────────────────────────────────────────────────────────────────────────────┤ - │ │ - │ │ - │ │ - │ │ - │ │ - │ │ - │ │ - │ │ - │ │ - │ │ - │ … │ - └──────────────────────────────────────────────────────────────────────────────┘ - """ - return ops.GeoConvert(self, source, target).to_expr() - - def line_locate_point(self, right: PointValue) -> ir.FloatingValue: - """Locate the distance a point falls along the length of a line. - - Returns a float between zero and one representing the location of the - closest point on the linestring to the given point, as a fraction of - the total 2d line length. - - Parameters - ---------- - right - Point geometry - - Returns - ------- - FloatingValue - Fraction of the total line length - """ - return ops.GeoLineLocatePoint(self, right).to_expr() - - def line_substring( - self, start: ir.FloatingValue, end: ir.FloatingValue - ) -> ir.LineStringValue: - """Clip a substring from a LineString. - - Returns a linestring that is a substring of the input one, starting - and ending at the given fractions of the total 2d length. The second - and third arguments are floating point values between zero and one. - This only works with linestrings. - - Parameters - ---------- - start - Start value - end - End value - - Returns - ------- - LineStringValue - Clipped linestring - """ - return ops.GeoLineSubstring(self, start, end).to_expr() - - def line_merge(self) -> ir.LineStringValue: - """Merge a `MultiLineString` into a `LineString`. - - Returns a (set of) LineString(s) formed by sewing together the - constituent line work of a MultiLineString. If a geometry other than - a LineString or MultiLineString is given, this will return an empty - geometry collection. - - Returns - ------- - GeoSpatialValue - Merged linestrings - """ - return ops.GeoLineMerge(self).to_expr() - - def flip_coordinates(self) -> GeoSpatialValue: - """Flip coordinates of a geometry so that x = y and y = x. - - Returns - ------- - GeoSpatialValue - New geometry with flipped coordinates - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.examples.zones.fetch() - >>> t.geom.centroid().flip_coordinates() - ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ GeoFlipCoordinates(GeoCentroid(geom)) ┃ - ┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩ - │ geospatial:geometry │ - ├───────────────────────────────────────┤ - │ │ - │ │ - │ │ - │ │ - │ │ - │ │ - │ │ - │ │ - │ │ - │ │ - │ … │ - └───────────────────────────────────────┘ - - """ - return ops.GeoFlipCoordinates(self).to_expr() - - -@public -class GeoSpatialScalar(NumericScalar, GeoSpatialValue): - pass - - -@public -class GeoSpatialColumn(NumericColumn, GeoSpatialValue): - def unary_union( - self, where: bool | ir.BooleanValue | None = None - ) -> ir.GeoSpatialScalar: - """Aggregate a set of geometries into a union. - - This corresponds to the aggregate version of the union. - We give it a different name (following the corresponding method - in GeoPandas) to avoid name conflicts with the non-aggregate version. - - Parameters - ---------- - where - Filter expression - - Returns - ------- - GeoSpatialScalar - Union of geometries - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.examples.zones.fetch() - >>> t.geom.unary_union() - ┌──────────────────────────────────────────────────────────────────────────────┐ - │ │ - └──────────────────────────────────────────────────────────────────────────────┘ - - """ - return ops.GeoUnaryUnion(self, where=where).to_expr() - - -@public -class PointValue(GeoSpatialValue): - pass - - -@public -class PointScalar(GeoSpatialScalar, PointValue): - pass - - -@public -class PointColumn(GeoSpatialColumn, PointValue): - pass - - -@public -class LineStringValue(GeoSpatialValue): - pass - - -@public -class LineStringScalar(GeoSpatialScalar, LineStringValue): - pass - - -@public -class LineStringColumn(GeoSpatialColumn, LineStringValue): - pass - - -@public -class PolygonValue(GeoSpatialValue): - pass - - -@public -class PolygonScalar(GeoSpatialScalar, PolygonValue): - pass - - -@public -class PolygonColumn(GeoSpatialColumn, PolygonValue): - pass - - -@public -class MultiLineStringValue(GeoSpatialValue): - pass - - -@public -class MultiLineStringScalar(GeoSpatialScalar, MultiLineStringValue): - pass - - -@public -class MultiLineStringColumn(GeoSpatialColumn, MultiLineStringValue): - pass - - -@public -class MultiPointValue(GeoSpatialValue): - pass - - -@public -class MultiPointScalar(GeoSpatialScalar, MultiPointValue): - pass - - -@public -class MultiPointColumn(GeoSpatialColumn, MultiPointValue): - pass - - -@public -class MultiPolygonValue(GeoSpatialValue): - pass - - -@public -class MultiPolygonScalar(GeoSpatialScalar, MultiPolygonValue): - pass - - -@public -class MultiPolygonColumn(GeoSpatialColumn, MultiPolygonValue): - pass diff --git a/third_party/bigframes_vendored/ibis/expr/types/groupby.py b/third_party/bigframes_vendored/ibis/expr/types/groupby.py deleted file mode 100644 index 369eb8a0a82..00000000000 --- a/third_party/bigframes_vendored/ibis/expr/types/groupby.py +++ /dev/null @@ -1,296 +0,0 @@ -# Contains code from https://github.com/ibis-project/ibis/blob/9.2.0/ibis/expr/types/groupby.py - -# Copyright 2014 Cloudera Inc. -# -# 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. - -"""User API for grouping operations.""" - -from __future__ import annotations - -from typing import TYPE_CHECKING, Annotated - -import bigframes_vendored.ibis -import bigframes_vendored.ibis.expr.datatypes as dt -import bigframes_vendored.ibis.expr.operations as ops -import bigframes_vendored.ibis.expr.types as ir -from bigframes_vendored.ibis.common.grounds import Concrete -from bigframes_vendored.ibis.common.patterns import Length # noqa: TCH001 -from bigframes_vendored.ibis.common.typing import VarTuple # noqa: TCH001 -from bigframes_vendored.ibis.expr.rewrites import rewrite_window_input -from public import public - -if TYPE_CHECKING: - from collections.abc import Sequence - - -@public -class GroupedTable(Concrete): - """An intermediate table expression to hold grouping information.""" - - table: ops.Relation - groupings: Annotated[VarTuple[ops.Value], Length(at_least=1)] - orderings: VarTuple[ops.SortKey] = () - havings: VarTuple[ops.Value[dt.Boolean]] = () - - def __getitem__(self, args): - # Shortcut for projection with window functions - return self.select(*args) - - def __getattr__(self, attr): - try: - field = getattr(self.table.to_expr(), attr) - except AttributeError as e: - raise AttributeError(f"GroupedTable has no attribute {attr}") from e - - if isinstance(field, ir.NumericValue): - return GroupedNumbers(field, self) - else: - return GroupedArray(field, self) - - def aggregate(self, *metrics, **kwds) -> ir.Table: - """Compute aggregates over a group by.""" - metrics = self.table.to_expr().bind(*metrics, **kwds) - return self.table.to_expr().aggregate( - metrics, by=self.groupings, having=self.havings - ) - - agg = aggregate - - def having(self, *predicates: ir.BooleanScalar) -> GroupedTable: - """Add a post-aggregation result filter `expr`. - - ::: {.callout-warning} - ## Expressions like `x is None` return `bool` and **will not** generate a SQL comparison to `NULL` - ::: - - Parameters - ---------- - predicates - Expressions that filters based on an aggregate value. - - Returns - ------- - GroupedTable - A grouped table expression - """ - table = self.table.to_expr() - havings = table.bind(*predicates) - return self.copy(havings=self.havings + havings) - - def order_by(self, *by: ir.Value) -> GroupedTable: - """Sort a grouped table expression by `expr`. - - Notes - ----- - This API call is ignored in aggregations. - - Parameters - ---------- - by - Expressions to order the results by - - Returns - ------- - GroupedTable - A sorted grouped GroupedTable - """ - table = self.table.to_expr() - orderings = table.bind(*by) - return self.copy(orderings=self.orderings + orderings) - - def mutate( - self, *exprs: ir.Value | Sequence[ir.Value], **kwexprs: ir.Value - ) -> ir.Table: - """Return a table projection with window functions applied. - - Any arguments can be functions. - - Parameters - ---------- - exprs - List of expressions - kwexprs - Expressions - - Examples - -------- - >>> import ibis - >>> import ibis.selectors as s - >>> ibis.options.interactive = True - >>> t = ibis.examples.penguins.fetch() - >>> t - ┏━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━┳━━━┓ - ┃ species ┃ island ┃ bill_length_mm ┃ bill_depth_mm ┃ flipper_length_mm ┃ … ┃ - ┡━━━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━╇━━━┩ - │ string │ string │ float64 │ float64 │ int64 │ … │ - ├─────────┼───────────┼────────────────┼───────────────┼───────────────────┼───┤ - │ Adelie │ Torgersen │ 39.1 │ 18.7 │ 181 │ … │ - │ Adelie │ Torgersen │ 39.5 │ 17.4 │ 186 │ … │ - │ Adelie │ Torgersen │ 40.3 │ 18.0 │ 195 │ … │ - │ Adelie │ Torgersen │ NULL │ NULL │ NULL │ … │ - │ Adelie │ Torgersen │ 36.7 │ 19.3 │ 193 │ … │ - │ Adelie │ Torgersen │ 39.3 │ 20.6 │ 190 │ … │ - │ Adelie │ Torgersen │ 38.9 │ 17.8 │ 181 │ … │ - │ Adelie │ Torgersen │ 39.2 │ 19.6 │ 195 │ … │ - │ Adelie │ Torgersen │ 34.1 │ 18.1 │ 193 │ … │ - │ Adelie │ Torgersen │ 42.0 │ 20.2 │ 190 │ … │ - │ … │ … │ … │ … │ … │ … │ - └─────────┴───────────┴────────────────┴───────────────┴───────────────────┴───┘ - >>> ( - ... t.select("species", "bill_length_mm") - ... .group_by("species") - ... .mutate(centered_bill_len=ibis._.bill_length_mm - ibis._.bill_length_mm.mean()) - ... .order_by(s.all()) - ... ) - ┏━━━━━━━━━┳━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━┓ - ┃ species ┃ bill_length_mm ┃ centered_bill_len ┃ - ┡━━━━━━━━━╇━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━┩ - │ string │ float64 │ float64 │ - ├─────────┼────────────────┼───────────────────┤ - │ Adelie │ 32.1 │ -6.691391 │ - │ Adelie │ 33.1 │ -5.691391 │ - │ Adelie │ 33.5 │ -5.291391 │ - │ Adelie │ 34.0 │ -4.791391 │ - │ Adelie │ 34.1 │ -4.691391 │ - │ Adelie │ 34.4 │ -4.391391 │ - │ Adelie │ 34.5 │ -4.291391 │ - │ Adelie │ 34.6 │ -4.191391 │ - │ Adelie │ 34.6 │ -4.191391 │ - │ Adelie │ 35.0 │ -3.791391 │ - │ … │ … │ … │ - └─────────┴────────────────┴───────────────────┘ - - Returns - ------- - Table - A table expression with window functions applied - """ - exprs = self._selectables(*exprs, **kwexprs) - return self.table.to_expr().mutate(exprs) - - def select(self, *exprs, **kwexprs) -> ir.Table: - """Project new columns out of the grouped table. - - See Also - -------- - [`GroupedTable.mutate`](#ibis.expr.types.groupby.GroupedTable.mutate) - """ - exprs = self._selectables(*exprs, **kwexprs) - return self.table.to_expr().select(exprs) - - def _selectables(self, *exprs, **kwexprs): - """Project new columns out of the grouped table. - - See Also - -------- - [`GroupedTable.mutate`](#ibis.expr.types.groupby.GroupedTable.mutate) - """ - table = self.table.to_expr() - values = table.bind(*exprs, **kwexprs) - window = bigframes_vendored.ibis.window( - group_by=self.groupings, order_by=self.orderings - ) - return [rewrite_window_input(expr.op(), window).to_expr() for expr in values] - - projection = select - - def over( - self, - window=None, - *, - rows=None, - range=None, - group_by=None, - order_by=None, - ) -> GroupedTable: - """Apply a window over the input expressions. - - Parameters - ---------- - window - Window to add to the input - rows - Whether to use the `ROWS` window clause - range - Whether to use the `RANGE` window clause - group_by - Grouping key - order_by - Ordering key - - Returns - ------- - GroupedTable - A new grouped table expression - """ - if window is None: - window = bigframes_vendored.ibis.window( - rows=rows, - range=range, - group_by=group_by, - order_by=order_by, - ) - - return self.__class__( - self.table, - self.by, - having=self._having, - order_by=self._order_by, - window=window, - ) - - def count(self) -> ir.Table: - """Computing the number of rows per group. - - Returns - ------- - Table - The aggregated table - """ - table = self.table.to_expr() - return table.aggregate(table.count(), by=self.groupings, having=self.havings) - - size = count - - -def _group_agg_dispatch(name): - def wrapper(self, *args, **kwargs): - f = getattr(self.arr, name) - metric = f(*args, **kwargs) - alias = f"{name}({self.arr.get_name()})" - return self.parent.aggregate(metric.name(alias)) - - wrapper.__name__ = name - return wrapper - - -@public -class GroupedArray: - def __init__(self, arr, parent): - self.arr = arr - self.parent = parent - - count = _group_agg_dispatch("count") - size = count - min = _group_agg_dispatch("min") - max = _group_agg_dispatch("max") - approx_nunique = _group_agg_dispatch("approx_nunique") - approx_median = _group_agg_dispatch("approx_median") - group_concat = _group_agg_dispatch("group_concat") - - -@public -class GroupedNumbers(GroupedArray): - mean = _group_agg_dispatch("mean") - sum = _group_agg_dispatch("sum") diff --git a/third_party/bigframes_vendored/ibis/expr/types/joins.py b/third_party/bigframes_vendored/ibis/expr/types/joins.py deleted file mode 100644 index 62c4a334fb5..00000000000 --- a/third_party/bigframes_vendored/ibis/expr/types/joins.py +++ /dev/null @@ -1,440 +0,0 @@ -# Contains code from https://github.com/ibis-project/ibis/blob/9.2.0/ibis/expr/types/joins.py - -from __future__ import annotations - -import functools -from typing import TYPE_CHECKING, Any - -import bigframes_vendored.ibis -import bigframes_vendored.ibis.expr.operations as ops -from bigframes_vendored.ibis import util -from bigframes_vendored.ibis.common.deferred import Deferred -from bigframes_vendored.ibis.common.egraph import DisjointSet -from bigframes_vendored.ibis.common.exceptions import ( - ExpressionError, - IbisInputError, - InputTypeError, - IntegrityError, -) -from bigframes_vendored.ibis.expr.rewrites import flatten_predicates, peel_join_field -from bigframes_vendored.ibis.expr.types.generic import Value -from bigframes_vendored.ibis.expr.types.relations import ( - DerefMap, - Table, - bind, - unwrap_aliases, -) -from public import public - -if TYPE_CHECKING: - from collections.abc import Sequence - - from bigframes_vendored.ibis.expr.operations.relations import JoinKind - - -def coerce_to_table(data): - try: - import pandas as pd - except ImportError: - pass - else: - if isinstance(data, pd.DataFrame): - return bigframes_vendored.ibis.memtable(data) - - try: - import pyarrow as pa - except ImportError: - pass - else: - if isinstance(data, pa.Table): - return bigframes_vendored.ibis.memtable(data) - - if not isinstance(data, Table): - raise TypeError(f"right operand must be a Table, got {type(data).__name__}") - return data - - -def disambiguate_fields( - how, - predicates, - equalities, - left_fields, - right_fields, - left_template, - right_template, -): - """Resolve name collisions between the left and right tables.""" - collisions = set() - left_template = left_template or "{name}" - right_template = right_template or "{name}" - - if how == "inner" and util.all_of(predicates, ops.Equals): - # for inner joins composed exclusively of equality predicates, we can - # avoid renaming columns with colliding names if their values are - # guaranteed to be equal due to the predicate - equalities = equalities.copy() - for pred in predicates: - if isinstance(pred.left, ops.Field) and isinstance(pred.right, ops.Field): - # disjoint sets are used to track the equality groups - equalities.add(pred.left) - equalities.add(pred.right) - equalities.union(pred.left, pred.right) - - if how in ("semi", "anti"): - # discard the right fields per left semi and left anty join semantics - return left_fields, collisions, equalities - - fields = {} - for name, field in left_fields.items(): - if name in right_fields: - # there is an overlap between this field and a field from the right - try: - # check if the fields are equal due to equality predicates - are_equal = equalities.connected(field, right_fields[name]) - except KeyError: - are_equal = False - if not are_equal: - # there is a name collision and the fields are not equal, so - # rename the field from the left according to the provided - # template (which is the name itself by default) - name = left_template.format(name=name) - - fields[name] = field - - for name, field in right_fields.items(): - if name in left_fields: - # there is an overlap between this field and a field from the left - try: - # check if the fields are equal due to equality predicates - are_equal = equalities.connected(field, left_fields[name]) - except KeyError: - are_equal = False - - if are_equal: - # even though there is a name collision, the fields are equal - # due to equality predicates, so we can safely discard the - # field from the right - continue - else: - # there is a name collision and the fields are not equal, so - # rename the field from the right according to the provided - # template - name = right_template.format(name=name) - - if name in fields: - # we can still have collisions after multiple joins, or a wrongly - # chosen template, so we need to track the collisions - collisions.add(name) - else: - # the field name does not collide with any field from the left - # and not occupied by any field from the right, so add it to the - # fields mapping - fields[name] = field - - return fields, collisions, equalities - - -def prepare_predicates( - chain: ops.JoinChain, - right: ops.Relation, - predicates: Sequence[Any], - comparison: type[ops.Comparison] = ops.Equals, -): - """Bind and dereference predicates to the left and right tables. - - The responsibility of this function is twofold: - 1. Convert the various input values to valid predicates, including binding. - 2. Dereference the predicates one of the ops.JoinTable(s) in the join chain - or the new JoinTable wrapping the right table. JoinTable(s) are used to - ensure that all join participants are unique, even if the same table is - joined multiple times. - - Since join predicates can be ambiguous sometimes, we do the two steps above - in the same time so that we have more contextual information to resolve - ambiguities. - - Possible inputs for the predicates: - 1. A python boolean literal, which is converted to a literal expression - 2. A boolean `Value` expression, which gets flattened and dereferenced. - If there are comparison expressions where both sides depend on the same - relation, then the left side is dereferenced to one of the join tables - already part of the join chain, while the right side is dereferenced to - the new join table wrapping the right table. - 3. A `Deferred` expression, which gets resolved on the left table and then - the same path is followed as for `Value` expressions. - 4. A pair of expression-like objects, which are getting bound to the left - and right tables respectively using the robust `bind` function handling - several cases, including `Deferred` expressions, `Selector`s, literals, - etc. Then the left are dereferenced to the join chain whereas the right - to the new join table wrapping the right table. - - Parameters - ---------- - chain - The join chain - right - The right table - predicates - Predicates to bind and dereference, see the possible values above - comparison - The comparison operation to construct if the input is a pair of - expression-like objects - """ - reverse = { - ops.Field(chain, k): v - for k, v in chain.values.items() - if isinstance(v, ops.Field) - } - deref_right = DerefMap.from_targets(right) - deref_left = DerefMap.from_targets(chain.tables, extra=reverse) - deref_both = DerefMap.from_targets([*chain.tables, right], extra=reverse) - - left, right = chain.to_expr(), right.to_expr() - for pred in util.promote_list(predicates): - if isinstance(pred, (Value, Deferred, bool)): - for bound in bind(left, pred): - yield deref_both.dereference(bound.op()) - else: - if isinstance(pred, tuple): - if len(pred) != 2: - raise ExpressionError("Join key tuple must be length 2") - lk, rk = pred - else: - lk = rk = pred - - for lhs, rhs in zip(bind(left, lk), bind(right, rk)): - lhs = deref_left.dereference(lhs.op()) - rhs = deref_right.dereference(rhs.op()) - yield comparison(lhs, rhs) - - -def finished(method): - """Decorator to ensure the join chain is finished before calling a method.""" - - @functools.wraps(method) - def wrapper(self, *args, **kwargs): - return method(self._finish(), *args, **kwargs) - - return wrapper - - -@public -class Join(Table): - __slots__ = ("_collisions", "_equalities") - - def __init__(self, arg, collisions=(), equalities=()): - assert isinstance(arg, ops.Node) - if not isinstance(arg, ops.JoinChain): - # coerce the input node to a join chain operation - arg = ops.JoinReference(arg, identifier=0) - arg = ops.JoinChain(arg, rest=(), values=arg.fields) - super().__init__(arg) - # the collisions and equalities are used to track the name collisions - # and the equality groups join fields based on equality predicates; - # these must be tracked in the join expression because the join chain - # operation doesn't hold any information about `lname` and `rname` - # parameters passed to the join methods and used to disambiguate field - # names; the collisions are used to raise an error if there are any - # name collisions after the join chain is finished - object.__setattr__(self, "_collisions", collisions or set()) - object.__setattr__(self, "_equalities", equalities or DisjointSet()) - - def _finish(self) -> Table: - """Construct a valid table expression from this join expression.""" - if self._collisions: - raise IntegrityError(f"Name collisions: {self._collisions}") - return Table(self.op()) - - @functools.wraps(Table.join) - def join( - self, - right, - predicates: Any, - how: JoinKind = "inner", - *, - lname: str = "", - rname: str = "{name}_right", - ): - right = coerce_to_table(right) - - if how == "left_semi": - how = "semi" - elif how == "asof": - raise IbisInputError("use table.asof_join(...) instead") - - chain = self.op() - right = right.op() - if not isinstance(right, ops.Reference): - right = ops.JoinReference(right, identifier=chain.length) - - # bind and dereference the predicates - preds = prepare_predicates(chain, right, predicates) - preds = flatten_predicates(preds) - if not preds and how not in {"cross", "positional"}: - # if there are no predicates, default to every row matching unless - # the join is a cross join, because a cross join already has this - # behavior - preds.append(ops.Literal(True, dtype="bool")) - - # calculate the fields based in lname and rname, this should be a best - # effort to avoid collisions, but does not raise if there are any - # if no disambiaution happens using a final .select() call, then - # the finish() method will raise due to the name collisions - values, collisions, equalities = disambiguate_fields( - how=how, - predicates=preds, - equalities=self._equalities, - left_fields=chain.values, - right_fields=right.fields, - left_template=lname, - right_template=rname, - ) - - # construct a new join link and add it to the join chain - link = ops.JoinLink(how, table=right, predicates=preds) - chain = chain.copy(rest=chain.rest + (link,), values=values) - - # return with a new JoinExpr wrapping the new join chain - return self.__class__(chain, collisions=collisions, equalities=equalities) - - @functools.wraps(Table.asof_join) - def asof_join( - self: Table, - right: Table, - on, - predicates=(), - tolerance=None, - *, - lname: str = "", - rname: str = "{name}_right", - ): - predicates = util.promote_list(predicates) - if tolerance is not None: - # `tolerance` parameter is mimicking the pandas API, but we express - # it at the expression level by a sequence of operations: - # 1. perform the `asof` join with the `on` an `predicates` parameters - # where the `on` parameter is an inequality predicate - # 2. filter the asof join result using the `tolerance` parameter and - # the `on` parameter - # 3. perform a left join between the original left table and the - # filtered asof join result using the `on` parameter but this - # time as an equality predicate - if isinstance(on, str): - # self is always a JoinChain so reference one of the join tables - left_on = self.op().values[on].to_expr() - right_on = right[on] - on = left_on >= right_on - elif isinstance(on, Value): - node = on.op() - if not isinstance(node, ops.Binary): - raise InputTypeError("`on` must be a comparison expression") - left_on = node.left.to_expr() - right_on = node.right.to_expr() - else: - raise TypeError("`on` must be a string or a ValueExpr") - - joined = self.asof_join( - right, on=on, predicates=predicates, lname=lname, rname=rname - ) - filtered = joined.filter( - left_on <= right_on + tolerance, left_on >= right_on - tolerance - ) - right_on = right_on.op().replace({right.op(): filtered.op()}).to_expr() - - # without joining twice the table would not contain the rows from - # the left table that do not match any row from the right table - # given the tolerance filter - result = self.left_join( - filtered, predicates=[left_on == right_on] + predicates - ) - values = {**filtered.op().values, **self.op().values} - - return result.select(**values) - - chain = self.op() - right = right.op() - if not isinstance(right, ops.Reference): - right = ops.JoinReference(right, identifier=chain.length) - - # TODO(kszucs): add extra validation for `on` with clear error messages - (on,) = prepare_predicates(chain, right, [on], comparison=ops.GreaterEqual) - preds = prepare_predicates(chain, right, predicates, comparison=ops.Equals) - preds = [on, *preds] - - values, collisions, equalities = disambiguate_fields( - how="asof", - predicates=preds, - equalities=self._equalities, - left_fields=chain.values, - right_fields=right.fields, - left_template=lname, - right_template=rname, - ) - - # construct a new join link and add it to the join chain - link = ops.JoinLink("asof", table=right, predicates=preds) - chain = chain.copy(rest=chain.rest + (link,), values=values) - - # return with a new JoinExpr wrapping the new join chain - return self.__class__(chain, collisions=collisions, equalities=equalities) - - @functools.wraps(Table.cross_join) - def cross_join( - self: Table, - right: Table, - *rest: Table, - lname: str = "", - rname: str = "{name}_right", - ): - left = self.join(right, how="cross", predicates=(), lname=lname, rname=rname) - for table in rest: - left = left.join( - table, how="cross", predicates=(), lname=lname, rname=rname - ) - return left - - @functools.wraps(Table.select) - def select(self, *args, **kwargs): - chain = self.op() - values = self.bind(*args, **kwargs) - values = unwrap_aliases(values) - - links = [link.table for link in chain.rest if link.how not in ("semi", "anti")] - derefmap = DerefMap.from_targets([chain.first, *links]) - - # if there are values referencing fields from the join chain constructed - # so far, we need to replace them the fields from one of the join links - values = { - k: v.replace(peel_join_field, filter=ops.Value) for k, v in values.items() - } - values = {k: derefmap.dereference(v) for k, v in values.items()} - - node = chain.copy(values=values) - return Table(node) - - aggregate = finished(Table.aggregate) - alias = finished(Table.alias) - cast = finished(Table.cast) - compile = finished(Table.compile) - count = finished(Table.count) - difference = finished(Table.difference) - distinct = finished(Table.distinct) - drop = finished(Table.drop) - dropna = finished(Table.dropna) - execute = finished(Table.execute) - fill_null = finished(Table.fill_null) - filter = finished(Table.filter) - group_by = finished(Table.group_by) - intersect = finished(Table.intersect) - limit = finished(Table.limit) - mutate = finished(Table.mutate) - nunique = finished(Table.nunique) - order_by = finished(Table.order_by) - sample = finished(Table.sample) - sql = finished(Table.sql) - unbind = finished(Table.unbind) - union = finished(Table.union) - view = finished(Table.view) - - -public(JoinExpr=Join) diff --git a/third_party/bigframes_vendored/ibis/expr/types/json.py b/third_party/bigframes_vendored/ibis/expr/types/json.py deleted file mode 100644 index 51d1642de00..00000000000 --- a/third_party/bigframes_vendored/ibis/expr/types/json.py +++ /dev/null @@ -1,492 +0,0 @@ -# Contains code from https://github.com/ibis-project/ibis/blob/9.2.0/ibis/expr/types/json.py - -"""JSON value operations.""" - -from __future__ import annotations - -from typing import TYPE_CHECKING - -import bigframes_vendored.ibis.common.exceptions as exc -import bigframes_vendored.ibis.expr.datatypes as dt -import bigframes_vendored.ibis.expr.operations as ops -from bigframes_vendored.ibis.expr.types import Column, Scalar, Value -from public import public - -if TYPE_CHECKING: - import bigframes_vendored.ibis.expr.types as ir - - -@public -class JSONValue(Value): - def __getitem__( - self, key: str | int | ir.StringValue | ir.IntegerValue - ) -> JSONValue: - """Access an JSON object's value or JSON array's element at `key`. - - Parameters - ---------- - key - Object field name or integer array index - - Returns - ------- - JSONValue - Element located at `key` - - Examples - -------- - Construct a table with a JSON column - - >>> import json, ibis - >>> ibis.options.interactive = True - >>> rows = [{"js": json.dumps({"a": [i, 1]})} for i in range(2)] - >>> t = ibis.memtable(rows, schema=ibis.schema(dict(js="json"))) - >>> t - ┏━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ js ┃ - ┡━━━━━━━━━━━━━━━━━━━━━━┩ - │ json │ - ├──────────────────────┤ - │ {'a': [...]} │ - │ {'a': [...]} │ - └──────────────────────┘ - - Extract the `"a"` field - - >>> t.js["a"] - ┏━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ JSONGetItem(js, 'a') ┃ - ┡━━━━━━━━━━━━━━━━━━━━━━┩ - │ json │ - ├──────────────────────┤ - │ [0, 1] │ - │ [1, 1] │ - └──────────────────────┘ - - Extract the first element of the JSON array at `"a"` - - >>> t.js["a"][0] - ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ JSONGetItem(JSONGetItem(js, 'a'), 0) ┃ - ┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩ - │ json │ - ├──────────────────────────────────────┤ - │ 0 │ - │ 1 │ - └──────────────────────────────────────┘ - - Extract a non-existent field - - >>> t.js["a"]["foo"] - ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ JSONGetItem(JSONGetItem(js, 'a'), 'foo') ┃ - ┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩ - │ json │ - ├──────────────────────────────────────────┤ - │ NULL │ - │ NULL │ - └──────────────────────────────────────────┘ - - Try to extract an array element, returns `NULL` - - >>> t.js[20] - ┏━━━━━━━━━━━━━━━━━━━━━┓ - ┃ JSONGetItem(js, 20) ┃ - ┡━━━━━━━━━━━━━━━━━━━━━┩ - │ json │ - ├─────────────────────┤ - │ NULL │ - │ NULL │ - └─────────────────────┘ - """ - return ops.JSONGetItem(self, key).to_expr() - - def unwrap_as(self, dtype: dt.DataType | str) -> ir.Value: - """Unwrap JSON into a specific data type. - - Returns - ------- - Value - An Ibis expression of a more specific type than JSON - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> data = { - ... "jstring": ['"a"', '""', None, "null"], - ... "jbool": ["true", "false", "null", None], - ... "jint": ["1", "null", None, "2"], - ... "jfloat": ["42.42", None, "null", "37.37"], - ... "jmap": ['{"a": 1}', "null", None, "{}"], - ... "jarray": ["[]", "null", None, '[{},"1",2]'], - ... } - >>> t = ibis.memtable(data, schema=dict.fromkeys(data.keys(), "json")) - >>> t - ┏━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━┳━━━┓ - ┃ jstring ┃ jbool ┃ jint ┃ … ┃ - ┡━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━╇━━━┩ - │ json │ json │ json │ … │ - ├──────────────────────┼──────────────────────┼──────────────────────┼───┤ - │ 'a' │ True │ 1 │ … │ - │ '' │ False │ None │ … │ - │ NULL │ None │ NULL │ … │ - │ None │ NULL │ 2 │ … │ - └──────────────────────┴──────────────────────┴──────────────────────┴───┘ - >>> t.select(unwrapped=t.jstring.unwrap_as(str), original=t.jstring) - ┏━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ unwrapped ┃ original ┃ - ┡━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━┩ - │ string │ json │ - ├───────────┼──────────────────────┤ - │ a │ 'a' │ - │ ~ │ '' │ - │ NULL │ NULL │ - │ NULL │ None │ - └───────────┴──────────────────────┘ - >>> t.select(unwrapped=t.jbool.unwrap_as("bool"), original=t.jbool) - ┏━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ unwrapped ┃ original ┃ - ┡━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━┩ - │ boolean │ json │ - ├───────────┼──────────────────────┤ - │ True │ True │ - │ False │ False │ - │ NULL │ None │ - │ NULL │ NULL │ - └───────────┴──────────────────────┘ - >>> t.select( - ... unwrapped_int64=t.jint.unwrap_as("int64"), - ... unwrapped_int32=t.jint.unwrap_as("int32"), - ... original=t.jint, - ... ) - ┏━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ unwrapped_int64 ┃ unwrapped_int32 ┃ original ┃ - ┡━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━┩ - │ int64 │ int32 │ json │ - ├─────────────────┼─────────────────┼──────────────────────┤ - │ 1 │ 1 │ 1 │ - │ NULL │ NULL │ None │ - │ NULL │ NULL │ NULL │ - │ 2 │ 2 │ 2 │ - └─────────────────┴─────────────────┴──────────────────────┘ - - You can cast to a more specific type than the types available in standards-compliant JSON. - - Here's an example of casting JSON numbers to `float32`: - - >>> t.select(unwrapped=t.jfloat.unwrap_as("float32"), original=t.jfloat) - ┏━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ unwrapped ┃ original ┃ - ┡━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━┩ - │ float32 │ json │ - ├───────────┼──────────────────────┤ - │ 42.419998 │ 42.42 │ - │ NULL │ NULL │ - │ NULL │ None │ - │ 37.369999 │ 37.37 │ - └───────────┴──────────────────────┘ - - You can cast JSON objects to a more specific `map` type: - - >>> t.select(unwrapped=t.jmap.unwrap_as("map"), original=t.jmap) - ┏━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ unwrapped ┃ original ┃ - ┡━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━┩ - │ map │ json │ - ├──────────────────────┼──────────────────────┤ - │ {'a': 1} │ {'a': 1} │ - │ NULL │ None │ - │ NULL │ NULL │ - │ {} │ {} │ - └──────────────────────┴──────────────────────┘ - - You can cast JSON arrays to an array type as well. In this case the - array values don't have a single element type so we cast to - `array`. - - >>> t.select(unwrapped=t.jarray.unwrap_as("array"), original=t.jarray) - ┏━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ unwrapped ┃ original ┃ - ┡━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━┩ - │ array │ json │ - ├───────────────────────┼──────────────────────┤ - │ [] │ [] │ - │ NULL │ None │ - │ NULL │ NULL │ - │ ['{}', '"1"', ... +1] │ [{...}, '1', ... +1] │ - └───────────────────────┴──────────────────────┘ - - See Also - -------- - [`JSONValue.str`](#ibis.expr.types.json.JSONValue.str) - [`JSONValue.int`](#ibis.expr.types.json.JSONValue.int) - [`JSONValue.float`](#ibis.expr.types.json.JSONValue.float) - [`JSONValue.bool`](#ibis.expr.types.json.JSONValue.bool) - [`JSONValue.map`](#ibis.expr.types.json.JSONValue.map) - [`JSONValue.array`](#ibis.expr.types.json.JSONValue.array) - [`Value.cast`](#ibis.expr.types.generic.Value.cast) - """ - dtype = dt.dtype(dtype) - if dtype.is_string(): - return self.str - elif dtype.is_boolean(): - return self.bool - elif dtype.is_integer(): - i = self.int - return i.cast(dtype) if i.type() != dtype else i - elif dtype.is_floating(): - f = self.float - return f.cast(dtype) if f.type() != dtype else f - elif dtype.is_map(): - m = self.map - return m.cast(dtype) if m.type() != dtype else m - elif dtype.is_array(): - a = self.array - return a.cast(dtype) if a.type() != dtype else a - else: - raise exc.IbisTypeError( - f"Data type {dtype} is unsupported for unwrapping JSON values. Supported " - "data types are strings, integers, floats, booleans, maps, and arrays." - ) - - @property - def map(self) -> ir.MapValue: - """Cast JSON to a map of string to JSON. - - Use this property to unlock map functionality on JSON objects. - - Returns - ------- - MapValue - Map of string to JSON - """ - return ops.ToJSONMap(self).to_expr() - - @property - def array(self) -> ir.ArrayValue: - """Cast JSON to an array of JSON. - - Use this property to unlock array functionality on JSON objects. - - Returns - ------- - ArrayValue - Array of JSON objects - """ - return ops.ToJSONArray(self).to_expr() - - @property - def int(self) -> ir.IntegerValue: - """Unwrap a JSON value into a backend-native int. - - Any non-float JSON values are returned as `NULL`. - - Examples - -------- - >>> import json, ibis - >>> ibis.options.interactive = True - >>> data = [ - ... {"name": "Alice", "json_data": '{"last_name":"Smith","age":40}'}, - ... {"name": "Bob", "json_data": '{"last_name":"Jones", "age":39}'}, - ... {"name": "Charlie", "json_data": '{"last_name":"Davies","age":54}'}, - ... ] - >>> t = ibis.memtable(data, schema={"name": "string", "json_data": "json"}) - >>> t - ┏━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ name ┃ json_data ┃ - ┡━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩ - │ string │ json │ - ├─────────┼────────────────────────────────────┤ - │ Alice │ {'last_name': 'Smith', 'age': 40} │ - │ Bob │ {'last_name': 'Jones', 'age': 39} │ - │ Charlie │ {'last_name': 'Davies', 'age': 54} │ - └─────────┴────────────────────────────────────┘ - >>> t.mutate(age=t.json_data["age"].int) - ┏━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━┓ - ┃ name ┃ json_data ┃ age ┃ - ┡━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━┩ - │ string │ json │ int64 │ - ├─────────┼────────────────────────────────────┼───────┤ - │ Alice │ {'last_name': 'Smith', 'age': 40} │ 40 │ - │ Bob │ {'last_name': 'Jones', 'age': 39} │ 39 │ - │ Charlie │ {'last_name': 'Davies', 'age': 54} │ 54 │ - └─────────┴────────────────────────────────────┴───────┘ - """ - return ops.UnwrapJSONInt64(self).to_expr() - - @property - def float(self) -> ir.FloatingValue: - """Unwrap a JSON value into a backend-native float. - - Any non-float JSON values are returned as `NULL`. - - ::: {.callout-warning} - ## The `float` property is lax with respect to integers - - The `float` property will attempt to coerce integers to floating point numbers. - ::: - - Examples - -------- - >>> import json, ibis - >>> ibis.options.interactive = True - >>> data = [ - ... {"name": "Alice", "json_data": '{"last_name":"Smith","salary":42.42}'}, - ... {"name": "Bob", "json_data": '{"last_name":"Jones", "salary":37.37}'}, - ... {"name": "Charlie", "json_data": '{"last_name":"Davies","salary":"NA"}'}, - ... {"name": "Joan", "json_data": '{"last_name":"Davies","salary":78}'}, - ... ] - >>> t = ibis.memtable(data, schema={"name": "string", "json_data": "json"}) - >>> t - ┏━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ name ┃ json_data ┃ - ┡━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩ - │ string │ json │ - ├─────────┼─────────────────────────────────────────┤ - │ Alice │ {'last_name': 'Smith', 'salary': 42.42} │ - │ Bob │ {'last_name': 'Jones', 'salary': 37.37} │ - │ Charlie │ {'last_name': 'Davies', 'salary': 'NA'} │ - │ Joan │ {'last_name': 'Davies', 'salary': 78} │ - └─────────┴─────────────────────────────────────────┘ - >>> t.mutate(salary=t.json_data["salary"].float) - ┏━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━┓ - ┃ name ┃ json_data ┃ salary ┃ - ┡━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━┩ - │ string │ json │ float64 │ - ├─────────┼─────────────────────────────────────────┼─────────┤ - │ Alice │ {'last_name': 'Smith', 'salary': 42.42} │ 42.42 │ - │ Bob │ {'last_name': 'Jones', 'salary': 37.37} │ 37.37 │ - │ Charlie │ {'last_name': 'Davies', 'salary': 'NA'} │ NULL │ - │ Joan │ {'last_name': 'Davies', 'salary': 78} │ 78.00 │ - └─────────┴─────────────────────────────────────────┴─────────┘ - """ - return ops.UnwrapJSONFloat64(self).to_expr() - - @property - def bool(self) -> ir.BooleanValue: - """Unwrap a JSON value into a backend-native boolean. - - Any non-boolean JSON values are returned as `NULL`. - - Examples - -------- - >>> import json, ibis - >>> ibis.options.interactive = True - >>> data = [ - ... {"name": "Alice", "json_data": '{"last_name":"Smith","is_bot":false}'}, - ... {"name": "Bob", "json_data": '{"last_name":"Jones","is_bot":true}'}, - ... {"name": "Charlie", "json_data": '{"last_name":"Davies","is_bot":false}'}, - ... ] - >>> t = ibis.memtable(data, schema={"name": "string", "json_data": "json"}) - >>> t - ┏━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ name ┃ json_data ┃ - ┡━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩ - │ string │ json │ - ├─────────┼──────────────────────────────────────────┤ - │ Alice │ {'last_name': 'Smith', 'is_bot': False} │ - │ Bob │ {'last_name': 'Jones', 'is_bot': True} │ - │ Charlie │ {'last_name': 'Davies', 'is_bot': False} │ - └─────────┴──────────────────────────────────────────┘ - >>> t.mutate(is_bot=t.json_data["is_bot"].bool) - ┏━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━┓ - ┃ name ┃ json_data ┃ is_bot ┃ - ┡━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━┩ - │ string │ json │ boolean │ - ├─────────┼──────────────────────────────────────────┼─────────┤ - │ Alice │ {'last_name': 'Smith', 'is_bot': False} │ False │ - │ Bob │ {'last_name': 'Jones', 'is_bot': True} │ True │ - │ Charlie │ {'last_name': 'Davies', 'is_bot': False} │ False │ - └─────────┴──────────────────────────────────────────┴─────────┘ - """ - return ops.UnwrapJSONBoolean(self).to_expr() - - @property - def str(self) -> ir.StringValue: - """Unwrap a JSON string into a backend-native string. - - Any non-string JSON values are returned as `NULL`. - - Returns - ------- - StringValue - A string expression - - Examples - -------- - >>> import json, ibis - >>> ibis.options.interactive = True - >>> t = ibis.memtable( - ... {"js": ['"a"', '"b"', "1", "{}", '[{"a": 1}]']}, - ... schema=ibis.schema(dict(js="json")), - ... ) - >>> t - ┏━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ js ┃ - ┡━━━━━━━━━━━━━━━━━━━━━━┩ - │ json │ - ├──────────────────────┤ - │ 'a' │ - │ 'b' │ - │ 1 │ - │ {} │ - │ [{...}] │ - └──────────────────────┘ - >>> t.js.str - ┏━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ UnwrapJSONString(js) ┃ - ┡━━━━━━━━━━━━━━━━━━━━━━┩ - │ string │ - ├──────────────────────┤ - │ a │ - │ b │ - │ NULL │ - │ NULL │ - │ NULL │ - └──────────────────────┘ - - Here's a more complex example with a table containing a JSON column - with nested fields. - - >>> data = [ - ... {"name": "Alice", "json_data": '{"last_name":"Smith"}'}, - ... {"name": "Bob", "json_data": '{"last_name":"Jones"}'}, - ... {"name": "Charlie", "json_data": '{"last_name":"Davies"}'}, - ... ] - >>> t = ibis.memtable(data, schema={"name": "string", "json_data": "json"}) - >>> t - ┏━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ name ┃ json_data ┃ - ┡━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━┩ - │ string │ json │ - ├─────────┼─────────────────────────┤ - │ Alice │ {'last_name': 'Smith'} │ - │ Bob │ {'last_name': 'Jones'} │ - │ Charlie │ {'last_name': 'Davies'} │ - └─────────┴─────────────────────────┘ - >>> t.mutate(last_name=t.json_data["last_name"].str) - ┏━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━┓ - ┃ name ┃ json_data ┃ last_name ┃ - ┡━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━┩ - │ string │ json │ string │ - ├─────────┼─────────────────────────┼───────────┤ - │ Alice │ {'last_name': 'Smith'} │ Smith │ - │ Bob │ {'last_name': 'Jones'} │ Jones │ - │ Charlie │ {'last_name': 'Davies'} │ Davies │ - └─────────┴─────────────────────────┴───────────┘ - """ - return ops.UnwrapJSONString(self).to_expr() - - -@public -class JSONScalar(Scalar, JSONValue): - pass - - -@public -class JSONColumn(Column, JSONValue): - def __getitem__( - self, key: str | int | ir.StringValue | ir.IntegerValue - ) -> JSONColumn: - return JSONValue.__getitem__(self, key) diff --git a/third_party/bigframes_vendored/ibis/expr/types/logical.py b/third_party/bigframes_vendored/ibis/expr/types/logical.py deleted file mode 100644 index 68ad6feae10..00000000000 --- a/third_party/bigframes_vendored/ibis/expr/types/logical.py +++ /dev/null @@ -1,557 +0,0 @@ -# Contains code from https://github.com/ibis-project/ibis/blob/9.2.0/ibis/expr/types/logical.py - -from __future__ import annotations - -from typing import TYPE_CHECKING - -import bigframes_vendored.ibis -import bigframes_vendored.ibis.expr.operations as ops -from bigframes_vendored.ibis.expr.types.core import _binop -from bigframes_vendored.ibis.expr.types.numeric import ( - NumericColumn, - NumericScalar, - NumericValue, -) -from public import public - -if TYPE_CHECKING: - import bigframes_vendored.ibis.expr.types as ir - - -@public -class BooleanValue(NumericValue): - def ifelse(self, true_expr: ir.Value, false_expr: ir.Value) -> ir.Value: - """Construct a ternary conditional expression. - - Parameters - ---------- - true_expr - Expression to return if `self` evaluates to `True` - false_expr - Expression to return if `self` evaluates to `False` or `NULL` - - Returns - ------- - Value - The value of `true_expr` if `arg` is `True` else `false_expr` - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.memtable({"is_person": [True, False, True, None]}) - >>> t.is_person.ifelse("yes", "no") - ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ IfElse(is_person, 'yes', 'no') ┃ - ┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩ - │ string │ - ├────────────────────────────────┤ - │ yes │ - │ no │ - │ yes │ - │ no │ - └────────────────────────────────┘ - """ - # Result will be the result of promotion of true/false exprs. These - # might be conflicting types; same type resolution as case expressions - # must be used. - return ops.IfElse(self, true_expr, false_expr).to_expr() - - def __and__(self, other: BooleanValue) -> BooleanValue: - """Construct a binary AND conditional expression with `self` and `other`. - - Parameters - ---------- - self - Left operand - other - Right operand - - Returns - ------- - BooleanValue - A Boolean expression - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.memtable({"arr": [[1], [], [42, 42], None]}) - >>> t.arr.contains(42) & (t.arr.contains(1)) - ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ And(ArrayContains(arr, 42), ArrayContains(arr, 1)) ┃ - ┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩ - │ boolean │ - ├────────────────────────────────────────────────────┤ - │ False │ - │ False │ - │ False │ - │ NULL │ - └────────────────────────────────────────────────────┘ - - >>> t.arr.contains(42) & (t.arr.contains(42)) - ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ And(ArrayContains(arr, 42), ArrayContains(arr, 42)) ┃ - ┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩ - │ boolean │ - ├─────────────────────────────────────────────────────┤ - │ False │ - │ False │ - │ True │ - │ NULL │ - └─────────────────────────────────────────────────────┘ - """ - return _binop(ops.And, self, other) - - __rand__ = __and__ - - def __or__(self, other: BooleanValue) -> BooleanValue: - """Construct a binary OR conditional expression with `self` and `other`. - - Parameters - ---------- - self - Left operand - other - Right operand - - Returns - ------- - BooleanValue - A Boolean expression - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.memtable({"arr": [1, 2, 3, None]}) - >>> (t.arr > 1) | (t.arr > 2) - ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ Or(Greater(arr, 1), Greater(arr, 2)) ┃ - ┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩ - │ boolean │ - ├──────────────────────────────────────┤ - │ False │ - │ True │ - │ True │ - │ NULL │ - └──────────────────────────────────────┘ - """ - return _binop(ops.Or, self, other) - - __ror__ = __or__ - - def __xor__(self, other: BooleanValue) -> BooleanValue: - """Construct a binary XOR conditional expression with `self` and `other`. - - Parameters - ---------- - self - Left operand - other - Right operand - - Returns - ------- - BooleanValue - A Boolean expression - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.memtable({"arr": [1, 2, 3, None]}) - >>> t.arr == 2 - ┏━━━━━━━━━━━━━━━━┓ - ┃ Equals(arr, 2) ┃ - ┡━━━━━━━━━━━━━━━━┩ - │ boolean │ - ├────────────────┤ - │ False │ - │ True │ - │ False │ - │ NULL │ - └────────────────┘ - - >>> (t.arr > 2) - ┏━━━━━━━━━━━━━━━━━┓ - ┃ Greater(arr, 2) ┃ - ┡━━━━━━━━━━━━━━━━━┩ - │ boolean │ - ├─────────────────┤ - │ False │ - │ False │ - │ True │ - │ NULL │ - └─────────────────┘ - - >>> (t.arr == 2) ^ (t.arr > 2) - ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ Xor(Equals(arr, 2), Greater(arr, 2)) ┃ - ┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩ - │ boolean │ - ├──────────────────────────────────────┤ - │ False │ - │ True │ - │ True │ - │ NULL │ - └──────────────────────────────────────┘ - """ - - return _binop(ops.Xor, self, other) - - __rxor__ = __xor__ - - def __invert__(self) -> BooleanValue: - """Construct a unary NOT conditional expression with `self`. - - Parameters - ---------- - self - Operand - - Returns - ------- - BooleanValue - A Boolean expression - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.memtable({"arr": [True, False, False, None]}) - >>> ~t.arr - ┏━━━━━━━━━━┓ - ┃ Not(arr) ┃ - ┡━━━━━━━━━━┩ - │ boolean │ - ├──────────┤ - │ False │ - │ True │ - │ True │ - │ NULL │ - └──────────┘ - """ - return self.negate() - - def negate(self) -> BooleanValue: - """Negate a boolean expression. - - Returns - ------- - BooleanValue - A boolean value expression - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.memtable({"values": [True, False, False, None]}) - >>> t.values.negate() - ┏━━━━━━━━━━━━━┓ - ┃ Not(values) ┃ - ┡━━━━━━━━━━━━━┩ - │ boolean │ - ├─────────────┤ - │ False │ - │ True │ - │ True │ - │ NULL │ - └─────────────┘ - """ - return ops.Not(self).to_expr() - - -@public -class BooleanScalar(NumericScalar, BooleanValue): - pass - - -@public -class BooleanColumn(NumericColumn, BooleanValue): - def any(self, where: BooleanValue | None = None) -> BooleanValue: - """Return whether at least one element is `True`. - - If the expression does not reference any foreign tables, the result - will be a scalar reduction, otherwise it will be a deferred expression - constructing an exists subquery when passed to a table method. - - Parameters - ---------- - where - Optional filter for the aggregation - - Returns - ------- - BooleanValue - Whether at least one element is `True`. - - Notes - ----- - Consider the following ibis expressions - - ```python - import ibis - - t = ibis.table(dict(a="string")) - s = ibis.table(dict(a="string")) - - cond = (t.a == s.a).any() - ``` - - Without knowing the table to use as the outer query there are two ways to - turn this expression into a SQL `EXISTS` predicate, depending on which of - `t` or `s` is filtered on. - - Filtering from `t`: - - ```sql - SELECT * - FROM t - WHERE EXISTS (SELECT 1 FROM s WHERE t.a = s.a) - ``` - - Filtering from `s`: - - ```sql - SELECT * - FROM s - WHERE EXISTS (SELECT 1 FROM t WHERE t.a = s.a) - ``` - - Notably the correlated subquery cannot stand on its own. - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.memtable({"arr": [1, 2, 3, None]}) - >>> (t.arr > 2).any() - ┌──────────┐ - │ np.True_ │ - └──────────┘ - >>> (t.arr > 4).any() - ┌───────────┐ - │ np.False_ │ - └───────────┘ - >>> (t.arr == None).any(where=t.arr != None) - ┌───────────┐ - │ np.False_ │ - └───────────┘ - """ - from bigframes_vendored.ibis.common.deferred import Call, Deferred, _ - - parents = self.op().relations - - def resolve_exists_subquery(outer): - """An exists subquery whose outer leaf table is unknown.""" - (inner,) = (t for t in parents if t != outer.op()) - relation = ops.Filter(inner, [self]) - return ops.ExistsSubquery(relation).to_expr() - - if len(parents) == 2: - return Deferred(Call(resolve_exists_subquery, _)) - elif len(parents) == 1: - op = ops.Any(self, where=self._bind_to_parent_table(where)) - elif len(parents) == 0: - # array reduction case - op = ops.Any(self, where=self._bind_to_parent_table(where)) - else: - raise NotImplementedError( - f'Cannot compute "any" for expression of type {type(self)} ' - f"with multiple foreign tables" - ) - - return op.to_expr() - - def notany(self, where: BooleanValue | None = None) -> BooleanValue: - """Return whether no elements are `True`. - - Parameters - ---------- - where - Optional filter for the aggregation - - Returns - ------- - BooleanValue - Whether no elements are `True`. - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.memtable({"arr": [1, 2, 3, 4]}) - >>> (t.arr > 1).notany() - ┌───────────┐ - │ np.False_ │ - └───────────┘ - >>> (t.arr > 4).notany() - ┌──────────┐ - │ np.True_ │ - └──────────┘ - >>> m = ibis.memtable({"arr": [True, True, True, False]}) - >>> (t.arr == None).notany(where=t.arr != None) - ┌──────────┐ - │ np.True_ │ - └──────────┘ - """ - return ~self.any(where=where) - - def all(self, where: BooleanValue | None = None) -> BooleanScalar: - """Return whether all elements are `True`. - - Parameters - ---------- - where - Optional filter for the aggregation - - Returns - ------- - BooleanValue - Whether all elements are `True` - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.memtable({"arr": [1, 2, 3, 4]}) - >>> (t.arr >= 1).all() - ┌──────────┐ - │ np.True_ │ - └──────────┘ - >>> (t.arr > 2).all() - ┌───────────┐ - │ np.False_ │ - └───────────┘ - >>> (t.arr == 2).all(where=t.arr == 2) - ┌──────────┐ - │ np.True_ │ - └──────────┘ - >>> (t.arr == 2).all(where=t.arr >= 2) - ┌───────────┐ - │ np.False_ │ - └───────────┘ - """ - return ops.All(self, where=self._bind_to_parent_table(where)).to_expr() - - def notall(self, where: BooleanValue | None = None) -> BooleanScalar: - """Return whether not all elements are `True`. - - Parameters - ---------- - where - Optional filter for the aggregation - - Returns - ------- - BooleanValue - Whether not all elements are `True` - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.memtable({"arr": [1, 2, 3, 4]}) - >>> (t.arr >= 1).notall() - ┌───────────┐ - │ np.False_ │ - └───────────┘ - >>> (t.arr > 2).notall() - ┌──────────┐ - │ np.True_ │ - └──────────┘ - >>> (t.arr == 2).notall(where=t.arr == 2) - ┌───────────┐ - │ np.False_ │ - └───────────┘ - >>> (t.arr == 2).notall(where=t.arr >= 2) - ┌──────────┐ - │ np.True_ │ - └──────────┘ - """ - return ~self.all(where=where) - - def cumany(self, *, where=None, group_by=None, order_by=None) -> BooleanColumn: - """Accumulate the `any` aggregate. - - Returns - ------- - BooleanColumn - A boolean column with the cumulative `any` aggregate. - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.memtable({"arr": [1, 2, 3, 4]}) - >>> ((t.arr > 1) | (t.arr >= 1)).cumany() - ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ Any(Or(Greater(arr, 1), GreaterEqual(arr, 1))) ┃ - ┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩ - │ boolean │ - ├────────────────────────────────────────────────┤ - │ True │ - │ True │ - │ True │ - │ True │ - └────────────────────────────────────────────────┘ - >>> ((t.arr > 1) & (t.arr >= 1)).cumany() - ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ Any(And(Greater(arr, 1), GreaterEqual(arr, 1))) ┃ - ┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩ - │ boolean │ - ├─────────────────────────────────────────────────┤ - │ False │ - │ True │ - │ True │ - │ True │ - └─────────────────────────────────────────────────┘ - """ - return self.any(where=where).over( - bigframes_vendored.ibis.cumulative_window( - group_by=group_by, order_by=order_by - ) - ) - - def cumall(self, *, where=None, group_by=None, order_by=None) -> BooleanColumn: - """Accumulate the `all` aggregate. - - Returns - ------- - BooleanColumn - A boolean column with the cumulative `all` aggregate. - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.memtable({"arr": [1, 2, 3, 4]}) - >>> ((t.arr > 1) & (t.arr >= 1)).cumall() - ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ All(And(Greater(arr, 1), GreaterEqual(arr, 1))) ┃ - ┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩ - │ boolean │ - ├─────────────────────────────────────────────────┤ - │ False │ - │ False │ - │ False │ - │ False │ - └─────────────────────────────────────────────────┘ - >>> ((t.arr > 0) & (t.arr >= 1)).cumall() - ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ All(And(Greater(arr, 0), GreaterEqual(arr, 1))) ┃ - ┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩ - │ boolean │ - ├─────────────────────────────────────────────────┤ - │ True │ - │ True │ - │ True │ - │ True │ - └─────────────────────────────────────────────────┘ - """ - return self.all(where=where).over( - bigframes_vendored.ibis.cumulative_window( - group_by=group_by, order_by=order_by - ) - ) diff --git a/third_party/bigframes_vendored/ibis/expr/types/maps.py b/third_party/bigframes_vendored/ibis/expr/types/maps.py deleted file mode 100644 index 0be7241b241..00000000000 --- a/third_party/bigframes_vendored/ibis/expr/types/maps.py +++ /dev/null @@ -1,494 +0,0 @@ -# Contains code from https://github.com/ibis-project/ibis/blob/9.2.0/ibis/expr/types/maps.py - -from __future__ import annotations - -from typing import TYPE_CHECKING, Any - -import bigframes_vendored.ibis.expr.operations as ops -from bigframes_vendored.ibis.common.deferred import deferrable -from bigframes_vendored.ibis.expr.types.generic import Column, Scalar, Value -from public import public - -if TYPE_CHECKING: - from collections.abc import Iterable, Mapping - - import bigframes_vendored.ibis.expr.types as ir - from bigframes_vendored.ibis.expr.types.arrays import ArrayValue - - -@public -class MapValue(Value): - """A dict-like collection with fixed-type keys and values. - - Maps are similar to a Python dictionary, with the restriction that all keys - must have the same type, and all values must have the same type. - - The key type and the value type can be different. - - For example, keys are `string`s, and values are `int64`s. - - Keys are unique within a given map value. - - Maps can be constructed with [`ibis.map()`](#ibis.expr.types.map). - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> tab = pa.table( - ... { - ... "m": pa.array( - ... [[("a", 1), ("b", 2)], [("a", 1)], None], - ... type=pa.map_(pa.utf8(), pa.int64()), - ... ) - ... } - ... ) - >>> t = ibis.memtable(tab) - >>> t - ┏━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ m ┃ - ┡━━━━━━━━━━━━━━━━━━━━━━┩ - │ map │ - ├──────────────────────┤ - │ {'a': 1, 'b': 2} │ - │ {'a': 1} │ - │ NULL │ - └──────────────────────┘ - - Can use `[]` to access values: - >>> t.m["a"] - ┏━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ MapGet(m, 'a', None) ┃ - ┡━━━━━━━━━━━━━━━━━━━━━━┩ - │ int64 │ - ├──────────────────────┤ - │ 1 │ - │ 1 │ - │ NULL │ - └──────────────────────┘ - - To provide default values, use `get`: - >>> t.m.get("b", 0) - ┏━━━━━━━━━━━━━━━━━━━┓ - ┃ MapGet(m, 'b', 0) ┃ - ┡━━━━━━━━━━━━━━━━━━━┩ - │ int64 │ - ├───────────────────┤ - │ 2 │ - │ 0 │ - │ NULL │ - └───────────────────┘ - """ - - def get(self, key: ir.Value, default: ir.Value | None = None) -> ir.Value: - """Return the value for `key` from `expr`. - - Return `default` if `key` is not in the map. - - Parameters - ---------- - key - Expression to use for key - default - Expression to return if `key` is not a key in `expr` - - Returns - ------- - Value - The element type of `self` - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> tab = pa.table( - ... { - ... "m": pa.array( - ... [[("a", 1), ("b", 2)], [("a", 1)], None], - ... type=pa.map_(pa.utf8(), pa.int64()), - ... ) - ... } - ... ) - >>> t = ibis.memtable(tab) - >>> t - ┏━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ m ┃ - ┡━━━━━━━━━━━━━━━━━━━━━━┩ - │ map │ - ├──────────────────────┤ - │ {'a': 1, 'b': 2} │ - │ {'a': 1} │ - │ NULL │ - └──────────────────────┘ - >>> t.m.get("a") - ┏━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ MapGet(m, 'a', None) ┃ - ┡━━━━━━━━━━━━━━━━━━━━━━┩ - │ int64 │ - ├──────────────────────┤ - │ 1 │ - │ 1 │ - │ NULL │ - └──────────────────────┘ - >>> t.m.get("b") - ┏━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ MapGet(m, 'b', None) ┃ - ┡━━━━━━━━━━━━━━━━━━━━━━┩ - │ int64 │ - ├──────────────────────┤ - │ 2 │ - │ NULL │ - │ NULL │ - └──────────────────────┘ - >>> t.m.get("b", 0) - ┏━━━━━━━━━━━━━━━━━━━┓ - ┃ MapGet(m, 'b', 0) ┃ - ┡━━━━━━━━━━━━━━━━━━━┩ - │ int64 │ - ├───────────────────┤ - │ 2 │ - │ 0 │ - │ NULL │ - └───────────────────┘ - """ - - return ops.MapGet(self, key, default).to_expr() - - def length(self) -> ir.IntegerValue: - """Return the number of key-value pairs in the map. - - Returns - ------- - IntegerValue - The number of elements in `self` - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> tab = pa.table( - ... { - ... "m": pa.array( - ... [[("a", 1), ("b", 2)], [("a", 1)], None], - ... type=pa.map_(pa.utf8(), pa.int64()), - ... ) - ... } - ... ) - >>> t = ibis.memtable(tab) - >>> t - ┏━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ m ┃ - ┡━━━━━━━━━━━━━━━━━━━━━━┩ - │ map │ - ├──────────────────────┤ - │ {'a': 1, 'b': 2} │ - │ {'a': 1} │ - │ NULL │ - └──────────────────────┘ - >>> t.m.length() - ┏━━━━━━━━━━━━━━┓ - ┃ MapLength(m) ┃ - ┡━━━━━━━━━━━━━━┩ - │ int64 │ - ├──────────────┤ - │ 2 │ - │ 1 │ - │ NULL │ - └──────────────┘ - """ - return ops.MapLength(self).to_expr() - - def __getitem__(self, key: ir.Value) -> ir.Value: - """Get the value for a given map `key`. - - ::: {.callout-note} - ## This operation may have different semantics depending on the backend. - - Some backends return `NULL` when a key is missing, others may fail - the query. - ::: - - Parameters - ---------- - key - A map key - - Returns - ------- - Value - An element with the value type of the map - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> tab = pa.table( - ... { - ... "m": pa.array( - ... [[("a", 1), ("b", 2)], [("a", 1)], None], - ... type=pa.map_(pa.utf8(), pa.int64()), - ... ) - ... } - ... ) - >>> t = ibis.memtable(tab) - >>> t - ┏━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ m ┃ - ┡━━━━━━━━━━━━━━━━━━━━━━┩ - │ map │ - ├──────────────────────┤ - │ {'a': 1, 'b': 2} │ - │ {'a': 1} │ - │ NULL │ - └──────────────────────┘ - >>> t.m["a"] - ┏━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ MapGet(m, 'a', None) ┃ - ┡━━━━━━━━━━━━━━━━━━━━━━┩ - │ int64 │ - ├──────────────────────┤ - │ 1 │ - │ 1 │ - │ NULL │ - └──────────────────────┘ - """ - return ops.MapGet(self, key).to_expr() - - def contains( - self, key: int | str | ir.IntegerValue | ir.StringValue - ) -> ir.BooleanValue: - """Return whether the map contains `key`. - - Parameters - ---------- - key - Mapping key for which to check - - Returns - ------- - BooleanValue - Boolean indicating the presence of `key` in the map expression - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> tab = pa.table( - ... { - ... "m": pa.array( - ... [[("a", 1), ("b", 2)], [("a", 1)], None], - ... type=pa.map_(pa.utf8(), pa.int64()), - ... ) - ... } - ... ) - >>> t = ibis.memtable(tab) - >>> t - ┏━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ m ┃ - ┡━━━━━━━━━━━━━━━━━━━━━━┩ - │ map │ - ├──────────────────────┤ - │ {'a': 1, 'b': 2} │ - │ {'a': 1} │ - │ NULL │ - └──────────────────────┘ - >>> t.m.contains("b") - ┏━━━━━━━━━━━━━━━━━━━━━┓ - ┃ MapContains(m, 'b') ┃ - ┡━━━━━━━━━━━━━━━━━━━━━┩ - │ boolean │ - ├─────────────────────┤ - │ True │ - │ False │ - │ NULL │ - └─────────────────────┘ - """ - return ops.MapContains(self, key).to_expr() - - def keys(self) -> ir.ArrayValue: - """Extract the keys of a map. - - Returns - ------- - ArrayValue - The keys of `self` - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> tab = pa.table( - ... { - ... "m": pa.array( - ... [[("a", 1), ("b", 2)], [("a", 1)], None], - ... type=pa.map_(pa.utf8(), pa.int64()), - ... ) - ... } - ... ) - >>> t = ibis.memtable(tab) - >>> t - ┏━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ m ┃ - ┡━━━━━━━━━━━━━━━━━━━━━━┩ - │ map │ - ├──────────────────────┤ - │ {'a': 1, 'b': 2} │ - │ {'a': 1} │ - │ NULL │ - └──────────────────────┘ - >>> t.m.keys() - ┏━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ MapKeys(m) ┃ - ┡━━━━━━━━━━━━━━━━━━━━━━┩ - │ array │ - ├──────────────────────┤ - │ ['a', 'b'] │ - │ ['a'] │ - │ NULL │ - └──────────────────────┘ - """ - return ops.MapKeys(self).to_expr() - - def values(self) -> ir.ArrayValue: - """Extract the values of a map. - - Returns - ------- - ArrayValue - The values of `self` - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> m = ibis.map({"a": 1, "b": 2}) - >>> m.values() - ┌────────┐ - │ [1, 2] │ - └────────┘ - """ - return ops.MapValues(self).to_expr() - - def __add__(self, other: MapValue) -> MapValue: - """Concatenate this map with another. - - Parameters - ---------- - other - Map to concatenate with `self` - - Returns - ------- - MapValue - `self` concatenated with `other` - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> m1 = ibis.map({"a": 1, "b": 2}) - >>> m2 = ibis.map({"c": 3, "d": 4}) - >>> m1 + m2 - ┌──────────────────────────┐ - │ {'a': 1, 'b': 2, ... +2} │ - └──────────────────────────┘ - """ - return ops.MapMerge(self, other).to_expr() - - def __radd__(self, other: MapValue) -> MapValue: - """Concatenate this map with another. - - Parameters - ---------- - other - Map to concatenate with `self` - - Returns - ------- - MapValue - `self` concatenated with `other` - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> m1 = ibis.map({"a": 1, "b": 2}) - >>> m2 = ibis.map({"c": 3, "d": 4}) - >>> m1 + m2 - ┌──────────────────────────┐ - │ {'a': 1, 'b': 2, ... +2} │ - └──────────────────────────┘ - """ - return ops.MapMerge(self, other).to_expr() - - -@public -class MapScalar(Scalar, MapValue): - pass - - -@public -class MapColumn(Column, MapValue): - def __getitem__(self, key: ir.Value) -> ir.Column: - return MapValue.__getitem__(self, key) - - -@public -@deferrable -def map( - keys: Iterable[Any] | Mapping[Any, Any] | ArrayValue, - values: Iterable[Any] | ArrayValue | None = None, -) -> MapValue: - """Create a MapValue. - - If any of the `keys` or `values` are Columns, then the output will be a MapColumn. - Otherwise, the output will be a MapScalar. - - Parameters - ---------- - keys - Keys of the map or `Mapping`. If `keys` is a `Mapping`, `values` must be `None`. - values - Values of the map or `None`. If `None`, the `keys` argument must be a `Mapping`. - - Returns - ------- - MapValue - Either a MapScalar or MapColumn, depending on the input shapes. - - Examples - -------- - Create a Map scalar from a dict with the type inferred - - >>> import ibis - >>> ibis.options.interactive = True - >>> ibis.map(dict(a=1, b=2)) - ┌──────────────────┐ - │ {'a': 1, 'b': 2} │ - └──────────────────┘ - - Create a Map Column from columns with keys and values - - >>> t = ibis.memtable({"keys": [["a", "b"], ["b"]], "values": [[1, 2], [3]]}) - >>> t - ┏━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ keys ┃ values ┃ - ┡━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━┩ - │ array │ array │ - ├──────────────────────┼──────────────────────┤ - │ ['a', 'b'] │ [1, 2] │ - │ ['b'] │ [3] │ - └──────────────────────┴──────────────────────┘ - >>> ibis.map(t.keys, t.values) - ┏━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ Map(keys, values) ┃ - ┡━━━━━━━━━━━━━━━━━━━━━━┩ - │ map │ - ├──────────────────────┤ - │ {'a': 1, 'b': 2} │ - │ {'b': 3} │ - └──────────────────────┘ - """ - if values is None: - keys, values = tuple(keys.keys()), tuple(keys.values()) - return ops.Map(keys, values).to_expr() diff --git a/third_party/bigframes_vendored/ibis/expr/types/numeric.py b/third_party/bigframes_vendored/ibis/expr/types/numeric.py deleted file mode 100644 index 84ef30b9f80..00000000000 --- a/third_party/bigframes_vendored/ibis/expr/types/numeric.py +++ /dev/null @@ -1,1219 +0,0 @@ -# Contains code from https://github.com/ibis-project/ibis/blob/9.2.0/ibis/expr/types/numeric.py - -from __future__ import annotations - -import functools -from typing import TYPE_CHECKING, Literal - -import bigframes_vendored.ibis -import bigframes_vendored.ibis.expr.operations as ops -from bigframes_vendored.ibis.common.exceptions import IbisTypeError -from bigframes_vendored.ibis.expr.types.core import _binop -from bigframes_vendored.ibis.expr.types.generic import Column, Scalar, Value -from public import public - -if TYPE_CHECKING: - from collections.abc import Iterable, Sequence - - import bigframes_vendored.ibis.expr.types as ir - - -@public -class NumericValue(Value): - def negate(self) -> NumericValue: - """Negate a numeric expression. - - Returns - ------- - NumericValue - A numeric value expression - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.memtable({"values": [-1, 0, 1]}) - >>> t.values.negate() - ┏━━━━━━━━━━━━━━━━┓ - ┃ Negate(values) ┃ - ┡━━━━━━━━━━━━━━━━┩ - │ int64 │ - ├────────────────┤ - │ 1 │ - │ 0 │ - │ -1 │ - └────────────────┘ - """ - return ops.Negate(self).to_expr() - - def __neg__(self) -> NumericValue: - """Negate `self`. - - Returns - ------- - NumericValue - `self` negated - """ - return self.negate() - - def round(self, digits: int | IntegerValue | None = None) -> NumericValue: - """Round values to an indicated number of decimal places. - - Parameters - ---------- - digits - The number of digits to round to. - - Here's how the `digits` parameter affects the expression output - type: - - - `digits` is `False`-y; `self.type()` is `decimal` → `decimal` - - `digits` is nonzero; `self.type()` is `decimal` → `decimal` - - `digits` is `False`-y; `self.type()` is Floating → `int64` - - `digits` is nonzero; `self.type()` is Floating → `float64` - - Returns - ------- - NumericValue - The rounded expression - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.memtable({"values": [1.22, 1.64, 2.15, 2.54]}) - >>> t - ┏━━━━━━━━━┓ - ┃ values ┃ - ┡━━━━━━━━━┩ - │ float64 │ - ├─────────┤ - │ 1.22 │ - │ 1.64 │ - │ 2.15 │ - │ 2.54 │ - └─────────┘ - >>> t.values.round() - ┏━━━━━━━━━━━━━━━┓ - ┃ Round(values) ┃ - ┡━━━━━━━━━━━━━━━┩ - │ int64 │ - ├───────────────┤ - │ 1 │ - │ 2 │ - │ 2 │ - │ 3 │ - └───────────────┘ - >>> t.values.round(digits=1) - ┏━━━━━━━━━━━━━━━━━━┓ - ┃ Round(values, 1) ┃ - ┡━━━━━━━━━━━━━━━━━━┩ - │ float64 │ - ├──────────────────┤ - │ 1.2 │ - │ 1.6 │ - │ 2.2 │ - │ 2.5 │ - └──────────────────┘ - """ - return ops.Round(self, digits).to_expr() - - def log(self, base: NumericValue | None = None) -> NumericValue: - r"""Compute $\log_{\texttt{base}}\left(\texttt{self}\right)$. - - Parameters - ---------- - base - The base of the logarithm. If `None`, base `e` is used. - - Returns - ------- - NumericValue - Logarithm of `arg` with base `base` - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> from math import e - >>> t = ibis.memtable({"values": [e, e**2, e**3]}) - >>> t.values.log() - ┏━━━━━━━━━━━━━┓ - ┃ Log(values) ┃ - ┡━━━━━━━━━━━━━┩ - │ float64 │ - ├─────────────┤ - │ 1.0 │ - │ 2.0 │ - │ 3.0 │ - └─────────────┘ - - - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.memtable({"values": [10, 100, 1000]}) - >>> t.values.log(base=10) - ┏━━━━━━━━━━━━━━━━━┓ - ┃ Log(values, 10) ┃ - ┡━━━━━━━━━━━━━━━━━┩ - │ float64 │ - ├─────────────────┤ - │ 1.0 │ - │ 2.0 │ - │ 3.0 │ - └─────────────────┘ - """ - return ops.Log(self, base).to_expr() - - def clip( - self, - lower: NumericValue | None = None, - upper: NumericValue | None = None, - ) -> NumericValue: - """Trim values outside of `lower` and `upper` bounds. - - `NULL` values are preserved and are not replaced with bounds. - - Parameters - ---------- - lower - Lower bound - upper - Upper bound - - Returns - ------- - NumericValue - Clipped input - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.memtable( - ... {"values": [None, 2, 3, None, 5, None, None, 8]}, - ... schema=dict(values="int"), - ... ) - >>> t.values.clip(lower=3, upper=6) - ┏━━━━━━━━━━━━━━━━━━━━┓ - ┃ Clip(values, 3, 6) ┃ - ┡━━━━━━━━━━━━━━━━━━━━┩ - │ int64 │ - ├────────────────────┤ - │ NULL │ - │ 3 │ - │ 3 │ - │ NULL │ - │ 5 │ - │ NULL │ - │ NULL │ - │ 6 │ - └────────────────────┘ - """ - if lower is None and upper is None: - raise ValueError("at least one of lower and upper must be provided") - - return ops.Clip(self, lower, upper).to_expr() - - def abs(self) -> NumericValue: - """Return the absolute value of `self`. - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.memtable({"values": [-1, 2, -3, 4]}) - >>> t.values.abs() - ┏━━━━━━━━━━━━━┓ - ┃ Abs(values) ┃ - ┡━━━━━━━━━━━━━┩ - │ int64 │ - ├─────────────┤ - │ 1 │ - │ 2 │ - │ 3 │ - │ 4 │ - └─────────────┘ - """ - return ops.Abs(self).to_expr() - - def ceil(self) -> DecimalValue | IntegerValue: - """Return the ceiling of `self`. - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.memtable({"values": [1, 1.1, 2, 2.1, 3.3]}) - >>> t.values.ceil() - ┏━━━━━━━━━━━━━━┓ - ┃ Ceil(values) ┃ - ┡━━━━━━━━━━━━━━┩ - │ int64 │ - ├──────────────┤ - │ 1 │ - │ 2 │ - │ 2 │ - │ 3 │ - │ 4 │ - └──────────────┘ - """ - return ops.Ceil(self).to_expr() - - def degrees(self) -> NumericValue: - """Compute the degrees of `self` radians. - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> from math import pi - >>> t = ibis.memtable({"values": [0, pi / 2, pi, 3 * pi / 2, 2 * pi]}) - >>> t.values.degrees() - ┏━━━━━━━━━━━━━━━━━┓ - ┃ Degrees(values) ┃ - ┡━━━━━━━━━━━━━━━━━┩ - │ float64 │ - ├─────────────────┤ - │ 0.0 │ - │ 90.0 │ - │ 180.0 │ - │ 270.0 │ - │ 360.0 │ - └─────────────────┘ - """ - return ops.Degrees(self).to_expr() - - rad2deg = degrees - - def exp(self) -> NumericValue: - r"""Compute $e^\texttt{self}$. - - Returns - ------- - NumericValue - $e^\texttt{self}$ - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.memtable({"values": range(4)}) - >>> t.values.exp() - ┏━━━━━━━━━━━━━┓ - ┃ Exp(values) ┃ - ┡━━━━━━━━━━━━━┩ - │ float64 │ - ├─────────────┤ - │ 1.000000 │ - │ 2.718282 │ - │ 7.389056 │ - │ 20.085537 │ - └─────────────┘ - """ - return ops.Exp(self).to_expr() - - def floor(self) -> DecimalValue | IntegerValue: - """Return the floor of an expression. - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.memtable({"values": [1, 1.1, 2, 2.1, 3.3]}) - >>> t.values.floor() - ┏━━━━━━━━━━━━━━━┓ - ┃ Floor(values) ┃ - ┡━━━━━━━━━━━━━━━┩ - │ int64 │ - ├───────────────┤ - │ 1 │ - │ 1 │ - │ 2 │ - │ 2 │ - │ 3 │ - └───────────────┘ - - """ - return ops.Floor(self).to_expr() - - def log2(self) -> NumericValue: - r"""Compute $\log_{2}\left(\texttt{self}\right)$. - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.memtable({"values": [1, 2, 4, 8]}) - >>> t.values.log2() - ┏━━━━━━━━━━━━━━┓ - ┃ Log2(values) ┃ - ┡━━━━━━━━━━━━━━┩ - │ float64 │ - ├──────────────┤ - │ 0.0 │ - │ 1.0 │ - │ 2.0 │ - │ 3.0 │ - └──────────────┘ - """ - return ops.Log2(self).to_expr() - - def log10(self) -> NumericValue: - r"""Compute $\log_{10}\left(\texttt{self}\right)$. - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.memtable({"values": [1, 10, 100]}) - >>> t.values.log10() - ┏━━━━━━━━━━━━━━━┓ - ┃ Log10(values) ┃ - ┡━━━━━━━━━━━━━━━┩ - │ float64 │ - ├───────────────┤ - │ 0.0 │ - │ 1.0 │ - │ 2.0 │ - └───────────────┘ - """ - return ops.Log10(self).to_expr() - - def ln(self) -> NumericValue: - r"""Compute $\ln\left(\texttt{self}\right)$. - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.memtable({"values": [1, 2.718281828, 3]}) - >>> t.values.ln() - ┏━━━━━━━━━━━━┓ - ┃ Ln(values) ┃ - ┡━━━━━━━━━━━━┩ - │ float64 │ - ├────────────┤ - │ 0.000000 │ - │ 1.000000 │ - │ 1.098612 │ - └────────────┘ - """ - return ops.Ln(self).to_expr() - - def radians(self) -> NumericValue: - """Compute radians from `self` degrees. - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.memtable({"values": [0, 90, 180, 270, 360]}) - >>> t.values.radians() - ┏━━━━━━━━━━━━━━━━━┓ - ┃ Radians(values) ┃ - ┡━━━━━━━━━━━━━━━━━┩ - │ float64 │ - ├─────────────────┤ - │ 0.000000 │ - │ 1.570796 │ - │ 3.141593 │ - │ 4.712389 │ - │ 6.283185 │ - └─────────────────┘ - """ - return ops.Radians(self).to_expr() - - deg2rad = radians - - def sign(self) -> NumericValue: - """Return the sign of the input. - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.memtable({"values": [-1, 2, -3, 4]}) - >>> t.values.sign() - ┏━━━━━━━━━━━━━━┓ - ┃ Sign(values) ┃ - ┡━━━━━━━━━━━━━━┩ - │ int64 │ - ├──────────────┤ - │ -1 │ - │ 1 │ - │ -1 │ - │ 1 │ - └──────────────┘ - """ - return ops.Sign(self).to_expr() - - def sqrt(self) -> NumericValue: - """Compute the square root of `self`. - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.memtable({"values": [1, 4, 9, 16]}) - >>> t.values.sqrt() - ┏━━━━━━━━━━━━━━┓ - ┃ Sqrt(values) ┃ - ┡━━━━━━━━━━━━━━┩ - │ float64 │ - ├──────────────┤ - │ 1.0 │ - │ 2.0 │ - │ 3.0 │ - │ 4.0 │ - └──────────────┘ - """ - return ops.Sqrt(self).to_expr() - - def acos(self) -> NumericValue: - """Compute the arc cosine of `self`. - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.memtable({"values": [-1, 0, 1]}) - >>> t.values.acos() - ┏━━━━━━━━━━━━━━┓ - ┃ Acos(values) ┃ - ┡━━━━━━━━━━━━━━┩ - │ float64 │ - ├──────────────┤ - │ 3.141593 │ - │ 1.570796 │ - │ 0.000000 │ - └──────────────┘ - - """ - return ops.Acos(self).to_expr() - - def asin(self) -> NumericValue: - """Compute the arc sine of `self`. - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.memtable({"values": [-1, 0, 1]}) - >>> t.values.asin() - ┏━━━━━━━━━━━━━━┓ - ┃ Asin(values) ┃ - ┡━━━━━━━━━━━━━━┩ - │ float64 │ - ├──────────────┤ - │ -1.570796 │ - │ 0.000000 │ - │ 1.570796 │ - └──────────────┘ - """ - return ops.Asin(self).to_expr() - - def atan(self) -> NumericValue: - """Compute the arc tangent of `self`. - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.memtable({"values": [-1, 0, 1]}) - >>> t.values.atan() - ┏━━━━━━━━━━━━━━┓ - ┃ Atan(values) ┃ - ┡━━━━━━━━━━━━━━┩ - │ float64 │ - ├──────────────┤ - │ -0.785398 │ - │ 0.000000 │ - │ 0.785398 │ - └──────────────┘ - """ - return ops.Atan(self).to_expr() - - def atan2(self, other: NumericValue) -> NumericValue: - """Compute the two-argument version of arc tangent. - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.memtable({"values": [-1, 0, 1]}) - >>> t.values.atan2(0) - ┏━━━━━━━━━━━━━━━━━━┓ - ┃ Atan2(values, 0) ┃ - ┡━━━━━━━━━━━━━━━━━━┩ - │ float64 │ - ├──────────────────┤ - │ -1.570796 │ - │ 0.000000 │ - │ 1.570796 │ - └──────────────────┘ - """ - return ops.Atan2(self, other).to_expr() - - def cos(self) -> NumericValue: - """Compute the cosine of `self`. - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.memtable({"values": [-1, 0, 1]}) - >>> t.values.cos() - ┏━━━━━━━━━━━━━┓ - ┃ Cos(values) ┃ - ┡━━━━━━━━━━━━━┩ - │ float64 │ - ├─────────────┤ - │ 0.540302 │ - │ 1.000000 │ - │ 0.540302 │ - └─────────────┘ - """ - return ops.Cos(self).to_expr() - - def cot(self) -> NumericValue: - """Compute the cotangent of `self`. - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.memtable({"values": [-1, -2, 3]}) - >>> t.values.cot() - ┏━━━━━━━━━━━━━┓ - ┃ Cot(values) ┃ - ┡━━━━━━━━━━━━━┩ - │ float64 │ - ├─────────────┤ - │ -0.642093 │ - │ 0.457658 │ - │ -7.015253 │ - └─────────────┘ - """ - return ops.Cot(self).to_expr() - - def sin(self) -> NumericValue: - """Compute the sine of `self`. - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.memtable({"values": [-1, 0, 1]}) - >>> t.values.sin() - ┏━━━━━━━━━━━━━┓ - ┃ Sin(values) ┃ - ┡━━━━━━━━━━━━━┩ - │ float64 │ - ├─────────────┤ - │ -0.841471 │ - │ 0.000000 │ - │ 0.841471 │ - └─────────────┘ - """ - return ops.Sin(self).to_expr() - - def tan(self) -> NumericValue: - """Compute the tangent of `self`. - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.memtable({"values": [-1, 0, 1]}) - >>> t.values.tan() - ┏━━━━━━━━━━━━━┓ - ┃ Tan(values) ┃ - ┡━━━━━━━━━━━━━┩ - │ float64 │ - ├─────────────┤ - │ -1.557408 │ - │ 0.000000 │ - │ 1.557408 │ - └─────────────┘ - """ - return ops.Tan(self).to_expr() - - def __add__(self, other: NumericValue) -> NumericValue: - """Add `self` with `other`.""" - return _binop(ops.Add, self, other) - - add = radd = __radd__ = __add__ - - def __sub__(self, other: NumericValue) -> NumericValue: - """Subtract `other` from `self`.""" - return _binop(ops.Subtract, self, other) - - sub = __sub__ - - def __rsub__(self, other: NumericValue) -> NumericValue: - """Subtract `self` from `other`.""" - return _binop(ops.Subtract, other, self) - - rsub = __rsub__ - - def __mul__(self, other: NumericValue) -> NumericValue: - """Multiply `self` and `other`.""" - return _binop(ops.Multiply, self, other) - - mul = rmul = __rmul__ = __mul__ - - def __truediv__(self, other): - """Divide `self` by `other`.""" - return _binop(ops.Divide, self, other) - - div = __div__ = __truediv__ - - def __rtruediv__(self, other: NumericValue) -> NumericValue: - """Divide `other` by `self`.""" - return _binop(ops.Divide, other, self) - - rdiv = __rdiv__ = __rtruediv__ - - def __floordiv__( - self, - other: NumericValue, - ) -> NumericValue: - """Floor divide `self` by `other`.""" - return _binop(ops.FloorDivide, self, other) - - floordiv = __floordiv__ - - def __rfloordiv__( - self, - other: NumericValue, - ) -> NumericValue: - """Floor divide `other` by `self`.""" - return _binop(ops.FloorDivide, other, self) - - rfloordiv = __rfloordiv__ - - def __pow__(self, other: NumericValue) -> NumericValue: - """Raise `self` to the `other`th power.""" - return _binop(ops.Power, self, other) - - pow = __pow__ - - def __rpow__(self, other: NumericValue) -> NumericValue: - """Raise `other` to the `self`th power.""" - return _binop(ops.Power, other, self) - - rpow = __rpow__ - - def __mod__(self, other: NumericValue) -> NumericValue: - """Compute `self` modulo `other`.""" - return _binop(ops.Modulus, self, other) - - mod = __mod__ - - def __rmod__(self, other: NumericValue) -> NumericValue: - """Compute `other` modulo `self`.""" - - return _binop(ops.Modulus, other, self) - - rmod = __rmod__ - - def point(self, right: int | float | NumericValue) -> ir.PointValue: - """Return a point constructed from the coordinate values. - - Constant coordinates result in construction of a `POINT` literal or - column. - - Parameters - ---------- - right - Y coordinate - - Returns - ------- - PointValue - Points - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.examples.zones.fetch() - >>> t.x_cent.point(t.y_cent) - ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ GeoPoint(x_cent, y_cent) ┃ - ┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩ - │ point:geometry │ - ├──────────────────────────────────┤ - │ │ - │ │ - │ │ - │ │ - │ │ - │ │ - │ │ - │ │ - │ │ - │ │ - │ … │ - └──────────────────────────────────┘ - """ - return ops.GeoPoint(self, right).to_expr() - - -@public -class NumericScalar(Scalar, NumericValue): - pass - - -@public -class NumericColumn(Column, NumericValue): - def std( - self, - where: ir.BooleanValue | None = None, - how: Literal["sample", "pop"] = "sample", - ) -> NumericScalar: - """Return the standard deviation of a numeric column. - - Parameters - ---------- - where - Filter - how - Sample or population standard deviation - - Returns - ------- - NumericScalar - Standard deviation of `arg` - """ - return ops.StandardDev( - self, how=how, where=self._bind_to_parent_table(where) - ).to_expr() - - def var( - self, - where: ir.BooleanValue | None = None, - how: Literal["sample", "pop"] = "sample", - ) -> NumericScalar: - """Return the variance of a numeric column. - - Parameters - ---------- - where - Filter - how - Sample or population variance - - Returns - ------- - NumericScalar - Standard deviation of `arg` - """ - return ops.Variance( - self, how=how, where=self._bind_to_parent_table(where) - ).to_expr() - - def corr( - self, - right: NumericColumn, - where: ir.BooleanValue | None = None, - how: Literal["sample", "pop"] = "sample", - ) -> NumericScalar: - """Return the correlation of two numeric columns. - - Parameters - ---------- - right - Numeric column - where - Filter - how - Population or sample correlation - - Returns - ------- - NumericScalar - The correlation of `left` and `right` - """ - return ops.Correlation( - self, - self._bind_to_parent_table(right), - how=how, - where=self._bind_to_parent_table(where), - ).to_expr() - - def cov( - self, - right: NumericColumn, - where: ir.BooleanValue | None = None, - how: Literal["sample", "pop"] = "sample", - ) -> NumericScalar: - """Return the covariance of two numeric columns. - - Parameters - ---------- - right - Numeric column - where - Filter - how - Population or sample covariance - - Returns - ------- - NumericScalar - The covariance of `self` and `right` - """ - return ops.Covariance( - self, - self._bind_to_parent_table(right), - how=how, - where=self._bind_to_parent_table(where), - ).to_expr() - - def mean( - self, - where: ir.BooleanValue | None = None, - ) -> NumericScalar: - """Return the mean of a numeric column. - - Parameters - ---------- - where - Filter - - Returns - ------- - NumericScalar - The mean of the input expression - """ - # TODO(kszucs): remove the alias from the reduction method in favor - # of default name generated by ops.Value operations - return ops.Mean(self, where=self._bind_to_parent_table(where)).to_expr() - - def cummean(self, *, where=None, group_by=None, order_by=None) -> NumericColumn: - """Return the cumulative mean of the input.""" - return self.mean(where=where).over( - bigframes_vendored.ibis.cumulative_window( - group_by=group_by, order_by=order_by - ) - ) - - def sum( - self, - where: ir.BooleanValue | None = None, - ) -> NumericScalar: - """Return the sum of a numeric column. - - Parameters - ---------- - where - Filter - - Returns - ------- - NumericScalar - The sum of the input expression - """ - return ops.Sum(self, where=self._bind_to_parent_table(where)).to_expr() - - def cumsum(self, *, where=None, group_by=None, order_by=None) -> NumericColumn: - """Return the cumulative sum of the input.""" - return self.sum(where=where).over( - bigframes_vendored.ibis.cumulative_window( - group_by=group_by, order_by=order_by - ) - ) - - def bucket( - self, - buckets: Sequence[int], - closed: Literal["left", "right"] = "left", - close_extreme: bool = True, - include_under: bool = False, - include_over: bool = False, - ) -> ir.IntegerColumn: - """Compute a discrete binning of a numeric array. - - Parameters - ---------- - buckets - List of buckets - closed - Which side of each interval is closed. For example: - - ```python - buckets = [0, 100, 200] - closed = "left" # 100 falls in 2nd bucket - closed = "right" # 100 falls in 1st bucket - ``` - close_extreme - Whether the extreme values fall in the last bucket - include_over - Include values greater than the last bucket in the last bucket - include_under - Include values less than the first bucket in the first bucket - - Returns - ------- - IntegerColumn - A categorical column expression - """ - return ops.Bucket( - self, - buckets, - closed=closed, - close_extreme=close_extreme, - include_under=include_under, - include_over=include_over, - ).to_expr() - - def histogram( - self, - nbins: int | None = None, - binwidth: float | None = None, - base: float | None = None, - eps: float = 1e-13, - ): - """Compute a histogram with fixed width bins. - - Parameters - ---------- - nbins - If supplied, will be used to compute the binwidth - binwidth - If not supplied, computed from the data (actual max and min values) - base - The value of the first histogram bin. Defaults to the minimum value - of `column`. - eps - Allowed floating point epsilon for histogram base - - Returns - ------- - Column - Bucketed column - """ - - if nbins is not None and binwidth is not None: - raise ValueError( - f"Cannot pass both `nbins` (got {nbins}) and `binwidth` (got {binwidth})" - ) - - if binwidth is None or base is None: - if nbins is None: - raise ValueError("`nbins` is required if `binwidth` is not provided") - - if base is None: - base = self.min() - eps - - binwidth = (self.max() - base) / nbins - - return ((self - base) / binwidth).floor() - - -@public -class IntegerValue(NumericValue): - def to_timestamp( - self, - unit: Literal["s", "ms", "us"] = "s", - ) -> ir.TimestampValue: - """Convert an integral UNIX timestamp to a timestamp expression. - - Parameters - ---------- - unit - The resolution of `arg` - - Returns - ------- - TimestampValue - `self` converted to a timestamp - """ - return ops.TimestampFromUNIX(self, unit).to_expr() - - def to_interval( - self, - unit: Literal["Y", "M", "W", "D", "h", "m", "s", "ms", "us", "ns"] = "s", - ) -> ir.IntervalValue: - """Convert an integer to an interval. - - Parameters - ---------- - unit - Unit for the resulting interval - - Returns - ------- - IntervalValue - An interval in units of `unit` - """ - return ops.IntervalFromInteger(self, unit).to_expr() - - def convert_base( - self, - from_base: IntegerValue, - to_base: IntegerValue, - ) -> IntegerValue: - """Convert an integer from one base to another. - - Parameters - ---------- - from_base - Numeric base of expression - to_base - New base - - Returns - ------- - IntegerValue - Converted expression - """ - return ops.BaseConvert(self, from_base, to_base).to_expr() - - def __and__(self, other: IntegerValue) -> IntegerValue: - """Bitwise and `self` with `other`.""" - return _binop(ops.BitwiseAnd, self, other) - - __rand__ = __and__ - - def __or__(self, other: IntegerValue) -> IntegerValue: - """Bitwise or `self` with `other`.""" - return _binop(ops.BitwiseOr, self, other) - - __ror__ = __or__ - - def __xor__(self, other: IntegerValue) -> IntegerValue: - """Bitwise xor `self` with `other`.""" - return _binop(ops.BitwiseXor, self, other) - - __rxor__ = __xor__ - - def __lshift__(self, other: IntegerValue) -> IntegerValue: - """Bitwise left shift `self` with `other`.""" - return _binop(ops.BitwiseLeftShift, self, other) - - def __rlshift__(self, other: IntegerValue) -> IntegerValue: - """Bitwise left shift `self` with `other`.""" - return _binop(ops.BitwiseLeftShift, other, self) - - def __rshift__(self, other: IntegerValue) -> IntegerValue: - """Bitwise right shift `self` with `other`.""" - return _binop(ops.BitwiseRightShift, self, other) - - def __rrshift__(self, other: IntegerValue) -> IntegerValue: - """Bitwise right shift `self` with `other`.""" - return _binop(ops.BitwiseRightShift, other, self) - - def __invert__(self) -> IntegerValue: - """Bitwise not of `self`. - - Returns - ------- - IntegerValue - Inverted bits of `self`. - """ - try: - node = ops.BitwiseNot(self) - except (IbisTypeError, NotImplementedError): - return NotImplemented - else: - return node.to_expr() - - def label(self, labels: Iterable[str], nulls: str | None = None) -> ir.StringValue: - """Label a set of integer values with strings. - - Parameters - ---------- - labels - An iterable of string labels. Each integer value in `self` will be mapped to - a value in `labels`. - nulls - String label to use for `NULL` values - - Returns - ------- - StringValue - `self` labeled with `labels` - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.memtable({"a": [0, 1, 0, 2]}) - >>> t.select(t.a, labeled=t.a.label(["a", "b", "c"])) - ┏━━━━━━━┳━━━━━━━━━┓ - ┃ a ┃ labeled ┃ - ┡━━━━━━━╇━━━━━━━━━┩ - │ int64 │ string │ - ├───────┼─────────┤ - │ 0 │ a │ - │ 1 │ b │ - │ 0 │ a │ - │ 2 │ c │ - └───────┴─────────┘ - """ - return ( - functools.reduce( - lambda stmt, inputs: stmt.when(*inputs), enumerate(labels), self.case() - ) - .else_(nulls) - .end() - ) - - -@public -class IntegerScalar(NumericScalar, IntegerValue): - pass - - -@public -class IntegerColumn(NumericColumn, IntegerValue): - def bit_and(self, where: ir.BooleanValue | None = None) -> IntegerScalar: - """Aggregate the column using the bitwise and operator.""" - return ops.BitAnd(self, where=self._bind_to_parent_table(where)).to_expr() - - def bit_or(self, where: ir.BooleanValue | None = None) -> IntegerScalar: - """Aggregate the column using the bitwise or operator.""" - return ops.BitOr(self, where=self._bind_to_parent_table(where)).to_expr() - - def bit_xor(self, where: ir.BooleanValue | None = None) -> IntegerScalar: - """Aggregate the column using the bitwise exclusive or operator.""" - return ops.BitXor(self, where=self._bind_to_parent_table(where)).to_expr() - - -@public -class FloatingValue(NumericValue): - def isnan(self) -> ir.BooleanValue: - """Return whether the value is NaN.""" - return ops.IsNan(self).to_expr() - - def isinf(self) -> ir.BooleanValue: - """Return whether the value is infinity.""" - return ops.IsInf(self).to_expr() - - -@public -class FloatingScalar(NumericScalar, FloatingValue): - pass - - -@public -class FloatingColumn(NumericColumn, FloatingValue): - pass - - -@public -class DecimalValue(NumericValue): - pass - - -@public -class DecimalScalar(NumericScalar, DecimalValue): - pass - - -@public -class DecimalColumn(NumericColumn, DecimalValue): - pass diff --git a/third_party/bigframes_vendored/ibis/expr/types/pretty.py b/third_party/bigframes_vendored/ibis/expr/types/pretty.py deleted file mode 100644 index 22617d84615..00000000000 --- a/third_party/bigframes_vendored/ibis/expr/types/pretty.py +++ /dev/null @@ -1,487 +0,0 @@ -# Contains code from https://github.com/ibis-project/ibis/blob/9.2.0/ibis/expr/types/pretty.py - -from __future__ import annotations - -import datetime -import json -from functools import singledispatch -from math import isfinite -from typing import TYPE_CHECKING -from urllib.parse import urlparse - -import bigframes_vendored.ibis -import bigframes_vendored.ibis.expr.datatypes as dt -import rich -import rich.table -from rich import box -from rich.align import Align -from rich.panel import Panel -from rich.pretty import Pretty -from rich.text import Text - -if TYPE_CHECKING: - from bigframes_vendored.ibis.expr.types import Column, Expr, Scalar, Table - - -def _format_nested( - values, - *, - max_length: int | None = None, - max_string: int | None = None, - max_depth: int | None = None, -): - return [ - Pretty( - v, - max_length=max_length - or bigframes_vendored.ibis.options.repr.interactive.max_length, - max_string=max_string - or bigframes_vendored.ibis.options.repr.interactive.max_string, - max_depth=max_depth - or bigframes_vendored.ibis.options.repr.interactive.max_depth, - ) - for v in values - ] - - -@singledispatch -def format_values(dtype, values, **fmt_kwargs): - return _format_nested(values, **fmt_kwargs) - - -@format_values.register(dt.Map) -def _(dtype, values, **fmt_kwargs): - return _format_nested( - [None if v is None else dict(v) for v in values], **fmt_kwargs - ) - - -@format_values.register(dt.GeoSpatial) -def _(dtype, values, **fmt_kwargs): - import shapely - - return _format_nested( - [None if v is None else shapely.from_wkb(v) for v in values], **fmt_kwargs - ) - - -@format_values.register(dt.JSON) -def _(dtype, values, **fmt_kwargs): - def try_json(v): - if v is None: - return None - try: - return json.loads(v) - except Exception: # noqa: BLE001 - return v - - return _format_nested([try_json(v) for v in values], **fmt_kwargs) - - -@format_values.register(dt.Boolean) -@format_values.register(dt.UUID) -def _(dtype, values, **fmt_kwargs): - return [Text(str(v)) for v in values] - - -@format_values.register(dt.Decimal) -def _(dtype, values, **fmt_kwargs): - if dtype.scale is not None: - fmt = f"{{:.{dtype.scale}f}}" - return [Text.styled(fmt.format(v), "bold cyan") for v in values] - else: - # No scale specified, convert to float and repr that way - return format_values(dt.float64, [float(v) for v in values]) - - -@format_values.register(dt.Integer) -def _(dtype, values, **fmt_kwargs): - return [Text.styled(str(int(v)), "bold cyan") for v in values] - - -@format_values.register(dt.Floating) -def _(dtype, values, **fmt_kwargs): - floats = [float(v) for v in values] - # Extract and format all finite floats - finites = [f for f in floats if isfinite(f)] - if finites and all(f == 0 or 1e-6 < abs(f) < 1e6 for f in finites): - strs = [f"{f:f}" for f in finites] - # Trim matching trailing zeros - while all(s.endswith("0") for s in strs): - strs = [s[:-1] for s in strs] - strs = [s + "0" if s.endswith(".") else s for s in strs] - else: - strs = [f"{f:e}" for f in finites] - # Merge together the formatted finite floats with non-finite values - iterstrs = iter(strs) - strs2 = (next(iterstrs) if isfinite(f) else str(f) for f in floats) - return [Text.styled(s, "bold cyan") for s in strs2] - - -@format_values.register(dt.Timestamp) -def _(dtype, values, **fmt_kwargs): - if all(v.microsecond == 0 for v in values): - timespec = "seconds" - elif all(v.microsecond % 1000 == 0 for v in values): - timespec = "milliseconds" - else: - timespec = "microseconds" - return [ - Text.styled(v.isoformat(sep=" ", timespec=timespec), "magenta") for v in values - ] - - -@format_values.register(dt.Date) -def _(dtype, values, **fmt_kwargs): - dates = [v.date() if isinstance(v, datetime.datetime) else v for v in values] - return [Text.styled(d.isoformat(), "magenta") for d in dates] - - -@format_values.register(dt.Time) -def _(dtype, values, **fmt_kwargs): - times = [v.time() if isinstance(v, datetime.datetime) else v for v in values] - if all(t.microsecond == 0 for t in times): - timespec = "seconds" - elif all(t.microsecond % 1000 == 0 for t in times): - timespec = "milliseconds" - else: - timespec = "microseconds" - return [Text.styled(t.isoformat(timespec=timespec), "magenta") for t in times] - - -@format_values.register(dt.Interval) -def _(dtype, values, **fmt_kwargs): - return [Text.styled(str(v), "magenta") for v in values] - - -_str_escapes = str.maketrans( - { - "\t": r"[orange3]\t[/]", - "\r": r"[orange3]\r[/]", - "\n": r"[orange3]\n[/]", - "\v": r"[orange3]\v[/]", - "\f": r"[orange3]\f[/]", - } -) - - -@format_values.register(dt.String) -def _(dtype, values, *, max_string: int | None = None, **fmt_kwargs): - max_string = ( - max_string or bigframes_vendored.ibis.options.repr.interactive.max_string - ) - out = [] - for v in values: - v = str(v) - if v: - raw_v = v - if len(v) > max_string: - v = v[: max_string - 1] + "…" - v = v[:max_string] - # Escape all literal `[` so rich doesn't treat them as markup - v = v.replace("[", r"\[") - # Replace ascii escape characters dimmed versions of their repr - v = v.translate(_str_escapes) - if not v.isprintable(): - # display all unprintable characters as a dimmed version of - # their repr - v = "".join( - f"[dim]{repr(c)[1:-1]}[/]" if not c.isprintable() else c for c in v - ) - url = urlparse(raw_v) - # check both scheme and netloc to avoid rendering e.g., - # `https://` as link - if url.scheme and url.netloc: - v = f"[link={raw_v}]{v}[/link]" - text = Text.from_markup(v, style="green") - else: - text = Text.styled("~", "dim") - out.append(text) - return out - - -def format_column( - dtype, - values, - *, - max_length: int | None = None, - max_string: int | None = None, - max_depth: int | None = None, -): - import pandas as pd - - null_str = Text.styled("NULL", style="dim") - if dtype.is_floating(): - # We don't want to treat `nan` as `NULL` for floating point types - def isnull(x): - return x is None or x is pd.NA - - else: - - def isnull(x): - o = pd.isna(x) - # pd.isna broadcasts if `x` is an array - return o if isinstance(o, bool) else False - - nonnull = [v for v in values if not isnull(v)] - if nonnull: - formatted = format_values( - dtype, - nonnull, - max_length=max_length, - max_string=max_string, - max_depth=max_depth, - ) - next_f = iter(formatted).__next__ - out = [null_str if isnull(v) else next_f() for v in values] - else: - out = [null_str] * len(values) - - try: - max_width = max(map(len, out)) - except Exception: # noqa: BLE001 - max_width = None - min_width = 20 - else: - if dtype.is_string(): - min_width = min(20, max_width) - else: - min_width = max_width - - return out, min_width, max_width - - -def format_dtype(dtype, max_string: int) -> Text: - strtyp = str(dtype) - if len(strtyp) > max_string: - strtyp = strtyp[: max_string - 1] + "…" - return Text.styled(strtyp, "dim") - - -def to_rich( - expr: Expr, - *, - max_rows: int | None = None, - max_columns: int | None = None, - max_length: int | None = None, - max_string: int | None = None, - max_depth: int | None = None, - console_width: int | float | None = None, -) -> Pretty: - """Truncate, evaluate, and render an Ibis expression as a rich object.""" - from bigframes_vendored.ibis.expr.types import Scalar - - if isinstance(expr, Scalar): - return _to_rich_scalar( - expr, max_length=max_length, max_string=max_string, max_depth=max_depth - ) - else: - return _to_rich_table( - expr, - max_rows=max_rows, - max_columns=max_columns, - max_length=max_length, - max_string=max_string, - max_depth=max_depth, - console_width=console_width, - ) - - -def _to_rich_scalar( - expr: Scalar, - *, - max_length: int | None = None, - max_string: int | None = None, - max_depth: int | None = None, -) -> Pretty: - scalar = Pretty( - expr.execute(), - max_length=max_length - or bigframes_vendored.ibis.options.repr.interactive.max_length, - max_string=max_string - or bigframes_vendored.ibis.options.repr.interactive.max_string, - max_depth=max_depth - or bigframes_vendored.ibis.options.repr.interactive.max_depth, - ) - return Panel(scalar, expand=False, box=box.SQUARE) - - -def _to_rich_table( - tablish: Table | Column, - *, - max_rows: int | None = None, - max_columns: int | None = None, - max_length: int | None = None, - max_string: int | None = None, - max_depth: int | None = None, - console_width: int | float | None = None, -) -> rich.table.Table: - max_rows = max_rows or bigframes_vendored.ibis.options.repr.interactive.max_rows - max_columns = ( - max_columns or bigframes_vendored.ibis.options.repr.interactive.max_columns - ) - console_width = console_width or float("inf") - max_string = ( - max_string or bigframes_vendored.ibis.options.repr.interactive.max_string - ) - show_types = bigframes_vendored.ibis.options.repr.interactive.show_types - - table = tablish.as_table() - orig_ncols = len(table.columns) - - if console_width == float("inf"): - # When there's infinite display space, only subset columns - # if an explicit limit has been set. - if max_columns and max_columns < orig_ncols: - table = table.select(*table.columns[:max_columns]) - else: - # Determine the maximum subset of columns that *might* fit in the - # current console. Note that not every column here may actually fit - # later on once we know the repr'd width of the data. - computed_cols = [] - remaining = console_width - 1 # 1 char for left boundary - for c in table.columns: - needed = len(c) + 3 # padding + 1 char for right boundary - if ( - needed < remaining or not computed_cols - ): # always select at least one col - computed_cols.append(c) - remaining -= needed - else: - break - if max_columns not in (0, None): - # If an explicit limit on max columns is set, apply it - computed_cols = computed_cols[:max_columns] - if orig_ncols > len(computed_cols): - table = table.select(*computed_cols) - - result = table.limit(max_rows + 1).to_pyarrow() - # Now format the columns in order, stopping if the console width would - # be exceeded. - col_info = [] - col_data = [] - formatted_dtypes = [] - remaining = console_width - 1 # 1 char for left boundary - for name, dtype in table.schema().items(): - formatted, min_width, max_width = format_column( - dtype, - result[name].to_pylist()[:max_rows], - max_length=max_length, - max_string=max_string, - max_depth=max_depth, - ) - dtype_str = format_dtype(dtype, max_string) - if show_types and not isinstance(dtype, (dt.Struct, dt.Map, dt.Array)): - # Don't truncate non-nested dtypes - min_width = max(min_width, len(dtype_str)) - - min_width = max(min_width, len(name)) - if max_width is not None: - max_width = max(min_width, max_width) - needed = min_width + 3 # padding + 1 char for right boundary - if needed < remaining: - col_info.append((name, dtype, min_width, max_width)) - col_data.append(formatted) - formatted_dtypes.append(dtype_str) - remaining -= needed - elif not col_info: - # Always pretty print at least one column. If only one column, we - # truncate to fill the available space, leaving room for the - # ellipsis & framing. - min_width = remaining - 3 # 3 for framing - if orig_ncols > 1: - min_width -= 4 # 4 for ellipsis - col_info.append((name, dtype, min_width, min_width)) - col_data.append(formatted) - formatted_dtypes.append(dtype_str) - break - else: - if remaining < 4: - # Not enough space for ellipsis column, drop previous column - col_info.pop() - col_data.pop() - formatted_dtypes.pop() - break - - # rich's column width computations are super buggy and can result in tables - # that are much wider than the available console space. To work around this - # for now we manually compute all column widths rather than letting rich - # figure it out for us. - columns_truncated = orig_ncols > len(col_info) - col_widths = {} - if console_width == float("inf"): - # Always use the max_width if there's infinite console space - for name, _, _, max_width in col_info: - col_widths[name] = max_width - else: - # Allocate the remaining space evenly between the flexible columns - flex_cols = [] - remaining = console_width - 1 - if columns_truncated: - remaining -= 4 - for name, _, min_width, max_width in col_info: - remaining -= min_width + 3 - col_widths[name] = min_width - if min_width != max_width: - flex_cols.append((name, max_width)) - - while True: - next_flex_cols = [] - for name, max_width in flex_cols: - if remaining: - remaining -= 1 - if max_width is not None: - col_widths[name] += 1 - if max_width is None or col_widths[name] < max_width: - next_flex_cols.append((name, max_width)) - else: - break - if not next_flex_cols: - break - - rich_table = rich.table.Table(padding=(0, 1, 0, 1)) - - # Configure the columns on the rich table. - for name, dtype, _, max_width in col_info: - rich_table.add_column( - Align(name, align="left"), - justify="right" if dtype.is_numeric() else "left", - vertical="middle", - width=None if max_width is None else col_widths[name], - min_width=None if max_width is not None else col_widths[name], - no_wrap=max_width is not None, - ) - - # If the columns are truncated, add a trailing ellipsis column - if columns_truncated: - rich_table.add_column( - Align("…", align="left"), - justify="left", - vertical="middle", - width=1, - min_width=1, - no_wrap=True, - ) - - def add_row(*args, **kwargs): - rich_table.add_row(*args, Align("[dim]…[/]", align="left"), **kwargs) - - else: - add_row = rich_table.add_row - - if show_types: - add_row( - *(Align(s, align="left") for s in formatted_dtypes), - end_section=True, - ) - - for row in zip(*col_data): - add_row(*row) - - # If the rows are truncated, add a trailing ellipsis row - if len(result) > max_rows: - rich_table.add_row( - *(Align("[dim]…[/]", align=c.justify) for c in rich_table.columns) - ) - - return rich_table diff --git a/third_party/bigframes_vendored/ibis/expr/types/relations.py b/third_party/bigframes_vendored/ibis/expr/types/relations.py deleted file mode 100644 index 956bb95dfae..00000000000 --- a/third_party/bigframes_vendored/ibis/expr/types/relations.py +++ /dev/null @@ -1,4892 +0,0 @@ -# Contains code from https://github.com/ibis-project/ibis/blob/9.2.0/ibis/expr/types/relations.py - -from __future__ import annotations - -import itertools -import operator -import re -from collections import deque -from collections.abc import Callable, Iterable, Iterator, Mapping, Sequence -from keyword import iskeyword -from typing import TYPE_CHECKING, Any, Literal - -import bigframes_vendored.ibis -import bigframes_vendored.ibis.common.exceptions as com -import bigframes_vendored.ibis.expr.datatypes as dt -import bigframes_vendored.ibis.expr.operations as ops -import bigframes_vendored.ibis.expr.schema as sch -import toolz -from bigframes_vendored.ibis import util -from bigframes_vendored.ibis.common.deferred import Deferred, Resolver -from bigframes_vendored.ibis.common.selectors import Selector -from bigframes_vendored.ibis.expr.rewrites import DerefMap -from bigframes_vendored.ibis.expr.types.core import Expr, _FixedTextJupyterMixin -from bigframes_vendored.ibis.expr.types.generic import Value, literal -from bigframes_vendored.ibis.expr.types.pretty import to_rich -from bigframes_vendored.ibis.expr.types.temporal import TimestampColumn -from bigframes_vendored.ibis.util import deprecated -from public import public - -if TYPE_CHECKING: - import bigframes_vendored.ibis.expr.types as ir - import bigframes_vendored.ibis.selectors as s - import pandas as pd - import polars as pl - import pyarrow as pa - from bigframes_vendored.ibis.expr.operations.relations import JoinKind, Set - from bigframes_vendored.ibis.expr.schema import SchemaLike - from bigframes_vendored.ibis.expr.types import Table - from bigframes_vendored.ibis.expr.types.groupby import GroupedTable - from bigframes_vendored.ibis.expr.types.temporal_windows import WindowedTable - from bigframes_vendored.ibis.formats.pyarrow import PyArrowData - from bigframes_vendored.ibis.selectors import IfAnyAll - from rich.table import Table as RichTable - - -def _regular_join_method( - name: str, - how: Literal[ - "inner", - "left", - "outer", - "right", - "semi", - "anti", - "any_inner", - "any_left", - ], -): - def f( # noqa: D417 - self: ir.Table, - right: ir.Table, - predicates: ( - str - | Sequence[str | tuple[str | ir.Column, str | ir.Column] | ir.BooleanValue] - ) = (), - *, - lname: str = "", - rname: str = "{name}_right", - ) -> ir.Table: - """Perform a join between two tables. - - Parameters - ---------- - right - Right table to join - predicates - Boolean or column names to join on - lname - A format string to use to rename overlapping columns in the left - table (e.g. ``"left_{name}"``). - rname - A format string to use to rename overlapping columns in the right - table (e.g. ``"right_{name}"``). - - Returns - ------- - Table - Joined table - """ - return self.join(right, predicates, how=how, lname=lname, rname=rname) - - f.__name__ = name - return f - - -def bind(table: Table, value) -> Iterator[ir.Value]: - """Bind a value to a table expression.""" - if isinstance(value, str): - # TODO(kszucs): perhaps use getattr(table, value) instead for nicer error msg - yield ops.Field(table, value).to_expr() - elif isinstance(value, ops.Value): - yield value.to_expr() - elif isinstance(value, Value): - yield value - elif isinstance(value, Table): - for name in value.columns: - yield ops.Field(value, name).to_expr() - elif isinstance(value, Deferred): - yield value.resolve(table) - elif isinstance(value, Resolver): - yield value.resolve({"_": table}) - elif isinstance(value, Selector): - yield from value.expand(table) - elif callable(value): - # rebind, otherwise the callable is required to return an expression - # which would preclude support for expressions like lambda _: 2 - yield from bind(table, value(table)) - else: - yield literal(value) - - -def unwrap_aliases(values: Iterator[ir.Value]) -> Mapping[str, ir.Value]: - """Unwrap aliases into a mapping of {name: expression}.""" - result = {} - for value in values: - node = value.op() - if node.name in result: - raise com.IbisInputError( - f"Duplicate column name {node.name!r} in result set" - ) - if isinstance(node, ops.Alias): - result[node.name] = node.arg - else: - result[node.name] = node - return result - - -@public -class Table(Expr, _FixedTextJupyterMixin): - """An immutable and lazy dataframe. - - Analogous to a SQL table or a pandas DataFrame. A table expression contains - an [ordered set of named columns](./schemas.qmd#ibis.expr.schema.Schema), - each with a single known type. Unless explicitly ordered with an - [`.order_by()`](./expression-tables.qmd#ibis.expr.types.relations.Table.order_by), - the order of rows is undefined. - - Table immutability means that the data underlying an Ibis `Table` cannot be modified: every - method on a Table returns a new Table with those changes. Laziness - means that an Ibis `Table` expression does not run your computation every time you call one of its methods. - Instead, it is a symbolic expression that represents a set of operations - to be performed, which typically is translated into a SQL query. That - SQL query is then executed on a backend, where the data actually lives. - The result (now small enough to be manageable) can then be materialized back - into python as a pandas/pyarrow/python DataFrame/Column/scalar. - - You will not create Table objects directly. Instead, you will create one - - - from a pandas DataFrame, pyarrow table, Polars table, or raw python dicts/lists - with [`ibis.memtable(df)`](./expression-tables.qmd#ibis.memtable) - - from an existing table in a data platform with - [`connection.table("name")`](./expression-tables.qmd#ibis.backends.duckdb.Backend.table) - - from a file or URL, into a specific backend with - [`connection.read_csv/parquet/json("path/to/file")`](../backends/duckdb.qmd#ibis.backends.duckdb.Backend.read_csv) - (only some backends, typically local ones, support this) - - from a file or URL, into the default backend with - [`ibis.read_csv/read_json/read_parquet("path/to/file")`](./expression-tables.qmd#ibis.read_csv) - - See the [user guide](https://ibis-project.org/how-to/input-output/basics) for more - info. - """ - - # Higher than numpy & dask objects - __array_priority__ = 20 - - __array_ufunc__ = None - - def get_name(self) -> str: - """Return the fully qualified name of the table.""" - arg = self._arg - namespace = getattr(arg, "namespace", ops.Namespace()) - pieces = namespace.catalog, namespace.database, arg.name - return ".".join(filter(None, pieces)) - - def __array__(self, dtype=None): - return self.execute().__array__(dtype) - - def __dataframe__(self, nan_as_null: bool = False, allow_copy: bool = True): - from bigframes_vendored.ibis.expr.types.dataframe_interchange import ( - IbisDataFrame, - ) - - return IbisDataFrame(self, nan_as_null=nan_as_null, allow_copy=allow_copy) - - def __arrow_c_stream__(self, requested_schema: object | None = None) -> object: - return self.to_pyarrow().__arrow_c_stream__(requested_schema) - - def __pyarrow_result__( - self, table: pa.Table, data_mapper: type[PyArrowData] | None = None - ) -> pa.Table: - if data_mapper is None: - from bigframes_vendored.ibis.formats.pyarrow import ( - PyArrowData as data_mapper, - ) - - return data_mapper.convert_table(table, self.schema()) - - def __pandas_result__( - self, df: pd.DataFrame, schema: sch.Schema | None = None - ) -> pd.DataFrame: - from bigframes_vendored.ibis.formats.pandas import PandasData - - return PandasData.convert_table(df, self.schema() if schema is None else schema) - - def __polars_result__(self, df: pl.DataFrame) -> Any: - from bigframes_vendored.ibis.formats.polars import PolarsData - - return PolarsData.convert_table(df, self.schema()) - - def _fast_bind(self, *args, **kwargs): - # allow the first argument to be either a dictionary or a list of values - if len(args) == 1: - if isinstance(args[0], dict): - kwargs = {**args[0], **kwargs} - args = () - else: - args = util.promote_list(args[0]) - # bind positional arguments - values = [] - for arg in args: - values.extend(bind(self, arg)) - - # bind keyword arguments where each entry can produce only one value - # which is then named with the given key - for key, arg in kwargs.items(): - bindings = tuple(bind(self, arg)) - if len(bindings) != 1: - raise com.IbisInputError( - "Keyword arguments cannot produce more than one value" - ) - (value,) = bindings - values.append(value.name(key)) - return values - - def bind(self, *args: Any, **kwargs: Any) -> tuple[Value, ...]: - """Bind column values to a table expression. - - This method handles the binding of every kind of column-like value that - Ibis handles, including strings, integers, deferred expressions and - selectors, to a table expression. - - Parameters - ---------- - args - Column-like values to bind. - kwargs - Column-like values to bind, with names. - - Returns - ------- - tuple[Value, ...] - A tuple of bound values - """ - values = self._fast_bind(*args, **kwargs) - # dereference the values to `self` - dm = DerefMap.from_targets(self.op()) - result = [] - for original in values: - value = dm.dereference(original.op()).to_expr() - value = value.name(original.get_name()) - result.append(value) - return tuple(result) - - def as_scalar(self) -> ir.ScalarExpr: - """Inform ibis that the table expression should be treated as a scalar. - - Note that the table must have exactly one column and one row for this to - work. If the table has more than one column an error will be raised in - expression construction time. If the table has more than one row an - error will be raised by the backend when the expression is executed. - - Returns - ------- - Scalar - A scalar subquery - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.examples.penguins.fetch() - >>> heavy_gentoo = t.filter(t.species == "Gentoo", t.body_mass_g > 6200) - >>> from_that_island = t.filter(t.island == heavy_gentoo.select("island").as_scalar()) - >>> from_that_island.species.value_counts().order_by("species") - ┏━━━━━━━━━┳━━━━━━━━━━━━━━━┓ - ┃ species ┃ species_count ┃ - ┡━━━━━━━━━╇━━━━━━━━━━━━━━━┩ - │ string │ int64 │ - ├─────────┼───────────────┤ - │ Adelie │ 44 │ - │ Gentoo │ 124 │ - └─────────┴───────────────┘ - """ - return ops.ScalarSubquery(self).to_expr() - - def as_table(self) -> Table: - """Promote the expression to a table. - - This method is a no-op for table expressions. - - Returns - ------- - Table - A table expression - - Examples - -------- - >>> t = ibis.table(dict(a="int"), name="t") - >>> s = t.as_table() - >>> t is s - True - """ - return self - - def __contains__(self, name: str) -> bool: - """Return whether `name` is a column in the table. - - Parameters - ---------- - name - Possible column name - - Returns - ------- - bool - Whether `name` is a column in `self` - - Examples - -------- - >>> t = ibis.table(dict(a="string", b="float"), name="t") - >>> "a" in t - True - >>> "c" in t - False - """ - return name in self.schema() - - def cast(self, schema: SchemaLike) -> Table: - """Cast the columns of a table. - - Similar to `pandas.DataFrame.astype`. - - ::: {.callout-note} - ## If you need to cast columns to a single type, use [selectors](./selectors.qmd). - ::: - - Parameters - ---------- - schema - Mapping, schema or iterable of pairs to use for casting - - Returns - ------- - Table - Casted table - - Examples - -------- - >>> import ibis - >>> import ibis.selectors as s - >>> ibis.options.interactive = True - >>> t = ibis.examples.penguins.fetch() - >>> t.schema() - ibis.Schema { - species string - island string - bill_length_mm float64 - bill_depth_mm float64 - flipper_length_mm int64 - body_mass_g int64 - sex string - year int64 - } - >>> cols = ["body_mass_g", "bill_length_mm"] - >>> t[cols].head() - ┏━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━┓ - ┃ body_mass_g ┃ bill_length_mm ┃ - ┡━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━┩ - │ int64 │ float64 │ - ├─────────────┼────────────────┤ - │ 3750 │ 39.1 │ - │ 3800 │ 39.5 │ - │ 3250 │ 40.3 │ - │ NULL │ NULL │ - │ 3450 │ 36.7 │ - └─────────────┴────────────────┘ - - Columns not present in the input schema will be passed through unchanged - - >>> t.columns - ['species', 'island', 'bill_length_mm', 'bill_depth_mm', 'flipper_length_mm', 'body_mass_g', 'sex', 'year'] - >>> expr = t.cast({"body_mass_g": "float64", "bill_length_mm": "int"}) - >>> expr.select(*cols).head() - ┏━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━┓ - ┃ body_mass_g ┃ bill_length_mm ┃ - ┡━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━┩ - │ float64 │ int64 │ - ├─────────────┼────────────────┤ - │ 3750.0 │ 39 │ - │ 3800.0 │ 40 │ - │ 3250.0 │ 40 │ - │ NULL │ NULL │ - │ 3450.0 │ 37 │ - └─────────────┴────────────────┘ - - Columns that are in the input `schema` but not in the table raise an error - - >>> t.cast({"foo": "string"}) # quartodoc: +EXPECTED_FAILURE - Traceback (most recent call last): - ... - ibis.common.exceptions.IbisError: Cast schema has fields that are not in the table: ['foo'] - """ - return self._cast(schema, cast_method="cast") - - def try_cast(self, schema: SchemaLike) -> Table: - """Cast the columns of a table. - - If the cast fails for a row, the value is returned - as `NULL` or `NaN` depending on backend behavior. - - Parameters - ---------- - schema - Mapping, schema or iterable of pairs to use for casting - - Returns - ------- - Table - Casted table - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.memtable({"a": ["1", "2", "3"], "b": ["2.2", "3.3", "book"]}) - >>> t.try_cast({"a": "int", "b": "float"}) - ┏━━━━━━━┳━━━━━━━━━┓ - ┃ a ┃ b ┃ - ┡━━━━━━━╇━━━━━━━━━┩ - │ int64 │ float64 │ - ├───────┼─────────┤ - │ 1 │ 2.2 │ - │ 2 │ 3.3 │ - │ 3 │ NULL │ - └───────┴─────────┘ - """ - return self._cast(schema, cast_method="try_cast") - - def _cast(self, schema: SchemaLike, cast_method: str = "cast") -> Table: - schema = sch.schema(schema) - - cols = [] - - columns = self.columns - if missing_fields := frozenset(schema.names).difference(columns): - raise com.IbisError( - f"Cast schema has fields that are not in the table: {sorted(missing_fields)}" - ) - - for col in columns: - if (new_type := schema.get(col)) is not None: - new_col = getattr(self[col], cast_method)(new_type).name(col) - else: - new_col = col - cols.append(new_col) - return self.select(*cols) - - def preview( - self, - *, - max_rows: int | None = None, - max_columns: int | None = None, - max_length: int | None = None, - max_string: int | None = None, - max_depth: int | None = None, - console_width: int | float | None = None, - ) -> RichTable: - """Return a subset as a Rich Table. - - This is an explicit version of what you get when you inspect - this object in interactive mode, except with this version you - can pass formatting options. The options are the same as those exposed - in `ibis.options.interactive`. - - Parameters - ---------- - max_rows - Maximum number of rows to display - max_columns - Maximum number of columns to display - max_length - Maximum length for pretty-printed arrays and maps - max_string - Maximum length for pretty-printed strings - max_depth - Maximum depth for nested data types - console_width - Width of the console in characters. If not specified, the width - will be inferred from the console. - - Examples - -------- - >>> import ibis - >>> t = ibis.examples.penguins.fetch() - - Because the console_width is too small, only 2 columns are shown even though - we specified up to 3. - - >>> t.preview( - ... max_rows=3, - ... max_columns=3, - ... max_string=8, - ... console_width=30, - ... ) # doctest: +SKIP - ┏━━━━━━━━━┳━━━━━━━━━━┳━━━┓ - ┃ species ┃ island ┃ … ┃ - ┡━━━━━━━━━╇━━━━━━━━━━╇━━━┩ - │ string │ string │ … │ - ├─────────┼──────────┼───┤ - │ Adelie │ Torgers… │ … │ - │ Adelie │ Torgers… │ … │ - │ Adelie │ Torgers… │ … │ - │ … │ … │ … │ - └─────────┴──────────┴───┘ - """ - return to_rich( - self, - max_columns=max_columns, - max_rows=max_rows, - max_length=max_length, - max_string=max_string, - max_depth=max_depth, - console_width=console_width, - ) - - def __getitem__(self, what): - """Select items from a table expression. - - This method implements square bracket syntax for table expressions, - including various forms of projection and filtering. - - Parameters - ---------- - what - Selection object. This can be a variety of types including strings, ints, lists. - - Returns - ------- - Table | Column - The return type depends on the input. For a single string or int - input a column is returned, otherwise a table is returned. - - Examples - -------- - >>> import ibis - >>> import ibis.selectors as s - >>> from ibis import _ - >>> ibis.options.interactive = True - >>> t = ibis.examples.penguins.fetch() - >>> t - ┏━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━┳━━━┓ - ┃ species ┃ island ┃ bill_length_mm ┃ bill_depth_mm ┃ flipper_length_mm ┃ … ┃ - ┡━━━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━╇━━━┩ - │ string │ string │ float64 │ float64 │ int64 │ … │ - ├─────────┼───────────┼────────────────┼───────────────┼───────────────────┼───┤ - │ Adelie │ Torgersen │ 39.1 │ 18.7 │ 181 │ … │ - │ Adelie │ Torgersen │ 39.5 │ 17.4 │ 186 │ … │ - │ Adelie │ Torgersen │ 40.3 │ 18.0 │ 195 │ … │ - │ Adelie │ Torgersen │ NULL │ NULL │ NULL │ … │ - │ Adelie │ Torgersen │ 36.7 │ 19.3 │ 193 │ … │ - │ Adelie │ Torgersen │ 39.3 │ 20.6 │ 190 │ … │ - │ Adelie │ Torgersen │ 38.9 │ 17.8 │ 181 │ … │ - │ Adelie │ Torgersen │ 39.2 │ 19.6 │ 195 │ … │ - │ Adelie │ Torgersen │ 34.1 │ 18.1 │ 193 │ … │ - │ Adelie │ Torgersen │ 42.0 │ 20.2 │ 190 │ … │ - │ … │ … │ … │ … │ … │ … │ - └─────────┴───────────┴────────────────┴───────────────┴───────────────────┴───┘ - - Return a column by name - - >>> t["island"] - ┏━━━━━━━━━━━┓ - ┃ island ┃ - ┡━━━━━━━━━━━┩ - │ string │ - ├───────────┤ - │ Torgersen │ - │ Torgersen │ - │ Torgersen │ - │ Torgersen │ - │ Torgersen │ - │ Torgersen │ - │ Torgersen │ - │ Torgersen │ - │ Torgersen │ - │ Torgersen │ - │ … │ - └───────────┘ - - Return the second column, starting from index 0 - - >>> t.columns[1] - 'island' - >>> t[1] - ┏━━━━━━━━━━━┓ - ┃ island ┃ - ┡━━━━━━━━━━━┩ - │ string │ - ├───────────┤ - │ Torgersen │ - │ Torgersen │ - │ Torgersen │ - │ Torgersen │ - │ Torgersen │ - │ Torgersen │ - │ Torgersen │ - │ Torgersen │ - │ Torgersen │ - │ Torgersen │ - │ … │ - └───────────┘ - - Extract a range of rows - - >>> t[:2] - ┏━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━┳━━━┓ - ┃ species ┃ island ┃ bill_length_mm ┃ bill_depth_mm ┃ flipper_length_mm ┃ … ┃ - ┡━━━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━╇━━━┩ - │ string │ string │ float64 │ float64 │ int64 │ … │ - ├─────────┼───────────┼────────────────┼───────────────┼───────────────────┼───┤ - │ Adelie │ Torgersen │ 39.1 │ 18.7 │ 181 │ … │ - │ Adelie │ Torgersen │ 39.5 │ 17.4 │ 186 │ … │ - └─────────┴───────────┴────────────────┴───────────────┴───────────────────┴───┘ - >>> t[:5] - ┏━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━┳━━━┓ - ┃ species ┃ island ┃ bill_length_mm ┃ bill_depth_mm ┃ flipper_length_mm ┃ … ┃ - ┡━━━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━╇━━━┩ - │ string │ string │ float64 │ float64 │ int64 │ … │ - ├─────────┼───────────┼────────────────┼───────────────┼───────────────────┼───┤ - │ Adelie │ Torgersen │ 39.1 │ 18.7 │ 181 │ … │ - │ Adelie │ Torgersen │ 39.5 │ 17.4 │ 186 │ … │ - │ Adelie │ Torgersen │ 40.3 │ 18.0 │ 195 │ … │ - │ Adelie │ Torgersen │ NULL │ NULL │ NULL │ … │ - │ Adelie │ Torgersen │ 36.7 │ 19.3 │ 193 │ … │ - └─────────┴───────────┴────────────────┴───────────────┴───────────────────┴───┘ - >>> t[2:5] - ┏━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━┳━━━┓ - ┃ species ┃ island ┃ bill_length_mm ┃ bill_depth_mm ┃ flipper_length_mm ┃ … ┃ - ┡━━━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━╇━━━┩ - │ string │ string │ float64 │ float64 │ int64 │ … │ - ├─────────┼───────────┼────────────────┼───────────────┼───────────────────┼───┤ - │ Adelie │ Torgersen │ 40.3 │ 18.0 │ 195 │ … │ - │ Adelie │ Torgersen │ NULL │ NULL │ NULL │ … │ - │ Adelie │ Torgersen │ 36.7 │ 19.3 │ 193 │ … │ - └─────────┴───────────┴────────────────┴───────────────┴───────────────────┴───┘ - - Some backends support negative slice indexing - - >>> t[-5:] # last 5 rows - ┏━━━━━━━━━━━┳━━━━━━━━┳━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━┳━━━┓ - ┃ species ┃ island ┃ bill_length_mm ┃ bill_depth_mm ┃ flipper_length_mm ┃ … ┃ - ┡━━━━━━━━━━━╇━━━━━━━━╇━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━╇━━━┩ - │ string │ string │ float64 │ float64 │ int64 │ … │ - ├───────────┼────────┼────────────────┼───────────────┼───────────────────┼───┤ - │ Chinstrap │ Dream │ 55.8 │ 19.8 │ 207 │ … │ - │ Chinstrap │ Dream │ 43.5 │ 18.1 │ 202 │ … │ - │ Chinstrap │ Dream │ 49.6 │ 18.2 │ 193 │ … │ - │ Chinstrap │ Dream │ 50.8 │ 19.0 │ 210 │ … │ - │ Chinstrap │ Dream │ 50.2 │ 18.7 │ 198 │ … │ - └───────────┴────────┴────────────────┴───────────────┴───────────────────┴───┘ - >>> t[-5:-3] # last 5th to 3rd rows - ┏━━━━━━━━━━━┳━━━━━━━━┳━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━┳━━━┓ - ┃ species ┃ island ┃ bill_length_mm ┃ bill_depth_mm ┃ flipper_length_mm ┃ … ┃ - ┡━━━━━━━━━━━╇━━━━━━━━╇━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━╇━━━┩ - │ string │ string │ float64 │ float64 │ int64 │ … │ - ├───────────┼────────┼────────────────┼───────────────┼───────────────────┼───┤ - │ Chinstrap │ Dream │ 55.8 │ 19.8 │ 207 │ … │ - │ Chinstrap │ Dream │ 43.5 │ 18.1 │ 202 │ … │ - └───────────┴────────┴────────────────┴───────────────┴───────────────────┴───┘ - >>> t[2:-2] # chop off the first two and last two rows - ┏━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━┳━━━┓ - ┃ species ┃ island ┃ bill_length_mm ┃ bill_depth_mm ┃ flipper_length_mm ┃ … ┃ - ┡━━━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━╇━━━┩ - │ string │ string │ float64 │ float64 │ int64 │ … │ - ├─────────┼───────────┼────────────────┼───────────────┼───────────────────┼───┤ - │ Adelie │ Torgersen │ 40.3 │ 18.0 │ 195 │ … │ - │ Adelie │ Torgersen │ NULL │ NULL │ NULL │ … │ - │ Adelie │ Torgersen │ 36.7 │ 19.3 │ 193 │ … │ - │ Adelie │ Torgersen │ 39.3 │ 20.6 │ 190 │ … │ - │ Adelie │ Torgersen │ 38.9 │ 17.8 │ 181 │ … │ - │ Adelie │ Torgersen │ 39.2 │ 19.6 │ 195 │ … │ - │ Adelie │ Torgersen │ 34.1 │ 18.1 │ 193 │ … │ - │ Adelie │ Torgersen │ 42.0 │ 20.2 │ 190 │ … │ - │ Adelie │ Torgersen │ 37.8 │ 17.1 │ 186 │ … │ - │ Adelie │ Torgersen │ 37.8 │ 17.3 │ 180 │ … │ - │ … │ … │ … │ … │ … │ … │ - └─────────┴───────────┴────────────────┴───────────────┴───────────────────┴───┘ - - Select columns - - >>> t[["island", "bill_length_mm"]].head() - ┏━━━━━━━━━━━┳━━━━━━━━━━━━━━━━┓ - ┃ island ┃ bill_length_mm ┃ - ┡━━━━━━━━━━━╇━━━━━━━━━━━━━━━━┩ - │ string │ float64 │ - ├───────────┼────────────────┤ - │ Torgersen │ 39.1 │ - │ Torgersen │ 39.5 │ - │ Torgersen │ 40.3 │ - │ Torgersen │ NULL │ - │ Torgersen │ 36.7 │ - └───────────┴────────────────┘ - >>> t["island", "bill_length_mm"].head() - ┏━━━━━━━━━━━┳━━━━━━━━━━━━━━━━┓ - ┃ island ┃ bill_length_mm ┃ - ┡━━━━━━━━━━━╇━━━━━━━━━━━━━━━━┩ - │ string │ float64 │ - ├───────────┼────────────────┤ - │ Torgersen │ 39.1 │ - │ Torgersen │ 39.5 │ - │ Torgersen │ 40.3 │ - │ Torgersen │ NULL │ - │ Torgersen │ 36.7 │ - └───────────┴────────────────┘ - >>> t[_.island, _.bill_length_mm].head() - ┏━━━━━━━━━━━┳━━━━━━━━━━━━━━━━┓ - ┃ island ┃ bill_length_mm ┃ - ┡━━━━━━━━━━━╇━━━━━━━━━━━━━━━━┩ - │ string │ float64 │ - ├───────────┼────────────────┤ - │ Torgersen │ 39.1 │ - │ Torgersen │ 39.5 │ - │ Torgersen │ 40.3 │ - │ Torgersen │ NULL │ - │ Torgersen │ 36.7 │ - └───────────┴────────────────┘ - - Filtering - - >>> t[t.island.lower() != "torgersen"].head() - ┏━━━━━━━━━┳━━━━━━━━┳━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━┳━━━┓ - ┃ species ┃ island ┃ bill_length_mm ┃ bill_depth_mm ┃ flipper_length_mm ┃ … ┃ - ┡━━━━━━━━━╇━━━━━━━━╇━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━╇━━━┩ - │ string │ string │ float64 │ float64 │ int64 │ … │ - ├─────────┼────────┼────────────────┼───────────────┼───────────────────┼───┤ - │ Adelie │ Biscoe │ 37.8 │ 18.3 │ 174 │ … │ - │ Adelie │ Biscoe │ 37.7 │ 18.7 │ 180 │ … │ - │ Adelie │ Biscoe │ 35.9 │ 19.2 │ 189 │ … │ - │ Adelie │ Biscoe │ 38.2 │ 18.1 │ 185 │ … │ - │ Adelie │ Biscoe │ 38.8 │ 17.2 │ 180 │ … │ - └─────────┴────────┴────────────────┴───────────────┴───────────────────┴───┘ - - Selectors - - >>> t[~s.numeric() | (s.numeric() & ~s.c("year"))].head() - ┏━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━┳━━━┓ - ┃ species ┃ island ┃ bill_length_mm ┃ bill_depth_mm ┃ flipper_length_mm ┃ … ┃ - ┡━━━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━╇━━━┩ - │ string │ string │ float64 │ float64 │ int64 │ … │ - ├─────────┼───────────┼────────────────┼───────────────┼───────────────────┼───┤ - │ Adelie │ Torgersen │ 39.1 │ 18.7 │ 181 │ … │ - │ Adelie │ Torgersen │ 39.5 │ 17.4 │ 186 │ … │ - │ Adelie │ Torgersen │ 40.3 │ 18.0 │ 195 │ … │ - │ Adelie │ Torgersen │ NULL │ NULL │ NULL │ … │ - │ Adelie │ Torgersen │ 36.7 │ 19.3 │ 193 │ … │ - └─────────┴───────────┴────────────────┴───────────────┴───────────────────┴───┘ - >>> t[s.r["bill_length_mm":"body_mass_g"]].head() - ┏━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━┓ - ┃ bill_length_mm ┃ bill_depth_mm ┃ flipper_length_mm ┃ body_mass_g ┃ - ┡━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━┩ - │ float64 │ float64 │ int64 │ int64 │ - ├────────────────┼───────────────┼───────────────────┼─────────────┤ - │ 39.1 │ 18.7 │ 181 │ 3750 │ - │ 39.5 │ 17.4 │ 186 │ 3800 │ - │ 40.3 │ 18.0 │ 195 │ 3250 │ - │ NULL │ NULL │ NULL │ NULL │ - │ 36.7 │ 19.3 │ 193 │ 3450 │ - └────────────────┴───────────────┴───────────────────┴─────────────┘ - """ - from bigframes_vendored.ibis.expr.types.logical import BooleanValue - - if isinstance(what, slice): - limit, offset = util.slice_to_limit_offset(what, self.count()) - return self.limit(limit, offset=offset) - # skip the self.bind call for single column access with strings or ints - # because dereferencing has significant overhead - elif isinstance(what, str): - return ops.Field(self.op(), what).to_expr() - elif isinstance(what, int): - return ops.Field(self.op(), self.columns[what]).to_expr() - - args = [ - self.columns[arg] if isinstance(arg, int) else arg - for arg in util.promote_list(what) - ] - values = self.bind(args) - - if isinstance(what, (str, int)): - assert len(values) == 1 - return values[0] - elif util.all_of(values, BooleanValue): - return self.filter(values) - else: - return self.select(values) - - def __len__(self): - raise com.ExpressionError("Use .count() instead") - - def __getattr__(self, key: str) -> ir.Column: - """Return the column name of a table. - - Parameters - ---------- - key - Column name - - Returns - ------- - Column - Column expression with name `key` - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.examples.penguins.fetch() - >>> t.island - ┏━━━━━━━━━━━┓ - ┃ island ┃ - ┡━━━━━━━━━━━┩ - │ string │ - ├───────────┤ - │ Torgersen │ - │ Torgersen │ - │ Torgersen │ - │ Torgersen │ - │ Torgersen │ - │ Torgersen │ - │ Torgersen │ - │ Torgersen │ - │ Torgersen │ - │ Torgersen │ - │ … │ - └───────────┘ - """ - try: - return ops.Field(self, key).to_expr() - except com.IbisTypeError: - pass - - # A mapping of common attribute typos, mapping them to the proper name - common_typos = { - "sort": "order_by", - "sort_by": "order_by", - "sortby": "order_by", - "orderby": "order_by", - "groupby": "group_by", - } - if key in common_typos: - hint = common_typos[key] - raise AttributeError( - f"{type(self).__name__} object has no attribute {key!r}, did you mean {hint!r}" - ) - - raise AttributeError(f"'Table' object has no attribute {key!r}") - - def __dir__(self) -> list[str]: - out = set(dir(type(self))) - out.update(c for c in self.columns if c.isidentifier() and not iskeyword(c)) - return sorted(out) - - def _ipython_key_completions_(self) -> list[str]: - return self.columns - - @property - def columns(self) -> list[str]: - """The list of column names in this table. - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.examples.penguins.fetch() - >>> t.columns - ['species', - 'island', - 'bill_length_mm', - 'bill_depth_mm', - 'flipper_length_mm', - 'body_mass_g', - 'sex', - 'year'] - """ - return list(self.schema().names) - - def schema(self) -> sch.Schema: - """Return the [Schema](./schemas.qmd#ibis.expr.schema.Schema) for this table. - - Returns - ------- - Schema - The table's schema. - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.examples.penguins.fetch() - >>> t.schema() - ibis.Schema { - species string - island string - bill_length_mm float64 - bill_depth_mm float64 - flipper_length_mm int64 - body_mass_g int64 - sex string - year int64 - } - """ - return self.op().schema - - def group_by( - self, - *by: str | ir.Value | Iterable[str] | Iterable[ir.Value] | None, - **key_exprs: str | ir.Value | Iterable[str] | Iterable[ir.Value], - ) -> GroupedTable: - """Create a grouped table expression. - - Similar to SQL's GROUP BY statement, or pandas .groupby() method. - - Parameters - ---------- - by - Grouping expressions - key_exprs - Named grouping expressions - - Returns - ------- - GroupedTable - A grouped table expression - - Examples - -------- - >>> import ibis - >>> from ibis import _ - >>> ibis.options.interactive = True - >>> t = ibis.memtable( - ... { - ... "fruit": ["apple", "apple", "banana", "orange"], - ... "price": [0.5, 0.5, 0.25, 0.33], - ... } - ... ) - >>> t - ┏━━━━━━━━┳━━━━━━━━━┓ - ┃ fruit ┃ price ┃ - ┡━━━━━━━━╇━━━━━━━━━┩ - │ string │ float64 │ - ├────────┼─────────┤ - │ apple │ 0.50 │ - │ apple │ 0.50 │ - │ banana │ 0.25 │ - │ orange │ 0.33 │ - └────────┴─────────┘ - >>> t.group_by("fruit").agg(total_cost=_.price.sum(), avg_cost=_.price.mean()).order_by( - ... "fruit" - ... ) - ┏━━━━━━━━┳━━━━━━━━━━━━┳━━━━━━━━━━┓ - ┃ fruit ┃ total_cost ┃ avg_cost ┃ - ┡━━━━━━━━╇━━━━━━━━━━━━╇━━━━━━━━━━┩ - │ string │ float64 │ float64 │ - ├────────┼────────────┼──────────┤ - │ apple │ 1.00 │ 0.50 │ - │ banana │ 0.25 │ 0.25 │ - │ orange │ 0.33 │ 0.33 │ - └────────┴────────────┴──────────┘ - """ - from bigframes_vendored.ibis.expr.types.groupby import GroupedTable - - by = tuple(v for v in by if v is not None) - groups = self.bind(*by, **key_exprs) - return GroupedTable(self, groups) - - # TODO(kszucs): shouldn't this be ibis.rowid() instead not bound to a specific table? - def rowid(self) -> ir.IntegerValue: - """A unique integer per row. - - ::: {.callout-note} - ## This operation is only valid on physical tables - - Any further meaning behind this expression is backend dependent. - Generally this corresponds to some index into the database storage - (for example, SQLite and DuckDB's `rowid`). - - For a monotonically increasing row number, see `ibis.row_number`. - ::: - - Returns - ------- - IntegerColumn - An integer column - """ - if not isinstance(self.op(), ops.PhysicalTable): - raise com.IbisTypeError( - "rowid() is only valid for physical tables, not for generic " - "table expressions" - ) - return ops.RowID(self).to_expr() - - def view(self) -> Table: - """Create a new table expression distinct from the current one. - - Use this API for any self-referencing operations like a self-join. - - Returns - ------- - Table - Table expression - """ - if isinstance(self.op(), ops.SelfReference): - return self - else: - return ops.SelfReference(self).to_expr() - - def aggregate( - self, - metrics: Sequence[ir.Scalar] | None = (), - by: Sequence[ir.Value] | None = (), - having: Sequence[ir.BooleanValue] | None = (), - **kwargs: ir.Value, - ) -> Table: - """Aggregate a table with a given set of reductions grouping by `by`. - - Parameters - ---------- - metrics - Aggregate expressions. These can be any scalar-producing - expression, including aggregation functions like `sum` or literal - values like `ibis.literal(1)`. - by - Grouping expressions. - having - Post-aggregation filters. The shape requirements are the same - `metrics`, but the output type for `having` is `boolean`. - - ::: {.callout-warning} - ## Expressions like `x is None` return `bool` and **will not** generate a SQL comparison to `NULL` - ::: - kwargs - Named aggregate expressions - - Returns - ------- - Table - An aggregate table expression - - Examples - -------- - >>> import ibis - >>> from ibis import _ - >>> ibis.options.interactive = True - >>> t = ibis.memtable( - ... { - ... "fruit": ["apple", "apple", "banana", "orange"], - ... "price": [0.5, 0.5, 0.25, 0.33], - ... } - ... ) - >>> t - ┏━━━━━━━━┳━━━━━━━━━┓ - ┃ fruit ┃ price ┃ - ┡━━━━━━━━╇━━━━━━━━━┩ - │ string │ float64 │ - ├────────┼─────────┤ - │ apple │ 0.50 │ - │ apple │ 0.50 │ - │ banana │ 0.25 │ - │ orange │ 0.33 │ - └────────┴─────────┘ - >>> t.aggregate( - ... by=["fruit"], - ... total_cost=_.price.sum(), - ... avg_cost=_.price.mean(), - ... having=_.price.sum() < 0.5, - ... ) - ┏━━━━━━━━┳━━━━━━━━━━━━┳━━━━━━━━━━┓ - ┃ fruit ┃ total_cost ┃ avg_cost ┃ - ┡━━━━━━━━╇━━━━━━━━━━━━╇━━━━━━━━━━┩ - │ string │ float64 │ float64 │ - ├────────┼────────────┼──────────┤ - │ banana │ 0.25 │ 0.25 │ - │ orange │ 0.33 │ 0.33 │ - └────────┴────────────┴──────────┘ - """ - from bigframes_vendored.ibis.common.patterns import Contains, In - from bigframes_vendored.ibis.expr.rewrites import p - - node = self.op() - - groups = self.bind(by) - metrics = self.bind(metrics, **kwargs) - having = self.bind(having) - - groups = unwrap_aliases(groups) - metrics = unwrap_aliases(metrics) - - # the user doesn't need to specify the metrics used in the having clause - # explicitly, we implicitly add them to the metrics list by looking for - # any metrics depending on self which are not specified explicitly - pattern = p.Reduction(relations=Contains(node)) & ~In(set(metrics.values())) - original_metrics = metrics.copy() - for pred in having: - for metric in pred.op().find_topmost(pattern): - if metric.name in metrics: - metrics[util.get_name("metric")] = metric - else: - metrics[metric.name] = metric - - # construct the aggregate node - agg = ops.Aggregate(node, groups, metrics).to_expr() - - if having: - # apply the having clause - agg = agg.filter(having) - # remove any metrics that were only used in the having clause - if metrics != original_metrics: - agg = agg.select(*groups.keys(), *original_metrics.keys()) - - return agg - - agg = aggregate - - def distinct( - self, - *, - on: str | Iterable[str] | s.Selector | None = None, - keep: Literal["first", "last"] | None = "first", - ) -> Table: - """Return a Table with duplicate rows removed. - - Similar to `pandas.DataFrame.drop_duplicates()`. - - ::: {.callout-note} - ## Some backends do not support `keep='last'` - ::: - - Parameters - ---------- - on - Only consider certain columns for identifying duplicates. - By default deduplicate all of the columns. - keep - Determines which duplicates to keep. - - - `"first"`: Drop duplicates except for the first occurrence. - - `"last"`: Drop duplicates except for the last occurrence. - - `None`: Drop all duplicates - - Examples - -------- - >>> import ibis - >>> import ibis.examples as ex - >>> import ibis.selectors as s - >>> ibis.options.interactive = True - >>> t = ex.penguins.fetch() - >>> t - ┏━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━┳━━━┓ - ┃ species ┃ island ┃ bill_length_mm ┃ bill_depth_mm ┃ flipper_length_mm ┃ … ┃ - ┡━━━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━╇━━━┩ - │ string │ string │ float64 │ float64 │ int64 │ … │ - ├─────────┼───────────┼────────────────┼───────────────┼───────────────────┼───┤ - │ Adelie │ Torgersen │ 39.1 │ 18.7 │ 181 │ … │ - │ Adelie │ Torgersen │ 39.5 │ 17.4 │ 186 │ … │ - │ Adelie │ Torgersen │ 40.3 │ 18.0 │ 195 │ … │ - │ Adelie │ Torgersen │ NULL │ NULL │ NULL │ … │ - │ Adelie │ Torgersen │ 36.7 │ 19.3 │ 193 │ … │ - │ Adelie │ Torgersen │ 39.3 │ 20.6 │ 190 │ … │ - │ Adelie │ Torgersen │ 38.9 │ 17.8 │ 181 │ … │ - │ Adelie │ Torgersen │ 39.2 │ 19.6 │ 195 │ … │ - │ Adelie │ Torgersen │ 34.1 │ 18.1 │ 193 │ … │ - │ Adelie │ Torgersen │ 42.0 │ 20.2 │ 190 │ … │ - │ … │ … │ … │ … │ … │ … │ - └─────────┴───────────┴────────────────┴───────────────┴───────────────────┴───┘ - - Compute the distinct rows of a subset of columns - - >>> t[["species", "island"]].distinct().order_by(s.all()) - ┏━━━━━━━━━━━┳━━━━━━━━━━━┓ - ┃ species ┃ island ┃ - ┡━━━━━━━━━━━╇━━━━━━━━━━━┩ - │ string │ string │ - ├───────────┼───────────┤ - │ Adelie │ Biscoe │ - │ Adelie │ Dream │ - │ Adelie │ Torgersen │ - │ Chinstrap │ Dream │ - │ Gentoo │ Biscoe │ - └───────────┴───────────┘ - - Drop all duplicate rows except the first - - >>> t.distinct(on=["species", "island"], keep="first").order_by(s.all()) - ┏━━━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━┳━━┓ - ┃ species ┃ island ┃ bill_length_mm ┃ bill_depth_… ┃ flipper_length_mm ┃ ┃ - ┡━━━━━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━╇━━┩ - │ string │ string │ float64 │ float64 │ int64 │ │ - ├───────────┼───────────┼────────────────┼──────────────┼───────────────────┼──┤ - │ Adelie │ Biscoe │ 37.8 │ 18.3 │ 174 │ │ - │ Adelie │ Dream │ 39.5 │ 16.7 │ 178 │ │ - │ Adelie │ Torgersen │ 39.1 │ 18.7 │ 181 │ │ - │ Chinstrap │ Dream │ 46.5 │ 17.9 │ 192 │ │ - │ Gentoo │ Biscoe │ 46.1 │ 13.2 │ 211 │ │ - └───────────┴───────────┴────────────────┴──────────────┴───────────────────┴──┘ - - Drop all duplicate rows except the last - - >>> t.distinct(on=["species", "island"], keep="last").order_by(s.all()) - ┏━━━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━┳━━┓ - ┃ species ┃ island ┃ bill_length_mm ┃ bill_depth_… ┃ flipper_length_mm ┃ ┃ - ┡━━━━━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━╇━━┩ - │ string │ string │ float64 │ float64 │ int64 │ │ - ├───────────┼───────────┼────────────────┼──────────────┼───────────────────┼──┤ - │ Adelie │ Biscoe │ 42.7 │ 18.3 │ 196 │ │ - │ Adelie │ Dream │ 41.5 │ 18.5 │ 201 │ │ - │ Adelie │ Torgersen │ 43.1 │ 19.2 │ 197 │ │ - │ Chinstrap │ Dream │ 50.2 │ 18.7 │ 198 │ │ - │ Gentoo │ Biscoe │ 49.9 │ 16.1 │ 213 │ │ - └───────────┴───────────┴────────────────┴──────────────┴───────────────────┴──┘ - - Drop all duplicated rows - - >>> expr = t.distinct(on=["species", "island", "year", "bill_length_mm"], keep=None) - >>> expr.count() - ┌───────────────┐ - │ np.int64(273) │ - └───────────────┘ - >>> t.count() - ┌───────────────┐ - │ np.int64(344) │ - └───────────────┘ - - You can pass [`selectors`](./selectors.qmd) to `on` - - >>> t.distinct(on=~s.numeric()) # doctest: +SKIP - ┏━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━┳━━━┓ - ┃ species ┃ island ┃ bill_length_mm ┃ bill_depth_mm ┃ flipper_length_mm ┃ … ┃ - ┡━━━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━╇━━━┩ - │ string │ string │ float64 │ float64 │ int64 │ … │ - ├─────────┼───────────┼────────────────┼───────────────┼───────────────────┼───┤ - │ Adelie │ Torgersen │ 39.1 │ 18.7 │ 181 │ … │ - │ Adelie │ Torgersen │ 39.5 │ 17.4 │ 186 │ … │ - │ Adelie │ Torgersen │ NULL │ NULL │ NULL │ … │ - │ Adelie │ Biscoe │ 37.8 │ 18.3 │ 174 │ … │ - │ Adelie │ Biscoe │ 37.7 │ 18.7 │ 180 │ … │ - │ Adelie │ Dream │ 39.5 │ 16.7 │ 178 │ … │ - │ Adelie │ Dream │ 37.2 │ 18.1 │ 178 │ … │ - │ Adelie │ Dream │ 37.5 │ 18.9 │ 179 │ … │ - │ Gentoo │ Biscoe │ 46.1 │ 13.2 │ 211 │ … │ - │ Gentoo │ Biscoe │ 50.0 │ 16.3 │ 230 │ … │ - │ … │ … │ … │ … │ … │ … │ - └─────────┴───────────┴────────────────┴───────────────┴───────────────────┴───┘ - - The only valid values of `keep` are `"first"`, `"last"` and [`None][None] - - >>> t.distinct(on="species", keep="second") # quartodoc: +EXPECTED_FAILURE - Traceback (most recent call last): - ... - ibis.common.exceptions.IbisError: Invalid value for keep: 'second' ... - """ - - import bigframes_vendored.ibis.selectors as s - - if on is None: - # dedup everything - if keep != "first": - raise com.IbisError( - f"Only keep='first' (the default) makes sense when deduplicating all columns; got keep={keep!r}" - ) - return ops.Distinct(self).to_expr() - - on = s._to_selector(on) - - if keep is None: - having = lambda t: t.count() == 1 - method = "first" - elif keep in ("first", "last"): - having = () - method = keep - else: - raise com.IbisError( - f"Invalid value for `keep`: {keep!r}, must be 'first', 'last' or None" - ) - - aggs = {col.get_name(): getattr(col, method)() for col in (~on).expand(self)} - res = self.aggregate(aggs, by=on, having=having) - - assert len(res.columns) == len(self.columns) - if res.columns != self.columns: - return res.select(self.columns) - return res - - def sample( - self, - fraction: float, - *, - method: Literal["row", "block"] = "row", - seed: int | None = None, - ) -> Table: - """Sample a fraction of rows from a table. - - ::: {.callout-note} - ## Results may be non-repeatable - - Sampling is by definition a random operation. Some backends support - specifying a `seed` for repeatable results, but not all backends - support that option. And some backends (duckdb, for example) do support - specifying a seed but may still not have repeatable results in all - cases. - - In all cases, results are backend-specific. An execution against one - backend is unlikely to sample the same rows when executed against a - different backend, even with the same `seed` set. - ::: - - Parameters - ---------- - fraction - The percentage of rows to include in the sample, expressed as a - float between 0 and 1. - method - The sampling method to use. The default is "row", which includes - each row with a probability of ``fraction``. If method is "block", - some backends may instead perform sampling a fraction of blocks of - rows (where "block" is a backend dependent definition). This is - identical to "row" for backends lacking a blockwise sampling - implementation. For those coming from SQL, "row" and "block" - correspond to "bernoulli" and "system" respectively in a - TABLESAMPLE clause. - seed - An optional random seed to use, for repeatable sampling. The range - of possible seed values is backend specific (most support at least - `[0, 2**31 - 1]`). Backends that never support specifying a seed - for repeatable sampling will error appropriately. Note that some - backends (like DuckDB) do support specifying a seed, but may still - not have repeatable results in all cases. - - Returns - ------- - Table - The input table, with `fraction` of rows selected. - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.memtable({"x": [1, 2, 3, 4], "y": ["a", "b", "c", "d"]}) - >>> t - ┏━━━━━━━┳━━━━━━━━┓ - ┃ x ┃ y ┃ - ┡━━━━━━━╇━━━━━━━━┩ - │ int64 │ string │ - ├───────┼────────┤ - │ 1 │ a │ - │ 2 │ b │ - │ 3 │ c │ - │ 4 │ d │ - └───────┴────────┘ - - Sample approximately half the rows, with a seed specified for - reproducibility. - - >>> t.sample(0.5, seed=1234) - ┏━━━━━━━┳━━━━━━━━┓ - ┃ x ┃ y ┃ - ┡━━━━━━━╇━━━━━━━━┩ - │ int64 │ string │ - ├───────┼────────┤ - │ 2 │ b │ - │ 3 │ c │ - └───────┴────────┘ - """ - if fraction == 1: - return self - elif fraction == 0: - return self.limit(0) - else: - return ops.Sample( - self, fraction=fraction, method=method, seed=seed - ).to_expr() - - def limit(self, n: int | None, offset: int = 0) -> Table: - """Select `n` rows from `self` starting at `offset`. - - ::: {.callout-note} - ## The result set is not deterministic without a call to [`order_by`](#ibis.expr.types.relations.Table.order_by). - ::: - - Parameters - ---------- - n - Number of rows to include. If `None`, the entire table is selected - starting from `offset`. - offset - Number of rows to skip first - - Returns - ------- - Table - The first `n` rows of `self` starting at `offset` - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.memtable({"a": [1, 1, 2], "b": ["c", "a", "a"]}) - >>> t - ┏━━━━━━━┳━━━━━━━━┓ - ┃ a ┃ b ┃ - ┡━━━━━━━╇━━━━━━━━┩ - │ int64 │ string │ - ├───────┼────────┤ - │ 1 │ c │ - │ 1 │ a │ - │ 2 │ a │ - └───────┴────────┘ - >>> t.limit(2) - ┏━━━━━━━┳━━━━━━━━┓ - ┃ a ┃ b ┃ - ┡━━━━━━━╇━━━━━━━━┩ - │ int64 │ string │ - ├───────┼────────┤ - │ 1 │ c │ - │ 1 │ a │ - └───────┴────────┘ - - You can use `None` with `offset` to slice starting from a particular row - - >>> t.limit(None, offset=1) - ┏━━━━━━━┳━━━━━━━━┓ - ┃ a ┃ b ┃ - ┡━━━━━━━╇━━━━━━━━┩ - │ int64 │ string │ - ├───────┼────────┤ - │ 1 │ a │ - │ 2 │ a │ - └───────┴────────┘ - - See Also - -------- - [`Table.order_by`](#ibis.expr.types.relations.Table.order_by) - """ - return ops.Limit(self, n, offset).to_expr() - - def head(self, n: int = 5) -> Table: - """Select the first `n` rows of a table. - - ::: {.callout-note} - ## The result set is not deterministic without a call to [`order_by`](#ibis.expr.types.relations.Table.order_by). - ::: - - Parameters - ---------- - n - Number of rows to include - - Returns - ------- - Table - `self` limited to `n` rows - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.memtable({"a": [1, 1, 2], "b": ["c", "a", "a"]}) - >>> t - ┏━━━━━━━┳━━━━━━━━┓ - ┃ a ┃ b ┃ - ┡━━━━━━━╇━━━━━━━━┩ - │ int64 │ string │ - ├───────┼────────┤ - │ 1 │ c │ - │ 1 │ a │ - │ 2 │ a │ - └───────┴────────┘ - >>> t.head(2) - ┏━━━━━━━┳━━━━━━━━┓ - ┃ a ┃ b ┃ - ┡━━━━━━━╇━━━━━━━━┩ - │ int64 │ string │ - ├───────┼────────┤ - │ 1 │ c │ - │ 1 │ a │ - └───────┴────────┘ - - See Also - -------- - [`Table.limit`](#ibis.expr.types.relations.Table.limit) - [`Table.order_by`](#ibis.expr.types.relations.Table.order_by) - """ - return self.limit(n=n) - - def order_by( - self, - *by: str - | ir.Column - | s.Selector - | Sequence[str] - | Sequence[ir.Column] - | Sequence[s.Selector] - | None, - ) -> Table: - """Sort a table by one or more expressions. - - Similar to `pandas.DataFrame.sort_values()`. - - Parameters - ---------- - by - Expressions to sort the table by. - - Returns - ------- - Table - Sorted table - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.memtable( - ... { - ... "a": [3, 2, 1, 3], - ... "b": ["a", "B", "c", "D"], - ... "c": [4, 6, 5, 7], - ... } - ... ) - >>> t - ┏━━━━━━━┳━━━━━━━━┳━━━━━━━┓ - ┃ a ┃ b ┃ c ┃ - ┡━━━━━━━╇━━━━━━━━╇━━━━━━━┩ - │ int64 │ string │ int64 │ - ├───────┼────────┼───────┤ - │ 3 │ a │ 4 │ - │ 2 │ B │ 6 │ - │ 1 │ c │ 5 │ - │ 3 │ D │ 7 │ - └───────┴────────┴───────┘ - - Sort by b. Default is ascending. Note how capital letters come before lowercase - - >>> t.order_by("b") - ┏━━━━━━━┳━━━━━━━━┳━━━━━━━┓ - ┃ a ┃ b ┃ c ┃ - ┡━━━━━━━╇━━━━━━━━╇━━━━━━━┩ - │ int64 │ string │ int64 │ - ├───────┼────────┼───────┤ - │ 2 │ B │ 6 │ - │ 3 │ D │ 7 │ - │ 3 │ a │ 4 │ - │ 1 │ c │ 5 │ - └───────┴────────┴───────┘ - - Sort in descending order - - >>> t.order_by(ibis.desc("b")) - ┏━━━━━━━┳━━━━━━━━┳━━━━━━━┓ - ┃ a ┃ b ┃ c ┃ - ┡━━━━━━━╇━━━━━━━━╇━━━━━━━┩ - │ int64 │ string │ int64 │ - ├───────┼────────┼───────┤ - │ 1 │ c │ 5 │ - │ 3 │ a │ 4 │ - │ 3 │ D │ 7 │ - │ 2 │ B │ 6 │ - └───────┴────────┴───────┘ - - You can also use the deferred API to get the same result - - >>> from ibis import _ - >>> t.order_by(_.b.desc()) - ┏━━━━━━━┳━━━━━━━━┳━━━━━━━┓ - ┃ a ┃ b ┃ c ┃ - ┡━━━━━━━╇━━━━━━━━╇━━━━━━━┩ - │ int64 │ string │ int64 │ - ├───────┼────────┼───────┤ - │ 1 │ c │ 5 │ - │ 3 │ a │ 4 │ - │ 3 │ D │ 7 │ - │ 2 │ B │ 6 │ - └───────┴────────┴───────┘ - - Sort by multiple columns/expressions - - >>> t.order_by(["a", _.c.desc()]) - ┏━━━━━━━┳━━━━━━━━┳━━━━━━━┓ - ┃ a ┃ b ┃ c ┃ - ┡━━━━━━━╇━━━━━━━━╇━━━━━━━┩ - │ int64 │ string │ int64 │ - ├───────┼────────┼───────┤ - │ 1 │ c │ 5 │ - │ 2 │ B │ 6 │ - │ 3 │ D │ 7 │ - │ 3 │ a │ 4 │ - └───────┴────────┴───────┘ - - You can actually pass arbitrary expressions to use as sort keys. - For example, to ignore the case of the strings in column `b` - - >>> t.order_by(_.b.lower()) - ┏━━━━━━━┳━━━━━━━━┳━━━━━━━┓ - ┃ a ┃ b ┃ c ┃ - ┡━━━━━━━╇━━━━━━━━╇━━━━━━━┩ - │ int64 │ string │ int64 │ - ├───────┼────────┼───────┤ - │ 3 │ a │ 4 │ - │ 2 │ B │ 6 │ - │ 1 │ c │ 5 │ - │ 3 │ D │ 7 │ - └───────┴────────┴───────┘ - - This means that shuffling a Table is super simple - - >>> t.order_by(ibis.random()) # doctest: +SKIP - ┏━━━━━━━┳━━━━━━━━┳━━━━━━━┓ - ┃ a ┃ b ┃ c ┃ - ┡━━━━━━━╇━━━━━━━━╇━━━━━━━┩ - │ int64 │ string │ int64 │ - ├───────┼────────┼───────┤ - │ 1 │ c │ 5 │ - │ 3 │ D │ 7 │ - │ 3 │ a │ 4 │ - │ 2 │ B │ 6 │ - └───────┴────────┴───────┘ - - [Selectors](./selectors.qmd) are allowed as sort keys and are a concise way to sort by - multiple columns matching some criteria - - >>> import ibis.selectors as s - >>> penguins = ibis.examples.penguins.fetch() - >>> penguins[["year", "island"]].value_counts().order_by(s.startswith("year")) - ┏━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━┓ - ┃ year ┃ island ┃ year_island_count ┃ - ┡━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━┩ - │ int64 │ string │ int64 │ - ├───────┼───────────┼───────────────────┤ - │ 2007 │ Torgersen │ 20 │ - │ 2007 │ Biscoe │ 44 │ - │ 2007 │ Dream │ 46 │ - │ 2008 │ Torgersen │ 16 │ - │ 2008 │ Dream │ 34 │ - │ 2008 │ Biscoe │ 64 │ - │ 2009 │ Torgersen │ 16 │ - │ 2009 │ Dream │ 44 │ - │ 2009 │ Biscoe │ 60 │ - └───────┴───────────┴───────────────────┘ - - Use the [`across`](./selectors.qmd#ibis.selectors.across) selector to - apply a specific order to multiple columns - - >>> penguins[["year", "island"]].value_counts().order_by( - ... s.across(s.startswith("year"), _.desc()) - ... ) - ┏━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━┓ - ┃ year ┃ island ┃ year_island_count ┃ - ┡━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━┩ - │ int64 │ string │ int64 │ - ├───────┼───────────┼───────────────────┤ - │ 2009 │ Biscoe │ 60 │ - │ 2009 │ Dream │ 44 │ - │ 2009 │ Torgersen │ 16 │ - │ 2008 │ Biscoe │ 64 │ - │ 2008 │ Dream │ 34 │ - │ 2008 │ Torgersen │ 16 │ - │ 2007 │ Dream │ 46 │ - │ 2007 │ Biscoe │ 44 │ - │ 2007 │ Torgersen │ 20 │ - └───────┴───────────┴───────────────────┘ - """ - keys = self.bind(*by) - keys = unwrap_aliases(keys) - if not keys: - raise com.IbisError("At least one sort key must be provided") - - node = ops.Sort(self, keys.values()) - return node.to_expr() - - def _assemble_set_op( - self, opcls: type[Set], table: Table, *rest: Table, distinct: bool - ) -> Table: - """Assemble a set operation expression. - - This exists to workaround an issue in sqlglot where codegen blows the - Python stack because of set operation nesting. - - The implementation here uses a queue to balance the operation tree. - """ - queue = deque() - - queue.append(self) - queue.append(table) - queue.extend(rest) - - while len(queue) > 1: - left = queue.popleft() - right = queue.popleft() - node = opcls(left, right, distinct=distinct) - queue.append(node) - result = queue.popleft() - assert not queue, "items left in queue" - return result.to_expr() - - def union(self, table: Table, *rest: Table, distinct: bool = False) -> Table: - """Compute the set union of multiple table expressions. - - The input tables must have identical schemas. - - Parameters - ---------- - table - A table expression - *rest - Additional table expressions - distinct - Only return distinct rows - - Returns - ------- - Table - A new table containing the union of all input tables. - - See Also - -------- - [`ibis.union`](./expression-tables.qmd#ibis.union) - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t1 = ibis.memtable({"a": [1, 2]}) - >>> t1 - ┏━━━━━━━┓ - ┃ a ┃ - ┡━━━━━━━┩ - │ int64 │ - ├───────┤ - │ 1 │ - │ 2 │ - └───────┘ - >>> t2 = ibis.memtable({"a": [2, 3]}) - >>> t2 - ┏━━━━━━━┓ - ┃ a ┃ - ┡━━━━━━━┩ - │ int64 │ - ├───────┤ - │ 2 │ - │ 3 │ - └───────┘ - >>> t1.union(t2) # union all by default - ┏━━━━━━━┓ - ┃ a ┃ - ┡━━━━━━━┩ - │ int64 │ - ├───────┤ - │ 1 │ - │ 2 │ - │ 2 │ - │ 3 │ - └───────┘ - >>> t1.union(t2, distinct=True).order_by("a") - ┏━━━━━━━┓ - ┃ a ┃ - ┡━━━━━━━┩ - │ int64 │ - ├───────┤ - │ 1 │ - │ 2 │ - │ 3 │ - └───────┘ - """ - return self._assemble_set_op(ops.Union, table, *rest, distinct=distinct) - - def intersect(self, table: Table, *rest: Table, distinct: bool = True) -> Table: - """Compute the set intersection of multiple table expressions. - - The input tables must have identical schemas. - - Parameters - ---------- - table - A table expression - *rest - Additional table expressions - distinct - Only return distinct rows - - Returns - ------- - Table - A new table containing the intersection of all input tables. - - See Also - -------- - [`ibis.intersect`](./expression-tables.qmd#ibis.intersect) - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t1 = ibis.memtable({"a": [1, 2]}) - >>> t1 - ┏━━━━━━━┓ - ┃ a ┃ - ┡━━━━━━━┩ - │ int64 │ - ├───────┤ - │ 1 │ - │ 2 │ - └───────┘ - >>> t2 = ibis.memtable({"a": [2, 3]}) - >>> t2 - ┏━━━━━━━┓ - ┃ a ┃ - ┡━━━━━━━┩ - │ int64 │ - ├───────┤ - │ 2 │ - │ 3 │ - └───────┘ - >>> t1.intersect(t2) - ┏━━━━━━━┓ - ┃ a ┃ - ┡━━━━━━━┩ - │ int64 │ - ├───────┤ - │ 2 │ - └───────┘ - """ - return self._assemble_set_op(ops.Intersection, table, *rest, distinct=distinct) - - def difference(self, table: Table, *rest: Table, distinct: bool = True) -> Table: - """Compute the set difference of multiple table expressions. - - The input tables must have identical schemas. - - Parameters - ---------- - table: - A table expression - *rest: - Additional table expressions - distinct - Only diff distinct rows not occurring in the calling table - - See Also - -------- - [`ibis.difference`](./expression-tables.qmd#ibis.difference) - - Returns - ------- - Table - The rows present in `self` that are not present in `tables`. - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t1 = ibis.memtable({"a": [1, 2]}) - >>> t1 - ┏━━━━━━━┓ - ┃ a ┃ - ┡━━━━━━━┩ - │ int64 │ - ├───────┤ - │ 1 │ - │ 2 │ - └───────┘ - >>> t2 = ibis.memtable({"a": [2, 3]}) - >>> t2 - ┏━━━━━━━┓ - ┃ a ┃ - ┡━━━━━━━┩ - │ int64 │ - ├───────┤ - │ 2 │ - │ 3 │ - └───────┘ - >>> t1.difference(t2) - ┏━━━━━━━┓ - ┃ a ┃ - ┡━━━━━━━┩ - │ int64 │ - ├───────┤ - │ 1 │ - └───────┘ - """ - node = ops.Difference(self, table, distinct=distinct) - for expr in rest: - node = ops.Difference(node, expr, distinct=distinct) - return node.to_expr() - - @deprecated(as_of="9.0", instead="use table.as_scalar() instead") - def to_array(self) -> ir.Column: - """View a single column table as an array. - - Returns - ------- - Value - A single column view of a table - """ - schema = self.schema() - if len(schema) != 1: - raise com.ExpressionError( - "Table must have exactly one column when viewed as array" - ) - return self.as_scalar() - - def mutate(self, *exprs: Sequence[ir.Expr] | None, **mutations: ir.Value) -> Table: - """Add columns to a table expression. - - Parameters - ---------- - exprs - List of named expressions to add as columns - mutations - Named expressions using keyword arguments - - Returns - ------- - Table - Table expression with additional columns - - Examples - -------- - >>> import ibis - >>> import ibis.selectors as s - >>> from ibis import _ - >>> ibis.options.interactive = True - >>> t = ibis.examples.penguins.fetch().select("species", "year", "bill_length_mm") - >>> t - ┏━━━━━━━━━┳━━━━━━━┳━━━━━━━━━━━━━━━━┓ - ┃ species ┃ year ┃ bill_length_mm ┃ - ┡━━━━━━━━━╇━━━━━━━╇━━━━━━━━━━━━━━━━┩ - │ string │ int64 │ float64 │ - ├─────────┼───────┼────────────────┤ - │ Adelie │ 2007 │ 39.1 │ - │ Adelie │ 2007 │ 39.5 │ - │ Adelie │ 2007 │ 40.3 │ - │ Adelie │ 2007 │ NULL │ - │ Adelie │ 2007 │ 36.7 │ - │ Adelie │ 2007 │ 39.3 │ - │ Adelie │ 2007 │ 38.9 │ - │ Adelie │ 2007 │ 39.2 │ - │ Adelie │ 2007 │ 34.1 │ - │ Adelie │ 2007 │ 42.0 │ - │ … │ … │ … │ - └─────────┴───────┴────────────────┘ - - Add a new column from a per-element expression - - >>> t.mutate(next_year=_.year + 1).head() - ┏━━━━━━━━━┳━━━━━━━┳━━━━━━━━━━━━━━━━┳━━━━━━━━━━━┓ - ┃ species ┃ year ┃ bill_length_mm ┃ next_year ┃ - ┡━━━━━━━━━╇━━━━━━━╇━━━━━━━━━━━━━━━━╇━━━━━━━━━━━┩ - │ string │ int64 │ float64 │ int64 │ - ├─────────┼───────┼────────────────┼───────────┤ - │ Adelie │ 2007 │ 39.1 │ 2008 │ - │ Adelie │ 2007 │ 39.5 │ 2008 │ - │ Adelie │ 2007 │ 40.3 │ 2008 │ - │ Adelie │ 2007 │ NULL │ 2008 │ - │ Adelie │ 2007 │ 36.7 │ 2008 │ - └─────────┴───────┴────────────────┴───────────┘ - - Add a new column based on an aggregation. Note the automatic broadcasting. - - >>> t.select("species", bill_demean=_.bill_length_mm - _.bill_length_mm.mean()).head() - ┏━━━━━━━━━┳━━━━━━━━━━━━━┓ - ┃ species ┃ bill_demean ┃ - ┡━━━━━━━━━╇━━━━━━━━━━━━━┩ - │ string │ float64 │ - ├─────────┼─────────────┤ - │ Adelie │ -4.82193 │ - │ Adelie │ -4.42193 │ - │ Adelie │ -3.62193 │ - │ Adelie │ NULL │ - │ Adelie │ -7.22193 │ - └─────────┴─────────────┘ - - Mutate across multiple columns - - >>> t.mutate(s.across(s.numeric() & ~s.c("year"), _ - _.mean())).head() - ┏━━━━━━━━━┳━━━━━━━┳━━━━━━━━━━━━━━━━┓ - ┃ species ┃ year ┃ bill_length_mm ┃ - ┡━━━━━━━━━╇━━━━━━━╇━━━━━━━━━━━━━━━━┩ - │ string │ int64 │ float64 │ - ├─────────┼───────┼────────────────┤ - │ Adelie │ 2007 │ -4.82193 │ - │ Adelie │ 2007 │ -4.42193 │ - │ Adelie │ 2007 │ -3.62193 │ - │ Adelie │ 2007 │ NULL │ - │ Adelie │ 2007 │ -7.22193 │ - └─────────┴───────┴────────────────┘ - """ - # string and integer inputs are going to be coerced to literals instead - # of interpreted as column references like in select - node = self.op() - values = self.bind(*exprs, **mutations) - values = unwrap_aliases(values) - # allow overriding of fields, hence the mutation behavior - values = {**node.fields, **values} - return self.select(**values) - - def select( - self, - *exprs: ir.Value | str | Iterable[ir.Value | str], - **named_exprs: ir.Value | str, - ) -> Table: - """Compute a new table expression using `exprs` and `named_exprs`. - - Passing an aggregate function to this method will broadcast the - aggregate's value over the number of rows in the table and - automatically constructs a window function expression. See the examples - section for more details. - - For backwards compatibility the keyword argument `exprs` is reserved - and cannot be used to name an expression. This behavior will be removed - in v4. - - Parameters - ---------- - exprs - Column expression, string, or list of column expressions and - strings. - named_exprs - Column expressions - - Returns - ------- - Table - Table expression - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.examples.penguins.fetch() - >>> t - ┏━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━┳━━━┓ - ┃ species ┃ island ┃ bill_length_mm ┃ bill_depth_mm ┃ flipper_length_mm ┃ … ┃ - ┡━━━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━╇━━━┩ - │ string │ string │ float64 │ float64 │ int64 │ … │ - ├─────────┼───────────┼────────────────┼───────────────┼───────────────────┼───┤ - │ Adelie │ Torgersen │ 39.1 │ 18.7 │ 181 │ … │ - │ Adelie │ Torgersen │ 39.5 │ 17.4 │ 186 │ … │ - │ Adelie │ Torgersen │ 40.3 │ 18.0 │ 195 │ … │ - │ Adelie │ Torgersen │ NULL │ NULL │ NULL │ … │ - │ Adelie │ Torgersen │ 36.7 │ 19.3 │ 193 │ … │ - │ Adelie │ Torgersen │ 39.3 │ 20.6 │ 190 │ … │ - │ Adelie │ Torgersen │ 38.9 │ 17.8 │ 181 │ … │ - │ Adelie │ Torgersen │ 39.2 │ 19.6 │ 195 │ … │ - │ Adelie │ Torgersen │ 34.1 │ 18.1 │ 193 │ … │ - │ Adelie │ Torgersen │ 42.0 │ 20.2 │ 190 │ … │ - │ … │ … │ … │ … │ … │ … │ - └─────────┴───────────┴────────────────┴───────────────┴───────────────────┴───┘ - - Simple projection - - >>> t.select("island", "bill_length_mm").head() - ┏━━━━━━━━━━━┳━━━━━━━━━━━━━━━━┓ - ┃ island ┃ bill_length_mm ┃ - ┡━━━━━━━━━━━╇━━━━━━━━━━━━━━━━┩ - │ string │ float64 │ - ├───────────┼────────────────┤ - │ Torgersen │ 39.1 │ - │ Torgersen │ 39.5 │ - │ Torgersen │ 40.3 │ - │ Torgersen │ NULL │ - │ Torgersen │ 36.7 │ - └───────────┴────────────────┘ - - In that simple case, you could also just use python's indexing syntax - - >>> t[["island", "bill_length_mm"]].head() - ┏━━━━━━━━━━━┳━━━━━━━━━━━━━━━━┓ - ┃ island ┃ bill_length_mm ┃ - ┡━━━━━━━━━━━╇━━━━━━━━━━━━━━━━┩ - │ string │ float64 │ - ├───────────┼────────────────┤ - │ Torgersen │ 39.1 │ - │ Torgersen │ 39.5 │ - │ Torgersen │ 40.3 │ - │ Torgersen │ NULL │ - │ Torgersen │ 36.7 │ - └───────────┴────────────────┘ - - Projection by zero-indexed column position - - >>> t.select(t[0], t[4]).head() - ┏━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━┓ - ┃ species ┃ flipper_length_mm ┃ - ┡━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━┩ - │ string │ int64 │ - ├─────────┼───────────────────┤ - │ Adelie │ 181 │ - │ Adelie │ 186 │ - │ Adelie │ 195 │ - │ Adelie │ NULL │ - │ Adelie │ 193 │ - └─────────┴───────────────────┘ - - Projection with renaming and compute in one call - - >>> t.select(next_year=t.year + 1).head() - ┏━━━━━━━━━━━┓ - ┃ next_year ┃ - ┡━━━━━━━━━━━┩ - │ int64 │ - ├───────────┤ - │ 2008 │ - │ 2008 │ - │ 2008 │ - │ 2008 │ - │ 2008 │ - └───────────┘ - - You can do the same thing with a named expression, and using the - deferred API - - >>> from ibis import _ - >>> t.select((_.year + 1).name("next_year")).head() - ┏━━━━━━━━━━━┓ - ┃ next_year ┃ - ┡━━━━━━━━━━━┩ - │ int64 │ - ├───────────┤ - │ 2008 │ - │ 2008 │ - │ 2008 │ - │ 2008 │ - │ 2008 │ - └───────────┘ - - Projection with aggregation expressions - - >>> t.select("island", bill_mean=t.bill_length_mm.mean()).head() - ┏━━━━━━━━━━━┳━━━━━━━━━━━┓ - ┃ island ┃ bill_mean ┃ - ┡━━━━━━━━━━━╇━━━━━━━━━━━┩ - │ string │ float64 │ - ├───────────┼───────────┤ - │ Torgersen │ 43.92193 │ - │ Torgersen │ 43.92193 │ - │ Torgersen │ 43.92193 │ - │ Torgersen │ 43.92193 │ - │ Torgersen │ 43.92193 │ - └───────────┴───────────┘ - - Projection with a selector - - >>> import ibis.selectors as s - >>> t.select(s.numeric() & ~s.c("year")).head() - ┏━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━┓ - ┃ bill_length_mm ┃ bill_depth_mm ┃ flipper_length_mm ┃ body_mass_g ┃ - ┡━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━┩ - │ float64 │ float64 │ int64 │ int64 │ - ├────────────────┼───────────────┼───────────────────┼─────────────┤ - │ 39.1 │ 18.7 │ 181 │ 3750 │ - │ 39.5 │ 17.4 │ 186 │ 3800 │ - │ 40.3 │ 18.0 │ 195 │ 3250 │ - │ NULL │ NULL │ NULL │ NULL │ - │ 36.7 │ 19.3 │ 193 │ 3450 │ - └────────────────┴───────────────┴───────────────────┴─────────────┘ - - Projection + aggregation across multiple columns - - >>> from ibis import _ - >>> t.select(s.across(s.numeric() & ~s.c("year"), _.mean())).head() - ┏━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━┓ - ┃ bill_length_mm ┃ bill_depth_mm ┃ flipper_length_mm ┃ body_mass_g ┃ - ┡━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━┩ - │ float64 │ float64 │ float64 │ float64 │ - ├────────────────┼───────────────┼───────────────────┼─────────────┤ - │ 43.92193 │ 17.15117 │ 200.915205 │ 4201.754386 │ - │ 43.92193 │ 17.15117 │ 200.915205 │ 4201.754386 │ - │ 43.92193 │ 17.15117 │ 200.915205 │ 4201.754386 │ - │ 43.92193 │ 17.15117 │ 200.915205 │ 4201.754386 │ - │ 43.92193 │ 17.15117 │ 200.915205 │ 4201.754386 │ - └────────────────┴───────────────┴───────────────────┴─────────────┘ - """ - from bigframes_vendored.ibis.expr.rewrites import rewrite_project_input - - values = self.bind(*exprs, **named_exprs) - values = unwrap_aliases(values) - if not values: - raise com.IbisTypeError( - "You must select at least one column for a valid projection" - ) - - # we need to detect reductions which are either turned into window functions - # or scalar subqueries depending on whether they are originating from self - values = { - k: rewrite_project_input(v, relation=self.op()) for k, v in values.items() - } - return ops.Project(self, values).to_expr() - - projection = select - - @util.deprecated( - as_of="7.0", - instead=( - "use `Table.rename` instead (if passing a mapping, note the meaning " - "of keys and values are swapped in Table.rename)." - ), - ) - def relabel( - self, - substitutions: ( - Mapping[str, str] - | Callable[[str], str | None] - | str - | Literal["snake_case", "ALL_CAPS"] - ), - ) -> Table: - """Deprecated in favor of `Table.rename`.""" - if isinstance(substitutions, Mapping): - substitutions = {new: old for old, new in substitutions.items()} - return self.rename(substitutions) - - def rename( - self, - method: ( - str - | Callable[[str], str | None] - | Literal["snake_case", "ALL_CAPS"] - | Mapping[str, str] - | None - ) = None, - /, - **substitutions: str, - ) -> Table: - """Rename columns in the table. - - Parameters - ---------- - method - An optional method for renaming columns. May be one of: - - - A format string to use to rename all columns, like - ``"prefix_{name}"``. - - A function from old name to new name. If the function returns - ``None`` the old name is used. - - The literal strings ``"snake_case"`` or ``"ALL_CAPS"`` to - rename all columns using a ``snake_case`` or ``"ALL_CAPS"`` - naming convention respectively. - - A mapping from new name to old name. Existing columns not present - in the mapping will passthrough with their original name. - substitutions - Columns to be explicitly renamed, expressed as ``new_name=old_name`` - keyword arguments. - - Returns - ------- - Table - A renamed table expression - - Examples - -------- - >>> import ibis - >>> import ibis.selectors as s - >>> ibis.options.interactive = True - >>> first3 = s.r[:3] # first 3 columns - >>> t = ibis.examples.penguins_raw_raw.fetch().select(first3) - >>> t - ┏━━━━━━━━━━━┳━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ studyName ┃ Sample Number ┃ Species ┃ - ┡━━━━━━━━━━━╇━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩ - │ string │ int64 │ string │ - ├───────────┼───────────────┼─────────────────────────────────────┤ - │ PAL0708 │ 1 │ Adelie Penguin (Pygoscelis adeliae) │ - │ PAL0708 │ 2 │ Adelie Penguin (Pygoscelis adeliae) │ - │ PAL0708 │ 3 │ Adelie Penguin (Pygoscelis adeliae) │ - │ PAL0708 │ 4 │ Adelie Penguin (Pygoscelis adeliae) │ - │ PAL0708 │ 5 │ Adelie Penguin (Pygoscelis adeliae) │ - │ PAL0708 │ 6 │ Adelie Penguin (Pygoscelis adeliae) │ - │ PAL0708 │ 7 │ Adelie Penguin (Pygoscelis adeliae) │ - │ PAL0708 │ 8 │ Adelie Penguin (Pygoscelis adeliae) │ - │ PAL0708 │ 9 │ Adelie Penguin (Pygoscelis adeliae) │ - │ PAL0708 │ 10 │ Adelie Penguin (Pygoscelis adeliae) │ - │ … │ … │ … │ - └───────────┴───────────────┴─────────────────────────────────────┘ - - Rename specific columns by passing keyword arguments like - ``new_name="old_name"`` - - >>> t.rename(study_name="studyName").head(1) - ┏━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ study_name ┃ Sample Number ┃ Species ┃ - ┡━━━━━━━━━━━━╇━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩ - │ string │ int64 │ string │ - ├────────────┼───────────────┼─────────────────────────────────────┤ - │ PAL0708 │ 1 │ Adelie Penguin (Pygoscelis adeliae) │ - └────────────┴───────────────┴─────────────────────────────────────┘ - - Rename all columns using a format string - - >>> t.rename("p_{name}").head(1) - ┏━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ p_studyName ┃ p_Sample Number ┃ p_Species ┃ - ┡━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩ - │ string │ int64 │ string │ - ├─────────────┼─────────────────┼─────────────────────────────────────┤ - │ PAL0708 │ 1 │ Adelie Penguin (Pygoscelis adeliae) │ - └─────────────┴─────────────────┴─────────────────────────────────────┘ - - Rename all columns using a snake_case convention - - >>> t.rename("snake_case").head(1) - ┏━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ study_name ┃ sample_number ┃ species ┃ - ┡━━━━━━━━━━━━╇━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩ - │ string │ int64 │ string │ - ├────────────┼───────────────┼─────────────────────────────────────┤ - │ PAL0708 │ 1 │ Adelie Penguin (Pygoscelis adeliae) │ - └────────────┴───────────────┴─────────────────────────────────────┘ - - Rename all columns using an ALL_CAPS convention - - >>> t.rename("ALL_CAPS").head(1) - ┏━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ STUDY_NAME ┃ SAMPLE_NUMBER ┃ SPECIES ┃ - ┡━━━━━━━━━━━━╇━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩ - │ string │ int64 │ string │ - ├────────────┼───────────────┼─────────────────────────────────────┤ - │ PAL0708 │ 1 │ Adelie Penguin (Pygoscelis adeliae) │ - └────────────┴───────────────┴─────────────────────────────────────┘ - - Rename all columns using a callable - - >>> t.rename(str.upper).head(1) - ┏━━━━━━━━━━━┳━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ STUDYNAME ┃ SAMPLE NUMBER ┃ SPECIES ┃ - ┡━━━━━━━━━━━╇━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩ - │ string │ int64 │ string │ - ├───────────┼───────────────┼─────────────────────────────────────┤ - │ PAL0708 │ 1 │ Adelie Penguin (Pygoscelis adeliae) │ - └───────────┴───────────────┴─────────────────────────────────────┘ - """ - if isinstance(method, Mapping): - substitutions.update(method) - method = None - - # A mapping from old_name -> renamed expr - renamed = {} - - for new_name, old_name in substitutions.items(): - if old_name not in renamed: - renamed[old_name] = (new_name, self[old_name].op()) - else: - raise ValueError("duplicate new names passed for renaming {old_name!r}") - - if isinstance(method, str) and method in {"snake_case", "ALL_CAPS"}: - - def rename(c): - c = c.strip() - if " " in c: - # Handle "space case possibly with-hyphens" - if method == "snake_case": - return "_".join(c.lower().split()).replace("-", "_") - elif method == "ALL_CAPS": - return "_".join(c.upper().split()).replace("-", "_") - # Handle PascalCase, camelCase, and kebab-case - c = re.sub(r"([A-Z]+)([A-Z][a-z])", r"\1_\2", c) - c = re.sub(r"([a-z\d])([A-Z])", r"\1_\2", c) - c = c.replace("-", "_") - if method == "snake_case": - return c.lower() - elif method == "ALL_CAPS": - return c.upper() - else: - return None - - elif isinstance(method, str): - - def rename(name): - return method.format(name=name) - - # Detect the case of missing or extra format string parameters - try: - dummy_name1 = "_unlikely_column_name_1_" - dummy_name2 = "_unlikely_column_name_2_" - invalid = rename(dummy_name1) == rename(dummy_name2) - except KeyError: - invalid = True - if invalid: - raise ValueError("Format strings must take a single parameter `name`") - else: - rename = method - - exprs = {} - fields = self.op().fields - for c in self.columns: - if (new_name_op := renamed.get(c)) is not None: - new_name, op = new_name_op - else: - op = fields[c] - if rename is None or (new_name := rename(c)) is None: - new_name = c - - exprs[new_name] = op - - return ops.Project(self, exprs).to_expr() - - def drop(self, *fields: str | Selector) -> Table: - """Remove fields from a table. - - Parameters - ---------- - fields - Fields to drop. Strings and selectors are accepted. - - Returns - ------- - Table - A table with all columns matching `fields` removed. - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.examples.penguins.fetch() - >>> t - ┏━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━┳━━━┓ - ┃ species ┃ island ┃ bill_length_mm ┃ bill_depth_mm ┃ flipper_length_mm ┃ … ┃ - ┡━━━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━╇━━━┩ - │ string │ string │ float64 │ float64 │ int64 │ … │ - ├─────────┼───────────┼────────────────┼───────────────┼───────────────────┼───┤ - │ Adelie │ Torgersen │ 39.1 │ 18.7 │ 181 │ … │ - │ Adelie │ Torgersen │ 39.5 │ 17.4 │ 186 │ … │ - │ Adelie │ Torgersen │ 40.3 │ 18.0 │ 195 │ … │ - │ Adelie │ Torgersen │ NULL │ NULL │ NULL │ … │ - │ Adelie │ Torgersen │ 36.7 │ 19.3 │ 193 │ … │ - │ Adelie │ Torgersen │ 39.3 │ 20.6 │ 190 │ … │ - │ Adelie │ Torgersen │ 38.9 │ 17.8 │ 181 │ … │ - │ Adelie │ Torgersen │ 39.2 │ 19.6 │ 195 │ … │ - │ Adelie │ Torgersen │ 34.1 │ 18.1 │ 193 │ … │ - │ Adelie │ Torgersen │ 42.0 │ 20.2 │ 190 │ … │ - │ … │ … │ … │ … │ … │ … │ - └─────────┴───────────┴────────────────┴───────────────┴───────────────────┴───┘ - - Drop one or more columns - - >>> t.drop("species").head() - ┏━━━━━━━━━━━┳━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━┳━━━┓ - ┃ island ┃ bill_length_mm ┃ bill_depth_mm ┃ flipper_length_mm ┃ … ┃ - ┡━━━━━━━━━━━╇━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━╇━━━┩ - │ string │ float64 │ float64 │ int64 │ … │ - ├───────────┼────────────────┼───────────────┼───────────────────┼───┤ - │ Torgersen │ 39.1 │ 18.7 │ 181 │ … │ - │ Torgersen │ 39.5 │ 17.4 │ 186 │ … │ - │ Torgersen │ 40.3 │ 18.0 │ 195 │ … │ - │ Torgersen │ NULL │ NULL │ NULL │ … │ - │ Torgersen │ 36.7 │ 19.3 │ 193 │ … │ - └───────────┴────────────────┴───────────────┴───────────────────┴───┘ - >>> t.drop("species", "bill_length_mm").head() - ┏━━━━━━━━━━━┳━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━┳━━━┓ - ┃ island ┃ bill_depth_mm ┃ flipper_length_mm ┃ body_mass_g ┃ sex ┃ … ┃ - ┡━━━━━━━━━━━╇━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━╇━━━┩ - │ string │ float64 │ int64 │ int64 │ string │ … │ - ├───────────┼───────────────┼───────────────────┼─────────────┼────────┼───┤ - │ Torgersen │ 18.7 │ 181 │ 3750 │ male │ … │ - │ Torgersen │ 17.4 │ 186 │ 3800 │ female │ … │ - │ Torgersen │ 18.0 │ 195 │ 3250 │ female │ … │ - │ Torgersen │ NULL │ NULL │ NULL │ NULL │ … │ - │ Torgersen │ 19.3 │ 193 │ 3450 │ female │ … │ - └───────────┴───────────────┴───────────────────┴─────────────┴────────┴───┘ - - Drop with selectors, mix and match - - >>> import ibis.selectors as s - >>> t.drop("species", s.startswith("bill_")).head() - ┏━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━┳━━━━━━━┓ - ┃ island ┃ flipper_length_mm ┃ body_mass_g ┃ sex ┃ year ┃ - ┡━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━╇━━━━━━━┩ - │ string │ int64 │ int64 │ string │ int64 │ - ├───────────┼───────────────────┼─────────────┼────────┼───────┤ - │ Torgersen │ 181 │ 3750 │ male │ 2007 │ - │ Torgersen │ 186 │ 3800 │ female │ 2007 │ - │ Torgersen │ 195 │ 3250 │ female │ 2007 │ - │ Torgersen │ NULL │ NULL │ NULL │ 2007 │ - │ Torgersen │ 193 │ 3450 │ female │ 2007 │ - └───────────┴───────────────────┴─────────────┴────────┴───────┘ - """ - if not fields: - # no-op if nothing to be dropped - return self - - columns_to_drop = tuple( - map(operator.methodcaller("get_name"), self._fast_bind(*fields)) - ) - return ops.DropColumns(parent=self, columns_to_drop=columns_to_drop).to_expr() - - def filter( - self, - *predicates: ir.BooleanValue | Sequence[ir.BooleanValue] | IfAnyAll, - ) -> Table: - """Select rows from `table` based on `predicates`. - - Parameters - ---------- - predicates - Boolean value expressions used to select rows in `table`. - - Returns - ------- - Table - Filtered table expression - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.examples.penguins.fetch() - >>> t - ┏━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━┳━━━┓ - ┃ species ┃ island ┃ bill_length_mm ┃ bill_depth_mm ┃ flipper_length_mm ┃ … ┃ - ┡━━━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━╇━━━┩ - │ string │ string │ float64 │ float64 │ int64 │ … │ - ├─────────┼───────────┼────────────────┼───────────────┼───────────────────┼───┤ - │ Adelie │ Torgersen │ 39.1 │ 18.7 │ 181 │ … │ - │ Adelie │ Torgersen │ 39.5 │ 17.4 │ 186 │ … │ - │ Adelie │ Torgersen │ 40.3 │ 18.0 │ 195 │ … │ - │ Adelie │ Torgersen │ NULL │ NULL │ NULL │ … │ - │ Adelie │ Torgersen │ 36.7 │ 19.3 │ 193 │ … │ - │ Adelie │ Torgersen │ 39.3 │ 20.6 │ 190 │ … │ - │ Adelie │ Torgersen │ 38.9 │ 17.8 │ 181 │ … │ - │ Adelie │ Torgersen │ 39.2 │ 19.6 │ 195 │ … │ - │ Adelie │ Torgersen │ 34.1 │ 18.1 │ 193 │ … │ - │ Adelie │ Torgersen │ 42.0 │ 20.2 │ 190 │ … │ - │ … │ … │ … │ … │ … │ … │ - └─────────┴───────────┴────────────────┴───────────────┴───────────────────┴───┘ - >>> t.filter([t.species == "Adelie", t.body_mass_g > 3500]).sex.value_counts().drop_null( - ... "sex" - ... ).order_by("sex") - ┏━━━━━━━━┳━━━━━━━━━━━┓ - ┃ sex ┃ sex_count ┃ - ┡━━━━━━━━╇━━━━━━━━━━━┩ - │ string │ int64 │ - ├────────┼───────────┤ - │ female │ 22 │ - │ male │ 68 │ - └────────┴───────────┘ - """ - from bigframes_vendored.ibis.expr.rewrites import ( - flatten_predicates, - rewrite_filter_input, - ) - - preds = self.bind(*predicates) - - # we can't use `unwrap_aliases` here because that function - # deduplicates based on name alone - # - # it's perfectly valid to repeat a filter, even if it might be - # useless, so enforcing uniquely named expressions here doesn't make - # sense - # - # instead, compute all distinct unaliased predicates - result = toolz.unique( - node.arg if isinstance(node := value.op(), ops.Alias) else node - for value in preds - ) - - preds = flatten_predicates(list(result)) - preds = list(map(rewrite_filter_input, preds)) - if not preds: - raise com.IbisInputError("You must pass at least one predicate to filter") - return ops.Filter(self, preds).to_expr() - - def nunique(self, where: ir.BooleanValue | None = None) -> ir.IntegerScalar: - """Compute the number of unique rows in the table. - - Parameters - ---------- - where - Optional boolean expression to filter rows when counting. - - Returns - ------- - IntegerScalar - Number of unique rows in the table - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.memtable({"a": ["foo", "bar", "bar"]}) - >>> t - ┏━━━━━━━━┓ - ┃ a ┃ - ┡━━━━━━━━┩ - │ string │ - ├────────┤ - │ foo │ - │ bar │ - │ bar │ - └────────┘ - >>> t.nunique() - ┌─────────────┐ - │ np.int64(2) │ - └─────────────┘ - >>> t.nunique(t.a != "foo") - ┌─────────────┐ - │ np.int64(1) │ - └─────────────┘ - """ - if where is not None: - (where,) = bind(self, where) - return ops.CountDistinctStar(self, where=where).to_expr() - - def count(self, where: ir.BooleanValue | None = None) -> ir.IntegerScalar: - """Compute the number of rows in the table. - - Parameters - ---------- - where - Optional boolean expression to filter rows when counting. - - Returns - ------- - IntegerScalar - Number of rows in the table - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.memtable({"a": ["foo", "bar", "baz"]}) - >>> t - ┏━━━━━━━━┓ - ┃ a ┃ - ┡━━━━━━━━┩ - │ string │ - ├────────┤ - │ foo │ - │ bar │ - │ baz │ - └────────┘ - >>> t.count() - ┌─────────────┐ - │ np.int64(3) │ - └─────────────┘ - >>> t.count(t.a != "foo") - ┌─────────────┐ - │ np.int64(2) │ - └─────────────┘ - >>> type(t.count()) - - """ - if where is not None: - (where,) = bind(self, where) - return ops.CountStar(self, where=where).to_expr() - - def drop_null( - self, - subset: Sequence[str] | str | None = None, - how: Literal["any", "all"] = "any", - ) -> Table: - """Remove rows with null values from the table. - - Parameters - ---------- - subset - Columns names to consider when dropping nulls. By default all columns - are considered. - how - Determine whether a row is removed if there is **at least one null - value in the row** (`'any'`), or if **all** row values are null - (`'all'`). - - Returns - ------- - Table - Table expression - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.examples.penguins.fetch() - >>> t - ┏━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━┳━━━┓ - ┃ species ┃ island ┃ bill_length_mm ┃ bill_depth_mm ┃ flipper_length_mm ┃ … ┃ - ┡━━━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━╇━━━┩ - │ string │ string │ float64 │ float64 │ int64 │ … │ - ├─────────┼───────────┼────────────────┼───────────────┼───────────────────┼───┤ - │ Adelie │ Torgersen │ 39.1 │ 18.7 │ 181 │ … │ - │ Adelie │ Torgersen │ 39.5 │ 17.4 │ 186 │ … │ - │ Adelie │ Torgersen │ 40.3 │ 18.0 │ 195 │ … │ - │ Adelie │ Torgersen │ NULL │ NULL │ NULL │ … │ - │ Adelie │ Torgersen │ 36.7 │ 19.3 │ 193 │ … │ - │ Adelie │ Torgersen │ 39.3 │ 20.6 │ 190 │ … │ - │ Adelie │ Torgersen │ 38.9 │ 17.8 │ 181 │ … │ - │ Adelie │ Torgersen │ 39.2 │ 19.6 │ 195 │ … │ - │ Adelie │ Torgersen │ 34.1 │ 18.1 │ 193 │ … │ - │ Adelie │ Torgersen │ 42.0 │ 20.2 │ 190 │ … │ - │ … │ … │ … │ … │ … │ … │ - └─────────┴───────────┴────────────────┴───────────────┴───────────────────┴───┘ - >>> t.count() - ┌───────────────┐ - │ np.int64(344) │ - └───────────────┘ - >>> t.drop_null(["bill_length_mm", "body_mass_g"]).count() - ┌───────────────┐ - │ np.int64(342) │ - └───────────────┘ - >>> t.drop_null(how="all").count() # no rows where all columns are null - ┌───────────────┐ - │ np.int64(344) │ - └───────────────┘ - """ - if subset is not None: - subset = self.bind(subset) - return ops.DropNull(self, how, subset).to_expr() - - def fill_null( - self, - replacements: ir.Scalar | Mapping[str, ir.Scalar], - ) -> Table: - """Fill null values in a table expression. - - ::: {.callout-note} - ## There is potential lack of type stability with the `fill_null` API - - For example, different library versions may impact whether a given - backend promotes integer replacement values to floats. - ::: - - Parameters - ---------- - replacements - Value with which to fill nulls. If `replacements` is a mapping, the - keys are column names that map to their replacement value. If - passed as a scalar all columns are filled with that value. - - Returns - ------- - Table - Table expression - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.examples.penguins.fetch() - >>> t.sex - ┏━━━━━━━━┓ - ┃ sex ┃ - ┡━━━━━━━━┩ - │ string │ - ├────────┤ - │ male │ - │ female │ - │ female │ - │ NULL │ - │ female │ - │ male │ - │ female │ - │ male │ - │ NULL │ - │ NULL │ - │ … │ - └────────┘ - >>> t.fill_null({"sex": "unrecorded"}).sex - ┏━━━━━━━━━━━━┓ - ┃ sex ┃ - ┡━━━━━━━━━━━━┩ - │ string │ - ├────────────┤ - │ male │ - │ female │ - │ female │ - │ unrecorded │ - │ female │ - │ male │ - │ female │ - │ male │ - │ unrecorded │ - │ unrecorded │ - │ … │ - └────────────┘ - """ - schema = self.schema() - - if isinstance(replacements, Mapping): - for col, val in replacements.items(): - if col not in schema: - columns_formatted = ", ".join(map(repr, schema.names)) - raise com.IbisTypeError( - f"Column {col!r} is not found in table. " - f"Existing columns: {columns_formatted}." - ) from None - - col_type = schema[col] - val_type = val.type() if isinstance(val, Expr) else dt.infer(val) - if not val_type.castable(col_type): - raise com.IbisTypeError( - f"Cannot fill_null on column {col!r} of type {col_type} with a " - f"value of type {val_type}" - ) - else: - val_type = ( - replacements.type() - if isinstance(replacements, Expr) - else dt.infer(replacements) - ) - for col, col_type in schema.items(): - if col_type.nullable and not val_type.castable(col_type): - raise com.IbisTypeError( - f"Cannot fill_null on column {col!r} of type {col_type} with a " - f"value of type {val_type} - pass in an explicit mapping " - f"of fill values to `fill_null` instead." - ) - return ops.FillNull(self, replacements).to_expr() - - @deprecated(as_of="9.1", instead="use drop_null instead") - def dropna( - self, - subset: Sequence[str] | str | None = None, - how: Literal["any", "all"] = "any", - ) -> Table: - """Deprecated - use `drop_null` instead.""" - - return self.drop_null(subset, how) - - @deprecated(as_of="9.1", instead="use fill_null instead") - def fillna( - self, - replacements: ir.Scalar | Mapping[str, ir.Scalar], - ) -> Table: - """Deprecated - use `fill_null` instead.""" - - return self.fill_null(replacements) - - def unpack(self, *columns: str) -> Table: - """Project the struct fields of each of `columns` into `self`. - - Existing fields are retained in the projection. - - Parameters - ---------- - columns - String column names to project into `self`. - - Returns - ------- - Table - The child table with struct fields of each of `columns` projected. - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> lines = ''' - ... {"name": "a", "pos": {"lat": 10.1, "lon": 30.3}} - ... {"name": "b", "pos": {"lat": 10.2, "lon": 30.2}} - ... {"name": "c", "pos": {"lat": 10.3, "lon": 30.1}} - ... ''' - >>> with open("/tmp/lines.json", "w") as f: - ... nbytes = f.write(lines) # nbytes is unused - >>> t = ibis.read_json("/tmp/lines.json") - >>> t - ┏━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ name ┃ pos ┃ - ┡━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩ - │ string │ struct │ - ├────────┼────────────────────────────────────┤ - │ a │ {'lat': 10.1, 'lon': 30.3} │ - │ b │ {'lat': 10.2, 'lon': 30.2} │ - │ c │ {'lat': 10.3, 'lon': 30.1} │ - └────────┴────────────────────────────────────┘ - >>> t.unpack("pos") - ┏━━━━━━━━┳━━━━━━━━━┳━━━━━━━━━┓ - ┃ name ┃ lat ┃ lon ┃ - ┡━━━━━━━━╇━━━━━━━━━╇━━━━━━━━━┩ - │ string │ float64 │ float64 │ - ├────────┼─────────┼─────────┤ - │ a │ 10.1 │ 30.3 │ - │ b │ 10.2 │ 30.2 │ - │ c │ 10.3 │ 30.1 │ - └────────┴─────────┴─────────┘ - - See Also - -------- - [`StructValue.lift`](./expression-collections.qmd#ibis.expr.types.structs.StructValue.lift) - """ - columns_to_unpack = frozenset(columns) - result_columns = [] - for column in self.columns: - if column in columns_to_unpack: - expr = self[column] - result_columns.extend(expr[field] for field in expr.names) - else: - result_columns.append(column) - return self[result_columns] - - def info(self) -> Table: - """Return summary information about a table. - - Returns - ------- - Table - Summary of `self` - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.examples.penguins.fetch() - >>> t.info() - ┏━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━┳━━━━━━━━━━┳━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━┳━━━┓ - ┃ name ┃ type ┃ nullable ┃ nulls ┃ non_nulls ┃ null_frac ┃ … ┃ - ┡━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━╇━━━━━━━━━━╇━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━━━━╇━━━┩ - │ string │ string │ boolean │ int64 │ int64 │ float64 │ … │ - ├───────────────────┼─────────┼──────────┼───────┼───────────┼───────────┼───┤ - │ species │ string │ True │ 0 │ 344 │ 0.000000 │ … │ - │ island │ string │ True │ 0 │ 344 │ 0.000000 │ … │ - │ bill_length_mm │ float64 │ True │ 2 │ 342 │ 0.005814 │ … │ - │ bill_depth_mm │ float64 │ True │ 2 │ 342 │ 0.005814 │ … │ - │ flipper_length_mm │ int64 │ True │ 2 │ 342 │ 0.005814 │ … │ - │ body_mass_g │ int64 │ True │ 2 │ 342 │ 0.005814 │ … │ - │ sex │ string │ True │ 11 │ 333 │ 0.031977 │ … │ - │ year │ int64 │ True │ 0 │ 344 │ 0.000000 │ … │ - └───────────────────┴─────────┴──────────┴───────┴───────────┴───────────┴───┘ - """ - from bigframes_vendored.ibis import literal as lit - - aggs = [] - - for pos, colname in enumerate(self.columns): - col = self[colname] - typ = col.type() - agg = self.select( - isna=bigframes_vendored.ibis.case().when(col.isnull(), 1).else_(0).end() - ).agg( - name=lit(colname), - type=lit(str(typ)), - nullable=lit(typ.nullable), - nulls=lambda t: t.isna.sum(), - non_nulls=lambda t: (1 - t.isna).sum(), - null_frac=lambda t: t.isna.mean(), - pos=lit(pos, type=dt.int16), - ) - aggs.append(agg) - return bigframes_vendored.ibis.union(*aggs).order_by( - bigframes_vendored.ibis.asc("pos") - ) - - def describe( - self, quantile: Sequence[ir.NumericValue | float] = (0.25, 0.5, 0.75) - ) -> Table: - """Return summary information about a table. - - Parameters - ---------- - quantile - The quantiles to compute for numerical columns. Defaults to (0.25, 0.5, 0.75). - - Returns - ------- - Table - A table containing summary information about the columns of self. - - Notes - ----- - This function computes summary statistics for each column in the table. For - numerical columns, it computes statistics such as minimum, maximum, mean, - standard deviation, and quantiles. For string columns, it computes the mode - and the number of unique values. - - Examples - -------- - >>> import ibis - >>> import ibis.selectors as s - >>> ibis.options.interactive = True - >>> p = ibis.examples.penguins.fetch() - >>> p.describe() - ┏━━━━━━━━━━━━━━━━━━━┳━━━━━━━┳━━━━━━━━━┳━━━━━━━┳━━━━━━━┳━━━━━━━━┳━━━━━━━━┳━━━┓ - ┃ name ┃ pos ┃ type ┃ count ┃ nulls ┃ unique ┃ mode ┃ … ┃ - ┡━━━━━━━━━━━━━━━━━━━╇━━━━━━━╇━━━━━━━━━╇━━━━━━━╇━━━━━━━╇━━━━━━━━╇━━━━━━━━╇━━━┩ - │ string │ int16 │ string │ int64 │ int64 │ int64 │ string │ … │ - ├───────────────────┼───────┼─────────┼───────┼───────┼────────┼────────┼───┤ - │ species │ 0 │ string │ 344 │ 0 │ 3 │ Adelie │ … │ - │ island │ 1 │ string │ 344 │ 0 │ 3 │ Biscoe │ … │ - │ bill_length_mm │ 2 │ float64 │ 344 │ 2 │ 164 │ NULL │ … │ - │ bill_depth_mm │ 3 │ float64 │ 344 │ 2 │ 80 │ NULL │ … │ - │ flipper_length_mm │ 4 │ int64 │ 344 │ 2 │ 55 │ NULL │ … │ - │ body_mass_g │ 5 │ int64 │ 344 │ 2 │ 94 │ NULL │ … │ - │ sex │ 6 │ string │ 344 │ 11 │ 2 │ male │ … │ - │ year │ 7 │ int64 │ 344 │ 0 │ 3 │ NULL │ … │ - └───────────────────┴───────┴─────────┴───────┴───────┴────────┴────────┴───┘ - >>> p.select(s.of_type("numeric")).describe() - ┏━━━━━━━━━━━━━━━━━━━┳━━━━━━━┳━━━━━━━━━┳━━━━━━━┳━━━━━━━┳━━━━━━━━┳━━━┓ - ┃ name ┃ pos ┃ type ┃ count ┃ nulls ┃ unique ┃ … ┃ - ┡━━━━━━━━━━━━━━━━━━━╇━━━━━━━╇━━━━━━━━━╇━━━━━━━╇━━━━━━━╇━━━━━━━━╇━━━┩ - │ string │ int16 │ string │ int64 │ int64 │ int64 │ … │ - ├───────────────────┼───────┼─────────┼───────┼───────┼────────┼───┤ - │ flipper_length_mm │ 2 │ int64 │ 344 │ 2 │ 55 │ … │ - │ body_mass_g │ 3 │ int64 │ 344 │ 2 │ 94 │ … │ - │ year │ 4 │ int64 │ 344 │ 0 │ 3 │ … │ - │ bill_length_mm │ 0 │ float64 │ 344 │ 2 │ 164 │ … │ - │ bill_depth_mm │ 1 │ float64 │ 344 │ 2 │ 80 │ … │ - └───────────────────┴───────┴─────────┴───────┴───────┴────────┴───┘ - >>> p.select(s.of_type("string")).describe() - ┏━━━━━━━━━┳━━━━━━━┳━━━━━━━━┳━━━━━━━┳━━━━━━━┳━━━━━━━━┳━━━━━━━━┓ - ┃ name ┃ pos ┃ type ┃ count ┃ nulls ┃ unique ┃ mode ┃ - ┡━━━━━━━━━╇━━━━━━━╇━━━━━━━━╇━━━━━━━╇━━━━━━━╇━━━━━━━━╇━━━━━━━━┩ - │ string │ int16 │ string │ int64 │ int64 │ int64 │ string │ - ├─────────┼───────┼────────┼───────┼───────┼────────┼────────┤ - │ sex │ 2 │ string │ 344 │ 11 │ 2 │ male │ - │ species │ 0 │ string │ 344 │ 0 │ 3 │ Adelie │ - │ island │ 1 │ string │ 344 │ 0 │ 3 │ Biscoe │ - └─────────┴───────┴────────┴───────┴───────┴────────┴────────┘ - """ - import bigframes_vendored.ibis.selectors as s - from bigframes_vendored.ibis.expr.types.generic import literal as lit - - quantile = sorted(quantile) - aggs = [] - string_col = False - numeric_col = False - for pos, colname in enumerate(self.columns): - col = self[colname] - typ = col.type() - - # default statistics to None - col_mean = lit(None).cast(float) - col_std = lit(None).cast(float) - col_min = lit(None).cast(float) - col_max = lit(None).cast(float) - col_mode = lit(None).cast(str) - quantile_values = { - f"p{100 * q:.6f}".rstrip("0").rstrip("."): lit(None).cast(float) - for q in quantile - } - - if typ.is_numeric(): - numeric_col = True - col_mean = col.mean() - col_std = col.std() - col_min = col.min().cast(float) - col_max = col.max().cast(float) - quantile_values = { - f"p{100 * q:.6f}".rstrip("0").rstrip("."): col.quantile(q).cast( - float - ) - for q in quantile - } - elif typ.is_string(): - string_col = True - col_mode = col.mode() - elif typ.is_boolean(): - numeric_col = True - col_mean = col.mean() - else: - # Will not calculate statistics for other types - continue - - agg = self.agg( - name=lit(colname), - pos=lit(pos, type=dt.int16), - type=lit(str(typ)), - count=col.isnull().count(), - nulls=col.isnull().sum(), - unique=col.nunique(), - mode=col_mode, - mean=col_mean, - std=col_std, - min=col_min, - **quantile_values, - max=col_max, - ) - aggs.append(agg) - - t = bigframes_vendored.ibis.union(*aggs) - - # TODO(jiting): Need a better way to remove columns with all NULL - if string_col and not numeric_col: - t = t.select(~s.of_type("float")) - elif numeric_col and not string_col: - t = t.drop("mode") - - return t - - def join( - left: Table, - right: Table, - predicates: ( - str - | Sequence[ - str - | ir.BooleanColumn - | Literal[True] - | Literal[False] - | tuple[ - str | ir.Column | ir.Deferred, - str | ir.Column | ir.Deferred, - ] - ] - ) = (), - how: JoinKind = "inner", - *, - lname: str = "", - rname: str = "{name}_right", - ) -> Table: - """Perform a join between two tables. - - Parameters - ---------- - left - Left table to join - right - Right table to join - predicates - Condition(s) to join on. See examples for details. - how - Join method, e.g. ``"inner"`` or ``"left"``. - lname - A format string to use to rename overlapping columns in the left - table (e.g. ``"left_{name}"``). - rname - A format string to use to rename overlapping columns in the right - table (e.g. ``"right_{name}"``). - - Examples - -------- - >>> import ibis - >>> from ibis import _ - >>> ibis.options.interactive = True - >>> movies = ibis.examples.ml_latest_small_movies.fetch() - >>> movies.head() - ┏━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ movieId ┃ title ┃ genres ┃ - ┡━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩ - │ int64 │ string │ string │ - ├─────────┼──────────────────────────────────┼─────────────────────────────────┤ - │ 1 │ Toy Story (1995) │ Adventure|Animation|Children|C… │ - │ 2 │ Jumanji (1995) │ Adventure|Children|Fantasy │ - │ 3 │ Grumpier Old Men (1995) │ Comedy|Romance │ - │ 4 │ Waiting to Exhale (1995) │ Comedy|Drama|Romance │ - │ 5 │ Father of the Bride Part II (19… │ Comedy │ - └─────────┴──────────────────────────────────┴─────────────────────────────────┘ - >>> ratings = ibis.examples.ml_latest_small_ratings.fetch().drop("timestamp") - >>> ratings.head() - ┏━━━━━━━━┳━━━━━━━━━┳━━━━━━━━━┓ - ┃ userId ┃ movieId ┃ rating ┃ - ┡━━━━━━━━╇━━━━━━━━━╇━━━━━━━━━┩ - │ int64 │ int64 │ float64 │ - ├────────┼─────────┼─────────┤ - │ 1 │ 1 │ 4.0 │ - │ 1 │ 3 │ 4.0 │ - │ 1 │ 6 │ 4.0 │ - │ 1 │ 47 │ 5.0 │ - │ 1 │ 50 │ 5.0 │ - └────────┴─────────┴─────────┘ - - Equality left join on the shared `movieId` column. - Note the `_right` suffix added to all overlapping - columns from the right table - (in this case only the "movieId" column). - - >>> ratings.join(movies, "movieId", how="left").head(5) - ┏━━━━━━━━┳━━━━━━━━━┳━━━━━━━━━┳━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━┓ - ┃ userId ┃ movieId ┃ rating ┃ movieId_right ┃ title ┃ … ┃ - ┡━━━━━━━━╇━━━━━━━━━╇━━━━━━━━━╇━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━┩ - │ int64 │ int64 │ float64 │ int64 │ string │ … │ - ├────────┼─────────┼─────────┼───────────────┼─────────────────────────────┼───┤ - │ 1 │ 1 │ 4.0 │ 1 │ Toy Story (1995) │ … │ - │ 1 │ 3 │ 4.0 │ 3 │ Grumpier Old Men (1995) │ … │ - │ 1 │ 6 │ 4.0 │ 6 │ Heat (1995) │ … │ - │ 1 │ 47 │ 5.0 │ 47 │ Seven (a.k.a. Se7en) (1995) │ … │ - │ 1 │ 50 │ 5.0 │ 50 │ Usual Suspects, The (1995) │ … │ - └────────┴─────────┴─────────┴───────────────┴─────────────────────────────┴───┘ - - Explicit equality join using the default `how` value of `"inner"`. - Note how there is no `_right` suffix added to the `movieId` column - since this is an inner join and the `movieId` column is part of the - join condition. - - >>> ratings.join(movies, ratings.movieId == movies.movieId).head(5) - ┏━━━━━━━━┳━━━━━━━━━┳━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ userId ┃ movieId ┃ rating ┃ title ┃ genres ┃ - ┡━━━━━━━━╇━━━━━━━━━╇━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━┩ - │ int64 │ int64 │ float64 │ string │ string │ - ├────────┼─────────┼─────────┼────────────────────────┼────────────────────────┤ - │ 1 │ 1 │ 4.0 │ Toy Story (1995) │ Adventure|Animation|C… │ - │ 1 │ 3 │ 4.0 │ Grumpier Old Men (199… │ Comedy|Romance │ - │ 1 │ 6 │ 4.0 │ Heat (1995) │ Action|Crime|Thriller │ - │ 1 │ 47 │ 5.0 │ Seven (a.k.a. Se7en) … │ Mystery|Thriller │ - │ 1 │ 50 │ 5.0 │ Usual Suspects, The (… │ Crime|Mystery|Thriller │ - └────────┴─────────┴─────────┴────────────────────────┴────────────────────────┘ - - >>> tags = ibis.examples.ml_latest_small_tags.fetch() - >>> tags.head() - ┏━━━━━━━━┳━━━━━━━━━┳━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━┓ - ┃ userId ┃ movieId ┃ tag ┃ timestamp ┃ - ┡━━━━━━━━╇━━━━━━━━━╇━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━┩ - │ int64 │ int64 │ string │ int64 │ - ├────────┼─────────┼─────────────────┼────────────┤ - │ 2 │ 60756 │ funny │ 1445714994 │ - │ 2 │ 60756 │ Highly quotable │ 1445714996 │ - │ 2 │ 60756 │ will ferrell │ 1445714992 │ - │ 2 │ 89774 │ Boxing story │ 1445715207 │ - │ 2 │ 89774 │ MMA │ 1445715200 │ - └────────┴─────────┴─────────────────┴────────────┘ - - You can join on multiple columns/conditions by passing in a - sequence. Find all instances where a user both tagged and - rated a movie: - - >>> tags.join(ratings, ["userId", "movieId"]).head(5).order_by("userId") - ┏━━━━━━━━┳━━━━━━━━━┳━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━┳━━━━━━━━━┓ - ┃ userId ┃ movieId ┃ tag ┃ timestamp ┃ rating ┃ - ┡━━━━━━━━╇━━━━━━━━━╇━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━╇━━━━━━━━━┩ - │ int64 │ int64 │ string │ int64 │ float64 │ - ├────────┼─────────┼────────────────┼────────────┼─────────┤ - │ 62 │ 2 │ Robin Williams │ 1528843907 │ 4.0 │ - │ 62 │ 110 │ sword fight │ 1528152535 │ 4.5 │ - │ 62 │ 410 │ gothic │ 1525636609 │ 4.5 │ - │ 62 │ 2023 │ mafia │ 1525636733 │ 5.0 │ - │ 62 │ 2124 │ quirky │ 1525636846 │ 5.0 │ - └────────┴─────────┴────────────────┴────────────┴─────────┘ - - To self-join a table with itself, you need to call - `.view()` on one of the arguments so the two tables - are distinct from each other. - - For crafting more complex join conditions, - a valid form of a join condition is a 2-tuple like - `({left_key}, {right_key})`, where each key can be - - - a Column - - Deferred expression - - lambda of the form (Table) -> Column - - For example, to find all movies pairings that received the same - (ignoring case) tags: - - >>> movie_tags = tags["movieId", "tag"] - >>> view = movie_tags.view() - >>> movie_tags.join( - ... view, - ... [ - ... movie_tags.movieId != view.movieId, - ... (_.tag.lower(), lambda t: t.tag.lower()), - ... ], - ... ).head().order_by(("movieId", "movieId_right")) - ┏━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━┓ - ┃ movieId ┃ tag ┃ movieId_right ┃ tag_right ┃ - ┡━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━┩ - │ int64 │ string │ int64 │ string │ - ├─────────┼───────────────────┼───────────────┼───────────────────┤ - │ 1732 │ funny │ 60756 │ funny │ - │ 1732 │ Highly quotable │ 60756 │ Highly quotable │ - │ 1732 │ drugs │ 106782 │ drugs │ - │ 5989 │ Leonardo DiCaprio │ 106782 │ Leonardo DiCaprio │ - │ 139385 │ tom hardy │ 89774 │ Tom Hardy │ - └─────────┴───────────────────┴───────────────┴───────────────────┘ - """ - from bigframes_vendored.ibis.expr.types.joins import Join - - return Join(left.op()).join( - right, predicates, how=how, lname=lname, rname=rname - ) - - def asof_join( - left: Table, - right: Table, - on: str | ir.BooleanColumn, - predicates: str | ir.Column | Sequence[str | ir.Column] = (), - tolerance: str | ir.IntervalScalar | None = None, - *, - lname: str = "", - rname: str = "{name}_right", - ) -> Table: - """Perform an "as-of" join between `left` and `right`. - - Similar to a left join except that the match is done on nearest key - rather than equal keys. - - Parameters - ---------- - left - Table expression - right - Table expression - on - Closest match inequality condition - predicates - Additional join predicates - tolerance - Amount of time to look behind when joining - lname - A format string to use to rename overlapping columns in the left - table (e.g. ``"left_{name}"``). - rname - A format string to use to rename overlapping columns in the right - table (e.g. ``"right_{name}"``). - - Returns - ------- - Table - Table expression - """ - from bigframes_vendored.ibis.expr.types.joins import Join - - return Join(left.op()).asof_join( - right, on, predicates, tolerance=tolerance, lname=lname, rname=rname - ) - - def cross_join( - left: Table, - right: Table, - *rest: Table, - lname: str = "", - rname: str = "{name}_right", - ) -> Table: - """Compute the cross join of a sequence of tables. - - Parameters - ---------- - left - Left table - right - Right table - rest - Additional tables to cross join - lname - A format string to use to rename overlapping columns in the left - table (e.g. ``"left_{name}"``). - rname - A format string to use to rename overlapping columns in the right - table (e.g. ``"right_{name}"``). - - Returns - ------- - Table - Cross join of `left`, `right` and `rest` - - Examples - -------- - >>> import ibis - >>> import ibis.selectors as s - >>> from ibis import _ - >>> ibis.options.interactive = True - >>> t = ibis.examples.penguins.fetch() - >>> t.count() - ┌───────────────┐ - │ np.int64(344) │ - └───────────────┘ - >>> agg = t.drop("year").agg(s.across(s.numeric(), _.mean())) - >>> expr = t.cross_join(agg) - >>> expr - ┏━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━┳━━━┓ - ┃ species ┃ island ┃ bill_length_mm ┃ bill_depth_mm ┃ flipper_length_mm ┃ … ┃ - ┡━━━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━╇━━━┩ - │ string │ string │ float64 │ float64 │ int64 │ … │ - ├─────────┼───────────┼────────────────┼───────────────┼───────────────────┼───┤ - │ Adelie │ Torgersen │ 39.1 │ 18.7 │ 181 │ … │ - │ Adelie │ Torgersen │ 39.5 │ 17.4 │ 186 │ … │ - │ Adelie │ Torgersen │ 40.3 │ 18.0 │ 195 │ … │ - │ Adelie │ Torgersen │ NULL │ NULL │ NULL │ … │ - │ Adelie │ Torgersen │ 36.7 │ 19.3 │ 193 │ … │ - │ Adelie │ Torgersen │ 39.3 │ 20.6 │ 190 │ … │ - │ Adelie │ Torgersen │ 38.9 │ 17.8 │ 181 │ … │ - │ Adelie │ Torgersen │ 39.2 │ 19.6 │ 195 │ … │ - │ Adelie │ Torgersen │ 34.1 │ 18.1 │ 193 │ … │ - │ Adelie │ Torgersen │ 42.0 │ 20.2 │ 190 │ … │ - │ … │ … │ … │ … │ … │ … │ - └─────────┴───────────┴────────────────┴───────────────┴───────────────────┴───┘ - >>> expr.columns - ['species', - 'island', - 'bill_length_mm', - 'bill_depth_mm', - 'flipper_length_mm', - 'body_mass_g', - 'sex', - 'year', - 'bill_length_mm_right', - 'bill_depth_mm_right', - 'flipper_length_mm_right', - 'body_mass_g_right'] - >>> expr.count() - ┌───────────────┐ - │ np.int64(344) │ - └───────────────┘ - """ - from bigframes_vendored.ibis.expr.types.joins import Join - - return Join(left.op()).cross_join(right, *rest, lname=lname, rname=rname) - - inner_join = _regular_join_method("inner_join", "inner") - left_join = _regular_join_method("left_join", "left") - outer_join = _regular_join_method("outer_join", "outer") - right_join = _regular_join_method("right_join", "right") - semi_join = _regular_join_method("semi_join", "semi") - anti_join = _regular_join_method("anti_join", "anti") - any_inner_join = _regular_join_method("any_inner_join", "any_inner") - any_left_join = _regular_join_method("any_left_join", "any_left") - - def alias(self, alias: str) -> ir.Table: - """Create a table expression with a specific name `alias`. - - This method is useful for exposing an ibis expression to the underlying - backend for use in the - [`Table.sql`](#ibis.expr.types.relations.Table.sql) method. - - ::: {.callout-note} - ## `.alias` will create a temporary view - - `.alias` creates a temporary view in the database. - - This side effect will be removed in a future version of ibis and **is - not part of the public API**. - ::: - - Parameters - ---------- - alias - Name of the child expression - - Returns - ------- - Table - An table expression - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.examples.penguins.fetch() - >>> expr = t.alias("pingüinos").sql('SELECT * FROM "pingüinos" LIMIT 5') - >>> expr - ┏━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━┳━━━┓ - ┃ species ┃ island ┃ bill_length_mm ┃ bill_depth_mm ┃ flipper_length_mm ┃ … ┃ - ┡━━━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━╇━━━┩ - │ string │ string │ float64 │ float64 │ int64 │ … │ - ├─────────┼───────────┼────────────────┼───────────────┼───────────────────┼───┤ - │ Adelie │ Torgersen │ 39.1 │ 18.7 │ 181 │ … │ - │ Adelie │ Torgersen │ 39.5 │ 17.4 │ 186 │ … │ - │ Adelie │ Torgersen │ 40.3 │ 18.0 │ 195 │ … │ - │ Adelie │ Torgersen │ NULL │ NULL │ NULL │ … │ - │ Adelie │ Torgersen │ 36.7 │ 19.3 │ 193 │ … │ - └─────────┴───────────┴────────────────┴───────────────┴───────────────────┴───┘ - """ - return ops.View(child=self, name=alias).to_expr() - - def sql(self, query: str, dialect: str | None = None) -> ir.Table: - '''Run a SQL query against a table expression. - - Parameters - ---------- - query - Query string - dialect - Optional string indicating the dialect of `query`. Defaults to the - backend's native dialect. - - Returns - ------- - Table - An opaque table expression - - Examples - -------- - >>> import ibis - >>> from ibis import _ - >>> ibis.options.interactive = True - >>> t = ibis.examples.penguins.fetch(table_name="penguins") - >>> expr = t.sql( - ... """ - ... SELECT island, mean(bill_length_mm) AS avg_bill_length - ... FROM penguins - ... GROUP BY 1 - ... ORDER BY 2 DESC - ... """ - ... ) - >>> expr - ┏━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┓ - ┃ island ┃ avg_bill_length ┃ - ┡━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━┩ - │ string │ float64 │ - ├───────────┼─────────────────┤ - │ Biscoe │ 45.257485 │ - │ Dream │ 44.167742 │ - │ Torgersen │ 38.950980 │ - └───────────┴─────────────────┘ - - Mix and match ibis expressions with SQL queries - - >>> t = ibis.examples.penguins.fetch(table_name="penguins") - >>> expr = t.sql( - ... """ - ... SELECT island, mean(bill_length_mm) AS avg_bill_length - ... FROM penguins - ... GROUP BY 1 - ... ORDER BY 2 DESC - ... """ - ... ) - >>> expr = expr.mutate( - ... island=_.island.lower(), - ... avg_bill_length=_.avg_bill_length.round(1), - ... ) - >>> expr - ┏━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┓ - ┃ island ┃ avg_bill_length ┃ - ┡━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━┩ - │ string │ float64 │ - ├───────────┼─────────────────┤ - │ biscoe │ 45.3 │ - │ dream │ 44.2 │ - │ torgersen │ 39.0 │ - └───────────┴─────────────────┘ - - Because ibis expressions aren't named, they aren't visible to - subsequent `.sql` calls. Use the [`alias`](#ibis.expr.types.relations.Table.alias) method - to assign a name to an expression. - - >>> expr.alias("b").sql("SELECT * FROM b WHERE avg_bill_length > 40") - ┏━━━━━━━━┳━━━━━━━━━━━━━━━━━┓ - ┃ island ┃ avg_bill_length ┃ - ┡━━━━━━━━╇━━━━━━━━━━━━━━━━━┩ - │ string │ float64 │ - ├────────┼─────────────────┤ - │ biscoe │ 45.3 │ - │ dream │ 44.2 │ - └────────┴─────────────────┘ - - See Also - -------- - [`Table.alias`](#ibis.expr.types.relations.Table.alias) - ''' - op = self.op() - backend = self._find_backend() - - if dialect is not None: - # only transpile if dialect was passed - query = backend._transpile_sql(query, dialect=dialect) - - if isinstance(op, ops.View): - name = op.name - expr = op.child.to_expr() - else: - name = util.gen_name("sql_query") - expr = self - - schema = backend._get_sql_string_view_schema(name, expr, query) - node = ops.SQLStringView(child=self.op(), query=query, schema=schema) - return node.to_expr() - - def to_pandas(self, **kwargs) -> pd.DataFrame: - """Convert a table expression to a pandas DataFrame. - - Parameters - ---------- - kwargs - Same as keyword arguments to [`execute`](./expression-generic.qmd#ibis.expr.types.core.Expr.execute) - """ - return self.execute(**kwargs) - - def cache(self) -> Table: - """Cache the provided expression. - - All subsequent operations on the returned expression will be performed - on the cached data. The lifetime of the cached table is tied to its - python references (ie. it is released once the last reference to it is - garbage collected). Alternatively, use the - [`with`](https://docs.python.org/3/reference/compound_stmts.html#with) - statement or call the `.release()` method for more control. - - This method is idempotent: calling it multiple times in succession will - return the same value as the first call. - - ::: {.callout-note} - ## This method eagerly evaluates the expression prior to caching - - Subsequent evaluations will not recompute the expression so method - chaining will not incur the overhead of caching more than once. - ::: - - Returns - ------- - Table - Cached table - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.examples.penguins.fetch() - >>> heavy_computation = ibis.literal("Heavy Computation") - >>> cached_penguins = t.mutate(computation=heavy_computation).cache() - >>> cached_penguins - ┏━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━┳━━━┓ - ┃ species ┃ island ┃ bill_length_mm ┃ bill_depth_mm ┃ flipper_length_mm ┃ … ┃ - ┡━━━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━╇━━━┩ - │ string │ string │ float64 │ float64 │ int64 │ … │ - ├─────────┼───────────┼────────────────┼───────────────┼───────────────────┼───┤ - │ Adelie │ Torgersen │ 39.1 │ 18.7 │ 181 │ … │ - │ Adelie │ Torgersen │ 39.5 │ 17.4 │ 186 │ … │ - │ Adelie │ Torgersen │ 40.3 │ 18.0 │ 195 │ … │ - │ Adelie │ Torgersen │ NULL │ NULL │ NULL │ … │ - │ Adelie │ Torgersen │ 36.7 │ 19.3 │ 193 │ … │ - │ Adelie │ Torgersen │ 39.3 │ 20.6 │ 190 │ … │ - │ Adelie │ Torgersen │ 38.9 │ 17.8 │ 181 │ … │ - │ Adelie │ Torgersen │ 39.2 │ 19.6 │ 195 │ … │ - │ Adelie │ Torgersen │ 34.1 │ 18.1 │ 193 │ … │ - │ Adelie │ Torgersen │ 42.0 │ 20.2 │ 190 │ … │ - │ … │ … │ … │ … │ … │ … │ - └─────────┴───────────┴────────────────┴───────────────┴───────────────────┴───┘ - - Explicit cache cleanup - - >>> with t.mutate(computation=heavy_computation).cache() as cached_penguins: - ... cached_penguins - ┏━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━┳━━━┓ - ┃ species ┃ island ┃ bill_length_mm ┃ bill_depth_mm ┃ flipper_length_mm ┃ … ┃ - ┡━━━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━╇━━━┩ - │ string │ string │ float64 │ float64 │ int64 │ … │ - ├─────────┼───────────┼────────────────┼───────────────┼───────────────────┼───┤ - │ Adelie │ Torgersen │ 39.1 │ 18.7 │ 181 │ … │ - │ Adelie │ Torgersen │ 39.5 │ 17.4 │ 186 │ … │ - │ Adelie │ Torgersen │ 40.3 │ 18.0 │ 195 │ … │ - │ Adelie │ Torgersen │ NULL │ NULL │ NULL │ … │ - │ Adelie │ Torgersen │ 36.7 │ 19.3 │ 193 │ … │ - │ Adelie │ Torgersen │ 39.3 │ 20.6 │ 190 │ … │ - │ Adelie │ Torgersen │ 38.9 │ 17.8 │ 181 │ … │ - │ Adelie │ Torgersen │ 39.2 │ 19.6 │ 195 │ … │ - │ Adelie │ Torgersen │ 34.1 │ 18.1 │ 193 │ … │ - │ Adelie │ Torgersen │ 42.0 │ 20.2 │ 190 │ … │ - │ … │ … │ … │ … │ … │ … │ - └─────────┴───────────┴────────────────┴───────────────┴───────────────────┴───┘ - """ - current_backend = self._find_backend(use_default=True) - return current_backend._cached(self) - - def pivot_longer( - self, - col: str | s.Selector, - *, - names_to: str | Iterable[str] = "name", - names_pattern: str | re.Pattern = r"(.+)", - names_transform: ( - Callable[[str], ir.Value] | Mapping[str, Callable[[str], ir.Value]] | None - ) = None, - values_to: str = "value", - values_transform: Callable[[ir.Value], ir.Value] | Deferred | None = None, - ) -> Table: - r"""Transform a table from wider to longer. - - Parameters - ---------- - col - String column name or selector. - names_to - A string or iterable of strings indicating how to name the new - pivoted columns. - names_pattern - Pattern to use to extract column names from the input. By default - the entire column name is extracted. - names_transform - Function or mapping of a name in `names_to` to a function to - transform a column name to a value. - values_to - Name of the pivoted value column. - values_transform - Apply a function to the value column. This can be a lambda or - deferred expression. - - Returns - ------- - Table - Pivoted table - - Examples - -------- - Basic usage - - >>> import ibis - >>> import ibis.selectors as s - >>> from ibis import _ - >>> ibis.options.interactive = True - >>> relig_income = ibis.examples.relig_income_raw.fetch() - >>> relig_income - ┏━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━┳━━━━━━━━━┳━━━━━━━━━┳━━━━━━━━━┳━━━━━━━━━┳━━━┓ - ┃ religion ┃ <$10k ┃ $10-20k ┃ $20-30k ┃ $30-40k ┃ $40-50k ┃ … ┃ - ┡━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━╇━━━━━━━━━╇━━━━━━━━━╇━━━━━━━━━╇━━━━━━━━━╇━━━┩ - │ string │ int64 │ int64 │ int64 │ int64 │ int64 │ … │ - ├─────────────────────────┼───────┼─────────┼─────────┼─────────┼─────────┼───┤ - │ Agnostic │ 27 │ 34 │ 60 │ 81 │ 76 │ … │ - │ Atheist │ 12 │ 27 │ 37 │ 52 │ 35 │ … │ - │ Buddhist │ 27 │ 21 │ 30 │ 34 │ 33 │ … │ - │ Catholic │ 418 │ 617 │ 732 │ 670 │ 638 │ … │ - │ Don’t know/refused │ 15 │ 14 │ 15 │ 11 │ 10 │ … │ - │ Evangelical Prot │ 575 │ 869 │ 1064 │ 982 │ 881 │ … │ - │ Hindu │ 1 │ 9 │ 7 │ 9 │ 11 │ … │ - │ Historically Black Prot │ 228 │ 244 │ 236 │ 238 │ 197 │ … │ - │ Jehovah's Witness │ 20 │ 27 │ 24 │ 24 │ 21 │ … │ - │ Jewish │ 19 │ 19 │ 25 │ 25 │ 30 │ … │ - │ … │ … │ … │ … │ … │ … │ … │ - └─────────────────────────┴───────┴─────────┴─────────┴─────────┴─────────┴───┘ - - Here we convert column names not matching the selector for the `religion` column - and convert those names into values - - >>> relig_income.pivot_longer(~s.c("religion"), names_to="income", values_to="count") - ┏━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━┳━━━━━━━┓ - ┃ religion ┃ income ┃ count ┃ - ┡━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━╇━━━━━━━┩ - │ string │ string │ int64 │ - ├──────────┼────────────────────┼───────┤ - │ Agnostic │ <$10k │ 27 │ - │ Agnostic │ $10-20k │ 34 │ - │ Agnostic │ $20-30k │ 60 │ - │ Agnostic │ $30-40k │ 81 │ - │ Agnostic │ $40-50k │ 76 │ - │ Agnostic │ $50-75k │ 137 │ - │ Agnostic │ $75-100k │ 122 │ - │ Agnostic │ $100-150k │ 109 │ - │ Agnostic │ >150k │ 84 │ - │ Agnostic │ Don't know/refused │ 96 │ - │ … │ … │ … │ - └──────────┴────────────────────┴───────┘ - - Similarly for a different example dataset, we convert names to values - but using a different selector and the default `values_to` value. - - >>> world_bank_pop = ibis.examples.world_bank_pop_raw.fetch() - >>> world_bank_pop.head() - ┏━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━┓ - ┃ country ┃ indicator ┃ 2000 ┃ 2001 ┃ 2002 ┃ … ┃ - ┡━━━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━┩ - │ string │ string │ float64 │ float64 │ float64 │ … │ - ├─────────┼─────────────┼──────────────┼──────────────┼──────────────┼───┤ - │ ABW │ SP.URB.TOTL │ 4.162500e+04 │ 4.202500e+04 │ 4.219400e+04 │ … │ - │ ABW │ SP.URB.GROW │ 1.664222e+00 │ 9.563731e-01 │ 4.013352e-01 │ … │ - │ ABW │ SP.POP.TOTL │ 8.910100e+04 │ 9.069100e+04 │ 9.178100e+04 │ … │ - │ ABW │ SP.POP.GROW │ 2.539234e+00 │ 1.768757e+00 │ 1.194718e+00 │ … │ - │ AFE │ SP.URB.TOTL │ 1.155517e+08 │ 1.197755e+08 │ 1.242275e+08 │ … │ - └─────────┴─────────────┴──────────────┴──────────────┴──────────────┴───┘ - >>> world_bank_pop.pivot_longer(s.matches(r"\d{4}"), names_to="year").head() - ┏━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━┳━━━━━━━━━┓ - ┃ country ┃ indicator ┃ year ┃ value ┃ - ┡━━━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━╇━━━━━━━━━┩ - │ string │ string │ string │ float64 │ - ├─────────┼─────────────┼────────┼─────────┤ - │ ABW │ SP.URB.TOTL │ 2000 │ 41625.0 │ - │ ABW │ SP.URB.TOTL │ 2001 │ 42025.0 │ - │ ABW │ SP.URB.TOTL │ 2002 │ 42194.0 │ - │ ABW │ SP.URB.TOTL │ 2003 │ 42277.0 │ - │ ABW │ SP.URB.TOTL │ 2004 │ 42317.0 │ - └─────────┴─────────────┴────────┴─────────┘ - - `pivot_longer` has some preprocessing capabiltiies like stripping a prefix and applying - a function to column names - - >>> billboard = ibis.examples.billboard.fetch() - >>> billboard - ┏━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━┳━━━━━━━┳━━━┓ - ┃ artist ┃ track ┃ date_entered ┃ wk1 ┃ wk2 ┃ … ┃ - ┡━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━╇━━━━━━━╇━━━┩ - │ string │ string │ date │ int64 │ int64 │ … │ - ├────────────────┼─────────────────────────┼──────────────┼───────┼───────┼───┤ - │ 2 Pac │ Baby Don't Cry (Keep... │ 2000-02-26 │ 87 │ 82 │ … │ - │ 2Ge+her │ The Hardest Part Of ... │ 2000-09-02 │ 91 │ 87 │ … │ - │ 3 Doors Down │ Kryptonite │ 2000-04-08 │ 81 │ 70 │ … │ - │ 3 Doors Down │ Loser │ 2000-10-21 │ 76 │ 76 │ … │ - │ 504 Boyz │ Wobble Wobble │ 2000-04-15 │ 57 │ 34 │ … │ - │ 98^0 │ Give Me Just One Nig... │ 2000-08-19 │ 51 │ 39 │ … │ - │ A*Teens │ Dancing Queen │ 2000-07-08 │ 97 │ 97 │ … │ - │ Aaliyah │ I Don't Wanna │ 2000-01-29 │ 84 │ 62 │ … │ - │ Aaliyah │ Try Again │ 2000-03-18 │ 59 │ 53 │ … │ - │ Adams, Yolanda │ Open My Heart │ 2000-08-26 │ 76 │ 76 │ … │ - │ … │ … │ … │ … │ … │ … │ - └────────────────┴─────────────────────────┴──────────────┴───────┴───────┴───┘ - >>> billboard.pivot_longer( - ... s.startswith("wk"), - ... names_to="week", - ... names_pattern=r"wk(.+)", - ... names_transform=int, - ... values_to="rank", - ... values_transform=_.cast(int), - ... ).drop_null("rank") - ┏━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━┳━━━━━━━┓ - ┃ artist ┃ track ┃ date_entered ┃ week ┃ rank ┃ - ┡━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━╇━━━━━━━┩ - │ string │ string │ date │ int8 │ int64 │ - ├─────────┼─────────────────────────┼──────────────┼──────┼───────┤ - │ 2 Pac │ Baby Don't Cry (Keep... │ 2000-02-26 │ 1 │ 87 │ - │ 2 Pac │ Baby Don't Cry (Keep... │ 2000-02-26 │ 2 │ 82 │ - │ 2 Pac │ Baby Don't Cry (Keep... │ 2000-02-26 │ 3 │ 72 │ - │ 2 Pac │ Baby Don't Cry (Keep... │ 2000-02-26 │ 4 │ 77 │ - │ 2 Pac │ Baby Don't Cry (Keep... │ 2000-02-26 │ 5 │ 87 │ - │ 2 Pac │ Baby Don't Cry (Keep... │ 2000-02-26 │ 6 │ 94 │ - │ 2 Pac │ Baby Don't Cry (Keep... │ 2000-02-26 │ 7 │ 99 │ - │ 2Ge+her │ The Hardest Part Of ... │ 2000-09-02 │ 1 │ 91 │ - │ 2Ge+her │ The Hardest Part Of ... │ 2000-09-02 │ 2 │ 87 │ - │ 2Ge+her │ The Hardest Part Of ... │ 2000-09-02 │ 3 │ 92 │ - │ … │ … │ … │ … │ … │ - └─────────┴─────────────────────────┴──────────────┴──────┴───────┘ - - You can use regular expression capture groups to extract multiple - variables stored in column names - - >>> who = ibis.examples.who.fetch() - >>> who - ┏━━━━━━━━━━━━━┳━━━━━━━━┳━━━━━━━━┳━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━┓ - ┃ country ┃ iso2 ┃ iso3 ┃ year ┃ new_sp_m014 ┃ new_sp_m1524 ┃ … ┃ - ┡━━━━━━━━━━━━━╇━━━━━━━━╇━━━━━━━━╇━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━┩ - │ string │ string │ string │ int64 │ int64 │ int64 │ … │ - ├─────────────┼────────┼────────┼───────┼─────────────┼──────────────┼───┤ - │ Afghanistan │ AF │ AFG │ 1980 │ NULL │ NULL │ … │ - │ Afghanistan │ AF │ AFG │ 1981 │ NULL │ NULL │ … │ - │ Afghanistan │ AF │ AFG │ 1982 │ NULL │ NULL │ … │ - │ Afghanistan │ AF │ AFG │ 1983 │ NULL │ NULL │ … │ - │ Afghanistan │ AF │ AFG │ 1984 │ NULL │ NULL │ … │ - │ Afghanistan │ AF │ AFG │ 1985 │ NULL │ NULL │ … │ - │ Afghanistan │ AF │ AFG │ 1986 │ NULL │ NULL │ … │ - │ Afghanistan │ AF │ AFG │ 1987 │ NULL │ NULL │ … │ - │ Afghanistan │ AF │ AFG │ 1988 │ NULL │ NULL │ … │ - │ Afghanistan │ AF │ AFG │ 1989 │ NULL │ NULL │ … │ - │ … │ … │ … │ … │ … │ … │ … │ - └─────────────┴────────┴────────┴───────┴─────────────┴──────────────┴───┘ - >>> len(who.columns) - 60 - >>> who.pivot_longer( - ... s.r["new_sp_m014":"newrel_f65"], - ... names_to=["diagnosis", "gender", "age"], - ... names_pattern="new_?(.*)_(.)(.*)", - ... values_to="count", - ... ) - ┏━━━━━━━━━━━━━┳━━━━━━━━┳━━━━━━━━┳━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━┳━━━━━━━━┳━━━━━━━┓ - ┃ country ┃ iso2 ┃ iso3 ┃ year ┃ diagnosis ┃ gender ┃ age ┃ count ┃ - ┡━━━━━━━━━━━━━╇━━━━━━━━╇━━━━━━━━╇━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━╇━━━━━━━━╇━━━━━━━┩ - │ string │ string │ string │ int64 │ string │ string │ string │ int64 │ - ├─────────────┼────────┼────────┼───────┼───────────┼────────┼────────┼───────┤ - │ Afghanistan │ AF │ AFG │ 1980 │ sp │ m │ 014 │ NULL │ - │ Afghanistan │ AF │ AFG │ 1980 │ sp │ m │ 1524 │ NULL │ - │ Afghanistan │ AF │ AFG │ 1980 │ sp │ m │ 2534 │ NULL │ - │ Afghanistan │ AF │ AFG │ 1980 │ sp │ m │ 3544 │ NULL │ - │ Afghanistan │ AF │ AFG │ 1980 │ sp │ m │ 4554 │ NULL │ - │ Afghanistan │ AF │ AFG │ 1980 │ sp │ m │ 5564 │ NULL │ - │ Afghanistan │ AF │ AFG │ 1980 │ sp │ m │ 65 │ NULL │ - │ Afghanistan │ AF │ AFG │ 1980 │ sp │ f │ 014 │ NULL │ - │ Afghanistan │ AF │ AFG │ 1980 │ sp │ f │ 1524 │ NULL │ - │ Afghanistan │ AF │ AFG │ 1980 │ sp │ f │ 2534 │ NULL │ - │ … │ … │ … │ … │ … │ … │ … │ … │ - └─────────────┴────────┴────────┴───────┴───────────┴────────┴────────┴───────┘ - - `names_transform` is flexible, and can be: - - 1. A mapping of one or more names in `names_to` to callable - 2. A callable that will be applied to every name - - Let's recode gender and age to numeric values using a mapping - - >>> who.pivot_longer( - ... s.r["new_sp_m014":"newrel_f65"], - ... names_to=["diagnosis", "gender", "age"], - ... names_pattern="new_?(.*)_(.)(.*)", - ... names_transform=dict( - ... gender={"m": 1, "f": 2}.get, - ... age=dict( - ... zip( - ... ["014", "1524", "2534", "3544", "4554", "5564", "65"], - ... range(7), - ... ) - ... ).get, - ... ), - ... values_to="count", - ... ) - ┏━━━━━━━━━━━━━┳━━━━━━━━┳━━━━━━━━┳━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━┳━━━━━━┳━━━━━━━┓ - ┃ country ┃ iso2 ┃ iso3 ┃ year ┃ diagnosis ┃ gender ┃ age ┃ count ┃ - ┡━━━━━━━━━━━━━╇━━━━━━━━╇━━━━━━━━╇━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━╇━━━━━━╇━━━━━━━┩ - │ string │ string │ string │ int64 │ string │ int8 │ int8 │ int64 │ - ├─────────────┼────────┼────────┼───────┼───────────┼────────┼──────┼───────┤ - │ Afghanistan │ AF │ AFG │ 1980 │ sp │ 1 │ 0 │ NULL │ - │ Afghanistan │ AF │ AFG │ 1980 │ sp │ 1 │ 1 │ NULL │ - │ Afghanistan │ AF │ AFG │ 1980 │ sp │ 1 │ 2 │ NULL │ - │ Afghanistan │ AF │ AFG │ 1980 │ sp │ 1 │ 3 │ NULL │ - │ Afghanistan │ AF │ AFG │ 1980 │ sp │ 1 │ 4 │ NULL │ - │ Afghanistan │ AF │ AFG │ 1980 │ sp │ 1 │ 5 │ NULL │ - │ Afghanistan │ AF │ AFG │ 1980 │ sp │ 1 │ 6 │ NULL │ - │ Afghanistan │ AF │ AFG │ 1980 │ sp │ 2 │ 0 │ NULL │ - │ Afghanistan │ AF │ AFG │ 1980 │ sp │ 2 │ 1 │ NULL │ - │ Afghanistan │ AF │ AFG │ 1980 │ sp │ 2 │ 2 │ NULL │ - │ … │ … │ … │ … │ … │ … │ … │ … │ - └─────────────┴────────┴────────┴───────┴───────────┴────────┴──────┴───────┘ - - The number of match groups in `names_pattern` must match the length of `names_to` - - >>> who.pivot_longer( # quartodoc: +EXPECTED_FAILURE - ... s.r["new_sp_m014":"newrel_f65"], - ... names_to=["diagnosis", "gender", "age"], - ... names_pattern="new_?(.*)_.(.*)", - ... ) - Traceback (most recent call last): - ... - ibis.common.exceptions.IbisInputError: Number of match groups in `names_pattern` ... - - `names_transform` must be a mapping or callable - - >>> who.pivot_longer( - ... s.r["new_sp_m014":"newrel_f65"], names_transform="upper" - ... ) # quartodoc: +EXPECTED_FAILURE - Traceback (most recent call last): - ... - ibis.common.exceptions.IbisTypeError: ... Got - """ # noqa: RUF002 - import bigframes_vendored.ibis.selectors as s - - pivot_sel = s._to_selector(col) - - pivot_cols = pivot_sel.expand(self) - if not pivot_cols: - # TODO: improve the repr of selectors - raise com.IbisInputError("Selector returned no columns to pivot on") - - names_to = util.promote_list(names_to) - - names_pattern = re.compile(names_pattern) - if (ngroups := names_pattern.groups) != (nnames := len(names_to)): - raise com.IbisInputError( - f"Number of match groups in `names_pattern`" - f"{names_pattern.pattern!r} ({ngroups:d} groups) doesn't " - f"match the length of `names_to` {names_to} (length {nnames:d})" - ) - - if names_transform is None: - names_transform = dict.fromkeys(names_to, toolz.identity) - elif not isinstance(names_transform, Mapping): - if callable(names_transform): - names_transform = dict.fromkeys(names_to, names_transform) - else: - raise com.IbisTypeError( - f"`names_transform` must be a mapping or callable. Got {type(names_transform)}" - ) - - for name in names_to: - names_transform.setdefault(name, toolz.identity) - - if values_transform is None: - values_transform = toolz.identity - elif isinstance(values_transform, Deferred): - values_transform = values_transform.resolve - - pieces = [] - - for pivot_col in pivot_cols: - col_name = pivot_col.get_name() - match_result = names_pattern.match(col_name) - row = { - name: names_transform[name](value) - for name, value in zip(names_to, match_result.groups()) - } - row[values_to] = values_transform(pivot_col) - pieces.append(bigframes_vendored.ibis.struct(row)) - - # nest into an array of structs to zip unnests together - pieces = bigframes_vendored.ibis.array(pieces) - - return self.select(~pivot_sel, __pivoted__=pieces.unnest()).unpack( - "__pivoted__" - ) - - @util.experimental - def pivot_wider( - self, - *, - id_cols: s.Selector | None = None, - names_from: str | Iterable[str] | s.Selector = "name", - names_prefix: str = "", - names_sep: str = "_", - names_sort: bool = False, - names: Iterable[str] | None = None, - values_from: str | Iterable[str] | s.Selector = "value", - values_fill: int | float | str | ir.Scalar | None = None, - values_agg: str | Callable[[ir.Value], ir.Scalar] | Deferred = "arbitrary", - ) -> Table: - """Pivot a table to a wider format. - - Parameters - ---------- - id_cols - A set of columns that uniquely identify each observation. - names_from - An argument describing which column or columns to use to get the - name of the output columns. - names_prefix - String added to the start of every column name. - names_sep - If `names_from` or `values_from` contains multiple columns, this - argument will be used to join their values together into a single - string to use as a column name. - names_sort - If [](`True`) columns are sorted. If [](`False`) column names are - ordered by appearance. - names - An explicit sequence of values to look for in columns matching - `names_from`. - - * When this value is `None`, the values will be computed from - `names_from`. - * When this value is not `None`, each element's length must match - the length of `names_from`. - - See examples below for more detail. - values_from - An argument describing which column or columns to get the cell - values from. - values_fill - A scalar value that specifies what each value should be filled with - when missing. - values_agg - A function applied to the value in each cell in the output. - - Returns - ------- - Table - Wider pivoted table - - Examples - -------- - >>> import ibis - >>> import ibis.selectors as s - >>> from ibis import _ - >>> ibis.options.interactive = True - - Basic usage - - >>> fish_encounters = ibis.examples.fish_encounters.fetch() - >>> fish_encounters - ┏━━━━━━━┳━━━━━━━━━┳━━━━━━━┓ - ┃ fish ┃ station ┃ seen ┃ - ┡━━━━━━━╇━━━━━━━━━╇━━━━━━━┩ - │ int64 │ string │ int64 │ - ├───────┼─────────┼───────┤ - │ 4842 │ Release │ 1 │ - │ 4842 │ I80_1 │ 1 │ - │ 4842 │ Lisbon │ 1 │ - │ 4842 │ Rstr │ 1 │ - │ 4842 │ Base_TD │ 1 │ - │ 4842 │ BCE │ 1 │ - │ 4842 │ BCW │ 1 │ - │ 4842 │ BCE2 │ 1 │ - │ 4842 │ BCW2 │ 1 │ - │ 4842 │ MAE │ 1 │ - │ … │ … │ … │ - └───────┴─────────┴───────┘ - >>> fish_encounters.pivot_wider(names_from="station", values_from="seen") # doctest: +SKIP - ┏━━━━━━━┳━━━━━━━━━┳━━━━━━━┳━━━━━━━━┳━━━━━━━┳━━━━━━━━━┳━━━━━━━┳━━━━━━━┳━━━┓ - ┃ fish ┃ Release ┃ I80_1 ┃ Lisbon ┃ Rstr ┃ Base_TD ┃ BCE ┃ BCW ┃ … ┃ - ┡━━━━━━━╇━━━━━━━━━╇━━━━━━━╇━━━━━━━━╇━━━━━━━╇━━━━━━━━━╇━━━━━━━╇━━━━━━━╇━━━┩ - │ int64 │ int64 │ int64 │ int64 │ int64 │ int64 │ int64 │ int64 │ … │ - ├───────┼─────────┼───────┼────────┼───────┼─────────┼───────┼───────┼───┤ - │ 4842 │ 1 │ 1 │ 1 │ 1 │ 1 │ 1 │ 1 │ … │ - │ 4843 │ 1 │ 1 │ 1 │ 1 │ 1 │ 1 │ 1 │ … │ - │ 4844 │ 1 │ 1 │ 1 │ 1 │ 1 │ 1 │ 1 │ … │ - │ 4845 │ 1 │ 1 │ 1 │ 1 │ 1 │ NULL │ NULL │ … │ - │ 4847 │ 1 │ 1 │ 1 │ NULL │ NULL │ NULL │ NULL │ … │ - │ 4848 │ 1 │ 1 │ 1 │ 1 │ NULL │ NULL │ NULL │ … │ - │ 4849 │ 1 │ 1 │ NULL │ NULL │ NULL │ NULL │ NULL │ … │ - │ 4850 │ 1 │ 1 │ NULL │ 1 │ 1 │ 1 │ 1 │ … │ - │ 4851 │ 1 │ 1 │ NULL │ NULL │ NULL │ NULL │ NULL │ … │ - │ 4854 │ 1 │ 1 │ NULL │ NULL │ NULL │ NULL │ NULL │ … │ - │ … │ … │ … │ … │ … │ … │ … │ … │ … │ - └───────┴─────────┴───────┴────────┴───────┴─────────┴───────┴───────┴───┘ - - Fill missing pivoted values using `values_fill` - - >>> fish_encounters.pivot_wider( - ... names_from="station", values_from="seen", values_fill=0 - ... ) # doctest: +SKIP - ┏━━━━━━━┳━━━━━━━━━┳━━━━━━━┳━━━━━━━━┳━━━━━━━┳━━━━━━━━━┳━━━━━━━┳━━━━━━━┳━━━┓ - ┃ fish ┃ Release ┃ I80_1 ┃ Lisbon ┃ Rstr ┃ Base_TD ┃ BCE ┃ BCW ┃ … ┃ - ┡━━━━━━━╇━━━━━━━━━╇━━━━━━━╇━━━━━━━━╇━━━━━━━╇━━━━━━━━━╇━━━━━━━╇━━━━━━━╇━━━┩ - │ int64 │ int64 │ int64 │ int64 │ int64 │ int64 │ int64 │ int64 │ … │ - ├───────┼─────────┼───────┼────────┼───────┼─────────┼───────┼───────┼───┤ - │ 4842 │ 1 │ 1 │ 1 │ 1 │ 1 │ 1 │ 1 │ … │ - │ 4843 │ 1 │ 1 │ 1 │ 1 │ 1 │ 1 │ 1 │ … │ - │ 4844 │ 1 │ 1 │ 1 │ 1 │ 1 │ 1 │ 1 │ … │ - │ 4845 │ 1 │ 1 │ 1 │ 1 │ 1 │ 0 │ 0 │ … │ - │ 4847 │ 1 │ 1 │ 1 │ 0 │ 0 │ 0 │ 0 │ … │ - │ 4848 │ 1 │ 1 │ 1 │ 1 │ 0 │ 0 │ 0 │ … │ - │ 4849 │ 1 │ 1 │ 0 │ 0 │ 0 │ 0 │ 0 │ … │ - │ 4850 │ 1 │ 1 │ 0 │ 1 │ 1 │ 1 │ 1 │ … │ - │ 4851 │ 1 │ 1 │ 0 │ 0 │ 0 │ 0 │ 0 │ … │ - │ 4854 │ 1 │ 1 │ 0 │ 0 │ 0 │ 0 │ 0 │ … │ - │ … │ … │ … │ … │ … │ … │ … │ … │ … │ - └───────┴─────────┴───────┴────────┴───────┴─────────┴───────┴───────┴───┘ - - Compute multiple values columns - - >>> us_rent_income = ibis.examples.us_rent_income.fetch() - >>> us_rent_income - ┏━━━━━━━━┳━━━━━━━━━━━━┳━━━━━━━━━━┳━━━━━━━━━━┳━━━━━━━┓ - ┃ geoid ┃ name ┃ variable ┃ estimate ┃ moe ┃ - ┡━━━━━━━━╇━━━━━━━━━━━━╇━━━━━━━━━━╇━━━━━━━━━━╇━━━━━━━┩ - │ string │ string │ string │ int64 │ int64 │ - ├────────┼────────────┼──────────┼──────────┼───────┤ - │ 01 │ Alabama │ income │ 24476 │ 136 │ - │ 01 │ Alabama │ rent │ 747 │ 3 │ - │ 02 │ Alaska │ income │ 32940 │ 508 │ - │ 02 │ Alaska │ rent │ 1200 │ 13 │ - │ 04 │ Arizona │ income │ 27517 │ 148 │ - │ 04 │ Arizona │ rent │ 972 │ 4 │ - │ 05 │ Arkansas │ income │ 23789 │ 165 │ - │ 05 │ Arkansas │ rent │ 709 │ 5 │ - │ 06 │ California │ income │ 29454 │ 109 │ - │ 06 │ California │ rent │ 1358 │ 3 │ - │ … │ … │ … │ … │ … │ - └────────┴────────────┴──────────┴──────────┴───────┘ - >>> us_rent_income.pivot_wider( - ... names_from="variable", values_from=["estimate", "moe"] - ... ) # doctest: +SKIP - ┏━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━┳━━━┓ - ┃ geoid ┃ name ┃ estimate_income ┃ moe_income ┃ … ┃ - ┡━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━╇━━━┩ - │ string │ string │ int64 │ int64 │ … │ - ├────────┼──────────────────────┼─────────────────┼────────────┼───┤ - │ 01 │ Alabama │ 24476 │ 136 │ … │ - │ 02 │ Alaska │ 32940 │ 508 │ … │ - │ 04 │ Arizona │ 27517 │ 148 │ … │ - │ 05 │ Arkansas │ 23789 │ 165 │ … │ - │ 06 │ California │ 29454 │ 109 │ … │ - │ 08 │ Colorado │ 32401 │ 109 │ … │ - │ 09 │ Connecticut │ 35326 │ 195 │ … │ - │ 10 │ Delaware │ 31560 │ 247 │ … │ - │ 11 │ District of Columbia │ 43198 │ 681 │ … │ - │ 12 │ Florida │ 25952 │ 70 │ … │ - │ … │ … │ … │ … │ … │ - └────────┴──────────────────────┴─────────────────┴────────────┴───┘ - - The column name separator can be changed using the `names_sep` parameter - - >>> us_rent_income.pivot_wider( - ... names_from="variable", - ... names_sep=".", - ... values_from=("estimate", "moe"), - ... ) # doctest: +SKIP - ┏━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━┳━━━┓ - ┃ geoid ┃ name ┃ estimate.income ┃ moe.income ┃ … ┃ - ┡━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━╇━━━┩ - │ string │ string │ int64 │ int64 │ … │ - ├────────┼──────────────────────┼─────────────────┼────────────┼───┤ - │ 01 │ Alabama │ 24476 │ 136 │ … │ - │ 02 │ Alaska │ 32940 │ 508 │ … │ - │ 04 │ Arizona │ 27517 │ 148 │ … │ - │ 05 │ Arkansas │ 23789 │ 165 │ … │ - │ 06 │ California │ 29454 │ 109 │ … │ - │ 08 │ Colorado │ 32401 │ 109 │ … │ - │ 09 │ Connecticut │ 35326 │ 195 │ … │ - │ 10 │ Delaware │ 31560 │ 247 │ … │ - │ 11 │ District of Columbia │ 43198 │ 681 │ … │ - │ 12 │ Florida │ 25952 │ 70 │ … │ - │ … │ … │ … │ … │ … │ - └────────┴──────────────────────┴─────────────────┴────────────┴───┘ - - Supply an alternative function to summarize values - - >>> warpbreaks = ibis.examples.warpbreaks.fetch().select("wool", "tension", "breaks") - >>> warpbreaks - ┏━━━━━━━━┳━━━━━━━━━┳━━━━━━━━┓ - ┃ wool ┃ tension ┃ breaks ┃ - ┡━━━━━━━━╇━━━━━━━━━╇━━━━━━━━┩ - │ string │ string │ int64 │ - ├────────┼─────────┼────────┤ - │ A │ L │ 26 │ - │ A │ L │ 30 │ - │ A │ L │ 54 │ - │ A │ L │ 25 │ - │ A │ L │ 70 │ - │ A │ L │ 52 │ - │ A │ L │ 51 │ - │ A │ L │ 26 │ - │ A │ L │ 67 │ - │ A │ M │ 18 │ - │ … │ … │ … │ - └────────┴─────────┴────────┘ - >>> warpbreaks.pivot_wider( - ... names_from="wool", values_from="breaks", values_agg="mean" - ... ).select("tension", "A", "B").order_by("tension") - ┏━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━┓ - ┃ tension ┃ A ┃ B ┃ - ┡━━━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━━━━┩ - │ string │ float64 │ float64 │ - ├─────────┼───────────┼───────────┤ - │ H │ 24.555556 │ 18.777778 │ - │ L │ 44.555556 │ 28.222222 │ - │ M │ 24.000000 │ 28.777778 │ - └─────────┴───────────┴───────────┘ - - Passing `Deferred` objects to `values_agg` is supported - - >>> warpbreaks.pivot_wider( - ... names_from="tension", - ... values_from="breaks", - ... values_agg=_.sum(), - ... ).select("wool", "H", "L", "M").order_by(s.all()) - ┏━━━━━━━━┳━━━━━━━┳━━━━━━━┳━━━━━━━┓ - ┃ wool ┃ H ┃ L ┃ M ┃ - ┡━━━━━━━━╇━━━━━━━╇━━━━━━━╇━━━━━━━┩ - │ string │ int64 │ int64 │ int64 │ - ├────────┼───────┼───────┼───────┤ - │ A │ 221 │ 401 │ 216 │ - │ B │ 169 │ 254 │ 259 │ - └────────┴───────┴───────┴───────┘ - - Use a custom aggregate function - - >>> warpbreaks.pivot_wider( - ... names_from="wool", - ... values_from="breaks", - ... values_agg=lambda col: col.std() / col.mean(), - ... ).select("tension", "A", "B").order_by("tension") - ┏━━━━━━━━━┳━━━━━━━━━━┳━━━━━━━━━━┓ - ┃ tension ┃ A ┃ B ┃ - ┡━━━━━━━━━╇━━━━━━━━━━╇━━━━━━━━━━┩ - │ string │ float64 │ float64 │ - ├─────────┼──────────┼──────────┤ - │ H │ 0.418344 │ 0.260590 │ - │ L │ 0.406183 │ 0.349325 │ - │ M │ 0.360844 │ 0.327719 │ - └─────────┴──────────┴──────────┘ - - Generate some random data, setting the random seed for reproducibility - - >>> import random - >>> random.seed(0) - >>> raw = ibis.memtable( - ... [ - ... dict( - ... product=product, - ... country=country, - ... year=year, - ... production=random.random(), - ... ) - ... for product in "AB" - ... for country in ["AI", "EI"] - ... for year in range(2000, 2015) - ... ] - ... ) - >>> production = raw.filter(((_.product == "A") & (_.country == "AI")) | (_.product == "B")) - >>> production.order_by(s.all()) - ┏━━━━━━━━━┳━━━━━━━━━┳━━━━━━━┳━━━━━━━━━━━━┓ - ┃ product ┃ country ┃ year ┃ production ┃ - ┡━━━━━━━━━╇━━━━━━━━━╇━━━━━━━╇━━━━━━━━━━━━┩ - │ string │ string │ int64 │ float64 │ - ├─────────┼─────────┼───────┼────────────┤ - │ A │ AI │ 2000 │ 0.844422 │ - │ A │ AI │ 2001 │ 0.757954 │ - │ A │ AI │ 2002 │ 0.420572 │ - │ A │ AI │ 2003 │ 0.258917 │ - │ A │ AI │ 2004 │ 0.511275 │ - │ A │ AI │ 2005 │ 0.404934 │ - │ A │ AI │ 2006 │ 0.783799 │ - │ A │ AI │ 2007 │ 0.303313 │ - │ A │ AI │ 2008 │ 0.476597 │ - │ A │ AI │ 2009 │ 0.583382 │ - │ … │ … │ … │ … │ - └─────────┴─────────┴───────┴────────────┘ - - Pivoting with multiple name columns - - >>> production.pivot_wider( - ... names_from=["product", "country"], - ... values_from="production", - ... ) # doctest: +SKIP - ┏━━━━━━━┳━━━━━━━━━━┳━━━━━━━━━━┳━━━━━━━━━━┓ - ┃ year ┃ B_AI ┃ B_EI ┃ A_AI ┃ - ┡━━━━━━━╇━━━━━━━━━━╇━━━━━━━━━━╇━━━━━━━━━━┩ - │ int64 │ float64 │ float64 │ float64 │ - ├───────┼──────────┼──────────┼──────────┤ - │ 2000 │ 0.477010 │ 0.870471 │ 0.844422 │ - │ 2001 │ 0.865310 │ 0.191067 │ 0.757954 │ - │ 2002 │ 0.260492 │ 0.567511 │ 0.420572 │ - │ 2003 │ 0.805028 │ 0.238616 │ 0.258917 │ - │ 2004 │ 0.548699 │ 0.967540 │ 0.511275 │ - │ 2005 │ 0.014042 │ 0.803179 │ 0.404934 │ - │ 2006 │ 0.719705 │ 0.447970 │ 0.783799 │ - │ 2007 │ 0.398824 │ 0.080446 │ 0.303313 │ - │ 2008 │ 0.824845 │ 0.320055 │ 0.476597 │ - │ 2009 │ 0.668153 │ 0.507941 │ 0.583382 │ - │ … │ … │ … │ … │ - └───────┴──────────┴──────────┴──────────┘ - - Select a subset of names. This call incurs no computation when - constructing the expression. - - >>> production.pivot_wider( - ... names_from=["product", "country"], - ... names=[("A", "AI"), ("B", "AI")], - ... values_from="production", - ... ) # doctest: +SKIP - ┏━━━━━━━┳━━━━━━━━━━┳━━━━━━━━━━┓ - ┃ year ┃ A_AI ┃ B_AI ┃ - ┡━━━━━━━╇━━━━━━━━━━╇━━━━━━━━━━┩ - │ int64 │ float64 │ float64 │ - ├───────┼──────────┼──────────┤ - │ 2000 │ 0.844422 │ 0.477010 │ - │ 2001 │ 0.757954 │ 0.865310 │ - │ 2002 │ 0.420572 │ 0.260492 │ - │ 2003 │ 0.258917 │ 0.805028 │ - │ 2004 │ 0.511275 │ 0.548699 │ - │ 2005 │ 0.404934 │ 0.014042 │ - │ 2006 │ 0.783799 │ 0.719705 │ - │ 2007 │ 0.303313 │ 0.398824 │ - │ 2008 │ 0.476597 │ 0.824845 │ - │ 2009 │ 0.583382 │ 0.668153 │ - │ … │ … │ … │ - └───────┴──────────┴──────────┘ - - Sort the new columns' names - - >>> production.pivot_wider( - ... names_from=["product", "country"], - ... values_from="production", - ... names_sort=True, - ... ) # doctest: +SKIP - ┏━━━━━━━┳━━━━━━━━━━┳━━━━━━━━━━┳━━━━━━━━━━┓ - ┃ year ┃ A_AI ┃ B_AI ┃ B_EI ┃ - ┡━━━━━━━╇━━━━━━━━━━╇━━━━━━━━━━╇━━━━━━━━━━┩ - │ int64 │ float64 │ float64 │ float64 │ - ├───────┼──────────┼──────────┼──────────┤ - │ 2000 │ 0.844422 │ 0.477010 │ 0.870471 │ - │ 2001 │ 0.757954 │ 0.865310 │ 0.191067 │ - │ 2002 │ 0.420572 │ 0.260492 │ 0.567511 │ - │ 2003 │ 0.258917 │ 0.805028 │ 0.238616 │ - │ 2004 │ 0.511275 │ 0.548699 │ 0.967540 │ - │ 2005 │ 0.404934 │ 0.014042 │ 0.803179 │ - │ 2006 │ 0.783799 │ 0.719705 │ 0.447970 │ - │ 2007 │ 0.303313 │ 0.398824 │ 0.080446 │ - │ 2008 │ 0.476597 │ 0.824845 │ 0.320055 │ - │ 2009 │ 0.583382 │ 0.668153 │ 0.507941 │ - │ … │ … │ … │ … │ - └───────┴──────────┴──────────┴──────────┘ - """ - import bigframes_vendored.ibis.selectors as s - import pandas as pd - from bigframes_vendored.ibis.expr.rewrites import _, p, x - - orig_names_from = util.promote_list(names_from) - - names_from = s._to_selector(orig_names_from) - values_from = s._to_selector(values_from) - - if id_cols is None: - id_cols = ~(names_from | values_from) - else: - id_cols = s._to_selector(id_cols) - - if isinstance(values_agg, str): - values_agg = operator.methodcaller(values_agg) - elif isinstance(values_agg, Deferred): - values_agg = values_agg.resolve - - if names is None: - # no names provided, compute them from the data - names = self.select(names_from).distinct().execute() - else: - if not (columns := [col.get_name() for col in names_from.expand(self)]): - raise com.IbisInputError( - f"No matching names columns in `names_from`: {orig_names_from}" - ) - names = pd.DataFrame(list(map(util.promote_list, names)), columns=columns) - - if names_sort: - names = names.sort_values(by=names.columns.tolist()) - - values_cols = values_from.expand(self) - more_than_one_value = len(values_cols) > 1 - aggs = {} - - names_cols_exprs = [self[col] for col in names.columns] - - for keys in names.itertuples(index=False): - where = bigframes_vendored.ibis.and_( - *map(operator.eq, names_cols_exprs, keys) - ) - - for values_col in values_cols: - arg = values_agg(values_col) - - # this allows users to write the aggregate without having to deal with - # the filter themselves - rules = ( - # add in the where clause to filter the appropriate values - p.Reduction(where=None) >> _.copy(where=where) - | p.Reduction(where=x) >> _.copy(where=where & x) - ) - arg = arg.op().replace(rules, filter=p.Value).to_expr() - - # build the components of the group by key - key_components = ( - # user provided prefix - names_prefix, - # include the `values` column name if there's more than one - # `values` column - values_col.get_name() * more_than_one_value, - # values computed from `names`/`names_from` - *keys, - ) - key = names_sep.join(filter(None, key_components)) - aggs[key] = arg if values_fill is None else arg.coalesce(values_fill) - - return self.group_by(id_cols).aggregate(**aggs) - - def relocate( - self, - *columns: str | s.Selector, - before: str | s.Selector | None = None, - after: str | s.Selector | None = None, - **kwargs: str, - ) -> Table: - """Relocate `columns` before or after other specified columns. - - Parameters - ---------- - columns - Columns to relocate. Selectors are accepted. - before - A column name or selector to insert the new columns before. - after - A column name or selector. Columns in `columns` are relocated after the last - column selected in `after`. - kwargs - Additional column names to relocate, renaming argument values to - keyword argument names. - - Returns - ------- - Table - A table with the columns relocated. - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> import ibis.selectors as s - >>> t = ibis.memtable(dict(a=[1], b=[1], c=[1], d=["a"], e=["a"], f=["a"])) - >>> t - ┏━━━━━━━┳━━━━━━━┳━━━━━━━┳━━━━━━━━┳━━━━━━━━┳━━━━━━━━┓ - ┃ a ┃ b ┃ c ┃ d ┃ e ┃ f ┃ - ┡━━━━━━━╇━━━━━━━╇━━━━━━━╇━━━━━━━━╇━━━━━━━━╇━━━━━━━━┩ - │ int64 │ int64 │ int64 │ string │ string │ string │ - ├───────┼───────┼───────┼────────┼────────┼────────┤ - │ 1 │ 1 │ 1 │ a │ a │ a │ - └───────┴───────┴───────┴────────┴────────┴────────┘ - >>> t.relocate("f") - ┏━━━━━━━━┳━━━━━━━┳━━━━━━━┳━━━━━━━┳━━━━━━━━┳━━━━━━━━┓ - ┃ f ┃ a ┃ b ┃ c ┃ d ┃ e ┃ - ┡━━━━━━━━╇━━━━━━━╇━━━━━━━╇━━━━━━━╇━━━━━━━━╇━━━━━━━━┩ - │ string │ int64 │ int64 │ int64 │ string │ string │ - ├────────┼───────┼───────┼───────┼────────┼────────┤ - │ a │ 1 │ 1 │ 1 │ a │ a │ - └────────┴───────┴───────┴───────┴────────┴────────┘ - >>> t.relocate("a", after="c") - ┏━━━━━━━┳━━━━━━━┳━━━━━━━┳━━━━━━━━┳━━━━━━━━┳━━━━━━━━┓ - ┃ b ┃ c ┃ a ┃ d ┃ e ┃ f ┃ - ┡━━━━━━━╇━━━━━━━╇━━━━━━━╇━━━━━━━━╇━━━━━━━━╇━━━━━━━━┩ - │ int64 │ int64 │ int64 │ string │ string │ string │ - ├───────┼───────┼───────┼────────┼────────┼────────┤ - │ 1 │ 1 │ 1 │ a │ a │ a │ - └───────┴───────┴───────┴────────┴────────┴────────┘ - >>> t.relocate("f", before="b") - ┏━━━━━━━┳━━━━━━━━┳━━━━━━━┳━━━━━━━┳━━━━━━━━┳━━━━━━━━┓ - ┃ a ┃ f ┃ b ┃ c ┃ d ┃ e ┃ - ┡━━━━━━━╇━━━━━━━━╇━━━━━━━╇━━━━━━━╇━━━━━━━━╇━━━━━━━━┩ - │ int64 │ string │ int64 │ int64 │ string │ string │ - ├───────┼────────┼───────┼───────┼────────┼────────┤ - │ 1 │ a │ 1 │ 1 │ a │ a │ - └───────┴────────┴───────┴───────┴────────┴────────┘ - >>> t.relocate("a", after=s.last()) - ┏━━━━━━━┳━━━━━━━┳━━━━━━━━┳━━━━━━━━┳━━━━━━━━┳━━━━━━━┓ - ┃ b ┃ c ┃ d ┃ e ┃ f ┃ a ┃ - ┡━━━━━━━╇━━━━━━━╇━━━━━━━━╇━━━━━━━━╇━━━━━━━━╇━━━━━━━┩ - │ int64 │ int64 │ string │ string │ string │ int64 │ - ├───────┼───────┼────────┼────────┼────────┼───────┤ - │ 1 │ 1 │ a │ a │ a │ 1 │ - └───────┴───────┴────────┴────────┴────────┴───────┘ - - Relocate allows renaming - - >>> t.relocate(ff="f") - ┏━━━━━━━━┳━━━━━━━┳━━━━━━━┳━━━━━━━┳━━━━━━━━┳━━━━━━━━┓ - ┃ ff ┃ a ┃ b ┃ c ┃ d ┃ e ┃ - ┡━━━━━━━━╇━━━━━━━╇━━━━━━━╇━━━━━━━╇━━━━━━━━╇━━━━━━━━┩ - │ string │ int64 │ int64 │ int64 │ string │ string │ - ├────────┼───────┼───────┼───────┼────────┼────────┤ - │ a │ 1 │ 1 │ 1 │ a │ a │ - └────────┴───────┴───────┴───────┴────────┴────────┘ - - You can relocate based on any predicate selector, such as - [`of_type`](./selectors.qmd#ibis.selectors.of_type) - - >>> t.relocate(s.of_type("string")) - ┏━━━━━━━━┳━━━━━━━━┳━━━━━━━━┳━━━━━━━┳━━━━━━━┳━━━━━━━┓ - ┃ d ┃ e ┃ f ┃ a ┃ b ┃ c ┃ - ┡━━━━━━━━╇━━━━━━━━╇━━━━━━━━╇━━━━━━━╇━━━━━━━╇━━━━━━━┩ - │ string │ string │ string │ int64 │ int64 │ int64 │ - ├────────┼────────┼────────┼───────┼───────┼───────┤ - │ a │ a │ a │ 1 │ 1 │ 1 │ - └────────┴────────┴────────┴───────┴───────┴───────┘ - >>> t.relocate(s.numeric(), after=s.last()) - ┏━━━━━━━━┳━━━━━━━━┳━━━━━━━━┳━━━━━━━┳━━━━━━━┳━━━━━━━┓ - ┃ d ┃ e ┃ f ┃ a ┃ b ┃ c ┃ - ┡━━━━━━━━╇━━━━━━━━╇━━━━━━━━╇━━━━━━━╇━━━━━━━╇━━━━━━━┩ - │ string │ string │ string │ int64 │ int64 │ int64 │ - ├────────┼────────┼────────┼───────┼───────┼───────┤ - │ a │ a │ a │ 1 │ 1 │ 1 │ - └────────┴────────┴────────┴───────┴───────┴───────┘ - >>> t.relocate(s.any_of(s.c(*"ae"))) - ┏━━━━━━━┳━━━━━━━━┳━━━━━━━┳━━━━━━━┳━━━━━━━━┳━━━━━━━━┓ - ┃ a ┃ e ┃ b ┃ c ┃ d ┃ f ┃ - ┡━━━━━━━╇━━━━━━━━╇━━━━━━━╇━━━━━━━╇━━━━━━━━╇━━━━━━━━┩ - │ int64 │ string │ int64 │ int64 │ string │ string │ - ├───────┼────────┼───────┼───────┼────────┼────────┤ - │ 1 │ a │ 1 │ 1 │ a │ a │ - └───────┴────────┴───────┴───────┴────────┴────────┘ - - When multiple columns are selected with `before` or `after`, those - selected columns are moved before and after the `selectors` input - - >>> t = ibis.memtable(dict(a=[1], b=["a"], c=[1], d=["a"])) - >>> t.relocate(s.numeric(), after=s.of_type("string")) - ┏━━━━━━━━┳━━━━━━━━┳━━━━━━━┳━━━━━━━┓ - ┃ b ┃ d ┃ a ┃ c ┃ - ┡━━━━━━━━╇━━━━━━━━╇━━━━━━━╇━━━━━━━┩ - │ string │ string │ int64 │ int64 │ - ├────────┼────────┼───────┼───────┤ - │ a │ a │ 1 │ 1 │ - └────────┴────────┴───────┴───────┘ - >>> t.relocate(s.numeric(), before=s.of_type("string")) - ┏━━━━━━━┳━━━━━━━┳━━━━━━━━┳━━━━━━━━┓ - ┃ a ┃ c ┃ b ┃ d ┃ - ┡━━━━━━━╇━━━━━━━╇━━━━━━━━╇━━━━━━━━┩ - │ int64 │ int64 │ string │ string │ - ├───────┼───────┼────────┼────────┤ - │ 1 │ 1 │ a │ a │ - └───────┴───────┴────────┴────────┘ - - When there are duplicate **renames** in a call to relocate, the - last one is preserved - - >>> t.relocate(e="d", f="d") - ┏━━━━━━━━┳━━━━━━━┳━━━━━━━━┳━━━━━━━┓ - ┃ f ┃ a ┃ b ┃ c ┃ - ┡━━━━━━━━╇━━━━━━━╇━━━━━━━━╇━━━━━━━┩ - │ string │ int64 │ string │ int64 │ - ├────────┼───────┼────────┼───────┤ - │ a │ 1 │ a │ 1 │ - └────────┴───────┴────────┴───────┘ - - However, if there are duplicates that are **not** part of a rename, the - order specified in the relocate call is preserved - - >>> t.relocate( - ... "b", - ... s.of_type("string"), # "b" is a string column, so the selector matches - ... ) - ┏━━━━━━━━┳━━━━━━━━┳━━━━━━━┳━━━━━━━┓ - ┃ b ┃ d ┃ a ┃ c ┃ - ┡━━━━━━━━╇━━━━━━━━╇━━━━━━━╇━━━━━━━┩ - │ string │ string │ int64 │ int64 │ - ├────────┼────────┼───────┼───────┤ - │ a │ a │ 1 │ 1 │ - └────────┴────────┴───────┴───────┘ - """ - if not columns and before is None and after is None and not kwargs: - raise com.IbisInputError( - "At least one selector or `before` or `after` must be provided" - ) - - if before is not None and after is not None: - raise com.IbisInputError("Cannot specify both `before` and `after`") - - sels = {} - - schema = self.schema() - positions = schema._name_locs - - for new_name, expr in itertools.zip_longest( - kwargs.keys(), self._fast_bind(*kwargs.values(), *columns) - ): - expr_name = expr.get_name() - pos = positions[expr_name] - renamed = new_name is not None - if renamed and pos in sels: - # **only when renaming**: make sure the last duplicate - # column wins by reinserting the position if it already - # exists - # - # to do that, we first delete the existing one, which causes - # the subsequent insertion to be at the end - del sels[pos] - sels[pos] = new_name if renamed else expr_name - - ncols = len(schema) - - if before is not None: - where = min( - (positions[expr.get_name()] for expr in self._fast_bind(before)), - default=0, - ) - elif after is not None: - where = ( - max( - (positions[expr.get_name()] for expr in self._fast_bind(after)), - default=ncols - 1, - ) - + 1 - ) - else: - assert before is None and after is None - where = 0 - - columns = schema.names - - fields = self.op().fields - - # all columns that should come BEFORE the matched selectors - exprs = { - name: fields[name] - for name in (columns[left] for left in range(where) if left not in sels) - } - - # selected columns - exprs.update((name, fields[columns[i]]) for i, name in sels.items()) - - # all columns that should come AFTER the matched selectors - exprs.update( - (name, fields[name]) - for name in ( - columns[right] for right in range(where, ncols) if right not in sels - ) - ) - - return ops.Project(self, exprs).to_expr() - - def window_by( - self, - time_col: str | ir.Value, - ) -> WindowedTable: - from bigframes_vendored.ibis.expr.types.temporal_windows import WindowedTable - - time_col = next(iter(self.bind(time_col))) - - # validate time_col is a timestamp column - if not isinstance(time_col, TimestampColumn): - raise com.IbisInputError( - f"`time_col` must be a timestamp column, got {time_col.type()}" - ) - - return WindowedTable(self, time_col) - - def value_counts(self) -> ir.Table: - """Compute a frequency table of this table's values. - - Returns - ------- - Table - Frequency table of this table's values. - - Examples - -------- - >>> from ibis import examples - >>> ibis.options.interactive = True - >>> t = examples.penguins.fetch() - >>> t.head() - ┏━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━┳━━━┓ - ┃ species ┃ island ┃ bill_length_mm ┃ bill_depth_mm ┃ flipper_length_mm ┃ … ┃ - ┡━━━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━╇━━━┩ - │ string │ string │ float64 │ float64 │ int64 │ … │ - ├─────────┼───────────┼────────────────┼───────────────┼───────────────────┼───┤ - │ Adelie │ Torgersen │ 39.1 │ 18.7 │ 181 │ … │ - │ Adelie │ Torgersen │ 39.5 │ 17.4 │ 186 │ … │ - │ Adelie │ Torgersen │ 40.3 │ 18.0 │ 195 │ … │ - │ Adelie │ Torgersen │ NULL │ NULL │ NULL │ … │ - │ Adelie │ Torgersen │ 36.7 │ 19.3 │ 193 │ … │ - └─────────┴───────────┴────────────────┴───────────────┴───────────────────┴───┘ - >>> t.year.value_counts().order_by("year") - ┏━━━━━━━┳━━━━━━━━━━━━┓ - ┃ year ┃ year_count ┃ - ┡━━━━━━━╇━━━━━━━━━━━━┩ - │ int64 │ int64 │ - ├───────┼────────────┤ - │ 2007 │ 110 │ - │ 2008 │ 114 │ - │ 2009 │ 120 │ - └───────┴────────────┘ - >>> t[["year", "island"]].value_counts().order_by("year", "island") - ┏━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━┓ - ┃ year ┃ island ┃ year_island_count ┃ - ┡━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━┩ - │ int64 │ string │ int64 │ - ├───────┼───────────┼───────────────────┤ - │ 2007 │ Biscoe │ 44 │ - │ 2007 │ Dream │ 46 │ - │ 2007 │ Torgersen │ 20 │ - │ 2008 │ Biscoe │ 64 │ - │ 2008 │ Dream │ 34 │ - │ 2008 │ Torgersen │ 16 │ - │ 2009 │ Biscoe │ 60 │ - │ 2009 │ Dream │ 44 │ - │ 2009 │ Torgersen │ 16 │ - └───────┴───────────┴───────────────────┘ - """ - columns = self.columns - return self.group_by(columns).agg( - lambda t: t.count().name("_".join(columns) + "_count") - ) - - def unnest( - self, column, offset: str | None = None, keep_empty: bool = False - ) -> Table: - """Unnest an array `column` from a table. - - When unnesting an existing column the newly unnested column replaces - the existing column. - - Parameters - ---------- - column - Array column to unnest. - offset - Name of the resulting index column. - keep_empty - Keep empty array values as `NULL` in the output table, as well as - existing `NULL` values. - - Returns - ------- - Table - Table with the array column `column` unnested. - - See Also - -------- - [`ArrayValue.unnest`](./expression-collections.qmd#ibis.expr.types.arrays.ArrayValue.unnest) - - Examples - -------- - >>> import ibis - >>> from ibis import _ - >>> ibis.options.interactive = True - - Construct a table expression with an array column. - - >>> t = ibis.memtable({"x": [[1, 2], [], None, [3, 4, 5]], "y": [1, 2, 3, 4]}) - >>> t - ┏━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━┓ - ┃ x ┃ y ┃ - ┡━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━┩ - │ array │ int64 │ - ├──────────────────────┼───────┤ - │ [1, 2] │ 1 │ - │ [] │ 2 │ - │ NULL │ 3 │ - │ [3, 4, ... +1] │ 4 │ - └──────────────────────┴───────┘ - - Unnest the array column `x`, replacing the **existing** `x` column. - - >>> t.unnest("x") - ┏━━━━━━━┳━━━━━━━┓ - ┃ x ┃ y ┃ - ┡━━━━━━━╇━━━━━━━┩ - │ int64 │ int64 │ - ├───────┼───────┤ - │ 1 │ 1 │ - │ 2 │ 1 │ - │ 3 │ 4 │ - │ 4 │ 4 │ - │ 5 │ 4 │ - └───────┴───────┘ - - Unnest the array column `x` with an offset. The `offset` parameter is - the name of the resulting index column. - - >>> t.unnest(t.x, offset="idx") - ┏━━━━━━━┳━━━━━━━┳━━━━━━━┓ - ┃ x ┃ y ┃ idx ┃ - ┡━━━━━━━╇━━━━━━━╇━━━━━━━┩ - │ int64 │ int64 │ int64 │ - ├───────┼───────┼───────┤ - │ 1 │ 1 │ 0 │ - │ 2 │ 1 │ 1 │ - │ 3 │ 4 │ 0 │ - │ 4 │ 4 │ 1 │ - │ 5 │ 4 │ 2 │ - └───────┴───────┴───────┘ - - Unnest the array column `x` keep empty array values as `NULL` in the - output table. - - >>> t.unnest(_.x, offset="idx", keep_empty=True) - ┏━━━━━━━┳━━━━━━━┳━━━━━━━┓ - ┃ x ┃ y ┃ idx ┃ - ┡━━━━━━━╇━━━━━━━╇━━━━━━━┩ - │ int64 │ int64 │ int64 │ - ├───────┼───────┼───────┤ - │ 1 │ 1 │ 0 │ - │ 2 │ 1 │ 1 │ - │ 3 │ 4 │ 0 │ - │ 4 │ 4 │ 1 │ - │ 5 │ 4 │ 2 │ - │ NULL │ 2 │ NULL │ - │ NULL │ 3 │ NULL │ - └───────┴───────┴───────┘ - - If you need to preserve the row order of the preserved empty arrays or - null values use - [`row_number`](./expression-tables.qmd#ibis.row_number) to - create an index column before calling `unnest`. - - >>> ( - ... t.mutate(original_row=ibis.row_number()) - ... .unnest("x", offset="idx", keep_empty=True) - ... .relocate("original_row") - ... .order_by("original_row") - ... ) - ┏━━━━━━━━━━━━━━┳━━━━━━━┳━━━━━━━┳━━━━━━━┓ - ┃ original_row ┃ x ┃ y ┃ idx ┃ - ┡━━━━━━━━━━━━━━╇━━━━━━━╇━━━━━━━╇━━━━━━━┩ - │ int64 │ int64 │ int64 │ int64 │ - ├──────────────┼───────┼───────┼───────┤ - │ 0 │ 1 │ 1 │ 0 │ - │ 0 │ 2 │ 1 │ 1 │ - │ 1 │ NULL │ 2 │ NULL │ - │ 2 │ NULL │ 3 │ NULL │ - │ 3 │ 3 │ 4 │ 0 │ - │ 3 │ 4 │ 4 │ 1 │ - │ 3 │ 5 │ 4 │ 2 │ - └──────────────┴───────┴───────┴───────┘ - - You can also unnest more complex expressions, and the resulting column - will be projected as the last expression in the result. - - >>> t.unnest(_.x.map(lambda v: v + 1).name("plus_one")) - ┏━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━┳━━━━━━━━━━┓ - ┃ x ┃ y ┃ plus_one ┃ - ┡━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━╇━━━━━━━━━━┩ - │ array │ int64 │ int64 │ - ├──────────────────────┼───────┼──────────┤ - │ [1, 2] │ 1 │ 2 │ - │ [1, 2] │ 1 │ 3 │ - │ [3, 4, ... +1] │ 4 │ 4 │ - │ [3, 4, ... +1] │ 4 │ 5 │ - │ [3, 4, ... +1] │ 4 │ 6 │ - └──────────────────────┴───────┴──────────┘ - """ - (column,) = self.bind(column) - return ops.TableUnnest( - parent=self, column=column, offset=offset, keep_empty=keep_empty - ).to_expr() - - -@public -class CachedTable(Table): - def __exit__(self, *_): - self.release() - - def __enter__(self): - return self - - def release(self): - """Release the underlying expression from the cache.""" - current_backend = self._find_backend(use_default=True) - return current_backend._release_cached(self) - - -public(Table=Table, CachedTable=CachedTable) diff --git a/third_party/bigframes_vendored/ibis/expr/types/strings.py b/third_party/bigframes_vendored/ibis/expr/types/strings.py deleted file mode 100644 index 29502740082..00000000000 --- a/third_party/bigframes_vendored/ibis/expr/types/strings.py +++ /dev/null @@ -1,1705 +0,0 @@ -# Contains code from https://github.com/ibis-project/ibis/blob/9.2.0/ibis/expr/types/strings.py - -from __future__ import annotations - -import functools -import operator -from typing import TYPE_CHECKING, Any, Literal - -import bigframes_vendored.ibis.expr.operations as ops -from bigframes_vendored.ibis import util -from bigframes_vendored.ibis.expr.types.core import _binop -from bigframes_vendored.ibis.expr.types.generic import Column, Scalar, Value -from public import public - -if TYPE_CHECKING: - from collections.abc import Iterable, Sequence - - import bigframes_vendored.ibis.expr.types as ir - - -@public -class StringValue(Value): - def __getitem__(self, key: slice | int | ir.IntegerScalar) -> StringValue: - """Index or slice a string expression. - - Parameters - ---------- - key - [](`int`), [](`slice`) or integer scalar expression - - Returns - ------- - StringValue - Indexed or sliced string value - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.memtable({"food": ["bread", "cheese", "rice"], "idx": [1, 2, 4]}) - >>> t - ┏━━━━━━━━┳━━━━━━━┓ - ┃ food ┃ idx ┃ - ┡━━━━━━━━╇━━━━━━━┩ - │ string │ int64 │ - ├────────┼───────┤ - │ bread │ 1 │ - │ cheese │ 2 │ - │ rice │ 4 │ - └────────┴───────┘ - >>> t.food[0] - ┏━━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ Substring(food, 0, 1) ┃ - ┡━━━━━━━━━━━━━━━━━━━━━━━┩ - │ string │ - ├───────────────────────┤ - │ b │ - │ c │ - │ r │ - └───────────────────────┘ - >>> t.food[:3] - ┏━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ StringSlice(food, 3) ┃ - ┡━━━━━━━━━━━━━━━━━━━━━━┩ - │ string │ - ├──────────────────────┤ - │ bre │ - │ che │ - │ ric │ - └──────────────────────┘ - >>> t.food[3:5] - ┏━━━━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ StringSlice(food, 3, 5) ┃ - ┡━━━━━━━━━━━━━━━━━━━━━━━━━┩ - │ string │ - ├─────────────────────────┤ - │ ad │ - │ es │ - │ e │ - └─────────────────────────┘ - >>> t.food[7] - ┏━━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ Substring(food, 7, 1) ┃ - ┡━━━━━━━━━━━━━━━━━━━━━━━┩ - │ string │ - ├───────────────────────┤ - │ ~ │ - │ ~ │ - │ ~ │ - └───────────────────────┘ - """ - from bigframes_vendored.ibis.expr import types as ir - - if isinstance(key, slice): - start, stop, step = key.start, key.stop, key.step - - if isinstance(step, ir.Expr) or (step is not None and step != 1): - raise ValueError("Step can only be 1") - if start is None and stop is None: - return self - return ops.StringSlice(self, start, stop).to_expr() - elif isinstance(key, int): - return self.substr(key, 1) - raise NotImplementedError(f"string __getitem__[{key.__class__.__name__}]") - - def length(self) -> ir.IntegerValue: - """Compute the length of a string. - - Returns - ------- - IntegerValue - The length of each string in the expression - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.memtable({"s": ["aaa", "a", "aa"]}) - >>> t.s.length() - ┏━━━━━━━━━━━━━━━━━┓ - ┃ StringLength(s) ┃ - ┡━━━━━━━━━━━━━━━━━┩ - │ int32 │ - ├─────────────────┤ - │ 3 │ - │ 1 │ - │ 2 │ - └─────────────────┘ - """ - return ops.StringLength(self).to_expr() - - def lower(self) -> StringValue: - """Convert string to all lowercase. - - Returns - ------- - StringValue - Lowercase string - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.memtable({"s": ["AAA", "a", "AA"]}) - >>> t - ┏━━━━━━━━┓ - ┃ s ┃ - ┡━━━━━━━━┩ - │ string │ - ├────────┤ - │ AAA │ - │ a │ - │ AA │ - └────────┘ - >>> t.s.lower() - ┏━━━━━━━━━━━━━━┓ - ┃ Lowercase(s) ┃ - ┡━━━━━━━━━━━━━━┩ - │ string │ - ├──────────────┤ - │ aaa │ - │ a │ - │ aa │ - └──────────────┘ - """ - return ops.Lowercase(self).to_expr() - - def upper(self) -> StringValue: - """Convert string to all uppercase. - - Returns - ------- - StringValue - Uppercase string - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.memtable({"s": ["aaa", "A", "aa"]}) - >>> t - ┏━━━━━━━━┓ - ┃ s ┃ - ┡━━━━━━━━┩ - │ string │ - ├────────┤ - │ aaa │ - │ A │ - │ aa │ - └────────┘ - >>> t.s.upper() - ┏━━━━━━━━━━━━━━┓ - ┃ Uppercase(s) ┃ - ┡━━━━━━━━━━━━━━┩ - │ string │ - ├──────────────┤ - │ AAA │ - │ A │ - │ AA │ - └──────────────┘ - """ - return ops.Uppercase(self).to_expr() - - def reverse(self) -> StringValue: - """Reverse the characters of a string. - - Returns - ------- - StringValue - Reversed string - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.memtable({"s": ["abc", "def", "ghi"]}) - >>> t - ┏━━━━━━━━┓ - ┃ s ┃ - ┡━━━━━━━━┩ - │ string │ - ├────────┤ - │ abc │ - │ def │ - │ ghi │ - └────────┘ - >>> t.s.reverse() - ┏━━━━━━━━━━━━┓ - ┃ Reverse(s) ┃ - ┡━━━━━━━━━━━━┩ - │ string │ - ├────────────┤ - │ cba │ - │ fed │ - │ ihg │ - └────────────┘ - """ - return ops.Reverse(self).to_expr() - - def ascii_str(self) -> ir.IntegerValue: - """Return the numeric ASCII code of the first character of a string. - - Returns - ------- - IntegerValue - ASCII code of the first character of the input - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.memtable({"s": ["abc", "def", "ghi"]}) - >>> t.s.ascii_str() - ┏━━━━━━━━━━━━━━━━┓ - ┃ StringAscii(s) ┃ - ┡━━━━━━━━━━━━━━━━┩ - │ int32 │ - ├────────────────┤ - │ 97 │ - │ 100 │ - │ 103 │ - └────────────────┘ - """ - return ops.StringAscii(self).to_expr() - - def strip(self) -> StringValue: - r"""Remove whitespace from left and right sides of a string. - - Returns - ------- - StringValue - Stripped string - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.memtable({"s": ["\ta\t", "\nb\n", "\vc\t"]}) - >>> t - ┏━━━━━━━━┓ - ┃ s ┃ - ┡━━━━━━━━┩ - │ string │ - ├────────┤ - │ \ta\t │ - │ \nb\n │ - │ \vc\t │ - └────────┘ - >>> t.s.strip() - ┏━━━━━━━━━━┓ - ┃ Strip(s) ┃ - ┡━━━━━━━━━━┩ - │ string │ - ├──────────┤ - │ a │ - │ b │ - │ c │ - └──────────┘ - """ - return ops.Strip(self).to_expr() - - def lstrip(self) -> StringValue: - r"""Remove whitespace from the left side of string. - - Returns - ------- - StringValue - Left-stripped string - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.memtable({"s": ["\ta\t", "\nb\n", "\vc\t"]}) - >>> t - ┏━━━━━━━━┓ - ┃ s ┃ - ┡━━━━━━━━┩ - │ string │ - ├────────┤ - │ \ta\t │ - │ \nb\n │ - │ \vc\t │ - └────────┘ - >>> t.s.lstrip() - ┏━━━━━━━━━━━┓ - ┃ LStrip(s) ┃ - ┡━━━━━━━━━━━┩ - │ string │ - ├───────────┤ - │ a\t │ - │ b\n │ - │ c\t │ - └───────────┘ - """ - return ops.LStrip(self).to_expr() - - def rstrip(self) -> StringValue: - r"""Remove whitespace from the right side of string. - - Returns - ------- - StringValue - Right-stripped string - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.memtable({"s": ["\ta\t", "\nb\n", "\vc\t"]}) - >>> t - ┏━━━━━━━━┓ - ┃ s ┃ - ┡━━━━━━━━┩ - │ string │ - ├────────┤ - │ \ta\t │ - │ \nb\n │ - │ \vc\t │ - └────────┘ - >>> t.s.rstrip() - ┏━━━━━━━━━━━┓ - ┃ RStrip(s) ┃ - ┡━━━━━━━━━━━┩ - │ string │ - ├───────────┤ - │ \ta │ - │ \nb │ - │ \vc │ - └───────────┘ - """ - return ops.RStrip(self).to_expr() - - def capitalize(self) -> StringValue: - """Uppercase the first letter, lowercase the rest. - - This API matches the semantics of the Python [](`str.capitalize`) - method. - - Returns - ------- - StringValue - Capitalized string - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.memtable({"s": ["aBC", " abc", "ab cd", None]}) - >>> t.s.capitalize() - ┏━━━━━━━━━━━━━━━┓ - ┃ Capitalize(s) ┃ - ┡━━━━━━━━━━━━━━━┩ - │ string │ - ├───────────────┤ - │ Abc │ - │ abc │ - │ Ab cd │ - │ NULL │ - └───────────────┘ - """ - return ops.Capitalize(self).to_expr() - - @util.deprecated( - instead="use the `capitalize` method", as_of="9.0", removed_in="10.0" - ) - def initcap(self) -> StringValue: - """Deprecated. Use `capitalize` instead.""" - return self.capitalize() - - def __contains__(self, *_: Any) -> bool: - raise TypeError("Use string_expr.contains(arg)") - - def contains(self, substr: str | StringValue) -> ir.BooleanValue: - """Return whether the expression contains `substr`. - - Parameters - ---------- - substr - Substring for which to check - - Returns - ------- - BooleanValue - Boolean indicating the presence of `substr` in the expression - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.memtable({"s": ["bab", "ddd", "eaf"]}) - >>> t.s.contains("a") - ┏━━━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ StringContains(s, 'a') ┃ - ┡━━━━━━━━━━━━━━━━━━━━━━━━┩ - │ boolean │ - ├────────────────────────┤ - │ True │ - │ False │ - │ True │ - └────────────────────────┘ - """ - return ops.StringContains(self, substr).to_expr() - - def hashbytes( - self, - how: Literal["md5", "sha1", "sha256", "sha512"] = "sha256", - ) -> ir.BinaryValue: - """Compute the binary hash value of the input. - - Parameters - ---------- - how - Hash algorithm to use - - Returns - ------- - BinaryValue - Binary expression - """ - return ops.HashBytes(self, how).to_expr() - - def hexdigest( - self, - how: Literal["md5", "sha1", "sha256", "sha512"] = "sha256", - ) -> ir.StringValue: - """Return the hash digest of the input as a hex encoded string. - - Parameters - ---------- - how - Hash algorithm to use - - Returns - ------- - StringValue - Hexadecimal representation of the hash as a string - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.memtable({"species": ["Adelie", "Chinstrap", "Gentoo"]}) - >>> t.species.hexdigest() - ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ HexDigest(species) ┃ - ┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩ - │ string │ - ├──────────────────────────────────────────────────────────────────┤ - │ a4d7d46b27480037bc1e513e0e157cbf258baae6ee69e3110d0f9ff418b57a3c │ - │ cb97d113ca69899ae4f1fb581f4a90d86989db77b4a33873d604b0ee412b4cc9 │ - │ b5e90cdff65949fe6bc226823245f7698110e563a12363fc57b3eed3e4a0a612 │ - └──────────────────────────────────────────────────────────────────┘ - """ - return ops.HexDigest(self, how.lower()).to_expr() - - def substr( - self, - start: int | ir.IntegerValue, - length: int | ir.IntegerValue | None = None, - ) -> StringValue: - """Extract a substring. - - Parameters - ---------- - start - First character to start splitting, indices start at 0 - length - Maximum length of each substring. If not supplied, searches the - entire string - - Returns - ------- - StringValue - Found substring - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.memtable({"s": ["abc", "defg", "hijlk"]}) - >>> t.s.substr(2) - ┏━━━━━━━━━━━━━━━━━┓ - ┃ Substring(s, 2) ┃ - ┡━━━━━━━━━━━━━━━━━┩ - │ string │ - ├─────────────────┤ - │ c │ - │ fg │ - │ jlk │ - └─────────────────┘ - """ - return ops.Substring(self, start, length).to_expr() - - def left(self, nchars: int | ir.IntegerValue) -> StringValue: - """Return the `nchars` left-most characters. - - Parameters - ---------- - nchars - Maximum number of characters to return - - Returns - ------- - StringValue - Characters from the start - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.memtable({"s": ["abc", "defg", "hijlk"]}) - >>> t.s.left(2) - ┏━━━━━━━━━━━━━━━━━━━━┓ - ┃ Substring(s, 0, 2) ┃ - ┡━━━━━━━━━━━━━━━━━━━━┩ - │ string │ - ├────────────────────┤ - │ ab │ - │ de │ - │ hi │ - └────────────────────┘ - """ - return self.substr(0, length=nchars) - - def right(self, nchars: int | ir.IntegerValue) -> StringValue: - """Return up to `nchars` from the end of each string. - - Parameters - ---------- - nchars - Maximum number of characters to return - - Returns - ------- - StringValue - Characters from the end - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.memtable({"s": ["abc", "defg", "hijlk"]}) - >>> t.s.right(2) - ┏━━━━━━━━━━━━━━━━┓ - ┃ StrRight(s, 2) ┃ - ┡━━━━━━━━━━━━━━━━┩ - │ string │ - ├────────────────┤ - │ bc │ - │ fg │ - │ lk │ - └────────────────┘ - """ - return ops.StrRight(self, nchars).to_expr() - - def repeat(self, n: int | ir.IntegerValue) -> StringValue: - """Repeat a string `n` times. - - Parameters - ---------- - n - Number of repetitions - - Returns - ------- - StringValue - Repeated string - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.memtable({"s": ["a", "bb", "c"]}) - >>> t.s.repeat(5) - ┏━━━━━━━━━━━━━━┓ - ┃ Repeat(s, 5) ┃ - ┡━━━━━━━━━━━━━━┩ - │ string │ - ├──────────────┤ - │ aaaaa │ - │ bbbbbbbbbb │ - │ ccccc │ - └──────────────┘ - """ - return ops.Repeat(self, n).to_expr() - - def translate(self, from_str: StringValue, to_str: StringValue) -> StringValue: - """Replace `from_str` characters in `self` characters in `to_str`. - - To avoid unexpected behavior, `from_str` should be shorter than - `to_str`. - - Parameters - ---------- - from_str - Characters in `arg` to replace - to_str - Characters to use for replacement - - Returns - ------- - StringValue - Translated string - - Examples - -------- - >>> import ibis - >>> table = ibis.table(dict(string_col="string")) - >>> result = table.string_col.translate("a", "b") - """ - return ops.Translate(self, from_str, to_str).to_expr() - - def find( - self, - substr: str | StringValue, - start: int | ir.IntegerValue | None = None, - end: int | ir.IntegerValue | None = None, - ) -> ir.IntegerValue: - """Return the position of the first occurrence of substring. - - Parameters - ---------- - substr - Substring to search for - start - Zero based index of where to start the search - end - Zero based index of where to stop the search. Currently not - implemented. - - Returns - ------- - IntegerValue - Position of `substr` in `arg` starting from `start` - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.memtable({"s": ["abc", "bac", "bca"]}) - >>> t.s.find("a") - ┏━━━━━━━━━━━━━━━━━━━━┓ - ┃ StringFind(s, 'a') ┃ - ┡━━━━━━━━━━━━━━━━━━━━┩ - │ int64 │ - ├────────────────────┤ - │ 0 │ - │ 1 │ - │ 2 │ - └────────────────────┘ - >>> t.s.find("z") - ┏━━━━━━━━━━━━━━━━━━━━┓ - ┃ StringFind(s, 'z') ┃ - ┡━━━━━━━━━━━━━━━━━━━━┩ - │ int64 │ - ├────────────────────┤ - │ -1 │ - │ -1 │ - │ -1 │ - └────────────────────┘ - """ - if end is not None: - raise NotImplementedError("`end` parameter is not yet implemented") - return ops.StringFind(self, substr, start, end).to_expr() - - def lpad( - self, - length: int | ir.IntegerValue, - pad: str | StringValue = " ", - ) -> StringValue: - """Pad `arg` by truncating on the right or padding on the left. - - Parameters - ---------- - length - Length of output string - pad - Pad character - - Returns - ------- - StringValue - Left-padded string - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.memtable({"s": ["abc", "def", "ghij"]}) - >>> t.s.lpad(5, "-") - ┏━━━━━━━━━━━━━━━━━┓ - ┃ LPad(s, 5, '-') ┃ - ┡━━━━━━━━━━━━━━━━━┩ - │ string │ - ├─────────────────┤ - │ --abc │ - │ --def │ - │ -ghij │ - └─────────────────┘ - """ - return ops.LPad(self, length, pad).to_expr() - - def rpad( - self, - length: int | ir.IntegerValue, - pad: str | StringValue = " ", - ) -> StringValue: - """Pad `self` by truncating or padding on the right. - - Parameters - ---------- - self - String to pad - length - Length of output string - pad - Pad character - - Returns - ------- - StringValue - Right-padded string - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.memtable({"s": ["abc", "def", "ghij"]}) - >>> t.s.rpad(5, "-") - ┏━━━━━━━━━━━━━━━━━┓ - ┃ RPad(s, 5, '-') ┃ - ┡━━━━━━━━━━━━━━━━━┩ - │ string │ - ├─────────────────┤ - │ abc-- │ - │ def-- │ - │ ghij- │ - └─────────────────┘ - """ - return ops.RPad(self, length, pad).to_expr() - - def find_in_set(self, str_list: Sequence[str]) -> ir.IntegerValue: - """Find the first occurrence of `str_list` within a list of strings. - - No string in `str_list` can have a comma. - - Parameters - ---------- - str_list - Sequence of strings - - Returns - ------- - IntegerValue - Position of `str_list` in `self`. Returns -1 if `self` isn't found - or if `self` contains `','`. - - Examples - -------- - >>> import ibis - >>> table = ibis.table(dict(string_col="string")) - >>> result = table.string_col.find_in_set(["a", "b"]) - """ - return ops.FindInSet(self, str_list).to_expr() - - def join(self, strings: Sequence[str | StringValue] | ir.ArrayValue) -> StringValue: - """Join a list of strings using `self` as the separator. - - Parameters - ---------- - strings - Strings to join with `arg` - - Returns - ------- - StringValue - Joined string - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.memtable({"arr": [["a", "b", "c"], None, [], ["b", None]]}) - >>> t - ┏━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ arr ┃ - ┡━━━━━━━━━━━━━━━━━━━━━━┩ - │ array │ - ├──────────────────────┤ - │ ['a', 'b', ... +1] │ - │ NULL │ - │ [] │ - │ ['b', None] │ - └──────────────────────┘ - >>> ibis.literal("|").join(t.arr) - ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ ArrayStringJoin(arr, '|') ┃ - ┡━━━━━━━━━━━━━━━━━━━━━━━━━━━┩ - │ string │ - ├───────────────────────────┤ - │ a|b|c │ - │ NULL │ - │ NULL │ - │ b │ - └───────────────────────────┘ - - See Also - -------- - [`ArrayValue.join`](./expression-collections.qmd#ibis.expr.types.arrays.ArrayValue.join) - """ - import bigframes_vendored.ibis.expr.types as ir - - if isinstance(strings, ir.ArrayValue): - cls = ops.ArrayStringJoin - else: - cls = ops.StringJoin - return cls(strings, sep=self).to_expr() - - def startswith(self, start: str | StringValue) -> ir.BooleanValue: - """Determine whether `self` starts with `end`. - - Parameters - ---------- - start - prefix to check for - - Returns - ------- - BooleanValue - Boolean indicating whether `self` starts with `start` - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.memtable({"s": ["Ibis project", "GitHub"]}) - >>> t.s.startswith("Ibis") - ┏━━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ StartsWith(s, 'Ibis') ┃ - ┡━━━━━━━━━━━━━━━━━━━━━━━┩ - │ boolean │ - ├───────────────────────┤ - │ True │ - │ False │ - └───────────────────────┘ - """ - return ops.StartsWith(self, start).to_expr() - - def endswith(self, end: str | StringValue) -> ir.BooleanValue: - """Determine if `self` ends with `end`. - - Parameters - ---------- - end - Suffix to check for - - Returns - ------- - BooleanValue - Boolean indicating whether `self` ends with `end` - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.memtable({"s": ["Ibis project", "GitHub"]}) - >>> t.s.endswith("project") - ┏━━━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ EndsWith(s, 'project') ┃ - ┡━━━━━━━━━━━━━━━━━━━━━━━━┩ - │ boolean │ - ├────────────────────────┤ - │ True │ - │ False │ - └────────────────────────┘ - """ - return ops.EndsWith(self, end).to_expr() - - def like( - self, - patterns: str | StringValue | Iterable[str | StringValue], - ) -> ir.BooleanValue: - """Match `patterns` against `self`, case-sensitive. - - This function is modeled after the SQL `LIKE` directive. Use `%` as a - multiple-character wildcard or `_` as a single-character wildcard. - - Use `re_search` or `rlike` for regular expression-based matching. - - Parameters - ---------- - patterns - If `pattern` is a list, then if any pattern matches the input then - the corresponding row in the output is `True`. - - Returns - ------- - BooleanValue - Column indicating matches - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.memtable({"s": ["Ibis project", "GitHub"]}) - >>> t.s.like("%project") - ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ StringSQLLike(s, '%project') ┃ - ┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩ - │ boolean │ - ├──────────────────────────────┤ - │ True │ - │ False │ - └──────────────────────────────┘ - """ - return functools.reduce( - operator.or_, - ( - ops.StringSQLLike(self, pattern).to_expr() - for pattern in util.promote_list(patterns) - ), - ) - - def ilike( - self, - patterns: str | StringValue | Iterable[str | StringValue], - ) -> ir.BooleanValue: - """Match `patterns` against `self`, case-insensitive. - - This function is modeled after SQL's `ILIKE` directive. Use `%` as a - multiple-character wildcard or `_` as a single-character wildcard. - - Use `re_search` or `rlike` for regular expression-based matching. - - Parameters - ---------- - patterns - If `pattern` is a list, then if any pattern matches the input then - the corresponding row in the output is `True`. - - Returns - ------- - BooleanValue - Column indicating matches - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.memtable({"s": ["Ibis project", "GitHub"]}) - >>> t.s.ilike("%PROJect") - ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ StringSQLILike(s, '%PROJect') ┃ - ┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩ - │ boolean │ - ├───────────────────────────────┤ - │ True │ - │ False │ - └───────────────────────────────┘ - """ - return functools.reduce( - operator.or_, - ( - ops.StringSQLILike(self, pattern).to_expr() - for pattern in util.promote_list(patterns) - ), - ) - - @util.backend_sensitive( - why="Different backends support different regular expression syntax." - ) - def re_search(self, pattern: str | StringValue) -> ir.BooleanValue: - """Return whether the values match `pattern`. - - Returns `True` if the regex matches a string and `False` otherwise. - - Parameters - ---------- - pattern - Regular expression use for searching - - Returns - ------- - BooleanValue - Indicator of matches - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.memtable({"s": ["Ibis project", "GitHub"]}) - >>> t.s.re_search(".+Hub") - ┏━━━━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ RegexSearch(s, '.+Hub') ┃ - ┡━━━━━━━━━━━━━━━━━━━━━━━━━┩ - │ boolean │ - ├─────────────────────────┤ - │ False │ - │ True │ - └─────────────────────────┘ - """ - return ops.RegexSearch(self, pattern).to_expr() - - rlike = re_search - - @util.backend_sensitive( - why="Different backends support different regular expression syntax." - ) - def re_extract( - self, - pattern: str | StringValue, - index: int | ir.IntegerValue, - ) -> StringValue: - """Return the specified match at `index` from a regex `pattern`. - - Parameters - ---------- - pattern - Regular expression pattern string - index - The index of the match group to return. - - The behavior of this function follows the behavior of Python's - [`match objects`](https://docs.python.org/3/library/re.html#match-objects): - when `index` is zero and there's a match, return the entire match, - otherwise return the content of the `index`-th match group. - - Returns - ------- - StringValue - Extracted match or whole string if `index` is zero - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.memtable({"s": ["abc", "bac", "bca"]}) - - Extract a specific group - - >>> t.s.re_extract(r"^(a)bc", 1) - ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ RegexExtract(s, '^(a)bc', 1) ┃ - ┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩ - │ string │ - ├──────────────────────────────┤ - │ a │ - │ ~ │ - │ ~ │ - └──────────────────────────────┘ - - Extract the entire match - - >>> t.s.re_extract(r"^(a)bc", 0) - ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ RegexExtract(s, '^(a)bc', 0) ┃ - ┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩ - │ string │ - ├──────────────────────────────┤ - │ abc │ - │ ~ │ - │ ~ │ - └──────────────────────────────┘ - """ - return ops.RegexExtract(self, pattern, index).to_expr() - - @util.backend_sensitive( - why="Different backends support different regular expression syntax." - ) - def re_split(self, pattern: str | StringValue) -> ir.ArrayValue: - r"""Split a string by a regular expression `pattern`. - - Parameters - ---------- - pattern - Regular expression string to split by - - Returns - ------- - ArrayValue - Array of strings from splitting by `pattern` - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.memtable(dict(s=["a.b", "b.....c", "c.........a", "def"])) - >>> t.s - ┏━━━━━━━━━━━━━┓ - ┃ s ┃ - ┡━━━━━━━━━━━━━┩ - │ string │ - ├─────────────┤ - │ a.b │ - │ b.....c │ - │ c.........a │ - │ def │ - └─────────────┘ - >>> t.s.re_split(r"\.+").name("splits") - ┏━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ splits ┃ - ┡━━━━━━━━━━━━━━━━━━━━━━┩ - │ array │ - ├──────────────────────┤ - │ ['a', 'b'] │ - │ ['b', 'c'] │ - │ ['c', 'a'] │ - │ ['def'] │ - └──────────────────────┘ - """ - return ops.RegexSplit(self, pattern).to_expr() - - @util.backend_sensitive( - why="Different backends support different regular expression syntax." - ) - def re_replace( - self, - pattern: str | StringValue, - replacement: str | StringValue, - ) -> StringValue: - r"""Replace all matches found by regex `pattern` with `replacement`. - - Parameters - ---------- - pattern - Regular expression string - replacement - Replacement string or regular expression - - Returns - ------- - StringValue - Modified string - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.memtable({"s": ["abc", "bac", "bca", "this has multi \t whitespace"]}) - >>> s = t.s - - Replace all "a"s that are at the beginning of the string with "b": - - >>> s.re_replace("^a", "b") - ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ RegexReplace(s, '^a', 'b') ┃ - ┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩ - │ string │ - ├───────────────────────────────┤ - │ bbc │ - │ bac │ - │ bca │ - │ this has multi \t whitespace │ - └───────────────────────────────┘ - - Double up any "a"s or "b"s, using capture groups and backreferences: - - >>> s.re_replace("([ab])", r"\0\0") - ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ RegexReplace(s, '()', '\\0\\0') ┃ - ┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩ - │ string │ - ├─────────────────────────────────────┤ - │ aabbc │ - │ bbaac │ - │ bbcaa │ - │ this haas multi \t whitespaace │ - └─────────────────────────────────────┘ - - Normalize all whitespace to a single space: - - >>> s.re_replace(r"\s+", " ") - ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ RegexReplace(s, '\\s+', ' ') ┃ - ┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩ - │ string │ - ├──────────────────────────────┤ - │ abc │ - │ bac │ - │ bca │ - │ this has multi whitespace │ - └──────────────────────────────┘ - """ - return ops.RegexReplace(self, pattern, replacement).to_expr() - - def replace( - self, - pattern: StringValue, - replacement: StringValue, - ) -> StringValue: - """Replace each exact match of `pattern` with `replacement`. - - Parameters - ---------- - pattern - String pattern - replacement - String replacement - - Returns - ------- - StringValue - Replaced string - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.memtable({"s": ["abc", "bac", "bca"]}) - >>> t.s.replace("b", "z") - ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ StringReplace(s, 'b', 'z') ┃ - ┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩ - │ string │ - ├────────────────────────────┤ - │ azc │ - │ zac │ - │ zca │ - └────────────────────────────┘ - """ - return ops.StringReplace(self, pattern, replacement).to_expr() - - def to_timestamp(self, format_str: str) -> ir.TimestampValue: - """Parse a string and return a timestamp. - - Parameters - ---------- - format_str - Format string in `strptime` format - - Returns - ------- - TimestampValue - Parsed timestamp value - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.memtable({"ts": ["20170206"]}) - >>> t.ts.to_timestamp("%Y%m%d") - ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ StringToTimestamp(ts, '%Y%m%d') ┃ - ┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩ - │ timestamp('UTC') │ - ├─────────────────────────────────┤ - │ 2017-02-06 00:00:00+00:00 │ - └─────────────────────────────────┘ - """ - return ops.StringToTimestamp(self, format_str).to_expr() - - def to_date(self, format_str: str) -> ir.DateValue: - """Parse a string and return a date. - - Parameters - ---------- - format_str - Format string in `strptime` format - - Returns - ------- - DateValue - Parsed date value - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.memtable({"ts": ["20170206"]}) - >>> t.ts.to_date("%Y%m%d") - ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ StringToDate(ts, '%Y%m%d') ┃ - ┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩ - │ date │ - ├────────────────────────────┤ - │ 2017-02-06 │ - └────────────────────────────┘ - """ - return ops.StringToDate(self, format_str).to_expr() - - def protocol(self): - """Parse a URL and extract protocol. - - Examples - -------- - >>> import ibis - >>> url = ibis.literal("https://user:pass@example.com:80/docs/books") - >>> result = url.protocol() # https - - Returns - ------- - StringValue - Extracted string value - """ - return ops.ExtractProtocol(self).to_expr() - - def authority(self): - """Parse a URL and extract authority. - - Examples - -------- - >>> import ibis - >>> url = ibis.literal("https://user:pass@example.com:80/docs/books") - >>> result = url.authority() # user:pass@example.com:80 - - Returns - ------- - StringValue - Extracted string value - """ - return ops.ExtractAuthority(self).to_expr() - - def userinfo(self): - """Parse a URL and extract user info. - - Examples - -------- - >>> import ibis - >>> url = ibis.literal("https://user:pass@example.com:80/docs/books") - >>> result = url.userinfo() # user:pass - - Returns - ------- - StringValue - Extracted string value - """ - return ops.ExtractUserInfo(self).to_expr() - - def host(self): - """Parse a URL and extract host. - - Examples - -------- - >>> import ibis - >>> url = ibis.literal("https://user:pass@example.com:80/docs/books") - >>> result = url.host() # example.com - - Returns - ------- - StringValue - Extracted string value - """ - return ops.ExtractHost(self).to_expr() - - def file(self): - """Parse a URL and extract file. - - Examples - -------- - >>> import ibis - >>> url = ibis.literal( - ... "https://example.com:80/docs/books/tutorial/index.html?name=networking" - ... ) - >>> result = url.file() # docs/books/tutorial/index.html?name=networking - - Returns - ------- - StringValue - Extracted string value - """ - return ops.ExtractFile(self).to_expr() - - def path(self): - """Parse a URL and extract path. - - Examples - -------- - >>> import ibis - >>> url = ibis.literal( - ... "https://example.com:80/docs/books/tutorial/index.html?name=networking" - ... ) - >>> result = url.path() # docs/books/tutorial/index.html - - Returns - ------- - StringValue - Extracted string value - """ - return ops.ExtractPath(self).to_expr() - - def query(self, key: str | StringValue | None = None): - """Parse a URL and returns query strring or query string parameter. - - If key is passed, return the value of the query string parameter named. - If key is absent, return the query string. - - Parameters - ---------- - key - Query component to extract - - Examples - -------- - >>> import ibis - >>> url = ibis.literal( - ... "https://example.com:80/docs/books/tutorial/index.html?name=networking" - ... ) - >>> result = url.query() # name=networking - >>> query_name = url.query("name") # networking - - Returns - ------- - StringValue - Extracted string value - """ - return ops.ExtractQuery(self, key).to_expr() - - def fragment(self): - """Parse a URL and extract fragment identifier. - - Examples - -------- - >>> import ibis - >>> url = ibis.literal("https://example.com:80/docs/#DOWNLOADING") - >>> result = url.fragment() # DOWNLOADING - - Returns - ------- - StringValue - Extracted string value - """ - return ops.ExtractFragment(self).to_expr() - - def split(self, delimiter: str | StringValue) -> ir.ArrayValue: - """Split as string on `delimiter`. - - ::: {.callout-note} - ## This API only works on backends with array support. - ::: - - Parameters - ---------- - delimiter - Value to split by - - Returns - ------- - ArrayValue - The string split by `delimiter` - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.memtable({"col": ["a,b,c", "d,e", "f"]}) - >>> t - ┏━━━━━━━━┓ - ┃ col ┃ - ┡━━━━━━━━┩ - │ string │ - ├────────┤ - │ a,b,c │ - │ d,e │ - │ f │ - └────────┘ - >>> t.col.split(",") - ┏━━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ StringSplit(col, ',') ┃ - ┡━━━━━━━━━━━━━━━━━━━━━━━┩ - │ array │ - ├───────────────────────┤ - │ ['a', 'b', ... +1] │ - │ ['d', 'e'] │ - │ ['f'] │ - └───────────────────────┘ - """ - return ops.StringSplit(self, delimiter).to_expr() - - def concat(self, other: str | StringValue, *args: str | StringValue) -> StringValue: - """Concatenate strings. - - NULLs are propagated. This methods is equivalent to using the `+` operator. - - Parameters - ---------- - other - String to concatenate - args - Additional strings to concatenate - - Returns - ------- - StringValue - All strings concatenated - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.memtable({"s": ["abc", None]}) - >>> t.s.concat("xyz", "123") - ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ StringConcat((s, 'xyz', '123')) ┃ - ┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩ - │ string │ - ├─────────────────────────────────┤ - │ abcxyz123 │ - │ NULL │ - └─────────────────────────────────┘ - >>> t.s + "xyz" - ┏━━━━━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ StringConcat((s, 'xyz')) ┃ - ┡━━━━━━━━━━━━━━━━━━━━━━━━━━┩ - │ string │ - ├──────────────────────────┤ - │ abcxyz │ - │ NULL │ - └──────────────────────────┘ - """ - return ops.StringConcat((self, other, *args)).to_expr() - - def __add__(self, other: str | StringValue) -> StringValue: - """Concatenate strings. - - Parameters - ---------- - other - String to concatenate - - Returns - ------- - StringValue - All strings concatenated - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.memtable({"s": ["abc", "bac", "bca"]}) - >>> t - ┏━━━━━━━━┓ - ┃ s ┃ - ┡━━━━━━━━┩ - │ string │ - ├────────┤ - │ abc │ - │ bac │ - │ bca │ - └────────┘ - >>> t.s + "z" - ┏━━━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ StringConcat((s, 'z')) ┃ - ┡━━━━━━━━━━━━━━━━━━━━━━━━┩ - │ string │ - ├────────────────────────┤ - │ abcz │ - │ bacz │ - │ bcaz │ - └────────────────────────┘ - >>> t.s + t.s - ┏━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ StringConcat((s, s)) ┃ - ┡━━━━━━━━━━━━━━━━━━━━━━┩ - │ string │ - ├──────────────────────┤ - │ abcabc │ - │ bacbac │ - │ bcabca │ - └──────────────────────┘ - """ - return self.concat(other) - - def __radd__(self, other: str | StringValue) -> StringValue: - """Concatenate strings. - - Parameters - ---------- - other - String to concatenate - - Returns - ------- - StringValue - All strings concatenated - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.memtable({"s": ["abc", "bac", "bca"]}) - >>> t - ┏━━━━━━━━┓ - ┃ s ┃ - ┡━━━━━━━━┩ - │ string │ - ├────────┤ - │ abc │ - │ bac │ - │ bca │ - └────────┘ - >>> "z" + t.s - ┏━━━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ StringConcat(('z', s)) ┃ - ┡━━━━━━━━━━━━━━━━━━━━━━━━┩ - │ string │ - ├────────────────────────┤ - │ zabc │ - │ zbac │ - │ zbca │ - └────────────────────────┘ - """ - return ops.StringConcat((other, self)).to_expr() - - def convert_base( - self, - from_base: int | ir.IntegerValue, - to_base: int | ir.IntegerValue, - ) -> ir.IntegerValue: - """Convert a string representing an integer from one base to another. - - Parameters - ---------- - from_base - Numeric base of the expression - to_base - New base - - Returns - ------- - IntegerValue - Converted expression - """ - return ops.BaseConvert(self, from_base, to_base).to_expr() - - def __mul__(self, n: int | ir.IntegerValue) -> StringValue: - return _binop(ops.Repeat, self, n) - - __rmul__ = __mul__ - - def levenshtein(self, other: StringValue) -> ir.IntegerValue: - """Return the Levenshtein distance between two strings. - - Parameters - ---------- - other - String to compare to - - Returns - ------- - IntegerValue - The edit distance between the two strings - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> s = ibis.literal("kitten") - >>> s.levenshtein("sitting") - ┌─────────────┐ - │ np.int64(3) │ - └─────────────┘ - """ - return ops.Levenshtein(self, other).to_expr() - - -@public -class StringScalar(Scalar, StringValue): - pass - - -@public -class StringColumn(Column, StringValue): - def __getitem__(self, key: slice | int | ir.IntegerScalar) -> StringColumn: - return StringValue.__getitem__(self, key) diff --git a/third_party/bigframes_vendored/ibis/expr/types/structs.py b/third_party/bigframes_vendored/ibis/expr/types/structs.py deleted file mode 100644 index eb5b5595a23..00000000000 --- a/third_party/bigframes_vendored/ibis/expr/types/structs.py +++ /dev/null @@ -1,403 +0,0 @@ -# Contains code from https://github.com/ibis-project/ibis/blob/9.2.0/ibis/expr/types/structs.py - -from __future__ import annotations - -import collections -from keyword import iskeyword -from typing import TYPE_CHECKING - -import bigframes_vendored.ibis.expr.operations as ops -from bigframes_vendored.ibis.common.deferred import deferrable -from bigframes_vendored.ibis.common.exceptions import IbisError -from bigframes_vendored.ibis.expr.types.generic import Column, Scalar, Value, literal -from public import public - -if TYPE_CHECKING: - from collections.abc import Iterable, Mapping, Sequence - - import bigframes_vendored.ibis.expr.datatypes as dt - import bigframes_vendored.ibis.expr.types as ir - from bigframes_vendored.ibis.expr.types.typing import V - - -@public -@deferrable -def struct( - value: Iterable[tuple[str, V]] | Mapping[str, V], - type: str | dt.DataType | None = None, -) -> StructValue: - """Create a struct expression. - - If any of the inputs are Columns, then the output will be a `StructColumn`. - Otherwise, the output will be a `StructScalar`. - - Parameters - ---------- - value - Either a `{str: Value}` mapping, or an iterable of tuples of the form - `(str, Value)`. - type - An instance of `ibis.expr.datatypes.DataType` or a string indicating - the Ibis type of `value`. This is only used if all of the input values - are Python literals. eg `struct`. - - Returns - ------- - StructValue - An StructScalar or StructColumn expression. - - Examples - -------- - Create a struct scalar literal from a `dict` with the type inferred - - >>> import ibis - >>> ibis.options.interactive = True - >>> ibis.struct(dict(a=1, b="foo")) - ┌──────────────────────┐ - │ {'a': 1, 'b': 'foo'} │ - └──────────────────────┘ - - Specify a type (note the 1 is now a `float`): - - >>> ibis.struct(dict(a=1, b="foo"), type="struct") - ┌────────────────────────┐ - │ {'a': 1.0, 'b': 'foo'} │ - └────────────────────────┘ - - Create a struct column from a column and a scalar literal - - >>> t = ibis.memtable({"a": [1, 2, 3]}) - >>> ibis.struct([("a", t.a), ("b", "foo")]) - ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ StructColumn({'a': a, 'b': 'foo'}) ┃ - ┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩ - │ struct │ - ├────────────────────────────────────┤ - │ {'a': 1, 'b': 'foo'} │ - │ {'a': 2, 'b': 'foo'} │ - │ {'a': 3, 'b': 'foo'} │ - └────────────────────────────────────┘ - """ - import bigframes_vendored.ibis.expr.operations as ops - - fields = dict(value) - if any(isinstance(value, Value) for value in fields.values()): - names = tuple(fields.keys()) - values = tuple(fields.values()) - return ops.StructColumn(names=names, values=values).to_expr() - else: - return literal(collections.OrderedDict(fields), type=type) - - -@public -class StructValue(Value): - """A Struct is a nested type with ordered fields of any type. - - For example, a Struct might have a field `a` of type `int64` and a field `b` - of type `string`. - - Structs can be constructed with [`ibis.struct()`](#ibis.expr.types.struct). - - Examples - -------- - Construct a `Struct` column with fields `a: int64` and `b: string` - - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.memtable({"s": [{"a": 1, "b": "foo"}, {"a": 3, "b": None}, None]}) - >>> t - ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ s ┃ - ┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩ - │ struct │ - ├─────────────────────────────┤ - │ {'a': 1, 'b': 'foo'} │ - │ {'a': 3, 'b': None} │ - │ NULL │ - └─────────────────────────────┘ - - You can use dot notation (`.`) or square-bracket syntax (`[]`) to access - struct column fields - - >>> t.s.a - ┏━━━━━━━┓ - ┃ a ┃ - ┡━━━━━━━┩ - │ int64 │ - ├───────┤ - │ 1 │ - │ 3 │ - │ NULL │ - └───────┘ - >>> t.s["a"] - ┏━━━━━━━┓ - ┃ a ┃ - ┡━━━━━━━┩ - │ int64 │ - ├───────┤ - │ 1 │ - │ 3 │ - │ NULL │ - └───────┘ - """ - - def __dir__(self): - out = set(dir(type(self))) - out.update( - c for c in self.type().names if c.isidentifier() and not iskeyword(c) - ) - return sorted(out) - - def _ipython_key_completions_(self) -> list[str]: - return sorted(self.type().names) - - def __getitem__(self, name: str) -> ir.Value: - """Extract the `name` field from this struct. - - Parameters - ---------- - name - The name of the field to access. - - Returns - ------- - Value - An expression with the type of the field being accessed. - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.memtable({"s": [{"a": 1, "b": "foo"}, {"a": 3, "b": None}, None]}) - >>> t - ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ s ┃ - ┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩ - │ struct │ - ├─────────────────────────────┤ - │ {'a': 1, 'b': 'foo'} │ - │ {'a': 3, 'b': None} │ - │ NULL │ - └─────────────────────────────┘ - >>> t.s["a"] - ┏━━━━━━━┓ - ┃ a ┃ - ┡━━━━━━━┩ - │ int64 │ - ├───────┤ - │ 1 │ - │ 3 │ - │ NULL │ - └───────┘ - >>> t.s["b"] - ┏━━━━━━━━┓ - ┃ b ┃ - ┡━━━━━━━━┩ - │ string │ - ├────────┤ - │ foo │ - │ NULL │ - │ NULL │ - └────────┘ - >>> t.s["foo_bar"] - Traceback (most recent call last): - ... - KeyError: 'foo_bar' - """ - if name not in self.names: - raise KeyError(name) - return ops.StructField(self, name).to_expr() - - def __setstate__(self, instance_dictionary): - self.__dict__ = instance_dictionary - - def __getattr__(self, name: str) -> ir.Value: - """Extract the `name` field from this struct. - - Parameters - ---------- - name - The name of the field to access. - - Returns - ------- - Value - An expression with the type of the field being accessed. - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.memtable({"s": [{"a": 1, "b": "foo"}, {"a": 3, "b": None}, None]}) - >>> t - ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ s ┃ - ┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩ - │ struct │ - ├─────────────────────────────┤ - │ {'a': 1, 'b': 'foo'} │ - │ {'a': 3, 'b': None} │ - │ NULL │ - └─────────────────────────────┘ - >>> t.s.a - ┏━━━━━━━┓ - ┃ a ┃ - ┡━━━━━━━┩ - │ int64 │ - ├───────┤ - │ 1 │ - │ 3 │ - │ NULL │ - └───────┘ - >>> t.s.b - ┏━━━━━━━━┓ - ┃ b ┃ - ┡━━━━━━━━┩ - │ string │ - ├────────┤ - │ foo │ - │ NULL │ - │ NULL │ - └────────┘ - >>> t.s.foo_bar - Traceback (most recent call last): - ... - AttributeError: foo_bar - """ - try: - return self[name] - except KeyError: - raise AttributeError(name) from None - - @property - def names(self) -> Sequence[str]: - """Return the field names of the struct.""" - return self.type().names - - @property - def types(self) -> Sequence[dt.DataType]: - """Return the field types of the struct.""" - return self.type().types - - @property - def fields(self) -> Mapping[str, dt.DataType]: - """Return a mapping from field name to field type of the struct.""" - return self.type().fields - - def lift(self) -> ir.Table: - """Project the fields of `self` into a table. - - This method is useful when analyzing data that has deeply nested - structs or arrays of structs. `lift` can be chained to avoid repeating - column names and table references. - - Returns - ------- - Table - A projection with this struct expression's fields. - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.memtable( - ... { - ... "pos": [ - ... {"lat": 10.1, "lon": 30.3}, - ... {"lat": 10.2, "lon": 30.2}, - ... {"lat": 10.3, "lon": 30.1}, - ... ] - ... } - ... ) - >>> t - ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ pos ┃ - ┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩ - │ struct │ - ├────────────────────────────────────┤ - │ {'lat': 10.1, 'lon': 30.3} │ - │ {'lat': 10.2, 'lon': 30.2} │ - │ {'lat': 10.3, 'lon': 30.1} │ - └────────────────────────────────────┘ - >>> t.pos.lift() - ┏━━━━━━━━━┳━━━━━━━━━┓ - ┃ lat ┃ lon ┃ - ┡━━━━━━━━━╇━━━━━━━━━┩ - │ float64 │ float64 │ - ├─────────┼─────────┤ - │ 10.1 │ 30.3 │ - │ 10.2 │ 30.2 │ - │ 10.3 │ 30.1 │ - └─────────┴─────────┘ - - See Also - -------- - [`Table.unpack`](./expression-tables.qmd#ibis.expr.types.relations.Table.unpack) - """ - try: - (table,) = self.op().relations - except ValueError: - raise IbisError("StructValue must depend on exactly one table") - - return table.to_expr().select([self[name] for name in self.names]) - - def destructure(self) -> list[ir.Value]: - """Destructure a ``StructValue`` into the corresponding struct fields. - - When assigned, a destruct value will be destructured and assigned to - multiple columns. - - Returns - ------- - list[AnyValue] - Value expressions corresponding to the struct fields. - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.memtable({"s": [{"a": 1, "b": "foo"}, {"a": 3, "b": None}, None]}) - >>> t - ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ s ┃ - ┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩ - │ struct │ - ├─────────────────────────────┤ - │ {'a': 1, 'b': 'foo'} │ - │ {'a': 3, 'b': None} │ - │ NULL │ - └─────────────────────────────┘ - >>> a, b = t.s.destructure() - >>> a - ┏━━━━━━━┓ - ┃ a ┃ - ┡━━━━━━━┩ - │ int64 │ - ├───────┤ - │ 1 │ - │ 3 │ - │ NULL │ - └───────┘ - >>> b - ┏━━━━━━━━┓ - ┃ b ┃ - ┡━━━━━━━━┩ - │ string │ - ├────────┤ - │ foo │ - │ NULL │ - │ NULL │ - └────────┘ - """ - return [self[field_name] for field_name in self.type().names] - - -@public -class StructScalar(Scalar, StructValue): - pass - - -@public -class StructColumn(Column, StructValue): - def __getitem__(self, name: str) -> ir.Column: - return StructValue.__getitem__(self, name) diff --git a/third_party/bigframes_vendored/ibis/expr/types/temporal.py b/third_party/bigframes_vendored/ibis/expr/types/temporal.py deleted file mode 100644 index 91e978d5402..00000000000 --- a/third_party/bigframes_vendored/ibis/expr/types/temporal.py +++ /dev/null @@ -1,996 +0,0 @@ -# Contains code from https://github.com/ibis-project/ibis/blob/9.2.0/ibis/expr/types/temporal.py - -from __future__ import annotations - -from typing import TYPE_CHECKING, Any, Literal - -import bigframes_vendored.ibis.expr.datashape as ds -import bigframes_vendored.ibis.expr.datatypes as dt -import bigframes_vendored.ibis.expr.operations as ops -from bigframes_vendored import ibis -from bigframes_vendored.ibis import util -from bigframes_vendored.ibis.common.annotations import annotated -from bigframes_vendored.ibis.common.temporal import IntervalUnit -from bigframes_vendored.ibis.expr.types.core import _binop -from bigframes_vendored.ibis.expr.types.generic import Column, Scalar, Value -from public import public - -if TYPE_CHECKING: - import datetime - - import bigframes_vendored.ibis.expr.types as ir - import pandas as pd - - -class _DateComponentMixin: - """Temporal expressions that have a date component.""" - - def epoch_seconds(self) -> ir.IntegerValue: - """Extract UNIX epoch in seconds.""" - return ops.ExtractEpochSeconds(self).to_expr() - - def year(self) -> ir.IntegerValue: - """Extract the year component.""" - return ops.ExtractYear(self).to_expr() - - def iso_year(self) -> ir.IntegerValue: - """Extract the ISO year component.""" - return ops.ExtractIsoYear(self).to_expr() - - def month(self) -> ir.IntegerValue: - """Extract the month component.""" - return ops.ExtractMonth(self).to_expr() - - def day(self) -> ir.IntegerValue: - """Extract the day component.""" - return ops.ExtractDay(self).to_expr() - - @property - def day_of_week(self) -> DayOfWeek: - """A namespace of methods for extracting day of week information. - - Returns - ------- - DayOfWeek - An namespace expression containing methods to use to extract - information. - """ - return DayOfWeek(self) - - def day_of_year(self) -> ir.IntegerValue: - """Extract the day of the year component.""" - return ops.ExtractDayOfYear(self).to_expr() - - def quarter(self) -> ir.IntegerValue: - """Extract the quarter component.""" - return ops.ExtractQuarter(self).to_expr() - - def week_of_year(self) -> ir.IntegerValue: - """Extract the week of the year component.""" - return ops.ExtractWeekOfYear(self).to_expr() - - -class _TimeComponentMixin: - """Temporal expressions that have a time component.""" - - def time(self) -> TimeValue: - """Return the time component of the expression. - - Returns - ------- - TimeValue - The time component of `self` - """ - return ops.Time(self).to_expr() - - def hour(self) -> ir.IntegerValue: - """Extract the hour component.""" - return ops.ExtractHour(self).to_expr() - - def minute(self) -> ir.IntegerValue: - """Extract the minute component.""" - return ops.ExtractMinute(self).to_expr() - - def second(self) -> ir.IntegerValue: - """Extract the second component.""" - return ops.ExtractSecond(self).to_expr() - - def microsecond(self) -> ir.IntegerValue: - """Extract the microsecond component.""" - return ops.ExtractMicrosecond(self).to_expr() - - def millisecond(self) -> ir.IntegerValue: - """Extract the millisecond component.""" - return ops.ExtractMillisecond(self).to_expr() - - def between( - self, - lower: str | datetime.time | TimeValue, - upper: str | datetime.time | TimeValue, - timezone: str | None = None, - ) -> ir.BooleanValue: - """Check if the expr falls between `lower` and `upper`, inclusive. - - Adjusts according to `timezone` if provided. - - Parameters - ---------- - lower - Lower bound - upper - Upper bound - timezone - Time zone - - Returns - ------- - BooleanValue - Whether `self` is between `lower` and `upper`, adjusting `timezone` - as needed. - """ - op = self.op() - if isinstance(op, ops.Time): - # Here we pull out the first argument to the underlying Time - # operation which is by definition (in _timestamp_value_methods) a - # TimestampValue. We do this so that we can potentially specialize - # the "between time" operation for - # timestamp_value_expr.time().between(). A similar mechanism is - # triggered when creating expressions like - # t.column.distinct().count(), which is turned into - # t.column.nunique(). - arg = op.arg.to_expr() - if timezone is not None: - arg = arg.cast(dt.Timestamp(timezone=timezone)) - op_cls = ops.BetweenTime - else: - arg = self - op_cls = ops.Between - - return op_cls(arg, lower, upper).to_expr() - - -@public -class TimeValue(_TimeComponentMixin, Value): - def strftime(self, format_str: str) -> ir.StringValue: - """Format a time according to `format_str`. - - Format string may depend on the backend, but we try to conform to ANSI - `strftime`. - - Parameters - ---------- - format_str - `strftime` format string - - Returns - ------- - StringValue - Formatted version of `arg` - """ - return ops.Strftime(self, format_str).to_expr() - - def truncate(self, unit: Literal["h", "m", "s", "ms", "us", "ns"]) -> TimeValue: - """Truncate the expression to a time expression in units of `unit`. - - Commonly used for time series resampling. - - Parameters - ---------- - unit - The unit to truncate to - - Returns - ------- - TimeValue - `self` truncated to `unit` - """ - return ops.TimeTruncate(self, unit).to_expr() - - def __add__( - self, - other: datetime.timedelta | pd.Timedelta | IntervalValue, - ) -> TimeValue: - """Add an interval to a time expression.""" - return _binop(ops.TimeAdd, self, other) - - add = radd = __radd__ = __add__ - """Add an interval to a time expression. - - Parameters - ---------- - other : datetime.timedelta | pd.Timedelta | IntervalValue - Interval to add to time expression - - Returns - ------- - Value : TimeValue - """ - - @annotated - def __sub__(self, other: ops.Value[dt.Interval | dt.Time, ds.Any]): - """Subtract a time or an interval from a time expression.""" - - if other.dtype.is_time(): - op = ops.TimeDiff - else: - op = ops.TimeSub # let the operation validate - - return _binop(op, self, other) - - sub = __sub__ - """Subtract a time or an interval from a time expression. - - Parameters - ---------- - other : TimeValue | IntervalValue - Interval to subtract from time expression - - Returns - ------- - Value : IntervalValue | TimeValue - """ - - @annotated - def __rsub__(self, other: ops.Value[dt.Interval | dt.Time, ds.Any]): - """Subtract a time or an interval from a time expression.""" - - if other.dtype.is_time(): - op = ops.TimeDiff - else: - op = ops.TimeSub # let the operation validate - - return _binop(op, other, self) - - rsub = __rsub__ - - def delta( - self, - other: datetime.time | Value[dt.Time], - part: Literal[ - "hour", "minute", "second", "millisecond", "microsecond", "nanosecond" - ] - | Value[dt.String], - ) -> ir.IntegerValue: - """Compute the number of `part`s between two times. - - ::: {.callout-note} - ## The order of operands matches standard subtraction - - The second argument is subtracted from the first. - ::: - - Parameters - ---------- - other - A time expression - part - The unit of time to compute the difference in - - Returns - ------- - IntegerValue - The number of `part`s between `self` and `other` - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> start = ibis.time("01:58:00") - >>> end = ibis.time("23:59:59") - >>> end.delta(start, "hour") - ┌──────────────┐ - │ np.int64(22) │ - └──────────────┘ - >>> data = '''tpep_pickup_datetime,tpep_dropoff_datetime - ... 2016-02-01T00:23:56,2016-02-01T00:42:28 - ... 2016-02-01T00:12:14,2016-02-01T00:21:41 - ... 2016-02-01T00:43:24,2016-02-01T00:46:14 - ... 2016-02-01T00:55:11,2016-02-01T01:24:34 - ... 2016-02-01T00:11:13,2016-02-01T00:16:59''' - >>> with open("/tmp/triptimes.csv", "w") as f: - ... nbytes = f.write(data) # nbytes is unused - >>> taxi = ibis.read_csv("/tmp/triptimes.csv") - >>> ride_duration = ( - ... taxi.tpep_dropoff_datetime.time() - ... .delta(taxi.tpep_pickup_datetime.time(), "minute") - ... .name("ride_minutes") - ... ) - >>> ride_duration - ┏━━━━━━━━━━━━━━┓ - ┃ ride_minutes ┃ - ┡━━━━━━━━━━━━━━┩ - │ int64 │ - ├──────────────┤ - │ 19 │ - │ 9 │ - │ 3 │ - │ 29 │ - │ 5 │ - └──────────────┘ - """ - return ops.TimeDelta(left=self, right=other, part=part).to_expr() - - -@public -class TimeScalar(Scalar, TimeValue): - pass - - -@public -class TimeColumn(Column, TimeValue): - pass - - -@public -class DateValue(Value, _DateComponentMixin): - def strftime(self, format_str: str) -> ir.StringValue: - """Format a date according to `format_str`. - - Format string may depend on the backend, but we try to conform to ANSI - `strftime`. - - Parameters - ---------- - format_str - `strftime` format string - - Returns - ------- - StringValue - Formatted version of `arg` - """ - return ops.Strftime(self, format_str).to_expr() - - def truncate(self, unit: Literal["Y", "Q", "M", "W", "D"]) -> DateValue: - """Truncate date expression to units of `unit`. - - Parameters - ---------- - unit - Unit to truncate `arg` to - - Returns - ------- - DateValue - Truncated date value expression - """ - return ops.DateTruncate(self, unit).to_expr() - - def __add__( - self, - other: datetime.timedelta | pd.Timedelta | IntervalValue, - ) -> DateValue: - """Add an interval to a date.""" - return _binop(ops.DateAdd, self, other) - - add = radd = __radd__ = __add__ - """Add an interval to a date. - - Parameters - ---------- - other : datetime.timedelta | pd.Timedelta | IntervalValue - Interval to add to DateValue - - Returns - ------- - Value : DateValue - """ - - @annotated - def __sub__(self, other: ops.Value[dt.Date | dt.Interval, ds.Any]): - """Subtract a date or an interval from a date.""" - - if other.dtype.is_date(): - op = ops.DateDiff - else: - op = ops.DateSub # let the operation validate - - return _binop(op, self, other) - - sub = __sub__ - """Subtract a date or an interval from a date. - - Parameters - ---------- - other : datetime.date | DateValue | datetime.timedelta | pd.Timedelta | IntervalValue - Interval to subtract from DateValue - - Returns - ------- - Value : DateValue - """ - - @annotated - def __rsub__(self, other: ops.Value[dt.Date | dt.Interval, ds.Any]): - """Subtract a date or an interval from a date.""" - - if other.dtype.is_date(): - op = ops.DateDiff - else: - op = ops.DateSub # let the operation validate - - return _binop(op, other, self) - - rsub = __rsub__ - - def delta( - self, - other: datetime.date | Value[dt.Date], - part: Literal["year", "quarter", "month", "week", "day"] | Value[dt.String], - ) -> ir.IntegerValue: - """Compute the number of `part`s between two dates. - - ::: {.callout-note} - ## The order of operands matches standard subtraction - - The second argument is subtracted from the first. - ::: - - Parameters - ---------- - other - A date expression - part - The unit of time to compute the difference in - - Returns - ------- - IntegerValue - The number of `part`s between `self` and `other` - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> start = ibis.date("1992-09-30") - >>> end = ibis.date("1992-10-01") - >>> end.delta(start, "day") - ┌─────────────┐ - │ np.int64(1) │ - └─────────────┘ - >>> prez = ibis.examples.presidential.fetch() - >>> prez.mutate( - ... years_in_office=prez.end.delta(prez.start, "year"), - ... hours_in_office=prez.end.delta(prez.start, "hour"), - ... ).drop("party") - ┏━━━━━━━━━━━━┳━━━━━━━━━━━━┳━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┓ - ┃ name ┃ start ┃ end ┃ years_in_office ┃ hours_in_office ┃ - ┡━━━━━━━━━━━━╇━━━━━━━━━━━━╇━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━┩ - │ string │ date │ date │ int64 │ int64 │ - ├────────────┼────────────┼────────────┼─────────────────┼─────────────────┤ - │ Eisenhower │ 1953-01-20 │ 1961-01-20 │ 8 │ 70128 │ - │ Kennedy │ 1961-01-20 │ 1963-11-22 │ 2 │ 24864 │ - │ Johnson │ 1963-11-22 │ 1969-01-20 │ 6 │ 45264 │ - │ Nixon │ 1969-01-20 │ 1974-08-09 │ 5 │ 48648 │ - │ Ford │ 1974-08-09 │ 1977-01-20 │ 3 │ 21480 │ - │ Carter │ 1977-01-20 │ 1981-01-20 │ 4 │ 35064 │ - │ Reagan │ 1981-01-20 │ 1989-01-20 │ 8 │ 70128 │ - │ Bush │ 1989-01-20 │ 1993-01-20 │ 4 │ 35064 │ - │ Clinton │ 1993-01-20 │ 2001-01-20 │ 8 │ 70128 │ - │ Bush │ 2001-01-20 │ 2009-01-20 │ 8 │ 70128 │ - │ … │ … │ … │ … │ … │ - └────────────┴────────────┴────────────┴─────────────────┴─────────────────┘ - """ - return ops.DateDelta(left=self, right=other, part=part).to_expr() - - -@public -class DateScalar(Scalar, DateValue): - pass - - -@public -class DateColumn(Column, DateValue): - pass - - -@public -class TimestampValue(_DateComponentMixin, _TimeComponentMixin, Value): - def strftime(self, format_str: str) -> ir.StringValue: - """Format a timestamp according to `format_str`. - - Format string may depend on the backend, but we try to conform to ANSI - `strftime`. - - Parameters - ---------- - format_str - `strftime` format string - - Returns - ------- - StringValue - Formatted version of `arg` - """ - return ops.Strftime(self, format_str).to_expr() - - def truncate( - self, - unit: Literal["Y", "Q", "M", "W", "D", "h", "m", "s", "ms", "us", "ns"], - ) -> TimestampValue: - """Truncate timestamp expression to units of `unit`. - - Parameters - ---------- - unit - Unit to truncate to - - Returns - ------- - TimestampValue - Truncated timestamp expression - """ - return ops.TimestampTruncate(self, unit).to_expr() - - @util.experimental - def bucket( - self, - interval: Any = None, - *, - years: int | None = None, - quarters: int | None = None, - months: int | None = None, - weeks: int | None = None, - days: int | None = None, - hours: int | None = None, - minutes: int | None = None, - seconds: int | None = None, - milliseconds: int | None = None, - microseconds: int | None = None, - nanoseconds: int | None = None, - offset: Any = None, - ) -> TimestampValue: - """Truncate the timestamp to buckets of a specified interval. - - This is similar to `truncate`, but supports truncating to arbitrary - intervals rather than a single unit. Buckets are computed as fixed - intervals starting from the UNIX epoch. This origin may be offset by - specifying `offset`. - - Parameters - ---------- - interval - The bucket width as an interval. Alternatively may be specified - via component keyword arguments. - years - Number of years - quarters - Number of quarters - months - Number of months - weeks - Number of weeks - days - Number of days - hours - Number of hours - minutes - Number of minutes - seconds - Number of seconds - milliseconds - Number of milliseconds - microseconds - Number of microseconds - nanoseconds - Number of nanoseconds - offset - An interval to use to offset the start of the bucket. - - Returns - ------- - TimestampValue - The start of the bucket as a timestamp. - - Examples - -------- - >>> import ibis - >>> from ibis import _ - >>> ibis.options.interactive = True - >>> t = ibis.memtable( - ... [ - ... ("2020-04-15 08:04:00", 1), - ... ("2020-04-15 08:06:00", 2), - ... ("2020-04-15 08:09:00", 3), - ... ("2020-04-15 08:11:00", 4), - ... ], - ... columns=["ts", "val"], - ... ).cast({"ts": "timestamp"}) - - Bucket the data into 5 minute wide buckets: - - >>> t.ts.bucket(minutes=5) - ┏━━━━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ TimestampBucket(ts, 5m) ┃ - ┡━━━━━━━━━━━━━━━━━━━━━━━━━┩ - │ timestamp │ - ├─────────────────────────┤ - │ 2020-04-15 08:00:00 │ - │ 2020-04-15 08:05:00 │ - │ 2020-04-15 08:05:00 │ - │ 2020-04-15 08:10:00 │ - └─────────────────────────┘ - - Bucket the data into 5 minute wide buckets, offset by 2 minutes: - - >>> t.ts.bucket(minutes=5, offset=ibis.interval(minutes=2)) - ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ TimestampBucket(ts, 5m, 2m) ┃ - ┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩ - │ timestamp │ - ├─────────────────────────────┤ - │ 2020-04-15 08:02:00 │ - │ 2020-04-15 08:02:00 │ - │ 2020-04-15 08:07:00 │ - │ 2020-04-15 08:07:00 │ - └─────────────────────────────┘ - - One common use of timestamp bucketing is computing statistics per - bucket. Here we compute the mean of `val` across 5 minute intervals: - - >>> mean_by_bucket = ( - ... t.group_by(t.ts.bucket(minutes=5).name("bucket")) - ... .agg(mean=_.val.mean()) - ... .order_by("bucket") - ... ) - >>> mean_by_bucket - ┏━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━┓ - ┃ bucket ┃ mean ┃ - ┡━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━┩ - │ timestamp │ float64 │ - ├─────────────────────┼─────────┤ - │ 2020-04-15 08:00:00 │ 1.0 │ - │ 2020-04-15 08:05:00 │ 2.5 │ - │ 2020-04-15 08:10:00 │ 4.0 │ - └─────────────────────┴─────────┘ - """ - - components = { - "years": years, - "quarters": quarters, - "months": months, - "weeks": weeks, - "days": days, - "hours": hours, - "minutes": minutes, - "seconds": seconds, - "milliseconds": milliseconds, - "microseconds": microseconds, - "nanoseconds": nanoseconds, - } - has_components = any(v is not None for v in components.values()) - if (interval is not None) == has_components: - raise ValueError( - "Must specify either interval value or components, but not both" - ) - if has_components: - interval = ibis.interval(**components) - return ops.TimestampBucket(self, interval, offset).to_expr() - - def date(self) -> DateValue: - """Return the date component of the expression. - - Returns - ------- - DateValue - The date component of `self` - """ - return ops.Date(self).to_expr() - - def __add__( - self, - other: datetime.timedelta | pd.Timedelta | IntervalValue, - ) -> TimestampValue: - """Add an interval to a timestamp.""" - return _binop(ops.TimestampAdd, self, other) - - add = radd = __radd__ = __add__ - """Add an interval to a timestamp. - - Parameters - ---------- - other : datetime.timedelta | pd.Timedelta | IntervalValue - Interval to subtract from timestamp - - Returns - ------- - Value : TimestampValue - """ - - @annotated - def __sub__(self, other: ops.Value[dt.Timestamp | dt.Interval, ds.Any]): - """Subtract a timestamp or an interval from a timestamp.""" - - if other.dtype.is_timestamp(): - op = ops.TimestampDiff - else: - op = ops.TimestampSub # let the operation validate - - return _binop(op, self, other) - - sub = __sub__ - """Subtract a timestamp or an interval from a timestamp. - - Parameters - ---------- - other : datetime.datetime | pd.Timestamp | TimestampValue | datetime.timedelta | pd.Timedelta | IntervalValue - Timestamp or interval to subtract from timestamp - - Returns - ------- - Value : IntervalValue | TimestampValue - """ - - @annotated - def __rsub__(self, other: ops.Value[dt.Timestamp | dt.Interval, ds.Any]): - """Subtract a timestamp or an interval from a timestamp.""" - - if other.dtype.is_timestamp(): - op = ops.TimestampDiff - else: - op = ops.TimestampSub # let the operation validate - - return _binop(op, other, self) - - rsub = __rsub__ - - def delta( - self, - other: datetime.datetime | Value[dt.Timestamp], - part: Literal[ - "year", - "quarter", - "month", - "week", - "day", - "hour", - "minute", - "second", - "millisecond", - "microsecond", - "nanosecond", - ] - | Value[dt.String], - ) -> ir.IntegerValue: - """Compute the number of `part`s between two timestamps. - - ::: {.callout-note} - ## The order of operands matches standard subtraction - - The second argument is subtracted from the first. - ::: - - Parameters - ---------- - other - A timestamp expression - part - The unit of time to compute the difference in - - Returns - ------- - IntegerValue - The number of `part`s between `self` and `other` - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> start = ibis.time("01:58:00") - >>> end = ibis.time("23:59:59") - >>> end.delta(start, "hour") - ┌──────────────┐ - │ np.int64(22) │ - └──────────────┘ - >>> data = '''tpep_pickup_datetime,tpep_dropoff_datetime - ... 2016-02-01T00:23:56,2016-02-01T00:42:28 - ... 2016-02-01T00:12:14,2016-02-01T00:21:41 - ... 2016-02-01T00:43:24,2016-02-01T00:46:14 - ... 2016-02-01T00:55:11,2016-02-01T01:24:34 - ... 2016-02-01T00:11:13,2016-02-01T00:16:59''' - >>> with open("/tmp/triptimes.csv", "w") as f: - ... nbytes = f.write(data) # nbytes is unused - >>> taxi = ibis.read_csv("/tmp/triptimes.csv") - >>> ride_duration = taxi.tpep_dropoff_datetime.delta( - ... taxi.tpep_pickup_datetime, "minute" - ... ).name("ride_minutes") - >>> ride_duration - ┏━━━━━━━━━━━━━━┓ - ┃ ride_minutes ┃ - ┡━━━━━━━━━━━━━━┩ - │ int64 │ - ├──────────────┤ - │ 19 │ - │ 9 │ - │ 3 │ - │ 29 │ - │ 5 │ - └──────────────┘ - """ - return ops.TimestampDelta(left=self, right=other, part=part).to_expr() - - -@public -class TimestampScalar(Scalar, TimestampValue): - pass - - -@public -class TimestampColumn(Column, TimestampValue): - pass - - -@public -class IntervalValue(Value): - def to_unit(self, target_unit: str) -> IntervalValue: - """Convert this interval to units of `target_unit`.""" - # TODO(kszucs): should use a separate operation for unit conversion - # which we can rewrite/simplify to integer multiplication/division - op = self.op() - current_unit = op.dtype.unit - target_unit = IntervalUnit.from_string(target_unit) - - if current_unit == target_unit: - return self - elif isinstance(op, ops.Literal): - value = util.convert_unit(op.value, current_unit.short, target_unit.short) - return ops.Literal(value, dtype=dt.Interval(target_unit)).to_expr() - else: - value = util.convert_unit( - self.cast(dt.int64), current_unit.short, target_unit.short - ) - return value.to_interval(target_unit) - - @property - def years(self) -> ir.IntegerValue: - """The number of years (IntegerValue).""" - return self.to_unit("Y") - - @property - def quarters(self) -> ir.IntegerValue: - """The number of quarters (IntegerValue).""" - return self.to_unit("Q") - - @property - def months(self) -> ir.IntegerValue: - """The number of months (IntegerValue).""" - return self.to_unit("M") - - @property - def weeks(self) -> ir.IntegerValue: - """The number of weeks (IntegerValue).""" - return self.to_unit("W") - - @property - def days(self) -> ir.IntegerValue: - """The number of days (IntegerValue).""" - return self.to_unit("D") - - @property - def hours(self) -> ir.IntegerValue: - """The number of hours (IntegerValue).""" - return self.to_unit("h") - - @property - def minutes(self) -> ir.IntegerValue: - """The number of minutes (IntegerValue).""" - return self.to_unit("m") - - @property - def seconds(self) -> ir.IntegerValue: - """The number of seconds (IntegerValue).""" - return self.to_unit("s") - - @property - def milliseconds(self) -> ir.IntegerValue: - """The number of milliseconds (IntegerValue).""" - return self.to_unit("ms") - - @property - def microseconds(self) -> ir.IntegerValue: - """The number of microseconds (IntegerValue).""" - return self.to_unit("us") - - @property - def nanoseconds(self) -> ir.IntegerValue: - """The number of nanoseconds (IntegerValue).""" - return self.to_unit("ns") - - def __add__( - self, - other: datetime.timedelta | pd.Timedelta | IntervalValue, - ) -> IntervalValue: - """Add this interval to `other`.""" - return _binop(ops.IntervalAdd, self, other) - - add = radd = __radd__ = __add__ - - def __sub__( - self, - other: datetime.timedelta | pd.Timedelta | IntervalValue, - ) -> IntervalValue: - """Subtract `other` from this interval.""" - return _binop(ops.IntervalSubtract, self, other) - - sub = __sub__ - - def __rsub__( - self, - other: datetime.timedelta | pd.Timedelta | IntervalValue, - ) -> IntervalValue: - """Subtract `other` from this interval.""" - return _binop(ops.IntervalSubtract, other, self) - - rsub = __rsub__ - - def __mul__( - self, - other: int | ir.IntegerValue, - ) -> IntervalValue: - """Multiply this interval by `other`.""" - return _binop(ops.IntervalMultiply, self, other) - - mul = rmul = __rmul__ = __mul__ - - def __floordiv__( - self, - other: ir.IntegerValue, - ) -> IntervalValue: - """Floor-divide this interval by `other`.""" - return _binop(ops.IntervalFloorDivide, self, other) - - floordiv = __floordiv__ - - def negate(self) -> ir.IntervalValue: - """Negate an interval expression. - - Returns - ------- - IntervalValue - A negated interval value expression - """ - return ops.Negate(self).to_expr() - - __neg__ = negate - - -@public -class IntervalScalar(Scalar, IntervalValue): - pass - - -@public -class IntervalColumn(Column, IntervalValue): - pass - - -@public -class DayOfWeek: - """A namespace of methods for extracting day of week information.""" - - def __init__(self, expr): - self._expr = expr - - def index(self): - """Get the index of the day of the week. - - ::: {.callout-note} - ## Ibis follows the `pandas` convention for day numbering: Monday = 0 and Sunday = 6. - ::: - - Returns - ------- - IntegerValue - The index of the day of the week. - """ - return ops.DayOfWeekIndex(self._expr).to_expr() - - def full_name(self): - """Get the name of the day of the week. - - Returns - ------- - StringValue - The name of the day of the week - """ - return ops.DayOfWeekName(self._expr).to_expr() diff --git a/third_party/bigframes_vendored/ibis/expr/types/temporal_windows.py b/third_party/bigframes_vendored/ibis/expr/types/temporal_windows.py deleted file mode 100644 index 13e917c744c..00000000000 --- a/third_party/bigframes_vendored/ibis/expr/types/temporal_windows.py +++ /dev/null @@ -1,94 +0,0 @@ -# Contains code from https://github.com/ibis-project/ibis/blob/9.2.0/ibis/expr/types/temporal_windows.py - -from __future__ import annotations - -from typing import TYPE_CHECKING, Literal - -import bigframes_vendored.ibis.common.exceptions as com -import bigframes_vendored.ibis.expr.operations as ops -import bigframes_vendored.ibis.expr.types as ir -from bigframes_vendored.ibis.common.collections import FrozenOrderedDict # noqa: TCH001 -from bigframes_vendored.ibis.common.grounds import Concrete -from bigframes_vendored.ibis.expr.operations.relations import Unaliased # noqa: TCH001 -from bigframes_vendored.ibis.expr.types.relations import unwrap_aliases -from public import public - -if TYPE_CHECKING: - from collections.abc import Sequence - - -@public -class WindowedTable(Concrete): - """An intermediate table expression to hold windowing information.""" - - parent: ir.Table - time_col: ops.Column - window_type: Literal["tumble", "hop"] | None = None - window_size: ir.IntervalScalar | None = None - window_slide: ir.IntervalScalar | None = None - window_offset: ir.IntervalScalar | None = None - groups: FrozenOrderedDict[str, Unaliased[ops.Column]] | None = None - metrics: FrozenOrderedDict[str, Unaliased[ops.Column]] | None = None - - def __init__(self, time_col: ops.Column, **kwargs): - if time_col is None: - raise com.IbisInputError( - "Window aggregations require `time_col` as an argument" - ) - super().__init__(time_col=time_col, **kwargs) - - def tumble( - self, - size: ir.IntervalScalar, - offset: ir.IntervalScalar | None = None, - ) -> WindowedTable: - return self.copy(window_type="tumble", window_size=size, window_offset=offset) - - def hop( - self, - size: ir.IntervalScalar, - slide: ir.IntervalScalar, - offset: ir.IntervalScalar | None = None, - ) -> WindowedTable: - return self.copy( - window_type="hop", - window_size=size, - window_slide=slide, - window_offset=offset, - ) - - def aggregate( - self, - metrics: Sequence[ir.Scalar] | None = (), - by: str | ir.Value | Sequence[str] | Sequence[ir.Value] | None = (), - **kwargs: ir.Value, - ) -> ir.Table: - by = self.parent.bind(by) - metrics = self.parent.bind(metrics, **kwargs) - - by = unwrap_aliases(by) - metrics = unwrap_aliases(metrics) - - groups = dict(self.groups) if self.groups is not None else {} - groups.update(by) - - return ops.WindowAggregate( - self.parent, - self.window_type, - self.time_col, - groups=groups, - metrics=metrics, - window_size=self.window_size, - window_slide=self.window_slide, - window_offset=self.window_offset, - ).to_expr() - - agg = aggregate - - def group_by( - self, *by: str | ir.Value | Sequence[str] | Sequence[ir.Value] - ) -> WindowedTable: - by = tuple(v for v in by if v is not None) - groups = self.parent.bind(*by) - groups = unwrap_aliases(groups) - return self.copy(groups=groups) diff --git a/third_party/bigframes_vendored/ibis/expr/types/typing.py b/third_party/bigframes_vendored/ibis/expr/types/typing.py deleted file mode 100644 index 68c59894d05..00000000000 --- a/third_party/bigframes_vendored/ibis/expr/types/typing.py +++ /dev/null @@ -1,11 +0,0 @@ -# Contains code from https://github.com/ibis-project/ibis/blob/9.2.0/ibis/expr/types/typing.py - -from __future__ import annotations - -from collections.abc import Hashable -from typing import TypeVar - -__all__ = ["K", "V"] - -K = TypeVar("K", bound=Hashable) -V = TypeVar("V") diff --git a/third_party/bigframes_vendored/ibis/expr/types/uuid.py b/third_party/bigframes_vendored/ibis/expr/types/uuid.py deleted file mode 100644 index 9e0f07c37c4..00000000000 --- a/third_party/bigframes_vendored/ibis/expr/types/uuid.py +++ /dev/null @@ -1,21 +0,0 @@ -# Contains code from https://github.com/ibis-project/ibis/blob/9.2.0/ibis/expr/types/uuid.py - -from __future__ import annotations - -from bigframes_vendored.ibis.expr.types.generic import Column, Scalar, Value -from public import public - - -@public -class UUIDValue(Value): - pass - - -@public -class UUIDScalar(Scalar, UUIDValue): - pass - - -@public -class UUIDColumn(Column, UUIDValue): - pass diff --git a/third_party/bigframes_vendored/ibis/expr/visualize.py b/third_party/bigframes_vendored/ibis/expr/visualize.py deleted file mode 100644 index 390c9c98282..00000000000 --- a/third_party/bigframes_vendored/ibis/expr/visualize.py +++ /dev/null @@ -1,255 +0,0 @@ -# Contains code from https://github.com/ibis-project/ibis/blob/9.2.0/ibis/expr/visualize.py - -from __future__ import annotations - -import contextlib -import sys -import tempfile -from collections.abc import Callable -from html import escape -from typing import Optional - -import bigframes_vendored.ibis -import bigframes_vendored.ibis.common.exceptions as com -import bigframes_vendored.ibis.expr.operations as ops -import graphviz as gv -from bigframes_vendored.ibis.common.graph import Graph - - -def get_type(node): - with contextlib.suppress(AttributeError, NotImplementedError): - return escape(str(node.dtype)) - - try: - schema = node.schema - except (AttributeError, NotImplementedError): - # TODO(kszucs): this branch should be removed - try: - # As a last resort try get the name of the output_type class - return node.output_type.__name__ - except (AttributeError, NotImplementedError): - return "\u2205" # empty set character - except com.IbisError: - assert isinstance(node, ops.Join) - left_table_name = getattr(node.left, "name", None) or ops.genname() - left_schema = node.left.schema - right_table_name = getattr(node.right, "name", None) or ops.genname() - right_schema = node.right.schema - pairs = [ - (f"{left_table_name}.{left_column}", type) - for left_column, type in left_schema.items() - ] + [ - (f"{right_table_name}.{right_column}", type) - for right_column, type in right_schema.items() - ] - schema = bigframes_vendored.ibis.schema(pairs) - else: - # Simple relations have the same schema as their parent so avoid - # re-rendering the same schema fields for these relations - if isinstance(node, ops.relations.Simple): - return '
:: …' - - return '
' + '
'.join( - f"{escape(name)}: {escape(str(type))}" - for name, type in zip(schema.names, schema.types) - ) - - -def get_label(node): - typename = get_type(node) # Already an escaped string - name = type(node).__name__ - nodename = ( - node.name - if isinstance( - node, - ( - ops.Literal, - ops.Field, - ops.Alias, - ops.PhysicalTable, - ), - ) - else None - ) - if nodename is not None: - if isinstance(node, ops.Relation): - label_fmt = "<{}: {}{}>" - else: - label_fmt = '<{}: {}
:: {}>' - # typename is already escaped - label = label_fmt.format(escape(nodename), escape(name), typename) - else: - if isinstance(node, ops.Relation): - label_fmt = "<{}{}>" - else: - label_fmt = '<{}
:: {}>' - label = label_fmt.format(escape(name), typename) - return label - - -DEFAULT_NODE_ATTRS = {"shape": "box", "fontname": "Deja Vu Sans Mono"} -DEFAULT_EDGE_ATTRS = {"fontname": "Deja Vu Sans Mono"} - -NodeAttributeGetter = Callable[[ops.Node], Optional[dict[str, str]]] -EdgeAttributeGetter = Callable[[ops.Node, ops.Node], Optional[dict[str, str]]] - - -def to_graph( - expr, - node_attr=None, - node_attr_getter: NodeAttributeGetter | None = None, - edge_attr=None, - edge_attr_getter: EdgeAttributeGetter | None = None, - label_edges: bool = False, -): - graph = Graph.from_bfs(expr.op(), filter=ops.Node) - - g = gv.Digraph( - node_attr=DEFAULT_NODE_ATTRS | (node_attr or {}), - edge_attr=DEFAULT_EDGE_ATTRS | (edge_attr or {}), - ) - - g.attr(rankdir="BT") - - seen = set() - edges = set() - - for v, us in graph.items(): - vhash = str(hash(v)) - if v not in seen: - g.node( - vhash, - label=get_label(v), - _attributes=node_attr_getter(v) if node_attr_getter else {}, - ) - seen.add(v) - - for u in us: - uhash = str(hash(u)) - if u not in seen: - g.node( - uhash, - label=get_label(u), - _attributes=node_attr_getter(u) if node_attr_getter else {}, - ) - seen.add(u) - if (edge := (u, v)) not in edges: - if not label_edges: - label = None - else: - if isinstance(v, ops.Relation): - if (name := getattr(u, "name", None)) in v.fields: - name = f"fields[{name!r}]" - else: - name = None - else: - for name, arg in zip(v.argnames, v.args): - if isinstance(arg, tuple) and u in arg: - index = arg.index(u) - name = f"{name}[{index}]" - break - elif arg == u: - break - else: - name = None - - if name is not None: - label = f"<.{name}>" - else: - label = None - - g.edge( - uhash, - vhash, - label=label, - _attributes=edge_attr_getter(u, v) if edge_attr_getter else {}, - ) - edges.add(edge) - return g - - -def draw(graph, path=None, format="png", verbose: bool = False): - if verbose: - print(graph.source, file=sys.stderr) # noqa: T201 - - piped_source = graph.pipe(format=format) - - if path is None: - with tempfile.NamedTemporaryFile( - delete=False, suffix=f".{format}", mode="wb" - ) as f: - f.write(piped_source) - return f.name - else: - with open(path, mode="wb") as f: - f.write(piped_source) - return path - - -if __name__ == "__main__": - import json - from argparse import ArgumentParser - - from bigframes_vendored.ibis import _ - - p = ArgumentParser( - description="Render a GraphViz SVG of an example ibis expression." - ) - - p.add_argument( - "-v", - "--verbose", - action="count", - default=0, - help="Print GraphViz DOT code to stderr.", - ) - p.add_argument( - "-l", - "--label-edges", - action="store_true", - help="Show operation inputs as edge labels.", - ) - p.add_argument( - "-n", - "--node-attr", - type=lambda x: json.loads(x) if x else {}, - default="{}", - help='JSON string of node attributes. E.g., \'{"fontname": "Roboto Mono", "fontsize": "10"}\'', - ) - p.add_argument( - "-e", - "--edge-attr", - type=lambda x: json.loads(x) if x else {}, - default="{}", - help='JSON string of edge attributes. E.g., \'{"fontsize": "8"}\'', - ) - - args = p.parse_args() - - left = bigframes_vendored.ibis.table(dict(a="int64", b="string"), name="left") - right = bigframes_vendored.ibis.table( - dict(b="string", c="int64", d="string"), name="right" - ) - expr = ( - left.inner_join(right, "b") - .select(left.a, b=right.c, c=right.d) - .filter((_.a + _.b * 2 * _.b / _.b**3 > 4) & (_.b > 5)) - .group_by(_.c) - .having(_.a.mean() > 0.0) - .aggregate(a_mean=_.a.mean(), b_sum=_.b.sum()) - .order_by(_.a_mean) - .mutate( - arrays=bigframes_vendored.ibis.array([1, 2, 3]), - maps=bigframes_vendored.ibis.map({"a": 1, "b": 2}), - structs=bigframes_vendored.ibis.struct( - {"a": [1, 2, 3], "b": {"c": 1, "d": 2}} - ), - ) - ) - - expr.visualize( - verbose=args.verbose > 0, - label_edges=args.label_edges, - node_attr=args.node_attr, - edge_attr=args.edge_attr, - ) diff --git a/third_party/bigframes_vendored/ibis/formats/__init__.py b/third_party/bigframes_vendored/ibis/formats/__init__.py deleted file mode 100644 index 627299a0da1..00000000000 --- a/third_party/bigframes_vendored/ibis/formats/__init__.py +++ /dev/null @@ -1,266 +0,0 @@ -# Contains code from https://github.com/ibis-project/ibis/blob/9.2.0/ibis/formats/__init__.py - -from __future__ import annotations - -from abc import abstractmethod -from typing import TYPE_CHECKING, Generic, TypeVar - -from bigframes_vendored.ibis.util import PseudoHashable, indent - -if TYPE_CHECKING: - import pandas as pd - import polars as pl - import pyarrow as pa - from bigframes_vendored.ibis.expr.datatypes import DataType - from bigframes_vendored.ibis.expr.schema import Schema - -C = TypeVar("C") -T = TypeVar("T") -S = TypeVar("S") - - -class TypeMapper(Generic[T]): - # `T` is the format-specific type object, e.g. pyarrow.DataType - - @classmethod - def from_ibis(cls, dtype: DataType) -> T: - """Convert an Ibis DataType to a format-specific type object. - - Parameters - ---------- - dtype - The Ibis DataType to convert. - - Returns - ------- - Format-specific type object. - - """ - raise NotImplementedError - - @classmethod - def to_ibis(cls, typ: T, nullable: bool = True) -> DataType: - """Convert a format-specific type object to an Ibis DataType. - - Parameters - ---------- - typ - The format-specific type object to convert. - nullable - Whether the Ibis DataType should be nullable. - - Returns - ------- - Ibis DataType. - - """ - raise NotImplementedError - - @classmethod - def from_string(cls, text: str, nullable: bool = True) -> DataType: - """Convert a backend-specific string representation into an Ibis DataType. - - Parameters - ---------- - text - The backend-specific string representation to convert. - nullable - Whether the Ibis DataType should be nullable. - - Returns - ------- - Ibis DataType. - - """ - raise NotImplementedError - - @classmethod - def to_string(cls, dtype: DataType) -> str: - """Convert `dtype` into a backend-specific string representation. - - Parameters - ---------- - dtype - The Ibis DataType to convert. - - Returns - ------- - Backend-specific string representation. - - """ - raise NotImplementedError - - -class SchemaMapper(Generic[S]): - # `S` is the format-specific schema object, e.g. pyarrow.Schema - - @classmethod - def from_ibis(cls, schema: Schema) -> S: - """Convert an Ibis Schema to a format-specific schema object. - - Parameters - ---------- - schema - The Ibis Schema to convert. - - Returns - ------- - Format-specific schema object. - - """ - raise NotImplementedError - - @classmethod - def to_ibis(cls, obj: S) -> Schema: - """Convert a format-specific schema object to an Ibis Schema. - - Parameters - ---------- - obj - The format-specific schema object to convert. - - Returns - ------- - Ibis Schema. - - """ - raise NotImplementedError - - -class DataMapper(Generic[S, C, T]): - # `S` is the format-specific scalar object, e.g. pyarrow.Scalar - # `C` is the format-specific column object, e.g. pyarrow.Array - # `T` is the format-specific table object, e.g. pyarrow.Table - - @classmethod - def convert_scalar(cls, obj: S, dtype: DataType) -> S: - """Convert a format-specific scalar to the given ibis datatype. - - Parameters - ---------- - obj - The format-specific scalar value to convert. - dtype - The Ibis datatype to convert to. - - Returns - ------- - Format specific scalar corresponding to the given Ibis datatype. - - """ - raise NotImplementedError - - @classmethod - def convert_column(cls, obj: C, dtype: DataType) -> C: - """Convert a format-specific column to the given ibis datatype. - - Parameters - ---------- - obj - The format-specific column value to convert. - dtype - The Ibis datatype to convert to. - - Returns - ------- - Format specific column corresponding to the given Ibis datatype. - - """ - raise NotImplementedError - - @classmethod - def convert_table(cls, obj: T, schema: Schema) -> T: - """Convert a format-specific table to the given ibis schema. - - Parameters - ---------- - obj - The format-specific table-like object to convert. - schema - The Ibis schema to convert to. - - Returns - ------- - Format specific table-like object corresponding to the given Ibis schema. - - """ - raise NotImplementedError - - @classmethod - def infer_scalar(cls, obj: S) -> DataType: - """Infer the Ibis datatype of a format-specific scalar. - - Parameters - ---------- - obj - The format-specific scalar to infer the Ibis datatype of. - - Returns - ------- - Ibis datatype corresponding to the given format-specific scalar. - - """ - raise NotImplementedError - - @classmethod - def infer_column(cls, obj: C) -> DataType: - """Infer the Ibis datatype of a format-specific column. - - Parameters - ---------- - obj - The format-specific column to infer the Ibis datatype of. - - Returns - ------- - Ibis datatype corresponding to the given format-specific column. - - """ - raise NotImplementedError - - @classmethod - def infer_table(cls, obj: T) -> Schema: - """Infer the Ibis schema of a format-specific table. - - Parameters - ---------- - obj - The format-specific table to infer the Ibis schema of. - - Returns - ------- - Ibis schema corresponding to the given format-specific table. - - """ - raise NotImplementedError - - -class TableProxy(PseudoHashable[T]): - def __repr__(self) -> str: - data_repr = indent(repr(self.obj), spaces=2) - return f"{self.__class__.__name__}:\n{data_repr}" - - def __len__(self) -> int: - return len(self.obj) - - @abstractmethod - def to_frame(self) -> pd.DataFrame: # pragma: no cover - """Convert this input to a pandas DataFrame.""" - - @abstractmethod - def to_pyarrow(self, schema: Schema) -> pa.Table: # pragma: no cover - """Convert this input to a PyArrow Table.""" - - @abstractmethod - def to_polars(self, schema: Schema) -> pl.DataFrame: # pragma: no cover - """Convert this input to a Polars DataFrame.""" - - def to_pyarrow_bytes(self, schema: Schema) -> bytes: - import pyarrow as pa - import pyarrow_hotfix # noqa: F401 - - data = self.to_pyarrow(schema=schema) - out = pa.BufferOutputStream() - with pa.RecordBatchFileWriter(out, data.schema) as writer: - writer.write(data) - return out.getvalue() diff --git a/third_party/bigframes_vendored/ibis/formats/numpy.py b/third_party/bigframes_vendored/ibis/formats/numpy.py deleted file mode 100644 index 76cab2888b3..00000000000 --- a/third_party/bigframes_vendored/ibis/formats/numpy.py +++ /dev/null @@ -1,103 +0,0 @@ -# Contains code from https://github.com/ibis-project/ibis/blob/9.2.0/ibis/formats/numpy.py - -from __future__ import annotations - -import bigframes_vendored.ibis.expr.datatypes as dt -import bigframes_vendored.ibis.expr.schema as sch -import numpy as np -import toolz -from bigframes_vendored.ibis.formats import SchemaMapper, TypeMapper - -_from_numpy_types = toolz.keymap( - np.dtype, - { - np.bool_: dt.Boolean, - np.int8: dt.Int8, - np.int16: dt.Int16, - np.int32: dt.Int32, - np.int64: dt.Int64, - np.uint8: dt.UInt8, - np.uint16: dt.UInt16, - np.uint32: dt.UInt32, - np.uint64: dt.UInt64, - np.float16: dt.Float16, - np.float32: dt.Float32, - np.float64: dt.Float64, - }, -) - - -_to_numpy_types = {v: k for k, v in _from_numpy_types.items()} - - -class NumpyType(TypeMapper[np.dtype]): - @classmethod - def to_ibis(cls, typ: np.dtype, nullable: bool = True) -> dt.DataType: - if np.issubdtype(typ, np.datetime64): - # TODO(kszucs): the following code provedes proper timestamp roundtrips - # between ibis and numpy/pandas but breaks the test suite at several - # places, we should revisit this later - # unit, _ = np.datetime_data(typ) - # if unit in {'generic', 'Y', 'M', 'D', 'h', 'm'}: - # return dt.Timestamp(nullable=nullable) - # else: - # return dt.Timestamp.from_unit(unit, nullable=nullable) - return dt.Timestamp(nullable=nullable) - elif np.issubdtype(typ, np.timedelta64): - unit, _ = np.datetime_data(typ) - if unit == "generic": - unit = "s" - return dt.Interval(unit, nullable=nullable) - elif np.issubdtype(typ, np.str_): - return dt.String(nullable=nullable) - elif np.issubdtype(typ, np.bytes_): - return dt.Binary(nullable=nullable) - else: - try: - return _from_numpy_types[typ](nullable=nullable) - except KeyError: - raise TypeError(f"numpy dtype {typ!r} is not supported") - - @classmethod - def from_ibis(cls, dtype: dt.DataType) -> np.dtype: - if dtype.is_interval(): - return np.dtype(f"timedelta64[{dtype.unit.short}]") - elif dtype.is_timestamp(): - # TODO(kszucs): the following code provedes proper timestamp roundtrips - # between ibis and numpy/pandas but breaks the test suite at several - # places, we should revisit this later - # return np.dtype(f"datetime64[{dtype.unit.short}]") - return np.dtype("datetime64[ns]") - elif dtype.is_date(): - return np.dtype("datetime64[D]") - elif dtype.is_time(): - return np.dtype("timedelta64[ns]") - elif ( - dtype.is_null() - or dtype.is_decimal() - or dtype.is_struct() - or dtype.is_variadic() - or dtype.is_unknown() - or dtype.is_uuid() - or dtype.is_geospatial() - or dtype.is_inet() - or dtype.is_macaddr() - ): - return np.dtype("object") - else: - try: - return _to_numpy_types[type(dtype)] - except KeyError: - raise TypeError(f"ibis dtype {dtype!r} is not supported") - - -class NumpySchema(SchemaMapper): - @classmethod - def from_ibis(cls, schema): - numpy_types = map(NumpyType.from_ibis, schema.types) - return list(zip(schema.names, numpy_types)) - - @classmethod - def to_ibis(cls, schema): - ibis_types = {name: NumpyType.to_ibis(typ) for name, typ in schema} - return sch.Schema(ibis_types) diff --git a/third_party/bigframes_vendored/ibis/formats/pandas.py b/third_party/bigframes_vendored/ibis/formats/pandas.py deleted file mode 100644 index a24c170ac50..00000000000 --- a/third_party/bigframes_vendored/ibis/formats/pandas.py +++ /dev/null @@ -1,427 +0,0 @@ -# Contains code from https://github.com/ibis-project/ibis/blob/9.2.0/ibis/formats/pandas.py - -from __future__ import annotations - -import contextlib -import datetime -import warnings -from functools import partial -from importlib.util import find_spec as _find_spec -from typing import TYPE_CHECKING - -import bigframes_vendored.ibis.expr.datatypes as dt -import bigframes_vendored.ibis.expr.schema as sch -import numpy as np -import pandas as pd -import pandas.api.types as pdt -from bigframes_vendored.ibis import util -from bigframes_vendored.ibis.common.numeric import normalize_decimal -from bigframes_vendored.ibis.common.temporal import normalize_timezone -from bigframes_vendored.ibis.formats import DataMapper, SchemaMapper, TableProxy -from bigframes_vendored.ibis.formats.numpy import NumpyType -from bigframes_vendored.ibis.formats.pyarrow import ( - PyArrowData, - PyArrowSchema, - PyArrowType, -) - -if TYPE_CHECKING: - import polars as pl - import pyarrow as pa - -_has_arrow_dtype = hasattr(pd, "ArrowDtype") - -if not _has_arrow_dtype: - warnings.warn( - f"The `ArrowDtype` class is not available in pandas {pd.__version__}. " - "Install pandas >= 1.5.0 for interop with pandas and arrow dtype support" - ) - -geospatial_supported = _find_spec("geopandas") is not None - - -class PandasType(NumpyType): - @classmethod - def to_ibis(cls, typ, nullable=True): - if isinstance(typ, pdt.DatetimeTZDtype): - return dt.Timestamp(timezone=str(typ.tz), nullable=nullable) - elif pdt.is_datetime64_dtype(typ): - return dt.Timestamp(nullable=nullable) - elif isinstance(typ, pdt.CategoricalDtype): - if typ.categories is None or pdt.is_string_dtype(typ.categories): - return dt.String(nullable=nullable) - return cls.to_ibis(typ.categories.dtype, nullable=nullable) - elif pdt.is_extension_array_dtype(typ): - if _has_arrow_dtype and isinstance(typ, pd.ArrowDtype): - return PyArrowType.to_ibis(typ.pyarrow_dtype, nullable=nullable) - else: - name = typ.__class__.__name__.replace("Dtype", "") - klass = getattr(dt, name) - return klass(nullable=nullable) - else: - return super().to_ibis(typ, nullable=nullable) - - @classmethod - def from_ibis(cls, dtype): - if dtype.is_timestamp() and dtype.timezone: - return pdt.DatetimeTZDtype("ns", dtype.timezone) - elif dtype.is_date(): - return np.dtype("M8[s]") - elif dtype.is_interval(): - return np.dtype(f"timedelta64[{dtype.unit.short}]") - else: - return super().from_ibis(dtype) - - -class PandasSchema(SchemaMapper): - @classmethod - def to_ibis(cls, pandas_schema): - if isinstance(pandas_schema, pd.Series): - pandas_schema = pandas_schema.to_list() - - fields = {name: PandasType.to_ibis(t) for name, t in pandas_schema} - - return sch.Schema(fields) - - @classmethod - def from_ibis(cls, schema): - names = schema.names - types = [PandasType.from_ibis(t) for t in schema.types] - return list(zip(names, types)) - - -class PandasData(DataMapper): - @classmethod - def infer_scalar(cls, s): - return PyArrowData.infer_scalar(s) - - @classmethod - def infer_column(cls, s): - return PyArrowData.infer_column(s) - - @classmethod - def infer_table(cls, df): - pairs = [] - for column_name in df.dtypes.keys(): - if not isinstance(column_name, str): - raise TypeError( - "Column names must be strings to use the pandas backend" - ) - - pandas_column = df[column_name] - pandas_dtype = pandas_column.dtype - if pandas_dtype == np.object_: - ibis_dtype = cls.infer_column(pandas_column) - else: - ibis_dtype = PandasType.to_ibis(pandas_dtype) - - pairs.append((column_name, ibis_dtype)) - - return sch.Schema.from_tuples(pairs) - - concat = staticmethod(pd.concat) - - @classmethod - def convert_table(cls, df, schema): - if len(schema) != len(df.columns): - raise ValueError( - "schema column count does not match input data column count" - ) - - columns = [] - for (_, series), dtype in zip(df.items(), schema.types): - columns.append(cls.convert_column(series, dtype)) - df = cls.concat(columns, axis=1) - - # return data with the schema's columns which may be different than the - # input columns - df.columns = schema.names - - if geospatial_supported: - from geopandas import GeoDataFrame - from geopandas.array import GeometryDtype - - if ( - # pluck out the first geometry column if it exists - geom := next( - ( - name - for name, c in df.items() - if isinstance(c.dtype, GeometryDtype) - ), - None, - ) - ) is not None: - return GeoDataFrame(df, geometry=geom) - return df - - @classmethod - def convert_column(cls, obj, dtype): - pandas_type = PandasType.from_ibis(dtype) - - method_name = f"convert_{dtype.__class__.__name__}" - convert_method = getattr(cls, method_name, cls.convert_default) - - result = convert_method(obj, dtype, pandas_type) - assert not isinstance(result, np.ndarray), f"{convert_method} -> {type(result)}" - return result - - @classmethod - def convert_scalar(cls, obj, dtype): - df = PandasData.convert_table(obj, sch.Schema({obj.columns[0]: dtype})) - return df.iat[0, 0] - - @classmethod - def convert_GeoSpatial(cls, s, dtype, pandas_type): - import geopandas as gpd - - if isinstance(s.dtype, gpd.array.GeometryDtype): - return gpd.GeoSeries(s) - return gpd.GeoSeries.from_wkb(s) - - convert_Point = convert_LineString = convert_Polygon = convert_MultiLineString = ( - convert_MultiPoint - ) = convert_MultiPolygon = convert_GeoSpatial - - @classmethod - def convert_default(cls, s, dtype, pandas_type): - if s.dtype == pandas_type and dtype.is_primitive(): - return s - try: - return s.astype(pandas_type) - except Exception: # noqa: BLE001 - return s - - @classmethod - def convert_Boolean(cls, s, dtype, pandas_type): - if s.empty: - return s.astype(pandas_type) - elif pdt.is_object_dtype(s.dtype): - return s - elif s.dtype != pandas_type: - return s.map(bool, na_action="ignore") - else: - return s - - @classmethod - def convert_Timestamp(cls, s, dtype, pandas_type): - if isinstance(pandas_type, pd.DatetimeTZDtype) and isinstance( - s.dtype, pd.DatetimeTZDtype - ): - return s if s.dtype == pandas_type else s.dt.tz_convert(dtype.timezone) - elif pdt.is_datetime64_dtype(s.dtype): - return s.dt.tz_localize(dtype.timezone) - else: - try: - return s.astype(pandas_type) - except pd.errors.OutOfBoundsDatetime: # uncovered - try: - from dateutil.parser import parse as date_parse - - return s.map(date_parse, na_action="ignore") - except TypeError: - return s - except (ValueError, TypeError): - try: - return pd.to_datetime(s).dt.tz_convert(dtype.timezone) - except TypeError: - return pd.to_datetime(s).dt.tz_localize(dtype.timezone) - - @classmethod - def convert_Date(cls, s, dtype, pandas_type): - if isinstance(s.dtype, pd.DatetimeTZDtype): - s = s.dt.tz_convert("UTC").dt.tz_localize(None) - try: - return s.astype(pandas_type).dt.date - except (ValueError, TypeError, pd._libs.tslibs.OutOfBoundsDatetime): - - def try_date(v): - if isinstance(v, datetime.datetime): - return v.date() - elif isinstance(v, str): - if v.endswith("Z"): - return datetime.datetime.fromisoformat(v[:-1]).date() - return datetime.date.fromisoformat(v) - else: - return v - - return s.map(try_date, na_action="ignore") - - @classmethod - def convert_Interval(cls, s, dtype, pandas_type): - values = s.values - try: - result = values.astype(pandas_type) - except ValueError: # can happen when `column` is DateOffsets # uncovered - result = s - else: - result = s.__class__(result, index=s.index, name=s.name) - return result - - @classmethod - def convert_String(cls, s, dtype, pandas_type): - return s.astype(pandas_type, errors="ignore") - - @classmethod - def convert_Decimal(cls, s, dtype, pandas_type): - func = partial( - normalize_decimal, - precision=dtype.precision, - scale=dtype.scale, - strict=False, - ) - return s.map(func, na_action="ignore") - - @classmethod - def convert_UUID(cls, s, dtype, pandas_type): - return s.map(cls.get_element_converter(dtype), na_action="ignore") - - @classmethod - def convert_Struct(cls, s, dtype, pandas_type): - return s.map(cls.get_element_converter(dtype), na_action="ignore") - - @classmethod - def convert_Array(cls, s, dtype, pandas_type): - return s.map(cls.get_element_converter(dtype), na_action="ignore") - - @classmethod - def convert_Map(cls, s, dtype, pandas_type): - return s.map(cls.get_element_converter(dtype), na_action="ignore") - - @classmethod - def convert_JSON(cls, s, dtype, pandas_type): - return s.map(cls.get_element_converter(dtype), na_action="ignore").astype( - "object" - ) - - @classmethod - def get_element_converter(cls, dtype): - name = f"convert_{type(dtype).__name__}_element" - funcgen = getattr(cls, name, lambda _: lambda x: x) - return funcgen(dtype) - - @classmethod - def convert_Struct_element(cls, dtype): - converters = tuple(map(cls.get_element_converter, dtype.types)) - - def convert(values, names=dtype.names, converters=converters): - if values is None: - return values - - items = ( - values.items() - if isinstance(values, dict) - else zip(names, util.promote_list(values)) - ) - return { - k: converter(v) if v is not None else v - for converter, (k, v) in zip(converters, items) - } - - return convert - - @classmethod - def convert_JSON_element(cls, _): - import json - - def convert(value): - if value is None: - return value - try: - return json.loads(value) - except (TypeError, json.JSONDecodeError): - return value - - return convert - - @classmethod - def convert_Timestamp_element(cls, dtype): - def converter(value, dtype=dtype): - if value is None: - return value - - with contextlib.suppress(AttributeError): - value = value.item() - - if isinstance(value, int): - # this can only mean a numpy or pandas timestamp because they - # both support nanosecond precision - # - # when the precision is less than or equal to the value - # supported by Python datetime.dateimte a call to .item() will - # return a datetime.datetime but when the precision is higher - # than the value supported by Python the value is an integer - # - # TODO: can we do better than implicit truncation to microseconds? - import dateutil - - value = datetime.datetime.fromtimestamp(value / 1e9, dateutil.tz.UTC) - - if (tz := dtype.timezone) is not None: - return value.astimezone(normalize_timezone(tz)) - - return value.replace(tzinfo=None) - - return converter - - @classmethod - def convert_Array_element(cls, dtype): - convert_value = cls.get_element_converter(dtype.value_type) - - def convert(values): - if values is None: - return values - - return [ - convert_value(value) if value is not None else value for value in values - ] - - return convert - - @classmethod - def convert_Map_element(cls, dtype): - convert_key = cls.get_element_converter(dtype.key_type) - convert_value = cls.get_element_converter(dtype.value_type) - - def convert(raw_row): - if raw_row is None: - return raw_row - - row = dict(raw_row) - return dict( - zip(map(convert_key, row.keys()), map(convert_value, row.values())) - ) - - return convert - - @classmethod - def convert_UUID_element(cls, _): - from uuid import UUID - - def convert(value): - if value is None: - return value - elif isinstance(value, UUID): - return value - return UUID(value) - - return convert - - -class PandasDataFrameProxy(TableProxy[pd.DataFrame]): - def to_frame(self) -> pd.DataFrame: - return self.obj - - def to_pyarrow(self, schema: sch.Schema) -> pa.Table: - import pyarrow as pa - import pyarrow_hotfix # noqa: F401 - - pyarrow_schema = PyArrowSchema.from_ibis(schema) - return pa.Table.from_pandas(self.obj, schema=pyarrow_schema) - - def to_polars(self, schema: sch.Schema) -> pl.DataFrame: - import polars as pl - from bigframes_vendored.ibis.formats.polars import PolarsSchema - - pl_schema = PolarsSchema.from_ibis(schema) - return pl.from_pandas(self.obj, schema_overrides=pl_schema) diff --git a/third_party/bigframes_vendored/ibis/formats/polars.py b/third_party/bigframes_vendored/ibis/formats/polars.py deleted file mode 100644 index 1c0b38ee804..00000000000 --- a/third_party/bigframes_vendored/ibis/formats/polars.py +++ /dev/null @@ -1,194 +0,0 @@ -# Contains code from https://github.com/ibis-project/ibis/blob/9.2.0/ibis/formats/polars.py - -from __future__ import annotations - -from typing import TYPE_CHECKING, Any - -import bigframes_vendored.ibis.expr.datatypes as dt -import polars as pl -from bigframes_vendored.ibis.expr.schema import Schema -from bigframes_vendored.ibis.formats import ( - DataMapper, - SchemaMapper, - TableProxy, - TypeMapper, -) - -if TYPE_CHECKING: - from collections.abc import Sequence - - import pandas as pd - import pyarrow as pa - - -_to_polars_types = { - dt.Boolean: pl.Boolean, - dt.Null: pl.Null, - dt.String: pl.Utf8, - dt.Binary: pl.Binary, - dt.Date: pl.Date, - dt.Time: pl.Time, - dt.Int8: pl.Int8, - dt.Int16: pl.Int16, - dt.Int32: pl.Int32, - dt.Int64: pl.Int64, - dt.UInt8: pl.UInt8, - dt.UInt16: pl.UInt16, - dt.UInt32: pl.UInt32, - dt.UInt64: pl.UInt64, - dt.Float32: pl.Float32, - dt.Float64: pl.Float64, -} - -_from_polars_types = {v: k for k, v in _to_polars_types.items()} - - -class PolarsType(TypeMapper): - @classmethod - def to_ibis(cls, typ: pl.DataType, nullable=True) -> dt.DataType: - """Convert a polars type to an ibis type.""" - - base_type = typ.base_type() - if base_type is pl.Categorical: - return dt.String(nullable=nullable) - elif base_type is pl.Decimal: - return dt.Decimal( - precision=typ.precision, scale=typ.scale, nullable=nullable - ) - elif base_type is pl.Datetime: - try: - timezone = typ.time_zone - except AttributeError: # pragma: no cover - timezone = typ.tz # pragma: no cover - - # this will raise on polars for seconds "s" unit as it's not supported - return dt.Timestamp.from_unit( - unit=typ.time_unit, timezone=timezone, nullable=nullable - ) - - elif base_type is pl.Duration: - try: - time_unit = typ.time_unit - except AttributeError: # pragma: no cover - time_unit = typ.tu # pragma: no cover - return dt.Interval(unit=time_unit, nullable=nullable) - elif base_type is pl.List: - return dt.Array(cls.to_ibis(typ.inner), nullable=nullable) - elif base_type is pl.Struct: - return dt.Struct.from_tuples( - [(field.name, cls.to_ibis(field.dtype)) for field in typ.fields], - nullable=nullable, - ) - else: - return _from_polars_types[base_type](nullable=nullable) - - @classmethod - def from_ibis(cls, dtype: dt.DataType) -> pl.DataType: - """Convert an ibis type to a polars type.""" - if dtype.is_decimal(): - return pl.Decimal( - precision=dtype.precision, - scale=9 if dtype.scale is None else dtype.scale, - ) - elif dtype.is_timestamp(): - unit = dtype.unit.short - if unit in {"us", "ns", "ms"}: - return pl.Datetime(unit, dtype.timezone) - else: - # this for "s", if something else is passed, it'll raise at - # the from_unit level. - return pl.Datetime("ns", dtype.timezone) # this was the default before - - elif dtype.is_interval(): - if dtype.unit.short in {"us", "ns", "ms"}: - return pl.Duration(dtype.unit.short) - else: - raise ValueError(f"Unsupported polars duration unit: {dtype.unit}") - elif dtype.is_struct(): - fields = [ - pl.Field(name=name, dtype=cls.from_ibis(dtype)) - for name, dtype in dtype.fields.items() - ] - return pl.Struct(fields) - elif dtype.is_array(): - return pl.List(cls.from_ibis(dtype.value_type)) - else: - try: - return _to_polars_types[type(dtype)] - except KeyError: - raise NotImplementedError( - f"Converting {dtype} to polars is not supported yet" - ) - - -class PolarsSchema(SchemaMapper): - @classmethod - def from_ibis(cls, schema: Schema) -> dict[str, pl.DataType]: - """Convert a schema to a polars schema.""" - return {name: PolarsType.from_ibis(typ) for name, typ in schema.items()} - - @classmethod - def to_ibis(cls, schema: dict[str, pl.DataType]) -> Schema: - """Convert a polars schema to a schema.""" - return Schema.from_tuples( - [(name, PolarsType.to_ibis(typ)) for name, typ in schema.items()] - ) - - -class PolarsData(DataMapper): - @classmethod - def infer_scalar(cls, scalar: Any) -> dt.DataType: - """Infer the ibis type of a scalar.""" - return PolarsType.to_ibis(pl.Series(values=[scalar]).dtype) - - @classmethod - def infer_column(cls, column: Sequence) -> dt.DataType: - """Infer the ibis type of a sequence.""" - if not isinstance(column, pl.Series): - column = pl.Series(values=column) - return PolarsType.to_ibis(column.dtype) - - @classmethod - def infer_table(cls, table) -> Schema: - """Infer the schema of a table.""" - if not isinstance(table, pl.DataFrame): - table = pl.DataFrame(table) - - return PolarsSchema.to_ibis(table.schema) - - @classmethod - def convert_scalar(cls, df: pl.DataFrame, dtype: dt.DataType) -> Any: - assert df.shape == (1, 1) - df = cls.convert_table(df, Schema({df.columns[0]: dtype})) - return df[0, 0] - - @classmethod - def convert_column(cls, df: pl.DataFrame, dtype: dt.DataType) -> pl.Series: - assert df.shape[1] == 1 - df = cls.convert_table(df, Schema({df.columns[0]: dtype})) - return df[:, 0] - - @classmethod - def convert_table(cls, df: pl.DataFrame, schema: Schema) -> pl.DataFrame: - pl_schema = PolarsSchema.from_ibis(schema) - - if tuple(df.columns) != tuple(schema.names): - df = df.rename(dict(zip(df.columns, schema.names))) - - if df.schema == pl_schema: - return df - return df.cast(pl_schema) - - -class PolarsDataFrameProxy(TableProxy[pl.DataFrame]): - def to_frame(self) -> pd.DataFrame: - return self.obj.to_pandas() - - def to_pyarrow(self, schema: Schema) -> pa.Table: - from bigframes_vendored.ibis.formats.pyarrow import PyArrowData - - table = self.obj.to_arrow() - return PyArrowData.convert_table(table, schema) - - def to_polars(self, schema: Schema) -> pl.DataFrame: - return self.obj diff --git a/third_party/bigframes_vendored/ibis/formats/pyarrow.py b/third_party/bigframes_vendored/ibis/formats/pyarrow.py deleted file mode 100644 index 5428264ee7c..00000000000 --- a/third_party/bigframes_vendored/ibis/formats/pyarrow.py +++ /dev/null @@ -1,379 +0,0 @@ -# Contains code from https://github.com/ibis-project/ibis/blob/9.2.0/ibis/formats/pyarrow.py - -from __future__ import annotations - -import functools -from typing import TYPE_CHECKING, Any - -import bigframes_vendored.ibis.expr.datatypes as dt -from bigframes_vendored.ibis.expr.schema import Schema -from bigframes_vendored.ibis.formats import ( - DataMapper, - SchemaMapper, - TableProxy, - TypeMapper, -) - -if TYPE_CHECKING: - from collections.abc import Sequence - - import polars as pl - import pyarrow as pa - - -@functools.cache -def _from_pyarrow_types(): - import pyarrow as pa - - return { - pa.int8(): dt.Int8, - pa.int16(): dt.Int16, - pa.int32(): dt.Int32, - pa.int64(): dt.Int64, - pa.uint8(): dt.UInt8, - pa.uint16(): dt.UInt16, - pa.uint32(): dt.UInt32, - pa.uint64(): dt.UInt64, - pa.float16(): dt.Float16, - pa.float32(): dt.Float32, - pa.float64(): dt.Float64, - pa.string(): dt.String, - pa.binary(): dt.Binary, - pa.bool_(): dt.Boolean, - pa.date32(): dt.Date, - pa.date64(): dt.Date, - pa.null(): dt.Null, - pa.string(): dt.String, - pa.large_binary(): dt.Binary, - pa.large_string(): dt.String, - pa.binary(): dt.Binary, - } - - -@functools.cache -def _to_pyarrow_types(): - import pyarrow as pa - import pyarrow_hotfix # noqa: F401 - - return { - dt.Null: pa.null(), - dt.Boolean: pa.bool_(), - dt.Binary: pa.binary(), - dt.Int8: pa.int8(), - dt.Int16: pa.int16(), - dt.Int32: pa.int32(), - dt.Int64: pa.int64(), - dt.UInt8: pa.uint8(), - dt.UInt16: pa.uint16(), - dt.UInt32: pa.uint32(), - dt.UInt64: pa.uint64(), - dt.Float16: pa.float16(), - dt.Float32: pa.float32(), - dt.Float64: pa.float64(), - dt.String: pa.string(), - dt.Binary: pa.binary(), - # assume unknown types can be converted into strings - dt.Unknown: pa.string(), - dt.MACADDR: pa.string(), - dt.INET: pa.string(), - dt.UUID: pa.string(), - dt.JSON: pa.string(), - } - - -class PyArrowType(TypeMapper): - @classmethod - def to_ibis(cls, typ: pa.DataType, nullable=True) -> dt.DataType: - """Convert a pyarrow type to an ibis type.""" - import pyarrow as pa - - if pa.types.is_null(typ): - return dt.null - elif pa.types.is_decimal(typ): - return dt.Decimal(typ.precision, typ.scale, nullable=nullable) - elif pa.types.is_timestamp(typ): - return dt.Timestamp.from_unit(typ.unit, timezone=typ.tz, nullable=nullable) - elif pa.types.is_time(typ): - return dt.Time(nullable=nullable) - elif pa.types.is_duration(typ): - return dt.Interval(typ.unit, nullable=nullable) - elif pa.types.is_interval(typ): - raise ValueError("Arrow interval type is not supported") - elif ( - pa.types.is_list(typ) - or pa.types.is_large_list(typ) - or pa.types.is_fixed_size_list(typ) - ): - value_dtype = cls.to_ibis(typ.value_type, typ.value_field.nullable) - return dt.Array(value_dtype, nullable=nullable) - elif pa.types.is_struct(typ): - field_dtypes = { - field.name: cls.to_ibis(field.type, field.nullable) for field in typ - } - return dt.Struct(field_dtypes, nullable=nullable) - elif pa.types.is_map(typ): - # TODO(kszucs): keys_sorted has just been exposed in pyarrow - key_dtype = cls.to_ibis(typ.key_type, typ.key_field.nullable) - value_dtype = cls.to_ibis(typ.item_type, typ.item_field.nullable) - return dt.Map(key_dtype, value_dtype, nullable=nullable) - elif pa.types.is_dictionary(typ): - return cls.to_ibis(typ.value_type) - elif ( - isinstance(typ, pa.ExtensionType) - and type(typ).__module__ == "geoarrow.types.type_pyarrow" - ): - from geoarrow import types as gat - - gat.type_pyarrow.register_extension_types() - - auth_code = None - if typ.crs is not None: - crs_dict = typ.crs.to_json_dict() - if "id" in crs_dict: - crs_id = crs_dict["id"] - if "authority" in crs_id and "code" in crs_id: - auth_code = (crs_id["authority"], crs_id["code"]) - - if typ.crs is not None and auth_code is None: - # It is possible to have PROJJSON that does not have an authority/code - # attached, either because the producer didn't have that information - # (e.g., because they were reading a older shapefile). In this case, - # pyproj can often guess the authority/code. - import pyproj - - auth_code = pyproj.CRS(typ.crs.to_json()).to_authority() - if auth_code is None: - raise ValueError(f"Can't resolve SRID of crs {typ.crs}") - - if auth_code is None: - srid = None - elif auth_code == ("OGC", "CRS84"): - # OGC:CRS84 and EPSG:4326 are identical except for the order of - # coordinates (i.e., lon lat vs. lat lon) in their official definition. - # This axis ordering is ignored in all but the most obscure scenarios - # such that these are identical. OGC:CRS84 is more correct, but EPSG:4326 - # is more common. - srid = 4326 - else: - # This works because the two most common srid authorities are EPSG and ESRI - # and the "codes" are all integers and don't intersect with each other on - # purpose. This won't scale to something like OGC:CRS27 (not common). - srid = int(auth_code[1]) - - if typ.edge_type == gat.EdgeType.SPHERICAL: - geotype = "geography" - else: - geotype = "geometry" - - return dt.GeoSpatial(geotype, srid, nullable) - else: - return _from_pyarrow_types()[typ](nullable=nullable) - - @classmethod - def from_ibis(cls, dtype: dt.DataType) -> pa.DataType: - """Convert an ibis type to a pyarrow type.""" - import pyarrow as pa - import pyarrow_hotfix # noqa: F401 - - if dtype.is_decimal(): - # set default precision and scale to something; unclear how to choose this - precision = 38 if dtype.precision is None else dtype.precision - scale = 9 if dtype.scale is None else dtype.scale - - if precision > 76: - raise ValueError( - f"Unsupported precision {dtype.precision} for decimal type" - ) - elif precision > 38: - return pa.decimal256(precision, scale) - else: - return pa.decimal128(precision, scale) - elif dtype.is_timestamp(): - return pa.timestamp( - dtype.unit.short if dtype.scale is not None else "us", tz=dtype.timezone - ) - elif dtype.is_interval(): - short = dtype.unit.short - if short in {"ns", "us", "ms", "s"}: - return pa.duration(short) - else: - return pa.month_day_nano_interval() - elif dtype.is_time(): - return pa.time64("ns") - elif dtype.is_date(): - return pa.date32() - elif dtype.is_array(): - value_field = pa.field( - "item", - cls.from_ibis(dtype.value_type), - nullable=dtype.value_type.nullable, - ) - return pa.list_(value_field) - elif dtype.is_struct(): - fields = [ - pa.field(name, cls.from_ibis(dtype), nullable=dtype.nullable) - for name, dtype in dtype.items() - ] - return pa.struct(fields) - elif dtype.is_map(): - key_field = pa.field( - "key", - cls.from_ibis(dtype.key_type), - nullable=False, # pyarrow doesn't allow nullable keys - ) - value_field = pa.field( - "value", - cls.from_ibis(dtype.value_type), - nullable=dtype.value_type.nullable, - ) - return pa.map_(key_field, value_field, keys_sorted=False) - elif dtype.is_geospatial(): - from geoarrow import types as gat - - # Resolve CRS - if dtype.srid is None: - crs = None - elif dtype.srid == 4326: - crs = gat.OGC_CRS84 - else: - import pyproj - - # Assume that these are EPSG codes. An srid is more accurately a key - # into a backend/connection-specific lookup table; however, most usage - # should work with this assumption. - crs = pyproj.CRS(f"EPSG:{dtype.srid}") - - # Resolve edge type - if dtype.geotype == "geography": - edge_type = gat.EdgeType.SPHERICAL - else: - edge_type = gat.EdgeType.PLANAR - - return gat.wkb(crs=crs, edge_type=edge_type).to_pyarrow() - else: - try: - return _to_pyarrow_types()[type(dtype)] - except KeyError: - raise NotImplementedError( - f"Converting {dtype} to pyarrow is not supported yet" - ) - - -class PyArrowSchema(SchemaMapper): - @classmethod - def from_ibis(cls, schema: Schema) -> pa.Schema: - """Convert a schema to a pyarrow schema.""" - import pyarrow as pa - import pyarrow_hotfix # noqa: F401 - - fields = [ - pa.field(name, PyArrowType.from_ibis(dtype), nullable=dtype.nullable) - for name, dtype in schema.items() - ] - return pa.schema(fields) - - @classmethod - def to_ibis(cls, schema: pa.Schema) -> Schema: - """Convert a pyarrow schema to a schema.""" - fields = [(f.name, PyArrowType.to_ibis(f.type, f.nullable)) for f in schema] - return Schema.from_tuples(fields) - - -class PyArrowData(DataMapper): - @classmethod - def infer_scalar(cls, scalar: Any) -> dt.DataType: - """Infer the ibis type of a scalar.""" - import pyarrow as pa - import pyarrow_hotfix # noqa: F401 - - return PyArrowType.to_ibis(pa.scalar(scalar).type) - - @classmethod - def infer_column(cls, column: Sequence) -> dt.DataType: - """Infer the ibis type of a sequence.""" - import pyarrow as pa - import pyarrow_hotfix # noqa: F401 - - if isinstance(column, pa.Array): - return PyArrowType.to_ibis(column.type) - - try: - pyarrow_type = pa.array(column, from_pandas=True).type - # pyarrow_type = pa.infer_type(column, from_pandas=True) - except pa.ArrowInvalid: - try: - # handle embedded series objects - return dt.highest_precedence(map(dt.infer, column)) - except TypeError: - # we can still have a type error, e.g., float64 and string in the - # same array - return dt.unknown - except pa.ArrowTypeError: - # arrow can't infer the type - return dt.unknown - else: - # arrow inferred the type, now convert that type to an ibis type - return PyArrowType.to_ibis(pyarrow_type) - - @classmethod - def infer_table(cls, table) -> Schema: - """Infer the schema of a table.""" - import pyarrow as pa - import pyarrow_hotfix # noqa: F401 - - if not isinstance(table, pa.Table): - table = pa.table(table) - - return PyArrowSchema.to_ibis(table.schema) - - @classmethod - def convert_scalar(cls, scalar: pa.Scalar, dtype: dt.DataType) -> pa.Scalar: - import pyarrow as pa - import pyarrow_hotfix # noqa: F401 - - desired_type = PyArrowType.from_ibis(dtype) - scalar_type = scalar.type - if scalar_type != desired_type: - try: - return scalar.cast(desired_type) - except pa.ArrowNotImplementedError: - # pyarrow doesn't support some scalar casts that are supported - # when using arrays or tables - return pa.array([scalar.as_py()], type=scalar_type).cast(desired_type)[ - 0 - ] - else: - return scalar - - @classmethod - def convert_column(cls, column: pa.Array, dtype: dt.DataType) -> pa.Array: - desired_type = PyArrowType.from_ibis(dtype) - if column.type != desired_type: - return column.cast(desired_type) - else: - return column - - @classmethod - def convert_table(cls, table: pa.Table, schema: Schema) -> pa.Table: - desired_schema = PyArrowSchema.from_ibis(schema) - pa_schema = table.schema - - if pa_schema != desired_schema: - return table.cast(desired_schema, safe=False) - else: - return table - - -class PyArrowTableProxy(TableProxy): - def to_frame(self): - return self.obj.to_pandas() - - def to_pyarrow(self, schema: Schema) -> pa.Table: - return self.obj - - def to_polars(self, schema: Schema) -> pl.DataFrame: - import polars as pl - from bigframes_vendored.ibis.formats.polars import PolarsData - - df = pl.from_arrow(self.obj) - return PolarsData.convert_table(df, schema) diff --git a/third_party/bigframes_vendored/ibis/selectors.py b/third_party/bigframes_vendored/ibis/selectors.py deleted file mode 100644 index 3b9f0107728..00000000000 --- a/third_party/bigframes_vendored/ibis/selectors.py +++ /dev/null @@ -1,655 +0,0 @@ -# Contains code from https://github.com/ibis-project/ibis/blob/9.2.0/ibis/selectors.py - -"""Convenient column selectors. - -::: {.callout-tip} -## Check out the [blog post on selectors](../posts/selectors) for examples! -::: - -## Rationale - -Column selectors are convenience functions for selecting columns that share some property. - -## Discussion - -For example, a common task is to be able to select all numeric columns for a -subsequent computation. - -Without selectors this becomes quite verbose and tedious to write: - ->>> import ibis ->>> t = ibis.table(dict(a="int", b="string", c="array", abcd="float")) ->>> expr = t.select([t[c] for c in t.columns if t[c].type().is_numeric()]) ->>> expr.columns -['a', 'abcd'] - -Compare that to the [`numeric`](#ibis.selectors.numeric) selector: - ->>> import ibis.selectors as s ->>> expr = t.select(s.numeric()) ->>> expr.columns -['a', 'abcd'] - -When there are multiple properties to check it gets worse: - ->>> expr = t.select( -... [ -... t[c] -... for c in t.columns -... if t[c].type().is_numeric() or t[c].type().is_string() -... if ("a" in c or "b" in c or "cd" in c) -... ] -... ) ->>> expr.columns -['a', 'b', 'abcd'] - -Using a composition of selectors this is much less tiresome: - ->>> expr = t.select((s.numeric() | s.of_type("string")) & s.contains(("a", "b", "cd"))) ->>> expr.columns -['a', 'b', 'abcd'] -""" - -from __future__ import annotations - -import functools -import inspect -import operator -import re -from collections.abc import Callable, Iterable, Mapping, Sequence -from typing import Optional, Union - -import bigframes_vendored.ibis.common.exceptions as exc -import bigframes_vendored.ibis.expr.datatypes as dt -import bigframes_vendored.ibis.expr.types as ir -from bigframes_vendored.ibis import util -from bigframes_vendored.ibis.common.collections import frozendict # noqa: TCH001 -from bigframes_vendored.ibis.common.deferred import Deferred, Resolver -from bigframes_vendored.ibis.common.exceptions import IbisError -from bigframes_vendored.ibis.common.grounds import Singleton -from bigframes_vendored.ibis.common.selectors import Selector -from public import public - - -class Predicate(Selector): - predicate: Callable[[ir.Value], bool] - - def expand(self, table: ir.Table) -> Sequence[ir.Value]: - """Evaluate `self.predicate` on every column of `table`. - - Parameters - ---------- - table - An ibis table expression - - """ - return [col for column in table.columns if self.predicate(col := table[column])] - - def __and__(self, other: Selector) -> Predicate: - """Compute the conjunction of two `Selector`s. - - Parameters - ---------- - other - Another selector - - """ - return self.__class__(lambda col: self.predicate(col) and other.predicate(col)) - - def __or__(self, other: Selector) -> Predicate: - """Compute the disjunction of two `Selector`s. - - Parameters - ---------- - other - Another selector - - """ - return self.__class__(lambda col: self.predicate(col) or other.predicate(col)) - - def __invert__(self) -> Predicate: - """Compute the logical negation of two `Selector`s.""" - return self.__class__(lambda col: not self.predicate(col)) - - -@public -def where(predicate: Callable[[ir.Value], bool]) -> Predicate: - """Select columns that satisfy `predicate`. - - Use this selector when one of the other selectors does not meet your needs. - - Parameters - ---------- - predicate - A callable that accepts an ibis value expression and returns a `bool` - - Examples - -------- - >>> import ibis - >>> import ibis.selectors as s - >>> t = ibis.table(dict(a="float32"), name="t") - >>> expr = t.select(s.where(lambda col: col.get_name() == "a")) - >>> expr.columns - ['a'] - - """ - return Predicate(predicate=predicate) - - -@public -def numeric() -> Predicate: - """Return numeric columns. - - Examples - -------- - >>> import ibis - >>> import ibis.selectors as s - >>> t = ibis.table(dict(a="int", b="string", c="array"), name="t") - >>> t.columns - ['a', 'b', 'c'] - >>> expr = t.select(s.numeric()) # `a` has integer type, so it's numeric - >>> expr.columns - ['a'] - - See Also - -------- - [`of_type`](#ibis.selectors.of_type) - - """ - return of_type(dt.Numeric) - - -@public -def of_type(dtype: dt.DataType | str | type[dt.DataType]) -> Predicate: - """Select columns of type `dtype`. - - Parameters - ---------- - dtype - `DataType` instance, `str` or `DataType` class - - Examples - -------- - Select according to a specific `DataType` instance - - >>> import ibis - >>> import ibis.expr.datatypes as dt - >>> import ibis.selectors as s - >>> t = ibis.table(dict(name="string", siblings="array", parents="array")) - >>> expr = t.select(s.of_type(dt.Array(dt.string))) - >>> expr.columns - ['siblings'] - - Strings are also accepted - - >>> expr = t.select(s.of_type("array")) - >>> expr.columns - ['siblings'] - - Abstract/unparametrized types may also be specified by their string name - (e.g. "integer" for any integer type), or by passing in a `DataType` class - instead. The following options are equivalent. - - >>> expr1 = t.select(s.of_type("array")) - >>> expr2 = t.select(s.of_type(dt.Array)) - >>> expr1.equals(expr2) - True - >>> expr2.columns - ['siblings', 'parents'] - - See Also - -------- - [`numeric`](#ibis.selectors.numeric) - - """ - if isinstance(dtype, str): - # A mapping of abstract or parametric types, to allow selecting all - # subclasses/parametrizations of these types, rather than only a - # specific instance. - abstract = { - "array": dt.Array, - "decimal": dt.Decimal, - "floating": dt.Floating, - "geospatial": dt.GeoSpatial, - "integer": dt.Integer, - "map": dt.Map, - "numeric": dt.Numeric, - "struct": dt.Struct, - "temporal": dt.Temporal, - } - if cls := abstract.get(dtype.lower()): - predicate = lambda col: isinstance(col.type(), cls) # noqa: E731 - else: - dtype = dt.dtype(dtype) - predicate = lambda col: col.type() == dtype # noqa: E731 - elif inspect.isclass(dtype) and issubclass(dtype, dt.DataType): - predicate = lambda col: isinstance(col.type(), dtype) # noqa: E731 - else: - dtype = dt.dtype(dtype) - predicate = lambda col: col.type() == dtype # noqa: E731 - return where(predicate) - - -@public -def startswith(prefixes: str | tuple[str, ...]) -> Predicate: - """Select columns whose name starts with one of `prefixes`. - - Parameters - ---------- - prefixes - Prefixes to compare column names against - - Examples - -------- - >>> import ibis - >>> import ibis.selectors as s - >>> t = ibis.table(dict(apples="int", oranges="float", bananas="bool"), name="t") - >>> expr = t.select(s.startswith(("a", "b"))) - >>> expr.columns - ['apples', 'bananas'] - - See Also - -------- - [`endswith`](#ibis.selectors.endswith) - - """ - return where(lambda col: col.get_name().startswith(prefixes)) - - -@public -def endswith(suffixes: str | tuple[str, ...]) -> Predicate: - """Select columns whose name ends with one of `suffixes`. - - Parameters - ---------- - suffixes - Suffixes to compare column names against - - See Also - -------- - [`startswith`](#ibis.selectors.startswith) - - """ - return where(lambda col: col.get_name().endswith(suffixes)) - - -@public -def contains( - needles: str | tuple[str, ...], how: Callable[[Iterable[bool]], bool] = any -) -> Predicate: - """Return columns whose name contains `needles`. - - Parameters - ---------- - needles - One or more strings to search for in column names - how - A boolean reduction to allow the configuration of how `needles` are summarized. - - Examples - -------- - Select columns that contain either `"a"` or `"b"` - - >>> import ibis - >>> import ibis.selectors as s - >>> t = ibis.table( - ... dict(a="int64", b="string", c="float", d="array", ab="struct") - ... ) - >>> expr = t.select(s.contains(("a", "b"))) - >>> expr.columns - ['a', 'b', 'ab'] - - Select columns that contain all of `"a"` and `"b"`, that is, both `"a"` and - `"b"` must be in each column's name to match. - - >>> expr = t.select(s.contains(("a", "b"), how=all)) - >>> expr.columns - ['ab'] - - See Also - -------- - [`matches`](#ibis.selectors.matches) - - """ - - def predicate(col: ir.Value) -> bool: - name = col.get_name() - return how(needle in name for needle in util.promote_list(needles)) - - return where(predicate) - - -@public -def matches(regex: str | re.Pattern) -> Selector: - """Return columns whose name matches the regular expression `regex`. - - Parameters - ---------- - regex - A string or `re.Pattern` object - - Examples - -------- - >>> import ibis - >>> import ibis.selectors as s - >>> t = ibis.table(dict(ab="string", abd="int", be="array")) - >>> expr = t.select(s.matches(r"ab+")) - >>> expr.columns - ['ab', 'abd'] - - See Also - -------- - [`contains`](#ibis.selectors.contains) - - """ - pattern = re.compile(regex) - return where(lambda col: pattern.search(col.get_name()) is not None) - - -@public -def any_of(*predicates: str | Predicate) -> Predicate: - """Include columns satisfying any of `predicates`.""" - return functools.reduce(operator.or_, map(_to_selector, predicates)) - - -@public -def all_of(*predicates: str | Predicate) -> Predicate: - """Include columns satisfying all of `predicates`.""" - return functools.reduce(operator.and_, map(_to_selector, predicates)) - - -@public -def c(*names: str | ir.Column) -> Predicate: - """Select specific column names.""" - names = frozenset(col if isinstance(col, str) else col.get_name() for col in names) - - def func(col: ir.Value) -> bool: - schema = col.op().rel.schema - if extra_cols := (names - schema.keys()): - raise exc.IbisInputError( - f"Columns {extra_cols} are not present in {schema.names}" - ) - return col.get_name() in names - - return where(func) - - -class Across(Selector): - selector: Selector - funcs: Union[ - Resolver, - Callable[[ir.Value], ir.Value], - frozendict[Optional[str], Union[Resolver, Callable[[ir.Value], ir.Value]]], - ] - names: Union[str, Callable[[str, Optional[str]], str]] - - def expand(self, table: ir.Table) -> Sequence[ir.Value]: - expanded = [] - - names = self.names - cols = self.selector.expand(table) - for func_name, func in self.funcs.items(): - for orig_col in cols: - if isinstance(func, Resolver): - col = func.resolve({"_": orig_col}) - else: - col = func(orig_col) - - if callable(names): - name = names(orig_col.get_name(), func_name) - else: - name = names.format(col=orig_col.get_name(), fn=func_name) - - expanded.append(col.name(name)) - - return expanded - - -@public -def across( - selector: Selector | Iterable[str] | str, - func: Deferred - | Callable[[ir.Value], ir.Value] - | Mapping[str | None, Deferred | Callable[[ir.Value], ir.Value]], - names: str | Callable[[str, str | None], str] | None = None, -) -> Across: - """Apply data transformations across multiple columns. - - Parameters - ---------- - selector - An expression that selects columns on which the transformation function - will be applied, an iterable of `str` column names or a single `str` - column name. - func - A function (or dictionary of functions) to use to transform the data. - names - A lambda function or a format string to name the columns created by the - transformation function. - - Returns - ------- - Across - An `Across` selector object - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> from ibis import _, selectors as s - >>> t = ibis.examples.penguins.fetch() - >>> t.select(s.startswith("bill")).mutate( - ... s.across(s.numeric(), dict(centered=_ - _.mean()), names="{fn}_{col}") - ... ) - ┏━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━┓ - ┃ bill_length_mm ┃ bill_depth_mm ┃ centered_bill_length_mm ┃ … ┃ - ┡━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━┩ - │ float64 │ float64 │ float64 │ … │ - ├────────────────┼───────────────┼─────────────────────────┼───┤ - │ 39.1 │ 18.7 │ -4.82193 │ … │ - │ 39.5 │ 17.4 │ -4.42193 │ … │ - │ 40.3 │ 18.0 │ -3.62193 │ … │ - │ NULL │ NULL │ NULL │ … │ - │ 36.7 │ 19.3 │ -7.22193 │ … │ - │ 39.3 │ 20.6 │ -4.62193 │ … │ - │ 38.9 │ 17.8 │ -5.02193 │ … │ - │ 39.2 │ 19.6 │ -4.72193 │ … │ - │ 34.1 │ 18.1 │ -9.82193 │ … │ - │ 42.0 │ 20.2 │ -1.92193 │ … │ - │ … │ … │ … │ … │ - └────────────────┴───────────────┴─────────────────────────┴───┘ - - """ - if names is None: - names = lambda col, fn: "_".join(filter(None, (col, fn))) # noqa: E731 - funcs = dict(func if isinstance(func, Mapping) else {None: func}) - if not isinstance(selector, Selector): - selector = c(*util.promote_list(selector)) - return Across(selector=selector, funcs=funcs, names=names) - - -class IfAnyAll(Selector): - selector: Selector - predicate: Union[Resolver, Callable[[ir.Value], ir.BooleanValue]] - summarizer: Callable[[ir.BooleanValue, ir.BooleanValue], ir.BooleanValue] - - def expand(self, table: ir.Table) -> Sequence[ir.Value]: - func = self.predicate - if isinstance(func, Resolver): - elems = (func.resolve({"_": col}) for col in self.selector.expand(table)) - else: - elems = (func(col) for col in self.selector.expand(table)) - - return [functools.reduce(self.summarizer, elems)] - - -@public -def if_any(selector: Selector, predicate: Deferred | Callable) -> IfAnyAll: - """Return the **disjunction** of `predicate` applied on all `selector` columns. - - Parameters - ---------- - selector - A column selector - predicate - A callable or deferred object defining a predicate to apply to each - column from `selector`. - - Examples - -------- - >>> import ibis - >>> from ibis import selectors as s, _ - >>> ibis.options.interactive = True - >>> penguins = ibis.examples.penguins.fetch() - >>> cols = s.across(s.endswith("_mm"), (_ - _.mean()) / _.std()) - >>> expr = penguins.mutate(cols).filter(s.if_any(s.endswith("_mm"), _.abs() > 2)) - >>> expr_by_hand = penguins.mutate(cols).filter( - ... (_.bill_length_mm.abs() > 2) - ... | (_.bill_depth_mm.abs() > 2) - ... | (_.flipper_length_mm.abs() > 2) - ... ) - >>> expr.equals(expr_by_hand) - True - >>> expr - ┏━━━━━━━━━┳━━━━━━━━┳━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━┳━━━┓ - ┃ species ┃ island ┃ bill_length_mm ┃ bill_depth_mm ┃ flipper_length_mm ┃ … ┃ - ┡━━━━━━━━━╇━━━━━━━━╇━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━╇━━━┩ - │ string │ string │ float64 │ float64 │ float64 │ … │ - ├─────────┼────────┼────────────────┼───────────────┼───────────────────┼───┤ - │ Adelie │ Biscoe │ -1.103002 │ 0.733662 │ -2.056307 │ … │ - │ Gentoo │ Biscoe │ 1.113285 │ -0.431017 │ 2.068368 │ … │ - │ Gentoo │ Biscoe │ 2.871660 │ -0.076550 │ 2.068368 │ … │ - │ Gentoo │ Biscoe │ 1.900890 │ -0.734846 │ 2.139483 │ … │ - │ Gentoo │ Biscoe │ 1.076652 │ -0.177826 │ 2.068368 │ … │ - │ Gentoo │ Biscoe │ 0.856855 │ -0.582932 │ 2.068368 │ … │ - │ Gentoo │ Biscoe │ 1.497929 │ -0.076550 │ 2.068368 │ … │ - │ Gentoo │ Biscoe │ 1.388031 │ -0.431017 │ 2.068368 │ … │ - │ Gentoo │ Biscoe │ 2.047422 │ -0.582932 │ 2.068368 │ … │ - │ Adelie │ Dream │ -2.165354 │ -0.836123 │ -0.918466 │ … │ - │ … │ … │ … │ … │ … │ … │ - └─────────┴────────┴────────────────┴───────────────┴───────────────────┴───┘ - - """ - return IfAnyAll(selector=selector, predicate=predicate, summarizer=operator.or_) - - -@public -def if_all(selector: Selector, predicate: Deferred | Callable) -> IfAnyAll: - """Return the **conjunction** of `predicate` applied on all `selector` columns. - - Parameters - ---------- - selector - A column selector - predicate - A callable or deferred object defining a predicate to apply to each - column from `selector`. - - Examples - -------- - >>> import ibis - >>> from ibis import selectors as s, _ - >>> ibis.options.interactive = True - >>> penguins = ibis.examples.penguins.fetch() - >>> cols = s.across(s.endswith("_mm"), (_ - _.mean()) / _.std()) - >>> expr = penguins.mutate(cols).filter(s.if_all(s.endswith("_mm"), _.abs() > 1)) - >>> expr_by_hand = penguins.mutate(cols).filter( - ... (_.bill_length_mm.abs() > 1) - ... & (_.bill_depth_mm.abs() > 1) - ... & (_.flipper_length_mm.abs() > 1) - ... ) - >>> expr.equals(expr_by_hand) - True - >>> expr - ┏━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━┳━━━┓ - ┃ species ┃ island ┃ bill_length_mm ┃ bill_depth_mm ┃ flipper_length_mm ┃ … ┃ - ┡━━━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━╇━━━┩ - │ string │ string │ float64 │ float64 │ float64 │ … │ - ├─────────┼───────────┼────────────────┼───────────────┼───────────────────┼───┤ - │ Adelie │ Dream │ -1.157951 │ 1.088129 │ -1.416272 │ … │ - │ Adelie │ Torgersen │ -1.231217 │ 1.138768 │ -1.202926 │ … │ - │ Gentoo │ Biscoe │ 1.149917 │ -1.443781 │ 1.214987 │ … │ - │ Gentoo │ Biscoe │ 1.040019 │ -1.089314 │ 1.072757 │ … │ - │ Gentoo │ Biscoe │ 1.131601 │ -1.089314 │ 1.712792 │ … │ - │ Gentoo │ Biscoe │ 1.241499 │ -1.089314 │ 1.570562 │ … │ - │ Gentoo │ Biscoe │ 1.351398 │ -1.494420 │ 1.214987 │ … │ - └─────────┴───────────┴────────────────┴───────────────┴───────────────────┴───┘ - - """ - return IfAnyAll(selector=selector, predicate=predicate, summarizer=operator.and_) - - -class Sliceable(Singleton): - def __getitem__(self, key: str | int | slice | Iterable[int | str]) -> Predicate: - def pred(col: ir.Value) -> bool: - try: - (table,) = col.op().relations - except ValueError: - raise IbisError("Column should depend on exactly one table") - - schema = table.schema - idxs = schema._name_locs - num_names = len(schema) - colname = col.get_name() - colidx = idxs[colname] - - if isinstance(key, str): - return key == colname - elif isinstance(key, int): - return key % num_names == colidx - elif util.is_iterable(key): - return any( - (isinstance(el, int) and el % num_names == colidx) - or (isinstance(el, str) and el == colname) - for el in key - ) - else: - start = key.start or 0 - stop = key.stop or num_names - step = key.step or 1 - - if isinstance(start, str): - start = idxs[start] - - if isinstance(stop, str): - stop = idxs[stop] + 1 - - return colidx in range(start, stop, step) - - return where(pred) - - -r = Sliceable() -"""Ranges of columns.""" - - -@public -def first() -> Predicate: - """Return the first column of a table.""" - return r[0] - - -@public -def last() -> Predicate: - """Return the last column of a table.""" - return r[-1] - - -@public -def all() -> Predicate: - """Return every column from a table.""" - return r[:] - - -def _to_selector( - obj: str | Selector | ir.Column | Sequence[str | Selector | ir.Column], -) -> Selector: - """Convert an object to a `Selector`.""" - if isinstance(obj, Selector): - return obj - elif isinstance(obj, ir.Column): - return c(obj.get_name()) - elif isinstance(obj, str): - return c(obj) - else: - return any_of(*obj) diff --git a/third_party/bigframes_vendored/ibis/util.py b/third_party/bigframes_vendored/ibis/util.py deleted file mode 100644 index 7da2a7afff8..00000000000 --- a/third_party/bigframes_vendored/ibis/util.py +++ /dev/null @@ -1,699 +0,0 @@ -# Contains code from https://github.com/ibis-project/ibis/blob/9.2.0/ibis/util.py - -"""Ibis utility functions.""" - -from __future__ import annotations - -import base64 -import collections -import collections.abc -import functools -import importlib.metadata -import itertools -import operator -import os -import sys -import textwrap -import types -import uuid -import warnings -from types import ModuleType -from typing import TYPE_CHECKING, Any, Generic, TypeVar -from uuid import uuid4 - -import toolz -from bigframes_vendored.ibis.common.typing import Coercible - -if TYPE_CHECKING: - from collections.abc import Callable, Iterator, Sequence - from numbers import Real - from pathlib import Path - - import bigframes_vendored.ibis.expr.types as ir - -T = TypeVar("T", covariant=True) -U = TypeVar("U", covariant=True) -K = TypeVar("K") -V = TypeVar("V") - - -# https://www.compart.com/en/unicode/U+22EE -VERTICAL_ELLIPSIS = "\u22ee" -# https://www.compart.com/en/unicode/U+2026 -HORIZONTAL_ELLIPSIS = "\u2026" - - -def guid() -> str: - """Return a uuid4 hexadecimal value.""" - return uuid4().hex - - -def indent(text: str, spaces: int) -> str: - """Apply an indentation using the given spaces into the given text. - - Parameters - ---------- - text - Text to indent - spaces - Number of leading spaces per line - - Returns - ------- - str - Indented text - - """ - prefix = " " * spaces - return textwrap.indent(text, prefix=prefix) - - -def is_one_of(values: Sequence[T], t: type[U]) -> Iterator[bool]: - """Check if the type of each value is the same of the given type. - - Parameters - ---------- - values - Input values - t - Type to check against - - Returns - ------- - tuple - - """ - return (isinstance(x, t) for x in values) - - -any_of = toolz.compose(any, is_one_of) -all_of = toolz.compose(all, is_one_of) - - -def promote_list(val: V | Sequence[V]) -> list[V]: - """Ensure that the value is a list. - - Parameters - ---------- - val - Value to promote - - Returns - ------- - list - - """ - if isinstance(val, list): - return val - elif isinstance(val, dict): - return [val] - elif is_iterable(val): - return list(val) - elif val is None: - return [] - else: - return [val] - - -def promote_tuple(val: V | Sequence[V]) -> tuple[V]: - """Ensure that the value is a tuple. - - Parameters - ---------- - val - Value to promote - - Returns - ------- - tuple - - """ - if isinstance(val, tuple): - return val - elif is_iterable(val): - return tuple(val) - elif val is None: - return () - else: - return (val,) - - -def is_function(v: Any) -> bool: - """Check if the given object is a function. - - Returns - ------- - bool - Whether `v` is a function - - """ - return isinstance(v, (types.FunctionType, types.LambdaType)) - - -def log(msg: str) -> None: - """Log `msg` using ``options.verbose_log`` if set, otherwise ``print``.""" - from bigframes_vendored.ibis.config import options - - if options.verbose: - (options.verbose_log or print)(msg) - - -def approx_equal(a: Real, b: Real, eps: Real): - """Return whether the difference between `a` and `b` is less than `eps`. - - Raises - ------ - AssertionError - - """ - assert abs(a - b) < eps - - -def safe_index(elements: Sequence[int], value: int) -> int: - """Find the location of `value` in `elements`. - - Return -1 if `value` is not found instead of raising ``ValueError``. - - Parameters - ---------- - elements - Elements to index into - value : int - Index of the given sequence/elements - - Returns - ------- - int - - Examples - -------- - >>> sequence = [1, 2, 3] - >>> safe_index(sequence, 2) - 1 - >>> safe_index(sequence, 4) - -1 - - """ - try: - return elements.index(value) - except ValueError: - return -1 - - -def is_iterable(o: Any) -> bool: - """Return whether `o` is iterable and not a :class:`str` or :class:`bytes`. - - Parameters - ---------- - o : object - Any python object - - Returns - ------- - bool - - Examples - -------- - >>> is_iterable("1") - False - >>> is_iterable(b"1") - False - >>> is_iterable(iter("1")) - True - >>> is_iterable(i for i in range(1)) - True - >>> is_iterable(1) - False - >>> is_iterable([]) - True - - """ - if isinstance(o, (str, bytes)): - return False - - try: - iter(o) - except TypeError: - return False - else: - return True - - -def convert_unit(value, unit, to, floor: bool = True): - """Convert a value between different units. - - Convert `value`, is assumed to be in units of `unit`, to units of `to`. - If `floor` is true, then use floor division on `value` if necessary. - - Parameters - ---------- - value - Number or numeric ibis expression - unit - Unit of `value` - to - Unit to convert to - floor - Whether or not to use floor division on `value` if necessary. - - Returns - ------- - Union[numbers.Integral, ibis.expr.types.NumericValue] - Integer converted unit - - Examples - -------- - >>> one_second = 1000 - >>> x = convert_unit(one_second, "ms", "s") - >>> x - 1 - >>> one_second = 1 - >>> x = convert_unit(one_second, "s", "ms") - >>> x - 1000 - >>> x = convert_unit(one_second, "s", "s") - >>> x - 1 - >>> x = convert_unit(one_second, "s", "M") - Traceback (most recent call last): - ... - ValueError: Cannot convert to or from unit ... to unit ... - - """ - # Don't do anything if from and to units are equivalent - if unit == to: - return value - - units = ("W", "D", "h", "m", "s", "ms", "us", "ns") - factors = (7, 24, 60, 60, 1000, 1000, 1000) - - monthly_units = ("Y", "Q", "M") - monthly_factors = (4, 3) - - try: - i, j = units.index(unit), units.index(to) - except ValueError: - try: - i, j = monthly_units.index(unit), monthly_units.index(to) - factors = monthly_factors - except ValueError: - raise ValueError( - f"Cannot convert interval value from unit {unit} to unit {to}" - ) - - factor = functools.reduce(operator.mul, factors[min(i, j) : max(i, j)], 1) - assert factor > 1 - - if i < j: - op = operator.mul - else: - assert i > j - op = operator.floordiv if floor else operator.truediv - try: - return op(value.to_expr(), factor).op() - except AttributeError: - return op(value, factor) - - -# taken from the itertools documentation -def consume(iterator: Iterator[T], n: int | None = None) -> None: - """Advance `iterator` n-steps ahead. If `n` is `None`, consume entirely.""" - # Use functions that consume iterators at C speed. - if n is None: - # feed the entire iterator into a zero-length deque - collections.deque(iterator, maxlen=0) - else: - # advance to the empty slice starting at position n - next(itertools.islice(iterator, n, n), None) - - -def flatten_iterable(iterable): - """Recursively flatten the iterable `iterable`.""" - if not is_iterable(iterable): - raise TypeError("flatten is only defined for non-str iterables") - - for item in iterable: - if is_iterable(item): - yield from flatten_iterable(item) - else: - yield item - - -def deprecated_msg(name, *, instead, as_of="", removed_in=""): - msg = f"`{name}` is deprecated" - - msgs = [] - - if as_of: - msgs.append(f"as of v{as_of}") - - if removed_in: - msgs.append(f"removed in v{removed_in}") - - if msgs: - msg += f" {', '.join(msgs)}" - msg += f"; {instead}" - return msg - - -def warn_deprecated(name, *, instead, as_of="", removed_in="", stacklevel=1): - """Warn about deprecated usage. - - The message includes a stacktrace and what to do instead. - """ - - msg = deprecated_msg(name, instead=instead, as_of=as_of, removed_in=removed_in) - warnings.warn(msg, FutureWarning, stacklevel=stacklevel + 1) - - -def append_admonition( - func: Callable, *, msg: str, body: str = "", kind: str = "warning" -) -> str: - """Append a `kind` admonition with `msg` to `func`'s docstring.""" - if docstr := func.__doc__: - preamble, *rest = docstr.split("\n\n", maxsplit=1) - - # count leading spaces and add them to the deprecation warning so the - # docstring parses correctly - leading_spaces = " " * sum( - 1 for _ in itertools.takewhile(str.isspace, rest[0] if rest else []) - ) - - lines = [f"::: {{.callout-{kind}}}", f"## {msg}", ":::"] - admonition_doc = textwrap.indent("\n".join(lines), leading_spaces) - - if body: - rest = [indent(body, spaces=len(leading_spaces) + 4), *rest] - - docstr = "\n\n".join([preamble, admonition_doc, *rest]) - else: - lines = [f"::: {{.callout-{kind}}}", f"## {msg}", ":::"] - admonition_doc = "\n".join(lines) - if body: - admonition_doc += f"\n\n{indent(body, spaces=4)}" - docstr = admonition_doc - return docstr - - -def deprecated(*, instead: str, as_of: str = "", removed_in: str = ""): - """Decorate to warn of deprecated usage and what to do instead.""" - - def decorator(func): - msg = deprecated_msg( - func.__qualname__, instead=instead, as_of=as_of, removed_in=removed_in - ) - - func.__doc__ = append_admonition(func, msg=f"DEPRECATED: {msg}") - - @functools.wraps(func) - def wrapper(*args, **kwargs): - warn_deprecated( - func.__qualname__, - instead=instead, - as_of=as_of, - removed_in=removed_in, - stacklevel=2, - ) - return func(*args, **kwargs) - - return wrapper - - return decorator - - -def backend_sensitive( - *, - msg: str = "This operation differs between backends.", - why: str = "", -): - """Indicate that an API may be sensitive to a backend.""" - - def wrapper(func): - func.__doc__ = append_admonition(func, msg=msg, body=why, kind="info") - return func - - return wrapper - - -def experimental(func): - """Decorate a callable to add warning about API instability in docstring.""" - - func.__doc__ = append_admonition( - func, msg="This API is experimental and subject to change." - ) - return func - - -_common_package_aliases = { - "pa": "pyarrow", - "pd": "pandas", - "np": "numpy", - "sk": "sklearn", - "sp": "scipy", - "tf": "tensorflow", -} - - -def unalias_package(name: str) -> str: - return _common_package_aliases.get(name, name) - - -def import_object(qualname: str) -> Any: - """Attempt to import an object given its full qualname. - - Examples - -------- - >>> ex = import_object("ibis.examples") - - Is the same as - - >>> from ibis import examples as ex - - """ - mod_name, name = qualname.rsplit(".", 1) - mod = importlib.import_module(mod_name) - try: - return getattr(mod, name) - except AttributeError: - raise ImportError(f"cannot import name {name!r} from {mod_name!r}") from None - - -def normalize_filename(source: str | Path) -> str: - source = str(source) - for prefix in ( - "parquet", - "csv", - "csv.gz", - "txt", - "txt.gz", - "tsv", - "tsv.gz", - "file", - ): - source = source.removeprefix(f"{prefix}://") - - def _absolufy_paths(name): - if not name.startswith( - ("http", "s3", "az", "abfs", "abfss", "adl", "gs", "gcs", "azure") - ): - return os.path.abspath(name) - return name - - source = _absolufy_paths(source) - return source - - -def normalize_filenames(source_list): - # Promote to list - source_list = promote_list(source_list) - - return list(map(normalize_filename, source_list)) - - -def gen_name(namespace: str) -> str: - """Create a unique identifier.""" - uid = base64.b32encode(uuid.uuid4().bytes).decode().rstrip("=").lower() - return f"ibis_{namespace}_{uid}" - - -def slice_to_limit_offset( - what: slice, count: ir.IntegerScalar -) -> tuple[int | ir.IntegerScalar, int | ir.IntegerScalar]: - """Convert a Python [`slice`](slice) to a `limit`, `offset` pair. - - Parameters - ---------- - what - The slice to convert - count - The total number of rows in the table as an expression - - Returns - ------- - tuple[int | ir.IntegerScalar, int | ir.IntegerScalar] - The offset and limit to use in a `Table.limit` call - - Examples - -------- - >>> import ibis - >>> t = ibis.table(dict(a="int", b="string"), name="t") - - First 10 rows - >>> count = t.count() - >>> what = slice(0, 10) - >>> limit, offset = slice_to_limit_offset(what, count) - >>> limit - 10 - >>> offset - 0 - - Last 10 rows - >>> what = slice(-10, None) - >>> limit, offset = slice_to_limit_offset(what, count) - >>> limit - 10 - >>> offset - r0 := UnboundTable: t - a int64 - b string - - Add(CountStar(t), -10): CountStar(r0) + -10 - - From 5th row to 10th row - >>> what = slice(5, 10) - >>> limit, offset = slice_to_limit_offset(what, count) - >>> limit, offset - (5, 5) - - """ - if (step := what.step) is not None and step != 1: - raise ValueError("Slice step can only be 1") - - import bigframes_vendored.ibis.expr.api as ibis_api - - start = what.start - stop = what.stop - - if start is None or start >= 0: - offset = start or 0 - - if stop is None: - limit = None - elif stop == 0: - limit = 0 - elif stop < 0: - limit = count + (stop - offset) - else: # stop > 0 - limit = max(stop - offset, 0) - else: # start < 0 - offset = count + start - - if stop is None: - limit = -start - elif stop == 0: - limit = offset = 0 - elif stop < 0: - limit = max(stop - start, 0) - if limit == 0: - offset = 0 - else: # stop > 0 - limit = ibis_api.greatest((stop - start) - count, 0) - return limit, offset - - -class Namespace: - """Convenience class for creating patterns for various types from a module. - - Useful to reduce boilerplate when creating patterns for various types from - a module. - - Parameters - ---------- - factory - The pattern to construct with the looked up types. - module - The module object or name to look up the types. - - """ - - __slots__ = ("_factory", "_module") - _factory: Callable - _module: ModuleType - - def __init__(self, factory, module): - if isinstance(module, str): - module = sys.modules[module] - self._module = module - self._factory = factory - - def __getattr__(self, name: str): - obj = getattr(self._module, name) - return self._factory(obj) - - -class PseudoHashable(Coercible, Generic[V]): - """A wrapper that provides a best effort precomputed hash.""" - - __slots__ = ("obj", "hash") - obj: V - - def __init__(self, obj: V): - if isinstance(obj, collections.abc.Hashable): - raise TypeError(f"Cannot wrap a hashable object: {obj!r}") - elif isinstance(obj, collections.abc.Sequence): - hashable_obj = tuple(obj) - elif isinstance(obj, collections.abc.Mapping): - hashable_obj = tuple(obj.items()) - elif isinstance(obj, collections.abc.Set): - hashable_obj = frozenset(obj) - else: - hashable_obj = id(obj) - - self.obj = obj - self.hash = hash((type(obj), hashable_obj)) - - @classmethod - def __coerce__(cls, value: V) -> PseudoHashable[V]: - if isinstance(value, cls): - return value - return cls(value) - - def __hash__(self): - return self.hash - - def __eq__(self, other): - if isinstance(other, PseudoHashable): - return self.obj == other.obj - else: - return NotImplemented - - def __ne__(self, other): - if isinstance(other, PseudoHashable): - return self.obj != other.obj - else: - return NotImplemented - - -def chunks(n: int, *, chunk_size: int) -> Iterator[tuple[int, int]]: - """Return an iterator of chunk start and end indices. - - Parameters - ---------- - n - The total number of elements. - chunk_size - The size of each chunk. - - Returns - ------- - int - THE start and end indices of each chunk. - - Examples - -------- - >>> list(chunks(10, chunk_size=3)) - [(0, 3), (3, 6), (6, 9), (9, 10)] - >>> list(chunks(10, chunk_size=4)) - [(0, 4), (4, 8), (8, 10)] - """ - return ((start, min(start + chunk_size, n)) for start in range(0, n, chunk_size)) diff --git a/third_party/bigframes_vendored/pandas/README.md b/third_party/bigframes_vendored/pandas/README.md index 1aa5068d5e5..9f2bc800e84 100644 --- a/third_party/bigframes_vendored/pandas/README.md +++ b/third_party/bigframes_vendored/pandas/README.md @@ -6,6 +6,7 @@ # pandas: powerful Python data analysis toolkit [![PyPI Latest Release](https://img.shields.io/pypi/v/pandas.svg)](https://pypi.org/project/pandas/) +[![Conda Latest Release](https://anaconda.org/conda-forge/pandas/badges/version.svg)](https://anaconda.org/anaconda/pandas/) [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.3509134.svg)](https://doi.org/10.5281/zenodo.3509134) [![Package Status](https://img.shields.io/pypi/status/pandas.svg)](https://pypi.org/project/pandas/) [![License](https://img.shields.io/pypi/l/pandas.svg)](https://github.com/pandas-dev/pandas/blob/main/LICENSE) @@ -85,10 +86,15 @@ The source code is currently hosted on GitHub at: https://github.com/pandas-dev/pandas Binary installers for the latest released version are available at the [Python -Package Index (PyPI)](https://pypi.org/project/pandas). +Package Index (PyPI)](https://pypi.org/project/pandas) and on [Conda](https://docs.conda.io/en/latest/). ```sh -# PyPI +# conda +conda install -c conda-forge pandas +``` + +```sh +# or PyPI pip install pandas ``` diff --git a/third_party/bigframes_vendored/pandas/_config/config.py b/third_party/bigframes_vendored/pandas/_config/config.py index 418f5868e57..8abaca76c78 100644 --- a/third_party/bigframes_vendored/pandas/_config/config.py +++ b/third_party/bigframes_vendored/pandas/_config/config.py @@ -2,26 +2,20 @@ import contextlib import operator +import bigframes + class option_context(contextlib.ContextDecorator): """ - Context manager to temporarily set thread-local options in the `with` - statement context. + Context manager to temporarily set options in the `with` statement context. You need to invoke as ``option_context(pat, val, [(pat, val), ...])``. - .. note:: - - `"bigquery"` options can't be changed on a running session. Setting any - of these options creates a new thread-local session that only lives for - the lifetime of the context manager. - - **Examples:** - - >>> import bigframes - - >>> with bigframes.option_context('display.max_rows', 10, 'display.max_columns', 5): - ... pass + Examples + -------- + >>> import bigframes + >>> with bigframes.option_context('display.max_rows', 10, 'display.max_columns', 5): + ... pass """ def __init__(self, *args) -> None: @@ -33,47 +27,19 @@ def __init__(self, *args) -> None: self.ops = list(zip(args[::2], args[1::2])) def __enter__(self) -> None: - # Avoid problems with circular imports. - import bigframes._config - self.undo = [ - (pat, operator.attrgetter(pat)(bigframes._config.options)) - for pat, _ in self.ops - # Don't try to undo changes to bigquery options. We're starting and - # closing a new thread-local session if those are set. - if not pat.startswith("bigquery.") + (pat, operator.attrgetter(pat)(bigframes.options)) for pat, val in self.ops ] for pat, val in self.ops: self._set_option(pat, val) def __exit__(self, *args) -> None: - # Avoid problems with circular imports. - import bigframes._config - import bigframes.core.global_session - if self.undo: for pat, val in self.undo: self._set_option(pat, val) - # TODO(tswast): What to do if someone nests several context managers - # with separate "bigquery" options? We might need a "stack" of - # sessions if we allow that. - if bigframes._config.options.is_bigquery_thread_local: - bigframes.core.global_session.close_session() - - # Reset bigquery_options so that we're no longer thread-local. - bigframes._config.options._local.bigquery_options = None - def _set_option(self, pat, val): - # Avoid problems with circular imports. - import bigframes._config - root, attr = pat.rsplit(".", 1) - - # We are now using a thread-specific session. - if root == "bigquery": - bigframes._config.options._init_bigquery_thread_local() - - parent = operator.attrgetter(root)(bigframes._config.options) + parent = operator.attrgetter(root)(bigframes.options) setattr(parent, attr, val) diff --git a/third_party/bigframes_vendored/pandas/core/arrays/arrow/accessors.py b/third_party/bigframes_vendored/pandas/core/arrays/arrow/accessors.py index 94319dbc102..8e3ea06a3d4 100644 --- a/third_party/bigframes_vendored/pandas/core/arrays/arrow/accessors.py +++ b/third_party/bigframes_vendored/pandas/core/arrays/arrow/accessors.py @@ -6,67 +6,6 @@ from bigframes import constants -class ListAccessor: - """Accessor object for list data properties of the Series values.""" - - def len(self): - """Compute the length of each list in the Series. - - **See Also:** - - - :func:`StringMethods.len` : Compute the length of each element in the Series/Index. - - **Examples:** - - >>> import bigframes.pandas as bpd - >>> s = bpd.Series( - ... [ - ... [1, 2, 3], - ... [3], - ... ], - ... dtype=bpd.ArrowDtype(pa.list_(pa.int64())), - ... ) - >>> s.list.len() - 0 3 - 1 1 - dtype: Int64 - - Returns: - bigframes.series.Series: A Series or Index of integer values indicating - the length of each element in the Series or Index. - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - def __getitem__(self, key: int | slice): - """Index or slice lists in the Series. - - **Examples:** - - >>> import bigframes.pandas as bpd - >>> s = bpd.Series( - ... [ - ... [1, 2, 3], - ... [3], - ... ], - ... dtype=bpd.ArrowDtype(pa.list_(pa.int64())), - ... ) - >>> s.list[0] - 0 1 - 1 3 - dtype: Int64 - - Args: - key (int | slice): Index or slice of indices to access from each list. - For integer indices, only non-negative values are accepted. For - slices, you must use a non-negative start, a non-negative end, and - a step of 1. - - Returns: - bigframes.series.Series: The list at requested index. - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - class StructAccessor: """ Accessor object for structured data properties of the Series values. @@ -79,6 +18,8 @@ def field(self, name_or_index: str | int): **Examples:** >>> import bigframes.pandas as bpd + >>> import pyarrow as pa + >>> bpd.options.display.progress_bar = None >>> s = bpd.Series( ... [ ... {"version": 1, "project": "pandas"}, @@ -123,6 +64,8 @@ def explode(self): **Examples:** >>> import bigframes.pandas as bpd + >>> import pyarrow as pa + >>> bpd.options.display.progress_bar = None >>> s = bpd.Series( ... [ ... {"version": 1, "project": "pandas"}, @@ -149,82 +92,3 @@ def explode(self): The data corresponding to all child fields. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - @property - def dtypes(self): - """ - Return the dtype object of each child field of the struct. - - **Examples:** - - >>> import bigframes.pandas as bpd - >>> s = bpd.Series( - ... [ - ... {"version": 1, "project": "pandas"}, - ... {"version": 2, "project": "pandas"}, - ... {"version": 1, "project": "numpy"}, - ... ], - ... dtype=bpd.ArrowDtype(pa.struct( - ... [("version", pa.int64()), ("project", pa.string())] - ... )) - ... ) - >>> s.struct.dtypes - version int64[pyarrow] - project string[pyarrow] - dtype: object - - Returns: - A *pandas* Series with the data type of all child fields. - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - -class StructFrameAccessor: - """ - Accessor object for structured data properties of the DataFrame values. - """ - - def explode(self, column, *, separator: str = "."): - """ - Extract all child fields of struct column(s) and add to the DataFrame. - - **Examples:** - - >>> import bigframes.pandas as bpd - >>> countries = bpd.Series(["cn", "es", "us"]) - >>> files = bpd.Series( - ... [ - ... {"version": 1, "project": "pandas"}, - ... {"version": 2, "project": "pandas"}, - ... {"version": 1, "project": "numpy"}, - ... ], - ... dtype=bpd.ArrowDtype(pa.struct( - ... [("version", pa.int64()), ("project", pa.string())] - ... )) - ... ) - >>> downloads = bpd.Series([100, 200, 300]) - >>> df = bpd.DataFrame({"country": countries, "file": files, "download_count": downloads}) - >>> df.struct.explode("file") - country file.version file.project download_count - 0 cn 1 pandas 100 - 1 es 2 pandas 200 - 2 us 1 numpy 300 - - [3 rows x 4 columns] - - Args: - column: - Column(s) to explode. For multiple columns, specify a non-empty - list with each element be str or tuple, and all specified - columns their list-like data on same row of the frame must - have matching length. - separator: - Separator/delimiter to use to separate the original column name - from the sub-field column name. - - - Returns: - DataFrame: - Original DataFrame with exploded struct column(s). - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) diff --git a/third_party/bigframes_vendored/pandas/core/arrays/datetimelike.py b/third_party/bigframes_vendored/pandas/core/arrays/datetimelike.py deleted file mode 100644 index ace91dad1e8..00000000000 --- a/third_party/bigframes_vendored/pandas/core/arrays/datetimelike.py +++ /dev/null @@ -1,101 +0,0 @@ -# Contains code from https://github.com/pandas-dev/pandas/blob/main/pandas/core/arrays/datetimelike.py - -from bigframes import constants - - -class DatelikeOps: - def strftime(self, date_format: str): - """ - Convert to string Series using specified date_format. - - Return a Series of formatted strings specified by date_format. Details - of the string format can be found in BigQuery format elements doc: - https://cloud.google.com/bigquery/docs/reference/standard-sql/format-elements#format_elements_date_time. - - **Examples:** - - >>> import bigframes.pandas as bpd - - >>> s = bpd.to_datetime( - ... ['2014-08-15 08:15:12', '2012-02-29 08:15:12+06:00', '2015-08-15 08:15:12+05:00'], - ... utc=True - ... ).astype("timestamp[us, tz=UTC][pyarrow]") - - >>> s.dt.strftime("%B %d, %Y, %r") - 0 August 15, 2014, 08:15:12 AM - 1 February 29, 2012, 02:15:12 AM - 2 August 15, 2015, 03:15:12 AM - dtype: string - - Args: - date_format (str): - Date format string (e.g. "%Y-%m-%d"). - - Returns: - bigframes.pandas.Series: - Series of formatted strings. - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - def normalize(self): - """ - Convert times to midnight. - - The time component of the date-time is converted to midnight i.e. - 00:00:00. This is useful in cases when the time does not matter. - The return dtype will match the source series. - - This method is available on Series with datetime values under the - .dt accessor. - - **Examples:** - - >>> import bigframes.pandas as bpd - >>> s = bpd.Series(pd.date_range( - ... start='2014-08-01 10:00', - ... freq='h', - ... periods=3, - ... tz='Asia/Calcutta')) # note timezones will be converted to UTC here - >>> s.dt.normalize() - 0 2014-08-01 00:00:00+00:00 - 1 2014-08-01 00:00:00+00:00 - 2 2014-08-01 00:00:00+00:00 - dtype: timestamp[us, tz=UTC][pyarrow] - - Returns: - bigframes.pandas.Series: - Series of the same dtype as the data. - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - def floor(self, freq: str): - """ - Perform floor operation on the data to the specified freq. - - Supported freq arguments are: 'Y' (year), 'Q' (quarter), 'M' - (month), 'W' (week), 'D' (day), 'h' (hour), 'min' (minute), 's' - (second), 'ms' (microsecond), 'us' (nanosecond), 'ns' (nanosecond) - - Behavior around clock changes (i.e. daylight savings) is determined - by the SQL engine, so "ambiguous" and "nonexistent" parameters are not - supported. Y, Q, M, and W freqs are not supported by pandas as of - version 2.2, but have been added here due to backend support. - - **Examples:** - - >>> rng = pd.date_range('1/1/2018 11:59:00', periods=3, freq='min') - >>> bpd.Series(rng).dt.floor("h") - 0 2018-01-01 11:00:00 - 1 2018-01-01 12:00:00 - 2 2018-01-01 12:00:00 - dtype: timestamp[us][pyarrow] - - Args: - freq (str): - Frequency string (e.g. "D", "min", "s"). - - Returns: - bigframes.pandas.Series: - Series of the same dtype as the data. - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) diff --git a/third_party/bigframes_vendored/pandas/core/col.py b/third_party/bigframes_vendored/pandas/core/col.py deleted file mode 100644 index 9b71293a7e3..00000000000 --- a/third_party/bigframes_vendored/pandas/core/col.py +++ /dev/null @@ -1,36 +0,0 @@ -# Contains code from https://github.com/pandas-dev/pandas/blob/main/pandas/core/col.py -from __future__ import annotations - -from collections.abc import Hashable - -from bigframes import constants - - -class Expression: - """ - Class representing a deferred column. - - This is not meant to be instantiated directly. Instead, use :meth:`pandas.col`. - """ - - -def col(col_name: Hashable) -> Expression: - """ - Generate deferred object representing a column of a DataFrame. - - Any place which accepts ``lambda df: df[col_name]``, such as - :meth:`DataFrame.assign` or :meth:`DataFrame.loc`, can also accept - ``pd.col(col_name)``. - - Args: - col_name (Hashable): - Column name. - - Returns: - Expression: - A deferred object representing a column of a DataFrame. - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - -__all__ = ["Expression", "col"] diff --git a/third_party/bigframes_vendored/pandas/core/common.py b/third_party/bigframes_vendored/pandas/core/common.py deleted file mode 100644 index 970ba92a91c..00000000000 --- a/third_party/bigframes_vendored/pandas/core/common.py +++ /dev/null @@ -1,68 +0,0 @@ -# Contains code from https://github.com/pandas-dev/pandas/blob/main/pandas/core/common.py -from __future__ import annotations - -from typing import TYPE_CHECKING, Callable - -from bigframes_vendored.pandas.core.dtypes.inference import iterable_not_string - -if TYPE_CHECKING: - from bigframes_vendored.pandas.pandas._typing import T - - -def pipe( - obj, func: Callable[..., T] | tuple[Callable[..., T], str], *args, **kwargs -) -> T: - """ - Apply a function ``func`` to object ``obj`` either by passing obj as the - first argument to the function or, in the case that the func is a tuple, - interpret the first element of the tuple as a function and pass the obj to - that function as a keyword argument whose key is the value of the second - element of the tuple. - - Args: - func (callable or tuple of (callable, str)): - Function to apply to this object or, alternatively, a - ``(callable, data_keyword)`` tuple where ``data_keyword`` is a - string indicating the keyword of ``callable`` that expects the - object. - args (iterable, optional): - Positional arguments passed into ``func``. - kwargs (dict, optional): - A dictionary of keyword arguments passed into ``func``. - - Returns: - object: the return type of ``func``. - """ - if isinstance(func, tuple): - func, target = func - if target in kwargs: - msg = f"{target} is both the pipe target and a keyword argument" - raise ValueError(msg) - kwargs[target] = obj - return func(*args, **kwargs) - else: - return func(obj, *args, **kwargs) - - -def flatten(line): - """ - Flatten an arbitrarily nested sequence. - - Parameters - ---------- - line : sequence - The non string sequence to flatten - - Notes - ----- - This doesn't consider strings sequences. - - Returns - ------- - flattened : generator - """ - for element in line: - if iterable_not_string(element): - yield from flatten(element) - else: - yield element diff --git a/third_party/bigframes_vendored/pandas/core/computation/align.py b/third_party/bigframes_vendored/pandas/core/computation/align.py deleted file mode 100644 index fbc53a094df..00000000000 --- a/third_party/bigframes_vendored/pandas/core/computation/align.py +++ /dev/null @@ -1,226 +0,0 @@ -# Contains code from https://github.com/pandas-dev/pandas/blob/main/pandas/core/computation/align.py -""" -Core eval alignment algorithms. -""" - -from __future__ import annotations - -import warnings -from functools import partial, wraps -from typing import TYPE_CHECKING, Callable, Union - -import bigframes_vendored.pandas.core.common as com -import numpy as np -from bigframes_vendored.pandas.core.computation.common import result_type_many -from bigframes_vendored.pandas.util._exceptions import find_stack_level -from pandas.errors import PerformanceWarning - -if TYPE_CHECKING: - from collections.abc import Sequence - - from bigframes_vendored.pandas.core.indexes.base import Index - from pandas._typing import F - - from bigframes.pandas import DataFrame, Series - - FrameT = Union[Series, DataFrame] - - -def _align_core_single_unary_op( - term, -) -> tuple[partial | FrameT, dict[str, Index] | None]: - typ: partial | FrameT - axes: dict[str, Index] | None = None - - if isinstance(term.value, np.ndarray): - typ = partial(np.asanyarray, dtype=term.value.dtype) - else: - typ = type(term.value) - if hasattr(term.value, "axes"): - axes = _zip_axes_from_type(typ, term.value.axes) - - return typ, axes - - -def _zip_axes_from_type(typ: FrameT, new_axes: Sequence[Index]) -> dict[str, Index]: - return {name: new_axes[i] for i, name in enumerate(typ._AXIS_ORDERS)} - - -def _any_pandas_objects(terms) -> bool: - """ - Check a sequence of terms for instances of PandasObject. - """ - return any(is_pandas_object(term.value) for term in terms) - - -def _filter_special_cases(f) -> Callable[[F], F]: - @wraps(f) - def wrapper(terms): - # single unary operand - if len(terms) == 1: - return _align_core_single_unary_op(terms[0]) - - term_values = (term.value for term in terms) - - # we don't have any pandas objects - if not _any_pandas_objects(terms): - return result_type_many(*term_values), None - - return f(terms) - - return wrapper - - -@_filter_special_cases -def _align_core(terms): - term_index = [i for i, term in enumerate(terms) if hasattr(term.value, "axes")] - term_dims = [terms[i].value.ndim for i in term_index] - - from pandas import Series - - ndims = Series(dict(zip(term_index, term_dims))) - - # initial axes are the axes of the largest-axis'd term - biggest = terms[ndims.idxmax()].value - typ = biggest._constructor - axes = biggest.axes - naxes = len(axes) - gt_than_one_axis = naxes > 1 - - for value in (terms[i].value for i in term_index): - value_is_series = is_series(value) - is_series_and_gt_one_axis = value_is_series and gt_than_one_axis - - for axis, items in enumerate(value.axes): - if is_series_and_gt_one_axis: - ax, itm = naxes - 1, value.index - else: - ax, itm = axis, items - - if not axes[ax].is_(itm): - axes[ax] = axes[ax].join(itm, how="outer") - - for i, ndim in ndims.items(): - for axis, items in zip(range(ndim), axes): - ti = terms[i].value - - if hasattr(ti, "reindex"): - transpose = value_is_series(ti) and naxes > 1 - reindexer = axes[naxes - 1] if transpose else items - - term_axis_size = len(ti.axes[axis]) - reindexer_size = len(reindexer) - - ordm = np.log10(max(1, abs(reindexer_size - term_axis_size))) - if ordm >= 1 and reindexer_size >= 10000: - w = ( - f"Alignment difference on axis {axis} is larger " - f"than an order of magnitude on term {repr(terms[i].name)}, " - f"by more than {ordm:.4g}; performance may suffer." - ) - warnings.warn( - w, category=PerformanceWarning, stacklevel=find_stack_level() - ) - - obj = ti.reindex(reindexer, axis=axis, copy=False) - terms[i].update(obj) - - terms[i].update(terms[i].value.values) - - return typ, _zip_axes_from_type(typ, axes) - - -def align_terms(terms): - """ - Align a set of terms. - """ - try: - # flatten the parse tree (a nested list, really) - terms = list(com.flatten(terms)) - except TypeError: - # can't iterate so it must just be a constant or single variable - if is_series_or_dataframe(terms.value): - typ = type(terms.value) - return typ, _zip_axes_from_type(typ, terms.value.axes) - return np.result_type(terms.type), None - - # if all resolved variables are numeric scalars - if all(term.is_scalar for term in terms): - return result_type_many(*(term.value for term in terms)).type, None - - # perform the main alignment - typ, axes = _align_core(terms) - return typ, axes - - -def reconstruct_object(typ, obj, axes, dtype): - """ - Reconstruct an object given its type, raw value, and possibly empty - (None) axes. - - Parameters - ---------- - typ : object - A type - obj : object - The value to use in the type constructor - axes : dict - The axes to use to construct the resulting pandas object - - Returns - ------- - ret : typ - An object of type ``typ`` with the value `obj` and possible axes - `axes`. - """ - try: - typ = typ.type - except AttributeError: - pass - - res_t = np.result_type(obj.dtype, dtype) - - if not isinstance(typ, partial) and is_pandas_type(typ): - return typ(obj, dtype=res_t, **axes) - - # special case for pathological things like ~True/~False - if hasattr(res_t, "type") and typ == np.bool_ and res_t != np.bool_: - ret_value = res_t.type(obj) - else: - ret_value = typ(obj).astype(res_t) - # The condition is to distinguish 0-dim array (returned in case of - # scalar) and 1 element array - # e.g. np.array(0) and np.array([0]) - if ( - len(obj.shape) == 1 - and len(obj) == 1 - and not isinstance(ret_value, np.ndarray) - ): - ret_value = np.array([ret_value]).astype(res_t) - - return ret_value - - -# Custom to recognize BigFrames types -def is_series(obj) -> bool: - from bigframes_vendored.pandas.core.series import Series - - return isinstance(obj, Series) - - -def is_series_or_dataframe(obj) -> bool: - from bigframes.pandas import DataFrame, Series - - return isinstance(obj, Series | DataFrame) - - -def is_pandas_object(obj) -> bool: - from bigframes.pandas import DataFrame, Index, Series - - return isinstance(obj, Series | DataFrame | Index) - - -def is_pandas_type(type) -> bool: - from bigframes.pandas import DataFrame, Index, Series - - return issubclass(type, Series | DataFrame | Index) diff --git a/third_party/bigframes_vendored/pandas/core/computation/common.py b/third_party/bigframes_vendored/pandas/core/computation/common.py deleted file mode 100644 index 7775489d0df..00000000000 --- a/third_party/bigframes_vendored/pandas/core/computation/common.py +++ /dev/null @@ -1,48 +0,0 @@ -# Contains code from https://github.com/pandas-dev/pandas/blob/main/pandas/core/computation/common.py -from __future__ import annotations - -from functools import reduce - -import numpy as np -from pandas._config import get_option - - -def ensure_decoded(s) -> str: - """ - If we have bytes, decode them to unicode. - """ - if isinstance(s, (np.bytes_, bytes)): - s = s.decode(get_option("display.encoding")) - return s - - -def result_type_many(*arrays_and_dtypes): - """ - Wrapper around numpy.result_type which overcomes the NPY_MAXARGS (32) - argument limit. - """ - try: - return np.result_type(*arrays_and_dtypes) - except ValueError: - # we have > NPY_MAXARGS terms in our expression - return reduce(np.result_type, arrays_and_dtypes) - except TypeError: - from pandas.core.dtypes.cast import find_common_type - from pandas.core.dtypes.common import is_extension_array_dtype - - arr_and_dtypes = list(arrays_and_dtypes) - ea_dtypes, non_ea_dtypes = [], [] - for arr_or_dtype in arr_and_dtypes: - if is_extension_array_dtype(arr_or_dtype): - ea_dtypes.append(arr_or_dtype) - else: - non_ea_dtypes.append(arr_or_dtype) - - if non_ea_dtypes: - try: - np_dtype = np.result_type(*non_ea_dtypes) - except ValueError: - np_dtype = reduce(np.result_type, arrays_and_dtypes) - return find_common_type(ea_dtypes + [np_dtype]) - - return find_common_type(ea_dtypes) diff --git a/third_party/bigframes_vendored/pandas/core/computation/engines.py b/third_party/bigframes_vendored/pandas/core/computation/engines.py deleted file mode 100644 index 8902bb08adb..00000000000 --- a/third_party/bigframes_vendored/pandas/core/computation/engines.py +++ /dev/null @@ -1,95 +0,0 @@ -# Contains code from https://github.com/pandas-dev/pandas/blob/main/pandas/core/computation/engines.py -""" -Engine classes for :func:`~pandas.eval` -""" - -from __future__ import annotations - -import abc - -from bigframes_vendored.pandas.core.computation.align import ( - align_terms, - reconstruct_object, -) -from pandas.io.formats import printing - - -class AbstractEngine(metaclass=abc.ABCMeta): - """Object serving as a base class for all engines.""" - - has_neg_frac = False - - def __init__(self, expr) -> None: - self.expr = expr - self.aligned_axes = None - self.result_type = None - - def convert(self) -> str: - """ - Convert an expression for evaluation. - - Defaults to return the expression as a string. - """ - return printing.pprint_thing(self.expr) - - def evaluate(self) -> object: - """ - Run the engine on the expression. - - This method performs alignment which is necessary no matter what engine - is being used, thus its implementation is in the base class. - - Returns - ------- - object - The result of the passed expression. - """ - if not self._is_aligned: - self.result_type, self.aligned_axes = align_terms(self.expr.terms) - - # make sure no names in resolvers and locals/globals clash - res = self._evaluate() - return reconstruct_object( - self.result_type, res, self.aligned_axes, self.expr.terms.return_type - ) - - @property - def _is_aligned(self) -> bool: - return self.aligned_axes is not None and self.result_type is not None - - @abc.abstractmethod - def _evaluate(self): - """ - Return an evaluated expression. - - Parameters - ---------- - env : Scope - The local and global environment in which to evaluate an - expression. - - Notes - ----- - Must be implemented by subclasses. - """ - - -class PythonEngine(AbstractEngine): - """ - Evaluate an expression in Python space. - - Mostly for testing purposes. - """ - - has_neg_frac = False - - def evaluate(self): - return self.expr() - - def _evaluate(self) -> None: - pass - - -ENGINES: dict[str, type[AbstractEngine]] = { - "python": PythonEngine, -} diff --git a/third_party/bigframes_vendored/pandas/core/computation/eval.py b/third_party/bigframes_vendored/pandas/core/computation/eval.py deleted file mode 100644 index bf7e1de3bf1..00000000000 --- a/third_party/bigframes_vendored/pandas/core/computation/eval.py +++ /dev/null @@ -1,369 +0,0 @@ -# Contains code from https://github.com/pandas-dev/pandas/blob/main/pandas/core/computation/eval.py -""" -Top level ``eval`` module. -""" - -from __future__ import annotations - -import tokenize -import warnings -from typing import TYPE_CHECKING - -from bigframes_vendored.pandas.core.computation.engines import ENGINES -from bigframes_vendored.pandas.core.computation.expr import PARSERS, Expr -from bigframes_vendored.pandas.core.computation.parsing import tokenize_string -from bigframes_vendored.pandas.core.computation.scope import ensure_scope -from bigframes_vendored.pandas.util._validators import validate_bool_kwarg -from pandas.io.formats.printing import pprint_thing - -if TYPE_CHECKING: - from pandas.core.computation.ops import BinOp - - -def _check_engine(engine: str | None) -> str: - """ - Make sure a valid engine is passed. - - Parameters - ---------- - engine : str - String to validate. - - Raises - ------ - KeyError - * If an invalid engine is passed. - - Returns - ------- - str - Engine name. - """ - - if engine is None: - engine = "python" - - if engine not in ENGINES: - valid_engines = list(ENGINES.keys()) - raise KeyError( - f"Invalid engine '{engine}' passed, valid engines are {valid_engines}" - ) - - return engine - - -def _check_parser(parser: str): - """ - Make sure a valid parser is passed. - - Parameters - ---------- - parser : str - - Raises - ------ - KeyError - * If an invalid parser is passed - """ - if parser not in PARSERS: - raise KeyError( - f"Invalid parser '{parser}' passed, valid parsers are {PARSERS.keys()}" - ) - - -def _check_resolvers(resolvers): - if resolvers is not None: - for resolver in resolvers: - if not hasattr(resolver, "__getitem__"): - name = type(resolver).__name__ - raise TypeError( - f"Resolver of type '{name}' does not " - "implement the __getitem__ method" - ) - - -def _check_expression(expr): - """ - Make sure an expression is not an empty string - - Parameters - ---------- - expr : object - An object that can be converted to a string - - Raises - ------ - ValueError - * If expr is an empty string - """ - if not expr: - raise ValueError("expr cannot be an empty string") - - -def _convert_expression(expr) -> str: - """ - Convert an object to an expression. - - This function converts an object to an expression (a unicode string) and - checks to make sure it isn't empty after conversion. This is used to - convert operators to their string representation for recursive calls to - :func:`~pandas.eval`. - - Parameters - ---------- - expr : object - The object to be converted to a string. - - Returns - ------- - str - The string representation of an object. - - Raises - ------ - ValueError - * If the expression is empty. - """ - s = pprint_thing(expr) - _check_expression(s) - return s - - -def _check_for_locals(expr: str, stack_level: int, parser: str): - at_top_of_stack = stack_level == 0 - not_pandas_parser = parser != "pandas" - - if not_pandas_parser: - msg = "The '@' prefix is only supported by the pandas parser" - elif at_top_of_stack: - msg = ( - "The '@' prefix is not allowed in top-level eval calls.\n" - "please refer to your variables by name without the '@' prefix." - ) - - if at_top_of_stack or not_pandas_parser: - for toknum, tokval in tokenize_string(expr): - if toknum == tokenize.OP and tokval == "@": - raise SyntaxError(msg) - - -def eval( - expr: str | BinOp, # we leave BinOp out of the docstr bc it isn't for users - parser: str = "pandas", - engine: str | None = None, - local_dict=None, - global_dict=None, - resolvers=(), - level: int = 0, - target=None, - inplace: bool = False, -): - """ - Evaluate a Python expression as a string using various backends. - - The following arithmetic operations are supported: ``+``, ``-``, ``*``, - ``/``, ``**``, ``%``, ``//`` (python engine only) along with the following - boolean operations: ``|`` (or), ``&`` (and), and ``~`` (not). - Additionally, the ``'pandas'`` parser allows the use of :keyword:`and`, - :keyword:`or`, and :keyword:`not` with the same semantics as the - corresponding bitwise operators. :class:`~pandas.Series` and - :class:`~pandas.DataFrame` objects are supported and behave as they would - with plain ol' Python evaluation. - - **Examples:** - - - >>> df = bpd.DataFrame({"animal": ["dog", "pig"], "age": [10, 20]}) - >>> df - animal age - 0 dog 10 - 1 pig 20 - - [2 rows x 2 columns] - - We can add a new column using ``pd.eval``: - - >>> df.eval("double_age = age * 2") - animal age double_age - 0 dog 10 20 - 1 pig 20 40 - - [2 rows x 3 columns] - - Args: - expr (str): - The expression to evaluate. This string cannot contain any Python - `statements - `__, - only Python `expressions - `__. - parser ({'pandas', 'python'}, default 'pandas'): - The parser to use to construct the syntax tree from the expression. The - default of ``'pandas'`` parses code slightly different than standard - Python. Alternatively, you can parse an expression using the - ``'python'`` parser to retain strict Python semantics. See the - :ref:`enhancing performance ` documentation for - more details. - engine ({'python'}, default None): - - The engine used to evaluate the expression. Supported engines are - - - None : defaults to ``python`` - - ``'python'`` : Performs operations as if you had ``eval``'d in top - level python. This engine is generally not that useful. - - More backends may be available in the future. - local_dict (dict or None, optional): - A dictionary of local variables, taken from locals() by default. - global_dict (dict or None, optional): - A dictionary of global variables, taken from globals() by default. - resolvers (list of dict-like or None, optional): - A list of objects implementing the ``__getitem__`` special method that - you can use to inject an additional collection of namespaces to use for - variable lookup. For example, this is used in the - :meth:`~DataFrame.query` method to inject the - ``DataFrame.index`` and ``DataFrame.columns`` - variables that refer to their respective :class:`~pandas.DataFrame` - instance attributes. - level (int, optional): - The number of prior stack frames to traverse and add to the current - scope. Most users will **not** need to change this parameter. - target (object, optional, default None): - This is the target object for assignment. It is used when there is - variable assignment in the expression. If so, then `target` must - support item assignment with string keys, and if a copy is being - returned, it must also support `.copy()`. - inplace (bool, default False): - If `target` is provided, and the expression mutates `target`, whether - to modify `target` inplace. Otherwise, return a copy of `target` with - the mutation. - - Returns: - ndarray, numeric scalar, DataFrame, Series, or None: - The completion value of evaluating the given code or None if ``inplace=True``. - - Raises: - ValueError: - There are many instances where such an error can be raised: - - - `target=None`, but the expression is multiline. - - The expression is multiline, but not all them have item assignment. - An example of such an arrangement is this: - - a = b + 1 - a + 2 - - Here, there are expressions on different lines, making it multiline, - but the last line has no variable assigned to the output of `a + 2`. - - `inplace=True`, but the expression is missing item assignment. - - Item assignment is provided, but the `target` does not support - string item assignment. - - Item assignment is provided and `inplace=False`, but the `target` - does not support the `.copy()` method - - """ - inplace = validate_bool_kwarg(inplace, "inplace") - - exprs: list[str | BinOp] - if isinstance(expr, str): - _check_expression(expr) - exprs = [e.strip() for e in expr.splitlines() if e.strip() != ""] - else: - # ops.BinOp; for internal compat, not intended to be passed by users - exprs = [expr] - multi_line = len(exprs) > 1 - - if multi_line and target is None: - raise ValueError( - "multi-line expressions are only valid in the " - "context of data, use DataFrame.eval" - ) - engine = _check_engine(engine) - _check_parser(parser) - _check_resolvers(resolvers) - - ret = None - first_expr = True - target_modified = False - - for expr in exprs: - expr = _convert_expression(expr) - _check_for_locals(expr, level, parser) - - # get our (possibly passed-in) scope - env = ensure_scope( - level + 1, - global_dict=global_dict, - local_dict=local_dict, - resolvers=resolvers, - target=target, - ) - - parsed_expr = Expr(expr, engine=engine, parser=parser, env=env) - - # construct the engine and evaluate the parsed expression - eng = ENGINES[engine] - eng_inst = eng(parsed_expr) - ret = eng_inst.evaluate() - - if parsed_expr.assigner is None: - if multi_line: - raise ValueError( - "Multi-line expressions are only valid " - "if all expressions contain an assignment" - ) - if inplace: - raise ValueError("Cannot operate inplace if there is no assignment") - - # assign if needed - assigner = parsed_expr.assigner - from bigframes.pandas import DataFrame, Series - - if env.target is not None and assigner is not None: - target_modified = True - - # if returning a copy, copy only on the first assignment - if not inplace and first_expr: - try: - target = env.target - if isinstance(target, Series | DataFrame): - target = target.copy() - except AttributeError as err: - raise ValueError("Cannot return a copy of the target") from err - else: - target = env.target - - # TypeError is most commonly raised (e.g. int, list), but you - # get IndexError if you try to do this assignment on np.ndarray. - # we will ignore numpy warnings here; e.g. if trying - # to use a non-numeric indexer - try: - with warnings.catch_warnings(record=True): - # TODO: Filter the warnings we actually care about here. - if inplace and isinstance(target, Series | DataFrame): - target.loc[:, assigner] = ret - else: - target[ # pyright: ignore[reportGeneralTypeIssues] - assigner - ] = ret - except (TypeError, IndexError) as err: - raise ValueError("Cannot assign expression output to target") from err - - if not resolvers: - resolvers = ({assigner: ret},) - else: - # existing resolver needs updated to handle - # case of mutating existing column in copy - for resolver in resolvers: - if assigner in resolver: - resolver[assigner] = ret - break - else: - resolvers += ({assigner: ret},) - - ret = None - first_expr = False - - # We want to exclude `inplace=None` as being False. - if inplace is False: - return target if target_modified else ret diff --git a/third_party/bigframes_vendored/pandas/core/computation/expr.py b/third_party/bigframes_vendored/pandas/core/computation/expr.py deleted file mode 100644 index e8def559a88..00000000000 --- a/third_party/bigframes_vendored/pandas/core/computation/expr.py +++ /dev/null @@ -1,829 +0,0 @@ -# Contains code from https://github.com/pandas-dev/pandas/blob/main/pandas/core/computation/expr.py -""" -:func:`~pandas.eval` parsers. -""" - -from __future__ import annotations - -import ast -import tokenize -from functools import partial, reduce -from keyword import iskeyword -from typing import Callable, TypeVar - -import bigframes_vendored.pandas.core.common as com -import numpy as np -from bigframes_vendored.pandas.core.computation.ops import ( - ARITH_OPS_SYMS, - BOOL_OPS_SYMS, - CMP_OPS_SYMS, - LOCAL_TAG, - UNARY_OPS_SYMS, - BinOp, - Constant, - Div, - FuncNode, - Op, - Term, - UnaryOp, - is_term, -) -from bigframes_vendored.pandas.core.computation.parsing import ( - clean_backtick_quoted_toks, - tokenize_string, -) -from bigframes_vendored.pandas.core.computation.scope import Scope -from pandas.errors import UndefinedVariableError -from pandas.io.formats import printing - - -def _rewrite_assign(tok: tuple[int, str]) -> tuple[int, str]: - """ - Rewrite the assignment operator for PyTables expressions that use ``=`` - as a substitute for ``==``. - - Parameters - ---------- - tok : tuple of int, str - ints correspond to the all caps constants in the tokenize module - - Returns - ------- - tuple of int, str - Either the input or token or the replacement values - """ - toknum, tokval = tok - return toknum, "==" if tokval == "=" else tokval - - -def _replace_booleans(tok: tuple[int, str]) -> tuple[int, str]: - """ - Replace ``&`` with ``and`` and ``|`` with ``or`` so that bitwise - precedence is changed to boolean precedence. - - Parameters - ---------- - tok : tuple of int, str - ints correspond to the all caps constants in the tokenize module - - Returns - ------- - tuple of int, str - Either the input or token or the replacement values - """ - toknum, tokval = tok - if toknum == tokenize.OP: - if tokval == "&": - return tokenize.NAME, "and" - elif tokval == "|": - return tokenize.NAME, "or" - return toknum, tokval - return toknum, tokval - - -def _replace_locals(tok: tuple[int, str]) -> tuple[int, str]: - """ - Replace local variables with a syntactically valid name. - - Parameters - ---------- - tok : tuple of int, str - ints correspond to the all caps constants in the tokenize module - - Returns - ------- - tuple of int, str - Either the input or token or the replacement values - - Notes - ----- - This is somewhat of a hack in that we rewrite a string such as ``'@a'`` as - ``'__pd_eval_local_a'`` by telling the tokenizer that ``__pd_eval_local_`` - is a ``tokenize.OP`` and to replace the ``'@'`` symbol with it. - """ - toknum, tokval = tok - if toknum == tokenize.OP and tokval == "@": - return tokenize.OP, LOCAL_TAG - return toknum, tokval - - -def _compose2(f, g): - """ - Compose 2 callables. - """ - return lambda *args, **kwargs: f(g(*args, **kwargs)) - - -def _compose(*funcs): - """ - Compose 2 or more callables. - """ - assert len(funcs) > 1, "At least 2 callables must be passed to compose" - return reduce(_compose2, funcs) - - -def _preparse( - source: str, - f=_compose( - _replace_locals, _replace_booleans, _rewrite_assign, clean_backtick_quoted_toks - ), -) -> str: - """ - Compose a collection of tokenization functions. - - Parameters - ---------- - source : str - A Python source code string - f : callable - This takes a tuple of (toknum, tokval) as its argument and returns a - tuple with the same structure but possibly different elements. Defaults - to the composition of ``_rewrite_assign``, ``_replace_booleans``, and - ``_replace_locals``. - - Returns - ------- - str - Valid Python source code - - Notes - ----- - The `f` parameter can be any callable that takes *and* returns input of the - form ``(toknum, tokval)``, where ``toknum`` is one of the constants from - the ``tokenize`` module and ``tokval`` is a string. - """ - assert callable(f), "f must be callable" - return tokenize.untokenize(f(x) for x in tokenize_string(source)) - - -def _is_type(t): - """ - Factory for a type checking function of type ``t`` or tuple of types. - """ - return lambda x: isinstance(x.value, t) - - -_is_list = _is_type(list) -_is_str = _is_type(str) - - -# partition all AST nodes -_all_nodes = frozenset( - node - for node in (getattr(ast, name) for name in dir(ast)) - if isinstance(node, type) and issubclass(node, ast.AST) -) - - -def _filter_nodes(superclass, all_nodes=_all_nodes): - """ - Filter out AST nodes that are subclasses of ``superclass``. - """ - node_names = (node.__name__ for node in all_nodes if issubclass(node, superclass)) - return frozenset(node_names) - - -_all_node_names = frozenset(x.__name__ for x in _all_nodes) -_mod_nodes = _filter_nodes(ast.mod) -_stmt_nodes = _filter_nodes(ast.stmt) -_expr_nodes = _filter_nodes(ast.expr) -_expr_context_nodes = _filter_nodes(ast.expr_context) -_boolop_nodes = _filter_nodes(ast.boolop) -_operator_nodes = _filter_nodes(ast.operator) -_unary_op_nodes = _filter_nodes(ast.unaryop) -_cmp_op_nodes = _filter_nodes(ast.cmpop) -_comprehension_nodes = _filter_nodes(ast.comprehension) -_handler_nodes = _filter_nodes(ast.excepthandler) -_arguments_nodes = _filter_nodes(ast.arguments) -_keyword_nodes = _filter_nodes(ast.keyword) -_alias_nodes = _filter_nodes(ast.alias) - - -# nodes that we don't support directly but are needed for parsing -_hacked_nodes = frozenset(["Assign", "Module", "Expr"]) - - -_unsupported_expr_nodes = frozenset( - [ - "Yield", - "GeneratorExp", - "IfExp", - "DictComp", - "SetComp", - "Repr", - "Lambda", - "Set", - "AST", - "Is", - "IsNot", - ] -) - -# these nodes are low priority or won't ever be supported (e.g., AST) -_unsupported_nodes = ( - _stmt_nodes - | _mod_nodes - | _handler_nodes - | _arguments_nodes - | _keyword_nodes - | _alias_nodes - | _expr_context_nodes - | _unsupported_expr_nodes -) - _hacked_nodes - -# we're adding a different assignment in some cases to be equality comparison -# and we don't want `stmt` and friends in their so get only the class whose -# names are capitalized -_base_supported_nodes = (_all_node_names - _unsupported_nodes) | _hacked_nodes -intersection = _unsupported_nodes & _base_supported_nodes -_msg = f"cannot both support and not support {intersection}" -assert not intersection, _msg - - -def _node_not_implemented(node_name: str) -> Callable[..., None]: - """ - Return a function that raises a NotImplementedError with a passed node name. - """ - - def f(self, *args, **kwargs): - raise NotImplementedError(f"'{node_name}' nodes are not implemented") - - return f - - -# should be bound by BaseExprVisitor but that creates a circular dependency: -# _T is used in disallow, but disallow is used to define BaseExprVisitor -# https://github.com/microsoft/pyright/issues/2315 -_T = TypeVar("_T") - - -def disallow(nodes: set[str]) -> Callable[[type[_T]], type[_T]]: - """ - Decorator to disallow certain nodes from parsing. Raises a - NotImplementedError instead. - - Returns - ------- - callable - """ - - def disallowed(cls: type[_T]) -> type[_T]: - # error: "Type[_T]" has no attribute "unsupported_nodes" - cls.unsupported_nodes = () # type: ignore[attr-defined] - for node in nodes: - new_method = _node_not_implemented(node) - name = f"visit_{node}" - # error: "Type[_T]" has no attribute "unsupported_nodes" - cls.unsupported_nodes += (name,) # type: ignore[attr-defined] - setattr(cls, name, new_method) - return cls - - return disallowed - - -def _op_maker(op_class, op_symbol): - """ - Return a function to create an op class with its symbol already passed. - - Returns - ------- - callable - """ - - def f(self, node, *args, **kwargs): - """ - Return a partial function with an Op subclass with an operator already passed. - - Returns - ------- - callable - """ - return partial(op_class, op_symbol, *args, **kwargs) - - return f - - -_op_classes = {"binary": BinOp, "unary": UnaryOp} - - -def add_ops(op_classes): - """ - Decorator to add default implementation of ops. - """ - - def f(cls): - for op_attr_name, op_class in op_classes.items(): - ops = getattr(cls, f"{op_attr_name}_ops") - ops_map = getattr(cls, f"{op_attr_name}_op_nodes_map") - for op in ops: - op_node = ops_map[op] - if op_node is not None: - made_op = _op_maker(op_class, op) - setattr(cls, f"visit_{op_node}", made_op) - return cls - - return f - - -@disallow(_unsupported_nodes) -@add_ops(_op_classes) -class BaseExprVisitor(ast.NodeVisitor): - """ - Custom ast walker. Parsers of other engines should subclass this class - if necessary. - - Parameters - ---------- - env : Scope - engine : str - parser : str - preparser : callable - """ - - const_type: type[Term] = Constant - term_type = Term - - binary_ops = CMP_OPS_SYMS + BOOL_OPS_SYMS + ARITH_OPS_SYMS - binary_op_nodes = ( - "Gt", - "Lt", - "GtE", - "LtE", - "Eq", - "NotEq", - "In", - "NotIn", - "BitAnd", - "BitOr", - "And", - "Or", - "Add", - "Sub", - "Mult", - None, - "Pow", - "FloorDiv", - "Mod", - ) - binary_op_nodes_map = dict(zip(binary_ops, binary_op_nodes)) - - unary_ops = UNARY_OPS_SYMS - unary_op_nodes = "UAdd", "USub", "Invert", "Not" - unary_op_nodes_map = dict(zip(unary_ops, unary_op_nodes)) - - rewrite_map = { - ast.Eq: ast.In, - ast.NotEq: ast.NotIn, - ast.In: ast.In, - ast.NotIn: ast.NotIn, - } - - unsupported_nodes: tuple[str, ...] - - def __init__(self, env, engine, parser, preparser=_preparse) -> None: - self.env = env - self.engine = engine - self.parser = parser - self.preparser = preparser - self.assigner = None - - def visit(self, node, **kwargs): - if isinstance(node, str): - clean = self.preparser(node) - try: - node = ast.fix_missing_locations(ast.parse(clean)) - except SyntaxError as e: - if any(iskeyword(x) for x in clean.split()): - e.msg = "Python keyword not valid identifier in numexpr query" - raise e - - method = f"visit_{type(node).__name__}" - visitor = getattr(self, method) - return visitor(node, **kwargs) - - def visit_Module(self, node, **kwargs): - if len(node.body) != 1: - raise SyntaxError("only a single expression is allowed") - expr = node.body[0] - return self.visit(expr, **kwargs) - - def visit_Expr(self, node, **kwargs): - return self.visit(node.value, **kwargs) - - def _rewrite_membership_op(self, node, left, right): - # the kind of the operator (is actually an instance) - op_instance = node.op - op_type = type(op_instance) - - # must be two terms and the comparison operator must be ==/!=/in/not in - if is_term(left) and is_term(right) and op_type in self.rewrite_map: - left_list, right_list = map(_is_list, (left, right)) - left_str, right_str = map(_is_str, (left, right)) - - # if there are any strings or lists in the expression - if left_list or right_list or left_str or right_str: - op_instance = self.rewrite_map[op_type]() - - # pop the string variable out of locals and replace it with a list - # of one string, kind of a hack - if right_str: - name = self.env.add_tmp([right.value]) - right = self.term_type(name, self.env) - - if left_str: - name = self.env.add_tmp([left.value]) - left = self.term_type(name, self.env) - - op = self.visit(op_instance) - return op, op_instance, left, right - - def _maybe_transform_eq_ne(self, node, left=None, right=None): - if left is None: - left = self.visit(node.left, side="left") - if right is None: - right = self.visit(node.right, side="right") - op, op_class, left, right = self._rewrite_membership_op(node, left, right) - return op, op_class, left, right - - def _maybe_downcast_constants(self, left, right): - f32 = np.dtype(np.float32) - if ( - left.is_scalar - and hasattr(left, "value") - and not right.is_scalar - and right.return_type == f32 - ): - # right is a float32 array, left is a scalar - name = self.env.add_tmp(np.float32(left.value)) - left = self.term_type(name, self.env) - if ( - right.is_scalar - and hasattr(right, "value") - and not left.is_scalar - and left.return_type == f32 - ): - # left is a float32 array, right is a scalar - name = self.env.add_tmp(np.float32(right.value)) - right = self.term_type(name, self.env) - - return left, right - - def _maybe_eval(self, binop, eval_in_python): - # eval `in` and `not in` (for now) in "partial" python space - # things that can be evaluated in "eval" space will be turned into - # temporary variables. for example, - # [1,2] in a + 2 * b - # in that case a + 2 * b will be evaluated using numexpr, and the "in" - # call will be evaluated using isin (in python space) - return binop.evaluate( - self.env, self.engine, self.parser, self.term_type, eval_in_python - ) - - def _maybe_evaluate_binop( - self, - op, - op_class, - lhs, - rhs, - eval_in_python=("in", "not in"), - maybe_eval_in_python=("==", "!=", "<", ">", "<=", ">="), - ): - res = op(lhs, rhs) - - if res.has_invalid_return_type: - raise TypeError( - f"unsupported operand type(s) for {res.op}: " - f"'{lhs.type}' and '{rhs.type}'" - ) - - if self.engine != "pytables" and ( - res.op in CMP_OPS_SYMS - and getattr(lhs, "is_datetime", False) - or getattr(rhs, "is_datetime", False) - ): - # all date ops must be done in python bc numexpr doesn't work - # well with NaT - return self._maybe_eval(res, self.binary_ops) - - if res.op in eval_in_python: - # "in"/"not in" ops are always evaluated in python - return self._maybe_eval(res, eval_in_python) - elif self.engine != "pytables": - if ( - getattr(lhs, "return_type", None) == object - or getattr(rhs, "return_type", None) == object - ): - # evaluate "==" and "!=" in python if either of our operands - # has an object return type - return self._maybe_eval(res, eval_in_python + maybe_eval_in_python) - return res - - def visit_BinOp(self, node, **kwargs): - op, op_class, left, right = self._maybe_transform_eq_ne(node) - left, right = self._maybe_downcast_constants(left, right) - return self._maybe_evaluate_binop(op, op_class, left, right) - - def visit_Div(self, node, **kwargs): - return lambda lhs, rhs: Div(lhs, rhs) - - def visit_UnaryOp(self, node, **kwargs): - op = self.visit(node.op) - operand = self.visit(node.operand) - return op(operand) - - def visit_Name(self, node, **kwargs): - return self.term_type(node.id, self.env, **kwargs) - - # TODO(py314): deprecated since Python 3.8. Remove after Python 3.14 is min - def visit_NameConstant(self, node, **kwargs) -> Term: - return self.const_type(node.value, self.env) - - # TODO(py314): deprecated since Python 3.8. Remove after Python 3.14 is min - def visit_Num(self, node, **kwargs) -> Term: - return self.const_type(node.value, self.env) - - def visit_Constant(self, node, **kwargs) -> Term: - return self.const_type(node.value, self.env) - - # TODO(py314): deprecated since Python 3.8. Remove after Python 3.14 is min - def visit_Str(self, node, **kwargs): - name = self.env.add_tmp(node.s) - return self.term_type(name, self.env) - - def visit_List(self, node, **kwargs): - name = self.env.add_tmp([self.visit(e)(self.env) for e in node.elts]) - return self.term_type(name, self.env) - - visit_Tuple = visit_List - - def visit_Index(self, node, **kwargs): - """df.index[4]""" - return self.visit(node.value) - - def visit_Subscript(self, node, **kwargs): - from pandas import eval as pd_eval - - value = self.visit(node.value) - slobj = self.visit(node.slice) - result = pd_eval( - slobj, local_dict=self.env, engine=self.engine, parser=self.parser - ) - try: - # a Term instance - v = value.value[result] - except AttributeError: - # an Op instance - lhs = pd_eval( - value, local_dict=self.env, engine=self.engine, parser=self.parser - ) - v = lhs[result] - name = self.env.add_tmp(v) - return self.term_type(name, env=self.env) - - def visit_Slice(self, node, **kwargs): - """df.index[slice(4,6)]""" - lower = node.lower - if lower is not None: - lower = self.visit(lower).value - upper = node.upper - if upper is not None: - upper = self.visit(upper).value - step = node.step - if step is not None: - step = self.visit(step).value - - return slice(lower, upper, step) - - def visit_Assign(self, node, **kwargs): - """ - support a single assignment node, like - - c = a + b - - set the assigner at the top level, must be a Name node which - might or might not exist in the resolvers - - """ - if len(node.targets) != 1: - raise SyntaxError("can only assign a single expression") - if not isinstance(node.targets[0], ast.Name): - raise SyntaxError("left hand side of an assignment must be a single name") - if self.env.target is None: - raise ValueError("cannot assign without a target object") - - try: - assigner = self.visit(node.targets[0], **kwargs) - except UndefinedVariableError: - assigner = node.targets[0].id - - self.assigner = getattr(assigner, "name", assigner) - if self.assigner is None: - raise SyntaxError( - "left hand side of an assignment must be a single resolvable name" - ) - - return self.visit(node.value, **kwargs) - - def visit_Attribute(self, node, **kwargs): - attr = node.attr - value = node.value - - ctx = node.ctx - if isinstance(ctx, ast.Load): - # resolve the value - resolved = self.visit(value).value - try: - v = getattr(resolved, attr) - name = self.env.add_tmp(v) - return self.term_type(name, self.env) - except AttributeError: - # something like datetime.datetime where scope is overridden - if isinstance(value, ast.Name) and value.id == attr: - return resolved - raise - - raise ValueError(f"Invalid Attribute context {type(ctx).__name__}") - - def visit_Call(self, node, side=None, **kwargs): - if isinstance(node.func, ast.Attribute) and node.func.attr != "__call__": - res = self.visit_Attribute(node.func) - elif not isinstance(node.func, ast.Name): - raise TypeError("Only named functions are supported") - else: - try: - res = self.visit(node.func) - except UndefinedVariableError: - # Check if this is a supported function name - try: - res = FuncNode(node.func.id) - except ValueError: - # Raise original error - raise - - if res is None: - # error: "expr" has no attribute "id" - raise ValueError( - f"Invalid function call {node.func.id}" # type: ignore[attr-defined] - ) - if hasattr(res, "value"): - res = res.value - - if isinstance(res, FuncNode): - new_args = [self.visit(arg) for arg in node.args] - - if node.keywords: - raise TypeError( - f'Function "{res.name}" does not support keyword arguments' - ) - - return res(*new_args) - - else: - new_args = [self.visit(arg)(self.env) for arg in node.args] - - for key in node.keywords: - if not isinstance(key, ast.keyword): - # error: "expr" has no attribute "id" - raise ValueError( - "keyword error in function call " # type: ignore[attr-defined] - f"'{node.func.id}'" - ) - - if key.arg: - kwargs[key.arg] = self.visit(key.value)(self.env) - - name = self.env.add_tmp(res(*new_args, **kwargs)) - return self.term_type(name=name, env=self.env) - - def translate_In(self, op): - return op - - def visit_Compare(self, node, **kwargs): - ops = node.ops - comps = node.comparators - - # base case: we have something like a CMP b - if len(comps) == 1: - op = self.translate_In(ops[0]) - binop = ast.BinOp(op=op, left=node.left, right=comps[0]) - return self.visit(binop) - - # recursive case: we have a chained comparison, a CMP b CMP c, etc. - left = node.left - values = [] - for op, comp in zip(ops, comps): - new_node = self.visit( - ast.Compare(comparators=[comp], left=left, ops=[self.translate_In(op)]) - ) - left = comp - values.append(new_node) - return self.visit(ast.BoolOp(op=ast.And(), values=values)) - - def _try_visit_binop(self, bop): - if isinstance(bop, (Op, Term)): - return bop - return self.visit(bop) - - def visit_BoolOp(self, node, **kwargs): - def visitor(x, y): - lhs = self._try_visit_binop(x) - rhs = self._try_visit_binop(y) - - op, op_class, lhs, rhs = self._maybe_transform_eq_ne(node, lhs, rhs) - return self._maybe_evaluate_binop(op, node.op, lhs, rhs) - - operands = node.values - return reduce(visitor, operands) - - -_python_not_supported = frozenset(["Dict", "BoolOp", "In", "NotIn"]) - - -@disallow( - (_unsupported_nodes | _python_not_supported) - - (_boolop_nodes | frozenset(["BoolOp", "Attribute", "In", "NotIn", "Tuple"])) -) -class PandasExprVisitor(BaseExprVisitor): - def __init__( - self, - env, - engine, - parser, - preparser=partial( - _preparse, - f=_compose(_replace_locals, _replace_booleans, clean_backtick_quoted_toks), - ), - ) -> None: - super().__init__(env, engine, parser, preparser) - - -@disallow(_unsupported_nodes | _python_not_supported | frozenset(["Not"])) -class PythonExprVisitor(BaseExprVisitor): - def __init__( - self, env, engine, parser, preparser=lambda source, f=None: source - ) -> None: - super().__init__(env, engine, parser, preparser=preparser) - - -class Expr: - """ - Object encapsulating an expression. - - Parameters - ---------- - expr : str - engine : str, optional, default 'numexpr' - parser : str, optional, default 'pandas' - env : Scope, optional, default None - level : int, optional, default 2 - """ - - env: Scope - engine: str - parser: str - - def __init__( - self, - expr, - engine: str = "numexpr", - parser: str = "pandas", - env: Scope | None = None, - level: int = 0, - ) -> None: - self.expr = expr - self.env = env or Scope(level=level + 1) - self.engine = engine - self.parser = parser - self._visitor = PARSERS[parser](self.env, self.engine, self.parser) - self.terms = self.parse() - - @property - def assigner(self): - return getattr(self._visitor, "assigner", None) - - def __call__(self): - return self.terms(self.env) - - def __repr__(self) -> str: - return printing.pprint_thing(self.terms) - - def __len__(self) -> int: - return len(self.expr) - - def parse(self): - """ - Parse an expression. - """ - return self._visitor.visit(self.expr) - - @property - def names(self): - """ - Get the names in an expression. - """ - if is_term(self.terms): - return frozenset([self.terms.name]) - return frozenset(term.name for term in com.flatten(self.terms)) - - -PARSERS = {"python": PythonExprVisitor, "pandas": PandasExprVisitor} diff --git a/third_party/bigframes_vendored/pandas/core/computation/ops.py b/third_party/bigframes_vendored/pandas/core/computation/ops.py deleted file mode 100644 index 0dfd77daf36..00000000000 --- a/third_party/bigframes_vendored/pandas/core/computation/ops.py +++ /dev/null @@ -1,605 +0,0 @@ -# Contains code from https://github.com/pandas-dev/pandas/blob/main/pandas/core/computation/ops.py -""" -Operator classes for eval. -""" - -from __future__ import annotations - -import operator -from datetime import datetime -from functools import partial -from typing import TYPE_CHECKING, Callable, Literal - -import bigframes_vendored.pandas.core.common as com -import numpy as np -from bigframes_vendored.pandas.core.computation.common import ( - ensure_decoded, - result_type_many, -) -from bigframes_vendored.pandas.core.computation.scope import DEFAULT_GLOBALS -from pandas._libs.tslibs import Timestamp -from pandas.core.dtypes.common import is_list_like, is_scalar -from pandas.io.formats.printing import pprint_thing, pprint_thing_encoded - -if TYPE_CHECKING: - from collections.abc import Iterable, Iterator - -REDUCTIONS = ("sum", "prod", "min", "max") - -_unary_math_ops = ( - "sin", - "cos", - "exp", - "log", - "expm1", - "log1p", - "sqrt", - "sinh", - "cosh", - "tanh", - "arcsin", - "arccos", - "arctan", - "arccosh", - "arcsinh", - "arctanh", - "abs", - "log10", - "floor", - "ceil", -) -_binary_math_ops = ("arctan2",) - -MATHOPS = _unary_math_ops + _binary_math_ops - - -LOCAL_TAG = "__pd_eval_local_" - - -class Term: - def __new__(cls, name, env, side=None, encoding=None): - klass = Constant if not isinstance(name, str) else cls - # error: Argument 2 for "super" not an instance of argument 1 - supr_new = super(Term, klass).__new__ # type: ignore[misc] - return supr_new(klass) - - is_local: bool - - def __init__(self, name, env, side=None, encoding=None) -> None: - # name is a str for Term, but may be something else for subclasses - self._name = name - self.env = env - self.side = side - tname = str(name) - self.is_local = tname.startswith(LOCAL_TAG) or tname in DEFAULT_GLOBALS - self._value = self._resolve_name() - self.encoding = encoding - - @property - def local_name(self) -> str: - return self.name.replace(LOCAL_TAG, "") - - def __repr__(self) -> str: - return pprint_thing(self.name) - - def __call__(self, *args, **kwargs): - return self.value - - def evaluate(self, *args, **kwargs) -> Term: - return self - - def _resolve_name(self): - local_name = str(self.local_name) - is_local = self.is_local - if local_name in self.env.scope and isinstance( - self.env.scope[local_name], type - ): - is_local = False - - res = self.env.resolve(local_name, is_local=is_local) - self.update(res) - - if hasattr(res, "ndim") and res.ndim > 2: - raise NotImplementedError( - "N-dimensional objects, where N > 2, are not supported with eval" - ) - return res - - def update(self, value) -> None: - """ - search order for local (i.e., @variable) variables: - - scope, key_variable - [('locals', 'local_name'), - ('globals', 'local_name'), - ('locals', 'key'), - ('globals', 'key')] - """ - key = self.name - - # if it's a variable name (otherwise a constant) - if isinstance(key, str): - self.env.swapkey(self.local_name, key, new_value=value) - - self.value = value - - @property - def is_scalar(self) -> bool: - return is_scalar(self._value) - - @property - def type(self): - try: - # potentially very slow for large, mixed dtype frames - return self._value.values.dtype - except AttributeError: - try: - # ndarray - return self._value.dtype - except AttributeError: - # scalar - return type(self._value) - - return_type = type - - @property - def raw(self) -> str: - return f"{type(self).__name__}(name={repr(self.name)}, type={self.type})" - - @property - def is_datetime(self) -> bool: - try: - t = self.type.type - except AttributeError: - t = self.type - - return issubclass(t, (datetime, np.datetime64)) - - @property - def value(self): - return self._value - - @value.setter - def value(self, new_value) -> None: - self._value = new_value - - @property - def name(self): - return self._name - - @property - def ndim(self) -> int: - return self._value.ndim - - -class Constant(Term): - def _resolve_name(self): - return self._name - - @property - def name(self): - return self.value - - def __repr__(self) -> str: - # in python 2 str() of float - # can truncate shorter than repr() - return repr(self.name) - - -_bool_op_map = {"not": "~", "and": "&", "or": "|"} - - -class Op: - """ - Hold an operator of arbitrary arity. - """ - - op: str - - def __init__(self, op: str, operands: Iterable[Term | Op], encoding=None) -> None: - self.op = _bool_op_map.get(op, op) - self.operands = operands - self.encoding = encoding - - def __iter__(self) -> Iterator: - return iter(self.operands) - - def __repr__(self) -> str: - """ - Print a generic n-ary operator and its operands using infix notation. - """ - # recurse over the operands - parened = (f"({pprint_thing(opr)})" for opr in self.operands) - return pprint_thing(f" {self.op} ".join(parened)) - - @property - def return_type(self): - # clobber types to bool if the op is a boolean operator - if self.op in (CMP_OPS_SYMS + BOOL_OPS_SYMS): - return np.bool_ - return result_type_many(*(term.type for term in com.flatten(self))) - - @property - def has_invalid_return_type(self) -> bool: - types = self.operand_types - obj_dtype_set = frozenset([np.dtype("object")]) - return self.return_type == object and types - obj_dtype_set - - @property - def operand_types(self): - return frozenset(term.type for term in com.flatten(self)) - - @property - def is_scalar(self) -> bool: - return all(operand.is_scalar for operand in self.operands) - - @property - def is_datetime(self) -> bool: - try: - t = self.return_type.type - except AttributeError: - t = self.return_type - - return issubclass(t, (datetime, np.datetime64)) - - -def _in(x, y): - """ - Compute the vectorized membership of ``x in y`` if possible, otherwise - use Python. - """ - try: - return x.isin(y) - except AttributeError: - if is_list_like(x): - try: - return y.isin(x) - except AttributeError: - pass - return x in y - - -def _not_in(x, y): - """ - Compute the vectorized membership of ``x not in y`` if possible, - otherwise use Python. - """ - try: - return ~x.isin(y) - except AttributeError: - if is_list_like(x): - try: - return ~y.isin(x) - except AttributeError: - pass - return x not in y - - -CMP_OPS_SYMS = (">", "<", ">=", "<=", "==", "!=", "in", "not in") -_cmp_ops_funcs = ( - operator.gt, - operator.lt, - operator.ge, - operator.le, - operator.eq, - operator.ne, - _in, - _not_in, -) -_cmp_ops_dict = dict(zip(CMP_OPS_SYMS, _cmp_ops_funcs)) - -BOOL_OPS_SYMS = ("&", "|", "and", "or") -_bool_ops_funcs = (operator.and_, operator.or_, operator.and_, operator.or_) -_bool_ops_dict = dict(zip(BOOL_OPS_SYMS, _bool_ops_funcs)) - -ARITH_OPS_SYMS = ("+", "-", "*", "/", "**", "//", "%") -_arith_ops_funcs = ( - operator.add, - operator.sub, - operator.mul, - operator.truediv, - operator.pow, - operator.floordiv, - operator.mod, -) -_arith_ops_dict = dict(zip(ARITH_OPS_SYMS, _arith_ops_funcs)) - -SPECIAL_CASE_ARITH_OPS_SYMS = ("**", "//", "%") -_special_case_arith_ops_funcs = (operator.pow, operator.floordiv, operator.mod) -_special_case_arith_ops_dict = dict( - zip(SPECIAL_CASE_ARITH_OPS_SYMS, _special_case_arith_ops_funcs) -) - -_binary_ops_dict = {} - -for d in (_cmp_ops_dict, _bool_ops_dict, _arith_ops_dict): - _binary_ops_dict.update(d) - - -def _cast_inplace(terms, acceptable_dtypes, dtype) -> None: - """ - Cast an expression inplace. - - Parameters - ---------- - terms : Op - The expression that should cast. - acceptable_dtypes : list of acceptable numpy.dtype - Will not cast if term's dtype in this list. - dtype : str or numpy.dtype - The dtype to cast to. - """ - dt = np.dtype(dtype) - for term in terms: - if term.type in acceptable_dtypes: - continue - - try: - new_value = term.value.astype(dt) - except AttributeError: - new_value = dt.type(term.value) - term.update(new_value) - - -def is_term(obj) -> bool: - return isinstance(obj, Term) - - -class BinOp(Op): - """ - Hold a binary operator and its operands. - - Parameters - ---------- - op : str - lhs : Term or Op - rhs : Term or Op - """ - - def __init__(self, op: str, lhs, rhs) -> None: - super().__init__(op, (lhs, rhs)) - self.lhs = lhs - self.rhs = rhs - - self._disallow_scalar_only_bool_ops() - - self.convert_values() - - try: - self.func = _binary_ops_dict[op] - except KeyError as err: - # has to be made a list for python3 - keys = list(_binary_ops_dict.keys()) - raise ValueError( - f"Invalid binary operator {repr(op)}, valid operators are {keys}" - ) from err - - def __call__(self, env): - """ - Recursively evaluate an expression in Python space. - - Parameters - ---------- - env : Scope - - Returns - ------- - object - The result of an evaluated expression. - """ - # recurse over the left/right nodes - left = self.lhs(env) - right = self.rhs(env) - - return self.func(left, right) - - def evaluate(self, env, engine: str, parser, term_type, eval_in_python): - """ - Evaluate a binary operation *before* being passed to the engine. - - Parameters - ---------- - env : Scope - engine : str - parser : str - term_type : type - eval_in_python : list - - Returns - ------- - term_type - The "pre-evaluated" expression as an instance of ``term_type`` - """ - if engine == "python": - res = self(env) - else: - # recurse over the left/right nodes - - left = self.lhs.evaluate( - env, - engine=engine, - parser=parser, - term_type=term_type, - eval_in_python=eval_in_python, - ) - - right = self.rhs.evaluate( - env, - engine=engine, - parser=parser, - term_type=term_type, - eval_in_python=eval_in_python, - ) - - # base cases - if self.op in eval_in_python: - res = self.func(left.value, right.value) - else: - from pandas.core.computation.eval import eval - - res = eval(self, local_dict=env, engine=engine, parser=parser) - - name = env.add_tmp(res) - return term_type(name, env=env) - - def convert_values(self) -> None: - """ - Convert datetimes to a comparable value in an expression. - """ - - def stringify(value): - encoder: Callable - if self.encoding is not None: - encoder = partial(pprint_thing_encoded, encoding=self.encoding) - else: - encoder = pprint_thing - return encoder(value) - - lhs, rhs = self.lhs, self.rhs - - if is_term(lhs) and lhs.is_datetime and is_term(rhs) and rhs.is_scalar: - v = rhs.value - if isinstance(v, (int, float)): - v = stringify(v) - v = Timestamp(ensure_decoded(v)) - if v.tz is not None: - v = v.tz_convert("UTC") - self.rhs.update(v) - - if is_term(rhs) and rhs.is_datetime and is_term(lhs) and lhs.is_scalar: - v = lhs.value - if isinstance(v, (int, float)): - v = stringify(v) - v = Timestamp(ensure_decoded(v)) - if v.tz is not None: - v = v.tz_convert("UTC") - self.lhs.update(v) - - def _disallow_scalar_only_bool_ops(self): - rhs = self.rhs - lhs = self.lhs - - # GH#24883 unwrap dtype if necessary to ensure we have a type object - rhs_rt = rhs.return_type - rhs_rt = getattr(rhs_rt, "type", rhs_rt) - lhs_rt = lhs.return_type - lhs_rt = getattr(lhs_rt, "type", lhs_rt) - if ( - (lhs.is_scalar or rhs.is_scalar) - and self.op in _bool_ops_dict - and ( - not ( - issubclass(rhs_rt, (bool, np.bool_)) - and issubclass(lhs_rt, (bool, np.bool_)) - ) - ) - ): - raise NotImplementedError("cannot evaluate scalar only bool ops") - - -def isnumeric(dtype) -> bool: - return issubclass(np.dtype(dtype).type, np.number) - - -class Div(BinOp): - """ - Div operator to special case casting. - - Parameters - ---------- - lhs, rhs : Term or Op - The Terms or Ops in the ``/`` expression. - """ - - def __init__(self, lhs, rhs) -> None: - super().__init__("/", lhs, rhs) - - if not isnumeric(lhs.return_type) or not isnumeric(rhs.return_type): - raise TypeError( - f"unsupported operand type(s) for {self.op}: " - f"'{lhs.return_type}' and '{rhs.return_type}'" - ) - - # do not upcast float32s to float64 un-necessarily - acceptable_dtypes = [np.float32, np.float64] - _cast_inplace(com.flatten(self), acceptable_dtypes, np.float64) - - -UNARY_OPS_SYMS = ("+", "-", "~", "not") -_unary_ops_funcs = (operator.pos, operator.neg, operator.invert, operator.invert) -_unary_ops_dict = dict(zip(UNARY_OPS_SYMS, _unary_ops_funcs)) - - -class UnaryOp(Op): - """ - Hold a unary operator and its operands. - - Parameters - ---------- - op : str - The token used to represent the operator. - operand : Term or Op - The Term or Op operand to the operator. - - Raises - ------ - ValueError - * If no function associated with the passed operator token is found. - """ - - def __init__(self, op: Literal["+", "-", "~", "not"], operand) -> None: - super().__init__(op, (operand,)) - self.operand = operand - - try: - self.func = _unary_ops_dict[op] - except KeyError as err: - raise ValueError( - f"Invalid unary operator {repr(op)}, " - f"valid operators are {UNARY_OPS_SYMS}" - ) from err - - def __call__(self, env) -> MathCall: - operand = self.operand(env) - # error: Cannot call function of unknown type - return self.func(operand) # type: ignore[operator] - - def __repr__(self) -> str: - return pprint_thing(f"{self.op}({self.operand})") - - @property - def return_type(self) -> np.dtype: - operand = self.operand - if operand.return_type == np.dtype("bool"): - return np.dtype("bool") - if isinstance(operand, Op) and ( - operand.op in _cmp_ops_dict or operand.op in _bool_ops_dict - ): - return np.dtype("bool") - return np.dtype("int") - - -class MathCall(Op): - def __init__(self, func, args) -> None: - super().__init__(func.name, args) - self.func = func - - def __call__(self, env): - # error: "Op" not callable - operands = [op(env) for op in self.operands] # type: ignore[operator] - return self.func.func(*operands) - - def __repr__(self) -> str: - operands = map(str, self.operands) - return pprint_thing(f"{self.op}({','.join(operands)})") - - -class FuncNode: - def __init__(self, name: str) -> None: - if name not in MATHOPS: - raise ValueError(f'"{name}" is not a supported function') - self.name = name - self.func = getattr(np, name) - - def __call__(self, *args): - return MathCall(self, args) diff --git a/third_party/bigframes_vendored/pandas/core/computation/parsing.py b/third_party/bigframes_vendored/pandas/core/computation/parsing.py deleted file mode 100644 index 569c3c50330..00000000000 --- a/third_party/bigframes_vendored/pandas/core/computation/parsing.py +++ /dev/null @@ -1,197 +0,0 @@ -# Contains code from https://github.com/pandas-dev/pandas/blob/main/pandas/core/computation/parsing.py -""" -:func:`~pandas.eval` source string parsing functions -""" - -from __future__ import annotations - -import token -import tokenize -from io import StringIO -from keyword import iskeyword -from typing import TYPE_CHECKING - -if TYPE_CHECKING: - from collections.abc import Hashable, Iterator - -# A token value Python's tokenizer probably will never use. -BACKTICK_QUOTED_STRING = 100 - - -def create_valid_python_identifier(name: str) -> str: - """ - Create valid Python identifiers from any string. - - Check if name contains any special characters. If it contains any - special characters, the special characters will be replaced by - a special string and a prefix is added. - - Raises - ------ - SyntaxError - If the returned name is not a Python valid identifier, raise an exception. - This can happen if there is a hashtag in the name, as the tokenizer will - than terminate and not find the backtick. - But also for characters that fall out of the range of (U+0001..U+007F). - """ - if name.isidentifier() and not iskeyword(name): - return name - - # Create a dict with the special characters and their replacement string. - # EXACT_TOKEN_TYPES contains these special characters - # token.tok_name contains a readable description of the replacement string. - special_characters_replacements = { - char: f"_{token.tok_name[tokval]}_" - for char, tokval in (tokenize.EXACT_TOKEN_TYPES.items()) - } - special_characters_replacements.update( - { - " ": "_", - "?": "_QUESTIONMARK_", - "!": "_EXCLAMATIONMARK_", - "$": "_DOLLARSIGN_", - "€": "_EUROSIGN_", - "°": "_DEGREESIGN_", - # Including quotes works, but there are exceptions. - "'": "_SINGLEQUOTE_", - '"': "_DOUBLEQUOTE_", - # Currently not possible. Terminates parser and won't find backtick. - # "#": "_HASH_", - } - ) - - name = "".join([special_characters_replacements.get(char, char) for char in name]) - name = f"BACKTICK_QUOTED_STRING_{name}" - - if not name.isidentifier(): - raise SyntaxError(f"Could not convert '{name}' to a valid Python identifier.") - - return name - - -def clean_backtick_quoted_toks(tok: tuple[int, str]) -> tuple[int, str]: - """ - Clean up a column name if surrounded by backticks. - - Backtick quoted string are indicated by a certain tokval value. If a string - is a backtick quoted token it will processed by - :func:`_create_valid_python_identifier` so that the parser can find this - string when the query is executed. - In this case the tok will get the NAME tokval. - - Parameters - ---------- - tok : tuple of int, str - ints correspond to the all caps constants in the tokenize module - - Returns - ------- - tok : Tuple[int, str] - Either the input or token or the replacement values - """ - toknum, tokval = tok - if toknum == BACKTICK_QUOTED_STRING: - return tokenize.NAME, create_valid_python_identifier(tokval) - return toknum, tokval - - -def clean_column_name(name: Hashable) -> Hashable: - """ - Function to emulate the cleaning of a backtick quoted name. - - The purpose for this function is to see what happens to the name of - identifier if it goes to the process of being parsed a Python code - inside a backtick quoted string and than being cleaned - (removed of any special characters). - - Parameters - ---------- - name : hashable - Name to be cleaned. - - Returns - ------- - name : hashable - Returns the name after tokenizing and cleaning. - - Notes - ----- - For some cases, a name cannot be converted to a valid Python identifier. - In that case :func:`tokenize_string` raises a SyntaxError. - In that case, we just return the name unmodified. - - If this name was used in the query string (this makes the query call impossible) - an error will be raised by :func:`tokenize_backtick_quoted_string` instead, - which is not caught and propagates to the user level. - """ - try: - tokenized = tokenize_string(f"`{name}`") - tokval = next(tokenized)[1] - return create_valid_python_identifier(tokval) - except SyntaxError: - return name - - -def tokenize_backtick_quoted_string( - token_generator: Iterator[tokenize.TokenInfo], source: str, string_start: int -) -> tuple[int, str]: - """ - Creates a token from a backtick quoted string. - - Moves the token_generator forwards till right after the next backtick. - - Parameters - ---------- - token_generator : Iterator[tokenize.TokenInfo] - The generator that yields the tokens of the source string (Tuple[int, str]). - The generator is at the first token after the backtick (`) - - source : str - The Python source code string. - - string_start : int - This is the start of backtick quoted string inside the source string. - - Returns - ------- - tok: Tuple[int, str] - The token that represents the backtick quoted string. - The integer is equal to BACKTICK_QUOTED_STRING (100). - """ - for _, tokval, start, _, _ in token_generator: - if tokval == "`": - string_end = start[1] - break - - return BACKTICK_QUOTED_STRING, source[string_start:string_end] - - -def tokenize_string(source: str) -> Iterator[tuple[int, str]]: - """ - Tokenize a Python source code string. - - Parameters - ---------- - source : str - The Python source code string. - - Returns - ------- - tok_generator : Iterator[Tuple[int, str]] - An iterator yielding all tokens with only toknum and tokval (Tuple[ing, str]). - """ - line_reader = StringIO(source).readline - token_generator = tokenize.generate_tokens(line_reader) - - # Loop over all tokens till a backtick (`) is found. - # Then, take all tokens till the next backtick to form a backtick quoted string - for toknum, tokval, start, _, _ in token_generator: - if tokval == "`": - try: - yield tokenize_backtick_quoted_string( - token_generator, source, string_start=start[1] + 1 - ) - except Exception as err: - raise SyntaxError(f"Failed to parse backticks in '{source}'.") from err - else: - yield toknum, tokval diff --git a/third_party/bigframes_vendored/pandas/core/computation/scope.py b/third_party/bigframes_vendored/pandas/core/computation/scope.py deleted file mode 100644 index 51b15e74a27..00000000000 --- a/third_party/bigframes_vendored/pandas/core/computation/scope.py +++ /dev/null @@ -1,356 +0,0 @@ -# Contains code from https://github.com/pandas-dev/pandas/blob/main/pandas/core/computation/scope.py -""" -Module for scope operations -""" - -from __future__ import annotations - -import datetime -import inspect -import itertools -import pprint -import struct -import sys -from collections import ChainMap -from io import StringIO -from typing import TypeVar - -import numpy as np -from pandas._libs.tslibs import Timestamp -from pandas.errors import UndefinedVariableError - -_KT = TypeVar("_KT") -_VT = TypeVar("_VT") - - -# https://docs.python.org/3/library/collections.html#chainmap-examples-and-recipes -class DeepChainMap(ChainMap[_KT, _VT]): - """ - Variant of ChainMap that allows direct updates to inner scopes. - - Only works when all passed mapping are mutable. - """ - - def __setitem__(self, key: _KT, value: _VT) -> None: - for mapping in self.maps: - if key in mapping: - mapping[key] = value - return - self.maps[0][key] = value - - def __delitem__(self, key: _KT) -> None: - """ - Raises - ------ - KeyError - If `key` doesn't exist. - """ - for mapping in self.maps: - if key in mapping: - del mapping[key] - return - raise KeyError(key) - - -def ensure_scope( - level: int, global_dict=None, local_dict=None, resolvers=(), target=None -) -> Scope: - """Ensure that we are grabbing the correct scope.""" - return Scope( - level + 1, - global_dict=global_dict, - local_dict=local_dict, - resolvers=resolvers, - target=target, - ) - - -def _replacer(x) -> str: - """ - Replace a number with its hexadecimal representation. Used to tag - temporary variables with their calling scope's id. - """ - # get the hex repr of the binary char and remove 0x and pad by pad_size - # zeros - try: - hexin = ord(x) - except TypeError: - # bytes literals masquerade as ints when iterating in py3 - hexin = x - - return hex(hexin) - - -def _raw_hex_id(obj) -> str: - """Return the padded hexadecimal id of ``obj``.""" - # interpret as a pointer since that's what really what id returns - packed = struct.pack("@P", id(obj)) - return "".join([_replacer(x) for x in packed]) - - -DEFAULT_GLOBALS = { - "Timestamp": Timestamp, - "datetime": datetime.datetime, - "True": True, - "False": False, - "list": list, - "tuple": tuple, - "inf": np.inf, - "Inf": np.inf, -} - - -def _get_pretty_string(obj) -> str: - """ - Return a prettier version of obj. - - Parameters - ---------- - obj : object - Object to pretty print - - Returns - ------- - str - Pretty print object repr - """ - sio = StringIO() - pprint.pprint(obj, stream=sio) - return sio.getvalue() - - -class Scope: - """ - Object to hold scope, with a few bells to deal with some custom syntax - and contexts added by pandas. - - Parameters - ---------- - level : int - global_dict : dict or None, optional, default None - local_dict : dict or Scope or None, optional, default None - resolvers : list-like or None, optional, default None - target : object - - Attributes - ---------- - level : int - scope : DeepChainMap - target : object - temps : dict - """ - - __slots__ = ["level", "scope", "target", "resolvers", "temps"] - level: int - scope: DeepChainMap - resolvers: DeepChainMap - temps: dict - - def __init__( - self, level: int, global_dict=None, local_dict=None, resolvers=(), target=None - ) -> None: - self.level = level + 1 - - # shallow copy because we don't want to keep filling this up with what - # was there before if there are multiple calls to Scope/_ensure_scope - self.scope = DeepChainMap(DEFAULT_GLOBALS.copy()) - self.target = target - - if isinstance(local_dict, Scope): - self.scope.update(local_dict.scope) - if local_dict.target is not None: - self.target = local_dict.target - self._update(local_dict.level) - - frame = sys._getframe(self.level) - - try: - # shallow copy here because we don't want to replace what's in - # scope when we align terms (alignment accesses the underlying - # numpy array of pandas objects) - scope_global = self.scope.new_child( - (global_dict if global_dict is not None else frame.f_globals).copy() - ) - self.scope = DeepChainMap(scope_global) - if not isinstance(local_dict, Scope): - scope_local = self.scope.new_child( - (local_dict if local_dict is not None else frame.f_locals).copy() - ) - self.scope = DeepChainMap(scope_local) - finally: - del frame - - # assumes that resolvers are going from outermost scope to inner - if isinstance(local_dict, Scope): - resolvers += tuple(local_dict.resolvers.maps) - self.resolvers = DeepChainMap(*resolvers) - self.temps = {} - - def __repr__(self) -> str: - scope_keys = _get_pretty_string(list(self.scope.keys())) - res_keys = _get_pretty_string(list(self.resolvers.keys())) - return f"{type(self).__name__}(scope={scope_keys}, resolvers={res_keys})" - - @property - def has_resolvers(self) -> bool: - """ - Return whether we have any extra scope. - - For example, DataFrames pass Their columns as resolvers during calls to - ``DataFrame.eval()`` and ``DataFrame.query()``. - - Returns - ------- - hr : bool - """ - return bool(len(self.resolvers)) - - def resolve(self, key: str, is_local: bool): - """ - Resolve a variable name in a possibly local context. - - Parameters - ---------- - key : str - A variable name - is_local : bool - Flag indicating whether the variable is local or not (prefixed with - the '@' symbol) - - Returns - ------- - value : object - The value of a particular variable - """ - try: - # only look for locals in outer scope - if is_local: - return self.scope[key] - - # not a local variable so check in resolvers if we have them - if self.has_resolvers: - return self.resolvers[key] - - # if we're here that means that we have no locals and we also have - # no resolvers - assert not is_local and not self.has_resolvers - return self.scope[key] - except KeyError: - try: - # last ditch effort we look in temporaries - # these are created when parsing indexing expressions - # e.g., df[df > 0] - return self.temps[key] - except KeyError as err: - raise UndefinedVariableError(key, is_local) from err - - def swapkey(self, old_key: str, new_key: str, new_value=None) -> None: - """ - Replace a variable name, with a potentially new value. - - Parameters - ---------- - old_key : str - Current variable name to replace - new_key : str - New variable name to replace `old_key` with - new_value : object - Value to be replaced along with the possible renaming - """ - if self.has_resolvers: - maps = self.resolvers.maps + self.scope.maps - else: - maps = self.scope.maps - - maps.append(self.temps) - - for mapping in maps: - if old_key in mapping: - mapping[new_key] = new_value - return - - def _get_vars(self, stack, scopes: list[str]) -> None: - """ - Get specifically scoped variables from a list of stack frames. - - Parameters - ---------- - stack : list - A list of stack frames as returned by ``inspect.stack()`` - scopes : sequence of strings - A sequence containing valid stack frame attribute names that - evaluate to a dictionary. For example, ('locals', 'globals') - """ - variables = itertools.product(scopes, stack) - for scope, (frame, _, _, _, _, _) in variables: - try: - d = getattr(frame, f"f_{scope}") - self.scope = DeepChainMap(self.scope.new_child(d)) - finally: - # won't remove it, but DECREF it - # in Py3 this probably isn't necessary since frame won't be - # scope after the loop - del frame - - def _update(self, level: int) -> None: - """ - Update the current scope by going back `level` levels. - - Parameters - ---------- - level : int - """ - sl = level + 1 - - # add sl frames to the scope starting with the - # most distant and overwriting with more current - # makes sure that we can capture variable scope - stack = inspect.stack() - - try: - self._get_vars(stack[:sl], scopes=["locals"]) - finally: - del stack[:], stack - - def add_tmp(self, value) -> str: - """ - Add a temporary variable to the scope. - - Parameters - ---------- - value : object - An arbitrary object to be assigned to a temporary variable. - - Returns - ------- - str - The name of the temporary variable created. - """ - name = f"{type(value).__name__}_{self.ntemps}_{_raw_hex_id(self)}" - - # add to inner most scope - assert name not in self.temps - self.temps[name] = value - assert name in self.temps - - # only increment if the variable gets put in the scope - return name - - @property - def ntemps(self) -> int: - """The number of temporary variables in this scope""" - return len(self.temps) - - @property - def full_scope(self) -> DeepChainMap: - """ - Return the full scope for use with passing to engines transparently - as a mapping. - - Returns - ------- - vars : DeepChainMap - All variables in this scope. - """ - maps = [self.temps] + self.resolvers.maps + self.scope.maps - return DeepChainMap(*maps) diff --git a/third_party/bigframes_vendored/pandas/core/config_init.py b/third_party/bigframes_vendored/pandas/core/config_init.py index bd40d05154b..198654015e0 100644 --- a/third_party/bigframes_vendored/pandas/core/config_init.py +++ b/third_party/bigframes_vendored/pandas/core/config_init.py @@ -1,5 +1,4 @@ -# Contains code from -# https://github.com/pandas-dev/pandas/blob/main/pandas/core/config_init.py +# Contains code from https://github.com/pandas-dev/pandas/blob/main/pandas/core/config_init.py """ This module is imported from the pandas package __init__.py file in order to ensure that the core.config options registered here will @@ -11,229 +10,50 @@ module is imported, register them here rather than in the module. """ - from __future__ import annotations -import dataclasses -from typing import Literal, Optional - - -@dataclasses.dataclass -class DisplayOptions: - """ - Encapsulates the configuration for displaying objects. - - **Examples:** - - Define Repr mode to "deferred" will prevent job execution in repr. - - >>> import bigframes.pandas as bpd - >>> df = bpd.read_gbq("bigquery-public-data.ml_datasets.penguins") - - >>> bpd.options.display.repr_mode = "deferred" # doctest: +SKIP - >>> df.head(20) # will no longer run the job # doctest: +SKIP - Computation deferred. Computation will process 28.9 kB - - Users can also get a dry run of the job by accessing the query_job - property before they've run the job. This will return a dry run - instance of the job they can inspect. - - >>> df.query_job.total_bytes_processed # doctest: +SKIP - 28947 - - User can execute the job by calling .to_pandas() - - >>> # df.to_pandas() - - Reset repr_mode option - - >>> bpd.options.display.repr_mode = "head" # doctest: +SKIP - - Can also set the progress_bar option to see the progress bar in terminal, - - >>> bpd.options.display.progress_bar = "terminal" # doctest: +SKIP - - notebook, - - >>> bpd.options.display.progress_bar = "notebook" # doctest: +SKIP - - or just remove it. - - Setting to default value "auto" will detect and show progress bar - automatically. - - >>> bpd.options.display.progress_bar = "auto" # doctest: +SKIP - """ - - # Options borrowed from pandas. - max_columns: int = 20 - """ - Maximum number of columns to display. Default 20. - - If `max_columns` is exceeded, switch to truncate view. - - **Examples:** - - >>> import bigframes.pandas as bpd - >>> bpd.options.display.max_columns = 50 # doctest: +SKIP - """ - - max_rows: int = 10 - """ - Maximum number of rows to display. Default 10. - - If `max_rows` is exceeded, switch to truncate view. - - **Examples:** - - >>> import bigframes.pandas as bpd - >>> bpd.options.display.max_rows = 50 # doctest: +SKIP - """ - - precision: int = 6 - """ - Controls the floating point output precision. Defaults to 6. - - See :attr:`pandas.options.display.precision`. - - **Examples:** - - >>> import bigframes.pandas as bpd - >>> bpd.options.display.precision = 2 # doctest: +SKIP - """ - - # Options unique to BigQuery DataFrames. - progress_bar: Optional[Literal["auto", "notebook", "terminal"]] = "auto" - """ - Determines if progress bars are shown during job runs. Default "auto". - - Valid values are `auto`, `notebook`, and `terminal`. Set - to `None` to remove progress bars. - - **Examples:** - - >>> import bigframes.pandas as bpd - >>> bpd.options.display.progress_bar = "terminal" # doctest: +SKIP - """ - - repr_mode: Literal["head", "deferred", "anywidget"] = "head" - """ - Determines how to display a DataFrame or Series. Default "head". - - `head` - Execute, download, and display results (limited to head) from - Dataframe and Series objects during repr. - - `deferred` - Prevent executions from repr statements in DataFrame and - Series objects. - Instead, estimated bytes processed will be shown. DataFrame and Series - objects can still be computed with methods that explicitly execute and - download results. - - `anywidget` - Display as interactive widget using `anywidget` library. - - **Examples:** - - >>> import bigframes.pandas as bpd - >>> bpd.options.display.repr_mode = "deferred" # doctest: +SKIP - """ - - render_mode: Literal["plaintext", "html", "anywidget"] = "html" - """ - Determines how to visualize a DataFrame or Series. Default "html". - - `plaintext` - Display as plain text. - - `html` - Display as HTML table. - - `anywidget` - Display as interactive widget using `anywidget` library. - """ - - max_colwidth: Optional[int] = 50 - """ - The maximum width in characters of a column in the repr. Default 50. - - When the column overflows, a "..." placeholder is embedded in the output. A - 'None' value means unlimited. - - **Examples:** - - >>> import bigframes.pandas as bpd - >>> bpd.options.display.max_colwidth = 20 # doctest: +SKIP - """ - - max_info_columns: int = 100 - """ - Used in DataFrame.info method to decide if information in each column will - be printed. Default 100. - - **Examples:** - - >>> import bigframes.pandas as bpd - >>> bpd.options.display.max_info_columns = 50 # doctest: +SKIP - """ - - max_info_rows: Optional[int] = 200_000 - """ - Limit null check in ``df.info()`` only to frames with smaller - dimensions than - max_info_rows. Default 200,000. - - df.info() will usually show null-counts for each column. - For large frames, this can be quite slow. max_info_rows and max_info_cols - limit this null check only to frames with smaller dimensions than - specified. - - **Examples:** - - >>> import bigframes.pandas as bpd - >>> bpd.options.display.max_info_rows = 100 # doctest: +SKIP - """ - - memory_usage: bool = True - """ - If True, memory usage of a DataFrame should be displayed when - df.info() is called. Default True. - - Valid values True, False. - - **Examples:** - - >>> import bigframes.pandas as bpd - >>> bpd.options.display.memory_usage = False # doctest: +SKIP - """ - - blob_display: bool = True - """ - If True, display the blob content in notebook DataFrame preview. Default - True. - - **Examples:** - - >>> import bigframes.pandas as bpd - >>> bpd.options.display.blob_display = True # doctest: +SKIP - """ - - blob_display_width: Optional[int] = None - """ - Width in pixels that the blob constrained to. Default None.. - - **Examples:** - - >>> import bigframes.pandas as bpd - >>> bpd.options.display.blob_display_width = 100 # doctest: +SKIP - """ - blob_display_height: Optional[int] = None - """ - Height in pixels that the blob constrained to. Default None.. - - **Examples:** +display_options_doc = """ +Encapsulates configuration for displaying objects. + +Attributes: + max_columns (int, default 20): + If `max_columns` is exceeded, switch to truncate view. + max_rows (int, default 25): + If `max_rows` is exceeded, switch to truncate view. + progress_bar (Optional(str), default "auto"): + Determines if progress bars are shown during job runs. + Valid values are `auto`, `notebook`, and `terminal`. Set + to `None` to remove progress bars. + repr_mode (Literal[`head`, `deferred`]): + `head`: + Execute, download, and display results (limited to head) from + dataframe and series objects during repr. + `deferred`: + Prevent executions from repr statements in dataframe and series objects. + Instead estimated bytes processed will be shown. Dataframe and Series + objects can still be computed with methods that explicitly execute and + download results. +""" - >>> import bigframes.pandas as bpd - >>> bpd.options.display.blob_display_height = 100 # doctest: +SKIP - """ +sampling_options_doc = """ +Encapsulates configuration for data sampling. + +Attributes: + max_download_size (int, default 500): + Download size threshold in MB. If value set to None, the download size + won't be checked. + enable_downsampling (bool, default False): + Whether to enable downsampling, If max_download_size is exceeded when + downloading data (e.g., to_pandas()), the data will be downsampled + if enable_downsampling is True, otherwise, an error will be raised. + sampling_method (str, default "uniform"): + Downsampling algorithms to be chosen from, the choices are: + "head": This algorithm returns a portion of the data from + the beginning. It is fast and requires minimal computations + to perform the downsampling.; "uniform": This algorithm returns + uniform random samples of the data. + random_state (int, default None): + The seed for the uniform downsampling algorithm. If provided, + the uniform method may take longer to execute and require more + computation. +""" diff --git a/third_party/bigframes_vendored/pandas/core/dtypes/inference.py b/third_party/bigframes_vendored/pandas/core/dtypes/inference.py deleted file mode 100644 index 7875c297bc2..00000000000 --- a/third_party/bigframes_vendored/pandas/core/dtypes/inference.py +++ /dev/null @@ -1,31 +0,0 @@ -# Contains code from https://github.com/pandas-dev/pandas/blob/main/pandas/core/dtypes/inference.py -"""basic inference routines""" - -from __future__ import annotations - -from collections import abc - - -def iterable_not_string(obj) -> bool: - """ - Check if the object is an iterable but not a string. - - Parameters - ---------- - obj : The object to check. - - Returns - ------- - is_iter_not_string : bool - Whether `obj` is a non-string iterable. - - Examples - -------- - >>> iterable_not_string([1, 2, 3]) - True - >>> iterable_not_string("foo") - False - >>> iterable_not_string(1) - False - """ - return isinstance(obj, abc.Iterable) and not isinstance(obj, str) diff --git a/third_party/bigframes_vendored/pandas/core/frame.py b/third_party/bigframes_vendored/pandas/core/frame.py index e84f46861d9..6f4f6be35d6 100644 --- a/third_party/bigframes_vendored/pandas/core/frame.py +++ b/third_party/bigframes_vendored/pandas/core/frame.py @@ -9,23 +9,20 @@ alignment and a host of useful data manipulation methods having to do with the labeling information """ - from __future__ import annotations -import datetime -from typing import Hashable, Iterable, Literal, Optional, Sequence, Union +from typing import Literal, Mapping, Optional, Sequence, Union -import bigframes_vendored.pandas.core.generic as generic import numpy as np -import pandas as pd -from bigframes_vendored import constants -from pandas.api import extensions as pd_ext + +from bigframes import constants +from third_party.bigframes_vendored.pandas.core.generic import NDFrame # ----------------------------------------------------------------------- # DataFrame class -class DataFrame(generic.NDFrame): +class DataFrame(NDFrame): """Two-dimensional, size-mutable, potentially heterogeneous tabular data. Data structure also contains labeled axes (rows and columns). @@ -41,15 +38,13 @@ def shape(self) -> tuple[int, int]: **Examples:** + >>> import bigframes.pandas as bpd + >>> bpd.options.display.progress_bar = None >>> df = bpd.DataFrame({'col1': [1, 2, 3], ... 'col2': [4, 5, 6]}) >>> df.shape (3, 2) - - Returns: - Tuple[int, int]: - Tuple of array dimensions. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @@ -63,12 +58,14 @@ def axes(self) -> list: **Examples:** + >>> import bigframes.pandas as bpd + >>> bpd.options.display.progress_bar = None >>> df = bpd.DataFrame({'col1': [1, 2], 'col2': [3, 4]}) >>> df.axes[1:] - [Index(['col1', 'col2'], dtype='str')] + [Index(['col1', 'col2'], dtype='object')] """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) + return [self.index, self.columns] @property def values(self) -> np.ndarray: @@ -76,6 +73,8 @@ def values(self) -> np.ndarray: **Examples:** + >>> import bigframes.pandas as bpd + >>> bpd.options.display.progress_bar = None >>> df = bpd.DataFrame({'col1': [1, 2], 'col2': [3, 4]}) >>> df.values @@ -90,286 +89,19 @@ def values(self) -> np.ndarray: on another array. na_value (default None): The value to use for missing values. - - Returns: - numpy.ndarray: - The values of the DataFrame. - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - @property - def T(self) -> DataFrame: - """ - The transpose of the DataFrame. - - All columns must be the same dtype (numerics can be coerced to a common supertype). - - **Examples:** - - >>> df = bpd.DataFrame({'col1': [1, 2], 'col2': [3, 4]}) - >>> df - col1 col2 - 0 1 3 - 1 2 4 - - [2 rows x 2 columns] - - >>> df.T - 0 1 - col1 1 2 - col2 3 4 - - [2 rows x 2 columns] - - Returns: - bigframes.pandas.DataFrame: The transposed DataFrame. - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - def transpose(self) -> DataFrame: - """ - Transpose index and columns. - - Reflect the DataFrame over its main diagonal by writing rows as columns - and vice-versa. The property :attr:`.T` is an accessor to the method - :meth:`transpose`. - - All columns must be the same dtype (numerics can be coerced to a common supertype). - - **Examples:** - - **Square DataFrame with homogeneous dtype** - - - >>> d1 = {'col1': [1, 2], 'col2': [3, 4]} - >>> df1 = bpd.DataFrame(data=d1) - >>> df1 - col1 col2 - 0 1 3 - 1 2 4 - - [2 rows x 2 columns] - - >>> df1_transposed = df1.T # or df1.transpose() - >>> df1_transposed - 0 1 - col1 1 2 - col2 3 4 - - [2 rows x 2 columns] - - When the dtype is homogeneous in the original DataFrame, we get a - transposed DataFrame with the same dtype: - - >>> df1.dtypes - col1 Int64 - col2 Int64 - dtype: object - >>> df1_transposed.dtypes - 0 Int64 - 1 Int64 - dtype: object - - Returns: - bigframes.pandas.DataFrame: The transposed DataFrame. - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - def info( - self, - verbose: bool | None = None, - buf=None, - max_cols: int | None = None, - memory_usage: bool | None = None, - show_counts: bool | None = None, - ) -> None: - """ - Print a concise summary of a DataFrame. - - This method prints information about a DataFrame including - the index dtypeand columns, non-null values and memory usage. - - Args: - verbose (bool, optional): - Whether to print the full summary. By default, the setting in - ``pandas.options.display.max_info_columns`` is followed. - buf (writable buffer, defaults to sys.stdout): - Where to send the output. By default, the output is printed to - sys.stdout. Pass a writable buffer if you need to further process - the output. - max_cols (int, optional): - When to switch from the verbose to the truncated output. If the - DataFrame has more than `max_cols` columns, the truncated output - is used. By default, the setting in - ``pandas.options.display.max_info_columns`` is used. - memory_usage (bool, optional): - Specifies whether total memory usage of the DataFrame - elements (including the index) should be displayed. By default, - this follows the ``pandas.options.display.memory_usage`` setting. - True always show memory usage. False never shows memory usage. - Memory estimation is made based in column dtype and number of rows - assuming values consume the same memory amount for corresponding dtypes. - show_counts (bool, optional): - Whether to show the non-null counts. By default, this is shown - only if the DataFrame is smaller than - ``pandas.options.display.max_info_rows`` and - ``pandas.options.display.max_info_columns``. A value of True always - shows the counts, and False never shows the counts. - - Returns: - None: This method prints a summary of a DataFrame and returns None. - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - def memory_usage(self, index: bool = True): - """ - Return the memory usage of each column in bytes. - - The memory usage can optionally include the contribution of - the index and elements of `object` dtype. - - This value is displayed in `DataFrame.info` by default. This can be - suppressed by setting ``pandas.options.display.memory_usage`` to False. - - Args: - index (bool, default True): - Specifies whether to include the memory usage of the DataFrame's - index in returned Series. If ``index=True``, the memory usage of - the index is the first item in the output. - - Returns: - bigframes.pandas.Series: A Series whose index is the original column names and whose values is the memory usage of each column in bytes. - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - def select_dtypes(self, include=None, exclude=None) -> DataFrame: - """ - Return a subset of the DataFrame's columns based on the column dtypes. - - **Examples:** - - - >>> df = bpd.DataFrame({'col1': [1, 2], 'col2': ["hello", "world"], 'col3': [True, False]}) - >>> df.select_dtypes(include=['Int64']) - col1 - 0 1 - 1 2 - - [2 rows x 1 columns] - - >>> df.select_dtypes(exclude=['Int64']) - col2 col3 - 0 hello True - 1 world False - - [2 rows x 2 columns] - - - Args: - include (scalar or list-like): - A selection of dtypes or strings to be included. - exclude (scalar or list-like): - A selection of dtypes or strings to be excluded. - - Returns: - bigframes.pandas.DataFrame: The subset of the frame including the dtypes in ``include`` and excluding the dtypes in ``exclude``. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) # ---------------------------------------------------------------------- # IO methods (to / from other formats) - @classmethod - def from_dict( - cls, - data: dict, - orient="columns", - dtype=None, - columns=None, - ) -> DataFrame: - """ - Construct DataFrame from dict of array-like or dicts. - - Creates DataFrame object from dictionary by columns or by index - allowing dtype specification. - - Args: - data (dict): - Of the form {field : array-like} or {field : dict}. - orient ({'columns', 'index', 'tight'}, default 'columns'): - The "orientation" of the data. If the keys of the passed dict - should be the columns of the resulting DataFrame, pass 'columns' - (default). Otherwise if the keys should be rows, pass 'index'. - If 'tight', assume a dict with keys ['index', 'columns', 'data', - 'index_names', 'column_names']. - dtype (dtype, default None): - Data type to force after DataFrame construction, otherwise infer. - columns (list, default None): - Column labels to use when ``orient='index'``. - - Raises: - ValueError: - If used with ``orient='columns'`` or ``orient='tight'``. - - Returns: - bigframes.pandas.DataFrame: DataFrame. - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - @classmethod - def from_records( - cls, - data, - index=None, - exclude=None, - columns=None, - coerce_float: bool = False, - nrows: int | None = None, - ) -> DataFrame: - """ - Convert structured or record ndarray to DataFrame. - - Creates a DataFrame object from a structured ndarray, sequence of - tuples or dicts, or DataFrame. - - Args: - data (structured ndarray, sequence of tuples or dicts): - Structured input data. - index (str, list of fields, array-like): - Field of array to use as the index, alternately a specific set of - input labels to use. - exclude (sequence, default None): - Columns or fields to exclude. - columns (sequence, default None): - Column names to use. If the passed data do not have names - associated with them, this argument provides names for the - columns. Otherwise this argument indicates the order of the columns - in the result (any names not found in the data will become all-NA - columns). - coerce_float (bool, default False): - Attempt to convert values of non-string, non-numeric objects (like - decimal.Decimal) to floating point, useful for SQL result sets. - nrows (int, default None): - Number of rows to read if data is an iterator. - - Returns: - bigframes.pandas.DataFrame: DataFrame. - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - def to_numpy( - self, - dtype=None, - copy=False, - na_value=pd_ext.no_default, - *, - allow_large_results=None, - **kwargs, - ) -> np.ndarray: + def to_numpy(self, dtype=None, copy=False, na_value=None, **kwargs) -> np.ndarray: """ Convert the DataFrame to a NumPy array. **Examples:** + >>> import bigframes.pandas as bpd + >>> bpd.options.display.progress_bar = None >>> df = bpd.DataFrame({'col1': [1, 2], 'col2': [3, 4]}) >>> df.to_numpy() @@ -385,9 +117,7 @@ def to_numpy( na_value (Any, default None): The value to use for missing values. The default value depends on dtype and the dtypes of the DataFrame columns. - allow_large_results (bool, default None): - If not None, overrides the global setting to allow or disallow - large query results over the default size limit of 10 GB. + Returns: numpy.ndarray: The converted NumPy array. """ @@ -400,14 +130,13 @@ def to_gbq( if_exists: Optional[Literal["fail", "replace", "append"]] = None, index: bool = True, ordering_id: Optional[str] = None, - clustering_columns: Union[pd.Index, Iterable[Hashable]] = (), - labels: dict[str, str] = {}, ) -> str: """Write a DataFrame to a BigQuery table. **Examples:** >>> import bigframes.pandas as bpd + >>> bpd.options.display.progress_bar = None Write a DataFrame to a BigQuery table. @@ -421,7 +150,7 @@ def to_gbq( >>> df = bpd.DataFrame({'col1': [1, 2], 'col2': [3, 4]}) >>> destination = df.to_gbq(ordering_id="ordering_id") >>> # The table created can be read outside of the current session. - >>> bpd.close_session() # Optional, to demonstrate a new session. # doctest: +SKIP + >>> bpd.close_session() # For demonstration, only. >>> bpd.read_gbq(destination, index_col="ordering_id") col1 col2 ordering_id @@ -430,17 +159,6 @@ def to_gbq( [2 rows x 2 columns] - Write a DataFrame to a BigQuery table with clustering columns: - - >>> df = bpd.DataFrame({'col1': [1, 2], 'col2': [3, 4], 'col3': [5, 6]}) - >>> clustering_cols = ['col1', 'col3'] - >>> df.to_gbq( - ... "bigframes-dev.birds.test-clusters", - ... if_exists="replace", - ... clustering_columns=clustering_cols, - ... ) - 'bigframes-dev.birds.test-clusters' - Args: destination_table (Optional[str]): Name of table to be written, in the form ``dataset.tablename`` @@ -469,46 +187,20 @@ def to_gbq( If set, write the ordering of the DataFrame as a column in the result table with this name. - clustering_columns (Union[pd.Index, Iterable[Hashable]], default ()): - Specifies the columns for clustering in the BigQuery table. The order - of columns in this list is significant for clustering hierarchy. Index - columns may be included in clustering if the `index` parameter is set - to True, and their names are specified in this. These index columns, - if included, precede DataFrame columns in the clustering order. The - clustering order within the Index/DataFrame columns follows the order - specified in `clustering_columns`. - - labels (dict[str, str], default None): - Specifies table labels within BigQuery - Returns: str: The fully-qualified ID for the written table, in the form ``project.dataset.tablename``. - - Raises: - ValueError: - If an invalid value is provided for ``if_exists`` when ``destination_table`` - is ``None``. ``None`` or ``replace`` are the only valid values for ``if_exists``. - ValueError: - If an invalid value is provided for ``destination_table`` that is - not one of ``datasetID.tableId`` or ``projectId.datasetId.tableId``. - ValueError: - If an invalid value is provided for ``if_exists`` that is not one of - ``fail``, ``replace``, or ``append``. - - """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) def to_parquet( self, - path: Optional[str], + path: str, *, compression: Optional[Literal["snappy", "gzip"]] = "snappy", index: bool = True, - allow_large_results: Optional[bool] = None, - ) -> Optional[bytes]: + ) -> None: """Write a DataFrame to the binary Parquet format. This function writes the dataframe as a `parquet file @@ -517,82 +209,29 @@ def to_parquet( **Examples:** >>> import bigframes.pandas as bpd + >>> bpd.options.display.progress_bar = None + >>> df = bpd.DataFrame({'col1': [1, 2], 'col2': [3, 4]}) >>> gcs_bucket = "gs://bigframes-dev-testing/sample_parquet*.parquet" >>> df.to_parquet(path=gcs_bucket) Args: - path (str, path object, file-like object, or None, default None): - String, path object (implementing ``os.PathLike[str]``), or file-like - object implementing a binary ``write()`` function. If None, the result is - returned as bytes. If a string or path, it will be used as Root Directory - path when writing a partitioned dataset. + path (str): Destination URI(s) of Cloud Storage files(s) to store the extracted dataframe - should be formatted ``gs:///``. + in format of ``gs:///``. If the data size is more than 1GB, you must use a wildcard to export the data into multiple files and the size of the files varies. + compression (str, default 'snappy'): Name of the compression to use. Use ``None`` for no compression. Supported options: ``'gzip'``, ``'snappy'``. + index (bool, default True): If ``True``, include the dataframe's index(es) in the file output. If ``False``, they will not be written to the file. - allow_large_results (bool, default None): - If not None, overrides the global setting to allow or disallow large - query results over the default size limit of 10 GB. This parameter has - no effect when results are saved to Google Cloud Storage (GCS). - - Returns: - None or bytes: - bytes if no path argument is provided else None - - Raises: - ValueError: - If an invalid value provided for `compression` that is not one of - ``None``, ``snappy``, or ``gzip``. - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - def to_csv( - self, - path_or_buf=None, - sep=",", - *, - header: bool = True, - index: bool = True, - allow_large_results: Optional[bool] = None, - ) -> Optional[str]: - """ - Write object to a comma-separated values (csv) file. - - **Examples:** - - >>> import bigframes.pandas as bpd - - >>> df = bpd.DataFrame({'col1': [1, 2], 'col2': [3, 4]}) - >>> df.to_csv() - \',col1,col2\\n0,1,3\\n1,2,4\\n\' - - Args: - path_or_buf (str, path object, file-like object, or None, default None): - String, path object (implementing os.PathLike[str]), or file-like object - implementing a write() function. If None, the result is returned as a string. - If a non-binary file object is passed, it should be opened with newline='', - disabling universal newlines. If a binary file object is passed, - mode might need to contain a 'b'. - Must contain a wildcard character '*' if this is a GCS path. - sep (str, default ','): - String of length 1. Field delimiter for the output file. - header (bool, default True): - Write out the column names. - index (bool, default True): - Write row names (index). - allow_large_results (bool, default None): - If not None, overrides the global setting to allow or disallow large - query results over the default size limit of 10 GB. Returns: - If path_or_buf is None, returns the resulting csv format as a string. Otherwise returns None. + None. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @@ -602,8 +241,6 @@ def to_dict( "dict", "list", "series", "split", "tight", "records", "index" ] = "dict", into: type[dict] = dict, - *, - allow_large_results: Optional[bool] = None, **kwargs, ) -> dict | list[dict]: """ @@ -614,10 +251,12 @@ def to_dict( **Examples:** + >>> import bigframes.pandas as bpd + >>> bpd.options.display.progress_bar = None >>> df = bpd.DataFrame({'col1': [1, 2], 'col2': [3, 4]}) >>> df.to_dict() - {'col1': {np.int64(0): 1, np.int64(1): 2}, 'col2': {np.int64(0): 3, np.int64(1): 4}} + {'col1': {0: 1, 1: 2}, 'col2': {0: 3, 1: 4}} You can specify the return orientation. @@ -655,13 +294,11 @@ def to_dict( in the return value. Can be the actual class or an empty instance of the mapping type you want. If you want a collections.defaultdict, you must pass it initialized. + index (bool, default True): Whether to include the index item (and index_names item if `orient` is 'tight') in the returned dictionary. Can only be ``False`` when `orient` is 'split' or 'tight'. - allow_large_results (bool, default None): - If not None, overrides the global setting to allow or disallow large - query results over the default size limit of 10 GB. Returns: dict or list of dict: Return a collections.abc.Mapping object representing the DataFrame. @@ -669,14 +306,7 @@ def to_dict( """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - def to_excel( - self, - excel_writer, - sheet_name: str = "Sheet1", - *, - allow_large_results: Optional[bool] = None, - **kwargs, - ) -> None: + def to_excel(self, excel_writer, sheet_name: str = "Sheet1", **kwargs) -> None: """ Write DataFrame to an Excel sheet. @@ -692,7 +322,9 @@ def to_excel( **Examples:** + >>> import bigframes.pandas as bpd >>> import tempfile + >>> bpd.options.display.progress_bar = None >>> df = bpd.DataFrame({'col1': [1, 2], 'col2': [3, 4]}) >>> df.to_excel(tempfile.TemporaryFile()) @@ -702,21 +334,11 @@ def to_excel( File path or existing ExcelWriter. sheet_name (str, default 'Sheet1'): Name of sheet which will contain DataFrame. - allow_large_results (bool, default None): - If not None, overrides the global setting to allow or disallow large - query results over the default size limit of 10 GB. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) def to_latex( - self, - buf=None, - columns=None, - header=True, - index=True, - *, - allow_large_results=None, - **kwargs, + self, buf=None, columns=None, header=True, index=True, **kwargs ) -> str | None: r""" Render object to a LaTeX tabular, longtable, or nested table. @@ -727,6 +349,8 @@ def to_latex( **Examples:** + >>> import bigframes.pandas as bpd + >>> bpd.options.display.progress_bar = None >>> df = bpd.DataFrame({'col1': [1, 2], 'col2': [3, 4]}) >>> print(df.to_latex()) @@ -750,23 +374,11 @@ def to_latex( it is assumed to be aliases for the column names. index (bool, default True): Write row names (index). - allow_large_results (bool, default None): - If not None, overrides the global setting to allow or disallow large - query results over the default size limit of 10 GB. - - Returns: - str or None: If buf is None, returns the result as a string. Otherwise returns - None. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) def to_records( - self, - index: bool = True, - column_dtypes=None, - index_dtypes=None, - *, - allow_large_results=None, + self, index: bool = True, column_dtypes=None, index_dtypes=None ) -> np.recarray: """ Convert DataFrame to a NumPy record array. @@ -776,11 +388,13 @@ def to_records( **Examples:** + >>> import bigframes.pandas as bpd + >>> bpd.options.display.progress_bar = None >>> df = bpd.DataFrame({'col1': [1, 2], 'col2': [3, 4]}) >>> df.to_records() rec.array([(0, 1, 3), (1, 2, 4)], - dtype=[('index', '>> import bigframes.pandas as bpd + >>> bpd.options.display.progress_bar = None >>> df = bpd.DataFrame({'col1': [1, 2], 'col2': [3, 4]}) >>> print(df.to_string()) @@ -890,9 +501,6 @@ def to_string( Max width to truncate each column in characters. By default, no limit. encoding (str, default "utf-8"): Set character encoding. - allow_large_results (bool, default None): - If not None, overrides the global setting to allow or disallow large - query results over the default size limit of 10 GB. Returns: str or None: If buf is None, returns the result as a string. Otherwise returns @@ -900,146 +508,19 @@ def to_string( """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - def to_html( - self, - buf=None, - columns: Sequence[str] | None = None, - col_space=None, - header: bool = True, - index: bool = True, - na_rep: str = "NaN", - formatters=None, - float_format=None, - sparsify: bool | None = None, - index_names: bool = True, - justify: str | None = None, - max_rows: int | None = None, - max_cols: int | None = None, - show_dimensions: bool = False, - decimal: str = ".", - bold_rows: bool = True, - classes: str | list | tuple | None = None, - escape: bool = True, - notebook: bool = False, - border: int | None = None, - table_id: str | None = None, - render_links: bool = False, - encoding: str | None = None, - *, - allow_large_results: bool | None = None, - ): - """Render a DataFrame as an HTML table. - - **Examples:** - - - >>> df = bpd.DataFrame({'col1': [1, 2], 'col2': [3, 4]}) - >>> print(df.to_html()) - - - - - - - - - - - - - - - - - - - - -
col1col2
013
124
- - Args: - buf (str, Path or StringIO-like, optional, default None): - Buffer to write to. If None, the output is returned as a string. - columns (sequence, optional, default None): - The subset of columns to write. Writes all columns by default. - col_space (str or int, list or dict of int or str, optional): - The minimum width of each column in CSS length units. An int is - assumed to be px units. - header (bool, optional): - Whether to print column labels, default True. - index (bool, optional, default True): - Whether to print index (row) labels. - na_rep (str, optional, default 'NaN'): - String representation of NAN to use. - formatters (list, tuple or dict of one-param. functions, optional): - Formatter functions to apply to columns' elements by position or - name. - The result of each function must be a unicode string. - List/tuple must be of length equal to the number of columns. - float_format (one-parameter function, optional, default None): - Formatter function to apply to columns' elements if they are - floats. This function must return a unicode string and will - be applied only to the non-NaN elements, with NaN being - handled by na_rep. - sparsify (bool, optional, default True): - Set to False for a DataFrame with a hierarchical index to print - every multiindex key at each row. - index_names (bool, optional, default True): - Prints the names of the indexes. - justify (str, default None): - How to justify the column labels. If None uses the option from - the print configuration (controlled by set_option), 'right' out - of the box. Valid values are, 'left', 'right', 'center', 'justify', - 'justify-all', 'start', 'end', 'inherit', 'match-parent', 'initial', - 'unset'. - max_rows (int, optional): - Maximum number of rows to display in the console. - max_cols (int, optional): - Maximum number of columns to display in the console. - show_dimensions (bool, default False): - Display DataFrame dimensions (number of rows by number of columns). - decimal (str, default '.'): - Character recognized as decimal separator, e.g. ',' in Europe. - bold_rows (bool, default True): - Make the row labels bold in the output. - classes (str or list or tuple, default None): - CSS class(es) to apply to the resulting html table. - escape (bool, default True): - Convert the characters <, >, and & to HTML-safe sequences. - notebook (bool, default False): - Whether the generated HTML is for IPython Notebook. - border (int): - A border=border attribute is included in the opening - tag. Default pd.options.display.html.border. - table_id (str, optional): - A css id is included in the opening
tag if specified. - render_links (bool, default False): - Convert URLs to HTML links. - encoding (str, default "utf-8"): - Set character encoding. - allow_large_results (bool, default None): - If not None, overrides the global setting to allow or disallow - large query results over the default size limit of 10 GB. - - Returns: - str or None: If buf is None, returns the result as a string. Otherwise - returns None. - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - def to_markdown( self, buf=None, mode: str = "wt", index: bool = True, - *, - allow_large_results: Optional[bool] = None, **kwargs, ): """Print DataFrame in Markdown-friendly format. **Examples:** + >>> import bigframes.pandas as bpd + >>> bpd.options.display.progress_bar = None >>> df = bpd.DataFrame({'col1': [1, 2], 'col2': [3, 4]}) >>> print(df.to_markdown()) @@ -1055,23 +536,21 @@ def to_markdown( Mode in which file is opened. index (bool, optional, default True): Add index (row) labels. - allow_large_results (bool, default None): - If not None, overrides the global setting to allow or disallow - large query results over the default size limit of 10 GB. **kwargs These parameters will be passed to `tabulate `_. Returns: - str: - DataFrame in Markdown-friendly format. + DataFrame in Markdown-friendly format. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - def to_pickle(self, path, *, allow_large_results, **kwargs) -> None: + def to_pickle(self, path, **kwargs) -> None: """Pickle (serialize) object to file. **Examples:** + >>> import bigframes.pandas as bpd + >>> bpd.options.display.progress_bar = None >>> df = bpd.DataFrame({'col1': [1, 2], 'col2': [3, 4]}) >>> gcs_bucket = "gs://bigframes-dev-testing/sample_pickle_gcs.pkl" @@ -1080,18 +559,17 @@ def to_pickle(self, path, *, allow_large_results, **kwargs) -> None: Args: path (str): File path where the pickled object will be stored. - allow_large_results (bool, default None): - If not None, overrides the global setting to allow or disallow - large query results over the default size limit of 10 GB. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - def to_orc(self, path=None, *, allow_large_results=None, **kwargs) -> bytes | None: + def to_orc(self, path=None, **kwargs) -> bytes | None: """ Write a DataFrame to the ORC format. **Examples:** + >>> import bigframes.pandas as bpd + >>> bpd.options.display.progress_bar = None >>> df = bpd.DataFrame({'col1': [1, 2], 'col2': [3, 4]}) >>> import tempfile @@ -1104,20 +582,34 @@ def to_orc(self, path=None, *, allow_large_results=None, **kwargs) -> bytes | No we refer to objects with a write() method, such as a file handle (e.g. via builtin open function). If path is None, a bytes object is returned. - allow_large_results (bool, default None): - If not None, overrides the global setting to allow or disallow - large query results over the default size limit of 10 GB. - - Returns: - bytes or None: - If buf is None, returns the result as bytes. Otherwise returns - None. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) # ---------------------------------------------------------------------- # Unsorted + def equals(self, other) -> bool: + """ + Test whether two objects contain the same elements. + + This function allows two Series or DataFrames to be compared against + each other to see if they have the same shape and elements. NaNs in + the same location are considered equal. + + The row/column index do not need to have the same type, as long + as the values are considered equal. Corresponding columns must be of + the same dtype. + + Args: + other (Series or DataFrame): + The other Series or DataFrame to be compared with the first. + + Returns: + bool: True if all elements are the same in both objects, False + otherwise. + """ + raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) + def assign(self, **kwargs) -> DataFrame: r""" Assign new columns to a DataFrame. @@ -1138,8 +630,7 @@ def assign(self, **kwargs) -> DataFrame: are simply assigned to the column. Returns: - bigframes.pandas.DataFrame: - A new DataFrame with the new columns + bigframes.dataframe.DataFrame: A new DataFrame with the new columns in addition to all the existing columns. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @@ -1173,7 +664,7 @@ def reindex( Axis to target. Can be either the axis name ('index', 'columns') or number (0, 1). Returns: - bigframes.pandas.DataFrame: DataFrame with changed index. + DataFrame: DataFrame with changed index. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @@ -1190,55 +681,7 @@ def reindex_like(self, other): of this object. Returns: - bigframes.pandas.DataFrame or bigframes.pandas.Series: - Same type as caller, but with changed indices on each axis. - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - def insert(self, loc, column, value, allow_duplicates=False): - """Insert column into DataFrame at specified location. - - **Examples:** - - - >>> df = bpd.DataFrame({'col1': [1, 2], 'col2': [3, 4]}) - - Insert a new column named 'col3' between 'col1' and 'col2' with all entries set to 5. - - >>> df.insert(1, 'col3', 5) - >>> df - col1 col3 col2 - 0 1 5 3 - 1 2 5 4 - - [2 rows x 3 columns] - - Insert another column named 'col2' at the beginning of the DataFrame with values [5, 6] - - >>> df.insert(0, 'col2', [5, 6], allow_duplicates=True) - >>> df - col2 col1 col3 col2 - 0 5 1 5 3 - 1 6 2 5 4 - - [2 rows x 4 columns] - - Args: - loc (int): - Insertion index. Must verify 0 <= loc <= len(columns). - column (str, number, or hashable object): - Label of the inserted column. - value (Scalar, Series, or array-like): - Content of the inserted column. - allow_duplicates (bool, default False): - Allow duplicate column labels to be created. - - Raises: - IndexError: - If ``column`` index is out of bounds with the total count of columns. - ValueError: - If ``column`` is already contained in the DataFrame, - unless ``allow_duplicates`` is set to True. + Series or DataFrame: Same type as caller, but with changed indices on each axis. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @@ -1249,97 +692,9 @@ def drop( Remove columns by directly specifying column names. - **Examples:** - - - >>> df = bpd.DataFrame(np.arange(12).reshape(3, 4), - ... columns=['A', 'B', 'C', 'D']) - >>> df - A B C D - 0 0 1 2 3 - 1 4 5 6 7 - 2 8 9 10 11 - - [3 rows x 4 columns] - - Drop columns: - - >>> df.drop(['B', 'C'], axis=1) - A D - 0 0 3 - 1 4 7 - 2 8 11 - - [3 rows x 2 columns] - - >>> df.drop(columns=['B', 'C']) - A D - 0 0 3 - 1 4 7 - 2 8 11 - - [3 rows x 2 columns] - - Drop a row by index: - - >>> df.drop([0, 1]) - A B C D - 2 8 9 10 11 - - [1 rows x 4 columns] - - Drop columns and/or rows of MultiIndex DataFrame: - - >>> midx = pd.MultiIndex(levels=[['llama', 'cow', 'falcon'], - ... ['speed', 'weight', 'length']], - ... codes=[[0, 0, 0, 1, 1, 1, 2, 2, 2], - ... [0, 1, 2, 0, 1, 2, 0, 1, 2]]) - >>> df = bpd.DataFrame(index=midx, columns=['big', 'small'], - ... data=[[45, 30], [200, 100], [1.5, 1], [30, 20], - ... [250, 150], [1.5, 0.8], [320, 250], - ... [1, 0.8], [0.3, 0.2]]) - >>> df - big small - llama speed 45.0 30.0 - weight 200.0 100.0 - length 1.5 1.0 - cow speed 30.0 20.0 - weight 250.0 150.0 - length 1.5 0.8 - falcon speed 320.0 250.0 - weight 1.0 0.8 - length 0.3 0.2 - - [9 rows x 2 columns] - - Drop a specific index and column combination from the MultiIndex - DataFrame, i.e., drop the index ``'cow'`` and column ``'small'``: - - >>> df.drop(index='cow', columns='small') - big - llama speed 45.0 - weight 200.0 - length 1.5 - falcon speed 320.0 - weight 1.0 - length 0.3 - - [6 rows x 1 columns] - - >>> df.drop(index='length', level=1) - big small - llama speed 45.0 30.0 - weight 200.0 100.0 - cow speed 30.0 20.0 - weight 250.0 150.0 - falcon speed 320.0 250.0 - weight 1.0 0.8 - - [6 rows x 2 columns] - Args: labels: - Index or column labels to drop. A tuple will be used as a single label and not treated as a list-like. + Index or column labels to drop. axis: Whether to drop labels from the index (0 or 'index') or columns (1 or 'columns'). @@ -1352,14 +707,10 @@ def drop( level: For MultiIndex, level from which the labels will be removed. Returns: - bigframes.pandas.DataFrame: - DataFrame without the removed column labels. + bigframes.dataframe.DataFrame: DataFrame without the removed column labels. Raises: KeyError: If any of the labels is not found in the selected axis. - ValueError: If values for both ``labels`` and ``index``/``columns`` are provided. - ValueError: If a multi-index tuple is provided as ``level``. - ValueError: If either ``labels`` or ``index``/``columns`` is not provided. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @@ -1377,7 +728,7 @@ def align( Args: other (DataFrame or Series): - join ({'outer', 'inner', 'left', 'right'}, default 'outer'): + join ({{'outer', 'inner', 'left', 'right'}}, default 'outer'): Type of alignment to be performed. left: use only keys from left frame, preserve key order. right: use only keys from right frame, preserve key order. @@ -1389,77 +740,46 @@ def align( Align on index (0), columns (1), or both (None). Returns: - Tuple[bigframes.pandas.DataFrame or bigframes.pandas.Series, type of other]: - Aligned objects. + tuple of (DataFrame, type of other): Aligned objects. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) def rename( self, *, - columns, - inplace, - ): + columns: Mapping, + ) -> DataFrame: """Rename columns. Dict values must be unique (1-to-1). Labels not contained in a dict will be left as-is. Extra labels listed don't throw an error. - **Examples:** - - - >>> df = bpd.DataFrame({"A": [1, 2, 3], "B": [4, 5, 6]}) - >>> df - A B - 0 1 4 - 1 2 5 - 2 3 6 - - [3 rows x 2 columns] - - Rename columns using a mapping: - - >>> df.rename(columns={"A": "col1", "B": "col2"}) - col1 col2 - 0 1 4 - 1 2 5 - 2 3 6 - - [3 rows x 2 columns] - Args: columns (Mapping): Dict-like from old column labels to new column labels. - inplace (bool): - Default False. Whether to modify the DataFrame rather than - creating a new one. Returns: - bigframes.pandas.DataFrame | None: - DataFrame with the renamed axis labels or None if ``inplace=True``. + bigframes.dataframe.DataFrame: DataFrame with the renamed axis labels. Raises: KeyError: If any of the labels is not found. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - def rename_axis(self, mapper, *, inplace, **kwargs): + def rename_axis(self, mapper: Optional[str], **kwargs) -> DataFrame: """ Set the name of the axis for the index. .. note:: + Currently only accepts a single string parameter (the new name of the index). Args: - mapper (str): + mapper str: Value to set the axis name attribute. - inplace (bool): - Default False. Modifies the object directly, instead of - creating a new Series or DataFrame. Returns: - bigframes.pandas.DataFrame | None: - DataFrame with the new index name or None if ``inplace=True``. + bigframes.dataframe.DataFrame: DataFrame with the new index name """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @@ -1475,45 +795,6 @@ def set_index( Set the DataFrame index (row labels) using one existing column. The index can replace the existing index. - **Examples:** - - - >>> df = bpd.DataFrame({'month': [1, 4, 7, 10], - ... 'year': [2012, 2014, 2013, 2014], - ... 'sale': [55, 40, 84, 31]}) - >>> df - month year sale - 0 1 2012 55 - 1 4 2014 40 - 2 7 2013 84 - 3 10 2014 31 - - [4 rows x 3 columns] - - Set the 'month' column to become the index: - - >>> df.set_index('month') - year sale - month - 1 2012 55 - 4 2014 40 - 7 2013 84 - 10 2014 31 - - [4 rows x 2 columns] - - Create a MultiIndex using columns 'year' and 'month': - - >>> df.set_index(['year', 'month']) - sale - year month - 2012 1 55 - 2014 4 40 - 2013 7 84 - 2014 10 31 - - [4 rows x 1 columns] - Args: keys: A label. This parameter can be a single column key. @@ -1521,12 +802,7 @@ def set_index( Delete columns to be used as the new index. Returns: - bigframes.pandas.DataFrame: - Changed row labels. - - Raises: - KeyError: - If key(s) are not in the columns. + DataFrame: Changed row labels. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @@ -1544,12 +820,7 @@ def reorder_levels( Where to reorder levels. Returns: - bigframes.pandas.DataFrame: - DataFrame of rearranged index. - - Raises: - ValueError: - If columns are not multi-index. + DataFrame: DataFrame of rearranged index. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @@ -1567,12 +838,7 @@ def swaplevel(self, i, j, axis: str | int = 0) -> DataFrame: 'columns' for column-wise. Returns: - bigframes.pandas.DataFrame: - DataFrame with levels swapped in MultiIndex. - - Raises: - ValueError: - If columns are not multi-index. + DataFrame: DataFrame with levels swapped in MultiIndex. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @@ -1591,138 +857,26 @@ def droplevel(self, level, axis: str | int = 0): * 0 or 'index': remove level(s) in column. * 1 or 'columns': remove level(s) in row. Returns: - bigframes.pandas.DataFrame: - DataFrame with requested index / column level(s) removed. - - Raises: - ValueError: - If columns are not multi-index + DataFrame: DataFrame with requested index / column level(s) removed. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) def reset_index( self, - level=None, *, drop: bool = False, - inplace: bool = False, - col_level: Hashable = 0, - col_fill: Hashable = "", - allow_duplicates: Optional[bool] = None, - names: Hashable | Sequence[Hashable] | None = None, ) -> DataFrame | None: """Reset the index. Reset the index of the DataFrame, and use the default one instead. - **Examples:** - - - >>> df = bpd.DataFrame([('bird', 389.0), - ... ('bird', 24.0), - ... ('mammal', 80.5), - ... ('mammal', np.nan)], - ... index=['falcon', 'parrot', 'lion', 'monkey'], - ... columns=('class', 'max_speed')) - >>> df - class max_speed - falcon bird 389.0 - parrot bird 24.0 - lion mammal 80.5 - monkey mammal - - [4 rows x 2 columns] - - When we reset the index, the old index is added as a column, and a new sequential index is used: - - >>> df.reset_index() - index class max_speed - 0 falcon bird 389.0 - 1 parrot bird 24.0 - 2 lion mammal 80.5 - 3 monkey mammal - - [4 rows x 3 columns] - - We can use the ``drop`` parameter to avoid the old index being added as a column: - - >>> df.reset_index(drop=True) - class max_speed - 0 bird 389.0 - 1 bird 24.0 - 2 mammal 80.5 - 3 mammal - - [4 rows x 2 columns] - - You can also use ``reset_index`` with ``MultiIndex``. - - >>> index = pd.MultiIndex.from_tuples([('bird', 'falcon'), - ... ('bird', 'parrot'), - ... ('mammal', 'lion'), - ... ('mammal', 'monkey')], - ... names=['class', 'name']) - >>> columns = ['speed', 'max'] - >>> df = bpd.DataFrame([(389.0, 'fly'), - ... (24.0, 'fly'), - ... (80.5, 'run'), - ... (np.nan, 'jump')], - ... index=index, - ... columns=columns) - >>> df - speed max - class name - bird falcon 389.0 fly - parrot 24.0 fly - mammal lion 80.5 run - monkey jump - - [4 rows x 2 columns] - - >>> df.reset_index() - class name speed max - 0 bird falcon 389.0 fly - 1 bird parrot 24.0 fly - 2 mammal lion 80.5 run - 3 mammal monkey jump - - [4 rows x 4 columns] - - >>> df.reset_index(drop=True) - speed max - 0 389.0 fly - 1 24.0 fly - 2 80.5 run - 3 jump - - [4 rows x 2 columns] - - Args: - level (int, str, tuple, or list, default None): - Only remove the given levels from the index. Removes all levels by - default. drop (bool, default False): Do not try to insert index into dataframe columns. This resets the index to the default integer index. - inplace (bool, default False): - Whether to modify the DataFrame rather than creating a new one. - col_level (int or str, default 0): - If the columns have multiple levels, determines which level the - labels are inserted into. By default it is inserted into the first - level. - col_fill (object, default ''): - If the columns have multiple levels, determines how the other - levels are named. If None then the index name is repeated. - allow_duplicates (bool, optional, default None): - Allow duplicate column labels to be created. - names (str or 1-dimensional list, default None): - Using the given string, rename the DataFrame column which contains the - index data. If the DataFrame has a MultiIndex, this has to be a list or - tuple with length equal to the number of levels Returns: - bigframes.pandas.DataFrame: DataFrame with the new index. + bigframes.dataframe.DataFrame: DataFrame with the new index. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @@ -1749,7 +903,7 @@ def drop_duplicates( - ``False`` : Drop all duplicates. Returns: - bigframes.pandas.DataFrame: DataFrame with duplicates removed + bigframes.dataframe.DataFrame: DataFrame with duplicates removed """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @@ -1771,7 +925,7 @@ def duplicated(self, subset=None, keep="first"): - False : Mark all duplicates as ``True``. Returns: - bigframes.pandas.Series: Boolean series for each duplicated rows. + bigframes.series.Series: Boolean series for each duplicated rows. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @@ -1780,88 +934,11 @@ def duplicated(self, subset=None, keep="first"): def dropna( self, - *, - axis: int | str = 0, - how: str = "any", - thresh: Optional[int] = None, - subset=None, - inplace: bool = False, - ignore_index=False, ) -> DataFrame: """Remove missing values. - **Examples:** - - - >>> df = bpd.DataFrame({"name": ['Alfred', 'Batman', 'Catwoman'], - ... "toy": [np.nan, 'Batmobile', 'Bullwhip'], - ... "born": [pd.NA, "1940-04-25", pd.NA]}) - >>> df - name toy born - 0 Alfred - 1 Batman Batmobile 1940-04-25 - 2 Catwoman Bullwhip - - [3 rows x 3 columns] - - Drop the rows where at least one element is missing: - - >>> df.dropna() - name toy born - 1 Batman Batmobile 1940-04-25 - - [1 rows x 3 columns] - - Drop the columns where at least one element is missing. - - >>> df.dropna(axis='columns') - name - 0 Alfred - 1 Batman - 2 Catwoman - - [3 rows x 1 columns] - - Drop the rows where all elements are missing: - - >>> df.dropna(how='all') - name toy born - 0 Alfred - 1 Batman Batmobile 1940-04-25 - 2 Catwoman Bullwhip - - [3 rows x 3 columns] - - Keep rows with at least 2 non-null values. - - >>> df.dropna(thresh=2) - name toy born - 1 Batman Batmobile 1940-04-25 - 2 Catwoman Bullwhip - - [2 rows x 3 columns] - - Keep columns with at least 2 non-null values: - - >>> df.dropna(axis='columns', thresh=2) - name toy - 0 Alfred - 1 Batman Batmobile - 2 Catwoman Bullwhip - - [3 rows x 2 columns] - - Define in which columns to look for missing values. - - >>> df.dropna(subset=['name', 'toy']) - name toy born - 1 Batman Batmobile 1940-04-25 - 2 Catwoman Bullwhip - - [2 rows x 3 columns] - Args: - axis ({0 or 'index', 1 or 'columns'}, default 0): + axis ({0 or 'index', 1 or 'columns'}, default 'columns'): Determine if rows or columns which contain missing values are removed. @@ -1873,27 +950,12 @@ def dropna( * 'any' : If any NA values are present, drop that row or column. * 'all' : If all values are NA, drop that row or column. - thresh (int, optional): - Require that many non-NA values. Cannot be combined with how. - subset (column label or sequence of labels, optional): - Labels along other axis to consider, e.g. if you are dropping - rows these would be a list of columns to include. - Only supports axis=0. - inplace (bool, default ``False``): - Not supported. ignore_index (bool, default ``False``): If ``True``, the resulting axis will be labeled 0, 1, …, n - 1. Returns: - bigframes.pandas.DataFrame: - DataFrame with NA entries dropped from it. - - Raises: - ValueError: - If ``how`` is not one of ``any`` or ``all``. - TyperError: - If both ``how`` and ``thresh`` are specified. + bigframes.dataframe.DataFrame: DataFrame with NA entries dropped from it. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @@ -1901,37 +963,6 @@ def isin(self, values): """ Whether each element in the DataFrame is contained in values. - **Examples:** - - - >>> df = bpd.DataFrame({'num_legs': [2, 4], 'num_wings': [2, 0]}, - ... index=['falcon', 'dog']) - >>> df - num_legs num_wings - falcon 2 2 - dog 4 0 - - [2 rows x 2 columns] - - When ``values`` is a list check whether every value in the DataFrame is - present in the list (which animals have 0 or 2 legs or wings). - - >>> df.isin([0, 2]) - num_legs num_wings - falcon True True - dog False True - - [2 rows x 2 columns] - - When ``values`` is a dict, we can pass it to check for each column separately: - - >>> df.isin({'num_wings': [0, 3]}) - num_legs num_wings - falcon False False - dog False True - - [2 rows x 2 columns] - Args: values (iterable, or dict): The result will only be true at a location if all the @@ -1939,13 +970,8 @@ def isin(self, values): the column names, which must match. Returns: - bigframes.pandas.DataFrame: - DataFrame of booleans showing whether each element - in the DataFrame is contained in values. - - Raises: - TypeError: - If values provided are not list-like objects. + DataFrame: DataFrame of booleans showing whether each element + in the DataFrame is contained in values. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @@ -1955,18 +981,20 @@ def keys(self): This is index for Series, columns for DataFrame. + Returns: + Index: Info axis. + **Examples:** + >>> import bigframes.pandas as bpd + >>> bpd.options.display.progress_bar = None >>> df = bpd.DataFrame({ ... 'A': [1, 2, 3], ... 'B': [4, 5, 6], ... }) >>> df.keys() - Index(['A', 'B'], dtype='str') - - Returns: - pandas.Index: Info axis. + Index(['A', 'B'], dtype='object') """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @@ -1974,23 +1002,24 @@ def iterrows(self): """ Iterate over DataFrame rows as (index, Series) pairs. + Yields: + a tuple (index, data) where data contains row values as a Series + **Examples:** + >>> import bigframes.pandas as bpd + >>> bpd.options.display.progress_bar = None >>> df = bpd.DataFrame({ ... 'A': [1, 2, 3], ... 'B': [4, 5, 6], ... }) >>> index, row = next(df.iterrows()) >>> index - np.int64(0) + 0 >>> row A 1 B 4 Name: 0, dtype: object - - Returns: - Iterable[Tuple]: - A tuple where data contains row values as a Series """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @@ -1998,15 +1027,6 @@ def itertuples(self, index: bool = True, name: str | None = "Pandas"): """ Iterate over DataFrame rows as namedtuples. - **Examples:** - - >>> df = bpd.DataFrame({ - ... 'A': [1, 2, 3], - ... 'B': [4, 5, 6], - ... }) - >>> next(df.itertuples(name="Pair")) - Pair(Index=np.int64(0), A=np.int64(1), B=np.int64(4)) - Args: index (bool, default True): If True, return the index as the first element of the tuple. @@ -2015,10 +1035,22 @@ def itertuples(self, index: bool = True, name: str | None = "Pandas"): tuples. Returns: - Iterable[Tuple]: + iterator: An object to iterate over namedtuples for each row in the DataFrame with the first field possibly being the index and following fields being the column values. + + + **Examples:** + + >>> import bigframes.pandas as bpd + >>> bpd.options.display.progress_bar = None + >>> df = bpd.DataFrame({ + ... 'A': [1, 2, 3], + ... 'B': [4, 5, 6], + ... }) + >>> next(df.itertuples(name="Pair")) + Pair(Index=0, A=1, B=4) """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @@ -2029,390 +1061,73 @@ def items(self): Iterates over the DataFrame columns, returning a tuple with the column name and the content as a Series. - **Examples:** - - - >>> df = bpd.DataFrame({'species': ['bear', 'bear', 'marsupial'], - ... 'population': [1864, 22000, 80000]}, - ... index=['panda', 'polar', 'koala']) - >>> df - species population - panda bear 1864 - polar bear 22000 - koala marsupial 80000 - - [3 rows x 2 columns] - - >>> for label, content in df.items(): - ... print(f'--> label: {label}') - ... print(f'--> content:\\n{content}') - ... - --> label: species - --> content: - panda bear - polar bear - koala marsupial - Name: species, dtype: string - --> label: population - --> content: - panda 1864 - polar 22000 - koala 80000 - Name: population, dtype: Int64 - Returns: Iterator: Iterator of label, Series for each column. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - def where(self, cond, other): - """Replace values where the condition is False. - - **Examples:** + # ---------------------------------------------------------------------- + # Sorting - >>> df = bpd.DataFrame({'a': [20, 10, 0], 'b': [0, 10, 20]}) - >>> df - a b - 0 20 0 - 1 10 10 - 2 0 20 - - [3 rows x 2 columns] + def sort_values( + self, + by: str | Sequence[str], + *, + ascending: bool | Sequence[bool] = True, + kind: str = "quicksort", + na_position="last", + ) -> DataFrame: + """Sort by the values along row axis. - You can filter the values in the dataframe based on a condition. The - values matching the condition would be kept, and not matching would be - replaced. The default replacement value is ``NA``. For example, when the - condition is a dataframe: + Args: + by (str or Sequence[str]): + Name or list of names to sort by. + ascending (bool or Sequence[bool], default True): + Sort ascending vs. descending. Specify list for multiple sort + orders. If this is a list of bools, must match the length of + the by. + kind (str, default 'quicksort'): + Choice of sorting algorithm. Accepts 'quicksort', 'mergesort', + 'heapsort', 'stable'. Ignored except when determining whether to + sort stably. 'mergesort' or 'stable' will result in stable reorder. + na_position ({'first', 'last'}, default `last`): + ``{'first', 'last'}``, default 'last' Puts NaNs at the beginning + if `first`; `last` puts NaNs at the end. - >>> df.where(df > 0) - a b - 0 20 - 1 10 10 - 2 20 - - [3 rows x 2 columns] + Returns: + DataFrame with sorted values. + """ + raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - You can specify a custom replacement value for non-matching values. + def sort_index( + self, + ) -> DataFrame: + """Sort object by labels (along an axis). - >>> df.where(df > 0, -1) - a b - 0 20 -1 - 1 10 10 - 2 -1 20 - - [3 rows x 2 columns] + Returns: + The original DataFrame sorted by the labels. + """ + raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - Besides dataframe, the condition can be a series too. For example: + # ---------------------------------------------------------------------- + # Arithmetic Methods - >>> df.where(df['a'] > 10, -1) - a b - 0 20 0 - 1 -1 -1 - 2 -1 -1 - - [3 rows x 2 columns] + def eq(self, other, axis: str | int = "columns") -> DataFrame: + """ + Get equal to of DataFrame and other, element-wise (binary operator `eq`). - As for the replacement, it can be a dataframe too. For example: + Among flexible wrappers (`eq`, `ne`, `le`, `lt`, `ge`, `gt`) to comparison + operators. - >>> df.where(df > 10, -df) - a b - 0 20 0 - 1 -10 -10 - 2 0 20 - - [3 rows x 2 columns] + Equivalent to `==`, `!=`, `<=`, `<`, `>=`, `>` with support to choose axis + (rows or columns) and level for comparison. - >>> df.where(df['a'] > 10, -df) - a b - 0 20 0 - 1 -10 -10 - 2 0 -20 - - [3 rows x 2 columns] + **Examples:** - Please note, replacement doesn't support Series for now. In pandas, when - specifying a Series as replacement, the axis value should be specified - at the same time, which is not supported in bigframes DataFrame. + >>> import bigframes.pandas as bpd + >>> bpd.options.display.progress_bar = None - Args: - cond (bool Series/DataFrame, array-like, or callable): - Where cond is True, keep the original value. Where False, replace - with corresponding value from other. If cond is callable, it is - computed on the Series/DataFrame and returns boolean - Series/DataFrame or array. The callable must not change input - Series/DataFrame. - other (scalar, DataFrame, or callable): - Entries where cond is False are replaced with corresponding value - from other. If other is callable, it is computed on the - DataFrame and returns scalar or DataFrame. The callable must not - change input DataFrame. If not specified, entries will be filled - with the corresponding NULL value (np.nan for numpy dtypes, - pd.NA for extension dtypes). - - Returns: - DataFrame: DataFrame after the replacement. - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - def mask(self, cond, other): - """Replace values where the condition is False. - - **Examples:** - - - >>> df = bpd.DataFrame({'a': [20, 10, 0], 'b': [0, 10, 20]}) - >>> df - a b - 0 20 0 - 1 10 10 - 2 0 20 - - [3 rows x 2 columns] - - You can filter the values in the dataframe based on a condition. The - values matching the condition would be kept, and not matching would be - replaced. The default replacement value is ``NA``. For example, when the - condition is a dataframe: - - >>> df.mask(df > 0) - a b - 0 0 - 1 - 2 0 - - [3 rows x 2 columns] - - You can specify a custom replacement value for non-matching values. - - >>> df.mask(df > 0, -1) - a b - 0 -1 0 - 1 -1 -1 - 2 0 -1 - - [3 rows x 2 columns] - - Besides dataframe, the condition can be a series too. For example: - - >>> df.mask(df['a'] > 10, -1) - a b - 0 -1 -1 - 1 10 10 - 2 0 20 - - [3 rows x 2 columns] - - As for the replacement, it can be a dataframe too. For example: - - >>> df.mask(df > 10, -df) - a b - 0 -20 0 - 1 10 10 - 2 0 -20 - - [3 rows x 2 columns] - - >>> df.mask(df['a'] > 10, -df) - a b - 0 -20 0 - 1 10 10 - 2 0 20 - - [3 rows x 2 columns] - - Please note, replacement doesn't support Series for now. In pandas, when - specifying a Series as replacement, the axis value should be specified - at the same time, which is not supported in bigframes DataFrame. - - Args: - cond (bool Series/DataFrame, array-like, or callable): - Where cond is False, keep the original value. Where True, replace - with corresponding value from other. If cond is callable, it is - computed on the Series/DataFrame and returns boolean - Series/DataFrame or array. The callable must not change input - Series/DataFrame (though pandas doesn’t check it). - other (scalar, DataFrame, or callable): - Entries where cond is True are replaced with corresponding value - from other. If other is callable, it is computed on the - DataFrame and returns scalar or DataFrame. The callable must not - change input DataFrame (though pandas doesn’t check it). If not - specified, entries will be filled with the corresponding NULL - value (np.nan for numpy dtypes, pd.NA for extension dtypes). - - Returns: - DataFrame: DataFrame after the replacement. - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - # ---------------------------------------------------------------------- - # Sorting - - def sort_values( - self, - by: str | Sequence[str], - *, - inplace: bool = False, - ascending: bool | Sequence[bool] = True, - kind: str | None = None, - na_position: Literal["first", "last"] = "last", - ): - """Sort by the values along row axis. - - **Examples:** - - - >>> df = bpd.DataFrame({ - ... 'col1': ['A', 'A', 'B', pd.NA, 'D', 'C'], - ... 'col2': [2, 1, 9, 8, 7, 4], - ... 'col3': [0, 1, 9, 4, 2, 3], - ... 'col4': ['a', 'B', 'c', 'D', 'e', 'F'] - ... }) - >>> df - col1 col2 col3 col4 - 0 A 2 0 a - 1 A 1 1 B - 2 B 9 9 c - 3 8 4 D - 4 D 7 2 e - 5 C 4 3 F - - [6 rows x 4 columns] - - Sort by col1: - - >>> df.sort_values(by=['col1']) - col1 col2 col3 col4 - 0 A 2 0 a - 1 A 1 1 B - 2 B 9 9 c - 5 C 4 3 F - 4 D 7 2 e - 3 8 4 D - - [6 rows x 4 columns] - - Sort by multiple columns: - - >>> df.sort_values(by=['col1', 'col2']) - col1 col2 col3 col4 - 1 A 1 1 B - 0 A 2 0 a - 2 B 9 9 c - 5 C 4 3 F - 4 D 7 2 e - 3 8 4 D - - [6 rows x 4 columns] - - Sort Descending: - - >>> df.sort_values(by='col1', ascending=False) - col1 col2 col3 col4 - 4 D 7 2 e - 5 C 4 3 F - 2 B 9 9 c - 0 A 2 0 a - 1 A 1 1 B - 3 8 4 D - - [6 rows x 4 columns] - - Putting NAs first: - - >>> df.sort_values(by='col1', ascending=False, na_position='first') - col1 col2 col3 col4 - 3 8 4 D - 4 D 7 2 e - 5 C 4 3 F - 2 B 9 9 c - 0 A 2 0 a - 1 A 1 1 B - - [6 rows x 4 columns] - - Args: - by (str or Sequence[str]): - Name or list of names to sort by. - ascending (bool or Sequence[bool], default True): - Sort ascending vs. descending. Specify list for multiple sort - orders. If this is a list of bools, must match the length of - the by. - inplace (bool, default False): - If True, perform operation in-place. - kind (str, default None): - Choice of sorting algorithm. Accepts 'quicksort', 'mergesort', - 'heapsort', 'stable'. Ignored except when determining whether to - sort stably. 'mergesort' or 'stable' will result in stable reorder. - na_position ({'first', 'last'}, default `last`): - ``{'first', 'last'}``, default 'last' Puts NaNs at the beginning - if `first`; `last` puts NaNs at the end. - - Returns: - bigframes.pandas.DataFram or None: - DataFrame with sorted values or None if inplace=True. - - Raises: - ValueError: - If value of ``na_position`` is not one of ``first`` or ``last``. - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - def sort_index( - self, - *, - axis: str | int = 0, - ascending: bool = True, - inplace: bool = False, - kind: str | None = None, - na_position: Literal["first", "last"] = "last", - ): - """Sort object by labels (along an axis). - - Args: - axis ({0 or 'index', 1 or 'columns'}, default 0): - The axis along which to sort. The value 0 identifies the rows, - and 1 identifies the columns. - ascending (bool, default True) - Sort ascending vs. descending. - inplace (bool, default False): - Whether to modify the DataFrame rather than creating a new one. - kind (str, default None): - Choice of sorting algorithm. Accepts 'quicksort', 'mergesort', - 'heapsort', 'stable'. Ignored except when determining whether to - sort stably. 'mergesort' or 'stable' will result in stable reorder. - na_position ({'first', 'last'}, default 'last'): - Puts NaNs at the beginning if `first`; `last` puts NaNs at the end. - Not implemented for MultiIndex. - - Returns: - bigframes.pandas.DataFrame: - DataFrame with sorted values or None if inplace=True. - - Raises: - ValueError: - If value of ``na_position`` is not one of ``first`` or ``last``. - ValueError: - If length of ``ascending`` dose not equal length of ``by``. - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - # ---------------------------------------------------------------------- - # Arithmetic and Logical Methods - - def eq(self, other, axis: str | int = "columns") -> DataFrame: - """ - Get equal to of DataFrame and other, element-wise (binary operator `eq`). - - Among flexible wrappers (`eq`, `ne`, `le`, `lt`, `ge`, `gt`) to comparison - operators. - - Equivalent to `==`, `!=`, `<=`, `<`, `>=`, `>` with support to choose axis - (rows or columns) and level for comparison. - - **Examples:** - - - You can use method name: + You can use method name: >>> df = bpd.DataFrame({'angles': [0, 3, 4], ... 'degrees': [360, 180, 360]}, @@ -2423,8 +1138,7 @@ def eq(self, other, axis: str | int = "columns") -> DataFrame: rectangle True Name: degrees, dtype: boolean - You can also use logical operator `==`: - + You can also use arithmetic operator ``==``: >>> df["degrees"] == 360 circle True triangle False @@ -2439,60 +1153,7 @@ def eq(self, other, axis: str | int = "columns") -> DataFrame: (1 or 'columns'). Returns: - bigframes.pandas.DataFrame: Result of the comparison. - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - def __eq__(self, other): - """ - Check equality of DataFrame and other, element-wise, using logical - operator `==`. - - Equivalent to `DataFrame.eq(other)`. - - **Examples:** - - - >>> df = bpd.DataFrame({ - ... 'a': [0, 3, 4], - ... 'b': [360, 0, 180] - ... }) - >>> df == 0 - a b - 0 True False - 1 False True - 2 False False - - [3 rows x 2 columns] - - Args: - other (scalar or DataFrame): - Object to be compared to the DataFrame for equality. - - Returns: - bigframes.pandas.DataFrame: The result of comparing `other` to DataFrame. - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - def __invert__(self) -> DataFrame: - """ - Returns the bitwise inversion of the DataFrame, element-wise - using operator `~`. - - **Examples:** - - - >>> df = bpd.DataFrame({'a':[True, False, True], 'b':[-1, 0, 1]}) - >>> ~df - a b - 0 False 0 - 1 True -1 - 2 False -2 - - [3 rows x 2 columns] - - Returns: - bigframes.pandas.DataFrame: The result of inverting elements in the input. + Result of the comparison. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @@ -2508,6 +1169,8 @@ def ne(self, other, axis: str | int = "columns") -> DataFrame: **Examples:** + >>> import bigframes.pandas as bpd + >>> bpd.options.display.progress_bar = None You can use method name: @@ -2535,38 +1198,7 @@ def ne(self, other, axis: str | int = "columns") -> DataFrame: Whether to compare by the index (0 or 'index') or columns (1 or 'columns'). Returns: - bigframes.pandas.DataFrame: Result of the comparison. - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - def __ne__(self, other): - """ - Check inequality of DataFrame and other, element-wise, using logical - operator `!=`. - - Equivalent to `DataFrame.ne(other)`. - - **Examples:** - - - >>> df = bpd.DataFrame({ - ... 'a': [0, 3, 4], - ... 'b': [360, 0, 180] - ... }) - >>> df != 0 - a b - 0 False True - 1 True False - 2 True True - - [3 rows x 2 columns] - - Args: - other (scalar or DataFrame): - Object to be compared to the DataFrame for inequality. - - Returns: - bigframes.pandas.DataFrame: The result of comparing `other` to DataFrame. + DataFrame: Result of the comparison. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @@ -2586,6 +1218,8 @@ def le(self, other, axis: str | int = "columns") -> DataFrame: **Examples:** + >>> import bigframes.pandas as bpd + >>> bpd.options.display.progress_bar = None You can use method name: @@ -2614,38 +1248,7 @@ def le(self, other, axis: str | int = "columns") -> DataFrame: (1 or 'columns'). Returns: - bigframes.pandas.DataFrame: DataFrame of bool. The result of the comparison. - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - def __le__(self, other): - """ - Check whether DataFrame is less than or equal to other, element-wise, - using logical operator `<=`. - - Equivalent to `DataFrame.le(other)`. - - **Examples:** - - - >>> df = bpd.DataFrame({ - ... 'a': [0, -1, 1], - ... 'b': [1, 0, -1] - ... }) - >>> df <= 0 - a b - 0 True False - 1 True True - 2 False True - - [3 rows x 2 columns] - - Args: - other (scalar or DataFrame): - Object to be compared to the DataFrame. - - Returns: - bigframes.pandas.DataFrame: The result of comparing `other` to DataFrame. + DataFrame: DataFrame of bool. The result of the comparison. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @@ -2665,6 +1268,8 @@ def lt(self, other, axis: str | int = "columns") -> DataFrame: **Examples:** + >>> import bigframes.pandas as bpd + >>> bpd.options.display.progress_bar = None You can use method name: @@ -2693,38 +1298,7 @@ def lt(self, other, axis: str | int = "columns") -> DataFrame: (1 or 'columns'). Returns: - bigframes.pandas.DataFrame: DataFrame of bool. The result of the comparison. - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - def __lt__(self, other): - """ - Check whether DataFrame is less than other, element-wise, using logical - operator `<`. - - Equivalent to `DataFrame.lt(other)`. - - **Examples:** - - - >>> df = bpd.DataFrame({ - ... 'a': [0, -1, 1], - ... 'b': [1, 0, -1] - ... }) - >>> df < 0 - a b - 0 False False - 1 True False - 2 False True - - [3 rows x 2 columns] - - Args: - other (scalar or DataFrame): - Object to be compared to the DataFrame. - - Returns: - bigframes.pandas.DataFrame: The result of comparing `other` to DataFrame. + DataFrame: DataFrame of bool. The result of the comparison. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @@ -2744,6 +1318,8 @@ def ge(self, other, axis: str | int = "columns") -> DataFrame: **Examples:** + >>> import bigframes.pandas as bpd + >>> bpd.options.display.progress_bar = None You can use method name: @@ -2772,38 +1348,7 @@ def ge(self, other, axis: str | int = "columns") -> DataFrame: (1 or 'columns'). Returns: - bigframes.pandas.DataFrame: DataFrame of bool. The result of the comparison. - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - def __ge__(self, other): - """ - Check whether DataFrame is greater than or equal to other, element-wise, - using logical operator `>=`. - - Equivalent to `DataFrame.ge(other)`. - - **Examples:** - - - >>> df = bpd.DataFrame({ - ... 'a': [0, -1, 1], - ... 'b': [1, 0, -1] - ... }) - >>> df >= 0 - a b - 0 True True - 1 False True - 2 True False - - [3 rows x 2 columns] - - Args: - other (scalar or DataFrame): - Object to be compared to the DataFrame. - - Returns: - bigframes.pandas.DataFrame: The result of comparing `other` to DataFrame. + DataFrame: DataFrame of bool. The result of the comparison. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @@ -2823,6 +1368,8 @@ def gt(self, other, axis: str | int = "columns") -> DataFrame: **Examples:** + >>> import bigframes.pandas as bpd + >>> bpd.options.display.progress_bar = None >>> df = bpd.DataFrame({'angles': [0, 3, 4], ... 'degrees': [360, 180, 360]}, @@ -2849,38 +1396,7 @@ def gt(self, other, axis: str | int = "columns") -> DataFrame: (1 or 'columns'). Returns: - bigframes.pandas.DataFrame: DataFrame of bool: The result of the comparison. - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - def __gt__(self, other): - """ - Check whether DataFrame is greater than other, element-wise, using logical - operator `>`. - - Equivalent to `DataFrame.gt(other)`. - - **Examples:** - - - >>> df = bpd.DataFrame({ - ... 'a': [0, -1, 1], - ... 'b': [1, 0, -1] - ... }) - >>> df > 0 - a b - 0 False True - 1 False False - 2 True False - - [3 rows x 2 columns] - - Args: - other (scalar or DataFrame): - Object to be compared to the DataFrame. - - Returns: - bigframes.pandas.DataFrame: The result of comparing `other` to DataFrame. + DataFrame: DataFrame of bool: The result of the comparison. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @@ -2897,6 +1413,8 @@ def add(self, other, axis: str | int = "columns") -> DataFrame: **Examples:** + >>> import bigframes.pandas as bpd + >>> bpd.options.display.progress_bar = None >>> df = bpd.DataFrame({ ... 'A': [1, 2, 3], @@ -2913,7 +1431,7 @@ def add(self, other, axis: str | int = "columns") -> DataFrame: You can also use arithmetic operator ``+``: - >>> df['A'] + df['B'] + >>> df['A'] + (df['B']) 0 5 1 7 2 9 @@ -2927,167 +1445,40 @@ def add(self, other, axis: str | int = "columns") -> DataFrame: (1 or 'columns'). For Series input, axis to match Series index on. Returns: - bigframes.pandas.DataFrame: DataFrame result of the arithmetic operation. + DataFrame: DataFrame result of the arithmetic operation. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - def __add__(self, other) -> DataFrame: - """Get addition of DataFrame and other, column-wise, using arithmetic - operator `+`. + def sub(self, other, axis: str | int = "columns") -> DataFrame: + """Get subtraction of DataFrame and other, element-wise (binary operator `-`). + + Equivalent to ``dataframe - other``. With reverse version, `rsub`. + + Among flexible wrappers (`add`, `sub`, `mul`, `div`, `mod`, `pow`) to + arithmetic operators: `+`, `-`, `*`, `/`, `//`, `%`, `**`. - Equivalent to ``DataFrame.add(other)``. + .. note:: + Mismatched indices will be unioned together. **Examples:** + >>> import bigframes.pandas as bpd + >>> bpd.options.display.progress_bar = None >>> df = bpd.DataFrame({ - ... 'height': [1.5, 2.6], - ... 'weight': [500, 800] - ... }, - ... index=['elk', 'moose']) - >>> df - height weight - elk 1.5 500 - moose 2.6 800 - - [2 rows x 2 columns] + ... 'A': [1, 2, 3], + ... 'B': [4, 5, 6], + ... }) - Adding a scalar affects all rows and columns. + You can use method name: - >>> df + 1.5 - height weight - elk 3.0 501.5 - moose 4.1 801.5 - - [2 rows x 2 columns] + >>> df['A'].sub(df['B']) + 0 -3 + 1 -3 + 2 -3 + dtype: Int64 - You can add another DataFrame with index and columns aligned. - - >>> delta = bpd.DataFrame({ - ... 'height': [0.5, 0.9], - ... 'weight': [50, 80] - ... }, - ... index=['elk', 'moose']) - >>> df + delta - height weight - elk 2.0 550 - moose 3.5 880 - - [2 rows x 2 columns] - - Adding any mis-aligned index and columns will result in invalid values. - - >>> delta = bpd.DataFrame({ - ... 'depth': [0.5, 0.9, 1.0], - ... 'weight': [50, 80, 100] - ... }, - ... index=['elk', 'moose', 'bison']) - >>> df + delta - depth height weight - elk 550 - moose 880 - bison - - [3 rows x 3 columns] - - Args: - other (scalar or DataFrame): - Object to be added to the DataFrame. - - Returns: - bigframes.pandas.DataFrame: The result of adding `other` to DataFrame. - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - def radd(self, other, axis: str | int = "columns") -> DataFrame: - """Get addition of DataFrame and other, element-wise (binary operator `+`). - - Equivalent to ``other + dataframe``. With reverse version, `add`. - - Among flexible wrappers (`add`, `sub`, `mul`, `div`, `mod`, `pow`) to - arithmetic operators: `+`, `-`, `*`, `/`, `//`, `%`, `**`. - - .. note:: - Mismatched indices will be unioned together. - - **Examples:** - - - >>> df = bpd.DataFrame({ - ... 'A': [1, 2, 3], - ... 'B': [4, 5, 6], - ... }) - - You can use method name: - - >>> df['A'].radd(df['B']) - 0 5 - 1 7 - 2 9 - dtype: Int64 - - You can also use arithmetic operator ``+``: - - >>> df['A'] + df['B'] - 0 5 - 1 7 - 2 9 - dtype: Int64 - - Args: - other (float, int, or Series): - Any single or multiple element data structure, or list-like object. - axis ({0 or 'index', 1 or 'columns'}): - Whether to compare by the index (0 or 'index') or columns. - (1 or 'columns'). For Series input, axis to match Series index on. - - Returns: - bigframes.pandas.DataFrame: DataFrame result of the arithmetic operation. - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - def __radd__(self, other) -> DataFrame: - """Get addition of other and DataFrame, element-wise (binary operator `+`). - - Equivalent to ``DataFrame.radd(other)``. - - Args: - other (float, int, or Series): - Any single or multiple element data structure, or list-like object. - - Returns: - bigframes.pandas.DataFrame: DataFrame result of the arithmetic operation. - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - def sub(self, other, axis: str | int = "columns") -> DataFrame: - """Get subtraction of DataFrame and other, element-wise (binary operator `-`). - - Equivalent to ``dataframe - other``. With reverse version, `rsub`. - - Among flexible wrappers (`add`, `sub`, `mul`, `div`, `mod`, `pow`) to - arithmetic operators: `+`, `-`, `*`, `/`, `//`, `%`, `**`. - - .. note:: - Mismatched indices will be unioned together. - - **Examples:** - - - >>> df = bpd.DataFrame({ - ... 'A': [1, 2, 3], - ... 'B': [4, 5, 6], - ... }) - - You can use method name: - - >>> df['A'].sub(df['B']) - 0 -3 - 1 -3 - 2 -3 - dtype: Int64 - - You can also use arithmetic operator ``-``: + You can also use arithmetic operator ``-``: >>> df['A'] - (df['B']) 0 -3 @@ -3103,48 +1494,7 @@ def sub(self, other, axis: str | int = "columns") -> DataFrame: (1 or 'columns'). For Series input, axis to match Series index on. Returns: - bigframes.pandas.DataFrame: DataFrame result of the arithmetic operation. - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - def __sub__(self, other): - """ - Get subtraction of other from DataFrame, element-wise, using operator `-`. - - Equivalent to `DataFrame.sub(other)`. - - **Examples:** - - - You can subtract a scalar: - - >>> df = bpd.DataFrame({"a": [1, 2, 3], "b": [4, 5, 6]}) - >>> df - 2 - a b - 0 -1 2 - 1 0 3 - 2 1 4 - - [3 rows x 2 columns] - - You can also subtract another DataFrame with index and column labels - aligned: - - >>> df1 = bpd.DataFrame({"a": [2, 2, 2], "b": [3, 3, 3]}) - >>> df - df1 - a b - 0 -1 1 - 1 0 2 - 2 1 3 - - [3 rows x 2 columns] - - Args: - other (scalar or DataFrame): - Object to subtract from the DataFrame. - - Returns: - bigframes.pandas.DataFrame: The result of the subtraction. + DataFrame: DataFrame result of the arithmetic operation. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @@ -3161,6 +1511,8 @@ def rsub(self, other, axis: str | int = "columns") -> DataFrame: **Examples:** + >>> import bigframes.pandas as bpd + >>> bpd.options.display.progress_bar = None >>> df = bpd.DataFrame({ ... 'A': [1, 2, 3], @@ -3188,22 +1540,7 @@ def rsub(self, other, axis: str | int = "columns") -> DataFrame: (1 or 'columns'). For Series input, axis to match Series index on. Returns: - bigframes.pandas.DataFrame: DataFrame result of the arithmetic operation. - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - def __rsub__(self, other): - """ - Get subtraction of DataFrame from other, element-wise, using operator `-`. - - Equivalent to `DataFrame.rsub(other)`. - - Args: - other (scalar or DataFrame): - Object to subtract the DataFrame from. - - Returns: - bigframes.pandas.DataFrame: The result of the subtraction. + DataFrame: DataFrame result of the arithmetic operation. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @@ -3220,6 +1557,8 @@ def mul(self, other, axis: str | int = "columns") -> DataFrame: **Examples:** + >>> import bigframes.pandas as bpd + >>> bpd.options.display.progress_bar = None >>> df = bpd.DataFrame({ ... 'A': [1, 2, 3], @@ -3250,136 +1589,7 @@ def mul(self, other, axis: str | int = "columns") -> DataFrame: (1 or 'columns'). For Series input, axis to match Series index on. Returns: - bigframes.pandas.DataFrame: DataFrame result of the arithmetic operation. - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - def __mul__(self, other): - """ - Get multiplication of DataFrame with other, element-wise, using operator `*`. - - Equivalent to `DataFrame.mul(other)`. - - **Examples:** - - - You can multiply with a scalar: - - >>> df = bpd.DataFrame({"a": [1, 2, 3], "b": [4, 5, 6]}) - >>> df * 3 - a b - 0 3 12 - 1 6 15 - 2 9 18 - - [3 rows x 2 columns] - - You can also multiply with another DataFrame with index and column labels - aligned: - - >>> df1 = bpd.DataFrame({"a": [2, 2, 2], "b": [3, 3, 3]}) - >>> df * df1 - a b - 0 2 12 - 1 4 15 - 2 6 18 - - [3 rows x 2 columns] - - Args: - other (scalar or DataFrame): - Object to multiply with the DataFrame. - - Returns: - bigframes.pandas.DataFrame: The result of the multiplication. - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - def rmul(self, other, axis: str | int = "columns") -> DataFrame: - """Get multiplication of DataFrame and other, element-wise (binary operator `*`). - - Equivalent to ``other * dataframe``. With reverse version, `mul`. - - Among flexible wrappers (`add`, `sub`, `mul`, `div`, `mod`, `pow`) to - arithmetic operators: `+`, `-`, `*`, `/`, `//`, `%`, `**`. - - .. note:: - Mismatched indices will be unioned together. - - **Examples:** - - - >>> df = bpd.DataFrame({ - ... 'A': [1, 2, 3], - ... 'B': [4, 5, 6], - ... }) - - You can use method name: - - >>> df['A'].rmul(df['B']) - 0 4 - 1 10 - 2 18 - dtype: Int64 - - You can also use arithmetic operator ``*``: - - >>> df['A'] * (df['B']) - 0 4 - 1 10 - 2 18 - dtype: Int64 - - Args: - other (float, int, or Series): - Any single or multiple element data structure, or list-like object. - axis ({0 or 'index', 1 or 'columns'}): - Whether to compare by the index (0 or 'index') or columns. - (1 or 'columns'). For Series input, axis to match Series index on. - - Returns: - bigframes.pandas.DataFrame: DataFrame result of the arithmetic operation. - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - def __rmul__(self, other): - """ - Get multiplication of DataFrame with other, element-wise, using operator `*`. - - Equivalent to `DataFrame.rmul(other)`. - - **Examples:** - - - You can multiply with a scalar: - - >>> df = bpd.DataFrame({"a": [1, 2, 3], "b": [4, 5, 6]}) - >>> df * 3 - a b - 0 3 12 - 1 6 15 - 2 9 18 - - [3 rows x 2 columns] - - You can also multiply with another DataFrame with index and column labels - aligned: - - >>> df1 = bpd.DataFrame({"a": [2, 2, 2], "b": [3, 3, 3]}) - >>> df * df1 - a b - 0 2 12 - 1 4 15 - 2 6 18 - - [3 rows x 2 columns] - - Args: - other (scalar or DataFrame): - Object to multiply the DataFrame with. - - Returns: - bigframes.pandas.DataFrame: The result of the multiplication. + DataFrame: DataFrame result of the arithmetic operation. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @@ -3396,6 +1606,8 @@ def truediv(self, other, axis: str | int = "columns") -> DataFrame: **Examples:** + >>> import bigframes.pandas as bpd + >>> bpd.options.display.progress_bar = None >>> df = bpd.DataFrame({ ... 'A': [1, 2, 3], @@ -3426,48 +1638,7 @@ def truediv(self, other, axis: str | int = "columns") -> DataFrame: (1 or 'columns'). For Series input, axis to match Series index on. Returns: - bigframes.pandas.DataFrame: DataFrame result of the arithmetic operation. - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - def __truediv__(self, other): - """ - Get division of DataFrame by other, element-wise, using operator `/`. - - Equivalent to `DataFrame.truediv(other)`. - - **Examples:** - - - You can multiply with a scalar: - - >>> df = bpd.DataFrame({"a": [1, 2, 3], "b": [4, 5, 6]}) - >>> df / 2 - a b - 0 0.5 2.0 - 1 1.0 2.5 - 2 1.5 3.0 - - [3 rows x 2 columns] - - You can also multiply with another DataFrame with index and column labels - aligned: - - >>> denominator = bpd.DataFrame({"a": [2, 2, 2], "b": [3, 3, 3]}) - >>> df / denominator - a b - 0 0.5 1.333333 - 1 1.0 1.666667 - 2 1.5 2.0 - - [3 rows x 2 columns] - - Args: - other (scalar or DataFrame): - Object to divide the DataFrame by. - - Returns: - bigframes.pandas.DataFrame: The result of the division. + DataFrame: DataFrame result of the arithmetic operation. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @@ -3484,6 +1655,8 @@ def rtruediv(self, other, axis: str | int = "columns") -> DataFrame: **Examples:** + >>> import bigframes.pandas as bpd + >>> bpd.options.display.progress_bar = None >>> df = bpd.DataFrame({ ... 'A': [1, 2, 3], @@ -3511,22 +1684,7 @@ def rtruediv(self, other, axis: str | int = "columns") -> DataFrame: (1 or 'columns'). For Series input, axis to match Series index on. Returns: - bigframes.pandas.DataFrame: DataFrame result of the arithmetic operation. - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - def __rtruediv__(self, other): - """ - Get division of other by DataFrame, element-wise, using operator `/`. - - Equivalent to `DataFrame.rtruediv(other)`. - - Args: - other (scalar or DataFrame): - Object to divide by the DataFrame. - - Returns: - bigframes.pandas.DataFrame: The result of the division. + DataFrame result of the arithmetic operation. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @@ -3543,6 +1701,8 @@ def floordiv(self, other, axis: str | int = "columns") -> DataFrame: **Examples:** + >>> import bigframes.pandas as bpd + >>> bpd.options.display.progress_bar = None >>> df = bpd.DataFrame({ ... 'A': [1, 2, 3], @@ -3573,48 +1733,7 @@ def floordiv(self, other, axis: str | int = "columns") -> DataFrame: (1 or 'columns'). For Series input, axis to match Series index on. Returns: - bigframes.pandas.DataFrame: DataFrame result of the arithmetic operation. - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - def __floordiv__(self, other): - """ - Get integer division of DataFrame by other, using arithmetic operator `//`. - - Equivalent to `DataFrame.floordiv(other)`. - - **Examples:** - - - You can divide by a scalar: - - >>> df = bpd.DataFrame({"a": [15, 15, 15], "b": [30, 30, 30]}) - >>> df // 2 - a b - 0 7 15 - 1 7 15 - 2 7 15 - - [3 rows x 2 columns] - - You can also divide by another DataFrame with index and column labels - aligned: - - >>> divisor = bpd.DataFrame({"a": [2, 3, 4], "b": [5, 6, 7]}) - >>> df // divisor - a b - 0 7 6 - 1 5 5 - 2 3 4 - - [3 rows x 2 columns] - - Args: - other (scalar or DataFrame): - Object to divide the DataFrame by. - - Returns: - bigframes.pandas.DataFrame: The result of the integer divison. + DataFrame: DataFrame result of the arithmetic operation. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @@ -3631,6 +1750,8 @@ def rfloordiv(self, other, axis: str | int = "columns") -> DataFrame: **Examples:** + >>> import bigframes.pandas as bpd + >>> bpd.options.display.progress_bar = None >>> df = bpd.DataFrame({ ... 'A': [1, 2, 3], @@ -3658,22 +1779,7 @@ def rfloordiv(self, other, axis: str | int = "columns") -> DataFrame: (1 or 'columns'). For Series input, axis to match Series index on. Returns: - bigframes.pandas.DataFrame: DataFrame result of the arithmetic operation. - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - def __rfloordiv__(self, other): - """ - Get integer divison of other by DataFrame. - - Equivalent to `DataFrame.rfloordiv(other)`. - - Args: - other (scalar or DataFrame): - Object to divide by the DataFrame. - - Returns: - bigframes.pandas.DataFrame: The result of the integer divison. + DataFrame: DataFrame result of the arithmetic operation. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @@ -3690,6 +1796,8 @@ def mod(self, other, axis: str | int = "columns") -> DataFrame: **Examples:** + >>> import bigframes.pandas as bpd + >>> bpd.options.display.progress_bar = None >>> df = bpd.DataFrame({ ... 'A': [1, 2, 3], @@ -3720,48 +1828,7 @@ def mod(self, other, axis: str | int = "columns") -> DataFrame: (1 or 'columns'). For Series input, axis to match Series index on. Returns: - bigframes.pandas.DataFrame: DataFrame result of the arithmetic operation. - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - def __mod__(self, other): - """ - Get modulo of DataFrame with other, element-wise, using operator `%`. - - Equivalent to `DataFrame.mod(other)`. - - **Examples:** - - - You can modulo with a scalar: - - >>> df = bpd.DataFrame({"a": [1, 2, 3], "b": [4, 5, 6]}) - >>> df % 3 - a b - 0 1 1 - 1 2 2 - 2 0 0 - - [3 rows x 2 columns] - - You can also modulo with another DataFrame with index and column labels - aligned: - - >>> modulo = bpd.DataFrame({"a": [2, 2, 2], "b": [3, 3, 3]}) - >>> df % modulo - a b - 0 1 1 - 1 0 2 - 2 1 0 - - [3 rows x 2 columns] - - Args: - other (scalar or DataFrame): - Object to modulo the DataFrame by. - - Returns: - bigframes.pandas.DataFrame: The result of the modulo. + DataFrame: DataFrame result of the arithmetic operation. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @@ -3778,6 +1845,8 @@ def rmod(self, other, axis: str | int = "columns") -> DataFrame: **Examples:** + >>> import bigframes.pandas as bpd + >>> bpd.options.display.progress_bar = None >>> df = bpd.DataFrame({ ... 'A': [1, 2, 3], @@ -3805,22 +1874,7 @@ def rmod(self, other, axis: str | int = "columns") -> DataFrame: (1 or 'columns'). For Series input, axis to match Series index on. Returns: - bigframes.pandas.DataFrame: DataFrame result of the arithmetic operation. - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - def __rmod__(self, other): - """ - Get integer divison of other by DataFrame. - - Equivalent to `DataFrame.rmod(other)`. - - Args: - other (scalar or DataFrame): - Object to modulo by the DataFrame. - - Returns: - bigframes.pandas.DataFrame: The result of the modulo. + DataFrame: DataFrame result of the arithmetic operation. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @@ -3838,6 +1892,8 @@ def pow(self, other, axis: str | int = "columns") -> DataFrame: **Examples:** + >>> import bigframes.pandas as bpd + >>> bpd.options.display.progress_bar = None >>> df = bpd.DataFrame({ ... 'A': [1, 2, 3], @@ -3868,49 +1924,7 @@ def pow(self, other, axis: str | int = "columns") -> DataFrame: (1 or 'columns'). For Series input, axis to match Series index on. Returns: - bigframes.pandas.DataFrame: DataFrame result of the arithmetic operation. - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - def __pow__(self, other): - """ - Get exponentiation of DataFrame with other, element-wise, using operator - `**`. - - Equivalent to `DataFrame.pow(other)`. - - **Examples:** - - - You can exponentiate with a scalar: - - >>> df = bpd.DataFrame({"a": [1, 2, 3], "b": [4, 5, 6]}) - >>> df ** 2 - a b - 0 1 16 - 1 4 25 - 2 9 36 - - [3 rows x 2 columns] - - You can also exponentiate with another DataFrame with index and column - labels aligned: - - >>> exponent = bpd.DataFrame({"a": [2, 2, 2], "b": [3, 3, 3]}) - >>> df ** exponent - a b - 0 1 64 - 1 4 125 - 2 9 216 - - [3 rows x 2 columns] - - Args: - other (scalar or DataFrame): - Object to exponentiate the DataFrame with. - - Returns: - bigframes.pandas.DataFrame: The result of the exponentiation. + DataFrame: DataFrame result of the arithmetic operation. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @@ -3928,6 +1942,8 @@ def rpow(self, other, axis: str | int = "columns") -> DataFrame: **Examples:** + >>> import bigframes.pandas as bpd + >>> bpd.options.display.progress_bar = None >>> df = bpd.DataFrame({ ... 'A': [1, 2, 3], @@ -3955,59 +1971,7 @@ def rpow(self, other, axis: str | int = "columns") -> DataFrame: (1 or 'columns'). For Series input, axis to match Series index on. Returns: - bigframes.pandas.DataFrame: DataFrame result of the arithmetic operation. - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - def __rpow__(self, other): - """ - Get exponentiation of other with DataFrame, element-wise, using operator - `**`. - - Equivalent to `DataFrame.rpow(other)`. - - Args: - other (scalar or DataFrame): - Object to exponentiate with the DataFrame. - - Returns: - DataFrame: The result of the exponentiation. - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - def __and__(self, other): - """Get bitwise AND of DataFrame and other, element-wise, using operator `&`. - - Args: - other (scalar, Series or DataFrame): - Object to bitwise AND with the DataFrame. - - Returns: - bigframes.dataframe.DataFrame: The result of the operation. - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - def __or__(self, other): - """Get bitwise OR of DataFrame and other, element-wise, using operator `|`. - - Args: - other (scalar, Series or DataFrame): - Object to bitwise OR with the DataFrame. - - Returns: - bigframes.dataframe.DataFrame: The result of the operation. - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - def __xor__(self, other): - """Get bitwise XOR of DataFrame and other, element-wise, using operator `^`. - - Args: - other (scalar, Series or DataFrame): - Object to bitwise XOR with the DataFrame. - - Returns: - bigframes.dataframe.DataFrame: The result of the operation. + DataFrame: DataFrame result of the arithmetic operation. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @@ -4022,6 +1986,8 @@ def combine( **Examples:** + >>> import bigframes.pandas as bpd + >>> bpd.options.display.progress_bar = None >>> df1 = bpd.DataFrame({'A': [0, 0], 'B': [4, 4]}) >>> df2 = bpd.DataFrame({'A': [1, 1], 'B': [3, 3]}) @@ -4047,12 +2013,7 @@ def combine( overwritten with NaNs. Returns: - bigframes.pandas.DataFrame: - Combination of the provided DataFrames. - - Raises: - ValueError: - If ``func`` return value is not Series. + DataFrame: Combination of the provided DataFrames. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @@ -4070,6 +2031,8 @@ def combine_first(self, other) -> DataFrame: **Examples:** + >>> import bigframes.pandas as bpd + >>> bpd.options.display.progress_bar = None >>> df1 = bpd.DataFrame({'A': [None, 0], 'B': [None, 4]}) >>> df2 = bpd.DataFrame({'A': [1, 1], 'B': [3, 3]}) @@ -4085,165 +2048,7 @@ def combine_first(self, other) -> DataFrame: Provided DataFrame to use to fill null values. Returns: - bigframes.pandas.DataFrame: - The result of combining the provided DataFrame with the other object. - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - def explode( - self, column: Union[str, Sequence[str]], *, ignore_index: Optional[bool] = False - ) -> DataFrame: - """ - Transform each element of an array to a row, replicating index values. - - **Examples:** - - - >>> df = bpd.DataFrame({'A': [[0, 1, 2], [], [], [3, 4]], - ... 'B': 1, - ... 'C': [['a', 'b', 'c'], np.nan, [], ['d', 'e']]}) - >>> df.explode('A') - A B C - 0 0 1 ['a' 'b' 'c'] - 0 1 1 ['a' 'b' 'c'] - 0 2 1 ['a' 'b' 'c'] - 1 1 [] - 2 1 [] - 3 3 1 ['d' 'e'] - 3 4 1 ['d' 'e'] - - [7 rows x 3 columns] - >>> df.explode(list('AC')) - A B C - 0 0 1 a - 0 1 1 b - 0 2 1 c - 1 1 - 2 1 - 3 3 1 d - 3 4 1 e - - [7 rows x 3 columns] - - Args: - column (str, Sequence[str]): - Column(s) to explode. For multiple columns, specify a non-empty list - with each element be str or tuple, and all specified columns their - list-like data on same row of the frame must have matching length. - ignore_index (bool, default False): - If True, the resulting index will be labeled 0, 1, …, n - 1. - - Returns: - bigframes.pandas.DataFrame: - Exploded lists to rows of the subset columns; - index will be duplicated for these rows. - - Raises: - ValueError: - * If columns of the frame are not unique. - * If specified columns to explode is empty list. - * If specified columns to explode have not matching count of elements rowwise in the frame. - KeyError: - If incorrect column names are provided - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - def corr(self, method, min_periods, numeric_only) -> DataFrame: - """ - Compute pairwise correlation of columns, excluding NA/null values. - - **Examples:** - - - >>> df = bpd.DataFrame({'A': [1, 2, 3], - ... 'B': [400, 500, 600], - ... 'C': [0.8, 0.4, 0.9]}) - >>> df.corr(numeric_only=True) - A B C - A 1.0 1.0 0.188982 - B 1.0 1.0 0.188982 - C 0.188982 0.188982 1.0 - - [3 rows x 3 columns] - - Args: - method (string, default "pearson"): - Correlation method to use - currently only "pearson" is supported. - min_periods (int, default None): - The minimum number of observations needed to return a result. Non-default values - are not yet supported, so a result will be returned for at least two observations. - numeric_only(bool, default False): - Include only float, int, boolean, decimal data. - - Returns: - bigframes.pandas.DataFrame: Correlation matrix. - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - def cov(self, *, numeric_only) -> DataFrame: - """ - Compute pairwise covariance of columns, excluding NA/null values. - - **Examples:** - - - >>> df = bpd.DataFrame({'A': [1, 2, 3], - ... 'B': [400, 500, 600], - ... 'C': [0.8, 0.4, 0.9]}) - >>> df.cov(numeric_only=True) - A B C - A 1.0 100.0 0.05 - B 100.0 10000.0 5.0 - C 0.05 5.0 0.07 - - [3 rows x 3 columns] - - Args: - numeric_only(bool, default False): - Include only float, int, boolean, decimal data. - - Returns: - bigframes.pandas.DataFrame: The covariance matrix of the series of the DataFrame. - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - def corrwith( - self, - other, - *, - numeric_only: bool = False, - ): - """ - Compute pairwise correlation. - - Pairwise correlation is computed between rows or columns of - DataFrame with rows or columns of Series or DataFrame. DataFrames - are first aligned along both axes before computing the - correlations. - - **Examples:** - - - >>> index = ["a", "b", "c", "d", "e"] - >>> columns = ["one", "two", "three", "four"] - >>> df1 = bpd.DataFrame(np.arange(20).reshape(5, 4), index=index, columns=columns) - >>> df2 = bpd.DataFrame(np.arange(16).reshape(4, 4), index=index[:4], columns=columns) - >>> df1.corrwith(df2) - one 1.0 - two 1.0 - three 1.0 - four 1.0 - dtype: Float64 - - Args: - other (DataFrame, Series): - Object with which to compute correlations. - - numeric_only (bool, default False): - Include only `float`, `int` or `boolean` data. - - Returns: - bigframes.pandas.Series: Pairwise correlations. + DataFrame: The result of combining the provided DataFrame with the other object. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @@ -4257,6 +2062,8 @@ def update( **Examples:** + >>> import bigframes.pandas as bpd + >>> bpd.options.display.progress_bar = None >>> df = bpd.DataFrame({'A': [1, 2, 3], ... 'B': [400, 500, 600]}) @@ -4293,10 +2100,6 @@ def update( Returns: None: This method directly changes calling object. - - Raises: - ValueError: - If a type of join other than ``left`` is provided as an argument. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @@ -4318,57 +2121,6 @@ def groupby( used to group large amounts of data and compute operations on these groups. - **Examples:** - - - >>> df = bpd.DataFrame({'Animal': ['Falcon', 'Falcon', - ... 'Parrot', 'Parrot'], - ... 'Max Speed': [380., 370., 24., 26.]}) - >>> df - Animal Max Speed - 0 Falcon 380.0 - 1 Falcon 370.0 - 2 Parrot 24.0 - 3 Parrot 26.0 - - [4 rows x 2 columns] - - >>> df.groupby(['Animal'])['Max Speed'].mean() - Animal - Falcon 375.0 - Parrot 25.0 - Name: Max Speed, dtype: Float64 - - We can also choose to include NA in group keys or not by setting `dropna`: - - >>> df = bpd.DataFrame([[1, 2, 3],[1, None, 4], [2, 1, 3], [1, 2, 2]], - ... columns=["a", "b", "c"]) - >>> df.groupby(by=["b"]).sum() - a c - b - 1.0 2 3 - 2.0 2 5 - - [2 rows x 2 columns] - - >>> df.groupby(by=["b"], dropna=False).sum() - a c - b - 1.0 2 3 - 2.0 2 5 - 1 4 - - [3 rows x 2 columns] - - We can also choose to return object with group labels or not by setting `as_index`: - - >>> df.groupby(by=["b"], as_index=False).sum() - b a c - 0 1.0 2 3 - 1 2.0 2 5 - - [2 rows x 3 columns] - Args: by (str, Sequence[str]): A label or list of labels may be passed to group by the columns @@ -4389,14 +2141,7 @@ def groupby( values will also be treated as the key in groups. Returns: - bigframes.core.groupby.SeriesGroupBy: - A groupby object that contains information about the groups. - - Raises: - ValueError: - If both ``by`` and ``level`` are specified. - TypeError: - If one of ``by`` or `level`` is not specified. + bigframes.core.groupby.SeriesGroupBy: A groupby object that contains information about the groups. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @@ -4410,205 +2155,38 @@ def map(self, func, na_action: Optional[str] = None) -> DataFrame: to every element of a DataFrame. .. note:: + In pandas 2.1.0, DataFrame.applymap is deprecated and renamed to DataFrame.map. - **Examples:** - - Let's use ``reuse=False`` flag to make sure a new ``remote_function`` - is created every time we run the following code, but you can skip it - to potentially reuse a previously deployed ``remote_function`` from - the same user defined function. - - >>> @bpd.remote_function(reuse=False, cloud_function_service_account="default") # doctest: +SKIP - ... def minutes_to_hours(x: int) -> float: - ... return x/60 - - >>> df_minutes = bpd.DataFrame( - ... {"system_minutes" : [0, 30, 60, 90, 120], - ... "user_minutes" : [0, 15, 75, 90, 6]}) - >>> df_minutes - system_minutes user_minutes - 0 0 0 - 1 30 15 - 2 60 75 - 3 90 90 - 4 120 6 - - [5 rows x 2 columns] - - >>> df_hours = df_minutes.map(minutes_to_hours) # doctest: +SKIP - >>> df_hours # doctest: +SKIP - system_minutes user_minutes - 0 0.0 0.0 - 1 0.5 0.25 - 2 1.0 1.25 - 3 1.5 1.5 - 4 2.0 0.1 - - [5 rows x 2 columns] - - If there are ``NA``/``None`` values in the data, you can ignore - applying the remote function on such values by specifying - ``na_action='ignore'``. - - >>> df_minutes = bpd.DataFrame( - ... { - ... "system_minutes" : [0, 30, 60, None, 90, 120, pd.NA], - ... "user_minutes" : [0, 15, 75, 90, 6, None, pd.NA] - ... }, dtype="Int64") - >>> df_hours = df_minutes.map(minutes_to_hours, na_action='ignore') # doctest: +SKIP - >>> df_hours # doctest: +SKIP - system_minutes user_minutes - 0 0.0 0.0 - 1 0.5 0.25 - 2 1.0 1.25 - 3 1.5 - 4 1.5 0.1 - 5 2.0 - 6 - - [7 rows x 2 columns] - - With experimental Python Transpiler enabled, you can use some lambda functions without - deploying them as remote functions. - - >>> bpd.options.experiments.enable_python_transpiler = True - >>> df_minutes.map(lambda hours: hours / 60) - system_minutes user_minutes - 0 0.0 0.0 - 1 0.5 0.25 - 2 1.0 1.25 - 3 1.5 - 4 1.5 0.1 - 5 2.0 - 6 - - [7 rows x 2 columns] - Args: - func (function): + func: Python function wrapped by ``remote_function`` decorator, returns a single value from a single value. na_action (Optional[str], default None): - ``{None, 'ignore'}``, default None. If `ignore`, propagate NaN + ``{None, 'ignore'}``, default None. If ‘ignore’, propagate NaN values, without passing them to func. Returns: - bigframes.pandas.DataFrame: - Transformed DataFrame. - - Raises: - TypeError: - If value provided for ``func`` is not callable. - ValueError: - If value provided for ``na_action`` is not ``None`` or ``ignore``. + bigframes.dataframe.DataFrame: Transformed DataFrame. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - applymap = map - # ---------------------------------------------------------------------- # Merging / joining methods - def join( - self, - other, - on: Optional[str] = None, - how: str = "left", - lsuffix: str = "", - rsuffix: str = "", - ) -> DataFrame: + def join(self, other, *, on: Optional[str] = None, how: str) -> DataFrame: """Join columns of another DataFrame. Join columns with `other` DataFrame on index - **Examples:** - - - Join two DataFrames by specifying how to handle the operation: - - >>> df1 = bpd.DataFrame({'col1': ['foo', 'bar'], 'col2': [1, 2]}, index=[10, 11]) - >>> df1 - col1 col2 - 10 foo 1 - 11 bar 2 - - [2 rows x 2 columns] - - >>> df2 = bpd.DataFrame({'col3': ['foo', 'baz'], 'col4': [3, 4]}, index=[11, 22]) - >>> df2 - col3 col4 - 11 foo 3 - 22 baz 4 - - [2 rows x 2 columns] - - >>> df1.join(df2) - col1 col2 col3 col4 - 10 foo 1 - 11 bar 2 foo 3 - - [2 rows x 4 columns] - - >>> df1.join(df2, how="left") - col1 col2 col3 col4 - 10 foo 1 - 11 bar 2 foo 3 - - [2 rows x 4 columns] - - >>> df1.join(df2, how="right") - col1 col2 col3 col4 - 11 bar 2 foo 3 - 22 baz 4 - - [2 rows x 4 columns] - - >>> df1.join(df2, how="outer") - col1 col2 col3 col4 - 10 foo 1 - 11 bar 2 foo 3 - 22 baz 4 - - [3 rows x 4 columns] - - >>> df1.join(df2, how="inner") - col1 col2 col3 col4 - 11 bar 2 foo 3 - - [1 rows x 4 columns] - - - Another option to join using the key columns is to use the on parameter: - - >>> df1.join(df2, on="col2", how="right") - col1 col2 col3 col4 - 11 foo 3 - 22 baz 4 - - [2 rows x 4 columns] - - If there are overlapping columns, `lsuffix` and `rsuffix` can be used: - - >>> df1 = bpd.DataFrame({'key': ['K0', 'K1', 'K2'], 'A': ['A0', 'A1', 'A2']}) - >>> df2 = bpd.DataFrame({'key': ['K0', 'K1', 'K2'], 'A': ['B0', 'B1', 'B2']}) - >>> df1.set_index('key').join(df2.set_index('key'), lsuffix='_left', rsuffix='_right') - A_left A_right - key - K0 A0 B0 - K1 A1 B1 - K2 A2 B2 - - [3 rows x 2 columns] - Args: other: - DataFrame or Series with an Index similar to the Index of this one. + DataFrame with an Index similar to the Index of this one. on: Column in the caller to join on the index in other, otherwise joins index-on-index. Like an Excel VLOOKUP operation. - how ({'left', 'right', 'outer', 'inner'}, default 'left'): + how ({'left', 'right', 'outer', 'inner'}, default 'left'`): How to handle the operation of the two objects. ``left``: use calling frame's index (or column if on is specified) ``right``: use `other`'s index. ``outer``: form union of calling @@ -4616,30 +2194,9 @@ def join( and sort it lexicographically. ``inner``: form intersection of calling frame's index (or column if on is specified) with `other`'s index, preserving the order of the calling's one. - ``cross``: creates the cartesian product from both frames, preserves - the order of the left keys. - lsuffix(str, default ''): - Suffix to use from left frame's overlapping columns. - rsuffix(str, default ''): - Suffix to use from right frame's overlapping columns. Returns: - bigframes.pandas.DataFrame: - A dataframe containing columns from both the caller and `other`. - - Raises: - ValueError: - If value for ``on`` is specified for cross join. - ValueError: - If join on columns does not match the index level of the other - DataFrame. Join on columns with multi-index is not supported. - ValueError: - If left index to join on does not have the same number of levels - as the right index. - ValueError: - If columns overlap but no suffix is specified. - ValueError: - If `on` column is not unique. + bigframes.dataframe.DataFrame: A dataframe containing columns from both the caller and `other`. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @@ -4651,14 +2208,11 @@ def merge( "left", "outer", "right", - "cross", ] = "inner", on: Optional[str] = None, *, left_on: Optional[str] = None, right_on: Optional[str] = None, - left_index: bool = False, - right_index: bool = False, sort: bool = False, suffixes: tuple[str, str] = ("_x", "_y"), ) -> DataFrame: @@ -4675,81 +2229,11 @@ def merge( rows will be matched against each other. This is different from usual SQL join behaviour and can lead to unexpected results. - **Examples:** - - - Merge DataFrames df1 and df2 by specifying type of merge: - - >>> df1 = bpd.DataFrame({'a': ['foo', 'bar'], 'b': [1, 2]}) - >>> df1 - a b - 0 foo 1 - 1 bar 2 - - [2 rows x 2 columns] - - >>> df2 = bpd.DataFrame({'a': ['foo', 'baz'], 'c': [3, 4]}) - >>> df2 - a c - 0 foo 3 - 1 baz 4 - - [2 rows x 2 columns] - - >>> df1.merge(df2, how="inner", on="a") - a b c - 0 foo 1 3 - - [1 rows x 3 columns] - - >>> df1.merge(df2, how='left', on='a') - a b c - 0 foo 1 3 - 1 bar 2 - - [2 rows x 3 columns] - - Merge df1 and df2 on the lkey and rkey columns. The value columns have - the default suffixes, _x and _y, appended. - - >>> df1 = bpd.DataFrame({'lkey': ['foo', 'bar', 'baz', 'foo'], - ... 'value': [1, 2, 3, 5]}) - >>> df1 - lkey value - 0 foo 1 - 1 bar 2 - 2 baz 3 - 3 foo 5 - - [4 rows x 2 columns] - - >>> df2 = bpd.DataFrame({'rkey': ['foo', 'bar', 'baz', 'foo'], - ... 'value': [5, 6, 7, 8]}) - >>> df2 - rkey value - 0 foo 5 - 1 bar 6 - 2 baz 7 - 3 foo 8 - - [4 rows x 2 columns] - - >>> df1.merge(df2, left_on='lkey', right_on='rkey') - lkey value_x rkey value_y - 0 foo 1 foo 5 - 1 foo 1 foo 8 - 2 bar 2 bar 6 - 3 baz 3 baz 7 - 4 foo 5 foo 5 - 5 foo 5 foo 8 - - [6 rows x 4 columns] - Args: right: Object to merge with. how: - ``{'left', 'right', 'outer', 'inner', 'cross'}, default 'inner'`` + ``{'left', 'right', 'outer', 'inner'}, default 'inner'`` Type of merge to be performed. ``left``: use only keys from left frame, similar to a SQL left outer join; preserve key order. @@ -4759,8 +2243,6 @@ def merge( join; sort keys lexicographically. ``inner``: use intersection of keys from both frames, similar to a SQL inner join; preserve the order of the left keys. - ``cross``: creates the cartesian product from both frames, preserves the order - of the left keys. on (label or list of labels): Columns to join on. It must be found in both DataFrames. Either on or left_on + right_on @@ -4771,10 +2253,6 @@ def merge( right_on (label or list of labels): Columns to join on in the right DataFrame. Either on or left_on + right_on must be passed in. - left_index (bool, default False): - Use the index from the left DataFrame as the join key. - right_index (bool, default False): - Use the index from the right DataFrame as the join key. sort: Default False. Sort the join keys lexicographically in the result DataFrame. If False, the order of the join keys depends @@ -4788,315 +2266,20 @@ def merge( no suffix. At least one of the values must not be None. Returns: - bigframes.pandas.DataFrame: - A DataFrame of the two merged objects. - - Raises: - ValueError: - If value for ``on`` is specified for cross join. - ValueError: - If ``on`` or ``left_on`` + ``right_on`` are not specified when ``on`` is ``None``. - ValueError: - If ``on`` and ``left_on`` + ``right_on`` are specified when ``on`` is not ``None``. - ValueError: - If no column with the provided label is found in ``self`` for left join. - ValueError: - If no column with the provided label is found in ``self`` for right join. + bigframes.dataframe.DataFrame: A DataFrame of the two merged objects. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - def resample( - self, - rule: str, - *, - closed: Optional[Literal["right", "left"]] = None, - label: Optional[Literal["right", "left"]] = None, - on=None, - level=None, - origin: Union[ - Union[pd.Timestamp, datetime.datetime, np.datetime64, int, float, str], - Literal["epoch", "start", "start_day", "end", "end_day"], - ] = "start_day", - ): - """Resample time-series data. - - **Examples:** + def apply(self, func, *, args=(), **kwargs): + """Apply a function along an axis of the DataFrame. - >>> import bigframes.pandas as bpd - >>> data = { - ... "timestamp_col": pd.date_range( - ... start="2021-01-01 13:00:00", periods=30, freq="1s" - ... ), - ... "int64_col": range(30), - ... "int64_too": range(10, 40), - ... } - - Resample on a DataFrame with index: - - >>> df = bpd.DataFrame(data).set_index("timestamp_col") - >>> df.resample(rule="7s").min() - int64_col int64_too - timestamp_col - 2021-01-01 12:59:55 0 10 - 2021-01-01 13:00:02 2 12 - 2021-01-01 13:00:09 9 19 - 2021-01-01 13:00:16 16 26 - 2021-01-01 13:00:23 23 33 - - [5 rows x 2 columns] - - Resample with column and origin set to 'start': - - >>> df = bpd.DataFrame(data) - >>> df.resample(rule="7s", on = "timestamp_col", origin="start").min() - int64_col int64_too - timestamp_col - 2021-01-01 13:00:00 0 10 - 2021-01-01 13:00:07 7 17 - 2021-01-01 13:00:14 14 24 - 2021-01-01 13:00:21 21 31 - 2021-01-01 13:00:28 28 38 - - [5 rows x 2 columns] - - Args: - rule (str): - The offset string representing target conversion. - Offsets 'ME', 'YE', 'QE', 'BME', 'BA', 'BQE', and 'W' are *not* - supported. - closed (Literal['left'] | None): - Which side of bin interval is closed. The default is 'left' for - all supported frequency offsets. - label (Literal['right'] | Literal['left'] | None): - Which bin edge label to label bucket with. The default is 'left' - for all supported frequency offsets. - on (str, default None): - For a DataFrame, column to use instead of index for resampling. Column - must be datetime-like. - level (str or int, default None): - For a MultiIndex, level (name or number) to use for resampling. - level must be datetime-like. - origin(str, default 'start_day'): - The timestamp on which to adjust the grouping. Must be one of the following: - 'epoch': origin is 1970-01-01 - 'start': origin is the first value of the timeseries - 'start_day': origin is the first day at midnight of the timeseries - Origin values 'end' and 'end_day' are *not* supported. - Returns: - DataFrameGroupBy: DataFrameGroupBy object. - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - def round(self, decimals): - """ - Round a DataFrame to a variable number of decimal places. - - **Examples:** - - >>> import bigframes.pandas as bpd - >>> df = bpd.DataFrame([(.21, .32), (.01, .67), (.66, .03), (.21, .18)], - ... columns=['dogs', 'cats']) - >>> df - dogs cats - 0 0.21 0.32 - 1 0.01 0.67 - 2 0.66 0.03 - 3 0.21 0.18 - - [4 rows x 2 columns] - - By providing an integer each column is rounded to the same number - of decimal places - - >>> df.round(1) - dogs cats - 0 0.2 0.3 - 1 0.0 0.7 - 2 0.7 0.0 - 3 0.2 0.2 - - [4 rows x 2 columns] - - With a dict, the number of places for specific columns can be - specified with the column names as key and the number of decimal - places as value - - >>> df.round({'dogs': 1, 'cats': 0}) - dogs cats - 0 0.2 0.0 - 1 0.0 1.0 - 2 0.7 0.0 - 3 0.2 0.0 - - [4 rows x 2 columns] - - Using a Series, the number of places for specific columns can be - specified with the column names as index and the number of - decimal places as value - - >>> decimals = pd.Series([0, 1], index=['cats', 'dogs']) - >>> df.round(decimals) - dogs cats - 0 0.2 0.0 - 1 0.0 1.0 - 2 0.7 0.0 - 3 0.2 0.0 - - [4 rows x 2 columns] - - Args: - decimals (int, dict, Series): - Number of decimal places to round each column to. If an int is - given, round each column to the same number of places. - Otherwise dict and Series round to variable numbers of places. - Column names should be in the keys if `decimals` is a - dict-like, or in the index if `decimals` is a Series. Any - columns not included in `decimals` will be left as is. Elements - of `decimals` which are not columns of the input will be - ignored. - - Returns: - bigframes.pandas.DataFrame: - A DataFrame with the affected columns rounded to the specified - number of decimal places. - - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - def apply(self, func, *, axis=0, args=(), **kwargs): - """Apply a function along an axis of the DataFrame. - - Objects passed to the function are Series objects whose index is - the DataFrame's index (``axis=0``) or the DataFrame's columns (``axis=1``). - The final return type is inferred from the return type of the applied - function. - - .. note:: - ``axis=1`` scenario is in preview. - - **Examples:** - - - >>> df = bpd.DataFrame({'col1': [1, 2], 'col2': [3, 4]}) - >>> df - col1 col2 - 0 1 3 - 1 2 4 - - [2 rows x 2 columns] - - >>> def square(x): - ... return x * x - - >>> df.apply(square) - col1 col2 - 0 1 9 - 1 4 16 - - [2 rows x 2 columns] - - You could apply a user defined function to every row of the DataFrame by - creating a remote function out of it, and using it with `axis=1`. Within - the function, each row is passed as a ``pandas.Series``. It is recommended - to select only the necessary columns before calling `apply()`. Note: This - feature is currently in **preview**. - - >>> @bpd.remote_function(reuse=False, cloud_function_service_account="default") # doctest: +SKIP - ... def foo(row: pd.Series) -> int: - ... result = 1 - ... result += row["col1"] - ... result += row["col2"]*row["col2"] - ... return result - - >>> df[["col1", "col2"]].apply(foo, axis=1) # doctest: +SKIP - 0 11 - 1 19 - dtype: Int64 - - You could return an array output for every input row from the remote - function. - - >>> @bpd.remote_function(reuse=False, cloud_function_service_account="default") # doctest: +SKIP - ... def marks_analyzer(marks: pd.Series) -> list[float]: - ... import statistics - ... average = marks.mean() - ... median = marks.median() - ... gemetric_mean = statistics.geometric_mean(marks.values) - ... harmonic_mean = statistics.harmonic_mean(marks.values) - ... return [ - ... round(stat, 2) for stat in - ... (average, median, gemetric_mean, harmonic_mean) - ... ] - - >>> df = bpd.DataFrame({ - ... "physics": [67, 80, 75], - ... "chemistry": [88, 56, 72], - ... "algebra": [78, 91, 79] - ... }, index=["Alice", "Bob", "Charlie"]) - >>> stats = df.apply(marks_analyzer, axis=1) # doctest: +SKIP - >>> stats # doctest: +SKIP - Alice [77.67 78. 77.19 76.71] - Bob [75.67 80. 74.15 72.56] - Charlie [75.33 75. 75.28 75.22] - dtype: list[pyarrow] - - You could also apply a remote function which accepts multiple parameters - to every row of a DataFrame by using it with `axis=1` if the DataFrame - has matching number of columns and data types. Note: This feature is - currently in **preview**. - - >>> df = bpd.DataFrame({ - ... 'col1': [1, 2], - ... 'col2': [3, 4], - ... 'col3': [5, 5] - ... }) - >>> df - col1 col2 col3 - 0 1 3 5 - 1 2 4 5 - - [2 rows x 3 columns] - - >>> @bpd.remote_function(reuse=False, cloud_function_service_account="default") # doctest: +SKIP - ... def foo(x: int, y: int, z: int) -> float: - ... result = 1 - ... result += x - ... result += y/z - ... return result - - >>> df.apply(foo, axis=1) # doctest: +SKIP - 0 2.6 - 1 3.8 - dtype: Float64 - - With experimental Python Transpiler enabled, you can use some lambda functions without - deploying them as remote functions: - - >>> bpd.options.experiments.enable_python_transpiler = True - >>> df.apply(lambda row: 1 + row.col1 + row.col2/row.col3, axis=1) - 0 2.6 - 1 3.8 - dtype: Float64 + Objects passed to the function are Series objects whose index is + the DataFrame's index (``axis=0``) the final return type + is inferred from the return type of the applied function. Args: func (function): - Function to apply to each column or row. To apply to each row - (i.e. when `axis=1` is specified) the function can be of one of - the two types: - - (1). It accepts a single input parameter of type `Series`, in - which case each row is delivered to the function as a pandas - Series. - - (2). It accept one or more parameters, in which case column values - are delivered to the function as separate arguments (mapping - to those parameters) for each row. For this to work the - `DataFrame` must have same number of columns and matching - data types. - axis ({index (0), columns (1)}): - Axis along which the function is applied. Specify 0 or 'index' - to apply function to each column. Specify 1 or 'columns' to - apply function to each row. + Function to apply to each column or row. args (tuple): Positional arguments to pass to `func` in addition to the array/series. @@ -5105,18 +2288,7 @@ def apply(self, func, *, axis=0, args=(), **kwargs): `func`. Returns: - bigframes.pandas.DataFrame or bigframes.pandas.Series: - Result of applying ``func`` along the given axis of the DataFrame. - - Raises: - ValueError: - If a remote function is not provided when ``axis=1`` is specified. - ValueError: - If number or input params in the remote function are not the same as - the number of columns in the dataframe. - ValueError: - If the dtypes of the columns in the dataframe are not compatible with - the data types of the remote function input params. + pandas.Series or bigframes.DataFrame: Result of applying ``func`` along the given axis of the DataFrame. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @@ -5131,31 +2303,6 @@ def any(self, *, axis=0, bool_only: bool = False): along a Dataframe axis that is True or equivalent (e.g. non-zero or non-empty). - **Examples:** - - - >>> df = bpd.DataFrame({"A": [True, True], "B": [False, False]}) - >>> df - A B - 0 True False - 1 True False - - [2 rows x 2 columns] - - Checking if each column contains at least one True element(the default behavior without an explicit axis parameter): - - >>> df.any() - A True - B False - dtype: boolean - - Checking if each row contains at least one True element: - - >>> df.any(axis=1) - 0 True - 1 True - dtype: boolean - Args: axis ({index (0), columns (1)}): Axis for the function to be applied on. @@ -5164,7 +2311,7 @@ def any(self, *, axis=0, bool_only: bool = False): Include only boolean columns. Returns: - bigframes.pandas.Series: Series indicating if any element is True per column. + Series """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @@ -5176,31 +2323,6 @@ def all(self, axis=0, *, bool_only: bool = False): along a DataFrame axis that is False or equivalent (e.g. zero or empty). - **Examples:** - - - >>> df = bpd.DataFrame({"A": [True, True], "B": [False, False]}) - >>> df - A B - 0 True False - 1 True False - - [2 rows x 2 columns] - - Checking if all values in each column are True(the default behavior without an explicit axis parameter): - - >>> df.all() - A True - B False - dtype: boolean - - Checking across rows to see if all values are True: - - >>> df.all(axis=1) - 0 False - 1 False - dtype: boolean - Args: axis ({index (0), columns (1)}): Axis for the function to be applied on. @@ -5209,7 +2331,7 @@ def all(self, axis=0, *, bool_only: bool = False): Include only boolean columns. Returns: - bigframes.pandas.Series: Series indicating if all elements are True per column. + bigframes.series.Series: Series if all elements are True. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @@ -5217,42 +2339,15 @@ def prod(self, axis=0, *, numeric_only: bool = False): """ Return the product of the values over the requested axis. - **Examples:** - - >>> import bigframes.pandas as bpd - >>> df = bpd.DataFrame({"A": [1, 2, 3], "B": [4.5, 5.5, 6.5]}) - >>> df - A B - 0 1 4.5 - 1 2 5.5 - 2 3 6.5 - - [3 rows x 2 columns] - - Calculating the product of each column(the default behavior without an explicit axis parameter): - - >>> df.prod() - A 6.0 - B 160.875 - dtype: Float64 - - Calculating the product of each row: - - >>> df.prod(axis=1) - 0 4.5 - 1 11.0 - 2 19.5 - dtype: Float64 - Args: - axis ({index (0), columns (1)}): + aßxis ({index (0), columns (1)}): Axis for the function to be applied on. For Series this parameter is unused and defaults to 0. numeric_only (bool. default False): Include only float, int, boolean columns. Returns: - bigframes.pandas.Series: Series with the product of the values. + bigframes.series.Series: Series with the product of the values. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @@ -5262,31 +2357,6 @@ def min(self, axis=0, *, numeric_only: bool = False): If you want the *index* of the minimum, use ``idxmin``. This is the equivalent of the ``numpy.ndarray`` method ``argmin``. - **Examples:** - - - >>> df = bpd.DataFrame({"A": [1, 3], "B": [2, 4]}) - >>> df - A B - 0 1 2 - 1 3 4 - - [2 rows x 2 columns] - - Finding the minimum value in each column (the default behavior without an explicit axis parameter). - - >>> df.min() - A 1 - B 2 - dtype: Int64 - - Finding the minimum value in each row. - - >>> df.min(axis=1) - 0 1 - 1 3 - dtype: Int64 - Args: axis ({index (0), columns (1)}): Axis for the function to be applied on. @@ -5295,7 +2365,7 @@ def min(self, axis=0, *, numeric_only: bool = False): Default False. Include only float, int, boolean columns. Returns: - bigframes.pandas.Series: Series with the minimum of the values. + bigframes.series.Series: Series with the minimum of the values. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @@ -5305,31 +2375,6 @@ def max(self, axis=0, *, numeric_only: bool = False): If you want the *index* of the maximum, use ``idxmax``. This is the equivalent of the ``numpy.ndarray`` method ``argmax``. - **Examples:** - - - >>> df = bpd.DataFrame({"A": [1, 3], "B": [2, 4]}) - >>> df - A B - 0 1 2 - 1 3 4 - - [2 rows x 2 columns] - - Finding the maximum value in each column (the default behavior without an explicit axis parameter). - - >>> df.max() - A 3 - B 4 - dtype: Int64 - - Finding the maximum value in each row. - - >>> df.max(axis=1) - 0 2 - 1 4 - dtype: Int64 - Args: axis ({index (0), columns (1)}): Axis for the function to be applied on. @@ -5338,7 +2383,7 @@ def max(self, axis=0, *, numeric_only: bool = False): Default False. Include only float, int, boolean columns. Returns: - bigframes.pandas.Series: Series after the maximum of values. + bigframes.series.Series: Series after the maximum of values. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @@ -5347,31 +2392,6 @@ def sum(self, axis=0, *, numeric_only: bool = False): This is equivalent to the method ``numpy.sum``. - **Examples:** - - - >>> df = bpd.DataFrame({"A": [1, 3], "B": [2, 4]}) - >>> df - A B - 0 1 2 - 1 3 4 - - [2 rows x 2 columns] - - Calculating the sum of each column (the default behavior without an explicit axis parameter). - - >>> df.sum() - A 4 - B 6 - dtype: Int64 - - Calculating the sum of each row. - - >>> df.sum(axis=1) - 0 3 - 1 7 - dtype: Int64 - Args: axis ({index (0), columns (1)}): Axis for the function to be applied on. @@ -5380,38 +2400,13 @@ def sum(self, axis=0, *, numeric_only: bool = False): Default False. Include only float, int, boolean columns. Returns: - bigframes.pandas.Series: Series with the sum of values. + bigframes.series.Series: Series with the sum of values. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) def mean(self, axis=0, *, numeric_only: bool = False): """Return the mean of the values over the requested axis. - **Examples:** - - - >>> df = bpd.DataFrame({"A": [1, 3], "B": [2, 4]}) - >>> df - A B - 0 1 2 - 1 3 4 - - [2 rows x 2 columns] - - Calculating the mean of each column (the default behavior without an explicit axis parameter). - - >>> df.mean() - A 2.0 - B 3.0 - dtype: Float64 - - Calculating the mean of each row. - - >>> df.mean(axis=1) - 0 1.5 - 1 3.5 - dtype: Float64 - Args: axis ({index (0), columns (1)}): Axis for the function to be applied on. @@ -5420,78 +2415,22 @@ def mean(self, axis=0, *, numeric_only: bool = False): Default False. Include only float, int, boolean columns. Returns: - bigframes.pandas.Series: Series with the mean of values. + bigframes.series.Series: Series with the mean of values. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - def median(self, *, numeric_only: bool = False, exact: bool = True): - """Return the median of the values over colunms. - - **Examples:** - - >>> import bigframes.pandas as bpd - >>> df = bpd.DataFrame({"A": [1, 3], "B": [2, 4]}) - >>> df - A B - 0 1 2 - 1 3 4 - - [2 rows x 2 columns] - - Finding the median value of each column. - - >>> df.median() - A 2.0 - B 3.0 - dtype: Float64 + def median(self, *, numeric_only: bool = False, exact: bool = False): + """Return the median of the values over the requested axis. Args: numeric_only (bool. default False): Default False. Include only float, int, boolean columns. - exact (bool. default True): - Default True. Get the exact median instead of an approximate - one. - - Returns: - bigframes.pandas.Series: Series with the median of values. - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - def quantile( - self, q: Union[float, Sequence[float]] = 0.5, *, numeric_only: bool = False - ): - """ - Return values at the given quantile over requested axis. - - **Examples:** - - >>> import bigframes.pandas as bpd - >>> df = bpd.DataFrame(np.array([[1, 1], [2, 10], [3, 100], [4, 100]]), - ... columns=['a', 'b']) - >>> df.quantile(.1) - a 1.3 - b 3.7 - Name: 0.1, dtype: Float64 - >>> df.quantile([.1, .5]) - a b - 0.1 1.3 3.7 - 0.5 2.5 55.0 - - [2 rows x 2 columns] - - Args: - q (float or array-like, default 0.5 (50% quantile)): - Value between 0 <= q <= 1, the quantile(s) to compute. - numeric_only (bool, default False): - Include only `float`, `int` or `boolean` data. + exact (bool. default False): + Default False. Get the exact median instead of an approximate + one. Note: ``exact=True`` not yet supported. Returns: - bigframes.pandas.DataFrame or bigframes.pandas.Series: - If ``q`` is an array, a DataFrame will be returned where the - index is ``q``, the columns are the columns of self, and the - values are the quantiles. - If ``q`` is a float, a Series will be returned where the - index is the columns of self and the values are the quantiles. + bigframes.series.Series: Series with the median of values. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @@ -5500,32 +2439,6 @@ def var(self, axis=0, *, numeric_only: bool = False): Normalized by N-1 by default. - **Examples:** - - - >>> df = bpd.DataFrame({"A": [1, 3], "B": [2, 4]}) - >>> df - A B - 0 1 2 - 1 3 4 - - [2 rows x 2 columns] - - Calculating the variance of each column (the default behavior without an explicit axis parameter). - - >>> df.var() - A 2.0 - B 2.0 - dtype: Float64 - - Calculating the variance of each row. - - >>> df.var(axis=1) - 0 0.5 - 1 0.5 - dtype: Float64 - - Args: axis ({index (0), columns (1)}): Axis for the function to be applied on. @@ -5534,162 +2447,66 @@ def var(self, axis=0, *, numeric_only: bool = False): Default False. Include only float, int, boolean columns. Returns: - bigframes.pandas.Series: Series with unbiased variance over requested axis. + bigframes.series.Series: Series with unbiased variance over requested axis. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) def skew(self, *, numeric_only: bool = False): - """Return unbiased skew over columns. + """Return unbiased skew over requested axis. Normalized by N-1. - **Examples:** - - - >>> df = bpd.DataFrame({'A': [1, 2, 3, 4, 5], - ... 'B': [5, 4, 3, 2, 1], - ... 'C': [2, 2, 3, 2, 2]}) - >>> df - A B C - 0 1 5 2 - 1 2 4 2 - 2 3 3 3 - 3 4 2 2 - 4 5 1 2 - - [5 rows x 3 columns] - - Calculating the skewness of each column. - - >>> df.skew() - A 0.0 - B 0.0 - C 2.236068 - dtype: Float64 - Args: numeric_only (bool, default False): Include only float, int, boolean columns. Returns: - bigframes.pandas.Series: Series. + Series """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) def kurt(self, *, numeric_only: bool = False): - """Return unbiased kurtosis over columns. + """Return unbiased kurtosis over requested axis. Kurtosis obtained using Fisher's definition of kurtosis (kurtosis of normal == 0.0). Normalized by N-1. - **Examples:** - - - >>> df = bpd.DataFrame({"A": [1, 2, 3, 4, 5], - ... "B": [3, 4, 3, 2, 1], - ... "C": [2, 2, 3, 2, 2]}) - >>> df - A B C - 0 1 3 2 - 1 2 4 2 - 2 3 3 3 - 3 4 2 2 - 4 5 1 2 - - [5 rows x 3 columns] - - Calculating the kurtosis value of each column: - - >>> df.kurt() - A -1.2 - B -0.177515 - C 5.0 - dtype: Float64 - Args: numeric_only (bool, default False): Include only float, int, boolean columns. Returns: - bigframes.pandas.Series: Series. + Series """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) def std(self, *, numeric_only: bool = False): - """Return sample standard deviation over columns. + """Return sample standard deviation over requested axis. Normalized by N-1 by default. - **Examples:** - - - >>> df = bpd.DataFrame({"A": [1, 2, 3, 4, 5], - ... "B": [3, 4, 3, 2, 1], - ... "C": [2, 2, 3, 2, 2]}) - >>> df - A B C - 0 1 3 2 - 1 2 4 2 - 2 3 3 3 - 3 4 2 2 - 4 5 1 2 - - [5 rows x 3 columns] - - Calculating the standard deviation of each column: - - >>> df.std() - A 1.581139 - B 1.140175 - C 0.447214 - dtype: Float64 - Args: numeric_only (bool. default False): Default False. Include only float, int, boolean columns. Returns: - bigframes.pandas.Series: Series with sample standard deviation. + bigframes.series.Series: Series with sample standard deviation. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) def count(self, *, numeric_only: bool = False): """ - Count non-NA cells for each column. + Count non-NA cells for each column or row. The values `None`, `NaN`, `NaT`, and optionally `numpy.inf` (depending on `pandas.options.mode.use_inf_as_na`) are considered NA. - **Examples:** - - - >>> df = bpd.DataFrame({"A": [1, None, 3, 4, 5], - ... "B": [1, 2, 3, 4, 5], - ... "C": [None, 3.5, None, 4.5, 5.0]}) - >>> df - A B C - 0 1.0 1 - 1 2 3.5 - 2 3.0 3 - 3 4.0 4 4.5 - 4 5.0 5 5.0 - - [5 rows x 3 columns] - - Counting non-NA values for each column: - - >>> df.count() - A 4 - B 5 - C 3 - dtype: Int64 - Args: numeric_only (bool, default False): Include only `float`, `int` or `boolean` data. Returns: - bigframes.pandas.Series: For each column/row the number of + bigframes.series.Series: For each column/row the number of non-NA/null entries. If `level` is specified returns a `DataFrame`. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @@ -5706,87 +2523,32 @@ def nlargest(self, n: int, columns, keep: str = "first"): ``df.sort_values(columns, ascending=False).head(n)``, but more performant. + Args: + n (int): + Number of rows to return. + columns (label or list of labels): + Column label(s) to order by. + keep ({'first', 'last', 'all'}, default 'first'): + Where there are duplicate values: + + - ``first`` : prioritize the first occurrence(s) + - ``last`` : prioritize the last occurrence(s) + - ``all`` : do not drop any duplicates, even it means + selecting more than `n` items. + + Returns: + DataFrame: The first `n` rows ordered by the given columns in descending order. + .. note:: This function cannot be used with all column types. For example, when specifying columns with `object` or `category` dtypes, ``TypeError`` is raised. + """ + raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - **Examples:** - - >>> import bigframes.pandas as bpd - >>> df = bpd.DataFrame({"A": [1, 1, 3, 3, 5, 5], - ... "B": [5, 6, 3, 4, 1, 2], - ... "C": ['a', 'b', 'a', 'b', 'a', 'b']}) - >>> df - A B C - 0 1 5 a - 1 1 6 b - 2 3 3 a - 3 3 4 b - 4 5 1 a - 5 5 2 b - - [6 rows x 3 columns] - - Returns rows with the largest value in 'A', including all ties: - - >>> df.nlargest(1, 'A', keep = "all") - A B C - 4 5 1 a - 5 5 2 b - - [2 rows x 3 columns] - - Returns the first row with the largest value in 'A', default behavior in case of ties: - - >>> df.nlargest(1, 'A') - A B C - 4 5 1 a - - [1 rows x 3 columns] - - Returns the last row with the largest value in 'A' in case of ties: - - >>> df.nlargest(1, 'A', keep = "last") - A B C - 5 5 2 b - - [1 rows x 3 columns] - - Returns the row with the largest combined values in both 'A' and 'C': - - >>> df.nlargest(1, ['A', 'C']) - A B C - 5 5 2 b - - [1 rows x 3 columns] - - Args: - n (int): - Number of rows to return. - columns (label or list of labels): - Column label(s) to order by. - keep ({'first', 'last', 'all'}, default 'first'): - Where there are duplicate values: - - - ``first`` : prioritize the first occurrence(s) - - ``last`` : prioritize the last occurrence(s) - - ``all`` : do not drop any duplicates, even it means - selecting more than `n` items. - - Returns: - bigframes.pandas.DataFrame: - The first `n` rows ordered by the given columns in descending order. - - Raises: - ValueError: - If value of ``keep`` is not ``first``, ``last``, or ``all``. - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - def nsmallest(self, n: int, columns, keep: str = "first"): - """ - Return the first `n` rows ordered by `columns` in ascending order. + def nsmallest(self, n: int, columns, keep: str = "first"): + """ + Return the first `n` rows ordered by `columns` in ascending order. Return the first `n` rows with the smallest values in `columns`, in ascending order. The columns that are not specified are returned as @@ -5796,62 +2558,6 @@ def nsmallest(self, n: int, columns, keep: str = "first"): ``df.sort_values(columns, ascending=True).head(n)``, but more performant. - .. note:: - This function cannot be used with all column types. For example, when - specifying columns with `object` or `category` dtypes, ``TypeError`` is - raised. - - **Examples:** - - >>> import bigframes.pandas as bpd - >>> df = bpd.DataFrame({"A": [1, 1, 3, 3, 5, 5], - ... "B": [5, 6, 3, 4, 1, 2], - ... "C": ['a', 'b', 'a', 'b', 'a', 'b']}) - >>> df - A B C - 0 1 5 a - 1 1 6 b - 2 3 3 a - 3 3 4 b - 4 5 1 a - 5 5 2 b - - [6 rows x 3 columns] - - Returns rows with the smallest value in 'A', including all ties: - - >>> df.nsmallest(1, 'A', keep = "all") - A B C - 0 1 5 a - 1 1 6 b - - [2 rows x 3 columns] - - Returns the first row with the smallest value in 'A', default behavior in case of ties: - - >>> df.nsmallest(1, 'A') - A B C - 0 1 5 a - - [1 rows x 3 columns] - - Returns the last row with the smallest value in 'A' in case of ties: - - >>> df.nsmallest(1, 'A', keep = "last") - A B C - 1 1 6 b - - [1 rows x 3 columns] - - Returns rows with the smallest values in 'A' and 'C' - - >>> df.nsmallest(1, ['A', 'C']) - A B C - 0 1 5 a - - [1 rows x 3 columns] - - Args: n (int): Number of rows to return. @@ -5866,68 +2572,34 @@ def nsmallest(self, n: int, columns, keep: str = "first"): selecting more than `n` items. Returns: - bigframes.pandas.DataFrame: - The first `n` rows ordered by the given columns in ascending order. + DataFrame: The first `n` rows ordered by the given columns in ascending order. - Raises: - ValueError: - If value of ``keep`` is not ``first``, ``last``, or ``all``. + .. note:: + This function cannot be used with all column types. For example, when + specifying columns with `object` or `category` dtypes, ``TypeError`` is + raised. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) def idxmin(self): """ - Return index of first occurrence of minimum over columns. + Return index of first occurrence of minimum over requested axis. NA/null values are excluded. - **Examples:** - - - >>> df = bpd.DataFrame({"A": [3, 1, 2], "B": [1, 2, 3]}) - >>> df - A B - 0 3 1 - 1 1 2 - 2 2 3 - - [3 rows x 2 columns] - - >>> df.idxmin() - A 1 - B 0 - dtype: Int64 - Returns: - bigframes.pandas.Series: Indexes of minima along the columns. + Series: Indexes of minima along the specified axis. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) def idxmax(self): """ - Return index of first occurrence of maximum over columns. + Return index of first occurrence of maximum over requested axis. NA/null values are excluded. - **Examples:** - - - >>> df = bpd.DataFrame({"A": [3, 1, 2], "B": [1, 2, 3]}) - >>> df - A B - 0 3 1 - 1 1 2 - 2 2 3 - - [3 rows x 2 columns] - - >>> df.idxmax() - A 0 - B 2 - dtype: Int64 - Returns: - bigframes.pandas.Series: Indexes of maxima along the columns. + Series: Indexes of maxima along the specified axis. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @@ -5941,313 +2613,95 @@ def melt(self, id_vars, value_vars, var_name, value_name): the row axis, leaving just two non-identifier columns, 'variable' and 'value'. - **Examples:** - - - >>> df = bpd.DataFrame({"A": [1, None, 3, 4, 5], - ... "B": [1, 2, 3, 4, 5], - ... "C": [None, 3.5, None, 4.5, 5.0]}) - >>> df - A B C - 0 1.0 1 - 1 2 3.5 - 2 3.0 3 - 3 4.0 4 4.5 - 4 5.0 5 5.0 - - [5 rows x 3 columns] - - Using `melt` without optional arguments: - - >>> df.melt() - variable value - 0 A 1.0 - 1 A - 2 A 3.0 - 3 A 4.0 - 4 A 5.0 - 5 B 1.0 - 6 B 2.0 - 7 B 3.0 - 8 B 4.0 - 9 B 5.0 - ... - - [15 rows x 2 columns] - - Using `melt` with `id_vars` and `value_vars`: - - >>> df.melt(id_vars='A', value_vars=['B', 'C']) - A variable value - 0 1.0 B 1.0 - 1 B 2.0 - 2 3.0 B 3.0 - 3 4.0 B 4.0 - 4 5.0 B 5.0 - 5 1.0 C - 6 C 3.5 - 7 3.0 C - 8 4.0 C 4.5 - 9 5.0 C 5.0 - - [10 rows x 3 columns] - - - Args: - id_vars (tuple, list, or ndarray, optional): - Column(s) to use as identifier variables. - value_vars (tuple, list, or ndarray, optional): - Column(s) to unpivot. If not specified, uses all columns that - are not set as `id_vars`. - var_name (scalar): - Name to use for the 'variable' column. If None it uses - ``frame.columns.name`` or 'variable'. - value_name (scalar, default 'value'): - Name to use for the 'value' column. + Parameters + ---------- + id_vars (tuple, list, or ndarray, optional): + Column(s) to use as identifier variables. + value_vars (tuple, list, or ndarray, optional): + Column(s) to unpivot. If not specified, uses all columns that + are not set as `id_vars`. + var_name (scalar): + Name to use for the 'variable' column. If None it uses + ``frame.columns.name`` or 'variable'. + value_name (scalar, default 'value'): + Name to use for the 'value' column. Returns: - bigframes.pandas.DataFrame: Unpivoted DataFrame. + DataFrame: Unpivoted DataFrame. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) def nunique(self): """ - Count number of distinct elements in each column. - - **Examples:** - - - >>> df = bpd.DataFrame({"A": [3, 1, 2], "B": [1, 2, 2]}) - >>> df - A B - 0 3 1 - 1 1 2 - 2 2 2 - - [3 rows x 2 columns] - - >>> df.nunique() - A 3 - B 2 - dtype: Int64 + Count number of distinct elements in specified axis. Returns: - bigframes.pandas.Series: Series with number of distinct elements. + bigframes.series.Series: Series with number of distinct elements. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) def cummin(self) -> DataFrame: - """Return cumulative minimum over columns. + """Return cumulative minimum over a DataFrame axis. Returns a DataFrame of the same size containing the cumulative minimum. - **Examples:** - - - >>> df = bpd.DataFrame({"A": [3, 1, 2], "B": [1, 2, 3]}) - >>> df - A B - 0 3 1 - 1 1 2 - 2 2 3 - - [3 rows x 2 columns] - - >>> df.cummin() - A B - 0 3 1 - 1 1 1 - 2 1 1 - - [3 rows x 2 columns] - Returns: - bigframes.pandas.DataFrame: Return cumulative minimum of DataFrame. + bigframes.dataframe.DataFrame: Return cumulative minimum of DataFrame. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) def cummax(self) -> DataFrame: - """Return cumulative maximum over columns. + """Return cumulative maximum over a DataFrame axis. Returns a DataFrame of the same size containing the cumulative maximum. - **Examples:** - - - >>> df = bpd.DataFrame({"A": [3, 1, 2], "B": [1, 2, 3]}) - >>> df - A B - 0 3 1 - 1 1 2 - 2 2 3 - - [3 rows x 2 columns] - - >>> df.cummax() - A B - 0 3 1 - 1 3 2 - 2 3 3 - - [3 rows x 2 columns] - Returns: - bigframes.pandas.DataFrame: Return cumulative maximum of DataFrame. + bigframes.dataframe.DataFrame: Return cumulative maximum of DataFrame. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) def cumsum(self) -> DataFrame: - """Return cumulative sum over columns. + """Return cumulative sum over a DataFrame axis. Returns a DataFrame of the same size containing the cumulative sum. - **Examples:** - - - >>> df = bpd.DataFrame({"A": [3, 1, 2], "B": [1, 2, 3]}) - >>> df - A B - 0 3 1 - 1 1 2 - 2 2 3 - - [3 rows x 2 columns] - - >>> df.cumsum() - A B - 0 3 1 - 1 4 3 - 2 6 6 - - [3 rows x 2 columns] - Returns: - bigframes.pandas.DataFrame: - Return cumulative sum of DataFrame. - - Raises: - ValueError: - If values are not of numeric type. + bigframes.dataframe.DataFrame: Return cumulative sum of DataFrame. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) def cumprod(self) -> DataFrame: - """Return cumulative product over columns. + """Return cumulative product over a DataFrame axis. Returns a DataFrame of the same size containing the cumulative product. - **Examples:** - - - >>> df = bpd.DataFrame({"A": [3, 1, 2], "B": [1, 2, 3]}) - >>> df - A B - 0 3 1 - 1 1 2 - 2 2 3 - - [3 rows x 2 columns] - - >>> df.cumprod() - A B - 0 3.0 1.0 - 1 3.0 2.0 - 2 6.0 6.0 - - [3 rows x 2 columns] - Returns: - bigframes.pandas.DataFrame: - Return cumulative product of DataFrame. - - Raises: - ValueError: - If values are not of numeric type. + bigframes.dataframe.DataFrame: Return cumulative product of DataFrame. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) def diff( self, periods: int = 1, - ) -> generic.NDFrame: + ) -> NDFrame: """First discrete difference of element. Calculates the difference of a DataFrame element compared with another element in the DataFrame (default is element in previous row). - **Examples:** - - - >>> df = bpd.DataFrame({"A": [3, 1, 2], "B": [1, 2, 3]}) - >>> df - A B - 0 3 1 - 1 1 2 - 2 2 3 - - [3 rows x 2 columns] - - Calculating difference with default periods=1: - - >>> df.diff() - A B - 0 - 1 -2 1 - 2 1 1 - - [3 rows x 2 columns] - - Calculating difference with periods=-1: - - >>> df.diff(periods=-1) - A B - 0 2 -1 - 1 -1 -1 - 2 - - [3 rows x 2 columns] - Args: periods (int, default 1): Periods to shift for calculating difference, accepts negative values. Returns: - bigframes.pandas.DataFrame: First differences of the Series. + bigframes.dataframe.DataFrame: First differences of the Series. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) def agg(self, func): """ - Aggregate using one or more operations over columns. - - **Examples:** - - - >>> df = bpd.DataFrame({"A": [3, 1, 2], "B": [1, 2, 3]}) - >>> df - A B - 0 3 1 - 1 1 2 - 2 2 3 - - [3 rows x 2 columns] - - Using a single function: - - >>> df.agg('sum') - A 6 - B 6 - dtype: Int64 - - Using a list of functions: - - >>> df.agg(['sum', 'mean']) - A B - sum 6.0 6.0 - mean 2.0 2.0 - - [2 rows x 2 columns] + Aggregate using one or more operations over the specified axis. Args: func (function): @@ -6256,11 +2710,11 @@ def agg(self, func): function names, e.g. ``['sum', 'mean']``. Returns: - bigframes.pandas.DataFrame or bigframes.pandas.Series: Aggregated results. + DataFrame or bigframes.series.Series: Aggregated results. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - def describe(self, include: None | Literal["all"] = None): + def describe(self): """ Generate descriptive statistics. @@ -6268,10 +2722,7 @@ def describe(self, include: None | Literal["all"] = None): tendency, dispersion and shape of a dataset's distribution, excluding ``NaN`` values. - 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. + Only supports numeric columns. .. note:: Percentile values are approximates only. @@ -6283,54 +2734,8 @@ def describe(self, include: None | Literal["all"] = None): upper percentile is ``75``. The ``50`` percentile is the same as the median. - **Examples:** - - >>> import bigframes.pandas as bpd - >>> df = bpd.DataFrame({"A": [3, 1, 2], "B": [0, 2, 8], "C": ["cat", "cat", "dog"]}) - >>> df - A B C - 0 3 0 cat - 1 1 2 cat - 2 2 8 dog - - [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 - - [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. - - Raises: - ValueError: - If unsupported ``include`` type is provided. + bigframes.dataframe.DataFrame: Summary statistics of the Series or Dataframe provided. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @@ -6353,50 +2758,6 @@ def pivot(self, *, columns, index=None, values=None): do not together uniquely identify input rows, the output will be silently non-deterministic. - **Examples:** - - - >>> df = bpd.DataFrame({ - ... "foo": ["one", "one", "one", "two", "two"], - ... "bar": ["A", "B", "C", "A", "B"], - ... "baz": [1, 2, 3, 4, 5], - ... "zoo": ['x', 'y', 'z', 'q', 'w'] - ... }) - - >>> df - foo bar baz zoo - 0 one A 1 x - 1 one B 2 y - 2 one C 3 z - 3 two A 4 q - 4 two B 5 w - - [5 rows x 4 columns] - - Using `pivot` without optional arguments: - - >>> df.pivot(columns='foo') - bar baz zoo - foo one two one two one two - 0 A 1 x - 1 B 2 y - 2 C 3 z - 3 A 4 q - 4 B 5 w - - [5 rows x 6 columns] - - Using `pivot` with `index` and `values`: - - >>> df.pivot(columns='foo', index='bar', values='baz') - foo one two - bar - A 1 4 - B 2 5 - C 3 - - [3 rows x 2 columns] - Args: columns (str or object or a list of str): Column to use to make new frame's columns. @@ -6410,95 +2771,11 @@ def pivot(self, *, columns, index=None, values=None): have hierarchically indexed columns. Returns: - bigframes.pandas.DataFrame: Returns reshaped DataFrame. - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - def pivot_table(self, values=None, index=None, columns=None, aggfunc="mean"): - """ - Create a spreadsheet-style pivot table as a DataFrame. - - The levels in the pivot table will be stored in MultiIndex objects (hierarchical indexes) - on the index and columns of the result DataFrame. - - **Examples:** - - >>> import bigframes.pandas as bpd - >>> df = bpd.DataFrame({ - ... 'Product': ['Product A', 'Product B', 'Product A', 'Product B', 'Product A', 'Product B'], - ... 'Region': ['East', 'West', 'East', 'West', 'West', 'East'], - ... 'Sales': [100, 200, 150, 100, 200, 150], - ... 'Rating': [3, 5, 4, 3, 3, 5] - ... }) - >>> df - Product Region Sales Rating - 0 Product A East 100 3 - 1 Product B West 200 5 - 2 Product A East 150 4 - 3 Product B West 100 3 - 4 Product A West 200 3 - 5 Product B East 150 5 - - [6 rows x 4 columns] - - Using `pivot_table` with default aggfunc "mean": - - >>> pivot_table = df.pivot_table( - ... values=['Sales', 'Rating'], - ... index='Product', - ... columns='Region' - ... ) - >>> pivot_table - Rating Sales - Region East West East West - Product - Product A 3.5 3.0 125.0 200.0 - Product B 5.0 4.0 150.0 150.0 - - [2 rows x 4 columns] - - Using `pivot_table` with specified aggfunc "max": - - >>> pivot_table = df.pivot_table( - ... values=['Sales', 'Rating'], - ... index='Product', - ... columns='Region', - ... aggfunc="max" - ... ) - >>> pivot_table - Rating Sales - Region East West East West - Product - Product A 4 3 150 200 - Product B 5 5 150 200 - - [2 rows x 4 columns] - - Args: - values (str, object or a list of the previous, optional): - Column(s) to use for populating new frame's values. If not - specified, all remaining columns will be used and the result will - have hierarchically indexed columns. - - index (str or object or a list of str, optional): - Column to use to make new frame's index. If not given, uses existing index. - - columns (str or object or a list of str): - Column to use to make new frame's columns. - - aggfunc (str, default "mean"): - Aggregation function name to compute summary statistics (e.g., 'sum', 'mean'). - - fill_value (scalar, default None): - Value to replace missing values with (in the resulting pivot table, after - aggregation). - - Returns: - bigframes.pandas.DataFrame: An Excel style pivot table. + Returns reshaped DataFrame. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - def stack(self, level=-1): + def stack(self): """ Stack the prescribed level(s) from columns to index. @@ -6516,34 +2793,12 @@ def stack(self, level=-1): BigQuery DataFrames does not support stack operations that would combine columns of different dtypes. - **Examples:** - - - >>> df = bpd.DataFrame({'A': [1, 3], 'B': [2, 4]}, index=['foo', 'bar']) - >>> df - A B - foo 1 2 - bar 3 4 - - [2 rows x 2 columns] - - >>> df.stack() - foo A 1 - B 2 - bar A 3 - B 4 - dtype: Int64 - - Args: - level (int, str, or list of these, default -1 (last level)): - Level(s) to stack from the column axis onto the index axis. - Returns: - bigframes.pandas.DataFrame or bigframes.pandas.Series: Stacked dataframe or series. + DataFrame or Series: Stacked dataframe or series. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - def unstack(self, level=-1): + def unstack(self): """ Pivot a level of the (necessarily hierarchical) index labels. @@ -6553,30 +2808,8 @@ def unstack(self, level=-1): If the index is not a MultiIndex, the output will be a Series (the analogue of stack when the columns are not a MultiIndex). - **Examples:** - - - >>> df = bpd.DataFrame({'A': [1, 3], 'B': [2, 4]}, index=['foo', 'bar']) - >>> df - A B - foo 1 2 - bar 3 4 - - [2 rows x 2 columns] - - >>> df.unstack() - A foo 1 - bar 3 - B foo 2 - bar 4 - dtype: Int64 - - Args: - level (int, str, or list of these, default -1 (last level)): - Level(s) of index to unstack, can pass level name. - Returns: - bigframes.pandas.DataFrame or bigframes.pandas.Series: DataFrame or Series. + DataFrame or Series """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @@ -6592,90 +2825,14 @@ def index(self): index is used for label-based access and alignment, and can be accessed or modified using this attribute. - **Examples:** - - - You can access the index of a DataFrame via ``index`` property. - - >>> df = bpd.DataFrame({'Name': ['Alice', 'Bob', 'Aritra'], - ... 'Age': [25, 30, 35], - ... 'Location': ['Seattle', 'New York', 'Kona']}, - ... index=([10, 20, 30])) - >>> df - Name Age Location - 10 Alice 25 Seattle - 20 Bob 30 New York - 30 Aritra 35 Kona - - [3 rows x 3 columns] - >>> df.index # doctest: +ELLIPSIS - Index([10, 20, 30], dtype='Int64') - >>> df.index.values - array([10, 20, 30]) - - Let's try setting a new index for the dataframe and see that reflect via - ``index`` property. - - >>> df1 = df.set_index(["Name", "Location"]) - >>> df1 - Age - Name Location - Alice Seattle 25 - Bob New York 30 - Aritra Kona 35 - - [3 rows x 1 columns] - >>> df1.index # doctest: +ELLIPSIS - MultiIndex([( 'Alice', 'Seattle'), - ( 'Bob', 'New York'), - ('Aritra', 'Kona')], - names=['Name', 'Location']) - >>> df1.index.values - array([('Alice', 'Seattle'), ('Bob', 'New York'), ('Aritra', 'Kona')], - dtype=object) - Returns: - Index: The index object of the DataFrame. + The index labels of the DataFrame. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @property def columns(self): - """The column labels of the DataFrame. - - **Examples:** - - - You can access the column labels of a DataFrame via ``columns`` property. - - >>> df = bpd.DataFrame({'Name': ['Alice', 'Bob', 'Aritra'], - ... 'Age': [25, 30, 35], - ... 'Location': ['Seattle', 'New York', 'Kona']}, - ... index=([10, 20, 30])) - >>> df - Name Age Location - 10 Alice 25 Seattle - 20 Bob 30 New York - 30 Aritra 35 Kona - - [3 rows x 3 columns] - >>> df.columns - Index(['Name', 'Age', 'Location'], dtype='str') - - You can also set new labels for columns. - - >>> df.columns = ["NewName", "NewAge", "NewLocation"] - >>> df - NewName NewAge NewLocation - 10 Alice 25 Seattle - 20 Bob 30 New York - 30 Aritra 35 Kona - - [3 rows x 3 columns] - >>> df.columns - Index(['NewName', 'NewAge', 'NewLocation'], dtype='str') - - """ + "The column labels of the DataFrame." raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) def value_counts( @@ -6689,60 +2846,6 @@ def value_counts( """ Return a Series containing counts of unique rows in the DataFrame. - **Examples:** - - - >>> df = bpd.DataFrame({'num_legs': [2, 4, 4, 6, 7], - ... 'num_wings': [2, 0, 0, 0, pd.NA]}, - ... index=['falcon', 'dog', 'cat', 'ant', 'octopus'], - ... dtype='Int64') - >>> df - num_legs num_wings - falcon 2 2 - dog 4 0 - cat 4 0 - ant 6 0 - octopus 7 - - [5 rows x 2 columns] - - ``value_counts`` sorts the result by counts in a descending order by default: - - >>> df.value_counts() - num_legs num_wings - 4 0 2 - 2 2 1 - 6 0 1 - Name: count, dtype: Int64 - - You can normalize the counts to return relative frequencies by setting ``normalize=True``: - - >>> df.value_counts(normalize=True) - num_legs num_wings - 4 0 0.5 - 2 2 0.25 - 6 0 0.25 - Name: proportion, dtype: Float64 - - You can get the rows in the ascending order of the counts by setting ``ascending=True``: - - >>> df.value_counts(ascending=True) - num_legs num_wings - 2 2 1 - 6 0 1 - 4 0 2 - Name: count, dtype: Int64 - - You can include the counts of the rows with ``NA`` values by setting ``dropna=False``: - - >>> df.value_counts(dropna=False) - num_legs num_wings - 4 0 2 - 2 2 1 - 6 0 1 - 7 1 - Name: count, dtype: Int64 - Args: subset (label or list of labels, optional): Columns to use when counting unique combinations. @@ -6756,266 +2859,50 @@ def value_counts( Don’t include counts of rows that contain NA values. Returns: - bigframes.pandas.Series: Series containing counts of unique rows in the DataFrame - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - def eval(self, expr: str) -> DataFrame: - """ - Evaluate a string describing operations on DataFrame columns. - - Operates on columns only, not specific rows or elements. This allows - `eval` to run arbitrary code, which can make you vulnerable to code - injection if you pass user input to this function. - - **Examples:** - - - >>> df = bpd.DataFrame({'A': range(1, 6), 'B': range(10, 0, -2)}) - >>> df - A B - 0 1 10 - 1 2 8 - 2 3 6 - 3 4 4 - 4 5 2 - - [5 rows x 2 columns] - >>> df.eval('A + B') - 0 11 - 1 10 - 2 9 - 3 8 - 4 7 - dtype: Int64 - - Assignment is allowed though by default the original DataFrame is not - modified. - - >>> df.eval('C = A + B') - A B C - 0 1 10 11 - 1 2 8 10 - 2 3 6 9 - 3 4 4 8 - 4 5 2 7 - - [5 rows x 3 columns] - >>> df - A B - 0 1 10 - 1 2 8 - 2 3 6 - 3 4 4 - 4 5 2 - - [5 rows x 2 columns] - - Multiple columns can be assigned to using multi-line expressions: - - >>> df.eval( - ... ''' - ... C = A + B - ... D = A - B - ... ''' - ... ) - A B C D - 0 1 10 11 -9 - 1 2 8 10 -6 - 2 3 6 9 -3 - 3 4 4 8 0 - 4 5 2 7 3 - - [5 rows x 4 columns] - - - Args: - expr (str): - The expression string to evaluate. - - Returns: - bigframes.pandas.DataFrame: DataFrame result after the operation. + Series: Series containing counts of unique rows in the DataFrame """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - def query(self, expr: str) -> DataFrame | None: + def interpolate(self, method: str = "linear"): """ - Query the columns of a DataFrame with a boolean expression. - - **Examples:** - - - >>> df = bpd.DataFrame({'A': range(1, 6), - ... 'B': range(10, 0, -2), - ... 'C C': range(10, 5, -1)}) - >>> df - A B C C - 0 1 10 10 - 1 2 8 9 - 2 3 6 8 - 3 4 4 7 - 4 5 2 6 - - [5 rows x 3 columns] - >>> df.query('A > B') - A B C C - 4 5 2 6 - - [1 rows x 3 columns] - - The previous expression is equivalent to - - >>> df[df.A > df.B] - A B C C - 4 5 2 6 - - [1 rows x 3 columns] - - For columns with spaces in their name, you can use backtick quoting. - - >>> df.query('B == `C C`') - A B C C - 0 1 10 10 - - [1 rows x 3 columns] - - The previous expression is equivalent to - - >>> df[df.B == df['C C']] - A B C C - 0 1 10 10 - - [1 rows x 3 columns] + Fill NaN values using an interpolation method. Args: - expr (str): - The query string to evaluate. - - You can refer to variables - in the environment by prefixing them with an '@' character like - ``@a + b``. - - You can refer to column names that are not valid Python variable names - by surrounding them in backticks. Thus, column names containing spaces - or punctuations (besides underscores) or starting with digits must be - surrounded by backticks. (For example, a column named "Area (cm^2)" would - be referenced as ```Area (cm^2)```). Column names which are Python keywords - (like "list", "for", "import", etc) cannot be used. - - For example, if one of your columns is called ``a a`` and you want - to sum it with ``b``, your query should be ```a a` + b``. + method (str, default 'linear'): + Interpolation technique to use. Only 'linear' supported. + 'linear': Ignore the index and treat the values as equally spaced. + This is the only method supported on MultiIndexes. Returns: - None or bigframes.pandas.DataFrame: - DataFrame result after the query operation, otherwise None. - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - def interpolate(self, method: str = "linear"): - """ - Fill NA (NULL in BigQuery) values using an interpolation method. + DataFrame: + Returns the same object type as the caller, interpolated at + some or all ``NaN`` values **Examples:** + >>> import bigframes.pandas as bpd + >>> bpd.options.display.progress_bar = None >>> df = bpd.DataFrame({ ... 'A': [1, 2, 3, None, None, 6], ... 'B': [None, 6, None, 2, None, 3], - ... }, index=[0, 0.1, 0.3, 0.7, 0.9, 1.0]) + ... }) >>> df.interpolate() - A B - 0.0 1.0 - 0.1 2.0 6.0 - 0.3 3.0 4.0 - 0.7 4.0 2.0 - 0.9 5.0 2.5 - 1.0 6.0 3.0 - - [6 rows x 2 columns] - >>> df.interpolate(method="values") - A B - 0.0 1.0 - 0.1 2.0 6.0 - 0.3 3.0 4.666667 - 0.7 4.714286 2.0 - 0.9 5.571429 2.666667 - 1.0 6.0 3.0 + A B + 0 1.0 + 1 2.0 6.0 + 2 3.0 4.0 + 3 4.0 2.0 + 4 5.0 2.5 + 5 6.0 3.0 [6 rows x 2 columns] - - Args: - method (str, default 'linear'): - Interpolation technique to use. Only 'linear' supported. - 'linear': Ignore the index and treat the values as equally spaced. - This is the only method supported on MultiIndexes. - 'index', 'values': use the actual numerical values of the index. - 'pad': Fill in NaNs using existing values. - 'nearest', 'zero', 'slinear': Emulates `scipy.interpolate.interp1d` - - Returns: - bigframes.pandas.DataFrame: - Returns the same object type as the caller, interpolated at - some or all ``NaN`` values """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) def fillna(self, value): """ - Fill NA (NULL in BigQuery) values using the specified method. - - Note that empty strings ``''``, :attr:`numpy.inf`, and - :attr:`numpy.nan` are ***not*** considered NA values. This NA/NULL - logic differs from numpy, but it is the same as BigQuery and the - :class:`pandas.ArrowDtype`. - - **Examples:** - - >>> df = bpd.DataFrame( - ... [ - ... pa.array([np.nan, 2, None, 0], type=pa.float64()), - ... pa.array([3, np.nan, None, 1], type=pa.float64()), - ... pa.array([None, None, np.nan, None], type=pa.float64()), - ... pa.array([4, 5, None, np.nan], type=pa.float64()), - ... ], columns=list("ABCD"), dtype=pd.ArrowDtype(pa.float64())) - >>> df - A B C D - 0 NaN 2.0 0.0 - 1 3.0 NaN 1.0 - 2 NaN - 3 4.0 5.0 NaN - - [4 rows x 4 columns] - - Replace all NA (NULL) elements with 0s. - - >>> df.fillna(0) - A B C D - 0 NaN 2.0 0.0 0.0 - 1 3.0 NaN 0.0 1.0 - 2 0.0 0.0 NaN 0.0 - 3 4.0 5.0 0.0 NaN - - [4 rows x 4 columns] - - You can use fill values from another DataFrame: - - >>> df_fill = bpd.DataFrame(np.arange(12).reshape(3, 4), - ... columns=['A', 'B', 'C', 'D']) - >>> df_fill - A B C D - 0 0 1 2 3 - 1 4 5 6 7 - 2 8 9 10 11 - - [3 rows x 4 columns] - >>> df.fillna(df_fill) - A B C D - 0 NaN 2.0 2.0 0.0 - 1 3.0 NaN 6.0 1.0 - 2 8.0 9.0 NaN 11.0 - 3 4.0 5.0 NaN - - [4 rows x 4 columns] + Fill NA/NaN values using the specified method. Args: value (scalar, Series): @@ -7026,177 +2913,23 @@ def fillna(self, value): be a list. Returns: - bigframes.pandas.DataFrame: Object with missing values filled - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - def replace( - self, - to_replace, - value=None, - *, - regex=False, - ): - """ - Replace values given in `to_replace` with `value`. - - Values of the Series/DataFrame are replaced with other values dynamically. - This differs from updating with ``.loc`` or ``.iloc``, which require - you to specify a location to update with some value. - - **Examples:** - - >>> import bigframes.pandas as bpd - >>> df = bpd.DataFrame({ - ... 'int_col': [1, 1, 2, 3], - ... 'string_col': ["a", "b", "c", "b"], - ... }) - - Using scalar `to_replace` and `value`: - - >>> df.replace("b", "e") - int_col string_col - 0 1 a - 1 1 e - 2 2 c - 3 3 e - - [4 rows x 2 columns] - - Using dictionary: - - >>> df.replace({"a": "e", 2: 5}) - int_col string_col - 0 1 e - 1 1 b - 2 5 c - 3 3 b - - [4 rows x 2 columns] - - Using regex: - - >>> df.replace("[ab]", "e", regex=True) - int_col string_col - 0 1 e - 1 1 e - 2 2 c - 3 3 e - - [4 rows x 2 columns] - - - Args: - to_replace (str, regex, list, int, float or None): - How to find the values that will be replaced. - numeric: numeric values equal to `to_replace` will be replaced with `value` - str: string exactly matching `to_replace` will be replaced with `value` - regex: regexs matching `to_replace` will be replaced with`value` - list of str, regex, or numeric: - First, if `to_replace` and `value` are both lists, they **must** be the same length. - Second, if ``regex=True`` then all of the strings in **both** - lists will be interpreted as regexs otherwise they will match - directly. This doesn't matter much for `value` since there - are only a few possible substitution regexes you can use. - str, regex and numeric rules apply as above. - - value (scalar, default None): - Value to replace any values matching `to_replace` with. - For a DataFrame a dict of values can be used to specify which - value to use for each column (columns not in the dict will not be - filled). Regular expressions, strings and lists or dicts of such - objects are also allowed. - regex (bool, default False): - Whether to interpret `to_replace` and/or `value` as regular - expressions. If this is ``True`` then `to_replace` *must* be a - string. - - Returns: - bigframes.pandas.DataFrame or bigframes.pandas.Series: - Object after replacement. + DataFrame: Object with missing values filled """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @property def iloc(self): - """Purely integer-location based indexing for selection by position. - - Returns: - bigframes.core.indexers.ILocDataFrameIndexer: Purely integer-location Indexers. - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - @property - def loc(self): - """Access a group of rows and columns by label(s) or a boolean array. - - Returns: - bigframes.core.indexers.ILocDataFrameIndexer: Indexers object. - """ + """Purely integer-location based indexing for selection by position.""" raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @property def iat(self): - """Access a single value for a row/column pair by integer position. - - **Examples:** - - - >>> df = bpd.DataFrame([[0, 2, 3], [0, 4, 1], [10, 20, 30]], - ... columns=['A', 'B', 'C']) - >>> df - A B C - 0 0 2 3 - 1 0 4 1 - 2 10 20 30 - - [3 rows x 3 columns] - - Get value at specified row/column pair - - >>> df.iat[1, 2] - np.int64(1) - - Get value within a series - - >>> df.loc[0].iat[1] - np.int64(2) - - Returns: - bigframes.core.indexers.IatDataFrameIndexer: Indexers object. - """ + """Access a single value for a row/column pair by integer position.""" raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @property def at(self): - """Access a single value for a row/column label pair. - - **Examples:** - - - >>> df = bpd.DataFrame([[0, 2, 3], [0, 4, 1], [10, 20, 30]], - ... index=[4, 5, 6], columns=['A', 'B', 'C']) - >>> df - A B C - 4 0 2 3 - 5 0 4 1 - 6 10 20 30 - - [3 rows x 3 columns] - - Get value at specified row/column pair - - >>> df.at[4, 'B'] - np.int64(2) - - Get value within a series - - >>> df.loc[5].at['B'] - np.int64(4) - - Returns: - bigframes.core.indexers.AtDataFrameIndexer: Indexers object. - """ + """Access a single value for a row/column label pair.""" raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) def dot(self, other): @@ -7214,371 +2947,17 @@ def dot(self, other): DataFrame and the index of other must contain the same values, as they will be aligned prior to the multiplication. - .. note:: The dot method for Series computes the inner product, instead of the matrix product here. - **Examples:** - - - >>> left = bpd.DataFrame([[0, 1, -2, -1], [1, 1, 1, 1]]) - >>> left - 0 1 2 3 - 0 0 1 -2 -1 - 1 1 1 1 1 - - [2 rows x 4 columns] - >>> right = bpd.DataFrame([[0, 1], [1, 2], [-1, -1], [2, 0]]) - >>> right - 0 1 - 0 0 1 - 1 1 2 - 2 -1 -1 - 3 2 0 - - [4 rows x 2 columns] - >>> left.dot(right) - 0 1 - 0 1 4 - 1 2 2 - - [2 rows x 2 columns] - - You can also use the operator ``@`` for the dot product: - - >>> left @ right - 0 1 - 0 1 4 - 1 2 2 - - [2 rows x 2 columns] - - The right input can be a Series, in which case the result will also be a - Series: - - >>> right = bpd.Series([1, 2, -1,0]) - >>> left @ right - 0 4 - 1 2 - dtype: Int64 - - Any user defined index of the left matrix and columns of the right - matrix will reflect in the result. - - >>> left = bpd.DataFrame([[1, 2, 3], [2, 5, 7]], index=["alpha", "beta"]) - >>> left - 0 1 2 - alpha 1 2 3 - beta 2 5 7 - - [2 rows x 3 columns] - >>> right = bpd.DataFrame([[2, 4, 8], [1, 5, 10], [3, 6, 9]], columns=["red", "green", "blue"]) - >>> right - red green blue - 0 2 4 8 - 1 1 5 10 - 2 3 6 9 - - [3 rows x 3 columns] - >>> left.dot(right) - red green blue - alpha 13 32 55 - beta 30 75 129 - - [2 rows x 3 columns] - Args: other (Series or DataFrame): The other object to compute the matrix product with. Returns: - bigframes.pandas.DataFrame or bigframes.pandas.Series: + Series or DataFrame If `other` is a Series, return the matrix product between self and other as a Series. If other is a DataFrame, return the matrix product of self and other in a DataFrame. - - Raises: - RuntimeError: - If unable to construct all columns. - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - def __matmul__(self, other): - """ - Compute the matrix multiplication between the DataFrame and other, using - operator `@`. - - Equivalent to `DataFrame.dot(other)`. - - **Examples:** - - - >>> left = bpd.DataFrame([[0, 1, -2, -1], [1, 1, 1, 1]]) - >>> left - 0 1 2 3 - 0 0 1 -2 -1 - 1 1 1 1 1 - - [2 rows x 4 columns] - >>> right = bpd.DataFrame([[0, 1], [1, 2], [-1, -1], [2, 0]]) - >>> right - 0 1 - 0 0 1 - 1 1 2 - 2 -1 -1 - 3 2 0 - - [4 rows x 2 columns] - >>> left @ right - 0 1 - 0 1 4 - 1 2 2 - - [2 rows x 2 columns] - - The operand can be a Series, in which case the result will also be a - Series: - - >>> right = bpd.Series([1, 2, -1,0]) - >>> left @ right - 0 4 - 1 2 - dtype: Int64 - - Args: - other (DataFrame or Series): - Object to be matrix multiplied with the DataFrame. - - Returns: - DataFrame or Series: The result of the matrix multiplication. - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - @property - def plot(self): - """ - Make plots of Dataframes. - - Returns: - bigframes.pandas.api.typing.PlotAccessor: - An accessor making plots. - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - def __len__(self): - """Returns number of rows in the DataFrame, serves `len` operator. - - **Examples:** - - - >>> df = bpd.DataFrame({ - ... 'a': [0, 1, 2], - ... 'b': [3, 4, 5] - ... }) - >>> len(df) - 3 - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - def __array__(self, dtype=None, copy: Optional[bool] = None): - """ - Returns the rows as NumPy array. - - Equivalent to `DataFrame.to_numpy(dtype)`. - - Users should not call this directly. Rather, it is invoked by - `numpy.array` and `numpy.asarray`. - - **Examples:** - - - >>> df = bpd.DataFrame({"a": [1, 2, 3], "b": [11, 22, 33]}) - - >>> np.array(df) - array([[1, 11], - [2, 22], - [3, 33]], dtype=object) - - >>> np.asarray(df) - array([[1, 11], - [2, 22], - [3, 33]], dtype=object) - - Args: - dtype (str or numpy.dtype, optional): - The dtype to use for the resulting NumPy array. By default, - the dtype is inferred from the data. - copy (bool or None, optional): - Whether to copy the data, False is not supported. - - Returns: - numpy.ndarray: - The rows in the DataFrame converted to a `numpy.ndarray` with - the specified dtype. - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - def __getitem__(self, key): - """Gets the specified column(s) from the DataFrame. - - **Examples:** - - - >>> df = bpd.DataFrame({ - ... "name" : ["alpha", "beta", "gamma"], - ... "age": [20, 30, 40], - ... "location": ["WA", "NY", "CA"] - ... }) - >>> df - name age location - 0 alpha 20 WA - 1 beta 30 NY - 2 gamma 40 CA - - [3 rows x 3 columns] - - You can specify a column label to retrieve the corresponding Series. - - >>> df["name"] - 0 alpha - 1 beta - 2 gamma - Name: name, dtype: string - - You can specify a list of column labels to retrieve a Dataframe. - - >>> df[["name", "age"]] - name age - 0 alpha 20 - 1 beta 30 - 2 gamma 40 - - [3 rows x 2 columns] - - You can specify a condition as a series of booleans to retrieve matching - rows. - - >>> df[df["age"] > 25] - name age location - 1 beta 30 NY - 2 gamma 40 CA - - [2 rows x 3 columns] - - You can specify a pandas Index with desired column labels. - - >>> df[pd.Index(["age", "location"])] - age location - 0 20 WA - 1 30 NY - 2 40 CA - - [3 rows x 2 columns] - - Args: - key (index): - Index or list of indices. It can be a column label, a list of - column labels, a Series of booleans or a pandas Index of desired - column labels - - Returns: - bigframes.pandas.Series or Any: Value(s) at the requested index(es). - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - def __setitem__(self, key, value): - """Modify or insert a column into the DataFrame. - - .. note:: - This does **not** modify the original table the DataFrame was - derived from. - - **Examples:** - - - >>> df = bpd.DataFrame({ - ... "name" : ["alpha", "beta", "gamma"], - ... "age": [20, 30, 40], - ... "location": ["WA", "NY", "CA"] - ... }) - >>> df - name age location - 0 alpha 20 WA - 1 beta 30 NY - 2 gamma 40 CA - - [3 rows x 3 columns] - - You can add assign a constant to a new column. - - >>> df["country"] = "USA" - >>> df - name age location country - 0 alpha 20 WA USA - 1 beta 30 NY USA - 2 gamma 40 CA USA - - [3 rows x 4 columns] - - You can assign a Series to a new column. - - >>> df["new_age"] = df["age"] + 5 - >>> df - name age location country new_age - 0 alpha 20 WA USA 25 - 1 beta 30 NY USA 35 - 2 gamma 40 CA USA 45 - - [3 rows x 5 columns] - - You can assign a Series to an existing column. - - >>> df["new_age"] = bpd.Series([29, 39, 19], index=[1, 2, 0]) - >>> df - name age location country new_age - 0 alpha 20 WA USA 19 - 1 beta 30 NY USA 29 - 2 gamma 40 CA USA 39 - - [3 rows x 5 columns] - - You can assign a scalar to multiple columns. - - >>> df[["age", "new_age"]] = 25 - >>> df - name age location country new_age - 0 alpha 25 WA USA 25 - 1 beta 25 NY USA 25 - 2 gamma 25 CA USA 25 - - [3 rows x 5 columns] - - You can use a sequence of scalars for assignment of multiple columns: - - >>> df[["age", "is_happy"]] = [20, True] - >>> df - name age location country new_age is_happy - 0 alpha 20 WA USA 25 True - 1 beta 20 NY USA 25 True - 2 gamma 20 CA USA 25 True - - [3 rows x 6 columns] - - You can use a dataframe for assignment of multiple columns: - >>> df[["age", "new_age"]] = df[["new_age", "age"]] - >>> df - name age location country new_age is_happy - 0 alpha 25 WA USA 20 True - 1 beta 25 NY USA 20 True - 2 gamma 25 CA USA 20 True - - [3 rows x 6 columns] - - Args: - key (column index): - It can be a new column to be inserted, or an existing column to - be modified. - value (scalar, Sequence, DataFrame, or Series): - Value to be assigned to the column """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) diff --git a/third_party/bigframes_vendored/pandas/core/generic.py b/third_party/bigframes_vendored/pandas/core/generic.py index 0e4ac335c8a..127efe6a3d6 100644 --- a/third_party/bigframes_vendored/pandas/core/generic.py +++ b/third_party/bigframes_vendored/pandas/core/generic.py @@ -1,13 +1,10 @@ # Contains code from https://github.com/pandas-dev/pandas/blob/main/pandas/core/generic.py from __future__ import annotations -from typing import TYPE_CHECKING, Callable, Iterator, Literal, Optional +from typing import Iterator, Literal, Optional -import bigframes_vendored.constants as constants -from bigframes_vendored.pandas.core import indexing - -if TYPE_CHECKING: - from bigframes_vendored.pandas.pandas._typing import T +from bigframes import constants +from third_party.bigframes_vendored.pandas.core import indexing class NDFrame(indexing.IndexingMixin): @@ -16,9 +13,6 @@ class NDFrame(indexing.IndexingMixin): size-mutable, labeled data structure """ - # Explicitly mark the class as unhashable - __hash__ = None # type: ignore - # ---------------------------------------------------------------------- # Axis @@ -35,17 +29,6 @@ def ndim(self) -> int: def size(self) -> int: """Return an int representing the number of elements in this object. - **Examples:** - - - >>> s = bpd.Series({'a': 1, 'b': 2, 'c': 3}) - >>> s.size - 3 - - >>> df = bpd.DataFrame({'col1': [1, 2], 'col2': [3, 4]}) - >>> df.size - 4 - Returns: int: Return the number of rows if Series. Otherwise return the number of rows times number of columns if DataFrame. @@ -54,14 +37,14 @@ def size(self) -> int: def __iter__(self) -> Iterator: """ - Iterate over column axis for DataFrame, or values for Series. + Iterate over info axis. - Returns: - Iterator: - Iterator of DataFrame or Series values. + Returns + iterator: Info axis as iterator. **Examples:** - + >>> import bigframes.pandas as bpd + >>> bpd.options.display.progress_bar = None >>> df = bpd.DataFrame({ ... 'A': [1, 2, 3], @@ -75,9 +58,9 @@ def __iter__(self) -> Iterator: >>> series = bpd.Series(["a", "b", "c"], index=[10, 20, 30]) >>> for x in series: ... print(x) - a - b - c + 10 + 20 + 30 """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @@ -90,8 +73,8 @@ def abs(self): This function only applies to elements that are all numeric. Returns: - bigframes.pandas.DataFrame or bigframes.pandas.Series: - A Series or DataFrame containing the absolute value of each element. + Series/DataFrame containing the absolute value of each element. + Returns a Series/DataFrame containing the absolute value of each element. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @@ -99,90 +82,18 @@ def astype(self, dtype): """ Cast a pandas object to a specified dtype ``dtype``. - **Examples:** - - Create a DataFrame: - - >>> d = {'col1': [1, 2], 'col2': [3, 4]} - >>> df = bpd.DataFrame(data=d) - >>> df.dtypes - col1 Int64 - col2 Int64 - dtype: object - - Cast all columns to ``Float64``: - - >>> df.astype('Float64').dtypes - col1 Float64 - col2 Float64 - dtype: object - - Create a series of type ``Int64``: - - >>> ser = bpd.Series([2023010000246789, 1624123244123101, 1054834234120101], dtype='Int64') - >>> ser - 0 2023010000246789 - 1 1624123244123101 - 2 1054834234120101 - dtype: Int64 - - Convert to ``Float64`` type: - - >>> ser.astype('Float64') - 0 2023010000246789.0 - 1 1624123244123101.0 - 2 1054834234120101.0 - dtype: Float64 - - Convert to ``pd.ArrowDtype(pa.timestamp("us", tz="UTC"))`` type: - - >>> ser.astype("timestamp[us, tz=UTC][pyarrow]") - 0 2034-02-08 11:13:20.246789+00:00 - 1 2021-06-19 17:20:44.123101+00:00 - 2 2003-06-05 17:30:34.120101+00:00 - dtype: timestamp[us, tz=UTC][pyarrow] - - Note that this is equivalent of using ``to_datetime`` with ``unit='us'``: - - >>> bpd.to_datetime(ser, unit='us', utc=True) # doctest: +SKIP - 0 2034-02-08 11:13:20.246789+00:00 - 1 2021-06-19 17:20:44.123101+00:00 - 2 2003-06-05 17:30:34.120101+00:00 - dtype: timestamp[us, tz=UTC][pyarrow] - - Convert ``pd.ArrowDtype(pa.timestamp("us", tz="UTC"))`` type to ``Int64`` type: - - >>> timestamp_ser = ser.astype("timestamp[us, tz=UTC][pyarrow]") - >>> timestamp_ser.astype('Int64') - 0 2023010000246789 - 1 1624123244123101 - 2 1054834234120101 - dtype: Int64 - Args: - dtype (str, data type or pandas.ExtensionDtype): - A dtype supported by BigQuery DataFrame include ``'boolean'``, - ``'Float64'``, ``'Int64'``, ``'int64\\[pyarrow\\]'``, - ``'string'``, ``'string\\[pyarrow\\]'``, - ``'timestamp\\[us, tz=UTC\\]\\[pyarrow\\]'``, - ``'timestamp\\[us\\]\\[pyarrow\\]'``, - ``'date32\\[day\\]\\[pyarrow\\]'``, - ``'time64\\[us\\]\\[pyarrow\\]'``. - A pandas.ExtensionDtype include ``pandas.BooleanDtype()``, - ``pandas.Float64Dtype()``, ``pandas.Int64Dtype()``, - ``pandas.StringDtype(storage="pyarrow")``, - ``pd.ArrowDtype(pa.date32())``, - ``pd.ArrowDtype(pa.time64("us"))``, - ``pd.ArrowDtype(pa.timestamp("us"))``, - ``pd.ArrowDtype(pa.timestamp("us", tz="UTC"))``. - errors ({'raise', 'null'}, default 'raise'): - Control raising of exceptions on invalid data for provided dtype. - If 'raise', allow exceptions to be raised if any value fails cast - If 'null', will assign null value if value fails cast + dtype (str or pandas.ExtensionDtype): + A dtype supported by BigQuery DataFrame include 'boolean','Float64','Int64', + 'string', 'tring[pyarrow]','timestamp[us, tz=UTC][pyarrow]', + 'timestamp[us][pyarrow]','date32[day][pyarrow]','time64[us][pyarrow]' + A pandas.ExtensionDtype include pandas.BooleanDtype(), pandas.Float64Dtype(), + pandas.Int64Dtype(), pandas.StringDtype(storage="pyarrow"), + pd.ArrowDtype(pa.date32()), pd.ArrowDtype(pa.time64("us")), + pd.ArrowDtype(pa.timestamp("us")), pd.ArrowDtype(pa.timestamp("us", tz="UTC")). Returns: - bigframes.pandas.DataFrame: - A BigQuery DataFrame. + same type as caller """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @@ -211,15 +122,14 @@ def empty(self) -> bool: def to_json( self, - path_or_buf, - orient: Optional[ - Literal["split", "records", "index", "columns", "values", "table"] - ] = None, + path_or_buf: str, + orient: Literal[ + "split", "records", "index", "columns", "values", "table" + ] = "columns", *, index: bool = True, lines: bool = False, - allow_large_results: Optional[bool] = None, - ) -> Optional[str]: + ) -> str | None: """Convert the object to a JSON string, written to Cloud Storage. Note NaN's and None will be converted to null and datetime objects @@ -229,18 +139,16 @@ def to_json( Only ``orient='records'`` and ``lines=True`` is supported so far. Args: - path_or_buf (str, path object, file-like object, or None, default None): - String, path object (implementing os.PathLike[str]), or file-like - object implementing a write() function. If None, the result is - returned as a string. - - Can be a destination URI of Cloud Storage files(s) to store the extracted + path_or_buf (str): + A destination URI of Cloud Storage files(s) to store the extracted dataframe in format of ``gs:///``. Must contain a wildcard `*` character. If the data size is more than 1GB, you must use a wildcard to export the data into multiple files and the size of the files varies. + + None, file-like objects or local file paths not yet supported. orient ({`split`, `records`, `index`, `columns`, `values`, `table`}, default 'columns): Indication of expected JSON string format. @@ -274,42 +182,18 @@ def to_json( throw ValueError if incorrect 'orient' since others are not list-like. - allow_large_results (bool, default None): - If not None, overrides the global setting to allow or disallow large - query results over the default size limit of 10 GB. This parameter has - no effect when results are saved to Google Cloud Storage (GCS). - Returns: - None or str: - If path_or_buf is None, returns the resulting json format as a - string. Otherwise returns None. - - Raises: - ValueError: - If ``lines`` is True but ``records`` is not provided as value for ``orient``. + None: String output not yet supported. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - def to_csv( - self, - path_or_buf, - *, - index: bool = True, - allow_large_results: Optional[bool] = None, - ) -> Optional[str]: + def to_csv(self, path_or_buf: str, *, index: bool = True) -> str | None: """Write object to a comma-separated values (csv) file on Cloud Storage. Args: - path_or_buf (str, path object, file-like object, or None, default None): - String, path object (implementing os.PathLike[str]), or file-like - object implementing a write() function. If None, the result is - returned as a string. If a non-binary file object is passed, it should - be opened with `newline=''`, disabling universal newlines. If a binary - file object is passed, `mode` might need to contain a `'b'`. - - Alternatively, a destination URI of Cloud Storage files(s) to store the - extracted dataframe in format of - ``gs:///``. + path_or_buf (str): + A destination URI of Cloud Storage files(s) to store the extracted dataframe + in format of ``gs:///``. If the data size is more than 1GB, you must use a wildcard to export the data into multiple files and the size of the files @@ -320,14 +204,8 @@ def to_csv( index (bool, default True): If True, write row names (index). - allow_large_results (bool, default None): - If not None, overrides the global setting to allow or disallow large - query results over the default size limit of 10 GB. This parameter has - no effect when results are saved to Google Cloud Storage (GCS). - Returns: - None or str: If path_or_buf is None, returns the resulting json format as a - string. Otherwise returns None. + None: String output not yet supported. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @@ -340,61 +218,16 @@ def get(self, key, default=None): Returns default value if not found. - **Examples:** - - - >>> df = bpd.DataFrame( - ... [ - ... [24.3, 75.7, "high"], - ... [31, 87.8, "high"], - ... [22, 71.6, "medium"], - ... [35, 95, "medium"], - ... ], - ... columns=["temp_celsius", "temp_fahrenheit", "windspeed"], - ... index=["2014-02-12", "2014-02-13", "2014-02-14", "2014-02-15"], - ... ) - >>> df - temp_celsius temp_fahrenheit windspeed - 2014-02-12 24.3 75.7 high - 2014-02-13 31.0 87.8 high - 2014-02-14 22.0 71.6 medium - 2014-02-15 35.0 95.0 medium - - [4 rows x 3 columns] - - >>> df.get(["temp_celsius", "windspeed"]) - temp_celsius windspeed - 2014-02-12 24.3 high - 2014-02-13 31.0 high - 2014-02-14 22.0 medium - 2014-02-15 35.0 medium - - [4 rows x 2 columns] - - >>> ser = df['windspeed'] - >>> ser - 2014-02-12 high - 2014-02-13 high - 2014-02-14 medium - 2014-02-15 medium - Name: windspeed, dtype: string - >>> ser.get('2014-02-13') - 'high' - - If the key is not found, the default value will be used. - - >>> df.get(["temp_celsius", "temp_kelvin"]) - >>> df.get(["temp_celsius", "temp_kelvin"], default="default_value") - 'default_value' - Args: key: object Returns: - Any: - same type as items contained in object + same type as items contained in object """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) + try: + return self[key] + except (KeyError, ValueError, IndexError): + return default def add_prefix(self, prefix: str, axis: int | str | None = None): """Prefix labels with string `prefix`. @@ -410,8 +243,7 @@ def add_prefix(self, prefix: str, axis: int | str | None = None): to add prefix on. Returns: - bigframes.pandas.DataFrame or bigframes.pandas.Series: - New Series or DataFrame with updated labels. + New Series or DataFrame with updated labels. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @@ -429,8 +261,7 @@ def add_suffix(self, suffix: str, axis: int | str | None = None): to add suffix on Returns: - bigframes.pandas.DataFrame or bigframes.pandas.Series: - New Series or DataFrame with updated labels. + New Series or DataFrame with updated labels. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @@ -441,72 +272,17 @@ def head(self, n: int = 5): on position. It is useful for quickly testing if your object has the right type of data in it. - For negative values of `n`, this function returns + **Not yet supported** For negative values of `n`, this function returns all rows except the last `|n|` rows, equivalent to ``df[:n]``. If n is larger than the number of rows, this function returns all rows. - **Examples:** - - - >>> df = bpd.DataFrame({'animal': ['alligator', 'bee', 'falcon', 'lion', - ... 'monkey', 'parrot', 'shark', 'whale', 'zebra']}) - >>> df - animal - 0 alligator - 1 bee - 2 falcon - 3 lion - 4 monkey - 5 parrot - 6 shark - 7 whale - 8 zebra - - [9 rows x 1 columns] - - Viewing the first 5 lines: - - >>> df.head() - animal - 0 alligator - 1 bee - 2 falcon - 3 lion - 4 monkey - - [5 rows x 1 columns] - - Viewing the first `n` lines (three in this case): - - >>> df.head(3) - animal - 0 alligator - 1 bee - 2 falcon - - [3 rows x 1 columns] - - For negative values of `n`: - - >>> df.head(-3) - animal - 0 alligator - 1 bee - 2 falcon - 3 lion - 4 monkey - 5 parrot - - [6 rows x 1 columns] - Args: n (int, default 5): Default 5. Number of rows to select. Returns: - bigframes.pandas.DataFrame or bigframes.pandas.Series: - The first ``n`` rows of the caller object. + The first `n` rows of the caller object. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @@ -527,8 +303,7 @@ def tail(self, n: int = 5): Number of rows to select. Returns: - bigframes.pandas.DataFrame: - The last `n` rows of the caller object. + The last `n` rows of the caller object. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @@ -538,55 +313,11 @@ def sample( frac: Optional[float] = None, *, random_state: Optional[int] = None, - sort: Optional[bool | Literal["random"]] = "random", ): """Return a random sample of items from an axis of object. You can use `random_state` for reproducibility. - **Examples:** - - >>> import bigframes.pandas as bpd - >>> df = bpd.DataFrame({'num_legs': [2, 4, 8, 0], - ... 'num_wings': [2, 0, 0, 0], - ... 'num_specimen_seen': [10, 2, 1, 8]}, - ... index=['falcon', 'dog', 'spider', 'fish']) - >>> df - num_legs num_wings num_specimen_seen - falcon 2 2 10 - dog 4 0 2 - spider 8 0 1 - fish 0 0 8 - - [4 rows x 3 columns] - - Fetch one random row from the DataFrame (Note that we use `random_state` - to ensure reproducibility of the examples): - - >>> df.sample(random_state=1) - num_legs num_wings num_specimen_seen - dog 4 0 2 - - [1 rows x 3 columns] - - A random 50% sample of the DataFrame: - - >>> df.sample(frac=0.5, random_state=1) - num_legs num_wings num_specimen_seen - dog 4 0 2 - fish 0 0 8 - - [2 rows x 3 columns] - - Extract 3 random elements from the Series `df['num_legs']`: - - >>> s = df['num_legs'] - >>> s.sample(n=3, random_state=1) - dog 4 - fish 0 - spider 8 - Name: num_legs, dtype: Int64 - Args: n (Optional[int], default None): Number of items from axis to return. Cannot be used with `frac`. @@ -595,21 +326,10 @@ def sample( Fraction of axis items to return. Cannot be used with `n`. random_state (Optional[int], default None): Seed for random number generator. - sort (Optional[bool|Literal["random"]], default "random"): - - - 'random' (default): No specific ordering will be applied after - sampling. - - 'True' : Index columns will determine the sample's order. - - 'False': The sample will retain the original object's order. Returns: - bigframes.pandas.DataFrame or bigframes.pandas.Series: - A new object of same type as caller containing `n` items randomly - sampled from the caller object. - - Raises: - ValueError: - If both ``n`` and ``frac`` are specified. + A new object of same type as caller containing `n` items randomly + sampled from the caller object. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @@ -624,19 +344,8 @@ def dtypes(self): The result's index is the original DataFrame's columns. Columns with mixed types aren't supported yet in BigQuery DataFrames. - **Examples:** - - - >>> df = bpd.DataFrame({'float': [1.0], 'int': [1], 'string': ['foo']}) - >>> df.dtypes - float Float64 - int Int64 - string string - dtype: object - Returns: - pandas.Series: - A *pandas* Series with the data type of each column. + A *pandas* Series with the data type of each column. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @@ -647,68 +356,8 @@ def copy(self): and indices. Modifications to the data or indices of the copy will not be reflected in the original object. - **Examples:** - - - Modification in the original Series will not affect the copy Series: - - >>> s = bpd.Series([1, 2], index=["a", "b"]) - >>> s - a 1 - b 2 - dtype: Int64 - - >>> s_copy = s.copy() - >>> s_copy - a 1 - b 2 - dtype: Int64 - - >>> s.loc['b'] = 22 - >>> s - a 1 - b 22 - dtype: Int64 - >>> s_copy - a 1 - b 2 - dtype: Int64 - - Modification in the original DataFrame will not affect the copy DataFrame: - - >>> df = bpd.DataFrame({'a': [1, 3], 'b': [2, 4]}) - >>> df - a b - 0 1 2 - 1 3 4 - - [2 rows x 2 columns] - - >>> df_copy = df.copy() - >>> df_copy - a b - 0 1 2 - 1 3 4 - - [2 rows x 2 columns] - - >>> df.loc[df["b"] == 2, "b"] = 22 - >>> df - a b - 0 1 22 - 1 3 4 - - [2 rows x 2 columns] - >>> df_copy - a b - 0 1 2 - 1 3 4 - - [2 rows x 2 columns] - Returns: - bigframes.pandas.DataFrame or bigframes.pandas.Series: - Object type matches caller. + Object type matches caller. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @@ -718,45 +367,6 @@ def copy(self): def ffill(self, *, limit: Optional[int] = None): """Fill NA/NaN values by propagating the last valid observation to next valid. - **Examples:** - - - >>> df = bpd.DataFrame([[np.nan, 2, np.nan, 0], - ... [3, 4, np.nan, 1], - ... [np.nan, np.nan, np.nan, np.nan], - ... [np.nan, 3, np.nan, 4]], - ... columns=list("ABCD")).astype("Float64") - >>> df - A B C D - 0 2.0 0.0 - 1 3.0 4.0 1.0 - 2 - 3 3.0 4.0 - - [4 rows x 4 columns] - - Fill NA/NaN values in DataFrames: - - >>> df.ffill() - A B C D - 0 2.0 0.0 - 1 3.0 4.0 1.0 - 2 3.0 4.0 1.0 - 3 3.0 3.0 4.0 - - [4 rows x 4 columns] - - - Fill NA/NaN values in Series: - - >>> series = bpd.Series([1, np.nan, 2, 3]) - >>> series.ffill() - 0 1.0 - 1 1.0 - 2 2.0 - 3 3.0 - dtype: Float64 - Args: limit : int, default None If method is specified, this is the maximum number of consecutive @@ -768,8 +378,7 @@ def ffill(self, *, limit: Optional[int] = None): Returns: - bigframes.pandas.DataFrame or bigframes.pandas.Series or None: - Object with missing values filled. + Series/DataFrame or None: Object with missing values filled. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @@ -786,101 +395,21 @@ def bfill(self, *, limit: Optional[int] = None): filled. Must be greater than 0 if not None. Returns: - bigframes.pandas.DataFrame or bigframes.pandas.Series or None: - Object with missing values filled. + Series/DataFrame or None: Object with missing values filled. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) def isna(self) -> NDFrame: - """Detect missing (NULL) values. - - Return a boolean same-sized object indicating if the values are NA - (NULL in BigQuery). NA/NULL values get mapped to True values. - Everything else gets mapped to False values. + """Detect missing values. - Note that empty strings ``''``, :attr:`numpy.inf`, and - :attr:`numpy.nan` are ***not*** considered NA values. This NA/NULL - logic differs from numpy, but it is the same as BigQuery and the - :class:`pandas.ArrowDtype`. - - **Examples:** - - >>> df = bpd.DataFrame(dict( - ... age=pd.Series(pa.array( - ... [5, 6, None, 4], - ... type=pa.int64(), - ... ), dtype=pd.ArrowDtype(pa.int64())), - ... born=pd.to_datetime([pd.NA, "1940-04-25", "1940-04-25", "1941-08-25"]), - ... name=['Alfred', 'Batman', '', 'Plastic Man'], - ... toy=[None, 'Batmobile', 'Joker', 'Play dough'], - ... height=pd.Series(pa.array( - ... [6.1, 5.9, None, np.nan], - ... type=pa.float64(), - ... ), dtype=pd.ArrowDtype(pa.float64())), - ... )) - >>> df - age born name toy height - 0 5 Alfred 6.1 - 1 6 1940-04-25 00:00:00 Batman Batmobile 5.9 - 2 1940-04-25 00:00:00 Joker - 3 4 1941-08-25 00:00:00 Plastic Man Play dough NaN - - [4 rows x 5 columns] - - Show which entries in a DataFrame are NA (NULL in BigQuery): - - >>> df.isna() - age born name toy height - 0 False True False True False - 1 False False False False False - 2 True False False False True - 3 False False False False False - - [4 rows x 5 columns] - - >>> df.isnull() - age born name toy height - 0 False True False True False - 1 False False False False False - 2 True False False False True - 3 False False False False False - - [4 rows x 5 columns] - - Show which entries in a Series are NA (NULL in BigQuery): - - >>> ser = bpd.Series(pa.array( - ... [5, None, 6, np.nan, None], - ... type=pa.float64(), - ... ), dtype=pd.ArrowDtype(pa.float64())) - >>> ser - 0 5.0 - 1 - 2 6.0 - 3 NaN - 4 - dtype: Float64 - - >>> ser.isna() - 0 False - 1 True - 2 False - 3 False - 4 True - dtype: boolean - - >>> ser.isnull() - 0 False - 1 True - 2 False - 3 False - 4 True - dtype: boolean + Return a boolean same-sized object indicating if the values are NA. + NA values get mapped to True values. Everything else gets mapped to + False values. Characters such as empty strings ``''`` or + :attr:`numpy.inf` are not considered NA values. Returns: - bigframes.pandas.DataFrame or bigframes.pandas.Series: - Mask of bool values for each element that indicates whether an - element is an NA value. + Mask of bool values for each element that indicates whether an + element is an NA value. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @@ -902,29 +431,6 @@ def notna(self) -> NDFrame: notnull = notna - def take(self, indices, axis=0, **kwargs) -> NDFrame: - """Return the elements in the given positional indices along an axis. - - This means that we are not indexing according to actual values in the index - attribute of the object. We are indexing according to the actual position of - the element in the object. - - Args: - indices (list-like): - An array of ints indicating which positions to take. - axis ({0 or 'index', 1 or 'columns', None}, default 0): - The axis on which to select elements. 0 means that we are selecting rows, - 1 means that we are selecting columns. For Series this parameter is - unused and defaults to 0. - **kwargs: - For compatibility with numpy.take(). Has no effect on the output. - - Returns: - bigframes.pandas.DataFrame or bigframes.pandas.Series: - Same type as input object. - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - def filter( self, items=None, @@ -951,12 +457,7 @@ def filter( DataFrame. For `Series` this parameter is unused and defaults to `None`. Returns: - bigframes.pandas.DataFrame or bigframes.pandas.Series: - Same type as input object. - - Raises: - ValueError: - If value provided is not exactly one of ``items``, ``like``, or ``regex``. + same type as input object """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @@ -997,7 +498,7 @@ def pct_change(self, periods: int = 1): Periods to shift for forming percent change. Returns: - bigframes.pandas.DataFrame or bigframes.pandas.Series: The same type as the calling object. + Series or DataFrame: The same type as the calling object. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @@ -1034,13 +535,8 @@ def rank( ascending (bool, default True): Whether or not the elements should be ranked in ascending order. - pct (bool, default False): - Whether or not to display the returned rankings in percentile - form. - Returns: - bigframes.pandas.DataFrame or bigframes.pandas.Series: - Return a Series or DataFrame with data ranks as values. + same type as caller: Return a Series or DataFrame with data ranks as values. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @@ -1048,66 +544,37 @@ def rolling( self, window, min_periods: int | None = None, - on: str | None = None, - closed: Literal["right", "left", "both", "neither"] = "right", ): """ Provide rolling window calculations. - **Examples:** - - >>> import bigframes.pandas as bpd - >>> s = bpd.Series([0,1,2,3,4]) - >>> s.rolling(window=3).min() - 0 - 1 - 2 0 - 3 1 - 4 2 - dtype: Int64 - - >>> df = bpd.DataFrame({'A': [0,1,2,3], 'B': [0,2,4,6]}) - >>> df.rolling(window=2, on='A', closed='both').sum() - A B - 0 0 - 1 1 2 - 2 2 6 - 3 3 12 - - [4 rows x 2 columns] - Args: - window (int, pandas.Timedelta, numpy.timedelta64, datetime.timedelta, str): + window (int, timedelta, str, offset, or BaseIndexer subclass): Size of the moving window. If an integer, the fixed number of observations used for each window. - If a string, the timedelta representation in string. This string - must be parsable by pandas.Timedelta(). + If a timedelta, str, or offset, the time period of each window. Each + window will be a variable sized based on the observations included in + the time-period. This is only valid for datetime-like indexes. + To learn more about the offsets & frequency strings, please see `this link + `__. - Otherwise, the time range for each window. + If a BaseIndexer subclass, the window boundaries + based on the defined ``get_window_bounds`` method. Additional rolling + keyword arguments, namely ``min_periods``, ``center``, ``closed`` and + ``step`` will be passed to ``get_window_bounds``. min_periods (int, default None): Minimum number of observations in window required to have a value; otherwise, result is ``np.nan``. + For a window that is specified by an offset, ``min_periods`` will default to 1. + For a window that is specified by an integer, ``min_periods`` will default to the size of the window. - For a window that is not spicified by an interger, ``min_periods`` will default - to 1. - - on (str, optional): - For a DataFrame, a column label on which to calculate the rolling window, - rather than the DataFrame’s index. - - closed (str, default 'right'): - If 'right', the first point in the window is excluded from calculations. - If 'left', the last point in the window is excluded from calculations. - If 'both', the no points in the window are excluded from calculations. - If 'neither', the first and last points in the window are excluded from calculations. - Returns: bigframes.core.window.Window: ``Window`` subclass if a ``win_type`` is passed. ``Rolling`` subclass if ``win_type`` is not passed. @@ -1128,128 +595,10 @@ def expanding(self, min_periods=1): """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - def pipe( - self, - func: Callable[..., T] | tuple[Callable[..., T], str], - *args, - **kwargs, - ) -> T: - """ - Apply chainable functions that expect Series or DataFrames. - - **Examples:** - - Constructing a income DataFrame from a dictionary. - - - >>> data = [[8000, 1000], [9500, np.nan], [5000, 2000]] - >>> df = bpd.DataFrame(data, columns=['Salary', 'Others']) - >>> df - Salary Others - 0 8000 1000.0 - 1 9500 - 2 5000 2000.0 - - [3 rows x 2 columns] - - Functions that perform tax reductions on an income DataFrame. - - >>> def subtract_federal_tax(df): - ... return df * 0.9 - >>> def subtract_state_tax(df, rate): - ... return df * (1 - rate) - >>> def subtract_national_insurance(df, rate, rate_increase): - ... new_rate = rate + rate_increase - ... return df * (1 - new_rate) - - Instead of writing - - >>> subtract_national_insurance( - ... subtract_state_tax(subtract_federal_tax(df), rate=0.12), - ... rate=0.05, - ... rate_increase=0.02) # doctest: +SKIP - - You can write - - >>> ( - ... df.pipe(subtract_federal_tax) - ... .pipe(subtract_state_tax, rate=0.12) - ... .pipe(subtract_national_insurance, rate=0.05, rate_increase=0.02) - ... ) - Salary Others - 0 5892.48 736.56 - 1 6997.32 - 2 3682.8 1473.12 - - [3 rows x 2 columns] - - If you have a function that takes the data as (say) the second - argument, pass a tuple indicating which keyword expects the - data. For example, suppose ``national_insurance`` takes its data as ``df`` - in the second argument: - - >>> def subtract_national_insurance(rate, df, rate_increase): - ... new_rate = rate + rate_increase - ... return df * (1 - new_rate) - >>> ( - ... df.pipe(subtract_federal_tax) - ... .pipe(subtract_state_tax, rate=0.12) - ... .pipe( - ... (subtract_national_insurance, 'df'), - ... rate=0.05, - ... rate_increase=0.02 - ... ) - ... ) - Salary Others - 0 5892.48 736.56 - 1 6997.32 - 2 3682.8 1473.12 - - [3 rows x 2 columns] - - Args: - func (function): - Function to apply to this object. - ``args``, and ``kwargs`` are passed into ``func``. - Alternatively a ``(callable, data_keyword)`` tuple where - ``data_keyword`` is a string indicating the keyword of - ``callable`` that expects this object. - args (iterable, optional): - Positional arguments passed into ``func``. - kwargs (mapping, optional): - A dictionary of keyword arguments passed into ``func``. - - Returns: - bigframes.pandas.DataFrame or bigframes.pandas.Series: - Object of same type as caller - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) + def __nonzero__(self): + raise ValueError( + f"The truth value of a {type(self).__name__} is ambiguous. " + "Use a.empty, a.bool(), a.item(), a.any() or a.all()." + ) - def __getattr__(self, name: str): - """ - After regular attribute access, try looking up the name - This allows simpler access to columns for interactive use. - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - def equals(self, other) -> bool: - """ - Test whether two objects contain the same elements. - - This function allows two Series or DataFrames to be compared against - each other to see if they have the same shape and elements. NaNs in - the same location are considered equal. - - The row/column index do not need to have the same type, as long - as the values are considered equal. Corresponding columns must be of - the same dtype. - - Args: - other (Series or DataFrame): - The other Series or DataFrame to be compared with the first. - - Returns: - bool: True if all elements are the same in both objects, False - otherwise. - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) + __bool__ = __nonzero__ diff --git a/third_party/bigframes_vendored/pandas/core/groupby/__init__.py b/third_party/bigframes_vendored/pandas/core/groupby/__init__.py index 579765fad5f..b05319b4f7d 100644 --- a/third_party/bigframes_vendored/pandas/core/groupby/__init__.py +++ b/third_party/bigframes_vendored/pandas/core/groupby/__init__.py @@ -7,11 +7,8 @@ class providing the base-class of operations. (defined in pandas.core.groupby.generic) expose these user-facing objects to provide specific functionality. """ - from __future__ import annotations -from typing import Literal - from bigframes import constants @@ -20,96 +17,14 @@ class GroupBy: Class for grouping and aggregating relational data. """ - def describe(self, include: None | Literal["all"] = None): - """ - Generate descriptive statistics. - - Descriptive statistics include those that summarize the central - tendency, dispersion and shape of a - dataset's distribution, excluding ``NaN`` values. - - 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. - - .. 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 - >>> df = bpd.DataFrame({"A": [1, 1, 1, 2, 2], "B": [0, 2, 8, 2, 7], "C": ["cat", "cat", "dog", "mouse", "cat"]}) - >>> df - A B C - 0 1 0 cat - 1 1 2 cat - 2 1 8 dog - 3 2 2 mouse - 4 2 7 cat - - [5 rows x 3 columns] - - >>> df.groupby("A").describe(include="all") - B C - count mean std min 25% 50% 75% max count nunique - A - 1 3 3.333333 4.163332 0 0 2 8 8 3 2 - 2 2 4.5 3.535534 2 2 2 7 7 2 2 - - [2 rows x 10 columns] - - Returns: - bigframes.pandas.DataFrame: - Summary statistics of the Series or Dataframe provided. - - Raises: - ValueError: - If unsupported ``include`` type is provided. - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - def any(self): """ Return True if any value in the group is true, else False. - **Examples:** - - For SeriesGroupBy: - - - >>> lst = ['a', 'a', 'b'] - >>> ser = bpd.Series([1, 2, 0], index=lst) - >>> ser.groupby(level=0).any() - a True - b False - dtype: boolean - - For DataFrameGroupBy: - - >>> data = [[1, 0, 3], [1, 0, 6], [7, 1, 9]] - >>> df = bpd.DataFrame(data, columns=["a", "b", "c"], - ... index=["ostrich", "penguin", "parrot"]) - >>> df.groupby(by=["a"]).any() - b c - a - 1 False True - 7 True True - - [2 rows x 2 columns] - Returns: - bigframes.pandas.DataFrame or bigframes.pandas.Series: - DataFrame or Series of boolean values, + Series or DataFrame: DataFrame or Series of boolean values, where a value is True if any element is True within its - respective group; otherwise False. + respective group, False otherwise. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @@ -117,36 +32,10 @@ def all(self): """ Return True if all values in the group are true, else False. - **Examples:** - - For SeriesGroupBy: - - - >>> lst = ['a', 'a', 'b'] - >>> ser = bpd.Series([1, 2, 0], index=lst) - >>> ser.groupby(level=0).all() - a True - b False - dtype: boolean - - For DataFrameGroupBy: - - >>> data = [[1, 0, 3], [1, 5, 6], [7, 8, 9]] - >>> df = bpd.DataFrame(data, columns=["a", "b", "c"], - ... index=["ostrich", "penguin", "parrot"]) - >>> df.groupby(by=["a"]).all() - b c - a - 1 False True - 7 True True - - [2 rows x 2 columns] - Returns: - bigframes.pandas.DataFrame or bigframes.pandas.Series: - DataFrame or Series of boolean values, + Series or DataFrame: DataFrame or Series of boolean values, where a value is True if all elements are True within its - respective group; otherwise False. + respective group, False otherwise. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @@ -154,34 +43,8 @@ def count(self): """ Compute count of group, excluding missing values. - **Examples:** - - For SeriesGroupBy: - - - >>> lst = ['a', 'a', 'b'] - >>> ser = bpd.Series([1, 2, np.nan], index=lst) - >>> ser.groupby(level=0).count() - a 2 - b 0 - dtype: Int64 - - For DataFrameGroupBy: - - >>> data = [[1, np.nan, 3], [1, np.nan, 6], [7, 8, 9]] - >>> df = bpd.DataFrame(data, columns=["a", "b", "c"], - ... index=["cow", "horse", "bull"]) - >>> df.groupby(by=["a"]).count() - b c - a - 1 0 2 - 7 1 1 - - [2 rows x 2 columns] - Returns: - bigframes.pandas.DataFrame or bigframes.pandas.Series: - Count of values within each group. + Series or DataFrame: Count of values within each group. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @@ -192,49 +55,12 @@ def mean( """ Compute mean of groups, excluding missing values. - **Examples:** - - >>> df = bpd.DataFrame({'A': [1, 1, 2, 1, 2], - ... 'B': [np.nan, 2, 3, 4, 5], - ... 'C': [1, 2, 1, 1, 2]}, columns=['A', 'B', 'C']) - - Groupby one column and return the mean of the remaining columns in each group. - - >>> df.groupby('A').mean() - B C - A - 1 3.0 1.333333 - 2 4.0 1.5 - - [2 rows x 2 columns] - - Groupby two columns and return the mean of the remaining column. - - >>> df.groupby(['A', 'B']).mean() - C - A B - 1 2.0 2.0 - 4.0 1.0 - 2 3.0 1.0 - 5.0 2.0 - - [4 rows x 1 columns] - - Groupby one column and return the mean of only particular column in the group. - - >>> df.groupby('A')['B'].mean() - A - 1 3.0 - 2 4.0 - Name: B, dtype: Float64 - Args: numeric_only (bool, default False): Include only float, int, boolean columns. Returns: - bigframes.pandas.DataFrame or bigframes.pandas.Series: - Mean of groups. + pandas.Series or pandas.DataFrame: Mean of groups. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @@ -242,75 +68,20 @@ def median( self, numeric_only: bool = False, *, - exact: bool = True, + exact: bool = False, ): """ Compute median of groups, excluding missing values. - **Examples:** - - For SeriesGroupBy: - - >>> import bigframes.pandas as bpd - >>> lst = ['a', 'a', 'a', 'b', 'b', 'b'] - >>> ser = bpd.Series([7, 2, 8, 4, 3, 3], index=lst) - >>> ser.groupby(level=0).median() - a 7.0 - b 3.0 - dtype: Float64 - - For DataFrameGroupBy: - - >>> data = {'a': [1, 3, 5, 7, 7, 8, 3], 'b': [1, 4, 8, 4, 4, 2, 1]} - >>> df = bpd.DataFrame(data, index=['dog', 'dog', 'dog', - ... 'mouse', 'mouse', 'mouse', 'mouse']) - >>> df.groupby(level=0).median() - a b - dog 3.0 4.0 - mouse 7.0 3.0 - - [2 rows x 2 columns] - Args: numeric_only (bool, default False): Include only float, int, boolean columns. - exact (bool, default True): - Calculate the exact median instead of an approximation. - - Returns: - bigframes.pandas.DataFrame or bigframes.pandas.Series: - Median of groups. - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - def quantile(self, q=0.5, *, numeric_only: bool = False): - """ - Return group values at the given quantile, a la numpy.percentile. - - **Examples:** - - >>> import bigframes.pandas as bpd - >>> df = bpd.DataFrame([ - ... ['a', 1], ['a', 2], ['a', 3], - ... ['b', 1], ['b', 3], ['b', 5] - ... ], columns=['key', 'val']) - >>> df.groupby('key').quantile() - val - key - a 2.0 - b 3.0 - - [2 rows x 1 columns] - - Args: - q (float or array-like, default 0.5 (50% quantile)): - Value(s) between 0 and 1 providing the quantile(s) to compute. - numeric_only (bool, default False): - Include only `float`, `int` or `boolean` data. + exact (bool, default False): + Calculate the exact median instead of an approximation. Note: + ``exact=True`` not yet supported. Returns: - bigframes.pandas.DataFrame or bigframes.pandas.Series: - Return type determined by caller of GroupBy object. + pandas.Series or pandas.DataFrame: Median of groups. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @@ -324,37 +95,12 @@ def std( For multiple groupings, the result index will be a MultiIndex. - **Examples:** - - For SeriesGroupBy: - - - >>> lst = ['a', 'a', 'a', 'b', 'b', 'b'] - >>> ser = bpd.Series([7, 2, 8, 4, 3, 3], index=lst) - >>> ser.groupby(level=0).std() - a 3.21455 - b 0.57735 - dtype: Float64 - - For DataFrameGroupBy: - - >>> data = {'a': [1, 3, 5, 7, 7, 8, 3], 'b': [1, 4, 8, 4, 4, 2, 1]} - >>> df = bpd.DataFrame(data, index=['dog', 'dog', 'dog', - ... 'mouse', 'mouse', 'mouse', 'mouse']) - >>> df.groupby(level=0).std() - a b - dog 2.0 3.511885 - mouse 2.217356 1.5 - - [2 rows x 2 columns] - Args: numeric_only (bool, default False): Include only `float`, `int` or `boolean` data. Returns: - bigframes.pandas.DataFrame or bigframes.pandas.Series: - Standard deviation of values within each group. + Series or DataFrame: Standard deviation of values within each group. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @@ -368,110 +114,16 @@ def var( For multiple groupings, the result index will be a MultiIndex. - **Examples:** - - For SeriesGroupBy: - - - >>> lst = ['a', 'a', 'a', 'b', 'b', 'b'] - >>> ser = bpd.Series([7, 2, 8, 4, 3, 3], index=lst) - >>> ser.groupby(level=0).var() - a 10.333333 - b 0.333333 - dtype: Float64 - - For DataFrameGroupBy: - - >>> data = {'a': [1, 3, 5, 7, 7, 8, 3], 'b': [1, 4, 8, 4, 4, 2, 1]} - >>> df = bpd.DataFrame(data, index=['dog', 'dog', 'dog', - ... 'mouse', 'mouse', 'mouse', 'mouse']) - >>> df.groupby(level=0).var() - a b - dog 4.0 12.333333 - mouse 4.916667 2.25 - - [2 rows x 2 columns] - Args: numeric_only (bool, default False): Include only `float`, `int` or `boolean` data. Returns: - bigframes.pandas.DataFrame or bigframes.pandas.Series: + Series or DataFrame Variance of values within each group. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - def rank( - self, - method: str = "average", - ascending: bool = True, - na_option: str = "keep", - ): - """ - Provide the rank of values within each group. - - **Examples:** - - >>> import bigframes.pandas as bpd - >>> df = bpd.DataFrame( - ... { - ... "group": ["a", "a", "a", "a", "a", "b", "b", "b", "b", "b"], - ... "value": [2, 4, 2, 3, 5, 1, 2, 4, 1, 5], - ... } - ... ) - >>> df - group value - 0 a 2 - 1 a 4 - 2 a 2 - 3 a 3 - 4 a 5 - 5 b 1 - 6 b 2 - 7 b 4 - 8 b 1 - 9 b 5 - - [10 rows x 2 columns] - >>> for method in ['average', 'min', 'max', 'dense', 'first']: - ... df[f'{method}_rank'] = df.groupby('group')['value'].rank(method) - >>> df - group value average_rank min_rank max_rank dense_rank first_rank - 0 a 2 1.5 1.0 2.0 1.0 1.0 - 1 a 4 4.0 4.0 4.0 3.0 4.0 - 2 a 2 1.5 1.0 2.0 1.0 2.0 - 3 a 3 3.0 3.0 3.0 2.0 3.0 - 4 a 5 5.0 5.0 5.0 4.0 5.0 - 5 b 1 1.5 1.0 2.0 1.0 1.0 - 6 b 2 3.0 3.0 3.0 2.0 3.0 - 7 b 4 4.0 4.0 4.0 3.0 4.0 - 8 b 1 1.5 1.0 2.0 1.0 2.0 - 9 b 5 5.0 5.0 5.0 4.0 5.0 - - [10 rows x 7 columns] - - Args: - method ({'average', 'min', 'max', 'first', 'dense'}, default 'average'): - * average: average rank of group. - * min: lowest rank in group. - * max: highest rank in group. - * first: ranks assigned in order they appear in the array. - * dense: like 'min', but rank always increases by 1 between groups. - ascending (bool, default True): - False for ranks by high (1) to low (N). - na_option ({'keep', 'top', 'bottom'}, default 'keep'): - * keep: leave NA values where they are. - * top: smallest rank if ascending. - * bottom: smallest rank if descending. - pct (bool, default False): - Compute percentage rank of data within each group - - Returns: - DataFrame with ranking of values within each group - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - def skew( self, *, @@ -482,26 +134,12 @@ def skew( Normalized by N-1. - **Examples:** - - For SeriesGroupBy: - - - >>> ser = bpd.Series([390., 350., 357., np.nan, 22., 20., 30.], - ... index=['Falcon', 'Falcon', 'Falcon', 'Falcon', - ... 'Parrot', 'Parrot', 'Parrot'], - ... name="Max Speed") - >>> ser.groupby(level=0).skew() - Falcon 1.525174 - Parrot 1.457863 - Name: Max Speed, dtype: Float64 - Args: numeric_only (bool, default False): Include only `float`, `int` or `boolean` data. Returns: - bigframes.pandas.DataFrame or bigframes.pandas.Series: + Series or DataFrame Variance of values within each group. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @@ -517,128 +155,16 @@ def kurt( Kurtosis obtained using Fisher's definition of kurtosis (kurtosis of normal == 0.0). Normalized by N-1. - **Examples:** - - - >>> lst = ['a', 'a', 'a', 'a', 'b', 'b', 'b', 'b', 'b'] - >>> ser = bpd.Series([0, 1, 1, 0, 0, 1, 2, 4, 5], index=lst) - >>> ser.groupby(level=0).kurt() - a -6.0 - b -1.963223 - dtype: Float64 - Args: numeric_only (bool, default False): Include only `float`, `int` or `boolean` data. Returns: - bigframes.pandas.DataFrame or bigframes.pandas.Series: + Series or DataFrame Variance of values within each group. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - def kurtosis( - self, - *, - numeric_only: bool = False, - ): - """ - Return unbiased kurtosis over requested axis. - - Kurtosis obtained using Fisher's definition of - kurtosis (kurtosis of normal == 0.0). Normalized by N-1. - - **Examples:** - - - >>> lst = ['a', 'a', 'a', 'a', 'b', 'b', 'b', 'b', 'b'] - >>> ser = bpd.Series([0, 1, 1, 0, 0, 1, 2, 4, 5], index=lst) - >>> ser.groupby(level=0).kurtosis() - a -6.0 - b -1.963223 - dtype: Float64 - - Args: - numeric_only (bool, default False): - Include only `float`, `int` or `boolean` data. - - Returns: - bigframes.pandas.DataFrame or bigframes.pandas.Series: - Variance of values within each group. - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - def first(self, numeric_only: bool = False, min_count: int = -1): - """ - Compute the first entry of each column within each group. - - Defaults to skipping NA elements. - - **Examples:** - - >>> import bigframes.pandas as bpd - >>> df = bpd.DataFrame(dict(A=[1, 1, 3], B=[None, 5, 6], C=[1, 2, 3])) - >>> df.groupby("A").first() - B C - A - 1 5.0 1 - 3 6.0 3 - - [2 rows x 2 columns] - - >>> df.groupby("A").first(min_count=2) - B C - A - 1 1 - 3 - - [2 rows x 2 columns] - - Args: - numeric_only (bool, default False): - Include only float, int, boolean columns. If None, will attempt to use - everything, then use only numeric data. - min_count (int, default -1): - The required number of valid values to perform the operation. If fewer - than ``min_count`` valid values are present the result will be NA. - - Returns: - bigframes.pandas.DataFrame or bigframes.pandas.Series: - First of values within each group. - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - def last(self, numeric_only: bool = False, min_count: int = -1): - """ - Compute the last entry of each column within each group. - - Defaults to skipping NA elements. - - **Examples:** - - >>> df = bpd.DataFrame(dict(A=[1, 1, 3], B=[5, None, 6], C=[1, 2, 3])) - >>> df.groupby("A").last() - B C - A - 1 5.0 2 - 3 6.0 3 - - [2 rows x 2 columns] - - Args: - numeric_only (bool, default False): - Include only float, int, boolean columns. If None, will attempt to use - everything, then use only numeric data. - min_count (int, default -1): - The required number of valid values to perform the operation. If fewer - than ``min_count`` valid values are present the result will be NA. - - Returns: - bigframes.pandas.DataFrame or bigframes.pandas.Series: - Last of values within each group. - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - def sum( self, numeric_only: bool = False, @@ -647,71 +173,31 @@ def sum( """ Compute sum of group values. - **Examples:** - - For SeriesGroupBy: - - - >>> lst = ['a', 'a', 'b', 'b'] - >>> ser = bpd.Series([1, 2, 3, 4], index=lst) - >>> ser.groupby(level=0).sum() - a 3 - b 7 - dtype: Int64 - - For DataFrameGroupBy: - - >>> data = [[1, 8, 2], [1, 2, 5], [2, 5, 8], [2, 6, 9]] - >>> df = bpd.DataFrame(data, columns=["a", "b", "c"], - ... index=["tiger", "leopard", "cheetah", "lion"]) - >>> df.groupby("a").sum() - b c - a - 1 10 7 - 2 11 17 - - [2 rows x 2 columns] - Args: numeric_only (bool, default False): Include only float, int, boolean columns. min_count (int, default 0): The required number of valid values to perform the operation. If fewer - than ``min_count`` and non-NA values are present, the result will be NA. + than ``min_count`` non-NA values are present the result will be NA. Returns: - bigframes.pandas.DataFrame or bigframes.pandas.Series: - Computed sum of values within each group. + Series or DataFrame: Computed sum of values within each group. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) def prod(self, numeric_only: bool = False, min_count: int = 0): """ Compute prod of group values. - (DataFrameGroupBy functionality is not yet available.) - - **Examples:** - - For SeriesGroupBy: - - - >>> lst = ['a', 'a', 'b', 'b'] - >>> ser = bpd.Series([1, 2, 3, 4], index=lst) - >>> ser.groupby(level=0).prod() - a 2.0 - b 12.0 - dtype: Float64 Args: numeric_only (bool, default False): Include only float, int, boolean columns. min_count (int, default 0): The required number of valid values to perform the operation. If fewer - than ``min_count`` and non-NA values are present, the result will be NA. + than ``min_count`` non-NA values are present the result will be NA. Returns: - bigframes.pandas.DataFrame or bigframes.pandas.Series: - Computed prod of values within each group. + Series or DataFrame: Computed prod of values within each group. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @@ -723,41 +209,15 @@ def min( """ Compute min of group values. - **Examples:** - - For SeriesGroupBy: - - - >>> lst = ['a', 'a', 'b', 'b'] - >>> ser = bpd.Series([1, 2, 3, 4], index=lst) - >>> ser.groupby(level=0).min() - a 1 - b 3 - dtype: Int64 - - For DataFrameGroupBy: - - >>> data = [[1, 8, 2], [1, 2, 5], [2, 5, 8], [2, 6, 9]] - >>> df = bpd.DataFrame(data, columns=["a", "b", "c"], - ... index=["tiger", "leopard", "cheetah", "lion"]) - >>> df.groupby(by=["a"]).min() - b c - a - 1 2 2 - 2 5 8 - - [2 rows x 2 columns] - Args: numeric_only (bool, default False): Include only float, int, boolean columns. min_count (int, default 0): The required number of valid values to perform the operation. If fewer - than ``min_count`` and non-NA values are present, the result will be NA. + than ``min_count`` non-NA values are present the result will be NA. Returns: - bigframes.pandas.DataFrame or bigframes.pandas.Series: - Computed min of values within each group. + Series or DataFrame: Computed min of values within each group. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @@ -769,41 +229,15 @@ def max( """ Compute max of group values. - **Examples:** - - For SeriesGroupBy: - - - >>> lst = ['a', 'a', 'b', 'b'] - >>> ser = bpd.Series([1, 2, 3, 4], index=lst) - >>> ser.groupby(level=0).max() - a 2 - b 4 - dtype: Int64 - - For DataFrameGroupBy: - - >>> data = [[1, 8, 2], [1, 2, 5], [2, 5, 8], [2, 6, 9]] - >>> df = bpd.DataFrame(data, columns=["a", "b", "c"], - ... index=["tiger", "leopard", "cheetah", "lion"]) - >>> df.groupby(by=["a"]).max() - b c - a - 1 8 5 - 2 6 9 - - [2 rows x 2 columns] - Args: numeric_only (bool, default False): Include only float, int, boolean columns. min_count (int, default 0): The required number of valid values to perform the operation. If fewer - than ``min_count`` and non-NA values are present, the result will be NA. + than ``min_count`` non-NA values are present the result will be NA. Returns: - bigframes.pandas.DataFrame or bigframes.pandas.Series: - Computed max of values within each group. + Series or DataFrame: Computed max of values within each group. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @@ -811,35 +245,12 @@ def cumcount(self, ascending: bool = True): """ Number each item in each group from 0 to the length of that group - 1. - **Examples:** - - For SeriesGroupBy: - - - >>> lst = ['a', 'a', 'b', 'b', 'c'] - >>> ser = bpd.Series([5, 1, 2, 3, 4], index=lst) - >>> ser.groupby(level=0).cumcount() - a 0 - a 1 - b 0 - b 1 - c 0 - dtype: Int64 - >>> ser.groupby(level=0).cumcount(ascending=False) - a 0 - a 1 - b 0 - b 1 - c 0 - dtype: Int64 - Args: ascending (bool, default True): If False, number in reverse, from length of group - 1 to 0. Returns: - bigframes.pandas.Series: - Sequence number of each element within each group. + Series: Sequence number of each element within each group. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @@ -847,35 +258,8 @@ def cumprod(self, *args, **kwargs): """ Cumulative product for each group. - **Examples:** - - For SeriesGroupBy: - - - >>> lst = ['a', 'a', 'b'] - >>> ser = bpd.Series([6, 2, 0], index=lst) - >>> ser.groupby(level=0).cumprod() - a 6.0 - a 12.0 - b 0.0 - dtype: Float64 - - For DataFrameGroupBy: - - >>> data = [[1, 8, 2], [1, 2, 5], [2, 6, 9]] - >>> df = bpd.DataFrame(data, columns=["a", "b", "c"], - ... index=["cow", "horse", "bull"]) - >>> df.groupby("a").cumprod() - b c - cow 8.0 2.0 - horse 16.0 10.0 - bull 6.0 9.0 - - [3 rows x 2 columns] - Returns: - bigframes.pandas.DataFrame or bigframes.pandas.Series: - Cumulative product for each group. + Series or DataFrame: Cumulative product for each group. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @@ -883,35 +267,8 @@ def cumsum(self, *args, **kwargs): """ Cumulative sum for each group. - **Examples:** - - For SeriesGroupBy: - - - >>> lst = ['a', 'a', 'b'] - >>> ser = bpd.Series([6, 2, 0], index=lst) - >>> ser.groupby(level=0).cumsum() - a 6 - a 8 - b 0 - dtype: Int64 - - For DataFrameGroupBy: - - >>> data = [[1, 8, 2], [1, 2, 5], [2, 6, 9]] - >>> df = bpd.DataFrame(data, columns=["a", "b", "c"], - ... index=["fox", "gorilla", "lion"]) - >>> df.groupby("a").cumsum() - b c - fox 8 2 - gorilla 10 7 - lion 6 9 - - [3 rows x 2 columns] - Returns: - bigframes.pandas.DataFrame or bigframes.pandas.Series: - Cumulative sum for each group. + Series or DataFrame: Cumulative sum for each group. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @@ -919,35 +276,8 @@ def cummin(self, *args, numeric_only: bool = False, **kwargs): """ Cumulative min for each group. - **Examples:** - - For SeriesGroupBy: - - - >>> lst = ['a', 'a', 'b'] - >>> ser = bpd.Series([6, 2, 0], index=lst) - >>> ser.groupby(level=0).cummin() - a 6 - a 2 - b 0 - dtype: Int64 - - For DataFrameGroupBy: - - >>> data = [[1, 8, 2], [1, 2, 5], [2, 6, 9]] - >>> df = bpd.DataFrame(data, columns=["a", "b", "c"], - ... index=["fox", "gorilla", "lion"]) - >>> df.groupby("a").cummin() - b c - fox 8 2 - gorilla 2 2 - lion 6 9 - - [3 rows x 2 columns] - Returns: - bigframes.pandas.DataFrame or bigframes.pandas.Series: - Cumulative min for each group. + Series or DataFrame: Cumulative min for each group. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @@ -955,35 +285,8 @@ def cummax(self, *args, numeric_only: bool = False, **kwargs): """ Cumulative max for each group. - **Examples:** - - For SeriesGroupBy: - - - >>> lst = ['a', 'a', 'b'] - >>> ser = bpd.Series([6, 2, 0], index=lst) - >>> ser.groupby(level=0).cummax() - a 6 - a 6 - b 0 - dtype: Int64 - - For DataFrameGroupBy: - - >>> data = [[1, 8, 2], [1, 2, 5], [2, 6, 9]] - >>> df = bpd.DataFrame(data, columns=["a", "b", "c"], - ... index=["fox", "gorilla", "lion"]) - >>> df.groupby("a").cummax() - b c - fox 8 2 - gorilla 8 5 - lion 6 9 - - [3 rows x 2 columns] - Returns: - bigframes.pandas.DataFrame or bigframes.pandas.Series: - Cumulative max for each group. + Series or DataFrame: Cumulative max for each group. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @@ -993,42 +296,8 @@ def diff(self): Calculates the difference of each element compared with another element in the group (default is element in previous row). - **Examples:** - - For SeriesGroupBy: - - - >>> lst = ['a', 'a', 'a', 'b', 'b', 'b'] - >>> ser = bpd.Series([7, 2, 8, 4, 3, 3], index=lst) - >>> ser.groupby(level=0).diff() - a - a -5 - a 6 - b - b -1 - b 0 - dtype: Int64 - - For DataFrameGroupBy: - - >>> data = {'a': [1, 3, 5, 7, 7, 8, 3], 'b': [1, 4, 8, 4, 4, 2, 1]} - >>> df = bpd.DataFrame(data, index=['dog', 'dog', 'dog', - ... 'mouse', 'mouse', 'mouse', 'mouse']) - >>> df.groupby(level=0).diff() - a b - dog - dog 2 3 - dog 2 4 - mouse - mouse 0 0 - mouse 1 -2 - mouse -5 -1 - - [7 rows x 2 columns] - Returns: - bigframes.pandas.DataFrame or bigframes.pandas.Series: - First differences. + Series or DataFrame: First differences. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @@ -1036,41 +305,12 @@ def shift(self, periods: int = 1): """ Shift each group by periods observations. - **Examples:** - - For SeriesGroupBy: - - - >>> lst = ['a', 'a', 'b', 'b'] - >>> ser = bpd.Series([1, 2, 3, 4], index=lst) - >>> ser.groupby(level=0).shift(1) - a - a 1 - b - b 3 - dtype: Int64 - - For DataFrameGroupBy: - - >>> data = [[1, 2, 3], [1, 5, 6], [2, 5, 8], [2, 6, 9]] - >>> df = bpd.DataFrame(data, columns=["a", "b", "c"], - ... index=["tuna", "salmon", "catfish", "goldfish"]) - >>> df.groupby("a").shift(1) - b c - tuna - salmon 2 3 - catfish - goldfish 5 8 - - [4 rows x 2 columns] - Args: periods (int, default 1): Number of periods to shift. Returns: - bigframes.pandas.DataFrame or bigframes.pandas.Series: - Object shifted within each group. + Series or DataFrame: Object shifted within each group. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @@ -1078,55 +318,19 @@ def rolling(self, *args, **kwargs): """ Returns a rolling grouper, providing rolling functionality per group. - **Examples:** - - >>> import bigframes.pandas as bpd - >>> lst = ['a', 'a', 'a', 'a', 'e'] - >>> ser = bpd.Series([1, 0, -2, -1, 2], index=lst) - >>> ser.groupby(level=0).rolling(2).min() - index index - a a - a 0 - a -2 - a -2 - e e - dtype: Int64 - Args: - window (int, pandas.Timedelta, numpy.timedelta64, datetime.timedelta, str): - Size of the moving window. - - If an integer, the fixed number of observations used for - each window. - - If a string, the timedelta representation in string. This string - must be parsable by pandas.Timedelta(). - - Otherwise, the time range for each window. - min_periods (int, default None): Minimum number of observations in window required to have a value; otherwise, result is ``np.nan``. + For a window that is specified by an offset, + ``min_periods`` will default to 1. + For a window that is specified by an integer, ``min_periods`` will default to the size of the window. - For a window that is not spicified by an interger, ``min_periods`` will default - to 1. - - on (str, optional): - For a DataFrame, a column label on which to calculate the rolling window, - rather than the DataFrame’s index. - - closed (str, default 'right'): - If 'right', the first point in the window is excluded from calculations. - If 'left', the last point in the window is excluded from calculations. - If 'both', the no points in the window are excluded from calculations. - If 'neither', the first and last points in the window are excluded from calculations. - Returns: - bigframes.pandas.DataFrame or bigframes.pandas.Series: - Return a new grouper with our rolling appended. + Series or DataFrame: Return a new grouper with our rolling appended. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @@ -1134,162 +338,8 @@ def expanding(self, *args, **kwargs): """ Provides expanding functionality. - **Examples:** - - >>> import bigframes.pandas as bpd - >>> lst = ['a', 'a', 'c', 'c', 'e'] - >>> ser = bpd.Series([1, 0, -2, -1, 2], index=lst) - >>> ser.groupby(level=0).expanding().min() - index index - a a 1 - a 0 - c c -2 - c -2 - e e 2 - dtype: Int64 - - Returns: - bigframes.pandas.DataFrame or bigframes.pandas.Series: - An expanding grouper, providing expanding functionality per group. - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - def head(self, n: int = 5): - """ - Return last first n rows of each group - - **Examples:** - - - >>> df = bpd.DataFrame([[1, 2], [1, 4], [5, 6]], - ... columns=['A', 'B']) - >>> df.groupby('A').head(1) - A B - 0 1 2 - 2 5 6 - [2 rows x 2 columns] - - Args: - n (int): - If positive: number of entries to include from start of each group. - If negative: number of entries to exclude from end of each group. - - Returns: - bigframes.pandas.DataFrame or bigframes.pandas.Series: - First n rows of the original DataFrame or Series - - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - def size(self): - """ - Compute group sizes. - - **Examples:** - - - For SeriesGroupBy: - - >>> lst = ['a', 'a', 'b'] - >>> ser = bpd.Series([1, 2, 3], index=lst) - >>> ser - a 1 - a 2 - b 3 - dtype: Int64 - >>> ser.groupby(level=0).size() - a 2 - b 1 - dtype: Int64 - - For DataFrameGroupBy: - - >>> data = [[1, 2, 3], [1, 5, 6], [7, 8, 9]] - >>> df = bpd.DataFrame(data, columns=["a", "b", "c"], - ... index=["owl", "toucan", "eagle"]) - >>> df - a b c - owl 1 2 3 - toucan 1 5 6 - eagle 7 8 9 - [3 rows x 3 columns] - >>> df.groupby("a").size() - a - 1 2 - 7 1 - dtype: Int64 - - Returns: - bigframes.pandas.DataFrame or bigframes.pandas.Series: - Number of rows in each group as a Series if as_index is True - or a DataFrame if as_index is False. - - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - def __iter__(self): - r""" - Groupby iterator. - - This method provides an iterator over the groups created by the ``resample`` - or ``groupby`` operation on the object. The method yields tuples where - the first element is the label (group key) corresponding to each group or - resampled bin, and the second element is the subset of the data that falls - within that group or bin. - - **Examples:** - - - For SeriesGroupBy: - - >>> lst = ["a", "a", "b"] - >>> ser = bpd.Series([1, 2, 3], index=lst) - >>> ser - a 1 - a 2 - b 3 - dtype: Int64 - >>> for x, y in ser.groupby(level=0): - ... print(f"{x}\n{y}\n") - a - a 1 - a 2 - dtype: Int64 - b - b 3 - dtype: Int64 - - For DataFrameGroupBy: - - >>> data = [[1, 2, 3], [1, 5, 6], [7, 8, 9]] - >>> df = bpd.DataFrame(data, columns=["a", "b", "c"]) - >>> df - a b c - 0 1 2 3 - 1 1 5 6 - 2 7 8 9 - - [3 rows x 3 columns] - >>> for x, y in df.groupby(by=["a"]): - ... print(f'{x}\n{y}\n') - (1,) - a b c - 0 1 2 3 - 1 1 5 6 - - [2 rows x 3 columns] - (7,) - - a b c - 2 7 8 9 - - [1 rows x 3 columns] - - Returns: - Iterable[Label | Tuple, bigframes.pandas.Series | bigframes.pandas.DataFrame]: - Generator yielding sequence of (name, subsetted object) - for each group. + Series or DataFrame: A expanding grouper, providing expanding functionality per group. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @@ -1299,47 +349,6 @@ def agg(self, func): """ Aggregate using one or more operations. - **Examples:** - - - >>> s = bpd.Series([1, 2, 3, 4], index=[1, 1, 2, 2]) - >>> s.groupby(level=0).agg(['min', 'max']) - min max - 1 1 2 - 2 3 4 - - [2 rows x 2 columns] - - Args: - func : function, str, list, dict or None - Function to use for aggregating the data. - - Accepted combinations are: - - - string function name - - list of function names, e.g. ``['sum', 'mean']`` - - Returns: - bigframes.pandas.Series: - A BigQuery Series. - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - def aggregate(self, func): - """ - Aggregate using one or more operations. - - **Examples:** - - - >>> s = bpd.Series([1, 2, 3, 4], index=[1, 1, 2, 2]) - >>> s.groupby(level=0).aggregate(['min', 'max']) - min max - 1 1 2 - 2 3 4 - - [2 rows x 2 columns] - Args: func : function, str, list, dict or None Function to use for aggregating the data. @@ -1350,54 +359,7 @@ def aggregate(self, func): - list of function names, e.g. ``['sum', 'mean']`` Returns: - bigframes.pandas.Series: - A BigQuery Series. - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - def nunique(self): - """ - Return number of unique elements in the group. - - **Examples:** - - - >>> lst = ['a', 'a', 'b', 'b'] - >>> ser = bpd.Series([1, 2, 3, 3], index=lst) - >>> ser.groupby(level=0).nunique() - a 2 - b 1 - dtype: Int64 - - Returns: - bigframes.pandas.Series: - Number of unique values within each group. - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - def value_counts( - self, - normalize: bool = False, - sort: bool = True, - ascending: bool = False, - dropna: bool = True, - ): - """ - Return a Series or DataFrame containing counts of unique rows. - - Args: - normalize (bool, default False): - Return proportions rather than frequencies. - sort (bool, default True): - Sort by frequencies. - ascending (bool, default False): - Sort in ascending order. - dropna (bool, default True): - Don't include counts of rows that contain NA values. - - Returns: - Series or DataFrame: - Series if the groupby as_index is True, otherwise DataFrame. + Series or DataFrame """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @@ -1407,92 +369,6 @@ def agg(self, func, **kwargs): """ Aggregate using one or more operations. - **Examples:** - - - >>> data = {"A": [1, 1, 2, 2], - ... "B": [1, 2, 3, 4], - ... "C": [0.362838, 0.227877, 1.267767, -0.562860]} - >>> df = bpd.DataFrame(data) - - The aggregation is for each column. - - >>> df.groupby('A').agg('min') - B C - A - 1 1 0.227877 - 2 3 -0.56286 - - [2 rows x 2 columns] - - Multiple aggregations - - >>> df.groupby('A').agg(['min', 'max']) - B C - min max min max - A - 1 1 2 0.227877 0.362838 - 2 3 4 -0.56286 1.267767 - - [2 rows x 4 columns] - - Args: - func (function, str, list, dict or None): - Function to use for aggregating the data. - - Accepted combinations are: - - - string function name - - list of function names, e.g. ``['sum', 'mean']`` - - dict of axis labels -> function names or list of such. - - None, in which case ``**kwargs`` are used with Named Aggregation. Here the - output has one column for each element in ``**kwargs``. The name of the - column is keyword, whereas the value determines the aggregation used to compute - the values in the column. - - kwargs - If ``func`` is None, ``**kwargs`` are used to define the output names and - aggregations via Named Aggregation. See ``func`` entry. - - Returns: - bigframes.pandas.DataFrame: - A BigQuery DataFrame. - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - def aggregate(self, func, **kwargs): - """ - Aggregate using one or more operations. - - **Examples:** - - - >>> data = {"A": [1, 1, 2, 2], - ... "B": [1, 2, 3, 4], - ... "C": [0.362838, 0.227877, 1.267767, -0.562860]} - >>> df = bpd.DataFrame(data) - - The aggregation is for each column. - - >>> df.groupby('A').aggregate('min') - B C - A - 1 1 0.227877 - 2 3 -0.56286 - - [2 rows x 2 columns] - - Multiple aggregations - - >>> df.groupby('A').agg(['min', 'max']) - B C - min max min max - A - 1 1 2 0.227877 0.362838 - 2 3 4 -0.56286 1.267767 - - [2 rows x 4 columns] - Args: func (function, str, list, dict or None): Function to use for aggregating the data. @@ -1512,191 +388,6 @@ def aggregate(self, func, **kwargs): aggregations via Named Aggregation. See ``func`` entry. Returns: - bigframes.pandas.DataFrame: - A BigQuery DataFrame. - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - def corr( - self, - *, - numeric_only: bool = False, - ): - """ - Compute pairwise correlation of columns, excluding NA/null values. - - **Examples:** - - - >>> df = bpd.DataFrame({'A': [1, 2, 3], - ... 'B': [400, 500, 600], - ... 'C': [0.8, 0.4, 0.9]}) - >>> df.corr(numeric_only=True) - A B C - A 1.0 1.0 0.188982 - B 1.0 1.0 0.188982 - C 0.188982 0.188982 1.0 - - [3 rows x 3 columns] - - Args: - numeric_only(bool, default False): - Include only float, int, boolean, decimal data. - - Returns: - bigframes.pandas.DataFrame: Correlation matrix. - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - def cov( - self, - *, - numeric_only: bool = False, - ): - """ - Compute pairwise covariance of columns, excluding NA/null values. - - **Examples:** - - - >>> df = bpd.DataFrame({'A': [1, 2, 3], - ... 'B': [400, 500, 600], - ... 'C': [0.8, 0.4, 0.9]}) - >>> df.cov(numeric_only=True) - A B C - A 1.0 100.0 0.05 - B 100.0 10000.0 5.0 - C 0.05 5.0 0.07 - - [3 rows x 3 columns] - - Args: - numeric_only(bool, default False): - Include only float, int, boolean, decimal data. - - Returns: - bigframes.pandas.DataFrame: The covariance matrix of the series of the DataFrame. - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - def nunique(self): - """ - Return DataFrame with counts of unique elements in each position. - - **Examples:** - - - >>> df = bpd.DataFrame({'id': ['spam', 'egg', 'egg', 'spam', - ... 'ham', 'ham'], - ... 'value1': [1, 5, 5, 2, 5, 5], - ... 'value2': list('abbaxy')}) - >>> df.groupby('id').nunique() - value1 value2 - id - egg 1 1 - ham 1 2 - spam 2 1 - - [3 rows x 2 columns] - - Returns: - bigframes.pandas.DataFrame: - Number of unique values within a BigQuery DataFrame. - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - def value_counts( - self, - subset=None, - normalize: bool = False, - sort: bool = True, - ascending: bool = False, - dropna: bool = True, - ): - """ - Return a Series or DataFrame containing counts of unique rows. - - **Examples:** - - - >>> df = bpd.DataFrame({ - ... 'gender': ['male', 'male', 'female', 'male', 'female', 'male'], - ... 'education': ['low', 'medium', 'high', 'low', 'high', 'low'], - ... 'country': ['US', 'FR', 'US', 'FR', 'FR', 'FR'] - ... }) - - >>> df - gender education country - 0 male low US - 1 male medium FR - 2 female high US - 3 male low FR - 4 female high FR - 5 male low FR - - [6 rows x 3 columns] - - >>> df.groupby('gender').value_counts() - gender education country - female high FR 1 - US 1 - male low FR 2 - US 1 - medium FR 1 - Name: count, dtype: Int64 - - >>> df.groupby('gender').value_counts(ascending=True) - gender education country - female high FR 1 - US 1 - male low US 1 - medium FR 1 - low FR 2 - Name: count, dtype: Int64 - - >>> df.groupby('gender').value_counts(normalize=True) - gender education country - female high FR 0.5 - US 0.5 - male low FR 0.5 - US 0.25 - medium FR 0.25 - Name: proportion, dtype: Float64 - - >>> df.groupby('gender', as_index=False).value_counts() - gender education country count - 0 female high FR 1 - 1 female high US 1 - 2 male low FR 2 - 3 male low US 1 - 4 male medium FR 1 - - [5 rows x 4 columns] - - >>> df.groupby('gender', as_index=False).value_counts(normalize=True) - gender education country proportion - 0 female high FR 0.5 - 1 female high US 0.5 - 2 male low FR 0.5 - 3 male low US 0.25 - 4 male medium FR 0.25 - - [5 rows x 4 columns] - - Args: - subset (list-like, optional): - Columns to use when counting unique combinations. - normalize (bool, default False): - Return proportions rather than frequencies. - sort (bool, default True): - Sort by frequencies. - ascending (bool, default False): - Sort in ascending order. - dropna (bool, default True): - Don't include counts of rows that contain NA values. - - Returns: - Series or DataFrame: - Series if the groupby as_index is True, otherwise DataFrame. + DataFrame """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) diff --git a/third_party/bigframes_vendored/pandas/core/indexes/accessor.py b/third_party/bigframes_vendored/pandas/core/indexes/accessor.py index da5f9e3b88a..2b4a3263175 100644 --- a/third_party/bigframes_vendored/pandas/core/indexes/accessor.py +++ b/third_party/bigframes_vendored/pandas/core/indexes/accessor.py @@ -1,5 +1,3 @@ -from typing import Literal - from bigframes import constants @@ -10,24 +8,7 @@ class DatetimeProperties: @property def day(self): - """The day of the datetime. - - **Examples:** - - >>> s = bpd.Series( - ... pd.date_range("2000-01-01", periods=3, freq="D") - ... ) - >>> s - 0 2000-01-01 00:00:00 - 1 2000-01-02 00:00:00 - 2 2000-01-03 00:00:00 - dtype: timestamp[us][pyarrow] - >>> s.dt.day - 0 1 - 1 2 - 2 3 - dtype: Int64 - """ + """The day of the datetime.""" raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @@ -36,342 +17,63 @@ def dayofweek(self): """The day of the week with Monday=0, Sunday=6. Return the day of the week. It is assumed the week starts on - Monday, which is denoted by 0 and ends on Sunday, which is denoted - by 6. - - **Examples:** - - >>> s = bpd.Series( - ... pd.date_range('2016-12-31', '2017-01-08', freq='D').to_series() - ... ) - >>> s.dt.dayofweek - 2016-12-31 00:00:00 5 - 2017-01-01 00:00:00 6 - 2017-01-02 00:00:00 0 - 2017-01-03 00:00:00 1 - 2017-01-04 00:00:00 2 - 2017-01-05 00:00:00 3 - 2017-01-06 00:00:00 4 - 2017-01-07 00:00:00 5 - 2017-01-08 00:00:00 6 - dtype: Int64 - - Returns: - Series: Containing integers indicating the day number. - """ - - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - @property - def day_of_week(self): - """The day of the week with Monday=0, Sunday=6. - - Return the day of the week. It is assumed the week starts on - Monday, which is denoted by 0 and ends on Sunday, which is denoted - by 6. - - **Examples:** - - >>> s = bpd.Series( - ... pd.date_range('2016-12-31', '2017-01-08', freq='D').to_series() - ... ) - >>> s.dt.day_of_week - 2016-12-31 00:00:00 5 - 2017-01-01 00:00:00 6 - 2017-01-02 00:00:00 0 - 2017-01-03 00:00:00 1 - 2017-01-04 00:00:00 2 - 2017-01-05 00:00:00 3 - 2017-01-06 00:00:00 4 - 2017-01-07 00:00:00 5 - 2017-01-08 00:00:00 6 - dtype: Int64 - - Returns: - Series: Containing integers indicating the day number. - """ - - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - @property - def weekday(self): - """The day of the week with Monday=0, Sunday=6. - - Return the day of the week. It is assumed the week starts on - Monday, which is denoted by 0 and ends on Sunday, which is denoted - by 6. - - **Examples:** - - >>> s = bpd.Series( - ... pd.date_range('2016-12-31', '2017-01-08', freq='D').to_series() - ... ) - >>> s.dt.weekday - 2016-12-31 00:00:00 5 - 2017-01-01 00:00:00 6 - 2017-01-02 00:00:00 0 - 2017-01-03 00:00:00 1 - 2017-01-04 00:00:00 2 - 2017-01-05 00:00:00 3 - 2017-01-06 00:00:00 4 - 2017-01-07 00:00:00 5 - 2017-01-08 00:00:00 6 - dtype: Int64 - - Returns: - Series: Containing integers indicating the day number. - """ - - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - @property - def day_name(self): - """ - Return the day names in english. - - **Examples:** - >>> s = bpd.Series(pd.date_range(start="2018-01-01", freq="D", periods=3)) - >>> s - 0 2018-01-01 00:00:00 - 1 2018-01-02 00:00:00 - 2 2018-01-03 00:00:00 - dtype: timestamp[us][pyarrow] - >>> s.dt.day_name() - 0 Monday - 1 Tuesday - 2 Wednesday - dtype: string - - Returns: - Series: Series of day names. - - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - @property - def dayofyear(self): - """The ordinal day of the year. - - **Examples:** - - >>> import bigframes.pandas as bpd - >>> s = bpd.Series( - ... pd.date_range('2016-12-28', '2017-01-03', freq='D').to_series() - ... ) - >>> s.dt.dayofyear - 2016-12-28 00:00:00 363 - 2016-12-29 00:00:00 364 - 2016-12-30 00:00:00 365 - 2016-12-31 00:00:00 366 - 2017-01-01 00:00:00 1 - 2017-01-02 00:00:00 2 - 2017-01-03 00:00:00 3 - dtype: Int64 - - Returns: - Series: Containing integers indicating the day number. - """ - - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - @property - def day_of_year(self): - """The ordinal day of the year. - - **Examples:** - - >>> import bigframes.pandas as bpd - >>> s = bpd.Series( - ... pd.date_range('2016-12-28', '2017-01-03', freq='D').to_series() - ... ) - >>> s.dt.day_of_year - 2016-12-28 00:00:00 363 - 2016-12-29 00:00:00 364 - 2016-12-30 00:00:00 365 - 2016-12-31 00:00:00 366 - 2017-01-01 00:00:00 1 - 2017-01-02 00:00:00 2 - 2017-01-03 00:00:00 3 - dtype: Int64 + Monday, which is denoted by 0 and ends on Sunday which is denoted + by 6. This method is available on both Series with datetime + values (using the `dt` accessor) or DatetimeIndex. Returns: - Series: Containing integers indicating the day number. + Series or Index: Containing integers indicating the day number. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @property def date(self): - """Returns a Series with the date part of Timestamps without time and + """Returns numpy array of Python :class:`datetime.date` objects. + + Namely, the date part of Timestamps without time and timezone information. .. warning:: This method returns a Series whereas pandas returns a numpy array. - - **Examples:** - - >>> import bigframes.pandas as bpd - >>> s = bpd.Series(["1/1/2020 10:00:00+00:00", "2/1/2020 11:00:00+00:00"]) - >>> s = bpd.to_datetime(s, utc=True, format="%d/%m/%Y %H:%M:%S%Ez") - >>> s - 0 2020-01-01 10:00:00+00:00 - 1 2020-01-02 11:00:00+00:00 - dtype: timestamp[us, tz=UTC][pyarrow] - >>> s.dt.date - 0 2020-01-01 - 1 2020-01-02 - dtype: date32[day][pyarrow] """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @property def hour(self): - """The hours of the datetime. - - **Examples:** - - >>> import bigframes.pandas as bpd - >>> s = bpd.Series( - ... pd.date_range("2000-01-01", periods=3, freq="h") - ... ) - >>> s - 0 2000-01-01 00:00:00 - 1 2000-01-01 01:00:00 - 2 2000-01-01 02:00:00 - dtype: timestamp[us][pyarrow] - >>> s.dt.hour - 0 0 - 1 1 - 2 2 - dtype: Int64 - """ + """The hours of the datetime.""" raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @property def minute(self): - """The minutes of the datetime. - - **Examples:** - - >>> import bigframes.pandas as bpd - >>> s = bpd.Series( - ... pd.date_range("2000-01-01", periods=3, freq="min") - ... ) - >>> s - 0 2000-01-01 00:00:00 - 1 2000-01-01 00:01:00 - 2 2000-01-01 00:02:00 - dtype: timestamp[us][pyarrow] - >>> s.dt.minute - 0 0 - 1 1 - 2 2 - dtype: Int64 - """ + """The minutes of the datetime.""" raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @property def month(self): - """The month as January=1, December=12. - - **Examples:** - - >>> s = bpd.Series( - ... pd.date_range("2000-01-01", periods=3, freq="ME") - ... ) - >>> s - 0 2000-01-31 00:00:00 - 1 2000-02-29 00:00:00 - 2 2000-03-31 00:00:00 - dtype: timestamp[us][pyarrow] - >>> s.dt.month - 0 1 - 1 2 - 2 3 - dtype: Int64 - """ + """The month as January=1, December=12.""" raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - def isocalendar(self): - """ - Calculate year, week, and day according to the ISO 8601 standard. - - **Examples:** - - >>> s = bpd.Series( - ... pd.date_range('2009-12-27', '2010-01-04', freq='d').to_series() - ... ) - >>> s.dt.isocalendar() - year week day - 2009-12-27 00:00:00 2009 52 7 - 2009-12-28 00:00:00 2009 53 1 - 2009-12-29 00:00:00 2009 53 2 - 2009-12-30 00:00:00 2009 53 3 - 2009-12-31 00:00:00 2009 53 4 - 2010-01-01 00:00:00 2009 53 5 - 2010-01-02 00:00:00 2009 53 6 - 2010-01-03 00:00:00 2009 53 7 - 2010-01-04 00:00:00 2010 1 1 - - [9 rows x 3 columns] - - - Returns: DataFrame - With columns year, week and day. - - - """ - @property def second(self): - """The seconds of the datetime. - - **Examples:** - - >>> import bigframes.pandas as bpd - >>> s = bpd.Series( - ... pd.date_range("2000-01-01", periods=3, freq="s") - ... ) - >>> s - 0 2000-01-01 00:00:00 - 1 2000-01-01 00:00:01 - 2 2000-01-01 00:00:02 - dtype: timestamp[us][pyarrow] - >>> s.dt.second - 0 0 - 1 1 - 2 2 - dtype: Int64 - """ + """The seconds of the datetime.""" raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @property def time(self): - """Returns a Series with the time part of the Timestamps. + """Returns numpy array of :class:`datetime.time` objects. + + The time part of the Timestamps. .. warning:: This method returns a Series whereas pandas returns a numpy array. - - **Examples:** - - >>> import bigframes.pandas as bpd - >>> s = bpd.Series(["1/1/2020 10:00:00+00:00", "2/1/2020 11:00:00+00:00"]) - >>> s = bpd.to_datetime(s, utc=True, format="%m/%d/%Y %H:%M:%S%Ez") - >>> s - 0 2020-01-01 10:00:00+00:00 - 1 2020-02-01 11:00:00+00:00 - dtype: timestamp[us, tz=UTC][pyarrow] - >>> s.dt.time - 0 10:00:00 - 1 11:00:00 - dtype: time64[us][pyarrow] """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @@ -380,172 +82,15 @@ def time(self): def quarter(self): """The quarter of the date. - **Examples:** - - >>> import bigframes.pandas as bpd - >>> s = bpd.Series(["1/1/2020 10:00:00+00:00", "4/1/2020 11:00:00+00:00"]) - >>> s = bpd.to_datetime(s, utc=True, format="%m/%d/%Y %H:%M:%S%Ez") - >>> s - 0 2020-01-01 10:00:00+00:00 - 1 2020-04-01 11:00:00+00:00 - dtype: timestamp[us, tz=UTC][pyarrow] - >>> s.dt.quarter - 0 1 - 1 2 - dtype: Int64 + .. warning:: + This method returns a Series whereas pandas returns + a numpy array. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @property def year(self): - """The year of the datetime. - - **Examples:** - - >>> s = bpd.Series( - ... pd.date_range("2000-01-01", periods=3, freq="YE") - ... ) - >>> s - 0 2000-12-31 00:00:00 - 1 2001-12-31 00:00:00 - 2 2002-12-31 00:00:00 - dtype: timestamp[us][pyarrow] - >>> s.dt.year - 0 2000 - 1 2001 - 2 2002 - dtype: Int64 - """ - - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - @property - def days(self): - """The numebr of days for each element - - **Examples:** - - >>> s = bpd.Series([pd.Timedelta("4d3m2s1us")]) - >>> s - 0 4 days 00:03:02.000001 - dtype: duration[us][pyarrow] - >>> s.dt.days - 0 4 - dtype: Int64 - """ - - @property - def seconds(self): - """Number of seconds (>= 0 and less than 1 day) for each element. - - **Examples:** - - >>> s = bpd.Series([pd.Timedelta("4d3m2s1us")]) - >>> s - 0 4 days 00:03:02.000001 - dtype: duration[us][pyarrow] - >>> s.dt.seconds - 0 182 - dtype: Int64 - """ - - @property - def microseconds(self): - """Number of microseconds (>= 0 and less than 1 second) for each element. - - **Examples:** - - >>> s = bpd.Series([pd.Timedelta("4d3m2s1us")]) - >>> s - 0 4 days 00:03:02.000001 - dtype: duration[us][pyarrow] - >>> s.dt.microseconds - 0 1 - dtype: Int64 - """ - - def total_seconds(self): - """Return total duration of each element expressed in seconds. - - **Examples:** - - >>> s = bpd.Series([pd.Timedelta("1d1m1s1us")]) - >>> s - 0 1 days 00:01:01.000001 - dtype: duration[us][pyarrow] - >>> s.dt.total_seconds() - 0 86461.000001 - dtype: Float64 - """ - - @property - def tz(self): - """Return the timezone. - - **Examples:** - - >>> import bigframes.pandas as bpd - >>> s = bpd.Series(["1/1/2020 10:00:00+00:00", "2/1/2020 11:00:00+00:00"]) - >>> s = bpd.to_datetime(s, utc=True, format="%m/%d/%Y %H:%M:%S%Ez") - >>> s - 0 2020-01-01 10:00:00+00:00 - 1 2020-02-01 11:00:00+00:00 - dtype: timestamp[us, tz=UTC][pyarrow] - >>> s.dt.tz - datetime.timezone.utc - - Returns: - datetime.tzinfo, pytz.tzinfo.BaseTZInfo, dateutil.tz.tz.tzfile, or None - """ - - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) + """The year of the datetime.""" - @property - def tz_localize(self, tz: Literal["UTC"] | None): - """Localize tz-naive Datetime Array/Index to tz-aware Datetime Array/Index. - - This method takes a time zone (tz) naive Datetime Array/Index object and makes - this time zone aware. It does not move the time to another time zone. Only "UTC" - timezone is supported. - - This method can also be used to do the inverse - to create a time zone unaware - object from an aware object. To that end, pass tz=None. - - **Examples:** - - >>> import bigframes.pandas as bpd - >>> s = bpd.Series([pd.Timestamp(year = 2026, month=1, day=1)]) - >>> s - 0 2026-01-01 00:00:00 - dtype: timestamp[us][pyarrow] - >>> s.dt.tz_localize('UTC') - 0 2026-01-01 00:00:00+00:00 - dtype: timestamp[us, tz=UTC][pyarrow] - - Returns: - A BigFrames series with the updated timezone. - """ - - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - @property - def unit(self) -> str: - """Returns the unit of time precision. - - **Examples:** - - >>> import bigframes.pandas as bpd - >>> s = bpd.Series(["1/1/2020 10:00:00+00:00", "2/1/2020 11:00:00+00:00"]) - >>> s = bpd.to_datetime(s, utc=True, format="%m/%d/%Y %H:%M:%S%Ez") - >>> s - 0 2020-01-01 10:00:00+00:00 - 1 2020-02-01 11:00:00+00:00 - dtype: timestamp[us, tz=UTC][pyarrow] - >>> s.dt.unit - 'us' - - Returns: - Unit as string (eg. "us"). - """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) diff --git a/third_party/bigframes_vendored/pandas/core/indexes/base.py b/third_party/bigframes_vendored/pandas/core/indexes/base.py index 632026a3311..e8737341a34 100644 --- a/third_party/bigframes_vendored/pandas/core/indexes/base.py +++ b/third_party/bigframes_vendored/pandas/core/indexes/base.py @@ -1,10 +1,6 @@ # Contains code from https://github.com/pandas-dev/pandas/blob/main/pandas/core/indexes/base.py from __future__ import annotations -import typing -from collections.abc import Hashable - -import bigframes from bigframes import constants @@ -12,415 +8,54 @@ class Index: """Immutable sequence used for indexing and alignment. The basic object storing axis labels for all objects. - - Args: - data (pandas.Series | pandas.Index | bigframes.series.Series | bigframes.core.indexes.base.Index): - Labels (1-dimensional). - dtype: - Data type for the output Index. If not specified, this will be - inferred from `data`. - name: - Name to be stored in the index. - session (Optional[bigframes.session.Session]): - BigQuery DataFrames session where queries are run. If not set, - a default session is used. """ @property def name(self): - """Returns Index name. - - **Examples:** - - - >>> idx = bpd.Index([1, 2, 3], name='x') - >>> idx - Index([1, 2, 3], dtype='Int64', name='x') - >>> idx.name - 'x' - - Returns: - blocks.Label: - Index or MultiIndex name - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - @property - def names(self): - """Returns the names of the Index. - - Returns: - Sequence[blocks.Label]: - A Sequence of Index or MultiIndex name - """ + """Returns Index name.""" raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @property def values(self): - """Return an array representing the data in the Index. - - **Examples:** - - - >>> idx = bpd.Index([1, 2, 3]) - >>> idx - Index([1, 2, 3], dtype='Int64') - - >>> idx.values - array([1, 2, 3]) - - Returns: - array: - Numpy.ndarray or ExtensionArray - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - @property - def ndim(self): - """ - Number of dimensions of the underlying data, by definition 1. - - **Examples:** - - - >>> s = bpd.Series(['Ant', 'Bear', 'Cow']) - >>> s - 0 Ant - 1 Bear - 2 Cow - dtype: string - - >>> s.ndim - 1 - - For Index: - - >>> idx = bpd.Index([1, 2, 3]) - >>> idx - Index([1, 2, 3], dtype='Int64') - - >>> idx.ndim - 1 - - Returns: - int: - Number or dimensions. - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - @property - def size(self) -> int: - """ - Return the number of elements in the underlying data. - - **Examples:** - - - For Series: - - >>> s = bpd.Series(['Ant', 'Bear', 'Cow']) - >>> s - 0 Ant - 1 Bear - 2 Cow - dtype: string - - For Index: - - >>> idx = bpd.Index([1, 2, 3]) - >>> idx - Index([1, 2, 3], dtype='Int64') - - Returns: - int: - Number of elements - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - @property - def empty(self) -> bool: - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - @property - def is_monotonic_increasing(self) -> bool: - """ - Return a boolean if the values are equal or increasing. - - **Examples:** - - - >>> bool(bpd.Index([1, 2, 3]).is_monotonic_increasing) - True - - >>> bool(bpd.Index([1, 2, 2]).is_monotonic_increasing) - True - - >>> bool(bpd.Index([1, 3, 2]).is_monotonic_increasing) - False - - Returns: - bool: - True, if the values monotonically increasing, otherwise False. - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - @property - def is_monotonic_decreasing(self) -> bool: - """ - Return a boolean if the values are equal or decreasing. - - **Examples:** - - - >>> bool(bpd.Index([3, 2, 1]).is_monotonic_decreasing) - True - - >>> bool(bpd.Index([3, 2, 2]).is_monotonic_decreasing) - True - - >>> bool(bpd.Index([3, 1, 2]).is_monotonic_decreasing) - False - - Returns: - bool: - True, if the values monotonically decreasing, otherwise False. - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - @classmethod - def from_frame(cls, frame) -> Index: - """ - Make a MultiIndex from a DataFrame. - - **Examples:** - - - >>> df = bpd.DataFrame([['HI', 'Temp'], ['HI', 'Precip'], - ... ['NJ', 'Temp'], ['NJ', 'Precip']], - ... columns=['a', 'b']) - >>> df - a b - 0 HI Temp - 1 HI Precip - 2 NJ Temp - 3 NJ Precip - - [4 rows x 2 columns] - - >>> bpd.MultiIndex.from_frame(df) - Index([0, 1, 2, 3], dtype='Int64') - - Args: - frame (Union[bigframes.pandas.Series, bigframes.pandas.DataFrame]): - bigframes.pandas.Series or bigframes.pandas.DataFrame to convert - to bigframes.pandas.Index. - - Returns: - bigframes.pandas.Index: - The Index representation of the given Series or DataFrame. - - Raises: - bigframes.exceptions.NullIndexError: - If Index is Null. - """ + """Return an array representing the data in the Index.""" raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @property def shape(self): """ Return a tuple of the shape of the underlying data. - - **Examples:** - - - >>> idx = bpd.Index([1, 2, 3]) - >>> idx - Index([1, 2, 3], dtype='Int64') - - >>> idx.shape - (3,) - - Returns: - Tuple[int]: - A Tuple of integers representing the shape. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @property def nlevels(self) -> int: - """Integer number of levels in this MultiIndex - - **Examples:** - - - >>> mi = bpd.MultiIndex.from_arrays([['a'], ['b'], ['c']]) - >>> mi - MultiIndex([('a', 'b', 'c')], - ) - >>> mi.nlevels - 3 - - Returns: - int: - Number of levels. - """ + """Number of levels.""" raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @property def is_unique(self) -> bool: - """Return if the index has unique values. - - **Examples:** - - - >>> idx = bpd.Index([1, 5, 7, 7]) - >>> idx.is_unique - False - - >>> idx = bpd.Index([1, 5, 7]) - >>> idx.is_unique - True - - Returns: - bool: - True if the index has unique values, otherwise False. - """ + """Return if the index has unique values.""" raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @property def has_duplicates(self) -> bool: - """Check if the Index has duplicate values. - - **Examples:** - - - >>> idx = bpd.Index([1, 5, 7, 7]) - >>> bool(idx.has_duplicates) - True - - >>> idx = bpd.Index([1, 5, 7]) - >>> bool(idx.has_duplicates) - False - - Returns: - bool: - Whether or not the Index has duplicate values. - """ + """Check if the Index has duplicate values.""" raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @property def dtype(self): - """Return the dtype object of the underlying data. - - **Examples:** - - - >>> idx = bpd.Index([1, 2, 3]) - >>> idx - Index([1, 2, 3], dtype='Int64') + """Return the dtype object of the underlying data.""" - >>> idx.dtype - Int64Dtype() - """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @property def dtypes(self): - """Return the dtypes as a Series for the underlying MultiIndex. - - Returns: - Pandas.Series: - Pandas.Series of the MultiIndex dtypes. - """ + """Return the dtypes as a Series for the underlying MultiIndex.""" raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @property def T(self) -> Index: - """Return the transpose, which is by definition self. - - **Examples:** - - - >>> s = bpd.Series(['Ant', 'Bear', 'Cow']) - >>> s - 0 Ant - 1 Bear - 2 Cow - dtype: string - - >>> s.T - 0 Ant - 1 Bear - 2 Cow - dtype: string - - For Index: - - >>> idx = bpd.Index([1, 2, 3]) - >>> idx.T - Index([1, 2, 3], dtype='Int64') - - Returns: - bigframes.pandas.Index: - Index - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - @property - def str(self): - """ - Vectorized string functions for Series and Index. - - NAs stay NA unless handled otherwise by a particular method. Patterned - after Python’s string methods, with some inspiration from R’s stringr package. - - **Examples:** - - >>> import bigframes.pandas as bpd - >>> s = bpd.Series(["A_Str_Series"]) - >>> s - 0 A_Str_Series - dtype: string - - >>> s.str.lower() - 0 a_str_series - dtype: string - - >>> s.str.replace("_", "") - 0 AStrSeries - dtype: string - - Returns: - bigframes.operations.strings.StringMethods: - An accessor containing string methods. - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - def copy( - self, - name=None, - ) -> Index: - """ - Make a copy of this object. - - Name is set on the new object. - - **Examples:** - - - >>> idx = bpd.Index(['a', 'b', 'c']) - >>> new_idx = idx.copy() - >>> idx is new_idx - False - - Args: - name (Label, optional): - Set name for new object. - - Returns: - bigframes.pandas.Index: - Index reference to new object, which is a copy of this object. - """ + """Return the transpose, which is by definition self.""" raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) def transpose(self) -> Index: @@ -428,7 +63,7 @@ def transpose(self) -> Index: Return the transpose, which is by definition self. Returns: - bigframes.pandas.Index + Index """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @@ -438,122 +73,11 @@ def astype(self, dtype): The class of a new Index is determined by dtype. When conversion is impossible, a TypeError exception is raised. - **Examples:** - - - >>> idx = bpd.Index([1, 2, 3]) - >>> idx - Index([1, 2, 3], dtype='Int64') - - - Args: - dtype (str, data type, or pandas.ExtensionDtype): - A dtype supported by BigQuery DataFrame include ``'boolean'``, - ``'Float64'``, ``'Int64'``, ``'int64\\[pyarrow\\]'``, - ``'string'``, ``'string\\[pyarrow\\]'``, - ``'timestamp\\[us, tz=UTC\\]\\[pyarrow\\]'``, - ``'timestamp\\[us\\]\\[pyarrow\\]'``, - ``'date32\\[day\\]\\[pyarrow\\]'``, - ``'time64\\[us\\]\\[pyarrow\\]'``. - A pandas.ExtensionDtype include ``pandas.BooleanDtype()``, - ``pandas.Float64Dtype()``, ``pandas.Int64Dtype()``, - ``pandas.StringDtype(storage="pyarrow")``, - ``pd.ArrowDtype(pa.date32())``, - ``pd.ArrowDtype(pa.time64("us"))``, - ``pd.ArrowDtype(pa.timestamp("us"))``, - ``pd.ArrowDtype(pa.timestamp("us", tz="UTC"))``. - errors ({'raise', 'null'}, default 'raise'): - Control raising of exceptions on invalid data for provided dtype. - If 'raise', allow exceptions to be raised if any value fails cast - If 'null', will assign null value if value fails cast - - Returns: - bigframes.pandas.Index: Index with values cast to specified dtype. - - Raises: - ValueError: - If ``errors`` is not one of ``raise``. - TypeError: - MultiIndex with more than 1 level does not support ``astype``. - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - def get_level_values(self, level) -> Index: - """ - Return an Index of values for requested level. - - This is primarily useful to get an individual level of values from a - MultiIndex, but is provided on Index as well for compatibility. - - **Examples:** - - - >>> idx = bpd.Index(list('abc')) - >>> idx - Index(['a', 'b', 'c'], dtype='string') - - Get level values by supplying level as integer: - - >>> idx.get_level_values(0) - Index(['a', 'b', 'c'], dtype='string') - Args: - level (int or str): - It is either the integer position or the name of the level. + dtype (numpy dtype or pandas type): Returns: - bigframes.pandas.Index: - Calling object, as there is only one level in the Index. - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - def to_series(self): - """ - Create a Series with both index and values equal to the index keys. - - Useful with map for returning an indexer based on an index. - - **Examples:** - - - >>> idx = bpd.Index(['Ant', 'Bear', 'Cow'], name='animal') - - By default, the original index and original name is reused. - - >>> idx.to_series() - animal - Ant Ant - Bear Bear - Cow Cow - Name: animal, dtype: string - - To enforce a new index, specify new labels to index: - - >>> idx.to_series(index=[0, 1, 2]) - 0 Ant - 1 Bear - 2 Cow - Name: animal, dtype: string - - To override the name of the resulting column, specify name: - - >>> idx.to_series(name='zoo') - animal - Ant Ant - Bear Bear - Cow Cow - Name: zoo, dtype: string - - Args: - index (Index, optional): - Index of resulting Series. If None, defaults to original index. - name (str, optional): - Name of resulting Series. If None, defaults to name of original - index. - - Returns: - bigframes.pandas.Series: - The dtype will be based on the type of the Index values. + Index: Index with values cast to specified dtype. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @@ -561,207 +85,60 @@ def isin(self, values): """ Return a boolean array where the index values are in `values`. - Compute boolean array to check whether each index value is found in the + Compute boolean array of whether each index value is found in the passed set of values. The length of the returned boolean array matches the length of the index. - **Examples:** - - - >>> idx = bpd.Index([1,2,3]) - >>> idx - Index([1, 2, 3], dtype='Int64') - - Check whether each index value in a list of values. - - >>> idx.isin([1, 4]) - Index([True, False, False], dtype='boolean') - - >>> midx = bpd.MultiIndex.from_arrays([[1,2,3], - ... ['red', 'blue', 'green']], - ... names=('number', 'color')) - >>> midx - MultiIndex([(1, 'red'), - (2, 'blue'), - (3, 'green')], - names=['number', 'color']) - Args: values (set or list-like): Sought values. Returns: - bigframes.pandas.Series: - Series of boolean values. - - Raises: - TypeError: - If object passed to ``isin()`` is not a list-like + Series: Series of boolean values. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) def all(self) -> bool: """Return whether all elements are Truthy. - **Examples:** - - - True, because nonzero integers are considered True. - - >>> bool(bpd.Index([1, 2, 3]).all()) - True - - False, because 0 is considered False. - - >>> bool(bpd.Index([0, 1, 2]).all()) - False - Returns: - bool: - A single element array-like may be converted to bool. - - Raises: - TypeError: - MultiIndex with more than 1 level does not support ``all``. + bool: A single element array-like may be converted to bool. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) def any(self) -> bool: """Return whether any element is Truthy. - **Examples:** - - - >>> index = bpd.Index([0, 1, 2]) - >>> bool(index.any()) - True - - >>> index = bpd.Index([0, 0, 0]) - >>> bool(index.any()) - False - Returns: - bool: - A single element array-like may be converted to bool. - - Raises: - TypeError: - MultiIndex with more than 1 level does not support ``any``. + bool: A single element array-like may be converted to bool. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) def min(self): """Return the minimum value of the Index. - **Examples:** - - - >>> idx = bpd.Index([3, 2, 1]) - >>> int(idx.min()) - 1 - - >>> idx = bpd.Index(['c', 'b', 'a']) - >>> idx.min() - 'a' - Returns: - scalar: - Minimum value. + scalar: Minimum value. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) def max(self): """Return the maximum value of the Index. - **Examples:** - - - >>> idx = bpd.Index([3, 2, 1]) - >>> int(idx.max()) - 3 - - >>> idx = bpd.Index(['c', 'b', 'a']) - >>> idx.max() - 'c' - Returns: - scalar: - Maximum value. + scalar: Maximum value. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) def argmin(self) -> int: """ - Return int position of the smallest value in the series. + Return int position of the smallest value in the Series. If the minimum is achieved in multiple locations, the first row position is returned. - **Examples:** - - - Consider dataset containing cereal calories - - >>> s = bpd.Series({'Corn Flakes': 100.0, 'Almond Delight': 110.0, - ... 'Cinnamon Toast Crunch': 120.0, 'Cocoa Puff': 110.0}) - >>> s - Corn Flakes 100.0 - Almond Delight 110.0 - Cinnamon Toast Crunch 120.0 - Cocoa Puff 110.0 - dtype: Float64 - - >>> int(s.argmax()) - 2 - - >>> int(s.argmin()) - 0 - - The maximum cereal calories is the third element and the minimum - cereal calories is the first element, since series is zero-indexed. - Returns: - int: - Row position of the minimum value. - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - def get_loc( - self, key: typing.Any - ) -> typing.Union[int, slice, bigframes.series.Series]: - """ - Get integer location, slice or boolean mask for requested label. - - **Examples:** - - - >>> unique_index = bpd.Index(list('abc')) - >>> unique_index.get_loc('b') - 1 - - >>> monotonic_index = bpd.Index(list('abbc')) - >>> monotonic_index.get_loc('b') - slice(1, 3, None) - - >>> non_monotonic_index = bpd.Index(list('abcb')) - >>> non_monotonic_index.get_loc('b') - 0 False - 1 True - 2 False - 3 True - dtype: boolean - - Args: - key: Label to get the location for. - - Returns: - Union[int, slice, bigframes.pandas.Series]: - Integer position of the label for unique indexes. - Slice object for monotonic indexes with duplicates. - Boolean Series mask for non-monotonic indexes with duplicates. - - Raises: - KeyError: If the key is not found in the index. + int: Row position of the minimum value. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @@ -772,32 +149,8 @@ def argmax(self) -> int: If the maximum is achieved in multiple locations, the first row position is returned. - **Examples:** - - Consider dataset containing cereal calories - - - >>> s = bpd.Series({'Corn Flakes': 100.0, 'Almond Delight': 110.0, - ... 'Cinnamon Toast Crunch': 120.0, 'Cocoa Puff': 110.0}) - >>> s - Corn Flakes 100.0 - Almond Delight 110.0 - Cinnamon Toast Crunch 120.0 - Cocoa Puff 110.0 - dtype: Float64 - - >>> int(s.argmax()) - 2 - - >>> int(s.argmin()) - 0 - - The maximum cereal calories is the third element and the minimum - cereal calories is the first element, since series is zero-indexed. - Returns: - int: - Row position of the maximum value. + int: Row position of the maximum value. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @@ -806,33 +159,13 @@ def nunique(self) -> int: Excludes NA values by default. - **Examples:** - - - >>> s = bpd.Series([1, 3, 5, 7, 7]) - >>> s - 0 1 - 1 3 - 2 5 - 3 7 - 4 7 - dtype: Int64 - - >>> int(s.nunique()) - 4 - Returns: - int: - Number of unique elements + int """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) def sort_values( - self, - *, - ascending: bool = True, - kind: str | None = None, - na_position: str = "last", + self, *, ascending: bool = True, na_position: str = "last" ) -> Index: """ Return a sorted copy of the index. @@ -840,35 +173,15 @@ def sort_values( Return a sorted copy of the index, and optionally return the indices that sorted the index itself. - **Examples:** - - - >>> idx = bpd.Index([10, 100, 1, 1000]) - >>> idx - Index([10, 100, 1, 1000], dtype='Int64') - - Sort values in ascending order (default behavior). - - >>> idx.sort_values() - Index([1, 10, 100, 1000], dtype='Int64') - Args: ascending (bool, default True): Should the index values be sorted in an ascending order. - kind (str, default None): - Choice of sorting algorithm. Accepts 'quicksort', 'mergesort', - 'heapsort', 'stable'. Ignored except when determining whether to - sort stably. 'mergesort' or 'stable' will result in stable reorder. na_position ({'first' or 'last'}, default 'last'): Argument 'first' puts NaNs at the beginning, 'last' puts NaNs at the end. Returns: pandas.Index: Sorted copy of the index. - - Raises: - ValueError: - If ``no_position`` is not one of ``first`` or ``last``. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @@ -886,43 +199,9 @@ def value_counts( first element is the most frequently-occurring element. Excludes NA values by default. - **Examples:** - - - >>> index = bpd.Index([3, 1, 2, 3, 4, np.nan]) - >>> index.value_counts() - 3.0 2 - 1.0 1 - 2.0 1 - 4.0 1 - Name: count, dtype: Int64 - - With normalize set to True, returns the relative frequency by - dividing all values by the sum of values. - - >>> s = bpd.Series([3, 1, 2, 3, 4, np.nan]) - >>> s.value_counts(normalize=True) - 3.0 0.4 - 1.0 0.2 - 2.0 0.2 - 4.0 0.2 - Name: proportion, dtype: Float64 - - ``dropna`` - - With dropna set to False we can also see NaN index values. - - >>> s.value_counts(dropna=False) - 3.0 2 - 1.0 1 - 2.0 1 - 4.0 1 - 1 - Name: count, dtype: Int64 - Args: normalize (bool, default False): - If True, then the object returned will contain the relative + If True then the object returned will contain the relative frequencies of the unique values. sort (bool, default True): Sort by frequencies. @@ -932,29 +211,13 @@ def value_counts( Don't include counts of NaN. Returns: - bigframes.pandas.Series + Series """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) def fillna(self, value) -> Index: """ - Fill NA (NULL in BigQuery) values using the specified method. - - Note that empty strings ``''``, :attr:`numpy.inf`, and - :attr:`numpy.nan` are ***not*** considered NA values. This NA/NULL - logic differs from numpy, but it is the same as BigQuery and the - :class:`pandas.ArrowDtype`. - - **Examples:** - - >>> idx = bpd.Index( - ... pa.array([None, np.nan, 3, None], type=pa.float64()), - ... dtype=pd.ArrowDtype(pa.float64()), - ... ) - >>> idx - Index([, nan, 3.0, ], dtype='Float64') - >>> idx.fillna(0) - Index([0.0, nan, 3.0, 0.0], dtype='Float64') + Fill NA/NaN values with the specified value. Args: value (scalar): @@ -962,42 +225,23 @@ def fillna(self, value) -> Index: This value cannot be a list-likes. Returns: - bigframes.pandas.Index - - Raises: - TypeError: - MultiIndex with more than 1 level does not support ``fillna``. + Index """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - def rename(self, name, *, inplace): + def rename(self, name) -> Index: """ Alter Index or MultiIndex name. Able to set new names without level. Defaults to returning new index. Length of names must match number of levels in MultiIndex. - **Examples:** - - - >>> idx = bpd.Index(['A', 'C', 'A', 'B'], name='score') - >>> idx.rename('grade') - Index(['A', 'C', 'A', 'B'], dtype='string', name='grade') - Args: name (label or list of labels): Name(s) to set. - inplace (bool): - Default False. Modifies the object directly, instead of - creating a new Index or MultiIndex. Returns: - bigframes.pandas.Index | None: - The same type as the caller or None if ``inplace=True``. - - Raises: - ValueError: - If ``name`` is not the same length as levels. + Index: The same type as the caller. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @@ -1005,42 +249,24 @@ def drop(self, labels) -> Index: """ Make new Index with passed list of labels deleted. - **Examples:** - - - >>> idx = bpd.Index(['a', 'b', 'c']) - >>> idx.drop(['a']) - Index(['b', 'c'], dtype='string') - Args: labels (array-like or scalar): Returns: - bigframes.pandas.Index: Will be same type as self. + Index: Will be same type as self """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - def dropna(self, how: typing.Literal["all", "any"] = "any"): + def dropna(self, how: str = "any"): """Return Index without NA/NaN values. - **Examples:** - - - >>> idx = bpd.Index([1, np.nan, 3]) - >>> idx.dropna() - Index([1.0, 3.0], dtype='Float64') - Args: how ({'any', 'all'}, default 'any'): If the Index is a MultiIndex, drop the value when any or all levels are NaN. Returns: - bigframes.pandas.Index - - Raises: - ValueError: - If ``how`` is not ``any`` or ``all`` + Index """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @@ -1048,32 +274,6 @@ def drop_duplicates(self, *, keep: str = "first"): """ Return Index with duplicate values removed. - **Examples:** - - >>> import bigframes.pandas as bpd - - Generate an pandas.Index with duplicate values. - - >>> idx = bpd.Index(['lama', 'cow', 'lama', 'beetle', 'lama', 'hippo']) - - The keep parameter controls which duplicate values are removed. - The value ``first`` keeps the first occurrence for each set of - duplicated entries. The default value of keep is ``first``. - - >>> idx.drop_duplicates(keep='first') - Index(['lama', 'cow', 'beetle', 'hippo'], dtype='string') - - The value ``last`` keeps the last occurrence for each set of - duplicated entries. - - >>> idx.drop_duplicates(keep='last') - Index(['cow', 'beetle', 'lama', 'hippo'], dtype='string') - - The value ``False`` discards all sets of duplicated entries. - - >>> idx.drop_duplicates(keep=False) - Index(['cow', 'beetle', 'hippo'], dtype='string') - Args: keep ({'first', 'last', ``False``}, default 'first'): One of: @@ -1082,57 +282,17 @@ def drop_duplicates(self, *, keep: str = "first"): ``False`` : Drop all duplicates. Returns: - bigframes.pandas.Index - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - def unique(self, level: Hashable | int | None = None): - """ - Returns unique values in the index. - - **Examples:** - - >>> idx = bpd.Index([1, 1, 2, 3, 3]) - >>> idx.unique() - Index([1, 2, 3], dtype='Int64') - - Args: - level (int or hashable, optional): - Only return values from specified level (for MultiIndex). - If int, gets the level by integer position, else by level name. - - Returns: - bigframes.pandas.Index - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - def item(self, *args, **kwargs): - """Return the first element of the underlying data as a Python scalar. - - **Examples:** - - >>> s = bpd.Series([1], index=['a']) - >>> s.index.item() - 'a' - - Returns: - scalar: The first element of Index. - - Raises: - ValueError: If the data is not length = 1. + Index """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - def to_numpy(self, dtype, *, allow_large_results=None): + def to_numpy(self, dtype): """ A NumPy ndarray representing the values in this Series or Index. Args: dtype: The dtype to pass to :meth:`numpy.asarray`. - allow_large_results (bool, default None): - If not None, overrides the global setting to allow or disallow - large query results over the default size limit of 10 GB. **kwargs: Additional keywords passed through to the ``to_numpy`` method of the underlying array (for extension arrays). diff --git a/third_party/bigframes_vendored/pandas/core/indexes/datetimes.py b/third_party/bigframes_vendored/pandas/core/indexes/datetimes.py deleted file mode 100644 index f22554e174d..00000000000 --- a/third_party/bigframes_vendored/pandas/core/indexes/datetimes.py +++ /dev/null @@ -1,88 +0,0 @@ -# Contains code from https://github.com/pandas-dev/pandas/blob/main/pandas/core/indexes/datetimes.py - -from __future__ import annotations - -from bigframes_vendored import constants -from bigframes_vendored.pandas.core.indexes import base - - -class DatetimeIndex(base.Index): - """Immutable sequence used for indexing and alignment with datetime-like values""" - - @property - def year(self) -> base.Index: - """The year of the datetime - - **Examples:** - - - >>> idx = bpd.Index([pd.Timestamp("20250215")]) - >>> idx.year - Index([2025], dtype='Int64') - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - @property - def month(self) -> base.Index: - """The month as January=1, December=12. - - **Examples:** - - - >>> idx = bpd.Index([pd.Timestamp("20250215")]) - >>> idx.month - Index([2], dtype='Int64') - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - @property - def day(self) -> base.Index: - """The day of the datetime. - - **Examples:** - - - >>> idx = bpd.Index([pd.Timestamp("20250215")]) - >>> idx.day - Index([15], dtype='Int64') - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - @property - def day_of_week(self) -> base.Index: - """The day of the week with Monday=0, Sunday=6. - - **Examples:** - - - >>> idx = bpd.Index([pd.Timestamp("20250215")]) - >>> idx.day_of_week - Index([5], dtype='Int64') - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - @property - def dayofweek(self) -> base.Index: - """The day of the week with Monday=0, Sunday=6. - - **Examples:** - - - >>> idx = bpd.Index([pd.Timestamp("20250215")]) - >>> idx.dayofweek - Index([5], dtype='Int64') - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - @property - def weekday(self) -> base.Index: - """The day of the week with Monday=0, Sunday=6. - - **Examples:** - - - >>> idx = bpd.Index([pd.Timestamp("20250215")]) - >>> idx.weekday - Index([5], dtype='Int64') - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) diff --git a/third_party/bigframes_vendored/pandas/core/indexes/multi.py b/third_party/bigframes_vendored/pandas/core/indexes/multi.py deleted file mode 100644 index 018e638de35..00000000000 --- a/third_party/bigframes_vendored/pandas/core/indexes/multi.py +++ /dev/null @@ -1,84 +0,0 @@ -# Contains code from https://github.com/pandas-dev/pandas/blob/main/pandas/core/indexes/multi.py -from __future__ import annotations - -from typing import Hashable, Iterable, Sequence - -import bigframes_vendored.pandas.core.indexes.base - -from bigframes import constants - - -class MultiIndex(bigframes_vendored.pandas.core.indexes.base.Index): - """ - A multi-level, or hierarchical, index object for pandas objects. - """ - - @classmethod - def from_tuples( - cls, - tuples: Iterable[tuple[Hashable, ...]], - sortorder: int | None = None, - names: Sequence[Hashable] | Hashable | None = None, - ) -> MultiIndex: - """ - Convert list of tuples to MultiIndex. - - **Examples:** - - >>> tuples = [(1, 'red'), (1, 'blue'), - ... (2, 'red'), (2, 'blue')] - >>> bpd.MultiIndex.from_tuples(tuples, names=('number', 'color')) - MultiIndex([(1, 'red'), - (1, 'blue'), - (2, 'red'), - (2, 'blue')], - names=['number', 'color']) - - Args: - tuples (list / sequence of tuple-likes): - Each tuple is the index of one row/column. - sortorder (int or None): - Level of sortedness (must be lexicographically sorted by that - level). - names (list / sequence of str, optional): - Names for the levels in the index. - - Returns: - MultiIndex - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - @classmethod - def from_arrays( - cls, - arrays, - sortorder: int | None = None, - names=None, - ) -> MultiIndex: - """ - Convert arrays to MultiIndex. - - **Examples:** - - >>> arrays = [[1, 1, 2, 2], ['red', 'blue', 'red', 'blue']] - >>> bpd.MultiIndex.from_arrays(arrays, names=('number', 'color')) - MultiIndex([(1, 'red'), - (1, 'blue'), - (2, 'red'), - (2, 'blue')], - names=['number', 'color']) - - Args: - arrays (list / sequence of array-likes): - Each array-like gives one level's value for each data point. - len(arrays) is the number of levels. - sortorder (int or None): - Level of sortedness (must be lexicographically sorted by that - level). - names (list / sequence of str, optional): - Names for the levels in the index. - - Returns: - MultiIndex - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) diff --git a/third_party/bigframes_vendored/pandas/core/indexing.py b/third_party/bigframes_vendored/pandas/core/indexing.py index a188c7197e2..fae5d6261f2 100644 --- a/third_party/bigframes_vendored/pandas/core/indexing.py +++ b/third_party/bigframes_vendored/pandas/core/indexing.py @@ -1,6 +1,6 @@ # Contains code from https://github.com/pandas-dev/pandas/blob/main/pandas/core/indexing.py -import bigframes_vendored.constants as constants +from bigframes import constants class IndexingMixin: diff --git a/third_party/bigframes_vendored/pandas/core/reshape/concat.py b/third_party/bigframes_vendored/pandas/core/reshape/concat.py index 0a6c4153bd7..b0472c524ab 100644 --- a/third_party/bigframes_vendored/pandas/core/reshape/concat.py +++ b/third_party/bigframes_vendored/pandas/core/reshape/concat.py @@ -2,7 +2,6 @@ """ Concat routines """ - from __future__ import annotations from bigframes import constants @@ -15,7 +14,8 @@ def concat( join: str = "outer", ignore_index: bool = False, ): - """Concatenate BigQuery DataFrames objects along a particular axis. + """ + Concatenate BigQuery DataFrames objects along a particular axis. Allows optional set logic along the other axes. @@ -23,116 +23,118 @@ def concat( which may be useful if the labels are the same (or overlapping) on the passed axis number. - .. note:: - It is not recommended to build DataFrames by adding single rows in a - for loop. Build a list of rows and make a DataFrame in a single concat. - - **Examples:** - - >>> import bigframes.pandas as pd - >>> pd.options.display.progress_bar = None - + Parameters + ---------- + objs: + Objects to concatenate. Any None objects will be dropped silently unless + they are all None in which case a ValueError will be raised. + axis : {0/'index', 1/'columns'}, default 0 + The axis to concatenate along. + join: {'inner', 'outer'}, default 'outer' + How to handle indexes on other axis (or axes). + ignore_index : bool, default False + If True, do not use the index values along the concatenation axis. The + resulting axis will be labeled 0, ..., n - 1. This is useful if you are + concatenating objects where the concatenation axis does not have + meaningful indexing information. Note the index values on the other + axes are still respected in the join. + + Returns + ------- + object, type of objs + When concatenating all ``Series`` along the index (axis=0), a + ``Series`` is returned. When ``objs`` contains at least one + ``DataFrame``, a ``DataFrame`` is returned. + + Notes + ----- + It is not recommended to build DataFrames by adding single rows in a + for loop. Build a list of rows and make a DataFrame in a single concat. + + Examples + -------- Combine two ``Series``. - >>> s1 = pd.Series(['a', 'b']) - >>> s2 = pd.Series(['c', 'd']) - >>> pd.concat([s1, s2]) - 0 a - 1 b - 0 c - 1 d - dtype: string + >>> import bigframes.pandas as pd + >>> pd.options.display.progress_bar = None + >>> s1 = pd.Series(['a', 'b']) + >>> s2 = pd.Series(['c', 'd']) + >>> pd.concat([s1, s2]) + 0 a + 1 b + 0 c + 1 d + dtype: string Clear the existing index and reset it in the result by setting the ``ignore_index`` option to ``True``. - >>> pd.concat([s1, s2], ignore_index=True) - 0 a - 1 b - 2 c - 3 d - dtype: string + >>> pd.concat([s1, s2], ignore_index=True) + 0 a + 1 b + 2 c + 3 d + dtype: string Combine two ``DataFrame`` objects with identical columns. - >>> df1 = pd.DataFrame([['a', 1], ['b', 2]], - ... columns=['letter', 'number']) - >>> df1 - letter number - 0 a 1 - 1 b 2 - - [2 rows x 2 columns] - >>> df2 = pd.DataFrame([['c', 3], ['d', 4]], - ... columns=['letter', 'number']) - >>> df2 - letter number - 0 c 3 - 1 d 4 - - [2 rows x 2 columns] - >>> pd.concat([df1, df2]) - letter number - 0 a 1 - 1 b 2 - 0 c 3 - 1 d 4 - - [4 rows x 2 columns] + >>> df1 = pd.DataFrame([['a', 1], ['b', 2]], + ... columns=['letter', 'number']) + >>> df1 + letter number + 0 a 1 + 1 b 2 + + [2 rows x 2 columns] + >>> df2 = pd.DataFrame([['c', 3], ['d', 4]], + ... columns=['letter', 'number']) + >>> df2 + letter number + 0 c 3 + 1 d 4 + + [2 rows x 2 columns] + >>> pd.concat([df1, df2]) + letter number + 0 a 1 + 1 b 2 + 0 c 3 + 1 d 4 + + [4 rows x 2 columns] Combine ``DataFrame`` objects with overlapping columns and return everything. Columns outside the intersection will be filled with ``NaN`` values. - >>> df3 = pd.DataFrame([['c', 3, 'cat'], ['d', 4, 'dog']], - ... columns=['letter', 'number', 'animal']) - >>> df3 - letter number animal - 0 c 3 cat - 1 d 4 dog - - [2 rows x 3 columns] - >>> pd.concat([df1, df3]) - letter number animal - 0 a 1 - 1 b 2 - 0 c 3 cat - 1 d 4 dog - - [4 rows x 3 columns] + >>> df3 = pd.DataFrame([['c', 3, 'cat'], ['d', 4, 'dog']], + ... columns=['letter', 'number', 'animal']) + >>> df3 + letter number animal + 0 c 3 cat + 1 d 4 dog + + [2 rows x 3 columns] + >>> pd.concat([df1, df3]) + letter number animal + 0 a 1 + 1 b 2 + 0 c 3 cat + 1 d 4 dog + + [4 rows x 3 columns] Combine ``DataFrame`` objects with overlapping columns and return only those that are shared by passing ``inner`` to the ``join`` keyword argument. - >>> pd.concat([df1, df3], join="inner") - letter number - 0 a 1 - 1 b 2 - 0 c 3 - 1 d 4 - - [4 rows x 2 columns] - - Args: - objs (list of objects): - Objects to concatenate. Any None objects will be dropped silently unless - they are all None in which case a ValueError will be raised. - axis ({0 or 'index', 1 or 'columns'}, default 0): - The axis to concatenate along. - join ({'inner', 'outer'}, default 'outer'): - How to handle indexes on other axis (or axes). - ignore_index (bool, default False): - If True, do not use the index values along the concatenation axis. The - resulting axis will be labeled 0, ..., n - 1. This is useful if you are - concatenating objects where the concatenation axis does not have - meaningful indexing information. Note the index values on the other - axes are still respected in the join. - - Returns: - object, type of objs: - When concatenating all ``Series`` along the index (axis=0), a - ``Series`` is returned. When ``objs`` contains at least one - ``DataFrame``, a ``DataFrame`` is returned. + >>> pd.concat([df1, df3], join="inner") + letter number + 0 a 1 + 1 b 2 + 0 c 3 + 1 d 4 + + [4 rows x 2 columns] """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) diff --git a/third_party/bigframes_vendored/pandas/core/reshape/encoding.py b/third_party/bigframes_vendored/pandas/core/reshape/encoding.py index 8d3b26a2a2b..da92b58f505 100644 --- a/third_party/bigframes_vendored/pandas/core/reshape/encoding.py +++ b/third_party/bigframes_vendored/pandas/core/reshape/encoding.py @@ -2,7 +2,6 @@ """ Encoding routines """ - from __future__ import annotations from bigframes import constants @@ -26,7 +25,6 @@ def get_dummies( prepended to the value. **Examples:** - >>> import bigframes.pandas as pd >>> pd.options.display.progress_bar = None >>> s = pd.Series(list('abca')) @@ -114,9 +112,8 @@ def get_dummies( Data type for new columns. Only a single dtype is allowed. Returns: - bigframes.pandas.DataFrame: - Dummy-coded data. If data contains other columns than the - dummy-coded one(s), these will be prepended, unaltered, to the - result. + DataFrame: Dummy-coded data. If data contains other columns than the + dummy-coded one(s), these will be prepended, unaltered, to the + result. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) diff --git a/third_party/bigframes_vendored/pandas/core/reshape/merge.py b/third_party/bigframes_vendored/pandas/core/reshape/merge.py index 448b21819c0..b03f366fca7 100644 --- a/third_party/bigframes_vendored/pandas/core/reshape/merge.py +++ b/third_party/bigframes_vendored/pandas/core/reshape/merge.py @@ -2,7 +2,6 @@ """ SQL-style merge routines """ - from __future__ import annotations @@ -14,8 +13,6 @@ def merge( *, left_on=None, right_on=None, - left_index: bool = False, - right_index: bool = False, sort=False, suffixes=("_x", "_y"), ): @@ -52,8 +49,6 @@ def merge( join; sort keys lexicographically. ``inner``: use intersection of keys from both frames, similar to a SQL inner join; preserve the order of the left keys. - ``cross``: creates the cartesian product from both frames, preserves the order - of the left keys. on (label or list of labels): Columns to join on. It must be found in both DataFrames. Either on or left_on + right_on @@ -64,10 +59,6 @@ def merge( right_on (label or list of labels): Columns to join on in the right DataFrame. Either on or left_on + right_on must be passed in. - left_index (bool, default False): - Use the index from the left DataFrame as the join key. - right_index (bool, default False): - Use the index from the right DataFrame as the join key. sort: Default False. Sort the join keys lexicographically in the result DataFrame. If False, the order of the join keys depends @@ -81,7 +72,6 @@ def merge( no suffix. At least one of the values must not be None. Returns: - bigframes.pandas.DataFrame: - A DataFrame of the two merged objects. + bigframes.dataframe.DataFrame: A DataFrame of the two merged objects. """ raise NotImplementedError("abstract method") diff --git a/third_party/bigframes_vendored/pandas/core/reshape/pivot.py b/third_party/bigframes_vendored/pandas/core/reshape/pivot.py deleted file mode 100644 index 8cc33525a4b..00000000000 --- a/third_party/bigframes_vendored/pandas/core/reshape/pivot.py +++ /dev/null @@ -1,57 +0,0 @@ -# Contains code from https://github.com/pandas-dev/pandas/blob/main/pandas/core/reshape/pivot.py -from __future__ import annotations - -from bigframes import constants - - -def crosstab( - index, - columns, - values=None, - rownames=None, - colnames=None, - aggfunc=None, -): - """ - Compute a simple cross tabulation of two (or more) factors. - - By default, computes a frequency table of the factors unless an - array of values and an aggregation function are passed. - - **Examples:** - >>> a = np.array(["foo", "foo", "foo", "foo", "bar", "bar", - ... "bar", "bar", "foo", "foo", "foo"], dtype=object) - >>> b = np.array(["one", "one", "one", "two", "one", "one", - ... "one", "two", "two", "two", "one"], dtype=object) - >>> c = np.array(["dull", "dull", "shiny", "dull", "dull", "shiny", - ... "shiny", "dull", "shiny", "shiny", "shiny"], - ... dtype=object) - >>> bpd.crosstab(a, [b, c], rownames=['a'], colnames=['b', 'c']) - b one two - c dull shiny dull shiny - a - bar 1 2 1 0 - foo 2 2 1 2 - - [2 rows x 4 columns] - - Args: - index (array-like, Series, or list of arrays/Series): - Values to group by in the rows. - columns (array-like, Series, or list of arrays/Series): - Values to group by in the columns. - values (array-like, optional): - Array of values to aggregate according to the factors. - Requires `aggfunc` be specified. - rownames (sequence, default None): - If passed, must match number of row arrays passed. - colnames (sequence, default None): - If passed, must match number of column arrays passed. - aggfunc (function, optional): - If specified, requires `values` be specified as well. - - Returns: - DataFrame: - Cross tabulation of the data. - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) diff --git a/third_party/bigframes_vendored/pandas/core/reshape/tile.py b/third_party/bigframes_vendored/pandas/core/reshape/tile.py index 546a65b73c2..d4471ed68e4 100644 --- a/third_party/bigframes_vendored/pandas/core/reshape/tile.py +++ b/third_party/bigframes_vendored/pandas/core/reshape/tile.py @@ -2,26 +2,16 @@ """ Quantilization functions and related routines """ - from __future__ import annotations -import typing - -import pandas as pd - from bigframes import constants def cut( x, - bins: typing.Union[ - int, - pd.IntervalIndex, - typing.Iterable, - ], + bins, *, - right: bool = True, - labels: typing.Union[typing.Iterable[str], bool, None] = None, + labels=None, ): """ Bin values into discrete intervals. @@ -32,114 +22,42 @@ def cut( age ranges. Supports binning into an equal number of bins, or a pre-specified array of bins. - **Examples:** - - >>> import bigframes.pandas as bpd - >>> s = bpd.Series([0, 1, 5, 10]) - >>> s - 0 0 - 1 1 - 2 5 - 3 10 - dtype: Int64 - - Cut with an integer (equal-width bins): + ``labels=False`` implies you just want the bins back. - >>> bpd.cut(s, bins=4) - 0 {'left_exclusive': -0.01, 'right_inclusive': 2.5} - 1 {'left_exclusive': -0.01, 'right_inclusive': 2.5} - 2 {'left_exclusive': 2.5, 'right_inclusive': 5.0} - 3 {'left_exclusive': 7.5, 'right_inclusive': 10.0} - dtype: struct[pyarrow] + Examples: - Cut with the same bins, but assign them specific labels: + .. code-block:: - >>> bpd.cut(s, bins=3, labels=["bad", "medium", "good"]) - 0 bad - 1 bad - 2 medium - 3 good - dtype: string + import bigframes.pandas as pd - `labels=False` implies you want the bins back. + pd.options.display.progress_bar = None + s = pd.Series([0, 1, 1, 2]) + pd.cut(s, bins=4, labels=False) - >>> bpd.cut(s, bins=4, labels=False) 0 0 - 1 0 + 1 1 2 1 3 3 dtype: Int64 - Cut with pd.IntervalIndex, requires importing pandas for IntervalIndex: - - >>> interval_index = pd.IntervalIndex.from_tuples([(0, 1), (1, 5), (5, 20)]) - >>> bpd.cut(s, bins=interval_index) - 0 - 1 {'left_exclusive': 0, 'right_inclusive': 1} - 2 {'left_exclusive': 1, 'right_inclusive': 5} - 3 {'left_exclusive': 5, 'right_inclusive': 20} - dtype: struct[pyarrow] - - Cut with an iterable of tuples: - - >>> bins_tuples = [(0, 1), (1, 4), (5, 20)] - >>> bpd.cut(s, bins=bins_tuples) - 0 - 1 {'left_exclusive': 0, 'right_inclusive': 1} - 2 - 3 {'left_exclusive': 5, 'right_inclusive': 20} - dtype: struct[pyarrow] - - Cut with an iterable of ints: - - >>> bins_ints = [0, 1, 5, 20] - >>> bpd.cut(s, bins=bins_ints) - 0 - 1 {'left_exclusive': 0, 'right_inclusive': 1} - 2 {'left_exclusive': 1, 'right_inclusive': 5} - 3 {'left_exclusive': 5, 'right_inclusive': 20} - dtype: struct[pyarrow] - - Cut with an interable of ints, where intervals are left-inclusive and right-exclusive. - - >>> bins_ints = [0, 1, 5, 20] - >>> bpd.cut(s, bins=bins_ints, right=False) - 0 {'left_inclusive': 0, 'right_exclusive': 1} - 1 {'left_inclusive': 1, 'right_exclusive': 5} - 2 {'left_inclusive': 5, 'right_exclusive': 20} - 3 {'left_inclusive': 5, 'right_exclusive': 20} - dtype: struct[pyarrow] - Args: - x (array-like): + x (Series): The input Series to be binned. Must be 1-dimensional. - bins (int, pd.IntervalIndex, Iterable): + bins (int): The criteria to bin by. - int: Defines the number of equal-width bins in the range of `x`. The + int : Defines the number of equal-width bins in the range of `x`. The range of `x` is extended by .1% on each side to include the minimum and maximum values of `x`. - - pd.IntervalIndex or Iterable of tuples: Defines the exact bins to be used. - It's important to ensure that these bins are non-overlapping. - - Iterable of numerics: Defines the exact bins by using the interval - between each item and its following item. The items must be monotonically - increasing. - right (bool, default True): - Indicates whether `bins` includes the rightmost edge or not. If - ``right == True`` (the default), then the `bins` ``[1, 2, 3, 4]`` - indicate (1,2], (2,3], (3,4]. This argument is ignored when - `bins` is an IntervalIndex. - labels (bool, Iterable, default None): + labels (None): Specifies the labels for the returned bins. Must be the same length as the resulting bins. If False, returns only integer indicators of the - bins. This affects the type of the output container. This argument is - ignored when `bins` is an IntervalIndex. If True, raises an error. + bins. This affects the type of the output container (see below). + If True, raises an error. When `ordered=False`, labels must be + provided. Returns: - bigframes.pandas.Series: - A Series representing the respective bin for each value + Series: A Series representing the respective bin for each value of `x`. The type depends on the value of `labels`. sequence of scalars : returns a Series for Series `x` or a Categorical for all other inputs. The values stored within @@ -171,8 +89,7 @@ def qcut(x, q, *, labels=None, duplicates="error"): If bin edges are not unique, raise ValueError or drop non-uniques. Returns: - bigframes.pandas.Series: - Categorical or Series of integers if labels is False + Series: Categorical or Series of integers if labels is False The return type (Categorical or Series) depends on the input: a Series of type category if input is a Series else Categorical. Bins are represented as categories when categorical data is returned. diff --git a/third_party/bigframes_vendored/pandas/core/series.py b/third_party/bigframes_vendored/pandas/core/series.py index 183f36ef5a4..b569e5699c7 100644 --- a/third_party/bigframes_vendored/pandas/core/series.py +++ b/third_party/bigframes_vendored/pandas/core/series.py @@ -1,89 +1,28 @@ """ Data structure for 1-dimensional cross-sectional and time series data """ - from __future__ import annotations -import datetime -from typing import ( - IO, - TYPE_CHECKING, - Hashable, - List, - Literal, - Mapping, - Optional, - Sequence, - Tuple, - Union, -) - -import numpy +from typing import Hashable, IO, Literal, Mapping, Sequence, TYPE_CHECKING + import numpy as np -import pandas as pd -from bigframes_vendored.pandas.core.generic import NDFrame +from pandas._libs import lib from pandas._typing import Axis, FilePath, NaPosition, WriteBuffer -from pandas.api import extensions as pd_ext from bigframes import constants +from third_party.bigframes_vendored.pandas.core.generic import NDFrame if TYPE_CHECKING: - from bigframes_vendored.pandas.core.frame import DataFrame - from bigframes_vendored.pandas.core.groupby import SeriesGroupBy + from third_party.bigframes_vendored.pandas.core.frame import DataFrame + from third_party.bigframes_vendored.pandas.core.groupby import SeriesGroupBy class Series(NDFrame): # type: ignore[misc] - """ - One-dimensional ndarray with axis labels (including time series). - """ - @property def dt(self): """ Accessor object for datetime-like properties of the Series values. - **Examples:** - - >>> import bigframes.pandas as bpd - >>> seconds_series = bpd.Series(pd.date_range("2000-01-01", periods=3, freq="s")) - >>> seconds_series - 0 2000-01-01 00:00:00 - 1 2000-01-01 00:00:01 - 2 2000-01-01 00:00:02 - dtype: timestamp[us][pyarrow] - - >>> seconds_series.dt.second - 0 0 - 1 1 - 2 2 - dtype: Int64 - - >>> hours_series = bpd.Series(pd.date_range("2000-01-01", periods=3, freq="h")) - >>> hours_series - 0 2000-01-01 00:00:00 - 1 2000-01-01 01:00:00 - 2 2000-01-01 02:00:00 - dtype: timestamp[us][pyarrow] - - >>> hours_series.dt.hour - 0 0 - 1 1 - 2 2 - dtype: Int64 - - >>> quarters_series = bpd.Series(pd.date_range("2000-01-01", periods=3, freq="QE")) - >>> quarters_series - 0 2000-03-31 00:00:00 - 1 2000-06-30 00:00:00 - 2 2000-09-30 00:00:00 - dtype: timestamp[us][pyarrow] - - >>> quarters_series.dt.quarter - 0 1 - 1 2 - 2 3 - dtype: Int64 - Returns: bigframes.operations.datetimes.DatetimeMethods: An accessor containing datetime methods. @@ -105,85 +44,25 @@ def struct(self): @property def index(self): - """The index (axis labels) of the Series. - - The index of a Series is used to label and identify each element of the - underlying data. The index can be thought of as an immutable ordered set - (technically a multi-set, as it may contain duplicate labels), and is - used to index and align data. - - **Examples:** - - - You can access the index of a Series via ``index`` property. - - >>> df = bpd.DataFrame({'Name': ['Alice', 'Bob', 'Aritra'], - ... 'Age': [25, 30, 35], - ... 'Location': ['Seattle', 'New York', 'Kona']}, - ... index=([10, 20, 30])) - >>> s = df["Age"] - >>> s - 10 25 - 20 30 - 30 35 - Name: Age, dtype: Int64 - >>> s.index # doctest: +ELLIPSIS - Index([10, 20, 30], dtype='Int64') - >>> s.index.values - array([10, 20, 30]) - - Let's try setting a multi-index case reflect via ``index`` property. - - >>> df1 = df.set_index(["Name", "Location"]) - >>> s1 = df1["Age"] - >>> s1 - Name Location - Alice Seattle 25 - Bob New York 30 - Aritra Kona 35 - Name: Age, dtype: Int64 - >>> s1.index # doctest: +ELLIPSIS - MultiIndex([( 'Alice', 'Seattle'), - ( 'Bob', 'New York'), - ('Aritra', 'Kona')], - names=['Name', 'Location']) - >>> s1.index.values - array([('Alice', 'Seattle'), ('Bob', 'New York'), ('Aritra', 'Kona')], - dtype=object) - - Returns: - Index: - The index object of the Series. - """ + """The index (axis labels) of the Series.""" raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @property def shape(self): - """Return a tuple of the shape of the underlying data. - - **Examples:** - - - >>> s = bpd.Series([1, 4, 9, 16]) - >>> s.shape - (4,) - >>> s = bpd.Series(['Alice', 'Bob', pd.NA]) - >>> s.shape - (3,) - """ + """Return a tuple of the shape of the underlying data.""" raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @property def dtype(self): """ Return the dtype object of the underlying data. + """ + raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - **Examples:** - - - >>> s = bpd.Series([1, 2, 3]) - >>> s.dtype - Int64Dtype() + @property + def dtypes(self): + """ + Return the dtype object of the underlying data. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @@ -196,127 +75,31 @@ def name(self) -> Hashable: to form a DataFrame. It is also used whenever displaying the Series using the interpreter. - **Examples:** - - - For a Series: - - >>> s = bpd.Series([1, 2, 3], dtype="Int64", name='Numbers') - >>> s - 0 1 - 1 2 - 2 3 - Name: Numbers, dtype: Int64 - >>> s.name - 'Numbers' - - >>> s.name = "Integers" - >>> s - 0 1 - 1 2 - 2 3 - Name: Integers, dtype: Int64 - - If the Series is part of a DataFrame: - - >>> df = bpd.DataFrame({'col1': [1, 2], 'col2': [3, 4]}) - >>> df - col1 col2 - 0 1 3 - 1 2 4 - - [2 rows x 2 columns] - >>> s = df["col1"] - >>> s.name - 'col1' - Returns: - hashable object: - The name of the Series, also the column name + hashable object: The name of the Series, also the column name if part of a DataFrame. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - @property - def hasnans(self) -> bool: - """ - Return True if there are any NaNs. - - **Examples:** - - - >>> s = bpd.Series([1, 2, 3, None]) - >>> s - 0 1.0 - 1 2.0 - 2 3.0 - 3 - dtype: Float64 - >>> s.hasnans - np.True_ - - Returns: - bool - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - @property def T(self) -> Series: - """Return the transpose, which is by definition self. - - **Examples:** - - - >>> s = bpd.Series(['Ant', 'Bear', 'Cow']) - >>> s - 0 Ant - 1 Bear - 2 Cow - dtype: string - - >>> s.T - 0 Ant - 1 Bear - 2 Cow - dtype: string - - """ + """Return the transpose, which is by definition self.""" raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) def transpose(self) -> Series: """ Return the transpose, which is by definition self. - **Examples:** - - - >>> s = bpd.Series(['Ant', 'Bear', 'Cow']) - >>> s - 0 Ant - 1 Bear - 2 Cow - dtype: string - - >>> s.transpose() - 0 Ant - 1 Bear - 2 Cow - dtype: string - Returns: - bigframes.pandas.Series: - Series. + Series """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) def reset_index( self, - level=None, *, drop: bool = False, - name=pd_ext.no_default, - inplace: bool = False, - allow_duplicates: Optional[bool] = None, + name=lib.no_default, ) -> DataFrame | Series | None: """ Generate a new DataFrame or Series with the index reset. @@ -325,73 +108,7 @@ def reset_index( when the index is meaningless and needs to be reset to the default before another operation. - **Examples:** - - - >>> s = bpd.Series([1, 2, 3, 4], name='foo', - ... index=['a', 'b', 'c', 'd']) - >>> s.index.name = "idx" - >>> s - idx - a 1 - b 2 - c 3 - d 4 - Name: foo, dtype: Int64 - - Generate a DataFrame with default index. - - >>> s.reset_index() - idx foo - 0 a 1 - 1 b 2 - 2 c 3 - 3 d 4 - - [4 rows x 2 columns] - - To specify the name of the new column use ``name`` param. - - >>> s.reset_index(name="bar") - idx bar - 0 a 1 - 1 b 2 - 2 c 3 - 3 d 4 - - [4 rows x 2 columns] - - To generate a new Series with the default index set param ``drop=True``. - - >>> s.reset_index(drop=True) - 0 1 - 1 2 - 2 3 - 3 4 - Name: foo, dtype: Int64 - - >>> arrays = [np.array(['bar', 'bar', 'baz', 'baz']), - ... np.array(['one', 'two', 'one', 'two'])] - >>> s2 = bpd.Series( - ... range(4), name='foo', - ... index=pd.MultiIndex.from_arrays(arrays, - ... names=['a', 'b'])) - - If level is not set, all levels are removed from the Index. - - >>> s2.reset_index() - a b foo - 0 bar one 0 - 1 bar two 1 - 2 baz one 2 - 3 baz two 3 - - [4 rows x 3 columns] - Args: - level (int, str, tuple, or list, default optional): - For a Series with a MultiIndex, only remove the specified levels - from the index. Removes all levels by default. drop (bool, default False): Just reset the index, without inserting it as a column in the new DataFrame. @@ -399,14 +116,9 @@ def reset_index( The name to use for the column containing the original Series values. Uses ``self.name`` by default. This argument is ignored when `drop` is True. - inplace (bool, default False): - Modify the Series in place (do not create a new object). - allow_duplicates (bool, optional, default None): - Allow duplicate column labels to be created. Returns: - bigframes.pandas.Series or bigframes.pandas.DataFrame or None: - When `drop` is False (the default), + Series or DataFrame or None; When `drop` is False (the default), a DataFrame is returned. The newly created columns will come first in the DataFrame, followed by the original Series values. When `drop` is True, a `Series` is returned. @@ -421,23 +133,6 @@ def __repr__(self) -> str: """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - def keys(self): - """ - Return alias for index. - - **Examples:** - - - >>> s = bpd.Series([1, 2, 3], index=[0, 1, 2]) - >>> s.keys() - Index([0, 1, 2], dtype='Int64') - - Returns: - Index: - Index of the Series. - """ - return self.index - # ---------------------------------------------------------------------- # IO methods (to / from other formats) @@ -453,8 +148,6 @@ def to_string( name: bool = False, max_rows: int | None = None, min_rows: int | None = None, - *, - allow_large_results: Optional[bool] = None, ) -> str | None: """ Render a string representation of the Series. @@ -483,13 +176,10 @@ def to_string( min_rows (int, optional): The number of rows to display in a truncated repr (when number of rows is above `max_rows`). - allow_large_results (bool, default None): - If not None, overrides the global setting to allow or disallow large - query results over the default size limit of 10 GB. Returns: - str or None: - String representation of Series if ``buf=None``, otherwise None. + str or None: String representation of Series if ``buf=None``, + otherwise None. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @@ -498,137 +188,37 @@ def to_markdown( buf: IO[str] | None = None, mode: str = "wt", index: bool = True, - *, - allow_large_results: Optional[bool] = None, **kwargs, ) -> str | None: """ - Print Series in Markdown-friendly format. - - **Examples:** - - - >>> s = bpd.Series(["elk", "pig", "dog", "quetzal"], name="animal") - >>> print(s.to_markdown()) - | | animal | - |---:|:---------| - | 0 | elk | - | 1 | pig | - | 2 | dog | - | 3 | quetzal | - - Output markdown with a tabulate option. - - >>> print(s.to_markdown(tablefmt="grid")) - +----+----------+ - | | animal | - +====+==========+ - | 0 | elk | - +----+----------+ - | 1 | pig | - +----+----------+ - | 2 | dog | - +----+----------+ - | 3 | quetzal | - +----+----------+ + Print {klass} in Markdown-friendly format. Args: buf (str, Path or StringIO-like, optional, default None): Buffer to write to. If None, the output is returned as a string. mode (str, optional): Mode in which file is opened, "wt" by default. - allow_large_results (bool, default None): - If not None, overrides the global setting to allow or disallow - large query results over the default size limit of 10 GB. index (bool, optional, default True): Add index (row) labels. Returns: - str: - Series in Markdown-friendly format. - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - def to_csv( - self, - path_or_buf=None, - sep=",", - *, - header: bool = True, - index: bool = True, - allow_large_results: Optional[bool] = None, - ) -> Optional[str]: - """ - Write object to a comma-separated values (csv) file. - - **Examples:** - - >>> import bigframes.pandas as bpd - - >>> s = bpd.Series([1,2,3], name='my_series') - >>> s.to_csv() - \',my_series\\n0,1\\n1,2\\n2,3\\n\' - - Args: - path_or_buf (str, path object, file-like object, or None, default None): - String, path object (implementing os.PathLike[str]), or file-like object - implementing a write() function. If None, the result is returned as a string. - If a non-binary file object is passed, it should be opened with newline='', - disabling universal newlines. If a binary file object is passed, - mode might need to contain a 'b'. - Must contain a wildcard character '*' if this is a GCS path. - sep (str, default ','): - String of length 1. Field delimiter for the output file. - header (bool, default True): - Write out the column names. - index (bool, default True): - Write row names (index). - allow_large_results (bool, default None): - If not None, overrides the global setting to allow or disallow large - query results over the default size limit of 10 GB. - - Returns: - If path_or_buf is None, returns the resulting csv format as a string. Otherwise returns None. + str: {klass} in Markdown-friendly format. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - def to_dict( - self, - into: type[dict] = dict, - *, - allow_large_results: Optional[bool] = None, - ) -> Mapping: + def to_dict(self, into: type[dict] = dict) -> Mapping: """ Convert Series to {label -> value} dict or dict-like object. - **Examples:** - - >>> from collections import OrderedDict, defaultdict - - >>> s = bpd.Series([1, 2, 3, 4]) - >>> s.to_dict() - {np.int64(0): 1, np.int64(1): 2, np.int64(2): 3, np.int64(3): 4} - - >>> s.to_dict(into=OrderedDict) - OrderedDict({np.int64(0): 1, np.int64(1): 2, np.int64(2): 3, np.int64(3): 4}) - - >>> dd = defaultdict(list) - >>> s.to_dict(into=dd) - defaultdict(, {np.int64(0): 1, np.int64(1): 2, np.int64(2): 3, np.int64(3): 4}) - Args: into (class, default dict): The collections.abc.Mapping subclass to use as the return object. Can be the actual class or an empty instance of the mapping type you want. If you want a collections.defaultdict, you must pass it initialized. - allow_large_results (bool, default None): - If not None, overrides the global setting to allow or disallow large - query results over the default size limit of 10 GB. Returns: - collections.abc.Mapping: - Key-value representation of Series. + collections.abc.Mapping: Key-value representation of Series. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @@ -639,35 +229,15 @@ def to_frame(self, name=None) -> DataFrame: The column in the new dataframe will be named name (the keyword parameter) if the name parameter is provided and not None. - **Examples:** - - - >>> s = bpd.Series(["a", "b", "c"], - ... name="vals") - >>> s.to_frame() - vals - 0 a - 1 b - 2 c - - [3 rows x 1 columns] - Args: name (Hashable, default None) Returns: - bigframes.pandas.DataFrame: - DataFrame representation of Series. + bigframes.dataframe.DataFrame: DataFrame representation of Series. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - def to_excel( - self, - excel_writer, - sheet_name, - *, - allow_large_results=None, - ): + def to_excel(self, excel_writer, sheet_name): """ Write Series to an Excel sheet. @@ -686,22 +256,10 @@ def to_excel( File path or existing ExcelWriter. sheet_name (str, default 'Sheet1'): Name of sheet to contain Series. - allow_large_results (bool, default None): - If not None, overrides the global setting to allow or disallow large - query results over the default size limit of 10 GB. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - def to_latex( - self, - buf=None, - columns=None, - header=True, - index=True, - *, - allow_large_results=None, - **kwargs, - ): + def to_latex(self, buf=None, columns=None, header=True, index=True, **kwargs): """ Render object to a LaTeX tabular, longtable, or nested table. @@ -715,18 +273,14 @@ def to_latex( it is assumed to be aliases for the column names. index (bool, default True): Write row names (index). - allow_large_results (bool, default None): - If not None, overrides the global setting to allow or disallow large - query results over the default size limit of 10 GB. Returns: - str or None: - If buf is None, returns the result as a string. + str or None: If buf is None, returns the result as a string. Otherwise returns None. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - def tolist(self, *, allow_large_results: Optional[bool] = None) -> list: + def tolist(self) -> list: """ Return a list of the values. @@ -734,61 +288,17 @@ def tolist(self, *, allow_large_results: Optional[bool] = None) -> list: (for str, int, float) or a pandas scalar (for Timestamp/Timedelta/Interval/Period). - **Examples:** - - - >>> s = bpd.Series([1, 2, 3]) - >>> s - 0 1 - 1 2 - 2 3 - dtype: Int64 - - >>> s.to_list() - [1, 2, 3] - - Args: - allow_large_results (bool, default None): - If not None, overrides the global setting to allow or disallow - large query results over the default size limit of 10 GB. - Returns: - list: - list of the values. + list: list of the values """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) to_list = tolist - def to_numpy( - self, dtype, copy=False, na_value=pd_ext.no_default, *, allow_large_results=None - ): + def to_numpy(self, dtype, copy=False, na_value=None): """ A NumPy ndarray representing the values in this Series or Index. - **Examples:** - - - >>> ser = bpd.Series(pd.Categorical(['a', 'b', 'a'])) - >>> ser.to_numpy() - array(['a', 'b', 'a'], dtype=object) - - Specify the dtype to control how datetime-aware data is represented. Use - dtype=object to return an ndarray of pandas Timestamp objects, each with - the correct tz. - - >>> ser = bpd.Series(pd.date_range('2000', periods=2, tz="CET")) - >>> ser.to_numpy(dtype=object) - array([Timestamp('1999-12-31 23:00:00+0000', tz='UTC'), - Timestamp('2000-01-01 23:00:00+0000', tz='UTC')], dtype=object) - - Or ``dtype=datetime64[ns]`` to return an ndarray of native datetime64 values. - The values are converted to UTC and the timezone info is dropped. - - >>> ser.to_numpy(dtype="datetime64[ns]") - array(['1999-12-31T23:00:00.000000000', '2000-01-01T23:00:00.000000000'], - dtype='datetime64[ns]') - Args: dtype (str or numpy.dtype, optional): The dtype to pass to :meth:`numpy.asarray`. @@ -800,102 +310,95 @@ def to_numpy( na_value (Any, optional): The value to use for missing values. The default value depends on `dtype` and the type of the array. - allow_large_results (bool, default None): - If not None, overrides the global setting to allow or disallow - large query results over the default size limit of 10 GB. ``**kwargs``: Additional keywords passed through to the ``to_numpy`` method of the underlying array (for extension arrays). Returns: - numpy.ndarray: - A NumPy ndarray representing the values in this + numpy.ndarray: A NumPy ndarray representing the values in this Series or Index. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - def to_pickle(self, path, *, allow_large_results=None, **kwargs): + def to_pickle(self, path, **kwargs): """ Pickle (serialize) object to file. - **Examples:** - - - >>> original_df = bpd.DataFrame({"foo": range(5), "bar": range(5, 10)}) - >>> original_df - foo bar - 0 0 5 - 1 1 6 - 2 2 7 - 3 3 8 - 4 4 9 - - [5 rows x 2 columns] - - >>> original_df.to_pickle("./dummy.pkl") - - >>> unpickled_df = bpd.read_pickle("./dummy.pkl") - >>> unpickled_df - foo bar - 0 0 5 - 1 1 6 - 2 2 7 - 3 3 8 - 4 4 9 - - [5 rows x 2 columns] - Args: path (str, path object, or file-like object): String, path object (implementing ``os.PathLike[str]``), or file-like object implementing a binary ``write()`` function. File path where the pickled object will be stored. - allow_large_results (bool, default None): - If not None, overrides the global setting to allow or disallow - large query results over the default size limit of 10 GB. - - Returns: - None """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - def to_xarray(self, *, allow_large_results=None): + def to_xarray(self): """ Return an xarray object from the pandas object. Returns: - xarray.DataArray or xarray.Dataset: - Data in the pandas structure + xarray.DataArray or xarray.Dataset: Data in the pandas structure converted to Dataset if the object is a DataFrame, or a DataArray if the object is a Series. - allow_large_results (bool, default None): - If not None, overrides the global setting to allow or disallow large - query results over the default size limit of 10 GB. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - def agg(self, func): + def to_json( + self, + path_or_buf=None, + orient: Literal[ + "split", "records", "index", "columns", "values", "table" + ] = "columns", + **kwarg, + ) -> str | None: """ - Aggregate using one or more operations over the specified axis. + Convert the object to a JSON string. - **Examples:** + Note NaN's and None will be converted to null and datetime objects + will be converted to UNIX timestamps. + + Args: + path_or_buf (str, path object, file-like object, or None, default None): + String, path object (implementing os.PathLike[str]), or file-like + object implementing a write() function. If None, the result is + returned as a string. + orient ({"split", "records", "index", "columns", "values", "table"}, default "columns"): + Indication of expected JSON string format. + 'split' : dict like {{'index' -> [index], 'columns' -> [columns],'data' -> [values]}} + 'records' : list like [{{column -> value}}, ... , {{column -> value}}] + 'index' : dict like {{index -> {{column -> value}}}} + 'columns' : dict like {{column -> {{index -> value}}}} + 'values' : just the values array + 'table' : dict like {{'schema': {{schema}}, 'data': {{data}}}} + Describing the data, where data component is like ``orient='records'``. + Returns: + None or str: If path_or_buf is None, returns the resulting json format as a + string. Otherwise returns None. + """ + raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) + + def to_csv(self, path_or_buf: str, *, index: bool = True) -> str | None: + """ + Write object to a comma-separated values (csv) file. - >>> s = bpd.Series([1, 2, 3, 4]) - >>> s - 0 1 - 1 2 - 2 3 - 3 4 - dtype: Int64 + Args: + path_or_buf (str, path object, file-like object, or None, default None): + String, path object (implementing os.PathLike[str]), or file-like + object implementing a write() function. If None, the result is + returned as a string. If a non-binary file object is passed, it should + be opened with `newline=''`, disabling universal newlines. If a binary + file object is passed, `mode` might need to contain a `'b'`. - >>> s.agg('min') - np.int64(1) + Returns: + None or str: If path_or_buf is None, returns the resulting csv format + as a string. Otherwise returns None. + """ + raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - >>> s.agg(['min', 'max']) - min 1 - max 4 - dtype: Int64 + def agg(self, func): + """ + Aggregate using one or more operations over the specified axis. Args: func (function): @@ -904,8 +407,7 @@ def agg(self, func): function names, e.g. ``['sum', 'mean']``. Returns: - scalar or bigframes.pandas.Series: - Aggregated results. + scalar or Series: Aggregated results """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @@ -913,21 +415,9 @@ def count(self): """ Return number of non-NA/null observations in the Series. - **Examples:** - - - >>> s = bpd.Series([0.0, 1.0, pd.NA]) - >>> s - 0 0.0 - 1 1.0 - 2 - dtype: Float64 - >>> s.count() - np.int64(2) - Returns: - int or bigframes.pandas.Series (if level specified): - Number of non-null values in the Series. + int or Series (if level specified): Number of non-null values in the + Series. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @@ -937,69 +427,8 @@ def nunique(self) -> int: Excludes NA values by default. - **Examples:** - - - >>> s = bpd.Series([1, 3, 5, 7, 7]) - >>> s - 0 1 - 1 3 - 2 5 - 3 7 - 4 7 - dtype: Int64 - - >>> s.nunique() - np.int64(4) - - Returns: - int: - number of unique elements in the object. - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - def unique(self, keep_order=True) -> Series: - """ - Return unique values of Series object. - - By default, uniques are returned in order of appearance. Hash table-based unique, - therefore does NOT sort. - - Args: - keep_order (bool, default True): - If True, preserves the order of the first appearance of each unique value. - If False, returns the elements in ascending order, which can be faster. - - **Examples:** - - - >>> s = bpd.Series([2, 1, 3, 3], name='A') - >>> s - 0 2 - 1 1 - 2 3 - 3 3 - Name: A, dtype: Int64 - - Example with order preservation: Slower, but keeps order - - >>> s.unique() - 0 2 - 1 1 - 2 3 - Name: A, dtype: Int64 - - Example without order preservation: Faster, but loses original order - - >>> s.unique(keep_order=False) - 0 1 - 1 2 - 2 3 - Name: A, dtype: Int64 - Returns: - bigframes.pandas.Series: - The unique values returned as a Series. + int: number of unique elements in the object. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @@ -1011,18 +440,8 @@ def mode(self) -> Series: Always returns Series even if only one value is returned. - **Examples:** - - - >>> s = bpd.Series([2, 4, 8, 2, 4, None]) - >>> s.mode() - 0 2.0 - 1 4.0 - dtype: Float64 - Returns: - bigframes.pandas.Series: - Modes of the Series in sorted order. + bigframes.series.Series: Modes of the Series in sorted order. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @@ -1034,51 +453,6 @@ def drop_duplicates( """ Return Series with duplicate values removed. - **Examples:** - - Generate a Series with duplicated entries. - - >>> import bigframes.pandas as bpd - >>> s = bpd.Series(['llama', 'cow', 'llama', 'beetle', 'llama', 'hippo'], - ... name='animal') - >>> s - 0 llama - 1 cow - 2 llama - 3 beetle - 4 llama - 5 hippo - Name: animal, dtype: string - - With the 'keep' parameter, the selection behaviour of duplicated values - can be changed. The value 'first' keeps the first occurrence for each set - of duplicated entries. The default value of keep is 'first'. - - >>> s.drop_duplicates() - 0 llama - 1 cow - 3 beetle - 5 hippo - Name: animal, dtype: string - - The value 'last' for parameter 'keep' keeps the last occurrence for - each set of duplicated entries. - - >>> s.drop_duplicates(keep='last') - 1 cow - 3 beetle - 4 llama - 5 hippo - Name: animal, dtype: string - - The value False for parameter 'keep' discards all sets of duplicated entries. - - >>> s.drop_duplicates(keep=False) - 1 cow - 3 beetle - 5 hippo - Name: animal, dtype: string - Args: keep ({'first', 'last', ``False``}, default 'first'): Method to handle dropping duplicates: @@ -1088,8 +462,7 @@ def drop_duplicates( ``False`` : Drop all duplicates. Returns: - bigframes.pandas.Series: - Series with duplicates dropped or None if ``inplace=True``. + bigframes.series.Series: Series with duplicates dropped or None if ``inplace=True``. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @@ -1101,53 +474,6 @@ def duplicated(self, keep="first") -> Series: Series. Either all duplicates, all except the first or all except the last occurrence of duplicates can be indicated. - **Examples:** - - >>> import bigframes.pandas as bpd - - By default, for each set of duplicated values, the first occurrence is - set on False and all others on True: - - >>> animals = bpd.Series(['llama', 'cow', 'llama', 'beetle', 'llama']) - >>> animals.duplicated() - 0 False - 1 False - 2 True - 3 False - 4 True - dtype: boolean - - which is equivalent to - - >>> animals.duplicated(keep='first') - 0 False - 1 False - 2 True - 3 False - 4 True - dtype: boolean - - By using 'last', the last occurrence of each set of duplicated values - is set on False and all others on True: - - >>> animals.duplicated(keep='last') - 0 True - 1 False - 2 True - 3 False - 4 False - dtype: boolean - - By setting keep on False, all duplicates are True: - - >>> animals.duplicated(keep=False) - 0 True - 1 False - 2 True - 3 False - 4 True - dtype: boolean - Args: keep ({'first', 'last', False}, default 'first'): Method to handle dropping duplicates: @@ -1159,8 +485,7 @@ def duplicated(self, keep="first") -> Series: ``False`` : Mark all duplicates as ``True``. Returns: - bigframes.pandas.Series: - Series indicating whether each value has occurred in the + bigframes.series.Series: Series indicating whether each value has occurred in the preceding values. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @@ -1172,21 +497,6 @@ def idxmin(self) -> Hashable: If multiple values equal the minimum, the first row label with that value is returned. - **Examples:** - - - >>> s = bpd.Series(data=[1, None, 4, 1], - ... index=['A', 'B', 'C', 'D']) - >>> s - A 1.0 - B - C 4.0 - D 1.0 - dtype: Float64 - - >>> s.idxmin() - 'A' - Returns: Index: Label of the minimum value. """ @@ -1199,22 +509,6 @@ def idxmax(self) -> Hashable: If multiple values equal the maximum, the first row label with that value is returned. - **Examples:** - - - >>> s = bpd.Series(data=[1, None, 4, 3, 4], - ... index=['A', 'B', 'C', 'D', 'E']) - >>> s - A 1.0 - B - C 4.0 - D 3.0 - E 4.0 - dtype: Float64 - - >>> s.idxmax() - 'C' - Returns: Index: Label of the maximum value. """ @@ -1224,64 +518,13 @@ def round(self, decimals: int = 0) -> Series: """ Round each value in a Series to the given number of decimals. - **Examples:** - - >>> import bigframes.pandas as bpd - >>> s = bpd.Series([0.1, 1.3, 2.7]) - >>> s.round() - 0 0.0 - 1 1.0 - 2 3.0 - dtype: Float64 - - >>> s = bpd.Series([0.123, 1.345, 2.789]) - >>> s.round(decimals=2) - 0 0.12 - 1 1.34 - 2 2.79 - dtype: Float64 - Args: decimals (int, default 0): Number of decimal places to round to. If decimals is negative, it specifies the number of positions to the left of the decimal point. Returns: - bigframes.pandas.Series: - Rounded values of the Series. - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - def explode(self, *, ignore_index: Optional[bool] = False) -> Series: - """ - Transform each element of a list-like to a row. - - **Examples:** - - - >>> s = bpd.Series([[1, 2, 3], [], [3, 4]]) - >>> s - 0 [1 2 3] - 1 [] - 2 [3 4] - dtype: list[pyarrow] - - >>> s.explode() - 0 1 - 0 2 - 0 3 - 1 - 2 3 - 2 4 - dtype: Int64 - - Args: - ignore_index (bool, default False): - If True, the resulting index will be labeled 0, 1, …, n - 1. - - Returns: - bigframes.pandas.Series: - Exploded lists to rows; index will be duplicated for these rows. + bigframes.series.Series: Rounded values of the Series. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @@ -1293,19 +536,6 @@ def corr(self, other, method="pearson", min_periods=None) -> float: Uses the "Pearson" method of correlation. Numbers are converted to float before calculation, so the result may be unstable. - **Examples:** - - - >>> s1 = bpd.Series([.2, .0, .6, .2]) - >>> s2 = bpd.Series([.3, .6, .0, .1]) - >>> s1.corr(s2) - np.float64(-0.8510644963469901) - - >>> s1 = bpd.Series([1, 2, 3], index=[0, 1, 2]) - >>> s2 = bpd.Series([1, 2, 3], index=[2, 1, 0]) - >>> s1.corr(s2) - np.float64(-1.0) - Args: other (Series): The series with which this is to be correlated. @@ -1316,127 +546,25 @@ def corr(self, other, method="pearson", min_periods=None) -> float: are not yet supported, so a result will be returned for at least two observations. Returns: - float: - Will return NaN if there are fewer than two numeric pairs, either series has a + float; Will return NaN if there are fewer than two numeric pairs, either series has a variance or covariance of zero, or any input value is infinite. """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) + raise NotImplementedError("abstract method") - def autocorr(self, lag: int = 1) -> float: + def diff(self) -> Series: """ - Compute the lag-N autocorrelation. - - This method computes the Pearson correlation between - the Series and its shifted self. + First discrete difference of element. - **Examples:** + Calculates the difference of a {klass} element compared with another + element in the {klass} (default is element in previous row). - - >>> s = bpd.Series([0.25, 0.5, 0.2, -0.05]) - >>> float(s.autocorr()) # doctest: +ELLIPSIS - 0.1035526330902... - - >>> float(s.autocorr(lag=2)) - -1.0 - - If the Pearson correlation is not well defined, then 'NaN' is returned. - - >>> s = bpd.Series([1, 0, 0, 0]) - >>> float(s.autocorr()) - nan - - Args: - lag (int, default 1): - Number of lags to apply before performing autocorrelation. - - Returns: - float: - The Pearson correlation between self and self.shift(lag). - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - def cov( - self, - other, - ) -> float: - """ - Compute covariance with Series, excluding missing values. - - The two `Series` objects are not required to be the same length and - will be aligned internally before the covariance is calculated. - - **Examples:** - - - >>> s1 = bpd.Series([0.90010907, 0.13484424, 0.62036035]) - >>> s2 = bpd.Series([0.12528585, 0.26962463, 0.51111198]) - >>> s1.cov(s2) - np.float64(-0.01685762652715874) - - Args: - other (Series): - Series with which to compute the covariance. - - Returns: - float: - Covariance between Series and other normalized by N-1 - (unbiased estimator). - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - def diff(self) -> Series: - """ - First discrete difference of element. - - Calculates the difference of a Series element compared with another - element in the Series (default is element in previous row). - - - **Examples:** - - - Difference with previous row - - >>> s = bpd.Series([1, 1, 2, 3, 5, 8]) - >>> s.diff() - 0 - 1 0 - 2 1 - 3 1 - 4 2 - 5 3 - dtype: Int64 - - Difference with 3rd previous row - - >>> s.diff(periods=3) - 0 - 1 - 2 - 3 2 - 4 4 - 5 6 - dtype: Int64 - - Difference with following row - - >>> s.diff(periods=-1) - 0 0 - 1 -1 - 2 -1 - 3 -2 - 4 -3 - 5 - dtype: Int64 - - Args: - periods (int, default 1): - Periods to shift for calculating difference, accepts negative - values. + Args: + periods (int, default 1): + Periods to shift for calculating difference, accepts negative + values. Returns: - bigframes.pandas.Series: - First differences of the Series. + {klass}: First differences of the Series. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @@ -1456,26 +584,12 @@ def dot(self, other) -> Series | np.ndarray: BigQuery Dataframes does not validate this property and will produce incorrect results if indices are not equal. - **Examples:** - - - >>> s = bpd.Series([0, 1, 2, 3]) - >>> other = bpd.Series([-1, 2, -3, 4]) - >>> s.dot(other) - np.int64(8) - - You can also use the operator ``@`` for the dot product: - - >>> s @ other - np.int64(8) - Args: other (Series): The other object to compute the dot product with its columns. Returns: - scalar, bigframes.pandas.Series or numpy.ndarray: - Return the dot product of the Series + scalar, Series or numpy.ndarray: Return the dot product of the Series and other if other is a Series, the Series of the dot product of Series and each rows of other if other is a DataFrame or a numpy.ndarray between the Series and each columns of the numpy array. @@ -1486,110 +600,45 @@ def dot(self, other) -> Series | np.ndarray: def __matmul__(self, other): """ - Matrix multiplication using binary `@` operator. + Matrix multiplication using binary `@` operator in Python>=3.5. """ - return NotImplemented + raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) def __rmatmul__(self, other): """ - Matrix multiplication using binary `@` operator. + Matrix multiplication using binary `@` operator in Python>=3.5. """ - return NotImplemented + raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) def sort_values( self, *, axis: Axis = 0, - inplace: bool = False, ascending: bool | int | Sequence[bool] | Sequence[int] = True, - kind: str | None = None, + kind: str = "quicksort", na_position: str = "last", - ): + ) -> Series | None: """ Sort by the values. Sort a Series in ascending or descending order by some criterion. - **Examples:** - - - >>> s = bpd.Series([np.nan, 1, 3, 10, 5]) - >>> s - 0 - 1 1.0 - 2 3.0 - 3 10.0 - 4 5.0 - dtype: Float64 - - Sort values ascending order (default behaviour): - - >>> s.sort_values(ascending=True) - 1 1.0 - 2 3.0 - 4 5.0 - 3 10.0 - 0 - dtype: Float64 - - Sort values descending order: - - >>> s.sort_values(ascending=False) - 3 10.0 - 4 5.0 - 2 3.0 - 1 1.0 - 0 - dtype: Float64 - - Sort values putting NAs first: - - >>> s.sort_values(na_position='first') - 0 - 1 1.0 - 2 3.0 - 4 5.0 - 3 10.0 - dtype: Float64 - - Sort a series of strings: - - >>> s = bpd.Series(['z', 'b', 'd', 'a', 'c']) - >>> s - 0 z - 1 b - 2 d - 3 a - 4 c - dtype: string - - >>> s.sort_values() - 3 a - 1 b - 4 c - 2 d - 0 z - dtype: string - Args: axis (0 or 'index'): Unused. Parameter needed for compatibility with DataFrame. - inplace (bool, default False): - Whether to modify the Series rather than creating a new one. ascending (bool or list of bools, default True): If True, sort values in ascending order, otherwise descending. - kind (str, default to None): - Choice of sorting algorithm. Accepts quicksort', 'mergesort', - 'heapsort', 'stable'. Ignored except when determining whether to + kind (str, default to 'quicksort'): + Choice of sorting algorithm. Accepts 'quicksort’, ‘mergesort’, + ‘heapsort’, ‘stable’. Ignored except when determining whether to sort stably. 'mergesort' or 'stable' will result in stable reorder na_position ({'first' or 'last'}, default 'last'): Argument 'first' puts NaNs at the beginning, 'last' puts NaNs at the end. Returns: - bigframes.pandas.Series or None: - Series ordered by values or None if ``inplace=True``. + bigframes.series.Series: Series ordered by values or None if ``inplace=True``. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @@ -1597,67 +646,27 @@ def sort_index( self, *, axis: Axis = 0, - inplace: bool = False, ascending: bool | Sequence[bool] = True, - kind: str | None = None, na_position: NaPosition = "last", - ): + ) -> Series | None: """ Sort Series by index labels. Returns a new Series sorted by label if `inplace` argument is ``False``, otherwise updates the original series and returns None. - **Examples:** - - - >>> s = bpd.Series(['a', 'b', 'c', 'd'], index=[3, 2, 1, 4]) - >>> s.sort_index() - 1 c - 2 b - 3 a - 4 d - dtype: string - - Sort Descending - - >>> s.sort_index(ascending=False) - 4 d - 3 a - 2 b - 1 c - dtype: string - - By default NaNs are put at the end, but use na_position to place them at - the beginning - - >>> s = bpd.Series(['a', 'b', 'c', 'd'], index=[3, 2, 1, np.nan]) - >>> s.sort_index(na_position='first') - d - 1.0 c - 2.0 b - 3.0 a - dtype: string - Args: axis ({0 or 'index'}): Unused. Parameter needed for compatibility with DataFrame. - inplace (bool, default False): - Whether to modify the Series rather than creating a new one. ascending (bool or list-like of bools, default True): Sort ascending vs. descending. When the index is a MultiIndex the sort direction can be controlled for each level individually. - kind (str, default None): - Choice of sorting algorithm. Accepts 'quicksort', 'mergesort', - 'heapsort', 'stable'. Ignored except when determining whether to - sort stably. 'mergesort' or 'stable' will result in stable reorder. na_position ({'first', 'last'}, default 'last'): If 'first' puts NaNs at the beginning, 'last' puts NaNs at the end. Not implemented for MultiIndex. Returns: - bigframes.pandas.Series or None: - The original Series sorted by the labels or None if + bigframes.series.Series: The original Series sorted by the labels or None if ``inplace=True``. """ @@ -1670,67 +679,6 @@ def nlargest( """ Return the largest `n` elements. - **Examples:** - - >>> import bigframes.pandas as bpd - >>> countries_population = {"Italy": 59000000, "France": 65000000, - ... "Malta": 434000, "Maldives": 434000, - ... "Brunei": 434000, "Iceland": 337000, - ... "Nauru": 11300, "Tuvalu": 11300, - ... "Anguilla": 11300, "Montserrat": 5200} - >>> s = bpd.Series(countries_population) - >>> s - Italy 59000000 - France 65000000 - Malta 434000 - Maldives 434000 - Brunei 434000 - Iceland 337000 - Nauru 11300 - Tuvalu 11300 - Anguilla 11300 - Montserrat 5200 - dtype: Int64 - - The n largest elements where `n=5` by default. - - >>> s.nlargest() - France 65000000 - Italy 59000000 - Malta 434000 - Maldives 434000 - Brunei 434000 - dtype: Int64 - - The n largest elements where `n=3`. Default keep value is `first` so Malta - will be kept. - - >>> s.nlargest(3) - France 65000000 - Italy 59000000 - Malta 434000 - dtype: Int64 - - The n largest elements where `n=3` and keeping the last duplicates. Brunei - will be kept since it is the last with value 434000 based on the index order. - - >>> s.nlargest(3, keep='last') - France 65000000 - Italy 59000000 - Brunei 434000 - dtype: Int64 - - The n largest elements where n`=3` with all duplicates kept. Note that the - returned Series has five elements due to the three duplicates. - - >>> s.nlargest(3, keep='all') - France 65000000 - Italy 59000000 - Malta 434000 - Maldives 434000 - Brunei 434000 - dtype: Int64 - Args: n (int, default 5): Return this many descending sorted values. @@ -1745,8 +693,7 @@ def nlargest( size larger than `n`. Returns: - bigframes.pandas.Series: - The `n` largest values in the Series, sorted in decreasing order. + bigframes.series.Series: The `n` largest values in the Series, sorted in decreasing order. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @@ -1754,57 +701,6 @@ def nsmallest(self, n: int = 5, keep: str = "first") -> Series: """ Return the smallest `n` elements. - **Examples:** - - >>> import bigframes.pandas as bpd - >>> countries_population = {"Italy": 59000000, "France": 65000000, - ... "Malta": 434000, "Maldives": 434000, - ... "Brunei": 434000, "Iceland": 337000, - ... "Nauru": 11300, "Tuvalu": 11300, - ... "Anguilla": 11300, "Montserrat": 5200} - >>> s = bpd.Series(countries_population) - >>> s - Italy 59000000 - France 65000000 - Malta 434000 - Maldives 434000 - Brunei 434000 - Iceland 337000 - Nauru 11300 - Tuvalu 11300 - Anguilla 11300 - Montserrat 5200 - dtype: Int64 - - The n smallest elements where `n=5` by default. - - >>> s.nsmallest() - Montserrat 5200 - Nauru 11300 - Tuvalu 11300 - Anguilla 11300 - Iceland 337000 - dtype: Int64 - - The n smallest elements where `n=3`. Default keep value is `first` so - Nauru and Tuvalu will be kept. - - >>> s.nsmallest(3) - Montserrat 5200 - Nauru 11300 - Tuvalu 11300 - dtype: Int64 - - The n smallest elements where `n=3` with all duplicates kept. Note that - the returned Series has four elements due to the three duplicates. - - >>> s.nsmallest(3, keep='all') - Montserrat 5200 - Nauru 11300 - Tuvalu 11300 - Anguilla 11300 - dtype: Int64 - Args: n (int, default 5): Return this many ascending sorted values. @@ -1820,8 +716,7 @@ def nsmallest(self, n: int = 5, keep: str = "first") -> Series: size larger than `n`. Returns: - bigframes.pandas.Series: - The `n` smallest values in the Series, sorted in increasing order. + bigframes.series.Series: The `n` smallest values in the Series, sorted in increasing order. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @@ -1831,193 +726,20 @@ def nsmallest(self, n: int = 5, keep: str = "first") -> Series: def apply( self, func, - by_row="compat", ) -> DataFrame | Series: """ - Invoke function on values of a Series. - - Can be ufunc (a NumPy function that applies to the entire Series) or a - Python function that only works on single values. If it is an arbitrary - python function then converting it into a `remote_function` is recommended. - - **Examples:** - - Simple vectorized functions, lambdas or ufuncs can be applied directly - with `by_row=False`. - - >>> nums = bpd.Series([1, 2, 3, 4]) - >>> nums - 0 1 - 1 2 - 2 3 - 3 4 - dtype: Int64 - >>> nums.apply(lambda x: x*x + 2*x + 1, by_row=False) - 0 4 - 1 9 - 2 16 - 3 25 - dtype: Int64 - - >>> def is_odd(num): - ... return num % 2 == 1 - >>> nums.apply(is_odd, by_row=False) - 0 True - 1 False - 2 True - 3 False - dtype: boolean - - >>> nums.apply(np.log, by_row=False) - 0 0.0 - 1 0.693147 - 2 1.098612 - 3 1.386294 - dtype: Float64 - - Use `remote_function` to apply an arbitrary Python function. - Set ``reuse=False`` flag to make sure a new `remote_function` - is created every time you run the following code. Omit it - to reuse a previously deployed `remote_function` from - the same user defined function if the hash of the function definition - hasn't changed. - - >>> @bpd.remote_function(reuse=False, cloud_function_service_account="default") # doctest: +SKIP - ... def minutes_to_hours(x: int) -> float: - ... return x/60 - - >>> minutes = bpd.Series([0, 30, 60, 90, 120]) - >>> minutes - 0 0 - 1 30 - 2 60 - 3 90 - 4 120 - dtype: Int64 - - >>> hours = minutes.apply(minutes_to_hours) # doctest: +SKIP - >>> hours # doctest: +SKIP - 0 0.0 - 1 0.5 - 2 1.0 - 3 1.5 - 4 2.0 - dtype: Float64 - - To turn a user defined function with external package dependencies into - a `remote_function`, you would provide the names of the packages via - `packages` param. - - >>> @bpd.remote_function( # doctest: +SKIP - ... reuse=False, - ... packages=["cryptography"], - ... cloud_function_service_account="default" - ... ) - ... def get_hash(input: str) -> str: - ... from cryptography.fernet import Fernet - ... - ... # handle missing value - ... if input is None: - ... input = "" - ... - ... key = Fernet.generate_key() - ... f = Fernet(key) - ... return f.encrypt(input.encode()).decode() - - >>> names = bpd.Series(["Alice", "Bob"]) - >>> hashes = names.apply(get_hash) # doctest: +SKIP - - You could return an array output from the remote function. - - >>> @bpd.remote_function(reuse=False, cloud_function_service_account="default") # doctest: +SKIP - ... def text_analyzer(text: str) -> list[int]: - ... words = text.count(" ") + 1 - ... periods = text.count(".") - ... exclamations = text.count("!") - ... questions = text.count("?") - ... return [words, periods, exclamations, questions] - - >>> texts = bpd.Series([ - ... "The quick brown fox jumps over the lazy dog.", - ... "I love this product! It's amazing.", - ... "Hungry? Wanna eat? Lets go!" - ... ]) - >>> features = texts.apply(text_analyzer) # doctest: +SKIP - >>> features # doctest: +SKIP - 0 [9 1 0 0] - 1 [6 1 1 0] - 2 [5 0 1 2] - dtype: list[pyarrow] - - Args: - func (function): - BigFrames DataFrames ``remote_function`` to apply. The function - should take a scalar and return a scalar. It will be applied to - every element in the ``Series``. - by_row (False or "compat", default "compat"): - If `"compat"` , func must be a remote function which will be - passed each element of the Series, like `Series.map`. If False, - the func will be passed the whole Series at once. - - Returns: - bigframes.pandas.Series: - A new Series with values representing the - return value of the ``func`` applied to each element of the - original Series. - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - def combine( - self, - other: Series | Hashable, - func, - ) -> Series: - """ - Combine the Series with a Series or scalar according to `func`. - - Combine the Series and `other` using `func` to perform elementwise - selection for combined Series. - `fill_value` is assumed when value is missing at some index - from one of the two objects being combined. - - **Examples:** - - Consider 2 Datasets ``s1`` and ``s2`` containing - highest clocked speeds of different birds. - - >>> import bigframes.pandas as bpd - >>> s1 = bpd.Series({'falcon': 330.0, 'eagle': 160.0}) - >>> s1 - falcon 330.0 - eagle 160.0 - dtype: Float64 - >>> s2 = bpd.Series({'falcon': 345.0, 'eagle': 200.0, 'duck': 30.0}) - >>> s2 - falcon 345.0 - eagle 200.0 - duck 30.0 - dtype: Float64 + Invoke function on values of Series. - Now, to combine the two datasets and view the highest speeds - of the birds across the two datasets - - >>> s1.combine(s2, np.maximum) - falcon 345.0 - eagle 200.0 - duck - dtype: Float64 + Can be ufunc (a NumPy function that applies to the entire Series) + or a Python function that only works on single values. Args: - other (Series or scalar): - The value(s) to be combined with the `Series`. func (function): - BigFrames DataFrames ``remote_function`` to apply. - Takes two scalars as inputs and returns an element. - Also accepts some numpy binary functions. + Python function or NumPy ufunc to apply. Returns: - bigframes.pandas.Series: - The result of combining the Series with the other object. + bigframes.series.Series: If func returns a Series object the result + will be a DataFrame. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @@ -2037,92 +759,6 @@ def groupby( used to group large amounts of data and compute operations on these groups. - **Examples:** - - - You can group by a named index level. - - >>> s = bpd.Series([380, 370., 24., 26.], - ... index=["Falcon", "Falcon", "Parrot", "Parrot"], - ... name="Max Speed") - >>> s.index.name="Animal" - >>> s - Animal - Falcon 380.0 - Falcon 370.0 - Parrot 24.0 - Parrot 26.0 - Name: Max Speed, dtype: Float64 - >>> s.groupby("Animal").mean() - Animal - Falcon 375.0 - Parrot 25.0 - Name: Max Speed, dtype: Float64 - - You can also group by more than one index levels. - - >>> s = bpd.Series([380, 370., 24., 26.], - ... index=pd.MultiIndex.from_tuples( - ... [("Falcon", "Clear"), - ... ("Falcon", "Cloudy"), - ... ("Parrot", "Clear"), - ... ("Parrot", "Clear")], - ... names=["Animal", "Sky"]), - ... name="Max Speed") - >>> s - Animal Sky - Falcon Clear 380.0 - Cloudy 370.0 - Parrot Clear 24.0 - Clear 26.0 - Name: Max Speed, dtype: Float64 - - >>> s.groupby("Animal").mean() - Animal - Falcon 375.0 - Parrot 25.0 - Name: Max Speed, dtype: Float64 - - >>> s.groupby("Sky").mean() - Sky - Clear 143.333333 - Cloudy 370.0 - Name: Max Speed, dtype: Float64 - - >>> s.groupby(["Animal", "Sky"]).mean() - Animal Sky - Falcon Clear 380.0 - Cloudy 370.0 - Parrot Clear 25.0 - Name: Max Speed, dtype: Float64 - - You can also group by values in a Series provided the index matches with - the original series. - - >>> df = bpd.DataFrame({'Animal': ['Falcon', 'Falcon', 'Parrot', 'Parrot'], - ... 'Max Speed': [380., 370., 24., 26.], - ... 'Age': [10., 20., 4., 6.]}) - >>> df - Animal Max Speed Age - 0 Falcon 380.0 10.0 - 1 Falcon 370.0 20.0 - 2 Parrot 24.0 4.0 - 3 Parrot 26.0 6.0 - - [4 rows x 3 columns] - - >>> df['Max Speed'].groupby(df['Animal']).mean() - Animal - Falcon 375.0 - Parrot 25.0 - Name: Max Speed, dtype: Float64 - - >>> df['Age'].groupby(df['Animal']).max() - Animal - Falcon 20.0 - Parrot 6.0 - Name: Age, dtype: Float64 - Args: by (mapping, function, label, pd.Grouper or list of such, default None): Used to determine the groups for the groupby. @@ -2156,8 +792,7 @@ def groupby( If False, NA values will also be treated as the key in groups. Returns: - bigframes.core.groupby.SeriesGroupBy: - Returns a groupby object that contains + bigframes.core.groupby.SeriesGroupBy: Returns a groupby object that contains information about the groups. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @@ -2207,52 +842,6 @@ def drop( When using a multi-index, labels on different levels can be removed by specifying the level. - **Examples:** - - - >>> s = bpd.Series(data=np.arange(3), index=['A', 'B', 'C']) - >>> s - A 0 - B 1 - C 2 - dtype: Int64 - - Drop labels B and C: - - >>> s.drop(labels=['B', 'C']) - A 0 - dtype: Int64 - - Drop 2nd level label in MultiIndex Series: - - >>> midx = pd.MultiIndex(levels=[['llama', 'cow', 'falcon'], - ... ['speed', 'weight', 'length']], - ... codes=[[0, 0, 0, 1, 1, 1, 2, 2, 2], - ... [0, 1, 2, 0, 1, 2, 0, 1, 2]]) - - >>> s = bpd.Series([45, 200, 1.2, 30, 250, 1.5, 320, 1, 0.3], - ... index=midx) - >>> s - llama speed 45.0 - weight 200.0 - length 1.2 - cow speed 30.0 - weight 250.0 - length 1.5 - falcon speed 320.0 - weight 1.0 - length 0.3 - dtype: Float64 - - >>> s.drop(labels='weight', level=1) - llama speed 45.0 - length 1.2 - cow speed 30.0 - length 1.5 - falcon speed 320.0 - length 0.3 - dtype: Float64 - Args: labels (single label or list-like): Index labels to drop. @@ -2267,13 +856,11 @@ def drop( For MultiIndex, level for which the labels will be removed. Returns: - bigframes.pandas.Series or None: - Series with specified index labels removed + bigframes.series.Series: Series with specified index labels removed or None if ``inplace=True``. Raises: - KeyError: - If none of the labels are found in the index. + KeyError: If none of the labels are found in the index. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @@ -2307,8 +894,7 @@ def swaplevel(self, i, j): Levels of the indices to be swapped. Can pass level name as string. Returns: - bigframes.pandas.Series: - Series with levels swapped in MultiIndex + Series: Series with levels swapped in MultiIndex """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @@ -2326,8 +912,7 @@ def droplevel(self, level, axis): For `Series` this parameter is unused and defaults to 0. Returns: - bigframes.pandas.Series: - Series with requested index / column level(s) removed. + Series with requested index / column level(s) removed. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @@ -2335,38 +920,31 @@ def interpolate(self, method: str = "linear"): """ Fill NaN values using an interpolation method. - **Examples:** - - - Filling in NaN in a Series via linear interpolation. - - >>> s = bpd.Series([0, 1, np.nan, 3]) - >>> s - 0 0.0 - 1 1.0 - 2 - 3 3.0 - dtype: Float64 - - >>> s.interpolate() - 0 0.0 - 1 1.0 - 2 2.0 - 3 3.0 - dtype: Float64 - Args: method (str, default 'linear'): Interpolation technique to use. Only 'linear' supported. 'linear': Ignore the index and treat the values as equally spaced. This is the only method supported on MultiIndexes. - 'index', 'values': use the actual numerical values of the index. - 'pad': Fill in NaNs using existing values. - 'nearest', 'zero', 'slinear': Emulates `scipy.interpolate.interp1d` + Returns: - bigframes.pandas.Series: + Series: Returns the same object type as the caller, interpolated at some or all ``NaN`` values + + **Examples:** + + >>> import bigframes.pandas as bpd + >>> bpd.options.display.progress_bar = None + + >>> series = bpd.Series([1, 2, 3, None, None, 6]) + >>> series.interpolate() + 0 1.0 + 1 2.0 + 2 3.0 + 3 4.0 + 4 5.0 + 5 6.0 + dtype: Float64 """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @@ -2375,52 +953,14 @@ def fillna( value=None, ) -> Series | None: """ - Fill NA (NULL in BigQuery) values using the specified method. - - Note that empty strings ``''``, :attr:`numpy.inf`, and - :attr:`numpy.nan` are ***not*** considered NA values. This NA/NULL - logic differs from numpy, but it is the same as BigQuery and the - :class:`pandas.ArrowDtype`. - - **Examples:** - - >>> s = bpd.Series( - ... pa.array([np.nan, 2, None, -1], type=pa.float64()), - ... dtype=pd.ArrowDtype(pa.float64()), - ... ) - >>> s - 0 NaN - 1 2.0 - 2 - 3 -1.0 - dtype: Float64 - - Replace all NA (NULL) elements with 0s. - - >>> s.fillna(0) - 0 NaN - 1 2.0 - 2 0.0 - 3 -1.0 - dtype: Float64 - - You can use fill values from another Series: - - >>> s_fill = bpd.Series([11, 22, 33]) - >>> s.fillna(s_fill) - 0 NaN - 1 2.0 - 2 33.0 - 3 -1.0 - dtype: Float64 + Fill NA/NaN values using the specified method. Args: value (scalar, dict, Series, or DataFrame, default None): Value to use to fill holes (e.g. 0). Returns: - bigframes.pandas.Series or None: - Object with missing values filled or None. + Series or None: Object with missing values filled or None. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @@ -2436,75 +976,6 @@ def replace( This differs from updating with ``.loc`` or ``.iloc``, which require you to specify a location to update with some value. - **Examples:** - - >>> import bigframes.pandas as bpd - >>> s = bpd.Series([1, 2, 3, 4, 5]) - >>> s - 0 1 - 1 2 - 2 3 - 3 4 - 4 5 - dtype: Int64 - - >>> s.replace(1, 5) - 0 5 - 1 2 - 2 3 - 3 4 - 4 5 - dtype: Int64 - - You can replace a list of values: - - >>> s.replace([1, 3, 5], -1) - 0 -1 - 1 2 - 2 -1 - 3 4 - 4 -1 - dtype: Int64 - - You can use a replacement mapping: - - >>> s.replace({1: 5, 3: 10}) - 0 5 - 1 2 - 2 10 - 3 4 - 4 5 - dtype: Int64 - - With a string Series you can use a simple string replacement or a regex - replacement: - - >>> s = bpd.Series(["Hello", "Another Hello"]) - >>> s.replace("Hello", "Hi") - 0 Hi - 1 Another Hello - dtype: string - - >>> s.replace("Hello", "Hi", regex=True) - 0 Hi - 1 Another Hi - dtype: string - - >>> s.replace("^Hello", "Hi", regex=True) - 0 Hi - 1 Another Hello - dtype: string - - >>> s.replace("Hello$", "Hi", regex=True) - 0 Hi - 1 Another Hi - dtype: string - - >>> s.replace("[Hh]e", "__", regex=True) - 0 __llo - 1 Anot__r __llo - dtype: string - Args: to_replace (str, regex, list, int, float or None): How to find the values that will be replaced. @@ -2540,8 +1011,7 @@ def replace( string. Returns: - bigframes.pandas.Series or bigframes.pandas.DataFrame: - Object after replacement. + Series/DataFrame: Object after replacement. Raises: TypeError: @@ -2557,107 +1027,10 @@ def replace( """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - def resample( - self, - rule: str, - *, - closed: Optional[Literal["right", "left"]] = None, - label: Optional[Literal["right", "left"]] = None, - level=None, - origin: Union[ - Union[pd.Timestamp, datetime.datetime, numpy.datetime64, int, float, str], - Literal["epoch", "start", "start_day", "end", "end_day"], - ] = "start_day", - ): - """Resample time-series data. - - **Examples:** - - >>> import bigframes.pandas as bpd - >>> data = { - ... "timestamp_col": pd.date_range( - ... start="2021-01-01 13:00:00", periods=30, freq="1s" - ... ), - ... "int64_col": range(30), - ... } - >>> s = bpd.DataFrame(data).set_index("timestamp_col") - >>> s.resample(rule="7s", origin="epoch").min() - int64_col - timestamp_col - 2021-01-01 12:59:56 0 - 2021-01-01 13:00:03 3 - 2021-01-01 13:00:10 10 - 2021-01-01 13:00:17 17 - 2021-01-01 13:00:24 24 - - [5 rows x 1 columns] - - Args: - rule (str): - The offset string representing target conversion. - Offsets 'ME', 'YE', 'QE', 'BME', 'BA', 'BQE', and 'W' are *not* - supported. - closed (Literal['left'] | None): - Which side of bin interval is closed. The default is 'left' for - all supported frequency offsets. - label (Literal['right'] | Literal['left'] | None): - Which bin edge label to label bucket with. The default is 'left' - for all supported frequency offsets. - on (str, default None): - For a DataFrame, column to use instead of index for resampling. Column - must be datetime-like. - level (str or int, default None): - For a MultiIndex, level (name or number) to use for resampling. - level must be datetime-like. - origin(str, default 'start_day'): - The timestamp on which to adjust the grouping. Must be one of the following: - 'epoch': origin is 1970-01-01 - 'start': origin is the first value of the timeseries - 'start_day': origin is the first day at midnight of the timeseries - Origin values 'end' and 'end_day' are *not* supported. - Returns: - SeriesGroupBy: SeriesGroupBy object. - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - def dropna(self, *, axis=0, inplace: bool = False, how=None) -> Series: """ Return a new Series with missing values removed. - **Examples:** - - - Drop NA values from a Series: - - >>> ser = bpd.Series([1., 2., np.nan]) - >>> ser - 0 1.0 - 1 2.0 - 2 - dtype: Float64 - - >>> ser.dropna() - 0 1.0 - 1 2.0 - dtype: Float64 - - Empty strings are not considered NA values. ``None`` is considered an NA value. - - >>> ser = bpd.Series(['2', pd.NA, '', None, 'I stay'], dtype='object') - >>> ser - 0 2 - 1 - 2 - 3 - 4 I stay - dtype: string - - >>> ser.dropna() - 0 2 - 2 - 4 I stay - dtype: string - Args: axis (0 or 'index'): Unused. Parameter needed for compatibility with DataFrame. @@ -2667,8 +1040,7 @@ def dropna(self, *, axis=0, inplace: bool = False, how=None) -> Series: Not in use. Kept for compatibility. Returns: - bigframes.pandas.Series: - Series with NA entries dropped from it. + Series: Series with NA entries dropped from it. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @@ -2685,40 +1057,6 @@ def between( corresponding Series element is between the boundary values `left` and `right`. NA values are treated as `False`. - **Examples:** - - - Boundary values are included by default: - - >>> s = bpd.Series([2, 0, 4, 8, np.nan]) - >>> s.between(1, 4) - 0 True - 1 False - 2 True - 3 False - 4 - dtype: boolean - - With inclusive set to "neither" boundary values are excluded: - - >>> s.between(1, 4, inclusive="neither") - 0 True - 1 False - 2 False - 3 False - 4 - dtype: boolean - - left and right can be any scalar value: - - >>> s = bpd.Series(['Alice', 'Bob', 'Carol', 'Eve']) - >>> s.between('Anna', 'Daniel') - 0 False - 1 True - 2 True - 3 False - dtype: boolean - Args: left (scalar or list-like): Left boundary. @@ -2728,75 +1066,9 @@ def between( Include boundaries. Whether to set each bound as closed or open. Returns: - bigframes.pandas.Series: - Series representing whether each element is between left and - right (inclusive). - - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - def case_when( - self, - caselist: List[Tuple[Series, Series]], - ) -> Series: - """Replace values where the conditions are True. - - **Examples:** - - - >>> c = bpd.Series([6, 7, 8, 9], name="c") - >>> a = bpd.Series([0, 0, 1, 2]) - >>> b = bpd.Series([0, 3, 4, 5]) - - >>> c.case_when( - ... caselist=[ - ... (a.gt(0), a), # condition, replacement - ... (b.gt(0), b), - ... ] - ... ) - 0 6 - 1 3 - 2 1 - 3 2 - Name: c, dtype: Int64 - - If you'd like to change the type, add a case with the condition True at the end of the case list - - >>> c.case_when( - ... caselist=[ - ... (a.gt(0), 'a'), # condition, replacement - ... (b.gt(0), 'b'), - ... (True, 'c'), - ... ] - ... ) - 0 c - 1 b - 2 a - 3 a - Name: c, dtype: string - - **See also:** - - - :func:`bigframes.pandas.Series.mask` : Replace values where the condition is True. - - Args: - caselist (A list of tuples of conditions and expected replacements): - Takes the form: ``(condition0, replacement0)``, - ``(condition1, replacement1)``, ... . - ``condition`` should be a 1-D boolean array-like object - or a callable. If ``condition`` is a callable, - it is computed on the Series - and should return a boolean Series or array. - The callable must not change the input Series - (though pandas doesn`t check it). ``replacement`` should be a - 1-D array-like object, a scalar or a callable. - If ``replacement`` is a callable, it is computed on the Series - and should return a scalar or Series. The callable - must not change the input Series - (though pandas doesn`t check it). + Series: Series representing whether each element is between left and + right (inclusive). - Returns: - bigframes.pandas.Series """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @@ -2807,31 +1079,8 @@ def cumprod(self): Returns a DataFrame or Series of the same size containing the cumulative product. - **Examples:** - - >>> import bigframes.pandas as bpd - >>> s = bpd.Series([2, np.nan, 5, -1, 0]) - >>> s - 0 2.0 - 1 - 2 5.0 - 3 -1.0 - 4 0.0 - dtype: Float64 - - By default, NA values are ignored. - - >>> s.cumprod() - 0 2.0 - 1 - 2 10.0 - 3 -10.0 - 4 0.0 - dtype: Float64 - Returns: - bigframes.pandas.Series: - Return cumulative sum of scalar or Series. + bigframes.series.Series: Return cumulative sum of scalar or Series. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @@ -2842,36 +1091,13 @@ def cumsum(self): Returns a DataFrame or Series of the same size containing the cumulative sum. - **Examples:** - - - >>> s = bpd.Series([2, np.nan, 5, -1, 0]) - >>> s - 0 2.0 - 1 - 2 5.0 - 3 -1.0 - 4 0.0 - dtype: Float64 - - By default, NA values are ignored. - - >>> s.cumsum() - 0 2.0 - 1 - 2 7.0 - 3 6.0 - 4 6.0 - dtype: Float64 - Args: axis ({0 or 'index', 1 or 'columns'}, default 0): The index or the name of the axis. 0 is equivalent to None or 'index'. For `Series` this parameter is unused and defaults to 0. Returns: - bigframes.pandas.Series: - Return cumulative sum of scalar or Series. + scalar or Series: Return cumulative sum of scalar or Series. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @@ -2882,32 +1108,13 @@ def cummax(self): Returns a DataFrame or Series of the same size containing the cumulative maximum. - **Examples:** - - - >>> s = bpd.Series([2, np.nan, 5, -1, 0]) - >>> s - 0 2.0 - 1 - 2 5.0 - 3 -1.0 - 4 0.0 - dtype: Float64 - - By default, NA values are ignored. - - >>> s.cummax() - 0 2.0 - 1 - 2 5.0 - 3 5.0 - 4 5.0 - dtype: Float64 - + Args: + axis ({{0 or 'index', 1 or 'columns'}}, default 0): + The index or the name of the axis. 0 is equivalent to None or 'index'. + For `Series` this parameter is unused and defaults to 0. Returns: - bigframes.pandas.Series: - Return cumulative maximum of scalar or Series. + bigframes.series.Series: Return cumulative maximum of scalar or Series. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @@ -2918,69 +1125,30 @@ def cummin(self): Returns a DataFrame or Series of the same size containing the cumulative minimum. - **Examples:** - - - >>> s = bpd.Series([2, np.nan, 5, -1, 0]) - >>> s - 0 2.0 - 1 - 2 5.0 - 3 -1.0 - 4 0.0 - dtype: Float64 - - By default, NA values are ignored. - - >>> s.cummin() - 0 2.0 - 1 - 2 2.0 - 3 -1.0 - 4 -1.0 - dtype: Float64 + Args: + axis ({0 or 'index', 1 or 'columns'}, default 0): + The index or the name of the axis. 0 is equivalent to None or 'index'. + For `Series` this parameter is unused and defaults to 0. + skipna (bool, default True): + Exclude NA/null values. If an entire row/column is NA, the result + will be NA. + `*args`, `**kwargs`: + Additional keywords have no effect but might be accepted for + compatibility with NumPy. Returns: - bigframes.pandas.Series: - Return cumulative minimum of scalar or Series. + bigframes.series.Series: Return cumulative minimum of scalar or Series. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) def eq(self, other) -> Series: """Return equal of Series and other, element-wise (binary operator eq). - Equivalent to ``other == series``, but with support to substitute a - fill_value for missing data in either one of the inputs. - - **Examples:** - - - >>> a = bpd.Series([1, 1, 1, np.nan], index=['a', 'b', 'c', 'd']) - >>> a - a 1.0 - b 1.0 - c 1.0 - d - dtype: Float64 - - >>> b = bpd.Series([1, np.nan, 1, np.nan], index=['a', 'b', 'd', 'e']) - >>> b - a 1.0 - b - d 1.0 - e - dtype: Float64 - - >>> a.eq(b) - a True - b - c - d - e - dtype: boolean + Equivalent to ``other == series``, but with support to substitute a fill_value for + missing data in either one of the inputs. Args: - other (Series or scalar value) + other (Series, or scalar value): Returns: Series: The result of the operation. @@ -2991,1985 +1159,469 @@ def eq(self, other) -> Series: def ne(self, other) -> Series: """Return not equal of Series and other, element-wise (binary operator ne). - Equivalent to ``other != series``, but with support to substitute a - fill_value for missing data in either one of the inputs. + Equivalent to ``other != series``, but with support to substitute a fill_value for + missing data in either one of the inputs. - **Examples:** - - - >>> a = bpd.Series([1, 1, 1, np.nan], index=['a', 'b', 'c', 'd']) - >>> a - a 1.0 - b 1.0 - c 1.0 - d - dtype: Float64 - - >>> b = bpd.Series([1, np.nan, 1, np.nan], index=['a', 'b', 'd', 'e']) - >>> b - a 1.0 - b - d 1.0 - e - dtype: Float64 - - >>> a.ne(b) - a False - b - c - d - e - dtype: boolean - - Args: - other (Series or scalar value) + Args: + other (Series, or scalar value): Returns: - bigframes.pandas.Series: - The result of the operation. + bigframes.series.Series: The result of the operation. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) def le(self, other) -> Series: - """Get 'less than or equal to' of Series and other, element-wise (binary - operator le). - - Equivalent to ``series <= other``, but with support to substitute a - fill_value for missing data in either one of the inputs. - - **Examples:** - - - >>> a = bpd.Series([1, 1, 1, np.nan], index=['a', 'b', 'c', 'd']) - >>> a - a 1.0 - b 1.0 - c 1.0 - d - dtype: Float64 - - >>> b = bpd.Series([1, np.nan, 1, np.nan], index=['a', 'b', 'd', 'e']) - >>> b - a 1.0 - b - d 1.0 - e - dtype: Float64 + """Get 'less than or equal to' of Series and other, element-wise (binary operator `<=`). - >>> a.le(b) - a True - b - c - d - e - dtype: boolean + Equivalent to ``series <= other``, but with support to substitute a fill_value for + missing data in either one of the inputs. Args: other: Series, or scalar value Returns: - bigframes.pandas.Series: - The result of the comparison. + bigframes.series.Series. The result of the comparison. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) def lt(self, other) -> Series: - """Get 'less than' of Series and other, element-wise (binary operator lt). + """Get 'less than' of Series and other, element-wise (binary operator `<`). - Equivalent to ``series < other``, but with support to substitute a - fill_value for missing data in either one of the inputs. - - **Examples:** - - - >>> a = bpd.Series([1, 1, 1, np.nan], index=['a', 'b', 'c', 'd']) - >>> a - a 1.0 - b 1.0 - c 1.0 - d - dtype: Float64 - - >>> b = bpd.Series([1, np.nan, 1, np.nan], index=['a', 'b', 'd', 'e']) - >>> b - a 1.0 - b - d 1.0 - e - dtype: Float64 - - >>> a.lt(b) - a False - b - c - d - e - dtype: boolean + Equivalent to ``series < other``, but with support to substitute a fill_value for + missing data in either one of the inputs. Args: - other (Series or scalar value) + other (Series, or scalar value): - Returns: - bigframes.pandas.Series: - The result of the operation. + Returns: + bigframes.series.Series: The result of the operation. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) def ge(self, other) -> Series: - """Get 'greater than or equal to' of Series and other, element-wise - (binary operator ge). - - Equivalent to ``series >= other``, but with support to substitute a - fill_value for missing data in either one of the inputs. - - **Examples:** - - - >>> a = bpd.Series([1, 1, 1, np.nan], index=['a', 'b', 'c', 'd']) - >>> a - a 1.0 - b 1.0 - c 1.0 - d - dtype: Float64 - - >>> b = bpd.Series([1, np.nan, 1, np.nan], index=['a', 'b', 'd', 'e']) - >>> b - a 1.0 - b - d 1.0 - e - dtype: Float64 + """Get 'greater than or equal to' of Series and other, element-wise (binary operator `>=`). - >>> a.ge(b) - a True - b - c - d - e - dtype: boolean + Equivalent to ``series >= other``, but with support to substitute a fill_value for + missing data in either one of the inputs. Args: - other (Series or scalar value) + other (Series, or scalar value): Returns: - bigframes.pandas.Series: - The result of the operation. + bigframes.series.Series: The result of the operation. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) def gt(self, other) -> Series: - """Return Greater than of series and other, element-wise - (binary operator gt). - - Equivalent to ``series <= other``, but with support to substitute a - fill_value for missing data in either one of the inputs. - - **Examples:** - - - >>> a = bpd.Series([1, 1, 1, np.nan], index=['a', 'b', 'c', 'd']) - >>> a - a 1.0 - b 1.0 - c 1.0 - d - dtype: Float64 - - >>> b = bpd.Series([1, np.nan, 1, np.nan], index=['a', 'b', 'd', 'e']) - >>> b - a 1.0 - b - d 1.0 - e - dtype: Float64 + """Get 'less than or equal to' of Series and other, element-wise (binary operator `<=`). - >>> a.gt(b) - a False - b - c - d - e - dtype: boolean + Equivalent to ``series <= other``, but with support to substitute a fill_value for + missing data in either one of the inputs. Args: - other (Series or scalar value) + other (Series, or scalar value): Returns: - bigframes.pandas.Series: The result of the operation. + bigframes.series.Series: The result of the operation. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) def add(self, other) -> Series: - """Return addition of Series and other, element-wise (binary operator - add). - - Equivalent to ``series + other``, but with support to substitute a - fill_value for missing data in either one of the inputs. - - **Examples:** + """Return addition of Series and other, element-wise (binary operator add). - - >>> a = bpd.Series([1, 2, 3, pd.NA]) - >>> a - 0 1 - 1 2 - 2 3 - 3 - dtype: Int64 - - >>> b = bpd.Series([10, 20, 30, 40]) - >>> b - 0 10 - 1 20 - 2 30 - 3 40 - dtype: Int64 - - >>> a.add(b) - 0 11 - 1 22 - 2 33 - 3 - dtype: Int64 - - You can also use the mathematical operator ``+``: - - >>> a + b - 0 11 - 1 22 - 2 33 - 3 - dtype: Int64 - - Adding two Series with explicit indexes: - - >>> a = bpd.Series([1, 2, 3, 4], index=['a', 'b', 'c', 'd']) - >>> b = bpd.Series([10, 20, 30, 40], index=['a', 'b', 'd', 'e']) - >>> a.add(b) - a 11 - b 22 - c - d 34 - e - dtype: Int64 + Equivalent to ``series + other``, but with support to substitute a fill_value for + missing data in either one of the inputs. Args: - other (Series or scalar value) + other (Series, or scalar value): Returns: - bigframes.pandas.Series: - The result of the operation. + bigframes.series.Series: The result of the operation. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - def __add__(self, other): - """Get addition of Series and other, element-wise, using operator `+`. - - Equivalent to `Series.add(other)`. - - **Examples:** - - - >>> s = bpd.Series([1.5, 2.6], index=['elk', 'moose']) - >>> s - elk 1.5 - moose 2.6 - dtype: Float64 + def radd(self, other) -> Series: + """Return addition of Series and other, element-wise (binary operator radd). - You can add a scalar. + Equivalent to ``other + series``, but with support to substitute a fill_value for + missing data in either one of the inputs. - >>> s + 1.5 - elk 3.0 - moose 4.1 - dtype: Float64 + Args: + other (Series, or scalar value): - You can add another Series with index aligned. + Returns: + bigframes.series.Series: The result of the operation. - >>> delta = bpd.Series([1.5, 2.6], index=['elk', 'moose']) - >>> s + delta - elk 3.0 - moose 5.2 - dtype: Float64 + """ + raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - Adding any mis-aligned index will result in invalid values. + def sub( + self, + other, + ) -> Series: + """Return subtraction of Series and other, element-wise (binary operator sub). - >>> delta = bpd.Series([1.5, 2.6], index=['moose', 'bison']) - >>> s + delta - elk - moose 4.1 - bison - dtype: Float64 + Equivalent to ``series - other``, but with support to substitute a fill_value for + missing data in either one of the inputs. Args: - other (scalar or Series): - Object to be added to the Series. + other (Series, or scalar value): Returns: - bigframes.pandas.Series: - The result of adding `other` to Series. + bigframes.series.Series: The result of the operation. + """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - def radd(self, other) -> Series: - """Return addition of Series and other, element-wise (binary operator - radd). + def rsub(self, other) -> Series: + """Return subtraction of Series and other, element-wise (binary operator rsub). - Equivalent to ``other + series``, but with support to substitute a - fill_value for missing data in either one of the inputs. + Equivalent to ``other - series``, but with support to substitute a fill_value for + missing data in either one of the inputs. - **Examples:** + Args: + other (Series, or scalar value): + Returns: + bigframes.series.Series: The result of the operation. - >>> a = bpd.Series([1, 1, 1, np.nan], index=['a', 'b', 'c', 'd']) - >>> a - a 1.0 - b 1.0 - c 1.0 - d - dtype: Float64 + """ + raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - >>> b = bpd.Series([1, np.nan, 1, np.nan], index=['a', 'b', 'd', 'e']) - >>> b - a 1.0 - b - d 1.0 - e - dtype: Float64 + def mul(self, other) -> Series: + """Return multiplication of Series and other, element-wise (binary operator mul). - >>> a.add(b) - a 2.0 - b - c - d - e - dtype: Float64 + Equivalent to ``other * series``, but with support to substitute a fill_value for + missing data in either one of the inputs. Args: - other (Series, or scalar value) + other (Series, or scalar value): Returns: - bigframes.pandas.Series: - The result of the operation. + bigframes.series.Series: The result of the operation. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - def __radd__(self, other): - """Get addition of Series and other, element-wise, using operator `+`. + def rmul(self, other) -> Series: + """Return multiplication of Series and other, element-wise (binary operator mul). - Equivalent to `Series.radd(other)`. + Equivalent to ``series * others``, but with support to substitute a fill_value for + missing data in either one of the inputs. Args: - other (scalar or Series): - Object to which Series should be added. + other (Series, or scalar value): Returns: - bigframes.pandas.Series: - The result of adding Series to `other`. + Series: The result of the operation. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - def sub( - self, - other, - ) -> Series: - """Return subtraction of Series and other, element-wise (binary operator - sub). - - Equivalent to ``series - other``, but with support to substitute a - fill_value for missing data in either one of the inputs. - - **Examples:** - - - >>> a = bpd.Series([1, 1, 1, np.nan], index=['a', 'b', 'c', 'd']) - >>> a - a 1.0 - b 1.0 - c 1.0 - d - dtype: Float64 - - >>> b = bpd.Series([1, np.nan, 1, np.nan], index=['a', 'b', 'd', 'e']) - >>> b - a 1.0 - b - d 1.0 - e - dtype: Float64 + def truediv(self, other) -> Series: + """Return floating division of Series and other, element-wise (binary operator truediv). - >>> a.subtract(b) - a 0.0 - b - c - d - e - dtype: Float64 + Equivalent to ``series / other``, but with support to substitute a fill_value for + missing data in either one of the inputs. Args: - other (Series, or scalar value) + other (Series, or scalar value): Returns: - bigframes.pandas.Series: - The result of the operation. + bigframes.series.Series: The result of the operation. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - def __sub__(self, other): - """Get subtraction of other from Series, element-wise, using operator `-`. - - Equivalent to `Series.sub(other)`. - - **Examples:** - - - >>> s = bpd.Series([1.5, 2.6], index=['elk', 'moose']) - >>> s - elk 1.5 - moose 2.6 - dtype: Float64 - - You can subtract a scalar. - - >>> s - 1.5 - elk 0.0 - moose 1.1 - dtype: Float64 - - You can subtract another Series with index aligned. - - >>> delta = bpd.Series([0.5, 1.0], index=['elk', 'moose']) - >>> s - delta - elk 1.0 - moose 1.6 - dtype: Float64 - - Adding any mis-aligned index will result in invalid values. + def rtruediv(self, other) -> Series: + """Return floating division of Series and other, element-wise (binary operator rtruediv). - >>> delta = bpd.Series([0.5, 1.0], index=['moose', 'bison']) - >>> s - delta - elk - moose 2.1 - bison - dtype: Float64 + Equivalent to ``other / series``, but with support to substitute a fill_value for + missing data in either one of the inputs. Args: - other (scalar or Series): - Object to subtract from the Series. + other (Series, or scalar value): Returns: - bigframes.pandas.Series: - The result of subtraction. + bigframes.series.Series: The result of the operation. + """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - def rsub(self, other) -> Series: - """Return subtraction of Series and other, element-wise (binary operator - rsub). - - Equivalent to ``other - series``, but with support to substitute a - fill_value for missing data in either one of the inputs. - - **Examples:** - - - >>> a = bpd.Series([1, 1, 1, np.nan], index=['a', 'b', 'c', 'd']) - >>> a - a 1.0 - b 1.0 - c 1.0 - d - dtype: Float64 - - >>> b = bpd.Series([1, np.nan, 1, np.nan], index=['a', 'b', 'd', 'e']) - >>> b - a 1.0 - b - d 1.0 - e - dtype: Float64 + def floordiv(self, other) -> Series: + """Return integer division of Series and other, element-wise (binary operator floordiv). - >>> a.subtract(b) - a 0.0 - b - c - d - e - dtype: Float64 + Equivalent to ``series // other``, but with support to substitute a fill_value for + missing data in either one of the inputs. Args: - other (Series, or scalar value) + other (Series, or scalar value): Returns: - bigframes.pandas.Series: - The result of the operation. + bigframes.series.Series: The result of the operation. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - def __rsub__(self, other): - """Get subtraction of Series from other, element-wise, using operator `-`. + def rfloordiv(self, other) -> Series: + """Return integer division of Series and other, element-wise (binary operator rfloordiv). - Equivalent to `Series.rsub(other)`. + Equivalent to ``other // series``, but with support to substitute a fill_value for + missing data in either one of the inputs. Args: - other (scalar or Series): - Object to subtract the Series from. + other (Series, or scalar value): Returns: - bigframes.pandas.Series: - The result of subtraction. + bigframes.series.Series: The result of the operation. + """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - def mul(self, other) -> Series: - """Return multiplication of Series and other, element-wise (binary - operator mul). + def mod(self, other) -> Series: + """Return modulo of Series and other, element-wise (binary operator mod). - Equivalent to ``other * series``, but with support to substitute a - fill_value for missing data in either one of the inputs. + Equivalent to ``series % other``, but with support to substitute a fill_value for + missing data in either one of the inputs. - **Examples:** + Args: + other (Series, or scalar value): + Returns: + bigframes.series.Series: The result of the operation. - >>> a = bpd.Series([1, 1, 1, np.nan], index=['a', 'b', 'c', 'd']) - >>> a - a 1.0 - b 1.0 - c 1.0 - d - dtype: Float64 + """ + raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - >>> b = bpd.Series([1, np.nan, 1, np.nan], index=['a', 'b', 'd', 'e']) - >>> b - a 1.0 - b - d 1.0 - e - dtype: Float64 + def rmod(self, other) -> Series: + """Return modulo of Series and other, element-wise (binary operator mod). - >>> a.multiply(b) - a 1.0 - b - c - d - e - dtype: Float64 + Equivalent to ``series % other``, but with support to substitute a fill_value for + missing data in either one of the inputs. Args: - other (Series or scalar value) + other (Series, or scalar value): Returns: - bigframes.pandas.Series: - The result of the operation. + bigframes.series.Series: The result of the operation. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - def __mul__(self, other): - """ - Get multiplication of Series with other, element-wise, using operator `*`. - - Equivalent to `Series.mul(other)`. + def pow(self, other) -> Series: + """Return Exponential power of series and other, element-wise (binary operator `pow`). - **Examples:** + Equivalent to ``series ** other``, but with support to substitute a fill_value for + missing data in either one of the inputs. + Args: + other (Series, or scalar value): - You can multiply with a scalar: + Returns: + bigframes.series.Series: The result of the operation. - >>> s = bpd.Series([1, 2, 3]) - >>> s * 3 - 0 3 - 1 6 - 2 9 - dtype: Int64 + """ + raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - You can also multiply with another Series: + def rpow(self, other) -> Series: + """Return Exponential power of series and other, element-wise (binary operator `rpow`). - >>> s1 = bpd.Series([2, 3, 4]) - >>> s * s1 - 0 2 - 1 6 - 2 12 - dtype: Int64 + Equivalent to ``other ** series``, but with support to substitute a fill_value for + missing data in either one of the inputs. Args: - other (scalar or Series): - Object to multiply with the Series. + other (Series, or scalar value): Returns: - bigframes.pandas.Series: - The result of the multiplication. + bigframes.series.Series: The result of the operation. + """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - def rmul(self, other) -> Series: - """Return multiplication of Series and other, element-wise (binary - operator mul). + def divmod(self, other) -> Series: + """Return integer division and modulo of Series and other, element-wise (binary operator divmod). - Equivalent to ``series * others``, but with support to substitute a - fill_value for missing data in either one of the inputs. + Equivalent to divmod(series, other). - **Examples:** + Args: + other: Series, or scalar value + Returns: + 2-Tuple of Series. The result of the operation. The result is always + consistent with (floordiv, mod) (though pandas may not). - >>> a = bpd.Series([1, 1, 1, np.nan], index=['a', 'b', 'c', 'd']) - >>> a - a 1.0 - b 1.0 - c 1.0 - d - dtype: Float64 + """ + raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - >>> b = bpd.Series([1, np.nan, 1, np.nan], index=['a', 'b', 'd', 'e']) - >>> b - a 1.0 - b - d 1.0 - e - dtype: Float64 + def rdivmod(self, other) -> Series: + """Return integer division and modulo of Series and other, element-wise (binary operator rdivmod). - >>> a.multiply(b) - a 1.0 - b - c - d - e - dtype: Float64 + Equivalent to other divmod series. Args: - other (Series or scalar value) + other: Series, or scalar value Returns: - bigframes.pandas.Series: - The result of the operation. + 2-Tuple of Series. The result of the operation. The result is always + consistent with (rfloordiv, rmod) (though pandas may not). + """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - def __rmul__(self, other): + def all( + self, + ): """ - Get multiplication of other with Series, element-wise, using operator `*`. - - Equivalent to `Series.rmul(other)`. + Return whether all elements are True, potentially over an axis. - Args: - other (scalar or Series): - Object to multiply the Series with. + Returns True unless there at least one element within a Series or along a + DataFrame axis that is False or equivalent (e.g. zero or empty). Returns: - bigframes.pandas.Series: The result of the multiplication. + scalar or Series: If level is specified, then, Series is returned; + otherwise, scalar is returned. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - def truediv(self, other) -> Series: - """Return floating division of Series and other, element-wise (binary - operator truediv). - - Equivalent to ``series / other``, but with support to substitute a - fill_value for missing data in either one of the inputs. - - **Examples:** + def any( + self, + ): + """ + Return whether any element is True, potentially over an axis. + Returns False unless there is at least one element within a series or along + a Dataframe axis that is True or equivalent (e.g. non-zero or non-empty). - >>> a = bpd.Series([1, 1, 1, np.nan], index=['a', 'b', 'c', 'd']) - >>> a - a 1.0 - b 1.0 - c 1.0 - d - dtype: Float64 + Returns: + scalar or Series: If level is specified, then, Series is returned; + otherwise, scalar is returned. + """ + raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - >>> b = bpd.Series([1, np.nan, 1, np.nan], index=['a', 'b', 'd', 'e']) - >>> b - a 1.0 - b - d 1.0 - e - dtype: Float64 + def max( + self, + ): + """ + Return the maximum of the values over the requested axis. - >>> a.divide(b) - a 1.0 - b - c - d - e - dtype: Float64 + If you want the index of the maximum, use ``idxmax``. This is the equivalent + of the ``numpy.ndarray`` method ``argmax``. - Args: - other (Series or scalar value) Returns: - bigframes.pandas.Series: - The result of the operation. - + scalar or scalar """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - def __truediv__(self, other): + def min( + self, + ): """ - Get division of Series by other, element-wise, using operator `/`. + Return the maximum of the values over the requested axis. - Equivalent to `Series.truediv(other)`. + If you want the index of the minimum, use ``idxmin``. This is the equivalent + of the ``numpy.ndarray`` method ``argmin``. - **Examples:** + Returns: + scalar or scalar + """ + raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) + def std( + self, + ): + """ + Return sample standard deviation over requested axis. - You can multiply with a scalar: + Normalized by N-1 by default. - >>> s = bpd.Series([1, 2, 3]) - >>> s / 2 - 0 0.5 - 1 1.0 - 2 1.5 - dtype: Float64 - You can also multiply with another Series: + Returns + ------- + scalar or Series (if level specified) + """ + raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - >>> denominator = bpd.Series([2, 3, 4]) - >>> s / denominator - 0 0.5 - 1 0.666667 - 2 0.75 - dtype: Float64 + def var( + self, + ): + """ + Return unbiased variance over requested axis. - Args: - other (scalar or Series): - Object to divide the Series by. + Normalized by N-1 by default. Returns: - bigframes.pandas.Series: - The result of the division. + scalar or Series (if level specified) """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - def rtruediv(self, other) -> Series: - """Return floating division of Series and other, element-wise (binary - operator rtruediv). - - Equivalent to ``other / series``, but with support to substitute a - fill_value for missing data in either one of the inputs. + def sum(self): + """Return the sum of the values over the requested axis. - **Examples:** + This is equivalent to the method ``numpy.sum``. + Returns: + scalar + """ + raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - >>> a = bpd.Series([1, 1, 1, np.nan], index=['a', 'b', 'c', 'd']) - >>> a - a 1.0 - b 1.0 - c 1.0 - d - dtype: Float64 + def mean(self): + """Return the mean of the values over the requested axis. - >>> b = bpd.Series([1, np.nan, 1, np.nan], index=['a', 'b', 'd', 'e']) - >>> b - a 1.0 - b - d 1.0 - e - dtype: Float64 + Returns: + scalar + """ + raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - >>> a.divide(b) - a 1.0 - b - c - d - e - dtype: Float64 + def median(self, *, exact: bool = False): + """Return the median of the values over the requested axis. Args: - other (Series or scalar value) + exact (bool. default False): + Default False. Get the exact median instead of an approximate + one. Note: ``exact=True`` not yet supported. Returns: - bigframes.pandas.Series: - The result of the operation. - + scalar """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - def __rtruediv__(self, other): + def prod(self): + """Return the product of the values over the requested axis. + + Returns: + scalar """ - Get division of other by Series, element-wise, using operator `/`. + raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - Equivalent to `Series.rtruediv(other)`. + def skew(self): + """Return unbiased skew over requested axis. - Args: - other (scalar or Series): - Object to divide by the Series. + Normalized by N-1. Returns: - bigframes.pandas.Series: The result of the division. + scalar """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - def floordiv(self, other) -> Series: - """Return integer division of Series and other, element-wise - (binary operator floordiv). - - Equivalent to ``series // other``, but with support to substitute a - fill_value for missing data in either one of the inputs. + def kurt(self): + """Return unbiased kurtosis over requested axis. - **Examples:** - - - >>> a = bpd.Series([1, 1, 1, np.nan], index=['a', 'b', 'c', 'd']) - >>> a - a 1.0 - b 1.0 - c 1.0 - d - dtype: Float64 - - >>> b = bpd.Series([1, np.nan, 1, np.nan], index=['a', 'b', 'd', 'e']) - >>> b - a 1.0 - b - d 1.0 - e - dtype: Float64 - - >>> a.floordiv(b) - a 1.0 - b - c - d - e - dtype: Float64 - - Args: - other (Series or scalar value) - - Returns: - bigframes.pandas.Series: - The result of the operation. - - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - def __floordiv__(self, other): - """ - Get integer division of Series by other, using arithmetic operator `//`. - - Equivalent to `Series.floordiv(other)`. - - **Examples:** - - - You can divide by a scalar: - - >>> s = bpd.Series([15, 30, 45]) - >>> s // 2 - 0 7 - 1 15 - 2 22 - dtype: Int64 - - You can also divide by another DataFrame: - - >>> divisor = bpd.Series([3, 4, 4]) - >>> s // divisor - 0 5 - 1 7 - 2 11 - dtype: Int64 - - Args: - other (scalar or Series): - Object to divide the Series by. - - Returns: - bigframes.pandas.Series: - The result of the integer division. - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - def rfloordiv(self, other) -> Series: - """Return integer division of Series and other, element-wise (binary - operator rfloordiv). - - Equivalent to ``other // series``, but with support to substitute a - fill_value for missing data in either one of the inputs. - - **Examples:** - - - >>> a = bpd.Series([1, 1, 1, np.nan], index=['a', 'b', 'c', 'd']) - >>> a - a 1.0 - b 1.0 - c 1.0 - d - dtype: Float64 - - >>> b = bpd.Series([1, np.nan, 1, np.nan], index=['a', 'b', 'd', 'e']) - >>> b - a 1.0 - b - d 1.0 - e - dtype: Float64 - - >>> a.floordiv(b) - a 1.0 - b - c - d - e - dtype: Float64 - - Args: - other (Series or scalar value) - - Returns: - bigframes.pandas.Series: - The result of the operation. - - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - def __rfloordiv__(self, other): - """ - Get integer division of other by Series, using arithmetic operator `//`. - - Equivalent to `Series.rfloordiv(other)`. - - Args: - other (scalar or Series): - Object to divide by the Series. - - Returns: - bigframes.pandas.Series: - The result of the integer division. - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - def mod(self, other) -> Series: - """Return modulo of Series and other, element-wise (binary operator mod). - - Equivalent to ``series % other``, but with support to substitute a - fill_value for missing data in either one of the inputs. - - **Examples:** - - - >>> a = bpd.Series([1, 1, 1, np.nan], index=['a', 'b', 'c', 'd']) - >>> a - a 1.0 - b 1.0 - c 1.0 - d - dtype: Float64 - - >>> b = bpd.Series([1, np.nan, 1, np.nan], index=['a', 'b', 'd', 'e']) - >>> b - a 1.0 - b - d 1.0 - e - dtype: Float64 - - >>> a.mod(b) - a 0.0 - b - c - d - e - dtype: Float64 - - Args: - other (Series or scalar value) - - Returns: - bigframes.pandas.Series: - The result of the operation. - - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - def __mod__(self, other): - """ - Get modulo of Series with other, element-wise, using operator `%`. - - Equivalent to `Series.mod(other)`. - - **Examples:** - - - You can modulo with a scalar: - - >>> s = bpd.Series([1, 2, 3]) - >>> s % 3 - 0 1 - 1 2 - 2 0 - dtype: Int64 - - You can also modulo with another Series: - - >>> modulo = bpd.Series([3, 3, 3]) - >>> s % modulo - 0 1 - 1 2 - 2 0 - dtype: Int64 - - Args: - other (scalar or Series): - Object to modulo the Series by. - - Returns: - bigframes.pandas.Series: - The result of the modulo. - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - def rmod(self, other) -> Series: - """Return modulo of Series and other, element-wise (binary operator mod). - - Equivalent to ``series % other``, but with support to substitute a - fill_value for missing data in either one of the inputs. - - **Examples:** - - - >>> a = bpd.Series([1, 1, 1, np.nan], index=['a', 'b', 'c', 'd']) - >>> a - a 1.0 - b 1.0 - c 1.0 - d - dtype: Float64 - - >>> b = bpd.Series([1, np.nan, 1, np.nan], index=['a', 'b', 'd', 'e']) - >>> b - a 1.0 - b - d 1.0 - e - dtype: Float64 - - >>> a.mod(b) - a 0.0 - b - c - d - e - dtype: Float64 - - Args: - other (Series or scalar value) - - Returns: - bigframes.pandas.Series: - The result of the operation. - - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - def __rmod__(self, other): - """ - Get modulo of other with Series, element-wise, using operator `%`. - - Equivalent to `Series.rmod(other)`. - - Args: - other (scalar or Series): - Object to modulo by the Series. - - Returns: - bigframes.pandas.Series: - The result of the modulo. - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - def pow(self, other) -> Series: - """Return Exponential power of series and other, element-wise (binary - operator `pow`). - - Equivalent to ``series ** other``, but with support to substitute a - fill_value for missing data in either one of the inputs. - - **Examples:** - - >>> import bigframes.pandas as bpd - >>> a = bpd.Series([1, 1, 1, np.nan], index=['a', 'b', 'c', 'd']) - >>> a - a 1.0 - b 1.0 - c 1.0 - d - dtype: Float64 - - >>> b = bpd.Series([1, np.nan, 1, np.nan], index=['a', 'b', 'd', 'e']) - >>> b - a 1.0 - b - d 1.0 - e - dtype: Float64 - - >>> a.pow(b) - a 1.0 - b 1.0 - c 1.0 - d - e - dtype: Float64 - - Args: - other (Series or scalar value) - - Returns: - bigframes.pandas.Series: - The result of the operation. - - """ - # TODO(b/452366836): adjust sample if needed to match pyarrow semantics. - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - def __pow__(self, other): - """ - Get exponentiation of Series with other, element-wise, using operator - `**`. - - Equivalent to `Series.pow(other)`. - - **Examples:** - - - You can exponentiate with a scalar: - - >>> s = bpd.Series([1, 2, 3]) - >>> s ** 2 - 0 1 - 1 4 - 2 9 - dtype: Int64 - - You can also exponentiate with another Series: - - >>> exponent = bpd.Series([3, 2, 1]) - >>> s ** exponent - 0 1 - 1 4 - 2 3 - dtype: Int64 - - Args: - other (scalar or Series): - Object to exponentiate the Series with. - - Returns: - bigframes.pandas.Series: - The result of the exponentiation. - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - def rpow(self, other) -> Series: - """Return Exponential power of series and other, element-wise (binary - operator `rpow`). - - Equivalent to ``other ** series``, but with support to substitute a - fill_value for missing data in either one of the inputs. - - **Examples:** - - >>> import bigframes.pandas as bpd - >>> a = bpd.Series([1, 1, 1, np.nan], index=['a', 'b', 'c', 'd']) - >>> a - a 1.0 - b 1.0 - c 1.0 - d - dtype: Float64 - - >>> b = bpd.Series([1, np.nan, 1, np.nan], index=['a', 'b', 'd', 'e']) - >>> b - a 1.0 - b - d 1.0 - e - dtype: Float64 - - >>> a.pow(b) - a 1.0 - b 1.0 - c 1.0 - d - e - dtype: Float64 - - Args: - other (Series or scalar value) - - Returns: - bigframes.pandas.Series: - The result of the operation. - - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - def __rpow__(self, other): - """ - Get exponentiation of other with Series, element-wise, using operator - `**`. - - Equivalent to `Series.rpow(other)`. - - Args: - other (scalar or Series): - Object to exponentiate with the Series. - - Returns: - bigframes.pandas.Series: - The result of the exponentiation. - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - def divmod(self, other) -> Series: - """Return integer division and modulo of Series and other, element-wise - (binary operator divmod). - - Equivalent to divmod(series, other). - - **Examples:** - - - >>> a = bpd.Series([1, 1, 1, np.nan], index=['a', 'b', 'c', 'd']) - >>> a - a 1.0 - b 1.0 - c 1.0 - d - dtype: Float64 - - >>> b = bpd.Series([1, np.nan, 1, np.nan], index=['a', 'b', 'd', 'e']) - >>> b - a 1.0 - b - d 1.0 - e - dtype: Float64 - - >>> a.divmod(b) - (a 1.0 - b - c - d - e - dtype: Float64, - a 0.0 - b - c - d - e - dtype: Float64) - - Args: - other: Series, or scalar value - - Returns: - Tuple[bigframes.pandas.Series, bigframes.pandas.Series]: - The result of the operation. The result is always - consistent with (floordiv, mod) (though pandas may not). - - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - def rdivmod(self, other) -> Series: - """Return integer division and modulo of Series and other, element-wise (binary operator rdivmod). - - Equivalent to other divmod series. - - **Examples:** - - - >>> a = bpd.Series([1, 1, 1, np.nan], index=['a', 'b', 'c', 'd']) - >>> a - a 1.0 - b 1.0 - c 1.0 - d - dtype: Float64 - - >>> b = bpd.Series([1, np.nan, 1, np.nan], index=['a', 'b', 'd', 'e']) - >>> b - a 1.0 - b - d 1.0 - e - dtype: Float64 - - >>> a.divmod(b) - (a 1.0 - b - c - d - e - dtype: Float64, - a 0.0 - b - c - d - e - dtype: Float64) - - Args: - other: Series, or scalar value - - Returns: - Tuple[bigframes.pandas.Series, bigframes.pandas.Series]: - The result of the operation. The result is always - consistent with (rfloordiv, rmod) (though pandas may not). - - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - def combine_first(self, other) -> Series: - """ - Update null elements with value in the same location in 'other'. - - Combine two Series objects by filling null values in one Series with - non-null values from the other Series. Result index will be the union - of the two indexes. - - **Examples:** - - - >>> s1 = bpd.Series([1, np.nan]) - >>> s2 = bpd.Series([3, 4, 5]) - >>> s1.combine_first(s2) - 0 1.0 - 1 4.0 - 2 5.0 - dtype: Float64 - - Null values still persist if the location of that null value - does not exist in `other` - - >>> s1 = bpd.Series({'falcon': np.nan, 'eagle': 160.0}) - >>> s2 = bpd.Series({'eagle': 200.0, 'duck': 30.0}) - >>> s1.combine_first(s2) - falcon - eagle 160.0 - duck 30.0 - dtype: Float64 - - Args: - other (Series): - The value(s) to be used for filling null values. - - Returns: - bigframes.pandas.Series: - The result of combining the provided Series with the other object. - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - def update(self, other) -> None: - """ - Modify Series in place using values from passed Series. - - Uses non-NA values from passed Series to make updates. Aligns - on index. - - **Examples:** - - - >>> s = bpd.Series([1, 2, 3]) - >>> s.update(bpd.Series([4, 5, 6])) - >>> s - 0 4 - 1 5 - 2 6 - dtype: Int64 - - >>> s = bpd.Series(['a', 'b', 'c']) - >>> s.update(bpd.Series(['d', 'e'], index=[0, 2])) - >>> s - 0 d - 1 b - 2 e - dtype: string - - >>> s = bpd.Series([1, 2, 3]) - >>> s.update(bpd.Series([4, 5, 6, 7, 8])) - >>> s - 0 4 - 1 5 - 2 6 - dtype: Int64 - - If ``other`` contains NA (NULL values) the corresponding values are not updated - in the original Series. - - >>> s = bpd.Series([1, 2, 3]) - >>> s.update(bpd.Series([4, np.nan, 6], dtype=pd.Int64Dtype())) - >>> s - 0 4 - 1 2 - 2 6 - dtype: Int64 - - ``other`` can also be a non-Series object type - that is coercible into a Series - - >>> s = bpd.Series([1, 2, 3]) - >>> s.update([4, np.nan, 6]) - >>> s - 0 4.0 - 1 2.0 - 2 6.0 - dtype: Float64 - - >>> s = bpd.Series([1, 2, 3]) - >>> s.update({1: 9}) - >>> s - 0 1 - 1 9 - 2 3 - dtype: Int64 - - Args: - other (Series, or object coercible into Series) - - Returns: - None - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - def all( - self, - ): - """ - Return whether all elements are True, potentially over an axis. - - Returns True unless there at least one element within a Series or along a - DataFrame axis that is False or equivalent (e.g. zero or empty). - - Returns: - scalar or bigframes.pandas.Series: - If level is specified, then, Series is returned; - otherwise, scalar is returned. - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - def any( - self, - ): - """ - Return whether any element is True, potentially over an axis. - - Returns False unless there is at least one element within a series or along - a Dataframe axis that is True or equivalent (e.g. non-zero or non-empty). - - **Examples:** - - - For Series input, the output is a scalar indicating whether any element is True. - - >>> bpd.Series([False, False]).any() - np.False_ - - >>> bpd.Series([True, False]).any() - np.True_ - - >>> bpd.Series([], dtype="float64").any() - np.False_ - - >>> bpd.Series([np.nan]).any() - np.False_ - - Returns: - scalar or bigframes.pandas.Series: - If level is specified, then, Series is returned; - otherwise, scalar is returned. - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - def max( - self, - ): - """ - Return the maximum of the values over the requested axis. - - If you want the index of the maximum, use ``idxmax``. This is the equivalent - of the ``numpy.ndarray`` method ``argmax``. - - **Examples:** - - - Calculating the max of a Series: - - >>> s = bpd.Series([1, 3]) - >>> s - 0 1 - 1 3 - dtype: Int64 - - >>> s.max() - np.int64(3) - - Calculating the max of a Series containing ``NA`` values: - - >>> s = bpd.Series([1, 3, pd.NA]) - >>> s - 0 1 - 1 3 - 2 - dtype: Int64 - - >>> s.max() - np.int64(3) - - Returns: - scalar: Scalar. - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - def min( - self, - ): - """ - Return the maximum of the values over the requested axis. - - If you want the index of the minimum, use ``idxmin``. This is the equivalent - of the ``numpy.ndarray`` method ``argmin``. - - **Examples:** - - - Calculating the min of a Series: - - >>> s = bpd.Series([1, 3]) - >>> s - 0 1 - 1 3 - dtype: Int64 - - >>> s.min() - np.int64(1) - - Calculating the min of a Series containing ``NA`` values: - - >>> s = bpd.Series([1, 3, pd.NA]) - >>> s - 0 1 - 1 3 - 2 - dtype: Int64 - - >>> s.min() - np.int64(1) - - Returns: - scalar: Scalar. - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - def std( - self, - ): - """ - Return sample standard deviation over requested axis. - - Normalized by N-1 by default. - - **Examples:** - - - >>> df = bpd.DataFrame({'person_id': [0, 1, 2, 3], - ... 'age': [21, 25, 62, 43], - ... 'height': [1.61, 1.87, 1.49, 2.01]} - ... ).set_index('person_id') - >>> df - age height - person_id - 0 21 1.61 - 1 25 1.87 - 2 62 1.49 - 3 43 2.01 - - [4 rows x 2 columns] - - >>> df.std() - age 18.786076 - height 0.237417 - dtype: Float64 - - Returns: - scalar or bigframes.pandas.Series (if level specified) - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - def var( - self, - ): - """ - Return unbiased variance over requested axis. - - Normalized by N-1 by default. - - Returns: - scalar or bigframes.pandas.Series (if level specified): - Variance. - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - def sum(self): - """Return the sum of the values over the requested axis. - - This is equivalent to the method ``numpy.sum``. - - **Examples:** - - - Calculating the sum of a Series: - - >>> s = bpd.Series([1, 3]) - >>> s - 0 1 - 1 3 - dtype: Int64 - - >>> s.sum() - np.int64(4) - - Calculating the sum of a Series containing ``NA`` values: - - >>> s = bpd.Series([1, 3, pd.NA]) - >>> s - 0 1 - 1 3 - 2 - dtype: Int64 - - >>> s.sum() - np.int64(4) - - Returns: - scalar: Scalar. - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - def mean(self): - """Return the mean of the values over the requested axis. - - **Examples:** - - - Calculating the mean of a Series: - - >>> s = bpd.Series([1, 3]) - >>> s - 0 1 - 1 3 - dtype: Int64 - - >>> s.mean() - np.float64(2.0) - - Calculating the mean of a Series containing ``NA`` values: - - >>> s = bpd.Series([1, 3, pd.NA]) - >>> s - 0 1 - 1 3 - 2 - dtype: Int64 - - >>> s.mean() - np.float64(2.0) - - Returns: - scalar: Scalar. - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - def median(self, *, exact: bool = True): - """Return the median of the values over the requested axis. - - **Examples:** - - >>> import bigframes.pandas as bpd - >>> s = bpd.Series([1, 2, 3]) - >>> s.median() - np.float64(2.0) - - With a DataFrame - - >>> df = bpd.DataFrame({'a': [1, 2], 'b': [2, 3]}, index=['tiger', 'zebra']) - >>> df - a b - tiger 1 2 - zebra 2 3 - - [2 rows x 2 columns] - - >>> df.median() - a 1.5 - b 2.5 - dtype: Float64 - - Args: - exact (bool. default True): - Default True. Get the exact median instead of an approximate - one. - - Returns: - scalar: Scalar. - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - def quantile( - self, - q: Union[float, Sequence[float]] = 0.5, - ) -> Union[Series, float]: - """ - Return value at the given quantile. - - **Examples:** - - >>> import bigframes.pandas as bpd - >>> s = bpd.Series([1, 2, 3, 4]) - >>> s.quantile(.5) - np.float64(2.5) - - >>> s.quantile([.25, .5, .75]) - 0.25 1.75 - 0.5 2.5 - 0.75 3.25 - dtype: Float64 - - Args: - q (Union[float, Sequence[float], default 0.5 (50% quantile)): - The quantile(s) to compute, which can lie in range: 0 <= q <= 1. - - Returns: - Union[float, bigframes.pandas.Series]: - If ``q`` is an array, a Series will be returned where the - index is ``q`` and the values are the quantiles, otherwise - a float will be returned. - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - def prod(self): - """Return the product of the values over the requested axis. - - Returns: - scalar: Scalar. - """ - 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:** - - - >>> 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. - - Normalized by N-1. - - **Examples:** - - - >>> s = bpd.Series([1, 2, 3]) - >>> s.skew() - np.float64(0.0) - - With a DataFrame - - >>> df = bpd.DataFrame({'a': [1, 2, 3], 'b': [2, 3, 4], 'c': [1, 3, 5]}, - ... index=['tiger', 'zebra', 'cow']) - >>> df - a b c - tiger 1 2 1 - zebra 2 3 3 - cow 3 4 5 - - [3 rows x 3 columns] - - >>> df.skew() - a 0.0 - b 0.0 - c 0.0 - dtype: Float64 - - Returns: - scalar: Scalar. - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - def kurt(self): - """Return unbiased kurtosis over requested axis. - - Kurtosis obtained using Fisher’s definition of kurtosis (kurtosis of - normal == 0.0). Normalized by N-1. - - **Examples:** - - - >>> s = bpd.Series([1, 2, 2, 3], index=['cat', 'dog', 'dog', 'mouse']) - >>> s - cat 1 - dog 2 - dog 2 - mouse 3 - dtype: Int64 - - >>> s.kurt() - np.float64(1.5) - - With a DataFrame - - >>> df = bpd.DataFrame({'a': [1, 2, 2, 3], 'b': [3, 4, 4, 4]}, - ... index=['cat', 'dog', 'dog', 'mouse']) - >>> df - a b - cat 1 3 - dog 2 4 - dog 2 4 - mouse 3 4 - - [4 rows x 2 columns] - - >>> df.kurt() - a 1.5 - b 4.0 - dtype: Float64 - - Returns: - scalar or scalar: - Unbiased kurtosis over requested axis. - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - def item(self: Series, *args, **kwargs): - """Return the first element of the underlying data as a Python scalar. - - **Examples:** - - >>> s = bpd.Series([1]) - >>> s.item() - np.int64(1) - - Returns: - scalar: The first element of Series. - - Raises: - ValueError: If the data is not length = 1. - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - def items(self): - """ - Lazily iterate over (index, value) tuples. - - This method returns an iterable tuple (index, value). - This is convenient if you want to create a lazy iterator. - - **Examples:** - - - >>> s = bpd.Series(['A', 'B', 'C']) - >>> for index, value in s.items(): - ... print(f"Index : {index}, Value : {value}") - Index : 0, Value : A - Index : 1, Value : B - Index : 2, Value : C + Kurtosis obtained using Fisher’s definition of kurtosis (kurtosis of normal == 0.0). Normalized by N-1. Returns: - iterable: - Iterable of tuples containing the (index, value) pairs from a - Series. + scalar or scalar: Unbiased kurtosis over requested axis. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) def where(self, cond, other): """Replace values where the condition is False. - **Examples:** - - - >>> s = bpd.Series([10, 11, 12, 13, 14]) - >>> s - 0 10 - 1 11 - 2 12 - 3 13 - 4 14 - dtype: Int64 - - You can filter the values in the Series based on a condition. The values - matching the condition would be kept, and not matching would be replaced. - The default replacement value is ``NA``. - - >>> s.where(s % 2 == 0) - 0 10 - 1 - 2 12 - 3 - 4 14 - dtype: Int64 - - You can specify a custom replacement value for non-matching values. - - >>> s.where(s % 2 == 0, -1) - 0 10 - 1 -1 - 2 12 - 3 -1 - 4 14 - dtype: Int64 - >>> s.where(s % 2 == 0, 100*s) - 0 10 - 1 1100 - 2 12 - 3 1300 - 4 14 - dtype: Int64 - Args: cond (bool Series/DataFrame, array-like, or callable): Where cond is True, keep the original value. Where False, replace @@ -4987,132 +1639,35 @@ def where(self, cond, other): extension dtypes). Returns: - bigframes.pandas.Series: - Series after the replacement. + bigframes.series.Series: Series after the replacement. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) def mask(self, cond, other): """Replace values where the condition is True. - **Examples:** - - >>> s = bpd.Series([10, 11, 12, 13, 14]) - >>> s - 0 10 - 1 11 - 2 12 - 3 13 - 4 14 - dtype: Int64 - - You can mask the values in the Series based on a condition. The values - matching the condition would be masked. The condition can be provided in - formm of a Series. - - >>> s.mask(s % 2 == 0) - 0 - 1 11 - 2 - 3 13 - 4 - dtype: Int64 - - You can specify a custom mask value. - - >>> s.mask(s % 2 == 0, -1) - 0 -1 - 1 11 - 2 -1 - 3 13 - 4 -1 - dtype: Int64 - >>> s.mask(s % 2 == 0, 100*s) - 0 1000 - 1 11 - 2 1200 - 3 13 - 4 1400 - dtype: Int64 - - You can also use a remote function to evaluate the mask condition. This - is useful in situation such as the following, where the mask - condition is evaluated based on a complicated business logic which cannot - be expressed in form of a Series. - - >>> @bpd.remote_function(reuse=False, cloud_function_service_account="default") # doctest: +SKIP - ... def should_mask(name: str) -> bool: - ... hash = 0 - ... for char_ in name: - ... hash += ord(char_) - ... return hash % 2 == 0 - - >>> s = bpd.Series(["Alice", "Bob", "Caroline"]) - >>> s - 0 Alice - 1 Bob - 2 Caroline - dtype: string - >>> s.mask(should_mask) # doctest: +SKIP - 0 - 1 Bob - 2 Caroline - dtype: string - >>> s.mask(should_mask, "REDACTED") # doctest: +SKIP - 0 REDACTED - 1 Bob - 2 Caroline - dtype: string - - Simple vectorized (i.e. they only perform operations supported on a - Series) lambdas or python functions can be used directly. - - >>> nums = bpd.Series([1, 2, 3, 4], name="nums") - >>> nums - 0 1 - 1 2 - 2 3 - 3 4 - Name: nums, dtype: Int64 - >>> nums.mask(lambda x: (x+1) % 2 == 1) - 0 1 - 1 - 2 3 - 3 - Name: nums, dtype: Int64 - - >>> def is_odd(num): - ... return num % 2 == 1 - >>> nums.mask(is_odd) - 0 - 1 2 - 2 - 3 4 - Name: nums, dtype: Int64 - Args: cond (bool Series/DataFrame, array-like, or callable): Where cond is False, keep the original value. Where True, replace with corresponding value from other. If cond is callable, it is computed on the Series/DataFrame and should return boolean Series/DataFrame or array. The callable must not change input - Series/DataFrame (though pandas doesn't check it). + Series/DataFrame (though pandas doesn’t check it). other (scalar, Series/DataFrame, or callable): Entries where cond is True are replaced with corresponding value from other. If other is callable, it is computed on the Series/DataFrame and should return scalar or Series/DataFrame. The callable must not change input Series/DataFrame (though pandas - doesn't check it). If not specified, entries will be filled with + doesn’t check it). If not specified, entries will be filled with the corresponding NULL value (np.nan for numpy dtypes, pd.NA for extension dtypes). Returns: - bigframes.pandas.Series: - Series after the replacement. + bigframes.series.Series: Series after the replacement. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - def clip(self, lower, upper): + def clip(self): """Trim values at input threshold(s). Assigns values outside boundary to boundary values. Thresholds can be @@ -5121,16 +1676,13 @@ def clip(self, lower, upper): Args: lower (float or array-like, default None): - Minimum threshold value. All values below this threshold will - be set to it. A missing threshold (e.g NA) will not clip the value. + Minimum threshold value. All values below this threshold will be set to it. A missing threshold (e.g NA) will not clip the value. upper (float or array-like, default None): - Maximum threshold value. All values above this threshold will - be set to it. A missing threshold (e.g NA) will not clip the value. + Maximum threshold value. All values above this threshold will be set to it. A missing threshold (e.g NA) will not clip the value. Returns: - bigframes.pandas.Series: - Series. + Series. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @@ -5138,84 +1690,38 @@ def unstack(self, level): """ Unstack, also known as pivot, Series with MultiIndex to produce DataFrame. + Args: + level (int, str, or list of these, default last level): + Level(s) to unstack, can pass level name. + Returns: - bigframes.pandas.DataFrame: Unstacked Series. + DataFrame: Unstacked Series. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) def argmax(self): """ - Return int position of the largest value in the series. - - If the maximum is achieved in multiple locations, the first row position - is returned. - - **Examples:** - - - Consider dataset containing cereal calories. - - >>> s = bpd.Series({'Corn Flakes': 100.0, 'Almond Delight': 110.0, - ... 'Cinnamon Toast Crunch': 120.0, 'Cocoa Puff': 110.0}) - >>> s - Corn Flakes 100.0 - Almond Delight 110.0 - Cinnamon Toast Crunch 120.0 - Cocoa Puff 110.0 - dtype: Float64 - - >>> s.argmax() - np.int64(2) - - >>> s.argmin() - np.int64(0) + Return int position of the smallest value in the Series. - The maximum cereal calories is the third element and the minimum cereal - calories is the first element, since series is zero-indexed. + If the minimum is achieved in multiple locations, the first row position is returned. Returns: - int: - Row position of the maximum value. + Series: Row position of the maximum value. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) def argmin(self): """ - Return int position of the smallest value in the Series. - - If the minimum is achieved in multiple locations, the first row position - is returned. - - **Examples:** - - - Consider dataset containing cereal calories. - - >>> s = bpd.Series({'Corn Flakes': 100.0, 'Almond Delight': 110.0, - ... 'Cinnamon Toast Crunch': 120.0, 'Cocoa Puff': 110.0}) - >>> s - Corn Flakes 100.0 - Almond Delight 110.0 - Cinnamon Toast Crunch 120.0 - Cocoa Puff 110.0 - dtype: Float64 - - >>> s.argmax() - np.int64(2) + Return int position of the largest value in the Series. - >>> s.argmin() - np.int64(0) - - The maximum cereal calories is the third element and the minimum cereal - calories is the first element, since series is zero-indexed. + If the maximum is achieved in multiple locations, the first row position is returned. Returns: - int: - Row position of the minimum value. + Series: Row position of the minimum value. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - def rename(self, index, *, inplace, **kwargs): + def rename(self, index, **kwargs) -> Series | None: """ Alter Series index labels or name. @@ -5225,49 +1731,20 @@ def rename(self, index, *, inplace, **kwargs): Alternatively, change ``Series.name`` with a scalar value. - **Examples:** - - - >>> s = bpd.Series([1, 2, 3]) - >>> s - 0 1 - 1 2 - 2 3 - dtype: Int64 - - You can changes the Series name by specifying a string scalar: - - >>> s.rename("my_name") - 0 1 - 1 2 - 2 3 - Name: my_name, dtype: Int64 - - You can change the labels by specifying a mapping: - - >>> s.rename({1: 3, 2: 5}) - 0 1 - 3 2 - 5 3 - dtype: Int64 - Args: index (scalar, hashable sequence, dict-like or function optional): Functions or dict-like are transformations to apply to the index. Scalar or hashable sequence-like will alter the ``Series.name`` attribute. - inplace (bool): - Default False. Whether to return a new Series. Returns: - bigframes.pandas.Series | None: - Series with index labels or None if ``inplace=True``. + bigframes.series.Series: Series with index labels. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - def rename_axis(self, mapper, *, inplace, **kwargs): + def rename_axis(self, mapper, **kwargs): """ Set the name of the axis for the index or columns. @@ -5275,51 +1752,8 @@ def rename_axis(self, mapper, *, inplace, **kwargs): mapper (scalar, list-like, optional): Value to set the axis name attribute. - **Examples:** - - - Series - - >>> s = bpd.Series(["dog", "cat", "monkey"]) - >>> s - 0 dog - 1 cat - 2 monkey - dtype: string - - >>> s.rename_axis("animal") - animal - 0 dog - 1 cat - 2 monkey - dtype: string - - DataFrame - - >>> df = bpd.DataFrame({"num_legs": [4, 4, 2], - ... "num_arms": [0, 0, 2]}, - ... ["dog", "cat", "monkey"]) - >>> df - num_legs num_arms - dog 4 0 - cat 4 0 - monkey 2 2 - - [3 rows x 2 columns] - - >>> df = df.rename_axis("animal") - >>> df - num_legs num_arms - animal - dog 4 0 - cat 4 0 - monkey 2 2 - - [3 rows x 2 columns] - Returns: - bigframes.pandas.Series or bigframes.pandas.DataFrame: - The same type as the caller. + bigframes.series.Series: Series with the name of the axis set. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @@ -5338,57 +1772,6 @@ def value_counts( first element is the most frequently-occurring element. Excludes NA values by default. - **Examples:** - - - >>> s = bpd.Series([3, 1, 2, 3, 4, pd.NA], dtype="Int64") - - >>> s - 0 3 - 1 1 - 2 2 - 3 3 - 4 4 - 5 - dtype: Int64 - - ``value_counts`` sorts the result by counts in a descending order by default: - - >>> s.value_counts() - 3 2 - 1 1 - 2 1 - 4 1 - Name: count, dtype: Int64 - - You can normalize the counts to return relative frequencies by setting ``normalize=True``: - - >>> s.value_counts(normalize=True) - 3 0.4 - 1 0.2 - 2 0.2 - 4 0.2 - Name: proportion, dtype: Float64 - - You can get the values in the ascending order of the counts by setting ``ascending=True``: - - >>> s.value_counts(ascending=True) - 1 1 - 2 1 - 4 1 - 3 2 - Name: count, dtype: Int64 - - You can include the counts of the ``NA`` values by setting ``dropna=False``: - - >>> s.value_counts(dropna=False) - 3 2 - 1 1 - 2 1 - 4 1 - 1 - Name: count, dtype: Int64 - Args: normalize (bool, default False): If True then the object returned will contain the relative @@ -5401,8 +1784,7 @@ def value_counts( Don't include counts of NaN. Returns: - bigframes.pandas.Series: - Series containing counts of unique values. + Series: Series containing counts of unique values. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @@ -5414,47 +1796,12 @@ def str(self): NAs stay NA unless handled otherwise by a particular method. Patterned after Python’s string methods, with some inspiration from R’s stringr package. - **Examples:** - - >>> import bigframes.pandas as bpd - >>> s = bpd.Series(["A_Str_Series"]) - >>> s - 0 A_Str_Series - dtype: string - - >>> s.str.lower() - 0 a_str_series - dtype: string - - >>> s.str.replace("_", "") - 0 AStrSeries - dtype: string - Returns: bigframes.operations.strings.StringMethods: An accessor containing string methods. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - @property - def plot(self): - """ - Make plots of Series. - - **Examples:** - - >>> import bigframes.pandas as bpd - >>> ser = bpd.Series([1, 2, 3, 3]) - >>> plot = ser.plot(kind='hist', title="My plot") - >>> plot - - - Returns: - bigframes.pandas.api.typing.PlotAccessor: - An accessor making plots. - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - def isin(self, values): """ Whether elements in Series are contained in values. @@ -5467,59 +1814,13 @@ def isin(self, values): the same. That is, if any form of NaN is present in values, all forms of NaN in the series will be considered a match. (though pandas may not) - **Examples:** - - - >>> s = bpd.Series(['llama', 'cow', 'llama', 'beetle', 'llama', - ... 'hippo'], name='animal') - >>> s - 0 llama - 1 cow - 2 llama - 3 beetle - 4 llama - 5 hippo - Name: animal, dtype: string - - To invert the boolean values, use the ~ operator: - - >>> ~s.isin(['cow', 'llama']) - 0 False - 1 False - 2 False - 3 True - 4 False - 5 True - Name: animal, dtype: boolean - - Passing a single string as s.isin('llama') will raise an error. Use a - list of one element instead: - - >>> s.isin(['llama']) - 0 True - 1 False - 2 True - 3 False - 4 True - 5 False - Name: animal, dtype: boolean - - Strings and integers are distinct and are therefore not comparable: - - >>> bpd.Series([1]).isin(['1']) - 0 False - dtype: boolean - >>> bpd.Series([1.1]).isin(['1.1']) - 0 False - dtype: boolean - Args: values (list-like): The sequence of values to test. Passing in a single string will raise a TypeError. Instead, turn a single string into a list of one element. Returns: - bigframes.pandas.Series: Series of booleans indicating if each element is in values. + bigframes.series.Series: Series of booleans indicating if each element is in values. Raises: TypeError: If input is not list-like. @@ -5531,20 +1832,8 @@ def is_monotonic_increasing(self) -> bool: """ Return boolean if values in the object are monotonically increasing. - **Examples:** - - - >>> s = bpd.Series([1, 2, 2]) - >>> s.is_monotonic_increasing - np.True_ - - >>> s = bpd.Series([3, 2, 1]) - >>> s.is_monotonic_increasing - np.False_ - Returns: - bool: - Boolean. + bool """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @@ -5553,20 +1842,8 @@ def is_monotonic_decreasing(self) -> bool: """ Return boolean if values in the object are monotonically decreasing. - **Examples:** - - - >>> s = bpd.Series([3, 2, 2, 1]) - >>> s.is_monotonic_decreasing - np.True_ - - >>> s = bpd.Series([1, 2, 3]) - >>> s.is_monotonic_decreasing - np.False_ - Returns: - bool: - Boolean. + bool """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @@ -5593,55 +1870,6 @@ def map( ``__missing__`` (i.e. provide a method for default values). These are treated the same as ``dict``. - **Examples:** - - >>> s = bpd.Series(['cat', 'dog', pd.NA, 'rabbit']) - >>> s - 0 cat - 1 dog - 2 - 3 rabbit - dtype: string - - `map` can accepts a `dict`. Values that are not found in the `dict` are - converted to `NA`: - - >>> s.map({'cat': 'kitten', 'dog': 'puppy'}) - 0 kitten - 1 puppy - 2 - 3 - dtype: string - - It also accepts a remote function: - - >>> @bpd.remote_function(cloud_function_service_account="default") # doctest: +SKIP - ... def my_mapper(val: str) -> str: - ... vowels = ["a", "e", "i", "o", "u"] - ... if val: - ... return "".join([ - ... ch.upper() if ch in vowels else ch for ch in val - ... ]) - ... return "N/A" - - >>> s.map(my_mapper) # doctest: +SKIP - 0 cAt - 1 dOg - 2 N/A - 3 rAbbIt - dtype: string - - With experimental Python Transpiler enabled, you can use some lambda functions without - deploying them as remote functions: - - >>> bpd.options.experiments.enable_python_transpiler = True - >>> s.map(lambda val: val + "fish") - 0 catfish - 1 dogfish - 2 - 3 rabbitfish - dtype: string - Args: arg (function, Mapping, Series): remote function, collections.abc.Mapping subclass or Series @@ -5658,487 +1886,21 @@ def map( index entry. Returns: - bigframes.pandas.Series: - Same index as caller. + Series: Same index as caller. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @property def iloc(self): - """Purely integer-location based indexing for selection by position. - - **Examples:** - - - >>> mydict = [{'a': 1, 'b': 2, 'c': 3, 'd': 4}, - ... {'a': 100, 'b': 200, 'c': 300, 'd': 400}, - ... {'a': 1000, 'b': 2000, 'c': 3000, 'd': 4000}] - >>> df = bpd.DataFrame(mydict) - >>> df - a b c d - 0 1 2 3 4 - 1 100 200 300 400 - 2 1000 2000 3000 4000 - - [3 rows x 4 columns] - - Indexing just the rows - - With a scalar integer. - - >>> type(df.iloc[0]) # doctest: +ELLIPSIS - - - >>> df.iloc[0] - a 1 - b 2 - c 3 - d 4 - Name: 0, dtype: Int64 - - With a list of integers. - - >>> df.iloc[0] - a 1 - b 2 - c 3 - d 4 - Name: 0, dtype: Int64 - - >>> type(df.iloc[[0]]) - - - >>> df.iloc[[0, 1]] - a b c d - 0 1 2 3 4 - 1 100 200 300 400 - - [2 rows x 4 columns] - - With a slice object. - - >>> df.iloc[:3] - a b c d - 0 1 2 3 4 - 1 100 200 300 400 - 2 1000 2000 3000 4000 - - [3 rows x 4 columns] - - Indexing both axes - - You can mix the indexer types for the index and columns. Use : to select - the entire axis. - - With scalar integers. - - >>> df.iloc[0, 1] - np.int64(2) - - Returns: - bigframes.core.indexers.IlocSeriesIndexer: - Purely integer-location Indexers. - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - @property - def loc(self): - """Access a group of rows and columns by label(s) or a boolean array. - - **Examples:** - - - >>> df = bpd.DataFrame([[1, 2], [4, 5], [7, 8]], - ... index=['cobra', 'viper', 'sidewinder'], - ... columns=['max_speed', 'shield']) - >>> df - max_speed shield - cobra 1 2 - viper 4 5 - sidewinder 7 8 - - [3 rows x 2 columns] - - Single label. Note this returns the row as a Series. - - >>> df.loc['viper'] - max_speed 4 - shield 5 - Name: viper, dtype: Int64 - - List of labels. Note using [[]] returns a DataFrame. - - >>> df.loc[['viper', 'sidewinder']] - max_speed shield - viper 4 5 - sidewinder 7 8 - - [2 rows x 2 columns] - - Slice with labels for row and single label for column. As mentioned - above, note that both the start and stop of the slice are included. - - >>> df.loc['cobra', 'shield'] - np.int64(2) - - Index (same behavior as df.reindex) - - >>> df.loc[bpd.Index(["cobra", "viper"], name="foo")] - max_speed shield - cobra 1 2 - viper 4 5 - - [2 rows x 2 columns] - - Conditional that returns a boolean Series with column labels specified - - >>> df.loc[df['shield'] > 6, ['max_speed']] - max_speed - sidewinder 7 - - [1 rows x 1 columns] - - Multiple conditional using | that returns a boolean Series - - >>> df.loc[(df['max_speed'] > 4) | (df['shield'] < 5)] - max_speed shield - cobra 1 2 - sidewinder 7 8 - - [2 rows x 2 columns] - - Please ensure that each condition is wrapped in parentheses (). - - Set value for an entire column - - >>> df.loc[:, 'max_speed'] = 30 - >>> df - max_speed shield - cobra 30 2 - viper 30 5 - sidewinder 30 8 - - [3 rows x 2 columns] - - Returns: - bigframes.core.indexers.LocSeriesIndexer: - Indexers object. - """ + """Purely integer-location based indexing for selection by position.""" raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @property def iat(self): - """Access a single value for a row/column pair by integer position. - - **Examples:** - - - >>> df = bpd.DataFrame([[0, 2, 3], [0, 4, 1], [10, 20, 30]], - ... columns=['A', 'B', 'C']) - >>> df - A B C - 0 0 2 3 - 1 0 4 1 - 2 10 20 30 - - [3 rows x 3 columns] - - Get value at specified row/column pair - - >>> df.iat[1, 2] - np.int64(1) - - Get value within a series - - >>> df.loc[0].iat[1] - np.int64(2) - - Returns: - bigframes.core.indexers.IatSeriesIndexer: - Indexers object. - """ + """Access a single value for a row/column pair by integer position.""" raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @property def at(self): - """Access a single value for a row/column label pair. - - **Examples:** - - - >>> df = bpd.DataFrame([[0, 2, 3], [0, 4, 1], [10, 20, 30]], - ... index=[4, 5, 6], columns=['A', 'B', 'C']) - >>> df - A B C - 4 0 2 3 - 5 0 4 1 - 6 10 20 30 - - [3 rows x 3 columns] - - Get value at specified row/column pair - - >>> df.at[4, 'B'] - np.int64(2) - - Get value at specified row label - - >>> df.loc[5].at['B'] - np.int64(4) - - Returns: - bigframes.core.indexers.AtSeriesIndexer: - Indexers object. - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - @property - def values(self): - """ - Return Series as ndarray or ndarray-like depending on the dtype. - - **Examples:** - - - >>> bpd.Series([1, 2, 3]).values - array([1, 2, 3]) - - >>> bpd.Series(list('aabc')).values - array(['a', 'a', 'b', 'c'], dtype=object) - - Returns: - numpy.ndarray or ndarray-like: - Values in the Series. - - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - @property - def size(self) -> int: - """Return the number of elements in the underlying data. - - **Examples:** - - - For Series: - - >>> s = bpd.Series(['Ant', 'Bear', 'Cow']) - >>> s - 0 Ant - 1 Bear - 2 Cow - dtype: string - >>> s.size - 3 - - For Index: - - >>> idx = bpd.Index(bpd.Series([1, 2, 3])) - >>> idx.size - 3 - - Returns: - int: - Return the number of elements in the underlying data. - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - def __array__(self, dtype=None, copy: Optional[bool] = None) -> numpy.ndarray: - """ - Returns the values as NumPy array. - - Equivalent to `Series.to_numpy(dtype)`. - - Users should not call this directly. Rather, it is invoked by - `numpy.array` and `numpy.asarray`. - - **Examples:** - - - >>> ser = bpd.Series([1, 2, 3]) - - >>> np.asarray(ser) - array([1, 2, 3]) - - Args: - dtype (str or numpy.dtype, optional): - The dtype to use for the resulting NumPy array. By default, - the dtype is inferred from the data. - copy (bool or None, optional): - Whether to copy the data, False is not supported. - - Returns: - numpy.ndarray: - The values in the series converted to a `numpy.ndarray` with the - specified dtype. - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - def __len__(self): - """Returns number of values in the Series, serves `len` operator. - - **Examples:** - - - >>> s = bpd.Series([1, 2, 3]) - >>> len(s) - 3 - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - def __invert__(self): - """ - Returns the logical inversion (binary NOT) of the Series, element-wise - using operator `~`. - - **Examples:** - - - >>> ser = bpd.Series([True, False, True]) - >>> ~ser - 0 False - 1 True - 2 False - dtype: boolean - - Returns: - bigframes.pandas.Series: - The inverted values in the series. - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - def __and__(self, other): - """Get bitwise AND of Series and other, element-wise, using operator `&`. - - **Examples:** - - - >>> s = bpd.Series([0, 1, 2, 3]) - - You can operate with a scalar. - - >>> s & 6 - 0 0 - 1 0 - 2 2 - 3 2 - dtype: Int64 - - You can operate with another Series. - - >>> s1 = bpd.Series([5, 6, 7, 8]) - >>> s & s1 - 0 0 - 1 0 - 2 2 - 3 0 - dtype: Int64 - - Args: - other (scalar or Series): - Object to bitwise AND with the Series. - - Returns: - bigframes.pandas.Series: - The result of the operation. - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - def __or__(self, other): - """Get bitwise OR of Series and other, element-wise, using operator `|`. - - **Examples:** - - - >>> s = bpd.Series([0, 1, 2, 3]) - - You can operate with a scalar. - - >>> s | 6 - 0 6 - 1 7 - 2 6 - 3 7 - dtype: Int64 - - You can operate with another Series. - - >>> s1 = bpd.Series([5, 6, 7, 8]) - >>> s | s1 - 0 5 - 1 7 - 2 7 - 3 11 - dtype: Int64 - - Args: - other (scalar or Series): - Object to bitwise OR with the Series. - - Returns: - bigframes.pandas.Series: - The result of the operation. - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - def __xor__(self, other): - """Get bitwise XOR of Series and other, element-wise, using operator `^`. - - **Examples:** - - - >>> s = bpd.Series([0, 1, 2, 3]) - - You can operate with a scalar. - - >>> s ^ 6 - 0 6 - 1 7 - 2 4 - 3 5 - dtype: Int64 - - You can operate with another Series. - - >>> s1 = bpd.Series([5, 6, 7, 8]) - >>> s ^ s1 - 0 5 - 1 7 - 2 5 - 3 11 - dtype: Int64 - - Args: - other (scalar or Series): - Object to bitwise XOR with the Series. - - Returns: - bigframes.pandas.Series: - The result of the operation. - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - def __getitem__(self, indexer): - """Gets the specified index from the Series. - - **Examples:** - - - >>> s = bpd.Series([15, 30, 45]) - >>> s[1] - np.int64(30) - - >>> s[0:2] - 0 15 - 1 30 - dtype: Int64 - - Args: - indexer (int or slice): - Index or slice of indices. - - Returns: - bigframes.pandas.Series or Value: - Value(s) at the requested index(es). - """ + """Access a single value for a row/column label pair.""" raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) diff --git a/third_party/bigframes_vendored/pandas/core/strings/accessor.py b/third_party/bigframes_vendored/pandas/core/strings/accessor.py index 9a72b98aee8..ecdd9547d54 100644 --- a/third_party/bigframes_vendored/pandas/core/strings/accessor.py +++ b/third_party/bigframes_vendored/pandas/core/strings/accessor.py @@ -13,36 +13,6 @@ class StringMethods: R's stringr package. """ - def __getitem__(self, key: typing.Union[int, slice]): - """ - Index or slice string or list in the Series. - - **Examples:** - - >>> import bigframes.pandas as bpd - - >>> s = bpd.Series(['Alice', 'Bob', 'Charlie']) - >>> s.str[0] - 0 A - 1 B - 2 C - dtype: string - - >>> s.str[0:3] - 0 Ali - 1 Bob - 2 Cha - dtype: string - - Args: - key (int | slice): - Index or slice of indices to access from each string or list. - - Returns: - bigframes.series.Series: The list at requested index. - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - def extract(self, pat: str, flags: int = 0): """ Extract capture groups in the regex `pat` as columns in a DataFrame. @@ -50,56 +20,19 @@ def extract(self, pat: str, flags: int = 0): For each subject string in the Series, extract groups from the first match of regular expression `pat`. - **Examples:** - - >>> import bigframes.pandas as bpd - - A pattern with two groups will return a DataFrame with two columns. - Non-matches will be `NaN`. - - >>> s = bpd.Series(['a1', 'b2', 'c3']) - >>> s.str.extract(r'([ab])(\\d)') - 0 1 - 0 a 1 - 1 b 2 - 2 - - [3 rows x 2 columns] - - Named groups will become column names in the result. - - >>> s.str.extract(r'(?P[ab])(?P\\d)') - letter digit - 0 a 1 - 1 b 2 - 2 - - [3 rows x 2 columns] - - A pattern with one group will return a DataFrame with one column. - - >>> s.str.extract(r'[ab](\\d)') - 0 - 0 1 - 1 2 - 2 - - [3 rows x 1 columns] - Args: - pat (str): + pat: Regular expression pattern with capturing groups. - flags (int, default 0 (no flags)): + flags: Flags from the ``re`` module, e.g. ``re.IGNORECASE``, that modify regular expression matching for things like case, spaces, etc. For more details, see :mod:`re`. Returns: - bigframes.dataframe.DataFrame: - A DataFrame with one row for each subject string, and one - column for each group. Any capture group names in regular - expression pat will be used for column names; otherwise - capture group numbers will be used. + A DataFrame with one row for each subject string, and one + column for each group. Any capture group names in regular + expression pat will be used for column names; otherwise + capture group numbers will be used. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @@ -110,23 +43,12 @@ def find(self, sub, start: int = 0, end=None): substring is fully contained between [start:end]. Return -1 on failure. Equivalent to standard :meth:`str.find`. - **Examples:** - - >>> import bigframes.pandas as bpd - - >>> ser = bpd.Series(["cow_", "duck_", "do_ve"]) - >>> ser.str.find("_") - 0 3 - 1 4 - 2 2 - dtype: Int64 - Args: - sub (str): + sub: Substring being searched. start (int, default 0): Left edge index. - end (int, default None): + end (None): Right edge index. Returns: @@ -140,19 +62,6 @@ def len(self): The element may be a sequence (such as a string, tuple or list) or a collection (such as a dictionary). - **Examples:** - - >>> import bigframes.pandas as bpd - - Returns the length (number of characters) in a string. - - >>> s = bpd.Series(['dog', '', pd.NA]) - >>> s.str.len() - 0 3 - 1 0 - 2 - dtype: Int64 - Returns: bigframes.series.Series: A Series or Index of integer values indicating the length of each element in the Series or Index. @@ -165,21 +74,6 @@ def lower(self): Equivalent to :meth:`str.lower`. - **Examples:** - - >>> import bigframes.pandas as bpd - - >>> s = bpd.Series(['lower', - ... 'CAPITALS', - ... 'this is a sentence', - ... 'SwApCaSe']) - >>> s.str.lower() - 0 lower - 1 capitals - 2 this is a sentence - 3 swapcase - dtype: string - Returns: bigframes.series.Series: Series with lowercase. """ @@ -189,35 +83,6 @@ def lower(self): def slice(self, start=None, stop=None): """Slice substrings from each element in the Series or Index. - **Examples:** - - >>> import bigframes.pandas as bpd - - >>> s = bpd.Series(["koala", "dog", "chameleon"]) - >>> s - 0 koala - 1 dog - 2 chameleon - dtype: string - - >>> s.str.slice(start=1) - 0 oala - 1 og - 2 hameleon - dtype: string - - >>> s.str.slice(stop=2) - 0 ko - 1 do - 2 ch - dtype: string - - >>> s.str.slice(start=2, stop=5) - 0 ala - 1 g - 2 ame - dtype: string - Args: start (int, optional): Start position for slice operation. @@ -233,7 +98,7 @@ def slice(self, start=None, stop=None): raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - def strip(self, to_strip: typing.Optional[str] = None): + def strip(self): """Remove leading and trailing characters. Strip whitespaces (including newlines) or a set of specified characters @@ -241,36 +106,6 @@ def strip(self, to_strip: typing.Optional[str] = None): Replaces any non-strings in Series with NaNs. Equivalent to :meth:`str.strip`. - **Examples:** - - >>> import bigframes.pandas as bpd - - >>> s = bpd.Series([ - ... '1. Ant.', - ... ' 2. Bee? ', - ... '\\t3. Cat!\\n', - ... pd.NA, - ... ]) - >>> s.str.strip() - 0 1. Ant. - 1 2. Bee? - 2 3. Cat! - 3 - dtype: string - - >>> s.str.strip('123.!? \\n\\t') - 0 Ant - 1 Bee - 2 Cat - 3 - dtype: string - - Args: - to_strip (str, default None): - Specifying the set of characters to be removed. All combinations - of this set of characters will be stripped. If None then - whitespaces are removed. - Returns: bigframes.series.Series: Series or Index without leading and trailing characters. @@ -283,21 +118,6 @@ def upper(self): Equivalent to :meth:`str.upper`. - **Examples:** - - >>> import bigframes.pandas as bpd - - >>> s = bpd.Series(['lower', - ... 'CAPITALS', - ... 'this is a sentence', - ... 'SwApCaSe']) - >>> s.str.upper() - 0 LOWER - 1 CAPITALS - 2 THIS IS A SENTENCE - 3 SWAPCASE - dtype: string - Returns: bigframes.series.Series: Series with uppercase strings. """ @@ -311,18 +131,6 @@ def isnumeric(self): :meth:`str.isnumeric` for each element of the Series/Index. If a string has zero characters, ``False`` is returned for that check. - **Examples:** - - >>> import bigframes.pandas as bpd - - >>> s1 = bpd.Series(['one', 'one1', '1', '']) - >>> s1.str.isnumeric() - 0 False - 1 False - 2 True - 3 False - dtype: boolean - Returns: bigframes.series.Series: Series or Index of boolean values with the same length as the original Series/Index. @@ -337,18 +145,6 @@ def isalpha(self): :meth:`str.isalpha` for each element of the Series/Index. If a string has zero characters, ``False`` is returned for that check. - **Examples:** - - >>> import bigframes.pandas as bpd - - >>> s1 = bpd.Series(['one', 'one1', '1', '']) - >>> s1.str.isalpha() - 0 True - 1 False - 2 False - 3 False - dtype: boolean - Returns: bigframes.series.Series: Series with the same length as the originalSeries/Index. """ @@ -362,18 +158,6 @@ def isdigit(self): :meth:`str.isdigit` for each element of the Series/Index. If a string has zero characters, ``False`` is returned for that check. - **Examples:** - - >>> import bigframes.pandas as bpd - - >>> s = bpd.Series(['23', '1a', '1/5', '']) - >>> s.str.isdigit() - 0 True - 1 False - 2 False - 3 False - dtype: boolean - Returns: bigframes.series.Series: Series with the same length as the originalSeries/Index. """ @@ -387,29 +171,6 @@ def isalnum(self): :meth:`str.isalnum` for each element of the Series/Index. If a string has zero characters, ``False`` is returned for that check. - **Examples:** - - >>> import bigframes.pandas as bpd - - >>> s1 = bpd.Series(['one', 'one1', '1', '']) - >>> s1.str.isalnum() - 0 True - 1 True - 2 True - 3 False - dtype: boolean - - Note that checks against characters mixed with any additional - punctuation or whitespace will evaluate to false for an alphanumeric - check. - - >>> s2 = bpd.Series(['A B', '1.5', '3,000']) - >>> s2.str.isalnum() - 0 False - 1 False - 2 False - dtype: boolean - Returns: bigframes.series.Series: Series or Index of boolean values with the same length as the original Series/Index. @@ -424,17 +185,6 @@ def isspace(self): :meth:`str.isspace` for each element of the Series/Index. If a string has zero characters, ``False`` is returned for that check. - **Examples:** - - >>> import bigframes.pandas as bpd - - >>> s = bpd.Series([' ', '\\t\\r\\n ', '']) - >>> s.str.isspace() - 0 True - 1 True - 2 False - dtype: boolean - Returns: bigframes.series.Series: Series or Index of boolean values with the same length as the original Series/Index. @@ -449,18 +199,6 @@ def islower(self): :meth:`str.islower` for each element of the Series/Index. If a string has zero characters, ``False`` is returned for that check. - **Examples:** - - >>> import bigframes.pandas as bpd - - >>> s = bpd.Series(['leopard', 'Golden Eagle', 'SNAKE', '']) - >>> s.str.islower() - 0 True - 1 False - 2 False - 3 False - dtype: boolean - Returns: bigframes.series.Series: Series or Index of boolean values with the same length as the original Series/Index. @@ -475,18 +213,6 @@ def isupper(self): :meth:`str.isupper` for each element of the Series/Index. If a string has zero characters, ``False`` is returned for that check. - **Examples:** - - >>> import bigframes.pandas as bpd - - >>> s = bpd.Series(['leopard', 'Golden Eagle', 'SNAKE', '']) - >>> s.str.isupper() - 0 False - 1 False - 2 True - 3 False - dtype: boolean - Returns: bigframes.series.Series: Series or Index of boolean values with the same length as the original Series/Index. @@ -501,21 +227,6 @@ def isdecimal(self): :meth:`str.isdecimal` for each element of the Series/Index. If a string has zero characters, ``False`` is returned for that check. - **Examples:** - - >>> import bigframes.pandas as bpd - - The `isdecimal` method checks for characters used to form numbers in - base 10. - - >>> s = bpd.Series(['23', '³', '⅕', '']) - >>> s.str.isdecimal() - 0 True - 1 False - 2 False - 3 False - dtype: boolean - Returns: bigframes.series.Series: Series or Index of boolean values with the same length as the original Series/Index. @@ -523,64 +234,28 @@ def isdecimal(self): raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - def rstrip(self, to_strip: typing.Optional[str] = None): - r"""Remove trailing characters. + def rstrip(self): + """Remove trailing characters. Strip whitespaces (including newlines) or a set of specified characters from each string in the Series/Index from right side. Replaces any non-strings in Series with NaNs. Equivalent to :meth:`str.rstrip`. - **Examples:** - - >>> import bigframes.pandas as bpd - - >>> s = bpd.Series(['Ant', ' Bee ', '\tCat\n', pd.NA]) - >>> s.str.rstrip() - 0 Ant - 1 Bee - 2 \tCat - 3 - dtype: string - - Args: - to_strip (str, default None): - Specifying the set of characters to be removed. All combinations - of this set of characters will be stripped. If None then - whitespaces are removed. - Returns: bigframes.series.Series: Series without trailing characters. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - def lstrip(self, to_strip: typing.Optional[str] = None): - r"""Remove leading characters. + def lstrip(self): + """Remove leading characters. Strip whitespaces (including newlines) or a set of specified characters from each string in the Series/Index from left side. Replaces any non-strings in Series with NaNs. Equivalent to :meth:`str.lstrip`. - **Examples:** - - >>> import bigframes.pandas as bpd - - >>> s = bpd.Series(['Ant', ' Bee ', '\tCat\n', pd.NA]) - >>> s.str.lstrip() - 0 Ant - 1 Bee - 2 Cat\n - 3 - dtype: string - - Args: - to_strip (str, default None): - Specifying the set of characters to be removed. All combinations - of this set of characters will be stripped. If None then - whitespaces are removed. - Returns: bigframes.series.Series: Series without leading characters. """ @@ -590,23 +265,6 @@ def lstrip(self, to_strip: typing.Optional[str] = None): def repeat(self, repeats: int): """Duplicate each string in the Series or Index. - **Examples:** - - >>> import bigframes.pandas as bpd - - >>> s = bpd.Series(['a', 'b', 'c']) - >>> s - 0 a - 1 b - 2 c - dtype: string - - >>> s.str.repeat(repeats=2) - 0 aa - 1 bb - 2 cc - dtype: string - Args: repeats : int or sequence of int Same value for all (int) or different value per (sequence). @@ -623,21 +281,6 @@ def capitalize(self): Equivalent to :meth:`str.capitalize`. - **Examples:** - - >>> import bigframes.pandas as bpd - - >>> s = bpd.Series(['lower', - ... 'CAPITALS', - ... 'this is a sentence', - ... 'SwApCaSe']) - >>> s.str.capitalize() - 0 Lower - 1 Capitals - 2 This is a sentence - 3 Swapcase - dtype: string - Returns: bigframes.series.Series: Series with captitalized strings. """ @@ -650,42 +293,8 @@ def cat(self, others, *, join): If `others` is specified, this function concatenates the Series/Index and elements of `others` element-wise. - **Examples:** - - >>> import bigframes.pandas as bpd - - You can concatenate each string in a Series to another string. - - >>> s = bpd.Series(['Jane', 'John']) - >>> s.str.cat(" Doe") - 0 Jane Doe - 1 John Doe - dtype: string - - You can concatenate another Series. By default left join is performed to - align the corresponding elements. - - >>> s.str.cat(bpd.Series([" Doe", " Foe", " Roe"])) - 0 Jane Doe - 1 John Foe - dtype: string - - >>> s.str.cat(bpd.Series([" Doe", " Foe", " Roe"], index=[2, 0, 1])) - 0 Jane Foe - 1 John Roe - dtype: string - - You can enforce an outer join. - - >>> s.str.cat(bpd.Series([" Doe", " Foe", " Roe"]), join="outer") - 0 Jane Doe - 1 John Foe - 2 - dtype: string - Args: - others (str or Series): - A string or a Series of strings. + others (Series): join ({'left', 'outer'}, default 'left'): Determines the join-style between the calling Series and any @@ -706,76 +315,6 @@ def contains(self, pat, case: bool = True, flags: int = 0, *, regex: bool = True Return boolean Series or Index based on whether a given pattern or regex is contained within a string of a Series or Index. - **Examples:** - - >>> import bigframes.pandas as bpd - - Returning a Series of booleans using only a literal pattern. - - >>> s1 = bpd.Series(['Mouse', 'dog', 'house and parrot', '23', None]) - >>> s1.str.contains('og') - 0 False - 1 True - 2 False - 3 False - 4 - dtype: boolean - - Specifying case sensitivity using `case`. - - >>> s1.str.contains('oG', case=True) - 0 False - 1 False - 2 False - 3 False - 4 - dtype: boolean - - Returning 'house' or 'dog' when either expression occurs in a string. - - >>> s1.str.contains('house|dog', regex=True) - 0 False - 1 True - 2 True - 3 False - 4 - dtype: boolean - - Ignoring case sensitivity using `flags` with regex. - - >>> import re - >>> s1.str.contains('PARROT', flags=re.IGNORECASE, regex=True) - 0 False - 1 False - 2 True - 3 False - 4 - dtype: boolean - - Returning any digit using regular expression. - - >>> s1.str.contains('\\d', regex=True) - 0 False - 1 False - 2 False - 3 True - 4 - dtype: boolean - - Ensure `pat` is a not a literal pattern when `regex` is set to True. - Note in the following example one might expect only *s2[1]* and *s2[3]* - to return `True`. However, '.0' as a regex matches any character - followed by a 0. - - >>> s2 = bpd.Series(['40', '40.0', '41', '41.0', '35']) - >>> s2.str.contains('.0', regex=True) - 0 True - 1 True - 2 False - 3 True - 4 False - dtype: boolean - Args: pat (str, re.Pattern): Character sequence or regular expression. @@ -809,31 +348,6 @@ def replace( Equivalent to :meth:`str.replace` or :func:`re.sub`, depending on the regex value. - **Examples:** - - >>> import bigframes.pandas as bpd - - When *pat* is a string and *regex* is True, the given *pat* is compiled - as a regex. When *repl* is a string, it replaces matching regex patterns - as with `re.sub()`. NaN value(s) in the Series are left as is: - - >>> s = bpd.Series(['foo', 'fuz', pd.NA]) - >>> s.str.replace('f.', 'ba', regex=True) - 0 bao - 1 baz - 2 - dtype: string - - When *pat* is a string and *regex* is False, every *pat* is replaced - with *repl* as with `str.replace()`: - - >>> s = bpd.Series(['f.o', 'fuz', pd.NA]) - >>> s.str.replace('f.', 'ba', regex=False) - 0 bao - 1 fuz - 2 - dtype: string - Args: pat (str, re.Pattern): String can be a character sequence or regular expression. @@ -870,32 +384,6 @@ def startswith( """ Test if the start of each string element matches a pattern. - **Examples:** - - >>> import bigframes.pandas as bpd - - >>> s = bpd.Series(['bat', 'Bear', 'caT', pd.NA]) - >>> s - 0 bat - 1 Bear - 2 caT - 3 - dtype: string - - >>> s.str.startswith('b') - 0 True - 1 False - 2 False - 3 - dtype: boolean - - >>> s.str.startswith(('b', 'B')) - 0 True - 1 True - 2 False - 3 - dtype: boolean - Args: pat (str, tuple[str, ...]): Character sequence or tuple of strings. Regular expressions are not @@ -914,32 +402,6 @@ def endswith( """ Test if the end of each string element matches a pattern. - **Examples:** - - >>> import bigframes.pandas as bpd - - >>> s = bpd.Series(['bat', 'bear', 'caT', pd.NA]) - >>> s - 0 bat - 1 bear - 2 caT - 3 - dtype: string - - >>> s.str.endswith('t') - 0 True - 1 False - 2 False - 3 - dtype: boolean - - >>> s.str.endswith(('t', 'T')) - 0 True - 1 False - 2 True - 3 - dtype: boolean - Args: pat (str, tuple[str, ...]): Character sequence or tuple of strings. Regular expressions are not @@ -951,67 +413,10 @@ def endswith( """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - def split( - self, - pat: str = " ", - regex: typing.Union[bool, None] = None, - ): - """ - Split strings around given separator/delimiter. - - **Examples:** - - >>> import bigframes.pandas as bpd - - >>> s = bpd.Series( - ... [ - ... "a regular sentence", - ... "https://docs.python.org/index.html", - ... np.nan - ... ] - ... ) - >>> s.str.split() - 0 ['a' 'regular' 'sentence'] - 1 ['https://docs.python.org/index.html'] - 2 [] - dtype: list[pyarrow] - - The pat parameter can be used to split by other characters. - - >>> s.str.split("//", regex=False) - 0 ['a regular sentence'] - 1 ['https:' 'docs.python.org/index.html'] - 2 [] - dtype: list[pyarrow] - - Args: - pat (str, default " "): - String to split on. If not specified, split on whitespace. - regex (bool, default None): - Determines if the passed-in pattern is a regular expression. Regular - expressions aren't currently supported. Please set `regex=False` when - `pat` length is not 1. - - Returns: - bigframes.series.Series: Type matches caller. - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - def match(self, pat: str, case: bool = True, flags: int = 0): """ Determine if each string starts with a match of a regular expression. - **Examples:** - - >>> import bigframes.pandas as bpd - - >>> ser = bpd.Series(["horse", "eagle", "donkey"]) - >>> ser.str.match("e") - 0 False - 1 True - 2 False - dtype: boolean - Args: pat (str): Character sequence or regular expression. @@ -1029,17 +434,6 @@ def fullmatch(self, pat: str, case: bool = True, flags: int = 0): """ Determine if each string entirely matches a regular expression. - **Examples:** - - >>> import bigframes.pandas as bpd - - >>> ser = bpd.Series(["cat", "duck", "dove"]) - >>> ser.str.fullmatch(r'd.+') - 0 False - 1 True - 2 True - dtype: boolean - Args: pat (str): Character sequence or regular expression. @@ -1060,17 +454,6 @@ def get(self, i: int): Extract element from lists, tuples, dict, or strings in each element in the Series/Index. - **Examples:** - - >>> import bigframes.pandas as bpd - - >>> s = bpd.Series(["apple", "banana", "fig"]) - >>> s.str.get(3) - 0 l - 1 a - 2 - dtype: string - Args: i (int): Position or key of element to extract. @@ -1089,31 +472,6 @@ def pad( """ Pad strings in the Series/Index up to width. - **Examples:** - - >>> import bigframes.pandas as bpd - - >>> s = bpd.Series(["caribou", "tiger"]) - >>> s - 0 caribou - 1 tiger - dtype: string - - >>> s.str.pad(width=10) - 0 caribou - 1 tiger - dtype: string - - >>> s.str.pad(width=10, side='right', fillchar='-') - 0 caribou--- - 1 tiger----- - dtype: string - - >>> s.str.pad(width=10, side='both', fillchar='-') - 0 -caribou-- - 1 --tiger--- - dtype: string - Args: width (int): Minimum width of resulting string; additional characters will be filled @@ -1136,17 +494,6 @@ def ljust( """ Pad right side of strings in the Series/Index up to width. - **Examples:** - - >>> import bigframes.pandas as bpd - - >>> ser = bpd.Series(['dog', 'bird', 'mouse']) - >>> ser.str.ljust(8, fillchar='.') - 0 dog..... - 1 bird.... - 2 mouse... - dtype: string - Args: width (int): Minimum width of resulting string; additional characters will be filled @@ -1167,17 +514,6 @@ def rjust( """ Pad left side of strings in the Series/Index up to width. - **Examples:** - - >>> import bigframes.pandas as bpd - - >>> ser = bpd.Series(['dog', 'bird', 'mouse']) - >>> ser.str.rjust(8, fillchar='.') - 0 .....dog - 1 ....bird - 2 ...mouse - dtype: string - Args: width (int): Minimum width of resulting string; additional characters will be filled @@ -1202,25 +538,6 @@ def zfill( in the Series/Index with length greater or equal to `width` are unchanged. - **Examples:** - - >>> import bigframes.pandas as bpd - - >>> s = bpd.Series(['-1', '1', '1000', pd.NA]) - >>> s - 0 -1 - 1 1 - 2 1000 - 3 - dtype: string - - >>> s.str.zfill(3) - 0 -01 - 1 001 - 2 1000 - 3 - dtype: string - Args: width (int): Minimum length of resulting string; strings with length less @@ -1241,17 +558,6 @@ def center( Equivalent to :meth:`str.center`. - **Examples:** - - >>> import bigframes.pandas as bpd - - >>> ser = bpd.Series(['dog', 'bird', 'mouse']) - >>> ser.str.center(8, fillchar='.') - 0 ..dog... - 1 ..bird.. - 2 .mouse.. - dtype: string - Args: width (int): Minimum width of resulting string; additional characters will be filled @@ -1263,41 +569,3 @@ def center( bigframes.series.Series: Returns Series or Index with minimum number of char in object. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - def join(self, sep: str): - """ - Join lists contained as elements in the Series/Index with passed delimiter. - - If the elements of a Series are lists themselves, join the content of these - lists using the delimiter passed to the function. - This function is an equivalent to :meth:`str.join`. - - **Examples:** - - >>> import bigframes.pandas as bpd - - Example with a list that contains non-string elements. - - >>> s = bpd.Series([['lion', 'elephant', 'zebra'], - ... ['dragon'], - ... ['duck', 'swan', 'fish', 'guppy']]) - >>> s - 0 ['lion' 'elephant' 'zebra'] - 1 ['dragon'] - 2 ['duck' 'swan' 'fish' 'guppy'] - dtype: list[pyarrow] - - >>> s.str.join('-') - 0 lion-elephant-zebra - 1 dragon - 2 duck-swan-fish-guppy - dtype: string - - Args: - sep (str): - Delimiter to use between list entries. - - Returns: - bigframes.series.Series: The list entries concatenated by intervening occurrences of the delimiter. - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) diff --git a/third_party/bigframes_vendored/pandas/core/tools/__init__.py b/third_party/bigframes_vendored/pandas/core/tools/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/third_party/bigframes_vendored/pandas/core/tools/datetimes.py b/third_party/bigframes_vendored/pandas/core/tools/datetimes.py deleted file mode 100644 index c5f9f8330f6..00000000000 --- a/third_party/bigframes_vendored/pandas/core/tools/datetimes.py +++ /dev/null @@ -1,83 +0,0 @@ -# Contains code from https://github.com/pandas-dev/pandas/blob/main/pandas/core/tools/datetimes.py - -from __future__ import annotations - -from datetime import date, datetime -from typing import List, Mapping, Tuple, Union - -import pandas as pd - -from bigframes import constants, dataframe, series - -local_iterables = Union[List, Tuple, pd.Series, pd.DataFrame, Mapping] - - -def to_datetime( - arg: Union[ - Union[int, float, str, datetime, date], - local_iterables, - series.Series, - dataframe.DataFrame, - ], - *, - utc=False, - format=None, - unit=None, -) -> Union[pd.Timestamp, datetime, series.Series]: - """ - This function converts a scalar, array-like or Series to a datetime object. - - .. note:: - BigQuery only supports precision up to microseconds (us). Therefore, when working - with timestamps that have a finer granularity than microseconds, be aware that - the additional precision will not be represented in BigQuery. - - .. note:: - The format strings for specifying datetime representations in BigQuery and pandas - are not completely identical. Ensure that the format string provided is compatible - with BigQuery (https://cloud.google.com/bigquery/docs/reference/standard-sql/format-elements#format_elements_date_time). - - **Examples:** - - >>> import bigframes.pandas as bpd - - Converting a Scalar to datetime: - - >>> scalar = 123456.789 - >>> bpd.to_datetime(scalar, unit = 's') - Timestamp('1970-01-02 10:17:36.789000') - - Converting a List of Strings without Timezone Information: - - >>> list_str = ["01-31-2021 14:30", "02-28-2021 15:45"] - >>> bpd.to_datetime(list_str, format="%m-%d-%Y %H:%M", utc=True) - 0 2021-01-31 14:30:00+00:00 - 1 2021-02-28 15:45:00+00:00 - dtype: timestamp[us, tz=UTC][pyarrow] - - Converting a Series of Strings with Timezone Information: - - >>> series_str = bpd.Series(["01-31-2021 14:30+08:00", "02-28-2021 15:45+00:00"]) - >>> bpd.to_datetime(series_str, format="%m-%d-%Y %H:%M%Z", utc=True) - 0 2021-01-31 06:30:00+00:00 - 1 2021-02-28 15:45:00+00:00 - dtype: timestamp[us, tz=UTC][pyarrow] - - Args: - arg (int, float, str, datetime, date, list, tuple, 1-d array, Series): - The object to convert to a datetime. - utc (bool, default False): - Control timezone-related parsing, localization and conversion. If True, the - function always returns a timezone-aware UTC-localized timestamp or series. - If False (default), inputs will not be coerced to UTC. - format (str, default None): - The strftime to parse time, e.g. "%d/%m/%Y". - unit (str, default 'ns'): - The unit of the arg (D,s,ms,us,ns) denote the unit, which is an integer or - float number. - - Returns: - Union[pandas.Timestamp, datetime.datetime or bigframes.pandas.Series]: - Return type depends on input. - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) diff --git a/third_party/bigframes_vendored/pandas/core/tools/timedeltas.py b/third_party/bigframes_vendored/pandas/core/tools/timedeltas.py deleted file mode 100644 index 92cac856a59..00000000000 --- a/third_party/bigframes_vendored/pandas/core/tools/timedeltas.py +++ /dev/null @@ -1,97 +0,0 @@ -# Contains code from https://github.com/pandas-dev/pandas/blob/v2.2.3/pandas/core/tools/timedeltas.py - -import typing - -import pandas as pd -from bigframes_vendored import constants - -from bigframes import series - -UnitChoices = typing.Literal[ - "W", - "w", - "D", - "d", - "days", - "day", - "hours", - "hour", - "hr", - "h", - "m", - "minute", - "min", - "minutes", - "s", - "seconds", - "sec", - "second", - "ms", - "milliseconds", - "millisecond", - "milli", - "millis", - "us", - "microseconds", - "microsecond", - "µs", - "micro", - "micros", -] - - -def to_timedelta( - arg: typing.Union[series.Series, str, int, float], - unit: typing.Optional[UnitChoices] = None, -) -> typing.Union[series.Series, pd.Timedelta]: - """ - Converts a scalar or Series to a timedelta object. - - .. note:: - BigQuery only supports precision up to microseconds (us). Therefore, when working - with timedeltas that have a finer granularity than microseconds, be aware that - the additional precision will not be represented in BigQuery. - - **Examples:** - - Converting a Scalar to timedelta - - >>> import bigframes.pandas as bpd - >>> scalar = 2 - >>> bpd.to_timedelta(scalar, unit='s') - Timedelta('0 days 00:00:02') - - Converting a Series of integers to a Series of timedeltas - - >>> int_series = bpd.Series([1,2,3]) - >>> bpd.to_timedelta(int_series, unit='s') - 0 0 days 00:00:01 - 1 0 days 00:00:02 - 2 0 days 00:00:03 - dtype: duration[us][pyarrow] - - Args: - arg (int, float, str, Series): - The object to convert to a dataframe - unit (str, default 'us'): - Denotes the unit of the arg for numeric `arg`. Defaults to ``"us"``. - - Possible values: - - * 'W' - * 'D' / 'days' / 'day' - * 'hours' / 'hour' / 'hr' / 'h' / 'H' - * 'm' / 'minute' / 'min' / 'minutes' - * 's' / 'seconds' / 'sec' / 'second' - * 'ms' / 'milliseconds' / 'millisecond' / 'milli' / 'millis' - * 'us' / 'microseconds' / 'microsecond' / 'micro' / 'micros' - - Returns: - Union[pandas.Timedelta, bigframes.pandas.Series]: - Return type depends on input - - Series: Series of duration[us][pyarrow] dtype - - scalar: timedelta - - """ - - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) diff --git a/third_party/bigframes_vendored/pandas/core/window/rolling.py b/third_party/bigframes_vendored/pandas/core/window/rolling.py index 7ca676fbe6d..a869c86e72a 100644 --- a/third_party/bigframes_vendored/pandas/core/window/rolling.py +++ b/third_party/bigframes_vendored/pandas/core/window/rolling.py @@ -37,52 +37,3 @@ def max(self): def min(self): """Calculate the weighted window minimum.""" raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - def agg(self, func): - """ - Aggregate using one or more operations over the specified axis. - - **Examples:** - - >>> import bigframes.pandas as bpd - - >>> df = bpd.DataFrame({"A": [1, 2, 3], "B": [4, 5, 6], "C": [7, 8, 9]}) - >>> df - A B C - 0 1 4 7 - 1 2 5 8 - 2 3 6 9 - - [3 rows x 3 columns] - - >>> df.rolling(2).sum() - A B C - 0 - 1 3 9 15 - 2 5 11 17 - - [3 rows x 3 columns] - - >>> df.rolling(2).agg({"A": "sum", "B": "min"}) - A B - 0 - 1 3 4 - 2 5 5 - - [3 rows x 2 columns] - - Args: - func (function, str, list or dict): - Function to use for aggregating the data. - - Accepted combinations are: - - - string function name - - list of function names, e.g. ``['sum', 'mean']`` - - dict of axis labels -> function names or list of such. - - Returns: - Series or DataFrame - - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) diff --git a/third_party/bigframes_vendored/pandas/io/common.py b/third_party/bigframes_vendored/pandas/io/common.py index cab3c36cc9f..506984e64da 100644 --- a/third_party/bigframes_vendored/pandas/io/common.py +++ b/third_party/bigframes_vendored/pandas/io/common.py @@ -1,6 +1,5 @@ # Contains code from https://github.com/pandas-dev/pandas/blob/main/pandas/io/common.py """Common IO api utilities""" - from __future__ import annotations from collections import defaultdict @@ -14,13 +13,13 @@ def dedup_names( """ Rename column names if duplicates exist. - Currently the renaming is done by appending a underscore and an - autonumeric, but a custom pattern may be supported in the future. + Currently the renaming is done by appending a period and an autonumeric, + but a custom pattern may be supported in the future. Examples ``` dedup_names(["x", "y", "x", "x"], is_potential_multiindex=False) - ['x', 'y', 'x_1', 'x_2'] + ['x', 'y', 'x.1', 'x.2'] ``` """ names = list(names) # so we can index @@ -35,9 +34,9 @@ def dedup_names( if is_potential_multiindex: # for mypy assert isinstance(col, tuple) - col = col[:-1] + (f"{col[-1]}_{cur_count}",) + col = col[:-1] + (f"{col[-1]}.{cur_count}",) else: - col = f"{col}_{cur_count}" + col = f"{col}.{cur_count}" cur_count = counts[col] names[i] = col diff --git a/third_party/bigframes_vendored/pandas/io/gbq.py b/third_party/bigframes_vendored/pandas/io/gbq.py index 242d2c50c8d..575c5016187 100644 --- a/third_party/bigframes_vendored/pandas/io/gbq.py +++ b/third_party/bigframes_vendored/pandas/io/gbq.py @@ -1,52 +1,31 @@ # Contains code from https://github.com/pandas-dev/pandas/blob/main/pandas/io/gbq.py -"""Google BigQuery support""" +""" Google BigQuery support """ from __future__ import annotations -from typing import Any, Dict, Iterable, Literal, Optional, Tuple, Union +from typing import Iterable, Optional -import bigframes.enums from bigframes import constants -FilterOps = Literal["in", "not in", "<", "<=", "==", "!=", ">=", ">", "LIKE"] -FilterType = Tuple[str, FilterOps, Any] -FiltersType = Union[Iterable[FilterType], Iterable[Iterable[FilterType]]] - class GBQIOMixin: def read_gbq( self, query_or_table: str, *, - index_col: Union[Iterable[str], str, bigframes.enums.DefaultIndexKind] = (), - columns: Iterable[str] = (), - configuration: Optional[Dict] = None, - max_results: Optional[int] = None, - filters: FiltersType = (), - use_cache: Optional[bool] = None, + index_col: Iterable[str] | str = (), col_order: Iterable[str] = (), - allow_large_results: Optional[bool] = None, + max_results: Optional[int] = None, ): """Loads a DataFrame from BigQuery. - BigQuery tables are an unordered, unindexed data source. To add support - pandas-compatibility, the following indexing options are supported via - the ``index_col`` parameter: - - * (Empty iterable, default) A default index. **Behavior may change.** - Explicitly set ``index_col`` if your application makes use of - specific index values. - - If a table has primary key(s), those are used as the index, - otherwise a sequential index is generated. - * (:attr:`bigframes.enums.DefaultIndexKind.SEQUENTIAL_INT64`) Add an - arbitrary sequential index and ordering. **Warning** This uses an - analytic windowed operation that prevents filtering push down. Avoid - using on large clustered or partitioned tables. - * (Recommended) Set the ``index_col`` argument to one or more columns. - Unique values for the row labels are recommended. Duplicate labels - are possible, but note that joins on a non-unique index can duplicate - rows via pandas-compatible outer join behavior. + BigQuery tables are an unordered, unindexed data source. By default, + the DataFrame will have an arbitrary index and ordering. + + Set the `index_col` argument to one or more columns to choose an + index. The resulting DataFrame is sorted by the index columns. For the + best performance, ensure the index columns don't contain duplicate + values. .. note:: By default, even SQL query inputs with an ORDER BY clause create a @@ -61,14 +40,21 @@ def read_gbq( **Examples:** >>> import bigframes.pandas as bpd + >>> bpd.options.display.progress_bar = None If the input is a table ID: >>> df = bpd.read_gbq("bigquery-public-data.ml_datasets.penguins") - - Read table path with wildcard suffix and filters: - - >>> df = bpd.read_gbq_table("bigquery-public-data.noaa_gsod.gsod19*", filters=[("_table_suffix", ">=", "30"), ("_table_suffix", "<=", "39")]) + >>> df.head(2) + species island culmen_length_mm \\ + 0 Adelie Penguin (Pygoscelis adeliae) Dream 36.6 + 1 Adelie Penguin (Pygoscelis adeliae) Dream 39.8 + + culmen_depth_mm flipper_length_mm body_mass_g sex + 0 18.4 184.0 3475.0 FEMALE + 1 19.1 184.0 4650.0 MALE + + [2 rows x 7 columns] Preserve ordering in a query input. @@ -86,96 +72,29 @@ def read_gbq( ... WHERE year = 2016 ... GROUP BY pitcherFirstName, pitcherLastName ... ''', index_col="rowindex") - >>> print("START_OF_OUTPUT"); df.head(2) # doctest: +ELLIPSIS,+NORMALIZE_WHITESPACE - START_OF_OUTPUT - ... + >>> df.head(2) pitcherFirstName pitcherLastName averagePitchSpeed - ... + rowindex 1 Albertin Chapman 96.514113 2 Zachary Britton 94.591039 [2 rows x 3 columns] - Reading data with `columns` and `filters` parameters: - - >>> columns = ['pitcherFirstName', 'pitcherLastName', 'year', 'pitchSpeed'] - >>> filters = [('year', '==', 2016), ('pitcherFirstName', 'in', ['John', 'Doe']), ('pitcherLastName', 'in', ['Gant']), ('pitchSpeed', '>', 94)] - >>> df = bpd.read_gbq( - ... "bigquery-public-data.baseball.games_wide", - ... columns=columns, - ... filters=filters, - ... ) - >>> df.head(1) - pitcherFirstName pitcherLastName year pitchSpeed - 0 John Gant 2016 95 - - [1 rows x 4 columns] - Args: query_or_table (str): A SQL string to be executed or a BigQuery table to be read. The table must be specified in the format of `project.dataset.tablename` or `dataset.tablename`. - Can also take wildcard table name, such as `project.dataset.table_prefix*`. - In tha case, will read all the matched table as one DataFrame. - index_col (Iterable[str], str, bigframes.enums.DefaultIndexKind): + index_col (Iterable[str] or str): Name of result column(s) to use for index in results DataFrame. - - If an empty iterable, such as ``()``, a default index is - generated. Do not depend on specific index values in this case. - - **New in bigframes version 1.3.0**: If ``index_cols`` is not - set, the primary key(s) of the table are used as the index. - - **New in bigframes version 1.4.0**: Support - :class:`bigframes.enums.DefaultIndexKind` to override default index - behavior. - columns (Iterable[str]): + col_order (Iterable[str]): List of BigQuery column names in the desired order for results DataFrame. - configuration (dict, optional): - Query config parameters for job processing. - For example: configuration = {'query': {'useQueryCache': False}}. - For more information see `BigQuery REST API Reference - `__. max_results (Optional[int], default None): If set, limit the maximum number of rows to fetch from the query results. - filters (Union[Iterable[FilterType], Iterable[Iterable[FilterType]]], default ()): To - filter out data. Filter syntax: [[(column, op, val), …],…] where - op is [==, >, >=, <, <=, !=, in, not in, LIKE]. The innermost tuples - are transposed into a set of filters applied through an AND - operation. The outer Iterable combines these sets of filters - through an OR operation. A single Iterable of tuples can also - be used, meaning that no OR operation between set of filters - is to be conducted. - If using wildcard table suffix in query_or_table, can specify - '_table_suffix' pseudo column to filter the tables to be read - into the DataFrame. - use_cache (Optional[bool], default None): - Caches query results if set to `True`. When `None`, it behaves - as `True`, but should not be combined with `useQueryCache` in - `configuration` to avoid conflicts. - col_order (Iterable[str]): - Alias for columns, retained for backwards compatibility. - allow_large_results (bool, optional): - Whether to allow large query results. If ``True``, the query - results can be larger than the maximum response size. This - option is only applicable when ``query_or_table`` is a query. - Defaults to ``bpd.options.compute.allow_large_results``. - - Raises: - bigframes.exceptions.DefaultIndexWarning: - Using the default index is discouraged, such as with clustered - or partitioned tables without primary keys. - ValueError: - When both ``columns`` and ``col_order`` are specified. - ValueError: - If ``configuration`` is specified when directly reading - from a table. Returns: - bigframes.pandas.DataFrame: - A DataFrame representing results of the query or table. + bigframes.dataframe.DataFrame: A DataFrame representing results of the query or table. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) diff --git a/third_party/bigframes_vendored/pandas/io/parquet.py b/third_party/bigframes_vendored/pandas/io/parquet.py index cfb653481bf..f97bd386a40 100644 --- a/third_party/bigframes_vendored/pandas/io/parquet.py +++ b/third_party/bigframes_vendored/pandas/io/parquet.py @@ -1,6 +1,5 @@ # Contains code from https://github.com/pandas-dev/pandas/blob/main/pandas/io/parquet.py -"""parquet compat""" - +""" parquet compat """ from __future__ import annotations from bigframes import constants @@ -10,8 +9,6 @@ class ParquetIOMixin: def read_parquet( self, path: str, - *, - engine: str = "auto", ): r"""Load a Parquet object from the file path (local or Cloud Storage), returning a DataFrame. @@ -20,27 +17,25 @@ def read_parquet( Instead, set a serialized index column as the index and sort by that in the resulting DataFrame. - .. note:: - For non-"bigquery" engine, data is inlined in the query SQL if it is - small enough (roughly 5MB or less in memory). Larger size data is - loaded to a BigQuery table instead. - **Examples:** >>> import bigframes.pandas as bpd + >>> bpd.options.display.progress_bar = None >>> gcs_path = "gs://cloud-samples-data/bigquery/us-states/us-states.parquet" - >>> df = bpd.read_parquet(path=gcs_path, engine="bigquery") + >>> df = bpd.read_parquet(path=gcs_path) + >>> df.head(2) + name post_abbr + 0 Alabama AL + 1 Alaska AK + + [2 rows x 2 columns] Args: path (str): Local or Cloud Storage path to Parquet file. - engine (str): - One of ``'auto', 'pyarrow', 'fastparquet'``, or ``'bigquery'``. - Parquet library to parse the file. If set to ``'bigquery'``, - order is not preserved. Default, ``'auto'``. Returns: - bigframes.pandas.DataFrame: A BigQuery DataFrames. + bigframes.dataframe.DataFrame: A BigQuery DataFrames. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) diff --git a/third_party/bigframes_vendored/pandas/io/parsers/readers.py b/third_party/bigframes_vendored/pandas/io/parsers/readers.py index 537974b5f32..e8ed6182a6f 100644 --- a/third_party/bigframes_vendored/pandas/io/parsers/readers.py +++ b/third_party/bigframes_vendored/pandas/io/parsers/readers.py @@ -4,13 +4,12 @@ GH#48849 provides a convenient way of deprecating keyword arguments """ - from __future__ import annotations from typing import ( - IO, Any, Dict, + IO, Literal, MutableSequence, Optional, @@ -21,7 +20,6 @@ import numpy as np -import bigframes.enums from bigframes import constants @@ -36,13 +34,7 @@ def read_csv( Union[MutableSequence[Any], np.ndarray[Any, Any], Tuple[Any, ...], range] ] = None, index_col: Optional[ - Union[ - int, - str, - Sequence[Union[str, int]], - bigframes.enums.DefaultIndexKind, - Literal[False], - ] + Union[int, str, Sequence[Union[str, int]], Literal[False]] ] = None, usecols=None, dtype: Optional[Dict] = None, @@ -50,10 +42,10 @@ def read_csv( Literal["c", "python", "pyarrow", "python-fwf", "bigquery"] ] = None, encoding: Optional[str] = None, - write_engine="default", **kwargs, ): - """Loads data from a comma-separated values (csv) file into a DataFrame. + """Loads DataFrame from comma-separated values (csv) file locally or from + Cloud Storage. The CSV file data will be persisted as a temporary BigQuery table, which can be automatically recycled after the Session is closed. @@ -61,17 +53,12 @@ def read_csv( .. note:: using `engine="bigquery"` will not guarantee the same ordering as the file. Instead, set a serialized index column as the index and sort by - that in the resulting DataFrame. Only files stored on your local machine - or in Google Cloud Storage are supported. - - .. note:: - For non-bigquery engine, data is inlined in the query SQL if it is - small enough (roughly 5MB or less in memory). Larger size data is - loaded to a BigQuery table instead. + that in the resulting DataFrame. **Examples:** >>> import bigframes.pandas as bpd + >>> bpd.options.display.progress_bar = None >>> gcs_path = "gs://cloud-samples-data/bigquery/us-states/us-states.csv" >>> df = bpd.read_csv(filepath_or_buffer=gcs_path) @@ -114,7 +101,7 @@ def read_csv( names (default None): a list of column names to use. If the file contains a header row and you want to pass this parameter, then `header=0` should be passed as well so the - first (header) row is ignored. + first (header) row is ignored. Only to be used with default engine. index_col (default None): column(s) to use as the row labels of the DataFrame, either given as string name or column index. `index_col=False` can be used with the default @@ -143,22 +130,12 @@ def read_csv( documentation for a comprehensive list, https://docs.python.org/3/library/codecs.html#standard-encodings The BigQuery engine only supports `UTF-8` and `ISO-8859-1`. - write_engine (str): - How data should be written to BigQuery (if at all). See - :func:`bigframes.pandas.read_pandas` for a full description of - supported values. - **kwargs: keyword arguments for `pandas.read_csv` when not using the BigQuery engine. - Returns: - bigframes.pandas.DataFrame: - A BigQuery DataFrames. - Raises: - bigframes.exceptions.DefaultIndexWarning: - Using the default index is discouraged, such as with clustered - or partitioned tables without primary keys. + Returns: + bigframes.dataframe.DataFrame: A BigQuery DataFrames. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @@ -173,7 +150,6 @@ def read_json( encoding: Optional[str] = None, lines: bool = False, engine: Literal["ujson", "pyarrow", "bigquery"] = "ujson", - write_engine="default", **kwargs, ): """ @@ -184,14 +160,10 @@ def read_json( file. Instead, set a serialized index column as the index and sort by that in the resulting DataFrame. - .. note:: - For non-bigquery engine, data is inlined in the query SQL if it is - small enough (roughly 5MB or less in memory). Larger size data is - loaded to a BigQuery table instead. - **Examples:** >>> import bigframes.pandas as bpd + >>> bpd.options.display.progress_bar = None >>> gcs_path = "gs://bigframes-dev-testing/sample1.json" >>> df = bpd.read_json(path_or_buf=gcs_path, lines=True, orient="records") @@ -233,23 +205,11 @@ def read_json( engine ({{"ujson", "pyarrow", "bigquery"}}, default "ujson"): Type of engine to use. If `engine="bigquery"` is specified, then BigQuery's load API will be used. Otherwise, the engine will be passed to `pandas.read_json`. - write_engine (str): - How data should be written to BigQuery (if at all). See - :func:`bigframes.pandas.read_pandas` for a full description of - supported values. - **kwargs: keyword arguments for `pandas.read_json` when not using the BigQuery engine. Returns: - bigframes.pandas.DataFrame: + bigframes.dataframe.DataFrame: The DataFrame representing JSON contents. - - Raises: - bigframes.exceptions.DefaultIndexWarning: - Using the default index is discouraged, such as with clustered - or partitioned tables without primary keys. - ValueError: - ``lines`` is only valid when ``orient`` is ``records``. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) diff --git a/third_party/bigframes_vendored/pandas/io/pickle.py b/third_party/bigframes_vendored/pandas/io/pickle.py index 10ceab3c2fa..053ba4871c1 100644 --- a/third_party/bigframes_vendored/pandas/io/pickle.py +++ b/third_party/bigframes_vendored/pandas/io/pickle.py @@ -1,6 +1,5 @@ # Contains code from https://github.com/pandas-dev/pandas/blob/main/pandas/io/pickle.py -"""pickle compat""" - +""" pickle compat """ from __future__ import annotations from pandas._typing import ( @@ -19,8 +18,6 @@ def read_pickle( filepath_or_buffer: FilePath | ReadPickleBuffer, compression: CompressionOptions = "infer", storage_options: StorageOptions = None, - *, - write_engine="default", ): """Load pickled BigFrames object (or any object) from file. @@ -28,17 +25,23 @@ def read_pickle( If the content of the pickle file is a Series and its name attribute is None, the name will be set to '0' by default. - .. note:: - Data is inlined in the query SQL if it is small enough (roughly 5MB - or less in memory). Larger size data is loaded to a BigQuery table - instead. - **Examples:** >>> import bigframes.pandas as bpd + >>> bpd.options.display.progress_bar = None >>> gcs_path = "gs://bigframes-dev-testing/test_pickle.pkl" >>> df = bpd.read_pickle(filepath_or_buffer=gcs_path) + >>> df.head(2) + species island culmen_length_mm \\ + 0 Adelie Penguin (Pygoscelis adeliae) Dream 36.6 + 1 Adelie Penguin (Pygoscelis adeliae) Dream 39.8 + + culmen_depth_mm flipper_length_mm body_mass_g sex + 0 18.4 184.0 3475.0 FEMALE + 1 19.1 184.0 4650.0 MALE + + [2 rows x 7 columns] Args: filepath_or_buffer (str, path object, or file-like object): @@ -64,13 +67,9 @@ def read_pickle( starting with “s3://”, and “gcs://”) the key-value pairs are forwarded to fsspec.open. Please see fsspec and urllib for more details, and for more examples on storage options refer here. - write_engine (str): - How data should be written to BigQuery (if at all). See - :func:`bigframes.pandas.read_pandas` for a full description of - supported values. Returns: - bigframes.pandas.DataFrame or bigframes.pandas.Series: same type as object + bigframes.dataframe.DataFrame or bigframes.series.Series: same type as object stored in file. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) diff --git a/third_party/bigframes_vendored/pandas/pandas/_typing.py b/third_party/bigframes_vendored/pandas/pandas/_typing.py index 3640ba25163..e665339fc83 100644 --- a/third_party/bigframes_vendored/pandas/pandas/_typing.py +++ b/third_party/bigframes_vendored/pandas/pandas/_typing.py @@ -1,11 +1,10 @@ # Copied from https://github.com/pandas-dev/pandas/blob/main/pandas/_typing.py from __future__ import annotations -import sys from datetime import datetime, timedelta, tzinfo from os import PathLike +import sys from typing import ( - TYPE_CHECKING, Any, Callable, Dict, @@ -18,10 +17,9 @@ Protocol, Sequence, Tuple, - TypeVar, - Union, ) from typing import Type as type_t +from typing import TYPE_CHECKING, TypeVar, Union import numpy as np @@ -236,11 +234,13 @@ def flush(self) -> Any: class ReadPickleBuffer(ReadBuffer[bytes], Protocol): - def readline(self) -> bytes: ... + def readline(self) -> bytes: + ... class WriteExcelBuffer(WriteBuffer[bytes], Protocol): - def truncate(self, size: int | None = ...) -> int: ... + def truncate(self, size: int | None = ...) -> int: + ... class ReadCsvBuffer(ReadBuffer[AnyStr_co], Protocol): diff --git a/third_party/bigframes_vendored/pandas/plotting/_core.py b/third_party/bigframes_vendored/pandas/plotting/_core.py deleted file mode 100644 index 6c2aed970de..00000000000 --- a/third_party/bigframes_vendored/pandas/plotting/_core.py +++ /dev/null @@ -1,441 +0,0 @@ -import typing - -from bigframes import constants - - -class PlotAccessor: - """ - Make plots of Series or DataFrame with the `matplotlib` backend. - - **Examples:** - For Series: - - >>> import bigframes.pandas as bpd - >>> ser = bpd.Series([1, 2, 3, 3]) - >>> plot = ser.plot(kind='hist', title="My plot") - - For DataFrame: - - >>> df = bpd.DataFrame({'length': [1.5, 0.5, 1.2, 0.9, 3], - ... 'width': [0.7, 0.2, 0.15, 0.2, 1.1]}, - ... index=['pig', 'rabbit', 'duck', 'chicken', 'horse']) - >>> plot = df.plot(title="DataFrame Plot") - - Args: - data (Series or DataFrame): - The object for which the method is called. - kind (str): - The kind of plot to produce: - - - 'line' : line plot (default) - - 'hist' : histogram - - 'area' : area plot - - 'scatter' : scatter plot (DataFrame only) - - **kwargs: - Options to pass to `pandas.DataFrame.plot` method. See pandas - documentation online for more on these arguments. - - Returns: - matplotlib.axes.Axes or np.ndarray of them: - An ndarray is returned with one :class:`matplotlib.axes.Axes` - per column when ``subplots=True``. - """ - - def hist( - self, by: typing.Optional[typing.Sequence[str]] = None, bins: int = 10, **kwargs - ): - """ - Draw one histogram of the DataFrame’s columns. - - A histogram is a representation of the distribution of data. - This function groups the values of all given Series in the DataFrame - into bins and draws all bins in one :class:`matplotlib.axes.Axes`. - This is useful when the DataFrame's Series are in a similar scale. - - **Examples:** - - >>> import bigframes.pandas as bpd - >>> df = bpd.DataFrame(np.random.randint(1, 7, 6000), columns=['one']) - >>> df['two'] = np.random.randint(1, 7, 6000) + np.random.randint(1, 7, 6000) - >>> ax = df.plot.hist(bins=12, alpha=0.5) - - Args: - by (str or sequence, optional): - Column in the DataFrame to group by. It is not supported yet. - bins (int, default 10): - Number of histogram bins to be used. - **kwargs: - Additional keyword arguments are documented in - :meth:`DataFrame.plot`. - - Returns: - class:`matplotlib.AxesSubplot`: A histogram plot. - - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - def line( - self, - x: typing.Optional[typing.Hashable] = None, - y: typing.Optional[typing.Hashable] = None, - **kwargs, - ): - """ - Plot Series or DataFrame as lines. This function is useful to plot lines - using DataFrame's values as coordinates. - - This function calls `pandas.plot` to generate a plot with a random sample - of items. For consistent results, the random sampling is reproducible. - Use the `sampling_random_state` parameter to modify the sampling seed. - - **Examples:** - - >>> import bigframes.pandas as bpd - >>> df = bpd.DataFrame( - ... { - ... 'one': [1, 2, 3, 4], - ... 'three': [3, 6, 9, 12], - ... 'reverse_ten': [40, 30, 20, 10], - ... } - ... ) - >>> ax = df.plot.line(x='one') - - Args: - x (label or position, optional): - Allows plotting of one column versus another. If not specified, - the index of the DataFrame is used. - y (label or position, optional): - Allows plotting of one column versus another. If not specified, - all numerical columns are used. - color (str, array-like, or dict, optional): - The color for each of the DataFrame's columns. Possible values are: - - - A single color string referred to by name, RGB or RGBA code, - for instance 'red' or '#a98d19'. - - - A sequence of color strings referred to by name, RGB or RGBA - code, which will be used for each column recursively. For - instance ['green','yellow'] each column's %(kind)s will be filled in - green or yellow, alternatively. If there is only a single column to - be plotted, then only the first color from the color list will be - used. - - - A dict of the form {column name : color}, so that each column will be - colored accordingly. For example, if your columns are called `a` and - `b`, then passing {'a': 'green', 'b': 'red'} will color %(kind)ss for - column `a` in green and %(kind)ss for column `b` in red. - sampling_n (int, default 100): - Number of random items for plotting. - sampling_random_state (int, default 0): - Seed for random number generator. - **kwargs: - Additional keyword arguments are documented in - :meth:`DataFrame.plot`. - - Returns: - matplotlib.axes.Axes or np.ndarray of them: - An ndarray is returned with one :class:`matplotlib.axes.Axes` - per column when ``subplots=True``. - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - def area( - self, - x: typing.Optional[typing.Hashable] = None, - y: typing.Optional[typing.Hashable] = None, - stacked: bool = True, - **kwargs, - ): - """ - Draw a stacked area plot. An area plot displays quantitative data visually. - - This function calls `pandas.plot` to generate a plot with a random sample - of items. For consistent results, the random sampling is reproducible. - Use the `sampling_random_state` parameter to modify the sampling seed. - - **Examples:** - - Draw an area plot based on basic business metrics: - - >>> import bigframes.pandas as bpd - >>> df = bpd.DataFrame( - ... { - ... 'sales': [3, 2, 3, 9, 10, 6], - ... 'signups': [5, 5, 6, 12, 14, 13], - ... 'visits': [20, 42, 28, 62, 81, 50], - ... }, - ... index=["01-31", "02-28", "03-31", "04-30", "05-31", "06-30"] - ... ) - >>> ax = df.plot.area() - - Area plots are stacked by default. To produce an unstacked plot, - pass ``stacked=False``: - - >>> ax = df.plot.area(stacked=False) - - Draw an area plot for a single column: - - >>> ax = df.plot.area(y='sales') - - Draw with a different `x`: - - >>> df = bpd.DataFrame({ - ... 'sales': [3, 2, 3], - ... 'visits': [20, 42, 28], - ... 'day': [1, 2, 3], - ... }) - >>> ax = df.plot.area(x='day') - - Args: - x (label or position, optional): - Coordinates for the X axis. By default uses the index. - y (label or position, optional): - Column to plot. By default uses all columns. - stacked (bool, default True): - Area plots are stacked by default. Set to False to create a - unstacked plot. - sampling_n (int, default 100): - Number of random items for plotting. - sampling_random_state (int, default 0): - Seed for random number generator. - **kwargs: - Additional keyword arguments are documented in - :meth:`DataFrame.plot`. - - Returns: - matplotlib.axes.Axes or numpy.ndarray: - Area plot, or array of area plots if subplots is True. - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - def bar( - self, - x: typing.Optional[typing.Hashable] = None, - y: typing.Optional[typing.Hashable] = None, - **kwargs, - ): - """ - Draw a vertical bar plot. - - This function calls `pandas.plot` to generate a plot with a random sample - of items. For consistent results, the random sampling is reproducible. - Use the `sampling_random_state` parameter to modify the sampling seed. - - **Examples:** - - Basic plot. - - >>> import bigframes.pandas as bpd - >>> df = bpd.DataFrame({'lab':['A', 'B', 'C'], 'val':[10, 30, 20]}) - >>> ax = df.plot.bar(x='lab', y='val', rot=0) - - Plot a whole dataframe to a bar plot. Each column is assigned a distinct color, - and each row is nested in a group along the horizontal axis. - - >>> speed = [0.1, 17.5, 40, 48, 52, 69, 88] - >>> lifespan = [2, 8, 70, 1.5, 25, 12, 28] - >>> index = ['snail', 'pig', 'elephant', - ... 'rabbit', 'giraffe', 'coyote', 'horse'] - >>> df = bpd.DataFrame({'speed': speed, 'lifespan': lifespan}, index=index) - >>> ax = df.plot.bar(rot=0) - - Plot stacked bar charts for the DataFrame. - - >>> ax = df.plot.bar(stacked=True) - - If you don’t like the default colours, you can specify how you’d like each column - to be colored. - - >>> axes = df.plot.bar( - ... rot=0, subplots=True, color={"speed": "red", "lifespan": "green"} - ... ) - - Args: - x (label or position, optional): - Allows plotting of one column versus another. If not specified, the index - of the DataFrame is used. - y (label or position, optional): - Allows plotting of one column versus another. If not specified, all numerical - columns are used. - **kwargs: - Additional keyword arguments are documented in - :meth:`DataFrame.plot`. - - Returns: - matplotlib.axes.Axes or numpy.ndarray: - Area plot, or array of area plots if subplots is True. - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - def barh( - self, - x: typing.Optional[typing.Hashable] = None, - y: typing.Optional[typing.Hashable] = None, - **kwargs, - ): - """ - Draw a horizontal bar plot. - - This function calls `pandas.plot` to generate a plot with a random sample - of items. For consistent results, the random sampling is reproducible. - Use the `sampling_random_state` parameter to modify the sampling seed. - - **Examples:** - - Basic plot. - - >>> import bigframes.pandas as bpd - >>> df = bpd.DataFrame({'lab':['A', 'B', 'C'], 'val':[10, 30, 20]}) - >>> ax = df.plot.barh(x='lab', y='val', rot=0) - - Plot a whole dataframe to a barh plot. Each column is assigned a distinct color, - and each row is nested in a group along the horizontal axis. - - >>> speed = [0.1, 17.5, 40, 48, 52, 69, 88] - >>> lifespan = [2, 8, 70, 1.5, 25, 12, 28] - >>> index = ['snail', 'pig', 'elephant', - ... 'rabbit', 'giraffe', 'coyote', 'horse'] - >>> df = bpd.DataFrame({'speed': speed, 'lifespan': lifespan}, index=index) - >>> ax = df.plot.barh(rot=0) - - Plot stacked barh charts for the DataFrame. - - >>> ax = df.plot.barh(stacked=True) - - If you don’t like the default colours, you can specify how you’d like each column - to be colored. - - >>> axes = df.plot.barh( - ... rot=0, subplots=True, color={"speed": "red", "lifespan": "green"} - ... ) - - Args: - x (label or position, optional): - Allows plotting of one column versus another. If not specified, the index - of the DataFrame is used. - y (label or position, optional): - Allows plotting of one column versus another. If not specified, all numerical - columns are used. - **kwargs: - Additional keyword arguments are documented in - :meth:`DataFrame.plot`. - - Returns: - matplotlib.axes.Axes or numpy.ndarray: - Area plot, or array of area plots if subplots is True. - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - def pie( - self, - y: typing.Optional[typing.Hashable] = None, - **kwargs, - ): - """ - Generate a pie plot. - - A pie plot is a proportional representation of the numerical data in a - column. This function wraps :meth:`matplotlib.pyplot.pie` for the - specified column. If no column reference is passed and - ``subplots=True`` a pie plot is drawn for each numerical column - independently. - - **Examples:** - - In the example below we have a DataFrame with the information about - planet's mass and radius. We pass the 'mass' column to the - pie function to get a pie plot. - - >>> import bigframes.pandas as bpd - - >>> df = bpd.DataFrame({'mass': [0.330, 4.87 , 5.97], - ... 'radius': [2439.7, 6051.8, 6378.1]}, - ... index=['Mercury', 'Venus', 'Earth']) - >>> plot = df.plot.pie(y='mass', figsize=(5, 5)) - - >>> plot = df.plot.pie(subplots=True, figsize=(11, 6)) - - Args: - y (int or label, optional): - Label or position of the column to plot. - If not provided, ``subplots=True`` argument must be passed. - **kwargs: - Keyword arguments to pass on to :meth:`DataFrame.plot`. - - Returns: - matplotlib.axes.Axes or np.ndarray: - A NumPy array is returned when `subplots` is True. - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - def scatter( - self, - x: typing.Optional[typing.Hashable] = None, - y: typing.Optional[typing.Hashable] = None, - s: typing.Union[typing.Hashable, typing.Sequence[typing.Hashable]] = None, - c: typing.Union[typing.Hashable, typing.Sequence[typing.Hashable]] = None, - **kwargs, - ): - """ - Create a scatter plot with varying marker point size and color. - - This function calls `pandas.plot` to generate a plot with a random sample - of items. For consistent results, the random sampling is reproducible. - Use the `sampling_random_state` parameter to modify the sampling seed. - - **Examples:** - - Let's see how to draw a scatter plot using coordinates from the values - in a DataFrame's columns. - - >>> import bigframes.pandas as bpd - >>> df = bpd.DataFrame([[5.1, 3.5, 0], [4.9, 3.0, 0], [7.0, 3.2, 1], - ... [6.4, 3.2, 1], [5.9, 3.0, 2]], - ... columns=['length', 'width', 'species']) - >>> ax1 = df.plot.scatter(x='length', - ... y='width', - ... c='DarkBlue') - - And now with the color determined by a column as well. - - >>> ax2 = df.plot.scatter(x='length', - ... y='width', - ... c='species', - ... colormap='viridis') - - Args: - x (int or str): - The column name or column position to be used as horizontal - coordinates for each point. - y (int or str): - The column name or column position to be used as vertical - coordinates for each point. - s (str, scalar or array-like, optional): - The size of each point. Possible values are: - - - A string with the name of the column to be used for marker's size. - - A single scalar so all points have the same size. - - c (str, int or array-like, optional): - The color of each point. Possible values are: - - - A single color string referred to by name, RGB or RGBA code, - for instance 'red' or '#a98d19'. - - A column name or position whose values will be used to color the - marker points according to a colormap. - - sampling_n (int, default 100): - Number of random items for plotting. - sampling_random_state (int, default 0): - Seed for random number generator. - **kwargs: - Additional keyword arguments are documented in - :meth:`DataFrame.plot`. - - Returns: - matplotlib.axes.Axes or np.ndarray of them: - An ndarray is returned with one :class:`matplotlib.axes.Axes` - per column when ``subplots=True``. - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) diff --git a/third_party/bigframes_vendored/pandas/util/_exceptions.py b/third_party/bigframes_vendored/pandas/util/_exceptions.py deleted file mode 100644 index 4ca649153a0..00000000000 --- a/third_party/bigframes_vendored/pandas/util/_exceptions.py +++ /dev/null @@ -1,29 +0,0 @@ -# Contains code from https://github.com/pandas-dev/pandas/blob/main/pandas/util/_exceptions.py -from __future__ import annotations - -import inspect -import os - - -def find_stack_level() -> int: - """ - Find the first place in the stack that is not inside pandas - (tests notwithstanding). - """ - - import pandas as pd - - pkg_dir = os.path.dirname(pd.__file__) - test_dir = os.path.join(pkg_dir, "tests") - - # https://stackoverflow.com/questions/17407119/python-inspect-stack-is-slow - frame = inspect.currentframe() - n = 0 - while frame: - fname = inspect.getfile(frame) - if fname.startswith(pkg_dir) and not fname.startswith(test_dir): - frame = frame.f_back - n += 1 - else: - break - return n diff --git a/third_party/bigframes_vendored/pandas/util/_validators.py b/third_party/bigframes_vendored/pandas/util/_validators.py deleted file mode 100644 index fe8c9b5d9c6..00000000000 --- a/third_party/bigframes_vendored/pandas/util/_validators.py +++ /dev/null @@ -1,59 +0,0 @@ -# Contains code from https://github.com/pandas-dev/pandas/blob/main/pandas/util/_validators.py -""" -Module that contains many useful utilities -for validating data or function arguments -""" - -from __future__ import annotations - -from typing import TypeVar - -from pandas.core.dtypes.common import is_bool - -BoolishT = TypeVar("BoolishT", bool, int) -BoolishNoneT = TypeVar("BoolishNoneT", bool, int, None) - - -def validate_bool_kwarg( - value: BoolishNoneT, - arg_name: str, - none_allowed: bool = True, - int_allowed: bool = False, -) -> BoolishNoneT: - """ - Ensure that argument passed in arg_name can be interpreted as boolean. - - Parameters - ---------- - value : bool - Value to be validated. - arg_name : str - Name of the argument. To be reflected in the error message. - none_allowed : bool, default True - Whether to consider None to be a valid boolean. - int_allowed : bool, default False - Whether to consider integer value to be a valid boolean. - - Returns - ------- - value - The same value as input. - - Raises - ------ - ValueError - If the value is not a valid boolean. - """ - good_value = is_bool(value) - if none_allowed: - good_value = good_value or (value is None) - - if int_allowed: - good_value = good_value or isinstance(value, int) - - if not good_value: - raise ValueError( - f'For argument "{arg_name}" expected type bool, received ' - f"type {type(value).__name__}." - ) - return value # pyright: ignore[reportGeneralTypeIssues] diff --git a/third_party/bigframes_vendored/py.typed b/third_party/bigframes_vendored/py.typed deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/third_party/bigframes_vendored/sklearn/base.py b/third_party/bigframes_vendored/sklearn/base.py index 57c9e79f8de..768328e5529 100644 --- a/third_party/bigframes_vendored/sklearn/base.py +++ b/third_party/bigframes_vendored/sklearn/base.py @@ -81,13 +81,13 @@ class ClassifierMixin: def score(self, X, y): """Return the mean accuracy on the given test data and labels. - In multi-label classification, this is the subset accuracy, - which is a harsh metric since you require that - each label set be correctly predicted for each sample. + In multi-label classification, this is the subset accuracy + which is a harsh metric since you require for each sample that + each label set be correctly predicted. .. note:: - Output matches that of the BigQuery ML.EVALUATE function. + Output matches that of the BigQuery ML.EVALUTE function. See: https://cloud.google.com/bigquery/docs/reference/standard-sql/bigqueryml-syntax-evaluate#classification_models for the outputs relevant to this model type. @@ -115,7 +115,7 @@ def score(self, X, y): .. note:: - Output matches that of the BigQuery ML.EVALUATE function. + Output matches that of the BigQuery ML.EVALUTE function. See: https://cloud.google.com/bigquery/docs/reference/standard-sql/bigqueryml-syntax-evaluate#regression_models for the outputs relevant to this model type. @@ -153,7 +153,7 @@ def fit_transform(self, X, y=None): Target values (None for unsupervised transformations). Returns: - bigframes.dataframe.DataFrame: DataFrame of shape (n_samples, n_features_new). + bigframes.dataframe.DataFrame: DataFrame of shape (n_samples, n_features_new) Transformed DataFrame. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) diff --git a/third_party/bigframes_vendored/sklearn/cluster/_kmeans.py b/third_party/bigframes_vendored/sklearn/cluster/_kmeans.py index 2b1778eec8a..5369d3662d7 100644 --- a/third_party/bigframes_vendored/sklearn/cluster/_kmeans.py +++ b/third_party/bigframes_vendored/sklearn/cluster/_kmeans.py @@ -13,73 +13,35 @@ from abc import ABC -from bigframes_vendored.sklearn.base import BaseEstimator - from bigframes import constants +from third_party.bigframes_vendored.sklearn.base import BaseEstimator class _BaseKMeans(BaseEstimator, ABC): """Base class for KMeans and MiniBatchKMeans""" - pass - - -class KMeans(_BaseKMeans): - """K-Means clustering. - - **Examples:** + def predict(self, X): + """Predict the closest cluster each sample in X belongs to. - >>> import bigframes.pandas as bpd - >>> from bigframes.ml.cluster import KMeans + Args: + X (bigframes.dataframe.DataFrame or bigframes.series.Series): + Series or DataFrame of shape (n_samples, n_features). The data matrix for + which we want to get the predictions. - >>> X = bpd.DataFrame({"feat0": [1, 1, 1, 10, 10, 10], "feat1": [2, 4, 0, 2, 4, 0]}) - >>> kmeans = KMeans(n_clusters=2).fit(X) - >>> kmeans.predict(bpd.DataFrame({"feat0": [0, 12], "feat1": [0, 3]}))["CENTROID_ID"] # doctest:+SKIP - 0 1 - 1 2 - Name: CENTROID_ID, dtype: Int64 + Returns: + bigframes.dataframe.DataFrame: DataFrame of shape (n_samples,), containing the + class labels for each sample. + """ + raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - >>> kmeans.cluster_centers_ # doctest:+SKIP - centroid_id feature numerical_value categorical_value - 0 1 feat0 5.5 [] - 1 1 feat1 1.0 [] - 2 2 feat0 5.5 [] - 3 2 feat1 4.0 [] - [4 rows x 4 columns] +class KMeans(_BaseKMeans): + """K-Means clustering. Args: n_clusters (int, default 8): The number of clusters to form as well as the number of centroids to generate. Default to 8. - - init ("kmeans++", "random" or "custom", default "kmeans++"): - The method of initializing the clusters. Default to "kmeans++" - - kmeas++: Initializes a number of centroids equal to the n_clusters value by using the k-means++ algorithm. Using this approach usually trains a better model than using random cluster initialization. - random: Initializes the centroids by randomly selecting a number of data points equal to the n_clusters value from the input data. - custom: Initializes the centroids using a provided column of type bool. Uses the rows with a value of True as the initial centroids. You specify the column to use by using the init_col option. - - init_col (str or None, default None): - The name of the column to use to initialize the centroids. This column must have a type of bool. If this column contains a value of True for a given row, then uses that row as an initial centroid. The number of True rows in this column must be equal to the value you have specified for the n_clusters option. - Only works with init method "custom". Default to None. - - distance_type ("euclidean" or "cosine", default "euclidean"): - The type of metric to use to compute the distance between two points. - Default to "euclidean". - - max_iter (int, default 20): - The maximum number of training iterations, where one iteration represents a single pass of the entire training data. Default to 20. - - tol (float, default 0.01): - The minimum relative loss improvement that is necessary to continue training. For example, a value of 0.01 specifies that each iteration must reduce the loss by 1% for training to continue. - Default to 0.01. - - warm_start (bool, default False): - Determines whether to train a model with new training data, new model options, or both. Unless you explicitly override them, the initial options used to train the model are used for the warm start run. - Default to False. - - """ def fit( @@ -90,13 +52,13 @@ def fit( """Compute k-means clustering. Args: - X (bigframes.dataframe.DataFrame or bigframes.series.Series or pandas.core.frame.DataFrame or pandas.core.series.Series): + X (bigframes.dataframe.DataFrame or bigframes.series.Series): DataFrame of shape (n_samples, n_features). Training data. y (default None): Not used, present here for API consistency by convention. Returns: - KMeans: Fitted estimator. + KMeans: Fitted Estimator. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @@ -107,31 +69,11 @@ def predict( """Predict the closest cluster each sample in X belongs to. Args: - X (bigframes.dataframe.DataFrame or bigframes.series.Series or pandas.core.frame.DataFrame or pandas.core.series.Series): + X (bigframes.dataframe.DataFrame or bigframes.series.Series): DataFrame of shape (n_samples, n_features). New data to predict. Returns: - bigframes.dataframe.DataFrame: DataFrame of shape (n_samples, n_input_columns + n_prediction_columns). Returns predicted labels. - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - def fit_predict( - self, - X, - y=None, - ): - """Compute cluster centers and predict cluster index for each sample. - - Convenience method; equivalent to calling fit(X) followed by predict(X). - - Args: - X (bigframes.dataframe.DataFrame or bigframes.series.Series or pandas.core.frame.DataFrame or pandas.core.series.Series): - DataFrame of shape (n_samples, n_features). Training data. - y (default None): - Not used, present here for API consistency by convention. - - Returns: - bigframes.dataframe.DataFrame: DataFrame of shape (n_samples, n_input_columns + n_prediction_columns). Returns predicted labels. + bigframes.dataframe.DataFrame: DataFrame of the cluster each sample belongs to. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @@ -144,12 +86,12 @@ def score( .. note:: - Output matches that of the BigQuery ML.EVALUATE function. + Output matches that of the BigQuery ML.EVALUTE function. See: https://cloud.google.com/bigquery/docs/reference/standard-sql/bigqueryml-syntax-evaluate#k-means_models for the outputs relevant to this model type. Args: - X (bigframes.dataframe.DataFrame or bigframes.series.Series or pandas.core.frame.DataFrame or pandas.core.series.Series): + X (bigframes.dataframe.DataFrame or bigframes.series.Series): DataFrame of shape (n_samples, n_features). New Data. y (default None) Not used, present here for API consistency by convention. diff --git a/third_party/bigframes_vendored/sklearn/compose/_column_transformer.py b/third_party/bigframes_vendored/sklearn/compose/_column_transformer.py index e0c03536e09..dead173b2df 100644 --- a/third_party/bigframes_vendored/sklearn/compose/_column_transformer.py +++ b/third_party/bigframes_vendored/sklearn/compose/_column_transformer.py @@ -3,11 +3,11 @@ # Andreas Mueller # License: BSD -from abc import ABCMeta -from bigframes_vendored.sklearn.base import BaseEstimator +from abc import ABCMeta from bigframes import constants +from third_party.bigframes_vendored.sklearn.base import BaseEstimator class _BaseComposition(BaseEstimator, metaclass=ABCMeta): @@ -18,9 +18,9 @@ class ColumnTransformer(_BaseComposition): """Applies transformers to columns of BigQuery DataFrames. This estimator allows different columns or column subsets of the input - to be transformed separately, and the features generated by each transformer + to be transformed separately and the features generated by each transformer will be concatenated to form a single feature space. - This is useful for heterogeneous or columnar data to combine several + This is useful for heterogeneous or columnar data, to combine several feature extraction mechanisms or transformations into a single transformer. Args: @@ -36,7 +36,7 @@ def fit( """Fit all transformers using X. Args: - X (bigframes.dataframe.DataFrame or bigframes.series.Series or pandas.core.frame.DataFrame or pandas.core.series.Series): + X (bigframes.dataframe.DataFrame or bigframes.series.Series): The Series or DataFrame of shape (n_samples, n_features). Training vector, where `n_samples` is the number of samples and `n_features` is the number of features. @@ -53,7 +53,7 @@ def transform( """Transform X separately by each transformer, concatenate results. Args: - X (bigframes.dataframe.DataFrame or bigframes.series.Series or pandas.core.frame.DataFrame or pandas.core.series.Series): + X (bigframes.dataframe.DataFrame or bigframes.series.Series): The Series or DataFrame to be transformed by subset. Returns: diff --git a/third_party/bigframes_vendored/sklearn/decomposition/_mf.py b/third_party/bigframes_vendored/sklearn/decomposition/_mf.py deleted file mode 100644 index 0ce79995d0c..00000000000 --- a/third_party/bigframes_vendored/sklearn/decomposition/_mf.py +++ /dev/null @@ -1,115 +0,0 @@ -"""Matrix Factorization.""" - -# Author: Alexandre Gramfort -# Olivier Grisel -# Mathieu Blondel -# Denis A. Engemann -# Michael Eickenberg -# Giorgio Patrini -# -# License: BSD 3 clause - -from abc import ABCMeta - -from bigframes_vendored.sklearn.base import BaseEstimator - -from bigframes import constants - - -class MatrixFactorization(BaseEstimator, metaclass=ABCMeta): - """Matrix Factorization (MF). - - **Examples:** - - >>> import bigframes.pandas as bpd - >>> from bigframes.ml.decomposition import MatrixFactorization - >>> X = bpd.DataFrame({ - ... "row": [0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6], - ... "column": [0,1] * 7, - ... "value": [1, 1, 2, 1, 3, 1.2, 4, 1, 5, 0.8, 6, 1, 2, 3], - ... }) - >>> model = MatrixFactorization(feedback_type='explicit', num_factors=6, user_col='row', item_col='column', rating_col='value', l2_reg=2.06) - >>> W = model.fit(X) # doctest: +SKIP - - Args: - feedback_type ('explicit' | 'implicit'): - Specifies the feedback type for the model. The feedback type determines the algorithm that is used during training. - num_factors (int or auto, default auto): - Specifies the number of latent factors to use. - user_col (str): - The user column name. - item_col (str): - The item column name. - l2_reg (float, default 1.0): - A floating point value for L2 regularization. The default value is 1.0. - """ - - def fit(self, X, y=None): - """Fit the model according to the given training data. - - Args: - X (bigframes.dataframe.DataFrame or bigframes.series.Series or pandas.core.frame.DataFrame or pandas.core.series.Series): - Series or DataFrame of shape (n_samples, n_features). Training vector, - where `n_samples` is the number of samples and `n_features` is - the number of features. - - y (default None): - Ignored. - - Returns: - bigframes.ml.decomposition.MatrixFactorization: Fitted estimator. - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - def score(self, X=None, y=None): - """Calculate evaluation metrics of the model. - - .. note:: - - Output matches that of the BigQuery ML.EVALUATE function. - See: https://cloud.google.com/bigquery/docs/reference/standard-sql/bigqueryml-syntax-evaluate#matrix_factorization_models - for the outputs relevant to this model type. - - Args: - X (bigframes.dataframe.DataFrame | bigframes.series.Series | None): - DataFrame of shape (n_samples, n_features). Test samples. - - y (bigframes.dataframe.DataFrame | bigframes.series.Series | None): - DataFrame of shape (n_samples,) or (n_samples, n_outputs). True - labels for `X`. - - Returns: - bigframes.dataframe.DataFrame: DataFrame that represents model metrics. - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - def predict(self, X): - """Generate a predicted rating for every user-item row combination for a matrix factorization model. - - Args: - X (bigframes.dataframe.DataFrame or bigframes.series.Series or pandas.core.frame.DataFrame or pandas.core.series.Series): - Series or a DataFrame to predict. - - Returns: - bigframes.dataframe.DataFrame: Predicted DataFrames.""" - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - def fit_predict( - self, - X, - y=None, - ): - """Fit the model with X and generate a predicted rating for every user-item row combination for a matrix factorization model. on X. - - Convenience method; equivalent to calling fit(X) followed by predict(X). - - Args: - X (bigframes.dataframe.DataFrame or bigframes.series.Series or pandas.core.frame.DataFrame or pandas.core.series.Series): - DataFrame of shape (n_samples, n_features). Training data. - y (default None): - Not used, present here for API consistency by convention. - - Returns: - bigframes.dataframe.DataFrame: DataFrame of shape (n_samples, n_input_columns + n_prediction_columns). Returns predicted labels. - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) diff --git a/third_party/bigframes_vendored/sklearn/decomposition/_pca.py b/third_party/bigframes_vendored/sklearn/decomposition/_pca.py index 138b7772b24..011ecc06dd3 100644 --- a/third_party/bigframes_vendored/sklearn/decomposition/_pca.py +++ b/third_party/bigframes_vendored/sklearn/decomposition/_pca.py @@ -1,4 +1,5 @@ -"""Principal Component Analysis.""" +""" Principal Component Analysis. +""" # Author: Alexandre Gramfort # Olivier Grisel @@ -11,44 +12,28 @@ from abc import ABCMeta -from bigframes_vendored.sklearn.base import BaseEstimator - from bigframes import constants +from third_party.bigframes_vendored.sklearn.base import BaseEstimator class PCA(BaseEstimator, metaclass=ABCMeta): """Principal component analysis (PCA). - **Examples:** - - >>> import bigframes.pandas as bpd - >>> from bigframes.ml.decomposition import PCA - >>> X = bpd.DataFrame({"feat0": [-1, -2, -3, 1, 2, 3], "feat1": [-1, -1, -2, 1, 1, 2]}) - >>> pca = PCA(n_components=2).fit(X) - >>> pca.predict(X) # doctest:+SKIP - principal_component_1 principal_component_2 - 0 -0.755243 0.157628 - 1 -1.05405 -0.141179 - 2 -1.809292 0.016449 - 3 0.755243 -0.157628 - 4 1.05405 0.141179 - 5 1.809292 -0.016449 - - [6 rows x 2 columns] - >>> pca.explained_variance_ratio_ # doctest:+SKIP - principal_component_id explained_variance_ratio - 0 1 0.00901 - 1 0 0.99099 - - [2 rows x 2 columns] + Linear dimensionality reduction using Singular Value Decomposition of the + data to project it to a lower dimensional space. The input data is centered + but not scaled for each feature before applying the SVD. + + It uses the LAPACK implementation of the full SVD or a randomized truncated + SVD by the method of Halko et al. 2009, depending on the shape of the input + data and the number of components to extract. + + It can also use the scipy.sparse.linalg ARPACK implementation of the + truncated SVD. Args: - n_components (int, float or None, default None): - Number of components to keep. If n_components is not set, all - components are kept, n_components = min(n_samples, n_features). - If 0 < n_components < 1, select the number of components such that the amount of variance that needs to be explained is greater than the percentage specified by n_components. - svd_solver ("full", "randomized" or "auto", default "auto"): - The solver to use to calculate the principal components. Details: https://cloud.google.com/bigquery/docs/reference/standard-sql/bigqueryml-syntax-create-pca#pca_solver. + n_components (Optional[int], default 3): + Number of components to keep. if n_components is not set all components + are kept. """ @@ -56,7 +41,7 @@ def fit(self, X, y=None): """Fit the model according to the given training data. Args: - X (bigframes.dataframe.DataFrame or bigframes.series.Series or pandas.core.frame.DataFrame or pandas.core.series.Series): + X (bigframes.dataframe.DataFrame or bigframes.series.Series): Series or DataFrame of shape (n_samples, n_features). Training vector, where `n_samples` is the number of samples and `n_features` is the number of features. @@ -74,7 +59,7 @@ def score(self, X=None, y=None): .. note:: - Output matches that of the BigQuery ML.EVALUATE function. + Output matches that of the BigQuery ML.EVALUTE function. See: https://cloud.google.com/bigquery/docs/reference/standard-sql/bigqueryml-syntax-evaluate#pca_models for the outputs relevant to this model type. @@ -93,31 +78,11 @@ def predict(self, X): """Predict the closest cluster for each sample in X. Args: - X (bigframes.dataframe.DataFrame or bigframes.series.Series or pandas.core.frame.DataFrame or pandas.core.series.Series): + X (bigframes.dataframe.DataFrame or bigframes.series.Series): Series or a DataFrame to predict. Returns: - bigframes.dataframe.DataFrame: Predicted DataFrames.""" - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - def fit_predict( - self, - X, - y=None, - ): - """Fit the model with X and apply the dimensionality reduction on X. - - Convenience method; equivalent to calling fit(X) followed by predict(X). - - Args: - X (bigframes.dataframe.DataFrame or bigframes.series.Series or pandas.core.frame.DataFrame or pandas.core.series.Series): - DataFrame of shape (n_samples, n_features). Training data. - y (default None): - Not used, present here for API consistency by convention. - - Returns: - bigframes.dataframe.DataFrame: DataFrame of shape (n_samples, n_input_columns + n_prediction_columns). Returns predicted labels. - """ + bigframes.dataframe.DataFrame: predicted DataFrames.""" raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @property @@ -132,7 +97,7 @@ def components_(self): numerical_value: If feature is numeric, the value of feature for the principal component that principal_component_id identifies. If feature isn't numeric, the value is NULL. - categorical_value: A list of mappings containing information about categorical features. Each mapping contains the following fields: + categorical_value: An list of mappings containing information about categorical features. Each mapping contains the following fields: categorical_value.category: The name of each category. categorical_value.value: The value of categorical_value.category for the centroid that centroid_id identifies. diff --git a/third_party/bigframes_vendored/sklearn/ensemble/_forest.py b/third_party/bigframes_vendored/sklearn/ensemble/_forest.py index fb81bd66847..6be41bf9aa5 100644 --- a/third_party/bigframes_vendored/sklearn/ensemble/_forest.py +++ b/third_party/bigframes_vendored/sklearn/ensemble/_forest.py @@ -47,23 +47,16 @@ def fit(self, X, y): """Build a forest of trees from the training set (X, y). Args: - X (bigframes.dataframe.DataFrame or bigframes.series.Series or pandas.core.frame.DataFrame or pandas.core.series.Series): + X: Series or DataFrame of shape (n_samples, n_features). Training data. - y (bigframes.dataframe.DataFrame or bigframes.series.Series or pandas.core.frame.DataFrame or pandas.core.series.Series): + y: Series or DataFrame of shape (n_samples,) or (n_samples, n_targets). Target values. Will be cast to X's dtype if necessary. - X_eval (bigframes.dataframe.DataFrame or bigframes.series.Series or pandas.core.frame.DataFrame or pandas.core.series.Series): - Series or DataFrame of shape (n_samples, n_features). Evaluation data. - - y_eval (bigframes.dataframe.DataFrame or bigframes.series.Series or pandas.core.frame.DataFrame or pandas.core.series.Series): - Series or DataFrame of shape (n_samples,) or (n_samples, n_targets). - Evaluation target values. Will be cast to X_eval's dtype if necessary. - Returns: - ForestModel: Fitted estimator. + Fitted Estimator. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @@ -80,12 +73,12 @@ def predict(self, X): mean predicted regression targets of the trees in the forest. Args: - X (bigframes.dataframe.DataFrame or bigframes.series.Series or pandas.core.frame.DataFrame or pandas.core.series.Series): + X: Series or DataFrame of shape (n_samples, n_features). The data matrix for which we want to get the predictions. Returns: - bigframes.dataframe.DataFrame: The predicted values. + The predicted values. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @@ -98,36 +91,38 @@ class RandomForestRegressor(ForestRegressor): to improve the predictive accuracy and control over-fitting. Args: - n_estimators (Optional[int]): + num_parallel_tree: Optional[int] Number of parallel trees constructed during each iteration. Default to 100. Minimum value is 2. - tree_method (Optional[str]): + tree_method: Optional[str] Specify which tree method to use. Default to "auto". If this parameter is set to - default, XGBoost will choose the most conservative option available. Possible values: "exact", "approx", + default, XGBoost will choose the most conservative option available. Possible values: ""exact", "approx", "hist". - min_child_weight (Optional[float]): + min_child_weight : Optional[float] Minimum sum of instance weight(hessian) needed in a child. Default to 1. - colsample_bytree (Optional[float]): + colsample_bytree : Optional[float] Subsample ratio of columns when constructing each tree. Default to 1.0. The value should be between 0 and 1. - colsample_bylevel (Optional[float]): + colsample_bylevel : Optional[float] Subsample ratio of columns for each level. Default to 1.0. The value should be between 0 and 1. - colsample_bynode (Optional[float]): + colsample_bynode : Optional[float] Subsample ratio of columns for each split. Default to 0.8. The value should be between 0 and 1. - gamma (Optional[float]): + gamma : Optional[float] (min_split_loss) Minimum loss reduction required to make a further partition on a leaf node of the tree. Default to 0.0. - max_depth (Optional[int]): + max_depth : Optional[int] Maximum tree depth for base learners. Default to 15. The value should be greater than 0 and less than 1. - subsample (Optional[float]: + subsample : Optional[float] Subsample ratio of the training instance. Default to 0.8. The value should be greater than 0 and less than 1. - reg_alpha (Optional[float]): + reg_alpha : Optional[float] L1 regularization term on weights (xgb's alpha). Default to 0.0. - reg_lambda (Optional[float]): + reg_lambda : Optional[float] L2 regularization term on weights (xgb's lambda). Default to 1.0. - tol (Optional[float]): - Minimum relative loss improvement necessary to continue training. Default to 0.01. - enable_global_explain (Optional[bool]): + early_stop: Optional[bool] + Whether training should stop after the first iteration. Default to True. + min_rel_progress: Optional[float] + Minimum relative loss improvement necessary to continue training when early_stop is set to True. Default to 0.01. + enable_global_explain: Optional[bool] Whether to compute global explanations using explainable AI to evaluate global feature importance to the model. Default to False. - xgboost_version (Optional[str]): + xgboost_version: Optional[str] Specifies the Xgboost version for model training. Default to "0.9". Possible values: "0.9", "1.1". """ @@ -149,7 +144,7 @@ def predict(self, X): which we want to get the predictions. Returns: - bigframes.dataframe.DataFrame: The predicted values. + The predicted values. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @@ -163,35 +158,37 @@ class RandomForestClassifier(ForestClassifier): improve the predictive accuracy and control over-fitting. Args: - n_estimators (Optional[int]): + num_parallel_tree: Optional[int] Number of parallel trees constructed during each iteration. Default to 100. Minimum value is 2. - tree_method (Optional[str]): + tree_method: Optional[str] Specify which tree method to use. Default to "auto". If this parameter is set to - default, XGBoost will choose the most conservative option available. Possible values: "exact", "approx", + default, XGBoost will choose the most conservative option available. Possible values: ""exact", "approx", "hist". - min_child_weight (Optional[float]): + min_child_weight : Optional[float] Minimum sum of instance weight(hessian) needed in a child. Default to 1. - colsample_bytree (Optional[float]): + colsample_bytree : Optional[float] Subsample ratio of columns when constructing each tree. Default to 1.0. The value should be between 0 and 1. - colsample_bylevel (Optional[float]): + colsample_bylevel : Optional[float] Subsample ratio of columns for each level. Default to 1.0. The value should be between 0 and 1. - colsample_bynode (Optional[float]): + colsample_bynode : Optional[float] Subsample ratio of columns for each split. Default to 0.8. The value should be between 0 and 1. - gamma (Optional[float]): + gamma : Optional[float] (min_split_loss) Minimum loss reduction required to make a further partition on a leaf node of the tree. Default to 0.0. - max_depth (Optional[int]): + max_depth : Optional[int] Maximum tree depth for base learners. Default to 15. The value should be greater than 0 and less than 1. - subsample (Optional[float]): + subsample : Optional[float] Subsample ratio of the training instance. Default to 0.8. The value should be greater than 0 and less than 1. - reg_alpha (Optional[float]): + reg_alpha : Optional[float] L1 regularization term on weights (xgb's alpha). Default to 0.0. - reg_lambda (Optional[float]): + reg_lambda : Optional[float] L2 regularization term on weights (xgb's lambda). Default to 1.0. - tol (Optional[float]): - Minimum relative loss improvement necessary to continue training. Default to 0.01. - enable_global_explain (Optional[bool]): + early_stop: Optional[bool] + Whether training should stop after the first iteration. Default to True. + min_rel_progress: Optional[float] + Minimum relative loss improvement necessary to continue training when early_stop is set to True. Default to 0.01. + enable_global_explain: Optional[bool] Whether to compute global explanations using explainable AI to evaluate global feature importance to the model. Default to False. - xgboost_version (Optional[str]): + xgboost_version: Optional[str] Specifies the Xgboost version for model training. Default to "0.9". Possible values: "0.9", "1.1".ß """ diff --git a/third_party/bigframes_vendored/sklearn/impute/_base.py b/third_party/bigframes_vendored/sklearn/impute/_base.py deleted file mode 100644 index 175ad86b21b..00000000000 --- a/third_party/bigframes_vendored/sklearn/impute/_base.py +++ /dev/null @@ -1,68 +0,0 @@ -# Authors: Nicolas Tresegnie -# Sergey Feldman -# License: BSD 3 clause - -from bigframes_vendored.sklearn.base import BaseEstimator, TransformerMixin - -from bigframes import constants - - -class _BaseImputer(TransformerMixin, BaseEstimator): - """Base class for all imputers.""" - - -class SimpleImputer(_BaseImputer): - """ - Univariate imputer for completing missing values with simple strategies. - - Replace missing values using a descriptive statistic (e.g. mean, median, or - most frequent) along each column. - - **Examples:** - - >>> import bigframes.pandas as bpd - >>> from bigframes.ml.impute import SimpleImputer - >>> X_train = bpd.DataFrame({"feat0": [7.0, 4.0, 10.0], "feat1": [2.0, None, 5.0], "feat2": [3.0, 6.0, 9.0]}) - >>> imp_mean = SimpleImputer().fit(X_train) - >>> X_test = bpd.DataFrame({"feat0": [None, 4.0, 10.0], "feat1": [2.0, None, None], "feat2": [3.0, 6.0, 9.0]}) - >>> imp_mean.transform(X_test) - imputer_feat0 imputer_feat1 imputer_feat2 - 0 7.0 2.0 3.0 - 1 4.0 3.5 6.0 - 2 10.0 3.5 9.0 - - [3 rows x 3 columns] - - Args: - strategy ({'mean', 'median', 'most_frequent'}, default='mean'): - The imputation strategy. 'mean': replace missing values using the mean along - the axis. 'median':replace missing values using the median along - the axis. 'most_frequent', replace missing using the most frequent - value along the axis. - """ - - def fit(self, X, y=None): - """Fit the imputer on X. - - Args: - X (bigframes.dataframe.DataFrame or bigframes.series.Series or pandas.core.frame.DataFrame or pandas.core.series.Series): - The Dataframe or Series with training data. - - y (default None): - Ignored. - - Returns: - SimpleImputer: Fitted scaler. - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - def transform(self, X): - """Impute all missing values in X. - - Args: - X (bigframes.dataframe.DataFrame or bigframes.series.Series or pandas.core.frame.DataFrame or pandas.core.series.Series): - The DataFrame or Series to be transformed. - - Returns: - bigframes.dataframe.DataFrame: Transformed result.""" - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) diff --git a/third_party/bigframes_vendored/sklearn/linear_model/_base.py b/third_party/bigframes_vendored/sklearn/linear_model/_base.py index 7543edd10b7..8dc3b6280a4 100644 --- a/third_party/bigframes_vendored/sklearn/linear_model/_base.py +++ b/third_party/bigframes_vendored/sklearn/linear_model/_base.py @@ -16,26 +16,26 @@ # Original location: https://github.com/scikit-learn/scikit-learn/blob/main/sklearn/linear_model/_base.py from abc import ABCMeta +from typing import List, Optional -from bigframes_vendored.sklearn.base import ( +from bigframes import constants +from third_party.bigframes_vendored.sklearn.base import ( BaseEstimator, ClassifierMixin, RegressorMixin, ) -from bigframes import constants - class LinearModel(BaseEstimator, metaclass=ABCMeta): def predict(self, X): """Predict using the linear model. Args: - X (bigframes.dataframe.DataFrame or bigframes.series.Series or pandas.core.frame.DataFrame or pandas.core.series.Series): + X (bigframes.dataframe.DataFrame or bigframes.series.Series): Series or DataFrame of shape (n_samples, n_features). Samples. Returns: - bigframes.dataframe.DataFrame: DataFrame of shape (n_samples, n_input_columns + n_prediction_columns). Returns predicted values. + bigframes.dataframe.DataFrame: DataFrame of shape (n_samples,). Returns predicted values. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @@ -45,12 +45,13 @@ def predict(self, X): """Predict class labels for samples in X. Args: - X (bigframes.dataframe.DataFrame or bigframes.series.Series or pandas.core.frame.DataFrame or pandas.core.series.Series): + X (bigframes.dataframe.DataFrame or bigframes.series.Series): Series or DataFrame of shape (n_samples, n_features). The data matrix for which we want to get the predictions. Returns: - bigframes.dataframe.DataFrame: DataFrame of shape (n_samples, n_input_columns + n_prediction_columns). Returns predicted values. + bigframes.dataframe.DataFrame: DataFrame of shape (n_samples,), containing + the class labels for each sample. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @@ -62,54 +63,27 @@ class LinearRegression(RegressorMixin, LinearModel): to minimize the residual sum of squares between the observed targets in the dataset, and the targets predicted by the linear approximation. - **Examples:** - - >>> from bigframes.ml.linear_model import LinearRegression - >>> import bigframes.pandas as bpd - >>> X = bpd.DataFrame({ \ - "feature0": [20, 21, 19, 18], \ - "feature1": [0, 1, 1, 0], \ - "feature2": [0.2, 0.3, 0.4, 0.5]}) - >>> y = bpd.DataFrame({"outcome": [0, 0, 1, 1]}) - >>> # Create the linear model - >>> model = LinearRegression() - >>> model.fit(X, y) - LinearRegression() - - >>> # Score the model - >>> score = model.score(X, y) - >>> print(score) # doctest:+SKIP - mean_absolute_error mean_squared_error mean_squared_log_error \ - 0 0.022812 0.000602 0.00035 - median_absolute_error r2_score explained_variance - 0 0.015077 0.997591 0.997591 - - Args: - optimize_strategy (str, default "auto_strategy"): + optimize_strategy (str, default "normal_equation"): The strategy to train linear regression models. Possible values are "auto_strategy", "batch_gradient_descent", "normal_equation". Default - to "auto_strategy". + to "normal_equation". fit_intercept (bool, default True): Default ``True``. Whether to calculate the intercept for this model. If set to False, no intercept will be used in calculations (i.e. data is expected to be centered). - l1_reg (float or None, default None): - The amount of L1 regularization applied. Default to None. Can't be set in "normal_equation" mode. If unset, value 0 is used. l2_reg (float, default 0.0): The amount of L2 regularization applied. Default to 0. max_iterations (int, default 20): The maximum number of training iterations or steps. Default to 20. - warm_start (bool, default False): - Determines whether to train a model with new training data, new model options, or both. Unless you explicitly override them, the initial options used to train the model are used for the warm start run. Default to False. - learning_rate (float or None, default None): - The learn rate for gradient descent when learning_rate_strategy='constant'. If unset, value 0.1 is used. If learning_rate_strategy='line_search', an error is returned. - learning_rate_strategy (str, default "line_search"): + learn_rate_strategy (str, default "line_search"): The strategy for specifying the learning rate during training. Default to "line_search". - tol (float, default 0.01): + early_stop (bool, default True): + Whether training should stop after the first iteration in which the relative loss improvement is less than the value specified for min_rel_progress. Default to True. + min_rel_progress (float, default 0.01): The minimum relative loss improvement that is necessary to continue training when EARLY_STOP is set to true. For example, a value of 0.01 specifies that each iteration must reduce the loss by 1% for training to continue. Default to 0.01. - ls_init_learning_rate (float or None, default None): - Sets the initial learning rate that learning_rate_strategy='line_search' uses. This option can only be used if line_search is specified. If unset, value 0.1 is used. + ls_init_learn_rate (float, default 0.1): + Sets the initial learning rate that learn_rate_strategy='line_search' uses. This option can only be used if line_search is specified. Default to 0.1. calculate_p_values (bool, default False): Specifies whether to compute p-values and standard errors during training. Default to False. enable_global_explain (bool, default False): @@ -124,21 +98,14 @@ def fit( """Fit linear model. Args: - X (bigframes.dataframe.DataFrame or bigframes.series.Series or pandas.core.frame.DataFrame or pandas.core.series.Series): + X (bigframes.dataframe.DataFrame or bigframes.series.Series): Series or DataFrame of shape (n_samples, n_features). Training data. - y (bigframes.dataframe.DataFrame or bigframes.series.Series or pandas.core.frame.DataFrame or pandas.core.series.Series): + y (bigframes.dataframe.DataFrame or bigframes.series.Series): Series or DataFrame of shape (n_samples,) or (n_samples, n_targets). Target values. Will be cast to X's dtype if necessary. - X_eval (bigframes.dataframe.DataFrame or bigframes.series.Series or pandas.core.frame.DataFrame or pandas.core.series.Series): - Series or DataFrame of shape (n_samples, n_features). Evaluation data. - - y_eval (bigframes.dataframe.DataFrame or bigframes.series.Series or pandas.core.frame.DataFrame or pandas.core.series.Series): - Series or DataFrame of shape (n_samples,) or (n_samples, n_targets). - Evaluation target values. Will be cast to X_eval's dtype if necessary. - Returns: - LinearRegression: Fitted estimator. + LinearRegression: Fitted Estimator. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) diff --git a/third_party/bigframes_vendored/sklearn/linear_model/_logistic.py b/third_party/bigframes_vendored/sklearn/linear_model/_logistic.py index a309166c90c..989ca03c827 100644 --- a/third_party/bigframes_vendored/sklearn/linear_model/_logistic.py +++ b/third_party/bigframes_vendored/sklearn/linear_model/_logistic.py @@ -11,87 +11,31 @@ # Arthur Mensch >> from bigframes.ml.linear_model import LogisticRegression - >>> import bigframes.pandas as bpd - >>> X = bpd.DataFrame({ \ - "feature0": [20, 21, 19, 18], \ - "feature1": [0, 1, 1, 0], \ - "feature2": [0.2, 0.3, 0.4, 0.5]}) - >>> y = bpd.DataFrame({"outcome": [0, 0, 1, 1]}) - >>> # Create the LogisticRegression - >>> model = LogisticRegression() - >>> model.fit(X, y) - LogisticRegression() - >>> model.predict(X) # doctest:+SKIP - predicted_outcome predicted_outcome_probs feature0 feature1 feature2 - 0 0 [{'label': 1, 'prob': 3.1895929877221615e-07} ... 20 0 0.2 - 1 0 [{'label': 1, 'prob': 5.662891265051953e-06} ... 21 1 0.3 - 2 1 [{'label': 1, 'prob': 0.9999917826885262} {'l... 19 1 0.4 - 3 1 [{'label': 1, 'prob': 0.9999999993659574} {'l... 18 0 0.5 - 4 rows × 5 columns - - [4 rows x 5 columns in total] - - >>> # Score the model - >>> score = model.score(X, y) - >>> score # doctest:+SKIP - precision recall accuracy f1_score log_loss roc_auc - 0 1.0 1.0 1.0 1.0 0.000004 1.0 - 1 rows × 6 columns - - [1 rows x 6 columns in total] - Args: - optimize_strategy (str, default "auto_strategy"): - The strategy to train logistic regression models. Possible values are - "auto_strategy" and "batch_gradient_descent". The two are equilevant since - "auto_strategy" will fall back to "batch_gradient_descent". The API is kept - for consistency. - Default to "auto_strategy". fit_intercept (default True): Default True. Specifies if a constant (a.k.a. bias or intercept) should be added to the decision function. - class_weight (dict or 'balanced', default None): + class_weights (dict or 'balanced', default None): Default None. Weights associated with classes in the form ``{class_label: weight}``.If not given, all classes are supposed to have weight one. The "balanced" mode uses the values of y to automatically adjust weights inversely proportional to class frequencies in the input data as ``n_samples / (n_classes * np.bincount(y))``. Dict isn't - supported. - l1_reg (float or None, default None): - The amount of L1 regularization applied. Default to None. Can't be set in "normal_equation" mode. If unset, value 0 is used. - l2_reg (float, default 0.0): - The amount of L2 regularization applied. Default to 0. - max_iterations (int, default 20): - The maximum number of training iterations or steps. Default to 20. - warm_start (bool, default False): - Determines whether to train a model with new training data, new model options, or both. Unless you explicitly override them, the initial options used to train the model are used for the warm start run. Default to False. - learning_rate (float or None, default None): - The learn rate for gradient descent when learning_rate_strategy='constant'. If unset, value 0.1 is used. If learning_rate_strategy='line_search', an error is returned. - learning_rate_strategy (str, default "line_search"): - The strategy for specifying the learning rate during training. Default to "line_search". - tol (float, default 0.01): - The minimum relative loss improvement that is necessary to continue training when EARLY_STOP is set to true. For example, a value of 0.01 specifies that each iteration must reduce the loss by 1% for training to continue. Default to 0.01. - ls_init_learning_rate (float or None, default None): - Sets the initial learning rate that learning_rate_strategy='line_search' uses. This option can only be used if line_search is specified. If unset, value 0.1 is used. - calculate_p_values (bool, default False): - Specifies whether to compute p-values and standard errors during training. Default to False. - enable_global_explain (bool, default False): - Whether to compute global explanations using explainable AI to evaluate global feature importance to the model. Default to False. + supported now. """ def fit( @@ -102,24 +46,16 @@ def fit( """Fit the model according to the given training data. Args: - X (bigframes.dataframe.DataFrame or bigframes.series.Series or pandas.core.frame.DataFrame or pandas.core.series.Series): + X (bigframes.dataframe.DataFrame or bigframes.series.Series): Series or DataFrame of shape (n_samples, n_features). Training vector, where `n_samples` is the number of samples and `n_features` is the number of features. - y (bigframes.dataframe.DataFrame or bigframes.series.Series or pandas.core.frame.DataFrame or pandas.core.series.Series): + y (bigframes.dataframe.DataFrame or bigframes.series.Series): DataFrame of shape (n_samples,). Target vector relative to X. - X_eval (bigframes.dataframe.DataFrame or bigframes.series.Series or pandas.core.frame.DataFrame or pandas.core.series.Series): - Series or DataFrame of shape (n_samples, n_features). Evaluation vector, - where `n_samples` is the number of samples and `n_features` is - the number of features. - - y_eval (bigframes.dataframe.DataFrame or bigframes.series.Series or pandas.core.frame.DataFrame or pandas.core.series.Series): - DataFrame of shape (n_samples,). Target vector relative to X_eval. - Returns: - LogisticRegression: Fitted estimator. + LogisticRegression: Fitted Estimator. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) diff --git a/third_party/bigframes_vendored/sklearn/metrics/_classification.py b/third_party/bigframes_vendored/sklearn/metrics/_classification.py index 085388b0456..a9d8038e59b 100644 --- a/third_party/bigframes_vendored/sklearn/metrics/_classification.py +++ b/third_party/bigframes_vendored/sklearn/metrics/_classification.py @@ -26,23 +26,6 @@ def accuracy_score(y_true, y_pred, normalize=True) -> float: """Accuracy classification score. - **Examples:** - - >>> import bigframes.pandas as bpd - >>> import bigframes.ml.metrics - - >>> y_true = bpd.DataFrame([0, 2, 1, 3]) - >>> y_pred = bpd.DataFrame([0, 1, 2, 3]) - >>> accuracy_score = bigframes.ml.metrics.accuracy_score(y_true, y_pred) - >>> accuracy_score - np.float64(0.5) - - If False, return the number of correctly classified samples: - - >>> accuracy_score = bigframes.ml.metrics.accuracy_score(y_true, y_pred, normalize=False) - >>> accuracy_score - np.int64(2) - Args: y_true (Series or DataFrame of shape (n_samples,)): Ground truth (correct) labels. @@ -75,29 +58,6 @@ def confusion_matrix( :math:`C_{0,0}`, false negatives is :math:`C_{1,0}`, true positives is :math:`C_{1,1}` and false positives is :math:`C_{0,1}`. - **Examples:** - - >>> import bigframes.pandas as bpd - >>> import bigframes.ml.metrics - - >>> y_true = bpd.DataFrame([2, 0, 2, 2, 0, 1]) - >>> y_pred = bpd.DataFrame([0, 0, 2, 2, 0, 2]) - >>> confusion_matrix = bigframes.ml.metrics.confusion_matrix(y_true, y_pred) - >>> confusion_matrix - 0 1 2 - 0 2 0 0 - 1 0 0 1 - 2 1 0 2 - - >>> y_true = bpd.DataFrame(["cat", "ant", "cat", "cat", "ant", "bird"]) - >>> y_pred = bpd.DataFrame(["ant", "ant", "cat", "cat", "ant", "cat"]) - >>> confusion_matrix = bigframes.ml.metrics.confusion_matrix(y_true, y_pred) - >>> confusion_matrix - ant bird cat - ant 2 0 0 - bird 0 0 1 - cat 1 0 2 - Args: y_true (Series or DataFrame of shape (n_samples,)): Ground truth (correct) target values. @@ -120,27 +80,12 @@ def recall_score( ): """Compute the recall. - The recall is the ratio ``tp / (tp + fn)``, where ``tp`` is the number of + The recall is the ratio ``tp / (tp + fn)`` where ``tp`` is the number of true positives and ``fn`` the number of false negatives. The recall is intuitively the ability of the classifier to find all the positive samples. The best value is 1 and the worst value is 0. - **Examples:** - - >>> import bigframes.pandas as bpd - >>> import bigframes.ml.metrics - - >>> y_true = bpd.DataFrame([0, 1, 2, 0, 1, 2]) - >>> y_pred = bpd.DataFrame([0, 2, 1, 0, 0, 1]) - >>> recall_score = bigframes.ml.metrics.recall_score(y_true, y_pred, average=None) - >>> recall_score - 0 1.0 - 1 0.0 - 2 0.0 - dtype: float64 - - Args: y_true (Series or DataFrame of shape (n_samples,)): Ground truth (correct) target values. @@ -150,7 +95,6 @@ def recall_score( default='binary'): This parameter is required for multiclass/multilabel targets. Possible values are 'None', 'micro', 'macro', 'samples', 'weighted', 'binary'. - Only average=None is supported. Returns: float (if average is not None) or Series of float of shape n_unique_labels,): Recall @@ -167,27 +111,13 @@ def precision_score( ): """Compute the precision. - The precision is the ratio ``tp / (tp + fp)``, where ``tp`` is the number of + The precision is the ratio ``tp / (tp + fp)`` where ``tp`` is the number of true positives and ``fp`` the number of false positives. The precision is intuitively the ability of the classifier not to label as positive a sample that is negative. The best value is 1 and the worst value is 0. - **Examples:** - - >>> import bigframes.pandas as bpd - >>> import bigframes.ml.metrics - - >>> y_true = bpd.DataFrame([0, 1, 2, 0, 1, 2]) - >>> y_pred = bpd.DataFrame([0, 2, 1, 0, 0, 1]) - >>> precision_score = bigframes.ml.metrics.precision_score(y_true, y_pred, average=None) - >>> precision_score - 0 0.666667 - 1 0.000000 - 2 0.000000 - dtype: float64 - Args: y_true: Series or DataFrame of shape (n_samples,) Ground truth (correct) target values. @@ -197,7 +127,6 @@ def precision_score( default='binary' This parameter is required for multiclass/multilabel targets. Possible values are 'None', 'micro', 'macro', 'samples', 'weighted', 'binary'. - Only None and 'binary' is supported. Returns: precision: float (if average is not None) or Series of float of shape \ @@ -224,24 +153,10 @@ def f1_score( the F1 score of each class with weighting depending on the ``average`` parameter. - **Examples:** - - >>> import bigframes.pandas as bpd - >>> import bigframes.ml.metrics - - >>> y_true = bpd.DataFrame([0, 1, 2, 0, 1, 2]) - >>> y_pred = bpd.DataFrame([0, 2, 1, 0, 0, 1]) - >>> f1_score = bigframes.ml.metrics.f1_score(y_true, y_pred, average=None) - >>> f1_score - 0 0.8 - 1 0.0 - 2 0.0 - dtype: float64 - Args: - y_true: Series or DataFrame of shape (n_samples,). + y_true: Series or DataFrame of shape (n_samples,) Ground truth (correct) target values. - y_pred: Series or DataFrame of shape (n_samples,). + y_pred: Series or DataFrame of shape (n_samples,) Estimated targets as returned by a classifier. average: {'micro', 'macro', 'samples', 'weighted', 'binary'} or None, \ default='binary' diff --git a/third_party/bigframes_vendored/sklearn/metrics/_ranking.py b/third_party/bigframes_vendored/sklearn/metrics/_ranking.py index cd5bd2cbcd5..ac919edbe33 100644 --- a/third_party/bigframes_vendored/sklearn/metrics/_ranking.py +++ b/third_party/bigframes_vendored/sklearn/metrics/_ranking.py @@ -16,8 +16,6 @@ # Michal Karbownik # License: BSD 3 clause -import numpy as np - from bigframes import constants @@ -29,28 +27,6 @@ def auc(x, y) -> float: way to summarize a precision-recall curve, see :func:`average_precision_score`. - **Examples:** - - >>> import bigframes.pandas as bpd - >>> import bigframes.ml.metrics - - >>> x = bpd.DataFrame([1, 1, 2, 2]) - >>> y = bpd.DataFrame([2, 3, 4, 5]) - >>> auc = bigframes.ml.metrics.auc(x, y) - >>> auc - np.float64(3.5) - - The input can be Series: - - >>> df = bpd.DataFrame( - ... {"x": [1, 1, 2, 2], - ... "y": [2, 3, 4, 5],} - ... ) - >>> auc = bigframes.ml.metrics.auc(df["x"], df["y"]) - >>> auc - np.float64(3.5) - - Args: x (Series or DataFrame of shape (n_samples,)): X coordinates. These must be either monotonic increasing or monotonic @@ -61,50 +37,13 @@ def auc(x, y) -> float: Returns: float: Area Under the Curve. """ - if len(x) < 2: - raise ValueError( - f"At least 2 points are needed to compute area under curve, but x.shape = {len(x)}" - ) - - if x.is_monotonic_decreasing: - d = -1 - elif x.is_monotonic_increasing: - d = 1 - else: - raise ValueError(f"x is neither increasing nor decreasing : {x}.") - - if hasattr(np, "trapezoid"): - # new in numpy 2.0 - return d * np.trapezoid(y, x) - # np.trapz has been deprecated in 2.0 - return d * np.trapz(y, x) # type: ignore + raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) def roc_auc_score(y_true, y_score) -> float: """Compute Area Under the Receiver Operating Characteristic Curve (ROC AUC) \ from prediction scores. - **Examples:** - - >>> import bigframes.pandas as bpd - >>> import bigframes.ml.metrics - - >>> y_true = bpd.DataFrame([0, 0, 1, 1, 0, 1, 0, 1, 1, 1]) - >>> y_score = bpd.DataFrame([0.1, 0.4, 0.35, 0.8, 0.65, 0.9, 0.5, 0.3, 0.6, 0.45]) - >>> roc_auc_score = bigframes.ml.metrics.roc_auc_score(y_true, y_score) - >>> roc_auc_score - np.float64(0.625) - - The input can be Series: - - >>> df = bpd.DataFrame( - ... {"y_true": [0, 0, 1, 1, 0, 1, 0, 1, 1, 1], - ... "y_score": [0.1, 0.4, 0.35, 0.8, 0.65, 0.9, 0.5, 0.3, 0.6, 0.45],} - ... ) - >>> roc_auc_score = bigframes.ml.metrics.roc_auc_score(df["y_true"], df["y_score"]) - >>> roc_auc_score - np.float64(0.625) - Args: y_true (Series or DataFrame of shape (n_samples,)): True labels or binary label indicators. The binary and multiclass cases @@ -133,38 +72,6 @@ def roc_curve( ): """Compute Receiver operating characteristic (ROC). - **Examples:** - - >>> import bigframes.pandas as bpd - >>> import bigframes.ml.metrics - - >>> y_true = bpd.DataFrame([1, 1, 2, 2]) - >>> y_score = bpd.DataFrame([0.1, 0.4, 0.35, 0.8]) - >>> fpr, tpr, thresholds = bigframes.ml.metrics.roc_curve(y_true, y_score, drop_intermediate=False) - >>> fpr - 0 0.0 - 1 0.0 - 2 0.0 - 3 0.0 - 4 0.0 - Name: fpr, dtype: Float64 - - >>> tpr - 0 0.0 - 1 0.333333 - 2 0.5 - 3 0.833333 - 4 1.0 - Name: tpr, dtype: Float64 - - >>> thresholds - 0 inf - 1 0.8 - 2 0.4 - 3 0.35 - 4 0.1 - Name: thresholds, dtype: Float64 - Args: y_true: Series or DataFrame of shape (n_samples,) True binary labels. If labels are not either {-1, 1} or {0, 1}, then diff --git a/third_party/bigframes_vendored/sklearn/metrics/_regression.py b/third_party/bigframes_vendored/sklearn/metrics/_regression.py index 85f0c1ecf94..9740c540e90 100644 --- a/third_party/bigframes_vendored/sklearn/metrics/_regression.py +++ b/third_party/bigframes_vendored/sklearn/metrics/_regression.py @@ -42,17 +42,6 @@ def r2_score(y_true, y_pred, force_finite=True) -> float: these cases are replaced with 1.0 (perfect predictions) or 0.0 (imperfect predictions) respectively. - **Examples:** - - >>> import bigframes.pandas as bpd - >>> import bigframes.ml.metrics - - >>> y_true = bpd.DataFrame([3, -0.5, 2, 7]) - >>> y_pred = bpd.DataFrame([2.5, 0.0, 2, 8]) - >>> r2_score = bigframes.ml.metrics.r2_score(y_true, y_pred) - >>> r2_score - np.float64(0.9486081370449679) - Args: y_true (Series or DataFrame of shape (n_samples,)): Ground truth (correct) target values. @@ -63,55 +52,3 @@ def r2_score(y_true, y_pred, force_finite=True) -> float: float: The :math:`R^2` score. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - -def mean_squared_error(y_true, y_pred) -> float: - """Mean squared error regression loss. - - **Examples:** - - >>> import bigframes.pandas as bpd - >>> import bigframes.ml.metrics - - >>> y_true = bpd.DataFrame([3, -0.5, 2, 7]) - >>> y_pred = bpd.DataFrame([2.5, 0.0, 2, 8]) - >>> mse = bigframes.ml.metrics.mean_squared_error(y_true, y_pred) - >>> mse - np.float64(0.375) - - Args: - y_true (Series or DataFrame of shape (n_samples,)): - Ground truth (correct) target values. - y_pred (Series or DataFrame of shape (n_samples,)): - Estimated target values. - - Returns: - float: Mean squared error. - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - -def mean_absolute_error(y_true, y_pred) -> float: - """Mean absolute error regression loss. - - **Examples:** - - >>> import bigframes.pandas as bpd - >>> import bigframes.ml.metrics - - >>> y_true = bpd.DataFrame([3, -0.5, 2, 7]) - >>> y_pred = bpd.DataFrame([2.5, 0.0, 2, 8]) - >>> mae = bigframes.ml.metrics.mean_absolute_error(y_true, y_pred) - >>> mae - np.float64(0.5) - - Args: - y_true (Series or DataFrame of shape (n_samples,)): - Ground truth (correct) target values. - y_pred (Series or DataFrame of shape (n_samples,)): - Estimated target values. - - Returns: - float: Mean absolute error. - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) diff --git a/third_party/bigframes_vendored/sklearn/metrics/pairwise.py b/third_party/bigframes_vendored/sklearn/metrics/pairwise.py deleted file mode 100644 index 37cbf23b29c..00000000000 --- a/third_party/bigframes_vendored/sklearn/metrics/pairwise.py +++ /dev/null @@ -1,56 +0,0 @@ -# Authors: Alexandre Gramfort -# Mathieu Blondel -# Robert Layton -# Andreas Mueller -# Philippe Gervais -# Lars Buitinck -# Joel Nothman -# License: BSD 3 clause - -import bigframes.pandas as bpd -from bigframes import constants - - -def paired_cosine_distances(X, Y) -> bpd.DataFrame: - """Compute the paired cosine distances between X and Y. - - Args: - X (Series or single column DataFrame of array of numeric type): - Input data. - Y (Series or single column DataFrame of array of numeric type): - Input data. X and Y are mapped by indexes, must have the same index. - - Returns: - bigframes.dataframe.DataFrame: DataFrame with columns of X, Y and cosine_distance. - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - -def paired_manhattan_distance(X, Y) -> bpd.DataFrame: - """Compute the L1 distances between the vectors in X and Y. - - Args: - X (Series or single column DataFrame of array of numeric type): - Input data. - Y (Series or single column DataFrame of array of numeric type): - Input data. X and Y are mapped by indexes, must have the same index. - - Returns: - bigframes.dataframe.DataFrame: DataFrame with columns of X, Y and manhattan_distance. - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - -def paired_euclidean_distances(X, Y) -> bpd.DataFrame: - """Compute the paired euclidean distances between X and Y. - - Args: - X (Series or single column DataFrame of array of numeric type): - Input data. - Y (Series or single column DataFrame of array of numeric type): - Input data. X and Y are mapped by indexes, must have the same index. - - Returns: - bigframes.dataframe.DataFrame: DataFrame with columns of X, Y and euclidean_distance. - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) diff --git a/third_party/bigframes_vendored/sklearn/model_selection/_split.py b/third_party/bigframes_vendored/sklearn/model_selection/_split.py deleted file mode 100644 index 2398cbe77ca..00000000000 --- a/third_party/bigframes_vendored/sklearn/model_selection/_split.py +++ /dev/null @@ -1,215 +0,0 @@ -""" -The :mod:`sklearn.model_selection._split` module includes classes and -functions to split the data based on a preset strategy. -""" - -# Author: Alexandre Gramfort -# Gael Varoquaux -# Olivier Grisel -# Raghav RV -# Leandro Hermida -# Rodion Martynov -# License: BSD 3 clause - -from abc import ABCMeta - -from bigframes import constants - - -class _BaseKFold(metaclass=ABCMeta): - """Base class for K-Fold cross-validators.""" - - def split(self, X, y=None): - """Generate indices to split data into training and test set. - - Args: - X (bigframes.dataframe.DataFrame or bigframes.series.Series): - BigFrames DataFrame or Series of shape (n_samples, n_features) - Training data, where `n_samples` is the number of samples - and `n_features` is the number of features. - - y (bigframes.dataframe.DataFrame, bigframes.series.Series or None): - BigFrames DataFrame, Series of shape (n_samples,) or None. - The target variable for supervised learning problems. Default to None. - - Yields: - X_train (bigframes.dataframe.DataFrame or bigframes.series.Series): - The training data for that split. - - X_test (bigframes.dataframe.DataFrame or bigframes.series.Series): - The testing data for that split. - - y_train (bigframes.dataframe.DataFrame, bigframes.series.Series or None): - The training label for that split. - - y_test (bigframes.dataframe.DataFrame, bigframes.series.Series or None): - The testing label for that split. - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - def get_n_splits(self): - """Returns the number of splitting iterations in the cross-validator. - - Returns: - int: the number of splitting iterations in the cross-validator. - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - -class KFold(_BaseKFold): - """K-Fold cross-validator. - - Split data in train/test sets. Split dataset into k consecutive folds. - - Each fold is then used once as a validation while the k - 1 remaining - folds form the training set. - - **Examples:** - - >>> import bigframes.pandas as bpd - >>> from bigframes.ml.model_selection import KFold - >>> X = bpd.DataFrame({"feat0": [1, 3, 5], "feat1": [2, 4, 6]}) - >>> y = bpd.DataFrame({"label": [1, 2, 3]}) - >>> kf = KFold(n_splits=3, random_state=42) - >>> for i, (X_train, X_test, y_train, y_test) in enumerate(kf.split(X, y)): - ... print(f"Fold {i}:") - ... print(f" X_train: {X_train}") - ... print(f" X_test: {X_test}") - ... print(f" y_train: {y_train}") - ... print(f" y_test: {y_test}") - ... - Fold 0: - X_train: feat0 feat1 - 1 3 4 - 2 5 6 - - [2 rows x 2 columns] - X_test: feat0 feat1 - 0 1 2 - - [1 rows x 2 columns] - y_train: label - 1 2 - 2 3 - - [2 rows x 1 columns] - y_test: label - 0 1 - - [1 rows x 1 columns] - Fold 1: - X_train: feat0 feat1 - 0 1 2 - 2 5 6 - - [2 rows x 2 columns] - X_test: feat0 feat1 - 1 3 4 - - [1 rows x 2 columns] - y_train: label - 0 1 - 2 3 - - [2 rows x 1 columns] - y_test: label - 1 2 - - [1 rows x 1 columns] - Fold 2: - X_train: feat0 feat1 - 0 1 2 - 1 3 4 - - [2 rows x 2 columns] - X_test: feat0 feat1 - 2 5 6 - - [1 rows x 2 columns] - y_train: label - 0 1 - 1 2 - - [2 rows x 1 columns] - y_test: label - 2 3 - - [1 rows x 1 columns] - - - Args: - n_splits (int): - Number of folds. Must be at least 2. Default to 5. - - random_state (Optional[int]): - A seed to use for randomly choosing the rows of the split. If not - set, a random split will be generated each time. Default to None. - """ - - -def train_test_split( - *arrays, - test_size=None, - train_size=None, - random_state=None, - stratify=None, -): - """Splits dataframes or series into random train and test subsets. - - **Examples:** - - >>> import bigframes.pandas as bpd - >>> from bigframes.ml.model_selection import train_test_split - >>> X = bpd.DataFrame({"feat0": [0, 2, 4, 6, 8], "feat1": [1, 3, 5, 7, 9]}) - >>> y = bpd.DataFrame({"label": [0, 1, 2, 3, 4]}) - >>> X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.33, random_state=42) - >>> X_train - feat0 feat1 - 0 0 1 - 1 2 3 - 4 8 9 - - [3 rows x 2 columns] - >>> y_train - label - 0 0 - 1 1 - 4 4 - - [3 rows x 1 columns] - >>> X_test - feat0 feat1 - 2 4 5 - 3 6 7 - - [2 rows x 2 columns] - >>> y_test - label - 2 2 - 3 3 - - [2 rows x 1 columns] - - Args: - *arrays (bigframes.dataframe.DataFrame or bigframes.series.Series): - A sequence of BigQuery DataFrames or Series that can be joined on - their indexes. - test_size (default None): - The proportion of the dataset to include in the test split. If - None, this will default to the complement of train_size. If both - are none, it will be set to 0.25. - train_size (default None): - The proportion of the dataset to include in the train split. If - None, this will default to the complement of test_size. - random_state (default None): - A seed to use for randomly choosing the rows of the split. If not - set, a random split will be generated each time. - stratify: (bigframes.series.Series or None, default None): - If not None, data is split in a stratified fashion, using this as the class labels. Each split has the same distribution of the class labels with the original dataset. - Default to None. - Note: By setting the stratify parameter, the memory consumption and generated SQL will be linear to the unique values in the Series. May return errors if the unique values size is too large. - - Returns: - List[Union[bigframes.dataframe.DataFrame, bigframes.series.Series]]: A list of BigQuery DataFrames or Series. - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) diff --git a/third_party/bigframes_vendored/sklearn/model_selection/_validation.py b/third_party/bigframes_vendored/sklearn/model_selection/_validation.py deleted file mode 100644 index 6f840188534..00000000000 --- a/third_party/bigframes_vendored/sklearn/model_selection/_validation.py +++ /dev/null @@ -1,62 +0,0 @@ -""" -The :mod:`sklearn.model_selection._validation` module includes classes and -functions to validate the model. -""" - -# Author: Alexandre Gramfort -# Gael Varoquaux -# Olivier Grisel -# Raghav RV -# Michal Karbownik -# License: BSD 3 clause - - -def cross_validate(estimator, X, y=None, *, cv=None): - """Evaluate metric(s) by cross-validation and also record fit/score times. - - **Examples:** - - >>> import bigframes.pandas as bpd - >>> from bigframes.ml.model_selection import cross_validate, KFold - >>> from bigframes.ml.linear_model import LinearRegression - >>> X = bpd.DataFrame({"feat0": [1, 3, 5], "feat1": [2, 4, 6]}) - >>> y = bpd.DataFrame({"label": [1, 2, 3]}) - >>> model = LinearRegression() - >>> scores = cross_validate(model, X, y, cv=3) # doctest: +SKIP - >>> for score in scores["test_score"]: # doctest: +SKIP - ... print(score["mean_squared_error"][0]) - ... - 5.218167286047954e-19 - 2.726229944928669e-18 - 1.6197635612324266e-17 - - Args: - estimator: - bigframes.ml model that implements fit(). - The object to use to fit the data. - - X (bigframes.dataframe.DataFrame or bigframes.series.Series): - The data to fit. - - y (bigframes.dataframe.DataFrame, bigframes.series.Series or None): - The target variable to try to predict in the case of supe()rvised learning. Default to None. - - cv (int, bigframes.ml.model_selection.KFold or None): - Determines the cross-validation splitting strategy. - Possible inputs for cv are: - - - None, to use the default 5-fold cross validation, - - int, to specify the number of folds in a `KFold`, - - bigframes.ml.model_selection.KFold instance. - - Returns: - Dict[str, List]: A dict of arrays containing the score/time arrays for each scorer is returned. The keys for this ``dict`` are: - - ``test_score`` - The score array for test scores on each cv split. - ``fit_time`` - The time for fitting the estimator on the train - set for each cv split. - ``score_time`` - The time for scoring the estimator on the test set for each - cv split.""" diff --git a/third_party/bigframes_vendored/sklearn/pipeline.py b/third_party/bigframes_vendored/sklearn/pipeline.py index 96eaa903be7..4b8eb25a973 100644 --- a/third_party/bigframes_vendored/sklearn/pipeline.py +++ b/third_party/bigframes_vendored/sklearn/pipeline.py @@ -11,22 +11,21 @@ from abc import ABCMeta -from bigframes_vendored.sklearn.base import BaseEstimator - from bigframes import constants +from third_party.bigframes_vendored.sklearn.base import BaseEstimator class Pipeline(BaseEstimator, metaclass=ABCMeta): """Pipeline of transforms with a final estimator. Sequentially apply a list of transforms and a final estimator. - Intermediate steps of the pipeline must be `transforms`. That is, they + Intermediate steps of the pipeline must be `transforms`, that is, they must implement `fit` and `transform` methods. The final estimator only needs to implement `fit`. The purpose of the pipeline is to assemble several steps that can be - cross-validated together while setting different parameters. This simplifies code and allows for - deploying an estimator and preprocessing together, e.g. with `Pipeline.to_gbq(...).` + cross-validated together while setting different parameters. This simplifies code, and allows deploying an estimator + and peprocessing together, e.g. with `Pipeline.to_gbq(...).` """ def fit( diff --git a/third_party/bigframes_vendored/sklearn/preprocessing/_data.py b/third_party/bigframes_vendored/sklearn/preprocessing/_data.py index b051cb24b43..5ce102d573f 100644 --- a/third_party/bigframes_vendored/sklearn/preprocessing/_data.py +++ b/third_party/bigframes_vendored/sklearn/preprocessing/_data.py @@ -7,9 +7,8 @@ # Eric Chang # License: BSD 3 clause -from bigframes_vendored.sklearn.base import BaseEstimator, TransformerMixin - from bigframes import constants +from third_party.bigframes_vendored.sklearn.base import BaseEstimator, TransformerMixin class StandardScaler(BaseEstimator, TransformerMixin): @@ -48,7 +47,7 @@ def fit(self, X, y=None): """Compute the mean and std to be used for later scaling. Args: - X (bigframes.dataframe.DataFrame or bigframes.series.Series or pandas.core.frame.DataFrame or pandas.core.series.Series): + X (bigframes.dataframe.DataFrame or bigframes.series.Series): The Dataframe or Series with training data. y (default None): @@ -63,7 +62,7 @@ def transform(self, X): """Perform standardization by centering and scaling. Args: - X (bigframes.dataframe.DataFrame or bigframes.series.Series or pandas.core.frame.DataFrame or pandas.core.series.Series): + X (bigframes.dataframe.DataFrame or bigframes.series.Series): The DataFrame or Series to be transformed. Returns: @@ -85,7 +84,7 @@ def fit(self, X, y=None): """Compute the maximum absolute value to be used for later scaling. Args: - X (bigframes.dataframe.DataFrame or bigframes.series.Series or pandas.core.frame.DataFrame or pandas.core.series.Series): + X (bigframes.dataframe.DataFrame or bigframes.series.Series): The Dataframe or Series with training data. y (default None): @@ -100,7 +99,7 @@ def transform(self, X): """Scale the data. Args: - X (bigframes.dataframe.DataFrame or bigframes.series.Series or pandas.core.frame.DataFrame or pandas.core.series.Series): + X (bigframes.dataframe.DataFrame or bigframes.series.Series): The DataFrame or Series to be transformed. Returns: @@ -121,7 +120,7 @@ def fit(self, X, y=None): """Compute the minimum and maximum to be used for later scaling. Args: - X (bigframes.dataframe.DataFrame or bigframes.series.Series or pandas.core.frame.DataFrame or pandas.core.series.Series): + X (bigframes.dataframe.DataFrame or bigframes.series.Series): The Dataframe or Series with training data. y (default None): @@ -136,7 +135,7 @@ def transform(self, X): """Scale the data. Args: - X (bigframes.dataframe.DataFrame or bigframes.series.Series or pandas.core.frame.DataFrame or pandas.core.series.Series): + X (bigframes.dataframe.DataFrame or bigframes.series.Series): The DataFrame or Series to be transformed. Returns: diff --git a/third_party/bigframes_vendored/sklearn/preprocessing/_discretization.py b/third_party/bigframes_vendored/sklearn/preprocessing/_discretization.py index 5fa84d2d159..0236558dd4b 100644 --- a/third_party/bigframes_vendored/sklearn/preprocessing/_discretization.py +++ b/third_party/bigframes_vendored/sklearn/preprocessing/_discretization.py @@ -3,9 +3,8 @@ # License: BSD -from bigframes_vendored.sklearn.base import BaseEstimator, TransformerMixin - from bigframes import constants +from third_party.bigframes_vendored.sklearn.base import BaseEstimator, TransformerMixin class KBinsDiscretizer(TransformerMixin, BaseEstimator): @@ -18,14 +17,14 @@ class KBinsDiscretizer(TransformerMixin, BaseEstimator): strategy ({'uniform', 'quantile'}, default='quantile'): Strategy used to define the widths of the bins. 'uniform': All bins in each feature have identical widths. 'quantile': All bins in each - feature have the same number of points. + feature have the same number of points. Only `uniform` is supported now. """ def fit(self, X, y=None): """Fit the estimator. Args: - X (bigframes.dataframe.DataFrame or bigframes.series.Series or pandas.core.frame.DataFrame or pandas.core.series.Series): + X (bigframes.dataframe.DataFrame or bigframes.series.Series): The Dataframe or Series with training data. y (default None): @@ -40,7 +39,7 @@ def transform(self, X): """Discretize the data. Args: - X (bigframes.dataframe.DataFrame or bigframes.series.Series or pandas.core.frame.DataFrame or pandas.core.series.Series): + X (bigframes.dataframe.DataFrame or bigframes.series.Series): The DataFrame or Series to be transformed. Returns: diff --git a/third_party/bigframes_vendored/sklearn/preprocessing/_encoder.py b/third_party/bigframes_vendored/sklearn/preprocessing/_encoder.py index 1301ef329ab..8da9a98c53f 100644 --- a/third_party/bigframes_vendored/sklearn/preprocessing/_encoder.py +++ b/third_party/bigframes_vendored/sklearn/preprocessing/_encoder.py @@ -2,9 +2,8 @@ # Joris Van den Bossche # License: BSD 3 clause -from bigframes_vendored.sklearn.base import BaseEstimator - from bigframes import constants +from third_party.bigframes_vendored.sklearn.base import BaseEstimator class OneHotEncoder(BaseEstimator): @@ -23,20 +22,15 @@ class OneHotEncoder(BaseEstimator): Given a dataset with two features, we let the encoder find the unique values per feature and transform the data to a binary one-hot encoding. - >>> from bigframes.ml.preprocessing import OneHotEncoder - >>> import bigframes.pandas as bpd + .. code-block:: - >>> enc = OneHotEncoder() - >>> X = bpd.DataFrame({"a": ["Male", "Female", "Female"], "b": ["1", "3", "2"]}) - >>> enc.fit(X) - OneHotEncoder() + from bigframes.ml.preprocessing import OneHotEncoder + import bigframes.pandas as bpd - >>> print(enc.transform(bpd.DataFrame({"a": ["Female", "Male"], "b": ["1", "4"]}))) - onehotencoded_a onehotencoded_b - 0 [{'index': 1, 'value': 1.0}] [{'index': 1, 'value': 1.0}] - 1 [{'index': 2, 'value': 1.0}] [{'index': 0, 'value': 1.0}] - - [2 rows x 2 columns] + enc = OneHotEncoder() + X = bpd.DataFrame({"a": ["Male", "Female", "Female"], "b": ["1", "3", "2"]}) + enc.fit(X) + print(enc.transform(bpd.DataFrame({"a": ["Female", "Male"], "b": ["1", "4"]}))) Args: drop (Optional[Literal["most_frequent"]], default None): @@ -57,14 +51,14 @@ class OneHotEncoder(BaseEstimator): Specifies an upper limit to the number of output features for each input feature when considering infrequent categories. If there are infrequent categories, max_categories includes the category representing the infrequent categories along with the frequent categories. - Default None. Set limit to 1,000,000. + Default None, set limit to 1,000,000. """ def fit(self, X, y=None): """Fit OneHotEncoder to X. Args: - X (bigframes.dataframe.DataFrame or bigframes.series.Series or pandas.core.frame.DataFrame or pandas.core.series.Series): + X (bigframes.dataframe.DataFrame or bigframes.series.Series): The DataFrame or Series with training data. y (default None): @@ -79,11 +73,10 @@ def transform(self, X): """Transform X using one-hot encoding. Args: - X (bigframes.dataframe.DataFrame or bigframes.series.Series or pandas.core.frame.DataFrame or pandas.core.series.Series): + X (bigframes.dataframe.DataFrame or bigframes.series.Series): The DataFrame or Series to be transformed. Returns: - bigframes.dataframe.DataFrame: The result is categorized as index: number, value: number, - where index is the position of the dict seeing the category, and value is 0 or 1. - """ + bigframes.dataframe.DataFrame: The result is categorized as index: number, value: number. + Where index is the position of the dict that seeing the category, and value is 0 or 1.""" raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) diff --git a/third_party/bigframes_vendored/sklearn/preprocessing/_label.py b/third_party/bigframes_vendored/sklearn/preprocessing/_label.py index 74b3ca347a2..83f8eb0f9c2 100644 --- a/third_party/bigframes_vendored/sklearn/preprocessing/_label.py +++ b/third_party/bigframes_vendored/sklearn/preprocessing/_label.py @@ -6,9 +6,8 @@ # Hamzeh Alsalhi # License: BSD 3 clause -from bigframes_vendored.sklearn.base import BaseEstimator - from bigframes import constants +from third_party.bigframes_vendored.sklearn.base import BaseEstimator class LabelEncoder(BaseEstimator): @@ -26,14 +25,14 @@ class LabelEncoder(BaseEstimator): Specifies an upper limit to the number of output features for each input feature when considering infrequent categories. If there are infrequent categories, max_categories includes the category representing the infrequent categories along with the frequent categories. - Default None. Set limit to 1,000,000. + Default None, set limit to 1,000,000. """ def fit(self, y): """Fit label encoder. Args: - y (bigframes.dataframe.DataFrame or bigframes.series.Series or pandas.core.frame.DataFrame or pandas.core.series.Series): + y (bigframes.dataframe.DataFrame or bigframes.series.Series): The DataFrame or Series with training data. Returns: @@ -45,7 +44,7 @@ def transform(self, y): """Transform y using label encoding. Args: - y (bigframes.dataframe.DataFrame or bigframes.series.Series or pandas.core.frame.DataFrame or pandas.core.series.Series): + y (bigframes.dataframe.DataFrame or bigframes.series.Series): The DataFrame or Series to be transformed. Returns: diff --git a/third_party/bigframes_vendored/sklearn/preprocessing/_polynomial.py b/third_party/bigframes_vendored/sklearn/preprocessing/_polynomial.py deleted file mode 100644 index aeed4dce922..00000000000 --- a/third_party/bigframes_vendored/sklearn/preprocessing/_polynomial.py +++ /dev/null @@ -1,43 +0,0 @@ -""" -This file contains preprocessing tools based on polynomials. -""" - -from bigframes_vendored.sklearn.base import BaseEstimator, TransformerMixin - -from bigframes import constants - - -class PolynomialFeatures(TransformerMixin, BaseEstimator): - """Generate polynomial and interaction features. - - Args: - degree (int): - Specifies the maximal degree of the polynomial features. Valid values [1, 4]. Default to 2. - """ - - def fit(self, X, y=None): - """Compute number of output features. - - Args: - X (bigframes.dataframe.DataFrame or bigframes.series.Series or pandas.core.frame.DataFrame or pandas.core.series.Series): - The Dataframe or Series with training data. - - y (default None): - Ignored. - - Returns: - PolynomialFeatures: Fitted transformer. - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - - def transform(self, X): - """Transform data to polynomial features. - - Args: - X (bigframes.dataframe.DataFrame or bigframes.series.Series or pandas.core.frame.DataFrame or pandas.core.series.Series): - The DataFrame or Series to be transformed. - - Returns: - bigframes.dataframe.DataFrame: Transformed result. - """ - raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) diff --git a/third_party/bigframes_vendored/sqlglot/LICENSE b/third_party/bigframes_vendored/sqlglot/LICENSE deleted file mode 100644 index 72c4dbcc54f..00000000000 --- a/third_party/bigframes_vendored/sqlglot/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2025 Toby Mao - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/third_party/bigframes_vendored/sqlglot/__init__.py b/third_party/bigframes_vendored/sqlglot/__init__.py deleted file mode 100644 index 7369b9b444b..00000000000 --- a/third_party/bigframes_vendored/sqlglot/__init__.py +++ /dev/null @@ -1,189 +0,0 @@ -# Contains code from https://github.com/tobymao/sqlglot/blob/v28.5.0/sqlglot/__init__.py - -# ruff: noqa: F401 -""" -.. include:: ../README.md - ----- -""" - -from __future__ import annotations - -import logging -import typing as t - -from bigframes_vendored.sqlglot import expressions as exp -from bigframes_vendored.sqlglot.dialects.dialect import Dialect as Dialect # noqa: F401 -from bigframes_vendored.sqlglot.dialects.dialect import ( # noqa: F401 - Dialects as Dialects, -) -from bigframes_vendored.sqlglot.diff import diff as diff # noqa: F401 -from bigframes_vendored.sqlglot.errors import ErrorLevel as ErrorLevel -from bigframes_vendored.sqlglot.errors import ParseError as ParseError -from bigframes_vendored.sqlglot.errors import TokenError as TokenError # noqa: F401 -from bigframes_vendored.sqlglot.errors import ( # noqa: F401 - UnsupportedError as UnsupportedError, -) -from bigframes_vendored.sqlglot.expressions import ( # noqa: F401 - Expression as Expression, -) -from bigframes_vendored.sqlglot.expressions import alias_ as alias # noqa: F401 -from bigframes_vendored.sqlglot.expressions import and_ as and_ # noqa: F401 -from bigframes_vendored.sqlglot.expressions import case as case # noqa: F401 -from bigframes_vendored.sqlglot.expressions import cast as cast # noqa: F401 -from bigframes_vendored.sqlglot.expressions import column as column # noqa: F401 -from bigframes_vendored.sqlglot.expressions import condition as condition # noqa: F401 -from bigframes_vendored.sqlglot.expressions import delete as delete # noqa: F401 -from bigframes_vendored.sqlglot.expressions import except_ as except_ # noqa: F401 -from bigframes_vendored.sqlglot.expressions import ( # noqa: F401 - find_tables as find_tables, -) -from bigframes_vendored.sqlglot.expressions import from_ as from_ # noqa: F401 -from bigframes_vendored.sqlglot.expressions import func as func # noqa: F401 -from bigframes_vendored.sqlglot.expressions import insert as insert # noqa: F401 -from bigframes_vendored.sqlglot.expressions import intersect as intersect # noqa: F401 -from bigframes_vendored.sqlglot.expressions import ( # noqa: F401 - maybe_parse as maybe_parse, -) -from bigframes_vendored.sqlglot.expressions import merge as merge # noqa: F401 -from bigframes_vendored.sqlglot.expressions import not_ as not_ # noqa: F401 -from bigframes_vendored.sqlglot.expressions import or_ as or_ # noqa: F401 -from bigframes_vendored.sqlglot.expressions import select as select # noqa: F401 -from bigframes_vendored.sqlglot.expressions import subquery as subquery # noqa: F401 -from bigframes_vendored.sqlglot.expressions import table_ as table # noqa: F401 -from bigframes_vendored.sqlglot.expressions import to_column as to_column # noqa: F401 -from bigframes_vendored.sqlglot.expressions import ( # noqa: F401 - to_identifier as to_identifier, -) -from bigframes_vendored.sqlglot.expressions import to_table as to_table # noqa: F401 -from bigframes_vendored.sqlglot.expressions import union as union # noqa: F401 -from bigframes_vendored.sqlglot.generator import Generator as Generator # noqa: F401 -from bigframes_vendored.sqlglot.parser import Parser as Parser # noqa: F401 -from bigframes_vendored.sqlglot.schema import ( # noqa: F401 - MappingSchema as MappingSchema, -) -from bigframes_vendored.sqlglot.schema import Schema as Schema # noqa: F401 -from bigframes_vendored.sqlglot.tokens import Token as Token # noqa: F401 -from bigframes_vendored.sqlglot.tokens import Tokenizer as Tokenizer # noqa: F401 -from bigframes_vendored.sqlglot.tokens import TokenType as TokenType # noqa: F401 - -if t.TYPE_CHECKING: - from bigframes_vendored.sqlglot._typing import E - from bigframes_vendored.sqlglot.dialects.dialect import DialectType as DialectType - -logger = logging.getLogger("sqlglot") - - -pretty = False -"""Whether to format generated SQL by default.""" - - -def tokenize( - sql: str, read: DialectType = None, dialect: DialectType = None -) -> t.List[Token]: - """ - Tokenizes the given SQL string. - - Args: - sql: the SQL code string to tokenize. - read: the SQL dialect to apply during tokenizing (eg. "spark", "hive", "presto", "mysql"). - dialect: the SQL dialect (alias for read). - - Returns: - The resulting list of tokens. - """ - return Dialect.get_or_raise(read or dialect).tokenize(sql) - - -def parse( - sql: str, read: DialectType = None, dialect: DialectType = None, **opts -) -> t.List[t.Optional[Expression]]: - """ - Parses the given SQL string into a collection of syntax trees, one per parsed SQL statement. - - Args: - sql: the SQL code string to parse. - read: the SQL dialect to apply during parsing (eg. "spark", "hive", "presto", "mysql"). - dialect: the SQL dialect (alias for read). - **opts: other `sqlglot.parser.Parser` options. - - Returns: - The resulting syntax tree collection. - """ - return Dialect.get_or_raise(read or dialect).parse(sql, **opts) - - -@t.overload -def parse_one(sql: str, *, into: t.Type[E], **opts) -> E: ... - - -@t.overload -def parse_one(sql: str, **opts) -> Expression: ... - - -def parse_one( - sql: str, - read: DialectType = None, - dialect: DialectType = None, - into: t.Optional[exp.IntoType] = None, - **opts, -) -> Expression: - """ - Parses the given SQL string and returns a syntax tree for the first parsed SQL statement. - - Args: - sql: the SQL code string to parse. - read: the SQL dialect to apply during parsing (eg. "spark", "hive", "presto", "mysql"). - dialect: the SQL dialect (alias for read) - into: the SQLGlot Expression to parse into. - **opts: other `sqlglot.parser.Parser` options. - - Returns: - The syntax tree for the first parsed statement. - """ - - dialect = Dialect.get_or_raise(read or dialect) - - if into: - result = dialect.parse_into(into, sql, **opts) - else: - result = dialect.parse(sql, **opts) - - for expression in result: - if not expression: - raise ParseError(f"No expression was parsed from '{sql}'") - return expression - else: - raise ParseError(f"No expression was parsed from '{sql}'") - - -def transpile( - sql: str, - read: DialectType = None, - write: DialectType = None, - identity: bool = True, - error_level: t.Optional[ErrorLevel] = None, - **opts, -) -> t.List[str]: - """ - Parses the given SQL string in accordance with the source dialect and returns a list of SQL strings transformed - to conform to the target dialect. Each string in the returned list represents a single transformed SQL statement. - - Args: - sql: the SQL code string to transpile. - read: the source dialect used to parse the input string (eg. "spark", "hive", "presto", "mysql"). - write: the target dialect into which the input should be transformed (eg. "spark", "hive", "presto", "mysql"). - identity: if set to `True` and if the target dialect is not specified the source dialect will be used as both: - the source and the target dialect. - error_level: the desired error level of the parser. - **opts: other `sqlglot.generator.Generator` options. - - Returns: - The list of transpiled SQL statements. - """ - write = (read if write is None else write) if identity else write - write = Dialect.get_or_raise(write) - return [ - write.generate(expression, copy=False, **opts) if expression else "" - for expression in parse(sql, read, error_level=error_level) - ] diff --git a/third_party/bigframes_vendored/sqlglot/dialects/__init__.py b/third_party/bigframes_vendored/sqlglot/dialects/__init__.py deleted file mode 100644 index 78285be445a..00000000000 --- a/third_party/bigframes_vendored/sqlglot/dialects/__init__.py +++ /dev/null @@ -1,99 +0,0 @@ -# Contains code from https://github.com/tobymao/sqlglot/blob/v28.5.0/sqlglot/dialects/__init__.py - -# ruff: noqa: F401 -""" -## Dialects - -While there is a SQL standard, most SQL engines support a variation of that standard. This makes it difficult -to write portable SQL code. SQLGlot bridges all the different variations, called "dialects", with an extensible -SQL transpilation framework. - -The base `sqlglot.dialects.dialect.Dialect` class implements a generic dialect that aims to be as universal as possible. - -Each SQL variation has its own `Dialect` subclass, extending the corresponding `Tokenizer`, `Parser` and `Generator` -classes as needed. - -### Implementing a custom Dialect - -Creating a new SQL dialect may seem complicated at first, but it is actually quite simple in SQLGlot: - -```python -from sqlglot import exp -from sqlglot.dialects.dialect import Dialect -from sqlglot.generator import Generator -from sqlglot.tokens import Tokenizer, TokenType - - -class Custom(Dialect): - class Tokenizer(Tokenizer): - QUOTES = ["'", '"'] # Strings can be delimited by either single or double quotes - IDENTIFIERS = ["`"] # Identifiers can be delimited by backticks - - # Associates certain meaningful words with tokens that capture their intent - KEYWORDS = { - **Tokenizer.KEYWORDS, - "INT64": TokenType.BIGINT, - "FLOAT64": TokenType.DOUBLE, - } - - class Generator(Generator): - # Specifies how AST nodes, i.e. subclasses of exp.Expression, should be converted into SQL - TRANSFORMS = { - exp.Array: lambda self, e: f"[{self.expressions(e)}]", - } - - # Specifies how AST nodes representing data types should be converted into SQL - TYPE_MAPPING = { - exp.DataType.Type.TINYINT: "INT64", - exp.DataType.Type.SMALLINT: "INT64", - exp.DataType.Type.INT: "INT64", - exp.DataType.Type.BIGINT: "INT64", - exp.DataType.Type.DECIMAL: "NUMERIC", - exp.DataType.Type.FLOAT: "FLOAT64", - exp.DataType.Type.DOUBLE: "FLOAT64", - exp.DataType.Type.BOOLEAN: "BOOL", - exp.DataType.Type.TEXT: "STRING", - } -``` - -The above example demonstrates how certain parts of the base `Dialect` class can be overridden to match a different -specification. Even though it is a fairly realistic starting point, we strongly encourage the reader to study existing -dialect implementations in order to understand how their various components can be modified, depending on the use-case. - ----- -""" - -import importlib -import threading - -DIALECTS = [ - "BigQuery", -] - -MODULE_BY_DIALECT = {name: name.lower() for name in DIALECTS} -DIALECT_MODULE_NAMES = MODULE_BY_DIALECT.values() - -MODULE_BY_ATTRIBUTE = { - **MODULE_BY_DIALECT, - "Dialect": "dialect", - "Dialects": "dialect", -} - -__all__ = list(MODULE_BY_ATTRIBUTE) - -# We use a reentrant lock because a dialect may depend on (i.e., import) other dialects. -# Without it, the first dialect import would never be completed, because subsequent -# imports would be blocked on the lock held by the first import. -_import_lock = threading.RLock() - - -def __getattr__(name): - module_name = MODULE_BY_ATTRIBUTE.get(name) - if module_name: - with _import_lock: - module = importlib.import_module( - f"bigframes_vendored.sqlglot.dialects.{module_name}" - ) - return getattr(module, name) - - raise AttributeError(f"module {__name__} has no attribute {name}") diff --git a/third_party/bigframes_vendored/sqlglot/dialects/bigquery.py b/third_party/bigframes_vendored/sqlglot/dialects/bigquery.py deleted file mode 100644 index 7da30231ff4..00000000000 --- a/third_party/bigframes_vendored/sqlglot/dialects/bigquery.py +++ /dev/null @@ -1,1681 +0,0 @@ -# Contains code from https://github.com/tobymao/sqlglot/blob/v28.5.0/sqlglot/dialects/bigquery.py - -from __future__ import annotations - -import logging -import re -import typing as t - -from bigframes_vendored.sqlglot import ( - exp, - generator, - jsonpath, - parser, - tokens, - transforms, -) -from bigframes_vendored.sqlglot.dialects.dialect import ( - Dialect, - NormalizationStrategy, - arg_max_or_min_no_count, - binary_from_function, - build_date_delta_with_interval, - build_formatted_time, - date_add_interval_sql, - datestrtodate_sql, - filter_array_using_unnest, - groupconcat_sql, - if_sql, - inline_array_unless_query, - max_or_greatest, - min_or_least, - no_ilike_sql, - regexp_replace_sql, - rename_func, - sha2_digest_sql, - sha256_sql, - strposition_sql, - timestrtotime_sql, - ts_or_ds_add_cast, - unit_to_var, -) -from bigframes_vendored.sqlglot.expressions import Expression as E -from bigframes_vendored.sqlglot.generator import unsupported_args -from bigframes_vendored.sqlglot.helper import seq_get, split_num_words -from bigframes_vendored.sqlglot.optimizer.annotate_types import TypeAnnotator -from bigframes_vendored.sqlglot.tokens import TokenType -from bigframes_vendored.sqlglot.typing.bigquery import EXPRESSION_METADATA - -if t.TYPE_CHECKING: - from bigframes_vendored.sqlglot._typing import Lit - -logger = logging.getLogger("sqlglot") - - -JSON_EXTRACT_TYPE = t.Union[ - exp.JSONExtract, exp.JSONExtractScalar, exp.JSONExtractArray -] - -DQUOTES_ESCAPING_JSON_FUNCTIONS = ("JSON_QUERY", "JSON_VALUE", "JSON_QUERY_ARRAY") - -MAKE_INTERVAL_KWARGS = ["year", "month", "day", "hour", "minute", "second"] - - -def _derived_table_values_to_unnest( - self: BigQuery.Generator, expression: exp.Values -) -> str: - if not expression.find_ancestor(exp.From, exp.Join): - return self.values_sql(expression) - - structs = [] - alias = expression.args.get("alias") - for tup in expression.find_all(exp.Tuple): - field_aliases = ( - alias.columns - if alias and alias.columns - else (f"_c{i}" for i in range(len(tup.expressions))) - ) - expressions = [ - exp.PropertyEQ(this=exp.to_identifier(name), expression=fld) - for name, fld in zip(field_aliases, tup.expressions) - ] - structs.append(exp.Struct(expressions=expressions)) - - # Due to `UNNEST_COLUMN_ONLY`, it is expected that the table alias be contained in the columns expression - alias_name_only = exp.TableAlias(columns=[alias.this]) if alias else None - return self.unnest_sql( - exp.Unnest(expressions=[exp.array(*structs, copy=False)], alias=alias_name_only) - ) - - -def _returnsproperty_sql( - self: BigQuery.Generator, expression: exp.ReturnsProperty -) -> str: - this = expression.this - if isinstance(this, exp.Schema): - this = f"{self.sql(this, 'this')} <{self.expressions(this)}>" - else: - this = self.sql(this) - return f"RETURNS {this}" - - -def _create_sql(self: BigQuery.Generator, expression: exp.Create) -> str: - returns = expression.find(exp.ReturnsProperty) - if expression.kind == "FUNCTION" and returns and returns.args.get("is_table"): - expression.set("kind", "TABLE FUNCTION") - - if isinstance(expression.expression, (exp.Subquery, exp.Literal)): - expression.set("expression", expression.expression.this) - - return self.create_sql(expression) - - -# https://issuetracker.google.com/issues/162294746 -# workaround for bigquery bug when grouping by an expression and then ordering -# WITH x AS (SELECT 1 y) -# SELECT y + 1 z -# FROM x -# GROUP BY x + 1 -# ORDER by z -def _alias_ordered_group(expression: exp.Expression) -> exp.Expression: - if isinstance(expression, exp.Select): - group = expression.args.get("group") - order = expression.args.get("order") - - if group and order: - aliases = { - select.this: select.args["alias"] - for select in expression.selects - if isinstance(select, exp.Alias) - } - - for grouped in group.expressions: - if grouped.is_int: - continue - alias = aliases.get(grouped) - if alias: - grouped.replace(exp.column(alias)) - - return expression - - -def _pushdown_cte_column_names(expression: exp.Expression) -> exp.Expression: - """BigQuery doesn't allow column names when defining a CTE, so we try to push them down.""" - if isinstance(expression, exp.CTE) and expression.alias_column_names: - cte_query = expression.this - - if cte_query.is_star: - logger.warning( - "Can't push down CTE column names for star queries. Run the query through" - " the optimizer or use 'qualify' to expand the star projections first." - ) - return expression - - column_names = expression.alias_column_names - expression.args["alias"].set("columns", None) - - for name, select in zip(column_names, cte_query.selects): - to_replace = select - - if isinstance(select, exp.Alias): - select = select.this - - # Inner aliases are shadowed by the CTE column names - to_replace.replace(exp.alias_(select, name)) - - return expression - - -def _build_parse_timestamp(args: t.List) -> exp.StrToTime: - this = build_formatted_time(exp.StrToTime, "bigquery")( - [seq_get(args, 1), seq_get(args, 0)] - ) - this.set("zone", seq_get(args, 2)) - return this - - -def _build_timestamp(args: t.List) -> exp.Timestamp: - timestamp = exp.Timestamp.from_arg_list(args) - timestamp.set("with_tz", True) - return timestamp - - -def _build_date(args: t.List) -> exp.Date | exp.DateFromParts: - expr_type = exp.DateFromParts if len(args) == 3 else exp.Date - return expr_type.from_arg_list(args) - - -def _build_to_hex(args: t.List) -> exp.Hex | exp.MD5: - # TO_HEX(MD5(..)) is common in BigQuery, so it's parsed into MD5 to simplify its transpilation - arg = seq_get(args, 0) - return ( - exp.MD5(this=arg.this) - if isinstance(arg, exp.MD5Digest) - else exp.LowerHex(this=arg) - ) - - -def _build_json_strip_nulls(args: t.List) -> exp.JSONStripNulls: - expression = exp.JSONStripNulls(this=seq_get(args, 0)) - - for arg in args[1:]: - if isinstance(arg, exp.Kwarg): - expression.set(arg.this.name.lower(), arg) - else: - expression.set("expression", arg) - - return expression - - -def _array_contains_sql(self: BigQuery.Generator, expression: exp.ArrayContains) -> str: - return self.sql( - exp.Exists( - this=exp.select("1") - .from_( - exp.Unnest(expressions=[expression.left]).as_("_unnest", table=["_col"]) - ) - .where(exp.column("_col").eq(expression.right)) - ) - ) - - -def _ts_or_ds_add_sql(self: BigQuery.Generator, expression: exp.TsOrDsAdd) -> str: - return date_add_interval_sql("DATE", "ADD")(self, ts_or_ds_add_cast(expression)) - - -def _ts_or_ds_diff_sql(self: BigQuery.Generator, expression: exp.TsOrDsDiff) -> str: - expression.this.replace(exp.cast(expression.this, exp.DataType.Type.TIMESTAMP)) - expression.expression.replace( - exp.cast(expression.expression, exp.DataType.Type.TIMESTAMP) - ) - unit = unit_to_var(expression) - return self.func("DATE_DIFF", expression.this, expression.expression, unit) - - -def _unix_to_time_sql(self: BigQuery.Generator, expression: exp.UnixToTime) -> str: - scale = expression.args.get("scale") - timestamp = expression.this - - if scale in (None, exp.UnixToTime.SECONDS): - return self.func("TIMESTAMP_SECONDS", timestamp) - if scale == exp.UnixToTime.MILLIS: - return self.func("TIMESTAMP_MILLIS", timestamp) - if scale == exp.UnixToTime.MICROS: - return self.func("TIMESTAMP_MICROS", timestamp) - - unix_seconds = exp.cast( - exp.Div(this=timestamp, expression=exp.func("POW", 10, scale)), - exp.DataType.Type.BIGINT, - ) - return self.func("TIMESTAMP_SECONDS", unix_seconds) - - -def _build_time(args: t.List) -> exp.Func: - if len(args) == 1: - return exp.TsOrDsToTime(this=args[0]) - if len(args) == 2: - return exp.Time.from_arg_list(args) - return exp.TimeFromParts.from_arg_list(args) - - -def _build_datetime(args: t.List) -> exp.Func: - if len(args) == 1: - return exp.TsOrDsToDatetime.from_arg_list(args) - if len(args) == 2: - return exp.Datetime.from_arg_list(args) - return exp.TimestampFromParts.from_arg_list(args) - - -def build_date_diff(args: t.List) -> exp.Expression: - expr = exp.DateDiff( - this=seq_get(args, 0), - expression=seq_get(args, 1), - unit=seq_get(args, 2), - date_part_boundary=True, - ) - - # Normalize plain WEEK to WEEK(SUNDAY) to preserve the semantic in the AST to facilitate transpilation - # This is done post exp.DateDiff construction since the TimeUnit mixin performs canonicalizations in its constructor too - unit = expr.args.get("unit") - - if isinstance(unit, exp.Var) and unit.name.upper() == "WEEK": - expr.set("unit", exp.WeekStart(this=exp.var("SUNDAY"))) - - return expr - - -def _build_regexp_extract( - expr_type: t.Type[E], default_group: t.Optional[exp.Expression] = None -) -> t.Callable[[t.List, BigQuery], E]: - def _builder(args: t.List, dialect: BigQuery) -> E: - try: - group = re.compile(args[1].name).groups == 1 - except re.error: - group = False - - # Default group is used for the transpilation of REGEXP_EXTRACT_ALL - return expr_type( - this=seq_get(args, 0), - expression=seq_get(args, 1), - position=seq_get(args, 2), - occurrence=seq_get(args, 3), - group=exp.Literal.number(1) if group else default_group, - **( - { - "null_if_pos_overflow": dialect.REGEXP_EXTRACT_POSITION_OVERFLOW_RETURNS_NULL - } - if expr_type is exp.RegexpExtract - else {} - ), - ) - - return _builder - - -def _build_extract_json_with_default_path( - expr_type: t.Type[E], -) -> t.Callable[[t.List, Dialect], E]: - def _builder(args: t.List, dialect: Dialect) -> E: - if len(args) == 1: - # The default value for the JSONPath is '$' i.e all of the data - args.append(exp.Literal.string("$")) - return parser.build_extract_json_with_path(expr_type)(args, dialect) - - return _builder - - -def _str_to_datetime_sql( - self: BigQuery.Generator, expression: exp.StrToDate | exp.StrToTime -) -> str: - this = self.sql(expression, "this") - dtype = "DATE" if isinstance(expression, exp.StrToDate) else "TIMESTAMP" - - if expression.args.get("safe"): - fmt = self.format_time( - expression, - self.dialect.INVERSE_FORMAT_MAPPING, - self.dialect.INVERSE_FORMAT_TRIE, - ) - return f"SAFE_CAST({this} AS {dtype} FORMAT {fmt})" - - fmt = self.format_time(expression) - return self.func(f"PARSE_{dtype}", fmt, this, expression.args.get("zone")) - - -@unsupported_args("ins_cost", "del_cost", "sub_cost") -def _levenshtein_sql(self: BigQuery.Generator, expression: exp.Levenshtein) -> str: - max_dist = expression.args.get("max_dist") - if max_dist: - max_dist = exp.Kwarg(this=exp.var("max_distance"), expression=max_dist) - - return self.func("EDIT_DISTANCE", expression.this, expression.expression, max_dist) - - -def _build_levenshtein(args: t.List) -> exp.Levenshtein: - max_dist = seq_get(args, 2) - return exp.Levenshtein( - this=seq_get(args, 0), - expression=seq_get(args, 1), - max_dist=max_dist.expression if max_dist else None, - ) - - -def _build_format_time( - expr_type: t.Type[exp.Expression], -) -> t.Callable[[t.List], exp.TimeToStr]: - def _builder(args: t.List) -> exp.TimeToStr: - formatted_time = build_formatted_time(exp.TimeToStr, "bigquery")( - [expr_type(this=seq_get(args, 1)), seq_get(args, 0)] - ) - formatted_time.set("zone", seq_get(args, 2)) - return formatted_time - - return _builder - - -def _build_contains_substring(args: t.List) -> exp.Contains: - # Lowercase the operands in case of transpilation, as exp.Contains - # is case-sensitive on other dialects - this = exp.Lower(this=seq_get(args, 0)) - expr = exp.Lower(this=seq_get(args, 1)) - - return exp.Contains(this=this, expression=expr, json_scope=seq_get(args, 2)) - - -def _json_extract_sql(self: BigQuery.Generator, expression: JSON_EXTRACT_TYPE) -> str: - name = (expression._meta and expression.meta.get("name")) or expression.sql_name() - upper = name.upper() - - dquote_escaping = upper in DQUOTES_ESCAPING_JSON_FUNCTIONS - - if dquote_escaping: - self._quote_json_path_key_using_brackets = False - - sql = rename_func(upper)(self, expression) - - if dquote_escaping: - self._quote_json_path_key_using_brackets = True - - return sql - - -class BigQuery(Dialect): - WEEK_OFFSET = -1 - UNNEST_COLUMN_ONLY = True - SUPPORTS_USER_DEFINED_TYPES = False - SUPPORTS_SEMI_ANTI_JOIN = False - LOG_BASE_FIRST = False - HEX_LOWERCASE = True - FORCE_EARLY_ALIAS_REF_EXPANSION = True - EXPAND_ONLY_GROUP_ALIAS_REF = True - PRESERVE_ORIGINAL_NAMES = True - HEX_STRING_IS_INTEGER_TYPE = True - BYTE_STRING_IS_BYTES_TYPE = True - UUID_IS_STRING_TYPE = True - ANNOTATE_ALL_SCOPES = True - PROJECTION_ALIASES_SHADOW_SOURCE_NAMES = True - TABLES_REFERENCEABLE_AS_COLUMNS = True - SUPPORTS_STRUCT_STAR_EXPANSION = True - EXCLUDES_PSEUDOCOLUMNS_FROM_STAR = True - QUERY_RESULTS_ARE_STRUCTS = True - JSON_EXTRACT_SCALAR_SCALAR_ONLY = True - LEAST_GREATEST_IGNORES_NULLS = False - DEFAULT_NULL_TYPE = exp.DataType.Type.BIGINT - PRIORITIZE_NON_LITERAL_TYPES = True - - # https://docs.cloud.google.com/bigquery/docs/reference/standard-sql/string_functions#initcap - INITCAP_DEFAULT_DELIMITER_CHARS = ' \t\n\r\f\v\\[\\](){}/|<>!?@"^#$&~_,.:;*%+\\-' - - # https://cloud.google.com/bigquery/docs/reference/standard-sql/lexical#case_sensitivity - NORMALIZATION_STRATEGY = NormalizationStrategy.CASE_INSENSITIVE - - # bigquery udfs are case sensitive - NORMALIZE_FUNCTIONS = False - - # https://cloud.google.com/bigquery/docs/reference/standard-sql/format-elements#format_elements_date_time - TIME_MAPPING = { - "%x": "%m/%d/%y", - "%D": "%m/%d/%y", - "%E6S": "%S.%f", - "%e": "%-d", - "%F": "%Y-%m-%d", - "%T": "%H:%M:%S", - "%c": "%a %b %e %H:%M:%S %Y", - } - - INVERSE_TIME_MAPPING = { - # Preserve %E6S instead of expanding to %T.%f - since both %E6S & %T.%f are semantically different in BigQuery - # %E6S is semantically different from %T.%f: %E6S works as a single atomic specifier for seconds with microseconds, while %T.%f expands incorrectly and fails to parse. - "%H:%M:%S.%f": "%H:%M:%E6S", - } - - FORMAT_MAPPING = { - "DD": "%d", - "MM": "%m", - "MON": "%b", - "MONTH": "%B", - "YYYY": "%Y", - "YY": "%y", - "HH": "%I", - "HH12": "%I", - "HH24": "%H", - "MI": "%M", - "SS": "%S", - "SSSSS": "%f", - "TZH": "%z", - } - - # The _PARTITIONTIME and _PARTITIONDATE pseudo-columns are not returned by a SELECT * statement - # https://cloud.google.com/bigquery/docs/querying-partitioned-tables#query_an_ingestion-time_partitioned_table - # https://cloud.google.com/bigquery/docs/querying-wildcard-tables#scanning_a_range_of_tables_using_table_suffix - # https://cloud.google.com/bigquery/docs/query-cloud-storage-data#query_the_file_name_pseudo-column - PSEUDOCOLUMNS = { - "_PARTITIONTIME", - "_PARTITIONDATE", - "_TABLE_SUFFIX", - "_FILE_NAME", - "_DBT_MAX_PARTITION", - } - - # All set operations require either a DISTINCT or ALL specifier - SET_OP_DISTINCT_BY_DEFAULT = dict.fromkeys( - (exp.Except, exp.Intersect, exp.Union), None - ) - - # https://cloud.google.com/bigquery/docs/reference/standard-sql/navigation_functions#percentile_cont - COERCES_TO = { - **TypeAnnotator.COERCES_TO, - exp.DataType.Type.BIGDECIMAL: {exp.DataType.Type.DOUBLE}, - } - COERCES_TO[exp.DataType.Type.DECIMAL] |= {exp.DataType.Type.BIGDECIMAL} - COERCES_TO[exp.DataType.Type.BIGINT] |= {exp.DataType.Type.BIGDECIMAL} - COERCES_TO[exp.DataType.Type.VARCHAR] |= { - exp.DataType.Type.DATE, - exp.DataType.Type.DATETIME, - exp.DataType.Type.TIME, - exp.DataType.Type.TIMESTAMP, - exp.DataType.Type.TIMESTAMPTZ, - } - - EXPRESSION_METADATA = EXPRESSION_METADATA.copy() - - def normalize_identifier(self, expression: E) -> E: - if ( - isinstance(expression, exp.Identifier) - and self.normalization_strategy is NormalizationStrategy.CASE_INSENSITIVE - ): - parent = expression.parent - while isinstance(parent, exp.Dot): - parent = parent.parent - - # In BigQuery, CTEs are case-insensitive, but UDF and table names are case-sensitive - # by default. The following check uses a heuristic to detect tables based on whether - # they are qualified. This should generally be correct, because tables in BigQuery - # must be qualified with at least a dataset, unless @@dataset_id is set. - case_sensitive = ( - isinstance(parent, exp.UserDefinedFunction) - or ( - isinstance(parent, exp.Table) - and parent.db - and ( - parent.meta.get("quoted_table") - or not parent.meta.get("maybe_column") - ) - ) - or expression.meta.get("is_table") - ) - if not case_sensitive: - expression.set("this", expression.this.lower()) - - return t.cast(E, expression) - - return super().normalize_identifier(expression) - - class JSONPathTokenizer(jsonpath.JSONPathTokenizer): - VAR_TOKENS = { - TokenType.DASH, - TokenType.VAR, - } - - class Tokenizer(tokens.Tokenizer): - QUOTES = ["'", '"', '"""', "'''"] - COMMENTS = ["--", "#", ("/*", "*/")] - IDENTIFIERS = ["`"] - STRING_ESCAPES = ["\\"] - - HEX_STRINGS = [("0x", ""), ("0X", "")] - - BYTE_STRINGS = [ - (prefix + q, q) - for q in t.cast(t.List[str], QUOTES) - for prefix in ("b", "B") - ] - - RAW_STRINGS = [ - (prefix + q, q) - for q in t.cast(t.List[str], QUOTES) - for prefix in ("r", "R") - ] - - NESTED_COMMENTS = False - - KEYWORDS = { - **tokens.Tokenizer.KEYWORDS, - "ANY TYPE": TokenType.VARIANT, - "BEGIN": TokenType.COMMAND, - "BEGIN TRANSACTION": TokenType.BEGIN, - "BYTEINT": TokenType.INT, - "BYTES": TokenType.BINARY, - "CURRENT_DATETIME": TokenType.CURRENT_DATETIME, - "DATETIME": TokenType.TIMESTAMP, - "DECLARE": TokenType.DECLARE, - "ELSEIF": TokenType.COMMAND, - "EXCEPTION": TokenType.COMMAND, - "EXPORT": TokenType.EXPORT, - "FLOAT64": TokenType.DOUBLE, - "FOR SYSTEM_TIME": TokenType.TIMESTAMP_SNAPSHOT, - "LOOP": TokenType.COMMAND, - "MODEL": TokenType.MODEL, - "NOT DETERMINISTIC": TokenType.VOLATILE, - "RECORD": TokenType.STRUCT, - "REPEAT": TokenType.COMMAND, - "TIMESTAMP": TokenType.TIMESTAMPTZ, - "WHILE": TokenType.COMMAND, - } - KEYWORDS.pop("DIV") - KEYWORDS.pop("VALUES") - KEYWORDS.pop("/*+") - - class Parser(parser.Parser): - PREFIXED_PIVOT_COLUMNS = True - LOG_DEFAULTS_TO_LN = True - SUPPORTS_IMPLICIT_UNNEST = True - JOINS_HAVE_EQUAL_PRECEDENCE = True - - # BigQuery does not allow ASC/DESC to be used as an identifier, allows GRANT as an identifier - ID_VAR_TOKENS = { - *parser.Parser.ID_VAR_TOKENS, - TokenType.GRANT, - } - {TokenType.ASC, TokenType.DESC} - - ALIAS_TOKENS = { - *parser.Parser.ALIAS_TOKENS, - TokenType.GRANT, - } - {TokenType.ASC, TokenType.DESC} - - TABLE_ALIAS_TOKENS = { - *parser.Parser.TABLE_ALIAS_TOKENS, - TokenType.GRANT, - } - {TokenType.ASC, TokenType.DESC} - - COMMENT_TABLE_ALIAS_TOKENS = { - *parser.Parser.COMMENT_TABLE_ALIAS_TOKENS, - TokenType.GRANT, - } - {TokenType.ASC, TokenType.DESC} - - UPDATE_ALIAS_TOKENS = { - *parser.Parser.UPDATE_ALIAS_TOKENS, - TokenType.GRANT, - } - {TokenType.ASC, TokenType.DESC} - - FUNCTIONS = { - **parser.Parser.FUNCTIONS, - "APPROX_TOP_COUNT": exp.ApproxTopK.from_arg_list, - "BIT_AND": exp.BitwiseAndAgg.from_arg_list, - "BIT_OR": exp.BitwiseOrAgg.from_arg_list, - "BIT_XOR": exp.BitwiseXorAgg.from_arg_list, - "BIT_COUNT": exp.BitwiseCount.from_arg_list, - "BOOL": exp.JSONBool.from_arg_list, - "CONTAINS_SUBSTR": _build_contains_substring, - "DATE": _build_date, - "DATE_ADD": build_date_delta_with_interval(exp.DateAdd), - "DATE_DIFF": build_date_diff, - "DATE_SUB": build_date_delta_with_interval(exp.DateSub), - "DATE_TRUNC": lambda args: exp.DateTrunc( - unit=seq_get(args, 1), - this=seq_get(args, 0), - zone=seq_get(args, 2), - ), - "DATETIME": _build_datetime, - "DATETIME_ADD": build_date_delta_with_interval(exp.DatetimeAdd), - "DATETIME_SUB": build_date_delta_with_interval(exp.DatetimeSub), - "DIV": binary_from_function(exp.IntDiv), - "EDIT_DISTANCE": _build_levenshtein, - "FORMAT_DATE": _build_format_time(exp.TsOrDsToDate), - "GENERATE_ARRAY": exp.GenerateSeries.from_arg_list, - "JSON_EXTRACT_SCALAR": _build_extract_json_with_default_path( - exp.JSONExtractScalar - ), - "JSON_EXTRACT_ARRAY": _build_extract_json_with_default_path( - exp.JSONExtractArray - ), - "JSON_EXTRACT_STRING_ARRAY": _build_extract_json_with_default_path( - exp.JSONValueArray - ), - "JSON_KEYS": exp.JSONKeysAtDepth.from_arg_list, - "JSON_QUERY": parser.build_extract_json_with_path(exp.JSONExtract), - "JSON_QUERY_ARRAY": _build_extract_json_with_default_path( - exp.JSONExtractArray - ), - "JSON_STRIP_NULLS": _build_json_strip_nulls, - "JSON_VALUE": _build_extract_json_with_default_path(exp.JSONExtractScalar), - "JSON_VALUE_ARRAY": _build_extract_json_with_default_path( - exp.JSONValueArray - ), - "LENGTH": lambda args: exp.Length(this=seq_get(args, 0), binary=True), - "MD5": exp.MD5Digest.from_arg_list, - "SHA1": exp.SHA1Digest.from_arg_list, - "NORMALIZE_AND_CASEFOLD": lambda args: exp.Normalize( - this=seq_get(args, 0), form=seq_get(args, 1), is_casefold=True - ), - "OCTET_LENGTH": exp.ByteLength.from_arg_list, - "TO_HEX": _build_to_hex, - "PARSE_DATE": lambda args: build_formatted_time(exp.StrToDate, "bigquery")( - [seq_get(args, 1), seq_get(args, 0)] - ), - "PARSE_TIME": lambda args: build_formatted_time(exp.ParseTime, "bigquery")( - [seq_get(args, 1), seq_get(args, 0)] - ), - "PARSE_TIMESTAMP": _build_parse_timestamp, - "PARSE_DATETIME": lambda args: build_formatted_time( - exp.ParseDatetime, "bigquery" - )([seq_get(args, 1), seq_get(args, 0)]), - "REGEXP_CONTAINS": exp.RegexpLike.from_arg_list, - "REGEXP_EXTRACT": _build_regexp_extract(exp.RegexpExtract), - "REGEXP_SUBSTR": _build_regexp_extract(exp.RegexpExtract), - "REGEXP_EXTRACT_ALL": _build_regexp_extract( - exp.RegexpExtractAll, default_group=exp.Literal.number(0) - ), - "SHA256": lambda args: exp.SHA2Digest( - this=seq_get(args, 0), length=exp.Literal.number(256) - ), - "SHA512": lambda args: exp.SHA2( - this=seq_get(args, 0), length=exp.Literal.number(512) - ), - "SPLIT": lambda args: exp.Split( - # https://cloud.google.com/bigquery/docs/reference/standard-sql/string_functions#split - this=seq_get(args, 0), - expression=seq_get(args, 1) or exp.Literal.string(","), - ), - "STRPOS": exp.StrPosition.from_arg_list, - "TIME": _build_time, - "TIME_ADD": build_date_delta_with_interval(exp.TimeAdd), - "TIME_SUB": build_date_delta_with_interval(exp.TimeSub), - "TIMESTAMP": _build_timestamp, - "TIMESTAMP_ADD": build_date_delta_with_interval(exp.TimestampAdd), - "TIMESTAMP_SUB": build_date_delta_with_interval(exp.TimestampSub), - "TIMESTAMP_MICROS": lambda args: exp.UnixToTime( - this=seq_get(args, 0), scale=exp.UnixToTime.MICROS - ), - "TIMESTAMP_MILLIS": lambda args: exp.UnixToTime( - this=seq_get(args, 0), scale=exp.UnixToTime.MILLIS - ), - "TIMESTAMP_SECONDS": lambda args: exp.UnixToTime(this=seq_get(args, 0)), - "TO_JSON": lambda args: exp.JSONFormat( - this=seq_get(args, 0), options=seq_get(args, 1), to_json=True - ), - "TO_JSON_STRING": exp.JSONFormat.from_arg_list, - "FORMAT_DATETIME": _build_format_time(exp.TsOrDsToDatetime), - "FORMAT_TIMESTAMP": _build_format_time(exp.TsOrDsToTimestamp), - "FORMAT_TIME": _build_format_time(exp.TsOrDsToTime), - "FROM_HEX": exp.Unhex.from_arg_list, - "WEEK": lambda args: exp.WeekStart(this=exp.var(seq_get(args, 0))), - } - # Remove SEARCH to avoid parameter routing issues - let it fall back to Anonymous function - FUNCTIONS.pop("SEARCH") - - FUNCTION_PARSERS = { - **parser.Parser.FUNCTION_PARSERS, - "ARRAY": lambda self: self.expression( - exp.Array, - expressions=[self._parse_statement()], - struct_name_inheritance=True, - ), - "JSON_ARRAY": lambda self: self.expression( - exp.JSONArray, expressions=self._parse_csv(self._parse_bitwise) - ), - "MAKE_INTERVAL": lambda self: self._parse_make_interval(), - "PREDICT": lambda self: self._parse_ml(exp.Predict), - "TRANSLATE": lambda self: self._parse_translate(), - "FEATURES_AT_TIME": lambda self: self._parse_features_at_time(), - "GENERATE_EMBEDDING": lambda self: self._parse_ml(exp.GenerateEmbedding), - "GENERATE_TEXT_EMBEDDING": lambda self: self._parse_ml( - exp.GenerateEmbedding, is_text=True - ), - "VECTOR_SEARCH": lambda self: self._parse_vector_search(), - "FORECAST": lambda self: self._parse_ml(exp.MLForecast), - } - FUNCTION_PARSERS.pop("TRIM") - - NO_PAREN_FUNCTIONS = { - **parser.Parser.NO_PAREN_FUNCTIONS, - TokenType.CURRENT_DATETIME: exp.CurrentDatetime, - } - - NESTED_TYPE_TOKENS = { - *parser.Parser.NESTED_TYPE_TOKENS, - TokenType.TABLE, - } - - PROPERTY_PARSERS = { - **parser.Parser.PROPERTY_PARSERS, - "NOT DETERMINISTIC": lambda self: self.expression( - exp.StabilityProperty, this=exp.Literal.string("VOLATILE") - ), - "OPTIONS": lambda self: self._parse_with_property(), - } - - CONSTRAINT_PARSERS = { - **parser.Parser.CONSTRAINT_PARSERS, - "OPTIONS": lambda self: exp.Properties( - expressions=self._parse_with_property() - ), - } - - RANGE_PARSERS = parser.Parser.RANGE_PARSERS.copy() - RANGE_PARSERS.pop(TokenType.OVERLAPS) - - DASHED_TABLE_PART_FOLLOW_TOKENS = { - TokenType.DOT, - TokenType.L_PAREN, - TokenType.R_PAREN, - } - - STATEMENT_PARSERS = { - **parser.Parser.STATEMENT_PARSERS, - TokenType.ELSE: lambda self: self._parse_as_command(self._prev), - TokenType.END: lambda self: self._parse_as_command(self._prev), - TokenType.FOR: lambda self: self._parse_for_in(), - TokenType.EXPORT: lambda self: self._parse_export_data(), - TokenType.DECLARE: lambda self: self._parse_declare(), - } - - BRACKET_OFFSETS = { - "OFFSET": (0, False), - "ORDINAL": (1, False), - "SAFE_OFFSET": (0, True), - "SAFE_ORDINAL": (1, True), - } - - def _parse_for_in(self) -> t.Union[exp.ForIn, exp.Command]: - index = self._index - this = self._parse_range() - self._match_text_seq("DO") - if self._match(TokenType.COMMAND): - self._retreat(index) - return self._parse_as_command(self._prev) - return self.expression( - exp.ForIn, this=this, expression=self._parse_statement() - ) - - def _parse_table_part(self, schema: bool = False) -> t.Optional[exp.Expression]: - this = super()._parse_table_part(schema=schema) or self._parse_number() - - # https://cloud.google.com/bigquery/docs/reference/standard-sql/lexical#table_names - if isinstance(this, exp.Identifier): - table_name = this.name - while self._match(TokenType.DASH, advance=False) and self._next: - start = self._curr - while self._is_connected() and not self._match_set( - self.DASHED_TABLE_PART_FOLLOW_TOKENS, advance=False - ): - self._advance() - - if start == self._curr: - break - - table_name += self._find_sql(start, self._prev) - - this = exp.Identifier( - this=table_name, quoted=this.args.get("quoted") - ).update_positions(this) - elif isinstance(this, exp.Literal): - table_name = this.name - - if self._is_connected() and self._parse_var(any_token=True): - table_name += self._prev.text - - this = exp.Identifier(this=table_name, quoted=True).update_positions( - this - ) - - return this - - def _parse_table_parts( - self, - schema: bool = False, - is_db_reference: bool = False, - wildcard: bool = False, - ) -> exp.Table: - table = super()._parse_table_parts( - schema=schema, is_db_reference=is_db_reference, wildcard=True - ) - - # proj-1.db.tbl -- `1.` is tokenized as a float so we need to unravel it here - if not table.catalog: - if table.db: - previous_db = table.args["db"] - parts = table.db.split(".") - if len(parts) == 2 and not table.args["db"].quoted: - table.set( - "catalog", - exp.Identifier(this=parts[0]).update_positions(previous_db), - ) - table.set( - "db", - exp.Identifier(this=parts[1]).update_positions(previous_db), - ) - else: - previous_this = table.this - parts = table.name.split(".") - if len(parts) == 2 and not table.this.quoted: - table.set( - "db", - exp.Identifier(this=parts[0]).update_positions( - previous_this - ), - ) - table.set( - "this", - exp.Identifier(this=parts[1]).update_positions( - previous_this - ), - ) - - if isinstance(table.this, exp.Identifier) and any( - "." in p.name for p in table.parts - ): - alias = table.this - catalog, db, this, *rest = ( - exp.to_identifier(p, quoted=True) - for p in split_num_words( - ".".join(p.name for p in table.parts), ".", 3 - ) - ) - - for part in (catalog, db, this): - if part: - part.update_positions(table.this) - - if rest and this: - this = exp.Dot.build([this, *rest]) # type: ignore - - table = exp.Table( - this=this, db=db, catalog=catalog, pivots=table.args.get("pivots") - ) - table.meta["quoted_table"] = True - else: - alias = None - - # The `INFORMATION_SCHEMA` views in BigQuery need to be qualified by a region or - # dataset, so if the project identifier is omitted we need to fix the ast so that - # the `INFORMATION_SCHEMA.X` bit is represented as a single (quoted) Identifier. - # Otherwise, we wouldn't correctly qualify a `Table` node that references these - # views, because it would seem like the "catalog" part is set, when it'd actually - # be the region/dataset. Merging the two identifiers into a single one is done to - # avoid producing a 4-part Table reference, which would cause issues in the schema - # module, when there are 3-part table names mixed with information schema views. - # - # See: https://cloud.google.com/bigquery/docs/information-schema-intro#syntax - table_parts = table.parts - if ( - len(table_parts) > 1 - and table_parts[-2].name.upper() == "INFORMATION_SCHEMA" - ): - # We need to alias the table here to avoid breaking existing qualified columns. - # This is expected to be safe, because if there's an actual alias coming up in - # the token stream, it will overwrite this one. If there isn't one, we are only - # exposing the name that can be used to reference the view explicitly (a no-op). - exp.alias_( - table, - t.cast(exp.Identifier, alias or table_parts[-1]), - table=True, - copy=False, - ) - - info_schema_view = f"{table_parts[-2].name}.{table_parts[-1].name}" - new_this = exp.Identifier( - this=info_schema_view, quoted=True - ).update_positions( - line=table_parts[-2].meta.get("line"), - col=table_parts[-1].meta.get("col"), - start=table_parts[-2].meta.get("start"), - end=table_parts[-1].meta.get("end"), - ) - table.set("this", new_this) - table.set("db", seq_get(table_parts, -3)) - table.set("catalog", seq_get(table_parts, -4)) - - return table - - def _parse_column(self) -> t.Optional[exp.Expression]: - column = super()._parse_column() - if isinstance(column, exp.Column): - parts = column.parts - if any("." in p.name for p in parts): - catalog, db, table, this, *rest = ( - exp.to_identifier(p, quoted=True) - for p in split_num_words( - ".".join(p.name for p in parts), ".", 4 - ) - ) - - if rest and this: - this = exp.Dot.build([this, *rest]) # type: ignore - - column = exp.Column(this=this, table=table, db=db, catalog=catalog) - column.meta["quoted_column"] = True - - return column - - @t.overload - def _parse_json_object(self, agg: Lit[False]) -> exp.JSONObject: ... - - @t.overload - def _parse_json_object(self, agg: Lit[True]) -> exp.JSONObjectAgg: ... - - def _parse_json_object(self, agg=False): - json_object = super()._parse_json_object() - array_kv_pair = seq_get(json_object.expressions, 0) - - # Converts BQ's "signature 2" of JSON_OBJECT into SQLGlot's canonical representation - # https://cloud.google.com/bigquery/docs/reference/standard-sql/json_functions#json_object_signature2 - if ( - array_kv_pair - and isinstance(array_kv_pair.this, exp.Array) - and isinstance(array_kv_pair.expression, exp.Array) - ): - keys = array_kv_pair.this.expressions - values = array_kv_pair.expression.expressions - - json_object.set( - "expressions", - [ - exp.JSONKeyValue(this=k, expression=v) - for k, v in zip(keys, values) - ], - ) - - return json_object - - def _parse_bracket( - self, this: t.Optional[exp.Expression] = None - ) -> t.Optional[exp.Expression]: - bracket = super()._parse_bracket(this) - - if isinstance(bracket, exp.Array): - bracket.set("struct_name_inheritance", True) - - if this is bracket: - return bracket - - if isinstance(bracket, exp.Bracket): - for expression in bracket.expressions: - name = expression.name.upper() - - if name not in self.BRACKET_OFFSETS: - break - - offset, safe = self.BRACKET_OFFSETS[name] - bracket.set("offset", offset) - bracket.set("safe", safe) - expression.replace(expression.expressions[0]) - - return bracket - - def _parse_unnest(self, with_alias: bool = True) -> t.Optional[exp.Unnest]: - unnest = super()._parse_unnest(with_alias=with_alias) - - if not unnest: - return None - - unnest_expr = seq_get(unnest.expressions, 0) - if unnest_expr: - from bigframes_vendored.sqlglot.optimizer.annotate_types import ( - annotate_types, - ) - - unnest_expr = annotate_types(unnest_expr, dialect=self.dialect) - - # Unnesting a nested array (i.e array of structs) explodes the top-level struct fields, - # in contrast to other dialects such as DuckDB which flattens only the array by default - if unnest_expr.is_type(exp.DataType.Type.ARRAY) and any( - array_elem.is_type(exp.DataType.Type.STRUCT) - for array_elem in unnest_expr._type.expressions - ): - unnest.set("explode_array", True) - - return unnest - - def _parse_make_interval(self) -> exp.MakeInterval: - expr = exp.MakeInterval() - - for arg_key in MAKE_INTERVAL_KWARGS: - value = self._parse_lambda() - - if not value: - break - - # Non-named arguments are filled sequentially, (optionally) followed by named arguments - # that can appear in any order e.g MAKE_INTERVAL(1, minute => 5, day => 2) - if isinstance(value, exp.Kwarg): - arg_key = value.this.name - - expr.set(arg_key, value) - - self._match(TokenType.COMMA) - - return expr - - def _parse_ml(self, expr_type: t.Type[E], **kwargs) -> E: - self._match_text_seq("MODEL") - this = self._parse_table() - - self._match(TokenType.COMMA) - self._match_text_seq("TABLE") - - # Certain functions like ML.FORECAST require a STRUCT argument but not a TABLE/SELECT one - expression = ( - self._parse_table() - if not self._match(TokenType.STRUCT, advance=False) - else None - ) - - self._match(TokenType.COMMA) - - return self.expression( - expr_type, - this=this, - expression=expression, - params_struct=self._parse_bitwise(), - **kwargs, - ) - - def _parse_translate(self) -> exp.Translate | exp.MLTranslate: - # Check if this is ML.TRANSLATE by looking at previous tokens - token = seq_get(self._tokens, self._index - 4) - if token and token.text.upper() == "ML": - return self._parse_ml(exp.MLTranslate) - - return exp.Translate.from_arg_list(self._parse_function_args()) - - def _parse_features_at_time(self) -> exp.FeaturesAtTime: - self._match(TokenType.TABLE) - this = self._parse_table() - - expr = self.expression(exp.FeaturesAtTime, this=this) - - while self._match(TokenType.COMMA): - arg = self._parse_lambda() - - # Get the LHS of the Kwarg and set the arg to that value, e.g - # "num_rows => 1" sets the expr's `num_rows` arg - if arg: - expr.set(arg.this.name, arg) - - return expr - - def _parse_vector_search(self) -> exp.VectorSearch: - self._match(TokenType.TABLE) - base_table = self._parse_table() - - self._match(TokenType.COMMA) - - column_to_search = self._parse_bitwise() - self._match(TokenType.COMMA) - - self._match(TokenType.TABLE) - query_table = self._parse_table() - - expr = self.expression( - exp.VectorSearch, - this=base_table, - column_to_search=column_to_search, - query_table=query_table, - ) - - while self._match(TokenType.COMMA): - # query_column_to_search can be named argument or positional - if self._match(TokenType.STRING, advance=False): - query_column = self._parse_string() - expr.set("query_column_to_search", query_column) - else: - arg = self._parse_lambda() - if arg: - expr.set(arg.this.name, arg) - - return expr - - def _parse_export_data(self) -> exp.Export: - self._match_text_seq("DATA") - - return self.expression( - exp.Export, - connection=self._match_text_seq("WITH", "CONNECTION") - and self._parse_table_parts(), - options=self._parse_properties(), - this=self._match_text_seq("AS") and self._parse_select(), - ) - - def _parse_column_ops( - self, this: t.Optional[exp.Expression] - ) -> t.Optional[exp.Expression]: - this = super()._parse_column_ops(this) - - if isinstance(this, exp.Dot): - prefix_name = this.this.name.upper() - func_name = this.name.upper() - if prefix_name == "NET": - if func_name == "HOST": - this = self.expression( - exp.NetHost, this=seq_get(this.expression.expressions, 0) - ) - elif prefix_name == "SAFE": - if func_name == "TIMESTAMP": - this = _build_timestamp(this.expression.expressions) - this.set("safe", True) - - return this - - class Generator(generator.Generator): - INTERVAL_ALLOWS_PLURAL_FORM = False - JOIN_HINTS = False - QUERY_HINTS = False - TABLE_HINTS = False - LIMIT_FETCH = "LIMIT" - RENAME_TABLE_WITH_DB = False - NVL2_SUPPORTED = False - UNNEST_WITH_ORDINALITY = False - COLLATE_IS_FUNC = True - LIMIT_ONLY_LITERALS = True - SUPPORTS_TABLE_ALIAS_COLUMNS = False - UNPIVOT_ALIASES_ARE_IDENTIFIERS = False - JSON_KEY_VALUE_PAIR_SEP = "," - NULL_ORDERING_SUPPORTED = False - IGNORE_NULLS_IN_FUNC = True - JSON_PATH_SINGLE_QUOTE_ESCAPE = True - CAN_IMPLEMENT_ARRAY_ANY = True - SUPPORTS_TO_NUMBER = False - NAMED_PLACEHOLDER_TOKEN = "@" - HEX_FUNC = "TO_HEX" - WITH_PROPERTIES_PREFIX = "OPTIONS" - SUPPORTS_EXPLODING_PROJECTIONS = False - EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE = False - SUPPORTS_UNIX_SECONDS = True - - SAFE_JSON_PATH_KEY_RE = re.compile(r"^[_\-a-zA-Z][\-\w]*$") - - TS_OR_DS_TYPES = ( - exp.TsOrDsToDatetime, - exp.TsOrDsToTimestamp, - exp.TsOrDsToTime, - exp.TsOrDsToDate, - ) - - TRANSFORMS = { - **generator.Generator.TRANSFORMS, - exp.ApproxTopK: rename_func("APPROX_TOP_COUNT"), - exp.ApproxDistinct: rename_func("APPROX_COUNT_DISTINCT"), - exp.ArgMax: arg_max_or_min_no_count("MAX_BY"), - exp.ArgMin: arg_max_or_min_no_count("MIN_BY"), - exp.Array: inline_array_unless_query, - exp.ArrayContains: _array_contains_sql, - exp.ArrayFilter: filter_array_using_unnest, - exp.ArrayRemove: filter_array_using_unnest, - exp.BitwiseAndAgg: rename_func("BIT_AND"), - exp.BitwiseOrAgg: rename_func("BIT_OR"), - exp.BitwiseXorAgg: rename_func("BIT_XOR"), - exp.BitwiseCount: rename_func("BIT_COUNT"), - exp.ByteLength: rename_func("BYTE_LENGTH"), - exp.Cast: transforms.preprocess( - [transforms.remove_precision_parameterized_types] - ), - exp.CollateProperty: lambda self, e: ( - f"DEFAULT COLLATE {self.sql(e, 'this')}" - if e.args.get("default") - else f"COLLATE {self.sql(e, 'this')}" - ), - exp.Commit: lambda *_: "COMMIT TRANSACTION", - exp.CountIf: rename_func("COUNTIF"), - exp.Create: _create_sql, - exp.CTE: transforms.preprocess([_pushdown_cte_column_names]), - exp.DateAdd: date_add_interval_sql("DATE", "ADD"), - exp.DateDiff: lambda self, e: self.func( - "DATE_DIFF", e.this, e.expression, unit_to_var(e) - ), - exp.DateFromParts: rename_func("DATE"), - exp.DateStrToDate: datestrtodate_sql, - exp.DateSub: date_add_interval_sql("DATE", "SUB"), - exp.DatetimeAdd: date_add_interval_sql("DATETIME", "ADD"), - exp.DatetimeSub: date_add_interval_sql("DATETIME", "SUB"), - exp.DateFromUnixDate: rename_func("DATE_FROM_UNIX_DATE"), - exp.FromTimeZone: lambda self, e: self.func( - "DATETIME", self.func("TIMESTAMP", e.this, e.args.get("zone")), "'UTC'" - ), - exp.GenerateSeries: rename_func("GENERATE_ARRAY"), - exp.GroupConcat: lambda self, e: groupconcat_sql( - self, e, func_name="STRING_AGG", within_group=False, sep=None - ), - exp.Hex: lambda self, e: self.func( - "UPPER", self.func("TO_HEX", self.sql(e, "this")) - ), - exp.HexString: lambda self, e: self.hexstring_sql( - e, binary_function_repr="FROM_HEX" - ), - exp.If: if_sql(false_value="NULL"), - exp.ILike: no_ilike_sql, - exp.IntDiv: rename_func("DIV"), - exp.Int64: rename_func("INT64"), - exp.JSONBool: rename_func("BOOL"), - exp.JSONExtract: _json_extract_sql, - exp.JSONExtractArray: _json_extract_sql, - exp.JSONExtractScalar: _json_extract_sql, - exp.JSONFormat: lambda self, e: self.func( - "TO_JSON" if e.args.get("to_json") else "TO_JSON_STRING", - e.this, - e.args.get("options"), - ), - exp.JSONKeysAtDepth: rename_func("JSON_KEYS"), - exp.JSONValueArray: rename_func("JSON_VALUE_ARRAY"), - exp.Levenshtein: _levenshtein_sql, - exp.Max: max_or_greatest, - exp.MD5: lambda self, e: self.func("TO_HEX", self.func("MD5", e.this)), - exp.MD5Digest: rename_func("MD5"), - exp.Min: min_or_least, - exp.Normalize: lambda self, e: self.func( - "NORMALIZE_AND_CASEFOLD" if e.args.get("is_casefold") else "NORMALIZE", - e.this, - e.args.get("form"), - ), - exp.PartitionedByProperty: lambda self, - e: f"PARTITION BY {self.sql(e, 'this')}", - exp.RegexpExtract: lambda self, e: self.func( - "REGEXP_EXTRACT", - e.this, - e.expression, - e.args.get("position"), - e.args.get("occurrence"), - ), - exp.RegexpExtractAll: lambda self, e: self.func( - "REGEXP_EXTRACT_ALL", e.this, e.expression - ), - exp.RegexpReplace: regexp_replace_sql, - exp.RegexpLike: rename_func("REGEXP_CONTAINS"), - exp.ReturnsProperty: _returnsproperty_sql, - exp.Rollback: lambda *_: "ROLLBACK TRANSACTION", - exp.ParseTime: lambda self, e: self.func( - "PARSE_TIME", self.format_time(e), e.this - ), - exp.ParseDatetime: lambda self, e: self.func( - "PARSE_DATETIME", self.format_time(e), e.this - ), - exp.Select: transforms.preprocess( - [ - transforms.explode_projection_to_unnest(), - transforms.unqualify_unnest, - transforms.eliminate_distinct_on, - _alias_ordered_group, - transforms.eliminate_semi_and_anti_joins, - ] - ), - exp.SHA: rename_func("SHA1"), - exp.SHA2: sha256_sql, - exp.SHA1Digest: rename_func("SHA1"), - exp.SHA2Digest: sha2_digest_sql, - exp.StabilityProperty: lambda self, e: ( - "DETERMINISTIC" if e.name == "IMMUTABLE" else "NOT DETERMINISTIC" - ), - exp.String: rename_func("STRING"), - exp.StrPosition: lambda self, e: ( - strposition_sql( - self, - e, - func_name="INSTR", - supports_position=True, - supports_occurrence=True, - ) - ), - exp.StrToDate: _str_to_datetime_sql, - exp.StrToTime: _str_to_datetime_sql, - exp.SessionUser: lambda *_: "SESSION_USER()", - exp.TimeAdd: date_add_interval_sql("TIME", "ADD"), - exp.TimeFromParts: rename_func("TIME"), - exp.TimestampFromParts: rename_func("DATETIME"), - exp.TimeSub: date_add_interval_sql("TIME", "SUB"), - exp.TimestampAdd: date_add_interval_sql("TIMESTAMP", "ADD"), - exp.TimestampDiff: rename_func("TIMESTAMP_DIFF"), - exp.TimestampSub: date_add_interval_sql("TIMESTAMP", "SUB"), - exp.TimeStrToTime: timestrtotime_sql, - exp.Transaction: lambda *_: "BEGIN TRANSACTION", - exp.TsOrDsAdd: _ts_or_ds_add_sql, - exp.TsOrDsDiff: _ts_or_ds_diff_sql, - exp.TsOrDsToTime: rename_func("TIME"), - exp.TsOrDsToDatetime: rename_func("DATETIME"), - exp.TsOrDsToTimestamp: rename_func("TIMESTAMP"), - exp.Unhex: rename_func("FROM_HEX"), - exp.UnixDate: rename_func("UNIX_DATE"), - exp.UnixToTime: _unix_to_time_sql, - exp.Uuid: lambda *_: "GENERATE_UUID()", - exp.Values: _derived_table_values_to_unnest, - exp.VariancePop: rename_func("VAR_POP"), - exp.SafeDivide: rename_func("SAFE_DIVIDE"), - } - - SUPPORTED_JSON_PATH_PARTS = { - exp.JSONPathKey, - exp.JSONPathRoot, - exp.JSONPathSubscript, - } - - TYPE_MAPPING = { - **generator.Generator.TYPE_MAPPING, - exp.DataType.Type.BIGDECIMAL: "BIGNUMERIC", - exp.DataType.Type.BIGINT: "INT64", - exp.DataType.Type.BINARY: "BYTES", - exp.DataType.Type.BLOB: "BYTES", - exp.DataType.Type.BOOLEAN: "BOOL", - exp.DataType.Type.CHAR: "STRING", - exp.DataType.Type.DECIMAL: "NUMERIC", - exp.DataType.Type.DOUBLE: "FLOAT64", - exp.DataType.Type.FLOAT: "FLOAT64", - exp.DataType.Type.INT: "INT64", - exp.DataType.Type.NCHAR: "STRING", - exp.DataType.Type.NVARCHAR: "STRING", - exp.DataType.Type.SMALLINT: "INT64", - exp.DataType.Type.TEXT: "STRING", - exp.DataType.Type.TIMESTAMP: "DATETIME", - exp.DataType.Type.TIMESTAMPNTZ: "DATETIME", - exp.DataType.Type.TIMESTAMPTZ: "TIMESTAMP", - exp.DataType.Type.TIMESTAMPLTZ: "TIMESTAMP", - exp.DataType.Type.TINYINT: "INT64", - exp.DataType.Type.ROWVERSION: "BYTES", - exp.DataType.Type.UUID: "STRING", - exp.DataType.Type.VARBINARY: "BYTES", - exp.DataType.Type.VARCHAR: "STRING", - exp.DataType.Type.VARIANT: "ANY TYPE", - } - - PROPERTIES_LOCATION = { - **generator.Generator.PROPERTIES_LOCATION, - exp.PartitionedByProperty: exp.Properties.Location.POST_SCHEMA, - exp.VolatileProperty: exp.Properties.Location.UNSUPPORTED, - } - - # WINDOW comes after QUALIFY - # https://cloud.google.com/bigquery/docs/reference/standard-sql/query-syntax#window_clause - AFTER_HAVING_MODIFIER_TRANSFORMS = { - "qualify": generator.Generator.AFTER_HAVING_MODIFIER_TRANSFORMS["qualify"], - "windows": generator.Generator.AFTER_HAVING_MODIFIER_TRANSFORMS["windows"], - } - - # from: https://cloud.google.com/bigquery/docs/reference/standard-sql/lexical#reserved_keywords - RESERVED_KEYWORDS = { - "all", - "and", - "any", - "array", - "as", - "asc", - "assert_rows_modified", - "at", - "between", - "by", - "case", - "cast", - "collate", - "contains", - "create", - "cross", - "cube", - "current", - "default", - "define", - "desc", - "distinct", - "else", - "end", - "enum", - "escape", - "except", - "exclude", - "exists", - "extract", - "false", - "fetch", - "following", - "for", - "from", - "full", - "group", - "grouping", - "groups", - "hash", - "having", - "if", - "ignore", - "in", - "inner", - "intersect", - "interval", - "into", - "is", - "join", - "lateral", - "left", - "like", - "limit", - "lookup", - "merge", - "natural", - "new", - "no", - "not", - "null", - "nulls", - "of", - "on", - "or", - "order", - "outer", - "over", - "partition", - "preceding", - "proto", - "qualify", - "range", - "recursive", - "respect", - "right", - "rollup", - "rows", - "select", - "set", - "some", - "struct", - "tablesample", - "then", - "to", - "treat", - "true", - "unbounded", - "union", - "unnest", - "using", - "when", - "where", - "window", - "with", - "within", - } - - def datetrunc_sql(self, expression: exp.DateTrunc) -> str: - unit = expression.unit - unit_sql = unit.name if unit.is_string else self.sql(unit) - return self.func( - "DATE_TRUNC", expression.this, unit_sql, expression.args.get("zone") - ) - - def mod_sql(self, expression: exp.Mod) -> str: - this = expression.this - expr = expression.expression - return self.func( - "MOD", - this.unnest() if isinstance(this, exp.Paren) else this, - expr.unnest() if isinstance(expr, exp.Paren) else expr, - ) - - def column_parts(self, expression: exp.Column) -> str: - if expression.meta.get("quoted_column"): - # If a column reference is of the form `dataset.table`.name, we need - # to preserve the quoted table path, otherwise the reference breaks - table_parts = ".".join(p.name for p in expression.parts[:-1]) - table_path = self.sql(exp.Identifier(this=table_parts, quoted=True)) - return f"{table_path}.{self.sql(expression, 'this')}" - - return super().column_parts(expression) - - def table_parts(self, expression: exp.Table) -> str: - # Depending on the context, `x.y` may not resolve to the same data source as `x`.`y`, so - # we need to make sure the correct quoting is used in each case. - # - # For example, if there is a CTE x that clashes with a schema name, then the former will - # return the table y in that schema, whereas the latter will return the CTE's y column: - # - # - WITH x AS (SELECT [1, 2] AS y) SELECT * FROM x, `x.y` -> cross join - # - WITH x AS (SELECT [1, 2] AS y) SELECT * FROM x, `x`.`y` -> implicit unnest - if expression.meta.get("quoted_table"): - table_parts = ".".join(p.name for p in expression.parts) - return self.sql(exp.Identifier(this=table_parts, quoted=True)) - - return super().table_parts(expression) - - def timetostr_sql(self, expression: exp.TimeToStr) -> str: - this = expression.this - if isinstance(this, exp.TsOrDsToDatetime): - func_name = "FORMAT_DATETIME" - elif isinstance(this, exp.TsOrDsToTimestamp): - func_name = "FORMAT_TIMESTAMP" - elif isinstance(this, exp.TsOrDsToTime): - func_name = "FORMAT_TIME" - else: - func_name = "FORMAT_DATE" - - time_expr = this if isinstance(this, self.TS_OR_DS_TYPES) else expression - return self.func( - func_name, - self.format_time(expression), - time_expr.this, - expression.args.get("zone"), - ) - - def eq_sql(self, expression: exp.EQ) -> str: - # Operands of = cannot be NULL in BigQuery - if isinstance(expression.left, exp.Null) or isinstance( - expression.right, exp.Null - ): - if not isinstance(expression.parent, exp.Update): - return "NULL" - - return self.binary(expression, "=") - - def attimezone_sql(self, expression: exp.AtTimeZone) -> str: - parent = expression.parent - - # BigQuery allows CAST(.. AS {STRING|TIMESTAMP} [FORMAT [AT TIME ZONE ]]). - # Only the TIMESTAMP one should use the below conversion, when AT TIME ZONE is included. - if not isinstance(parent, exp.Cast) or not parent.to.is_type("text"): - return self.func( - "TIMESTAMP", - self.func("DATETIME", expression.this, expression.args.get("zone")), - ) - - return super().attimezone_sql(expression) - - def trycast_sql(self, expression: exp.TryCast) -> str: - return self.cast_sql(expression, safe_prefix="SAFE_") - - def bracket_sql(self, expression: exp.Bracket) -> str: - this = expression.this - expressions = expression.expressions - - if ( - len(expressions) == 1 - and this - and this.is_type(exp.DataType.Type.STRUCT) - ): - arg = expressions[0] - if arg.type is None: - from bigframes_vendored.sqlglot.optimizer.annotate_types import ( - annotate_types, - ) - - arg = annotate_types(arg, dialect=self.dialect) - - if arg.type and arg.type.this in exp.DataType.TEXT_TYPES: - # BQ doesn't support bracket syntax with string values for structs - return f"{self.sql(this)}.{arg.name}" - - expressions_sql = self.expressions(expression, flat=True) - offset = expression.args.get("offset") - - if offset == 0: - expressions_sql = f"OFFSET({expressions_sql})" - elif offset == 1: - expressions_sql = f"ORDINAL({expressions_sql})" - elif offset is not None: - self.unsupported(f"Unsupported array offset: {offset}") - - if expression.args.get("safe"): - expressions_sql = f"SAFE_{expressions_sql}" - - return f"{self.sql(this)}[{expressions_sql}]" - - def in_unnest_op(self, expression: exp.Unnest) -> str: - return self.sql(expression) - - def version_sql(self, expression: exp.Version) -> str: - if expression.name == "TIMESTAMP": - expression.set("this", "SYSTEM_TIME") - return super().version_sql(expression) - - def contains_sql(self, expression: exp.Contains) -> str: - this = expression.this - expr = expression.expression - - if isinstance(this, exp.Lower) and isinstance(expr, exp.Lower): - this = this.this - expr = expr.this - - return self.func( - "CONTAINS_SUBSTR", this, expr, expression.args.get("json_scope") - ) - - def cast_sql( - self, expression: exp.Cast, safe_prefix: t.Optional[str] = None - ) -> str: - this = expression.this - - # This ensures that inline type-annotated ARRAY literals like ARRAY[1, 2, 3] - # are roundtripped unaffected. The inner check excludes ARRAY(SELECT ...) expressions, - # because they aren't literals and so the above syntax is invalid BigQuery. - if isinstance(this, exp.Array): - elem = seq_get(this.expressions, 0) - if not (elem and elem.find(exp.Query)): - return f"{self.sql(expression, 'to')}{self.sql(this)}" - - return super().cast_sql(expression, safe_prefix=safe_prefix) - - def declareitem_sql(self, expression: exp.DeclareItem) -> str: - variables = self.expressions(expression, "this") - default = self.sql(expression, "default") - default = f" DEFAULT {default}" if default else "" - kind = self.sql(expression, "kind") - kind = f" {kind}" if kind else "" - - return f"{variables}{kind}{default}" - - def timestamp_sql(self, expression: exp.Timestamp) -> str: - prefix = "SAFE." if expression.args.get("safe") else "" - return self.func( - f"{prefix}TIMESTAMP", expression.this, expression.args.get("zone") - ) diff --git a/third_party/bigframes_vendored/sqlglot/dialects/dialect.py b/third_party/bigframes_vendored/sqlglot/dialects/dialect.py deleted file mode 100644 index 8e26b777abd..00000000000 --- a/third_party/bigframes_vendored/sqlglot/dialects/dialect.py +++ /dev/null @@ -1,2368 +0,0 @@ -# Contains code from https://github.com/tobymao/sqlglot/blob/v28.5.0/sqlglot/dialects/dialect.py - -from __future__ import annotations - -import importlib -import logging -import sys -import typing as t -from enum import Enum, auto -from functools import reduce - -from bigframes_vendored.sqlglot import exp -from bigframes_vendored.sqlglot.dialects import DIALECT_MODULE_NAMES -from bigframes_vendored.sqlglot.errors import ParseError -from bigframes_vendored.sqlglot.generator import Generator, unsupported_args -from bigframes_vendored.sqlglot.helper import ( - AutoName, - flatten, - is_int, - seq_get, - suggest_closest_match_and_fail, - to_bool, -) -from bigframes_vendored.sqlglot.jsonpath import JSONPathTokenizer -from bigframes_vendored.sqlglot.jsonpath import parse as parse_json_path -from bigframes_vendored.sqlglot.parser import Parser -from bigframes_vendored.sqlglot.time import TIMEZONES, format_time, subsecond_precision -from bigframes_vendored.sqlglot.tokens import Token, Tokenizer, TokenType -from bigframes_vendored.sqlglot.trie import new_trie -from bigframes_vendored.sqlglot.typing import EXPRESSION_METADATA - -DATE_ADD_OR_DIFF = t.Union[ - exp.DateAdd, - exp.DateDiff, - exp.DateSub, - exp.TsOrDsAdd, - exp.TsOrDsDiff, -] -DATE_ADD_OR_SUB = t.Union[exp.DateAdd, exp.TsOrDsAdd, exp.DateSub] -JSON_EXTRACT_TYPE = t.Union[ - exp.JSONExtract, exp.JSONExtractScalar, exp.JSONBExtract, exp.JSONBExtractScalar -] -DATETIME_DELTA = t.Union[ - exp.DateAdd, - exp.DatetimeAdd, - exp.DatetimeSub, - exp.TimeAdd, - exp.TimeSub, - exp.TimestampAdd, - exp.TimestampSub, - exp.TsOrDsAdd, -] -DATETIME_ADD = ( - exp.DateAdd, - exp.TimeAdd, - exp.DatetimeAdd, - exp.TsOrDsAdd, - exp.TimestampAdd, -) - -if t.TYPE_CHECKING: - from sqlglot._typing import B, E, F - -logger = logging.getLogger("sqlglot") - -UNESCAPED_SEQUENCES = { - "\\a": "\a", - "\\b": "\b", - "\\f": "\f", - "\\n": "\n", - "\\r": "\r", - "\\t": "\t", - "\\v": "\v", - "\\\\": "\\", -} - - -class Dialects(str, Enum): - """Dialects supported by SQLGLot.""" - - DIALECT = "" - - ATHENA = "athena" - BIGQUERY = "bigquery" - CLICKHOUSE = "clickhouse" - DATABRICKS = "databricks" - DORIS = "doris" - DREMIO = "dremio" - DRILL = "drill" - DRUID = "druid" - DUCKDB = "duckdb" - DUNE = "dune" - FABRIC = "fabric" - HIVE = "hive" - MATERIALIZE = "materialize" - MYSQL = "mysql" - ORACLE = "oracle" - POSTGRES = "postgres" - PRESTO = "presto" - PRQL = "prql" - REDSHIFT = "redshift" - RISINGWAVE = "risingwave" - SNOWFLAKE = "snowflake" - SOLR = "solr" - SPARK = "spark" - SPARK2 = "spark2" - SQLITE = "sqlite" - STARROCKS = "starrocks" - TABLEAU = "tableau" - TERADATA = "teradata" - TRINO = "trino" - TSQL = "tsql" - EXASOL = "exasol" - - -class NormalizationStrategy(str, AutoName): - """Specifies the strategy according to which identifiers should be normalized.""" - - LOWERCASE = auto() - """Unquoted identifiers are lowercased.""" - - UPPERCASE = auto() - """Unquoted identifiers are uppercased.""" - - CASE_SENSITIVE = auto() - """Always case-sensitive, regardless of quotes.""" - - CASE_INSENSITIVE = auto() - """Always case-insensitive (lowercase), regardless of quotes.""" - - CASE_INSENSITIVE_UPPERCASE = auto() - """Always case-insensitive (uppercase), regardless of quotes.""" - - -class _Dialect(type): - _classes: t.Dict[str, t.Type[Dialect]] = {} - - def __eq__(cls, other: t.Any) -> bool: - if cls is other: - return True - if isinstance(other, str): - return cls is cls.get(other) - if isinstance(other, Dialect): - return cls is type(other) - - return False - - def __hash__(cls) -> int: - return hash(cls.__name__.lower()) - - @property - def classes(cls): - if len(DIALECT_MODULE_NAMES) != len(cls._classes): - for key in DIALECT_MODULE_NAMES: - cls._try_load(key) - - return cls._classes - - @classmethod - def _try_load(cls, key: str | Dialects) -> None: - if isinstance(key, Dialects): - key = key.value - - # This import will lead to a new dialect being loaded, and hence, registered. - # We check that the key is an actual sqlglot module to avoid blindly importing - # files. Custom user dialects need to be imported at the top-level package, in - # order for them to be registered as soon as possible. - if key in DIALECT_MODULE_NAMES: - importlib.import_module(f"bigframes_vendored.sqlglot.dialects.{key}") - - @classmethod - def __getitem__(cls, key: str) -> t.Type[Dialect]: - if key not in cls._classes: - cls._try_load(key) - - return cls._classes[key] - - @classmethod - def get( - cls, key: str, default: t.Optional[t.Type[Dialect]] = None - ) -> t.Optional[t.Type[Dialect]]: - if key not in cls._classes: - cls._try_load(key) - - return cls._classes.get(key, default) - - def __new__(cls, clsname, bases, attrs): - klass = super().__new__(cls, clsname, bases, attrs) - enum = Dialects.__members__.get(clsname.upper()) - cls._classes[enum.value if enum is not None else clsname.lower()] = klass - - klass.TIME_TRIE = new_trie(klass.TIME_MAPPING) - klass.FORMAT_TRIE = ( - new_trie(klass.FORMAT_MAPPING) if klass.FORMAT_MAPPING else klass.TIME_TRIE - ) - # Merge class-defined INVERSE_TIME_MAPPING with auto-generated mappings - # This allows dialects to define custom inverse mappings for roundtrip correctness - klass.INVERSE_TIME_MAPPING = {v: k for k, v in klass.TIME_MAPPING.items()} | ( - klass.__dict__.get("INVERSE_TIME_MAPPING") or {} - ) - klass.INVERSE_TIME_TRIE = new_trie(klass.INVERSE_TIME_MAPPING) - klass.INVERSE_FORMAT_MAPPING = {v: k for k, v in klass.FORMAT_MAPPING.items()} - klass.INVERSE_FORMAT_TRIE = new_trie(klass.INVERSE_FORMAT_MAPPING) - - klass.INVERSE_CREATABLE_KIND_MAPPING = { - v: k for k, v in klass.CREATABLE_KIND_MAPPING.items() - } - - base = seq_get(bases, 0) - base_tokenizer = (getattr(base, "tokenizer_class", Tokenizer),) - base_jsonpath_tokenizer = ( - getattr(base, "jsonpath_tokenizer_class", JSONPathTokenizer), - ) - base_parser = (getattr(base, "parser_class", Parser),) - base_generator = (getattr(base, "generator_class", Generator),) - - klass.tokenizer_class = klass.__dict__.get( - "Tokenizer", type("Tokenizer", base_tokenizer, {}) - ) - klass.jsonpath_tokenizer_class = klass.__dict__.get( - "JSONPathTokenizer", type("JSONPathTokenizer", base_jsonpath_tokenizer, {}) - ) - klass.parser_class = klass.__dict__.get( - "Parser", type("Parser", base_parser, {}) - ) - klass.generator_class = klass.__dict__.get( - "Generator", type("Generator", base_generator, {}) - ) - - klass.QUOTE_START, klass.QUOTE_END = list( - klass.tokenizer_class._QUOTES.items() - )[0] - klass.IDENTIFIER_START, klass.IDENTIFIER_END = list( - klass.tokenizer_class._IDENTIFIERS.items() - )[0] - - def get_start_end( - token_type: TokenType, - ) -> t.Tuple[t.Optional[str], t.Optional[str]]: - return next( - ( - (s, e) - for s, (e, t) in klass.tokenizer_class._FORMAT_STRINGS.items() - if t == token_type - ), - (None, None), - ) - - klass.BIT_START, klass.BIT_END = get_start_end(TokenType.BIT_STRING) - klass.HEX_START, klass.HEX_END = get_start_end(TokenType.HEX_STRING) - klass.BYTE_START, klass.BYTE_END = get_start_end(TokenType.BYTE_STRING) - klass.UNICODE_START, klass.UNICODE_END = get_start_end(TokenType.UNICODE_STRING) - - if "\\" in klass.tokenizer_class.STRING_ESCAPES: - klass.UNESCAPED_SEQUENCES = { - **UNESCAPED_SEQUENCES, - **klass.UNESCAPED_SEQUENCES, - } - - klass.ESCAPED_SEQUENCES = {v: k for k, v in klass.UNESCAPED_SEQUENCES.items()} - - klass.SUPPORTS_COLUMN_JOIN_MARKS = "(+)" in klass.tokenizer_class.KEYWORDS - - if enum not in ("", "bigquery", "snowflake"): - klass.INITCAP_SUPPORTS_CUSTOM_DELIMITERS = False - - if enum not in ("", "bigquery"): - klass.generator_class.SELECT_KINDS = () - - if enum not in ("", "athena", "presto", "trino", "duckdb"): - klass.generator_class.TRY_SUPPORTED = False - klass.generator_class.SUPPORTS_UESCAPE = False - - if enum not in ("", "databricks", "hive", "spark", "spark2"): - modifier_transforms = ( - klass.generator_class.AFTER_HAVING_MODIFIER_TRANSFORMS.copy() - ) - for modifier in ("cluster", "distribute", "sort"): - modifier_transforms.pop(modifier, None) - - klass.generator_class.AFTER_HAVING_MODIFIER_TRANSFORMS = modifier_transforms - - if enum not in ("", "doris", "mysql"): - klass.parser_class.ID_VAR_TOKENS = klass.parser_class.ID_VAR_TOKENS | { - TokenType.STRAIGHT_JOIN, - } - klass.parser_class.TABLE_ALIAS_TOKENS = ( - klass.parser_class.TABLE_ALIAS_TOKENS - | { - TokenType.STRAIGHT_JOIN, - } - ) - - if enum not in ("", "databricks", "oracle", "redshift", "snowflake", "spark"): - klass.generator_class.SUPPORTS_DECODE_CASE = False - - if not klass.SUPPORTS_SEMI_ANTI_JOIN: - klass.parser_class.TABLE_ALIAS_TOKENS = ( - klass.parser_class.TABLE_ALIAS_TOKENS - | { - TokenType.ANTI, - TokenType.SEMI, - } - ) - - if enum not in ( - "", - "postgres", - "duckdb", - "redshift", - "snowflake", - "presto", - "trino", - "mysql", - "singlestore", - ): - no_paren_functions = klass.parser_class.NO_PAREN_FUNCTIONS.copy() - no_paren_functions.pop(TokenType.LOCALTIME, None) - if enum != "oracle": - no_paren_functions.pop(TokenType.LOCALTIMESTAMP, None) - klass.parser_class.NO_PAREN_FUNCTIONS = no_paren_functions - - if enum in ( - "", - "postgres", - "duckdb", - "trino", - ): - no_paren_functions = klass.parser_class.NO_PAREN_FUNCTIONS.copy() - no_paren_functions[TokenType.CURRENT_CATALOG] = exp.CurrentCatalog - klass.parser_class.NO_PAREN_FUNCTIONS = no_paren_functions - else: - # For dialects that don't support this keyword, treat it as a regular identifier - # This fixes the "Unexpected token" error in BQ, Spark, etc. - klass.parser_class.ID_VAR_TOKENS = klass.parser_class.ID_VAR_TOKENS | { - TokenType.CURRENT_CATALOG, - } - - if enum in ( - "", - "duckdb", - "spark", - "postgres", - "tsql", - ): - no_paren_functions = klass.parser_class.NO_PAREN_FUNCTIONS.copy() - no_paren_functions[TokenType.SESSION_USER] = exp.SessionUser - klass.parser_class.NO_PAREN_FUNCTIONS = no_paren_functions - else: - klass.parser_class.ID_VAR_TOKENS = klass.parser_class.ID_VAR_TOKENS | { - TokenType.SESSION_USER, - } - - klass.VALID_INTERVAL_UNITS = { - *klass.VALID_INTERVAL_UNITS, - *klass.DATE_PART_MAPPING.keys(), - *klass.DATE_PART_MAPPING.values(), - } - - return klass - - -class Dialect(metaclass=_Dialect): - INDEX_OFFSET = 0 - """The base index offset for arrays.""" - - WEEK_OFFSET = 0 - """First day of the week in DATE_TRUNC(week). Defaults to 0 (Monday). -1 would be Sunday.""" - - UNNEST_COLUMN_ONLY = False - """Whether `UNNEST` table aliases are treated as column aliases.""" - - ALIAS_POST_TABLESAMPLE = False - """Whether the table alias comes after tablesample.""" - - TABLESAMPLE_SIZE_IS_PERCENT = False - """Whether a size in the table sample clause represents percentage.""" - - NORMALIZATION_STRATEGY = NormalizationStrategy.LOWERCASE - """Specifies the strategy according to which identifiers should be normalized.""" - - IDENTIFIERS_CAN_START_WITH_DIGIT = False - """Whether an unquoted identifier can start with a digit.""" - - DPIPE_IS_STRING_CONCAT = True - """Whether the DPIPE token (`||`) is a string concatenation operator.""" - - STRICT_STRING_CONCAT = False - """Whether `CONCAT`'s arguments must be strings.""" - - SUPPORTS_USER_DEFINED_TYPES = True - """Whether user-defined data types are supported.""" - - SUPPORTS_SEMI_ANTI_JOIN = True - """Whether `SEMI` or `ANTI` joins are supported.""" - - SUPPORTS_COLUMN_JOIN_MARKS = False - """Whether the old-style outer join (+) syntax is supported.""" - - COPY_PARAMS_ARE_CSV = True - """Separator of COPY statement parameters.""" - - NORMALIZE_FUNCTIONS: bool | str = "upper" - """ - Determines how function names are going to be normalized. - Possible values: - "upper" or True: Convert names to uppercase. - "lower": Convert names to lowercase. - False: Disables function name normalization. - """ - - PRESERVE_ORIGINAL_NAMES: bool = False - """ - Whether the name of the function should be preserved inside the node's metadata, - can be useful for roundtripping deprecated vs new functions that share an AST node - e.g JSON_VALUE vs JSON_EXTRACT_SCALAR in BigQuery - """ - - LOG_BASE_FIRST: t.Optional[bool] = True - """ - Whether the base comes first in the `LOG` function. - Possible values: `True`, `False`, `None` (two arguments are not supported by `LOG`) - """ - - NULL_ORDERING = "nulls_are_small" - """ - Default `NULL` ordering method to use if not explicitly set. - Possible values: `"nulls_are_small"`, `"nulls_are_large"`, `"nulls_are_last"` - """ - - TYPED_DIVISION = False - """ - Whether the behavior of `a / b` depends on the types of `a` and `b`. - False means `a / b` is always float division. - True means `a / b` is integer division if both `a` and `b` are integers. - """ - - SAFE_DIVISION = False - """Whether division by zero throws an error (`False`) or returns NULL (`True`).""" - - CONCAT_COALESCE = False - """A `NULL` arg in `CONCAT` yields `NULL` by default, but in some dialects it yields an empty string.""" - - HEX_LOWERCASE = False - """Whether the `HEX` function returns a lowercase hexadecimal string.""" - - DATE_FORMAT = "'%Y-%m-%d'" - DATEINT_FORMAT = "'%Y%m%d'" - TIME_FORMAT = "'%Y-%m-%d %H:%M:%S'" - - TIME_MAPPING: t.Dict[str, str] = {} - """Associates this dialect's time formats with their equivalent Python `strftime` formats.""" - - # https://cloud.google.com/bigquery/docs/reference/standard-sql/format-elements#format_model_rules_date_time - # https://docs.teradata.com/r/Teradata-Database-SQL-Functions-Operators-Expressions-and-Predicates/March-2017/Data-Type-Conversions/Character-to-DATE-Conversion/Forcing-a-FORMAT-on-CAST-for-Converting-Character-to-DATE - FORMAT_MAPPING: t.Dict[str, str] = {} - """ - Helper which is used for parsing the special syntax `CAST(x AS DATE FORMAT 'yyyy')`. - If empty, the corresponding trie will be constructed off of `TIME_MAPPING`. - """ - - UNESCAPED_SEQUENCES: t.Dict[str, str] = {} - """Mapping of an escaped sequence (`\\n`) to its unescaped version (`\n`).""" - - PSEUDOCOLUMNS: t.Set[str] = set() - """ - Columns that are auto-generated by the engine corresponding to this dialect. - For example, such columns may be excluded from `SELECT *` queries. - """ - - PREFER_CTE_ALIAS_COLUMN = False - """ - Some dialects, such as Snowflake, allow you to reference a CTE column alias in the - HAVING clause of the CTE. This flag will cause the CTE alias columns to override - any projection aliases in the subquery. - - For example, - WITH y(c) AS ( - SELECT SUM(a) FROM (SELECT 1 a) AS x HAVING c > 0 - ) SELECT c FROM y; - - will be rewritten as - - WITH y(c) AS ( - SELECT SUM(a) AS c FROM (SELECT 1 AS a) AS x HAVING c > 0 - ) SELECT c FROM y; - """ - - COPY_PARAMS_ARE_CSV = True - """ - Whether COPY statement parameters are separated by comma or whitespace - """ - - FORCE_EARLY_ALIAS_REF_EXPANSION = False - """ - Whether alias reference expansion (_expand_alias_refs()) should run before column qualification (_qualify_columns()). - - For example: - WITH data AS ( - SELECT - 1 AS id, - 2 AS my_id - ) - SELECT - id AS my_id - FROM - data - WHERE - my_id = 1 - GROUP BY - my_id, - HAVING - my_id = 1 - - In most dialects, "my_id" would refer to "data.my_id" across the query, except: - - BigQuery, which will forward the alias to GROUP BY + HAVING clauses i.e - it resolves to "WHERE my_id = 1 GROUP BY id HAVING id = 1" - - Clickhouse, which will forward the alias across the query i.e it resolves - to "WHERE id = 1 GROUP BY id HAVING id = 1" - """ - - EXPAND_ONLY_GROUP_ALIAS_REF = False - """Whether alias reference expansion before qualification should only happen for the GROUP BY clause.""" - - ANNOTATE_ALL_SCOPES = False - """Whether to annotate all scopes during optimization. Used by BigQuery for UNNEST support.""" - - DISABLES_ALIAS_REF_EXPANSION = False - """ - Whether alias reference expansion is disabled for this dialect. - - Some dialects like Oracle do NOT support referencing aliases in projections or WHERE clauses. - The original expression must be repeated instead. - - For example, in Oracle: - SELECT y.foo AS bar, bar * 2 AS baz FROM y -- INVALID - SELECT y.foo AS bar, y.foo * 2 AS baz FROM y -- VALID - """ - - SUPPORTS_ALIAS_REFS_IN_JOIN_CONDITIONS = False - """ - Whether alias references are allowed in JOIN ... ON clauses. - - Most dialects do not support this, but Snowflake allows alias expansion in the JOIN ... ON - clause (and almost everywhere else) - - For example, in Snowflake: - SELECT a.id AS user_id FROM a JOIN b ON user_id = b.id -- VALID - - Reference: https://docs.snowflake.com/en/sql-reference/sql/select#usage-notes - """ - - SUPPORTS_ORDER_BY_ALL = False - """ - Whether ORDER BY ALL is supported (expands to all the selected columns) as in DuckDB, Spark3/Databricks - """ - - PROJECTION_ALIASES_SHADOW_SOURCE_NAMES = False - """ - Whether projection alias names can shadow table/source names in GROUP BY and HAVING clauses. - - In BigQuery, when a projection alias has the same name as a source table, the alias takes - precedence in GROUP BY and HAVING clauses, and the table becomes inaccessible by that name. - - For example, in BigQuery: - SELECT id, ARRAY_AGG(col) AS custom_fields - FROM custom_fields - GROUP BY id - HAVING id >= 1 - - The "custom_fields" source is shadowed by the projection alias, so we cannot qualify "id" - with "custom_fields" in GROUP BY/HAVING. - """ - - TABLES_REFERENCEABLE_AS_COLUMNS = False - """ - Whether table names can be referenced as columns (treated as structs). - - BigQuery allows tables to be referenced as columns in queries, automatically treating - them as struct values containing all the table's columns. - - For example, in BigQuery: - SELECT t FROM my_table AS t -- Returns entire row as a struct - """ - - SUPPORTS_STRUCT_STAR_EXPANSION = False - """ - Whether the dialect supports expanding struct fields using star notation (e.g., struct_col.*). - - BigQuery allows struct fields to be expanded with the star operator: - SELECT t.struct_col.* FROM table t - RisingWave also allows struct field expansion with the star operator using parentheses: - SELECT (t.struct_col).* FROM table t - - This expands to all fields within the struct. - """ - - EXCLUDES_PSEUDOCOLUMNS_FROM_STAR = False - """ - Whether pseudocolumns should be excluded from star expansion (SELECT *). - - Pseudocolumns are special dialect-specific columns (e.g., Oracle's ROWNUM, ROWID, LEVEL, - or BigQuery's _PARTITIONTIME, _PARTITIONDATE) that are implicitly available but not part - of the table schema. When this is True, SELECT * will not include these pseudocolumns; - they must be explicitly selected. - """ - - QUERY_RESULTS_ARE_STRUCTS = False - """ - Whether query results are typed as structs in metadata for type inference. - - In BigQuery, subqueries store their column types as a STRUCT in metadata, - enabling special type inference for ARRAY(SELECT ...) expressions: - ARRAY(SELECT x, y FROM t) → ARRAY> - - For single column subqueries, BigQuery unwraps the struct: - ARRAY(SELECT x FROM t) → ARRAY - - This is metadata-only for type inference. - """ - - REQUIRES_PARENTHESIZED_STRUCT_ACCESS = False - """ - Whether struct field access requires parentheses around the expression. - - RisingWave requires parentheses for struct field access in certain contexts: - SELECT (col.field).subfield FROM table -- Parentheses required - - Without parentheses, the parser may not correctly interpret nested struct access. - - Reference: https://docs.risingwave.com/sql/data-types/struct#retrieve-data-in-a-struct - """ - - SUPPORTS_NULL_TYPE = False - """ - Whether NULL/VOID is supported as a valid data type (not just a value). - - Databricks and Spark v3+ support NULL as an actual type, allowing expressions like: - SELECT NULL AS col -- Has type NULL, not just value NULL - CAST(x AS VOID) -- Valid type cast - """ - - COALESCE_COMPARISON_NON_STANDARD = False - """ - Whether COALESCE in comparisons has non-standard NULL semantics. - - We can't convert `COALESCE(x, 1) = 2` into `NOT x IS NULL AND x = 2` for redshift, - because they are not always equivalent. For example, if `x` is `NULL` and it comes - from a table, then the result is `NULL`, despite `FALSE AND NULL` evaluating to `FALSE`. - - In standard SQL and most dialects, these expressions are equivalent, but Redshift treats - table NULLs differently in this context. - """ - - HAS_DISTINCT_ARRAY_CONSTRUCTORS = False - """ - Whether the ARRAY constructor is context-sensitive, i.e in Redshift ARRAY[1, 2, 3] != ARRAY(1, 2, 3) - as the former is of type INT[] vs the latter which is SUPER - """ - - SUPPORTS_FIXED_SIZE_ARRAYS = False - """ - Whether expressions such as x::INT[5] should be parsed as fixed-size array defs/casts e.g. - in DuckDB. In dialects which don't support fixed size arrays such as Snowflake, this should - be interpreted as a subscript/index operator. - """ - - STRICT_JSON_PATH_SYNTAX = True - """Whether failing to parse a JSON path expression using the JSONPath dialect will log a warning.""" - - ON_CONDITION_EMPTY_BEFORE_ERROR = True - """Whether "X ON EMPTY" should come before "X ON ERROR" (for dialects like T-SQL, MySQL, Oracle).""" - - ARRAY_AGG_INCLUDES_NULLS: t.Optional[bool] = True - """Whether ArrayAgg needs to filter NULL values.""" - - PROMOTE_TO_INFERRED_DATETIME_TYPE = False - """ - This flag is used in the optimizer's canonicalize rule and determines whether x will be promoted - to the literal's type in x::DATE < '2020-01-01 12:05:03' (i.e., DATETIME). When false, the literal - is cast to x's type to match it instead. - """ - - SUPPORTS_VALUES_DEFAULT = True - """Whether the DEFAULT keyword is supported in the VALUES clause.""" - - NUMBERS_CAN_BE_UNDERSCORE_SEPARATED = False - """Whether number literals can include underscores for better readability""" - - HEX_STRING_IS_INTEGER_TYPE: bool = False - """Whether hex strings such as x'CC' evaluate to integer or binary/blob type""" - - REGEXP_EXTRACT_DEFAULT_GROUP = 0 - """The default value for the capturing group.""" - - REGEXP_EXTRACT_POSITION_OVERFLOW_RETURNS_NULL = True - """Whether REGEXP_EXTRACT returns NULL when the position arg exceeds the string length.""" - - SET_OP_DISTINCT_BY_DEFAULT: t.Dict[t.Type[exp.Expression], t.Optional[bool]] = { - exp.Except: True, - exp.Intersect: True, - exp.Union: True, - } - """ - Whether a set operation uses DISTINCT by default. This is `None` when either `DISTINCT` or `ALL` - must be explicitly specified. - """ - - CREATABLE_KIND_MAPPING: dict[str, str] = {} - """ - Helper for dialects that use a different name for the same creatable kind. For example, the Clickhouse - equivalent of CREATE SCHEMA is CREATE DATABASE. - """ - - ALTER_TABLE_SUPPORTS_CASCADE = False - """ - Hive by default does not update the schema of existing partitions when a column is changed. - the CASCADE clause is used to indicate that the change should be propagated to all existing partitions. - the Spark dialect, while derived from Hive, does not support the CASCADE clause. - """ - - # Whether ADD is present for each column added by ALTER TABLE - ALTER_TABLE_ADD_REQUIRED_FOR_EACH_COLUMN = True - - # Whether the value/LHS of the TRY_CAST( AS ) should strictly be a - # STRING type (Snowflake's case) or can be of any type - TRY_CAST_REQUIRES_STRING: t.Optional[bool] = None - - # Whether the double negation can be applied - # Not safe with MySQL and SQLite due to type coercion (may not return boolean) - SAFE_TO_ELIMINATE_DOUBLE_NEGATION = True - - # Whether the INITCAP function supports custom delimiter characters as the second argument - # Default delimiter characters for INITCAP function: whitespace and non-alphanumeric characters - INITCAP_SUPPORTS_CUSTOM_DELIMITERS = True - INITCAP_DEFAULT_DELIMITER_CHARS = ( - " \t\n\r\f\v!\"#$%&'()*+,\\-./:;<=>?@\\[\\]^_`{|}~" - ) - - BYTE_STRING_IS_BYTES_TYPE: bool = False - """ - Whether byte string literals (ex: BigQuery's b'...') are typed as BYTES/BINARY - """ - - UUID_IS_STRING_TYPE: bool = False - """ - Whether a UUID is considered a string or a UUID type. - """ - - JSON_EXTRACT_SCALAR_SCALAR_ONLY = False - """ - Whether JSON_EXTRACT_SCALAR returns null if a non-scalar value is selected. - """ - - DEFAULT_FUNCTIONS_COLUMN_NAMES: t.Dict[ - t.Type[exp.Func], t.Union[str, t.Tuple[str, ...]] - ] = {} - """ - Maps function expressions to their default output column name(s). - - For example, in Postgres, generate_series function outputs a column named "generate_series" by default, - so we map the ExplodingGenerateSeries expression to "generate_series" string. - """ - - DEFAULT_NULL_TYPE = exp.DataType.Type.UNKNOWN - """ - The default type of NULL for producing the correct projection type. - - For example, in BigQuery the default type of the NULL value is INT64. - """ - - LEAST_GREATEST_IGNORES_NULLS = True - """ - Whether LEAST/GREATEST functions ignore NULL values, e.g: - - BigQuery, Snowflake, MySQL, Presto/Trino: LEAST(1, NULL, 2) -> NULL - - Spark, Postgres, DuckDB, TSQL: LEAST(1, NULL, 2) -> 1 - """ - - PRIORITIZE_NON_LITERAL_TYPES = False - """ - Whether to prioritize non-literal types over literals during type annotation. - """ - - # --- Autofilled --- - - tokenizer_class = Tokenizer - jsonpath_tokenizer_class = JSONPathTokenizer - parser_class = Parser - generator_class = Generator - - # A trie of the time_mapping keys - TIME_TRIE: t.Dict = {} - FORMAT_TRIE: t.Dict = {} - - INVERSE_TIME_MAPPING: t.Dict[str, str] = {} - INVERSE_TIME_TRIE: t.Dict = {} - INVERSE_FORMAT_MAPPING: t.Dict[str, str] = {} - INVERSE_FORMAT_TRIE: t.Dict = {} - - INVERSE_CREATABLE_KIND_MAPPING: dict[str, str] = {} - - ESCAPED_SEQUENCES: t.Dict[str, str] = {} - - # Delimiters for string literals and identifiers - QUOTE_START = "'" - QUOTE_END = "'" - IDENTIFIER_START = '"' - IDENTIFIER_END = '"' - - VALID_INTERVAL_UNITS: t.Set[str] = set() - - # Delimiters for bit, hex, byte and unicode literals - BIT_START: t.Optional[str] = None - BIT_END: t.Optional[str] = None - HEX_START: t.Optional[str] = None - HEX_END: t.Optional[str] = None - BYTE_START: t.Optional[str] = None - BYTE_END: t.Optional[str] = None - UNICODE_START: t.Optional[str] = None - UNICODE_END: t.Optional[str] = None - - DATE_PART_MAPPING = { - "Y": "YEAR", - "YY": "YEAR", - "YYY": "YEAR", - "YYYY": "YEAR", - "YR": "YEAR", - "YEARS": "YEAR", - "YRS": "YEAR", - "MM": "MONTH", - "MON": "MONTH", - "MONS": "MONTH", - "MONTHS": "MONTH", - "D": "DAY", - "DD": "DAY", - "DAYS": "DAY", - "DAYOFMONTH": "DAY", - "DAY OF WEEK": "DAYOFWEEK", - "WEEKDAY": "DAYOFWEEK", - "DOW": "DAYOFWEEK", - "DW": "DAYOFWEEK", - "WEEKDAY_ISO": "DAYOFWEEKISO", - "DOW_ISO": "DAYOFWEEKISO", - "DW_ISO": "DAYOFWEEKISO", - "DAYOFWEEK_ISO": "DAYOFWEEKISO", - "DAY OF YEAR": "DAYOFYEAR", - "DOY": "DAYOFYEAR", - "DY": "DAYOFYEAR", - "W": "WEEK", - "WK": "WEEK", - "WEEKOFYEAR": "WEEK", - "WOY": "WEEK", - "WY": "WEEK", - "WEEK_ISO": "WEEKISO", - "WEEKOFYEARISO": "WEEKISO", - "WEEKOFYEAR_ISO": "WEEKISO", - "Q": "QUARTER", - "QTR": "QUARTER", - "QTRS": "QUARTER", - "QUARTERS": "QUARTER", - "H": "HOUR", - "HH": "HOUR", - "HR": "HOUR", - "HOURS": "HOUR", - "HRS": "HOUR", - "M": "MINUTE", - "MI": "MINUTE", - "MIN": "MINUTE", - "MINUTES": "MINUTE", - "MINS": "MINUTE", - "S": "SECOND", - "SEC": "SECOND", - "SECONDS": "SECOND", - "SECS": "SECOND", - "MS": "MILLISECOND", - "MSEC": "MILLISECOND", - "MSECS": "MILLISECOND", - "MSECOND": "MILLISECOND", - "MSECONDS": "MILLISECOND", - "MILLISEC": "MILLISECOND", - "MILLISECS": "MILLISECOND", - "MILLISECON": "MILLISECOND", - "MILLISECONDS": "MILLISECOND", - "US": "MICROSECOND", - "USEC": "MICROSECOND", - "USECS": "MICROSECOND", - "MICROSEC": "MICROSECOND", - "MICROSECS": "MICROSECOND", - "USECOND": "MICROSECOND", - "USECONDS": "MICROSECOND", - "MICROSECONDS": "MICROSECOND", - "NS": "NANOSECOND", - "NSEC": "NANOSECOND", - "NANOSEC": "NANOSECOND", - "NSECOND": "NANOSECOND", - "NSECONDS": "NANOSECOND", - "NANOSECS": "NANOSECOND", - "EPOCH_SECOND": "EPOCH", - "EPOCH_SECONDS": "EPOCH", - "EPOCH_MILLISECONDS": "EPOCH_MILLISECOND", - "EPOCH_MICROSECONDS": "EPOCH_MICROSECOND", - "EPOCH_NANOSECONDS": "EPOCH_NANOSECOND", - "TZH": "TIMEZONE_HOUR", - "TZM": "TIMEZONE_MINUTE", - "DEC": "DECADE", - "DECS": "DECADE", - "DECADES": "DECADE", - "MIL": "MILLENNIUM", - "MILS": "MILLENNIUM", - "MILLENIA": "MILLENNIUM", - "C": "CENTURY", - "CENT": "CENTURY", - "CENTS": "CENTURY", - "CENTURIES": "CENTURY", - } - - # Specifies what types a given type can be coerced into - COERCES_TO: t.Dict[exp.DataType.Type, t.Set[exp.DataType.Type]] = {} - - # Specifies type inference & validation rules for expressions - EXPRESSION_METADATA = EXPRESSION_METADATA.copy() - - # Determines the supported Dialect instance settings - SUPPORTED_SETTINGS = { - "normalization_strategy", - "version", - } - - @classmethod - def get_or_raise(cls, dialect: DialectType) -> Dialect: - """ - Look up a dialect in the global dialect registry and return it if it exists. - - Args: - dialect: The target dialect. If this is a string, it can be optionally followed by - additional key-value pairs that are separated by commas and are used to specify - dialect settings, such as whether the dialect's identifiers are case-sensitive. - - Example: - >>> dialect = dialect_class = get_or_raise("duckdb") - >>> dialect = get_or_raise("mysql, normalization_strategy = case_sensitive") - - Returns: - The corresponding Dialect instance. - """ - - if not dialect: - return cls() - if isinstance(dialect, _Dialect): - return dialect() - if isinstance(dialect, Dialect): - return dialect - if isinstance(dialect, str): - try: - dialect_name, *kv_strings = dialect.split(",") - kv_pairs = (kv.split("=") for kv in kv_strings) - kwargs = {} - for pair in kv_pairs: - key = pair[0].strip() - value: t.Union[bool | str | None] = None - - if len(pair) == 1: - # Default initialize standalone settings to True - value = True - elif len(pair) == 2: - value = pair[1].strip() - - kwargs[key] = to_bool(value) - - except ValueError: - raise ValueError( - f"Invalid dialect format: '{dialect}'. " - "Please use the correct format: 'dialect [, k1 = v2 [, ...]]'." - ) - - result = cls.get(dialect_name.strip()) - if not result: - suggest_closest_match_and_fail( - "dialect", dialect_name, list(DIALECT_MODULE_NAMES) - ) - - assert result is not None - return result(**kwargs) - - raise ValueError(f"Invalid dialect type for '{dialect}': '{type(dialect)}'.") - - @classmethod - def format_time( - cls, expression: t.Optional[str | exp.Expression] - ) -> t.Optional[exp.Expression]: - """Converts a time format in this dialect to its equivalent Python `strftime` format.""" - if isinstance(expression, str): - return exp.Literal.string( - # the time formats are quoted - format_time(expression[1:-1], cls.TIME_MAPPING, cls.TIME_TRIE) - ) - - if expression and expression.is_string: - return exp.Literal.string( - format_time(expression.this, cls.TIME_MAPPING, cls.TIME_TRIE) - ) - - return expression - - def __init__(self, **kwargs) -> None: - parts = str(kwargs.pop("version", sys.maxsize)).split(".") - parts.extend(["0"] * (3 - len(parts))) - self.version = tuple(int(p) for p in parts[:3]) - - normalization_strategy = kwargs.pop("normalization_strategy", None) - if normalization_strategy is None: - self.normalization_strategy = self.NORMALIZATION_STRATEGY - else: - self.normalization_strategy = NormalizationStrategy( - normalization_strategy.upper() - ) - - self.settings = kwargs - - for unsupported_setting in kwargs.keys() - self.SUPPORTED_SETTINGS: - suggest_closest_match_and_fail( - "setting", unsupported_setting, self.SUPPORTED_SETTINGS - ) - - def __eq__(self, other: t.Any) -> bool: - # Does not currently take dialect state into account - return isinstance(self, other.__class__) - - def __hash__(self) -> int: - # Does not currently take dialect state into account - return hash(type(self)) - - def normalize_identifier(self, expression: E) -> E: - """ - Transforms an identifier in a way that resembles how it'd be resolved by this dialect. - - For example, an identifier like `FoO` would be resolved as `foo` in Postgres, because it - lowercases all unquoted identifiers. On the other hand, Snowflake uppercases them, so - it would resolve it as `FOO`. If it was quoted, it'd need to be treated as case-sensitive, - and so any normalization would be prohibited in order to avoid "breaking" the identifier. - - There are also dialects like Spark, which are case-insensitive even when quotes are - present, and dialects like MySQL, whose resolution rules match those employed by the - underlying operating system, for example they may always be case-sensitive in Linux. - - Finally, the normalization behavior of some engines can even be controlled through flags, - like in Redshift's case, where users can explicitly set enable_case_sensitive_identifier. - - SQLGlot aims to understand and handle all of these different behaviors gracefully, so - that it can analyze queries in the optimizer and successfully capture their semantics. - """ - if ( - isinstance(expression, exp.Identifier) - and self.normalization_strategy is not NormalizationStrategy.CASE_SENSITIVE - and ( - not expression.quoted - or self.normalization_strategy - in ( - NormalizationStrategy.CASE_INSENSITIVE, - NormalizationStrategy.CASE_INSENSITIVE_UPPERCASE, - ) - ) - ): - normalized = ( - expression.this.upper() - if self.normalization_strategy - in ( - NormalizationStrategy.UPPERCASE, - NormalizationStrategy.CASE_INSENSITIVE_UPPERCASE, - ) - else expression.this.lower() - ) - expression.set("this", normalized) - - return expression - - def case_sensitive(self, text: str) -> bool: - """Checks if text contains any case sensitive characters, based on the dialect's rules.""" - if self.normalization_strategy is NormalizationStrategy.CASE_INSENSITIVE: - return False - - unsafe = ( - str.islower - if self.normalization_strategy is NormalizationStrategy.UPPERCASE - else str.isupper - ) - return any(unsafe(char) for char in text) - - def can_quote( - self, identifier: exp.Identifier, identify: str | bool = "safe" - ) -> bool: - """Checks if an identifier can be quoted - - Args: - identifier: The identifier to check. - identify: - `True`: Always returns `True` except for certain cases. - `"safe"`: Only returns `True` if the identifier is case-insensitive. - `"unsafe"`: Only returns `True` if the identifier is case-sensitive. - - Returns: - Whether the given text can be identified. - """ - if identifier.quoted: - return True - if not identify: - return False - if isinstance(identifier.parent, exp.Func): - return False - if identify is True: - return True - - is_safe = not self.case_sensitive(identifier.this) and bool( - exp.SAFE_IDENTIFIER_RE.match(identifier.this) - ) - - if identify == "safe": - return is_safe - if identify == "unsafe": - return not is_safe - - raise ValueError(f"Unexpected argument for identify: '{identify}'") - - def quote_identifier(self, expression: E, identify: bool = True) -> E: - """ - Adds quotes to a given expression if it is an identifier. - - Args: - expression: The expression of interest. If it's not an `Identifier`, this method is a no-op. - identify: If set to `False`, the quotes will only be added if the identifier is deemed - "unsafe", with respect to its characters and this dialect's normalization strategy. - """ - if isinstance(expression, exp.Identifier): - expression.set("quoted", self.can_quote(expression, identify or "unsafe")) - return expression - - def to_json_path( - self, path: t.Optional[exp.Expression] - ) -> t.Optional[exp.Expression]: - if isinstance(path, exp.Literal): - path_text = path.name - if path.is_number: - path_text = f"[{path_text}]" - try: - return parse_json_path(path_text, self) - except ParseError as e: - if self.STRICT_JSON_PATH_SYNTAX and not path_text.lstrip().startswith( - ("lax", "strict") - ): - logger.warning(f"Invalid JSON path syntax. {str(e)}") - - return path - - def parse(self, sql: str, **opts) -> t.List[t.Optional[exp.Expression]]: - return self.parser(**opts).parse(self.tokenize(sql), sql) - - def parse_into( - self, expression_type: exp.IntoType, sql: str, **opts - ) -> t.List[t.Optional[exp.Expression]]: - return self.parser(**opts).parse_into(expression_type, self.tokenize(sql), sql) - - def generate(self, expression: exp.Expression, copy: bool = True, **opts) -> str: - return self.generator(**opts).generate(expression, copy=copy) - - def transpile(self, sql: str, **opts) -> t.List[str]: - return [ - self.generate(expression, copy=False, **opts) if expression else "" - for expression in self.parse(sql) - ] - - def tokenize(self, sql: str, **opts) -> t.List[Token]: - return self.tokenizer(**opts).tokenize(sql) - - def tokenizer(self, **opts) -> Tokenizer: - return self.tokenizer_class(**{"dialect": self, **opts}) - - def jsonpath_tokenizer(self, **opts) -> JSONPathTokenizer: - return self.jsonpath_tokenizer_class(**{"dialect": self, **opts}) - - def parser(self, **opts) -> Parser: - return self.parser_class(**{"dialect": self, **opts}) - - def generator(self, **opts) -> Generator: - return self.generator_class(**{"dialect": self, **opts}) - - def generate_values_aliases(self, expression: exp.Values) -> t.List[exp.Identifier]: - return [ - exp.to_identifier(f"_col_{i}") - for i, _ in enumerate(expression.expressions[0].expressions) - ] - - -DialectType = t.Union[str, Dialect, t.Type[Dialect], None] - - -def rename_func(name: str) -> t.Callable[[Generator, exp.Expression], str]: - return lambda self, expression: self.func(name, *flatten(expression.args.values())) - - -@unsupported_args("accuracy") -def approx_count_distinct_sql(self: Generator, expression: exp.ApproxDistinct) -> str: - return self.func("APPROX_COUNT_DISTINCT", expression.this) - - -def if_sql( - name: str = "IF", false_value: t.Optional[exp.Expression | str] = None -) -> t.Callable[[Generator, exp.If], str]: - def _if_sql(self: Generator, expression: exp.If) -> str: - return self.func( - name, - expression.this, - expression.args.get("true"), - expression.args.get("false") or false_value, - ) - - return _if_sql - - -def arrow_json_extract_sql(self: Generator, expression: JSON_EXTRACT_TYPE) -> str: - this = expression.this - if ( - self.JSON_TYPE_REQUIRED_FOR_EXTRACTION - and isinstance(this, exp.Literal) - and this.is_string - ): - this.replace(exp.cast(this, exp.DataType.Type.JSON)) - - return self.binary( - expression, "->" if isinstance(expression, exp.JSONExtract) else "->>" - ) - - -def inline_array_sql(self: Generator, expression: exp.Expression) -> str: - return f"[{self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)}]" - - -def inline_array_unless_query(self: Generator, expression: exp.Expression) -> str: - elem = seq_get(expression.expressions, 0) - if ( - len(expression.expressions) == 1 - and isinstance(elem, exp.Expression) - and ( - isinstance(elem, exp.Query) - or (isinstance(elem, exp.Subquery) and isinstance(elem.this, exp.Query)) - ) - ): - return self.func("ARRAY", elem) - return inline_array_sql(self, expression) - - -def no_ilike_sql(self: Generator, expression: exp.ILike) -> str: - return self.like_sql( - exp.Like( - this=exp.Lower(this=expression.this), - expression=exp.Lower(this=expression.expression), - ) - ) - - -def no_paren_current_date_sql(self: Generator, expression: exp.CurrentDate) -> str: - zone = self.sql(expression, "this") - return f"CURRENT_DATE AT TIME ZONE {zone}" if zone else "CURRENT_DATE" - - -def no_recursive_cte_sql(self: Generator, expression: exp.With) -> str: - if expression.args.get("recursive"): - self.unsupported("Recursive CTEs are unsupported") - expression.set("recursive", False) - return self.with_sql(expression) - - -def no_tablesample_sql(self: Generator, expression: exp.TableSample) -> str: - self.unsupported("TABLESAMPLE unsupported") - return self.sql(expression.this) - - -def no_pivot_sql(self: Generator, expression: exp.Pivot) -> str: - self.unsupported("PIVOT unsupported") - return "" - - -def no_trycast_sql(self: Generator, expression: exp.TryCast) -> str: - return self.cast_sql(expression) - - -def no_comment_column_constraint_sql( - self: Generator, expression: exp.CommentColumnConstraint -) -> str: - self.unsupported("CommentColumnConstraint unsupported") - return "" - - -def no_map_from_entries_sql(self: Generator, expression: exp.MapFromEntries) -> str: - self.unsupported("MAP_FROM_ENTRIES unsupported") - return "" - - -def property_sql(self: Generator, expression: exp.Property) -> str: - return f"{self.property_name(expression, string_key=True)}={self.sql(expression, 'value')}" - - -def strposition_sql( - self: Generator, - expression: exp.StrPosition, - func_name: str = "STRPOS", - supports_position: bool = False, - supports_occurrence: bool = False, - use_ansi_position: bool = True, -) -> str: - string = expression.this - substr = expression.args.get("substr") - position = expression.args.get("position") - occurrence = expression.args.get("occurrence") - zero = exp.Literal.number(0) - one = exp.Literal.number(1) - - if supports_occurrence and occurrence and supports_position and not position: - position = one - - transpile_position = position and not supports_position - if transpile_position: - string = exp.Substring(this=string, start=position) - - if func_name == "POSITION" and use_ansi_position: - func = exp.Anonymous( - this=func_name, expressions=[exp.In(this=substr, field=string)] - ) - else: - args = ( - [substr, string] - if func_name in ("LOCATE", "CHARINDEX") - else [string, substr] - ) - if supports_position: - args.append(position) - if occurrence: - if supports_occurrence: - args.append(occurrence) - else: - self.unsupported( - f"{func_name} does not support the occurrence parameter." - ) - func = exp.Anonymous(this=func_name, expressions=args) - - if transpile_position: - func_with_offset = exp.Sub(this=func + position, expression=one) - func_wrapped = exp.If(this=func.eq(zero), true=zero, false=func_with_offset) - return self.sql(func_wrapped) - - return self.sql(func) - - -def struct_extract_sql(self: Generator, expression: exp.StructExtract) -> str: - return f"{self.sql(expression, 'this')}.{self.sql(exp.to_identifier(expression.expression.name))}" - - -def var_map_sql( - self: Generator, expression: exp.Map | exp.VarMap, map_func_name: str = "MAP" -) -> str: - keys = expression.args.get("keys") - values = expression.args.get("values") - - if not isinstance(keys, exp.Array) or not isinstance(values, exp.Array): - self.unsupported("Cannot convert array columns into map.") - return self.func(map_func_name, keys, values) - - args = [] - for key, value in zip(keys.expressions, values.expressions): - args.append(self.sql(key)) - args.append(self.sql(value)) - - return self.func(map_func_name, *args) - - -def months_between_sql(self: Generator, expression: exp.MonthsBetween) -> str: - """ - Transpile MONTHS_BETWEEN to dialects that don't have native support. - - Snowflake's MONTHS_BETWEEN returns whole months + fractional part where: - - Fractional part = (DAY(date1) - DAY(date2)) / 31 - - Special case: If both dates are last day of month, fractional part = 0 - - Formula: DATEDIFF('month', date2, date1) + (DAY(date1) - DAY(date2)) / 31.0 - """ - date1 = expression.this - date2 = expression.expression - - # Cast to DATE to ensure consistent behavior - date1_cast = exp.cast(date1, exp.DataType.Type.DATE, copy=False) - date2_cast = exp.cast(date2, exp.DataType.Type.DATE, copy=False) - - # Whole months: DATEDIFF('month', date2, date1) - whole_months = exp.DateDiff( - this=date1_cast, expression=date2_cast, unit=exp.var("month") - ) - - # Day components - day1 = exp.Day(this=date1_cast.copy()) - day2 = exp.Day(this=date2_cast.copy()) - - # Last day of month components - last_day_of_month1 = exp.LastDay(this=date1_cast.copy()) - last_day_of_month2 = exp.LastDay(this=date2_cast.copy()) - - day_of_last_day1 = exp.Day(this=last_day_of_month1) - day_of_last_day2 = exp.Day(this=last_day_of_month2) - - # Check if both are last day of month - last_day1 = exp.EQ(this=day1.copy(), expression=day_of_last_day1) - last_day2 = exp.EQ(this=day2.copy(), expression=day_of_last_day2) - both_last_day = exp.And(this=last_day1, expression=last_day2) - - # Fractional part: (DAY(date1) - DAY(date2)) / 31.0 - fractional = exp.Div( - this=exp.Paren(this=exp.Sub(this=day1.copy(), expression=day2.copy())), - expression=exp.Literal.number("31.0"), - ) - - # If both are last day of month, fractional = 0, else calculate fractional - fractional_with_check = exp.If( - this=both_last_day, true=exp.Literal.number("0"), false=fractional - ) - - # Final result: whole_months + fractional - result = exp.Add(this=whole_months, expression=fractional_with_check) - - return self.sql(result) - - -def build_formatted_time( - exp_class: t.Type[E], dialect: str, default: t.Optional[bool | str] = None -) -> t.Callable[[t.List], E]: - """Helper used for time expressions. - - Args: - exp_class: the expression class to instantiate. - dialect: target sql dialect. - default: the default format, True being time. - - Returns: - A callable that can be used to return the appropriately formatted time expression. - """ - - def _builder(args: t.List): - return exp_class( - this=seq_get(args, 0), - format=Dialect[dialect].format_time( - seq_get(args, 1) - or ( - Dialect[dialect].TIME_FORMAT if default is True else default or None - ) - ), - ) - - return _builder - - -def time_format( - dialect: DialectType = None, -) -> t.Callable[[Generator, exp.UnixToStr | exp.StrToUnix], t.Optional[str]]: - def _time_format( - self: Generator, expression: exp.UnixToStr | exp.StrToUnix - ) -> t.Optional[str]: - """ - Returns the time format for a given expression, unless it's equivalent - to the default time format of the dialect of interest. - """ - time_format = self.format_time(expression) - return ( - time_format - if time_format != Dialect.get_or_raise(dialect).TIME_FORMAT - else None - ) - - return _time_format - - -def build_date_delta( - exp_class: t.Type[E], - unit_mapping: t.Optional[t.Dict[str, str]] = None, - default_unit: t.Optional[str] = "DAY", - supports_timezone: bool = False, -) -> t.Callable[[t.List], E]: - def _builder(args: t.List) -> E: - unit_based = len(args) >= 3 - has_timezone = len(args) == 4 - this = args[2] if unit_based else seq_get(args, 0) - unit = None - if unit_based or default_unit: - unit = args[0] if unit_based else exp.Literal.string(default_unit) - unit = ( - exp.var(unit_mapping.get(unit.name.lower(), unit.name)) - if unit_mapping - else unit - ) - expression = exp_class(this=this, expression=seq_get(args, 1), unit=unit) - if supports_timezone and has_timezone: - expression.set("zone", args[-1]) - return expression - - return _builder - - -def build_date_delta_with_interval( - expression_class: t.Type[E], -) -> t.Callable[[t.List], t.Optional[E]]: - def _builder(args: t.List) -> t.Optional[E]: - if len(args) < 2: - return None - - interval = args[1] - - if not isinstance(interval, exp.Interval): - raise ParseError(f"INTERVAL expression expected but got '{interval}'") - - return expression_class( - this=args[0], expression=interval.this, unit=unit_to_str(interval) - ) - - return _builder - - -def date_trunc_to_time(args: t.List) -> exp.DateTrunc | exp.TimestampTrunc: - unit = seq_get(args, 0) - this = seq_get(args, 1) - - if isinstance(this, exp.Cast) and this.is_type("date"): - return exp.DateTrunc(unit=unit, this=this) - return exp.TimestampTrunc(this=this, unit=unit) - - -def date_add_interval_sql( - data_type: str, kind: str -) -> t.Callable[[Generator, exp.Expression], str]: - def func(self: Generator, expression: exp.Expression) -> str: - this = self.sql(expression, "this") - interval = exp.Interval( - this=expression.expression, unit=unit_to_var(expression) - ) - return f"{data_type}_{kind}({this}, {self.sql(interval)})" - - return func - - -def timestamptrunc_sql( - func: str = "DATE_TRUNC", zone: bool = False -) -> t.Callable[[Generator, exp.TimestampTrunc], str]: - def _timestamptrunc_sql(self: Generator, expression: exp.TimestampTrunc) -> str: - args = [unit_to_str(expression), expression.this] - if zone: - args.append(expression.args.get("zone")) - return self.func(func, *args) - - return _timestamptrunc_sql - - -def no_timestamp_sql(self: Generator, expression: exp.Timestamp) -> str: - zone = expression.args.get("zone") - if not zone: - from sqlglot.optimizer.annotate_types import annotate_types - - target_type = ( - annotate_types(expression, dialect=self.dialect).type - or exp.DataType.Type.TIMESTAMP - ) - return self.sql(exp.cast(expression.this, target_type)) - if zone.name.lower() in TIMEZONES: - return self.sql( - exp.AtTimeZone( - this=exp.cast(expression.this, exp.DataType.Type.TIMESTAMP), - zone=zone, - ) - ) - return self.func("TIMESTAMP", expression.this, zone) - - -def no_time_sql(self: Generator, expression: exp.Time) -> str: - # Transpile BQ's TIME(timestamp, zone) to CAST(TIMESTAMPTZ AT TIME ZONE AS TIME) - this = exp.cast(expression.this, exp.DataType.Type.TIMESTAMPTZ) - expr = exp.cast( - exp.AtTimeZone(this=this, zone=expression.args.get("zone")), - exp.DataType.Type.TIME, - ) - return self.sql(expr) - - -def no_datetime_sql(self: Generator, expression: exp.Datetime) -> str: - this = expression.this - expr = expression.expression - - if expr.name.lower() in TIMEZONES: - # Transpile BQ's DATETIME(timestamp, zone) to CAST(TIMESTAMPTZ AT TIME ZONE AS TIMESTAMP) - this = exp.cast(this, exp.DataType.Type.TIMESTAMPTZ) - this = exp.cast( - exp.AtTimeZone(this=this, zone=expr), exp.DataType.Type.TIMESTAMP - ) - return self.sql(this) - - this = exp.cast(this, exp.DataType.Type.DATE) - expr = exp.cast(expr, exp.DataType.Type.TIME) - - return self.sql( - exp.cast(exp.Add(this=this, expression=expr), exp.DataType.Type.TIMESTAMP) - ) - - -def left_to_substring_sql(self: Generator, expression: exp.Left) -> str: - return self.sql( - exp.Substring( - this=expression.this, - start=exp.Literal.number(1), - length=expression.expression, - ) - ) - - -def right_to_substring_sql(self: Generator, expression: exp.Left) -> str: - return self.sql( - exp.Substring( - this=expression.this, - start=exp.Length(this=expression.this) - - exp.paren(expression.expression - 1), - ) - ) - - -def timestrtotime_sql( - self: Generator, - expression: exp.TimeStrToTime, - include_precision: bool = False, -) -> str: - datatype = exp.DataType.build( - exp.DataType.Type.TIMESTAMPTZ - if expression.args.get("zone") - else exp.DataType.Type.TIMESTAMP - ) - - if isinstance(expression.this, exp.Literal) and include_precision: - precision = subsecond_precision(expression.this.name) - if precision > 0: - datatype = exp.DataType.build( - datatype.this, - expressions=[exp.DataTypeParam(this=exp.Literal.number(precision))], - ) - - return self.sql(exp.cast(expression.this, datatype, dialect=self.dialect)) - - -def datestrtodate_sql(self: Generator, expression: exp.DateStrToDate) -> str: - return self.sql(exp.cast(expression.this, exp.DataType.Type.DATE)) - - -# Used for Presto and Duckdb which use functions that don't support charset, and assume utf-8 -def encode_decode_sql( - self: Generator, expression: exp.Expression, name: str, replace: bool = True -) -> str: - charset = expression.args.get("charset") - if charset and charset.name.lower() != "utf-8": - self.unsupported(f"Expected utf-8 character set, got {charset}.") - - return self.func( - name, expression.this, expression.args.get("replace") if replace else None - ) - - -def min_or_least(self: Generator, expression: exp.Min) -> str: - name = "LEAST" if expression.expressions else "MIN" - return rename_func(name)(self, expression) - - -def max_or_greatest(self: Generator, expression: exp.Max) -> str: - name = "GREATEST" if expression.expressions else "MAX" - return rename_func(name)(self, expression) - - -def count_if_to_sum(self: Generator, expression: exp.CountIf) -> str: - cond = expression.this - - if isinstance(expression.this, exp.Distinct): - cond = expression.this.expressions[0] - self.unsupported("DISTINCT is not supported when converting COUNT_IF to SUM") - - return self.func("sum", exp.func("if", cond, 1, 0)) - - -def trim_sql(self: Generator, expression: exp.Trim, default_trim_type: str = "") -> str: - target = self.sql(expression, "this") - trim_type = self.sql(expression, "position") or default_trim_type - remove_chars = self.sql(expression, "expression") - collation = self.sql(expression, "collation") - - # Use TRIM/LTRIM/RTRIM syntax if the expression isn't database-specific - if not remove_chars: - return self.trim_sql(expression) - - trim_type = f"{trim_type} " if trim_type else "" - remove_chars = f"{remove_chars} " if remove_chars else "" - from_part = "FROM " if trim_type or remove_chars else "" - collation = f" COLLATE {collation}" if collation else "" - return f"TRIM({trim_type}{remove_chars}{from_part}{target}{collation})" - - -def str_to_time_sql(self: Generator, expression: exp.Expression) -> str: - return self.func("STRPTIME", expression.this, self.format_time(expression)) - - -def concat_to_dpipe_sql(self: Generator, expression: exp.Concat) -> str: - return self.sql( - reduce(lambda x, y: exp.DPipe(this=x, expression=y), expression.expressions) - ) - - -def concat_ws_to_dpipe_sql(self: Generator, expression: exp.ConcatWs) -> str: - delim, *rest_args = expression.expressions - return self.sql( - reduce( - lambda x, y: exp.DPipe( - this=x, expression=exp.DPipe(this=delim, expression=y) - ), - rest_args, - ) - ) - - -@unsupported_args("position", "occurrence", "parameters") -def regexp_extract_sql( - self: Generator, expression: exp.RegexpExtract | exp.RegexpExtractAll -) -> str: - group = expression.args.get("group") - - # Do not render group if it's the default value for this dialect - if group and group.name == str(self.dialect.REGEXP_EXTRACT_DEFAULT_GROUP): - group = None - - return self.func( - expression.sql_name(), expression.this, expression.expression, group - ) - - -@unsupported_args("position", "occurrence", "modifiers") -def regexp_replace_sql(self: Generator, expression: exp.RegexpReplace) -> str: - return self.func( - "REGEXP_REPLACE", - expression.this, - expression.expression, - expression.args["replacement"], - ) - - -def pivot_column_names( - aggregations: t.List[exp.Expression], dialect: DialectType -) -> t.List[str]: - names = [] - for agg in aggregations: - if isinstance(agg, exp.Alias): - names.append(agg.alias) - else: - """ - This case corresponds to aggregations without aliases being used as suffixes - (e.g. col_avg(foo)). We need to unquote identifiers because they're going to - be quoted in the base parser's `_parse_pivot` method, due to `to_identifier`. - Otherwise, we'd end up with `col_avg(`foo`)` (notice the double quotes). - """ - agg_all_unquoted = agg.transform( - lambda node: ( - exp.Identifier(this=node.name, quoted=False) - if isinstance(node, exp.Identifier) - else node - ) - ) - names.append( - agg_all_unquoted.sql(dialect=dialect, normalize_functions="lower") - ) - - return names - - -def binary_from_function(expr_type: t.Type[B]) -> t.Callable[[t.List], B]: - return lambda args: expr_type(this=seq_get(args, 0), expression=seq_get(args, 1)) - - -# Used to represent DATE_TRUNC in Doris, Postgres and Starrocks dialects -def build_timestamp_trunc(args: t.List) -> exp.TimestampTrunc: - return exp.TimestampTrunc(this=seq_get(args, 1), unit=seq_get(args, 0)) - - -def any_value_to_max_sql(self: Generator, expression: exp.AnyValue) -> str: - return self.func("MAX", expression.this) - - -def bool_xor_sql(self: Generator, expression: exp.Xor) -> str: - a = self.sql(expression.left) - b = self.sql(expression.right) - return f"({a} AND (NOT {b})) OR ((NOT {a}) AND {b})" - - -def is_parse_json(expression: exp.Expression) -> bool: - return isinstance(expression, exp.ParseJSON) or ( - isinstance(expression, exp.Cast) and expression.is_type("json") - ) - - -def isnull_to_is_null(args: t.List) -> exp.Expression: - return exp.Paren(this=exp.Is(this=seq_get(args, 0), expression=exp.null())) - - -def generatedasidentitycolumnconstraint_sql( - self: Generator, expression: exp.GeneratedAsIdentityColumnConstraint -) -> str: - start = self.sql(expression, "start") or "1" - increment = self.sql(expression, "increment") or "1" - return f"IDENTITY({start}, {increment})" - - -def arg_max_or_min_no_count( - name: str, -) -> t.Callable[[Generator, exp.ArgMax | exp.ArgMin], str]: - @unsupported_args("count") - def _arg_max_or_min_sql( - self: Generator, expression: exp.ArgMax | exp.ArgMin - ) -> str: - return self.func(name, expression.this, expression.expression) - - return _arg_max_or_min_sql - - -def ts_or_ds_add_cast(expression: exp.TsOrDsAdd) -> exp.TsOrDsAdd: - this = expression.this.copy() - - return_type = expression.return_type - if return_type.is_type(exp.DataType.Type.DATE): - # If we need to cast to a DATE, we cast to TIMESTAMP first to make sure we - # can truncate timestamp strings, because some dialects can't cast them to DATE - this = exp.cast(this, exp.DataType.Type.TIMESTAMP) - - expression.this.replace(exp.cast(this, return_type)) - return expression - - -def date_delta_sql( - name: str, cast: bool = False -) -> t.Callable[[Generator, DATE_ADD_OR_DIFF], str]: - def _delta_sql(self: Generator, expression: DATE_ADD_OR_DIFF) -> str: - if cast and isinstance(expression, exp.TsOrDsAdd): - expression = ts_or_ds_add_cast(expression) - - return self.func( - name, - unit_to_var(expression), - expression.expression, - expression.this, - ) - - return _delta_sql - - -def date_delta_to_binary_interval_op( - cast: bool = True, -) -> t.Callable[[Generator, DATETIME_DELTA], str]: - def date_delta_to_binary_interval_op_sql( - self: Generator, expression: DATETIME_DELTA - ) -> str: - this = expression.this - unit = unit_to_var(expression) - op = "+" if isinstance(expression, DATETIME_ADD) else "-" - - to_type: t.Optional[exp.DATA_TYPE] = None - if cast: - if isinstance(expression, exp.TsOrDsAdd): - to_type = expression.return_type - elif this.is_string: - # Cast string literals (i.e function parameters) to the appropriate type for +/- interval to work - to_type = ( - exp.DataType.Type.DATETIME - if isinstance(expression, (exp.DatetimeAdd, exp.DatetimeSub)) - else exp.DataType.Type.DATE - ) - - this = exp.cast(this, to_type) if to_type else this - - expr = expression.expression - interval = ( - expr - if isinstance(expr, exp.Interval) - else exp.Interval(this=expr, unit=unit) - ) - - return f"{self.sql(this)} {op} {self.sql(interval)}" - - return date_delta_to_binary_interval_op_sql - - -def unit_to_str( - expression: exp.Expression, default: str = "DAY" -) -> t.Optional[exp.Expression]: - unit = expression.args.get("unit") - if not unit: - return exp.Literal.string(default) if default else None - - if isinstance(unit, exp.Placeholder) or type(unit) not in (exp.Var, exp.Literal): - return unit - - return exp.Literal.string(unit.name) - - -def unit_to_var( - expression: exp.Expression, default: str = "DAY" -) -> t.Optional[exp.Expression]: - unit = expression.args.get("unit") - - if isinstance(unit, (exp.Var, exp.Placeholder, exp.WeekStart, exp.Column)): - return unit - - value = unit.name if unit else default - return exp.Var(this=value) if value else None - - -@t.overload -def map_date_part(part: exp.Expression, dialect: DialectType = Dialect) -> exp.Var: - pass - - -@t.overload -def map_date_part( - part: t.Optional[exp.Expression], dialect: DialectType = Dialect -) -> t.Optional[exp.Expression]: - pass - - -def map_date_part(part, dialect: DialectType = Dialect): - mapped = ( - Dialect.get_or_raise(dialect).DATE_PART_MAPPING.get(part.name.upper()) - if part and not (isinstance(part, exp.Column) and len(part.parts) != 1) - else None - ) - if mapped: - return exp.Literal.string(mapped) if part.is_string else exp.var(mapped) - - return part - - -def no_last_day_sql(self: Generator, expression: exp.LastDay) -> str: - trunc_curr_date = exp.func("date_trunc", "month", expression.this) - plus_one_month = exp.func("date_add", trunc_curr_date, 1, "month") - minus_one_day = exp.func("date_sub", plus_one_month, 1, "day") - - return self.sql(exp.cast(minus_one_day, exp.DataType.Type.DATE)) - - -def merge_without_target_sql(self: Generator, expression: exp.Merge) -> str: - """Remove table refs from columns in when statements.""" - alias = expression.this.args.get("alias") - - def normalize(identifier: t.Optional[exp.Identifier]) -> t.Optional[str]: - return ( - self.dialect.normalize_identifier(identifier).name if identifier else None - ) - - targets = {normalize(expression.this.this)} - - if alias: - targets.add(normalize(alias.this)) - - for when in expression.args["whens"].expressions: - # only remove the target table names from certain parts of WHEN MATCHED / WHEN NOT MATCHED - # they are still valid in the , the right hand side of each UPDATE and the VALUES part - # (not the column list) of the INSERT - then: exp.Insert | exp.Update | None = when.args.get("then") - if then: - if isinstance(then, exp.Update): - for equals in then.find_all(exp.EQ): - equal_lhs = equals.this - if ( - isinstance(equal_lhs, exp.Column) - and normalize(equal_lhs.args.get("table")) in targets - ): - equal_lhs.replace(exp.column(equal_lhs.this)) - if isinstance(then, exp.Insert): - column_list = then.this - if isinstance(column_list, exp.Tuple): - for column in column_list.expressions: - if normalize(column.args.get("table")) in targets: - column.replace(exp.column(column.this)) - - return self.merge_sql(expression) - - -def build_json_extract_path( - expr_type: t.Type[F], - zero_based_indexing: bool = True, - arrow_req_json_type: bool = False, - json_type: t.Optional[str] = None, -) -> t.Callable[[t.List], F]: - def _builder(args: t.List) -> F: - segments: t.List[exp.JSONPathPart] = [exp.JSONPathRoot()] - for arg in args[1:]: - if not isinstance(arg, exp.Literal): - # We use the fallback parser because we can't really transpile non-literals safely - return expr_type.from_arg_list(args) - - text = arg.name - if is_int(text) and (not arrow_req_json_type or not arg.is_string): - index = int(text) - segments.append( - exp.JSONPathSubscript( - this=index if zero_based_indexing else index - 1 - ) - ) - else: - segments.append(exp.JSONPathKey(this=text)) - - # This is done to avoid failing in the expression validator due to the arg count - del args[2:] - kwargs = { - "this": seq_get(args, 0), - "expression": exp.JSONPath(expressions=segments), - } - - is_jsonb = issubclass(expr_type, (exp.JSONBExtract, exp.JSONBExtractScalar)) - if not is_jsonb: - kwargs["only_json_types"] = arrow_req_json_type - - if json_type is not None: - kwargs["json_type"] = json_type - - return expr_type(**kwargs) - - return _builder - - -def json_extract_segments( - name: str, quoted_index: bool = True, op: t.Optional[str] = None -) -> t.Callable[[Generator, JSON_EXTRACT_TYPE], str]: - def _json_extract_segments(self: Generator, expression: JSON_EXTRACT_TYPE) -> str: - path = expression.expression - if not isinstance(path, exp.JSONPath): - return rename_func(name)(self, expression) - - escape = path.args.get("escape") - - segments = [] - for segment in path.expressions: - path = self.sql(segment) - if path: - if isinstance(segment, exp.JSONPathPart) and ( - quoted_index or not isinstance(segment, exp.JSONPathSubscript) - ): - if escape: - path = self.escape_str(path) - - path = f"{self.dialect.QUOTE_START}{path}{self.dialect.QUOTE_END}" - - segments.append(path) - - if op: - return f" {op} ".join([self.sql(expression.this), *segments]) - return self.func(name, expression.this, *segments) - - return _json_extract_segments - - -def json_path_key_only_name(self: Generator, expression: exp.JSONPathKey) -> str: - if isinstance(expression.this, exp.JSONPathWildcard): - self.unsupported("Unsupported wildcard in JSONPathKey expression") - - return expression.name - - -def filter_array_using_unnest( - self: Generator, expression: exp.ArrayFilter | exp.ArrayRemove -) -> str: - cond = expression.expression - if isinstance(cond, exp.Lambda) and len(cond.expressions) == 1: - alias = cond.expressions[0] - cond = cond.this - elif isinstance(cond, exp.Predicate): - alias = "_u" - elif isinstance(expression, exp.ArrayRemove): - alias = "_u" - cond = exp.NEQ(this=alias, expression=expression.expression) - else: - self.unsupported("Unsupported filter condition") - return "" - - unnest = exp.Unnest(expressions=[expression.this]) - filtered = ( - exp.select(alias).from_(exp.alias_(unnest, None, table=[alias])).where(cond) - ) - return self.sql(exp.Array(expressions=[filtered])) - - -def remove_from_array_using_filter(self: Generator, expression: exp.ArrayRemove) -> str: - lambda_id = exp.to_identifier("_u") - cond = exp.NEQ(this=lambda_id, expression=expression.expression) - return self.sql( - exp.ArrayFilter( - this=expression.this, - expression=exp.Lambda(this=cond, expressions=[lambda_id]), - ) - ) - - -def to_number_with_nls_param(self: Generator, expression: exp.ToNumber) -> str: - return self.func( - "TO_NUMBER", - expression.this, - expression.args.get("format"), - expression.args.get("nlsparam"), - ) - - -def build_default_decimal_type( - precision: t.Optional[int] = None, scale: t.Optional[int] = None -) -> t.Callable[[exp.DataType], exp.DataType]: - def _builder(dtype: exp.DataType) -> exp.DataType: - if dtype.expressions or precision is None: - return dtype - - params = f"{precision}{f', {scale}' if scale is not None else ''}" - return exp.DataType.build(f"DECIMAL({params})") - - return _builder - - -def build_timestamp_from_parts(args: t.List) -> exp.Func: - if len(args) == 2: - # Other dialects don't have the TIMESTAMP_FROM_PARTS(date, time) concept, - # so we parse this into Anonymous for now instead of introducing complexity - return exp.Anonymous(this="TIMESTAMP_FROM_PARTS", expressions=args) - - return exp.TimestampFromParts.from_arg_list(args) - - -def sha256_sql(self: Generator, expression: exp.SHA2) -> str: - return self.func(f"SHA{expression.text('length') or '256'}", expression.this) - - -def sha2_digest_sql(self: Generator, expression: exp.SHA2Digest) -> str: - return self.func(f"SHA{expression.text('length') or '256'}", expression.this) - - -def sequence_sql( - self: Generator, expression: exp.GenerateSeries | exp.GenerateDateArray -) -> str: - start = expression.args.get("start") - end = expression.args.get("end") - step = expression.args.get("step") - - if isinstance(start, exp.Cast): - target_type = start.to - elif isinstance(end, exp.Cast): - target_type = end.to - else: - target_type = None - - if start and end: - if target_type and target_type.is_type("date", "timestamp"): - if isinstance(start, exp.Cast) and target_type is start.to: - end = exp.cast(end, target_type) - else: - start = exp.cast(start, target_type) - - if expression.args.get("is_end_exclusive"): - step_value = step or exp.Literal.number(1) - end = exp.paren(exp.Sub(this=end, expression=step_value), copy=False) - - sequence_call = exp.Anonymous( - this="SEQUENCE", expressions=[e for e in (start, end, step) if e] - ) - zero = exp.Literal.number(0) - should_return_empty = exp.or_( - exp.EQ(this=step_value.copy(), expression=zero.copy()), - exp.and_( - exp.GT(this=step_value.copy(), expression=zero.copy()), - exp.GTE(this=start.copy(), expression=end.copy()), - ), - exp.and_( - exp.LT(this=step_value.copy(), expression=zero.copy()), - exp.LTE(this=start.copy(), expression=end.copy()), - ), - ) - empty_array_or_sequence = exp.If( - this=should_return_empty, - true=exp.Array(expressions=[]), - false=sequence_call, - ) - return self.sql(self._simplify_unless_literal(empty_array_or_sequence)) - - return self.func("SEQUENCE", start, end, step) - - -def build_like( - expr_type: t.Type[E], not_like: bool = False -) -> t.Callable[[t.List], exp.Expression]: - def _builder(args: t.List) -> exp.Expression: - like_expr: exp.Expression = expr_type( - this=seq_get(args, 0), expression=seq_get(args, 1) - ) - - if escape := seq_get(args, 2): - like_expr = exp.Escape(this=like_expr, expression=escape) - - if not_like: - like_expr = exp.Not(this=like_expr) - - return like_expr - - return _builder - - -def build_regexp_extract(expr_type: t.Type[E]) -> t.Callable[[t.List, Dialect], E]: - def _builder(args: t.List, dialect: Dialect) -> E: - # The "position" argument specifies the index of the string character to start matching from. - # `null_if_pos_overflow` reflects the dialect's behavior when position is greater than the string - # length. If true, returns NULL. If false, returns an empty string. `null_if_pos_overflow` is - # only needed for exp.RegexpExtract - exp.RegexpExtractAll always returns an empty array if - # position overflows. - return expr_type( - this=seq_get(args, 0), - expression=seq_get(args, 1), - group=seq_get(args, 2) - or exp.Literal.number(dialect.REGEXP_EXTRACT_DEFAULT_GROUP), - parameters=seq_get(args, 3), - **( - { - "null_if_pos_overflow": dialect.REGEXP_EXTRACT_POSITION_OVERFLOW_RETURNS_NULL - } - if expr_type is exp.RegexpExtract - else {} - ), - ) - - return _builder - - -def explode_to_unnest_sql(self: Generator, expression: exp.Lateral) -> str: - if isinstance(expression.this, exp.Explode): - return self.sql( - exp.Join( - this=exp.Unnest( - expressions=[expression.this.this], - alias=expression.args.get("alias"), - offset=isinstance(expression.this, exp.Posexplode), - ), - kind="cross", - ) - ) - return self.lateral_sql(expression) - - -def timestampdiff_sql( - self: Generator, expression: exp.DatetimeDiff | exp.TimestampDiff -) -> str: - return self.func( - "TIMESTAMPDIFF", expression.unit, expression.expression, expression.this - ) - - -def no_make_interval_sql( - self: Generator, expression: exp.MakeInterval, sep: str = ", " -) -> str: - args = [] - for unit, value in expression.args.items(): - if isinstance(value, exp.Kwarg): - value = value.expression - - args.append(f"{value} {unit}") - - return f"INTERVAL '{self.format_args(*args, sep=sep)}'" - - -def length_or_char_length_sql(self: Generator, expression: exp.Length) -> str: - length_func = "LENGTH" if expression.args.get("binary") else "CHAR_LENGTH" - return self.func(length_func, expression.this) - - -def groupconcat_sql( - self: Generator, - expression: exp.GroupConcat, - func_name="LISTAGG", - sep: t.Optional[str] = ",", - within_group: bool = True, - on_overflow: bool = False, -) -> str: - this = expression.this - separator = self.sql( - expression.args.get("separator") or (exp.Literal.string(sep) if sep else None) - ) - - on_overflow_sql = self.sql(expression, "on_overflow") - on_overflow_sql = ( - f" ON OVERFLOW {on_overflow_sql}" if (on_overflow and on_overflow_sql) else "" - ) - - if isinstance(this, exp.Limit) and this.this: - limit = this - this = limit.this.pop() - else: - limit = None - - order = this.find(exp.Order) - - if order and order.this: - this = order.this.pop() - - args = self.format_args( - this, f"{separator}{on_overflow_sql}" if separator or on_overflow_sql else None - ) - - listagg: exp.Expression = exp.Anonymous(this=func_name, expressions=[args]) - - modifiers = self.sql(limit) - - if order: - if within_group: - listagg = exp.WithinGroup(this=listagg, expression=order) - else: - modifiers = f"{self.sql(order)}{modifiers}" - - if modifiers: - listagg.set("expressions", [f"{args}{modifiers}"]) - - return self.sql(listagg) - - -def build_timetostr_or_tochar( - args: t.List, dialect: DialectType -) -> exp.TimeToStr | exp.ToChar: - if len(args) == 2: - this = args[0] - if not this.type: - from sqlglot.optimizer.annotate_types import annotate_types - - annotate_types(this, dialect=dialect) - - if this.is_type(*exp.DataType.TEMPORAL_TYPES): - dialect_name = dialect.__class__.__name__.lower() - return build_formatted_time(exp.TimeToStr, dialect_name, default=True)(args) - - return exp.ToChar.from_arg_list(args) - - -def build_replace_with_optional_replacement(args: t.List) -> exp.Replace: - return exp.Replace( - this=seq_get(args, 0), - expression=seq_get(args, 1), - replacement=seq_get(args, 2) or exp.Literal.string(""), - ) - - -def regexp_replace_global_modifier( - expression: exp.RegexpReplace, -) -> exp.Expression | None: - modifiers = expression.args.get("modifiers") - single_replace = expression.args.get("single_replace") - occurrence = expression.args.get("occurrence") - - if not single_replace and ( - not occurrence or (occurrence.is_int and occurrence.to_py() == 0) - ): - if not modifiers or modifiers.is_string: - # Append 'g' to the modifiers if they are not provided since - # the semantics of REGEXP_REPLACE from the input dialect - # is to replace all occurrences of the pattern. - value = "" if not modifiers else modifiers.name - modifiers = exp.Literal.string(value + "g") - - return modifiers diff --git a/third_party/bigframes_vendored/sqlglot/diff.py b/third_party/bigframes_vendored/sqlglot/diff.py deleted file mode 100644 index e0e4eb4b0be..00000000000 --- a/third_party/bigframes_vendored/sqlglot/diff.py +++ /dev/null @@ -1,513 +0,0 @@ -# Contains code from https://github.com/tobymao/sqlglot/blob/v28.5.0/sqlglot/diff.py - -""" -.. include:: ../posts/sql_diff.md - ----- -""" - -from __future__ import annotations - -import typing as t -from collections import defaultdict -from dataclasses import dataclass -from heapq import heappop, heappush -from itertools import chain - -from bigframes_vendored.sqlglot import Dialect -from bigframes_vendored.sqlglot import expressions as exp -from bigframes_vendored.sqlglot.helper import seq_get - -if t.TYPE_CHECKING: - from bigframes_vendored.sqlglot.dialects.dialect import DialectType - - -@dataclass(frozen=True) -class Insert: - """Indicates that a new node has been inserted""" - - expression: exp.Expression - - -@dataclass(frozen=True) -class Remove: - """Indicates that an existing node has been removed""" - - expression: exp.Expression - - -@dataclass(frozen=True) -class Move: - """Indicates that an existing node's position within the tree has changed""" - - source: exp.Expression - target: exp.Expression - - -@dataclass(frozen=True) -class Update: - """Indicates that an existing node has been updated""" - - source: exp.Expression - target: exp.Expression - - -@dataclass(frozen=True) -class Keep: - """Indicates that an existing node hasn't been changed""" - - source: exp.Expression - target: exp.Expression - - -if t.TYPE_CHECKING: - from bigframes_vendored.sqlglot._typing import T - - Edit = t.Union[Insert, Remove, Move, Update, Keep] - - -def diff( - source: exp.Expression, - target: exp.Expression, - matchings: t.List[t.Tuple[exp.Expression, exp.Expression]] | None = None, - delta_only: bool = False, - **kwargs: t.Any, -) -> t.List[Edit]: - """ - Returns the list of changes between the source and the target expressions. - - Examples: - >>> diff(parse_one("a + b"), parse_one("a + c")) - [ - Remove(expression=(COLUMN this: (IDENTIFIER this: b, quoted: False))), - Insert(expression=(COLUMN this: (IDENTIFIER this: c, quoted: False))), - Keep( - source=(ADD this: ...), - target=(ADD this: ...) - ), - Keep( - source=(COLUMN this: (IDENTIFIER this: a, quoted: False)), - target=(COLUMN this: (IDENTIFIER this: a, quoted: False)) - ), - ] - - Args: - source: the source expression. - target: the target expression against which the diff should be calculated. - matchings: the list of pre-matched node pairs which is used to help the algorithm's - heuristics produce better results for subtrees that are known by a caller to be matching. - Note: expression references in this list must refer to the same node objects that are - referenced in the source / target trees. - delta_only: excludes all `Keep` nodes from the diff. - kwargs: additional arguments to pass to the ChangeDistiller instance. - - Returns: - the list of Insert, Remove, Move, Update and Keep objects for each node in the source and the - target expression trees. This list represents a sequence of steps needed to transform the source - expression tree into the target one. - """ - matchings = matchings or [] - - def compute_node_mappings( - old_nodes: tuple[exp.Expression, ...], new_nodes: tuple[exp.Expression, ...] - ) -> t.Dict[int, exp.Expression]: - node_mapping = {} - for old_node, new_node in zip(reversed(old_nodes), reversed(new_nodes)): - new_node._hash = hash(new_node) - node_mapping[id(old_node)] = new_node - - return node_mapping - - # if the source and target have any shared objects, that means there's an issue with the ast - # the algorithm won't work because the parent / hierarchies will be inaccurate - source_nodes = tuple(source.walk()) - target_nodes = tuple(target.walk()) - source_ids = {id(n) for n in source_nodes} - target_ids = {id(n) for n in target_nodes} - - copy = ( - len(source_nodes) != len(source_ids) - or len(target_nodes) != len(target_ids) - or source_ids & target_ids - ) - - source_copy = source.copy() if copy else source - target_copy = target.copy() if copy else target - - try: - # We cache the hash of each new node here to speed up equality comparisons. If the input - # trees aren't copied, these hashes will be evicted before returning the edit script. - if copy and matchings: - source_mapping = compute_node_mappings( - source_nodes, tuple(source_copy.walk()) - ) - target_mapping = compute_node_mappings( - target_nodes, tuple(target_copy.walk()) - ) - matchings = [ - (source_mapping[id(s)], target_mapping[id(t)]) for s, t in matchings - ] - else: - for node in chain(reversed(source_nodes), reversed(target_nodes)): - node._hash = hash(node) - - edit_script = ChangeDistiller(**kwargs).diff( - source_copy, - target_copy, - matchings=matchings, - delta_only=delta_only, - ) - finally: - if not copy: - for node in chain(source_nodes, target_nodes): - node._hash = None - - return edit_script - - -# The expression types for which Update edits are allowed. -UPDATABLE_EXPRESSION_TYPES = ( - exp.Alias, - exp.Boolean, - exp.Column, - exp.DataType, - exp.Lambda, - exp.Literal, - exp.Table, - exp.Window, -) - -IGNORED_LEAF_EXPRESSION_TYPES = (exp.Identifier,) - - -class ChangeDistiller: - """ - The implementation of the Change Distiller algorithm described by Beat Fluri and Martin Pinzger in - their paper https://ieeexplore.ieee.org/document/4339230, which in turn is based on the algorithm by - Chawathe et al. described in http://ilpubs.stanford.edu:8090/115/1/1995-46.pdf. - """ - - def __init__( - self, f: float = 0.6, t: float = 0.6, dialect: DialectType = None - ) -> None: - self.f = f - self.t = t - self._sql_generator = Dialect.get_or_raise(dialect).generator() - - def diff( - self, - source: exp.Expression, - target: exp.Expression, - matchings: t.List[t.Tuple[exp.Expression, exp.Expression]] | None = None, - delta_only: bool = False, - ) -> t.List[Edit]: - matchings = matchings or [] - pre_matched_nodes = {id(s): id(t) for s, t in matchings} - - self._source = source - self._target = target - self._source_index = { - id(n): n - for n in self._source.bfs() - if not isinstance(n, IGNORED_LEAF_EXPRESSION_TYPES) - } - self._target_index = { - id(n): n - for n in self._target.bfs() - if not isinstance(n, IGNORED_LEAF_EXPRESSION_TYPES) - } - self._unmatched_source_nodes = set(self._source_index) - set(pre_matched_nodes) - self._unmatched_target_nodes = set(self._target_index) - set( - pre_matched_nodes.values() - ) - self._bigram_histo_cache: t.Dict[int, t.DefaultDict[str, int]] = {} - - matching_set = self._compute_matching_set() | set(pre_matched_nodes.items()) - return self._generate_edit_script(dict(matching_set), delta_only) - - def _generate_edit_script( - self, matchings: t.Dict[int, int], delta_only: bool - ) -> t.List[Edit]: - edit_script: t.List[Edit] = [] - for removed_node_id in self._unmatched_source_nodes: - edit_script.append(Remove(self._source_index[removed_node_id])) - for inserted_node_id in self._unmatched_target_nodes: - edit_script.append(Insert(self._target_index[inserted_node_id])) - for kept_source_node_id, kept_target_node_id in matchings.items(): - source_node = self._source_index[kept_source_node_id] - target_node = self._target_index[kept_target_node_id] - - identical_nodes = source_node == target_node - - if ( - not isinstance(source_node, UPDATABLE_EXPRESSION_TYPES) - or identical_nodes - ): - if identical_nodes: - source_parent = source_node.parent - target_parent = target_node.parent - - if ( - (source_parent and not target_parent) - or (not source_parent and target_parent) - or ( - source_parent - and target_parent - and matchings.get(id(source_parent)) != id(target_parent) - ) - ): - edit_script.append(Move(source=source_node, target=target_node)) - else: - edit_script.extend( - self._generate_move_edits(source_node, target_node, matchings) - ) - - source_non_expression_leaves = dict( - _get_non_expression_leaves(source_node) - ) - target_non_expression_leaves = dict( - _get_non_expression_leaves(target_node) - ) - - if source_non_expression_leaves != target_non_expression_leaves: - edit_script.append(Update(source_node, target_node)) - elif not delta_only: - edit_script.append(Keep(source_node, target_node)) - else: - edit_script.append(Update(source_node, target_node)) - - return edit_script - - def _generate_move_edits( - self, - source: exp.Expression, - target: exp.Expression, - matchings: t.Dict[int, int], - ) -> t.List[Move]: - source_args = [id(e) for e in _expression_only_args(source)] - target_args = [id(e) for e in _expression_only_args(target)] - - args_lcs = set( - _lcs( - source_args, - target_args, - lambda ll, r: matchings.get(t.cast(int, ll)) == r, - ) - ) - - move_edits = [] - for a in source_args: - if a not in args_lcs and a not in self._unmatched_source_nodes: - move_edits.append( - Move( - source=self._source_index[a], - target=self._target_index[matchings[a]], - ) - ) - - return move_edits - - def _compute_matching_set(self) -> t.Set[t.Tuple[int, int]]: - leaves_matching_set = self._compute_leaf_matching_set() - matching_set = leaves_matching_set.copy() - - ordered_unmatched_source_nodes = { - id(n): None - for n in self._source.bfs() - if id(n) in self._unmatched_source_nodes - } - ordered_unmatched_target_nodes = { - id(n): None - for n in self._target.bfs() - if id(n) in self._unmatched_target_nodes - } - - for source_node_id in ordered_unmatched_source_nodes: - for target_node_id in ordered_unmatched_target_nodes: - source_node = self._source_index[source_node_id] - target_node = self._target_index[target_node_id] - if _is_same_type(source_node, target_node): - source_leaf_ids = { - id(ll) for ll in _get_expression_leaves(source_node) - } - target_leaf_ids = { - id(ll) for ll in _get_expression_leaves(target_node) - } - - max_leaves_num = max(len(source_leaf_ids), len(target_leaf_ids)) - if max_leaves_num: - common_leaves_num = sum( - 1 if s in source_leaf_ids and t in target_leaf_ids else 0 - for s, t in leaves_matching_set - ) - leaf_similarity_score = common_leaves_num / max_leaves_num - else: - leaf_similarity_score = 0.0 - - adjusted_t = ( - self.t - if min(len(source_leaf_ids), len(target_leaf_ids)) > 4 - else 0.4 - ) - - if leaf_similarity_score >= 0.8 or ( - leaf_similarity_score >= adjusted_t - and self._dice_coefficient(source_node, target_node) >= self.f - ): - matching_set.add((source_node_id, target_node_id)) - self._unmatched_source_nodes.remove(source_node_id) - self._unmatched_target_nodes.remove(target_node_id) - ordered_unmatched_target_nodes.pop(target_node_id, None) - break - - return matching_set - - def _compute_leaf_matching_set(self) -> t.Set[t.Tuple[int, int]]: - candidate_matchings: t.List[ - t.Tuple[float, int, int, exp.Expression, exp.Expression] - ] = [] - source_expression_leaves = list(_get_expression_leaves(self._source)) - target_expression_leaves = list(_get_expression_leaves(self._target)) - for source_leaf in source_expression_leaves: - for target_leaf in target_expression_leaves: - if _is_same_type(source_leaf, target_leaf): - similarity_score = self._dice_coefficient(source_leaf, target_leaf) - if similarity_score >= self.f: - heappush( - candidate_matchings, - ( - -similarity_score, - -_parent_similarity_score(source_leaf, target_leaf), - len(candidate_matchings), - source_leaf, - target_leaf, - ), - ) - - # Pick best matchings based on the highest score - matching_set = set() - while candidate_matchings: - _, _, _, source_leaf, target_leaf = heappop(candidate_matchings) - if ( - id(source_leaf) in self._unmatched_source_nodes - and id(target_leaf) in self._unmatched_target_nodes - ): - matching_set.add((id(source_leaf), id(target_leaf))) - self._unmatched_source_nodes.remove(id(source_leaf)) - self._unmatched_target_nodes.remove(id(target_leaf)) - - return matching_set - - def _dice_coefficient( - self, source: exp.Expression, target: exp.Expression - ) -> float: - source_histo = self._bigram_histo(source) - target_histo = self._bigram_histo(target) - - total_grams = sum(source_histo.values()) + sum(target_histo.values()) - if not total_grams: - return 1.0 if source == target else 0.0 - - overlap_len = 0 - overlapping_grams = set(source_histo) & set(target_histo) - for g in overlapping_grams: - overlap_len += min(source_histo[g], target_histo[g]) - - return 2 * overlap_len / total_grams - - def _bigram_histo(self, expression: exp.Expression) -> t.DefaultDict[str, int]: - if id(expression) in self._bigram_histo_cache: - return self._bigram_histo_cache[id(expression)] - - expression_str = self._sql_generator.generate(expression) - count = max(0, len(expression_str) - 1) - bigram_histo: t.DefaultDict[str, int] = defaultdict(int) - for i in range(count): - bigram_histo[expression_str[i : i + 2]] += 1 - - self._bigram_histo_cache[id(expression)] = bigram_histo - return bigram_histo - - -def _get_expression_leaves(expression: exp.Expression) -> t.Iterator[exp.Expression]: - has_child_exprs = False - - for node in expression.iter_expressions(): - if not isinstance(node, IGNORED_LEAF_EXPRESSION_TYPES): - has_child_exprs = True - yield from _get_expression_leaves(node) - - if not has_child_exprs: - yield expression - - -def _get_non_expression_leaves( - expression: exp.Expression, -) -> t.Iterator[t.Tuple[str, t.Any]]: - for arg, value in expression.args.items(): - if ( - value is None - or isinstance(value, exp.Expression) - or ( - isinstance(value, list) - and isinstance(seq_get(value, 0), exp.Expression) - ) - ): - continue - - yield (arg, value) - - -def _is_same_type(source: exp.Expression, target: exp.Expression) -> bool: - if type(source) is type(target): - if isinstance(source, exp.Join): - return source.args.get("side") == target.args.get("side") - - if isinstance(source, exp.Anonymous): - return source.this == target.this - - return True - - return False - - -def _parent_similarity_score( - source: t.Optional[exp.Expression], target: t.Optional[exp.Expression] -) -> int: - if source is None or target is None or type(source) is not type(target): - return 0 - - return 1 + _parent_similarity_score(source.parent, target.parent) - - -def _expression_only_args(expression: exp.Expression) -> t.Iterator[exp.Expression]: - yield from ( - arg - for arg in expression.iter_expressions() - if not isinstance(arg, IGNORED_LEAF_EXPRESSION_TYPES) - ) - - -def _lcs( - seq_a: t.Sequence[T], seq_b: t.Sequence[T], equal: t.Callable[[T, T], bool] -) -> t.Sequence[t.Optional[T]]: - """Calculates the longest common subsequence""" - - len_a = len(seq_a) - len_b = len(seq_b) - lcs_result = [[None] * (len_b + 1) for i in range(len_a + 1)] - - for i in range(len_a + 1): - for j in range(len_b + 1): - if i == 0 or j == 0: - lcs_result[i][j] = [] # type: ignore - elif equal(seq_a[i - 1], seq_b[j - 1]): - lcs_result[i][j] = lcs_result[i - 1][j - 1] + [seq_a[i - 1]] # type: ignore - else: - lcs_result[i][j] = ( - lcs_result[i - 1][j] - if len(lcs_result[i - 1][j]) > len(lcs_result[i][j - 1]) # type: ignore - else lcs_result[i][j - 1] - ) - - return lcs_result[len_a][len_b] # type: ignore diff --git a/third_party/bigframes_vendored/sqlglot/errors.py b/third_party/bigframes_vendored/sqlglot/errors.py deleted file mode 100644 index fe8e31d1960..00000000000 --- a/third_party/bigframes_vendored/sqlglot/errors.py +++ /dev/null @@ -1,167 +0,0 @@ -# Contains code from https://github.com/tobymao/sqlglot/blob/v28.5.0/sqlglot/errors.py - -from __future__ import annotations - -import typing as t -from enum import auto - -from bigframes_vendored.sqlglot.helper import AutoName - -# ANSI escape codes for error formatting -ANSI_UNDERLINE = "\033[4m" -ANSI_RESET = "\033[0m" -ERROR_MESSAGE_CONTEXT_DEFAULT = 100 - - -class ErrorLevel(AutoName): - IGNORE = auto() - """Ignore all errors.""" - - WARN = auto() - """Log all errors.""" - - RAISE = auto() - """Collect all errors and raise a single exception.""" - - IMMEDIATE = auto() - """Immediately raise an exception on the first error found.""" - - -class SqlglotError(Exception): - pass - - -class UnsupportedError(SqlglotError): - pass - - -class ParseError(SqlglotError): - def __init__( - self, - message: str, - errors: t.Optional[t.List[t.Dict[str, t.Any]]] = None, - ): - super().__init__(message) - self.errors = errors or [] - - @classmethod - def new( - cls, - message: str, - description: t.Optional[str] = None, - line: t.Optional[int] = None, - col: t.Optional[int] = None, - start_context: t.Optional[str] = None, - highlight: t.Optional[str] = None, - end_context: t.Optional[str] = None, - into_expression: t.Optional[str] = None, - ) -> ParseError: - return cls( - message, - [ - { - "description": description, - "line": line, - "col": col, - "start_context": start_context, - "highlight": highlight, - "end_context": end_context, - "into_expression": into_expression, - } - ], - ) - - -class TokenError(SqlglotError): - pass - - -class OptimizeError(SqlglotError): - pass - - -class SchemaError(SqlglotError): - pass - - -class ExecuteError(SqlglotError): - pass - - -def highlight_sql( - sql: str, - positions: t.List[t.Tuple[int, int]], - context_length: int = ERROR_MESSAGE_CONTEXT_DEFAULT, -) -> t.Tuple[str, str, str, str]: - """ - Highlight a SQL string using ANSI codes at the given positions. - - Args: - sql: The complete SQL string. - positions: List of (start, end) tuples where both start and end are inclusive 0-based - indexes. For example, to highlight "foo" in "SELECT foo", use (7, 9). - The positions will be sorted and de-duplicated if they overlap. - context_length: Number of characters to show before the first highlight and after - the last highlight. - - Returns: - A tuple of (formatted_sql, start_context, highlight, end_context) where: - - formatted_sql: The SQL with ANSI underline codes applied to highlighted sections - - start_context: Plain text before the first highlight - - highlight: Plain text from the first highlight start to the last highlight end, - including any non-highlighted text in between (no ANSI) - - end_context: Plain text after the last highlight - - Note: - If positions is empty, raises a ValueError. - """ - if not positions: - raise ValueError("positions must contain at least one (start, end) tuple") - - start_context = "" - end_context = "" - first_highlight_start = 0 - formatted_parts = [] - previous_part_end = 0 - sorted_positions = sorted(positions, key=lambda pos: pos[0]) - - if sorted_positions[0][0] > 0: - first_highlight_start = sorted_positions[0][0] - start_context = sql[ - max(0, first_highlight_start - context_length) : first_highlight_start - ] - formatted_parts.append(start_context) - previous_part_end = first_highlight_start - - for start, end in sorted_positions: - highlight_start = max(start, previous_part_end) - highlight_end = end + 1 - if highlight_start >= highlight_end: - continue # Skip invalid or overlapping highlights - if highlight_start > previous_part_end: - formatted_parts.append(sql[previous_part_end:highlight_start]) - formatted_parts.append( - f"{ANSI_UNDERLINE}{sql[highlight_start:highlight_end]}{ANSI_RESET}" - ) - previous_part_end = highlight_end - - if previous_part_end < len(sql): - end_context = sql[previous_part_end : previous_part_end + context_length] - formatted_parts.append(end_context) - - formatted_sql = "".join(formatted_parts) - highlight = sql[first_highlight_start:previous_part_end] - - return formatted_sql, start_context, highlight, end_context - - -def concat_messages(errors: t.Sequence[t.Any], maximum: int) -> str: - msg = [str(e) for e in errors[:maximum]] - remaining = len(errors) - maximum - if remaining > 0: - msg.append(f"... and {remaining} more") - return "\n\n".join(msg) - - -def merge_errors(errors: t.Sequence[ParseError]) -> t.List[t.Dict[str, t.Any]]: - return [e_dict for error in errors for e_dict in error.errors] diff --git a/third_party/bigframes_vendored/sqlglot/expressions.py b/third_party/bigframes_vendored/sqlglot/expressions.py deleted file mode 100644 index e8e4cc8e10d..00000000000 --- a/third_party/bigframes_vendored/sqlglot/expressions.py +++ /dev/null @@ -1,10471 +0,0 @@ -# Contains code from https://github.com/tobymao/sqlglot/blob/v28.5.0/sqlglot/expressions.py - -""" -## Expressions - -Every AST node in SQLGlot is represented by a subclass of `Expression`. - -This module contains the implementation of all supported `Expression` types. Additionally, -it exposes a number of helper functions, which are mainly used to programmatically build -SQL expressions, such as `sqlglot.expressions.select`. - ----- -""" - -from __future__ import annotations - -import datetime -import math -import numbers -import re -import sys -import textwrap -import typing as t -from collections import deque -from copy import deepcopy -from decimal import Decimal -from enum import auto -from functools import reduce - -from bigframes_vendored.sqlglot.errors import ErrorLevel, ParseError -from bigframes_vendored.sqlglot.helper import ( - AutoName, - camel_to_snake_case, - ensure_collection, - ensure_list, - seq_get, - split_num_words, - subclasses, - to_bool, -) -from bigframes_vendored.sqlglot.tokens import Token, TokenError - -if t.TYPE_CHECKING: - from bigframes_vendored.sqlglot._typing import E, Lit - from bigframes_vendored.sqlglot.dialects.dialect import DialectType - from typing_extensions import Self - - Q = t.TypeVar("Q", bound="Query") - S = t.TypeVar("S", bound="SetOperation") - - -class _Expression(type): - def __new__(cls, clsname, bases, attrs): - klass = super().__new__(cls, clsname, bases, attrs) - - # When an Expression class is created, its key is automatically set - # to be the lowercase version of the class' name. - klass.key = clsname.lower() - klass.required_args = {k for k, v in klass.arg_types.items() if v} - - # This is so that docstrings are not inherited in pdoc - klass.__doc__ = klass.__doc__ or "" - - return klass - - -SQLGLOT_META = "sqlglot.meta" -SQLGLOT_ANONYMOUS = "sqlglot.anonymous" -TABLE_PARTS = ("this", "db", "catalog") -COLUMN_PARTS = ("this", "table", "db", "catalog") -POSITION_META_KEYS = ("line", "col", "start", "end") -UNITTEST = "unittest" in sys.modules or "pytest" in sys.modules - - -class Expression(metaclass=_Expression): - """ - The base class for all expressions in a syntax tree. Each Expression encapsulates any necessary - context, such as its child expressions, their names (arg keys), and whether a given child expression - is optional or not. - - Attributes: - key: a unique key for each class in the Expression hierarchy. This is useful for hashing - and representing expressions as strings. - arg_types: determines the arguments (child nodes) supported by an expression. It maps - arg keys to booleans that indicate whether the corresponding args are optional. - parent: a reference to the parent expression (or None, in case of root expressions). - arg_key: the arg key an expression is associated with, i.e. the name its parent expression - uses to refer to it. - index: the index of an expression if it is inside of a list argument in its parent. - comments: a list of comments that are associated with a given expression. This is used in - order to preserve comments when transpiling SQL code. - type: the `sqlglot.expressions.DataType` type of an expression. This is inferred by the - optimizer, in order to enable some transformations that require type information. - meta: a dictionary that can be used to store useful metadata for a given expression. - - Example: - >>> class Foo(Expression): - ... arg_types = {"this": True, "expression": False} - - The above definition informs us that Foo is an Expression that requires an argument called - "this" and may also optionally receive an argument called "expression". - - Args: - args: a mapping used for retrieving the arguments of an expression, given their arg keys. - """ - - key = "expression" - arg_types = {"this": True} - required_args = {"this"} - __slots__ = ( - "args", - "parent", - "arg_key", - "index", - "comments", - "_type", - "_meta", - "_hash", - ) - - def __init__(self, **args: t.Any): - self.args: t.Dict[str, t.Any] = args - self.parent: t.Optional[Expression] = None - self.arg_key: t.Optional[str] = None - self.index: t.Optional[int] = None - self.comments: t.Optional[t.List[str]] = None - self._type: t.Optional[DataType] = None - self._meta: t.Optional[t.Dict[str, t.Any]] = None - self._hash: t.Optional[int] = None - - for arg_key, value in self.args.items(): - self._set_parent(arg_key, value) - - def __eq__(self, other) -> bool: - return self is other or ( - type(self) is type(other) and hash(self) == hash(other) - ) - - def __hash__(self) -> int: - if self._hash is None: - nodes = [] - queue = deque([self]) - - while queue: - node = queue.popleft() - nodes.append(node) - - for v in node.iter_expressions(): - if v._hash is None: - queue.append(v) - - for node in reversed(nodes): - hash_ = hash(node.key) - t = type(node) - - if t is Literal or t is Identifier: - for k, v in sorted(node.args.items()): - if v: - hash_ = hash((hash_, k, v)) - else: - for k, v in sorted(node.args.items()): - t = type(v) - - if t is list: - for x in v: - if x is not None and x is not False: - hash_ = hash( - (hash_, k, x.lower() if type(x) is str else x) - ) - else: - hash_ = hash((hash_, k)) - elif v is not None and v is not False: - hash_ = hash((hash_, k, v.lower() if t is str else v)) - - node._hash = hash_ - assert self._hash - return self._hash - - def __reduce__(self) -> t.Tuple[t.Callable, t.Tuple[t.List[t.Dict[str, t.Any]]]]: - from bigframes_vendored.sqlglot.serde import dump, load - - return (load, (dump(self),)) - - @property - def this(self) -> t.Any: - """ - Retrieves the argument with key "this". - """ - return self.args.get("this") - - @property - def expression(self) -> t.Any: - """ - Retrieves the argument with key "expression". - """ - return self.args.get("expression") - - @property - def expressions(self) -> t.List[t.Any]: - """ - Retrieves the argument with key "expressions". - """ - return self.args.get("expressions") or [] - - def text(self, key) -> str: - """ - Returns a textual representation of the argument corresponding to "key". This can only be used - for args that are strings or leaf Expression instances, such as identifiers and literals. - """ - field = self.args.get(key) - if isinstance(field, str): - return field - if isinstance(field, (Identifier, Literal, Var)): - return field.this - if isinstance(field, (Star, Null)): - return field.name - return "" - - @property - def is_string(self) -> bool: - """ - Checks whether a Literal expression is a string. - """ - return isinstance(self, Literal) and self.args["is_string"] - - @property - def is_number(self) -> bool: - """ - Checks whether a Literal expression is a number. - """ - return (isinstance(self, Literal) and not self.args["is_string"]) or ( - isinstance(self, Neg) and self.this.is_number - ) - - def to_py(self) -> t.Any: - """ - Returns a Python object equivalent of the SQL node. - """ - raise ValueError(f"{self} cannot be converted to a Python object.") - - @property - def is_int(self) -> bool: - """ - Checks whether an expression is an integer. - """ - return self.is_number and isinstance(self.to_py(), int) - - @property - def is_star(self) -> bool: - """Checks whether an expression is a star.""" - return isinstance(self, Star) or ( - isinstance(self, Column) and isinstance(self.this, Star) - ) - - @property - def alias(self) -> str: - """ - Returns the alias of the expression, or an empty string if it's not aliased. - """ - if isinstance(self.args.get("alias"), TableAlias): - return self.args["alias"].name - return self.text("alias") - - @property - def alias_column_names(self) -> t.List[str]: - table_alias = self.args.get("alias") - if not table_alias: - return [] - return [c.name for c in table_alias.args.get("columns") or []] - - @property - def name(self) -> str: - return self.text("this") - - @property - def alias_or_name(self) -> str: - return self.alias or self.name - - @property - def output_name(self) -> str: - """ - Name of the output column if this expression is a selection. - - If the Expression has no output name, an empty string is returned. - - Example: - >>> from sqlglot import parse_one - >>> parse_one("SELECT a").expressions[0].output_name - 'a' - >>> parse_one("SELECT b AS c").expressions[0].output_name - 'c' - >>> parse_one("SELECT 1 + 2").expressions[0].output_name - '' - """ - return "" - - @property - def type(self) -> t.Optional[DataType]: - return self._type - - @type.setter - def type(self, dtype: t.Optional[DataType | DataType.Type | str]) -> None: - if dtype and not isinstance(dtype, DataType): - dtype = DataType.build(dtype) - self._type = dtype # type: ignore - - def is_type(self, *dtypes) -> bool: - return self.type is not None and self.type.is_type(*dtypes) - - def is_leaf(self) -> bool: - return not any( - isinstance(v, (Expression, list)) and v for v in self.args.values() - ) - - @property - def meta(self) -> t.Dict[str, t.Any]: - if self._meta is None: - self._meta = {} - return self._meta - - def __deepcopy__(self, memo): - root = self.__class__() - stack = [(self, root)] - - while stack: - node, copy = stack.pop() - - if node.comments is not None: - copy.comments = deepcopy(node.comments) - if node._type is not None: - copy._type = deepcopy(node._type) - if node._meta is not None: - copy._meta = deepcopy(node._meta) - if node._hash is not None: - copy._hash = node._hash - - for k, vs in node.args.items(): - if hasattr(vs, "parent"): - stack.append((vs, vs.__class__())) - copy.set(k, stack[-1][-1]) - elif type(vs) is list: - copy.args[k] = [] - - for v in vs: - if hasattr(v, "parent"): - stack.append((v, v.__class__())) - copy.append(k, stack[-1][-1]) - else: - copy.append(k, v) - else: - copy.args[k] = vs - - return root - - def copy(self) -> Self: - """ - Returns a deep copy of the expression. - """ - return deepcopy(self) - - def add_comments( - self, comments: t.Optional[t.List[str]] = None, prepend: bool = False - ) -> None: - if self.comments is None: - self.comments = [] - - if comments: - for comment in comments: - _, *meta = comment.split(SQLGLOT_META) - if meta: - for kv in "".join(meta).split(","): - k, *v = kv.split("=") - value = v[0].strip() if v else True - self.meta[k.strip()] = to_bool(value) - - if not prepend: - self.comments.append(comment) - - if prepend: - self.comments = comments + self.comments - - def pop_comments(self) -> t.List[str]: - comments = self.comments or [] - self.comments = None - return comments - - def append(self, arg_key: str, value: t.Any) -> None: - """ - Appends value to arg_key if it's a list or sets it as a new list. - - Args: - arg_key (str): name of the list expression arg - value (Any): value to append to the list - """ - if type(self.args.get(arg_key)) is not list: - self.args[arg_key] = [] - self._set_parent(arg_key, value) - values = self.args[arg_key] - if hasattr(value, "parent"): - value.index = len(values) - values.append(value) - - def set( - self, - arg_key: str, - value: t.Any, - index: t.Optional[int] = None, - overwrite: bool = True, - ) -> None: - """ - Sets arg_key to value. - - Args: - arg_key: name of the expression arg. - value: value to set the arg to. - index: if the arg is a list, this specifies what position to add the value in it. - overwrite: assuming an index is given, this determines whether to overwrite the - list entry instead of only inserting a new value (i.e., like list.insert). - """ - expression: t.Optional[Expression] = self - - while expression and expression._hash is not None: - expression._hash = None - expression = expression.parent - - if index is not None: - expressions = self.args.get(arg_key) or [] - - if seq_get(expressions, index) is None: - return - if value is None: - expressions.pop(index) - for v in expressions[index:]: - v.index = v.index - 1 - return - - if isinstance(value, list): - expressions.pop(index) - expressions[index:index] = value - elif overwrite: - expressions[index] = value - else: - expressions.insert(index, value) - - value = expressions - elif value is None: - self.args.pop(arg_key, None) - return - - self.args[arg_key] = value - self._set_parent(arg_key, value, index) - - def _set_parent( - self, arg_key: str, value: t.Any, index: t.Optional[int] = None - ) -> None: - if hasattr(value, "parent"): - value.parent = self - value.arg_key = arg_key - value.index = index - elif type(value) is list: - for index, v in enumerate(value): - if hasattr(v, "parent"): - v.parent = self - v.arg_key = arg_key - v.index = index - - @property - def depth(self) -> int: - """ - Returns the depth of this tree. - """ - if self.parent: - return self.parent.depth + 1 - return 0 - - def iter_expressions(self, reverse: bool = False) -> t.Iterator[Expression]: - """Yields the key and expression for all arguments, exploding list args.""" - for vs in reversed(self.args.values()) if reverse else self.args.values(): # type: ignore - if type(vs) is list: - for v in reversed(vs) if reverse else vs: # type: ignore - if hasattr(v, "parent"): - yield v - elif hasattr(vs, "parent"): - yield vs - - def find(self, *expression_types: t.Type[E], bfs: bool = True) -> t.Optional[E]: - """ - Returns the first node in this tree which matches at least one of - the specified types. - - Args: - expression_types: the expression type(s) to match. - bfs: whether to search the AST using the BFS algorithm (DFS is used if false). - - Returns: - The node which matches the criteria or None if no such node was found. - """ - return next(self.find_all(*expression_types, bfs=bfs), None) - - def find_all(self, *expression_types: t.Type[E], bfs: bool = True) -> t.Iterator[E]: - """ - Returns a generator object which visits all nodes in this tree and only - yields those that match at least one of the specified expression types. - - Args: - expression_types: the expression type(s) to match. - bfs: whether to search the AST using the BFS algorithm (DFS is used if false). - - Returns: - The generator object. - """ - for expression in self.walk(bfs=bfs): - if isinstance(expression, expression_types): - yield expression - - def find_ancestor(self, *expression_types: t.Type[E]) -> t.Optional[E]: - """ - Returns a nearest parent matching expression_types. - - Args: - expression_types: the expression type(s) to match. - - Returns: - The parent node. - """ - ancestor = self.parent - while ancestor and not isinstance(ancestor, expression_types): - ancestor = ancestor.parent - return ancestor # type: ignore - - @property - def parent_select(self) -> t.Optional[Select]: - """ - Returns the parent select statement. - """ - return self.find_ancestor(Select) - - @property - def same_parent(self) -> bool: - """Returns if the parent is the same class as itself.""" - return type(self.parent) is self.__class__ - - def root(self) -> Expression: - """ - Returns the root expression of this tree. - """ - expression = self - while expression.parent: - expression = expression.parent - return expression - - def walk( - self, bfs: bool = True, prune: t.Optional[t.Callable[[Expression], bool]] = None - ) -> t.Iterator[Expression]: - """ - Returns a generator object which visits all nodes in this tree. - - Args: - bfs: if set to True the BFS traversal order will be applied, - otherwise the DFS traversal will be used instead. - prune: callable that returns True if the generator should stop traversing - this branch of the tree. - - Returns: - the generator object. - """ - if bfs: - yield from self.bfs(prune=prune) - else: - yield from self.dfs(prune=prune) - - def dfs( - self, prune: t.Optional[t.Callable[[Expression], bool]] = None - ) -> t.Iterator[Expression]: - """ - Returns a generator object which visits all nodes in this tree in - the DFS (Depth-first) order. - - Returns: - The generator object. - """ - stack = [self] - - while stack: - node = stack.pop() - - yield node - - if prune and prune(node): - continue - - for v in node.iter_expressions(reverse=True): - stack.append(v) - - def bfs( - self, prune: t.Optional[t.Callable[[Expression], bool]] = None - ) -> t.Iterator[Expression]: - """ - Returns a generator object which visits all nodes in this tree in - the BFS (Breadth-first) order. - - Returns: - The generator object. - """ - queue = deque([self]) - - while queue: - node = queue.popleft() - - yield node - - if prune and prune(node): - continue - - for v in node.iter_expressions(): - queue.append(v) - - def unnest(self): - """ - Returns the first non parenthesis child or self. - """ - expression = self - while type(expression) is Paren: - expression = expression.this - return expression - - def unalias(self): - """ - Returns the inner expression if this is an Alias. - """ - if isinstance(self, Alias): - return self.this - return self - - def unnest_operands(self): - """ - Returns unnested operands as a tuple. - """ - return tuple(arg.unnest() for arg in self.iter_expressions()) - - def flatten(self, unnest=True): - """ - Returns a generator which yields child nodes whose parents are the same class. - - A AND B AND C -> [A, B, C] - """ - for node in self.dfs( - prune=lambda n: n.parent and type(n) is not self.__class__ - ): - if type(node) is not self.__class__: - yield ( - node.unnest() if unnest and not isinstance(node, Subquery) else node - ) - - def __str__(self) -> str: - return self.sql() - - def __repr__(self) -> str: - return _to_s(self) - - def to_s(self) -> str: - """ - Same as __repr__, but includes additional information which can be useful - for debugging, like empty or missing args and the AST nodes' object IDs. - """ - return _to_s(self, verbose=True) - - def sql(self, dialect: DialectType = None, **opts) -> str: - """ - Returns SQL string representation of this tree. - - Args: - dialect: the dialect of the output SQL string (eg. "spark", "hive", "presto", "mysql"). - opts: other `sqlglot.generator.Generator` options. - - Returns: - The SQL string. - """ - from bigframes_vendored.sqlglot.dialects import Dialect - - return Dialect.get_or_raise(dialect).generate(self, **opts) - - def transform( - self, fun: t.Callable, *args: t.Any, copy: bool = True, **kwargs - ) -> Expression: - """ - Visits all tree nodes (excluding already transformed ones) - and applies the given transformation function to each node. - - Args: - fun: a function which takes a node as an argument and returns a - new transformed node or the same node without modifications. If the function - returns None, then the corresponding node will be removed from the syntax tree. - copy: if set to True a new tree instance is constructed, otherwise the tree is - modified in place. - - Returns: - The transformed tree. - """ - root = None - new_node = None - - for node in (self.copy() if copy else self).dfs( - prune=lambda n: n is not new_node - ): - parent, arg_key, index = node.parent, node.arg_key, node.index - new_node = fun(node, *args, **kwargs) - - if not root: - root = new_node - elif parent and arg_key and new_node is not node: - parent.set(arg_key, new_node, index) - - assert root - return root.assert_is(Expression) - - @t.overload - def replace(self, expression: E) -> E: ... - - @t.overload - def replace(self, expression: None) -> None: ... - - def replace(self, expression): - """ - Swap out this expression with a new expression. - - For example:: - - >>> tree = Select().select("x").from_("tbl") - >>> tree.find(Column).replace(column("y")) - Column( - this=Identifier(this=y, quoted=False)) - >>> tree.sql() - 'SELECT y FROM tbl' - - Args: - expression: new node - - Returns: - The new expression or expressions. - """ - parent = self.parent - - if not parent or parent is expression: - return expression - - key = self.arg_key - value = parent.args.get(key) - - if type(expression) is list and isinstance(value, Expression): - # We are trying to replace an Expression with a list, so it's assumed that - # the intention was to really replace the parent of this expression. - value.parent.replace(expression) - else: - parent.set(key, expression, self.index) - - if expression is not self: - self.parent = None - self.arg_key = None - self.index = None - - return expression - - def pop(self: E) -> E: - """ - Remove this expression from its AST. - - Returns: - The popped expression. - """ - self.replace(None) - return self - - def assert_is(self, type_: t.Type[E]) -> E: - """ - Assert that this `Expression` is an instance of `type_`. - - If it is NOT an instance of `type_`, this raises an assertion error. - Otherwise, this returns this expression. - - Examples: - This is useful for type security in chained expressions: - - >>> import sqlglot - >>> sqlglot.parse_one("SELECT x from y").assert_is(Select).select("z").sql() - 'SELECT x, z FROM y' - """ - if not isinstance(self, type_): - raise AssertionError(f"{self} is not {type_}.") - return self - - def error_messages(self, args: t.Optional[t.Sequence] = None) -> t.List[str]: - """ - Checks if this expression is valid (e.g. all mandatory args are set). - - Args: - args: a sequence of values that were used to instantiate a Func expression. This is used - to check that the provided arguments don't exceed the function argument limit. - - Returns: - A list of error messages for all possible errors that were found. - """ - errors: t.List[str] = [] - - if UNITTEST: - for k in self.args: - if k not in self.arg_types: - raise TypeError(f"Unexpected keyword: '{k}' for {self.__class__}") - - for k in self.required_args: - v = self.args.get(k) - if v is None or (type(v) is list and not v): - errors.append(f"Required keyword: '{k}' missing for {self.__class__}") - - if ( - args - and isinstance(self, Func) - and len(args) > len(self.arg_types) - and not self.is_var_len_args - ): - errors.append( - f"The number of provided arguments ({len(args)}) is greater than " - f"the maximum number of supported arguments ({len(self.arg_types)})" - ) - - return errors - - def dump(self): - """ - Dump this Expression to a JSON-serializable dict. - """ - from bigframes_vendored.sqlglot.serde import dump - - return dump(self) - - @classmethod - def load(cls, obj): - """ - Load a dict (as returned by `Expression.dump`) into an Expression instance. - """ - from bigframes_vendored.sqlglot.serde import load - - return load(obj) - - def and_( - self, - *expressions: t.Optional[ExpOrStr], - dialect: DialectType = None, - copy: bool = True, - wrap: bool = True, - **opts, - ) -> Condition: - """ - AND this condition with one or multiple expressions. - - Example: - >>> condition("x=1").and_("y=1").sql() - 'x = 1 AND y = 1' - - Args: - *expressions: the SQL code strings to parse. - If an `Expression` instance is passed, it will be used as-is. - dialect: the dialect used to parse the input expression. - copy: whether to copy the involved expressions (only applies to Expressions). - wrap: whether to wrap the operands in `Paren`s. This is true by default to avoid - precedence issues, but can be turned off when the produced AST is too deep and - causes recursion-related issues. - opts: other options to use to parse the input expressions. - - Returns: - The new And condition. - """ - return and_(self, *expressions, dialect=dialect, copy=copy, wrap=wrap, **opts) - - def or_( - self, - *expressions: t.Optional[ExpOrStr], - dialect: DialectType = None, - copy: bool = True, - wrap: bool = True, - **opts, - ) -> Condition: - """ - OR this condition with one or multiple expressions. - - Example: - >>> condition("x=1").or_("y=1").sql() - 'x = 1 OR y = 1' - - Args: - *expressions: the SQL code strings to parse. - If an `Expression` instance is passed, it will be used as-is. - dialect: the dialect used to parse the input expression. - copy: whether to copy the involved expressions (only applies to Expressions). - wrap: whether to wrap the operands in `Paren`s. This is true by default to avoid - precedence issues, but can be turned off when the produced AST is too deep and - causes recursion-related issues. - opts: other options to use to parse the input expressions. - - Returns: - The new Or condition. - """ - return or_(self, *expressions, dialect=dialect, copy=copy, wrap=wrap, **opts) - - def not_(self, copy: bool = True): - """ - Wrap this condition with NOT. - - Example: - >>> condition("x=1").not_().sql() - 'NOT x = 1' - - Args: - copy: whether to copy this object. - - Returns: - The new Not instance. - """ - return not_(self, copy=copy) - - def update_positions( - self: E, - other: t.Optional[Token | Expression] = None, - line: t.Optional[int] = None, - col: t.Optional[int] = None, - start: t.Optional[int] = None, - end: t.Optional[int] = None, - ) -> E: - """ - Update this expression with positions from a token or other expression. - - Args: - other: a token or expression to update this expression with. - line: the line number to use if other is None - col: column number - start: start char index - end: end char index - - Returns: - The updated expression. - """ - if other is None: - self.meta["line"] = line - self.meta["col"] = col - self.meta["start"] = start - self.meta["end"] = end - elif hasattr(other, "meta"): - for k in POSITION_META_KEYS: - self.meta[k] = other.meta[k] - else: - self.meta["line"] = other.line - self.meta["col"] = other.col - self.meta["start"] = other.start - self.meta["end"] = other.end - return self - - def as_( - self, - alias: str | Identifier, - quoted: t.Optional[bool] = None, - dialect: DialectType = None, - copy: bool = True, - **opts, - ) -> Alias: - return alias_(self, alias, quoted=quoted, dialect=dialect, copy=copy, **opts) - - def _binop(self, klass: t.Type[E], other: t.Any, reverse: bool = False) -> E: - this = self.copy() - other = convert(other, copy=True) - if not isinstance(this, klass) and not isinstance(other, klass): - this = _wrap(this, Binary) - other = _wrap(other, Binary) - if reverse: - return klass(this=other, expression=this) - return klass(this=this, expression=other) - - def __getitem__(self, other: ExpOrStr | t.Tuple[ExpOrStr]) -> Bracket: - return Bracket( - this=self.copy(), - expressions=[convert(e, copy=True) for e in ensure_list(other)], - ) - - def __iter__(self) -> t.Iterator: - if "expressions" in self.arg_types: - return iter(self.args.get("expressions") or []) - # We define this because __getitem__ converts Expression into an iterable, which is - # problematic because one can hit infinite loops if they do "for x in some_expr: ..." - # See: https://peps.python.org/pep-0234/ - raise TypeError(f"'{self.__class__.__name__}' object is not iterable") - - def isin( - self, - *expressions: t.Any, - query: t.Optional[ExpOrStr] = None, - unnest: t.Optional[ExpOrStr] | t.Collection[ExpOrStr] = None, - copy: bool = True, - **opts, - ) -> In: - subquery = maybe_parse(query, copy=copy, **opts) if query else None - if subquery and not isinstance(subquery, Subquery): - subquery = subquery.subquery(copy=False) - - return In( - this=maybe_copy(self, copy), - expressions=[convert(e, copy=copy) for e in expressions], - query=subquery, - unnest=( - Unnest( - expressions=[ - maybe_parse(t.cast(ExpOrStr, e), copy=copy, **opts) - for e in ensure_list(unnest) - ] - ) - if unnest - else None - ), - ) - - def between( - self, - low: t.Any, - high: t.Any, - copy: bool = True, - symmetric: t.Optional[bool] = None, - **opts, - ) -> Between: - between = Between( - this=maybe_copy(self, copy), - low=convert(low, copy=copy, **opts), - high=convert(high, copy=copy, **opts), - ) - if symmetric is not None: - between.set("symmetric", symmetric) - - return between - - def is_(self, other: ExpOrStr) -> Is: - return self._binop(Is, other) - - def like(self, other: ExpOrStr) -> Like: - return self._binop(Like, other) - - def ilike(self, other: ExpOrStr) -> ILike: - return self._binop(ILike, other) - - def eq(self, other: t.Any) -> EQ: - return self._binop(EQ, other) - - def neq(self, other: t.Any) -> NEQ: - return self._binop(NEQ, other) - - def rlike(self, other: ExpOrStr) -> RegexpLike: - return self._binop(RegexpLike, other) - - def div(self, other: ExpOrStr, typed: bool = False, safe: bool = False) -> Div: - div = self._binop(Div, other) - div.set("typed", typed) - div.set("safe", safe) - return div - - def asc(self, nulls_first: bool = True) -> Ordered: - return Ordered(this=self.copy(), nulls_first=nulls_first) - - def desc(self, nulls_first: bool = False) -> Ordered: - return Ordered(this=self.copy(), desc=True, nulls_first=nulls_first) - - def __lt__(self, other: t.Any) -> LT: - return self._binop(LT, other) - - def __le__(self, other: t.Any) -> LTE: - return self._binop(LTE, other) - - def __gt__(self, other: t.Any) -> GT: - return self._binop(GT, other) - - def __ge__(self, other: t.Any) -> GTE: - return self._binop(GTE, other) - - def __add__(self, other: t.Any) -> Add: - return self._binop(Add, other) - - def __radd__(self, other: t.Any) -> Add: - return self._binop(Add, other, reverse=True) - - def __sub__(self, other: t.Any) -> Sub: - return self._binop(Sub, other) - - def __rsub__(self, other: t.Any) -> Sub: - return self._binop(Sub, other, reverse=True) - - def __mul__(self, other: t.Any) -> Mul: - return self._binop(Mul, other) - - def __rmul__(self, other: t.Any) -> Mul: - return self._binop(Mul, other, reverse=True) - - def __truediv__(self, other: t.Any) -> Div: - return self._binop(Div, other) - - def __rtruediv__(self, other: t.Any) -> Div: - return self._binop(Div, other, reverse=True) - - def __floordiv__(self, other: t.Any) -> IntDiv: - return self._binop(IntDiv, other) - - def __rfloordiv__(self, other: t.Any) -> IntDiv: - return self._binop(IntDiv, other, reverse=True) - - def __mod__(self, other: t.Any) -> Mod: - return self._binop(Mod, other) - - def __rmod__(self, other: t.Any) -> Mod: - return self._binop(Mod, other, reverse=True) - - def __pow__(self, other: t.Any) -> Pow: - return self._binop(Pow, other) - - def __rpow__(self, other: t.Any) -> Pow: - return self._binop(Pow, other, reverse=True) - - def __and__(self, other: t.Any) -> And: - return self._binop(And, other) - - def __rand__(self, other: t.Any) -> And: - return self._binop(And, other, reverse=True) - - def __or__(self, other: t.Any) -> Or: - return self._binop(Or, other) - - def __ror__(self, other: t.Any) -> Or: - return self._binop(Or, other, reverse=True) - - def __neg__(self) -> Neg: - return Neg(this=_wrap(self.copy(), Binary)) - - def __invert__(self) -> Not: - return not_(self.copy()) - - -IntoType = t.Union[ - str, - t.Type[Expression], - t.Collection[t.Union[str, t.Type[Expression]]], -] -ExpOrStr = t.Union[str, Expression] - - -class Condition(Expression): - """Logical conditions like x AND y, or simply x""" - - -class Predicate(Condition): - """Relationships like x = y, x > 1, x >= y.""" - - -class DerivedTable(Expression): - @property - def selects(self) -> t.List[Expression]: - return self.this.selects if isinstance(self.this, Query) else [] - - @property - def named_selects(self) -> t.List[str]: - return [select.output_name for select in self.selects] - - -class Query(Expression): - def subquery( - self, alias: t.Optional[ExpOrStr] = None, copy: bool = True - ) -> Subquery: - """ - Returns a `Subquery` that wraps around this query. - - Example: - >>> subquery = Select().select("x").from_("tbl").subquery() - >>> Select().select("x").from_(subquery).sql() - 'SELECT x FROM (SELECT x FROM tbl)' - - Args: - alias: an optional alias for the subquery. - copy: if `False`, modify this expression instance in-place. - """ - instance = maybe_copy(self, copy) - if not isinstance(alias, Expression): - alias = TableAlias(this=to_identifier(alias)) if alias else None - - return Subquery(this=instance, alias=alias) - - def limit( - self: Q, - expression: ExpOrStr | int, - dialect: DialectType = None, - copy: bool = True, - **opts, - ) -> Q: - """ - Adds a LIMIT clause to this query. - - Example: - >>> select("1").union(select("1")).limit(1).sql() - 'SELECT 1 UNION SELECT 1 LIMIT 1' - - Args: - expression: the SQL code string to parse. - This can also be an integer. - If a `Limit` instance is passed, it will be used as-is. - If another `Expression` instance is passed, it will be wrapped in a `Limit`. - dialect: the dialect used to parse the input expression. - copy: if `False`, modify this expression instance in-place. - opts: other options to use to parse the input expressions. - - Returns: - A limited Select expression. - """ - return _apply_builder( - expression=expression, - instance=self, - arg="limit", - into=Limit, - prefix="LIMIT", - dialect=dialect, - copy=copy, - into_arg="expression", - **opts, - ) - - def offset( - self: Q, - expression: ExpOrStr | int, - dialect: DialectType = None, - copy: bool = True, - **opts, - ) -> Q: - """ - Set the OFFSET expression. - - Example: - >>> Select().from_("tbl").select("x").offset(10).sql() - 'SELECT x FROM tbl OFFSET 10' - - Args: - expression: the SQL code string to parse. - This can also be an integer. - If a `Offset` instance is passed, this is used as-is. - If another `Expression` instance is passed, it will be wrapped in a `Offset`. - dialect: the dialect used to parse the input expression. - copy: if `False`, modify this expression instance in-place. - opts: other options to use to parse the input expressions. - - Returns: - The modified Select expression. - """ - return _apply_builder( - expression=expression, - instance=self, - arg="offset", - into=Offset, - prefix="OFFSET", - dialect=dialect, - copy=copy, - into_arg="expression", - **opts, - ) - - def order_by( - self: Q, - *expressions: t.Optional[ExpOrStr], - append: bool = True, - dialect: DialectType = None, - copy: bool = True, - **opts, - ) -> Q: - """ - Set the ORDER BY expression. - - Example: - >>> Select().from_("tbl").select("x").order_by("x DESC").sql() - 'SELECT x FROM tbl ORDER BY x DESC' - - Args: - *expressions: the SQL code strings to parse. - If a `Group` instance is passed, this is used as-is. - If another `Expression` instance is passed, it will be wrapped in a `Order`. - append: if `True`, add to any existing expressions. - Otherwise, this flattens all the `Order` expression into a single expression. - dialect: the dialect used to parse the input expression. - copy: if `False`, modify this expression instance in-place. - opts: other options to use to parse the input expressions. - - Returns: - The modified Select expression. - """ - return _apply_child_list_builder( - *expressions, - instance=self, - arg="order", - append=append, - copy=copy, - prefix="ORDER BY", - into=Order, - dialect=dialect, - **opts, - ) - - @property - def ctes(self) -> t.List[CTE]: - """Returns a list of all the CTEs attached to this query.""" - with_ = self.args.get("with_") - return with_.expressions if with_ else [] - - @property - def selects(self) -> t.List[Expression]: - """Returns the query's projections.""" - raise NotImplementedError("Query objects must implement `selects`") - - @property - def named_selects(self) -> t.List[str]: - """Returns the output names of the query's projections.""" - raise NotImplementedError("Query objects must implement `named_selects`") - - def select( - self: Q, - *expressions: t.Optional[ExpOrStr], - append: bool = True, - dialect: DialectType = None, - copy: bool = True, - **opts, - ) -> Q: - """ - Append to or set the SELECT expressions. - - Example: - >>> Select().select("x", "y").sql() - 'SELECT x, y' - - Args: - *expressions: the SQL code strings to parse. - If an `Expression` instance is passed, it will be used as-is. - append: if `True`, add to any existing expressions. - Otherwise, this resets the expressions. - dialect: the dialect used to parse the input expressions. - copy: if `False`, modify this expression instance in-place. - opts: other options to use to parse the input expressions. - - Returns: - The modified Query expression. - """ - raise NotImplementedError("Query objects must implement `select`") - - def where( - self: Q, - *expressions: t.Optional[ExpOrStr], - append: bool = True, - dialect: DialectType = None, - copy: bool = True, - **opts, - ) -> Q: - """ - Append to or set the WHERE expressions. - - Examples: - >>> Select().select("x").from_("tbl").where("x = 'a' OR x < 'b'").sql() - "SELECT x FROM tbl WHERE x = 'a' OR x < 'b'" - - Args: - *expressions: the SQL code strings to parse. - If an `Expression` instance is passed, it will be used as-is. - Multiple expressions are combined with an AND operator. - append: if `True`, AND the new expressions to any existing expression. - Otherwise, this resets the expression. - dialect: the dialect used to parse the input expressions. - copy: if `False`, modify this expression instance in-place. - opts: other options to use to parse the input expressions. - - Returns: - The modified expression. - """ - return _apply_conjunction_builder( - *[expr.this if isinstance(expr, Where) else expr for expr in expressions], - instance=self, - arg="where", - append=append, - into=Where, - dialect=dialect, - copy=copy, - **opts, - ) - - def with_( - self: Q, - alias: ExpOrStr, - as_: ExpOrStr, - recursive: t.Optional[bool] = None, - materialized: t.Optional[bool] = None, - append: bool = True, - dialect: DialectType = None, - copy: bool = True, - scalar: t.Optional[bool] = None, - **opts, - ) -> Q: - """ - Append to or set the common table expressions. - - Example: - >>> Select().with_("tbl2", as_="SELECT * FROM tbl").select("x").from_("tbl2").sql() - 'WITH tbl2 AS (SELECT * FROM tbl) SELECT x FROM tbl2' - - Args: - alias: the SQL code string to parse as the table name. - If an `Expression` instance is passed, this is used as-is. - as_: the SQL code string to parse as the table expression. - If an `Expression` instance is passed, it will be used as-is. - recursive: set the RECURSIVE part of the expression. Defaults to `False`. - materialized: set the MATERIALIZED part of the expression. - append: if `True`, add to any existing expressions. - Otherwise, this resets the expressions. - dialect: the dialect used to parse the input expression. - copy: if `False`, modify this expression instance in-place. - scalar: if `True`, this is a scalar common table expression. - opts: other options to use to parse the input expressions. - - Returns: - The modified expression. - """ - return _apply_cte_builder( - self, - alias, - as_, - recursive=recursive, - materialized=materialized, - append=append, - dialect=dialect, - copy=copy, - scalar=scalar, - **opts, - ) - - def union( - self, - *expressions: ExpOrStr, - distinct: bool = True, - dialect: DialectType = None, - **opts, - ) -> Union: - """ - Builds a UNION expression. - - Example: - >>> import sqlglot - >>> sqlglot.parse_one("SELECT * FROM foo").union("SELECT * FROM bla").sql() - 'SELECT * FROM foo UNION SELECT * FROM bla' - - Args: - expressions: the SQL code strings. - If `Expression` instances are passed, they will be used as-is. - distinct: set the DISTINCT flag if and only if this is true. - dialect: the dialect used to parse the input expression. - opts: other options to use to parse the input expressions. - - Returns: - The new Union expression. - """ - return union(self, *expressions, distinct=distinct, dialect=dialect, **opts) - - def intersect( - self, - *expressions: ExpOrStr, - distinct: bool = True, - dialect: DialectType = None, - **opts, - ) -> Intersect: - """ - Builds an INTERSECT expression. - - Example: - >>> import sqlglot - >>> sqlglot.parse_one("SELECT * FROM foo").intersect("SELECT * FROM bla").sql() - 'SELECT * FROM foo INTERSECT SELECT * FROM bla' - - Args: - expressions: the SQL code strings. - If `Expression` instances are passed, they will be used as-is. - distinct: set the DISTINCT flag if and only if this is true. - dialect: the dialect used to parse the input expression. - opts: other options to use to parse the input expressions. - - Returns: - The new Intersect expression. - """ - return intersect(self, *expressions, distinct=distinct, dialect=dialect, **opts) - - def except_( - self, - *expressions: ExpOrStr, - distinct: bool = True, - dialect: DialectType = None, - **opts, - ) -> Except: - """ - Builds an EXCEPT expression. - - Example: - >>> import sqlglot - >>> sqlglot.parse_one("SELECT * FROM foo").except_("SELECT * FROM bla").sql() - 'SELECT * FROM foo EXCEPT SELECT * FROM bla' - - Args: - expressions: the SQL code strings. - If `Expression` instance are passed, they will be used as-is. - distinct: set the DISTINCT flag if and only if this is true. - dialect: the dialect used to parse the input expression. - opts: other options to use to parse the input expressions. - - Returns: - The new Except expression. - """ - return except_(self, *expressions, distinct=distinct, dialect=dialect, **opts) - - -class UDTF(DerivedTable): - @property - def selects(self) -> t.List[Expression]: - alias = self.args.get("alias") - return alias.columns if alias else [] - - -class Cache(Expression): - arg_types = { - "this": True, - "lazy": False, - "options": False, - "expression": False, - } - - -class Uncache(Expression): - arg_types = {"this": True, "exists": False} - - -class Refresh(Expression): - arg_types = {"this": True, "kind": True} - - -class DDL(Expression): - @property - def ctes(self) -> t.List[CTE]: - """Returns a list of all the CTEs attached to this statement.""" - with_ = self.args.get("with_") - return with_.expressions if with_ else [] - - @property - def selects(self) -> t.List[Expression]: - """If this statement contains a query (e.g. a CTAS), this returns the query's projections.""" - return self.expression.selects if isinstance(self.expression, Query) else [] - - @property - def named_selects(self) -> t.List[str]: - """ - If this statement contains a query (e.g. a CTAS), this returns the output - names of the query's projections. - """ - return ( - self.expression.named_selects if isinstance(self.expression, Query) else [] - ) - - -# https://docs.teradata.com/r/Enterprise_IntelliFlex_VMware/SQL-Data-Manipulation-Language/Statement-Syntax/LOCKING-Request-Modifier/LOCKING-Request-Modifier-Syntax -class LockingStatement(Expression): - arg_types = {"this": True, "expression": True} - - -class DML(Expression): - def returning( - self, - expression: ExpOrStr, - dialect: DialectType = None, - copy: bool = True, - **opts, - ) -> "Self": - """ - Set the RETURNING expression. Not supported by all dialects. - - Example: - >>> delete("tbl").returning("*", dialect="postgres").sql() - 'DELETE FROM tbl RETURNING *' - - Args: - expression: the SQL code strings to parse. - If an `Expression` instance is passed, it will be used as-is. - dialect: the dialect used to parse the input expressions. - copy: if `False`, modify this expression instance in-place. - opts: other options to use to parse the input expressions. - - Returns: - Delete: the modified expression. - """ - return _apply_builder( - expression=expression, - instance=self, - arg="returning", - prefix="RETURNING", - dialect=dialect, - copy=copy, - into=Returning, - **opts, - ) - - -class Create(DDL): - arg_types = { - "with_": False, - "this": True, - "kind": True, - "expression": False, - "exists": False, - "properties": False, - "replace": False, - "refresh": False, - "unique": False, - "indexes": False, - "no_schema_binding": False, - "begin": False, - "end": False, - "clone": False, - "concurrently": False, - "clustered": False, - } - - @property - def kind(self) -> t.Optional[str]: - kind = self.args.get("kind") - return kind and kind.upper() - - -class SequenceProperties(Expression): - arg_types = { - "increment": False, - "minvalue": False, - "maxvalue": False, - "cache": False, - "start": False, - "owned": False, - "options": False, - } - - -class TruncateTable(Expression): - arg_types = { - "expressions": True, - "is_database": False, - "exists": False, - "only": False, - "cluster": False, - "identity": False, - "option": False, - "partition": False, - } - - -# https://docs.snowflake.com/en/sql-reference/sql/create-clone -# https://cloud.google.com/bigquery/docs/reference/standard-sql/data-definition-language#create_table_clone_statement -# https://cloud.google.com/bigquery/docs/reference/standard-sql/data-definition-language#create_table_copy -class Clone(Expression): - arg_types = {"this": True, "shallow": False, "copy": False} - - -class Describe(Expression): - arg_types = { - "this": True, - "style": False, - "kind": False, - "expressions": False, - "partition": False, - "format": False, - } - - -# https://duckdb.org/docs/sql/statements/attach.html#attach -class Attach(Expression): - arg_types = {"this": True, "exists": False, "expressions": False} - - -# https://duckdb.org/docs/sql/statements/attach.html#detach -class Detach(Expression): - arg_types = {"this": True, "exists": False} - - -# https://duckdb.org/docs/sql/statements/load_and_install.html -class Install(Expression): - arg_types = {"this": True, "from_": False, "force": False} - - -# https://duckdb.org/docs/guides/meta/summarize.html -class Summarize(Expression): - arg_types = {"this": True, "table": False} - - -class Kill(Expression): - arg_types = {"this": True, "kind": False} - - -class Pragma(Expression): - pass - - -class Declare(Expression): - arg_types = {"expressions": True} - - -class DeclareItem(Expression): - arg_types = {"this": True, "kind": False, "default": False} - - -class Set(Expression): - arg_types = {"expressions": False, "unset": False, "tag": False} - - -class Heredoc(Expression): - arg_types = {"this": True, "tag": False} - - -class SetItem(Expression): - arg_types = { - "this": False, - "expressions": False, - "kind": False, - "collate": False, # MySQL SET NAMES statement - "global_": False, - } - - -class QueryBand(Expression): - arg_types = {"this": True, "scope": False, "update": False} - - -class Show(Expression): - arg_types = { - "this": True, - "history": False, - "terse": False, - "target": False, - "offset": False, - "starts_with": False, - "limit": False, - "from_": False, - "like": False, - "where": False, - "db": False, - "scope": False, - "scope_kind": False, - "full": False, - "mutex": False, - "query": False, - "channel": False, - "global_": False, - "log": False, - "position": False, - "types": False, - "privileges": False, - "for_table": False, - "for_group": False, - "for_user": False, - "for_role": False, - "into_outfile": False, - "json": False, - } - - -class UserDefinedFunction(Expression): - arg_types = {"this": True, "expressions": False, "wrapped": False} - - -class CharacterSet(Expression): - arg_types = {"this": True, "default": False} - - -class RecursiveWithSearch(Expression): - arg_types = {"kind": True, "this": True, "expression": True, "using": False} - - -class With(Expression): - arg_types = {"expressions": True, "recursive": False, "search": False} - - @property - def recursive(self) -> bool: - return bool(self.args.get("recursive")) - - -class WithinGroup(Expression): - arg_types = {"this": True, "expression": False} - - -# clickhouse supports scalar ctes -# https://clickhouse.com/docs/en/sql-reference/statements/select/with -class CTE(DerivedTable): - arg_types = { - "this": True, - "alias": True, - "scalar": False, - "materialized": False, - "key_expressions": False, - } - - -class ProjectionDef(Expression): - arg_types = {"this": True, "expression": True} - - -class TableAlias(Expression): - arg_types = {"this": False, "columns": False} - - @property - def columns(self): - return self.args.get("columns") or [] - - -class BitString(Condition): - pass - - -class HexString(Condition): - arg_types = {"this": True, "is_integer": False} - - -class ByteString(Condition): - arg_types = {"this": True, "is_bytes": False} - - -class RawString(Condition): - pass - - -class UnicodeString(Condition): - arg_types = {"this": True, "escape": False} - - -class Column(Condition): - arg_types = { - "this": True, - "table": False, - "db": False, - "catalog": False, - "join_mark": False, - } - - @property - def table(self) -> str: - return self.text("table") - - @property - def db(self) -> str: - return self.text("db") - - @property - def catalog(self) -> str: - return self.text("catalog") - - @property - def output_name(self) -> str: - return self.name - - @property - def parts(self) -> t.List[Identifier]: - """Return the parts of a column in order catalog, db, table, name.""" - return [ - t.cast(Identifier, self.args[part]) - for part in ("catalog", "db", "table", "this") - if self.args.get(part) - ] - - def to_dot(self, include_dots: bool = True) -> Dot | Identifier: - """Converts the column into a dot expression.""" - parts = self.parts - parent = self.parent - - if include_dots: - while isinstance(parent, Dot): - parts.append(parent.expression) - parent = parent.parent - - return Dot.build(deepcopy(parts)) if len(parts) > 1 else parts[0] - - -class Pseudocolumn(Column): - pass - - -class ColumnPosition(Expression): - arg_types = {"this": False, "position": True} - - -class ColumnDef(Expression): - arg_types = { - "this": True, - "kind": False, - "constraints": False, - "exists": False, - "position": False, - "default": False, - "output": False, - } - - @property - def constraints(self) -> t.List[ColumnConstraint]: - return self.args.get("constraints") or [] - - @property - def kind(self) -> t.Optional[DataType]: - return self.args.get("kind") - - -class AlterColumn(Expression): - arg_types = { - "this": True, - "dtype": False, - "collate": False, - "using": False, - "default": False, - "drop": False, - "comment": False, - "allow_null": False, - "visible": False, - "rename_to": False, - } - - -# https://dev.mysql.com/doc/refman/8.0/en/invisible-indexes.html -class AlterIndex(Expression): - arg_types = {"this": True, "visible": True} - - -# https://docs.aws.amazon.com/redshift/latest/dg/r_ALTER_TABLE.html -class AlterDistStyle(Expression): - pass - - -class AlterSortKey(Expression): - arg_types = {"this": False, "expressions": False, "compound": False} - - -class AlterSet(Expression): - arg_types = { - "expressions": False, - "option": False, - "tablespace": False, - "access_method": False, - "file_format": False, - "copy_options": False, - "tag": False, - "location": False, - "serde": False, - } - - -class RenameColumn(Expression): - arg_types = {"this": True, "to": True, "exists": False} - - -class AlterRename(Expression): - pass - - -class SwapTable(Expression): - pass - - -class Comment(Expression): - arg_types = { - "this": True, - "kind": True, - "expression": True, - "exists": False, - "materialized": False, - } - - -class Comprehension(Expression): - arg_types = { - "this": True, - "expression": True, - "position": False, - "iterator": True, - "condition": False, - } - - -# https://clickhouse.com/docs/en/engines/table-engines/mergetree-family/mergetree#mergetree-table-ttl -class MergeTreeTTLAction(Expression): - arg_types = { - "this": True, - "delete": False, - "recompress": False, - "to_disk": False, - "to_volume": False, - } - - -# https://clickhouse.com/docs/en/engines/table-engines/mergetree-family/mergetree#mergetree-table-ttl -class MergeTreeTTL(Expression): - arg_types = { - "expressions": True, - "where": False, - "group": False, - "aggregates": False, - } - - -# https://dev.mysql.com/doc/refman/8.0/en/create-table.html -class IndexConstraintOption(Expression): - arg_types = { - "key_block_size": False, - "using": False, - "parser": False, - "comment": False, - "visible": False, - "engine_attr": False, - "secondary_engine_attr": False, - } - - -class ColumnConstraint(Expression): - arg_types = {"this": False, "kind": True} - - @property - def kind(self) -> ColumnConstraintKind: - return self.args["kind"] - - -class ColumnConstraintKind(Expression): - pass - - -class AutoIncrementColumnConstraint(ColumnConstraintKind): - pass - - -class ZeroFillColumnConstraint(ColumnConstraint): - arg_types = {} - - -class PeriodForSystemTimeConstraint(ColumnConstraintKind): - arg_types = {"this": True, "expression": True} - - -class CaseSpecificColumnConstraint(ColumnConstraintKind): - arg_types = {"not_": True} - - -class CharacterSetColumnConstraint(ColumnConstraintKind): - arg_types = {"this": True} - - -class CheckColumnConstraint(ColumnConstraintKind): - arg_types = {"this": True, "enforced": False} - - -class ClusteredColumnConstraint(ColumnConstraintKind): - pass - - -class CollateColumnConstraint(ColumnConstraintKind): - pass - - -class CommentColumnConstraint(ColumnConstraintKind): - pass - - -class CompressColumnConstraint(ColumnConstraintKind): - arg_types = {"this": False} - - -class DateFormatColumnConstraint(ColumnConstraintKind): - arg_types = {"this": True} - - -class DefaultColumnConstraint(ColumnConstraintKind): - pass - - -class EncodeColumnConstraint(ColumnConstraintKind): - pass - - -# https://www.postgresql.org/docs/current/sql-createtable.html#SQL-CREATETABLE-EXCLUDE -class ExcludeColumnConstraint(ColumnConstraintKind): - pass - - -class EphemeralColumnConstraint(ColumnConstraintKind): - arg_types = {"this": False} - - -class WithOperator(Expression): - arg_types = {"this": True, "op": True} - - -class GeneratedAsIdentityColumnConstraint(ColumnConstraintKind): - # this: True -> ALWAYS, this: False -> BY DEFAULT - arg_types = { - "this": False, - "expression": False, - "on_null": False, - "start": False, - "increment": False, - "minvalue": False, - "maxvalue": False, - "cycle": False, - "order": False, - } - - -class GeneratedAsRowColumnConstraint(ColumnConstraintKind): - arg_types = {"start": False, "hidden": False} - - -# https://dev.mysql.com/doc/refman/8.0/en/create-table.html -# https://github.com/ClickHouse/ClickHouse/blob/master/src/Parsers/ParserCreateQuery.h#L646 -class IndexColumnConstraint(ColumnConstraintKind): - arg_types = { - "this": False, - "expressions": False, - "kind": False, - "index_type": False, - "options": False, - "expression": False, # Clickhouse - "granularity": False, - } - - -class InlineLengthColumnConstraint(ColumnConstraintKind): - pass - - -class NonClusteredColumnConstraint(ColumnConstraintKind): - pass - - -class NotForReplicationColumnConstraint(ColumnConstraintKind): - arg_types = {} - - -# https://docs.snowflake.com/en/sql-reference/sql/create-table -class MaskingPolicyColumnConstraint(ColumnConstraintKind): - arg_types = {"this": True, "expressions": False} - - -class NotNullColumnConstraint(ColumnConstraintKind): - arg_types = {"allow_null": False} - - -# https://dev.mysql.com/doc/refman/5.7/en/timestamp-initialization.html -class OnUpdateColumnConstraint(ColumnConstraintKind): - pass - - -class PrimaryKeyColumnConstraint(ColumnConstraintKind): - arg_types = {"desc": False, "options": False} - - -class TitleColumnConstraint(ColumnConstraintKind): - pass - - -class UniqueColumnConstraint(ColumnConstraintKind): - arg_types = { - "this": False, - "index_type": False, - "on_conflict": False, - "nulls": False, - "options": False, - } - - -class UppercaseColumnConstraint(ColumnConstraintKind): - arg_types: t.Dict[str, t.Any] = {} - - -# https://docs.risingwave.com/processing/watermarks#syntax -class WatermarkColumnConstraint(Expression): - arg_types = {"this": True, "expression": True} - - -class PathColumnConstraint(ColumnConstraintKind): - pass - - -# https://docs.snowflake.com/en/sql-reference/sql/create-table -class ProjectionPolicyColumnConstraint(ColumnConstraintKind): - pass - - -# computed column expression -# https://learn.microsoft.com/en-us/sql/t-sql/statements/create-table-transact-sql?view=sql-server-ver16 -class ComputedColumnConstraint(ColumnConstraintKind): - arg_types = { - "this": True, - "persisted": False, - "not_null": False, - "data_type": False, - } - - -class Constraint(Expression): - arg_types = {"this": True, "expressions": True} - - -class Delete(DML): - arg_types = { - "with_": False, - "this": False, - "using": False, - "where": False, - "returning": False, - "order": False, - "limit": False, - "tables": False, # Multiple-Table Syntax (MySQL) - "cluster": False, # Clickhouse - } - - def delete( - self, - table: ExpOrStr, - dialect: DialectType = None, - copy: bool = True, - **opts, - ) -> Delete: - """ - Create a DELETE expression or replace the table on an existing DELETE expression. - - Example: - >>> delete("tbl").sql() - 'DELETE FROM tbl' - - Args: - table: the table from which to delete. - dialect: the dialect used to parse the input expression. - copy: if `False`, modify this expression instance in-place. - opts: other options to use to parse the input expressions. - - Returns: - Delete: the modified expression. - """ - return _apply_builder( - expression=table, - instance=self, - arg="this", - dialect=dialect, - into=Table, - copy=copy, - **opts, - ) - - def where( - self, - *expressions: t.Optional[ExpOrStr], - append: bool = True, - dialect: DialectType = None, - copy: bool = True, - **opts, - ) -> Delete: - """ - Append to or set the WHERE expressions. - - Example: - >>> delete("tbl").where("x = 'a' OR x < 'b'").sql() - "DELETE FROM tbl WHERE x = 'a' OR x < 'b'" - - Args: - *expressions: the SQL code strings to parse. - If an `Expression` instance is passed, it will be used as-is. - Multiple expressions are combined with an AND operator. - append: if `True`, AND the new expressions to any existing expression. - Otherwise, this resets the expression. - dialect: the dialect used to parse the input expressions. - copy: if `False`, modify this expression instance in-place. - opts: other options to use to parse the input expressions. - - Returns: - Delete: the modified expression. - """ - return _apply_conjunction_builder( - *expressions, - instance=self, - arg="where", - append=append, - into=Where, - dialect=dialect, - copy=copy, - **opts, - ) - - -class Drop(Expression): - arg_types = { - "this": False, - "kind": False, - "expressions": False, - "exists": False, - "temporary": False, - "materialized": False, - "cascade": False, - "constraints": False, - "purge": False, - "cluster": False, - "concurrently": False, - } - - @property - def kind(self) -> t.Optional[str]: - kind = self.args.get("kind") - return kind and kind.upper() - - -# https://cloud.google.com/bigquery/docs/reference/standard-sql/export-statements -class Export(Expression): - arg_types = {"this": True, "connection": False, "options": True} - - -class Filter(Expression): - arg_types = {"this": True, "expression": True} - - -class Check(Expression): - pass - - -class Changes(Expression): - arg_types = {"information": True, "at_before": False, "end": False} - - -# https://docs.snowflake.com/en/sql-reference/constructs/connect-by -class Connect(Expression): - arg_types = {"start": False, "connect": True, "nocycle": False} - - -class CopyParameter(Expression): - arg_types = {"this": True, "expression": False, "expressions": False} - - -class Copy(DML): - arg_types = { - "this": True, - "kind": True, - "files": False, - "credentials": False, - "format": False, - "params": False, - } - - -class Credentials(Expression): - arg_types = { - "credentials": False, - "encryption": False, - "storage": False, - "iam_role": False, - "region": False, - } - - -class Prior(Expression): - pass - - -class Directory(Expression): - arg_types = {"this": True, "local": False, "row_format": False} - - -# https://docs.snowflake.com/en/user-guide/data-load-dirtables-query -class DirectoryStage(Expression): - pass - - -class ForeignKey(Expression): - arg_types = { - "expressions": False, - "reference": False, - "delete": False, - "update": False, - "options": False, - } - - -class ColumnPrefix(Expression): - arg_types = {"this": True, "expression": True} - - -class PrimaryKey(Expression): - arg_types = {"this": False, "expressions": True, "options": False, "include": False} - - -# https://www.postgresql.org/docs/9.1/sql-selectinto.html -# https://docs.aws.amazon.com/redshift/latest/dg/r_SELECT_INTO.html#r_SELECT_INTO-examples -class Into(Expression): - arg_types = { - "this": False, - "temporary": False, - "unlogged": False, - "bulk_collect": False, - "expressions": False, - } - - -class From(Expression): - @property - def name(self) -> str: - return self.this.name - - @property - def alias_or_name(self) -> str: - return self.this.alias_or_name - - -class Having(Expression): - pass - - -class Hint(Expression): - arg_types = {"expressions": True} - - -class JoinHint(Expression): - arg_types = {"this": True, "expressions": True} - - -class Identifier(Expression): - arg_types = {"this": True, "quoted": False, "global_": False, "temporary": False} - - @property - def quoted(self) -> bool: - return bool(self.args.get("quoted")) - - @property - def output_name(self) -> str: - return self.name - - -# https://www.postgresql.org/docs/current/indexes-opclass.html -class Opclass(Expression): - arg_types = {"this": True, "expression": True} - - -class Index(Expression): - arg_types = { - "this": False, - "table": False, - "unique": False, - "primary": False, - "amp": False, # teradata - "params": False, - } - - -class IndexParameters(Expression): - arg_types = { - "using": False, - "include": False, - "columns": False, - "with_storage": False, - "partition_by": False, - "tablespace": False, - "where": False, - "on": False, - } - - -class Insert(DDL, DML): - arg_types = { - "hint": False, - "with_": False, - "is_function": False, - "this": False, - "expression": False, - "conflict": False, - "returning": False, - "overwrite": False, - "exists": False, - "alternative": False, - "where": False, - "ignore": False, - "by_name": False, - "stored": False, - "partition": False, - "settings": False, - "source": False, - "default": False, - } - - def with_( - self, - alias: ExpOrStr, - as_: ExpOrStr, - recursive: t.Optional[bool] = None, - materialized: t.Optional[bool] = None, - append: bool = True, - dialect: DialectType = None, - copy: bool = True, - **opts, - ) -> Insert: - """ - Append to or set the common table expressions. - - Example: - >>> insert("SELECT x FROM cte", "t").with_("cte", as_="SELECT * FROM tbl").sql() - 'WITH cte AS (SELECT * FROM tbl) INSERT INTO t SELECT x FROM cte' - - Args: - alias: the SQL code string to parse as the table name. - If an `Expression` instance is passed, this is used as-is. - as_: the SQL code string to parse as the table expression. - If an `Expression` instance is passed, it will be used as-is. - recursive: set the RECURSIVE part of the expression. Defaults to `False`. - materialized: set the MATERIALIZED part of the expression. - append: if `True`, add to any existing expressions. - Otherwise, this resets the expressions. - dialect: the dialect used to parse the input expression. - copy: if `False`, modify this expression instance in-place. - opts: other options to use to parse the input expressions. - - Returns: - The modified expression. - """ - return _apply_cte_builder( - self, - alias, - as_, - recursive=recursive, - materialized=materialized, - append=append, - dialect=dialect, - copy=copy, - **opts, - ) - - -class ConditionalInsert(Expression): - arg_types = {"this": True, "expression": False, "else_": False} - - -class MultitableInserts(Expression): - arg_types = {"expressions": True, "kind": True, "source": True} - - -class OnConflict(Expression): - arg_types = { - "duplicate": False, - "expressions": False, - "action": False, - "conflict_keys": False, - "constraint": False, - "where": False, - } - - -class OnCondition(Expression): - arg_types = {"error": False, "empty": False, "null": False} - - -class Returning(Expression): - arg_types = {"expressions": True, "into": False} - - -# https://dev.mysql.com/doc/refman/8.0/en/charset-introducer.html -class Introducer(Expression): - arg_types = {"this": True, "expression": True} - - -# national char, like n'utf8' -class National(Expression): - pass - - -class LoadData(Expression): - arg_types = { - "this": True, - "local": False, - "overwrite": False, - "inpath": True, - "partition": False, - "input_format": False, - "serde": False, - } - - -class Partition(Expression): - arg_types = {"expressions": True, "subpartition": False} - - -class PartitionRange(Expression): - arg_types = {"this": True, "expression": False, "expressions": False} - - -# https://clickhouse.com/docs/en/sql-reference/statements/alter/partition#how-to-set-partition-expression -class PartitionId(Expression): - pass - - -class Fetch(Expression): - arg_types = { - "direction": False, - "count": False, - "limit_options": False, - } - - -class Grant(Expression): - arg_types = { - "privileges": True, - "kind": False, - "securable": True, - "principals": True, - "grant_option": False, - } - - -class Revoke(Expression): - arg_types = {**Grant.arg_types, "cascade": False} - - -class Group(Expression): - arg_types = { - "expressions": False, - "grouping_sets": False, - "cube": False, - "rollup": False, - "totals": False, - "all": False, - } - - -class Cube(Expression): - arg_types = {"expressions": False} - - -class Rollup(Expression): - arg_types = {"expressions": False} - - -class GroupingSets(Expression): - arg_types = {"expressions": True} - - -class Lambda(Expression): - arg_types = {"this": True, "expressions": True, "colon": False} - - -class Limit(Expression): - arg_types = { - "this": False, - "expression": True, - "offset": False, - "limit_options": False, - "expressions": False, - } - - -class LimitOptions(Expression): - arg_types = { - "percent": False, - "rows": False, - "with_ties": False, - } - - -class Literal(Condition): - arg_types = {"this": True, "is_string": True} - - @classmethod - def number(cls, number) -> Literal: - return cls(this=str(number), is_string=False) - - @classmethod - def string(cls, string) -> Literal: - return cls(this=str(string), is_string=True) - - @property - def output_name(self) -> str: - return self.name - - def to_py(self) -> int | str | Decimal: - if self.is_number: - try: - return int(self.this) - except ValueError: - return Decimal(self.this) - return self.this - - -class Join(Expression): - arg_types = { - "this": True, - "on": False, - "side": False, - "kind": False, - "using": False, - "method": False, - "global_": False, - "hint": False, - "match_condition": False, # Snowflake - "expressions": False, - "pivots": False, - } - - @property - def method(self) -> str: - return self.text("method").upper() - - @property - def kind(self) -> str: - return self.text("kind").upper() - - @property - def side(self) -> str: - return self.text("side").upper() - - @property - def hint(self) -> str: - return self.text("hint").upper() - - @property - def alias_or_name(self) -> str: - return self.this.alias_or_name - - @property - def is_semi_or_anti_join(self) -> bool: - return self.kind in ("SEMI", "ANTI") - - def on( - self, - *expressions: t.Optional[ExpOrStr], - append: bool = True, - dialect: DialectType = None, - copy: bool = True, - **opts, - ) -> Join: - """ - Append to or set the ON expressions. - - Example: - >>> import sqlglot - >>> sqlglot.parse_one("JOIN x", into=Join).on("y = 1").sql() - 'JOIN x ON y = 1' - - Args: - *expressions: the SQL code strings to parse. - If an `Expression` instance is passed, it will be used as-is. - Multiple expressions are combined with an AND operator. - append: if `True`, AND the new expressions to any existing expression. - Otherwise, this resets the expression. - dialect: the dialect used to parse the input expressions. - copy: if `False`, modify this expression instance in-place. - opts: other options to use to parse the input expressions. - - Returns: - The modified Join expression. - """ - join = _apply_conjunction_builder( - *expressions, - instance=self, - arg="on", - append=append, - dialect=dialect, - copy=copy, - **opts, - ) - - if join.kind == "CROSS": - join.set("kind", None) - - return join - - def using( - self, - *expressions: t.Optional[ExpOrStr], - append: bool = True, - dialect: DialectType = None, - copy: bool = True, - **opts, - ) -> Join: - """ - Append to or set the USING expressions. - - Example: - >>> import sqlglot - >>> sqlglot.parse_one("JOIN x", into=Join).using("foo", "bla").sql() - 'JOIN x USING (foo, bla)' - - Args: - *expressions: the SQL code strings to parse. - If an `Expression` instance is passed, it will be used as-is. - append: if `True`, concatenate the new expressions to the existing "using" list. - Otherwise, this resets the expression. - dialect: the dialect used to parse the input expressions. - copy: if `False`, modify this expression instance in-place. - opts: other options to use to parse the input expressions. - - Returns: - The modified Join expression. - """ - join = _apply_list_builder( - *expressions, - instance=self, - arg="using", - append=append, - dialect=dialect, - copy=copy, - **opts, - ) - - if join.kind == "CROSS": - join.set("kind", None) - - return join - - -class Lateral(UDTF): - arg_types = { - "this": True, - "view": False, - "outer": False, - "alias": False, - "cross_apply": False, # True -> CROSS APPLY, False -> OUTER APPLY - "ordinality": False, - } - - -# https://docs.snowflake.com/sql-reference/literals-table -# https://docs.snowflake.com/en/sql-reference/functions-table#using-a-table-function -class TableFromRows(UDTF): - arg_types = { - "this": True, - "alias": False, - "joins": False, - "pivots": False, - "sample": False, - } - - -class MatchRecognizeMeasure(Expression): - arg_types = { - "this": True, - "window_frame": False, - } - - -class MatchRecognize(Expression): - arg_types = { - "partition_by": False, - "order": False, - "measures": False, - "rows": False, - "after": False, - "pattern": False, - "define": False, - "alias": False, - } - - -# Clickhouse FROM FINAL modifier -# https://clickhouse.com/docs/en/sql-reference/statements/select/from/#final-modifier -class Final(Expression): - pass - - -class Offset(Expression): - arg_types = {"this": False, "expression": True, "expressions": False} - - -class Order(Expression): - arg_types = {"this": False, "expressions": True, "siblings": False} - - -# https://clickhouse.com/docs/en/sql-reference/statements/select/order-by#order-by-expr-with-fill-modifier -class WithFill(Expression): - arg_types = { - "from_": False, - "to": False, - "step": False, - "interpolate": False, - } - - -# hive specific sorts -# https://cwiki.apache.org/confluence/display/Hive/LanguageManual+SortBy -class Cluster(Order): - pass - - -class Distribute(Order): - pass - - -class Sort(Order): - pass - - -class Ordered(Expression): - arg_types = {"this": True, "desc": False, "nulls_first": True, "with_fill": False} - - @property - def name(self) -> str: - return self.this.name - - -class Property(Expression): - arg_types = {"this": True, "value": True} - - -class GrantPrivilege(Expression): - arg_types = {"this": True, "expressions": False} - - -class GrantPrincipal(Expression): - arg_types = {"this": True, "kind": False} - - -class AllowedValuesProperty(Expression): - arg_types = {"expressions": True} - - -class AlgorithmProperty(Property): - arg_types = {"this": True} - - -class AutoIncrementProperty(Property): - arg_types = {"this": True} - - -# https://docs.aws.amazon.com/prescriptive-guidance/latest/materialized-views-redshift/refreshing-materialized-views.html -class AutoRefreshProperty(Property): - arg_types = {"this": True} - - -class BackupProperty(Property): - arg_types = {"this": True} - - -# https://doris.apache.org/docs/sql-manual/sql-statements/table-and-view/async-materialized-view/CREATE-ASYNC-MATERIALIZED-VIEW/ -class BuildProperty(Property): - arg_types = {"this": True} - - -class BlockCompressionProperty(Property): - arg_types = { - "autotemp": False, - "always": False, - "default": False, - "manual": False, - "never": False, - } - - -class CharacterSetProperty(Property): - arg_types = {"this": True, "default": True} - - -class ChecksumProperty(Property): - arg_types = {"on": False, "default": False} - - -class CollateProperty(Property): - arg_types = {"this": True, "default": False} - - -class CopyGrantsProperty(Property): - arg_types = {} - - -class DataBlocksizeProperty(Property): - arg_types = { - "size": False, - "units": False, - "minimum": False, - "maximum": False, - "default": False, - } - - -class DataDeletionProperty(Property): - arg_types = {"on": True, "filter_column": False, "retention_period": False} - - -class DefinerProperty(Property): - arg_types = {"this": True} - - -class DistKeyProperty(Property): - arg_types = {"this": True} - - -# https://docs.starrocks.io/docs/sql-reference/sql-statements/data-definition/CREATE_TABLE/#distribution_desc -# https://doris.apache.org/docs/sql-manual/sql-statements/Data-Definition-Statements/Create/CREATE-TABLE?_highlight=create&_highlight=table#distribution_desc -class DistributedByProperty(Property): - arg_types = {"expressions": False, "kind": True, "buckets": False, "order": False} - - -class DistStyleProperty(Property): - arg_types = {"this": True} - - -class DuplicateKeyProperty(Property): - arg_types = {"expressions": True} - - -class EngineProperty(Property): - arg_types = {"this": True} - - -class HeapProperty(Property): - arg_types = {} - - -class ToTableProperty(Property): - arg_types = {"this": True} - - -class ExecuteAsProperty(Property): - arg_types = {"this": True} - - -class ExternalProperty(Property): - arg_types = {"this": False} - - -class FallbackProperty(Property): - arg_types = {"no": True, "protection": False} - - -# https://docs.databricks.com/aws/en/sql/language-manual/sql-ref-syntax-ddl-create-table-hiveformat -class FileFormatProperty(Property): - arg_types = {"this": False, "expressions": False, "hive_format": False} - - -class CredentialsProperty(Property): - arg_types = {"expressions": True} - - -class FreespaceProperty(Property): - arg_types = {"this": True, "percent": False} - - -class GlobalProperty(Property): - arg_types = {} - - -class IcebergProperty(Property): - arg_types = {} - - -class InheritsProperty(Property): - arg_types = {"expressions": True} - - -class InputModelProperty(Property): - arg_types = {"this": True} - - -class OutputModelProperty(Property): - arg_types = {"this": True} - - -class IsolatedLoadingProperty(Property): - arg_types = {"no": False, "concurrent": False, "target": False} - - -class JournalProperty(Property): - arg_types = { - "no": False, - "dual": False, - "before": False, - "local": False, - "after": False, - } - - -class LanguageProperty(Property): - arg_types = {"this": True} - - -class EnviromentProperty(Property): - arg_types = {"expressions": True} - - -# spark ddl -class ClusteredByProperty(Property): - arg_types = {"expressions": True, "sorted_by": False, "buckets": True} - - -class DictProperty(Property): - arg_types = {"this": True, "kind": True, "settings": False} - - -class DictSubProperty(Property): - pass - - -class DictRange(Property): - arg_types = {"this": True, "min": True, "max": True} - - -class DynamicProperty(Property): - arg_types = {} - - -# Clickhouse CREATE ... ON CLUSTER modifier -# https://clickhouse.com/docs/en/sql-reference/distributed-ddl -class OnCluster(Property): - arg_types = {"this": True} - - -# Clickhouse EMPTY table "property" -class EmptyProperty(Property): - arg_types = {} - - -class LikeProperty(Property): - arg_types = {"this": True, "expressions": False} - - -class LocationProperty(Property): - arg_types = {"this": True} - - -class LockProperty(Property): - arg_types = {"this": True} - - -class LockingProperty(Property): - arg_types = { - "this": False, - "kind": True, - "for_or_in": False, - "lock_type": True, - "override": False, - } - - -class LogProperty(Property): - arg_types = {"no": True} - - -class MaterializedProperty(Property): - arg_types = {"this": False} - - -class MergeBlockRatioProperty(Property): - arg_types = {"this": False, "no": False, "default": False, "percent": False} - - -class NoPrimaryIndexProperty(Property): - arg_types = {} - - -class OnProperty(Property): - arg_types = {"this": True} - - -class OnCommitProperty(Property): - arg_types = {"delete": False} - - -class PartitionedByProperty(Property): - arg_types = {"this": True} - - -class PartitionedByBucket(Property): - arg_types = {"this": True, "expression": True} - - -class PartitionByTruncate(Property): - arg_types = {"this": True, "expression": True} - - -# https://docs.starrocks.io/docs/sql-reference/sql-statements/table_bucket_part_index/CREATE_TABLE/ -class PartitionByRangeProperty(Property): - arg_types = {"partition_expressions": True, "create_expressions": True} - - -# https://docs.starrocks.io/docs/table_design/data_distribution/#range-partitioning -class PartitionByRangePropertyDynamic(Expression): - arg_types = {"this": False, "start": True, "end": True, "every": True} - - -# https://doris.apache.org/docs/table-design/data-partitioning/manual-partitioning -class PartitionByListProperty(Property): - arg_types = {"partition_expressions": True, "create_expressions": True} - - -# https://doris.apache.org/docs/table-design/data-partitioning/manual-partitioning -class PartitionList(Expression): - arg_types = {"this": True, "expressions": True} - - -# https://doris.apache.org/docs/sql-manual/sql-statements/table-and-view/async-materialized-view/CREATE-ASYNC-MATERIALIZED-VIEW -class RefreshTriggerProperty(Property): - arg_types = { - "method": True, - "kind": False, - "every": False, - "unit": False, - "starts": False, - } - - -# https://docs.starrocks.io/docs/sql-reference/sql-statements/table_bucket_part_index/CREATE_TABLE/ -class UniqueKeyProperty(Property): - arg_types = {"expressions": True} - - -# https://www.postgresql.org/docs/current/sql-createtable.html -class PartitionBoundSpec(Expression): - # this -> IN / MODULUS, expression -> REMAINDER, from_expressions -> FROM (...), to_expressions -> TO (...) - arg_types = { - "this": False, - "expression": False, - "from_expressions": False, - "to_expressions": False, - } - - -class PartitionedOfProperty(Property): - # this -> parent_table (schema), expression -> FOR VALUES ... / DEFAULT - arg_types = {"this": True, "expression": True} - - -class StreamingTableProperty(Property): - arg_types = {} - - -class RemoteWithConnectionModelProperty(Property): - arg_types = {"this": True} - - -class ReturnsProperty(Property): - arg_types = {"this": False, "is_table": False, "table": False, "null": False} - - -class StrictProperty(Property): - arg_types = {} - - -class RowFormatProperty(Property): - arg_types = {"this": True} - - -class RowFormatDelimitedProperty(Property): - # https://cwiki.apache.org/confluence/display/hive/languagemanual+dml - arg_types = { - "fields": False, - "escaped": False, - "collection_items": False, - "map_keys": False, - "lines": False, - "null": False, - "serde": False, - } - - -class RowFormatSerdeProperty(Property): - arg_types = {"this": True, "serde_properties": False} - - -# https://spark.apache.org/docs/3.1.2/sql-ref-syntax-qry-select-transform.html -class QueryTransform(Expression): - arg_types = { - "expressions": True, - "command_script": True, - "schema": False, - "row_format_before": False, - "record_writer": False, - "row_format_after": False, - "record_reader": False, - } - - -class SampleProperty(Property): - arg_types = {"this": True} - - -# https://prestodb.io/docs/current/sql/create-view.html#synopsis -class SecurityProperty(Property): - arg_types = {"this": True} - - -class SchemaCommentProperty(Property): - arg_types = {"this": True} - - -class SemanticView(Expression): - arg_types = { - "this": True, - "metrics": False, - "dimensions": False, - "facts": False, - "where": False, - } - - -class SerdeProperties(Property): - arg_types = {"expressions": True, "with_": False} - - -class SetProperty(Property): - arg_types = {"multi": True} - - -class SharingProperty(Property): - arg_types = {"this": False} - - -class SetConfigProperty(Property): - arg_types = {"this": True} - - -class SettingsProperty(Property): - arg_types = {"expressions": True} - - -class SortKeyProperty(Property): - arg_types = {"this": True, "compound": False} - - -class SqlReadWriteProperty(Property): - arg_types = {"this": True} - - -class SqlSecurityProperty(Property): - arg_types = {"this": True} - - -class StabilityProperty(Property): - arg_types = {"this": True} - - -class StorageHandlerProperty(Property): - arg_types = {"this": True} - - -class TemporaryProperty(Property): - arg_types = {"this": False} - - -class SecureProperty(Property): - arg_types = {} - - -# https://docs.snowflake.com/en/sql-reference/sql/create-table -class Tags(ColumnConstraintKind, Property): - arg_types = {"expressions": True} - - -class TransformModelProperty(Property): - arg_types = {"expressions": True} - - -class TransientProperty(Property): - arg_types = {"this": False} - - -class UnloggedProperty(Property): - arg_types = {} - - -# https://docs.snowflake.com/en/sql-reference/sql/create-table#create-table-using-template -class UsingTemplateProperty(Property): - arg_types = {"this": True} - - -# https://learn.microsoft.com/en-us/sql/t-sql/statements/create-view-transact-sql?view=sql-server-ver16 -class ViewAttributeProperty(Property): - arg_types = {"this": True} - - -class VolatileProperty(Property): - arg_types = {"this": False} - - -class WithDataProperty(Property): - arg_types = {"no": True, "statistics": False} - - -class WithJournalTableProperty(Property): - arg_types = {"this": True} - - -class WithSchemaBindingProperty(Property): - arg_types = {"this": True} - - -class WithSystemVersioningProperty(Property): - arg_types = { - "on": False, - "this": False, - "data_consistency": False, - "retention_period": False, - "with_": True, - } - - -class WithProcedureOptions(Property): - arg_types = {"expressions": True} - - -class EncodeProperty(Property): - arg_types = {"this": True, "properties": False, "key": False} - - -class IncludeProperty(Property): - arg_types = {"this": True, "alias": False, "column_def": False} - - -class ForceProperty(Property): - arg_types = {} - - -class Properties(Expression): - arg_types = {"expressions": True} - - NAME_TO_PROPERTY = { - "ALGORITHM": AlgorithmProperty, - "AUTO_INCREMENT": AutoIncrementProperty, - "CHARACTER SET": CharacterSetProperty, - "CLUSTERED_BY": ClusteredByProperty, - "COLLATE": CollateProperty, - "COMMENT": SchemaCommentProperty, - "CREDENTIALS": CredentialsProperty, - "DEFINER": DefinerProperty, - "DISTKEY": DistKeyProperty, - "DISTRIBUTED_BY": DistributedByProperty, - "DISTSTYLE": DistStyleProperty, - "ENGINE": EngineProperty, - "EXECUTE AS": ExecuteAsProperty, - "FORMAT": FileFormatProperty, - "LANGUAGE": LanguageProperty, - "LOCATION": LocationProperty, - "LOCK": LockProperty, - "PARTITIONED_BY": PartitionedByProperty, - "RETURNS": ReturnsProperty, - "ROW_FORMAT": RowFormatProperty, - "SORTKEY": SortKeyProperty, - "ENCODE": EncodeProperty, - "INCLUDE": IncludeProperty, - } - - PROPERTY_TO_NAME = {v: k for k, v in NAME_TO_PROPERTY.items()} - - # CREATE property locations - # Form: schema specified - # create [POST_CREATE] - # table a [POST_NAME] - # (b int) [POST_SCHEMA] - # with ([POST_WITH]) - # index (b) [POST_INDEX] - # - # Form: alias selection - # create [POST_CREATE] - # table a [POST_NAME] - # as [POST_ALIAS] (select * from b) [POST_EXPRESSION] - # index (c) [POST_INDEX] - class Location(AutoName): - POST_CREATE = auto() - POST_NAME = auto() - POST_SCHEMA = auto() - POST_WITH = auto() - POST_ALIAS = auto() - POST_EXPRESSION = auto() - POST_INDEX = auto() - UNSUPPORTED = auto() - - @classmethod - def from_dict(cls, properties_dict: t.Dict) -> Properties: - expressions = [] - for key, value in properties_dict.items(): - property_cls = cls.NAME_TO_PROPERTY.get(key.upper()) - if property_cls: - expressions.append(property_cls(this=convert(value))) - else: - expressions.append( - Property(this=Literal.string(key), value=convert(value)) - ) - - return cls(expressions=expressions) - - -class Qualify(Expression): - pass - - -class InputOutputFormat(Expression): - arg_types = {"input_format": False, "output_format": False} - - -# https://www.ibm.com/docs/en/ias?topic=procedures-return-statement-in-sql -class Return(Expression): - pass - - -class Reference(Expression): - arg_types = {"this": True, "expressions": False, "options": False} - - -class Tuple(Expression): - arg_types = {"expressions": False} - - def isin( - self, - *expressions: t.Any, - query: t.Optional[ExpOrStr] = None, - unnest: t.Optional[ExpOrStr] | t.Collection[ExpOrStr] = None, - copy: bool = True, - **opts, - ) -> In: - return In( - this=maybe_copy(self, copy), - expressions=[convert(e, copy=copy) for e in expressions], - query=maybe_parse(query, copy=copy, **opts) if query else None, - unnest=( - Unnest( - expressions=[ - maybe_parse(t.cast(ExpOrStr, e), copy=copy, **opts) - for e in ensure_list(unnest) - ] - ) - if unnest - else None - ), - ) - - -QUERY_MODIFIERS = { - "match": False, - "laterals": False, - "joins": False, - "connect": False, - "pivots": False, - "prewhere": False, - "where": False, - "group": False, - "having": False, - "qualify": False, - "windows": False, - "distribute": False, - "sort": False, - "cluster": False, - "order": False, - "limit": False, - "offset": False, - "locks": False, - "sample": False, - "settings": False, - "format": False, - "options": False, -} - - -# https://learn.microsoft.com/en-us/sql/t-sql/queries/option-clause-transact-sql?view=sql-server-ver16 -# https://learn.microsoft.com/en-us/sql/t-sql/queries/hints-transact-sql-query?view=sql-server-ver16 -class QueryOption(Expression): - arg_types = {"this": True, "expression": False} - - -# https://learn.microsoft.com/en-us/sql/t-sql/queries/hints-transact-sql-table?view=sql-server-ver16 -class WithTableHint(Expression): - arg_types = {"expressions": True} - - -# https://dev.mysql.com/doc/refman/8.0/en/index-hints.html -class IndexTableHint(Expression): - arg_types = {"this": True, "expressions": False, "target": False} - - -# https://docs.snowflake.com/en/sql-reference/constructs/at-before -class HistoricalData(Expression): - arg_types = {"this": True, "kind": True, "expression": True} - - -# https://docs.snowflake.com/en/sql-reference/sql/put -class Put(Expression): - arg_types = {"this": True, "target": True, "properties": False} - - -# https://docs.snowflake.com/en/sql-reference/sql/get -class Get(Expression): - arg_types = {"this": True, "target": True, "properties": False} - - -class Table(Expression): - arg_types = { - "this": False, - "alias": False, - "db": False, - "catalog": False, - "laterals": False, - "joins": False, - "pivots": False, - "hints": False, - "system_time": False, - "version": False, - "format": False, - "pattern": False, - "ordinality": False, - "when": False, - "only": False, - "partition": False, - "changes": False, - "rows_from": False, - "sample": False, - "indexed": False, - } - - @property - def name(self) -> str: - if not self.this or isinstance(self.this, Func): - return "" - return self.this.name - - @property - def db(self) -> str: - return self.text("db") - - @property - def catalog(self) -> str: - return self.text("catalog") - - @property - def selects(self) -> t.List[Expression]: - return [] - - @property - def named_selects(self) -> t.List[str]: - return [] - - @property - def parts(self) -> t.List[Expression]: - """Return the parts of a table in order catalog, db, table.""" - parts: t.List[Expression] = [] - - for arg in ("catalog", "db", "this"): - part = self.args.get(arg) - - if isinstance(part, Dot): - parts.extend(part.flatten()) - elif isinstance(part, Expression): - parts.append(part) - - return parts - - def to_column(self, copy: bool = True) -> Expression: - parts = self.parts - last_part = parts[-1] - - if isinstance(last_part, Identifier): - col: Expression = column(*reversed(parts[0:4]), fields=parts[4:], copy=copy) # type: ignore - else: - # This branch will be reached if a function or array is wrapped in a `Table` - col = last_part - - alias = self.args.get("alias") - if alias: - col = alias_(col, alias.this, copy=copy) - - return col - - -class SetOperation(Query): - arg_types = { - "with_": False, - "this": True, - "expression": True, - "distinct": False, - "by_name": False, - "side": False, - "kind": False, - "on": False, - **QUERY_MODIFIERS, - } - - def select( - self: S, - *expressions: t.Optional[ExpOrStr], - append: bool = True, - dialect: DialectType = None, - copy: bool = True, - **opts, - ) -> S: - this = maybe_copy(self, copy) - this.this.unnest().select( - *expressions, append=append, dialect=dialect, copy=False, **opts - ) - this.expression.unnest().select( - *expressions, append=append, dialect=dialect, copy=False, **opts - ) - return this - - @property - def named_selects(self) -> t.List[str]: - expression = self - while isinstance(expression, SetOperation): - expression = expression.this.unnest() - return expression.named_selects - - @property - def is_star(self) -> bool: - return self.this.is_star or self.expression.is_star - - @property - def selects(self) -> t.List[Expression]: - expression = self - while isinstance(expression, SetOperation): - expression = expression.this.unnest() - return expression.selects - - @property - def left(self) -> Query: - return self.this - - @property - def right(self) -> Query: - return self.expression - - @property - def kind(self) -> str: - return self.text("kind").upper() - - @property - def side(self) -> str: - return self.text("side").upper() - - -class Union(SetOperation): - pass - - -class Except(SetOperation): - pass - - -class Intersect(SetOperation): - pass - - -class Update(DML): - arg_types = { - "with_": False, - "this": False, - "expressions": False, - "from_": False, - "where": False, - "returning": False, - "order": False, - "limit": False, - "options": False, - } - - def table( - self, - expression: ExpOrStr, - dialect: DialectType = None, - copy: bool = True, - **opts, - ) -> Update: - """ - Set the table to update. - - Example: - >>> Update().table("my_table").set_("x = 1").sql() - 'UPDATE my_table SET x = 1' - - Args: - expression : the SQL code strings to parse. - If a `Table` instance is passed, this is used as-is. - If another `Expression` instance is passed, it will be wrapped in a `Table`. - dialect: the dialect used to parse the input expression. - copy: if `False`, modify this expression instance in-place. - opts: other options to use to parse the input expressions. - - Returns: - The modified Update expression. - """ - return _apply_builder( - expression=expression, - instance=self, - arg="this", - into=Table, - prefix=None, - dialect=dialect, - copy=copy, - **opts, - ) - - def set_( - self, - *expressions: ExpOrStr, - append: bool = True, - dialect: DialectType = None, - copy: bool = True, - **opts, - ) -> Update: - """ - Append to or set the SET expressions. - - Example: - >>> Update().table("my_table").set_("x = 1").sql() - 'UPDATE my_table SET x = 1' - - Args: - *expressions: the SQL code strings to parse. - If `Expression` instance(s) are passed, they will be used as-is. - Multiple expressions are combined with a comma. - append: if `True`, add the new expressions to any existing SET expressions. - Otherwise, this resets the expressions. - dialect: the dialect used to parse the input expressions. - copy: if `False`, modify this expression instance in-place. - opts: other options to use to parse the input expressions. - """ - return _apply_list_builder( - *expressions, - instance=self, - arg="expressions", - append=append, - into=Expression, - prefix=None, - dialect=dialect, - copy=copy, - **opts, - ) - - def where( - self, - *expressions: t.Optional[ExpOrStr], - append: bool = True, - dialect: DialectType = None, - copy: bool = True, - **opts, - ) -> Select: - """ - Append to or set the WHERE expressions. - - Example: - >>> Update().table("tbl").set_("x = 1").where("x = 'a' OR x < 'b'").sql() - "UPDATE tbl SET x = 1 WHERE x = 'a' OR x < 'b'" - - Args: - *expressions: the SQL code strings to parse. - If an `Expression` instance is passed, it will be used as-is. - Multiple expressions are combined with an AND operator. - append: if `True`, AND the new expressions to any existing expression. - Otherwise, this resets the expression. - dialect: the dialect used to parse the input expressions. - copy: if `False`, modify this expression instance in-place. - opts: other options to use to parse the input expressions. - - Returns: - Select: the modified expression. - """ - return _apply_conjunction_builder( - *expressions, - instance=self, - arg="where", - append=append, - into=Where, - dialect=dialect, - copy=copy, - **opts, - ) - - def from_( - self, - expression: t.Optional[ExpOrStr] = None, - dialect: DialectType = None, - copy: bool = True, - **opts, - ) -> Update: - """ - Set the FROM expression. - - Example: - >>> Update().table("my_table").set_("x = 1").from_("baz").sql() - 'UPDATE my_table SET x = 1 FROM baz' - - Args: - expression : the SQL code strings to parse. - If a `From` instance is passed, this is used as-is. - If another `Expression` instance is passed, it will be wrapped in a `From`. - If nothing is passed in then a from is not applied to the expression - dialect: the dialect used to parse the input expression. - copy: if `False`, modify this expression instance in-place. - opts: other options to use to parse the input expressions. - - Returns: - The modified Update expression. - """ - if not expression: - return maybe_copy(self, copy) - - return _apply_builder( - expression=expression, - instance=self, - arg="from_", - into=From, - prefix="FROM", - dialect=dialect, - copy=copy, - **opts, - ) - - def with_( - self, - alias: ExpOrStr, - as_: ExpOrStr, - recursive: t.Optional[bool] = None, - materialized: t.Optional[bool] = None, - append: bool = True, - dialect: DialectType = None, - copy: bool = True, - **opts, - ) -> Update: - """ - Append to or set the common table expressions. - - Example: - >>> Update().table("my_table").set_("x = 1").from_("baz").with_("baz", "SELECT id FROM foo").sql() - 'WITH baz AS (SELECT id FROM foo) UPDATE my_table SET x = 1 FROM baz' - - Args: - alias: the SQL code string to parse as the table name. - If an `Expression` instance is passed, this is used as-is. - as_: the SQL code string to parse as the table expression. - If an `Expression` instance is passed, it will be used as-is. - recursive: set the RECURSIVE part of the expression. Defaults to `False`. - materialized: set the MATERIALIZED part of the expression. - append: if `True`, add to any existing expressions. - Otherwise, this resets the expressions. - dialect: the dialect used to parse the input expression. - copy: if `False`, modify this expression instance in-place. - opts: other options to use to parse the input expressions. - - Returns: - The modified expression. - """ - return _apply_cte_builder( - self, - alias, - as_, - recursive=recursive, - materialized=materialized, - append=append, - dialect=dialect, - copy=copy, - **opts, - ) - - -# DuckDB supports VALUES followed by https://duckdb.org/docs/stable/sql/query_syntax/limit -class Values(UDTF): - arg_types = { - "expressions": True, - "alias": False, - "order": False, - "limit": False, - "offset": False, - } - - -class Var(Expression): - pass - - -class Version(Expression): - """ - Time travel, iceberg, bigquery etc - https://trino.io/docs/current/connector/iceberg.html?highlight=snapshot#using-snapshots - https://www.databricks.com/blog/2019/02/04/introducing-delta-time-travel-for-large-scale-data-lakes.html - https://cloud.google.com/bigquery/docs/reference/standard-sql/query-syntax#for_system_time_as_of - https://learn.microsoft.com/en-us/sql/relational-databases/tables/querying-data-in-a-system-versioned-temporal-table?view=sql-server-ver16 - this is either TIMESTAMP or VERSION - kind is ("AS OF", "BETWEEN") - """ - - arg_types = {"this": True, "kind": True, "expression": False} - - -class Schema(Expression): - arg_types = {"this": False, "expressions": False} - - -# https://dev.mysql.com/doc/refman/8.0/en/select.html -# https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/SELECT.html -class Lock(Expression): - arg_types = {"update": True, "expressions": False, "wait": False, "key": False} - - -class Select(Query): - arg_types = { - "with_": False, - "kind": False, - "expressions": False, - "hint": False, - "distinct": False, - "into": False, - "from_": False, - "operation_modifiers": False, - **QUERY_MODIFIERS, - } - - def from_( - self, - expression: ExpOrStr, - dialect: DialectType = None, - copy: bool = True, - **opts, - ) -> Select: - """ - Set the FROM expression. - - Example: - >>> Select().from_("tbl").select("x").sql() - 'SELECT x FROM tbl' - - Args: - expression : the SQL code strings to parse. - If a `From` instance is passed, this is used as-is. - If another `Expression` instance is passed, it will be wrapped in a `From`. - dialect: the dialect used to parse the input expression. - copy: if `False`, modify this expression instance in-place. - opts: other options to use to parse the input expressions. - - Returns: - The modified Select expression. - """ - return _apply_builder( - expression=expression, - instance=self, - arg="from_", - into=From, - prefix="FROM", - dialect=dialect, - copy=copy, - **opts, - ) - - def group_by( - self, - *expressions: t.Optional[ExpOrStr], - append: bool = True, - dialect: DialectType = None, - copy: bool = True, - **opts, - ) -> Select: - """ - Set the GROUP BY expression. - - Example: - >>> Select().from_("tbl").select("x", "COUNT(1)").group_by("x").sql() - 'SELECT x, COUNT(1) FROM tbl GROUP BY x' - - Args: - *expressions: the SQL code strings to parse. - If a `Group` instance is passed, this is used as-is. - If another `Expression` instance is passed, it will be wrapped in a `Group`. - If nothing is passed in then a group by is not applied to the expression - append: if `True`, add to any existing expressions. - Otherwise, this flattens all the `Group` expression into a single expression. - dialect: the dialect used to parse the input expression. - copy: if `False`, modify this expression instance in-place. - opts: other options to use to parse the input expressions. - - Returns: - The modified Select expression. - """ - if not expressions: - return self if not copy else self.copy() - - return _apply_child_list_builder( - *expressions, - instance=self, - arg="group", - append=append, - copy=copy, - prefix="GROUP BY", - into=Group, - dialect=dialect, - **opts, - ) - - def sort_by( - self, - *expressions: t.Optional[ExpOrStr], - append: bool = True, - dialect: DialectType = None, - copy: bool = True, - **opts, - ) -> Select: - """ - Set the SORT BY expression. - - Example: - >>> Select().from_("tbl").select("x").sort_by("x DESC").sql(dialect="hive") - 'SELECT x FROM tbl SORT BY x DESC' - - Args: - *expressions: the SQL code strings to parse. - If a `Group` instance is passed, this is used as-is. - If another `Expression` instance is passed, it will be wrapped in a `SORT`. - append: if `True`, add to any existing expressions. - Otherwise, this flattens all the `Order` expression into a single expression. - dialect: the dialect used to parse the input expression. - copy: if `False`, modify this expression instance in-place. - opts: other options to use to parse the input expressions. - - Returns: - The modified Select expression. - """ - return _apply_child_list_builder( - *expressions, - instance=self, - arg="sort", - append=append, - copy=copy, - prefix="SORT BY", - into=Sort, - dialect=dialect, - **opts, - ) - - def cluster_by( - self, - *expressions: t.Optional[ExpOrStr], - append: bool = True, - dialect: DialectType = None, - copy: bool = True, - **opts, - ) -> Select: - """ - Set the CLUSTER BY expression. - - Example: - >>> Select().from_("tbl").select("x").cluster_by("x DESC").sql(dialect="hive") - 'SELECT x FROM tbl CLUSTER BY x DESC' - - Args: - *expressions: the SQL code strings to parse. - If a `Group` instance is passed, this is used as-is. - If another `Expression` instance is passed, it will be wrapped in a `Cluster`. - append: if `True`, add to any existing expressions. - Otherwise, this flattens all the `Order` expression into a single expression. - dialect: the dialect used to parse the input expression. - copy: if `False`, modify this expression instance in-place. - opts: other options to use to parse the input expressions. - - Returns: - The modified Select expression. - """ - return _apply_child_list_builder( - *expressions, - instance=self, - arg="cluster", - append=append, - copy=copy, - prefix="CLUSTER BY", - into=Cluster, - dialect=dialect, - **opts, - ) - - def select( - self, - *expressions: t.Optional[ExpOrStr], - append: bool = True, - dialect: DialectType = None, - copy: bool = True, - **opts, - ) -> Select: - return _apply_list_builder( - *expressions, - instance=self, - arg="expressions", - append=append, - dialect=dialect, - into=Expression, - copy=copy, - **opts, - ) - - def lateral( - self, - *expressions: t.Optional[ExpOrStr], - append: bool = True, - dialect: DialectType = None, - copy: bool = True, - **opts, - ) -> Select: - """ - Append to or set the LATERAL expressions. - - Example: - >>> Select().select("x").lateral("OUTER explode(y) tbl2 AS z").from_("tbl").sql() - 'SELECT x FROM tbl LATERAL VIEW OUTER EXPLODE(y) tbl2 AS z' - - Args: - *expressions: the SQL code strings to parse. - If an `Expression` instance is passed, it will be used as-is. - append: if `True`, add to any existing expressions. - Otherwise, this resets the expressions. - dialect: the dialect used to parse the input expressions. - copy: if `False`, modify this expression instance in-place. - opts: other options to use to parse the input expressions. - - Returns: - The modified Select expression. - """ - return _apply_list_builder( - *expressions, - instance=self, - arg="laterals", - append=append, - into=Lateral, - prefix="LATERAL VIEW", - dialect=dialect, - copy=copy, - **opts, - ) - - def join( - self, - expression: ExpOrStr, - on: t.Optional[ExpOrStr] = None, - using: t.Optional[ExpOrStr | t.Collection[ExpOrStr]] = None, - append: bool = True, - join_type: t.Optional[str] = None, - join_alias: t.Optional[Identifier | str] = None, - dialect: DialectType = None, - copy: bool = True, - **opts, - ) -> Select: - """ - Append to or set the JOIN expressions. - - Example: - >>> Select().select("*").from_("tbl").join("tbl2", on="tbl1.y = tbl2.y").sql() - 'SELECT * FROM tbl JOIN tbl2 ON tbl1.y = tbl2.y' - - >>> Select().select("1").from_("a").join("b", using=["x", "y", "z"]).sql() - 'SELECT 1 FROM a JOIN b USING (x, y, z)' - - Use `join_type` to change the type of join: - - >>> Select().select("*").from_("tbl").join("tbl2", on="tbl1.y = tbl2.y", join_type="left outer").sql() - 'SELECT * FROM tbl LEFT OUTER JOIN tbl2 ON tbl1.y = tbl2.y' - - Args: - expression: the SQL code string to parse. - If an `Expression` instance is passed, it will be used as-is. - on: optionally specify the join "on" criteria as a SQL string. - If an `Expression` instance is passed, it will be used as-is. - using: optionally specify the join "using" criteria as a SQL string. - If an `Expression` instance is passed, it will be used as-is. - append: if `True`, add to any existing expressions. - Otherwise, this resets the expressions. - join_type: if set, alter the parsed join type. - join_alias: an optional alias for the joined source. - dialect: the dialect used to parse the input expressions. - copy: if `False`, modify this expression instance in-place. - opts: other options to use to parse the input expressions. - - Returns: - Select: the modified expression. - """ - parse_args: t.Dict[str, t.Any] = {"dialect": dialect, **opts} - - try: - expression = maybe_parse(expression, into=Join, prefix="JOIN", **parse_args) - except ParseError: - expression = maybe_parse(expression, into=(Join, Expression), **parse_args) - - join = expression if isinstance(expression, Join) else Join(this=expression) - - if isinstance(join.this, Select): - join.this.replace(join.this.subquery()) - - if join_type: - method: t.Optional[Token] - side: t.Optional[Token] - kind: t.Optional[Token] - - method, side, kind = maybe_parse(join_type, into="JOIN_TYPE", **parse_args) # type: ignore - - if method: - join.set("method", method.text) - if side: - join.set("side", side.text) - if kind: - join.set("kind", kind.text) - - if on: - on = and_(*ensure_list(on), dialect=dialect, copy=copy, **opts) - join.set("on", on) - - if using: - join = _apply_list_builder( - *ensure_list(using), - instance=join, - arg="using", - append=append, - copy=copy, - into=Identifier, - **opts, - ) - - if join_alias: - join.set("this", alias_(join.this, join_alias, table=True)) - - return _apply_list_builder( - join, - instance=self, - arg="joins", - append=append, - copy=copy, - **opts, - ) - - def having( - self, - *expressions: t.Optional[ExpOrStr], - append: bool = True, - dialect: DialectType = None, - copy: bool = True, - **opts, - ) -> Select: - """ - Append to or set the HAVING expressions. - - Example: - >>> Select().select("x", "COUNT(y)").from_("tbl").group_by("x").having("COUNT(y) > 3").sql() - 'SELECT x, COUNT(y) FROM tbl GROUP BY x HAVING COUNT(y) > 3' - - Args: - *expressions: the SQL code strings to parse. - If an `Expression` instance is passed, it will be used as-is. - Multiple expressions are combined with an AND operator. - append: if `True`, AND the new expressions to any existing expression. - Otherwise, this resets the expression. - dialect: the dialect used to parse the input expressions. - copy: if `False`, modify this expression instance in-place. - opts: other options to use to parse the input expressions. - - Returns: - The modified Select expression. - """ - return _apply_conjunction_builder( - *expressions, - instance=self, - arg="having", - append=append, - into=Having, - dialect=dialect, - copy=copy, - **opts, - ) - - def window( - self, - *expressions: t.Optional[ExpOrStr], - append: bool = True, - dialect: DialectType = None, - copy: bool = True, - **opts, - ) -> Select: - return _apply_list_builder( - *expressions, - instance=self, - arg="windows", - append=append, - into=Window, - dialect=dialect, - copy=copy, - **opts, - ) - - def qualify( - self, - *expressions: t.Optional[ExpOrStr], - append: bool = True, - dialect: DialectType = None, - copy: bool = True, - **opts, - ) -> Select: - return _apply_conjunction_builder( - *expressions, - instance=self, - arg="qualify", - append=append, - into=Qualify, - dialect=dialect, - copy=copy, - **opts, - ) - - def distinct( - self, *ons: t.Optional[ExpOrStr], distinct: bool = True, copy: bool = True - ) -> Select: - """ - Set the OFFSET expression. - - Example: - >>> Select().from_("tbl").select("x").distinct().sql() - 'SELECT DISTINCT x FROM tbl' - - Args: - ons: the expressions to distinct on - distinct: whether the Select should be distinct - copy: if `False`, modify this expression instance in-place. - - Returns: - Select: the modified expression. - """ - instance = maybe_copy(self, copy) - on = ( - Tuple(expressions=[maybe_parse(on, copy=copy) for on in ons if on]) - if ons - else None - ) - instance.set("distinct", Distinct(on=on) if distinct else None) - return instance - - def ctas( - self, - table: ExpOrStr, - properties: t.Optional[t.Dict] = None, - dialect: DialectType = None, - copy: bool = True, - **opts, - ) -> Create: - """ - Convert this expression to a CREATE TABLE AS statement. - - Example: - >>> Select().select("*").from_("tbl").ctas("x").sql() - 'CREATE TABLE x AS SELECT * FROM tbl' - - Args: - table: the SQL code string to parse as the table name. - If another `Expression` instance is passed, it will be used as-is. - properties: an optional mapping of table properties - dialect: the dialect used to parse the input table. - copy: if `False`, modify this expression instance in-place. - opts: other options to use to parse the input table. - - Returns: - The new Create expression. - """ - instance = maybe_copy(self, copy) - table_expression = maybe_parse(table, into=Table, dialect=dialect, **opts) - - properties_expression = None - if properties: - properties_expression = Properties.from_dict(properties) - - return Create( - this=table_expression, - kind="TABLE", - expression=instance, - properties=properties_expression, - ) - - def lock(self, update: bool = True, copy: bool = True) -> Select: - """ - Set the locking read mode for this expression. - - Examples: - >>> Select().select("x").from_("tbl").where("x = 'a'").lock().sql("mysql") - "SELECT x FROM tbl WHERE x = 'a' FOR UPDATE" - - >>> Select().select("x").from_("tbl").where("x = 'a'").lock(update=False).sql("mysql") - "SELECT x FROM tbl WHERE x = 'a' FOR SHARE" - - Args: - update: if `True`, the locking type will be `FOR UPDATE`, else it will be `FOR SHARE`. - copy: if `False`, modify this expression instance in-place. - - Returns: - The modified expression. - """ - inst = maybe_copy(self, copy) - inst.set("locks", [Lock(update=update)]) - - return inst - - def hint( - self, *hints: ExpOrStr, dialect: DialectType = None, copy: bool = True - ) -> Select: - """ - Set hints for this expression. - - Examples: - >>> Select().select("x").from_("tbl").hint("BROADCAST(y)").sql(dialect="spark") - 'SELECT /*+ BROADCAST(y) */ x FROM tbl' - - Args: - hints: The SQL code strings to parse as the hints. - If an `Expression` instance is passed, it will be used as-is. - dialect: The dialect used to parse the hints. - copy: If `False`, modify this expression instance in-place. - - Returns: - The modified expression. - """ - inst = maybe_copy(self, copy) - inst.set( - "hint", - Hint( - expressions=[maybe_parse(h, copy=copy, dialect=dialect) for h in hints] - ), - ) - - return inst - - @property - def named_selects(self) -> t.List[str]: - selects = [] - - for e in self.expressions: - if e.alias_or_name: - selects.append(e.output_name) - elif isinstance(e, Aliases): - selects.extend([a.name for a in e.aliases]) - return selects - - @property - def is_star(self) -> bool: - return any(expression.is_star for expression in self.expressions) - - @property - def selects(self) -> t.List[Expression]: - return self.expressions - - -UNWRAPPED_QUERIES = (Select, SetOperation) - - -class Subquery(DerivedTable, Query): - arg_types = { - "this": True, - "alias": False, - "with_": False, - **QUERY_MODIFIERS, - } - - def unnest(self): - """Returns the first non subquery.""" - expression = self - while isinstance(expression, Subquery): - expression = expression.this - return expression - - def unwrap(self) -> Subquery: - expression = self - while expression.same_parent and expression.is_wrapper: - expression = t.cast(Subquery, expression.parent) - return expression - - def select( - self, - *expressions: t.Optional[ExpOrStr], - append: bool = True, - dialect: DialectType = None, - copy: bool = True, - **opts, - ) -> Subquery: - this = maybe_copy(self, copy) - this.unnest().select( - *expressions, append=append, dialect=dialect, copy=False, **opts - ) - return this - - @property - def is_wrapper(self) -> bool: - """ - Whether this Subquery acts as a simple wrapper around another expression. - - SELECT * FROM (((SELECT * FROM t))) - ^ - This corresponds to a "wrapper" Subquery node - """ - return all(v is None for k, v in self.args.items() if k != "this") - - @property - def is_star(self) -> bool: - return self.this.is_star - - @property - def output_name(self) -> str: - return self.alias - - -class TableSample(Expression): - arg_types = { - "expressions": False, - "method": False, - "bucket_numerator": False, - "bucket_denominator": False, - "bucket_field": False, - "percent": False, - "rows": False, - "size": False, - "seed": False, - } - - -class Tag(Expression): - """Tags are used for generating arbitrary sql like SELECT x.""" - - arg_types = { - "this": False, - "prefix": False, - "postfix": False, - } - - -# Represents both the standard SQL PIVOT operator and DuckDB's "simplified" PIVOT syntax -# https://duckdb.org/docs/sql/statements/pivot -class Pivot(Expression): - arg_types = { - "this": False, - "alias": False, - "expressions": False, - "fields": False, - "unpivot": False, - "using": False, - "group": False, - "columns": False, - "include_nulls": False, - "default_on_null": False, - "into": False, - "with_": False, - } - - @property - def unpivot(self) -> bool: - return bool(self.args.get("unpivot")) - - @property - def fields(self) -> t.List[Expression]: - return self.args.get("fields", []) - - -# https://duckdb.org/docs/sql/statements/unpivot#simplified-unpivot-syntax -# UNPIVOT ... INTO [NAME VALUE ][...,] -class UnpivotColumns(Expression): - arg_types = {"this": True, "expressions": True} - - -class Window(Condition): - arg_types = { - "this": True, - "partition_by": False, - "order": False, - "spec": False, - "alias": False, - "over": False, - "first": False, - } - - -class WindowSpec(Expression): - arg_types = { - "kind": False, - "start": False, - "start_side": False, - "end": False, - "end_side": False, - "exclude": False, - } - - -class PreWhere(Expression): - pass - - -class Where(Expression): - pass - - -class Star(Expression): - arg_types = {"except_": False, "replace": False, "rename": False} - - @property - def name(self) -> str: - return "*" - - @property - def output_name(self) -> str: - return self.name - - -class Parameter(Condition): - arg_types = {"this": True, "expression": False} - - -class SessionParameter(Condition): - arg_types = {"this": True, "kind": False} - - -# https://www.databricks.com/blog/parameterized-queries-pyspark -# https://jdbc.postgresql.org/documentation/query/#using-the-statement-or-preparedstatement-interface -class Placeholder(Condition): - arg_types = {"this": False, "kind": False, "widget": False, "jdbc": False} - - @property - def name(self) -> str: - return self.this or "?" - - -class Null(Condition): - arg_types: t.Dict[str, t.Any] = {} - - @property - def name(self) -> str: - return "NULL" - - def to_py(self) -> Lit[None]: - return None - - -class Boolean(Condition): - def to_py(self) -> bool: - return self.this - - -class DataTypeParam(Expression): - arg_types = {"this": True, "expression": False} - - @property - def name(self) -> str: - return self.this.name - - -# The `nullable` arg is helpful when transpiling types from other dialects to ClickHouse, which -# assumes non-nullable types by default. Values `None` and `True` mean the type is nullable. -class DataType(Expression): - arg_types = { - "this": True, - "expressions": False, - "nested": False, - "values": False, - "prefix": False, - "kind": False, - "nullable": False, - } - - class Type(AutoName): - ARRAY = auto() - AGGREGATEFUNCTION = auto() - SIMPLEAGGREGATEFUNCTION = auto() - BIGDECIMAL = auto() - BIGINT = auto() - BIGNUM = auto() - BIGSERIAL = auto() - BINARY = auto() - BIT = auto() - BLOB = auto() - BOOLEAN = auto() - BPCHAR = auto() - CHAR = auto() - DATE = auto() - DATE32 = auto() - DATEMULTIRANGE = auto() - DATERANGE = auto() - DATETIME = auto() - DATETIME2 = auto() - DATETIME64 = auto() - DECIMAL = auto() - DECIMAL32 = auto() - DECIMAL64 = auto() - DECIMAL128 = auto() - DECIMAL256 = auto() - DECFLOAT = auto() - DOUBLE = auto() - DYNAMIC = auto() - ENUM = auto() - ENUM8 = auto() - ENUM16 = auto() - FILE = auto() - FIXEDSTRING = auto() - FLOAT = auto() - GEOGRAPHY = auto() - GEOGRAPHYPOINT = auto() - GEOMETRY = auto() - POINT = auto() - RING = auto() - LINESTRING = auto() - MULTILINESTRING = auto() - POLYGON = auto() - MULTIPOLYGON = auto() - HLLSKETCH = auto() - HSTORE = auto() - IMAGE = auto() - INET = auto() - INT = auto() - INT128 = auto() - INT256 = auto() - INT4MULTIRANGE = auto() - INT4RANGE = auto() - INT8MULTIRANGE = auto() - INT8RANGE = auto() - INTERVAL = auto() - IPADDRESS = auto() - IPPREFIX = auto() - IPV4 = auto() - IPV6 = auto() - JSON = auto() - JSONB = auto() - LIST = auto() - LONGBLOB = auto() - LONGTEXT = auto() - LOWCARDINALITY = auto() - MAP = auto() - MEDIUMBLOB = auto() - MEDIUMINT = auto() - MEDIUMTEXT = auto() - MONEY = auto() - NAME = auto() - NCHAR = auto() - NESTED = auto() - NOTHING = auto() - NULL = auto() - NUMMULTIRANGE = auto() - NUMRANGE = auto() - NVARCHAR = auto() - OBJECT = auto() - RANGE = auto() - ROWVERSION = auto() - SERIAL = auto() - SET = auto() - SMALLDATETIME = auto() - SMALLINT = auto() - SMALLMONEY = auto() - SMALLSERIAL = auto() - STRUCT = auto() - SUPER = auto() - TEXT = auto() - TINYBLOB = auto() - TINYTEXT = auto() - TIME = auto() - TIMETZ = auto() - TIME_NS = auto() - TIMESTAMP = auto() - TIMESTAMPNTZ = auto() - TIMESTAMPLTZ = auto() - TIMESTAMPTZ = auto() - TIMESTAMP_S = auto() - TIMESTAMP_MS = auto() - TIMESTAMP_NS = auto() - TINYINT = auto() - TSMULTIRANGE = auto() - TSRANGE = auto() - TSTZMULTIRANGE = auto() - TSTZRANGE = auto() - UBIGINT = auto() - UINT = auto() - UINT128 = auto() - UINT256 = auto() - UMEDIUMINT = auto() - UDECIMAL = auto() - UDOUBLE = auto() - UNION = auto() - UNKNOWN = auto() # Sentinel value, useful for type annotation - USERDEFINED = "USER-DEFINED" - USMALLINT = auto() - UTINYINT = auto() - UUID = auto() - VARBINARY = auto() - VARCHAR = auto() - VARIANT = auto() - VECTOR = auto() - XML = auto() - YEAR = auto() - TDIGEST = auto() - - STRUCT_TYPES = { - Type.FILE, - Type.NESTED, - Type.OBJECT, - Type.STRUCT, - Type.UNION, - } - - ARRAY_TYPES = { - Type.ARRAY, - Type.LIST, - } - - NESTED_TYPES = { - *STRUCT_TYPES, - *ARRAY_TYPES, - Type.MAP, - } - - TEXT_TYPES = { - Type.CHAR, - Type.NCHAR, - Type.NVARCHAR, - Type.TEXT, - Type.VARCHAR, - Type.NAME, - } - - SIGNED_INTEGER_TYPES = { - Type.BIGINT, - Type.INT, - Type.INT128, - Type.INT256, - Type.MEDIUMINT, - Type.SMALLINT, - Type.TINYINT, - } - - UNSIGNED_INTEGER_TYPES = { - Type.UBIGINT, - Type.UINT, - Type.UINT128, - Type.UINT256, - Type.UMEDIUMINT, - Type.USMALLINT, - Type.UTINYINT, - } - - INTEGER_TYPES = { - *SIGNED_INTEGER_TYPES, - *UNSIGNED_INTEGER_TYPES, - Type.BIT, - } - - FLOAT_TYPES = { - Type.DOUBLE, - Type.FLOAT, - } - - REAL_TYPES = { - *FLOAT_TYPES, - Type.BIGDECIMAL, - Type.DECIMAL, - Type.DECIMAL32, - Type.DECIMAL64, - Type.DECIMAL128, - Type.DECIMAL256, - Type.DECFLOAT, - Type.MONEY, - Type.SMALLMONEY, - Type.UDECIMAL, - Type.UDOUBLE, - } - - NUMERIC_TYPES = { - *INTEGER_TYPES, - *REAL_TYPES, - } - - TEMPORAL_TYPES = { - Type.DATE, - Type.DATE32, - Type.DATETIME, - Type.DATETIME2, - Type.DATETIME64, - Type.SMALLDATETIME, - Type.TIME, - Type.TIMESTAMP, - Type.TIMESTAMPNTZ, - Type.TIMESTAMPLTZ, - Type.TIMESTAMPTZ, - Type.TIMESTAMP_MS, - Type.TIMESTAMP_NS, - Type.TIMESTAMP_S, - Type.TIMETZ, - } - - @classmethod - def build( - cls, - dtype: DATA_TYPE, - dialect: DialectType = None, - udt: bool = False, - copy: bool = True, - **kwargs, - ) -> DataType: - """ - Constructs a DataType object. - - Args: - dtype: the data type of interest. - dialect: the dialect to use for parsing `dtype`, in case it's a string. - udt: when set to True, `dtype` will be used as-is if it can't be parsed into a - DataType, thus creating a user-defined type. - copy: whether to copy the data type. - kwargs: additional arguments to pass in the constructor of DataType. - - Returns: - The constructed DataType object. - """ - from bigframes_vendored.sqlglot import parse_one - - if isinstance(dtype, str): - if dtype.upper() == "UNKNOWN": - return DataType(this=DataType.Type.UNKNOWN, **kwargs) - - try: - data_type_exp = parse_one( - dtype, read=dialect, into=DataType, error_level=ErrorLevel.IGNORE - ) - except ParseError: - if udt: - return DataType( - this=DataType.Type.USERDEFINED, kind=dtype, **kwargs - ) - raise - elif isinstance(dtype, (Identifier, Dot)) and udt: - return DataType(this=DataType.Type.USERDEFINED, kind=dtype, **kwargs) - elif isinstance(dtype, DataType.Type): - data_type_exp = DataType(this=dtype) - elif isinstance(dtype, DataType): - return maybe_copy(dtype, copy) - else: - raise ValueError( - f"Invalid data type: {type(dtype)}. Expected str or DataType.Type" - ) - - return DataType(**{**data_type_exp.args, **kwargs}) - - def is_type(self, *dtypes: DATA_TYPE, check_nullable: bool = False) -> bool: - """ - Checks whether this DataType matches one of the provided data types. Nested types or precision - will be compared using "structural equivalence" semantics, so e.g. array != array. - - Args: - dtypes: the data types to compare this DataType to. - check_nullable: whether to take the NULLABLE type constructor into account for the comparison. - If false, it means that NULLABLE is equivalent to INT. - - Returns: - True, if and only if there is a type in `dtypes` which is equal to this DataType. - """ - self_is_nullable = self.args.get("nullable") - for dtype in dtypes: - other_type = DataType.build(dtype, copy=False, udt=True) - other_is_nullable = other_type.args.get("nullable") - if ( - other_type.expressions - or (check_nullable and (self_is_nullable or other_is_nullable)) - or self.this == DataType.Type.USERDEFINED - or other_type.this == DataType.Type.USERDEFINED - ): - matches = self == other_type - else: - matches = self.this == other_type.this - - if matches: - return True - return False - - -# https://www.postgresql.org/docs/15/datatype-pseudo.html -class PseudoType(DataType): - arg_types = {"this": True} - - -# https://www.postgresql.org/docs/15/datatype-oid.html -class ObjectIdentifier(DataType): - arg_types = {"this": True} - - -# WHERE x EXISTS|ALL|ANY|SOME(SELECT ...) -class SubqueryPredicate(Predicate): - pass - - -class All(SubqueryPredicate): - pass - - -class Any(SubqueryPredicate): - pass - - -# Commands to interact with the databases or engines. For most of the command -# expressions we parse whatever comes after the command's name as a string. -class Command(Expression): - arg_types = {"this": True, "expression": False} - - -class Transaction(Expression): - arg_types = {"this": False, "modes": False, "mark": False} - - -class Commit(Expression): - arg_types = {"chain": False, "this": False, "durability": False} - - -class Rollback(Expression): - arg_types = {"savepoint": False, "this": False} - - -class Alter(Expression): - arg_types = { - "this": False, - "kind": True, - "actions": True, - "exists": False, - "only": False, - "options": False, - "cluster": False, - "not_valid": False, - "check": False, - "cascade": False, - } - - @property - def kind(self) -> t.Optional[str]: - kind = self.args.get("kind") - return kind and kind.upper() - - @property - def actions(self) -> t.List[Expression]: - return self.args.get("actions") or [] - - -class AlterSession(Expression): - arg_types = {"expressions": True, "unset": False} - - -class Analyze(Expression): - arg_types = { - "kind": False, - "this": False, - "options": False, - "mode": False, - "partition": False, - "expression": False, - "properties": False, - } - - -class AnalyzeStatistics(Expression): - arg_types = { - "kind": True, - "option": False, - "this": False, - "expressions": False, - } - - -class AnalyzeHistogram(Expression): - arg_types = { - "this": True, - "expressions": True, - "expression": False, - "update_options": False, - } - - -class AnalyzeSample(Expression): - arg_types = {"kind": True, "sample": True} - - -class AnalyzeListChainedRows(Expression): - arg_types = {"expression": False} - - -class AnalyzeDelete(Expression): - arg_types = {"kind": False} - - -class AnalyzeWith(Expression): - arg_types = {"expressions": True} - - -class AnalyzeValidate(Expression): - arg_types = { - "kind": True, - "this": False, - "expression": False, - } - - -class AnalyzeColumns(Expression): - pass - - -class UsingData(Expression): - pass - - -class AddConstraint(Expression): - arg_types = {"expressions": True} - - -class AddPartition(Expression): - arg_types = {"this": True, "exists": False, "location": False} - - -class AttachOption(Expression): - arg_types = {"this": True, "expression": False} - - -class DropPartition(Expression): - arg_types = {"expressions": True, "exists": False} - - -# https://clickhouse.com/docs/en/sql-reference/statements/alter/partition#replace-partition -class ReplacePartition(Expression): - arg_types = {"expression": True, "source": True} - - -# Binary expressions like (ADD a b) -class Binary(Condition): - arg_types = {"this": True, "expression": True} - - @property - def left(self) -> Expression: - return self.this - - @property - def right(self) -> Expression: - return self.expression - - -class Add(Binary): - pass - - -class Connector(Binary): - pass - - -class BitwiseAnd(Binary): - arg_types = {"this": True, "expression": True, "padside": False} - - -class BitwiseLeftShift(Binary): - pass - - -class BitwiseOr(Binary): - arg_types = {"this": True, "expression": True, "padside": False} - - -class BitwiseRightShift(Binary): - pass - - -class BitwiseXor(Binary): - arg_types = {"this": True, "expression": True, "padside": False} - - -class Div(Binary): - arg_types = {"this": True, "expression": True, "typed": False, "safe": False} - - -class Overlaps(Binary): - pass - - -class ExtendsLeft(Binary): - pass - - -class ExtendsRight(Binary): - pass - - -class Dot(Binary): - @property - def is_star(self) -> bool: - return self.expression.is_star - - @property - def name(self) -> str: - return self.expression.name - - @property - def output_name(self) -> str: - return self.name - - @classmethod - def build(self, expressions: t.Sequence[Expression]) -> Dot: - """Build a Dot object with a sequence of expressions.""" - if len(expressions) < 2: - raise ValueError("Dot requires >= 2 expressions.") - - return t.cast(Dot, reduce(lambda x, y: Dot(this=x, expression=y), expressions)) - - @property - def parts(self) -> t.List[Expression]: - """Return the parts of a table / column in order catalog, db, table.""" - this, *parts = self.flatten() - - parts.reverse() - - for arg in COLUMN_PARTS: - part = this.args.get(arg) - - if isinstance(part, Expression): - parts.append(part) - - parts.reverse() - return parts - - -DATA_TYPE = t.Union[str, Identifier, Dot, DataType, DataType.Type] - - -class DPipe(Binary): - arg_types = {"this": True, "expression": True, "safe": False} - - -class EQ(Binary, Predicate): - pass - - -class NullSafeEQ(Binary, Predicate): - pass - - -class NullSafeNEQ(Binary, Predicate): - pass - - -# Represents e.g. := in DuckDB which is mostly used for setting parameters -class PropertyEQ(Binary): - pass - - -class Distance(Binary): - pass - - -class Escape(Binary): - pass - - -class Glob(Binary, Predicate): - pass - - -class GT(Binary, Predicate): - pass - - -class GTE(Binary, Predicate): - pass - - -class ILike(Binary, Predicate): - pass - - -class IntDiv(Binary): - pass - - -class Is(Binary, Predicate): - pass - - -class Kwarg(Binary): - """Kwarg in special functions like func(kwarg => y).""" - - -class Like(Binary, Predicate): - pass - - -class Match(Binary, Predicate): - pass - - -class LT(Binary, Predicate): - pass - - -class LTE(Binary, Predicate): - pass - - -class Mod(Binary): - pass - - -class Mul(Binary): - pass - - -class NEQ(Binary, Predicate): - pass - - -# https://www.postgresql.org/docs/current/ddl-schemas.html#DDL-SCHEMAS-PATH -class Operator(Binary): - arg_types = {"this": True, "operator": True, "expression": True} - - -class SimilarTo(Binary, Predicate): - pass - - -class Sub(Binary): - pass - - -# https://www.postgresql.org/docs/current/functions-range.html -# Represents range adjacency operator: -|- -class Adjacent(Binary): - pass - - -# Unary Expressions -# (NOT a) -class Unary(Condition): - pass - - -class BitwiseNot(Unary): - pass - - -class Not(Unary): - pass - - -class Paren(Unary): - @property - def output_name(self) -> str: - return self.this.name - - -class Neg(Unary): - def to_py(self) -> int | Decimal: - if self.is_number: - return self.this.to_py() * -1 - return super().to_py() - - -class Alias(Expression): - arg_types = {"this": True, "alias": False} - - @property - def output_name(self) -> str: - return self.alias - - -# BigQuery requires the UNPIVOT column list aliases to be either strings or ints, but -# other dialects require identifiers. This enables us to transpile between them easily. -class PivotAlias(Alias): - pass - - -# Represents Snowflake's ANY [ ORDER BY ... ] syntax -# https://docs.snowflake.com/en/sql-reference/constructs/pivot -class PivotAny(Expression): - arg_types = {"this": False} - - -class Aliases(Expression): - arg_types = {"this": True, "expressions": True} - - @property - def aliases(self): - return self.expressions - - -# https://docs.aws.amazon.com/redshift/latest/dg/query-super.html -class AtIndex(Expression): - arg_types = {"this": True, "expression": True} - - -class AtTimeZone(Expression): - arg_types = {"this": True, "zone": True} - - -class FromTimeZone(Expression): - arg_types = {"this": True, "zone": True} - - -class FormatPhrase(Expression): - """Format override for a column in Teradata. - Can be expanded to additional dialects as needed - - https://docs.teradata.com/r/Enterprise_IntelliFlex_VMware/SQL-Data-Types-and-Literals/Data-Type-Formats-and-Format-Phrases/FORMAT - """ - - arg_types = {"this": True, "format": True} - - -class Between(Predicate): - arg_types = {"this": True, "low": True, "high": True, "symmetric": False} - - -class Bracket(Condition): - # https://cloud.google.com/bigquery/docs/reference/standard-sql/operators#array_subscript_operator - arg_types = { - "this": True, - "expressions": True, - "offset": False, - "safe": False, - "returns_list_for_maps": False, - } - - @property - def output_name(self) -> str: - if len(self.expressions) == 1: - return self.expressions[0].output_name - - return super().output_name - - -class Distinct(Expression): - arg_types = {"expressions": False, "on": False} - - -class In(Predicate): - arg_types = { - "this": True, - "expressions": False, - "query": False, - "unnest": False, - "field": False, - "is_global": False, - } - - -# https://cloud.google.com/bigquery/docs/reference/standard-sql/procedural-language#for-in -class ForIn(Expression): - arg_types = {"this": True, "expression": True} - - -class TimeUnit(Expression): - """Automatically converts unit arg into a var.""" - - arg_types = {"unit": False} - - UNABBREVIATED_UNIT_NAME = { - "D": "DAY", - "H": "HOUR", - "M": "MINUTE", - "MS": "MILLISECOND", - "NS": "NANOSECOND", - "Q": "QUARTER", - "S": "SECOND", - "US": "MICROSECOND", - "W": "WEEK", - "Y": "YEAR", - } - - VAR_LIKE = (Column, Literal, Var) - - def __init__(self, **args): - unit = args.get("unit") - if type(unit) in self.VAR_LIKE and not ( - isinstance(unit, Column) and len(unit.parts) != 1 - ): - args["unit"] = Var( - this=(self.UNABBREVIATED_UNIT_NAME.get(unit.name) or unit.name).upper() - ) - elif isinstance(unit, Week): - unit.set("this", Var(this=unit.this.name.upper())) - - super().__init__(**args) - - @property - def unit(self) -> t.Optional[Var | IntervalSpan]: - return self.args.get("unit") - - -class IntervalOp(TimeUnit): - arg_types = {"unit": False, "expression": True} - - def interval(self): - return Interval( - this=self.expression.copy(), - unit=self.unit.copy() if self.unit else None, - ) - - -# https://www.oracletutorial.com/oracle-basics/oracle-interval/ -# https://trino.io/docs/current/language/types.html#interval-day-to-second -# https://docs.databricks.com/en/sql/language-manual/data-types/interval-type.html -class IntervalSpan(DataType): - arg_types = {"this": True, "expression": True} - - -class Interval(TimeUnit): - arg_types = {"this": False, "unit": False} - - -class IgnoreNulls(Expression): - pass - - -class RespectNulls(Expression): - pass - - -# https://cloud.google.com/bigquery/docs/reference/standard-sql/aggregate-function-calls#max_min_clause -class HavingMax(Expression): - arg_types = {"this": True, "expression": True, "max": True} - - -# Functions -class Func(Condition): - """ - The base class for all function expressions. - - Attributes: - is_var_len_args (bool): if set to True the last argument defined in arg_types will be - treated as a variable length argument and the argument's value will be stored as a list. - _sql_names (list): the SQL name (1st item in the list) and aliases (subsequent items) for this - function expression. These values are used to map this node to a name during parsing as - well as to provide the function's name during SQL string generation. By default the SQL - name is set to the expression's class name transformed to snake case. - """ - - is_var_len_args = False - - @classmethod - def from_arg_list(cls, args): - if cls.is_var_len_args: - all_arg_keys = list(cls.arg_types) - # If this function supports variable length argument treat the last argument as such. - non_var_len_arg_keys = ( - all_arg_keys[:-1] if cls.is_var_len_args else all_arg_keys - ) - num_non_var = len(non_var_len_arg_keys) - - args_dict = { - arg_key: arg for arg, arg_key in zip(args, non_var_len_arg_keys) - } - args_dict[all_arg_keys[-1]] = args[num_non_var:] - else: - args_dict = {arg_key: arg for arg, arg_key in zip(args, cls.arg_types)} - - return cls(**args_dict) - - @classmethod - def sql_names(cls): - if cls is Func: - raise NotImplementedError( - "SQL name is only supported by concrete function implementations" - ) - if "_sql_names" not in cls.__dict__: - cls._sql_names = [camel_to_snake_case(cls.__name__)] - return cls._sql_names - - @classmethod - def sql_name(cls): - sql_names = cls.sql_names() - assert sql_names, f"Expected non-empty 'sql_names' for Func: {cls.__name__}." - return sql_names[0] - - @classmethod - def default_parser_mappings(cls): - return {name: cls.from_arg_list for name in cls.sql_names()} - - -class Typeof(Func): - pass - - -class Acos(Func): - pass - - -class Acosh(Func): - pass - - -class Asin(Func): - pass - - -class Asinh(Func): - pass - - -class Atan(Func): - arg_types = {"this": True, "expression": False} - - -class Atanh(Func): - pass - - -class Atan2(Func): - arg_types = {"this": True, "expression": True} - - -class Cot(Func): - pass - - -class Coth(Func): - pass - - -class Cos(Func): - pass - - -class Csc(Func): - pass - - -class Csch(Func): - pass - - -class Sec(Func): - pass - - -class Sech(Func): - pass - - -class Sin(Func): - pass - - -class Sinh(Func): - pass - - -class Tan(Func): - pass - - -class Tanh(Func): - pass - - -class Degrees(Func): - pass - - -class Cosh(Func): - pass - - -class CosineDistance(Func): - arg_types = {"this": True, "expression": True} - - -class DotProduct(Func): - arg_types = {"this": True, "expression": True} - - -class EuclideanDistance(Func): - arg_types = {"this": True, "expression": True} - - -class ManhattanDistance(Func): - arg_types = {"this": True, "expression": True} - - -class JarowinklerSimilarity(Func): - arg_types = {"this": True, "expression": True} - - -class AggFunc(Func): - pass - - -class BitwiseAndAgg(AggFunc): - pass - - -class BitwiseOrAgg(AggFunc): - pass - - -class BitwiseXorAgg(AggFunc): - pass - - -class BoolxorAgg(AggFunc): - pass - - -class BitwiseCount(Func): - pass - - -class BitmapBucketNumber(Func): - pass - - -class BitmapCount(Func): - pass - - -class BitmapBitPosition(Func): - pass - - -class BitmapConstructAgg(AggFunc): - pass - - -class BitmapOrAgg(AggFunc): - pass - - -class ByteLength(Func): - pass - - -class Boolnot(Func): - pass - - -class Booland(Func): - arg_types = {"this": True, "expression": True} - - -class Boolor(Func): - arg_types = {"this": True, "expression": True} - - -# https://cloud.google.com/bigquery/docs/reference/standard-sql/json_functions#bool_for_json -class JSONBool(Func): - pass - - -class ArrayRemove(Func): - arg_types = {"this": True, "expression": True} - - -class ParameterizedAgg(AggFunc): - arg_types = {"this": True, "expressions": True, "params": True} - - -class Abs(Func): - pass - - -class ArgMax(AggFunc): - arg_types = {"this": True, "expression": True, "count": False} - _sql_names = ["ARG_MAX", "ARGMAX", "MAX_BY"] - - -class ArgMin(AggFunc): - arg_types = {"this": True, "expression": True, "count": False} - _sql_names = ["ARG_MIN", "ARGMIN", "MIN_BY"] - - -class ApproxTopK(AggFunc): - arg_types = {"this": True, "expression": False, "counters": False} - - -# https://docs.snowflake.com/en/sql-reference/functions/approx_top_k_accumulate -# https://spark.apache.org/docs/preview/api/sql/index.html#approx_top_k_accumulate -class ApproxTopKAccumulate(AggFunc): - arg_types = {"this": True, "expression": False} - - -# https://docs.snowflake.com/en/sql-reference/functions/approx_top_k_combine -class ApproxTopKCombine(AggFunc): - arg_types = {"this": True, "expression": False} - - -class ApproxTopKEstimate(Func): - arg_types = {"this": True, "expression": False} - - -class ApproxTopSum(AggFunc): - arg_types = {"this": True, "expression": True, "count": True} - - -class ApproxQuantiles(AggFunc): - arg_types = {"this": True, "expression": False} - - -# https://docs.snowflake.com/en/sql-reference/functions/approx_percentile_combine -class ApproxPercentileCombine(AggFunc): - pass - - -# https://docs.snowflake.com/en/sql-reference/functions/minhash -class Minhash(AggFunc): - arg_types = {"this": True, "expressions": True} - is_var_len_args = True - - -# https://docs.snowflake.com/en/sql-reference/functions/minhash_combine -class MinhashCombine(AggFunc): - pass - - -# https://docs.snowflake.com/en/sql-reference/functions/approximate_similarity -class ApproximateSimilarity(AggFunc): - _sql_names = ["APPROXIMATE_SIMILARITY", "APPROXIMATE_JACCARD_INDEX"] - - -class FarmFingerprint(Func): - arg_types = {"expressions": True} - is_var_len_args = True - _sql_names = ["FARM_FINGERPRINT", "FARMFINGERPRINT64"] - - -class Flatten(Func): - arg_types = {"this": True, "depth": False} - - -class Float64(Func): - arg_types = {"this": True, "expression": False} - - -# https://spark.apache.org/docs/latest/api/sql/index.html#transform -class Transform(Func): - arg_types = {"this": True, "expression": True} - - -class Translate(Func): - arg_types = {"this": True, "from_": True, "to": True} - - -class Grouping(AggFunc): - arg_types = {"expressions": True} - is_var_len_args = True - - -class GroupingId(AggFunc): - arg_types = {"expressions": True} - is_var_len_args = True - - -class Anonymous(Func): - arg_types = {"this": True, "expressions": False} - is_var_len_args = True - - @property - def name(self) -> str: - return self.this if isinstance(self.this, str) else self.this.name - - -class AnonymousAggFunc(AggFunc): - arg_types = {"this": True, "expressions": False} - is_var_len_args = True - - -# https://clickhouse.com/docs/en/sql-reference/aggregate-functions/combinators -class CombinedAggFunc(AnonymousAggFunc): - arg_types = {"this": True, "expressions": False} - - -class CombinedParameterizedAgg(ParameterizedAgg): - arg_types = {"this": True, "expressions": True, "params": True} - - -# https://docs.snowflake.com/en/sql-reference/functions/hash_agg -class HashAgg(AggFunc): - arg_types = {"this": True, "expressions": False} - is_var_len_args = True - - -# https://docs.snowflake.com/en/sql-reference/functions/hll -# https://docs.aws.amazon.com/redshift/latest/dg/r_HLL_function.html -class Hll(AggFunc): - arg_types = {"this": True, "expressions": False} - is_var_len_args = True - - -class ApproxDistinct(AggFunc): - arg_types = {"this": True, "accuracy": False} - _sql_names = ["APPROX_DISTINCT", "APPROX_COUNT_DISTINCT"] - - -class Apply(Func): - arg_types = {"this": True, "expression": True} - - -class Array(Func): - arg_types = { - "expressions": False, - "bracket_notation": False, - "struct_name_inheritance": False, - } - is_var_len_args = True - - -class Ascii(Func): - pass - - -# https://docs.snowflake.com/en/sql-reference/functions/to_array -class ToArray(Func): - pass - - -class ToBoolean(Func): - arg_types = {"this": True, "safe": False} - - -# https://materialize.com/docs/sql/types/list/ -class List(Func): - arg_types = {"expressions": False} - is_var_len_args = True - - -# String pad, kind True -> LPAD, False -> RPAD -class Pad(Func): - arg_types = { - "this": True, - "expression": True, - "fill_pattern": False, - "is_left": True, - } - - -# https://docs.snowflake.com/en/sql-reference/functions/to_char -# https://docs.oracle.com/en/database/oracle/oracle-database/23/sqlrf/TO_CHAR-number.html -class ToChar(Func): - arg_types = { - "this": True, - "format": False, - "nlsparam": False, - "is_numeric": False, - } - - -class ToCodePoints(Func): - pass - - -# https://docs.snowflake.com/en/sql-reference/functions/to_decimal -# https://docs.oracle.com/en/database/oracle/oracle-database/23/sqlrf/TO_NUMBER.html -class ToNumber(Func): - arg_types = { - "this": True, - "format": False, - "nlsparam": False, - "precision": False, - "scale": False, - "safe": False, - "safe_name": False, - } - - -# https://docs.snowflake.com/en/sql-reference/functions/to_double -class ToDouble(Func): - arg_types = { - "this": True, - "format": False, - "safe": False, - } - - -# https://docs.snowflake.com/en/sql-reference/functions/to_decfloat -class ToDecfloat(Func): - arg_types = { - "this": True, - "format": False, - } - - -# https://docs.snowflake.com/en/sql-reference/functions/try_to_decfloat -class TryToDecfloat(Func): - arg_types = { - "this": True, - "format": False, - } - - -# https://docs.snowflake.com/en/sql-reference/functions/to_file -class ToFile(Func): - arg_types = { - "this": True, - "path": False, - "safe": False, - } - - -class CodePointsToBytes(Func): - pass - - -class Columns(Func): - arg_types = {"this": True, "unpack": False} - - -# https://learn.microsoft.com/en-us/sql/t-sql/functions/cast-and-convert-transact-sql?view=sql-server-ver16#syntax -class Convert(Func): - arg_types = {"this": True, "expression": True, "style": False, "safe": False} - - -# https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/CONVERT.html -class ConvertToCharset(Func): - arg_types = {"this": True, "dest": True, "source": False} - - -class ConvertTimezone(Func): - arg_types = { - "source_tz": False, - "target_tz": True, - "timestamp": True, - "options": False, - } - - -class CodePointsToString(Func): - pass - - -class GenerateSeries(Func): - arg_types = {"start": True, "end": True, "step": False, "is_end_exclusive": False} - - -# Postgres' GENERATE_SERIES function returns a row set, i.e. it implicitly explodes when it's -# used in a projection, so this expression is a helper that facilitates transpilation to other -# dialects. For example, we'd generate UNNEST(GENERATE_SERIES(...)) in DuckDB -class ExplodingGenerateSeries(GenerateSeries): - pass - - -class ArrayAgg(AggFunc): - arg_types = {"this": True, "nulls_excluded": False} - - -class ArrayUniqueAgg(AggFunc): - pass - - -class AIAgg(AggFunc): - arg_types = {"this": True, "expression": True} - _sql_names = ["AI_AGG"] - - -class AISummarizeAgg(AggFunc): - _sql_names = ["AI_SUMMARIZE_AGG"] - - -class AIClassify(Func): - arg_types = {"this": True, "categories": True, "config": False} - _sql_names = ["AI_CLASSIFY"] - - -class ArrayAll(Func): - arg_types = {"this": True, "expression": True} - - -# Represents Python's `any(f(x) for x in array)`, where `array` is `this` and `f` is `expression` -class ArrayAny(Func): - arg_types = {"this": True, "expression": True} - - -class ArrayConcat(Func): - _sql_names = ["ARRAY_CONCAT", "ARRAY_CAT"] - arg_types = {"this": True, "expressions": False} - is_var_len_args = True - - -class ArrayConcatAgg(AggFunc): - pass - - -class ArrayConstructCompact(Func): - arg_types = {"expressions": False} - is_var_len_args = True - - -class ArrayContains(Binary, Func): - arg_types = {"this": True, "expression": True, "ensure_variant": False} - _sql_names = ["ARRAY_CONTAINS", "ARRAY_HAS"] - - -class ArrayContainsAll(Binary, Func): - _sql_names = ["ARRAY_CONTAINS_ALL", "ARRAY_HAS_ALL"] - - -class ArrayFilter(Func): - arg_types = {"this": True, "expression": True} - _sql_names = ["FILTER", "ARRAY_FILTER"] - - -class ArrayFirst(Func): - pass - - -class ArrayLast(Func): - pass - - -class ArrayReverse(Func): - pass - - -class ArraySlice(Func): - arg_types = {"this": True, "start": True, "end": False, "step": False} - - -class ArrayToString(Func): - arg_types = {"this": True, "expression": True, "null": False} - _sql_names = ["ARRAY_TO_STRING", "ARRAY_JOIN"] - - -class ArrayIntersect(Func): - arg_types = {"expressions": True} - is_var_len_args = True - _sql_names = ["ARRAY_INTERSECT", "ARRAY_INTERSECTION"] - - -class StPoint(Func): - arg_types = {"this": True, "expression": True, "null": False} - _sql_names = ["ST_POINT", "ST_MAKEPOINT"] - - -class StDistance(Func): - arg_types = {"this": True, "expression": True, "use_spheroid": False} - - -# https://cloud.google.com/bigquery/docs/reference/standard-sql/timestamp_functions#string -class String(Func): - arg_types = {"this": True, "zone": False} - - -class StringToArray(Func): - arg_types = {"this": True, "expression": False, "null": False} - _sql_names = ["STRING_TO_ARRAY", "SPLIT_BY_STRING", "STRTOK_TO_ARRAY"] - - -class ArrayOverlaps(Binary, Func): - pass - - -class ArraySize(Func): - arg_types = {"this": True, "expression": False} - _sql_names = ["ARRAY_SIZE", "ARRAY_LENGTH"] - - -class ArraySort(Func): - arg_types = {"this": True, "expression": False} - - -class ArraySum(Func): - arg_types = {"this": True, "expression": False} - - -class ArrayUnionAgg(AggFunc): - pass - - -class Avg(AggFunc): - pass - - -class AnyValue(AggFunc): - pass - - -class Lag(AggFunc): - arg_types = {"this": True, "offset": False, "default": False} - - -class Lead(AggFunc): - arg_types = {"this": True, "offset": False, "default": False} - - -# some dialects have a distinction between first and first_value, usually first is an aggregate func -# and first_value is a window func -class First(AggFunc): - arg_types = {"this": True, "expression": False} - - -class Last(AggFunc): - arg_types = {"this": True, "expression": False} - - -class FirstValue(AggFunc): - pass - - -class LastValue(AggFunc): - pass - - -class NthValue(AggFunc): - arg_types = {"this": True, "offset": True} - - -class ObjectAgg(AggFunc): - arg_types = {"this": True, "expression": True} - - -class Case(Func): - arg_types = {"this": False, "ifs": True, "default": False} - - def when( - self, condition: ExpOrStr, then: ExpOrStr, copy: bool = True, **opts - ) -> Case: - instance = maybe_copy(self, copy) - instance.append( - "ifs", - If( - this=maybe_parse(condition, copy=copy, **opts), - true=maybe_parse(then, copy=copy, **opts), - ), - ) - return instance - - def else_(self, condition: ExpOrStr, copy: bool = True, **opts) -> Case: - instance = maybe_copy(self, copy) - instance.set("default", maybe_parse(condition, copy=copy, **opts)) - return instance - - -class Cast(Func): - arg_types = { - "this": True, - "to": True, - "format": False, - "safe": False, - "action": False, - "default": False, - } - - @property - def name(self) -> str: - return self.this.name - - @property - def to(self) -> DataType: - return self.args["to"] - - @property - def output_name(self) -> str: - return self.name - - def is_type(self, *dtypes: DATA_TYPE) -> bool: - """ - Checks whether this Cast's DataType matches one of the provided data types. Nested types - like arrays or structs will be compared using "structural equivalence" semantics, so e.g. - array != array. - - Args: - dtypes: the data types to compare this Cast's DataType to. - - Returns: - True, if and only if there is a type in `dtypes` which is equal to this Cast's DataType. - """ - return self.to.is_type(*dtypes) - - -class TryCast(Cast): - arg_types = {**Cast.arg_types, "requires_string": False} - - -# https://clickhouse.com/docs/sql-reference/data-types/newjson#reading-json-paths-as-sub-columns -class JSONCast(Cast): - pass - - -class JustifyDays(Func): - pass - - -class JustifyHours(Func): - pass - - -class JustifyInterval(Func): - pass - - -class Try(Func): - pass - - -class CastToStrType(Func): - arg_types = {"this": True, "to": True} - - -class CheckJson(Func): - arg_types = {"this": True} - - -class CheckXml(Func): - arg_types = {"this": True, "disable_auto_convert": False} - - -# https://docs.teradata.com/r/Enterprise_IntelliFlex_VMware/SQL-Functions-Expressions-and-Predicates/String-Operators-and-Functions/TRANSLATE/TRANSLATE-Function-Syntax -class TranslateCharacters(Expression): - arg_types = {"this": True, "expression": True, "with_error": False} - - -class Collate(Binary, Func): - pass - - -class Collation(Func): - pass - - -class Ceil(Func): - arg_types = {"this": True, "decimals": False, "to": False} - _sql_names = ["CEIL", "CEILING"] - - -class Coalesce(Func): - arg_types = {"this": True, "expressions": False, "is_nvl": False, "is_null": False} - is_var_len_args = True - _sql_names = ["COALESCE", "IFNULL", "NVL"] - - -class Chr(Func): - arg_types = {"expressions": True, "charset": False} - is_var_len_args = True - _sql_names = ["CHR", "CHAR"] - - -class Concat(Func): - arg_types = {"expressions": True, "safe": False, "coalesce": False} - is_var_len_args = True - - -class ConcatWs(Concat): - _sql_names = ["CONCAT_WS"] - - -# https://cloud.google.com/bigquery/docs/reference/standard-sql/string_functions#contains_substr -class Contains(Func): - arg_types = {"this": True, "expression": True, "json_scope": False} - - -# https://docs.oracle.com/cd/B13789_01/server.101/b10759/operators004.htm#i1035022 -class ConnectByRoot(Func): - pass - - -class Count(AggFunc): - arg_types = {"this": False, "expressions": False, "big_int": False} - is_var_len_args = True - - -class CountIf(AggFunc): - _sql_names = ["COUNT_IF", "COUNTIF"] - - -# cube root -class Cbrt(Func): - pass - - -class CurrentAccount(Func): - arg_types = {} - - -class CurrentAccountName(Func): - arg_types = {} - - -class CurrentAvailableRoles(Func): - arg_types = {} - - -class CurrentClient(Func): - arg_types = {} - - -class CurrentIpAddress(Func): - arg_types = {} - - -class CurrentDatabase(Func): - arg_types = {} - - -class CurrentSchemas(Func): - arg_types = {"this": False} - - -class CurrentSecondaryRoles(Func): - arg_types = {} - - -class CurrentSession(Func): - arg_types = {} - - -class CurrentStatement(Func): - arg_types = {} - - -class CurrentVersion(Func): - arg_types = {} - - -class CurrentTransaction(Func): - arg_types = {} - - -class CurrentWarehouse(Func): - arg_types = {} - - -class CurrentDate(Func): - arg_types = {"this": False} - - -class CurrentDatetime(Func): - arg_types = {"this": False} - - -class CurrentTime(Func): - arg_types = {"this": False} - - -# https://www.postgresql.org/docs/current/functions-datetime.html#FUNCTIONS-DATETIME-CURRENT -# In Postgres, the difference between CURRENT_TIME vs LOCALTIME etc is that the latter does not have tz -class Localtime(Func): - arg_types = {"this": False} - - -class Localtimestamp(Func): - arg_types = {"this": False} - - -class CurrentTimestamp(Func): - arg_types = {"this": False, "sysdate": False} - - -class CurrentTimestampLTZ(Func): - arg_types = {} - - -class CurrentTimezone(Func): - arg_types = {} - - -class CurrentOrganizationName(Func): - arg_types = {} - - -class CurrentSchema(Func): - arg_types = {"this": False} - - -class CurrentUser(Func): - arg_types = {"this": False} - - -class CurrentCatalog(Func): - arg_types = {} - - -class CurrentRegion(Func): - arg_types = {} - - -class CurrentRole(Func): - arg_types = {} - - -class CurrentRoleType(Func): - arg_types = {} - - -class CurrentOrganizationUser(Func): - arg_types = {} - - -class SessionUser(Func): - arg_types = {} - - -class UtcDate(Func): - arg_types = {} - - -class UtcTime(Func): - arg_types = {"this": False} - - -class UtcTimestamp(Func): - arg_types = {"this": False} - - -class DateAdd(Func, IntervalOp): - arg_types = {"this": True, "expression": True, "unit": False} - - -class DateBin(Func, IntervalOp): - arg_types = { - "this": True, - "expression": True, - "unit": False, - "zone": False, - "origin": False, - } - - -class DateSub(Func, IntervalOp): - arg_types = {"this": True, "expression": True, "unit": False} - - -class DateDiff(Func, TimeUnit): - _sql_names = ["DATEDIFF", "DATE_DIFF"] - arg_types = { - "this": True, - "expression": True, - "unit": False, - "zone": False, - "big_int": False, - "date_part_boundary": False, - } - - -class DateTrunc(Func): - arg_types = {"unit": True, "this": True, "zone": False} - - def __init__(self, **args): - # Across most dialects it's safe to unabbreviate the unit (e.g. 'Q' -> 'QUARTER') except Oracle - # https://docs.oracle.com/en/database/oracle/oracle-database/21/sqlrf/ROUND-and-TRUNC-Date-Functions.html - unabbreviate = args.pop("unabbreviate", True) - - unit = args.get("unit") - if isinstance(unit, TimeUnit.VAR_LIKE) and not ( - isinstance(unit, Column) and len(unit.parts) != 1 - ): - unit_name = unit.name.upper() - if unabbreviate and unit_name in TimeUnit.UNABBREVIATED_UNIT_NAME: - unit_name = TimeUnit.UNABBREVIATED_UNIT_NAME[unit_name] - - args["unit"] = Literal.string(unit_name) - - super().__init__(**args) - - @property - def unit(self) -> Expression: - return self.args["unit"] - - -# https://cloud.google.com/bigquery/docs/reference/standard-sql/datetime_functions#datetime -# expression can either be time_expr or time_zone -class Datetime(Func): - arg_types = {"this": True, "expression": False} - - -class DatetimeAdd(Func, IntervalOp): - arg_types = {"this": True, "expression": True, "unit": False} - - -class DatetimeSub(Func, IntervalOp): - arg_types = {"this": True, "expression": True, "unit": False} - - -class DatetimeDiff(Func, TimeUnit): - arg_types = {"this": True, "expression": True, "unit": False} - - -class DatetimeTrunc(Func, TimeUnit): - arg_types = {"this": True, "unit": True, "zone": False} - - -class DateFromUnixDate(Func): - pass - - -class DayOfWeek(Func): - _sql_names = ["DAY_OF_WEEK", "DAYOFWEEK"] - - -# https://duckdb.org/docs/sql/functions/datepart.html#part-specifiers-only-usable-as-date-part-specifiers -# ISO day of week function in duckdb is ISODOW -class DayOfWeekIso(Func): - _sql_names = ["DAYOFWEEK_ISO", "ISODOW"] - - -class DayOfMonth(Func): - _sql_names = ["DAY_OF_MONTH", "DAYOFMONTH"] - - -class DayOfYear(Func): - _sql_names = ["DAY_OF_YEAR", "DAYOFYEAR"] - - -class Dayname(Func): - arg_types = {"this": True, "abbreviated": False} - - -class ToDays(Func): - pass - - -class WeekOfYear(Func): - _sql_names = ["WEEK_OF_YEAR", "WEEKOFYEAR"] - - -class YearOfWeek(Func): - _sql_names = ["YEAR_OF_WEEK", "YEAROFWEEK"] - - -class YearOfWeekIso(Func): - _sql_names = ["YEAR_OF_WEEK_ISO", "YEAROFWEEKISO"] - - -class MonthsBetween(Func): - arg_types = {"this": True, "expression": True, "roundoff": False} - - -class MakeInterval(Func): - arg_types = { - "year": False, - "month": False, - "week": False, - "day": False, - "hour": False, - "minute": False, - "second": False, - } - - -class LastDay(Func, TimeUnit): - _sql_names = ["LAST_DAY", "LAST_DAY_OF_MONTH"] - arg_types = {"this": True, "unit": False} - - -class PreviousDay(Func): - arg_types = {"this": True, "expression": True} - - -class LaxBool(Func): - pass - - -class LaxFloat64(Func): - pass - - -class LaxInt64(Func): - pass - - -class LaxString(Func): - pass - - -class Extract(Func): - arg_types = {"this": True, "expression": True} - - -class Exists(Func, SubqueryPredicate): - arg_types = {"this": True, "expression": False} - - -class Elt(Func): - arg_types = {"this": True, "expressions": True} - is_var_len_args = True - - -class Timestamp(Func): - arg_types = {"this": False, "zone": False, "with_tz": False, "safe": False} - - -class TimestampAdd(Func, TimeUnit): - arg_types = {"this": True, "expression": True, "unit": False} - - -class TimestampSub(Func, TimeUnit): - arg_types = {"this": True, "expression": True, "unit": False} - - -class TimestampDiff(Func, TimeUnit): - _sql_names = ["TIMESTAMPDIFF", "TIMESTAMP_DIFF"] - arg_types = {"this": True, "expression": True, "unit": False} - - -class TimestampTrunc(Func, TimeUnit): - arg_types = {"this": True, "unit": True, "zone": False} - - -class TimeSlice(Func, TimeUnit): - arg_types = {"this": True, "expression": True, "unit": True, "kind": False} - - -class TimeAdd(Func, TimeUnit): - arg_types = {"this": True, "expression": True, "unit": False} - - -class TimeSub(Func, TimeUnit): - arg_types = {"this": True, "expression": True, "unit": False} - - -class TimeDiff(Func, TimeUnit): - arg_types = {"this": True, "expression": True, "unit": False} - - -class TimeTrunc(Func, TimeUnit): - arg_types = {"this": True, "unit": True, "zone": False} - - -class DateFromParts(Func): - _sql_names = ["DATE_FROM_PARTS", "DATEFROMPARTS"] - arg_types = {"year": True, "month": False, "day": False} - - -class TimeFromParts(Func): - _sql_names = ["TIME_FROM_PARTS", "TIMEFROMPARTS"] - arg_types = { - "hour": True, - "min": True, - "sec": True, - "nano": False, - "fractions": False, - "precision": False, - } - - -class DateStrToDate(Func): - pass - - -class DateToDateStr(Func): - pass - - -class DateToDi(Func): - pass - - -# https://cloud.google.com/bigquery/docs/reference/standard-sql/date_functions#date -class Date(Func): - arg_types = {"this": False, "zone": False, "expressions": False} - is_var_len_args = True - - -class Day(Func): - pass - - -class Decode(Func): - arg_types = {"this": True, "charset": True, "replace": False} - - -class DecodeCase(Func): - arg_types = {"expressions": True} - is_var_len_args = True - - -class DenseRank(AggFunc): - arg_types = {"expressions": False} - is_var_len_args = True - - -class DiToDate(Func): - pass - - -class Encode(Func): - arg_types = {"this": True, "charset": True} - - -class EqualNull(Func): - arg_types = {"this": True, "expression": True} - - -class Exp(Func): - pass - - -class Factorial(Func): - pass - - -# https://docs.snowflake.com/en/sql-reference/functions/flatten -class Explode(Func, UDTF): - arg_types = {"this": True, "expressions": False} - is_var_len_args = True - - -# https://spark.apache.org/docs/latest/api/sql/#inline -class Inline(Func): - pass - - -class ExplodeOuter(Explode): - pass - - -class Posexplode(Explode): - pass - - -class PosexplodeOuter(Posexplode, ExplodeOuter): - pass - - -class PositionalColumn(Expression): - pass - - -class Unnest(Func, UDTF): - arg_types = { - "expressions": True, - "alias": False, - "offset": False, - "explode_array": False, - } - - @property - def selects(self) -> t.List[Expression]: - columns = super().selects - offset = self.args.get("offset") - if offset: - columns = columns + [to_identifier("offset") if offset is True else offset] - return columns - - -class Floor(Func): - arg_types = {"this": True, "decimals": False, "to": False} - - -class FromBase32(Func): - pass - - -class FromBase64(Func): - pass - - -class ToBase32(Func): - pass - - -class ToBase64(Func): - pass - - -class ToBinary(Func): - arg_types = {"this": True, "format": False, "safe": False} - - -# https://docs.snowflake.com/en/sql-reference/functions/base64_decode_binary -class Base64DecodeBinary(Func): - arg_types = {"this": True, "alphabet": False} - - -# https://docs.snowflake.com/en/sql-reference/functions/base64_decode_string -class Base64DecodeString(Func): - arg_types = {"this": True, "alphabet": False} - - -# https://docs.snowflake.com/en/sql-reference/functions/base64_encode -class Base64Encode(Func): - arg_types = {"this": True, "max_line_length": False, "alphabet": False} - - -# https://docs.snowflake.com/en/sql-reference/functions/try_base64_decode_binary -class TryBase64DecodeBinary(Func): - arg_types = {"this": True, "alphabet": False} - - -# https://docs.snowflake.com/en/sql-reference/functions/try_base64_decode_string -class TryBase64DecodeString(Func): - arg_types = {"this": True, "alphabet": False} - - -# https://docs.snowflake.com/en/sql-reference/functions/try_hex_decode_binary -class TryHexDecodeBinary(Func): - pass - - -# https://docs.snowflake.com/en/sql-reference/functions/try_hex_decode_string -class TryHexDecodeString(Func): - pass - - -# https://trino.io/docs/current/functions/datetime.html#from_iso8601_timestamp -class FromISO8601Timestamp(Func): - _sql_names = ["FROM_ISO8601_TIMESTAMP"] - - -class GapFill(Func): - arg_types = { - "this": True, - "ts_column": True, - "bucket_width": True, - "partitioning_columns": False, - "value_columns": False, - "origin": False, - "ignore_nulls": False, - } - - -# https://cloud.google.com/bigquery/docs/reference/standard-sql/array_functions#generate_date_array -class GenerateDateArray(Func): - arg_types = {"start": True, "end": True, "step": False} - - -# https://cloud.google.com/bigquery/docs/reference/standard-sql/array_functions#generate_timestamp_array -class GenerateTimestampArray(Func): - arg_types = {"start": True, "end": True, "step": True} - - -# https://docs.snowflake.com/en/sql-reference/functions/get -class GetExtract(Func): - arg_types = {"this": True, "expression": True} - - -class Getbit(Func): - arg_types = {"this": True, "expression": True} - - -class Greatest(Func): - arg_types = {"this": True, "expressions": False, "ignore_nulls": True} - is_var_len_args = True - - -# Trino's `ON OVERFLOW TRUNCATE [filler_string] {WITH | WITHOUT} COUNT` -# https://trino.io/docs/current/functions/aggregate.html#listagg -class OverflowTruncateBehavior(Expression): - arg_types = {"this": False, "with_count": True} - - -class GroupConcat(AggFunc): - arg_types = {"this": True, "separator": False, "on_overflow": False} - - -class Hex(Func): - pass - - -# https://docs.snowflake.com/en/sql-reference/functions/hex_decode_string -class HexDecodeString(Func): - pass - - -# https://docs.snowflake.com/en/sql-reference/functions/hex_encode -class HexEncode(Func): - arg_types = {"this": True, "case": False} - - -class Hour(Func): - pass - - -class Minute(Func): - pass - - -class Second(Func): - pass - - -# T-SQL: https://learn.microsoft.com/en-us/sql/t-sql/functions/compress-transact-sql?view=sql-server-ver17 -# Snowflake: https://docs.snowflake.com/en/sql-reference/functions/compress -class Compress(Func): - arg_types = {"this": True, "method": False} - - -# Snowflake: https://docs.snowflake.com/en/sql-reference/functions/decompress_binary -class DecompressBinary(Func): - arg_types = {"this": True, "method": True} - - -# Snowflake: https://docs.snowflake.com/en/sql-reference/functions/decompress_string -class DecompressString(Func): - arg_types = {"this": True, "method": True} - - -class LowerHex(Hex): - pass - - -class And(Connector, Func): - pass - - -class Or(Connector, Func): - pass - - -class Xor(Connector, Func): - arg_types = {"this": False, "expression": False, "expressions": False} - - -class If(Func): - arg_types = {"this": True, "true": True, "false": False} - _sql_names = ["IF", "IIF"] - - -class Nullif(Func): - arg_types = {"this": True, "expression": True} - - -class Initcap(Func): - arg_types = {"this": True, "expression": False} - - -class IsAscii(Func): - pass - - -class IsNan(Func): - _sql_names = ["IS_NAN", "ISNAN"] - - -# https://cloud.google.com/bigquery/docs/reference/standard-sql/json_functions#int64_for_json -class Int64(Func): - pass - - -class IsInf(Func): - _sql_names = ["IS_INF", "ISINF"] - - -class IsNullValue(Func): - pass - - -# https://www.postgresql.org/docs/current/functions-json.html -class JSON(Expression): - arg_types = {"this": False, "with_": False, "unique": False} - - -class JSONPath(Expression): - arg_types = {"expressions": True, "escape": False} - - @property - def output_name(self) -> str: - last_segment = self.expressions[-1].this - return last_segment if isinstance(last_segment, str) else "" - - -class JSONPathPart(Expression): - arg_types = {} - - -class JSONPathFilter(JSONPathPart): - arg_types = {"this": True} - - -class JSONPathKey(JSONPathPart): - arg_types = {"this": True} - - -class JSONPathRecursive(JSONPathPart): - arg_types = {"this": False} - - -class JSONPathRoot(JSONPathPart): - pass - - -class JSONPathScript(JSONPathPart): - arg_types = {"this": True} - - -class JSONPathSlice(JSONPathPart): - arg_types = {"start": False, "end": False, "step": False} - - -class JSONPathSelector(JSONPathPart): - arg_types = {"this": True} - - -class JSONPathSubscript(JSONPathPart): - arg_types = {"this": True} - - -class JSONPathUnion(JSONPathPart): - arg_types = {"expressions": True} - - -class JSONPathWildcard(JSONPathPart): - pass - - -class FormatJson(Expression): - pass - - -class Format(Func): - arg_types = {"this": True, "expressions": False} - is_var_len_args = True - - -class JSONKeyValue(Expression): - arg_types = {"this": True, "expression": True} - - -# https://cloud.google.com/bigquery/docs/reference/standard-sql/json_functions#json_keys -class JSONKeysAtDepth(Func): - arg_types = {"this": True, "expression": False, "mode": False} - - -class JSONObject(Func): - arg_types = { - "expressions": False, - "null_handling": False, - "unique_keys": False, - "return_type": False, - "encoding": False, - } - - -class JSONObjectAgg(AggFunc): - arg_types = { - "expressions": False, - "null_handling": False, - "unique_keys": False, - "return_type": False, - "encoding": False, - } - - -# https://www.postgresql.org/docs/9.5/functions-aggregate.html -class JSONBObjectAgg(AggFunc): - arg_types = {"this": True, "expression": True} - - -# https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/JSON_ARRAY.html -class JSONArray(Func): - arg_types = { - "expressions": False, - "null_handling": False, - "return_type": False, - "strict": False, - } - - -# https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/JSON_ARRAYAGG.html -class JSONArrayAgg(AggFunc): - arg_types = { - "this": True, - "order": False, - "null_handling": False, - "return_type": False, - "strict": False, - } - - -class JSONExists(Func): - arg_types = { - "this": True, - "path": True, - "passing": False, - "on_condition": False, - "from_dcolonqmark": False, - } - - -# https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/JSON_TABLE.html -# Note: parsing of JSON column definitions is currently incomplete. -class JSONColumnDef(Expression): - arg_types = { - "this": False, - "kind": False, - "path": False, - "nested_schema": False, - "ordinality": False, - } - - -class JSONSchema(Expression): - arg_types = {"expressions": True} - - -class JSONSet(Func): - arg_types = {"this": True, "expressions": True} - is_var_len_args = True - _sql_names = ["JSON_SET"] - - -# https://cloud.google.com/bigquery/docs/reference/standard-sql/json_functions#json_strip_nulls -class JSONStripNulls(Func): - arg_types = { - "this": True, - "expression": False, - "include_arrays": False, - "remove_empty": False, - } - _sql_names = ["JSON_STRIP_NULLS"] - - -# https://dev.mysql.com/doc/refman/8.4/en/json-search-functions.html#function_json-value -class JSONValue(Expression): - arg_types = { - "this": True, - "path": True, - "returning": False, - "on_condition": False, - } - - -class JSONValueArray(Func): - arg_types = {"this": True, "expression": False} - - -class JSONRemove(Func): - arg_types = {"this": True, "expressions": True} - is_var_len_args = True - _sql_names = ["JSON_REMOVE"] - - -# https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/JSON_TABLE.html -class JSONTable(Func): - arg_types = { - "this": True, - "schema": True, - "path": False, - "error_handling": False, - "empty_handling": False, - } - - -# https://cloud.google.com/bigquery/docs/reference/standard-sql/json_functions#json_type -# https://doris.apache.org/docs/sql-manual/sql-functions/scalar-functions/json-functions/json-type#description -class JSONType(Func): - arg_types = {"this": True, "expression": False} - _sql_names = ["JSON_TYPE"] - - -# https://docs.snowflake.com/en/sql-reference/functions/object_insert -class ObjectInsert(Func): - arg_types = { - "this": True, - "key": True, - "value": True, - "update_flag": False, - } - - -class OpenJSONColumnDef(Expression): - arg_types = {"this": True, "kind": True, "path": False, "as_json": False} - - -class OpenJSON(Func): - arg_types = {"this": True, "path": False, "expressions": False} - - -class JSONBContains(Binary, Func): - _sql_names = ["JSONB_CONTAINS"] - - -# https://www.postgresql.org/docs/9.5/functions-json.html -class JSONBContainsAnyTopKeys(Binary, Func): - pass - - -# https://www.postgresql.org/docs/9.5/functions-json.html -class JSONBContainsAllTopKeys(Binary, Func): - pass - - -class JSONBExists(Func): - arg_types = {"this": True, "path": True} - _sql_names = ["JSONB_EXISTS"] - - -# https://www.postgresql.org/docs/9.5/functions-json.html -class JSONBDeleteAtPath(Binary, Func): - pass - - -class JSONExtract(Binary, Func): - arg_types = { - "this": True, - "expression": True, - "only_json_types": False, - "expressions": False, - "variant_extract": False, - "json_query": False, - "option": False, - "quote": False, - "on_condition": False, - "requires_json": False, - } - _sql_names = ["JSON_EXTRACT"] - is_var_len_args = True - - @property - def output_name(self) -> str: - return self.expression.output_name if not self.expressions else "" - - -# https://trino.io/docs/current/functions/json.html#json-query -class JSONExtractQuote(Expression): - arg_types = { - "option": True, - "scalar": False, - } - - -class JSONExtractArray(Func): - arg_types = {"this": True, "expression": False} - _sql_names = ["JSON_EXTRACT_ARRAY"] - - -class JSONExtractScalar(Binary, Func): - arg_types = { - "this": True, - "expression": True, - "only_json_types": False, - "expressions": False, - "json_type": False, - "scalar_only": False, - } - _sql_names = ["JSON_EXTRACT_SCALAR"] - is_var_len_args = True - - @property - def output_name(self) -> str: - return self.expression.output_name - - -class JSONBExtract(Binary, Func): - _sql_names = ["JSONB_EXTRACT"] - - -class JSONBExtractScalar(Binary, Func): - arg_types = {"this": True, "expression": True, "json_type": False} - _sql_names = ["JSONB_EXTRACT_SCALAR"] - - -class JSONFormat(Func): - arg_types = {"this": False, "options": False, "is_json": False, "to_json": False} - _sql_names = ["JSON_FORMAT"] - - -class JSONArrayAppend(Func): - arg_types = {"this": True, "expressions": True} - is_var_len_args = True - _sql_names = ["JSON_ARRAY_APPEND"] - - -# https://dev.mysql.com/doc/refman/8.0/en/json-search-functions.html#operator_member-of -class JSONArrayContains(Binary, Predicate, Func): - arg_types = {"this": True, "expression": True, "json_type": False} - _sql_names = ["JSON_ARRAY_CONTAINS"] - - -class JSONArrayInsert(Func): - arg_types = {"this": True, "expressions": True} - is_var_len_args = True - _sql_names = ["JSON_ARRAY_INSERT"] - - -class ParseBignumeric(Func): - pass - - -class ParseNumeric(Func): - pass - - -class ParseJSON(Func): - # BigQuery, Snowflake have PARSE_JSON, Presto has JSON_PARSE - # Snowflake also has TRY_PARSE_JSON, which is represented using `safe` - _sql_names = ["PARSE_JSON", "JSON_PARSE"] - arg_types = {"this": True, "expression": False, "safe": False} - - -# Snowflake: https://docs.snowflake.com/en/sql-reference/functions/parse_url -# Databricks: https://docs.databricks.com/aws/en/sql/language-manual/functions/parse_url -class ParseUrl(Func): - arg_types = { - "this": True, - "part_to_extract": False, - "key": False, - "permissive": False, - } - - -class ParseIp(Func): - arg_types = {"this": True, "type": True, "permissive": False} - - -class ParseTime(Func): - arg_types = {"this": True, "format": True} - - -class ParseDatetime(Func): - arg_types = {"this": True, "format": False, "zone": False} - - -class Least(Func): - arg_types = {"this": True, "expressions": False, "ignore_nulls": True} - is_var_len_args = True - - -class Left(Func): - arg_types = {"this": True, "expression": True} - - -class Right(Func): - arg_types = {"this": True, "expression": True} - - -class Reverse(Func): - pass - - -class Length(Func): - arg_types = {"this": True, "binary": False, "encoding": False} - _sql_names = ["LENGTH", "LEN", "CHAR_LENGTH", "CHARACTER_LENGTH"] - - -class RtrimmedLength(Func): - pass - - -class BitLength(Func): - pass - - -class Levenshtein(Func): - arg_types = { - "this": True, - "expression": False, - "ins_cost": False, - "del_cost": False, - "sub_cost": False, - "max_dist": False, - } - - -class Ln(Func): - pass - - -class Log(Func): - arg_types = {"this": True, "expression": False} - - -class LogicalOr(AggFunc): - _sql_names = ["LOGICAL_OR", "BOOL_OR", "BOOLOR_AGG"] - - -class LogicalAnd(AggFunc): - _sql_names = ["LOGICAL_AND", "BOOL_AND", "BOOLAND_AGG"] - - -class Lower(Func): - _sql_names = ["LOWER", "LCASE"] - - -class Map(Func): - arg_types = {"keys": False, "values": False} - - @property - def keys(self) -> t.List[Expression]: - keys = self.args.get("keys") - return keys.expressions if keys else [] - - @property - def values(self) -> t.List[Expression]: - values = self.args.get("values") - return values.expressions if values else [] - - -# Represents the MAP {...} syntax in DuckDB - basically convert a struct to a MAP -class ToMap(Func): - pass - - -class MapFromEntries(Func): - pass - - -class MapCat(Func): - arg_types = {"this": True, "expression": True} - - -class MapContainsKey(Func): - arg_types = {"this": True, "key": True} - - -class MapDelete(Func): - arg_types = {"this": True, "expressions": True} - is_var_len_args = True - - -class MapInsert(Func): - arg_types = {"this": True, "key": False, "value": True, "update_flag": False} - - -class MapKeys(Func): - pass - - -class MapPick(Func): - arg_types = {"this": True, "expressions": True} - is_var_len_args = True - - -class MapSize(Func): - pass - - -# https://learn.microsoft.com/en-us/sql/t-sql/language-elements/scope-resolution-operator-transact-sql?view=sql-server-ver16 -class ScopeResolution(Expression): - arg_types = {"this": False, "expression": True} - - -class Slice(Expression): - arg_types = {"this": False, "expression": False, "step": False} - - -class Stream(Expression): - pass - - -class StarMap(Func): - pass - - -class VarMap(Func): - arg_types = {"keys": True, "values": True} - is_var_len_args = True - - @property - def keys(self) -> t.List[Expression]: - return self.args["keys"].expressions - - @property - def values(self) -> t.List[Expression]: - return self.args["values"].expressions - - -# https://dev.mysql.com/doc/refman/8.0/en/fulltext-search.html -class MatchAgainst(Func): - arg_types = {"this": True, "expressions": True, "modifier": False} - - -class Max(AggFunc): - arg_types = {"this": True, "expressions": False} - is_var_len_args = True - - -class MD5(Func): - _sql_names = ["MD5"] - - -# Represents the variant of the MD5 function that returns a binary value -class MD5Digest(Func): - _sql_names = ["MD5_DIGEST"] - - -# https://docs.snowflake.com/en/sql-reference/functions/md5_number_lower64 -class MD5NumberLower64(Func): - pass - - -# https://docs.snowflake.com/en/sql-reference/functions/md5_number_upper64 -class MD5NumberUpper64(Func): - pass - - -class Median(AggFunc): - pass - - -class Mode(AggFunc): - arg_types = {"this": False, "deterministic": False} - - -class Min(AggFunc): - arg_types = {"this": True, "expressions": False} - is_var_len_args = True - - -class Month(Func): - pass - - -class Monthname(Func): - arg_types = {"this": True, "abbreviated": False} - - -class AddMonths(Func): - arg_types = {"this": True, "expression": True, "preserve_end_of_month": False} - - -class Nvl2(Func): - arg_types = {"this": True, "true": True, "false": False} - - -class Ntile(AggFunc): - arg_types = {"this": False} - - -class Normalize(Func): - arg_types = {"this": True, "form": False, "is_casefold": False} - - -class Normal(Func): - arg_types = {"this": True, "stddev": True, "gen": True} - - -# https://cloud.google.com/bigquery/docs/reference/standard-sql/net_functions#nethost -class NetHost(Func): - _sql_names = ["NET.HOST"] - - -class Overlay(Func): - arg_types = {"this": True, "expression": True, "from_": True, "for_": False} - - -# https://cloud.google.com/bigquery/docs/reference/standard-sql/bigqueryml-syntax-predict#mlpredict_function -class Predict(Func): - arg_types = {"this": True, "expression": True, "params_struct": False} - - -# https://cloud.google.com/bigquery/docs/reference/standard-sql/bigqueryml-syntax-translate#mltranslate_function -class MLTranslate(Func): - arg_types = {"this": True, "expression": True, "params_struct": True} - - -# https://cloud.google.com/bigquery/docs/reference/standard-sql/bigqueryml-syntax-feature-time -class FeaturesAtTime(Func): - arg_types = { - "this": True, - "time": False, - "num_rows": False, - "ignore_feature_nulls": False, - } - - -# https://cloud.google.com/bigquery/docs/reference/standard-sql/bigqueryml-syntax-generate-embedding -class GenerateEmbedding(Func): - arg_types = { - "this": True, - "expression": True, - "params_struct": False, - "is_text": False, - } - - -class MLForecast(Func): - arg_types = {"this": True, "expression": False, "params_struct": False} - - -# Represents Snowflake's ! syntax. For example: SELECT model!PREDICT(INPUT_DATA => {*}) -# See: https://docs.snowflake.com/en/guides-overview-ml-functions -class ModelAttribute(Expression): - arg_types = {"this": True, "expression": True} - - -# https://cloud.google.com/bigquery/docs/reference/standard-sql/search_functions#vector_search -class VectorSearch(Func): - arg_types = { - "this": True, - "column_to_search": True, - "query_table": True, - "query_column_to_search": False, - "top_k": False, - "distance_type": False, - "options": False, - } - - -class Pi(Func): - arg_types = {} - - -class Pow(Binary, Func): - _sql_names = ["POWER", "POW"] - - -class PercentileCont(AggFunc): - arg_types = {"this": True, "expression": False} - - -class PercentileDisc(AggFunc): - arg_types = {"this": True, "expression": False} - - -class PercentRank(AggFunc): - arg_types = {"expressions": False} - is_var_len_args = True - - -class Quantile(AggFunc): - arg_types = {"this": True, "quantile": True} - - -class ApproxQuantile(Quantile): - arg_types = { - "this": True, - "quantile": True, - "accuracy": False, - "weight": False, - "error_tolerance": False, - } - - -# https://docs.snowflake.com/en/sql-reference/functions/approx_percentile_accumulate -class ApproxPercentileAccumulate(AggFunc): - pass - - -# https://docs.snowflake.com/en/sql-reference/functions/approx_percentile_estimate -class ApproxPercentileEstimate(Func): - arg_types = {"this": True, "percentile": True} - - -class Quarter(Func): - pass - - -# https://docs.teradata.com/r/Enterprise_IntelliFlex_VMware/SQL-Functions-Expressions-and-Predicates/Arithmetic-Trigonometric-Hyperbolic-Operators/Functions/RANDOM/RANDOM-Function-Syntax -# teradata lower and upper bounds -class Rand(Func): - _sql_names = ["RAND", "RANDOM"] - arg_types = {"this": False, "lower": False, "upper": False} - - -class Randn(Func): - arg_types = {"this": False} - - -class Randstr(Func): - arg_types = {"this": True, "generator": False} - - -class RangeN(Func): - arg_types = {"this": True, "expressions": True, "each": False} - - -class RangeBucket(Func): - arg_types = {"this": True, "expression": True} - - -class Rank(AggFunc): - arg_types = {"expressions": False} - is_var_len_args = True - - -class ReadCSV(Func): - _sql_names = ["READ_CSV"] - is_var_len_args = True - arg_types = {"this": True, "expressions": False} - - -class ReadParquet(Func): - is_var_len_args = True - arg_types = {"expressions": True} - - -class Reduce(Func): - arg_types = {"this": True, "initial": True, "merge": True, "finish": False} - - -class RegexpExtract(Func): - arg_types = { - "this": True, - "expression": True, - "position": False, - "occurrence": False, - "parameters": False, - "group": False, - "null_if_pos_overflow": False, # for transpilation target behavior - } - - -class RegexpExtractAll(Func): - arg_types = { - "this": True, - "expression": True, - "group": False, - "parameters": False, - "position": False, - "occurrence": False, - } - - -class RegexpReplace(Func): - arg_types = { - "this": True, - "expression": True, - "replacement": False, - "position": False, - "occurrence": False, - "modifiers": False, - "single_replace": False, - } - - -class RegexpLike(Binary, Func): - arg_types = {"this": True, "expression": True, "flag": False} - - -class RegexpILike(Binary, Func): - arg_types = {"this": True, "expression": True, "flag": False} - - -class RegexpFullMatch(Binary, Func): - arg_types = {"this": True, "expression": True, "options": False} - - -class RegexpInstr(Func): - arg_types = { - "this": True, - "expression": True, - "position": False, - "occurrence": False, - "option": False, - "parameters": False, - "group": False, - } - - -# https://spark.apache.org/docs/latest/api/python/reference/pyspark.sql/api/pyspark.sql.functions.split.html -# limit is the number of times a pattern is applied -class RegexpSplit(Func): - arg_types = {"this": True, "expression": True, "limit": False} - - -class RegexpCount(Func): - arg_types = { - "this": True, - "expression": True, - "position": False, - "parameters": False, - } - - -class RegrValx(AggFunc): - arg_types = {"this": True, "expression": True} - - -class RegrValy(AggFunc): - arg_types = {"this": True, "expression": True} - - -class RegrAvgy(AggFunc): - arg_types = {"this": True, "expression": True} - - -class RegrAvgx(AggFunc): - arg_types = {"this": True, "expression": True} - - -class RegrCount(AggFunc): - arg_types = {"this": True, "expression": True} - - -class RegrIntercept(AggFunc): - arg_types = {"this": True, "expression": True} - - -class RegrR2(AggFunc): - arg_types = {"this": True, "expression": True} - - -class RegrSxx(AggFunc): - arg_types = {"this": True, "expression": True} - - -class RegrSxy(AggFunc): - arg_types = {"this": True, "expression": True} - - -class RegrSyy(AggFunc): - arg_types = {"this": True, "expression": True} - - -class RegrSlope(AggFunc): - arg_types = {"this": True, "expression": True} - - -class Repeat(Func): - arg_types = {"this": True, "times": True} - - -# Some dialects like Snowflake support two argument replace -class Replace(Func): - arg_types = {"this": True, "expression": True, "replacement": False} - - -class Radians(Func): - pass - - -# https://learn.microsoft.com/en-us/sql/t-sql/functions/round-transact-sql?view=sql-server-ver16 -# tsql third argument function == trunctaion if not 0 -class Round(Func): - arg_types = { - "this": True, - "decimals": False, - "truncate": False, - "casts_non_integer_decimals": False, - } - - -class RowNumber(Func): - arg_types = {"this": False} - - -class SafeAdd(Func): - arg_types = {"this": True, "expression": True} - - -class SafeDivide(Func): - arg_types = {"this": True, "expression": True} - - -class SafeMultiply(Func): - arg_types = {"this": True, "expression": True} - - -class SafeNegate(Func): - pass - - -class SafeSubtract(Func): - arg_types = {"this": True, "expression": True} - - -class SafeConvertBytesToString(Func): - pass - - -class SHA(Func): - _sql_names = ["SHA", "SHA1"] - - -class SHA2(Func): - _sql_names = ["SHA2"] - arg_types = {"this": True, "length": False} - - -# Represents the variant of the SHA1 function that returns a binary value -class SHA1Digest(Func): - pass - - -# Represents the variant of the SHA2 function that returns a binary value -class SHA2Digest(Func): - arg_types = {"this": True, "length": False} - - -class Sign(Func): - _sql_names = ["SIGN", "SIGNUM"] - - -class SortArray(Func): - arg_types = {"this": True, "asc": False, "nulls_first": False} - - -class Soundex(Func): - pass - - -# https://docs.snowflake.com/en/sql-reference/functions/soundex_p123 -class SoundexP123(Func): - pass - - -class Split(Func): - arg_types = {"this": True, "expression": True, "limit": False} - - -# https://spark.apache.org/docs/latest/api/python/reference/pyspark.sql/api/pyspark.sql.functions.split_part.html -# https://docs.snowflake.com/en/sql-reference/functions/split_part -# https://docs.snowflake.com/en/sql-reference/functions/strtok -class SplitPart(Func): - arg_types = {"this": True, "delimiter": False, "part_index": False} - - -# Start may be omitted in the case of postgres -# https://www.postgresql.org/docs/9.1/functions-string.html @ Table 9-6 -class Substring(Func): - _sql_names = ["SUBSTRING", "SUBSTR"] - arg_types = {"this": True, "start": False, "length": False} - - -class SubstringIndex(Func): - """ - SUBSTRING_INDEX(str, delim, count) - - *count* > 0 → left slice before the *count*-th delimiter - *count* < 0 → right slice after the |count|-th delimiter - """ - - arg_types = {"this": True, "delimiter": True, "count": True} - - -class StandardHash(Func): - arg_types = {"this": True, "expression": False} - - -class StartsWith(Func): - _sql_names = ["STARTS_WITH", "STARTSWITH"] - arg_types = {"this": True, "expression": True} - - -class EndsWith(Func): - _sql_names = ["ENDS_WITH", "ENDSWITH"] - arg_types = {"this": True, "expression": True} - - -class StrPosition(Func): - arg_types = { - "this": True, - "substr": True, - "position": False, - "occurrence": False, - } - - -# Snowflake: https://docs.snowflake.com/en/sql-reference/functions/search -# BigQuery: https://cloud.google.com/bigquery/docs/reference/standard-sql/search_functions#search -class Search(Func): - arg_types = { - "this": True, # data_to_search / search_data - "expression": True, # search_query / search_string - "json_scope": False, # BigQuery: JSON_VALUES | JSON_KEYS | JSON_KEYS_AND_VALUES - "analyzer": False, # Both: analyzer / ANALYZER - "analyzer_options": False, # BigQuery: analyzer_options_values - "search_mode": False, # Snowflake: OR | AND - } - - -# Snowflake: https://docs.snowflake.com/en/sql-reference/functions/search_ip -class SearchIp(Func): - arg_types = {"this": True, "expression": True} - - -class StrToDate(Func): - arg_types = {"this": True, "format": False, "safe": False} - - -class StrToTime(Func): - arg_types = { - "this": True, - "format": True, - "zone": False, - "safe": False, - "target_type": False, - } - - -# Spark allows unix_timestamp() -# https://spark.apache.org/docs/3.1.3/api/python/reference/api/pyspark.sql.functions.unix_timestamp.html -class StrToUnix(Func): - arg_types = {"this": False, "format": False} - - -# https://prestodb.io/docs/current/functions/string.html -# https://spark.apache.org/docs/latest/api/sql/index.html#str_to_map -class StrToMap(Func): - arg_types = { - "this": True, - "pair_delim": False, - "key_value_delim": False, - "duplicate_resolution_callback": False, - } - - -class NumberToStr(Func): - arg_types = {"this": True, "format": True, "culture": False} - - -class FromBase(Func): - arg_types = {"this": True, "expression": True} - - -class Space(Func): - """ - SPACE(n) → string consisting of n blank characters - """ - - pass - - -class Struct(Func): - arg_types = {"expressions": False} - is_var_len_args = True - - -class StructExtract(Func): - arg_types = {"this": True, "expression": True} - - -# https://learn.microsoft.com/en-us/sql/t-sql/functions/stuff-transact-sql?view=sql-server-ver16 -# https://docs.snowflake.com/en/sql-reference/functions/insert -class Stuff(Func): - _sql_names = ["STUFF", "INSERT"] - arg_types = {"this": True, "start": True, "length": True, "expression": True} - - -class Sum(AggFunc): - pass - - -class Sqrt(Func): - pass - - -class Stddev(AggFunc): - _sql_names = ["STDDEV", "STDEV"] - - -class StddevPop(AggFunc): - pass - - -class StddevSamp(AggFunc): - pass - - -# https://cloud.google.com/bigquery/docs/reference/standard-sql/time_functions#time -class Time(Func): - arg_types = {"this": False, "zone": False} - - -class TimeToStr(Func): - arg_types = {"this": True, "format": True, "culture": False, "zone": False} - - -class TimeToTimeStr(Func): - pass - - -class TimeToUnix(Func): - pass - - -class TimeStrToDate(Func): - pass - - -class TimeStrToTime(Func): - arg_types = {"this": True, "zone": False} - - -class TimeStrToUnix(Func): - pass - - -class Trim(Func): - arg_types = { - "this": True, - "expression": False, - "position": False, - "collation": False, - } - - -class TsOrDsAdd(Func, TimeUnit): - # return_type is used to correctly cast the arguments of this expression when transpiling it - arg_types = {"this": True, "expression": True, "unit": False, "return_type": False} - - @property - def return_type(self) -> DataType: - return DataType.build(self.args.get("return_type") or DataType.Type.DATE) - - -class TsOrDsDiff(Func, TimeUnit): - arg_types = {"this": True, "expression": True, "unit": False} - - -class TsOrDsToDateStr(Func): - pass - - -class TsOrDsToDate(Func): - arg_types = {"this": True, "format": False, "safe": False} - - -class TsOrDsToDatetime(Func): - pass - - -class TsOrDsToTime(Func): - arg_types = {"this": True, "format": False, "safe": False} - - -class TsOrDsToTimestamp(Func): - pass - - -class TsOrDiToDi(Func): - pass - - -class Unhex(Func): - arg_types = {"this": True, "expression": False} - - -class Unicode(Func): - pass - - -class Uniform(Func): - arg_types = {"this": True, "expression": True, "gen": False, "seed": False} - - -# https://cloud.google.com/bigquery/docs/reference/standard-sql/date_functions#unix_date -class UnixDate(Func): - pass - - -class UnixToStr(Func): - arg_types = {"this": True, "format": False} - - -# https://prestodb.io/docs/current/functions/datetime.html -# presto has weird zone/hours/minutes -class UnixToTime(Func): - arg_types = { - "this": True, - "scale": False, - "zone": False, - "hours": False, - "minutes": False, - "format": False, - } - - SECONDS = Literal.number(0) - DECIS = Literal.number(1) - CENTIS = Literal.number(2) - MILLIS = Literal.number(3) - DECIMILLIS = Literal.number(4) - CENTIMILLIS = Literal.number(5) - MICROS = Literal.number(6) - DECIMICROS = Literal.number(7) - CENTIMICROS = Literal.number(8) - NANOS = Literal.number(9) - - -class UnixToTimeStr(Func): - pass - - -class UnixSeconds(Func): - pass - - -class UnixMicros(Func): - pass - - -class UnixMillis(Func): - pass - - -class Uuid(Func): - _sql_names = ["UUID", "GEN_RANDOM_UUID", "GENERATE_UUID", "UUID_STRING"] - - arg_types = {"this": False, "name": False, "is_string": False} - - -TIMESTAMP_PARTS = { - "year": False, - "month": False, - "day": False, - "hour": False, - "min": False, - "sec": False, - "nano": False, -} - - -class TimestampFromParts(Func): - _sql_names = ["TIMESTAMP_FROM_PARTS", "TIMESTAMPFROMPARTS"] - arg_types = { - **TIMESTAMP_PARTS, - "zone": False, - "milli": False, - "this": False, - "expression": False, - } - - -class TimestampLtzFromParts(Func): - _sql_names = ["TIMESTAMP_LTZ_FROM_PARTS", "TIMESTAMPLTZFROMPARTS"] - arg_types = TIMESTAMP_PARTS.copy() - - -class TimestampTzFromParts(Func): - _sql_names = ["TIMESTAMP_TZ_FROM_PARTS", "TIMESTAMPTZFROMPARTS"] - arg_types = { - **TIMESTAMP_PARTS, - "zone": False, - } - - -class Upper(Func): - _sql_names = ["UPPER", "UCASE"] - - -class Corr(Binary, AggFunc): - pass - - -# https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/CUME_DIST.html -class CumeDist(AggFunc): - arg_types = {"expressions": False} - is_var_len_args = True - - -class Variance(AggFunc): - _sql_names = ["VARIANCE", "VARIANCE_SAMP", "VAR_SAMP"] - - -class VariancePop(AggFunc): - _sql_names = ["VARIANCE_POP", "VAR_POP"] - - -class Skewness(AggFunc): - pass - - -class WidthBucket(Func): - arg_types = { - "this": True, - "min_value": True, - "max_value": True, - "num_buckets": True, - } - - -class CovarSamp(Binary, AggFunc): - pass - - -class CovarPop(Binary, AggFunc): - pass - - -class Week(Func): - arg_types = {"this": True, "mode": False} - - -class WeekStart(Expression): - pass - - -class NextDay(Func): - arg_types = {"this": True, "expression": True} - - -class XMLElement(Func): - _sql_names = ["XMLELEMENT"] - arg_types = {"this": True, "expressions": False} - - -class XMLGet(Func): - _sql_names = ["XMLGET"] - arg_types = {"this": True, "expression": True, "instance": False} - - -class XMLTable(Func): - arg_types = { - "this": True, - "namespaces": False, - "passing": False, - "columns": False, - "by_ref": False, - } - - -class XMLNamespace(Expression): - pass - - -# https://learn.microsoft.com/en-us/sql/t-sql/queries/select-for-clause-transact-sql?view=sql-server-ver17#syntax -class XMLKeyValueOption(Expression): - arg_types = {"this": True, "expression": False} - - -class Year(Func): - pass - - -class Zipf(Func): - arg_types = {"this": True, "elementcount": True, "gen": True} - - -class Use(Expression): - arg_types = {"this": False, "expressions": False, "kind": False} - - -class Merge(DML): - arg_types = { - "this": True, - "using": True, - "on": False, - "using_cond": False, - "whens": True, - "with_": False, - "returning": False, - } - - -class When(Expression): - arg_types = {"matched": True, "source": False, "condition": False, "then": True} - - -class Whens(Expression): - """Wraps around one or more WHEN [NOT] MATCHED [...] clauses.""" - - arg_types = {"expressions": True} - - -# https://docs.oracle.com/javadb/10.8.3.0/ref/rrefsqljnextvaluefor.html -# https://learn.microsoft.com/en-us/sql/t-sql/functions/next-value-for-transact-sql?view=sql-server-ver16 -class NextValueFor(Func): - arg_types = {"this": True, "order": False} - - -# Refers to a trailing semi-colon. This is only used to preserve trailing comments -# select 1; -- my comment -class Semicolon(Expression): - arg_types = {} - - -# BigQuery allows SELECT t FROM t and treats the projection as a struct value. This expression -# type is intended to be constructed by qualify so that we can properly annotate its type later -class TableColumn(Expression): - pass - - -ALL_FUNCTIONS = subclasses(__name__, Func, {AggFunc, Anonymous, Func}) -FUNCTION_BY_NAME = {name: func for func in ALL_FUNCTIONS for name in func.sql_names()} - -JSON_PATH_PARTS = subclasses(__name__, JSONPathPart, {JSONPathPart}) - -PERCENTILES = (PercentileCont, PercentileDisc) - - -# Helpers -@t.overload -def maybe_parse( - sql_or_expression: ExpOrStr, - *, - into: t.Type[E], - dialect: DialectType = None, - prefix: t.Optional[str] = None, - copy: bool = False, - **opts, -) -> E: ... - - -@t.overload -def maybe_parse( - sql_or_expression: str | E, - *, - into: t.Optional[IntoType] = None, - dialect: DialectType = None, - prefix: t.Optional[str] = None, - copy: bool = False, - **opts, -) -> E: ... - - -def maybe_parse( - sql_or_expression: ExpOrStr, - *, - into: t.Optional[IntoType] = None, - dialect: DialectType = None, - prefix: t.Optional[str] = None, - copy: bool = False, - **opts, -) -> Expression: - """Gracefully handle a possible string or expression. - - Example: - >>> maybe_parse("1") - Literal(this=1, is_string=False) - >>> maybe_parse(to_identifier("x")) - Identifier(this=x, quoted=False) - - Args: - sql_or_expression: the SQL code string or an expression - into: the SQLGlot Expression to parse into - dialect: the dialect used to parse the input expressions (in the case that an - input expression is a SQL string). - prefix: a string to prefix the sql with before it gets parsed - (automatically includes a space) - copy: whether to copy the expression. - **opts: other options to use to parse the input expressions (again, in the case - that an input expression is a SQL string). - - Returns: - Expression: the parsed or given expression. - """ - if isinstance(sql_or_expression, Expression): - if copy: - return sql_or_expression.copy() - return sql_or_expression - - if sql_or_expression is None: - raise ParseError("SQL cannot be None") - - import bigframes_vendored.sqlglot - - sql = str(sql_or_expression) - if prefix: - sql = f"{prefix} {sql}" - - return bigframes_vendored.sqlglot.parse_one(sql, read=dialect, into=into, **opts) - - -@t.overload -def maybe_copy(instance: None, copy: bool = True) -> None: ... - - -@t.overload -def maybe_copy(instance: E, copy: bool = True) -> E: ... - - -def maybe_copy(instance, copy=True): - return instance.copy() if copy and instance else instance - - -def _to_s( - node: t.Any, verbose: bool = False, level: int = 0, repr_str: bool = False -) -> str: - """Generate a textual representation of an Expression tree""" - indent = "\n" + (" " * (level + 1)) - delim = f",{indent}" - - if isinstance(node, Expression): - args = { - k: v for k, v in node.args.items() if (v is not None and v != []) or verbose - } - - if (node.type or verbose) and not isinstance(node, DataType): - args["_type"] = node.type - if node.comments or verbose: - args["_comments"] = node.comments - - if verbose: - args["_id"] = id(node) - - # Inline leaves for a more compact representation - if node.is_leaf(): - indent = "" - delim = ", " - - repr_str = node.is_string or (isinstance(node, Identifier) and node.quoted) - items = delim.join( - [ - f"{k}={_to_s(v, verbose, level + 1, repr_str=repr_str)}" - for k, v in args.items() - ] - ) - return f"{node.__class__.__name__}({indent}{items})" - - if isinstance(node, list): - items = delim.join(_to_s(i, verbose, level + 1) for i in node) - items = f"{indent}{items}" if items else "" - return f"[{items}]" - - # We use the representation of the string to avoid stripping out important whitespace - if repr_str and isinstance(node, str): - node = repr(node) - - # Indent multiline strings to match the current level - return indent.join(textwrap.dedent(str(node).strip("\n")).splitlines()) - - -def _is_wrong_expression(expression, into): - return isinstance(expression, Expression) and not isinstance(expression, into) - - -def _apply_builder( - expression, - instance, - arg, - copy=True, - prefix=None, - into=None, - dialect=None, - into_arg="this", - **opts, -): - if _is_wrong_expression(expression, into): - expression = into(**{into_arg: expression}) - instance = maybe_copy(instance, copy) - expression = maybe_parse( - sql_or_expression=expression, - prefix=prefix, - into=into, - dialect=dialect, - **opts, - ) - instance.set(arg, expression) - return instance - - -def _apply_child_list_builder( - *expressions, - instance, - arg, - append=True, - copy=True, - prefix=None, - into=None, - dialect=None, - properties=None, - **opts, -): - instance = maybe_copy(instance, copy) - parsed = [] - properties = {} if properties is None else properties - - for expression in expressions: - if expression is not None: - if _is_wrong_expression(expression, into): - expression = into(expressions=[expression]) - - expression = maybe_parse( - expression, - into=into, - dialect=dialect, - prefix=prefix, - **opts, - ) - for k, v in expression.args.items(): - if k == "expressions": - parsed.extend(v) - else: - properties[k] = v - - existing = instance.args.get(arg) - if append and existing: - parsed = existing.expressions + parsed - - child = into(expressions=parsed) - for k, v in properties.items(): - child.set(k, v) - instance.set(arg, child) - - return instance - - -def _apply_list_builder( - *expressions, - instance, - arg, - append=True, - copy=True, - prefix=None, - into=None, - dialect=None, - **opts, -): - inst = maybe_copy(instance, copy) - - expressions = [ - maybe_parse( - sql_or_expression=expression, - into=into, - prefix=prefix, - dialect=dialect, - **opts, - ) - for expression in expressions - if expression is not None - ] - - existing_expressions = inst.args.get(arg) - if append and existing_expressions: - expressions = existing_expressions + expressions - - inst.set(arg, expressions) - return inst - - -def _apply_conjunction_builder( - *expressions, - instance, - arg, - into=None, - append=True, - copy=True, - dialect=None, - **opts, -): - expressions = [exp for exp in expressions if exp is not None and exp != ""] - if not expressions: - return instance - - inst = maybe_copy(instance, copy) - - existing = inst.args.get(arg) - if append and existing is not None: - expressions = [existing.this if into else existing] + list(expressions) - - node = and_(*expressions, dialect=dialect, copy=copy, **opts) - - inst.set(arg, into(this=node) if into else node) - return inst - - -def _apply_cte_builder( - instance: E, - alias: ExpOrStr, - as_: ExpOrStr, - recursive: t.Optional[bool] = None, - materialized: t.Optional[bool] = None, - append: bool = True, - dialect: DialectType = None, - copy: bool = True, - scalar: t.Optional[bool] = None, - **opts, -) -> E: - alias_expression = maybe_parse(alias, dialect=dialect, into=TableAlias, **opts) - as_expression = maybe_parse(as_, dialect=dialect, copy=copy, **opts) - if scalar and not isinstance(as_expression, Subquery): - # scalar CTE must be wrapped in a subquery - as_expression = Subquery(this=as_expression) - cte = CTE( - this=as_expression, - alias=alias_expression, - materialized=materialized, - scalar=scalar, - ) - return _apply_child_list_builder( - cte, - instance=instance, - arg="with_", - append=append, - copy=copy, - into=With, - properties={"recursive": recursive} if recursive else {}, - ) - - -def _combine( - expressions: t.Sequence[t.Optional[ExpOrStr]], - operator: t.Type[Connector], - dialect: DialectType = None, - copy: bool = True, - wrap: bool = True, - **opts, -) -> Expression: - conditions = [ - condition(expression, dialect=dialect, copy=copy, **opts) - for expression in expressions - if expression is not None - ] - - this, *rest = conditions - if rest and wrap: - this = _wrap(this, Connector) - for expression in rest: - this = operator( - this=this, expression=_wrap(expression, Connector) if wrap else expression - ) - - return this - - -@t.overload -def _wrap(expression: None, kind: t.Type[Expression]) -> None: ... - - -@t.overload -def _wrap(expression: E, kind: t.Type[Expression]) -> E | Paren: ... - - -def _wrap(expression: t.Optional[E], kind: t.Type[Expression]) -> t.Optional[E] | Paren: - return Paren(this=expression) if isinstance(expression, kind) else expression - - -def _apply_set_operation( - *expressions: ExpOrStr, - set_operation: t.Type[S], - distinct: bool = True, - dialect: DialectType = None, - copy: bool = True, - **opts, -) -> S: - return reduce( - lambda x, y: set_operation(this=x, expression=y, distinct=distinct, **opts), - (maybe_parse(e, dialect=dialect, copy=copy, **opts) for e in expressions), - ) - - -def union( - *expressions: ExpOrStr, - distinct: bool = True, - dialect: DialectType = None, - copy: bool = True, - **opts, -) -> Union: - """ - Initializes a syntax tree for the `UNION` operation. - - Example: - >>> union("SELECT * FROM foo", "SELECT * FROM bla").sql() - 'SELECT * FROM foo UNION SELECT * FROM bla' - - Args: - expressions: the SQL code strings, corresponding to the `UNION`'s operands. - If `Expression` instances are passed, they will be used as-is. - distinct: set the DISTINCT flag if and only if this is true. - dialect: the dialect used to parse the input expression. - copy: whether to copy the expression. - opts: other options to use to parse the input expressions. - - Returns: - The new Union instance. - """ - assert len(expressions) >= 2, "At least two expressions are required by `union`." - return _apply_set_operation( - *expressions, - set_operation=Union, - distinct=distinct, - dialect=dialect, - copy=copy, - **opts, - ) - - -def intersect( - *expressions: ExpOrStr, - distinct: bool = True, - dialect: DialectType = None, - copy: bool = True, - **opts, -) -> Intersect: - """ - Initializes a syntax tree for the `INTERSECT` operation. - - Example: - >>> intersect("SELECT * FROM foo", "SELECT * FROM bla").sql() - 'SELECT * FROM foo INTERSECT SELECT * FROM bla' - - Args: - expressions: the SQL code strings, corresponding to the `INTERSECT`'s operands. - If `Expression` instances are passed, they will be used as-is. - distinct: set the DISTINCT flag if and only if this is true. - dialect: the dialect used to parse the input expression. - copy: whether to copy the expression. - opts: other options to use to parse the input expressions. - - Returns: - The new Intersect instance. - """ - assert len(expressions) >= 2, ( - "At least two expressions are required by `intersect`." - ) - return _apply_set_operation( - *expressions, - set_operation=Intersect, - distinct=distinct, - dialect=dialect, - copy=copy, - **opts, - ) - - -def except_( - *expressions: ExpOrStr, - distinct: bool = True, - dialect: DialectType = None, - copy: bool = True, - **opts, -) -> Except: - """ - Initializes a syntax tree for the `EXCEPT` operation. - - Example: - >>> except_("SELECT * FROM foo", "SELECT * FROM bla").sql() - 'SELECT * FROM foo EXCEPT SELECT * FROM bla' - - Args: - expressions: the SQL code strings, corresponding to the `EXCEPT`'s operands. - If `Expression` instances are passed, they will be used as-is. - distinct: set the DISTINCT flag if and only if this is true. - dialect: the dialect used to parse the input expression. - copy: whether to copy the expression. - opts: other options to use to parse the input expressions. - - Returns: - The new Except instance. - """ - assert len(expressions) >= 2, "At least two expressions are required by `except_`." - return _apply_set_operation( - *expressions, - set_operation=Except, - distinct=distinct, - dialect=dialect, - copy=copy, - **opts, - ) - - -def select(*expressions: ExpOrStr, dialect: DialectType = None, **opts) -> Select: - """ - Initializes a syntax tree from one or multiple SELECT expressions. - - Example: - >>> select("col1", "col2").from_("tbl").sql() - 'SELECT col1, col2 FROM tbl' - - Args: - *expressions: the SQL code string to parse as the expressions of a - SELECT statement. If an Expression instance is passed, this is used as-is. - dialect: the dialect used to parse the input expressions (in the case that an - input expression is a SQL string). - **opts: other options to use to parse the input expressions (again, in the case - that an input expression is a SQL string). - - Returns: - Select: the syntax tree for the SELECT statement. - """ - return Select().select(*expressions, dialect=dialect, **opts) - - -def from_(expression: ExpOrStr, dialect: DialectType = None, **opts) -> Select: - """ - Initializes a syntax tree from a FROM expression. - - Example: - >>> from_("tbl").select("col1", "col2").sql() - 'SELECT col1, col2 FROM tbl' - - Args: - *expression: the SQL code string to parse as the FROM expressions of a - SELECT statement. If an Expression instance is passed, this is used as-is. - dialect: the dialect used to parse the input expression (in the case that the - input expression is a SQL string). - **opts: other options to use to parse the input expressions (again, in the case - that the input expression is a SQL string). - - Returns: - Select: the syntax tree for the SELECT statement. - """ - return Select().from_(expression, dialect=dialect, **opts) - - -def update( - table: str | Table, - properties: t.Optional[dict] = None, - where: t.Optional[ExpOrStr] = None, - from_: t.Optional[ExpOrStr] = None, - with_: t.Optional[t.Dict[str, ExpOrStr]] = None, - dialect: DialectType = None, - **opts, -) -> Update: - """ - Creates an update statement. - - Example: - >>> update("my_table", {"x": 1, "y": "2", "z": None}, from_="baz_cte", where="baz_cte.id > 1 and my_table.id = baz_cte.id", with_={"baz_cte": "SELECT id FROM foo"}).sql() - "WITH baz_cte AS (SELECT id FROM foo) UPDATE my_table SET x = 1, y = '2', z = NULL FROM baz_cte WHERE baz_cte.id > 1 AND my_table.id = baz_cte.id" - - Args: - properties: dictionary of properties to SET which are - auto converted to sql objects eg None -> NULL - where: sql conditional parsed into a WHERE statement - from_: sql statement parsed into a FROM statement - with_: dictionary of CTE aliases / select statements to include in a WITH clause. - dialect: the dialect used to parse the input expressions. - **opts: other options to use to parse the input expressions. - - Returns: - Update: the syntax tree for the UPDATE statement. - """ - update_expr = Update(this=maybe_parse(table, into=Table, dialect=dialect)) - if properties: - update_expr.set( - "expressions", - [ - EQ(this=maybe_parse(k, dialect=dialect, **opts), expression=convert(v)) - for k, v in properties.items() - ], - ) - if from_: - update_expr.set( - "from_", - maybe_parse(from_, into=From, dialect=dialect, prefix="FROM", **opts), - ) - if isinstance(where, Condition): - where = Where(this=where) - if where: - update_expr.set( - "where", - maybe_parse(where, into=Where, dialect=dialect, prefix="WHERE", **opts), - ) - if with_: - cte_list = [ - alias_( - CTE(this=maybe_parse(qry, dialect=dialect, **opts)), alias, table=True - ) - for alias, qry in with_.items() - ] - update_expr.set( - "with_", - With(expressions=cte_list), - ) - return update_expr - - -def delete( - table: ExpOrStr, - where: t.Optional[ExpOrStr] = None, - returning: t.Optional[ExpOrStr] = None, - dialect: DialectType = None, - **opts, -) -> Delete: - """ - Builds a delete statement. - - Example: - >>> delete("my_table", where="id > 1").sql() - 'DELETE FROM my_table WHERE id > 1' - - Args: - where: sql conditional parsed into a WHERE statement - returning: sql conditional parsed into a RETURNING statement - dialect: the dialect used to parse the input expressions. - **opts: other options to use to parse the input expressions. - - Returns: - Delete: the syntax tree for the DELETE statement. - """ - delete_expr = Delete().delete(table, dialect=dialect, copy=False, **opts) - if where: - delete_expr = delete_expr.where(where, dialect=dialect, copy=False, **opts) - if returning: - delete_expr = delete_expr.returning( - returning, dialect=dialect, copy=False, **opts - ) - return delete_expr - - -def insert( - expression: ExpOrStr, - into: ExpOrStr, - columns: t.Optional[t.Sequence[str | Identifier]] = None, - overwrite: t.Optional[bool] = None, - returning: t.Optional[ExpOrStr] = None, - dialect: DialectType = None, - copy: bool = True, - **opts, -) -> Insert: - """ - Builds an INSERT statement. - - Example: - >>> insert("VALUES (1, 2, 3)", "tbl").sql() - 'INSERT INTO tbl VALUES (1, 2, 3)' - - Args: - expression: the sql string or expression of the INSERT statement - into: the tbl to insert data to. - columns: optionally the table's column names. - overwrite: whether to INSERT OVERWRITE or not. - returning: sql conditional parsed into a RETURNING statement - dialect: the dialect used to parse the input expressions. - copy: whether to copy the expression. - **opts: other options to use to parse the input expressions. - - Returns: - Insert: the syntax tree for the INSERT statement. - """ - expr = maybe_parse(expression, dialect=dialect, copy=copy, **opts) - this: Table | Schema = maybe_parse( - into, into=Table, dialect=dialect, copy=copy, **opts - ) - - if columns: - this = Schema( - this=this, expressions=[to_identifier(c, copy=copy) for c in columns] - ) - - insert = Insert(this=this, expression=expr, overwrite=overwrite) - - if returning: - insert = insert.returning(returning, dialect=dialect, copy=False, **opts) - - return insert - - -def merge( - *when_exprs: ExpOrStr, - into: ExpOrStr, - using: ExpOrStr, - on: ExpOrStr, - returning: t.Optional[ExpOrStr] = None, - dialect: DialectType = None, - copy: bool = True, - **opts, -) -> Merge: - """ - Builds a MERGE statement. - - Example: - >>> merge("WHEN MATCHED THEN UPDATE SET col1 = source_table.col1", - ... "WHEN NOT MATCHED THEN INSERT (col1) VALUES (source_table.col1)", - ... into="my_table", - ... using="source_table", - ... on="my_table.id = source_table.id").sql() - 'MERGE INTO my_table USING source_table ON my_table.id = source_table.id WHEN MATCHED THEN UPDATE SET col1 = source_table.col1 WHEN NOT MATCHED THEN INSERT (col1) VALUES (source_table.col1)' - - Args: - *when_exprs: The WHEN clauses specifying actions for matched and unmatched rows. - into: The target table to merge data into. - using: The source table to merge data from. - on: The join condition for the merge. - returning: The columns to return from the merge. - dialect: The dialect used to parse the input expressions. - copy: Whether to copy the expression. - **opts: Other options to use to parse the input expressions. - - Returns: - Merge: The syntax tree for the MERGE statement. - """ - expressions: t.List[Expression] = [] - for when_expr in when_exprs: - expression = maybe_parse( - when_expr, dialect=dialect, copy=copy, into=Whens, **opts - ) - expressions.extend( - [expression] if isinstance(expression, When) else expression.expressions - ) - - merge = Merge( - this=maybe_parse(into, dialect=dialect, copy=copy, **opts), - using=maybe_parse(using, dialect=dialect, copy=copy, **opts), - on=maybe_parse(on, dialect=dialect, copy=copy, **opts), - whens=Whens(expressions=expressions), - ) - if returning: - merge = merge.returning(returning, dialect=dialect, copy=False, **opts) - - if isinstance(using_clause := merge.args.get("using"), Alias): - using_clause.replace( - alias_(using_clause.this, using_clause.args["alias"], table=True) - ) - - return merge - - -def condition( - expression: ExpOrStr, dialect: DialectType = None, copy: bool = True, **opts -) -> Condition: - """ - Initialize a logical condition expression. - - Example: - >>> condition("x=1").sql() - 'x = 1' - - This is helpful for composing larger logical syntax trees: - >>> where = condition("x=1") - >>> where = where.and_("y=1") - >>> Select().from_("tbl").select("*").where(where).sql() - 'SELECT * FROM tbl WHERE x = 1 AND y = 1' - - Args: - *expression: the SQL code string to parse. - If an Expression instance is passed, this is used as-is. - dialect: the dialect used to parse the input expression (in the case that the - input expression is a SQL string). - copy: Whether to copy `expression` (only applies to expressions). - **opts: other options to use to parse the input expressions (again, in the case - that the input expression is a SQL string). - - Returns: - The new Condition instance - """ - return maybe_parse( - expression, - into=Condition, - dialect=dialect, - copy=copy, - **opts, - ) - - -def and_( - *expressions: t.Optional[ExpOrStr], - dialect: DialectType = None, - copy: bool = True, - wrap: bool = True, - **opts, -) -> Condition: - """ - Combine multiple conditions with an AND logical operator. - - Example: - >>> and_("x=1", and_("y=1", "z=1")).sql() - 'x = 1 AND (y = 1 AND z = 1)' - - Args: - *expressions: the SQL code strings to parse. - If an Expression instance is passed, this is used as-is. - dialect: the dialect used to parse the input expression. - copy: whether to copy `expressions` (only applies to Expressions). - wrap: whether to wrap the operands in `Paren`s. This is true by default to avoid - precedence issues, but can be turned off when the produced AST is too deep and - causes recursion-related issues. - **opts: other options to use to parse the input expressions. - - Returns: - The new condition - """ - return t.cast( - Condition, _combine(expressions, And, dialect, copy=copy, wrap=wrap, **opts) - ) - - -def or_( - *expressions: t.Optional[ExpOrStr], - dialect: DialectType = None, - copy: bool = True, - wrap: bool = True, - **opts, -) -> Condition: - """ - Combine multiple conditions with an OR logical operator. - - Example: - >>> or_("x=1", or_("y=1", "z=1")).sql() - 'x = 1 OR (y = 1 OR z = 1)' - - Args: - *expressions: the SQL code strings to parse. - If an Expression instance is passed, this is used as-is. - dialect: the dialect used to parse the input expression. - copy: whether to copy `expressions` (only applies to Expressions). - wrap: whether to wrap the operands in `Paren`s. This is true by default to avoid - precedence issues, but can be turned off when the produced AST is too deep and - causes recursion-related issues. - **opts: other options to use to parse the input expressions. - - Returns: - The new condition - """ - return t.cast( - Condition, _combine(expressions, Or, dialect, copy=copy, wrap=wrap, **opts) - ) - - -def xor( - *expressions: t.Optional[ExpOrStr], - dialect: DialectType = None, - copy: bool = True, - wrap: bool = True, - **opts, -) -> Condition: - """ - Combine multiple conditions with an XOR logical operator. - - Example: - >>> xor("x=1", xor("y=1", "z=1")).sql() - 'x = 1 XOR (y = 1 XOR z = 1)' - - Args: - *expressions: the SQL code strings to parse. - If an Expression instance is passed, this is used as-is. - dialect: the dialect used to parse the input expression. - copy: whether to copy `expressions` (only applies to Expressions). - wrap: whether to wrap the operands in `Paren`s. This is true by default to avoid - precedence issues, but can be turned off when the produced AST is too deep and - causes recursion-related issues. - **opts: other options to use to parse the input expressions. - - Returns: - The new condition - """ - return t.cast( - Condition, _combine(expressions, Xor, dialect, copy=copy, wrap=wrap, **opts) - ) - - -def not_( - expression: ExpOrStr, dialect: DialectType = None, copy: bool = True, **opts -) -> Not: - """ - Wrap a condition with a NOT operator. - - Example: - >>> not_("this_suit='black'").sql() - "NOT this_suit = 'black'" - - Args: - expression: the SQL code string to parse. - If an Expression instance is passed, this is used as-is. - dialect: the dialect used to parse the input expression. - copy: whether to copy the expression or not. - **opts: other options to use to parse the input expressions. - - Returns: - The new condition. - """ - this = condition( - expression, - dialect=dialect, - copy=copy, - **opts, - ) - return Not(this=_wrap(this, Connector)) - - -def paren(expression: ExpOrStr, copy: bool = True) -> Paren: - """ - Wrap an expression in parentheses. - - Example: - >>> paren("5 + 3").sql() - '(5 + 3)' - - Args: - expression: the SQL code string to parse. - If an Expression instance is passed, this is used as-is. - copy: whether to copy the expression or not. - - Returns: - The wrapped expression. - """ - return Paren(this=maybe_parse(expression, copy=copy)) - - -SAFE_IDENTIFIER_RE: t.Pattern[str] = re.compile(r"^[_a-zA-Z][\w]*$") - - -@t.overload -def to_identifier( - name: None, quoted: t.Optional[bool] = None, copy: bool = True -) -> None: ... - - -@t.overload -def to_identifier( - name: str | Identifier, quoted: t.Optional[bool] = None, copy: bool = True -) -> Identifier: ... - - -def to_identifier(name, quoted=None, copy=True): - """Builds an identifier. - - Args: - name: The name to turn into an identifier. - quoted: Whether to force quote the identifier. - copy: Whether to copy name if it's an Identifier. - - Returns: - The identifier ast node. - """ - - if name is None: - return None - - if isinstance(name, Identifier): - identifier = maybe_copy(name, copy) - elif isinstance(name, str): - identifier = Identifier( - this=name, - quoted=not SAFE_IDENTIFIER_RE.match(name) if quoted is None else quoted, - ) - else: - raise ValueError( - f"Name needs to be a string or an Identifier, got: {name.__class__}" - ) - return identifier - - -def parse_identifier(name: str | Identifier, dialect: DialectType = None) -> Identifier: - """ - Parses a given string into an identifier. - - Args: - name: The name to parse into an identifier. - dialect: The dialect to parse against. - - Returns: - The identifier ast node. - """ - try: - expression = maybe_parse(name, dialect=dialect, into=Identifier) - except (ParseError, TokenError): - expression = to_identifier(name) - - return expression - - -INTERVAL_STRING_RE = re.compile(r"\s*(-?[0-9]+(?:\.[0-9]+)?)\s*([a-zA-Z]+)\s*") - -# Matches day-time interval strings that contain -# - A number of days (possibly negative or with decimals) -# - At least one space -# - Portions of a time-like signature, potentially negative -# - Standard format [-]h+:m+:s+[.f+] -# - Just minutes/seconds/frac seconds [-]m+:s+.f+ -# - Just hours, minutes, maybe colon [-]h+:m+[:] -# - Just hours, maybe colon [-]h+[:] -# - Just colon : -INTERVAL_DAY_TIME_RE = re.compile( - r"\s*-?\s*\d+(?:\.\d+)?\s+(?:-?(?:\d+:)?\d+:\d+(?:\.\d+)?|-?(?:\d+:){1,2}|:)\s*" -) - - -def to_interval(interval: str | Literal) -> Interval: - """Builds an interval expression from a string like '1 day' or '5 months'.""" - if isinstance(interval, Literal): - if not interval.is_string: - raise ValueError("Invalid interval string.") - - interval = interval.this - - interval = maybe_parse(f"INTERVAL {interval}") - assert isinstance(interval, Interval) - return interval - - -def to_table( - sql_path: str | Table, dialect: DialectType = None, copy: bool = True, **kwargs -) -> Table: - """ - Create a table expression from a `[catalog].[schema].[table]` sql path. Catalog and schema are optional. - If a table is passed in then that table is returned. - - Args: - sql_path: a `[catalog].[schema].[table]` string. - dialect: the source dialect according to which the table name will be parsed. - copy: Whether to copy a table if it is passed in. - kwargs: the kwargs to instantiate the resulting `Table` expression with. - - Returns: - A table expression. - """ - if isinstance(sql_path, Table): - return maybe_copy(sql_path, copy=copy) - - try: - table = maybe_parse(sql_path, into=Table, dialect=dialect) - except ParseError: - catalog, db, this = split_num_words(sql_path, ".", 3) - - if not this: - raise - - table = table_(this, db=db, catalog=catalog) - - for k, v in kwargs.items(): - table.set(k, v) - - return table - - -def to_column( - sql_path: str | Column, - quoted: t.Optional[bool] = None, - dialect: DialectType = None, - copy: bool = True, - **kwargs, -) -> Column: - """ - Create a column from a `[table].[column]` sql path. Table is optional. - If a column is passed in then that column is returned. - - Args: - sql_path: a `[table].[column]` string. - quoted: Whether or not to force quote identifiers. - dialect: the source dialect according to which the column name will be parsed. - copy: Whether to copy a column if it is passed in. - kwargs: the kwargs to instantiate the resulting `Column` expression with. - - Returns: - A column expression. - """ - if isinstance(sql_path, Column): - return maybe_copy(sql_path, copy=copy) - - try: - col = maybe_parse(sql_path, into=Column, dialect=dialect) - except ParseError: - return column(*reversed(sql_path.split(".")), quoted=quoted, **kwargs) - - for k, v in kwargs.items(): - col.set(k, v) - - if quoted: - for i in col.find_all(Identifier): - i.set("quoted", True) - - return col - - -def alias_( - expression: ExpOrStr, - alias: t.Optional[str | Identifier], - table: bool | t.Sequence[str | Identifier] = False, - quoted: t.Optional[bool] = None, - dialect: DialectType = None, - copy: bool = True, - **opts, -): - """Create an Alias expression. - - Example: - >>> alias_('foo', 'bar').sql() - 'foo AS bar' - - >>> alias_('(select 1, 2)', 'bar', table=['a', 'b']).sql() - '(SELECT 1, 2) AS bar(a, b)' - - Args: - expression: the SQL code strings to parse. - If an Expression instance is passed, this is used as-is. - alias: the alias name to use. If the name has - special characters it is quoted. - table: Whether to create a table alias, can also be a list of columns. - quoted: whether to quote the alias - dialect: the dialect used to parse the input expression. - copy: Whether to copy the expression. - **opts: other options to use to parse the input expressions. - - Returns: - Alias: the aliased expression - """ - exp = maybe_parse(expression, dialect=dialect, copy=copy, **opts) - alias = to_identifier(alias, quoted=quoted) - - if table: - table_alias = TableAlias(this=alias) - exp.set("alias", table_alias) - - if not isinstance(table, bool): - for column in table: - table_alias.append("columns", to_identifier(column, quoted=quoted)) - - return exp - - # We don't set the "alias" arg for Window expressions, because that would add an IDENTIFIER node in - # the AST, representing a "named_window" [1] construct (eg. bigquery). What we want is an ALIAS node - # for the complete Window expression. - # - # [1]: https://cloud.google.com/bigquery/docs/reference/standard-sql/window-function-calls - - if "alias" in exp.arg_types and not isinstance(exp, Window): - exp.set("alias", alias) - return exp - return Alias(this=exp, alias=alias) - - -def subquery( - expression: ExpOrStr, - alias: t.Optional[Identifier | str] = None, - dialect: DialectType = None, - **opts, -) -> Select: - """ - Build a subquery expression that's selected from. - - Example: - >>> subquery('select x from tbl', 'bar').select('x').sql() - 'SELECT x FROM (SELECT x FROM tbl) AS bar' - - Args: - expression: the SQL code strings to parse. - If an Expression instance is passed, this is used as-is. - alias: the alias name to use. - dialect: the dialect used to parse the input expression. - **opts: other options to use to parse the input expressions. - - Returns: - A new Select instance with the subquery expression included. - """ - - expression = maybe_parse(expression, dialect=dialect, **opts).subquery( - alias, **opts - ) - return Select().from_(expression, dialect=dialect, **opts) - - -@t.overload -def column( - col: str | Identifier, - table: t.Optional[str | Identifier] = None, - db: t.Optional[str | Identifier] = None, - catalog: t.Optional[str | Identifier] = None, - *, - fields: t.Collection[t.Union[str, Identifier]], - quoted: t.Optional[bool] = None, - copy: bool = True, -) -> Dot: - pass - - -@t.overload -def column( - col: str | Identifier | Star, - table: t.Optional[str | Identifier] = None, - db: t.Optional[str | Identifier] = None, - catalog: t.Optional[str | Identifier] = None, - *, - fields: Lit[None] = None, - quoted: t.Optional[bool] = None, - copy: bool = True, -) -> Column: - pass - - -def column( - col, - table=None, - db=None, - catalog=None, - *, - fields=None, - quoted=None, - copy=True, -): - """ - Build a Column. - - Args: - col: Column name. - table: Table name. - db: Database name. - catalog: Catalog name. - fields: Additional fields using dots. - quoted: Whether to force quotes on the column's identifiers. - copy: Whether to copy identifiers if passed in. - - Returns: - The new Column instance. - """ - if not isinstance(col, Star): - col = to_identifier(col, quoted=quoted, copy=copy) - - this = Column( - this=col, - table=to_identifier(table, quoted=quoted, copy=copy), - db=to_identifier(db, quoted=quoted, copy=copy), - catalog=to_identifier(catalog, quoted=quoted, copy=copy), - ) - - if fields: - this = Dot.build( - ( - this, - *(to_identifier(field, quoted=quoted, copy=copy) for field in fields), - ) - ) - return this - - -def cast( - expression: ExpOrStr, - to: DATA_TYPE, - copy: bool = True, - dialect: DialectType = None, - **opts, -) -> Cast: - """Cast an expression to a data type. - - Example: - >>> cast('x + 1', 'int').sql() - 'CAST(x + 1 AS INT)' - - Args: - expression: The expression to cast. - to: The datatype to cast to. - copy: Whether to copy the supplied expressions. - dialect: The target dialect. This is used to prevent a re-cast in the following scenario: - - The expression to be cast is already a exp.Cast expression - - The existing cast is to a type that is logically equivalent to new type - - For example, if :expression='CAST(x as DATETIME)' and :to=Type.TIMESTAMP, - but in the target dialect DATETIME is mapped to TIMESTAMP, then we will NOT return `CAST(x (as DATETIME) as TIMESTAMP)` - and instead just return the original expression `CAST(x as DATETIME)`. - - This is to prevent it being output as a double cast `CAST(x (as TIMESTAMP) as TIMESTAMP)` once the DATETIME -> TIMESTAMP - mapping is applied in the target dialect generator. - - Returns: - The new Cast instance. - """ - expr = maybe_parse(expression, copy=copy, dialect=dialect, **opts) - data_type = DataType.build(to, copy=copy, dialect=dialect, **opts) - - # dont re-cast if the expression is already a cast to the correct type - if isinstance(expr, Cast): - from bigframes_vendored.sqlglot.dialects.dialect import Dialect - - target_dialect = Dialect.get_or_raise(dialect) - type_mapping = target_dialect.generator_class.TYPE_MAPPING - - existing_cast_type: DataType.Type = expr.to.this - new_cast_type: DataType.Type = data_type.this - types_are_equivalent = type_mapping.get( - existing_cast_type, existing_cast_type.value - ) == type_mapping.get(new_cast_type, new_cast_type.value) - - if expr.is_type(data_type) or types_are_equivalent: - return expr - - expr = Cast(this=expr, to=data_type) - expr.type = data_type - - return expr - - -def table_( - table: Identifier | str, - db: t.Optional[Identifier | str] = None, - catalog: t.Optional[Identifier | str] = None, - quoted: t.Optional[bool] = None, - alias: t.Optional[Identifier | str] = None, -) -> Table: - """Build a Table. - - Args: - table: Table name. - db: Database name. - catalog: Catalog name. - quote: Whether to force quotes on the table's identifiers. - alias: Table's alias. - - Returns: - The new Table instance. - """ - return Table( - this=to_identifier(table, quoted=quoted) if table else None, - db=to_identifier(db, quoted=quoted) if db else None, - catalog=to_identifier(catalog, quoted=quoted) if catalog else None, - alias=TableAlias(this=to_identifier(alias)) if alias else None, - ) - - -def values( - values: t.Iterable[t.Tuple[t.Any, ...]], - alias: t.Optional[str] = None, - columns: t.Optional[t.Iterable[str] | t.Dict[str, DataType]] = None, -) -> Values: - """Build VALUES statement. - - Example: - >>> values([(1, '2')]).sql() - "VALUES (1, '2')" - - Args: - values: values statements that will be converted to SQL - alias: optional alias - columns: Optional list of ordered column names or ordered dictionary of column names to types. - If either are provided then an alias is also required. - - Returns: - Values: the Values expression object - """ - if columns and not alias: - raise ValueError("Alias is required when providing columns") - - return Values( - expressions=[convert(tup) for tup in values], - alias=( - TableAlias( - this=to_identifier(alias), columns=[to_identifier(x) for x in columns] - ) - if columns - else (TableAlias(this=to_identifier(alias)) if alias else None) - ), - ) - - -def var(name: t.Optional[ExpOrStr]) -> Var: - """Build a SQL variable. - - Example: - >>> repr(var('x')) - 'Var(this=x)' - - >>> repr(var(column('x', table='y'))) - 'Var(this=x)' - - Args: - name: The name of the var or an expression who's name will become the var. - - Returns: - The new variable node. - """ - if not name: - raise ValueError("Cannot convert empty name into var.") - - if isinstance(name, Expression): - name = name.name - return Var(this=name) - - -def rename_table( - old_name: str | Table, - new_name: str | Table, - dialect: DialectType = None, -) -> Alter: - """Build ALTER TABLE... RENAME... expression - - Args: - old_name: The old name of the table - new_name: The new name of the table - dialect: The dialect to parse the table. - - Returns: - Alter table expression - """ - old_table = to_table(old_name, dialect=dialect) - new_table = to_table(new_name, dialect=dialect) - return Alter( - this=old_table, - kind="TABLE", - actions=[ - AlterRename(this=new_table), - ], - ) - - -def rename_column( - table_name: str | Table, - old_column_name: str | Column, - new_column_name: str | Column, - exists: t.Optional[bool] = None, - dialect: DialectType = None, -) -> Alter: - """Build ALTER TABLE... RENAME COLUMN... expression - - Args: - table_name: Name of the table - old_column: The old name of the column - new_column: The new name of the column - exists: Whether to add the `IF EXISTS` clause - dialect: The dialect to parse the table/column. - - Returns: - Alter table expression - """ - table = to_table(table_name, dialect=dialect) - old_column = to_column(old_column_name, dialect=dialect) - new_column = to_column(new_column_name, dialect=dialect) - return Alter( - this=table, - kind="TABLE", - actions=[ - RenameColumn(this=old_column, to=new_column, exists=exists), - ], - ) - - -def convert(value: t.Any, copy: bool = False) -> Expression: - """Convert a python value into an expression object. - - Raises an error if a conversion is not possible. - - Args: - value: A python object. - copy: Whether to copy `value` (only applies to Expressions and collections). - - Returns: - The equivalent expression object. - """ - if isinstance(value, Expression): - return maybe_copy(value, copy) - if isinstance(value, str): - return Literal.string(value) - if isinstance(value, bool): - return Boolean(this=value) - if value is None or (isinstance(value, float) and math.isnan(value)): - return null() - if isinstance(value, numbers.Number): - return Literal.number(value) - if isinstance(value, bytes): - return HexString(this=value.hex()) - if isinstance(value, datetime.datetime): - datetime_literal = Literal.string(value.isoformat(sep=" ")) - - tz = None - if value.tzinfo: - # this works for zoneinfo.ZoneInfo, pytz.timezone and datetime.datetime.utc to return IANA timezone names like "America/Los_Angeles" - # instead of abbreviations like "PDT". This is for consistency with other timezone handling functions in SQLGlot - tz = Literal.string(str(value.tzinfo)) - - return TimeStrToTime(this=datetime_literal, zone=tz) - if isinstance(value, datetime.date): - date_literal = Literal.string(value.strftime("%Y-%m-%d")) - return DateStrToDate(this=date_literal) - if isinstance(value, datetime.time): - time_literal = Literal.string(value.isoformat()) - return TsOrDsToTime(this=time_literal) - if isinstance(value, tuple): - if hasattr(value, "_fields"): - return Struct( - expressions=[ - PropertyEQ( - this=to_identifier(k), - expression=convert(getattr(value, k), copy=copy), - ) - for k in value._fields - ] - ) - return Tuple(expressions=[convert(v, copy=copy) for v in value]) - if isinstance(value, list): - return Array(expressions=[convert(v, copy=copy) for v in value]) - if isinstance(value, dict): - return Map( - keys=Array(expressions=[convert(k, copy=copy) for k in value]), - values=Array(expressions=[convert(v, copy=copy) for v in value.values()]), - ) - if hasattr(value, "__dict__"): - return Struct( - expressions=[ - PropertyEQ(this=to_identifier(k), expression=convert(v, copy=copy)) - for k, v in value.__dict__.items() - ] - ) - raise ValueError(f"Cannot convert {value}") - - -def replace_children(expression: Expression, fun: t.Callable, *args, **kwargs) -> None: - """ - Replace children of an expression with the result of a lambda fun(child) -> exp. - """ - for k, v in tuple(expression.args.items()): - is_list_arg = type(v) is list - - child_nodes = v if is_list_arg else [v] - new_child_nodes = [] - - for cn in child_nodes: - if isinstance(cn, Expression): - for child_node in ensure_collection(fun(cn, *args, **kwargs)): - new_child_nodes.append(child_node) - else: - new_child_nodes.append(cn) - - expression.set( - k, new_child_nodes if is_list_arg else seq_get(new_child_nodes, 0) - ) - - -def replace_tree( - expression: Expression, - fun: t.Callable, - prune: t.Optional[t.Callable[[Expression], bool]] = None, -) -> Expression: - """ - Replace an entire tree with the result of function calls on each node. - - This will be traversed in reverse dfs, so leaves first. - If new nodes are created as a result of function calls, they will also be traversed. - """ - stack = list(expression.dfs(prune=prune)) - - while stack: - node = stack.pop() - new_node = fun(node) - - if new_node is not node: - node.replace(new_node) - - if isinstance(new_node, Expression): - stack.append(new_node) - - return new_node - - -def find_tables(expression: Expression) -> t.Set[Table]: - """ - Find all tables referenced in a query. - - Args: - expressions: The query to find the tables in. - - Returns: - A set of all the tables. - """ - from bigframes_vendored.sqlglot.optimizer.scope import traverse_scope - - return { - table - for scope in traverse_scope(expression) - for table in scope.tables - if table.name and table.name not in scope.cte_sources - } - - -def column_table_names(expression: Expression, exclude: str = "") -> t.Set[str]: - """ - Return all table names referenced through columns in an expression. - - Example: - >>> import sqlglot - >>> sorted(column_table_names(sqlglot.parse_one("a.b AND c.d AND c.e"))) - ['a', 'c'] - - Args: - expression: expression to find table names. - exclude: a table name to exclude - - Returns: - A list of unique names. - """ - return { - table - for table in (column.table for column in expression.find_all(Column)) - if table and table != exclude - } - - -def table_name( - table: Table | str, dialect: DialectType = None, identify: bool = False -) -> str: - """Get the full name of a table as a string. - - Args: - table: Table expression node or string. - dialect: The dialect to generate the table name for. - identify: Determines when an identifier should be quoted. Possible values are: - False (default): Never quote, except in cases where it's mandatory by the dialect. - True: Always quote. - - Examples: - >>> from sqlglot import exp, parse_one - >>> table_name(parse_one("select * from a.b.c").find(exp.Table)) - 'a.b.c' - - Returns: - The table name. - """ - - table = maybe_parse(table, into=Table, dialect=dialect) - - if not table: - raise ValueError(f"Cannot parse {table}") - - return ".".join( - ( - part.sql(dialect=dialect, identify=True, copy=False, comments=False) - if identify or not SAFE_IDENTIFIER_RE.match(part.name) - else part.name - ) - for part in table.parts - ) - - -def normalize_table_name( - table: str | Table, dialect: DialectType = None, copy: bool = True -) -> str: - """Returns a case normalized table name without quotes. - - Args: - table: the table to normalize - dialect: the dialect to use for normalization rules - copy: whether to copy the expression. - - Examples: - >>> normalize_table_name("`A-B`.c", dialect="bigquery") - 'A-B.c' - """ - from bigframes_vendored.sqlglot.optimizer.normalize_identifiers import ( - normalize_identifiers, - ) - - return ".".join( - p.name - for p in normalize_identifiers( - to_table(table, dialect=dialect, copy=copy), dialect=dialect - ).parts - ) - - -def replace_tables( - expression: E, - mapping: t.Dict[str, str], - dialect: DialectType = None, - copy: bool = True, -) -> E: - """Replace all tables in expression according to the mapping. - - Args: - expression: expression node to be transformed and replaced. - mapping: mapping of table names. - dialect: the dialect of the mapping table - copy: whether to copy the expression. - - Examples: - >>> from sqlglot import exp, parse_one - >>> replace_tables(parse_one("select * from a.b"), {"a.b": "c"}).sql() - 'SELECT * FROM c /* a.b */' - - Returns: - The mapped expression. - """ - - mapping = {normalize_table_name(k, dialect=dialect): v for k, v in mapping.items()} - - def _replace_tables(node: Expression) -> Expression: - if isinstance(node, Table) and node.meta.get("replace") is not False: - original = normalize_table_name(node, dialect=dialect) - new_name = mapping.get(original) - - if new_name: - table = to_table( - new_name, - **{k: v for k, v in node.args.items() if k not in TABLE_PARTS}, - dialect=dialect, - ) - table.add_comments([original]) - return table - return node - - return expression.transform(_replace_tables, copy=copy) # type: ignore - - -def replace_placeholders(expression: Expression, *args, **kwargs) -> Expression: - """Replace placeholders in an expression. - - Args: - expression: expression node to be transformed and replaced. - args: positional names that will substitute unnamed placeholders in the given order. - kwargs: keyword arguments that will substitute named placeholders. - - Examples: - >>> from sqlglot import exp, parse_one - >>> replace_placeholders( - ... parse_one("select * from :tbl where ? = ?"), - ... exp.to_identifier("str_col"), "b", tbl=exp.to_identifier("foo") - ... ).sql() - "SELECT * FROM foo WHERE str_col = 'b'" - - Returns: - The mapped expression. - """ - - def _replace_placeholders(node: Expression, args, **kwargs) -> Expression: - if isinstance(node, Placeholder): - if node.this: - new_name = kwargs.get(node.this) - if new_name is not None: - return convert(new_name) - else: - try: - return convert(next(args)) - except StopIteration: - pass - return node - - return expression.transform(_replace_placeholders, iter(args), **kwargs) - - -def expand( - expression: Expression, - sources: t.Dict[str, Query | t.Callable[[], Query]], - dialect: DialectType = None, - copy: bool = True, -) -> Expression: - """Transforms an expression by expanding all referenced sources into subqueries. - - Examples: - >>> from sqlglot import parse_one - >>> expand(parse_one("select * from x AS z"), {"x": parse_one("select * from y")}).sql() - 'SELECT * FROM (SELECT * FROM y) AS z /* source: x */' - - >>> expand(parse_one("select * from x AS z"), {"x": parse_one("select * from y"), "y": parse_one("select * from z")}).sql() - 'SELECT * FROM (SELECT * FROM (SELECT * FROM z) AS y /* source: y */) AS z /* source: x */' - - Args: - expression: The expression to expand. - sources: A dict of name to query or a callable that provides a query on demand. - dialect: The dialect of the sources dict or the callable. - copy: Whether to copy the expression during transformation. Defaults to True. - - Returns: - The transformed expression. - """ - normalized_sources = { - normalize_table_name(k, dialect=dialect): v for k, v in sources.items() - } - - def _expand(node: Expression): - if isinstance(node, Table): - name = normalize_table_name(node, dialect=dialect) - source = normalized_sources.get(name) - - if source: - # Create a subquery with the same alias (or table name if no alias) - parsed_source = source() if callable(source) else source - subquery = parsed_source.subquery(node.alias or name) - subquery.comments = [f"source: {name}"] - - # Continue expanding within the subquery - return subquery.transform(_expand, copy=False) - - return node - - return expression.transform(_expand, copy=copy) - - -def func( - name: str, *args, copy: bool = True, dialect: DialectType = None, **kwargs -) -> Func: - """ - Returns a Func expression. - - Examples: - >>> func("abs", 5).sql() - 'ABS(5)' - - >>> func("cast", this=5, to=DataType.build("DOUBLE")).sql() - 'CAST(5 AS DOUBLE)' - - Args: - name: the name of the function to build. - args: the args used to instantiate the function of interest. - copy: whether to copy the argument expressions. - dialect: the source dialect. - kwargs: the kwargs used to instantiate the function of interest. - - Note: - The arguments `args` and `kwargs` are mutually exclusive. - - Returns: - An instance of the function of interest, or an anonymous function, if `name` doesn't - correspond to an existing `sqlglot.expressions.Func` class. - """ - if args and kwargs: - raise ValueError("Can't use both args and kwargs to instantiate a function.") - - from bigframes_vendored.sqlglot.dialects.dialect import Dialect - - dialect = Dialect.get_or_raise(dialect) - - converted: t.List[Expression] = [ - maybe_parse(arg, dialect=dialect, copy=copy) for arg in args - ] - kwargs = { - key: maybe_parse(value, dialect=dialect, copy=copy) - for key, value in kwargs.items() - } - - constructor = dialect.parser_class.FUNCTIONS.get(name.upper()) - if constructor: - if converted: - if "dialect" in constructor.__code__.co_varnames: - function = constructor(converted, dialect=dialect) - else: - function = constructor(converted) - elif constructor.__name__ == "from_arg_list": - function = constructor.__self__(**kwargs) # type: ignore - else: - constructor = FUNCTION_BY_NAME.get(name.upper()) - if constructor: - function = constructor(**kwargs) - else: - raise ValueError( - f"Unable to convert '{name}' into a Func. Either manually construct " - "the Func expression of interest or parse the function call." - ) - else: - kwargs = kwargs or {"expressions": converted} - function = Anonymous(this=name, **kwargs) - - for error_message in function.error_messages(converted): - raise ValueError(error_message) - - return function - - -def case( - expression: t.Optional[ExpOrStr] = None, - **opts, -) -> Case: - """ - Initialize a CASE statement. - - Example: - case().when("a = 1", "foo").else_("bar") - - Args: - expression: Optionally, the input expression (not all dialects support this) - **opts: Extra keyword arguments for parsing `expression` - """ - if expression is not None: - this = maybe_parse(expression, **opts) - else: - this = None - return Case(this=this, ifs=[]) - - -def array( - *expressions: ExpOrStr, copy: bool = True, dialect: DialectType = None, **kwargs -) -> Array: - """ - Returns an array. - - Examples: - >>> array(1, 'x').sql() - 'ARRAY(1, x)' - - Args: - expressions: the expressions to add to the array. - copy: whether to copy the argument expressions. - dialect: the source dialect. - kwargs: the kwargs used to instantiate the function of interest. - - Returns: - An array expression. - """ - return Array( - expressions=[ - maybe_parse(expression, copy=copy, dialect=dialect, **kwargs) - for expression in expressions - ] - ) - - -def tuple_( - *expressions: ExpOrStr, copy: bool = True, dialect: DialectType = None, **kwargs -) -> Tuple: - """ - Returns an tuple. - - Examples: - >>> tuple_(1, 'x').sql() - '(1, x)' - - Args: - expressions: the expressions to add to the tuple. - copy: whether to copy the argument expressions. - dialect: the source dialect. - kwargs: the kwargs used to instantiate the function of interest. - - Returns: - A tuple expression. - """ - return Tuple( - expressions=[ - maybe_parse(expression, copy=copy, dialect=dialect, **kwargs) - for expression in expressions - ] - ) - - -def true() -> Boolean: - """ - Returns a true Boolean expression. - """ - return Boolean(this=True) - - -def false() -> Boolean: - """ - Returns a false Boolean expression. - """ - return Boolean(this=False) - - -def null() -> Null: - """ - Returns a Null expression. - """ - return Null() - - -NONNULL_CONSTANTS = ( - Literal, - Boolean, -) - -CONSTANTS = ( - Literal, - Boolean, - Null, -) diff --git a/third_party/bigframes_vendored/sqlglot/generator.py b/third_party/bigframes_vendored/sqlglot/generator.py deleted file mode 100644 index 80546fadc44..00000000000 --- a/third_party/bigframes_vendored/sqlglot/generator.py +++ /dev/null @@ -1,5850 +0,0 @@ -# Contains code from https://github.com/tobymao/sqlglot/blob/v28.5.0/sqlglot/generator.py - -from __future__ import annotations - -import logging -import re -import typing as t -from collections import defaultdict -from functools import reduce, wraps - -from bigframes_vendored.sqlglot import exp -from bigframes_vendored.sqlglot.errors import ( - ErrorLevel, - UnsupportedError, - concat_messages, -) -from bigframes_vendored.sqlglot.helper import ( - apply_index_offset, - csv, - name_sequence, - seq_get, -) -from bigframes_vendored.sqlglot.jsonpath import ( - ALL_JSON_PATH_PARTS, - JSON_PATH_PART_TRANSFORMS, -) -from bigframes_vendored.sqlglot.time import format_time -from bigframes_vendored.sqlglot.tokens import TokenType - -if t.TYPE_CHECKING: - from bigframes_vendored.sqlglot._typing import E - from bigframes_vendored.sqlglot.dialects.dialect import DialectType - - G = t.TypeVar("G", bound="Generator") - GeneratorMethod = t.Callable[[G, E], str] - -logger = logging.getLogger("sqlglot") - -ESCAPED_UNICODE_RE = re.compile(r"\\(\d+)") -UNSUPPORTED_TEMPLATE = ( - "Argument '{}' is not supported for expression '{}' when targeting {}." -) - - -def unsupported_args( - *args: t.Union[str, t.Tuple[str, str]], -) -> t.Callable[[GeneratorMethod], GeneratorMethod]: - """ - Decorator that can be used to mark certain args of an `Expression` subclass as unsupported. - It expects a sequence of argument names or pairs of the form (argument_name, diagnostic_msg). - """ - diagnostic_by_arg: t.Dict[str, t.Optional[str]] = {} - for arg in args: - if isinstance(arg, str): - diagnostic_by_arg[arg] = None - else: - diagnostic_by_arg[arg[0]] = arg[1] - - def decorator(func: GeneratorMethod) -> GeneratorMethod: - @wraps(func) - def _func(generator: G, expression: E) -> str: - expression_name = expression.__class__.__name__ - dialect_name = generator.dialect.__class__.__name__ - - for arg_name, diagnostic in diagnostic_by_arg.items(): - if expression.args.get(arg_name): - diagnostic = diagnostic or UNSUPPORTED_TEMPLATE.format( - arg_name, expression_name, dialect_name - ) - generator.unsupported(diagnostic) - - return func(generator, expression) - - return _func - - return decorator - - -class _Generator(type): - def __new__(cls, clsname, bases, attrs): - klass = super().__new__(cls, clsname, bases, attrs) - - # Remove transforms that correspond to unsupported JSONPathPart expressions - for part in ALL_JSON_PATH_PARTS - klass.SUPPORTED_JSON_PATH_PARTS: - klass.TRANSFORMS.pop(part, None) - - return klass - - -class Generator(metaclass=_Generator): - """ - Generator converts a given syntax tree to the corresponding SQL string. - - Args: - pretty: Whether to format the produced SQL string. - Default: False. - identify: Determines when an identifier should be quoted. Possible values are: - False (default): Never quote, except in cases where it's mandatory by the dialect. - True: Always quote except for specials cases. - 'safe': Only quote identifiers that are case insensitive. - normalize: Whether to normalize identifiers to lowercase. - Default: False. - pad: The pad size in a formatted string. For example, this affects the indentation of - a projection in a query, relative to its nesting level. - Default: 2. - indent: The indentation size in a formatted string. For example, this affects the - indentation of subqueries and filters under a `WHERE` clause. - Default: 2. - normalize_functions: How to normalize function names. Possible values are: - "upper" or True (default): Convert names to uppercase. - "lower": Convert names to lowercase. - False: Disables function name normalization. - unsupported_level: Determines the generator's behavior when it encounters unsupported expressions. - Default ErrorLevel.WARN. - max_unsupported: Maximum number of unsupported messages to include in a raised UnsupportedError. - This is only relevant if unsupported_level is ErrorLevel.RAISE. - Default: 3 - leading_comma: Whether the comma is leading or trailing in select expressions. - This is only relevant when generating in pretty mode. - Default: False - max_text_width: The max number of characters in a segment before creating new lines in pretty mode. - The default is on the smaller end because the length only represents a segment and not the true - line length. - Default: 80 - comments: Whether to preserve comments in the output SQL code. - Default: True - """ - - TRANSFORMS: t.Dict[t.Type[exp.Expression], t.Callable[..., str]] = { - **JSON_PATH_PART_TRANSFORMS, - exp.Adjacent: lambda self, e: self.binary(e, "-|-"), - exp.AllowedValuesProperty: lambda self, - e: f"ALLOWED_VALUES {self.expressions(e, flat=True)}", - exp.AnalyzeColumns: lambda self, e: self.sql(e, "this"), - exp.AnalyzeWith: lambda self, e: self.expressions(e, prefix="WITH ", sep=" "), - exp.ArrayContainsAll: lambda self, e: self.binary(e, "@>"), - exp.ArrayOverlaps: lambda self, e: self.binary(e, "&&"), - exp.AutoRefreshProperty: lambda self, e: f"AUTO REFRESH {self.sql(e, 'this')}", - exp.BackupProperty: lambda self, e: f"BACKUP {self.sql(e, 'this')}", - exp.CaseSpecificColumnConstraint: lambda _, - e: f"{'NOT ' if e.args.get('not_') else ''}CASESPECIFIC", - exp.Ceil: lambda self, e: self.ceil_floor(e), - exp.CharacterSetColumnConstraint: lambda self, - e: f"CHARACTER SET {self.sql(e, 'this')}", - exp.CharacterSetProperty: lambda self, - e: f"{'DEFAULT ' if e.args.get('default') else ''}CHARACTER SET={self.sql(e, 'this')}", - exp.ClusteredColumnConstraint: lambda self, - e: f"CLUSTERED ({self.expressions(e, 'this', indent=False)})", - exp.CollateColumnConstraint: lambda self, e: f"COLLATE {self.sql(e, 'this')}", - exp.CommentColumnConstraint: lambda self, e: f"COMMENT {self.sql(e, 'this')}", - exp.ConnectByRoot: lambda self, e: f"CONNECT_BY_ROOT {self.sql(e, 'this')}", - exp.ConvertToCharset: lambda self, e: self.func( - "CONVERT", e.this, e.args["dest"], e.args.get("source") - ), - exp.CopyGrantsProperty: lambda *_: "COPY GRANTS", - exp.CredentialsProperty: lambda self, - e: f"CREDENTIALS=({self.expressions(e, 'expressions', sep=' ')})", - exp.CurrentCatalog: lambda *_: "CURRENT_CATALOG", - exp.SessionUser: lambda *_: "SESSION_USER", - exp.DateFormatColumnConstraint: lambda self, e: f"FORMAT {self.sql(e, 'this')}", - exp.DefaultColumnConstraint: lambda self, e: f"DEFAULT {self.sql(e, 'this')}", - exp.DynamicProperty: lambda *_: "DYNAMIC", - exp.EmptyProperty: lambda *_: "EMPTY", - exp.EncodeColumnConstraint: lambda self, e: f"ENCODE {self.sql(e, 'this')}", - exp.EnviromentProperty: lambda self, - e: f"ENVIRONMENT ({self.expressions(e, flat=True)})", - exp.EphemeralColumnConstraint: lambda self, - e: f"EPHEMERAL{(' ' + self.sql(e, 'this')) if e.this else ''}", - exp.ExcludeColumnConstraint: lambda self, - e: f"EXCLUDE {self.sql(e, 'this').lstrip()}", - exp.ExecuteAsProperty: lambda self, e: self.naked_property(e), - exp.Except: lambda self, e: self.set_operations(e), - exp.ExternalProperty: lambda *_: "EXTERNAL", - exp.Floor: lambda self, e: self.ceil_floor(e), - exp.Get: lambda self, e: self.get_put_sql(e), - exp.GlobalProperty: lambda *_: "GLOBAL", - exp.HeapProperty: lambda *_: "HEAP", - exp.IcebergProperty: lambda *_: "ICEBERG", - exp.InheritsProperty: lambda self, - e: f"INHERITS ({self.expressions(e, flat=True)})", - exp.InlineLengthColumnConstraint: lambda self, - e: f"INLINE LENGTH {self.sql(e, 'this')}", - exp.InputModelProperty: lambda self, e: f"INPUT{self.sql(e, 'this')}", - exp.Intersect: lambda self, e: self.set_operations(e), - exp.IntervalSpan: lambda self, - e: f"{self.sql(e, 'this')} TO {self.sql(e, 'expression')}", - exp.Int64: lambda self, e: self.sql(exp.cast(e.this, exp.DataType.Type.BIGINT)), - exp.JSONBContainsAnyTopKeys: lambda self, e: self.binary(e, "?|"), - exp.JSONBContainsAllTopKeys: lambda self, e: self.binary(e, "?&"), - exp.JSONBDeleteAtPath: lambda self, e: self.binary(e, "#-"), - exp.LanguageProperty: lambda self, e: self.naked_property(e), - exp.LocationProperty: lambda self, e: self.naked_property(e), - exp.LogProperty: lambda _, e: f"{'NO ' if e.args.get('no') else ''}LOG", - exp.MaterializedProperty: lambda *_: "MATERIALIZED", - exp.NonClusteredColumnConstraint: lambda self, - e: f"NONCLUSTERED ({self.expressions(e, 'this', indent=False)})", - exp.NoPrimaryIndexProperty: lambda *_: "NO PRIMARY INDEX", - exp.NotForReplicationColumnConstraint: lambda *_: "NOT FOR REPLICATION", - exp.OnCommitProperty: lambda _, - e: f"ON COMMIT {'DELETE' if e.args.get('delete') else 'PRESERVE'} ROWS", - exp.OnProperty: lambda self, e: f"ON {self.sql(e, 'this')}", - exp.OnUpdateColumnConstraint: lambda self, - e: f"ON UPDATE {self.sql(e, 'this')}", - exp.Operator: lambda self, e: self.binary( - e, "" - ), # The operator is produced in `binary` - exp.OutputModelProperty: lambda self, e: f"OUTPUT{self.sql(e, 'this')}", - exp.ExtendsLeft: lambda self, e: self.binary(e, "&<"), - exp.ExtendsRight: lambda self, e: self.binary(e, "&>"), - exp.PathColumnConstraint: lambda self, e: f"PATH {self.sql(e, 'this')}", - exp.PartitionedByBucket: lambda self, e: self.func( - "BUCKET", e.this, e.expression - ), - exp.PartitionByTruncate: lambda self, e: self.func( - "TRUNCATE", e.this, e.expression - ), - exp.PivotAny: lambda self, e: f"ANY{self.sql(e, 'this')}", - exp.PositionalColumn: lambda self, e: f"#{self.sql(e, 'this')}", - exp.ProjectionPolicyColumnConstraint: lambda self, - e: f"PROJECTION POLICY {self.sql(e, 'this')}", - exp.ZeroFillColumnConstraint: lambda self, e: "ZEROFILL", - exp.Put: lambda self, e: self.get_put_sql(e), - exp.RemoteWithConnectionModelProperty: lambda self, - e: f"REMOTE WITH CONNECTION {self.sql(e, 'this')}", - exp.ReturnsProperty: lambda self, e: ( - "RETURNS NULL ON NULL INPUT" - if e.args.get("null") - else self.naked_property(e) - ), - exp.SampleProperty: lambda self, e: f"SAMPLE BY {self.sql(e, 'this')}", - exp.SecureProperty: lambda *_: "SECURE", - exp.SecurityProperty: lambda self, e: f"SECURITY {self.sql(e, 'this')}", - exp.SetConfigProperty: lambda self, e: self.sql(e, "this"), - exp.SetProperty: lambda _, e: f"{'MULTI' if e.args.get('multi') else ''}SET", - exp.SettingsProperty: lambda self, - e: f"SETTINGS{self.seg('')}{(self.expressions(e))}", - exp.SharingProperty: lambda self, e: f"SHARING={self.sql(e, 'this')}", - exp.SqlReadWriteProperty: lambda _, e: e.name, - exp.SqlSecurityProperty: lambda self, e: f"SQL SECURITY {self.sql(e, 'this')}", - exp.StabilityProperty: lambda _, e: e.name, - exp.Stream: lambda self, e: f"STREAM {self.sql(e, 'this')}", - exp.StreamingTableProperty: lambda *_: "STREAMING", - exp.StrictProperty: lambda *_: "STRICT", - exp.SwapTable: lambda self, e: f"SWAP WITH {self.sql(e, 'this')}", - exp.TableColumn: lambda self, e: self.sql(e.this), - exp.Tags: lambda self, e: f"TAG ({self.expressions(e, flat=True)})", - exp.TemporaryProperty: lambda *_: "TEMPORARY", - exp.TitleColumnConstraint: lambda self, e: f"TITLE {self.sql(e, 'this')}", - exp.ToMap: lambda self, e: f"MAP {self.sql(e, 'this')}", - exp.ToTableProperty: lambda self, e: f"TO {self.sql(e.this)}", - exp.TransformModelProperty: lambda self, e: self.func( - "TRANSFORM", *e.expressions - ), - exp.TransientProperty: lambda *_: "TRANSIENT", - exp.Union: lambda self, e: self.set_operations(e), - exp.UnloggedProperty: lambda *_: "UNLOGGED", - exp.UsingTemplateProperty: lambda self, - e: f"USING TEMPLATE {self.sql(e, 'this')}", - exp.UsingData: lambda self, e: f"USING DATA {self.sql(e, 'this')}", - exp.UppercaseColumnConstraint: lambda *_: "UPPERCASE", - exp.UtcDate: lambda self, e: self.sql( - exp.CurrentDate(this=exp.Literal.string("UTC")) - ), - exp.UtcTime: lambda self, e: self.sql( - exp.CurrentTime(this=exp.Literal.string("UTC")) - ), - exp.UtcTimestamp: lambda self, e: self.sql( - exp.CurrentTimestamp(this=exp.Literal.string("UTC")) - ), - exp.VarMap: lambda self, e: self.func("MAP", e.args["keys"], e.args["values"]), - exp.ViewAttributeProperty: lambda self, e: f"WITH {self.sql(e, 'this')}", - exp.VolatileProperty: lambda *_: "VOLATILE", - exp.WithJournalTableProperty: lambda self, - e: f"WITH JOURNAL TABLE={self.sql(e, 'this')}", - exp.WithProcedureOptions: lambda self, - e: f"WITH {self.expressions(e, flat=True)}", - exp.WithSchemaBindingProperty: lambda self, - e: f"WITH SCHEMA {self.sql(e, 'this')}", - exp.WithOperator: lambda self, - e: f"{self.sql(e, 'this')} WITH {self.sql(e, 'op')}", - exp.ForceProperty: lambda *_: "FORCE", - } - - # Whether null ordering is supported in order by - # True: Full Support, None: No support, False: No support for certain cases - # such as window specifications, aggregate functions etc - NULL_ORDERING_SUPPORTED: t.Optional[bool] = True - - # Whether ignore nulls is inside the agg or outside. - # FIRST(x IGNORE NULLS) OVER vs FIRST (x) IGNORE NULLS OVER - IGNORE_NULLS_IN_FUNC = False - - # Whether locking reads (i.e. SELECT ... FOR UPDATE/SHARE) are supported - LOCKING_READS_SUPPORTED = False - - # Whether the EXCEPT and INTERSECT operations can return duplicates - EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE = True - - # Wrap derived values in parens, usually standard but spark doesn't support it - WRAP_DERIVED_VALUES = True - - # Whether create function uses an AS before the RETURN - CREATE_FUNCTION_RETURN_AS = True - - # Whether MERGE ... WHEN MATCHED BY SOURCE is allowed - MATCHED_BY_SOURCE = True - - # Whether the INTERVAL expression works only with values like '1 day' - SINGLE_STRING_INTERVAL = False - - # Whether the plural form of date parts like day (i.e. "days") is supported in INTERVALs - INTERVAL_ALLOWS_PLURAL_FORM = True - - # Whether limit and fetch are supported (possible values: "ALL", "LIMIT", "FETCH") - LIMIT_FETCH = "ALL" - - # Whether limit and fetch allows expresions or just limits - LIMIT_ONLY_LITERALS = False - - # Whether a table is allowed to be renamed with a db - RENAME_TABLE_WITH_DB = True - - # The separator for grouping sets and rollups - GROUPINGS_SEP = "," - - # The string used for creating an index on a table - INDEX_ON = "ON" - - # Whether join hints should be generated - JOIN_HINTS = True - - # Whether table hints should be generated - TABLE_HINTS = True - - # Whether query hints should be generated - QUERY_HINTS = True - - # What kind of separator to use for query hints - QUERY_HINT_SEP = ", " - - # Whether comparing against booleans (e.g. x IS TRUE) is supported - IS_BOOL_ALLOWED = True - - # Whether to include the "SET" keyword in the "INSERT ... ON DUPLICATE KEY UPDATE" statement - DUPLICATE_KEY_UPDATE_WITH_SET = True - - # Whether to generate the limit as TOP instead of LIMIT - LIMIT_IS_TOP = False - - # Whether to generate INSERT INTO ... RETURNING or INSERT INTO RETURNING ... - RETURNING_END = True - - # Whether to generate an unquoted value for EXTRACT's date part argument - EXTRACT_ALLOWS_QUOTES = True - - # Whether TIMETZ / TIMESTAMPTZ will be generated using the "WITH TIME ZONE" syntax - TZ_TO_WITH_TIME_ZONE = False - - # Whether the NVL2 function is supported - NVL2_SUPPORTED = True - - # https://cloud.google.com/bigquery/docs/reference/standard-sql/query-syntax - SELECT_KINDS: t.Tuple[str, ...] = ("STRUCT", "VALUE") - - # Whether VALUES statements can be used as derived tables. - # MySQL 5 and Redshift do not allow this, so when False, it will convert - # SELECT * VALUES into SELECT UNION - VALUES_AS_TABLE = True - - # Whether the word COLUMN is included when adding a column with ALTER TABLE - ALTER_TABLE_INCLUDE_COLUMN_KEYWORD = True - - # UNNEST WITH ORDINALITY (presto) instead of UNNEST WITH OFFSET (bigquery) - UNNEST_WITH_ORDINALITY = True - - # Whether FILTER (WHERE cond) can be used for conditional aggregation - AGGREGATE_FILTER_SUPPORTED = True - - # Whether JOIN sides (LEFT, RIGHT) are supported in conjunction with SEMI/ANTI join kinds - SEMI_ANTI_JOIN_WITH_SIDE = True - - # Whether to include the type of a computed column in the CREATE DDL - COMPUTED_COLUMN_WITH_TYPE = True - - # Whether CREATE TABLE .. COPY .. is supported. False means we'll generate CLONE instead of COPY - SUPPORTS_TABLE_COPY = True - - # Whether parentheses are required around the table sample's expression - TABLESAMPLE_REQUIRES_PARENS = True - - # Whether a table sample clause's size needs to be followed by the ROWS keyword - TABLESAMPLE_SIZE_IS_ROWS = True - - # The keyword(s) to use when generating a sample clause - TABLESAMPLE_KEYWORDS = "TABLESAMPLE" - - # Whether the TABLESAMPLE clause supports a method name, like BERNOULLI - TABLESAMPLE_WITH_METHOD = True - - # The keyword to use when specifying the seed of a sample clause - TABLESAMPLE_SEED_KEYWORD = "SEED" - - # Whether COLLATE is a function instead of a binary operator - COLLATE_IS_FUNC = False - - # Whether data types support additional specifiers like e.g. CHAR or BYTE (oracle) - DATA_TYPE_SPECIFIERS_ALLOWED = False - - # Whether conditions require booleans WHERE x = 0 vs WHERE x - ENSURE_BOOLS = False - - # Whether the "RECURSIVE" keyword is required when defining recursive CTEs - CTE_RECURSIVE_KEYWORD_REQUIRED = True - - # Whether CONCAT requires >1 arguments - SUPPORTS_SINGLE_ARG_CONCAT = True - - # Whether LAST_DAY function supports a date part argument - LAST_DAY_SUPPORTS_DATE_PART = True - - # Whether named columns are allowed in table aliases - SUPPORTS_TABLE_ALIAS_COLUMNS = True - - # Whether UNPIVOT aliases are Identifiers (False means they're Literals) - UNPIVOT_ALIASES_ARE_IDENTIFIERS = True - - # What delimiter to use for separating JSON key/value pairs - JSON_KEY_VALUE_PAIR_SEP = ":" - - # INSERT OVERWRITE TABLE x override - INSERT_OVERWRITE = " OVERWRITE TABLE" - - # Whether the SELECT .. INTO syntax is used instead of CTAS - SUPPORTS_SELECT_INTO = False - - # Whether UNLOGGED tables can be created - SUPPORTS_UNLOGGED_TABLES = False - - # Whether the CREATE TABLE LIKE statement is supported - SUPPORTS_CREATE_TABLE_LIKE = True - - # Whether the LikeProperty needs to be specified inside of the schema clause - LIKE_PROPERTY_INSIDE_SCHEMA = False - - # Whether DISTINCT can be followed by multiple args in an AggFunc. If not, it will be - # transpiled into a series of CASE-WHEN-ELSE, ultimately using a tuple conseisting of the args - MULTI_ARG_DISTINCT = True - - # Whether the JSON extraction operators expect a value of type JSON - JSON_TYPE_REQUIRED_FOR_EXTRACTION = False - - # Whether bracketed keys like ["foo"] are supported in JSON paths - JSON_PATH_BRACKETED_KEY_SUPPORTED = True - - # Whether to escape keys using single quotes in JSON paths - JSON_PATH_SINGLE_QUOTE_ESCAPE = False - - # The JSONPathPart expressions supported by this dialect - SUPPORTED_JSON_PATH_PARTS = ALL_JSON_PATH_PARTS.copy() - - # Whether any(f(x) for x in array) can be implemented by this dialect - CAN_IMPLEMENT_ARRAY_ANY = False - - # Whether the function TO_NUMBER is supported - SUPPORTS_TO_NUMBER = True - - # Whether EXCLUDE in window specification is supported - SUPPORTS_WINDOW_EXCLUDE = False - - # Whether or not set op modifiers apply to the outer set op or select. - # SELECT * FROM x UNION SELECT * FROM y LIMIT 1 - # True means limit 1 happens after the set op, False means it it happens on y. - SET_OP_MODIFIERS = True - - # Whether parameters from COPY statement are wrapped in parentheses - COPY_PARAMS_ARE_WRAPPED = True - - # Whether values of params are set with "=" token or empty space - COPY_PARAMS_EQ_REQUIRED = False - - # Whether COPY statement has INTO keyword - COPY_HAS_INTO_KEYWORD = True - - # Whether the conditional TRY(expression) function is supported - TRY_SUPPORTED = True - - # Whether the UESCAPE syntax in unicode strings is supported - SUPPORTS_UESCAPE = True - - # Function used to replace escaped unicode codes in unicode strings - UNICODE_SUBSTITUTE: t.Optional[t.Callable[[re.Match[str]], str]] = None - - # The keyword to use when generating a star projection with excluded columns - STAR_EXCEPT = "EXCEPT" - - # The HEX function name - HEX_FUNC = "HEX" - - # The keywords to use when prefixing & separating WITH based properties - WITH_PROPERTIES_PREFIX = "WITH" - - # Whether to quote the generated expression of exp.JsonPath - QUOTE_JSON_PATH = True - - # Whether the text pattern/fill (3rd) parameter of RPAD()/LPAD() is optional (defaults to space) - PAD_FILL_PATTERN_IS_REQUIRED = False - - # Whether a projection can explode into multiple rows, e.g. by unnesting an array. - SUPPORTS_EXPLODING_PROJECTIONS = True - - # Whether ARRAY_CONCAT can be generated with varlen args or if it should be reduced to 2-arg version - ARRAY_CONCAT_IS_VAR_LEN = True - - # Whether CONVERT_TIMEZONE() is supported; if not, it will be generated as exp.AtTimeZone - SUPPORTS_CONVERT_TIMEZONE = False - - # Whether MEDIAN(expr) is supported; if not, it will be generated as PERCENTILE_CONT(expr, 0.5) - SUPPORTS_MEDIAN = True - - # Whether UNIX_SECONDS(timestamp) is supported - SUPPORTS_UNIX_SECONDS = False - - # Whether to wrap in `AlterSet`, e.g., ALTER ... SET () - ALTER_SET_WRAPPED = False - - # Whether to normalize the date parts in EXTRACT( FROM ) into a common representation - # For instance, to extract the day of week in ISO semantics, one can use ISODOW, DAYOFWEEKISO etc depending on the dialect. - # TODO: The normalization should be done by default once we've tested it across all dialects. - NORMALIZE_EXTRACT_DATE_PARTS = False - - # The name to generate for the JSONPath expression. If `None`, only `this` will be generated - PARSE_JSON_NAME: t.Optional[str] = "PARSE_JSON" - - # The function name of the exp.ArraySize expression - ARRAY_SIZE_NAME: str = "ARRAY_LENGTH" - - # The syntax to use when altering the type of a column - ALTER_SET_TYPE = "SET DATA TYPE" - - # Whether exp.ArraySize should generate the dimension arg too (valid for Postgres & DuckDB) - # None -> Doesn't support it at all - # False (DuckDB) -> Has backwards-compatible support, but preferably generated without - # True (Postgres) -> Explicitly requires it - ARRAY_SIZE_DIM_REQUIRED: t.Optional[bool] = None - - # Whether a multi-argument DECODE(...) function is supported. If not, a CASE expression is generated - SUPPORTS_DECODE_CASE = True - - # Whether SYMMETRIC and ASYMMETRIC flags are supported with BETWEEN expression - SUPPORTS_BETWEEN_FLAGS = False - - # Whether LIKE and ILIKE support quantifiers such as LIKE ANY/ALL/SOME - SUPPORTS_LIKE_QUANTIFIERS = True - - # Prefix which is appended to exp.Table expressions in MATCH AGAINST - MATCH_AGAINST_TABLE_PREFIX: t.Optional[str] = None - - # Whether to include the VARIABLE keyword for SET assignments - SET_ASSIGNMENT_REQUIRES_VARIABLE_KEYWORD = False - - TYPE_MAPPING = { - exp.DataType.Type.DATETIME2: "TIMESTAMP", - exp.DataType.Type.NCHAR: "CHAR", - exp.DataType.Type.NVARCHAR: "VARCHAR", - exp.DataType.Type.MEDIUMTEXT: "TEXT", - exp.DataType.Type.LONGTEXT: "TEXT", - exp.DataType.Type.TINYTEXT: "TEXT", - exp.DataType.Type.BLOB: "VARBINARY", - exp.DataType.Type.MEDIUMBLOB: "BLOB", - exp.DataType.Type.LONGBLOB: "BLOB", - exp.DataType.Type.TINYBLOB: "BLOB", - exp.DataType.Type.INET: "INET", - exp.DataType.Type.ROWVERSION: "VARBINARY", - exp.DataType.Type.SMALLDATETIME: "TIMESTAMP", - } - - UNSUPPORTED_TYPES: set[exp.DataType.Type] = set() - - TIME_PART_SINGULARS = { - "MICROSECONDS": "MICROSECOND", - "SECONDS": "SECOND", - "MINUTES": "MINUTE", - "HOURS": "HOUR", - "DAYS": "DAY", - "WEEKS": "WEEK", - "MONTHS": "MONTH", - "QUARTERS": "QUARTER", - "YEARS": "YEAR", - } - - AFTER_HAVING_MODIFIER_TRANSFORMS = { - "cluster": lambda self, e: self.sql(e, "cluster"), - "distribute": lambda self, e: self.sql(e, "distribute"), - "sort": lambda self, e: self.sql(e, "sort"), - "windows": lambda self, e: ( - self.seg("WINDOW ") + self.expressions(e, key="windows", flat=True) - if e.args.get("windows") - else "" - ), - "qualify": lambda self, e: self.sql(e, "qualify"), - } - - TOKEN_MAPPING: t.Dict[TokenType, str] = {} - - STRUCT_DELIMITER = ("<", ">") - - PARAMETER_TOKEN = "@" - NAMED_PLACEHOLDER_TOKEN = ":" - - EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: t.Set[str] = set() - - PROPERTIES_LOCATION = { - exp.AllowedValuesProperty: exp.Properties.Location.POST_SCHEMA, - exp.AlgorithmProperty: exp.Properties.Location.POST_CREATE, - exp.AutoIncrementProperty: exp.Properties.Location.POST_SCHEMA, - exp.AutoRefreshProperty: exp.Properties.Location.POST_SCHEMA, - exp.BackupProperty: exp.Properties.Location.POST_SCHEMA, - exp.BlockCompressionProperty: exp.Properties.Location.POST_NAME, - exp.CharacterSetProperty: exp.Properties.Location.POST_SCHEMA, - exp.ChecksumProperty: exp.Properties.Location.POST_NAME, - exp.CollateProperty: exp.Properties.Location.POST_SCHEMA, - exp.CopyGrantsProperty: exp.Properties.Location.POST_SCHEMA, - exp.Cluster: exp.Properties.Location.POST_SCHEMA, - exp.ClusteredByProperty: exp.Properties.Location.POST_SCHEMA, - exp.DistributedByProperty: exp.Properties.Location.POST_SCHEMA, - exp.DuplicateKeyProperty: exp.Properties.Location.POST_SCHEMA, - exp.DataBlocksizeProperty: exp.Properties.Location.POST_NAME, - exp.DataDeletionProperty: exp.Properties.Location.POST_SCHEMA, - exp.DefinerProperty: exp.Properties.Location.POST_CREATE, - exp.DictRange: exp.Properties.Location.POST_SCHEMA, - exp.DictProperty: exp.Properties.Location.POST_SCHEMA, - exp.DynamicProperty: exp.Properties.Location.POST_CREATE, - exp.DistKeyProperty: exp.Properties.Location.POST_SCHEMA, - exp.DistStyleProperty: exp.Properties.Location.POST_SCHEMA, - exp.EmptyProperty: exp.Properties.Location.POST_SCHEMA, - exp.EncodeProperty: exp.Properties.Location.POST_EXPRESSION, - exp.EngineProperty: exp.Properties.Location.POST_SCHEMA, - exp.EnviromentProperty: exp.Properties.Location.POST_SCHEMA, - exp.ExecuteAsProperty: exp.Properties.Location.POST_SCHEMA, - exp.ExternalProperty: exp.Properties.Location.POST_CREATE, - exp.FallbackProperty: exp.Properties.Location.POST_NAME, - exp.FileFormatProperty: exp.Properties.Location.POST_WITH, - exp.FreespaceProperty: exp.Properties.Location.POST_NAME, - exp.GlobalProperty: exp.Properties.Location.POST_CREATE, - exp.HeapProperty: exp.Properties.Location.POST_WITH, - exp.InheritsProperty: exp.Properties.Location.POST_SCHEMA, - exp.IcebergProperty: exp.Properties.Location.POST_CREATE, - exp.IncludeProperty: exp.Properties.Location.POST_SCHEMA, - exp.InputModelProperty: exp.Properties.Location.POST_SCHEMA, - exp.IsolatedLoadingProperty: exp.Properties.Location.POST_NAME, - exp.JournalProperty: exp.Properties.Location.POST_NAME, - exp.LanguageProperty: exp.Properties.Location.POST_SCHEMA, - exp.LikeProperty: exp.Properties.Location.POST_SCHEMA, - exp.LocationProperty: exp.Properties.Location.POST_SCHEMA, - exp.LockProperty: exp.Properties.Location.POST_SCHEMA, - exp.LockingProperty: exp.Properties.Location.POST_ALIAS, - exp.LogProperty: exp.Properties.Location.POST_NAME, - exp.MaterializedProperty: exp.Properties.Location.POST_CREATE, - exp.MergeBlockRatioProperty: exp.Properties.Location.POST_NAME, - exp.NoPrimaryIndexProperty: exp.Properties.Location.POST_EXPRESSION, - exp.OnProperty: exp.Properties.Location.POST_SCHEMA, - exp.OnCommitProperty: exp.Properties.Location.POST_EXPRESSION, - exp.Order: exp.Properties.Location.POST_SCHEMA, - exp.OutputModelProperty: exp.Properties.Location.POST_SCHEMA, - exp.PartitionedByProperty: exp.Properties.Location.POST_WITH, - exp.PartitionedOfProperty: exp.Properties.Location.POST_SCHEMA, - exp.PrimaryKey: exp.Properties.Location.POST_SCHEMA, - exp.Property: exp.Properties.Location.POST_WITH, - exp.RemoteWithConnectionModelProperty: exp.Properties.Location.POST_SCHEMA, - exp.ReturnsProperty: exp.Properties.Location.POST_SCHEMA, - exp.RowFormatProperty: exp.Properties.Location.POST_SCHEMA, - exp.RowFormatDelimitedProperty: exp.Properties.Location.POST_SCHEMA, - exp.RowFormatSerdeProperty: exp.Properties.Location.POST_SCHEMA, - exp.SampleProperty: exp.Properties.Location.POST_SCHEMA, - exp.SchemaCommentProperty: exp.Properties.Location.POST_SCHEMA, - exp.SecureProperty: exp.Properties.Location.POST_CREATE, - exp.SecurityProperty: exp.Properties.Location.POST_SCHEMA, - exp.SerdeProperties: exp.Properties.Location.POST_SCHEMA, - exp.Set: exp.Properties.Location.POST_SCHEMA, - exp.SettingsProperty: exp.Properties.Location.POST_SCHEMA, - exp.SetProperty: exp.Properties.Location.POST_CREATE, - exp.SetConfigProperty: exp.Properties.Location.POST_SCHEMA, - exp.SharingProperty: exp.Properties.Location.POST_EXPRESSION, - exp.SequenceProperties: exp.Properties.Location.POST_EXPRESSION, - exp.SortKeyProperty: exp.Properties.Location.POST_SCHEMA, - exp.SqlReadWriteProperty: exp.Properties.Location.POST_SCHEMA, - exp.SqlSecurityProperty: exp.Properties.Location.POST_CREATE, - exp.StabilityProperty: exp.Properties.Location.POST_SCHEMA, - exp.StorageHandlerProperty: exp.Properties.Location.POST_SCHEMA, - exp.StreamingTableProperty: exp.Properties.Location.POST_CREATE, - exp.StrictProperty: exp.Properties.Location.POST_SCHEMA, - exp.Tags: exp.Properties.Location.POST_WITH, - exp.TemporaryProperty: exp.Properties.Location.POST_CREATE, - exp.ToTableProperty: exp.Properties.Location.POST_SCHEMA, - exp.TransientProperty: exp.Properties.Location.POST_CREATE, - exp.TransformModelProperty: exp.Properties.Location.POST_SCHEMA, - exp.MergeTreeTTL: exp.Properties.Location.POST_SCHEMA, - exp.UnloggedProperty: exp.Properties.Location.POST_CREATE, - exp.UsingTemplateProperty: exp.Properties.Location.POST_SCHEMA, - exp.ViewAttributeProperty: exp.Properties.Location.POST_SCHEMA, - exp.VolatileProperty: exp.Properties.Location.POST_CREATE, - exp.WithDataProperty: exp.Properties.Location.POST_EXPRESSION, - exp.WithJournalTableProperty: exp.Properties.Location.POST_NAME, - exp.WithProcedureOptions: exp.Properties.Location.POST_SCHEMA, - exp.WithSchemaBindingProperty: exp.Properties.Location.POST_SCHEMA, - exp.WithSystemVersioningProperty: exp.Properties.Location.POST_SCHEMA, - exp.ForceProperty: exp.Properties.Location.POST_CREATE, - } - - # Keywords that can't be used as unquoted identifier names - RESERVED_KEYWORDS: t.Set[str] = set() - - # Expressions whose comments are separated from them for better formatting - WITH_SEPARATED_COMMENTS: t.Tuple[t.Type[exp.Expression], ...] = ( - exp.Command, - exp.Create, - exp.Describe, - exp.Delete, - exp.Drop, - exp.From, - exp.Insert, - exp.Join, - exp.MultitableInserts, - exp.Order, - exp.Group, - exp.Having, - exp.Select, - exp.SetOperation, - exp.Update, - exp.Where, - exp.With, - ) - - # Expressions that should not have their comments generated in maybe_comment - EXCLUDE_COMMENTS: t.Tuple[t.Type[exp.Expression], ...] = ( - exp.Binary, - exp.SetOperation, - ) - - # Expressions that can remain unwrapped when appearing in the context of an INTERVAL - UNWRAPPED_INTERVAL_VALUES: t.Tuple[t.Type[exp.Expression], ...] = ( - exp.Column, - exp.Literal, - exp.Neg, - exp.Paren, - ) - - PARAMETERIZABLE_TEXT_TYPES = { - exp.DataType.Type.NVARCHAR, - exp.DataType.Type.VARCHAR, - exp.DataType.Type.CHAR, - exp.DataType.Type.NCHAR, - } - - # Expressions that need to have all CTEs under them bubbled up to them - EXPRESSIONS_WITHOUT_NESTED_CTES: t.Set[t.Type[exp.Expression]] = set() - - RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS: t.Tuple[ - t.Type[exp.Expression], ... - ] = () - - SAFE_JSON_PATH_KEY_RE = exp.SAFE_IDENTIFIER_RE - - SENTINEL_LINE_BREAK = "__SQLGLOT__LB__" - - __slots__ = ( - "pretty", - "identify", - "normalize", - "pad", - "_indent", - "normalize_functions", - "unsupported_level", - "max_unsupported", - "leading_comma", - "max_text_width", - "comments", - "dialect", - "unsupported_messages", - "_escaped_quote_end", - "_escaped_byte_quote_end", - "_escaped_identifier_end", - "_next_name", - "_identifier_start", - "_identifier_end", - "_quote_json_path_key_using_brackets", - ) - - def __init__( - self, - pretty: t.Optional[bool] = None, - identify: str | bool = False, - normalize: bool = False, - pad: int = 2, - indent: int = 2, - normalize_functions: t.Optional[str | bool] = None, - unsupported_level: ErrorLevel = ErrorLevel.WARN, - max_unsupported: int = 3, - leading_comma: bool = False, - max_text_width: int = 80, - comments: bool = True, - dialect: DialectType = None, - ): - import bigframes_vendored.sqlglot - from bigframes_vendored.sqlglot.dialects import Dialect - - self.pretty = ( - pretty if pretty is not None else bigframes_vendored.sqlglot.pretty - ) - self.identify = identify - self.normalize = normalize - self.pad = pad - self._indent = indent - self.unsupported_level = unsupported_level - self.max_unsupported = max_unsupported - self.leading_comma = leading_comma - self.max_text_width = max_text_width - self.comments = comments - self.dialect = Dialect.get_or_raise(dialect) - - # This is both a Dialect property and a Generator argument, so we prioritize the latter - self.normalize_functions = ( - self.dialect.NORMALIZE_FUNCTIONS - if normalize_functions is None - else normalize_functions - ) - - self.unsupported_messages: t.List[str] = [] - self._escaped_quote_end: str = ( - self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.QUOTE_END - ) - self._escaped_byte_quote_end: str = ( - self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.BYTE_END - if self.dialect.BYTE_END - else "" - ) - self._escaped_identifier_end = self.dialect.IDENTIFIER_END * 2 - - self._next_name = name_sequence("_t") - - self._identifier_start = self.dialect.IDENTIFIER_START - self._identifier_end = self.dialect.IDENTIFIER_END - - self._quote_json_path_key_using_brackets = True - - def generate(self, expression: exp.Expression, copy: bool = True) -> str: - """ - Generates the SQL string corresponding to the given syntax tree. - - Args: - expression: The syntax tree. - copy: Whether to copy the expression. The generator performs mutations so - it is safer to copy. - - Returns: - The SQL string corresponding to `expression`. - """ - if copy: - expression = expression.copy() - - expression = self.preprocess(expression) - - self.unsupported_messages = [] - sql = self.sql(expression).strip() - - if self.pretty: - sql = sql.replace(self.SENTINEL_LINE_BREAK, "\n") - - if self.unsupported_level == ErrorLevel.IGNORE: - return sql - - if self.unsupported_level == ErrorLevel.WARN: - for msg in self.unsupported_messages: - logger.warning(msg) - elif self.unsupported_level == ErrorLevel.RAISE and self.unsupported_messages: - raise UnsupportedError( - concat_messages(self.unsupported_messages, self.max_unsupported) - ) - - return sql - - def preprocess(self, expression: exp.Expression) -> exp.Expression: - """Apply generic preprocessing transformations to a given expression.""" - expression = self._move_ctes_to_top_level(expression) - - if self.ENSURE_BOOLS: - from bigframes_vendored.sqlglot.transforms import ensure_bools - - expression = ensure_bools(expression) - - return expression - - def _move_ctes_to_top_level(self, expression: E) -> E: - if ( - not expression.parent - and type(expression) in self.EXPRESSIONS_WITHOUT_NESTED_CTES - and any( - node.parent is not expression for node in expression.find_all(exp.With) - ) - ): - from bigframes_vendored.sqlglot.transforms import move_ctes_to_top_level - - expression = move_ctes_to_top_level(expression) - return expression - - def unsupported(self, message: str) -> None: - if self.unsupported_level == ErrorLevel.IMMEDIATE: - raise UnsupportedError(message) - self.unsupported_messages.append(message) - - def sep(self, sep: str = " ") -> str: - return f"{sep.strip()}\n" if self.pretty else sep - - def seg(self, sql: str, sep: str = " ") -> str: - return f"{self.sep(sep)}{sql}" - - def sanitize_comment(self, comment: str) -> str: - comment = " " + comment if comment[0].strip() else comment - comment = comment + " " if comment[-1].strip() else comment - - if not self.dialect.tokenizer_class.NESTED_COMMENTS: - # Necessary workaround to avoid syntax errors due to nesting: /* ... */ ... */ - comment = comment.replace("*/", "* /") - - return comment - - def maybe_comment( - self, - sql: str, - expression: t.Optional[exp.Expression] = None, - comments: t.Optional[t.List[str]] = None, - separated: bool = False, - ) -> str: - comments = ( - ((expression and expression.comments) if comments is None else comments) # type: ignore - if self.comments - else None - ) - - if not comments or isinstance(expression, self.EXCLUDE_COMMENTS): - return sql - - comments_sql = " ".join( - f"/*{self.sanitize_comment(comment)}*/" for comment in comments if comment - ) - - if not comments_sql: - return sql - - comments_sql = self._replace_line_breaks(comments_sql) - - if separated or isinstance(expression, self.WITH_SEPARATED_COMMENTS): - return ( - f"{self.sep()}{comments_sql}{sql}" - if not sql or sql[0].isspace() - else f"{comments_sql}{self.sep()}{sql}" - ) - - return f"{sql} {comments_sql}" - - def wrap(self, expression: exp.Expression | str) -> str: - this_sql = ( - self.sql(expression) - if isinstance(expression, exp.UNWRAPPED_QUERIES) - else self.sql(expression, "this") - ) - if not this_sql: - return "()" - - this_sql = self.indent(this_sql, level=1, pad=0) - return f"({self.sep('')}{this_sql}{self.seg(')', sep='')}" - - def no_identify(self, func: t.Callable[..., str], *args, **kwargs) -> str: - original = self.identify - self.identify = False - result = func(*args, **kwargs) - self.identify = original - return result - - def normalize_func(self, name: str) -> str: - if self.normalize_functions == "upper" or self.normalize_functions is True: - return name.upper() - if self.normalize_functions == "lower": - return name.lower() - return name - - def indent( - self, - sql: str, - level: int = 0, - pad: t.Optional[int] = None, - skip_first: bool = False, - skip_last: bool = False, - ) -> str: - if not self.pretty or not sql: - return sql - - pad = self.pad if pad is None else pad - lines = sql.split("\n") - - return "\n".join( - ( - line - if (skip_first and i == 0) or (skip_last and i == len(lines) - 1) - else f"{' ' * (level * self._indent + pad)}{line}" - ) - for i, line in enumerate(lines) - ) - - def sql( - self, - expression: t.Optional[str | exp.Expression], - key: t.Optional[str] = None, - comment: bool = True, - ) -> str: - if not expression: - return "" - - if isinstance(expression, str): - return expression - - if key: - value = expression.args.get(key) - if value: - return self.sql(value) - return "" - - transform = self.TRANSFORMS.get(expression.__class__) - - if callable(transform): - sql = transform(self, expression) - elif isinstance(expression, exp.Expression): - exp_handler_name = f"{expression.key}_sql" - - if hasattr(self, exp_handler_name): - sql = getattr(self, exp_handler_name)(expression) - elif isinstance(expression, exp.Func): - sql = self.function_fallback_sql(expression) - elif isinstance(expression, exp.Property): - sql = self.property_sql(expression) - else: - raise ValueError( - f"Unsupported expression type {expression.__class__.__name__}" - ) - else: - raise ValueError( - f"Expected an Expression. Received {type(expression)}: {expression}" - ) - - return self.maybe_comment(sql, expression) if self.comments and comment else sql - - def uncache_sql(self, expression: exp.Uncache) -> str: - table = self.sql(expression, "this") - exists_sql = " IF EXISTS" if expression.args.get("exists") else "" - return f"UNCACHE TABLE{exists_sql} {table}" - - def cache_sql(self, expression: exp.Cache) -> str: - lazy = " LAZY" if expression.args.get("lazy") else "" - table = self.sql(expression, "this") - options = expression.args.get("options") - options = ( - f" OPTIONS({self.sql(options[0])} = {self.sql(options[1])})" - if options - else "" - ) - sql = self.sql(expression, "expression") - sql = f" AS{self.sep()}{sql}" if sql else "" - sql = f"CACHE{lazy} TABLE {table}{options}{sql}" - return self.prepend_ctes(expression, sql) - - def characterset_sql(self, expression: exp.CharacterSet) -> str: - if isinstance(expression.parent, exp.Cast): - return f"CHAR CHARACTER SET {self.sql(expression, 'this')}" - default = "DEFAULT " if expression.args.get("default") else "" - return f"{default}CHARACTER SET={self.sql(expression, 'this')}" - - def column_parts(self, expression: exp.Column) -> str: - return ".".join( - self.sql(part) - for part in ( - expression.args.get("catalog"), - expression.args.get("db"), - expression.args.get("table"), - expression.args.get("this"), - ) - if part - ) - - def column_sql(self, expression: exp.Column) -> str: - join_mark = " (+)" if expression.args.get("join_mark") else "" - - if join_mark and not self.dialect.SUPPORTS_COLUMN_JOIN_MARKS: - join_mark = "" - self.unsupported( - "Outer join syntax using the (+) operator is not supported." - ) - - return f"{self.column_parts(expression)}{join_mark}" - - def pseudocolumn_sql(self, expression: exp.Pseudocolumn) -> str: - return self.column_sql(expression) - - def columnposition_sql(self, expression: exp.ColumnPosition) -> str: - this = self.sql(expression, "this") - this = f" {this}" if this else "" - position = self.sql(expression, "position") - return f"{position}{this}" - - def columndef_sql(self, expression: exp.ColumnDef, sep: str = " ") -> str: - column = self.sql(expression, "this") - kind = self.sql(expression, "kind") - constraints = self.expressions( - expression, key="constraints", sep=" ", flat=True - ) - exists = "IF NOT EXISTS " if expression.args.get("exists") else "" - kind = f"{sep}{kind}" if kind else "" - constraints = f" {constraints}" if constraints else "" - position = self.sql(expression, "position") - position = f" {position}" if position else "" - - if ( - expression.find(exp.ComputedColumnConstraint) - and not self.COMPUTED_COLUMN_WITH_TYPE - ): - kind = "" - - return f"{exists}{column}{kind}{constraints}{position}" - - def columnconstraint_sql(self, expression: exp.ColumnConstraint) -> str: - this = self.sql(expression, "this") - kind_sql = self.sql(expression, "kind").strip() - return f"CONSTRAINT {this} {kind_sql}" if this else kind_sql - - def computedcolumnconstraint_sql( - self, expression: exp.ComputedColumnConstraint - ) -> str: - this = self.sql(expression, "this") - if expression.args.get("not_null"): - persisted = " PERSISTED NOT NULL" - elif expression.args.get("persisted"): - persisted = " PERSISTED" - else: - persisted = "" - - return f"AS {this}{persisted}" - - def autoincrementcolumnconstraint_sql(self, _) -> str: - return self.token_sql(TokenType.AUTO_INCREMENT) - - def compresscolumnconstraint_sql( - self, expression: exp.CompressColumnConstraint - ) -> str: - if isinstance(expression.this, list): - this = self.wrap(self.expressions(expression, key="this", flat=True)) - else: - this = self.sql(expression, "this") - - return f"COMPRESS {this}" - - def generatedasidentitycolumnconstraint_sql( - self, expression: exp.GeneratedAsIdentityColumnConstraint - ) -> str: - this = "" - if expression.this is not None: - on_null = " ON NULL" if expression.args.get("on_null") else "" - this = " ALWAYS" if expression.this else f" BY DEFAULT{on_null}" - - start = expression.args.get("start") - start = f"START WITH {start}" if start else "" - increment = expression.args.get("increment") - increment = f" INCREMENT BY {increment}" if increment else "" - minvalue = expression.args.get("minvalue") - minvalue = f" MINVALUE {minvalue}" if minvalue else "" - maxvalue = expression.args.get("maxvalue") - maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" - cycle = expression.args.get("cycle") - cycle_sql = "" - - if cycle is not None: - cycle_sql = f"{' NO' if not cycle else ''} CYCLE" - cycle_sql = cycle_sql.strip() if not start and not increment else cycle_sql - - sequence_opts = "" - if start or increment or cycle_sql: - sequence_opts = f"{start}{increment}{minvalue}{maxvalue}{cycle_sql}" - sequence_opts = f" ({sequence_opts.strip()})" - - expr = self.sql(expression, "expression") - expr = f"({expr})" if expr else "IDENTITY" - - return f"GENERATED{this} AS {expr}{sequence_opts}" - - def generatedasrowcolumnconstraint_sql( - self, expression: exp.GeneratedAsRowColumnConstraint - ) -> str: - start = "START" if expression.args.get("start") else "END" - hidden = " HIDDEN" if expression.args.get("hidden") else "" - return f"GENERATED ALWAYS AS ROW {start}{hidden}" - - def periodforsystemtimeconstraint_sql( - self, expression: exp.PeriodForSystemTimeConstraint - ) -> str: - return f"PERIOD FOR SYSTEM_TIME ({self.sql(expression, 'this')}, {self.sql(expression, 'expression')})" - - def notnullcolumnconstraint_sql( - self, expression: exp.NotNullColumnConstraint - ) -> str: - return f"{'' if expression.args.get('allow_null') else 'NOT '}NULL" - - def primarykeycolumnconstraint_sql( - self, expression: exp.PrimaryKeyColumnConstraint - ) -> str: - desc = expression.args.get("desc") - if desc is not None: - return f"PRIMARY KEY{' DESC' if desc else ' ASC'}" - options = self.expressions(expression, key="options", flat=True, sep=" ") - options = f" {options}" if options else "" - return f"PRIMARY KEY{options}" - - def uniquecolumnconstraint_sql(self, expression: exp.UniqueColumnConstraint) -> str: - this = self.sql(expression, "this") - this = f" {this}" if this else "" - index_type = expression.args.get("index_type") - index_type = f" USING {index_type}" if index_type else "" - on_conflict = self.sql(expression, "on_conflict") - on_conflict = f" {on_conflict}" if on_conflict else "" - nulls_sql = " NULLS NOT DISTINCT" if expression.args.get("nulls") else "" - options = self.expressions(expression, key="options", flat=True, sep=" ") - options = f" {options}" if options else "" - return f"UNIQUE{nulls_sql}{this}{index_type}{on_conflict}{options}" - - def createable_sql(self, expression: exp.Create, locations: t.DefaultDict) -> str: - return self.sql(expression, "this") - - def create_sql(self, expression: exp.Create) -> str: - kind = self.sql(expression, "kind") - kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind - properties = expression.args.get("properties") - properties_locs = ( - self.locate_properties(properties) if properties else defaultdict() - ) - - this = self.createable_sql(expression, properties_locs) - - properties_sql = "" - if properties_locs.get( - exp.Properties.Location.POST_SCHEMA - ) or properties_locs.get(exp.Properties.Location.POST_WITH): - props_ast = exp.Properties( - expressions=[ - *properties_locs[exp.Properties.Location.POST_SCHEMA], - *properties_locs[exp.Properties.Location.POST_WITH], - ] - ) - props_ast.parent = expression - properties_sql = self.sql(props_ast) - - if properties_locs.get(exp.Properties.Location.POST_SCHEMA): - properties_sql = self.sep() + properties_sql - elif not self.pretty: - # Standalone POST_WITH properties need a leading whitespace in non-pretty mode - properties_sql = f" {properties_sql}" - - begin = " BEGIN" if expression.args.get("begin") else "" - end = " END" if expression.args.get("end") else "" - - expression_sql = self.sql(expression, "expression") - if expression_sql: - expression_sql = f"{begin}{self.sep()}{expression_sql}{end}" - - if self.CREATE_FUNCTION_RETURN_AS or not isinstance( - expression.expression, exp.Return - ): - postalias_props_sql = "" - if properties_locs.get(exp.Properties.Location.POST_ALIAS): - postalias_props_sql = self.properties( - exp.Properties( - expressions=properties_locs[ - exp.Properties.Location.POST_ALIAS - ] - ), - wrapped=False, - ) - postalias_props_sql = ( - f" {postalias_props_sql}" if postalias_props_sql else "" - ) - expression_sql = f" AS{postalias_props_sql}{expression_sql}" - - postindex_props_sql = "" - if properties_locs.get(exp.Properties.Location.POST_INDEX): - postindex_props_sql = self.properties( - exp.Properties( - expressions=properties_locs[exp.Properties.Location.POST_INDEX] - ), - wrapped=False, - prefix=" ", - ) - - indexes = self.expressions(expression, key="indexes", indent=False, sep=" ") - indexes = f" {indexes}" if indexes else "" - index_sql = indexes + postindex_props_sql - - replace = " OR REPLACE" if expression.args.get("replace") else "" - refresh = " OR REFRESH" if expression.args.get("refresh") else "" - unique = " UNIQUE" if expression.args.get("unique") else "" - - clustered = expression.args.get("clustered") - if clustered is None: - clustered_sql = "" - elif clustered: - clustered_sql = " CLUSTERED COLUMNSTORE" - else: - clustered_sql = " NONCLUSTERED COLUMNSTORE" - - postcreate_props_sql = "" - if properties_locs.get(exp.Properties.Location.POST_CREATE): - postcreate_props_sql = self.properties( - exp.Properties( - expressions=properties_locs[exp.Properties.Location.POST_CREATE] - ), - sep=" ", - prefix=" ", - wrapped=False, - ) - - modifiers = "".join( - (clustered_sql, replace, refresh, unique, postcreate_props_sql) - ) - - postexpression_props_sql = "" - if properties_locs.get(exp.Properties.Location.POST_EXPRESSION): - postexpression_props_sql = self.properties( - exp.Properties( - expressions=properties_locs[exp.Properties.Location.POST_EXPRESSION] - ), - sep=" ", - prefix=" ", - wrapped=False, - ) - - concurrently = " CONCURRENTLY" if expression.args.get("concurrently") else "" - exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" - no_schema_binding = ( - " WITH NO SCHEMA BINDING" - if expression.args.get("no_schema_binding") - else "" - ) - - clone = self.sql(expression, "clone") - clone = f" {clone}" if clone else "" - - if kind in self.EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: - properties_expression = f"{expression_sql}{properties_sql}" - else: - properties_expression = f"{properties_sql}{expression_sql}" - - expression_sql = f"CREATE{modifiers} {kind}{concurrently}{exists_sql} {this}{properties_expression}{postexpression_props_sql}{index_sql}{no_schema_binding}{clone}" - return self.prepend_ctes(expression, expression_sql) - - def sequenceproperties_sql(self, expression: exp.SequenceProperties) -> str: - start = self.sql(expression, "start") - start = f"START WITH {start}" if start else "" - increment = self.sql(expression, "increment") - increment = f" INCREMENT BY {increment}" if increment else "" - minvalue = self.sql(expression, "minvalue") - minvalue = f" MINVALUE {minvalue}" if minvalue else "" - maxvalue = self.sql(expression, "maxvalue") - maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" - owned = self.sql(expression, "owned") - owned = f" OWNED BY {owned}" if owned else "" - - cache = expression.args.get("cache") - if cache is None: - cache_str = "" - elif cache is True: - cache_str = " CACHE" - else: - cache_str = f" CACHE {cache}" - - options = self.expressions(expression, key="options", flat=True, sep=" ") - options = f" {options}" if options else "" - - return f"{start}{increment}{minvalue}{maxvalue}{cache_str}{options}{owned}".lstrip() - - def clone_sql(self, expression: exp.Clone) -> str: - this = self.sql(expression, "this") - shallow = "SHALLOW " if expression.args.get("shallow") else "" - keyword = ( - "COPY" - if expression.args.get("copy") and self.SUPPORTS_TABLE_COPY - else "CLONE" - ) - return f"{shallow}{keyword} {this}" - - def describe_sql(self, expression: exp.Describe) -> str: - style = expression.args.get("style") - style = f" {style}" if style else "" - partition = self.sql(expression, "partition") - partition = f" {partition}" if partition else "" - format = self.sql(expression, "format") - format = f" {format}" if format else "" - - return f"DESCRIBE{style}{format} {self.sql(expression, 'this')}{partition}" - - def heredoc_sql(self, expression: exp.Heredoc) -> str: - tag = self.sql(expression, "tag") - return f"${tag}${self.sql(expression, 'this')}${tag}$" - - def prepend_ctes(self, expression: exp.Expression, sql: str) -> str: - with_ = self.sql(expression, "with_") - if with_: - sql = f"{with_}{self.sep()}{sql}" - return sql - - def with_sql(self, expression: exp.With) -> str: - sql = self.expressions(expression, flat=True) - recursive = ( - "RECURSIVE " - if self.CTE_RECURSIVE_KEYWORD_REQUIRED and expression.args.get("recursive") - else "" - ) - search = self.sql(expression, "search") - search = f" {search}" if search else "" - - return f"WITH {recursive}{sql}{search}" - - def cte_sql(self, expression: exp.CTE) -> str: - alias = expression.args.get("alias") - if alias: - alias.add_comments(expression.pop_comments()) - - alias_sql = self.sql(expression, "alias") - - materialized = expression.args.get("materialized") - if materialized is False: - materialized = "NOT MATERIALIZED " - elif materialized: - materialized = "MATERIALIZED " - - key_expressions = self.expressions(expression, key="key_expressions", flat=True) - key_expressions = f" USING KEY ({key_expressions})" if key_expressions else "" - - return f"{alias_sql}{key_expressions} AS {materialized or ''}{self.wrap(expression)}" - - def tablealias_sql(self, expression: exp.TableAlias) -> str: - alias = self.sql(expression, "this") - columns = self.expressions(expression, key="columns", flat=True) - columns = f"({columns})" if columns else "" - - if columns and not self.SUPPORTS_TABLE_ALIAS_COLUMNS: - columns = "" - self.unsupported("Named columns are not supported in table alias.") - - if not alias and not self.dialect.UNNEST_COLUMN_ONLY: - alias = self._next_name() - - return f"{alias}{columns}" - - def bitstring_sql(self, expression: exp.BitString) -> str: - this = self.sql(expression, "this") - if self.dialect.BIT_START: - return f"{self.dialect.BIT_START}{this}{self.dialect.BIT_END}" - return f"{int(this, 2)}" - - def hexstring_sql( - self, expression: exp.HexString, binary_function_repr: t.Optional[str] = None - ) -> str: - this = self.sql(expression, "this") - is_integer_type = expression.args.get("is_integer") - - if (is_integer_type and not self.dialect.HEX_STRING_IS_INTEGER_TYPE) or ( - not self.dialect.HEX_START and not binary_function_repr - ): - # Integer representation will be returned if: - # - The read dialect treats the hex value as integer literal but not the write - # - The transpilation is not supported (write dialect hasn't set HEX_START or the param flag) - return f"{int(this, 16)}" - - if not is_integer_type: - # Read dialect treats the hex value as BINARY/BLOB - if binary_function_repr: - # The write dialect supports the transpilation to its equivalent BINARY/BLOB - return self.func(binary_function_repr, exp.Literal.string(this)) - if self.dialect.HEX_STRING_IS_INTEGER_TYPE: - # The write dialect does not support the transpilation, it'll treat the hex value as INTEGER - self.unsupported( - "Unsupported transpilation from BINARY/BLOB hex string" - ) - - return f"{self.dialect.HEX_START}{this}{self.dialect.HEX_END}" - - def bytestring_sql(self, expression: exp.ByteString) -> str: - this = self.sql(expression, "this") - if self.dialect.BYTE_START: - escaped_byte_string = self.escape_str( - this, - escape_backslash=False, - delimiter=self.dialect.BYTE_END, - escaped_delimiter=self._escaped_byte_quote_end, - ) - is_bytes = expression.args.get("is_bytes", False) - delimited_byte_string = ( - f"{self.dialect.BYTE_START}{escaped_byte_string}{self.dialect.BYTE_END}" - ) - if is_bytes and not self.dialect.BYTE_STRING_IS_BYTES_TYPE: - return self.sql( - exp.cast( - delimited_byte_string, - exp.DataType.Type.BINARY, - dialect=self.dialect, - ) - ) - if not is_bytes and self.dialect.BYTE_STRING_IS_BYTES_TYPE: - return self.sql( - exp.cast( - delimited_byte_string, - exp.DataType.Type.VARCHAR, - dialect=self.dialect, - ) - ) - - return delimited_byte_string - return this - - def unicodestring_sql(self, expression: exp.UnicodeString) -> str: - this = self.sql(expression, "this") - escape = expression.args.get("escape") - - if self.dialect.UNICODE_START: - escape_substitute = r"\\\1" - left_quote, right_quote = ( - self.dialect.UNICODE_START, - self.dialect.UNICODE_END, - ) - else: - escape_substitute = r"\\u\1" - left_quote, right_quote = self.dialect.QUOTE_START, self.dialect.QUOTE_END - - if escape: - escape_pattern = re.compile(rf"{escape.name}(\d+)") - escape_sql = f" UESCAPE {self.sql(escape)}" if self.SUPPORTS_UESCAPE else "" - else: - escape_pattern = ESCAPED_UNICODE_RE - escape_sql = "" - - if not self.dialect.UNICODE_START or (escape and not self.SUPPORTS_UESCAPE): - this = escape_pattern.sub( - self.UNICODE_SUBSTITUTE or escape_substitute, this - ) - - return f"{left_quote}{this}{right_quote}{escape_sql}" - - def rawstring_sql(self, expression: exp.RawString) -> str: - string = expression.this - if "\\" in self.dialect.tokenizer_class.STRING_ESCAPES: - string = string.replace("\\", "\\\\") - - string = self.escape_str(string, escape_backslash=False) - return f"{self.dialect.QUOTE_START}{string}{self.dialect.QUOTE_END}" - - def datatypeparam_sql(self, expression: exp.DataTypeParam) -> str: - this = self.sql(expression, "this") - specifier = self.sql(expression, "expression") - specifier = ( - f" {specifier}" if specifier and self.DATA_TYPE_SPECIFIERS_ALLOWED else "" - ) - return f"{this}{specifier}" - - def datatype_sql(self, expression: exp.DataType) -> str: - nested = "" - values = "" - interior = self.expressions(expression, flat=True) - - type_value = expression.this - if type_value in self.UNSUPPORTED_TYPES: - self.unsupported( - f"Data type {type_value.value} is not supported when targeting {self.dialect.__class__.__name__}" - ) - - if type_value == exp.DataType.Type.USERDEFINED and expression.args.get("kind"): - type_sql = self.sql(expression, "kind") - else: - type_sql = ( - self.TYPE_MAPPING.get(type_value, type_value.value) - if isinstance(type_value, exp.DataType.Type) - else type_value - ) - - if interior: - if expression.args.get("nested"): - nested = ( - f"{self.STRUCT_DELIMITER[0]}{interior}{self.STRUCT_DELIMITER[1]}" - ) - if expression.args.get("values") is not None: - delimiters = ( - ("[", "]") - if type_value == exp.DataType.Type.ARRAY - else ("(", ")") - ) - values = self.expressions(expression, key="values", flat=True) - values = f"{delimiters[0]}{values}{delimiters[1]}" - elif type_value == exp.DataType.Type.INTERVAL: - nested = f" {interior}" - else: - nested = f"({interior})" - - type_sql = f"{type_sql}{nested}{values}" - if self.TZ_TO_WITH_TIME_ZONE and type_value in ( - exp.DataType.Type.TIMETZ, - exp.DataType.Type.TIMESTAMPTZ, - ): - type_sql = f"{type_sql} WITH TIME ZONE" - - return type_sql - - def directory_sql(self, expression: exp.Directory) -> str: - local = "LOCAL " if expression.args.get("local") else "" - row_format = self.sql(expression, "row_format") - row_format = f" {row_format}" if row_format else "" - return f"{local}DIRECTORY {self.sql(expression, 'this')}{row_format}" - - def delete_sql(self, expression: exp.Delete) -> str: - this = self.sql(expression, "this") - this = f" FROM {this}" if this else "" - using = self.expressions(expression, key="using") - using = f" USING {using}" if using else "" - cluster = self.sql(expression, "cluster") - cluster = f" {cluster}" if cluster else "" - where = self.sql(expression, "where") - returning = self.sql(expression, "returning") - order = self.sql(expression, "order") - limit = self.sql(expression, "limit") - tables = self.expressions(expression, key="tables") - tables = f" {tables}" if tables else "" - if self.RETURNING_END: - expression_sql = f"{this}{using}{cluster}{where}{returning}{order}{limit}" - else: - expression_sql = f"{returning}{this}{using}{cluster}{where}{order}{limit}" - return self.prepend_ctes(expression, f"DELETE{tables}{expression_sql}") - - def drop_sql(self, expression: exp.Drop) -> str: - this = self.sql(expression, "this") - expressions = self.expressions(expression, flat=True) - expressions = f" ({expressions})" if expressions else "" - kind = expression.args["kind"] - kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind - exists_sql = " IF EXISTS " if expression.args.get("exists") else " " - concurrently_sql = ( - " CONCURRENTLY" if expression.args.get("concurrently") else "" - ) - on_cluster = self.sql(expression, "cluster") - on_cluster = f" {on_cluster}" if on_cluster else "" - temporary = " TEMPORARY" if expression.args.get("temporary") else "" - materialized = " MATERIALIZED" if expression.args.get("materialized") else "" - cascade = " CASCADE" if expression.args.get("cascade") else "" - constraints = " CONSTRAINTS" if expression.args.get("constraints") else "" - purge = " PURGE" if expression.args.get("purge") else "" - return f"DROP{temporary}{materialized} {kind}{concurrently_sql}{exists_sql}{this}{on_cluster}{expressions}{cascade}{constraints}{purge}" - - def set_operation(self, expression: exp.SetOperation) -> str: - op_type = type(expression) - op_name = op_type.key.upper() - - distinct = expression.args.get("distinct") - if ( - distinct is False - and op_type in (exp.Except, exp.Intersect) - and not self.EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE - ): - self.unsupported(f"{op_name} ALL is not supported") - - default_distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[op_type] - - if distinct is None: - distinct = default_distinct - if distinct is None: - self.unsupported(f"{op_name} requires DISTINCT or ALL to be specified") - - if distinct is default_distinct: - distinct_or_all = "" - else: - distinct_or_all = " DISTINCT" if distinct else " ALL" - - side_kind = " ".join(filter(None, [expression.side, expression.kind])) - side_kind = f"{side_kind} " if side_kind else "" - - by_name = " BY NAME" if expression.args.get("by_name") else "" - on = self.expressions(expression, key="on", flat=True) - on = f" ON ({on})" if on else "" - - return f"{side_kind}{op_name}{distinct_or_all}{by_name}{on}" - - def set_operations(self, expression: exp.SetOperation) -> str: - if not self.SET_OP_MODIFIERS: - limit = expression.args.get("limit") - order = expression.args.get("order") - - if limit or order: - select = self._move_ctes_to_top_level( - exp.subquery(expression, "_l_0", copy=False).select("*", copy=False) - ) - - if limit: - select = select.limit(limit.pop(), copy=False) - if order: - select = select.order_by(order.pop(), copy=False) - return self.sql(select) - - sqls: t.List[str] = [] - stack: t.List[t.Union[str, exp.Expression]] = [expression] - - while stack: - node = stack.pop() - - if isinstance(node, exp.SetOperation): - stack.append(node.expression) - stack.append( - self.maybe_comment( - self.set_operation(node), comments=node.comments, separated=True - ) - ) - stack.append(node.this) - else: - sqls.append(self.sql(node)) - - this = self.sep().join(sqls) - this = self.query_modifiers(expression, this) - return self.prepend_ctes(expression, this) - - def fetch_sql(self, expression: exp.Fetch) -> str: - direction = expression.args.get("direction") - direction = f" {direction}" if direction else "" - count = self.sql(expression, "count") - count = f" {count}" if count else "" - limit_options = self.sql(expression, "limit_options") - limit_options = f"{limit_options}" if limit_options else " ROWS ONLY" - return f"{self.seg('FETCH')}{direction}{count}{limit_options}" - - def limitoptions_sql(self, expression: exp.LimitOptions) -> str: - percent = " PERCENT" if expression.args.get("percent") else "" - rows = " ROWS" if expression.args.get("rows") else "" - with_ties = " WITH TIES" if expression.args.get("with_ties") else "" - if not with_ties and rows: - with_ties = " ONLY" - return f"{percent}{rows}{with_ties}" - - def filter_sql(self, expression: exp.Filter) -> str: - if self.AGGREGATE_FILTER_SUPPORTED: - this = self.sql(expression, "this") - where = self.sql(expression, "expression").strip() - return f"{this} FILTER({where})" - - agg = expression.this - agg_arg = agg.this - cond = expression.expression.this - agg_arg.replace(exp.If(this=cond.copy(), true=agg_arg.copy())) - return self.sql(agg) - - def hint_sql(self, expression: exp.Hint) -> str: - if not self.QUERY_HINTS: - self.unsupported("Hints are not supported") - return "" - - return ( - f" /*+ {self.expressions(expression, sep=self.QUERY_HINT_SEP).strip()} */" - ) - - def indexparameters_sql(self, expression: exp.IndexParameters) -> str: - using = self.sql(expression, "using") - using = f" USING {using}" if using else "" - columns = self.expressions(expression, key="columns", flat=True) - columns = f"({columns})" if columns else "" - partition_by = self.expressions(expression, key="partition_by", flat=True) - partition_by = f" PARTITION BY {partition_by}" if partition_by else "" - where = self.sql(expression, "where") - include = self.expressions(expression, key="include", flat=True) - if include: - include = f" INCLUDE ({include})" - with_storage = self.expressions(expression, key="with_storage", flat=True) - with_storage = f" WITH ({with_storage})" if with_storage else "" - tablespace = self.sql(expression, "tablespace") - tablespace = f" USING INDEX TABLESPACE {tablespace}" if tablespace else "" - on = self.sql(expression, "on") - on = f" ON {on}" if on else "" - - return f"{using}{columns}{include}{with_storage}{tablespace}{partition_by}{where}{on}" - - def index_sql(self, expression: exp.Index) -> str: - unique = "UNIQUE " if expression.args.get("unique") else "" - primary = "PRIMARY " if expression.args.get("primary") else "" - amp = "AMP " if expression.args.get("amp") else "" - name = self.sql(expression, "this") - name = f"{name} " if name else "" - table = self.sql(expression, "table") - table = f"{self.INDEX_ON} {table}" if table else "" - - index = "INDEX " if not table else "" - - params = self.sql(expression, "params") - return f"{unique}{primary}{amp}{index}{name}{table}{params}" - - def identifier_sql(self, expression: exp.Identifier) -> str: - text = expression.name - lower = text.lower() - text = lower if self.normalize and not expression.quoted else text - text = text.replace(self._identifier_end, self._escaped_identifier_end) - if ( - expression.quoted - or self.dialect.can_quote(expression, self.identify) - or lower in self.RESERVED_KEYWORDS - or ( - not self.dialect.IDENTIFIERS_CAN_START_WITH_DIGIT and text[:1].isdigit() - ) - ): - text = f"{self._identifier_start}{text}{self._identifier_end}" - return text - - def hex_sql(self, expression: exp.Hex) -> str: - text = self.func(self.HEX_FUNC, self.sql(expression, "this")) - if self.dialect.HEX_LOWERCASE: - text = self.func("LOWER", text) - - return text - - def lowerhex_sql(self, expression: exp.LowerHex) -> str: - text = self.func(self.HEX_FUNC, self.sql(expression, "this")) - if not self.dialect.HEX_LOWERCASE: - text = self.func("LOWER", text) - return text - - def inputoutputformat_sql(self, expression: exp.InputOutputFormat) -> str: - input_format = self.sql(expression, "input_format") - input_format = f"INPUTFORMAT {input_format}" if input_format else "" - output_format = self.sql(expression, "output_format") - output_format = f"OUTPUTFORMAT {output_format}" if output_format else "" - return self.sep().join((input_format, output_format)) - - def national_sql(self, expression: exp.National, prefix: str = "N") -> str: - string = self.sql(exp.Literal.string(expression.name)) - return f"{prefix}{string}" - - def partition_sql(self, expression: exp.Partition) -> str: - partition_keyword = ( - "SUBPARTITION" if expression.args.get("subpartition") else "PARTITION" - ) - return f"{partition_keyword}({self.expressions(expression, flat=True)})" - - def properties_sql(self, expression: exp.Properties) -> str: - root_properties = [] - with_properties = [] - - for p in expression.expressions: - p_loc = self.PROPERTIES_LOCATION[p.__class__] - if p_loc == exp.Properties.Location.POST_WITH: - with_properties.append(p) - elif p_loc == exp.Properties.Location.POST_SCHEMA: - root_properties.append(p) - - root_props_ast = exp.Properties(expressions=root_properties) - root_props_ast.parent = expression.parent - - with_props_ast = exp.Properties(expressions=with_properties) - with_props_ast.parent = expression.parent - - root_props = self.root_properties(root_props_ast) - with_props = self.with_properties(with_props_ast) - - if root_props and with_props and not self.pretty: - with_props = " " + with_props - - return root_props + with_props - - def root_properties(self, properties: exp.Properties) -> str: - if properties.expressions: - return self.expressions(properties, indent=False, sep=" ") - return "" - - def properties( - self, - properties: exp.Properties, - prefix: str = "", - sep: str = ", ", - suffix: str = "", - wrapped: bool = True, - ) -> str: - if properties.expressions: - expressions = self.expressions(properties, sep=sep, indent=False) - if expressions: - expressions = self.wrap(expressions) if wrapped else expressions - return f"{prefix}{' ' if prefix.strip() else ''}{expressions}{suffix}" - return "" - - def with_properties(self, properties: exp.Properties) -> str: - return self.properties( - properties, prefix=self.seg(self.WITH_PROPERTIES_PREFIX, sep="") - ) - - def locate_properties(self, properties: exp.Properties) -> t.DefaultDict: - properties_locs = defaultdict(list) - for p in properties.expressions: - p_loc = self.PROPERTIES_LOCATION[p.__class__] - if p_loc != exp.Properties.Location.UNSUPPORTED: - properties_locs[p_loc].append(p) - else: - self.unsupported(f"Unsupported property {p.key}") - - return properties_locs - - def property_name(self, expression: exp.Property, string_key: bool = False) -> str: - if isinstance(expression.this, exp.Dot): - return self.sql(expression, "this") - return f"'{expression.name}'" if string_key else expression.name - - def property_sql(self, expression: exp.Property) -> str: - property_cls = expression.__class__ - if property_cls == exp.Property: - return f"{self.property_name(expression)}={self.sql(expression, 'value')}" - - property_name = exp.Properties.PROPERTY_TO_NAME.get(property_cls) - if not property_name: - self.unsupported(f"Unsupported property {expression.key}") - - return f"{property_name}={self.sql(expression, 'this')}" - - def likeproperty_sql(self, expression: exp.LikeProperty) -> str: - if self.SUPPORTS_CREATE_TABLE_LIKE: - options = " ".join( - f"{e.name} {self.sql(e, 'value')}" for e in expression.expressions - ) - options = f" {options}" if options else "" - - like = f"LIKE {self.sql(expression, 'this')}{options}" - if self.LIKE_PROPERTY_INSIDE_SCHEMA and not isinstance( - expression.parent, exp.Schema - ): - like = f"({like})" - - return like - - if expression.expressions: - self.unsupported("Transpilation of LIKE property options is unsupported") - - select = exp.select("*").from_(expression.this).limit(0) - return f"AS {self.sql(select)}" - - def fallbackproperty_sql(self, expression: exp.FallbackProperty) -> str: - no = "NO " if expression.args.get("no") else "" - protection = " PROTECTION" if expression.args.get("protection") else "" - return f"{no}FALLBACK{protection}" - - def journalproperty_sql(self, expression: exp.JournalProperty) -> str: - no = "NO " if expression.args.get("no") else "" - local = expression.args.get("local") - local = f"{local} " if local else "" - dual = "DUAL " if expression.args.get("dual") else "" - before = "BEFORE " if expression.args.get("before") else "" - after = "AFTER " if expression.args.get("after") else "" - return f"{no}{local}{dual}{before}{after}JOURNAL" - - def freespaceproperty_sql(self, expression: exp.FreespaceProperty) -> str: - freespace = self.sql(expression, "this") - percent = " PERCENT" if expression.args.get("percent") else "" - return f"FREESPACE={freespace}{percent}" - - def checksumproperty_sql(self, expression: exp.ChecksumProperty) -> str: - if expression.args.get("default"): - property = "DEFAULT" - elif expression.args.get("on"): - property = "ON" - else: - property = "OFF" - return f"CHECKSUM={property}" - - def mergeblockratioproperty_sql( - self, expression: exp.MergeBlockRatioProperty - ) -> str: - if expression.args.get("no"): - return "NO MERGEBLOCKRATIO" - if expression.args.get("default"): - return "DEFAULT MERGEBLOCKRATIO" - - percent = " PERCENT" if expression.args.get("percent") else "" - return f"MERGEBLOCKRATIO={self.sql(expression, 'this')}{percent}" - - def datablocksizeproperty_sql(self, expression: exp.DataBlocksizeProperty) -> str: - default = expression.args.get("default") - minimum = expression.args.get("minimum") - maximum = expression.args.get("maximum") - if default or minimum or maximum: - if default: - prop = "DEFAULT" - elif minimum: - prop = "MINIMUM" - else: - prop = "MAXIMUM" - return f"{prop} DATABLOCKSIZE" - units = expression.args.get("units") - units = f" {units}" if units else "" - return f"DATABLOCKSIZE={self.sql(expression, 'size')}{units}" - - def blockcompressionproperty_sql( - self, expression: exp.BlockCompressionProperty - ) -> str: - autotemp = expression.args.get("autotemp") - always = expression.args.get("always") - default = expression.args.get("default") - manual = expression.args.get("manual") - never = expression.args.get("never") - - if autotemp is not None: - prop = f"AUTOTEMP({self.expressions(autotemp)})" - elif always: - prop = "ALWAYS" - elif default: - prop = "DEFAULT" - elif manual: - prop = "MANUAL" - elif never: - prop = "NEVER" - return f"BLOCKCOMPRESSION={prop}" - - def isolatedloadingproperty_sql( - self, expression: exp.IsolatedLoadingProperty - ) -> str: - no = expression.args.get("no") - no = " NO" if no else "" - concurrent = expression.args.get("concurrent") - concurrent = " CONCURRENT" if concurrent else "" - target = self.sql(expression, "target") - target = f" {target}" if target else "" - return f"WITH{no}{concurrent} ISOLATED LOADING{target}" - - def partitionboundspec_sql(self, expression: exp.PartitionBoundSpec) -> str: - if isinstance(expression.this, list): - return f"IN ({self.expressions(expression, key='this', flat=True)})" - if expression.this: - modulus = self.sql(expression, "this") - remainder = self.sql(expression, "expression") - return f"WITH (MODULUS {modulus}, REMAINDER {remainder})" - - from_expressions = self.expressions( - expression, key="from_expressions", flat=True - ) - to_expressions = self.expressions(expression, key="to_expressions", flat=True) - return f"FROM ({from_expressions}) TO ({to_expressions})" - - def partitionedofproperty_sql(self, expression: exp.PartitionedOfProperty) -> str: - this = self.sql(expression, "this") - - for_values_or_default = expression.expression - if isinstance(for_values_or_default, exp.PartitionBoundSpec): - for_values_or_default = f" FOR VALUES {self.sql(for_values_or_default)}" - else: - for_values_or_default = " DEFAULT" - - return f"PARTITION OF {this}{for_values_or_default}" - - def lockingproperty_sql(self, expression: exp.LockingProperty) -> str: - kind = expression.args.get("kind") - this = f" {self.sql(expression, 'this')}" if expression.this else "" - for_or_in = expression.args.get("for_or_in") - for_or_in = f" {for_or_in}" if for_or_in else "" - lock_type = expression.args.get("lock_type") - override = " OVERRIDE" if expression.args.get("override") else "" - return f"LOCKING {kind}{this}{for_or_in} {lock_type}{override}" - - def withdataproperty_sql(self, expression: exp.WithDataProperty) -> str: - data_sql = f"WITH {'NO ' if expression.args.get('no') else ''}DATA" - statistics = expression.args.get("statistics") - statistics_sql = "" - if statistics is not None: - statistics_sql = f" AND {'NO ' if not statistics else ''}STATISTICS" - return f"{data_sql}{statistics_sql}" - - def withsystemversioningproperty_sql( - self, expression: exp.WithSystemVersioningProperty - ) -> str: - this = self.sql(expression, "this") - this = f"HISTORY_TABLE={this}" if this else "" - data_consistency: t.Optional[str] = self.sql(expression, "data_consistency") - data_consistency = ( - f"DATA_CONSISTENCY_CHECK={data_consistency}" if data_consistency else None - ) - retention_period: t.Optional[str] = self.sql(expression, "retention_period") - retention_period = ( - f"HISTORY_RETENTION_PERIOD={retention_period}" if retention_period else None - ) - - if this: - on_sql = self.func("ON", this, data_consistency, retention_period) - else: - on_sql = "ON" if expression.args.get("on") else "OFF" - - sql = f"SYSTEM_VERSIONING={on_sql}" - - return f"WITH({sql})" if expression.args.get("with_") else sql - - def insert_sql(self, expression: exp.Insert) -> str: - hint = self.sql(expression, "hint") - overwrite = expression.args.get("overwrite") - - if isinstance(expression.this, exp.Directory): - this = " OVERWRITE" if overwrite else " INTO" - else: - this = self.INSERT_OVERWRITE if overwrite else " INTO" - - stored = self.sql(expression, "stored") - stored = f" {stored}" if stored else "" - alternative = expression.args.get("alternative") - alternative = f" OR {alternative}" if alternative else "" - ignore = " IGNORE" if expression.args.get("ignore") else "" - is_function = expression.args.get("is_function") - if is_function: - this = f"{this} FUNCTION" - this = f"{this} {self.sql(expression, 'this')}" - - exists = " IF EXISTS" if expression.args.get("exists") else "" - where = self.sql(expression, "where") - where = f"{self.sep()}REPLACE WHERE {where}" if where else "" - expression_sql = f"{self.sep()}{self.sql(expression, 'expression')}" - on_conflict = self.sql(expression, "conflict") - on_conflict = f" {on_conflict}" if on_conflict else "" - by_name = " BY NAME" if expression.args.get("by_name") else "" - default_values = "DEFAULT VALUES" if expression.args.get("default") else "" - returning = self.sql(expression, "returning") - - if self.RETURNING_END: - expression_sql = f"{expression_sql}{on_conflict}{default_values}{returning}" - else: - expression_sql = f"{returning}{expression_sql}{on_conflict}" - - partition_by = self.sql(expression, "partition") - partition_by = f" {partition_by}" if partition_by else "" - settings = self.sql(expression, "settings") - settings = f" {settings}" if settings else "" - - source = self.sql(expression, "source") - source = f"TABLE {source}" if source else "" - - sql = f"INSERT{hint}{alternative}{ignore}{this}{stored}{by_name}{exists}{partition_by}{settings}{where}{expression_sql}{source}" - return self.prepend_ctes(expression, sql) - - def introducer_sql(self, expression: exp.Introducer) -> str: - return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}" - - def kill_sql(self, expression: exp.Kill) -> str: - kind = self.sql(expression, "kind") - kind = f" {kind}" if kind else "" - this = self.sql(expression, "this") - this = f" {this}" if this else "" - return f"KILL{kind}{this}" - - def pseudotype_sql(self, expression: exp.PseudoType) -> str: - return expression.name - - def objectidentifier_sql(self, expression: exp.ObjectIdentifier) -> str: - return expression.name - - def onconflict_sql(self, expression: exp.OnConflict) -> str: - conflict = ( - "ON DUPLICATE KEY" if expression.args.get("duplicate") else "ON CONFLICT" - ) - - constraint = self.sql(expression, "constraint") - constraint = f" ON CONSTRAINT {constraint}" if constraint else "" - - conflict_keys = self.expressions(expression, key="conflict_keys", flat=True) - conflict_keys = f"({conflict_keys}) " if conflict_keys else " " - action = self.sql(expression, "action") - - expressions = self.expressions(expression, flat=True) - if expressions: - set_keyword = "SET " if self.DUPLICATE_KEY_UPDATE_WITH_SET else "" - expressions = f" {set_keyword}{expressions}" - - where = self.sql(expression, "where") - return f"{conflict}{constraint}{conflict_keys}{action}{expressions}{where}" - - def returning_sql(self, expression: exp.Returning) -> str: - return f"{self.seg('RETURNING')} {self.expressions(expression, flat=True)}" - - def rowformatdelimitedproperty_sql( - self, expression: exp.RowFormatDelimitedProperty - ) -> str: - fields = self.sql(expression, "fields") - fields = f" FIELDS TERMINATED BY {fields}" if fields else "" - escaped = self.sql(expression, "escaped") - escaped = f" ESCAPED BY {escaped}" if escaped else "" - items = self.sql(expression, "collection_items") - items = f" COLLECTION ITEMS TERMINATED BY {items}" if items else "" - keys = self.sql(expression, "map_keys") - keys = f" MAP KEYS TERMINATED BY {keys}" if keys else "" - lines = self.sql(expression, "lines") - lines = f" LINES TERMINATED BY {lines}" if lines else "" - null = self.sql(expression, "null") - null = f" NULL DEFINED AS {null}" if null else "" - return f"ROW FORMAT DELIMITED{fields}{escaped}{items}{keys}{lines}{null}" - - def withtablehint_sql(self, expression: exp.WithTableHint) -> str: - return f"WITH ({self.expressions(expression, flat=True)})" - - def indextablehint_sql(self, expression: exp.IndexTableHint) -> str: - this = f"{self.sql(expression, 'this')} INDEX" - target = self.sql(expression, "target") - target = f" FOR {target}" if target else "" - return f"{this}{target} ({self.expressions(expression, flat=True)})" - - def historicaldata_sql(self, expression: exp.HistoricalData) -> str: - this = self.sql(expression, "this") - kind = self.sql(expression, "kind") - expr = self.sql(expression, "expression") - return f"{this} ({kind} => {expr})" - - def table_parts(self, expression: exp.Table) -> str: - return ".".join( - self.sql(part) - for part in ( - expression.args.get("catalog"), - expression.args.get("db"), - expression.args.get("this"), - ) - if part is not None - ) - - def table_sql(self, expression: exp.Table, sep: str = " AS ") -> str: - table = self.table_parts(expression) - only = "ONLY " if expression.args.get("only") else "" - partition = self.sql(expression, "partition") - partition = f" {partition}" if partition else "" - version = self.sql(expression, "version") - version = f" {version}" if version else "" - alias = self.sql(expression, "alias") - alias = f"{sep}{alias}" if alias else "" - - sample = self.sql(expression, "sample") - if self.dialect.ALIAS_POST_TABLESAMPLE: - sample_pre_alias = sample - sample_post_alias = "" - else: - sample_pre_alias = "" - sample_post_alias = sample - - hints = self.expressions(expression, key="hints", sep=" ") - hints = f" {hints}" if hints and self.TABLE_HINTS else "" - pivots = self.expressions(expression, key="pivots", sep="", flat=True) - joins = self.indent( - self.expressions(expression, key="joins", sep="", flat=True), - skip_first=True, - ) - laterals = self.expressions(expression, key="laterals", sep="") - - file_format = self.sql(expression, "format") - if file_format: - pattern = self.sql(expression, "pattern") - pattern = f", PATTERN => {pattern}" if pattern else "" - file_format = f" (FILE_FORMAT => {file_format}{pattern})" - - ordinality = expression.args.get("ordinality") or "" - if ordinality: - ordinality = f" WITH ORDINALITY{alias}" - alias = "" - - when = self.sql(expression, "when") - if when: - table = f"{table} {when}" - - changes = self.sql(expression, "changes") - changes = f" {changes}" if changes else "" - - rows_from = self.expressions(expression, key="rows_from") - if rows_from: - table = f"ROWS FROM {self.wrap(rows_from)}" - - indexed = expression.args.get("indexed") - if indexed is not None: - indexed = f" INDEXED BY {self.sql(indexed)}" if indexed else " NOT INDEXED" - else: - indexed = "" - - # Workaround https://github.com/tobymao/sqlglot/issues/7073 - return f"{only}{table}{changes}{alias}{partition}{version}{file_format}{sample_pre_alias}{indexed}{hints}{pivots}{sample_post_alias}{joins}{laterals}{ordinality}" - - def tablefromrows_sql(self, expression: exp.TableFromRows) -> str: - table = self.func("TABLE", expression.this) - alias = self.sql(expression, "alias") - alias = f" AS {alias}" if alias else "" - sample = self.sql(expression, "sample") - pivots = self.expressions(expression, key="pivots", sep="", flat=True) - joins = self.indent( - self.expressions(expression, key="joins", sep="", flat=True), - skip_first=True, - ) - return f"{table}{alias}{pivots}{sample}{joins}" - - def tablesample_sql( - self, - expression: exp.TableSample, - tablesample_keyword: t.Optional[str] = None, - ) -> str: - method = self.sql(expression, "method") - method = f"{method} " if method and self.TABLESAMPLE_WITH_METHOD else "" - numerator = self.sql(expression, "bucket_numerator") - denominator = self.sql(expression, "bucket_denominator") - field = self.sql(expression, "bucket_field") - field = f" ON {field}" if field else "" - bucket = f"BUCKET {numerator} OUT OF {denominator}{field}" if numerator else "" - seed = self.sql(expression, "seed") - seed = f" {self.TABLESAMPLE_SEED_KEYWORD} ({seed})" if seed else "" - - size = self.sql(expression, "size") - if size and self.TABLESAMPLE_SIZE_IS_ROWS: - size = f"{size} ROWS" - - percent = self.sql(expression, "percent") - if percent and not self.dialect.TABLESAMPLE_SIZE_IS_PERCENT: - percent = f"{percent} PERCENT" - - expr = f"{bucket}{percent}{size}" - if self.TABLESAMPLE_REQUIRES_PARENS: - expr = f"({expr})" - - return ( - f" {tablesample_keyword or self.TABLESAMPLE_KEYWORDS} {method}{expr}{seed}" - ) - - def pivot_sql(self, expression: exp.Pivot) -> str: - expressions = self.expressions(expression, flat=True) - direction = "UNPIVOT" if expression.unpivot else "PIVOT" - - group = self.sql(expression, "group") - - if expression.this: - this = self.sql(expression, "this") - if not expressions: - sql = f"UNPIVOT {this}" - else: - on = f"{self.seg('ON')} {expressions}" - into = self.sql(expression, "into") - into = f"{self.seg('INTO')} {into}" if into else "" - using = self.expressions(expression, key="using", flat=True) - using = f"{self.seg('USING')} {using}" if using else "" - sql = f"{direction} {this}{on}{into}{using}{group}" - return self.prepend_ctes(expression, sql) - - alias = self.sql(expression, "alias") - alias = f" AS {alias}" if alias else "" - - fields = self.expressions( - expression, - "fields", - sep=" ", - dynamic=True, - new_line=True, - skip_first=True, - skip_last=True, - ) - - include_nulls = expression.args.get("include_nulls") - if include_nulls is not None: - nulls = " INCLUDE NULLS " if include_nulls else " EXCLUDE NULLS " - else: - nulls = "" - - default_on_null = self.sql(expression, "default_on_null") - default_on_null = ( - f" DEFAULT ON NULL ({default_on_null})" if default_on_null else "" - ) - sql = f"{self.seg(direction)}{nulls}({expressions} FOR {fields}{default_on_null}{group}){alias}" - return self.prepend_ctes(expression, sql) - - def version_sql(self, expression: exp.Version) -> str: - this = f"FOR {expression.name}" - kind = expression.text("kind") - expr = self.sql(expression, "expression") - return f"{this} {kind} {expr}" - - def tuple_sql(self, expression: exp.Tuple) -> str: - return f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})" - - def update_sql(self, expression: exp.Update) -> str: - this = self.sql(expression, "this") - set_sql = self.expressions(expression, flat=True) - from_sql = self.sql(expression, "from_") - where_sql = self.sql(expression, "where") - returning = self.sql(expression, "returning") - order = self.sql(expression, "order") - limit = self.sql(expression, "limit") - if self.RETURNING_END: - expression_sql = f"{from_sql}{where_sql}{returning}" - else: - expression_sql = f"{returning}{from_sql}{where_sql}" - options = self.expressions(expression, key="options") - options = f" OPTION({options})" if options else "" - sql = f"UPDATE {this} SET {set_sql}{expression_sql}{order}{limit}{options}" - return self.prepend_ctes(expression, sql) - - def values_sql(self, expression: exp.Values, values_as_table: bool = True) -> str: - values_as_table = values_as_table and self.VALUES_AS_TABLE - - # The VALUES clause is still valid in an `INSERT INTO ..` statement, for example - if values_as_table or not expression.find_ancestor(exp.From, exp.Join): - args = self.expressions(expression) - alias = self.sql(expression, "alias") - values = f"VALUES{self.seg('')}{args}" - values = ( - f"({values})" - if self.WRAP_DERIVED_VALUES - and (alias or isinstance(expression.parent, (exp.From, exp.Table))) - else values - ) - values = self.query_modifiers(expression, values) - return f"{values} AS {alias}" if alias else values - - # Converts `VALUES...` expression into a series of select unions. - alias_node = expression.args.get("alias") - column_names = alias_node and alias_node.columns - - selects: t.List[exp.Query] = [] - - for i, tup in enumerate(expression.expressions): - row = tup.expressions - - if i == 0 and column_names: - row = [ - exp.alias_(value, column_name) - for value, column_name in zip(row, column_names) - ] - - selects.append(exp.Select(expressions=row)) - - if self.pretty: - # This may result in poor performance for large-cardinality `VALUES` tables, due to - # the deep nesting of the resulting exp.Unions. If this is a problem, either increase - # `sys.setrecursionlimit` to avoid RecursionErrors, or don't set `pretty`. - query = reduce( - lambda x, y: exp.union(x, y, distinct=False, copy=False), selects - ) - return self.subquery_sql( - query.subquery(alias_node and alias_node.this, copy=False) - ) - - alias = f" AS {self.sql(alias_node, 'this')}" if alias_node else "" - unions = " UNION ALL ".join(self.sql(select) for select in selects) - return f"({unions}){alias}" - - def var_sql(self, expression: exp.Var) -> str: - return self.sql(expression, "this") - - @unsupported_args("expressions") - def into_sql(self, expression: exp.Into) -> str: - temporary = " TEMPORARY" if expression.args.get("temporary") else "" - unlogged = " UNLOGGED" if expression.args.get("unlogged") else "" - return ( - f"{self.seg('INTO')}{temporary or unlogged} {self.sql(expression, 'this')}" - ) - - def from_sql(self, expression: exp.From) -> str: - return f"{self.seg('FROM')} {self.sql(expression, 'this')}" - - def groupingsets_sql(self, expression: exp.GroupingSets) -> str: - grouping_sets = self.expressions(expression, indent=False) - return f"GROUPING SETS {self.wrap(grouping_sets)}" - - def rollup_sql(self, expression: exp.Rollup) -> str: - expressions = self.expressions(expression, indent=False) - return f"ROLLUP {self.wrap(expressions)}" if expressions else "WITH ROLLUP" - - def cube_sql(self, expression: exp.Cube) -> str: - expressions = self.expressions(expression, indent=False) - return f"CUBE {self.wrap(expressions)}" if expressions else "WITH CUBE" - - def group_sql(self, expression: exp.Group) -> str: - group_by_all = expression.args.get("all") - if group_by_all is True: - modifier = " ALL" - elif group_by_all is False: - modifier = " DISTINCT" - else: - modifier = "" - - group_by = self.op_expressions(f"GROUP BY{modifier}", expression) - - grouping_sets = self.expressions(expression, key="grouping_sets") - cube = self.expressions(expression, key="cube") - rollup = self.expressions(expression, key="rollup") - - groupings = csv( - self.seg(grouping_sets) if grouping_sets else "", - self.seg(cube) if cube else "", - self.seg(rollup) if rollup else "", - self.seg("WITH TOTALS") if expression.args.get("totals") else "", - sep=self.GROUPINGS_SEP, - ) - - if ( - expression.expressions - and groupings - and groupings.strip() not in ("WITH CUBE", "WITH ROLLUP") - ): - group_by = f"{group_by}{self.GROUPINGS_SEP}" - - return f"{group_by}{groupings}" - - def having_sql(self, expression: exp.Having) -> str: - this = self.indent(self.sql(expression, "this")) - return f"{self.seg('HAVING')}{self.sep()}{this}" - - def connect_sql(self, expression: exp.Connect) -> str: - start = self.sql(expression, "start") - start = self.seg(f"START WITH {start}") if start else "" - nocycle = " NOCYCLE" if expression.args.get("nocycle") else "" - connect = self.sql(expression, "connect") - connect = self.seg(f"CONNECT BY{nocycle} {connect}") - return start + connect - - def prior_sql(self, expression: exp.Prior) -> str: - return f"PRIOR {self.sql(expression, 'this')}" - - def join_sql(self, expression: exp.Join) -> str: - if not self.SEMI_ANTI_JOIN_WITH_SIDE and expression.kind in ("SEMI", "ANTI"): - side = None - else: - side = expression.side - - op_sql = " ".join( - op - for op in ( - expression.method, - "GLOBAL" if expression.args.get("global_") else None, - side, - expression.kind, - expression.hint if self.JOIN_HINTS else None, - ) - if op - ) - match_cond = self.sql(expression, "match_condition") - match_cond = f" MATCH_CONDITION ({match_cond})" if match_cond else "" - on_sql = self.sql(expression, "on") - using = expression.args.get("using") - - if not on_sql and using: - on_sql = csv(*(self.sql(column) for column in using)) - - this = expression.this - this_sql = self.sql(this) - - exprs = self.expressions(expression) - if exprs: - this_sql = f"{this_sql},{self.seg(exprs)}" - - if on_sql: - on_sql = self.indent(on_sql, skip_first=True) - space = self.seg(" " * self.pad) if self.pretty else " " - if using: - on_sql = f"{space}USING ({on_sql})" - else: - on_sql = f"{space}ON {on_sql}" - elif not op_sql: - if ( - isinstance(this, exp.Lateral) - and this.args.get("cross_apply") is not None - ): - return f" {this_sql}" - - return f", {this_sql}" - - if op_sql != "STRAIGHT_JOIN": - op_sql = f"{op_sql} JOIN" if op_sql else "JOIN" - - pivots = self.expressions(expression, key="pivots", sep="", flat=True) - return f"{self.seg(op_sql)} {this_sql}{match_cond}{on_sql}{pivots}" - - def lambda_sql( - self, expression: exp.Lambda, arrow_sep: str = "->", wrap: bool = True - ) -> str: - args = self.expressions(expression, flat=True) - args = f"({args})" if wrap and len(args.split(",")) > 1 else args - return f"{args} {arrow_sep} {self.sql(expression, 'this')}" - - def lateral_op(self, expression: exp.Lateral) -> str: - cross_apply = expression.args.get("cross_apply") - - # https://www.mssqltips.com/sqlservertip/1958/sql-server-cross-apply-and-outer-apply/ - if cross_apply is True: - op = "INNER JOIN " - elif cross_apply is False: - op = "LEFT JOIN " - else: - op = "" - - return f"{op}LATERAL" - - def lateral_sql(self, expression: exp.Lateral) -> str: - this = self.sql(expression, "this") - - if expression.args.get("view"): - alias = expression.args["alias"] - columns = self.expressions(alias, key="columns", flat=True) - table = f" {alias.name}" if alias.name else "" - columns = f" AS {columns}" if columns else "" - op_sql = self.seg( - f"LATERAL VIEW{' OUTER' if expression.args.get('outer') else ''}" - ) - return f"{op_sql}{self.sep()}{this}{table}{columns}" - - alias = self.sql(expression, "alias") - alias = f" AS {alias}" if alias else "" - - ordinality = expression.args.get("ordinality") or "" - if ordinality: - ordinality = f" WITH ORDINALITY{alias}" - alias = "" - - return f"{self.lateral_op(expression)} {this}{alias}{ordinality}" - - def limit_sql(self, expression: exp.Limit, top: bool = False) -> str: - this = self.sql(expression, "this") - - args = [ - self._simplify_unless_literal(e) if self.LIMIT_ONLY_LITERALS else e - for e in (expression.args.get(k) for k in ("offset", "expression")) - if e - ] - - args_sql = ", ".join(self.sql(e) for e in args) - args_sql = ( - f"({args_sql})" if top and any(not e.is_number for e in args) else args_sql - ) - expressions = self.expressions(expression, flat=True) - limit_options = self.sql(expression, "limit_options") - expressions = f" BY {expressions}" if expressions else "" - - return f"{this}{self.seg('TOP' if top else 'LIMIT')} {args_sql}{limit_options}{expressions}" - - def offset_sql(self, expression: exp.Offset) -> str: - this = self.sql(expression, "this") - value = expression.expression - value = ( - self._simplify_unless_literal(value) if self.LIMIT_ONLY_LITERALS else value - ) - expressions = self.expressions(expression, flat=True) - expressions = f" BY {expressions}" if expressions else "" - return f"{this}{self.seg('OFFSET')} {self.sql(value)}{expressions}" - - def setitem_sql(self, expression: exp.SetItem) -> str: - kind = self.sql(expression, "kind") - if not self.SET_ASSIGNMENT_REQUIRES_VARIABLE_KEYWORD and kind == "VARIABLE": - kind = "" - else: - kind = f"{kind} " if kind else "" - this = self.sql(expression, "this") - expressions = self.expressions(expression) - collate = self.sql(expression, "collate") - collate = f" COLLATE {collate}" if collate else "" - global_ = "GLOBAL " if expression.args.get("global_") else "" - return f"{global_}{kind}{this}{expressions}{collate}" - - def set_sql(self, expression: exp.Set) -> str: - expressions = f" {self.expressions(expression, flat=True)}" - tag = " TAG" if expression.args.get("tag") else "" - return f"{'UNSET' if expression.args.get('unset') else 'SET'}{tag}{expressions}" - - def queryband_sql(self, expression: exp.QueryBand) -> str: - this = self.sql(expression, "this") - update = " UPDATE" if expression.args.get("update") else "" - scope = self.sql(expression, "scope") - scope = f" FOR {scope}" if scope else "" - - return f"QUERY_BAND = {this}{update}{scope}" - - def pragma_sql(self, expression: exp.Pragma) -> str: - return f"PRAGMA {self.sql(expression, 'this')}" - - def lock_sql(self, expression: exp.Lock) -> str: - if not self.LOCKING_READS_SUPPORTED: - self.unsupported("Locking reads using 'FOR UPDATE/SHARE' are not supported") - return "" - - update = expression.args["update"] - key = expression.args.get("key") - if update: - lock_type = "FOR NO KEY UPDATE" if key else "FOR UPDATE" - else: - lock_type = "FOR KEY SHARE" if key else "FOR SHARE" - expressions = self.expressions(expression, flat=True) - expressions = f" OF {expressions}" if expressions else "" - wait = expression.args.get("wait") - - if wait is not None: - if isinstance(wait, exp.Literal): - wait = f" WAIT {self.sql(wait)}" - else: - wait = " NOWAIT" if wait else " SKIP LOCKED" - - return f"{lock_type}{expressions}{wait or ''}" - - def literal_sql(self, expression: exp.Literal) -> str: - text = expression.this or "" - if expression.is_string: - text = f"{self.dialect.QUOTE_START}{self.escape_str(text)}{self.dialect.QUOTE_END}" - return text - - def escape_str( - self, - text: str, - escape_backslash: bool = True, - delimiter: t.Optional[str] = None, - escaped_delimiter: t.Optional[str] = None, - ) -> str: - if self.dialect.ESCAPED_SEQUENCES: - to_escaped = self.dialect.ESCAPED_SEQUENCES - text = "".join( - to_escaped.get(ch, ch) if escape_backslash or ch != "\\" else ch - for ch in text - ) - - delimiter = delimiter or self.dialect.QUOTE_END - escaped_delimiter = escaped_delimiter or self._escaped_quote_end - - return self._replace_line_breaks(text).replace(delimiter, escaped_delimiter) - - def loaddata_sql(self, expression: exp.LoadData) -> str: - local = " LOCAL" if expression.args.get("local") else "" - inpath = f" INPATH {self.sql(expression, 'inpath')}" - overwrite = " OVERWRITE" if expression.args.get("overwrite") else "" - this = f" INTO TABLE {self.sql(expression, 'this')}" - partition = self.sql(expression, "partition") - partition = f" {partition}" if partition else "" - input_format = self.sql(expression, "input_format") - input_format = f" INPUTFORMAT {input_format}" if input_format else "" - serde = self.sql(expression, "serde") - serde = f" SERDE {serde}" if serde else "" - return ( - f"LOAD DATA{local}{inpath}{overwrite}{this}{partition}{input_format}{serde}" - ) - - def null_sql(self, *_) -> str: - return "NULL" - - def boolean_sql(self, expression: exp.Boolean) -> str: - return "TRUE" if expression.this else "FALSE" - - def booland_sql(self, expression: exp.Booland) -> str: - return f"(({self.sql(expression, 'this')}) AND ({self.sql(expression, 'expression')}))" - - def boolor_sql(self, expression: exp.Boolor) -> str: - return f"(({self.sql(expression, 'this')}) OR ({self.sql(expression, 'expression')}))" - - def order_sql(self, expression: exp.Order, flat: bool = False) -> str: - this = self.sql(expression, "this") - this = f"{this} " if this else this - siblings = "SIBLINGS " if expression.args.get("siblings") else "" - return self.op_expressions( - f"{this}ORDER {siblings}BY", expression, flat=this or flat - ) # type: ignore - - def withfill_sql(self, expression: exp.WithFill) -> str: - from_sql = self.sql(expression, "from_") - from_sql = f" FROM {from_sql}" if from_sql else "" - to_sql = self.sql(expression, "to") - to_sql = f" TO {to_sql}" if to_sql else "" - step_sql = self.sql(expression, "step") - step_sql = f" STEP {step_sql}" if step_sql else "" - interpolated_values = [ - f"{self.sql(e, 'alias')} AS {self.sql(e, 'this')}" - if isinstance(e, exp.Alias) - else self.sql(e, "this") - for e in expression.args.get("interpolate") or [] - ] - interpolate = ( - f" INTERPOLATE ({', '.join(interpolated_values)})" - if interpolated_values - else "" - ) - return f"WITH FILL{from_sql}{to_sql}{step_sql}{interpolate}" - - def cluster_sql(self, expression: exp.Cluster) -> str: - return self.op_expressions("CLUSTER BY", expression) - - def distribute_sql(self, expression: exp.Distribute) -> str: - return self.op_expressions("DISTRIBUTE BY", expression) - - def sort_sql(self, expression: exp.Sort) -> str: - return self.op_expressions("SORT BY", expression) - - def ordered_sql(self, expression: exp.Ordered) -> str: - desc = expression.args.get("desc") - asc = not desc - - nulls_first = expression.args.get("nulls_first") - nulls_last = not nulls_first - nulls_are_large = self.dialect.NULL_ORDERING == "nulls_are_large" - nulls_are_small = self.dialect.NULL_ORDERING == "nulls_are_small" - nulls_are_last = self.dialect.NULL_ORDERING == "nulls_are_last" - - this = self.sql(expression, "this") - - sort_order = " DESC" if desc else (" ASC" if desc is False else "") - nulls_sort_change = "" - if nulls_first and ( - (asc and nulls_are_large) or (desc and nulls_are_small) or nulls_are_last - ): - nulls_sort_change = " NULLS FIRST" - elif ( - nulls_last - and ((asc and nulls_are_small) or (desc and nulls_are_large)) - and not nulls_are_last - ): - nulls_sort_change = " NULLS LAST" - - # If the NULLS FIRST/LAST clause is unsupported, we add another sort key to simulate it - if nulls_sort_change and not self.NULL_ORDERING_SUPPORTED: - window = expression.find_ancestor(exp.Window, exp.Select) - if isinstance(window, exp.Window) and window.args.get("spec"): - self.unsupported( - f"'{nulls_sort_change.strip()}' translation not supported in window functions" - ) - nulls_sort_change = "" - elif self.NULL_ORDERING_SUPPORTED is False and ( - (asc and nulls_sort_change == " NULLS LAST") - or (desc and nulls_sort_change == " NULLS FIRST") - ): - # BigQuery does not allow these ordering/nulls combinations when used under - # an aggregation func or under a window containing one - ancestor = expression.find_ancestor(exp.AggFunc, exp.Window, exp.Select) - - if isinstance(ancestor, exp.Window): - ancestor = ancestor.this - if isinstance(ancestor, exp.AggFunc): - self.unsupported( - f"'{nulls_sort_change.strip()}' translation not supported for aggregate functions with {sort_order} sort order" - ) - nulls_sort_change = "" - elif self.NULL_ORDERING_SUPPORTED is None: - if expression.this.is_int: - self.unsupported( - f"'{nulls_sort_change.strip()}' translation not supported with positional ordering" - ) - elif not isinstance(expression.this, exp.Rand): - null_sort_order = ( - " DESC" if nulls_sort_change == " NULLS FIRST" else "" - ) - this = f"CASE WHEN {this} IS NULL THEN 1 ELSE 0 END{null_sort_order}, {this}" - nulls_sort_change = "" - - with_fill = self.sql(expression, "with_fill") - with_fill = f" {with_fill}" if with_fill else "" - - return f"{this}{sort_order}{nulls_sort_change}{with_fill}" - - def matchrecognizemeasure_sql(self, expression: exp.MatchRecognizeMeasure) -> str: - window_frame = self.sql(expression, "window_frame") - window_frame = f"{window_frame} " if window_frame else "" - - this = self.sql(expression, "this") - - return f"{window_frame}{this}" - - def matchrecognize_sql(self, expression: exp.MatchRecognize) -> str: - partition = self.partition_by_sql(expression) - order = self.sql(expression, "order") - measures = self.expressions(expression, key="measures") - measures = self.seg(f"MEASURES{self.seg(measures)}") if measures else "" - rows = self.sql(expression, "rows") - rows = self.seg(rows) if rows else "" - after = self.sql(expression, "after") - after = self.seg(after) if after else "" - pattern = self.sql(expression, "pattern") - pattern = self.seg(f"PATTERN ({pattern})") if pattern else "" - definition_sqls = [ - f"{self.sql(definition, 'alias')} AS {self.sql(definition, 'this')}" - for definition in expression.args.get("define", []) - ] - definitions = self.expressions(sqls=definition_sqls) - define = self.seg(f"DEFINE{self.seg(definitions)}") if definitions else "" - body = "".join( - ( - partition, - order, - measures, - rows, - after, - pattern, - define, - ) - ) - alias = self.sql(expression, "alias") - alias = f" {alias}" if alias else "" - return f"{self.seg('MATCH_RECOGNIZE')} {self.wrap(body)}{alias}" - - def query_modifiers(self, expression: exp.Expression, *sqls: str) -> str: - limit = expression.args.get("limit") - - if self.LIMIT_FETCH == "LIMIT" and isinstance(limit, exp.Fetch): - limit = exp.Limit(expression=exp.maybe_copy(limit.args.get("count"))) - elif self.LIMIT_FETCH == "FETCH" and isinstance(limit, exp.Limit): - limit = exp.Fetch(direction="FIRST", count=exp.maybe_copy(limit.expression)) - - return csv( - *sqls, - *[self.sql(join) for join in expression.args.get("joins") or []], - self.sql(expression, "match"), - *[self.sql(lateral) for lateral in expression.args.get("laterals") or []], - self.sql(expression, "prewhere"), - self.sql(expression, "where"), - self.sql(expression, "connect"), - self.sql(expression, "group"), - self.sql(expression, "having"), - *[ - gen(self, expression) - for gen in self.AFTER_HAVING_MODIFIER_TRANSFORMS.values() - ], - self.sql(expression, "order"), - *self.offset_limit_modifiers( - expression, isinstance(limit, exp.Fetch), limit - ), - *self.after_limit_modifiers(expression), - self.options_modifier(expression), - self.for_modifiers(expression), - sep="", - ) - - def options_modifier(self, expression: exp.Expression) -> str: - options = self.expressions(expression, key="options") - return f" {options}" if options else "" - - def for_modifiers(self, expression: exp.Expression) -> str: - for_modifiers = self.expressions(expression, key="for_") - return f"{self.sep()}FOR XML{self.seg(for_modifiers)}" if for_modifiers else "" - - def queryoption_sql(self, expression: exp.QueryOption) -> str: - self.unsupported("Unsupported query option.") - return "" - - def offset_limit_modifiers( - self, - expression: exp.Expression, - fetch: bool, - limit: t.Optional[exp.Fetch | exp.Limit], - ) -> t.List[str]: - return [ - self.sql(expression, "offset") if fetch else self.sql(limit), - self.sql(limit) if fetch else self.sql(expression, "offset"), - ] - - def after_limit_modifiers(self, expression: exp.Expression) -> t.List[str]: - locks = self.expressions(expression, key="locks", sep=" ") - locks = f" {locks}" if locks else "" - return [locks, self.sql(expression, "sample")] - - def select_sql(self, expression: exp.Select) -> str: - into = expression.args.get("into") - if not self.SUPPORTS_SELECT_INTO and into: - into.pop() - - hint = self.sql(expression, "hint") - distinct = self.sql(expression, "distinct") - distinct = f" {distinct}" if distinct else "" - kind = self.sql(expression, "kind") - - limit = expression.args.get("limit") - if isinstance(limit, exp.Limit) and self.LIMIT_IS_TOP: - top = self.limit_sql(limit, top=True) - limit.pop() - else: - top = "" - - expressions = self.expressions(expression) - - if kind: - if kind in self.SELECT_KINDS: - kind = f" AS {kind}" - else: - if kind == "STRUCT": - expressions = self.expressions( - sqls=[ - self.sql( - exp.Struct( - expressions=[ - exp.PropertyEQ( - this=e.args.get("alias"), expression=e.this - ) - if isinstance(e, exp.Alias) - else e - for e in expression.expressions - ] - ) - ) - ] - ) - kind = "" - - operation_modifiers = self.expressions( - expression, key="operation_modifiers", sep=" " - ) - operation_modifiers = ( - f"{self.sep()}{operation_modifiers}" if operation_modifiers else "" - ) - - # We use LIMIT_IS_TOP as a proxy for whether DISTINCT should go first because tsql and Teradata - # are the only dialects that use LIMIT_IS_TOP and both place DISTINCT first. - top_distinct = ( - f"{distinct}{hint}{top}" if self.LIMIT_IS_TOP else f"{top}{hint}{distinct}" - ) - expressions = f"{self.sep()}{expressions}" if expressions else expressions - sql = self.query_modifiers( - expression, - f"SELECT{top_distinct}{operation_modifiers}{kind}{expressions}", - self.sql(expression, "into", comment=False), - self.sql(expression, "from_", comment=False), - ) - - # If both the CTE and SELECT clauses have comments, generate the latter earlier - if expression.args.get("with_"): - sql = self.maybe_comment(sql, expression) - expression.pop_comments() - - sql = self.prepend_ctes(expression, sql) - - if not self.SUPPORTS_SELECT_INTO and into: - if into.args.get("temporary"): - table_kind = " TEMPORARY" - elif self.SUPPORTS_UNLOGGED_TABLES and into.args.get("unlogged"): - table_kind = " UNLOGGED" - else: - table_kind = "" - sql = f"CREATE{table_kind} TABLE {self.sql(into.this)} AS {sql}" - - return sql - - def schema_sql(self, expression: exp.Schema) -> str: - this = self.sql(expression, "this") - sql = self.schema_columns_sql(expression) - return f"{this} {sql}" if this and sql else this or sql - - def schema_columns_sql(self, expression: exp.Schema) -> str: - if expression.expressions: - return ( - f"({self.sep('')}{self.expressions(expression)}{self.seg(')', sep='')}" - ) - return "" - - def star_sql(self, expression: exp.Star) -> str: - except_ = self.expressions(expression, key="except_", flat=True) - except_ = f"{self.seg(self.STAR_EXCEPT)} ({except_})" if except_ else "" - replace = self.expressions(expression, key="replace", flat=True) - replace = f"{self.seg('REPLACE')} ({replace})" if replace else "" - rename = self.expressions(expression, key="rename", flat=True) - rename = f"{self.seg('RENAME')} ({rename})" if rename else "" - return f"*{except_}{replace}{rename}" - - def parameter_sql(self, expression: exp.Parameter) -> str: - this = self.sql(expression, "this") - return f"{self.PARAMETER_TOKEN}{this}" - - def sessionparameter_sql(self, expression: exp.SessionParameter) -> str: - this = self.sql(expression, "this") - kind = expression.text("kind") - if kind: - kind = f"{kind}." - return f"@@{kind}{this}" - - def placeholder_sql(self, expression: exp.Placeholder) -> str: - return ( - f"{self.NAMED_PLACEHOLDER_TOKEN}{expression.name}" - if expression.this - else "?" - ) - - def subquery_sql(self, expression: exp.Subquery, sep: str = " AS ") -> str: - alias = self.sql(expression, "alias") - alias = f"{sep}{alias}" if alias else "" - sample = self.sql(expression, "sample") - if self.dialect.ALIAS_POST_TABLESAMPLE and sample: - alias = f"{sample}{alias}" - - # Set to None so it's not generated again by self.query_modifiers() - expression.set("sample", None) - - pivots = self.expressions(expression, key="pivots", sep="", flat=True) - sql = self.query_modifiers(expression, self.wrap(expression), alias, pivots) - return self.prepend_ctes(expression, sql) - - def qualify_sql(self, expression: exp.Qualify) -> str: - this = self.indent(self.sql(expression, "this")) - return f"{self.seg('QUALIFY')}{self.sep()}{this}" - - def unnest_sql(self, expression: exp.Unnest) -> str: - args = self.expressions(expression, flat=True) - - alias = expression.args.get("alias") - offset = expression.args.get("offset") - - if self.UNNEST_WITH_ORDINALITY: - if alias and isinstance(offset, exp.Expression): - alias.append("columns", offset) - - if alias and self.dialect.UNNEST_COLUMN_ONLY: - columns = alias.columns - alias = self.sql(columns[0]) if columns else "" - else: - alias = self.sql(alias) - - alias = f" AS {alias}" if alias else alias - if self.UNNEST_WITH_ORDINALITY: - suffix = f" WITH ORDINALITY{alias}" if offset else alias - else: - if isinstance(offset, exp.Expression): - suffix = f"{alias} WITH OFFSET AS {self.sql(offset)}" - elif offset: - suffix = f"{alias} WITH OFFSET" - else: - suffix = alias - - return f"UNNEST({args}){suffix}" - - def prewhere_sql(self, expression: exp.PreWhere) -> str: - return "" - - def where_sql(self, expression: exp.Where) -> str: - this = self.indent(self.sql(expression, "this")) - return f"{self.seg('WHERE')}{self.sep()}{this}" - - def window_sql(self, expression: exp.Window) -> str: - this = self.sql(expression, "this") - partition = self.partition_by_sql(expression) - order = expression.args.get("order") - order = self.order_sql(order, flat=True) if order else "" - spec = self.sql(expression, "spec") - alias = self.sql(expression, "alias") - over = self.sql(expression, "over") or "OVER" - - this = f"{this} {'AS' if expression.arg_key == 'windows' else over}" - - first = expression.args.get("first") - if first is None: - first = "" - else: - first = "FIRST" if first else "LAST" - - if not partition and not order and not spec and alias: - return f"{this} {alias}" - - args = self.format_args( - *[arg for arg in (alias, first, partition, order, spec) if arg], sep=" " - ) - return f"{this} ({args})" - - def partition_by_sql(self, expression: exp.Window | exp.MatchRecognize) -> str: - partition = self.expressions(expression, key="partition_by", flat=True) - return f"PARTITION BY {partition}" if partition else "" - - def windowspec_sql(self, expression: exp.WindowSpec) -> str: - kind = self.sql(expression, "kind") - start = csv( - self.sql(expression, "start"), self.sql(expression, "start_side"), sep=" " - ) - end = ( - csv(self.sql(expression, "end"), self.sql(expression, "end_side"), sep=" ") - or "CURRENT ROW" - ) - - window_spec = f"{kind} BETWEEN {start} AND {end}" - - exclude = self.sql(expression, "exclude") - if exclude: - if self.SUPPORTS_WINDOW_EXCLUDE: - window_spec += f" EXCLUDE {exclude}" - else: - self.unsupported("EXCLUDE clause is not supported in the WINDOW clause") - - return window_spec - - def withingroup_sql(self, expression: exp.WithinGroup) -> str: - this = self.sql(expression, "this") - expression_sql = self.sql(expression, "expression")[ - 1: - ] # order has a leading space - return f"{this} WITHIN GROUP ({expression_sql})" - - def between_sql(self, expression: exp.Between) -> str: - this = self.sql(expression, "this") - low = self.sql(expression, "low") - high = self.sql(expression, "high") - symmetric = expression.args.get("symmetric") - - if symmetric and not self.SUPPORTS_BETWEEN_FLAGS: - return ( - f"({this} BETWEEN {low} AND {high} OR {this} BETWEEN {high} AND {low})" - ) - - flag = ( - " SYMMETRIC" - if symmetric - else " ASYMMETRIC" - if symmetric is False and self.SUPPORTS_BETWEEN_FLAGS - else "" # silently drop ASYMMETRIC – semantics identical - ) - return f"{this} BETWEEN{flag} {low} AND {high}" - - def bracket_offset_expressions( - self, expression: exp.Bracket, index_offset: t.Optional[int] = None - ) -> t.List[exp.Expression]: - return apply_index_offset( - expression.this, - expression.expressions, - (index_offset or self.dialect.INDEX_OFFSET) - - expression.args.get("offset", 0), - dialect=self.dialect, - ) - - def bracket_sql(self, expression: exp.Bracket) -> str: - expressions = self.bracket_offset_expressions(expression) - expressions_sql = ", ".join(self.sql(e) for e in expressions) - return f"{self.sql(expression, 'this')}[{expressions_sql}]" - - def all_sql(self, expression: exp.All) -> str: - this = self.sql(expression, "this") - if not isinstance(expression.this, (exp.Tuple, exp.Paren)): - this = self.wrap(this) - return f"ALL {this}" - - def any_sql(self, expression: exp.Any) -> str: - this = self.sql(expression, "this") - if isinstance(expression.this, (*exp.UNWRAPPED_QUERIES, exp.Paren)): - if isinstance(expression.this, exp.UNWRAPPED_QUERIES): - this = self.wrap(this) - return f"ANY{this}" - return f"ANY {this}" - - def exists_sql(self, expression: exp.Exists) -> str: - return f"EXISTS{self.wrap(expression)}" - - def case_sql(self, expression: exp.Case) -> str: - this = self.sql(expression, "this") - statements = [f"CASE {this}" if this else "CASE"] - - for e in expression.args["ifs"]: - statements.append(f"WHEN {self.sql(e, 'this')}") - statements.append(f"THEN {self.sql(e, 'true')}") - - default = self.sql(expression, "default") - - if default: - statements.append(f"ELSE {default}") - - statements.append("END") - - if self.pretty and self.too_wide(statements): - return self.indent("\n".join(statements), skip_first=True, skip_last=True) - - return " ".join(statements) - - def constraint_sql(self, expression: exp.Constraint) -> str: - this = self.sql(expression, "this") - expressions = self.expressions(expression, flat=True) - return f"CONSTRAINT {this} {expressions}" - - def nextvaluefor_sql(self, expression: exp.NextValueFor) -> str: - order = expression.args.get("order") - order = f" OVER ({self.order_sql(order, flat=True)})" if order else "" - return f"NEXT VALUE FOR {self.sql(expression, 'this')}{order}" - - def extract_sql(self, expression: exp.Extract) -> str: - from bigframes_vendored.sqlglot.dialects.dialect import map_date_part - - this = ( - map_date_part(expression.this, self.dialect) - if self.NORMALIZE_EXTRACT_DATE_PARTS - else expression.this - ) - this_sql = self.sql(this) if self.EXTRACT_ALLOWS_QUOTES else this.name - expression_sql = self.sql(expression, "expression") - - return f"EXTRACT({this_sql} FROM {expression_sql})" - - def trim_sql(self, expression: exp.Trim) -> str: - trim_type = self.sql(expression, "position") - - if trim_type == "LEADING": - func_name = "LTRIM" - elif trim_type == "TRAILING": - func_name = "RTRIM" - else: - func_name = "TRIM" - - return self.func(func_name, expression.this, expression.expression) - - def convert_concat_args( - self, expression: exp.Concat | exp.ConcatWs - ) -> t.List[exp.Expression]: - args = expression.expressions - if isinstance(expression, exp.ConcatWs): - args = args[1:] # Skip the delimiter - - if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): - args = [exp.cast(e, exp.DataType.Type.TEXT) for e in args] - - if not self.dialect.CONCAT_COALESCE and expression.args.get("coalesce"): - - def _wrap_with_coalesce(e: exp.Expression) -> exp.Expression: - if not e.type: - from bigframes_vendored.sqlglot.optimizer.annotate_types import ( - annotate_types, - ) - - e = annotate_types(e, dialect=self.dialect) - - if e.is_string or e.is_type(exp.DataType.Type.ARRAY): - return e - - return exp.func("coalesce", e, exp.Literal.string("")) - - args = [_wrap_with_coalesce(e) for e in args] - - return args - - def concat_sql(self, expression: exp.Concat) -> str: - if self.dialect.CONCAT_COALESCE and not expression.args.get("coalesce"): - # Dialect's CONCAT function coalesces NULLs to empty strings, but the expression does not. - # Transpile to double pipe operators, which typically returns NULL if any args are NULL - # instead of coalescing them to empty string. - from bigframes_vendored.sqlglot.dialects.dialect import concat_to_dpipe_sql - - return concat_to_dpipe_sql(self, expression) - - expressions = self.convert_concat_args(expression) - - # Some dialects don't allow a single-argument CONCAT call - if not self.SUPPORTS_SINGLE_ARG_CONCAT and len(expressions) == 1: - return self.sql(expressions[0]) - - return self.func("CONCAT", *expressions) - - def concatws_sql(self, expression: exp.ConcatWs) -> str: - return self.func( - "CONCAT_WS", - seq_get(expression.expressions, 0), - *self.convert_concat_args(expression), - ) - - def check_sql(self, expression: exp.Check) -> str: - this = self.sql(expression, key="this") - return f"CHECK ({this})" - - def foreignkey_sql(self, expression: exp.ForeignKey) -> str: - expressions = self.expressions(expression, flat=True) - expressions = f" ({expressions})" if expressions else "" - reference = self.sql(expression, "reference") - reference = f" {reference}" if reference else "" - delete = self.sql(expression, "delete") - delete = f" ON DELETE {delete}" if delete else "" - update = self.sql(expression, "update") - update = f" ON UPDATE {update}" if update else "" - options = self.expressions(expression, key="options", flat=True, sep=" ") - options = f" {options}" if options else "" - return f"FOREIGN KEY{expressions}{reference}{delete}{update}{options}" - - def primarykey_sql(self, expression: exp.PrimaryKey) -> str: - this = self.sql(expression, "this") - this = f" {this}" if this else "" - expressions = self.expressions(expression, flat=True) - include = self.sql(expression, "include") - options = self.expressions(expression, key="options", flat=True, sep=" ") - options = f" {options}" if options else "" - return f"PRIMARY KEY{this} ({expressions}){include}{options}" - - def if_sql(self, expression: exp.If) -> str: - return self.case_sql( - exp.Case(ifs=[expression], default=expression.args.get("false")) - ) - - def matchagainst_sql(self, expression: exp.MatchAgainst) -> str: - if self.MATCH_AGAINST_TABLE_PREFIX: - expressions = [] - for expr in expression.expressions: - if isinstance(expr, exp.Table): - expressions.append(f"TABLE {self.sql(expr)}") - else: - expressions.append(expr) - else: - expressions = expression.expressions - - modifier = expression.args.get("modifier") - modifier = f" {modifier}" if modifier else "" - return f"{self.func('MATCH', *expressions)} AGAINST({self.sql(expression, 'this')}{modifier})" - - def jsonkeyvalue_sql(self, expression: exp.JSONKeyValue) -> str: - return f"{self.sql(expression, 'this')}{self.JSON_KEY_VALUE_PAIR_SEP} {self.sql(expression, 'expression')}" - - def jsonpath_sql(self, expression: exp.JSONPath) -> str: - path = self.expressions(expression, sep="", flat=True).lstrip(".") - - if expression.args.get("escape"): - path = self.escape_str(path) - - if self.QUOTE_JSON_PATH: - path = f"{self.dialect.QUOTE_START}{path}{self.dialect.QUOTE_END}" - - return path - - def json_path_part(self, expression: int | str | exp.JSONPathPart) -> str: - if isinstance(expression, exp.JSONPathPart): - transform = self.TRANSFORMS.get(expression.__class__) - if not callable(transform): - self.unsupported( - f"Unsupported JSONPathPart type {expression.__class__.__name__}" - ) - return "" - - return transform(self, expression) - - if isinstance(expression, int): - return str(expression) - - if ( - self._quote_json_path_key_using_brackets - and self.JSON_PATH_SINGLE_QUOTE_ESCAPE - ): - escaped = expression.replace("'", "\\'") - escaped = f"\\'{expression}\\'" - else: - escaped = expression.replace('"', '\\"') - escaped = f'"{escaped}"' - - return escaped - - def formatjson_sql(self, expression: exp.FormatJson) -> str: - return f"{self.sql(expression, 'this')} FORMAT JSON" - - def formatphrase_sql(self, expression: exp.FormatPhrase) -> str: - # Output the Teradata column FORMAT override. - # https://docs.teradata.com/r/Enterprise_IntelliFlex_VMware/SQL-Data-Types-and-Literals/Data-Type-Formats-and-Format-Phrases/FORMAT - this = self.sql(expression, "this") - fmt = self.sql(expression, "format") - return f"{this} (FORMAT {fmt})" - - def jsonobject_sql(self, expression: exp.JSONObject | exp.JSONObjectAgg) -> str: - null_handling = expression.args.get("null_handling") - null_handling = f" {null_handling}" if null_handling else "" - - unique_keys = expression.args.get("unique_keys") - if unique_keys is not None: - unique_keys = f" {'WITH' if unique_keys else 'WITHOUT'} UNIQUE KEYS" - else: - unique_keys = "" - - return_type = self.sql(expression, "return_type") - return_type = f" RETURNING {return_type}" if return_type else "" - encoding = self.sql(expression, "encoding") - encoding = f" ENCODING {encoding}" if encoding else "" - - return self.func( - "JSON_OBJECT" - if isinstance(expression, exp.JSONObject) - else "JSON_OBJECTAGG", - *expression.expressions, - suffix=f"{null_handling}{unique_keys}{return_type}{encoding})", - ) - - def jsonobjectagg_sql(self, expression: exp.JSONObjectAgg) -> str: - return self.jsonobject_sql(expression) - - def jsonarray_sql(self, expression: exp.JSONArray) -> str: - null_handling = expression.args.get("null_handling") - null_handling = f" {null_handling}" if null_handling else "" - return_type = self.sql(expression, "return_type") - return_type = f" RETURNING {return_type}" if return_type else "" - strict = " STRICT" if expression.args.get("strict") else "" - return self.func( - "JSON_ARRAY", - *expression.expressions, - suffix=f"{null_handling}{return_type}{strict})", - ) - - def jsonarrayagg_sql(self, expression: exp.JSONArrayAgg) -> str: - this = self.sql(expression, "this") - order = self.sql(expression, "order") - null_handling = expression.args.get("null_handling") - null_handling = f" {null_handling}" if null_handling else "" - return_type = self.sql(expression, "return_type") - return_type = f" RETURNING {return_type}" if return_type else "" - strict = " STRICT" if expression.args.get("strict") else "" - return self.func( - "JSON_ARRAYAGG", - this, - suffix=f"{order}{null_handling}{return_type}{strict})", - ) - - def jsoncolumndef_sql(self, expression: exp.JSONColumnDef) -> str: - path = self.sql(expression, "path") - path = f" PATH {path}" if path else "" - nested_schema = self.sql(expression, "nested_schema") - - if nested_schema: - return f"NESTED{path} {nested_schema}" - - this = self.sql(expression, "this") - kind = self.sql(expression, "kind") - kind = f" {kind}" if kind else "" - - ordinality = " FOR ORDINALITY" if expression.args.get("ordinality") else "" - return f"{this}{kind}{path}{ordinality}" - - def jsonschema_sql(self, expression: exp.JSONSchema) -> str: - return self.func("COLUMNS", *expression.expressions) - - def jsontable_sql(self, expression: exp.JSONTable) -> str: - this = self.sql(expression, "this") - path = self.sql(expression, "path") - path = f", {path}" if path else "" - error_handling = expression.args.get("error_handling") - error_handling = f" {error_handling}" if error_handling else "" - empty_handling = expression.args.get("empty_handling") - empty_handling = f" {empty_handling}" if empty_handling else "" - schema = self.sql(expression, "schema") - return self.func( - "JSON_TABLE", - this, - suffix=f"{path}{error_handling}{empty_handling} {schema})", - ) - - def openjsoncolumndef_sql(self, expression: exp.OpenJSONColumnDef) -> str: - this = self.sql(expression, "this") - kind = self.sql(expression, "kind") - path = self.sql(expression, "path") - path = f" {path}" if path else "" - as_json = " AS JSON" if expression.args.get("as_json") else "" - return f"{this} {kind}{path}{as_json}" - - def openjson_sql(self, expression: exp.OpenJSON) -> str: - this = self.sql(expression, "this") - path = self.sql(expression, "path") - path = f", {path}" if path else "" - expressions = self.expressions(expression) - with_ = ( - f" WITH ({self.seg(self.indent(expressions), sep='')}{self.seg(')', sep='')}" - if expressions - else "" - ) - return f"OPENJSON({this}{path}){with_}" - - def in_sql(self, expression: exp.In) -> str: - query = expression.args.get("query") - unnest = expression.args.get("unnest") - field = expression.args.get("field") - is_global = " GLOBAL" if expression.args.get("is_global") else "" - - if query: - in_sql = self.sql(query) - elif unnest: - in_sql = self.in_unnest_op(unnest) - elif field: - in_sql = self.sql(field) - else: - in_sql = f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})" - - return f"{self.sql(expression, 'this')}{is_global} IN {in_sql}" - - def in_unnest_op(self, unnest: exp.Unnest) -> str: - return f"(SELECT {self.sql(unnest)})" - - def interval_sql(self, expression: exp.Interval) -> str: - unit_expression = expression.args.get("unit") - unit = self.sql(unit_expression) if unit_expression else "" - if not self.INTERVAL_ALLOWS_PLURAL_FORM: - unit = self.TIME_PART_SINGULARS.get(unit, unit) - unit = f" {unit}" if unit else "" - - if self.SINGLE_STRING_INTERVAL: - this = expression.this.name if expression.this else "" - if this: - if unit_expression and isinstance(unit_expression, exp.IntervalSpan): - return f"INTERVAL '{this}'{unit}" - return f"INTERVAL '{this}{unit}'" - return f"INTERVAL{unit}" - - this = self.sql(expression, "this") - if this: - unwrapped = isinstance(expression.this, self.UNWRAPPED_INTERVAL_VALUES) - this = f" {this}" if unwrapped else f" ({this})" - - return f"INTERVAL{this}{unit}" - - def return_sql(self, expression: exp.Return) -> str: - return f"RETURN {self.sql(expression, 'this')}" - - def reference_sql(self, expression: exp.Reference) -> str: - this = self.sql(expression, "this") - expressions = self.expressions(expression, flat=True) - expressions = f"({expressions})" if expressions else "" - options = self.expressions(expression, key="options", flat=True, sep=" ") - options = f" {options}" if options else "" - return f"REFERENCES {this}{expressions}{options}" - - def anonymous_sql(self, expression: exp.Anonymous) -> str: - # We don't normalize qualified functions such as a.b.foo(), because they can be case-sensitive - parent = expression.parent - is_qualified = isinstance(parent, exp.Dot) and expression is parent.expression - return self.func( - self.sql(expression, "this"), - *expression.expressions, - normalize=not is_qualified, - ) - - def paren_sql(self, expression: exp.Paren) -> str: - sql = self.seg(self.indent(self.sql(expression, "this")), sep="") - return f"({sql}{self.seg(')', sep='')}" - - def neg_sql(self, expression: exp.Neg) -> str: - # This makes sure we don't convert "- - 5" to "--5", which is a comment - this_sql = self.sql(expression, "this") - sep = " " if this_sql[0] == "-" else "" - return f"-{sep}{this_sql}" - - def not_sql(self, expression: exp.Not) -> str: - return f"NOT {self.sql(expression, 'this')}" - - def alias_sql(self, expression: exp.Alias) -> str: - alias = self.sql(expression, "alias") - alias = f" AS {alias}" if alias else "" - return f"{self.sql(expression, 'this')}{alias}" - - def pivotalias_sql(self, expression: exp.PivotAlias) -> str: - alias = expression.args["alias"] - - parent = expression.parent - pivot = parent and parent.parent - - if isinstance(pivot, exp.Pivot) and pivot.unpivot: - identifier_alias = isinstance(alias, exp.Identifier) - literal_alias = isinstance(alias, exp.Literal) - - if identifier_alias and not self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: - alias.replace(exp.Literal.string(alias.output_name)) - elif ( - not identifier_alias - and literal_alias - and self.UNPIVOT_ALIASES_ARE_IDENTIFIERS - ): - alias.replace(exp.to_identifier(alias.output_name)) - - return self.alias_sql(expression) - - def aliases_sql(self, expression: exp.Aliases) -> str: - return f"{self.sql(expression, 'this')} AS ({self.expressions(expression, flat=True)})" - - def atindex_sql(self, expression: exp.AtTimeZone) -> str: - this = self.sql(expression, "this") - index = self.sql(expression, "expression") - return f"{this} AT {index}" - - def attimezone_sql(self, expression: exp.AtTimeZone) -> str: - this = self.sql(expression, "this") - zone = self.sql(expression, "zone") - return f"{this} AT TIME ZONE {zone}" - - def fromtimezone_sql(self, expression: exp.FromTimeZone) -> str: - this = self.sql(expression, "this") - zone = self.sql(expression, "zone") - return f"{this} AT TIME ZONE {zone} AT TIME ZONE 'UTC'" - - def add_sql(self, expression: exp.Add) -> str: - return self.binary(expression, "+") - - def and_sql( - self, - expression: exp.And, - stack: t.Optional[t.List[str | exp.Expression]] = None, - ) -> str: - return self.connector_sql(expression, "AND", stack) - - def or_sql( - self, expression: exp.Or, stack: t.Optional[t.List[str | exp.Expression]] = None - ) -> str: - return self.connector_sql(expression, "OR", stack) - - def xor_sql( - self, - expression: exp.Xor, - stack: t.Optional[t.List[str | exp.Expression]] = None, - ) -> str: - return self.connector_sql(expression, "XOR", stack) - - def connector_sql( - self, - expression: exp.Connector, - op: str, - stack: t.Optional[t.List[str | exp.Expression]] = None, - ) -> str: - if stack is not None: - if expression.expressions: - stack.append(self.expressions(expression, sep=f" {op} ")) - else: - stack.append(expression.right) - if expression.comments and self.comments: - for comment in expression.comments: - if comment: - op += f" /*{self.sanitize_comment(comment)}*/" - stack.extend((op, expression.left)) - return op - - stack = [expression] - sqls: t.List[str] = [] - ops = set() - - while stack: - node = stack.pop() - if isinstance(node, exp.Connector): - ops.add(getattr(self, f"{node.key}_sql")(node, stack)) - else: - sql = self.sql(node) - if sqls and sqls[-1] in ops: - sqls[-1] += f" {sql}" - else: - sqls.append(sql) - - sep = "\n" if self.pretty and self.too_wide(sqls) else " " - return sep.join(sqls) - - def bitwiseand_sql(self, expression: exp.BitwiseAnd) -> str: - return self.binary(expression, "&") - - def bitwiseleftshift_sql(self, expression: exp.BitwiseLeftShift) -> str: - return self.binary(expression, "<<") - - def bitwisenot_sql(self, expression: exp.BitwiseNot) -> str: - return f"~{self.sql(expression, 'this')}" - - def bitwiseor_sql(self, expression: exp.BitwiseOr) -> str: - return self.binary(expression, "|") - - def bitwiserightshift_sql(self, expression: exp.BitwiseRightShift) -> str: - return self.binary(expression, ">>") - - def bitwisexor_sql(self, expression: exp.BitwiseXor) -> str: - return self.binary(expression, "^") - - def cast_sql( - self, expression: exp.Cast, safe_prefix: t.Optional[str] = None - ) -> str: - format_sql = self.sql(expression, "format") - format_sql = f" FORMAT {format_sql}" if format_sql else "" - to_sql = self.sql(expression, "to") - to_sql = f" {to_sql}" if to_sql else "" - action = self.sql(expression, "action") - action = f" {action}" if action else "" - default = self.sql(expression, "default") - default = f" DEFAULT {default} ON CONVERSION ERROR" if default else "" - return f"{safe_prefix or ''}CAST({self.sql(expression, 'this')} AS{to_sql}{default}{format_sql}{action})" - - # Base implementation that excludes safe, zone, and target_type metadata args - def strtotime_sql(self, expression: exp.StrToTime) -> str: - return self.func("STR_TO_TIME", expression.this, expression.args.get("format")) - - def currentdate_sql(self, expression: exp.CurrentDate) -> str: - zone = self.sql(expression, "this") - return f"CURRENT_DATE({zone})" if zone else "CURRENT_DATE" - - def collate_sql(self, expression: exp.Collate) -> str: - if self.COLLATE_IS_FUNC: - return self.function_fallback_sql(expression) - return self.binary(expression, "COLLATE") - - def command_sql(self, expression: exp.Command) -> str: - return f"{self.sql(expression, 'this')} {expression.text('expression').strip()}" - - def comment_sql(self, expression: exp.Comment) -> str: - this = self.sql(expression, "this") - kind = expression.args["kind"] - materialized = " MATERIALIZED" if expression.args.get("materialized") else "" - exists_sql = " IF EXISTS " if expression.args.get("exists") else " " - expression_sql = self.sql(expression, "expression") - return f"COMMENT{exists_sql}ON{materialized} {kind} {this} IS {expression_sql}" - - def mergetreettlaction_sql(self, expression: exp.MergeTreeTTLAction) -> str: - this = self.sql(expression, "this") - delete = " DELETE" if expression.args.get("delete") else "" - recompress = self.sql(expression, "recompress") - recompress = f" RECOMPRESS {recompress}" if recompress else "" - to_disk = self.sql(expression, "to_disk") - to_disk = f" TO DISK {to_disk}" if to_disk else "" - to_volume = self.sql(expression, "to_volume") - to_volume = f" TO VOLUME {to_volume}" if to_volume else "" - return f"{this}{delete}{recompress}{to_disk}{to_volume}" - - def mergetreettl_sql(self, expression: exp.MergeTreeTTL) -> str: - where = self.sql(expression, "where") - group = self.sql(expression, "group") - aggregates = self.expressions(expression, key="aggregates") - aggregates = self.seg("SET") + self.seg(aggregates) if aggregates else "" - - if not (where or group or aggregates) and len(expression.expressions) == 1: - return f"TTL {self.expressions(expression, flat=True)}" - - return f"TTL{self.seg(self.expressions(expression))}{where}{group}{aggregates}" - - def transaction_sql(self, expression: exp.Transaction) -> str: - modes = self.expressions(expression, key="modes") - modes = f" {modes}" if modes else "" - return f"BEGIN{modes}" - - def commit_sql(self, expression: exp.Commit) -> str: - chain = expression.args.get("chain") - if chain is not None: - chain = " AND CHAIN" if chain else " AND NO CHAIN" - - return f"COMMIT{chain or ''}" - - def rollback_sql(self, expression: exp.Rollback) -> str: - savepoint = expression.args.get("savepoint") - savepoint = f" TO {savepoint}" if savepoint else "" - return f"ROLLBACK{savepoint}" - - def altercolumn_sql(self, expression: exp.AlterColumn) -> str: - this = self.sql(expression, "this") - - dtype = self.sql(expression, "dtype") - if dtype: - collate = self.sql(expression, "collate") - collate = f" COLLATE {collate}" if collate else "" - using = self.sql(expression, "using") - using = f" USING {using}" if using else "" - alter_set_type = self.ALTER_SET_TYPE + " " if self.ALTER_SET_TYPE else "" - return f"ALTER COLUMN {this} {alter_set_type}{dtype}{collate}{using}" - - default = self.sql(expression, "default") - if default: - return f"ALTER COLUMN {this} SET DEFAULT {default}" - - comment = self.sql(expression, "comment") - if comment: - return f"ALTER COLUMN {this} COMMENT {comment}" - - visible = expression.args.get("visible") - if visible: - return f"ALTER COLUMN {this} SET {visible}" - - allow_null = expression.args.get("allow_null") - drop = expression.args.get("drop") - - if not drop and not allow_null: - self.unsupported("Unsupported ALTER COLUMN syntax") - - if allow_null is not None: - keyword = "DROP" if drop else "SET" - return f"ALTER COLUMN {this} {keyword} NOT NULL" - - return f"ALTER COLUMN {this} DROP DEFAULT" - - def alterindex_sql(self, expression: exp.AlterIndex) -> str: - this = self.sql(expression, "this") - - visible = expression.args.get("visible") - visible_sql = "VISIBLE" if visible else "INVISIBLE" - - return f"ALTER INDEX {this} {visible_sql}" - - def alterdiststyle_sql(self, expression: exp.AlterDistStyle) -> str: - this = self.sql(expression, "this") - if not isinstance(expression.this, exp.Var): - this = f"KEY DISTKEY {this}" - return f"ALTER DISTSTYLE {this}" - - def altersortkey_sql(self, expression: exp.AlterSortKey) -> str: - compound = " COMPOUND" if expression.args.get("compound") else "" - this = self.sql(expression, "this") - expressions = self.expressions(expression, flat=True) - expressions = f"({expressions})" if expressions else "" - return f"ALTER{compound} SORTKEY {this or expressions}" - - def alterrename_sql( - self, expression: exp.AlterRename, include_to: bool = True - ) -> str: - if not self.RENAME_TABLE_WITH_DB: - # Remove db from tables - expression = expression.transform( - lambda n: exp.table_(n.this) if isinstance(n, exp.Table) else n - ).assert_is(exp.AlterRename) - this = self.sql(expression, "this") - to_kw = " TO" if include_to else "" - return f"RENAME{to_kw} {this}" - - def renamecolumn_sql(self, expression: exp.RenameColumn) -> str: - exists = " IF EXISTS" if expression.args.get("exists") else "" - old_column = self.sql(expression, "this") - new_column = self.sql(expression, "to") - return f"RENAME COLUMN{exists} {old_column} TO {new_column}" - - def alterset_sql(self, expression: exp.AlterSet) -> str: - exprs = self.expressions(expression, flat=True) - if self.ALTER_SET_WRAPPED: - exprs = f"({exprs})" - - return f"SET {exprs}" - - def alter_sql(self, expression: exp.Alter) -> str: - actions = expression.args["actions"] - - if not self.dialect.ALTER_TABLE_ADD_REQUIRED_FOR_EACH_COLUMN and isinstance( - actions[0], exp.ColumnDef - ): - actions_sql = self.expressions(expression, key="actions", flat=True) - actions_sql = f"ADD {actions_sql}" - else: - actions_list = [] - for action in actions: - if isinstance(action, (exp.ColumnDef, exp.Schema)): - action_sql = self.add_column_sql(action) - else: - action_sql = self.sql(action) - if isinstance(action, exp.Query): - action_sql = f"AS {action_sql}" - - actions_list.append(action_sql) - - actions_sql = self.format_args(*actions_list).lstrip("\n") - - exists = " IF EXISTS" if expression.args.get("exists") else "" - on_cluster = self.sql(expression, "cluster") - on_cluster = f" {on_cluster}" if on_cluster else "" - only = " ONLY" if expression.args.get("only") else "" - options = self.expressions(expression, key="options") - options = f", {options}" if options else "" - kind = self.sql(expression, "kind") - not_valid = " NOT VALID" if expression.args.get("not_valid") else "" - check = " WITH CHECK" if expression.args.get("check") else "" - cascade = ( - " CASCADE" - if expression.args.get("cascade") - and self.dialect.ALTER_TABLE_SUPPORTS_CASCADE - else "" - ) - this = self.sql(expression, "this") - this = f" {this}" if this else "" - - return f"ALTER {kind}{exists}{only}{this}{on_cluster}{check}{self.sep()}{actions_sql}{not_valid}{options}{cascade}" - - def altersession_sql(self, expression: exp.AlterSession) -> str: - items_sql = self.expressions(expression, flat=True) - keyword = "UNSET" if expression.args.get("unset") else "SET" - return f"{keyword} {items_sql}" - - def add_column_sql(self, expression: exp.Expression) -> str: - sql = self.sql(expression) - if isinstance(expression, exp.Schema): - column_text = " COLUMNS" - elif ( - isinstance(expression, exp.ColumnDef) - and self.ALTER_TABLE_INCLUDE_COLUMN_KEYWORD - ): - column_text = " COLUMN" - else: - column_text = "" - - return f"ADD{column_text} {sql}" - - def droppartition_sql(self, expression: exp.DropPartition) -> str: - expressions = self.expressions(expression) - exists = " IF EXISTS " if expression.args.get("exists") else " " - return f"DROP{exists}{expressions}" - - def addconstraint_sql(self, expression: exp.AddConstraint) -> str: - return f"ADD {self.expressions(expression, indent=False)}" - - def addpartition_sql(self, expression: exp.AddPartition) -> str: - exists = "IF NOT EXISTS " if expression.args.get("exists") else "" - location = self.sql(expression, "location") - location = f" {location}" if location else "" - return f"ADD {exists}{self.sql(expression.this)}{location}" - - def distinct_sql(self, expression: exp.Distinct) -> str: - this = self.expressions(expression, flat=True) - - if not self.MULTI_ARG_DISTINCT and len(expression.expressions) > 1: - case = exp.case() - for arg in expression.expressions: - case = case.when(arg.is_(exp.null()), exp.null()) - this = self.sql(case.else_(f"({this})")) - - this = f" {this}" if this else "" - - on = self.sql(expression, "on") - on = f" ON {on}" if on else "" - return f"DISTINCT{this}{on}" - - def ignorenulls_sql(self, expression: exp.IgnoreNulls) -> str: - return self._embed_ignore_nulls(expression, "IGNORE NULLS") - - def respectnulls_sql(self, expression: exp.RespectNulls) -> str: - return self._embed_ignore_nulls(expression, "RESPECT NULLS") - - def havingmax_sql(self, expression: exp.HavingMax) -> str: - this_sql = self.sql(expression, "this") - expression_sql = self.sql(expression, "expression") - kind = "MAX" if expression.args.get("max") else "MIN" - return f"{this_sql} HAVING {kind} {expression_sql}" - - def intdiv_sql(self, expression: exp.IntDiv) -> str: - return self.sql( - exp.Cast( - this=exp.Div(this=expression.this, expression=expression.expression), - to=exp.DataType(this=exp.DataType.Type.INT), - ) - ) - - def dpipe_sql(self, expression: exp.DPipe) -> str: - if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): - return self.func( - "CONCAT", - *(exp.cast(e, exp.DataType.Type.TEXT) for e in expression.flatten()), - ) - return self.binary(expression, "||") - - def div_sql(self, expression: exp.Div) -> str: - l, r = expression.left, expression.right - - if not self.dialect.SAFE_DIVISION and expression.args.get("safe"): - r.replace(exp.Nullif(this=r.copy(), expression=exp.Literal.number(0))) - - if self.dialect.TYPED_DIVISION and not expression.args.get("typed"): - if not l.is_type(*exp.DataType.REAL_TYPES) and not r.is_type( - *exp.DataType.REAL_TYPES - ): - l.replace(exp.cast(l.copy(), to=exp.DataType.Type.DOUBLE)) - - elif not self.dialect.TYPED_DIVISION and expression.args.get("typed"): - if l.is_type(*exp.DataType.INTEGER_TYPES) and r.is_type( - *exp.DataType.INTEGER_TYPES - ): - return self.sql( - exp.cast( - l / r, - to=exp.DataType.Type.BIGINT, - ) - ) - - return self.binary(expression, "/") - - def safedivide_sql(self, expression: exp.SafeDivide) -> str: - n = exp._wrap(expression.this, exp.Binary) - d = exp._wrap(expression.expression, exp.Binary) - return self.sql(exp.If(this=d.neq(0), true=n / d, false=exp.Null())) - - def overlaps_sql(self, expression: exp.Overlaps) -> str: - return self.binary(expression, "OVERLAPS") - - def distance_sql(self, expression: exp.Distance) -> str: - return self.binary(expression, "<->") - - def dot_sql(self, expression: exp.Dot) -> str: - return f"{self.sql(expression, 'this')}.{self.sql(expression, 'expression')}" - - def eq_sql(self, expression: exp.EQ) -> str: - return self.binary(expression, "=") - - def propertyeq_sql(self, expression: exp.PropertyEQ) -> str: - return self.binary(expression, ":=") - - def escape_sql(self, expression: exp.Escape) -> str: - return self.binary(expression, "ESCAPE") - - def glob_sql(self, expression: exp.Glob) -> str: - return self.binary(expression, "GLOB") - - def gt_sql(self, expression: exp.GT) -> str: - return self.binary(expression, ">") - - def gte_sql(self, expression: exp.GTE) -> str: - return self.binary(expression, ">=") - - def is_sql(self, expression: exp.Is) -> str: - if not self.IS_BOOL_ALLOWED and isinstance(expression.expression, exp.Boolean): - return self.sql( - expression.this - if expression.expression.this - else exp.not_(expression.this) - ) - return self.binary(expression, "IS") - - def _like_sql(self, expression: exp.Like | exp.ILike) -> str: - this = expression.this - rhs = expression.expression - - if isinstance(expression, exp.Like): - exp_class: t.Type[exp.Like | exp.ILike] = exp.Like - op = "LIKE" - else: - exp_class = exp.ILike - op = "ILIKE" - - if isinstance(rhs, (exp.All, exp.Any)) and not self.SUPPORTS_LIKE_QUANTIFIERS: - exprs = rhs.this.unnest() - - if isinstance(exprs, exp.Tuple): - exprs = exprs.expressions - - connective = exp.or_ if isinstance(rhs, exp.Any) else exp.and_ - - like_expr: exp.Expression = exp_class(this=this, expression=exprs[0]) - for expr in exprs[1:]: - like_expr = connective(like_expr, exp_class(this=this, expression=expr)) - - parent = expression.parent - if not isinstance(parent, type(like_expr)) and isinstance( - parent, exp.Condition - ): - like_expr = exp.paren(like_expr, copy=False) - - return self.sql(like_expr) - - return self.binary(expression, op) - - def like_sql(self, expression: exp.Like) -> str: - return self._like_sql(expression) - - def ilike_sql(self, expression: exp.ILike) -> str: - return self._like_sql(expression) - - def match_sql(self, expression: exp.Match) -> str: - return self.binary(expression, "MATCH") - - def similarto_sql(self, expression: exp.SimilarTo) -> str: - return self.binary(expression, "SIMILAR TO") - - def lt_sql(self, expression: exp.LT) -> str: - return self.binary(expression, "<") - - def lte_sql(self, expression: exp.LTE) -> str: - return self.binary(expression, "<=") - - def mod_sql(self, expression: exp.Mod) -> str: - return self.binary(expression, "%") - - def mul_sql(self, expression: exp.Mul) -> str: - return self.binary(expression, "*") - - def neq_sql(self, expression: exp.NEQ) -> str: - return self.binary(expression, "<>") - - def nullsafeeq_sql(self, expression: exp.NullSafeEQ) -> str: - return self.binary(expression, "IS NOT DISTINCT FROM") - - def nullsafeneq_sql(self, expression: exp.NullSafeNEQ) -> str: - return self.binary(expression, "IS DISTINCT FROM") - - def sub_sql(self, expression: exp.Sub) -> str: - return self.binary(expression, "-") - - def trycast_sql(self, expression: exp.TryCast) -> str: - return self.cast_sql(expression, safe_prefix="TRY_") - - def jsoncast_sql(self, expression: exp.JSONCast) -> str: - return self.cast_sql(expression) - - def try_sql(self, expression: exp.Try) -> str: - if not self.TRY_SUPPORTED: - self.unsupported("Unsupported TRY function") - return self.sql(expression, "this") - - return self.func("TRY", expression.this) - - def log_sql(self, expression: exp.Log) -> str: - this = expression.this - expr = expression.expression - - if self.dialect.LOG_BASE_FIRST is False: - this, expr = expr, this - elif self.dialect.LOG_BASE_FIRST is None and expr: - if this.name in ("2", "10"): - return self.func(f"LOG{this.name}", expr) - - self.unsupported(f"Unsupported logarithm with base {self.sql(this)}") - - return self.func("LOG", this, expr) - - def use_sql(self, expression: exp.Use) -> str: - kind = self.sql(expression, "kind") - kind = f" {kind}" if kind else "" - this = self.sql(expression, "this") or self.expressions(expression, flat=True) - this = f" {this}" if this else "" - return f"USE{kind}{this}" - - def binary(self, expression: exp.Binary, op: str) -> str: - sqls: t.List[str] = [] - stack: t.List[t.Union[str, exp.Expression]] = [expression] - binary_type = type(expression) - - while stack: - node = stack.pop() - - if type(node) is binary_type: - op_func = node.args.get("operator") - if op_func: - op = f"OPERATOR({self.sql(op_func)})" - - stack.append(node.right) - stack.append(f" {self.maybe_comment(op, comments=node.comments)} ") - stack.append(node.left) - else: - sqls.append(self.sql(node)) - - return "".join(sqls) - - def ceil_floor(self, expression: exp.Ceil | exp.Floor) -> str: - to_clause = self.sql(expression, "to") - if to_clause: - return f"{expression.sql_name()}({self.sql(expression, 'this')} TO {to_clause})" - - return self.function_fallback_sql(expression) - - def function_fallback_sql(self, expression: exp.Func) -> str: - args = [] - - for key in expression.arg_types: - arg_value = expression.args.get(key) - - if isinstance(arg_value, list): - for value in arg_value: - args.append(value) - elif arg_value is not None: - args.append(arg_value) - - if self.dialect.PRESERVE_ORIGINAL_NAMES: - name = ( - expression._meta and expression.meta.get("name") - ) or expression.sql_name() - else: - name = expression.sql_name() - - return self.func(name, *args) - - def func( - self, - name: str, - *args: t.Optional[exp.Expression | str], - prefix: str = "(", - suffix: str = ")", - normalize: bool = True, - ) -> str: - name = self.normalize_func(name) if normalize else name - return f"{name}{prefix}{self.format_args(*args)}{suffix}" - - def format_args( - self, *args: t.Optional[str | exp.Expression], sep: str = ", " - ) -> str: - arg_sqls = tuple( - self.sql(arg) - for arg in args - if arg is not None and not isinstance(arg, bool) - ) - if self.pretty and self.too_wide(arg_sqls): - return self.indent( - "\n" + f"{sep.strip()}\n".join(arg_sqls) + "\n", - skip_first=True, - skip_last=True, - ) - return sep.join(arg_sqls) - - def too_wide(self, args: t.Iterable) -> bool: - return sum(len(arg) for arg in args) > self.max_text_width - - def format_time( - self, - expression: exp.Expression, - inverse_time_mapping: t.Optional[t.Dict[str, str]] = None, - inverse_time_trie: t.Optional[t.Dict] = None, - ) -> t.Optional[str]: - return format_time( - self.sql(expression, "format"), - inverse_time_mapping or self.dialect.INVERSE_TIME_MAPPING, - inverse_time_trie or self.dialect.INVERSE_TIME_TRIE, - ) - - def expressions( - self, - expression: t.Optional[exp.Expression] = None, - key: t.Optional[str] = None, - sqls: t.Optional[t.Collection[str | exp.Expression]] = None, - flat: bool = False, - indent: bool = True, - skip_first: bool = False, - skip_last: bool = False, - sep: str = ", ", - prefix: str = "", - dynamic: bool = False, - new_line: bool = False, - ) -> str: - expressions = expression.args.get(key or "expressions") if expression else sqls - - if not expressions: - return "" - - if flat: - return sep.join(sql for sql in (self.sql(e) for e in expressions) if sql) - - num_sqls = len(expressions) - result_sqls = [] - - for i, e in enumerate(expressions): - sql = self.sql(e, comment=False) - if not sql: - continue - - comments = ( - self.maybe_comment("", e) if isinstance(e, exp.Expression) else "" - ) - - if self.pretty: - if self.leading_comma: - result_sqls.append(f"{sep if i > 0 else ''}{prefix}{sql}{comments}") - else: - result_sqls.append( - f"{prefix}{sql}{(sep.rstrip() if comments else sep) if i + 1 < num_sqls else ''}{comments}" - ) - else: - result_sqls.append( - f"{prefix}{sql}{comments}{sep if i + 1 < num_sqls else ''}" - ) - - if self.pretty and (not dynamic or self.too_wide(result_sqls)): - if new_line: - result_sqls.insert(0, "") - result_sqls.append("") - result_sql = "\n".join(s.rstrip() for s in result_sqls) - else: - result_sql = "".join(result_sqls) - - return ( - self.indent(result_sql, skip_first=skip_first, skip_last=skip_last) - if indent - else result_sql - ) - - def op_expressions( - self, op: str, expression: exp.Expression, flat: bool = False - ) -> str: - flat = flat or isinstance(expression.parent, exp.Properties) - expressions_sql = self.expressions(expression, flat=flat) - if flat: - return f"{op} {expressions_sql}" - return f"{self.seg(op)}{self.sep() if expressions_sql else ''}{expressions_sql}" - - def naked_property(self, expression: exp.Property) -> str: - property_name = exp.Properties.PROPERTY_TO_NAME.get(expression.__class__) - if not property_name: - self.unsupported(f"Unsupported property {expression.__class__.__name__}") - return f"{property_name} {self.sql(expression, 'this')}" - - def tag_sql(self, expression: exp.Tag) -> str: - return f"{expression.args.get('prefix')}{self.sql(expression.this)}{expression.args.get('postfix')}" - - def token_sql(self, token_type: TokenType) -> str: - return self.TOKEN_MAPPING.get(token_type, token_type.name) - - def userdefinedfunction_sql(self, expression: exp.UserDefinedFunction) -> str: - this = self.sql(expression, "this") - expressions = self.no_identify(self.expressions, expression) - expressions = ( - self.wrap(expressions) - if expression.args.get("wrapped") - else f" {expressions}" - ) - return f"{this}{expressions}" if expressions.strip() != "" else this - - def joinhint_sql(self, expression: exp.JoinHint) -> str: - this = self.sql(expression, "this") - expressions = self.expressions(expression, flat=True) - return f"{this}({expressions})" - - def kwarg_sql(self, expression: exp.Kwarg) -> str: - return self.binary(expression, "=>") - - def when_sql(self, expression: exp.When) -> str: - matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED" - source = ( - " BY SOURCE" - if self.MATCHED_BY_SOURCE and expression.args.get("source") - else "" - ) - condition = self.sql(expression, "condition") - condition = f" AND {condition}" if condition else "" - - then_expression = expression.args.get("then") - if isinstance(then_expression, exp.Insert): - this = self.sql(then_expression, "this") - this = f"INSERT {this}" if this else "INSERT" - then = self.sql(then_expression, "expression") - then = f"{this} VALUES {then}" if then else this - elif isinstance(then_expression, exp.Update): - if isinstance(then_expression.args.get("expressions"), exp.Star): - then = f"UPDATE {self.sql(then_expression, 'expressions')}" - else: - expressions_sql = self.expressions(then_expression) - then = ( - f"UPDATE SET{self.sep()}{expressions_sql}" - if expressions_sql - else "UPDATE" - ) - - else: - then = self.sql(then_expression) - return f"WHEN {matched}{source}{condition} THEN {then}" - - def whens_sql(self, expression: exp.Whens) -> str: - return self.expressions(expression, sep=" ", indent=False) - - def merge_sql(self, expression: exp.Merge) -> str: - table = expression.this - table_alias = "" - - hints = table.args.get("hints") - if hints and table.alias and isinstance(hints[0], exp.WithTableHint): - # T-SQL syntax is MERGE ... [WITH ()] [[AS] table_alias] - table_alias = f" AS {self.sql(table.args['alias'].pop())}" - - this = self.sql(table) - using = f"USING {self.sql(expression, 'using')}" - whens = self.sql(expression, "whens") - - on = self.sql(expression, "on") - on = f"ON {on}" if on else "" - - if not on: - on = self.expressions(expression, key="using_cond") - on = f"USING ({on})" if on else "" - - returning = self.sql(expression, "returning") - if returning: - whens = f"{whens}{returning}" - - sep = self.sep() - - return self.prepend_ctes( - expression, - f"MERGE INTO {this}{table_alias}{sep}{using}{sep}{on}{sep}{whens}", - ) - - @unsupported_args("format") - def tochar_sql(self, expression: exp.ToChar) -> str: - return self.sql(exp.cast(expression.this, exp.DataType.Type.TEXT)) - - def tonumber_sql(self, expression: exp.ToNumber) -> str: - if not self.SUPPORTS_TO_NUMBER: - self.unsupported("Unsupported TO_NUMBER function") - return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE)) - - fmt = expression.args.get("format") - if not fmt: - self.unsupported("Conversion format is required for TO_NUMBER") - return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE)) - - return self.func("TO_NUMBER", expression.this, fmt) - - def dictproperty_sql(self, expression: exp.DictProperty) -> str: - this = self.sql(expression, "this") - kind = self.sql(expression, "kind") - settings_sql = self.expressions(expression, key="settings", sep=" ") - args = ( - f"({self.sep('')}{settings_sql}{self.seg(')', sep='')}" - if settings_sql - else "()" - ) - return f"{this}({kind}{args})" - - def dictrange_sql(self, expression: exp.DictRange) -> str: - this = self.sql(expression, "this") - max = self.sql(expression, "max") - min = self.sql(expression, "min") - return f"{this}(MIN {min} MAX {max})" - - def dictsubproperty_sql(self, expression: exp.DictSubProperty) -> str: - return f"{self.sql(expression, 'this')} {self.sql(expression, 'value')}" - - def duplicatekeyproperty_sql(self, expression: exp.DuplicateKeyProperty) -> str: - return f"DUPLICATE KEY ({self.expressions(expression, flat=True)})" - - # https://docs.starrocks.io/docs/sql-reference/sql-statements/table_bucket_part_index/CREATE_TABLE/ - def uniquekeyproperty_sql( - self, expression: exp.UniqueKeyProperty, prefix: str = "UNIQUE KEY" - ) -> str: - return f"{prefix} ({self.expressions(expression, flat=True)})" - - # https://docs.starrocks.io/docs/sql-reference/sql-statements/data-definition/CREATE_TABLE/#distribution_desc - def distributedbyproperty_sql(self, expression: exp.DistributedByProperty) -> str: - expressions = self.expressions(expression, flat=True) - expressions = f" {self.wrap(expressions)}" if expressions else "" - buckets = self.sql(expression, "buckets") - kind = self.sql(expression, "kind") - buckets = f" BUCKETS {buckets}" if buckets else "" - order = self.sql(expression, "order") - return f"DISTRIBUTED BY {kind}{expressions}{buckets}{order}" - - def oncluster_sql(self, expression: exp.OnCluster) -> str: - return "" - - def clusteredbyproperty_sql(self, expression: exp.ClusteredByProperty) -> str: - expressions = self.expressions(expression, key="expressions", flat=True) - sorted_by = self.expressions(expression, key="sorted_by", flat=True) - sorted_by = f" SORTED BY ({sorted_by})" if sorted_by else "" - buckets = self.sql(expression, "buckets") - return f"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS" - - def anyvalue_sql(self, expression: exp.AnyValue) -> str: - this = self.sql(expression, "this") - having = self.sql(expression, "having") - - if having: - this = f"{this} HAVING {'MAX' if expression.args.get('max') else 'MIN'} {having}" - - return self.func("ANY_VALUE", this) - - def querytransform_sql(self, expression: exp.QueryTransform) -> str: - transform = self.func("TRANSFORM", *expression.expressions) - row_format_before = self.sql(expression, "row_format_before") - row_format_before = f" {row_format_before}" if row_format_before else "" - record_writer = self.sql(expression, "record_writer") - record_writer = f" RECORDWRITER {record_writer}" if record_writer else "" - using = f" USING {self.sql(expression, 'command_script')}" - schema = self.sql(expression, "schema") - schema = f" AS {schema}" if schema else "" - row_format_after = self.sql(expression, "row_format_after") - row_format_after = f" {row_format_after}" if row_format_after else "" - record_reader = self.sql(expression, "record_reader") - record_reader = f" RECORDREADER {record_reader}" if record_reader else "" - return f"{transform}{row_format_before}{record_writer}{using}{schema}{row_format_after}{record_reader}" - - def indexconstraintoption_sql(self, expression: exp.IndexConstraintOption) -> str: - key_block_size = self.sql(expression, "key_block_size") - if key_block_size: - return f"KEY_BLOCK_SIZE = {key_block_size}" - - using = self.sql(expression, "using") - if using: - return f"USING {using}" - - parser = self.sql(expression, "parser") - if parser: - return f"WITH PARSER {parser}" - - comment = self.sql(expression, "comment") - if comment: - return f"COMMENT {comment}" - - visible = expression.args.get("visible") - if visible is not None: - return "VISIBLE" if visible else "INVISIBLE" - - engine_attr = self.sql(expression, "engine_attr") - if engine_attr: - return f"ENGINE_ATTRIBUTE = {engine_attr}" - - secondary_engine_attr = self.sql(expression, "secondary_engine_attr") - if secondary_engine_attr: - return f"SECONDARY_ENGINE_ATTRIBUTE = {secondary_engine_attr}" - - self.unsupported("Unsupported index constraint option.") - return "" - - def checkcolumnconstraint_sql(self, expression: exp.CheckColumnConstraint) -> str: - enforced = " ENFORCED" if expression.args.get("enforced") else "" - return f"CHECK ({self.sql(expression, 'this')}){enforced}" - - def indexcolumnconstraint_sql(self, expression: exp.IndexColumnConstraint) -> str: - kind = self.sql(expression, "kind") - kind = f"{kind} INDEX" if kind else "INDEX" - this = self.sql(expression, "this") - this = f" {this}" if this else "" - index_type = self.sql(expression, "index_type") - index_type = f" USING {index_type}" if index_type else "" - expressions = self.expressions(expression, flat=True) - expressions = f" ({expressions})" if expressions else "" - options = self.expressions(expression, key="options", sep=" ") - options = f" {options}" if options else "" - return f"{kind}{this}{index_type}{expressions}{options}" - - def nvl2_sql(self, expression: exp.Nvl2) -> str: - if self.NVL2_SUPPORTED: - return self.function_fallback_sql(expression) - - case = exp.Case().when( - expression.this.is_(exp.null()).not_(copy=False), - expression.args["true"], - copy=False, - ) - else_cond = expression.args.get("false") - if else_cond: - case.else_(else_cond, copy=False) - - return self.sql(case) - - def comprehension_sql(self, expression: exp.Comprehension) -> str: - this = self.sql(expression, "this") - expr = self.sql(expression, "expression") - position = self.sql(expression, "position") - position = f", {position}" if position else "" - iterator = self.sql(expression, "iterator") - condition = self.sql(expression, "condition") - condition = f" IF {condition}" if condition else "" - return f"{this} FOR {expr}{position} IN {iterator}{condition}" - - def columnprefix_sql(self, expression: exp.ColumnPrefix) -> str: - return f"{self.sql(expression, 'this')}({self.sql(expression, 'expression')})" - - def opclass_sql(self, expression: exp.Opclass) -> str: - return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}" - - def _ml_sql(self, expression: exp.Func, name: str) -> str: - model = self.sql(expression, "this") - model = f"MODEL {model}" - expr = expression.expression - if expr: - expr_sql = self.sql(expression, "expression") - expr_sql = ( - f"TABLE {expr_sql}" if not isinstance(expr, exp.Subquery) else expr_sql - ) - else: - expr_sql = None - - parameters = self.sql(expression, "params_struct") or None - - return self.func(name, model, expr_sql, parameters) - - def predict_sql(self, expression: exp.Predict) -> str: - return self._ml_sql(expression, "PREDICT") - - def generateembedding_sql(self, expression: exp.GenerateEmbedding) -> str: - name = ( - "GENERATE_TEXT_EMBEDDING" - if expression.args.get("is_text") - else "GENERATE_EMBEDDING" - ) - return self._ml_sql(expression, name) - - def mltranslate_sql(self, expression: exp.MLTranslate) -> str: - return self._ml_sql(expression, "TRANSLATE") - - def mlforecast_sql(self, expression: exp.MLForecast) -> str: - return self._ml_sql(expression, "FORECAST") - - def featuresattime_sql(self, expression: exp.FeaturesAtTime) -> str: - this_sql = self.sql(expression, "this") - if isinstance(expression.this, exp.Table): - this_sql = f"TABLE {this_sql}" - - return self.func( - "FEATURES_AT_TIME", - this_sql, - expression.args.get("time"), - expression.args.get("num_rows"), - expression.args.get("ignore_feature_nulls"), - ) - - def vectorsearch_sql(self, expression: exp.VectorSearch) -> str: - this_sql = self.sql(expression, "this") - if isinstance(expression.this, exp.Table): - this_sql = f"TABLE {this_sql}" - - query_table = self.sql(expression, "query_table") - if isinstance(expression.args["query_table"], exp.Table): - query_table = f"TABLE {query_table}" - - return self.func( - "VECTOR_SEARCH", - this_sql, - expression.args.get("column_to_search"), - query_table, - expression.args.get("query_column_to_search"), - expression.args.get("top_k"), - expression.args.get("distance_type"), - expression.args.get("options"), - ) - - def forin_sql(self, expression: exp.ForIn) -> str: - this = self.sql(expression, "this") - expression_sql = self.sql(expression, "expression") - return f"FOR {this} DO {expression_sql}" - - def refresh_sql(self, expression: exp.Refresh) -> str: - this = self.sql(expression, "this") - kind = ( - "" - if isinstance(expression.this, exp.Literal) - else f"{expression.text('kind')} " - ) - return f"REFRESH {kind}{this}" - - def toarray_sql(self, expression: exp.ToArray) -> str: - arg = expression.this - if not arg.type: - from bigframes_vendored.sqlglot.optimizer.annotate_types import ( - annotate_types, - ) - - arg = annotate_types(arg, dialect=self.dialect) - - if arg.is_type(exp.DataType.Type.ARRAY): - return self.sql(arg) - - cond_for_null = arg.is_(exp.null()) - return self.sql( - exp.func("IF", cond_for_null, exp.null(), exp.array(arg, copy=False)) - ) - - def tsordstotime_sql(self, expression: exp.TsOrDsToTime) -> str: - this = expression.this - time_format = self.format_time(expression) - - if time_format: - return self.sql( - exp.cast( - exp.StrToTime(this=this, format=expression.args["format"]), - exp.DataType.Type.TIME, - ) - ) - - if isinstance(this, exp.TsOrDsToTime) or this.is_type(exp.DataType.Type.TIME): - return self.sql(this) - - return self.sql(exp.cast(this, exp.DataType.Type.TIME)) - - def tsordstotimestamp_sql(self, expression: exp.TsOrDsToTimestamp) -> str: - this = expression.this - if isinstance(this, exp.TsOrDsToTimestamp) or this.is_type( - exp.DataType.Type.TIMESTAMP - ): - return self.sql(this) - - return self.sql( - exp.cast(this, exp.DataType.Type.TIMESTAMP, dialect=self.dialect) - ) - - def tsordstodatetime_sql(self, expression: exp.TsOrDsToDatetime) -> str: - this = expression.this - if isinstance(this, exp.TsOrDsToDatetime) or this.is_type( - exp.DataType.Type.DATETIME - ): - return self.sql(this) - - return self.sql( - exp.cast(this, exp.DataType.Type.DATETIME, dialect=self.dialect) - ) - - def tsordstodate_sql(self, expression: exp.TsOrDsToDate) -> str: - this = expression.this - time_format = self.format_time(expression) - - if time_format and time_format not in ( - self.dialect.TIME_FORMAT, - self.dialect.DATE_FORMAT, - ): - return self.sql( - exp.cast( - exp.StrToTime(this=this, format=expression.args["format"]), - exp.DataType.Type.DATE, - ) - ) - - if isinstance(this, exp.TsOrDsToDate) or this.is_type(exp.DataType.Type.DATE): - return self.sql(this) - - return self.sql(exp.cast(this, exp.DataType.Type.DATE)) - - def unixdate_sql(self, expression: exp.UnixDate) -> str: - return self.sql( - exp.func( - "DATEDIFF", - expression.this, - exp.cast(exp.Literal.string("1970-01-01"), exp.DataType.Type.DATE), - "day", - ) - ) - - def lastday_sql(self, expression: exp.LastDay) -> str: - if self.LAST_DAY_SUPPORTS_DATE_PART: - return self.function_fallback_sql(expression) - - unit = expression.text("unit") - if unit and unit != "MONTH": - self.unsupported("Date parts are not supported in LAST_DAY.") - - return self.func("LAST_DAY", expression.this) - - def dateadd_sql(self, expression: exp.DateAdd) -> str: - from bigframes_vendored.sqlglot.dialects.dialect import unit_to_str - - return self.func( - "DATE_ADD", expression.this, expression.expression, unit_to_str(expression) - ) - - def arrayany_sql(self, expression: exp.ArrayAny) -> str: - if self.CAN_IMPLEMENT_ARRAY_ANY: - filtered = exp.ArrayFilter( - this=expression.this, expression=expression.expression - ) - filtered_not_empty = exp.ArraySize(this=filtered).neq(0) - original_is_empty = exp.ArraySize(this=expression.this).eq(0) - return self.sql(exp.paren(original_is_empty.or_(filtered_not_empty))) - - from bigframes_vendored.sqlglot.dialects import Dialect - - # SQLGlot's executor supports ARRAY_ANY, so we don't wanna warn for the SQLGlot dialect - if self.dialect.__class__ != Dialect: - self.unsupported("ARRAY_ANY is unsupported") - - return self.function_fallback_sql(expression) - - def struct_sql(self, expression: exp.Struct) -> str: - expression.set( - "expressions", - [ - exp.alias_(e.expression, e.name if e.this.is_string else e.this) - if isinstance(e, exp.PropertyEQ) - else e - for e in expression.expressions - ], - ) - - return self.function_fallback_sql(expression) - - def partitionrange_sql(self, expression: exp.PartitionRange) -> str: - low = self.sql(expression, "this") - high = self.sql(expression, "expression") - - return f"{low} TO {high}" - - def truncatetable_sql(self, expression: exp.TruncateTable) -> str: - target = "DATABASE" if expression.args.get("is_database") else "TABLE" - tables = f" {self.expressions(expression)}" - - exists = " IF EXISTS" if expression.args.get("exists") else "" - - on_cluster = self.sql(expression, "cluster") - on_cluster = f" {on_cluster}" if on_cluster else "" - - identity = self.sql(expression, "identity") - identity = f" {identity} IDENTITY" if identity else "" - - option = self.sql(expression, "option") - option = f" {option}" if option else "" - - partition = self.sql(expression, "partition") - partition = f" {partition}" if partition else "" - - return f"TRUNCATE {target}{exists}{tables}{on_cluster}{identity}{option}{partition}" - - # This transpiles T-SQL's CONVERT function - # https://learn.microsoft.com/en-us/sql/t-sql/functions/cast-and-convert-transact-sql?view=sql-server-ver16 - def convert_sql(self, expression: exp.Convert) -> str: - to = expression.this - value = expression.expression - style = expression.args.get("style") - safe = expression.args.get("safe") - strict = expression.args.get("strict") - - if not to or not value: - return "" - - # Retrieve length of datatype and override to default if not specified - if ( - not seq_get(to.expressions, 0) - and to.this in self.PARAMETERIZABLE_TEXT_TYPES - ): - to = exp.DataType.build( - to.this, expressions=[exp.Literal.number(30)], nested=False - ) - - transformed: t.Optional[exp.Expression] = None - cast = exp.Cast if strict else exp.TryCast - - # Check whether a conversion with format (T-SQL calls this 'style') is applicable - if isinstance(style, exp.Literal) and style.is_int: - from bigframes_vendored.sqlglot.dialects.tsql import TSQL - - style_value = style.name - converted_style = TSQL.CONVERT_FORMAT_MAPPING.get(style_value) - if not converted_style: - self.unsupported(f"Unsupported T-SQL 'style' value: {style_value}") - - fmt = exp.Literal.string(converted_style) - - if to.this == exp.DataType.Type.DATE: - transformed = exp.StrToDate(this=value, format=fmt) - elif to.this in (exp.DataType.Type.DATETIME, exp.DataType.Type.DATETIME2): - transformed = exp.StrToTime(this=value, format=fmt) - elif to.this in self.PARAMETERIZABLE_TEXT_TYPES: - transformed = cast( - this=exp.TimeToStr(this=value, format=fmt), to=to, safe=safe - ) - elif to.this == exp.DataType.Type.TEXT: - transformed = exp.TimeToStr(this=value, format=fmt) - - if not transformed: - transformed = cast(this=value, to=to, safe=safe) - - return self.sql(transformed) - - def _jsonpathkey_sql(self, expression: exp.JSONPathKey) -> str: - this = expression.this - if isinstance(this, exp.JSONPathWildcard): - this = self.json_path_part(this) - return f".{this}" if this else "" - - if self.SAFE_JSON_PATH_KEY_RE.match(this): - return f".{this}" - - this = self.json_path_part(this) - return ( - f"[{this}]" - if self._quote_json_path_key_using_brackets - and self.JSON_PATH_BRACKETED_KEY_SUPPORTED - else f".{this}" - ) - - def _jsonpathsubscript_sql(self, expression: exp.JSONPathSubscript) -> str: - this = self.json_path_part(expression.this) - return f"[{this}]" if this else "" - - def _simplify_unless_literal(self, expression: E) -> E: - if not isinstance(expression, exp.Literal): - from bigframes_vendored.sqlglot.optimizer.simplify import simplify - - expression = simplify(expression, dialect=self.dialect) - - return expression - - def _embed_ignore_nulls( - self, expression: exp.IgnoreNulls | exp.RespectNulls, text: str - ) -> str: - this = expression.this - if isinstance(this, self.RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS): - self.unsupported( - f"RESPECT/IGNORE NULLS is not supported for {type(this).key} in {self.dialect.__class__.__name__}" - ) - return self.sql(this) - - if self.IGNORE_NULLS_IN_FUNC and not expression.meta.get("inline"): - # The first modifier here will be the one closest to the AggFunc's arg - mods = sorted( - expression.find_all(exp.HavingMax, exp.Order, exp.Limit), - key=lambda x: 0 - if isinstance(x, exp.HavingMax) - else (1 if isinstance(x, exp.Order) else 2), - ) - - if mods: - mod = mods[0] - this = expression.__class__(this=mod.this.copy()) - this.meta["inline"] = True - mod.this.replace(this) - return self.sql(expression.this) - - agg_func = expression.find(exp.AggFunc) - - if agg_func: - agg_func_sql = self.sql(agg_func, comment=False)[:-1] + f" {text})" - return self.maybe_comment(agg_func_sql, comments=agg_func.comments) - - return f"{self.sql(expression, 'this')} {text}" - - def _replace_line_breaks(self, string: str) -> str: - """We don't want to extra indent line breaks so we temporarily replace them with sentinels.""" - if self.pretty: - return string.replace("\n", self.SENTINEL_LINE_BREAK) - return string - - def copyparameter_sql(self, expression: exp.CopyParameter) -> str: - option = self.sql(expression, "this") - - if expression.expressions: - upper = option.upper() - - # Snowflake FILE_FORMAT options are separated by whitespace - sep = " " if upper == "FILE_FORMAT" else ", " - - # Databricks copy/format options do not set their list of values with EQ - op = " " if upper in ("COPY_OPTIONS", "FORMAT_OPTIONS") else " = " - values = self.expressions(expression, flat=True, sep=sep) - return f"{option}{op}({values})" - - value = self.sql(expression, "expression") - - if not value: - return option - - op = " = " if self.COPY_PARAMS_EQ_REQUIRED else " " - - return f"{option}{op}{value}" - - def credentials_sql(self, expression: exp.Credentials) -> str: - cred_expr = expression.args.get("credentials") - if isinstance(cred_expr, exp.Literal): - # Redshift case: CREDENTIALS - credentials = self.sql(expression, "credentials") - credentials = f"CREDENTIALS {credentials}" if credentials else "" - else: - # Snowflake case: CREDENTIALS = (...) - credentials = self.expressions( - expression, key="credentials", flat=True, sep=" " - ) - credentials = ( - f"CREDENTIALS = ({credentials})" if cred_expr is not None else "" - ) - - storage = self.sql(expression, "storage") - storage = f"STORAGE_INTEGRATION = {storage}" if storage else "" - - encryption = self.expressions(expression, key="encryption", flat=True, sep=" ") - encryption = f" ENCRYPTION = ({encryption})" if encryption else "" - - iam_role = self.sql(expression, "iam_role") - iam_role = f"IAM_ROLE {iam_role}" if iam_role else "" - - region = self.sql(expression, "region") - region = f" REGION {region}" if region else "" - - return f"{credentials}{storage}{encryption}{iam_role}{region}" - - def copy_sql(self, expression: exp.Copy) -> str: - this = self.sql(expression, "this") - this = f" INTO {this}" if self.COPY_HAS_INTO_KEYWORD else f" {this}" - - credentials = self.sql(expression, "credentials") - credentials = self.seg(credentials) if credentials else "" - files = self.expressions(expression, key="files", flat=True) - kind = ( - self.seg("FROM" if expression.args.get("kind") else "TO") if files else "" - ) - - sep = ", " if self.dialect.COPY_PARAMS_ARE_CSV else " " - params = self.expressions( - expression, - key="params", - sep=sep, - new_line=True, - skip_last=True, - skip_first=True, - indent=self.COPY_PARAMS_ARE_WRAPPED, - ) - - if params: - if self.COPY_PARAMS_ARE_WRAPPED: - params = f" WITH ({params})" - elif not self.pretty and (files or credentials): - params = f" {params}" - - return f"COPY{this}{kind} {files}{credentials}{params}" - - def semicolon_sql(self, expression: exp.Semicolon) -> str: - return "" - - def datadeletionproperty_sql(self, expression: exp.DataDeletionProperty) -> str: - on_sql = "ON" if expression.args.get("on") else "OFF" - filter_col: t.Optional[str] = self.sql(expression, "filter_column") - filter_col = f"FILTER_COLUMN={filter_col}" if filter_col else None - retention_period: t.Optional[str] = self.sql(expression, "retention_period") - retention_period = ( - f"RETENTION_PERIOD={retention_period}" if retention_period else None - ) - - if filter_col or retention_period: - on_sql = self.func("ON", filter_col, retention_period) - - return f"DATA_DELETION={on_sql}" - - def maskingpolicycolumnconstraint_sql( - self, expression: exp.MaskingPolicyColumnConstraint - ) -> str: - this = self.sql(expression, "this") - expressions = self.expressions(expression, flat=True) - expressions = f" USING ({expressions})" if expressions else "" - return f"MASKING POLICY {this}{expressions}" - - def gapfill_sql(self, expression: exp.GapFill) -> str: - this = self.sql(expression, "this") - this = f"TABLE {this}" - return self.func( - "GAP_FILL", this, *[v for k, v in expression.args.items() if k != "this"] - ) - - def scope_resolution(self, rhs: str, scope_name: str) -> str: - return self.func("SCOPE_RESOLUTION", scope_name or None, rhs) - - def scoperesolution_sql(self, expression: exp.ScopeResolution) -> str: - this = self.sql(expression, "this") - expr = expression.expression - - if isinstance(expr, exp.Func): - # T-SQL's CLR functions are case sensitive - expr = f"{self.sql(expr, 'this')}({self.format_args(*expr.expressions)})" - else: - expr = self.sql(expression, "expression") - - return self.scope_resolution(expr, this) - - def parsejson_sql(self, expression: exp.ParseJSON) -> str: - if self.PARSE_JSON_NAME is None: - return self.sql(expression.this) - - return self.func(self.PARSE_JSON_NAME, expression.this, expression.expression) - - def rand_sql(self, expression: exp.Rand) -> str: - lower = self.sql(expression, "lower") - upper = self.sql(expression, "upper") - - if lower and upper: - return ( - f"({upper} - {lower}) * {self.func('RAND', expression.this)} + {lower}" - ) - return self.func("RAND", expression.this) - - def changes_sql(self, expression: exp.Changes) -> str: - information = self.sql(expression, "information") - information = f"INFORMATION => {information}" - at_before = self.sql(expression, "at_before") - at_before = f"{self.seg('')}{at_before}" if at_before else "" - end = self.sql(expression, "end") - end = f"{self.seg('')}{end}" if end else "" - - return f"CHANGES ({information}){at_before}{end}" - - def pad_sql(self, expression: exp.Pad) -> str: - prefix = "L" if expression.args.get("is_left") else "R" - - fill_pattern = self.sql(expression, "fill_pattern") or None - if not fill_pattern and self.PAD_FILL_PATTERN_IS_REQUIRED: - fill_pattern = "' '" - - return self.func( - f"{prefix}PAD", expression.this, expression.expression, fill_pattern - ) - - def summarize_sql(self, expression: exp.Summarize) -> str: - table = " TABLE" if expression.args.get("table") else "" - return f"SUMMARIZE{table} {self.sql(expression.this)}" - - def explodinggenerateseries_sql( - self, expression: exp.ExplodingGenerateSeries - ) -> str: - generate_series = exp.GenerateSeries(**expression.args) - - parent = expression.parent - if isinstance(parent, (exp.Alias, exp.TableAlias)): - parent = parent.parent - - if self.SUPPORTS_EXPLODING_PROJECTIONS and not isinstance( - parent, (exp.Table, exp.Unnest) - ): - return self.sql(exp.Unnest(expressions=[generate_series])) - - if isinstance(parent, exp.Select): - self.unsupported("GenerateSeries projection unnesting is not supported.") - - return self.sql(generate_series) - - def arrayconcat_sql( - self, expression: exp.ArrayConcat, name: str = "ARRAY_CONCAT" - ) -> str: - exprs = expression.expressions - if not self.ARRAY_CONCAT_IS_VAR_LEN: - if len(exprs) == 0: - rhs: t.Union[str, exp.Expression] = exp.Array(expressions=[]) - else: - rhs = reduce( - lambda x, y: exp.ArrayConcat(this=x, expressions=[y]), exprs - ) - else: - rhs = self.expressions(expression) # type: ignore - - return self.func(name, expression.this, rhs or None) - - def converttimezone_sql(self, expression: exp.ConvertTimezone) -> str: - if self.SUPPORTS_CONVERT_TIMEZONE: - return self.function_fallback_sql(expression) - - source_tz = expression.args.get("source_tz") - target_tz = expression.args.get("target_tz") - timestamp = expression.args.get("timestamp") - - if source_tz and timestamp: - timestamp = exp.AtTimeZone( - this=exp.cast(timestamp, exp.DataType.Type.TIMESTAMPNTZ), zone=source_tz - ) - - expr = exp.AtTimeZone(this=timestamp, zone=target_tz) - - return self.sql(expr) - - def json_sql(self, expression: exp.JSON) -> str: - this = self.sql(expression, "this") - this = f" {this}" if this else "" - - _with = expression.args.get("with_") - - if _with is None: - with_sql = "" - elif not _with: - with_sql = " WITHOUT" - else: - with_sql = " WITH" - - unique_sql = " UNIQUE KEYS" if expression.args.get("unique") else "" - - return f"JSON{this}{with_sql}{unique_sql}" - - def jsonvalue_sql(self, expression: exp.JSONValue) -> str: - def _generate_on_options(arg: t.Any) -> str: - return arg if isinstance(arg, str) else f"DEFAULT {self.sql(arg)}" - - path = self.sql(expression, "path") - returning = self.sql(expression, "returning") - returning = f" RETURNING {returning}" if returning else "" - - on_condition = self.sql(expression, "on_condition") - on_condition = f" {on_condition}" if on_condition else "" - - return self.func( - "JSON_VALUE", expression.this, f"{path}{returning}{on_condition}" - ) - - def conditionalinsert_sql(self, expression: exp.ConditionalInsert) -> str: - else_ = "ELSE " if expression.args.get("else_") else "" - condition = self.sql(expression, "expression") - condition = f"WHEN {condition} THEN " if condition else else_ - insert = self.sql(expression, "this")[len("INSERT") :].strip() - return f"{condition}{insert}" - - def multitableinserts_sql(self, expression: exp.MultitableInserts) -> str: - kind = self.sql(expression, "kind") - expressions = self.seg(self.expressions(expression, sep=" ")) - res = f"INSERT {kind}{expressions}{self.seg(self.sql(expression, 'source'))}" - return res - - def oncondition_sql(self, expression: exp.OnCondition) -> str: - # Static options like "NULL ON ERROR" are stored as strings, in contrast to "DEFAULT ON ERROR" - empty = expression.args.get("empty") - empty = ( - f"DEFAULT {empty} ON EMPTY" - if isinstance(empty, exp.Expression) - else self.sql(expression, "empty") - ) - - error = expression.args.get("error") - error = ( - f"DEFAULT {error} ON ERROR" - if isinstance(error, exp.Expression) - else self.sql(expression, "error") - ) - - if error and empty: - error = ( - f"{empty} {error}" - if self.dialect.ON_CONDITION_EMPTY_BEFORE_ERROR - else f"{error} {empty}" - ) - empty = "" - - null = self.sql(expression, "null") - - return f"{empty}{error}{null}" - - def jsonextractquote_sql(self, expression: exp.JSONExtractQuote) -> str: - scalar = " ON SCALAR STRING" if expression.args.get("scalar") else "" - return f"{self.sql(expression, 'option')} QUOTES{scalar}" - - def jsonexists_sql(self, expression: exp.JSONExists) -> str: - this = self.sql(expression, "this") - path = self.sql(expression, "path") - - passing = self.expressions(expression, "passing") - passing = f" PASSING {passing}" if passing else "" - - on_condition = self.sql(expression, "on_condition") - on_condition = f" {on_condition}" if on_condition else "" - - path = f"{path}{passing}{on_condition}" - - return self.func("JSON_EXISTS", this, path) - - def arrayagg_sql(self, expression: exp.ArrayAgg) -> str: - array_agg = self.function_fallback_sql(expression) - - # Add a NULL FILTER on the column to mimic the results going from a dialect that excludes nulls - # on ARRAY_AGG (e.g Spark) to one that doesn't (e.g. DuckDB) - if self.dialect.ARRAY_AGG_INCLUDES_NULLS and expression.args.get( - "nulls_excluded" - ): - parent = expression.parent - if isinstance(parent, exp.Filter): - parent_cond = parent.expression.this - parent_cond.replace( - parent_cond.and_(expression.this.is_(exp.null()).not_()) - ) - else: - this = expression.this - # Do not add the filter if the input is not a column (e.g. literal, struct etc) - if this.find(exp.Column): - # DISTINCT is already present in the agg function, do not propagate it to FILTER as well - this_sql = ( - self.expressions(this) - if isinstance(this, exp.Distinct) - else self.sql(expression, "this") - ) - - array_agg = f"{array_agg} FILTER(WHERE {this_sql} IS NOT NULL)" - - return array_agg - - def slice_sql(self, expression: exp.Slice) -> str: - step = self.sql(expression, "step") - end = self.sql(expression.expression) - begin = self.sql(expression.this) - - sql = f"{end}:{step}" if step else end - return f"{begin}:{sql}" if sql else f"{begin}:" - - def apply_sql(self, expression: exp.Apply) -> str: - this = self.sql(expression, "this") - expr = self.sql(expression, "expression") - - return f"{this} APPLY({expr})" - - def _grant_or_revoke_sql( - self, - expression: exp.Grant | exp.Revoke, - keyword: str, - preposition: str, - grant_option_prefix: str = "", - grant_option_suffix: str = "", - ) -> str: - privileges_sql = self.expressions(expression, key="privileges", flat=True) - - kind = self.sql(expression, "kind") - kind = f" {kind}" if kind else "" - - securable = self.sql(expression, "securable") - securable = f" {securable}" if securable else "" - - principals = self.expressions(expression, key="principals", flat=True) - - if not expression.args.get("grant_option"): - grant_option_prefix = grant_option_suffix = "" - - # cascade for revoke only - cascade = self.sql(expression, "cascade") - cascade = f" {cascade}" if cascade else "" - - return f"{keyword} {grant_option_prefix}{privileges_sql} ON{kind}{securable} {preposition} {principals}{grant_option_suffix}{cascade}" - - def grant_sql(self, expression: exp.Grant) -> str: - return self._grant_or_revoke_sql( - expression, - keyword="GRANT", - preposition="TO", - grant_option_suffix=" WITH GRANT OPTION", - ) - - def revoke_sql(self, expression: exp.Revoke) -> str: - return self._grant_or_revoke_sql( - expression, - keyword="REVOKE", - preposition="FROM", - grant_option_prefix="GRANT OPTION FOR ", - ) - - def grantprivilege_sql(self, expression: exp.GrantPrivilege): - this = self.sql(expression, "this") - columns = self.expressions(expression, flat=True) - columns = f"({columns})" if columns else "" - - return f"{this}{columns}" - - def grantprincipal_sql(self, expression: exp.GrantPrincipal): - this = self.sql(expression, "this") - - kind = self.sql(expression, "kind") - kind = f"{kind} " if kind else "" - - return f"{kind}{this}" - - def columns_sql(self, expression: exp.Columns): - func = self.function_fallback_sql(expression) - if expression.args.get("unpack"): - func = f"*{func}" - - return func - - def overlay_sql(self, expression: exp.Overlay): - this = self.sql(expression, "this") - expr = self.sql(expression, "expression") - from_sql = self.sql(expression, "from_") - for_sql = self.sql(expression, "for_") - for_sql = f" FOR {for_sql}" if for_sql else "" - - return f"OVERLAY({this} PLACING {expr} FROM {from_sql}{for_sql})" - - @unsupported_args("format") - def todouble_sql(self, expression: exp.ToDouble) -> str: - return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE)) - - def string_sql(self, expression: exp.String) -> str: - this = expression.this - zone = expression.args.get("zone") - - if zone: - # This is a BigQuery specific argument for STRING(, ) - # BigQuery stores timestamps internally as UTC, so ConvertTimezone is used with UTC - # set for source_tz to transpile the time conversion before the STRING cast - this = exp.ConvertTimezone( - source_tz=exp.Literal.string("UTC"), target_tz=zone, timestamp=this - ) - - return self.sql(exp.cast(this, exp.DataType.Type.VARCHAR)) - - def median_sql(self, expression: exp.Median): - if not self.SUPPORTS_MEDIAN: - return self.sql( - exp.PercentileCont( - this=expression.this, expression=exp.Literal.number(0.5) - ) - ) - - return self.function_fallback_sql(expression) - - def overflowtruncatebehavior_sql( - self, expression: exp.OverflowTruncateBehavior - ) -> str: - filler = self.sql(expression, "this") - filler = f" {filler}" if filler else "" - with_count = ( - "WITH COUNT" if expression.args.get("with_count") else "WITHOUT COUNT" - ) - return f"TRUNCATE{filler} {with_count}" - - def unixseconds_sql(self, expression: exp.UnixSeconds) -> str: - if self.SUPPORTS_UNIX_SECONDS: - return self.function_fallback_sql(expression) - - start_ts = exp.cast( - exp.Literal.string("1970-01-01 00:00:00+00"), - to=exp.DataType.Type.TIMESTAMPTZ, - ) - - return self.sql( - exp.TimestampDiff( - this=expression.this, expression=start_ts, unit=exp.var("SECONDS") - ) - ) - - def arraysize_sql(self, expression: exp.ArraySize) -> str: - dim = expression.expression - - # For dialects that don't support the dimension arg, we can safely transpile it's default value (1st dimension) - if dim and self.ARRAY_SIZE_DIM_REQUIRED is None: - if not (dim.is_int and dim.name == "1"): - self.unsupported("Cannot transpile dimension argument for ARRAY_LENGTH") - dim = None - - # If dimension is required but not specified, default initialize it - if self.ARRAY_SIZE_DIM_REQUIRED and not dim: - dim = exp.Literal.number(1) - - return self.func(self.ARRAY_SIZE_NAME, expression.this, dim) - - def attach_sql(self, expression: exp.Attach) -> str: - this = self.sql(expression, "this") - exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" - expressions = self.expressions(expression) - expressions = f" ({expressions})" if expressions else "" - - return f"ATTACH{exists_sql} {this}{expressions}" - - def detach_sql(self, expression: exp.Detach) -> str: - this = self.sql(expression, "this") - # the DATABASE keyword is required if IF EXISTS is set - # without it, DuckDB throws an error: Parser Error: syntax error at or near "exists" (Line Number: 1) - # ref: https://duckdb.org/docs/stable/sql/statements/attach.html#detach-syntax - exists_sql = " DATABASE IF EXISTS" if expression.args.get("exists") else "" - - return f"DETACH{exists_sql} {this}" - - def attachoption_sql(self, expression: exp.AttachOption) -> str: - this = self.sql(expression, "this") - value = self.sql(expression, "expression") - value = f" {value}" if value else "" - return f"{this}{value}" - - def watermarkcolumnconstraint_sql( - self, expression: exp.WatermarkColumnConstraint - ) -> str: - return f"WATERMARK FOR {self.sql(expression, 'this')} AS {self.sql(expression, 'expression')}" - - def encodeproperty_sql(self, expression: exp.EncodeProperty) -> str: - encode = "KEY ENCODE" if expression.args.get("key") else "ENCODE" - encode = f"{encode} {self.sql(expression, 'this')}" - - properties = expression.args.get("properties") - if properties: - encode = f"{encode} {self.properties(properties)}" - - return encode - - def includeproperty_sql(self, expression: exp.IncludeProperty) -> str: - this = self.sql(expression, "this") - include = f"INCLUDE {this}" - - column_def = self.sql(expression, "column_def") - if column_def: - include = f"{include} {column_def}" - - alias = self.sql(expression, "alias") - if alias: - include = f"{include} AS {alias}" - - return include - - def xmlelement_sql(self, expression: exp.XMLElement) -> str: - name = f"NAME {self.sql(expression, 'this')}" - return self.func("XMLELEMENT", name, *expression.expressions) - - def xmlkeyvalueoption_sql(self, expression: exp.XMLKeyValueOption) -> str: - this = self.sql(expression, "this") - expr = self.sql(expression, "expression") - expr = f"({expr})" if expr else "" - return f"{this}{expr}" - - def partitionbyrangeproperty_sql( - self, expression: exp.PartitionByRangeProperty - ) -> str: - partitions = self.expressions(expression, "partition_expressions") - create = self.expressions(expression, "create_expressions") - return f"PARTITION BY RANGE {self.wrap(partitions)} {self.wrap(create)}" - - def partitionbyrangepropertydynamic_sql( - self, expression: exp.PartitionByRangePropertyDynamic - ) -> str: - start = self.sql(expression, "start") - end = self.sql(expression, "end") - - every = expression.args["every"] - if isinstance(every, exp.Interval) and every.this.is_string: - every.this.replace(exp.Literal.number(every.name)) - - return f"START {self.wrap(start)} END {self.wrap(end)} EVERY {self.wrap(self.sql(every))}" - - def unpivotcolumns_sql(self, expression: exp.UnpivotColumns) -> str: - name = self.sql(expression, "this") - values = self.expressions(expression, flat=True) - - return f"NAME {name} VALUE {values}" - - def analyzesample_sql(self, expression: exp.AnalyzeSample) -> str: - kind = self.sql(expression, "kind") - sample = self.sql(expression, "sample") - return f"SAMPLE {sample} {kind}" - - def analyzestatistics_sql(self, expression: exp.AnalyzeStatistics) -> str: - kind = self.sql(expression, "kind") - option = self.sql(expression, "option") - option = f" {option}" if option else "" - this = self.sql(expression, "this") - this = f" {this}" if this else "" - columns = self.expressions(expression) - columns = f" {columns}" if columns else "" - return f"{kind}{option} STATISTICS{this}{columns}" - - def analyzehistogram_sql(self, expression: exp.AnalyzeHistogram) -> str: - this = self.sql(expression, "this") - columns = self.expressions(expression) - inner_expression = self.sql(expression, "expression") - inner_expression = f" {inner_expression}" if inner_expression else "" - update_options = self.sql(expression, "update_options") - update_options = f" {update_options} UPDATE" if update_options else "" - return f"{this} HISTOGRAM ON {columns}{inner_expression}{update_options}" - - def analyzedelete_sql(self, expression: exp.AnalyzeDelete) -> str: - kind = self.sql(expression, "kind") - kind = f" {kind}" if kind else "" - return f"DELETE{kind} STATISTICS" - - def analyzelistchainedrows_sql(self, expression: exp.AnalyzeListChainedRows) -> str: - inner_expression = self.sql(expression, "expression") - return f"LIST CHAINED ROWS{inner_expression}" - - def analyzevalidate_sql(self, expression: exp.AnalyzeValidate) -> str: - kind = self.sql(expression, "kind") - this = self.sql(expression, "this") - this = f" {this}" if this else "" - inner_expression = self.sql(expression, "expression") - return f"VALIDATE {kind}{this}{inner_expression}" - - def analyze_sql(self, expression: exp.Analyze) -> str: - options = self.expressions(expression, key="options", sep=" ") - options = f" {options}" if options else "" - kind = self.sql(expression, "kind") - kind = f" {kind}" if kind else "" - this = self.sql(expression, "this") - this = f" {this}" if this else "" - mode = self.sql(expression, "mode") - mode = f" {mode}" if mode else "" - properties = self.sql(expression, "properties") - properties = f" {properties}" if properties else "" - partition = self.sql(expression, "partition") - partition = f" {partition}" if partition else "" - inner_expression = self.sql(expression, "expression") - inner_expression = f" {inner_expression}" if inner_expression else "" - return f"ANALYZE{options}{kind}{this}{partition}{mode}{inner_expression}{properties}" - - def xmltable_sql(self, expression: exp.XMLTable) -> str: - this = self.sql(expression, "this") - namespaces = self.expressions(expression, key="namespaces") - namespaces = f"XMLNAMESPACES({namespaces}), " if namespaces else "" - passing = self.expressions(expression, key="passing") - passing = f"{self.sep()}PASSING{self.seg(passing)}" if passing else "" - columns = self.expressions(expression, key="columns") - columns = f"{self.sep()}COLUMNS{self.seg(columns)}" if columns else "" - by_ref = ( - f"{self.sep()}RETURNING SEQUENCE BY REF" - if expression.args.get("by_ref") - else "" - ) - return f"XMLTABLE({self.sep('')}{self.indent(namespaces + this + passing + by_ref + columns)}{self.seg(')', sep='')}" - - def xmlnamespace_sql(self, expression: exp.XMLNamespace) -> str: - this = self.sql(expression, "this") - return this if isinstance(expression.this, exp.Alias) else f"DEFAULT {this}" - - def export_sql(self, expression: exp.Export) -> str: - this = self.sql(expression, "this") - connection = self.sql(expression, "connection") - connection = f"WITH CONNECTION {connection} " if connection else "" - options = self.sql(expression, "options") - return f"EXPORT DATA {connection}{options} AS {this}" - - def declare_sql(self, expression: exp.Declare) -> str: - return f"DECLARE {self.expressions(expression, flat=True)}" - - def declareitem_sql(self, expression: exp.DeclareItem) -> str: - variable = self.sql(expression, "this") - default = self.sql(expression, "default") - default = f" = {default}" if default else "" - - kind = self.sql(expression, "kind") - if isinstance(expression.args.get("kind"), exp.Schema): - kind = f"TABLE {kind}" - - return f"{variable} AS {kind}{default}" - - def recursivewithsearch_sql(self, expression: exp.RecursiveWithSearch) -> str: - kind = self.sql(expression, "kind") - this = self.sql(expression, "this") - set = self.sql(expression, "expression") - using = self.sql(expression, "using") - using = f" USING {using}" if using else "" - - kind_sql = kind if kind == "CYCLE" else f"SEARCH {kind} FIRST BY" - - return f"{kind_sql} {this} SET {set}{using}" - - def parameterizedagg_sql(self, expression: exp.ParameterizedAgg) -> str: - params = self.expressions(expression, key="params", flat=True) - return self.func(expression.name, *expression.expressions) + f"({params})" - - def anonymousaggfunc_sql(self, expression: exp.AnonymousAggFunc) -> str: - return self.func(expression.name, *expression.expressions) - - def combinedaggfunc_sql(self, expression: exp.CombinedAggFunc) -> str: - return self.anonymousaggfunc_sql(expression) - - def combinedparameterizedagg_sql( - self, expression: exp.CombinedParameterizedAgg - ) -> str: - return self.parameterizedagg_sql(expression) - - def show_sql(self, expression: exp.Show) -> str: - self.unsupported("Unsupported SHOW statement") - return "" - - def install_sql(self, expression: exp.Install) -> str: - self.unsupported("Unsupported INSTALL statement") - return "" - - def get_put_sql(self, expression: exp.Put | exp.Get) -> str: - # Snowflake GET/PUT statements: - # PUT - # GET - props = expression.args.get("properties") - props_sql = ( - self.properties(props, prefix=" ", sep=" ", wrapped=False) if props else "" - ) - this = self.sql(expression, "this") - target = self.sql(expression, "target") - - if isinstance(expression, exp.Put): - return f"PUT {this} {target}{props_sql}" - else: - return f"GET {target} {this}{props_sql}" - - def translatecharacters_sql(self, expression: exp.TranslateCharacters): - this = self.sql(expression, "this") - expr = self.sql(expression, "expression") - with_error = " WITH ERROR" if expression.args.get("with_error") else "" - return f"TRANSLATE({this} USING {expr}{with_error})" - - def decodecase_sql(self, expression: exp.DecodeCase) -> str: - if self.SUPPORTS_DECODE_CASE: - return self.func("DECODE", *expression.expressions) - - expression, *expressions = expression.expressions - - ifs = [] - for search, result in zip(expressions[::2], expressions[1::2]): - if isinstance(search, exp.Literal): - ifs.append(exp.If(this=expression.eq(search), true=result)) - elif isinstance(search, exp.Null): - ifs.append(exp.If(this=expression.is_(exp.Null()), true=result)) - else: - if isinstance(search, exp.Binary): - search = exp.paren(search) - - cond = exp.or_( - expression.eq(search), - exp.and_( - expression.is_(exp.Null()), search.is_(exp.Null()), copy=False - ), - copy=False, - ) - ifs.append(exp.If(this=cond, true=result)) - - case = exp.Case( - ifs=ifs, default=expressions[-1] if len(expressions) % 2 == 1 else None - ) - return self.sql(case) - - def semanticview_sql(self, expression: exp.SemanticView) -> str: - this = self.sql(expression, "this") - this = self.seg(this, sep="") - dimensions = self.expressions( - expression, "dimensions", dynamic=True, skip_first=True, skip_last=True - ) - dimensions = self.seg(f"DIMENSIONS {dimensions}") if dimensions else "" - metrics = self.expressions( - expression, "metrics", dynamic=True, skip_first=True, skip_last=True - ) - metrics = self.seg(f"METRICS {metrics}") if metrics else "" - facts = self.expressions( - expression, "facts", dynamic=True, skip_first=True, skip_last=True - ) - facts = self.seg(f"FACTS {facts}") if facts else "" - where = self.sql(expression, "where") - where = self.seg(f"WHERE {where}") if where else "" - body = self.indent(this + metrics + dimensions + facts + where, skip_first=True) - return f"SEMANTIC_VIEW({body}{self.seg(')', sep='')}" - - def getextract_sql(self, expression: exp.GetExtract) -> str: - this = expression.this - expr = expression.expression - - if not this.type or not expression.type: - from bigframes_vendored.sqlglot.optimizer.annotate_types import ( - annotate_types, - ) - - this = annotate_types(this, dialect=self.dialect) - - if this.is_type(*(exp.DataType.Type.ARRAY, exp.DataType.Type.MAP)): - return self.sql(exp.Bracket(this=this, expressions=[expr])) - - return self.sql( - exp.JSONExtract(this=this, expression=self.dialect.to_json_path(expr)) - ) - - def datefromunixdate_sql(self, expression: exp.DateFromUnixDate) -> str: - return self.sql( - exp.DateAdd( - this=exp.cast(exp.Literal.string("1970-01-01"), exp.DataType.Type.DATE), - expression=expression.this, - unit=exp.var("DAY"), - ) - ) - - def space_sql(self: Generator, expression: exp.Space) -> str: - return self.sql(exp.Repeat(this=exp.Literal.string(" "), times=expression.this)) - - def buildproperty_sql(self, expression: exp.BuildProperty) -> str: - return f"BUILD {self.sql(expression, 'this')}" - - def refreshtriggerproperty_sql(self, expression: exp.RefreshTriggerProperty) -> str: - method = self.sql(expression, "method") - kind = expression.args.get("kind") - if not kind: - return f"REFRESH {method}" - - every = self.sql(expression, "every") - unit = self.sql(expression, "unit") - every = f" EVERY {every} {unit}" if every else "" - starts = self.sql(expression, "starts") - starts = f" STARTS {starts}" if starts else "" - - return f"REFRESH {method} ON {kind}{every}{starts}" - - def modelattribute_sql(self, expression: exp.ModelAttribute) -> str: - self.unsupported("The model!attribute syntax is not supported") - return "" - - def directorystage_sql(self, expression: exp.DirectoryStage) -> str: - return self.func("DIRECTORY", expression.this) - - def uuid_sql(self, expression: exp.Uuid) -> str: - is_string = expression.args.get("is_string", False) - uuid_func_sql = self.func("UUID") - - if is_string and not self.dialect.UUID_IS_STRING_TYPE: - return self.sql( - exp.cast(uuid_func_sql, exp.DataType.Type.VARCHAR, dialect=self.dialect) - ) - - return uuid_func_sql - - def initcap_sql(self, expression: exp.Initcap) -> str: - delimiters = expression.expression - - if delimiters: - # do not generate delimiters arg if we are round-tripping from default delimiters - if ( - delimiters.is_string - and delimiters.this == self.dialect.INITCAP_DEFAULT_DELIMITER_CHARS - ): - delimiters = None - elif not self.dialect.INITCAP_SUPPORTS_CUSTOM_DELIMITERS: - self.unsupported("INITCAP does not support custom delimiters") - delimiters = None - - return self.func("INITCAP", expression.this, delimiters) - - def localtime_sql(self, expression: exp.Localtime) -> str: - this = expression.this - return self.func("LOCALTIME", this) if this else "LOCALTIME" - - def localtimestamp_sql(self, expression: exp.Localtime) -> str: - this = expression.this - return self.func("LOCALTIMESTAMP", this) if this else "LOCALTIMESTAMP" - - def weekstart_sql(self, expression: exp.WeekStart) -> str: - this = expression.this.name.upper() - if self.dialect.WEEK_OFFSET == -1 and this == "SUNDAY": - # BigQuery specific optimization since WEEK(SUNDAY) == WEEK - return "WEEK" - - return self.func("WEEK", expression.this) diff --git a/third_party/bigframes_vendored/sqlglot/helper.py b/third_party/bigframes_vendored/sqlglot/helper.py deleted file mode 100644 index 5cd16e2c3cd..00000000000 --- a/third_party/bigframes_vendored/sqlglot/helper.py +++ /dev/null @@ -1,532 +0,0 @@ -# Contains code from https://github.com/tobymao/sqlglot/blob/v28.5.0/sqlglot/helper.py - -from __future__ import annotations - -import datetime -import inspect -import logging -import re -import sys -import typing as t -from collections.abc import Collection, Set -from copy import copy -from difflib import get_close_matches -from enum import Enum -from itertools import count - -if t.TYPE_CHECKING: - from bigframes_vendored.sqlglot import exp - from bigframes_vendored.sqlglot._typing import A, E, T - from bigframes_vendored.sqlglot.dialects.dialect import DialectType - from bigframes_vendored.sqlglot.expressions import Expression - - -CAMEL_CASE_PATTERN = re.compile("(? t.Any: - return classmethod(self.fget).__get__(None, owner)() # type: ignore - - -def suggest_closest_match_and_fail( - kind: str, - word: str, - possibilities: t.Iterable[str], -) -> None: - close_matches = get_close_matches(word, possibilities, n=1) - - similar = seq_get(close_matches, 0) or "" - if similar: - similar = f" Did you mean {similar}?" - - raise ValueError(f"Unknown {kind} '{word}'.{similar}") - - -def seq_get(seq: t.Sequence[T], index: int) -> t.Optional[T]: - """Returns the value in `seq` at position `index`, or `None` if `index` is out of bounds.""" - try: - return seq[index] - except IndexError: - return None - - -@t.overload -def ensure_list(value: t.Collection[T]) -> t.List[T]: ... - - -@t.overload -def ensure_list(value: None) -> t.List: ... - - -@t.overload -def ensure_list(value: T) -> t.List[T]: ... - - -def ensure_list(value): - """ - Ensures that a value is a list, otherwise casts or wraps it into one. - - Args: - value: The value of interest. - - Returns: - The value cast as a list if it's a list or a tuple, or else the value wrapped in a list. - """ - if value is None: - return [] - if isinstance(value, (list, tuple)): - return list(value) - - return [value] - - -@t.overload -def ensure_collection(value: t.Collection[T]) -> t.Collection[T]: ... - - -@t.overload -def ensure_collection(value: T) -> t.Collection[T]: ... - - -def ensure_collection(value): - """ - Ensures that a value is a collection (excluding `str` and `bytes`), otherwise wraps it into a list. - - Args: - value: The value of interest. - - Returns: - The value if it's a collection, or else the value wrapped in a list. - """ - if value is None: - return [] - return ( - value - if isinstance(value, Collection) and not isinstance(value, (str, bytes)) - else [value] - ) - - -def csv(*args: str, sep: str = ", ") -> str: - """ - Formats any number of string arguments as CSV. - - Args: - args: The string arguments to format. - sep: The argument separator. - - Returns: - The arguments formatted as a CSV string. - """ - return sep.join(arg for arg in args if arg) - - -def subclasses( - module_name: str, - classes: t.Type | t.Tuple[t.Type, ...], - exclude: t.Set[t.Type] = set(), -) -> t.List[t.Type]: - """ - Returns all subclasses for a collection of classes, possibly excluding some of them. - - Args: - module_name: The name of the module to search for subclasses in. - classes: Class(es) we want to find the subclasses of. - exclude: Classes we want to exclude from the returned list. - - Returns: - The target subclasses. - """ - return [ - obj - for _, obj in inspect.getmembers( - sys.modules[module_name], - lambda obj: inspect.isclass(obj) - and issubclass(obj, classes) - and obj not in exclude, - ) - ] - - -def apply_index_offset( - this: exp.Expression, - expressions: t.List[E], - offset: int, - dialect: DialectType = None, -) -> t.List[E]: - """ - Applies an offset to a given integer literal expression. - - Args: - this: The target of the index. - expressions: The expression the offset will be applied to, wrapped in a list. - offset: The offset that will be applied. - dialect: the dialect of interest. - - Returns: - The original expression with the offset applied to it, wrapped in a list. If the provided - `expressions` argument contains more than one expression, it's returned unaffected. - """ - if not offset or len(expressions) != 1: - return expressions - - expression = expressions[0] - - from bigframes_vendored.sqlglot import exp - from bigframes_vendored.sqlglot.optimizer.annotate_types import annotate_types - from bigframes_vendored.sqlglot.optimizer.simplify import simplify - - if not this.type: - annotate_types(this, dialect=dialect) - - if t.cast(exp.DataType, this.type).this not in ( - exp.DataType.Type.UNKNOWN, - exp.DataType.Type.ARRAY, - ): - return expressions - - if not expression.type: - annotate_types(expression, dialect=dialect) - - if t.cast(exp.DataType, expression.type).this in exp.DataType.INTEGER_TYPES: - logger.info("Applying array index offset (%s)", offset) - expression = simplify(expression + offset) - return [expression] - - return expressions - - -def camel_to_snake_case(name: str) -> str: - """Converts `name` from camelCase to snake_case and returns the result.""" - return CAMEL_CASE_PATTERN.sub("_", name).upper() - - -def while_changing(expression: Expression, func: t.Callable[[Expression], E]) -> E: - """ - Applies a transformation to a given expression until a fix point is reached. - - Args: - expression: The expression to be transformed. - func: The transformation to be applied. - - Returns: - The transformed expression. - """ - - while True: - start_hash = hash(expression) - expression = func(expression) - end_hash = hash(expression) - - if start_hash == end_hash: - break - - return expression - - -def tsort(dag: t.Dict[T, t.Set[T]]) -> t.List[T]: - """ - Sorts a given directed acyclic graph in topological order. - - Args: - dag: The graph to be sorted. - - Returns: - A list that contains all of the graph's nodes in topological order. - """ - result = [] - - for node, deps in tuple(dag.items()): - for dep in deps: - if dep not in dag: - dag[dep] = set() - - while dag: - current = {node for node, deps in dag.items() if not deps} - - if not current: - raise ValueError("Cycle error") - - for node in current: - dag.pop(node) - - for deps in dag.values(): - deps -= current - - result.extend(sorted(current)) # type: ignore - - return result - - -def find_new_name(taken: t.Collection[str], base: str) -> str: - """ - Searches for a new name. - - Args: - taken: A collection of taken names. - base: Base name to alter. - - Returns: - The new, available name. - """ - if base not in taken: - return base - - i = 2 - new = f"{base}_{i}" - while new in taken: - i += 1 - new = f"{base}_{i}" - - return new - - -def is_int(text: str) -> bool: - return is_type(text, int) - - -def is_float(text: str) -> bool: - return is_type(text, float) - - -def is_type(text: str, target_type: t.Type) -> bool: - try: - target_type(text) - return True - except ValueError: - return False - - -def name_sequence(prefix: str) -> t.Callable[[], str]: - """Returns a name generator given a prefix (e.g. a0, a1, a2, ... if the prefix is "a").""" - sequence = count() - return lambda: f"{prefix}{next(sequence)}" - - -def object_to_dict(obj: t.Any, **kwargs) -> t.Dict: - """Returns a dictionary created from an object's attributes.""" - return { - **{ - k: v.copy() if hasattr(v, "copy") else copy(v) for k, v in vars(obj).items() - }, - **kwargs, - } - - -def split_num_words( - value: str, sep: str, min_num_words: int, fill_from_start: bool = True -) -> t.List[t.Optional[str]]: - """ - Perform a split on a value and return N words as a result with `None` used for words that don't exist. - - Args: - value: The value to be split. - sep: The value to use to split on. - min_num_words: The minimum number of words that are going to be in the result. - fill_from_start: Indicates that if `None` values should be inserted at the start or end of the list. - - Examples: - >>> split_num_words("db.table", ".", 3) - [None, 'db', 'table'] - >>> split_num_words("db.table", ".", 3, fill_from_start=False) - ['db', 'table', None] - >>> split_num_words("db.table", ".", 1) - ['db', 'table'] - - Returns: - The list of words returned by `split`, possibly augmented by a number of `None` values. - """ - words = value.split(sep) - if fill_from_start: - return [None] * (min_num_words - len(words)) + words - return words + [None] * (min_num_words - len(words)) - - -def is_iterable(value: t.Any) -> bool: - """ - Checks if the value is an iterable, excluding the types `str` and `bytes`. - - Examples: - >>> is_iterable([1,2]) - True - >>> is_iterable("test") - False - - Args: - value: The value to check if it is an iterable. - - Returns: - A `bool` value indicating if it is an iterable. - """ - from bigframes_vendored.sqlglot import Expression - - return hasattr(value, "__iter__") and not isinstance( - value, (str, bytes, Expression) - ) - - -def flatten(values: t.Iterable[t.Iterable[t.Any] | t.Any]) -> t.Iterator[t.Any]: - """ - Flattens an iterable that can contain both iterable and non-iterable elements. Objects of - type `str` and `bytes` are not regarded as iterables. - - Examples: - >>> list(flatten([[1, 2], 3, {4}, (5, "bla")])) - [1, 2, 3, 4, 5, 'bla'] - >>> list(flatten([1, 2, 3])) - [1, 2, 3] - - Args: - values: The value to be flattened. - - Yields: - Non-iterable elements in `values`. - """ - for value in values: - if is_iterable(value): - yield from flatten(value) - else: - yield value - - -def dict_depth(d: t.Dict) -> int: - """ - Get the nesting depth of a dictionary. - - Example: - >>> dict_depth(None) - 0 - >>> dict_depth({}) - 1 - >>> dict_depth({"a": "b"}) - 1 - >>> dict_depth({"a": {}}) - 2 - >>> dict_depth({"a": {"b": {}}}) - 3 - """ - try: - return 1 + dict_depth(next(iter(d.values()))) - except AttributeError: - # d doesn't have attribute "values" - return 0 - except StopIteration: - # d.values() returns an empty sequence - return 1 - - -def first(it: t.Iterable[T]) -> T: - """Returns the first element from an iterable (useful for sets).""" - return next(i for i in it) - - -def to_bool(value: t.Optional[str | bool]) -> t.Optional[str | bool]: - if isinstance(value, bool) or value is None: - return value - - # Coerce the value to boolean if it matches to the truthy/falsy values below - value_lower = value.lower() - if value_lower in ("true", "1"): - return True - if value_lower in ("false", "0"): - return False - - return value - - -def merge_ranges(ranges: t.List[t.Tuple[A, A]]) -> t.List[t.Tuple[A, A]]: - """ - Merges a sequence of ranges, represented as tuples (low, high) whose values - belong to some totally-ordered set. - - Example: - >>> merge_ranges([(1, 3), (2, 6)]) - [(1, 6)] - """ - if not ranges: - return [] - - ranges = sorted(ranges) - - merged = [ranges[0]] - - for start, end in ranges[1:]: - last_start, last_end = merged[-1] - - if start <= last_end: - merged[-1] = (last_start, max(last_end, end)) - else: - merged.append((start, end)) - - return merged - - -def is_iso_date(text: str) -> bool: - try: - datetime.date.fromisoformat(text) - return True - except ValueError: - return False - - -def is_iso_datetime(text: str) -> bool: - try: - datetime.datetime.fromisoformat(text) - return True - except ValueError: - return False - - -# Interval units that operate on date components -DATE_UNITS = {"day", "week", "month", "quarter", "year", "year_month"} - - -def is_date_unit(expression: t.Optional[exp.Expression]) -> bool: - return expression is not None and expression.name.lower() in DATE_UNITS - - -K = t.TypeVar("K") -V = t.TypeVar("V") - - -class SingleValuedMapping(t.Mapping[K, V]): - """ - Mapping where all keys return the same value. - - This rigamarole is meant to avoid copying keys, which was originally intended - as an optimization while qualifying columns for tables with lots of columns. - """ - - def __init__(self, keys: t.Collection[K], value: V): - self._keys = keys if isinstance(keys, Set) else set(keys) - self._value = value - - def __getitem__(self, key: K) -> V: - if key in self._keys: - return self._value - raise KeyError(key) - - def __len__(self) -> int: - return len(self._keys) - - def __iter__(self) -> t.Iterator[K]: - return iter(self._keys) diff --git a/third_party/bigframes_vendored/sqlglot/jsonpath.py b/third_party/bigframes_vendored/sqlglot/jsonpath.py deleted file mode 100644 index cc5ac7edd3d..00000000000 --- a/third_party/bigframes_vendored/sqlglot/jsonpath.py +++ /dev/null @@ -1,238 +0,0 @@ -# Contains code from https://github.com/tobymao/sqlglot/blob/v28.5.0/sqlglot/jsonpath.py - -from __future__ import annotations - -import typing as t - -import bigframes_vendored.sqlglot.expressions as exp -from bigframes_vendored.sqlglot.errors import ParseError -from bigframes_vendored.sqlglot.tokens import Token, Tokenizer, TokenType - -if t.TYPE_CHECKING: - from bigframes_vendored.sqlglot._typing import Lit - from bigframes_vendored.sqlglot.dialects.dialect import DialectType - - -class JSONPathTokenizer(Tokenizer): - SINGLE_TOKENS = { - "(": TokenType.L_PAREN, - ")": TokenType.R_PAREN, - "[": TokenType.L_BRACKET, - "]": TokenType.R_BRACKET, - ":": TokenType.COLON, - ",": TokenType.COMMA, - "-": TokenType.DASH, - ".": TokenType.DOT, - "?": TokenType.PLACEHOLDER, - "@": TokenType.PARAMETER, - "'": TokenType.QUOTE, - '"': TokenType.QUOTE, - "$": TokenType.DOLLAR, - "*": TokenType.STAR, - } - - KEYWORDS = { - "..": TokenType.DOT, - } - - IDENTIFIER_ESCAPES = ["\\"] - STRING_ESCAPES = ["\\"] - - VAR_TOKENS = { - TokenType.VAR, - } - - -def parse(path: str, dialect: DialectType = None) -> exp.JSONPath: - """Takes in a JSON path string and parses it into a JSONPath expression.""" - from bigframes_vendored.sqlglot.dialects import Dialect - - jsonpath_tokenizer = Dialect.get_or_raise(dialect).jsonpath_tokenizer() - tokens = jsonpath_tokenizer.tokenize(path) - size = len(tokens) - - i = 0 - - def _curr() -> t.Optional[TokenType]: - return tokens[i].token_type if i < size else None - - def _prev() -> Token: - return tokens[i - 1] - - def _advance() -> Token: - nonlocal i - i += 1 - return _prev() - - def _error(msg: str) -> str: - return f"{msg} at index {i}: {path}" - - @t.overload - def _match(token_type: TokenType, raise_unmatched: Lit[True] = True) -> Token: - pass - - @t.overload - def _match( - token_type: TokenType, raise_unmatched: Lit[False] = False - ) -> t.Optional[Token]: - pass - - def _match(token_type, raise_unmatched=False): - if _curr() == token_type: - return _advance() - if raise_unmatched: - raise ParseError(_error(f"Expected {token_type}")) - return None - - def _match_set(types: t.Collection[TokenType]) -> t.Optional[Token]: - return _advance() if _curr() in types else None - - def _parse_literal() -> t.Any: - token = _match(TokenType.STRING) or _match(TokenType.IDENTIFIER) - if token: - return token.text - if _match(TokenType.STAR): - return exp.JSONPathWildcard() - if _match(TokenType.PLACEHOLDER) or _match(TokenType.L_PAREN): - script = _prev().text == "(" - start = i - - while True: - if _match(TokenType.L_BRACKET): - _parse_bracket() # nested call which we can throw away - if _curr() in (TokenType.R_BRACKET, None): - break - _advance() - - expr_type = exp.JSONPathScript if script else exp.JSONPathFilter - return expr_type(this=path[tokens[start].start : tokens[i].end]) - - number = "-" if _match(TokenType.DASH) else "" - - token = _match(TokenType.NUMBER) - if token: - number += token.text - - if number: - return int(number) - - return False - - def _parse_slice() -> t.Any: - start = _parse_literal() - end = _parse_literal() if _match(TokenType.COLON) else None - step = _parse_literal() if _match(TokenType.COLON) else None - - if end is None and step is None: - return start - - return exp.JSONPathSlice(start=start, end=end, step=step) - - def _parse_bracket() -> exp.JSONPathPart: - literal = _parse_slice() - - if isinstance(literal, str) or literal is not False: - indexes = [literal] - while _match(TokenType.COMMA): - literal = _parse_slice() - - if literal: - indexes.append(literal) - - if len(indexes) == 1: - if isinstance(literal, str): - node: exp.JSONPathPart = exp.JSONPathKey(this=indexes[0]) - elif isinstance(literal, exp.JSONPathPart) and isinstance( - literal, (exp.JSONPathScript, exp.JSONPathFilter) - ): - node = exp.JSONPathSelector(this=indexes[0]) - else: - node = exp.JSONPathSubscript(this=indexes[0]) - else: - node = exp.JSONPathUnion(expressions=indexes) - else: - raise ParseError(_error("Cannot have empty segment")) - - _match(TokenType.R_BRACKET, raise_unmatched=True) - - return node - - def _parse_var_text() -> str: - """ - Consumes & returns the text for a var. In BigQuery it's valid to have a key with spaces - in it, e.g JSON_QUERY(..., '$. a b c ') should produce a single JSONPathKey(' a b c '). - This is done by merging "consecutive" vars until a key separator is found (dot, colon etc) - or the path string is exhausted. - """ - prev_index = i - 2 - - while _match_set(jsonpath_tokenizer.VAR_TOKENS): - pass - - start = 0 if prev_index < 0 else tokens[prev_index].end + 1 - - if i >= len(tokens): - # This key is the last token for the path, so it's text is the remaining path - text = path[start:] - else: - text = path[start : tokens[i].start] - - return text - - # We canonicalize the JSON path AST so that it always starts with a - # "root" element, so paths like "field" will be generated as "$.field" - _match(TokenType.DOLLAR) - expressions: t.List[exp.JSONPathPart] = [exp.JSONPathRoot()] - - while _curr(): - if _match(TokenType.DOT) or _match(TokenType.COLON): - recursive = _prev().text == ".." - - if _match_set(jsonpath_tokenizer.VAR_TOKENS): - value: t.Optional[str | exp.JSONPathWildcard] = _parse_var_text() - elif _match(TokenType.IDENTIFIER): - value = _prev().text - elif _match(TokenType.STAR): - value = exp.JSONPathWildcard() - else: - value = None - - if recursive: - expressions.append(exp.JSONPathRecursive(this=value)) - elif value: - expressions.append(exp.JSONPathKey(this=value)) - else: - raise ParseError(_error("Expected key name or * after DOT")) - elif _match(TokenType.L_BRACKET): - expressions.append(_parse_bracket()) - elif _match_set(jsonpath_tokenizer.VAR_TOKENS): - expressions.append(exp.JSONPathKey(this=_parse_var_text())) - elif _match(TokenType.IDENTIFIER): - expressions.append(exp.JSONPathKey(this=_prev().text)) - elif _match(TokenType.STAR): - expressions.append(exp.JSONPathWildcard()) - else: - raise ParseError(_error(f"Unexpected {tokens[i].token_type}")) - - return exp.JSONPath(expressions=expressions) - - -JSON_PATH_PART_TRANSFORMS: t.Dict[t.Type[exp.Expression], t.Callable[..., str]] = { - exp.JSONPathFilter: lambda _, e: f"?{e.this}", - exp.JSONPathKey: lambda self, e: self._jsonpathkey_sql(e), - exp.JSONPathRecursive: lambda _, e: f"..{e.this or ''}", - exp.JSONPathRoot: lambda *_: "$", - exp.JSONPathScript: lambda _, e: f"({e.this}", - exp.JSONPathSelector: lambda self, e: f"[{self.json_path_part(e.this)}]", - exp.JSONPathSlice: lambda self, e: ":".join( - "" if p is False else self.json_path_part(p) - for p in [e.args.get("start"), e.args.get("end"), e.args.get("step")] - if p is not None - ), - exp.JSONPathSubscript: lambda self, e: self._jsonpathsubscript_sql(e), - exp.JSONPathUnion: lambda self, - e: f"[{','.join(self.json_path_part(p) for p in e.expressions)}]", - exp.JSONPathWildcard: lambda *_: "*", -} - -ALL_JSON_PATH_PARTS = set(JSON_PATH_PART_TRANSFORMS) diff --git a/third_party/bigframes_vendored/sqlglot/lineage.py b/third_party/bigframes_vendored/sqlglot/lineage.py deleted file mode 100644 index 826e64bfdc8..00000000000 --- a/third_party/bigframes_vendored/sqlglot/lineage.py +++ /dev/null @@ -1,455 +0,0 @@ -# Contains code from https://github.com/tobymao/sqlglot/blob/v28.5.0/sqlglot/lineage.py - -from __future__ import annotations - -import json -import logging -import typing as t -from dataclasses import dataclass, field - -from bigframes_vendored.sqlglot import Schema, exp, maybe_parse -from bigframes_vendored.sqlglot.errors import SqlglotError -from bigframes_vendored.sqlglot.optimizer import ( - Scope, - build_scope, - find_all_in_scope, - normalize_identifiers, - qualify, -) -from bigframes_vendored.sqlglot.optimizer.scope import ScopeType - -if t.TYPE_CHECKING: - from bigframes_vendored.sqlglot.dialects.dialect import DialectType - -logger = logging.getLogger("sqlglot") - - -@dataclass(frozen=True) -class Node: - name: str - expression: exp.Expression - source: exp.Expression - downstream: t.List[Node] = field(default_factory=list) - source_name: str = "" - reference_node_name: str = "" - - def walk(self) -> t.Iterator[Node]: - yield self - - for d in self.downstream: - yield from d.walk() - - def to_html(self, dialect: DialectType = None, **opts) -> GraphHTML: - nodes = {} - edges = [] - - for node in self.walk(): - if isinstance(node.expression, exp.Table): - label = f"FROM {node.expression.this}" - title = f"
SELECT {node.name} FROM {node.expression.this}
" - group = 1 - else: - label = node.expression.sql(pretty=True, dialect=dialect) - source = node.source.transform( - lambda n: ( - exp.Tag(this=n, prefix="", postfix="") - if n is node.expression - else n - ), - copy=False, - ).sql(pretty=True, dialect=dialect) - title = f"
{source}
" - group = 0 - - node_id = id(node) - - nodes[node_id] = { - "id": node_id, - "label": label, - "title": title, - "group": group, - } - - for d in node.downstream: - edges.append({"from": node_id, "to": id(d)}) - return GraphHTML(nodes, edges, **opts) - - -def lineage( - column: str | exp.Column, - sql: str | exp.Expression, - schema: t.Optional[t.Dict | Schema] = None, - sources: t.Optional[t.Mapping[str, str | exp.Query]] = None, - dialect: DialectType = None, - scope: t.Optional[Scope] = None, - trim_selects: bool = True, - copy: bool = True, - **kwargs, -) -> Node: - """Build the lineage graph for a column of a SQL query. - - Args: - column: The column to build the lineage for. - sql: The SQL string or expression. - schema: The schema of tables. - sources: A mapping of queries which will be used to continue building lineage. - dialect: The dialect of input SQL. - scope: A pre-created scope to use instead. - trim_selects: Whether to clean up selects by trimming to only relevant columns. - copy: Whether to copy the Expression arguments. - **kwargs: Qualification optimizer kwargs. - - Returns: - A lineage node. - """ - - expression = maybe_parse(sql, copy=copy, dialect=dialect) - column = normalize_identifiers.normalize_identifiers(column, dialect=dialect).name - - if sources: - expression = exp.expand( - expression, - { - k: t.cast(exp.Query, maybe_parse(v, copy=copy, dialect=dialect)) - for k, v in sources.items() - }, - dialect=dialect, - copy=copy, - ) - - if not scope: - expression = qualify.qualify( - expression, - dialect=dialect, - schema=schema, - **{"validate_qualify_columns": False, "identify": False, **kwargs}, # type: ignore - ) - - scope = build_scope(expression) - - if not scope: - raise SqlglotError("Cannot build lineage, sql must be SELECT") - - if not any(select.alias_or_name == column for select in scope.expression.selects): - raise SqlglotError(f"Cannot find column '{column}' in query.") - - return to_node(column, scope, dialect, trim_selects=trim_selects) - - -def to_node( - column: str | int, - scope: Scope, - dialect: DialectType, - scope_name: t.Optional[str] = None, - upstream: t.Optional[Node] = None, - source_name: t.Optional[str] = None, - reference_node_name: t.Optional[str] = None, - trim_selects: bool = True, -) -> Node: - # Find the specific select clause that is the source of the column we want. - # This can either be a specific, named select or a generic `*` clause. - select = ( - scope.expression.selects[column] - if isinstance(column, int) - else next( - ( - select - for select in scope.expression.selects - if select.alias_or_name == column - ), - exp.Star() if scope.expression.is_star else scope.expression, - ) - ) - - if isinstance(scope.expression, exp.Subquery): - for source in scope.subquery_scopes: - return to_node( - column, - scope=source, - dialect=dialect, - upstream=upstream, - source_name=source_name, - reference_node_name=reference_node_name, - trim_selects=trim_selects, - ) - if isinstance(scope.expression, exp.SetOperation): - name = type(scope.expression).__name__.upper() - upstream = upstream or Node( - name=name, source=scope.expression, expression=select - ) - - index = ( - column - if isinstance(column, int) - else next( - ( - i - for i, select in enumerate(scope.expression.selects) - if select.alias_or_name == column or select.is_star - ), - -1, # mypy will not allow a None here, but a negative index should never be returned - ) - ) - - if index == -1: - raise ValueError(f"Could not find {column} in {scope.expression}") - - for s in scope.union_scopes: - to_node( - index, - scope=s, - dialect=dialect, - upstream=upstream, - source_name=source_name, - reference_node_name=reference_node_name, - trim_selects=trim_selects, - ) - - return upstream - - if trim_selects and isinstance(scope.expression, exp.Select): - # For better ergonomics in our node labels, replace the full select with - # a version that has only the column we care about. - # "x", SELECT x, y FROM foo - # => "x", SELECT x FROM foo - source = t.cast(exp.Expression, scope.expression.select(select, append=False)) - else: - source = scope.expression - - # Create the node for this step in the lineage chain, and attach it to the previous one. - node = Node( - name=f"{scope_name}.{column}" if scope_name else str(column), - source=source, - expression=select, - source_name=source_name or "", - reference_node_name=reference_node_name or "", - ) - - if upstream: - upstream.downstream.append(node) - - subquery_scopes = { - id(subquery_scope.expression): subquery_scope - for subquery_scope in scope.subquery_scopes - } - - for subquery in find_all_in_scope(select, exp.UNWRAPPED_QUERIES): - subquery_scope = subquery_scopes.get(id(subquery)) - if not subquery_scope: - logger.warning(f"Unknown subquery scope: {subquery.sql(dialect=dialect)}") - continue - - for name in subquery.named_selects: - to_node( - name, - scope=subquery_scope, - dialect=dialect, - upstream=node, - trim_selects=trim_selects, - ) - - # if the select is a star add all scope sources as downstreams - if isinstance(select, exp.Star): - for source in scope.sources.values(): - if isinstance(source, Scope): - source = source.expression - node.downstream.append( - Node(name=select.sql(comments=False), source=source, expression=source) - ) - - # Find all columns that went into creating this one to list their lineage nodes. - source_columns = set(find_all_in_scope(select, exp.Column)) - - # If the source is a UDTF find columns used in the UDTF to generate the table - if isinstance(source, exp.UDTF): - source_columns |= set(source.find_all(exp.Column)) - derived_tables = [ - source.expression.parent - for source in scope.sources.values() - if isinstance(source, Scope) and source.is_derived_table - ] - else: - derived_tables = scope.derived_tables - - source_names = { - dt.alias: dt.comments[0].split()[1] - for dt in derived_tables - if dt.comments and dt.comments[0].startswith("source: ") - } - - pivots = scope.pivots - pivot = pivots[0] if len(pivots) == 1 and not pivots[0].unpivot else None - if pivot: - # For each aggregation function, the pivot creates a new column for each field in category - # combined with the aggfunc. So the columns parsed have this order: cat_a_value_sum, cat_a, - # b_value_sum, b. Because of this step wise manner the aggfunc 'sum(value) as value_sum' - # belongs to the column indices 0, 2, and the aggfunc 'max(price)' without an alias belongs - # to the column indices 1, 3. Here, only the columns used in the aggregations are of interest - # in the lineage, so lookup the pivot column name by index and map that with the columns used - # in the aggregation. - # - # Example: PIVOT (SUM(value) AS value_sum, MAX(price)) FOR category IN ('a' AS cat_a, 'b') - pivot_columns = pivot.args["columns"] - pivot_aggs_count = len(pivot.expressions) - - pivot_column_mapping = {} - for i, agg in enumerate(pivot.expressions): - agg_cols = list(agg.find_all(exp.Column)) - for col_index in range(i, len(pivot_columns), pivot_aggs_count): - pivot_column_mapping[pivot_columns[col_index].name] = agg_cols - - for c in source_columns: - table = c.table - source = scope.sources.get(table) - - if isinstance(source, Scope): - reference_node_name = None - if ( - source.scope_type == ScopeType.DERIVED_TABLE - and table not in source_names - ): - reference_node_name = table - elif source.scope_type == ScopeType.CTE: - selected_node, _ = scope.selected_sources.get(table, (None, None)) - reference_node_name = selected_node.name if selected_node else None - - # The table itself came from a more specific scope. Recurse into that one using the unaliased column name. - to_node( - c.name, - scope=source, - dialect=dialect, - scope_name=table, - upstream=node, - source_name=source_names.get(table) or source_name, - reference_node_name=reference_node_name, - trim_selects=trim_selects, - ) - elif pivot and pivot.alias_or_name == c.table: - downstream_columns = [] - - column_name = c.name - if any(column_name == pivot_column.name for pivot_column in pivot_columns): - downstream_columns.extend(pivot_column_mapping[column_name]) - else: - # The column is not in the pivot, so it must be an implicit column of the - # pivoted source -- adapt column to be from the implicit pivoted source. - downstream_columns.append( - exp.column(c.this, table=pivot.parent.alias_or_name) - ) - - for downstream_column in downstream_columns: - table = downstream_column.table - source = scope.sources.get(table) - if isinstance(source, Scope): - to_node( - downstream_column.name, - scope=source, - scope_name=table, - dialect=dialect, - upstream=node, - source_name=source_names.get(table) or source_name, - reference_node_name=reference_node_name, - trim_selects=trim_selects, - ) - else: - source = source or exp.Placeholder() - node.downstream.append( - Node( - name=downstream_column.sql(comments=False), - source=source, - expression=source, - ) - ) - else: - # The source is not a scope and the column is not in any pivot - we've reached the end - # of the line. At this point, if a source is not found it means this column's lineage - # is unknown. This can happen if the definition of a source used in a query is not - # passed into the `sources` map. - source = source or exp.Placeholder() - node.downstream.append( - Node(name=c.sql(comments=False), source=source, expression=source) - ) - - return node - - -class GraphHTML: - """Node to HTML generator using vis.js. - - https://visjs.github.io/vis-network/docs/network/ - """ - - def __init__( - self, - nodes: t.Dict, - edges: t.List, - imports: bool = True, - options: t.Optional[t.Dict] = None, - ): - self.imports = imports - - self.options = { - "height": "500px", - "width": "100%", - "layout": { - "hierarchical": { - "enabled": True, - "nodeSpacing": 200, - "sortMethod": "directed", - }, - }, - "interaction": { - "dragNodes": False, - "selectable": False, - }, - "physics": { - "enabled": False, - }, - "edges": { - "arrows": "to", - }, - "nodes": { - "font": "20px monaco", - "shape": "box", - "widthConstraint": { - "maximum": 300, - }, - }, - **(options or {}), - } - - self.nodes = nodes - self.edges = edges - - def __str__(self): - nodes = json.dumps(list(self.nodes.values())) - edges = json.dumps(self.edges) - options = json.dumps(self.options) - imports = ( - """ - - """ - if self.imports - else "" - ) - - return f"""
-
- {imports} - -
""" - - def _repr_html_(self) -> str: - return self.__str__() diff --git a/third_party/bigframes_vendored/sqlglot/optimizer/__init__.py b/third_party/bigframes_vendored/sqlglot/optimizer/__init__.py deleted file mode 100644 index 9cc759fbe23..00000000000 --- a/third_party/bigframes_vendored/sqlglot/optimizer/__init__.py +++ /dev/null @@ -1,24 +0,0 @@ -# Contains code from https://github.com/tobymao/sqlglot/blob/v28.5.0/sqlglot/optimizer/__init__.py - -# ruff: noqa: F401 - -from bigframes_vendored.sqlglot.optimizer.optimizer import RULES as RULES # noqa: F401 -from bigframes_vendored.sqlglot.optimizer.optimizer import ( # noqa: F401 - optimize as optimize, -) -from bigframes_vendored.sqlglot.optimizer.scope import Scope as Scope # noqa: F401 -from bigframes_vendored.sqlglot.optimizer.scope import ( # noqa: F401 - build_scope as build_scope, -) -from bigframes_vendored.sqlglot.optimizer.scope import ( # noqa: F401 - find_all_in_scope as find_all_in_scope, -) -from bigframes_vendored.sqlglot.optimizer.scope import ( # noqa: F401 - find_in_scope as find_in_scope, -) -from bigframes_vendored.sqlglot.optimizer.scope import ( # noqa: F401 - traverse_scope as traverse_scope, -) -from bigframes_vendored.sqlglot.optimizer.scope import ( # noqa: F401 - walk_in_scope as walk_in_scope, -) diff --git a/third_party/bigframes_vendored/sqlglot/optimizer/annotate_types.py b/third_party/bigframes_vendored/sqlglot/optimizer/annotate_types.py deleted file mode 100644 index cca95feee82..00000000000 --- a/third_party/bigframes_vendored/sqlglot/optimizer/annotate_types.py +++ /dev/null @@ -1,893 +0,0 @@ -# Contains code from https://github.com/tobymao/sqlglot/blob/v28.5.0/sqlglot/optimizer/annotate_types.py - -from __future__ import annotations - -import functools -import logging -import typing as t - -from bigframes_vendored.sqlglot import exp -from bigframes_vendored.sqlglot.dialects.dialect import Dialect -from bigframes_vendored.sqlglot.helper import ( - ensure_list, - is_date_unit, - is_iso_date, - is_iso_datetime, - seq_get, -) -from bigframes_vendored.sqlglot.optimizer.scope import Scope, traverse_scope -from bigframes_vendored.sqlglot.schema import MappingSchema, Schema, ensure_schema - -if t.TYPE_CHECKING: - from bigframes_vendored.sqlglot._typing import B, E - - BinaryCoercionFunc = t.Callable[[exp.Expression, exp.Expression], exp.DataType.Type] - BinaryCoercions = t.Dict[ - t.Tuple[exp.DataType.Type, exp.DataType.Type], - BinaryCoercionFunc, - ] - - from bigframes_vendored.sqlglot.dialects.dialect import DialectType - from bigframes_vendored.sqlglot.typing import ExpressionMetadataType - -logger = logging.getLogger("sqlglot") - - -def annotate_types( - expression: E, - schema: t.Optional[t.Dict | Schema] = None, - expression_metadata: t.Optional[ExpressionMetadataType] = None, - coerces_to: t.Optional[t.Dict[exp.DataType.Type, t.Set[exp.DataType.Type]]] = None, - dialect: DialectType = None, - overwrite_types: bool = True, -) -> E: - """ - Infers the types of an expression, annotating its AST accordingly. - - Example: - >>> import sqlglot - >>> schema = {"y": {"cola": "SMALLINT"}} - >>> sql = "SELECT x.cola + 2.5 AS cola FROM (SELECT y.cola AS cola FROM y AS y) AS x" - >>> annotated_expr = annotate_types(sqlglot.parse_one(sql), schema=schema) - >>> annotated_expr.expressions[0].type.this # Get the type of "x.cola + 2.5 AS cola" - - - Args: - expression: Expression to annotate. - schema: Database schema. - expression_metadata: Maps expression type to corresponding annotation function. - coerces_to: Maps expression type to set of types that it can be coerced into. - overwrite_types: Re-annotate the existing AST types. - - Returns: - The expression annotated with types. - """ - - schema = ensure_schema(schema, dialect=dialect) - - return TypeAnnotator( - schema=schema, - expression_metadata=expression_metadata, - coerces_to=coerces_to, - overwrite_types=overwrite_types, - ).annotate(expression) - - -def _coerce_date_literal( - l: exp.Expression, unit: t.Optional[exp.Expression] -) -> exp.DataType.Type: - date_text = l.name - is_iso_date_ = is_iso_date(date_text) - - if is_iso_date_ and is_date_unit(unit): - return exp.DataType.Type.DATE - - # An ISO date is also an ISO datetime, but not vice versa - if is_iso_date_ or is_iso_datetime(date_text): - return exp.DataType.Type.DATETIME - - return exp.DataType.Type.UNKNOWN - - -def _coerce_date( - l: exp.Expression, unit: t.Optional[exp.Expression] -) -> exp.DataType.Type: - if not is_date_unit(unit): - return exp.DataType.Type.DATETIME - return l.type.this if l.type else exp.DataType.Type.UNKNOWN - - -def swap_args(func: BinaryCoercionFunc) -> BinaryCoercionFunc: - @functools.wraps(func) - def _swapped(ll: exp.Expression, r: exp.Expression) -> exp.DataType.Type: - return func(r, ll) - - return _swapped - - -def swap_all(coercions: BinaryCoercions) -> BinaryCoercions: - return { - **coercions, - **{(b, a): swap_args(func) for (a, b), func in coercions.items()}, - } - - -class _TypeAnnotator(type): - def __new__(cls, clsname, bases, attrs): - klass = super().__new__(cls, clsname, bases, attrs) - - # Highest-to-lowest type precedence, as specified in Spark's docs (ANSI): - # https://spark.apache.org/docs/3.2.0/sql-ref-ansi-compliance.html - text_precedence = ( - exp.DataType.Type.TEXT, - exp.DataType.Type.NVARCHAR, - exp.DataType.Type.VARCHAR, - exp.DataType.Type.NCHAR, - exp.DataType.Type.CHAR, - ) - numeric_precedence = ( - exp.DataType.Type.DECFLOAT, - exp.DataType.Type.DOUBLE, - exp.DataType.Type.FLOAT, - exp.DataType.Type.BIGDECIMAL, - exp.DataType.Type.DECIMAL, - exp.DataType.Type.BIGINT, - exp.DataType.Type.INT, - exp.DataType.Type.SMALLINT, - exp.DataType.Type.TINYINT, - ) - timelike_precedence = ( - exp.DataType.Type.TIMESTAMPLTZ, - exp.DataType.Type.TIMESTAMPTZ, - exp.DataType.Type.TIMESTAMP, - exp.DataType.Type.DATETIME, - exp.DataType.Type.DATE, - ) - - for type_precedence in ( - text_precedence, - numeric_precedence, - timelike_precedence, - ): - coerces_to = set() - for data_type in type_precedence: - klass.COERCES_TO[data_type] = coerces_to.copy() - coerces_to |= {data_type} - return klass - - -class TypeAnnotator(metaclass=_TypeAnnotator): - NESTED_TYPES = { - exp.DataType.Type.ARRAY, - } - - # Specifies what types a given type can be coerced into (autofilled) - COERCES_TO: t.Dict[exp.DataType.Type, t.Set[exp.DataType.Type]] = {} - - # Coercion functions for binary operations. - # Map of type pairs to a callable that takes both sides of the binary operation and returns the resulting type. - BINARY_COERCIONS: BinaryCoercions = { - **swap_all( - { - (t, exp.DataType.Type.INTERVAL): lambda ll, r: _coerce_date_literal( - ll, r.args.get("unit") - ) - for t in exp.DataType.TEXT_TYPES - } - ), - **swap_all( - { - # text + numeric will yield the numeric type to match most dialects' semantics - (text, numeric): lambda ll, r: t.cast( - exp.DataType.Type, - ll.type if ll.type in exp.DataType.NUMERIC_TYPES else r.type, - ) - for text in exp.DataType.TEXT_TYPES - for numeric in exp.DataType.NUMERIC_TYPES - } - ), - **swap_all( - { - ( - exp.DataType.Type.DATE, - exp.DataType.Type.INTERVAL, - ): lambda ll, r: _coerce_date(ll, r.args.get("unit")), - } - ), - } - - def __init__( - self, - schema: Schema, - expression_metadata: t.Optional[ExpressionMetadataType] = None, - coerces_to: t.Optional[ - t.Dict[exp.DataType.Type, t.Set[exp.DataType.Type]] - ] = None, - binary_coercions: t.Optional[BinaryCoercions] = None, - overwrite_types: bool = True, - ) -> None: - self.schema = schema - dialect = schema.dialect or Dialect() - self.dialect = dialect - self.expression_metadata = expression_metadata or dialect.EXPRESSION_METADATA - self.coerces_to = coerces_to or dialect.COERCES_TO or self.COERCES_TO - self.binary_coercions = binary_coercions or self.BINARY_COERCIONS - - # Caches the ids of annotated sub-Expressions, to ensure we only visit them once - self._visited: t.Set[int] = set() - - # Caches NULL-annotated expressions to set them to UNKNOWN after type inference is completed - self._null_expressions: t.Dict[int, exp.Expression] = {} - - # Databricks and Spark ≥v3 actually support NULL (i.e., VOID) as a type - self._supports_null_type = dialect.SUPPORTS_NULL_TYPE - - # Maps an exp.SetOperation's id (e.g. UNION) to its projection types. This is computed if the - # exp.SetOperation is the expression of a scope source, as selecting from it multiple times - # would reprocess the entire subtree to coerce the types of its operands' projections - self._setop_column_types: t.Dict[ - int, t.Dict[str, exp.DataType | exp.DataType.Type] - ] = {} - - # When set to False, this enables partial annotation by skipping already-annotated nodes - self._overwrite_types = overwrite_types - - def clear(self) -> None: - self._visited.clear() - self._null_expressions.clear() - self._setop_column_types.clear() - - def _set_type( - self, expression: E, target_type: t.Optional[exp.DataType | exp.DataType.Type] - ) -> E: - prev_type = expression.type - expression_id = id(expression) - - expression.type = target_type or exp.DataType.Type.UNKNOWN # type: ignore - self._visited.add(expression_id) - - if ( - not self._supports_null_type - and t.cast(exp.DataType, expression.type).this == exp.DataType.Type.NULL - ): - self._null_expressions[expression_id] = expression - elif ( - prev_type and t.cast(exp.DataType, prev_type).this == exp.DataType.Type.NULL - ): - self._null_expressions.pop(expression_id, None) - - if ( - isinstance(expression, exp.Column) - and expression.is_type(exp.DataType.Type.JSON) - and (dot_parts := expression.meta.get("dot_parts")) - ): - # JSON dot access is case sensitive across all dialects, so we need to undo the normalization. - i = iter(dot_parts) - parent = expression.parent - while isinstance(parent, exp.Dot): - parent.expression.set("this", exp.to_identifier(next(i), quoted=True)) - parent = parent.parent - - expression.meta.pop("dot_parts", None) - - return expression - - def annotate(self, expression: E, annotate_scope: bool = True) -> E: - # This flag is used to avoid costly scope traversals when we only care about annotating - # non-column expressions (partial type inference), e.g., when simplifying in the optimizer - if annotate_scope: - for scope in traverse_scope(expression): - self.annotate_scope(scope) - - # This takes care of non-traversable expressions - self._annotate_expression(expression) - - # Replace NULL type with the default type of the targeted dialect, since the former is not an actual type; - # it is mostly used to aid type coercion, e.g. in query set operations. - for expr in self._null_expressions.values(): - expr.type = self.dialect.DEFAULT_NULL_TYPE - - return expression - - def annotate_scope(self, scope: Scope) -> None: - selects = {} - - for name, source in scope.sources.items(): - if not isinstance(source, Scope): - continue - - expression = source.expression - if isinstance(expression, exp.UDTF): - values = [] - - if isinstance(expression, exp.Lateral): - if isinstance(expression.this, exp.Explode): - values = [expression.this.this] - elif isinstance(expression, exp.Unnest): - values = [expression] - elif not isinstance(expression, exp.TableFromRows): - values = expression.expressions[0].expressions - - if not values: - continue - - alias_column_names = expression.alias_column_names - - if ( - isinstance(expression, exp.Unnest) - and not alias_column_names - and expression.type - and expression.type.is_type(exp.DataType.Type.STRUCT) - ): - selects[name] = { - col_def.name: t.cast( - t.Union[exp.DataType, exp.DataType.Type], col_def.kind - ) - for col_def in expression.type.expressions - if isinstance(col_def, exp.ColumnDef) and col_def.kind - } - else: - selects[name] = { - alias: column.type - for alias, column in zip(alias_column_names, values) - } - elif isinstance(expression, exp.SetOperation) and len( - expression.left.selects - ) == len(expression.right.selects): - selects[name] = self._get_setop_column_types(expression) - - else: - selects[name] = {s.alias_or_name: s.type for s in expression.selects} - - if isinstance(self.schema, MappingSchema): - for table_column in scope.table_columns: - source = scope.sources.get(table_column.name) - - if isinstance(source, exp.Table): - schema = self.schema.find( - source, raise_on_missing=False, ensure_data_types=True - ) - if not isinstance(schema, dict): - continue - - struct_type = exp.DataType( - this=exp.DataType.Type.STRUCT, - expressions=[ - exp.ColumnDef(this=exp.to_identifier(c), kind=kind) - for c, kind in schema.items() - ], - nested=True, - ) - self._set_type(table_column, struct_type) - elif ( - isinstance(source, Scope) - and isinstance(source.expression, exp.Query) - and ( - source.expression.meta.get("query_type") - or exp.DataType.build("UNKNOWN") - ).is_type(exp.DataType.Type.STRUCT) - ): - self._set_type(table_column, source.expression.meta["query_type"]) - - # Iterate through all the expressions of the current scope in post-order, and annotate - self._annotate_expression(scope.expression, scope, selects) - - if self.dialect.QUERY_RESULTS_ARE_STRUCTS and isinstance( - scope.expression, exp.Query - ): - struct_type = exp.DataType( - this=exp.DataType.Type.STRUCT, - expressions=[ - exp.ColumnDef( - this=exp.to_identifier(select.output_name), - kind=select.type.copy() if select.type else None, - ) - for select in scope.expression.selects - ], - nested=True, - ) - - if not any( - cd.kind.is_type(exp.DataType.Type.UNKNOWN) - for cd in struct_type.expressions - if cd.kind - ): - # We don't use `_set_type` on purpose here. If we annotated the query directly, then - # using it in other contexts (e.g., ARRAY()) could result in incorrect type - # annotations, i.e., it shouldn't be interpreted as a STRUCT value. - scope.expression.meta["query_type"] = struct_type - - def _annotate_expression( - self, - expression: exp.Expression, - scope: t.Optional[Scope] = None, - selects: t.Optional[t.Dict[str, t.Dict[str, t.Any]]] = None, - ) -> None: - stack = [(expression, False)] - selects = selects or {} - - while stack: - expr, children_annotated = stack.pop() - - if id(expr) in self._visited or ( - not self._overwrite_types - and expr.type - and not expr.is_type(exp.DataType.Type.UNKNOWN) - ): - continue # We've already inferred the expression's type - - if not children_annotated: - stack.append((expr, True)) - for child_expr in expr.iter_expressions(): - stack.append((child_expr, False)) - continue - - if scope and isinstance(expr, exp.Column) and expr.table: - source = scope.sources.get(expr.table) - if isinstance(source, exp.Table): - self._set_type(expr, self.schema.get_column_type(source, expr)) - elif source: - if expr.table in selects and expr.name in selects[expr.table]: - self._set_type(expr, selects[expr.table][expr.name]) - elif isinstance(source.expression, exp.Unnest): - self._set_type(expr, source.expression.type) - else: - self._set_type(expr, exp.DataType.Type.UNKNOWN) - else: - self._set_type(expr, exp.DataType.Type.UNKNOWN) - - if expr.type and expr.type.args.get("nullable") is False: - expr.meta["nonnull"] = True - continue - - spec = self.expression_metadata.get(expr.__class__) - - if spec and (annotator := spec.get("annotator")): - annotator(self, expr) - elif spec and (returns := spec.get("returns")): - self._set_type(expr, t.cast(exp.DataType.Type, returns)) - else: - self._set_type(expr, exp.DataType.Type.UNKNOWN) - - def _maybe_coerce( - self, - type1: exp.DataType | exp.DataType.Type, - type2: exp.DataType | exp.DataType.Type, - ) -> exp.DataType | exp.DataType.Type: - """ - Returns type2 if type1 can be coerced into it, otherwise type1. - - If either type is parameterized (e.g. DECIMAL(18, 2) contains two parameters), - we assume type1 does not coerce into type2, so we also return it in this case. - """ - if isinstance(type1, exp.DataType): - if type1.expressions: - return type1 - type1_value = type1.this - else: - type1_value = type1 - - if isinstance(type2, exp.DataType): - if type2.expressions: - return type2 - type2_value = type2.this - else: - type2_value = type2 - - # We propagate the UNKNOWN type upwards if found - if exp.DataType.Type.UNKNOWN in (type1_value, type2_value): - return exp.DataType.Type.UNKNOWN - - if type1_value == exp.DataType.Type.NULL: - return type2_value - if type2_value == exp.DataType.Type.NULL: - return type1_value - - return ( - type2_value - if type2_value in self.coerces_to.get(type1_value, {}) - else type1_value - ) - - def _get_setop_column_types( - self, setop: exp.SetOperation - ) -> t.Dict[str, exp.DataType | exp.DataType.Type]: - """ - Computes and returns the coerced column types for a SetOperation. - - This handles UNION, INTERSECT, EXCEPT, etc., coercing types across - left and right operands for all projections/columns. - - Args: - setop: The SetOperation expression to analyze - - Returns: - Dictionary mapping column names to their coerced types - """ - setop_id = id(setop) - if setop_id in self._setop_column_types: - return self._setop_column_types[setop_id] - - col_types: t.Dict[str, exp.DataType | exp.DataType.Type] = {} - - # Validate that left and right have same number of projections - if not ( - isinstance(setop, exp.SetOperation) - and setop.left.selects - and setop.right.selects - and len(setop.left.selects) == len(setop.right.selects) - ): - return col_types - - # Process a chain / sub-tree of set operations - for set_op in setop.walk( - prune=lambda n: not isinstance(n, (exp.SetOperation, exp.Subquery)) - ): - if not isinstance(set_op, exp.SetOperation): - continue - - if set_op.args.get("by_name"): - r_type_by_select = { - s.alias_or_name: s.type for s in set_op.right.selects - } - setop_cols = { - s.alias_or_name: self._maybe_coerce( - t.cast(exp.DataType, s.type), - r_type_by_select.get(s.alias_or_name) - or exp.DataType.Type.UNKNOWN, - ) - for s in set_op.left.selects - } - else: - setop_cols = { - ls.alias_or_name: self._maybe_coerce( - t.cast(exp.DataType, ls.type), t.cast(exp.DataType, rs.type) - ) - for ls, rs in zip(set_op.left.selects, set_op.right.selects) - } - - # Coerce intermediate results with the previously registered types, if they exist - for col_name, col_type in setop_cols.items(): - col_types[col_name] = self._maybe_coerce( - col_type, col_types.get(col_name, exp.DataType.Type.NULL) - ) - - self._setop_column_types[setop_id] = col_types - return col_types - - def _annotate_binary(self, expression: B) -> B: - left, right = expression.left, expression.right - if not left or not right: - expression_sql = expression.sql(self.dialect) - logger.warning( - f"Failed to annotate badly formed binary expression: {expression_sql}" - ) - self._set_type(expression, None) - return expression - - left_type, right_type = left.type.this, right.type.this # type: ignore - - if isinstance(expression, (exp.Connector, exp.Predicate)): - self._set_type(expression, exp.DataType.Type.BOOLEAN) - elif (left_type, right_type) in self.binary_coercions: - self._set_type( - expression, self.binary_coercions[(left_type, right_type)](left, right) - ) - else: - self._set_type(expression, self._maybe_coerce(left_type, right_type)) - - if isinstance(expression, exp.Is) or ( - left.meta.get("nonnull") is True and right.meta.get("nonnull") is True - ): - expression.meta["nonnull"] = True - - return expression - - def _annotate_unary(self, expression: E) -> E: - if isinstance(expression, exp.Not): - self._set_type(expression, exp.DataType.Type.BOOLEAN) - else: - self._set_type(expression, expression.this.type) - - if expression.this.meta.get("nonnull") is True: - expression.meta["nonnull"] = True - - return expression - - def _annotate_literal(self, expression: exp.Literal) -> exp.Literal: - if expression.is_string: - self._set_type(expression, exp.DataType.Type.VARCHAR) - elif expression.is_int: - self._set_type(expression, exp.DataType.Type.INT) - else: - self._set_type(expression, exp.DataType.Type.DOUBLE) - - expression.meta["nonnull"] = True - - return expression - - @t.no_type_check - def _annotate_by_args( - self, - expression: E, - *args: str | exp.Expression, - promote: bool = False, - array: bool = False, - ) -> E: - literal_type = None - non_literal_type = None - nested_type = None - - for arg in args: - if isinstance(arg, str): - expressions = expression.args.get(arg) - else: - expressions = arg - - for expr in ensure_list(expressions): - expr_type = expr.type - - # Stop at the first nested data type found - we don't want to _maybe_coerce nested types - if expr_type.args.get("nested"): - nested_type = expr_type - break - - if not expr_type.is_type(exp.DataType.Type.UNKNOWN): - if isinstance(expr, exp.Literal): - literal_type = self._maybe_coerce( - literal_type or expr_type, expr_type - ) - else: - non_literal_type = self._maybe_coerce( - non_literal_type or expr_type, expr_type - ) - - if nested_type: - break - - result_type = None - - if nested_type: - result_type = nested_type - elif literal_type and non_literal_type: - if self.dialect.PRIORITIZE_NON_LITERAL_TYPES: - literal_this_type = ( - literal_type.this - if isinstance(literal_type, exp.DataType) - else literal_type - ) - non_literal_this_type = ( - non_literal_type.this - if isinstance(non_literal_type, exp.DataType) - else non_literal_type - ) - if ( - literal_this_type in exp.DataType.INTEGER_TYPES - and non_literal_this_type in exp.DataType.INTEGER_TYPES - ) or ( - literal_this_type in exp.DataType.REAL_TYPES - and non_literal_this_type in exp.DataType.REAL_TYPES - ): - result_type = non_literal_type - else: - result_type = literal_type or non_literal_type or exp.DataType.Type.UNKNOWN - - self._set_type( - expression, - result_type or self._maybe_coerce(non_literal_type, literal_type), - ) - - if promote: - if expression.type.this in exp.DataType.INTEGER_TYPES: - self._set_type(expression, exp.DataType.Type.BIGINT) - elif expression.type.this in exp.DataType.FLOAT_TYPES: - self._set_type(expression, exp.DataType.Type.DOUBLE) - - if array: - self._set_type( - expression, - exp.DataType( - this=exp.DataType.Type.ARRAY, - expressions=[expression.type], - nested=True, - ), - ) - - return expression - - def _annotate_timeunit( - self, expression: exp.TimeUnit | exp.DateTrunc - ) -> exp.TimeUnit | exp.DateTrunc: - if expression.this.type.this in exp.DataType.TEXT_TYPES: - datatype = _coerce_date_literal(expression.this, expression.unit) - elif expression.this.type.this in exp.DataType.TEMPORAL_TYPES: - datatype = _coerce_date(expression.this, expression.unit) - else: - datatype = exp.DataType.Type.UNKNOWN - - self._set_type(expression, datatype) - return expression - - def _annotate_bracket(self, expression: exp.Bracket) -> exp.Bracket: - bracket_arg = expression.expressions[0] - this = expression.this - - if isinstance(bracket_arg, exp.Slice): - self._set_type(expression, this.type) - elif this.type.is_type(exp.DataType.Type.ARRAY): - self._set_type(expression, seq_get(this.type.expressions, 0)) - elif isinstance(this, (exp.Map, exp.VarMap)) and bracket_arg in this.keys: - index = this.keys.index(bracket_arg) - value = seq_get(this.values, index) - self._set_type(expression, value.type if value else None) - else: - self._set_type(expression, exp.DataType.Type.UNKNOWN) - - return expression - - def _annotate_div(self, expression: exp.Div) -> exp.Div: - left_type, right_type = expression.left.type.this, expression.right.type.this # type: ignore - - if ( - expression.args.get("typed") - and left_type in exp.DataType.INTEGER_TYPES - and right_type in exp.DataType.INTEGER_TYPES - ): - self._set_type(expression, exp.DataType.Type.BIGINT) - else: - self._set_type(expression, self._maybe_coerce(left_type, right_type)) - if expression.type and expression.type.this not in exp.DataType.REAL_TYPES: - self._set_type( - expression, - self._maybe_coerce(expression.type, exp.DataType.Type.DOUBLE), - ) - - return expression - - def _annotate_dot(self, expression: exp.Dot) -> exp.Dot: - self._set_type(expression, None) - this_type = expression.this.type - - if this_type and this_type.is_type(exp.DataType.Type.STRUCT): - for e in this_type.expressions: - if e.name == expression.expression.name: - self._set_type(expression, e.kind) - break - - return expression - - def _annotate_explode(self, expression: exp.Explode) -> exp.Explode: - self._set_type(expression, seq_get(expression.this.type.expressions, 0)) - return expression - - def _annotate_unnest(self, expression: exp.Unnest) -> exp.Unnest: - child = seq_get(expression.expressions, 0) - - if child and child.is_type(exp.DataType.Type.ARRAY): - expr_type = seq_get(child.type.expressions, 0) - else: - expr_type = None - - self._set_type(expression, expr_type) - return expression - - def _annotate_subquery(self, expression: exp.Subquery) -> exp.Subquery: - # For scalar subqueries (subqueries with a single projection), infer the type - # from that single projection. This allows type propagation in cases like: - # SELECT (SELECT 1 AS c) AS c - query = expression.unnest() - - if isinstance(query, exp.Query): - selects = query.selects - if len(selects) == 1: - self._set_type(expression, selects[0].type) - return expression - - self._set_type(expression, exp.DataType.Type.UNKNOWN) - return expression - - def _annotate_struct_value( - self, expression: exp.Expression - ) -> t.Optional[exp.DataType] | exp.ColumnDef: - # Case: STRUCT(key AS value) - this: t.Optional[exp.Expression] = None - kind = expression.type - - if alias := expression.args.get("alias"): - this = alias.copy() - elif expression.expression: - # Case: STRUCT(key = value) or STRUCT(key := value) - this = expression.this.copy() - kind = expression.expression.type - elif isinstance(expression, exp.Column): - # Case: STRUCT(c) - this = expression.this.copy() - - if kind and kind.is_type(exp.DataType.Type.UNKNOWN): - return None - - if this: - return exp.ColumnDef(this=this, kind=kind) - - return kind - - def _annotate_struct(self, expression: exp.Struct) -> exp.Struct: - expressions = [] - for expr in expression.expressions: - struct_field_type = self._annotate_struct_value(expr) - if struct_field_type is None: - self._set_type(expression, None) - return expression - - expressions.append(struct_field_type) - - self._set_type( - expression, - exp.DataType( - this=exp.DataType.Type.STRUCT, expressions=expressions, nested=True - ), - ) - return expression - - @t.overload - def _annotate_map(self, expression: exp.Map) -> exp.Map: ... - - @t.overload - def _annotate_map(self, expression: exp.VarMap) -> exp.VarMap: ... - - def _annotate_map(self, expression): - keys = expression.args.get("keys") - values = expression.args.get("values") - - map_type = exp.DataType(this=exp.DataType.Type.MAP) - if isinstance(keys, exp.Array) and isinstance(values, exp.Array): - key_type = seq_get(keys.type.expressions, 0) or exp.DataType.Type.UNKNOWN - value_type = ( - seq_get(values.type.expressions, 0) or exp.DataType.Type.UNKNOWN - ) - - if ( - key_type != exp.DataType.Type.UNKNOWN - and value_type != exp.DataType.Type.UNKNOWN - ): - map_type.set("expressions", [key_type, value_type]) - map_type.set("nested", True) - - self._set_type(expression, map_type) - return expression - - def _annotate_to_map(self, expression: exp.ToMap) -> exp.ToMap: - map_type = exp.DataType(this=exp.DataType.Type.MAP) - arg = expression.this - if arg.is_type(exp.DataType.Type.STRUCT): - for coldef in arg.type.expressions: - kind = coldef.kind - if kind != exp.DataType.Type.UNKNOWN: - map_type.set("expressions", [exp.DataType.build("varchar"), kind]) - map_type.set("nested", True) - break - - self._set_type(expression, map_type) - return expression - - def _annotate_extract(self, expression: exp.Extract) -> exp.Extract: - part = expression.name - if part == "TIME": - self._set_type(expression, exp.DataType.Type.TIME) - elif part == "DATE": - self._set_type(expression, exp.DataType.Type.DATE) - else: - self._set_type(expression, exp.DataType.Type.INT) - return expression - - def _annotate_by_array_element(self, expression: exp.Expression) -> exp.Expression: - array_arg = expression.this - if array_arg.type.is_type(exp.DataType.Type.ARRAY): - element_type = ( - seq_get(array_arg.type.expressions, 0) or exp.DataType.Type.UNKNOWN - ) - self._set_type(expression, element_type) - else: - self._set_type(expression, exp.DataType.Type.UNKNOWN) - - return expression diff --git a/third_party/bigframes_vendored/sqlglot/optimizer/canonicalize.py b/third_party/bigframes_vendored/sqlglot/optimizer/canonicalize.py deleted file mode 100644 index ec17916e137..00000000000 --- a/third_party/bigframes_vendored/sqlglot/optimizer/canonicalize.py +++ /dev/null @@ -1,243 +0,0 @@ -# Contains code from https://github.com/tobymao/sqlglot/blob/v28.5.0/sqlglot/optimizer/canonicalize.py - -from __future__ import annotations - -import itertools -import typing as t - -from bigframes_vendored.sqlglot import exp -from bigframes_vendored.sqlglot.dialects.dialect import Dialect, DialectType -from bigframes_vendored.sqlglot.helper import is_date_unit, is_iso_date, is_iso_datetime -from bigframes_vendored.sqlglot.optimizer.annotate_types import TypeAnnotator - - -def canonicalize( - expression: exp.Expression, dialect: DialectType = None -) -> exp.Expression: - """Converts a sql expression into a standard form. - - This method relies on annotate_types because many of the - conversions rely on type inference. - - Args: - expression: The expression to canonicalize. - """ - - dialect = Dialect.get_or_raise(dialect) - - def _canonicalize(expression: exp.Expression) -> exp.Expression: - expression = add_text_to_concat(expression) - expression = replace_date_funcs(expression, dialect=dialect) - expression = coerce_type(expression, dialect.PROMOTE_TO_INFERRED_DATETIME_TYPE) - expression = remove_redundant_casts(expression) - expression = ensure_bools(expression, _replace_int_predicate) - expression = remove_ascending_order(expression) - return expression - - return exp.replace_tree(expression, _canonicalize) - - -def add_text_to_concat(node: exp.Expression) -> exp.Expression: - if ( - isinstance(node, exp.Add) - and node.type - and node.type.this in exp.DataType.TEXT_TYPES - ): - node = exp.Concat( - expressions=[node.left, node.right], - # All known dialects, i.e. Redshift and T-SQL, that support - # concatenating strings with the + operator do not coalesce NULLs. - coalesce=False, - ) - return node - - -def replace_date_funcs(node: exp.Expression, dialect: DialectType) -> exp.Expression: - if ( - isinstance(node, (exp.Date, exp.TsOrDsToDate)) - and not node.expressions - and not node.args.get("zone") - and node.this.is_string - and is_iso_date(node.this.name) - ): - return exp.cast(node.this, to=exp.DataType.Type.DATE) - if isinstance(node, exp.Timestamp) and not node.args.get("zone"): - if not node.type: - from bigframes_vendored.sqlglot.optimizer.annotate_types import ( - annotate_types, - ) - - node = annotate_types(node, dialect=dialect) - return exp.cast(node.this, to=node.type or exp.DataType.Type.TIMESTAMP) - - return node - - -COERCIBLE_DATE_OPS = ( - exp.Add, - exp.Sub, - exp.EQ, - exp.NEQ, - exp.GT, - exp.GTE, - exp.LT, - exp.LTE, - exp.NullSafeEQ, - exp.NullSafeNEQ, -) - - -def coerce_type( - node: exp.Expression, promote_to_inferred_datetime_type: bool -) -> exp.Expression: - if isinstance(node, COERCIBLE_DATE_OPS): - _coerce_date(node.left, node.right, promote_to_inferred_datetime_type) - elif isinstance(node, exp.Between): - _coerce_date(node.this, node.args["low"], promote_to_inferred_datetime_type) - elif isinstance(node, exp.Extract) and not node.expression.is_type( - *exp.DataType.TEMPORAL_TYPES - ): - _replace_cast(node.expression, exp.DataType.Type.DATETIME) - elif isinstance(node, (exp.DateAdd, exp.DateSub, exp.DateTrunc)): - _coerce_timeunit_arg(node.this, node.unit) - elif isinstance(node, exp.DateDiff): - _coerce_datediff_args(node) - - return node - - -def remove_redundant_casts(expression: exp.Expression) -> exp.Expression: - if ( - isinstance(expression, exp.Cast) - and expression.this.type - and expression.to == expression.this.type - ): - return expression.this - - if ( - isinstance(expression, (exp.Date, exp.TsOrDsToDate)) - and expression.this.type - and expression.this.type.this == exp.DataType.Type.DATE - and not expression.this.type.expressions - ): - return expression.this - - return expression - - -def ensure_bools( - expression: exp.Expression, replace_func: t.Callable[[exp.Expression], None] -) -> exp.Expression: - if isinstance(expression, exp.Connector): - replace_func(expression.left) - replace_func(expression.right) - elif isinstance(expression, exp.Not): - replace_func(expression.this) - # We can't replace num in CASE x WHEN num ..., because it's not the full predicate - elif isinstance(expression, exp.If) and not ( - isinstance(expression.parent, exp.Case) and expression.parent.this - ): - replace_func(expression.this) - elif isinstance(expression, (exp.Where, exp.Having)): - replace_func(expression.this) - - return expression - - -def remove_ascending_order(expression: exp.Expression) -> exp.Expression: - if isinstance(expression, exp.Ordered) and expression.args.get("desc") is False: - # Convert ORDER BY a ASC to ORDER BY a - expression.set("desc", None) - - return expression - - -def _coerce_date( - a: exp.Expression, - b: exp.Expression, - promote_to_inferred_datetime_type: bool, -) -> None: - for a, b in itertools.permutations([a, b]): - if isinstance(b, exp.Interval): - a = _coerce_timeunit_arg(a, b.unit) - - a_type = a.type - if ( - not a_type - or a_type.this not in exp.DataType.TEMPORAL_TYPES - or not b.type - or b.type.this not in exp.DataType.TEXT_TYPES - ): - continue - - if promote_to_inferred_datetime_type: - if b.is_string: - date_text = b.name - if is_iso_date(date_text): - b_type = exp.DataType.Type.DATE - elif is_iso_datetime(date_text): - b_type = exp.DataType.Type.DATETIME - else: - b_type = a_type.this - else: - # If b is not a datetime string, we conservatively promote it to a DATETIME, - # in order to ensure there are no surprising truncations due to downcasting - b_type = exp.DataType.Type.DATETIME - - target_type = ( - b_type - if b_type in TypeAnnotator.COERCES_TO.get(a_type.this, {}) - else a_type - ) - else: - target_type = a_type - - if target_type != a_type: - _replace_cast(a, target_type) - - _replace_cast(b, target_type) - - -def _coerce_timeunit_arg( - arg: exp.Expression, unit: t.Optional[exp.Expression] -) -> exp.Expression: - if not arg.type: - return arg - - if arg.type.this in exp.DataType.TEXT_TYPES: - date_text = arg.name - is_iso_date_ = is_iso_date(date_text) - - if is_iso_date_ and is_date_unit(unit): - return arg.replace(exp.cast(arg.copy(), to=exp.DataType.Type.DATE)) - - # An ISO date is also an ISO datetime, but not vice versa - if is_iso_date_ or is_iso_datetime(date_text): - return arg.replace(exp.cast(arg.copy(), to=exp.DataType.Type.DATETIME)) - - elif arg.type.this == exp.DataType.Type.DATE and not is_date_unit(unit): - return arg.replace(exp.cast(arg.copy(), to=exp.DataType.Type.DATETIME)) - - return arg - - -def _coerce_datediff_args(node: exp.DateDiff) -> None: - for e in (node.this, node.expression): - if e.type.this not in exp.DataType.TEMPORAL_TYPES: - e.replace(exp.cast(e.copy(), to=exp.DataType.Type.DATETIME)) - - -def _replace_cast(node: exp.Expression, to: exp.DATA_TYPE) -> None: - node.replace(exp.cast(node.copy(), to=to)) - - -# this was originally designed for presto, there is a similar transform for tsql -# this is different in that it only operates on int types, this is because -# presto has a boolean type whereas tsql doesn't (people use bits) -# with y as (select true as x) select x = 0 FROM y -- illegal presto query -def _replace_int_predicate(expression: exp.Expression) -> None: - if isinstance(expression, exp.Coalesce): - for child in expression.iter_expressions(): - _replace_int_predicate(child) - elif expression.type and expression.type.this in exp.DataType.INTEGER_TYPES: - expression.replace(expression.neq(0)) diff --git a/third_party/bigframes_vendored/sqlglot/optimizer/eliminate_ctes.py b/third_party/bigframes_vendored/sqlglot/optimizer/eliminate_ctes.py deleted file mode 100644 index 8714c6bfa1b..00000000000 --- a/third_party/bigframes_vendored/sqlglot/optimizer/eliminate_ctes.py +++ /dev/null @@ -1,45 +0,0 @@ -# Contains code from https://github.com/tobymao/sqlglot/blob/v28.5.0/sqlglot/optimizer/eliminate_ctes.py - -from bigframes_vendored.sqlglot.optimizer.scope import Scope, build_scope - - -def eliminate_ctes(expression): - """ - Remove unused CTEs from an expression. - - Example: - >>> import sqlglot - >>> sql = "WITH y AS (SELECT a FROM x) SELECT a FROM z" - >>> expression = sqlglot.parse_one(sql) - >>> eliminate_ctes(expression).sql() - 'SELECT a FROM z' - - Args: - expression (sqlglot.Expression): expression to optimize - Returns: - sqlglot.Expression: optimized expression - """ - root = build_scope(expression) - - if root: - ref_count = root.ref_count() - - # Traverse the scope tree in reverse so we can remove chains of unused CTEs - for scope in reversed(list(root.traverse())): - if scope.is_cte: - count = ref_count[id(scope)] - if count <= 0: - cte_node = scope.expression.parent - with_node = cte_node.parent - cte_node.pop() - - # Pop the entire WITH clause if this is the last CTE - if with_node and len(with_node.expressions) <= 0: - with_node.pop() - - # Decrement the ref count for all sources this CTE selects from - for _, source in scope.selected_sources.values(): - if isinstance(source, Scope): - ref_count[id(source)] -= 1 - - return expression diff --git a/third_party/bigframes_vendored/sqlglot/optimizer/eliminate_joins.py b/third_party/bigframes_vendored/sqlglot/optimizer/eliminate_joins.py deleted file mode 100644 index db6621495cf..00000000000 --- a/third_party/bigframes_vendored/sqlglot/optimizer/eliminate_joins.py +++ /dev/null @@ -1,191 +0,0 @@ -# Contains code from https://github.com/tobymao/sqlglot/blob/v28.5.0/sqlglot/optimizer/eliminate_joins.py - -from bigframes_vendored.sqlglot import expressions as exp -from bigframes_vendored.sqlglot.optimizer.normalize import normalized -from bigframes_vendored.sqlglot.optimizer.scope import Scope, traverse_scope - - -def eliminate_joins(expression): - """ - Remove unused joins from an expression. - - This only removes joins when we know that the join condition doesn't produce duplicate rows. - - Example: - >>> import sqlglot - >>> sql = "SELECT x.a FROM x LEFT JOIN (SELECT DISTINCT y.b FROM y) AS y ON x.b = y.b" - >>> expression = sqlglot.parse_one(sql) - >>> eliminate_joins(expression).sql() - 'SELECT x.a FROM x' - - Args: - expression (sqlglot.Expression): expression to optimize - Returns: - sqlglot.Expression: optimized expression - """ - for scope in traverse_scope(expression): - # If any columns in this scope aren't qualified, it's hard to determine if a join isn't used. - # It's probably possible to infer this from the outputs of derived tables. - # But for now, let's just skip this rule. - if scope.unqualified_columns: - continue - - joins = scope.expression.args.get("joins", []) - - # Reverse the joins so we can remove chains of unused joins - for join in reversed(joins): - if join.is_semi_or_anti_join: - continue - - alias = join.alias_or_name - if _should_eliminate_join(scope, join, alias): - join.pop() - scope.remove_source(alias) - return expression - - -def _should_eliminate_join(scope, join, alias): - inner_source = scope.sources.get(alias) - return ( - isinstance(inner_source, Scope) - and not _join_is_used(scope, join, alias) - and ( - ( - join.side == "LEFT" - and _is_joined_on_all_unique_outputs(inner_source, join) - ) - or (not join.args.get("on") and _has_single_output_row(inner_source)) - ) - ) - - -def _join_is_used(scope, join, alias): - # We need to find all columns that reference this join. - # But columns in the ON clause shouldn't count. - on = join.args.get("on") - if on: - on_clause_columns = {id(column) for column in on.find_all(exp.Column)} - else: - on_clause_columns = set() - return any( - column - for column in scope.source_columns(alias) - if id(column) not in on_clause_columns - ) - - -def _is_joined_on_all_unique_outputs(scope, join): - unique_outputs = _unique_outputs(scope) - if not unique_outputs: - return False - - _, join_keys, _ = join_condition(join) - remaining_unique_outputs = unique_outputs - {c.name for c in join_keys} - return not remaining_unique_outputs - - -def _unique_outputs(scope): - """Determine output columns of `scope` that must have a unique combination per row""" - if scope.expression.args.get("distinct"): - return set(scope.expression.named_selects) - - group = scope.expression.args.get("group") - if group: - grouped_expressions = set(group.expressions) - grouped_outputs = set() - - unique_outputs = set() - for select in scope.expression.selects: - output = select.unalias() - if output in grouped_expressions: - grouped_outputs.add(output) - unique_outputs.add(select.alias_or_name) - - # All the grouped expressions must be in the output - if not grouped_expressions.difference(grouped_outputs): - return unique_outputs - else: - return set() - - if _has_single_output_row(scope): - return set(scope.expression.named_selects) - - return set() - - -def _has_single_output_row(scope): - return isinstance(scope.expression, exp.Select) and ( - all(isinstance(e.unalias(), exp.AggFunc) for e in scope.expression.selects) - or _is_limit_1(scope) - or not scope.expression.args.get("from_") - ) - - -def _is_limit_1(scope): - limit = scope.expression.args.get("limit") - return limit and limit.expression.this == "1" - - -def join_condition(join): - """ - Extract the join condition from a join expression. - - Args: - join (exp.Join) - Returns: - tuple[list[str], list[str], exp.Expression]: - Tuple of (source key, join key, remaining predicate) - """ - name = join.alias_or_name - on = (join.args.get("on") or exp.true()).copy() - source_key = [] - join_key = [] - - def extract_condition(condition): - left, right = condition.unnest_operands() - left_tables = exp.column_table_names(left) - right_tables = exp.column_table_names(right) - - if name in left_tables and name not in right_tables: - join_key.append(left) - source_key.append(right) - condition.replace(exp.true()) - elif name in right_tables and name not in left_tables: - join_key.append(right) - source_key.append(left) - condition.replace(exp.true()) - - # find the join keys - # SELECT - # FROM x - # JOIN y - # ON x.a = y.b AND y.b > 1 - # - # should pull y.b as the join key and x.a as the source key - if normalized(on): - on = on if isinstance(on, exp.And) else exp.and_(on, exp.true(), copy=False) - - for condition in on.flatten(): - if isinstance(condition, exp.EQ): - extract_condition(condition) - elif normalized(on, dnf=True): - conditions = None - - for condition in on.flatten(): - parts = [part for part in condition.flatten() if isinstance(part, exp.EQ)] - if conditions is None: - conditions = parts - else: - temp = [] - for p in parts: - cs = [c for c in conditions if p == c] - - if cs: - temp.append(p) - temp.extend(cs) - conditions = temp - - for condition in conditions: - extract_condition(condition) - - return source_key, join_key, on diff --git a/third_party/bigframes_vendored/sqlglot/optimizer/eliminate_subqueries.py b/third_party/bigframes_vendored/sqlglot/optimizer/eliminate_subqueries.py deleted file mode 100644 index 9deb0f65dc5..00000000000 --- a/third_party/bigframes_vendored/sqlglot/optimizer/eliminate_subqueries.py +++ /dev/null @@ -1,195 +0,0 @@ -# Contains code from https://github.com/tobymao/sqlglot/blob/v28.5.0/sqlglot/optimizer/eliminate_subqueries.py - -from __future__ import annotations - -import itertools -import typing as t - -from bigframes_vendored.sqlglot import expressions as exp -from bigframes_vendored.sqlglot.helper import find_new_name -from bigframes_vendored.sqlglot.optimizer.scope import Scope, build_scope - -if t.TYPE_CHECKING: - ExistingCTEsMapping = t.Dict[exp.Expression, str] - TakenNameMapping = t.Dict[str, t.Union[Scope, exp.Expression]] - - -def eliminate_subqueries(expression: exp.Expression) -> exp.Expression: - """ - Rewrite derived tables as CTES, deduplicating if possible. - - Example: - >>> import sqlglot - >>> expression = sqlglot.parse_one("SELECT a FROM (SELECT * FROM x) AS y") - >>> eliminate_subqueries(expression).sql() - 'WITH y AS (SELECT * FROM x) SELECT a FROM y AS y' - - This also deduplicates common subqueries: - >>> expression = sqlglot.parse_one("SELECT a FROM (SELECT * FROM x) AS y CROSS JOIN (SELECT * FROM x) AS z") - >>> eliminate_subqueries(expression).sql() - 'WITH y AS (SELECT * FROM x) SELECT a FROM y AS y CROSS JOIN y AS z' - - Args: - expression (sqlglot.Expression): expression - Returns: - sqlglot.Expression: expression - """ - if isinstance(expression, exp.Subquery): - # It's possible to have subqueries at the root, e.g. (SELECT * FROM x) LIMIT 1 - eliminate_subqueries(expression.this) - return expression - - root = build_scope(expression) - - if not root: - return expression - - # Map of alias->Scope|Table - # These are all aliases that are already used in the expression. - # We don't want to create new CTEs that conflict with these names. - taken: TakenNameMapping = {} - - # All CTE aliases in the root scope are taken - for scope in root.cte_scopes: - taken[scope.expression.parent.alias] = scope - - # All table names are taken - for scope in root.traverse(): - taken.update( - { - source.name: source - for _, source in scope.sources.items() - if isinstance(source, exp.Table) - } - ) - - # Map of Expression->alias - # Existing CTES in the root expression. We'll use this for deduplication. - existing_ctes: ExistingCTEsMapping = {} - - with_ = root.expression.args.get("with_") - recursive = False - if with_: - recursive = with_.args.get("recursive") - for cte in with_.expressions: - existing_ctes[cte.this] = cte.alias - new_ctes = [] - - # We're adding more CTEs, but we want to maintain the DAG order. - # Derived tables within an existing CTE need to come before the existing CTE. - for cte_scope in root.cte_scopes: - # Append all the new CTEs from this existing CTE - for scope in cte_scope.traverse(): - if scope is cte_scope: - # Don't try to eliminate this CTE itself - continue - new_cte = _eliminate(scope, existing_ctes, taken) - if new_cte: - new_ctes.append(new_cte) - - # Append the existing CTE itself - new_ctes.append(cte_scope.expression.parent) - - # Now append the rest - for scope in itertools.chain( - root.union_scopes, root.subquery_scopes, root.table_scopes - ): - for child_scope in scope.traverse(): - new_cte = _eliminate(child_scope, existing_ctes, taken) - if new_cte: - new_ctes.append(new_cte) - - if new_ctes: - query = expression.expression if isinstance(expression, exp.DDL) else expression - query.set("with_", exp.With(expressions=new_ctes, recursive=recursive)) - - return expression - - -def _eliminate( - scope: Scope, existing_ctes: ExistingCTEsMapping, taken: TakenNameMapping -) -> t.Optional[exp.Expression]: - if scope.is_derived_table: - return _eliminate_derived_table(scope, existing_ctes, taken) - - if scope.is_cte: - return _eliminate_cte(scope, existing_ctes, taken) - - return None - - -def _eliminate_derived_table( - scope: Scope, existing_ctes: ExistingCTEsMapping, taken: TakenNameMapping -) -> t.Optional[exp.Expression]: - # This makes sure that we don't: - # - drop the "pivot" arg from a pivoted subquery - # - eliminate a lateral correlated subquery - if scope.parent.pivots or isinstance(scope.parent.expression, exp.Lateral): - return None - - # Get rid of redundant exp.Subquery expressions, i.e. those that are just used as wrappers - to_replace = scope.expression.parent.unwrap() - name, cte = _new_cte(scope, existing_ctes, taken) - table = exp.alias_(exp.table_(name), alias=to_replace.alias or name) - table.set("joins", to_replace.args.get("joins")) - - to_replace.replace(table) - - return cte - - -def _eliminate_cte( - scope: Scope, existing_ctes: ExistingCTEsMapping, taken: TakenNameMapping -) -> t.Optional[exp.Expression]: - parent = scope.expression.parent - name, cte = _new_cte(scope, existing_ctes, taken) - - with_ = parent.parent - parent.pop() - if not with_.expressions: - with_.pop() - - # Rename references to this CTE - for child_scope in scope.parent.traverse(): - for table, source in child_scope.selected_sources.values(): - if source is scope: - new_table = exp.alias_( - exp.table_(name), alias=table.alias_or_name, copy=False - ) - table.replace(new_table) - - return cte - - -def _new_cte( - scope: Scope, existing_ctes: ExistingCTEsMapping, taken: TakenNameMapping -) -> t.Tuple[str, t.Optional[exp.Expression]]: - """ - Returns: - tuple of (name, cte) - where `name` is a new name for this CTE in the root scope and `cte` is a new CTE instance. - If this CTE duplicates an existing CTE, `cte` will be None. - """ - duplicate_cte_alias = existing_ctes.get(scope.expression) - parent = scope.expression.parent - name = parent.alias - - if not name: - name = find_new_name(taken=taken, base="cte") - - if duplicate_cte_alias: - name = duplicate_cte_alias - elif taken.get(name): - name = find_new_name(taken=taken, base=name) - - taken[name] = scope - - if not duplicate_cte_alias: - existing_ctes[scope.expression] = name - cte = exp.CTE( - this=scope.expression, - alias=exp.TableAlias(this=exp.to_identifier(name)), - ) - else: - cte = None - return name, cte diff --git a/third_party/bigframes_vendored/sqlglot/optimizer/isolate_table_selects.py b/third_party/bigframes_vendored/sqlglot/optimizer/isolate_table_selects.py deleted file mode 100644 index f2ebf8a1a8a..00000000000 --- a/third_party/bigframes_vendored/sqlglot/optimizer/isolate_table_selects.py +++ /dev/null @@ -1,54 +0,0 @@ -# Contains code from https://github.com/tobymao/sqlglot/blob/v28.5.0/sqlglot/optimizer/isolate_table_selects.py - -from __future__ import annotations - -import typing as t - -from bigframes_vendored.sqlglot import alias, exp -from bigframes_vendored.sqlglot.errors import OptimizeError -from bigframes_vendored.sqlglot.optimizer.scope import traverse_scope -from bigframes_vendored.sqlglot.schema import ensure_schema - -if t.TYPE_CHECKING: - from bigframes_vendored.sqlglot._typing import E - from bigframes_vendored.sqlglot.dialects.dialect import DialectType - from bigframes_vendored.sqlglot.schema import Schema - - -def isolate_table_selects( - expression: E, - schema: t.Optional[t.Dict | Schema] = None, - dialect: DialectType = None, -) -> E: - schema = ensure_schema(schema, dialect=dialect) - - for scope in traverse_scope(expression): - if len(scope.selected_sources) == 1: - continue - - for _, source in scope.selected_sources.values(): - assert source.parent - - if ( - not isinstance(source, exp.Table) - or not schema.column_names(source) - or isinstance(source.parent, exp.Subquery) - or isinstance(source.parent.parent, exp.Table) - ): - continue - - if not source.alias: - raise OptimizeError( - "Tables require an alias. Run qualify_tables optimization." - ) - - source.replace( - exp.select("*") - .from_( - alias(source, source.alias_or_name, table=True), - copy=False, - ) - .subquery(source.alias, copy=False) - ) - - return expression diff --git a/third_party/bigframes_vendored/sqlglot/optimizer/merge_subqueries.py b/third_party/bigframes_vendored/sqlglot/optimizer/merge_subqueries.py deleted file mode 100644 index 81e213ee814..00000000000 --- a/third_party/bigframes_vendored/sqlglot/optimizer/merge_subqueries.py +++ /dev/null @@ -1,446 +0,0 @@ -# Contains code from https://github.com/tobymao/sqlglot/blob/v28.5.0/sqlglot/optimizer/merge_subqueries.py - -from __future__ import annotations - -import typing as t -from collections import defaultdict - -from bigframes_vendored.sqlglot import expressions as exp -from bigframes_vendored.sqlglot.helper import find_new_name, seq_get -from bigframes_vendored.sqlglot.optimizer.scope import Scope, traverse_scope - -if t.TYPE_CHECKING: - from bigframes_vendored.sqlglot._typing import E - - FromOrJoin = t.Union[exp.From, exp.Join] - - -def merge_subqueries(expression: E, leave_tables_isolated: bool = False) -> E: - """ - Rewrite sqlglot AST to merge derived tables into the outer query. - - This also merges CTEs if they are selected from only once. - - Example: - >>> import sqlglot - >>> expression = sqlglot.parse_one("SELECT a FROM (SELECT x.a FROM x) CROSS JOIN y") - >>> merge_subqueries(expression).sql() - 'SELECT x.a FROM x CROSS JOIN y' - - If `leave_tables_isolated` is True, this will not merge inner queries into outer - queries if it would result in multiple table selects in a single query: - >>> expression = sqlglot.parse_one("SELECT a FROM (SELECT x.a FROM x) CROSS JOIN y") - >>> merge_subqueries(expression, leave_tables_isolated=True).sql() - 'SELECT a FROM (SELECT x.a FROM x) CROSS JOIN y' - - Inspired by https://dev.mysql.com/doc/refman/8.0/en/derived-table-optimization.html - - Args: - expression (sqlglot.Expression): expression to optimize - leave_tables_isolated (bool): - Returns: - sqlglot.Expression: optimized expression - """ - expression = merge_ctes(expression, leave_tables_isolated) - expression = merge_derived_tables(expression, leave_tables_isolated) - return expression - - -# If a derived table has these Select args, it can't be merged -UNMERGABLE_ARGS = set(exp.Select.arg_types) - { - "expressions", - "from_", - "joins", - "where", - "order", - "hint", -} - - -# Projections in the outer query that are instances of these types can be replaced -# without getting wrapped in parentheses, because the precedence won't be altered. -SAFE_TO_REPLACE_UNWRAPPED = ( - exp.Column, - exp.EQ, - exp.Func, - exp.NEQ, - exp.Paren, -) - - -def merge_ctes(expression: E, leave_tables_isolated: bool = False) -> E: - scopes = traverse_scope(expression) - - # All places where we select from CTEs. - # We key on the CTE scope so we can detect CTES that are selected from multiple times. - cte_selections = defaultdict(list) - for outer_scope in scopes: - for table, inner_scope in outer_scope.selected_sources.values(): - if isinstance(inner_scope, Scope) and inner_scope.is_cte: - cte_selections[id(inner_scope)].append( - ( - outer_scope, - inner_scope, - table, - ) - ) - - singular_cte_selections = [v[0] for k, v in cte_selections.items() if len(v) == 1] - for outer_scope, inner_scope, table in singular_cte_selections: - from_or_join = table.find_ancestor(exp.From, exp.Join) - if _mergeable(outer_scope, inner_scope, leave_tables_isolated, from_or_join): - alias = table.alias_or_name - _rename_inner_sources(outer_scope, inner_scope, alias) - _merge_from(outer_scope, inner_scope, table, alias) - _merge_expressions(outer_scope, inner_scope, alias) - _merge_order(outer_scope, inner_scope) - _merge_joins(outer_scope, inner_scope, from_or_join) - _merge_where(outer_scope, inner_scope, from_or_join) - _merge_hints(outer_scope, inner_scope) - _pop_cte(inner_scope) - outer_scope.clear_cache() - return expression - - -def merge_derived_tables(expression: E, leave_tables_isolated: bool = False) -> E: - for outer_scope in traverse_scope(expression): - for subquery in outer_scope.derived_tables: - from_or_join = subquery.find_ancestor(exp.From, exp.Join) - alias = subquery.alias_or_name - inner_scope = outer_scope.sources[alias] - if _mergeable( - outer_scope, inner_scope, leave_tables_isolated, from_or_join - ): - _rename_inner_sources(outer_scope, inner_scope, alias) - _merge_from(outer_scope, inner_scope, subquery, alias) - _merge_expressions(outer_scope, inner_scope, alias) - _merge_order(outer_scope, inner_scope) - _merge_joins(outer_scope, inner_scope, from_or_join) - _merge_where(outer_scope, inner_scope, from_or_join) - _merge_hints(outer_scope, inner_scope) - outer_scope.clear_cache() - - return expression - - -def _mergeable( - outer_scope: Scope, - inner_scope: Scope, - leave_tables_isolated: bool, - from_or_join: FromOrJoin, -) -> bool: - """ - Return True if `inner_select` can be merged into outer query. - """ - inner_select = inner_scope.expression.unnest() - - def _is_a_window_expression_in_unmergable_operation(): - window_aliases = { - s.alias_or_name for s in inner_select.selects if s.find(exp.Window) - } - inner_select_name = from_or_join.alias_or_name - unmergable_window_columns = [ - column - for column in outer_scope.columns - if column.find_ancestor( - exp.Where, exp.Group, exp.Order, exp.Join, exp.Having, exp.AggFunc - ) - ] - window_expressions_in_unmergable = [ - column - for column in unmergable_window_columns - if column.table == inner_select_name and column.name in window_aliases - ] - return any(window_expressions_in_unmergable) - - def _outer_select_joins_on_inner_select_join(): - """ - All columns from the inner select in the ON clause must be from the first FROM table. - - That is, this can be merged: - SELECT * FROM x JOIN (SELECT y.a AS a FROM y JOIN z) AS q ON x.a = q.a - ^^^ ^ - But this can't: - SELECT * FROM x JOIN (SELECT z.a AS a FROM y JOIN z) AS q ON x.a = q.a - ^^^ ^ - """ - if not isinstance(from_or_join, exp.Join): - return False - - alias = from_or_join.alias_or_name - - on = from_or_join.args.get("on") - if not on: - return False - selections = [c.name for c in on.find_all(exp.Column) if c.table == alias] - inner_from = inner_scope.expression.args.get("from_") - if not inner_from: - return False - inner_from_table = inner_from.alias_or_name - inner_projections = {s.alias_or_name: s for s in inner_scope.expression.selects} - return any( - col.table != inner_from_table - for selection in selections - for col in inner_projections[selection].find_all(exp.Column) - ) - - def _is_recursive(): - # Recursive CTEs look like this: - # WITH RECURSIVE cte AS ( - # SELECT * FROM x <-- inner scope - # UNION ALL - # SELECT * FROM cte <-- outer scope - # ) - cte = inner_scope.expression.parent - node = outer_scope.expression.parent - - while node: - if node is cte: - return True - node = node.parent - return False - - return ( - isinstance(outer_scope.expression, exp.Select) - and not outer_scope.expression.is_star - and isinstance(inner_select, exp.Select) - and not any(inner_select.args.get(arg) for arg in UNMERGABLE_ARGS) - and inner_select.args.get("from_") is not None - and not outer_scope.pivots - and not any( - e.find(exp.AggFunc, exp.Select, exp.Explode) - for e in inner_select.expressions - ) - and not (leave_tables_isolated and len(outer_scope.selected_sources) > 1) - and not (isinstance(from_or_join, exp.Join) and inner_select.args.get("joins")) - and not ( - isinstance(from_or_join, exp.Join) - and inner_select.args.get("where") - and from_or_join.side in ("FULL", "LEFT", "RIGHT") - ) - and not ( - isinstance(from_or_join, exp.From) - and inner_select.args.get("where") - and any( - j.side in ("FULL", "RIGHT") - for j in outer_scope.expression.args.get("joins", []) - ) - ) - and not _outer_select_joins_on_inner_select_join() - and not _is_a_window_expression_in_unmergable_operation() - and not _is_recursive() - and not (inner_select.args.get("order") and outer_scope.is_union) - and not isinstance(seq_get(inner_select.expressions, 0), exp.QueryTransform) - ) - - -def _rename_inner_sources(outer_scope: Scope, inner_scope: Scope, alias: str) -> None: - """ - Renames any sources in the inner query that conflict with names in the outer query. - """ - inner_taken = set(inner_scope.selected_sources) - outer_taken = set(outer_scope.selected_sources) - conflicts = outer_taken.intersection(inner_taken) - conflicts -= {alias} - - taken = outer_taken.union(inner_taken) - - for conflict in conflicts: - new_name = find_new_name(taken, conflict) - - source, _ = inner_scope.selected_sources[conflict] - new_alias = exp.to_identifier(new_name) - - if isinstance(source, exp.Table) and source.alias: - source.set("alias", new_alias) - elif isinstance(source, exp.Table): - source.replace(exp.alias_(source, new_alias)) - elif isinstance(source.parent, exp.Subquery): - source.parent.set("alias", exp.TableAlias(this=new_alias)) - - for column in inner_scope.source_columns(conflict): - column.set("table", exp.to_identifier(new_name)) - - inner_scope.rename_source(conflict, new_name) - - -def _merge_from( - outer_scope: Scope, - inner_scope: Scope, - node_to_replace: t.Union[exp.Subquery, exp.Table], - alias: str, -) -> None: - """ - Merge FROM clause of inner query into outer query. - """ - new_subquery = inner_scope.expression.args["from_"].this - new_subquery.set("joins", node_to_replace.args.get("joins")) - node_to_replace.replace(new_subquery) - for join_hint in outer_scope.join_hints: - tables = join_hint.find_all(exp.Table) - for table in tables: - if table.alias_or_name == node_to_replace.alias_or_name: - table.set("this", exp.to_identifier(new_subquery.alias_or_name)) - outer_scope.remove_source(alias) - outer_scope.add_source( - new_subquery.alias_or_name, inner_scope.sources[new_subquery.alias_or_name] - ) - - -def _merge_joins( - outer_scope: Scope, inner_scope: Scope, from_or_join: FromOrJoin -) -> None: - """ - Merge JOIN clauses of inner query into outer query. - """ - - new_joins = [] - - joins = inner_scope.expression.args.get("joins") or [] - - for join in joins: - new_joins.append(join) - outer_scope.add_source( - join.alias_or_name, inner_scope.sources[join.alias_or_name] - ) - - if new_joins: - outer_joins = outer_scope.expression.args.get("joins", []) - - # Maintain the join order - if isinstance(from_or_join, exp.From): - position = 0 - else: - position = outer_joins.index(from_or_join) + 1 - outer_joins[position:position] = new_joins - - outer_scope.expression.set("joins", outer_joins) - - -def _merge_expressions(outer_scope: Scope, inner_scope: Scope, alias: str) -> None: - """ - Merge projections of inner query into outer query. - - Args: - outer_scope (sqlglot.optimizer.scope.Scope) - inner_scope (sqlglot.optimizer.scope.Scope) - alias (str) - """ - # Collect all columns that reference the alias of the inner query - outer_columns = defaultdict(list) - for column in outer_scope.columns: - if column.table == alias: - outer_columns[column.name].append(column) - - # Replace columns with the projection expression in the inner query - for expression in inner_scope.expression.expressions: - projection_name = expression.alias_or_name - if not projection_name: - continue - columns_to_replace = outer_columns.get(projection_name, []) - - expression = expression.unalias() - must_wrap_expression = not isinstance(expression, SAFE_TO_REPLACE_UNWRAPPED) - - for column in columns_to_replace: - # Ensures we don't alter the intended operator precedence if there's additional - # context surrounding the outer expression (i.e. it's not a simple projection). - if ( - isinstance(column.parent, (exp.Unary, exp.Binary)) - and must_wrap_expression - ): - expression = exp.paren(expression, copy=False) - - # make sure we do not accidentally change the name of the column - if isinstance(column.parent, exp.Select) and column.name != expression.name: - expression = exp.alias_(expression, column.name) - - column.replace(expression.copy()) - - -def _merge_where( - outer_scope: Scope, inner_scope: Scope, from_or_join: FromOrJoin -) -> None: - """ - Merge WHERE clause of inner query into outer query. - - Args: - outer_scope (sqlglot.optimizer.scope.Scope) - inner_scope (sqlglot.optimizer.scope.Scope) - from_or_join (exp.From|exp.Join) - """ - where = inner_scope.expression.args.get("where") - if not where or not where.this: - return - - expression = outer_scope.expression - - if isinstance(from_or_join, exp.Join): - # Merge predicates from an outer join to the ON clause - # if it only has columns that are already joined - from_ = expression.args.get("from_") - sources = {from_.alias_or_name} if from_ else set() - - for join in expression.args["joins"]: - source = join.alias_or_name - sources.add(source) - if source == from_or_join.alias_or_name: - break - - if exp.column_table_names(where.this) <= sources: - from_or_join.on(where.this, copy=False) - from_or_join.set("on", from_or_join.args.get("on")) - return - - expression.where(where.this, copy=False) - - -def _merge_order(outer_scope: Scope, inner_scope: Scope) -> None: - """ - Merge ORDER clause of inner query into outer query. - - Args: - outer_scope (sqlglot.optimizer.scope.Scope) - inner_scope (sqlglot.optimizer.scope.Scope) - """ - if ( - any( - outer_scope.expression.args.get(arg) - for arg in ["group", "distinct", "having", "order"] - ) - or len(outer_scope.selected_sources) != 1 - or any( - expression.find(exp.AggFunc) - for expression in outer_scope.expression.expressions - ) - ): - return - - outer_scope.expression.set("order", inner_scope.expression.args.get("order")) - - -def _merge_hints(outer_scope: Scope, inner_scope: Scope) -> None: - inner_scope_hint = inner_scope.expression.args.get("hint") - if not inner_scope_hint: - return - outer_scope_hint = outer_scope.expression.args.get("hint") - if outer_scope_hint: - for hint_expression in inner_scope_hint.expressions: - outer_scope_hint.append("expressions", hint_expression) - else: - outer_scope.expression.set("hint", inner_scope_hint) - - -def _pop_cte(inner_scope: Scope) -> None: - """ - Remove CTE from the AST. - - Args: - inner_scope (sqlglot.optimizer.scope.Scope) - """ - cte = inner_scope.expression.parent - with_ = cte.parent - if len(with_.expressions) == 1: - with_.pop() - else: - cte.pop() diff --git a/third_party/bigframes_vendored/sqlglot/optimizer/normalize.py b/third_party/bigframes_vendored/sqlglot/optimizer/normalize.py deleted file mode 100644 index daa4bfb84d0..00000000000 --- a/third_party/bigframes_vendored/sqlglot/optimizer/normalize.py +++ /dev/null @@ -1,216 +0,0 @@ -# Contains code from https://github.com/tobymao/sqlglot/blob/v28.5.0/sqlglot/optimizer/normalize.py - -from __future__ import annotations - -import logging - -from bigframes_vendored.sqlglot import exp -from bigframes_vendored.sqlglot.errors import OptimizeError -from bigframes_vendored.sqlglot.helper import while_changing -from bigframes_vendored.sqlglot.optimizer.scope import find_all_in_scope -from bigframes_vendored.sqlglot.optimizer.simplify import Simplifier, flatten - -logger = logging.getLogger("sqlglot") - - -def normalize(expression: exp.Expression, dnf: bool = False, max_distance: int = 128): - """ - Rewrite sqlglot AST into conjunctive normal form or disjunctive normal form. - - Example: - >>> import sqlglot - >>> expression = sqlglot.parse_one("(x AND y) OR z") - >>> normalize(expression, dnf=False).sql() - '(x OR z) AND (y OR z)' - - Args: - expression: expression to normalize - dnf: rewrite in disjunctive normal form instead. - max_distance (int): the maximal estimated distance from cnf/dnf to attempt conversion - Returns: - sqlglot.Expression: normalized expression - """ - simplifier = Simplifier(annotate_new_expressions=False) - - for node in tuple(expression.walk(prune=lambda e: isinstance(e, exp.Connector))): - if isinstance(node, exp.Connector): - if normalized(node, dnf=dnf): - continue - root = node is expression - original = node.copy() - - node.transform(simplifier.rewrite_between, copy=False) - distance = normalization_distance(node, dnf=dnf, max_=max_distance) - - if distance > max_distance: - logger.info( - f"Skipping normalization because distance {distance} exceeds max {max_distance}" - ) - return expression - - try: - node = node.replace( - while_changing( - node, - lambda e: distributive_law( - e, dnf, max_distance, simplifier=simplifier - ), - ) - ) - except OptimizeError as e: - logger.info(e) - node.replace(original) - if root: - return original - return expression - - if root: - expression = node - - return expression - - -def normalized(expression: exp.Expression, dnf: bool = False) -> bool: - """ - Checks whether a given expression is in a normal form of interest. - - Example: - >>> from sqlglot import parse_one - >>> normalized(parse_one("(a AND b) OR c OR (d AND e)"), dnf=True) - True - >>> normalized(parse_one("(a OR b) AND c")) # Checks CNF by default - True - >>> normalized(parse_one("a AND (b OR c)"), dnf=True) - False - - Args: - expression: The expression to check if it's normalized. - dnf: Whether to check if the expression is in Disjunctive Normal Form (DNF). - Default: False, i.e. we check if it's in Conjunctive Normal Form (CNF). - """ - ancestor, root = (exp.And, exp.Or) if dnf else (exp.Or, exp.And) - return not any( - connector.find_ancestor(ancestor) - for connector in find_all_in_scope(expression, root) - ) - - -def normalization_distance( - expression: exp.Expression, dnf: bool = False, max_: float = float("inf") -) -> int: - """ - The difference in the number of predicates between a given expression and its normalized form. - - This is used as an estimate of the cost of the conversion which is exponential in complexity. - - Example: - >>> import sqlglot - >>> expression = sqlglot.parse_one("(a AND b) OR (c AND d)") - >>> normalization_distance(expression) - 4 - - Args: - expression: The expression to compute the normalization distance for. - dnf: Whether to check if the expression is in Disjunctive Normal Form (DNF). - Default: False, i.e. we check if it's in Conjunctive Normal Form (CNF). - max_: stop early if count exceeds this. - - Returns: - The normalization distance. - """ - total = -(sum(1 for _ in expression.find_all(exp.Connector)) + 1) - - for length in _predicate_lengths(expression, dnf, max_): - total += length - if total > max_: - return total - - return total - - -def _predicate_lengths(expression, dnf, max_=float("inf"), depth=0): - """ - Returns a list of predicate lengths when expanded to normalized form. - - (A AND B) OR C -> [2, 2] because len(A OR C), len(B OR C). - """ - if depth > max_: - yield depth - return - - expression = expression.unnest() - - if not isinstance(expression, exp.Connector): - yield 1 - return - - depth += 1 - left, right = expression.args.values() - - if isinstance(expression, exp.And if dnf else exp.Or): - for a in _predicate_lengths(left, dnf, max_, depth): - for b in _predicate_lengths(right, dnf, max_, depth): - yield a + b - else: - yield from _predicate_lengths(left, dnf, max_, depth) - yield from _predicate_lengths(right, dnf, max_, depth) - - -def distributive_law(expression, dnf, max_distance, simplifier=None): - """ - x OR (y AND z) -> (x OR y) AND (x OR z) - (x AND y) OR (y AND z) -> (x OR y) AND (x OR z) AND (y OR y) AND (y OR z) - """ - if normalized(expression, dnf=dnf): - return expression - - distance = normalization_distance(expression, dnf=dnf, max_=max_distance) - - if distance > max_distance: - raise OptimizeError( - f"Normalization distance {distance} exceeds max {max_distance}" - ) - - exp.replace_children(expression, lambda e: distributive_law(e, dnf, max_distance)) - to_exp, from_exp = (exp.Or, exp.And) if dnf else (exp.And, exp.Or) - - if isinstance(expression, from_exp): - a, b = expression.unnest_operands() - - from_func = exp.and_ if from_exp == exp.And else exp.or_ - to_func = exp.and_ if to_exp == exp.And else exp.or_ - - simplifier = simplifier or Simplifier(annotate_new_expressions=False) - - if isinstance(a, to_exp) and isinstance(b, to_exp): - if len(tuple(a.find_all(exp.Connector))) > len( - tuple(b.find_all(exp.Connector)) - ): - return _distribute(a, b, from_func, to_func, simplifier) - return _distribute(b, a, from_func, to_func, simplifier) - if isinstance(a, to_exp): - return _distribute(b, a, from_func, to_func, simplifier) - if isinstance(b, to_exp): - return _distribute(a, b, from_func, to_func, simplifier) - - return expression - - -def _distribute(a, b, from_func, to_func, simplifier): - if isinstance(a, exp.Connector): - exp.replace_children( - a, - lambda c: to_func( - simplifier.uniq_sort(flatten(from_func(c, b.left))), - simplifier.uniq_sort(flatten(from_func(c, b.right))), - copy=False, - ), - ) - else: - a = to_func( - simplifier.uniq_sort(flatten(from_func(a, b.left))), - simplifier.uniq_sort(flatten(from_func(a, b.right))), - copy=False, - ) - - return a diff --git a/third_party/bigframes_vendored/sqlglot/optimizer/normalize_identifiers.py b/third_party/bigframes_vendored/sqlglot/optimizer/normalize_identifiers.py deleted file mode 100644 index eacf4305c49..00000000000 --- a/third_party/bigframes_vendored/sqlglot/optimizer/normalize_identifiers.py +++ /dev/null @@ -1,86 +0,0 @@ -# Contains code from https://github.com/tobymao/sqlglot/blob/v28.5.0/sqlglot/optimizer/normalize_identifiers.py - -from __future__ import annotations - -import typing as t - -from bigframes_vendored.sqlglot import exp -from bigframes_vendored.sqlglot.dialects.dialect import Dialect, DialectType - -if t.TYPE_CHECKING: - from bigframes_vendored.sqlglot._typing import E - - -@t.overload -def normalize_identifiers( - expression: E, - dialect: DialectType = None, - store_original_column_identifiers: bool = False, -) -> E: ... - - -@t.overload -def normalize_identifiers( - expression: str, - dialect: DialectType = None, - store_original_column_identifiers: bool = False, -) -> exp.Identifier: ... - - -def normalize_identifiers( - expression, dialect=None, store_original_column_identifiers=False -): - """ - Normalize identifiers by converting them to either lower or upper case, - ensuring the semantics are preserved in each case (e.g. by respecting - case-sensitivity). - - This transformation reflects how identifiers would be resolved by the engine corresponding - to each SQL dialect, and plays a very important role in the standardization of the AST. - - It's possible to make this a no-op by adding a special comment next to the - identifier of interest: - - SELECT a /* sqlglot.meta case_sensitive */ FROM table - - In this example, the identifier `a` will not be normalized. - - Note: - Some dialects (e.g. DuckDB) treat all identifiers as case-insensitive even - when they're quoted, so in these cases all identifiers are normalized. - - Example: - >>> import sqlglot - >>> expression = sqlglot.parse_one('SELECT Bar.A AS A FROM "Foo".Bar') - >>> normalize_identifiers(expression).sql() - 'SELECT bar.a AS a FROM "Foo".bar' - >>> normalize_identifiers("foo", dialect="snowflake").sql(dialect="snowflake") - 'FOO' - - Args: - expression: The expression to transform. - dialect: The dialect to use in order to decide how to normalize identifiers. - store_original_column_identifiers: Whether to store the original column identifiers in - the meta data of the expression in case we want to undo the normalization at a later point. - - Returns: - The transformed expression. - """ - dialect = Dialect.get_or_raise(dialect) - - if isinstance(expression, str): - expression = exp.parse_identifier(expression, dialect=dialect) - - for node in expression.walk(prune=lambda n: n.meta.get("case_sensitive")): - if not node.meta.get("case_sensitive"): - if store_original_column_identifiers and isinstance(node, exp.Column): - # TODO: This does not handle non-column cases, e.g PARSE_JSON(...).key - parent = node - while parent and isinstance(parent.parent, exp.Dot): - parent = parent.parent - - node.meta["dot_parts"] = [p.name for p in parent.parts] - - dialect.normalize_identifier(node) - - return expression diff --git a/third_party/bigframes_vendored/sqlglot/optimizer/optimize_joins.py b/third_party/bigframes_vendored/sqlglot/optimizer/optimize_joins.py deleted file mode 100644 index d09d8cc6ce0..00000000000 --- a/third_party/bigframes_vendored/sqlglot/optimizer/optimize_joins.py +++ /dev/null @@ -1,128 +0,0 @@ -# Contains code from https://github.com/tobymao/sqlglot/blob/v28.5.0/sqlglot/optimizer/optimize_joins.py - -from __future__ import annotations - -import typing as t - -from bigframes_vendored.sqlglot import exp -from bigframes_vendored.sqlglot.helper import tsort - -JOIN_ATTRS = ("on", "side", "kind", "using", "method") - - -def optimize_joins(expression): - """ - Removes cross joins if possible and reorder joins based on predicate dependencies. - - Example: - >>> from sqlglot import parse_one - >>> optimize_joins(parse_one("SELECT * FROM x CROSS JOIN y JOIN z ON x.a = z.a AND y.a = z.a")).sql() - 'SELECT * FROM x JOIN z ON x.a = z.a AND TRUE JOIN y ON y.a = z.a' - """ - - for select in expression.find_all(exp.Select): - joins = select.args.get("joins", []) - - if not _is_reorderable(joins): - continue - - references = {} - cross_joins = [] - - for join in joins: - tables = other_table_names(join) - - if tables: - for table in tables: - references[table] = references.get(table, []) + [join] - else: - cross_joins.append((join.alias_or_name, join)) - - for name, join in cross_joins: - for dep in references.get(name, []): - on = dep.args["on"] - - if isinstance(on, exp.Connector): - if len(other_table_names(dep)) < 2: - continue - - operator = type(on) - for predicate in on.flatten(): - if name in exp.column_table_names(predicate): - predicate.replace(exp.true()) - predicate = exp._combine( - [join.args.get("on"), predicate], operator, copy=False - ) - join.on(predicate, append=False, copy=False) - - expression = reorder_joins(expression) - expression = normalize(expression) - return expression - - -def reorder_joins(expression): - """ - Reorder joins by topological sort order based on predicate references. - """ - for from_ in expression.find_all(exp.From): - parent = from_.parent - joins = parent.args.get("joins", []) - - if not _is_reorderable(joins): - continue - - joins_by_name = {join.alias_or_name: join for join in joins} - dag = {name: other_table_names(join) for name, join in joins_by_name.items()} - parent.set( - "joins", - [ - joins_by_name[name] - for name in tsort(dag) - if name != from_.alias_or_name and name in joins_by_name - ], - ) - return expression - - -def normalize(expression): - """ - Remove INNER and OUTER from joins as they are optional. - """ - for join in expression.find_all(exp.Join): - if not any(join.args.get(k) for k in JOIN_ATTRS): - join.set("kind", "CROSS") - - if join.kind == "CROSS": - join.set("on", None) - else: - if join.kind in ("INNER", "OUTER"): - join.set("kind", None) - - if not join.args.get("on") and not join.args.get("using"): - join.set("on", exp.true()) - return expression - - -def other_table_names(join: exp.Join) -> t.Set[str]: - on = join.args.get("on") - return exp.column_table_names(on, join.alias_or_name) if on else set() - - -def _is_reorderable(joins: t.List[exp.Join]) -> bool: - """ - Checks if joins can be reordered without changing query semantics. - - Joins with a side (LEFT, RIGHT, FULL) cannot be reordered easily, - the order affects which rows are included in the result. - - Example: - >>> from sqlglot import parse_one, exp - >>> from sqlglot.optimizer.optimize_joins import _is_reorderable - >>> ast = parse_one("SELECT * FROM x JOIN y ON x.id = y.id JOIN z ON y.id = z.id") - >>> _is_reorderable(ast.find(exp.Select).args.get("joins", [])) - True - >>> ast = parse_one("SELECT * FROM x LEFT JOIN y ON x.id = y.id JOIN z ON y.id = z.id") - >>> _is_reorderable(ast.find(exp.Select).args.get("joins", [])) - False - """ - return not any(join.side for join in joins) diff --git a/third_party/bigframes_vendored/sqlglot/optimizer/optimizer.py b/third_party/bigframes_vendored/sqlglot/optimizer/optimizer.py deleted file mode 100644 index ba13d17383e..00000000000 --- a/third_party/bigframes_vendored/sqlglot/optimizer/optimizer.py +++ /dev/null @@ -1,106 +0,0 @@ -# Contains code from https://github.com/tobymao/sqlglot/blob/v28.5.0/sqlglot/optimizer/optimizer.py - -from __future__ import annotations - -import inspect -import typing as t - -from bigframes_vendored.sqlglot import Schema, exp -from bigframes_vendored.sqlglot.dialects.dialect import DialectType -from bigframes_vendored.sqlglot.optimizer.annotate_types import annotate_types -from bigframes_vendored.sqlglot.optimizer.canonicalize import canonicalize -from bigframes_vendored.sqlglot.optimizer.eliminate_ctes import eliminate_ctes -from bigframes_vendored.sqlglot.optimizer.eliminate_joins import eliminate_joins -from bigframes_vendored.sqlglot.optimizer.eliminate_subqueries import ( - eliminate_subqueries, -) -from bigframes_vendored.sqlglot.optimizer.merge_subqueries import merge_subqueries -from bigframes_vendored.sqlglot.optimizer.normalize import normalize -from bigframes_vendored.sqlglot.optimizer.optimize_joins import optimize_joins -from bigframes_vendored.sqlglot.optimizer.pushdown_predicates import pushdown_predicates -from bigframes_vendored.sqlglot.optimizer.pushdown_projections import ( - pushdown_projections, -) -from bigframes_vendored.sqlglot.optimizer.qualify import qualify -from bigframes_vendored.sqlglot.optimizer.qualify_columns import quote_identifiers -from bigframes_vendored.sqlglot.optimizer.simplify import simplify -from bigframes_vendored.sqlglot.optimizer.unnest_subqueries import unnest_subqueries -from bigframes_vendored.sqlglot.schema import ensure_schema - -RULES = ( - qualify, - pushdown_projections, - normalize, - unnest_subqueries, - pushdown_predicates, - optimize_joins, - eliminate_subqueries, - merge_subqueries, - eliminate_joins, - eliminate_ctes, - quote_identifiers, - annotate_types, - canonicalize, - simplify, -) - - -def optimize( - expression: str | exp.Expression, - schema: t.Optional[dict | Schema] = None, - db: t.Optional[str | exp.Identifier] = None, - catalog: t.Optional[str | exp.Identifier] = None, - dialect: DialectType = None, - rules: t.Sequence[t.Callable] = RULES, - sql: t.Optional[str] = None, - **kwargs, -) -> exp.Expression: - """ - Rewrite a sqlglot AST into an optimized form. - - Args: - expression: expression to optimize - schema: database schema. - This can either be an instance of `sqlglot.optimizer.Schema` or a mapping in one of - the following forms: - 1. {table: {col: type}} - 2. {db: {table: {col: type}}} - 3. {catalog: {db: {table: {col: type}}}} - If no schema is provided then the default schema defined at `sqlgot.schema` will be used - db: specify the default database, as might be set by a `USE DATABASE db` statement - catalog: specify the default catalog, as might be set by a `USE CATALOG c` statement - dialect: The dialect to parse the sql string. - rules: sequence of optimizer rules to use. - Many of the rules require tables and columns to be qualified. - Do not remove `qualify` from the sequence of rules unless you know what you're doing! - sql: Original SQL string for error highlighting. If not provided, errors will not include - highlighting. Requires that the expression has position metadata from parsing. - **kwargs: If a rule has a keyword argument with a same name in **kwargs, it will be passed in. - - Returns: - The optimized expression. - """ - schema = ensure_schema(schema, dialect=dialect) - possible_kwargs = { - "db": db, - "catalog": catalog, - "schema": schema, - "dialect": dialect, - "sql": sql, - "isolate_tables": True, # needed for other optimizations to perform well - "quote_identifiers": False, - **kwargs, - } - - optimized = exp.maybe_parse(expression, dialect=dialect, copy=True) - for rule in rules: - # Find any additional rule parameters, beyond `expression` - rule_params = inspect.getfullargspec(rule).args - rule_kwargs = { - param: possible_kwargs[param] - for param in rule_params - if param in possible_kwargs - } - optimized = rule(optimized, **rule_kwargs) - - return optimized diff --git a/third_party/bigframes_vendored/sqlglot/optimizer/pushdown_predicates.py b/third_party/bigframes_vendored/sqlglot/optimizer/pushdown_predicates.py deleted file mode 100644 index 092d513ac7d..00000000000 --- a/third_party/bigframes_vendored/sqlglot/optimizer/pushdown_predicates.py +++ /dev/null @@ -1,237 +0,0 @@ -# Contains code from https://github.com/tobymao/sqlglot/blob/v28.5.0/sqlglot/optimizer/pushdown_predicates.py - -from bigframes_vendored.sqlglot import Dialect, exp -from bigframes_vendored.sqlglot.optimizer.normalize import normalized -from bigframes_vendored.sqlglot.optimizer.scope import build_scope, find_in_scope -from bigframes_vendored.sqlglot.optimizer.simplify import simplify - - -def pushdown_predicates(expression, dialect=None): - """ - Rewrite sqlglot AST to pushdown predicates in FROMS and JOINS - - Example: - >>> import sqlglot - >>> sql = "SELECT y.a AS a FROM (SELECT x.a AS a FROM x AS x) AS y WHERE y.a = 1" - >>> expression = sqlglot.parse_one(sql) - >>> pushdown_predicates(expression).sql() - 'SELECT y.a AS a FROM (SELECT x.a AS a FROM x AS x WHERE x.a = 1) AS y WHERE TRUE' - - Args: - expression (sqlglot.Expression): expression to optimize - Returns: - sqlglot.Expression: optimized expression - """ - from bigframes_vendored.sqlglot.dialects.athena import Athena - from bigframes_vendored.sqlglot.dialects.presto import Presto - - root = build_scope(expression) - - dialect = Dialect.get_or_raise(dialect) - unnest_requires_cross_join = isinstance(dialect, (Athena, Presto)) - - if root: - scope_ref_count = root.ref_count() - - for scope in reversed(list(root.traverse())): - select = scope.expression - where = select.args.get("where") - if where: - selected_sources = scope.selected_sources - join_index = { - join.alias_or_name: i - for i, join in enumerate(select.args.get("joins") or []) - } - - # a right join can only push down to itself and not the source FROM table - # presto, trino and athena don't support inner joins where the RHS is an UNNEST expression - pushdown_allowed = True - for k, (node, source) in selected_sources.items(): - parent = node.find_ancestor(exp.Join, exp.From) - if isinstance(parent, exp.Join): - if parent.side == "RIGHT": - selected_sources = {k: (node, source)} - break - if isinstance(node, exp.Unnest) and unnest_requires_cross_join: - pushdown_allowed = False - break - - if pushdown_allowed: - pushdown( - where.this, - selected_sources, - scope_ref_count, - dialect, - join_index, - ) - - # joins should only pushdown into itself, not to other joins - # so we limit the selected sources to only itself - for join in select.args.get("joins") or []: - name = join.alias_or_name - if name in scope.selected_sources: - pushdown( - join.args.get("on"), - {name: scope.selected_sources[name]}, - scope_ref_count, - dialect, - ) - - return expression - - -def pushdown(condition, sources, scope_ref_count, dialect, join_index=None): - if not condition: - return - - condition = condition.replace(simplify(condition, dialect=dialect)) - cnf_like = normalized(condition) or not normalized(condition, dnf=True) - - predicates = list( - condition.flatten() - if isinstance(condition, exp.And if cnf_like else exp.Or) - else [condition] - ) - - if cnf_like: - pushdown_cnf(predicates, sources, scope_ref_count, join_index=join_index) - else: - pushdown_dnf(predicates, sources, scope_ref_count) - - -def pushdown_cnf(predicates, sources, scope_ref_count, join_index=None): - """ - If the predicates are in CNF like form, we can simply replace each block in the parent. - """ - join_index = join_index or {} - for predicate in predicates: - for node in nodes_for_predicate(predicate, sources, scope_ref_count).values(): - if isinstance(node, exp.Join): - name = node.alias_or_name - predicate_tables = exp.column_table_names(predicate, name) - - # Don't push the predicate if it references tables that appear in later joins - this_index = join_index[name] - if all( - join_index.get(table, -1) < this_index for table in predicate_tables - ): - predicate.replace(exp.true()) - node.on(predicate, copy=False) - break - if isinstance(node, exp.Select): - predicate.replace(exp.true()) - inner_predicate = replace_aliases(node, predicate) - if find_in_scope(inner_predicate, exp.AggFunc): - node.having(inner_predicate, copy=False) - else: - node.where(inner_predicate, copy=False) - - -def pushdown_dnf(predicates, sources, scope_ref_count): - """ - If the predicates are in DNF form, we can only push down conditions that are in all blocks. - Additionally, we can't remove predicates from their original form. - """ - # find all the tables that can be pushdown too - # these are tables that are referenced in all blocks of a DNF - # (a.x AND b.x) OR (a.y AND c.y) - # only table a can be push down - pushdown_tables = set() - - for a in predicates: - a_tables = exp.column_table_names(a) - - for b in predicates: - a_tables &= exp.column_table_names(b) - - pushdown_tables.update(a_tables) - - conditions = {} - - # pushdown all predicates to their respective nodes - for table in sorted(pushdown_tables): - for predicate in predicates: - nodes = nodes_for_predicate(predicate, sources, scope_ref_count) - - if table not in nodes: - continue - - conditions[table] = ( - exp.or_(conditions[table], predicate) - if table in conditions - else predicate - ) - - for name, node in nodes.items(): - if name not in conditions: - continue - - predicate = conditions[name] - - if isinstance(node, exp.Join): - node.on(predicate, copy=False) - elif isinstance(node, exp.Select): - inner_predicate = replace_aliases(node, predicate) - if find_in_scope(inner_predicate, exp.AggFunc): - node.having(inner_predicate, copy=False) - else: - node.where(inner_predicate, copy=False) - - -def nodes_for_predicate(predicate, sources, scope_ref_count): - nodes = {} - tables = exp.column_table_names(predicate) - where_condition = isinstance( - predicate.find_ancestor(exp.Join, exp.Where), exp.Where - ) - - for table in sorted(tables): - node, source = sources.get(table) or (None, None) - - # if the predicate is in a where statement we can try to push it down - # we want to find the root join or from statement - if node and where_condition: - node = node.find_ancestor(exp.Join, exp.From) - - # a node can reference a CTE which should be pushed down - if isinstance(node, exp.From) and not isinstance(source, exp.Table): - with_ = source.parent.expression.args.get("with_") - if with_ and with_.recursive: - return {} - node = source.expression - - if isinstance(node, exp.Join): - if node.side and node.side != "RIGHT": - return {} - nodes[table] = node - elif isinstance(node, exp.Select) and len(tables) == 1: - # We can't push down window expressions - has_window_expression = any( - select for select in node.selects if select.find(exp.Window) - ) - # we can't push down predicates to select statements if they are referenced in - # multiple places. - if ( - not node.args.get("group") - and scope_ref_count[id(source)] < 2 - and not has_window_expression - ): - nodes[table] = node - return nodes - - -def replace_aliases(source, predicate): - aliases = {} - - for select in source.selects: - if isinstance(select, exp.Alias): - aliases[select.alias] = select.this - else: - aliases[select.name] = select - - def _replace_alias(column): - if isinstance(column, exp.Column) and column.name in aliases: - return aliases[column.name].copy() - return column - - return predicate.transform(_replace_alias) diff --git a/third_party/bigframes_vendored/sqlglot/optimizer/pushdown_projections.py b/third_party/bigframes_vendored/sqlglot/optimizer/pushdown_projections.py deleted file mode 100644 index b83dcb2c563..00000000000 --- a/third_party/bigframes_vendored/sqlglot/optimizer/pushdown_projections.py +++ /dev/null @@ -1,183 +0,0 @@ -# Contains code from https://github.com/tobymao/sqlglot/blob/v28.5.0/sqlglot/optimizer/pushdown_projections.py - -from __future__ import annotations - -import typing as t -from collections import defaultdict - -from bigframes_vendored.sqlglot import alias, exp -from bigframes_vendored.sqlglot.errors import OptimizeError -from bigframes_vendored.sqlglot.helper import seq_get -from bigframes_vendored.sqlglot.optimizer.qualify_columns import Resolver -from bigframes_vendored.sqlglot.optimizer.scope import Scope, traverse_scope -from bigframes_vendored.sqlglot.schema import ensure_schema - -if t.TYPE_CHECKING: - from bigframes_vendored.sqlglot._typing import E - from bigframes_vendored.sqlglot.dialects.dialect import DialectType - from bigframes_vendored.sqlglot.schema import Schema - -# Sentinel value that means an outer query selecting ALL columns -SELECT_ALL = object() - - -# Selection to use if selection list is empty -def default_selection(is_agg: bool) -> exp.Alias: - return alias(exp.Max(this=exp.Literal.number(1)) if is_agg else "1", "_") - - -def pushdown_projections( - expression: E, - schema: t.Optional[t.Dict | Schema] = None, - remove_unused_selections: bool = True, - dialect: DialectType = None, -) -> E: - """ - Rewrite sqlglot AST to remove unused columns projections. - - Example: - >>> import sqlglot - >>> sql = "SELECT y.a AS a FROM (SELECT x.a AS a, x.b AS b FROM x) AS y" - >>> expression = sqlglot.parse_one(sql) - >>> pushdown_projections(expression).sql() - 'SELECT y.a AS a FROM (SELECT x.a AS a FROM x) AS y' - - Args: - expression (sqlglot.Expression): expression to optimize - remove_unused_selections (bool): remove selects that are unused - Returns: - sqlglot.Expression: optimized expression - """ - # Map of Scope to all columns being selected by outer queries. - schema = ensure_schema(schema, dialect=dialect) - source_column_alias_count: t.Dict[exp.Expression | Scope, int] = {} - referenced_columns: t.DefaultDict[Scope, t.Set[str | object]] = defaultdict(set) - - # We build the scope tree (which is traversed in DFS postorder), then iterate - # over the result in reverse order. This should ensure that the set of selected - # columns for a particular scope are completely build by the time we get to it. - for scope in reversed(traverse_scope(expression)): - parent_selections = referenced_columns.get(scope, {SELECT_ALL}) - alias_count = source_column_alias_count.get(scope, 0) - - # We can't remove columns SELECT DISTINCT nor UNION DISTINCT. - if scope.expression.args.get("distinct"): - parent_selections = {SELECT_ALL} - - if isinstance(scope.expression, exp.SetOperation): - set_op = scope.expression - if not (set_op.kind or set_op.side): - # Do not optimize this set operation if it's using the BigQuery specific - # kind / side syntax (e.g INNER UNION ALL BY NAME) which changes the semantics of the operation - left, right = scope.union_scopes - if len(left.expression.selects) != len(right.expression.selects): - scope_sql = scope.expression.sql(dialect=dialect) - raise OptimizeError( - f"Invalid set operation due to column mismatch: {scope_sql}." - ) - - referenced_columns[left] = parent_selections - - if any(select.is_star for select in right.expression.selects): - referenced_columns[right] = parent_selections - elif not any(select.is_star for select in left.expression.selects): - if scope.expression.args.get("by_name"): - referenced_columns[right] = referenced_columns[left] - else: - referenced_columns[right] = { - right.expression.selects[i].alias_or_name - for i, select in enumerate(left.expression.selects) - if SELECT_ALL in parent_selections - or select.alias_or_name in parent_selections - } - - if isinstance(scope.expression, exp.Select): - if remove_unused_selections: - _remove_unused_selections(scope, parent_selections, schema, alias_count) - - if scope.expression.is_star: - continue - - # Group columns by source name - selects = defaultdict(set) - for col in scope.columns: - table_name = col.table - col_name = col.name - selects[table_name].add(col_name) - - # Push the selected columns down to the next scope - for name, (node, source) in scope.selected_sources.items(): - if isinstance(source, Scope): - select = seq_get(source.expression.selects, 0) - - if scope.pivots or isinstance(select, exp.QueryTransform): - columns = {SELECT_ALL} - else: - columns = selects.get(name) or set() - - referenced_columns[source].update(columns) - - column_aliases = node.alias_column_names - if column_aliases: - source_column_alias_count[source] = len(column_aliases) - - return expression - - -def _remove_unused_selections(scope, parent_selections, schema, alias_count): - order = scope.expression.args.get("order") - - if order: - # Assume columns without a qualified table are references to output columns - order_refs = {c.name for c in order.find_all(exp.Column) if not c.table} - else: - order_refs = set() - - new_selections = [] - removed = False - star = False - is_agg = False - - select_all = SELECT_ALL in parent_selections - - for selection in scope.expression.selects: - name = selection.alias_or_name - - if ( - select_all - or name in parent_selections - or name in order_refs - or alias_count > 0 - ): - new_selections.append(selection) - alias_count -= 1 - else: - if selection.is_star: - star = True - removed = True - - if not is_agg and selection.find(exp.AggFunc): - is_agg = True - - if star: - resolver = Resolver(scope, schema) - names = {s.alias_or_name for s in new_selections} - - for name in sorted(parent_selections): - if name not in names: - new_selections.append( - alias( - exp.column(name, table=resolver.get_table(name)), - name, - copy=False, - ) - ) - - # If there are no remaining selections, just select a single constant - if not new_selections: - new_selections.append(default_selection(is_agg)) - - scope.expression.select(*new_selections, append=False, copy=False) - - if removed: - scope.clear_cache() diff --git a/third_party/bigframes_vendored/sqlglot/optimizer/qualify.py b/third_party/bigframes_vendored/sqlglot/optimizer/qualify.py deleted file mode 100644 index cf518b06015..00000000000 --- a/third_party/bigframes_vendored/sqlglot/optimizer/qualify.py +++ /dev/null @@ -1,124 +0,0 @@ -# Contains code from https://github.com/tobymao/sqlglot/blob/v28.5.0/sqlglot/optimizer/qualify.py - -from __future__ import annotations - -import typing as t - -from bigframes_vendored.sqlglot import exp -from bigframes_vendored.sqlglot.dialects.dialect import Dialect, DialectType -from bigframes_vendored.sqlglot.optimizer.isolate_table_selects import ( - isolate_table_selects, -) -from bigframes_vendored.sqlglot.optimizer.normalize_identifiers import ( - normalize_identifiers, -) -from bigframes_vendored.sqlglot.optimizer.qualify_columns import ( - qualify_columns as qualify_columns_func, -) -from bigframes_vendored.sqlglot.optimizer.qualify_columns import ( - quote_identifiers as quote_identifiers_func, -) -from bigframes_vendored.sqlglot.optimizer.qualify_columns import ( - validate_qualify_columns as validate_qualify_columns_func, -) -from bigframes_vendored.sqlglot.optimizer.qualify_tables import qualify_tables -from bigframes_vendored.sqlglot.schema import Schema, ensure_schema - - -def qualify( - expression: exp.Expression, - dialect: DialectType = None, - db: t.Optional[str] = None, - catalog: t.Optional[str] = None, - schema: t.Optional[dict | Schema] = None, - expand_alias_refs: bool = True, - expand_stars: bool = True, - infer_schema: t.Optional[bool] = None, - isolate_tables: bool = False, - qualify_columns: bool = True, - allow_partial_qualification: bool = False, - validate_qualify_columns: bool = True, - quote_identifiers: bool = True, - identify: bool = True, - canonicalize_table_aliases: bool = False, - on_qualify: t.Optional[t.Callable[[exp.Expression], None]] = None, - sql: t.Optional[str] = None, -) -> exp.Expression: - """ - Rewrite sqlglot AST to have normalized and qualified tables and columns. - - This step is necessary for all further SQLGlot optimizations. - - Example: - >>> import sqlglot - >>> schema = {"tbl": {"col": "INT"}} - >>> expression = sqlglot.parse_one("SELECT col FROM tbl") - >>> qualify(expression, schema=schema).sql() - 'SELECT "tbl"."col" AS "col" FROM "tbl" AS "tbl"' - - Args: - expression: Expression to qualify. - db: Default database name for tables. - catalog: Default catalog name for tables. - schema: Schema to infer column names and types. - expand_alias_refs: Whether to expand references to aliases. - expand_stars: Whether to expand star queries. This is a necessary step - for most of the optimizer's rules to work; do not set to False unless you - know what you're doing! - infer_schema: Whether to infer the schema if missing. - isolate_tables: Whether to isolate table selects. - qualify_columns: Whether to qualify columns. - allow_partial_qualification: Whether to allow partial qualification. - validate_qualify_columns: Whether to validate columns. - quote_identifiers: Whether to run the quote_identifiers step. - This step is necessary to ensure correctness for case sensitive queries. - But this flag is provided in case this step is performed at a later time. - identify: If True, quote all identifiers, else only necessary ones. - canonicalize_table_aliases: Whether to use canonical aliases (_0, _1, ...) for all sources - instead of preserving table names. - on_qualify: Callback after a table has been qualified. - sql: Original SQL string for error highlighting. If not provided, errors will not include - highlighting. Requires that the expression has position metadata from parsing. - - Returns: - The qualified expression. - """ - schema = ensure_schema(schema, dialect=dialect) - dialect = Dialect.get_or_raise(dialect) - - expression = normalize_identifiers( - expression, - dialect=dialect, - store_original_column_identifiers=True, - ) - expression = qualify_tables( - expression, - db=db, - catalog=catalog, - dialect=dialect, - on_qualify=on_qualify, - canonicalize_table_aliases=canonicalize_table_aliases, - ) - - if isolate_tables: - expression = isolate_table_selects(expression, schema=schema) - - if qualify_columns: - expression = qualify_columns_func( - expression, - schema, - expand_alias_refs=expand_alias_refs, - expand_stars=expand_stars, - infer_schema=infer_schema, - allow_partial_qualification=allow_partial_qualification, - ) - - if quote_identifiers: - expression = quote_identifiers_func( - expression, dialect=dialect, identify=identify - ) - - if validate_qualify_columns: - validate_qualify_columns_func(expression, sql=sql) - - return expression diff --git a/third_party/bigframes_vendored/sqlglot/optimizer/qualify_columns.py b/third_party/bigframes_vendored/sqlglot/optimizer/qualify_columns.py deleted file mode 100644 index 51a0a6d4dc0..00000000000 --- a/third_party/bigframes_vendored/sqlglot/optimizer/qualify_columns.py +++ /dev/null @@ -1,1053 +0,0 @@ -# Contains code from https://github.com/tobymao/sqlglot/blob/v28.5.0/sqlglot/optimizer/qualify_columns.py - -from __future__ import annotations - -import itertools -import typing as t - -from bigframes_vendored.sqlglot import alias, exp -from bigframes_vendored.sqlglot.dialects.dialect import Dialect, DialectType -from bigframes_vendored.sqlglot.errors import OptimizeError, highlight_sql -from bigframes_vendored.sqlglot.helper import seq_get -from bigframes_vendored.sqlglot.optimizer.annotate_types import TypeAnnotator -from bigframes_vendored.sqlglot.optimizer.resolver import Resolver -from bigframes_vendored.sqlglot.optimizer.scope import ( - Scope, - build_scope, - traverse_scope, - walk_in_scope, -) -from bigframes_vendored.sqlglot.optimizer.simplify import simplify_parens -from bigframes_vendored.sqlglot.schema import Schema, ensure_schema - -if t.TYPE_CHECKING: - from bigframes_vendored.sqlglot._typing import E - - -def qualify_columns( - expression: exp.Expression, - schema: t.Dict | Schema, - expand_alias_refs: bool = True, - expand_stars: bool = True, - infer_schema: t.Optional[bool] = None, - allow_partial_qualification: bool = False, - dialect: DialectType = None, -) -> exp.Expression: - """ - Rewrite sqlglot AST to have fully qualified columns. - - Example: - >>> import sqlglot - >>> schema = {"tbl": {"col": "INT"}} - >>> expression = sqlglot.parse_one("SELECT col FROM tbl") - >>> qualify_columns(expression, schema).sql() - 'SELECT tbl.col AS col FROM tbl' - - Args: - expression: Expression to qualify. - schema: Database schema. - expand_alias_refs: Whether to expand references to aliases. - expand_stars: Whether to expand star queries. This is a necessary step - for most of the optimizer's rules to work; do not set to False unless you - know what you're doing! - infer_schema: Whether to infer the schema if missing. - allow_partial_qualification: Whether to allow partial qualification. - - Returns: - The qualified expression. - - Notes: - - Currently only handles a single PIVOT or UNPIVOT operator - """ - schema = ensure_schema(schema, dialect=dialect) - annotator = TypeAnnotator(schema) - infer_schema = schema.empty if infer_schema is None else infer_schema - dialect = schema.dialect or Dialect() - pseudocolumns = dialect.PSEUDOCOLUMNS - - for scope in traverse_scope(expression): - if dialect.PREFER_CTE_ALIAS_COLUMN: - pushdown_cte_alias_columns(scope) - - scope_expression = scope.expression - is_select = isinstance(scope_expression, exp.Select) - - _separate_pseudocolumns(scope, pseudocolumns) - - resolver = Resolver(scope, schema, infer_schema=infer_schema) - _pop_table_column_aliases(scope.ctes) - _pop_table_column_aliases(scope.derived_tables) - using_column_tables = _expand_using(scope, resolver) - - if ( - schema.empty or dialect.FORCE_EARLY_ALIAS_REF_EXPANSION - ) and expand_alias_refs: - _expand_alias_refs( - scope, - resolver, - dialect, - expand_only_groupby=dialect.EXPAND_ONLY_GROUP_ALIAS_REF, - ) - - _convert_columns_to_dots(scope, resolver) - _qualify_columns( - scope, - resolver, - allow_partial_qualification=allow_partial_qualification, - ) - - if not schema.empty and expand_alias_refs: - _expand_alias_refs(scope, resolver, dialect) - - if is_select: - if expand_stars: - _expand_stars( - scope, - resolver, - using_column_tables, - pseudocolumns, - annotator, - ) - qualify_outputs(scope) - - _expand_group_by(scope, dialect) - - # DISTINCT ON and ORDER BY follow the same rules (tested in DuckDB, Postgres, ClickHouse) - # https://www.postgresql.org/docs/current/sql-select.html#SQL-DISTINCT - _expand_order_by_and_distinct_on(scope, resolver) - - if dialect.ANNOTATE_ALL_SCOPES: - annotator.annotate_scope(scope) - - return expression - - -def validate_qualify_columns(expression: E, sql: t.Optional[str] = None) -> E: - """Raise an `OptimizeError` if any columns aren't qualified""" - all_unqualified_columns = [] - for scope in traverse_scope(expression): - if isinstance(scope.expression, exp.Select): - unqualified_columns = scope.unqualified_columns - - if ( - scope.external_columns - and not scope.is_correlated_subquery - and not scope.pivots - ): - column = scope.external_columns[0] - for_table = f" for table: '{column.table}'" if column.table else "" - line = column.this.meta.get("line") - col = column.this.meta.get("col") - start = column.this.meta.get("start") - end = column.this.meta.get("end") - - error_msg = f"Column '{column.name}' could not be resolved{for_table}." - if line and col: - error_msg += f" Line: {line}, Col: {col}" - if sql and start is not None and end is not None: - formatted_sql = highlight_sql(sql, [(start, end)])[0] - error_msg += f"\n {formatted_sql}" - - raise OptimizeError(error_msg) - - if unqualified_columns and scope.pivots and scope.pivots[0].unpivot: - # New columns produced by the UNPIVOT can't be qualified, but there may be columns - # under the UNPIVOT's IN clause that can and should be qualified. We recompute - # this list here to ensure those in the former category will be excluded. - unpivot_columns = set(_unpivot_columns(scope.pivots[0])) - unqualified_columns = [ - c for c in unqualified_columns if c not in unpivot_columns - ] - - all_unqualified_columns.extend(unqualified_columns) - - if all_unqualified_columns: - first_column = all_unqualified_columns[0] - line = first_column.this.meta.get("line") - col = first_column.this.meta.get("col") - start = first_column.this.meta.get("start") - end = first_column.this.meta.get("end") - - error_msg = f"Ambiguous column '{first_column.name}'" - if line and col: - error_msg += f" (Line: {line}, Col: {col})" - if sql and start is not None and end is not None: - formatted_sql = highlight_sql(sql, [(start, end)])[0] - error_msg += f"\n {formatted_sql}" - - raise OptimizeError(error_msg) - - return expression - - -def _separate_pseudocolumns(scope: Scope, pseudocolumns: t.Set[str]) -> None: - if not pseudocolumns: - return - - has_pseudocolumns = False - scope_expression = scope.expression - - for column in scope.columns: - name = column.name.upper() - if name not in pseudocolumns: - continue - - if name != "LEVEL" or ( - isinstance(scope_expression, exp.Select) - and scope_expression.args.get("connect") - ): - column.replace(exp.Pseudocolumn(**column.args)) - has_pseudocolumns = True - - if has_pseudocolumns: - scope.clear_cache() - - -def _unpivot_columns(unpivot: exp.Pivot) -> t.Iterator[exp.Column]: - name_columns = [ - field.this - for field in unpivot.fields - if isinstance(field, exp.In) and isinstance(field.this, exp.Column) - ] - value_columns = (c for e in unpivot.expressions for c in e.find_all(exp.Column)) - - return itertools.chain(name_columns, value_columns) - - -def _pop_table_column_aliases(derived_tables: t.List[exp.CTE | exp.Subquery]) -> None: - """ - Remove table column aliases. - - For example, `col1` and `col2` will be dropped in SELECT ... FROM (SELECT ...) AS foo(col1, col2) - """ - for derived_table in derived_tables: - if ( - isinstance(derived_table.parent, exp.With) - and derived_table.parent.recursive - ): - continue - table_alias = derived_table.args.get("alias") - if table_alias: - table_alias.set("columns", None) - - -def _expand_using(scope: Scope, resolver: Resolver) -> t.Dict[str, t.Any]: - columns = {} - - def _update_source_columns(source_name: str) -> None: - for column_name in resolver.get_source_columns(source_name): - if column_name not in columns: - columns[column_name] = source_name - - joins = list(scope.find_all(exp.Join)) - names = {join.alias_or_name for join in joins} - ordered = [key for key in scope.selected_sources if key not in names] - - if names and not ordered: - raise OptimizeError(f"Joins {names} missing source table {scope.expression}") - - # Mapping of automatically joined column names to an ordered set of source names (dict). - column_tables: t.Dict[str, t.Dict[str, t.Any]] = {} - - for source_name in ordered: - _update_source_columns(source_name) - - for i, join in enumerate(joins): - source_table = ordered[-1] - if source_table: - _update_source_columns(source_table) - - join_table = join.alias_or_name - ordered.append(join_table) - - using = join.args.get("using") - if not using: - continue - - join_columns = resolver.get_source_columns(join_table) - conditions = [] - using_identifier_count = len(using) - is_semi_or_anti_join = join.is_semi_or_anti_join - - for identifier in using: - identifier = identifier.name - table = columns.get(identifier) - - if not table or identifier not in join_columns: - if (columns and "*" not in columns) and join_columns: - raise OptimizeError(f"Cannot automatically join: {identifier}") - - table = table or source_table - - if i == 0 or using_identifier_count == 1: - lhs: exp.Expression = exp.column(identifier, table=table) - else: - coalesce_columns = [ - exp.column(identifier, table=t) - for t in ordered[:-1] - if identifier in resolver.get_source_columns(t) - ] - if len(coalesce_columns) > 1: - lhs = exp.func("coalesce", *coalesce_columns) - else: - lhs = exp.column(identifier, table=table) - - conditions.append(lhs.eq(exp.column(identifier, table=join_table))) - - # Set all values in the dict to None, because we only care about the key ordering - tables = column_tables.setdefault(identifier, {}) - - # Do not update the dict if this was a SEMI/ANTI join in - # order to avoid generating COALESCE columns for this join pair - if not is_semi_or_anti_join: - if table not in tables: - tables[table] = None - if join_table not in tables: - tables[join_table] = None - - join.set("using", None) - join.set("on", exp.and_(*conditions, copy=False)) - - if column_tables: - for column in scope.columns: - if not column.table and column.name in column_tables: - tables = column_tables[column.name] - coalesce_args = [ - exp.column(column.name, table=table) for table in tables - ] - replacement: exp.Expression = exp.func("coalesce", *coalesce_args) - - if isinstance(column.parent, exp.Select): - # Ensure the USING column keeps its name if it's projected - replacement = alias(replacement, alias=column.name, copy=False) - elif isinstance(column.parent, exp.Struct): - # Ensure the USING column keeps its name if it's an anonymous STRUCT field - replacement = exp.PropertyEQ( - this=exp.to_identifier(column.name), expression=replacement - ) - - scope.replace(column, replacement) - - return column_tables - - -def _expand_alias_refs( - scope: Scope, - resolver: Resolver, - dialect: Dialect, - expand_only_groupby: bool = False, -) -> None: - """ - Expand references to aliases. - Example: - SELECT y.foo AS bar, bar * 2 AS baz FROM y - => SELECT y.foo AS bar, y.foo * 2 AS baz FROM y - """ - expression = scope.expression - - if not isinstance(expression, exp.Select) or dialect.DISABLES_ALIAS_REF_EXPANSION: - return - - alias_to_expression: t.Dict[str, t.Tuple[exp.Expression, int]] = {} - projections = {s.alias_or_name for s in expression.selects} - replaced = False - - def replace_columns( - node: t.Optional[exp.Expression], - resolve_table: bool = False, - literal_index: bool = False, - ) -> None: - nonlocal replaced - is_group_by = isinstance(node, exp.Group) - is_having = isinstance(node, exp.Having) - if not node or (expand_only_groupby and not is_group_by): - return - - for column in walk_in_scope(node, prune=lambda node: node.is_star): - if not isinstance(column, exp.Column): - continue - - # BigQuery's GROUP BY allows alias expansion only for standalone names, e.g: - # SELECT FUNC(col) AS col FROM t GROUP BY col --> Can be expanded - # SELECT FUNC(col) AS col FROM t GROUP BY FUNC(col) --> Shouldn't be expanded, will result to FUNC(FUNC(col)) - # This not required for the HAVING clause as it can evaluate expressions using both the alias & the table columns - if expand_only_groupby and is_group_by and column.parent is not node: - continue - - skip_replace = False - table = ( - resolver.get_table(column.name) - if resolve_table and not column.table - else None - ) - alias_expr, i = alias_to_expression.get(column.name, (None, 1)) - - if alias_expr: - skip_replace = bool( - alias_expr.find(exp.AggFunc) - and column.find_ancestor(exp.AggFunc) - and not isinstance( - column.find_ancestor(exp.Window, exp.Select), exp.Window - ) - ) - - # BigQuery's having clause gets confused if an alias matches a source. - # SELECT x.a, max(x.b) as x FROM x GROUP BY 1 HAVING x > 1; - # If "HAVING x" is expanded to "HAVING max(x.b)", BQ would blindly replace the "x" reference with the projection MAX(x.b) - # i.e HAVING MAX(MAX(x.b).b), resulting in the error: "Aggregations of aggregations are not allowed" - if is_having and dialect.PROJECTION_ALIASES_SHADOW_SOURCE_NAMES: - skip_replace = skip_replace or any( - node.parts[0].name in projections - for node in alias_expr.find_all(exp.Column) - ) - elif dialect.PROJECTION_ALIASES_SHADOW_SOURCE_NAMES and ( - is_group_by or is_having - ): - column_table = table.name if table else column.table - if column_table in projections: - # BigQuery's GROUP BY and HAVING clauses get confused if the column name - # matches a source name and a projection. For instance: - # SELECT id, ARRAY_AGG(col) AS custom_fields FROM custom_fields GROUP BY id HAVING id >= 1 - # We should not qualify "id" with "custom_fields" in either clause, since the aggregation shadows the actual table - # and we'd get the error: "Column custom_fields contains an aggregation function, which is not allowed in GROUP BY clause" - column.replace(exp.to_identifier(column.name)) - replaced = True - return - - if table and (not alias_expr or skip_replace): - column.set("table", table) - elif not column.table and alias_expr and not skip_replace: - if (isinstance(alias_expr, exp.Literal) or alias_expr.is_number) and ( - literal_index or resolve_table - ): - if literal_index: - column.replace(exp.Literal.number(i)) - replaced = True - else: - replaced = True - column = column.replace(exp.paren(alias_expr)) - simplified = simplify_parens(column, dialect) - if simplified is not column: - column.replace(simplified) - - for i, projection in enumerate(expression.selects): - replace_columns(projection) - if isinstance(projection, exp.Alias): - alias_to_expression[projection.alias] = (projection.this, i + 1) - - parent_scope = scope - on_right_sub_tree = False - while parent_scope and not parent_scope.is_cte: - if parent_scope.is_union: - on_right_sub_tree = ( - parent_scope.parent.expression.right is parent_scope.expression - ) - parent_scope = parent_scope.parent - - # We shouldn't expand aliases if they match the recursive CTE's columns - # and we are in the recursive part (right sub tree) of the CTE - if parent_scope and on_right_sub_tree: - cte = parent_scope.expression.parent - if cte.find_ancestor(exp.With).recursive: - for recursive_cte_column in cte.args["alias"].columns or cte.this.selects: - alias_to_expression.pop(recursive_cte_column.output_name, None) - - replace_columns(expression.args.get("where")) - replace_columns(expression.args.get("group"), literal_index=True) - replace_columns(expression.args.get("having"), resolve_table=True) - replace_columns(expression.args.get("qualify"), resolve_table=True) - - if dialect.SUPPORTS_ALIAS_REFS_IN_JOIN_CONDITIONS: - for join in expression.args.get("joins") or []: - replace_columns(join) - - if replaced: - scope.clear_cache() - - -def _expand_group_by(scope: Scope, dialect: Dialect) -> None: - expression = scope.expression - group = expression.args.get("group") - if not group: - return - - group.set( - "expressions", _expand_positional_references(scope, group.expressions, dialect) - ) - expression.set("group", group) - - -def _expand_order_by_and_distinct_on(scope: Scope, resolver: Resolver) -> None: - for modifier_key in ("order", "distinct"): - modifier = scope.expression.args.get(modifier_key) - if isinstance(modifier, exp.Distinct): - modifier = modifier.args.get("on") - - if not isinstance(modifier, exp.Expression): - continue - - modifier_expressions = modifier.expressions - if modifier_key == "order": - modifier_expressions = [ordered.this for ordered in modifier_expressions] - - for original, expanded in zip( - modifier_expressions, - _expand_positional_references( - scope, modifier_expressions, resolver.dialect, alias=True - ), - ): - for agg in original.find_all(exp.AggFunc): - for col in agg.find_all(exp.Column): - if not col.table: - col.set("table", resolver.get_table(col.name)) - - original.replace(expanded) - - if scope.expression.args.get("group"): - selects = { - s.this: exp.column(s.alias_or_name) for s in scope.expression.selects - } - - for expression in modifier_expressions: - expression.replace( - exp.to_identifier(_select_by_pos(scope, expression).alias) - if expression.is_int - else selects.get(expression, expression) - ) - - -def _expand_positional_references( - scope: Scope, - expressions: t.Iterable[exp.Expression], - dialect: Dialect, - alias: bool = False, -) -> t.List[exp.Expression]: - new_nodes: t.List[exp.Expression] = [] - ambiguous_projections = None - - for node in expressions: - if node.is_int: - select = _select_by_pos(scope, t.cast(exp.Literal, node)) - - if alias: - new_nodes.append(exp.column(select.args["alias"].copy())) - else: - select = select.this - - if dialect.PROJECTION_ALIASES_SHADOW_SOURCE_NAMES: - if ambiguous_projections is None: - # When a projection name is also a source name and it is referenced in the - # GROUP BY clause, BQ can't understand what the identifier corresponds to - ambiguous_projections = { - s.alias_or_name - for s in scope.expression.selects - if s.alias_or_name in scope.selected_sources - } - - ambiguous = any( - column.parts[0].name in ambiguous_projections - for column in select.find_all(exp.Column) - ) - else: - ambiguous = False - - if ( - isinstance(select, exp.CONSTANTS) - or select.is_number - or select.find(exp.Explode, exp.Unnest) - or ambiguous - ): - new_nodes.append(node) - else: - new_nodes.append(select.copy()) - else: - new_nodes.append(node) - - return new_nodes - - -def _select_by_pos(scope: Scope, node: exp.Literal) -> exp.Alias: - try: - return scope.expression.selects[int(node.this) - 1].assert_is(exp.Alias) - except IndexError: - raise OptimizeError(f"Unknown output column: {node.name}") - - -def _convert_columns_to_dots(scope: Scope, resolver: Resolver) -> None: - """ - Converts `Column` instances that represent STRUCT or JSON field lookup into chained `Dots`. - - These lookups may be parsed as columns (e.g. "col"."field"."field2"), but they need to be - normalized to `Dot(Dot(...(
., field1), field2, ...))` to be qualified properly. - """ - converted = False - for column in itertools.chain(scope.columns, scope.stars): - if isinstance(column, exp.Dot): - continue - - column_table: t.Optional[str | exp.Identifier] = column.table - dot_parts = column.meta.pop("dot_parts", []) - if ( - column_table - and column_table not in scope.sources - and ( - not scope.parent - or column_table not in scope.parent.sources - or not scope.is_correlated_subquery - ) - ): - root, *parts = column.parts - - if root.name in scope.sources: - # The struct is already qualified, but we still need to change the AST - column_table = root - root, *parts = parts - was_qualified = True - else: - column_table = resolver.get_table(root.name) - was_qualified = False - - if column_table: - converted = True - new_column = exp.column(root, table=column_table) - - if dot_parts: - # Remove the actual column parts from the rest of dot parts - new_column.meta["dot_parts"] = dot_parts[ - 2 if was_qualified else 1 : - ] - - column.replace(exp.Dot.build([new_column, *parts])) - - if converted: - # We want to re-aggregate the converted columns, otherwise they'd be skipped in - # a `for column in scope.columns` iteration, even though they shouldn't be - scope.clear_cache() - - -def _qualify_columns( - scope: Scope, - resolver: Resolver, - allow_partial_qualification: bool, -) -> None: - """Disambiguate columns, ensuring each column specifies a source""" - for column in scope.columns: - column_table = column.table - column_name = column.name - - if column_table and column_table in scope.sources: - source_columns = resolver.get_source_columns(column_table) - if ( - not allow_partial_qualification - and source_columns - and column_name not in source_columns - and "*" not in source_columns - ): - raise OptimizeError(f"Unknown column: {column_name}") - - if not column_table: - if scope.pivots and not column.find_ancestor(exp.Pivot): - # If the column is under the Pivot expression, we need to qualify it - # using the name of the pivoted source instead of the pivot's alias - column.set("table", exp.to_identifier(scope.pivots[0].alias)) - continue - - # column_table can be a '' because bigquery unnest has no table alias - column_table = resolver.get_table(column) - - if column_table: - column.set("table", column_table) - elif ( - resolver.dialect.TABLES_REFERENCEABLE_AS_COLUMNS - and len(column.parts) == 1 - and column_name in scope.selected_sources - ): - # BigQuery and Postgres allow tables to be referenced as columns, treating them as structs/records - scope.replace(column, exp.TableColumn(this=column.this)) - - for pivot in scope.pivots: - for column in pivot.find_all(exp.Column): - if not column.table and column.name in resolver.all_columns: - column_table = resolver.get_table(column.name) - if column_table: - column.set("table", column_table) - - -def _expand_struct_stars_no_parens( - expression: exp.Dot, -) -> t.List[exp.Alias]: - """[BigQuery] Expand/Flatten foo.bar.* where bar is a struct column""" - - dot_column = expression.find(exp.Column) - if not isinstance(dot_column, exp.Column) or not dot_column.is_type( - exp.DataType.Type.STRUCT - ): - return [] - - # All nested struct values are ColumnDefs, so normalize the first exp.Column in one - dot_column = dot_column.copy() - starting_struct = exp.ColumnDef(this=dot_column.this, kind=dot_column.type) - - # First part is the table name and last part is the star so they can be dropped - dot_parts = expression.parts[1:-1] - - # If we're expanding a nested struct eg. t.c.f1.f2.* find the last struct (f2 in this case) - for part in dot_parts[1:]: - for field in t.cast(exp.DataType, starting_struct.kind).expressions: - # Unable to expand star unless all fields are named - if not isinstance(field.this, exp.Identifier): - return [] - - if field.name == part.name and field.kind.is_type(exp.DataType.Type.STRUCT): - starting_struct = field - break - else: - # There is no matching field in the struct - return [] - - taken_names = set() - new_selections = [] - - for field in t.cast(exp.DataType, starting_struct.kind).expressions: - name = field.name - - # Ambiguous or anonymous fields can't be expanded - if name in taken_names or not isinstance(field.this, exp.Identifier): - return [] - - taken_names.add(name) - - this = field.this.copy() - root, *parts = [part.copy() for part in itertools.chain(dot_parts, [this])] - new_column = exp.column( - t.cast(exp.Identifier, root), - table=dot_column.args.get("table"), - fields=t.cast(t.List[exp.Identifier], parts), - ) - new_selections.append(alias(new_column, this, copy=False)) - - return new_selections - - -def _expand_struct_stars_with_parens(expression: exp.Dot) -> t.List[exp.Alias]: - """[RisingWave] Expand/Flatten (.bar).*, where bar is a struct column""" - - # it is not ().* pattern, which means we can't expand - if not isinstance(expression.this, exp.Paren): - return [] - - # find column definition to get data-type - dot_column = expression.find(exp.Column) - if not isinstance(dot_column, exp.Column) or not dot_column.is_type( - exp.DataType.Type.STRUCT - ): - return [] - - parent = dot_column.parent - starting_struct = dot_column.type - - # walk up AST and down into struct definition in sync - while parent is not None: - if isinstance(parent, exp.Paren): - parent = parent.parent - continue - - # if parent is not a dot, then something is wrong - if not isinstance(parent, exp.Dot): - return [] - - # if the rhs of the dot is star we are done - rhs = parent.right - if isinstance(rhs, exp.Star): - break - - # if it is not identifier, then something is wrong - if not isinstance(rhs, exp.Identifier): - return [] - - # Check if current rhs identifier is in struct - matched = False - for struct_field_def in t.cast(exp.DataType, starting_struct).expressions: - if struct_field_def.name == rhs.name: - matched = True - starting_struct = struct_field_def.kind # update struct - break - - if not matched: - return [] - - parent = parent.parent - - # build new aliases to expand star - new_selections = [] - - # fetch the outermost parentheses for new aliaes - outer_paren = expression.this - - for struct_field_def in t.cast(exp.DataType, starting_struct).expressions: - new_identifier = struct_field_def.this.copy() - new_dot = exp.Dot.build([outer_paren.copy(), new_identifier]) - new_alias = alias(new_dot, new_identifier, copy=False) - new_selections.append(new_alias) - - return new_selections - - -def _expand_stars( - scope: Scope, - resolver: Resolver, - using_column_tables: t.Dict[str, t.Any], - pseudocolumns: t.Set[str], - annotator: TypeAnnotator, -) -> None: - """Expand stars to lists of column selections""" - - new_selections: t.List[exp.Expression] = [] - except_columns: t.Dict[int, t.Set[str]] = {} - replace_columns: t.Dict[int, t.Dict[str, exp.Alias]] = {} - rename_columns: t.Dict[int, t.Dict[str, str]] = {} - - coalesced_columns = set() - dialect = resolver.dialect - - pivot_output_columns = None - pivot_exclude_columns: t.Set[str] = set() - - pivot = t.cast(t.Optional[exp.Pivot], seq_get(scope.pivots, 0)) - if isinstance(pivot, exp.Pivot) and not pivot.alias_column_names: - if pivot.unpivot: - pivot_output_columns = [c.output_name for c in _unpivot_columns(pivot)] - - for field in pivot.fields: - if isinstance(field, exp.In): - pivot_exclude_columns.update( - c.output_name - for e in field.expressions - for c in e.find_all(exp.Column) - ) - - else: - pivot_exclude_columns = set( - c.output_name for c in pivot.find_all(exp.Column) - ) - - pivot_output_columns = [ - c.output_name for c in pivot.args.get("columns", []) - ] - if not pivot_output_columns: - pivot_output_columns = [c.alias_or_name for c in pivot.expressions] - - if dialect.SUPPORTS_STRUCT_STAR_EXPANSION and any( - isinstance(col, exp.Dot) for col in scope.stars - ): - # Found struct expansion, annotate scope ahead of time - annotator.annotate_scope(scope) - - for expression in scope.expression.selects: - tables = [] - if isinstance(expression, exp.Star): - tables.extend(scope.selected_sources) - _add_except_columns(expression, tables, except_columns) - _add_replace_columns(expression, tables, replace_columns) - _add_rename_columns(expression, tables, rename_columns) - elif expression.is_star: - if not isinstance(expression, exp.Dot): - tables.append(expression.table) - _add_except_columns(expression.this, tables, except_columns) - _add_replace_columns(expression.this, tables, replace_columns) - _add_rename_columns(expression.this, tables, rename_columns) - elif ( - dialect.SUPPORTS_STRUCT_STAR_EXPANSION - and not dialect.REQUIRES_PARENTHESIZED_STRUCT_ACCESS - ): - struct_fields = _expand_struct_stars_no_parens(expression) - if struct_fields: - new_selections.extend(struct_fields) - continue - elif dialect.REQUIRES_PARENTHESIZED_STRUCT_ACCESS: - struct_fields = _expand_struct_stars_with_parens(expression) - if struct_fields: - new_selections.extend(struct_fields) - continue - - if not tables: - new_selections.append(expression) - continue - - for table in tables: - if table not in scope.sources: - raise OptimizeError(f"Unknown table: {table}") - - columns = resolver.get_source_columns(table, only_visible=True) - columns = columns or scope.outer_columns - - if pseudocolumns and dialect.EXCLUDES_PSEUDOCOLUMNS_FROM_STAR: - columns = [ - name for name in columns if name.upper() not in pseudocolumns - ] - - if not columns or "*" in columns: - return - - table_id = id(table) - columns_to_exclude = except_columns.get(table_id) or set() - renamed_columns = rename_columns.get(table_id, {}) - replaced_columns = replace_columns.get(table_id, {}) - - if pivot: - if pivot_output_columns and pivot_exclude_columns: - pivot_columns = [ - c for c in columns if c not in pivot_exclude_columns - ] - pivot_columns.extend(pivot_output_columns) - else: - pivot_columns = pivot.alias_column_names - - if pivot_columns: - new_selections.extend( - alias(exp.column(name, table=pivot.alias), name, copy=False) - for name in pivot_columns - if name not in columns_to_exclude - ) - continue - - for name in columns: - if name in columns_to_exclude or name in coalesced_columns: - continue - if name in using_column_tables and table in using_column_tables[name]: - coalesced_columns.add(name) - tables = using_column_tables[name] - coalesce_args = [exp.column(name, table=table) for table in tables] - - new_selections.append( - alias( - exp.func("coalesce", *coalesce_args), alias=name, copy=False - ) - ) - else: - alias_ = renamed_columns.get(name, name) - selection_expr = replaced_columns.get(name) or exp.column( - name, table=table - ) - new_selections.append( - alias(selection_expr, alias_, copy=False) - if alias_ != name - else selection_expr - ) - - # Ensures we don't overwrite the initial selections with an empty list - if new_selections and isinstance(scope.expression, exp.Select): - scope.expression.set("expressions", new_selections) - - -def _add_except_columns( - expression: exp.Expression, tables, except_columns: t.Dict[int, t.Set[str]] -) -> None: - except_ = expression.args.get("except_") - - if not except_: - return - - columns = {e.name for e in except_} - - for table in tables: - except_columns[id(table)] = columns - - -def _add_rename_columns( - expression: exp.Expression, tables, rename_columns: t.Dict[int, t.Dict[str, str]] -) -> None: - rename = expression.args.get("rename") - - if not rename: - return - - columns = {e.this.name: e.alias for e in rename} - - for table in tables: - rename_columns[id(table)] = columns - - -def _add_replace_columns( - expression: exp.Expression, - tables, - replace_columns: t.Dict[int, t.Dict[str, exp.Alias]], -) -> None: - replace = expression.args.get("replace") - - if not replace: - return - - columns = {e.alias: e for e in replace} - - for table in tables: - replace_columns[id(table)] = columns - - -def qualify_outputs(scope_or_expression: Scope | exp.Expression) -> None: - """Ensure all output columns are aliased""" - if isinstance(scope_or_expression, exp.Expression): - scope = build_scope(scope_or_expression) - if not isinstance(scope, Scope): - return - else: - scope = scope_or_expression - - new_selections = [] - for i, (selection, aliased_column) in enumerate( - itertools.zip_longest(scope.expression.selects, scope.outer_columns) - ): - if selection is None or isinstance(selection, exp.QueryTransform): - break - - if isinstance(selection, exp.Subquery): - if not selection.output_name: - selection.set( - "alias", exp.TableAlias(this=exp.to_identifier(f"_col_{i}")) - ) - elif ( - not isinstance(selection, (exp.Alias, exp.Aliases)) - and not selection.is_star - ): - selection = alias( - selection, - alias=selection.output_name or f"_col_{i}", - copy=False, - ) - if aliased_column: - selection.set("alias", exp.to_identifier(aliased_column)) - - new_selections.append(selection) - - if new_selections and isinstance(scope.expression, exp.Select): - scope.expression.set("expressions", new_selections) - - -def quote_identifiers( - expression: E, dialect: DialectType = None, identify: bool = True -) -> E: - """Makes sure all identifiers that need to be quoted are quoted.""" - return expression.transform( - Dialect.get_or_raise(dialect).quote_identifier, identify=identify, copy=False - ) # type: ignore - - -def pushdown_cte_alias_columns(scope: Scope) -> None: - """ - Pushes down the CTE alias columns into the projection, - - This step is useful in Snowflake where the CTE alias columns can be referenced in the HAVING. - - Args: - scope: Scope to find ctes to pushdown aliases. - """ - for cte in scope.ctes: - if cte.alias_column_names and isinstance(cte.this, exp.Select): - new_expressions = [] - for _alias, projection in zip(cte.alias_column_names, cte.this.expressions): - if isinstance(projection, exp.Alias): - projection.set("alias", exp.to_identifier(_alias)) - else: - projection = alias(projection, alias=_alias) - new_expressions.append(projection) - cte.this.set("expressions", new_expressions) diff --git a/third_party/bigframes_vendored/sqlglot/optimizer/qualify_tables.py b/third_party/bigframes_vendored/sqlglot/optimizer/qualify_tables.py deleted file mode 100644 index 42e99f668e4..00000000000 --- a/third_party/bigframes_vendored/sqlglot/optimizer/qualify_tables.py +++ /dev/null @@ -1,227 +0,0 @@ -# Contains code from https://github.com/tobymao/sqlglot/blob/v28.5.0/sqlglot/optimizer/qualify_tables.py - -from __future__ import annotations - -import typing as t - -from bigframes_vendored.sqlglot import exp -from bigframes_vendored.sqlglot.dialects.dialect import Dialect, DialectType -from bigframes_vendored.sqlglot.helper import ensure_list, name_sequence, seq_get -from bigframes_vendored.sqlglot.optimizer.normalize_identifiers import ( - normalize_identifiers, -) -from bigframes_vendored.sqlglot.optimizer.scope import Scope, traverse_scope - -if t.TYPE_CHECKING: - from bigframes_vendored.sqlglot._typing import E - - -def qualify_tables( - expression: E, - db: t.Optional[str | exp.Identifier] = None, - catalog: t.Optional[str | exp.Identifier] = None, - on_qualify: t.Optional[t.Callable[[exp.Table], None]] = None, - dialect: DialectType = None, - canonicalize_table_aliases: bool = False, -) -> E: - """ - Rewrite sqlglot AST to have fully qualified tables. Join constructs such as - (t1 JOIN t2) AS t will be expanded into (SELECT * FROM t1 AS t1, t2 AS t2) AS t. - - Examples: - >>> import sqlglot - >>> expression = sqlglot.parse_one("SELECT 1 FROM tbl") - >>> qualify_tables(expression, db="db").sql() - 'SELECT 1 FROM db.tbl AS tbl' - >>> - >>> expression = sqlglot.parse_one("SELECT 1 FROM (t1 JOIN t2) AS t") - >>> qualify_tables(expression).sql() - 'SELECT 1 FROM (SELECT * FROM t1 AS t1, t2 AS t2) AS t' - - Args: - expression: Expression to qualify - db: Database name - catalog: Catalog name - on_qualify: Callback after a table has been qualified. - dialect: The dialect to parse catalog and schema into. - canonicalize_table_aliases: Whether to use canonical aliases (_0, _1, ...) for all sources - instead of preserving table names. Defaults to False. - - Returns: - The qualified expression. - """ - dialect = Dialect.get_or_raise(dialect) - next_alias_name = name_sequence("_") - - if db := db or None: - db = exp.parse_identifier(db, dialect=dialect) - db.meta["is_table"] = True - db = normalize_identifiers(db, dialect=dialect) - if catalog := catalog or None: - catalog = exp.parse_identifier(catalog, dialect=dialect) - catalog.meta["is_table"] = True - catalog = normalize_identifiers(catalog, dialect=dialect) - - def _qualify(table: exp.Table) -> None: - if isinstance(table.this, exp.Identifier): - if db and not table.args.get("db"): - table.set("db", db.copy()) - if catalog and not table.args.get("catalog") and table.args.get("db"): - table.set("catalog", catalog.copy()) - - if (db or catalog) and not isinstance(expression, exp.Query): - with_ = expression.args.get("with_") or exp.With() - cte_names = {cte.alias_or_name for cte in with_.expressions} - - for node in expression.walk(prune=lambda n: isinstance(n, exp.Query)): - if isinstance(node, exp.Table) and node.name not in cte_names: - _qualify(node) - - def _set_alias( - expression: exp.Expression, - canonical_aliases: t.Dict[str, str], - target_alias: t.Optional[str] = None, - scope: t.Optional[Scope] = None, - normalize: bool = False, - columns: t.Optional[t.List[t.Union[str, exp.Identifier]]] = None, - ) -> None: - alias = expression.args.get("alias") or exp.TableAlias() - - if canonicalize_table_aliases: - new_alias_name = next_alias_name() - canonical_aliases[alias.name or target_alias or ""] = new_alias_name - elif not alias.name: - new_alias_name = target_alias or next_alias_name() - if normalize and target_alias: - new_alias_name = normalize_identifiers( - new_alias_name, dialect=dialect - ).name - else: - return - - alias.set("this", exp.to_identifier(new_alias_name)) - - if columns: - alias.set("columns", [exp.to_identifier(c) for c in columns]) - - expression.set("alias", alias) - - if scope: - scope.rename_source(None, new_alias_name) - - for scope in traverse_scope(expression): - local_columns = scope.local_columns - canonical_aliases: t.Dict[str, str] = {} - - for query in scope.subqueries: - subquery = query.parent - if isinstance(subquery, exp.Subquery): - subquery.unwrap().replace(subquery) - - for derived_table in scope.derived_tables: - unnested = derived_table.unnest() - if isinstance(unnested, exp.Table): - joins = unnested.args.get("joins") - unnested.set("joins", None) - derived_table.this.replace( - exp.select("*").from_(unnested.copy(), copy=False) - ) - derived_table.this.set("joins", joins) - - _set_alias(derived_table, canonical_aliases, scope=scope) - if pivot := seq_get(derived_table.args.get("pivots") or [], 0): - _set_alias(pivot, canonical_aliases) - - table_aliases = {} - - for name, source in scope.sources.items(): - if isinstance(source, exp.Table): - # When the name is empty, it means that we have a non-table source, e.g. a pivoted cte - is_real_table_source = bool(name) - - if pivot := seq_get(source.args.get("pivots") or [], 0): - name = source.name - - table_this = source.this - table_alias = source.args.get("alias") - function_columns: t.List[t.Union[str, exp.Identifier]] = [] - if isinstance(table_this, exp.Func): - if not table_alias: - function_columns = ensure_list( - dialect.DEFAULT_FUNCTIONS_COLUMN_NAMES.get(type(table_this)) - ) - elif columns := table_alias.columns: - function_columns = columns - elif type(table_this) in dialect.DEFAULT_FUNCTIONS_COLUMN_NAMES: - function_columns = ensure_list(source.alias_or_name) - source.set("alias", None) - name = None - - _set_alias( - source, - canonical_aliases, - target_alias=name or source.name or None, - normalize=True, - columns=function_columns, - ) - - source_fqn = ".".join(p.name for p in source.parts) - table_aliases[source_fqn] = source.args["alias"].this.copy() - - if pivot: - target_alias = source.alias if pivot.unpivot else None - _set_alias( - pivot, - canonical_aliases, - target_alias=target_alias, - normalize=True, - ) - - # This case corresponds to a pivoted CTE, we don't want to qualify that - if isinstance(scope.sources.get(source.alias_or_name), Scope): - continue - - if is_real_table_source: - _qualify(source) - - if on_qualify: - on_qualify(source) - elif isinstance(source, Scope) and source.is_udtf: - _set_alias(udtf := source.expression, canonical_aliases) - - table_alias = udtf.args["alias"] - - if isinstance(udtf, exp.Values) and not table_alias.columns: - column_aliases = [ - normalize_identifiers(i, dialect=dialect) - for i in dialect.generate_values_aliases(udtf) - ] - table_alias.set("columns", column_aliases) - - for table in scope.tables: - if not table.alias and isinstance(table.parent, (exp.From, exp.Join)): - _set_alias(table, canonical_aliases, target_alias=table.name) - - for column in local_columns: - table = column.table - - if column.db: - table_alias = table_aliases.get( - ".".join(p.name for p in column.parts[0:-1]) - ) - - if table_alias: - for p in exp.COLUMN_PARTS[1:]: - column.set(p, None) - - column.set("table", table_alias.copy()) - elif ( - canonical_aliases - and table - and (canonical_table := canonical_aliases.get(table, "")) - != column.table - ): - # Amend existing aliases, e.g. t.c -> _0.c if t is aliased to _0 - column.set("table", exp.to_identifier(canonical_table)) - - return expression diff --git a/third_party/bigframes_vendored/sqlglot/optimizer/resolver.py b/third_party/bigframes_vendored/sqlglot/optimizer/resolver.py deleted file mode 100644 index 02b216ff0e6..00000000000 --- a/third_party/bigframes_vendored/sqlglot/optimizer/resolver.py +++ /dev/null @@ -1,399 +0,0 @@ -# Contains code from https://github.com/tobymao/sqlglot/blob/v28.5.0/sqlglot/optimizer/resolver.py - -from __future__ import annotations - -import itertools -import typing as t - -from bigframes_vendored.sqlglot import exp -from bigframes_vendored.sqlglot.dialects.dialect import Dialect -from bigframes_vendored.sqlglot.errors import OptimizeError -from bigframes_vendored.sqlglot.helper import SingleValuedMapping, seq_get -from bigframes_vendored.sqlglot.optimizer.scope import Scope - -if t.TYPE_CHECKING: - from bigframes_vendored.sqlglot.schema import Schema - - -class Resolver: - """ - Helper for resolving columns. - - This is a class so we can lazily load some things and easily share them across functions. - """ - - def __init__(self, scope: Scope, schema: Schema, infer_schema: bool = True): - self.scope = scope - self.schema = schema - self.dialect = schema.dialect or Dialect() - self._source_columns: t.Optional[t.Dict[str, t.Sequence[str]]] = None - self._unambiguous_columns: t.Optional[t.Mapping[str, str]] = None - self._all_columns: t.Optional[t.Set[str]] = None - self._infer_schema = infer_schema - self._get_source_columns_cache: t.Dict[t.Tuple[str, bool], t.Sequence[str]] = {} - - def get_table(self, column: str | exp.Column) -> t.Optional[exp.Identifier]: - """ - Get the table for a column name. - - Args: - column: The column expression (or column name) to find the table for. - Returns: - The table name if it can be found/inferred. - """ - column_name = column if isinstance(column, str) else column.name - - table_name = self._get_table_name_from_sources(column_name) - - if not table_name and isinstance(column, exp.Column): - # Fall-back case: If we couldn't find the `table_name` from ALL of the sources, - # attempt to disambiguate the column based on other characteristics e.g if this column is in a join condition, - # we may be able to disambiguate based on the source order. - if join_context := self._get_column_join_context(column): - # In this case, the return value will be the join that _may_ be able to disambiguate the column - # and we can use the source columns available at that join to get the table name - # catch OptimizeError if column is still ambiguous and try to resolve with schema inference below - try: - table_name = self._get_table_name_from_sources( - column_name, self._get_available_source_columns(join_context) - ) - except OptimizeError: - pass - - if not table_name and self._infer_schema: - sources_without_schema = tuple( - source - for source, columns in self._get_all_source_columns().items() - if not columns or "*" in columns - ) - if len(sources_without_schema) == 1: - table_name = sources_without_schema[0] - - if table_name not in self.scope.selected_sources: - return exp.to_identifier(table_name) - - node, _ = self.scope.selected_sources.get(table_name) - - if isinstance(node, exp.Query): - while node and node.alias != table_name: - node = node.parent - - node_alias = node.args.get("alias") - if node_alias: - return exp.to_identifier(node_alias.this) - - return exp.to_identifier(table_name) - - @property - def all_columns(self) -> t.Set[str]: - """All available columns of all sources in this scope""" - if self._all_columns is None: - self._all_columns = { - column - for columns in self._get_all_source_columns().values() - for column in columns - } - return self._all_columns - - def get_source_columns_from_set_op(self, expression: exp.Expression) -> t.List[str]: - if isinstance(expression, exp.Select): - return expression.named_selects - if isinstance(expression, exp.Subquery) and isinstance( - expression.this, exp.SetOperation - ): - # Different types of SET modifiers can be chained together if they're explicitly grouped by nesting - return self.get_source_columns_from_set_op(expression.this) - if not isinstance(expression, exp.SetOperation): - raise OptimizeError(f"Unknown set operation: {expression}") - - set_op = expression - - # BigQuery specific set operations modifiers, e.g INNER UNION ALL BY NAME - on_column_list = set_op.args.get("on") - - if on_column_list: - # The resulting columns are the columns in the ON clause: - # {INNER | LEFT | FULL} UNION ALL BY NAME ON (col1, col2, ...) - columns = [col.name for col in on_column_list] - elif set_op.side or set_op.kind: - side = set_op.side - kind = set_op.kind - - # Visit the children UNIONs (if any) in a post-order traversal - left = self.get_source_columns_from_set_op(set_op.left) - right = self.get_source_columns_from_set_op(set_op.right) - - # We use dict.fromkeys to deduplicate keys and maintain insertion order - if side == "LEFT": - columns = left - elif side == "FULL": - columns = list(dict.fromkeys(left + right)) - elif kind == "INNER": - columns = list(dict.fromkeys(left).keys() & dict.fromkeys(right).keys()) - else: - columns = set_op.named_selects - - return columns - - def get_source_columns( - self, name: str, only_visible: bool = False - ) -> t.Sequence[str]: - """Resolve the source columns for a given source `name`.""" - cache_key = (name, only_visible) - if cache_key not in self._get_source_columns_cache: - if name not in self.scope.sources: - raise OptimizeError(f"Unknown table: {name}") - - source = self.scope.sources[name] - - if isinstance(source, exp.Table): - columns = self.schema.column_names(source, only_visible) - elif isinstance(source, Scope) and isinstance( - source.expression, (exp.Values, exp.Unnest) - ): - columns = source.expression.named_selects - - # in bigquery, unnest structs are automatically scoped as tables, so you can - # directly select a struct field in a query. - # this handles the case where the unnest is statically defined. - if self.dialect.UNNEST_COLUMN_ONLY and isinstance( - source.expression, exp.Unnest - ): - unnest = source.expression - - # if type is not annotated yet, try to get it from the schema - if not unnest.type or unnest.type.is_type( - exp.DataType.Type.UNKNOWN - ): - unnest_expr = seq_get(unnest.expressions, 0) - if isinstance(unnest_expr, exp.Column) and self.scope.parent: - col_type = self._get_unnest_column_type(unnest_expr) - # extract element type if it's an ARRAY - if col_type and col_type.is_type(exp.DataType.Type.ARRAY): - element_types = col_type.expressions - if element_types: - unnest.type = element_types[0].copy() - else: - if col_type: - unnest.type = col_type.copy() - # check if the result type is a STRUCT - extract struct field names - if unnest.is_type(exp.DataType.Type.STRUCT): - for k in unnest.type.expressions: # type: ignore - columns.append(k.name) - elif isinstance(source, Scope) and isinstance( - source.expression, exp.SetOperation - ): - columns = self.get_source_columns_from_set_op(source.expression) - - else: - select = seq_get(source.expression.selects, 0) - - if isinstance(select, exp.QueryTransform): - # https://spark.apache.org/docs/3.5.1/sql-ref-syntax-qry-select-transform.html - schema = select.args.get("schema") - columns = ( - [c.name for c in schema.expressions] - if schema - else ["key", "value"] - ) - else: - columns = source.expression.named_selects - - node, _ = self.scope.selected_sources.get(name) or (None, None) - if isinstance(node, Scope): - column_aliases = node.expression.alias_column_names - elif isinstance(node, exp.Expression): - column_aliases = node.alias_column_names - else: - column_aliases = [] - - if column_aliases: - # If the source's columns are aliased, their aliases shadow the corresponding column names. - # This can be expensive if there are lots of columns, so only do this if column_aliases exist. - columns = [ - alias or name - for (name, alias) in itertools.zip_longest(columns, column_aliases) - ] - - self._get_source_columns_cache[cache_key] = columns - - return self._get_source_columns_cache[cache_key] - - def _get_all_source_columns(self) -> t.Dict[str, t.Sequence[str]]: - if self._source_columns is None: - self._source_columns = { - source_name: self.get_source_columns(source_name) - for source_name, source in itertools.chain( - self.scope.selected_sources.items(), - self.scope.lateral_sources.items(), - ) - } - return self._source_columns - - def _get_table_name_from_sources( - self, - column_name: str, - source_columns: t.Optional[t.Dict[str, t.Sequence[str]]] = None, - ) -> t.Optional[str]: - if not source_columns: - # If not supplied, get all sources to calculate unambiguous columns - if self._unambiguous_columns is None: - self._unambiguous_columns = self._get_unambiguous_columns( - self._get_all_source_columns() - ) - - unambiguous_columns = self._unambiguous_columns - else: - unambiguous_columns = self._get_unambiguous_columns(source_columns) - - return unambiguous_columns.get(column_name) - - def _get_column_join_context(self, column: exp.Column) -> t.Optional[exp.Join]: - """ - Check if a column participating in a join can be qualified based on the source order. - """ - args = self.scope.expression.args - joins = args.get("joins") - - if not joins or args.get("laterals") or args.get("pivots"): - # Feature gap: We currently don't try to disambiguate columns if other sources - # (e.g laterals, pivots) exist alongside joins - return None - - join_ancestor = column.find_ancestor(exp.Join, exp.Select) - - if ( - isinstance(join_ancestor, exp.Join) - and join_ancestor.alias_or_name in self.scope.selected_sources - ): - # Ensure that the found ancestor is a join that contains an actual source, - # e.g in Clickhouse `b` is an array expression in `a ARRAY JOIN b` - return join_ancestor - - return None - - def _get_available_source_columns( - self, join_ancestor: exp.Join - ) -> t.Dict[str, t.Sequence[str]]: - """ - Get the source columns that are available at the point where a column is referenced. - - For columns in JOIN conditions, this only includes tables that have been joined - up to that point. Example: - - ``` - SELECT * FROM t_1 INNER JOIN ... INNER JOIN t_n ON t_1.a = c INNER JOIN t_n+1 ON ... - ``` ^ - | - +----------------------------------+ - | - ⌄ - The unqualified column `c` is not ambiguous if no other sources up until that - join i.e t_1, ..., t_n, contain a column named `c`. - - """ - args = self.scope.expression.args - - # Collect tables in order: FROM clause tables + joined tables up to current join - from_name = args["from_"].alias_or_name - available_sources = {from_name: self.get_source_columns(from_name)} - - for join in args["joins"][: t.cast(int, join_ancestor.index) + 1]: - available_sources[join.alias_or_name] = self.get_source_columns( - join.alias_or_name - ) - - return available_sources - - def _get_unambiguous_columns( - self, source_columns: t.Dict[str, t.Sequence[str]] - ) -> t.Mapping[str, str]: - """ - Find all the unambiguous columns in sources. - - Args: - source_columns: Mapping of names to source columns. - - Returns: - Mapping of column name to source name. - """ - if not source_columns: - return {} - - source_columns_pairs = list(source_columns.items()) - - first_table, first_columns = source_columns_pairs[0] - - if len(source_columns_pairs) == 1: - # Performance optimization - avoid copying first_columns if there is only one table. - return SingleValuedMapping(first_columns, first_table) - - unambiguous_columns = {col: first_table for col in first_columns} - all_columns = set(unambiguous_columns) - - for table, columns in source_columns_pairs[1:]: - unique = set(columns) - ambiguous = all_columns.intersection(unique) - all_columns.update(columns) - - for column in ambiguous: - unambiguous_columns.pop(column, None) - for column in unique.difference(ambiguous): - unambiguous_columns[column] = table - - return unambiguous_columns - - def _get_unnest_column_type(self, column: exp.Column) -> t.Optional[exp.DataType]: - """ - Get the type of a column being unnested, tracing through CTEs/subqueries to find the base table. - - Args: - column: The column expression being unnested. - - Returns: - The DataType of the column, or None if not found. - """ - scope = self.scope.parent - - # if column is qualified, use that table, otherwise disambiguate using the resolver - if column.table: - table_name = column.table - else: - # use the parent scope's resolver to disambiguate the column - parent_resolver = Resolver(scope, self.schema, self._infer_schema) - table_identifier = parent_resolver.get_table(column) - if not table_identifier: - return None - table_name = table_identifier.name - - source = scope.sources.get(table_name) - return self._get_column_type_from_scope(source, column) if source else None - - def _get_column_type_from_scope( - self, source: t.Union[Scope, exp.Table], column: exp.Column - ) -> t.Optional[exp.DataType]: - """ - Get a column's type by tracing through scopes/tables to find the base table. - - Args: - source: The source to search - can be a Scope (to iterate its sources) or a Table. - column: The column to find the type for. - - Returns: - The DataType of the column, or None if not found. - """ - if isinstance(source, exp.Table): - # base table - get the column type from schema - col_type: t.Optional[exp.DataType] = self.schema.get_column_type( - source, column - ) - if col_type and not col_type.is_type(exp.DataType.Type.UNKNOWN): - return col_type - elif isinstance(source, Scope): - # iterate over all sources in the scope - for source_name, nested_source in source.sources.items(): - col_type = self._get_column_type_from_scope(nested_source, column) - if col_type and not col_type.is_type(exp.DataType.Type.UNKNOWN): - return col_type - - return None diff --git a/third_party/bigframes_vendored/sqlglot/optimizer/scope.py b/third_party/bigframes_vendored/sqlglot/optimizer/scope.py deleted file mode 100644 index 4256abc6173..00000000000 --- a/third_party/bigframes_vendored/sqlglot/optimizer/scope.py +++ /dev/null @@ -1,983 +0,0 @@ -# Contains code from https://github.com/tobymao/sqlglot/blob/v28.5.0/sqlglot/optimizer/scope.py - -from __future__ import annotations - -import itertools -import logging -import typing as t -from collections import defaultdict -from enum import Enum, auto - -from bigframes_vendored.sqlglot import exp -from bigframes_vendored.sqlglot.errors import OptimizeError -from bigframes_vendored.sqlglot.helper import ensure_collection, find_new_name, seq_get - -logger = logging.getLogger("sqlglot") - -TRAVERSABLES = (exp.Query, exp.DDL, exp.DML) - - -class ScopeType(Enum): - ROOT = auto() - SUBQUERY = auto() - DERIVED_TABLE = auto() - CTE = auto() - UNION = auto() - UDTF = auto() - - -class Scope: - """ - Selection scope. - - Attributes: - expression (exp.Select|exp.SetOperation): Root expression of this scope - sources (dict[str, exp.Table|Scope]): Mapping of source name to either - a Table expression or another Scope instance. For example: - SELECT * FROM x {"x": Table(this="x")} - SELECT * FROM x AS y {"y": Table(this="x")} - SELECT * FROM (SELECT ...) AS y {"y": Scope(...)} - lateral_sources (dict[str, exp.Table|Scope]): Sources from laterals - For example: - SELECT c FROM x LATERAL VIEW EXPLODE (a) AS c; - The LATERAL VIEW EXPLODE gets x as a source. - cte_sources (dict[str, Scope]): Sources from CTES - outer_columns (list[str]): If this is a derived table or CTE, and the outer query - defines a column list for the alias of this scope, this is that list of columns. - For example: - SELECT * FROM (SELECT ...) AS y(col1, col2) - The inner query would have `["col1", "col2"]` for its `outer_columns` - parent (Scope): Parent scope - scope_type (ScopeType): Type of this scope, relative to it's parent - subquery_scopes (list[Scope]): List of all child scopes for subqueries - cte_scopes (list[Scope]): List of all child scopes for CTEs - derived_table_scopes (list[Scope]): List of all child scopes for derived_tables - udtf_scopes (list[Scope]): List of all child scopes for user defined tabular functions - table_scopes (list[Scope]): derived_table_scopes + udtf_scopes, in the order that they're defined - union_scopes (list[Scope, Scope]): If this Scope is for a Union expression, this will be - a list of the left and right child scopes. - """ - - def __init__( - self, - expression, - sources=None, - outer_columns=None, - parent=None, - scope_type=ScopeType.ROOT, - lateral_sources=None, - cte_sources=None, - can_be_correlated=None, - ): - self.expression = expression - self.sources = sources or {} - self.lateral_sources = lateral_sources or {} - self.cte_sources = cte_sources or {} - self.sources.update(self.lateral_sources) - self.sources.update(self.cte_sources) - self.outer_columns = outer_columns or [] - self.parent = parent - self.scope_type = scope_type - self.subquery_scopes = [] - self.derived_table_scopes = [] - self.table_scopes = [] - self.cte_scopes = [] - self.union_scopes = [] - self.udtf_scopes = [] - self.can_be_correlated = can_be_correlated - self.clear_cache() - - def clear_cache(self): - self._collected = False - self._raw_columns = None - self._table_columns = None - self._stars = None - self._derived_tables = None - self._udtfs = None - self._tables = None - self._ctes = None - self._subqueries = None - self._selected_sources = None - self._columns = None - self._external_columns = None - self._local_columns = None - self._join_hints = None - self._pivots = None - self._references = None - self._semi_anti_join_tables = None - - def branch( - self, - expression, - scope_type, - sources=None, - cte_sources=None, - lateral_sources=None, - **kwargs, - ): - """Branch from the current scope to a new, inner scope""" - return Scope( - expression=expression.unnest(), - sources=sources.copy() if sources else None, - parent=self, - scope_type=scope_type, - cte_sources={**self.cte_sources, **(cte_sources or {})}, - lateral_sources=lateral_sources.copy() if lateral_sources else None, - can_be_correlated=self.can_be_correlated - or scope_type in (ScopeType.SUBQUERY, ScopeType.UDTF), - **kwargs, - ) - - def _collect(self): - self._tables = [] - self._ctes = [] - self._subqueries = [] - self._derived_tables = [] - self._udtfs = [] - self._raw_columns = [] - self._table_columns = [] - self._stars = [] - self._join_hints = [] - self._semi_anti_join_tables = set() - - for node in self.walk(bfs=False): - if node is self.expression: - continue - - if isinstance(node, exp.Dot) and node.is_star: - self._stars.append(node) - elif isinstance(node, exp.Column) and not isinstance( - node, exp.Pseudocolumn - ): - if isinstance(node.this, exp.Star): - self._stars.append(node) - else: - self._raw_columns.append(node) - elif isinstance(node, exp.Table) and not isinstance( - node.parent, exp.JoinHint - ): - parent = node.parent - if isinstance(parent, exp.Join) and parent.is_semi_or_anti_join: - self._semi_anti_join_tables.add(node.alias_or_name) - - self._tables.append(node) - elif isinstance(node, exp.JoinHint): - self._join_hints.append(node) - elif isinstance(node, exp.UDTF): - self._udtfs.append(node) - elif isinstance(node, exp.CTE): - self._ctes.append(node) - elif _is_derived_table(node) and _is_from_or_join(node): - self._derived_tables.append(node) - elif isinstance(node, exp.UNWRAPPED_QUERIES) and not _is_from_or_join(node): - self._subqueries.append(node) - elif isinstance(node, exp.TableColumn): - self._table_columns.append(node) - - self._collected = True - - def _ensure_collected(self): - if not self._collected: - self._collect() - - def walk(self, bfs=True, prune=None): - return walk_in_scope(self.expression, bfs=bfs, prune=None) - - def find(self, *expression_types, bfs=True): - return find_in_scope(self.expression, expression_types, bfs=bfs) - - def find_all(self, *expression_types, bfs=True): - return find_all_in_scope(self.expression, expression_types, bfs=bfs) - - def replace(self, old, new): - """ - Replace `old` with `new`. - - This can be used instead of `exp.Expression.replace` to ensure the `Scope` is kept up-to-date. - - Args: - old (exp.Expression): old node - new (exp.Expression): new node - """ - old.replace(new) - self.clear_cache() - - @property - def tables(self): - """ - List of tables in this scope. - - Returns: - list[exp.Table]: tables - """ - self._ensure_collected() - return self._tables - - @property - def ctes(self): - """ - List of CTEs in this scope. - - Returns: - list[exp.CTE]: ctes - """ - self._ensure_collected() - return self._ctes - - @property - def derived_tables(self): - """ - List of derived tables in this scope. - - For example: - SELECT * FROM (SELECT ...) <- that's a derived table - - Returns: - list[exp.Subquery]: derived tables - """ - self._ensure_collected() - return self._derived_tables - - @property - def udtfs(self): - """ - List of "User Defined Tabular Functions" in this scope. - - Returns: - list[exp.UDTF]: UDTFs - """ - self._ensure_collected() - return self._udtfs - - @property - def subqueries(self): - """ - List of subqueries in this scope. - - For example: - SELECT * FROM x WHERE a IN (SELECT ...) <- that's a subquery - - Returns: - list[exp.Select | exp.SetOperation]: subqueries - """ - self._ensure_collected() - return self._subqueries - - @property - def stars(self) -> t.List[exp.Column | exp.Dot]: - """ - List of star expressions (columns or dots) in this scope. - """ - self._ensure_collected() - return self._stars - - @property - def columns(self): - """ - List of columns in this scope. - - Returns: - list[exp.Column]: Column instances in this scope, plus any - Columns that reference this scope from correlated subqueries. - """ - if self._columns is None: - self._ensure_collected() - columns = self._raw_columns - - external_columns = [ - column - for scope in itertools.chain( - self.subquery_scopes, - self.udtf_scopes, - (dts for dts in self.derived_table_scopes if dts.can_be_correlated), - ) - for column in scope.external_columns - ] - - named_selects = set(self.expression.named_selects) - - self._columns = [] - for column in columns + external_columns: - ancestor = column.find_ancestor( - exp.Select, - exp.Qualify, - exp.Order, - exp.Having, - exp.Hint, - exp.Table, - exp.Star, - exp.Distinct, - ) - if ( - not ancestor - or column.table - or isinstance(ancestor, exp.Select) - or ( - isinstance(ancestor, exp.Table) - and not isinstance(ancestor.this, exp.Func) - ) - or ( - isinstance(ancestor, (exp.Order, exp.Distinct)) - and ( - isinstance(ancestor.parent, (exp.Window, exp.WithinGroup)) - or not isinstance(ancestor.parent, exp.Select) - or column.name not in named_selects - ) - ) - or ( - isinstance(ancestor, exp.Star) - and not column.arg_key == "except_" - ) - ): - self._columns.append(column) - - return self._columns - - @property - def table_columns(self): - if self._table_columns is None: - self._ensure_collected() - - return self._table_columns - - @property - def selected_sources(self): - """ - Mapping of nodes and sources that are actually selected from in this scope. - - That is, all tables in a schema are selectable at any point. But a - table only becomes a selected source if it's included in a FROM or JOIN clause. - - Returns: - dict[str, (exp.Table|exp.Select, exp.Table|Scope)]: selected sources and nodes - """ - if self._selected_sources is None: - result = {} - - for name, node in self.references: - if name in self._semi_anti_join_tables: - # The RHS table of SEMI/ANTI joins shouldn't be collected as a - # selected source - continue - - if name in result: - raise OptimizeError(f"Alias already used: {name}") - if name in self.sources: - result[name] = (node, self.sources[name]) - - self._selected_sources = result - return self._selected_sources - - @property - def references(self) -> t.List[t.Tuple[str, exp.Expression]]: - if self._references is None: - self._references = [] - - for table in self.tables: - self._references.append((table.alias_or_name, table)) - for expression in itertools.chain(self.derived_tables, self.udtfs): - self._references.append( - ( - _get_source_alias(expression), - expression - if expression.args.get("pivots") - else expression.unnest(), - ) - ) - - return self._references - - @property - def external_columns(self): - """ - Columns that appear to reference sources in outer scopes. - - Returns: - list[exp.Column]: Column instances that don't reference sources in the current scope. - """ - if self._external_columns is None: - if isinstance(self.expression, exp.SetOperation): - left, right = self.union_scopes - self._external_columns = left.external_columns + right.external_columns - else: - self._external_columns = [ - c - for c in self.columns - if c.table not in self.sources - and c.table not in self.semi_or_anti_join_tables - ] - - return self._external_columns - - @property - def local_columns(self): - """ - Columns in this scope that are not external. - - Returns: - list[exp.Column]: Column instances that reference sources in the current scope. - """ - if self._local_columns is None: - external_columns = set(self.external_columns) - self._local_columns = [c for c in self.columns if c not in external_columns] - - return self._local_columns - - @property - def unqualified_columns(self): - """ - Unqualified columns in the current scope. - - Returns: - list[exp.Column]: Unqualified columns - """ - return [c for c in self.columns if not c.table] - - @property - def join_hints(self): - """ - Hints that exist in the scope that reference tables - - Returns: - list[exp.JoinHint]: Join hints that are referenced within the scope - """ - if self._join_hints is None: - return [] - return self._join_hints - - @property - def pivots(self): - if not self._pivots: - self._pivots = [ - pivot - for _, node in self.references - for pivot in node.args.get("pivots") or [] - ] - - return self._pivots - - @property - def semi_or_anti_join_tables(self): - return self._semi_anti_join_tables or set() - - def source_columns(self, source_name): - """ - Get all columns in the current scope for a particular source. - - Args: - source_name (str): Name of the source - Returns: - list[exp.Column]: Column instances that reference `source_name` - """ - return [column for column in self.columns if column.table == source_name] - - @property - def is_subquery(self): - """Determine if this scope is a subquery""" - return self.scope_type == ScopeType.SUBQUERY - - @property - def is_derived_table(self): - """Determine if this scope is a derived table""" - return self.scope_type == ScopeType.DERIVED_TABLE - - @property - def is_union(self): - """Determine if this scope is a union""" - return self.scope_type == ScopeType.UNION - - @property - def is_cte(self): - """Determine if this scope is a common table expression""" - return self.scope_type == ScopeType.CTE - - @property - def is_root(self): - """Determine if this is the root scope""" - return self.scope_type == ScopeType.ROOT - - @property - def is_udtf(self): - """Determine if this scope is a UDTF (User Defined Table Function)""" - return self.scope_type == ScopeType.UDTF - - @property - def is_correlated_subquery(self): - """Determine if this scope is a correlated subquery""" - return bool(self.can_be_correlated and self.external_columns) - - def rename_source(self, old_name, new_name): - """Rename a source in this scope""" - old_name = old_name or "" - if old_name in self.sources: - self.sources[new_name] = self.sources.pop(old_name) - - def add_source(self, name, source): - """Add a source to this scope""" - self.sources[name] = source - self.clear_cache() - - def remove_source(self, name): - """Remove a source from this scope""" - self.sources.pop(name, None) - self.clear_cache() - - def __repr__(self): - return f"Scope<{self.expression.sql()}>" - - def traverse(self): - """ - Traverse the scope tree from this node. - - Yields: - Scope: scope instances in depth-first-search post-order - """ - stack = [self] - result = [] - while stack: - scope = stack.pop() - result.append(scope) - stack.extend( - itertools.chain( - scope.cte_scopes, - scope.union_scopes, - scope.table_scopes, - scope.subquery_scopes, - ) - ) - - yield from reversed(result) - - def ref_count(self): - """ - Count the number of times each scope in this tree is referenced. - - Returns: - dict[int, int]: Mapping of Scope instance ID to reference count - """ - scope_ref_count = defaultdict(lambda: 0) - - for scope in self.traverse(): - for _, source in scope.selected_sources.values(): - scope_ref_count[id(source)] += 1 - - for name in scope._semi_anti_join_tables: - # semi/anti join sources are not actually selected but we still need to - # increment their ref count to avoid them being optimized away - if name in scope.sources: - scope_ref_count[id(scope.sources[name])] += 1 - - return scope_ref_count - - -def traverse_scope(expression: exp.Expression) -> t.List[Scope]: - """ - Traverse an expression by its "scopes". - - "Scope" represents the current context of a Select statement. - - This is helpful for optimizing queries, where we need more information than - the expression tree itself. For example, we might care about the source - names within a subquery. Returns a list because a generator could result in - incomplete properties which is confusing. - - Examples: - >>> import sqlglot - >>> expression = sqlglot.parse_one("SELECT a FROM (SELECT a FROM x) AS y") - >>> scopes = traverse_scope(expression) - >>> scopes[0].expression.sql(), list(scopes[0].sources) - ('SELECT a FROM x', ['x']) - >>> scopes[1].expression.sql(), list(scopes[1].sources) - ('SELECT a FROM (SELECT a FROM x) AS y', ['y']) - - Args: - expression: Expression to traverse - - Returns: - A list of the created scope instances - """ - if isinstance(expression, TRAVERSABLES): - return list(_traverse_scope(Scope(expression))) - return [] - - -def build_scope(expression: exp.Expression) -> t.Optional[Scope]: - """ - Build a scope tree. - - Args: - expression: Expression to build the scope tree for. - - Returns: - The root scope - """ - return seq_get(traverse_scope(expression), -1) - - -def _traverse_scope(scope): - expression = scope.expression - - if isinstance(expression, exp.Select): - yield from _traverse_select(scope) - elif isinstance(expression, exp.SetOperation): - yield from _traverse_ctes(scope) - yield from _traverse_union(scope) - return - elif isinstance(expression, exp.Subquery): - if scope.is_root: - yield from _traverse_select(scope) - else: - yield from _traverse_subqueries(scope) - elif isinstance(expression, exp.Table): - yield from _traverse_tables(scope) - elif isinstance(expression, exp.UDTF): - yield from _traverse_udtfs(scope) - elif isinstance(expression, exp.DDL): - if isinstance(expression.expression, exp.Query): - yield from _traverse_ctes(scope) - yield from _traverse_scope( - Scope(expression.expression, cte_sources=scope.cte_sources) - ) - return - elif isinstance(expression, exp.DML): - yield from _traverse_ctes(scope) - for query in find_all_in_scope(expression, exp.Query): - # This check ensures we don't yield the CTE/nested queries twice - if not isinstance(query.parent, (exp.CTE, exp.Subquery)): - yield from _traverse_scope(Scope(query, cte_sources=scope.cte_sources)) - return - else: - logger.warning( - "Cannot traverse scope %s with type '%s'", expression, type(expression) - ) - return - - yield scope - - -def _traverse_select(scope): - yield from _traverse_ctes(scope) - yield from _traverse_tables(scope) - yield from _traverse_subqueries(scope) - - -def _traverse_union(scope): - prev_scope = None - union_scope_stack = [scope] - expression_stack = [scope.expression.right, scope.expression.left] - - while expression_stack: - expression = expression_stack.pop() - union_scope = union_scope_stack[-1] - - new_scope = union_scope.branch( - expression, - outer_columns=union_scope.outer_columns, - scope_type=ScopeType.UNION, - ) - - if isinstance(expression, exp.SetOperation): - yield from _traverse_ctes(new_scope) - - union_scope_stack.append(new_scope) - expression_stack.extend([expression.right, expression.left]) - continue - - for scope in _traverse_scope(new_scope): - yield scope - - if prev_scope: - union_scope_stack.pop() - union_scope.union_scopes = [prev_scope, scope] - prev_scope = union_scope - - yield union_scope - else: - prev_scope = scope - - -def _traverse_ctes(scope): - sources = {} - - for cte in scope.ctes: - cte_name = cte.alias - - # if the scope is a recursive cte, it must be in the form of base_case UNION recursive. - # thus the recursive scope is the first section of the union. - with_ = scope.expression.args.get("with_") - if with_ and with_.recursive: - union = cte.this - - if isinstance(union, exp.SetOperation): - sources[cte_name] = scope.branch(union.this, scope_type=ScopeType.CTE) - - child_scope = None - - for child_scope in _traverse_scope( - scope.branch( - cte.this, - cte_sources=sources, - outer_columns=cte.alias_column_names, - scope_type=ScopeType.CTE, - ) - ): - yield child_scope - - # append the final child_scope yielded - if child_scope: - sources[cte_name] = child_scope - scope.cte_scopes.append(child_scope) - - scope.sources.update(sources) - scope.cte_sources.update(sources) - - -def _is_derived_table(expression: exp.Subquery) -> bool: - """ - We represent (tbl1 JOIN tbl2) as a Subquery, but it's not really a "derived table", - as it doesn't introduce a new scope. If an alias is present, it shadows all names - under the Subquery, so that's one exception to this rule. - """ - return isinstance(expression, exp.Subquery) and bool( - expression.alias or isinstance(expression.this, exp.UNWRAPPED_QUERIES) - ) - - -def _is_from_or_join(expression: exp.Expression) -> bool: - """ - Determine if `expression` is the FROM or JOIN clause of a SELECT statement. - """ - parent = expression.parent - - # Subqueries can be arbitrarily nested - while isinstance(parent, exp.Subquery): - parent = parent.parent - - return isinstance(parent, (exp.From, exp.Join)) - - -def _traverse_tables(scope): - sources = {} - - # Traverse FROMs, JOINs, and LATERALs in the order they are defined - expressions = [] - from_ = scope.expression.args.get("from_") - if from_: - expressions.append(from_.this) - - for join in scope.expression.args.get("joins") or []: - expressions.append(join.this) - - if isinstance(scope.expression, exp.Table): - expressions.append(scope.expression) - - expressions.extend(scope.expression.args.get("laterals") or []) - - for expression in expressions: - if isinstance(expression, exp.Final): - expression = expression.this - if isinstance(expression, exp.Table): - table_name = expression.name - source_name = expression.alias_or_name - - if table_name in scope.sources and not expression.db: - # This is a reference to a parent source (e.g. a CTE), not an actual table, unless - # it is pivoted, because then we get back a new table and hence a new source. - pivots = expression.args.get("pivots") - if pivots: - sources[pivots[0].alias] = expression - else: - sources[source_name] = scope.sources[table_name] - elif source_name in sources: - sources[find_new_name(sources, table_name)] = expression - else: - sources[source_name] = expression - - # Make sure to not include the joins twice - if expression is not scope.expression: - expressions.extend( - join.this for join in expression.args.get("joins") or [] - ) - - continue - - if not isinstance(expression, exp.DerivedTable): - continue - - if isinstance(expression, exp.UDTF): - lateral_sources = sources - scope_type = ScopeType.UDTF - scopes = scope.udtf_scopes - elif _is_derived_table(expression): - lateral_sources = None - scope_type = ScopeType.DERIVED_TABLE - scopes = scope.derived_table_scopes - expressions.extend(join.this for join in expression.args.get("joins") or []) - else: - # Makes sure we check for possible sources in nested table constructs - expressions.append(expression.this) - expressions.extend(join.this for join in expression.args.get("joins") or []) - continue - - child_scope = None - - for child_scope in _traverse_scope( - scope.branch( - expression, - lateral_sources=lateral_sources, - outer_columns=expression.alias_column_names, - scope_type=scope_type, - ) - ): - yield child_scope - - # Tables without aliases will be set as "" - # This shouldn't be a problem once qualify_columns runs, as it adds aliases on everything. - # Until then, this means that only a single, unaliased derived table is allowed (rather, - # the latest one wins. - sources[_get_source_alias(expression)] = child_scope - - # append the final child_scope yielded - if child_scope: - scopes.append(child_scope) - scope.table_scopes.append(child_scope) - - scope.sources.update(sources) - - -def _traverse_subqueries(scope): - for subquery in scope.subqueries: - top = None - for child_scope in _traverse_scope( - scope.branch(subquery, scope_type=ScopeType.SUBQUERY) - ): - yield child_scope - top = child_scope - scope.subquery_scopes.append(top) - - -def _traverse_udtfs(scope): - if isinstance(scope.expression, exp.Unnest): - expressions = scope.expression.expressions - elif isinstance(scope.expression, exp.Lateral): - expressions = [scope.expression.this] - else: - expressions = [] - - sources = {} - for expression in expressions: - if isinstance(expression, exp.Subquery): - top = None - for child_scope in _traverse_scope( - scope.branch( - expression, - scope_type=ScopeType.SUBQUERY, - outer_columns=expression.alias_column_names, - ) - ): - yield child_scope - top = child_scope - sources[_get_source_alias(expression)] = child_scope - - scope.subquery_scopes.append(top) - - scope.sources.update(sources) - - -def walk_in_scope(expression, bfs=True, prune=None): - """ - Returns a generator object which visits all nodes in the syntrax tree, stopping at - nodes that start child scopes. - - Args: - expression (exp.Expression): - bfs (bool): if set to True the BFS traversal order will be applied, - otherwise the DFS traversal will be used instead. - prune ((node, parent, arg_key) -> bool): callable that returns True if - the generator should stop traversing this branch of the tree. - - Yields: - tuple[exp.Expression, Optional[exp.Expression], str]: node, parent, arg key - """ - # We'll use this variable to pass state into the dfs generator. - # Whenever we set it to True, we exclude a subtree from traversal. - crossed_scope_boundary = False - - for node in expression.walk( - bfs=bfs, prune=lambda n: crossed_scope_boundary or (prune and prune(n)) - ): - crossed_scope_boundary = False - - yield node - - if node is expression: - continue - - if ( - isinstance(node, exp.CTE) - or ( - isinstance(node.parent, (exp.From, exp.Join)) - and _is_derived_table(node) - ) - or (isinstance(node.parent, exp.UDTF) and isinstance(node, exp.Query)) - or isinstance(node, exp.UNWRAPPED_QUERIES) - ): - crossed_scope_boundary = True - - if isinstance(node, (exp.Subquery, exp.UDTF)): - # The following args are not actually in the inner scope, so we should visit them - for key in ("joins", "laterals", "pivots"): - for arg in node.args.get(key) or []: - yield from walk_in_scope(arg, bfs=bfs) - - -def find_all_in_scope(expression, expression_types, bfs=True): - """ - Returns a generator object which visits all nodes in this scope and only yields those that - match at least one of the specified expression types. - - This does NOT traverse into subscopes. - - Args: - expression (exp.Expression): - expression_types (tuple[type]|type): the expression type(s) to match. - bfs (bool): True to use breadth-first search, False to use depth-first. - - Yields: - exp.Expression: nodes - """ - for expression in walk_in_scope(expression, bfs=bfs): - if isinstance(expression, tuple(ensure_collection(expression_types))): - yield expression - - -def find_in_scope(expression, expression_types, bfs=True): - """ - Returns the first node in this scope which matches at least one of the specified types. - - This does NOT traverse into subscopes. - - Args: - expression (exp.Expression): - expression_types (tuple[type]|type): the expression type(s) to match. - bfs (bool): True to use breadth-first search, False to use depth-first. - - Returns: - exp.Expression: the node which matches the criteria or None if no node matching - the criteria was found. - """ - return next(find_all_in_scope(expression, expression_types, bfs=bfs), None) - - -def _get_source_alias(expression): - alias_arg = expression.args.get("alias") - alias_name = expression.alias - - if ( - not alias_name - and isinstance(alias_arg, exp.TableAlias) - and len(alias_arg.columns) == 1 - ): - alias_name = alias_arg.columns[0].name - - return alias_name diff --git a/third_party/bigframes_vendored/sqlglot/optimizer/simplify.py b/third_party/bigframes_vendored/sqlglot/optimizer/simplify.py deleted file mode 100644 index 573dc9e67d3..00000000000 --- a/third_party/bigframes_vendored/sqlglot/optimizer/simplify.py +++ /dev/null @@ -1,1796 +0,0 @@ -# Contains code from https://github.com/tobymao/sqlglot/blob/v28.5.0/sqlglot/optimizer/simplify.py - -from __future__ import annotations - -import datetime -import functools -import itertools -import logging -import typing as t -from collections import defaultdict, deque -from functools import reduce, wraps - -import bigframes_vendored.sqlglot -from bigframes_vendored.sqlglot import Dialect, exp -from bigframes_vendored.sqlglot.helper import first, merge_ranges, while_changing -from bigframes_vendored.sqlglot.optimizer.annotate_types import TypeAnnotator -from bigframes_vendored.sqlglot.optimizer.scope import find_all_in_scope, walk_in_scope -from bigframes_vendored.sqlglot.schema import ensure_schema - -if t.TYPE_CHECKING: - from bigframes_vendored.sqlglot.dialects.dialect import DialectType - - DateRange = t.Tuple[datetime.date, datetime.date] - DateTruncBinaryTransform = t.Callable[ - [exp.Expression, datetime.date, str, Dialect, exp.DataType], - t.Optional[exp.Expression], - ] - - -logger = logging.getLogger("sqlglot") - - -# Final means that an expression should not be simplified -FINAL = "final" - -SIMPLIFIABLE = ( - exp.Binary, - exp.Func, - exp.Lambda, - exp.Predicate, - exp.Unary, -) - - -def simplify( - expression: exp.Expression, - constant_propagation: bool = False, - coalesce_simplification: bool = False, - dialect: DialectType = None, -): - """ - Rewrite sqlglot AST to simplify expressions. - - Example: - >>> import sqlglot - >>> expression = sqlglot.parse_one("TRUE AND TRUE") - >>> simplify(expression).sql() - 'TRUE' - - Args: - expression: expression to simplify - constant_propagation: whether the constant propagation rule should be used - coalesce_simplification: whether the simplify coalesce rule should be used. - This rule tries to remove coalesce functions, which can be useful in certain analyses but - can leave the query more verbose. - Returns: - sqlglot.Expression: simplified expression - """ - return Simplifier(dialect=dialect).simplify( - expression, - constant_propagation=constant_propagation, - coalesce_simplification=coalesce_simplification, - ) - - -class UnsupportedUnit(Exception): - pass - - -def catch(*exceptions): - """Decorator that ignores a simplification function if any of `exceptions` are raised""" - - def decorator(func): - def wrapped(expression, *args, **kwargs): - try: - return func(expression, *args, **kwargs) - except exceptions: - return expression - - return wrapped - - return decorator - - -def annotate_types_on_change(func): - @wraps(func) - def _func( - self, expression: exp.Expression, *args, **kwargs - ) -> t.Optional[exp.Expression]: - new_expression = func(self, expression, *args, **kwargs) - - if new_expression is None: - return new_expression - - if self.annotate_new_expressions and expression != new_expression: - self._annotator.clear() - - # We annotate this to ensure new children nodes are also annotated - new_expression = self._annotator.annotate( - expression=new_expression, - annotate_scope=False, - ) - - # Whatever expression the original expression is transformed into needs to preserve - # the original type, otherwise the simplification could result in a different schema - new_expression.type = expression.type - - return new_expression - - return _func - - -def flatten(expression): - """ - A AND (B AND C) -> A AND B AND C - A OR (B OR C) -> A OR B OR C - """ - if isinstance(expression, exp.Connector): - for node in expression.args.values(): - child = node.unnest() - if isinstance(child, expression.__class__): - node.replace(child) - return expression - - -def simplify_parens(expression: exp.Expression, dialect: DialectType) -> exp.Expression: - if not isinstance(expression, exp.Paren): - return expression - - this = expression.this - parent = expression.parent - parent_is_predicate = isinstance(parent, exp.Predicate) - - if isinstance(this, exp.Select): - return expression - - if isinstance(parent, (exp.SubqueryPredicate, exp.Bracket)): - return expression - - if ( - Dialect.get_or_raise(dialect).REQUIRES_PARENTHESIZED_STRUCT_ACCESS - and isinstance(parent, exp.Dot) - and (isinstance(parent.right, (exp.Identifier, exp.Star))) - ): - return expression - - if ( - not isinstance(parent, (exp.Condition, exp.Binary)) - or isinstance(parent, exp.Paren) - or ( - not isinstance(this, exp.Binary) - and not (isinstance(this, (exp.Not, exp.Is)) and parent_is_predicate) - ) - or ( - isinstance(this, exp.Predicate) - and not (parent_is_predicate or isinstance(parent, exp.Neg)) - ) - or (isinstance(this, exp.Add) and isinstance(parent, exp.Add)) - or (isinstance(this, exp.Mul) and isinstance(parent, exp.Mul)) - or (isinstance(this, exp.Mul) and isinstance(parent, (exp.Add, exp.Sub))) - ): - return this - - return expression - - -def propagate_constants(expression, root=True): - """ - Propagate constants for conjunctions in DNF: - - SELECT * FROM t WHERE a = b AND b = 5 becomes - SELECT * FROM t WHERE a = 5 AND b = 5 - - Reference: https://www.sqlite.org/optoverview.html - """ - - if ( - isinstance(expression, exp.And) - and (root or not expression.same_parent) - and bigframes_vendored.sqlglot.optimizer.normalize.normalized( - expression, dnf=True - ) - ): - constant_mapping = {} - for expr in walk_in_scope( - expression, prune=lambda node: isinstance(node, exp.If) - ): - if isinstance(expr, exp.EQ): - l, r = expr.left, expr.right - - # TODO: create a helper that can be used to detect nested literal expressions such - # as CAST(123456 AS BIGINT), since we usually want to treat those as literals too - if isinstance(l, exp.Column) and isinstance(r, exp.Literal): - constant_mapping[l] = (id(l), r) - - if constant_mapping: - for column in find_all_in_scope(expression, exp.Column): - parent = column.parent - column_id, constant = constant_mapping.get(column) or (None, None) - if ( - column_id is not None - and id(column) != column_id - and not ( - isinstance(parent, exp.Is) - and isinstance(parent.expression, exp.Null) - ) - ): - column.replace(constant.copy()) - - return expression - - -def _is_number(expression: exp.Expression) -> bool: - return expression.is_number - - -def _is_interval(expression: exp.Expression) -> bool: - return ( - isinstance(expression, exp.Interval) - and extract_interval(expression) is not None - ) - - -def _is_nonnull_constant(expression: exp.Expression) -> bool: - return isinstance(expression, exp.NONNULL_CONSTANTS) or _is_date_literal(expression) - - -def _is_constant(expression: exp.Expression) -> bool: - return isinstance(expression, exp.CONSTANTS) or _is_date_literal(expression) - - -def _datetrunc_range( - date: datetime.date, unit: str, dialect: Dialect -) -> t.Optional[DateRange]: - """ - Get the date range for a DATE_TRUNC equality comparison: - - Example: - _datetrunc_range(date(2021-01-01), 'year') == (date(2021-01-01), date(2022-01-01)) - Returns: - tuple of [min, max) or None if a value can never be equal to `date` for `unit` - """ - floor = date_floor(date, unit, dialect) - - if date != floor: - # This will always be False, except for NULL values. - return None - - return floor, floor + interval(unit) - - -def _datetrunc_eq_expression( - left: exp.Expression, drange: DateRange, target_type: t.Optional[exp.DataType] -) -> exp.Expression: - """Get the logical expression for a date range""" - return exp.and_( - left >= date_literal(drange[0], target_type), - left < date_literal(drange[1], target_type), - copy=False, - ) - - -def _datetrunc_eq( - left: exp.Expression, - date: datetime.date, - unit: str, - dialect: Dialect, - target_type: t.Optional[exp.DataType], -) -> t.Optional[exp.Expression]: - drange = _datetrunc_range(date, unit, dialect) - if not drange: - return None - - return _datetrunc_eq_expression(left, drange, target_type) - - -def _datetrunc_neq( - left: exp.Expression, - date: datetime.date, - unit: str, - dialect: Dialect, - target_type: t.Optional[exp.DataType], -) -> t.Optional[exp.Expression]: - drange = _datetrunc_range(date, unit, dialect) - if not drange: - return None - - return exp.and_( - left < date_literal(drange[0], target_type), - left >= date_literal(drange[1], target_type), - copy=False, - ) - - -def always_true(expression): - return (isinstance(expression, exp.Boolean) and expression.this) or ( - isinstance(expression, exp.Literal) - and expression.is_number - and not is_zero(expression) - ) - - -def always_false(expression): - return is_false(expression) or is_null(expression) or is_zero(expression) - - -def is_zero(expression): - return isinstance(expression, exp.Literal) and expression.to_py() == 0 - - -def is_complement(a, b): - return isinstance(b, exp.Not) and b.this == a - - -def is_false(a: exp.Expression) -> bool: - return type(a) is exp.Boolean and not a.this - - -def is_null(a: exp.Expression) -> bool: - return type(a) is exp.Null - - -def eval_boolean(expression, a, b): - if isinstance(expression, (exp.EQ, exp.Is)): - return boolean_literal(a == b) - if isinstance(expression, exp.NEQ): - return boolean_literal(a != b) - if isinstance(expression, exp.GT): - return boolean_literal(a > b) - if isinstance(expression, exp.GTE): - return boolean_literal(a >= b) - if isinstance(expression, exp.LT): - return boolean_literal(a < b) - if isinstance(expression, exp.LTE): - return boolean_literal(a <= b) - return None - - -def cast_as_date(value: t.Any) -> t.Optional[datetime.date]: - if isinstance(value, datetime.datetime): - return value.date() - if isinstance(value, datetime.date): - return value - try: - return datetime.datetime.fromisoformat(value).date() - except ValueError: - return None - - -def cast_as_datetime(value: t.Any) -> t.Optional[datetime.datetime]: - if isinstance(value, datetime.datetime): - return value - if isinstance(value, datetime.date): - return datetime.datetime(year=value.year, month=value.month, day=value.day) - try: - return datetime.datetime.fromisoformat(value) - except ValueError: - return None - - -def cast_value( - value: t.Any, to: exp.DataType -) -> t.Optional[t.Union[datetime.date, datetime.date]]: - if not value: - return None - if to.is_type(exp.DataType.Type.DATE): - return cast_as_date(value) - if to.is_type(*exp.DataType.TEMPORAL_TYPES): - return cast_as_datetime(value) - return None - - -def extract_date( - cast: exp.Expression, -) -> t.Optional[t.Union[datetime.date, datetime.date]]: - if isinstance(cast, exp.Cast): - to = cast.to - elif isinstance(cast, exp.TsOrDsToDate) and not cast.args.get("format"): - to = exp.DataType.build(exp.DataType.Type.DATE) - else: - return None - - if isinstance(cast.this, exp.Literal): - value: t.Any = cast.this.name - elif isinstance(cast.this, (exp.Cast, exp.TsOrDsToDate)): - value = extract_date(cast.this) - else: - return None - return cast_value(value, to) - - -def _is_date_literal(expression: exp.Expression) -> bool: - return extract_date(expression) is not None - - -def extract_interval(expression): - try: - n = int(expression.this.to_py()) - unit = expression.text("unit").lower() - return interval(unit, n) - except (UnsupportedUnit, ModuleNotFoundError, ValueError): - return None - - -def extract_type(*expressions): - target_type = None - for expression in expressions: - target_type = ( - expression.to if isinstance(expression, exp.Cast) else expression.type - ) - if target_type: - break - - return target_type - - -def date_literal(date, target_type=None): - if not target_type or not target_type.is_type(*exp.DataType.TEMPORAL_TYPES): - target_type = ( - exp.DataType.Type.DATETIME - if isinstance(date, datetime.datetime) - else exp.DataType.Type.DATE - ) - - return exp.cast(exp.Literal.string(date), target_type) - - -def interval(unit: str, n: int = 1): - from dateutil.relativedelta import relativedelta - - if unit == "year": - return relativedelta(years=1 * n) - if unit == "quarter": - return relativedelta(months=3 * n) - if unit == "month": - return relativedelta(months=1 * n) - if unit == "week": - return relativedelta(weeks=1 * n) - if unit == "day": - return relativedelta(days=1 * n) - if unit == "hour": - return relativedelta(hours=1 * n) - if unit == "minute": - return relativedelta(minutes=1 * n) - if unit == "second": - return relativedelta(seconds=1 * n) - - raise UnsupportedUnit(f"Unsupported unit: {unit}") - - -def date_floor(d: datetime.date, unit: str, dialect: Dialect) -> datetime.date: - if unit == "year": - return d.replace(month=1, day=1) - if unit == "quarter": - if d.month <= 3: - return d.replace(month=1, day=1) - elif d.month <= 6: - return d.replace(month=4, day=1) - elif d.month <= 9: - return d.replace(month=7, day=1) - else: - return d.replace(month=10, day=1) - if unit == "month": - return d.replace(month=d.month, day=1) - if unit == "week": - # Assuming week starts on Monday (0) and ends on Sunday (6) - return d - datetime.timedelta(days=d.weekday() - dialect.WEEK_OFFSET) - if unit == "day": - return d - - raise UnsupportedUnit(f"Unsupported unit: {unit}") - - -def date_ceil(d: datetime.date, unit: str, dialect: Dialect) -> datetime.date: - floor = date_floor(d, unit, dialect) - - if floor == d: - return d - - return floor + interval(unit) - - -def boolean_literal(condition): - return exp.true() if condition else exp.false() - - -class Simplifier: - def __init__( - self, dialect: DialectType = None, annotate_new_expressions: bool = True - ): - self.dialect = Dialect.get_or_raise(dialect) - self.annotate_new_expressions = annotate_new_expressions - - self._annotator: TypeAnnotator = TypeAnnotator( - schema=ensure_schema(None, dialect=self.dialect), overwrite_types=False - ) - - # Value ranges for byte-sized signed/unsigned integers - TINYINT_MIN = -128 - TINYINT_MAX = 127 - UTINYINT_MIN = 0 - UTINYINT_MAX = 255 - - COMPLEMENT_COMPARISONS = { - exp.LT: exp.GTE, - exp.GT: exp.LTE, - exp.LTE: exp.GT, - exp.GTE: exp.LT, - exp.EQ: exp.NEQ, - exp.NEQ: exp.EQ, - } - - COMPLEMENT_SUBQUERY_PREDICATES = { - exp.All: exp.Any, - exp.Any: exp.All, - } - - LT_LTE = (exp.LT, exp.LTE) - GT_GTE = (exp.GT, exp.GTE) - - COMPARISONS = ( - *LT_LTE, - *GT_GTE, - exp.EQ, - exp.NEQ, - exp.Is, - ) - - INVERSE_COMPARISONS: t.Dict[t.Type[exp.Expression], t.Type[exp.Expression]] = { - exp.LT: exp.GT, - exp.GT: exp.LT, - exp.LTE: exp.GTE, - exp.GTE: exp.LTE, - } - - NONDETERMINISTIC = (exp.Rand, exp.Randn) - AND_OR = (exp.And, exp.Or) - - INVERSE_DATE_OPS: t.Dict[t.Type[exp.Expression], t.Type[exp.Expression]] = { - exp.DateAdd: exp.Sub, - exp.DateSub: exp.Add, - exp.DatetimeAdd: exp.Sub, - exp.DatetimeSub: exp.Add, - } - - INVERSE_OPS: t.Dict[t.Type[exp.Expression], t.Type[exp.Expression]] = { - **INVERSE_DATE_OPS, - exp.Add: exp.Sub, - exp.Sub: exp.Add, - } - - NULL_OK = (exp.NullSafeEQ, exp.NullSafeNEQ, exp.PropertyEQ) - - CONCATS = (exp.Concat, exp.DPipe) - - DATETRUNC_BINARY_COMPARISONS: t.Dict[ - t.Type[exp.Expression], DateTruncBinaryTransform - ] = { - exp.LT: lambda ll, dt, u, d, t: ll - < date_literal( - dt if dt == date_floor(dt, u, d) else date_floor(dt, u, d) + interval(u), t - ), - exp.GT: lambda ll, dt, u, d, t: ll - >= date_literal(date_floor(dt, u, d) + interval(u), t), - exp.LTE: lambda ll, dt, u, d, t: ll - < date_literal(date_floor(dt, u, d) + interval(u), t), - exp.GTE: lambda ll, dt, u, d, t: ll >= date_literal(date_ceil(dt, u, d), t), - exp.EQ: _datetrunc_eq, - exp.NEQ: _datetrunc_neq, - } - - DATETRUNC_COMPARISONS = {exp.In, *DATETRUNC_BINARY_COMPARISONS} - DATETRUNCS = (exp.DateTrunc, exp.TimestampTrunc) - - SAFE_CONNECTOR_ELIMINATION_RESULT = (exp.Connector, exp.Boolean) - - # CROSS joins result in an empty table if the right table is empty. - # So we can only simplify certain types of joins to CROSS. - # Or in other words, LEFT JOIN x ON TRUE != CROSS JOIN x - JOINS = { - ("", ""), - ("", "INNER"), - ("RIGHT", ""), - ("RIGHT", "OUTER"), - } - - def simplify( - self, - expression: exp.Expression, - constant_propagation: bool = False, - coalesce_simplification: bool = False, - ): - wheres = [] - joins = [] - - for node in expression.walk( - prune=lambda n: bool(isinstance(n, exp.Condition) or n.meta.get(FINAL)) - ): - if node.meta.get(FINAL): - continue - - # group by expressions cannot be simplified, for example - # select x + 1 + 1 FROM y GROUP BY x + 1 + 1 - # the projection must exactly match the group by key - group = node.args.get("group") - - if group and hasattr(node, "selects"): - groups = set(group.expressions) - group.meta[FINAL] = True - - for s in node.selects: - for n in s.walk(FINAL): - if n in groups: - s.meta[FINAL] = True - break - - having = node.args.get("having") - - if having: - for n in having.walk(): - if n in groups: - having.meta[FINAL] = True - break - - if isinstance(node, exp.Condition): - simplified = while_changing( - node, - lambda e: self._simplify( - e, constant_propagation, coalesce_simplification - ), - ) - - if node is expression: - expression = simplified - elif isinstance(node, exp.Where): - wheres.append(node) - elif isinstance(node, exp.Join): - # snowflake match_conditions have very strict ordering rules - if match := node.args.get("match_condition"): - match.meta[FINAL] = True - - joins.append(node) - - for where in wheres: - if always_true(where.this): - where.pop() - for join in joins: - if ( - always_true(join.args.get("on")) - and not join.args.get("using") - and not join.args.get("method") - and (join.side, join.kind) in self.JOINS - ): - join.args["on"].pop() - join.set("side", None) - join.set("kind", "CROSS") - - return expression - - def _simplify( - self, - expression: exp.Expression, - constant_propagation: bool, - coalesce_simplification: bool, - ): - pre_transformation_stack = [expression] - post_transformation_stack = [] - - while pre_transformation_stack: - original = pre_transformation_stack.pop() - node = original - - if not isinstance(node, SIMPLIFIABLE): - if isinstance(node, exp.Query): - self.simplify(node, constant_propagation, coalesce_simplification) - continue - - parent = node.parent - root = node is expression - - node = self.rewrite_between(node) - node = self.uniq_sort(node, root) - node = self.absorb_and_eliminate(node, root) - node = self.simplify_concat(node) - node = self.simplify_conditionals(node) - - if constant_propagation: - node = propagate_constants(node, root) - - if node is not original: - original.replace(node) - - for n in node.iter_expressions(reverse=True): - if n.meta.get(FINAL): - raise - pre_transformation_stack.extend( - n for n in node.iter_expressions(reverse=True) if not n.meta.get(FINAL) - ) - post_transformation_stack.append((node, parent)) - - while post_transformation_stack: - original, parent = post_transformation_stack.pop() - root = original is expression - - # Resets parent, arg_key, index pointers– this is needed because some of the - # previous transformations mutate the AST, leading to an inconsistent state - for k, v in tuple(original.args.items()): - original.set(k, v) - - # Post-order transformations - node = self.simplify_not(original) - node = flatten(node) - node = self.simplify_connectors(node, root) - node = self.remove_complements(node, root) - - if coalesce_simplification: - node = self.simplify_coalesce(node) - node.parent = parent - - node = self.simplify_literals(node, root) - node = self.simplify_equality(node) - node = simplify_parens(node, dialect=self.dialect) - node = self.simplify_datetrunc(node) - node = self.sort_comparison(node) - node = self.simplify_startswith(node) - - if node is not original: - original.replace(node) - - return node - - @annotate_types_on_change - def rewrite_between(self, expression: exp.Expression) -> exp.Expression: - """Rewrite x between y and z to x >= y AND x <= z. - - This is done because comparison simplification is only done on lt/lte/gt/gte. - """ - if isinstance(expression, exp.Between): - negate = isinstance(expression.parent, exp.Not) - - expression = exp.and_( - exp.GTE(this=expression.this.copy(), expression=expression.args["low"]), - exp.LTE( - this=expression.this.copy(), expression=expression.args["high"] - ), - copy=False, - ) - - if negate: - expression = exp.paren(expression, copy=False) - - return expression - - @annotate_types_on_change - def simplify_not(self, expression: exp.Expression) -> exp.Expression: - """ - Demorgan's Law - NOT (x OR y) -> NOT x AND NOT y - NOT (x AND y) -> NOT x OR NOT y - """ - if isinstance(expression, exp.Not): - this = expression.this - if is_null(this): - return exp.and_(exp.null(), exp.true(), copy=False) - if this.__class__ in self.COMPLEMENT_COMPARISONS: - right = this.expression - complement_subquery_predicate = self.COMPLEMENT_SUBQUERY_PREDICATES.get( - right.__class__ - ) - if complement_subquery_predicate: - right = complement_subquery_predicate(this=right.this) - - return self.COMPLEMENT_COMPARISONS[this.__class__]( - this=this.this, expression=right - ) - if isinstance(this, exp.Paren): - condition = this.unnest() - if isinstance(condition, exp.And): - return exp.paren( - exp.or_( - exp.not_(condition.left, copy=False), - exp.not_(condition.right, copy=False), - copy=False, - ), - copy=False, - ) - if isinstance(condition, exp.Or): - return exp.paren( - exp.and_( - exp.not_(condition.left, copy=False), - exp.not_(condition.right, copy=False), - copy=False, - ), - copy=False, - ) - if is_null(condition): - return exp.and_(exp.null(), exp.true(), copy=False) - if always_true(this): - return exp.false() - if is_false(this): - return exp.true() - if ( - isinstance(this, exp.Not) - and self.dialect.SAFE_TO_ELIMINATE_DOUBLE_NEGATION - ): - inner = this.this - if inner.is_type(exp.DataType.Type.BOOLEAN): - # double negation - # NOT NOT x -> x, if x is BOOLEAN type - return inner - return expression - - @annotate_types_on_change - def simplify_connectors(self, expression, root=True): - def _simplify_connectors(expression, left, right): - if isinstance(expression, exp.And): - if is_false(left) or is_false(right): - return exp.false() - if is_zero(left) or is_zero(right): - return exp.false() - if ( - (is_null(left) and is_null(right)) - or (is_null(left) and always_true(right)) - or (always_true(left) and is_null(right)) - ): - return exp.null() - if always_true(left) and always_true(right): - return exp.true() - if always_true(left): - return right - if always_true(right): - return left - return self._simplify_comparison(expression, left, right) - elif isinstance(expression, exp.Or): - if always_true(left) or always_true(right): - return exp.true() - if ( - (is_null(left) and is_null(right)) - or (is_null(left) and always_false(right)) - or (always_false(left) and is_null(right)) - ): - return exp.null() - if is_false(left): - return right - if is_false(right): - return left - return self._simplify_comparison(expression, left, right, or_=True) - - if isinstance(expression, exp.Connector): - original_parent = expression.parent - expression = self._flat_simplify(expression, _simplify_connectors, root) - - # If we reduced a connector to, e.g., a column (t1 AND ... AND tn -> Tk), then we need - # to ensure that the resulting type is boolean. We know this is true only for connectors, - # boolean values and columns that are essentially operands to a connector: - # - # A AND (((B))) - # ~ this is safe to keep because it will eventually be part of another connector - if not isinstance( - expression, self.SAFE_CONNECTOR_ELIMINATION_RESULT - ) and not expression.is_type(exp.DataType.Type.BOOLEAN): - while True: - if isinstance(original_parent, exp.Connector): - break - if not isinstance(original_parent, exp.Paren): - expression = expression.and_(exp.true(), copy=False) - break - - original_parent = original_parent.parent - - return expression - - @annotate_types_on_change - def _simplify_comparison(self, expression, left, right, or_=False): - if isinstance(left, self.COMPARISONS) and isinstance(right, self.COMPARISONS): - ll, lr = left.args.values() - rl, rr = right.args.values() - - largs = {ll, lr} - rargs = {rl, rr} - - matching = largs & rargs - columns = { - m - for m in matching - if not _is_constant(m) and not m.find(*self.NONDETERMINISTIC) - } - - if matching and columns: - try: - l0 = first(largs - columns) - r = first(rargs - columns) - except StopIteration: - return expression - - if l0.is_number and r.is_number: - l0 = l0.to_py() - r = r.to_py() - elif l0.is_string and r.is_string: - l0 = l0.name - r = r.name - else: - l0 = extract_date(l0) - if not l0: - return None - r = extract_date(r) - if not r: - return None - # python won't compare date and datetime, but many engines will upcast - l0, r = cast_as_datetime(l0), cast_as_datetime(r) - - for (a, av), (b, bv) in itertools.permutations( - ((left, l0), (right, r)) - ): - if isinstance(a, self.LT_LTE) and isinstance(b, self.LT_LTE): - return left if (av > bv if or_ else av <= bv) else right - if isinstance(a, self.GT_GTE) and isinstance(b, self.GT_GTE): - return left if (av < bv if or_ else av >= bv) else right - - # we can't ever shortcut to true because the column could be null - if not or_: - if isinstance(a, exp.LT) and isinstance(b, self.GT_GTE): - if av <= bv: - return exp.false() - elif isinstance(a, exp.GT) and isinstance(b, self.LT_LTE): - if av >= bv: - return exp.false() - elif isinstance(a, exp.EQ): - if isinstance(b, exp.LT): - return exp.false() if av >= bv else a - if isinstance(b, exp.LTE): - return exp.false() if av > bv else a - if isinstance(b, exp.GT): - return exp.false() if av <= bv else a - if isinstance(b, exp.GTE): - return exp.false() if av < bv else a - if isinstance(b, exp.NEQ): - return exp.false() if av == bv else a - return None - - @annotate_types_on_change - def remove_complements(self, expression, root=True): - """ - Removing complements. - - A AND NOT A -> FALSE (only for non-NULL A) - A OR NOT A -> TRUE (only for non-NULL A) - """ - if isinstance(expression, self.AND_OR) and (root or not expression.same_parent): - ops = set(expression.flatten()) - for op in ops: - if isinstance(op, exp.Not) and op.this in ops: - if expression.meta.get("nonnull") is True: - return ( - exp.false() - if isinstance(expression, exp.And) - else exp.true() - ) - - return expression - - @annotate_types_on_change - def uniq_sort(self, expression, root=True): - """ - Uniq and sort a connector. - - C AND A AND B AND B -> A AND B AND C - """ - if isinstance(expression, exp.Connector) and ( - root or not expression.same_parent - ): - flattened = tuple(expression.flatten()) - - if isinstance(expression, exp.Xor): - result_func = exp.xor - # Do not deduplicate XOR as A XOR A != A if A == True - deduped = None - arr = tuple((gen(e), e) for e in flattened) - else: - result_func = exp.and_ if isinstance(expression, exp.And) else exp.or_ - deduped = {gen(e): e for e in flattened} - arr = tuple(deduped.items()) - - # check if the operands are already sorted, if not sort them - # A AND C AND B -> A AND B AND C - for i, (sql, e) in enumerate(arr[1:]): - if sql < arr[i][0]: - expression = result_func(*(e for _, e in sorted(arr)), copy=False) - break - else: - # we didn't have to sort but maybe we need to dedup - if deduped and len(deduped) < len(flattened): - unique_operand = flattened[0] - if len(deduped) == 1: - expression = unique_operand.and_(exp.true(), copy=False) - else: - expression = result_func(*deduped.values(), copy=False) - - return expression - - @annotate_types_on_change - def absorb_and_eliminate(self, expression, root=True): - """ - absorption: - A AND (A OR B) -> A - A OR (A AND B) -> A - A AND (NOT A OR B) -> A AND B - A OR (NOT A AND B) -> A OR B - elimination: - (A AND B) OR (A AND NOT B) -> A - (A OR B) AND (A OR NOT B) -> A - """ - if isinstance(expression, self.AND_OR) and (root or not expression.same_parent): - kind = exp.Or if isinstance(expression, exp.And) else exp.And - - ops = tuple(expression.flatten()) - - # Initialize lookup tables: - # Set of all operands, used to find complements for absorption. - op_set = set() - # Sub-operands, used to find subsets for absorption. - subops = defaultdict(list) - # Pairs of complements, used for elimination. - pairs = defaultdict(list) - - # Populate the lookup tables - for op in ops: - op_set.add(op) - - if not isinstance(op, kind): - # In cases like: A OR (A AND B) - # Subop will be: ^ - subops[op].append({op}) - continue - - # In cases like: (A AND B) OR (A AND B AND C) - # Subops will be: ^ ^ - subset = set(op.flatten()) - for i in subset: - subops[i].append(subset) - - a, b = op.unnest_operands() - if isinstance(a, exp.Not): - pairs[frozenset((a.this, b))].append((op, b)) - if isinstance(b, exp.Not): - pairs[frozenset((a, b.this))].append((op, a)) - - for op in ops: - if not isinstance(op, kind): - continue - - a, b = op.unnest_operands() - - # Absorb - if isinstance(a, exp.Not) and a.this in op_set: - a.replace(exp.true() if kind == exp.And else exp.false()) - continue - if isinstance(b, exp.Not) and b.this in op_set: - b.replace(exp.true() if kind == exp.And else exp.false()) - continue - superset = set(op.flatten()) - if any( - any(subset < superset for subset in subops[i]) for i in superset - ): - op.replace(exp.false() if kind == exp.And else exp.true()) - continue - - # Eliminate - for other, complement in pairs[frozenset((a, b))]: - op.replace(complement) - other.replace(complement) - - return expression - - @annotate_types_on_change - @catch(ModuleNotFoundError, UnsupportedUnit) - def simplify_equality(self, expression: exp.Expression) -> exp.Expression: - """ - Use the subtraction and addition properties of equality to simplify expressions: - - x + 1 = 3 becomes x = 2 - - There are two binary operations in the above expression: + and = - Here's how we reference all the operands in the code below: - - l r - x + 1 = 3 - a b - """ - if isinstance(expression, self.COMPARISONS): - ll, r = expression.left, expression.right - - if ll.__class__ not in self.INVERSE_OPS: - return expression - - if r.is_number: - a_predicate = _is_number - b_predicate = _is_number - elif _is_date_literal(r): - a_predicate = _is_date_literal - b_predicate = _is_interval - else: - return expression - - if ll.__class__ in self.INVERSE_DATE_OPS: - ll = t.cast(exp.IntervalOp, ll) - a = ll.this - b = ll.interval() - else: - ll = t.cast(exp.Binary, ll) - a, b = ll.left, ll.right - - if not a_predicate(a) and b_predicate(b): - pass - elif not a_predicate(b) and b_predicate(a): - a, b = b, a - else: - return expression - - return expression.__class__( - this=a, expression=self.INVERSE_OPS[ll.__class__](this=r, expression=b) - ) - return expression - - @annotate_types_on_change - def simplify_literals(self, expression, root=True): - if isinstance(expression, exp.Binary) and not isinstance( - expression, exp.Connector - ): - return self._flat_simplify(expression, self._simplify_binary, root) - - if isinstance(expression, exp.Neg) and isinstance(expression.this, exp.Neg): - return expression.this.this - - if type(expression) in self.INVERSE_DATE_OPS: - return ( - self._simplify_binary( - expression, expression.this, expression.interval() - ) - or expression - ) - - return expression - - def _simplify_integer_cast(self, expr: exp.Expression) -> exp.Expression: - if isinstance(expr, exp.Cast) and isinstance(expr.this, exp.Cast): - this = self._simplify_integer_cast(expr.this) - else: - this = expr.this - - if isinstance(expr, exp.Cast) and this.is_int: - num = this.to_py() - - # Remove the (up)cast from small (byte-sized) integers in predicates which is side-effect free. Downcasts on any - # integer type might cause overflow, thus the cast cannot be eliminated and the behavior is - # engine-dependent - if ( - self.TINYINT_MIN <= num <= self.TINYINT_MAX - and expr.to.this in exp.DataType.SIGNED_INTEGER_TYPES - ) or ( - self.UTINYINT_MIN <= num <= self.UTINYINT_MAX - and expr.to.this in exp.DataType.UNSIGNED_INTEGER_TYPES - ): - return this - - return expr - - def _simplify_binary(self, expression, a, b): - if isinstance(expression, self.COMPARISONS): - a = self._simplify_integer_cast(a) - b = self._simplify_integer_cast(b) - - if isinstance(expression, exp.Is): - if isinstance(b, exp.Not): - c = b.this - not_ = True - else: - c = b - not_ = False - - if is_null(c): - if isinstance(a, exp.Literal): - return exp.true() if not_ else exp.false() - if is_null(a): - return exp.false() if not_ else exp.true() - elif isinstance(expression, self.NULL_OK): - return None - elif (is_null(a) or is_null(b)) and isinstance(expression.parent, exp.If): - return exp.null() - - if a.is_number and b.is_number: - num_a = a.to_py() - num_b = b.to_py() - - if isinstance(expression, exp.Add): - return exp.Literal.number(num_a + num_b) - if isinstance(expression, exp.Mul): - return exp.Literal.number(num_a * num_b) - - # We only simplify Sub, Div if a and b have the same parent because they're not associative - if isinstance(expression, exp.Sub): - return ( - exp.Literal.number(num_a - num_b) if a.parent is b.parent else None - ) - if isinstance(expression, exp.Div): - # engines have differing int div behavior so intdiv is not safe - if ( - isinstance(num_a, int) and isinstance(num_b, int) - ) or a.parent is not b.parent: - return None - return exp.Literal.number(num_a / num_b) - - boolean = eval_boolean(expression, num_a, num_b) - - if boolean: - return boolean - elif a.is_string and b.is_string: - boolean = eval_boolean(expression, a.this, b.this) - - if boolean: - return boolean - elif _is_date_literal(a) and isinstance(b, exp.Interval): - date, b = extract_date(a), extract_interval(b) - if date and b: - if isinstance(expression, (exp.Add, exp.DateAdd, exp.DatetimeAdd)): - return date_literal(date + b, extract_type(a)) - if isinstance(expression, (exp.Sub, exp.DateSub, exp.DatetimeSub)): - return date_literal(date - b, extract_type(a)) - elif isinstance(a, exp.Interval) and _is_date_literal(b): - a, date = extract_interval(a), extract_date(b) - # you cannot subtract a date from an interval - if a and b and isinstance(expression, exp.Add): - return date_literal(a + date, extract_type(b)) - elif _is_date_literal(a) and _is_date_literal(b): - if isinstance(expression, exp.Predicate): - a, b = extract_date(a), extract_date(b) - boolean = eval_boolean(expression, a, b) - if boolean: - return boolean - - return None - - @annotate_types_on_change - def simplify_coalesce(self, expression: exp.Expression) -> exp.Expression: - # COALESCE(x) -> x - if ( - isinstance(expression, exp.Coalesce) - and (not expression.expressions or _is_nonnull_constant(expression.this)) - # COALESCE is also used as a Spark partitioning hint - and not isinstance(expression.parent, exp.Hint) - ): - return expression.this - - if self.dialect.COALESCE_COMPARISON_NON_STANDARD: - return expression - - if not isinstance(expression, self.COMPARISONS): - return expression - - if isinstance(expression.left, exp.Coalesce): - coalesce = expression.left - other = expression.right - elif isinstance(expression.right, exp.Coalesce): - coalesce = expression.right - other = expression.left - else: - return expression - - # This transformation is valid for non-constants, - # but it really only does anything if they are both constants. - if not _is_constant(other): - return expression - - # Find the first constant arg - for arg_index, arg in enumerate(coalesce.expressions): - if _is_constant(arg): - break - else: - return expression - - coalesce.set("expressions", coalesce.expressions[:arg_index]) - - # Remove the COALESCE function. This is an optimization, skipping a simplify iteration, - # since we already remove COALESCE at the top of this function. - coalesce = coalesce if coalesce.expressions else coalesce.this - - # This expression is more complex than when we started, but it will get simplified further - return exp.paren( - exp.or_( - exp.and_( - coalesce.is_(exp.null()).not_(copy=False), - expression.copy(), - copy=False, - ), - exp.and_( - coalesce.is_(exp.null()), - type(expression)(this=arg.copy(), expression=other.copy()), - copy=False, - ), - copy=False, - ), - copy=False, - ) - - @annotate_types_on_change - def simplify_concat(self, expression): - """Reduces all groups that contain string literals by concatenating them.""" - if not isinstance(expression, self.CONCATS) or ( - # We can't reduce a CONCAT_WS call if we don't statically know the separator - isinstance(expression, exp.ConcatWs) - and not expression.expressions[0].is_string - ): - return expression - - if isinstance(expression, exp.ConcatWs): - sep_expr, *expressions = expression.expressions - sep = sep_expr.name - concat_type = exp.ConcatWs - args = {} - else: - expressions = expression.expressions - sep = "" - concat_type = exp.Concat - args = { - "safe": expression.args.get("safe"), - "coalesce": expression.args.get("coalesce"), - } - - new_args = [] - for is_string_group, group in itertools.groupby( - expressions or expression.flatten(), lambda e: e.is_string - ): - if is_string_group: - new_args.append( - exp.Literal.string(sep.join(string.name for string in group)) - ) - else: - new_args.extend(group) - - if len(new_args) == 1 and new_args[0].is_string: - return new_args[0] - - if concat_type is exp.ConcatWs: - new_args = [sep_expr] + new_args - elif isinstance(expression, exp.DPipe): - return reduce(lambda x, y: exp.DPipe(this=x, expression=y), new_args) - - return concat_type(expressions=new_args, **args) - - @annotate_types_on_change - def simplify_conditionals(self, expression): - """Simplifies expressions like IF, CASE if their condition is statically known.""" - if isinstance(expression, exp.Case): - this = expression.this - for case in expression.args["ifs"]: - cond = case.this - if this: - # Convert CASE x WHEN matching_value ... to CASE WHEN x = matching_value ... - cond = cond.replace(this.pop().eq(cond)) - - if always_true(cond): - return case.args["true"] - - if always_false(cond): - case.pop() - if not expression.args["ifs"]: - return expression.args.get("default") or exp.null() - elif isinstance(expression, exp.If) and not isinstance( - expression.parent, exp.Case - ): - if always_true(expression.this): - return expression.args["true"] - if always_false(expression.this): - return expression.args.get("false") or exp.null() - - return expression - - @annotate_types_on_change - def simplify_startswith(self, expression: exp.Expression) -> exp.Expression: - """ - Reduces a prefix check to either TRUE or FALSE if both the string and the - prefix are statically known. - - Example: - >>> from bigframes_vendored.sqlglot import parse_one - >>> Simplifier().simplify_startswith(parse_one("STARTSWITH('foo', 'f')")).sql() - 'TRUE' - """ - if ( - isinstance(expression, exp.StartsWith) - and expression.this.is_string - and expression.expression.is_string - ): - return exp.convert(expression.name.startswith(expression.expression.name)) - - return expression - - def _is_datetrunc_predicate( - self, left: exp.Expression, right: exp.Expression - ) -> bool: - return isinstance(left, self.DATETRUNCS) and _is_date_literal(right) - - @annotate_types_on_change - @catch(ModuleNotFoundError, UnsupportedUnit) - def simplify_datetrunc(self, expression: exp.Expression) -> exp.Expression: - """Simplify expressions like `DATE_TRUNC('year', x) >= CAST('2021-01-01' AS DATE)`""" - comparison = expression.__class__ - - if isinstance(expression, self.DATETRUNCS): - this = expression.this - trunc_type = extract_type(this) - date = extract_date(this) - if date and expression.unit: - return date_literal( - date_floor(date, expression.unit.name.lower(), self.dialect), - trunc_type, - ) - elif comparison not in self.DATETRUNC_COMPARISONS: - return expression - - if isinstance(expression, exp.Binary): - ll, r = expression.left, expression.right - - if not self._is_datetrunc_predicate(ll, r): - return expression - - ll = t.cast(exp.DateTrunc, ll) - trunc_arg = ll.this - unit = ll.unit.name.lower() - date = extract_date(r) - - if not date: - return expression - - return ( - self.DATETRUNC_BINARY_COMPARISONS[comparison]( - trunc_arg, date, unit, self.dialect, extract_type(r) - ) - or expression - ) - - if isinstance(expression, exp.In): - ll = expression.this - rs = expression.expressions - - if rs and all(self._is_datetrunc_predicate(ll, r) for r in rs): - ll = t.cast(exp.DateTrunc, ll) - unit = ll.unit.name.lower() - - ranges = [] - for r in rs: - date = extract_date(r) - if not date: - return expression - drange = _datetrunc_range(date, unit, self.dialect) - if drange: - ranges.append(drange) - - if not ranges: - return expression - - ranges = merge_ranges(ranges) - target_type = extract_type(*rs) - - return exp.or_( - *[ - _datetrunc_eq_expression(ll, drange, target_type) - for drange in ranges - ], - copy=False, - ) - - return expression - - @annotate_types_on_change - def sort_comparison(self, expression: exp.Expression) -> exp.Expression: - if expression.__class__ in self.COMPLEMENT_COMPARISONS: - l, r = expression.this, expression.expression - l_column = isinstance(l, exp.Column) - r_column = isinstance(r, exp.Column) - l_const = _is_constant(l) - r_const = _is_constant(r) - - if ( - (l_column and not r_column) - or (r_const and not l_const) - or isinstance(r, exp.SubqueryPredicate) - ): - return expression - if ( - (r_column and not l_column) - or (l_const and not r_const) - or (gen(l) > gen(r)) - ): - return self.INVERSE_COMPARISONS.get( - expression.__class__, expression.__class__ - )(this=r, expression=l) - return expression - - def _flat_simplify(self, expression, simplifier, root=True): - if root or not expression.same_parent: - operands = [] - queue = deque(expression.flatten(unnest=False)) - size = len(queue) - - while queue: - a = queue.popleft() - - for b in queue: - result = simplifier(expression, a, b) - - if result and result is not expression: - queue.remove(b) - queue.appendleft(result) - break - else: - operands.append(a) - - if len(operands) < size: - return functools.reduce( - lambda a, b: expression.__class__(this=a, expression=b), operands - ) - return expression - - -def gen(expression: t.Any, comments: bool = False) -> str: - """Simple pseudo sql generator for quickly generating sortable and uniq strings. - - Sorting and deduping sql is a necessary step for optimization. Calling the actual - generator is expensive so we have a bare minimum sql generator here. - - Args: - expression: the expression to convert into a SQL string. - comments: whether to include the expression's comments. - """ - return Gen().gen(expression, comments=comments) - - -class Gen: - def __init__(self): - self.stack = [] - self.sqls = [] - - def gen(self, expression: exp.Expression, comments: bool = False) -> str: - self.stack = [expression] - self.sqls.clear() - - while self.stack: - node = self.stack.pop() - - if isinstance(node, exp.Expression): - if comments and node.comments: - self.stack.append(f" /*{','.join(node.comments)}*/") - - exp_handler_name = f"{node.key}_sql" - - if hasattr(self, exp_handler_name): - getattr(self, exp_handler_name)(node) - elif isinstance(node, exp.Func): - self._function(node) - else: - key = node.key.upper() - self.stack.append(f"{key} " if self._args(node) else key) - elif type(node) is list: - for n in reversed(node): - if n is not None: - self.stack.extend((n, ",")) - if node: - self.stack.pop() - else: - if node is not None: - self.sqls.append(str(node)) - - return "".join(self.sqls) - - def add_sql(self, e: exp.Add) -> None: - self._binary(e, " + ") - - def alias_sql(self, e: exp.Alias) -> None: - self.stack.extend( - ( - e.args.get("alias"), - " AS ", - e.args.get("this"), - ) - ) - - def and_sql(self, e: exp.And) -> None: - self._binary(e, " AND ") - - def anonymous_sql(self, e: exp.Anonymous) -> None: - this = e.this - if isinstance(this, str): - name = this.upper() - elif isinstance(this, exp.Identifier): - name = this.this - name = f'"{name}"' if this.quoted else name.upper() - else: - raise ValueError( - f"Anonymous.this expects a str or an Identifier, got '{this.__class__.__name__}'." - ) - - self.stack.extend( - ( - ")", - e.expressions, - "(", - name, - ) - ) - - def between_sql(self, e: exp.Between) -> None: - self.stack.extend( - ( - e.args.get("high"), - " AND ", - e.args.get("low"), - " BETWEEN ", - e.this, - ) - ) - - def boolean_sql(self, e: exp.Boolean) -> None: - self.stack.append("TRUE" if e.this else "FALSE") - - def bracket_sql(self, e: exp.Bracket) -> None: - self.stack.extend( - ( - "]", - e.expressions, - "[", - e.this, - ) - ) - - def column_sql(self, e: exp.Column) -> None: - for p in reversed(e.parts): - self.stack.extend((p, ".")) - self.stack.pop() - - def datatype_sql(self, e: exp.DataType) -> None: - self._args(e, 1) - self.stack.append(f"{e.this.name} ") - - def div_sql(self, e: exp.Div) -> None: - self._binary(e, " / ") - - def dot_sql(self, e: exp.Dot) -> None: - self._binary(e, ".") - - def eq_sql(self, e: exp.EQ) -> None: - self._binary(e, " = ") - - def from_sql(self, e: exp.From) -> None: - self.stack.extend((e.this, "FROM ")) - - def gt_sql(self, e: exp.GT) -> None: - self._binary(e, " > ") - - def gte_sql(self, e: exp.GTE) -> None: - self._binary(e, " >= ") - - def identifier_sql(self, e: exp.Identifier) -> None: - self.stack.append(f'"{e.this}"' if e.quoted else e.this) - - def ilike_sql(self, e: exp.ILike) -> None: - self._binary(e, " ILIKE ") - - def in_sql(self, e: exp.In) -> None: - self.stack.append(")") - self._args(e, 1) - self.stack.extend( - ( - "(", - " IN ", - e.this, - ) - ) - - def intdiv_sql(self, e: exp.IntDiv) -> None: - self._binary(e, " DIV ") - - def is_sql(self, e: exp.Is) -> None: - self._binary(e, " IS ") - - def like_sql(self, e: exp.Like) -> None: - self._binary(e, " Like ") - - def literal_sql(self, e: exp.Literal) -> None: - self.stack.append(f"'{e.this}'" if e.is_string else e.this) - - def lt_sql(self, e: exp.LT) -> None: - self._binary(e, " < ") - - def lte_sql(self, e: exp.LTE) -> None: - self._binary(e, " <= ") - - def mod_sql(self, e: exp.Mod) -> None: - self._binary(e, " % ") - - def mul_sql(self, e: exp.Mul) -> None: - self._binary(e, " * ") - - def neg_sql(self, e: exp.Neg) -> None: - self._unary(e, "-") - - def neq_sql(self, e: exp.NEQ) -> None: - self._binary(e, " <> ") - - def not_sql(self, e: exp.Not) -> None: - self._unary(e, "NOT ") - - def null_sql(self, e: exp.Null) -> None: - self.stack.append("NULL") - - def or_sql(self, e: exp.Or) -> None: - self._binary(e, " OR ") - - def paren_sql(self, e: exp.Paren) -> None: - self.stack.extend( - ( - ")", - e.this, - "(", - ) - ) - - def sub_sql(self, e: exp.Sub) -> None: - self._binary(e, " - ") - - def subquery_sql(self, e: exp.Subquery) -> None: - self._args(e, 2) - alias = e.args.get("alias") - if alias: - self.stack.append(alias) - self.stack.extend((")", e.this, "(")) - - def table_sql(self, e: exp.Table) -> None: - self._args(e, 4) - alias = e.args.get("alias") - if alias: - self.stack.append(alias) - for p in reversed(e.parts): - self.stack.extend((p, ".")) - self.stack.pop() - - def tablealias_sql(self, e: exp.TableAlias) -> None: - columns = e.columns - - if columns: - self.stack.extend((")", columns, "(")) - - self.stack.extend((e.this, " AS ")) - - def var_sql(self, e: exp.Var) -> None: - self.stack.append(e.this) - - def _binary(self, e: exp.Binary, op: str) -> None: - self.stack.extend((e.expression, op, e.this)) - - def _unary(self, e: exp.Unary, op: str) -> None: - self.stack.extend((e.this, op)) - - def _function(self, e: exp.Func) -> None: - self.stack.extend( - ( - ")", - list(e.args.values()), - "(", - e.sql_name(), - ) - ) - - def _args(self, node: exp.Expression, arg_index: int = 0) -> bool: - kvs = [] - arg_types = list(node.arg_types)[arg_index:] if arg_index else node.arg_types - - for k in arg_types: - v = node.args.get(k) - - if v is not None: - kvs.append([f":{k}", v]) - if kvs: - self.stack.append(kvs) - return True - return False diff --git a/third_party/bigframes_vendored/sqlglot/optimizer/unnest_subqueries.py b/third_party/bigframes_vendored/sqlglot/optimizer/unnest_subqueries.py deleted file mode 100644 index 0e3431bfa9b..00000000000 --- a/third_party/bigframes_vendored/sqlglot/optimizer/unnest_subqueries.py +++ /dev/null @@ -1,331 +0,0 @@ -# Contains code from https://github.com/tobymao/sqlglot/blob/v28.5.0/sqlglot/optimizer/unnest_subqueries.py - -from bigframes_vendored.sqlglot import exp -from bigframes_vendored.sqlglot.helper import name_sequence -from bigframes_vendored.sqlglot.optimizer.scope import ( - ScopeType, - find_in_scope, - traverse_scope, -) - - -def unnest_subqueries(expression): - """ - Rewrite sqlglot AST to convert some predicates with subqueries into joins. - - Convert scalar subqueries into cross joins. - Convert correlated or vectorized subqueries into a group by so it is not a many to many left join. - - Example: - >>> import sqlglot - >>> expression = sqlglot.parse_one("SELECT * FROM x AS x WHERE (SELECT y.a AS a FROM y AS y WHERE x.a = y.a) = 1 ") - >>> unnest_subqueries(expression).sql() - 'SELECT * FROM x AS x LEFT JOIN (SELECT y.a AS a FROM y AS y WHERE TRUE GROUP BY y.a) AS _u_0 ON x.a = _u_0.a WHERE _u_0.a = 1' - - Args: - expression (sqlglot.Expression): expression to unnest - Returns: - sqlglot.Expression: unnested expression - """ - next_alias_name = name_sequence("_u_") - - for scope in traverse_scope(expression): - select = scope.expression - parent = select.parent_select - if not parent: - continue - if scope.external_columns: - decorrelate(select, parent, scope.external_columns, next_alias_name) - elif scope.scope_type == ScopeType.SUBQUERY: - unnest(select, parent, next_alias_name) - - return expression - - -def unnest(select, parent_select, next_alias_name): - if len(select.selects) > 1: - return - - predicate = select.find_ancestor(exp.Condition) - if ( - not predicate - or parent_select is not predicate.parent_select - or not parent_select.args.get("from_") - ): - return - - if isinstance(select, exp.SetOperation): - select = exp.select(*select.selects).from_(select.subquery(next_alias_name())) - - alias = next_alias_name() - clause = predicate.find_ancestor(exp.Having, exp.Where, exp.Join) - - # This subquery returns a scalar and can just be converted to a cross join - if not isinstance(predicate, (exp.In, exp.Any)): - column = exp.column(select.selects[0].alias_or_name, alias) - - clause_parent_select = clause.parent_select if clause else None - - if ( - isinstance(clause, exp.Having) and clause_parent_select is parent_select - ) or ( - (not clause or clause_parent_select is not parent_select) - and ( - parent_select.args.get("group") - or any( - find_in_scope(select, exp.AggFunc) - for select in parent_select.selects - ) - ) - ): - column = exp.Max(this=column) - elif not isinstance(select.parent, exp.Subquery): - return - - join_type = "CROSS" - on_clause = None - if isinstance(predicate, exp.Exists): - # If a subquery returns no rows, cross-joining against it incorrectly eliminates all rows - # from the parent query. Therefore, we use a LEFT JOIN that always matches (ON TRUE), then - # check for non-NULL column values to determine whether the subquery contained rows. - column = column.is_(exp.null()).not_() - join_type = "LEFT" - on_clause = exp.true() - - _replace(select.parent, column) - parent_select.join( - select, on=on_clause, join_type=join_type, join_alias=alias, copy=False - ) - return - - if select.find(exp.Limit, exp.Offset): - return - - if isinstance(predicate, exp.Any): - predicate = predicate.find_ancestor(exp.EQ) - - if not predicate or parent_select is not predicate.parent_select: - return - - column = _other_operand(predicate) - value = select.selects[0] - - join_key = exp.column(value.alias, alias) - join_key_not_null = join_key.is_(exp.null()).not_() - - if isinstance(clause, exp.Join): - _replace(predicate, exp.true()) - parent_select.where(join_key_not_null, copy=False) - else: - _replace(predicate, join_key_not_null) - - group = select.args.get("group") - - if group: - if {value.this} != set(group.expressions): - select = ( - exp.select(exp.alias_(exp.column(value.alias, "_q"), value.alias)) - .from_(select.subquery("_q", copy=False), copy=False) - .group_by(exp.column(value.alias, "_q"), copy=False) - ) - elif not find_in_scope(value.this, exp.AggFunc): - select = select.group_by(value.this, copy=False) - - parent_select.join( - select, - on=column.eq(join_key), - join_type="LEFT", - join_alias=alias, - copy=False, - ) - - -def decorrelate(select, parent_select, external_columns, next_alias_name): - where = select.args.get("where") - - if not where or where.find(exp.Or) or select.find(exp.Limit, exp.Offset): - return - - table_alias = next_alias_name() - keys = [] - - # for all external columns in the where statement, find the relevant predicate - # keys to convert it into a join - for column in external_columns: - if column.find_ancestor(exp.Where) is not where: - return - - predicate = column.find_ancestor(exp.Predicate) - - if not predicate or predicate.find_ancestor(exp.Where) is not where: - return - - if isinstance(predicate, exp.Binary): - key = ( - predicate.right - if any(node is column for node in predicate.left.walk()) - else predicate.left - ) - else: - return - - keys.append((key, column, predicate)) - - if not any(isinstance(predicate, exp.EQ) for *_, predicate in keys): - return - - is_subquery_projection = any( - node is select.parent - for node in map(lambda s: s.unalias(), parent_select.selects) - if isinstance(node, exp.Subquery) - ) - - value = select.selects[0] - key_aliases = {} - group_by = [] - - for key, _, predicate in keys: - # if we filter on the value of the subquery, it needs to be unique - if key == value.this: - key_aliases[key] = value.alias - group_by.append(key) - else: - if key not in key_aliases: - key_aliases[key] = next_alias_name() - # all predicates that are equalities must also be in the unique - # so that we don't do a many to many join - if isinstance(predicate, exp.EQ) and key not in group_by: - group_by.append(key) - - parent_predicate = select.find_ancestor(exp.Predicate) - - # if the value of the subquery is not an agg or a key, we need to collect it into an array - # so that it can be grouped. For subquery projections, we use a MAX aggregation instead. - agg_func = exp.Max if is_subquery_projection else exp.ArrayAgg - if not value.find(exp.AggFunc) and value.this not in group_by: - select.select( - exp.alias_(agg_func(this=value.this), value.alias, quoted=False), - append=False, - copy=False, - ) - - # exists queries should not have any selects as it only checks if there are any rows - # all selects will be added by the optimizer and only used for join keys - if isinstance(parent_predicate, exp.Exists): - select.set("expressions", []) - - for key, alias in key_aliases.items(): - if key in group_by: - # add all keys to the projections of the subquery - # so that we can use it as a join key - if isinstance(parent_predicate, exp.Exists) or key != value.this: - select.select(f"{key} AS {alias}", copy=False) - else: - select.select( - exp.alias_(agg_func(this=key.copy()), alias, quoted=False), copy=False - ) - - alias = exp.column(value.alias, table_alias) - other = _other_operand(parent_predicate) - op_type = type(parent_predicate.parent) if parent_predicate else None - - if isinstance(parent_predicate, exp.Exists): - alias = exp.column(list(key_aliases.values())[0], table_alias) - parent_predicate = _replace(parent_predicate, f"NOT {alias} IS NULL") - elif isinstance(parent_predicate, exp.All): - assert issubclass(op_type, exp.Binary) - predicate = op_type(this=other, expression=exp.column("_x")) - parent_predicate = _replace( - parent_predicate.parent, f"ARRAY_ALL({alias}, _x -> {predicate})" - ) - elif isinstance(parent_predicate, exp.Any): - assert issubclass(op_type, exp.Binary) - if value.this in group_by: - predicate = op_type(this=other, expression=alias) - parent_predicate = _replace(parent_predicate.parent, predicate) - else: - predicate = op_type(this=other, expression=exp.column("_x")) - parent_predicate = _replace( - parent_predicate, f"ARRAY_ANY({alias}, _x -> {predicate})" - ) - elif isinstance(parent_predicate, exp.In): - if value.this in group_by: - parent_predicate = _replace(parent_predicate, f"{other} = {alias}") - else: - parent_predicate = _replace( - parent_predicate, - f"ARRAY_ANY({alias}, _x -> _x = {parent_predicate.this})", - ) - else: - if is_subquery_projection and select.parent.alias: - alias = exp.alias_(alias, select.parent.alias) - - # COUNT always returns 0 on empty datasets, so we need take that into consideration here - # by transforming all counts into 0 and using that as the coalesced value - if value.find(exp.Count): - - def remove_aggs(node): - if isinstance(node, exp.Count): - return exp.Literal.number(0) - elif isinstance(node, exp.AggFunc): - return exp.null() - return node - - alias = exp.Coalesce( - this=alias, expressions=[value.this.transform(remove_aggs)] - ) - - select.parent.replace(alias) - - for key, column, predicate in keys: - predicate.replace(exp.true()) - nested = exp.column(key_aliases[key], table_alias) - - if is_subquery_projection: - key.replace(nested) - if not isinstance(predicate, exp.EQ): - parent_select.where(predicate, copy=False) - continue - - if key in group_by: - key.replace(nested) - elif isinstance(predicate, exp.EQ): - parent_predicate = _replace( - parent_predicate, - f"({parent_predicate} AND ARRAY_CONTAINS({nested}, {column}))", - ) - else: - key.replace(exp.to_identifier("_x")) - parent_predicate = _replace( - parent_predicate, - f"({parent_predicate} AND ARRAY_ANY({nested}, _x -> {predicate}))", - ) - - parent_select.join( - select.group_by(*group_by, copy=False), - on=[predicate for *_, predicate in keys if isinstance(predicate, exp.EQ)], - join_type="LEFT", - join_alias=table_alias, - copy=False, - ) - - -def _replace(expression, condition): - return expression.replace(exp.condition(condition)) - - -def _other_operand(expression): - if isinstance(expression, exp.In): - return expression.this - - if isinstance(expression, (exp.Any, exp.All)): - return _other_operand(expression.parent) - - if isinstance(expression, exp.Binary): - return ( - expression.right - if isinstance(expression.left, (exp.Subquery, exp.Any, exp.Exists, exp.All)) - else expression.left - ) - - return None diff --git a/third_party/bigframes_vendored/sqlglot/parser.py b/third_party/bigframes_vendored/sqlglot/parser.py deleted file mode 100644 index 706649f43fb..00000000000 --- a/third_party/bigframes_vendored/sqlglot/parser.py +++ /dev/null @@ -1,9719 +0,0 @@ -# Contains code from https://github.com/tobymao/sqlglot/blob/v28.5.0/sqlglot/parser.py - -from __future__ import annotations - -import itertools -import logging -import re -import typing as t -from collections import defaultdict - -from bigframes_vendored.sqlglot import exp -from bigframes_vendored.sqlglot.errors import ( - ErrorLevel, - ParseError, - TokenError, - concat_messages, - highlight_sql, - merge_errors, -) -from bigframes_vendored.sqlglot.helper import apply_index_offset, ensure_list, seq_get -from bigframes_vendored.sqlglot.time import format_time -from bigframes_vendored.sqlglot.tokens import Token, Tokenizer, TokenType -from bigframes_vendored.sqlglot.trie import TrieResult, in_trie, new_trie - -if t.TYPE_CHECKING: - from bigframes_vendored.sqlglot._typing import E, Lit - from bigframes_vendored.sqlglot.dialects.dialect import Dialect, DialectType - - T = t.TypeVar("T") - TCeilFloor = t.TypeVar("TCeilFloor", exp.Ceil, exp.Floor) - -logger = logging.getLogger("sqlglot") - -OPTIONS_TYPE = t.Dict[str, t.Sequence[t.Union[t.Sequence[str], str]]] - -# Used to detect alphabetical characters and +/- in timestamp literals -TIME_ZONE_RE: t.Pattern[str] = re.compile(r":.*?[a-zA-Z\+\-]") - - -def build_var_map(args: t.List) -> exp.StarMap | exp.VarMap: - if len(args) == 1 and args[0].is_star: - return exp.StarMap(this=args[0]) - - keys = [] - values = [] - for i in range(0, len(args), 2): - keys.append(args[i]) - values.append(args[i + 1]) - - return exp.VarMap( - keys=exp.array(*keys, copy=False), values=exp.array(*values, copy=False) - ) - - -def build_like(args: t.List) -> exp.Escape | exp.Like: - like = exp.Like(this=seq_get(args, 1), expression=seq_get(args, 0)) - return exp.Escape(this=like, expression=seq_get(args, 2)) if len(args) > 2 else like - - -def binary_range_parser( - expr_type: t.Type[exp.Expression], reverse_args: bool = False -) -> t.Callable[[Parser, t.Optional[exp.Expression]], t.Optional[exp.Expression]]: - def _parse_binary_range( - self: Parser, this: t.Optional[exp.Expression] - ) -> t.Optional[exp.Expression]: - expression = self._parse_bitwise() - if reverse_args: - this, expression = expression, this - return self._parse_escape( - self.expression(expr_type, this=this, expression=expression) - ) - - return _parse_binary_range - - -def build_logarithm(args: t.List, dialect: Dialect) -> exp.Func: - # Default argument order is base, expression - this = seq_get(args, 0) - expression = seq_get(args, 1) - - if expression: - if not dialect.LOG_BASE_FIRST: - this, expression = expression, this - return exp.Log(this=this, expression=expression) - - return (exp.Ln if dialect.parser_class.LOG_DEFAULTS_TO_LN else exp.Log)(this=this) - - -def build_hex(args: t.List, dialect: Dialect) -> exp.Hex | exp.LowerHex: - arg = seq_get(args, 0) - return exp.LowerHex(this=arg) if dialect.HEX_LOWERCASE else exp.Hex(this=arg) - - -def build_lower(args: t.List) -> exp.Lower | exp.Hex: - # LOWER(HEX(..)) can be simplified to LowerHex to simplify its transpilation - arg = seq_get(args, 0) - return ( - exp.LowerHex(this=arg.this) if isinstance(arg, exp.Hex) else exp.Lower(this=arg) - ) - - -def build_upper(args: t.List) -> exp.Upper | exp.Hex: - # UPPER(HEX(..)) can be simplified to Hex to simplify its transpilation - arg = seq_get(args, 0) - return exp.Hex(this=arg.this) if isinstance(arg, exp.Hex) else exp.Upper(this=arg) - - -def build_extract_json_with_path( - expr_type: t.Type[E], -) -> t.Callable[[t.List, Dialect], E]: - def _builder(args: t.List, dialect: Dialect) -> E: - expression = expr_type( - this=seq_get(args, 0), expression=dialect.to_json_path(seq_get(args, 1)) - ) - if len(args) > 2 and expr_type is exp.JSONExtract: - expression.set("expressions", args[2:]) - if expr_type is exp.JSONExtractScalar: - expression.set("scalar_only", dialect.JSON_EXTRACT_SCALAR_SCALAR_ONLY) - - return expression - - return _builder - - -def build_mod(args: t.List) -> exp.Mod: - this = seq_get(args, 0) - expression = seq_get(args, 1) - - # Wrap the operands if they are binary nodes, e.g. MOD(a + 1, 7) -> (a + 1) % 7 - this = exp.Paren(this=this) if isinstance(this, exp.Binary) else this - expression = ( - exp.Paren(this=expression) if isinstance(expression, exp.Binary) else expression - ) - - return exp.Mod(this=this, expression=expression) - - -def build_pad(args: t.List, is_left: bool = True): - return exp.Pad( - this=seq_get(args, 0), - expression=seq_get(args, 1), - fill_pattern=seq_get(args, 2), - is_left=is_left, - ) - - -def build_array_constructor( - exp_class: t.Type[E], args: t.List, bracket_kind: TokenType, dialect: Dialect -) -> exp.Expression: - array_exp = exp_class(expressions=args) - - if exp_class == exp.Array and dialect.HAS_DISTINCT_ARRAY_CONSTRUCTORS: - array_exp.set("bracket_notation", bracket_kind == TokenType.L_BRACKET) - - return array_exp - - -def build_convert_timezone( - args: t.List, default_source_tz: t.Optional[str] = None -) -> t.Union[exp.ConvertTimezone, exp.Anonymous]: - if len(args) == 2: - source_tz = exp.Literal.string(default_source_tz) if default_source_tz else None - return exp.ConvertTimezone( - source_tz=source_tz, target_tz=seq_get(args, 0), timestamp=seq_get(args, 1) - ) - - return exp.ConvertTimezone.from_arg_list(args) - - -def build_trim(args: t.List, is_left: bool = True): - return exp.Trim( - this=seq_get(args, 0), - expression=seq_get(args, 1), - position="LEADING" if is_left else "TRAILING", - ) - - -def build_coalesce( - args: t.List, is_nvl: t.Optional[bool] = None, is_null: t.Optional[bool] = None -) -> exp.Coalesce: - return exp.Coalesce( - this=seq_get(args, 0), expressions=args[1:], is_nvl=is_nvl, is_null=is_null - ) - - -def build_locate_strposition(args: t.List): - return exp.StrPosition( - this=seq_get(args, 1), - substr=seq_get(args, 0), - position=seq_get(args, 2), - ) - - -class _Parser(type): - def __new__(cls, clsname, bases, attrs): - klass = super().__new__(cls, clsname, bases, attrs) - - klass.SHOW_TRIE = new_trie(key.split(" ") for key in klass.SHOW_PARSERS) - klass.SET_TRIE = new_trie(key.split(" ") for key in klass.SET_PARSERS) - - return klass - - -class Parser(metaclass=_Parser): - """ - Parser consumes a list of tokens produced by the Tokenizer and produces a parsed syntax tree. - - Args: - error_level: The desired error level. - Default: ErrorLevel.IMMEDIATE - error_message_context: The amount of context to capture from a query string when displaying - the error message (in number of characters). - Default: 100 - max_errors: Maximum number of error messages to include in a raised ParseError. - This is only relevant if error_level is ErrorLevel.RAISE. - Default: 3 - """ - - FUNCTIONS: t.Dict[str, t.Callable] = { - **{name: func.from_arg_list for name, func in exp.FUNCTION_BY_NAME.items()}, - **dict.fromkeys(("COALESCE", "IFNULL", "NVL"), build_coalesce), - "ARRAY": lambda args, dialect: exp.Array(expressions=args), - "ARRAYAGG": lambda args, dialect: exp.ArrayAgg( - this=seq_get(args, 0), - nulls_excluded=dialect.ARRAY_AGG_INCLUDES_NULLS is None or None, - ), - "ARRAY_AGG": lambda args, dialect: exp.ArrayAgg( - this=seq_get(args, 0), - nulls_excluded=dialect.ARRAY_AGG_INCLUDES_NULLS is None or None, - ), - "CHAR": lambda args: exp.Chr(expressions=args), - "CHR": lambda args: exp.Chr(expressions=args), - "COUNT": lambda args: exp.Count( - this=seq_get(args, 0), expressions=args[1:], big_int=True - ), - "CONCAT": lambda args, dialect: exp.Concat( - expressions=args, - safe=not dialect.STRICT_STRING_CONCAT, - coalesce=dialect.CONCAT_COALESCE, - ), - "CONCAT_WS": lambda args, dialect: exp.ConcatWs( - expressions=args, - safe=not dialect.STRICT_STRING_CONCAT, - coalesce=dialect.CONCAT_COALESCE, - ), - "CONVERT_TIMEZONE": build_convert_timezone, - "DATE_TO_DATE_STR": lambda args: exp.Cast( - this=seq_get(args, 0), - to=exp.DataType(this=exp.DataType.Type.TEXT), - ), - "GENERATE_DATE_ARRAY": lambda args: exp.GenerateDateArray( - start=seq_get(args, 0), - end=seq_get(args, 1), - step=seq_get(args, 2) - or exp.Interval(this=exp.Literal.string(1), unit=exp.var("DAY")), - ), - "GENERATE_UUID": lambda args, dialect: exp.Uuid( - is_string=dialect.UUID_IS_STRING_TYPE or None - ), - "GLOB": lambda args: exp.Glob( - this=seq_get(args, 1), expression=seq_get(args, 0) - ), - "GREATEST": lambda args, dialect: exp.Greatest( - this=seq_get(args, 0), - expressions=args[1:], - ignore_nulls=dialect.LEAST_GREATEST_IGNORES_NULLS, - ), - "LEAST": lambda args, dialect: exp.Least( - this=seq_get(args, 0), - expressions=args[1:], - ignore_nulls=dialect.LEAST_GREATEST_IGNORES_NULLS, - ), - "HEX": build_hex, - "JSON_EXTRACT": build_extract_json_with_path(exp.JSONExtract), - "JSON_EXTRACT_SCALAR": build_extract_json_with_path(exp.JSONExtractScalar), - "JSON_EXTRACT_PATH_TEXT": build_extract_json_with_path(exp.JSONExtractScalar), - "LIKE": build_like, - "LOG": build_logarithm, - "LOG2": lambda args: exp.Log( - this=exp.Literal.number(2), expression=seq_get(args, 0) - ), - "LOG10": lambda args: exp.Log( - this=exp.Literal.number(10), expression=seq_get(args, 0) - ), - "LOWER": build_lower, - "LPAD": lambda args: build_pad(args), - "LEFTPAD": lambda args: build_pad(args), - "LTRIM": lambda args: build_trim(args), - "MOD": build_mod, - "RIGHTPAD": lambda args: build_pad(args, is_left=False), - "RPAD": lambda args: build_pad(args, is_left=False), - "RTRIM": lambda args: build_trim(args, is_left=False), - "SCOPE_RESOLUTION": lambda args: ( - exp.ScopeResolution(expression=seq_get(args, 0)) - if len(args) != 2 - else exp.ScopeResolution(this=seq_get(args, 0), expression=seq_get(args, 1)) - ), - "STRPOS": exp.StrPosition.from_arg_list, - "CHARINDEX": lambda args: build_locate_strposition(args), - "INSTR": exp.StrPosition.from_arg_list, - "LOCATE": lambda args: build_locate_strposition(args), - "TIME_TO_TIME_STR": lambda args: exp.Cast( - this=seq_get(args, 0), - to=exp.DataType(this=exp.DataType.Type.TEXT), - ), - "TO_HEX": build_hex, - "TS_OR_DS_TO_DATE_STR": lambda args: exp.Substring( - this=exp.Cast( - this=seq_get(args, 0), - to=exp.DataType(this=exp.DataType.Type.TEXT), - ), - start=exp.Literal.number(1), - length=exp.Literal.number(10), - ), - "UNNEST": lambda args: exp.Unnest(expressions=ensure_list(seq_get(args, 0))), - "UPPER": build_upper, - "UUID": lambda args, dialect: exp.Uuid( - is_string=dialect.UUID_IS_STRING_TYPE or None - ), - "VAR_MAP": build_var_map, - } - - NO_PAREN_FUNCTIONS = { - TokenType.CURRENT_DATE: exp.CurrentDate, - TokenType.CURRENT_DATETIME: exp.CurrentDate, - TokenType.CURRENT_TIME: exp.CurrentTime, - TokenType.CURRENT_TIMESTAMP: exp.CurrentTimestamp, - TokenType.CURRENT_USER: exp.CurrentUser, - TokenType.LOCALTIME: exp.Localtime, - TokenType.LOCALTIMESTAMP: exp.Localtimestamp, - TokenType.CURRENT_ROLE: exp.CurrentRole, - } - - STRUCT_TYPE_TOKENS = { - TokenType.FILE, - TokenType.NESTED, - TokenType.OBJECT, - TokenType.STRUCT, - TokenType.UNION, - } - - NESTED_TYPE_TOKENS = { - TokenType.ARRAY, - TokenType.LIST, - TokenType.LOWCARDINALITY, - TokenType.MAP, - TokenType.NULLABLE, - TokenType.RANGE, - *STRUCT_TYPE_TOKENS, - } - - ENUM_TYPE_TOKENS = { - TokenType.DYNAMIC, - TokenType.ENUM, - TokenType.ENUM8, - TokenType.ENUM16, - } - - AGGREGATE_TYPE_TOKENS = { - TokenType.AGGREGATEFUNCTION, - TokenType.SIMPLEAGGREGATEFUNCTION, - } - - TYPE_TOKENS = { - TokenType.BIT, - TokenType.BOOLEAN, - TokenType.TINYINT, - TokenType.UTINYINT, - TokenType.SMALLINT, - TokenType.USMALLINT, - TokenType.INT, - TokenType.UINT, - TokenType.BIGINT, - TokenType.UBIGINT, - TokenType.BIGNUM, - TokenType.INT128, - TokenType.UINT128, - TokenType.INT256, - TokenType.UINT256, - TokenType.MEDIUMINT, - TokenType.UMEDIUMINT, - TokenType.FIXEDSTRING, - TokenType.FLOAT, - TokenType.DOUBLE, - TokenType.UDOUBLE, - TokenType.CHAR, - TokenType.NCHAR, - TokenType.VARCHAR, - TokenType.NVARCHAR, - TokenType.BPCHAR, - TokenType.TEXT, - TokenType.MEDIUMTEXT, - TokenType.LONGTEXT, - TokenType.BLOB, - TokenType.MEDIUMBLOB, - TokenType.LONGBLOB, - TokenType.BINARY, - TokenType.VARBINARY, - TokenType.JSON, - TokenType.JSONB, - TokenType.INTERVAL, - TokenType.TINYBLOB, - TokenType.TINYTEXT, - TokenType.TIME, - TokenType.TIMETZ, - TokenType.TIME_NS, - TokenType.TIMESTAMP, - TokenType.TIMESTAMP_S, - TokenType.TIMESTAMP_MS, - TokenType.TIMESTAMP_NS, - TokenType.TIMESTAMPTZ, - TokenType.TIMESTAMPLTZ, - TokenType.TIMESTAMPNTZ, - TokenType.DATETIME, - TokenType.DATETIME2, - TokenType.DATETIME64, - TokenType.SMALLDATETIME, - TokenType.DATE, - TokenType.DATE32, - TokenType.INT4RANGE, - TokenType.INT4MULTIRANGE, - TokenType.INT8RANGE, - TokenType.INT8MULTIRANGE, - TokenType.NUMRANGE, - TokenType.NUMMULTIRANGE, - TokenType.TSRANGE, - TokenType.TSMULTIRANGE, - TokenType.TSTZRANGE, - TokenType.TSTZMULTIRANGE, - TokenType.DATERANGE, - TokenType.DATEMULTIRANGE, - TokenType.DECIMAL, - TokenType.DECIMAL32, - TokenType.DECIMAL64, - TokenType.DECIMAL128, - TokenType.DECIMAL256, - TokenType.DECFLOAT, - TokenType.UDECIMAL, - TokenType.BIGDECIMAL, - TokenType.UUID, - TokenType.GEOGRAPHY, - TokenType.GEOGRAPHYPOINT, - TokenType.GEOMETRY, - TokenType.POINT, - TokenType.RING, - TokenType.LINESTRING, - TokenType.MULTILINESTRING, - TokenType.POLYGON, - TokenType.MULTIPOLYGON, - TokenType.HLLSKETCH, - TokenType.HSTORE, - TokenType.PSEUDO_TYPE, - TokenType.SUPER, - TokenType.SERIAL, - TokenType.SMALLSERIAL, - TokenType.BIGSERIAL, - TokenType.XML, - TokenType.YEAR, - TokenType.USERDEFINED, - TokenType.MONEY, - TokenType.SMALLMONEY, - TokenType.ROWVERSION, - TokenType.IMAGE, - TokenType.VARIANT, - TokenType.VECTOR, - TokenType.VOID, - TokenType.OBJECT, - TokenType.OBJECT_IDENTIFIER, - TokenType.INET, - TokenType.IPADDRESS, - TokenType.IPPREFIX, - TokenType.IPV4, - TokenType.IPV6, - TokenType.UNKNOWN, - TokenType.NOTHING, - TokenType.NULL, - TokenType.NAME, - TokenType.TDIGEST, - TokenType.DYNAMIC, - *ENUM_TYPE_TOKENS, - *NESTED_TYPE_TOKENS, - *AGGREGATE_TYPE_TOKENS, - } - - SIGNED_TO_UNSIGNED_TYPE_TOKEN = { - TokenType.BIGINT: TokenType.UBIGINT, - TokenType.INT: TokenType.UINT, - TokenType.MEDIUMINT: TokenType.UMEDIUMINT, - TokenType.SMALLINT: TokenType.USMALLINT, - TokenType.TINYINT: TokenType.UTINYINT, - TokenType.DECIMAL: TokenType.UDECIMAL, - TokenType.DOUBLE: TokenType.UDOUBLE, - } - - SUBQUERY_PREDICATES = { - TokenType.ANY: exp.Any, - TokenType.ALL: exp.All, - TokenType.EXISTS: exp.Exists, - TokenType.SOME: exp.Any, - } - - RESERVED_TOKENS = { - *Tokenizer.SINGLE_TOKENS.values(), - TokenType.SELECT, - } - {TokenType.IDENTIFIER} - - DB_CREATABLES = { - TokenType.DATABASE, - TokenType.DICTIONARY, - TokenType.FILE_FORMAT, - TokenType.MODEL, - TokenType.NAMESPACE, - TokenType.SCHEMA, - TokenType.SEMANTIC_VIEW, - TokenType.SEQUENCE, - TokenType.SINK, - TokenType.SOURCE, - TokenType.STAGE, - TokenType.STORAGE_INTEGRATION, - TokenType.STREAMLIT, - TokenType.TABLE, - TokenType.TAG, - TokenType.VIEW, - TokenType.WAREHOUSE, - } - - CREATABLES = { - TokenType.COLUMN, - TokenType.CONSTRAINT, - TokenType.FOREIGN_KEY, - TokenType.FUNCTION, - TokenType.INDEX, - TokenType.PROCEDURE, - *DB_CREATABLES, - } - - ALTERABLES = { - TokenType.INDEX, - TokenType.TABLE, - TokenType.VIEW, - TokenType.SESSION, - } - - # Tokens that can represent identifiers - ID_VAR_TOKENS = { - TokenType.ALL, - TokenType.ANALYZE, - TokenType.ATTACH, - TokenType.VAR, - TokenType.ANTI, - TokenType.APPLY, - TokenType.ASC, - TokenType.ASOF, - TokenType.AUTO_INCREMENT, - TokenType.BEGIN, - TokenType.BPCHAR, - TokenType.CACHE, - TokenType.CASE, - TokenType.COLLATE, - TokenType.COMMAND, - TokenType.COMMENT, - TokenType.COMMIT, - TokenType.CONSTRAINT, - TokenType.COPY, - TokenType.CUBE, - TokenType.CURRENT_SCHEMA, - TokenType.DEFAULT, - TokenType.DELETE, - TokenType.DESC, - TokenType.DESCRIBE, - TokenType.DETACH, - TokenType.DICTIONARY, - TokenType.DIV, - TokenType.END, - TokenType.EXECUTE, - TokenType.EXPORT, - TokenType.ESCAPE, - TokenType.FALSE, - TokenType.FIRST, - TokenType.FILTER, - TokenType.FINAL, - TokenType.FORMAT, - TokenType.FULL, - TokenType.GET, - TokenType.IDENTIFIER, - TokenType.IS, - TokenType.ISNULL, - TokenType.INTERVAL, - TokenType.KEEP, - TokenType.KILL, - TokenType.LEFT, - TokenType.LIMIT, - TokenType.LOAD, - TokenType.LOCK, - TokenType.MATCH, - TokenType.MERGE, - TokenType.NATURAL, - TokenType.NEXT, - TokenType.OFFSET, - TokenType.OPERATOR, - TokenType.ORDINALITY, - TokenType.OVER, - TokenType.OVERLAPS, - TokenType.OVERWRITE, - TokenType.PARTITION, - TokenType.PERCENT, - TokenType.PIVOT, - TokenType.PRAGMA, - TokenType.PUT, - TokenType.RANGE, - TokenType.RECURSIVE, - TokenType.REFERENCES, - TokenType.REFRESH, - TokenType.RENAME, - TokenType.REPLACE, - TokenType.RIGHT, - TokenType.ROLLUP, - TokenType.ROW, - TokenType.ROWS, - TokenType.SEMI, - TokenType.SET, - TokenType.SETTINGS, - TokenType.SHOW, - TokenType.TEMPORARY, - TokenType.TOP, - TokenType.TRUE, - TokenType.TRUNCATE, - TokenType.UNIQUE, - TokenType.UNNEST, - TokenType.UNPIVOT, - TokenType.UPDATE, - TokenType.USE, - TokenType.VOLATILE, - TokenType.WINDOW, - *ALTERABLES, - *CREATABLES, - *SUBQUERY_PREDICATES, - *TYPE_TOKENS, - *NO_PAREN_FUNCTIONS, - } - ID_VAR_TOKENS.remove(TokenType.UNION) - - TABLE_ALIAS_TOKENS = ID_VAR_TOKENS - { - TokenType.ANTI, - TokenType.ASOF, - TokenType.FULL, - TokenType.LEFT, - TokenType.LOCK, - TokenType.NATURAL, - TokenType.RIGHT, - TokenType.SEMI, - TokenType.WINDOW, - } - - ALIAS_TOKENS = ID_VAR_TOKENS - - COLON_PLACEHOLDER_TOKENS = ID_VAR_TOKENS - - ARRAY_CONSTRUCTORS = { - "ARRAY": exp.Array, - "LIST": exp.List, - } - - COMMENT_TABLE_ALIAS_TOKENS = TABLE_ALIAS_TOKENS - {TokenType.IS} - - UPDATE_ALIAS_TOKENS = TABLE_ALIAS_TOKENS - {TokenType.SET} - - TRIM_TYPES = {"LEADING", "TRAILING", "BOTH"} - - FUNC_TOKENS = { - TokenType.COLLATE, - TokenType.COMMAND, - TokenType.CURRENT_DATE, - TokenType.CURRENT_DATETIME, - TokenType.CURRENT_SCHEMA, - TokenType.CURRENT_TIMESTAMP, - TokenType.CURRENT_TIME, - TokenType.CURRENT_USER, - TokenType.CURRENT_CATALOG, - TokenType.FILTER, - TokenType.FIRST, - TokenType.FORMAT, - TokenType.GET, - TokenType.GLOB, - TokenType.IDENTIFIER, - TokenType.INDEX, - TokenType.ISNULL, - TokenType.ILIKE, - TokenType.INSERT, - TokenType.LIKE, - TokenType.LOCALTIME, - TokenType.LOCALTIMESTAMP, - TokenType.MERGE, - TokenType.NEXT, - TokenType.OFFSET, - TokenType.PRIMARY_KEY, - TokenType.RANGE, - TokenType.REPLACE, - TokenType.RLIKE, - TokenType.ROW, - TokenType.SESSION_USER, - TokenType.UNNEST, - TokenType.VAR, - TokenType.LEFT, - TokenType.RIGHT, - TokenType.SEQUENCE, - TokenType.DATE, - TokenType.DATETIME, - TokenType.TABLE, - TokenType.TIMESTAMP, - TokenType.TIMESTAMPTZ, - TokenType.TRUNCATE, - TokenType.UTC_DATE, - TokenType.UTC_TIME, - TokenType.UTC_TIMESTAMP, - TokenType.WINDOW, - TokenType.XOR, - *TYPE_TOKENS, - *SUBQUERY_PREDICATES, - } - - CONJUNCTION: t.Dict[TokenType, t.Type[exp.Expression]] = { - TokenType.AND: exp.And, - } - - ASSIGNMENT: t.Dict[TokenType, t.Type[exp.Expression]] = { - TokenType.COLON_EQ: exp.PropertyEQ, - } - - DISJUNCTION: t.Dict[TokenType, t.Type[exp.Expression]] = { - TokenType.OR: exp.Or, - } - - EQUALITY = { - TokenType.EQ: exp.EQ, - TokenType.NEQ: exp.NEQ, - TokenType.NULLSAFE_EQ: exp.NullSafeEQ, - } - - COMPARISON = { - TokenType.GT: exp.GT, - TokenType.GTE: exp.GTE, - TokenType.LT: exp.LT, - TokenType.LTE: exp.LTE, - } - - BITWISE = { - TokenType.AMP: exp.BitwiseAnd, - TokenType.CARET: exp.BitwiseXor, - TokenType.PIPE: exp.BitwiseOr, - } - - TERM = { - TokenType.DASH: exp.Sub, - TokenType.PLUS: exp.Add, - TokenType.MOD: exp.Mod, - TokenType.COLLATE: exp.Collate, - } - - FACTOR = { - TokenType.DIV: exp.IntDiv, - TokenType.LR_ARROW: exp.Distance, - TokenType.SLASH: exp.Div, - TokenType.STAR: exp.Mul, - } - - EXPONENT: t.Dict[TokenType, t.Type[exp.Expression]] = {} - - TIMES = { - TokenType.TIME, - TokenType.TIMETZ, - } - - TIMESTAMPS = { - TokenType.TIMESTAMP, - TokenType.TIMESTAMPNTZ, - TokenType.TIMESTAMPTZ, - TokenType.TIMESTAMPLTZ, - *TIMES, - } - - SET_OPERATIONS = { - TokenType.UNION, - TokenType.INTERSECT, - TokenType.EXCEPT, - } - - JOIN_METHODS = { - TokenType.ASOF, - TokenType.NATURAL, - TokenType.POSITIONAL, - } - - JOIN_SIDES = { - TokenType.LEFT, - TokenType.RIGHT, - TokenType.FULL, - } - - JOIN_KINDS = { - TokenType.ANTI, - TokenType.CROSS, - TokenType.INNER, - TokenType.OUTER, - TokenType.SEMI, - TokenType.STRAIGHT_JOIN, - } - - JOIN_HINTS: t.Set[str] = set() - - LAMBDAS = { - TokenType.ARROW: lambda self, expressions: self.expression( - exp.Lambda, - this=self._replace_lambda( - self._parse_disjunction(), - expressions, - ), - expressions=expressions, - ), - TokenType.FARROW: lambda self, expressions: self.expression( - exp.Kwarg, - this=exp.var(expressions[0].name), - expression=self._parse_disjunction(), - ), - } - - COLUMN_OPERATORS = { - TokenType.DOT: None, - TokenType.DOTCOLON: lambda self, this, to: self.expression( - exp.JSONCast, - this=this, - to=to, - ), - TokenType.DCOLON: lambda self, this, to: self.build_cast( - strict=self.STRICT_CAST, this=this, to=to - ), - TokenType.ARROW: lambda self, this, path: self.expression( - exp.JSONExtract, - this=this, - expression=self.dialect.to_json_path(path), - only_json_types=self.JSON_ARROWS_REQUIRE_JSON_TYPE, - ), - TokenType.DARROW: lambda self, this, path: self.expression( - exp.JSONExtractScalar, - this=this, - expression=self.dialect.to_json_path(path), - only_json_types=self.JSON_ARROWS_REQUIRE_JSON_TYPE, - scalar_only=self.dialect.JSON_EXTRACT_SCALAR_SCALAR_ONLY, - ), - TokenType.HASH_ARROW: lambda self, this, path: self.expression( - exp.JSONBExtract, - this=this, - expression=path, - ), - TokenType.DHASH_ARROW: lambda self, this, path: self.expression( - exp.JSONBExtractScalar, - this=this, - expression=path, - ), - TokenType.PLACEHOLDER: lambda self, this, key: self.expression( - exp.JSONBContains, - this=this, - expression=key, - ), - } - - CAST_COLUMN_OPERATORS = { - TokenType.DOTCOLON, - TokenType.DCOLON, - } - - EXPRESSION_PARSERS = { - exp.Cluster: lambda self: self._parse_sort(exp.Cluster, TokenType.CLUSTER_BY), - exp.Column: lambda self: self._parse_column(), - exp.ColumnDef: lambda self: self._parse_column_def(self._parse_column()), - exp.Condition: lambda self: self._parse_disjunction(), - exp.DataType: lambda self: self._parse_types( - allow_identifiers=False, schema=True - ), - exp.Expression: lambda self: self._parse_expression(), - exp.From: lambda self: self._parse_from(joins=True), - exp.GrantPrincipal: lambda self: self._parse_grant_principal(), - exp.GrantPrivilege: lambda self: self._parse_grant_privilege(), - exp.Group: lambda self: self._parse_group(), - exp.Having: lambda self: self._parse_having(), - exp.Hint: lambda self: self._parse_hint_body(), - exp.Identifier: lambda self: self._parse_id_var(), - exp.Join: lambda self: self._parse_join(), - exp.Lambda: lambda self: self._parse_lambda(), - exp.Lateral: lambda self: self._parse_lateral(), - exp.Limit: lambda self: self._parse_limit(), - exp.Offset: lambda self: self._parse_offset(), - exp.Order: lambda self: self._parse_order(), - exp.Ordered: lambda self: self._parse_ordered(), - exp.Properties: lambda self: self._parse_properties(), - exp.PartitionedByProperty: lambda self: self._parse_partitioned_by(), - exp.Qualify: lambda self: self._parse_qualify(), - exp.Returning: lambda self: self._parse_returning(), - exp.Select: lambda self: self._parse_select(), - exp.Sort: lambda self: self._parse_sort(exp.Sort, TokenType.SORT_BY), - exp.Table: lambda self: self._parse_table_parts(), - exp.TableAlias: lambda self: self._parse_table_alias(), - exp.Tuple: lambda self: self._parse_value(values=False), - exp.Whens: lambda self: self._parse_when_matched(), - exp.Where: lambda self: self._parse_where(), - exp.Window: lambda self: self._parse_named_window(), - exp.With: lambda self: self._parse_with(), - "JOIN_TYPE": lambda self: self._parse_join_parts(), - } - - STATEMENT_PARSERS = { - TokenType.ALTER: lambda self: self._parse_alter(), - TokenType.ANALYZE: lambda self: self._parse_analyze(), - TokenType.BEGIN: lambda self: self._parse_transaction(), - TokenType.CACHE: lambda self: self._parse_cache(), - TokenType.COMMENT: lambda self: self._parse_comment(), - TokenType.COMMIT: lambda self: self._parse_commit_or_rollback(), - TokenType.COPY: lambda self: self._parse_copy(), - TokenType.CREATE: lambda self: self._parse_create(), - TokenType.DELETE: lambda self: self._parse_delete(), - TokenType.DESC: lambda self: self._parse_describe(), - TokenType.DESCRIBE: lambda self: self._parse_describe(), - TokenType.DROP: lambda self: self._parse_drop(), - TokenType.GRANT: lambda self: self._parse_grant(), - TokenType.REVOKE: lambda self: self._parse_revoke(), - TokenType.INSERT: lambda self: self._parse_insert(), - TokenType.KILL: lambda self: self._parse_kill(), - TokenType.LOAD: lambda self: self._parse_load(), - TokenType.MERGE: lambda self: self._parse_merge(), - TokenType.PIVOT: lambda self: self._parse_simplified_pivot(), - TokenType.PRAGMA: lambda self: self.expression( - exp.Pragma, this=self._parse_expression() - ), - TokenType.REFRESH: lambda self: self._parse_refresh(), - TokenType.ROLLBACK: lambda self: self._parse_commit_or_rollback(), - TokenType.SET: lambda self: self._parse_set(), - TokenType.TRUNCATE: lambda self: self._parse_truncate_table(), - TokenType.UNCACHE: lambda self: self._parse_uncache(), - TokenType.UNPIVOT: lambda self: self._parse_simplified_pivot(is_unpivot=True), - TokenType.UPDATE: lambda self: self._parse_update(), - TokenType.USE: lambda self: self._parse_use(), - TokenType.SEMICOLON: lambda self: exp.Semicolon(), - } - - UNARY_PARSERS = { - TokenType.PLUS: lambda self: ( - self._parse_unary() - ), # Unary + is handled as a no-op - TokenType.NOT: lambda self: self.expression( - exp.Not, this=self._parse_equality() - ), - TokenType.TILDA: lambda self: self.expression( - exp.BitwiseNot, this=self._parse_unary() - ), - TokenType.DASH: lambda self: self.expression(exp.Neg, this=self._parse_unary()), - TokenType.PIPE_SLASH: lambda self: self.expression( - exp.Sqrt, this=self._parse_unary() - ), - TokenType.DPIPE_SLASH: lambda self: self.expression( - exp.Cbrt, this=self._parse_unary() - ), - } - - STRING_PARSERS = { - TokenType.HEREDOC_STRING: lambda self, token: self.expression( - exp.RawString, token=token - ), - TokenType.NATIONAL_STRING: lambda self, token: self.expression( - exp.National, token=token - ), - TokenType.RAW_STRING: lambda self, token: self.expression( - exp.RawString, token=token - ), - TokenType.STRING: lambda self, token: self.expression( - exp.Literal, token=token, is_string=True - ), - TokenType.UNICODE_STRING: lambda self, token: self.expression( - exp.UnicodeString, - token=token, - escape=self._match_text_seq("UESCAPE") and self._parse_string(), - ), - } - - NUMERIC_PARSERS = { - TokenType.BIT_STRING: lambda self, token: self.expression( - exp.BitString, token=token - ), - TokenType.BYTE_STRING: lambda self, token: self.expression( - exp.ByteString, - token=token, - is_bytes=self.dialect.BYTE_STRING_IS_BYTES_TYPE or None, - ), - TokenType.HEX_STRING: lambda self, token: self.expression( - exp.HexString, - token=token, - is_integer=self.dialect.HEX_STRING_IS_INTEGER_TYPE or None, - ), - TokenType.NUMBER: lambda self, token: self.expression( - exp.Literal, token=token, is_string=False - ), - } - - PRIMARY_PARSERS = { - **STRING_PARSERS, - **NUMERIC_PARSERS, - TokenType.INTRODUCER: lambda self, token: self._parse_introducer(token), - TokenType.NULL: lambda self, _: self.expression(exp.Null), - TokenType.TRUE: lambda self, _: self.expression(exp.Boolean, this=True), - TokenType.FALSE: lambda self, _: self.expression(exp.Boolean, this=False), - TokenType.SESSION_PARAMETER: lambda self, _: self._parse_session_parameter(), - TokenType.STAR: lambda self, _: self._parse_star_ops(), - } - - PLACEHOLDER_PARSERS = { - TokenType.PLACEHOLDER: lambda self: self.expression(exp.Placeholder), - TokenType.PARAMETER: lambda self: self._parse_parameter(), - TokenType.COLON: lambda self: ( - self.expression(exp.Placeholder, this=self._prev.text) - if self._match_set(self.COLON_PLACEHOLDER_TOKENS) - else None - ), - } - - RANGE_PARSERS = { - TokenType.AT_GT: binary_range_parser(exp.ArrayContainsAll), - TokenType.BETWEEN: lambda self, this: self._parse_between(this), - TokenType.GLOB: binary_range_parser(exp.Glob), - TokenType.ILIKE: binary_range_parser(exp.ILike), - TokenType.IN: lambda self, this: self._parse_in(this), - TokenType.IRLIKE: binary_range_parser(exp.RegexpILike), - TokenType.IS: lambda self, this: self._parse_is(this), - TokenType.LIKE: binary_range_parser(exp.Like), - TokenType.LT_AT: binary_range_parser(exp.ArrayContainsAll, reverse_args=True), - TokenType.OVERLAPS: binary_range_parser(exp.Overlaps), - TokenType.RLIKE: binary_range_parser(exp.RegexpLike), - TokenType.SIMILAR_TO: binary_range_parser(exp.SimilarTo), - TokenType.FOR: lambda self, this: self._parse_comprehension(this), - TokenType.QMARK_AMP: binary_range_parser(exp.JSONBContainsAllTopKeys), - TokenType.QMARK_PIPE: binary_range_parser(exp.JSONBContainsAnyTopKeys), - TokenType.HASH_DASH: binary_range_parser(exp.JSONBDeleteAtPath), - TokenType.ADJACENT: binary_range_parser(exp.Adjacent), - TokenType.OPERATOR: lambda self, this: self._parse_operator(this), - TokenType.AMP_LT: binary_range_parser(exp.ExtendsLeft), - TokenType.AMP_GT: binary_range_parser(exp.ExtendsRight), - } - - PIPE_SYNTAX_TRANSFORM_PARSERS = { - "AGGREGATE": lambda self, query: self._parse_pipe_syntax_aggregate(query), - "AS": lambda self, query: self._build_pipe_cte( - query, [exp.Star()], self._parse_table_alias() - ), - "EXTEND": lambda self, query: self._parse_pipe_syntax_extend(query), - "LIMIT": lambda self, query: self._parse_pipe_syntax_limit(query), - "ORDER BY": lambda self, query: query.order_by( - self._parse_order(), append=False, copy=False - ), - "PIVOT": lambda self, query: self._parse_pipe_syntax_pivot(query), - "SELECT": lambda self, query: self._parse_pipe_syntax_select(query), - "TABLESAMPLE": lambda self, query: self._parse_pipe_syntax_tablesample(query), - "UNPIVOT": lambda self, query: self._parse_pipe_syntax_pivot(query), - "WHERE": lambda self, query: query.where(self._parse_where(), copy=False), - } - - PROPERTY_PARSERS: t.Dict[str, t.Callable] = { - "ALLOWED_VALUES": lambda self: self.expression( - exp.AllowedValuesProperty, expressions=self._parse_csv(self._parse_primary) - ), - "ALGORITHM": lambda self: self._parse_property_assignment( - exp.AlgorithmProperty - ), - "AUTO": lambda self: self._parse_auto_property(), - "AUTO_INCREMENT": lambda self: self._parse_property_assignment( - exp.AutoIncrementProperty - ), - "BACKUP": lambda self: self.expression( - exp.BackupProperty, this=self._parse_var(any_token=True) - ), - "BLOCKCOMPRESSION": lambda self: self._parse_blockcompression(), - "CHARSET": lambda self, **kwargs: self._parse_character_set(**kwargs), - "CHARACTER SET": lambda self, **kwargs: self._parse_character_set(**kwargs), - "CHECKSUM": lambda self: self._parse_checksum(), - "CLUSTER BY": lambda self: self._parse_cluster(), - "CLUSTERED": lambda self: self._parse_clustered_by(), - "COLLATE": lambda self, **kwargs: self._parse_property_assignment( - exp.CollateProperty, **kwargs - ), - "COMMENT": lambda self: self._parse_property_assignment( - exp.SchemaCommentProperty - ), - "CONTAINS": lambda self: self._parse_contains_property(), - "COPY": lambda self: self._parse_copy_property(), - "DATABLOCKSIZE": lambda self, **kwargs: self._parse_datablocksize(**kwargs), - "DATA_DELETION": lambda self: self._parse_data_deletion_property(), - "DEFINER": lambda self: self._parse_definer(), - "DETERMINISTIC": lambda self: self.expression( - exp.StabilityProperty, this=exp.Literal.string("IMMUTABLE") - ), - "DISTRIBUTED": lambda self: self._parse_distributed_property(), - "DUPLICATE": lambda self: self._parse_composite_key_property( - exp.DuplicateKeyProperty - ), - "DYNAMIC": lambda self: self.expression(exp.DynamicProperty), - "DISTKEY": lambda self: self._parse_distkey(), - "DISTSTYLE": lambda self: self._parse_property_assignment( - exp.DistStyleProperty - ), - "EMPTY": lambda self: self.expression(exp.EmptyProperty), - "ENGINE": lambda self: self._parse_property_assignment(exp.EngineProperty), - "ENVIRONMENT": lambda self: self.expression( - exp.EnviromentProperty, - expressions=self._parse_wrapped_csv(self._parse_assignment), - ), - "EXECUTE": lambda self: self._parse_property_assignment(exp.ExecuteAsProperty), - "EXTERNAL": lambda self: self.expression(exp.ExternalProperty), - "FALLBACK": lambda self, **kwargs: self._parse_fallback(**kwargs), - "FORMAT": lambda self: self._parse_property_assignment(exp.FileFormatProperty), - "FREESPACE": lambda self: self._parse_freespace(), - "GLOBAL": lambda self: self.expression(exp.GlobalProperty), - "HEAP": lambda self: self.expression(exp.HeapProperty), - "ICEBERG": lambda self: self.expression(exp.IcebergProperty), - "IMMUTABLE": lambda self: self.expression( - exp.StabilityProperty, this=exp.Literal.string("IMMUTABLE") - ), - "INHERITS": lambda self: self.expression( - exp.InheritsProperty, expressions=self._parse_wrapped_csv(self._parse_table) - ), - "INPUT": lambda self: self.expression( - exp.InputModelProperty, this=self._parse_schema() - ), - "JOURNAL": lambda self, **kwargs: self._parse_journal(**kwargs), - "LANGUAGE": lambda self: self._parse_property_assignment(exp.LanguageProperty), - "LAYOUT": lambda self: self._parse_dict_property(this="LAYOUT"), - "LIFETIME": lambda self: self._parse_dict_range(this="LIFETIME"), - "LIKE": lambda self: self._parse_create_like(), - "LOCATION": lambda self: self._parse_property_assignment(exp.LocationProperty), - "LOCK": lambda self: self._parse_locking(), - "LOCKING": lambda self: self._parse_locking(), - "LOG": lambda self, **kwargs: self._parse_log(**kwargs), - "MATERIALIZED": lambda self: self.expression(exp.MaterializedProperty), - "MERGEBLOCKRATIO": lambda self, **kwargs: self._parse_mergeblockratio(**kwargs), - "MODIFIES": lambda self: self._parse_modifies_property(), - "MULTISET": lambda self: self.expression(exp.SetProperty, multi=True), - "NO": lambda self: self._parse_no_property(), - "ON": lambda self: self._parse_on_property(), - "ORDER BY": lambda self: self._parse_order(skip_order_token=True), - "OUTPUT": lambda self: self.expression( - exp.OutputModelProperty, this=self._parse_schema() - ), - "PARTITION": lambda self: self._parse_partitioned_of(), - "PARTITION BY": lambda self: self._parse_partitioned_by(), - "PARTITIONED BY": lambda self: self._parse_partitioned_by(), - "PARTITIONED_BY": lambda self: self._parse_partitioned_by(), - "PRIMARY KEY": lambda self: self._parse_primary_key(in_props=True), - "RANGE": lambda self: self._parse_dict_range(this="RANGE"), - "READS": lambda self: self._parse_reads_property(), - "REMOTE": lambda self: self._parse_remote_with_connection(), - "RETURNS": lambda self: self._parse_returns(), - "STRICT": lambda self: self.expression(exp.StrictProperty), - "STREAMING": lambda self: self.expression(exp.StreamingTableProperty), - "ROW": lambda self: self._parse_row(), - "ROW_FORMAT": lambda self: self._parse_property_assignment( - exp.RowFormatProperty - ), - "SAMPLE": lambda self: self.expression( - exp.SampleProperty, - this=self._match_text_seq("BY") and self._parse_bitwise(), - ), - "SECURE": lambda self: self.expression(exp.SecureProperty), - "SECURITY": lambda self: self._parse_security(), - "SET": lambda self: self.expression(exp.SetProperty, multi=False), - "SETTINGS": lambda self: self._parse_settings_property(), - "SHARING": lambda self: self._parse_property_assignment(exp.SharingProperty), - "SORTKEY": lambda self: self._parse_sortkey(), - "SOURCE": lambda self: self._parse_dict_property(this="SOURCE"), - "STABLE": lambda self: self.expression( - exp.StabilityProperty, this=exp.Literal.string("STABLE") - ), - "STORED": lambda self: self._parse_stored(), - "SYSTEM_VERSIONING": lambda self: self._parse_system_versioning_property(), - "TBLPROPERTIES": lambda self: self._parse_wrapped_properties(), - "TEMP": lambda self: self.expression(exp.TemporaryProperty), - "TEMPORARY": lambda self: self.expression(exp.TemporaryProperty), - "TO": lambda self: self._parse_to_table(), - "TRANSIENT": lambda self: self.expression(exp.TransientProperty), - "TRANSFORM": lambda self: self.expression( - exp.TransformModelProperty, - expressions=self._parse_wrapped_csv(self._parse_expression), - ), - "TTL": lambda self: self._parse_ttl(), - "USING": lambda self: self._parse_property_assignment(exp.FileFormatProperty), - "UNLOGGED": lambda self: self.expression(exp.UnloggedProperty), - "VOLATILE": lambda self: self._parse_volatile_property(), - "WITH": lambda self: self._parse_with_property(), - } - - CONSTRAINT_PARSERS = { - "AUTOINCREMENT": lambda self: self._parse_auto_increment(), - "AUTO_INCREMENT": lambda self: self._parse_auto_increment(), - "CASESPECIFIC": lambda self: self.expression( - exp.CaseSpecificColumnConstraint, not_=False - ), - "CHARACTER SET": lambda self: self.expression( - exp.CharacterSetColumnConstraint, this=self._parse_var_or_string() - ), - "CHECK": lambda self: self.expression( - exp.CheckColumnConstraint, - this=self._parse_wrapped(self._parse_assignment), - enforced=self._match_text_seq("ENFORCED"), - ), - "COLLATE": lambda self: self.expression( - exp.CollateColumnConstraint, - this=self._parse_identifier() or self._parse_column(), - ), - "COMMENT": lambda self: self.expression( - exp.CommentColumnConstraint, this=self._parse_string() - ), - "COMPRESS": lambda self: self._parse_compress(), - "CLUSTERED": lambda self: self.expression( - exp.ClusteredColumnConstraint, - this=self._parse_wrapped_csv(self._parse_ordered), - ), - "NONCLUSTERED": lambda self: self.expression( - exp.NonClusteredColumnConstraint, - this=self._parse_wrapped_csv(self._parse_ordered), - ), - "DEFAULT": lambda self: self.expression( - exp.DefaultColumnConstraint, this=self._parse_bitwise() - ), - "ENCODE": lambda self: self.expression( - exp.EncodeColumnConstraint, this=self._parse_var() - ), - "EPHEMERAL": lambda self: self.expression( - exp.EphemeralColumnConstraint, this=self._parse_bitwise() - ), - "EXCLUDE": lambda self: self.expression( - exp.ExcludeColumnConstraint, this=self._parse_index_params() - ), - "FOREIGN KEY": lambda self: self._parse_foreign_key(), - "FORMAT": lambda self: self.expression( - exp.DateFormatColumnConstraint, this=self._parse_var_or_string() - ), - "GENERATED": lambda self: self._parse_generated_as_identity(), - "IDENTITY": lambda self: self._parse_auto_increment(), - "INLINE": lambda self: self._parse_inline(), - "LIKE": lambda self: self._parse_create_like(), - "NOT": lambda self: self._parse_not_constraint(), - "NULL": lambda self: self.expression( - exp.NotNullColumnConstraint, allow_null=True - ), - "ON": lambda self: ( - ( - self._match(TokenType.UPDATE) - and self.expression( - exp.OnUpdateColumnConstraint, this=self._parse_function() - ) - ) - or self.expression(exp.OnProperty, this=self._parse_id_var()) - ), - "PATH": lambda self: self.expression( - exp.PathColumnConstraint, this=self._parse_string() - ), - "PERIOD": lambda self: self._parse_period_for_system_time(), - "PRIMARY KEY": lambda self: self._parse_primary_key(), - "REFERENCES": lambda self: self._parse_references(match=False), - "TITLE": lambda self: self.expression( - exp.TitleColumnConstraint, this=self._parse_var_or_string() - ), - "TTL": lambda self: self.expression( - exp.MergeTreeTTL, expressions=[self._parse_bitwise()] - ), - "UNIQUE": lambda self: self._parse_unique(), - "UPPERCASE": lambda self: self.expression(exp.UppercaseColumnConstraint), - "WITH": lambda self: self.expression( - exp.Properties, expressions=self._parse_wrapped_properties() - ), - "BUCKET": lambda self: self._parse_partitioned_by_bucket_or_truncate(), - "TRUNCATE": lambda self: self._parse_partitioned_by_bucket_or_truncate(), - } - - def _parse_partitioned_by_bucket_or_truncate(self) -> t.Optional[exp.Expression]: - if not self._match(TokenType.L_PAREN, advance=False): - # Partitioning by bucket or truncate follows the syntax: - # PARTITION BY (BUCKET(..) | TRUNCATE(..)) - # If we don't have parenthesis after each keyword, we should instead parse this as an identifier - self._retreat(self._index - 1) - return None - - klass = ( - exp.PartitionedByBucket - if self._prev.text.upper() == "BUCKET" - else exp.PartitionByTruncate - ) - - args = self._parse_wrapped_csv( - lambda: self._parse_primary() or self._parse_column() - ) - this, expression = seq_get(args, 0), seq_get(args, 1) - - if isinstance(this, exp.Literal): - # Check for Iceberg partition transforms (bucket / truncate) and ensure their arguments are in the right order - # - For Hive, it's `bucket(, )` or `truncate(, )` - # - For Trino, it's reversed - `bucket(, )` or `truncate(, )` - # Both variants are canonicalized in the latter i.e `bucket(, )` - # - # Hive ref: https://docs.aws.amazon.com/athena/latest/ug/querying-iceberg-creating-tables.html#querying-iceberg-partitioning - # Trino ref: https://docs.aws.amazon.com/athena/latest/ug/create-table-as.html#ctas-table-properties - this, expression = expression, this - - return self.expression(klass, this=this, expression=expression) - - ALTER_PARSERS = { - "ADD": lambda self: self._parse_alter_table_add(), - "AS": lambda self: self._parse_select(), - "ALTER": lambda self: self._parse_alter_table_alter(), - "CLUSTER BY": lambda self: self._parse_cluster(wrapped=True), - "DELETE": lambda self: self.expression(exp.Delete, where=self._parse_where()), - "DROP": lambda self: self._parse_alter_table_drop(), - "RENAME": lambda self: self._parse_alter_table_rename(), - "SET": lambda self: self._parse_alter_table_set(), - "SWAP": lambda self: self.expression( - exp.SwapTable, - this=self._match(TokenType.WITH) and self._parse_table(schema=True), - ), - } - - ALTER_ALTER_PARSERS = { - "DISTKEY": lambda self: self._parse_alter_diststyle(), - "DISTSTYLE": lambda self: self._parse_alter_diststyle(), - "SORTKEY": lambda self: self._parse_alter_sortkey(), - "COMPOUND": lambda self: self._parse_alter_sortkey(compound=True), - } - - SCHEMA_UNNAMED_CONSTRAINTS = { - "CHECK", - "EXCLUDE", - "FOREIGN KEY", - "LIKE", - "PERIOD", - "PRIMARY KEY", - "UNIQUE", - "BUCKET", - "TRUNCATE", - } - - NO_PAREN_FUNCTION_PARSERS = { - "ANY": lambda self: self.expression(exp.Any, this=self._parse_bitwise()), - "CASE": lambda self: self._parse_case(), - "CONNECT_BY_ROOT": lambda self: self.expression( - exp.ConnectByRoot, this=self._parse_column() - ), - "IF": lambda self: self._parse_if(), - } - - INVALID_FUNC_NAME_TOKENS = { - TokenType.IDENTIFIER, - TokenType.STRING, - } - - FUNCTIONS_WITH_ALIASED_ARGS = {"STRUCT"} - - KEY_VALUE_DEFINITIONS = (exp.Alias, exp.EQ, exp.PropertyEQ, exp.Slice) - - FUNCTION_PARSERS = { - **{ - name: lambda self: self._parse_max_min_by(exp.ArgMax) - for name in exp.ArgMax.sql_names() - }, - **{ - name: lambda self: self._parse_max_min_by(exp.ArgMin) - for name in exp.ArgMin.sql_names() - }, - "CAST": lambda self: self._parse_cast(self.STRICT_CAST), - "CEIL": lambda self: self._parse_ceil_floor(exp.Ceil), - "CONVERT": lambda self: self._parse_convert(self.STRICT_CAST), - "DECODE": lambda self: self._parse_decode(), - "EXTRACT": lambda self: self._parse_extract(), - "FLOOR": lambda self: self._parse_ceil_floor(exp.Floor), - "GAP_FILL": lambda self: self._parse_gap_fill(), - "INITCAP": lambda self: self._parse_initcap(), - "JSON_OBJECT": lambda self: self._parse_json_object(), - "JSON_OBJECTAGG": lambda self: self._parse_json_object(agg=True), - "JSON_TABLE": lambda self: self._parse_json_table(), - "MATCH": lambda self: self._parse_match_against(), - "NORMALIZE": lambda self: self._parse_normalize(), - "OPENJSON": lambda self: self._parse_open_json(), - "OVERLAY": lambda self: self._parse_overlay(), - "POSITION": lambda self: self._parse_position(), - "SAFE_CAST": lambda self: self._parse_cast(False, safe=True), - "STRING_AGG": lambda self: self._parse_string_agg(), - "SUBSTRING": lambda self: self._parse_substring(), - "TRIM": lambda self: self._parse_trim(), - "TRY_CAST": lambda self: self._parse_cast(False, safe=True), - "TRY_CONVERT": lambda self: self._parse_convert(False, safe=True), - "XMLELEMENT": lambda self: self.expression( - exp.XMLElement, - this=self._match_text_seq("NAME") and self._parse_id_var(), - expressions=self._match(TokenType.COMMA) - and self._parse_csv(self._parse_expression), - ), - "XMLTABLE": lambda self: self._parse_xml_table(), - } - - QUERY_MODIFIER_PARSERS = { - TokenType.MATCH_RECOGNIZE: lambda self: ( - "match", - self._parse_match_recognize(), - ), - TokenType.PREWHERE: lambda self: ("prewhere", self._parse_prewhere()), - TokenType.WHERE: lambda self: ("where", self._parse_where()), - TokenType.GROUP_BY: lambda self: ("group", self._parse_group()), - TokenType.HAVING: lambda self: ("having", self._parse_having()), - TokenType.QUALIFY: lambda self: ("qualify", self._parse_qualify()), - TokenType.WINDOW: lambda self: ("windows", self._parse_window_clause()), - TokenType.ORDER_BY: lambda self: ("order", self._parse_order()), - TokenType.LIMIT: lambda self: ("limit", self._parse_limit()), - TokenType.FETCH: lambda self: ("limit", self._parse_limit()), - TokenType.OFFSET: lambda self: ("offset", self._parse_offset()), - TokenType.FOR: lambda self: ("locks", self._parse_locks()), - TokenType.LOCK: lambda self: ("locks", self._parse_locks()), - TokenType.TABLE_SAMPLE: lambda self: ( - "sample", - self._parse_table_sample(as_modifier=True), - ), - TokenType.USING: lambda self: ( - "sample", - self._parse_table_sample(as_modifier=True), - ), - TokenType.CLUSTER_BY: lambda self: ( - "cluster", - self._parse_sort(exp.Cluster, TokenType.CLUSTER_BY), - ), - TokenType.DISTRIBUTE_BY: lambda self: ( - "distribute", - self._parse_sort(exp.Distribute, TokenType.DISTRIBUTE_BY), - ), - TokenType.SORT_BY: lambda self: ( - "sort", - self._parse_sort(exp.Sort, TokenType.SORT_BY), - ), - TokenType.CONNECT_BY: lambda self: ( - "connect", - self._parse_connect(skip_start_token=True), - ), - TokenType.START_WITH: lambda self: ("connect", self._parse_connect()), - } - QUERY_MODIFIER_TOKENS = set(QUERY_MODIFIER_PARSERS) - - SET_PARSERS = { - "GLOBAL": lambda self: self._parse_set_item_assignment("GLOBAL"), - "LOCAL": lambda self: self._parse_set_item_assignment("LOCAL"), - "SESSION": lambda self: self._parse_set_item_assignment("SESSION"), - "TRANSACTION": lambda self: self._parse_set_transaction(), - } - - SHOW_PARSERS: t.Dict[str, t.Callable] = {} - - TYPE_LITERAL_PARSERS = { - exp.DataType.Type.JSON: lambda self, this, _: self.expression( - exp.ParseJSON, this=this - ), - } - - TYPE_CONVERTERS: t.Dict[ - exp.DataType.Type, t.Callable[[exp.DataType], exp.DataType] - ] = {} - - DDL_SELECT_TOKENS = {TokenType.SELECT, TokenType.WITH, TokenType.L_PAREN} - - PRE_VOLATILE_TOKENS = {TokenType.CREATE, TokenType.REPLACE, TokenType.UNIQUE} - - TRANSACTION_KIND = {"DEFERRED", "IMMEDIATE", "EXCLUSIVE"} - TRANSACTION_CHARACTERISTICS: OPTIONS_TYPE = { - "ISOLATION": ( - ("LEVEL", "REPEATABLE", "READ"), - ("LEVEL", "READ", "COMMITTED"), - ("LEVEL", "READ", "UNCOMITTED"), - ("LEVEL", "SERIALIZABLE"), - ), - "READ": ("WRITE", "ONLY"), - } - - CONFLICT_ACTIONS: OPTIONS_TYPE = dict.fromkeys( - ("ABORT", "FAIL", "IGNORE", "REPLACE", "ROLLBACK", "UPDATE"), tuple() - ) - CONFLICT_ACTIONS["DO"] = ("NOTHING", "UPDATE") - - CREATE_SEQUENCE: OPTIONS_TYPE = { - "SCALE": ("EXTEND", "NOEXTEND"), - "SHARD": ("EXTEND", "NOEXTEND"), - "NO": ("CYCLE", "CACHE", "MAXVALUE", "MINVALUE"), - **dict.fromkeys( - ( - "SESSION", - "GLOBAL", - "KEEP", - "NOKEEP", - "ORDER", - "NOORDER", - "NOCACHE", - "CYCLE", - "NOCYCLE", - "NOMINVALUE", - "NOMAXVALUE", - "NOSCALE", - "NOSHARD", - ), - tuple(), - ), - } - - ISOLATED_LOADING_OPTIONS: OPTIONS_TYPE = {"FOR": ("ALL", "INSERT", "NONE")} - - USABLES: OPTIONS_TYPE = dict.fromkeys( - ("ROLE", "WAREHOUSE", "DATABASE", "SCHEMA", "CATALOG"), tuple() - ) - - CAST_ACTIONS: OPTIONS_TYPE = dict.fromkeys(("RENAME", "ADD"), ("FIELDS",)) - - SCHEMA_BINDING_OPTIONS: OPTIONS_TYPE = { - "TYPE": ("EVOLUTION",), - **dict.fromkeys(("BINDING", "COMPENSATION", "EVOLUTION"), tuple()), - } - - PROCEDURE_OPTIONS: OPTIONS_TYPE = {} - - EXECUTE_AS_OPTIONS: OPTIONS_TYPE = dict.fromkeys( - ("CALLER", "SELF", "OWNER"), tuple() - ) - - KEY_CONSTRAINT_OPTIONS: OPTIONS_TYPE = { - "NOT": ("ENFORCED",), - "MATCH": ( - "FULL", - "PARTIAL", - "SIMPLE", - ), - "INITIALLY": ("DEFERRED", "IMMEDIATE"), - "USING": ( - "BTREE", - "HASH", - ), - **dict.fromkeys(("DEFERRABLE", "NORELY", "RELY"), tuple()), - } - - WINDOW_EXCLUDE_OPTIONS: OPTIONS_TYPE = { - "NO": ("OTHERS",), - "CURRENT": ("ROW",), - **dict.fromkeys(("GROUP", "TIES"), tuple()), - } - - INSERT_ALTERNATIVES = {"ABORT", "FAIL", "IGNORE", "REPLACE", "ROLLBACK"} - - CLONE_KEYWORDS = {"CLONE", "COPY"} - HISTORICAL_DATA_PREFIX = {"AT", "BEFORE", "END"} - HISTORICAL_DATA_KIND = {"OFFSET", "STATEMENT", "STREAM", "TIMESTAMP", "VERSION"} - - OPCLASS_FOLLOW_KEYWORDS = {"ASC", "DESC", "NULLS", "WITH"} - - OPTYPE_FOLLOW_TOKENS = {TokenType.COMMA, TokenType.R_PAREN} - - TABLE_INDEX_HINT_TOKENS = {TokenType.FORCE, TokenType.IGNORE, TokenType.USE} - - VIEW_ATTRIBUTES = {"ENCRYPTION", "SCHEMABINDING", "VIEW_METADATA"} - - WINDOW_ALIAS_TOKENS = ID_VAR_TOKENS - {TokenType.RANGE, TokenType.ROWS} - WINDOW_BEFORE_PAREN_TOKENS = {TokenType.OVER} - WINDOW_SIDES = {"FOLLOWING", "PRECEDING"} - - JSON_KEY_VALUE_SEPARATOR_TOKENS = {TokenType.COLON, TokenType.COMMA, TokenType.IS} - - FETCH_TOKENS = ID_VAR_TOKENS - {TokenType.ROW, TokenType.ROWS, TokenType.PERCENT} - - ADD_CONSTRAINT_TOKENS = { - TokenType.CONSTRAINT, - TokenType.FOREIGN_KEY, - TokenType.INDEX, - TokenType.KEY, - TokenType.PRIMARY_KEY, - TokenType.UNIQUE, - } - - DISTINCT_TOKENS = {TokenType.DISTINCT} - - UNNEST_OFFSET_ALIAS_TOKENS = TABLE_ALIAS_TOKENS - SET_OPERATIONS - - SELECT_START_TOKENS = {TokenType.L_PAREN, TokenType.WITH, TokenType.SELECT} - - COPY_INTO_VARLEN_OPTIONS = { - "FILE_FORMAT", - "COPY_OPTIONS", - "FORMAT_OPTIONS", - "CREDENTIAL", - } - - IS_JSON_PREDICATE_KIND = {"VALUE", "SCALAR", "ARRAY", "OBJECT"} - - ODBC_DATETIME_LITERALS: t.Dict[str, t.Type[exp.Expression]] = {} - - ON_CONDITION_TOKENS = {"ERROR", "NULL", "TRUE", "FALSE", "EMPTY"} - - PRIVILEGE_FOLLOW_TOKENS = {TokenType.ON, TokenType.COMMA, TokenType.L_PAREN} - - # The style options for the DESCRIBE statement - DESCRIBE_STYLES = {"ANALYZE", "EXTENDED", "FORMATTED", "HISTORY"} - - SET_ASSIGNMENT_DELIMITERS = {"=", ":=", "TO"} - - # The style options for the ANALYZE statement - ANALYZE_STYLES = { - "BUFFER_USAGE_LIMIT", - "FULL", - "LOCAL", - "NO_WRITE_TO_BINLOG", - "SAMPLE", - "SKIP_LOCKED", - "VERBOSE", - } - - ANALYZE_EXPRESSION_PARSERS = { - "ALL": lambda self: self._parse_analyze_columns(), - "COMPUTE": lambda self: self._parse_analyze_statistics(), - "DELETE": lambda self: self._parse_analyze_delete(), - "DROP": lambda self: self._parse_analyze_histogram(), - "ESTIMATE": lambda self: self._parse_analyze_statistics(), - "LIST": lambda self: self._parse_analyze_list(), - "PREDICATE": lambda self: self._parse_analyze_columns(), - "UPDATE": lambda self: self._parse_analyze_histogram(), - "VALIDATE": lambda self: self._parse_analyze_validate(), - } - - PARTITION_KEYWORDS = {"PARTITION", "SUBPARTITION"} - - AMBIGUOUS_ALIAS_TOKENS = (TokenType.LIMIT, TokenType.OFFSET) - - OPERATION_MODIFIERS: t.Set[str] = set() - - RECURSIVE_CTE_SEARCH_KIND = {"BREADTH", "DEPTH", "CYCLE"} - - MODIFIABLES = (exp.Query, exp.Table, exp.TableFromRows, exp.Values) - - STRICT_CAST = True - - PREFIXED_PIVOT_COLUMNS = False - IDENTIFY_PIVOT_STRINGS = False - - LOG_DEFAULTS_TO_LN = False - - # Whether the table sample clause expects CSV syntax - TABLESAMPLE_CSV = False - - # The default method used for table sampling - DEFAULT_SAMPLING_METHOD: t.Optional[str] = None - - # Whether the SET command needs a delimiter (e.g. "=") for assignments - SET_REQUIRES_ASSIGNMENT_DELIMITER = True - - # Whether the TRIM function expects the characters to trim as its first argument - TRIM_PATTERN_FIRST = False - - # Whether string aliases are supported `SELECT COUNT(*) 'count'` - STRING_ALIASES = False - - # Whether query modifiers such as LIMIT are attached to the UNION node (vs its right operand) - MODIFIERS_ATTACHED_TO_SET_OP = True - SET_OP_MODIFIERS = {"order", "limit", "offset"} - - # Whether to parse IF statements that aren't followed by a left parenthesis as commands - NO_PAREN_IF_COMMANDS = True - - # Whether the -> and ->> operators expect documents of type JSON (e.g. Postgres) - JSON_ARROWS_REQUIRE_JSON_TYPE = False - - # Whether the `:` operator is used to extract a value from a VARIANT column - COLON_IS_VARIANT_EXTRACT = False - - # Whether or not a VALUES keyword needs to be followed by '(' to form a VALUES clause. - # If this is True and '(' is not found, the keyword will be treated as an identifier - VALUES_FOLLOWED_BY_PAREN = True - - # Whether implicit unnesting is supported, e.g. SELECT 1 FROM y.z AS z, z.a (Redshift) - SUPPORTS_IMPLICIT_UNNEST = False - - # Whether or not interval spans are supported, INTERVAL 1 YEAR TO MONTHS - INTERVAL_SPANS = True - - # Whether a PARTITION clause can follow a table reference - SUPPORTS_PARTITION_SELECTION = False - - # Whether the `name AS expr` schema/column constraint requires parentheses around `expr` - WRAPPED_TRANSFORM_COLUMN_CONSTRAINT = True - - # Whether the 'AS' keyword is optional in the CTE definition syntax - OPTIONAL_ALIAS_TOKEN_CTE = True - - # Whether renaming a column with an ALTER statement requires the presence of the COLUMN keyword - ALTER_RENAME_REQUIRES_COLUMN = True - - # Whether Alter statements are allowed to contain Partition specifications - ALTER_TABLE_PARTITIONS = False - - # Whether all join types have the same precedence, i.e., they "naturally" produce a left-deep tree. - # In standard SQL, joins that use the JOIN keyword take higher precedence than comma-joins. That is - # to say, JOIN operators happen before comma operators. This is not the case in some dialects, such - # as BigQuery, where all joins have the same precedence. - JOINS_HAVE_EQUAL_PRECEDENCE = False - - # Whether TIMESTAMP can produce a zone-aware timestamp - ZONE_AWARE_TIMESTAMP_CONSTRUCTOR = False - - # Whether map literals support arbitrary expressions as keys. - # When True, allows complex keys like arrays or literals: {[1, 2]: 3}, {1: 2} (e.g. DuckDB). - # When False, keys are typically restricted to identifiers. - MAP_KEYS_ARE_ARBITRARY_EXPRESSIONS = False - - # Whether JSON_EXTRACT requires a JSON expression as the first argument, e.g this - # is true for Snowflake but not for BigQuery which can also process strings - JSON_EXTRACT_REQUIRES_JSON_EXPRESSION = False - - # Dialects like Databricks support JOINS without join criteria - # Adding an ON TRUE, makes transpilation semantically correct for other dialects - ADD_JOIN_ON_TRUE = False - - # Whether INTERVAL spans with literal format '\d+ hh:[mm:[ss[.ff]]]' - # can omit the span unit `DAY TO MINUTE` or `DAY TO SECOND` - SUPPORTS_OMITTED_INTERVAL_SPAN_UNIT = False - - __slots__ = ( - "error_level", - "error_message_context", - "max_errors", - "dialect", - "sql", - "errors", - "_tokens", - "_index", - "_curr", - "_next", - "_prev", - "_prev_comments", - "_pipe_cte_counter", - ) - - # Autofilled - SHOW_TRIE: t.Dict = {} - SET_TRIE: t.Dict = {} - - def __init__( - self, - error_level: t.Optional[ErrorLevel] = None, - error_message_context: int = 100, - max_errors: int = 3, - dialect: DialectType = None, - ): - from bigframes_vendored.sqlglot.dialects import Dialect - - self.error_level = error_level or ErrorLevel.IMMEDIATE - self.error_message_context = error_message_context - self.max_errors = max_errors - self.dialect = Dialect.get_or_raise(dialect) - self.reset() - - def reset(self): - self.sql = "" - self.errors = [] - self._tokens = [] - self._index = 0 - self._curr = None - self._next = None - self._prev = None - self._prev_comments = None - self._pipe_cte_counter = 0 - - def parse( - self, raw_tokens: t.List[Token], sql: t.Optional[str] = None - ) -> t.List[t.Optional[exp.Expression]]: - """ - Parses a list of tokens and returns a list of syntax trees, one tree - per parsed SQL statement. - - Args: - raw_tokens: The list of tokens. - sql: The original SQL string, used to produce helpful debug messages. - - Returns: - The list of the produced syntax trees. - """ - return self._parse( - parse_method=self.__class__._parse_statement, raw_tokens=raw_tokens, sql=sql - ) - - def parse_into( - self, - expression_types: exp.IntoType, - raw_tokens: t.List[Token], - sql: t.Optional[str] = None, - ) -> t.List[t.Optional[exp.Expression]]: - """ - Parses a list of tokens into a given Expression type. If a collection of Expression - types is given instead, this method will try to parse the token list into each one - of them, stopping at the first for which the parsing succeeds. - - Args: - expression_types: The expression type(s) to try and parse the token list into. - raw_tokens: The list of tokens. - sql: The original SQL string, used to produce helpful debug messages. - - Returns: - The target Expression. - """ - errors = [] - for expression_type in ensure_list(expression_types): - parser = self.EXPRESSION_PARSERS.get(expression_type) - if not parser: - raise TypeError(f"No parser registered for {expression_type}") - - try: - return self._parse(parser, raw_tokens, sql) - except ParseError as e: - e.errors[0]["into_expression"] = expression_type - errors.append(e) - - raise ParseError( - f"Failed to parse '{sql or raw_tokens}' into {expression_types}", - errors=merge_errors(errors), - ) from errors[-1] - - def _parse( - self, - parse_method: t.Callable[[Parser], t.Optional[exp.Expression]], - raw_tokens: t.List[Token], - sql: t.Optional[str] = None, - ) -> t.List[t.Optional[exp.Expression]]: - self.reset() - self.sql = sql or "" - - total = len(raw_tokens) - chunks: t.List[t.List[Token]] = [[]] - - for i, token in enumerate(raw_tokens): - if token.token_type == TokenType.SEMICOLON: - if token.comments: - chunks.append([token]) - - if i < total - 1: - chunks.append([]) - else: - chunks[-1].append(token) - - expressions = [] - - for tokens in chunks: - self._index = -1 - self._tokens = tokens - self._advance() - - expressions.append(parse_method(self)) - - if self._index < len(self._tokens): - self.raise_error("Invalid expression / Unexpected token") - - self.check_errors() - - return expressions - - def check_errors(self) -> None: - """Logs or raises any found errors, depending on the chosen error level setting.""" - if self.error_level == ErrorLevel.WARN: - for error in self.errors: - logger.error(str(error)) - elif self.error_level == ErrorLevel.RAISE and self.errors: - raise ParseError( - concat_messages(self.errors, self.max_errors), - errors=merge_errors(self.errors), - ) - - def raise_error(self, message: str, token: t.Optional[Token] = None) -> None: - """ - Appends an error in the list of recorded errors or raises it, depending on the chosen - error level setting. - """ - token = token or self._curr or self._prev or Token.string("") - formatted_sql, start_context, highlight, end_context = highlight_sql( - sql=self.sql, - positions=[(token.start, token.end)], - context_length=self.error_message_context, - ) - formatted_message = ( - f"{message}. Line {token.line}, Col: {token.col}.\n {formatted_sql}" - ) - - error = ParseError.new( - formatted_message, - description=message, - line=token.line, - col=token.col, - start_context=start_context, - highlight=highlight, - end_context=end_context, - ) - - if self.error_level == ErrorLevel.IMMEDIATE: - raise error - - self.errors.append(error) - - def expression( - self, - exp_class: t.Type[E], - token: t.Optional[Token] = None, - comments: t.Optional[t.List[str]] = None, - **kwargs, - ) -> E: - """ - Creates a new, validated Expression. - - Args: - exp_class: The expression class to instantiate. - comments: An optional list of comments to attach to the expression. - kwargs: The arguments to set for the expression along with their respective values. - - Returns: - The target expression. - """ - if token: - instance = exp_class(this=token.text, **kwargs) - instance.update_positions(token) - else: - instance = exp_class(**kwargs) - instance.add_comments(comments) if comments else self._add_comments(instance) - return self.validate_expression(instance) - - def _add_comments(self, expression: t.Optional[exp.Expression]) -> None: - if expression and self._prev_comments: - expression.add_comments(self._prev_comments) - self._prev_comments = None - - def validate_expression(self, expression: E, args: t.Optional[t.List] = None) -> E: - """ - Validates an Expression, making sure that all its mandatory arguments are set. - - Args: - expression: The expression to validate. - args: An optional list of items that was used to instantiate the expression, if it's a Func. - - Returns: - The validated expression. - """ - if self.error_level != ErrorLevel.IGNORE: - for error_message in expression.error_messages(args): - self.raise_error(error_message) - - return expression - - def _find_sql(self, start: Token, end: Token) -> str: - return self.sql[start.start : end.end + 1] - - def _is_connected(self) -> bool: - return self._prev and self._curr and self._prev.end + 1 == self._curr.start - - def _advance(self, times: int = 1) -> None: - self._index += times - self._curr = seq_get(self._tokens, self._index) - self._next = seq_get(self._tokens, self._index + 1) - - if self._index > 0: - self._prev = self._tokens[self._index - 1] - self._prev_comments = self._prev.comments - else: - self._prev = None - self._prev_comments = None - - def _retreat(self, index: int) -> None: - if index != self._index: - self._advance(index - self._index) - - def _warn_unsupported(self) -> None: - if len(self._tokens) <= 1: - return - - # We use _find_sql because self.sql may comprise multiple chunks, and we're only - # interested in emitting a warning for the one being currently processed. - sql = self._find_sql(self._tokens[0], self._tokens[-1])[ - : self.error_message_context - ] - - logger.warning( - f"'{sql}' contains unsupported syntax. Falling back to parsing as a 'Command'." - ) - - def _parse_command(self) -> exp.Command: - self._warn_unsupported() - return self.expression( - exp.Command, - comments=self._prev_comments, - this=self._prev.text.upper(), - expression=self._parse_string(), - ) - - def _try_parse( - self, parse_method: t.Callable[[], T], retreat: bool = False - ) -> t.Optional[T]: - """ - Attemps to backtrack if a parse function that contains a try/catch internally raises an error. - This behavior can be different depending on the uset-set ErrorLevel, so _try_parse aims to - solve this by setting & resetting the parser state accordingly - """ - index = self._index - error_level = self.error_level - - self.error_level = ErrorLevel.IMMEDIATE - try: - this = parse_method() - except ParseError: - this = None - finally: - if not this or retreat: - self._retreat(index) - self.error_level = error_level - - return this - - def _parse_comment(self, allow_exists: bool = True) -> exp.Expression: - start = self._prev - exists = self._parse_exists() if allow_exists else None - - self._match(TokenType.ON) - - materialized = self._match_text_seq("MATERIALIZED") - kind = self._match_set(self.CREATABLES) and self._prev - if not kind: - return self._parse_as_command(start) - - if kind.token_type in (TokenType.FUNCTION, TokenType.PROCEDURE): - this = self._parse_user_defined_function(kind=kind.token_type) - elif kind.token_type == TokenType.TABLE: - this = self._parse_table(alias_tokens=self.COMMENT_TABLE_ALIAS_TOKENS) - elif kind.token_type == TokenType.COLUMN: - this = self._parse_column() - else: - this = self._parse_id_var() - - self._match(TokenType.IS) - - return self.expression( - exp.Comment, - this=this, - kind=kind.text, - expression=self._parse_string(), - exists=exists, - materialized=materialized, - ) - - def _parse_to_table( - self, - ) -> exp.ToTableProperty: - table = self._parse_table_parts(schema=True) - return self.expression(exp.ToTableProperty, this=table) - - # https://clickhouse.com/docs/en/engines/table-engines/mergetree-family/mergetree#mergetree-table-ttl - def _parse_ttl(self) -> exp.Expression: - def _parse_ttl_action() -> t.Optional[exp.Expression]: - this = self._parse_bitwise() - - if self._match_text_seq("DELETE"): - return self.expression(exp.MergeTreeTTLAction, this=this, delete=True) - if self._match_text_seq("RECOMPRESS"): - return self.expression( - exp.MergeTreeTTLAction, this=this, recompress=self._parse_bitwise() - ) - if self._match_text_seq("TO", "DISK"): - return self.expression( - exp.MergeTreeTTLAction, this=this, to_disk=self._parse_string() - ) - if self._match_text_seq("TO", "VOLUME"): - return self.expression( - exp.MergeTreeTTLAction, this=this, to_volume=self._parse_string() - ) - - return this - - expressions = self._parse_csv(_parse_ttl_action) - where = self._parse_where() - group = self._parse_group() - - aggregates = None - if group and self._match(TokenType.SET): - aggregates = self._parse_csv(self._parse_set_item) - - return self.expression( - exp.MergeTreeTTL, - expressions=expressions, - where=where, - group=group, - aggregates=aggregates, - ) - - def _parse_statement(self) -> t.Optional[exp.Expression]: - if self._curr is None: - return None - - if self._match_set(self.STATEMENT_PARSERS): - comments = self._prev_comments - stmt = self.STATEMENT_PARSERS[self._prev.token_type](self) - stmt.add_comments(comments, prepend=True) - return stmt - - if self._match_set(self.dialect.tokenizer_class.COMMANDS): - return self._parse_command() - - expression = self._parse_expression() - expression = ( - self._parse_set_operations(expression) - if expression - else self._parse_select() - ) - return self._parse_query_modifiers(expression) - - def _parse_drop(self, exists: bool = False) -> exp.Drop | exp.Command: - start = self._prev - temporary = self._match(TokenType.TEMPORARY) - materialized = self._match_text_seq("MATERIALIZED") - - kind = self._match_set(self.CREATABLES) and self._prev.text.upper() - if not kind: - return self._parse_as_command(start) - - concurrently = self._match_text_seq("CONCURRENTLY") - if_exists = exists or self._parse_exists() - - if kind == "COLUMN": - this = self._parse_column() - else: - this = self._parse_table_parts( - schema=True, is_db_reference=self._prev.token_type == TokenType.SCHEMA - ) - - cluster = self._parse_on_property() if self._match(TokenType.ON) else None - - if self._match(TokenType.L_PAREN, advance=False): - expressions = self._parse_wrapped_csv(self._parse_types) - else: - expressions = None - - return self.expression( - exp.Drop, - exists=if_exists, - this=this, - expressions=expressions, - kind=self.dialect.CREATABLE_KIND_MAPPING.get(kind) or kind, - temporary=temporary, - materialized=materialized, - cascade=self._match_text_seq("CASCADE"), - constraints=self._match_text_seq("CONSTRAINTS"), - purge=self._match_text_seq("PURGE"), - cluster=cluster, - concurrently=concurrently, - ) - - def _parse_exists(self, not_: bool = False) -> t.Optional[bool]: - return ( - self._match_text_seq("IF") - and (not not_ or self._match(TokenType.NOT)) - and self._match(TokenType.EXISTS) - ) - - def _parse_create(self) -> exp.Create | exp.Command: - # Note: this can't be None because we've matched a statement parser - start = self._prev - - replace = ( - start.token_type == TokenType.REPLACE - or self._match_pair(TokenType.OR, TokenType.REPLACE) - or self._match_pair(TokenType.OR, TokenType.ALTER) - ) - refresh = self._match_pair(TokenType.OR, TokenType.REFRESH) - - unique = self._match(TokenType.UNIQUE) - - if self._match_text_seq("CLUSTERED", "COLUMNSTORE"): - clustered = True - elif self._match_text_seq( - "NONCLUSTERED", "COLUMNSTORE" - ) or self._match_text_seq("COLUMNSTORE"): - clustered = False - else: - clustered = None - - if self._match_pair(TokenType.TABLE, TokenType.FUNCTION, advance=False): - self._advance() - - properties = None - create_token = self._match_set(self.CREATABLES) and self._prev - - if not create_token: - # exp.Properties.Location.POST_CREATE - properties = self._parse_properties() - create_token = self._match_set(self.CREATABLES) and self._prev - - if not properties or not create_token: - return self._parse_as_command(start) - - concurrently = self._match_text_seq("CONCURRENTLY") - exists = self._parse_exists(not_=True) - this = None - expression: t.Optional[exp.Expression] = None - indexes = None - no_schema_binding = None - begin = None - end = None - clone = None - - def extend_props(temp_props: t.Optional[exp.Properties]) -> None: - nonlocal properties - if properties and temp_props: - properties.expressions.extend(temp_props.expressions) - elif temp_props: - properties = temp_props - - if create_token.token_type in (TokenType.FUNCTION, TokenType.PROCEDURE): - this = self._parse_user_defined_function(kind=create_token.token_type) - - # exp.Properties.Location.POST_SCHEMA ("schema" here is the UDF's type signature) - extend_props(self._parse_properties()) - - expression = self._match(TokenType.ALIAS) and self._parse_heredoc() - extend_props(self._parse_properties()) - - if not expression: - if self._match(TokenType.COMMAND): - expression = self._parse_as_command(self._prev) - else: - begin = self._match(TokenType.BEGIN) - return_ = self._match_text_seq("RETURN") - - if self._match(TokenType.STRING, advance=False): - # Takes care of BigQuery's JavaScript UDF definitions that end in an OPTIONS property - # # https://cloud.google.com/bigquery/docs/reference/standard-sql/data-definition-language#create_function_statement - expression = self._parse_string() - extend_props(self._parse_properties()) - else: - expression = self._parse_user_defined_function_expression() - - end = self._match_text_seq("END") - - if return_: - expression = self.expression(exp.Return, this=expression) - elif create_token.token_type == TokenType.INDEX: - # Postgres allows anonymous indexes, eg. CREATE INDEX IF NOT EXISTS ON t(c) - if not self._match(TokenType.ON): - index = self._parse_id_var() - anonymous = False - else: - index = None - anonymous = True - - this = self._parse_index(index=index, anonymous=anonymous) - elif create_token.token_type in self.DB_CREATABLES: - table_parts = self._parse_table_parts( - schema=True, is_db_reference=create_token.token_type == TokenType.SCHEMA - ) - - # exp.Properties.Location.POST_NAME - self._match(TokenType.COMMA) - extend_props(self._parse_properties(before=True)) - - this = self._parse_schema(this=table_parts) - - # exp.Properties.Location.POST_SCHEMA and POST_WITH - extend_props(self._parse_properties()) - - has_alias = self._match(TokenType.ALIAS) - if not self._match_set(self.DDL_SELECT_TOKENS, advance=False): - # exp.Properties.Location.POST_ALIAS - extend_props(self._parse_properties()) - - if create_token.token_type == TokenType.SEQUENCE: - expression = self._parse_types() - props = self._parse_properties() - if props: - sequence_props = exp.SequenceProperties() - options = [] - for prop in props: - if isinstance(prop, exp.SequenceProperties): - for arg, value in prop.args.items(): - if arg == "options": - options.extend(value) - else: - sequence_props.set(arg, value) - prop.pop() - - if options: - sequence_props.set("options", options) - - props.append("expressions", sequence_props) - extend_props(props) - else: - expression = self._parse_ddl_select() - - # Some dialects also support using a table as an alias instead of a SELECT. - # Here we fallback to this as an alternative. - if not expression and has_alias: - expression = self._try_parse(self._parse_table_parts) - - if create_token.token_type == TokenType.TABLE: - # exp.Properties.Location.POST_EXPRESSION - extend_props(self._parse_properties()) - - indexes = [] - while True: - index = self._parse_index() - - # exp.Properties.Location.POST_INDEX - extend_props(self._parse_properties()) - if not index: - break - else: - self._match(TokenType.COMMA) - indexes.append(index) - elif create_token.token_type == TokenType.VIEW: - if self._match_text_seq("WITH", "NO", "SCHEMA", "BINDING"): - no_schema_binding = True - elif create_token.token_type in (TokenType.SINK, TokenType.SOURCE): - extend_props(self._parse_properties()) - - shallow = self._match_text_seq("SHALLOW") - - if self._match_texts(self.CLONE_KEYWORDS): - copy = self._prev.text.lower() == "copy" - clone = self.expression( - exp.Clone, - this=self._parse_table(schema=True), - shallow=shallow, - copy=copy, - ) - - if self._curr and not self._match_set( - (TokenType.R_PAREN, TokenType.COMMA), advance=False - ): - return self._parse_as_command(start) - - create_kind_text = create_token.text.upper() - return self.expression( - exp.Create, - this=this, - kind=self.dialect.CREATABLE_KIND_MAPPING.get(create_kind_text) - or create_kind_text, - replace=replace, - refresh=refresh, - unique=unique, - expression=expression, - exists=exists, - properties=properties, - indexes=indexes, - no_schema_binding=no_schema_binding, - begin=begin, - end=end, - clone=clone, - concurrently=concurrently, - clustered=clustered, - ) - - def _parse_sequence_properties(self) -> t.Optional[exp.SequenceProperties]: - seq = exp.SequenceProperties() - - options = [] - index = self._index - - while self._curr: - self._match(TokenType.COMMA) - if self._match_text_seq("INCREMENT"): - self._match_text_seq("BY") - self._match_text_seq("=") - seq.set("increment", self._parse_term()) - elif self._match_text_seq("MINVALUE"): - seq.set("minvalue", self._parse_term()) - elif self._match_text_seq("MAXVALUE"): - seq.set("maxvalue", self._parse_term()) - elif self._match(TokenType.START_WITH) or self._match_text_seq("START"): - self._match_text_seq("=") - seq.set("start", self._parse_term()) - elif self._match_text_seq("CACHE"): - # T-SQL allows empty CACHE which is initialized dynamically - seq.set("cache", self._parse_number() or True) - elif self._match_text_seq("OWNED", "BY"): - # "OWNED BY NONE" is the default - seq.set( - "owned", - None if self._match_text_seq("NONE") else self._parse_column(), - ) - else: - opt = self._parse_var_from_options( - self.CREATE_SEQUENCE, raise_unmatched=False - ) - if opt: - options.append(opt) - else: - break - - seq.set("options", options if options else None) - return None if self._index == index else seq - - def _parse_property_before(self) -> t.Optional[exp.Expression]: - # only used for teradata currently - self._match(TokenType.COMMA) - - kwargs = { - "no": self._match_text_seq("NO"), - "dual": self._match_text_seq("DUAL"), - "before": self._match_text_seq("BEFORE"), - "default": self._match_text_seq("DEFAULT"), - "local": (self._match_text_seq("LOCAL") and "LOCAL") - or (self._match_text_seq("NOT", "LOCAL") and "NOT LOCAL"), - "after": self._match_text_seq("AFTER"), - "minimum": self._match_texts(("MIN", "MINIMUM")), - "maximum": self._match_texts(("MAX", "MAXIMUM")), - } - - if self._match_texts(self.PROPERTY_PARSERS): - parser = self.PROPERTY_PARSERS[self._prev.text.upper()] - try: - return parser(self, **{k: v for k, v in kwargs.items() if v}) - except TypeError: - self.raise_error(f"Cannot parse property '{self._prev.text}'") - - return None - - def _parse_wrapped_properties(self) -> t.List[exp.Expression]: - return self._parse_wrapped_csv(self._parse_property) - - def _parse_property(self) -> t.Optional[exp.Expression]: - if self._match_texts(self.PROPERTY_PARSERS): - return self.PROPERTY_PARSERS[self._prev.text.upper()](self) - - if self._match(TokenType.DEFAULT) and self._match_texts(self.PROPERTY_PARSERS): - return self.PROPERTY_PARSERS[self._prev.text.upper()](self, default=True) - - if self._match_text_seq("COMPOUND", "SORTKEY"): - return self._parse_sortkey(compound=True) - - if self._match_text_seq("SQL", "SECURITY"): - return self.expression( - exp.SqlSecurityProperty, - this=self._match_texts(("DEFINER", "INVOKER")) - and self._prev.text.upper(), - ) - - index = self._index - - seq_props = self._parse_sequence_properties() - if seq_props: - return seq_props - - self._retreat(index) - key = self._parse_column() - - if not self._match(TokenType.EQ): - self._retreat(index) - return None - - # Transform the key to exp.Dot if it's dotted identifiers wrapped in exp.Column or to exp.Var otherwise - if isinstance(key, exp.Column): - key = key.to_dot() if len(key.parts) > 1 else exp.var(key.name) - - value = self._parse_bitwise() or self._parse_var(any_token=True) - - # Transform the value to exp.Var if it was parsed as exp.Column(exp.Identifier()) - if isinstance(value, exp.Column): - value = exp.var(value.name) - - return self.expression(exp.Property, this=key, value=value) - - def _parse_stored( - self, - ) -> t.Union[exp.FileFormatProperty, exp.StorageHandlerProperty]: - if self._match_text_seq("BY"): - return self.expression( - exp.StorageHandlerProperty, this=self._parse_var_or_string() - ) - - self._match(TokenType.ALIAS) - input_format = ( - self._parse_string() if self._match_text_seq("INPUTFORMAT") else None - ) - output_format = ( - self._parse_string() if self._match_text_seq("OUTPUTFORMAT") else None - ) - - return self.expression( - exp.FileFormatProperty, - this=( - self.expression( - exp.InputOutputFormat, - input_format=input_format, - output_format=output_format, - ) - if input_format or output_format - else self._parse_var_or_string() - or self._parse_number() - or self._parse_id_var() - ), - hive_format=True, - ) - - def _parse_unquoted_field(self) -> t.Optional[exp.Expression]: - field = self._parse_field() - if isinstance(field, exp.Identifier) and not field.quoted: - field = exp.var(field) - - return field - - def _parse_property_assignment(self, exp_class: t.Type[E], **kwargs: t.Any) -> E: - self._match(TokenType.EQ) - self._match(TokenType.ALIAS) - - return self.expression(exp_class, this=self._parse_unquoted_field(), **kwargs) - - def _parse_properties( - self, before: t.Optional[bool] = None - ) -> t.Optional[exp.Properties]: - properties = [] - while True: - if before: - prop = self._parse_property_before() - else: - prop = self._parse_property() - if not prop: - break - for p in ensure_list(prop): - properties.append(p) - - if properties: - return self.expression(exp.Properties, expressions=properties) - - return None - - def _parse_fallback(self, no: bool = False) -> exp.FallbackProperty: - return self.expression( - exp.FallbackProperty, no=no, protection=self._match_text_seq("PROTECTION") - ) - - def _parse_security(self) -> t.Optional[exp.SecurityProperty]: - if self._match_texts(("NONE", "DEFINER", "INVOKER")): - security_specifier = self._prev.text.upper() - return self.expression(exp.SecurityProperty, this=security_specifier) - return None - - def _parse_settings_property(self) -> exp.SettingsProperty: - return self.expression( - exp.SettingsProperty, expressions=self._parse_csv(self._parse_assignment) - ) - - def _parse_volatile_property(self) -> exp.VolatileProperty | exp.StabilityProperty: - if self._index >= 2: - pre_volatile_token = self._tokens[self._index - 2] - else: - pre_volatile_token = None - - if ( - pre_volatile_token - and pre_volatile_token.token_type in self.PRE_VOLATILE_TOKENS - ): - return exp.VolatileProperty() - - return self.expression( - exp.StabilityProperty, this=exp.Literal.string("VOLATILE") - ) - - def _parse_retention_period(self) -> exp.Var: - # Parse TSQL's HISTORY_RETENTION_PERIOD: {INFINITE | DAY | DAYS | MONTH ...} - number = self._parse_number() - number_str = f"{number} " if number else "" - unit = self._parse_var(any_token=True) - return exp.var(f"{number_str}{unit}") - - def _parse_system_versioning_property( - self, with_: bool = False - ) -> exp.WithSystemVersioningProperty: - self._match(TokenType.EQ) - prop = self.expression( - exp.WithSystemVersioningProperty, - on=True, - with_=with_, - ) - - if self._match_text_seq("OFF"): - prop.set("on", False) - return prop - - self._match(TokenType.ON) - if self._match(TokenType.L_PAREN): - while self._curr and not self._match(TokenType.R_PAREN): - if self._match_text_seq("HISTORY_TABLE", "="): - prop.set("this", self._parse_table_parts()) - elif self._match_text_seq("DATA_CONSISTENCY_CHECK", "="): - prop.set( - "data_consistency", - self._advance_any() and self._prev.text.upper(), - ) - elif self._match_text_seq("HISTORY_RETENTION_PERIOD", "="): - prop.set("retention_period", self._parse_retention_period()) - - self._match(TokenType.COMMA) - - return prop - - def _parse_data_deletion_property(self) -> exp.DataDeletionProperty: - self._match(TokenType.EQ) - on = self._match_text_seq("ON") or not self._match_text_seq("OFF") - prop = self.expression(exp.DataDeletionProperty, on=on) - - if self._match(TokenType.L_PAREN): - while self._curr and not self._match(TokenType.R_PAREN): - if self._match_text_seq("FILTER_COLUMN", "="): - prop.set("filter_column", self._parse_column()) - elif self._match_text_seq("RETENTION_PERIOD", "="): - prop.set("retention_period", self._parse_retention_period()) - - self._match(TokenType.COMMA) - - return prop - - def _parse_distributed_property(self) -> exp.DistributedByProperty: - kind = "HASH" - expressions: t.Optional[t.List[exp.Expression]] = None - if self._match_text_seq("BY", "HASH"): - expressions = self._parse_wrapped_csv(self._parse_id_var) - elif self._match_text_seq("BY", "RANDOM"): - kind = "RANDOM" - - # If the BUCKETS keyword is not present, the number of buckets is AUTO - buckets: t.Optional[exp.Expression] = None - if self._match_text_seq("BUCKETS") and not self._match_text_seq("AUTO"): - buckets = self._parse_number() - - return self.expression( - exp.DistributedByProperty, - expressions=expressions, - kind=kind, - buckets=buckets, - order=self._parse_order(), - ) - - def _parse_composite_key_property(self, expr_type: t.Type[E]) -> E: - self._match_text_seq("KEY") - expressions = self._parse_wrapped_id_vars() - return self.expression(expr_type, expressions=expressions) - - def _parse_with_property( - self, - ) -> t.Optional[exp.Expression] | t.List[exp.Expression]: - if self._match_text_seq("(", "SYSTEM_VERSIONING"): - prop = self._parse_system_versioning_property(with_=True) - self._match_r_paren() - return prop - - if self._match(TokenType.L_PAREN, advance=False): - return self._parse_wrapped_properties() - - if self._match_text_seq("JOURNAL"): - return self._parse_withjournaltable() - - if self._match_texts(self.VIEW_ATTRIBUTES): - return self.expression( - exp.ViewAttributeProperty, this=self._prev.text.upper() - ) - - if self._match_text_seq("DATA"): - return self._parse_withdata(no=False) - elif self._match_text_seq("NO", "DATA"): - return self._parse_withdata(no=True) - - if self._match(TokenType.SERDE_PROPERTIES, advance=False): - return self._parse_serde_properties(with_=True) - - if self._match(TokenType.SCHEMA): - return self.expression( - exp.WithSchemaBindingProperty, - this=self._parse_var_from_options(self.SCHEMA_BINDING_OPTIONS), - ) - - if self._match_texts(self.PROCEDURE_OPTIONS, advance=False): - return self.expression( - exp.WithProcedureOptions, - expressions=self._parse_csv(self._parse_procedure_option), - ) - - if not self._next: - return None - - return self._parse_withisolatedloading() - - def _parse_procedure_option(self) -> exp.Expression | None: - if self._match_text_seq("EXECUTE", "AS"): - return self.expression( - exp.ExecuteAsProperty, - this=self._parse_var_from_options( - self.EXECUTE_AS_OPTIONS, raise_unmatched=False - ) - or self._parse_string(), - ) - - return self._parse_var_from_options(self.PROCEDURE_OPTIONS) - - # https://dev.mysql.com/doc/refman/8.0/en/create-view.html - def _parse_definer(self) -> t.Optional[exp.DefinerProperty]: - self._match(TokenType.EQ) - - user = self._parse_id_var() - self._match(TokenType.PARAMETER) - host = self._parse_id_var() or (self._match(TokenType.MOD) and self._prev.text) - - if not user or not host: - return None - - return exp.DefinerProperty(this=f"{user}@{host}") - - def _parse_withjournaltable(self) -> exp.WithJournalTableProperty: - self._match(TokenType.TABLE) - self._match(TokenType.EQ) - return self.expression( - exp.WithJournalTableProperty, this=self._parse_table_parts() - ) - - def _parse_log(self, no: bool = False) -> exp.LogProperty: - return self.expression(exp.LogProperty, no=no) - - def _parse_journal(self, **kwargs) -> exp.JournalProperty: - return self.expression(exp.JournalProperty, **kwargs) - - def _parse_checksum(self) -> exp.ChecksumProperty: - self._match(TokenType.EQ) - - on = None - if self._match(TokenType.ON): - on = True - elif self._match_text_seq("OFF"): - on = False - - return self.expression( - exp.ChecksumProperty, on=on, default=self._match(TokenType.DEFAULT) - ) - - def _parse_cluster(self, wrapped: bool = False) -> exp.Cluster: - return self.expression( - exp.Cluster, - expressions=( - self._parse_wrapped_csv(self._parse_ordered) - if wrapped - else self._parse_csv(self._parse_ordered) - ), - ) - - def _parse_clustered_by(self) -> exp.ClusteredByProperty: - self._match_text_seq("BY") - - self._match_l_paren() - expressions = self._parse_csv(self._parse_column) - self._match_r_paren() - - if self._match_text_seq("SORTED", "BY"): - self._match_l_paren() - sorted_by = self._parse_csv(self._parse_ordered) - self._match_r_paren() - else: - sorted_by = None - - self._match(TokenType.INTO) - buckets = self._parse_number() - self._match_text_seq("BUCKETS") - - return self.expression( - exp.ClusteredByProperty, - expressions=expressions, - sorted_by=sorted_by, - buckets=buckets, - ) - - def _parse_copy_property(self) -> t.Optional[exp.CopyGrantsProperty]: - if not self._match_text_seq("GRANTS"): - self._retreat(self._index - 1) - return None - - return self.expression(exp.CopyGrantsProperty) - - def _parse_freespace(self) -> exp.FreespaceProperty: - self._match(TokenType.EQ) - return self.expression( - exp.FreespaceProperty, - this=self._parse_number(), - percent=self._match(TokenType.PERCENT), - ) - - def _parse_mergeblockratio( - self, no: bool = False, default: bool = False - ) -> exp.MergeBlockRatioProperty: - if self._match(TokenType.EQ): - return self.expression( - exp.MergeBlockRatioProperty, - this=self._parse_number(), - percent=self._match(TokenType.PERCENT), - ) - - return self.expression(exp.MergeBlockRatioProperty, no=no, default=default) - - def _parse_datablocksize( - self, - default: t.Optional[bool] = None, - minimum: t.Optional[bool] = None, - maximum: t.Optional[bool] = None, - ) -> exp.DataBlocksizeProperty: - self._match(TokenType.EQ) - size = self._parse_number() - - units = None - if self._match_texts(("BYTES", "KBYTES", "KILOBYTES")): - units = self._prev.text - - return self.expression( - exp.DataBlocksizeProperty, - size=size, - units=units, - default=default, - minimum=minimum, - maximum=maximum, - ) - - def _parse_blockcompression(self) -> exp.BlockCompressionProperty: - self._match(TokenType.EQ) - always = self._match_text_seq("ALWAYS") - manual = self._match_text_seq("MANUAL") - never = self._match_text_seq("NEVER") - default = self._match_text_seq("DEFAULT") - - autotemp = None - if self._match_text_seq("AUTOTEMP"): - autotemp = self._parse_schema() - - return self.expression( - exp.BlockCompressionProperty, - always=always, - manual=manual, - never=never, - default=default, - autotemp=autotemp, - ) - - def _parse_withisolatedloading(self) -> t.Optional[exp.IsolatedLoadingProperty]: - index = self._index - no = self._match_text_seq("NO") - concurrent = self._match_text_seq("CONCURRENT") - - if not self._match_text_seq("ISOLATED", "LOADING"): - self._retreat(index) - return None - - target = self._parse_var_from_options( - self.ISOLATED_LOADING_OPTIONS, raise_unmatched=False - ) - return self.expression( - exp.IsolatedLoadingProperty, no=no, concurrent=concurrent, target=target - ) - - def _parse_locking(self) -> exp.LockingProperty: - if self._match(TokenType.TABLE): - kind = "TABLE" - elif self._match(TokenType.VIEW): - kind = "VIEW" - elif self._match(TokenType.ROW): - kind = "ROW" - elif self._match_text_seq("DATABASE"): - kind = "DATABASE" - else: - kind = None - - if kind in ("DATABASE", "TABLE", "VIEW"): - this = self._parse_table_parts() - else: - this = None - - if self._match(TokenType.FOR): - for_or_in = "FOR" - elif self._match(TokenType.IN): - for_or_in = "IN" - else: - for_or_in = None - - if self._match_text_seq("ACCESS"): - lock_type = "ACCESS" - elif self._match_texts(("EXCL", "EXCLUSIVE")): - lock_type = "EXCLUSIVE" - elif self._match_text_seq("SHARE"): - lock_type = "SHARE" - elif self._match_text_seq("READ"): - lock_type = "READ" - elif self._match_text_seq("WRITE"): - lock_type = "WRITE" - elif self._match_text_seq("CHECKSUM"): - lock_type = "CHECKSUM" - else: - lock_type = None - - override = self._match_text_seq("OVERRIDE") - - return self.expression( - exp.LockingProperty, - this=this, - kind=kind, - for_or_in=for_or_in, - lock_type=lock_type, - override=override, - ) - - def _parse_partition_by(self) -> t.List[exp.Expression]: - if self._match(TokenType.PARTITION_BY): - return self._parse_csv(self._parse_disjunction) - return [] - - def _parse_partition_bound_spec(self) -> exp.PartitionBoundSpec: - def _parse_partition_bound_expr() -> t.Optional[exp.Expression]: - if self._match_text_seq("MINVALUE"): - return exp.var("MINVALUE") - if self._match_text_seq("MAXVALUE"): - return exp.var("MAXVALUE") - return self._parse_bitwise() - - this: t.Optional[exp.Expression | t.List[exp.Expression]] = None - expression = None - from_expressions = None - to_expressions = None - - if self._match(TokenType.IN): - this = self._parse_wrapped_csv(self._parse_bitwise) - elif self._match(TokenType.FROM): - from_expressions = self._parse_wrapped_csv(_parse_partition_bound_expr) - self._match_text_seq("TO") - to_expressions = self._parse_wrapped_csv(_parse_partition_bound_expr) - elif self._match_text_seq("WITH", "(", "MODULUS"): - this = self._parse_number() - self._match_text_seq(",", "REMAINDER") - expression = self._parse_number() - self._match_r_paren() - else: - self.raise_error("Failed to parse partition bound spec.") - - return self.expression( - exp.PartitionBoundSpec, - this=this, - expression=expression, - from_expressions=from_expressions, - to_expressions=to_expressions, - ) - - # https://www.postgresql.org/docs/current/sql-createtable.html - def _parse_partitioned_of(self) -> t.Optional[exp.PartitionedOfProperty]: - if not self._match_text_seq("OF"): - self._retreat(self._index - 1) - return None - - this = self._parse_table(schema=True) - - if self._match(TokenType.DEFAULT): - expression: exp.Var | exp.PartitionBoundSpec = exp.var("DEFAULT") - elif self._match_text_seq("FOR", "VALUES"): - expression = self._parse_partition_bound_spec() - else: - self.raise_error("Expecting either DEFAULT or FOR VALUES clause.") - - return self.expression( - exp.PartitionedOfProperty, this=this, expression=expression - ) - - def _parse_partitioned_by(self) -> exp.PartitionedByProperty: - self._match(TokenType.EQ) - return self.expression( - exp.PartitionedByProperty, - this=self._parse_schema() or self._parse_bracket(self._parse_field()), - ) - - def _parse_withdata(self, no: bool = False) -> exp.WithDataProperty: - if self._match_text_seq("AND", "STATISTICS"): - statistics = True - elif self._match_text_seq("AND", "NO", "STATISTICS"): - statistics = False - else: - statistics = None - - return self.expression(exp.WithDataProperty, no=no, statistics=statistics) - - def _parse_contains_property(self) -> t.Optional[exp.SqlReadWriteProperty]: - if self._match_text_seq("SQL"): - return self.expression(exp.SqlReadWriteProperty, this="CONTAINS SQL") - return None - - def _parse_modifies_property(self) -> t.Optional[exp.SqlReadWriteProperty]: - if self._match_text_seq("SQL", "DATA"): - return self.expression(exp.SqlReadWriteProperty, this="MODIFIES SQL DATA") - return None - - def _parse_no_property(self) -> t.Optional[exp.Expression]: - if self._match_text_seq("PRIMARY", "INDEX"): - return exp.NoPrimaryIndexProperty() - if self._match_text_seq("SQL"): - return self.expression(exp.SqlReadWriteProperty, this="NO SQL") - return None - - def _parse_on_property(self) -> t.Optional[exp.Expression]: - if self._match_text_seq("COMMIT", "PRESERVE", "ROWS"): - return exp.OnCommitProperty() - if self._match_text_seq("COMMIT", "DELETE", "ROWS"): - return exp.OnCommitProperty(delete=True) - return self.expression( - exp.OnProperty, this=self._parse_schema(self._parse_id_var()) - ) - - def _parse_reads_property(self) -> t.Optional[exp.SqlReadWriteProperty]: - if self._match_text_seq("SQL", "DATA"): - return self.expression(exp.SqlReadWriteProperty, this="READS SQL DATA") - return None - - def _parse_distkey(self) -> exp.DistKeyProperty: - return self.expression( - exp.DistKeyProperty, this=self._parse_wrapped(self._parse_id_var) - ) - - def _parse_create_like(self) -> t.Optional[exp.LikeProperty]: - table = self._parse_table(schema=True) - - options = [] - while self._match_texts(("INCLUDING", "EXCLUDING")): - this = self._prev.text.upper() - - id_var = self._parse_id_var() - if not id_var: - return None - - options.append( - self.expression( - exp.Property, this=this, value=exp.var(id_var.this.upper()) - ) - ) - - return self.expression(exp.LikeProperty, this=table, expressions=options) - - def _parse_sortkey(self, compound: bool = False) -> exp.SortKeyProperty: - return self.expression( - exp.SortKeyProperty, this=self._parse_wrapped_id_vars(), compound=compound - ) - - def _parse_character_set(self, default: bool = False) -> exp.CharacterSetProperty: - self._match(TokenType.EQ) - return self.expression( - exp.CharacterSetProperty, this=self._parse_var_or_string(), default=default - ) - - def _parse_remote_with_connection(self) -> exp.RemoteWithConnectionModelProperty: - self._match_text_seq("WITH", "CONNECTION") - return self.expression( - exp.RemoteWithConnectionModelProperty, this=self._parse_table_parts() - ) - - def _parse_returns(self) -> exp.ReturnsProperty: - value: t.Optional[exp.Expression] - null = None - is_table = self._match(TokenType.TABLE) - - if is_table: - if self._match(TokenType.LT): - value = self.expression( - exp.Schema, - this="TABLE", - expressions=self._parse_csv(self._parse_struct_types), - ) - if not self._match(TokenType.GT): - self.raise_error("Expecting >") - else: - value = self._parse_schema(exp.var("TABLE")) - elif self._match_text_seq("NULL", "ON", "NULL", "INPUT"): - null = True - value = None - else: - value = self._parse_types() - - return self.expression( - exp.ReturnsProperty, this=value, is_table=is_table, null=null - ) - - def _parse_describe(self) -> exp.Describe: - kind = self._match_set(self.CREATABLES) and self._prev.text - style = self._match_texts(self.DESCRIBE_STYLES) and self._prev.text.upper() - if self._match(TokenType.DOT): - style = None - self._retreat(self._index - 2) - - format = ( - self._parse_property() - if self._match(TokenType.FORMAT, advance=False) - else None - ) - - if self._match_set(self.STATEMENT_PARSERS, advance=False): - this = self._parse_statement() - else: - this = self._parse_table(schema=True) - - properties = self._parse_properties() - expressions = properties.expressions if properties else None - partition = self._parse_partition() - return self.expression( - exp.Describe, - this=this, - style=style, - kind=kind, - expressions=expressions, - partition=partition, - format=format, - ) - - def _parse_multitable_inserts( - self, comments: t.Optional[t.List[str]] - ) -> exp.MultitableInserts: - kind = self._prev.text.upper() - expressions = [] - - def parse_conditional_insert() -> t.Optional[exp.ConditionalInsert]: - if self._match(TokenType.WHEN): - expression = self._parse_disjunction() - self._match(TokenType.THEN) - else: - expression = None - - else_ = self._match(TokenType.ELSE) - - if not self._match(TokenType.INTO): - return None - - return self.expression( - exp.ConditionalInsert, - this=self.expression( - exp.Insert, - this=self._parse_table(schema=True), - expression=self._parse_derived_table_values(), - ), - expression=expression, - else_=else_, - ) - - expression = parse_conditional_insert() - while expression is not None: - expressions.append(expression) - expression = parse_conditional_insert() - - return self.expression( - exp.MultitableInserts, - kind=kind, - comments=comments, - expressions=expressions, - source=self._parse_table(), - ) - - def _parse_insert(self) -> t.Union[exp.Insert, exp.MultitableInserts]: - comments = [] - hint = self._parse_hint() - overwrite = self._match(TokenType.OVERWRITE) - ignore = self._match(TokenType.IGNORE) - local = self._match_text_seq("LOCAL") - alternative = None - is_function = None - - if self._match_text_seq("DIRECTORY"): - this: t.Optional[exp.Expression] = self.expression( - exp.Directory, - this=self._parse_var_or_string(), - local=local, - row_format=self._parse_row_format(match_row=True), - ) - else: - if self._match_set((TokenType.FIRST, TokenType.ALL)): - comments += ensure_list(self._prev_comments) - return self._parse_multitable_inserts(comments) - - if self._match(TokenType.OR): - alternative = ( - self._match_texts(self.INSERT_ALTERNATIVES) and self._prev.text - ) - - self._match(TokenType.INTO) - comments += ensure_list(self._prev_comments) - self._match(TokenType.TABLE) - is_function = self._match(TokenType.FUNCTION) - - this = self._parse_function() if is_function else self._parse_insert_table() - - returning = self._parse_returning() # TSQL allows RETURNING before source - - return self.expression( - exp.Insert, - comments=comments, - hint=hint, - is_function=is_function, - this=this, - stored=self._match_text_seq("STORED") and self._parse_stored(), - by_name=self._match_text_seq("BY", "NAME"), - exists=self._parse_exists(), - where=self._match_pair(TokenType.REPLACE, TokenType.WHERE) - and self._parse_disjunction(), - partition=self._match(TokenType.PARTITION_BY) - and self._parse_partitioned_by(), - settings=self._match_text_seq("SETTINGS") - and self._parse_settings_property(), - default=self._match_text_seq("DEFAULT", "VALUES"), - expression=self._parse_derived_table_values() or self._parse_ddl_select(), - conflict=self._parse_on_conflict(), - returning=returning or self._parse_returning(), - overwrite=overwrite, - alternative=alternative, - ignore=ignore, - source=self._match(TokenType.TABLE) and self._parse_table(), - ) - - def _parse_insert_table(self) -> t.Optional[exp.Expression]: - this = self._parse_table(schema=True, parse_partition=True) - if isinstance(this, exp.Table) and self._match(TokenType.ALIAS, advance=False): - this.set("alias", self._parse_table_alias()) - return this - - def _parse_kill(self) -> exp.Kill: - kind = ( - exp.var(self._prev.text) - if self._match_texts(("CONNECTION", "QUERY")) - else None - ) - - return self.expression( - exp.Kill, - this=self._parse_primary(), - kind=kind, - ) - - def _parse_on_conflict(self) -> t.Optional[exp.OnConflict]: - conflict = self._match_text_seq("ON", "CONFLICT") - duplicate = self._match_text_seq("ON", "DUPLICATE", "KEY") - - if not conflict and not duplicate: - return None - - conflict_keys = None - constraint = None - - if conflict: - if self._match_text_seq("ON", "CONSTRAINT"): - constraint = self._parse_id_var() - elif self._match(TokenType.L_PAREN): - conflict_keys = self._parse_csv(self._parse_id_var) - self._match_r_paren() - - action = self._parse_var_from_options(self.CONFLICT_ACTIONS) - if self._prev.token_type == TokenType.UPDATE: - self._match(TokenType.SET) - expressions = self._parse_csv(self._parse_equality) - else: - expressions = None - - return self.expression( - exp.OnConflict, - duplicate=duplicate, - expressions=expressions, - action=action, - conflict_keys=conflict_keys, - constraint=constraint, - where=self._parse_where(), - ) - - def _parse_returning(self) -> t.Optional[exp.Returning]: - if not self._match(TokenType.RETURNING): - return None - return self.expression( - exp.Returning, - expressions=self._parse_csv(self._parse_expression), - into=self._match(TokenType.INTO) and self._parse_table_part(), - ) - - def _parse_row( - self, - ) -> t.Optional[exp.RowFormatSerdeProperty | exp.RowFormatDelimitedProperty]: - if not self._match(TokenType.FORMAT): - return None - return self._parse_row_format() - - def _parse_serde_properties( - self, with_: bool = False - ) -> t.Optional[exp.SerdeProperties]: - index = self._index - with_ = with_ or self._match_text_seq("WITH") - - if not self._match(TokenType.SERDE_PROPERTIES): - self._retreat(index) - return None - return self.expression( - exp.SerdeProperties, - expressions=self._parse_wrapped_properties(), - with_=with_, - ) - - def _parse_row_format( - self, match_row: bool = False - ) -> t.Optional[exp.RowFormatSerdeProperty | exp.RowFormatDelimitedProperty]: - if match_row and not self._match_pair(TokenType.ROW, TokenType.FORMAT): - return None - - if self._match_text_seq("SERDE"): - this = self._parse_string() - - serde_properties = self._parse_serde_properties() - - return self.expression( - exp.RowFormatSerdeProperty, this=this, serde_properties=serde_properties - ) - - self._match_text_seq("DELIMITED") - - kwargs = {} - - if self._match_text_seq("FIELDS", "TERMINATED", "BY"): - kwargs["fields"] = self._parse_string() - if self._match_text_seq("ESCAPED", "BY"): - kwargs["escaped"] = self._parse_string() - if self._match_text_seq("COLLECTION", "ITEMS", "TERMINATED", "BY"): - kwargs["collection_items"] = self._parse_string() - if self._match_text_seq("MAP", "KEYS", "TERMINATED", "BY"): - kwargs["map_keys"] = self._parse_string() - if self._match_text_seq("LINES", "TERMINATED", "BY"): - kwargs["lines"] = self._parse_string() - if self._match_text_seq("NULL", "DEFINED", "AS"): - kwargs["null"] = self._parse_string() - - return self.expression(exp.RowFormatDelimitedProperty, **kwargs) # type: ignore - - def _parse_load(self) -> exp.LoadData | exp.Command: - if self._match_text_seq("DATA"): - local = self._match_text_seq("LOCAL") - self._match_text_seq("INPATH") - inpath = self._parse_string() - overwrite = self._match(TokenType.OVERWRITE) - self._match_pair(TokenType.INTO, TokenType.TABLE) - - return self.expression( - exp.LoadData, - this=self._parse_table(schema=True), - local=local, - overwrite=overwrite, - inpath=inpath, - partition=self._parse_partition(), - input_format=self._match_text_seq("INPUTFORMAT") - and self._parse_string(), - serde=self._match_text_seq("SERDE") and self._parse_string(), - ) - return self._parse_as_command(self._prev) - - def _parse_delete(self) -> exp.Delete: - # This handles MySQL's "Multiple-Table Syntax" - # https://dev.mysql.com/doc/refman/8.0/en/delete.html - tables = None - if not self._match(TokenType.FROM, advance=False): - tables = self._parse_csv(self._parse_table) or None - - returning = self._parse_returning() - - return self.expression( - exp.Delete, - tables=tables, - this=self._match(TokenType.FROM) and self._parse_table(joins=True), - using=self._match(TokenType.USING) - and self._parse_csv(lambda: self._parse_table(joins=True)), - cluster=self._match(TokenType.ON) and self._parse_on_property(), - where=self._parse_where(), - returning=returning or self._parse_returning(), - order=self._parse_order(), - limit=self._parse_limit(), - ) - - def _parse_update(self) -> exp.Update: - kwargs: t.Dict[str, t.Any] = { - "this": self._parse_table( - joins=True, alias_tokens=self.UPDATE_ALIAS_TOKENS - ), - } - while self._curr: - if self._match(TokenType.SET): - kwargs["expressions"] = self._parse_csv(self._parse_equality) - elif self._match(TokenType.RETURNING, advance=False): - kwargs["returning"] = self._parse_returning() - elif self._match(TokenType.FROM, advance=False): - kwargs["from_"] = self._parse_from(joins=True) - elif self._match(TokenType.WHERE, advance=False): - kwargs["where"] = self._parse_where() - elif self._match(TokenType.ORDER_BY, advance=False): - kwargs["order"] = self._parse_order() - elif self._match(TokenType.LIMIT, advance=False): - kwargs["limit"] = self._parse_limit() - else: - break - - return self.expression(exp.Update, **kwargs) - - def _parse_use(self) -> exp.Use: - return self.expression( - exp.Use, - kind=self._parse_var_from_options(self.USABLES, raise_unmatched=False), - this=self._parse_table(schema=False), - ) - - def _parse_uncache(self) -> exp.Uncache: - if not self._match(TokenType.TABLE): - self.raise_error("Expecting TABLE after UNCACHE") - - return self.expression( - exp.Uncache, - exists=self._parse_exists(), - this=self._parse_table(schema=True), - ) - - def _parse_cache(self) -> exp.Cache: - lazy = self._match_text_seq("LAZY") - self._match(TokenType.TABLE) - table = self._parse_table(schema=True) - - options = [] - if self._match_text_seq("OPTIONS"): - self._match_l_paren() - k = self._parse_string() - self._match(TokenType.EQ) - v = self._parse_string() - options = [k, v] - self._match_r_paren() - - self._match(TokenType.ALIAS) - return self.expression( - exp.Cache, - this=table, - lazy=lazy, - options=options, - expression=self._parse_select(nested=True), - ) - - def _parse_partition(self) -> t.Optional[exp.Partition]: - if not self._match_texts(self.PARTITION_KEYWORDS): - return None - - return self.expression( - exp.Partition, - subpartition=self._prev.text.upper() == "SUBPARTITION", - expressions=self._parse_wrapped_csv(self._parse_disjunction), - ) - - def _parse_value(self, values: bool = True) -> t.Optional[exp.Tuple]: - def _parse_value_expression() -> t.Optional[exp.Expression]: - if self.dialect.SUPPORTS_VALUES_DEFAULT and self._match(TokenType.DEFAULT): - return exp.var(self._prev.text.upper()) - return self._parse_expression() - - if self._match(TokenType.L_PAREN): - expressions = self._parse_csv(_parse_value_expression) - self._match_r_paren() - return self.expression(exp.Tuple, expressions=expressions) - - # In some dialects we can have VALUES 1, 2 which results in 1 column & 2 rows. - expression = self._parse_expression() - if expression: - return self.expression(exp.Tuple, expressions=[expression]) - return None - - def _parse_projections(self) -> t.List[exp.Expression]: - return self._parse_expressions() - - def _parse_wrapped_select(self, table: bool = False) -> t.Optional[exp.Expression]: - if self._match_set((TokenType.PIVOT, TokenType.UNPIVOT)): - this: t.Optional[exp.Expression] = self._parse_simplified_pivot( - is_unpivot=self._prev.token_type == TokenType.UNPIVOT - ) - elif self._match(TokenType.FROM): - from_ = self._parse_from(skip_from_token=True, consume_pipe=True) - # Support parentheses for duckdb FROM-first syntax - select = self._parse_select(from_=from_) - if select: - if not select.args.get("from_"): - select.set("from_", from_) - this = select - else: - this = exp.select("*").from_(t.cast(exp.From, from_)) - this = self._parse_query_modifiers(self._parse_set_operations(this)) - else: - this = ( - self._parse_table(consume_pipe=True) - if table - else self._parse_select(nested=True, parse_set_operation=False) - ) - - # Transform exp.Values into a exp.Table to pass through parse_query_modifiers - # in case a modifier (e.g. join) is following - if table and isinstance(this, exp.Values) and this.alias: - alias = this.args["alias"].pop() - this = exp.Table(this=this, alias=alias) - - this = self._parse_query_modifiers(self._parse_set_operations(this)) - - return this - - def _parse_select( - self, - nested: bool = False, - table: bool = False, - parse_subquery_alias: bool = True, - parse_set_operation: bool = True, - consume_pipe: bool = True, - from_: t.Optional[exp.From] = None, - ) -> t.Optional[exp.Expression]: - query = self._parse_select_query( - nested=nested, - table=table, - parse_subquery_alias=parse_subquery_alias, - parse_set_operation=parse_set_operation, - ) - - if consume_pipe and self._match(TokenType.PIPE_GT, advance=False): - if not query and from_: - query = exp.select("*").from_(from_) - if isinstance(query, exp.Query): - query = self._parse_pipe_syntax_query(query) - query = query.subquery(copy=False) if query and table else query - - return query - - def _parse_select_query( - self, - nested: bool = False, - table: bool = False, - parse_subquery_alias: bool = True, - parse_set_operation: bool = True, - ) -> t.Optional[exp.Expression]: - cte = self._parse_with() - - if cte: - this = self._parse_statement() - - if not this: - self.raise_error("Failed to parse any statement following CTE") - return cte - - while isinstance(this, exp.Subquery) and this.is_wrapper: - this = this.this - - if "with_" in this.arg_types: - this.set("with_", cte) - else: - self.raise_error(f"{this.key} does not support CTE") - this = cte - - return this - - # duckdb supports leading with FROM x - from_ = ( - self._parse_from(joins=True, consume_pipe=True) - if self._match(TokenType.FROM, advance=False) - else None - ) - - if self._match(TokenType.SELECT): - comments = self._prev_comments - - hint = self._parse_hint() - - if self._next and not self._next.token_type == TokenType.DOT: - all_ = self._match(TokenType.ALL) - distinct = self._match_set(self.DISTINCT_TOKENS) - else: - all_, distinct = None, None - - kind = ( - self._match(TokenType.ALIAS) - and self._match_texts(("STRUCT", "VALUE")) - and self._prev.text.upper() - ) - - if distinct: - distinct = self.expression( - exp.Distinct, - on=self._parse_value(values=False) - if self._match(TokenType.ON) - else None, - ) - - if all_ and distinct: - self.raise_error("Cannot specify both ALL and DISTINCT after SELECT") - - operation_modifiers = [] - while self._curr and self._match_texts(self.OPERATION_MODIFIERS): - operation_modifiers.append(exp.var(self._prev.text.upper())) - - limit = self._parse_limit(top=True) - projections = self._parse_projections() - - this = self.expression( - exp.Select, - kind=kind, - hint=hint, - distinct=distinct, - expressions=projections, - limit=limit, - operation_modifiers=operation_modifiers or None, - ) - this.comments = comments - - into = self._parse_into() - if into: - this.set("into", into) - - if not from_: - from_ = self._parse_from() - - if from_: - this.set("from_", from_) - - this = self._parse_query_modifiers(this) - elif (table or nested) and self._match(TokenType.L_PAREN): - this = self._parse_wrapped_select(table=table) - - # We return early here so that the UNION isn't attached to the subquery by the - # following call to _parse_set_operations, but instead becomes the parent node - self._match_r_paren() - return self._parse_subquery(this, parse_alias=parse_subquery_alias) - elif self._match(TokenType.VALUES, advance=False): - this = self._parse_derived_table_values() - elif from_: - this = exp.select("*").from_(from_.this, copy=False) - elif self._match(TokenType.SUMMARIZE): - table = self._match(TokenType.TABLE) - this = self._parse_select() or self._parse_string() or self._parse_table() - return self.expression(exp.Summarize, this=this, table=table) - elif self._match(TokenType.DESCRIBE): - this = self._parse_describe() - else: - this = None - - return self._parse_set_operations(this) if parse_set_operation else this - - def _parse_recursive_with_search(self) -> t.Optional[exp.RecursiveWithSearch]: - self._match_text_seq("SEARCH") - - kind = ( - self._match_texts(self.RECURSIVE_CTE_SEARCH_KIND) - and self._prev.text.upper() - ) - - if not kind: - return None - - self._match_text_seq("FIRST", "BY") - - return self.expression( - exp.RecursiveWithSearch, - kind=kind, - this=self._parse_id_var(), - expression=self._match_text_seq("SET") and self._parse_id_var(), - using=self._match_text_seq("USING") and self._parse_id_var(), - ) - - def _parse_with(self, skip_with_token: bool = False) -> t.Optional[exp.With]: - if not skip_with_token and not self._match(TokenType.WITH): - return None - - comments = self._prev_comments - recursive = self._match(TokenType.RECURSIVE) - - last_comments = None - expressions = [] - while True: - cte = self._parse_cte() - if isinstance(cte, exp.CTE): - expressions.append(cte) - if last_comments: - cte.add_comments(last_comments) - - if not self._match(TokenType.COMMA) and not self._match(TokenType.WITH): - break - else: - self._match(TokenType.WITH) - - last_comments = self._prev_comments - - return self.expression( - exp.With, - comments=comments, - expressions=expressions, - recursive=recursive, - search=self._parse_recursive_with_search(), - ) - - def _parse_cte(self) -> t.Optional[exp.CTE]: - index = self._index - - alias = self._parse_table_alias(self.ID_VAR_TOKENS) - if not alias or not alias.this: - self.raise_error("Expected CTE to have alias") - - key_expressions = ( - self._parse_wrapped_id_vars() - if self._match_text_seq("USING", "KEY") - else None - ) - - if not self._match(TokenType.ALIAS) and not self.OPTIONAL_ALIAS_TOKEN_CTE: - self._retreat(index) - return None - - comments = self._prev_comments - - if self._match_text_seq("NOT", "MATERIALIZED"): - materialized = False - elif self._match_text_seq("MATERIALIZED"): - materialized = True - else: - materialized = None - - cte = self.expression( - exp.CTE, - this=self._parse_wrapped(self._parse_statement), - alias=alias, - materialized=materialized, - key_expressions=key_expressions, - comments=comments, - ) - - values = cte.this - if isinstance(values, exp.Values): - if values.alias: - cte.set("this", exp.select("*").from_(values)) - else: - cte.set( - "this", - exp.select("*").from_(exp.alias_(values, "_values", table=True)), - ) - - return cte - - def _parse_table_alias( - self, alias_tokens: t.Optional[t.Collection[TokenType]] = None - ) -> t.Optional[exp.TableAlias]: - # In some dialects, LIMIT and OFFSET can act as both identifiers and keywords (clauses) - # so this section tries to parse the clause version and if it fails, it treats the token - # as an identifier (alias) - if self._can_parse_limit_or_offset(): - return None - - any_token = self._match(TokenType.ALIAS) - alias = ( - self._parse_id_var( - any_token=any_token, tokens=alias_tokens or self.TABLE_ALIAS_TOKENS - ) - or self._parse_string_as_identifier() - ) - - index = self._index - if self._match(TokenType.L_PAREN): - columns = self._parse_csv(self._parse_function_parameter) - self._match_r_paren() if columns else self._retreat(index) - else: - columns = None - - if not alias and not columns: - return None - - table_alias = self.expression(exp.TableAlias, this=alias, columns=columns) - - # We bubble up comments from the Identifier to the TableAlias - if isinstance(alias, exp.Identifier): - table_alias.add_comments(alias.pop_comments()) - - return table_alias - - def _parse_subquery( - self, this: t.Optional[exp.Expression], parse_alias: bool = True - ) -> t.Optional[exp.Subquery]: - if not this: - return None - - return self.expression( - exp.Subquery, - this=this, - pivots=self._parse_pivots(), - alias=self._parse_table_alias() if parse_alias else None, - sample=self._parse_table_sample(), - ) - - def _implicit_unnests_to_explicit(self, this: E) -> E: - from bigframes_vendored.sqlglot.optimizer.normalize_identifiers import ( - normalize_identifiers as _norm, - ) - - refs = { - _norm(this.args["from_"].this.copy(), dialect=self.dialect).alias_or_name - } - for i, join in enumerate(this.args.get("joins") or []): - table = join.this - normalized_table = table.copy() - normalized_table.meta["maybe_column"] = True - normalized_table = _norm(normalized_table, dialect=self.dialect) - - if isinstance(table, exp.Table) and not join.args.get("on"): - if normalized_table.parts[0].name in refs: - table_as_column = table.to_column() - unnest = exp.Unnest(expressions=[table_as_column]) - - # Table.to_column creates a parent Alias node that we want to convert to - # a TableAlias and attach to the Unnest, so it matches the parser's output - if isinstance(table.args.get("alias"), exp.TableAlias): - table_as_column.replace(table_as_column.this) - exp.alias_( - unnest, None, table=[table.args["alias"].this], copy=False - ) - - table.replace(unnest) - - refs.add(normalized_table.alias_or_name) - - return this - - @t.overload - def _parse_query_modifiers(self, this: E) -> E: ... - - @t.overload - def _parse_query_modifiers(self, this: None) -> None: ... - - def _parse_query_modifiers(self, this): - if isinstance(this, self.MODIFIABLES): - for join in self._parse_joins(): - this.append("joins", join) - for lateral in iter(self._parse_lateral, None): - this.append("laterals", lateral) - - while True: - if self._match_set(self.QUERY_MODIFIER_PARSERS, advance=False): - modifier_token = self._curr - parser = self.QUERY_MODIFIER_PARSERS[modifier_token.token_type] - key, expression = parser(self) - - if expression: - if this.args.get(key): - self.raise_error( - f"Found multiple '{modifier_token.text.upper()}' clauses", - token=modifier_token, - ) - - this.set(key, expression) - if key == "limit": - offset = expression.args.get("offset") - expression.set("offset", None) - - if offset: - offset = exp.Offset(expression=offset) - this.set("offset", offset) - - limit_by_expressions = expression.expressions - expression.set("expressions", None) - offset.set("expressions", limit_by_expressions) - continue - break - - if self.SUPPORTS_IMPLICIT_UNNEST and this and this.args.get("from_"): - this = self._implicit_unnests_to_explicit(this) - - return this - - def _parse_hint_fallback_to_string(self) -> t.Optional[exp.Hint]: - start = self._curr - while self._curr: - self._advance() - - end = self._tokens[self._index - 1] - return exp.Hint(expressions=[self._find_sql(start, end)]) - - def _parse_hint_function_call(self) -> t.Optional[exp.Expression]: - return self._parse_function_call() - - def _parse_hint_body(self) -> t.Optional[exp.Hint]: - start_index = self._index - should_fallback_to_string = False - - hints = [] - try: - for hint in iter( - lambda: self._parse_csv( - lambda: ( - self._parse_hint_function_call() or self._parse_var(upper=True) - ), - ), - [], - ): - hints.extend(hint) - except ParseError: - should_fallback_to_string = True - - if should_fallback_to_string or self._curr: - self._retreat(start_index) - return self._parse_hint_fallback_to_string() - - return self.expression(exp.Hint, expressions=hints) - - def _parse_hint(self) -> t.Optional[exp.Hint]: - if self._match(TokenType.HINT) and self._prev_comments: - return exp.maybe_parse( - self._prev_comments[0], into=exp.Hint, dialect=self.dialect - ) - - return None - - def _parse_into(self) -> t.Optional[exp.Into]: - if not self._match(TokenType.INTO): - return None - - temp = self._match(TokenType.TEMPORARY) - unlogged = self._match_text_seq("UNLOGGED") - self._match(TokenType.TABLE) - - return self.expression( - exp.Into, - this=self._parse_table(schema=True), - temporary=temp, - unlogged=unlogged, - ) - - def _parse_from( - self, - joins: bool = False, - skip_from_token: bool = False, - consume_pipe: bool = False, - ) -> t.Optional[exp.From]: - if not skip_from_token and not self._match(TokenType.FROM): - return None - - return self.expression( - exp.From, - comments=self._prev_comments, - this=self._parse_table(joins=joins, consume_pipe=consume_pipe), - ) - - def _parse_match_recognize_measure(self) -> exp.MatchRecognizeMeasure: - return self.expression( - exp.MatchRecognizeMeasure, - window_frame=self._match_texts(("FINAL", "RUNNING")) - and self._prev.text.upper(), - this=self._parse_expression(), - ) - - def _parse_match_recognize(self) -> t.Optional[exp.MatchRecognize]: - if not self._match(TokenType.MATCH_RECOGNIZE): - return None - - self._match_l_paren() - - partition = self._parse_partition_by() - order = self._parse_order() - - measures = ( - self._parse_csv(self._parse_match_recognize_measure) - if self._match_text_seq("MEASURES") - else None - ) - - if self._match_text_seq("ONE", "ROW", "PER", "MATCH"): - rows = exp.var("ONE ROW PER MATCH") - elif self._match_text_seq("ALL", "ROWS", "PER", "MATCH"): - text = "ALL ROWS PER MATCH" - if self._match_text_seq("SHOW", "EMPTY", "MATCHES"): - text += " SHOW EMPTY MATCHES" - elif self._match_text_seq("OMIT", "EMPTY", "MATCHES"): - text += " OMIT EMPTY MATCHES" - elif self._match_text_seq("WITH", "UNMATCHED", "ROWS"): - text += " WITH UNMATCHED ROWS" - rows = exp.var(text) - else: - rows = None - - if self._match_text_seq("AFTER", "MATCH", "SKIP"): - text = "AFTER MATCH SKIP" - if self._match_text_seq("PAST", "LAST", "ROW"): - text += " PAST LAST ROW" - elif self._match_text_seq("TO", "NEXT", "ROW"): - text += " TO NEXT ROW" - elif self._match_text_seq("TO", "FIRST"): - text += f" TO FIRST {self._advance_any().text}" # type: ignore - elif self._match_text_seq("TO", "LAST"): - text += f" TO LAST {self._advance_any().text}" # type: ignore - after = exp.var(text) - else: - after = None - - if self._match_text_seq("PATTERN"): - self._match_l_paren() - - if not self._curr: - self.raise_error("Expecting )", self._curr) - - paren = 1 - start = self._curr - - while self._curr and paren > 0: - if self._curr.token_type == TokenType.L_PAREN: - paren += 1 - if self._curr.token_type == TokenType.R_PAREN: - paren -= 1 - - end = self._prev - self._advance() - - if paren > 0: - self.raise_error("Expecting )", self._curr) - - pattern = exp.var(self._find_sql(start, end)) - else: - pattern = None - - define = ( - self._parse_csv(self._parse_name_as_expression) - if self._match_text_seq("DEFINE") - else None - ) - - self._match_r_paren() - - return self.expression( - exp.MatchRecognize, - partition_by=partition, - order=order, - measures=measures, - rows=rows, - after=after, - pattern=pattern, - define=define, - alias=self._parse_table_alias(), - ) - - def _parse_lateral(self) -> t.Optional[exp.Lateral]: - cross_apply = self._match_pair(TokenType.CROSS, TokenType.APPLY) - if not cross_apply and self._match_pair(TokenType.OUTER, TokenType.APPLY): - cross_apply = False - - if cross_apply is not None: - this = self._parse_select(table=True) - view = None - outer = None - elif self._match(TokenType.LATERAL): - this = self._parse_select(table=True) - view = self._match(TokenType.VIEW) - outer = self._match(TokenType.OUTER) - else: - return None - - if not this: - this = ( - self._parse_unnest() - or self._parse_function() - or self._parse_id_var(any_token=False) - ) - - while self._match(TokenType.DOT): - this = exp.Dot( - this=this, - expression=self._parse_function() - or self._parse_id_var(any_token=False), - ) - - ordinality: t.Optional[bool] = None - - if view: - table = self._parse_id_var(any_token=False) - columns = ( - self._parse_csv(self._parse_id_var) - if self._match(TokenType.ALIAS) - else [] - ) - table_alias: t.Optional[exp.TableAlias] = self.expression( - exp.TableAlias, this=table, columns=columns - ) - elif isinstance(this, (exp.Subquery, exp.Unnest)) and this.alias: - # We move the alias from the lateral's child node to the lateral itself - table_alias = this.args["alias"].pop() - else: - ordinality = self._match_pair(TokenType.WITH, TokenType.ORDINALITY) - table_alias = self._parse_table_alias() - - return self.expression( - exp.Lateral, - this=this, - view=view, - outer=outer, - alias=table_alias, - cross_apply=cross_apply, - ordinality=ordinality, - ) - - def _parse_stream(self) -> t.Optional[exp.Stream]: - index = self._index - if self._match_text_seq("STREAM"): - this = self._try_parse(self._parse_table) - if this: - return self.expression(exp.Stream, this=this) - - self._retreat(index) - return None - - def _parse_join_parts( - self, - ) -> t.Tuple[t.Optional[Token], t.Optional[Token], t.Optional[Token]]: - return ( - self._match_set(self.JOIN_METHODS) and self._prev, - self._match_set(self.JOIN_SIDES) and self._prev, - self._match_set(self.JOIN_KINDS) and self._prev, - ) - - def _parse_using_identifiers(self) -> t.List[exp.Expression]: - def _parse_column_as_identifier() -> t.Optional[exp.Expression]: - this = self._parse_column() - if isinstance(this, exp.Column): - return this.this - return this - - return self._parse_wrapped_csv(_parse_column_as_identifier, optional=True) - - def _parse_join( - self, skip_join_token: bool = False, parse_bracket: bool = False - ) -> t.Optional[exp.Join]: - if self._match(TokenType.COMMA): - table = self._try_parse(self._parse_table) - cross_join = self.expression(exp.Join, this=table) if table else None - - if cross_join and self.JOINS_HAVE_EQUAL_PRECEDENCE: - cross_join.set("kind", "CROSS") - - return cross_join - - index = self._index - method, side, kind = self._parse_join_parts() - hint = self._prev.text if self._match_texts(self.JOIN_HINTS) else None - join = self._match(TokenType.JOIN) or ( - kind and kind.token_type == TokenType.STRAIGHT_JOIN - ) - join_comments = self._prev_comments - - if not skip_join_token and not join: - self._retreat(index) - kind = None - method = None - side = None - - outer_apply = self._match_pair(TokenType.OUTER, TokenType.APPLY, False) - cross_apply = self._match_pair(TokenType.CROSS, TokenType.APPLY, False) - - if not skip_join_token and not join and not outer_apply and not cross_apply: - return None - - kwargs: t.Dict[str, t.Any] = { - "this": self._parse_table(parse_bracket=parse_bracket) - } - if kind and kind.token_type == TokenType.ARRAY and self._match(TokenType.COMMA): - kwargs["expressions"] = self._parse_csv( - lambda: self._parse_table(parse_bracket=parse_bracket) - ) - - if method: - kwargs["method"] = method.text.upper() - if side: - kwargs["side"] = side.text.upper() - if kind: - kwargs["kind"] = kind.text.upper() - if hint: - kwargs["hint"] = hint - - if self._match(TokenType.MATCH_CONDITION): - kwargs["match_condition"] = self._parse_wrapped(self._parse_comparison) - - if self._match(TokenType.ON): - kwargs["on"] = self._parse_disjunction() - elif self._match(TokenType.USING): - kwargs["using"] = self._parse_using_identifiers() - elif ( - not method - and not (outer_apply or cross_apply) - and not isinstance(kwargs["this"], exp.Unnest) - and not (kind and kind.token_type in (TokenType.CROSS, TokenType.ARRAY)) - ): - index = self._index - joins: t.Optional[list] = list(self._parse_joins()) - - if joins and self._match(TokenType.ON): - kwargs["on"] = self._parse_disjunction() - elif joins and self._match(TokenType.USING): - kwargs["using"] = self._parse_using_identifiers() - else: - joins = None - self._retreat(index) - - kwargs["this"].set("joins", joins if joins else None) - - kwargs["pivots"] = self._parse_pivots() - - comments = [ - c for token in (method, side, kind) if token for c in token.comments - ] - comments = (join_comments or []) + comments - - if ( - self.ADD_JOIN_ON_TRUE - and not kwargs.get("on") - and not kwargs.get("using") - and not kwargs.get("method") - and kwargs.get("kind") in (None, "INNER", "OUTER") - ): - kwargs["on"] = exp.true() - - return self.expression(exp.Join, comments=comments, **kwargs) - - def _parse_opclass(self) -> t.Optional[exp.Expression]: - this = self._parse_disjunction() - - if self._match_texts(self.OPCLASS_FOLLOW_KEYWORDS, advance=False): - return this - - if not self._match_set(self.OPTYPE_FOLLOW_TOKENS, advance=False): - return self.expression( - exp.Opclass, this=this, expression=self._parse_table_parts() - ) - - return this - - def _parse_index_params(self) -> exp.IndexParameters: - using = ( - self._parse_var(any_token=True) if self._match(TokenType.USING) else None - ) - - if self._match(TokenType.L_PAREN, advance=False): - columns = self._parse_wrapped_csv(self._parse_with_operator) - else: - columns = None - - include = ( - self._parse_wrapped_id_vars() if self._match_text_seq("INCLUDE") else None - ) - partition_by = self._parse_partition_by() - with_storage = self._match(TokenType.WITH) and self._parse_wrapped_properties() - tablespace = ( - self._parse_var(any_token=True) - if self._match_text_seq("USING", "INDEX", "TABLESPACE") - else None - ) - where = self._parse_where() - - on = self._parse_field() if self._match(TokenType.ON) else None - - return self.expression( - exp.IndexParameters, - using=using, - columns=columns, - include=include, - partition_by=partition_by, - where=where, - with_storage=with_storage, - tablespace=tablespace, - on=on, - ) - - def _parse_index( - self, index: t.Optional[exp.Expression] = None, anonymous: bool = False - ) -> t.Optional[exp.Index]: - if index or anonymous: - unique = None - primary = None - amp = None - - self._match(TokenType.ON) - self._match(TokenType.TABLE) # hive - table = self._parse_table_parts(schema=True) - else: - unique = self._match(TokenType.UNIQUE) - primary = self._match_text_seq("PRIMARY") - amp = self._match_text_seq("AMP") - - if not self._match(TokenType.INDEX): - return None - - index = self._parse_id_var() - table = None - - params = self._parse_index_params() - - return self.expression( - exp.Index, - this=index, - table=table, - unique=unique, - primary=primary, - amp=amp, - params=params, - ) - - def _parse_table_hints(self) -> t.Optional[t.List[exp.Expression]]: - hints: t.List[exp.Expression] = [] - if self._match_pair(TokenType.WITH, TokenType.L_PAREN): - # https://learn.microsoft.com/en-us/sql/t-sql/queries/hints-transact-sql-table?view=sql-server-ver16 - hints.append( - self.expression( - exp.WithTableHint, - expressions=self._parse_csv( - lambda: ( - self._parse_function() or self._parse_var(any_token=True) - ) - ), - ) - ) - self._match_r_paren() - else: - # https://dev.mysql.com/doc/refman/8.0/en/index-hints.html - while self._match_set(self.TABLE_INDEX_HINT_TOKENS): - hint = exp.IndexTableHint(this=self._prev.text.upper()) - - self._match_set((TokenType.INDEX, TokenType.KEY)) - if self._match(TokenType.FOR): - hint.set("target", self._advance_any() and self._prev.text.upper()) - - hint.set("expressions", self._parse_wrapped_id_vars()) - hints.append(hint) - - return hints or None - - def _parse_table_part(self, schema: bool = False) -> t.Optional[exp.Expression]: - return ( - (not schema and self._parse_function(optional_parens=False)) - or self._parse_id_var(any_token=False) - or self._parse_string_as_identifier() - or self._parse_placeholder() - ) - - def _parse_table_parts( - self, - schema: bool = False, - is_db_reference: bool = False, - wildcard: bool = False, - ) -> exp.Table: - catalog = None - db = None - table: t.Optional[exp.Expression | str] = self._parse_table_part(schema=schema) - - while self._match(TokenType.DOT): - if catalog: - # This allows nesting the table in arbitrarily many dot expressions if needed - table = self.expression( - exp.Dot, - this=table, - expression=self._parse_table_part(schema=schema), - ) - else: - catalog = db - db = table - # "" used for tsql FROM a..b case - table = self._parse_table_part(schema=schema) or "" - - if ( - wildcard - and self._is_connected() - and (isinstance(table, exp.Identifier) or not table) - and self._match(TokenType.STAR) - ): - if isinstance(table, exp.Identifier): - table.args["this"] += "*" - else: - table = exp.Identifier(this="*") - - # We bubble up comments from the Identifier to the Table - comments = table.pop_comments() if isinstance(table, exp.Expression) else None - - if is_db_reference: - catalog = db - db = table - table = None - - if not table and not is_db_reference: - self.raise_error(f"Expected table name but got {self._curr}") - if not db and is_db_reference: - self.raise_error(f"Expected database name but got {self._curr}") - - table = self.expression( - exp.Table, - comments=comments, - this=table, - db=db, - catalog=catalog, - ) - - changes = self._parse_changes() - if changes: - table.set("changes", changes) - - at_before = self._parse_historical_data() - if at_before: - table.set("when", at_before) - - pivots = self._parse_pivots() - if pivots: - table.set("pivots", pivots) - - return table - - def _parse_table( - self, - schema: bool = False, - joins: bool = False, - alias_tokens: t.Optional[t.Collection[TokenType]] = None, - parse_bracket: bool = False, - is_db_reference: bool = False, - parse_partition: bool = False, - consume_pipe: bool = False, - ) -> t.Optional[exp.Expression]: - stream = self._parse_stream() - if stream: - return stream - - lateral = self._parse_lateral() - if lateral: - return lateral - - unnest = self._parse_unnest() - if unnest: - return unnest - - values = self._parse_derived_table_values() - if values: - return values - - subquery = self._parse_select(table=True, consume_pipe=consume_pipe) - if subquery: - if not subquery.args.get("pivots"): - subquery.set("pivots", self._parse_pivots()) - return subquery - - bracket = parse_bracket and self._parse_bracket(None) - bracket = self.expression(exp.Table, this=bracket) if bracket else None - - rows_from = self._match_text_seq("ROWS", "FROM") and self._parse_wrapped_csv( - self._parse_table - ) - rows_from = ( - self.expression(exp.Table, rows_from=rows_from) if rows_from else None - ) - - only = self._match(TokenType.ONLY) - - this = t.cast( - exp.Expression, - bracket - or rows_from - or self._parse_bracket( - self._parse_table_parts(schema=schema, is_db_reference=is_db_reference) - ), - ) - - if only: - this.set("only", only) - - # Postgres supports a wildcard (table) suffix operator, which is a no-op in this context - self._match_text_seq("*") - - parse_partition = parse_partition or self.SUPPORTS_PARTITION_SELECTION - if parse_partition and self._match(TokenType.PARTITION, advance=False): - this.set("partition", self._parse_partition()) - - if schema: - return self._parse_schema(this=this) - - # see: https://docs.cloud.google.com/bigquery/docs/reference/standard-sql/query-syntax#from_clause - # from_item, then alias, then time travel, then sample. - alias = self._parse_table_alias( - alias_tokens=alias_tokens or self.TABLE_ALIAS_TOKENS - ) - if alias: - this.set("alias", alias) - - version = self._parse_version() - - if version: - this.set("version", version) - - if self.dialect.ALIAS_POST_TABLESAMPLE: - this.set("sample", self._parse_table_sample()) - - if self._match(TokenType.INDEXED_BY): - this.set("indexed", self._parse_table_parts()) - elif self._match_text_seq("NOT", "INDEXED"): - this.set("indexed", False) - - if isinstance(this, exp.Table) and self._match_text_seq("AT"): - return self.expression( - exp.AtIndex, - this=this.to_column(copy=False), - expression=self._parse_id_var(), - ) - - this.set("hints", self._parse_table_hints()) - - if not this.args.get("pivots"): - this.set("pivots", self._parse_pivots()) - - if not self.dialect.ALIAS_POST_TABLESAMPLE: - this.set("sample", self._parse_table_sample()) - - if joins: - for join in self._parse_joins(): - this.append("joins", join) - - if self._match_pair(TokenType.WITH, TokenType.ORDINALITY): - this.set("ordinality", True) - this.set("alias", self._parse_table_alias()) - - return this - - def _parse_version(self) -> t.Optional[exp.Version]: - if self._match(TokenType.TIMESTAMP_SNAPSHOT): - this = "TIMESTAMP" - elif self._match(TokenType.VERSION_SNAPSHOT): - this = "VERSION" - else: - return None - - if self._match_set((TokenType.FROM, TokenType.BETWEEN)): - kind = self._prev.text.upper() - start = self._parse_bitwise() - self._match_texts(("TO", "AND")) - end = self._parse_bitwise() - expression: t.Optional[exp.Expression] = self.expression( - exp.Tuple, expressions=[start, end] - ) - elif self._match_text_seq("CONTAINED", "IN"): - kind = "CONTAINED IN" - expression = self.expression( - exp.Tuple, expressions=self._parse_wrapped_csv(self._parse_bitwise) - ) - elif self._match(TokenType.ALL): - kind = "ALL" - expression = None - else: - self._match_text_seq("AS", "OF") - kind = "AS OF" - expression = self._parse_type() - - return self.expression(exp.Version, this=this, expression=expression, kind=kind) - - def _parse_historical_data(self) -> t.Optional[exp.HistoricalData]: - # https://docs.snowflake.com/en/sql-reference/constructs/at-before - index = self._index - historical_data = None - if self._match_texts(self.HISTORICAL_DATA_PREFIX): - this = self._prev.text.upper() - kind = ( - self._match(TokenType.L_PAREN) - and self._match_texts(self.HISTORICAL_DATA_KIND) - and self._prev.text.upper() - ) - expression = self._match(TokenType.FARROW) and self._parse_bitwise() - - if expression: - self._match_r_paren() - historical_data = self.expression( - exp.HistoricalData, this=this, kind=kind, expression=expression - ) - else: - self._retreat(index) - - return historical_data - - def _parse_changes(self) -> t.Optional[exp.Changes]: - if not self._match_text_seq("CHANGES", "(", "INFORMATION", "=>"): - return None - - information = self._parse_var(any_token=True) - self._match_r_paren() - - return self.expression( - exp.Changes, - information=information, - at_before=self._parse_historical_data(), - end=self._parse_historical_data(), - ) - - def _parse_unnest(self, with_alias: bool = True) -> t.Optional[exp.Unnest]: - if not self._match_pair(TokenType.UNNEST, TokenType.L_PAREN, advance=False): - return None - - self._advance() - - expressions = self._parse_wrapped_csv(self._parse_equality) - offset = self._match_pair(TokenType.WITH, TokenType.ORDINALITY) - - alias = self._parse_table_alias() if with_alias else None - - if alias: - if self.dialect.UNNEST_COLUMN_ONLY: - if alias.args.get("columns"): - self.raise_error("Unexpected extra column alias in unnest.") - - alias.set("columns", [alias.this]) - alias.set("this", None) - - columns = alias.args.get("columns") or [] - if offset and len(expressions) < len(columns): - offset = columns.pop() - - if not offset and self._match_pair(TokenType.WITH, TokenType.OFFSET): - self._match(TokenType.ALIAS) - offset = self._parse_id_var( - any_token=False, tokens=self.UNNEST_OFFSET_ALIAS_TOKENS - ) or exp.to_identifier("offset") - - return self.expression( - exp.Unnest, expressions=expressions, alias=alias, offset=offset - ) - - def _parse_derived_table_values(self) -> t.Optional[exp.Values]: - is_derived = self._match_pair(TokenType.L_PAREN, TokenType.VALUES) - if not is_derived and not ( - # ClickHouse's `FORMAT Values` is equivalent to `VALUES` - self._match_text_seq("VALUES") or self._match_text_seq("FORMAT", "VALUES") - ): - return None - - expressions = self._parse_csv(self._parse_value) - alias = self._parse_table_alias() - - if is_derived: - self._match_r_paren() - - return self.expression( - exp.Values, - expressions=expressions, - alias=alias or self._parse_table_alias(), - ) - - def _parse_table_sample( - self, as_modifier: bool = False - ) -> t.Optional[exp.TableSample]: - if not self._match(TokenType.TABLE_SAMPLE) and not ( - as_modifier and self._match_text_seq("USING", "SAMPLE") - ): - return None - - bucket_numerator = None - bucket_denominator = None - bucket_field = None - percent = None - size = None - seed = None - - method = self._parse_var(tokens=(TokenType.ROW,), upper=True) - matched_l_paren = self._match(TokenType.L_PAREN) - - if self.TABLESAMPLE_CSV: - num = None - expressions = self._parse_csv(self._parse_primary) - else: - expressions = None - num = ( - self._parse_factor() - if self._match(TokenType.NUMBER, advance=False) - else self._parse_primary() or self._parse_placeholder() - ) - - if self._match_text_seq("BUCKET"): - bucket_numerator = self._parse_number() - self._match_text_seq("OUT", "OF") - bucket_denominator = bucket_denominator = self._parse_number() - self._match(TokenType.ON) - bucket_field = self._parse_field() - elif self._match_set((TokenType.PERCENT, TokenType.MOD)): - percent = num - elif ( - self._match(TokenType.ROWS) or not self.dialect.TABLESAMPLE_SIZE_IS_PERCENT - ): - size = num - else: - percent = num - - if matched_l_paren: - self._match_r_paren() - - if self._match(TokenType.L_PAREN): - method = self._parse_var(upper=True) - seed = self._match(TokenType.COMMA) and self._parse_number() - self._match_r_paren() - elif self._match_texts(("SEED", "REPEATABLE")): - seed = self._parse_wrapped(self._parse_number) - - if not method and self.DEFAULT_SAMPLING_METHOD: - method = exp.var(self.DEFAULT_SAMPLING_METHOD) - - return self.expression( - exp.TableSample, - expressions=expressions, - method=method, - bucket_numerator=bucket_numerator, - bucket_denominator=bucket_denominator, - bucket_field=bucket_field, - percent=percent, - size=size, - seed=seed, - ) - - def _parse_pivots(self) -> t.Optional[t.List[exp.Pivot]]: - return list(iter(self._parse_pivot, None)) or None - - def _parse_joins(self) -> t.Iterator[exp.Join]: - return iter(self._parse_join, None) - - def _parse_unpivot_columns(self) -> t.Optional[exp.UnpivotColumns]: - if not self._match(TokenType.INTO): - return None - - return self.expression( - exp.UnpivotColumns, - this=self._match_text_seq("NAME") and self._parse_column(), - expressions=self._match_text_seq("VALUE") - and self._parse_csv(self._parse_column), - ) - - # https://duckdb.org/docs/sql/statements/pivot - def _parse_simplified_pivot(self, is_unpivot: t.Optional[bool] = None) -> exp.Pivot: - def _parse_on() -> t.Optional[exp.Expression]: - this = self._parse_bitwise() - - if self._match(TokenType.IN): - # PIVOT ... ON col IN (row_val1, row_val2) - return self._parse_in(this) - if self._match(TokenType.ALIAS, advance=False): - # UNPIVOT ... ON (col1, col2, col3) AS row_val - return self._parse_alias(this) - - return this - - this = self._parse_table() - expressions = self._match(TokenType.ON) and self._parse_csv(_parse_on) - into = self._parse_unpivot_columns() - using = self._match(TokenType.USING) and self._parse_csv( - lambda: self._parse_alias(self._parse_column()) - ) - group = self._parse_group() - - return self.expression( - exp.Pivot, - this=this, - expressions=expressions, - using=using, - group=group, - unpivot=is_unpivot, - into=into, - ) - - def _parse_pivot_in(self) -> exp.In: - def _parse_aliased_expression() -> t.Optional[exp.Expression]: - this = self._parse_select_or_expression() - - self._match(TokenType.ALIAS) - alias = self._parse_bitwise() - if alias: - if isinstance(alias, exp.Column) and not alias.db: - alias = alias.this - return self.expression(exp.PivotAlias, this=this, alias=alias) - - return this - - value = self._parse_column() - - if not self._match(TokenType.IN): - self.raise_error("Expecting IN") - - if self._match(TokenType.L_PAREN): - if self._match(TokenType.ANY): - exprs: t.List[exp.Expression] = ensure_list( - exp.PivotAny(this=self._parse_order()) - ) - else: - exprs = self._parse_csv(_parse_aliased_expression) - self._match_r_paren() - return self.expression(exp.In, this=value, expressions=exprs) - - return self.expression(exp.In, this=value, field=self._parse_id_var()) - - def _parse_pivot_aggregation(self) -> t.Optional[exp.Expression]: - func = self._parse_function() - if not func: - if self._prev and self._prev.token_type == TokenType.COMMA: - return None - self.raise_error("Expecting an aggregation function in PIVOT") - - return self._parse_alias(func) - - def _parse_pivot(self) -> t.Optional[exp.Pivot]: - index = self._index - include_nulls = None - - if self._match(TokenType.PIVOT): - unpivot = False - elif self._match(TokenType.UNPIVOT): - unpivot = True - - # https://docs.databricks.com/en/sql/language-manual/sql-ref-syntax-qry-select-unpivot.html#syntax - if self._match_text_seq("INCLUDE", "NULLS"): - include_nulls = True - elif self._match_text_seq("EXCLUDE", "NULLS"): - include_nulls = False - else: - return None - - expressions = [] - - if not self._match(TokenType.L_PAREN): - self._retreat(index) - return None - - if unpivot: - expressions = self._parse_csv(self._parse_column) - else: - expressions = self._parse_csv(self._parse_pivot_aggregation) - - if not expressions: - self.raise_error("Failed to parse PIVOT's aggregation list") - - if not self._match(TokenType.FOR): - self.raise_error("Expecting FOR") - - fields = [] - while True: - field = self._try_parse(self._parse_pivot_in) - if not field: - break - fields.append(field) - - default_on_null = self._match_text_seq( - "DEFAULT", "ON", "NULL" - ) and self._parse_wrapped(self._parse_bitwise) - - group = self._parse_group() - - self._match_r_paren() - - pivot = self.expression( - exp.Pivot, - expressions=expressions, - fields=fields, - unpivot=unpivot, - include_nulls=include_nulls, - default_on_null=default_on_null, - group=group, - ) - - if not self._match_set((TokenType.PIVOT, TokenType.UNPIVOT), advance=False): - pivot.set("alias", self._parse_table_alias()) - - if not unpivot: - names = self._pivot_column_names( - t.cast(t.List[exp.Expression], expressions) - ) - - columns: t.List[exp.Expression] = [] - all_fields = [] - for pivot_field in pivot.fields: - pivot_field_expressions = pivot_field.expressions - - # The `PivotAny` expression corresponds to `ANY ORDER BY `; we can't infer in this case. - if isinstance(seq_get(pivot_field_expressions, 0), exp.PivotAny): - continue - - all_fields.append( - [ - fld.sql() if self.IDENTIFY_PIVOT_STRINGS else fld.alias_or_name - for fld in pivot_field_expressions - ] - ) - - if all_fields: - if names: - all_fields.append(names) - - # Generate all possible combinations of the pivot columns - # e.g PIVOT(sum(...) as total FOR year IN (2000, 2010) FOR country IN ('NL', 'US')) - # generates the product between [[2000, 2010], ['NL', 'US'], ['total']] - for fld_parts_tuple in itertools.product(*all_fields): - fld_parts = list(fld_parts_tuple) - - if names and self.PREFIXED_PIVOT_COLUMNS: - # Move the "name" to the front of the list - fld_parts.insert(0, fld_parts.pop(-1)) - - columns.append(exp.to_identifier("_".join(fld_parts))) - - pivot.set("columns", columns) - - return pivot - - def _pivot_column_names(self, aggregations: t.List[exp.Expression]) -> t.List[str]: - return [agg.alias for agg in aggregations if agg.alias] - - def _parse_prewhere( - self, skip_where_token: bool = False - ) -> t.Optional[exp.PreWhere]: - if not skip_where_token and not self._match(TokenType.PREWHERE): - return None - - return self.expression( - exp.PreWhere, comments=self._prev_comments, this=self._parse_disjunction() - ) - - def _parse_where(self, skip_where_token: bool = False) -> t.Optional[exp.Where]: - if not skip_where_token and not self._match(TokenType.WHERE): - return None - - return self.expression( - exp.Where, comments=self._prev_comments, this=self._parse_disjunction() - ) - - def _parse_group(self, skip_group_by_token: bool = False) -> t.Optional[exp.Group]: - if not skip_group_by_token and not self._match(TokenType.GROUP_BY): - return None - comments = self._prev_comments - - elements: t.Dict[str, t.Any] = defaultdict(list) - - if self._match(TokenType.ALL): - elements["all"] = True - elif self._match(TokenType.DISTINCT): - elements["all"] = False - - if self._match_set(self.QUERY_MODIFIER_TOKENS, advance=False): - return self.expression(exp.Group, comments=comments, **elements) # type: ignore - - while True: - index = self._index - - elements["expressions"].extend( - self._parse_csv( - lambda: ( - None - if self._match_set( - (TokenType.CUBE, TokenType.ROLLUP), advance=False - ) - else self._parse_disjunction() - ) - ) - ) - - before_with_index = self._index - with_prefix = self._match(TokenType.WITH) - - if cube_or_rollup := self._parse_cube_or_rollup(with_prefix=with_prefix): - key = "rollup" if isinstance(cube_or_rollup, exp.Rollup) else "cube" - elements[key].append(cube_or_rollup) - elif grouping_sets := self._parse_grouping_sets(): - elements["grouping_sets"].append(grouping_sets) - elif self._match_text_seq("TOTALS"): - elements["totals"] = True # type: ignore - - if before_with_index <= self._index <= before_with_index + 1: - self._retreat(before_with_index) - break - - if index == self._index: - break - - return self.expression(exp.Group, comments=comments, **elements) # type: ignore - - def _parse_cube_or_rollup( - self, with_prefix: bool = False - ) -> t.Optional[exp.Cube | exp.Rollup]: - if self._match(TokenType.CUBE): - kind: t.Type[exp.Cube | exp.Rollup] = exp.Cube - elif self._match(TokenType.ROLLUP): - kind = exp.Rollup - else: - return None - - return self.expression( - kind, - expressions=[] - if with_prefix - else self._parse_wrapped_csv(self._parse_bitwise), - ) - - def _parse_grouping_sets(self) -> t.Optional[exp.GroupingSets]: - if self._match(TokenType.GROUPING_SETS): - return self.expression( - exp.GroupingSets, - expressions=self._parse_wrapped_csv(self._parse_grouping_set), - ) - return None - - def _parse_grouping_set(self) -> t.Optional[exp.Expression]: - return ( - self._parse_grouping_sets() - or self._parse_cube_or_rollup() - or self._parse_bitwise() - ) - - def _parse_having(self, skip_having_token: bool = False) -> t.Optional[exp.Having]: - if not skip_having_token and not self._match(TokenType.HAVING): - return None - return self.expression( - exp.Having, comments=self._prev_comments, this=self._parse_disjunction() - ) - - def _parse_qualify(self) -> t.Optional[exp.Qualify]: - if not self._match(TokenType.QUALIFY): - return None - return self.expression(exp.Qualify, this=self._parse_disjunction()) - - def _parse_connect_with_prior(self) -> t.Optional[exp.Expression]: - self.NO_PAREN_FUNCTION_PARSERS["PRIOR"] = lambda self: self.expression( - exp.Prior, this=self._parse_bitwise() - ) - connect = self._parse_disjunction() - self.NO_PAREN_FUNCTION_PARSERS.pop("PRIOR") - return connect - - def _parse_connect(self, skip_start_token: bool = False) -> t.Optional[exp.Connect]: - if skip_start_token: - start = None - elif self._match(TokenType.START_WITH): - start = self._parse_disjunction() - else: - return None - - self._match(TokenType.CONNECT_BY) - nocycle = self._match_text_seq("NOCYCLE") - connect = self._parse_connect_with_prior() - - if not start and self._match(TokenType.START_WITH): - start = self._parse_disjunction() - - return self.expression( - exp.Connect, start=start, connect=connect, nocycle=nocycle - ) - - def _parse_name_as_expression(self) -> t.Optional[exp.Expression]: - this = self._parse_id_var(any_token=True) - if self._match(TokenType.ALIAS): - this = self.expression( - exp.Alias, alias=this, this=self._parse_disjunction() - ) - return this - - def _parse_interpolate(self) -> t.Optional[t.List[exp.Expression]]: - if self._match_text_seq("INTERPOLATE"): - return self._parse_wrapped_csv(self._parse_name_as_expression) - return None - - def _parse_order( - self, this: t.Optional[exp.Expression] = None, skip_order_token: bool = False - ) -> t.Optional[exp.Expression]: - siblings = None - if not skip_order_token and not self._match(TokenType.ORDER_BY): - if not self._match(TokenType.ORDER_SIBLINGS_BY): - return this - - siblings = True - - return self.expression( - exp.Order, - comments=self._prev_comments, - this=this, - expressions=self._parse_csv(self._parse_ordered), - siblings=siblings, - ) - - def _parse_sort(self, exp_class: t.Type[E], token: TokenType) -> t.Optional[E]: - if not self._match(token): - return None - return self.expression( - exp_class, expressions=self._parse_csv(self._parse_ordered) - ) - - def _parse_ordered( - self, parse_method: t.Optional[t.Callable] = None - ) -> t.Optional[exp.Ordered]: - this = parse_method() if parse_method else self._parse_disjunction() - if not this: - return None - - if this.name.upper() == "ALL" and self.dialect.SUPPORTS_ORDER_BY_ALL: - this = exp.var("ALL") - - asc = self._match(TokenType.ASC) - desc = self._match(TokenType.DESC) or (asc and False) - - is_nulls_first = self._match_text_seq("NULLS", "FIRST") - is_nulls_last = self._match_text_seq("NULLS", "LAST") - - nulls_first = is_nulls_first or False - explicitly_null_ordered = is_nulls_first or is_nulls_last - - if ( - not explicitly_null_ordered - and ( - (not desc and self.dialect.NULL_ORDERING == "nulls_are_small") - or (desc and self.dialect.NULL_ORDERING != "nulls_are_small") - ) - and self.dialect.NULL_ORDERING != "nulls_are_last" - ): - nulls_first = True - - if self._match_text_seq("WITH", "FILL"): - with_fill = self.expression( - exp.WithFill, - from_=self._match(TokenType.FROM) and self._parse_bitwise(), - to=self._match_text_seq("TO") and self._parse_bitwise(), - step=self._match_text_seq("STEP") and self._parse_bitwise(), - interpolate=self._parse_interpolate(), - ) - else: - with_fill = None - - return self.expression( - exp.Ordered, - this=this, - desc=desc, - nulls_first=nulls_first, - with_fill=with_fill, - ) - - def _parse_limit_options(self) -> t.Optional[exp.LimitOptions]: - percent = self._match_set((TokenType.PERCENT, TokenType.MOD)) - rows = self._match_set((TokenType.ROW, TokenType.ROWS)) - self._match_text_seq("ONLY") - with_ties = self._match_text_seq("WITH", "TIES") - - if not (percent or rows or with_ties): - return None - - return self.expression( - exp.LimitOptions, percent=percent, rows=rows, with_ties=with_ties - ) - - def _parse_limit( - self, - this: t.Optional[exp.Expression] = None, - top: bool = False, - skip_limit_token: bool = False, - ) -> t.Optional[exp.Expression]: - if skip_limit_token or self._match(TokenType.TOP if top else TokenType.LIMIT): - comments = self._prev_comments - if top: - limit_paren = self._match(TokenType.L_PAREN) - expression = self._parse_term() if limit_paren else self._parse_number() - - if limit_paren: - self._match_r_paren() - - else: - # Parsing LIMIT x% (i.e x PERCENT) as a term leads to an error, since - # we try to build an exp.Mod expr. For that matter, we backtrack and instead - # consume the factor plus parse the percentage separately - index = self._index - expression = self._try_parse(self._parse_term) - if isinstance(expression, exp.Mod): - self._retreat(index) - expression = self._parse_factor() - elif not expression: - expression = self._parse_factor() - limit_options = self._parse_limit_options() - - if self._match(TokenType.COMMA): - offset = expression - expression = self._parse_term() - else: - offset = None - - limit_exp = self.expression( - exp.Limit, - this=this, - expression=expression, - offset=offset, - comments=comments, - limit_options=limit_options, - expressions=self._parse_limit_by(), - ) - - return limit_exp - - if self._match(TokenType.FETCH): - direction = self._match_set((TokenType.FIRST, TokenType.NEXT)) - direction = self._prev.text.upper() if direction else "FIRST" - - count = self._parse_field(tokens=self.FETCH_TOKENS) - - return self.expression( - exp.Fetch, - direction=direction, - count=count, - limit_options=self._parse_limit_options(), - ) - - return this - - def _parse_offset( - self, this: t.Optional[exp.Expression] = None - ) -> t.Optional[exp.Expression]: - if not self._match(TokenType.OFFSET): - return this - - count = self._parse_term() - self._match_set((TokenType.ROW, TokenType.ROWS)) - - return self.expression( - exp.Offset, this=this, expression=count, expressions=self._parse_limit_by() - ) - - def _can_parse_limit_or_offset(self) -> bool: - if not self._match_set(self.AMBIGUOUS_ALIAS_TOKENS, advance=False): - return False - - index = self._index - result = bool( - self._try_parse(self._parse_limit, retreat=True) - or self._try_parse(self._parse_offset, retreat=True) - ) - self._retreat(index) - return result - - def _parse_limit_by(self) -> t.Optional[t.List[exp.Expression]]: - return self._match_text_seq("BY") and self._parse_csv(self._parse_bitwise) - - def _parse_locks(self) -> t.List[exp.Lock]: - locks = [] - while True: - update, key = None, None - if self._match_text_seq("FOR", "UPDATE"): - update = True - elif self._match_text_seq("FOR", "SHARE") or self._match_text_seq( - "LOCK", "IN", "SHARE", "MODE" - ): - update = False - elif self._match_text_seq("FOR", "KEY", "SHARE"): - update, key = False, True - elif self._match_text_seq("FOR", "NO", "KEY", "UPDATE"): - update, key = True, True - else: - break - - expressions = None - if self._match_text_seq("OF"): - expressions = self._parse_csv(lambda: self._parse_table(schema=True)) - - wait: t.Optional[bool | exp.Expression] = None - if self._match_text_seq("NOWAIT"): - wait = True - elif self._match_text_seq("WAIT"): - wait = self._parse_primary() - elif self._match_text_seq("SKIP", "LOCKED"): - wait = False - - locks.append( - self.expression( - exp.Lock, update=update, expressions=expressions, wait=wait, key=key - ) - ) - - return locks - - def parse_set_operation( - self, this: t.Optional[exp.Expression], consume_pipe: bool = False - ) -> t.Optional[exp.Expression]: - start = self._index - _, side_token, kind_token = self._parse_join_parts() - - side = side_token.text if side_token else None - kind = kind_token.text if kind_token else None - - if not self._match_set(self.SET_OPERATIONS): - self._retreat(start) - return None - - token_type = self._prev.token_type - - if token_type == TokenType.UNION: - operation: t.Type[exp.SetOperation] = exp.Union - elif token_type == TokenType.EXCEPT: - operation = exp.Except - else: - operation = exp.Intersect - - comments = self._prev.comments - - if self._match(TokenType.DISTINCT): - distinct: t.Optional[bool] = True - elif self._match(TokenType.ALL): - distinct = False - else: - distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[operation] - if distinct is None: - self.raise_error(f"Expected DISTINCT or ALL for {operation.__name__}") - - by_name = self._match_text_seq("BY", "NAME") or self._match_text_seq( - "STRICT", "CORRESPONDING" - ) - if self._match_text_seq("CORRESPONDING"): - by_name = True - if not side and not kind: - kind = "INNER" - - on_column_list = None - if by_name and self._match_texts(("ON", "BY")): - on_column_list = self._parse_wrapped_csv(self._parse_column) - - expression = self._parse_select( - nested=True, parse_set_operation=False, consume_pipe=consume_pipe - ) - - return self.expression( - operation, - comments=comments, - this=this, - distinct=distinct, - by_name=by_name, - expression=expression, - side=side, - kind=kind, - on=on_column_list, - ) - - def _parse_set_operations( - self, this: t.Optional[exp.Expression] - ) -> t.Optional[exp.Expression]: - while this: - setop = self.parse_set_operation(this) - if not setop: - break - this = setop - - if isinstance(this, exp.SetOperation) and self.MODIFIERS_ATTACHED_TO_SET_OP: - expression = this.expression - - if expression: - for arg in self.SET_OP_MODIFIERS: - expr = expression.args.get(arg) - if expr: - this.set(arg, expr.pop()) - - return this - - def _parse_expression(self) -> t.Optional[exp.Expression]: - return self._parse_alias(self._parse_assignment()) - - def _parse_assignment(self) -> t.Optional[exp.Expression]: - this = self._parse_disjunction() - if not this and self._next and self._next.token_type in self.ASSIGNMENT: - # This allows us to parse := - this = exp.column( - t.cast(str, self._advance_any(ignore_reserved=True) and self._prev.text) - ) - - while self._match_set(self.ASSIGNMENT): - if isinstance(this, exp.Column) and len(this.parts) == 1: - this = this.this - - this = self.expression( - self.ASSIGNMENT[self._prev.token_type], - this=this, - comments=self._prev_comments, - expression=self._parse_assignment(), - ) - - return this - - def _parse_disjunction(self) -> t.Optional[exp.Expression]: - return self._parse_tokens(self._parse_conjunction, self.DISJUNCTION) - - def _parse_conjunction(self) -> t.Optional[exp.Expression]: - return self._parse_tokens(self._parse_equality, self.CONJUNCTION) - - def _parse_equality(self) -> t.Optional[exp.Expression]: - return self._parse_tokens(self._parse_comparison, self.EQUALITY) - - def _parse_comparison(self) -> t.Optional[exp.Expression]: - return self._parse_tokens(self._parse_range, self.COMPARISON) - - def _parse_range( - self, this: t.Optional[exp.Expression] = None - ) -> t.Optional[exp.Expression]: - this = this or self._parse_bitwise() - negate = self._match(TokenType.NOT) - - if self._match_set(self.RANGE_PARSERS): - expression = self.RANGE_PARSERS[self._prev.token_type](self, this) - if not expression: - return this - - this = expression - elif self._match(TokenType.ISNULL) or (negate and self._match(TokenType.NULL)): - this = self.expression(exp.Is, this=this, expression=exp.Null()) - - # Postgres supports ISNULL and NOTNULL for conditions. - # https://blog.andreiavram.ro/postgresql-null-composite-type/ - if self._match(TokenType.NOTNULL): - this = self.expression(exp.Is, this=this, expression=exp.Null()) - this = self.expression(exp.Not, this=this) - - if negate: - this = self._negate_range(this) - - if self._match(TokenType.IS): - this = self._parse_is(this) - - return this - - def _negate_range( - self, this: t.Optional[exp.Expression] = None - ) -> t.Optional[exp.Expression]: - if not this: - return this - - return self.expression(exp.Not, this=this) - - def _parse_is(self, this: t.Optional[exp.Expression]) -> t.Optional[exp.Expression]: - index = self._index - 1 - negate = self._match(TokenType.NOT) - - if self._match_text_seq("DISTINCT", "FROM"): - klass = exp.NullSafeEQ if negate else exp.NullSafeNEQ - return self.expression(klass, this=this, expression=self._parse_bitwise()) - - if self._match(TokenType.JSON): - kind = ( - self._match_texts(self.IS_JSON_PREDICATE_KIND) - and self._prev.text.upper() - ) - - if self._match_text_seq("WITH"): - _with = True - elif self._match_text_seq("WITHOUT"): - _with = False - else: - _with = None - - unique = self._match(TokenType.UNIQUE) - self._match_text_seq("KEYS") - expression: t.Optional[exp.Expression] = self.expression( - exp.JSON, - this=kind, - with_=_with, - unique=unique, - ) - else: - expression = self._parse_null() or self._parse_bitwise() - if not expression: - self._retreat(index) - return None - - this = self.expression(exp.Is, this=this, expression=expression) - this = self.expression(exp.Not, this=this) if negate else this - return self._parse_column_ops(this) - - def _parse_in( - self, this: t.Optional[exp.Expression], alias: bool = False - ) -> exp.In: - unnest = self._parse_unnest(with_alias=False) - if unnest: - this = self.expression(exp.In, this=this, unnest=unnest) - elif self._match_set((TokenType.L_PAREN, TokenType.L_BRACKET)): - matched_l_paren = self._prev.token_type == TokenType.L_PAREN - expressions = self._parse_csv( - lambda: self._parse_select_or_expression(alias=alias) - ) - - if len(expressions) == 1 and isinstance(query := expressions[0], exp.Query): - this = self.expression( - exp.In, - this=this, - query=self._parse_query_modifiers(query).subquery(copy=False), - ) - else: - this = self.expression(exp.In, this=this, expressions=expressions) - - if matched_l_paren: - self._match_r_paren(this) - elif not self._match(TokenType.R_BRACKET, expression=this): - self.raise_error("Expecting ]") - else: - this = self.expression(exp.In, this=this, field=self._parse_column()) - - return this - - def _parse_between(self, this: t.Optional[exp.Expression]) -> exp.Between: - symmetric = None - if self._match_text_seq("SYMMETRIC"): - symmetric = True - elif self._match_text_seq("ASYMMETRIC"): - symmetric = False - - low = self._parse_bitwise() - self._match(TokenType.AND) - high = self._parse_bitwise() - - return self.expression( - exp.Between, - this=this, - low=low, - high=high, - symmetric=symmetric, - ) - - def _parse_escape( - self, this: t.Optional[exp.Expression] - ) -> t.Optional[exp.Expression]: - if not self._match(TokenType.ESCAPE): - return this - return self.expression( - exp.Escape, this=this, expression=self._parse_string() or self._parse_null() - ) - - def _parse_interval( - self, match_interval: bool = True - ) -> t.Optional[exp.Add | exp.Interval]: - index = self._index - - if not self._match(TokenType.INTERVAL) and match_interval: - return None - - if self._match(TokenType.STRING, advance=False): - this = self._parse_primary() - else: - this = self._parse_term() - - if not this or ( - isinstance(this, exp.Column) - and not this.table - and not this.this.quoted - and self._curr - and self._curr.text.upper() not in self.dialect.VALID_INTERVAL_UNITS - ): - self._retreat(index) - return None - - # handle day-time format interval span with omitted units: - # INTERVAL ' hh[:][mm[:ss[.ff]]]' - interval_span_units_omitted = None - if ( - this - and this.is_string - and self.SUPPORTS_OMITTED_INTERVAL_SPAN_UNIT - and exp.INTERVAL_DAY_TIME_RE.match(this.name) - ): - index = self._index - - # Var "TO" Var - first_unit = self._parse_var(any_token=True, upper=True) - second_unit = None - if first_unit and self._match_text_seq("TO"): - second_unit = self._parse_var(any_token=True, upper=True) - - interval_span_units_omitted = not (first_unit and second_unit) - - self._retreat(index) - - unit = ( - None - if interval_span_units_omitted - else ( - self._parse_function() - or ( - not self._match(TokenType.ALIAS, advance=False) - and self._parse_var(any_token=True, upper=True) - ) - ) - ) - - # Most dialects support, e.g., the form INTERVAL '5' day, thus we try to parse - # each INTERVAL expression into this canonical form so it's easy to transpile - if this and this.is_number: - this = exp.Literal.string(this.to_py()) - elif this and this.is_string: - parts = exp.INTERVAL_STRING_RE.findall(this.name) - if parts and unit: - # Unconsume the eagerly-parsed unit, since the real unit was part of the string - unit = None - self._retreat(self._index - 1) - - if len(parts) == 1: - this = exp.Literal.string(parts[0][0]) - unit = self.expression(exp.Var, this=parts[0][1].upper()) - - if self.INTERVAL_SPANS and self._match_text_seq("TO"): - unit = self.expression( - exp.IntervalSpan, - this=unit, - expression=self._parse_var(any_token=True, upper=True), - ) - - interval = self.expression(exp.Interval, this=this, unit=unit) - - index = self._index - self._match(TokenType.PLUS) - - # Convert INTERVAL 'val_1' unit_1 [+] ... [+] 'val_n' unit_n into a sum of intervals - if self._match_set((TokenType.STRING, TokenType.NUMBER), advance=False): - return self.expression( - exp.Add, - this=interval, - expression=self._parse_interval(match_interval=False), - ) - - self._retreat(index) - return interval - - def _parse_bitwise(self) -> t.Optional[exp.Expression]: - this = self._parse_term() - - while True: - if self._match_set(self.BITWISE): - this = self.expression( - self.BITWISE[self._prev.token_type], - this=this, - expression=self._parse_term(), - ) - elif self.dialect.DPIPE_IS_STRING_CONCAT and self._match(TokenType.DPIPE): - this = self.expression( - exp.DPipe, - this=this, - expression=self._parse_term(), - safe=not self.dialect.STRICT_STRING_CONCAT, - ) - elif self._match(TokenType.DQMARK): - this = self.expression( - exp.Coalesce, this=this, expressions=ensure_list(self._parse_term()) - ) - elif self._match_pair(TokenType.LT, TokenType.LT): - this = self.expression( - exp.BitwiseLeftShift, this=this, expression=self._parse_term() - ) - elif self._match_pair(TokenType.GT, TokenType.GT): - this = self.expression( - exp.BitwiseRightShift, this=this, expression=self._parse_term() - ) - else: - break - - return this - - def _parse_term(self) -> t.Optional[exp.Expression]: - this = self._parse_factor() - - while self._match_set(self.TERM): - klass = self.TERM[self._prev.token_type] - comments = self._prev_comments - expression = self._parse_factor() - - this = self.expression( - klass, this=this, comments=comments, expression=expression - ) - - if isinstance(this, exp.Collate): - expr = this.expression - - # Preserve collations such as pg_catalog."default" (Postgres) as columns, otherwise - # fallback to Identifier / Var - if isinstance(expr, exp.Column) and len(expr.parts) == 1: - ident = expr.this - if isinstance(ident, exp.Identifier): - this.set( - "expression", ident if ident.quoted else exp.var(ident.name) - ) - - return this - - def _parse_factor(self) -> t.Optional[exp.Expression]: - parse_method = self._parse_exponent if self.EXPONENT else self._parse_unary - this = self._parse_at_time_zone(parse_method()) - - while self._match_set(self.FACTOR): - klass = self.FACTOR[self._prev.token_type] - comments = self._prev_comments - expression = parse_method() - - if not expression and klass is exp.IntDiv and self._prev.text.isalpha(): - self._retreat(self._index - 1) - return this - - this = self.expression( - klass, this=this, comments=comments, expression=expression - ) - - if isinstance(this, exp.Div): - this.set("typed", self.dialect.TYPED_DIVISION) - this.set("safe", self.dialect.SAFE_DIVISION) - - return this - - def _parse_exponent(self) -> t.Optional[exp.Expression]: - return self._parse_tokens(self._parse_unary, self.EXPONENT) - - def _parse_unary(self) -> t.Optional[exp.Expression]: - if self._match_set(self.UNARY_PARSERS): - return self.UNARY_PARSERS[self._prev.token_type](self) - return self._parse_type() - - def _parse_type( - self, parse_interval: bool = True, fallback_to_identifier: bool = False - ) -> t.Optional[exp.Expression]: - interval = parse_interval and self._parse_interval() - if interval: - return self._parse_column_ops(interval) - - index = self._index - data_type = self._parse_types(check_func=True, allow_identifiers=False) - - # parse_types() returns a Cast if we parsed BQ's inline constructor () e.g. - # STRUCT(1, 'foo'), which is canonicalized to CAST( AS ) - if isinstance(data_type, exp.Cast): - # This constructor can contain ops directly after it, for instance struct unnesting: - # STRUCT(1, 'foo').* --> CAST(STRUCT(1, 'foo') AS STRUCT 1: - self._retreat(index2) - return self._parse_column_ops(data_type) - - self._retreat(index) - - if fallback_to_identifier: - return self._parse_id_var() - - this = self._parse_column() - return this and self._parse_column_ops(this) - - def _parse_type_size(self) -> t.Optional[exp.DataTypeParam]: - this = self._parse_type() - if not this: - return None - - if isinstance(this, exp.Column) and not this.table: - this = exp.var(this.name.upper()) - - return self.expression( - exp.DataTypeParam, this=this, expression=self._parse_var(any_token=True) - ) - - def _parse_user_defined_type( - self, identifier: exp.Identifier - ) -> t.Optional[exp.Expression]: - type_name = identifier.name - - while self._match(TokenType.DOT): - type_name = f"{type_name}.{self._advance_any() and self._prev.text}" - - return exp.DataType.build(type_name, dialect=self.dialect, udt=True) - - def _parse_types( - self, - check_func: bool = False, - schema: bool = False, - allow_identifiers: bool = True, - ) -> t.Optional[exp.Expression]: - index = self._index - - this: t.Optional[exp.Expression] = None - prefix = self._match_text_seq("SYSUDTLIB", ".") - - if self._match_set(self.TYPE_TOKENS): - type_token = self._prev.token_type - else: - type_token = None - identifier = allow_identifiers and self._parse_id_var( - any_token=False, tokens=(TokenType.VAR,) - ) - if isinstance(identifier, exp.Identifier): - try: - tokens = self.dialect.tokenize(identifier.name) - except TokenError: - tokens = None - - if ( - tokens - and len(tokens) == 1 - and tokens[0].token_type in self.TYPE_TOKENS - ): - type_token = tokens[0].token_type - elif self.dialect.SUPPORTS_USER_DEFINED_TYPES: - this = self._parse_user_defined_type(identifier) - else: - self._retreat(self._index - 1) - return None - else: - return None - - if type_token == TokenType.PSEUDO_TYPE: - return self.expression(exp.PseudoType, this=self._prev.text.upper()) - - if type_token == TokenType.OBJECT_IDENTIFIER: - return self.expression(exp.ObjectIdentifier, this=self._prev.text.upper()) - - # https://materialize.com/docs/sql/types/map/ - if type_token == TokenType.MAP and self._match(TokenType.L_BRACKET): - key_type = self._parse_types( - check_func=check_func, - schema=schema, - allow_identifiers=allow_identifiers, - ) - if not self._match(TokenType.FARROW): - self._retreat(index) - return None - - value_type = self._parse_types( - check_func=check_func, - schema=schema, - allow_identifiers=allow_identifiers, - ) - if not self._match(TokenType.R_BRACKET): - self._retreat(index) - return None - - return exp.DataType( - this=exp.DataType.Type.MAP, - expressions=[key_type, value_type], - nested=True, - prefix=prefix, - ) - - nested = type_token in self.NESTED_TYPE_TOKENS - is_struct = type_token in self.STRUCT_TYPE_TOKENS - is_aggregate = type_token in self.AGGREGATE_TYPE_TOKENS - expressions = None - maybe_func = False - - if self._match(TokenType.L_PAREN): - if is_struct: - expressions = self._parse_csv( - lambda: self._parse_struct_types(type_required=True) - ) - elif nested: - expressions = self._parse_csv( - lambda: self._parse_types( - check_func=check_func, - schema=schema, - allow_identifiers=allow_identifiers, - ) - ) - if type_token == TokenType.NULLABLE and len(expressions) == 1: - this = expressions[0] - this.set("nullable", True) - self._match_r_paren() - return this - elif type_token in self.ENUM_TYPE_TOKENS: - expressions = self._parse_csv(self._parse_equality) - elif is_aggregate: - func_or_ident = self._parse_function( - anonymous=True - ) or self._parse_id_var( - any_token=False, tokens=(TokenType.VAR, TokenType.ANY) - ) - if not func_or_ident: - return None - expressions = [func_or_ident] - if self._match(TokenType.COMMA): - expressions.extend( - self._parse_csv( - lambda: self._parse_types( - check_func=check_func, - schema=schema, - allow_identifiers=allow_identifiers, - ) - ) - ) - else: - expressions = self._parse_csv(self._parse_type_size) - - # https://docs.snowflake.com/en/sql-reference/data-types-vector - if type_token == TokenType.VECTOR and len(expressions) == 2: - expressions = self._parse_vector_expressions(expressions) - - if not self._match(TokenType.R_PAREN): - self._retreat(index) - return None - - maybe_func = True - - values: t.Optional[t.List[exp.Expression]] = None - - if nested and self._match(TokenType.LT): - if is_struct: - expressions = self._parse_csv( - lambda: self._parse_struct_types(type_required=True) - ) - else: - expressions = self._parse_csv( - lambda: self._parse_types( - check_func=check_func, - schema=schema, - allow_identifiers=allow_identifiers, - ) - ) - - if not self._match(TokenType.GT): - self.raise_error("Expecting >") - - if self._match_set((TokenType.L_BRACKET, TokenType.L_PAREN)): - values = self._parse_csv(self._parse_disjunction) - if not values and is_struct: - values = None - self._retreat(self._index - 1) - else: - self._match_set((TokenType.R_BRACKET, TokenType.R_PAREN)) - - if type_token in self.TIMESTAMPS: - if self._match_text_seq("WITH", "TIME", "ZONE"): - maybe_func = False - tz_type = ( - exp.DataType.Type.TIMETZ - if type_token in self.TIMES - else exp.DataType.Type.TIMESTAMPTZ - ) - this = exp.DataType(this=tz_type, expressions=expressions) - elif self._match_text_seq("WITH", "LOCAL", "TIME", "ZONE"): - maybe_func = False - this = exp.DataType( - this=exp.DataType.Type.TIMESTAMPLTZ, expressions=expressions - ) - elif self._match_text_seq("WITHOUT", "TIME", "ZONE"): - maybe_func = False - elif type_token == TokenType.INTERVAL: - unit = self._parse_var(upper=True) - if unit: - if self._match_text_seq("TO"): - unit = exp.IntervalSpan( - this=unit, expression=self._parse_var(upper=True) - ) - - this = self.expression( - exp.DataType, this=self.expression(exp.Interval, unit=unit) - ) - else: - this = self.expression(exp.DataType, this=exp.DataType.Type.INTERVAL) - elif type_token == TokenType.VOID: - this = exp.DataType(this=exp.DataType.Type.NULL) - - if maybe_func and check_func: - index2 = self._index - peek = self._parse_string() - - if not peek: - self._retreat(index) - return None - - self._retreat(index2) - - if not this: - if self._match_text_seq("UNSIGNED"): - unsigned_type_token = self.SIGNED_TO_UNSIGNED_TYPE_TOKEN.get(type_token) - if not unsigned_type_token: - self.raise_error(f"Cannot convert {type_token.value} to unsigned.") - - type_token = unsigned_type_token or type_token - - # NULLABLE without parentheses can be a column (Presto/Trino) - if type_token == TokenType.NULLABLE and not expressions: - self._retreat(index) - return None - - this = exp.DataType( - this=exp.DataType.Type[type_token.value], - expressions=expressions, - nested=nested, - prefix=prefix, - ) - - # Empty arrays/structs are allowed - if values is not None: - cls = exp.Struct if is_struct else exp.Array - this = exp.cast(cls(expressions=values), this, copy=False) - - elif expressions: - this.set("expressions", expressions) - - # https://materialize.com/docs/sql/types/list/#type-name - while self._match(TokenType.LIST): - this = exp.DataType( - this=exp.DataType.Type.LIST, expressions=[this], nested=True - ) - - index = self._index - - # Postgres supports the INT ARRAY[3] syntax as a synonym for INT[3] - matched_array = self._match(TokenType.ARRAY) - - while self._curr: - datatype_token = self._prev.token_type - matched_l_bracket = self._match(TokenType.L_BRACKET) - - if (not matched_l_bracket and not matched_array) or ( - datatype_token == TokenType.ARRAY and self._match(TokenType.R_BRACKET) - ): - # Postgres allows casting empty arrays such as ARRAY[]::INT[], - # not to be confused with the fixed size array parsing - break - - matched_array = False - values = self._parse_csv(self._parse_disjunction) or None - if ( - values - and not schema - and ( - not self.dialect.SUPPORTS_FIXED_SIZE_ARRAYS - or datatype_token == TokenType.ARRAY - or not self._match(TokenType.R_BRACKET, advance=False) - ) - ): - # Retreating here means that we should not parse the following values as part of the data type, e.g. in DuckDB - # ARRAY[1] should retreat and instead be parsed into exp.Array in contrast to INT[x][y] which denotes a fixed-size array data type - self._retreat(index) - break - - this = exp.DataType( - this=exp.DataType.Type.ARRAY, - expressions=[this], - values=values, - nested=True, - ) - self._match(TokenType.R_BRACKET) - - if self.TYPE_CONVERTERS and isinstance(this.this, exp.DataType.Type): - converter = self.TYPE_CONVERTERS.get(this.this) - if converter: - this = converter(t.cast(exp.DataType, this)) - - return this - - def _parse_vector_expressions( - self, expressions: t.List[exp.Expression] - ) -> t.List[exp.Expression]: - return [ - exp.DataType.build(expressions[0].name, dialect=self.dialect), - *expressions[1:], - ] - - def _parse_struct_types( - self, type_required: bool = False - ) -> t.Optional[exp.Expression]: - index = self._index - - if ( - self._curr - and self._next - and self._curr.token_type in self.TYPE_TOKENS - and self._next.token_type in self.TYPE_TOKENS - ): - # Takes care of special cases like `STRUCT>` where the identifier is also a - # type token. Without this, the list will be parsed as a type and we'll eventually crash - this = self._parse_id_var() - else: - this = ( - self._parse_type(parse_interval=False, fallback_to_identifier=True) - or self._parse_id_var() - ) - - self._match(TokenType.COLON) - - if ( - type_required - and not isinstance(this, exp.DataType) - and not self._match_set(self.TYPE_TOKENS, advance=False) - ): - self._retreat(index) - return self._parse_types() - - return self._parse_column_def(this) - - def _parse_at_time_zone( - self, this: t.Optional[exp.Expression] - ) -> t.Optional[exp.Expression]: - if not self._match_text_seq("AT", "TIME", "ZONE"): - return this - return self._parse_at_time_zone( - self.expression(exp.AtTimeZone, this=this, zone=self._parse_unary()) - ) - - def _parse_column(self) -> t.Optional[exp.Expression]: - this = self._parse_column_reference() - column = self._parse_column_ops(this) if this else self._parse_bracket(this) - - if self.dialect.SUPPORTS_COLUMN_JOIN_MARKS and column: - column.set("join_mark", self._match(TokenType.JOIN_MARKER)) - - return column - - def _parse_column_reference(self) -> t.Optional[exp.Expression]: - this = self._parse_field() - if ( - not this - and self._match(TokenType.VALUES, advance=False) - and self.VALUES_FOLLOWED_BY_PAREN - and (not self._next or self._next.token_type != TokenType.L_PAREN) - ): - this = self._parse_id_var() - - if isinstance(this, exp.Identifier): - # We bubble up comments from the Identifier to the Column - this = self.expression(exp.Column, comments=this.pop_comments(), this=this) - - return this - - def _parse_colon_as_variant_extract( - self, this: t.Optional[exp.Expression] - ) -> t.Optional[exp.Expression]: - casts = [] - json_path = [] - escape = None - - while self._match(TokenType.COLON): - start_index = self._index - - # Snowflake allows reserved keywords as json keys but advance_any() excludes TokenType.SELECT from any_tokens=True - path = self._parse_column_ops( - self._parse_field(any_token=True, tokens=(TokenType.SELECT,)) - ) - - # The cast :: operator has a lower precedence than the extraction operator :, so - # we rearrange the AST appropriately to avoid casting the JSON path - while isinstance(path, exp.Cast): - casts.append(path.to) - path = path.this - - if casts: - dcolon_offset = next( - i - for i, t in enumerate(self._tokens[start_index:]) - if t.token_type == TokenType.DCOLON - ) - end_token = self._tokens[start_index + dcolon_offset - 1] - else: - end_token = self._prev - - if path: - # Escape single quotes from Snowflake's colon extraction (e.g. col:"a'b") as - # it'll roundtrip to a string literal in GET_PATH - if isinstance(path, exp.Identifier) and path.quoted: - escape = True - - json_path.append(self._find_sql(self._tokens[start_index], end_token)) - - # The VARIANT extract in Snowflake/Databricks is parsed as a JSONExtract; Snowflake uses the json_path in GET_PATH() while - # Databricks transforms it back to the colon/dot notation - if json_path: - json_path_expr = self.dialect.to_json_path( - exp.Literal.string(".".join(json_path)) - ) - - if json_path_expr: - json_path_expr.set("escape", escape) - - this = self.expression( - exp.JSONExtract, - this=this, - expression=json_path_expr, - variant_extract=True, - requires_json=self.JSON_EXTRACT_REQUIRES_JSON_EXPRESSION, - ) - - while casts: - this = self.expression(exp.Cast, this=this, to=casts.pop()) - - return this - - def _parse_dcolon(self) -> t.Optional[exp.Expression]: - return self._parse_types() - - def _parse_column_ops( - self, this: t.Optional[exp.Expression] - ) -> t.Optional[exp.Expression]: - this = self._parse_bracket(this) - - while self._match_set(self.COLUMN_OPERATORS): - op_token = self._prev.token_type - op = self.COLUMN_OPERATORS.get(op_token) - - if op_token in self.CAST_COLUMN_OPERATORS: - field = self._parse_dcolon() - if not field: - self.raise_error("Expected type") - elif op and self._curr: - field = self._parse_column_reference() or self._parse_bitwise() - if isinstance(field, exp.Column) and self._match( - TokenType.DOT, advance=False - ): - field = self._parse_column_ops(field) - else: - field = self._parse_field(any_token=True, anonymous_func=True) - - # Function calls can be qualified, e.g., x.y.FOO() - # This converts the final AST to a series of Dots leading to the function call - # https://cloud.google.com/bigquery/docs/reference/standard-sql/functions-reference#function_call_rules - if isinstance(field, (exp.Func, exp.Window)) and this: - this = this.transform( - lambda n: ( - n.to_dot(include_dots=False) if isinstance(n, exp.Column) else n - ) - ) - - if op: - this = op(self, this, field) - elif isinstance(this, exp.Column) and not this.args.get("catalog"): - this = self.expression( - exp.Column, - comments=this.comments, - this=field, - table=this.this, - db=this.args.get("table"), - catalog=this.args.get("db"), - ) - elif isinstance(field, exp.Window): - # Move the exp.Dot's to the window's function - window_func = self.expression(exp.Dot, this=this, expression=field.this) - field.set("this", window_func) - this = field - else: - this = self.expression(exp.Dot, this=this, expression=field) - - if field and field.comments: - t.cast(exp.Expression, this).add_comments(field.pop_comments()) - - this = self._parse_bracket(this) - - return ( - self._parse_colon_as_variant_extract(this) - if self.COLON_IS_VARIANT_EXTRACT - else this - ) - - def _parse_paren(self) -> t.Optional[exp.Expression]: - if not self._match(TokenType.L_PAREN): - return None - - comments = self._prev_comments - query = self._parse_select() - - if query: - expressions = [query] - else: - expressions = self._parse_expressions() - - this = seq_get(expressions, 0) - - if not this and self._match(TokenType.R_PAREN, advance=False): - this = self.expression(exp.Tuple) - elif isinstance(this, exp.UNWRAPPED_QUERIES): - this = self._parse_subquery(this=this, parse_alias=False) - elif isinstance(this, (exp.Subquery, exp.Values)): - this = self._parse_subquery( - this=self._parse_query_modifiers(self._parse_set_operations(this)), - parse_alias=False, - ) - elif len(expressions) > 1 or self._prev.token_type == TokenType.COMMA: - this = self.expression(exp.Tuple, expressions=expressions) - else: - this = self.expression(exp.Paren, this=this) - - if this: - this.add_comments(comments) - - self._match_r_paren(expression=this) - - if isinstance(this, exp.Paren) and isinstance(this.this, exp.AggFunc): - return self._parse_window(this) - - return this - - def _parse_primary(self) -> t.Optional[exp.Expression]: - if self._match_set(self.PRIMARY_PARSERS): - token_type = self._prev.token_type - primary = self.PRIMARY_PARSERS[token_type](self, self._prev) - - if token_type == TokenType.STRING: - expressions = [primary] - while self._match(TokenType.STRING): - expressions.append(exp.Literal.string(self._prev.text)) - - if len(expressions) > 1: - return self.expression( - exp.Concat, - expressions=expressions, - coalesce=self.dialect.CONCAT_COALESCE, - ) - - return primary - - if self._match_pair(TokenType.DOT, TokenType.NUMBER): - return exp.Literal.number(f"0.{self._prev.text}") - - return self._parse_paren() - - def _parse_field( - self, - any_token: bool = False, - tokens: t.Optional[t.Collection[TokenType]] = None, - anonymous_func: bool = False, - ) -> t.Optional[exp.Expression]: - if anonymous_func: - field = ( - self._parse_function(anonymous=anonymous_func, any_token=any_token) - or self._parse_primary() - ) - else: - field = self._parse_primary() or self._parse_function( - anonymous=anonymous_func, any_token=any_token - ) - return field or self._parse_id_var(any_token=any_token, tokens=tokens) - - def _parse_function( - self, - functions: t.Optional[t.Dict[str, t.Callable]] = None, - anonymous: bool = False, - optional_parens: bool = True, - any_token: bool = False, - ) -> t.Optional[exp.Expression]: - # This allows us to also parse {fn } syntax (Snowflake, MySQL support this) - # See: https://community.snowflake.com/s/article/SQL-Escape-Sequences - fn_syntax = False - if ( - self._match(TokenType.L_BRACE, advance=False) - and self._next - and self._next.text.upper() == "FN" - ): - self._advance(2) - fn_syntax = True - - func = self._parse_function_call( - functions=functions, - anonymous=anonymous, - optional_parens=optional_parens, - any_token=any_token, - ) - - if fn_syntax: - self._match(TokenType.R_BRACE) - - return func - - def _parse_function_args(self, alias: bool = False) -> t.List[exp.Expression]: - return self._parse_csv(lambda: self._parse_lambda(alias=alias)) - - def _parse_function_call( - self, - functions: t.Optional[t.Dict[str, t.Callable]] = None, - anonymous: bool = False, - optional_parens: bool = True, - any_token: bool = False, - ) -> t.Optional[exp.Expression]: - if not self._curr: - return None - - comments = self._curr.comments - prev = self._prev - token = self._curr - token_type = self._curr.token_type - this = self._curr.text - upper = this.upper() - - parser = self.NO_PAREN_FUNCTION_PARSERS.get(upper) - if ( - optional_parens - and parser - and token_type not in self.INVALID_FUNC_NAME_TOKENS - ): - self._advance() - return self._parse_window(parser(self)) - - if not self._next or self._next.token_type != TokenType.L_PAREN: - if optional_parens and token_type in self.NO_PAREN_FUNCTIONS: - self._advance() - return self.expression(self.NO_PAREN_FUNCTIONS[token_type]) - - return None - - if any_token: - if token_type in self.RESERVED_TOKENS: - return None - elif token_type not in self.FUNC_TOKENS: - return None - - self._advance(2) - - parser = self.FUNCTION_PARSERS.get(upper) - if parser and not anonymous: - this = parser(self) - else: - subquery_predicate = self.SUBQUERY_PREDICATES.get(token_type) - - if subquery_predicate: - expr = None - if self._curr.token_type in (TokenType.SELECT, TokenType.WITH): - expr = self._parse_select() - self._match_r_paren() - elif prev and prev.token_type in (TokenType.LIKE, TokenType.ILIKE): - # Backtrack one token since we've consumed the L_PAREN here. Instead, we'd like - # to parse "LIKE [ANY | ALL] (...)" as a whole into an exp.Tuple or exp.Paren - self._advance(-1) - expr = self._parse_bitwise() - - if expr: - return self.expression( - subquery_predicate, comments=comments, this=expr - ) - - if functions is None: - functions = self.FUNCTIONS - - function = functions.get(upper) - known_function = function and not anonymous - - alias = not known_function or upper in self.FUNCTIONS_WITH_ALIASED_ARGS - args = self._parse_function_args(alias) - - post_func_comments = self._curr and self._curr.comments - if known_function and post_func_comments: - # If the user-inputted comment "/* sqlglot.anonymous */" is following the function - # call we'll construct it as exp.Anonymous, even if it's "known" - if any( - comment.lstrip().startswith(exp.SQLGLOT_ANONYMOUS) - for comment in post_func_comments - ): - known_function = False - - if alias and known_function: - args = self._kv_to_prop_eq(args) - - if known_function: - func_builder = t.cast(t.Callable, function) - - if "dialect" in func_builder.__code__.co_varnames: - func = func_builder(args, dialect=self.dialect) - else: - func = func_builder(args) - - func = self.validate_expression(func, args) - if self.dialect.PRESERVE_ORIGINAL_NAMES: - func.meta["name"] = this - - this = func - else: - if token_type == TokenType.IDENTIFIER: - this = exp.Identifier(this=this, quoted=True).update_positions( - token - ) - - this = self.expression(exp.Anonymous, this=this, expressions=args) - - this = this.update_positions(token) - - if isinstance(this, exp.Expression): - this.add_comments(comments) - - self._match_r_paren(this) - return self._parse_window(this) - - def _to_prop_eq(self, expression: exp.Expression, index: int) -> exp.Expression: - return expression - - def _kv_to_prop_eq( - self, expressions: t.List[exp.Expression], parse_map: bool = False - ) -> t.List[exp.Expression]: - transformed = [] - - for index, e in enumerate(expressions): - if isinstance(e, self.KEY_VALUE_DEFINITIONS): - if isinstance(e, exp.Alias): - e = self.expression( - exp.PropertyEQ, this=e.args.get("alias"), expression=e.this - ) - - if not isinstance(e, exp.PropertyEQ): - e = self.expression( - exp.PropertyEQ, - this=e.this if parse_map else exp.to_identifier(e.this.name), - expression=e.expression, - ) - - if isinstance(e.this, exp.Column): - e.this.replace(e.this.this) - else: - e = self._to_prop_eq(e, index) - - transformed.append(e) - - return transformed - - def _parse_user_defined_function_expression(self) -> t.Optional[exp.Expression]: - return self._parse_statement() - - def _parse_function_parameter(self) -> t.Optional[exp.Expression]: - return self._parse_column_def(this=self._parse_id_var(), computed_column=False) - - def _parse_user_defined_function( - self, kind: t.Optional[TokenType] = None - ) -> t.Optional[exp.Expression]: - this = self._parse_table_parts(schema=True) - - if not self._match(TokenType.L_PAREN): - return this - - expressions = self._parse_csv(self._parse_function_parameter) - self._match_r_paren() - return self.expression( - exp.UserDefinedFunction, this=this, expressions=expressions, wrapped=True - ) - - def _parse_introducer(self, token: Token) -> exp.Introducer | exp.Identifier: - literal = self._parse_primary() - if literal: - return self.expression(exp.Introducer, token=token, expression=literal) - - return self._identifier_expression(token) - - def _parse_session_parameter(self) -> exp.SessionParameter: - kind = None - this = self._parse_id_var() or self._parse_primary() - - if this and self._match(TokenType.DOT): - kind = this.name - this = self._parse_var() or self._parse_primary() - - return self.expression(exp.SessionParameter, this=this, kind=kind) - - def _parse_lambda_arg(self) -> t.Optional[exp.Expression]: - return self._parse_id_var() - - def _parse_lambda(self, alias: bool = False) -> t.Optional[exp.Expression]: - index = self._index - - if self._match(TokenType.L_PAREN): - expressions = t.cast( - t.List[t.Optional[exp.Expression]], - self._parse_csv(self._parse_lambda_arg), - ) - - if not self._match(TokenType.R_PAREN): - self._retreat(index) - else: - expressions = [self._parse_lambda_arg()] - - if self._match_set(self.LAMBDAS): - return self.LAMBDAS[self._prev.token_type](self, expressions) - - self._retreat(index) - - this: t.Optional[exp.Expression] - - if self._match(TokenType.DISTINCT): - this = self.expression( - exp.Distinct, expressions=self._parse_csv(self._parse_disjunction) - ) - else: - this = self._parse_select_or_expression(alias=alias) - - return self._parse_limit( - self._parse_order( - self._parse_having_max(self._parse_respect_or_ignore_nulls(this)) - ) - ) - - def _parse_schema( - self, this: t.Optional[exp.Expression] = None - ) -> t.Optional[exp.Expression]: - index = self._index - if not self._match(TokenType.L_PAREN): - return this - - # Disambiguate between schema and subquery/CTE, e.g. in INSERT INTO table (), - # expr can be of both types - if self._match_set(self.SELECT_START_TOKENS): - self._retreat(index) - return this - args = self._parse_csv( - lambda: self._parse_constraint() or self._parse_field_def() - ) - self._match_r_paren() - return self.expression(exp.Schema, this=this, expressions=args) - - def _parse_field_def(self) -> t.Optional[exp.Expression]: - return self._parse_column_def(self._parse_field(any_token=True)) - - def _parse_column_def( - self, this: t.Optional[exp.Expression], computed_column: bool = True - ) -> t.Optional[exp.Expression]: - # column defs are not really columns, they're identifiers - if isinstance(this, exp.Column): - this = this.this - - if not computed_column: - self._match(TokenType.ALIAS) - - kind = self._parse_types(schema=True) - - if self._match_text_seq("FOR", "ORDINALITY"): - return self.expression(exp.ColumnDef, this=this, ordinality=True) - - constraints: t.List[exp.Expression] = [] - - if (not kind and self._match(TokenType.ALIAS)) or self._match_texts( - ("ALIAS", "MATERIALIZED") - ): - persisted = self._prev.text.upper() == "MATERIALIZED" - constraint_kind = exp.ComputedColumnConstraint( - this=self._parse_disjunction(), - persisted=persisted or self._match_text_seq("PERSISTED"), - data_type=exp.Var(this="AUTO") - if self._match_text_seq("AUTO") - else self._parse_types(), - not_null=self._match_pair(TokenType.NOT, TokenType.NULL), - ) - constraints.append( - self.expression(exp.ColumnConstraint, kind=constraint_kind) - ) - elif ( - kind - and self._match(TokenType.ALIAS, advance=False) - and ( - not self.WRAPPED_TRANSFORM_COLUMN_CONSTRAINT - or (self._next and self._next.token_type == TokenType.L_PAREN) - ) - ): - self._advance() - constraints.append( - self.expression( - exp.ColumnConstraint, - kind=exp.ComputedColumnConstraint( - this=self._parse_disjunction(), - persisted=self._match_texts(("STORED", "VIRTUAL")) - and self._prev.text.upper() == "STORED", - ), - ) - ) - - while True: - constraint = self._parse_column_constraint() - if not constraint: - break - constraints.append(constraint) - - if not kind and not constraints: - return this - - return self.expression( - exp.ColumnDef, this=this, kind=kind, constraints=constraints - ) - - def _parse_auto_increment( - self, - ) -> exp.GeneratedAsIdentityColumnConstraint | exp.AutoIncrementColumnConstraint: - start = None - increment = None - order = None - - if self._match(TokenType.L_PAREN, advance=False): - args = self._parse_wrapped_csv(self._parse_bitwise) - start = seq_get(args, 0) - increment = seq_get(args, 1) - elif self._match_text_seq("START"): - start = self._parse_bitwise() - self._match_text_seq("INCREMENT") - increment = self._parse_bitwise() - if self._match_text_seq("ORDER"): - order = True - elif self._match_text_seq("NOORDER"): - order = False - - if start and increment: - return exp.GeneratedAsIdentityColumnConstraint( - start=start, increment=increment, this=False, order=order - ) - - return exp.AutoIncrementColumnConstraint() - - def _parse_auto_property(self) -> t.Optional[exp.AutoRefreshProperty]: - if not self._match_text_seq("REFRESH"): - self._retreat(self._index - 1) - return None - return self.expression( - exp.AutoRefreshProperty, this=self._parse_var(upper=True) - ) - - def _parse_compress(self) -> exp.CompressColumnConstraint: - if self._match(TokenType.L_PAREN, advance=False): - return self.expression( - exp.CompressColumnConstraint, - this=self._parse_wrapped_csv(self._parse_bitwise), - ) - - return self.expression(exp.CompressColumnConstraint, this=self._parse_bitwise()) - - def _parse_generated_as_identity( - self, - ) -> ( - exp.GeneratedAsIdentityColumnConstraint - | exp.ComputedColumnConstraint - | exp.GeneratedAsRowColumnConstraint - ): - if self._match_text_seq("BY", "DEFAULT"): - on_null = self._match_pair(TokenType.ON, TokenType.NULL) - this = self.expression( - exp.GeneratedAsIdentityColumnConstraint, this=False, on_null=on_null - ) - else: - self._match_text_seq("ALWAYS") - this = self.expression(exp.GeneratedAsIdentityColumnConstraint, this=True) - - self._match(TokenType.ALIAS) - - if self._match_text_seq("ROW"): - start = self._match_text_seq("START") - if not start: - self._match(TokenType.END) - hidden = self._match_text_seq("HIDDEN") - return self.expression( - exp.GeneratedAsRowColumnConstraint, start=start, hidden=hidden - ) - - identity = self._match_text_seq("IDENTITY") - - if self._match(TokenType.L_PAREN): - if self._match(TokenType.START_WITH): - this.set("start", self._parse_bitwise()) - if self._match_text_seq("INCREMENT", "BY"): - this.set("increment", self._parse_bitwise()) - if self._match_text_seq("MINVALUE"): - this.set("minvalue", self._parse_bitwise()) - if self._match_text_seq("MAXVALUE"): - this.set("maxvalue", self._parse_bitwise()) - - if self._match_text_seq("CYCLE"): - this.set("cycle", True) - elif self._match_text_seq("NO", "CYCLE"): - this.set("cycle", False) - - if not identity: - this.set("expression", self._parse_range()) - elif not this.args.get("start") and self._match( - TokenType.NUMBER, advance=False - ): - args = self._parse_csv(self._parse_bitwise) - this.set("start", seq_get(args, 0)) - this.set("increment", seq_get(args, 1)) - - self._match_r_paren() - - return this - - def _parse_inline(self) -> exp.InlineLengthColumnConstraint: - self._match_text_seq("LENGTH") - return self.expression( - exp.InlineLengthColumnConstraint, this=self._parse_bitwise() - ) - - def _parse_not_constraint(self) -> t.Optional[exp.Expression]: - if self._match_text_seq("NULL"): - return self.expression(exp.NotNullColumnConstraint) - if self._match_text_seq("CASESPECIFIC"): - return self.expression(exp.CaseSpecificColumnConstraint, not_=True) - if self._match_text_seq("FOR", "REPLICATION"): - return self.expression(exp.NotForReplicationColumnConstraint) - - # Unconsume the `NOT` token - self._retreat(self._index - 1) - return None - - def _parse_column_constraint(self) -> t.Optional[exp.Expression]: - this = self._match(TokenType.CONSTRAINT) and self._parse_id_var() - - procedure_option_follows = ( - self._match(TokenType.WITH, advance=False) - and self._next - and self._next.text.upper() in self.PROCEDURE_OPTIONS - ) - - if not procedure_option_follows and self._match_texts(self.CONSTRAINT_PARSERS): - return self.expression( - exp.ColumnConstraint, - this=this, - kind=self.CONSTRAINT_PARSERS[self._prev.text.upper()](self), - ) - - return this - - def _parse_constraint(self) -> t.Optional[exp.Expression]: - if not self._match(TokenType.CONSTRAINT): - return self._parse_unnamed_constraint( - constraints=self.SCHEMA_UNNAMED_CONSTRAINTS - ) - - return self.expression( - exp.Constraint, - this=self._parse_id_var(), - expressions=self._parse_unnamed_constraints(), - ) - - def _parse_unnamed_constraints(self) -> t.List[exp.Expression]: - constraints = [] - while True: - constraint = self._parse_unnamed_constraint() or self._parse_function() - if not constraint: - break - constraints.append(constraint) - - return constraints - - def _parse_unnamed_constraint( - self, constraints: t.Optional[t.Collection[str]] = None - ) -> t.Optional[exp.Expression]: - if self._match(TokenType.IDENTIFIER, advance=False) or not self._match_texts( - constraints or self.CONSTRAINT_PARSERS - ): - return None - - constraint = self._prev.text.upper() - if constraint not in self.CONSTRAINT_PARSERS: - self.raise_error(f"No parser found for schema constraint {constraint}.") - - return self.CONSTRAINT_PARSERS[constraint](self) - - def _parse_unique_key(self) -> t.Optional[exp.Expression]: - return self._parse_id_var(any_token=False) - - def _parse_unique(self) -> exp.UniqueColumnConstraint: - self._match_texts(("KEY", "INDEX")) - return self.expression( - exp.UniqueColumnConstraint, - nulls=self._match_text_seq("NULLS", "NOT", "DISTINCT"), - this=self._parse_schema(self._parse_unique_key()), - index_type=self._match(TokenType.USING) - and self._advance_any() - and self._prev.text, - on_conflict=self._parse_on_conflict(), - options=self._parse_key_constraint_options(), - ) - - def _parse_key_constraint_options(self) -> t.List[str]: - options = [] - while True: - if not self._curr: - break - - if self._match(TokenType.ON): - action = None - on = self._advance_any() and self._prev.text - - if self._match_text_seq("NO", "ACTION"): - action = "NO ACTION" - elif self._match_text_seq("CASCADE"): - action = "CASCADE" - elif self._match_text_seq("RESTRICT"): - action = "RESTRICT" - elif self._match_pair(TokenType.SET, TokenType.NULL): - action = "SET NULL" - elif self._match_pair(TokenType.SET, TokenType.DEFAULT): - action = "SET DEFAULT" - else: - self.raise_error("Invalid key constraint") - - options.append(f"ON {on} {action}") - else: - var = self._parse_var_from_options( - self.KEY_CONSTRAINT_OPTIONS, raise_unmatched=False - ) - if not var: - break - options.append(var.name) - - return options - - def _parse_references(self, match: bool = True) -> t.Optional[exp.Reference]: - if match and not self._match(TokenType.REFERENCES): - return None - - expressions = None - this = self._parse_table(schema=True) - options = self._parse_key_constraint_options() - return self.expression( - exp.Reference, this=this, expressions=expressions, options=options - ) - - def _parse_foreign_key(self) -> exp.ForeignKey: - expressions = ( - self._parse_wrapped_id_vars() - if not self._match(TokenType.REFERENCES, advance=False) - else None - ) - reference = self._parse_references() - on_options = {} - - while self._match(TokenType.ON): - if not self._match_set((TokenType.DELETE, TokenType.UPDATE)): - self.raise_error("Expected DELETE or UPDATE") - - kind = self._prev.text.lower() - - if self._match_text_seq("NO", "ACTION"): - action = "NO ACTION" - elif self._match(TokenType.SET): - self._match_set((TokenType.NULL, TokenType.DEFAULT)) - action = "SET " + self._prev.text.upper() - else: - self._advance() - action = self._prev.text.upper() - - on_options[kind] = action - - return self.expression( - exp.ForeignKey, - expressions=expressions, - reference=reference, - options=self._parse_key_constraint_options(), - **on_options, # type: ignore - ) - - def _parse_primary_key_part(self) -> t.Optional[exp.Expression]: - return self._parse_field() - - def _parse_period_for_system_time( - self, - ) -> t.Optional[exp.PeriodForSystemTimeConstraint]: - if not self._match(TokenType.TIMESTAMP_SNAPSHOT): - self._retreat(self._index - 1) - return None - - id_vars = self._parse_wrapped_id_vars() - return self.expression( - exp.PeriodForSystemTimeConstraint, - this=seq_get(id_vars, 0), - expression=seq_get(id_vars, 1), - ) - - def _parse_primary_key( - self, wrapped_optional: bool = False, in_props: bool = False - ) -> exp.PrimaryKeyColumnConstraint | exp.PrimaryKey: - desc = ( - self._match_set((TokenType.ASC, TokenType.DESC)) - and self._prev.token_type == TokenType.DESC - ) - - this = None - if ( - self._curr.text.upper() not in self.CONSTRAINT_PARSERS - and self._next - and self._next.token_type == TokenType.L_PAREN - ): - this = self._parse_id_var() - - if not in_props and not self._match(TokenType.L_PAREN, advance=False): - return self.expression( - exp.PrimaryKeyColumnConstraint, - desc=desc, - options=self._parse_key_constraint_options(), - ) - - expressions = self._parse_wrapped_csv( - self._parse_primary_key_part, optional=wrapped_optional - ) - - return self.expression( - exp.PrimaryKey, - this=this, - expressions=expressions, - include=self._parse_index_params(), - options=self._parse_key_constraint_options(), - ) - - def _parse_bracket_key_value( - self, is_map: bool = False - ) -> t.Optional[exp.Expression]: - return self._parse_slice( - self._parse_alias(self._parse_disjunction(), explicit=True) - ) - - def _parse_odbc_datetime_literal(self) -> exp.Expression: - """ - Parses a datetime column in ODBC format. We parse the column into the corresponding - types, for example `{d'yyyy-mm-dd'}` will be parsed as a `Date` column, exactly the - same as we did for `DATE('yyyy-mm-dd')`. - - Reference: - https://learn.microsoft.com/en-us/sql/odbc/reference/develop-app/date-time-and-timestamp-literals - """ - self._match(TokenType.VAR) - exp_class = self.ODBC_DATETIME_LITERALS[self._prev.text.lower()] - expression = self.expression(exp_class=exp_class, this=self._parse_string()) - if not self._match(TokenType.R_BRACE): - self.raise_error("Expected }") - return expression - - def _parse_bracket( - self, this: t.Optional[exp.Expression] = None - ) -> t.Optional[exp.Expression]: - if not self._match_set((TokenType.L_BRACKET, TokenType.L_BRACE)): - return this - - if self.MAP_KEYS_ARE_ARBITRARY_EXPRESSIONS: - map_token = seq_get(self._tokens, self._index - 2) - parse_map = map_token is not None and map_token.text.upper() == "MAP" - else: - parse_map = False - - bracket_kind = self._prev.token_type - if ( - bracket_kind == TokenType.L_BRACE - and self._curr - and self._curr.token_type == TokenType.VAR - and self._curr.text.lower() in self.ODBC_DATETIME_LITERALS - ): - return self._parse_odbc_datetime_literal() - - expressions = self._parse_csv( - lambda: self._parse_bracket_key_value( - is_map=bracket_kind == TokenType.L_BRACE - ) - ) - - if bracket_kind == TokenType.L_BRACKET and not self._match(TokenType.R_BRACKET): - self.raise_error("Expected ]") - elif bracket_kind == TokenType.L_BRACE and not self._match(TokenType.R_BRACE): - self.raise_error("Expected }") - - # https://duckdb.org/docs/sql/data_types/struct.html#creating-structs - if bracket_kind == TokenType.L_BRACE: - this = self.expression( - exp.Struct, - expressions=self._kv_to_prop_eq( - expressions=expressions, parse_map=parse_map - ), - ) - elif not this: - this = build_array_constructor( - exp.Array, - args=expressions, - bracket_kind=bracket_kind, - dialect=self.dialect, - ) - else: - constructor_type = self.ARRAY_CONSTRUCTORS.get(this.name.upper()) - if constructor_type: - return build_array_constructor( - constructor_type, - args=expressions, - bracket_kind=bracket_kind, - dialect=self.dialect, - ) - - expressions = apply_index_offset( - this, expressions, -self.dialect.INDEX_OFFSET, dialect=self.dialect - ) - this = self.expression( - exp.Bracket, - this=this, - expressions=expressions, - comments=this.pop_comments(), - ) - - self._add_comments(this) - return self._parse_bracket(this) - - def _parse_slice( - self, this: t.Optional[exp.Expression] - ) -> t.Optional[exp.Expression]: - if not self._match(TokenType.COLON): - return this - - if self._match_pair(TokenType.DASH, TokenType.COLON, advance=False): - self._advance() - end: t.Optional[exp.Expression] = -exp.Literal.number("1") - else: - end = self._parse_unary() - step = self._parse_unary() if self._match(TokenType.COLON) else None - return self.expression(exp.Slice, this=this, expression=end, step=step) - - def _parse_case(self) -> t.Optional[exp.Expression]: - if self._match(TokenType.DOT, advance=False): - # Avoid raising on valid expressions like case.*, supported by, e.g., spark & snowflake - self._retreat(self._index - 1) - return None - - ifs = [] - default = None - - comments = self._prev_comments - expression = self._parse_disjunction() - - while self._match(TokenType.WHEN): - this = self._parse_disjunction() - self._match(TokenType.THEN) - then = self._parse_disjunction() - ifs.append(self.expression(exp.If, this=this, true=then)) - - if self._match(TokenType.ELSE): - default = self._parse_disjunction() - - if not self._match(TokenType.END): - if ( - isinstance(default, exp.Interval) - and default.this.sql().upper() == "END" - ): - default = exp.column("interval") - else: - self.raise_error("Expected END after CASE", self._prev) - - return self.expression( - exp.Case, comments=comments, this=expression, ifs=ifs, default=default - ) - - def _parse_if(self) -> t.Optional[exp.Expression]: - if self._match(TokenType.L_PAREN): - args = self._parse_csv( - lambda: self._parse_alias(self._parse_assignment(), explicit=True) - ) - this = self.validate_expression(exp.If.from_arg_list(args), args) - self._match_r_paren() - else: - index = self._index - 1 - - if self.NO_PAREN_IF_COMMANDS and index == 0: - return self._parse_as_command(self._prev) - - condition = self._parse_disjunction() - - if not condition: - self._retreat(index) - return None - - self._match(TokenType.THEN) - true = self._parse_disjunction() - false = self._parse_disjunction() if self._match(TokenType.ELSE) else None - self._match(TokenType.END) - this = self.expression(exp.If, this=condition, true=true, false=false) - - return this - - def _parse_next_value_for(self) -> t.Optional[exp.Expression]: - if not self._match_text_seq("VALUE", "FOR"): - self._retreat(self._index - 1) - return None - - return self.expression( - exp.NextValueFor, - this=self._parse_column(), - order=self._match(TokenType.OVER) - and self._parse_wrapped(self._parse_order), - ) - - def _parse_extract(self) -> exp.Extract | exp.Anonymous: - this = self._parse_function() or self._parse_var_or_string(upper=True) - - if self._match(TokenType.FROM): - return self.expression( - exp.Extract, this=this, expression=self._parse_bitwise() - ) - - if not self._match(TokenType.COMMA): - self.raise_error("Expected FROM or comma after EXTRACT", self._prev) - - return self.expression(exp.Extract, this=this, expression=self._parse_bitwise()) - - def _parse_gap_fill(self) -> exp.GapFill: - self._match(TokenType.TABLE) - this = self._parse_table() - - self._match(TokenType.COMMA) - args = [this, *self._parse_csv(self._parse_lambda)] - - gap_fill = exp.GapFill.from_arg_list(args) - return self.validate_expression(gap_fill, args) - - def _parse_cast( - self, strict: bool, safe: t.Optional[bool] = None - ) -> exp.Expression: - this = self._parse_disjunction() - - if not self._match(TokenType.ALIAS): - if self._match(TokenType.COMMA): - return self.expression( - exp.CastToStrType, this=this, to=self._parse_string() - ) - - self.raise_error("Expected AS after CAST") - - fmt = None - to = self._parse_types() - - default = self._match(TokenType.DEFAULT) - if default: - default = self._parse_bitwise() - self._match_text_seq("ON", "CONVERSION", "ERROR") - - if self._match_set((TokenType.FORMAT, TokenType.COMMA)): - fmt_string = self._parse_string() - fmt = self._parse_at_time_zone(fmt_string) - - if not to: - to = exp.DataType.build(exp.DataType.Type.UNKNOWN) - if to.this in exp.DataType.TEMPORAL_TYPES: - this = self.expression( - exp.StrToDate - if to.this == exp.DataType.Type.DATE - else exp.StrToTime, - this=this, - format=exp.Literal.string( - format_time( - fmt_string.this if fmt_string else "", - self.dialect.FORMAT_MAPPING or self.dialect.TIME_MAPPING, - self.dialect.FORMAT_TRIE or self.dialect.TIME_TRIE, - ) - ), - safe=safe, - ) - - if isinstance(fmt, exp.AtTimeZone) and isinstance(this, exp.StrToTime): - this.set("zone", fmt.args["zone"]) - return this - elif not to: - self.raise_error("Expected TYPE after CAST") - elif isinstance(to, exp.Identifier): - to = exp.DataType.build(to.name, dialect=self.dialect, udt=True) - elif to.this == exp.DataType.Type.CHAR: - if self._match(TokenType.CHARACTER_SET): - to = self.expression(exp.CharacterSet, this=self._parse_var_or_string()) - - return self.build_cast( - strict=strict, - this=this, - to=to, - format=fmt, - safe=safe, - action=self._parse_var_from_options( - self.CAST_ACTIONS, raise_unmatched=False - ), - default=default, - ) - - def _parse_string_agg(self) -> exp.GroupConcat: - if self._match(TokenType.DISTINCT): - args: t.List[t.Optional[exp.Expression]] = [ - self.expression(exp.Distinct, expressions=[self._parse_disjunction()]) - ] - if self._match(TokenType.COMMA): - args.extend(self._parse_csv(self._parse_disjunction)) - else: - args = self._parse_csv(self._parse_disjunction) # type: ignore - - if self._match_text_seq("ON", "OVERFLOW"): - # trino: LISTAGG(expression [, separator] [ON OVERFLOW overflow_behavior]) - if self._match_text_seq("ERROR"): - on_overflow: t.Optional[exp.Expression] = exp.var("ERROR") - else: - self._match_text_seq("TRUNCATE") - on_overflow = self.expression( - exp.OverflowTruncateBehavior, - this=self._parse_string(), - with_count=( - self._match_text_seq("WITH", "COUNT") - or not self._match_text_seq("WITHOUT", "COUNT") - ), - ) - else: - on_overflow = None - - index = self._index - if not self._match(TokenType.R_PAREN) and args: - # postgres: STRING_AGG([DISTINCT] expression, separator [ORDER BY expression1 {ASC | DESC} [, ...]]) - # bigquery: STRING_AGG([DISTINCT] expression [, separator] [ORDER BY key [{ASC | DESC}] [, ... ]] [LIMIT n]) - # The order is parsed through `this` as a canonicalization for WITHIN GROUPs - args[0] = self._parse_limit(this=self._parse_order(this=args[0])) - return self.expression( - exp.GroupConcat, this=args[0], separator=seq_get(args, 1) - ) - - # Checks if we can parse an order clause: WITHIN GROUP (ORDER BY [ASC | DESC]). - # This is done "manually", instead of letting _parse_window parse it into an exp.WithinGroup node, so that - # the STRING_AGG call is parsed like in MySQL / SQLite and can thus be transpiled more easily to them. - if not self._match_text_seq("WITHIN", "GROUP"): - self._retreat(index) - return self.validate_expression(exp.GroupConcat.from_arg_list(args), args) - - # The corresponding match_r_paren will be called in parse_function (caller) - self._match_l_paren() - - return self.expression( - exp.GroupConcat, - this=self._parse_order(this=seq_get(args, 0)), - separator=seq_get(args, 1), - on_overflow=on_overflow, - ) - - def _parse_convert( - self, strict: bool, safe: t.Optional[bool] = None - ) -> t.Optional[exp.Expression]: - this = self._parse_bitwise() - - if self._match(TokenType.USING): - to: t.Optional[exp.Expression] = self.expression( - exp.CharacterSet, this=self._parse_var() - ) - elif self._match(TokenType.COMMA): - to = self._parse_types() - else: - to = None - - return self.build_cast(strict=strict, this=this, to=to, safe=safe) - - def _parse_xml_table(self) -> exp.XMLTable: - namespaces = None - passing = None - columns = None - - if self._match_text_seq("XMLNAMESPACES", "("): - namespaces = self._parse_xml_namespace() - self._match_text_seq(")", ",") - - this = self._parse_string() - - if self._match_text_seq("PASSING"): - # The BY VALUE keywords are optional and are provided for semantic clarity - self._match_text_seq("BY", "VALUE") - passing = self._parse_csv(self._parse_column) - - by_ref = self._match_text_seq("RETURNING", "SEQUENCE", "BY", "REF") - - if self._match_text_seq("COLUMNS"): - columns = self._parse_csv(self._parse_field_def) - - return self.expression( - exp.XMLTable, - this=this, - namespaces=namespaces, - passing=passing, - columns=columns, - by_ref=by_ref, - ) - - def _parse_xml_namespace(self) -> t.List[exp.XMLNamespace]: - namespaces = [] - - while True: - if self._match(TokenType.DEFAULT): - uri = self._parse_string() - else: - uri = self._parse_alias(self._parse_string()) - namespaces.append(self.expression(exp.XMLNamespace, this=uri)) - if not self._match(TokenType.COMMA): - break - - return namespaces - - def _parse_decode(self) -> t.Optional[exp.Decode | exp.DecodeCase]: - args = self._parse_csv(self._parse_disjunction) - - if len(args) < 3: - return self.expression( - exp.Decode, this=seq_get(args, 0), charset=seq_get(args, 1) - ) - - return self.expression(exp.DecodeCase, expressions=args) - - def _parse_json_key_value(self) -> t.Optional[exp.JSONKeyValue]: - self._match_text_seq("KEY") - key = self._parse_column() - self._match_set(self.JSON_KEY_VALUE_SEPARATOR_TOKENS) - self._match_text_seq("VALUE") - value = self._parse_bitwise() - - if not key and not value: - return None - return self.expression(exp.JSONKeyValue, this=key, expression=value) - - def _parse_format_json( - self, this: t.Optional[exp.Expression] - ) -> t.Optional[exp.Expression]: - if not this or not self._match_text_seq("FORMAT", "JSON"): - return this - - return self.expression(exp.FormatJson, this=this) - - def _parse_on_condition(self) -> t.Optional[exp.OnCondition]: - # MySQL uses "X ON EMPTY Y ON ERROR" (e.g. JSON_VALUE) while Oracle uses the opposite (e.g. JSON_EXISTS) - if self.dialect.ON_CONDITION_EMPTY_BEFORE_ERROR: - empty = self._parse_on_handling("EMPTY", *self.ON_CONDITION_TOKENS) - error = self._parse_on_handling("ERROR", *self.ON_CONDITION_TOKENS) - else: - error = self._parse_on_handling("ERROR", *self.ON_CONDITION_TOKENS) - empty = self._parse_on_handling("EMPTY", *self.ON_CONDITION_TOKENS) - - null = self._parse_on_handling("NULL", *self.ON_CONDITION_TOKENS) - - if not empty and not error and not null: - return None - - return self.expression( - exp.OnCondition, - empty=empty, - error=error, - null=null, - ) - - def _parse_on_handling( - self, on: str, *values: str - ) -> t.Optional[str] | t.Optional[exp.Expression]: - # Parses the "X ON Y" or "DEFAULT ON Y syntax, e.g. NULL ON NULL (Oracle, T-SQL, MySQL) - for value in values: - if self._match_text_seq(value, "ON", on): - return f"{value} ON {on}" - - index = self._index - if self._match(TokenType.DEFAULT): - default_value = self._parse_bitwise() - if self._match_text_seq("ON", on): - return default_value - - self._retreat(index) - - return None - - @t.overload - def _parse_json_object(self, agg: Lit[False]) -> exp.JSONObject: ... - - @t.overload - def _parse_json_object(self, agg: Lit[True]) -> exp.JSONObjectAgg: ... - - def _parse_json_object(self, agg=False): - star = self._parse_star() - expressions = ( - [star] - if star - else self._parse_csv( - lambda: self._parse_format_json(self._parse_json_key_value()) - ) - ) - null_handling = self._parse_on_handling("NULL", "NULL", "ABSENT") - - unique_keys = None - if self._match_text_seq("WITH", "UNIQUE"): - unique_keys = True - elif self._match_text_seq("WITHOUT", "UNIQUE"): - unique_keys = False - - self._match_text_seq("KEYS") - - return_type = self._match_text_seq("RETURNING") and self._parse_format_json( - self._parse_type() - ) - encoding = self._match_text_seq("ENCODING") and self._parse_var() - - return self.expression( - exp.JSONObjectAgg if agg else exp.JSONObject, - expressions=expressions, - null_handling=null_handling, - unique_keys=unique_keys, - return_type=return_type, - encoding=encoding, - ) - - # Note: this is currently incomplete; it only implements the "JSON_value_column" part - def _parse_json_column_def(self) -> exp.JSONColumnDef: - if not self._match_text_seq("NESTED"): - this = self._parse_id_var() - ordinality = self._match_pair(TokenType.FOR, TokenType.ORDINALITY) - kind = self._parse_types(allow_identifiers=False) - nested = None - else: - this = None - ordinality = None - kind = None - nested = True - - path = self._match_text_seq("PATH") and self._parse_string() - nested_schema = nested and self._parse_json_schema() - - return self.expression( - exp.JSONColumnDef, - this=this, - kind=kind, - path=path, - nested_schema=nested_schema, - ordinality=ordinality, - ) - - def _parse_json_schema(self) -> exp.JSONSchema: - self._match_text_seq("COLUMNS") - return self.expression( - exp.JSONSchema, - expressions=self._parse_wrapped_csv( - self._parse_json_column_def, optional=True - ), - ) - - def _parse_json_table(self) -> exp.JSONTable: - this = self._parse_format_json(self._parse_bitwise()) - path = self._match(TokenType.COMMA) and self._parse_string() - error_handling = self._parse_on_handling("ERROR", "ERROR", "NULL") - empty_handling = self._parse_on_handling("EMPTY", "ERROR", "NULL") - schema = self._parse_json_schema() - - return exp.JSONTable( - this=this, - schema=schema, - path=path, - error_handling=error_handling, - empty_handling=empty_handling, - ) - - def _parse_match_against(self) -> exp.MatchAgainst: - if self._match_text_seq("TABLE"): - # parse SingleStore MATCH(TABLE ...) syntax - # https://docs.singlestore.com/cloud/reference/sql-reference/full-text-search-functions/match/ - expressions = [] - table = self._parse_table() - if table: - expressions = [table] - else: - expressions = self._parse_csv(self._parse_column) - - self._match_text_seq(")", "AGAINST", "(") - - this = self._parse_string() - - if self._match_text_seq("IN", "NATURAL", "LANGUAGE", "MODE"): - modifier = "IN NATURAL LANGUAGE MODE" - if self._match_text_seq("WITH", "QUERY", "EXPANSION"): - modifier = f"{modifier} WITH QUERY EXPANSION" - elif self._match_text_seq("IN", "BOOLEAN", "MODE"): - modifier = "IN BOOLEAN MODE" - elif self._match_text_seq("WITH", "QUERY", "EXPANSION"): - modifier = "WITH QUERY EXPANSION" - else: - modifier = None - - return self.expression( - exp.MatchAgainst, this=this, expressions=expressions, modifier=modifier - ) - - # https://learn.microsoft.com/en-us/sql/t-sql/functions/openjson-transact-sql?view=sql-server-ver16 - def _parse_open_json(self) -> exp.OpenJSON: - this = self._parse_bitwise() - path = self._match(TokenType.COMMA) and self._parse_string() - - def _parse_open_json_column_def() -> exp.OpenJSONColumnDef: - this = self._parse_field(any_token=True) - kind = self._parse_types() - path = self._parse_string() - as_json = self._match_pair(TokenType.ALIAS, TokenType.JSON) - - return self.expression( - exp.OpenJSONColumnDef, this=this, kind=kind, path=path, as_json=as_json - ) - - expressions = None - if self._match_pair(TokenType.R_PAREN, TokenType.WITH): - self._match_l_paren() - expressions = self._parse_csv(_parse_open_json_column_def) - - return self.expression( - exp.OpenJSON, this=this, path=path, expressions=expressions - ) - - def _parse_position(self, haystack_first: bool = False) -> exp.StrPosition: - args = self._parse_csv(self._parse_bitwise) - - if self._match(TokenType.IN): - return self.expression( - exp.StrPosition, this=self._parse_bitwise(), substr=seq_get(args, 0) - ) - - if haystack_first: - haystack = seq_get(args, 0) - needle = seq_get(args, 1) - else: - haystack = seq_get(args, 1) - needle = seq_get(args, 0) - - return self.expression( - exp.StrPosition, this=haystack, substr=needle, position=seq_get(args, 2) - ) - - def _parse_join_hint(self, func_name: str) -> exp.JoinHint: - args = self._parse_csv(self._parse_table) - return exp.JoinHint(this=func_name.upper(), expressions=args) - - def _parse_substring(self) -> exp.Substring: - # Postgres supports the form: substring(string [from int] [for int]) - # (despite being undocumented, the reverse order also works) - # https://www.postgresql.org/docs/9.1/functions-string.html @ Table 9-6 - - args = t.cast( - t.List[t.Optional[exp.Expression]], self._parse_csv(self._parse_bitwise) - ) - - start, length = None, None - - while self._curr: - if self._match(TokenType.FROM): - start = self._parse_bitwise() - elif self._match(TokenType.FOR): - if not start: - start = exp.Literal.number(1) - length = self._parse_bitwise() - else: - break - - if start: - args.append(start) - if length: - args.append(length) - - return self.validate_expression(exp.Substring.from_arg_list(args), args) - - def _parse_trim(self) -> exp.Trim: - # https://www.w3resource.com/sql/character-functions/trim.php - # https://docs.oracle.com/javadb/10.8.3.0/ref/rreftrimfunc.html - - position = None - collation = None - expression = None - - if self._match_texts(self.TRIM_TYPES): - position = self._prev.text.upper() - - this = self._parse_bitwise() - if self._match_set((TokenType.FROM, TokenType.COMMA)): - invert_order = ( - self._prev.token_type == TokenType.FROM or self.TRIM_PATTERN_FIRST - ) - expression = self._parse_bitwise() - - if invert_order: - this, expression = expression, this - - if self._match(TokenType.COLLATE): - collation = self._parse_bitwise() - - return self.expression( - exp.Trim, - this=this, - position=position, - expression=expression, - collation=collation, - ) - - def _parse_window_clause(self) -> t.Optional[t.List[exp.Expression]]: - return self._match(TokenType.WINDOW) and self._parse_csv( - self._parse_named_window - ) - - def _parse_named_window(self) -> t.Optional[exp.Expression]: - return self._parse_window(self._parse_id_var(), alias=True) - - def _parse_respect_or_ignore_nulls( - self, this: t.Optional[exp.Expression] - ) -> t.Optional[exp.Expression]: - if self._match_text_seq("IGNORE", "NULLS"): - return self.expression(exp.IgnoreNulls, this=this) - if self._match_text_seq("RESPECT", "NULLS"): - return self.expression(exp.RespectNulls, this=this) - return this - - def _parse_having_max( - self, this: t.Optional[exp.Expression] - ) -> t.Optional[exp.Expression]: - if self._match(TokenType.HAVING): - self._match_texts(("MAX", "MIN")) - max = self._prev.text.upper() != "MIN" - return self.expression( - exp.HavingMax, this=this, expression=self._parse_column(), max=max - ) - - return this - - def _parse_window( - self, this: t.Optional[exp.Expression], alias: bool = False - ) -> t.Optional[exp.Expression]: - func = this - comments = func.comments if isinstance(func, exp.Expression) else None - - # T-SQL allows the OVER (...) syntax after WITHIN GROUP. - # https://learn.microsoft.com/en-us/sql/t-sql/functions/percentile-disc-transact-sql?view=sql-server-ver16 - if self._match_text_seq("WITHIN", "GROUP"): - order = self._parse_wrapped(self._parse_order) - this = self.expression(exp.WithinGroup, this=this, expression=order) - - if self._match_pair(TokenType.FILTER, TokenType.L_PAREN): - self._match(TokenType.WHERE) - this = self.expression( - exp.Filter, - this=this, - expression=self._parse_where(skip_where_token=True), - ) - self._match_r_paren() - - # SQL spec defines an optional [ { IGNORE | RESPECT } NULLS ] OVER - # Some dialects choose to implement and some do not. - # https://dev.mysql.com/doc/refman/8.0/en/window-function-descriptions.html - - # There is some code above in _parse_lambda that handles - # SELECT FIRST_VALUE(TABLE.COLUMN IGNORE|RESPECT NULLS) OVER ... - - # The below changes handle - # SELECT FIRST_VALUE(TABLE.COLUMN) IGNORE|RESPECT NULLS OVER ... - - # Oracle allows both formats - # (https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/img_text/first_value.html) - # and Snowflake chose to do the same for familiarity - # https://docs.snowflake.com/en/sql-reference/functions/first_value.html#usage-notes - if isinstance(this, exp.AggFunc): - ignore_respect = this.find(exp.IgnoreNulls, exp.RespectNulls) - - if ignore_respect and ignore_respect is not this: - ignore_respect.replace(ignore_respect.this) - this = self.expression(ignore_respect.__class__, this=this) - - this = self._parse_respect_or_ignore_nulls(this) - - # bigquery select from window x AS (partition by ...) - if alias: - over = None - self._match(TokenType.ALIAS) - elif not self._match_set(self.WINDOW_BEFORE_PAREN_TOKENS): - return this - else: - over = self._prev.text.upper() - - if comments and isinstance(func, exp.Expression): - func.pop_comments() - - if not self._match(TokenType.L_PAREN): - return self.expression( - exp.Window, - comments=comments, - this=this, - alias=self._parse_id_var(False), - over=over, - ) - - window_alias = self._parse_id_var( - any_token=False, tokens=self.WINDOW_ALIAS_TOKENS - ) - - first = self._match(TokenType.FIRST) - if self._match_text_seq("LAST"): - first = False - - partition, order = self._parse_partition_and_order() - kind = self._match_set((TokenType.ROWS, TokenType.RANGE)) and self._prev.text - - if kind: - self._match(TokenType.BETWEEN) - start = self._parse_window_spec() - - end = self._parse_window_spec() if self._match(TokenType.AND) else {} - exclude = ( - self._parse_var_from_options(self.WINDOW_EXCLUDE_OPTIONS) - if self._match_text_seq("EXCLUDE") - else None - ) - - spec = self.expression( - exp.WindowSpec, - kind=kind, - start=start["value"], - start_side=start["side"], - end=end.get("value"), - end_side=end.get("side"), - exclude=exclude, - ) - else: - spec = None - - self._match_r_paren() - - window = self.expression( - exp.Window, - comments=comments, - this=this, - partition_by=partition, - order=order, - spec=spec, - alias=window_alias, - over=over, - first=first, - ) - - # This covers Oracle's FIRST/LAST syntax: aggregate KEEP (...) OVER (...) - if self._match_set(self.WINDOW_BEFORE_PAREN_TOKENS, advance=False): - return self._parse_window(window, alias=alias) - - return window - - def _parse_partition_and_order( - self, - ) -> t.Tuple[t.List[exp.Expression], t.Optional[exp.Expression]]: - return self._parse_partition_by(), self._parse_order() - - def _parse_window_spec(self) -> t.Dict[str, t.Optional[str | exp.Expression]]: - self._match(TokenType.BETWEEN) - - return { - "value": ( - (self._match_text_seq("UNBOUNDED") and "UNBOUNDED") - or (self._match_text_seq("CURRENT", "ROW") and "CURRENT ROW") - or self._parse_bitwise() - ), - "side": self._match_texts(self.WINDOW_SIDES) and self._prev.text, - } - - def _parse_alias( - self, this: t.Optional[exp.Expression], explicit: bool = False - ) -> t.Optional[exp.Expression]: - # In some dialects, LIMIT and OFFSET can act as both identifiers and keywords (clauses) - # so this section tries to parse the clause version and if it fails, it treats the token - # as an identifier (alias) - if self._can_parse_limit_or_offset(): - return this - - any_token = self._match(TokenType.ALIAS) - comments = self._prev_comments or [] - - if explicit and not any_token: - return this - - if self._match(TokenType.L_PAREN): - aliases = self.expression( - exp.Aliases, - comments=comments, - this=this, - expressions=self._parse_csv(lambda: self._parse_id_var(any_token)), - ) - self._match_r_paren(aliases) - return aliases - - alias = self._parse_id_var(any_token, tokens=self.ALIAS_TOKENS) or ( - self.STRING_ALIASES and self._parse_string_as_identifier() - ) - - if alias: - comments.extend(alias.pop_comments()) - this = self.expression(exp.Alias, comments=comments, this=this, alias=alias) - column = this.this - - # Moves the comment next to the alias in `expr /* comment */ AS alias` - if not this.comments and column and column.comments: - this.comments = column.pop_comments() - - return this - - def _parse_id_var( - self, - any_token: bool = True, - tokens: t.Optional[t.Collection[TokenType]] = None, - ) -> t.Optional[exp.Expression]: - expression = self._parse_identifier() - if not expression and ( - (any_token and self._advance_any()) - or self._match_set(tokens or self.ID_VAR_TOKENS) - ): - quoted = self._prev.token_type == TokenType.STRING - expression = self._identifier_expression(quoted=quoted) - - return expression - - def _parse_string(self) -> t.Optional[exp.Expression]: - if self._match_set(self.STRING_PARSERS): - return self.STRING_PARSERS[self._prev.token_type](self, self._prev) - return self._parse_placeholder() - - def _parse_string_as_identifier(self) -> t.Optional[exp.Identifier]: - output = exp.to_identifier( - self._match(TokenType.STRING) and self._prev.text, quoted=True - ) - if output: - output.update_positions(self._prev) - return output - - def _parse_number(self) -> t.Optional[exp.Expression]: - if self._match_set(self.NUMERIC_PARSERS): - return self.NUMERIC_PARSERS[self._prev.token_type](self, self._prev) - return self._parse_placeholder() - - def _parse_identifier(self) -> t.Optional[exp.Expression]: - if self._match(TokenType.IDENTIFIER): - return self._identifier_expression(quoted=True) - return self._parse_placeholder() - - def _parse_var( - self, - any_token: bool = False, - tokens: t.Optional[t.Collection[TokenType]] = None, - upper: bool = False, - ) -> t.Optional[exp.Expression]: - if ( - (any_token and self._advance_any()) - or self._match(TokenType.VAR) - or (self._match_set(tokens) if tokens else False) - ): - return self.expression( - exp.Var, this=self._prev.text.upper() if upper else self._prev.text - ) - return self._parse_placeholder() - - def _advance_any(self, ignore_reserved: bool = False) -> t.Optional[Token]: - if self._curr and ( - ignore_reserved or self._curr.token_type not in self.RESERVED_TOKENS - ): - self._advance() - return self._prev - return None - - def _parse_var_or_string(self, upper: bool = False) -> t.Optional[exp.Expression]: - return self._parse_string() or self._parse_var(any_token=True, upper=upper) - - def _parse_primary_or_var(self) -> t.Optional[exp.Expression]: - return self._parse_primary() or self._parse_var(any_token=True) - - def _parse_null(self) -> t.Optional[exp.Expression]: - if self._match_set((TokenType.NULL, TokenType.UNKNOWN)): - return self.PRIMARY_PARSERS[TokenType.NULL](self, self._prev) - return self._parse_placeholder() - - def _parse_boolean(self) -> t.Optional[exp.Expression]: - if self._match(TokenType.TRUE): - return self.PRIMARY_PARSERS[TokenType.TRUE](self, self._prev) - if self._match(TokenType.FALSE): - return self.PRIMARY_PARSERS[TokenType.FALSE](self, self._prev) - return self._parse_placeholder() - - def _parse_star(self) -> t.Optional[exp.Expression]: - if self._match(TokenType.STAR): - return self.PRIMARY_PARSERS[TokenType.STAR](self, self._prev) - return self._parse_placeholder() - - def _parse_parameter(self) -> exp.Parameter: - this = self._parse_identifier() or self._parse_primary_or_var() - return self.expression(exp.Parameter, this=this) - - def _parse_placeholder(self) -> t.Optional[exp.Expression]: - if self._match_set(self.PLACEHOLDER_PARSERS): - placeholder = self.PLACEHOLDER_PARSERS[self._prev.token_type](self) - if placeholder: - return placeholder - self._advance(-1) - return None - - def _parse_star_op(self, *keywords: str) -> t.Optional[t.List[exp.Expression]]: - if not self._match_texts(keywords): - return None - if self._match(TokenType.L_PAREN, advance=False): - return self._parse_wrapped_csv(self._parse_expression) - - expression = self._parse_alias(self._parse_disjunction(), explicit=True) - return [expression] if expression else None - - def _parse_csv( - self, parse_method: t.Callable, sep: TokenType = TokenType.COMMA - ) -> t.List[exp.Expression]: - parse_result = parse_method() - items = [parse_result] if parse_result is not None else [] - - while self._match(sep): - self._add_comments(parse_result) - parse_result = parse_method() - if parse_result is not None: - items.append(parse_result) - - return items - - def _parse_tokens( - self, parse_method: t.Callable, expressions: t.Dict - ) -> t.Optional[exp.Expression]: - this = parse_method() - - while self._match_set(expressions): - this = self.expression( - expressions[self._prev.token_type], - this=this, - comments=self._prev_comments, - expression=parse_method(), - ) - - return this - - def _parse_wrapped_id_vars(self, optional: bool = False) -> t.List[exp.Expression]: - return self._parse_wrapped_csv(self._parse_id_var, optional=optional) - - def _parse_wrapped_csv( - self, - parse_method: t.Callable, - sep: TokenType = TokenType.COMMA, - optional: bool = False, - ) -> t.List[exp.Expression]: - return self._parse_wrapped( - lambda: self._parse_csv(parse_method, sep=sep), optional=optional - ) - - def _parse_wrapped(self, parse_method: t.Callable, optional: bool = False) -> t.Any: - wrapped = self._match(TokenType.L_PAREN) - if not wrapped and not optional: - self.raise_error("Expecting (") - parse_result = parse_method() - if wrapped: - self._match_r_paren() - return parse_result - - def _parse_expressions(self) -> t.List[exp.Expression]: - return self._parse_csv(self._parse_expression) - - def _parse_select_or_expression( - self, alias: bool = False - ) -> t.Optional[exp.Expression]: - return ( - self._parse_set_operations( - self._parse_alias(self._parse_assignment(), explicit=True) - if alias - else self._parse_assignment() - ) - or self._parse_select() - ) - - def _parse_ddl_select(self) -> t.Optional[exp.Expression]: - return self._parse_query_modifiers( - self._parse_set_operations( - self._parse_select(nested=True, parse_subquery_alias=False) - ) - ) - - def _parse_transaction(self) -> exp.Transaction | exp.Command: - this = None - if self._match_texts(self.TRANSACTION_KIND): - this = self._prev.text - - self._match_texts(("TRANSACTION", "WORK")) - - modes = [] - while True: - mode = [] - while self._match(TokenType.VAR) or self._match(TokenType.NOT): - mode.append(self._prev.text) - - if mode: - modes.append(" ".join(mode)) - if not self._match(TokenType.COMMA): - break - - return self.expression(exp.Transaction, this=this, modes=modes) - - def _parse_commit_or_rollback(self) -> exp.Commit | exp.Rollback: - chain = None - savepoint = None - is_rollback = self._prev.token_type == TokenType.ROLLBACK - - self._match_texts(("TRANSACTION", "WORK")) - - if self._match_text_seq("TO"): - self._match_text_seq("SAVEPOINT") - savepoint = self._parse_id_var() - - if self._match(TokenType.AND): - chain = not self._match_text_seq("NO") - self._match_text_seq("CHAIN") - - if is_rollback: - return self.expression(exp.Rollback, savepoint=savepoint) - - return self.expression(exp.Commit, chain=chain) - - def _parse_refresh(self) -> exp.Refresh | exp.Command: - if self._match(TokenType.TABLE): - kind = "TABLE" - elif self._match_text_seq("MATERIALIZED", "VIEW"): - kind = "MATERIALIZED VIEW" - else: - kind = "" - - this = self._parse_string() or self._parse_table() - if not kind and not isinstance(this, exp.Literal): - return self._parse_as_command(self._prev) - - return self.expression(exp.Refresh, this=this, kind=kind) - - def _parse_column_def_with_exists(self): - start = self._index - self._match(TokenType.COLUMN) - - exists_column = self._parse_exists(not_=True) - expression = self._parse_field_def() - - if not isinstance(expression, exp.ColumnDef): - self._retreat(start) - return None - - expression.set("exists", exists_column) - - return expression - - def _parse_add_column(self) -> t.Optional[exp.ColumnDef]: - if not self._prev.text.upper() == "ADD": - return None - - expression = self._parse_column_def_with_exists() - if not expression: - return None - - # https://docs.databricks.com/delta/update-schema.html#explicitly-update-schema-to-add-columns - if self._match_texts(("FIRST", "AFTER")): - position = self._prev.text - column_position = self.expression( - exp.ColumnPosition, this=self._parse_column(), position=position - ) - expression.set("position", column_position) - - return expression - - def _parse_drop_column(self) -> t.Optional[exp.Drop | exp.Command]: - drop = self._match(TokenType.DROP) and self._parse_drop() - if drop and not isinstance(drop, exp.Command): - drop.set("kind", drop.args.get("kind", "COLUMN")) - return drop - - # https://docs.aws.amazon.com/athena/latest/ug/alter-table-drop-partition.html - def _parse_drop_partition( - self, exists: t.Optional[bool] = None - ) -> exp.DropPartition: - return self.expression( - exp.DropPartition, - expressions=self._parse_csv(self._parse_partition), - exists=exists, - ) - - def _parse_alter_table_add(self) -> t.List[exp.Expression]: - def _parse_add_alteration() -> t.Optional[exp.Expression]: - self._match_text_seq("ADD") - if self._match_set(self.ADD_CONSTRAINT_TOKENS, advance=False): - return self.expression( - exp.AddConstraint, - expressions=self._parse_csv(self._parse_constraint), - ) - - column_def = self._parse_add_column() - if isinstance(column_def, exp.ColumnDef): - return column_def - - exists = self._parse_exists(not_=True) - if self._match_pair(TokenType.PARTITION, TokenType.L_PAREN, advance=False): - return self.expression( - exp.AddPartition, - exists=exists, - this=self._parse_field(any_token=True), - location=self._match_text_seq("LOCATION", advance=False) - and self._parse_property(), - ) - - return None - - if not self._match_set(self.ADD_CONSTRAINT_TOKENS, advance=False) and ( - not self.dialect.ALTER_TABLE_ADD_REQUIRED_FOR_EACH_COLUMN - or self._match_text_seq("COLUMNS") - ): - schema = self._parse_schema() - - return ( - ensure_list(schema) - if schema - else self._parse_csv(self._parse_column_def_with_exists) - ) - - return self._parse_csv(_parse_add_alteration) - - def _parse_alter_table_alter(self) -> t.Optional[exp.Expression]: - if self._match_texts(self.ALTER_ALTER_PARSERS): - return self.ALTER_ALTER_PARSERS[self._prev.text.upper()](self) - - # Many dialects support the ALTER [COLUMN] syntax, so if there is no - # keyword after ALTER we default to parsing this statement - self._match(TokenType.COLUMN) - column = self._parse_field(any_token=True) - - if self._match_pair(TokenType.DROP, TokenType.DEFAULT): - return self.expression(exp.AlterColumn, this=column, drop=True) - if self._match_pair(TokenType.SET, TokenType.DEFAULT): - return self.expression( - exp.AlterColumn, this=column, default=self._parse_disjunction() - ) - if self._match(TokenType.COMMENT): - return self.expression( - exp.AlterColumn, this=column, comment=self._parse_string() - ) - if self._match_text_seq("DROP", "NOT", "NULL"): - return self.expression( - exp.AlterColumn, - this=column, - drop=True, - allow_null=True, - ) - if self._match_text_seq("SET", "NOT", "NULL"): - return self.expression( - exp.AlterColumn, - this=column, - allow_null=False, - ) - - if self._match_text_seq("SET", "VISIBLE"): - return self.expression(exp.AlterColumn, this=column, visible="VISIBLE") - if self._match_text_seq("SET", "INVISIBLE"): - return self.expression(exp.AlterColumn, this=column, visible="INVISIBLE") - - self._match_text_seq("SET", "DATA") - self._match_text_seq("TYPE") - return self.expression( - exp.AlterColumn, - this=column, - dtype=self._parse_types(), - collate=self._match(TokenType.COLLATE) and self._parse_term(), - using=self._match(TokenType.USING) and self._parse_disjunction(), - ) - - def _parse_alter_diststyle(self) -> exp.AlterDistStyle: - if self._match_texts(("ALL", "EVEN", "AUTO")): - return self.expression( - exp.AlterDistStyle, this=exp.var(self._prev.text.upper()) - ) - - self._match_text_seq("KEY", "DISTKEY") - return self.expression(exp.AlterDistStyle, this=self._parse_column()) - - def _parse_alter_sortkey( - self, compound: t.Optional[bool] = None - ) -> exp.AlterSortKey: - if compound: - self._match_text_seq("SORTKEY") - - if self._match(TokenType.L_PAREN, advance=False): - return self.expression( - exp.AlterSortKey, - expressions=self._parse_wrapped_id_vars(), - compound=compound, - ) - - self._match_texts(("AUTO", "NONE")) - return self.expression( - exp.AlterSortKey, this=exp.var(self._prev.text.upper()), compound=compound - ) - - def _parse_alter_table_drop(self) -> t.List[exp.Expression]: - index = self._index - 1 - - partition_exists = self._parse_exists() - if self._match(TokenType.PARTITION, advance=False): - return self._parse_csv( - lambda: self._parse_drop_partition(exists=partition_exists) - ) - - self._retreat(index) - return self._parse_csv(self._parse_drop_column) - - def _parse_alter_table_rename( - self, - ) -> t.Optional[exp.AlterRename | exp.RenameColumn]: - if self._match(TokenType.COLUMN) or not self.ALTER_RENAME_REQUIRES_COLUMN: - exists = self._parse_exists() - old_column = self._parse_column() - to = self._match_text_seq("TO") - new_column = self._parse_column() - - if old_column is None or to is None or new_column is None: - return None - - return self.expression( - exp.RenameColumn, this=old_column, to=new_column, exists=exists - ) - - self._match_text_seq("TO") - return self.expression(exp.AlterRename, this=self._parse_table(schema=True)) - - def _parse_alter_table_set(self) -> exp.AlterSet: - alter_set = self.expression(exp.AlterSet) - - if self._match(TokenType.L_PAREN, advance=False) or self._match_text_seq( - "TABLE", "PROPERTIES" - ): - alter_set.set( - "expressions", self._parse_wrapped_csv(self._parse_assignment) - ) - elif self._match_text_seq("FILESTREAM_ON", advance=False): - alter_set.set("expressions", [self._parse_assignment()]) - elif self._match_texts(("LOGGED", "UNLOGGED")): - alter_set.set("option", exp.var(self._prev.text.upper())) - elif self._match_text_seq("WITHOUT") and self._match_texts(("CLUSTER", "OIDS")): - alter_set.set("option", exp.var(f"WITHOUT {self._prev.text.upper()}")) - elif self._match_text_seq("LOCATION"): - alter_set.set("location", self._parse_field()) - elif self._match_text_seq("ACCESS", "METHOD"): - alter_set.set("access_method", self._parse_field()) - elif self._match_text_seq("TABLESPACE"): - alter_set.set("tablespace", self._parse_field()) - elif self._match_text_seq("FILE", "FORMAT") or self._match_text_seq( - "FILEFORMAT" - ): - alter_set.set("file_format", [self._parse_field()]) - elif self._match_text_seq("STAGE_FILE_FORMAT"): - alter_set.set("file_format", self._parse_wrapped_options()) - elif self._match_text_seq("STAGE_COPY_OPTIONS"): - alter_set.set("copy_options", self._parse_wrapped_options()) - elif self._match_text_seq("TAG") or self._match_text_seq("TAGS"): - alter_set.set("tag", self._parse_csv(self._parse_assignment)) - else: - if self._match_text_seq("SERDE"): - alter_set.set("serde", self._parse_field()) - - properties = self._parse_wrapped(self._parse_properties, optional=True) - alter_set.set("expressions", [properties]) - - return alter_set - - def _parse_alter_session(self) -> exp.AlterSession: - """Parse ALTER SESSION SET/UNSET statements.""" - if self._match(TokenType.SET): - expressions = self._parse_csv(lambda: self._parse_set_item_assignment()) - return self.expression( - exp.AlterSession, expressions=expressions, unset=False - ) - - self._match_text_seq("UNSET") - expressions = self._parse_csv( - lambda: self.expression( - exp.SetItem, this=self._parse_id_var(any_token=True) - ) - ) - return self.expression(exp.AlterSession, expressions=expressions, unset=True) - - def _parse_alter(self) -> exp.Alter | exp.Command: - start = self._prev - - alter_token = self._match_set(self.ALTERABLES) and self._prev - if not alter_token: - return self._parse_as_command(start) - - exists = self._parse_exists() - only = self._match_text_seq("ONLY") - - if alter_token.token_type == TokenType.SESSION: - this = None - check = None - cluster = None - else: - this = self._parse_table( - schema=True, parse_partition=self.ALTER_TABLE_PARTITIONS - ) - check = self._match_text_seq("WITH", "CHECK") - cluster = self._parse_on_property() if self._match(TokenType.ON) else None - - if self._next: - self._advance() - - parser = self.ALTER_PARSERS.get(self._prev.text.upper()) if self._prev else None - if parser: - actions = ensure_list(parser(self)) - not_valid = self._match_text_seq("NOT", "VALID") - options = self._parse_csv(self._parse_property) - cascade = ( - self.dialect.ALTER_TABLE_SUPPORTS_CASCADE - and self._match_text_seq("CASCADE") - ) - - if not self._curr and actions: - return self.expression( - exp.Alter, - this=this, - kind=alter_token.text.upper(), - exists=exists, - actions=actions, - only=only, - options=options, - cluster=cluster, - not_valid=not_valid, - check=check, - cascade=cascade, - ) - - return self._parse_as_command(start) - - def _parse_analyze(self) -> exp.Analyze | exp.Command: - start = self._prev - # https://duckdb.org/docs/sql/statements/analyze - if not self._curr: - return self.expression(exp.Analyze) - - options = [] - while self._match_texts(self.ANALYZE_STYLES): - if self._prev.text.upper() == "BUFFER_USAGE_LIMIT": - options.append(f"BUFFER_USAGE_LIMIT {self._parse_number()}") - else: - options.append(self._prev.text.upper()) - - this: t.Optional[exp.Expression] = None - inner_expression: t.Optional[exp.Expression] = None - - kind = self._curr and self._curr.text.upper() - - if self._match(TokenType.TABLE) or self._match(TokenType.INDEX): - this = self._parse_table_parts() - elif self._match_text_seq("TABLES"): - if self._match_set((TokenType.FROM, TokenType.IN)): - kind = f"{kind} {self._prev.text.upper()}" - this = self._parse_table(schema=True, is_db_reference=True) - elif self._match_text_seq("DATABASE"): - this = self._parse_table(schema=True, is_db_reference=True) - elif self._match_text_seq("CLUSTER"): - this = self._parse_table() - # Try matching inner expr keywords before fallback to parse table. - elif self._match_texts(self.ANALYZE_EXPRESSION_PARSERS): - kind = None - inner_expression = self.ANALYZE_EXPRESSION_PARSERS[self._prev.text.upper()]( - self - ) - else: - # Empty kind https://prestodb.io/docs/current/sql/analyze.html - kind = None - this = self._parse_table_parts() - - partition = self._try_parse(self._parse_partition) - if not partition and self._match_texts(self.PARTITION_KEYWORDS): - return self._parse_as_command(start) - - # https://docs.starrocks.io/docs/sql-reference/sql-statements/cbo_stats/ANALYZE_TABLE/ - if self._match_text_seq("WITH", "SYNC", "MODE") or self._match_text_seq( - "WITH", "ASYNC", "MODE" - ): - mode = f"WITH {self._tokens[self._index - 2].text.upper()} MODE" - else: - mode = None - - if self._match_texts(self.ANALYZE_EXPRESSION_PARSERS): - inner_expression = self.ANALYZE_EXPRESSION_PARSERS[self._prev.text.upper()]( - self - ) - - properties = self._parse_properties() - return self.expression( - exp.Analyze, - kind=kind, - this=this, - mode=mode, - partition=partition, - properties=properties, - expression=inner_expression, - options=options, - ) - - # https://spark.apache.org/docs/3.5.1/sql-ref-syntax-aux-analyze-table.html - def _parse_analyze_statistics(self) -> exp.AnalyzeStatistics: - this = None - kind = self._prev.text.upper() - option = self._prev.text.upper() if self._match_text_seq("DELTA") else None - expressions = [] - - if not self._match_text_seq("STATISTICS"): - self.raise_error("Expecting token STATISTICS") - - if self._match_text_seq("NOSCAN"): - this = "NOSCAN" - elif self._match(TokenType.FOR): - if self._match_text_seq("ALL", "COLUMNS"): - this = "FOR ALL COLUMNS" - if self._match_texts("COLUMNS"): - this = "FOR COLUMNS" - expressions = self._parse_csv(self._parse_column_reference) - elif self._match_text_seq("SAMPLE"): - sample = self._parse_number() - expressions = [ - self.expression( - exp.AnalyzeSample, - sample=sample, - kind=self._prev.text.upper() - if self._match(TokenType.PERCENT) - else None, - ) - ] - - return self.expression( - exp.AnalyzeStatistics, - kind=kind, - option=option, - this=this, - expressions=expressions, - ) - - # https://docs.oracle.com/en/database/oracle/oracle-database/21/sqlrf/ANALYZE.html - def _parse_analyze_validate(self) -> exp.AnalyzeValidate: - kind = None - this = None - expression: t.Optional[exp.Expression] = None - if self._match_text_seq("REF", "UPDATE"): - kind = "REF" - this = "UPDATE" - if self._match_text_seq("SET", "DANGLING", "TO", "NULL"): - this = "UPDATE SET DANGLING TO NULL" - elif self._match_text_seq("STRUCTURE"): - kind = "STRUCTURE" - if self._match_text_seq("CASCADE", "FAST"): - this = "CASCADE FAST" - elif self._match_text_seq("CASCADE", "COMPLETE") and self._match_texts( - ("ONLINE", "OFFLINE") - ): - this = f"CASCADE COMPLETE {self._prev.text.upper()}" - expression = self._parse_into() - - return self.expression( - exp.AnalyzeValidate, kind=kind, this=this, expression=expression - ) - - def _parse_analyze_columns(self) -> t.Optional[exp.AnalyzeColumns]: - this = self._prev.text.upper() - if self._match_text_seq("COLUMNS"): - return self.expression( - exp.AnalyzeColumns, this=f"{this} {self._prev.text.upper()}" - ) - return None - - def _parse_analyze_delete(self) -> t.Optional[exp.AnalyzeDelete]: - kind = self._prev.text.upper() if self._match_text_seq("SYSTEM") else None - if self._match_text_seq("STATISTICS"): - return self.expression(exp.AnalyzeDelete, kind=kind) - return None - - def _parse_analyze_list(self) -> t.Optional[exp.AnalyzeListChainedRows]: - if self._match_text_seq("CHAINED", "ROWS"): - return self.expression( - exp.AnalyzeListChainedRows, expression=self._parse_into() - ) - return None - - # https://dev.mysql.com/doc/refman/8.4/en/analyze-table.html - def _parse_analyze_histogram(self) -> exp.AnalyzeHistogram: - this = self._prev.text.upper() - expression: t.Optional[exp.Expression] = None - expressions = [] - update_options = None - - if self._match_text_seq("HISTOGRAM", "ON"): - expressions = self._parse_csv(self._parse_column_reference) - with_expressions = [] - while self._match(TokenType.WITH): - # https://docs.starrocks.io/docs/sql-reference/sql-statements/cbo_stats/ANALYZE_TABLE/ - if self._match_texts(("SYNC", "ASYNC")): - if self._match_text_seq("MODE", advance=False): - with_expressions.append(f"{self._prev.text.upper()} MODE") - self._advance() - else: - buckets = self._parse_number() - if self._match_text_seq("BUCKETS"): - with_expressions.append(f"{buckets} BUCKETS") - if with_expressions: - expression = self.expression( - exp.AnalyzeWith, expressions=with_expressions - ) - - if self._match_texts(("MANUAL", "AUTO")) and self._match( - TokenType.UPDATE, advance=False - ): - update_options = self._prev.text.upper() - self._advance() - elif self._match_text_seq("USING", "DATA"): - expression = self.expression(exp.UsingData, this=self._parse_string()) - - return self.expression( - exp.AnalyzeHistogram, - this=this, - expressions=expressions, - expression=expression, - update_options=update_options, - ) - - def _parse_merge(self) -> exp.Merge: - self._match(TokenType.INTO) - target = self._parse_table() - - if target and self._match(TokenType.ALIAS, advance=False): - target.set("alias", self._parse_table_alias()) - - self._match(TokenType.USING) - using = self._parse_table() - - return self.expression( - exp.Merge, - this=target, - using=using, - on=self._match(TokenType.ON) and self._parse_disjunction(), - using_cond=self._match(TokenType.USING) and self._parse_using_identifiers(), - whens=self._parse_when_matched(), - returning=self._parse_returning(), - ) - - def _parse_when_matched(self) -> exp.Whens: - whens = [] - - while self._match(TokenType.WHEN): - matched = not self._match(TokenType.NOT) - self._match_text_seq("MATCHED") - source = ( - False - if self._match_text_seq("BY", "TARGET") - else self._match_text_seq("BY", "SOURCE") - ) - condition = ( - self._parse_disjunction() if self._match(TokenType.AND) else None - ) - - self._match(TokenType.THEN) - - if self._match(TokenType.INSERT): - this = self._parse_star() - if this: - then: t.Optional[exp.Expression] = self.expression( - exp.Insert, this=this - ) - else: - then = self.expression( - exp.Insert, - this=exp.var("ROW") - if self._match_text_seq("ROW") - else self._parse_value(values=False), - expression=self._match_text_seq("VALUES") - and self._parse_value(), - ) - elif self._match(TokenType.UPDATE): - expressions = self._parse_star() - if expressions: - then = self.expression(exp.Update, expressions=expressions) - else: - then = self.expression( - exp.Update, - expressions=self._match(TokenType.SET) - and self._parse_csv(self._parse_equality), - ) - elif self._match(TokenType.DELETE): - then = self.expression(exp.Var, this=self._prev.text) - else: - then = self._parse_var_from_options(self.CONFLICT_ACTIONS) - - whens.append( - self.expression( - exp.When, - matched=matched, - source=source, - condition=condition, - then=then, - ) - ) - return self.expression(exp.Whens, expressions=whens) - - def _parse_show(self) -> t.Optional[exp.Expression]: - parser = self._find_parser(self.SHOW_PARSERS, self.SHOW_TRIE) - if parser: - return parser(self) - return self._parse_as_command(self._prev) - - def _parse_set_item_assignment( - self, kind: t.Optional[str] = None - ) -> t.Optional[exp.Expression]: - index = self._index - - if kind in ("GLOBAL", "SESSION") and self._match_text_seq("TRANSACTION"): - return self._parse_set_transaction(global_=kind == "GLOBAL") - - left = self._parse_primary() or self._parse_column() - assignment_delimiter = self._match_texts(self.SET_ASSIGNMENT_DELIMITERS) - - if not left or ( - self.SET_REQUIRES_ASSIGNMENT_DELIMITER and not assignment_delimiter - ): - self._retreat(index) - return None - - right = self._parse_statement() or self._parse_id_var() - if isinstance(right, (exp.Column, exp.Identifier)): - right = exp.var(right.name) - - this = self.expression(exp.EQ, this=left, expression=right) - return self.expression(exp.SetItem, this=this, kind=kind) - - def _parse_set_transaction(self, global_: bool = False) -> exp.Expression: - self._match_text_seq("TRANSACTION") - characteristics = self._parse_csv( - lambda: self._parse_var_from_options(self.TRANSACTION_CHARACTERISTICS) - ) - return self.expression( - exp.SetItem, - expressions=characteristics, - kind="TRANSACTION", - global_=global_, - ) - - def _parse_set_item(self) -> t.Optional[exp.Expression]: - parser = self._find_parser(self.SET_PARSERS, self.SET_TRIE) - return parser(self) if parser else self._parse_set_item_assignment(kind=None) - - def _parse_set( - self, unset: bool = False, tag: bool = False - ) -> exp.Set | exp.Command: - index = self._index - set_ = self.expression( - exp.Set, - expressions=self._parse_csv(self._parse_set_item), - unset=unset, - tag=tag, - ) - - if self._curr: - self._retreat(index) - return self._parse_as_command(self._prev) - - return set_ - - def _parse_var_from_options( - self, options: OPTIONS_TYPE, raise_unmatched: bool = True - ) -> t.Optional[exp.Var]: - start = self._curr - if not start: - return None - - option = start.text.upper() - continuations = options.get(option) - - index = self._index - self._advance() - for keywords in continuations or []: - if isinstance(keywords, str): - keywords = (keywords,) - - if self._match_text_seq(*keywords): - option = f"{option} {' '.join(keywords)}" - break - else: - if continuations or continuations is None: - if raise_unmatched: - self.raise_error(f"Unknown option {option}") - - self._retreat(index) - return None - - return exp.var(option) - - def _parse_as_command(self, start: Token) -> exp.Command: - while self._curr: - self._advance() - text = self._find_sql(start, self._prev) - size = len(start.text) - self._warn_unsupported() - return exp.Command(this=text[:size], expression=text[size:]) - - def _parse_dict_property(self, this: str) -> exp.DictProperty: - settings = [] - - self._match_l_paren() - kind = self._parse_id_var() - - if self._match(TokenType.L_PAREN): - while True: - key = self._parse_id_var() - value = self._parse_primary() - if not key and value is None: - break - settings.append( - self.expression(exp.DictSubProperty, this=key, value=value) - ) - self._match(TokenType.R_PAREN) - - self._match_r_paren() - - return self.expression( - exp.DictProperty, - this=this, - kind=kind.this if kind else None, - settings=settings, - ) - - def _parse_dict_range(self, this: str) -> exp.DictRange: - self._match_l_paren() - has_min = self._match_text_seq("MIN") - if has_min: - min = self._parse_var() or self._parse_primary() - self._match_text_seq("MAX") - max = self._parse_var() or self._parse_primary() - else: - max = self._parse_var() or self._parse_primary() - min = exp.Literal.number(0) - self._match_r_paren() - return self.expression(exp.DictRange, this=this, min=min, max=max) - - def _parse_comprehension( - self, this: t.Optional[exp.Expression] - ) -> t.Optional[exp.Comprehension]: - index = self._index - expression = self._parse_column() - position = self._match(TokenType.COMMA) and self._parse_column() - - if not self._match(TokenType.IN): - self._retreat(index - 1) - return None - iterator = self._parse_column() - condition = self._parse_disjunction() if self._match_text_seq("IF") else None - return self.expression( - exp.Comprehension, - this=this, - expression=expression, - position=position, - iterator=iterator, - condition=condition, - ) - - def _parse_heredoc(self) -> t.Optional[exp.Heredoc]: - if self._match(TokenType.HEREDOC_STRING): - return self.expression(exp.Heredoc, this=self._prev.text) - - if not self._match_text_seq("$"): - return None - - tags = ["$"] - tag_text = None - - if self._is_connected(): - self._advance() - tags.append(self._prev.text.upper()) - else: - self.raise_error("No closing $ found") - - if tags[-1] != "$": - if self._is_connected() and self._match_text_seq("$"): - tag_text = tags[-1] - tags.append("$") - else: - self.raise_error("No closing $ found") - - heredoc_start = self._curr - - while self._curr: - if self._match_text_seq(*tags, advance=False): - this = self._find_sql(heredoc_start, self._prev) - self._advance(len(tags)) - return self.expression(exp.Heredoc, this=this, tag=tag_text) - - self._advance() - - self.raise_error(f"No closing {''.join(tags)} found") - return None - - def _find_parser( - self, parsers: t.Dict[str, t.Callable], trie: t.Dict - ) -> t.Optional[t.Callable]: - if not self._curr: - return None - - index = self._index - this = [] - while True: - # The current token might be multiple words - curr = self._curr.text.upper() - key = curr.split(" ") - this.append(curr) - - self._advance() - result, trie = in_trie(trie, key) - if result == TrieResult.FAILED: - break - - if result == TrieResult.EXISTS: - subparser = parsers[" ".join(this)] - return subparser - - self._retreat(index) - return None - - def _match(self, token_type, advance=True, expression=None): - if not self._curr: - return None - - if self._curr.token_type == token_type: - if advance: - self._advance() - self._add_comments(expression) - return True - - return None - - def _match_set(self, types, advance=True): - if not self._curr: - return None - - if self._curr.token_type in types: - if advance: - self._advance() - return True - - return None - - def _match_pair(self, token_type_a, token_type_b, advance=True): - if not self._curr or not self._next: - return None - - if ( - self._curr.token_type == token_type_a - and self._next.token_type == token_type_b - ): - if advance: - self._advance(2) - return True - - return None - - def _match_l_paren(self, expression: t.Optional[exp.Expression] = None) -> None: - if not self._match(TokenType.L_PAREN, expression=expression): - self.raise_error("Expecting (") - - def _match_r_paren(self, expression: t.Optional[exp.Expression] = None) -> None: - if not self._match(TokenType.R_PAREN, expression=expression): - self.raise_error("Expecting )") - - def _match_texts(self, texts, advance=True): - if ( - self._curr - and self._curr.token_type != TokenType.STRING - and self._curr.text.upper() in texts - ): - if advance: - self._advance() - return True - return None - - def _match_text_seq(self, *texts, advance=True): - index = self._index - for text in texts: - if ( - self._curr - and self._curr.token_type != TokenType.STRING - and self._curr.text.upper() == text - ): - self._advance() - else: - self._retreat(index) - return None - - if not advance: - self._retreat(index) - - return True - - def _replace_lambda( - self, node: t.Optional[exp.Expression], expressions: t.List[exp.Expression] - ) -> t.Optional[exp.Expression]: - if not node: - return node - - lambda_types = {e.name: e.args.get("to") or False for e in expressions} - - for column in node.find_all(exp.Column): - typ = lambda_types.get(column.parts[0].name) - if typ is not None: - dot_or_id = column.to_dot() if column.table else column.this - - if typ: - dot_or_id = self.expression( - exp.Cast, - this=dot_or_id, - to=typ, - ) - - parent = column.parent - - while isinstance(parent, exp.Dot): - if not isinstance(parent.parent, exp.Dot): - parent.replace(dot_or_id) - break - parent = parent.parent - else: - if column is node: - node = dot_or_id - else: - column.replace(dot_or_id) - return node - - def _parse_truncate_table(self) -> t.Optional[exp.TruncateTable] | exp.Expression: - start = self._prev - - # Not to be confused with TRUNCATE(number, decimals) function call - if self._match(TokenType.L_PAREN): - self._retreat(self._index - 2) - return self._parse_function() - - # Clickhouse supports TRUNCATE DATABASE as well - is_database = self._match(TokenType.DATABASE) - - self._match(TokenType.TABLE) - - exists = self._parse_exists(not_=False) - - expressions = self._parse_csv( - lambda: self._parse_table(schema=True, is_db_reference=is_database) - ) - - cluster = self._parse_on_property() if self._match(TokenType.ON) else None - - if self._match_text_seq("RESTART", "IDENTITY"): - identity = "RESTART" - elif self._match_text_seq("CONTINUE", "IDENTITY"): - identity = "CONTINUE" - else: - identity = None - - if self._match_text_seq("CASCADE") or self._match_text_seq("RESTRICT"): - option = self._prev.text - else: - option = None - - partition = self._parse_partition() - - # Fallback case - if self._curr: - return self._parse_as_command(start) - - return self.expression( - exp.TruncateTable, - expressions=expressions, - is_database=is_database, - exists=exists, - cluster=cluster, - identity=identity, - option=option, - partition=partition, - ) - - def _parse_with_operator(self) -> t.Optional[exp.Expression]: - this = self._parse_ordered(self._parse_opclass) - - if not self._match(TokenType.WITH): - return this - - op = self._parse_var(any_token=True) - - return self.expression(exp.WithOperator, this=this, op=op) - - def _parse_wrapped_options(self) -> t.List[t.Optional[exp.Expression]]: - self._match(TokenType.EQ) - self._match(TokenType.L_PAREN) - - opts: t.List[t.Optional[exp.Expression]] = [] - option: exp.Expression | None - while self._curr and not self._match(TokenType.R_PAREN): - if self._match_text_seq("FORMAT_NAME", "="): - # The FORMAT_NAME can be set to an identifier for Snowflake and T-SQL - option = self._parse_format_name() - else: - option = self._parse_property() - - if option is None: - self.raise_error("Unable to parse option") - break - - opts.append(option) - - return opts - - def _parse_copy_parameters(self) -> t.List[exp.CopyParameter]: - sep = TokenType.COMMA if self.dialect.COPY_PARAMS_ARE_CSV else None - - options = [] - while self._curr and not self._match(TokenType.R_PAREN, advance=False): - option = self._parse_var(any_token=True) - prev = self._prev.text.upper() - - # Different dialects might separate options and values by white space, "=" and "AS" - self._match(TokenType.EQ) - self._match(TokenType.ALIAS) - - param = self.expression(exp.CopyParameter, this=option) - - if prev in self.COPY_INTO_VARLEN_OPTIONS and self._match( - TokenType.L_PAREN, advance=False - ): - # Snowflake FILE_FORMAT case, Databricks COPY & FORMAT options - param.set("expressions", self._parse_wrapped_options()) - elif prev == "FILE_FORMAT": - # T-SQL's external file format case - param.set("expression", self._parse_field()) - elif ( - prev == "FORMAT" - and self._prev.token_type == TokenType.ALIAS - and self._match_texts(("AVRO", "JSON")) - ): - param.set("this", exp.var(f"FORMAT AS {self._prev.text.upper()}")) - param.set("expression", self._parse_field()) - else: - param.set( - "expression", self._parse_unquoted_field() or self._parse_bracket() - ) - - options.append(param) - self._match(sep) - - return options - - def _parse_credentials(self) -> t.Optional[exp.Credentials]: - expr = self.expression(exp.Credentials) - - if self._match_text_seq("STORAGE_INTEGRATION", "="): - expr.set("storage", self._parse_field()) - if self._match_text_seq("CREDENTIALS"): - # Snowflake case: CREDENTIALS = (...), Redshift case: CREDENTIALS - creds = ( - self._parse_wrapped_options() - if self._match(TokenType.EQ) - else self._parse_field() - ) - expr.set("credentials", creds) - if self._match_text_seq("ENCRYPTION"): - expr.set("encryption", self._parse_wrapped_options()) - if self._match_text_seq("IAM_ROLE"): - expr.set( - "iam_role", - exp.var(self._prev.text) - if self._match(TokenType.DEFAULT) - else self._parse_field(), - ) - if self._match_text_seq("REGION"): - expr.set("region", self._parse_field()) - - return expr - - def _parse_file_location(self) -> t.Optional[exp.Expression]: - return self._parse_field() - - def _parse_copy(self) -> exp.Copy | exp.Command: - start = self._prev - - self._match(TokenType.INTO) - - this = ( - self._parse_select(nested=True, parse_subquery_alias=False) - if self._match(TokenType.L_PAREN, advance=False) - else self._parse_table(schema=True) - ) - - kind = self._match(TokenType.FROM) or not self._match_text_seq("TO") - - files = self._parse_csv(self._parse_file_location) - if self._match(TokenType.EQ, advance=False): - # Backtrack one token since we've consumed the lhs of a parameter assignment here. - # This can happen for Snowflake dialect. Instead, we'd like to parse the parameter - # list via `_parse_wrapped(..)` below. - self._advance(-1) - files = [] - - credentials = self._parse_credentials() - - self._match_text_seq("WITH") - - params = self._parse_wrapped(self._parse_copy_parameters, optional=True) - - # Fallback case - if self._curr: - return self._parse_as_command(start) - - return self.expression( - exp.Copy, - this=this, - kind=kind, - credentials=credentials, - files=files, - params=params, - ) - - def _parse_normalize(self) -> exp.Normalize: - return self.expression( - exp.Normalize, - this=self._parse_bitwise(), - form=self._match(TokenType.COMMA) and self._parse_var(), - ) - - def _parse_ceil_floor(self, expr_type: t.Type[TCeilFloor]) -> TCeilFloor: - args = self._parse_csv(lambda: self._parse_lambda()) - - this = seq_get(args, 0) - decimals = seq_get(args, 1) - - return expr_type( - this=this, - decimals=decimals, - to=self._match_text_seq("TO") and self._parse_var(), - ) - - def _parse_star_ops(self) -> t.Optional[exp.Expression]: - star_token = self._prev - - if self._match_text_seq("COLUMNS", "(", advance=False): - this = self._parse_function() - if isinstance(this, exp.Columns): - this.set("unpack", True) - return this - - return self.expression( - exp.Star, - except_=self._parse_star_op("EXCEPT", "EXCLUDE"), - replace=self._parse_star_op("REPLACE"), - rename=self._parse_star_op("RENAME"), - ).update_positions(star_token) - - def _parse_grant_privilege(self) -> t.Optional[exp.GrantPrivilege]: - privilege_parts = [] - - # Keep consuming consecutive keywords until comma (end of this privilege) or ON - # (end of privilege list) or L_PAREN (start of column list) are met - while self._curr and not self._match_set( - self.PRIVILEGE_FOLLOW_TOKENS, advance=False - ): - privilege_parts.append(self._curr.text.upper()) - self._advance() - - this = exp.var(" ".join(privilege_parts)) - expressions = ( - self._parse_wrapped_csv(self._parse_column) - if self._match(TokenType.L_PAREN, advance=False) - else None - ) - - return self.expression(exp.GrantPrivilege, this=this, expressions=expressions) - - def _parse_grant_principal(self) -> t.Optional[exp.GrantPrincipal]: - kind = self._match_texts(("ROLE", "GROUP")) and self._prev.text.upper() - principal = self._parse_id_var() - - if not principal: - return None - - return self.expression(exp.GrantPrincipal, this=principal, kind=kind) - - def _parse_grant_revoke_common( - self, - ) -> t.Tuple[t.Optional[t.List], t.Optional[str], t.Optional[exp.Expression]]: - privileges = self._parse_csv(self._parse_grant_privilege) - - self._match(TokenType.ON) - kind = self._match_set(self.CREATABLES) and self._prev.text.upper() - - # Attempt to parse the securable e.g. MySQL allows names - # such as "foo.*", "*.*" which are not easily parseable yet - securable = self._try_parse(self._parse_table_parts) - - return privileges, kind, securable - - def _parse_grant(self) -> exp.Grant | exp.Command: - start = self._prev - - privileges, kind, securable = self._parse_grant_revoke_common() - - if not securable or not self._match_text_seq("TO"): - return self._parse_as_command(start) - - principals = self._parse_csv(self._parse_grant_principal) - - grant_option = self._match_text_seq("WITH", "GRANT", "OPTION") - - if self._curr: - return self._parse_as_command(start) - - return self.expression( - exp.Grant, - privileges=privileges, - kind=kind, - securable=securable, - principals=principals, - grant_option=grant_option, - ) - - def _parse_revoke(self) -> exp.Revoke | exp.Command: - start = self._prev - - grant_option = self._match_text_seq("GRANT", "OPTION", "FOR") - - privileges, kind, securable = self._parse_grant_revoke_common() - - if not securable or not self._match_text_seq("FROM"): - return self._parse_as_command(start) - - principals = self._parse_csv(self._parse_grant_principal) - - cascade = None - if self._match_texts(("CASCADE", "RESTRICT")): - cascade = self._prev.text.upper() - - if self._curr: - return self._parse_as_command(start) - - return self.expression( - exp.Revoke, - privileges=privileges, - kind=kind, - securable=securable, - principals=principals, - grant_option=grant_option, - cascade=cascade, - ) - - def _parse_overlay(self) -> exp.Overlay: - def _parse_overlay_arg(text: str) -> t.Optional[exp.Expression]: - return ( - self._match(TokenType.COMMA) or self._match_text_seq(text) - ) and self._parse_bitwise() - - return self.expression( - exp.Overlay, - this=self._parse_bitwise(), - expression=_parse_overlay_arg("PLACING"), - from_=_parse_overlay_arg("FROM"), - for_=_parse_overlay_arg("FOR"), - ) - - def _parse_format_name(self) -> exp.Property: - # Note: Although not specified in the docs, Snowflake does accept a string/identifier - # for FILE_FORMAT = - return self.expression( - exp.Property, - this=exp.var("FORMAT_NAME"), - value=self._parse_string() or self._parse_table_parts(), - ) - - def _parse_max_min_by(self, expr_type: t.Type[exp.AggFunc]) -> exp.AggFunc: - args: t.List[exp.Expression] = [] - - if self._match(TokenType.DISTINCT): - args.append( - self.expression(exp.Distinct, expressions=[self._parse_lambda()]) - ) - self._match(TokenType.COMMA) - - args.extend(self._parse_function_args()) - - return self.expression( - expr_type, - this=seq_get(args, 0), - expression=seq_get(args, 1), - count=seq_get(args, 2), - ) - - def _identifier_expression( - self, token: t.Optional[Token] = None, **kwargs: t.Any - ) -> exp.Identifier: - return self.expression(exp.Identifier, token=token or self._prev, **kwargs) - - def _build_pipe_cte( - self, - query: exp.Query, - expressions: t.List[exp.Expression], - alias_cte: t.Optional[exp.TableAlias] = None, - ) -> exp.Select: - new_cte: t.Optional[t.Union[str, exp.TableAlias]] - if alias_cte: - new_cte = alias_cte - else: - self._pipe_cte_counter += 1 - new_cte = f"__tmp{self._pipe_cte_counter}" - - with_ = query.args.get("with_") - ctes = with_.pop() if with_ else None - - new_select = exp.select(*expressions, copy=False).from_(new_cte, copy=False) - if ctes: - new_select.set("with_", ctes) - - return new_select.with_(new_cte, as_=query, copy=False) - - def _parse_pipe_syntax_select(self, query: exp.Select) -> exp.Select: - select = self._parse_select(consume_pipe=False) - if not select: - return query - - return self._build_pipe_cte( - query=query.select(*select.expressions, append=False), - expressions=[exp.Star()], - ) - - def _parse_pipe_syntax_limit(self, query: exp.Select) -> exp.Select: - limit = self._parse_limit() - offset = self._parse_offset() - if limit: - curr_limit = query.args.get("limit", limit) - if curr_limit.expression.to_py() >= limit.expression.to_py(): - query.limit(limit, copy=False) - if offset: - curr_offset = query.args.get("offset") - curr_offset = curr_offset.expression.to_py() if curr_offset else 0 - query.offset( - exp.Literal.number(curr_offset + offset.expression.to_py()), copy=False - ) - - return query - - def _parse_pipe_syntax_aggregate_fields(self) -> t.Optional[exp.Expression]: - this = self._parse_disjunction() - if self._match_text_seq("GROUP", "AND", advance=False): - return this - - this = self._parse_alias(this) - - if self._match_set((TokenType.ASC, TokenType.DESC), advance=False): - return self._parse_ordered(lambda: this) - - return this - - def _parse_pipe_syntax_aggregate_group_order_by( - self, query: exp.Select, group_by_exists: bool = True - ) -> exp.Select: - expr = self._parse_csv(self._parse_pipe_syntax_aggregate_fields) - aggregates_or_groups, orders = [], [] - for element in expr: - if isinstance(element, exp.Ordered): - this = element.this - if isinstance(this, exp.Alias): - element.set("this", this.args["alias"]) - orders.append(element) - else: - this = element - aggregates_or_groups.append(this) - - if group_by_exists: - query.select(*aggregates_or_groups, copy=False).group_by( - *[ - projection.args.get("alias", projection) - for projection in aggregates_or_groups - ], - copy=False, - ) - else: - query.select(*aggregates_or_groups, append=False, copy=False) - - if orders: - return query.order_by(*orders, append=False, copy=False) - - return query - - def _parse_pipe_syntax_aggregate(self, query: exp.Select) -> exp.Select: - self._match_text_seq("AGGREGATE") - query = self._parse_pipe_syntax_aggregate_group_order_by( - query, group_by_exists=False - ) - - if self._match(TokenType.GROUP_BY) or ( - self._match_text_seq("GROUP", "AND") and self._match(TokenType.ORDER_BY) - ): - query = self._parse_pipe_syntax_aggregate_group_order_by(query) - - return self._build_pipe_cte(query=query, expressions=[exp.Star()]) - - def _parse_pipe_syntax_set_operator( - self, query: exp.Query - ) -> t.Optional[exp.Query]: - first_setop = self.parse_set_operation(this=query) - if not first_setop: - return None - - def _parse_and_unwrap_query() -> t.Optional[exp.Select]: - expr = self._parse_paren() - return expr.assert_is(exp.Subquery).unnest() if expr else None - - first_setop.this.pop() - - setops = [ - first_setop.expression.pop().assert_is(exp.Subquery).unnest(), - *self._parse_csv(_parse_and_unwrap_query), - ] - - query = self._build_pipe_cte(query=query, expressions=[exp.Star()]) - with_ = query.args.get("with_") - ctes = with_.pop() if with_ else None - - if isinstance(first_setop, exp.Union): - query = query.union(*setops, copy=False, **first_setop.args) - elif isinstance(first_setop, exp.Except): - query = query.except_(*setops, copy=False, **first_setop.args) - else: - query = query.intersect(*setops, copy=False, **first_setop.args) - - query.set("with_", ctes) - - return self._build_pipe_cte(query=query, expressions=[exp.Star()]) - - def _parse_pipe_syntax_join(self, query: exp.Query) -> t.Optional[exp.Query]: - join = self._parse_join() - if not join: - return None - - if isinstance(query, exp.Select): - return query.join(join, copy=False) - - return query - - def _parse_pipe_syntax_pivot(self, query: exp.Select) -> exp.Select: - pivots = self._parse_pivots() - if not pivots: - return query - - from_ = query.args.get("from_") - if from_: - from_.this.set("pivots", pivots) - - return self._build_pipe_cte(query=query, expressions=[exp.Star()]) - - def _parse_pipe_syntax_extend(self, query: exp.Select) -> exp.Select: - self._match_text_seq("EXTEND") - query.select( - *[exp.Star(), *self._parse_expressions()], append=False, copy=False - ) - return self._build_pipe_cte(query=query, expressions=[exp.Star()]) - - def _parse_pipe_syntax_tablesample(self, query: exp.Select) -> exp.Select: - sample = self._parse_table_sample() - - with_ = query.args.get("with_") - if with_: - with_.expressions[-1].this.set("sample", sample) - else: - query.set("sample", sample) - - return query - - def _parse_pipe_syntax_query(self, query: exp.Query) -> t.Optional[exp.Query]: - if isinstance(query, exp.Subquery): - query = exp.select("*").from_(query, copy=False) - - if not query.args.get("from_"): - query = exp.select("*").from_(query.subquery(copy=False), copy=False) - - while self._match(TokenType.PIPE_GT): - start = self._curr - parser = self.PIPE_SYNTAX_TRANSFORM_PARSERS.get(self._curr.text.upper()) - if not parser: - # The set operators (UNION, etc) and the JOIN operator have a few common starting - # keywords, making it tricky to disambiguate them without lookahead. The approach - # here is to try and parse a set operation and if that fails, then try to parse a - # join operator. If that fails as well, then the operator is not supported. - parsed_query = self._parse_pipe_syntax_set_operator(query) - parsed_query = parsed_query or self._parse_pipe_syntax_join(query) - if not parsed_query: - self._retreat(start) - self.raise_error( - f"Unsupported pipe syntax operator: '{start.text.upper()}'." - ) - break - query = parsed_query - else: - query = parser(self, query) - - return query - - def _parse_declareitem(self) -> t.Optional[exp.DeclareItem]: - vars = self._parse_csv(self._parse_id_var) - if not vars: - return None - - return self.expression( - exp.DeclareItem, - this=vars, - kind=self._parse_types(), - default=self._match(TokenType.DEFAULT) and self._parse_bitwise(), - ) - - def _parse_declare(self) -> exp.Declare | exp.Command: - start = self._prev - expressions = self._try_parse(lambda: self._parse_csv(self._parse_declareitem)) - - if not expressions or self._curr: - return self._parse_as_command(start) - - return self.expression(exp.Declare, expressions=expressions) - - def build_cast(self, strict: bool, **kwargs) -> exp.Cast: - exp_class = exp.Cast if strict else exp.TryCast - - if exp_class == exp.TryCast: - kwargs["requires_string"] = self.dialect.TRY_CAST_REQUIRES_STRING - - return self.expression(exp_class, **kwargs) - - def _parse_json_value(self) -> exp.JSONValue: - this = self._parse_bitwise() - self._match(TokenType.COMMA) - path = self._parse_bitwise() - - returning = self._match(TokenType.RETURNING) and self._parse_type() - - return self.expression( - exp.JSONValue, - this=this, - path=self.dialect.to_json_path(path), - returning=returning, - on_condition=self._parse_on_condition(), - ) - - def _parse_group_concat(self) -> t.Optional[exp.Expression]: - def concat_exprs( - node: t.Optional[exp.Expression], exprs: t.List[exp.Expression] - ) -> exp.Expression: - if isinstance(node, exp.Distinct) and len(node.expressions) > 1: - concat_exprs = [ - self.expression( - exp.Concat, - expressions=node.expressions, - safe=True, - coalesce=self.dialect.CONCAT_COALESCE, - ) - ] - node.set("expressions", concat_exprs) - return node - if len(exprs) == 1: - return exprs[0] - return self.expression( - exp.Concat, - expressions=args, - safe=True, - coalesce=self.dialect.CONCAT_COALESCE, - ) - - args = self._parse_csv(self._parse_lambda) - - if args: - order = args[-1] if isinstance(args[-1], exp.Order) else None - - if order: - # Order By is the last (or only) expression in the list and has consumed the 'expr' before it, - # remove 'expr' from exp.Order and add it back to args - args[-1] = order.this - order.set("this", concat_exprs(order.this, args)) - - this = order or concat_exprs(args[0], args) - else: - this = None - - separator = self._parse_field() if self._match(TokenType.SEPARATOR) else None - - return self.expression(exp.GroupConcat, this=this, separator=separator) - - def _parse_initcap(self) -> exp.Initcap: - expr = exp.Initcap.from_arg_list(self._parse_function_args()) - - # attach dialect's default delimiters - if expr.args.get("expression") is None: - expr.set( - "expression", - exp.Literal.string(self.dialect.INITCAP_DEFAULT_DELIMITER_CHARS), - ) - - return expr - - def _parse_operator( - self, this: t.Optional[exp.Expression] - ) -> t.Optional[exp.Expression]: - while True: - if not self._match(TokenType.L_PAREN): - break - - op = "" - while self._curr and not self._match(TokenType.R_PAREN): - op += self._curr.text - self._advance() - - this = self.expression( - exp.Operator, - comments=self._prev_comments, - this=this, - operator=op, - expression=self._parse_bitwise(), - ) - - if not self._match(TokenType.OPERATOR): - break - - return this diff --git a/third_party/bigframes_vendored/sqlglot/planner.py b/third_party/bigframes_vendored/sqlglot/planner.py deleted file mode 100644 index d564253e57b..00000000000 --- a/third_party/bigframes_vendored/sqlglot/planner.py +++ /dev/null @@ -1,473 +0,0 @@ -# Contains code from https://github.com/tobymao/sqlglot/blob/v28.5.0/sqlglot/planner.py - -from __future__ import annotations - -import math -import typing as t - -from bigframes_vendored.sqlglot import alias, exp -from bigframes_vendored.sqlglot.helper import name_sequence -from bigframes_vendored.sqlglot.optimizer.eliminate_joins import join_condition - - -class Plan: - def __init__(self, expression: exp.Expression) -> None: - self.expression = expression.copy() - self.root = Step.from_expression(self.expression) - self._dag: t.Dict[Step, t.Set[Step]] = {} - - @property - def dag(self) -> t.Dict[Step, t.Set[Step]]: - if not self._dag: - dag: t.Dict[Step, t.Set[Step]] = {} - nodes = {self.root} - - while nodes: - node = nodes.pop() - dag[node] = set() - - for dep in node.dependencies: - dag[node].add(dep) - nodes.add(dep) - - self._dag = dag - - return self._dag - - @property - def leaves(self) -> t.Iterator[Step]: - return (node for node, deps in self.dag.items() if not deps) - - def __repr__(self) -> str: - return f"Plan\n----\n{repr(self.root)}" - - -class Step: - @classmethod - def from_expression( - cls, expression: exp.Expression, ctes: t.Optional[t.Dict[str, Step]] = None - ) -> Step: - """ - Builds a DAG of Steps from a SQL expression so that it's easier to execute in an engine. - Note: the expression's tables and subqueries must be aliased for this method to work. For - example, given the following expression: - - SELECT - x.a, - SUM(x.b) - FROM x AS x - JOIN y AS y - ON x.a = y.a - GROUP BY x.a - - the following DAG is produced (the expression IDs might differ per execution): - - - Aggregate: x (4347984624) - Context: - Aggregations: - - SUM(x.b) - Group: - - x.a - Projections: - - x.a - - "x"."" - Dependencies: - - Join: x (4347985296) - Context: - y: - On: x.a = y.a - Projections: - Dependencies: - - Scan: x (4347983136) - Context: - Source: x AS x - Projections: - - Scan: y (4343416624) - Context: - Source: y AS y - Projections: - - Args: - expression: the expression to build the DAG from. - ctes: a dictionary that maps CTEs to their corresponding Step DAG by name. - - Returns: - A Step DAG corresponding to `expression`. - """ - ctes = ctes or {} - expression = expression.unnest() - with_ = expression.args.get("with_") - - # CTEs break the mold of scope and introduce themselves to all in the context. - if with_: - ctes = ctes.copy() - for cte in with_.expressions: - step = Step.from_expression(cte.this, ctes) - step.name = cte.alias - ctes[step.name] = step # type: ignore - - from_ = expression.args.get("from_") - - if isinstance(expression, exp.Select) and from_: - step = Scan.from_expression(from_.this, ctes) - elif isinstance(expression, exp.SetOperation): - step = SetOperation.from_expression(expression, ctes) - else: - step = Scan() - - joins = expression.args.get("joins") - - if joins: - join = Join.from_joins(joins, ctes) - join.name = step.name - join.source_name = step.name - join.add_dependency(step) - step = join - - projections = [] # final selects in this chain of steps representing a select - operands = {} # intermediate computations of agg funcs eg x + 1 in SUM(x + 1) - aggregations = {} - next_operand_name = name_sequence("_a_") - - def extract_agg_operands(expression): - agg_funcs = tuple(expression.find_all(exp.AggFunc)) - if agg_funcs: - aggregations[expression] = None - - for agg in agg_funcs: - for operand in agg.unnest_operands(): - if isinstance(operand, exp.Column): - continue - if operand not in operands: - operands[operand] = next_operand_name() - - operand.replace(exp.column(operands[operand], quoted=True)) - - return bool(agg_funcs) - - def set_ops_and_aggs(step): - step.operands = tuple( - alias(operand, alias_) for operand, alias_ in operands.items() - ) - step.aggregations = list(aggregations) - - for e in expression.expressions: - if e.find(exp.AggFunc): - projections.append(exp.column(e.alias_or_name, step.name, quoted=True)) - extract_agg_operands(e) - else: - projections.append(e) - - where = expression.args.get("where") - - if where: - step.condition = where.this - - group = expression.args.get("group") - - if group or aggregations: - aggregate = Aggregate() - aggregate.source = step.name - aggregate.name = step.name - - having = expression.args.get("having") - - if having: - if extract_agg_operands(exp.alias_(having.this, "_h", quoted=True)): - aggregate.condition = exp.column("_h", step.name, quoted=True) - else: - aggregate.condition = having.this - - set_ops_and_aggs(aggregate) - - # give aggregates names and replace projections with references to them - aggregate.group = { - f"_g{i}": e for i, e in enumerate(group.expressions if group else []) - } - - intermediate: t.Dict[str | exp.Expression, str] = {} - for k, v in aggregate.group.items(): - intermediate[v] = k - if isinstance(v, exp.Column): - intermediate[v.name] = k - - for projection in projections: - for node in projection.walk(): - name = intermediate.get(node) - if name: - node.replace(exp.column(name, step.name)) - - if aggregate.condition: - for node in aggregate.condition.walk(): - name = intermediate.get(node) or intermediate.get(node.name) - if name: - node.replace(exp.column(name, step.name)) - - aggregate.add_dependency(step) - step = aggregate - else: - aggregate = None - - order = expression.args.get("order") - - if order: - if aggregate and isinstance(step, Aggregate): - for i, ordered in enumerate(order.expressions): - if extract_agg_operands( - exp.alias_(ordered.this, f"_o_{i}", quoted=True) - ): - ordered.this.replace( - exp.column(f"_o_{i}", step.name, quoted=True) - ) - - set_ops_and_aggs(aggregate) - - sort = Sort() - sort.name = step.name - sort.key = order.expressions - sort.add_dependency(step) - step = sort - - step.projections = projections - - if isinstance(expression, exp.Select) and expression.args.get("distinct"): - distinct = Aggregate() - distinct.source = step.name - distinct.name = step.name - distinct.group = { - e.alias_or_name: exp.column(col=e.alias_or_name, table=step.name) - for e in projections or expression.expressions - } - distinct.add_dependency(step) - step = distinct - - limit = expression.args.get("limit") - - if limit: - step.limit = int(limit.text("expression")) - - return step - - def __init__(self) -> None: - self.name: t.Optional[str] = None - self.dependencies: t.Set[Step] = set() - self.dependents: t.Set[Step] = set() - self.projections: t.Sequence[exp.Expression] = [] - self.limit: float = math.inf - self.condition: t.Optional[exp.Expression] = None - - def add_dependency(self, dependency: Step) -> None: - self.dependencies.add(dependency) - dependency.dependents.add(self) - - def __repr__(self) -> str: - return self.to_s() - - def to_s(self, level: int = 0) -> str: - indent = " " * level - nested = f"{indent} " - - context = self._to_s(f"{nested} ") - - if context: - context = [f"{nested}Context:"] + context - - lines = [ - f"{indent}- {self.id}", - *context, - f"{nested}Projections:", - ] - - for expression in self.projections: - lines.append(f"{nested} - {expression.sql()}") - - if self.condition: - lines.append(f"{nested}Condition: {self.condition.sql()}") - - if self.limit is not math.inf: - lines.append(f"{nested}Limit: {self.limit}") - - if self.dependencies: - lines.append(f"{nested}Dependencies:") - for dependency in self.dependencies: - lines.append(" " + dependency.to_s(level + 1)) - - return "\n".join(lines) - - @property - def type_name(self) -> str: - return self.__class__.__name__ - - @property - def id(self) -> str: - name = self.name - name = f" {name}" if name else "" - return f"{self.type_name}:{name} ({id(self)})" - - def _to_s(self, _indent: str) -> t.List[str]: - return [] - - -class Scan(Step): - @classmethod - def from_expression( - cls, expression: exp.Expression, ctes: t.Optional[t.Dict[str, Step]] = None - ) -> Step: - table = expression - alias_ = expression.alias_or_name - - if isinstance(expression, exp.Subquery): - table = expression.this - step = Step.from_expression(table, ctes) - step.name = alias_ - return step - - step = Scan() - step.name = alias_ - step.source = expression - if ctes and table.name in ctes: - step.add_dependency(ctes[table.name]) - - return step - - def __init__(self) -> None: - super().__init__() - self.source: t.Optional[exp.Expression] = None - - def _to_s(self, indent: str) -> t.List[str]: - return [f"{indent}Source: {self.source.sql() if self.source else '-static-'}"] # type: ignore - - -class Join(Step): - @classmethod - def from_joins( - cls, joins: t.Iterable[exp.Join], ctes: t.Optional[t.Dict[str, Step]] = None - ) -> Join: - step = Join() - - for join in joins: - source_key, join_key, condition = join_condition(join) - step.joins[join.alias_or_name] = { - "side": join.side, # type: ignore - "join_key": join_key, - "source_key": source_key, - "condition": condition, - } - - step.add_dependency(Scan.from_expression(join.this, ctes)) - - return step - - def __init__(self) -> None: - super().__init__() - self.source_name: t.Optional[str] = None - self.joins: t.Dict[str, t.Dict[str, t.List[str] | exp.Expression]] = {} - - def _to_s(self, indent: str) -> t.List[str]: - lines = [f"{indent}Source: {self.source_name or self.name}"] - for name, join in self.joins.items(): - lines.append(f"{indent}{name}: {join['side'] or 'INNER'}") - join_key = ", ".join( - str(key) for key in t.cast(list, join.get("join_key") or []) - ) - if join_key: - lines.append(f"{indent}Key: {join_key}") - if join.get("condition"): - lines.append(f"{indent}On: {join['condition'].sql()}") # type: ignore - return lines - - -class Aggregate(Step): - def __init__(self) -> None: - super().__init__() - self.aggregations: t.List[exp.Expression] = [] - self.operands: t.Tuple[exp.Expression, ...] = () - self.group: t.Dict[str, exp.Expression] = {} - self.source: t.Optional[str] = None - - def _to_s(self, indent: str) -> t.List[str]: - lines = [f"{indent}Aggregations:"] - - for expression in self.aggregations: - lines.append(f"{indent} - {expression.sql()}") - - if self.group: - lines.append(f"{indent}Group:") - for expression in self.group.values(): - lines.append(f"{indent} - {expression.sql()}") - if self.condition: - lines.append(f"{indent}Having:") - lines.append(f"{indent} - {self.condition.sql()}") - if self.operands: - lines.append(f"{indent}Operands:") - for expression in self.operands: - lines.append(f"{indent} - {expression.sql()}") - - return lines - - -class Sort(Step): - def __init__(self) -> None: - super().__init__() - self.key = None - - def _to_s(self, indent: str) -> t.List[str]: - lines = [f"{indent}Key:"] - - for expression in self.key: # type: ignore - lines.append(f"{indent} - {expression.sql()}") - - return lines - - -class SetOperation(Step): - def __init__( - self, - op: t.Type[exp.Expression], - left: str | None, - right: str | None, - distinct: bool = False, - ) -> None: - super().__init__() - self.op = op - self.left = left - self.right = right - self.distinct = distinct - - @classmethod - def from_expression( - cls, expression: exp.Expression, ctes: t.Optional[t.Dict[str, Step]] = None - ) -> SetOperation: - assert isinstance(expression, exp.SetOperation) - - left = Step.from_expression(expression.left, ctes) - # SELECT 1 UNION SELECT 2 <-- these subqueries don't have names - left.name = left.name or "left" - right = Step.from_expression(expression.right, ctes) - right.name = right.name or "right" - step = cls( - op=expression.__class__, - left=left.name, - right=right.name, - distinct=bool(expression.args.get("distinct")), - ) - - step.add_dependency(left) - step.add_dependency(right) - - limit = expression.args.get("limit") - - if limit: - step.limit = int(limit.text("expression")) - - return step - - def _to_s(self, indent: str) -> t.List[str]: - lines = [] - if self.distinct: - lines.append(f"{indent}Distinct: {self.distinct}") - return lines - - @property - def type_name(self) -> str: - return self.op.__name__ diff --git a/third_party/bigframes_vendored/sqlglot/py.typed b/third_party/bigframes_vendored/sqlglot/py.typed deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/third_party/bigframes_vendored/sqlglot/schema.py b/third_party/bigframes_vendored/sqlglot/schema.py deleted file mode 100644 index 87928f2fc6f..00000000000 --- a/third_party/bigframes_vendored/sqlglot/schema.py +++ /dev/null @@ -1,641 +0,0 @@ -# Contains code from https://github.com/tobymao/sqlglot/blob/v28.5.0/sqlglot/schema.py - -from __future__ import annotations - -import abc -import typing as t - -from bigframes_vendored.sqlglot import expressions as exp -from bigframes_vendored.sqlglot.dialects.dialect import Dialect -from bigframes_vendored.sqlglot.errors import SchemaError -from bigframes_vendored.sqlglot.helper import dict_depth, first -from bigframes_vendored.sqlglot.trie import TrieResult, in_trie, new_trie - -if t.TYPE_CHECKING: - from bigframes_vendored.sqlglot.dialects.dialect import DialectType - - ColumnMapping = t.Union[t.Dict, str, t.List] - - -class Schema(abc.ABC): - """Abstract base class for database schemas""" - - @property - def dialect(self) -> t.Optional[Dialect]: - """ - Returns None by default. Subclasses that require dialect-specific - behavior should override this property. - """ - return None - - @abc.abstractmethod - def add_table( - self, - table: exp.Table | str, - column_mapping: t.Optional[ColumnMapping] = None, - dialect: DialectType = None, - normalize: t.Optional[bool] = None, - match_depth: bool = True, - ) -> None: - """ - Register or update a table. Some implementing classes may require column information to also be provided. - The added table must have the necessary number of qualifiers in its path to match the schema's nesting level. - - Args: - table: the `Table` expression instance or string representing the table. - column_mapping: a column mapping that describes the structure of the table. - dialect: the SQL dialect that will be used to parse `table` if it's a string. - normalize: whether to normalize identifiers according to the dialect of interest. - match_depth: whether to enforce that the table must match the schema's depth or not. - """ - - @abc.abstractmethod - def column_names( - self, - table: exp.Table | str, - only_visible: bool = False, - dialect: DialectType = None, - normalize: t.Optional[bool] = None, - ) -> t.Sequence[str]: - """ - Get the column names for a table. - - Args: - table: the `Table` expression instance. - only_visible: whether to include invisible columns. - dialect: the SQL dialect that will be used to parse `table` if it's a string. - normalize: whether to normalize identifiers according to the dialect of interest. - - Returns: - The sequence of column names. - """ - - @abc.abstractmethod - def get_column_type( - self, - table: exp.Table | str, - column: exp.Column | str, - dialect: DialectType = None, - normalize: t.Optional[bool] = None, - ) -> exp.DataType: - """ - Get the `sqlglot.exp.DataType` type of a column in the schema. - - Args: - table: the source table. - column: the target column. - dialect: the SQL dialect that will be used to parse `table` if it's a string. - normalize: whether to normalize identifiers according to the dialect of interest. - - Returns: - The resulting column type. - """ - - def has_column( - self, - table: exp.Table | str, - column: exp.Column | str, - dialect: DialectType = None, - normalize: t.Optional[bool] = None, - ) -> bool: - """ - Returns whether `column` appears in `table`'s schema. - - Args: - table: the source table. - column: the target column. - dialect: the SQL dialect that will be used to parse `table` if it's a string. - normalize: whether to normalize identifiers according to the dialect of interest. - - Returns: - True if the column appears in the schema, False otherwise. - """ - name = column if isinstance(column, str) else column.name - return name in self.column_names(table, dialect=dialect, normalize=normalize) - - @property - @abc.abstractmethod - def supported_table_args(self) -> t.Tuple[str, ...]: - """ - Table arguments this schema support, e.g. `("this", "db", "catalog")` - """ - - @property - def empty(self) -> bool: - """Returns whether the schema is empty.""" - return True - - -class AbstractMappingSchema: - def __init__( - self, - mapping: t.Optional[t.Dict] = None, - ) -> None: - self.mapping = mapping or {} - self.mapping_trie = new_trie( - tuple(reversed(t)) for t in flatten_schema(self.mapping, depth=self.depth()) - ) - self._supported_table_args: t.Tuple[str, ...] = tuple() - - @property - def empty(self) -> bool: - return not self.mapping - - def depth(self) -> int: - return dict_depth(self.mapping) - - @property - def supported_table_args(self) -> t.Tuple[str, ...]: - if not self._supported_table_args and self.mapping: - depth = self.depth() - - if not depth: # None - self._supported_table_args = tuple() - elif 1 <= depth <= 3: - self._supported_table_args = exp.TABLE_PARTS[:depth] - else: - raise SchemaError(f"Invalid mapping shape. Depth: {depth}") - - return self._supported_table_args - - def table_parts(self, table: exp.Table) -> t.List[str]: - return [part.name for part in reversed(table.parts)] - - def find( - self, - table: exp.Table, - raise_on_missing: bool = True, - ensure_data_types: bool = False, - ) -> t.Optional[t.Any]: - """ - Returns the schema of a given table. - - Args: - table: the target table. - raise_on_missing: whether to raise in case the schema is not found. - ensure_data_types: whether to convert `str` types to their `DataType` equivalents. - - Returns: - The schema of the target table. - """ - parts = self.table_parts(table)[0 : len(self.supported_table_args)] - value, trie = in_trie(self.mapping_trie, parts) - - if value == TrieResult.FAILED: - return None - - if value == TrieResult.PREFIX: - possibilities = flatten_schema(trie) - - if len(possibilities) == 1: - parts.extend(possibilities[0]) - else: - message = ", ".join(".".join(parts) for parts in possibilities) - if raise_on_missing: - raise SchemaError(f"Ambiguous mapping for {table}: {message}.") - return None - - return self.nested_get(parts, raise_on_missing=raise_on_missing) - - def nested_get( - self, - parts: t.Sequence[str], - d: t.Optional[t.Dict] = None, - raise_on_missing=True, - ) -> t.Optional[t.Any]: - return nested_get( - d or self.mapping, - *zip(self.supported_table_args, reversed(parts)), - raise_on_missing=raise_on_missing, - ) - - -class MappingSchema(AbstractMappingSchema, Schema): - """ - Schema based on a nested mapping. - - Args: - schema: Mapping in one of the following forms: - 1. {table: {col: type}} - 2. {db: {table: {col: type}}} - 3. {catalog: {db: {table: {col: type}}}} - 4. None - Tables will be added later - visible: Optional mapping of which columns in the schema are visible. If not provided, all columns - are assumed to be visible. The nesting should mirror that of the schema: - 1. {table: set(*cols)}} - 2. {db: {table: set(*cols)}}} - 3. {catalog: {db: {table: set(*cols)}}}} - dialect: The dialect to be used for custom type mappings & parsing string arguments. - normalize: Whether to normalize identifier names according to the given dialect or not. - """ - - def __init__( - self, - schema: t.Optional[t.Dict] = None, - visible: t.Optional[t.Dict] = None, - dialect: DialectType = None, - normalize: bool = True, - ) -> None: - self.visible = {} if visible is None else visible - self.normalize = normalize - self._dialect = Dialect.get_or_raise(dialect) - self._type_mapping_cache: t.Dict[str, exp.DataType] = {} - self._depth = 0 - schema = {} if schema is None else schema - - super().__init__(self._normalize(schema) if self.normalize else schema) - - @property - def dialect(self) -> Dialect: - """Returns the dialect for this mapping schema.""" - return self._dialect - - @classmethod - def from_mapping_schema(cls, mapping_schema: MappingSchema) -> MappingSchema: - return MappingSchema( - schema=mapping_schema.mapping, - visible=mapping_schema.visible, - dialect=mapping_schema.dialect, - normalize=mapping_schema.normalize, - ) - - def find( - self, - table: exp.Table, - raise_on_missing: bool = True, - ensure_data_types: bool = False, - ) -> t.Optional[t.Any]: - schema = super().find( - table, - raise_on_missing=raise_on_missing, - ensure_data_types=ensure_data_types, - ) - if ensure_data_types and isinstance(schema, dict): - schema = { - col: self._to_data_type(dtype) if isinstance(dtype, str) else dtype - for col, dtype in schema.items() - } - - return schema - - def copy(self, **kwargs) -> MappingSchema: - return MappingSchema( - **{ # type: ignore - "schema": self.mapping.copy(), - "visible": self.visible.copy(), - "dialect": self.dialect, - "normalize": self.normalize, - **kwargs, - } - ) - - def add_table( - self, - table: exp.Table | str, - column_mapping: t.Optional[ColumnMapping] = None, - dialect: DialectType = None, - normalize: t.Optional[bool] = None, - match_depth: bool = True, - ) -> None: - """ - Register or update a table. Updates are only performed if a new column mapping is provided. - The added table must have the necessary number of qualifiers in its path to match the schema's nesting level. - - Args: - table: the `Table` expression instance or string representing the table. - column_mapping: a column mapping that describes the structure of the table. - dialect: the SQL dialect that will be used to parse `table` if it's a string. - normalize: whether to normalize identifiers according to the dialect of interest. - match_depth: whether to enforce that the table must match the schema's depth or not. - """ - normalized_table = self._normalize_table( - table, dialect=dialect, normalize=normalize - ) - - if ( - match_depth - and not self.empty - and len(normalized_table.parts) != self.depth() - ): - raise SchemaError( - f"Table {normalized_table.sql(dialect=self.dialect)} must match the " - f"schema's nesting level: {self.depth()}." - ) - - normalized_column_mapping = { - self._normalize_name(key, dialect=dialect, normalize=normalize): value - for key, value in ensure_column_mapping(column_mapping).items() - } - - schema = self.find(normalized_table, raise_on_missing=False) - if schema and not normalized_column_mapping: - return - - parts = self.table_parts(normalized_table) - - nested_set(self.mapping, tuple(reversed(parts)), normalized_column_mapping) - new_trie([parts], self.mapping_trie) - - def column_names( - self, - table: exp.Table | str, - only_visible: bool = False, - dialect: DialectType = None, - normalize: t.Optional[bool] = None, - ) -> t.List[str]: - normalized_table = self._normalize_table( - table, dialect=dialect, normalize=normalize - ) - - schema = self.find(normalized_table) - if schema is None: - return [] - - if not only_visible or not self.visible: - return list(schema) - - visible = ( - self.nested_get(self.table_parts(normalized_table), self.visible) or [] - ) - return [col for col in schema if col in visible] - - def get_column_type( - self, - table: exp.Table | str, - column: exp.Column | str, - dialect: DialectType = None, - normalize: t.Optional[bool] = None, - ) -> exp.DataType: - normalized_table = self._normalize_table( - table, dialect=dialect, normalize=normalize - ) - - normalized_column_name = self._normalize_name( - column if isinstance(column, str) else column.this, - dialect=dialect, - normalize=normalize, - ) - - table_schema = self.find(normalized_table, raise_on_missing=False) - if table_schema: - column_type = table_schema.get(normalized_column_name) - - if isinstance(column_type, exp.DataType): - return column_type - elif isinstance(column_type, str): - return self._to_data_type(column_type, dialect=dialect) - - return exp.DataType.build("unknown") - - def has_column( - self, - table: exp.Table | str, - column: exp.Column | str, - dialect: DialectType = None, - normalize: t.Optional[bool] = None, - ) -> bool: - normalized_table = self._normalize_table( - table, dialect=dialect, normalize=normalize - ) - - normalized_column_name = self._normalize_name( - column if isinstance(column, str) else column.this, - dialect=dialect, - normalize=normalize, - ) - - table_schema = self.find(normalized_table, raise_on_missing=False) - return normalized_column_name in table_schema if table_schema else False - - def _normalize(self, schema: t.Dict) -> t.Dict: - """ - Normalizes all identifiers in the schema. - - Args: - schema: the schema to normalize. - - Returns: - The normalized schema mapping. - """ - normalized_mapping: t.Dict = {} - flattened_schema = flatten_schema(schema) - error_msg = "Table {} must match the schema's nesting level: {}." - - for keys in flattened_schema: - columns = nested_get(schema, *zip(keys, keys)) - - if not isinstance(columns, dict): - raise SchemaError( - error_msg.format(".".join(keys[:-1]), len(flattened_schema[0])) - ) - if not columns: - raise SchemaError( - f"Table {'.'.join(keys[:-1])} must have at least one column" - ) - if isinstance(first(columns.values()), dict): - raise SchemaError( - error_msg.format( - ".".join(keys + flatten_schema(columns)[0]), - len(flattened_schema[0]), - ), - ) - - normalized_keys = [self._normalize_name(key, is_table=True) for key in keys] - for column_name, column_type in columns.items(): - nested_set( - normalized_mapping, - normalized_keys + [self._normalize_name(column_name)], - column_type, - ) - - return normalized_mapping - - def _normalize_table( - self, - table: exp.Table | str, - dialect: DialectType = None, - normalize: t.Optional[bool] = None, - ) -> exp.Table: - dialect = dialect or self.dialect - normalize = self.normalize if normalize is None else normalize - - normalized_table = exp.maybe_parse( - table, into=exp.Table, dialect=dialect, copy=normalize - ) - - if normalize: - for part in normalized_table.parts: - if isinstance(part, exp.Identifier): - part.replace( - normalize_name( - part, dialect=dialect, is_table=True, normalize=normalize - ) - ) - - return normalized_table - - def _normalize_name( - self, - name: str | exp.Identifier, - dialect: DialectType = None, - is_table: bool = False, - normalize: t.Optional[bool] = None, - ) -> str: - return normalize_name( - name, - dialect=dialect or self.dialect, - is_table=is_table, - normalize=self.normalize if normalize is None else normalize, - ).name - - def depth(self) -> int: - if not self.empty and not self._depth: - # The columns themselves are a mapping, but we don't want to include those - self._depth = super().depth() - 1 - return self._depth - - def _to_data_type( - self, schema_type: str, dialect: DialectType = None - ) -> exp.DataType: - """ - Convert a type represented as a string to the corresponding `sqlglot.exp.DataType` object. - - Args: - schema_type: the type we want to convert. - dialect: the SQL dialect that will be used to parse `schema_type`, if needed. - - Returns: - The resulting expression type. - """ - if schema_type not in self._type_mapping_cache: - dialect = Dialect.get_or_raise(dialect) if dialect else self.dialect - udt = dialect.SUPPORTS_USER_DEFINED_TYPES - - try: - expression = exp.DataType.build(schema_type, dialect=dialect, udt=udt) - self._type_mapping_cache[schema_type] = expression - except AttributeError: - in_dialect = f" in dialect {dialect}" if dialect else "" - raise SchemaError(f"Failed to build type '{schema_type}'{in_dialect}.") - - return self._type_mapping_cache[schema_type] - - -def normalize_name( - identifier: str | exp.Identifier, - dialect: DialectType = None, - is_table: bool = False, - normalize: t.Optional[bool] = True, -) -> exp.Identifier: - if isinstance(identifier, str): - identifier = exp.parse_identifier(identifier, dialect=dialect) - - if not normalize: - return identifier - - # this is used for normalize_identifier, bigquery has special rules pertaining tables - identifier.meta["is_table"] = is_table - return Dialect.get_or_raise(dialect).normalize_identifier(identifier) - - -def ensure_schema(schema: Schema | t.Optional[t.Dict], **kwargs: t.Any) -> Schema: - if isinstance(schema, Schema): - return schema - - return MappingSchema(schema, **kwargs) - - -def ensure_column_mapping(mapping: t.Optional[ColumnMapping]) -> t.Dict: - if mapping is None: - return {} - elif isinstance(mapping, dict): - return mapping - elif isinstance(mapping, str): - col_name_type_strs = [x.strip() for x in mapping.split(",")] - return { - name_type_str.split(":")[0].strip(): name_type_str.split(":")[1].strip() - for name_type_str in col_name_type_strs - } - elif isinstance(mapping, list): - return {x.strip(): None for x in mapping} - - raise ValueError(f"Invalid mapping provided: {type(mapping)}") - - -def flatten_schema( - schema: t.Dict, depth: t.Optional[int] = None, keys: t.Optional[t.List[str]] = None -) -> t.List[t.List[str]]: - tables = [] - keys = keys or [] - depth = dict_depth(schema) - 1 if depth is None else depth - - for k, v in schema.items(): - if depth == 1 or not isinstance(v, dict): - tables.append(keys + [k]) - elif depth >= 2: - tables.extend(flatten_schema(v, depth - 1, keys + [k])) - - return tables - - -def nested_get( - d: t.Dict, *path: t.Tuple[str, str], raise_on_missing: bool = True -) -> t.Optional[t.Any]: - """ - Get a value for a nested dictionary. - - Args: - d: the dictionary to search. - *path: tuples of (name, key), where: - `key` is the key in the dictionary to get. - `name` is a string to use in the error if `key` isn't found. - - Returns: - The value or None if it doesn't exist. - """ - for name, key in path: - d = d.get(key) # type: ignore - if d is None: - if raise_on_missing: - name = "table" if name == "this" else name - raise ValueError(f"Unknown {name}: {key}") - return None - - return d - - -def nested_set(d: t.Dict, keys: t.Sequence[str], value: t.Any) -> t.Dict: - """ - In-place set a value for a nested dictionary - - Example: - >>> nested_set({}, ["top_key", "second_key"], "value") - {'top_key': {'second_key': 'value'}} - - >>> nested_set({"top_key": {"third_key": "third_value"}}, ["top_key", "second_key"], "value") - {'top_key': {'third_key': 'third_value', 'second_key': 'value'}} - - Args: - d: dictionary to update. - keys: the keys that makeup the path to `value`. - value: the value to set in the dictionary for the given key path. - - Returns: - The (possibly) updated dictionary. - """ - if not keys: - return d - - if len(keys) == 1: - d[keys[0]] = value - return d - - subd = d - for key in keys[:-1]: - if key not in subd: - subd = subd.setdefault(key, {}) - else: - subd = subd[key] - - subd[keys[-1]] = value - return d diff --git a/third_party/bigframes_vendored/sqlglot/serde.py b/third_party/bigframes_vendored/sqlglot/serde.py deleted file mode 100644 index dc21407e5f3..00000000000 --- a/third_party/bigframes_vendored/sqlglot/serde.py +++ /dev/null @@ -1,127 +0,0 @@ -# Contains code from https://github.com/tobymao/sqlglot/blob/v28.5.0/sqlglot/serde.py - -from __future__ import annotations - -import typing as t - -from bigframes_vendored.sqlglot import expressions as exp - -INDEX = "i" -ARG_KEY = "k" -IS_ARRAY = "a" -CLASS = "c" -TYPE = "t" -COMMENTS = "o" -META = "m" -VALUE = "v" -DATA_TYPE = "DataType.Type" - - -def dump(expression: exp.Expression) -> t.List[t.Dict[str, t.Any]]: - """ - Dump an Expression into a JSON serializable List. - """ - i = 0 - payloads = [] - stack: t.List[t.Tuple[t.Any, t.Optional[int], t.Optional[str], bool]] = [ - (expression, None, None, False) - ] - - while stack: - node, index, arg_key, is_array = stack.pop() - - payload: t.Dict[str, t.Any] = {} - - if index is not None: - payload[INDEX] = index - if arg_key is not None: - payload[ARG_KEY] = arg_key - if is_array: - payload[IS_ARRAY] = is_array - - payloads.append(payload) - - if hasattr(node, "parent"): - klass = node.__class__.__qualname__ - - if node.__class__.__module__ != exp.__name__: - klass = f"{node.__module__}.{klass}" - - payload[CLASS] = klass - - if node.type: - payload[TYPE] = dump(node.type) - if node.comments: - payload[COMMENTS] = node.comments - if node._meta is not None: - payload[META] = node._meta - if node.args: - for k, vs in reversed(node.args.items()): - if type(vs) is list: - for v in reversed(vs): - stack.append((v, i, k, True)) - elif vs is not None: - stack.append((vs, i, k, False)) - elif type(node) is exp.DataType.Type: - payload[CLASS] = DATA_TYPE - payload[VALUE] = node.value - else: - payload[VALUE] = node - - i += 1 - - return payloads - - -@t.overload -def load(payloads: None) -> None: ... - - -@t.overload -def load(payloads: t.List[t.Dict[str, t.Any]]) -> exp.Expression: ... - - -def load(payloads): - """ - Load a list of dicts generated by dump into an Expression. - """ - - if not payloads: - return None - - payload, *tail = payloads - root = _load(payload) - nodes = [root] - for payload in tail: - node = _load(payload) - nodes.append(node) - parent = nodes[payload[INDEX]] - arg_key = payload[ARG_KEY] - - if payload.get(IS_ARRAY): - parent.append(arg_key, node) - else: - parent.set(arg_key, node) - - return root - - -def _load(payload: t.Dict[str, t.Any]) -> exp.Expression | exp.DataType.Type: - class_name = payload.get(CLASS) - - if not class_name: - return payload[VALUE] - if class_name == DATA_TYPE: - return exp.DataType.Type(payload[VALUE]) - - if "." in class_name: - module_path, class_name = class_name.rsplit(".", maxsplit=1) - module = __import__(module_path, fromlist=[class_name]) - else: - module = exp - - expression = getattr(module, class_name)() - expression.type = load(payload.get(TYPE)) - expression.comments = payload.get(COMMENTS) - expression._meta = payload.get(META) - return expression diff --git a/third_party/bigframes_vendored/sqlglot/time.py b/third_party/bigframes_vendored/sqlglot/time.py deleted file mode 100644 index 05873b187a9..00000000000 --- a/third_party/bigframes_vendored/sqlglot/time.py +++ /dev/null @@ -1,689 +0,0 @@ -# Contains code from https://github.com/tobymao/sqlglot/blob/v28.5.0/sqlglot/time.py - -import datetime -import typing as t - -# The generic time format is based on python time.strftime. -# https://docs.python.org/3/library/time.html#time.strftime -from bigframes_vendored.sqlglot.trie import TrieResult, in_trie, new_trie - - -def format_time( - string: str, mapping: t.Dict[str, str], trie: t.Optional[t.Dict] = None -) -> t.Optional[str]: - """ - Converts a time string given a mapping. - - Examples: - >>> format_time("%Y", {"%Y": "YYYY"}) - 'YYYY' - - Args: - mapping: dictionary of time format to target time format. - trie: optional trie, can be passed in for performance. - - Returns: - The converted time string. - """ - if not string: - return None - - start = 0 - end = 1 - size = len(string) - trie = trie or new_trie(mapping) - current = trie - chunks = [] - sym = None - - while end <= size: - chars = string[start:end] - result, current = in_trie(current, chars[-1]) - - if result == TrieResult.FAILED: - if sym: - end -= 1 - chars = sym - sym = None - else: - chars = chars[0] - end = start + 1 - - start += len(chars) - chunks.append(chars) - current = trie - elif result == TrieResult.EXISTS: - sym = chars - - end += 1 - - if result != TrieResult.FAILED and end > size: - chunks.append(chars) - - return "".join(mapping.get(chars, chars) for chars in chunks) - - -TIMEZONES = { - tz.lower() - for tz in ( - "Africa/Abidjan", - "Africa/Accra", - "Africa/Addis_Ababa", - "Africa/Algiers", - "Africa/Asmara", - "Africa/Asmera", - "Africa/Bamako", - "Africa/Bangui", - "Africa/Banjul", - "Africa/Bissau", - "Africa/Blantyre", - "Africa/Brazzaville", - "Africa/Bujumbura", - "Africa/Cairo", - "Africa/Casablanca", - "Africa/Ceuta", - "Africa/Conakry", - "Africa/Dakar", - "Africa/Dar_es_Salaam", - "Africa/Djibouti", - "Africa/Douala", - "Africa/El_Aaiun", - "Africa/Freetown", - "Africa/Gaborone", - "Africa/Harare", - "Africa/Johannesburg", - "Africa/Juba", - "Africa/Kampala", - "Africa/Khartoum", - "Africa/Kigali", - "Africa/Kinshasa", - "Africa/Lagos", - "Africa/Libreville", - "Africa/Lome", - "Africa/Luanda", - "Africa/Lubumbashi", - "Africa/Lusaka", - "Africa/Malabo", - "Africa/Maputo", - "Africa/Maseru", - "Africa/Mbabane", - "Africa/Mogadishu", - "Africa/Monrovia", - "Africa/Nairobi", - "Africa/Ndjamena", - "Africa/Niamey", - "Africa/Nouakchott", - "Africa/Ouagadougou", - "Africa/Porto-Novo", - "Africa/Sao_Tome", - "Africa/Timbuktu", - "Africa/Tripoli", - "Africa/Tunis", - "Africa/Windhoek", - "America/Adak", - "America/Anchorage", - "America/Anguilla", - "America/Antigua", - "America/Araguaina", - "America/Argentina/Buenos_Aires", - "America/Argentina/Catamarca", - "America/Argentina/ComodRivadavia", - "America/Argentina/Cordoba", - "America/Argentina/Jujuy", - "America/Argentina/La_Rioja", - "America/Argentina/Mendoza", - "America/Argentina/Rio_Gallegos", - "America/Argentina/Salta", - "America/Argentina/San_Juan", - "America/Argentina/San_Luis", - "America/Argentina/Tucuman", - "America/Argentina/Ushuaia", - "America/Aruba", - "America/Asuncion", - "America/Atikokan", - "America/Atka", - "America/Bahia", - "America/Bahia_Banderas", - "America/Barbados", - "America/Belem", - "America/Belize", - "America/Blanc-Sablon", - "America/Boa_Vista", - "America/Bogota", - "America/Boise", - "America/Buenos_Aires", - "America/Cambridge_Bay", - "America/Campo_Grande", - "America/Cancun", - "America/Caracas", - "America/Catamarca", - "America/Cayenne", - "America/Cayman", - "America/Chicago", - "America/Chihuahua", - "America/Ciudad_Juarez", - "America/Coral_Harbour", - "America/Cordoba", - "America/Costa_Rica", - "America/Creston", - "America/Cuiaba", - "America/Curacao", - "America/Danmarkshavn", - "America/Dawson", - "America/Dawson_Creek", - "America/Denver", - "America/Detroit", - "America/Dominica", - "America/Edmonton", - "America/Eirunepe", - "America/El_Salvador", - "America/Ensenada", - "America/Fort_Nelson", - "America/Fort_Wayne", - "America/Fortaleza", - "America/Glace_Bay", - "America/Godthab", - "America/Goose_Bay", - "America/Grand_Turk", - "America/Grenada", - "America/Guadeloupe", - "America/Guatemala", - "America/Guayaquil", - "America/Guyana", - "America/Halifax", - "America/Havana", - "America/Hermosillo", - "America/Indiana/Indianapolis", - "America/Indiana/Knox", - "America/Indiana/Marengo", - "America/Indiana/Petersburg", - "America/Indiana/Tell_City", - "America/Indiana/Vevay", - "America/Indiana/Vincennes", - "America/Indiana/Winamac", - "America/Indianapolis", - "America/Inuvik", - "America/Iqaluit", - "America/Jamaica", - "America/Jujuy", - "America/Juneau", - "America/Kentucky/Louisville", - "America/Kentucky/Monticello", - "America/Knox_IN", - "America/Kralendijk", - "America/La_Paz", - "America/Lima", - "America/Los_Angeles", - "America/Louisville", - "America/Lower_Princes", - "America/Maceio", - "America/Managua", - "America/Manaus", - "America/Marigot", - "America/Martinique", - "America/Matamoros", - "America/Mazatlan", - "America/Mendoza", - "America/Menominee", - "America/Merida", - "America/Metlakatla", - "America/Mexico_City", - "America/Miquelon", - "America/Moncton", - "America/Monterrey", - "America/Montevideo", - "America/Montreal", - "America/Montserrat", - "America/Nassau", - "America/New_York", - "America/Nipigon", - "America/Nome", - "America/Noronha", - "America/North_Dakota/Beulah", - "America/North_Dakota/Center", - "America/North_Dakota/New_Salem", - "America/Nuuk", - "America/Ojinaga", - "America/Panama", - "America/Pangnirtung", - "America/Paramaribo", - "America/Phoenix", - "America/Port-au-Prince", - "America/Port_of_Spain", - "America/Porto_Acre", - "America/Porto_Velho", - "America/Puerto_Rico", - "America/Punta_Arenas", - "America/Rainy_River", - "America/Rankin_Inlet", - "America/Recife", - "America/Regina", - "America/Resolute", - "America/Rio_Branco", - "America/Rosario", - "America/Santa_Isabel", - "America/Santarem", - "America/Santiago", - "America/Santo_Domingo", - "America/Sao_Paulo", - "America/Scoresbysund", - "America/Shiprock", - "America/Sitka", - "America/St_Barthelemy", - "America/St_Johns", - "America/St_Kitts", - "America/St_Lucia", - "America/St_Thomas", - "America/St_Vincent", - "America/Swift_Current", - "America/Tegucigalpa", - "America/Thule", - "America/Thunder_Bay", - "America/Tijuana", - "America/Toronto", - "America/Tortola", - "America/Vancouver", - "America/Virgin", - "America/Whitehorse", - "America/Winnipeg", - "America/Yakutat", - "America/Yellowknife", - "Antarctica/Casey", - "Antarctica/Davis", - "Antarctica/DumontDUrville", - "Antarctica/Macquarie", - "Antarctica/Mawson", - "Antarctica/McMurdo", - "Antarctica/Palmer", - "Antarctica/Rothera", - "Antarctica/South_Pole", - "Antarctica/Syowa", - "Antarctica/Troll", - "Antarctica/Vostok", - "Arctic/Longyearbyen", - "Asia/Aden", - "Asia/Almaty", - "Asia/Amman", - "Asia/Anadyr", - "Asia/Aqtau", - "Asia/Aqtobe", - "Asia/Ashgabat", - "Asia/Ashkhabad", - "Asia/Atyrau", - "Asia/Baghdad", - "Asia/Bahrain", - "Asia/Baku", - "Asia/Bangkok", - "Asia/Barnaul", - "Asia/Beirut", - "Asia/Bishkek", - "Asia/Brunei", - "Asia/Calcutta", - "Asia/Chita", - "Asia/Choibalsan", - "Asia/Chongqing", - "Asia/Chungking", - "Asia/Colombo", - "Asia/Dacca", - "Asia/Damascus", - "Asia/Dhaka", - "Asia/Dili", - "Asia/Dubai", - "Asia/Dushanbe", - "Asia/Famagusta", - "Asia/Gaza", - "Asia/Harbin", - "Asia/Hebron", - "Asia/Ho_Chi_Minh", - "Asia/Hong_Kong", - "Asia/Hovd", - "Asia/Irkutsk", - "Asia/Istanbul", - "Asia/Jakarta", - "Asia/Jayapura", - "Asia/Jerusalem", - "Asia/Kabul", - "Asia/Kamchatka", - "Asia/Karachi", - "Asia/Kashgar", - "Asia/Kathmandu", - "Asia/Katmandu", - "Asia/Khandyga", - "Asia/Kolkata", - "Asia/Krasnoyarsk", - "Asia/Kuala_Lumpur", - "Asia/Kuching", - "Asia/Kuwait", - "Asia/Macao", - "Asia/Macau", - "Asia/Magadan", - "Asia/Makassar", - "Asia/Manila", - "Asia/Muscat", - "Asia/Nicosia", - "Asia/Novokuznetsk", - "Asia/Novosibirsk", - "Asia/Omsk", - "Asia/Oral", - "Asia/Phnom_Penh", - "Asia/Pontianak", - "Asia/Pyongyang", - "Asia/Qatar", - "Asia/Qostanay", - "Asia/Qyzylorda", - "Asia/Rangoon", - "Asia/Riyadh", - "Asia/Saigon", - "Asia/Sakhalin", - "Asia/Samarkand", - "Asia/Seoul", - "Asia/Shanghai", - "Asia/Singapore", - "Asia/Srednekolymsk", - "Asia/Taipei", - "Asia/Tashkent", - "Asia/Tbilisi", - "Asia/Tehran", - "Asia/Tel_Aviv", - "Asia/Thimbu", - "Asia/Thimphu", - "Asia/Tokyo", - "Asia/Tomsk", - "Asia/Ujung_Pandang", - "Asia/Ulaanbaatar", - "Asia/Ulan_Bator", - "Asia/Urumqi", - "Asia/Ust-Nera", - "Asia/Vientiane", - "Asia/Vladivostok", - "Asia/Yakutsk", - "Asia/Yangon", - "Asia/Yekaterinburg", - "Asia/Yerevan", - "Atlantic/Azores", - "Atlantic/Bermuda", - "Atlantic/Canary", - "Atlantic/Cape_Verde", - "Atlantic/Faeroe", - "Atlantic/Faroe", - "Atlantic/Jan_Mayen", - "Atlantic/Madeira", - "Atlantic/Reykjavik", - "Atlantic/South_Georgia", - "Atlantic/St_Helena", - "Atlantic/Stanley", - "Australia/ACT", - "Australia/Adelaide", - "Australia/Brisbane", - "Australia/Broken_Hill", - "Australia/Canberra", - "Australia/Currie", - "Australia/Darwin", - "Australia/Eucla", - "Australia/Hobart", - "Australia/LHI", - "Australia/Lindeman", - "Australia/Lord_Howe", - "Australia/Melbourne", - "Australia/NSW", - "Australia/North", - "Australia/Perth", - "Australia/Queensland", - "Australia/South", - "Australia/Sydney", - "Australia/Tasmania", - "Australia/Victoria", - "Australia/West", - "Australia/Yancowinna", - "Brazil/Acre", - "Brazil/DeNoronha", - "Brazil/East", - "Brazil/West", - "CET", - "CST6CDT", - "Canada/Atlantic", - "Canada/Central", - "Canada/Eastern", - "Canada/Mountain", - "Canada/Newfoundland", - "Canada/Pacific", - "Canada/Saskatchewan", - "Canada/Yukon", - "Chile/Continental", - "Chile/EasterIsland", - "Cuba", - "EET", - "EST", - "EST5EDT", - "Egypt", - "Eire", - "Etc/GMT", - "Etc/GMT+0", - "Etc/GMT+1", - "Etc/GMT+10", - "Etc/GMT+11", - "Etc/GMT+12", - "Etc/GMT+2", - "Etc/GMT+3", - "Etc/GMT+4", - "Etc/GMT+5", - "Etc/GMT+6", - "Etc/GMT+7", - "Etc/GMT+8", - "Etc/GMT+9", - "Etc/GMT-0", - "Etc/GMT-1", - "Etc/GMT-10", - "Etc/GMT-11", - "Etc/GMT-12", - "Etc/GMT-13", - "Etc/GMT-14", - "Etc/GMT-2", - "Etc/GMT-3", - "Etc/GMT-4", - "Etc/GMT-5", - "Etc/GMT-6", - "Etc/GMT-7", - "Etc/GMT-8", - "Etc/GMT-9", - "Etc/GMT0", - "Etc/Greenwich", - "Etc/UCT", - "Etc/UTC", - "Etc/Universal", - "Etc/Zulu", - "Europe/Amsterdam", - "Europe/Andorra", - "Europe/Astrakhan", - "Europe/Athens", - "Europe/Belfast", - "Europe/Belgrade", - "Europe/Berlin", - "Europe/Bratislava", - "Europe/Brussels", - "Europe/Bucharest", - "Europe/Budapest", - "Europe/Busingen", - "Europe/Chisinau", - "Europe/Copenhagen", - "Europe/Dublin", - "Europe/Gibraltar", - "Europe/Guernsey", - "Europe/Helsinki", - "Europe/Isle_of_Man", - "Europe/Istanbul", - "Europe/Jersey", - "Europe/Kaliningrad", - "Europe/Kiev", - "Europe/Kirov", - "Europe/Kyiv", - "Europe/Lisbon", - "Europe/Ljubljana", - "Europe/London", - "Europe/Luxembourg", - "Europe/Madrid", - "Europe/Malta", - "Europe/Mariehamn", - "Europe/Minsk", - "Europe/Monaco", - "Europe/Moscow", - "Europe/Nicosia", - "Europe/Oslo", - "Europe/Paris", - "Europe/Podgorica", - "Europe/Prague", - "Europe/Riga", - "Europe/Rome", - "Europe/Samara", - "Europe/San_Marino", - "Europe/Sarajevo", - "Europe/Saratov", - "Europe/Simferopol", - "Europe/Skopje", - "Europe/Sofia", - "Europe/Stockholm", - "Europe/Tallinn", - "Europe/Tirane", - "Europe/Tiraspol", - "Europe/Ulyanovsk", - "Europe/Uzhgorod", - "Europe/Vaduz", - "Europe/Vatican", - "Europe/Vienna", - "Europe/Vilnius", - "Europe/Volgograd", - "Europe/Warsaw", - "Europe/Zagreb", - "Europe/Zaporozhye", - "Europe/Zurich", - "GB", - "GB-Eire", - "GMT", - "GMT+0", - "GMT-0", - "GMT0", - "Greenwich", - "HST", - "Hongkong", - "Iceland", - "Indian/Antananarivo", - "Indian/Chagos", - "Indian/Christmas", - "Indian/Cocos", - "Indian/Comoro", - "Indian/Kerguelen", - "Indian/Mahe", - "Indian/Maldives", - "Indian/Mauritius", - "Indian/Mayotte", - "Indian/Reunion", - "Iran", - "Israel", - "Jamaica", - "Japan", - "Kwajalein", - "Libya", - "MET", - "MST", - "MST7MDT", - "Mexico/BajaNorte", - "Mexico/BajaSur", - "Mexico/General", - "NZ", - "NZ-CHAT", - "Navajo", - "PRC", - "PST8PDT", - "Pacific/Apia", - "Pacific/Auckland", - "Pacific/Bougainville", - "Pacific/Chatham", - "Pacific/Chuuk", - "Pacific/Easter", - "Pacific/Efate", - "Pacific/Enderbury", - "Pacific/Fakaofo", - "Pacific/Fiji", - "Pacific/Funafuti", - "Pacific/Galapagos", - "Pacific/Gambier", - "Pacific/Guadalcanal", - "Pacific/Guam", - "Pacific/Honolulu", - "Pacific/Johnston", - "Pacific/Kanton", - "Pacific/Kiritimati", - "Pacific/Kosrae", - "Pacific/Kwajalein", - "Pacific/Majuro", - "Pacific/Marquesas", - "Pacific/Midway", - "Pacific/Nauru", - "Pacific/Niue", - "Pacific/Norfolk", - "Pacific/Noumea", - "Pacific/Pago_Pago", - "Pacific/Palau", - "Pacific/Pitcairn", - "Pacific/Pohnpei", - "Pacific/Ponape", - "Pacific/Port_Moresby", - "Pacific/Rarotonga", - "Pacific/Saipan", - "Pacific/Samoa", - "Pacific/Tahiti", - "Pacific/Tarawa", - "Pacific/Tongatapu", - "Pacific/Truk", - "Pacific/Wake", - "Pacific/Wallis", - "Pacific/Yap", - "Poland", - "Portugal", - "ROC", - "ROK", - "Singapore", - "Turkey", - "UCT", - "US/Alaska", - "US/Aleutian", - "US/Arizona", - "US/Central", - "US/East-Indiana", - "US/Eastern", - "US/Hawaii", - "US/Indiana-Starke", - "US/Michigan", - "US/Mountain", - "US/Pacific", - "US/Samoa", - "UTC", - "Universal", - "W-SU", - "WET", - "Zulu", - ) -} - - -def subsecond_precision(timestamp_literal: str) -> int: - """ - Given an ISO-8601 timestamp literal, eg '2023-01-01 12:13:14.123456+00:00' - figure out its subsecond precision so we can construct types like DATETIME(6) - - Note that in practice, this is either 3 or 6 digits (3 = millisecond precision, 6 = microsecond precision) - - 6 is the maximum because strftime's '%f' formats to microseconds and almost every database supports microsecond precision in timestamps - - Except Presto/Trino which in most cases only supports millisecond precision but will still honour '%f' and format to microseconds (replacing the remaining 3 digits with 0's) - - Python prior to 3.11 only supports 0, 3 or 6 digits in a timestamp literal. Any other amounts will throw a 'ValueError: Invalid isoformat string:' error - """ - try: - parsed = datetime.datetime.fromisoformat(timestamp_literal) - subsecond_digit_count = len(str(parsed.microsecond).rstrip("0")) - precision = 0 - if subsecond_digit_count > 3: - precision = 6 - elif subsecond_digit_count > 0: - precision = 3 - return precision - except ValueError: - return 0 diff --git a/third_party/bigframes_vendored/sqlglot/tokens.py b/third_party/bigframes_vendored/sqlglot/tokens.py deleted file mode 100644 index 9a2c4f7a650..00000000000 --- a/third_party/bigframes_vendored/sqlglot/tokens.py +++ /dev/null @@ -1,1640 +0,0 @@ -# Contains code from https://github.com/tobymao/sqlglot/blob/v28.5.0/sqlglot/tokens.py - -from __future__ import annotations - -import os -import typing as t -from enum import auto - -from bigframes_vendored.sqlglot.errors import SqlglotError, TokenError -from bigframes_vendored.sqlglot.helper import AutoName -from bigframes_vendored.sqlglot.trie import TrieResult, in_trie, new_trie - -if t.TYPE_CHECKING: - from bigframes_vendored.sqlglot.dialects.dialect import DialectType - - -try: - from bigframes_vendored.sqlglotrs import Tokenizer as RsTokenizer # type: ignore - from bigframes_vendored.sqlglotrs import ( - TokenizerDialectSettings as RsTokenizerDialectSettings, - ) - from bigframes_vendored.sqlglotrs import TokenizerSettings as RsTokenizerSettings - from bigframes_vendored.sqlglotrs import TokenTypeSettings as RsTokenTypeSettings - - USE_RS_TOKENIZER = os.environ.get("SQLGLOTRS_TOKENIZER", "1") == "1" -except ImportError: - USE_RS_TOKENIZER = False - - -class TokenType(AutoName): - L_PAREN = auto() - R_PAREN = auto() - L_BRACKET = auto() - R_BRACKET = auto() - L_BRACE = auto() - R_BRACE = auto() - COMMA = auto() - DOT = auto() - DASH = auto() - PLUS = auto() - COLON = auto() - DOTCOLON = auto() - DCOLON = auto() - DCOLONDOLLAR = auto() - DCOLONPERCENT = auto() - DCOLONQMARK = auto() - DQMARK = auto() - SEMICOLON = auto() - STAR = auto() - BACKSLASH = auto() - SLASH = auto() - LT = auto() - LTE = auto() - GT = auto() - GTE = auto() - NOT = auto() - EQ = auto() - NEQ = auto() - NULLSAFE_EQ = auto() - COLON_EQ = auto() - COLON_GT = auto() - NCOLON_GT = auto() - AND = auto() - OR = auto() - AMP = auto() - DPIPE = auto() - PIPE_GT = auto() - PIPE = auto() - PIPE_SLASH = auto() - DPIPE_SLASH = auto() - CARET = auto() - CARET_AT = auto() - TILDA = auto() - ARROW = auto() - DARROW = auto() - FARROW = auto() - HASH = auto() - HASH_ARROW = auto() - DHASH_ARROW = auto() - LR_ARROW = auto() - DAT = auto() - LT_AT = auto() - AT_GT = auto() - DOLLAR = auto() - PARAMETER = auto() - SESSION = auto() - SESSION_PARAMETER = auto() - SESSION_USER = auto() - DAMP = auto() - AMP_LT = auto() - AMP_GT = auto() - ADJACENT = auto() - XOR = auto() - DSTAR = auto() - QMARK_AMP = auto() - QMARK_PIPE = auto() - HASH_DASH = auto() - EXCLAMATION = auto() - - URI_START = auto() - - BLOCK_START = auto() - BLOCK_END = auto() - - SPACE = auto() - BREAK = auto() - - STRING = auto() - NUMBER = auto() - IDENTIFIER = auto() - DATABASE = auto() - COLUMN = auto() - COLUMN_DEF = auto() - SCHEMA = auto() - TABLE = auto() - WAREHOUSE = auto() - STAGE = auto() - STREAMLIT = auto() - VAR = auto() - BIT_STRING = auto() - HEX_STRING = auto() - BYTE_STRING = auto() - NATIONAL_STRING = auto() - RAW_STRING = auto() - HEREDOC_STRING = auto() - UNICODE_STRING = auto() - - # types - BIT = auto() - BOOLEAN = auto() - TINYINT = auto() - UTINYINT = auto() - SMALLINT = auto() - USMALLINT = auto() - MEDIUMINT = auto() - UMEDIUMINT = auto() - INT = auto() - UINT = auto() - BIGINT = auto() - UBIGINT = auto() - BIGNUM = auto() # unlimited precision int - INT128 = auto() - UINT128 = auto() - INT256 = auto() - UINT256 = auto() - FLOAT = auto() - DOUBLE = auto() - UDOUBLE = auto() - DECIMAL = auto() - DECIMAL32 = auto() - DECIMAL64 = auto() - DECIMAL128 = auto() - DECIMAL256 = auto() - DECFLOAT = auto() - UDECIMAL = auto() - BIGDECIMAL = auto() - CHAR = auto() - NCHAR = auto() - VARCHAR = auto() - NVARCHAR = auto() - BPCHAR = auto() - TEXT = auto() - MEDIUMTEXT = auto() - LONGTEXT = auto() - BLOB = auto() - MEDIUMBLOB = auto() - LONGBLOB = auto() - TINYBLOB = auto() - TINYTEXT = auto() - NAME = auto() - BINARY = auto() - VARBINARY = auto() - JSON = auto() - JSONB = auto() - TIME = auto() - TIMETZ = auto() - TIME_NS = auto() - TIMESTAMP = auto() - TIMESTAMPTZ = auto() - TIMESTAMPLTZ = auto() - TIMESTAMPNTZ = auto() - TIMESTAMP_S = auto() - TIMESTAMP_MS = auto() - TIMESTAMP_NS = auto() - DATETIME = auto() - DATETIME2 = auto() - DATETIME64 = auto() - SMALLDATETIME = auto() - DATE = auto() - DATE32 = auto() - INT4RANGE = auto() - INT4MULTIRANGE = auto() - INT8RANGE = auto() - INT8MULTIRANGE = auto() - NUMRANGE = auto() - NUMMULTIRANGE = auto() - TSRANGE = auto() - TSMULTIRANGE = auto() - TSTZRANGE = auto() - TSTZMULTIRANGE = auto() - DATERANGE = auto() - DATEMULTIRANGE = auto() - UUID = auto() - GEOGRAPHY = auto() - GEOGRAPHYPOINT = auto() - NULLABLE = auto() - GEOMETRY = auto() - POINT = auto() - RING = auto() - LINESTRING = auto() - LOCALTIME = auto() - LOCALTIMESTAMP = auto() - MULTILINESTRING = auto() - POLYGON = auto() - MULTIPOLYGON = auto() - HLLSKETCH = auto() - HSTORE = auto() - SUPER = auto() - SERIAL = auto() - SMALLSERIAL = auto() - BIGSERIAL = auto() - XML = auto() - YEAR = auto() - USERDEFINED = auto() - MONEY = auto() - SMALLMONEY = auto() - ROWVERSION = auto() - IMAGE = auto() - VARIANT = auto() - OBJECT = auto() - INET = auto() - IPADDRESS = auto() - IPPREFIX = auto() - IPV4 = auto() - IPV6 = auto() - ENUM = auto() - ENUM8 = auto() - ENUM16 = auto() - FIXEDSTRING = auto() - LOWCARDINALITY = auto() - NESTED = auto() - AGGREGATEFUNCTION = auto() - SIMPLEAGGREGATEFUNCTION = auto() - TDIGEST = auto() - UNKNOWN = auto() - VECTOR = auto() - DYNAMIC = auto() - VOID = auto() - - # keywords - ALIAS = auto() - ALTER = auto() - ALL = auto() - ANTI = auto() - ANY = auto() - APPLY = auto() - ARRAY = auto() - ASC = auto() - ASOF = auto() - ATTACH = auto() - AUTO_INCREMENT = auto() - BEGIN = auto() - BETWEEN = auto() - BULK_COLLECT_INTO = auto() - CACHE = auto() - CASE = auto() - CHARACTER_SET = auto() - CLUSTER_BY = auto() - COLLATE = auto() - COMMAND = auto() - COMMENT = auto() - COMMIT = auto() - CONNECT_BY = auto() - CONSTRAINT = auto() - COPY = auto() - CREATE = auto() - CROSS = auto() - CUBE = auto() - CURRENT_DATE = auto() - CURRENT_DATETIME = auto() - CURRENT_SCHEMA = auto() - CURRENT_TIME = auto() - CURRENT_TIMESTAMP = auto() - CURRENT_USER = auto() - CURRENT_ROLE = auto() - CURRENT_CATALOG = auto() - DECLARE = auto() - DEFAULT = auto() - DELETE = auto() - DESC = auto() - DESCRIBE = auto() - DETACH = auto() - DICTIONARY = auto() - DISTINCT = auto() - DISTRIBUTE_BY = auto() - DIV = auto() - DROP = auto() - ELSE = auto() - END = auto() - ESCAPE = auto() - EXCEPT = auto() - EXECUTE = auto() - EXISTS = auto() - FALSE = auto() - FETCH = auto() - FILE = auto() - FILE_FORMAT = auto() - FILTER = auto() - FINAL = auto() - FIRST = auto() - FOR = auto() - FORCE = auto() - FOREIGN_KEY = auto() - FORMAT = auto() - FROM = auto() - FULL = auto() - FUNCTION = auto() - GET = auto() - GLOB = auto() - GLOBAL = auto() - GRANT = auto() - GROUP_BY = auto() - GROUPING_SETS = auto() - HAVING = auto() - HINT = auto() - IGNORE = auto() - ILIKE = auto() - IN = auto() - INDEX = auto() - INDEXED_BY = auto() - INNER = auto() - INSERT = auto() - INSTALL = auto() - INTERSECT = auto() - INTERVAL = auto() - INTO = auto() - INTRODUCER = auto() - IRLIKE = auto() - IS = auto() - ISNULL = auto() - JOIN = auto() - JOIN_MARKER = auto() - KEEP = auto() - KEY = auto() - KILL = auto() - LANGUAGE = auto() - LATERAL = auto() - LEFT = auto() - LIKE = auto() - LIMIT = auto() - LIST = auto() - LOAD = auto() - LOCK = auto() - MAP = auto() - MATCH = auto() - MATCH_CONDITION = auto() - MATCH_RECOGNIZE = auto() - MEMBER_OF = auto() - MERGE = auto() - MOD = auto() - MODEL = auto() - NATURAL = auto() - NEXT = auto() - NOTHING = auto() - NOTNULL = auto() - NULL = auto() - OBJECT_IDENTIFIER = auto() - OFFSET = auto() - ON = auto() - ONLY = auto() - OPERATOR = auto() - ORDER_BY = auto() - ORDER_SIBLINGS_BY = auto() - ORDERED = auto() - ORDINALITY = auto() - OUTER = auto() - OVER = auto() - OVERLAPS = auto() - OVERWRITE = auto() - PARTITION = auto() - PARTITION_BY = auto() - PERCENT = auto() - PIVOT = auto() - PLACEHOLDER = auto() - POSITIONAL = auto() - PRAGMA = auto() - PREWHERE = auto() - PRIMARY_KEY = auto() - PROCEDURE = auto() - PROPERTIES = auto() - PSEUDO_TYPE = auto() - PUT = auto() - QUALIFY = auto() - QUOTE = auto() - QDCOLON = auto() - RANGE = auto() - RECURSIVE = auto() - REFRESH = auto() - RENAME = auto() - REPLACE = auto() - RETURNING = auto() - REVOKE = auto() - REFERENCES = auto() - RIGHT = auto() - RLIKE = auto() - ROLLBACK = auto() - ROLLUP = auto() - ROW = auto() - ROWS = auto() - SELECT = auto() - SEMI = auto() - SEPARATOR = auto() - SEQUENCE = auto() - SERDE_PROPERTIES = auto() - SET = auto() - SETTINGS = auto() - SHOW = auto() - SIMILAR_TO = auto() - SOME = auto() - SORT_BY = auto() - SOUNDS_LIKE = auto() - START_WITH = auto() - STORAGE_INTEGRATION = auto() - STRAIGHT_JOIN = auto() - STRUCT = auto() - SUMMARIZE = auto() - TABLE_SAMPLE = auto() - TAG = auto() - TEMPORARY = auto() - TOP = auto() - THEN = auto() - TRUE = auto() - TRUNCATE = auto() - UNCACHE = auto() - UNION = auto() - UNNEST = auto() - UNPIVOT = auto() - UPDATE = auto() - USE = auto() - USING = auto() - VALUES = auto() - VIEW = auto() - SEMANTIC_VIEW = auto() - VOLATILE = auto() - WHEN = auto() - WHERE = auto() - WINDOW = auto() - WITH = auto() - UNIQUE = auto() - UTC_DATE = auto() - UTC_TIME = auto() - UTC_TIMESTAMP = auto() - VERSION_SNAPSHOT = auto() - TIMESTAMP_SNAPSHOT = auto() - OPTION = auto() - SINK = auto() - SOURCE = auto() - ANALYZE = auto() - NAMESPACE = auto() - EXPORT = auto() - - # sentinel - HIVE_TOKEN_STREAM = auto() - - -_ALL_TOKEN_TYPES = list(TokenType) -_TOKEN_TYPE_TO_INDEX = {token_type: i for i, token_type in enumerate(_ALL_TOKEN_TYPES)} - - -class Token: - __slots__ = ("token_type", "text", "line", "col", "start", "end", "comments") - - @classmethod - def number(cls, number: int) -> Token: - """Returns a NUMBER token with `number` as its text.""" - return cls(TokenType.NUMBER, str(number)) - - @classmethod - def string(cls, string: str) -> Token: - """Returns a STRING token with `string` as its text.""" - return cls(TokenType.STRING, string) - - @classmethod - def identifier(cls, identifier: str) -> Token: - """Returns an IDENTIFIER token with `identifier` as its text.""" - return cls(TokenType.IDENTIFIER, identifier) - - @classmethod - def var(cls, var: str) -> Token: - """Returns an VAR token with `var` as its text.""" - return cls(TokenType.VAR, var) - - def __init__( - self, - token_type: TokenType, - text: str, - line: int = 1, - col: int = 1, - start: int = 0, - end: int = 0, - comments: t.Optional[t.List[str]] = None, - ) -> None: - """Token initializer. - - Args: - token_type: The TokenType Enum. - text: The text of the token. - line: The line that the token ends on. - col: The column that the token ends on. - start: The start index of the token. - end: The ending index of the token. - comments: The comments to attach to the token. - """ - self.token_type = token_type - self.text = text - self.line = line - self.col = col - self.start = start - self.end = end - self.comments = [] if comments is None else comments - - def __repr__(self) -> str: - attributes = ", ".join(f"{k}: {getattr(self, k)}" for k in self.__slots__) - return f"" - - -class _Tokenizer(type): - def __new__(cls, clsname, bases, attrs): - klass = super().__new__(cls, clsname, bases, attrs) - - def _convert_quotes(arr: t.List[str | t.Tuple[str, str]]) -> t.Dict[str, str]: - return dict( - (item, item) if isinstance(item, str) else (item[0], item[1]) - for item in arr - ) - - def _quotes_to_format( - token_type: TokenType, arr: t.List[str | t.Tuple[str, str]] - ) -> t.Dict[str, t.Tuple[str, TokenType]]: - return {k: (v, token_type) for k, v in _convert_quotes(arr).items()} - - klass._QUOTES = _convert_quotes(klass.QUOTES) - klass._IDENTIFIERS = _convert_quotes(klass.IDENTIFIERS) - - klass._FORMAT_STRINGS = { - **{ - p + s: (e, TokenType.NATIONAL_STRING) - for s, e in klass._QUOTES.items() - for p in ("n", "N") - }, - **_quotes_to_format(TokenType.BIT_STRING, klass.BIT_STRINGS), - **_quotes_to_format(TokenType.BYTE_STRING, klass.BYTE_STRINGS), - **_quotes_to_format(TokenType.HEX_STRING, klass.HEX_STRINGS), - **_quotes_to_format(TokenType.RAW_STRING, klass.RAW_STRINGS), - **_quotes_to_format(TokenType.HEREDOC_STRING, klass.HEREDOC_STRINGS), - **_quotes_to_format(TokenType.UNICODE_STRING, klass.UNICODE_STRINGS), - } - - klass._STRING_ESCAPES = set(klass.STRING_ESCAPES) - klass._ESCAPE_FOLLOW_CHARS = set(klass.ESCAPE_FOLLOW_CHARS) - klass._IDENTIFIER_ESCAPES = set(klass.IDENTIFIER_ESCAPES) - klass._COMMENTS = { - **dict( - (comment, None) - if isinstance(comment, str) - else (comment[0], comment[1]) - for comment in klass.COMMENTS - ), - "{#": "#}", # Ensure Jinja comments are tokenized correctly in all dialects - } - if klass.HINT_START in klass.KEYWORDS: - klass._COMMENTS[klass.HINT_START] = "*/" - - klass._KEYWORD_TRIE = new_trie( - key.upper() - for key in ( - *klass.KEYWORDS, - *klass._COMMENTS, - *klass._QUOTES, - *klass._FORMAT_STRINGS, - ) - if " " in key or any(single in key for single in klass.SINGLE_TOKENS) - ) - - if USE_RS_TOKENIZER: - settings = RsTokenizerSettings( - white_space={ - k: _TOKEN_TYPE_TO_INDEX[v] for k, v in klass.WHITE_SPACE.items() - }, - single_tokens={ - k: _TOKEN_TYPE_TO_INDEX[v] for k, v in klass.SINGLE_TOKENS.items() - }, - keywords={ - k: _TOKEN_TYPE_TO_INDEX[v] for k, v in klass.KEYWORDS.items() - }, - numeric_literals=klass.NUMERIC_LITERALS, - identifiers=klass._IDENTIFIERS, - identifier_escapes=klass._IDENTIFIER_ESCAPES, - string_escapes=klass._STRING_ESCAPES, - quotes=klass._QUOTES, - format_strings={ - k: (v1, _TOKEN_TYPE_TO_INDEX[v2]) - for k, (v1, v2) in klass._FORMAT_STRINGS.items() - }, - has_bit_strings=bool(klass.BIT_STRINGS), - has_hex_strings=bool(klass.HEX_STRINGS), - comments=klass._COMMENTS, - var_single_tokens=klass.VAR_SINGLE_TOKENS, - commands={_TOKEN_TYPE_TO_INDEX[v] for v in klass.COMMANDS}, - command_prefix_tokens={ - _TOKEN_TYPE_TO_INDEX[v] for v in klass.COMMAND_PREFIX_TOKENS - }, - heredoc_tag_is_identifier=klass.HEREDOC_TAG_IS_IDENTIFIER, - string_escapes_allowed_in_raw_strings=klass.STRING_ESCAPES_ALLOWED_IN_RAW_STRINGS, - nested_comments=klass.NESTED_COMMENTS, - hint_start=klass.HINT_START, - tokens_preceding_hint={ - _TOKEN_TYPE_TO_INDEX[v] for v in klass.TOKENS_PRECEDING_HINT - }, - escape_follow_chars=klass._ESCAPE_FOLLOW_CHARS, - ) - token_types = RsTokenTypeSettings( - bit_string=_TOKEN_TYPE_TO_INDEX[TokenType.BIT_STRING], - break_=_TOKEN_TYPE_TO_INDEX[TokenType.BREAK], - dcolon=_TOKEN_TYPE_TO_INDEX[TokenType.DCOLON], - heredoc_string=_TOKEN_TYPE_TO_INDEX[TokenType.HEREDOC_STRING], - raw_string=_TOKEN_TYPE_TO_INDEX[TokenType.RAW_STRING], - hex_string=_TOKEN_TYPE_TO_INDEX[TokenType.HEX_STRING], - identifier=_TOKEN_TYPE_TO_INDEX[TokenType.IDENTIFIER], - number=_TOKEN_TYPE_TO_INDEX[TokenType.NUMBER], - parameter=_TOKEN_TYPE_TO_INDEX[TokenType.PARAMETER], - semicolon=_TOKEN_TYPE_TO_INDEX[TokenType.SEMICOLON], - string=_TOKEN_TYPE_TO_INDEX[TokenType.STRING], - var=_TOKEN_TYPE_TO_INDEX[TokenType.VAR], - heredoc_string_alternative=_TOKEN_TYPE_TO_INDEX[ - klass.HEREDOC_STRING_ALTERNATIVE - ], - hint=_TOKEN_TYPE_TO_INDEX[TokenType.HINT], - ) - klass._RS_TOKENIZER = RsTokenizer(settings, token_types) - else: - klass._RS_TOKENIZER = None - - return klass - - -class Tokenizer(metaclass=_Tokenizer): - SINGLE_TOKENS = { - "(": TokenType.L_PAREN, - ")": TokenType.R_PAREN, - "[": TokenType.L_BRACKET, - "]": TokenType.R_BRACKET, - "{": TokenType.L_BRACE, - "}": TokenType.R_BRACE, - "&": TokenType.AMP, - "^": TokenType.CARET, - ":": TokenType.COLON, - ",": TokenType.COMMA, - ".": TokenType.DOT, - "-": TokenType.DASH, - "=": TokenType.EQ, - ">": TokenType.GT, - "<": TokenType.LT, - "%": TokenType.MOD, - "!": TokenType.NOT, - "|": TokenType.PIPE, - "+": TokenType.PLUS, - ";": TokenType.SEMICOLON, - "/": TokenType.SLASH, - "\\": TokenType.BACKSLASH, - "*": TokenType.STAR, - "~": TokenType.TILDA, - "?": TokenType.PLACEHOLDER, - "@": TokenType.PARAMETER, - "#": TokenType.HASH, - # Used for breaking a var like x'y' but nothing else the token type doesn't matter - "'": TokenType.UNKNOWN, - "`": TokenType.UNKNOWN, - '"': TokenType.UNKNOWN, - } - - BIT_STRINGS: t.List[str | t.Tuple[str, str]] = [] - BYTE_STRINGS: t.List[str | t.Tuple[str, str]] = [] - HEX_STRINGS: t.List[str | t.Tuple[str, str]] = [] - RAW_STRINGS: t.List[str | t.Tuple[str, str]] = [] - HEREDOC_STRINGS: t.List[str | t.Tuple[str, str]] = [] - UNICODE_STRINGS: t.List[str | t.Tuple[str, str]] = [] - IDENTIFIERS: t.List[str | t.Tuple[str, str]] = ['"'] - QUOTES: t.List[t.Tuple[str, str] | str] = ["'"] - STRING_ESCAPES = ["'"] - VAR_SINGLE_TOKENS: t.Set[str] = set() - ESCAPE_FOLLOW_CHARS: t.List[str] = [] - - # The strings in this list can always be used as escapes, regardless of the surrounding - # identifier delimiters. By default, the closing delimiter is assumed to also act as an - # identifier escape, e.g. if we use double-quotes, then they also act as escapes: "x""" - IDENTIFIER_ESCAPES: t.List[str] = [] - - # Whether the heredoc tags follow the same lexical rules as unquoted identifiers - HEREDOC_TAG_IS_IDENTIFIER = False - - # Token that we'll generate as a fallback if the heredoc prefix doesn't correspond to a heredoc - HEREDOC_STRING_ALTERNATIVE = TokenType.VAR - - # Whether string escape characters function as such when placed within raw strings - STRING_ESCAPES_ALLOWED_IN_RAW_STRINGS = True - - NESTED_COMMENTS = True - - HINT_START = "/*+" - - TOKENS_PRECEDING_HINT = { - TokenType.SELECT, - TokenType.INSERT, - TokenType.UPDATE, - TokenType.DELETE, - } - - # Autofilled - _COMMENTS: t.Dict[str, str] = {} - _FORMAT_STRINGS: t.Dict[str, t.Tuple[str, TokenType]] = {} - _IDENTIFIERS: t.Dict[str, str] = {} - _IDENTIFIER_ESCAPES: t.Set[str] = set() - _QUOTES: t.Dict[str, str] = {} - _STRING_ESCAPES: t.Set[str] = set() - _KEYWORD_TRIE: t.Dict = {} - _RS_TOKENIZER: t.Optional[t.Any] = None - _ESCAPE_FOLLOW_CHARS: t.Set[str] = set() - - KEYWORDS: t.Dict[str, TokenType] = { - **{f"{{%{postfix}": TokenType.BLOCK_START for postfix in ("", "+", "-")}, - **{f"{prefix}%}}": TokenType.BLOCK_END for prefix in ("", "+", "-")}, - **{f"{{{{{postfix}": TokenType.BLOCK_START for postfix in ("+", "-")}, - **{f"{prefix}}}}}": TokenType.BLOCK_END for prefix in ("+", "-")}, - HINT_START: TokenType.HINT, - "&<": TokenType.AMP_LT, - "&>": TokenType.AMP_GT, - "==": TokenType.EQ, - "::": TokenType.DCOLON, - "?::": TokenType.QDCOLON, - "||": TokenType.DPIPE, - "|>": TokenType.PIPE_GT, - ">=": TokenType.GTE, - "<=": TokenType.LTE, - "<>": TokenType.NEQ, - "!=": TokenType.NEQ, - ":=": TokenType.COLON_EQ, - "<=>": TokenType.NULLSAFE_EQ, - "->": TokenType.ARROW, - "->>": TokenType.DARROW, - "=>": TokenType.FARROW, - "#>": TokenType.HASH_ARROW, - "#>>": TokenType.DHASH_ARROW, - "<->": TokenType.LR_ARROW, - "&&": TokenType.DAMP, - "??": TokenType.DQMARK, - "~~~": TokenType.GLOB, - "~~": TokenType.LIKE, - "~~*": TokenType.ILIKE, - "~*": TokenType.IRLIKE, - "-|-": TokenType.ADJACENT, - "ALL": TokenType.ALL, - "AND": TokenType.AND, - "ANTI": TokenType.ANTI, - "ANY": TokenType.ANY, - "ASC": TokenType.ASC, - "AS": TokenType.ALIAS, - "ASOF": TokenType.ASOF, - "AUTOINCREMENT": TokenType.AUTO_INCREMENT, - "AUTO_INCREMENT": TokenType.AUTO_INCREMENT, - "BEGIN": TokenType.BEGIN, - "BETWEEN": TokenType.BETWEEN, - "CACHE": TokenType.CACHE, - "UNCACHE": TokenType.UNCACHE, - "CASE": TokenType.CASE, - "CHARACTER SET": TokenType.CHARACTER_SET, - "CLUSTER BY": TokenType.CLUSTER_BY, - "COLLATE": TokenType.COLLATE, - "COLUMN": TokenType.COLUMN, - "COMMIT": TokenType.COMMIT, - "CONNECT BY": TokenType.CONNECT_BY, - "CONSTRAINT": TokenType.CONSTRAINT, - "COPY": TokenType.COPY, - "CREATE": TokenType.CREATE, - "CROSS": TokenType.CROSS, - "CUBE": TokenType.CUBE, - "CURRENT_DATE": TokenType.CURRENT_DATE, - "CURRENT_SCHEMA": TokenType.CURRENT_SCHEMA, - "CURRENT_TIME": TokenType.CURRENT_TIME, - "CURRENT_TIMESTAMP": TokenType.CURRENT_TIMESTAMP, - "CURRENT_USER": TokenType.CURRENT_USER, - "CURRENT_CATALOG": TokenType.CURRENT_CATALOG, - "DATABASE": TokenType.DATABASE, - "DEFAULT": TokenType.DEFAULT, - "DELETE": TokenType.DELETE, - "DESC": TokenType.DESC, - "DESCRIBE": TokenType.DESCRIBE, - "DISTINCT": TokenType.DISTINCT, - "DISTRIBUTE BY": TokenType.DISTRIBUTE_BY, - "DIV": TokenType.DIV, - "DROP": TokenType.DROP, - "ELSE": TokenType.ELSE, - "END": TokenType.END, - "ENUM": TokenType.ENUM, - "ESCAPE": TokenType.ESCAPE, - "EXCEPT": TokenType.EXCEPT, - "EXECUTE": TokenType.EXECUTE, - "EXISTS": TokenType.EXISTS, - "FALSE": TokenType.FALSE, - "FETCH": TokenType.FETCH, - "FILTER": TokenType.FILTER, - "FILE": TokenType.FILE, - "FIRST": TokenType.FIRST, - "FULL": TokenType.FULL, - "FUNCTION": TokenType.FUNCTION, - "FOR": TokenType.FOR, - "FOREIGN KEY": TokenType.FOREIGN_KEY, - "FORMAT": TokenType.FORMAT, - "FROM": TokenType.FROM, - "GEOGRAPHY": TokenType.GEOGRAPHY, - "GEOMETRY": TokenType.GEOMETRY, - "GLOB": TokenType.GLOB, - "GROUP BY": TokenType.GROUP_BY, - "GROUPING SETS": TokenType.GROUPING_SETS, - "HAVING": TokenType.HAVING, - "ILIKE": TokenType.ILIKE, - "IN": TokenType.IN, - "INDEX": TokenType.INDEX, - "INET": TokenType.INET, - "INNER": TokenType.INNER, - "INSERT": TokenType.INSERT, - "INTERVAL": TokenType.INTERVAL, - "INTERSECT": TokenType.INTERSECT, - "INTO": TokenType.INTO, - "IS": TokenType.IS, - "ISNULL": TokenType.ISNULL, - "JOIN": TokenType.JOIN, - "KEEP": TokenType.KEEP, - "KILL": TokenType.KILL, - "LATERAL": TokenType.LATERAL, - "LEFT": TokenType.LEFT, - "LIKE": TokenType.LIKE, - "LIMIT": TokenType.LIMIT, - "LOAD": TokenType.LOAD, - "LOCALTIME": TokenType.LOCALTIME, - "LOCALTIMESTAMP": TokenType.LOCALTIMESTAMP, - "LOCK": TokenType.LOCK, - "MERGE": TokenType.MERGE, - "NAMESPACE": TokenType.NAMESPACE, - "NATURAL": TokenType.NATURAL, - "NEXT": TokenType.NEXT, - "NOT": TokenType.NOT, - "NOTNULL": TokenType.NOTNULL, - "NULL": TokenType.NULL, - "OBJECT": TokenType.OBJECT, - "OFFSET": TokenType.OFFSET, - "ON": TokenType.ON, - "OR": TokenType.OR, - "XOR": TokenType.XOR, - "ORDER BY": TokenType.ORDER_BY, - "ORDINALITY": TokenType.ORDINALITY, - "OUTER": TokenType.OUTER, - "OVER": TokenType.OVER, - "OVERLAPS": TokenType.OVERLAPS, - "OVERWRITE": TokenType.OVERWRITE, - "PARTITION": TokenType.PARTITION, - "PARTITION BY": TokenType.PARTITION_BY, - "PARTITIONED BY": TokenType.PARTITION_BY, - "PARTITIONED_BY": TokenType.PARTITION_BY, - "PERCENT": TokenType.PERCENT, - "PIVOT": TokenType.PIVOT, - "PRAGMA": TokenType.PRAGMA, - "PRIMARY KEY": TokenType.PRIMARY_KEY, - "PROCEDURE": TokenType.PROCEDURE, - "OPERATOR": TokenType.OPERATOR, - "QUALIFY": TokenType.QUALIFY, - "RANGE": TokenType.RANGE, - "RECURSIVE": TokenType.RECURSIVE, - "REGEXP": TokenType.RLIKE, - "RENAME": TokenType.RENAME, - "REPLACE": TokenType.REPLACE, - "RETURNING": TokenType.RETURNING, - "REFERENCES": TokenType.REFERENCES, - "RIGHT": TokenType.RIGHT, - "RLIKE": TokenType.RLIKE, - "ROLLBACK": TokenType.ROLLBACK, - "ROLLUP": TokenType.ROLLUP, - "ROW": TokenType.ROW, - "ROWS": TokenType.ROWS, - "SCHEMA": TokenType.SCHEMA, - "SELECT": TokenType.SELECT, - "SEMI": TokenType.SEMI, - "SESSION": TokenType.SESSION, - "SESSION_USER": TokenType.SESSION_USER, - "SET": TokenType.SET, - "SETTINGS": TokenType.SETTINGS, - "SHOW": TokenType.SHOW, - "SIMILAR TO": TokenType.SIMILAR_TO, - "SOME": TokenType.SOME, - "SORT BY": TokenType.SORT_BY, - "START WITH": TokenType.START_WITH, - "STRAIGHT_JOIN": TokenType.STRAIGHT_JOIN, - "TABLE": TokenType.TABLE, - "TABLESAMPLE": TokenType.TABLE_SAMPLE, - "TEMP": TokenType.TEMPORARY, - "TEMPORARY": TokenType.TEMPORARY, - "THEN": TokenType.THEN, - "TRUE": TokenType.TRUE, - "TRUNCATE": TokenType.TRUNCATE, - "UNION": TokenType.UNION, - "UNKNOWN": TokenType.UNKNOWN, - "UNNEST": TokenType.UNNEST, - "UNPIVOT": TokenType.UNPIVOT, - "UPDATE": TokenType.UPDATE, - "USE": TokenType.USE, - "USING": TokenType.USING, - "UUID": TokenType.UUID, - "VALUES": TokenType.VALUES, - "VIEW": TokenType.VIEW, - "VOLATILE": TokenType.VOLATILE, - "WHEN": TokenType.WHEN, - "WHERE": TokenType.WHERE, - "WINDOW": TokenType.WINDOW, - "WITH": TokenType.WITH, - "APPLY": TokenType.APPLY, - "ARRAY": TokenType.ARRAY, - "BIT": TokenType.BIT, - "BOOL": TokenType.BOOLEAN, - "BOOLEAN": TokenType.BOOLEAN, - "BYTE": TokenType.TINYINT, - "MEDIUMINT": TokenType.MEDIUMINT, - "INT1": TokenType.TINYINT, - "TINYINT": TokenType.TINYINT, - "INT16": TokenType.SMALLINT, - "SHORT": TokenType.SMALLINT, - "SMALLINT": TokenType.SMALLINT, - "HUGEINT": TokenType.INT128, - "UHUGEINT": TokenType.UINT128, - "INT2": TokenType.SMALLINT, - "INTEGER": TokenType.INT, - "INT": TokenType.INT, - "INT4": TokenType.INT, - "INT32": TokenType.INT, - "INT64": TokenType.BIGINT, - "INT128": TokenType.INT128, - "INT256": TokenType.INT256, - "LONG": TokenType.BIGINT, - "BIGINT": TokenType.BIGINT, - "INT8": TokenType.TINYINT, - "UINT": TokenType.UINT, - "UINT128": TokenType.UINT128, - "UINT256": TokenType.UINT256, - "DEC": TokenType.DECIMAL, - "DECIMAL": TokenType.DECIMAL, - "DECIMAL32": TokenType.DECIMAL32, - "DECIMAL64": TokenType.DECIMAL64, - "DECIMAL128": TokenType.DECIMAL128, - "DECIMAL256": TokenType.DECIMAL256, - "DECFLOAT": TokenType.DECFLOAT, - "BIGDECIMAL": TokenType.BIGDECIMAL, - "BIGNUMERIC": TokenType.BIGDECIMAL, - "BIGNUM": TokenType.BIGNUM, - "LIST": TokenType.LIST, - "MAP": TokenType.MAP, - "NULLABLE": TokenType.NULLABLE, - "NUMBER": TokenType.DECIMAL, - "NUMERIC": TokenType.DECIMAL, - "FIXED": TokenType.DECIMAL, - "REAL": TokenType.FLOAT, - "FLOAT": TokenType.FLOAT, - "FLOAT4": TokenType.FLOAT, - "FLOAT8": TokenType.DOUBLE, - "DOUBLE": TokenType.DOUBLE, - "DOUBLE PRECISION": TokenType.DOUBLE, - "JSON": TokenType.JSON, - "JSONB": TokenType.JSONB, - "CHAR": TokenType.CHAR, - "CHARACTER": TokenType.CHAR, - "CHAR VARYING": TokenType.VARCHAR, - "CHARACTER VARYING": TokenType.VARCHAR, - "NCHAR": TokenType.NCHAR, - "VARCHAR": TokenType.VARCHAR, - "VARCHAR2": TokenType.VARCHAR, - "NVARCHAR": TokenType.NVARCHAR, - "NVARCHAR2": TokenType.NVARCHAR, - "BPCHAR": TokenType.BPCHAR, - "STR": TokenType.TEXT, - "STRING": TokenType.TEXT, - "TEXT": TokenType.TEXT, - "LONGTEXT": TokenType.LONGTEXT, - "MEDIUMTEXT": TokenType.MEDIUMTEXT, - "TINYTEXT": TokenType.TINYTEXT, - "CLOB": TokenType.TEXT, - "LONGVARCHAR": TokenType.TEXT, - "BINARY": TokenType.BINARY, - "BLOB": TokenType.VARBINARY, - "LONGBLOB": TokenType.LONGBLOB, - "MEDIUMBLOB": TokenType.MEDIUMBLOB, - "TINYBLOB": TokenType.TINYBLOB, - "BYTEA": TokenType.VARBINARY, - "VARBINARY": TokenType.VARBINARY, - "TIME": TokenType.TIME, - "TIMETZ": TokenType.TIMETZ, - "TIME_NS": TokenType.TIME_NS, - "TIMESTAMP": TokenType.TIMESTAMP, - "TIMESTAMPTZ": TokenType.TIMESTAMPTZ, - "TIMESTAMPLTZ": TokenType.TIMESTAMPLTZ, - "TIMESTAMP_LTZ": TokenType.TIMESTAMPLTZ, - "TIMESTAMPNTZ": TokenType.TIMESTAMPNTZ, - "TIMESTAMP_NTZ": TokenType.TIMESTAMPNTZ, - "DATE": TokenType.DATE, - "DATETIME": TokenType.DATETIME, - "INT4RANGE": TokenType.INT4RANGE, - "INT4MULTIRANGE": TokenType.INT4MULTIRANGE, - "INT8RANGE": TokenType.INT8RANGE, - "INT8MULTIRANGE": TokenType.INT8MULTIRANGE, - "NUMRANGE": TokenType.NUMRANGE, - "NUMMULTIRANGE": TokenType.NUMMULTIRANGE, - "TSRANGE": TokenType.TSRANGE, - "TSMULTIRANGE": TokenType.TSMULTIRANGE, - "TSTZRANGE": TokenType.TSTZRANGE, - "TSTZMULTIRANGE": TokenType.TSTZMULTIRANGE, - "DATERANGE": TokenType.DATERANGE, - "DATEMULTIRANGE": TokenType.DATEMULTIRANGE, - "UNIQUE": TokenType.UNIQUE, - "VECTOR": TokenType.VECTOR, - "STRUCT": TokenType.STRUCT, - "SEQUENCE": TokenType.SEQUENCE, - "VARIANT": TokenType.VARIANT, - "ALTER": TokenType.ALTER, - "ANALYZE": TokenType.ANALYZE, - "CALL": TokenType.COMMAND, - "COMMENT": TokenType.COMMENT, - "EXPLAIN": TokenType.COMMAND, - "GRANT": TokenType.GRANT, - "REVOKE": TokenType.REVOKE, - "OPTIMIZE": TokenType.COMMAND, - "PREPARE": TokenType.COMMAND, - "VACUUM": TokenType.COMMAND, - "USER-DEFINED": TokenType.USERDEFINED, - "FOR VERSION": TokenType.VERSION_SNAPSHOT, - "FOR TIMESTAMP": TokenType.TIMESTAMP_SNAPSHOT, - } - - WHITE_SPACE: t.Dict[t.Optional[str], TokenType] = { - " ": TokenType.SPACE, - "\t": TokenType.SPACE, - "\n": TokenType.BREAK, - "\r": TokenType.BREAK, - } - - COMMANDS = { - TokenType.COMMAND, - TokenType.EXECUTE, - TokenType.FETCH, - TokenType.SHOW, - TokenType.RENAME, - } - - COMMAND_PREFIX_TOKENS = {TokenType.SEMICOLON, TokenType.BEGIN} - - # Handle numeric literals like in hive (3L = BIGINT) - NUMERIC_LITERALS: t.Dict[str, str] = {} - - COMMENTS = ["--", ("/*", "*/")] - - __slots__ = ( - "sql", - "size", - "tokens", - "dialect", - "use_rs_tokenizer", - "_start", - "_current", - "_line", - "_col", - "_comments", - "_char", - "_end", - "_peek", - "_prev_token_line", - "_rs_dialect_settings", - ) - - def __init__( - self, - dialect: DialectType = None, - use_rs_tokenizer: t.Optional[bool] = None, - **opts: t.Any, - ) -> None: - from bigframes_vendored.sqlglot.dialects import Dialect - - self.dialect = Dialect.get_or_raise(dialect) - - # initialize `use_rs_tokenizer`, and allow it to be overwritten per Tokenizer instance - self.use_rs_tokenizer = ( - use_rs_tokenizer if use_rs_tokenizer is not None else USE_RS_TOKENIZER - ) - - if self.use_rs_tokenizer: - self._rs_dialect_settings = RsTokenizerDialectSettings( - unescaped_sequences=self.dialect.UNESCAPED_SEQUENCES, - identifiers_can_start_with_digit=self.dialect.IDENTIFIERS_CAN_START_WITH_DIGIT, - numbers_can_be_underscore_separated=self.dialect.NUMBERS_CAN_BE_UNDERSCORE_SEPARATED, - ) - - self.reset() - - def reset(self) -> None: - self.sql = "" - self.size = 0 - self.tokens: t.List[Token] = [] - self._start = 0 - self._current = 0 - self._line = 1 - self._col = 0 - self._comments: t.List[str] = [] - - self._char = "" - self._end = False - self._peek = "" - self._prev_token_line = -1 - - def tokenize(self, sql: str) -> t.List[Token]: - """Returns a list of tokens corresponding to the SQL string `sql`.""" - if self.use_rs_tokenizer: - return self.tokenize_rs(sql) - - self.reset() - self.sql = sql - self.size = len(sql) - - try: - self._scan() - except Exception as e: - start = max(self._current - 50, 0) - end = min(self._current + 50, self.size - 1) - context = self.sql[start:end] - raise TokenError(f"Error tokenizing '{context}'") from e - - return self.tokens - - def _scan(self, until: t.Optional[t.Callable] = None) -> None: - while self.size and not self._end: - current = self._current - - # Skip spaces here rather than iteratively calling advance() for performance reasons - while current < self.size: - char = self.sql[current] - - if char.isspace() and (char == " " or char == "\t"): - current += 1 - else: - break - - offset = current - self._current if current > self._current else 1 - - self._start = current - self._advance(offset) - - if not self._char.isspace(): - if self._char.isdigit(): - self._scan_number() - elif self._char in self._IDENTIFIERS: - self._scan_identifier(self._IDENTIFIERS[self._char]) - else: - self._scan_keywords() - - if until and until(): - break - - if self.tokens and self._comments: - self.tokens[-1].comments.extend(self._comments) - - def _chars(self, size: int) -> str: - if size == 1: - return self._char - - start = self._current - 1 - end = start + size - - return self.sql[start:end] if end <= self.size else "" - - def _advance(self, i: int = 1, alnum: bool = False) -> None: - if self.WHITE_SPACE.get(self._char) is TokenType.BREAK: - # Ensures we don't count an extra line if we get a \r\n line break sequence - if not (self._char == "\r" and self._peek == "\n"): - self._col = i - self._line += 1 - else: - self._col += i - - self._current += i - self._end = self._current >= self.size - self._char = self.sql[self._current - 1] - self._peek = "" if self._end else self.sql[self._current] - - if alnum and self._char.isalnum(): - # Here we use local variables instead of attributes for better performance - _col = self._col - _current = self._current - _end = self._end - _peek = self._peek - - while _peek.isalnum(): - _col += 1 - _current += 1 - _end = _current >= self.size - _peek = "" if _end else self.sql[_current] - - self._col = _col - self._current = _current - self._end = _end - self._peek = _peek - self._char = self.sql[_current - 1] - - @property - def _text(self) -> str: - return self.sql[self._start : self._current] - - def _add(self, token_type: TokenType, text: t.Optional[str] = None) -> None: - self._prev_token_line = self._line - - if self._comments and token_type == TokenType.SEMICOLON and self.tokens: - self.tokens[-1].comments.extend(self._comments) - self._comments = [] - - self.tokens.append( - Token( - token_type, - text=self._text if text is None else text, - line=self._line, - col=self._col, - start=self._start, - end=self._current - 1, - comments=self._comments, - ) - ) - self._comments = [] - - # If we have either a semicolon or a begin token before the command's token, we'll parse - # whatever follows the command's token as a string - if ( - token_type in self.COMMANDS - and self._peek != ";" - and ( - len(self.tokens) == 1 - or self.tokens[-2].token_type in self.COMMAND_PREFIX_TOKENS - ) - ): - start = self._current - tokens = len(self.tokens) - self._scan(lambda: self._peek == ";") - self.tokens = self.tokens[:tokens] - text = self.sql[start : self._current].strip() - if text: - self._add(TokenType.STRING, text) - - def _scan_keywords(self) -> None: - size = 0 - word = None - chars = self._text - char = chars - prev_space = False - skip = False - trie = self._KEYWORD_TRIE - single_token = char in self.SINGLE_TOKENS - - while chars: - if skip: - result = TrieResult.PREFIX - else: - result, trie = in_trie(trie, char.upper()) - - if result == TrieResult.FAILED: - break - if result == TrieResult.EXISTS: - word = chars - - end = self._current + size - size += 1 - - if end < self.size: - char = self.sql[end] - single_token = single_token or char in self.SINGLE_TOKENS - is_space = char.isspace() - - if not is_space or not prev_space: - if is_space: - char = " " - chars += char - prev_space = is_space - skip = False - else: - skip = True - else: - char = "" - break - - if word: - if self._scan_string(word): - return - if self._scan_comment(word): - return - if prev_space or single_token or not char: - self._advance(size - 1) - word = word.upper() - self._add(self.KEYWORDS[word], text=word) - return - - if self._char in self.SINGLE_TOKENS: - self._add(self.SINGLE_TOKENS[self._char], text=self._char) - return - - self._scan_var() - - def _scan_comment(self, comment_start: str) -> bool: - if comment_start not in self._COMMENTS: - return False - - comment_start_line = self._line - comment_start_size = len(comment_start) - comment_end = self._COMMENTS[comment_start] - - if comment_end: - # Skip the comment's start delimiter - self._advance(comment_start_size) - - comment_count = 1 - comment_end_size = len(comment_end) - - while not self._end: - if self._chars(comment_end_size) == comment_end: - comment_count -= 1 - if not comment_count: - break - - self._advance(alnum=True) - - # Nested comments are allowed by some dialects, e.g. databricks, duckdb, postgres - if ( - self.NESTED_COMMENTS - and not self._end - and self._chars(comment_end_size) == comment_start - ): - self._advance(comment_start_size) - comment_count += 1 - - self._comments.append( - self._text[comment_start_size : -comment_end_size + 1] - ) - self._advance(comment_end_size - 1) - else: - while ( - not self._end - and self.WHITE_SPACE.get(self._peek) is not TokenType.BREAK - ): - self._advance(alnum=True) - self._comments.append(self._text[comment_start_size:]) - - if ( - comment_start == self.HINT_START - and self.tokens - and self.tokens[-1].token_type in self.TOKENS_PRECEDING_HINT - ): - self._add(TokenType.HINT) - - # Leading comment is attached to the succeeding token, whilst trailing comment to the preceding. - # Multiple consecutive comments are preserved by appending them to the current comments list. - if comment_start_line == self._prev_token_line: - self.tokens[-1].comments.extend(self._comments) - self._comments = [] - self._prev_token_line = self._line - - return True - - def _scan_number(self) -> None: - if self._char == "0": - peek = self._peek.upper() - if peek == "B": - return ( - self._scan_bits() - if self.BIT_STRINGS - else self._add(TokenType.NUMBER) - ) - elif peek == "X": - return ( - self._scan_hex() - if self.HEX_STRINGS - else self._add(TokenType.NUMBER) - ) - - decimal = False - scientific = 0 - - while True: - if self._peek.isdigit(): - self._advance() - elif self._peek == "." and not decimal: - if self.tokens and self.tokens[-1].token_type == TokenType.PARAMETER: - return self._add(TokenType.NUMBER) - decimal = True - self._advance() - elif self._peek in ("-", "+") and scientific == 1: - # Only consume +/- if followed by a digit - if ( - self._current + 1 < self.size - and self.sql[self._current + 1].isdigit() - ): - scientific += 1 - self._advance() - else: - return self._add(TokenType.NUMBER) - elif self._peek.upper() == "E" and not scientific: - scientific += 1 - self._advance() - elif self._peek == "_" and self.dialect.NUMBERS_CAN_BE_UNDERSCORE_SEPARATED: - self._advance() - elif self._peek.isidentifier(): - number_text = self._text - literal = "" - - while self._peek.strip() and self._peek not in self.SINGLE_TOKENS: - literal += self._peek - self._advance() - - token_type = self.KEYWORDS.get( - self.NUMERIC_LITERALS.get(literal.upper(), "") - ) - - if token_type: - self._add(TokenType.NUMBER, number_text) - self._add(TokenType.DCOLON, "::") - return self._add(token_type, literal) - elif self.dialect.IDENTIFIERS_CAN_START_WITH_DIGIT: - return self._add(TokenType.VAR) - - self._advance(-len(literal)) - return self._add(TokenType.NUMBER, number_text) - else: - return self._add(TokenType.NUMBER) - - def _scan_bits(self) -> None: - self._advance() - value = self._extract_value() - try: - # If `value` can't be converted to a binary, fallback to tokenizing it as an identifier - int(value, 2) - self._add(TokenType.BIT_STRING, value[2:]) # Drop the 0b - except ValueError: - self._add(TokenType.IDENTIFIER) - - def _scan_hex(self) -> None: - self._advance() - value = self._extract_value() - try: - # If `value` can't be converted to a hex, fallback to tokenizing it as an identifier - int(value, 16) - self._add(TokenType.HEX_STRING, value[2:]) # Drop the 0x - except ValueError: - self._add(TokenType.IDENTIFIER) - - def _extract_value(self) -> str: - while True: - char = self._peek.strip() - if char and char not in self.SINGLE_TOKENS: - self._advance(alnum=True) - else: - break - - return self._text - - def _scan_string(self, start: str) -> bool: - base = None - token_type = TokenType.STRING - - if start in self._QUOTES: - end = self._QUOTES[start] - elif start in self._FORMAT_STRINGS: - end, token_type = self._FORMAT_STRINGS[start] - - if token_type == TokenType.HEX_STRING: - base = 16 - elif token_type == TokenType.BIT_STRING: - base = 2 - elif token_type == TokenType.HEREDOC_STRING: - self._advance() - - if self._char == end: - tag = "" - else: - tag = self._extract_string( - end, - raw_string=True, - raise_unmatched=not self.HEREDOC_TAG_IS_IDENTIFIER, - ) - - if ( - tag - and self.HEREDOC_TAG_IS_IDENTIFIER - and (self._end or tag.isdigit() or any(c.isspace() for c in tag)) - ): - if not self._end: - self._advance(-1) - - self._advance(-len(tag)) - self._add(self.HEREDOC_STRING_ALTERNATIVE) - return True - - end = f"{start}{tag}{end}" - else: - return False - - self._advance(len(start)) - text = self._extract_string(end, raw_string=token_type == TokenType.RAW_STRING) - - if base and text: - try: - int(text, base) - except Exception: - raise TokenError( - f"Numeric string contains invalid characters from {self._line}:{self._start}" - ) - - self._add(token_type, text) - return True - - def _scan_identifier(self, identifier_end: str) -> None: - self._advance() - text = self._extract_string( - identifier_end, escapes=self._IDENTIFIER_ESCAPES | {identifier_end} - ) - self._add(TokenType.IDENTIFIER, text) - - def _scan_var(self) -> None: - while True: - char = self._peek.strip() - if char and ( - char in self.VAR_SINGLE_TOKENS or char not in self.SINGLE_TOKENS - ): - self._advance(alnum=True) - else: - break - - self._add( - TokenType.VAR - if self.tokens and self.tokens[-1].token_type == TokenType.PARAMETER - else self.KEYWORDS.get(self._text.upper(), TokenType.VAR) - ) - - def _extract_string( - self, - delimiter: str, - escapes: t.Optional[t.Set[str]] = None, - raw_string: bool = False, - raise_unmatched: bool = True, - ) -> str: - text = "" - delim_size = len(delimiter) - escapes = self._STRING_ESCAPES if escapes is None else escapes - - while True: - if ( - not raw_string - and self.dialect.UNESCAPED_SEQUENCES - and self._peek - and self._char in self.STRING_ESCAPES - ): - unescaped_sequence = self.dialect.UNESCAPED_SEQUENCES.get( - self._char + self._peek - ) - if unescaped_sequence: - self._advance(2) - text += unescaped_sequence - continue - - is_valid_custom_escape = ( - self.ESCAPE_FOLLOW_CHARS - and self._char == "\\" - and self._peek not in self.ESCAPE_FOLLOW_CHARS - ) - - if ( - (self.STRING_ESCAPES_ALLOWED_IN_RAW_STRINGS or not raw_string) - and self._char in escapes - and ( - self._peek == delimiter - or self._peek in escapes - or is_valid_custom_escape - ) - and (self._char not in self._QUOTES or self._char == self._peek) - ): - if self._peek == delimiter: - text += self._peek - elif is_valid_custom_escape and self._char != self._peek: - text += self._peek - else: - text += self._char + self._peek - - if self._current + 1 < self.size: - self._advance(2) - else: - raise TokenError( - f"Missing {delimiter} from {self._line}:{self._current}" - ) - else: - if self._chars(delim_size) == delimiter: - if delim_size > 1: - self._advance(delim_size - 1) - break - - if self._end: - if not raise_unmatched: - return text + self._char - - raise TokenError( - f"Missing {delimiter} from {self._line}:{self._start}" - ) - - current = self._current - 1 - self._advance(alnum=True) - text += self.sql[current : self._current - 1] - - return text - - def tokenize_rs(self, sql: str) -> t.List[Token]: - if not self._RS_TOKENIZER: - raise SqlglotError("Rust tokenizer is not available") - - tokens, error_msg = self._RS_TOKENIZER.tokenize(sql, self._rs_dialect_settings) - for token in tokens: - token.token_type = _ALL_TOKEN_TYPES[token.token_type_index] - - # Setting this here so partial token lists can be inspected even if there is a failure - self.tokens = tokens - - if error_msg is not None: - raise TokenError(error_msg) - - return tokens diff --git a/third_party/bigframes_vendored/sqlglot/transforms.py b/third_party/bigframes_vendored/sqlglot/transforms.py deleted file mode 100644 index edb1a21d6cb..00000000000 --- a/third_party/bigframes_vendored/sqlglot/transforms.py +++ /dev/null @@ -1,1127 +0,0 @@ -# Contains code from https://github.com/tobymao/sqlglot/blob/v28.5.0/sqlglot/transforms.py - -from __future__ import annotations - -import typing as t - -from bigframes_vendored.sqlglot import expressions as exp -from bigframes_vendored.sqlglot.errors import UnsupportedError -from bigframes_vendored.sqlglot.helper import find_new_name, name_sequence, seq_get - -if t.TYPE_CHECKING: - from bigframes_vendored.sqlglot._typing import E - from bigframes_vendored.sqlglot.generator import Generator - - -def preprocess( - transforms: t.List[t.Callable[[exp.Expression], exp.Expression]], - generator: t.Optional[t.Callable[[Generator, exp.Expression], str]] = None, -) -> t.Callable[[Generator, exp.Expression], str]: - """ - Creates a new transform by chaining a sequence of transformations and converts the resulting - expression to SQL, using either the "_sql" method corresponding to the resulting expression, - or the appropriate `Generator.TRANSFORMS` function (when applicable -- see below). - - Args: - transforms: sequence of transform functions. These will be called in order. - - Returns: - Function that can be used as a generator transform. - """ - - def _to_sql(self, expression: exp.Expression) -> str: - expression_type = type(expression) - - try: - expression = transforms[0](expression) - for transform in transforms[1:]: - expression = transform(expression) - except UnsupportedError as unsupported_error: - self.unsupported(str(unsupported_error)) - - if generator: - return generator(self, expression) - - _sql_handler = getattr(self, expression.key + "_sql", None) - if _sql_handler: - return _sql_handler(expression) - - transforms_handler = self.TRANSFORMS.get(type(expression)) - if transforms_handler: - if expression_type is type(expression): - if isinstance(expression, exp.Func): - return self.function_fallback_sql(expression) - - # Ensures we don't enter an infinite loop. This can happen when the original expression - # has the same type as the final expression and there's no _sql method available for it, - # because then it'd re-enter _to_sql. - raise ValueError( - f"Expression type {expression.__class__.__name__} requires a _sql method in order to be transformed." - ) - - return transforms_handler(self, expression) - - raise ValueError( - f"Unsupported expression type {expression.__class__.__name__}." - ) - - return _to_sql - - -def unnest_generate_date_array_using_recursive_cte( - expression: exp.Expression, -) -> exp.Expression: - if isinstance(expression, exp.Select): - count = 0 - recursive_ctes = [] - - for unnest in expression.find_all(exp.Unnest): - if ( - not isinstance(unnest.parent, (exp.From, exp.Join)) - or len(unnest.expressions) != 1 - or not isinstance(unnest.expressions[0], exp.GenerateDateArray) - ): - continue - - generate_date_array = unnest.expressions[0] - start = generate_date_array.args.get("start") - end = generate_date_array.args.get("end") - step = generate_date_array.args.get("step") - - if not start or not end or not isinstance(step, exp.Interval): - continue - - alias = unnest.args.get("alias") - column_name = ( - alias.columns[0] if isinstance(alias, exp.TableAlias) else "date_value" - ) - - start = exp.cast(start, "date") - date_add = exp.func( - "date_add", - column_name, - exp.Literal.number(step.name), - step.args.get("unit"), - ) - cast_date_add = exp.cast(date_add, "date") - - cte_name = "_generated_dates" + (f"_{count}" if count else "") - - base_query = exp.select(start.as_(column_name)) - recursive_query = ( - exp.select(cast_date_add) - .from_(cte_name) - .where(cast_date_add <= exp.cast(end, "date")) - ) - cte_query = base_query.union(recursive_query, distinct=False) - - generate_dates_query = exp.select(column_name).from_(cte_name) - unnest.replace(generate_dates_query.subquery(cte_name)) - - recursive_ctes.append( - exp.alias_(exp.CTE(this=cte_query), cte_name, table=[column_name]) - ) - count += 1 - - if recursive_ctes: - with_expression = expression.args.get("with_") or exp.With() - with_expression.set("recursive", True) - with_expression.set( - "expressions", [*recursive_ctes, *with_expression.expressions] - ) - expression.set("with_", with_expression) - - return expression - - -def unnest_generate_series(expression: exp.Expression) -> exp.Expression: - """Unnests GENERATE_SERIES or SEQUENCE table references.""" - this = expression.this - if isinstance(expression, exp.Table) and isinstance(this, exp.GenerateSeries): - unnest = exp.Unnest(expressions=[this]) - if expression.alias: - return exp.alias_(unnest, alias="_u", table=[expression.alias], copy=False) - - return unnest - - return expression - - -def eliminate_distinct_on(expression: exp.Expression) -> exp.Expression: - """ - Convert SELECT DISTINCT ON statements to a subquery with a window function. - - This is useful for dialects that don't support SELECT DISTINCT ON but support window functions. - - Args: - expression: the expression that will be transformed. - - Returns: - The transformed expression. - """ - if ( - isinstance(expression, exp.Select) - and expression.args.get("distinct") - and isinstance(expression.args["distinct"].args.get("on"), exp.Tuple) - ): - row_number_window_alias = find_new_name(expression.named_selects, "_row_number") - - distinct_cols = expression.args["distinct"].pop().args["on"].expressions - window = exp.Window(this=exp.RowNumber(), partition_by=distinct_cols) - - order = expression.args.get("order") - if order: - window.set("order", order.pop()) - else: - window.set( - "order", exp.Order(expressions=[c.copy() for c in distinct_cols]) - ) - - window = exp.alias_(window, row_number_window_alias) - expression.select(window, copy=False) - - # We add aliases to the projections so that we can safely reference them in the outer query - new_selects = [] - taken_names = {row_number_window_alias} - for select in expression.selects[:-1]: - if select.is_star: - new_selects = [exp.Star()] - break - - if not isinstance(select, exp.Alias): - alias = find_new_name(taken_names, select.output_name or "_col") - quoted = ( - select.this.args.get("quoted") - if isinstance(select, exp.Column) - else None - ) - select = select.replace(exp.alias_(select, alias, quoted=quoted)) - - taken_names.add(select.output_name) - new_selects.append(select.args["alias"]) - - return ( - exp.select(*new_selects, copy=False) - .from_(expression.subquery("_t", copy=False), copy=False) - .where(exp.column(row_number_window_alias).eq(1), copy=False) - ) - - return expression - - -def eliminate_qualify(expression: exp.Expression) -> exp.Expression: - """ - Convert SELECT statements that contain the QUALIFY clause into subqueries, filtered equivalently. - - The idea behind this transformation can be seen in Snowflake's documentation for QUALIFY: - https://docs.snowflake.com/en/sql-reference/constructs/qualify - - Some dialects don't support window functions in the WHERE clause, so we need to include them as - projections in the subquery, in order to refer to them in the outer filter using aliases. Also, - if a column is referenced in the QUALIFY clause but is not selected, we need to include it too, - otherwise we won't be able to refer to it in the outer query's WHERE clause. Finally, if a - newly aliased projection is referenced in the QUALIFY clause, it will be replaced by the - corresponding expression to avoid creating invalid column references. - """ - if isinstance(expression, exp.Select) and expression.args.get("qualify"): - taken = set(expression.named_selects) - for select in expression.selects: - if not select.alias_or_name: - alias = find_new_name(taken, "_c") - select.replace(exp.alias_(select, alias)) - taken.add(alias) - - def _select_alias_or_name(select: exp.Expression) -> str | exp.Column: - alias_or_name = select.alias_or_name - identifier = select.args.get("alias") or select.this - if isinstance(identifier, exp.Identifier): - return exp.column(alias_or_name, quoted=identifier.args.get("quoted")) - return alias_or_name - - outer_selects = exp.select( - *list(map(_select_alias_or_name, expression.selects)) - ) - qualify_filters = expression.args["qualify"].pop().this - expression_by_alias = { - select.alias: select.this - for select in expression.selects - if isinstance(select, exp.Alias) - } - - select_candidates = ( - exp.Window if expression.is_star else (exp.Window, exp.Column) - ) - for select_candidate in list(qualify_filters.find_all(select_candidates)): - if isinstance(select_candidate, exp.Window): - if expression_by_alias: - for column in select_candidate.find_all(exp.Column): - expr = expression_by_alias.get(column.name) - if expr: - column.replace(expr) - - alias = find_new_name(expression.named_selects, "_w") - expression.select(exp.alias_(select_candidate, alias), copy=False) - column = exp.column(alias) - - if isinstance(select_candidate.parent, exp.Qualify): - qualify_filters = column - else: - select_candidate.replace(column) - elif select_candidate.name not in expression.named_selects: - expression.select(select_candidate.copy(), copy=False) - - return outer_selects.from_( - expression.subquery(alias="_t", copy=False), copy=False - ).where(qualify_filters, copy=False) - - return expression - - -def remove_precision_parameterized_types(expression: exp.Expression) -> exp.Expression: - """ - Some dialects only allow the precision for parameterized types to be defined in the DDL and not in - other expressions. This transforms removes the precision from parameterized types in expressions. - """ - for node in expression.find_all(exp.DataType): - node.set( - "expressions", - [e for e in node.expressions if not isinstance(e, exp.DataTypeParam)], - ) - - return expression - - -def unqualify_unnest(expression: exp.Expression) -> exp.Expression: - """Remove references to unnest table aliases, added by the optimizer's qualify_columns step.""" - from bigframes_vendored.sqlglot.optimizer.scope import find_all_in_scope - - if isinstance(expression, exp.Select): - unnest_aliases = { - unnest.alias - for unnest in find_all_in_scope(expression, exp.Unnest) - if isinstance(unnest.parent, (exp.From, exp.Join)) - } - if unnest_aliases: - for column in expression.find_all(exp.Column): - leftmost_part = column.parts[0] - if ( - leftmost_part.arg_key != "this" - and leftmost_part.this in unnest_aliases - ): - leftmost_part.pop() - - return expression - - -def unnest_to_explode( - expression: exp.Expression, - unnest_using_arrays_zip: bool = True, -) -> exp.Expression: - """Convert cross join unnest into lateral view explode.""" - - def _unnest_zip_exprs( - u: exp.Unnest, unnest_exprs: t.List[exp.Expression], has_multi_expr: bool - ) -> t.List[exp.Expression]: - if has_multi_expr: - if not unnest_using_arrays_zip: - raise UnsupportedError( - "Cannot transpile UNNEST with multiple input arrays" - ) - - # Use INLINE(ARRAYS_ZIP(...)) for multiple expressions - zip_exprs: t.List[exp.Expression] = [ - exp.Anonymous(this="ARRAYS_ZIP", expressions=unnest_exprs) - ] - u.set("expressions", zip_exprs) - return zip_exprs - return unnest_exprs - - def _udtf_type(u: exp.Unnest, has_multi_expr: bool) -> t.Type[exp.Func]: - if u.args.get("offset"): - return exp.Posexplode - return exp.Inline if has_multi_expr else exp.Explode - - if isinstance(expression, exp.Select): - from_ = expression.args.get("from_") - - if from_ and isinstance(from_.this, exp.Unnest): - unnest = from_.this - alias = unnest.args.get("alias") - exprs = unnest.expressions - has_multi_expr = len(exprs) > 1 - this, *_ = _unnest_zip_exprs(unnest, exprs, has_multi_expr) - - columns = alias.columns if alias else [] - offset = unnest.args.get("offset") - if offset: - columns.insert( - 0, - offset - if isinstance(offset, exp.Identifier) - else exp.to_identifier("pos"), - ) - - unnest.replace( - exp.Table( - this=_udtf_type(unnest, has_multi_expr)(this=this), - alias=exp.TableAlias(this=alias.this, columns=columns) - if alias - else None, - ) - ) - - joins = expression.args.get("joins") or [] - for join in list(joins): - join_expr = join.this - - is_lateral = isinstance(join_expr, exp.Lateral) - - unnest = join_expr.this if is_lateral else join_expr - - if isinstance(unnest, exp.Unnest): - if is_lateral: - alias = join_expr.args.get("alias") - else: - alias = unnest.args.get("alias") - exprs = unnest.expressions - # The number of unnest.expressions will be changed by _unnest_zip_exprs, we need to record it here - has_multi_expr = len(exprs) > 1 - exprs = _unnest_zip_exprs(unnest, exprs, has_multi_expr) - - joins.remove(join) - - alias_cols = alias.columns if alias else [] - - # # Handle UNNEST to LATERAL VIEW EXPLODE: Exception is raised when there are 0 or > 2 aliases - # Spark LATERAL VIEW EXPLODE requires single alias for array/struct and two for Map type column unlike unnest in trino/presto which can take an arbitrary amount. - # Refs: https://spark.apache.org/docs/latest/sql-ref-syntax-qry-select-lateral-view.html - - if not has_multi_expr and len(alias_cols) not in (1, 2): - raise UnsupportedError( - "CROSS JOIN UNNEST to LATERAL VIEW EXPLODE transformation requires explicit column aliases" - ) - - offset = unnest.args.get("offset") - if offset: - alias_cols.insert( - 0, - offset - if isinstance(offset, exp.Identifier) - else exp.to_identifier("pos"), - ) - - for e, column in zip(exprs, alias_cols): - expression.append( - "laterals", - exp.Lateral( - this=_udtf_type(unnest, has_multi_expr)(this=e), - view=True, - alias=exp.TableAlias( - this=alias.this, # type: ignore - columns=alias_cols, - ), - ), - ) - - return expression - - -def explode_projection_to_unnest( - index_offset: int = 0, -) -> t.Callable[[exp.Expression], exp.Expression]: - """Convert explode/posexplode projections into unnests.""" - - def _explode_projection_to_unnest(expression: exp.Expression) -> exp.Expression: - if isinstance(expression, exp.Select): - from bigframes_vendored.sqlglot.optimizer.scope import Scope - - taken_select_names = set(expression.named_selects) - taken_source_names = {name for name, _ in Scope(expression).references} - - def new_name(names: t.Set[str], name: str) -> str: - name = find_new_name(names, name) - names.add(name) - return name - - arrays: t.List[exp.Condition] = [] - series_alias = new_name(taken_select_names, "pos") - series = exp.alias_( - exp.Unnest( - expressions=[ - exp.GenerateSeries(start=exp.Literal.number(index_offset)) - ] - ), - new_name(taken_source_names, "_u"), - table=[series_alias], - ) - - # we use list here because expression.selects is mutated inside the loop - for select in list(expression.selects): - explode = select.find(exp.Explode) - - if explode: - pos_alias = "" - explode_alias = "" - - if isinstance(select, exp.Alias): - explode_alias = select.args["alias"] - alias = select - elif isinstance(select, exp.Aliases): - pos_alias = select.aliases[0] - explode_alias = select.aliases[1] - alias = select.replace(exp.alias_(select.this, "", copy=False)) - else: - alias = select.replace(exp.alias_(select, "")) - explode = alias.find(exp.Explode) - assert explode - - is_posexplode = isinstance(explode, exp.Posexplode) - explode_arg = explode.this - - if isinstance(explode, exp.ExplodeOuter): - bracket = explode_arg[0] - bracket.set("safe", True) - bracket.set("offset", True) - explode_arg = exp.func( - "IF", - exp.func( - "ARRAY_SIZE", - exp.func("COALESCE", explode_arg, exp.Array()), - ).eq(0), - exp.array(bracket, copy=False), - explode_arg, - ) - - # This ensures that we won't use [POS]EXPLODE's argument as a new selection - if isinstance(explode_arg, exp.Column): - taken_select_names.add(explode_arg.output_name) - - unnest_source_alias = new_name(taken_source_names, "_u") - - if not explode_alias: - explode_alias = new_name(taken_select_names, "col") - - if is_posexplode: - pos_alias = new_name(taken_select_names, "pos") - - if not pos_alias: - pos_alias = new_name(taken_select_names, "pos") - - alias.set("alias", exp.to_identifier(explode_alias)) - - series_table_alias = series.args["alias"].this - column = exp.If( - this=exp.column(series_alias, table=series_table_alias).eq( - exp.column(pos_alias, table=unnest_source_alias) - ), - true=exp.column(explode_alias, table=unnest_source_alias), - ) - - explode.replace(column) - - if is_posexplode: - expressions = expression.expressions - expressions.insert( - expressions.index(alias) + 1, - exp.If( - this=exp.column( - series_alias, table=series_table_alias - ).eq(exp.column(pos_alias, table=unnest_source_alias)), - true=exp.column(pos_alias, table=unnest_source_alias), - ).as_(pos_alias), - ) - expression.set("expressions", expressions) - - if not arrays: - if expression.args.get("from_"): - expression.join(series, copy=False, join_type="CROSS") - else: - expression.from_(series, copy=False) - - size: exp.Condition = exp.ArraySize(this=explode_arg.copy()) - arrays.append(size) - - # trino doesn't support left join unnest with on conditions - # if it did, this would be much simpler - expression.join( - exp.alias_( - exp.Unnest( - expressions=[explode_arg.copy()], - offset=exp.to_identifier(pos_alias), - ), - unnest_source_alias, - table=[explode_alias], - ), - join_type="CROSS", - copy=False, - ) - - if index_offset != 1: - size = size - 1 - - expression.where( - exp.column(series_alias, table=series_table_alias) - .eq(exp.column(pos_alias, table=unnest_source_alias)) - .or_( - ( - exp.column(series_alias, table=series_table_alias) - > size - ).and_( - exp.column(pos_alias, table=unnest_source_alias).eq( - size - ) - ) - ), - copy=False, - ) - - if arrays: - end: exp.Condition = exp.Greatest( - this=arrays[0], expressions=arrays[1:] - ) - - if index_offset != 1: - end = end - (1 - index_offset) - series.expressions[0].set("end", end) - - return expression - - return _explode_projection_to_unnest - - -def add_within_group_for_percentiles(expression: exp.Expression) -> exp.Expression: - """Transforms percentiles by adding a WITHIN GROUP clause to them.""" - if ( - isinstance(expression, exp.PERCENTILES) - and not isinstance(expression.parent, exp.WithinGroup) - and expression.expression - ): - column = expression.this.pop() - expression.set("this", expression.expression.pop()) - order = exp.Order(expressions=[exp.Ordered(this=column)]) - expression = exp.WithinGroup(this=expression, expression=order) - - return expression - - -def remove_within_group_for_percentiles(expression: exp.Expression) -> exp.Expression: - """Transforms percentiles by getting rid of their corresponding WITHIN GROUP clause.""" - if ( - isinstance(expression, exp.WithinGroup) - and isinstance(expression.this, exp.PERCENTILES) - and isinstance(expression.expression, exp.Order) - ): - quantile = expression.this.this - input_value = t.cast(exp.Ordered, expression.find(exp.Ordered)).this - return expression.replace( - exp.ApproxQuantile(this=input_value, quantile=quantile) - ) - - return expression - - -def add_recursive_cte_column_names(expression: exp.Expression) -> exp.Expression: - """Uses projection output names in recursive CTE definitions to define the CTEs' columns.""" - if isinstance(expression, exp.With) and expression.recursive: - next_name = name_sequence("_c_") - - for cte in expression.expressions: - if not cte.args["alias"].columns: - query = cte.this - if isinstance(query, exp.SetOperation): - query = query.this - - cte.args["alias"].set( - "columns", - [ - exp.to_identifier(s.alias_or_name or next_name()) - for s in query.selects - ], - ) - - return expression - - -def epoch_cast_to_ts(expression: exp.Expression) -> exp.Expression: - """Replace 'epoch' in casts by the equivalent date literal.""" - if ( - isinstance(expression, (exp.Cast, exp.TryCast)) - and expression.name.lower() == "epoch" - and expression.to.this in exp.DataType.TEMPORAL_TYPES - ): - expression.this.replace(exp.Literal.string("1970-01-01 00:00:00")) - - return expression - - -def eliminate_semi_and_anti_joins(expression: exp.Expression) -> exp.Expression: - """Convert SEMI and ANTI joins into equivalent forms that use EXIST instead.""" - if isinstance(expression, exp.Select): - for join in expression.args.get("joins") or []: - on = join.args.get("on") - if on and join.kind in ("SEMI", "ANTI"): - subquery = exp.select("1").from_(join.this).where(on) - exists = exp.Exists(this=subquery) - if join.kind == "ANTI": - exists = exists.not_(copy=False) - - join.pop() - expression.where(exists, copy=False) - - return expression - - -def eliminate_full_outer_join(expression: exp.Expression) -> exp.Expression: - """ - Converts a query with a FULL OUTER join to a union of identical queries that - use LEFT/RIGHT OUTER joins instead. This transformation currently only works - for queries that have a single FULL OUTER join. - """ - if isinstance(expression, exp.Select): - full_outer_joins = [ - (index, join) - for index, join in enumerate(expression.args.get("joins") or []) - if join.side == "FULL" - ] - - if len(full_outer_joins) == 1: - expression_copy = expression.copy() - expression.set("limit", None) - index, full_outer_join = full_outer_joins[0] - - tables = ( - expression.args["from_"].alias_or_name, - full_outer_join.alias_or_name, - ) - join_conditions = full_outer_join.args.get("on") or exp.and_( - *[ - exp.column(col, tables[0]).eq(exp.column(col, tables[1])) - for col in full_outer_join.args.get("using") - ] - ) - - full_outer_join.set("side", "left") - anti_join_clause = ( - exp.select("1").from_(expression.args["from_"]).where(join_conditions) - ) - expression_copy.args["joins"][index].set("side", "right") - expression_copy = expression_copy.where( - exp.Exists(this=anti_join_clause).not_() - ) - expression_copy.set("with_", None) # remove CTEs from RIGHT side - expression.set("order", None) # remove order by from LEFT side - - return exp.union(expression, expression_copy, copy=False, distinct=False) - - return expression - - -def move_ctes_to_top_level(expression: E) -> E: - """ - Some dialects (e.g. Hive, T-SQL, Spark prior to version 3) only allow CTEs to be - defined at the top-level, so for example queries like: - - SELECT * FROM (WITH t(c) AS (SELECT 1) SELECT * FROM t) AS subq - - are invalid in those dialects. This transformation can be used to ensure all CTEs are - moved to the top level so that the final SQL code is valid from a syntax standpoint. - - TODO: handle name clashes whilst moving CTEs (it can get quite tricky & costly). - """ - top_level_with = expression.args.get("with_") - for inner_with in expression.find_all(exp.With): - if inner_with.parent is expression: - continue - - if not top_level_with: - top_level_with = inner_with.pop() - expression.set("with_", top_level_with) - else: - if inner_with.recursive: - top_level_with.set("recursive", True) - - parent_cte = inner_with.find_ancestor(exp.CTE) - inner_with.pop() - - if parent_cte: - i = top_level_with.expressions.index(parent_cte) - top_level_with.expressions[i:i] = inner_with.expressions - top_level_with.set("expressions", top_level_with.expressions) - else: - top_level_with.set( - "expressions", top_level_with.expressions + inner_with.expressions - ) - - return expression - - -def ensure_bools(expression: exp.Expression) -> exp.Expression: - """Converts numeric values used in conditions into explicit boolean expressions.""" - from bigframes_vendored.sqlglot.optimizer.canonicalize import ensure_bools - - def _ensure_bool(node: exp.Expression) -> None: - if ( - node.is_number - or ( - not isinstance(node, exp.SubqueryPredicate) - and node.is_type(exp.DataType.Type.UNKNOWN, *exp.DataType.NUMERIC_TYPES) - ) - or (isinstance(node, exp.Column) and not node.type) - ): - node.replace(node.neq(0)) - - for node in expression.walk(): - ensure_bools(node, _ensure_bool) - - return expression - - -def unqualify_columns(expression: exp.Expression) -> exp.Expression: - for column in expression.find_all(exp.Column): - # We only wanna pop off the table, db, catalog args - for part in column.parts[:-1]: - part.pop() - - return expression - - -def remove_unique_constraints(expression: exp.Expression) -> exp.Expression: - assert isinstance(expression, exp.Create) - for constraint in expression.find_all(exp.UniqueColumnConstraint): - if constraint.parent: - constraint.parent.pop() - - return expression - - -def ctas_with_tmp_tables_to_create_tmp_view( - expression: exp.Expression, - tmp_storage_provider: t.Callable[[exp.Expression], exp.Expression] = lambda e: e, -) -> exp.Expression: - assert isinstance(expression, exp.Create) - properties = expression.args.get("properties") - temporary = any( - isinstance(prop, exp.TemporaryProperty) - for prop in (properties.expressions if properties else []) - ) - - # CTAS with temp tables map to CREATE TEMPORARY VIEW - if expression.kind == "TABLE" and temporary: - if expression.expression: - return exp.Create( - kind="TEMPORARY VIEW", - this=expression.this, - expression=expression.expression, - ) - return tmp_storage_provider(expression) - - return expression - - -def move_schema_columns_to_partitioned_by(expression: exp.Expression) -> exp.Expression: - """ - In Hive, the PARTITIONED BY property acts as an extension of a table's schema. When the - PARTITIONED BY value is an array of column names, they are transformed into a schema. - The corresponding columns are removed from the create statement. - """ - assert isinstance(expression, exp.Create) - has_schema = isinstance(expression.this, exp.Schema) - is_partitionable = expression.kind in {"TABLE", "VIEW"} - - if has_schema and is_partitionable: - prop = expression.find(exp.PartitionedByProperty) - if prop and prop.this and not isinstance(prop.this, exp.Schema): - schema = expression.this - columns = {v.name.upper() for v in prop.this.expressions} - partitions = [ - col for col in schema.expressions if col.name.upper() in columns - ] - schema.set( - "expressions", [e for e in schema.expressions if e not in partitions] - ) - prop.replace( - exp.PartitionedByProperty(this=exp.Schema(expressions=partitions)) - ) - expression.set("this", schema) - - return expression - - -def move_partitioned_by_to_schema_columns(expression: exp.Expression) -> exp.Expression: - """ - Spark 3 supports both "HIVEFORMAT" and "DATASOURCE" formats for CREATE TABLE. - - Currently, SQLGlot uses the DATASOURCE format for Spark 3. - """ - assert isinstance(expression, exp.Create) - prop = expression.find(exp.PartitionedByProperty) - if ( - prop - and prop.this - and isinstance(prop.this, exp.Schema) - and all(isinstance(e, exp.ColumnDef) and e.kind for e in prop.this.expressions) - ): - prop_this = exp.Tuple( - expressions=[exp.to_identifier(e.this) for e in prop.this.expressions] - ) - schema = expression.this - for e in prop.this.expressions: - schema.append("expressions", e) - prop.set("this", prop_this) - - return expression - - -def struct_kv_to_alias(expression: exp.Expression) -> exp.Expression: - """Converts struct arguments to aliases, e.g. STRUCT(1 AS y).""" - if isinstance(expression, exp.Struct): - expression.set( - "expressions", - [ - exp.alias_(e.expression, e.this) if isinstance(e, exp.PropertyEQ) else e - for e in expression.expressions - ], - ) - - return expression - - -def eliminate_join_marks(expression: exp.Expression) -> exp.Expression: - """https://docs.oracle.com/cd/B19306_01/server.102/b14200/queries006.htm#sthref3178 - - 1. You cannot specify the (+) operator in a query block that also contains FROM clause join syntax. - - 2. The (+) operator can appear only in the WHERE clause or, in the context of left-correlation (that is, when specifying the TABLE clause) in the FROM clause, and can be applied only to a column of a table or view. - - The (+) operator does not produce an outer join if you specify one table in the outer query and the other table in an inner query. - - You cannot use the (+) operator to outer-join a table to itself, although self joins are valid. - - The (+) operator can be applied only to a column, not to an arbitrary expression. However, an arbitrary expression can contain one or more columns marked with the (+) operator. - - A WHERE condition containing the (+) operator cannot be combined with another condition using the OR logical operator. - - A WHERE condition cannot use the IN comparison condition to compare a column marked with the (+) operator with an expression. - - A WHERE condition cannot compare any column marked with the (+) operator with a subquery. - - -- example with WHERE - SELECT d.department_name, sum(e.salary) as total_salary - FROM departments d, employees e - WHERE e.department_id(+) = d.department_id - group by department_name - - -- example of left correlation in select - SELECT d.department_name, ( - SELECT SUM(e.salary) - FROM employees e - WHERE e.department_id(+) = d.department_id) AS total_salary - FROM departments d; - - -- example of left correlation in from - SELECT d.department_name, t.total_salary - FROM departments d, ( - SELECT SUM(e.salary) AS total_salary - FROM employees e - WHERE e.department_id(+) = d.department_id - ) t - """ - - from collections import defaultdict - - from bigframes_vendored.sqlglot.optimizer.normalize import normalize, normalized - from bigframes_vendored.sqlglot.optimizer.scope import traverse_scope - - # we go in reverse to check the main query for left correlation - for scope in reversed(traverse_scope(expression)): - query = scope.expression - - where = query.args.get("where") - joins = query.args.get("joins", []) - - if not where or not any( - c.args.get("join_mark") for c in where.find_all(exp.Column) - ): - continue - - # knockout: we do not support left correlation (see point 2) - assert not scope.is_correlated_subquery, "Correlated queries are not supported" - - # make sure we have AND of ORs to have clear join terms - where = normalize(where.this) - assert normalized(where), "Cannot normalize JOIN predicates" - - joins_ons = defaultdict(list) # dict of {name: list of join AND conditions} - for cond in [where] if not isinstance(where, exp.And) else where.flatten(): - join_cols = [ - col for col in cond.find_all(exp.Column) if col.args.get("join_mark") - ] - - left_join_table = set(col.table for col in join_cols) - if not left_join_table: - continue - - assert not (len(left_join_table) > 1), ( - "Cannot combine JOIN predicates from different tables" - ) - - for col in join_cols: - col.set("join_mark", False) - - joins_ons[left_join_table.pop()].append(cond) - - old_joins = {join.alias_or_name: join for join in joins} - new_joins = {} - query_from = query.args["from_"] - - for table, predicates in joins_ons.items(): - join_what = old_joins.get(table, query_from).this.copy() - new_joins[join_what.alias_or_name] = exp.Join( - this=join_what, on=exp.and_(*predicates), kind="LEFT" - ) - - for p in predicates: - while isinstance(p.parent, exp.Paren): - p.parent.replace(p) - - parent = p.parent - p.pop() - if isinstance(parent, exp.Binary): - parent.replace(parent.right if parent.left is None else parent.left) - elif isinstance(parent, exp.Where): - parent.pop() - - if query_from.alias_or_name in new_joins: - only_old_joins = old_joins.keys() - new_joins.keys() - assert len(only_old_joins) >= 1, ( - "Cannot determine which table to use in the new FROM clause" - ) - - new_from_name = list(only_old_joins)[0] - query.set("from_", exp.From(this=old_joins[new_from_name].this)) - - if new_joins: - for n, j in old_joins.items(): # preserve any other joins - if n not in new_joins and n != query.args["from_"].name: - if not j.kind: - j.set("kind", "CROSS") - new_joins[n] = j - query.set("joins", list(new_joins.values())) - - return expression - - -def any_to_exists(expression: exp.Expression) -> exp.Expression: - """ - Transform ANY operator to Spark's EXISTS - - For example, - - Postgres: SELECT * FROM tbl WHERE 5 > ANY(tbl.col) - - Spark: SELECT * FROM tbl WHERE EXISTS(tbl.col, x -> x < 5) - - Both ANY and EXISTS accept queries but currently only array expressions are supported for this - transformation - """ - if isinstance(expression, exp.Select): - for any_expr in expression.find_all(exp.Any): - this = any_expr.this - if isinstance(this, exp.Query) or isinstance( - any_expr.parent, (exp.Like, exp.ILike) - ): - continue - - binop = any_expr.parent - if isinstance(binop, exp.Binary): - lambda_arg = exp.to_identifier("x") - any_expr.replace(lambda_arg) - lambda_expr = exp.Lambda(this=binop.copy(), expressions=[lambda_arg]) - binop.replace(exp.Exists(this=this.unnest(), expression=lambda_expr)) - - return expression - - -def eliminate_window_clause(expression: exp.Expression) -> exp.Expression: - """Eliminates the `WINDOW` query clause by inling each named window.""" - if isinstance(expression, exp.Select) and expression.args.get("windows"): - from bigframes_vendored.sqlglot.optimizer.scope import find_all_in_scope - - windows = expression.args["windows"] - expression.set("windows", None) - - window_expression: t.Dict[str, exp.Expression] = {} - - def _inline_inherited_window(window: exp.Expression) -> None: - inherited_window = window_expression.get(window.alias.lower()) - if not inherited_window: - return - - window.set("alias", None) - for key in ("partition_by", "order", "spec"): - arg = inherited_window.args.get(key) - if arg: - window.set(key, arg.copy()) - - for window in windows: - _inline_inherited_window(window) - window_expression[window.name.lower()] = window - - for window in find_all_in_scope(expression, exp.Window): - _inline_inherited_window(window) - - return expression - - -def inherit_struct_field_names(expression: exp.Expression) -> exp.Expression: - """ - Inherit field names from the first struct in an array. - - BigQuery supports implicitly inheriting names from the first STRUCT in an array: - - Example: - ARRAY[ - STRUCT('Alice' AS name, 85 AS score), -- defines names - STRUCT('Bob', 92), -- inherits names - STRUCT('Diana', 95) -- inherits names - ] - - This transformation makes the field names explicit on all structs by adding - PropertyEQ nodes, in order to facilitate transpilation to other dialects. - - Args: - expression: The expression tree to transform - - Returns: - The modified expression with field names inherited in all structs - """ - if ( - isinstance(expression, exp.Array) - and expression.args.get("struct_name_inheritance") - and isinstance(first_item := seq_get(expression.expressions, 0), exp.Struct) - and all(isinstance(fld, exp.PropertyEQ) for fld in first_item.expressions) - ): - field_names = [fld.this for fld in first_item.expressions] - - # Apply field names to subsequent structs that don't have them - for struct in expression.expressions[1:]: - if not isinstance(struct, exp.Struct) or len(struct.expressions) != len( - field_names - ): - continue - - # Convert unnamed expressions to PropertyEQ with inherited names - new_expressions = [] - for i, expr in enumerate(struct.expressions): - if not isinstance(expr, exp.PropertyEQ): - # Create PropertyEQ: field_name := value - new_expressions.append( - exp.PropertyEQ( - this=exp.Identifier(this=field_names[i].copy()), - expression=expr, - ) - ) - else: - new_expressions.append(expr) - - struct.set("expressions", new_expressions) - - return expression diff --git a/third_party/bigframes_vendored/sqlglot/trie.py b/third_party/bigframes_vendored/sqlglot/trie.py deleted file mode 100644 index 1475ea58774..00000000000 --- a/third_party/bigframes_vendored/sqlglot/trie.py +++ /dev/null @@ -1,83 +0,0 @@ -# Contains code from https://github.com/tobymao/sqlglot/blob/v28.5.0/sqlglot/trie.py - -import typing as t -from enum import Enum, auto - -key = t.Sequence[t.Hashable] - - -class TrieResult(Enum): - FAILED = auto() - PREFIX = auto() - EXISTS = auto() - - -def new_trie(keywords: t.Iterable[key], trie: t.Optional[t.Dict] = None) -> t.Dict: - """ - Creates a new trie out of a collection of keywords. - - The trie is represented as a sequence of nested dictionaries keyed by either single - character strings, or by 0, which is used to designate that a keyword is in the trie. - - Example: - >>> new_trie(["bla", "foo", "blab"]) - {'b': {'l': {'a': {0: True, 'b': {0: True}}}}, 'f': {'o': {'o': {0: True}}}} - - Args: - keywords: the keywords to create the trie from. - trie: a trie to mutate instead of creating a new one - - Returns: - The trie corresponding to `keywords`. - """ - trie = {} if trie is None else trie - - for key in keywords: - current = trie - for char in key: - current = current.setdefault(char, {}) - - current[0] = True - - return trie - - -def in_trie(trie: t.Dict, key: key) -> t.Tuple[TrieResult, t.Dict]: - """ - Checks whether a key is in a trie. - - Examples: - >>> in_trie(new_trie(["cat"]), "bob") - (, {'c': {'a': {'t': {0: True}}}}) - - >>> in_trie(new_trie(["cat"]), "ca") - (, {'t': {0: True}}) - - >>> in_trie(new_trie(["cat"]), "cat") - (, {0: True}) - - Args: - trie: The trie to be searched. - key: The target key. - - Returns: - A pair `(value, subtrie)`, where `subtrie` is the sub-trie we get at the point - where the search stops, and `value` is a TrieResult value that can be one of: - - - TrieResult.FAILED: the search was unsuccessful - - TrieResult.PREFIX: `value` is a prefix of a keyword in `trie` - - TrieResult.EXISTS: `key` exists in `trie` - """ - if not key: - return (TrieResult.FAILED, trie) - - current = trie - for char in key: - if char not in current: - return (TrieResult.FAILED, current) - current = current[char] - - if 0 in current: - return (TrieResult.EXISTS, current) - - return (TrieResult.PREFIX, current) diff --git a/third_party/bigframes_vendored/sqlglot/typing/__init__.py b/third_party/bigframes_vendored/sqlglot/typing/__init__.py deleted file mode 100644 index 0e666836196..00000000000 --- a/third_party/bigframes_vendored/sqlglot/typing/__init__.py +++ /dev/null @@ -1,360 +0,0 @@ -# Contains code from https://github.com/tobymao/sqlglot/blob/v28.5.0/sqlglot/typing/__init__.py - -import typing as t - -from bigframes_vendored.sqlglot import exp -from bigframes_vendored.sqlglot.helper import subclasses - -ExpressionMetadataType = t.Dict[type[exp.Expression], t.Dict[str, t.Any]] - -TIMESTAMP_EXPRESSIONS = { - exp.CurrentTimestamp, - exp.StrToTime, - exp.TimeStrToTime, - exp.TimestampAdd, - exp.TimestampSub, - exp.UnixToTime, -} - -EXPRESSION_METADATA: ExpressionMetadataType = { - **{ - expr_type: {"annotator": lambda self, e: self._annotate_binary(e)} - for expr_type in subclasses(exp.__name__, exp.Binary) - }, - **{ - expr_type: {"annotator": lambda self, e: self._annotate_unary(e)} - for expr_type in subclasses(exp.__name__, (exp.Unary, exp.Alias)) - }, - **{ - expr_type: {"returns": exp.DataType.Type.BIGINT} - for expr_type in { - exp.ApproxDistinct, - exp.ArraySize, - exp.CountIf, - exp.Int64, - exp.Length, - exp.UnixDate, - exp.UnixSeconds, - exp.UnixMicros, - exp.UnixMillis, - } - }, - **{ - expr_type: {"returns": exp.DataType.Type.BINARY} - for expr_type in { - exp.FromBase32, - exp.FromBase64, - } - }, - **{ - expr_type: {"returns": exp.DataType.Type.BOOLEAN} - for expr_type in { - exp.All, - exp.Any, - exp.Between, - exp.Boolean, - exp.Contains, - exp.EndsWith, - exp.Exists, - exp.In, - exp.LogicalAnd, - exp.LogicalOr, - exp.RegexpLike, - exp.StartsWith, - } - }, - **{ - expr_type: {"returns": exp.DataType.Type.DATE} - for expr_type in { - exp.CurrentDate, - exp.Date, - exp.DateFromParts, - exp.DateStrToDate, - exp.DiToDate, - exp.LastDay, - exp.StrToDate, - exp.TimeStrToDate, - exp.TsOrDsToDate, - } - }, - **{ - expr_type: {"returns": exp.DataType.Type.DATETIME} - for expr_type in { - exp.CurrentDatetime, - exp.Datetime, - exp.DatetimeAdd, - exp.DatetimeSub, - } - }, - **{ - expr_type: {"returns": exp.DataType.Type.DOUBLE} - for expr_type in { - exp.ApproxQuantile, - exp.Avg, - exp.Exp, - exp.Ln, - exp.Log, - exp.Pi, - exp.Pow, - exp.Quantile, - exp.Radians, - exp.Round, - exp.SafeDivide, - exp.Sqrt, - exp.Stddev, - exp.StddevPop, - exp.StddevSamp, - exp.ToDouble, - exp.Variance, - exp.VariancePop, - exp.Skewness, - } - }, - **{ - expr_type: {"returns": exp.DataType.Type.INT} - for expr_type in { - exp.Ascii, - exp.Ceil, - exp.DatetimeDiff, - exp.TimestampDiff, - exp.TimeDiff, - exp.Unicode, - exp.DateToDi, - exp.Levenshtein, - exp.Sign, - exp.StrPosition, - exp.TsOrDiToDi, - } - }, - **{ - expr_type: {"returns": exp.DataType.Type.INTERVAL} - for expr_type in { - exp.Interval, - exp.JustifyDays, - exp.JustifyHours, - exp.JustifyInterval, - exp.MakeInterval, - } - }, - **{ - expr_type: {"returns": exp.DataType.Type.JSON} - for expr_type in { - exp.ParseJSON, - } - }, - **{ - expr_type: {"returns": exp.DataType.Type.TIME} - for expr_type in { - exp.CurrentTime, - exp.Time, - exp.TimeAdd, - exp.TimeSub, - } - }, - **{ - expr_type: {"returns": exp.DataType.Type.TIMESTAMPLTZ} - for expr_type in { - exp.TimestampLtzFromParts, - } - }, - **{ - expr_type: {"returns": exp.DataType.Type.TIMESTAMPTZ} - for expr_type in { - exp.CurrentTimestampLTZ, - exp.TimestampTzFromParts, - } - }, - **{ - expr_type: {"returns": exp.DataType.Type.TIMESTAMP} - for expr_type in TIMESTAMP_EXPRESSIONS - }, - **{ - expr_type: {"returns": exp.DataType.Type.TINYINT} - for expr_type in { - exp.Day, - exp.DayOfMonth, - exp.DayOfWeek, - exp.DayOfWeekIso, - exp.DayOfYear, - exp.Month, - exp.Quarter, - exp.Week, - exp.WeekOfYear, - exp.Year, - exp.YearOfWeek, - exp.YearOfWeekIso, - } - }, - **{ - expr_type: {"returns": exp.DataType.Type.VARCHAR} - for expr_type in { - exp.ArrayToString, - exp.Concat, - exp.ConcatWs, - exp.Chr, - exp.DateToDateStr, - exp.DPipe, - exp.GroupConcat, - exp.Initcap, - exp.Lower, - exp.Substring, - exp.String, - exp.TimeToStr, - exp.TimeToTimeStr, - exp.Trim, - exp.ToBase32, - exp.ToBase64, - exp.TsOrDsToDateStr, - exp.UnixToStr, - exp.UnixToTimeStr, - exp.Upper, - } - }, - **{ - expr_type: {"annotator": lambda self, e: self._annotate_by_args(e, "this")} - for expr_type in { - exp.Abs, - exp.AnyValue, - exp.ArrayConcatAgg, - exp.ArrayReverse, - exp.ArraySlice, - exp.Filter, - exp.HavingMax, - exp.LastValue, - exp.Limit, - exp.Order, - exp.SortArray, - exp.Window, - } - }, - **{ - expr_type: { - "annotator": lambda self, e: self._annotate_by_args( - e, "this", "expressions" - ) - } - for expr_type in { - exp.ArrayConcat, - exp.Coalesce, - exp.Greatest, - exp.Least, - exp.Max, - exp.Min, - } - }, - **{ - expr_type: {"annotator": lambda self, e: self._annotate_by_array_element(e)} - for expr_type in { - exp.ArrayFirst, - exp.ArrayLast, - } - }, - **{ - expr_type: {"returns": exp.DataType.Type.UNKNOWN} - for expr_type in { - exp.Anonymous, - exp.Slice, - } - }, - **{ - expr_type: {"annotator": lambda self, e: self._annotate_timeunit(e)} - for expr_type in { - exp.DateAdd, - exp.DateSub, - exp.DateTrunc, - } - }, - **{ - expr_type: {"annotator": lambda self, e: self._set_type(e, e.args["to"])} - for expr_type in { - exp.Cast, - exp.TryCast, - } - }, - **{ - expr_type: {"annotator": lambda self, e: self._annotate_map(e)} - for expr_type in { - exp.Map, - exp.VarMap, - } - }, - exp.Array: { - "annotator": lambda self, e: self._annotate_by_args( - e, "expressions", array=True - ) - }, - exp.ArrayAgg: { - "annotator": lambda self, e: self._annotate_by_args(e, "this", array=True) - }, - exp.Bracket: {"annotator": lambda self, e: self._annotate_bracket(e)}, - exp.Case: { - "annotator": lambda self, e: self._annotate_by_args( - e, *[if_expr.args["true"] for if_expr in e.args["ifs"]], "default" - ) - }, - exp.Count: { - "annotator": lambda self, e: self._set_type( - e, - exp.DataType.Type.BIGINT - if e.args.get("big_int") - else exp.DataType.Type.INT, - ) - }, - exp.DateDiff: { - "annotator": lambda self, e: self._set_type( - e, - exp.DataType.Type.BIGINT - if e.args.get("big_int") - else exp.DataType.Type.INT, - ) - }, - exp.DataType: {"annotator": lambda self, e: self._set_type(e, e.copy())}, - exp.Div: {"annotator": lambda self, e: self._annotate_div(e)}, - exp.Distinct: { - "annotator": lambda self, e: self._annotate_by_args(e, "expressions") - }, - exp.Dot: {"annotator": lambda self, e: self._annotate_dot(e)}, - exp.Explode: {"annotator": lambda self, e: self._annotate_explode(e)}, - exp.Extract: {"annotator": lambda self, e: self._annotate_extract(e)}, - exp.GenerateSeries: { - "annotator": lambda self, e: self._annotate_by_args( - e, "start", "end", "step", array=True - ) - }, - exp.GenerateDateArray: { - "annotator": lambda self, e: self._set_type( - e, exp.DataType.build("ARRAY") - ) - }, - exp.GenerateTimestampArray: { - "annotator": lambda self, e: self._set_type( - e, exp.DataType.build("ARRAY") - ) - }, - exp.If: {"annotator": lambda self, e: self._annotate_by_args(e, "true", "false")}, - exp.Literal: {"annotator": lambda self, e: self._annotate_literal(e)}, - exp.Null: {"returns": exp.DataType.Type.NULL}, - exp.Nullif: { - "annotator": lambda self, e: self._annotate_by_args(e, "this", "expression") - }, - exp.PropertyEQ: { - "annotator": lambda self, e: self._annotate_by_args(e, "expression") - }, - exp.Struct: {"annotator": lambda self, e: self._annotate_struct(e)}, - exp.Sum: { - "annotator": lambda self, e: self._annotate_by_args( - e, "this", "expressions", promote=True - ) - }, - exp.Timestamp: { - "annotator": lambda self, e: self._set_type( - e, - exp.DataType.Type.TIMESTAMPTZ - if e.args.get("with_tz") - else exp.DataType.Type.TIMESTAMP, - ) - }, - exp.ToMap: {"annotator": lambda self, e: self._annotate_to_map(e)}, - exp.Unnest: {"annotator": lambda self, e: self._annotate_unnest(e)}, - exp.Subquery: {"annotator": lambda self, e: self._annotate_subquery(e)}, -} diff --git a/third_party/bigframes_vendored/sqlglot/typing/bigquery.py b/third_party/bigframes_vendored/sqlglot/typing/bigquery.py deleted file mode 100644 index 37304eef36c..00000000000 --- a/third_party/bigframes_vendored/sqlglot/typing/bigquery.py +++ /dev/null @@ -1,402 +0,0 @@ -# Contains code from https://github.com/tobymao/sqlglot/blob/v28.5.0/sqlglot/typing/bigquery.py - -from __future__ import annotations - -import typing as t - -from bigframes_vendored.sqlglot import exp -from bigframes_vendored.sqlglot.typing import EXPRESSION_METADATA, TIMESTAMP_EXPRESSIONS - -if t.TYPE_CHECKING: - from bigframes_vendored.sqlglot.optimizer.annotate_types import TypeAnnotator - - -def _annotate_math_functions( - self: TypeAnnotator, expression: exp.Expression -) -> exp.Expression: - """ - Many BigQuery math functions such as CEIL, FLOOR etc follow this return type convention: - +---------+---------+---------+------------+---------+ - | INPUT | INT64 | NUMERIC | BIGNUMERIC | FLOAT64 | - +---------+---------+---------+------------+---------+ - | OUTPUT | FLOAT64 | NUMERIC | BIGNUMERIC | FLOAT64 | - +---------+---------+---------+------------+---------+ - """ - this: exp.Expression = expression.this - - self._set_type( - expression, - exp.DataType.Type.DOUBLE - if this.is_type(*exp.DataType.INTEGER_TYPES) - else this.type, - ) - return expression - - -def _annotate_safe_divide( - self: TypeAnnotator, expression: exp.SafeDivide -) -> exp.Expression: - """ - +------------+------------+------------+-------------+---------+ - | INPUT | INT64 | NUMERIC | BIGNUMERIC | FLOAT64 | - +------------+------------+------------+-------------+---------+ - | INT64 | FLOAT64 | NUMERIC | BIGNUMERIC | FLOAT64 | - | NUMERIC | NUMERIC | NUMERIC | BIGNUMERIC | FLOAT64 | - | BIGNUMERIC | BIGNUMERIC | BIGNUMERIC | BIGNUMERIC | FLOAT64 | - | FLOAT64 | FLOAT64 | FLOAT64 | FLOAT64 | FLOAT64 | - +------------+------------+------------+-------------+---------+ - """ - if expression.this.is_type( - *exp.DataType.INTEGER_TYPES - ) and expression.expression.is_type(*exp.DataType.INTEGER_TYPES): - return self._set_type(expression, exp.DataType.Type.DOUBLE) - - return _annotate_by_args_with_coerce(self, expression) - - -def _annotate_by_args_with_coerce( - self: TypeAnnotator, expression: exp.Expression -) -> exp.Expression: - """ - +------------+------------+------------+-------------+---------+ - | INPUT | INT64 | NUMERIC | BIGNUMERIC | FLOAT64 | - +------------+------------+------------+-------------+---------+ - | INT64 | INT64 | NUMERIC | BIGNUMERIC | FLOAT64 | - | NUMERIC | NUMERIC | NUMERIC | BIGNUMERIC | FLOAT64 | - | BIGNUMERIC | BIGNUMERIC | BIGNUMERIC | BIGNUMERIC | FLOAT64 | - | FLOAT64 | FLOAT64 | FLOAT64 | FLOAT64 | FLOAT64 | - +------------+------------+------------+-------------+---------+ - """ - self._set_type( - expression, self._maybe_coerce(expression.this.type, expression.expression.type) - ) - return expression - - -def _annotate_by_args_approx_top( - self: TypeAnnotator, expression: exp.ApproxTopK -) -> exp.ApproxTopK: - struct_type = exp.DataType( - this=exp.DataType.Type.STRUCT, - expressions=[expression.this.type, exp.DataType(this=exp.DataType.Type.BIGINT)], - nested=True, - ) - self._set_type( - expression, - exp.DataType( - this=exp.DataType.Type.ARRAY, expressions=[struct_type], nested=True - ), - ) - - return expression - - -def _annotate_concat(self: TypeAnnotator, expression: exp.Concat) -> exp.Concat: - annotated = self._annotate_by_args(expression, "expressions") - - # Args must be BYTES or types that can be cast to STRING, return type is either BYTES or STRING - # https://cloud.google.com/bigquery/docs/reference/standard-sql/string_functions#concat - if not annotated.is_type(exp.DataType.Type.BINARY, exp.DataType.Type.UNKNOWN): - self._set_type(annotated, exp.DataType.Type.VARCHAR) - - return annotated - - -def _annotate_array(self: TypeAnnotator, expression: exp.Array) -> exp.Array: - array_args = expression.expressions - - # BigQuery behaves as follows: - # - # SELECT t, TYPEOF(t) FROM (SELECT 'foo') AS t -- foo, STRUCT - # SELECT ARRAY(SELECT 'foo'), TYPEOF(ARRAY(SELECT 'foo')) -- foo, ARRAY - # ARRAY(SELECT ... UNION ALL SELECT ...) -- ARRAY - if len(array_args) == 1: - unnested = array_args[0].unnest() - projection_type: t.Optional[exp.DataType | exp.DataType.Type] = None - - # Handle ARRAY(SELECT ...) - single SELECT query - if isinstance(unnested, exp.Select): - if ( - (query_type := unnested.meta.get("query_type")) is not None - and query_type.is_type(exp.DataType.Type.STRUCT) - and len(query_type.expressions) == 1 - and isinstance(col_def := query_type.expressions[0], exp.ColumnDef) - and (col_type := col_def.kind) is not None - and not col_type.is_type(exp.DataType.Type.UNKNOWN) - ): - projection_type = col_type - - # Handle ARRAY(SELECT ... UNION ALL SELECT ...) - set operations - elif isinstance(unnested, exp.SetOperation): - # Get all column types for the SetOperation - col_types = self._get_setop_column_types(unnested) - # For ARRAY constructor, there should only be one projection - # https://docs.cloud.google.com/bigquery/docs/reference/standard-sql/array_functions#array - if col_types and unnested.left.selects: - first_col_name = unnested.left.selects[0].alias_or_name - projection_type = col_types.get(first_col_name) - - # If we successfully determine a projection type and it's not UNKNOWN, wrap it in ARRAY - if projection_type and not ( - ( - isinstance(projection_type, exp.DataType) - and projection_type.is_type(exp.DataType.Type.UNKNOWN) - ) - or projection_type == exp.DataType.Type.UNKNOWN - ): - element_type = ( - projection_type.copy() - if isinstance(projection_type, exp.DataType) - else exp.DataType(this=projection_type) - ) - array_type = exp.DataType( - this=exp.DataType.Type.ARRAY, - expressions=[element_type], - nested=True, - ) - return self._set_type(expression, array_type) - - return self._annotate_by_args(expression, "expressions", array=True) - - -EXPRESSION_METADATA = { - **EXPRESSION_METADATA, - **{ - expr_type: {"annotator": lambda self, e: _annotate_math_functions(self, e)} - for expr_type in { - exp.Avg, - exp.Ceil, - exp.Exp, - exp.Floor, - exp.Ln, - exp.Log, - exp.Round, - exp.Sqrt, - } - }, - **{ - expr_type: {"annotator": lambda self, e: self._annotate_by_args(e, "this")} - for expr_type in { - exp.Abs, - exp.ArgMax, - exp.ArgMin, - exp.DateTrunc, - exp.DatetimeTrunc, - exp.FirstValue, - exp.GroupConcat, - exp.IgnoreNulls, - exp.JSONExtract, - exp.Lead, - exp.Left, - exp.Lower, - exp.NthValue, - exp.Pad, - exp.PercentileDisc, - exp.RegexpExtract, - exp.RegexpReplace, - exp.Repeat, - exp.Replace, - exp.RespectNulls, - exp.Reverse, - exp.Right, - exp.SafeNegate, - exp.Sign, - exp.Substring, - exp.TimestampTrunc, - exp.Translate, - exp.Trim, - exp.Upper, - } - }, - **{ - expr_type: {"returns": exp.DataType.Type.BIGINT} - for expr_type in { - exp.Ascii, - exp.BitwiseAndAgg, - exp.BitwiseCount, - exp.BitwiseOrAgg, - exp.BitwiseXorAgg, - exp.ByteLength, - exp.DenseRank, - exp.FarmFingerprint, - exp.Grouping, - exp.LaxInt64, - exp.Length, - exp.Ntile, - exp.Rank, - exp.RangeBucket, - exp.RegexpInstr, - exp.RowNumber, - exp.Unicode, - } - }, - **{ - expr_type: {"returns": exp.DataType.Type.BINARY} - for expr_type in { - exp.ByteString, - exp.CodePointsToBytes, - exp.MD5Digest, - exp.SHA, - exp.SHA2, - exp.SHA1Digest, - exp.SHA2Digest, - exp.Unhex, - } - }, - **{ - expr_type: {"returns": exp.DataType.Type.BOOLEAN} - for expr_type in { - exp.IsInf, - exp.IsNan, - exp.JSONBool, - exp.LaxBool, - } - }, - **{ - expr_type: {"returns": exp.DataType.Type.DATETIME} - for expr_type in { - exp.ParseDatetime, - exp.TimestampFromParts, - } - }, - **{ - expr_type: {"returns": exp.DataType.Type.DOUBLE} - for expr_type in { - exp.Acos, - exp.Acosh, - exp.Asin, - exp.Asinh, - exp.Atan, - exp.Atan2, - exp.Atanh, - exp.Cbrt, - exp.Corr, - exp.CosineDistance, - exp.Cot, - exp.Coth, - exp.CovarPop, - exp.CovarSamp, - exp.Csc, - exp.Csch, - exp.CumeDist, - exp.EuclideanDistance, - exp.Float64, - exp.LaxFloat64, - exp.PercentRank, - exp.Rand, - exp.Sec, - exp.Sech, - exp.Sin, - exp.Sinh, - } - }, - **{ - expr_type: {"returns": exp.DataType.Type.JSON} - for expr_type in { - exp.JSONArray, - exp.JSONArrayAppend, - exp.JSONArrayInsert, - exp.JSONObject, - exp.JSONRemove, - exp.JSONSet, - exp.JSONStripNulls, - } - }, - **{ - expr_type: {"returns": exp.DataType.Type.TIME} - for expr_type in { - exp.ParseTime, - exp.TimeFromParts, - exp.TimeTrunc, - exp.TsOrDsToTime, - } - }, - **{ - expr_type: {"returns": exp.DataType.Type.VARCHAR} - for expr_type in { - exp.CodePointsToString, - exp.Format, - exp.JSONExtractScalar, - exp.JSONType, - exp.LaxString, - exp.LowerHex, - exp.MD5, - exp.NetHost, - exp.Normalize, - exp.SafeConvertBytesToString, - exp.Soundex, - exp.Uuid, - } - }, - **{ - expr_type: {"annotator": lambda self, e: _annotate_by_args_with_coerce(self, e)} - for expr_type in { - exp.PercentileCont, - exp.SafeAdd, - exp.SafeDivide, - exp.SafeMultiply, - exp.SafeSubtract, - } - }, - **{ - expr_type: { - "annotator": lambda self, e: self._annotate_by_args(e, "this", array=True) - } - for expr_type in { - exp.ApproxQuantiles, - exp.JSONExtractArray, - exp.RegexpExtractAll, - exp.Split, - } - }, - **{ - expr_type: {"returns": exp.DataType.Type.TIMESTAMPTZ} - for expr_type in TIMESTAMP_EXPRESSIONS - }, - exp.ApproxTopK: { - "annotator": lambda self, e: _annotate_by_args_approx_top(self, e) - }, - exp.ApproxTopSum: { - "annotator": lambda self, e: _annotate_by_args_approx_top(self, e) - }, - exp.Array: {"annotator": _annotate_array}, - exp.ArrayConcat: { - "annotator": lambda self, e: self._annotate_by_args(e, "this", "expressions") - }, - exp.Concat: {"annotator": _annotate_concat}, - exp.DateFromUnixDate: {"returns": exp.DataType.Type.DATE}, - exp.GenerateTimestampArray: { - "annotator": lambda self, e: self._set_type( - e, exp.DataType.build("ARRAY", dialect="bigquery") - ) - }, - exp.JSONFormat: { - "annotator": lambda self, e: self._set_type( - e, - exp.DataType.Type.JSON - if e.args.get("to_json") - else exp.DataType.Type.VARCHAR, - ) - }, - exp.JSONKeysAtDepth: { - "annotator": lambda self, e: self._set_type( - e, exp.DataType.build("ARRAY", dialect="bigquery") - ) - }, - exp.JSONValueArray: { - "annotator": lambda self, e: self._set_type( - e, exp.DataType.build("ARRAY", dialect="bigquery") - ) - }, - exp.Lag: { - "annotator": lambda self, e: self._annotate_by_args(e, "this", "default") - }, - exp.ParseBignumeric: {"returns": exp.DataType.Type.BIGDECIMAL}, - exp.ParseNumeric: {"returns": exp.DataType.Type.DECIMAL}, - exp.SafeDivide: {"annotator": lambda self, e: _annotate_safe_divide(self, e)}, - exp.ToCodePoints: { - "annotator": lambda self, e: self._set_type( - e, exp.DataType.build("ARRAY", dialect="bigquery") - ) - }, -} diff --git a/third_party/bigframes_vendored/tpch/LICENSE b/third_party/bigframes_vendored/tpch/LICENSE deleted file mode 100644 index 06d01f6abfb..00000000000 --- a/third_party/bigframes_vendored/tpch/LICENSE +++ /dev/null @@ -1,19 +0,0 @@ -Copyright (c) 2020 Ritchie Vink - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/third_party/bigframes_vendored/tpch/METADATA b/third_party/bigframes_vendored/tpch/METADATA deleted file mode 100644 index 65dd8cab935..00000000000 --- a/third_party/bigframes_vendored/tpch/METADATA +++ /dev/null @@ -1,19 +0,0 @@ -name: "polars-tpch" -description: - "This repository contains modified TPC-H benchmark queries that are " - "specifically adapted to evaluate the performance of the BigFrames library. " - "These benchmarks are designed to test complex data processing workflows " - "that are typical in decision support systems." - -third_party { - identifier { - type: "Git" - value: "https://github.com/pola-rs/tpch" - primary_source: true - version: "Latest Commit on Main Branch as of Access" - } - version: "Latest Commit on Main Branch as of Access" - last_upgrade_date { year: 2024 month: 7 day: 12 } - license_type: PERMISSIVE - local_modifications: "Modified the queries to test and benchmark the BigFrames project" -} diff --git a/third_party/bigframes_vendored/tpch/README.md b/third_party/bigframes_vendored/tpch/README.md deleted file mode 100644 index ef0b77d7d3c..00000000000 --- a/third_party/bigframes_vendored/tpch/README.md +++ /dev/null @@ -1,34 +0,0 @@ -polars-tpch -=========== - -This repo contains the code used for performance evaluation of polars. The benchmarks are TPC-standardised queries and data designed to test the performance of "real" workflows. - -From the [TPC website](https://www.tpc.org/tpch/): -> TPC-H is a decision support benchmark. It consists of a suite of business-oriented ad hoc queries and concurrent data modifications. The queries and the data populating the database have been chosen to have broad industry-wide relevance. This benchmark illustrates decision support systems that examine large volumes of data, execute queries with a high degree of complexity, and give answers to critical business questions. - -## Generating TPC-H Data - -### Project setup - -```shell -# clone this repository -git clone https://github.com/pola-rs/tpch.git -cd tpch/tpch-dbgen - -# build tpch-dbgen -make -``` - -### Execute - -```shell -# change directory to the root of the repository -cd ../ -./run.sh -``` - -This will do the following, - -- Create a new virtual environment with all required dependencies. -- Generate data for benchmarks. -- Run the benchmark suite. diff --git a/third_party/bigframes_vendored/tpch/TPC-EULA.txt b/third_party/bigframes_vendored/tpch/TPC-EULA.txt deleted file mode 100644 index feed8c4973b..00000000000 --- a/third_party/bigframes_vendored/tpch/TPC-EULA.txt +++ /dev/null @@ -1,320 +0,0 @@ -END USER LICENSE AGREEMENT -VERSION 2.2 - -READ THE TERMS AND CONDITIONS OF THIS AGREEMENT ("AGREEMENT") CAREFULLY -BEFORE INSTALLING OR USING THE ACCOMPANYING SOFTWARE. BY INSTALLING OR -USING THE SOFTWARE OR RELATED DOCUMENTATION, YOU AGREE TO BE BOUND BY -THE TERMS OF THIS AGREEMENT. IF YOU DO NOT AGREE TO THE TERMS OF THIS -AGREEMENT, DO NOT INSTALL OR USE THE SOFTWARE. IF YOU ARE ACCESSING THE -SOFTWARE ON BEHALF OF YOUR ORGANIZATION, YOU REPRESENT AND WARRANT THAT -YOU HAVE SUFFICIENT AUTHORITY TO BIND YOUR ORGANIZATION TO THIS -AGREEMENT. - -USE AND RE-EXPORT OF THE SOFTWARE IS SUBJECT TO THE UNITED STATES EXPORT -CONTROL ADMINISTRATION REGULATIONS. THE SOFTWARE MAY NOT BE USED BY -UNLICENSED PERSONS OR ENTITIES, AND MAY NOT BE RE- EXPORTED TO ANOTHER -COUNTRY. SEE EXPORT ASSURANCE (CLAUSE 13) OF THIS LICENSE. - -This is a legal agreement between you (or, if you are accessing the -software on behalf of your organization, your organization) ("You" or -"User") and the Transaction Processing Performance Council ("TPC"). This -Agreement states the terms and conditions upon which TPC offers to -license the Software, including, but not limited to, the source code, -scripts, executable programs, drivers, libraries and data files -associated with such programs, and modifications thereof (the -"Software"), and online, electronic or printed documentation -("Documentation," together with the Software, "Materials"). - -LICENSE - -1. Definitions - -"Executive Summary" shall mean a short summary of a TPC Benchmark Result -that shows the configuration, primary metrics, performance data, and -pricing details. The exact requirements for the Executive Summary are -defined in each TPC Benchmark Standard. -"Full Disclosure Report (FDR)" shall mean a document that describes The -TPC Benchmark Result in sufficient detail such that the Result could be -recreated. The exact requirements for the FDR are defined in each TPC -Benchmark Standard. -"TPC Benchmark Result (Result)" shall mean a performance test submitted -to the TPC attested to meet the requirements of a TPC Benchmark Standard -at the time of submission. A Result is documented by an Executive -Summary and, if required, a FDR. -"TPC Benchmark Standard" shall mean a TPC Benchmark Specification and -any associated code or binaries approved by the TPC. The various TPC -Benchmark Standards can be found at -http://www.tpc.org/information/current_specifications.asp. -"TPC Policies" shall mean the guiding principles for how the TPC -conducts its operations and business. The current TPC Policies can be -found at http://www.tpc.org/information/current_specifications.asp. - -2. Ownership. The Materials are licensed, not sold, to You for use only -under the terms of this Agreement. As between You and TPC (and, to the -extent applicable, its licensors), TPC retains all rights, title and -interest to and ownership of the Materials and reserves all rights not -expressly granted to You. - -3. License Grant. Subject to Your compliance in all material respects -with the terms and conditions of this Agreement, TPC grants You a -restricted, non-exclusive, revocable license to install and use the -Materials, but only as expressly permitted herein. You may only use the -Software on computer systems under Your direct control. You may download -multiple copies of the Materials and make verbatim copies of the -original of the Software so long as Your use of such copies complies -with the terms of this Agreement. -a. Use by Individual. If You are accessing the Materials as an -individual, only You (as an individual) may access and use the -Materials. -b. Use by Organization. If You are accessing the Materials on behalf of -Your organization, only You and those within Your organization may use -the Materials. Your organization must identify a contact person to TPC -and conduct communications with TPC through that contact person. - -4. Restrictions. The following restrictions apply to all use of the -Materials by You. -a. General: You may not: -(1) use, copy, print, modify, adapt, create derivative works from, -market, deliver, rent, lease, sublicense, make, have made, assign, -pledge, transfer, sell, offer to sell, import, reproduce, distribute, -publicly perform, publicly display or otherwise grant rights to the -Materials, or any copy thereof, in whole or in part, except as expressly -permitted under this Agreement; or -(2) use the Materials in any way that does not comply with all -applicable laws and regulations. -b. Modification: You may modify the Software. -c. Public Disclosure: You may not publicly disclose any performance -results produced while using the Software except in the following -circumstances: -(1) as part of a TPC Benchmark Result. For purposes of this Agreement, a -"TPC Benchmark Result" is a performance test submitted to the TPC, -documented by a Full Disclosure Report and Executive Summary, claiming -to meet the requirements of an official TPC Benchmark Standard. You -agree that TPC Benchmark Results may only be published in accordance -with the TPC Policies. viewable at http: //www.tpc.org -(2) as part of an academic or research effort that does not imply or -state a marketing position -(3) any other use of the Software, provided that any performance results -must be clearly identified as not being comparable to TPC Benchmark -Results unless specifically authorized by TPC. - -5. License Modification. Requests for modification of this license shall -be addressed to info@tpc.org. You may not remove or modify this license -without permission. - -6. Copyright. The Materials are owned by TPC and/or its licensors, and -are protected by United States copyright laws and international treaty -provisions. You may not remove the copyright notice from the original or -any copy of the Materials, and You must apply the notice if You extract -part of the Materials not bearing a notice. - -7. Use of Name. You acknowledge and agree that TPC owns all trademark -and trade name rights in the names, trademarks and logos used by TPC in -the Materials. User shall preserve any notices regarding such ownership. -User may only use such names, trademarks and logos in accordance with -the usage guidelines specified by the TPC Policies. - -8. Merger or Integration. Any portion of the Materials merged into or -integrated with other software or documentation will continue to be -subject to the terms and conditions of this Agreement. - -9. Limited Grants of Sublicense. You may distribute the Software as -provided or as modified as permitted under clause 4 b. of this -Agreement, provided You comply with all of the terms of this Agreement -and the following conditions: - -a. If You distribute any portion of the Software in its original form -You may do so only under this Agreement by including a complete copy of -this Agreement with Your distribution, and if You distribute the -Software in modified form, You may only do so under a license that at a -minimum provides all of the protections and conditions of use contained -within this Agreement; - -b. You must include on each copy of the Software that You distribute the -following legend in all caps, at the top of the label and license, and -in a font not less than 12 point and no less prominent than any other -printing: "THE TPC SOFTWARE IS AVAILABLE WITHOUT CHARGE FROM TPC."; - -c. You must retain all copyright, patent, trademark, and attribution -notices that are present in the Software; and - -d. You may not charge a fee for the distribution of this Software, -including any modifications permitted under clause 4.b. - -10. Term and Termination. -a. Term. The license granted to You is effective until terminated. -b. Termination. -(1) By You. You may terminate this Agreement at any time by returning -the Materials (including any portions or copies thereof) to TPC or -providing written notice to the TPC that all copies of the Materials -within Your custody or control have been deleted or destroyed. -(2) By TPC. In the event You materially fail to comply with any term or -condition of this Agreement, and You fail to remedy such non-compliance -within 30 days after the receipt of notice to that effect, then TPC -shall have the right to terminate this Agreement immediately upon -written notice at the end of such 30-day period. -c. Effect of Termination. Termination of this Agreement in accordance -with this clause 10 will not terminate the rights of end users -sublicensed by You pursuant to this Agreement. Moreover, upon -termination and at TPC's written request, You agree to either (1) return -the Materials (including any portions or copies thereof) to TPC or (2) -immediately destroy all copies of the Materials within Your custody or -control and inform the TPC of the destruction of the Materials. Upon -termination, TPC may also enforce any rights provided by law. The -provisions of this Agreement that protect the proprietary rights of TPC -and its Licensors will continue in force after termination. - -11. No Warranty; Materials Provided "As Is". TO THE MAXIMUM EXTENT -PERMITTED BY APPLICABLE LAW, THE MATERIALS ARE PROVIDED "AS IS" AND WITH -ALL FAULTS, AND TPC (AND ITS LICENSORS) AND THE AUTHORS AND DEVELOPERS -OF THE MATERIALS HEREBY DISCLAIM ALL WARRANTIES, REPRESENTATIONS AND -CONDITIONS, EITHER EXPRESS, IMPLIED OR STATUTORY, INCLUDING, BUT NOT -LIMITED TO, ANY IMPLIED WARRANTIES, DUTIES OR CONDITIONS RELATING TO -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, ACCURACY OR -COMPLETENESS OF RESPONSES, RESULTS, WORKMANLIKE EFFORT, LACK OF VIRUSES, -LACK OF NEGLIGENCE, TITLE, QUIET ENJOYMENT, QUIET POSSESSION, -CORRESPONDENCE TO DESCRIPTION OR NONINFRINGEMENT. USER RECOGNIZES THAT -THE MATERIALS ARE THE RESULT OF A COOPERATIVE, NON-PROFIT EFFORT AND -THAT TPC DOES NOT CONDUCT A TYPICAL BUSINESS. USER ACCEPTS THE MATERIALS -"AS IS" AND WITHOUT ANY WARRANTY, EXPRESS OR IMPLIED. - -Without limitation, TPC (and its licensors) do not warrant that the -functions contained in the Software or Materials will meet Your -requirements or that the operation of the Software will be -uninterrupted, error-free or free from malicious code. For purposes of -this paragraph, "malicious code" means any program code designed to -contaminate other computer programs or computer data, consume computer -resources, modify, destroy, record, or transmit data, or in some other -fashion usurp the normal operation of the computer, computer system, or -computer network, including viruses, Trojan horses, droppers, worms, -logic bombs, and the like. TPC (and its licensors) shall not be liable -for the accuracy of any information provided by TPC or third-party -technical support personnel, or any damages caused, either directly or -indirectly, by acts taken or omissions made by You as a result of such -technical support. - -You assume full responsibility for the selection of the Materials to -achieve Your intended results, and for the installation, use and results -obtained from the Materials. You also assume the entire risk as it -applies to the quality and performance of the Materials. Should the -Materials prove defective, You (and not TPC) assume the entire liability -of any and all necessary servicing, repair or correction. Some -countries/states do not allow the exclusion of implied warranties, so -the above exclusion may not apply to You. TPC (and its licensors) -further disclaims all warranties of any kind if the Materials were -customized, repackaged or altered in any way by any party other than TPC -(or its licensors). - -12. Disclaimer of Liability. TPC (and its licensors) assumes no -liability with respect to the Materials, including liability for -infringement of intellectual property rights, negligence, or any other -liability. TPC is not aware of any infringement of copyright or patent -that may result from its grant of rights to User of the Materials. If -User receives any notice of infringement, such notice shall be -immediately communicated to TPC who will have sole discretion to take -action to evaluate the claim and, if practicable, modify the Materials -as necessary to avoid infringement. In the event that TPC determines -that the Materials cannot be modified to avoid such infringement (or any -other infringement claim communicated to TPC), TPC may terminate this -Agreement immediately. User shall suspend use of the Materials until -modifications to avoid claims of infringement have been completed. User -waives any claim against TPC in the event of such infringement claims by -others. - -13. Export Assurance. Use and re-export of the Materials and related -technical information is subject to the Export Administration -Regulations (EAR) of the United States Department of Commerce. User -hereby agrees that User (a) assumes responsibility for compliance with -the EAR in its use of the Materials and technical information, and (b) -will not export, re-export, or otherwise disclose directly or -indirectly, the Materials, technical data, or any direct product of the -Materials or technical data in violation of the EAR. - -14. Limitation of Remedies And Damages. IN NO EVENT WILL TPC OR ITS -LICENSORS OR LICENSEE BE LIABLE FOR ANY INDIRECT, INCIDENTAL, SPECIAL OR -CONSEQUENTIAL DAMAGES OR FOR ANY LOST PROFITS, LOST SAVINGS, LOST -REVENUES OR LOST DATA ARISING FROM OR RELATING TO THE MATERIALS OR THIS -AGREEMENT, EVEN IF TPC OR ITS LICENSORS OR LICENSEE HAVE BEEN ADVISED OF -THE POSSIBILITY OF SUCH DAMAGES. IN NO EVENT WILL TPC'S OR ITS -LICENSORS' LIABILITY OR DAMAGES TO YOU OR ANY OTHER PERSON EVER EXCEED -U.S. ONE HUNDRED DOLLARS (US $100), REGARDLESS OF THE FORM OF THE CLAIM. -IN NO EVENT WILL LICENSEE'S LIABILITY OR DAMAGES TO TPC OR ANY OTHER -PERSON EVER EXCEED $1,000,000, REGARDLESS OF THE FORM OF THE CLAIM. Some -countries/states do not allow the limitation or exclusion of liability -for incidental or consequential damages, so the above limitation or -exclusion may not apply to You. - -15. U.S. Government Restricted Rights. All Software and related -documentation are provided with restricted rights. Use, duplication or -disclosure by the U.S. Government is subject to restrictions as set -forth in subdivision (b)(3)(ii) of the Rights in Technical Data and -Computer Software Clause at 252.227-7013. If You are using the Software -outside of the United States, You will comply with the applicable local -laws of Your country, U.S. export control law, and the English version -of this Agreement. - -16. Contractor/Manufacturer. The Contractor/Manufacturer for the -Software is: - -Transaction Processing Performance Council -572B Ruger Street, P.O. Box 29920 -San Francisco, CA 94129 - -17. General. This Agreement is binding on You as well as Your employees, -employers, contractors and agents, and on any successors and assignees. -This Agreement is governed by the laws of the State of California -(except to the extent federal law governs copyrights and trademarks) -without respect to any provisions of California law that would cause -application of the law of another state or country. The parties agree -that the United Nations Convention on Contracts for the International -Sale of Goods will not govern this Agreement. This Agreement is the -entire agreement between us regarding the subject matter hereof and -supersedes any other understandings or agreements with respect to the -Materials or the subject matter hereof. If any provision of this -Agreement is deemed invalid or unenforceable by any court having -jurisdiction, that particular provision will be deemed modified to the -extent necessary to make the provision valid and enforceable, and the -remaining provisions will remain in full force and effect. - -SPECIAL PROVISIONS APPLICABLE TO THE EUROPEAN UNION - -If You acquired the Materials in the European Union (EU), the following -provisions also apply to You. If there is any inconsistency between the -terms of the Software License Agreement set out earlier and the -following provisions, the following provisions shall take precedence. - -1. Distribution. You may sublicense modifications of the Software -covered in this Agreement if they meet the requirements of clause 9 -above. - -2. Limited Warranty. EXCEPT AS STATED EARLIER IN THIS AGREEMENT, AND AS -PROVIDED UNDER THE HEADING "STATUTORY RIGHTS", THE SOFTWARE IS PROVIDED -AS-IS WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, -INCLUDING, BUT NOT LIMITED TO, ANY IMPLIED WARRANTIES, NONINFRINGEMENT, -OR CONDITIONS OF MERCHANTABILITY, QUALITY AND FITNESS FOR A PARTICULAR -PURPOSE. - -3. Limitation of Remedy and Damages. THE LIMITATIONS OF REMEDIES AND -DAMAGES IN THE SOFTWARE LICENSE AGREEMENT SHALL NOT APPLY TO PERSONAL -INJURY (INCLUDING DEATH) TO ANY PERSON CAUSED BY TPC'S NEGLIGENCE AND -ARE SUBJECT TO THE PROVISION SET OUT UNDER THE HEADING "STATUTORY -RIGHTS". - -4. Statutory Rights: Irish law provides that certain conditions and -warranties may be implied in contracts for the sale of goods and in -contracts for the supply of services. Such conditions and warranties are -hereby excluded, to the extent such exclusion, in the context of this -transaction, is lawful under Irish law. Conversely, such conditions and -warranties, insofar as they may not be lawfully excluded, shall apply. -Accordingly nothing in this Agreement shall prejudice any rights that -You may enjoy by virtue of Sections 12, 13, 14 or 15 of the Irish Sale -of Goods Act 1893 (as amended). - -5. General. This Agreement is governed by the laws of the Republic of -Ireland. The local language version of this agreement shall apply to -Materials acquired in the EU. This Agreement is the entire agreement -between us with respect to the subject matter hereof and You agree that -TPC will not have any liability for any untrue statement or -representation made by it, its agents or anyone else (whether innocently -or negligently) upon which You relied upon entering this Agreement, -unless such untrue statement or representation was made fraudulently. diff --git a/third_party/bigframes_vendored/tpch/__init__.py b/third_party/bigframes_vendored/tpch/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/third_party/bigframes_vendored/tpch/queries/__init__.py b/third_party/bigframes_vendored/tpch/queries/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/third_party/bigframes_vendored/tpch/queries/q1.py b/third_party/bigframes_vendored/tpch/queries/q1.py deleted file mode 100644 index aa6289866c5..00000000000 --- a/third_party/bigframes_vendored/tpch/queries/q1.py +++ /dev/null @@ -1,42 +0,0 @@ -# Contains code from https://github.com/pola-rs/tpch/blob/main/queries/pandas/q1.py - -import typing -from datetime import datetime - -import bigframes -import bigframes.pandas as bpd - - -def q(project_id: str, dataset_id: str, session: bigframes.Session): - lineitem = session.read_gbq( - f"{project_id}.{dataset_id}.LINEITEM", - index_col=bigframes.enums.DefaultIndexKind.NULL, - ) - - var1 = datetime(1998, 9, 2) - lineitem = lineitem[lineitem["L_SHIPDATE"] <= var1.date()] - - lineitem["DISC_PRICE"] = lineitem["L_EXTENDEDPRICE"] * ( - 1.0 - lineitem["L_DISCOUNT"] - ) - lineitem["CHARGE_PRICE"] = ( - lineitem["L_EXTENDEDPRICE"] - * (1.0 - lineitem["L_DISCOUNT"]) - * (1.0 + lineitem["L_TAX"]) - ) - - result = lineitem.groupby(["L_RETURNFLAG", "L_LINESTATUS"], as_index=False).agg( - SUM_QTY=bpd.NamedAgg(column="L_QUANTITY", aggfunc="sum"), - SUM_BASE_PRICE=bpd.NamedAgg(column="L_EXTENDEDPRICE", aggfunc="sum"), - SUM_DISC_PRICE=bpd.NamedAgg(column="DISC_PRICE", aggfunc="sum"), - SUM_CHARGE=bpd.NamedAgg(column="CHARGE_PRICE", aggfunc="sum"), - AVG_QTY=bpd.NamedAgg(column="L_QUANTITY", aggfunc="mean"), - AVG_PRICE=bpd.NamedAgg(column="L_EXTENDEDPRICE", aggfunc="mean"), - AVG_DISC=bpd.NamedAgg(column="L_DISCOUNT", aggfunc="mean"), - COUNT_ORDER=bpd.NamedAgg(column="L_QUANTITY", aggfunc="count"), - ) - result = typing.cast(bpd.DataFrame, result).sort_values( - ["L_RETURNFLAG", "L_LINESTATUS"] - ) - - next(result.to_pandas_batches(max_results=1500)) diff --git a/third_party/bigframes_vendored/tpch/queries/q10.py b/third_party/bigframes_vendored/tpch/queries/q10.py deleted file mode 100644 index 19a33b07ec2..00000000000 --- a/third_party/bigframes_vendored/tpch/queries/q10.py +++ /dev/null @@ -1,79 +0,0 @@ -# Contains code from https://github.com/pola-rs/tpch/blob/main/queries/polars/q10.py - -import typing -from datetime import date - -import bigframes -import bigframes.pandas as bpd - - -def q(project_id: str, dataset_id: str, session: bigframes.Session): - customer = session.read_gbq( - f"{project_id}.{dataset_id}.CUSTOMER", - index_col=bigframes.enums.DefaultIndexKind.NULL, - ) - lineitem = session.read_gbq( - f"{project_id}.{dataset_id}.LINEITEM", - index_col=bigframes.enums.DefaultIndexKind.NULL, - ) - nation = session.read_gbq( - f"{project_id}.{dataset_id}.NATION", - index_col=bigframes.enums.DefaultIndexKind.NULL, - ) - orders = session.read_gbq( - f"{project_id}.{dataset_id}.ORDERS", - index_col=bigframes.enums.DefaultIndexKind.NULL, - ) - - var1 = date(1993, 10, 1) - var2 = date(1994, 1, 1) - - q_final = ( - customer.merge(orders, left_on="C_CUSTKEY", right_on="O_CUSTKEY") - .merge(lineitem, left_on="O_ORDERKEY", right_on="L_ORDERKEY") - .merge(nation, left_on="C_NATIONKEY", right_on="N_NATIONKEY") - ) - - q_final = typing.cast( - bpd.DataFrame, - q_final[ - (q_final["O_ORDERDATE"] >= var1) - & (q_final["O_ORDERDATE"] < var2) - & (q_final["L_RETURNFLAG"] == "R") - ], - ) - q_final["INTERMEDIATE_REVENUE"] = ( - q_final["L_EXTENDEDPRICE"] * (1 - q_final["L_DISCOUNT"]) - ).round(2) - - q_final = q_final.groupby( - [ - "C_CUSTKEY", - "C_NAME", - "C_ACCTBAL", - "C_PHONE", - "N_NAME", - "C_ADDRESS", - "C_COMMENT", - ], - as_index=False, - ).agg(REVENUE=bpd.NamedAgg(column="INTERMEDIATE_REVENUE", aggfunc="sum")) - - q_final = ( - q_final[ - [ - "C_CUSTKEY", - "C_NAME", - "REVENUE", - "C_ACCTBAL", - "N_NAME", - "C_ADDRESS", - "C_PHONE", - "C_COMMENT", - ] - ] - .sort_values(by="REVENUE", ascending=False) - .head(20) - ) - - next(q_final.to_pandas_batches(max_results=1500)) diff --git a/third_party/bigframes_vendored/tpch/queries/q11.py b/third_party/bigframes_vendored/tpch/queries/q11.py deleted file mode 100644 index 365aa12eb92..00000000000 --- a/third_party/bigframes_vendored/tpch/queries/q11.py +++ /dev/null @@ -1,46 +0,0 @@ -# Contains code from https://github.com/pola-rs/tpch/blob/main/queries/duckdb/q11.py - -import bigframes -import bigframes.pandas as bpd - - -def q(project_id: str, dataset_id: str, session: bigframes.Session): - supplier = session.read_gbq( - f"{project_id}.{dataset_id}.SUPPLIER", - index_col=bigframes.enums.DefaultIndexKind.NULL, - ) - partsupp = session.read_gbq( - f"{project_id}.{dataset_id}.PARTSUPP", - index_col=bigframes.enums.DefaultIndexKind.NULL, - ) - nation = session.read_gbq( - f"{project_id}.{dataset_id}.NATION", - index_col=bigframes.enums.DefaultIndexKind.NULL, - ) - - nation = nation[nation["N_NAME"] == "GERMANY"] - - merged_df = nation.merge(supplier, left_on="N_NATIONKEY", right_on="S_NATIONKEY") - merged_df = merged_df.merge(partsupp, left_on="S_SUPPKEY", right_on="PS_SUPPKEY") - - merged_df["VALUE"] = merged_df["PS_SUPPLYCOST"] * merged_df["PS_AVAILQTY"] - grouped = merged_df.groupby("PS_PARTKEY", as_index=False).agg( - VALUE=bpd.NamedAgg(column="VALUE", aggfunc="sum") - ) - - grouped["VALUE"] = grouped["VALUE"].round(2) - - total_value = ( - (merged_df["PS_SUPPLYCOST"] * merged_df["PS_AVAILQTY"]).to_frame().sum() - ) - threshold = (total_value * 0.0001).rename("THRESHOLD") - - grouped = grouped.merge(threshold, how="cross") - - result_df = grouped[grouped["VALUE"] > grouped["THRESHOLD"]].drop( - columns="THRESHOLD" - ) - - result_df = result_df.sort_values(by="VALUE", ascending=False) - - next(result_df.to_pandas_batches(max_results=1500)) diff --git a/third_party/bigframes_vendored/tpch/queries/q12.py b/third_party/bigframes_vendored/tpch/queries/q12.py deleted file mode 100644 index 20097f72ca1..00000000000 --- a/third_party/bigframes_vendored/tpch/queries/q12.py +++ /dev/null @@ -1,49 +0,0 @@ -# Contains code from https://github.com/pola-rs/tpch/blob/main/queries/polars/q12.py - -import typing -from datetime import date - -import bigframes -import bigframes.pandas as bpd - - -def q(project_id: str, dataset_id: str, session: bigframes.Session): - lineitem = session.read_gbq( - f"{project_id}.{dataset_id}.LINEITEM", - index_col=bigframes.enums.DefaultIndexKind.NULL, - ) - orders = session.read_gbq( - f"{project_id}.{dataset_id}.ORDERS", - index_col=bigframes.enums.DefaultIndexKind.NULL, - ) - - var1 = "MAIL" - var2 = "SHIP" - var3 = date(1994, 1, 1) - var4 = date(1995, 1, 1) - - q_final = orders.merge(lineitem, left_on="O_ORDERKEY", right_on="L_ORDERKEY") - - q_final = q_final[ - (q_final["L_SHIPMODE"].isin([var1, var2])) - & (q_final["L_COMMITDATE"] < q_final["L_RECEIPTDATE"]) - & (q_final["L_SHIPDATE"] < q_final["L_COMMITDATE"]) - & (q_final["L_RECEIPTDATE"] >= var3) - & (q_final["L_RECEIPTDATE"] < var4) - ] - - q_final["HIGH_LINE_COUNT"] = ( - q_final["O_ORDERPRIORITY"].isin(["1-URGENT", "2-HIGH"]) - ).astype("Int64") - q_final["LOW_LINE_COUNT"] = ( - ~q_final["O_ORDERPRIORITY"].isin(["1-URGENT", "2-HIGH"]) - ).astype("Int64") - - agg_results = q_final.groupby("L_SHIPMODE", as_index=False).agg( - HIGH_LINE_COUNT=bpd.NamedAgg(column="HIGH_LINE_COUNT", aggfunc="sum"), - LOW_LINE_COUNT=bpd.NamedAgg(column="LOW_LINE_COUNT", aggfunc="sum"), - ) - - agg_results = typing.cast(bpd.DataFrame, agg_results).sort_values("L_SHIPMODE") - - next(agg_results.to_pandas_batches(max_results=1500)) diff --git a/third_party/bigframes_vendored/tpch/queries/q13.py b/third_party/bigframes_vendored/tpch/queries/q13.py deleted file mode 100644 index 8201a1191dc..00000000000 --- a/third_party/bigframes_vendored/tpch/queries/q13.py +++ /dev/null @@ -1,37 +0,0 @@ -# Contains code from https://github.com/pola-rs/tpch/blob/main/queries/polars/q13.py - -import typing - -import bigframes -import bigframes.pandas as bpd - - -def q(project_id: str, dataset_id: str, session: bigframes.Session): - customer = session.read_gbq( - f"{project_id}.{dataset_id}.CUSTOMER", - index_col=bigframes.enums.DefaultIndexKind.NULL, - ) - orders = session.read_gbq( - f"{project_id}.{dataset_id}.ORDERS", - index_col=bigframes.enums.DefaultIndexKind.NULL, - ) - - var1 = "special" - var2 = "requests" - - regex_pattern = f"{var1}.*{var2}" - orders = orders[~orders["O_COMMENT"].str.contains(regex_pattern, regex=True)] - - q_final = ( - customer.merge(orders, left_on="C_CUSTKEY", right_on="O_CUSTKEY", how="left") - .groupby("C_CUSTKEY", as_index=False) - .agg(C_COUNT=bpd.NamedAgg(column="O_ORDERKEY", aggfunc="count")) - .groupby("C_COUNT", as_index=False) - .agg("size") - .rename(columns={"size": "CUSTDIST"}) - ) - q_final = typing.cast(bpd.DataFrame, q_final).sort_values( - ["CUSTDIST", "C_COUNT"], ascending=[False, False] - ) - - next(q_final.to_pandas_batches(max_results=1500)) diff --git a/third_party/bigframes_vendored/tpch/queries/q14.py b/third_party/bigframes_vendored/tpch/queries/q14.py deleted file mode 100644 index a0260394b9b..00000000000 --- a/third_party/bigframes_vendored/tpch/queries/q14.py +++ /dev/null @@ -1,45 +0,0 @@ -# Contains code from https://github.com/pola-rs/tpch/blob/main/queries/polars/q14.py - -from datetime import date - -import bigframes - - -def q(project_id: str, dataset_id: str, session: bigframes.Session): - lineitem = session.read_gbq( - f"{project_id}.{dataset_id}.LINEITEM", - index_col=bigframes.enums.DefaultIndexKind.NULL, - ) - part = session.read_gbq( - f"{project_id}.{dataset_id}.PART", - index_col=bigframes.enums.DefaultIndexKind.NULL, - ) - - var1 = date(1995, 9, 1) - var2 = date(1995, 10, 1) - - merged = part.merge(lineitem, left_on="P_PARTKEY", right_on="L_PARTKEY") - - filtered = merged[(merged["L_SHIPDATE"] >= var1) & (merged["L_SHIPDATE"] < var2)] - - filtered["CONDI_REVENUE"] = ( - filtered["L_EXTENDEDPRICE"] * (1 - filtered["L_DISCOUNT"]) - ) * filtered["P_TYPE"].str.contains("PROMO").astype("Int64") - - total_revenue = ( - (filtered["L_EXTENDEDPRICE"] * (1 - filtered["L_DISCOUNT"])) - .to_frame(name="TEMP") - .sum() - ) - - promo_revenue = filtered["CONDI_REVENUE"].to_frame(name="TEMP").sum() - - promo_revenue_percent = ( - (100.00 * promo_revenue / total_revenue) - .sort_index() - .reset_index(drop=True) - .round(2) - .to_frame(name="PROMO_REVENUE") - ) - - next(promo_revenue_percent.to_pandas_batches(max_results=1500)) diff --git a/third_party/bigframes_vendored/tpch/queries/q15.py b/third_party/bigframes_vendored/tpch/queries/q15.py deleted file mode 100644 index 0e3460189d1..00000000000 --- a/third_party/bigframes_vendored/tpch/queries/q15.py +++ /dev/null @@ -1,54 +0,0 @@ -# Contains code from https://github.com/pola-rs/tpch/blob/main/queries/polars/q15.py - -from datetime import date - -import bigframes -import bigframes.pandas as bpd - - -def q(project_id: str, dataset_id: str, session: bigframes.Session): - lineitem = session.read_gbq( - f"{project_id}.{dataset_id}.LINEITEM", - index_col=bigframes.enums.DefaultIndexKind.NULL, - ) - supplier = session.read_gbq( - f"{project_id}.{dataset_id}.SUPPLIER", - index_col=bigframes.enums.DefaultIndexKind.NULL, - ) - - var1 = date(1996, 1, 1) - var2 = date(1996, 4, 1) - - filtered_lineitem = lineitem[ - (lineitem["L_SHIPDATE"] >= var1) & (lineitem["L_SHIPDATE"] < var2) - ] - filtered_lineitem["REVENUE"] = filtered_lineitem["L_EXTENDEDPRICE"] * ( - 1 - filtered_lineitem["L_DISCOUNT"] - ) - - grouped_revenue = ( - filtered_lineitem.groupby("L_SUPPKEY", as_index=False) - .agg(TOTAL_REVENUE=bpd.NamedAgg(column="REVENUE", aggfunc="sum")) - .rename(columns={"L_SUPPKEY": "SUPPLIER_NO"}) - ) - # Round earlier to prevent non-determinism in the later join due to - # differences in distributed floating point operation sort order. - grouped_revenue = grouped_revenue.assign( - TOTAL_REVENUE=grouped_revenue["TOTAL_REVENUE"].round(2) - ) - - joined_data = bpd.merge( - supplier, grouped_revenue, left_on="S_SUPPKEY", right_on="SUPPLIER_NO" - ) - - max_revenue = joined_data[["TOTAL_REVENUE"]].max().rename("MAX_REVENUE") - - joined_data = joined_data.merge(max_revenue, how="cross") - - max_revenue_suppliers = joined_data[ - joined_data["TOTAL_REVENUE"] == joined_data["MAX_REVENUE"] - ] - q_final = max_revenue_suppliers[ - ["S_SUPPKEY", "S_NAME", "S_ADDRESS", "S_PHONE", "TOTAL_REVENUE"] - ].sort_values("S_SUPPKEY") - next(q_final.to_pandas_batches(max_results=1500)) diff --git a/third_party/bigframes_vendored/tpch/queries/q16.py b/third_party/bigframes_vendored/tpch/queries/q16.py deleted file mode 100644 index f55939b03c0..00000000000 --- a/third_party/bigframes_vendored/tpch/queries/q16.py +++ /dev/null @@ -1,44 +0,0 @@ -# Contains code from https://github.com/pola-rs/tpch/blob/main/queries/polars/q16.py - -import bigframes -import bigframes.pandas as bpd - - -def q(project_id: str, dataset_id: str, session: bigframes.Session): - part = session.read_gbq( - f"{project_id}.{dataset_id}.PART", - index_col=bigframes.enums.DefaultIndexKind.NULL, - ) - partsupp = session.read_gbq( - f"{project_id}.{dataset_id}.PARTSUPP", - index_col=bigframes.enums.DefaultIndexKind.NULL, - ) - supplier = session.read_gbq( - f"{project_id}.{dataset_id}.SUPPLIER", - index_col=bigframes.enums.DefaultIndexKind.NULL, - ) - - var1 = "Brand#45" - - supplier = supplier[ - ~supplier["S_COMMENT"].str.contains("Customer.*Complaints", regex=True) - ]["S_SUPPKEY"] - - q_filtered = part.merge(partsupp, left_on="P_PARTKEY", right_on="PS_PARTKEY") - q_filtered = q_filtered[q_filtered["P_BRAND"] != var1] - q_filtered = q_filtered[~q_filtered["P_TYPE"].str.contains("MEDIUM POLISHED")] - q_filtered = q_filtered[q_filtered["P_SIZE"].isin([49, 14, 23, 45, 19, 3, 36, 9])] - - final_df = q_filtered[q_filtered["PS_SUPPKEY"].isin(supplier)] - - grouped = final_df.groupby(["P_BRAND", "P_TYPE", "P_SIZE"], as_index=False) - result = grouped.agg( - SUPPLIER_CNT=bpd.NamedAgg(column="PS_SUPPKEY", aggfunc="nunique") - ) - - q_final = result.sort_values( - by=["SUPPLIER_CNT", "P_BRAND", "P_TYPE", "P_SIZE"], - ascending=[False, True, True, True], - ) - - next(q_final.to_pandas_batches(max_results=1500)) diff --git a/third_party/bigframes_vendored/tpch/queries/q17.py b/third_party/bigframes_vendored/tpch/queries/q17.py deleted file mode 100644 index aa7f7436021..00000000000 --- a/third_party/bigframes_vendored/tpch/queries/q17.py +++ /dev/null @@ -1,40 +0,0 @@ -# Contains code from https://github.com/pola-rs/tpch/blob/main/queries/polars/q17.py - -import bigframes -import bigframes.pandas as bpd - - -def q(project_id: str, dataset_id: str, session: bigframes.Session): - lineitem = session.read_gbq( - f"{project_id}.{dataset_id}.LINEITEM", - index_col=bigframes.enums.DefaultIndexKind.NULL, - ) - part = session.read_gbq( - f"{project_id}.{dataset_id}.PART", - index_col=bigframes.enums.DefaultIndexKind.NULL, - ) - - VAR1 = "Brand#23" - VAR2 = "MED BOX" - - filtered_part = part[(part["P_BRAND"] == VAR1) & (part["P_CONTAINER"] == VAR2)] - q1 = bpd.merge( - lineitem, filtered_part, how="right", left_on="L_PARTKEY", right_on="P_PARTKEY" - ) - - grouped = ( - q1.groupby("P_PARTKEY", as_index=False) - .agg(AVG_QUANTITY=bpd.NamedAgg(column="L_QUANTITY", aggfunc="mean")) - .rename(columns={"P_PARTKEY": "KEY"}) - ) - grouped["AVG_QUANTITY"] = grouped["AVG_QUANTITY"] * 0.2 - - q_final = bpd.merge(grouped, q1, left_on="KEY", right_on="P_PARTKEY") - - q_final = q_final[q_final["L_QUANTITY"] < q_final["AVG_QUANTITY"]] - - q_final = ( - (q_final[["L_EXTENDEDPRICE"]].sum() / 7.0).round(2).to_frame(name="AVG_YEARLY") - ) - - next(q_final.to_pandas_batches(max_results=1500)) diff --git a/third_party/bigframes_vendored/tpch/queries/q18.py b/third_party/bigframes_vendored/tpch/queries/q18.py deleted file mode 100644 index 576ce58d5ce..00000000000 --- a/third_party/bigframes_vendored/tpch/queries/q18.py +++ /dev/null @@ -1,50 +0,0 @@ -# Contains code from https://github.com/pola-rs/tpch/blob/main/queries/polars/q18.py - -import typing - -import bigframes -import bigframes.pandas as bpd - - -def q(project_id: str, dataset_id: str, session: bigframes.Session): - customer = session.read_gbq( - f"{project_id}.{dataset_id}.CUSTOMER", - index_col=bigframes.enums.DefaultIndexKind.NULL, - ) - lineitem = session.read_gbq( - f"{project_id}.{dataset_id}.LINEITEM", - index_col=bigframes.enums.DefaultIndexKind.NULL, - ) - orders = session.read_gbq( - f"{project_id}.{dataset_id}.ORDERS", - index_col=bigframes.enums.DefaultIndexKind.NULL, - ) - - var1 = 300 - - # order with over 300 items - q1 = lineitem.groupby("L_ORDERKEY", as_index=False).agg( - SUM_QUANTITY=bpd.NamedAgg(column="L_QUANTITY", aggfunc="sum") - ) - q1 = q1[q1["SUM_QUANTITY"] > var1] - - filtered_orders = orders[orders["O_ORDERKEY"].isin(q1["L_ORDERKEY"])] - - result = filtered_orders.merge( - lineitem, left_on="O_ORDERKEY", right_on="L_ORDERKEY" - ) - result = result.merge(customer, left_on="O_CUSTKEY", right_on="C_CUSTKEY") - - final_result = result.groupby( - ["C_NAME", "C_CUSTKEY", "O_ORDERKEY", "O_ORDERDATE", "O_TOTALPRICE"], - as_index=False, - ).agg(COL6=bpd.NamedAgg(column="L_QUANTITY", aggfunc="sum")) - - final_result = final_result.rename(columns={"O_ORDERDATE": "O_ORDERDAT"}) - - final_result = typing.cast(bpd.DataFrame, final_result).sort_values( - ["O_TOTALPRICE", "O_ORDERDAT"], ascending=[False, True] - ) - - q_final = final_result.head(100) - next(q_final.to_pandas_batches(max_results=1500)) diff --git a/third_party/bigframes_vendored/tpch/queries/q19.py b/third_party/bigframes_vendored/tpch/queries/q19.py deleted file mode 100644 index a217db3dc32..00000000000 --- a/third_party/bigframes_vendored/tpch/queries/q19.py +++ /dev/null @@ -1,63 +0,0 @@ -# Contains code from https://github.com/pola-rs/tpch/blob/main/queries/polars/q19.py - -import bigframes -import bigframes.pandas as bpd - - -def q(project_id: str, dataset_id: str, session: bigframes.Session): - lineitem = session.read_gbq( - f"{project_id}.{dataset_id}.LINEITEM", - index_col=bigframes.enums.DefaultIndexKind.NULL, - ) - part = session.read_gbq( - f"{project_id}.{dataset_id}.PART", - index_col=bigframes.enums.DefaultIndexKind.NULL, - ) - - merged = bpd.merge(part, lineitem, left_on="P_PARTKEY", right_on="L_PARTKEY") - - filtered = merged[ - (merged["L_SHIPMODE"].isin(["AIR", "AIR REG"])) - & (merged["L_SHIPINSTRUCT"] == "DELIVER IN PERSON") - & ( - ( - (merged["P_BRAND"] == "Brand#12") - & ( - merged["P_CONTAINER"].isin( - ["SM CASE", "SM BOX", "SM PACK", "SM PKG"] - ) - ) - & (merged["L_QUANTITY"].between(1, 11, inclusive="both")) - & (merged["P_SIZE"].between(1, 5, inclusive="both")) - ) - | ( - (merged["P_BRAND"] == "Brand#23") - & ( - merged["P_CONTAINER"].isin( - ["MED BAG", "MED BOX", "MED PKG", "MED PACK"] - ) - ) - & (merged["L_QUANTITY"].between(10, 20, inclusive="both")) - & (merged["P_SIZE"].between(1, 10, inclusive="both")) - ) - | ( - (merged["P_BRAND"] == "Brand#34") - & ( - merged["P_CONTAINER"].isin( - ["LG CASE", "LG BOX", "LG PACK", "LG PKG"] - ) - ) - & (merged["L_QUANTITY"].between(20, 30, inclusive="both")) - & (merged["P_SIZE"].between(1, 15, inclusive="both")) - ) - ) - ] - - result_df = ( - (filtered["L_EXTENDEDPRICE"] * (1 - filtered["L_DISCOUNT"])) - .agg(["sum"]) - .rename("REVENUE") - .to_frame() - ) - - next(result_df.to_pandas_batches(max_results=1500)) diff --git a/third_party/bigframes_vendored/tpch/queries/q2.py b/third_party/bigframes_vendored/tpch/queries/q2.py deleted file mode 100644 index e154e8ae983..00000000000 --- a/third_party/bigframes_vendored/tpch/queries/q2.py +++ /dev/null @@ -1,62 +0,0 @@ -# Contains code from https://github.com/pola-rs/tpch/blob/main/queries/pandas/q2.py - -import bigframes - - -def q(project_id: str, dataset_id: str, session: bigframes.Session): - region = session.read_gbq( - f"{project_id}.{dataset_id}.REGION", - index_col=bigframes.enums.DefaultIndexKind.NULL, - ) - nation = session.read_gbq( - f"{project_id}.{dataset_id}.NATION", - index_col=bigframes.enums.DefaultIndexKind.NULL, - ) - supplier = session.read_gbq( - f"{project_id}.{dataset_id}.SUPPLIER", - index_col=bigframes.enums.DefaultIndexKind.NULL, - ) - part = session.read_gbq( - f"{project_id}.{dataset_id}.PART", - index_col=bigframes.enums.DefaultIndexKind.NULL, - ) - partsupp = session.read_gbq( - f"{project_id}.{dataset_id}.PARTSUPP", - index_col=bigframes.enums.DefaultIndexKind.NULL, - ) - - jn = ( - part.merge(partsupp, left_on="P_PARTKEY", right_on="PS_PARTKEY") - .merge(supplier, left_on="PS_SUPPKEY", right_on="S_SUPPKEY") - .merge(nation, left_on="S_NATIONKEY", right_on="N_NATIONKEY") - .merge(region, left_on="N_REGIONKEY", right_on="R_REGIONKEY") - ) - - jn = jn[jn["P_SIZE"] == 15] - jn = jn[jn["P_TYPE"].str.endswith("BRASS")] - jn = jn[jn["R_NAME"] == "EUROPE"] - - gb = jn.groupby("P_PARTKEY", as_index=False) - agg = gb["PS_SUPPLYCOST"].min() - jn2 = agg.merge(jn, on=["P_PARTKEY", "PS_SUPPLYCOST"]) - - sel = jn2[ - [ - "S_ACCTBAL", - "S_NAME", - "N_NAME", - "P_PARTKEY", - "P_MFGR", - "S_ADDRESS", - "S_PHONE", - "S_COMMENT", - ] - ] - - sort = sel.sort_values( - by=["S_ACCTBAL", "N_NAME", "S_NAME", "P_PARTKEY"], - ascending=[False, True, True, True], - ) - - result_df = sort.head(100) - next(result_df.to_pandas_batches(max_results=1500)) diff --git a/third_party/bigframes_vendored/tpch/queries/q20.py b/third_party/bigframes_vendored/tpch/queries/q20.py deleted file mode 100644 index 7c434eba03b..00000000000 --- a/third_party/bigframes_vendored/tpch/queries/q20.py +++ /dev/null @@ -1,62 +0,0 @@ -# Contains code from https://github.com/pola-rs/tpch/blob/main/queries/polars/q20.py - -from datetime import date - -import bigframes -import bigframes.pandas as bpd - - -def q(project_id: str, dataset_id: str, session: bigframes.Session): - lineitem = session.read_gbq( - f"{project_id}.{dataset_id}.LINEITEM", - index_col=bigframes.enums.DefaultIndexKind.NULL, - ) - nation = session.read_gbq( - f"{project_id}.{dataset_id}.NATION", - index_col=bigframes.enums.DefaultIndexKind.NULL, - ) - part = session.read_gbq( - f"{project_id}.{dataset_id}.PART", - index_col=bigframes.enums.DefaultIndexKind.NULL, - ) - partsupp = session.read_gbq( - f"{project_id}.{dataset_id}.PARTSUPP", - index_col=bigframes.enums.DefaultIndexKind.NULL, - ) - supplier = session.read_gbq( - f"{project_id}.{dataset_id}.SUPPLIER", - index_col=bigframes.enums.DefaultIndexKind.NULL, - ) - - var1 = date(1994, 1, 1) - var2 = date(1995, 1, 1) - var3 = "CANADA" - var4 = "forest" - - q1 = lineitem[(lineitem["L_SHIPDATE"] >= var1) & (lineitem["L_SHIPDATE"] < var2)] - q1 = q1.groupby(["L_PARTKEY", "L_SUPPKEY"], as_index=False).agg( - SUM_QUANTITY=bpd.NamedAgg(column="L_QUANTITY", aggfunc="sum") - ) - q1["SUM_QUANTITY"] = q1["SUM_QUANTITY"] * 0.5 - q2 = nation[nation["N_NAME"] == var3] - - q3 = supplier.merge(q2, left_on="S_NATIONKEY", right_on="N_NATIONKEY") - - filtered_parts = part[part["P_NAME"].str.startswith(var4)] - - filtered_parts = filtered_parts["P_PARTKEY"] - joined_parts = partsupp[partsupp["PS_PARTKEY"].isin(filtered_parts)] - - final_join = q1.merge( - joined_parts, - left_on=["L_SUPPKEY", "L_PARTKEY"], - right_on=["PS_SUPPKEY", "PS_PARTKEY"], - ) - final_filtered = final_join[final_join["PS_AVAILQTY"] > final_join["SUM_QUANTITY"]][ - "PS_SUPPKEY" - ] - - final_result = q3[q3["S_SUPPKEY"].isin(final_filtered)] - final_result = final_result[["S_NAME", "S_ADDRESS"]].sort_values(by="S_NAME") - - next(final_result.to_pandas_batches(max_results=1500)) diff --git a/third_party/bigframes_vendored/tpch/queries/q21.py b/third_party/bigframes_vendored/tpch/queries/q21.py deleted file mode 100644 index c27aab0e69b..00000000000 --- a/third_party/bigframes_vendored/tpch/queries/q21.py +++ /dev/null @@ -1,59 +0,0 @@ -# Contains code from https://github.com/pola-rs/tpch/blob/main/queries/polars/q21.py - -import bigframes -import bigframes.pandas as bpd - - -def q(project_id: str, dataset_id: str, session: bigframes.Session): - lineitem = session.read_gbq( - f"{project_id}.{dataset_id}.LINEITEM", - index_col=bigframes.enums.DefaultIndexKind.NULL, - ) - nation = session.read_gbq( - f"{project_id}.{dataset_id}.NATION", - index_col=bigframes.enums.DefaultIndexKind.NULL, - ) - orders = session.read_gbq( - f"{project_id}.{dataset_id}.ORDERS", - index_col=bigframes.enums.DefaultIndexKind.NULL, - ) - supplier = session.read_gbq( - f"{project_id}.{dataset_id}.SUPPLIER", - index_col=bigframes.enums.DefaultIndexKind.NULL, - ) - - var1 = "SAUDI ARABIA" - - q1 = lineitem.groupby("L_ORDERKEY", as_index=False).agg( - N_SUPP_BY_ORDER=bpd.NamedAgg(column="L_SUPPKEY", aggfunc="size") - ) - q1 = q1[q1["N_SUPP_BY_ORDER"] > 1] - - lineitem_filtered = lineitem[lineitem["L_RECEIPTDATE"] > lineitem["L_COMMITDATE"]] - - q1 = q1.merge(lineitem_filtered, on="L_ORDERKEY") - - q_final = q1.groupby("L_ORDERKEY", as_index=False).agg( - N_SUPP_BY_ORDER_FINAL=bpd.NamedAgg(column="L_SUPPKEY", aggfunc="size") - ) - - q_final = q_final.merge(q1, on="L_ORDERKEY") - q_final = q_final.merge(supplier, left_on="L_SUPPKEY", right_on="S_SUPPKEY") - q_final = q_final.merge(nation, left_on="S_NATIONKEY", right_on="N_NATIONKEY") - q_final = q_final.merge(orders, left_on="L_ORDERKEY", right_on="O_ORDERKEY") - - q_final = q_final[ - (q_final["N_SUPP_BY_ORDER_FINAL"] == 1) - & (q_final["N_NAME"] == var1) - & (q_final["O_ORDERSTATUS"] == "F") - ] - - q_final = q_final.groupby("S_NAME", as_index=False).agg( - NUMWAIT=bpd.NamedAgg(column="L_SUPPKEY", aggfunc="size") - ) - - q_final = q_final.sort_values( - by=["NUMWAIT", "S_NAME"], ascending=[False, True] - ).head(100) - - next(q_final.to_pandas_batches(max_results=1500)) diff --git a/third_party/bigframes_vendored/tpch/queries/q22.py b/third_party/bigframes_vendored/tpch/queries/q22.py deleted file mode 100644 index a8d147eae42..00000000000 --- a/third_party/bigframes_vendored/tpch/queries/q22.py +++ /dev/null @@ -1,40 +0,0 @@ -# Contains code from https://github.com/pola-rs/tpch/blob/main/queries/polars/q22.py - -import bigframes -import bigframes.pandas as bpd - - -def q(project_id: str, dataset_id: str, session: bigframes.Session): - customer = session.read_gbq( - f"{project_id}.{dataset_id}.CUSTOMER", - index_col=bigframes.enums.DefaultIndexKind.NULL, - ) - orders = session.read_gbq( - f"{project_id}.{dataset_id}.ORDERS", - index_col=bigframes.enums.DefaultIndexKind.NULL, - ) - - country_codes = ["13", "31", "23", "29", "30", "18", "17"] - customer["CNTRYCODE"] = customer["C_PHONE"].str.slice(0, 2) - customer = customer[customer["CNTRYCODE"].isin(country_codes)] - - avg_acctbal = ( - customer[customer["C_ACCTBAL"] > 0.0][["C_ACCTBAL"]] - .mean() - .rename("AVG_ACCTBAL") - ) - customer = customer.merge(avg_acctbal, how="cross") - - filtered_customer = customer[customer["C_ACCTBAL"] > customer["AVG_ACCTBAL"]] - - filtered_customer = filtered_customer[ - ~filtered_customer["C_CUSTKEY"].isin(orders["O_CUSTKEY"]) - ] - result = filtered_customer.groupby("CNTRYCODE", as_index=False).agg( - NUMCUST=bpd.NamedAgg(column="C_CUSTKEY", aggfunc="count"), - TOTACCTBAL=bpd.NamedAgg(column="C_ACCTBAL", aggfunc="sum"), - ) - - result = result.sort_values(by="CNTRYCODE") - - next(result.to_pandas_batches(max_results=1500)) diff --git a/third_party/bigframes_vendored/tpch/queries/q3.py b/third_party/bigframes_vendored/tpch/queries/q3.py deleted file mode 100644 index 5a43f5fff73..00000000000 --- a/third_party/bigframes_vendored/tpch/queries/q3.py +++ /dev/null @@ -1,44 +0,0 @@ -# Contains code from https://github.com/pola-rs/tpch/blob/main/queries/pandas/q3.py - -from datetime import date - -import bigframes - - -def q(project_id: str, dataset_id: str, session: bigframes.Session): - customer = session.read_gbq( - f"{project_id}.{dataset_id}.CUSTOMER", - index_col=bigframes.enums.DefaultIndexKind.NULL, - ) - lineitem = session.read_gbq( - f"{project_id}.{dataset_id}.LINEITEM", - index_col=bigframes.enums.DefaultIndexKind.NULL, - ) - orders = session.read_gbq( - f"{project_id}.{dataset_id}.ORDERS", - index_col=bigframes.enums.DefaultIndexKind.NULL, - ) - - date_var = date(1995, 3, 15) - - fcustomer = customer[customer["C_MKTSEGMENT"] == "BUILDING"] - - filtered_orders = orders[orders["O_ORDERDATE"] < date_var] - filtered_lineitem = lineitem[lineitem["L_SHIPDATE"] > date_var] - - jn1 = filtered_lineitem.merge( - filtered_orders, left_on="L_ORDERKEY", right_on="O_ORDERKEY" - ) - jn2 = fcustomer.merge(jn1, left_on="C_CUSTKEY", right_on="O_CUSTKEY") - jn2["REVENUE"] = jn2["L_EXTENDEDPRICE"] * (1 - jn2["L_DISCOUNT"]) - - gb = jn2.groupby(["O_ORDERKEY", "O_ORDERDATE", "O_SHIPPRIORITY"], as_index=False) - agg = gb["REVENUE"].sum() - - sel = agg[["O_ORDERKEY", "REVENUE", "O_ORDERDATE", "O_SHIPPRIORITY"]] - sel = sel.rename(columns={"O_ORDERKEY": "L_ORDERKEY"}) - - sorted_sel = sel.sort_values(by=["REVENUE", "O_ORDERDATE"], ascending=[False, True]) - result_df = sorted_sel.head(10) - - next(result_df.to_pandas_batches(max_results=1500)) diff --git a/third_party/bigframes_vendored/tpch/queries/q4.py b/third_party/bigframes_vendored/tpch/queries/q4.py deleted file mode 100644 index 9c855704ab9..00000000000 --- a/third_party/bigframes_vendored/tpch/queries/q4.py +++ /dev/null @@ -1,35 +0,0 @@ -# Contains code from https://github.com/pola-rs/tpch/blob/main/queries/pandas/q4.py - - -import typing -from datetime import date - -import bigframes -import bigframes.pandas as bpd - - -def q(project_id: str, dataset_id: str, session: bigframes.Session): - lineitem = session.read_gbq( - f"{project_id}.{dataset_id}.LINEITEM", - index_col=bigframes.enums.DefaultIndexKind.NULL, - ) - orders = session.read_gbq( - f"{project_id}.{dataset_id}.ORDERS", - index_col=bigframes.enums.DefaultIndexKind.NULL, - ) - - var1 = date(1993, 7, 1) - var2 = date(1993, 10, 1) - - jn = lineitem.merge(orders, left_on="L_ORDERKEY", right_on="O_ORDERKEY") - - jn = jn[(jn["O_ORDERDATE"] >= var1) & (jn["O_ORDERDATE"] < var2)] - jn = jn[jn["L_COMMITDATE"] < jn["L_RECEIPTDATE"]] - - jn = jn.groupby(["O_ORDERPRIORITY", "L_ORDERKEY"], as_index=False).agg("size") - - gb = jn.groupby("O_ORDERPRIORITY", as_index=False) - agg = gb.agg(ORDER_COUNT=bpd.NamedAgg(column="L_ORDERKEY", aggfunc="count")) - - result_df = typing.cast(bpd.DataFrame, agg).sort_values(["O_ORDERPRIORITY"]) - next(result_df.to_pandas_batches(max_results=1500)) diff --git a/third_party/bigframes_vendored/tpch/queries/q5.py b/third_party/bigframes_vendored/tpch/queries/q5.py deleted file mode 100644 index 1361c40901b..00000000000 --- a/third_party/bigframes_vendored/tpch/queries/q5.py +++ /dev/null @@ -1,55 +0,0 @@ -# Contains code from https://github.com/pola-rs/tpch/blob/main/queries/pandas/q5.py - -from datetime import date - -import bigframes - - -def q(project_id: str, dataset_id: str, session: bigframes.Session): - region = session.read_gbq( - f"{project_id}.{dataset_id}.REGION", - index_col=bigframes.enums.DefaultIndexKind.NULL, - ) - nation = session.read_gbq( - f"{project_id}.{dataset_id}.NATION", - index_col=bigframes.enums.DefaultIndexKind.NULL, - ) - customer = session.read_gbq( - f"{project_id}.{dataset_id}.CUSTOMER", - index_col=bigframes.enums.DefaultIndexKind.NULL, - ) - lineitem = session.read_gbq( - f"{project_id}.{dataset_id}.LINEITEM", - index_col=bigframes.enums.DefaultIndexKind.NULL, - ) - orders = session.read_gbq( - f"{project_id}.{dataset_id}.ORDERS", - index_col=bigframes.enums.DefaultIndexKind.NULL, - ) - supplier = session.read_gbq( - f"{project_id}.{dataset_id}.SUPPLIER", - index_col=bigframes.enums.DefaultIndexKind.NULL, - ) - - var1 = "ASIA" - var2 = date(1994, 1, 1) - var3 = date(1995, 1, 1) - - region = region[region["R_NAME"] == var1] - orders = orders[(orders["O_ORDERDATE"] >= var2) & (orders["O_ORDERDATE"] < var3)] - lineitem["REVENUE"] = lineitem["L_EXTENDEDPRICE"] * (1.0 - lineitem["L_DISCOUNT"]) - - jn1 = region.merge(nation, left_on="R_REGIONKEY", right_on="N_REGIONKEY") - jn2 = jn1.merge(customer, left_on="N_NATIONKEY", right_on="C_NATIONKEY") - jn3 = orders.merge(jn2, left_on="O_CUSTKEY", right_on="C_CUSTKEY") - jn4 = lineitem.merge(jn3, left_on="L_ORDERKEY", right_on="O_ORDERKEY") - jn5 = jn4.merge( - supplier, - left_on=["L_SUPPKEY", "N_NATIONKEY"], - right_on=["S_SUPPKEY", "S_NATIONKEY"], - ) - - gb = jn5.groupby("N_NAME", as_index=False)["REVENUE"].sum() - result_df = gb.sort_values("REVENUE", ascending=False) - - next(result_df.to_pandas_batches(max_results=1500)) diff --git a/third_party/bigframes_vendored/tpch/queries/q6.py b/third_party/bigframes_vendored/tpch/queries/q6.py deleted file mode 100644 index 8fe067bafec..00000000000 --- a/third_party/bigframes_vendored/tpch/queries/q6.py +++ /dev/null @@ -1,30 +0,0 @@ -# Contains code from https://github.com/pola-rs/tpch/blob/main/queries/pandas/q6.py - -from datetime import date - -import bigframes - - -def q(project_id: str, dataset_id: str, session: bigframes.Session): - lineitem = session.read_gbq( - f"{project_id}.{dataset_id}.LINEITEM", - index_col=bigframes.enums.DefaultIndexKind.NULL, - ) - - var1 = date(1994, 1, 1) - var2 = date(1995, 1, 1) - var3 = 0.05 - var4 = 0.07 - var5 = 24 - - filt = lineitem[(lineitem["L_SHIPDATE"] >= var1) & (lineitem["L_SHIPDATE"] < var2)] - filt = filt[(filt["L_DISCOUNT"] >= var3) & (filt["L_DISCOUNT"] <= var4)] - filt = filt[filt["L_QUANTITY"] < var5] - result_df = ( - (filt["L_EXTENDEDPRICE"] * filt["L_DISCOUNT"]) - .agg(["sum"]) - .rename("REVENUE") - .to_frame() - ) - - next(result_df.to_pandas_batches(max_results=1500)) diff --git a/third_party/bigframes_vendored/tpch/queries/q7.py b/third_party/bigframes_vendored/tpch/queries/q7.py deleted file mode 100644 index 0756bcc6566..00000000000 --- a/third_party/bigframes_vendored/tpch/queries/q7.py +++ /dev/null @@ -1,63 +0,0 @@ -# Contains code from https://github.com/pola-rs/tpch/blob/main/queries/pandas/q7.py - -import typing -from datetime import date - -import bigframes -import bigframes.dataframe -import bigframes.pandas as bpd - - -def q(project_id: str, dataset_id: str, session: bigframes.Session): - nation = session.read_gbq( - f"{project_id}.{dataset_id}.NATION", - index_col=bigframes.enums.DefaultIndexKind.NULL, - ) - customer = session.read_gbq( - f"{project_id}.{dataset_id}.CUSTOMER", - index_col=bigframes.enums.DefaultIndexKind.NULL, - ) - lineitem = session.read_gbq( - f"{project_id}.{dataset_id}.LINEITEM", - index_col=bigframes.enums.DefaultIndexKind.NULL, - ) - orders = session.read_gbq( - f"{project_id}.{dataset_id}.ORDERS", - index_col=bigframes.enums.DefaultIndexKind.NULL, - ) - supplier = session.read_gbq( - f"{project_id}.{dataset_id}.SUPPLIER", - index_col=bigframes.enums.DefaultIndexKind.NULL, - ) - - var1 = "FRANCE" - var2 = "GERMANY" - var3 = date(1995, 1, 1) - var4 = date(1996, 12, 31) - - nation = nation[nation["N_NAME"].isin([var1, var2])] - lineitem = lineitem[ - (lineitem["L_SHIPDATE"] >= var3) & (lineitem["L_SHIPDATE"] <= var4) - ] - - jn1 = customer.merge(nation, left_on="C_NATIONKEY", right_on="N_NATIONKEY") - jn2 = jn1.merge(orders, left_on="C_CUSTKEY", right_on="O_CUSTKEY") - jn2 = jn2.rename(columns={"N_NAME": "CUST_NATION"}) - jn3 = jn2.merge(lineitem, left_on="O_ORDERKEY", right_on="L_ORDERKEY") - jn4 = jn3.merge(supplier, left_on="L_SUPPKEY", right_on="S_SUPPKEY") - jn5 = jn4.merge(nation, left_on="S_NATIONKEY", right_on="N_NATIONKEY") - df1 = jn5.rename(columns={"N_NAME": "SUPP_NATION"}) - total = df1[df1["CUST_NATION"] != df1["SUPP_NATION"]] - - total["VOLUME"] = total["L_EXTENDEDPRICE"] * (1.0 - total["L_DISCOUNT"]) - total["L_YEAR"] = total["L_SHIPDATE"].dt.year - - gb = typing.cast(bpd.DataFrame, total).groupby( - ["SUPP_NATION", "CUST_NATION", "L_YEAR"], as_index=False - ) - agg = gb.agg(REVENUE=bpd.NamedAgg(column="VOLUME", aggfunc="sum")) - - result_df = typing.cast(bpd.DataFrame, agg).sort_values( - ["SUPP_NATION", "CUST_NATION", "L_YEAR"] - ) - next(result_df.to_pandas_batches(max_results=1500)) diff --git a/third_party/bigframes_vendored/tpch/queries/q8.py b/third_party/bigframes_vendored/tpch/queries/q8.py deleted file mode 100644 index 67e1af12419..00000000000 --- a/third_party/bigframes_vendored/tpch/queries/q8.py +++ /dev/null @@ -1,72 +0,0 @@ -# Contains code from https://github.com/pola-rs/tpch/blob/main/queries/pandas/q8.py - -from datetime import date - -import bigframes - - -def q(project_id: str, dataset_id: str, session: bigframes.Session): - customer = session.read_gbq( - f"{project_id}.{dataset_id}.CUSTOMER", - index_col=bigframes.enums.DefaultIndexKind.NULL, - ) - lineitem = session.read_gbq( - f"{project_id}.{dataset_id}.LINEITEM", - index_col=bigframes.enums.DefaultIndexKind.NULL, - ) - nation = session.read_gbq( - f"{project_id}.{dataset_id}.NATION", - index_col=bigframes.enums.DefaultIndexKind.NULL, - ) - orders = session.read_gbq( - f"{project_id}.{dataset_id}.ORDERS", - index_col=bigframes.enums.DefaultIndexKind.NULL, - ) - part = session.read_gbq( - f"{project_id}.{dataset_id}.PART", - index_col=bigframes.enums.DefaultIndexKind.NULL, - ) - region = session.read_gbq( - f"{project_id}.{dataset_id}.REGION", - index_col=bigframes.enums.DefaultIndexKind.NULL, - ) - supplier = session.read_gbq( - f"{project_id}.{dataset_id}.SUPPLIER", - index_col=bigframes.enums.DefaultIndexKind.NULL, - ) - - var1 = "BRAZIL" - var2 = "AMERICA" - var3 = "ECONOMY ANODIZED STEEL" - var4 = date(1995, 1, 1) - var5 = date(1996, 12, 31) - - n1 = nation[["N_NATIONKEY", "N_REGIONKEY"]] - n2 = nation[["N_NATIONKEY", "N_NAME"]] - - jn1 = part.merge(lineitem, left_on="P_PARTKEY", right_on="L_PARTKEY") - jn2 = jn1.merge(supplier, left_on="L_SUPPKEY", right_on="S_SUPPKEY") - jn3 = jn2.merge(orders, left_on="L_ORDERKEY", right_on="O_ORDERKEY") - jn4 = jn3.merge(customer, left_on="O_CUSTKEY", right_on="C_CUSTKEY") - jn5 = jn4.merge(n1, left_on="C_NATIONKEY", right_on="N_NATIONKEY") - jn6 = jn5.merge(region, left_on="N_REGIONKEY", right_on="R_REGIONKEY") - - jn6 = jn6[(jn6["R_NAME"] == var2)] - - jn7 = jn6.merge(n2, left_on="S_NATIONKEY", right_on="N_NATIONKEY") - - jn7 = jn7[(jn7["O_ORDERDATE"] >= var4) & (jn7["O_ORDERDATE"] <= var5)] - jn7 = jn7[jn7["P_TYPE"] == var3] - - jn7["O_YEAR"] = jn7["O_ORDERDATE"].dt.year - jn7["VOLUME"] = jn7["L_EXTENDEDPRICE"] * (1.0 - jn7["L_DISCOUNT"]) - jn7 = jn7.rename(columns={"N_NAME": "NATION"}) - - jn7["numerator"] = jn7["VOLUME"].where(jn7["NATION"] == var1, 0) - jn7["denominator"] = jn7["VOLUME"] - - sums = jn7.groupby("O_YEAR")[["numerator", "denominator"]].sum() - sums["MKT_SHARE"] = (sums["numerator"] / sums["denominator"]).round(2) - - result_df = sums["MKT_SHARE"].sort_index().rename("MKT_SHARE").reset_index() - next(result_df.to_pandas_batches(max_results=1500)) diff --git a/third_party/bigframes_vendored/tpch/queries/q9.py b/third_party/bigframes_vendored/tpch/queries/q9.py deleted file mode 100644 index 5c9ca1e9c30..00000000000 --- a/third_party/bigframes_vendored/tpch/queries/q9.py +++ /dev/null @@ -1,72 +0,0 @@ -# Contains code from https://github.com/pola-rs/tpch/blob/main/queries/polars/q9.py - -import typing - -import bigframes -import bigframes.pandas as bpd - - -def q(project_id: str, dataset_id: str, session: bigframes.Session): - lineitem = session.read_gbq( - f"{project_id}.{dataset_id}.LINEITEM", - index_col=bigframes.enums.DefaultIndexKind.NULL, - ) - nation = session.read_gbq( - f"{project_id}.{dataset_id}.NATION", - index_col=bigframes.enums.DefaultIndexKind.NULL, - ) - orders = session.read_gbq( - f"{project_id}.{dataset_id}.ORDERS", - index_col=bigframes.enums.DefaultIndexKind.NULL, - ) - part = session.read_gbq( - f"{project_id}.{dataset_id}.PART", - index_col=bigframes.enums.DefaultIndexKind.NULL, - ) - partsupp = session.read_gbq( - f"{project_id}.{dataset_id}.PARTSUPP", - index_col=bigframes.enums.DefaultIndexKind.NULL, - ) - supplier = session.read_gbq( - f"{project_id}.{dataset_id}.SUPPLIER", - index_col=bigframes.enums.DefaultIndexKind.NULL, - ) - - q_final = ( - part.merge( - lineitem, - left_on="P_PARTKEY", - right_on="L_PARTKEY", - ) - .merge( - partsupp, - left_on=["L_SUPPKEY", "L_PARTKEY"], - right_on=["PS_SUPPKEY", "PS_PARTKEY"], - ) - .merge(supplier, left_on="L_SUPPKEY", right_on="S_SUPPKEY") - .merge(orders, left_on="L_ORDERKEY", right_on="O_ORDERKEY") - .merge(nation, left_on="S_NATIONKEY", right_on="N_NATIONKEY") - ) - - q_final = q_final[q_final["P_NAME"].str.contains("green")] - - q_final = q_final.rename(columns={"N_NAME": "NATION"}) - q_final["O_YEAR"] = q_final["O_ORDERDATE"].dt.year - q_final["AMOUNT"] = ( - q_final["L_EXTENDEDPRICE"] * (1 - q_final["L_DISCOUNT"]) - - q_final["PS_SUPPLYCOST"] * q_final["L_QUANTITY"] - ) - - q_final = q_final[["NATION", "O_YEAR", "AMOUNT"]] - - q_final = q_final.groupby(["NATION", "O_YEAR"], as_index=False).agg( - SUM_PROFIT=bpd.NamedAgg(column="AMOUNT", aggfunc="sum") - ) - - q_final["SUM_PROFIT"] = q_final["SUM_PROFIT"].round(2) - - q_final = typing.cast(bpd.DataFrame, q_final).sort_values( - ["NATION", "O_YEAR"], ascending=[True, False] - ) - - next(q_final.to_pandas_batches(max_results=1500)) diff --git a/third_party/bigframes_vendored/tpch/sql_queries/q1.sql b/third_party/bigframes_vendored/tpch/sql_queries/q1.sql deleted file mode 100644 index c359614583b..00000000000 --- a/third_party/bigframes_vendored/tpch/sql_queries/q1.sql +++ /dev/null @@ -1,21 +0,0 @@ -select - l_returnflag, - l_linestatus, - sum(l_quantity) as sum_qty, - sum(l_extendedprice) as sum_base_price, - sum(l_extendedprice * (1 - l_discount)) as sum_disc_price, - sum(l_extendedprice * (1 - l_discount) * (1 + l_tax)) as sum_charge, - avg(l_quantity) as avg_qty, - avg(l_extendedprice) as avg_price, - avg(l_discount) as avg_disc, - count(*) as count_order -from - {line_item_ds} -where - l_shipdate <= '1998-09-02' -group by - l_returnflag, - l_linestatus -order by - l_returnflag, - l_linestatus diff --git a/third_party/bigframes_vendored/tpch/sql_queries/q10.sql b/third_party/bigframes_vendored/tpch/sql_queries/q10.sql deleted file mode 100644 index c07aa0b4c9e..00000000000 --- a/third_party/bigframes_vendored/tpch/sql_queries/q10.sql +++ /dev/null @@ -1,32 +0,0 @@ -select - c_custkey, - c_name, - round(sum(l_extendedprice * (1 - l_discount)), 2) as revenue, - c_acctbal, - n_name, - c_address, - c_phone, - c_comment -from - {customer_ds}, - {orders_ds}, - {line_item_ds}, - {nation_ds} -where - c_custkey = o_custkey - and l_orderkey = o_orderkey - and o_orderdate >= date '1993-10-01' - and o_orderdate < date '1993-10-01' + interval '3' month - and l_returnflag = 'R' - and c_nationkey = n_nationkey -group by - c_custkey, - c_name, - c_acctbal, - c_phone, - n_name, - c_address, - c_comment -order by - revenue desc -limit 20 diff --git a/third_party/bigframes_vendored/tpch/sql_queries/q11.sql b/third_party/bigframes_vendored/tpch/sql_queries/q11.sql deleted file mode 100644 index 08c45604233..00000000000 --- a/third_party/bigframes_vendored/tpch/sql_queries/q11.sql +++ /dev/null @@ -1,27 +0,0 @@ -select - ps_partkey, - round(sum(ps_supplycost * ps_availqty), 2) as value -from - {part_supp_ds}, - {supplier_ds}, - {nation_ds} -where - ps_suppkey = s_suppkey - and s_nationkey = n_nationkey - and n_name = 'GERMANY' -group by - ps_partkey having - sum(ps_supplycost * ps_availqty) > ( - select - sum(ps_supplycost * ps_availqty) * 0.0001 - from - {part_supp_ds}, - {supplier_ds}, - {nation_ds} - where - ps_suppkey = s_suppkey - and s_nationkey = n_nationkey - and n_name = 'GERMANY' - ) - order by - value desc diff --git a/third_party/bigframes_vendored/tpch/sql_queries/q12.sql b/third_party/bigframes_vendored/tpch/sql_queries/q12.sql deleted file mode 100644 index cb97f1fb3c2..00000000000 --- a/third_party/bigframes_vendored/tpch/sql_queries/q12.sql +++ /dev/null @@ -1,28 +0,0 @@ -select - l_shipmode, - sum(case - when o_orderpriority = '1-URGENT' - or o_orderpriority = '2-HIGH' - then 1 - else 0 - end) as high_line_count, - sum(case - when o_orderpriority <> '1-URGENT' - and o_orderpriority <> '2-HIGH' - then 1 - else 0 - end) as low_line_count -from - {orders_ds}, - {line_item_ds} -where - o_orderkey = l_orderkey - and l_shipmode in ('MAIL', 'SHIP') - and l_commitdate < l_receiptdate - and l_shipdate < l_commitdate - and l_receiptdate >= date '1994-01-01' - and l_receiptdate < date '1994-01-01' + interval '1' year -group by - l_shipmode -order by - l_shipmode diff --git a/third_party/bigframes_vendored/tpch/sql_queries/q13.sql b/third_party/bigframes_vendored/tpch/sql_queries/q13.sql deleted file mode 100644 index d1616f53609..00000000000 --- a/third_party/bigframes_vendored/tpch/sql_queries/q13.sql +++ /dev/null @@ -1,18 +0,0 @@ -SELECT - c_count, COUNT(*) AS custdist -FROM ( - SELECT - c_custkey, - COUNT(o_orderkey) AS c_count - FROM - {customer_ds} LEFT OUTER JOIN {orders_ds} ON - c_custkey = o_custkey - AND o_comment NOT LIKE '%special%requests%' - GROUP BY - c_custkey -) AS c_orders -GROUP BY - c_count -ORDER BY - custdist DESC, - c_count DESC diff --git a/third_party/bigframes_vendored/tpch/sql_queries/q14.sql b/third_party/bigframes_vendored/tpch/sql_queries/q14.sql deleted file mode 100644 index 1620ab97620..00000000000 --- a/third_party/bigframes_vendored/tpch/sql_queries/q14.sql +++ /dev/null @@ -1,13 +0,0 @@ -select - round(100.00 * sum(case - when p_type like 'PROMO%' - then l_extendedprice * (1 - l_discount) - else 0 - end) / sum(l_extendedprice * (1 - l_discount)), 2) as promo_revenue -from - {line_item_ds}, - {part_ds} -where - l_partkey = p_partkey - and l_shipdate >= date '1995-09-01' - and l_shipdate < date '1995-09-01' + interval '1' month diff --git a/third_party/bigframes_vendored/tpch/sql_queries/q15.sql b/third_party/bigframes_vendored/tpch/sql_queries/q15.sql deleted file mode 100644 index cbf77827bca..00000000000 --- a/third_party/bigframes_vendored/tpch/sql_queries/q15.sql +++ /dev/null @@ -1,26 +0,0 @@ -WITH revenue AS ( - SELECT - l_suppkey AS supplier_no, - SUM(l_extendedprice * (1 - l_discount)) AS total_revenue - FROM - {line_item_ds} - WHERE - l_shipdate >= DATE '1996-01-01' - AND l_shipdate < DATE '1996-01-01' + INTERVAL '3' month - GROUP BY - l_suppkey -) -SELECT - s.s_suppkey, - s.s_name, - s.s_address, - s.s_phone, - r.total_revenue -FROM - {supplier_ds} s -JOIN - revenue r ON s.s_suppkey = r.supplier_no -WHERE - r.total_revenue = (SELECT MAX(total_revenue) FROM revenue) -ORDER BY - s.s_suppkey; diff --git a/third_party/bigframes_vendored/tpch/sql_queries/q16.sql b/third_party/bigframes_vendored/tpch/sql_queries/q16.sql deleted file mode 100644 index 193c8e462d8..00000000000 --- a/third_party/bigframes_vendored/tpch/sql_queries/q16.sql +++ /dev/null @@ -1,30 +0,0 @@ -select - p_brand, - p_type, - p_size, - count(distinct ps_suppkey) as supplier_cnt -from - {part_supp_ds}, - {part_ds} -where - p_partkey = ps_partkey - and p_brand <> 'Brand#45' - and p_type not like 'MEDIUM POLISHED%' - and p_size in (49, 14, 23, 45, 19, 3, 36, 9) - and ps_suppkey not in ( - select - s_suppkey - from - {supplier_ds} - where - s_comment like '%Customer%Complaints%' - ) -group by - p_brand, - p_type, - p_size -order by - supplier_cnt desc, - p_brand, - p_type, - p_size diff --git a/third_party/bigframes_vendored/tpch/sql_queries/q17.sql b/third_party/bigframes_vendored/tpch/sql_queries/q17.sql deleted file mode 100644 index 390ecdd33bc..00000000000 --- a/third_party/bigframes_vendored/tpch/sql_queries/q17.sql +++ /dev/null @@ -1,17 +0,0 @@ -select - round(sum(l_extendedprice) / 7.0, 2) as avg_yearly -from - {line_item_ds}, - {part_ds} -where - p_partkey = l_partkey - and p_brand = 'Brand#23' - and p_container = 'MED BOX' - and l_quantity < ( - select - 0.2 * avg(l_quantity) - from - {line_item_ds} - where - l_partkey = p_partkey - ) diff --git a/third_party/bigframes_vendored/tpch/sql_queries/q18.sql b/third_party/bigframes_vendored/tpch/sql_queries/q18.sql deleted file mode 100644 index 4a98abafb97..00000000000 --- a/third_party/bigframes_vendored/tpch/sql_queries/q18.sql +++ /dev/null @@ -1,33 +0,0 @@ -select - c_name, - c_custkey, - o_orderkey, - o_orderdate as o_orderdat, - o_totalprice, - sum(l_quantity) as col6 -from - {customer_ds}, - {orders_ds}, - {line_item_ds} -where - o_orderkey in ( - select - l_orderkey - from - {line_item_ds} - group by - l_orderkey having - sum(l_quantity) > 300 - ) - and c_custkey = o_custkey - and o_orderkey = l_orderkey -group by - c_name, - c_custkey, - o_orderkey, - o_orderdate, - o_totalprice -order by - o_totalprice desc, - o_orderdate -limit 100 diff --git a/third_party/bigframes_vendored/tpch/sql_queries/q19.sql b/third_party/bigframes_vendored/tpch/sql_queries/q19.sql deleted file mode 100644 index 30b41ff3fff..00000000000 --- a/third_party/bigframes_vendored/tpch/sql_queries/q19.sql +++ /dev/null @@ -1,35 +0,0 @@ -select - round(sum(l_extendedprice* (1 - l_discount)), 2) as revenue -from - {line_item_ds}, - {part_ds} -where - ( - p_partkey = l_partkey - and p_brand = 'Brand#12' - and p_container in ('SM CASE', 'SM BOX', 'SM PACK', 'SM PKG') - and l_quantity >= 1 and l_quantity <= 1 + 10 - and p_size between 1 and 5 - and l_shipmode in ('AIR', 'AIR REG') - and l_shipinstruct = 'DELIVER IN PERSON' - ) - or - ( - p_partkey = l_partkey - and p_brand = 'Brand#23' - and p_container in ('MED BAG', 'MED BOX', 'MED PKG', 'MED PACK') - and l_quantity >= 10 and l_quantity <= 20 - and p_size between 1 and 10 - and l_shipmode in ('AIR', 'AIR REG') - and l_shipinstruct = 'DELIVER IN PERSON' - ) - or - ( - p_partkey = l_partkey - and p_brand = 'Brand#34' - and p_container in ('LG CASE', 'LG BOX', 'LG PACK', 'LG PKG') - and l_quantity >= 20 and l_quantity <= 30 - and p_size between 1 and 15 - and l_shipmode in ('AIR', 'AIR REG') - and l_shipinstruct = 'DELIVER IN PERSON' - ) diff --git a/third_party/bigframes_vendored/tpch/sql_queries/q2.sql b/third_party/bigframes_vendored/tpch/sql_queries/q2.sql deleted file mode 100644 index 082e1e7f536..00000000000 --- a/third_party/bigframes_vendored/tpch/sql_queries/q2.sql +++ /dev/null @@ -1,44 +0,0 @@ -select - s_acctbal, - s_name, - n_name, - p_partkey, - p_mfgr, - s_address, - s_phone, - s_comment -from - {part_ds}, - {supplier_ds}, - {part_supp_ds}, - {nation_ds}, - {region_ds} -where - p_partkey = ps_partkey - and s_suppkey = ps_suppkey - and p_size = 15 - and p_type like '%BRASS' - and s_nationkey = n_nationkey - and n_regionkey = r_regionkey - and r_name = 'EUROPE' - and ps_supplycost = ( - select - min(ps_supplycost) - from - {part_supp_ds}, - {supplier_ds}, - {nation_ds}, - {region_ds} - where - p_partkey = ps_partkey - and s_suppkey = ps_suppkey - and s_nationkey = n_nationkey - and n_regionkey = r_regionkey - and r_name = 'EUROPE' - ) -order by - s_acctbal desc, - n_name, - s_name, - p_partkey -limit 100 diff --git a/third_party/bigframes_vendored/tpch/sql_queries/q20.sql b/third_party/bigframes_vendored/tpch/sql_queries/q20.sql deleted file mode 100644 index 03348e82b89..00000000000 --- a/third_party/bigframes_vendored/tpch/sql_queries/q20.sql +++ /dev/null @@ -1,37 +0,0 @@ -select - s_name, - s_address -from - {supplier_ds}, - {nation_ds} -where - s_suppkey in ( - select - ps_suppkey - from - {part_supp_ds} - where - ps_partkey in ( - select - p_partkey - from - {part_ds} - where - p_name like 'forest%' - ) - and ps_availqty > ( - select - 0.5 * sum(l_quantity) - from - {line_item_ds} - where - l_partkey = ps_partkey - and l_suppkey = ps_suppkey - and l_shipdate >= date '1994-01-01' - and l_shipdate < date '1994-01-01' + interval '1' year - ) - ) - and s_nationkey = n_nationkey - and n_name = 'CANADA' -order by - s_name diff --git a/third_party/bigframes_vendored/tpch/sql_queries/q21.sql b/third_party/bigframes_vendored/tpch/sql_queries/q21.sql deleted file mode 100644 index 444d1274696..00000000000 --- a/third_party/bigframes_vendored/tpch/sql_queries/q21.sql +++ /dev/null @@ -1,40 +0,0 @@ -select - s_name, - count(*) as numwait -from - {supplier_ds}, - {line_item_ds} l1, - {orders_ds}, - {nation_ds} -where - s_suppkey = l1.l_suppkey - and o_orderkey = l1.l_orderkey - and o_orderstatus = 'F' - and l1.l_receiptdate > l1.l_commitdate - and exists ( - select - * - from - {line_item_ds} l2 - where - l2.l_orderkey = l1.l_orderkey - and l2.l_suppkey <> l1.l_suppkey - ) - and not exists ( - select - * - from - {line_item_ds} l3 - where - l3.l_orderkey = l1.l_orderkey - and l3.l_suppkey <> l1.l_suppkey - and l3.l_receiptdate > l3.l_commitdate - ) - and s_nationkey = n_nationkey - and n_name = 'SAUDI ARABIA' -group by - s_name -order by - numwait desc, - s_name -limit 100 diff --git a/third_party/bigframes_vendored/tpch/sql_queries/q22.sql b/third_party/bigframes_vendored/tpch/sql_queries/q22.sql deleted file mode 100644 index a1e1b2a2533..00000000000 --- a/third_party/bigframes_vendored/tpch/sql_queries/q22.sql +++ /dev/null @@ -1,36 +0,0 @@ -select - cntrycode, - count(*) as numcust, - sum(c_acctbal) as totacctbal -from ( - select - SUBSTR(c_phone, 1, 2) AS cntrycode, - c_acctbal - from - {customer_ds} - where - SUBSTR(c_phone, 1, 2) in - ('13', '31', '23', '29', '30', '18', '17') - and c_acctbal > ( - select - avg(c_acctbal) - from - {customer_ds} - where - c_acctbal > 0.00 - and SUBSTR(c_phone, 1, 2) in - ('13', '31', '23', '29', '30', '18', '17') - ) - and not exists ( - select - * - from - {orders_ds} - where - o_custkey = c_custkey - ) - ) as custsale -group by - cntrycode -order by - cntrycode diff --git a/third_party/bigframes_vendored/tpch/sql_queries/q3.sql b/third_party/bigframes_vendored/tpch/sql_queries/q3.sql deleted file mode 100644 index 69a40b8ef79..00000000000 --- a/third_party/bigframes_vendored/tpch/sql_queries/q3.sql +++ /dev/null @@ -1,23 +0,0 @@ -select - l_orderkey, - sum(l_extendedprice * (1 - l_discount)) as revenue, - o_orderdate, - o_shippriority -from - {customer_ds}, - {orders_ds}, - {line_item_ds} -where - c_mktsegment = 'BUILDING' - and c_custkey = o_custkey - and l_orderkey = o_orderkey - and o_orderdate < '1995-03-15' - and l_shipdate > '1995-03-15' -group by - l_orderkey, - o_orderdate, - o_shippriority -order by - revenue desc, - o_orderdate -limit 10 diff --git a/third_party/bigframes_vendored/tpch/sql_queries/q4.sql b/third_party/bigframes_vendored/tpch/sql_queries/q4.sql deleted file mode 100644 index 57204e8d709..00000000000 --- a/third_party/bigframes_vendored/tpch/sql_queries/q4.sql +++ /dev/null @@ -1,21 +0,0 @@ -select - o_orderpriority, - count(*) as order_count -from - {orders_ds} -where - o_orderdate >= date '1993-07-01' - and o_orderdate < date '1993-10-01' - and exists ( - select - * - from - {line_item_ds} - where - l_orderkey = o_orderkey - and l_commitdate < l_receiptdate - ) -group by - o_orderpriority -order by - o_orderpriority diff --git a/third_party/bigframes_vendored/tpch/sql_queries/q5.sql b/third_party/bigframes_vendored/tpch/sql_queries/q5.sql deleted file mode 100644 index 78dbb96ffa6..00000000000 --- a/third_party/bigframes_vendored/tpch/sql_queries/q5.sql +++ /dev/null @@ -1,24 +0,0 @@ -select - n_name, - sum(l_extendedprice * (1 - l_discount)) as revenue -from - {customer_ds}, - {orders_ds}, - {line_item_ds}, - {supplier_ds}, - {nation_ds}, - {region_ds} -where - c_custkey = o_custkey - and l_orderkey = o_orderkey - and l_suppkey = s_suppkey - and c_nationkey = s_nationkey - and s_nationkey = n_nationkey - and n_regionkey = r_regionkey - and r_name = 'ASIA' - and o_orderdate >= date '1994-01-01' - and o_orderdate < date '1995-01-01' -group by - n_name -order by - revenue desc diff --git a/third_party/bigframes_vendored/tpch/sql_queries/q6.sql b/third_party/bigframes_vendored/tpch/sql_queries/q6.sql deleted file mode 100644 index 0ea158332e3..00000000000 --- a/third_party/bigframes_vendored/tpch/sql_queries/q6.sql +++ /dev/null @@ -1,9 +0,0 @@ -select - sum(l_extendedprice * l_discount) as revenue -from - {line_item_ds} -where - l_shipdate >= date '1994-01-01' - and l_shipdate < date '1994-01-01' + interval '1' year - and l_discount between .05 and .07 - and l_quantity < 24 diff --git a/third_party/bigframes_vendored/tpch/sql_queries/q7.sql b/third_party/bigframes_vendored/tpch/sql_queries/q7.sql deleted file mode 100644 index 002e89e4a09..00000000000 --- a/third_party/bigframes_vendored/tpch/sql_queries/q7.sql +++ /dev/null @@ -1,50 +0,0 @@ -select - supp_nation, - cust_nation, - l_year, - sum(volume) as revenue -from - ( - select - n1.n_name as supp_nation, - n2.n_name as cust_nation, - EXTRACT( - YEAR - FROM - l_shipdate - ) as l_year, - l_extendedprice * (1 - l_discount) as volume - from - {supplier_ds}, - {line_item_ds}, - {orders_ds}, - {customer_ds}, - {nation_ds} n1, - {nation_ds} n2 - where - s_suppkey = l_suppkey - and o_orderkey = l_orderkey - and c_custkey = o_custkey - and s_nationkey = n1.n_nationkey - and c_nationkey = n2.n_nationkey - and ( - ( - n1.n_name = 'FRANCE' - and n2.n_name = 'GERMANY' - ) - or ( - n1.n_name = 'GERMANY' - and n2.n_name = 'FRANCE' - ) - ) - and l_shipdate between date '1995-01-01' - and date '1996-12-31' - ) as shipping -group by - supp_nation, - cust_nation, - l_year -order by - supp_nation, - cust_nation, - l_year diff --git a/third_party/bigframes_vendored/tpch/sql_queries/q8.sql b/third_party/bigframes_vendored/tpch/sql_queries/q8.sql deleted file mode 100644 index d4d1ddd2759..00000000000 --- a/third_party/bigframes_vendored/tpch/sql_queries/q8.sql +++ /dev/null @@ -1,39 +0,0 @@ -select - o_year, - round( - sum(case - when nation = 'BRAZIL' then volume - else 0 - end) / sum(volume) - , 2) as mkt_share -from - ( - select - extract(year from o_orderdate) as o_year, - l_extendedprice * (1 - l_discount) as volume, - n2.n_name as nation - from - {part_ds}, - {supplier_ds}, - {line_item_ds}, - {orders_ds}, - {customer_ds}, - {nation_ds} n1, - {nation_ds} n2, - {region_ds} - where - p_partkey = l_partkey - and s_suppkey = l_suppkey - and l_orderkey = o_orderkey - and o_custkey = c_custkey - and c_nationkey = n1.n_nationkey - and n1.n_regionkey = r_regionkey - and r_name = 'AMERICA' - and s_nationkey = n2.n_nationkey - and o_orderdate between date '1995-01-01' and date '1996-12-31' - and p_type = 'ECONOMY ANODIZED STEEL' - ) as all_nations -group by - o_year -order by - o_year diff --git a/third_party/bigframes_vendored/tpch/sql_queries/q9.sql b/third_party/bigframes_vendored/tpch/sql_queries/q9.sql deleted file mode 100644 index fcc3e194008..00000000000 --- a/third_party/bigframes_vendored/tpch/sql_queries/q9.sql +++ /dev/null @@ -1,32 +0,0 @@ -select - nation, - o_year, - round(sum(amount), 2) as sum_profit -from - ( - select - n_name as nation, - EXTRACT(YEAR FROM o_orderdate) as o_year, - l_extendedprice * (1 - l_discount) - ps_supplycost * l_quantity as amount - from - {part_ds}, - {supplier_ds}, - {line_item_ds}, - {part_supp_ds}, - {orders_ds}, - {nation_ds} - where - s_suppkey = l_suppkey - and ps_suppkey = l_suppkey - and ps_partkey = l_partkey - and p_partkey = l_partkey - and o_orderkey = l_orderkey - and s_nationkey = n_nationkey - and p_name like '%green%' - ) as profit -group by - nation, - o_year -order by - nation, - o_year desc diff --git a/third_party/bigframes_vendored/version.py b/third_party/bigframes_vendored/version.py deleted file mode 100644 index db3e62ea976..00000000000 --- a/third_party/bigframes_vendored/version.py +++ /dev/null @@ -1,19 +0,0 @@ -# Copyright 2022 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. - -__version__ = "2.47.0" - -# {x-release-please-start-date} -__release_date__ = "2026-06-12" -# {x-release-please-end} diff --git a/third_party/bigframes_vendored/xgboost/sklearn.py b/third_party/bigframes_vendored/xgboost/sklearn.py index 60a22e83d07..b7b43b85a3e 100644 --- a/third_party/bigframes_vendored/xgboost/sklearn.py +++ b/third_party/bigframes_vendored/xgboost/sklearn.py @@ -1,4 +1,4 @@ -"""scikit-learn Wrapper interface for XGBoost.""" +"""Scikit-Learn Wrapper interface for XGBoost.""" from typing import Any @@ -18,7 +18,7 @@ def predict(self, X): Series or DataFrame of shape (n_samples, n_features). Samples. Returns: - bigframes.dataframe.DataFrame: DataFrame of shape (n_samples, n_input_columns + n_prediction_columns). Returns predicted values. + DataFrame of shape (n_samples,): Returns predicted values. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @@ -37,15 +37,8 @@ def fit(self, X, y): DataFrame of shape (n_samples,) or (n_samples, n_targets). Target values. Will be cast to X's dtype if necessary. - X_eval (bigframes.dataframe.DataFrame or bigframes.series.Series): - Series or DataFrame of shape (n_samples, n_features). Evaluation data. - - y_eval (bigframes.dataframe.DataFrame or bigframes.series.Series): - DataFrame of shape (n_samples,) or (n_samples, n_targets). - Evaluation target values. Will be cast to X_eval's dtype if necessary. - Returns: - XGBModel: Fitted estimator. + XGBModel: Fitted Estimator. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) @@ -62,7 +55,7 @@ class XGBRegressor(XGBModel, XGBRegressorBase): XGBoost regression model. Args: - n_estimators (Optional[int]): + num_parallel_tree (Optional[int]): Number of parallel trees constructed during each iteration. Default to 1. booster (Optional[str]): Specify which booster to use: gbtree or dart. Default to "gbtree". @@ -70,7 +63,7 @@ class XGBRegressor(XGBModel, XGBRegressorBase): Type of normalization algorithm for DART booster. Possible values: "TREE", "FOREST". Default to "TREE". tree_method (Optional[str]): Specify which tree method to use. Default to "auto". If this parameter is set to - default, XGBoost will choose the most conservative option available. Possible values: "exact", "approx", + default, XGBoost will choose the most conservative option available. Possible values: ""exact", "approx", "hist". min_child_weight (Optional[float]): Minimum sum of instance weight(hessian) needed in a child. Default to 1. @@ -91,12 +84,14 @@ class XGBRegressor(XGBModel, XGBRegressorBase): L1 regularization term on weights (xgb's alpha). Default to 0.0. reg_lambda (Optional[float]): L2 regularization term on weights (xgb's lambda). Default to 1.0. + early_stop (Optional[bool]): + Whether training should stop after the first iteration. Default to True. learning_rate (Optional[float]): Boosting learning rate (xgb's "eta"). Default to 0.3. max_iterations (Optional[int]): Maximum number of rounds for boosting. Default to 20. - tol (Optional[float]): - Minimum relative loss improvement necessary to continue training. Default to 0.01. + min_rel_progress (Optional[float]): + Minimum relative loss improvement necessary to continue training when early_stop is set to True. Default to 0.01. enable_global_explain (Optional[bool]): Whether to compute global explanations using explainable AI to evaluate global feature importance to the model. Default to False. xgboost_version (Optional[str]): @@ -109,7 +104,7 @@ class XGBClassifier(XGBModel, XGBClassifierMixIn, XGBClassifierBase): XGBoost classifier model. Args: - n_estimators (Optional[int]): + num_parallel_tree (Optional[int]): Number of parallel trees constructed during each iteration. Default to 1. booster (Optional[str]): Specify which booster to use: gbtree or dart. Default to "gbtree". @@ -117,7 +112,7 @@ class XGBClassifier(XGBModel, XGBClassifierMixIn, XGBClassifierBase): Type of normalization algorithm for DART booster. Possible values: "TREE", "FOREST". Default to "TREE". tree_method (Optional[str]): Specify which tree method to use. Default to "auto". If this parameter is set to - default, XGBoost will choose the most conservative option available. Possible values: "exact", "approx", + default, XGBoost will choose the most conservative option available. Possible values: ""exact", "approx", "hist". min_child_weight (Optional[float]): Minimum sum of instance weight(hessian) needed in a child. Default to 1. @@ -138,12 +133,14 @@ class XGBClassifier(XGBModel, XGBClassifierMixIn, XGBClassifierBase): L1 regularization term on weights (xgb's alpha). Default to 0.0. reg_lambda (Optional[float]): L2 regularization term on weights (xgb's lambda). Default to 1.0. + early_stop (Optional[bool]): + Whether training should stop after the first iteration. Default to True. learning_rate (Optional[float]): Boosting learning rate (xgb's "eta"). Default to 0.3. max_iterations (Optional[int]): Maximum number of rounds for boosting. Default to 20. - tol (Optional[float]): - Minimum relative loss improvement necessary to continue training. Default to 0.01. + min_rel_progress (Optional[float]): + Minimum relative loss improvement necessary to continue training when early_stop is set to True. Default to 0.01. enable_global_explain (Optional[bool]): Whether to compute global explanations using explainable AI to evaluate global feature importance to the model. Default to False. xgboost_version (Optional[str]): diff --git a/third_party/logo/colab-logo.png b/third_party/logo/colab-logo.png deleted file mode 100644 index 75740a2b6ae..00000000000 Binary files a/third_party/logo/colab-logo.png and /dev/null differ diff --git a/third_party/logo/github-logo.png b/third_party/logo/github-logo.png deleted file mode 100644 index 8b25551a979..00000000000 Binary files a/third_party/logo/github-logo.png and /dev/null differ diff --git a/third_party/sphinx/LICENSE.rst b/third_party/sphinx/LICENSE.rst deleted file mode 100644 index de3688cd2c6..00000000000 --- a/third_party/sphinx/LICENSE.rst +++ /dev/null @@ -1,31 +0,0 @@ -License for Sphinx -================== - -Unless otherwise indicated, all code in the Sphinx project is licenced under the -two clause BSD licence below. - -Copyright (c) 2007-2025 by the Sphinx team (see AUTHORS file). -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - -* Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - -* Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/third_party/sphinx/ext/autosummary/templates/autosummary/class.rst b/third_party/sphinx/ext/autosummary/templates/autosummary/class.rst deleted file mode 100644 index 6651591be64..00000000000 --- a/third_party/sphinx/ext/autosummary/templates/autosummary/class.rst +++ /dev/null @@ -1,42 +0,0 @@ -{{ fullname | escape | underline}} - -.. currentmodule:: {{ module }} - -{% set is_pandas = module.startswith("bigframes.pandas") or module.startswith("bigframes.geopandas") %} -{% set skip_inherited = is_pandas and not module.startswith("bigframes.pandas.typing.api") %} - -{% if is_pandas %} -.. autoclass:: {{ objname }} - :no-members: - - {% block attributes %} - {% if attributes %} - .. rubric:: {{ _('Attributes') }} - - .. autosummary:: - :toctree: - {% for item in attributes %} - {%- if not skip_inherited or not item in inherited_members%} - ~{{ name }}.{{ item }} - {%- endif %} - {%- endfor %} - {% endif %} - {% endblock %} - - {% block methods %} - {% if methods %} - .. rubric:: {{ _('Methods') }} - - .. autosummary:: - :toctree: - - {% for item in methods %} - {%- if not skip_inherited or not item in inherited_members%} - ~{{ name }}.{{ item }} - {%- endif %} - {%- endfor %} - {% endif %} - {% endblock %} -{% else %} -.. autoclass:: {{ objname }} -{% endif %} diff --git a/third_party/sphinx/ext/autosummary/templates/autosummary/module.rst b/third_party/sphinx/ext/autosummary/templates/autosummary/module.rst deleted file mode 100644 index 98d86d15230..00000000000 --- a/third_party/sphinx/ext/autosummary/templates/autosummary/module.rst +++ /dev/null @@ -1,57 +0,0 @@ -{{ fullname | escape | underline}} - -.. - Originally at - https://github.com/sphinx-doc/sphinx/blob/master/sphinx/ext/autosummary/templates/autosummary/module.rst - with modifications to support recursive generation from - https://github.com/sphinx-doc/sphinx/issues/7912 - -.. automodule:: {{ fullname }} - :no-members: - - {% block functions %} - {%- if functions %} - .. rubric:: {{ _('Functions') }} - - .. autosummary:: - :toctree: - {% for item in functions %} - {{ item }} - {%- endfor %} - {% endif %} - {%- endblock %} - - {%- block classes %} - {%- if classes %} - .. rubric:: {{ _('Classes') }} - - .. autosummary:: - :toctree: - {% for item in classes %}{% if item not in attributes %} - {{ item }} - {% endif %}{%- endfor %} - {% endif %} - {%- endblock %} - - {%- block exceptions %} - {%- if exceptions %} - .. rubric:: {{ _('Exceptions') }} - - .. autosummary:: - :toctree: - {% for item in exceptions %} - {{ item }} - {%- endfor %} - {% endif %} - {%- endblock %} - -{%- block attributes %} -{%- if attributes %} -.. rubric:: {{ _('Module Attributes') }} - -{% for item in attributes %} -.. autoattribute:: {{ fullname }}.{{ item }} - :no-index: -{% endfor %} -{% endif %} -{%- endblock %}